解析C++中的虚拟函数及其静态类型和动态类型

 更新时间:2016年06月24日 16:20:42   作者:pizzq  
虚拟函数(Visual Function)亦常被成为虚函数,是C++中的一个重要特性,本文我们就来解析C++中的虚拟函数及其静态类型和动态类型

虚拟函数是C++语言引入的一个很重要的特性,它提供了“动态绑定”机制,正是这一机制使得继承的语义变得相对明晰。
(1)基类抽象了通用的数据及操作,就数据而言,如果该数据成员在各派生类中都需要用到,那么就需要将其声明在基类中;就操作而言,如果该操作对各派生类都有意义,无论其语义是否会被修改或扩展,那么就需要将其声明在基类中。
(2)有些操作,如果对于各个派生类而言,语义保持完全一致,而无需修改或扩展,那么这些操作声明为基类的非虚拟成员函数。各派生类在声明为基类的派生类时,默认继承了这些非虚拟成员函数的声明/实现,如同默认继承基类的数据成员一样,而不必另外做任何声明,这就是继承带来的代码重用的优点。
(3)另外还有一些操作,虽然对于各派生类而言都有意义,但是其语义并不相同。这时,这些操作应该声明为基类的虚拟成员函数。各派生类虽然也默认继承了这些虚拟成员函数的声明/实现,但是语义上它们应该对这些虚拟成员函数的实现进行修改或者扩展。另外在实现这些修改或扩展过程中,需要用到额外的该派生类独有的数据时,将这些数据声明为此派生类自己的数据成员。
再考虑更大背景下的继承体系,当更高层次的程序框架(继承体系的使用者)使用此继承体系时,它处理的是一个抽象层次的对象集合(即基类)。虽然这个对象集合的成员实质上可能是各种派生类对象,但在处理这个对象集合中的对象时,它用的是抽象层次的操作。并不区分在这些操作中,哪些操作对各派生类来说是保持不变的,而哪些操作对各派生类来说有所不同。这是因为,当运行时实际执行到各操作时,运行时系统能够识别哪些操作需要用到“动态绑定”,从而找到对应此派生类的修改或扩展的该操作版本。
也就是说,即只需关心它自己问题域的业务逻辑,只要保证正确,其任务就算完成了
。即使继承体系内部增加了某种派生类,或者删除了某种派生类,或者某某派生类的某个虚拟函数的实现发生了改变,它的代码不必任何修改。这也意味着,程序的模块化程度得到了极大的提高。而模块化的提高也就意味着可扩展性、可维护性,以及代码的可读性的提高,这也是“面向对象”编程的一个很大的优点。

虚拟函数的静态类型和动态类型
先来看一个问题,如果一个子类重载的虚拟函数为privete,那么通过父类的指针可以访问到它吗?

#include <IOSTREAM> 
class B 
{ 
public: 
  virtual void fun()  
  { 
    std::cout << "base fun called"; 
  }; 
}; 
class D : public B  
{ 
private: 
  virtual void fun()  
  { 
    std::cout << "driver fun called"; 
  }; 
}; 
int main(int argc, char* argv[]) 
{   
  B* p = new D(); 
  p->fun(); 
  return 0; 
} 

运行时会输出

driver fun called

从这个实验,可以更深入的了解虚拟函数编译时的一些特征:

在编译虚拟函数调用的时候,例如p->fun(); 只是按其静态类型来处理的, 在这里p的类型就是B,不会考虑其实际指向的类型(动态类型)。

也就是说,碰到p->fun();编译器就当作调用B的fun来进行相应的检查和处理。

因为在B里fun是public的,所以这里在“访问控制检查”这一关就完全可以通过了。然后就会转换成(*p->vptr[1])(p)这样的方式处理, p实际指向的动态类型是D,所以p作为参数传给fun后(类的非静态成员函数都会编译加一个指针参数,指向调用该函数的对象,我们平常用的this就是该指针的值), 实际运行时p->vptr[1]则获取到的是D::fun()的地址,也就调用了该函数, 这也就是动态运行的机理。

为了进一步的实验,可以将B里的fun改为private的,D里的改为public的,则编译就会出错。

C++的注意条款中有一条" 绝不重新定义继承而来的缺省参数值" (Effective C++ Item37, never redefine a function's inherited default parameter value) 也是同样的道理。
可以再做个实验

class B 
{ 
public: 
  virtual void fun(int i = 1)  
  { 
    std::cout << "base fun called, " << i; 
  }; 
}; 
class D : public B  
{ 
private: 
  virtual void fun(int i = 2)  
  { 
    std::cout << "driver fun called, " << i; 
  }; 
}; 

则运行会输出

driver fun called, 1

关于这一点,Effective上讲的很清楚“virtual 函数系动态绑定, 而缺省参数却是静态绑定”,也就是说在编译的时候已经按照p的静态类型处理其默认参数了,转换成了(*p->vptr[1])(p, 1)这样的方式。

相关文章

  • Qt实现简单折线图表

    Qt实现简单折线图表

    这篇文章主要为大家详细介绍了Qt实现简单折线图表,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • C语言文件操作 fopen, fclose, mkdir详解

    C语言文件操作 fopen, fclose, mkdir详解

    本文给大家详细介绍了下C语言的文件操作函数fopen, fclose, mkdir的用法及示例,非常的简单实用,有需要的小伙伴可以参考下。
    2016-03-03
  • C++中引用的使用总结

    C++中引用的使用总结

    以下是对C++中引用的使用进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • C++调用EasyX库实现嫦娥奔月小游戏

    C++调用EasyX库实现嫦娥奔月小游戏

    这篇文章主要为大家详细介绍了C++如何调用EasyX库编写一个简单的嫦娥奔月小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下
    2023-09-09
  • C语言删除输入字符串中的空格示例代码

    C语言删除输入字符串中的空格示例代码

    最近工作中遇到了需求,要删除字符串中的所有空格,就要筛选出空格字符,这篇文章主要给大家介绍了关于利用C语言删除输入字符串中的空格的相关资料,需要的朋友可以参考下
    2022-12-12
  • C++中检查vector是否包含给定元素的几种方式详解

    C++中检查vector是否包含给定元素的几种方式详解

    这篇文章主要介绍了C++中检查vector是否包含给定元素的几种方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Qt界面美化之自定义qss样式表的详细步骤

    Qt界面美化之自定义qss样式表的详细步骤

    很多人应该和我一样,想做界面才接触的Qt,结果就是做不出来华丽的界面,下面这篇文章主要给大家介绍了关于Qt界面美化之自定义qss样式表的详细步骤,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • C++中的多态与多重继承实现与Java的区别

    C++中的多态与多重继承实现与Java的区别

    这篇文章主要介绍了C++中的多态与多重继承实现与Java的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • 基于QT5实现一个时钟桌面

    基于QT5实现一个时钟桌面

    这篇文章主要介绍了利用QT5实现的一个时钟桌面,文中的示例代码讲解详细,对我们学习或工作有一定的帮助,感兴趣的小伙伴可以了解一下
    2022-01-01
  • C语言中数据结构之链表归并排序实例代码

    C语言中数据结构之链表归并排序实例代码

    这篇文章主要介绍了C语言中数据结构之链表归并排序实例代码的相关资料,需要的朋友可以参考下
    2017-05-05

最新评论