本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。

对于了解一门语言来说,会关心我们在函数调用的时候,参数到底是传的值,还是引用?

其实对于传值和传引用,是一个比较古老的话题,做研发的都有这个概念,但是可能不是非常清楚。对于我们做Go语言开发的来说,也想知道到底是什么传递。

那么我们先来看看什么是值传递,什么是引用传递。

什么是传值(值传递)

传值的意思是:函数传递的总是原来这个东西的一个副本,一副拷贝。比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。

对于int这类基础类型我们可以很好的理解,它们就是一个拷贝,但是指针呢?我们觉得可以通过它修改原来的值,怎么会是一个拷贝呢?下面我们看个例子。

123456789101112

func main() {

i:=10

ip:=&i

fmt.Printf("原始指针的内存地址是:%p\n",&ip)

modify(ip)

fmt.Println("int值被修改了,新值为:",i)

}

func modify(ip *int){

fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)

*ip=1

}

我们运行,可以看到输入结果如下:

123

原始指针的内存地址是:0xc42000c028

函数里接收到的指针的内存地址是:0xc42000c038

int值被修改了,新值为: 1

首先我们要知道,任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指针的内存。

所以通过输出我们可以看到,这是一个指针的拷贝,因为存放这两个指针的内存地址是不同的,虽然指针的值相同,但是是两个不同的指针。

通过上面的图,可以更好的理解。

首先我们看到,我们声明了一个变量i,值为10,它的内存存放地址是0xc420018070,通过这个内存地址,我们可以找到变量i,这个内存地址也就是变量i的指针ip。

指针ip也是一个指针类型的变量,它也需要内存存放它,它的内存地址是多少呢?是0xc42000c028。

在我们传递指针变量ip给modify函数的时候,是该指针变量的拷贝,所以新拷贝的指针变量ip,它的内存地址已经变了,是新的0xc42000c038。

不管是0xc42000c028还是0xc42000c038,我们都可以称之为指针的指针,他们指向同一个指针0xc420018070,这个0xc420018070又指向变量i,这也就是为什么我们可以修改变量i的值。

什么是传引用(引用传递)

Go语言(Golang)是没有引用传递的,这里我不能使用Go举例子,但是可以通过说明描述。

以上面的例子为例,如果在modify函数里打印出来的内存地址是不变的,也是0xc42000c028,那么就是引用传递。

迷惑Map

了解清楚了传值和传引用,但是对于Map类型来说,可能觉得还是迷惑,一来我们可以通过方法修改它的内容,二来它没有明显的指针。

123456789101112131415

func main() {

persons:=make(map[string]int)

persons["张三"]=19

mp:=&persons

fmt.Printf("原始map的内存地址是:%p\n",mp)

modify(persons)

fmt.Println("map值被修改了,新值为:",persons)

}

func modify(p map[string]int){

fmt.Printf("函数里接收到map的内存地址是:%p\n",&p)

p["张三"]=20

}

运行打印输出:

123

原始map的内存地址是:0xc42000c028

函数里接收到map的内存地址是:0xc42000c038

map值被修改了,新值为: map[张三:20]

两个内存地址是不一样的,所以这又是一个值传递(值的拷贝),那么为什么我们可以修改Map的内容呢?先不急,我们先看一个自己实现的struct。

123456789101112131415

func main() {

p:=Person{"张三"}

fmt.Printf("原始Person的内存地址是:%p\n",&p)

modify(p)

fmt.Println(p)

}

type Person struct {

Name string

}

func modify(p Person) {

fmt.Printf("函数里接收到Person的内存地址是:%p\n",&p)

p.Name = "李四"

}

运行打印输出:

123

原始Person的内存地址是:0xc4200721b0

函数里接收到Person的内存地址是:0xc4200721c0

{张三}

我们发现,我们自己定义的Person类型,在函数传参的时候也是值传递,但是它的值(Name字段)并没有被修改,我们想改成李四,发现最后的结果还是张三。

这也就是说,map类型和我们自己定义的struct类型是不一样的。我们尝试把modify函数的接收参数改为Person的指针。

12345678910111213

func main() {

p:=Person{"张三"}

modify(&p)

fmt.Println(p)

}

type Person struct {

Name string

}

func modify(p *Person) {

p.Name = "李四"

}

在运行查看输出,我们发现,这次被修改了。我们这里省略了内存地址的打印,因为我们上面int类型的例子已经证明了指针类型的参数也是值传递的。

