1.2k likes | 1.26k Views
第五章 多态性. 5.1 编译时的多态性与运行时的多态性. 多态性是指用 同一个名字 定义不同的函数,这些函数 执行不同 但又类似的 操作 。 联编的概念: 一个源程序经过编译、连接、成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程。 静态联编(前期联编) 静态联编要求在程序编译时就知道调用哪个函数,就决定如何实现某一动作。 动态联编(后期联编、滞后联编) 一直要到程序运行时才能确定调用哪个函数。系统在运行时才动态完成的联编。
E N D
5.1 编译时的多态性与运行时的多态性 多态性是指用同一个名字定义不同的函数,这些函数执行不同但又类似的操作。 联编的概念: 一个源程序经过编译、连接、成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程。 静态联编(前期联编) 静态联编要求在程序编译时就知道调用哪个函数,就决定如何实现某一动作。 动态联编(后期联编、滞后联编) 一直要到程序运行时才能确定调用哪个函数。系统在运行时才动态完成的联编。 静态联编支持的多态性称为编译时多态性,也称静态多态性。在C++中,编译时多态性是通过函数重载和运算符重载实现的。 动态联编支持的多态性称为运行时多态性,也称动态多态性。在C++中,运行 时多态性是通过继承和虚函数来实现的。
5.2 函数重载 Void main() { point p(20,20); circle c(8,8,30); cout<<p.area()<<endl; //执行基类point中的area()的函数 cout<<c.area()<<endl; //执行派生类circle中的area()函数 cout<<c.point::area()<<endl;//执行基类point中的area()的函数 } 程序运行结果为: 0 2827.439941 0 编译时多态性可以通过函数重载来实现。函数重载的意义在于它能用同一个名字访问一组相关的函数,也就是说,能使用户为某一类操作取一个通用的名字,而由编译程序来选择具体由哪个函数来执行,因而有助于解决程序的复杂性问题。普通成员函数和构造函数都可以重载 例5.1 基类和派生类中函数重载的例子 #include <iostream.h> class point{ int x,y; public: point(int a,int b){x=a;y=b;} float area(){return 0.0;} //基类中的area函数 }; class circle:public point{ int radius; public: circle(int x,int y,int rad):point(x,y){radius=rad;} float area(){return 3,1416*radius*radius;}//派生类中的area函数 };
说明: 在基类和派生类中的函数重载有两种情况: 1.参数有所差别的重载,这种重载函数的定义和调用方法在前面章节已进行了介绍;在编译时根据参数不同来区分函数重载。 2.函数所带的参数完全相同,只是它们属于不同的类。这时要人工干预,以下两种方法可以使编译时区别这类函数: (1)使用对象名加以区分。例如:p.area()和c.erea()分别调用类point的area()函数和类circle的area()函数。 (2)使用类名加以区分。例如:point::area()和circle::area()分别调用类point和类circle的area()函数。
5.3 运算符重载 在C++中,除了可以对函数重载外,还可以对大多数运算符实施重载。 运算符重载通过创建运算符函数operator()来实现。运算符函数定义了重载的运算符将要进行的新的操作,这种操作通常作用在一个类上。 函数operator()可以 不是类的成员函数是类的成员函数类的友元函数
5.3.1 类以外的运算符重载 对基本的数据类型,C++提供了许多预定义的运算符,如“+”、“-”、“*”、“/”、“=”等,若有一个复数类 complex: class complex{ public: double real,imag; complex(double r=0,double i=0) {real=r;imag=i;} }; 若要把类complex的两个对象com1和com2加在一起,下面的语句是不能实现的: void main() { complex com1(1.1,2.2), com2(3.3,4.4), total; total=com1+com2; //错误 //... } 错误原因是类complex的类型不是基本数据类型,而是用户自定义的数据类型。C++还是无法直接将两个complex类对象相加。
运算符函数 为了表达上的方便,人们希望预定义的内部运算符(如“+”、“-”、“*”、“/”等)在特定类的对象上以新的含义进行解释,如希望能够实现total=com1+com2,这就需要用重载运算符来解决。 C++为运算符重载提供了一种方法,即在进行运算符重载时,必须写一个运算符函数,其名字为operator后随一个要重载的运算符。例如,要重载“+”号,应该写一个名字为 operator+ 的函数。 表5.1 运算符函数
运算符函数operator+() 在编译时遇到名为operator@的运算符函数(@表示所要重载的运算符),就检查传递给函数的参数的类型。如果编译器在一个运算符的两边看到自定义的数据类型,就执行用户自己的函数,而不是常规运算符。 若要将上述类complex的两个对象相加,只要写一个运算符函数operator+() complex operator+(complex om1,complex om2) { complex temp; temp.real=om1.real+om2.real; temp.imag=om1.imag+om2.imag; return temp; } 我们就能方便的使用语句 total=com1+com2; 将类complex的两个对象com1和com2相加。 也可以使用以下的调用语句,将两个complex类对象相加:total=operator+(com1,com2); 这两个调用语句是等价的,但显然后者不如前者简明和方便。
例 5.2 运算符函数operator+()将两个complex类对象相加程序 #include <iostream.h> class complex{ public: double real; double imag; complex(double r=0,double i=0) {real=r;imag=i;} }; complex operator+(complex co1,complex co2) { complex temp; temp.real=co1.real+co2.real; temp.imag=co1.imag+co2.imag; return temp; } Void main() {complex com1(1.1,2.2),com2(3.3,4.4), total1, total2; total1=operator+(com1,com2); //调用运算符函数operator+()的第一种方式 cout<<"real1="<<total1.real<<” "<<"imag1="<<total1.imag<<endl; total2=com1+com2;//调用运算符函数operator+()的第二种方 cout<<"real2="<<total2.real<<” "<<"imag2="<<total2.imag<<endl; } 程序运行结果为: real1=4.4 imag1=6.6 real2=4.4 imag2=6.6
说明: (1)重载运算符与预定义运算符的使用方法完全相同,它不能改变原有运算符的参数个数(单目或双目),也不能改变原有的优先级和结合性。 (2)在C++中,大多数系统预定义的运算符都能被重载,例如+ - * / % ^ & |~ ! = < > += -= *=/= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- [] () new delete也有一些运算符是不能被重载的,如:. .* :: ?:预处理符号#和##也不能重载。
5.3.2 成员运算符函数 运算符函数可以定义为类的成员(称为成员运算符函数) 1. 成员运算符函数定义的语法形式 成员运算符函数在类中的声明格式为: class X{ //... type operator@(参数表); //... }; 其中type为函数的返回类型,@为所要重载的运算符符号,X是重载此运算符的类名,参数表中罗列的是该运算符所需要的操作数。 成员运算符函数定义的更一般形式为: type X::operator @(参数表) { //函数体 } 在成员运算符函数的参数表中,若运算符是单目的,则参数表为空;若运算符是双目的,则参数表中有一个操作数。
2.双目运算符重载 对双目运算符而言,成员运算符函数的参数表中仅有一个参数,它们为运算符的右操作数,此时当前对象作为运算符的左操作数,它们通过this指针隐含地传递给函数的。 例如: class X{ //... int operator +(X a); }; 在类X中声明了重载“+”的成员运算符函数,返回类型为int,它具有两个操作数,一个是当前对象,另一个是对象a。 例 5.3 用双目运算符函数进行复数运算的例子 两个复数a+bi和c+di进行加、减、乘、除的方法如下: 加法:(a+bi)+(c+di)=(a+c)+(b+d)i 减法:(a+bi)-(c+di)=(a-c)+(b-d)i 乘法:(a+bi)*(c+di)=(ac-bd)+(ad+bc)i 除法:(a+bi)/(c+di)=((a+bi)*(c-di))/(c*c+d*d)
一个复数类complex,类中含有两个数据成员,即复数的实数部分一个复数类complex,类中含有两个数据成员,即复数的实数部分 #include <iostream.h> class complex{ private: double real; //复数的实数部分 double imag; //复数的虚数部分 public: complex(double r=0.0,double i=0.0); //构造函数 void print(); //显示输出复数 complex operator +(complex c); //重载复数“+”运算符 complex operator -(complex c); //重载复数“-”运算符 complex operator *(complex c); //重载复数“*”运算符 complex operator /(complex c); //重载复数“/”运算符 }; complex::complex(double r,double i) //定义构造函数 {real=r;imag=i;}
接1 例5.3 complex complex::operator +(complex c) //重载“+”定义 { complex temp; temp.real=real+c.real; temp.imag=imag+c.imag; return temp; } complex complex::operator -(complex c) //重载“-”定义 { complex temp; temp.real=real-c.real; temp.imag=imag-c.imag; return temp; }
接2 例5.3 complex complex::operator *(complex c) //重载“*”定义 { complex temp; temp.real=real*c.real-imag*c.imag; temp.imag=imag*c.real+real*c.imag; return temp; } complex complex::operator /(complex c) //重载“/”定义 { complex temp; double t; t=1/(c.real*c.real+c.imag*c.imag); temp.real=(real*c.real+imag*c.imag)*t; temp.imag=(c.real*imag-real*c.imag)*t; return temp; }
接3 例5.3 void complex::print() //显示复数的实数部分和虚数部分 { cout<<real; if(imag>0)cout<<"+"; if(imag!=0)cout<<imag<<"i\n"; } void main() { complex A1(2.3,4.6),A2(3.6,2.8),A3,A4,A5,A6; //定义六个复数类对象 A3=A1+A2; //复数相加 A4=A1-A2; //复数相减 A5=A1*A2; //复数相乘 A6=A1/A2; //复数相除 A1.print(); //输出复数A1 A2.print(); //输出复数A2 A3.print(); //输出复数相加的结果A3 A4.print(); //输出复数相减的结果A4 A5.print(); //输出复数相乘的结果A5 A6.print(); //输出复数相除的结果A6 } 程序运行结果如下: 2.3+4.6i 3.6+2.8i 5.9+7.4i -1.3+1.8i -4.6+23i 1.017308+0.486538i
在主函数main()中的语句 A3=A1+A2; A4=A1-A2; A5=A1*A2; A6=A1/A2; 所用的运算符“+”、“-”、“*”、“/”是重载后的运算符。程序执行到这四条语句时C++将其解释为: A3=A1.operator +(A2); A4=A1.operator -(A2); A5=A1.operator *(A2); A6=A1.operator /(A2); 由此我们可以看出,成员运算符函数operator @实际上是由双目运算符左边的对象A1调用的,尽管双目运算符函数的参数表只有一个操作数A2,但另一个操作数是由对象A1通过this指针隐含地传递的。以上两组语句的执行结果是完全相同的。一般而言,采用成员函数重载双目运算符@后,可以用以下两种方法来使用: aa @ bb; //隐式调用 aa.operator @(bb); //显式调用 成员运算符函数operator @ 所需的一个操作数由对象aa通过this指针隐含地传递。因此,它的参数表中只有一个操作数bb。
3.单目运算符重载 对单目运算符而言,成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。 例5.4 重载单目运算符“++”的例子 #include <iostream.h> class coord{ int x,y; public: coord(int i=0,int j=0); void print(); coord operator ++(); //声明成员运算符函数operator ++() }; coord::coord(int i,int j) {x=i;y=j;} void coord::print() {cout<<" x:"<<x<<",y:"<<y<<endl;} coord coord::operator ++() //定义成员运算符函数operator ++() { ++x; ++y; return *this; }
接1 例5.4 void main() { coord ob(10,20); ob.print(); ++ob; //隐式调用成员运算符函数operator++( ) ob.print(); ob.operator ++(); //显式调用成员运算符函数operator ++() ob.print(); } 程序运行结果如下: x:10, y:20 x:11, y:21 x;12, y=22 本例主函数main()中调用成员运算符函数operator++()采用了两种方法:++ob; 与ob.operator++() 两者是等价的,其执行效果是完全相同的。 从本例还可以看出,当成员函数重载单目运算符时,没有参数被显式地传递给成员运算符函数。参数是通过this指针隐含地传递给函数的。
一般而言 采用成员函数重载单目运算符时,可以用以下两种方法来使用: @aa; //隐式调用aa.operator @(); //显式调用 成员运算符函数operator @所需的一个操作数由对象aa通过this指针隐含地传递。因此,在它的参数表中没有参数。 说明: (1)运算符重载函数operator @()可以返回任何类型,甚至可以是void类型但通常返回类型与它所操作的类的类型相同,这样可以使重载运算符用在复杂的表达式中。例如,在例5.3中,可以将几个复数连续进行加、减、乘、除的运算。 (2)在重载运算符时,运算符函数所作的操作不一定要保持C++中该运算符原有的含义。例如,可以把加运算符重载成减操作,但这样容易造成混乱。所以保持原含义,容易被接受,也符合人们的习惯。 (3)在C++中,用户不能定义新的运算符,只能从C++已有的运算符中选择一个恰当的运算符重载。 (4)C++编译器根据参数的个数和类型来决定调用哪个重载函数。因此,可以为同一个运算符定义几个运算符重载函数来进行不同的操作
5.3.3 友元运算符函数 把运算符函数定义成某个类的友元函数,称为友元运算函数符. 1.友元运算符函数定义的语法形式 友元运算符函数与成员运算符函数不同,后者本身是类中的成员函数,而前者是类的友元函数。 友元运算符函数在类的内部声明格式如下: friend type operator @ (参数表); 与成员运算符函数的声明格式相比较,只是在前面多了一个关键字friend。 定义友元运算符函数与定义一般的友元函数相似,其格式如下: type operator @ (参数表) { //函数体 } 由于友元函数不是类的成员函数,所以不需要缀上类名。与成员运算符函数不同,友元运算符函数是不属于任何类对象的,它没有this指针。若重载的是双目运算符,则参数表中有两个操作数;若重载的是单目运算符,则参数表中只有一个操作数。
2.双目运算符重载 当用友元函数重载双目运算符时,两个操作数都要传给运算符函数。 例5.5 友元函数重载双目运算符 在例5.3中,用成员运算符函数进行复数运算,现在我们采用友元运算符函数。 #include<iostream.h> class complex{ private: double real; double imag; public: complex(double r=0.0,double I=0.0) {real=r; imag=i;} void print(); friend complex operator + (complex a,complex b); //用友元运算符函数重载复数 “+” friend complex operator – (complex a,complex b); //用友元运算符函数重载复数 “-” friend complex operator * (complex a,complex b); //用友元运算符函数重载复数 “*” friend complex operator / (complex a,complex b); //用友元运算符函数重载复数 “/” };
接1 例5.5 complex operator +(complex a,complex b) //重载“+”定义 { complex temp; temp.real=a.real+b.real; temp.imag=a.imag+b.imag; return temp; } complex operator-(complex a,complex b) //重载“-”定义 { complex temp; temp.real=a.real-b.real; temp.imag=a.imag-b.imag; return temp; } complex operator (complex a,complex b) //重载“*”定义 { complex temp; temp.real=a.real*b.real-a.imag*b.imag; temp.imag=a.real*b.imag+a.imag*b.real; return temp; }
接2 例5.5 complex operator/(complex a,complex b) //重载“/”定义 { complex temp; double t; t=1/(b.real*b.real+b.imag*b.imag); temp.real=(a.real*b.real+a.imag*b.imag)*t; temp.imag=(b.real*a.imag-a.real*b.imag)*t; return temp; } void complex::print() //输出显示复数 { cout<<real; if(imag>0) cout<<“+”; if(imag!=0) cout<<imag<<“i\n” }
void main() { complexA1(2,3,4.6),A2(3,6,2.8), A3,A4,A5,A6;//定义6个复数对象 A3=A1+A2; //复数相加 A4=A1-A2; //复数相减 A5=A1*a2; //复数相乘 A6=A1/A2; //复数相除 A1.print(); //输出复数A1 A2.print(); //输出复数A2 A3.print();//输出复数相加结果A3 A4.print();//输出复数相减结果A4 A5.print();//输出复数相乘结果A5 A6.print();//输出复数相除结果A6 } 程序运行结果如下: 2.3+4.6i 5.9+7.4i 3.6+2.8i -1.3+1.8i -4.6+23i 1.017308+0.486538i
3.单目运算符重载 mian() { nclass ob1(10,20),ob2; ob1.show(); ob2=-ob1; ob2.show(); return 0; } 用友元函数重载单目运算符时,需要一个显式的操作数。 例5.6 用友元函数重载单目运算符“-”例子。 #include<iostream.h> class nclass{ int a,b; public: nclass(int x=0,int y=0) {a=x;b=y;} // nclass的构造函数 friend nclass operator-(nclass obj);//声明重载单目运算符“-” void show(); }; nclass operator-(nclass obj)//定义重载单目运算符“-” { obj.a=-obj.a; obj.b=-obj.b; return obj; } void nclass::show() { cout<<“a””<<a“<”=”<<b<<end } 程序运行结果如下: a=10 b=20 a=-10 b=-20
使用友元函数重载“+ +”,“- -”这样的运算符,可能会出现的问题 在例5.4用成员函数重载“++”的成员运算符函数: coord coord::operator++() { ++x; ++y; return *this; }; 由于所的成员函数都有一个this指针,this指针指向该函数所属类对象的指针,因此对私有数据的任何修改都将影响实际调用成员运算符函数的对象,因而例5.4的执行情况是完全正确的。 如果用友元函数改写上面的运算符函数: coord operator++(coord op) { ++x; // ++op.x; ++y; // ++op.y; return op; };
由于友元函数没有this指针,所以不能引用this指针所指的对象。这个函数是通过传值的方法传递参数的,函数体内对op的所以修改都无法传到函数体外。因此,在operator++函数中,任何内容的改变不会影响产生调用的操作数,也就是说。实际上对象x和y并未增加。由于友元函数没有this指针,所以不能引用this指针所指的对象。这个函数是通过传值的方法传递参数的,函数体内对op的所以修改都无法传到函数体外。因此,在operator++函数中,任何内容的改变不会影响产生调用的操作数,也就是说。实际上对象x和y并未增加。 为了解决以上问题,使用友元函数重载单目运算符“++”或“—”时,应采用引用参数传递操作数,这样函数参数的任何改变都影响产生调用的操作数,从而保持了两个运算符的原意。
程序运行的结果如下: x:10,y:20 x:11,y:21 x:12,y:22 例5.7 使用友元运算符函数重写例5.4的程序。 #include<iostream.h> class coord{ int x,y; public: coord(int i=0,int j=0) {x=i;y=j;} void print() {cout<<“ x:”<<x<<“ ,y:“<<y<<endl;} friend coord operator ++(corrd&op); //声明友元运算符函数,形参为引用 }; coord operator ++(coord&op) //定义友元运算符函数 { ++op.x; ++op.y; return op; } Void main() { coord ob(10,20); ob.print(); ++ob; //隐式调用友元运算符函数 ob.print(); operator++(ob);//显式调用友元运算符函数 ob.print(); }
当用友元函数重载单目运算符时,参数表中有一个操作数当用友元函数重载单目运算符时,参数表中有一个操作数 一般而言,采用友元函数重载单目运算符@后,可以采用以下两种方法来使用: @aa; //隐式调用 operator@(aa); //显式调用 说明: (1)不能用友元函数重载的运算符是:=,(),[ ],-> 其余的运算符都可以使用友元函数来实现重载。 (2)由于单目运算符“-”可不改变操作数自身的值,所以在例5.6重载单目运算符“-” 的友元运算符函数的原型可写成: friend nclass operator –(nclass obj); 通过传值的方式传递参数。
5.3.4成员运算符函数与友元运算符函数的比较 我们对成员运算符函数与友元运算符函数作一比较。 (1)对双目运算符,成员运算符函数带有一个参数,友元运算符函数带有两个参数对单目运算符,成员运算符函数不带参数,友元运算符函数带有一个参数。 (2)双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但有一种情况,必须使用友元函数。 例如,在例5.6的 类nclass 中,用成员运算符函数重载“+”运算 nclass nclass::operator+(int x) { nclass temp; temp.a=a+x; temp.b=b+x; return temp; }
若类nclass的对象ob要做赋值运算和加法运算,以下是一条正确的语句:若类nclass的对象ob要做赋值运算和加法运算,以下是一条正确的语句: ob=ob+100; 由于对象ob是运算符“+”的左操作数,所以它调用了“+”运算符重载函数,把一个整数加到了对象ob的某些元素上去。然而,下一条语句就不能工作了: ob=100+ob; 不能工作的原因是运算符的左操作数是一个整数,而整数是一个内部数据类型,100不能产生对成员运算符函数的调用。
用两种形式的友元函数来重载运算符函数“+”用两种形式的友元函数来重载运算符函数“+” 就能消除由于运算符“+”的左操作数是内部数据类型而带来的问题,下述程序说明了如何实现。 例5.8用两种形式的友元函数来重载运算符函数“+” #include<iostream.h> class nclass{ int a,b; public: nclass(int x=0,int y=0) {a=x;b=y} friend nclass opterator +(nclass ob,int x); //声明一种友元运算符函数 friend nclass opterator +(int x,nclass ob); //声明另一种友元运算符函数 void show(); };
接1 例5.8 nclass operator +(nclass ob,int x) //定义常规类型在右边的友元运算符函数 { nclass temp; temp.a=a+x; temp.b=b+x; return temp; } nclass operator +(int x ,nclass ob) //定义常规类型在左边的友元运算符函数 { nclass temp; temp.a=x+ob.a; temp.b=x+ob.b; return temp; } void nclass::show() {cout<<“a=”<<a<<” “<<“b=“<<b<<“\n”; } void main() { nclass ob1(30,40),ob2; ob2=ob1+30; ob2.show(); ob2=50+ob1; ob2.show(); }
(3) (4) (3)成员运算符函数和友元运算符函数都可以用习惯方式调用,也可以用它们专用的方式调用,表5.2列出了一般情况下运算符函数的调用形式。 习惯形式 友元运算符函数调用形式 成员运算符函数调用形式 a+b operator+(a,b) a.operator+(b) -a operator-(a) a.operator-() a++ operator++(a,0) a.operator++(0) (4) c++的大部分运算符既可说明为成员运算符函数,又可说明为友元运算符函数。究竟选择哪一种运算符函数好一些,没有定论,这主要取决于实际情况和程序员的习惯。 一般而言,对于双目运算符,将它重载为一个友元运算符函数比重载为一个成员运算符函数便于使用。若一个运算符的操作需要修改类对象的状态,则选择成员运算符函数较好。如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载必须用友元函数,而不能用成员函数。
5.3.5“++”和“- -”的重载 运算符“++”和“- -”放置在变量的前面与后面,其作用是有区别的。但是C++2.1之前的版本在重载“++”(或“- -”)时,不能显式地区分是前缀还是后缀方式。也就是说,在例5.7的main()函数中,以下两条语句是完全相同的: ob++; ++ob; 在C++2.1及以后的版本中,编辑器可以通过在运算符函数参数表中是否插入关键字 int 来区分这两种方式。 (1) 前缀方式 ++ob,可以用运算符函数重载为: ob.operator++(); //成员函数重载 operator++(X&ob); //友元函数重载,其中ob为X类对象 (2)后缀方式 ob++,可以用运算符函数重载为: ob.operator++(int); //成员函数重载 operator++(X&ob,int); //友元函数重载 调用时,参数 int 一般用0值。 重载运算符“- -”也可用类似的方法。
例5.9包含“++”和“--”两种重载运算符 #include<iostream.h> #include<iomanip.h> class over{ int i1, i2, i3; public: void init(int I1, int I2, int I3) {i1=I1; i2=I2; i3=I3;} //给数据成员赋初值 void print(); {cout<<“ i1:”<<i1<<” i2:”<<i2<<” i3:”<<i3<<endl; //显示输出数据成员 over operator++(); //成员函数重载“++”(前缀方式)声明 over operator++(int); //成员函数重载“++”(后缀方式)声明 friend over operator- -(over&) ; //友元函数重载“- -”(前缀方式)声明 friend over operator- -(over&,int); //友元函数重载“- -”(后缀方式)声明 };
接1 例5.9 over over::operator++() //定义成员函数重载“++”(前缀方式) { ++i1; ++i2; ++i3; return *this: } over over::operator++(int) //定义成员函数重载++”(后缀方式) { i1++; i2++; i3++; return *this; } over operator- -(over&op) //定义友元函数重载“- -”(前缀方式) { - -op.i1; - -op.i2; - -op.i3; return op; } over operator- -(over&op, int)//定义友元函数重载“- -”(后缀方式) { op.i1- -; op.i2- -; op.i3- -; return op; }
接2 例5.9 程序运行结果如下: i1:5 i2:3 i3:6 i1:3 i2:6 i3:10 i1:7 i2:2 i3:7 i1:2 i2:5 i3:6 --------------- i1:6 i2:4 i3:7 i1:4 i2:7 i3:11 i1:6 i2:1 i3:6 i1:1 i2:4 i3:5 以上例子中,使用成员函数分别以前缀方式和后缀方式重载了运算符“++”,使用友元函数分别以前缀方式和后缀方式重载了运算符“- -”。 Void main() { over obj1, obj2, obj3, obj4; obj1.init(4,2,5); obj2.init(2,5,9); obj3.init(8,3,8); obj4.init(3,6,7); ++obj1; //隐式调用over operator++() obj2++; //隐式调用over operator++(int) --obj3; //隐式调用over operator- -(over&) obj4--; //隐式调用over operator- -(over&,int) obj1.print(); obj2.print(); obj3.print(); obj4.print(); cout<<”---------------\n”; obj1.operator++(); //显式调用,意为++obj1 obj2.operator++(0); //显示调用,意为obj2++ operator--(obj3); //显示调用,意为- -obj3 operator--(obj4,0); //显示调用,意为obj4- - obj1.print(); obj2.print(); obj3.print(); obj4.print(); }
5.3.6赋值运算符“=”的重载 对任一类X,如果没有用户自定义的赋值运算符函数,那么系统将自动地为其生成一个缺省的赋值运算符函数,例如: X﹠X::operator=(const X ﹠source) { //成员间赋值 } 若obj1和obj2是类X的两个对象,obj2已被创建,则编译程序遇到如下语句: obj1=obj2; 就调用缺省的赋值运算符函数,将对象obj2的数据成员逐域拷贝到对象obj1中。 通常,缺省的赋值运算符函数是能够胜任工作的。但在某些特殊情况下,如类中有指针类型时,使用缺省赋值运算符函数会产生错误。请看下面的例子。
例5.10 #include<iostream.h> #include<string.h> class string{ char *ptr; public: string(char *s) { ptr=new char[strlen(s)+1]; strcpy(ptr,s); } ~string() { delete ptr; } void print() { cont<<ptr<<endl; } }; 程序运行结果如下: p2:chen p1: ♀♀ Null Point assignment void main() { string p1("chen"); { string p2(" "); p2=p1; //string 类对象间赋值 cont<<"p2:"; p2.print(); } cout<<"p1:"; p1.print(); }
接1 例5.10 运行上述程序输出p1时发生错误。原因是执行到赋值语句p2=p1时,实际是使对象p2的指针ptr与对象p1的指针ptr指向new开辟的同一个空间。当p2的生命周期(内层的一对花括号间)结束时,系统自动调用析构函数将这一空间撤消。这样,p2中的ptr原来所指向的区域被封锁起来,并不能再被使用,这就产生了指针悬挂问题。这时尽管p1的指针ptr存在,其所指向的空间却无法访问了,如图所示。当p1的生命走其结束时,将再次调用析构函数,释放同一空间,从而导致运行错误。
动态空间1 动态空间2 P2: P1: ptr ptr (a) 执行P2=P1之前 动态空间1 动态空间2 ? P2: P1: ptr ptr (c)p2的生命周期结束后 (b) 执行P2=P1之后
void main() { string p1("chen"); { string p2 (" "); p2=p1; cout<<"p2:"; p2.print(); } cout<<"p1:"; p1.print(); } 显式地定义一个赋值运算符重载函数 例5.11显式地定义一个赋值运算符重载函数,使P1和P2有各自的存储区。 #include<iostream.h> #include<string.h> class string{ char *ptr; public: string(char *) { ptr=new char[strlen(s)+1]; strcpy(ptr,s); } ~string() { delete ptr; } void print() {count<<ptr<<endl;} string &operator=(const string&); //声明赋值运算符重载函数 }; string &string::opertor=(const string &s) //定义赋值运算符重载 { if(this==&s)return *this; delete ptr; ptr=new char[strlen(s.ptr)+1]; strcpy(ptr,s.ptr); return *this; } 程序运行结果如下: p2:chen p1:chen
说明: (1)赋值运算符“=”只能重载为成员函数,而不能把它重载为友元函数,因为若把上述赋值运算符“=”重载为友元函数: friend string &operator=(string &p2,string &p1) 这时,表达式:p1="chen” 将被解释为: operator=(p1,“chen”) 这显然是没有什么问题的, 但对于表达式 "chen"=p1 将被解释为: operator=("chen",p1) 即C++编译器首先将“chen”转换成一个隐藏的string对象,然后使用对象p2引用该隐藏对象,编译器不认为这个表达式是错误的,从而将导致赋值语句上的混乱。因此双目赋值运算符应重载为成员函数的形式,而不能重载为友元函数的形式。 (2)类的赋值运算符“=”可以被重载,但重载了的运算符函数operator=()不能被继承。
5.3.7 函数调用运算符“()”与下标运算符“[]”的重载 1.重载函数调用运算符“()” C++中函数的形参和实参都用“( )”括号括起,函数调用运算符一般使用格式为: 基本表达式(〈表达式表〉) 可以认为函数调用运算符“()”是一个双目运算符,其操作数为〈基本表达式〉即函数名和〈表达式表〉(可能为空)。 我们可以对运算符“()” 进行重载,使它起别的作用。 设obj是类myclass的对象,而且类myclass中定义了重载函数调用运算符“()”,它起的作用是能把对象obj的数据扩大 k 倍和 m 倍。 对应的运算符函数为 myclass operator()(int k, int m) 则函数调用 (把对象obj的数据扩大 10 倍和 100 倍) obj(10,100); 可被解释为: obj.operator()(10,100); 一般的重载运算符“()”: 类X operator()(arg1,arg2,……) obj(arg1,arg2,…………); 可被解释为: obj.operator()(arg1,arg2,……);
重载运算符“()”例 void main() { myclass obj1(10,11.5); obj1.display(); obj1(10,100); //重载运算符“()”的调用,可解 // 释为obj1.perator()(10,.100) obj1.display(); } 我们知道,双目运算符是由左边的对象产生对operator函数调用的。This指针总是指向产生调用的对象。重载运算符“()”的operator函数可以带任何类型的操作数,返回任何有效的类型。下面的程序将帮助我们理解函数调用运算符“()”的重载。 例5.12运算符“()的重载。 #include<iostream.h> class myclass{ int i; float j; public: myclass(int x=0,float y=0) { I=x; j=y; } myclass operator( )( int k, float m ); //声明重载运算符“()”函数 void display() { cout<<I<<” "<<j<<"\n"; } }; myclass myclass::operator( )(int k,float m) //定义运算符“()”函数 { i=i*k; j=j*m; return *this; } 程序运行结果为: 10 11.5 100 1150.0 说明:函数调用运算符“()”必须重载为一个成员函数
2.重载下标.运算符“[ ]” C++中运算符“[ ]”可作数组下标,下标运算符的一般使用格式为: 〈 基本表达式〉 [〈 表达式〉 ] C++把它看成双目运算符,其操作数为〈 基本表达式〉即数组名和〈表达式〉即下标。 我们可以对运算符“[ ]” 进行重载,使它起别的作用。 设obj是类myclass的对象,而且类myclass中定义了重载下标运算符“[ ]”,它起的作用是能把对象obj中的某个数据取下标。 在重载“[ ]”时,C++也把它看成双目运算符,其操作数为〈基本表达式〉和〈表达式〉。 相应的运算符函数为 myclass operator[ ](int sub) 则表达式 obj[5]; //取obj中某个数据的第6个 可被解释为: obj.operator[ ](5); 对运算符 “[ ]” 重载定义只能使用成员函数,其形式如下: 类型 类名::operator[ ] (形参) { 函数体 } 注意:形参只能有一个。
对string数据类型定义以下的“[ ]”运算符重载函数: 例如,可以对string数据类型定义以下的“[ ]”运算符重载函数: class string{ char *ptr; //……………… public: string(const char *); char &operator[ ](int); //……………… }; 其中重载“[ ]”的operator函数定义为: char & string::opertor[ ](int I) { return ptr[I]; } 由于string::operator[ ]( )的返回值在这里是一个char&,所以函数可以被用于一个赋值运算符的任一边,例如: string op= "abcd"; op[1]=op[3]; 相当与:op.operator[ ](1)=op.operator[ ](3); 因而op的新值为 “adcd"。
重载运算符“[ ]”来处理数组成员 例5.13重载运算符“[ ]”来处理例子 #include<iostream.h> #include<iomanip.h> #include<string.h> #include<stdlib.h> chass sales{ char compName[25]; //定义字符串 int divisionTotals[5]; //定义整数数组成员 public: void init(char CN[ ]) { strcpy(compName,CN); } int &operator[ ](int); //重载运算符“[ ]”函数的声明 char *getName(void) { return compName; } }; int &sales::operator[ ](int sub) //定义重载运算符“[ ]”函数 { if (sub<0||sub>4) //检查数据边界 { cerr<<"Bad subscript!"<<sub<<"is not allowed.\n"; exit(1); } return divisionTotals[sub]; }