这是我参与「第五届青训营 」笔记创作活动的第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 型参数,会发生扩容后指向不同底层内存的事儿吗?