Golang结构体类型的深浅拷贝

在开发中会经常的把一个变量复制给另一个变量,有时候为了不让对象直接相互干扰,我们需要深度赋值对象

那么这个过程,可能涉及到深浅拷贝

1、浅拷贝

浅拷贝是指对地址的拷贝

浅拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化,释放内存地址时,同时释放内存地址

slicemapfunction

浅拷贝的特点:

ab

2、深拷贝

深拷贝是指将地址指向的值进行拷贝

深拷贝的是数据本身,创造一个一样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放

intfloatboolarraystruct

深拷贝的特点:

  • 复制的时候会新创建一个对象
  • 指向完全不同的内存地址
  • 修改是互不影响的

通过指针求值,将值拷贝实现,修改拷贝的值不影响原来的值

type Author struct {
	Name string
	Aage int
}

type Title struct {
	Main string
	Sub  string
}

type Book2 struct {
	Author *Author
	Title  *Title
}

func (b *Book2) GetName() string {
	return b.Author.GetName() + "book"
}

func TestMain8(t *testing.T) {
	b1 := Book2{
		Author: &Author{
			Name: "old author",
		},
		Title: &Title{},
	}

	b2 := &Book2{
		Author: &Author{},
		Title: &Title{},
	}
	*b2.Author = *b1.Author
	*b2.Title = *b1.Title

	b2.Author.Name = "new author"
	fmt.Println(b1.Author.Name)  // old author
	fmt.Println(b2.Author.Name)  // new author
}

3、结构体的深拷贝

默认情况下,结构体类型中的字段是值类型,拷贝时都是深拷贝

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int
}

func main()  {
	p1 := Per{
		Name:     "ssgeek",
		Age:      24,
		HouseIds: [2]int{22, 33},
	}
	p2 := p1
	fmt.Printf("%v %p \n", p1, &p1)  // {ssgeek 24 [22 33]} 0xc000180030
	fmt.Printf("%v %p \n", p2, &p2)  // {ssgeek 24 [22 33]} 0xc000180060
	p2.Age = 19
	p2.Name = "likui"
	p2.HouseIds[1] = 44
	fmt.Printf("%v %p \n", p1, &p1)  // {ssgeek 24 [22 33]} 0xc000098180
	fmt.Printf("%v %p \n", p2, &p2)  // {likui 19 [22 44]} 0xc0000981b0
}

4、结构体的浅拷贝

p1p2
package main

import "fmt"

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int
}

func main()  {
	p1 := Per{
		Name:     "ssgeek",
		Age:      24,
		HouseIds: [2]int{22, 33},
	}
	p2 := &p1
	fmt.Printf("%v %p \n", p1, &p1)  // {ssgeek 24 [22 33]} 0xc000076180
	fmt.Printf("%v %p \n", p2, p2)  // &{ssgeek 24 [22 33]} 0xc000076180
	p2.Age = 19
	p2.Name = "likui"
	p2.HouseIds[1] = 44
	fmt.Printf("%v %p \n", p1, &p1)  // {likui 19 [22 44]} 0xc000076180
	fmt.Printf("%v %p \n", p2, p2)  // &{likui 19 [22 44]} 0xc000076180
}

5、结构体值类型的浅拷贝

new

值类型的默认是深拷贝,想要实现值类型的浅拷贝,一般是两种方法

  • 使用指针
  • 使用new函数(new函数返回的是指针)
package main

import "fmt"

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int
}

func main()  {
	p1 := new(Per)
	p1.HouseIds = [2]int{22, 33}
	p1.Name = "songjiang"
	p1.Age = 20

	p2 := p1
	fmt.Printf("%v %p \n", p1, p1)  // &{songjiang 20 [22 33]} 0xc000076180
	fmt.Printf("%v %p \n", p2, p2)  // &{songjiang 20 [22 33]} 0xc000076180
	p2.Age = 19
	p2.Name = "likui"
	p2.HouseIds[1] = 44
	fmt.Printf("%v %p \n", p1, p1)  // &{likui 19 [22 44]} 0xc000076180
	fmt.Printf("%v %p \n", p2, p2)  // &{likui 19 [22 44]} 0xc000076180
}

6、结构体引用类型的浅拷贝

结构体默认是深拷贝,但如果结构体中包含map、slice等这些引用类型,默认也还是浅拷贝

map是引用类型,引用类型浅拷贝是默认的情况

package main

import "fmt"

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int  // 数组,指定了长度
	CarIds   []int  // 切片,没指定长度
	Labels   map[string]string
}

