append函数破坏原有slice数据
先从slice添加元素开始
假设往整数slice指定位置添加元素,例如:
func main() {
s0 := []string{"a", "b", "c", "d", "e"}
i := 2
s9 := append(append(s0[:i], "X"), s0[i:]...)
print("s0", s0)
print("s9", s9)
}
原意把“X”插入到数组s0的位置2,也就是“c“的前面;这里采用的办法是两次append。
- 第一个取出子slice ["a", "b"]
- 往子slice加入X,append(["a", "b"], "X") ==> ["a", "b", "X"]
- 在加入后半部分, append(["a", "b", "X"], ["c", "d", "e"]) ==> ["a", "b", "X", "c", "d", "e"]
运行结果:
s0: ["a", "b", "X", "d", "e"]
s9: ["a", "b", "X", "X", "d", "e"]
和预想的完全不一样,不但s9不对,而且原slice s0也发生了改变,这究竟发生了什么???
我们把上述步骤分解开来看:
func main() {
s0 := []string{"a", "b", "c", "d", "e"} ; print("s0", &s0)
i := 2
//s9 := append(append(s0[:i], "X"), s0[2:]...)
s1 := s0[:i] ; print("s1", &s1)
s2 := append(s1, "X") ; print("s2", &s2)
s3 := s0[i:] ; print("s3", &s3)
s9 := append(s2, s3...) ; print("s9", &s9)
}
运行结果:
s0: [a b c d e]
s1: [a b]
s2: [a b X]
s3: [X d e]
s9: [a b X X d e]
发现s3不对了,而s3 := s0[i:]
,这之前只有s2 := append(s1, "X")
一条语句,难道这条语句改变了s3(s0)的内容啦?我们挪动一下s2和s3的位置两者交换,让s3先运行,结果是s3是:
s0: [a b c d e]
s1: [a b]
s3: [c d e]
s2: [a b X]
s9: [a b X X d e]
可见就是append函数破坏了原来s0的内容。
那么append为什么会破坏呢?我们增强一下代码把slice的内容打出来:
package main
import (
"fmt"
"unsafe"
)
func printslice(n string, s *[]string) {
type SliceStruct struct {
ptr uintptr
len uint64
cap uint64
}
p := (* SliceStruct)(unsafe.Pointer(s));
fmt.Printf("%s: p=%p,ptr=0x%x,len=%d,cap=%d,val=%v\n", n, p, p.ptr, p.len, p.cap, s)
}
func main() {
s0 := []string{"a", "b", "c", "d", "e"} ; printslice("s0", &s0)
i := 2
//s9 := append(append(s0[:i], "X"), s0[2:]...)
s1 := s0[:i] ; printslice("s1", &s1)
s2 := append(s1, "X") ; printslice("s2", &s2)
s3 := s0[i:] ; printslice("s3", &s3)
s9 := append(s2, s3...) ; printslice("s9", &s9)
}
运行结果:
s0: p=0xc0000a0020,ptr=0xc0000b6000,len=5,cap=5,val=&[a b c d e]
s1: p=0xc0000a0060,ptr=0xc0000b6000,len=2,cap=5,val=&[a b]
s2: p=0xc0000a00a0,ptr=0xc0000b6000,len=3,cap=5,val=&[a b X]
s3: p=0xc0000a00e0,ptr=0xc0000b6020,len=3,cap=3,val=&[X d e]
s9: p=0xc0000a0120,ptr=0xc0000c2000,len=6,cap=10,val=&[a b X X d e]
这里我们看到问题了没有,s0, s1, s2他们的数据指针值是相同的,也就是说slice s0, s1, s2指向的是同一块数据内存,然而数据大小(len)不一致,所以问题的根源在于:
语句s2 := append(s1, "X")
有很大的问题,s1和s0共用数据空间,然后s1的len是2,于是append(s1, ...)的时候把新数据插入到s1的下标为s1[2]的位置,而这个位置原来就是s0[2]的位置,于是就把s0[i]的原来的值("c")给覆盖掉了。
其根源在于s1并没有独立分配出来自己的数据空间,还是使用的s0的数据空间。
有没有办法解决这个问题呢?又要用到append的一个属性。
如果append后的slice的长度大于capacity值,那么append就会申请新的数据内容空间,这样就不会破坏原有的s0的数据空间。
前面我们看到s1的len是2,而cap的值是5,这是从s0拷贝过来的,于是append(s1, "X")的时候,append计算2+1 < 5就不分配新的空间,就直接在s1[2]的位置替换“X”,就冲掉了s0的"c"值,那么如果我们把s1的cap值改成2,那么2+1>2,会导致append重启分配新的空间,我们试一下,这是一个临时hack的办法:
func printslice(n string, s *[]string) {
type SliceStruct struct {
ptr uintptr
len uint64
cap uint64
}
p := (* SliceStruct)(unsafe.Pointer(s));
if n == "s1" {
p.cap = 2 // rewrite cap to 2 for s1 slice by force
}
fmt.Printf("%s: p=%p,ptr=0x%x,len=%d,cap=%d,val=%v\n", n, p, p.ptr, p.len, p.cap, s)
}
再运行:
s0: p=0xc0000bc000,ptr=0xc0000be000,len=5,cap=5,val=&[a b c d e]
s1: p=0xc0000bc040,ptr=0xc0000be000,len=2,cap=2,val=&[a b]
s2: p=0xc0000bc080,ptr=0xc0000c0040,len=3,cap=4,val=&[a b X]
s3: p=0xc0000bc0c0,ptr=0xc0000be020,len=3,cap=3,val=&[c d e]
s9: p=0xc0000bc100,ptr=0xc0000cc000,len=6,cap=8,val=&[a b X c d e]
看到s2的数据地址就和s0,s1不一样,是重新另分配出来的,就不会导致数据被重写,而且我们看到最终的结果s9也是所期望的值。
总结就是我们要理解append函数的定义,看文档,不能按我们自己的理解来使用append。
最后一个问题,如何更改slice的cap值呢,好像不行唉。
有疑问加站长微信联系(非本文作者)