1、虚函数

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。C++中虚函数的作用主要是实现多态机制。所谓多态就是用父类指针指向子类对象,然后通过父类指针调用实际子类的成员函数,这种技术可以让父类指针有“多种形态”。

2、纯虚函数

在虚函数形参后面写上=0,则该函数为纯虚函数。纯虚函数没有函数体;纯虚函数只有函数的名字而不具备函数的功能,不能被调用。纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在一个类中声明了纯虚函数,在其派生类中没有对其函数进行定义,则该虚函数在派生类中仍然为纯虚函数。

3、虚函数表

虚函数是通过虚函数表来实现的,在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题。在有虚函数的实例中这张表被分配在这个实例的内存中,所以当我们用父类指针来操作一个子类的时候,这张虚函数表就像地图一样指明实际所应该调用的函数。

eg:

class Base{
public:virtual void f(){cout<<"Base::f"<<endl;}virtual void g(){cout<<"Base::g"<<endl;}virtual void h(){cout<<"Base::h"<<endl;}
};typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout<<"虚函数表的地址:"<<(int*)(&b)<<endl;
cout<<"虚函数表的第一个函数地址:"<<(int*)*(int*)(&b)<<endl;
pFun = (Fun)*((int*)*(int*)(&b));
pFun();/*
实际运行经果如下:(Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表的第一个函数地址:0044F148
Base::f同理可得到其他虚函数:
(Fun)*((int*)*(int*)(&b)+0);  // Base::f()
(Fun)*((int*)*(int*)(&b)+1);  // Base::g()
(Fun)*((int*)*(int*)(&b)+2);  // Base::h()
*/

(1)一般继承:假如有派生类继承基类,但是没有覆盖其虚函数

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;}
};Derive d;
((Fun)*((int*)*(int*)(&d)+0))();  // Base::f()
((Fun)*((int*)*(int*)(&d)+1))();  // Base::g()
((Fun)*((int*)*(int*)(&d)+2))();  // Base::h()
((Fun)*((int*)*(int*)(&d)+3))();  // Derive::f1()
((Fun)*((int*)*(int*)(&d)+4))();  // Derive::g1()
((Fun)*((int*)*(int*)(&d)+5))();  // Derive::h1()// 父类指针指向子类对象
Base* b = &d;
((Fun)*((int*)*(int*)b+0))(); // Base::f()
((Fun)*((int*)*(int*)b+1))(); // Base::g()
((Fun)*((int*)*(int*)b+2))(); // Base::h()
((Fun)*((int*)*(int*)b+3))(); // Derive::f1()
((Fun)*((int*)*(int*)b+4))(); // Derive::g1()
((Fun)*((int*)*(int*)b+5))(); // Derive::h1()/*
1、虚函数按照其声明顺序放在
2、父类的虚函数在子类的虚函数前面
*/

(2)一般继承:假如派生类覆盖了父类的虚函数

class Derive :public Base
{
public:virtual void f(){cout<<"Derive::f"<<endl;}virtual void g1(){cout<<"Derive::g1"<<endl;}virtual void h1(){cout<<"Derive::h1"<<endl;}
}Derive b;
Base* d = &b;
(Fun)*((int*)*(int*)d+0);  // Derive::f()
(Fun)*((int*)*(int*)d+1);  // Base::g()
(Fun)*((int*)*(int*)d+2);  // Base::h()
(Fun)*((int*)*(int*)d+3);  // Derive::g1()
(Fun)*((int*)*(int*)d+4);  // Derive::h1()// 由上可知,被覆盖的取代原来父类虚函数的位置,没覆盖的按声明顺序

(3)多重继承:无虚函数覆盖

