[Go语言函数传参方式 | 青训营笔记]

100 阅读2分钟

这是我参与「第五届青训营 」笔记创作活动的第1天。

一、问题发现

本节课是青训营第一堂课熟悉语法,老师在讲到传递map的时候说对于map向形参中添加内容后实参也会被更改,突然想到之前传递slice的一堆坑,趁此机会了解了一下go的传参方式。

二、Go传参方式

结论:Go中只有值传递,没有引用传递

对于正常数据结构的传递

对于int,string,自己定义的struct等数据结构,传参方式统一为值传递,这个比较简单,不作代码演示了。

关于slice的传递

传递slice时有时会出现明明传递到func中的的是slice值,但是经过func更改后,有时slice内容会变化,有时会不变,这是为什么呢?

首先看一下slice的结构

 type SliceHeader struct {
     Data uintptr    // Data是指向数据地址的指针
     Len  int
     Cap  int
 }

一个Slice包含了Data,Len,Cap三部分,其中Data是指向数据地址的指针,而其他两个是普通变量。我们来分析如下代码,看运行后会输出什么内容:

 func change1(s []int) {
     s[0] = 666
 }
 ​
 func change2(s []int) {
     s = append(s, 777)
 }
 ​
 func change3(s *[]int) {
     *s = append(*s, 888)
 }
 ​
 func main() {
     s := []int{0, 1, 2, 3}
     change1(s)
     fmt.Println(s)  // [666 1 2 3]
     change2(s)
     fmt.Println(s)  // [666 1 2 3]
     change3(&s)
     fmt.Println(s)  // [666 1 2 3 888]
 }

输出的内容已经在print后的注释中。

调用change1时,因为Go是值传递,系统会新建一个s的副本s',并将该副本s'传递给change1。因为s和s'中的Data指向的数据地址是一样的,所以若Slice的Data地址不发生改变( 没有发生copy、重分配、append触发扩容 )则可以修改slice公共的部分。

但若s进行了扩容,则此时函数内部对数据的修改将不再影响到外部的切片,代表长度的len和容量cap也均不会被修改。 如调用change2后,main中的s不会改变.

若想传递slice之后,在函数中的改变可以完全影响到原slice,可以采用change3的方式传递s的指针

关于map的传递

如果用 Map 当函数参数,Map发生扩容后,函数内外的Map变量指向的底层内存仍是一致的。

go的官方Blog里面写道,可以像是看待指针一样看待map。

Map types are reference types, like pointers or slices, ...

详细的信息涉及到map的底层构造,有兴趣的可以看一下这篇文章Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?