Download presentation
Presentation is loading. Please wait.
1
第10章 虚函数与多态性
2
10.1 共同的基类 概念中的共性 在面向对象的程序设计中,每个类都是对一种概念的描述。通常,一些概念之间存在着共性,这种共性又体现了新的概念,得到这种新概念的过程就是抽象。 class Rectangle class Circle 除了少量的特殊方法外,Rectangle与Circle有着完全相同的一组方法,只是方法实现的过程又不相同。
3
10.1 共同的基类(续) 10.1.2 公共基类 1. 利用公共基类完善概念关系,消除代码冗余 2. 简单抽象中存在的问题
将所有图形元素的共性抽象出来,形成一种新的公共基类Shape,使Rectangle与Circle等由此基类派生。 重新描述的概念关系使代码得到了简化,Rectangle与Circle中的公共方法及属性被封能装在Shape中,利用继承关系消除了重复的拷贝。 2. 简单抽象中存在的问题 如何利用Shape指针访问Rectangle和Circle对象呢?这是技术上的问题,解决的方法是采用虚函数技术。 由于Shape描述的是一种抽象的概念,不应该生成对象,这是合理性的问题,解决的方法是采用抽象类技术 。
4
10.2 虚函数 面向对象设计中的另一个重要概念——多态性。
5
10.2.1 多态性、静态联编与动态联编 1. 多态性 强制多态和重载多态统称为专用多态,而参数多态和包含多态称为通用多态。
指程序中具有相似功能的不同函数用同一个名称来实现,进而可以使用相同的方式来调用这些具有不同功能的同名函数。 面向对象的多态性可以严格地分为四类:强制多态、重载多态、参数多态和包含多态。 强制多态和重载多态统称为专用多态,而参数多态和包含多态称为通用多态。
6
10.2.1 多态性、静态联编与动态联编(续) 强制多态是指编译器对类型的隐式转换。 重载多态就是指利用函数重载所实现的多态。
如计算表达式“3.0+2”时编译器将2由int类型转换为double类型。强制多态可以避免类型转换的麻烦,减少编译错误。 重载多态就是指利用函数重载所实现的多态。 参数多态与模板相关联。 包含多态是指具有继承关系的类族(层次)中定义于不同类中的同名成员函数的多态行为。 主要是通过虚函数来实现的。
7
10.2.1 多态性、静态联编与动态联编(续) 2. 静态联编与动态联编 从处理时刻看,多态可以划分为两类:编译时的多态和运行时的多态。
联编(或绑定):确定操作的具体对象的过程。 在编译连接阶段完成的联编称为静态联编(early binding)。也称为早期或先期联编。 强制多态、重载多态和参数多态都通过静态联编处理。 在程序运行阶段完成的联编称为动态联编(late binding),也称为晚期或后期联编。 包含多态是以动态联编方式处理的。
8
用虚函数实现动态联编 1. 虚函数的语法 虚函数(virtual function)是指一个类中定义的一类特殊方法,声明形式如下: virtual 函数原型; class Base { ... public: virtual void method(); //虚函数声明 }; void Base::method() //虚函数实现,无virtual关键字 }
9
用虚函数实现动态联编(续) 2. 虚函数的作用 可以使指向派生类对象的基类指针(或引用)在不经类型转换的情况下直接调用派生类的方法。 见下页示例。 虚函数仅对继承关系有效,即如果基类和派生类中各自定义了相同的虚函数,系统将在运行时才确定基类指针所指向对象的类型,进而决定应该调用哪个类的方法。
10
virtual void print(){ cout << "Base Class A.\n"; } };
{ public: virtual void print(){ cout << "Base Class A.\n"; } }; class B: public A //派生类 virtual void print(){ cout << "Derived Class B.\n"; } int main( ) { A a; B b; A* ap = &a; //基类指针指向基类对象 ap->print(); //调用基类方法 ap = &b; //基类指针指向派生类对象 ap->print(); //调用派生类对象 A& ar = b; //基类引用绑定到派生类对象 ar.print(); //调用派生类方法 } 程序运行时的输出结果如下: Base Class A. Derived Claaa B.
11
10.2.2 用虚函数实现动态联编(续) 3. 有关虚函数的说明
⑴虚函数应在基类中声明,其特性被自动传递给派生类,故派生类中的virtual修饰可有可无。 ⑵实现虚函数关系的前提是两类之间以公有方式派生。 ⑶派生类中只有原型与基类的虚函数完全相同时才是虚函数。 见下页示例。
12
#include <iostream> using namespace std; class A { public:
virtual void func1(int){ cout<<"A func1.\n"; } virtual int func2(float){ cout << "A func2.\n"; } }; class B:public A void func1(int){ cout << "B func1.\n"; } virtual int func2(int){ cout << "B func2.\n"; } int main( ) { B b; A* ap = &b; ap->func1(0); //调用B类的方法 ap->func2(0); //调用A类的方法 } B func1. A func2.
13
10.2.2 用虚函数实现动态联编(续) ⑷只有类的非静态方法可以是虚函数,静态方法不能是虚函数
⑸派生类中不能定义与基类的虚函数名字参数都相同而仅仅返回值不同的方法。 例外:如果基类虚函数返回基类型指针或引用,派生类虚函数返回派生类类型的指针或引用,此时被认为合理,具有虚函数关系并被滞后联编。 virtual A* func1(int); //基类A中定义了虚函数 B* func1(int); ⑹内联函数不能是虚函数。如果虚函数在类内部实现,自动被视为非内联的方法。
14
构造、析构与虚函数 在C++中,构造函数不能是虚函数,也尽量不要调用虚函数,但析构函数可以是虚函数,且在一个类含有虚函数时析构函数通常也定义为虚函数。
15
class A { public: virtual ~A() { cout<<"Base class A destroyed.\n"; } }; class B : public A { public: ~B() { cout<<"Derived class B destroyed.\n"; } int main( ) { A* ap = new A; delete ap; ap = new B; } Base class A destroyed. Derived class B destroyed.
16
* 虚函数的内部实现机制 此部分内容自学。
17
重载、覆盖和隐藏 在一个子类从基类派生时,由于既包含继承来的方法,又可以自己重新定义方法,且基类中可能定义了虚函数。因此,需要仔细辨别C++在处理这些问题上的细微差别。
18
{ cout << "A func1().\n"; } virtual void func2()
class A { public: void func1() { cout << "A func1().\n"; } virtual void func2() { cout << "A func2().\n"; } }; class B : public A { cout << "B func1().\n"; } void func1(int) { A::func1(); func1(); cout<<"B func1(int).\n"; } void func1(double) { cout << "B func1(double).\n"; } void func2() { cout << "B func2().\n"; } void func2(int) { cout << "B func2(int).\n"; } int main( ) { A oA,*pA; oA.func1(); //静态调用A类的func1 oA.func2(); //静态调用A类的func2 pA = &oA; pA->func1(); //静态调用A类的func1 pA->func2(); //动态调用A类的func2 B oB; oB.func1(); //静态调用B类的func1 oB.A::func1(); //静态调用A类的func1 oB.func1(1); //静态调用B类的func1(int) oB.func2(); //静态调用B类的func2 oB.func2(1); //静态调用B类的func2(int) pA = &oB; pA->func2(); //动态调用B类的func2 }
19
10.2.6 动态造型(dynamic_cast) dynamic_cast只用于指针(引用)类型转换,语法形式为:
dynamic_cast<type*>(expression) type必须是有虚函数的类的指针、引用或者void *(空类型)指针 type是类的指针或引用时,expression也必须是指针或引用。 class A { ... }; class B : public A { ... }; void func(A *pa) { A a; B b; //两个对象 A* pa = &a; //基类指针指向基类对象 B* pb1 = dynamic_cast<B*>(pa); //指针pb1为0 pa = &b; //基类指针指向子类对象 B *pb2 = dynamic_cast<B*>(pa); //指针pb2指向对象b }
20
10.2.6 动态造型(dynamic_cast) (续)
在类层次间进行上行(子类向父类)转换时,dynamic_cast与static_cast的效果相同。 在进行下行(父类向子类)转换时,dynamic_cast具有类型检查功能,比static_cast更安全。 如果类型检查失败,表达式dynamic_cast<type>的值为0(空指针)或导致运行错误(对于引用)。
21
10.3 纯虚函数与抽象类 纯虚函数 对于没有函数体的虚函数方法,派生类重新定义原型相同的虚函数来覆盖它,以使得程序在处理指向派生类对象的基类指针和引用时能够实现动态联编。 对于此类方法,C++采用了一种特殊的方式,并将其定义为纯虚函数 。 纯虚函数的语法形式为: virtual type func_name(形式参数表) = 0;
22
10.3 纯虚函数与抽象类(续) 10.3.2 抽象类 如果一个类至少含有一个纯虚函数,那么就称其为抽象类。
C++对于抽象类的使用有如下限制: ⑴抽象类只能用作其它类的基类,不能建立抽象类对象。 ⑵抽象类不能用作参数类型、函数返回类型或参加显式转换类型。 ⑶可以声明指向抽象类的指针和引用,目的是使指针能指向它的派生类,进而实现多态性。 ⑷若一个类是某个抽象类的派生类,则需要重新定义基类的所有纯虚函数,才能成为具体类,否则仍是抽象类。 见例10_5.
23
最后说明: 虚拟继承与虚函数及多态性没有关系。
Similar presentations