这篇根据底层分析切片
切片实际对应的是一个结构
type Slice struct{
array unsafe.Pointer
len int
cap int
}
三个成员依次是数据指针,长度和容量。现在我们主要来看函数调用
func test_slice(b []int) {
b = append(b, 100)
fmt.Println(b)
}
func main() {
//创建切片a,长度为0,容量为10
var a = make([]int, 0, 10)
//追加5个元素之后,长度为5,容量为10
a = append(a, 1, 2, 3, 4, 5)
//调用函数
test_slice(a)
fmt.Println(a)
}
代码比较简明,我们重点看实参和形参。
如前文所述,切片对应的是一个结构,那么可以认为把实参a传过去,相当于将a的结构体传过去,这一步理解至关重要。
接着,形参实参一结合,b就得到一份a的结构副本。由于当前a容量还有5个元素,因此追加100之后,没有扩容。所谓的扩容主要就是指结构成员的array指针发生变化。
因此b和a依然共享同一段切片内存。打印结果b=[]int{1,2,3,4,5,100},但是打印a却等于a=[]int{1,2,3,4,5},没有发生变化,这是为什么呢?
原因就在于,切片b追加之后,它的长度len也跟着变化。但是原来a的结构体内部的len并没有变化¹,因此为了能看到a确实共享同一个切片,需要将a的切片长度进行修正。
¹补充:如果学过C语言,我们想像有一个结构类型
struct Slice{
int a;
int b;
int c;
};
当将结构传值传过去时,成员a,b,c就复制一个副本,因此,如果副本发生变化和原结构一点都没关系。类比切片的结构,len是一个int,也是一样只是传个副本...
test_slice(a);
a=a[:6] //修正a的长度
fmt.Println(a)
再次打印可以看到1,2,3,4,5,100。
现在我们看一下扩容的例子
func test_slice(b []int) {
//追加三个元素,导致切片扩容
b = append(b, 20,21,22)
fmt.Println(b)
}
func main() {
//创建切片a,长度为0,容量为10
var a = make([]int, 0, 10)
//追加8个元素之后,长度为8,容量为10
a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
//调用函数
test_slice(a)
fmt.Println(a)
}
分析:
调用函数后,由于追加3个元素,而原切片只剩2个元素,因此需要切片扩容。所谓切片扩容用一个词来形容就是“另起炉灶”,和原切片完全脱离关系,因为是在新内存中创建的。
那么b结构体中的array指针发生变化,指向新地址值。而原切片由于脱离关系不受干扰,还是原样。
结果:
b=[]int{1,2,3,4,5,6,7,8,20,21,22}
a=[]int{1,2,3,4,5,6,7,8}
如果就想通过函数改变原切片怎么办呢?上面的例子我们可以看出,实际是一个值传递的问题,我们能不能牢牢的控制结构内部的指针还属于a呢?自然就想到将切片的指针传过去,相当于C语言中将结构体的地址传过去。
//形参传为切片的地址
func test_slice(b *[]int) {
//追加三个元素,导致切片扩容
*b = append(*b, 20,21,22)
fmt.Println(*b)
}
func main() {
//创建切片a,长度为0,容量为10
var a = make([]int, 0, 10)
//追加8个元素之后,长度为8,容量为10
a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
//调用函数,传切片地址
test_slice(&a)
fmt.Println(a)
}
完美的三篇文章!应对实际开发中绝大部分场景。
页面更新:2024-05-12
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号