func main() {
	//p1 := new(Per)
	p1 := Per{
		Name:     "ssgeek",
		Age:      24,
		HouseIds: [2]int{22, 33},  // 数组,指定了长度,深拷贝
		CarIds: []int{911, 718},  // 切片,引用类型,浅拷贝
		Labels:   map[string]string{"k1": "v1", "k2": "v2"},
		// 上述三个都是值类型,深拷贝,这个map是引用类型,浅拷贝
	}
	p2 := p1
	fmt.Printf("%v %p \n", p1, &p1) // {ssgeek 18 [22 33] map[k1:v1 k2:v2]} 0xc000076180
	fmt.Printf("%v %p \n", p2, &p2) // {ssgeek 18 [22 33] map[k1:v1 k2:v2]} 0xc0000761e0
	p2.Age = 19
	p2.Name = "likui"
	p2.HouseIds[1] = 44
	p2.CarIds[0] = 119
	p2.Labels["k1"] = "m1"
	fmt.Printf("%v %p \n", p1, &p1) // {ssgeek 24 [22 33] map[k1:m1 k2:v2]} 0xc000076180
	fmt.Printf("%v %p \n", p2, &p2) // {likui 19 [22 44] map[k1:m1 k2:v2]} 0xc0000761e0
}

7、结构体引用类型的深拷贝

结构体中含有引用类型的字段,那么这个字段就是浅拷贝,但是往往希望的是深拷贝,解决方案如下

  • 方法一:挨个把可导致浅拷贝的引用类型字段自行赋值

赋值后,修改值就相互不影响了

package main

import "fmt"

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int  // 数组,指定了长度
	CarIds   []int  // 切片,没指定长度
	Labels   map[string]string
}

func main() {
	//p1 := new(Per)
	p1 := Per{
		Name:     "ssgeek",
		Age:      24,
		HouseIds: [2]int{22, 33},  // 数组,指定了长度,深拷贝
		CarIds: []int{911, 718},  // 切片,引用类型,浅拷贝
		Labels:   map[string]string{"k1": "v1", "k2": "v2"},
		// 上述三个都是值类型,深拷贝,这个map是引用类型,浅拷贝
	}
	p2 := p1
	// 切片赋值到新的切片
	tmpCarIds := make([]int, 0)
	for _, c := range p1.CarIds{
		tmpCarIds = append(tmpCarIds, c)
	}
	// map赋值到新的map
	tmpLabels := make(map[string]string)
	for k, v := range p1.Labels{
		tmpLabels[k] = v
	}
	p2.CarIds = tmpCarIds
	p2.Labels = tmpLabels
	fmt.Printf("%v %p \n", p1, &p1) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c050
	fmt.Printf("%v %p \n", p2, &p2) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c0a0
	p1.Age = 19
	p1.Name = "likui"
	p1.HouseIds[1] = 44
	p1.CarIds[0] = 119
	p1.Labels["k1"] = "m1"
	fmt.Printf("%v %p \n", p1, &p1) // {likui 19 [22 44] [119 718] map[k1:m1 k2:v2]} 0xc00006c050
	fmt.Printf("%v %p \n", p2, &p2) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c0a0
}
json
jsondumpdump
package main

import (
	"encoding/json"
	"fmt"
)

type Per struct {
	Name     string
	Age      int
	HouseIds [2]int // 数组,指定了长度
	CarIds   []int  // 切片,没指定长度
	Labels   map[string]string
}

func main() {
	p1 := Per{
		Name:     "ssgeek",
		Age:      24,
		HouseIds: [2]int{22, 33},  // 数组,指定了长度,深拷贝
		CarIds:   []int{911, 718}, // 切片,引用类型,浅拷贝
		Labels:   map[string]string{"k1": "v1", "k2": "v2"},
		// 上述三个都是值类型,深拷贝,这个map是引用类型,浅拷贝
	}
	data, _ := json.Marshal(p1)
	var p2 Per
	json.Unmarshal(data, &p2)
	fmt.Printf("%v %p \n", p1, &p1) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c050
	fmt.Printf("%v %p \n", p2, &p2) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c140
	p1.Age = 19
	p1.Name = "likui"
	p1.HouseIds[1] = 44
	p1.CarIds[0] = 119
	p1.Labels["k1"] = "m1"
	fmt.Printf("%v %p \n", p1, &p1) // {likui 19 [22 44] [119 718] map[k1:m1 k2:v2]} 0xc00006c050
	fmt.Printf("%v %p \n", p2, &p2) // {ssgeek 24 [22 33] [911 718] map[k1:v1 k2:v2]} 0xc00006c140
}
  • 方法三:使用其他三方库(这里还没深入)