不要在说go存在引用传递了

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 theint, 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 := 1
    fmt.Println(x) // 输出 1
    modify(x)
    fmt.Println(x) // 仍然输出 1,因为修改的是 x 的副本
}

func modify(x int) {
    x = 2
}
  • 引用类型的例子,map、slice、channel都是引用类型,它们的底层实际都是一个指针,传递的是指针,比如slice是一个数组指针,map变量的本质的是指针*hmap,channel变量本质是指针*hchan。
package main

import "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 main

import "fmt"

func main() {
    m := make(map[string]int)
    m["age"] = 1

    fmt.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 main

import "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 main

import "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,所以最终影响了原结构体,实际上还是值传递,只是指向了原结构体字段,有了具体指向。

所以,go中的传递全都是值传递!!!可别说是引用传递了!!!!

你可能感兴趣的:(golang,开发语言,后端,中间件,面试)