首页 > 其他分享 >Go函数参数传递到底是值传递还是引用传递?

Go函数参数传递到底是值传递还是引用传递?

时间:2023-12-13 10:58:35浏览次数:24  
标签:参数传递 函数 形参 fmt 传递 Printf Go 内存地址 指针

在函数中,如果参数是非引用类型(int、string、array、struct等),这样就在函数中就无法修改原内容数据;

如果参数是引用类型(指针、map、slice、chan等),这样就可以修改原内容数据。

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

先说下结论:Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。

一、值传递【适用于基本数据类型或结构体等较小的数据】

将实参的值传递给形参,形参是实参的一份拷贝,实参和形参的内存地址不同。函数内对形参值内容的修改,是否会影响实参的值内容,取决于参数是否是引用类型

二、引用传递

将实参的地址传递给形参,函数内对形参值内容的修改,将会影响实参的值内容。Go语言是没有引用传递的,在C++中,函数参数的传递方式有引用传递。

三、各类型参数传递

int类型

形参和实际参数内存地址不一样,证明是值传递;参数是值类型,所以函数内对形参的修改,不会修改原内容数据

package main
 
import "fmt"
 
func main() {
 var i int64 = 1
 fmt.Printf("原始int内存地址是 %p\n", &i)
 modifyInt(i) // args就是实际参数
 fmt.Printf("改动后的值是: %v\n", i)
}
 
func modifyInt(i int64) { //这里定义的args就是形式参数
 fmt.Printf("函数里接收到int的内存地址是:%p\n", &i)
 i = 10
}
 
原始int内存地址是 0xc0000180b8
函数里接收到int的内存地址是:0xc0000180c0
改动后的值是: 1

指针类型

形参和实际参数内存地址不一样,证明是值传递,由于形参和实参是指针,指向同一个变量。函数内对指针指向变量的修改,会修改原内容数据

package main
 
import "fmt"
 
func main() {
 var args int64 = 1                  // int类型变量
 p := &args                          // 指针类型变量
 fmt.Printf("原始指针的内存地址是 %p\n", &p)   // 存放指针类型变量
 fmt.Printf("原始指针指向变量的内存地址 %p\n", p) // 存放int变量
 modifyPointer(p)                    // args就是实际参数
 fmt.Printf("改动后的值是: %v\n", *p)
}
 
func modifyPointer(p *int64) { //这里定义的args就是形式参数
 fmt.Printf("函数里接收到指针的内存地址是 %p \n", &p)
 fmt.Printf("函数里接收到指针指向变量的内存地址 %p\n", p)
 *p = 10
}
 
原始指针的内存地址是 0xc000110018
原始指针指向变量的内存地址 0xc00010c008
函数里接收到指针的内存地址是 0xc000110028 
函数里接收到指针指向变量的内存地址 0xc00010c008
改动后的值是: 10

slice类型

形参和实际参数内存地址一样,不代表是引用类型;下面进行详细说明slice还是值传递,传递的是指针

package main
 
import "fmt"
 
func main() {
 var s = []int64{1, 2, 3}
 // &操作符打印出的地址是无效的,是fmt函数作了特殊处理
 fmt.Printf("直接对原始切片取地址%v \n", &s)
 // 打印slice的内存地址是可以直接通过%p打印的,不用使用&取地址符转换
 fmt.Printf("原始切片的内存地址: %p \n", s)
 fmt.Printf("原始切片第一个元素的内存地址: %p \n", &s[0])
 modifySlice(s)
 fmt.Printf("改动后的值是: %v\n", s)
}
 
func modifySlice(s []int64) {
 // &操作符打印出的地址是无效的,是fmt函数作了特殊处理
 fmt.Printf("直接对函数里接收到切片取地址%v\n", &s)
 // 打印slice的内存地址是可以直接通过%p打印的,不用使用&取地址符转换
 fmt.Printf("函数里接收到切片的内存地址是 %p \n", s)
 fmt.Printf("函数里接收到切片第一个元素的内存地址: %p \n", &s[0])
 s[0] = 10
}
 
