您的位置:首页 > 编程语言 > Go语言

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

2018-07-20 19:32 295 查看

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

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

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

[code]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)  // 对应函数内变量名所在地址
}

返回结果如下:

[code]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,希望有大神予以指正,谢谢!

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: