深拷贝和浅拷贝

目录

一.什么是深拷贝浅拷贝

1.浅拷贝

2. 深拷贝

二.浅拷贝的实现方式

1.拓展运算符

2.Object.assign()

 3.Array.prototype.concat

4.Array.prototype.slice()

5.Array.from()

 6.手写实现浅拷贝

7.函数库lodash提供的clone()方法 

三.深拷贝的实现方式

1.JSON.parse(JSON.stringify())

2. jQuery.extend()方法

3.手写循环递归

4.structuredClone()

5.函数库loadsh提供的cloneDeep()方法

四.浅拷贝和 = 复制的区别


一.什么是深拷贝浅拷贝

数据类型分为简单数据类型和复杂数据类型,两者存储数据的方式不同:

  • 基本数据类型:是直接存储在栈里面的。
  • 引用数据类型:是在栈里面存放一个地址,真实的数据存储在堆内存里面,地址指向这个真实数据。

深拷贝和浅拷贝是指复制引用数据类型(下面统指对象)时的两种不同的方法。

1.浅拷贝

创建一个新的对象,新对象的属性和原始对象相同,如果属性是基本数据类型,拷贝的就是基本数据类型存在栈里的真实数据,两个对象的该属性不相互影响。如果数据是引用数据类型,拷贝的就是地址,因此如果该属性的值发生改变,那么另外一个对象的该属性也会随之发生改变。

        可以理解为只拷贝第一层的真实数据,再深入就是引用地址。

换做数组就是,如果元素是基本数据类型,拷贝的就是基本数据类型存在栈里的真实数据,两个数组的该元素不互相影响。如果元素是引用数据类型,拷贝的就是地址,因此如果该元素的值发生改变,那么另外一个数组的该元素也会随之发生改变。

    let obj1 = {
      name: "于家宝",
      body: {
        age: 18,
      },
    };
    // 拓展运算符是浅拷贝的一种方式
    let obj2 = { ...obj1 };

    obj1.name = "yujiabao"; // 该属性name为基本数据,拷贝的是真实数据,因此两个对象的该属性不会相互影响
    obj1.body.age = 19; // 该属性body为引用数据,拷贝的是地址,指向同一个真实数据,一个对象的该属性改变,另外一个对象的该属性也会随之改变
    console.log(obj2);

  还有一种情况不要混淆!!!:

    let obj1 = {
      name: "于家宝",
      body: {
        age: 18,
      },
    };
    // 拓展运算符是浅拷贝的一种方式
    let obj2 = { ...obj1 };

    obj1.name = "yujiabao"; // 该属性name为基本数据,复制的是真实数据,因此两个对象的该属性不会相互影响
    obj1.body = {
      age: 19,
    }; // 该属性body为引用数据,但是这样是直接改变了该属性的地址,因此两个对象的地址不一样,不指向同一个数据,所以两个对象的该属性不会相互影响
    console.log("obj1", obj1);
    console.log("obj2", obj2);

2. 深拷贝

创建一个新对象,新对象和原始对象的属性完全相同但是指向不同的地址,修改其中一个不会影响另外一个。

        可以理解为递归的去拷贝了每一层的真实数据。

    let obj1 = {
      name: "yujiabao",
      body: {
        age: 18,
      },
    };
    // JSON.stringify是深拷贝的一种方式
    let obj2 = JSON.parse(JSON.stringify(obj1));
    obj1.name = "于家宝";
    obj1.body.age = 19; // 无论什么属性,新旧对象都不会互相产生影响
    console.log("obj1", obj1);
    console.log("obj2", obj2);

 

二.浅拷贝的实现方式

1.拓展运算符

    let obj1 = {
      name: "yujiabao",
      body: {
        age: 18,
      },
    };
    let obj2 = { ...obj1 };
    obj1.name = "于家宝";
    obj1.body.age = 19;
    console.log(obj2.name); // yujiabao  不影响
    console.log(obj2.body.age); // 19  影响

2.Object.assign()

    let obj1 = {
      name: "yujiabao",
      body: {
        age: 18,
      },
    };
    let obj2 = Object.assign({}, obj1);
    obj1.name = "于家宝";
    obj1.body.age = 19;
    console.log(obj2.name); // yujiabao  不影响
    console.log(obj2.body.age); // 19  影响

 3.Array.prototype.concat

    let arr1 = [1, 2, [3, 4], 5];
    let arr2 = arr1.concat();
    arr1[0] = 6;
    arr1[2][0] = 7;
    console.log(arr2[0]); // 1 不影响
    console.log(arr2[2][0]); // 7 影响