指针类型可以修改,非指针类型不行,那么我们可以大胆的猜测,我们使用make函数创建的map是不是一个指针类型呢?看一下源代码:

12345678

// makemap implements a Go map creation make(map[k]v, hint)// If the compiler has determined that the map or the first bucket// can be created on the stack, h and/or bucket may be non-nil.// If h != nil, the map can be created directly in h.// If bucket != nil, bucket can be used as the first bucket.func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {

//省略无关代码}

通过查看src/runtime/hashmap.go源代码发现,的确和我们猜测的一样,make函数返回的是一个hmap类型的指针*hmap。也就是说map===*hmap。

现在看func modify(p map)这样的函数,其实就等于func modify(p *hmap),和我们前面第一节什么是值传递里举的func modify(ip *int)的例子一样,可以参考分析。

所以在这里,Go语言通过make函数,字面量的包装,为我们省去了指针的操作,让我们可以更容易的使用map。这里的map可以理解为引用类型,但是记住引用类型不是传引用。

chan类型

chan类型本质上和map类型是一样的,这里不做过多的介绍,参考下源代码:

123

func makechan(t *chantype, size int64) *hchan {

//省略无关代码}

chan也是一个引用类型,和map相差无几,make返回的是一个*hchan。

和map、chan都不一样的slice

slice和map、chan都不太一样的,一样的是,它也是引用类型,它也可以在函数中修改对应的内容。

1234567891011

func main() {

ages:=[]int{6,6,6}

fmt.Printf("原始slice的内存地址是%p\n",ages)

modify(ages)

fmt.Println(ages)

}

func modify(ages []int){

fmt.Printf("函数里接收到slice的内存地址是%p\n",ages)

ages[0]=1

}

运行打印结果,发现的确是被修改了,而且我们这里打印slice的内存地址是可以直接通过%p打印的,不用使用&取地址符转换。

这就可以证明make的slice也是一个指针了吗?不一定,也可能fmt.Printf把slice特殊处理了。

1234567891011

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

}

//省略部分代码}

通过源代码发现,对于chan、map、slice等被当成指针处理,通过value.Pointer()获取对应的值的指针。

123456789101112

// If v's Kind is Slice, the returned pointer is to the first// element of the slice. If the slice is nil the returned value// is 0. If the slice is empty but non-nil the return value is non-zero.func (v Value) Pointer() uintptr {

// TODO: deprecatek := v.kind()

switch k {

//省略无关代码case Slice:

return (*SliceHeader)(v.ptr).Data

}

}

很明显了,当是slice类型的时候,返回是slice这个结构体里,字段Data第一个元素的地址。

1234567891011

type SliceHeader struct {

Data uintptr

Len int

Cap int

}

type slice struct {

array unsafe.Pointer

len int

cap int

}

所以我们通过%p打印的slice变量ages的地址其实就是内部存储数组元素的地址,slice是一种结构体+元素指针的混合类型,通过元素array(Data)的指针,可以达到修改slice里存储元素的目的。

所以修改类型的内容的办法有很多种,类型本身作为指针可以,类型里有指针类型的字段也可以。

单纯的从slice这个结构体看,我们可以通过modify修改存储元素的内容,但是永远修改不了len和cap,因为他们只是一个拷贝,如果要修改,那就要传递*slice作为参数才可以。

123456789101112131415161718192021

func main() {

i:=19

p:=Person{name:"张三",age:&i}

fmt.Println(p)

modify(p)

fmt.Println(p)

}

type Person struct {

name string

age *int

}

func (p Person) String() string{

return "姓名为:" + p.name + ",年龄为:"+ strconv.Itoa(*p.age)

}

func modify(p Person){

p.name = "李四"

*p.age = 20

}

运行打印输出结果为:

12

姓名为:张三,年龄为:19

姓名为:张三,年龄为:20

通过这个Person和slice对比,就更好理解了,Person的name字段就类似于slice的len和cap字段,age字段类似于array字段。在传参为非指针类型的情况下,只能修改age字段,name字段无法修改。要修改name字段,就要把传参改为指针,比如:

12345

modify(&p)

func modify(p *Person){

p.name = "李四"

*p.age = 20

}

这样name和age字段双双都被修改了。

所以slice类型也是引用类型。

小结

最终我们可以确认的是Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。

这里也要记住,引用类型和传引用是两个概念。

再记住,Go里只有传值(值传递)。

本文为原创文章,转载请注明出处,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。

