C++运算符重载

一、基本属性

  (1)运算符重载的目的是扩展C++中提供的运算符的适用范围,使之能作用于对象,或自定义的数据类型;

  (2)运算符重载的实质是函数重载,可以重载为普通成员函数,也可以重载为成员函数;

  (3)运算符重载也是多态的一种,和函数重载称为静态多态,表示函数地址早绑定,在编译阶段就确定好了地址;

二、运算符重载的原理

  以+运算符为例,对于内置的数据类型,比如整型,浮点型,+运算符可以进行相加操作,但是对于自定义的数据类型,如对象等,会报错,没有与这些操作符匹配的+运算符。

  以对象相加为例,我们可以在类中定义成员函数实现对象中的属性相加,一般为如下形式:

 1 class Person{
 2 public:
 3     Person(){};
 4     Person(const Person&){}
 5     Person(int a, int b) :m_A(a), m_B(b){};
 6 
 7     int m_A;
 8     int m_B;
 9 
10     //下面这种方式可以返回局部变量,因为返回的是指针,生命周期在代码块之外,若返回Person则不安全,这种说法是不对的
11     Person* plusPerson(Person &p){
12         Person tmp_1(0, 0); //这里是自定义的tmp_1进行初始化,调用了无效的拷贝构造函数,完成初始化,跳过编译器检查,后面继续赋值,完成功能
13         Person *tmp = new Person(tmp_1);
14         (*tmp).m_A = this->m_A + p.m_A;
15         (*tmp).m_B = this->m_B + p.m_B;
16         return tmp;
17     }
18 };
19 
20 void test01(){
21     Person p1(10, 20);
22     Person p2(20, 30);
23 
24     Person *p3 = p1.plusPerson(p2);//不想用这种函数调用的方式
25     cout << p3->m_A << " " << p3->m_B << endl;
26     delete p3;
27 }

  注意:上述代码中有个Bug,(1)如果只定义函数初始化列表,则默认构造函数和默认拷贝构造都不会提供了;(2)自定义的拷贝构造函数体为空,在返回局部对象时,会显示为初始化的错误。(3)返回局部对象的指针时,不会调用拷贝构造函数。上述代码中可以直接返回局部对象而不用在堆上开辟指针。

  但我们并不想用成员函数的方式调用,这是可以用"opertor 运算符"进行重载,只需要将plusPerson函数名换成operator+即可,将函数实现复制即可。另外除了返回对象指针,返回对象也可以。

 1 class Person{
 2 public: 5     Person(int a, int b) :m_A(a), m_B(b){};
 6 
 7     int m_A;
 8     int m_B;
 9 
10     //成员函数进行运算符重载
11     Person* operator+(Person &p){
12         Person tmp_1(0, 0);
13         Person *tmp = new Person(tmp_1);
14         (*tmp).m_A = this->m_A + p.m_A;
15         (*tmp).m_B = this->m_B + p.m_B;
16         return tmp;
17     }
18 };
19 
20 //--------全局函数对运算符进行重载
21 //Person* operator+(Person &p1, Person &p2){
22 //    Person tmp_1(0, 0);
23 //    Person *tmp = new Person(tmp_1);
24 //    (*tmp).m_A = p1.m_A + p2.m_A;
25 //    (*tmp).m_B = p1.m_B + p2.m_B;
26 //    return tmp;
27 //}
28 //---------------------------
29 
30 void test01(){
31     Person p1(10, 20);
32     Person p2(20, 30);
33 
34     Person *p3 = p1 + p2; //相当于Person *p3 = p1.operator+(p2);或者operator+(p1, p2)
35     cout << p3->m_A << " " << p3->m_B << endl;
36     delete p3;
37 }

三、运算符重载总结与示例

1.总结

  (1)重载运算符(),[] ,->, =的时候,运算符重载函数必须声明为类的成员函数;

  (2)重载运算符<<,>>的时候,运算符只能通过全局函数配合友元函数进行重载;

  (3)不要重载&&和||运算符,因为无法实现短路原则。

2.示例

