编程语言中浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

news2024/11/26 15:05:16

编程语言中浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

编程语言中浅拷贝(Shallow Copy)和深拷贝(Deep Copy)概念及JavaScript、Python、C++、Java深拷贝和浅拷贝情况介绍。

浅拷贝和深拷贝

浅拷贝(Shallow Copy)

浅拷贝是指创建一个新的对象,并将原始对象的属性值复制到新对象的过程。但是,如果属性是引用类型(如对象、数组、类实例等),浅拷贝将复制引用而不是引用的实际对象。因此,原始对象及其浅拷贝会共享那些引用类型的属性。对其中一个对象的引用类型属性所做的修改将影响另一个对象。

【浅拷贝(Shallow Copy)是创建一个新对象,该对象是原始对象的一级复制("浅"到一层,即只"浅"到最外一层,不会递归地复制嵌套的对象结构)。具体来说:

对于基本数据类型的属性,复制其值。

对于引用类型的属性,复制引用(内存地址),而不是引用的对象本身。】

这意味着新对象和原始对象共享嵌套的引用类型数据。修改原始对象中的嵌套对象会影响新对象,反之亦然。

下面用JavaScript示例说明:

JavaScript示例说明:
let original = {
    name: "John",
    age: 30,
    address: {
        city: "New York",
        country: "USA"
    }
};

let shallowCopy = Object.assign({}, original);

original.age = 31;
original.address.city = "Los Angeles";

console.log(shallowCopy.age);  // 30 (不受影响)
console.log(shallowCopy.address.city);  // "Los Angeles" (受影响)

在这个例子中:

age 是原始数据类型,浅拷贝创建了独立的副本。

address 是引用数据类型,浅拷贝只复制了引用,所以原始对象的修改会影响到浅拷贝对象。

深拷贝(Deep Copy)

深拷贝是指创建一个新的对象,并将原始对象的属性值及其所有子对象(递归地)复制到新对象的过程。这意味着新对象拥有原始对象的一个完全独立的副本。对深拷贝对象的任何修改都不会影响原始对象,反之亦然。

【深拷贝(Deep Copy)是创建一个新对象,该对象是原始对象的完整复制,包括所有嵌套的对象结构。具体来说:

递归地复制原始对象中的所有属性。

对于基本数据类型的属性,复制其值。

对于引用类型的属性,创建新的对象或数组,并递归地复制其内容。

这样,新对象和原始对象完全独立,修改一个不会影响另一个。】

下面用JavaScript示例说明:在 JavaScript 中,我们可以使用 JSON.parse(JSON.stringify()) 方法来实现简单的深拷贝,或者使用递归函数来处理更复杂的情况。

先看使用 JSON.parse(JSON.stringify()),源码如下:

let original = {
    name: "John",
    age: 30,
    address: {
        city: "New York",
        country: "USA"
    },
    hobbies: ["reading", "swimming"]
};

// 深拷贝
let deepCopy = JSON.parse(JSON.stringify(original));

// 修改原始对象
original.age = 31;
original.address.city = "Los Angeles";
original.hobbies.push("running");

// 输出比较
console.log("Original:", original);
console.log("Deep Copy:", deepCopy);

// 验证深拷贝效果
console.log("Age (Original):", original.age);   // 31
console.log("Age (Deep Copy):", deepCopy.age);   // 30
console.log("City (Original):", original.address.city);  // Los Angeles
console.log("City (Deep Copy):", deepCopy.address.city); // New York
console.log("Hobbies (Original):", original.hobbies);   // ["reading", "swimming", "running"]
console.log("Hobbies (Deep Copy):", deepCopy.hobbies);  // ["reading", "swimming"]

再看使用递归函数(处理更复杂的情况,如循环引用),源码如下:

// 函数
function performDeepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    let copy = Array.isArray(obj) ? [] : {};

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = performDeepCopy(obj[key]);
        }
    }

    return copy;
}

