1. 基本概念
Slice 是 Go 语言中动态大小的数组,是对数组的引用。
Slice 与数组的区别:
- 数组的长度固定,创建时就已确定;而 slice 是动态的,可以根据需要自动扩展。
声明和初始化 slice:
- var s []int //未初始化的slice
- s := []int{1, 2, 3, 4, 5} //字面量初始化slice
- s := make([]int, 5) //make创建silce,长度和容量相同
- s := make([]int, 5, 10) //长度和容量不同
- arr := [5]int{1, 2, 3, 4, 5} //定义数组,从数组中取得silce
s := arr[1:4]
- var s []int // 零值 slice,通过append初始化slice
s = append(s, 1, 2, 3)
- s := [][]int{//多维slice
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
2. 底层原理
Slice 本质上是一个结构体,包含三个部分:指向底层数组的指针(pointer)、长度(len)、容量(cap)。
动态扩容当 slice 的容量不够时,Go 会分配一个更大的底层数组,并将现有的元素复制到新的数组上。
3. 容量与长度
获取长度和容量:
- len(slice) 获取长度,使用 cap(slice) 获取容量。
增加或减少长度: - 通过 append() 函数向 slice 添加元素会自动增加长度;减少长度可以通过重新切片来实现,例如 slice = slice[:len(slice)-1]。
4. 切片操作
slice[start:end] 的含义:
- slice[start:end] 表示从索引 start 到索引 end(不包括 end),截取原 slice 的一部分。
添加元素: - 通过 append 向 slice 添加元素: slice = append(slice, value)。
5. slice 的共享特性
如果多个 slice 共享同一个底层数组,那么修改一个 slice 中的元素可能会影响其他 slice。也可能导致“内存泄漏”风险,当其他部分仍然引用旧的数组时,内存无法被释放。
6. 深浅拷贝
浅拷贝和深拷贝:
- 浅拷贝可以直接赋值,但两个 slice 共享相同的底层数组;深拷贝需要使用 copy() 函数,如 copy(dest, src)。
7. slice 的陷阱
slice 的常见陷阱或边界情况:
- 多个 slice 共享同一个底层数组,修改其中一个 slice 的内容可能会影响其他 slice。
- append() 操作可能导致底层数组重新分配,原 slice 的修改不再影响新 slice。
- 切片容量不足:如果 slice 反复扩容,每次 append() 都会导致底层数组重新分配和数据拷贝,导致性能问题。
- 截取 slice 时,即使只保留少量数据,底层数组仍然可能被保留,导致无法释放内存。
- 索引越界:对 slice 进行切片时,索引不合法会导致程序 panic。
- 因为 slice 传递的是引用,因此 modifySlice 函数中对 slice 的修改也会影响到外部的 slice。
- 零值 slice 是合法的,只是在使用之前没有分配内存,但是 append() 会自动处理这些情况。
- 在循环中频繁使用 append() 和 copy() 可能导致不必要的性能损耗。
8. slice 在函数中的传递
slice 是按引用传递的,因为它内部包含了一个指针指向底层数组。array是值传递。