Go 到底有没有引用传参(对比 C++ )
2017-09-02 14:53
323 查看
Go 到底有没有引用传参(对比 C++ )
C++ 中三种参数传递方式
值传递:
最常见的一种传参方式,函数的形参是实参的拷贝,函数中改变形参不会影响到函数外部的形参。一般是函数内部修改参数而又不希望影响到调用者的时候会采用值传递。指针传递
形参是指向实参地址的一个指针,顾名思义,在函数中对形参指向的内容操作,实参本身会被修改。引用传递
在 C++ 中,引用是变量的别名,实际上是同一个东西,在内存中也存在同一个地址。换句话说,不管在哪里对引用操作,都相当直接操作被引用的变量。下面看 demo:
#include <iostream> //值传递 void func1(int a) { std::cout << "值传递,变量地址:" << &a << ", 变量值:" << a << std::endl; a ++ ; } //指针传递 void func2 (int* a) { std::cout << "指针传递,变量地址:" << a << ", 变量值:" << *a << std::endl; *a = *a + 1; } //引用传递 void func3 (int& a) { std::cout << "指针传递,变量地址:" << &a << ", 变量值:" << a << std::endl; a ++; } int main() { int a = 5; std::cout << "变量实际地址:" << &a << ", 变量值:" << a << std::endl; func1(a); std::cout << "值传递操作后,变量值:" << a << std::endl; std::cout << "变量实际地址:" << &a << ", 变量值:" << a << std::endl; func2(&a); std::cout << "指针传递操作后,变量值:" << a << std::endl; std::cout << "变量实际地址:" << &a << ", 变量值:" << a << std::endl; func3(a); std::cout << "引用传递操作后,变量值:" << a << std::endl; return 0; }
输出结果如下:
变量实际地址:0x28feac, 变量值:5 值传递,变量地址:0x28fe90, 变量值:5 值传递操作后,变量值:5 变量实际地址:0x28feac, 变量值:5 指针传递,变量地址:0x28feac, 变量值:5 指针传递操作后,变量值:6 变量实际地址:0x28feac, 变量值:6 指针传递,变量地址:0x28feac, 变量值:6 引用传递操作后,变量值:7
Go 中的参数传递
上面介绍了 C++ 的三种参数传递方式,值传递和指针传递容易理解,那么 Go 是不是也有这些传参方式呢?这引起过争论,但是对比 C++ 的引用传递的概念,我们可以说,Go 没有引用传递方式。为什么这么说,因为 Go 没有变量的引用这一概念。但是 Go 有引用类型,这个稍后再解释。先看一个 Go 传值和传指针的例子:
package main import ( "fmt" ) func main() { a := 1 fmt.Println( "变量实际地址:", &a, "变量值:", a) func1 (a) fmt.Println( "值传递操作后,变量值:", a) fmt.Println( "变量实际地址:", &a, "变量值:", a) func2(&a) fmt.Println( "指针传递操作后,变量值:", a) } //值传递 func func1 (a int) { a++ fmt.Println( "值传递,变量地址:", &a, "变量值:", a) } //指针传递 func func2 (a *int) { *a = *a + 1 fmt.Println( "指针传递,变量地址:", a, "变量值:", *a) }
输出结果如下:
变量实际地址: 0xc04203c1d0 变量值: 1 值传递,变量地址: 0xc04203c210 变量值: 2 值传递操作后,变量值: 1 变量实际地址: 0xc04203c1d0 变量值: 1 指针传递,变量地址: 0xc04203c1d0 变量值: 2 指针传递操作后,变量值: 2
可以看出,Go 基本类型的值传递和指针传递和 C++ 并没有什么不同,但是它没有变量的引用这一概念。那 Go 的引用类型怎么理解呢?
Go 的引用类型
在 Go 中,引用类型包含切片、字典、通道等。以切片为例,传切片是传引用么?举个例子:
package main import ( "fmt" ) func main() { m1 := make([]string, 1) m1[0] = "test" fmt.Println("调用 func1 前 m1 值:", m1) func1(m1) fmt.Println("调用 func1 后 m1 值:", m1) } func func1 (a []string) { a[0] = "val1" fmt.Println("func1中:", a) }
输出结果如下:
调用 func1 前 m1 值: [test] func1中: [val1] 调用 func1 后 m1 值: [val1]
函数中对切片做出的修改影响了实际参数的值。是不是说这事引用传递?
其实并不是,要回答这个问题,首先得搞清楚调用函数切片
m1到底有没有改变。首先我们要认清楚切片的本质。
一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度。
也就是说,上面我们打印的并不是切片本身,而是切片指向的数组。再举个例子,验证一下切片到底有没有发生变化。
package main import ( "fmt" ) func main() { m1 := make([]string, 1) m1[0] = "test" fmt.Println("调用 func1 前 m1 值:", m1, cap(m1)) func1(m1) fmt.Println("调用 func1 后 m1 值:", m1, cap(m1)) } func func1 (a []string) { a = append(a, "val1") fmt.Println("func1中:", a, cap(a)) }
输出结果如下:
调用 func1 前 m1 值: [test] 1 func1中: [test val1] 2 调用 func1 后 m1 值: [test] 1
这个结果说明,调用前后切片并没有发生变化。之前例子中所谓的“变化”其实是切片中指向数组的指针指向的数组的元素发生了变化,这句话可能比较拗口,但实际如此。再次证明,引用类型的传参不是 pass-by-reference 。
想透彻的了解
一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度这句话,有兴趣可以看这篇文章:http://www.2cto.com/kf/201604/499045.html。学习一下切片的内存模型。
总结
总结很简单,语言也需要透过现象看本质。还有本文的结论需要记住:There is no pass-by-reference in Go.
相关文章推荐
- Go 到底有没有引用传参(对比 C++ )
- Go语言到底有没有引用传参(对比 C++ )
- Java到底有没有引用传递?
- c++中引用形参与非引用形参对比
- c++==与c内存分配释放及引用的对比(3)
- 指针数组,数组指针,傻傻分不清?常量指针,指针常量到底谁不变?数组引用,引用数组,到底有没有?
- java到底有没有引用传值
- c++引用有没有存储空间?
- C++指针与引用(一):函数传参,是该传对象,还是传指针,抑或是引用
- C++引用和指针简单样例对比
- C++的引用类型的变量到底占不占用内存空间?
- C++按值传参、按地址传参、按引用传参外加const比较详解
- GO和C++代码对比,GO少个25%代码
- Go语言的传参和传引用
- python引用在函数传参时的体现以及可变与不可变对象的对比
- C++中传值方式传参与引用常量传参的使用规则
- Java值传递和引用传递及形参实参的分析(实例对比c++)
- C#中关于引用类型做参数时的若干问题及与C/C++对比
- C++指针、引用传参操作对象----测试
- Go 语言的传参和传引用