运算符重载

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 }
View Code
  • 可以成员函数重载,或者全局函数重载
  • 也可以将运算符重载当成函数再进行重载(例如代码中,将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 }
View Code

总结:

  • 无法利用成员函数去重载左移运算符,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 }
View Code

总结(这个有很多知识点,请认真看完)

  1. 在C++中,编译器根据后置自增运算符的函数签名来区分前置和后置版本。前置自增运算符不需要传入任何参数,只需对当前对象进行自增操作即可;在后置自增运算符中,会传入一个额外的整数参数(可以命名为任何合法标识符,但通常命名为int),此参数用于标识该运算符为后置版本。(规定即是如此,不必纠结)
  2. 重载后置++运算符,MyInteger operator++(int) {} 这边不需要加&引用的原因是 -- 下面创建的temp是一个局部变量,超出作用域后就失效了,再返回它的引用肯定会报错。
  3. 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 }
View Code

 

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 }
View Code

 

posted @ 2023-07-29 01:07  C++杀我  阅读(18)  评论(0编辑  收藏  举报