4.Array.prototype.slice()

    let arr1 = [1, 2, [3, 4], 5];
    let arr2 = arr1.slice();
    arr1[0] = 6;
    arr1[2][0] = 7;
    console.log(arr2[0]); // 1 不影响
    console.log(arr2[2][0]); // 7 影响

5.Array.from()

    let arr1 = [1, 2, [3, 4], 5];
    let arr2 = Array.from(arr1);
    arr1[0] = 6;
    arr1[2][0] = 7;
    console.log(arr2[0]); // 1 不影响
    console.log(arr2[2][0]); // 7 影响

 6.手写实现浅拷贝

function shallowCopy (params) {
    // 基本类型直接返回
    if (!params || typeof params !== "object") return params;
​
    // 根据 params 的类型判断是新建一个数组还是对象
    let newObject = Array.isArray(params) ? [] : {};
​
    // 遍历 params 并判断是 params 的属性才拷贝
    for (let key in params) {
        if (params.hasOwnProperty(key)) {
            newObject[key] = params[key];
        }
    }
​
    return newObject;
}
​
let params = { a: 1, b: { c: 1 } }
​
let newObj = shallowCopy(params)
​params.a = 222
params.b.c = 666
console.log(params); // { a: 222, b: { c: 666 } }
console.log(newObj); // { a: 1, b: { c: 666 } }

7.函数库lodash提供的clone()方法 

三.深拷贝的实现方式

1.JSON.parse(JSON.stringify())

有个缺点是不接受函数,undefined,symbol。

    let obj1 = {
      name: "yujiabao",
      body: {
        age: 18,
      },
    };
    let obj2 = JSON.parse(JSON.stringify(obj1));
    obj1.name = "于家宝";
    obj1.body.age = 19;
    console.log(obj2.name); // yujiabao  不影响
    console.log(obj2.body.age); // 18  不影响

2. jQuery.extend()方法

$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);

3.手写循环递归

简单的:无法实现对函数 、RegExp等特殊对象的克隆,会抛弃对象的constructor,所有的构造函数会指向Object,对象有循环引用,会报错;

function deepCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;
  // 根据 object 的类型判断是新建一个数组还是对象
  let newObj = Array.isArray(object) ? [] : {};
  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObj[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }
  return newObj;
}

进阶版:

function deepClone(target) {
  const map = new WeekMap();

  function isObject(target) {
    return (typeof target === "object" && target) || (typeof target === "function");
  }

  function clone(data) {

    if (!isObject(target)) {
      return;
    }

    if ([Date, RegExp].includes(data.constructor)) {
      return new data.constructor(data);
    }

    if (typeof data === "function") {
      return new Function("return " + data.toString())();
    }

    const exist = map.get(data);
    if (exist) {
      return exist;
    }

    if (data instanceof Map) {
      const result = new Map();
      map.set(data, result);
      data.forEach((val, key) => {
        if (isObject(val)) {
          result.set(key, clone(val));
        } else {
          result.set(key, val);
        }
      });
      return result;
    }

    if (data instanceof Set) {
      const result = new Set();
      map.set(data, result);
      data.forEach(val => {
        if (isObject(val)) {
          result.add(clone(val));
        } else {
          result.add(val);
        }
      });
      return result;
    }

    const keys = Reflect.ownKeys(data);
    const allDesc = Object.getOwnPropertyDescriptors(data);
    const result = Object.create(Object.getPrototypeOf(data), allDesc);
    map.set(data, result);
    keys.forEach(key => {
      const val = data[key];
      if (isObject(val)) {
        result[key] = clone(val);
      } else {
        result[key] = val;
      }
    });
    return result;
  }

  return clone(target);
}

4.structuredClone()

    let obj = { a: 1, b: { c: 2 }, d: [3, 4], e: new Date() };
    let deepCopy = structuredClone(obj);

    deepCopy.b.c = 100;
    deepCopy.d[0] = 300;
    console.log(obj.b.c); // 输出: 2
    console.log(obj.d[0]); // 输出: 3
    console.log(deepCopy.b.c); // 输出: 100
    console.log(deepCopy.d[0]); // 输出: 300
    console.log(deepCopy.e); // Thu Jan 02 2025 17:36:26 GMT+0800 (中国标准时间)

5.函数库loadsh提供的cloneDeep()方法

四.浅拷贝和 = 复制的区别

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

举个例子:

    let obj1 = {
      name: "于家宝",
      body: {
        age: 18,
      },
    };
    let obj2 = Object.assign({}, obj1);
    obj1.name = "yujiabao";
    obj1.body.age = 19;
    console.log(obj2);

