C++虚函数本质论

C++虚函数本质论

13.2.1 多态性原理
1.多态性概念
对多态性最简单的理解就是一种事物有多种形态。在面向对象设计中,多态性指的是向不同的对象发送同一消息时,会产生不同的动作(或行为、功能)。所谓的“向不同的对象发送同一消息”,其实就是指调用不同对象的某个函数;而“产生不同的动作”,指的是该函数会实现不同的功能。没有实现多态性的程序设计语言不能称为真正的面向对象语言。比如语句pa->f();,若C++实现了多态性时,则会根据pa所指向的类对象的不同而调用相同的函数f(在语句形式上是相同的),这就是“向不同的对象发送同一消息”,这里的同一消息指的是调用函数f(),然后程序根据函数f所在的类不同会实现不同的功能,这就是“产生不同的动作”。再比如A、B、C是同一年级而不同班级的三个学生,当听到上课铃声时,他们会进入不同的教室去上课。A、B、C三个对象在收到同一消息“上课铃声”时,执行了不同的动作,这就是多态性。至于多态性怎样实现,根据程序设计语言而定。
2.动态类型与静态类型
(1)静态类型指的是对象(指针、引用)在声明时的类型。这种类型在编译阶段就能确定,仅从表达式的字面形式就能够确定其类型。静态类型在程序运行阶段不会改变。
(2)动态类型指的是当前对象(包括指针和引用)实际指向的类型。这种类型需要在运行阶段才能确定,对象的动态类型可以更改。
(3)编译阶段与运行阶段的区别:在编译阶段,系统只做静态的语法检查,即从表达式的字面形式(或语句形式)就能够确定信息。比如A* pa,则对于pa->f();,在编译阶段从表达式的语句形式是无法确定指针pa所指向的类型(动态类型)的,因为pa可指向不同的子类类型对象,这就需要等到运行阶段才能确定。但pa自身的类型(即类A类型),在编译时是可以确定的,就是静态类型。
示例如下:
A pa=&mb;
这里pa的静态类型是A
,pa的动态类型是mb的类型。pa的动态类型可以改变(只需重新指向另一个对象即可),而静态类型无法更改。pa的动态类型需要在程序运行阶段才能确定,在编译阶段是无法确定的。
A* pa=new A();
此时pa的静态类型和动态类型都是A*。
3.C++中的动态类型与静态类型
(1)在C++的继承关系中,指针或引用的动态类型与静态类型可以不同,这是C++实现多态性的关键。比如class A{};class B:public A{};B mb;,则对于A pa=&mb;,此时pa的静态类型为A,动态类型为B*,可见pa的静态类型与动态类型并不相同。
(2)在继承关系中,类对象(类指针和类引用除外)的静态类型与动态类型总是相同的。因为在把子类对象赋给父类对象时(注意:父类对象不能赋给子类对象),会把子类的父类部分“切除”,然后把“切除”部分赋给父类对象,这时父类对象的类型相当于是子类被“切除”的父类类型(其实就是父类类型)。比如class A{};class B:public A{};B mb; A ma=mb;,这时ma的动态类型与子类mb被“切除”的父类类型BA 相同,即ma的动态类型就是类型BA,这与ma的静态类型是相同的。
4.动态绑定与静态绑定
(1)绑定(binding):确定调用的是哪个具体对象的行为。在本文中,绑定一般是指把某个函数名与某一个类对象绑定在一起的行为。
(2)动态绑定:在程序执行时(运行阶段)绑定到对象的动态类型的行为,其特性依赖于该对象的动态类型。动态绑定后的结果就是根据所绑定的动态类型,调用动态类型中的函数。动态绑定又被称为后期绑定。
(3)静态绑定:绑定到对象的静态类型的行为。静态绑定后的结果就是根据所绑定的静态类型,调用静态类型中的函数,该过程在编译阶段进行。静态绑定又被称为早期绑定。
示例如下:
class A{public:void f(){}}; class B:public A{public:void f(){}};
B mb; A pa=&mb; pa->f();
① 若函数f绑定到指针pa的动态类型(即动态绑定),则调用pa的动态类型中的函数f,此时pa的动态类型是B
,因此pa->f();调用类B中的函数f。可见,动态绑定时调用哪一个函数,是由指针所指向的动态类型决定的。
② 若函数f绑定到指针pa的静态类型(即静态绑定),则调用pa的静态类型中的函数f,此时pa的静态类型是A*,因此pa->f();调用类A中的函数f。
5.C++中的动态绑定与多态性、虚函数
(1)C++中的多态表示的是使用一个公有的父类指针(或引用),寻址出一个子类对象。换句话说,就是“一个接口,多个方法”。在非多态情形下,当声明一个父类指针指向子类对象时,这个父类指针只能访问父类中的成员函数,不能访问子类中特有的成员变量或函数(因为父类并不知道子类特有的成员)。C++多态性的实质就是为了实现“使用指向子类对象的父类指针(或引用)访问子类中的成员函数,而不是父类中的成员函数”。比如,若类B和类C是类A的子类,且都有各自的成员函数f,则对于B mb; C mc; A* pa;,若pa=&mb;,那么在多态情形下,pa->f()将调用类B中的成员函数f();同理若pa=&mc; ,那么pa->f()将调用类C中的成员函数f;若没有多态,则无论pa指向类A的哪个子对象,pa->f()都将调用类A中的函数f。这就是C++中的多态,即根据共有的父类指针pa,通过pa指向不同的子对象,可以调用不同子对象中的成员函数。
(2)C++使用动态绑定实现多态性,静态绑定是不能实现多态性的。比如,若类B是类A的子类,且都有各自的成员函数f,则对于B mb; A* pa;,若pa=&mb;,要实现多态性就必须使pa->f()调用子类B中的函数f,而这种调用就是动态绑定,因此只有动态绑定才能实现多态性;若pa->f()的调用是静态绑定,则无法调用子类B中的函数f。
(3)C++实现动态绑定:在C++的继承关系中,动态绑定必须使用虚函数和父类类型的指针(或引用)来实现,这两者缺一不可。
① 为什么需要虚函数:在C++中,只有虚函数才有可能使用动态绑定,虚函数是实现动态绑定的条件之一,非虚函数全部使用静态绑定。
② 为什么需要指针或引用:在C++中,指针或引用的动态类型与静态类型可以不同,但类对象的静态类型与动态类型总是相同的,只有在静态类型与动态类型不相同时,使用动态绑定才能实现多态性,因此使用类对象无法绑定到动态类型,即无法实现动态绑定,动态绑定必须使用指针或引用才能实现。
③ 为什么必须是父类的指针或引用:在C++中,子类可以转换为父类,但不存在从父类到子类的转换,因此要实现动态绑定应使用父类类型的指针或引用。不管是通过动态绑定还是静态绑定,把子类对象绑定到子类类型、把父类对象绑定到父类类型都没有实际意义,而把父类对象绑定到子类类型是错误的,因此只有把子类类型动态绑定到父类类型才有意义。
示例如下:
class A{public:void f(){}};class B:public A{public:void f(){}};
B mb; A ma; A pa; B pb;
① 对于pa=&mb; pa->f(); :
 pa的静态类型为A*,动态类型为B*,其静态类型与动态类型是不同的。
 若函数f是虚函数,则通过动态绑定把函数f绑定到指针pa的动态类型,调用类B中的函数f。只有此情形才可实现多态性。
 若函数f是非虚函数,则通过静态绑定把函数f绑定到指针pa的静态类型,调用类A中的函数f。因为使用的是静态绑定,所以未实现多态性。
 ② 对于pa=&ma; pa->f();:
 pa的静态类型为A*,动态类型为A*,其静态类型与动态类型是相同的。
 若函数f是虚函数,则通过动态绑定把函数f绑定到指针pa的动态类型,调用类A中的函数f。因为静态类型与动态类型相同,所以未实现多态性。
 若函数f是非虚函数,则通过静态绑定把函数f绑定到指针pa的静态类型,调用类A中的函数f。因为使用的是静态绑定,所以未实现多态性。
③ pb=&mb; pb->f();,原理同② 。
④ pb=&ma; 或mb=ma; ,错误,不存在从父类到子类的转换。
⑤ ma=mb; ma.f(); mb.f(); ,此时不管函数f是虚函数还是非虚函数,对于类对象使用的都是静态绑定,因此ma.f()中的函数f被绑定到ma的静态类型,调用类A中的f函数。mb.f()中的f函数被绑定到mb的静态类型,调用类B中的函数f。
⑥ 由以上可见,要实现多态性,必须要满足两个条件,即虚函数和父类类型的指针(或引用)。只有使用虚函数,才可以进行动态绑定;只有使用引用或指针,才能使静态类型与动态类型不同。
(4)总结: C++中的多态就是“通过父类的引用(或指针)调用虚函数”,这时发生的是动态绑定,指针或引用的动态类型与静态类型可以不同(因为引用(或指针)既可以指向父类对象,也可以指向子类对象),这是实现动态绑定的关键。用引用(或指针)调用虚函数时,被调用的是引用(或指针)所指对象的实际类型(动态类型)中所定义的虚函数。

以上内容摘自本人所作《C++语法详解》一书,电子工业出版社出版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值