在 Go 语言中,数组(Array)和切片(Slice)是两种重要的数据结构,但它们在设计、使用场景和特性上有显著区别。以下是它们的详细对比:
1. 定义与大小
特性 | 数组 | 切片 |
---|---|---|
大小 | 固定长度,声明时必须指定长度(如 [5]int )。 | 动态长度,声明时无需指定长度(如 []int )。 |
类型组成 | 类型包含长度信息,例如 [3]int 和 [4]int 是不同类型的数组。 | 类型不包含长度信息,所有 []int 类型的切片都是同一类型。 |
示例 | var arr [5]int | slice := []int{1, 2, 3} |
2. 内存分配
特性 | 数组 | 切片 |
---|---|---|
存储方式 | 数组是连续的内存块,存储所有元素。 | 切片本身是一个结构体,包含三个字段:指向底层数组的指针、长度(len )、容量(cap )。 |
传递方式 | 传递数组时会复制整个数组(值类型)。 | 传递切片时只复制结构体(指针、长度、容量),不复制底层数组(引用类型)。 |
内存效率 | 适合小规模数据,但大数组传递会消耗较多内存。 | 更高效,适合处理动态数据,节省内存。 |
3. 灵活性
特性 | 数组 | 切片 |
---|---|---|
长度修改 | 长度不可变,无法直接扩容或缩容。 | 可通过 append 动态扩容,容量不足时会重新分配底层数组。 |
切片操作 | 不支持切片操作(如 arr[1:3] 会生成新的数组)。 | 支持切片操作(如 slice[1:3] 会生成新的切片,共享底层数组)。 |
初始化方式 | 必须指定长度,例如 var arr [3]int = [3]int{1, 2, 3} 。 | 可通过 make 或字面量初始化,例如 slice := make([]int, 3) 。 |
4. 底层实现
-
数组
- 数组是值类型,直接存储元素。
- 例如,
[5]int{1, 2, 3, 4, 5}
是一块连续的内存,存储了 5 个整数。
-
切片
- 切片是引用类型,其结构体包含:
- 指针:指向底层数组的第一个元素。
- 长度(
len
):当前切片包含的元素数量。 - 容量(
cap
):从底层数组第一个元素到末尾的元素数量。
- 例如,
slice := arr[1:3]
会创建一个切片,指向arr[1]
,长度为 2,容量为len(arr) - 1
。
- 切片是引用类型,其结构体包含:
5. 使用场景
场景 | 推荐使用 |
---|---|
固定大小数据 | 数组更适合已知且固定大小的数据集合(如固定长度的传感器读数)。 |
动态数据 | 切片更适合需要动态调整大小的数据集合(如用户输入、动态队列)。 |
性能敏感场景 | 小规模数据使用数组(避免切片的间接寻址开销);大规模数据使用切片(避免复制数组)。 |
6. 示例代码
数组
// 固定长度的数组
var arr [3]int = [3]int{1, 2, 3}
fmt.Println(arr) // [1 2 3]
// 传递数组会复制整个数组
func modifyArray(arr [3]int) {
arr[0] = 100
}
modifyArray(arr)
fmt.Println(arr) // [1 2 3](原数组未被修改)
切片
// 动态长度的切片
slice := []int{1, 2, 3}
fmt.Println(slice) // [1 2 3]
// 传递切片共享底层数组
func modifySlice(slice []int) {
slice[0] = 100
}
modifySlice(slice)
fmt.Println(slice) // [100 2 3](原切片被修改)
// 动态扩容
slice = append(slice, 4, 5)
fmt.Println(slice) // [100 2 3 4 5]
7. 常见问题与注意事项
-
切片扩容
- 当切片容量不足时,
append
会重新分配底层数组,并将原数据复制到新数组中。 - 新容量通常是原容量的 2 倍(当原容量小于 1024 时),超过 1024 后按 1.25 倍增长。
- 当切片容量不足时,
-
切片共享底层数组的风险
- 多个切片可能共享同一个底层数组,修改其中一个切片可能影响其他切片。
- 示例:
arr := [5]int{1, 2, 3, 4, 5} s1 := arr[1:3] // [2, 3] s2 := arr[2:4] // [3, 4] s1[0] = 100 // 修改 s1 的第一个元素 fmt.Println(s2) // [100, 4](s2 的第一个元素也被修改)
-
空切片与空数组
- 空数组(
[0]int{}
)和空切片([]int{}
)是不同的,空切片指向nil
,但可以动态添加元素。
- 空数组(
总结
特性 | 数组 | 切片 |
---|---|---|
长度 | 固定 | 动态 |
类型 | 类型包含长度 | 类型不包含长度 |
内存分配 | 连续存储所有元素 | 仅存储结构体(指针、长度、容量) |
传递方式 | 值传递(复制整个数组) | 引用传递(共享底层数组) |
适用场景 | 固定大小的数据 | 动态调整大小的数据 |
最佳实践:
- 优先使用切片,因为其灵活性和高效性。
- 仅在需要严格固定大小或性能敏感的小规模数据时使用数组。