(1)重载<<和>>运算符

  重载<<的目的是为了让自定义数据类型和内置类型一样通过cout输出,如cout<<p1<<endl;但从上面+运算符重载的例子可以看出,p1+p2的实现中,成员函数为:p1.operator+(p2),全局函数为:operator+(p1, p2)。

  (a)将cout << p1和p1+p2进行比较可以看出,成员函数要cout.operator<<(p1),全局函数要operator<<(cout, p1),因此只能通过全局函数进行重载;

  (b)配合友元函数是因为要访问私有成员属性;

  (c)为了实现连续<<输出(链式编程思想),需要将返回值写为ostream&引用类型。

 1 class Person{
 2     friend ostream& operator<<(ostream& cout, Person& p1);
 3 public: 6     Person(int a, int b) :m_A(a), m_B(b){};
 7 
 8 private:
 9     int m_A;
10     int m_B;
11 };
12 
13 ostream& operator<<(ostream& cout, Person& p1){
14     cout << "m_A的值:" << p1.m_A << "m_B的值" << p1.m_B ; //重载函数内部为常规的函数实现
15     return cout;
16 }
17 
18 void test01(){
19     Person p1(10, 20);
20 
21     operator<<(cout, p1);
22 
23     cout << p1 << "你看我可以继续输出" << endl;
24 }

 (2)重载前置后置运算符,其中前置运算符的返回值为类名+引用,后置运算符的返回值为类名。

  第一个代码调用了无效的拷贝构造函数,但完成初始化跳过了编译器检查,然后又再次赋值从而实现功能,但这种代码无法返回局部对象

 1 class Person{
 2     friend ostream& operator<<(ostream& cout, Person& p1);
 3 public:
 4     Person(){ m_A = 10; };
 5     Person(const Person&p){ 
 6         cout << "调用拷贝构造函数" << endl;
 7     }
 8 
 9     //前置++,引用作为返回值,返回局部对象
10     Person& operator++(){
11         this->m_A++;
12         return *this;
13     }
14 
15     //后置++,代码中用到了无效的拷贝构造函数,进行了无效的初始化,跳过编译器检查,然后又再次赋值从而实现效果
16     Person* operator++(int){
17         Person* tmp = new Person (*this);
18         tmp->m_A = this->m_A;
19         this->m_A++;
20         return tmp;
21     }
22 private:
23     int m_A;
24 };

  对上面的代码进行修改,可以添加有效的拷贝构造函数,或者取消掉无效的拷贝构造函数均可,这样就可以返回局部对象了。

 1 class Person{
 2     friend ostream& operator<<(ostream& cout, Person& p1);
 3 public:
 4   Person(const Person&p){
 5     this->m_A = p.m_A;}; 6     Person(int a) :m_A(a){}
 7 
 8     //前置++,引用作为返回值,返回局部对象
 9     Person& operator++(){
10         this->m_A++;
11         return *this;
12     }
13 
14     //后置++,返回局部对象会调用拷贝构造函数
15     Person operator++(int){
16         Person tmp(*this);
17         this->m_A++;
18         return tmp;
19     }
20 
21 
22 private:
23     int m_A;
24 };
25 
26 
27 ostream& operator<<(ostream& cout, Person& p1){
28     cout << "m_A的值:" << p1.m_A ; //重载函数内部为常规的函数实现
29     return cout;
30 }
31 
32 void test01(){
33     Person p1(10);
34 
35     cout << ++p1 << "前置++" << endl;
36     cout << p1++ << "后置++" << endl;
37 }

  注意:C++内置类型的后置++返回的是变量的拷贝,也就是不可修改的值;前置++返回的是变量的引用,因此可以作为修改的左值。即++(++a)或(++a)++都可以,但++(a++)不可以,(C++默认必须修改a的值,如果不修改则报错)。但我们实现的重载中,++(a++)不会报错,因为我们仅仅是为了输出值,没有对是否修改a做警告处理,但并没有对a加两次。

 (3)重置指针运算符与智能指针

   一般用于实现智能指针,托管自定义类型的对象,让对象自动释放;若想让智能指针像Person *p等智能指针一样,则需要重载->和*,必须在成员函数中重载。

 1 class Person{
 2 public:
 3     Person(){ m_A = 0; }
 4     Person(int a) :m_A(a){}
 5 
 6     void showage(){
 7         cout << this->m_A << endl;
 8     }
 9 
10     ~Person(){
11         cout << "对象析构" << endl;
12     }
13 
14 private:
15     int m_A;
16 };
17 
18 //常规的对象指针在堆上开辟后,要记得释放
19 void test00(){
20     Person *p = new Person(10);
21     p->showage();
22     delete p;
23 }
24 
25 //智能指针类的成员属性为原指针
26 class SmatrPointer{
27 public:
28 
29     SmatrPointer(){}
30 
31     SmatrPointer(Person *person){
32         cout << "有参构造" << endl;
33         this->person = person;
34     }
35     SmatrPointer(const SmatrPointer& person){
36         cout << "拷贝构造" << endl;
37         this->person = new Person;
38         this->person = person.person;
39     }
40 
41 
42     ~SmatrPointer(){
43         cout << "智能指针析构" << endl;
44 
45         if (this->person != NULL){
46             delete this->person;
47             this->person = NULL;
48         }
49     }
50 
51     //若实现sp->showage()像Person *p的p->showage()一样
52     //即sp->为p   返回为指针类型,实际为sp->->showage(),这里编译器做了优化
53     Person* operator->(){
54         return this->person;
55     }
56 
57     //若实现(*sp).showage()像Person *p的(*p).showage()一样
58     //即*sp为*p    返回为对象类型
59     Person& operator*(){
60         return *this->person;
61     }
62 
63 private:
64     Person *person;
65 };
66 
67 void test01(){
68     SmatrPointer *sp = new SmatrPointer(new Person(10)); //调用有参构造,堆上开辟
69     (*sp)->showage();
70     (**sp).showage();
71     delete sp;
72     
73     SmatrPointer sp_1(new Person(20)); //调用有参构造,栈上开辟
74     //若想让智能指针像Person *p一样,则需要重载->和*
75     sp_1->showage();
76     (*sp_1).showage();
77     
78 }

