开篇明义,Go lang中从来就不存在所谓的“引用传递”,从来就只有一种变量传递方式,那就是值传递。因为引用传递的前提是存在“引用变量”,但是Go lang中从来就没有出现过所谓的“引用变量”,所以也就不可能存在引用传递这种变量传递的方式。

引用类型

首先,Go lang的基本数据类型是值类型,比如整数、浮点、字符串、布尔、数组及错误类型,它们本质上是原始类型,也就是不可改变的,所以对它们进行操作,一般都会返回一个新创建的值,所以把这些值传递给函数时,其实传递的是一个值的拷贝副本,这一点,基本没啥争议。

而引用类型指的是它的修改动作可以影响到任何引用到它的变量。在 Go 语言中,引用类型有切片(slice)、字典(map)、接口(interface)、函数(func) 以及通道(chan) 。

问题是,如果我们在某一个函数体内对外部定义的引用类型数据做修改操作:

package main  

import "fmt"  

func changeMap(data map[string]string) {
data["123"] = "333"
} func main() {
a := map[string]string{}
a["123"] = "123"
fmt.Println("begin:", a)
changeMap(a)
fmt.Println("after:", a)
}

程序返回:

begin: map[123:123]
after: map[123:333]

很明显,函数changeMap改变了外部的字典类型的值,那么我们就可以得出结论,引用类型的传参是使用的引用传递?

引用变量(reference variable)和引用传递(pass-by-reference)

事实上,引用变量(reference variable)和引用传递(pass-by-reference)确实存在,只不过存在于其他的语言中,比如说Python:

a = [2]
print(id(a)) def change(a):
print(id(a))
a.append(1) if __name__ == '__main__': print(a) change(a) print(a)

这里我们定义了一个可变数据类型:列表a,然后将它传入函数change中,进行修改操作,同时使用系统内置的id()方法分别打印修改前的值和内存地址以及修改后的值和内存地址,程序返回:

4311179392
[2]
4311179392
[2, 1]

这说明什么?说明变量a是引用变量(reference variable),同时它作为参数的传递方式是引用传递(pass-by-reference),证据就是它原始的内存地址和传递到函数内的内存地址是一致的,都是4311179392。

所以引用变量和引用传递应该具备如下特点:引用变量和原变量的内存地址一样。就像上面的例子里函数内引用变量a和原变量a的内存地址相同。函数使用引用传递,可以改变外部实参的值。就像上面的例子里,change函数使用了引用传递,改变了外部实参a的值。

Golang是否存在引用变量(reference variable)

Go lang中不存在引用变量:

package main  

import "fmt"  

func main() {
a := 1
var a1 *int = &a
var a2 *int = &a
fmt.Println("值", a1, " 内存地址:", &a1)
fmt.Println("值:", a2, " 内存地址:", &a2)
}

程序返回:

值 0x140000140b8  内存地址: 0x1400000e028
值: 0x140000140b8 内存地址: 0x1400000e030

和Python不同的是,在Go lang里,不可能有两个变量有相同的内存地址,所以也就不存在引用变量了。变量a1和a2的值相同,都指向变量a的内存地址,但是变量a1和a2自己本身的内存地址是不一样的,而Python里的引用变量和原变量的内存地址是相同的。

因此,在Go语言里是不存在引用变量的,也就自然没有引用传递了。

字典为什么可以做到值传递但是可以更改原对象?

因为字典虽然名字叫做字典,或者叫做map,但那并不重要,其实它是指针:

package main  

import (
"fmt"
"unsafe"
) func main() {
data := make(map[string]int)
var p uintptr
fmt.Println("字典大小:", unsafe.Sizeof(data))
fmt.Println("指针大小:", unsafe.Sizeof(p))
}

程序返回:

字典大小: 8
指针大小: 8

从占据内存空间大小就可以看出,字典和指针其实就是一种东西,那如果字典是指针,那make返回的不应该是*map[string]int吗?为什么我们使用字典传实参,从来都不加*?

在Go lang早期,的确对于字典是使用过指针形式的,但是最后Golang的设计者发现,几乎没有人使用字典不加指针,因此就直接去掉了形式上的指针符号*,类比的话,我们会发现现实中几乎从来就没有人管AC米兰叫AC米兰,都是直呼米兰,因为大家都认为米兰就是AC米兰,所以都自动省略了形式上的“AC”。

本质上,我们可以理解字典作为参数传递方式是值传递,只不过引用类型传递的是一个指向底层数据的指针,所以我们在操作的时候,可以修改共享的底层数据的值,进而影响到所有引用到这个共享底层数据的变量,这也就是为什么字典在函数内操作可以影响原对象的原因。

结语

引用类型之所以可以引用,是因为我们创建引用类型的变量,其实是一个标头值,标头值里包含一个指针,指向底层的数据结构,当我们在函数中传递引用类型时,其实传递的是这个标头值的副本,它所指向的底层结构并没有被复制传递,这也是引用类型传递高效的原因,换句话说,Go lang为了保证值传递的纯粹性,才引入了指针的概念,如果Go lang里存在引用变量和引用传递,那指针不就成了画蛇添足的浮笔浪墨了吗?

