GO 浅拷贝和深拷贝

深浅本质区别

  • 是否真正获取到了被拷贝对象的单独掌控权,而不会产生互相影响的问题!

深拷贝(Deep Copy)

定义

  • 拷贝之后互不影响,即深可以理解为完全拷贝!
  • 拷贝的是数据本身,创造一个一样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
  • 这样就不会产生引用问题,导致GC无法回收,也不会导致内存泄露问题!
  • 如果结构中不含指针,则直接赋值就是深度拷贝;

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

什么时候需要深拷贝

在函数中需要更改结构体变量内容而对函数外的变量不产生影响时,需要深拷贝。

示例:

值类型就是深拷贝

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var it int = 20
    ita := it
    fmt.Println(it, ita)
    ita = 30
    fmt.Println(it, ita)
    it = 40
    fmt.Println(it, ita)
}

浅拷贝(Shallow Copy)

定义

  • 拷贝之后互相影响,即浅拷贝可以理解为只拷贝了一部分,比如引用类型,只会拷贝指向的底层数据指针,并不会拷贝底层数据!
  • 即拷贝对象和被拷贝对象指向的内存地址是一样的,所以会互相影响!只有都被释放时,才会同时释放内存地址。
  • 浅拷贝会引起内存泄露、GC不能回收等问题!
  • 如果结构体中含有指针(包括自定义指针,以及切片,map等使用了指针的内置类型),则数据源和拷贝之间对应指针会共同指向同一块内存,这时如果想深度拷贝需要特别处理。
  • 用类型的数据,默认全部都是浅复制,Slice,Map

什么时候需要浅拷贝

很明显,仅当我们想让两个变量的修改互相被影响时,才使用浅拷贝,其他任何情况都应该使用深拷贝!

示例

  • 直接将引用类型变量赋值给另一个变量,属于浅拷贝
  • 将引用类型变量作为实参传给函数,也属于浅拷贝
  • append,不扩容的话也属于浅拷贝
  • copy拷贝的切片中的元素,如果元素是引用类型,那么此时也属于浅拷贝!

将一个引用类型变量赋值给另一个变量

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var sl []int = make([]int, 3, 4)
    fmt.Println("sl=", sl)   //sl= [0 0 0]
    sla := sl                //这就属于浅拷贝
    fmt.Println("sla=", sla) //sla= [0 0 0]
    sla[0] = 2
    sl[1] = 4
    fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
    fmt.Printf("sla: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sla, sla, sla)
}

运行结果:

sl= [0 0 0]
sla= [0 0 0]
sl: 为该变量分配的地址为=0xc000092060;指向内存地址=0xc0000a4060;值=[2 4 0]
sla: 为该变量分配的地址为=0xc000092090;指向内存地址=0xc0000a4060;值=[2 4 0]

new也属于浅拷贝

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var sl []int = make([]int, 3, 4)
    fmt.Println("sl=", sl) //sl= [0 0 0]
    sla := new([]int)
    *sla = sl                //这就属于浅拷贝
    //sla = &sl和*sla = sl等价的
    fmt.Println("sla=", sla) //sla= [0 0 0]
    (*sla)[0] = 2
    sl[1] = 4
    fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
    fmt.Printf("sla: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sla, *sla, sla)
}
sl= [0 0 0]
sla= &[0 0 0]
sl: 为该变量分配的地址为=0xc000008078;指向内存地址=0xc00000e1e0;值=[2 4 0]
sla: 为该变量分配的地址为=0xc00000a030;指向内存地址=0xc00000e1e0;值=&[2 4 0]

Copy

元素类型为值类型(深拷贝)

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var sl []int = make([]int, 3, 4)
    sl[0] = 20
    sl[1] = 200
    sl[2] = 2000
    cpSl := make([]int, len(sl))//由于元素类型为值类型,所以这里是深度拷贝
    copy(cpSl, sl)
    fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl)
}

运行结果:很明显指向的内存地址不是一个! 


D:\GOMOD\hello>go run hello.go
sl: 为该变量分配的地址为=0xc000008078;指向内存地址=0xc00000e1e0;值=[20 200 2000]
cpSl: 为该变量分配的地址为=0xc000008090;指向内存地址=0xc000010210;值=[20 200 2000]

元素类型为引用类型(浅拷贝)

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var sl [][]int = make([][]int, 3, 4)
    sl[0] = make([]int, 1, 2)
    sl[0] = []int{1}
    sl[1] = make([]int, 1)
    sl[1] = []int{2}
    sl[2] = make([]int, 1)
    sl[2] = []int{3}
    cpSl := make([][]int, len(sl)) //由于元素类型为引用类型,所以这里是浅拷贝
    copy(cpSl, sl)
    //由于是二维切片,此时sl 通过%p获取的是系统为第二维的切片值分配的内存地址,而不是最终指向数据的地址
    //&sl获取到的系统为变量sl分配的内存地址
    //&sl[0]和sl通过%p获取的地址都是为了保存第二维的切片又开辟的一个内存空间的地址,即[][]int,为了保存后面的[]int数据而开辟的空间
    //sl[0]通过%p获取到的地址才是一个内存空间地址,指向的数据类型为Int的数组,具体指向的是数组的第一个元素
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
    sl[0][0] = 11
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
    cpSl[1][0] = 22
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
}