(4)赋值运算符重载

  一个类默认创建默认构造,析构,拷贝构造和operator=赋值运算符(可以进行简单的值传递),需要注意new的用法。

 1 class Person{
 2 public:
 3 
 4     Person(char *name){
 5         //m_Name = new char(strlen(name)+1); //这里出错,声明出错,应该为字符数组,这里为赋初值了
 6         m_Name = new char[strlen(name) + 1];
 7         strcpy(m_Name, name);
 8     }
 9 
10     Person(const Person&p){
11         if (this->m_Name != NULL){
12             delete [] this->m_Name;
13             this->m_Name = NULL;
14         }
15         this->m_Name = new char[strlen(p.m_Name) + 1];
16         strcpy(this->m_Name, p.m_Name);
17     }
18 
19     ~Person(){
20         if (this->m_Name != NULL){
21             delete [] this->m_Name;
22             this->m_Name = NULL;
23         }
24     }
25 
26     
27     //void operator=(Person &p){
28     //    if (this->m_Name != NULL){
29     //        delete [] this->m_Name;
30     //        this->m_Name = NULL;
31     //    }
32     //
33     //    this->m_Name = new char[strlen(p.m_Name) + 1];
34     //    strcpy(this->m_Name, p.m_Name);
35     //}
36 
37     Person& operator=(Person &p){
38         if (this->m_Name != NULL){
39             delete[] this->m_Name;
40             this->m_Name = NULL;
41         }
42 
43         this->m_Name = new char[strlen(p.m_Name) + 1];
44         strcpy(this->m_Name, p.m_Name);
45 
46         return *this;
47     }
48 
49     void show(){
50         cout << this->m_Name << endl;
51     }
52 
53     char *m_Name;
54 };
55 
56 void test01(){
57     Person p1("我滴神啊");
58     Person p2("My God");
59     p1.show();
60 
61     //默认提供的operator=可以实现简单值的复制,如果涉及到指针成员属性
62     //则会将地址进行赋值,出现内存泄漏问题,和深浅拷贝差不多
63     //因此需要进行运算符重载
64     p1 = p2;
65     p1.show();
66 
67     //有的人想进行链式赋值,这里链式编程思想,返回类名&
68     Person p3("诶呀");
69     p1 = p2 = p3;
70     p3.show();
71 }

(5)[]运算符重载

  主要实现通过[]对类写成的动态数组进行索引。

1 //将对应索引的数组值输出,若还想通过索引进行修改加引用
2 int& MyArray::operator[](int index){
3     return this->address[index];
4 }
5 
6 void test01(){
7     MyArray p3;
8     cout << p3[1 << endl;
9 }

(6)关系运算符的重载

  对==,!=进行重载,返回值为bool值

(7)()重载

  看上去像函数调用,实际为仿函数

 1 class MyPrint{
 2 public:
 3     MyPrint(){}
 4 
 5     void myprint(const string text){
 6         cout << text << endl;
 7     }
 8 
 9     void operator()(const string text){
10         cout << text << endl;
11     }
12 
13 };
14 
15 void test01(){
16     MyPrint myPrint;
17     myPrint.myprint("拔罐啦");
18 
19     //如果要让对象进行调用,必须对()重载,看上去想是函数调用,其实是仿函数
20     myPrint("理疗真疼");
21 
22 }

 

posted @ 2019-01-14 20:17  两猿社  阅读(795)  评论(0编辑  收藏  举报