直接对原始切片取地址&[1 2 3] 
原始切片的内存地址: 0xc0000b8000 
原始切片第一个元素的内存地址: 0xc0000b8000 
直接对函数里接收到切片取地址&[1 2 3]
函数里接收到切片的内存地址是 0xc0000b8000 
函数里接收到切片第一个元素的内存地址: 0xc0000b8000 
改动后的值是: [10 2 3]

slice是一个结构体,他的第一个元素是一个指针类型,这个指针指向的是底层数组的第一个元素。当参数是slice类型的时候,fmt.printf通过%p打印的slice变量的地址其实就是内部存储数组元素的地址,所以打印出来形参和实参内存地址一样。

因为slice作为参数时本质是传递的指针,上面证明了指针也是值传递,所以参数为slice也是值传递,指针指向的是同一个变量,函数内对形参的修改,会修改原内容数据

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

map类型

形参和实际参数内存地址不一样,证明是值传递

package main
 
import "fmt"
 
func main() {
 m := make(map[string]int)
 m["age"] = 8
 
 fmt.Printf("原始map的内存地址是:%p\n", &m)
 modifyMap(m)
 fmt.Printf("改动后的值是: %v\n", m)
}
 
func modifyMap(m map[string]int) {
 fmt.Printf("函数里接收到map的内存地址是:%p\n", &m)
 m["age"] = 9
}
 
原始map的内存地址是:0xc00000e028
函数里接收到map的内存地址是:0xc00000e038
改动后的值是: map[age:9]

通过make函数创建的map变量本质是一个hmap类型的指针*hmap,所以函数内对形参的修改,会修改原内容数据

//src/runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    if overflow || mem > maxAlloc {
        hint = 0
    }
 
    // initialize Hmap
    if h == nil {
        h = new(hmap)
    }
    h.hash0 = fastrand()
}

channel类型

形参和实际参数内存地址不一样,证明是值传递

package main
 
import (
 "fmt"
 "time"
)
 
func main() {
 p := make(chan bool)
 fmt.Printf("原始chan的内存地址是:%p\n", &p)
 go func(p chan bool) {
  fmt.Printf("函数里接收到chan的内存地址是:%p\n", &p)
  //模拟耗时
  time.Sleep(2 * time.Second)
  p <- true
 }(p)
 
 select {
 case l := <-p:
  fmt.Printf("接收到的值是: %v\n", l)
 }
}
 
原始chan的内存地址是:0xc00000e028
函数里接收到chan的内存地址是:0xc00000e038
接收到的值是: true

通过make函数创建的chan变量本质是一个hchan类型的指针*hchan,所以函数内对形参的修改,会修改原内容数据

// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
    elem := t.elem
 
    // compiler checks this but be safe.
    if elem.size >= 1<<16 {
        throw("makechan: invalid channel element type")
    }
    if hchanSize%maxAlign != 0 || elem.align > maxAlign {
        throw("makechan: bad alignment")
    }
 
    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))
    }
}

struct类型

形参和实际参数内存地址不一样,证明是值传递。形参不是引用类型或者指针类型,所以函数内对形参的修改,不会修改原内容数据

package main
 
import "fmt"
 
type Person struct {
 Name string
 Age  int
}
 
func main() {
 per := Person{
  Name: "test",
  Age:  8,
 }
 fmt.Printf("原始struct的内存地址是:%p\n", &per)
 modifyStruct(per)
 fmt.Printf("改动后的值是: %v\n", per)
}
 
func modifyStruct(per Person) {
 fmt.Printf("函数里接收到struct的内存地址是:%p\n", &per)
 per.Age = 10
}
 
原始struct的内存地址是:0xc0000a6018
函数里接收到struct的内存地址是:0xc0000a6030
改动后的值是: {test 8}

 

标签:参数传递,函数,形参,fmt,传递,Printf,Go,内存地址,指针
From: https://www.cnblogs.com/beatle-go/p/17898561.html

