
Go语言中的数组(Array)
Go语言中的数组是一种具有固定长度的同类型元素序列。数组的长度在声明时确定,并且是其类型的一部分。这意味着[10]int和[20]int是两种完全不同的类型。
核心特性:
- 固定长度: 数组的长度不可变。
- 值类型: 数组是值类型。当一个数组被赋值给另一个数组变量,或者作为参数传递给函数时,Go语言会创建一个该数组的完整副本。这意味着对副本的任何修改都不会影响原始数组。
- 长度是类型的一部分: 例如,var a [5]int声明了一个包含5个整数的数组。
示例:
package main
import "fmt"
func modifyArray(arr [3]int) {
arr[0] = 99 // 修改的是副本
fmt.Println("函数内修改后的数组副本:", arr)
}
func main() {
var arr1 [3]int = [3]int{1, 2, 3}
fmt.Println("原始数组 arr1:", arr1)
modifyArray(arr1) // 传递的是 arr1 的副本
fmt.Println("函数调用后原始数组 arr1:", arr1) // 原始数组未被修改
var arr2 [3]int
arr2 = arr1 // 数组赋值是全量复制
arr2[0] = 100
fmt.Println("arr1 赋值给 arr2 后 arr1:", arr1)
fmt.Println("arr2:", arr2)
}输出:
立即学习“go语言免费学习笔记(深入)”;
原始数组 arr1: [1 2 3] 函数内修改后的数组副本: [99 2 3] 函数调用后原始数组 arr1: [1 2 3] arr1 赋值给 arr2 后 arr1: [1 2 3] arr2: [100 2 3]
Go语言中的切片(Slice)
切片是Go语言中一种更常用、更灵活的数据结构。它建立在数组之上,提供了一种动态长度的视图,可以引用底层数组的一部分或全部。切片本身并不是数组,它是一个结构体,包含三个字段:
- 指针(Pointer): 指向底层数组的起始位置。
- 长度(Length): 切片中元素的数量。
- 容量(Capacity): 从切片起始位置到底层数组末尾的元素数量。
核心特性:
- 动态长度: 切片的长度可以动态变化(通过append等操作)。
- 引用类型: 切片是引用类型。当一个切片被赋值给另一个切片变量,或者作为参数传递给函数时,Go语言会创建一个该切片 头信息 的副本(即指针、长度、容量)。由于指针仍然指向同一个底层数组,因此通过副本对底层数组元素的修改会影响到原始切片。
- 切片字面量: 声明切片字面量与数组字面量非常相似,但省略了元素计数。例如,[]int{1, 2, 3}是一个切片字面量,而[3]int{1, 2, 3}是一个数组字面量。
示例:
package main
import "fmt"
func modifySlice(s []int) {
s[0] = 99 // 修改的是底层数组
fmt.Println("函数内修改后的切片:", s)
}
func main() {
var s1 []int = []int{1, 2, 3} // 这是一个切片字面量
fmt.Println("原始切片 s1:", s1)
modifySlice(s1) // 传递的是切片头信息的副本,但指针指向同一底层数组
fmt.Println("函数调用后原始切片 s1:", s1) // 原始切片被修改
var s2 []int
s2 = s1 // 切片赋值是头信息复制,共享底层数组
s2[0] = 100
fmt.Println("s1 赋值给 s2 后 s1:", s1)
fmt.Println("s2:", s2)
// 验证切片字面量与数组字面量的区别
// var arr3 [3]int = {1, 2, 3} // 编译错误,需要完整声明
var arr3 = [3]int{1, 2, 3} // 数组字面量
fmt.Printf("arr3 类型: %T, 值: %v\n", arr3, arr3)
var slc3 = []int{1, 2, 3} // 切片字面量
fmt.Printf("slc3 类型: %T, 值: %v\n", slc3, slc3)
}输出:
立即学习“go语言免费学习笔记(深入)”;
原始切片 s1: [1 2 3] 函数内修改后的切片: [99 2 3] 函数调用后原始切片 s1: [99 2 3] s1 赋值给 s2 后 s1: [100 2 3] s2: [100 2 3] arr3 类型: [3]int, 值: [1 2 3] slc3 类型: []int, 值: [1 2 3]
sort.Ints函数与切片的行为解析
现在,我们来解决最初的困惑:为什么sort.Ints函数能够修改传递给它的变量?
问题中的代码片段:
var av = []int{1,5,2,3,7}
fmt.Println(av)
sort.Ints(av)
fmt.Println(av)关键点在于: var av = []int{1,5,2,3,7} 声明的 av 是一个切片,而不是一个数组。虽然它的字面量形式与数组字面量相似,但由于缺少了长度的指定(例如[5]int),它被Go编译器识别为切片字面量。
sort.Ints函数的签名如下:
func Ints(a []int)
这明确表示sort.Ints函数接收一个[]int类型的参数,即一个整数切片。
当av(一个切片)被传递给sort.Ints时,Go语言会按照值传递的规则,复制av的切片头信息(指针、长度、容量)。这个复制的头信息中的指针仍然指向av底层数组的相同内存位置。sort.Ints函数通过这个复制的指针,直接操作并修改了底层数组的元素顺序。因此,当函数返回后,av所引用的底层数组内容已经被排序,所以fmt.Println(av)会输出排序后的结果。
如果尝试将一个真正的数组传递给sort.Ints,会发生什么?
package main
import (
"fmt"
"sort"
)
func main() {
var fixedArray = [5]int{1, 5, 2, 3, 7}
fmt.Println("原始数组:", fixedArray)
// sort.Ints(fixedArray) // 编译错误: cannot use fixedArray (type [5]int) as type []int in argument to sort.Ints
// 如果要对数组进行排序,需要先将其转换为切片
sort.Ints(fixedArray[:]) // 将数组转换为切片,然后传递
fmt.Println("排序后的数组 (通过切片操作):", fixedArray)
}编译错误信息(如果直接传递数组):
cannot use fixedArray (type [5]int) as type []int in argument to sort.Ints
这进一步证明了sort.Ints函数严格要求传入一个切片。通过fixedArray[:]这种切片表达式,我们可以将一个数组转换为一个引用该数组全部元素的切片,然后将其传递给sort.Ints。此时,sort.Ints仍然是修改底层数组,因此原数组在函数返回后也会被修改。
总结与注意事项
- 数组是值类型,长度固定。 传递数组或赋值数组会创建完整副本。
- 切片是引用类型,长度可变。 传递切片或赋值切片会复制其头信息(指针、长度、容量),但它们共享同一个底层数组。对切片元素的修改会影响所有引用该底层数组的切片。
- 切片字面量与数组字面量: 缺少长度指定的是切片字面量([]int{...}),指定了长度的是数组字面量([N]int{...})。
- 函数参数类型: 仔细检查函数签名,了解它期望接收的是数组还是切片。像sort.Ints这样的函数通常接受切片,因为它提供了更大的灵活性和效率。
- 性能考量: 传递大型数组会涉及大量数据复制,可能影响性能。而传递切片只需要复制一个小的头信息,效率更高。这也是Go语言中切片比数组更常用的原因之一。
理解Go语言中数组和切片的这些核心差异,对于编写高效、正确且符合预期的Go程序至关重要。开发者应根据具体需求,合理选择和使用这两种数据类型。










