Goland的值与引用类型

Goland的值与引用类型

在新的项目中,用到的Goland。以前用Python做项目,Python基本变量的赋值是引用重定向。Goland有些不一样,这里做个测试

直接利用打印变量值、地址的方式确认变量拷贝情况


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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main
import "fmt"

func getInt() int {
i := 5
println("in getInt, i", i, &i)
return i
}

func intTest(i int) {
println("in intTest, i", i, &i)
i = 99
}

func listTest(list []int) {
println("in listTest, list", &list, list)
list[0] = 99
}

func mapTest(mapInt map[int]int) {
println("in mapTest, mapInt", mapInt, &mapInt)
mapInt[99] = 100
}

func main() {
t := 1
fmt.Println("in main, t", t, &t)
t = 2
fmt.Println("in main, t", t, &t)
t = getInt()
fmt.Println("in main, t", t, &t)
intTest(t)
fmt.Println("in main, t", t, &t)

fmt.Println("*************************")
l := []int{1,2,3}
println("in main, l", &l, l)
l = []int{1,2,3,4,5}
println("in main, l", &l, l)

l2 := l
println("in main, l2", &l2, l2)
listTest(l)
fmt.Println("in main, l", l)

fmt.Println("*************************")
mapInt := make(map[int]int)
mapInt[1] = 2
println("in main, mapInt", mapInt, &mapInt)
mapTest(mapInt)
println("in main, mapInt", mapInt, &mapInt)
fmt.Println("in main, mapInt", mapInt)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输出结果
in main, t 1 0xc0000140a0
in main, t 2 0xc0000140a0
in getInt, i 5 0xc00006ade0
in main, t 5 0xc0000140a0
in intTest, i 5 0xc00006ade8
in main, t 5 0xc0000140a0
*************************
in main, l 0xc00006ae58 [3/3]0xc000012140
in main, l 0xc00006ae58 [5/5]0xc00001a060
in main, l2 0xc00006ae40 [5/5]0xc00001a060
in listTest, list 0xc00006ae28 [5/5]0xc00001a060
in main, l [99 2 3 4 5]
*************************
in main, mapInt 0xc00006adf0 0xc000062180
in mapTest, mapInt 0xc00006adf8 0xc000062180
in main, mapInt 0xc00006adf0 0xc000062180
in main, mapInt map[1:2 99:100]

分析

先看第一组结果,简单int变量t,重新赋值为2后,在内存中的地址不变。与Python不同,Python简单对象赋值是引用重定向,引用一个新的对象,会改变id(obj)的结果。Goland是在原对象的基础上修改,改变原对象中的内容。函数的传参、返回值都会拷贝一个新对象,修改拷贝的对象不影响原对象。

再看第二组结果,切片类型l,打印第一个数值是变量地址,后面接的是[len/cap]数组内存地址,切片独享保留着对数组内存地址的引用。第二次对切片赋值,同样的对象地址不变,其len、cap、数组地址发生变化。在函数listTest中,形参同样会生成一个新的对象,切片拷贝对象的len、cap、数组内存地址与元对象一致,如果改变拷贝切片中的元素,由于其引用同一个连续数组内存,原对象上也会得到体现,最后main中l[0]=99

再看第三组结果,map类型,打印的第一个值是map变量的地址,第二个参数是map对象引用的数据块地址。同样的,map对象作为形参,会拷贝出一个新对象,与list类似,新老对象引用的数据块地址一致,修改拷贝对象数据块中的数据,会影响老对象的数据。

总结

  • Goland对存在的对象重新赋值,会改变原对象内存中的数据,与Python引用重定向相区别
  • Goland中slices、map、channel为引用类型,其余简单类型为值类型,值类型对象中空间中包含了对象所需的全部内容,引用类型除了基本信息外还引用着一块可以共享的数据块
  • Goland对象作为函数参数、函数返回值、对象间赋值、copy等机制会从老对象拷贝出一个新的对象。这里是浅拷贝机制,值类型完全复制,共用引用数据块,复制引用地址。
  • 修改拷贝对象时,拷贝对象是引用类型,且修改的是引用数据块中的数据,会影响所有引用该数据块引用对象,这里容易出bug。
  • 深拷贝可考虑序列化、反序列化来实现,深拷贝都比较耗时,非必须不建议使用