Golang中函数传参存在引用传递吗?

上篇文章后,继续来探讨下面的几个问题:c++

  1. 函数传参中值传递、指针传递与引用传递到底有什么不同?
  2. 为何说 slicemapchannel 是引用类型?
  3. Go中 slice 在传入函数时究竟是不是引用传递?若是不是,在函数内为何能修改其值?
In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
文档地址: https://golang.org/ref/spec#C...

官方文档已经明确说明:Go里边函数传参只有值传递一种方式,为了增强本身的理解,再来把每种传参方式进行一次梳理。程序员

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中若是对参数进行修改,将不会影响到实际参数。

概念总给人一种教科书的感受,写点代码验证下。golang

func main() {
    a := 10
    fmt.Printf("%#v\n", &a) // (*int)(0xc420018080)
    vFoo(a)
}

func vFoo(b int) {
    fmt.Printf("%#v\n", &b) // (*int)(0xc420018090)
}

注释内容是我机器的输出,你若是运行会获得不同的输出c#

根据代码来解释下,所谓的值传递就是:实参 a 在传递给函数 vFoo 的形参 b 后,在 vFoo 的内部,b 会被看成局部变量在栈上分配空间,而且彻底拷贝 a 的值。segmentfault

代码执行后,咱们看到的结果即是:a、b拥有彻底不一样的内存地址, 说明他们虽然值相同(b拷贝的a,值确定同样),可是分别在内存中不一样的地方,也所以在函数 vFoo 内部若是改变 b 的值,a 是不会受到影响的。数组

funcCall

图中左侧是还未调用时,内存的分配,右侧是调用函数后内存分别分配的变量。这里须要注意,就算vFoo的参数名字是a,实参与形参也分别有本身的内存空间,由于参数的名字仅仅是给程序员看的,上篇文章已经说清楚了。ide

指针传递

形参为指向实参地址的指针,当对形参的指向操做时,就至关于对实参自己进行的操做。

是否是云里雾里的?仍是经过代码结合来分析所谓的指针传递。函数

func main() {
    a := 10
    pa := &a
    fmt.Printf("value: %#v\n", pa) // value: (*int)(0xc420080008)
    fmt.Printf("addr: %#v\n", &pa) // addr: (**int)(0xc420088018)
    pFoo(pa)
}

func pFoo(p * int) {
    fmt.Printf("value: %#v\n", p) // value: (*int)(0xc420080008)
    fmt.Printf("addr: %#v\n", &p) // addr: (**int)(0xc420088028)
}

定义了一个变量 a,并把地址保存在指针变量 pa 里边了。按照咱们定的结论,Go中只有值传递,那么指针变量pa传给函数的形参p后,形参将会是它在栈上的一份拷贝,他们自己将各自拥有不一样的地址,可是两者的值是同样的(都是变量a的地址)。上面的注释部分是我程序运行后的结果,pa 与 p 的地址各自互不相关,说明在参数传递中发生了值拷贝。lua

在函数 pFoo 中,形参 p 的地址与实参 pa 的地址并不同,可是他们在内存中的值都是变量 a 的地址,所以能够经过指针相关的操做来改变a的值。
funcCallspa

图中 &a 表示a的地址,值为: 0xc420080008

引用传递

所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

因为 Go 里边并不存在引用传递,咱们经常看到说 Go 中的引用传递也是针对:SliceMapChannel 这几种类型(这是个错误观点),所以为了解释清楚引用传递,先劳烦你们看一段 C++ 的代码(固然很是简单)。

void rFoo(int & ref) {
    printf("%p\n", &ref);// 0x7ffee5aef768
}

int main() {
    int a = 10;
      printf("%p\n", &a);// 0x7ffee7307768
    int & b = a;
    printf("%p\n", &b);// 0x7ffee5aef768
    rFoo(b);
    return 0;
}

这里就是简单的在main中定义一个引用,而后传给函数 rFoo,那么来看看正统的引用传递是什么样的?

这里 b 是 a 的别名(引用,不清楚的能够看我上篇文章),所以a、b一定具有相同的地址。那么按照引用传递的定义,实参 b 传给形参 ref 以后,ref 将是 b 的别名(也即a、b、ref都是同一个变量),他们将拥有相同地址。经过在 rFoo 函数中的打印信息,能够看到三者具备彻底形同的地址,这是所谓的引用传递。

Go中没有引用传递

Go中函数调用只有值传递,可是类型引用有引用类型,他们是:slicemapchannel。来看看官方的说法:

There's a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use. Changing these types to act as references to the associated, shared data structures resolved these issues. This change added some regrettable complexity to the language but had a large effect on usability: Go became a more productive, comfortable language when it was introduced.

大概意思是说:最开始用的是指针语法,因为种种缘由改为了引用,可是这个引用与C++的引用是不一样的,它是共享关联数据的结构。关于这个问题的深刻讨论我会放到 slice 相关文章中进行讨论,如今回到今天讨论的主题。

那么Go的引用传递源起何处?我以为让你们误解的是,map、slice、channel这类引用类型在传递到函数内部,能够在函数内部对它的值进行修改而引发的误会。

针对这种三种类型是 by value 传递,咱们用 slice 来进行验证。

func main() {
    arr := [5]int{1, 3, 5, 6, 7}
    fmt.Printf("addr:%p\n", &arr)// addr:0xc42001a1e0
    s1 := arr[:]
    fmt.Printf("addr:%p\n", &s1)// addr:0xc42000a060

    changeSlice(s1)
}

func changeSlice(s []int) {
    fmt.Printf("addr:%p\n", &s)// addr:0xc42000a080
    fmt.Printf("addr:%p\n", &s[0])// addr:0xc42001a1e0
}

代码中定义了一个数组 arr,而后用它生成了一个slice。若是go中存在引用传递,形参 s 的地址应该与实参 s1 同样(上面c++的证实),经过实际的状况咱们发现它们具有彻底不一样的地址,也就是传参依然发生了拷贝——值传递。

可是这里有个奇怪的现象,你们看到了 arr 的地址与 s[0] 有相同的地址,这也就是为何咱们在函数内部可以修改 slice 的缘由,由于当它做为参数传入函数时,虽然 slice 自己是值拷贝,可是它内部引用了对应数组的结构,所以 s[0] 就是 arr[0] 的引用,这也就是可以进行修改的缘由。

funcCall

小结

  • Go 中函数传参仅有值传递一种方式;
  • slicemapchannel都是引用类型,可是跟c++的不一样;
  • slice可以经过函数传参后,修改对应的数组值,是由于 slice 内部保存了引用数组的指针,并非由于引用传递。

接下来的文章尝试解析下:slice 为何必定要用 make 进行初始话,它初始化作了哪些事情?它每次动态扩展容量的时候进行了什么操做?

相关文章
相关标签/搜索