引入多态是为了解决一般化问题和架构的抽象。
Ø 非多态的特点
1) 如果你以一个「基础类别之指针」指向「衍生类别之对象」,那么经由该指针你只能够调用基础类别所定义的函数。
2) 如果你以一个「衍生类别之指针」指向一个「基础类别之对象」,你必须先做明显的转型动作(explicit cast)。这种作法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。另外,子类对象要调用父类别的函数,你必须使用scope resolution operator(::)明白指出。
3) 如果基础类别和衍生类别都定义了「相同名称之成员函数」,那么透过对象指针调用成员函数时,到底调用到哪一个函数,必须视该指针的原始型别而定,而不是视指针实际所指之对象的型别而定。这与第1 点其实意义相通。
举例来讲,CAnimal,CMonkey都具有Roar()函数,但是pAnimal=(CAnimal*)pMonkey 、pAnimal-> Roar()实际上调用的是CAnimal:: Roar(),在一般化处理时这并非程序员的期望。当然,还可以使用pAnimal -> CMonkey::Roar()明白指出调用哪一个函数,但程序就不再那么优雅与弹性了。
Ø 多态的解决方式
上述问题可以使用多态实现。实现多态的关键是虚拟函数,什么是虚拟函数呢?如果你预期衍生类别有可能重新定义某一个成员函数,那么你就在基础类别中把此函数设为virtual。
Class CAnimal { Virtual Roar(){}; }
使用虚拟函数之后,我们以相同的指令却唤起了不同的函数,这种性质称为Polymorphism,意思是"theability to assume many forms"(多态)。其原理就是编译器无法在编译时期判断pAnimal-> Roar()到底是调用哪一个函数,必须在执行时期才能评估之,这称为后期绑定late binding 或动态绑定dynamic binding。至于C 函数或C++ 的non-virtual 函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding 或静态绑定static binding。
多态比较彻底的使用方式是采用纯虚拟函数,这样类就为抽象类,不能生成对象、很安全。关于抽象类别,我还有一点补充。CMonkey继承了CAnimal之后,如果没有改写CAnimal中的纯虚拟函数,那么CMonkey本身也就成为一个拥有纯虚拟函数的类别,于是它也是一个抽象类别。
Ø 小节
1) 如果你期望衍生类别重新定义一个成员函数,那么你应该在基础类别中把此函数设为virtual。
2) 以单一指令唤起不同函数,这种性质称为Polymorphism,意思是"the ability toassume many forms",也就是多态。
3) 虚拟函数是C++ 语言的Polymorphism 性质以及动态绑定的关键。
4) 既然抽象类别中的虚拟函数不打算被调用,我们就不应该定义它,应该把它设为纯虚拟函数(在函数声明之后加上"=0" 即可)。
5) 我们可以说,拥有纯虚拟函数者为抽象类别(abstract Class),以别于所谓的具象类别(concrete class)。
6) 抽象类别不能产生出对象实体,但是我们可以拥有指向抽象类别之指针,以便于操作抽象类别的各个衍生类别。
7) 虚拟函数衍生下去仍为虚拟函数,而且可以省略virtual 关键词。