编程语言中浅拷贝(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)这两个概念在不同的编程语言中应用时保持一致,只是实现细节和方法有所差异。
JavaScript、Python、C++、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]