class Base1{
public:virtual void f(){cout<<"Base1::f"<<endl;}virtual void g(){cout<<"Base1::g"<<endl;}virtual void h(){cout<<"Base1::h"<<endl;}
};class Base2{
public:virtual void f(){cout<<"Base2::f"<<endl;}virtual void g(){cout<<"Base2::g"<<endl;}virtual void h(){cout<<"Base2::h"<<endl;}
};class Base3{
public:virtual void f(){cout<<"Base3::f"<<endl;}virtual void g(){cout<<"Base3::g"<<endl;}virtual void h(){cout<<"Base3::h"<<endl;}
};class Derive1: public Base1,public Base2,public Base3
{
public:virtual void f1(){cout<<"Derive1::f1"<<endl;}virtual void g1(){cout<<"Derive1::g1"<<endl;}
};// #1 直接从子类对象查找虚函数对应的地址
Derive1 d;
((Fun)*((int*)*((int*)(&d)+0)+0))(); // Base1::f
((Fun)*((int*)*((int*)(&d)+0)+1))(); // Base1::g
((Fun)*((int*)*((int*)(&d)+0)+2))(); // Base1::h
((Fun)*((int*)*((int*)(&d)+0)+3))(); // Derive1::f1
((Fun)*((int*)*((int*)(&d)+0)+4))(); // Derive1::g1((Fun)((int*)*((int*)(&d)+1)+0))(); // Base2::f
((Fun)*((int*)*((int*)(&d)+1)+1))(); // Base2::g
((Fun)*((int*)*((int*)(&d)+1)+2))(); // Base2::h((Fun)*((int*)*((int*)(&d)+2)+0))(); // Base3::f
((Fun)*((int*)*((int*)(&d)+2)+1))(); // Base3::f
((Fun)*((int*)*((int*)(&d)+2)+2))(); // Base3::f// #2 从父类指针指向子类对象查找虚函数对应的地址
Base1* b1 = &d;
((Fun)*((int*)*((int*)b1+0)+0))(); // Base1::f
((Fun)*((int*)*((int*)b1+0)+1))(); // Base1::g
((Fun)*((int*)*((int*)b1+0)+2))(); // Base1::h
((Fun)*((int*)*((int*)b1+0)+3))(); // Derive1::f1
((Fun)*((int*)*((int*)b1+0)+4))(); // Derive1::g1Base2* b2 = &d;
((Fun)*((int*)*((int*)b2+0)+0))(); // Base2::f
((Fun)*((int*)*((int*)b2+0)+1))(); // Base2::g
((Fun)*((int*)*((int*)b2+0)+2))(); // Base2::hBase3* b3 = &d;
((Fun)*((int*)*((int*)b3+0)+0))(); // Base3::f
((Fun)*((int*)*((int*)b3+0)+1))(); // Base3::g
((Fun)*((int*)*((int*)b3+0)+2))(); // Base3::h/*
#1和#2都说明了子类的虚函数是紧接着第一个继承的父类,
而且多重继承的虚函数表是不一样的,
(1)对于子类对象来说,虚函数表像一个二维数组一样,
数组的第一行放置第一个继承父类的虚函数和子类本身的虚函数
数组的第二行放置第二个继承父类的虚函数
数组的第三行放置第个继承父类的虚函数
(2)对于父类来说,
Base1的虚函数表有其自身的虚函数以及子类的虚函数
Base2和Base3的虚函数表都只有其自身的虚函数
*/

(4)多重继承:有虚函数覆盖

class Derive2: public Base1,public Base2,public Base3
{
public:virtual void f(){cout<<"Derive2::f"<<endl;}virtual void g1(){cout<<"Derive2::g1"<<endl;}
};// #1 直接从子类对象查找虚函数对应的地址
Derive2 d;
((Fun)*((int*)*((int*)(&d)+0)+0))(); // Derive2::f// #2 从父类指针指向子类对象查找虚函数对应的地址
Base1* b1 = &d;
((Fun)*((int*)*((int*)b1+0)+0))(); // Derive2::fBase2* b2 = &d;
((Fun)*((int*)*((int*)b2+0)+0))(); // Derive2::fBase3* b3 = &d;
((Fun)*((int*)*((int*)b3+0)+0))(); // Derive2::f// 这时候每个父类的f()虚函数都会被子类的f()所覆盖,其他虚函数不变

