欢迎加入GolangRoadmap,一个年轻的GO开发者社区https://www.golangroadmap.com/,目前是邀请制注册,注册码:Gopher-1035-0722,已开放Golang岗位内推,Golang企业题库,Golang精品资源等栏目
以下题目资源都来自GolangRoadmap->Go求职->企业题库
题目ID-21 Golang Slice 的底层实现
切片是基于数组实现的,它的底层是数组,它自己本身非常小,可以理解为对 底层数组的抽象。因为基于数组实现,所以它的底层的内存是连续分配的,效 率非常高,还可以通过索引获得数据。
切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用 底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一 个只读对象,其工作机制类似数组指针的一种封装。
切片对象非常小,是因为它是只有 3 个字段的数据结构:
- 指向底层数组的指针
- 切片的长度
- 切片的容量
题目ID-23 扩容前后的 Slice 是否相同?
slice和map、chan都不太一样的,一样的是,它也可以在函数中修改对应的内容。
func modify(nums []int) {
fmt.Printf("%p\n", nums) // num 中真实存储的地址
fmt.Printf("%p\n", &nums[0]) // 地址nums==&nums[0]==a== &a[0]
fmt.Printf("%p\n", &nums) // num 的地址
nums[0] = 2333
}
func main() {
a := []int{6, 6, 6}
fmt.Printf("%p\n", &a) // slice 的地址
fmt.Printf("%p\n", a) // slice 中真实存储的地址
fmt.Printf("%p\n", &a[0]) // 地址a == &a[0]
modify(a)
fmt.Println(a)
}
/*
0xc0000a6020
0xc0000b8000
0xc0000b8000
0xc0000b8000
0xc0000b8000
0xc0000a6060
[2333 6 6]
*/
运行打印结果,发现slice的确是被修改了,而且我们这里打印slice的内存地址是可以直接通过%p打印的,不用使用&取地址符转换。
这就可以证明make的slice也是一个指针了吗?不一定,也可能fmt.Printf把slice特殊处理了。
type slice struct {
array unsafe.Pointer
len int
cap int
}
//...
func makeslice(et *_type, len, cap int) unsafe.Pointer {
//...
}
很明显了,当是slice类型的时候,返回是slice这个结构体里,字段Data第一个元素的地址。
所以我们通过%p打印的slice变量的地址其实就是内部存储数组元素的地址,slice是一种结构体+元素指针的混合类型,通过元素array的指针,可以达到修改slice里存储元素的目的。
所以修改类型的内容的办法有很多种,类型本身作为指针可以,类型里有指针类型的字段也可以。
单纯的从slice这个结构体看,我们可以通过modify修改存储元素的内容,但是永远修改不了len和cap,因为他们只是一个拷贝,不是指针,如果要修改,那就要传递*slice作为参数才可以。