清源正本,鉴往知来,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中引用类型是否进行引用传递EP18的更多相关文章

  1. 仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16

    Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右.其中还有一些保留关键字属于"锦上添花",什么叫锦上添 ...

  2. 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17

    先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...

  3. 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06

    再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...

  4. 百亿数据百亿花, 库若恒河沙复沙,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang数据库操作实践EP12

    Golang可以通过Gorm包来操作数据库,所谓ORM,即Object Relational Mapping(数据关系映射),说白了就是通过模式化的语法来操作数据库的行对象或者表对象,对比相对灵活繁复 ...

  5. 层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10

    Go lang使用包(package)这种概念元素来统筹代码,所有代码功能上的可调用性都定义在包这个级别,如果我们需要调用依赖,那就"导包"就行了,无论是内部的还是外部的,使用im ...

  6. 兔起鹘落全端涵盖,Go lang1.18入门精炼教程,由白丁入鸿儒,全平台(Sublime 4)Go lang开发环境搭建EP00

    Go lang,为并发而生的静态语言,源于C语言又不拘泥于性能,高效却不流于古板,Python灵活,略输性能,Java严谨,稍逊风骚.君不见各大厂牌均纷纷使用Go lang对自己的高并发业务进行重构, ...

  7. 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07

    函数是基于功能或者逻辑进行聚合的可复用的代码块.将一些复杂的.冗长的代码抽离封装成多个代码片段,即函数,有助于提高代码逻辑的可读性和可维护性.不同于Python,由于 Go lang是编译型语言,编译 ...

  8. 因势而变,因时而动,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang泛型(generic)的使用EP15

    事实上,泛型才是Go lang1.18最具特色的所在,但为什么我们一定要拖到后面才去探讨泛型?类比的话,我们可以想象一下给小学一年级的学生讲王勃的千古名篇<滕王阁序>,小学生有多大的概率可 ...

  9. 巨细靡遗流程控制,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang流程结构详解EP09

    流程结构就是指程序逻辑到底怎么执行,进而言之,程序执行逻辑的顺序.众所周知,程序整体都是自上由下执行的,但有的时候,又不仅仅是从上往下执行那么简单,大体上,Go lang程序的流程控制结构一共有三种: ...

随机推荐

  1. idea部署项目运行没问题,但是页面404。

    解决方案: 这个位置不要添加内容. 参考:https://blog.csdn.net/hupixiong/article/details/105443606

  2. 记一次重复造轮子(Obsidian 插件设置说明汉化)

    杂谈 #Java脚本 因本人英语不好在使用Obsidian时,一些插件的设置英文多令人头痛.故有写一个的翻译插件介绍和设置脚本的想法.看到有些前人写的一下翻译方法,简直惨目忍睹.竟然要手动.这个应该写 ...

  3. 一个思维导图,带你深入理解 Linux 网络

    说明 思维导图 目录 内核接收网络包 内核与用户进程协作 内核发送网络包 TCP 连接 网络性能优化 GitHub 项目 说明 经朋友推荐发现一本好书:<深入理解 Linux 网络>,本文 ...

  4. Drone-比Jenkins更轻量化的持续集成部署工具

    Drone 简介 Drone 是一个基于Docker容器技术的可扩展的持续集成引擎,由GO语言编写,可用于自动化测试与构建,甚至发布.每个构建都在一个临时的Docker容器中执行,使开发人员能够完全控 ...

  5. 使用Java客户端发送消息和消费的应用

    体验链接:https://developer.aliyun.com/adc/scenario/fb1b72ee956a4068a95228066c3a40d6 实验简介 本教程将Demo演示使用jav ...

  6. 2022-7-14 java_2 第七组 刘昀航

    @ 目录 一.java约定规范 1.关于建包 2.控制台输入(Scanner) 关于Scanner的bug 计算器小练习: 二. 1.数组 建立数组的三种方式: 数据类型的初始值: 2.二维数组 使用 ...

  7. Centos7借助docker部署mysql,提供远程链接服务

    Centos7 借助docker部署mysql,并提供远程连接服务 安装docker 运行docker 注意安装docker和运行docker的步骤很简单,可以参考我学习docker的笔记 docke ...

  8. kubernetes 静态存储与动态存储

    静态存储   Kubernetes 同样将操作系统和 Docker 的 Volume 概念延续了下来,并且对其进一步细化.Kubernetes 将 Volume 分为持久化的 PersistentVo ...

  9. 分享一些小tips

    谷歌浏览器破解 google拓展商店中hackbar地址 https://chrome.google.com/webstore/detail/djmoeoifnlhjolebkehmpaocfnipk ...

  10. go更新腾讯云DNSPod的解析记录

    纯粹练手用的,大家轻喷 获取SecretId,SecretKey 打开腾讯云,登录之后打开https://console.cloud.tencent.com/cam/capi,然后新建密钥记录生成的S ...