运算符重载
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
1 #pragma once 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 //加号运算符重载 6 //1. 成员函数重载 7 class Person { 8 public: 9 //1.成员函数重载+号 10 Person operator+(Person &p) { 11 Person temp; 12 temp.m_A = this->m_A + p.m_A; 13 temp.m_B = this->m_B + p.m_B; 14 return temp; 15 } 16 int m_A; 17 int m_B; 18 }; 19 20 //2. 全局函数重载 21 Person operator+(Person& m, Person& n) { 22 Person temp; 23 temp.m_A = m.m_A + n.m_A; 24 temp.m_B = m.m_B + n.m_B; 25 return temp; 26 } 27 28 //3.函数重载的版本 29 Person operator+(Person &p, int num) { 30 Person temp; 31 temp.m_A = p.m_A + num; 32 temp.m_B = p.m_B + num; 33 return temp; 34 } 35 36 void test01() { 37 Person p1; 38 p1.m_A = 10; 39 p1.m_B = 20; 40 Person p2; 41 p2.m_A = 5; 42 p2.m_B = 15; 43 //成员函数重载本质 44 Person p4 = p1.operator+(p2); 45 //全局函数重载本质 46 Person p5 = operator+(p1, p2); 47 Person p3 = p1 + p2; //可以简化成这样子 48 49 //操作符重载也能做函数重载 50 Person p6 = p1 + 10; //Person + int 51 52 cout << "p3.m_A :" << p3.m_A << endl; 53 cout << "p3.m_B :" << p3.m_B << endl; 54 } 55 int main() { 56 test01(); 57 system("pause"); 58 }
- 可以成员函数重载,或者全局函数重载
- 也可以将运算符重载当成函数再进行重载(例如代码中,将Person + Person 重载成 Person + int)
- 对于内置的数据类型的运算符是不可能改变的,例如1+1就是2,不允许自定义运算规则。
4.5.2 左移运算符重载
1 #pragma once 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 //左移运算符重载 6 class Person { 7 public: 8 9 //利用成员函数重载 左移运算符 p.opeartor<<(cout) 简化版本 p << cout 10 //通常不会利用成员函数重载<<运算符 , 因为无法实现 cout 在 << 左侧 11 /*void operator<< () { 12 13 }*/ 14 15 int m_A; 16 int m_B; 17 }; 18 //只能利用全局函数重载左移运算符 19 ostream& operator<<(ostream &cout, Person &p) { //本质 operator<< (cout, p) 简化成 cout << p 20 cout << "p.m_A: " << p.m_A << " p.m_B: " << p.m_B; 21 return cout; 22 } 23 void test01() { 24 Person p; 25 p.m_A = 10; 26 p.m_B = 20; 27 cout << p << endl; //链式编程 28 } 29 int main() { 30 test01(); 31 system("pause"); 32 }
总结:
- 无法利用成员函数去重载左移运算符,void operator<< (cout) { }; 如果这样写的话,其最后调用的本质为 Person.operator<<(cout) 先有<<再有cout -- 就变成了<< cout。
- 只能利用全局函数去重载, cout是一种输出流(ostream)的类型
- cout << p << endl; 必须让( cout << p )整体也成为一个输出流,才能继续链式的使用<< endl,因此重载函数最后要返回ostream &
4.5.3 递增和递减运算符重载
作用:通过重载递增,递减运算符,实现自己的整型数据递增,递减。
重载递增运算符:
1 #pragma once 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 //重载递增运算符 6 //自定义整型 7 class MyInteger { 8 public: 9 friend ostream& operator<<(ostream& cout, const MyInteger& p); 10 MyInteger() { 11 m_Num = 0; 12 } 13 //重载前置++运算符 14 MyInteger& operator++ () { //返回引用是为了一直对同一个数据进行操作 15 m_Num++; //先进行++运算 //++m_Num也是可以的 16 return *this; //再将自身进行返回 17 } 18 //重载后置++运算符 19 MyInteger operator++(int) { //这里为什么不用引用是因为,下面创建的temp是一个局部变量,作用域后就失效了,再返回它的引用肯定会出问题 20 MyInteger temp = *this; //记录当前本身的值,然后让本身的值+1,但是返回的是以前的值,达到先返回后++的效果 21 m_Num++; 22 return temp; 23 } 24 private: 25 int m_Num; 26 }; 27 28 //重载<<运算符 29 ostream& operator<<(ostream& cout, const MyInteger& p) { 30 cout << p.m_Num << endl; 31 return cout; 32 } 33 void test01() { 34 MyInteger a; 35 cout << ++a << endl; 36 cout << a << endl; 37 } 38 void test02() { 39 MyInteger b; 40 cout << b++ << endl; 41 cout << b << endl; 42 } 43 int main() { 44 test01(); 45 test02(); 46 system("pause"); 47 }
重载递减运算符:
1 #include <iostream> 2 using namespace std; 3 //重载递减运算符 4 class Decrement { 5 public: 6 friend ostream& operator<<(ostream& cout, Decrement p); 7 Decrement() { 8 m_Num = 0; 9 } 10 11 //重载前置递减运算符 12 Decrement& operator--() { 13 --m_Num; 14 return *this; 15 } 16 //重载后置递减运算符 17 Decrement operator--(int) { 18 Decrement temp = *this; 19 m_Num--; 20 return temp; 21 } 22 private: 23 int m_Num; 24 }; 25 26 //重载<<运算符 27 ostream& operator<<(ostream& cout, Decrement p) { 28 cout << p.m_Num << endl; 29 return cout; 30 } 31 void test01() { 32 Decrement p; 33 cout << --p << endl; 34 cout << p << endl; 35 } 36 void test02() { 37 Decrement p2; 38 cout << p2-- << endl; 39 cout << p2 << endl; 40 } 41 int main() { 42 test01(); 43 test02(); 44 return 0; 45 }
总结(这个有很多知识点,请认真看完):
- 在C++中,编译器根据后置自增运算符的函数签名来区分前置和后置版本。前置自增运算符不需要传入任何参数,只需对当前对象进行自增操作即可;在后置自增运算符中,会传入一个额外的整数参数(可以命名为任何合法标识符,但通常命名为
int
),此参数用于标识该运算符为后置版本。(规定即是如此,不必纠结) - 重载后置++运算符,MyInteger operator++(int) {} 这边不需要加&引用的原因是 -- 下面创建的temp是一个局部变量,超出作用域后就失效了,再返回它的引用肯定会报错。
- cout << b++ << endl; 一开始这句代码报错(没有与这些操作数匹配的 "<<" 运算符)。可以从两个角度理解:
(1) 因为上面第二点说了,return的MyInteger是一个临时变量,在C++中临时变量是不能修改的,即默认为const变量。非const引用只能绑定与该引用同类型的变量,就是说 ostream& operator<<(ostream& cout, MyInteger& p) {}中 MyInterger& 这个参数很明显的是一个非const引用,它不能引用一个const对象,而重载函数中return的MyInterger是一个const对象, 因此引用失败。
(2) 大家都知道,引用本质上是一种指针常量,即指向不能变,但是指向的值可以变。代码中,operator<<(ostream& cout, MyInteger& p) {};我们想将MyInterger& p这个引用参数指向 MyInterger,如果此时MyInterger是一个局部变量的话,它的地址出了作用域就被释放了,那原本将p指向MyInterger这一过程就不再生效,即引用失败。
因此,重载<<运算符函数可以写成 ostream& operator<<(ostream& cout, const Decrement& p) {} 或者 ostream& operator<<(ostream& cout, Decrement p) {}
4. 前置递增返回引用,后置递增返回值。
5. 不要使用链式的后置递增(a++)++ 或者递减(a--)--,这样肯定会报错。原生C++的连续后置递增/递减也会报错,因为第二次是对局部变量进行了违规操作。具体原因上面说的应该很清楚了。
6. 测试会发现,原生C++的前置也会比后置要快,因为从源码也是像我给出的代码中所写的那样,前置直接返回对象就行了,而后置要先保存以下当前对象状态,再去返回。
4.5.4 赋值运算符重载
C++编译器默认至少给一个类添加4个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性值进行拷贝
4.默认赋值运算符 operator=,对属性值进行赋值
先给出一个浅拷贝带来的内存重复释放的案例:
1 class Person { 2 public: 3 Person(int age) { 4 m_Age = new int(age); //创建在堆区 5 } 6 ~Person() 7 { 8 if (m_Age != NULL) { 9 delete m_Age; 10 m_Age = NULL; 11 } 12 } 13 int* m_Age; 14 }; 15 void test01() { 16 Person p1(18); 17 Person p2(20); 18 p2 = p1; //赋值操作 19 cout << "p1的年龄为:" << *(p1.m_Age) << endl; 20 cout << "p2的年龄为:" << *(p2.m_Age) << endl; 21 }
如果用系统默认的p2 = p1赋值,p1和p2指向的堆区内存其实是同一块,那么在析构函数就会产生重复释放的问题。解决方案:
利用深拷贝重载赋值运算的代码:
1 #pragma once 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 //赋值运算符重载 6 class Person { 7 public: 8 Person(int age) { 9 m_Age = new int(age); //创建在堆区 10 } 11 ~Person() 12 { 13 if (m_Age != NULL) { 14 delete m_Age; 15 m_Age = NULL; 16 } 17 } 18 //重载赋值运算符 19 Person& operator=(Person &p) { 20 //编译器提供的是浅拷贝 -- m_Age = p.m_Age 21 22 //应该先判断是否有属性在堆区,如果有,应该先释放干净 23 if (p.m_Age != NULL) { 24 delete m_Age; 25 m_Age = NULL; 26 } 27 m_Age = new int(*p.m_Age); 28 return *this; //解引用 不是指针了,相当于一个变量了 29 //return this 返回的是地址 30 //return *this 返回的是对象 31 } 32 int* m_Age; 33 }; 34 void test01() { 35 Person p1(10); 36 Person p2(20); 37 Person p3(30); 38 p1 = p2; //赋值操作 39 cout << "p1的年龄为:" << *(p1.m_Age) << endl; 40 cout << "p2的年龄为:" << *(p2.m_Age) << endl; 41 42 p3 = p2 = p1; //链式赋值操作 43 cout << "p1: " << *p1.m_Age << " p2: " << *p2.m_Age << " p3: " << *p3.m_Age << endl; 44 } 45 int main() { 46 test01(); 47 system("pause"); 48 }
4.5.5 关系运算符重载
此部分比较简单
1 #include <iostream> 2 using namespace std; 3 //赋值运算符重载 4 class Person{ 5 public: 6 Person(string name, int age){ 7 this->m_Name = name; 8 this->m_Age = age; 9 } 10 11 bool operator==(Person& p){ 12 if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { 13 return true; 14 } else { 15 return false; 16 } 17 } 18 string m_Name; 19 int m_Age; 20 }; 21 void test01(){ 22 Person p1("张艺", 100); 23 Person p2("张艺", 100); 24 25 if (p1 == p2) 26 cout << "p1 == p2" << endl; 27 else 28 cout << "p1 != p2" << endl; 29 } 30 int main() { 31 test01(); 32 return 0; 33 }
4.5.5 函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
1 #pragma once 2 #include <iostream> 3 using namespace std; 4 //函数调用运算符重载 5 class Person { 6 public: 7 void AddFunction(int a, int b) { //加法仿函数 8 cout << a + b << endl; 9 } 10 11 void Print(string test) { //打印输出仿函数 12 cout << test << endl; 13 } 14 15 void Compare(int a, int b) { //对比仿函数 16 cout << max(a,b) << endl; 17 } 18 //............可以看出函数调用运算符重载非常灵活,可以根据具体需求修改 19 }; 20 void test01() { 21 Person p1; 22 //类比正常的函数调用 void AddFunction(int a, int b) 23 //因为很像正常函数调用,所以被称为仿函数 24 p1.AddFunction(10,20); 25 p1.Print("hhhhhhhhhhhhhh"); 26 p1.Compare(100, 200); 27 } 28 int main() { 29 test01(); 30 system("pause"); 31 }