1、Go语言切片Slice
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切
片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
1.1 定义切片
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)
// 也可以简写为
slice1 := make([]type, len)
也可以指定容量,其中 capacity 为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
1.2 切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
s := arr[:]
初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex]
默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1。
s :=make([]int,len,cap)
通过内置函数 make() 初始化切片 s,[]int 标识为其元素类型为 int 的切片。
1.3 len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
package mainimport "fmt"func main() {var numbers = make([]int, 3, 5)printSlice(numbers)
}func printSlice(x []int) {// len=3 cap=5 slice=[0 0 0]fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.4 空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0。
package mainimport "fmt"func main() {var numbers []int// len=0 cap=0 slice=[]printSlice(numbers)if numbers == nil {// 切片是空的fmt.Printf("切片是空的")}
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.5 切片截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]。
package mainimport "fmt"func main() {/* 创建切片 */numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}// len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]printSlice(numbers)/* 打印原始切片 */// numbers == [0 1 2 3 4 5 6 7 8]fmt.Println("numbers ==", numbers)/* 打印子切片从索引1(包含) 到索引4(不包含)*/// numbers[1:4] == [1 2 3]fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默认下限为 0*/// numbers[:3] == [0 1 2]fmt.Println("numbers[:3] ==", numbers[:3])/* 默认上限为 len(s)*/// numbers[4:] == [4 5 6 7 8]fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int, 0, 5)// len=0 cap=5 slice=[]printSlice(numbers1)/* 打印子切片从索引 0(包含) 到索引 2(不包含) */number2 := numbers[:2]// len=2 cap=9 slice=[0 1]// cap=9-0=9(0为start的下标)printSlice(number2)/* 打印子切片从索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]// len=3 cap=7 slice=[2 3 4]// cap=9-2=7(2为start的下标)printSlice(number3)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
我们可以看出切片,实际的是获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底
层数组的指针、len、cap。
1.6 append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package mainimport "fmt"func main() {var numbers []int// len=0 cap=0 slice=[]printSlice(numbers)/* 允许追加空切片 */numbers = append(numbers, 0)// len=1 cap=1 slice=[0]printSlice(numbers)/* 向切片添加一个元素 */numbers = append(numbers, 1)// len=2 cap=2 slice=[0 1]printSlice(numbers)/* 同时添加多个元素 */numbers = append(numbers, 2, 3, 4)// len=5 cap=6 slice=[0 1 2 3 4]// len(list)+len([params])为奇数// cap=len(list)+len([params])+1=2+3+1=6printSlice(numbers)/* 创建切片 numbers1 是之前切片的两倍容量*/numbers1 := make([]int, len(numbers), (cap(numbers))*2)/* 拷贝 numbers 的内容到 numbers1 */copy(numbers1, numbers)// len=5 cap=12 slice=[0 1 2 3 4]printSlice(numbers1)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
合并多个数组:
package mainimport "fmt"func main() {var arr1 = []int{1, 2, 3}var arr2 = []int{4, 5, 6}var arr3 = []int{7, 8, 9}var s1 = append(append(arr1, arr2...), arr3...)// s1: [1 2 3 4 5 6 7 8 9]fmt.Printf("s1: %v\n", s1)
}
使用 copy 函数要注意对于 copy(dst, src),要初始化 dst 的 size,否则无法复制。
// 错误示例
package mainimport "fmt"func main() {dst := make([]int, 0)src := []int{1, 2, 3}copy(dst, src)// len=3 cap=3 slice=[1 2 3]printSlice(src)// len=0 cap=0 slice=[]printSlice(dst)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
// 正确示例
package mainimport "fmt"func main() {// 令size=3dst := make([]int, 3)src := []int{1, 2, 3}copy(dst, src)// len=3 cap=3 slice=[1 2 3]printSlice(src)// len=3 cap=3 slice=[1 2 3]printSlice(dst)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.7 切片截取的长度问题
我们基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?
这里有个公式,对于底层数组容量是 k 的切片 slice[i:j] 来说:
长度: j-i
容量: k-i
实例:
package mainimport "fmt"func main() {numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}// len=11 cap=11 slice=[0 1 2 3 4 5 6 7 8 9 10]printSlice(numbers)// [1 2]fmt.Printf("%d\n", numbers[1:3])// [2 3 4 5 6]fmt.Printf("%d\n", numbers[2:7])// [0 1 2]fmt.Printf("%d\n", numbers[:3])// [4 5 6 7 8 9 10]fmt.Printf("%d\n", numbers[4:])number1 := make([]int, 0, 5)number2 := numbers[:3]// len=0 cap=5 slice=[]printSlice(number1)// len=3 cap=11 slice=[0 1 2]// cap=11-0=11printSlice(number2)number3 := numbers[2:5]// len=3 cap=9 slice=[2 3 4]// capacity为9是因为number3的ptr指向第2个元素,后面还剩2,3,4,5,6,7,8,9,10, 所以 cap=9// cap=11-2=9printSlice(number3)number4 := numbers[3:8]// len=5 cap=8 slice=[3 4 5 6 7]// cap=11-3=8printSlice(number4)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.8 append函数长度问题
package mainimport "fmt"func main() {numbers := []int{0, 1}// numbers的容量: 2fmt.Println("numbers的容量: ", cap(numbers))// numbers的长度: 2fmt.Println("numbers的长度: ", len(numbers))numbers = append(numbers, 2, 3, 4)// numbers的容量: 6fmt.Println("numbers的容量: ", cap(numbers))// numbers的长度: 5fmt.Println("numbers的长度: ", len(numbers))
}
当 numbers = [0, 1] 时,append(numbers, 2, 3, 4) 为什么 cap 从 2 变成 6?
append(list, [params]) 最终cap的计算:
1、当同时添加多个元素时:
len(list)+len([params])为偶数: cap=len(list)+len([params])
len(list)+len([params])为奇数: cap=len(list)+len([params])+1
即 cap 始终为偶数。
2、当一个一个添加元素时:
len(list)+1<=cap: cap=cap
len(list)+1>cap: cap=2*cap
即 cap 总是呈 2 倍的增加(也是偶数)。
通过 append() 函数向数组中添加元素,首先 cap 会以二倍的速度增长,如果发现增长 2 倍以上的容量可以满足扩
容后的需求,那么 cap*2,否则就会看扩容后数组的 length 是多少 cap=length+1。
每次cap改变的时候指向array内存的指针都在变化,当在使用 append 的时候,如果 cap==len 了这个时候就会新
开辟一块更大内存,然后把之前的数据复制过去(实际go在append的时候放大cap是有规律的,在 cap 小于1024
的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap)。
通过查看$GOROOT/src/runtime/slice.go
源码:
// cap为需要的容量,即新申请的容量
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {newcap = cap
} else {const threshold = 256if old.cap < threshold {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {// Transition from growing 2x for small slices// to growing 1.25x for large slices. This formula// gives a smooth-ish transition between the two.newcap += (newcap + 3*threshold) / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}
}
1、首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
2、否则,如果旧切片的长度小于阈值,则最终容量(newcap)就是旧容量(old.cap)的两倍,即
(
newcap=doublecap
)。3、否则,如果旧切片长度大于等于阈值,则最终容量(newcap)从旧容量(old.cap)开始循环增加为原来的1.25
倍,即
newcap += (newcap + 3*threshold) / 4
,直到最终容量(newcap)大于等于新申请的容量(cap),即(
newcap >= cap
)。4、如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
package mainimport "fmt"func main() {var numbers []int// len=0 cap=0 slice=[]printSlice(numbers)/* 允许追加空切片 */numbers = append(numbers, 0)// len=1 cap=1 slice=[0]printSlice(numbers)/* 向切片添加一个元素 */numbers = append(numbers, 1)// len=2 cap=2 slice=[0 1]printSlice(numbers)/* 注意cap容量的变化 */numbers = append(numbers, 2)// len=3 cap=4 slice=[0 1 2]printSlice(numbers)numbers = append(numbers, 3)// len=4 cap=4 slice=[0 1 2 3]printSlice(numbers)// 可以看出,容量不够时,cap会自动扩容到2倍numbers = append(numbers, 4)// len=5 cap=8 slice=[0 1 2 3 4]printSlice(numbers)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.9 切片是引用传值
在做函数调用时,slice 按引用传递,array 按值传递。
package mainimport "fmt"func main() {changeSliceTest()
}func changeSliceTest() {arr1 := []int{1, 2, 3}arr2 := [3]int{1, 2, 3}arr3 := [3]int{1, 2, 3}// before change arr1, [1 2 3]fmt.Println("before change arr1, ", arr1)// slice 按引用传递changeSlice(arr1)// after change arr1, [9999 2 3]fmt.Println("after change arr1, ", arr1)// before change arr2, [1 2 3]fmt.Println("before change arr2, ", arr2)// array 按值传递changeArray(arr2)// after change arr2, [1 2 3]fmt.Println("after change arr2, ", arr2)// before change arr3, [1 2 3]fmt.Println("before change arr3, ", arr3)// 可以显式取array的指针changeArrayByPointer(&arr3)// after change arr3, [6666 2 3]fmt.Println("after change arr3, ", arr3)
}func changeSlice(arr []int) {arr[0] = 9999
}func changeArray(arr [3]int) {arr[0] = 6666
}func changeArrayByPointer(arr *[3]int) {arr[0] = 6666
}
1.10 切片内部结构
struct Slice
{ byte* array; // actual datauintgo len; // number of elementsuintgo cap; // allocated number of elements};
第一个字段表示 array 的指针,是真实数据的指针。第二个是表示 slice 的长度,第三个是表示 slice 的容量。
所以 unsafe.Sizeof(切片) 永远都是 24。
当把 slice 作为参数,本身传递的是值,但其内容就 byte* array,实际传递的是引用,所以可以在函数内部修
改。但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数
外面的切片没有变化。
package mainimport ("fmt""unsafe"
)func main() {slice_test := []int{1, 2, 3, 4, 5}// 24fmt.Println(unsafe.Sizeof(slice_test))// main:[]int{1, 2, 3, 4, 5},5,5fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))// slice_value:[]int{1, 100, 3, 4, 5, 6},6,10slice_value(slice_test)// main:[]int{1, 100, 3, 4, 5},5,5fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))// slice_ptr:[]int{1, 100, 3, 4, 5, 7},6,10slice_ptr(&slice_test)// main:[]int{1, 100, 3, 4, 5, 7},6,10fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))// 24fmt.Println(unsafe.Sizeof(slice_test))
}func slice_value(slice_test []int) {slice_test[1] = 100 // 函数外的slice确实有被修改slice_test = append(slice_test, 6) // 函数外的不变fmt.Printf("slice_value:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
}func slice_ptr(slice_test *[]int) { // 这样才能修改函数外的slice*slice_test = append(*slice_test, 7)fmt.Printf("slice_ptr:%#v,%#v,%#v\n", *slice_test, len(*slice_test), cap(*slice_test))
}
slice 的底层是数组指针,所以 slice a 和 s 指向的是同一个底层数组,所以当修改 s时,a 也会被修改;修改a时,
s也会被修改。
实例1:
package mainimport "fmt"func main() {// len=3, cap=3s := []int{1, 2, 3}a := ss[0] = 888// [888 2 3] 3 3fmt.Println(a, len(a), cap(a))// [888 2 3] 3 3fmt.Println(s, len(s), cap(s))// append添加的元素对a不生效s = append(s, 4)// [888 2 3 4] 4 6fmt.Println(s, len(s), cap(s))// [888 2 3] 3 3fmt.Println(a, len(a), cap(a))
}
实例2:
package mainimport "fmt"func main() {var array = []int{1, 2, 3, 4, 5}// len=5 cap=5 slice=[1 2 3 4 5]printSlice(array)slice := array[1:]// len=4 cap=4 slice=[2 3 4 5]printSlice(slice)array[1] = 100// len=4 cap=4 slice=[100 3 4 5]printSlice(slice)// len=5 cap=5 slice=[1 100 3 4 5]printSlice(array)slice[2] = 1000// len=5 cap=5 slice=[1 100 3 1000 5]printSlice(array)// len=4 cap=4 slice=[100 3 1000 5]printSlice(slice)
}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
1.11 三个参数的切片
做 slice 截取时建议用两个参数,尤其是从底层数组进行切片操作时,因为这样在进行第一次 append 操作时,会
给切片重新分配空间,这样减少切片对数组的影响。
s = s[low : high : max]
切片的三个参数的切片截取的意义为 low 为截取的起始下标(含), high 为窃取
的结束下标(不含 high),max 为切片保留的原切片的最大下标(不含 max);即新切片从老切片的 low 下标元
素开始,len = high - low
, cap = max - low
;high 和 max 一旦超出在老切片中越界,就会发生 runtime
err,slice out of range。另外如果省略第三个参数的时候,第三个参数默认和第二个参数相同,即 len = cap。
实例:
package mainimport "fmt"func main() {s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s = s[1:9:10]// [1 2 3 4 5 6 7 8]fmt.Println(s)// 8fmt.Println(len(s))// 9fmt.Println(cap(s))
}
修改 max 值,发生越界错误:
package mainimport "fmt"func main() {s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}// 修改 max 值为 13s = s[1:9:13]fmt.Println(s)fmt.Println(len(s))fmt.Println(cap(s))
}
// 报错
panic: runtime error: slice bounds out of range [::13] with capacity 10goroutine 1 [running]:
1.12 append会让切片和与它相关的切片脱钩,但地址不变
&会让原切片发生改变,切片也会让原切片发生改变,append不会发生改变:
package mainimport ("fmt"
)func main() {a := []int{1, 2, 3, 4}b := a// part1 a len=4 cap=4 slice=[1 2 3 4]printSlice(a, "part1 a")// part1 b len=4 cap=4 slice=[1 2 3 4]printSlice(b, "part1 b")fmt.Printf("\n")a[0] = 9// part2 a len=4 cap=4 slice=[9 2 3 4]printSlice(a, "part2 a")// part2 b len=4 cap=4 slice=[9 2 3 4]printSlice(b, "part2 b")fmt.Printf("\n")// [9 2 3 4 5]a = append(a, 5)a[0] = 1// part3 a len=5 cap=8 slice=[1 2 3 4 5]printSlice(a, "part3 a")// part3 b len=4 cap=4 slice=[9 2 3 4]printSlice(b, "part3 b")fmt.Printf("\n")c := ad := &a// part4 a len=5 cap=8 slice=[1 2 3 4 5]printSlice(a, "part4 a")// part4 c len=5 cap=8 slice=[1 2 3 4 5]printSlice(c, "part4 c")// part4 *d len=5 cap=8 slice=[1 2 3 4 5]printSlice(*d, "part4 *d")fmt.Printf("\n")// [1 2 3 4 5 6]a = append(a, 6)// part5 a len=6 cap=8 slice=[1 2 3 4 5 6]printSlice(a, "part5 a")// part5 c len=5 cap=8 slice=[1 2 3 4 5]printSlice(c, "part5 c")// part5 *d len=6 cap=8 slice=[1 2 3 4 5 6]printSlice(*d, "part5 *d")
}func printSlice(x []int, y string) {fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
猜测脱钩的情况是由于切片底层数组扩张(创建了新数组替换旧数组)导致:
package mainimport "fmt"func main() {x := make([]int, 4)a := x[:2]a[0] = 1// [1 0]fmt.Println(a)// [1 0 0 0]fmt.Println(x)// [1 0 2]a = append(a, 2)a[0] = 0// [0 0 2]fmt.Println(a)// [0 0 2 0]fmt.Println(x)// 切片a的地址:0xc00000e1a0fmt.Printf("切片a的地址:%p\n", a)// 切片x的地址:0xc00000e1a0fmt.Printf("切片x的地址:%p\n", x)fmt.Println()y := make([]int, 4)b := yb[0] = 1// [1 0 0 0]fmt.Println(b)// [1 0 0 0]fmt.Println(y)// [1 0 0 0 2]b = append(b, 2)b[0] = 0// [0 0 0 0 2]fmt.Println(b)// [1 0 0 0]fmt.Println(y)// 切片b的地址:0xc000014240fmt.Printf("切片b的地址:%p\n", b)// 切片y的地址:0xc00000e1e0fmt.Printf("切片y的地址:%p\n", y)fmt.Println()
}
脱钩:b:=a,修改a的值b的值不会改变,正常情况下是要改变的。
在 cap(b)<len(a)
的情况下会发生脱钩,但是在 cap(b)≥len(a)
时,append并不能使切片脱钩。
通过 b:=a
引用的方式,当 cap(b) < len(a)
,修改 a[i]
的值并不会改变 b[i]
的值,发生了脱钩;但是如果
cap(b) ≥ len(a)
时,修改 a[i]
的值就会改变 b[i]
的值,没有发生脱钩;要想使两个切片同步改变,最好的
方式是使用切片指针来实现,也就是上面的 *d
。
package mainimport ("fmt"
)func main() {a := []int{1, 2, 3, 4}b := a// part1 a len=4 cap=4 slice=[1 2 3 4]printSlice(a, "part1 a")// part1 b len=4 cap=4 slice=[1 2 3 4]printSlice(b, "part1 b")fmt.Printf("\n")a[0] = 9// part2 a len=4 cap=4 slice=[9 2 3 4]printSlice(a, "part2 a")// part2 b len=4 cap=4 slice=[9 2 3 4]printSlice(b, "part2 b")fmt.Printf("\n")a = append(a, 5)a[0] = 1// part3 a len=5 cap=8 slice=[1 2 3 4 5]printSlice(a, "part3 a")// part3 b len=4 cap=4 slice=[9 2 3 4]printSlice(b, "part3 b")fmt.Printf("\n")
}func printSlice(x []int, y string) {fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
在 part3 中,通过 a[0]=1 修改了 a[0] 的值,但是 b[0] 的值并没有改变;通过 a=append(a,5) 增加了一个数
据,b 切片没有增加数据;这只是在 b 的 cap 比较小的情况下才会出现的情况;如果 b 的 cap 足够大呢?
将代码修改成:
package mainimport ("fmt"
)func main() {a := make([]int, 4, 10)// part1 a len=4 cap=10 slice=[0 0 0 0]printSlice(a, "part1 a")a[0] = 1a[1] = 2a[2] = 3a[3] = 4b := a// part1 a len=4 cap=10 slice=[1 2 3 4]printSlice(a, "part1 a")// part1 b len=4 cap=10 slice=[1 2 3 4]printSlice(b, "part1 b")fmt.Printf("\n")a[0] = 99// part2 a len=4 cap=10 slice=[99 2 3 4]printSlice(a, "part2 a")// part2 b len=4 cap=10 slice=[99 2 3 4]printSlice(b, "part2 b")fmt.Printf("\n")a = append(a, 5)a[0] = 100// part3 a len=5 cap=10 slice=[100 2 3 4 5]printSlice(a, "part3 a")// part3 b len=4 cap=10 slice=[100 2 3 4]printSlice(b, "part3 b")fmt.Printf("\n")}func printSlice(x []int, y string) {fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
修改后的代码中,将 a, b 的 cap 都设置成 10;
在 part3 部分,通过 a[0] 修改 a[0] 值,b[0] 值也会跟着修改;通过 a=append(a, 5) 可以给 a 增加数据项,但
是 b 的数据项并没有增加。
1.13 nil切片和空切片
make([]int,0)
与 var a []int
建的切片是有区别的,前者的切片指针有分配,后者的内部指针为nil。
package mainimport ("fmt""reflect""unsafe"
)func main() {var a []intb := make([]int, 0)if a == nil {// a is nilfmt.Println("a is nil")} else {fmt.Println("a is not nil")}//虽然b的底层数组大小为0,但切片并不是nilif b == nil {fmt.Println("b is nil")} else {// b is not nilfmt.Println("b is not nil")}//使用反射中的SliceHeader来获取切片运行时的数据结构as := (*reflect.SliceHeader)(unsafe.Pointer(&a))bs := (*reflect.SliceHeader)(unsafe.Pointer(&b))// len=0,cap=0,type=0fmt.Printf("len=%d,cap=%d,type=%d\n", len(a), cap(a), as.Data)// len=0,cap=0,type=824634810008fmt.Printf("len=%d,cap=%d,type=%d\n", len(b), cap(b), bs.Data)
}
1.14 多个切片引用同一个底层数组引发的混乱
切片可以由数组创建,一个底层数组可以创建多个切片,这些切片共享底层数组,使用append 扩展切片过程中可
能修改底层数组的元素,间接地影响其他切片的值,也可能发生数组复制重建,共用底层数组的切片,由于其行为
不明朗,不推荐使用。
多个切片共享一个底层数组,其中一个切片的 append 操作可能引发如下两种情况。
(1)、append追加的元素没有超过底层数组的容量,此种 append 操作会直接操作共享的底层数组,如果其他切
片有引用数组被覆盖的元素,则会导致其他切片的值也隐式地发生变化。
(2)、append追加的元素加上原来的元素如果超出底层数组的容 ,则此种 append 操作会重新申请新数组,并将
原来数组值复制到新数组。
由于有这种二义性,所以在使用切片的过程中应该尽量避免多个切面共享底层数组, 可以使用copy进行显式的复
制。
package mainimport ("fmt""reflect""unsafe"
)func main() {a := []int{0, 1, 2, 3, 4, 5, 6}b := a[0:4]as := (*reflect.SliceHeader)(unsafe.Pointer(&a))bs := (*reflect.SliceHeader)(unsafe.Pointer(&b))//a、b共享底层数组// a=[0 1 2 3 4 5 6],len=7,cap=7,type=824633803328fmt.Printf("a=%v,len=%d,cap=%d,type=%d\n", a, len(a), cap(a), as.Data)// b=[0 1 2 3],len=4,cap=7,type=824633803328fmt.Printf("b=%v,len=%d,cap=%d,type=%d\n", b, len(b), cap(b), bs.Data)b = append(b, 10, 11, 12)//a、b继续共享底层数组,修改b会影响共享的底层数组,间接影响a// a=[0 1 2 3 10 11 12],len=7,cap=7fmt.Printf("a=%v,len=%d,cap=%d\n", a, len(a), cap(a))// b=[0 1 2 3 10 11 12],len=7,cap=7fmt.Printf("b=%v,len=%d,cap=%d\n", b, len(b), cap(b))//len(b)=7,底层数组容量是7,此时需要重新分配数组,并将原来数组值复制到新数组b = append(b, 13, 14)as = (*reflect.SliceHeader)(unsafe.Pointer(&a))bs = (*reflect.SliceHeader)(unsafe.Pointer(&b))//可以看到a和b指向底层数组的指针已经不同了// a=[0 1 2 3 10 11 12],len=7,cap=7,type=824633803328fmt.Printf("a=%v,len=%d,cap=%d,type=%d\n", a, len(a), cap(a), as.Data)// b=[0 1 2 3 10 11 12 13 14],len=9,cap=14,type=824633786592fmt.Printf("b=%v,len=%d,cap=%d,type=%d\n", b, len(b), cap(b), bs.Data)
}