Go是引用传递还是值传递?
先总结结论:go语言中,所有的参数传递全都是值传递!!!,最终传递的都是一个副本,或者说拷贝了一份。
可以看一下人家官方文档怎么解释的:
As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an
intvalue to a function makes a copy of the
int, and passing a pointer value makes a copy of the pointer, but not the data it points to
翻译过来就是:
与 C 家族的所有语言一样,Go 中的所有内容都是按值传递的。也就是说,一个函数总是得到被传递的东西的副本,就好像有一个赋值语句将值赋给参数一样。例如,将int
值传递给函数会生成 的副本int
,传递指针值会生成指针的副本,但不会复制它指向的数据。
什么是值传递?
将实参的值传递给形参,形参是实参的一份拷贝,实参和形参的内存地址不同。函数内对形参值内容的修改,是否会影响实参的值内容,取决于参数是否是引用类型
什么是引用传递?
将实参的地址传递给形参,函数内对形参值内容的修改,将会影响实参的值内容。Go语言是没有引用传递的,在C++中,函数参数的传递方式有引用传递。
下面分别举三个例子
- 值类型的例子,go语言中,基本类型以及数组类型都是值类型,意味着它们是被复制一份给函数。
// 这个容易理解,传递的是x的副本,函数内部的改变不影响外面的变量
func main() {x := 1fmt.Println(x) // 输出 1modify(x)fmt.Println(x) // 仍然输出 1,因为修改的是 x 的副本
}func modify(x int) {x = 2
}
- 引用类型的例子,map、slice、channel都是引用类型,它们的底层实际都是一个指针,传递的是指针,比如slice是一个数组指针,map变量的本质的是指针*hmap,channel变量本质是指针*hchan。
package mainimport "fmt"func main() {var s = []int64{1, 2, 3}fmt.Printf("直接对原始切片取地址%v \n", &s)fmt.Printf("原始切片的内存地址: %p \n", s)fmt.Printf("原始切片第一个元素的内存地址: %p \n", &s[0])modifySlice(s)fmt.Printf("改动后的值是: %v\n", s)
}func modifySlice(s []int64) {fmt.Printf("直接对函数里接收到切片取地址%v\n", &s)fmt.Printf("函数里接收到切片的内存地址是 %p \n", s)fmt.Printf("函数里接收到切片第一个元素的内存地址: %p \n", &s[0])s[0] = 10
}直接对原始切片取地址&[1 2 3]
原始切片的内存地址: 0xc0000b8000
原始切片第一个元素的内存地址: 0xc0000b8000
直接对函数里接收到切片取地址&[1 2 3]
函数里接收到切片的内存地址是 0xc0000b8000
函数里接收到切片第一个元素的内存地址: 0xc0000b8000
改动后的值是: [10 2 3]
单从slice的结构体去看,slice的底层是一个数组指针,指向的是底层数组第一个元素,所以在进行传参的时候打印出来的地址一样,但是实际还是值传递,因为从modifySlice中是永远改变不了len和cap的,除非传递&slice
map的例子:
package mainimport "fmt"func main() {m := make(map[string]int)m["age"] = 1fmt.Printf("原始map的内存地址是:%p\n", &m)modifyMap(m)fmt.Printf("改动后的值是: %v\n", m)
}func modifyMap(m map[string]int) {fmt.Printf("函数里接收到map的内存地址是:%p\n", &m)m["age"] = 2
}原始map的内存地址是:0xc00000e038
函数里接收到map的内存地址是:0xc00000e048
改动后的值是: map[age:2
这个就好理解了,modify之后和之前的内存地址不一样,说明是值传递。
- 从传递结构体和传递结构体指针讲解一下值传递
package mainimport "fmt"type Person struct {Name string
}func main() {p := &Person{Name: "aa",}modifyStruct(p)fmt.Println(p.Name) // aa
}func modifyStruct(p *Person) {p = nil
}
为什么这个打印出来是aa呢,不是传递指针了吗,为什么不是打印不是nil呢?
因为go函数传递都是值传递,即使传递了指针,实际就是对指针的一份拷贝,p = nil就是对新的指针指向的nil,并不是对原结构体指向nil,所以并不影响原结构体。
那我传递指针有什么作用呢?----->那就看下一个例子
package mainimport "fmt"type Person struct {Name string
}func main() {p := &Person{Name: "aa",}modifyStruct(p)fmt.Println(p.Name) // bb
}func modifyStruct(p *Person) {p.Name = "bb"
}
这个为什么就是bb了呢?
因为拷贝过来的指针指向了原结构体的name字段,虽然拷贝了指针,但是这个指针指向了原来结构体的name字段,也就是aa,我将aa改变成了bb,所以最终影响了原结构体,实际上还是值传递,只是指向了原结构体字段,有了具体指向。