本篇文章主要来讲述,C++多态的实现原理,也就是虚函数和虚函数列表是怎么回事?它们是如何实现多态的?

  • 虚函数概述:

首先,C++多态的实现是通过关键字virtual,有了这个关键字之后,通过继承的关系就可以在程序运行时,使用子类的函数替换掉父类的函数,达到多态的作用。

C++实现虚函数的方法:为每个类对象添加一个隐藏成员,隐藏成员保存了一个指针,这个指针叫虚表指针(vptr),它指向一个虚函数表(virtual function table, vtbl)(备注:一个类对象一个虚指针,一个类对应一个虚函数列表)。

虚函数表就像一个数组,表中有许多的槽(slot),每个槽中存放的是一个虚函数的地址(可以理解为数组里存放着指向每个虚函数的指针)。如下所示:

说明:

1.虚函数列表中的最后一个.表示的是虚函数列表的结束符,类似于字符串的/0。

2.虚函数指针往往是在类对象的第一个元素。

3.对于派生类而言,如果派生类实现了基类中的虚函数,在派生类的虚函数列表中,对应的虚函数会被替换成派生类的这个函数地址。

  • 单继承的多态实例分析

例子:

#include <iostream>
using namespace std;class Base  {
public:virtual void f();virtual void print();
};
void Base::f() {cout<<"Base f() ."<<endl;
}
void Base::print() {cout<< "Base print() ."<<endl;
}
class Derive :public Base{
public:void f1();virtual void print();
};
void Derive::f1() {cout<<"Derive f1() ."<<endl;
}
void Derive::print() {cout<< "Derive print() ."<<endl;
}
typedef void(*Fun)(void);
int main() {Base b;cout << "b:虚函数表的地址:" << (int*)(&b) << endl;for(int i = 0; i < 2; i++){unsigned long* vtbl = (unsigned long*)(*(unsigned long*)&b) + i;cout << "b:虚函数表的第"<<i<<"个函数地址:" << vtbl << endl;Fun pFun = (Fun) *(vtbl);pFun();}Base b1;cout << "b1:虚函数表的地址:" << (int*)(&b1) << endl;for(int i = 0; i < 2; i++){unsigned long* vtbl = (unsigned long*)(*(unsigned long*)&b1) + i;cout << "b1:虚函数表的第"<<i<<"个函数地址:" << vtbl << endl;Fun pFun = (Fun) *(vtbl);pFun();}Derive d;cout << "d:虚函数表的地址:" << (int*)(&d) << endl;for(int i = 0; i < 2; i++){unsigned long* vtbl = (unsigned long*)(*(unsigned long*)&d) + i;cout << "d:虚函数表的第"<<i<<"个函数地址:" << vtbl << endl;Fun pFun = (Fun) *(vtbl);pFun();}return 0;
}

输出结果:

b:虚函数表的地址:0x7fffd27d5e70 // 1. 对象b对应的虚指针
b:虚函数表的第0个函数地址:0x400f10 // 这里是对象b对应的虚函数列表首地址
Base f() .
b:虚函数表的第1个函数地址:0x400f18
Base print() .
b1:虚函数表的地址:0x7fffd27d5e80 // 2. 对象b1对应的虚指针
b1:虚函数表的第0个函数地址:0x400f10 // 这里是对象b1对应的虚函数列表首地址
Base f() .
b1:虚函数表的第1个函数地址:0x400f18
Base print() .
d:虚函数表的地址:0x7fffd27d5e90 // 3. 对象d对应的虚指针
d:虚函数表的第0个函数地址:0x400ef0 // 这里是对象d对应的虚函数列表首地址
Base f() .
d:虚函数表的第1个函数地址:0x400ef8
Derive print() .

运行结果分析:

1. 虚指针是跟对象绑定的,每一个类对象会对应一个虚指针,这个原因应该是虚指针是作为类的一个数据存储的导致的。例子参考 Base b和b1两个对象的虚指针地址,明显是不相同的。

2. 虚函数列表跟类是绑定的,每一个类会生成一个虚函数列表的地址,应该是存储在全局数据区。

