GoLang之Go 的参数是如何传递的

一个程序中,变量分为变量名和变量内容,变量内容的存储一般会被分配到堆和栈上。而在 Go 语言中有两种传递变量的方式值传递和引用传递。其中值传递会直接将变量内容附在变量名上传递,而引用传递会将变量内容的地址附在变量名上传递。

“Go 的参数是如何传递的?”你会怎么回答呢?
这个问题其实只有一个答案。因为在 Golang 中所有的类型传递都是通过值传递实现的,而不是引用传递,即使是指针的传递也是通过 copy 指针的方式进行。另外对于一些包裹了底层数据的数据结构,其值传递的过程中,复制的也只是实例的指针,而不是底层数据所暴露出来的指针。

下面以 Go 版本 1.8 的 slice 为例来简单了解一下:
可以看到 slice 在初始化的过程中调用了 runtime 中的 makeslice 函数,这个函数会将 slice 的地址返回给接受的变量。

func makeslice(et *_type, len, cap int) unsafe.Pointer {}

下面打印时出现的是的内容,这是因为 Go 内部实现了自动解引用(即 Go 内部实现的解引用操作)。 自动解引用时 receive 会从指针类型转变为值类型。顺带一提自动取引用时 receiver 会从值类型转变为指针类型。

func main() {
	p := make([]int, 0)
	fmt.Printf("变量p的地址%p\n", &p)  //变量p的地址0xc000004078
	fmt.Printf("slice的地址%p\n", p) //slice的地址0x391438

	a := 4
	fmt.Printf("%p\n", &a) //0xc0000aa040
	fmt.Printf("%p\n", a)  //%!p(int=4)
}

9219e4e183db40bf9c44ab945a1fad23.png

如果未实现自动解引用时会怎样呢?下面是未实现自动解引用的情况:
当我们打印变量p的时候,实际过程是发生了如下这样的变化,只是猜测,当然发生解引用是一定的:
取地址操作符,根据地址取值操作 也称之为解引用运算法,间址运算符
1.获取指针地址 &p
2.获取array的地址 &((&p).array)
3.获取底层数组实际内容 *&((&p).array)
未实现自动借用的函数传递过程,也是通过复制指针的方式来传递的,内容如下:

45e91aea15bb4c56bcf25a9b55bed8e4.png

需要注意的是,在函数传递的过程中 copy 的不是 slice 内部指向底层数组的指针,而是在 makeslice 函数所返回的指针。


func change(p1 []int) {
	fmt.Printf("p1的内存地址是: %p\n", &p1)        // p1的内存地址是: 0xc000088018
	fmt.Printf("函数里接收到slice的内存地址是:%p\n", p1) // 函数里接收到slice的内存地址是:0xc00000a390
	p1 = append(p1, 30)
}

func main() {
	p := make([]int, 3) // 抛出一个指针
	p = append(p, 20)
	fmt.Printf("p的内存地址是: %p\n", &p)      // p的内存地址是: 0xc000004078
	fmt.Printf("slice的内存地址是:%p\n", p)    // slice的内存地址是:0xc00000a390
	change(p)                            // 重新生成一份地址p1 指向slice地址
	fmt.Printf("修改之后p的内存地址 %p\n", &p)    // 修改之后p的内存地址 0xc000004078
	fmt.Printf("修改之后slice的内存地址 %p\n", p) // 修改之后slice的内存地址 0xc00000a390
	fmt.Println("修改之后的slice:", p)        // 修改之后的slice [0 0 0 20]
	fmt.Println(*&p)                     // [0 0 0 20]
}

大家在看一些老旧的文章的时候,可能看到过这样的说法:make 返回的是 slice 的实例。但其实这种说法已经过时了,在 Golang 1.2 版本之后 make 返回的就是实例的指针。
其实和 slice 类似的还有 map,chan。
先说 map,map 的官网定义:“Go provides a built-in map type that implements a hash table.Map types are reference types, like pointers or slices.”而 chan 和 map 一样也是一个指针,也就是说二者和 slice 的原理相似。

func makemap(t *maptype, hint int, h *hmap) *hmap {}
func makechan(t *chantype, size int) *hchan {}