GO中只有值传递,没有引用传递
所谓值传递,就是实参通过拷贝将自身内容传递给形参。也就是将传递的内容拷贝一份,给函数。所以函数外和函数里对这个参数地址求值,应该是不一样的。
证明如下:
func main() {
slice := []int{0,1,2,3}
m := make(map[string]string)
m["A"] = "a"
var i *int
a := 123
i = &a
fmt.Printf("[main pointer] %p\n", &i)
fmt.Printf("[main map] %p\n", &m)
fmt.Printf("[main slice] %p\n", &slice)
get(slice, m, i)
}
func get(s []int, m map[string]string, i *int) {
fmt.Printf("[get pointer] %p\n", &i)
fmt.Printf("[get map] %p\n", &m)
fmt.Printf("[get slice] %p\n", &s)
}
// output
[main pointer] 0xc0000ae020
[main map] 0xc0000ae018
[main slice] 0xc0000a6020
[get pointer] 0xc0000ae038
[get map] 0xc0000ae030
[get slice] 0xc0000a6040
可以发现,slice、map、指针在传递过程中,地址都发生了变化。这说明传递的是一份拷贝。
但是我们又发现,在函数里修改slice、map,函数外的值也会改变,这是为什么呢?
// main
get(slice, m, i)
fmt.Println(slice)
fmt.Println(m)
fmt.Println(*i)
// get
s[1] = 9999
m["A"] = "QQQQQ"
a := 999
i = &a
// output
[0 9999 2 3] // 修改之前为 []int{0,1,2,3}
map[A:QQQQQ] // 修改之前为 m["A"] = "a"
123 // 修改之前为 *i = 123
这就必须说到slice、map的存储结构了。以slice为例
slice 存储结构
slice结构主要包括3个部分:data、len、cap。结构中的len和cap分别指示元素数量和容量。append使得len变大,但如果append之后的元素个数大于cap,会引发扩容机制。此时,会重新创建一个容量适合的底层数组。data为指针,指向底层数组。
所以slice本质上指的就是这个结构(data+len+cap,不包括底层的数组)。在参数传递过程中,这个结构拷贝了一份,data指向的还是原来的底层数组。当我们对slice中的元素进行修改时,还是会通过拷贝之后的data,直接对底层数组进行修改。
go 中,slice、map、channel都是引用类型,所以都会有如上的特性。
由于slice在扩容的过程中,会重新创建底层数组,data指向新的数组。那么,我们可以推测,在get函数中如果能触发扩容,那么修改新的底层数组,并不会对main中的slice造成影响。是否这样呢?
func main() {
slice := []int{0,1,2,3}
fmt.Printf("[main slice] %p\n", &slice)
get(slice)
fmt.Printf("[new slice] %v\n",slice)
}
func get(s []int) {
fmt.Printf("[get slice] %p\n", &s)
s = append(s, 9999)
}
// output
[main slice] 0xc0000a6020
[get slice] 0xc0000a6040
[new slice] [0 1 2 3]
果然如此!那么就很清楚了。
总结
1、go中函数传递都是值传递
2、slice、map、channel都是引用类型,即便是值传递,结构内部还是指向原来的引用对象,所以函数体内可以直接修改元素。
3、如果slice触发扩容,data会指向新的底层数组,而不指向外部的底层数组了。所以之后再修改slice,不会对外部的slice造成影响。
|
请发表评论