首页 > 编程笔记 > Go语言笔记 阅读:12

Go语言中的深拷贝和浅拷贝(图文并茂,附带实例)

在 Go 语言中,变量拷贝发生在执行数据处理的时候,目的是保留数据处理前的变量而重新定义新的变量。简单来说,就是将一个变量的数据复制并存放到另一个变量中。

变量拷贝分为深拷贝和浅拷贝,深浅拷贝的区别在于变量之间是否共用一个内存地址。

深拷贝是两个变量分别使用不同的内存地址存储相同的数据,如变量 a 的内存地址为 0xc000040240,存放数据为字符串“hello”;变量 b 通过变量 a 赋值,但变量 b 的内存地址为 0xc000040260,此时变量 a 和 b 在不同内存地址中,两者互不干扰,仅仅是存储了相同的数据。

浅拷贝是两个变量的内存地址相同,当其中一个变量修改数据时,另一个变量的数据也会随之变化,如变量 a 和变量 b 的内存地址为 0xc000040240,当变量 a 修改内存地址的数据后,变量 b 的数据也会随之变化,换句话说,浅拷贝好比一个人有两个名字。

Go 语言对不同数据类型的变量设置了对应的拷贝方式,不同的数据类型分为值类型变量和引用类型变量。

对值类型变量的说明如下:
对引用类型变量的说明如下:
为了更好地理解值类型数据、引用类型数据和深浅拷贝的关系,我们通过示例加以说明,代码如下:
package main

import "fmt"

func main() {
   /* 值类型变量 */
   s := "hello"
   fmt.Printf("变量s的内存地址:%p,变量值为:%v\n", s, s)
   fmt.Printf("变量s的内存地址:%p,变量值为:%v\n", &s, &s)
   // 将变量赋值给另一个变量,执行深拷贝方式
   ss := s
   fmt.Printf("变量ss的内存地址:%p,变量值为:%v\n", ss, ss)
   fmt.Printf("变量ss的内存地址:%p,变量值为:%v\n", &ss, &ss)

   /* 引用类型变量 */
   m := make(map[string]interface{})
   m["name"] = "Tom"
   fmt.Printf("变量m的内存地址:%p,变量值为:%v\n", m, m)
   fmt.Printf("变量m的内存地址:%p,变量值为:%v\n", &m, &m)
   // 将变量赋值给另一个变量,执行浅拷贝方式
   mm := m
   fmt.Printf("变量mm的内存地址:%p,变量值为:%v\n", mm, mm)
   fmt.Printf("变量mm的内存地址:%p,变量值为:%v\n", &mm, &mm)
   // 修改某个变量的值,另一个变量随之变化
   mm["name"] = "Tim"
   fmt.Printf("变量m的内存地址:%p,变量值为:%v\n", m, m)
   fmt.Printf("变量m的内存地址:%p,变量值为:%v\n", &m, &m)
   fmt.Printf("变量mm的内存地址:%p,变量值为:%v\n", mm, mm)
   fmt.Printf("变量mm的内存地址:%p,变量值为:%v\n", &mm, &mm)
}
运行上述代码,运行结果为:

变量 s的内存地址: %!p(string=hello),变量值为: hello
变量 s的内存地址: 0xc00000402240,变量值为: 0xc00000402240
变量 ss的内存地址: %!p(string=hello),变量值为: hello
变量 ss的内存地址: 0xc0000040270,变量值为: 0xc0000040270
变量 m的内存地址: 0xc000007a3f0,变量值为: map[name:Tom]
变量 m的内存地址: 0xc000006030,变量值为: &map[name:Tom]
变量 mm的内存地址: 0xc000007a3f0,变量值为: map[name:Tom]
变量 mm的内存地址: 0xc000006038,变量值为: &map[name:Tom]
变量 m的内存地址: 0xc000007a3f0,变量值为: map[name:Tim]
变量 m的内存地址: 0xc000006030,变量值为: &map[name:Tim]
变量 mm的内存地址: 0xc000007a3f0,变量值为: map[name:Tim]
变量 mm的内存地址: 0xc000006038,变量值为: &map[name:Tim]

分析程序的运行结果得知:
1) 当变量为值类型变量的时候,如变量 s 为字符串类型,它仅分配了一个内存地址存储数据(即运行结果的 0xc000040240,%!p(string=hello)没有获取内存地址)。

2) 当变量 s 的值赋予变量 ss,变量 ss 拥有独立的内存地址,如 0xc000040270,变量 s 和 ss 是两个互不干扰的变量,说明值类型变量之间是通过深拷贝方式进行复制的。

3) 当变量为引用类型变量时,如变量 m 为集合 Map 类型,它具有两个不同的内存地址,如 0xc00007a3f0 和 0xc000006030,内存地址 0xc000006030 存储集合数据,而内存地址 0xc00007a3f0 作为变量 m 和集合数据的桥梁。

4) 当变量 m 的值赋予变量 mm 时,变量 mm 与变量 m 的内存地址相同,如内存地址 0xc00007a3f0,但两者的集合数据的内存地址不相同。

5) 当修改变量 mm 的集合数据时,尽管变量 m 和 mm 集合数据的内存地址不相同,但变量 m 的集合数据仍会随之变化。

如果将深浅拷贝、值类型和引用类型之间的关系通过图解表示,值类型与深拷贝的图解关系如下图所示:


图 1 值类型与深拷贝的图解关系

引用类型与浅拷贝的图解关系如下图所示:


图 2 引用类型与浅拷贝的图解关系

理解深浅拷贝、值类型和引用类型之间的关系之后,还需要掌握 Go 语言哪些数据类型是值类型;哪些数据类型是引用类型。在日常开发中,开发者常常会由于一时大意而忘记数据类型是值类型还是引用类型,导致程序出现错误而无法查明原因。

相关文章