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.

最新文章

  1. 那些年,坑死自己的事之fread/fwrite
  2. 个人JS零碎知识整理
  3. SpaceBase – 基于 Sass 的响应式 CSS 框架
  4. js自定义延迟执行函数
  5. DevExpress GridView加入DevExpress中的右键菜单PopuMenu
  6. perl中-&gt;和=&gt;作用
  7. PHPNow升级PHP版本为5.3.5的方法
  8. AbsListView.OnScrollListener 使用注意事项
  9. WPFMediaKit照相功能
  10. SQL Server视图
  11. iOS开发——图形编程OC篇&amp;OpenGL ES2.0编程步骤
  12. Oracle statspack 安装及使用
  13. iOS摄像头和相册-UIImagePickerController-浅析(转)
  14. ViewConfiguration滑动参数设置类
  15. 硬盘重装Ubuntu12.04的感受
  16. 【日常学习】【欧拉功能】codevs2296 荣誉的解决方案卫队的一个问题
  17. Servlet实现文件上传(多文件)(三)
  18. 1.PHP连接mysql
  19. oracle存储大文本clob、blob
  20. TextView 链接显示及跳转

热门文章

  1. (一)SQL关联查询的使用技巧 (各种 join)
  2. 将域名转移到 Google Domains
  3. 使用maven搭建环境
  4. ReactiveCocoa有关集合类的使用
  5. 定时任务FluentScheduler 学习笔记 .net
  6. Stacked Regression的详细步骤和使用注意事项
  7. app请求服务器数据方法1-HttpUrlConnection
  8. [补档]暑假集训D1总结
  9. direct-path插入方式提升性能的分析
  10. (1)xcode基本设置和控制器等介绍