Go深入理解深拷贝与浅拷贝

深拷贝和浅拷贝

深拷贝和浅拷贝是编程中处理对象或数据结构复制时的两种主要策略。理解它们之间的基本概念和差异对于避免潜在的数据共享和修改冲突至关重要。

定义

  • 浅拷贝, 它创建一个新的对象,并复制原始对象的所有非引用类型字段的值。然而,对于引用类型的字段(如切片、映射、通道、接口和指向结构体或数组的指针),浅拷贝仅仅复制了引用的地址,而非引用的实际内容。这意味着新对象和原始对象共享相同的引用类型字段的数据。

  • 深拷贝, 深拷贝则是对对象的完全复制,包括对象引用的其他对象。它递归地遍历原始对象的所有字段,并创建新的内存空间来存储这些字段的值,包括引用类型字段所指向的实际数据。这样,深拷贝后的对象与原始对象在内存中是完全独立的,对其中一个对象的修改不会影响另一个对象。

简单讲:通常在变量赋值、参数传递、函数返回值过程中产生变量拷贝,在变量拷贝过程中拷贝的是数据(值),还是存储数据的地址(引用)就是深拷贝(拷贝值)和浅拷贝(拷贝引用)的区别。

重点关注的是对引用类型的差别处理。

区别

本质就是,深拷贝会拷贝数据(两个变量存储地址不同,拷贝结束互不影响)。而浅拷贝只会拷贝内存的地址(即使拷贝结束,还是互相影响)。

两者的优劣势

在编程中,深拷贝和浅拷贝都有其特定的应用场景和需求。

浅拷贝场景:

  • 性能更好:浅拷贝只复制了对象本身和值类型的字段,而没有复制对象引用的其他对象,性能更好。尤其是在大对象的复制场景中。

  • 内存使用更少:浅拷贝没有创建新的对象来复制对象引用的其他对象,使用浅拷贝可能会减少内存使用。

  • 共享状态:浅拷贝可以共享被引用对象的状态。对被引用对象的修改,可以反应到所有的复制对象中。

深拷贝场景

  • 独立性:深拷贝可以确保两个对象在内存中的状态是完全独立的。当修改其中一个对象的属性或数据时,另一个对象不会受到影响。

  • 生命周期管理:深拷贝可以确保即使一个对象被销毁,另一个对象仍然拥有一个完好无损的数据副本。这避免了因为原始对象被销毁而导致的悬挂指针或多次释放的问题,从而保证了程序的稳定性和安全性。

  • 避免内存泄漏:浅拷贝可能导致两个对象在析构时尝试释放同一块内存的引用,造成内存泄漏。深拷贝通过重新为新对象分配内存,并复制实际数据,避免了这一问题。

  • 数据安全性:如果有多个(复制的)对象需要访问或修改(被引用的)数据,浅拷贝可能会导致数据冲突和不可预测的行为。深拷贝通过复制实际数据,确保了每个对象都有自己的数据副本,从而提高了数据的安全性。

Go中类型拷贝

GO中类型分为如下两种:

  • 值类型:基本数据类型, 如int、float、string等
  • 引用类型:map,slice,channel,func,指针。

在Go中只有 5个引用类型+ 1个接口类型可以被赋值为 nil 类型。 速记方式 mscfip

拷贝发生的场景

函数调用时,传参数的方式:

  • 值传递:拷贝值,递给函数的是变量的副本。
  • 引用传递:拷贝指针,递给函数的是变量的指针。

函数内部,给新变量赋值的时候:

  • 深拷贝:拷贝值,也叫值拷贝。
  • 浅拷贝:拷贝指针。

拷贝规则:值类型一般都是深拷贝,引用类型都是浅拷贝。
传递规则:go里面都是值传递。

注意:切片在一定条件下也是值拷贝。 这取决于 slice 内部的数组长度没有被重新的分配。

浅拷贝几种有趣例子

在Go语言中,方法的接收者可以是值类型或指针类型。这两种接收者类型在使用上有一些区别。

  • 值接收者:当方法的接收者是值类型时,方法内部对该接收者的修改不会影响原始值。这是因为Go语言中的方法调用是通过值传递来实现的,即传递的是接收者的副本。因此,对副本的修改不会反映到原始值上。

  • 指针接收者:当方法的接收者是指针类型时,方法内部对该接收者的修改会影响原始值。这是因为指针接收者直接操作的是原始值的内存地址,所以对指针的修改会反映到原始值上。

需要注意的是,无论方法的接收者是值类型还是指针类型,都可以通过值或指针来调用该方法。Go语言会自动进行取地址或解引用操作,以便正确调用方法。这得益于Go语言的类型系统和编译器的智能处理。

结论: 值接收者 和 指针接受者。正确的理解是: 值接收者是对 Rectangle 对象的浅拷贝, 而指针接受者是对Reactagle 对象的指针引用。 不可错误理解为:值时完全隔离的

关于我
loading