一、深浅拷贝
在很多编程语言都有深浅拷贝的这个概念,当然golang也不例外。
在 go 语言中 值类型 赋值都是 深拷贝 ,引用类型 一般都是 浅拷贝。
其本质就是,深拷贝会拷贝数据(两变量存储地址不同,拷贝结束互不影响)。而浅拷贝只会拷贝内存的地址(即使拷贝结束,还是互相影响),所以就会出现,像 slice 那样修改底层数组的值,slice 的值也跟着改动。
二、深拷贝
b 拷贝 a 后,如果修改 a 的值,b不变,说明是值的拷贝,也就是深拷贝。
package main
import (
"fmt"
)
func main() {
var a = 123
b := a //值的拷贝,深拷贝
fmt.Println(a, b)
a = 456
fmt.Println(a, b)
}
输出结果:
123 123
456 123
三、浅拷贝
b 拷贝 a 后,修改 a 的值,b 的值也跟着修改了;修改 b 的值,a 的值也跟着修改了。那就是地址的拷贝,是浅拷贝。
如下,a、b 两个 slice 指向同一个内存地址,slice 拷贝是浅拷贝。本质是因为 slice 属于 Go 的引用类型。
package main
import (
"fmt"
)
func main() {
a := []int{1, 2, 3}
b := a //地址的拷贝,浅拷贝
fmt.Println(a, b)
a[0] = 1000
fmt.Println(a, b)
b[2] = 4000
fmt.Println(a, b)
}
输出结果:
[1 2 3] [1 2 3]
[1000 2 3] [1000 2 3]
[1000 2 4000] [1000 2 4000]
实现 slice 深拷贝:
1.make 一个新的,然后使用 append 赋值:
package main
import (
"fmt"
)
func main() {
a := []int{1, 2, 3}
b := make([]int, 0) //创建新切片
b = append(b, a[:]...)
fmt.Println(a, b)
a[1] = 1000
fmt.Println(a, b)
fmt.Printf("%p,%p", a, b) // make 的新 b 和 a 拥有不同的地址
}
输出结果:
[1 2 3] [1 2 3]
[1 1000 3] [1 2 3]
0xc00000e270,0xc00000e288
1.也可以通过内置的 copy 函数进行复制
需要注意的是copy函数不会扩容,也就是要复制的 slice 比原 slice 要大的时候,只会移除多余的。
package main
import (
"fmt"
)
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
fmt.Println(slice1, slice2)
slice2 = []int{5, 4, 3}
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
fmt.Println(slice1, slice2)
}
输出结果:
[1 2 3 4 5] [1 2 3]
[5 4 3 4 5] [5 4 3]
四、总结
归根结底,使用 := 进行拷贝时:
1. 会产生深拷贝还是浅拷贝取决于被拷贝数据的数据类型:
- 如果是值类型,就会产生深拷贝
- 如果是引用类型,就会产生浅拷贝
关于 Go 的常用数据类型是值类型还是引用类型请参见我的另一篇文章:【Go】Go语言数据类型‘
2. 深浅拷贝的区别:
- 深拷贝:光拷贝值,地址不相关。拷贝结束两变量互不影响。
- 浅拷贝:拷贝地址。两变量指向同一地址。拷贝结束也相关,改变一个,另一个也跟着变。
3. 另外,对于引用类型,想实现深拷贝,就不能直接 := ,而是要先 new ,再赋值。
参考链接