Golang深拷贝浅拷贝

在了解原型设计模式之前我们需要新知道Golang的深拷贝与浅拷贝之间的区别。

数据结构:

//速度速值
type Speed int

//风扇转速
type FanSpeed struct {
	Speed Speed
}

//售价
type Money struct {
	Length float64
}

//内存数量以及大小
type Memory struct {
	Count      int
	MemorySize []int
}

//电脑信息
type Computer struct {
	SystemName string              //系统名字
	UseNumber  int                 //使用次数
	Memory     Memory              //存储
	Fan        map[string]FanSpeed //风扇
	Money      Money               //售价
}

浅拷贝:

 接触过 Java或者C#的同学应该知道浅拷贝对于值类型的话是完全拷贝一份,而对于引用类型是拷贝其地址。也就是拷贝的对象修改引用类型的变量同样会影响到源对象。

这里Golang同理,在上述测试类型中涉及到 Slice 和 Map的修改则会互相影响。

测试1:

func ComputerStart1() {
	Pc1 := Computer{
		SystemName: "Windows",
		UseNumber:  1000,
		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
		Money:      Money{123.45},
	}

    //浅拷贝
	Pc2:=Pc1
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    //修改切片内容以及map信息影响Pc1
    Pc2.SystemName ="MacOs"
	Pc2.UseNumber =100
    Pc2.Memory.Count =2
    Pc2.Memory.MemorySize[0]=8
	Pc2.Memory.MemorySize[1]=8
	Pc2.Memory.MemorySize[2]=0
	Pc2.Memory.MemorySize[3]=0
	Pc2.Fan["left"]=FanSpeed{2000}
	Pc2.Fan["right"]=FanSpeed{1500}
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
}

 输入信息:

PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}, Pc2:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

对于PC2的修改影响到了PC1的Slice 和 Map

测试2:

func ComputerStart2() {
	Pc1 := Computer{
		SystemName: "Windows",
		UseNumber:  1000,
		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
		Money:      Money{123.45},
	}

    //浅拷贝
	Pc2:=Pc1
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)

	ModifyCat(Pc2)
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
}

func ModifyCat(pc Computer) {
	fmt.Printf("PcInfo Pc1:%v\n", pc)
	pc.SystemName ="MacOs"
	pc.UseNumber =100
	pc.Memory.Count =2
	pc.Memory.MemorySize[0]=8
	pc.Memory.MemorySize[1]=8
	pc.Memory.MemorySize[2]=0
	pc.Memory.MemorySize[3]=0
	pc.Fan["left"]=FanSpeed{2000}
	pc.Fan["right"]=FanSpeed{1500}
	fmt.Printf("PcInfo Pc1:%v\n", pc)
}

输入:

PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}
PcInfo Pc1:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}, Pc2:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

这里在方法中修改PC2同样影响到了 PC1以及PC2,是因为在Golang中方法中传递的参数同样被拷贝了一份,他们修改的Slice 和 Map都是同一份地址。

那么对于浅拷贝来说如何避免这种情况的发生呢?

测试3:

func ComputerStart2() {
	Pc1 := Computer{
		SystemName: "Windows",
		UseNumber:  1000,
		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
		Money:      Money{123.45},
	}

    //浅拷贝
	Pc2:=Pc1
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    //切片以及map新空间互不影响
    Pc2.SystemName ="MacOs"
	Pc2.UseNumber =100
	Pc2.Memory =Memory{Count: 2, MemorySize: []int{8, 8}}
	Pc2.Fan =map[string]FanSpeed{"left": {2000}, "right": {1500}}
	Pc2.Money =Money{1000.45}
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
}

输出:

PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{MacOs 100 {2 [8 8]} map[left:{2000} right:{1500}] {1000.45}}

既然只有Slice 和 Map会受到影响我们这里重新给定地址重新生成一个Slice和Map就可以不受影响。

深拷贝

对于深拷贝就比较好了解了,任何对象都会被完完整整的拷贝一份,拷贝对象与被拷贝对象不存在如何联系,也就不会互相影响。如果你需要拷贝的对象中没有引用类型,那么对于Golang而言使用浅拷贝就可以了。

基于序列化和反序列化来实现对象的深度拷贝:

func deepCopy(dst, src interface{}) error {
    var buf bytes.Buffer
    if err := gob.NewEncoder(&buf).Encode(src); err != nil {
        return err
    }
    return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

需要深拷贝的变量必须首字母大写才可以被拷贝

测试1:

func ComputerStart4() {
	Pc1 := &Computer{
		SystemName: "Windows",
		UseNumber:  1000,
		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
		Money:      Money{123.45},
	}

	//深拷贝
	Pc2:= new(Computer)
	if err:= deepCopy(Pc2,Pc1);err!=nil{
          panic(err.Error())
	}
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)

	ModifyCat1(*Pc2)
	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
}

输出:

PcInfo Pc1:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
PcInfo Pc1:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}
PcInfo Pc1:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:&{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

可以看到PC2经过浅拷贝(参数传递)在修改Slice和Map受到影响的也只有PC2和PC2的浅拷贝对象。对于PC1没有任何影响。