C++面向对象-15-赋值运算符的重载

这一篇来学习赋值运算符重载,先来了解什么是赋值运算符,也就是单个等号(=)就是赋值运算符。什么时候,我们需要对赋值运算符进行重载呢?这里面离不开值拷贝的知识。

 

1.C++编译器默认功能

C++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

 

2.问题背景

这里先写一段代码,然后分析为什么会报错

#include <iostream>
using namespace std;

class Person
{
public:
    //  把年龄这个属性写成指针
    int *m_Age;

    // 年龄开辟到堆区
    Person(int age)
    {
        m_Age = new int(age);  // new 来的对象都是一个内存地址
    }

    // 析构函数,程序员自己控制内存
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
    }
};

void test01()
{
    Person p1(18);
    Person p2(20);

    //赋值操作
    p2 = p1;

    cout << "p1的age为: " << *p1.m_Age << endl;
    cout << "p2的age为: " << *p2.m_Age << endl;

}

int main()
{
    test01();
    system("pause");
    return 0;
}

上面代码解释:

1.写了有参构造和析构函数

2.有参构造中属性使用了在堆区开辟内存空间

3.类的成员变量定义为指针,所以test01()中cout输出,需要解引用,否则输出看到的是内存地址

4.在test01方法中p2 = p1 这行代码是赋值操作,所以p2的m_Age输出值应该和p1一样,也就是18岁。

运行结果:

虽然结果打印出来,但是程序没有运行到main函数中暂停这行代码就崩溃,报了一个为加载xxx.pdb的错误。

错误分析如下

1)p2 = p1是赋值操作,发送了浅拷贝也就是p2中m_Age的指针和p1中m_Age的指针一模一样

2)回顾下C++类中构造函数和析构函数执行顺序,p1的构造执行,然后执行p1的析构函数

3)在p1执行析构函数,进行了内存回收,m_Age这个指针设置为NULL

4)执行了p2 = p1操作,会执行p2的析构函数,也会释放内存

5)p1和p2都对同一个指针进行内存释放,第二个释放肯定会报错。

以上就是崩溃的错误分析过程。

为了避免这种方法,我们想在执行p2 = p1这行代码的时候,不想使用编译器默认的拷贝,而是在堆区重新开辟内存来指向m_Age,也就是执行深拷贝,所以我们需要对等号(赋值运算)进行重载。

 

3.赋值运算符重载

因为暂时不知道重载函数返回类型是什么,我们先按照void写,来一个成员函数重载的实现方式

#include <iostream>
using namespace std;

class Person
{
public:
    //  把年龄这个属性写成指针
    int *m_Age;

    // 年龄开辟到堆区
    Person(int age)
    {
        m_Age = new int(age);  // new 来的对象都是一个内存地址
    }

    // 析构函数,程序员自己控制内存
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
    }

    // 成员函数方法实现运算符重载
    void operator=(Person &p)
    {
        // 编译器默认是执行 m_Age = p.m_Age; 操作(浅拷贝)
        // 我们这里需判断,对象的属性是否在堆区存在,如果存在先释放内存,再进行深拷贝
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
        // 深拷贝
        m_Age = new int(*p.m_Age); // 解引用拿出年龄,例如18岁
    }
};

void test01()
{
    Person p1(18);
    Person p2(20);

    //赋值操作
    p2 = p1;

    cout << "p1的age为: " << *p1.m_Age << endl;
    cout << "p2的age为: " << *p2.m_Age << endl;

}

int main()
{
    test01();
    system("pause");
    return 0;
}

运行结果

看起来好像没有问题,但是我们test01()代码改成这样

void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);

    //赋值操作
    p3 = p2 = p1;

    cout << "p1的age为: " << *p1.m_Age << endl;
    cout << "p2的age为: " << *p2.m_Age << endl;
    cout << "p3的age为: " << *p3.m_Age << endl;

}

发现这行代码 p3 = p2 = p1会报错,但是我们肯定知道 如果是int 类型三个变量a,b,c 肯定支持 c = b = a这样的赋值操作,所以这里我们还需要思考赋值运算符重载函数的返回值是什么

#include <iostream>
using namespace std;

class Person
{
public:
    //  把年龄这个属性写成指针
    int *m_Age;

    // 年龄开辟到堆区
    Person(int age)
    {
        m_Age = new int(age);  // new 来的对象都是一个内存地址
    }

    // 析构函数,程序员自己控制内存
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
    }

    // 成员函数方法实现运算符重载
    Person& operator=(Person &p)
    {
        // 编译器默认是执行 m_Age = p.m_Age; 操作(浅拷贝)
        // 我们这里需判断,对象的属性是否在堆区存在,如果存在先释放内存,再进行深拷贝
        if (m_Age != NULL)
        {
            delete m_Age; // 删除所占内存,并置空
            m_Age = NULL;
        }
        // 深拷贝
        m_Age = new int(*p.m_Age); // 解引用拿出年龄,例如18岁
        // 返回当前对象,支持链式编程
        return *this;
    }
};

void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);

    //赋值操作
    p3 = p2 = p1;

    cout << "p1的age为: " << *p1.m_Age << endl;
    cout << "p2的age为: " << *p2.m_Age << endl;
    cout << "p3的age为: " << *p3.m_Age << endl;

}

int main()
{
    test01();
    system("pause");
    return 0;
}

解释一下,为什么这个函数重载需要返回一个引用,主要原因就是要支持这种链式编程风格。这里使用返回引用才是真正的本身,如果改成返回值,会报错,因为返回值的话,就是调用拷贝,是对象本身的一个副本。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值