3. 基类的虚函数列表和继承类的虚函数列表是两个,是不相同的,继承类的虚函数列表中存储的是继承类的虚函数实现,如果继承类没有实现基类的虚函数的话,会存储基类的虚函数地址。例子参见继承类的执行结果。

  • 多继承的多态实例分析

例子:

#include <iostream>
using namespace std;class Base  {
public:virtual void f();virtual void print();
};
void Base::f() {cout<<"Base f() ."<<endl;
}
void Base::print() {cout<< "Base print() ."<<endl;
}
class Base1  {
public:virtual void f1();virtual void print();
};
void Base1::f1() {cout<<"Base1 f() ."<<endl;
}
void Base1::print() {cout<< "Base1 print() ."<<endl;
}
class Derive :public Base,public Base1{
public:virtual void f();virtual void f1();
};
void Derive::f() {cout<<"Derive f() ."<<endl;
}
void Derive::f1() {cout<<"Derive f1() ."<<endl;
}typedef void(*Fun)(void);
int main() {void *p;Derive d;cout<<"d中虚指针数量:"<<sizeof(d)/sizeof(p)<<endl;unsigned long** vtbl = (unsigned long**)(&d);cout << "d:虚函数表的地址:" << (int*)(&d) << endl;for(int i = 0; i < 2; i++){for (int j = 0; j< 2; j++) {cout << "d:虚函数表的第["<<i<<"]["<<j<<"]个函数地址:" << &vtbl[i][j] << endl;Fun pFun = (Fun) (vtbl[i][j]);pFun();}}return 0;
}

输出结果:

d中虚指针数量:2 // 1. 这里可以看出是2个虚函数指针,对应于基类数量
d:虚函数表的地址:0x7fff5934cc80
d:虚函数表的第[0][0]个函数地址:0x400e90 // 2.第一个基类的虚函数表首地址
Derive f() .
d:虚函数表的第[0][1]个函数地址:0x400e98
Base print() .
d:虚函数表的第[1][0]个函数地址:0x400eb8 // 3.第二个基类的虚函数首地址
Derive f1() .
d:虚函数表的第[1][1]个函数地址:0x400ec0
Base1 print() .

执行结果分析:

通过上面执行结果,我们可以看出多继承的情况下,继承类对象中的虚函数指针个数是虚基类的数量。同样,如果继承类实现了虚基类中的虚函数的话,会被替换成继承类中实现的函数。

  • C++多态的副作用

C++采用虚函数和虚函数列表的方式来实现多态,确实给我们带来了很大的好处,让我们可以在不改变代码的时候,就能直接替换成运行的继承类的函数。

同样这种实现策略,却也带来了隐患,我们可以通过上面例子的方式来访问基类所有的虚函数,就算这个人虚函数被设置成了private也不行,所以让C++的封装行遭到了破坏。


(友情说明:Go语言系列一周会出1到2篇文章,并没有停止更新;C++最近有些囤货,尽量一天一篇文章。)


公众号 “灰子学技术”:

