重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
今天就跟大家聊聊有关Golang中函数传参是否存在引用传递,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
站在用户的角度思考问题,与客户深入沟通,找到蔚县网站设计与蔚县网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站设计制作、网站制作、企业官网、英文网站、手机端网站、网站推广、域名申请、网络空间、企业邮箱。业务覆盖蔚县地区。
Go里边函数传参只有值传递一种方式。
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
概念总给人一种教科书的感觉,写点代码验证下。
func main() {
a := 10
fmt.Printf("%#v\n", &a) // (*int)(0xc420018080)
vFoo(a)
}
func vFoo(b int) {
fmt.Printf("%#v\n", &b) // (*int)(0xc420018090)
}
注释内容是我机器的输出,你如果运行会得到不一样的输出
根据代码来解释下,所谓的值传递就是:实参 a在传递给函数 vFoo的形参 b后,在 vFoo的内部,b会被当作局部变量在栈上分配空间,并且完全拷贝 a的值。
代码执行后,我们看到的结果便是:a、b拥有完全不同的内存地址, 说明他们虽然值相同(b拷贝的a,值肯定一样),但是分别在内存中不同的地方,也因此在函数 vFoo内部如果改变 b的值,a是不会受到影响的。
图中左侧是还未调用时,内存的分配,右侧是调用函数后内存分别分配的变量。这里需要注意,就算vFoo的参数名字是a,实参与形参也分别有自己的内存空间,因为参数的名字仅仅是给程序员看的,上篇文章已经说清楚了。
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
是不是云里雾里的?还是通过代码结合来分析所谓的指针传递。
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 的地址各自互不相关,说明在参数传递中发生了值拷贝。
在函数 pFoo中,形参 p 的地址与实参 pa 的地址并不一样,但是他们在内存中的值都是变量 a 的地址,因此可以通过指针相关的操作来改变a的值。
图中 &a 表示a的地址,值为: 0xc420080008
所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
由于 Go 里边并不存在引用传递,我们常常看到说 Go 中的引用传递也是针对:Slice、Map、Channel这几种类型(这是个错误观点),因此为了解释清楚引用传递,先劳烦大家看一段 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中函数调用只有值传递,但是类型引用有引用类型,他们是:slice、map、channel。来看看官方的说法:
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]的引用,这也就是能够进行修改的原因。
Go 中函数传参仅有值传递一种方式;
slice、map、channel都是引用类型,但是跟c++的不同;
slice能够通过函数传参后,修改对应的数组值,是因为 slice 内部保存了引用数组的指针,并不是因为引用传递。
看完上述内容,你们对Golang中函数传参是否存在引用传递有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。