浅拷贝的话 基础类型 name 没有改变,引用类型body里面的 age改变。 

    let obj1 = {
      name: "于家宝",
      body: {
        age: 18,
      },
    };
    let obj2 = obj1;
    obj1.name = "yujiabao";
    obj1.body.age = 19;
    console.log(obj2);

 赋值的话 基础类型和引用类型都改变了。 

<think>嗯,用户想了解C++中的深拷贝浅拷贝的区别以及实现方法。首先,我需要回忆一下这两个概念的基本定义。浅拷贝应该是指简单的复制指针,导致两个对象指向同一块内存,而深拷贝则是复制指针所指向的数据本身,这样两个对象就独立了。 根据用户提供的引用内容,引用[1]里提到浅拷贝只拷贝指针,深拷贝会拷贝内容,默认的拷贝构造函数是浅拷贝,需要自己实现深拷贝。引用[2][3]给出了具体的代码示例,展示了如何通过自定义拷贝构造函数来实现深拷贝,比如在Person类中为Name或m_height重新分配内存并复制数据。引用[4]中的Array类也演示了类似的深拷贝实现,即在拷贝构造函数中新建内存空间并复制数据。 接下来,我需要将这些信息组织成一个清晰的回答。首先解释两者的区别,用比喻的话,浅拷贝像快捷方式,深拷贝像复制文件,这样用户容易理解。然后,说明默认拷贝构造函数的问题,比如会导致重复释放内存,出现错误。接着,用用户提供的代码例子来说明如何手动实现深拷贝,比如在拷贝构造函数中分配新内存,并复制数据。可能还需要提到析构函数的重要性,确保深拷贝的对象能正确释放内存。 用户可能还关心什么时候需要深拷贝,应该是在类中有指针成员指向动态分配的内存时。这时候必须自己实现深拷贝,否则会出现问题。同时,需要注意在实现深拷贝时,除了拷贝构造函数,可能还需要重载赋值运算符,但用户的问题可能没有提到,所以可能暂时不需要展开,但如果有相关问题可以提到。 最后,生成的相关问题需要围绕实现深拷贝的方法、常见错误场景以及如何避免等问题。这样用户如果有进一步的问题,可以继续查阅相关内容。确保回答中正确引用提供的资料,比如引用[1]、[3]中的例子来说明浅拷贝的问题深拷贝的实现步骤。</think>### C++深拷贝浅拷贝的区别及实现方式 #### 一、核心概念 1. **浅拷贝** - 仅复制对象的指针成员变量,拷贝后新旧对象的指针**指向同一块内存地址**,例如: ```cpp //示例代码片段 m_height = p.m_height; //浅拷贝直接复制指针[^3] ``` - 类似文件快捷方式,多个指针共享同一资源,可能导致**重复释放内存**的崩溃问题[^1] 2. **深拷贝** - 为指针成员**创建独立的内存空间**,完全复制原对象的数据内容: ```cpp m_height = new int(*p.m_height); //新建堆空间并复制数据[^3] ``` - 类似完整文件复制,新旧对象完全独立,避免资源冲突[^1] #### 二、实现方式对比 | 特征 | 浅拷贝 | 深拷贝 | |--------------|---------------------------|---------------------------| | 内存分配 | 共享原对象内存 | 新建独立内存空间 | | 实现复杂度 | 编译器默认提供 | 需手动实现拷贝构造函数 | | 资源管理风险 | 易出现重复释放 | 内存管理安全 | | 典型应用场景 | 无指针成员的简单对象 | 包含指针/动态资源的对象 | #### 三、关键实现步骤(以类`person`为例) 1. **定义深拷贝构造函数** ```cpp person(const person& p) { m_age = p.m_age; m_height = new int(*p.m_height); //关键:新建堆内存并复制数据 } ``` 2. **配套析构函数** ```cpp ~person() { delete m_height; //确保正确释放深拷贝分配的内存 m_height = nullptr; } ``` 3. **错误示例(浅拷贝陷阱)** 若直接使用默认拷贝构造函数: ```cpp person p2(p1); //此时p2.m_height与p1.m_height指向同一地址 ``` 析构时会导致**重复释放同一内存地址**,引发程序崩溃[^1] #### 四、应用场景分析 - **必须使用深拷贝**:类包含指针成员且指向动态分配内存时(如字符串、数组等) - **可使用浅拷贝**:仅包含基本数据类型或没有资源所有权的指针时 - **典型案例**:自定义容器类(如动态数组`Array`类[^4])、包含文件句柄的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值