go语言接收html传值,Go语言参数传递是传值还是传引用相关推荐

  1. vue中传值和传引用_vue prop属性传值与传引用示例

    vue prop属性传值与传引用示例 vue组件在prop里根据type决定传值还是传引用. 简要如下: 传值:String.Number.Boolean 传引用:Array.Object 若想将数组 ...

  2. go语言参数传递到底是传值还是传引用

    前言 哈喽,大家好,我是asong.今天女朋友问我,小松子,你知道Go语言参数传递是传值还是传引用吗?哎呀哈,我竟然被瞧不起了,我立马一顿操作,给他讲的明明白白的,小丫头片子,还是太嫩,大家且听我细细 ...

  3. 女朋友问我:小松子,你知道Go语言参数传递是传值还是传引用吗?

    前言 哈喽,大家好,我是asong.今天女朋友问我,小松子,你知道Go语言参数传递是传值还是传引用吗?哎呀哈,我竟然被瞧不起了,我立马一顿操作,给他讲的明明白白的,小丫头片子,还是太嫩,大家且听我细细 ...

  4. C语言基础:数组作为函数参数传递演示源码

    将做工程过程中常用的内容片段记录起来,如下内容内容是关于C语言基础:数组作为函数参数传递演示的内容,应该能对小伙伴也有好处. #include <stdio.h>void show_arr ...

  5. python语言接收信息的内置函数是_Python语言接收信息的内置函数是________________...

    Python语言接收信息的内置函数是________________ 答: input() 中国大学MOOC: 铁素体稳定元素倾向于 奥氏体区,使共析点向 方移动. 答:缩小:左上: 注射剂中热原检查 ...

  6. 语言深入:java中究竟是传值还是传引用

    http://hi.baidu.com/hugoxian/item/5212a65bb1546aded48bace1 首先,推荐对Java有一定理解的同仁一本书<Practical Java&g ...

  7. C++ 函数参数传递:传值,传指针,传引用

    PS:首先理解形参   实参概念.形参是在函数定义的括号内定义的专用变量,它们的目的是保存按实参传递给它们的信息,实参被列在函数调用语句的括号内. int func(int x)//x是形参 {ret ...

  8. 如何理解Java中参数传递只能传值?

    以前学习C#的时候,是完全在工作岗位上学习,一些底层较为深入的道理都不是很清楚.如今学习了Java,对于Java参数传递只能传值,不能传引用(指针)感到很困惑,在C#中不是常常说把某个引用传递到函数中 ...

  9. 【中级软考】函数参数传递传值与传引用的区别(global关键字,函数内定义全局变量)

    传值调用最显著的特征就是被调用的函数内部对形参的修改不影响实参的值. 引用调用是将实参的地址传递给形参,使得形参的地址就是实参的地址. (对于python而言,普通的实参传个变量(或常量)进去就相当于 ...

最新文章

  1. jstl sql标签使用
  2. App设计灵感之十二组精美的租车类App设计案例
  3. 轻松精通数据库管理之道——运维巡检之四(数据库备份)
  4. Apache Karaf遇到Apache HBase
  5. android 权限
  6. 【jiasuba】巧妙运用win键
  7. 雷军:程序员如何成功创业?
  8. 结巴分词有前空格_结巴分词详细讲解
  9. 【截屏、录屏】工具分享-最简单的工具-QQ
  10. python入门教程百度云资源-python教程大全,全套视频教程学习资料通过百度云网盘下载...
  11. 秋招公司真题刷题2019-2020java工程师
  12. saiku 展示优化第二步(要诀和技巧)
  13. 微信开发 (四) 微信网页授权
  14. OpenCV 录制视频
  15. 西电李航 操作系统课程笔记 day11 IO softwarelayer
  16. double类型的输出方式
  17. 如何让会议更加快速有效
  18. Angular+Node实战之登陆注册
  19. java se系列(十二)集合
  20. python要用到的数学知识_AI之路,第一篇:python数学知识1

热门文章

  1. s3k3 破旧不堪的拐杖被扔出去几米远
  2. 我是一个工资涨不上去的失败程序员
  3. 第一句就是定义了一种ptrfun的C++类型
  4. typedef让p去除了普通变量的C++身份
  5. 飞鸽传书2011看到一篇国外的博客
  6. 互联网各岗位的生存指南
  7. 别怕,是我......程序猿
  8. gdc服务器故障输入信号超出范围,H1Z1信号输入超出范围 | 手游网游页游攻略大全...
  9. shell调用python权限不足_使用权限从python运行shell脚本
  10. Mac电脑「空格键」的使用技巧