let original = {
    name: "John",
    age: 30,
    address: {
        city: "New York",
        country: "USA"
    },
    hobbies: ["reading", "swimming"],
    job: {
        title: "Developer",
        company: {
            name: "Tech Corp",
            location: "Downtown"
        }
    }
};

// 深拷贝
let deepCopy = performDeepCopy(original);

// 修改原始对象
original.age = 31;
original.address.city = "Los Angeles";
original.hobbies.push("running");
original.job.company.name = "New Tech Corp";

// 输出比较
console.log("Original:", original);
console.log("Deep Copy:", deepCopy);

// 验证深拷贝效果
console.log("Age (Original):", original.age);    // 31
console.log("Age (Deep Copy):", deepCopy.age);    // 30
console.log("City (Original):", original.address.city);    // Los Angeles
console.log("City (Deep Copy):", deepCopy.address.city);   // New York
console.log("Hobbies (Original):", original.hobbies);  // ["reading", "swimming", "running"]
console.log("Hobbies (Deep Copy):", deepCopy.hobbies);   // ["reading", "swimming"]
console.log("Company (Original):", original.job.company.name);  // New Tech Corp
console.log("Company (Deep Copy):", deepCopy.job.company.name);  // Tech Corp

这两个例子都展示了深拷贝的效果:

所有嵌套层级的数据都被完全复制。

修改原始对象的任何部分(包括嵌套对象和数组)不会影响深拷贝对象。

注意:

JSON.parse(JSON.stringify()) 方法简单易用,但有局限性:不能处理函数、undefined、Symbol 等。

递归方法更灵活,可以处理更复杂的对象结构,但需要注意处理循环引用的情况。

浅拷贝(Shallow Copy)和深拷贝(Deep Copy)这两个概念在不同的编程语言中应用时保持一致,只是实现细节和方法有所差异。

JavaScriptPythonC++Java深拷贝和浅拷贝情况介绍

JavaScript

JavaScript 中的深拷贝和浅拷贝是处理对象复制时的两种不同方法。这两种方法在复制嵌套对象时表现出显著差异。

浅拷贝 (Shallow Copy)

浅拷贝创建一个新对象,其属性是原始对象属性的副本。如果属性是原始值,则复制其值;如果属性是对象引用,则复制引用而不是引用的对象。

特点:

只复制对象的第一层属性。

嵌套对象仍然共享相同的引用。

方法:

a) Object.assign()

// 浅拷贝示例
const obj1 = { a: 1, b: { c: 2 } };

// 使用 Object.assign 进行浅拷贝
const obj2 = Object.assign({}, obj1);

// 修改 obj1 中的嵌套对象
obj1.b.c = 42;

console.log(obj1); // 输出: { a: 1, b: { c: 42 } }
console.log(obj2); // 输出: { a: 1, b: { c: 42 } } (受影响)

在这个例子中,obj2 是 obj1 的浅拷贝,修改嵌套对象的属性会影响到 obj2。

b) 展开运算符

扩展运算符 ... 也称为剩余运算符

const myObj = { a: 1, b: 2, c: 3, d: 4 };
const { a, b, ...rest } = myObj;

console.log(a, b); // 1 2
console.log(rest); // { c: 3, d: 4 }

在这个例子中,扩展运算符可以轻松地提取对象中指定的属性,并将其余的属性作为一个新的对象返回。

c) Array.slice() (用于数组)

let originalArray = [1, [2, 3]];
let shallowCopyArray = originalArray.slice();

console.log(shallowCopyArray); // [1, [2, 3]]

slice() 是JavaScript中数组的一个方法,它可以用来创建一个数组的浅拷贝,并选择性地提取出该数组中的一段元素,而不会改变原始数组。

深拷贝 (Deep Copy)

深拷贝创建一个新对象,并递归地复制原始对象的所有嵌套对象,创建全新的副本。

