430 likes | 660 Views
第十五章 目 录. § 15.1 函数重载. § 15.2 子类型. § 15.3 静态联编和动态联编. § 15.4 虚函数. § 15.5 纯虚函数和抽象类. § 15.6 虚析构函数. 第十五章小结. 本课件可在 www.cdmobile.net 进行 下载. 第十五章 多态性和虚函数. 封装性、继承性和多态性构成了面向对象程序设计语言的三大特性。 封装性是基础,继承性是关键,多态性是扩充。 多态性是指对不同类的对象发出相同的消息将会有不同的行为。
E N D
第十五章 目 录 §15.1 函数重载 §15.2 子类型 §15.3 静态联编和动态联编 §15.4 虚函数 §15.5 纯虚函数和抽象类 §15.6 虚析构函数 第十五章小结 本课件可在 www.cdmobile.net 进行下载
第十五章 多态性和虚函数 封装性、继承性和多态性构成了面向对象程序设计语言的三大特性。 封装性是基础,继承性是关键,多态性是扩充。 多态性是指对不同类的对象发出相同的消息将会有不同的行为。 消息主要是指对类的成员函数的调用,不同的行为是指不同的实现。 本章主要介绍多态性;动态联编,虚函数;纯虚函数和抽象类等重要内容。
§15.1 函数重载 函数重载是多态性的一种简单形式,它是指允许在相同的作用域内,相同的函数名对应着不同的实现。 函数重载的条件是要求函数参数的类型或个数有所不同。对成员函数的重载有以下三种表达方式: 1、在一个类中重载; 2、在不同类中重载; 3、基类的成员函数在派生类中重载。
具有相同名字的重载函数是在编译时区分的,有以下三种区分方法:具有相同名字的重载函数是在编译时区分的,有以下三种区分方法: 1、根据参数的特征加以区别,例如: show(int, char); show (char*, float); 2、使用类作用域符“::”加以区分,例如: Circle::show(); Point::show(); 3、根据类对象加以区分,例如: acirle.show()调用Circle::show() apoint.show()调用Point::show()
除了函数重载这种简单形式之外,C++ 还提供了一种更为灵活的特征机制——虚函数。 虚函数允许函数调用与函数体的联系在运行时才给出。当需要同一接口、多种实现时,这种功能显得尤其重要。 在讲述虚函数的概念之前,先介绍子类型及静态联编和动态联编的相关内容。
§15.2 子类型 有一个特定的类型S,当且仅当它至少提供了类型T 的行为,则称类型S 是类型T 的子类型。 在继承关系中,若类B 是类A 以公有继承形式产生的派生类,则类B 包含了类A 的行为,并且它本身还可具有新的行为,可称类B 是类A 的一个子类型。 若类B 是类A 的子类型,则类A 对象可操作的函数,类B 的对象也可以进行操作,称类B 适应类 A。
子类型的重要作用就在于类型适应,即在公有继承方式下,派生类的对象、指向对象的指针和对象的引用都适应于基类的对象、指向对象的指针和对象引用所能使用的场合。子类型的重要作用就在于类型适应,即在公有继承方式下,派生类的对象、指向对象的指针和对象的引用都适应于基类的对象、指向对象的指针和对象引用所能使用的场合。 子类型关系是不可逆的。已知类B 是类A 的子类型,而认为类A 也是类B 的子类型是错误的。 例如:假设M 是基类,N 是以公有继承方式产生的派生类,函数void fun(M& P)以基类M 的引用作为形参数,则 void main() { M m(7),q; N n(3,8);
q=n; //派生类对象赋与基类对象 M *pm=new M(6); N *pn=new N(5,9); pm=pn; //派生类指针赋给基类指针 fun(*pn); //实参为派生类传递给基类的引用 //…… } 由于子类型的类型适应性,main()中被注释的语句都是合法的。 根据类型适应性,在公有继承方式下,指向基类和派生类的指针变量是相关的。如果B 是基类,D 是从B 公有派生出来的派生类,则在C++ 中,指向基类B 的指针P 也可以指向派生类D。
当P 指向派生类D 的对象时,利用指针P 可以访问从基类B 继承的成员,但派生类D 自己定义的成员不能用P 访问(除非用显式类型转换)。 例如:下面是指向基类对象的指针指向派生类对象,而访问从基类继承的成员的例子。 #include <iostream.h> #include <string.h> class B { char name[80]; public: void put_name(char *s) { strcpy(name,s); } void show_name() { cout<<name<<“\n”; } };
class D: public B { char phone_num[80]; public: void put_phone(char *num) { strcpy(phone_num,num); } void show_phone() { cout<<phone_num<<“\n”; } }; main() { B *p; B Bobj; D *dp; D Dobj;
p=&Bobj; P->put_name(“Zhang Fang”); p=&Dobj; p->put_name(“Wang Ming”); //访问从基类继承的成员函数 Bobj.show_name(); Dobj.show_name(); dp=&Dobj; dp->put_phone(“83768493”); //访问自己的成员 dp->show_phone(); p->show_phone(); //error // 指向基类指针不能访问派生类定 义的成员 ((D *)P)->show_phone(); //强行类型转换 }
结果: Zhang Fang Wang Ming 83768493 83768493 注意:希望用基类指针访问其公有派生类的特定成员,必须将基类指针用显式类型转换为派生类指针。根据类型适应性的原则,一个指向基类的指针可用来指向以公有派生的任何对象是C++ 实现运行时多态性的关键。
§15.3 静态联编和动态联编 联编是指一个程序自身彼此关联的过程。按照联编所进行的阶段不同,可分为静态联编和动态联编。 一、静态联编 静态联编是指在程序编译链接阶段进行联编,也称为早期联编。 这种联编工作由于在程序运行之前完成,所调用的函数与执行该函数的代码之间的关系已确定。
例如:下面是一个静态联编的例子 #include <iostream.h> class Base { protected: int x; public: Base(int a) { x=a; } void print() { cout<<“Base ”<<x<<“\n”; } }; class First_d: public Base { public: First_d(int a): Base(a) { }
void print() { cout<<“First derivation\n”<<x<<“\n”; } }; class Second_d: public Base { public: Second_d(int a) : Base(a) { } void print() { cout<<“Second derivation\n”<<x<<“\n”; } }; void main() { Base *p; Base obj1(1); First_d obj2(2); Second_d obj3(3); p=&obj1; p->print(); p=&obj2; p->print(); p=&obj3; p->print(); obj2.print(); obj3.print(); } 运行结果: Base 1 Base 2 Base 3 First derivation 2 Second derivation 3
注意: 指向基类的指针P,在运行前,p->print() 已确定为访问基类的成员函数print()。所以不管P 指向基类,还是派生类的对象,p->print()都是基类绑定的成员函数,结果都相同。这是静态联编的结果。 P 指针不能直接访问派生类中的成员函数print(),所以必须显式地用obj2.print();和obj3.print();调用。 二、动态联编 动态联编是指在程序运行时进行的联编,也称晚期联编。 动态联编要求在运行时解决程序中的函数调用与执行该函数代码间的关系。
上例中,如果采用动态联编,则随P 指向的对象不同,使p->print()能调用不同类中print()版本,这样就可以用一个界面p->print()访问多个实现版本。即该函数调用依赖于运行时P 所指向的对象,具有多态性。 使用虚函数可实现动态联编,不同联编可以选择不同的实现,这便是多态性。 继承是动态联编的基础,虚函数是动态联编的关键。
§15.4 虚函数 Virtual functions allow the programmer to declare functions in a base class that can be redefined in each derived class. The compiler and loader will guarantee the correct correspondence between objects and the functions applied to them. For example: class Employee { string first_name,family_name; short department; //…
public: Employee(const string& name, int dept); virtual void print() const; //… }; The keyword virtual indicates that print() can act as an interface to the print() function defined in this class and the print() functions defined in classes derived from it. Where such print() functions once defined in derived classes, the compiler ensures that the right print() for given Employee object is invoked in each case.
To allow a virtual function declaration to act as an interface to function defined in derived classes, the argument types specified for a function in a derived class cannot differ from the argument types dictated in the base. A virtual member function is sometimes called a method. A virtual function must be defined for the class in which it is first declared (unless it is declared to be a pure virtual function.)
虚函数是一种非静态的成员函数,定义格式如下:虚函数是一种非静态的成员函数,定义格式如下: virtual <类型说明符><函数名>(<参数表>) { //<函数体> } 其中,virtual是关键字。 如果某个类中的一个成员函数被说明为虚函数,该成员函数可能在派生类中存在着不同的实现版本。 由于存在有虚函数,编译器将进行动态联编,使调用虚函数的对象在运行时确定,以实现动态联编的多态性。
例如:使用虚函数将上例改为动态联编的情况,将得到不同的结果例如:使用虚函数将上例改为动态联编的情况,将得到不同的结果 #include <iostream.h> class Base { protected: int x; public: Base(int a) { x=a; } virtual void print() { cout<<“Base ”<<x<<“\n”; } };
class First_d: public Base { public: First_d(int a): Base(a) { } virtual void print() { cout<<“First derivation\n”<<x<<“\n”; } }; class Second_d: public Base { public: Second_d(int a)=Base(a) { } virtual void print() { cout<<“Second derivation\n”<<x<<“\n”; } };
void main() { Base *p; Base obj1(1); First_d obj2(2); Second_d obj3(3); p=&obj1; p->print(); p=&obj2; p->print(); p=&obj3; p->print(); obj2.print(); obj3.print(); } 运行结果: Base 1 First derivation 2 Second derivation 3 First derivation 2 Second derivation 3 程序中,p->print();出现了三次,由于p 指向的对象不同,每次执行了print() 的不同实现版本。实现了“单界面、多实现版本”的思想。
基类函数具有虚特性的条件是: 1、在基类中,将该函数说明为虚函数(virtual); 2、定义基类的公有派生类; 3、在基类的公有派生类中重载该虚函数; 4、定义指向基类的指针变量,它指向基类的公有派生类的对象。 注意: 重载虚函数不是一般的重载函数,它要求函数名、返回类型、参数个数、参数类型和顺序完全相同。 由于对虚函数进行重载,因此,在派生类中的虚函数前的virtual关键字可以省略。
例如:下面是“单界面、多实现版本”概念的另一个程序例子例如:下面是“单界面、多实现版本”概念的另一个程序例子 #include <iostream.h> class figure { protected: double x,y; public: void set_dim(double i; double j=0) { x=i; y=j; } virtual void show_area() { cout<<“No area computation define ”; cout<<“for this class.\n”; } };
class triangle: public figure { public: void show_area() { cout<<“Triangle with high”; cout<<x<<“and base”<<y; cout<<“has an ares of”; cout<<x*0.5*y; } }; class square: public figure { public: void show_area() {
cout<<“Square with dimension”; cout<<x<<“*”<<y; cout<<“has an area of”; cout<<x*y<<“\n”; } }; class circle: public figure { public: void show_area() { cout<<“Circle with radius”; cout<<x; cout<<“has an area of”; cout<<3.14*x*x; } };
p= &s; p->set_dim(10.0,5.0); p->show_area(); p=&c; p->set_dim(9.0); p->show_area(); } void main() { figure *p; triangle t; square s; circle c; p=&t; p->set_dim(10.0,5.0); p->show_area(); 运行结果: Triangle with high 10 and 5 has an area of 25.0 Square with dimension 10*5 has an area of 50.0 Circle with radius 9 has an area of 254.34
§15.5 纯虚函数和抽象类(Pure virtual functions and Abstract classes) 纯虚函数是一种特殊的虚函数,是一种没有具体实现的虚函数,其定义格式如下: class <类名> { virtual <类定义说明符><函数名>(<参数表>)=0; //… } A virtual function is “made pure” by the initializer =0;
For example class Shape { public: virtual void draw()=0; //pure virtual function virtual bool is_closed()=0; //pure virtual function //… }; A class with one or more pure virtual functions is an abstract class, and no objects of that abstract class can be created. Class Shape is an abstract class. Shape S; //error: Creating the object of abstract class Shape An abstract class can be used only as an interface and as a base class for other classes.
A pure virtual function that is not defined in a derived class remains a pure virtual function, so that derived class is also an abstract class. For example class B { public: virtual void init()=0; virtual write(char *pstring)=0; }; class D1: public B { public: virtual void init() { } };
Application MyProgram Shape Trapezoid Square Triangle Rectangle Circle Class D1 also is an abstract class. 例如:下面举一个计算某些几何图形的面积之和的例子,以说明纯虚函数及抽象类的应用。例中各类之间的关系如下:
# include <iostream.h> Class Shape { Public: virtual double Area() const=0; }; class Circle: public Shape { public: Circle(double r) { R=r; } double Area() const { return 3.14*R*R; } private: double R; };
class Trapezoid: public Shape { public: Trapezoid(double t, double b, double h) { T=t; B=b; H=h; } double Area() const { return 0.5*(T+B)*H; } private: double T,B,H; }; class Square: public Shape { public: Square(double s) { S=s; } double Area() const { return S*S; } private: double S; };
class Application { public: double Compute(Shape *s[ ],int n) const }; double Application::Compute(Shape *s[ ], int n) const { double sum=0; for(int i=0; i<n; i++) sum+=s[i]->Area(); return sum; }
class MyProgram : public Application { MyProgram(); ~Myprogram(); double Run(); private: Shape **S; }; MyProgram::MyProgram() { S=new Shape *[5]; S[0]=new Triangle(3.0,5.0); S[1]=new Rectangle(5.0,8.0); S[2]=new Circle(8.5); S[3]=new Trapezoid(12.0,8.0,6.0); S[4]=new Square(6.8); }
MyProgram::~MyProgram() { for(int i=0; i<5; i++) delete S[i]; for(int i=0; i<5; i++) delete [ ]S; } double MyProgram::Run() { double sum=compute(S,5); return sum; } void main() { MyProgram M; cout<<“Area’s sum=”<<M.Run()<<endl; } 输出结果: Area’s sum=380.713
程序分析: 1、该程序定义了8个类,其关系如前图所示; 2、类Shape 定义为抽象类,相当于Triangle等5个类的根,Shape类中定义纯虚函数Area(),在它的5个派生类中定义其具体实现; 3、在类Application中定义了计算面积和的函数Compute(),在其派生类MyProgram中定义Run()函数,以调用计算面积和函数Compute()。
该例说明的设计特点:类库和应用程序设计分开。该例说明的设计特点:类库和应用程序设计分开。 几何图形面积类库——建立抽象类,作为继承的层次结构中的公共根,方便图形的增添或减少。 应用程序——取决于用户需求,只需修改MyProgram 类的构造函数中的参数。 体现面向对象设计的特点:增删容易、修改方便、便于共享。
An important use of abstract classes is to provide an interface without exposing any implementation details, for example, an operating system might hide the detail of its device drivers behind an abstract class: class character_device { public: virtual int open(int opt)=0; virtual int close(int opt)=0; virtual int read(char *p, int n)=0; virtual int write(const char *p, int n)=0; virtual int ioct1(int …)=0; virtual ~character_device() { } //virtual destructor };
§15.6 虚析构函数 构造函数不能说明为虚函数,而析构函数可以说明为虚函数。其方法是在析构函数前加上关键字virtual 说明符。 如果一个基类的析构函数被说明为虚函数,则其派生类中的析构函数也是虚析构函数,可省略virtual 说明符。 虚析构函数可以采用动态联编,可在运行时选择析构函数,能确保析构函数说明为虚析构函数的正确执行。 因此,在继承的情况下,将基类中的析构函数说明为虚析构函数是没有坏处的。
第十五章 小结 虚函数是C++ 提供的一种更为灵活的多态性机制。 虚函数允许函数调用与函数体的联系在运行时动态进行。 在类的某个成员函数被说明为虚函数,便意味着该成员函数在派生类中可以定义不同的实现版本。即,具有“同一接口,多种实现”的特点。 没有定义具体实现的虚函数称为纯虚函数,其实现在它的派生类中,它是抽象类的必要条件。 包含有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它不能定义对象。其主要作用是用来组织一个继承的层次结构,并由它提供一个公共根,而相关的子类由它派生出来。