相关文章

  • A fast and simple algorithm for training neural probabilistic language models
    目录概NoisecontrastiveestimationMnihA.andTehY.W.Afastandsimplealgorithmfortrainingneuralprobabilisticlanguagemodels.ICML,2012.概NCE用在语言模型的训练上.Noisecontrastiveestimation给定context\(h\),下一个词为\(w\)的条件概率按......
  • Django runserver 时报错 [Errno 11001] getaddrinfo failed
    现象描述:python使用Django命令pythonmanage.pyrunserver0:8000时,在浏览器登录遇到错误[Errno11001]getaddrinfofailed错误:解决办法:查看本机ip地址(windows在cmd中输入ipconfig可查看本机ip)打开Django项目的settings.py文件,在ALLOWED_HOSTS=[]中填入查......
  • 基于Django的安全性学习(下)
    基于Django的安全性学习(下)SSL/HTTPS通过HTTPS部署网页是保障安全的最佳办法。没有它,恶意用户就可以在客户端和服务器之间嗅探验证资格或其他信息,在某些情况下,比如:主动网络攻击者,会修改发送中的数据。设置SECURE_PROXY_SSL_HEADER,否则,将会导致CSRF漏洞,如果操作不正确,也......
  • 基于Django的安全性学习(上)
    基于Django的安全性学习(上)防御跨站脚本攻击(XSS)发起XSS攻击的人可以向其他用户的浏览器诸如客户端脚本。这种攻击通常由存储在数据库中的恶意脚本实现,这些脚本会被检索出来并显示给其他用户;或者通过其他用户点击会令攻击者的JavaScript脚本在浏览器中执行的链接来实现。......
  • [CF839E] Mother of Dragons
    最优方案一定是选择一个团,并在团里平均分配点权。实际上,定义一个点\(u\)的权重\(w_u\)为\(\sum\limits_{(u,v)}s_v\),那么如果方案中\(w_x>w_y\),将\(y\)去掉并将其点权加在\(x\)上一定更优,所以答案一定会被调整成一个团。对于求最大团,只需要meetinthemiddle加上......
  • 「PPT 下载」Google DevFest Keynote | 复杂的海外网络环境下,如何提升连接质量
    12月10日,“GoogleDevFest2023上海站”大会如期在上海市东方万国宴会中心举办。延续过往的技术交流碰撞、前沿技术学习基调传统,本届大会聚焦行业前瞻、AI洞见、出海加速等议题,吸引数千开发者齐聚一堂、热烈交流。关注【融云全球互联网通信云】了解更多融云IM通讯技术专家吴......
  • Django学习(三) 之 模板中标签的使用
    写在前面最近看到稀土掘金在搞2023年终总结征文活动,一直想尝试投稿试试,周末我就花了近一下午时间写完初稿,然后周一、周二完成精读再改稿,感觉OK,昨晚凌晨第一时间在稀土掘金投稿。结果,又发生了同样的事情。同样的文章,在博客园上、公号上阅读量很OK,在稀土掘金上就上不来。这应......
  • Go语言学习笔记
    Go语言入门教程:https://c.biancheng.net/golang/Go语言的基本类型有:boolstringint、int8、int16、int32、int64uint、uint8、uint16、uint32、uint64、uintptrbyte//uint8的别名rune//int32的别名代表一个Unicode码float32、float64complex64、complex128当......
  • golang http post 执行函数效率最高,速度最快实现
    在Go语言中,使用标准库的net/http包可以进行HTTPPOST请求。为了获得最高的执行效率和最快的速度,可以使用http.Client结构体来管理和复用HTTP连接,并使用http.NewRequest创建请求对象,然后使用http.Client的Do方法发送请求。以下是一个示例代码,展示如何使用Go语言的net/http包执行高效......
  • SpringBoot MongoTemplate 实现分页
    一、MongoTemplate实现分页springboot集成Mongodb好像没有现成的分页工具,只能自己先查总数再查数据,需要进行两次查询。例如:@Testpublicvoidtest_119()throwsException{Queryquery=newQuery();longtotal=mongoTemplate.count(query,Dog.class);query.w......