go切片实现原理

近日一直在学习golang,已经产出如下博客一篇

引言

最近在使用go语言的切片时,出现了一些意料之外的情况,遂查询相关文档学习后写下此篇博客

正文

首先,我们思考,go在通过函数传递一个切片时,是通过引用传递的吗,还是通过值传递的呢(答案将会很意外的哦)

值传递?

首先,先看如下简单代码,将一个string类型的切片传入函数后经过修改,在使用append()函数对切片进行添加之后,在函数的外部进行打印后却能发现,在内部添加数据并没有影响function()函数外面的str

看起来像是值传递,让我们继续往下看

func function(str []string){
	str = append(str,"c","lua","c#")
}


func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ java golang]

[Done] exited with code=0 in 1.586 seconds

引用传递?

将一个string类型的切片传入函数后经过修改,修改后影响到了外面[]string切片

func function(str []string){
	str[1] = "python"
}

func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ python golang]

所以,go的切片是使用的引用传递吗?

no,请继续向下看

我们可以惊奇的发现,先对切片进行append追加,在进行修改后,在函数外面进行打印,修改居然失效了

func function(str []string){
	str = append(str,"c","lua","c#")
	str[1] = "python"
}


func main() {
	str := []string{"c++","java","golang"}
	function(str)
	fmt.Println(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ java golang]

但如果我们先用make()函数先对[]string切片的容量进行设置,在进行赋值又能发现是有效的

func function(str []string) {
	str = append(str, "c", "lua", "c#")
	str[1] = "python"
}

func main() {
	str := make([]string,3,10)
	str1 := []string{"c++", "java", "golang"}
	copy(str,str1)
	function(str)
	fmt.Print(str)
}
[Running] go run "d:\goProject\src\learn\package main.go"
[c++ python golang]
[Done] exited with code=0 in 1.858 seconds

到这里,读者的cpu是不是已经麻成一团了呢,哈哈,请先让我先对其中切片的原理进行讲解后,再回头来看,相信一定能看懂

原理解析

首先,切片类型在编译时期会生成一个结构体

  • array:相当于一个c语言的数组指针,指向切片的实际内存区域
  • len:切片的实际使用大小
  • cap:切片当前能够容纳的最大数量
type splice struct {
	array    unsafe.Pointer
	len      int
	cap		 int
}

如下图所示

而在我们将切片通过函数传入时候,go直接对这个结构体进行了一次拷贝,也就是说,拷贝的是这个结构体的值,而不是真正的数组,如下图所示,拷贝是一次浅拷贝,两个结构体指针指向同一个底层的数组

image-20231226211810026

所以,当我们没有使用make()函数生成切片类型,并且设置切片的cap容量时候:

go就会对底层的数组进行一次扩容,此时传入函数的切片的array就会指向一块新的内存,如下图所示,故修改无用

image-20231226211910073

然而,如果我们使用make()生成切片,并且设置了cap,那么就会发生如下事情

假设len==3,cap==10

  1. 切片传入函数后添加3条数据,此时函数内的切片len==6,cap==10
  2. 由于传入时候,传入的splice结构体是值传递,所以,函数外的splice结构体len==3,cap==10,也就是说len变量并没有被修改,但是对于[0,len]这个区间内的参数的修改是可见的,然而,由于go有着比较严格的内存安全检查,如果我们直接对[3,6]这个区间的内存进行访问,go会提示运行时错误

实测

接下来我们进行实测,通过一点类似于c语言指针的骚操作 ,绕过go的安全检查,验证我们理论的正确性

  1. 首先使用make()生成切片,设置len==3,cap==10
  2. 使用copy()函数,将切片前三个string变量进行赋值
  3. 将切片通过函数传递给function()函数,在function()函数内部进行追加以及修改
  4. function()函数返回后,通过类似于c语言指针的骚操作,绕过go的安全检查,访问到len[3:6]这个区间的内容
  5. 通过打印可以看见,如我们所想,在function()函数内的修改和添加都成功了
func function(str []string) {
	str = append(str, "c", "lua", "c#")
	str[1] = "python"
}

func main() {
	str := make([]string,3,10)
	str1 := []string{"c++", "java", "golang"}
	copy(str,str1)
	function(str)
	//fmt.Print(str)

    for i := 0; i < 6; i++ {
        ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&str[0])) + uintptr(i)*unsafe.Sizeof(str[0])) 
        fmt.Printf("%s,", *(*string)(unsafe.Pointer(ptr)))
    }
}
[Running] go run "d:\goProject\src\learn\package main.go"
c++,python,golang,c,lua,c#,
[Done] exited with code=0 in 1.757 seconds

总结

  • 切片在底层是一个结构体,在进行赋值传递时候,是将该结构体进行浅拷贝
  • 切片就是相当于一个动态数组,容量足够时候直接添加,不够时候重新创建一个更大的数组,再将原本的数据移动到新的数组(经过个人测试:默认二倍扩容,大小超过512时候不在使用二倍扩容,转而使用其他算法)

相关推荐

  1. go 切面 AOP 实现

    2024-03-10 20:44:06       28 阅读
  2. Go-学会使用切片

    2024-03-10 20:44:06       16 阅读
  3. go切片删除

    2024-03-10 20:44:06       21 阅读
  4. Go语言切片

    2024-03-10 20:44:06       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-10 20:44:06       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-10 20:44:06       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-10 20:44:06       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-10 20:44:06       18 阅读

热门阅读

  1. python基础总复习

    2024-03-10 20:44:06       25 阅读
  2. 大数据培训之Zookeeper零基础-1

    2024-03-10 20:44:06       19 阅读
  3. 大模型交互-超拟人合成

    2024-03-10 20:44:06       17 阅读
  4. Android如何对应用进行系统签名

    2024-03-10 20:44:06       23 阅读
  5. 【CSS】初学轻松学会使用Flex布局

    2024-03-10 20:44:06       18 阅读
  6. 【CMake】顶层 CMakeList.txt 常用命令总结

    2024-03-10 20:44:06       24 阅读
  7. 点投影到平面方程

    2024-03-10 20:44:06       26 阅读
  8. leetcode 2834.找出美丽数组的最小和

    2024-03-10 20:44:06       18 阅读
  9. MySQL的页与行格式

    2024-03-10 20:44:06       27 阅读