第8章多态性和虚函数
封装性基础
面向对象系统三特征继承性关键
多态性补充
多态性是指发出同样的消息被不同类型的对象接收时导致完全不同的行为。这里所说的消息主要是指对类的成员函数的调用,而不同的行为是指不同的实现。利用多态性,用户只需发送一般形式的消息,而将所有的实现留给接收消息的对象。
*多态的类型:
简单的多态性是函数重载和运算符重载。
重要的多态性是建立在虚函数的概念和方法上的。
8.1 函数重载
8.2 运算符重载
运算符重载就是赋予已有的运算符多重含义,使它能够用对类的对象进行特定的操作。
8.2.1运算符重载的几个问题
1.C++中不能重载的运算符是:
. , .* ,:: , ?:
2.运算符重载不改变原运算符的优先级和结合性。
3.编译程序对运算符重载的选择,遵循函数重载的原则。
4.重载运算符的限制:
(1)不可臆造新的运算符。
(2)重载运算符坚持4个“不能改变”。
·不能改变运算符操作数的个数;
·不能改变运算符原有的优先级;
·不能改变运算符原有的结合性;
·不能改变运算符原有的语法结构。
5.运算符重载时必须遵循哪些原则:
(1)重载运算符含义必须清楚。
(2)重载运算符不能有二义性。
8.2.2 运算符重载的两种形式
*运算符重载的两种形式:成员函数形式和友元函数形式。1.重载为类的成员函数
例8.2复数类四则运算重载
#include
class complex
{ public:
complex( ) { real=imag=0; }
complex(double r, double i){real=r,imag=i;}
complex operator +(const complex &c);
complex operator -(const complex &c);
complex operator *(const complex &c);
complex operator /(const complex &c);
friend void print(const complex &c);
private:
double real,imag;
};
inline complex complex::operator +(const complex &c) { return complex(real+c.real,imag+c.imag); }
inline complex complex::operator -(const complex &c) { return complex(real-c.real,imag-c.imag); }
inline complex complex::operator *(const complex &c) { return complex(real * c.real-imag * c.imag,real * c.imag+imag * c.real); }
inline complex complex::operator /(const complex &c) { return complex((real * c.real + imag + c.imag)/ (c.real*c.real+c.imag*c.imag),
(imag* c.real - real * c.imag)/(c.real * c.real + c.imag *c.imag)); }
void print(const complex &c)
{ if(c.imag<0)
cout< else cout< } void main( ) { complex c1(2.0,3.0),c2(4.0,-2.0),c3; c3=c1+c2; cout<<"\nc1+c2="; print(c3); c3=c1-c2; cout<<"\nc1-c2="; print(c3); c3=c1*c2; cout<<"\nc1*c2="; print(c3); c3=c1/c2; cout<<"\nc1/c2="; print(c3); c3=(c1+c2)*(c1-c2)*c2/c1; cout<<"\n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout< } 该程序的运行结果为: c1+c2=6+1i c1-c2=-2+5i c1*c2=14+8i c1/c2=0.45+0.8i (c1+c2)*(c1-c2)*c2/c1=9.61538+25.2308i 小结: 1.在程序中,定义了4个成员函数作为运算符重载函数。将运算符重载函数说明为类的成员函数格式如下: 类名 operator 运算符(参数表) 2.程序中出现的表达式:c1+c2 编译程序将给解释为: c1.operator +(c2) 其中,c1和c2是complex类的对象。operator+( )是运算+ 的重载函数。 3.该运算符重载函数仅有一个参数c2。可见,当重载为成员函数时,双目运算符仅有一个参数。对单目运算符,重载为成员函数时,不能再显式说明参数。重载为成员函数时,总时隐含了一个参数,该参数是this指针。this指针是指向调用该成员函数对象的指针。 例8.2时间类 #include class Time { public: Time( ){ hours=minutes=seconds=0;} Time(int h,int m,int s) { hours=h; minutes=m; seconds=s;} Time operator +(Time &c); //+运算符重载 Time operator ++( ); //前置单目运算符重载 Time operator ++(int); //后置单目运算符重载 void ShowTime( ); private: int hours,minutes,seconds; }; Time Time::operator +(Time &c) //+运算符重载 { hours=hours+c.hours; minutes=minutes+c.minutes; seconds=seconds+c.seconds; return Time (*this);} Time Time::operator ++( ) //前置单目运算符重载 { ++seconds; return Time (*this); } Time Time::operator ++(int) //后置单目运算符重载{ seconds++; //注意形参表中的参数 return Time (*this); } void Time::ShowTime( ) { if(seconds>=60) { seconds-=60; minutes++;} if(minutes>=60) { minutes-=60; hours++;} if(hours>=24) hours-=24; cout< } void main( ) { Time t1(2,23,45),t2(12,34,46),t3,t4; t3=t1+t2; t3.ShowTime( ); t1++; t1.ShowTime( ); ++t4; t4.ShowTime( ); } 输出: 14:58:31 14:58:32 0:0:1 在本例中,我们把时间自增前置“++”和后置“++”运算 重载为时间类的成员函数,前置单目运算符和后置单目运算符 重载的最主要的区别就在于重载函数的形参。语法规定,前置 单目运算符重载为成员函数时没有形参,而后置单日运算符重 载为成员函数时需要有一个int型形参。这个int型参数在函 数体中并不使用,纯粹是用来区别前置与后置。因此参数表中 可以只给出类型名,没有参数名。 2.重载为友元函数 当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函 数有一个参数。但是,有些运算符不能重载为友元函数,它们是:=,( ),[ ]和->。 重载为友元函数的运算符重载函数的定义格式如下: friend 类型说明符 operator 运算符(参数表) {……} 例8.4用友元函数代替成员函数,重编例8.2。 #include class complex {public: complex( ) { real=imag=0; } complex(double r,double i) {real=r,imag=i; } friend complex operator+(const complex &c1, const complex &c2); friend complex operator-(const complex &c1, const complex &c2); friend complex operator*(const complex &c1, const complex &c2); friend complex operator/(const complex &c1, const complex &c2); friend void print(const complex &c); private: double real,imag; }; complex operator+(const complex &c1,const complex &c2) { return complex(c1.real+c2.real,c1.imag+c2.imag); } complex operator-(const complex &c1,const complex &c2) { return complex(c1.real-c2.real,c1.imag-c2.imag); } complex operator *(const complex &c1,const complex &c2) { return complex(c1.real*c2.real-c1.imag*c2.imag, c1.real*c2.imag+c1.imag*c2.real);} complex operator/(const complex &c1,const complex &c2) { return complex((c1.real*c2.real+c1.imag+c2.imag)/ (c2.real*c2.real+c2.imag*c2.imag),(c1.imag * c2.real - c1.real*c2.imag)/(c2.real*c2.real+c2.imag* c2.imag)); } void print(const complex &c) { if(c.imag<0) cout< else cout< } void main( ) { complex c1(2.0,3.0),c2(4.0,-2.0),c3; c3=c1+c2; cout<<"\nc1+c2="; print(c3); c3=c1-c2; cout<<"\nc1-c2="; print(c3); c3=c1*c2; cout<<"\nc1*c2="; print(c3); c3=c1/c2; cout<<"\nc1/c2="; print(c3); c3=(c1+c2)*(c1-c2)*c2/c1; cout<<"\n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout< } 注意: 1、该程序的运行结果与上例相同。 2、程序中出现的c1+c2编译程序解释为:operator+(c1,c2)调用如下函数,进行求值, complex operator +(const complex &c1,const complex &c2) 3.两种重载形式的比较 一般情况下,单目运算符最好重载为成员函数;双目运算符最好重载为友元函数。 *8.3 静态联编和动态联编 联编是指一个计算机程序自身彼此关联的过程。按照联编所进行的阶段不同,可分为:静态联编和动态联编。 8.3.1静态联编 静态联编是指联编工作出现在编译连接阶段,又称早期联编,因为这种联编过程是在程序开始运行之前完成的。在编译时所进行的这种联编又称静态束定。 *例8.4下面举一个静态联编的例子。 *#include class Point { public: Point(double i,double j) { x=i;y=j; } double Area( ) const { return 0.0; } private: double x, y; }; class Rectangle:public Point {public: Rectangle(double i,double j,double k,double l) :Point(i,j) { w=k; h=l;} double Area( ) const { return w*h; } private: double w, h; }; void fun(Point &s) { cout< void main( ) { Rectangle rec(3.0,5.2,15.0,25.0); fun(rec); } 运行结果为: 输出结果表明在fun( )函数中,s所引用的对象执行的Area( )操作被关联到Point::Area( )的实现代码上。这是因为静态联编的结果。在程序编译阶段,对s所引用的对象所执行的Area( )操作只能束定到Point类的函数上。因此,导致程序输出了所不期望的结果。因为我们期望的是s引用的对象所执行的Area( )操作应该束定到Rectangle类的Area( )函数上。这是静态联编所达不到的。 8.3.2动态联编 从对静态联编的上述分析中可以知道,编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将 要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定,又叫晚期联编。 动态联编实际上是进行动态识别。C++规定动态联编是在虚函数的支持下实现的。可见,静态联编和动态联编也都是属于多态性的,它们是不同阶段对不同实现进行不同的选择。 8.7 虚函数 虚函数是动态联编的基础。 虚函数的定义方法: virtual 类型说明符函数名(参数表) 虚函数在派生类中可能有不同的实现。 动态联编只能通过指针或引用标识对象来操作虚函数。如 果采用一般类型的标识对象来操作虚函数,则将采用静态联编 的方式调用虚函数。 *例8.9 #include class Point { public: Point(double i,double j){x=i;y=j;} virtual double Area( ) const {return 0.0;}//虚函数 private: double x,y; }; class Rectangle:public Point { public: Rectangle(double i,double j,double k,double l) :Point(i,j) { w=k; h=l;} virtual double Area( ) const{ return w*h;}//虚函数 private: double w,h; }; void fun(Point &s) { cout< } void main( ) { Rectangle rec(3.0,5.2,15.0,25.0); fun(rec); } 输出: 375 派生类中对基类的虚函数进行替换时,要求派生类中说明 的虚函数与基类中被替换的虚函数之间应满足: (1)与基类的虚函数的参数个数相同。 (2)与基类的虚函数的参数类型一致。 (3)其返回值或者与基类虚函数的相同,或者都返回指针 或引用。 满足上述条件的派生类的成员函数,自然是虚函数,不必 加virtual说明。 例8.10 #include class A { public: virtual void act1( ) //虚函数 { cout<<”A::act1( ) called.”< void act2( ) { act1( );} //成员函数调用虚函数}; class B:public A { public: void act1( ) // 省略virtual { cout<<”B::act1( ) called.”< }; void main( ) { B b; b.act2( ); } 输出: B::act1( ) called. 如果: void act2( ) { this->act1( );} 输出: B::act1( ) called. 如果: void act2( ) { A::act1( );} 输出: A::act1( ) called. 总结动态联编的实现条件: (1)要有说明的虚函数; (2)调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数。 (3)子类型关系的建立 8.5 纯虚函数和抽象类 8.5.1纯虚函数 在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它说明为纯虚函数,它的实现放到派生类去做。 纯虚函数定义格式如下: virtual 类型函数名(参数表)=0; 例8.14 #include class point { public: point(int i=0,int j=0) {x0=i;y0=j;} virtual void set( )=0;//纯虚函数 virtual void draw( )=0;//纯虚函数 protected: int x0,y0; }; class line:public point {public: line(int i=0,int j=0,int m=0,int n=0):point(i,j) {x1=m;y1=n;} void set( ){cout<<”line::set( ) called.\n”;} void draw( ){cout<<”line::draw( ) called.\n”;} protected: int x1,y1; }; class ellipse:public point { public: ellipse(int i=0,int j=0,int p=0,int q=0):point(i,j) {x2=p;y2=q;} void set( ){cout<<”ellipse::set( ) called.\n”;} void draw( ){cout<<”ellipse::draw( ) called.\n”;} protected: int x2,y2; }; void drawobj(point *p) { p->draw( );} void setobj(point *p) { p->set( );} void main( ) { line *lineobj=new line; ellipse *elliobj=new ellipse; drawobj(lineobj); drawobj(elliobj); cout< setobj(lineobj); setobj(elliobj); cout<<”\nRedraw the object…\n”; drawobj(lineobj); drawobj(elliobj); } 输出: line::draw( ) called. ellipse::draw( ) called. line::set( ) called. ellipse::set( ) called. Redraw the object… line::draw( ) called. ellipse::draw( ) called. 8.5.2 抽象类 抽象类带有纯虚函数的类。 抽象类是不能定义对象的。 作业: 1.定义一个人民币类RMB。使用友员函数重载运算符“ + ”,实现人民币的加和减运算。 #include class Rmb { private: int y,j,f; public: Rmb(){y=j=f=0;} Rmb(int a,int b,int c) { y=a,j=b,f=c; } friend Rmb operator +(Rmb &s1,Rmb &s2) { int a,b,c; a=s1.y+s2.y; b=s1.j+s2.j; c=s1.f+s2.f; if(c>10) {b++;c=c-10;} if(b>10) {a++;b=b-10;} return Rmb(a,b,c); } friend void print(Rmb &s) { cout< } }; void main( ) { Rmb a1(5,8,5),a2(5,8,6),a3; a3=a1+a2; print(a3); }