C++深入理解(一)——自定义类中重载运算符返回引用以及连续赋值的机制理解

本文探讨了C++中自定义类的运算符重载,特别是赋值运算符的实现方式,对比了返回对象和返回引用的区别。通过具体示例,分析了不同返回类型对连续赋值操作的影响,以及拷贝构造函数的调用情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 写作目的

好记性不如烂笔头。

结论:在自定义的类中,运算符重载需要返回类对象的引用。如果返回的是类对象,则在赋值的过程中会调用拷贝构造函数,引起不必要的运算。如果返回的是void,则不能进行连续赋值(如a = b = c)。

补充:在自定义类的对象连续赋值中a = b = c(a,b,c均为新定义的类的实例),

当运算符重载函数返回的是类对象引用时,其过程为,b = c调用运算符重载函数,返回b的引用,然后a = b调用运算符重载函数,返回a 的引用。

当运算符重载函数返回的是类对象时,其过程为,b = c先调用运算符重载函数,该函数结束后,由于返回的是类对象,因此在a = b = c这行代码中会创建一个临时类对象tem_c,用于接收运算符重载之后的结果,此时会调用拷贝构造函数tem_c( *this ),其中this是运算符重载函数中的。同时a = tem_c调用运算符重载函数,然后在a = b = c创建一个临时类对象tem_a,然后调用拷贝构造函数tem_a( *this ),然后释放tem_a(析构函数),再释放tem_c(析构函数))。

