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.jb51.net/kf/201604/499045.html。学习一下切片的内存模型。

总结

总结很简单,语言也需要透过现象看本质。还有本文的结论需要记住:

There is no pass-by-reference in Go.

以上所述是小编给大家介绍的Go语言到底有没有引用传参(对比 C++ ),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2017-08-30

C++ 中cerr和cout的区别实例详解

C++ 中cerr和cout的区别实例详解 前言: cerrThe object controls unbuffered insertions to the standard error output as a byte stream. Once the object is nstructed, the expression cerr.flags & unitbuf is nonzero. Example // iostream_cerr.cpp // compile with: /EHsc /

C++中继承与多态的基础虚函数类详解

前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

C++ 实现哈希表的实例

C++ 实现哈希表的实例 该散列表的散列函数采用了除法散列函数.乘法散列函数.全域散列函数,每一个槽都是使用有序单向链表实现. 实现代码: LinkNode.h #include<iostream> using namespace std; class Link; class LinkNode { private: int key; LinkNode* next; friend Link; public: LinkNode():key(-1),next(NULL){} LinkNode(int

C/C++实现控制台输出不同颜色字体的方法

本文实例讲述了C/C++实现控制台输出不同颜色字体的方法.分享给大家供大家参考,具体如下: 在控制台输出不同颜色的字 效果 代码: #include "stdio.h" #include "windows.h" int main(int argn, char **argv) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_GREEN); printf("Hello&q

关于C++中void*的小作用浅析

本文主要给大家分享了关于C++中void*的一些你可能不了解的小作用,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 先来看一段代码: #include <iostream> #include <string> using namespace std; void o(int* x, void* y){ cout << *x << endl; cout << x << endl; cout << *(int

关于C++的强制类型转换浅析

前言 一说起强制类型转换大家都很熟悉,相信很多学习完C++的朋友还在使用C语言的强制类型的方式 (类型)变量. C++其实也具有自己的一套强制类型转换它们分明是:static_cast  reinterpret_cast  const_cast  dynamic_cast四种类型. 那么肯定会有人好奇C++是不是闲,C语言的强制类型用的舒舒服服的,为什么要新推出来这几个? 新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换.C++中风格是static_cast<typ

C++面向对象之多态的实现和应用详解

前言 本文主要给大家介绍的是关于C++面向对象之多态的实现和应用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 多态 大家应该都听过C++三大特性之一多态,那么什么多态呢?多态有什么用?通俗一点来讲-> 多态性可以简单地概括为"一个接口,多种方法",程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念.当多态应用形参类型的时候,可以接受更多的类型.当多态用于返回值类型的时候,可以返回更多类型的数据.多态可以让你的代码拥有更好的扩展性. 多态分

Java语言面向对象编程思想之类与对象实例详解

在初学者学Java的时候,面向对象很难让人搞懂,那么今天小编就来为大家把这个思想来为大家用极为简单的方法理解吧. 首先我们来简单的阐述面向对象的思想. 面向对象: 官方的语言很抽象,我们把官方的解释和定义抛开.想想,自己有什么,对!!我们自己有手脚眼口鼻等一系列的器官.来把自己所具有的器官就可以看作我们的属性,自己是不是可以喜怒哀乐和嬉笑怒骂,这些是不是我们的行为,那么自己的具有的属性加自己有的行为就称为一个对象. 注意!!我们自己,一个个体是一个对象,因为,你是你,我是我,我们虽然有相同的,但

java中多态概念、实现原理详解

一.什么是多态? 1.多态的定义 指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用) 2.多态的作用 消除类型之间的耦合关系 3.多态的说明 近代网络小说泛滥,我们可以用它来举一个例子 某日你看见你手机上有多部小说同时更新了,比如有大主宰,雪鹰领主,龙王传说-在这里我们可以描述成如下: 小说a=大主宰 小说b=雪鹰领主 小说c=龙王传说 - 这里所表现的就是多态,大主宰,雪鹰领主,龙王传说都是小说的子类,我们仅仅可以通过小说这个

