1. 起因

我们知道golang有值传递与引用传递两种传递方式。

值类型: 基本数据类型int系列,float系列,bool,string,数组和结构体struct

引用类型: 指针,slice切片,map,管道chan,interface等

这两者的区别是,值传递相当于是复制了一份。而引用传递,是复制了相同的指针地址。如下图所示:

value-passing-and-reference-passing

2. 基础类型

基础类型的比较简单易懂

2.1. 改变指针指向内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func test(a *int) {
var b = 20
*a = b
fmt.Println(a) //0xc00001c098
}

func main() {
var a = 10
fmt.Println(&a) //0xc00001c098
test(&a)
fmt.Println(&a) //0xc00001c098
fmt.Println(a) //20
return
}
*a = bamaina1020testamain

2.2. 改变指针指向地址

ba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func test(a *int) {
var b = 20
*a = b
fmt.Println(a) //0xc00001c098
fmt.Println(&a) //0xc00001c0a0
a = &b
fmt.Println(a) //0xc00001c0b0
*a = 30
fmt.Println(*a) //30
}

func main() {
var a = 10
fmt.Println(&a) //0xc00001c098
test(&a)
fmt.Println(&a) //0xc00001c098
fmt.Println(a) //20
return
}
testabamainatestamain

指向分析

2.3. 小结

改变指针所指向地址的内容,会对所有指向该地址的指针产生影响。但是,如果将指针所指向的地址发生改变,那就不同了。这对golang的其他数据类型同样适用。

3. 结构体

结构体可以由我们自己觉得它的方法是值传递还是引用传递,如果是引用传递,对结构体内部成员的修改才会生效。但是,如果我们想直接将当前结构体的一个实例在方法里替换成另一个实例要怎么做呢?

3.1. 错误演示

1
2
3
4
5
6
7
8
9
10
11
12
13
func (this *MyStruct1) test(there MyStruct1) {
//目前this是指针类型
fmt.Printf("%p\n", this) //0xc00000a060
this = &there
}
func main() {
var str1 = &MyStruct1{"张三", 18}
fmt.Printf("%p\n", str1) //0xc00000a060
var str2 = MyStruct1{"李四", 20}
fmt.Println(str1) //&{张三 18}
str1.test(str2)
fmt.Println(str1) //&{张三 18}
}
thisstr1基础类型

3.2. 正确演示

与基础类型的一样,我们只要将指向地址的值进行修改替换,就可以达到目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
func (this *MyStruct1) test(there MyStruct1) {
//目前this是指针类型
fmt.Printf("%p\n", this) //0xc00000a060
*this = there
}
func main() {
var str1 = &MyStruct1{"张三", 18}
fmt.Printf("%p\n", str1) //0xc00000a060
var str2 = MyStruct1{"李四", 20}
fmt.Println(str1) //&{张三 18}
str1.test(str2)
fmt.Println(str1) //&{李四 20}
}
thisthere
4. 切片

切片的比较复杂一点,原因是我们知道切片的数据结构其实是有指针的,切片通过这个指针的指向,来获得地址中对应的数值。当然,把切片认为是一个结构体,然后结构体里有一个指针,这样会更好理解。

slice数据结构

4.1. 错误改变切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func test(a []int) {
fmt.Printf("%p\n", a) // 0xc0000161a0
fmt.Printf("%p\n", &a) // 0xc000088080
a[0] = 10
a = append(a, 1)
fmt.Printf("%p\n", a) // 0xc000014090
fmt.Printf("%p\n", &a) // 0xc000088080
}

func main() {
a := []int{2, 3, 4}
fmt.Println(a) // [2 3 4]
fmt.Printf("%p\n", a) // 0xc0000161a0
fmt.Printf("%p\n", &a) //0xc000094020
test(a)
fmt.Println(a) // [10 3 4]
fmt.Printf("%p\n", a) // 0xc0000161a0
fmt.Printf("%p\n", &a) //0xc000094020
}
fmt.Printf("%p\n", a)fmt.Printf("%p\n", &a)fmt.Printf("%p\n", a)a指针地址fmt.Printf("%p\n", &a)a
testa[0]testaa
appendappendtestmaina

4.2. 正确方式

我们使用引用传递,将a本身地址传入,就可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func test(a *[]int) {
fmt.Printf("%p\n", *a) //0xc0000161a0
fmt.Printf("%p\n", a) //0xc00000a060
*a = append(*a, 1)
fmt.Printf("%p\n", *a) //0xc000014090
fmt.Printf("%p\n", a) //0xc00000a060
}

func main() {
a := []int{2, 3, 4}
fmt.Println(a)
fmt.Printf("%p\n", a) //0xc0000161a0
fmt.Printf("%p\n", &a) //0xc00000a060
test(&a)
fmt.Println(a)
fmt.Printf("%p\n", a) //0xc000014090
fmt.Printf("%p\n", &a) //0xc00000a060
}
testafmt.Printf("%p\n", a)mainafmt.Printf("%p\n", *a)
appendtestamaina