目录
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
在以上情况下,编译器需要根据已有的对象创建一个新的对象,这时就会调用拷贝构造函数。拷贝构造函数的作用就是定义如何使用已存在对象的数据来初始化新创建的对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
浅拷贝(Shallow Copy)
浅拷贝只复制对象中成员变量的值,而不会创建新的资源副本。如果对象中有指向堆内存的指针,浅拷贝仅复制指针的值,而不复制指针指向的实际数据。这意味着两个对象将共享同一块内存,这可能会导致问题: 当一个对象的析构函数调用 delete 释放了内存,另一个对象也会受到影响,因为它们共享相同的指针。 当一个对象改变了堆内存中的数据,另一个对象也会受到影响,因为它们指向相同的内存位置。
深拷贝(Deep Copy)
深拷贝创建了一个新的资源副本,而不仅仅是复制指针的值。这意味着每个对象都有自己的独立内存副本,彼此之间不会相互影响。 对于指向堆内存的指针,深拷贝会为每个对象分配新的内存,并将原始数据复制到新的内存中。
可以简单理解成,浅拷贝是完全地复制数值,深拷贝会另开辟空间
就像是考试作弊(不提倡),浅拷贝是很死板地复制别人的试卷,甚至连班级姓名也一样,但是深拷贝就聪明很多,会写自己的班级姓名。
#include <iostream>
#include <cstring>
class MyString {
private:
char* buffer;
public:
// 构造函数
MyString(const char* initialInput) {
if (initialInput != nullptr) {
buffer = new char[strlen(initialInput) + 1];
strcpy(buffer, initialInput);
} else {
buffer = nullptr;
}
}
// 拷贝构造函数(浅拷贝)
MyString(const MyString& other) {
buffer = other.buffer; // 浅拷贝,只复制指针的值
}
// 拷贝构造函数(深拷贝)
//MyString(const MyString& other) {
// if (other.buffer != nullptr) {
// buffer = new char[strlen(other.buffer) + 1];
// strcpy(buffer, other.buffer);
// } else {
// buffer = nullptr;
// }
//}
// 析构函数
~MyString() {
delete[] buffer;
}
// 打印字符串
void Print() {
if (buffer != nullptr) {
std::cout << buffer;
} else {
std::cout << "(null)";
}
std::cout << std::endl;
}
};
int main() {
MyString str1("Hello");
MyString str2 = str1; // 调用拷贝构造函数
// 修改str1
str1.Print(); // 输出 "Hello"
str2.Print(); // 输出 "Hello"
delete[] str1; // 删除str1的buffer
str1.Print(); // 输出 "(null)"
str2.Print(); // 输出 "(null)"
return 0;
}
无限递归问题的原因
无限递归问题通常出现在拷贝构造函数中,参数没有使用引用而是传值,从而导致拷贝构造函数反复调用自身。
class MyClass {
public:
MyClass(MyClass other) { // 错误:拷贝构造函数参数未使用引用
// 复制 other 的数据成员
_data = other._data;
}
};
在上面的代码中,拷贝构造函数的参数 other
不是引用类型,而是按值传递。按值传递意味着在调用拷贝构造函数时,会创建 other
的副本,这又会调用拷贝构造函数,从而导致无限递归,最终导致程序栈溢出并崩溃。
解决无限递归问题的方法是确保拷贝构造函数的参数使用引用传递。
class MyClass {
public:
MyClass(const MyClass& other) { // 正确:拷贝构造函数参数使用常量引用
// 复制 other 的数据成员
_data = other._data;
}
};
我们还可以结合运算符重载
拷贝赋值运算符
拷贝赋值运算符用于将一个对象的内容复制到另一个已经存在的对象中。它通常被重载为类成员函数,并采用形式如 ClassName& operator=(const ClassName& other) 的声明,其中 ClassName 是类的名称,other 是要复制的对象的引用。拷贝赋值运算符常用于实现对象的赋值操作,例如 obj1 = obj2。
#include <iostream>
class MyClass {
private:
int value;
public:
// 构造函数
MyClass(int val) : value(val) {}
// 拷贝赋值运算符
MyClass& operator=(const MyClass& other) {
std::cout << "Copy assignment operator called" << std::endl;
if (this != &other) { // 避免自我赋值
value = other.value; // 复制另一个对象的值
}
return *this;
}
// 打印值
void printValue() {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
MyClass obj1(10);
MyClass obj2(20);
obj2 = obj1; // 调用拷贝赋值运算符
obj1.printValue(); // 输出 "Value: 10"
obj2.printValue(); // 输出 "Value: 10"
return 0;
}
注意,拷贝函数需要返回对象,这是时又涉及到上边的问题啦,我们要时刻注意返回值也要返回对象的引用,不可以直接传值返回,不能调入无限递归的陷阱。
多看两遍,加深理解,动起来,做一个不焦虑不内耗的程序员!加油哇老铁