关于等号运算符重载的解读

等号运算符重载


关于本篇

C++中说到重载,可能我们会想到函数重载,除此之外还有一个很重要的就是运算符重载,那么这一篇博客主要讲的是等号运算符重载,主要也是我在Coding过程中出现了一些问题,在查阅相关的资料和书籍之后,想要总结分享一下。

为什么要重载?

C++中对于内置的数据类型,我们显然是可以很友好的使用=运算符来进行赋值初始化等操作,但是随着我们对C++了解的程度的加深和基于对技术的好奇和追逐,我们会碰到运算符重载这个术语,初次接触到的时候我们会不太好理解这些东西(尤其是和类的相关概念纠缠到一起),没关系我们一起来学习。

下图中的代码a_i = b_i,这里的=是用来初始化的,和之后的两句注释语句是等效的。而接下来的b_s = a_s看起来像是没什么两样,但是要注意这里变量b_s是已经是实例化,调用string类的无参构造函数所创建的,所以b_s = a_s其实是等号运算符的重载

#include<iostream>
#include<string>

using namespace std;

int main() {
	int a_i = 6;
	int b_i = a_i;
	//int b_i(a_i);
	//int b_i{a_i};

	string a_s = "st1";
	string b_s;
    cout<<"value: "<<b_s<<" ,address: "<<&b_s<<endl;
	b_s = a_s;
	cout<<"value: "<<b_s<<" ,address: "<<&b_s<<endl;

	return 0;
}

通过输出我们可以很明显的看到,这两个=的区别,也可以证实b_s是先被string类的无参构造函数初始化之后才重载等号赋值的。

6
value:  ,address:00AFFCEC
value: st1 ,address: 00AFFCEC

在VS中ctrl+左键点击b_s = a_s中的=,即可以跳转到函数定义的地方,可以看到这就是等号运算符的重载。值得注意的是if()的判断和返回类型,之后我会解读这里的。

basic_string& operator=(const basic_string& _Right) { // assign _Right
	if (this != _STD addressof(_Right)) { // different, assign it
		_Copy_assign(_Right, _Choose_pocca<_Alty>{});
	}
	return *this;
}

所以我们重载的目的就是由于满足赋值操作的多样性,以及将自定义类中的变量赋值封装到一起,与此同时涉及到浅拷贝和深拷贝,需要我们显式的提供赋值运算符的重载。

重载等号的问题

在实际的Coding过程中,我们会碰到很多的坑,如果不去踩一踩的话,光凭脑子的理解还是不够直观的。这里我就说一下我重载等号过程中的一些坑和疑惑。

重载等号应该定义在何处?

等号的重载是应该在类内还是类外,必须是类内定义的(对于某些运算符,比如+类内类外定义都是可以的)。因为如果我们没有在类内定义赋值运算符重载函数,那么编译器会提供隐式的定义,我们在类外定义赋值运算符重载函数的时候,就会出现调用的时候二义性。

T& operator=(const T& t){}				// 类内的定义

重载等号的返回类型是什么?

对于这一个问题,我们要思考一下,重载=到底意味着什么。下图代码中我首先是定义了一个void返回值的重载函数,运行代码,结果很正常,void应该是可以的。事实真的如此吗?
这里表面是重载赋值运算符,其实和setter方法没什么两样。我们需要考虑的是没有返回值类型就意味着,我们不能连续调用,即形如t3 = t2 = t1是会出错的,我们作为程序的开发者,我们当然可以避免这种操作,但是既然是面向对象,那么我们就要避免这种行为,提高程序的鲁棒性。
重新回到t3 = t2 = t1这里,注意=是右结合,即t3 = (t2 = t1)。为了实现"连锁赋值",赋值运算符必须返回一个指向操作符的左侧实参的引用。当然了返回Test值貌似也是可以的,但是我们需要注意会有额外的开销,包括匿名对象的拷贝构造函数和析构函数,当我们没有提供显式的拷贝构造函数时,匿名对象赋值给一个实例可能会出错,错误的原因就在于默认的拷贝构造函数是将类中的值完全的复制,当涉及到指针的时候,由于仅仅是浅拷贝,会在匿名对象被析构之后,指针会变成空悬指针。

#include<iostream>
#include<string>

using namespace std;

class TestA {
public:
	TestA() { m_a = 0; m_b = 0; }
	TestA(int a, int b) { m_a = a; m_b = b; }
	/*void operator=(const TestA& tt) {
		this->m_a = tt.m_a;
		this->m_b = tt.m_b;
	}*/

	TestA& operator=(const TestA& tt) {
		this->m_a = tt.m_a;
		this->m_b = tt.m_b;
		return *this;
	}
	void printA() { cout << m_a << " " << m_b << endl; }
private:
	int m_a;
	int m_b;
};

int main() {
	TestA t1(3, 4);
	TestA t2(4, 5);
    TestA t3;
	t3 = t2 = t1;

	t2.printA();
    t3.printA();
	return 0;
}

输出如下,调用返回值为引用的赋值运算符重载函数,结果是符合预期的。

3 4
3 4 

总结

这篇博客主要是针对等号运算符重载的一些问题进行了分析和总结,也留下了一些坑,之后我会陆续的填上的。然后是如果有不对的地方,还请不吝赐教。这是我第一次使用markdown语法写排版blog,有格式建议的可以交流交流,谢谢!

参考文献

  • C++ Primer 5th
  • Effective C++

Write the code, change the world!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值