Go语言数组与切片的基本概念
Go语言中的数组是固定长度的相同类型元素序列,声明时需要指定长度和元素类型,长度属于数组类型的一部分。切片则是基于数组的抽象,是动态长度的序列,底层依赖数组实现,提供了更灵活的操作方式。

数组与切片的核心类型差异
1. 类型定义与长度特性
数组的长度在声明时就确定,且属于类型的一部分,比如[3]int和[4]int是完全不同的类型。切片的长度可以动态变化,类型定义时不需要指定长度,格式为[]T,其中T是元素类型。
package main
import "fmt"
func main() {
// 数组声明,长度固定为3
var arr [3]int = [3]int{1, 2, 3}
// 切片声明,无需指定长度
var slice []int = []int{1, 2, 3}
fmt.Printf("数组类型: %Tn", arr) // 输出 [3]int
fmt.Printf("切片类型: %Tn", slice) // 输出 []int
}
2. 底层结构与可变性
数组是值类型,直接存储元素数据,内存布局是连续的元素空间。切片是引用类型,底层结构包含指向底层数组的指针、切片长度和容量三个部分,修改切片的元素会影响底层数组的对应元素。
| 对比维度 | 数组 | 切片 |
|---|---|---|
| 类型属性 | 值类型 | 引用类型 |
| 长度特性 | 固定,属于类型一部分 | 动态可变 |
| 传递方式 | 传递时会复制整个数组 | 传递时仅复制切片头结构 |
| 底层依赖 | 无额外依赖,直接存储元素 | 依赖底层数组 |
3. 传递行为差异
数组作为函数参数传递时,会复制整个数组的所有元素,修改函数内的数组不会影响原数组。切片作为函数参数传递时,仅复制切片的指针、长度和容量三个字段,修改切片的元素会影响原底层数组的内容。
package main
import "fmt"
func modifyArr(a [3]int) {
a[0] = 100
}
func modifySlice(s []int) {
s[0] = 100
}
func main() {
arr := [3]int{1, 2, 3}
slice := []int{1, 2, 3}
modifyArr(arr)
modifySlice(slice)
fmt.Println(arr) // 输出 [1 2 3],原数组未修改
fmt.Println(slice) // 输出 [100 2 3],原切片元素被修改
}
数组与切片的高效使用建议
1. 合理选择使用场景
如果元素数量固定且不需要动态扩容,优先使用数组,比如存储坐标点、固定长度的配置项等场景。如果需要动态增减元素,或者元素数量不确定,优先使用切片,比如存储动态查询结果、临时数据集合等场景。
2. 切片预分配容量减少扩容开销
切片在追加元素时如果容量不足会触发扩容,扩容会申请新的底层数组并复制原有元素,带来额外性能开销。如果可以预估切片的最终长度,使用make函数预分配容量可以提升性能。
package main
import "fmt"
func main() {
// 预分配容量为10的切片,避免多次扩容
slice := make([]int, 0, 10)
for i := 0; i < 10; i++ {
slice = append(slice, i)
}
fmt.Println(slice)
}
3. 避免切片的不必要引用导致内存泄漏
切片引用大数组的一部分时,会导致整个底层数组无法被垃圾回收。如果只需要使用切片的部分元素,建议复制需要的元素到新的切片,释放对大数组的引用。
package main
import "fmt"
func main() {
bigArr := [10000]int{}
// 仅需要bigArr的前10个元素,复制为新切片避免引用整个大数组
smallSlice := make([]int, 10)
copy(smallSlice, bigArr[:10])
fmt.Println(smallSlice)
}
4. 数组传递优先使用指针避免复制开销
如果需要在函数中修改数组内容,或者数组长度较大不想产生复制开销,可以传递数组的指针,既可以减少内存复制,也可以实现对原数组的修改。
package main
import "fmt"
func modifyArrByPtr(a *[3]int) {
a[0] = 100
}
func main() {
arr := [3]int{1, 2, 3}
modifyArrByPtr(&arr)
fmt.Println(arr) // 输出 [100 2 3]
}