Go 的引用类型

废话不多说,先看一段简化的代码

func main() {
    m := make(map[int]int)
    m[1]=1
    change(m)
    fmt.Println(m)
}
func change(m map[int]int){
    m[1] = 100
}

最终打印出来的结果

E:\go\projectOne\blog>go run main.go
map[1:100]

虽然我们传递的是一个 map 变量给函数处理,打印出来后发现 m 已经被函数修改。类似于引用传递的效果。
这是由于 Go 的引用数据类型的结果

值类型#

基本数据类型,int,float,bool,string, 以及数组和 struct 特点:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放

type User struct {
    Age int
}
func main() {
    var user User
    user.Age = 11
    change(user)
    fmt.Println(user) //{11}
}

func change(user User){ //值拷贝
    user.Age = 100
}

引用类型#

变量存储的是一个地址,这个地址存储最终的值。内存通常在 堆上分配。通过 GC 回收。

引用类型: 指针、slice 切片、管道 channel、接口 interface、map、函数等

func main() {
    m := make(map[int]int)
    m[1]=1
    change(m)
    fmt.Println(m) //map[1:100]
}
func change(m map[int]int){ //也是值拷贝,只是拷贝的是地址,相当于引用处理
    m[1] = 100
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
走出舒适区
讨论数量: 5

:see_no_evil:我一直把这些类型当对象看待....

5年前 评论
Runtoweb3 (楼主) 5年前
DukeAnn

首先发表下观点: Golang 里面是没有引用传递的,当时刚看 Golang 的📚,这句话印象深刻,都是指针传递和值传递。有些很容易把指针传递混淆成引用传递。

指针传递: 形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

引用传递: 形参相当于是实参的 “别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈 中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过 栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

如果是引用传递那如下示例就会打印出

map[c:d]
package main

import "fmt"

func main() {
    m := make(map[string]string)
    m = map[string]string{"a": "b"}
    change(m)
    fmt.Println(m)
}

func change(m map[string]string) {
    m = map[string]string{"c": "d"}
}

实际上打印出:

map[a:b]

不知道我理解的到不到位,欢迎指正

5年前 评论
Runtoweb3 (楼主) 5年前
农村闲散劳动力

是的 和楼上一样 go 没有引用
感兴趣可以看看这篇文章 www.cnblogs.com/codedoge/p/9895081...

5年前 评论
Runtoweb3 (楼主) 5年前

上面的代码不对.

file

func TestLianxi2(t *testing.T) {
m := make(map[string]string)
m = map[string]string{“a”: “b”}
fmt.Printf (“变量本身的地址 % p\n”, &m)
fmt.Printf (“变量存储的地址 % p\n”, m)

change1(m)
fmt.Println(m)
}
func change1(m map[string]string) {
fmt.Printf (“变量本身的地址 % p\n”, &m)
fmt.Printf (“变量存储的地址 % p\n”, m)
m = map[string]string{“c”: “d”}
// 应该写成
//m[“a”] = d
fmt.Printf (“变量本身的地址 % p\n”, &m)
fmt.Printf (“变量存储的地址 % p\n”, m)

}

变量本身的地址 0xc000006038
变量存储的地址 0xc000052540
变量本身的地址 0xc000006040
变量存储的地址 0xc000052540
变量本身的地址 0xc000006040
变量存储的地址 0xc000052570

都是复制。只是指向了同一个地址。操作同一个地址下的东西。值会改变. m = map [string] string {“c”: “d”} 指向了新的地址。所以原来的不会改变。上面的图很清楚了

我学 c++ 的时候就是这样理解的。欢迎指正

5年前 评论
Runtoweb3 (楼主) 5年前

变量在栈中分配还是在堆中分配跟这个变量是值类型还是引用类型无关吧?全局变量在数据段中,已初始化在.data,未初始化在.bss 中,应该跟 C 是一致的;局部变量在函数的栈帧中,函数返回后栈帧也被回收,该函数的所有局部变量的生命周期结束,如果局部变量被函数外的其他地方引用了,那这个变量就不仅仅是这个函数局部变量了,会在堆中重新分配空间,同时原来的栈中的局部变量被替换为堆中新分配的空间的指针,即变量逃逸;堆供用户程序手动申请的空间使用,所有通过 new 或 make 创建的复合类型(struct map slice 等)的值,都在堆中分配,局部变量保存指针,跟变量是值类型还是引用类型无关,值类型的 struct,也是在存在堆中的,栈中的局部变量存指针,只有局部变量是基本类型才会直接保存在栈中。引用类型本质上是包含了指针的复合类型,所以引用类型的值都在堆中分配,但是说值类型在栈中应该是不对的,对于局部变量,只有基本类型的值才直接在栈中保存(不发生逃逸的情况下),非基本类型的值和发生逃逸的基本类型的值也都是在堆中保存的,栈中存指针。不知道我的理解有没有不对的地方?

4年前 评论