目录
一、拷贝构造函数
1.定义
用已有对象来初始化新对象。如果用户没有自定义拷贝构造函数,则系统默认提供一个缺省的拷贝构造函数。
强调:默认的拷贝构造函数是按位拷贝的方式初始化。
2.按位拷贝
(1)定义
是一种对象复制方式,指将一个对象的所有二进制位(bit)原样复制到另一个对象,即逐字节(byte)复制对象在内存中的原始数据。
强调:按位拷贝是编译器默认的拷贝行为,包含默认拷贝构造函数和默认拷贝赋值运算符的实现逻辑。
(2)特点
①不执行任何自定义代码,仅复制内存数据。
②对指针类型,仅复制指针值(地址),不复制指向的内存。
③当对象包含动态资源(比如堆内存)时,会导致多个对象共享同一块内存,调用析构函数后会导致重复释放问题。此时,不合适使用按位拷贝。
(3)使用场景
①对象成员都是基本类型
class Point {
private:
int x; // 基本类型,无动态资源
int y;
public:
// 编译器生成的默认拷贝构造函数会执行按位拷贝
Point(int x_, int y_) : x(x_), y(y_) {}
};
int main() {
Point p1(10, 20);
Point p2 = p1; // 按位拷贝:复制x=10和y=20,安全有效
return 0;
}
②不需要手动释放资源
③对象的指针成员指向全局内存或字符串常量
class ReadOnlyData {
private:
const char* str; // 指向字符串常量(全局资源)
public:
ReadOnlyData(const char* s) : str(s) {}
};
int main() {
ReadOnlyData d1("hello");
ReadOnlyData d2 = d1; // 按位拷贝:d2.str和d1.str指向同一常量,安全
return 0;
}
注意:字符串常量存放在数据区,无需手动释放。
二、浅拷贝
根据下面的代码找到运行时出现的问题
class MyString
{
private:
char* str;
public:
MyString(const char* p = nullptr) :str(nullptr) //构造函数
{
if (nullptr != p) //先判断是否为空
{
int len = strlen(p);//计算字符串的长度
str = new char[len + 1];//使用new申请空间——堆区申请空间 (1)sizeof计算大小 (2)malloc (3)构造 (4)返回地址
strcpy(str, p);//将p的值拷贝到str中
}
}
~MyString()//析构函数,
{
if (nullptr != str)
{
delete[] str;
str = nullptr;
}
}
void Print() const //常方法和普通方法都可以进行调用
{
if (str != nullptr)
{
cout << str << endl;
}
}
};
int main()
{
MyString s1("yhping");
MyString s2(s1);//系统自动调用默认拷贝构造函数进行初始化
}
存在的问题:(浅拷贝引起的堆区内存问题)
由于用户没有实现拷贝构造函数,系统默认调用自身的拷贝构造函数,调用后,s1和s2指向同一块内存空间,在调用析构函数时,s3先释放资源,此时,当s1调用析构函数时,会因为多次释放同一块空间,造成程序崩溃。
如图所示:
1.什么是浅拷贝
直接复制对象的所有成员变量值,对于指针类型只复制指针值(地址),不复制指针指向的内存。
2.特点
①多个对象共享同一块动态内存。
②析构时可能导致多次释放同一块内存,引发悬空指针或内存泄漏。
三、深拷贝(适用于对态资源)
对于上述代码的修改就可以使用深拷贝解决!
1.什么是深拷贝
复制对象的所有成员变量值,对于指针类型,会重新分配内存并复制其指向的内容。
2.特点
①每个对象拥有独立的动态资源副本。
②析构时不会相互影响,避免内存问题。
四、拷贝赋值运算符的深赋值和浅赋值
如果用户没有自定义赋值运算符时,则系统默认提供一个赋值运算符的函数。是一种浅赋值的操作,在语义上等价于按位赋值(逐成员赋值对象)。
如图所示:(默认是浅赋值)
1.浅赋值
(1)定义
赋值时仅复制对象的成员变量值,对于指针类型只复制指针值(地址),不复制指针指向的内存。
(2)特点
多个对象共享同一块动态内存。
赋值后,原对象和目标对象的指针指向同一内存地址。
析构时可能导致多次释放同一块内存,引发悬空指针或内存泄漏。
2.深复制
对于上述代码造成的问题,可以使用户深赋值解决。
(1)定义
赋值时复制对象的所有成员变量值,对于指针类型,会重新分配内存并复制其指向的内容。
(2)特点
每个对象拥有独立的动态资源副本。
赋值后,原对象和目标对象的指针指向不同内存地址,但内容相同。
析构时不会相互影响,避免内存问题。
五、总结
在类型设计中,使用动态内存或使用内核对象时,必须重新实现拷贝构造函数和赋值重载。(深拷贝、深赋值)。