博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++入门学习——虚函数表介绍
阅读量:7062 次
发布时间:2019-06-28

本文共 3461 字,大约阅读时间需要 11 分钟。

hot3.png

多态

多态是指使用相同的函数名来访问函数不同的实现方法,可以简单概括为“一种接口,多种方法”。

C++支持编译时多态(也叫静态多态)和运行时多态(也叫动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。

静态多态与动态多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态多态(编译时多态),就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定,是动态多态(运行时多态)。

这里,我们探究的是动态多态。

C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖(override),或者称为重写。

最常见的用法是 :父类(基类)指针,基类指针可以指向任何子类(派生类)对象(实例),然后通过基类的指针调用实际派生类的成员函数,示例如下:

#include 
using namespace std; class Base { public:   void f(int x){ cout << "Base::f(int) " << x << endl; }   void f(float x){ cout << "Base::f(float) " << x << endl; }  // 必须有virtual关键字,此为虚函数  virtual void g(void){ cout << "Base::g(void)" << endl;} }; class Derive: public Base { public:   // virtual关键字,可有可无,此为虚函数  virtual void g(void){ cout << "Derived::g(void)" << endl;} }; int main(void) {   Derive d;   Base *pb = &d;   pb->f(42);     // 运行结果: Base::f(int) 42   pb->f(3.14f);  // 运行结果: Base::f(float) 3.14   pb->g();       // 运行结果: Derived::g(void)     return 0;}

虚函数表

C++ 中的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table。 每个含有虚函数的类有一张虚函数表,表中每一项是一个虚函数的地址, 也就是说,虚函数表的每一项是一个虚函数的指针 。 没有虚函数的C++类,是不会有虚函数表的。 

22180100_EzKr.jpg

22180100_e0wj.jpg

下面通过一些示例简单介绍虚函数表: 

#include 
using namespace std; class Base {public:  virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; }   virtual void h() { cout << "Base::h" << endl; } };int main(void) {   Base b;    b.f(); //"Base::f"    b.g(); //"Base::g"    b.h(); //"Base::h"      return 0;}

Base的实例(即对象b)的虚函数表如下:

22180101_6oiP.jpg

注意:在上面这个图中,虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。 

无虚函数覆盖的虚函数表

下面,再让我们来看看继承时的虚函数表是什么样的。 

假设有如下所示的一个继承关系: 

#include 
using namespace std; class Base {public:  virtual void f() { cout << "Base::f" << endl; }  virtual void g() { cout << "Base::g" << endl; }    virtual void h() { cout << "Base::h" << endl; } };class Derive:public Base{public:  virtual void f1() { cout << "Derive::f1" << endl; }  virtual void g1() { cout << "Derive::g1" << endl; }  virtual void h1() { cout << "Derive::h1" << endl; }    };int main(void) {   Derive d; //派生类对象      return 0;}

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例(Derive d)中,其虚函数表如下所示:

22180101_hjx8.jpg

无虚函数覆盖的虚函数表特点如下: 

1)虚函数按照其声明顺序放于表中。

2)父类(派生类)的虚函数在子类(基类)的虚函数前面。

有虚函数覆盖的虚函数表

没有覆盖父类的虚函数是毫无意义的。之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。 

下面,我们来看一下,如果子类中有重载了父类的虚函数,会是一个什么样子? 

#include 
using namespace std; class Base {public:  virtual void f() { cout << "Base::f" << endl; }    virtual void g() { cout << "Base::g" << endl; }    virtual void h() { cout << "Base::h" << endl; } };class Derive:public Base{public:  //重写父类的f()虚函数   virtual void f() { cout << "Derive::f" << endl; }   virtual void g1() { cout << "Derive::g1" << endl; }   virtual void h1() { cout << "Derive::h1" << endl; }     };int main(void) {   Base *p = NULL;   //父类指针    Base b;   //父类对象    Derive d;   //子类对象      p = &b;   //父类指针指向父类对象    p->f();   //运行结果:"Base::f"      p = &d;   //父类指针指向子类对象    p->f();   //运行结果:"Derive::f"       return 0;}

为了让大家看到被继承过后的效果,在这个类的设计中,只覆盖了父类的一个虚函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

22180101_wCcG.jpg

有虚函数覆盖的虚函数表特点如下: 

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

本例中: 

Base *p = NULL; //父类指针

p = &d; //父类指针指向子类对象

p->f();   //运行结果:"Derive::f"

由 p(即 &d)所指的内存中的父类虚函数表的 f() 的位置已经被实际子类Derive::f()函数地址所取代,于是在实际调用发生时,是 Derive::f() 被调用了 。这就实现了多态。 

更多操作实例,请点此链接:

本教程示例代码下载请点此链接: 

本文转自: 

版权声明:本博客文章,大多是本人整理编写,或在网络中收集,转载请注明出处!!

转载于:https://my.oschina.net/badboy2/blog/520762

你可能感兴趣的文章
吴恩达机器学习笔记7-梯度下降III(Gradient descent intuition) --梯度下降的线性回归...
查看>>
iPhone-获取网络数据或者路径的文件名
查看>>
jquery简单实现点击弹出层效果实例
查看>>
TOSSIM进行无线传感网络仿真的大致流程
查看>>
微信内打开链接显示已停止访问该网页
查看>>
基于servlet和jsp的简单注册登录页面(包括:增删查改及分页显示)
查看>>
数据结构基础之一
查看>>
10.29随笔
查看>>
ScintillaNET v2.5 简单应用实例讲解
查看>>
I.MX6 Android busybox 从哪里生成的
查看>>
循环节长度 蓝桥杯
查看>>
linux软件安装:源码(1)
查看>>
c++-merge k sorted lists heap的灵活应用
查看>>
干货站
查看>>
RabbitMQ 基础概念介绍
查看>>
1117bootstrap
查看>>
centos6.5上卸载和安装JDK7
查看>>
从文件加载至NSData
查看>>
关于代码通过API操作阿里云RDS的巨坑
查看>>
jqgrid 获取选中行主键集合
查看>>