Linvis Blog

Go的参数传递

2020-01-01

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
[0 0 0 0 0]
[0 0 0 0 0]

指针传递

对于值传递,如果想在函数中修改参数的值,并且使原始值发生变化,通常的做法,是传递指针

比如上述代码,修改为

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

1
2
[0 0 0 0 0]
[1 0 0 0 0]

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

1
2
[]
[]

结果原始变量没有发生更改

如果想让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

1
2
[0 0 0]
[1 0 0]

解析

为什么两种行为会产生不同的结果?

这里涉及到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)

// 通过指针直接获取a[3]
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) {
// cap是3, 增加新元素会有引发扩容
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 := make([]int, 3)
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

Tags: Go

扫描二维码,分享此文章