彻底搞懂什么是深拷贝和浅拷贝

本文深入探讨了JavaScript中深拷贝和浅拷贝的概念及其实现方式,通过具体实例讲解了两者之间的区别,并提供了实现深拷贝的递归函数和JSON方法。

更详细的文章请转看: 浅谈Javacript浅拷贝和深拷贝的实现

深拷贝和浅拷贝,主要是对象发生复制的时候,根据复制的层级不同来区分的。很多人在这里经常变量赋值发生混淆。我们前面知道了,对于JavaScript数组等复杂的数据类型来说,将其赋值给其它变量,其实只是复制了对象的地址给它,两个变量指向的是同一个对象,因此普通的赋值既不是深拷贝也不是浅拷贝

浅拷贝

所谓浅拷贝就是指对象复制的时候只复制一层。

var xiaoHong = {
    name: '小红',
    age: 18
}

var zhangSan = {
    name: '张三',
    age: 20,
    girl: xiaoHong
}

上面的代码在内存中我们可以这样描述:

在这里插入图片描述

也就是说,对象里面的值还是个对象(引用类型),如果我们去复制zhangSan这个对象,我们可以这样复制:

var xiaoMing = {};
// 将zhangSan的所有属性都复制给xiaoMing
for (var i in zhangSan){
    // 检查对象是否有用某个属性
    if (zhangSan.hasOwnProperty(i)) {
    	xiaoMing[i] = zhangSan[i];
    }
}
console.log(xiaoMing);

我们在继续运行代码:

xiaoMing.girl.name = '花花';
// 我们发现,这两个对象的girl属性指向的是同一个对象
console.log(xiaoMing);
console.log(zhangSan);

我们发现,这两个对象的girl属性指向的事同一个对象,一个发生变化的时候,另一个跟着也会变。这种只复制对象一层的情况,我们称之为浅拷贝,内存中的样子是这样的:

在这里插入图片描述

// 浅拷贝封装的函数
// 对象的拷贝的目的是得到一个新对象
function simpleCopy(obj) {
    var newObj = {};
    for (var key in obj) {
        // 检查对象是否有用某个属性
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
深拷贝

深拷贝是指复制对象的所有层级。就像前面的例子一样,深拷贝完成后应该是这样的效果:

在这里插入图片描述

也就是说,所有层级都是复制出来了,而且互不干涉,就像克隆的一样,长得一模一样,但是所有内容都是相互独立。

怎么实现深拷贝的效果呢?常用的方法有两种:

  1. 转成JSON字符串,再转成对象。

    var xiaoHong = {
        name: '小红',
        age: 18
    }
    
    var zhangSan = {
        name: '张三',
        age: 20,
        girl: xiaoHong
    }
    
    var xiaoMing = JSON.parse(JSON.stringify(zhangSan));
    
    xiaoMing.girl.name = '小花';
    console.log(xiaoMing);
    console.log(zhangSan);
    
  2. 使用递归处理

    我们上面的例子写的比较简单,只有两层,因此深拷贝也只需要拷贝两层,其实我们很多时候是不知道一个对象有多少层级的,因此递归比较适合。

    var xiaoHong = {
        name: '小红',
        age: 18
    }
    
    var zhangSan = {
        name: '张三',
        age: 20,
        girl: xiaoHong
    }
    
    var xiaoMing = deepCopy(zhangSan);
    xiaoMing.girl.name = '小花';
    console.log(xiaoMing);
    console.log(zhangSan);
    

实现深拷贝:

 // 方法一:
  function deepClone(obj) {
    var result = Array.isArray(obj) ? [] : {};
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object' && obj[key]!==null) {
          result[key] = deepClone(obj[key]); 
        } else {
          result[key] = obj[key];
        }
      }
    }
    return result;
  }
  
方法二:
	function deepClone(arr){
    	return JSON.parse(JSON.stringify(arr))
	}
递归函数

所谓递归函数就是指调用自己的函数。

// 求两个整数之间的所有整数的和
function sum(start, end) {
    var total = start;

    if (start < end) {
        start++;
        // 自己调用自己了
        total = total + sum(start, end);
    } 
    return total;
}
console.log(sum(1, 5));

/**
 * total 的值的变化
 * start = 1 时
 * total = 1 + sum(2,5) 在内存中等着
 * start = 2 时
 * total = 2 + sum(3,5) 在内存中等着
 * start = 3 时
 * total = 3 + sum(4,5) 在内存中等着
 * start = 4 时
 * total = 4 + sum(5,5) 在内存中等着
 * start = 5 时,发现不符合继续调用条件,if执行完,继续执行下面的代码
 * 由于if之前发生了一次total的赋值,此时return的total的值为5;即sum(5,5)的之是5
 * 然后在内存中等着的total = 4 + sum(5,5) 的值就是 4 + 5 = 9,执行完后继续执行到return total,
 * 返回值是 9,即:sum(4,5) 返回9
 * 然后在内存中等着的total = 3 + sum(4,5) 的值就是 3 + 9 = 12,执行完后继续执行到return total,
 * 返回值是 12,即:sum(3,5) 返回12
 * 然后在内存中等着的total = 2 + sum(3,5) 的值就是 2 + 12 = 14,执行完后继续执行到return total,
 * 返回值是 14,即:sum(2,5) 返回14
 * 然后在内存中等着的total = 1 + sum(2,5) 的值就是 1 + 14 = 15
 * 此时相当于第一轮的if语句执行完,继续执行到return total,最终返回值为15
 */