运行结果:

sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [22] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[11] [22] [3]]
  • 很明显最终指向数组内存空间的地址是一个
  • 而我们分别修改两个变量都会彼此影响

如何解决上面引用类型浅拷贝问题呢

化简为繁,我们依次拷贝二维切片第二维的每一个元素!

/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

func main() {
    var sl [][]int = make([][]int, 3, 4)
    sl[0] = make([]int, 1, 2)
    sl[0] = []int{1}
    sl[1] = make([]int, 1)
    sl[1] = []int{2}
    sl[2] = make([]int, 1)
    sl[2] = []int{3}
    cpSl := make([][]int, len(sl)) //由于元素类型为引用类型,所以这里是浅拷贝
    
    for k, v := range sl {//依次拷贝!
        cpSl[k] = make([]int, len(v))
        copy(cpSl[k], v)
    }
    //copy(cpSl, sl)
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
    sl[0][0] = 11
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
    cpSl[1][0] = 22
    fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
    fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
}
D:\GOMOD\hello>go run hello.go
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [22] [3]]

结构体

结构体拷贝:

  • 字段类型为引用类型或者指针类型,那么就会被浅拷贝
  • 字段类型为值类型,那么就是深拷贝
  • 接口虽然也是引用类型,但是属于深拷贝
/*
 * @Description:
 * @version:
 * @Author: Steven
 * @Date: 2022-10-10 23:44:53
 */
package main

import "fmt"

type goods struct {
    name  string
    sl    []int
    ptr   *int
    inter interface{}
}

func main() {
    var ga goods = goods{}
    ga.name = "辣条"
    ga.sl = make([]int, 2)
    ga.sl[0] = 20
    ga.ptr = new(int)
    ga.inter = "aaaaa"
    gb := ga
    fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
    gb.sl[1] = 30
    gb.name = "汉堡"
    gb.inter = "bbbbb"
    fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
    ga.sl[1] = 40
    ga.name = "可乐"
    ga.inter = "cccc"
    fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
}

运行结果:

D:\GOMOD\hello>go run hello.go
ga[v={name:辣条 sl:[20 0] ptr:0xc0000a6058 inter:aaaaa};];gb[v={name:辣条 sl:[20 0] ptr:0xc0000a6058 inter:aaaaa}]
ga[v={name:辣条 sl:[20 30] ptr:0xc0000a6058 inter:aaaaa};];gb[v={name:汉堡 sl:[20 30] ptr:0xc0000a6058 inter:bbbbb}]
ga[v={name:可乐 sl:[20 40] ptr:0xc0000a6058 inter:cccc};];gb[v={name:汉堡 sl:[20 40] ptr:0xc0000a6058 inter:bbbbb}]

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Go语言中,map是一种引用类型,所赋值操作会进行浅拷贝,即只会复制指向底层数据的指针,并不会复制底层数据本身。如果需要进行深拷贝,可以通过以下方法实现: 1. 手动复制元素: - 创建一个新的空map。 - 遍历原始map的键值对,将每个键值对复制到新map中。 - 这样就创建了一个新的map,其中的键值对是原始map中键值对的副本。 示例代码: ```go originalMap := map[string]int{"a": 1, "b": 2, "c": 3} // 进行深拷贝 newMap := make(map[string]int) for key, value := range originalMap { newMap[key] = value } // 修改原始map的值 originalMap["a"] = 100 fmt.Println(originalMap) // 输出 map[a:100 b:2 c:3] fmt.Println(newMap) // 输出 map[a:1 b:2 c:3] ``` 2. 使用第三方库: - 可以使用一些第三方库来实现深拷贝,例如`github.com/mitchellh/copystructure`库提供了`copystructure.Copy()`函数,可以用于深拷贝map及其他复杂数据结构。 示例代码: ```go import ( "github.com/mitchellh/copystructure" ) originalMap := map[string]int{"a": 1, "b": 2, "c": 3} // 进行深拷贝 newMap, err := copystructure.Copy(originalMap) if err != nil { // 错误处理 } // 修改原始map的值 originalMap["a"] = 100 fmt.Println(originalMap) // 输出 map[a:100 b:2 c:3] fmt.Println(newMap) // 输出 map[a:1 b:2 c:3] ``` 无论是手动复制元素还是使用第三方库,都可以实现map深拷贝操作。根据实际需求选择适合的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值