C++对象拷贝-重载赋值运算符

对象拷贝两大核心形式

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;
}

继承场景下的对象拷贝

  1. 赋值运算符重载继承处理
    子类重载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;
}
};
  1. 拷贝构造函数继承规则
    拷贝构造不会自动继承,子类自定义拷贝构造时,必须手动调用父类拷贝构造(初始化列表):
cpp 复制代码
Son(const Son& other) : Father(other)
{
    score = other.score;
}

重载赋值运算符核心注意事项

1.必须处理全部成员:普通变量、堆指针都要拷贝

2.存在继承时,显式调用 父类::operator=(other) 复制父类成员

3.先判断自赋值,避免释放自身内存后无法拷贝

4.先释放对象原有堆空间,防止内存泄漏

5.返回引用 类名&,支持连续赋值

6.拷贝构造函数不能自动继承,子类需要手动在初始化列表调用父类拷贝构造

总结

1.拷贝构造:新建对象时复制;赋值运算符:已有对象互相覆盖

2.默认拷贝 / 赋值都是浅拷贝,带指针成员必须手写深拷贝

3.深拷贝赋值四步:判自赋值→释放旧内存→分配新内存→返回*this

4.继承类重载赋值,要手动调用父类赋值;拷贝构造不自动继承,需初始化列表调用父类拷贝构造