另外,需要注意的是,当父类指针指向子类对象时,不能访问子类特有的虚函数,只能访问子类覆盖父类的虚函数。

虚函数与纯虚函数以及虚函数表之间的关系相关推荐

  1. 函数连续,函数可微,函数可导,偏导数存在,偏导数连续之间的关系

    1.可导 即设y=f(x)是一个单变量函数, 如果y在x=x0处存在导数y′=f′(x),则称y在x=x[0]处可导. 如果一个函数在x0处可导,那么它一定在x0处是连续函数. 函数可导定义: (1) ...

  2. 计算机函数测试结果误差表IF,误差函数表.doc

    误差函数表 误差函数表 xerf(x)erfc(x)xerf(x)erfc(x)0.000.0000000000001.0000000000000.50.5204998760350.479500123 ...

  3. 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数

    虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...

  4. 一口气搞懂《虚函数和纯虚函数》

    学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...

  5. 析构函数和虚函数、纯虚函数

    置于"-"是析构函数:析构函数因使用"-"符号(逻辑非运算符),表示它为逆构造函数,加上类名称来定义.  析构函数也是特殊的类成员函数,它没有返回类型,没有参数 ...

  6. C++知识点51——虚函数与纯虚函数(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146 10.练习 示例 class base { public:base() ...

  7. C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

    C++ 在继承中虚函数.纯虚函数.普通函数,三者的区别 1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的 ...

  8. c++中虚函数和纯虚函数定义

    只有用virtual声明类的成员函数,使之成为虚函数,不能将类外的普通函数声明为虚函数.因为虚函数的作用是允许在派生类中对基类的虚函数重新定义.所以虚函数只能用于类的继承层次结构中. 一个成员函数被声 ...

  9. C++ 虚函数和纯虚函数

    C++中这两个概念不容易区分. 首先这两个函数都是为了方便使用多态这种面向对象的特性.下面将介绍两个函数的不同点. 虚函数: 一个类中定义了虚函数,通过指向派生类的基类指针,访问派生类中同名覆盖成员函 ...

最新文章

  1. 广告基本知识-在线广告的市场
  2. shell的执行流控制
  3. Lyft Level 5 Challenge 2018 - Elimination Round翻车记
  4. python arcgis批量绘图_ARCGIS中Python实现批量裁剪
  5. java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
  6. (笔试题)和一半的组合数
  7. rsync工具介绍,rsync常用选项,rsync通过ssh同步
  8. IC卡读写器c++builder源代码续
  9. python 单向链表逆序_python实现单链表反转(经典笔试题)
  10. U盘解决 日立硬盘 c1门
  11. 机器人动力学参数辨识
  12. Unity 粒子特效
  13. css字体.ttf文件压缩3.1M变8K(原生和Vue中使用)
  14. 视觉SLAM总结-本质矩阵E分解
  15. 小米pro15拆机_小米笔记本Pro 15增强版拆解:重新定义高质低价
  16. 你需要知道的关于元宇宙NFT平台艺术数字藏品交易的一切
  17. vim编辑器---基本使用方法03(末行模式)
  18. 二阶常系数齐次线性微分方程通解的求取
  19. 【C#】如何给变量取一个好的名字
  20. corpus iweb_「As is depicted」和「As depicted」哪个对?

热门文章

  1. Idea 中出现:运行 Test 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。
  2. Codeforces 877 E Danil and a Part-time Job(线段树+dfs序)
  3. 王峰十问Nervos联合创始人王宁宁:缘何“中国最懂以太坊的人”要走中国公链的自主创新之路?...
  4. NYOJ迷宫寻宝(一)
  5. 机械革命Code01开启Hyper-V/安装Docker无限蓝屏解决方法
  6. kafka 命令重新启动_命令行基础知识:关闭和重新启动
  7. 计算机网络中tx和fx,100Base-TX/T4/FX以太网含义及用法
  8. Node.js 字体格式转换 ttf2eot ttf2woff ttf2svg
  9. 企业邮局在koomail里的设置方法
  10. 真正的IT女是什么样子的?