虚函数与动态多态

1、为什么把虚函数与多态放在一起讨论

       之所以把虚函数与多态结合在一起讨论,一方面是因为虚函数的作用主要是实现了动态多态,它们的联系非常紧密。另一方面,虚函数与多态也是C++相关面试的常见考点,掌握它们非常有必要。

2、关于虚函数

​       它是一种在基类中用virtual关键字声明的函数,并且可以在一个或多个派生类中再定义。虚函数的一个重要特点是,只要定义一个基类的指针,就可以指向派生类的对象,对这话如果不理解,可以看下面的例子。

       首先定义一个简单的基类,它的一个虚函数是net_price()函数(一般,对于继承关系中根节点的类,通常会将其析构函数定义为虚函数,从而实现析构函数的动态绑定,并且虚析构函数的虚属性也会被其派生类继承,原因见后文),如下图所示:

接着定义它的派生类,并在派生类中定义自己版本的net_price()函数(其中 override 关键字显式的注明该成员函数由虚函数改写),如下图所示:

然后定义两个类各自的对象,从下例中能够发现基类的指针p可以指向派生类的对象

其原因在于,由于派生类继承自基类,它含有与基类对应的组成部分,所以可以把派生类的对象当做基类对象使用。

虚函数还有以下特点:

  • 使用虚函数实现运行期多态的关键在于:必须通过基类指针访问这些函数(之所以不能用派生类指针去访问基类的对象,是因为派生类中含有自己定义的数据成员,而这是基类所不具有的,因此使用派生类指针去访问基类对象是不可行的)。
  • 一旦一个函数定义为虚函数,无论传下去多少层,一直为虚函数。
  • 纯虚函数:定义在基类中的一种只给出函数原型,没有任何与该基类相关的定义的函数: virtual type func_name(args)=0;
  • 含有纯虚函数的类称为 抽象基类,抽象基类不能建立对象,但是抽象基类可以有指向自己的指针,从而实现运行期多态。

3、虚函数表与虚函数表指针

​       如果一个类具有虚函数,那么它就拥有自己的虚函数表,该类的所有对象共享这张虚函数表。而虚函数表指针,顾名思义,也就是指向该虚函数表的指针。虚函数表是该类的虚函数的地址表,C++的编译器一般会把虚函数表的指针放在该类的对象实例化内存位置中最前面的地方(从而保证虚函数表有最高的性能),实例化一个含有虚函数的类的对象时,在对象的构造状态中,虚函数表指针就会指向这个虚表。当这个对象调用了类中某个虚函数时,就会通过虚表指针在虚函数表中寻找对应的虚函数。

4、关于多态

​       多态是C++面向对象编程(Object Oriented Programming,OOP)的三大特性之一(另外两个是 封装性 继承性 ),它分为运行期多态(也称为动态多态)与编译期多态(也称为静态多态)。

​       运行期多态通过继承实现,它依赖于虚函数机制,有虚函数的类的在实例化过程中,虚函数表会被分配在实例的内存中。在某个函数中,若参数列表里含有父类的对象,且函数体中调用了该对象的虚函数,则该函数在运行期间通过虚函数表指针与虚函数表,去确定具体调用的是父类的对象,还是子类的对象;即用父类的指针操作一个子类,从而实现运行期多态。上面这句话的理解可以结合下面的例子,定义一个名为print_total的函数,它的参数列表中调用了基类的对象item,它的函数体中调用了item的虚函数net_price()。:

       当在main函数或其他函数中使用print_total函数时,若传递基类对象作为实参给print_total函数调用,则运行时根据基类的虚函数表指针选择基类的虚函数(下图中的第一个print_total函数)。若传递的实参是派生类对象(下图中的第二个print_total函数),则运行时实际调用的虚函数就是派生类的虚函数,与print_total函数中item的静态类型(编译时类型,定义时类型)不一致,从而实现了动态多态(即定义的类型与运行时的实际类型不同)。一般情况下,对象的类型在编译期就已经确定,因此我们常称C++为静态语言,而在这种特殊情况下,item的实际类型不是编译期确定的,而是运行期。

       在第二个print_total函数调用中,item对象的静态类型为Quote,而实际上其动态类型(运行时类型)为Bulk_quote,若该函数执行完后调用析构函数,就会产生类型不匹配的问题(Bulk_quote类型对象含有Quote类型不具有的成员,Quote类的析构函数无法将之析构)。因此需要将上文中的基类的析构函数定义为虚函数,以防止对象的静态类型为基类,而动态类型实际上为派生类的情况时,delete对象调用了错误的析构函数。通过虚析构函数,来确保执行正确的版本。

​       编译期多态主要是通过模板实例化函数重载实现,用不同的模板参数实例化导致调用不同的函数(函数或类模板的实例化生成在编译期)(此处主要针对代码的复用),或者通过定义相同名字但参数列表不同的函数,来实现编译期多态。

5、为什么需要虚函数与多态?

  • 通过使用多态,程序员不必再为每一个派生类编写功能调用,只需要对抽象基类进行处理即可,通过继承提高了程序的可复用性
  • 通过虚函数,派生类的对象可以被基类的指针所绑定,这称之为向后兼容,从而提高了可扩展性可维护性

猜你喜欢

转载自www.cnblogs.com/wyjKeepMoveOn/p/12588995.html