特点:

创建对象及其所有嵌套对象的全新副本。

修改副本不会影响原始对象的任何部分。

方法:

JSON.parse() 和 JSON.stringify():

// 深拷贝示例
const obj1 = { a: 1, b: { c: 2 } };

// 使用 JSON 方法进行深拷贝
const obj2 = JSON.parse(JSON.stringify(obj1));

// 修改 obj1 中的嵌套对象
obj1.b.c = 42;

console.log(obj1); // 输出: { a: 1, b: { c: 42 } }
console.log(obj2); // 输出: { a: 1, b: { c: 2 } } (未受影响)

在这个例子中,obj2 是 obj1 的深拷贝,修改嵌套对象的属性不会影响到 obj2。

JSON.parse(JSON.stringify()) 方法有局限性:

    不能复制函数、undefined、Symbol

    不能处理循环引用

可能丢失原型链

b) 递归函数

function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    if (Array.isArray(obj)) {
        return obj.map(deepClone);
    }
    const clonedObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clonedObj[key] = deepClone(obj[key]);
        }
    }
    return clonedObj;
}

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = deepClone(obj1);

obj1.b.c = 42;

console.log(obj1); // 输出: { a: 1, b: { c: 42 } }
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

c) 使用第三方库,在此就不多说了。

JavaScript 中的深拷贝和浅拷贝更多情况,详见

https://developer.mozilla.org/zh-CN/docs/Glossary/Shallow_copy

https://developer.mozilla.org/zh-CN/docs/Glossary/Deep_copy

Python

在 Python 中,可以使用 copy 模块的 copy 函数进行浅拷贝, 使用deepcopy 函数进行深拷贝。

copy --- 浅复制和深复制操作 https://docs.python.org/zh-cn/3/library/copy.html

浅拷贝:

使用 copy.copy() 或对象的 copy() 方法

创建新对象,但内部引用仍指向原始对象的内存地址

深拷贝:

使用 copy.deepcopy()

递归地复制对象及其所有嵌套对象

示例:

import copy

original = [1, [2, 3], 4]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[1][0] = 'X'
print(shallow)  # [1, ['X', 3], 4]
print(deep)     # [1, [2, 3], 4]

C++

在 C++ 中,通过复制构造函数和赋值运算符可以实现对象的深拷贝和浅拷贝。

浅拷贝:

默认的拷贝构造函数和赋值运算符通常执行浅拷贝

复制对象的成员变量,但不复制指针指向的数据

深拷贝:

需要自定义拷贝构造函数和赋值运算符

需要显式分配新内存并复制所有数据

示例:

#include <iostream>
#include <cstring>

class MyClass {
private:
    int* data;
public:
    // 构造函数
    MyClass(int value) {
        data = new int;
        *data = value;
        std::cout << "Constructor called, value: " << *data << std::endl;
    }

    // 复制构造函数(用于深拷贝)
    MyClass(const MyClass& other) {
        data = new int;
        *data = *(other.data);
        std::cout << "Copy constructor called, value: " << *data << std::endl;
    }

    // 赋值运算符(用于深拷贝)
    MyClass& operator=(const MyClass& other) {
        if (this == &other) {
            return *this; // 防止自我赋值
        }

        delete data; // 删除旧数据

        data = new int;
        *data = *(other.data);
        std::cout << "Assignment operator called, value: " << *data << std::endl;
        return *this;
    }

    // 获取数据
    int getValue() const {
        return *data;
    }

    // 析构函数
    ~MyClass() {
        delete data;
        std::cout << "Destructor called" << std::endl;
    }
};

int main() {
    MyClass obj1(42); // 使用构造函数创建对象
    MyClass obj2 = obj1; // 使用复制构造函数进行深拷贝

    MyClass obj3(0);
    obj3 = obj1; // 使用赋值运算符进行深拷贝

    std::cout << "obj1 value: " << obj1.getValue() << std::endl;
    std::cout << "obj2 value: " << obj2.getValue() << std::endl;
    std::cout << "obj3 value: " << obj3.getValue() << std::endl;

    return 0;
}