(补充2:自定义的类,C++编译器会自动创建6个函数,其中(默认的move constructor 和 默认的move assignment operator函数是从C++11之后有的,由于平时不采用这两种函数进行赋值,因此不对此进行考虑)。主要关注其余4中默认函数——默认构造函数(default constructor),拷贝构造函数(copy constructor),赋值运算符重载函数(copy assignment operator)和析构函数(Destructor) [1]

1. 运算符重载返回对象

#include <string>
#include<iostream>
using namespace std;

class CStudent
{
public:
    CStudent(char *pName) :m_pName(NULL)
    {
        if (NULL == pName)
        {
            return;
        }
        m_pName = new char[strlen(pName) + 1];
        strcpy(m_pName, pName);
        printf("CStudent: address:0x%08x name:%s\n", this, m_pName);
    }
    ~CStudent()
    {
        printf("~CStudent: address:0x%08x name:%s\n", this, m_pName);
        delete []m_pName;
    }
    //拷贝构造函数
    CStudent(const CStudent &a) :m_pName(NULL)
    {
        if (NULL == a.m_pName)
        {
			cout<<"reference"<<endl;
            return;
        }
        int len = strlen(a.m_pName);
        this->m_pName = new char[len + 20];
        memset(m_pName, 0, len + 20);
        strcpy(m_pName, a.m_pName);
        strcat(m_pName, "_copy_construct"); // 为了测试
        printf("copy construct: address:0x%08x name:%s\n", this, this->m_pName);
    }
public:
#if 1
    //重载赋值函数   返回对象
    CStudent   operator=(const CStudent &b)
    { 
        if (&b == this)
        {
            return *this;
        }
        if (NULL == b.m_pName)
        {
            delete []m_pName;
            m_pName = NULL;
            return *this;
        }
        delete []m_pName;
        int len=strlen(b.m_pName);
        m_pName=new char[len+1];
        memset(m_pName, 0, len+1);
        strcpy(m_pName,b.m_pName);
        return *this;
    }
#else
    //重载赋值函数  返回引用
    CStudent& operator=(const CStudent &b)
    {
        if (&b == this)
        {
            return *this;
        }
        if (NULL == b.m_pName)
        {
            delete []m_pName;
            m_pName = NULL;
            return *this;
        }
        delete []m_pName;
        int len = strlen(b.m_pName);
        m_pName = new char[len + 1];
        memset(m_pName, 0, len+1);
        strcpy(m_pName, b.m_pName);
        return *this;
    }
#endif
    //或者 我们也可以这样重载赋值运算符 即不返回任何值。如果这样的话,他将不支持客户代码中的链式赋值
    //例如a=b=c will be prohibited!
    //void operator=(const CStudent &a);

private:
    char *m_pName;
};



int main()
{
    {
        CStudent  liubei("liubei");
        CStudent  guanyu("guanyu");
        CStudent  zhangfei("zhangfei");
        //CStudent  dd(NULL);
        //CStudent  ff(dd);
        liubei = guanyu = zhangfei;
    } 
    system("pause"); 
    return 0;
}

运行结果:

这里为了说明问题,在拷贝构造函数中添加了类对象的处理,因此出现了上图结果中最后一行析构函数中出现了zhang_fei_copy_construct的结果。在实际中肯定不会这样做,因此实际在写代码中,返回引用和返回对象的结果都一样,但是由于返回对象的运算符重载函数多了拷贝构造函数的调用, 而且当运算顺序为(a=b)=c时,结果与预想的结果不同,因此建议使用运算符重载函数返回引用。

当把上述代码修改为时:

(liubei = guanyu) = zhangfei

运行结果为(为什么结果是这样的,由于 liubei = guanyu 先调用运算符重载函数得到结果,产生一个临时对象tem_c,然后调用拷贝构造函数tem_c(*this), *this是运算符重载函数的返回对象,同时tem_c = zhangfei调用了运算符重载函数,得到一个临时对象tem_a,然后调用拷贝构造函数tem_a(*this),因此得到如下结果):

2. 运算符重载函数返回类对象的引用

#include <string>
#include<iostream>
using namespace std;

class CStudent
{
public:
    CStudent(char *pName) :m_pName(NULL)
    {
        if (NULL == pName)
        {
            return;
        }
        m_pName = new char[strlen(pName) + 1];
        strcpy(m_pName, pName);
        printf("CStudent: address:0x%08x name:%s\n", this, m_pName);
    }
    ~CStudent()
    {
        printf("~CStudent: address:0x%08x name:%s\n", this, m_pName);
        delete []m_pName;
    }
    //拷贝构造函数
    CStudent(const CStudent &a) :m_pName(NULL)
    {
        if (NULL == a.m_pName)
        {
			cout<<"reference"<<endl;
            return;
        }
        int len = strlen(a.m_pName);
        this->m_pName = new char[len + 20];
        memset(m_pName, 0, len + 20);
        strcpy(m_pName, a.m_pName);
        strcat(m_pName, "_copy_construct"); // 为了测试
        printf("copy construct: address:0x%08x name:%s\n", this, this->m_pName);
    }
public:
#if 0
    //重载赋值函数   返回对象
    CStudent   operator=(const CStudent &b)
    { 
        if (&b == this)
        {
            return *this;
        }
        if (NULL == b.m_pName)
        {
            delete []m_pName;
            m_pName = NULL;
            return *this;
        }
        delete []m_pName;
        int len=strlen(b.m_pName);
        m_pName=new char[len+1];
        memset(m_pName, 0, len+1);
        strcpy(m_pName,b.m_pName);
        return *this;
    }
#else
    //重载赋值函数  返回引用
    CStudent& operator=(const CStudent &b)
    {
        if (&b == this)
        {
            return *this;
        }
        if (NULL == b.m_pName)
        {
            delete []m_pName;
            m_pName = NULL;
            return *this;
        }
        delete []m_pName;
        int len = strlen(b.m_pName);
        m_pName = new char[len + 1];
        memset(m_pName, 0, len+1);
        strcpy(m_pName, b.m_pName);
        return *this;
    }
#endif
    //或者 我们也可以这样重载赋值运算符 即不返回任何值。如果这样的话,他将不支持客户代码中的链式赋值
    //例如a=b=c will be prohibited!
    //void operator=(const CStudent &a);

private:
    char *m_pName;
};



int main()
{
    {
        CStudent  liubei("liubei");
        CStudent  guanyu("guanyu");
        CStudent  zhangfei("zhangfei");
        //CStudent  dd(NULL);
        //CStudent  ff(dd);
        liubei = guanyu = zhangfei;
    } 
    system("pause"); 
    return 0;
}

运行结果:

当把上述代码修改为时:

(liubei = guanyu) = zhangfei

运行结果为(运行过程为: liubei = guanyu先调用运算符重载函数,由于返回的是liubei的引用,因此进行 liubei = zhangfei调用运算符重载函数):

[Reference]

[1]代码参考:https://blog.csdn.net/miyunhong/article/details/51149376

### C++ 中拷贝构造函数赋值操作符重载的区别 #### 拷贝构造函数 当创建个新的对象并初始化它为另个同类型对象的时候,会调用拷贝构造函数。如果类中没有显式定义拷贝构造函数,则编译器提供默认版本[^4]。 对于自定义资源管理(比如动态分配内存),通常需要编写自己的拷贝构造函数来执行深拷贝而不是简单的位复制(即浅拷贝)。这可以防止多个对象共享同块堆内存放的数据而导致潜在的风险和错误。 ```cpp class MyClass { public: int* data; // 构造函数 MyClass(int value) : data(new int(value)) {} // 拷贝构造函数 MyClass(const MyClass& other) : data(new int(*other.data)) {} // 执行深拷贝 ~MyClass() { delete data; } }; ``` #### 赋值操作符重载赋值操作符用于已经存在的对象之间相互赋值的情况。同样地,默认情况下编译器提供了基本功能;但是为了处理复杂情况下的正确性和效率问题,可能也需要手动实现这个过程——特别是涉及到动态内存或其他形式外部资源的情况下: - 需要释放当前持有的资源; - 复制源对象的内容到目标对象上; - 返回`*this`以便支持链式表达式的使用方式。 ```cpp class MyClass { public: int* data; // ...其他成员... // 赋值操作符重载 MyClass& operator=(const MyClass& rhs) { if (this != &rhs) { // 自我赋值检查 *data = *(rhs.data); // 进行必要的清理工作以及新数据的复制 } return *this; // 支持连续赋值语句 } private: }; ``` #### 主要区别 1. **生命周期阶段不同** - 拷贝构造发生在对象创建之时。 - 赋值则是在两个已有的对象间交换状态之后发生。 2. **作用域差异** - 前者仅限于构造期间有效。 - 对于赋值来说非常重要,因为可能存在同对象给自己赋值的情形。 - 拷贝构造不存在这种情况,因为它总是在构建全新的实例时触发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值