Go 参数传递是传值还是传引用
什么是传值(值传递)?
传值的意思是:函数传递的总是原来这个东西的一个副本、一个副拷贝。比如我们传递一个 int 类型的参数,传递
的其实这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个指针的一份拷贝,而不是这个指针指向的
值
对于 int 这类基础类型的我们可以很容易理解,它们就是一个拷贝,但是指针呢?我们可以通过它修改原来的值,怎
么会是一个拷贝呢?看如下示例:
package main import "fmt" func modify(ip *int) {
fmt.Printf("函数里接收指针的内存地址是:%p\n",&ip) *ip = 1
} func main() {
i := 10
ip := &i fmt.Printf("原始指针的内存地址是:%p\n",&ip) modify(ip)
fmt.Println("int 值被修改了,新值是:",i)
}
输出结果如下:
原始指针的内存地址是:0xc00007e018
函数里接收指针的内存地址是:0xc00007e028
int 值被修改了,新值是: 1
我们都知道,任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指
针的内存
首先,我们声明了一个变量 i,值为 10,它的内存存放地址是 0xc000056080,通过这个地址我们可以找到变量 i,
这个内存地址也就是变量 i 的指针 ip,指针 ip 是一个指针类型的变量,它也需要内存存放它,它的内存地址是 0xc0000
7e018,在我们传递指针变量 ip 给 modify 函数的时候,是对这个指针变量的一个拷贝,所以新拷贝的指针变量 ip 它的内
存地址就变了,是新的 0xc00007e028,不管是 0xc00007e018 还是 xc00007e028 我们都可以称之为指针的指针,他们
指向同一个指针 0xc000056080,这个 0xc000056080 又指向 变量 i,这也就是为什么我们可以修改变量 i 的值
什么是引用传递?
在 Go 语言中没有引用传递,以上面的例子为例,如果在 modify 函数里面打印的地址不变,那么就是引用传递
迷惑 Map
了解清楚了值传递和引用传递,但是对于 Map 类型来说,可能觉得还是迷惑,一个是我们可以通过方法修改它的内容,
二个是它没有明显的指针,如下示例:
package main import "fmt" func modify(p map[string]int) {
fmt.Printf("函数里接收到的map内存地址:%p\n",&p) p["张三"] = 25
} func main() {
// 声明一个 map
persons := make(map[string]int)
persons["张三"] = 19 mp := &persons
fmt.Printf("原始map的内存地址是:%p\n",mp) modify(persons)
fmt.Println("map值被修改了,新值是:",persons)
}
运行打印输出结果:
原始map的内存地址是:0xc000006028
函数里接收到的map内存地址:0xc000006038
map值被修改了,新值是: map[张三:25]
两个内存地址不一样,说明是值传递,那么为什么是值传递我们还可以修改 Map 的内容呢?我们先看一个
自己实现的 struct ,如下示例:
package main import "fmt" // 声明一个结构体
type Person struct {
Name string
} func modify(p Person) {
fmt.Printf("函数里接收到的Person内存地址:%p\n",&p) p.Name = "李四"
} func main() {
p := Person{"张三"}
fmt.Printf("原始Person的内存地址是:%p\n",&p) modify(p)
fmt.Println(p)
}
运行打印输出:
原始Person的内存地址是:0xc00004a1c0
函数里接收到的Person内存地址:0xc00004a1d0
{张三}
我们看到,自己定义的 Person 类型,在传递参数的时候也是值传递,但是它的值并没有被修改,这也就
是说 map 类型 和我们自己定义 struct 类型是不一样的,我们把 modify 函数接收的参数改为 Person 的指针:
package main import "fmt" // 声明一个结构体
type Person struct {
Name string
} func modify(p *Person) {
fmt.Printf("函数里接收到的Person内存地址:%p\n",&p) p.Name = "李四"
} func main() {
p := Person{"张三"}
fmt.Printf("原始Person的内存地址是:%p\n",&p) modify(&p)
fmt.Println(p)
}
运行打印结果:
原始Person的内存地址是:0xc00004a1c0
函数里接收到的Person内存地址:0xc00007e020
{李四}
看上面的输出结果,我们发现这次被修改了,那么指针类型的可以被修改,非指针类型的不能被修改,是不是我们
使用 make 创建的 map 就是一个指针类型呢?,看一下 Go 源码:
源码地址:https://github.com/golang/go/blob/master/src/runtime/map.go
func makemap64(t *maptype, hint int64, h *hmap) *hmap {
// 省略无关代码
}
我们看到 make 函数返回的是一个 hmap 类型的指针 *hmap,也就是 map == *hmap。现在再来看 func modify(p Person)
其实就是 func modify(p *Persoon) ,所以,这里 Go 通过 make 函数,为我们省去了指针的操作,在这里 map 可以理解为引用
类型但不是传引用
Slice 类型
slice 类型也是引用类型,它也可以在函数中修改对应的内容:
package main import "fmt" func modify(ages []int) {
fmt.Printf("函数里接收到 slice 的内存地址:%p\n",ages)
ages[0] = 6
} func main() {
ages := []int{10,20,30}
fmt.Printf("原始 slice 的内存地址:%p\n",ages) modify(ages)
fmt.Println(ages)
}
运行打印结果,发现的确是被修改了,而且在这里我们打印 slice 内存地址可以直接通过 %p,不用使用
& 取地址符转换,我们看一下 fmt.Printf 源码:
func (p *pp) fmtPointer(value reflect.Value, verb rune) {
var u uintptr
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
u = value.Pointer()
default:
p.badVerb(verb)
return
}
// 省略部分代码
}
通过上面的源码发现,对于 map、slice 等被当成指针处理,通过 value.Pointer() 获取对应值的指针:
func (v Value) Pointer() uintptr {
// TODO: deprecate
k := v.kind()
switch k {
case Chan, Map, Ptr, UnsafePointer:
return uintptr(v.pointer())
case Func:
if v.flag&flagMethod != 0 {
// As the doc comment says, the returned pointer is an
// underlying code pointer but not necessarily enough to
// identify a single function uniquely. All method expressions
// created via reflect have the same underlying code pointer,
// so their Pointers are equal. The function used here must
// match the one used in makeMethodValue.
f := methodValueCall
return **(**uintptr)(unsafe.Pointer(&f))
}
p := v.pointer()
// Non-nil func value points at data block.
// First word of data block is actual code.
if p != nil {
p = *(*unsafe.Pointer)(p)
}
return uintptr(p) case Slice:
return (*SliceHeader)(v.ptr).Data
}
panic(&ValueError{"reflect.Value.Pointer", v.kind()})
}
看上面的代码,如果类型是 slice 的话返回的是 slice 这个结构体里,字段 Data 第一个元素的地址:
type SliceHeader struct {
Data uintptr
Len int
Cap int
} type slice struct {
array unsafe.Pointer
len int
cap int
}
所以,我们通过 %p 打印的 slice 变量 ages 的地址其实就是内部存储数组元素的地址,slice 是一种结构体 +
元素指针的混合类型
Go 语言中所有的传参都是值传递,都是一个副本、一个拷贝。因为拷贝的内容是非引用类型(int、string、struct等这些),这样就在函数
中无法修改原内容数据的;有的是引用类型(map、slice等),这样的就可以修改原内容数据
是否可以修改原内容数据,和传值、传引用没有必然的关系,在 Go 语言中,虽然只有传值,但是我们也可以修改原内容数据,因为参数
是引用类型,引用类型和传引用是两个概念
Go 只有值传递
Go 参数传递是传值还是传引用的更多相关文章
- python函数的参数传递问题---传值还是传引用?
摘要:在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象.不可更改对象的传递属于传值,可更改对象属于传引用.想要在函数中传递 ...
- 关于Java对象作为参数传递是传值还是传引用的问题
前言 在Java中,当对象作为参数传递时,究竟传递的是对象的值,还是对象的引用,这是一个饱受争议的话题.若传的是值,那么函数接收的只是实参的一个副本,函数对形参的操作并不会对实参产生影响:若传的是引用 ...
- python函数传参是传值还是传引用?
首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...
- Java内存管理-Stackoverflow问答-Java是传值还是传引用?(十一)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 本文导图: 一.由一个提问引发的思考 在Stack Overflow 看到这样一个问题 ...
- python传参是传值还是传引用
在此之前先来看看变量和对象的关系:Python 中一切皆为对象,数字是对象,列表是对象,函数也是对象,任何东西都是对象.而变量是对象的一个引用(又称为名字或者标签),对象的操作都是通过引用来完成的.例 ...
- C++传值、传引用
C++传值.传引用 C++的函数参数传递方式,可以是传值方式,也可以是传引用方式.传值的本质是:形参是实参的一份复制.传引用的本质是:形参和实参是同一个东西. 传值和传引用,对大多数常见类型都是适用的 ...
- 【转载】Java是传值还是传引用
1. 简单类型是按值传递的 Java 方法的参数是简单类型的时候,是按值传递的 (pass by value).这一点我们可以通过一个简单的例子来说明: /* 例 1 */ /** * @(#) Te ...
- Java经典问题:传值与传引用?
转自:http://developer.51cto.com/art/201104/254715.htm Java到底是传值还是传引用?相信很少有人能完全回答正确.通常的说法是:对于基本数据类型(整型. ...
- Java中的值传递和地址传递(传值、传引用)
首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中.更何况,要想知道 ...
随机推荐
- UIView的绘制原理
当UIView调用setNeedDisplay之后, 系统会调用view对应layer的 setNeedsDisplay, 在当前runloop即将结束的时候调用CALayer的display方法. ...
- webpack4.0.1安装问题和webpack.config.js的配置变化
The CLI moved into a separate package: webpack-cli. Please install 'webpack-cli' in addition to webp ...
- OC获取ip地址
+(NSString *)getIp{ NSError *error;NSURL *ipURL = [NSURL URLWithString:@"http://pv.sohu.com/cit ...
- 2019春第六周作业Compile Summarize
这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 在这里 我在这个课程的目标是 能够熟练掌握指针的用法 这个作业在那个具体方面帮助我实现目标 对指针的使用更加得心应手 参考文献与网址 C语 ...
- pytorch加载预训练模型参数的方式
1.直接使用默认程序里的下载方式,往往比较慢: 2.通过修改源代码,使得模型加载已经下载好的参数,修改地方如下: 通过查找自己代码里所调用网络的类,使用pycharm自带的函数查找功能(ctrl+鼠标 ...
- Nuxt 自动化部署及打包后文件自动上传七牛云
1.安装七牛云 2.在项目根目录新建 upload.js 3.上传凭证 4.上传配置,详情参考七牛云文档 5.在 package.js中修改项目build完成执行upload文件命令 7. nuxt. ...
- 《linux就该这么学》第十三节课:第11章和第12章,vsftpd服务与samba和nfs服务
第十一章 (借鉴请改动) 11.1.文件传输协议 FTP文件穿数协议,端口20用于数据传输,21端口用于传输相关FTP命令 ftp协议的两种工作模式: 主动模式:ftp向客户端发起 被动模式(默认): ...
- windows----------windows10如何固定局域网ip
1. 2. 3. 4. 5.
- 关于git的认证方式
之前对github的使用,形成了两种观点.就是有两种url的模式,一种是http或https的,另一种是git专属的.然后git专属的url方式可以配置公钥认证,http(s)的则需要输入密码. 近期 ...
- Gem install Mysql2的问题
运行 ‘bundle install’ 或者 ‘gem install mysql2′ 遇到如下错误 > gem install mysql2 ERROR: Error installing m ...