Go中的数据可以分为值类型和引用类型,值类型存储的是具体的值,引用类型存储的是指向某个地址的指针。
如下图所示,变量a
代表的地址A
中存储的就是值111
,存储的是值本身,所以通过变量能直接拿到值。变量b
代表的地址B
中不是直接存储的值,而是存储的一个指针(指向地址=A)
,通过地址B
能间接拿到地址A
中存储的值。
Go中的值类型和引用类型有:
- 值类型:数组、结构体、布尔值、数字、字符串
- 引用类型:切片、映射、通道、指针、函数、接口
浅拷贝与深拷贝的不同在于对引用类型的处理,浅拷贝遇到引用类型时,会复制指针,而深拷贝遇到引用类型时,会复制引用类型指向的具体的值。
浅拷贝
赋值语句
数组是值类型,切片是引用类型,对数组赋值是复制了一份值到新的变量中,对切片赋值时是复制的指针,所以对其中一个变量的修改会影响到另一个变量指向的值。
数组,修改不互相影响:
go
a1 := [3]int{10, 20, 30}
a2 := a1
a2[0] = 100
fmt.Println(a1) // [10 20 30]
fmt.Println(a2) // [100 20 30]
切片,修改的是 指针指向的同一个值 所以会相互影响:
go
s1 := []int{1, 2, 3, 4, 5}
s2 := s1
s2[0] = 100
fmt.Println(s1) // [100 2 3 4 5]
fmt.Println(s2) // [100 2 3 4 5]
copy
copy是用于复制切片的函数,会将一个切片复制到另一个切片中,copy比直接赋值好一点,会复制切片中的值到新的切片中:
go
s3 := make([]int, len(s1))
copy(s3, s1) // 将s1的值复制到s3
s3[0] = 500
fmt.Println(s1) // [100 2 3 4 5]
fmt.Println(s3) // [500 2 3 4 5]
所以s3和s1也是不互相影响的。
但是对于切片中包含引用类型的情况,copy复制的是引用类型的地址,所以还是会有影响:
go
s4 := [][]int{
{1, 2, 3},
{4, 5},
}
s5 := make([][]int, len(s4))
copy(s5, s4)
s5[0][0] = 100
fmt.Println(s4) // [[100 2 3] [4 5]]
fmt.Println(s5) // [[100 2 3] [4 5]]
这时候需要进行再一层的复制才行:
go
s6 := make([][]int, len(s4))
for i, val := range s4 {
item := make([]int, len(val))
copy(item, val)
s6[i] = item
}
s6[0][0] = 500
fmt.Println(s4) // [[100 2 3] [4 5]]
fmt.Println(s6) // [[500 2 3] [4 5]]
这里用到了make
,顺带一提make
和new
的区别,看它们的注释就能知道了:
go
// The make built-in function allocates and initializes an object of type slice, map, or chan (only).
// Like new, the first argument is a type, not a value.
// Unlike new, make's return type is the same as the type of its argument, not a pointer to it.
x1 := make([]int, 10)
// The new built-in function allocates memory.
// The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.
x2 := new([]int)
make
和new
都是用于分配内存,第一个参数都是数据类型,不同点在于:
make
只用于类型slice
、map
、chan
,而new
用于更多类型。make
传入的是什么类型,返回的就是什么类型的值,而new
返回的是指向传入的类型的指针类型。
深拷贝
使用json序列化
使用json.Marshal(序列化)
和json.UnMarshal(反序列化)
,先用json.Marshal
将数据编码为json
字符串,然后用json.UnMarshal
将数据反编码为对应的值。
go
s7 := [][][]int{
{{1, 2, 3}, {4, 5}},
{{6}},
}
var s8 [][][]int
b, _ := json.Marshal(s7)
json.Unmarshal(b, &s8)
s8[0][0][0] = 100
fmt.Println(s7) // [[[1 2 3] [4 5]] [[6]]]
fmt.Println(s8) // [[[100 2 3] [4 5]] [[6]]]
这种方法只对可导出的字段生效,对于不可导出的字段不能进行json
序列化和反序列化。也不能对函数类型进行序列化和反序列化(当然一般不会把函数类型作为数据来使用)。
go
d1 := Data{
SliceData: []int{1, 2, 3},
mapData: map[string]int{"A": 1},
}
var d2 Data
b1, _ := json.Marshal(d1)
json.Unmarshal(b1, &d2)
d2.SliceData[0] = 100
fmt.Println(d1) // {[1 2 3] map[A:1]}
fmt.Println(d2) // {[100 2 3] map[]}
手动复制
创建一个新的对象,手动将值复制到新对象中。手动复制不论字段是导出字段还是不可导出字段都可以复制。
go
d1 := Data{
SliceData: []int{1, 2, 3},
mapData: map[string]int{"A": 1},
}
var d3 Data
sliceData := make([]int, len(d1.SliceData))
// 这个切片的复制可以直接用copy(sliceData, d1.SliceData) 替换
for i, v := range d1.SliceData {
sliceData[i] = v
}
d3.SliceData = sliceData
d3.SliceData[0] = 500
mapData := make(map[string]int)
for key, val := range d1.mapData {
mapData[key] = val
}
d3.mapData = mapData
d3.mapData["B"] = 2
fmt.Println(d1) // {[1 2 3] map[A:1]}
fmt.Println(d3) // {[500 2 3] map[A:1 B:2]}
封装通用的方法
手动复制的过程中一些方法其实是通用的,把这些方法抽象出来作为通用的方法可以提高效率。
go
package deepcopy
// 复制指针
// 使用 ... 是为了使函数copier作为可选参数,而不是必传的
func CopyPointer[T any](original *T, copier ...func(T) T) *T {
if original == nil {
return nil
}
var cp T
if len(copier) > 0 {
cp = copier[0](*original)
} else {
cp = *original
}
return &cp
}
// 复制切片
func CopySlice[T any](original []T, copier ...func(T) T) []T {
if original == nil {
return nil
}
var cs = make([]T, len(original))
for i, val := range original {
if len(copier) > 0 {
cs[i] = copier[0](val)
} else {
cs[i] = val
}
}
return cs
}
// 复制映射
func CopyMap[K comparable, V any](original map[K]V, copier ...func(V) V) map[K]V {
if original == nil {
return nil
}
cm := make(map[K]V)
for key, val := range original {
if len(copier) > 0 {
cm[key] = copier[0](val)
} else {
cm[key] = val
}
}
return cm
}
复制的时候这样使用:
go
package deepcopy
import "fmt"
func init() {
type Data struct {
PointerData *int
SliceData []int
mapData map[string]int
}
var num = 1
p1 := &num
d5 := Data{
PointerData: p1,
SliceData: []int{1, 2, 3},
mapData: map[string]int{"A": 1},
}
d6 := Data{
PointerData: CopyPointer[int](d5.PointerData),
SliceData: CopySlice[int](d5.SliceData),
mapData: CopyMap[string, int](d5.mapData),
}
var num1 = 100
p2 := &num1
d6.PointerData = p2
d6.SliceData[0] = 500
d6.mapData["B"] = 1000
fmt.Println(d5) // {0xc0000ac018 [1 2 3] map[A:1]}
fmt.Println(d6) // {0xc0000ac048 [500 2 3] map[A:1 B:1000]}
}
更通用的方法(暂时无法实现)
尝试通过递归实现下面这种方法,直接将值传入就能得到一个深拷贝的值,但是无法实现。因为无法通过反射拿到具体的类型作为函数的类型参数。
go
func DeepCopyAny[T any](val T) T
比如:
go
// 拿不到类型中的类型,比如切片中每个元素的类型
func copier[T any](val T) T {
var empty T
// 通过反射拿到数据的类型,比如slice、struct、map、pointer等
vv := reflect.ValueOf(val)
kind := vv.Kind()
// 根据不同的类型选择不同的复制函数
switch {
case kind == reflect.Slice:
if vv.Len() == 0 {
return empty
}
vvItem := vv.Index(0)
// 拿到元素的类型
// reflect.TypeOf(vvItem) is not a typecompilerNotAType
// func reflect.TypeOf(i any) reflect.Type
return CopySlice[reflect.TypeOf(vvItem)](val)
case kind == reflect.Struct:
return CopyStruct(val)
case kind == reflect.Pointer:
return CopyPointer(val)
case kind == reflect.Map:
return CopyMap(val)
default:
return val
}