赞
踩
Go语言的slice结合了数组的性能优势,同时提供了便捷的操作模式。它的本质只是在数组之上提供了访问指定范围内(起始索引和终止索引)的数组数据。要理解slice首先要说说数组。任何数组 A 都具有一个固定不变的长度 len 和指向首地址的指针 ptr,以及高效访问第n个数组成员的方法 A[ptr+n] (0 <= x < N)。
数组的长度是固定的,这是数组的最大劣势。在实际应用中大部分情况下我们都期望数组的长度是可以变动的。比如变小取其片段,变大追加新成员。变小的话只需一个新数组首地址 ptr和长度len 即可,变大的话就必须要重新申请一块更大的内存空间。至此golang屏蔽了原版数组,为我们提供尽可能更方便的数组,这就是slice。slice具有ptr首地址指针和len属性,增加了capacity属性(隐含原版数组len )。我们可以把slice看成一个原版数组上的虚拟子数组。
不仅如此我们甚至可以根据以上内存布局直接把slice转化为结构体。
type Slice struct {
ptr unsafe.Pointer
len int
cap int
}
func main() {
s1 := []int{0}
s2 := (*Slice)(unsafe.Pointer(&s1))
fmt.Printf("ptr:%v len:%v cap:%v", s2.ptr, s2.len, s2.cap)
}
slice总是声明时的原版数组的子集片段(非 真子集)。可以节省大量的数组拷贝行为,当然也是潜在错误的根源。
p := make([]byte, 0, 10) //capacity为10,即申请的原数组内存长度为10
fmt.Println(len(p)) //slice为原数组子集,长度为0
p = p[10:11] //panic: 超出原数组范围报错
s := p[9:10] //可以在原数组的长度内任意取片段
s[0] = 9
fmt.Println(s[0]) //9
a := p[:cap(p)] //原数组的全集
a[9] = 5 //s[0]被修改
fmt.Println(s[0]) //5
fmt.Println(a[9]) //5
//原数组子集slice可当成独立变量使用
p = append(p,'t','o','m','b','o','y')
name := p[0:3]
sex := p[3:6]
fmt.Println(string(name)) //输出 tom
fmt.Println(string(sex)) //输出 boy
//当然应尽量不要去append
name = append(name,'g','i','r','l')
fmt.Println(string(sex)) //输出 gir
以上是操作数组和它的子集,golang提供的方式还是很方便的。但一定要注意修改slice成员值的覆盖问题,毕竟同一个原数组的各个子集是共用内存空间的。
当原数组长度不够时,我们追加成员将导致重新申请内存。这是slice的另一个大坑。
//紧跟上面的示例
s[0] = 9
fmt.Println(s[0]) //9
fmt.Println(a[9]) //9 s与a处于同一原数组中
x := s
s = append(s, 10) //长度不够,需要新申请内存空间
fmt.Println(cap(s)) //新申请的内存空间长度为8
s[0] = 0
fmt.Println(s[0]) //0
fmt.Println(x[0]) //9 x与a处于同一原数组中
fmt.Println(a[9]) //9 s与a处于不同数组中
从上面例子可以看出我们应该尽量避免对真子集slice进行append操作,因为这极可能偏离开发者的意图。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。