对象拷贝两大核心形式
1.拷贝构造函数 :创建全新对象时,用已有旧对象初始化新对象
触发场景:Person p2 = p1; / Person p2(p1);
2.赋值运算符重载 :operator= 两个已经存在的对象之间互相赋值覆盖
触发场景:p2 = p1;
区别:拷贝构造是「新建对象」;赋值运算符是「覆盖已有对象」
默认赋值运算符(编译器自动生成)
不手动重载 = 时,编译器提供默认赋值重载
默认逻辑为浅拷贝:逐成员复制变量值
执行流程:把等号右侧对象全部成员复制到左侧对象
基础浅拷贝赋值示例(有指针会崩溃)
cpp
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
public:
char* name;
int age;
Person(const char* n, int a)
{
age = a;
name = new char[strlen(n)+1];
strcpy(name, n);
}
~Person()
{
delete[] name;
}
};
int main()
{
Person p1("小明", 18);
Person p2("小红", 20);
p2 = p1; // 默认赋值,浅拷贝,仅复制name指针地址
// 析构时p1、p2指向同一块堆内存,重复delete,程序崩溃
return 0;
}
浅拷贝的缺陷
1.仅复制指针变量本身(地址值),不复制指针指向的堆数据
2.赋值后左右对象指针共享同一块堆内存
3.析构阶段两个对象先后释放同一内存,触发双重释放崩溃
4.修改其中一个对象的指针内容,另一个对象的数据也会被篡改
重载赋值运算符实现深拷贝
重载operator=固定四步流程:
1.判断自赋值 if(this == &other),防止自身赋值出错
2.释放当前对象旧堆内存,避免内存泄漏
3.开辟全新堆内存,复制源对象指针数据
4.返回 *this,支持连续赋值 a=b=c
cpp
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
public:
char* name;
int age;
// 普通构造
Person(const char* n = "", int a = 0)
{
age = a;
name = new char[strlen(n)+1];
strcpy(name, n);
}
// 拷贝构造(深拷贝)
Person(const Person& other)
{
age = other.age;
name = new char[strlen(other.name)+1];
strcpy(name, other.name);
}
// 重载赋值运算符 深拷贝
Person& operator=(const Person& other)
{
// 1.防止自赋值
if(this == &other)
return *this;
// 2.释放当前对象原有堆内存
delete[] this->name;
// 3.分配新内存并拷贝数据
age = other.age;
name = new char[strlen(other.name)+1];
strcpy(name, other.name);
// 4.返回自身,支持链式赋值
return *this;
}
~Person()
{
delete[] name;
}
void show()
{
cout << name << " " << age << endl;
}
};
int main()
{
Person p1("张三", 18);
Person p2("李四", 22);
p2 = p1; // 调用重载赋值,深拷贝,内存独立
p2.show();
Person p3 = p1; // 调用拷贝构造
p3.show();
return 0;
}
继承场景下的对象拷贝
- 赋值运算符重载继承处理
子类重载operator=时,必须显式调用父类赋值运算符,否则父类成员不会被复制:
cpp
class Father
{
public:
int money;
Father& operator=(const Father& other)
{
money = other.money;
return *this;
}
};
class Son : public Father
{
public:
int score;
Son& operator=(const Son& other)
{
// 显式调用父类赋值,复制父类成员
Father::operator=(other);
// 子类自身成员拷贝
score = other.score;
return *this;
}
};
- 拷贝构造函数继承规则
拷贝构造不会自动继承,子类自定义拷贝构造时,必须手动调用父类拷贝构造(初始化列表):
cpp
Son(const Son& other) : Father(other)
{
score = other.score;
}
重载赋值运算符核心注意事项
1.必须处理全部成员:普通变量、堆指针都要拷贝
2.存在继承时,显式调用 父类::operator=(other) 复制父类成员
3.先判断自赋值,避免释放自身内存后无法拷贝
4.先释放对象原有堆空间,防止内存泄漏
5.返回引用 类名&,支持连续赋值
6.拷贝构造函数不能自动继承,子类需要手动在初始化列表调用父类拷贝构造
总结
1.拷贝构造:新建对象时复制;赋值运算符:已有对象互相覆盖
2.默认拷贝 / 赋值都是浅拷贝,带指针成员必须手写深拷贝
3.深拷贝赋值四步:判自赋值→释放旧内存→分配新内存→返回*this
4.继承类重载赋值,要手动调用父类赋值;拷贝构造不自动继承,需初始化列表调用父类拷贝构造