【C++】虚函数指针和虚函数列表相关推荐

  1. C++多态的原理(虚函数指针和虚函数表)

    C++多态的原理 (虚函数指针和虚函数表) 1.虚函数指针和虚函数表 2.继承中的虚函数表 2.1单继承中的虚函数表 2.2多继承中的虚函数表 3.多态的原理 4.总结 1.虚函数指针和虚函数表 以下 ...

  2. C++中虚继承产生的虚基类指针和虚基类表,虚函数产生的虚函数指针和虚函数表

    本博客主要通过查看类的内容的变化,深入探讨有关虚指针和虚表的问题. 一.虚继承产生的虚基类表指针和虚基类表 如下代码:写一个棱形继承,父类Base,子类Son1和Son2虚继承Base,又来一个类Gr ...

  3. 【C语言进阶深度学习记录】三十二 函数指针与使用函数指针实现回调函数

    回调函数是非常重要的概念 文章目录 1 函数的类型 2 函数指针 2.1 函数指针的使用 2.2 使用函数指针实现回调函数 3 总结 1 函数的类型 跟以前学数组的时候是一样的,C语言中的数组是有自己 ...

  4. C++ 函数指针 类成员函数指针

    一.函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址. 1.函数 ...

  5. c语言函数指针封装函数,C语言之函数指针、回调函数的使用

    一.背景 首先看下如下代码,这个定义是放在头文件的,在程序中tCdrvCallbackFkt也定义了另一个变量,而且括号后面还跟定义了几个变量,不理解这个定义. typedef void (PUBLI ...

  6. 【C/C 】浅谈C/C 中函数指针与回调函数

    01.函数指针 1.1.函数指针定义 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似.我们可以把函数的这个首地址(或称入口地址)赋予 ...

  7. Linux C 函数指针应用---回调函数

    (这里引用了知乎上一些知友的回答,感觉不错,有助于理解,这里引用作为借鉴,如有冒犯,烦请告知) 我们先来回顾一下函数指针,函数指针是专门用来存放函数地址的指针,函数地址是一个函数的入口地址,函数名代表 ...

  8. 函数指针与回调函数、句柄

    函数指针 定义: 函数指针是指向函数的指针变量. 因而"函数指针"本身首先应是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整型变量.字符型.数组一样,这里是指向函数. ...

  9. Linux C 函数指针应用---回调函数

    (这里引用了知乎上一些知友的回答,感觉不错,有助于理解,这里引用作为借鉴,如有冒犯,烦请告知) 我们先来回顾一下函数指针,函数指针是专门用来存放函数地址的指针,函数地址是一个函数的入口地址,函数名代表 ...

  10. C++函数指针与成员函数指针

    1.函数指针 函数指针:即可以指向函数地址的指针,经常被用作函数参数,作为回调函数使用. 既然是函数指针,那么肯定与普通函数有关联的,即返回值以及函数的参数列表与普通函数一致. 假设我们构造一个a+b ...

最新文章

  1. 2012 iis php mysql_Win2012 R2 IIS8.5+PHP(FastCGI)+MySQL运行环境搭建wordpress博客教程
  2. apache工作原理
  3. 1103: [POI2007]大都市meg(dfs序+线段树||树状数组)
  4. C# 中的 is 和 as 运算符 简单举例说明
  5. Python:集合、三元运算符
  6. APK的Mokey测试
  7. 我的第一个python web开发框架(4)——数据库结构设计与创建
  8. JavaScript编写计算器-《JavaScript王者归来》读书笔记1
  9. c语言的按位取反运算符
  10. R语言书籍学习02 《R语言数据分析、挖掘建模与可视化》-第一章 R语言必备基础知识
  11. Arduino与Proteus仿真实例-MPX4250压力传感器驱动仿真
  12. android开发apk捆绑,[原创]ApkAssist(Apk一键捆绑工具)
  13. 传播延迟与传输延迟以及带宽时延积
  14. github工具之OA综合利用python
  15. 找学习资料的网址/地方
  16. 关于Android如何集成QQ登录及分享
  17. 计算机网络智能化在铁路通信的发展,关于接入网技术在铁路通信中的应用
  18. 计算机一级msoffice考哪个版本,2021计算机二级office考哪个版本 如何备考
  19. 小游戏怎么提升app的用户留存
  20. mysql 评价表设计_来聊聊mysql单表评论系统怎么设计

热门文章

  1. Java笔记之线程池详解
  2. 2018年Fintech金融科技关键词和入行互金从业必懂知识
  3. Redis基础及原理详解
  4. Slope Trick
  5. PasswordEncoder
  6. 【无脑速通设计模式】设计模式简介 | 七大原则 | 模式分类
  7. 【化学信息学】药物靶标的主要类型和结构特征
  8. 千万级用户的Android客户端是如何养成的
  9. 武汉理工大学计算机考研排名2015,武汉理工大学和南京理工大学计算机研究生比较一下...
  10. 【Go语言Web开发框架】Iris快速入门