目录
运算符重载:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
一、加号运算符重载
实现两个自定义数据类型的相加运算
实例:
void test01() {
person p1;
p1.m_A = 10;
p1.m_B = 10;
person p2;
p2.m_A = 20;
p2.m_B = 20;
person p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
写下该相加的测试案例时,报错p1与p2不可相加。其本身为对象,要想实现这两个数据类型的相加,需要用到编译器自带的operator函数。其有不同的定义方式与调用方式,例如在全局函数中进行加号运算符重载、在成员中进行加号运算符重载。
成员函数加号运算符重载:
class person {
public:
//1.成员函数重载+号
person operator+(person& p) {
person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
全局函数加号运算符重载:
//全局函数重载+号
person operator+(person& p1, person& p2) {
person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
当编写成员函数加号运算符重载后进行调用,可以通过其基本性质进行调用(类中的成员函数):
person p3 = p1.operator+(p2);
当想要实现对象与int类型数据相加时(成员函数同理):
person operator+(person& p1, int num) {
person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
二、左移运算符重载
在最初学习C语言时,输出的方式大部分使用printf的方式输出,如今在C++中我们更常用cout<<的形式作输出。两者相较不同的是printf本质是一个函数而cout是一个数据类型,通过选中cout选择转到定义的方式可以看到它是一个ostream(输出流)的数据类型。
了解完cout的本质后可以学习左移运算符的重载,实现输出一个person类的对象p;
cout << p << endl;
误区:
写左移运算符重载的版本同样需要operator,根据加号运算符重载的样式,可能会写出以下样式:
class person {
public:
void operator<<(cout) {
}
int m_a;
int m_b;
};
此时按照这个样式在成员函数编写operator,得到的效果为p.operator<<(cout)即p<<cout;与原本预想的cout<<p;顺序相反。因此,不能使用成员函数编写左移运算符的重载,因为无法实现cout在左移运算符左侧。只能使用全局函数编写左移运算符重载。
此时的输出流对象全局只能有一个,不能创建新的cout,因此使用引用的方式输入。
void operator<<(ostream& cout, person& p) {
cout << "m_a=" << p.m_a << endl;
cout << "m_b=" << p.m_b << endl;
}
编写完成后,cout << p;可以使用,但cout<< p << endl;显示报错。 因为在此时返回值类型为void,因此不能进行链式的编程。想要完成cout链式的编程,应该为其设置返回值为ostream。并用引用的方式返回cout。
ostream& operator<<(ostream& cout, person& p) {
cout << "m_a=" << p.m_a << endl;
cout << "m_b=" << p.m_b << endl;
return cout;
因为左移运算符重载位于全局函数,将成员属性m_a和m_b设置为私有时,可以采用友元的方式进行访问。
class person {
friend ostream& operator<<(ostream& cout, person& p);
private:
int m_a;
int m_b;
};
三、递增运算符重载
前言:了解前置递增与后置递增的机制
实例:实现自定义的数据的前置后置递增。
①前置递增运算符重载
先写出数据与重载的左移运算符
//自定义整型
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger() {
m_num = 0;
}
private:
int m_num;
};
//重载<<运算符
ostream& operator<<(ostream& cout, MyInteger myint) {
cout << myint.m_num;
return cout;
}
在成员函数中编写前置递增运算符:
MyInteger& operator++() {
m_num++;
return *this;
}
注意:返回值类型必须是使用引用的形式。
如果返回值类型不是一个引用,则返回的是一个新的变量,而非最初的变量。如计算++(++myint)时,实际的myint的值只递增了一次。
②后置递增运算符重载
根据函数重载的条件,返回值类型不作为函数的重载条件,但参数的不同可以作为函数的重载条件,因此可以使用这一条件加入一个占位参数来区分前置递增与后置递增。(具体在函数重载环节)
void operator++(int) {
}
后置递增与前置递增的区别为:先返回原先结果,后对数据进行递增操作。因此在这里实现该操作的思路应为:先使用temp记录原先数据,后递增数据,返回temp。
MyInteger operator++(int) {
MyInteger temp = *this;
m_num++;
return temp;
}
此时返回的应为值,而不是引用。因为返回的是局部变量temp,返回完成后temp释放。若返回引用,后续会进行非法操作。
备注:
在编写左移运算符时,一般会参照上一标题的实例方式为第二个参数返回引用,在前置运算符重载测试后没有发生报错。但在后置运算符重载测试里面报错。此时涉及到匿名变量不能初始化引用的问题。具体可以参考博客:C++ | 重载运算符实现后置++的时候 关于函数形参不可使用引用的问题_重载后置++运算符-CSDN博客
四、赋值运算符重载
在我们给对象属性赋值,可能会涉及到开辟在堆区的数据(例如对象p1 = p2)。在对象的特性中,若在有堆区数据的情况下只是使用原有的拷贝函数(浅拷贝),则可能会造成程序的崩溃。此时就需要赋值运算符重载解决类似问题。
实例:
按照前言的说法设置以下实例,并安排上对应所需的析构函数。
class person {
public:
//构造函数:将传入的数据开辟到堆区,并使用指针指向该数据。
person(int age) {
m_age = new int(age);
}
//析构函数
~person() {
if (m_age != NULL) {
delete m_age;
m_age = NULL;
}
}
//创建一个指针
int* m_age;
};
void test01() {
person p1(18);
person p2(20);
p1 = p2;
}
运行test01测试案例,结果报错。原因在p1 = p2的赋值方式进行的是浅拷贝,只进行了简单的赋值操作,致使p1与p2的指针也重复相同,在后续运行析构函数时,p1与p2指向相同的地址被重复释放。因此解决这个问题可以通过赋值运算符重载进行深拷贝。
//赋值运算符重载
person& operator=(person& p) {
//先判断是否有属性在堆区,如果有则先释放干净,然后进行深拷贝
if (m_age != NULL) {
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);
return *this;
}