前言
在 Go 中,数组与切片是不同的概念,需要加以区别。
数组 array
在 Go 中,数组是值类型,传参时发生的是值拷贝。
因此,若数组作为参数传递,就要根据实际情况考虑到底是传数组本身还是传数组指针。
func try(nums [2]int) {nums[0] = 100}func main() {nums := [2]int{1, 2}try(nums)fmt.Println(nums)}输出如下:[1 2]
因为修改的数组其实是 nums 的拷贝,所以并不会影响 nums。
切片 slice
创建切片
// 1 直接定义var s []int = {1, 2, 3} // 函数外// 2s := []int{1, 2, 3} // 函数内// 3s := make([]int, 0, 3) // make([]T, len int, cap ints[0] = 1 // 报错,因为底层数组长度为 0 ,表示没有元素,怎么能访问呢
切片本质
切片本质是对数组的引用。
type struct {ptr *[]int // 指向底层数组的指针len int // 底层数组的长度cap int // 底层数组的容量}s := make([]int, 2, 4) // 创建一个切片,它指向一个长度为 2,容量为 4 的数组

当通过 append() 方法往切片中添加元素时,其实是往切片指向的底层数组中添加,有两种情况:
- 当底层数组容量足够时,添加需要 O(1) 时间。
- 当底层数组容量不够时,添加需要 O(n) 时间。因为会开辟新的内存块,存放原来的元素和新添加的元素。

cap 的大小由具体的内存分配策略决定。
因此当往切片中添加元素时,如果能知道 cap 而避免发生内存拷贝,性能会比较好。
注意:如果 s := make([]int, 0, 4) 的话
给 s[0-3] 赋值都会出错。
切片传参陷阱
package mainimport ("fmt")func insert(nums []int, val int) {nums = append(nums, val)}func main() {nums := []int{1, 2}for i := 0; i < 5; i++ {insert(nums, i)}fmt.Println(nums)}
输出是什么呢?
是 [1, 2, 0, 1, 2, 3, 4] 吗?
不是!是 [1, 2] ,为什么?
因为传参时,传的是切片的拷贝,即拷贝了 *ptr, len, cap 这三个值的结构体。
调用 append 方法时由于容量不足而发生了扩容(内存复制),两个函数中的 nums 指向了不同的底层数组。
改正方法有 2 种:
传切片指针
nums *[]int。func insert(nums *[]int, val int) {*nums = append(*nums, val)}
设置返回值,返回新的切片。
func insert(nums []int, val int) []int {nums = append(nums, val)return nums}
切片的切片
在切片上再创建切片,会出现内存陷阱,影响性能。
func copy1(nums []int) []int {return nums[len(nums) - 3 : ]}func copy2(nums []int) []int {temp := make([]int, 2)copy(temp, nums[len(nums) - 3 : ])return temp}
copy1 和 copy2 都是复制 nums 的最后 2 个元素,但是有如下差别:
copy1 在切片的基础上再切片,新切片和旧切片指向同一个底层数组,底层数组得不到释放。如果底层数组很大,那么很占内存。
copy2 通过内存复制使得新切片指向一个新的底层数组,原底层数组得到释放。
