关于GO语言中值类型与引用类型的思考

首先需要明确的是,在GO语言中,我们利用fmt.Printf("%p", ptr)来显示指针的地址。

先看结论,这里引用GO语言圣经中3.1.2章节中关于值语义和引用语义的论述:

可以得知切片、map、channel和接口是引用类型。

package main

import (
	"fmt"
)

func main() {
	a := make(map[int]string, 1)
	a[1] = "a"
	fmt.Printf("a: %p\n", a)
	fmt.Printf("&a: %p\n", &a)
	a[3] = "c"
	fmt.Printf("a: %p\n", a)
	fmt.Printf("&a: %p\n", &a) // a的cap为1, 添加a[3]发生扩容, 但是a和&a都没变, 说明扩容不会改变地址

	modMap(a)
	fmt.Println("a value: ", a) // 修改后的a变成{1:a 3:c 5:t}
	fmt.Printf("a after mod: %p\n", a)
	fmt.Printf("&a after mod: %p\n", &a) // a在函数修改之后a和&a也没变

	newA := a
	fmt.Printf("newA: %p\n", newA)  // a赋值给了newA, 地址不变
	fmt.Printf("&newA: %p\n", &newA)  // a和newA两个变量名不同所以 &newA与&a不一样
	newA = map[int]string{11:"aa"}  // newA从新赋值
	fmt.Printf("newA: %p\n", newA)  // newA地址变化
	fmt.Printf("&newA: %p\n", &newA)  // 但&newA不变

    modPtr(&newA)
	fmt.Println("newA after modPtr: ", newA)
	fmt.Printf("newA: %p\n", newA)  // newA值变化
	fmt.Printf("&newA: %p\n", &newA)  // 但&newA不变

	c := 10
	modInt(c)
	fmt.Printf("&c after mod: %p\n", &c)
}

func modMap(p map[int]string) {
	p[1] = "aa"
	//p = nil
	fmt.Println("p value: ", p)
	fmt.Printf("a in func: %p\n", p)
	fmt.Printf("&a in func: %p\n", &p) // 这里&a变了, 但是a还是没变
}

func modInt(n int) {
	n++
	fmt.Printf("&c in func: %p\n", &n) // 这里的&n 和外边的 &c不一样, 所以我还是觉得&c和&a一样, 确实是存的变量名的地址
	// 由于传值导致了变量拷贝, 所以地址是不同的
}

func modPtr(a *map[int]string){
	*a = nil
	fmt.Printf("newA in ptrfunc: %p\n", a)  // 由于是指针,对应函数外&newA地址,不变
	fmt.Printf("&newA in ptrfunc: %p\n", &a)  // 对应函数内变量名所在地址
}

返回结果如下:

a: 0xc420076180
&a: 0xc42000c028
a: 0xc420076180
&a: 0xc42000c028
p value:  map[1:aa 3:c]
a in func: 0xc420076180
&a in func: 0xc42000c038
a value:  map[1:aa 3:c]
a after mod: 0xc420076180
&a after mod: 0xc42000c028
newA: 0xc420076180
&newA: 0xc42000c040
newA: 0xc420076210
&newA: 0xc42000c040
newA in ptrfunc: 0xc42000c040
&newA in ptrfunc: 0xc42000c048
newA after modPtr:  map[]
newA: 0x0
&newA: 0xc42000c040
&c in func: 0xc4200160d0
&c after mod: 0xc4200160c8

其中相关的猜测写在了注释中,根据结果总结如下:

1、对于map一类本身是指针的类型,map变量名为地址,&map为变量名存在的地址;

2、当用一个变量创建一个引用语义的数据时(slice/map),这个变量其实“是”(相当于)指针,函数传参时进行了拷贝,拷贝的是指针,通过这种方式达到共享同一份数据的目的。 而如果对指针变量重新复制的话(=nil或赋新值),其实是将copy的这个指针指向了新值而已,外面的那个指针变量还是指向的原来的数据。

3、函数的调用为值传递,而并非用slice/map时就是引用传递,但需要注意的时,GO语言中传指针类型确实为引用(废话都指针了还不是引用?),如例子中modPtr方法所示。由于是指针,在函数内进行赋值nil的操作影响了函数外的map。

4、在实际过程如果传参使用了引用类型,注意修改值时是否会发生变化,避免引起错误。

以上为本人与论坛网友Leigg关于传参问题讨论得到的结论,谨以此博客做过程记录,原贴链接如下:https://studygolang.com/topics/5997,希望有大神予以指正,谢谢!

猜你喜欢

转载自blog.csdn.net/yuanlaidewo000/article/details/81133350