一、深浅拷贝

在很多编程语言都有深浅拷贝的这个概念,当然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 ,再赋值。


参考链接