Go的参数传递
值传递
普通变量、数组作为函数参数,都是值传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import "fmt"
func modify(a [5]int) { a[0] = 1 }
func main() { var a [5]int
fmt.Println(a)
modify(a) }
|
Output
指针传递
对于值传递,如果想在函数中修改参数的值,并且使原始值发生变化,通常的做法,是传递指针
比如上述代码,修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import "fmt"
func modify(a *[5]int) { a[0] = 1 }
func main() { var a [5]int
fmt.Println(a)
modify(&a)
fmt.Println(a) }
|
Output
slice作为函数参数
函数中append
Slice作为函数参数,稍微有些区别
首先,Slice作为函数参数,在函数中append,并不会对原始值产生影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import "fmt"
func modify(a []int) { a = append(a, 1) }
func main() { a := []int{}
fmt.Println(a)
modify(a)
fmt.Println(a) }
|
Output
结果原始变量没有发生更改
如果想让append生效,可以使用传递指针的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func modify(b *[]int) { fmt.Printf("%p\n", b) *b = append(*b, 4) }
func main() { a := []int{1}
fmt.Printf("%p\n", &a) modify(&a)
fmt.Println(a) } >> 0xc000088020 0xc000088020 [1 4]
|
函数中修改slice中的元素
在函数中修改slice中的元素,可以生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import "fmt"
func modify(a []int) { a[0] = 1 }
func main() { a := []int{0, 0, 0}
fmt.Println(a)
modify(a)
fmt.Println(a) }
|
Output
解析
为什么两种行为会产生不同的结果?
这里涉及到slice源码 解释
go中定义slice为
1 2 3 4 5
| type slice struct { array unsafe.Pointer len int cap int }
|
在slice作为函数参数的时候,是值传递,拷贝传递slice这个结构体,
假设原始slice为A,值传递后的slice为B,
A和B的array指针,是直接指向数据存放的内存地址,是同一个值,因此,修改slice中的元素,即直接通过内存地址进行修改,所以会修改成功
而append操作后,假使没有发生扩容,数据是放在array指针后的,但是只有B的len发生了变化,A的len并没有发生变化,所有A并没有被修改
通过直接取地址,是可以拿到append后的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main
import ( "fmt" "unsafe" )
func modify(a []int) { a = append(a, 1) }
func main() { a := []int{}
a = append(a, 0, 0, 0)
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
modify(a)
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
p := unsafe.Pointer(&a[2]) q := uintptr(p) + 8 v := (*int)(unsafe.Pointer(q))
fmt.Println(*v) }
|
Output
1 2 3 4
| len: 3 cap:4 data:[0 0 0] [0 0 0] len: 3 cap:4 data:[0 0 0] 1
|
但是,如果发生扩容,append后array地址就会发生变化,也就无法通过这种方式获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package main
import ( "fmt" "unsafe" )
func modify(a []int) { a = append(a, 1) }
func main() { a := []int{0, 0, 0}
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
modify(a)
fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a)
p := unsafe.Pointer(&a[2]) q := uintptr(p) + 8 v := (*int)(unsafe.Pointer(q))
fmt.Println(*v) }
|
map作为函数参数
map是引用类型,作为参数传递后(可以认为是值传递),修改参数,原始值也会被修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import ( "fmt" )
func modify(a map[int]int) { fmt.Printf("%p\n", a) a[1] = 2 }
func main() { a := make(map[int]int) a[1] = 1
fmt.Printf("%p\n", a) modify(a)
fmt.Println(a) }
|
Output
1 2 3
| 0xc000072180 0xc000072180 map[1:2]
|
可以看到,地址都是一样的
这里 %p 输出的是map在内存中的地址
如果slice中的append例子,也通过%p打印,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" )
func modify(b []int) { fmt.Printf("%p\n", b) b = append(b, 4) }
func main() { a := []int{1}
fmt.Printf("%p\n", a) modify(a)
fmt.Println(a) }
>> 0xc000096008 0xc000096008 [1]
|
会发现a, b的地址是一样的,这是由于对于slice来说,输出的是第一个元素的地址,也就是slice struct中的array地址,所以是一样的,如果打印&a,&b,就不一样了
%p address of 0th element in base 16 notation, with leading 0x