<think>我们正在讨论C++中的深拷贝浅拷贝概念。根据引用内容,我们可以总结如下: 1. 浅拷贝(Shallow Copy): - 只复制对象的成员值,包括指针的值(即地址),而不复制指针所指向的内容。 - 如果对象中有指针成员,那么拷贝后两个对象的指针将指向同一块内存。 - 编译器默认提供的拷贝构造函数赋值运算符重载是浅拷贝。 - 问题:当两个对象析构时,会尝试释放同一块内存两次,导致程序崩溃(指针悬挂问题)。 2. 深拷贝(Deep Copy): - 不仅复制对象的成员值,还会为指针成员分配新的内存空间,并复制指针所指向的内容。 - 这样,两个对象的指针成员将指向各自独立的内存区域。 - 深拷贝需要自定义拷贝构造函数赋值运算符重载来实现。 实现方式: - 在自定义拷贝构造函数中,为指针成员分配新的内存,并复制对象指针指向的内容。 - 同样,在赋值运算符重载中,需要先释放当前对象指针指向的内存(如果有),然后分配新内存并复制内容。 下面是一个示例代码,展示如何实现深拷贝: ```cpp #include <iostream> #include <cstring> // 用于strcpy class MyString { private: char* m_data; // 指针成员 int m_length; public: // 构造函数 MyString(const char* str = "") { m_length = strlen(str) + 1; m_data = new char[m_length]; strcpy(m_data, str); } // 自定义析构函数:释放内存 ~MyString() { delete[] m_data; } // 自定义拷贝构造函数(深拷贝) MyString(const MyString& other) { m_length = other.m_length; m_data = new char[m_length]; // 分配新内存 strcpy(m_data, other.m_data); // 复制内容 } // 自定义赋值运算符重载(深拷贝) MyString& operator=(const MyString& other) { if (this != &other) { // 防止自赋值 // 先释放当前对象的内存 delete[] m_data; // 复制新内容 m_length = other.m_length; m_data = new char[m_length]; strcpy(m_data, other.m_data); } return *this; } // 打印字符串 void print() const { std::cout << m_data << std::endl; } }; int main() { MyString str1("Hello"); MyString str2 = str1; // 调用拷贝构造函数(深拷贝) MyString str3; str3 = str1; // 调用赋值运算符重载(深拷贝) // 此时,str1, str2, str3各自拥有独立的内存空间 return 0; } ``` 注意: - 在拷贝构造函数赋值运算符重载中,我们为每个对象都创建了独立的内存空间。 - 在赋值运算符重载中,需要检查自赋值(如`a = a;`),否则先删除自身内存再复制会导致错误。 - 析构函数中释放内存,由于每个对象都有自己的内存,所以不会出现重复释放的问题。 总结: - 当类中没有指针成员时,浅拷贝足够。 - 当类中有指针成员且指向动态分配的内存时,必须使用深拷贝以避免重复释放指针悬挂问题。 §§相关问题§§ 1. 为什么默认的拷贝构造函数是浅拷贝? 2. 在C++11中,移动构造函数移动赋值运算符如何影响深拷贝浅拷贝? 3. 如何避免深拷贝带来的性能开销?</think>### C++深拷贝浅拷贝的区别及实现 #### 1. **核心区别** - **浅拷贝**:仅复制指针值,拷贝后新旧对象**共享同一块内存**(类似文件快捷方式) ```cpp // 默认拷贝构造函数(浅拷贝) Object(const Object& src) : data_ptr(src.data_ptr) {} ``` - **深拷贝**:复制指针**并创建新内存**,新旧对象**完全独立**(类似文件复制) ```cpp // 深拷贝构造函数 Object(const Object& src) { data_ptr = new int(*src.data_ptr); // 创建新内存并复制值 } ``` #### 2. **内存示意图** ``` 浅拷贝深拷贝: [对象A] [对象A] │ │ ▼ ▼ [内存X] [内存X] ↗ ↗ [对象B] [内存Y] ← [对象B] // 独立新内存 ``` #### 3. **关键差异点** | **特性** | 浅拷贝 | 深拷贝 | |----------------|--------------------------|--------------------------| | 内存管理 | 共享内存 | 独立内存 | | 析构安全性 | 可能双重释放导致崩溃 | 安全 | | 默认行为 | 编译器自动生成 | 需手动实现 | | 指针悬挂风险 | 存在 | 不存在 | | 适用场景 | 无指针成员或静态数据 | 含动态内存分配的指针成员 | #### 4. **实现深拷贝的步骤** ① **自定义拷贝构造函数** ```cpp class Array { public: int* data; int size; // 深拷贝构造函数 Array(const Array& src) : size(src.size) { data = new int[size]; // 1. 分配新内存 std::copy(src.data, src.data + size, data); // 2. 复制内容 } }; ``` ② **重载赋值运算符** ```cpp Array& operator=(const Array& src) { if (this != &src) { // 防止自赋值 delete[] data; // 1. 释放旧内存 size = src.size; data = new int[size]; // 2. 分配新内存 std::copy(src.data, src.data + size, data); } return *this; } ``` ③ **实现析构函数** ```cpp ~Array() { delete[] data; // 释放动态内存 } ``` #### 5. **典型问题场景** - **浅拷贝陷阱**:两个对象析构时尝试释放同一内存 ```cpp Array a(10); Array b = a; // 浅拷贝 → 析构时崩溃 ``` - **深拷贝解决方案**: ```cpp Array c(10); Array d = c; // 深拷贝 → 安全析构 ``` #### 6. **最佳实践** - 含动态内存的类必须实现**深拷贝三件套**(拷贝构造、赋值运算符、析构函数) - 使用`std::vector`等智能容器可避免手动深拷贝 - C++11起可通过`= delete`禁用拷贝 ```cpp Object(const Object&) = delete; // 禁止拷贝 ``` > 关键总结:浅拷贝复制地址,深拷贝复制内容。动态资源管理必须使用深拷贝以避免内存错误[^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值