1、运算符重载的意义 在C++中,运算符的操作对象只能是基本数据类型,而不能对于类、结构体等复杂数据类型进行操作,因此引入了运算符重载机制。运算符重载对已有的运算符赋予多重含义,使得同一个运算符作用于不同类型的数据时做出不同的行为。
运算符重载实际上是函数重载,它提供了C++的可扩展性。
2、运算符重载的规则 1、只能重载C++已有的运算符,不能创建新的运算符。
2、重载之后的运算符不能改变其优先级和结合性,也不能改变其操作数的个数以及语法结构。
3、运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造,重载功能应与原有功能相类似,避免没有目的地使用重载运算符。
4、C++中有五个运算符不可以重载:类属关系运算符“.”、成员指针运算符“*”、作用域运算符“::”、sizeof运算符和三目运算符“?:”。
3、运算符重载方式 重载运算符是具有特殊名字的函数,它们的名字由关键字operator和其后要重载的运算符共同组成。即:
1 2 3 4 返回类型 operator 运算符(参数列表) { 函数体; }
3.1、重载为类的成员函数 重载为类的成员函数,可以自由地访问本类的数据成员,运算的操作数会以调用者或参数的形式表示。
如果是双目运算符重载为类的成员函数,则它有两个操作数,左操作数一定是对象本身的数据,由this指针指出,右操作数则通过运算符重载函数的参数表来传递,即:
1 2 3 4 5 6 7 8 9 10 11 12 13 左操作数.运算符重载函数(右操作数); class A { private : int x; int y; public : A(int x1=0 ,int y1=0 ):x(x1),y(y1){}; 类名A operator +(const A& a) const { return A(x+a.x,y+a.y); } };
如果是单目运算符重载为类的成员函数,则要分为前置运算符和后置运算符,如果是前置运算符,则它的操作数是函数调用者,函数没有参数,即:
如果是后置单目运算符,则函数需要待一个整型参数,该参数不起任何作用,只是用来区分前置和后置。
1 2 3 左操作数.运算符重载函数(int ); a1++;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std ;class A { private : int x; int y; public : A(int x1=0 ,int y1=0 ):x(x1),y(y1){}; void show () const ; A operator ++() { ++x; ++y; return *this ; } A operator ++(int ) { A a = *this ; ++(*this ); return a; } }; void A::show () const { cout <<"(x,y) = " <<"(" <<x<<"," <<y<<")" <<endl ; } int main (void ) { A a1(1,2),a2(3,4); (a1++).show(); (++a2).show(); system("pause" ); return 0 ; }
注意:
前置单目运算符与后置单目运算符重载的最主要区别就算函数的形参,后置单目运算符带一个int型形参,但它只起区分作用。
3.2、重载为类的友元函数 运算符重载为类的友元函数,只需要在函数前加一个friend 关键字。即:
1 2 3 4 5 6 7 friend 返回类型 operator 运算符(参数列表){ 函数体; }
重载为类的友元函数(类外定义的函数)时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左到右需要保持一一对应 。
一般调用格式为:
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream using namespace std ;class A { private : int x; int y; public : A(int x1=0 ,int y1=0 ):x(x1),y(y1){}; void show () const ; friend A operator +(const A &a1,const A &a2); friend A operator -(const A &a1,const A &a2); }; void A::show () const { cout <<"(x,y) = " <<"(" <<x<<"," <<y<<")" <<endl ; } A operator +(const A &a1,const A &a2) { return A(a1.x+a2.x,a1.y+a2.y); } A operator -(const A &a1,const A &a2) { return A(a1.x-a2.x,a1.y-a2.y); } int main (void ) { A a1(1,2),a2(3,4); A a; cout <<"a1:" ; a1.show(); cout <<"a2:" a2.show(); a =a1+a2; cout <<"a:" ; a.show(); a=a1-a2; cout <<"a:" ; a.show(); system("pause" ); return 0 ; }
注意:
1、一般情况下,单目运算符最好重载为类的成员函数,双目运算符最好重载为类的友元函数。
2、若一个运算符的操作需要修改对象的状态,选择重载为成员函数比较好。
3、若运算符的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。
4、具有对称性的运算符可能转换任意一端的运算对象,如算术、关系运算符等等,通常重载为非成员函数。
5、由4个运算符必须重载为类的成员函数:赋值(=)、下标([])、调用(())、成员访问箭头(->)。
(new、delete、类型转换函数也算)
6、类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。
7、C++规定,重载运算符必须和用户定义的自定义类型对象一起使用。
MyClass operator *(double,MyClass);
MyClass operator *(MyClass,MyClass);
MyClass operator *(MyClass,double);
对于一个运算符函数来说,它或者是类的成员,或者至少包含一个类类型参数。操作符重载允许将标准C++操作符用于类对象;
8、当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。
典型例子:输入输出运算符不能重载为类的成员函数 ,因为它们左侧的运算符对象必须式istream/ostream流对象,而如果重载为类的成员函数,则左侧的操作对象必须是我们定义的一个类对象 。
1 2 ostream& operator <<(ostream&,const 类对象引用); istream& operator >>(istream&,类对象引用);
对于输出运算符”<<”来说,第一个参数是ostream对象引用,因为向六种写入数据会改变流的状态,所以不能用const修饰ostream对象。
对于输入运算符“>>”来说,第一个参数是istream对象的引用,第二个参数要想其中存入数据的对象,不能为常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std ;class A { private : int x; int y; public A(int x1=0 ,int y1=0 ):x(x1),y(y1){}; friend ostream& operator <<(ostream& os,const A &a); friend istream& operator >>(istream& is,A &a); }; ostream& operator <<(ostream& os,const A &a) { os<<a.x<<a.y; return os; } istream& operator >>(istream& os,A &a) { is>>a.x>>a.y; return os; } int main (void ) { A A1(1,2),A2(3,4); cout <<"a1:" <<a1<<endl ; cout <<"a2:" <<a2<<endl ; cout <<"请重新为a2对象输入数据:" <<endl ; cin >>a2; cout <<"重新输入后a2:" <<a2<<endl ; system("pause" ); return 0 ; }
注意:
通常情况下,输出运算符主要负责输出对象的内容,而非控制格式,即在重载时可以不需要重载格式控制符,如空格、换行、制表位等等,因为如果把格式控制符也重载进去的话,用户又是想在同一行输出一些描述性的文本,就无法完成了。
4、常用运算符的重载 4.1、输入输出运算符重载 如上所示。
4.2、关系运算符的重载 注意:
1、通过关系运算符都要成对的重载,即重载了“>”运算符,那么就要重载“<”运算符,反之亦然。
2、通常情况下,“==”运算符应该具有传递性,即a=b,b=c,则a=c成立。(都为两个=号)
3、当成对的出现运算符重载时,可以把一个运算符的工作委托给另外一个运算符,比如重载”!=”可以在重载”==”运算符基础上重载。
4.3、赋值运算符的重载 对于赋值运算符来说,如果不重载,那么类会自动为我们提供一个赋值运算符。这个默认的赋值运算符与默认拷贝构造函数意义,就是把一个对象的数据成员的值复制给另外一个对象相应的数据成员,即浅拷贝。如果数据成员种有指针,则无法满足要求,会造成内存泄露,这时我们应该重载赋值函数,实现深拷贝。
4.4、下标运算符 4.5、类型转换函数 类型转换函数又称类型转换运算符重载函数,是指将类对象等复杂数据类型转换为基本数据类型 。即:
类型转换函数也是以operator关键字开头,这点和运算符重载时的规律是一致的,只是被重载的是类型名。在函数名前面不能指定函数类型,函数也没有参数。其返回值的类型是由函数名中指定的类型名来确定的 (如果类型名是double,则将类型数据转换为double类型返回)。
类型转换运算符重载函数只能作为类的成员函数,因为转换的主体是本类的对象。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std ;class Swap { private : int a,b; public : Swap(int m,int n):a(m),b(n){}; operator char () { return static_cast <char >(a); } void show () { cout <<a<<" " <<b<<endl ; } }; int main (void ) { Swap s1 (65 ,2 ) ; cout <<"s1:" ; s1.show(); char ch =s1; cout <<ch<<endl ; system("pause" ); return 0 ; } 65 2 A
注意:
类型转换函数有一个特殊功能。当需要的时候,编译系统会自动调用重载函数,建立一个无名的临时对象(或临时变量),如上图,将s1对象赋值给ch时,如果没有重载“=”运算符,编译器会首先检查是否由类型转换函数,如果由,则调用operator char()函数将s1对象转换为一个临时的char类型变量,再将这个临时变量赋值给ch。
4.6、转换构造函数 转换构造函数是当一个构造函数只有一个参数,并且这个参数又不是本类的const引用时,这种构造函数称为转换构造函数。即它用来将基本数据变量转换为类对象等复杂数据变量。
转换构造函数不仅可以将一个标准类型数据转换为类对象,还可以将另一个类的对象转换为转换构造函数所在类的类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> using namespace std ;class Complex { public : Complex(){real=0 ;imag=0 ;} Complex(double r){real=r;imag=0 ;} Complex(double r,double i){real=r;imag=i;} friend Complex operator + (Complex c1,Complex c2); void display () ; private : double real; double imag; }; Complex operator + (Complex c1,Complex c2) { return Complex(c1.real+c2.real, c1.imag+c2.imag); } void Complex::display () { cout <<"(" <<real<<"," <<imag<<"i)" <<endl ; } int main () { Complex c1(3,4),c2(5,-10),c3; double d=0 ; c3=c1+2.5 ; c3.display (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <iostream> #include <string> using namespace std ;class person { public :string name;person(){}; person(string nam):name(nam){}; friend class Teacher ;}; class Student :public person{ public : Student(string num,string nam,string cla,int score):number(num),name(nam),classes(cla),scores(score),person(nam){}; void Print () { cout <<"学生:" <<name<<",学号:" <<number<<",班级:" <<classes<<endl ; } friend class Teacher ; string number; string name; string classes; int scores; }; class Teacher :public person { public : Teacher(string num,string nam,string st,string de):number(num),name(nam),stuff(st),department(de),person(nam){}; Teacher(Student &s):person(s.name) { number = s.number; name = s.name; } void Print () { cout <<"教师:" <<name<<",编号:" <<number<<",职称:" <<stuff<<",部门:" <<department<<endl ; } string number; string name; string stuff; string department; }; int main (void ) { string number; string name,sex; int score; cin >> number >> name >> sex >>score; string st,department; cin >>st>>department; person p (name) ; Student stu (number,name,sex,score) ; stu.Print(); Teacher t = (Teacher)stu; t.stuff=st; t.department=department; t.Print(); }