发布网友 发布时间:2024-10-01 08:22
共1个回答
热心网友 时间:2024-11-03 13:39
一个程序中,变量分为变量名和变量内容,变量内容的存储一般会被分配到堆和栈上。而在Go语言中有两种传递变量的方式值传递和引用传递。其中值传递会直接将变量内容附在变量名上传递,而引用传递会将变量内容的地址附在变量名上传递。
Golang中是如何做到如果在面试时有面试官提问你:“Go的参数是如何传递的?”你会怎么回答呢?
这个问题其实只有一个答案。因为在Golang中所有的类型传递都是通过值传递实现的,而不是引用传递,即使是指针的传递也是通过copy指针的方式进行。另外对于一些包裹了底层数据的数据结构,其值传递的过程中,复制的也只是实例的指针,而不是底层数据所暴露出来的指针。
下面以Go版本1.8的slice为例来简单了解一下:
funcmakeslice(et*_type,len,capint)unsafe.Pointer{mem,overflow:=math.MulUintptr(et.size,uintptr(cap))ifoverflow||mem>maxAlloc||len<0||len>cap{//NOTE:Procea'lenoutofrange'errorinsteadofa//'capoutofrange'errorwhensomeonedoesmake([]T,bignumber).//'capoutofrange'istruetoo,butsincethecapisonlybeing//suppliedimplicitly,sayinglenisclearer.//Seegolang.org/issue/4085.mem,overflow:=math.MulUintptr(et.size,uintptr(len))ifoverflow||mem>maxAlloc||len<0{panicmakeslicelen()}panicmakeslicecap()}returnmallocgc(mem,et,true)//申请内存}可以看到slice在初始化的过程中调用了runtime中的makeslice函数,这个函数会将slice的地址返回给接受的变量。
typeslicestruct{arrayunsafe.Pointer//底层数组的地址lenintcapint}//初始化过程p:=make([]int,0)fmt.Printf("变量p的地址%p",&p)fmt.Printf("slice的地址%p\n",p)上面打印时出现的是的内容,这是因为Go内部实现了自动解引用(即Go内部实现的解引用操作)。自动解引用时receive会从指针类型转变为值类型。顺带一提自动取引用时receiver会从值类型转变为指针类型。
如果未实现自动解引用时会怎样呢?下面是未实现自动解引用的情况:
//当我们打印变量p的时候,实际过程是发生了这样的变化//只是猜测,当然发生解引用是一定的//&取地址操作符//*根据地址取值操作也称之为解引用运算法,间址运算符//1.获取指针地址&p//2.获取array的地址&((&p).array)//3.获取底层数组实际内容*&((&p).array)未实现自动借用的函数传递过程,也是通过复制指针的方式来传递的,内容如下:
packagemainimport("fmt")funcchange(p1[]int){fmt.Printf("p1的内存地址是:%p\n",&p1)//p1的内存地址是:0xc0000a6048fmt.Printf("函数里接收到slice的内存地址是:%p\n",p1)//函数里接收到slice的内存地址是:0xc00008c030p1=append(p1,30)}funcmain(){p:=make([]int,3)//抛出一个指针p=append(p,20)fmt.Printf("p的内存地址是:%p\n",&p)//p的内存地址是:0xc00009a018fmt.Printf("slice的内存地址是:%p\n",p)//slice的内存地址是:0xc00008c030change(p)//重新生成一份地址p1指向slice地址fmt.Printf("修改之后p的内存地址%p\n",&p)//修改之后p的内存地址0xc00009a018fmt.Printf("修改之后slice的内存地址%p\n",p)//修改之后slice的内存地址0xc00008c030fmt.Println("修改之后的slice:",p)//修改之后的slice[00020]fmt.Println(*&p)//[00020]}需要注意的是,在函数传递的过程中copy的不是slice内部指向底层数组的指针,而是在makeslice函数所返回的指针。
源码实现大家在看一些老旧的文章的时候,可能看到过这样的说法:make返回的是slice的实例。但其实这种说法已经过时了,在Golang1.2版本之后make返回的就是实例的指针。
githubpr地址:https://github.com/golang/go/commits/dev.boringcrypto.go1.12/src/runtime/slice.go
扩展其实和slice类似的还有map,chan。
先说map,map的官网定义:“Goprovidesabuilt-inmaptypethatimplementsahashtable.Maptypesarereferencetypes,likepointersorslices.”而chan和map一样也是一个指针,也就是说二者和slice的原理相似。
funcmakemap(t*maptype,hintint,h*hmap)*hmap{mem,overflow:=math.MulUintptr(uintptr(hint),t.bucket.size)ifoverflow||mem>maxAlloc{hint=0}...}funcmakechan(t*chantype,sizeint)*hchan{...mem,overflow:=math.MulUintptr(elem.size,uintptr(size))ifoverflow||mem>maxAlloc-hchanSize||size<0{panic(plainError("makechan:sizeoutofrange"))}...}如果平时的使用中不注意,会出现一些不必要的麻烦,如:
packagemainimport"fmt"typeInfoInsstruct{Namestringinfo[]string}funcNewInfoIns()InfoIns{returnInfoIns{Name:"",info:nil,}}func(n*InfoIns)SetInfo(info[]string){n.info=info}funcmain(){infoIns:=NewInfoIns()info:=[]string{"p1","p2","p3"}infoIns.SetInfo(info)info[1]="p4"fmt.Println(infoIns.info)//[p1p4p3]}这里的InfoIns在SetInfo之后存的是info的地址。一旦info在后续有改动InfoIns中的内容也随之会被改动。解决的方法是在SetInfo的时候重新申请一份地址。
func(n*InfoIns)SetInfo(info[]string){n.info=make([]string,len(info))copy(n.info,info)}脚注借助Goland查看Go源码的方式:Ctrl+Shift+f全局搜索,选择Scope中的ALLPlace。
推荐阅读一文聊透IP地址的那些事
Golang常见设计模式之装饰模式
原文:https://juejin.cn/post/7099268340513767461