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 }