首先,定义一个简单的类 MyClass,在这个类中,我们定义了一个指向 int 类型的指针 data,并实现了以下几个函数:

构造函数,用于初始化对象。

复制构造函数,用于深拷贝,在创建新对象时复制另一个对象的数据。

赋值运算符,用于深拷贝,在将一个对象赋值给另一个对象时复制数据。

析构函数,用于释放动态分配的内存。

接下来,我们可以编写一个简单的主函数来测试深拷贝和浅拷贝的行为。创建了三个 MyClass 对象,并进行了深拷贝。输出将显示构造函数、复制构造函数、赋值运算符和析构函数的调用情况,验证深拷贝是否正确进行。

在深拷贝过程中,每个对象都有自己独立的内存空间,即使修改其中一个对象的数据,也不会影响其他对象。而浅拷贝只会复制指针的值,不会复制指针所指向的内存,这样多个对象会共享同一块内存,可能导致意外的行为。

输出:

Constructor called, value: 42
Copy constructor called, value: 42
Constructor called, value: 0
Assignment operator called, value: 42
obj1 value: 42
obj2 value: 42
obj3 value: 42
Destructor called
Destructor called
Destructor called

Java

在 Java 中,可以通过实现 Cloneable 接口和重写 clone 方法来实现对象的深拷贝和浅拷贝。

浅拷贝:

默认的 Object.clone() 方法执行浅拷贝

复制对象的引用,而不是对象本身

深拷贝:

需要实现 Cloneable 接口并重写 clone() 方法

对于复杂对象,可能需要递归克隆所有成员

示例:

包括两个文件:

第一个文件MyClass.java:

class MyClass implements Cloneable {
    private int[] data;

    // 构造函数
    public MyClass(int size) {
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = i;
        }
    }

    // 浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 深拷贝
    protected MyClass deepClone() throws CloneNotSupportedException {
        MyClass cloned = (MyClass) super.clone();
        cloned.data = data.clone();
        return cloned;
    }

    // 获取数据
    public int[] getData() {
        return data;
    }

    // 设置数据
    public void setData(int index, int value) {
        data[index] = value;
    }
}

第二个文件Main.java:

public class Main {
    public static void main(String[] args) {
        try {
            MyClass obj1 = new MyClass(5); // 使用构造函数创建对象
            MyClass obj2 = (MyClass) obj1.clone(); // 使用浅拷贝
            MyClass obj3 = obj1.deepClone(); // 使用深拷贝

            // 修改 obj1 的数据
            obj1.setData(0, 42);

            System.out.println("obj1 data: " + java.util.Arrays.toString(obj1.getData()));
            System.out.println("obj2 data: " + java.util.Arrays.toString(obj2.getData()));
            System.out.println("obj3 data: " + java.util.Arrays.toString(obj3.getData()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

首先,定义一个简单的类 MyClass,在这个类中,我们实现了以下几个函数:

构造函数,用于初始化对象。

clone 方法,用于浅拷贝,通过调用 super.clone() 实现。

deepClone 方法,用于深拷贝,通过调用 super.clone() 并对成员变量进行克隆实现。

接下来,我们可以编写一个简单的主函数来测试深拷贝和浅拷贝的行为,创建了三个 MyClass 对象,并分别进行了浅拷贝和深拷贝。输出将显示各个对象的数据,验证浅拷贝和深拷贝是否正确进行。

浅拷贝的 obj2 和 obj1 共享同一个数组,因此修改 obj1 的数据会影响 obj2 的数据。而深拷贝的 obj3 有自己独立的数组,因此修改 obj1 的数据不会影响 obj3 的数据。

输出:

obj1 data: [42, 1, 2, 3, 4]
obj2 data: [42, 1, 2, 3, 4]
obj3 data: [0, 1, 2, 3, 4]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1894213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】三大Redis内存分析工具介绍(Redisinsight、RDR、RMA)

目录 一、RedisInsight工具介绍 1、工具概述 2、关键特性 3、版本与服务 4、实际应用 二、Redis Data Reveal工具介绍 1、简介 2、主要特性 3、使用方法 4、注意事项 5、总结 三、Redis Memory Analyzer (RMA)工具介绍 1、工具概述 2、技术特点 3、应用场景 4、…

day02-统计数据

numpy统计学 1.求平均值[数组名.mean()/np.mean(数组名)] m1 np.arange(20).reshape((4,5))m1.mean() #9.5若想要求某一维的平均值&#xff0c;设置axis参数&#xff0c;多维数组元素指定&#xff1a; axis 0&#xff0c;将从上往下计算。axis 1&#xff0c;将从左往右计算…

使用getline()从文件中读取一行字符串

我们知道&#xff0c;getline() 方法定义在 istream 类中&#xff0c;而 fstream 和 ifstream 类继承自 istream 类&#xff0c;因此 fstream 和 ifstream 的类对象可以调用 getline() 成员方法。 当文件流对象调用 getline() 方法时&#xff0c;该方法的功能就变成了从指定文件…

最新抖音极速版双红包雨掘金助手

项目介绍&#xff1a; 抖音极速版目前小说里有双红包雨&#xff0c;单广告2000&#xff0c;金币1万比1&#xff0c;脚本自动看广告 设备需求&#xff1a; 安卓手机&#xff08;最高支持安卓13.0版本&#xff09; 购买后包含月卡脚本详细使用教程 百度网盘 请输入提取码百度…

SpringMVC 的工作流程和详细解释

Spring MVC&#xff08;Model-View-Controller&#xff09;框架是基于经典的 MVC 设计模式构建的&#xff0c;用于开发 Web 应用程序。下面是 Spring Boot MVC 的工作流程和详细解释&#xff1a; 1.客户端发起请求 1.客户端&#xff08;通常是浏览器&#xff09;发起 HTTP 请求…

中序遍历的两种实现——二叉树专题复习

递归实现&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right)…

K8s 集群(kubeadm) CA 证书过期解决方案

Author&#xff1a;Arsen Date&#xff1a;2024/07/04 目录 一、现象描述二、解决方案三、集群验证 一、现象描述 之前有篇文章《K8s Token 过期解决方案&#xff08;Kubeadm&#xff09;》提到了默认生成的 Token 有效期只有 24 小时&#xff0c;过期后 Token 将不可用&#…

Robust Test-Time Adaptation in Dynamic Scenarios--论文阅读

论文笔记 资料 1.代码地址 https://github.com/BIT-DA/RoTTA 2.论文地址 https://arxiv.org/abs/2303.13899 3.数据集地址 coming soon 1论文摘要的翻译 测试时间自适应(TTA)旨在使预先7训练的模型适用于仅具有未标记测试数据流的测试分布。大多数以前的TTA方法已经在…

实现统计n个数以下质数的个数

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>int main() {int n 0;scanf("%d", &n);int sum 0;for (int i 1; i < n; i){for (int j 2; j < i; j) {if (i % j 0){sum;break;}}}printf("%d", n - sum-1);return 0; } n为输…

数字媒体技术基础之:DNG 文件

DNG&#xff08;Digital Negative&#xff09;文件是一种用于存储原始图像数据的文件格式&#xff0c;由 Adobe Systems 于2004年开发并推广。DNG 是一种开放的、非专利的原始图像格式&#xff0c;旨在为不同相机制造商提供一个统一的存储格式。DNG 文件保存了原始的、未处理的…

【C语言】刷题笔记 Day2

【笔记】 【1】局部变量不初始化&#xff0c;默认放的随机值。 1 int n0; 2 scanf("%d",&n); //13.141 【2】这里虽然输入的是一个浮点数&#xff0c;但是只取整数部分。 【3】3.156e7 表示的是3.156*10的7次方。 【4】多组输入&#xff0c;保存和不保存…

Studying-代码随想录训练营day29| 134. 加油站、135. 分发糖果、860.柠檬水找零、406.根据身高重建队列

第29天&#xff0c;贪心part03&#xff0c;快过半了(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 134.加油站 135. 分发糖果 860.柠檬水找零 406.根据身高重建队列 134.加油站 文档讲解&#xff1a;代码随想录加油站 视频讲解&#xff1a;手撕加油站…

2.2 ROS2话题通信

场景 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即&#xff1a;一个节点发布消息&#xff0c;另一个节点订阅该消息。话题通信的应用场景也极其广泛&#xff0c;比如如下场景&#xff1a; 机器人在执行导航功能&#…

5个文章生成器免费版,自动写作文章更轻松

在这个信息如洪流般涌动的时代&#xff0c;写作所具有的重要性不言而喻。不管是学生需要完成的作业&#xff0c;还是职场人士得提交的报告&#xff0c;亦或是自媒体创作者必须输出的内容&#xff0c;都迫切要求我们具备一定的写作技能。然而&#xff0c;写作对很多人来说&#…

基于 STM32 的智能睡眠呼吸监测系统设计

本设计的硬件构成&#xff1a; STM32F103C8T6单片机最小系统板&#xff08;包含3.3V稳压电路时钟晶振电路复位电路&#xff08;上电自复位&#xff0c;手动复位&#xff09;&#xff09;&#xff0c;心率传感器、气压传感器、液晶显示、按键、蜂鸣器、LED灯、蓝牙模块组合而成…

Nettyの网络聊天室扩展序列化算法

1、网络聊天室综合案例 客户端初始代码&#xff1a; Slf4j public class ChatClient {public static void main(String[] args) {NioEventLoopGroup group new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER new LoggingHandler(LogLevel.DEBUG);MessageCodecSharabl…

2024-07-04 base SAS programming学习笔记8(HTML)

当使用ODS来进行结果或数据集输出的时候&#xff0c;可以同时设置多个ODS 命令&#xff0c;同时输出到多个不同的文件。使用_ALL_ 表示关闭所有的ODS输出窗口&#xff0c;比如&#xff1a; ods html file(body)"html-file-pathname"; ods html file"pdf-file-pa…

【Ubuntu24.04无显示器远控】【Todesk远程桌面黑屏】【Linux虚拟显示器】解决方案

1️⃣版本 Ubuntu 24.04Todesk 4.7.2.0xserver-xorg-video-dummy 1:0.4.0-1build1 2️⃣安装配置虚拟显示器 sudo apt install xserver-xorg-video-dummy编辑/etc/gdm3/custom.conf&#xff0c;关闭Ubuntu24.04Wayland切换为X11 WaylandEnablefalse /usr/share/X11/xorg.con…

Python 插入、替换、提取、或删除Excel中的图片

Excel是主要用于处理表格和数据的工具&#xff0c;我们也能在其中插入、编辑或管理图片&#xff0c;为工作表增添视觉效果&#xff0c;提升报告的吸引力。本文将详细介绍如何使用Python操作Excel中的图片&#xff0c;包含以下4个基础示例&#xff1a; 文章目录 Python 在Excel…

三菱PLC标签使用(I/O的映射)与内容

今天&#xff0c;小编继续开始三菱PLC的学习&#xff0c;今天的内容是标签及其标签的内容说明&#xff0c;如果对你有帮助&#xff0c;欢迎评论收藏。 标签的种类&#xff0c;等级&#xff0c;定义 种类 三菱3U的PLC的种类分别为二种&#xff1a;全局标签与局部标签 全局标签…