C#面向对象特征的具体实现及作用详解

众所周知,面向对象编程的特点为:封装.继承.多态.C#是一门完全面向对象的语言,由于比Java推出的时间还要晚,所以对面向对象的思想的体现比Java还要完美,那么在C#中如何体现封装.继承和多态呢?下面举例并进行说明. 1.封装 封装的好处有以下几点: ①数据不外泄,可以做到一定的保护 ②类的使用者不用考虑具体的数据运算,方便 ③程序结构性强,层次清晰,便于维护 对相关的字段.方法进行封装固然对面向对象编程起到不可缺少的重要作用,但并不代表不可以访问类或者说具体的实例化对象中的内容,而且为使用者

PHP面向对象程序设计重载(overloading)操作详解

本文实例讲述了PHP面向对象程序设计重载(overloading)操作.分享给大家供大家参考,具体如下: 重载 PHP中的"重载"与其它绝大多数面向对象语言不同,只是他们都是用的相同的名词而已.传统的"重载"是用于提供多个同名的 类方法,但各方法的参数类型和个数不同. PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法.当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用.是通过魔术方法

PHP面向对象程序设计之构造方法和析构方法详解

本文实例讲述了PHP面向对象程序设计之构造方法和析构方法.分享给大家供大家参考,具体如下: 构造方法和析构方法是对象中的两个特殊方法,它们都与对象的生命周期有关.构造方法是对象创建完成后第一个被对象自动调用的方法,这是我们在对象中使用构造方法的原因.而析构方法是对象在销毁之前最后一个被对象自动调用的方法,这也是我们在对象中使用析构方法的原因.所以通常使用构造方法完成一些对象的初始化工作,使用析构方法完成一些对象在销毁之前的清理工作. 1.构造方法 在每个声明的类中都有一个呗称为构造方法的特殊成员

JS面向对象编程实现的Tab选项卡案例详解

本文实例讲述了JS面向对象编程实现的Tab选项卡.分享给大家供大家参考,具体如下: Tab选项卡案例 下面是一个简单面向过程的Tab选项卡. <!DOCTYPE html> <html> <head> <style> #tabBox input { background: #F6F3F3; border: 1px solid #FF0000; } #tabBox .active { background: #E9D4D4; } #tabBox div { w

详解JavaScript基于面向对象之继承

一.面相对象继承机制       这个实例使用UML很好的解释了继承机制.       说明继承机制最简单的方式是,利用一个经典的例子就是几何形状.实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形.矩形和五边形都是多边形的一种,具有不同数量的边.正方形是矩形的一种,所有的边等长.这就构成了一种完美的继承关系,很好的解释了面向对象的继承机制.        在这个例子中,形状是椭圆形和多边形的基类(通常我们也可以叫它父类,所有类都由

详解Java基础篇--面向对象1(构造方法,static、this关键字)

面向对象,面向过程的区别.拿下五子棋来说: 面向过程分析: 开始游戏 黑棋先走 绘制画面 判断输赢 轮到白棋 绘制画面 判断输赢 返回步骤2 输出结果 面向对象分析: 黑白双方,双方行为是一模一样的 棋盘系统,负责绘制画面 规则系统,判断犯规.输赢 传统的面向过程编程是思考问题的解决步骤,这种思维方式适用于问题规模较小时.可是当问题规模大,要求程序有更好的可扩展性,能更快速地查错时面向对象设计思想就能体现出其优势.面向对象更接近人类地自然思维方式,将现实世界中的事物抽象为对象和对象的方法. 面向

Python面向对象之多态原理与用法案例分析

本文实例讲述了Python面向对象之多态原理与用法.分享给大家供大家参考,具体如下: 目标 多态 面向对象三大特性 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中 定义类的准则 继承 实现代码的重用,相同的代码不需要重复的编写 设计类的技巧 子类针对自己特有的需求,编写特定的代码 多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果 多态 可以 增加代码的灵活度 以 继承 和 重写父类方法 为前提 是调用方法的技巧,不会影响到类的内部设计 多态案例演练 需求 1.在