在前一节中曾提到过,C++中运行时的多态性主要是通过虚函数来实现的,而编译时的多态性是由函数重载和运算符重载来实现的。这一系列我将主要讲解C++中有关运算符重载方面的内容。在每一个系列讲解之前,都会有它的一些基础知识需要我们去理解。而运算符重载的基础就是运算符重载函数。所以今天主要讲的是运算符重载函数。
1.运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用域不同类型的数据导致不同行为的发生。比如
1 int i; 2 int i1=10,i2=10; 3 i=i1+i2; 4 std::cout<<"i1+i2="<<i<<std::endl; 5 6 double d; 7 double d1=20,d2=20; 8 d=d1+d2; 9 std::cout<<"d1+d2="<<d<<std::endl;
在这个程序里"+"既完成两个整形数的加法运算,又完成了双精度型的加法运算。为什么同一个运算符"+"可以用于完成不同类型的数据的加法运算?这是因为C++针对预定义基本数据类型已经对"+"运算符做了适当的重载。在编译程序编译不同类型数据的加法表达式时,会自动调用相应类型的加法运算符重载函数。但是C++中所提供的预定义的基本数据类型毕竟是有限的,在解决一些实际的问题时,往往需要用户自定义数据类型。比如高中数学里所提到的复数:
1 class Complex //复数类 2 { 3 public: 4 double real;//实数 5 double imag;//虚数 6 Complex(double real=0,double imag=0) 7 { 8 this->real=real; 9 this->imag=imag; 10 } 11 }
假如我们建立两个复数,并用"+"运算符让它们直接相加:
1 Complex com1(10,10),com2(20,20),sum; 2 sum=com1+com2;
那么会提示没有与这些操作数匹配的 "+" 运算符的错误。这是因为Complex类类型不是预定义类型,系统没用对该类型的数据进行加法运算符函数的重载。C++就为运算符重载提供了一种方法,即运算符重载函数。其函数名字规定为operator后紧跟重载运算符。比如:operator+(),operator*()等。现在我们给上述程序声明一个加法运算符的重载函数用于完成复数的加法运算:
1 #include "stdafx.h" 2 #include <iostream> 3 4 class Complex //复数类 5 { 6 public: 7 double real;//实数 8 double imag;//虚数 9 Complex(double real=0,double imag=0) 10 { 11 this->real=real; 12 this->imag=imag; 13 } 14 }; 15 16 Complex operator+(Complex com1,Complex com2)//运算符重载函数 17 { 18 return Complex(com1.real+com2.real,com1.imag+com2.imag); 19 } 20 21 int main() 22 { 23 Complex com1(10,10),com2(20,20),sum; 24 sum=com1+com2;//或sum=operator+(com1,com2) 25 26 std::cout<<"sum的实数部分为"<<sum.real<<std::endl; 27 std::cout<<"sum的虚数部分为"<<sum.imag<<"i"<<std::endl; 28 29 return0; 30 }
结果:
在上述示例代码中,调用运算符重载函数时,也可以以operator+(com1,com2)的形式来调用,实际上com1+com2在程序解释时也是转化成前者一样的形式。但是直接用com1+com2的形式更加符合人的书写习惯。
2.上述示例中的运算符重载函数是不属于任何的类,是全局的函数。因为在Complex类(复数类)中的数据成员是公有的性质,所以运算符重载函数可以访问。但如果定义为私有的呢,那该怎么办。其实,在实际的运算符重载函数声明当中,要不定义其为要操作类的成员函数或类的友元函数。
(1)运算符重载函数作为类的友元函数的形式:
class 类名
{
friend 返回类型 operator运算符(形参表);
}
类外定义格式:
返回类型 operator运算符(参数表)
{
函数体
}
友元函数重载双目运算符(有两个操作数,通常在运算符的左右两则),参数表中的个数为两个。若是重载单目运算符(只有一个操作数),则参数表中只有一参数。
i.友元函数重载双目运算符(+):
1 #include "stdafx.h" 2 #include <iostream> 3 4 class Complex //复数类 5 { 6 private://私有 7 double real;//实数 8 double imag;//虚数 9 public: 10 Complex(double real=0,double imag=0) 11 { 12 this->real=real; 13 this->imag=imag; 14 } 15 friend Complex operator+(Complex com1,Complex com2);//友元函数重载双目运算符+ 16 void showSum(); 17 }; 18 19 20 Complex operator+(Complex com1,Complex com2)//友元运算符重载函数 21 { 22 return Complex(com1.real+com2.real,com1.imag+com2.imag); 23 } 24 25 void Complex::showSum() 26 { 27 std::cout<<real; 28 if(imag>0) 29 std::cout<<"+"; 30 if(imag!=0) 31 std::cout<<imag<<"i"<<std::endl; 32 } 33 34 int main() 35 { 36 Complex com1(10,10),com2(20,-20),sum; 37 sum=com1+com2;//或sum=operator+(com1,com2) 38 sum.showSum();//输出复数相加结果 39 40 return0; 41 }
结果:
ii.友元函数重载单目运算符(++):
1 #include "stdafx.h" 2 #include <iostream> 3 4 class Point//坐标类 5 { 6 private: 7 int x; 8 int y; 9 public: 10 Point(int x,int y) 11 { 12 this->x=x; 13 this->y=y; 14 } 15 friend voidoperator++(Point& point);//友元函数重载单目运算符++ 16 void showPoint(); 17 }; 18 19 voidoperator++(Point& point)//友元运算符重载函数 20 { 21 ++point.x; 22 ++point.y; 23 } 24 25 void Point::showPoint() 26 { 27 std::cout<<"("<<x<<","<<y<<")"<<std::endl; 28 } 29 30 int main() 31 { 32 Point point(10,10); 33 ++point;//或operator++(point) 34 point.showPoint();//输出坐标值 35 36 return0; 37 }
结果:
运算符重载函数可以返回任何类型,甚至是void,但通常返回类型都与它所操作的类类型一样,这样可以使运算符使用在复杂的表达式中。比如把上述双目运算符重载函数示例代码中main()主函数里的com1+com2改为com1+com2+com2,那么结果又会不一样了。像赋值运算符=、下标运算符[]、函数调用运算符()等是不能被定义为友元运算符重载函数。同一个运算符可以定义多个运算符重载函数来进行不同的操作。
(2)运算符重载函数作为类的成员函数的形式:
class 类名
{
返回类型 operator 运算符(形参表);
}
类外定义格式:
返回类型 类名:: operator 运算符(形参表)
{
函数体;
}
对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。同样的是重载,为什么和友元函数在参数的个数上会有所区别的。原因在于友元函数,没有this指针。
i.成员函数重载双目运算符(+):
1 #include "stdafx.h" 2 #include <iostream> 3 4 class Complex //复数类 5 { 6 private://私有 7 double real;//实数 8 double imag;//虚数 9 public: 10 Complex(double real=0,double imag=0) 11 { 12 this->real=real; 13 this->imag=imag; 14 } 15 Complex operator+(Complex com1);//成员函数重载双目运算符+ 16 void showSum(); 17 }; 18 19 20 Complex Complex::operator+(Complex com1) 21 { 22 return Complex(real+com1.real,imag+com1.imag); 23 } 24 25 void Complex::showSum() 26 { 27 std::cout<<real; 28 if(imag>0) 29 std::cout<<"+"; 30 if(imag!=0) 31 std::cout<<imag<<"i"<<std::endl; 32 } 33 34 35 int main() 36 { 37 Complex com1(10,10),com2(20,-20),sum; 38 sum=com1+com2;//或sum=com1.operator+(com2) 39 sum.showSum();//输出复数相加结果 40 return0; 41 }
对于双目运算符而言,运算符重载函数的形参中仅为一个参数,它作为运算符的右操作数(如com2对象),而当前对象作为左操作数(如:上述中的com1对象),它是通过this指针隐含传递给成员运算符重载函数的。
ii.成员函数重载单目运算符(++):
1 #include "stdafx.h" 2 #include <iostream> 3 4 5 class Point//坐标类 6 { 7 private: 8 int x; 9 int y; 10 public: 11 Point(int x,int y) 12 { 13 this->x=x; 14 this->y=y; 15 } 16 voidoperator++();//成员函数重载双目运算符++ 17 void showPoint(); 18 }; 19 20 21 void Point::operator++() 22 { 23 ++x; 24 ++y; 25 } 26 27 28 void Point::showPoint() 29 { 30 std::cout<<"("<<x<<","<<y<<")"<<std::endl; 31 } 32 33 int main() 34 { 35 Point point(10,10); 36 ++point;//或point.operator++() 37 point.showPoint();//输出坐标值 38 39 return0; 40 }
对于单目运算符而言,当前对象作为运算符的操作数。
在运算符重载运用时应该注意以下几个问题:(1)C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符;(2)C++中绝大部分的运算符可重载,除了成员访问运算符.,成员指针访问运算符.*,作用域运算符::,长度运算符sizeof以及条件运算符?:;(3)重载后不能改变运算符的操作对象(操作数)的个数。如:"+"是实现两个操作数的运算符,重载后仍然为双目运算符;(4)重载不能改变运算符原有的优先级;(5)重载不能改变运算符原有结合的特性。比如:z=x/y*a,执行时是先做左结合的运算x/y,重载后也是如此,不会变成先做右结合y*a;(6)运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质;(7)从上述的示例中可以看到双目运算符可以被重载为友元函数也可以重载为成员函数,但有一种情况,只能使用友元函数,是什么情况呢?我举个例子:
1 class Complex //复数类 2 { 3 private://私有 4 double real;//实数 5 double imag;//虚数 6 public: 7 Complex(double real=0,double imag=0) 8 { 9 this->real=real; 10 this->imag=imag; 11 } 12 Complex operator+(int x); 13 }; 14 15 Complex Complex::operator+(int x) 16 { 17 return Complex(real+x,imag); 18 } 19 20 int main() 21 { 22 Complex com1(5,10),total; 23 total=com1+5; 24 25 return0; 26 }
如果我们把上述main()主函数实现部分里的total=com1+5改为total=5+com1;那么程序就会报错(没有与这些操作数匹配的 "+" 运算符),因为左操作数5不是该复数类的对象,不能调用相应的成员函数Complex operator+(int x),所以编译错误。但如果我们定义一下两个友元函数就能解决上述的问题:
friend Complex operator+(Complex com1,int x);
friend Complex operator+(int x,Complex com1);
3.最后还是一样,我将用一个示例来总结一下今天所讲的内容(开发工具:vs2010):
1 #include "stdafx.h" 2 #include <iostream> 3 4 class Complex //复数类 5 { 6 private://私有 7 double real;//实数 8 double imag;//虚数 9 public: 10 Complex(double real=0,double imag=0) 11 { 12 this->real=real; 13 this->imag=imag; 14 } 15 Complex operator+(Complex com1);//成员函数重载双目运算符+ 16 //或friend Complex operator+(Complex com1,Complex com2);//友元函数重载双目运算符+ 17 friend Complex operator+(Complex com1,int x);//友元函数重载双目运算符+ 18 //或Complex operator+(int x); 19 friend Complex operator+(int x,Complex com1);//友元函数重载双目运算符+ 20 void showSum(); 21 }; 22 23 24 Complex Complex::operator+(Complex com1) 25 { 26 return Complex(real+com1.real,imag+com1.imag); 27 } 28 29 Complex operator+(Complex com1,int x)//左操作数类型为复数,右操作数的类型为整数 30 { 31 return Complex(com1.real+x,com1.imag); 32 } 33 34 Complex operator+(int x,Complex com1)//左操作数类型为整数,右操作数的类型为复数 35 { 36 return Complex(x+com1.real,com1.imag); 37 } 38 39 void Complex::showSum() 40 { 41 std::cout<<real; 42 if(imag>0) 43 std::cout<<"+"; 44 if(imag!=0) 45 std::cout<<imag<<"i"<<std::endl; 46 } 47 48 class Point//坐标类 49 { 50 private: 51 int x; 52 int y; 53 public: 54 Point(int x,int y) 55 { 56 this->x=x; 57 this->y=y; 58 } 59 friend voidoperator++(Point& point);//友元函数重载单目运算符++ 60 Point operator++();//成员函数重载双目运算符++ 61 void showPoint(); 62 }; 63 64 voidoperator++(Point& point)//友元运算符重载函数 65 { 66 ++point.x; 67 ++point.y; 68 } 69 70 Point Point::operator++() 71 { 72 ++x; 73 ++y; 74 return*this;//返回当前对象 75 } 76 77 78 void Point::showPoint() 79 { 80 std::cout<<"("<<x<<","<<y<<")"<<std::endl; 81 } 82 83 int main() 84 { 85 //两个复数相加 86 std::cout<<"两个复数相加:"<<std::endl; 87 88 Complex com1(10,10),com2(20,-20),sum; 89 sum=com1+com2;//或sum=com1.operator+(com2) 90 std::cout<<"(10+10i)+(20-20i)="; 91 sum.showSum();//输出复数相加结果 92 93 //三个复数相加 94 std::cout<<"三个复数相加:"<<std::endl; 95 96 sum=com1+com2+com2; 97 std::cout<<"(10+10i)+(20-20i)+(20-20i)="; 98 sum.showSum(); 99 100 //整数和复数相加 101 std::cout<<"整数和复数相加:"<<std::endl; 102 103 Complex com3(5,10),total; 104 total=com3+5;//或total=operator+(com1,5); 105 std::cout<<"(5+10i)+5="; 106 total.showSum(); 107 108 total=5+com3;//或total=operator+(5,com1); 109 //只能用友元函数来重载运算符 110 std::cout<<"5+(5+10i)="; 111 total.showSum(); 112 113 //单目运算符++重载 114 std::cout<<"单目运算符++重载:"<<std::endl; 115 116 //注意:下述实现部分不能只用一个++point会造成二义性 117 Point point(10,10); 118 //调用友元函数 119 operator++(point);//或++point 120 std::cout<<"调用友元函数:++(10,10)="; 121 point.showPoint();//输出坐标值 122 123 //调用成员函数 124 point=point.operator++();//或++point; 125 std::cout<<"调用成员函数:++(10,10)="; 126 point.showPoint(); 127 128 return0; 129 }
结果:
在上述示例代码中,调用运算符重载函数时,也可以以operator+(com1,com2)的形式来调用,实际上com1+com2在程序解释时也是转化成前者一样的形式。但是直接用com1+com2的形式更加符合人的书写习惯。
2.上述示例中的运算符重载函数是不属于任何的类,是全局的函数。因为在Complex类(复数类)中的数据成员是公有的性质,所以运算符重载函数可以访问。但如果定义为私有的呢,那该怎么办。其实,在实际的运算符重载函数声明当中,要不定义其为要操作类的成员函数或类的友元函数。
(1)运算符重载函数作为类的友元函数的形式:
class 类名
{
friend 返回类型 operator运算符(形参表);
}
类外定义格式:
返回类型 operator运算符(参数表)
{
函数体
}
友元函数重载双目运算符(有两个操作数,通常在运算符的左右两则),参数表中的个数为两个。若是重载单目运算符(只有一个操作数),则参数表中只有一参数。
i.友元函数重载双目运算符(+):
View Code
结果:
ii.友元函数重载单目运算符(++):
View Code
结果:
运算符重载函数可以返回任何类型,甚至是void,但通常返回类型都与它所操作的类类型一样,这样可以使运算符使用在复杂的表达式中。比如把上述双目运算符重载函数示例代码中main()主函数里的com1+com2改为com1+com2+com2,那么结果又会不一样了。像赋值运算符=、下标运算符[]、函数调用运算符()等是不能被定义为友元运算符重载函数。同一个运算符可以定义多个运算符重载函数来进行不同的操作。
(2)运算符重载函数作为类的成员函数的形式:
class 类名
{
返回类型 operator 运算符(形参表);
}
类外定义格式:
返回类型 类名:: operator 运算符(形参表)
{
函数体;
}
对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。同样的是重载,为什么和友元函数在参数的个数上会有所区别的。原因在于友元函数,没有this指针。
i.成员函数重载双目运算符(+):
View Code
对于双目运算符而言,运算符重载函数的形参中仅为一个参数,它作为运算符的右操作数(如com2对象),而当前对象作为左操作数(如:上述中的com1对象),它是通过this指针隐含传递给成员运算符重载函数的。
ii.成员函数重载单目运算符(++):
View Code
对于单目运算符而言,当前对象作为运算符的操作数。
在运算符重载运用时应该注意以下几个问题:(1)C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符;(2)C++中绝大部分的运算符可重载,除了成员访问运算符.,成员指针访问运算符.*,作用域运算符::,长度运算符sizeof以及条件运算符?:;(3)重载后不能改变运算符的操作对象(操作数)的个数。如:"+"是实现两个操作数的运算符,重载后仍然为双目运算符;(4)重载不能改变运算符原有的优先级;(5)重载不能改变运算符原有结合的特性。比如:z=x/y*a,执行时是先做左结合的运算x/y,重载后也是如此,不会变成先做右结合y*a;(6)运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质;(7)从上述的示例中可以看到双目运算符可以被重载为友元函数也可以重载为成员函数,但有一种情况,只能使用友元函数,是什么情况呢?我举个例子:
1 class Complex //复数类 2 { 3 private://私有 4 double real;//实数 5 double imag;//虚数 6 public: 7 Complex(double real=0,double imag=0) 8 { 9 this->real=real; 10 this->imag=imag; 11 } 12 Complex operator+(int x); 13 }; 14 15 Complex Complex::operator+(int x) 16 { 17 return Complex(real+x,imag); 18 } 19 20 int main() 21 { 22 Complex com1(5,10),total; 23 total=com1+5; 24 25 return0; 26 }
如果我们把上述main()主函数实现部分里的total=com1+5改为total=5+com1;那么程序就会报错(没有与这些操作数匹配的 "+" 运算符),因为左操作数5不是该复数类的对象,不能调用相应的成员函数Complex operator+(int x),所以编译错误。但如果我们定义一下两个友元函数就能解决上述的问题:
friend Complex operator+(Complex com1,int x);
friend Complex operator+(int x,Complex com1);
3.最后还是一样,我将用一个示例来总结一下今天所讲的内容(开发工具:vs2010):
View Code
结果:
在定义了重载运算符的函数后,可以说: 函数operator+重载了运算符+。 为了说明在运算符重载后,执行表达式就是调用函数的过程,可以把两个整数相加也想像为调用下面的函数: int operator + (int a,int b) {return (a+b);}
如果有表达式5+8,就调用此函数,将5和8作为调用函数时的实参,函数的返回值为13。这就是用函数的方法理解运算符。可以在例10.1程序的基础上重载运算符“+”,使之用于复数相加。
例10.2 改写例10.1,重载运算符“+”,使之能用于两个复数相加。 #include <iostream> using namespace std; class Complex { public: Complex( ){real=0;imag=0;} Complex(double r,double i){real=r;imag=i;} Complex operator+(Complex &c2);//声明重载运算符的函数 void display( ); private: double real; double imag; }; Complex Complex∷operator+(Complex &c2) //定义重载运算符的函数 { Complex c; c.real=real+c2.real; c.imag=imag+c2.imag; return c; }
void Complex∷display( ) { cout<<″(″<<real<<″,″<<imag<<″i)″<<endl; }
int main( ) { Complex c1(3,4),c2(5,-10),c3; c3=c1+c2; //运算符+用于复数运算 cout<<″c1=″;c1.display( ); cout<<″c2=″;c2.display( ); cout<<″c1+c2=″;c3.display( ); return 0; } 运行结果与例10.1相同: c1=(3+4i) c2=(5-10i) c1+c2=(8,-6i)
请比较例10.1和例10.2,只有两处不同:
- 在例10.2中以operator+函数取代了例10.1中的complex_add函数,而且只是函数名不同,函数体和函数返回值的类型都是相同的。
- 在main函数中,以“c3=c1+c2;”取代了例10.1中的“c3=c1.complex_add(c2);”。在将运算符+重载为类的成员函数后,C++编译系统将程序中的表达式c1+c2解释为
c1.operator+(c2) //其中c1和c2是Complex类的对象 即以c2为实参调用c1的运算符重载函数operator+(Complex &c2),进行求值,得到两个复数之和。
虽然重载运算符所实现的功能完全可以用函数实现,但是使用运算符重载能使用户程序易于编写、阅读和维护。在实际工作中,类的声明和类的使用往往是分离的。假如在声明Complex类时,对运算符+,-,*,/都进行了重载,那么使用这个类的用户在编程时可以完全不考虑函数是怎么实现的,放心大胆地直接使用+,-,*,/进行复数的运算即可,十分方便。 对上面的运算符重载函数operator+还可以改写得更简练一些: Complex Complex∷operator + (Complex &c2) {return Complex(real+c2.real, imag+c2.imag);}
需要说明的是: 运算符被重载后,其原有的功能仍然保留,没有丧失或改变。通过运算符重载,扩大了C++已有运算符的作用范围,使之能用于类对象。
运算符重载对C++有重要的意义,把运算符重载和类结合起来,可以在C++程序中定义出很有实用意义而使用方便的新的数据类型。运算符重载使C++具有更强大的功能、更好的可扩充性和适应性,这是C++最吸引人的特点之一。
- C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。
- C++允许重载的运算符。
C++中绝大部分的运算符允许重载,不能重载的运算符只有5个: . (成员访问运算符) .* (成员指针访问运算符) ∷ (域运算符) sizeof(长度运算符) ?: (条件运算符)
|
请发表评论