C++学习笔记--从虚函数说开去
虚函数与纯虚函数:
虚函数:在某基类中声明为virtual并在一个或多个派生类中被重新定义的成员函数,virtual 函数返回类型 函数名(参数表){函数体;} ,实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。注意虚函数在基类中是有定义的,即便定义是空。
纯虚函数:在基类中是没有定义的,必须由派生类重定义实现,否则不能由对象进行调用。
看下面的例子:
#include<iostream>
using namespace std;
class Cshape
{
public:
void SetColor(int color){m_nColor=color;}
virtual void Display(void){};
private:
int m_nColor;
}; class Crectangle:public Cshape{ //公有继承
public: }; void main()
{
Crectangle obRectangle;
Cshape* pShape=&obRectangle;
pShape->Display();
}
上面例子中,Display是虚函数,虽然是空定义,并且在派生类中没有重写该函数,但是上面的代码在编译阶段不会出错,而是在链接时出错,而如果将Display声明为纯虚函数,即 virtual void Display( )=0; 则该代码将在编译阶段出错,即编译器需要确保纯虚函数必须在子类中进行重写。注意:如果在基类中定义了纯虚函数,那么基类是不能实例化对象的,它只是提供一个接口,就想老板造房子,我就告诉你干嘛,具体怎么干还得靠自己,但是完全不说,你也当然不知道要干嘛。
虚函数怎么实现多态?
前面讲到虚函数的作用主要是实现多态,那么具体是怎么实现的?为什么需要用基类指针来指向派生类对象?
多态:C++中实现多态有虚函数,抽象类,重载,覆盖,模板等方法,虚函数是最重要的一种。把不同的子类对象当做父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化,实际上是通过基类来访问子对象的。那么子对象在程序中是怎么通过基类来找到与该对象对应的方法呢?这就要说到虚函数表。
虚函数表
虚函数是通过虚函数表来实现的,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当用父类的指针来操作一个子类的时候,这张虚函数表就像一张地图,指明实际应该调用的函数。
这里引用一下CSDN上的一篇题为“C++虚函数表解析”博客(博主的地址是http://blog.csdn.net/haoel):
假设我们有这样一个类,
class Base{
public:
virtual void f(){cout << "Base::f" << endl;}
virtual void g(){cout << "Base::g" << endl;}
virtual void h(){cout << "Base::h" << endl;}
};
我们可以通过Base的实例来得到虚函数表:下面是实际例程:
void main()
{
typedef void(*Fun)(void); //这里是定义一个参数为void,返回值为void的函数指针。
Base b;
Fun pFun=NULL;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表--第一个函数地址:" << (int*)*(int*)(&b) << endl;
// 调用第一个虚拟函数
pFun=(Fun)*((int*)*(int*)(&b));
pFun();
}
程序运行结果如下:(Windows 7 + Visual C++6.0)

由以上程序可以知道,如果要调用Base::g 和Base::h,代码如下:
pFun=(Fun)*((int*)*(int*)(&b)+);
pFun=(Fun)*((int*)*(int*)(&b)+);
虚拟函数表的图示如下:

类中的虚函数所占内存为多少?
虚函数要占用4个字节的内存空间,用来指定虚函数的虚拟函数表的入口地址。既然是入口地址,所以一个类的虚函数所占的地址是不变的,和虚拟函数的个数没有关系,如果派生类继承了多个超类的虚拟函数,那个这个派生类的虚拟函数所占用的内存空间等于4*(超类个数),即为每个超类维护一张虚函数表(关于多重继承的虚函数表内存分布,更详细请参考博客http://blog.csdn.net/haoel/article/details/1948051#)。另外要注意的是:父类和子类共享一个虚函数指针。这一点可以从下面的代码中看出:
#include<iostream>
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 Child:public Base{
virtual void i(){cout << "Child::i" << endl;}
};
void main()
{
typedef void(*Fun)(void);
Child child;
Base* pBase=&child;
Fun pFun=NULL;
cout << sizeof(Base) << " " << sizeof(Child) << endl;
cout << "虚拟函数i()的地址是:" << (int*)*(int*)pBase+ << endl;
pFun=(Fun)*((int*)*(int*)pBase+);
pFun();
}
代码运行结果如下:

C++学习笔记--从虚函数说开去的更多相关文章
- 学习笔记---C++虚函数,纯虚函数
1 .虚函数 假设people是man的父类,people类和man类都定义了实函数walk() people* p = new man(); p->walk(); 这里P执行的是people类 ...
- C++学习笔记27,虚函数作品
C++它指定虚函数的行为,但实现的作者编译器. 通常,编译器处理虚函数的方法是给每个对象加入一个隐藏成员.隐藏成员中保存了一个指向函数地址数组的指针. 这个数组称为虚函数表(virtual funct ...
- IOS学习笔记07---C语言函数-printf函数
IOS学习笔记07---C语言函数-printf函数 0 7.C语言5-printf函数 ------------------------- ----------------------------- ...
- IOS学习笔记06---C语言函数
IOS学习笔记06---C语言函数 -------------------------------------------- qq交流群:创梦技术交流群:251572072 ...
- Typescript 学习笔记三:函数
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- ES6学习笔记<三> 生成器函数与yield
为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...
- OpenCV 学习笔记03 findContours函数
opencv-python 4.0.1 1 函数释义 词义:发现轮廓! 从二进制图像中查找轮廓(Finds contours in a binary image):轮廓是形状分析和物体检测和识别的 ...
- canvas学习笔记、小函数整理
http://bbs.csdn.net/topics/391493648 canvas实例分享 2016-3-16 http://bbs.csdn.net/topics/390582151 html5 ...
- Go语言学习笔记七: 函数
Go语言学习笔记七: 函数 Go语言有函数还有方法,神奇不.这有点像python了. 函数定义 func function_name( [parameter list] ) [return_types ...
随机推荐
- debug - taotao项目 - IDEA拖动文件的自动重命名是超级巨坑, 一定要非常小心
大量的如下错误: org.springframework.beans.factory.BeanCreationException: Could not autowire field 还是要相信报错 不 ...
- Find the hotel HDU - 3193(RMQ)
题意: 有n个旅馆,从这n个旅馆中找出若干个旅馆,使得这若干个旅馆满足这样的条件:不能从其它和剩下的旅馆中找到一个价格和距离都小于这个旅馆的旅馆... 解析: 按price 排序,若price相同, ...
- Django对应的路由名称
1. 名字很长,修改起来很麻烦 2.Django提供了一种方法 在urls.py中修改了名字以后,html中会自动对应做修改.这样提交表单的时候就会比较方便了.跳转到写死的某个URL. 3.根据名字i ...
- 【刷题】BZOJ 1211 [HNOI2004]树的计数
Description 一个有n个结点的树,设它的结点分别为v1, v2, -, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵.给定n,d1, d2, -, dn,编程需要 ...
- 【BZOJ1565】【NOI2009】植物大战僵尸(网络流)
[BZOJ1565][NOI2009]植物大战僵尸(网络流) 题面 BZOJ 洛谷 题解 做了这么多神仙题,终于有一道能够凭借自己智商能够想出来的题目了.... 好感动. 这就是一个比较裸的最小割模型 ...
- 洛谷 P4319 变化的道路 解题报告
P4319 变化的道路 题目描述 小 w 和小 c 在 H 国,近年来,随着 H 国的发展,H 国的道路也在不断变化着 根据 H 国的道路法,H 国道路都有一个值 \(w\),表示如果小 w 和小 c ...
- 电子商务(电销)平台中财务模块(Finance)数据库设计明细
以下是自己在电子商务系统设计中的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 资金账户表 (finance_account)|-- 自动编号|-- 用户编号|-- 预付款 (ad ...
- Hbase(二)hbase建表
一.建表高级属性 下面几个 shell 命令在 hbase 操作中可以起到很到的作用,且主要体现在建表的过程中,看 下面几个 create 属性 1.bloomfilter 布隆过滤器 默认是 NON ...
- Linux基础--------监控系统、进程管理、软件包管理-------free、dd、kill、 rpm、yum、源码安装python
作业一:1) 开启Linux系统前添加一块大小为15G的SCSI硬盘 2) 开启系统,右击桌面,打开终端 3) 为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分区 ...
- NYOJ--7
原题链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=7 分析:x与y分开考虑,分别排序,邮局定在最中间的两个数之间就可以了. 街区最短路径问题 ...