【步兵 c++】 多态&虚函数
2016-05-04 00:18
316 查看
【步兵 c++】 多态&虚函数 by EOS.
多态和虚函数
其实,多态的主要表现其实就是通过父类来调子类。而这种表现的主角就是虚函数。至于多态和虚函数的具体概念,请自行搜索,我这里就不多说了。
提到虚函数就不能不提虚函数表,虚函数表是一个什么东西呢,可以认为它就是一个存储函数指针的列表。
但是实际上,他是一个跳转到自身函数的中转站,不考虑多继承的话,每个类只有一个虚函数表,而却可以有多个实例,而每个实例又有一个虚函数表指针指向这个虚函数表,通过中专找到自身函数的位置。
下面结合实例来讲解
实例讲解
static inline void print(const char* str) { OutputDebugString(str); OutputDebugString("\n"); } class Hero { public: Hero() { print("Hero_Create"); }; ~Hero() { print("Hero_Destroy"); }; void attack() { print("Hero_Attack"); } }; class Knight : public Hero { public: Knight() { print("Knight_Create"); }; ~Knight() { print("Knight_Destroy"); }; void attack() { print("Knight_Attack"); } };
以上是准备工作,我们定义了一个英雄基类,然后派生了一个骑士的子类。
Hero* k = new Knight; k->attack(); delete k; //输出结果 //Hero_Create //Knight_Create //Hero_Attack //Hero_Destroy
显然,Knight的析构没有被调用,如果你在Knight的构造中,申请了大量的内存空间,预计在Knight的
的析构中去释放,结果却没有被执行,那么将直接造成内存泄漏。what the fuck~
而且attack方法也不是我们想要的结果。
当在Hero的析构函数前加入virtual关键字时,输出发生了改变
//virtual ~Hero() { print("Hero_Destroy"); }; Hero* k = new Knight; k->attack(); delete k; //输出结果 //Hero_Create //Knight_Create //Hero_Attack //Knight_Destroy //Hero_Destroy
虽然效果达到了,我们调用到了Knight的析构函数,但我不得不说,这个写法很不明智。
因为如果一个基类只有析构函数是虚函数,那么这个基类是没有完全意义的,上面可以看到,
攻击函数还是调用的Hero的,那么我用基类调不到子类的方法,无疑我们没有实现多态性。
所以,会有这样的说法:当析构函数是虚函数时,至少存在一个成员函数是虚函数。
说法不重要,重要的是我们要知道,基类要想调用到子类函数,那么这个函数必须是虚函数。
//virtual void attack() { print("Hero_Attack"); } Hero* k = new Knight; k->attack(); delete k; //输出结果 //Hero_Create //Knight_Create //Knight_Attack //Knight_Destroy //Hero_Destroy
这才是我们想要的结果,也就是多态的实现,父类来调子类。
虚函数表(vtbl)
Hero 虚函数表 |
---|
&Hero::析构 |
&Hero::attack |
Knight 虚函数表 |
---|
&Knight::析构 |
&Knight::attack |
Hero* k = new Knight;把Knight转为了Hero,但是这个对象的原有属性还在(首地址和内容没变),
实际表现就是,强制转换回来
Knight* k2 = static_cast<Knight*>(k);依旧可以当作Knight使用。
所以它的虚函数表指针(vptr)依然指向的是Knight的虚函数表,然后根据自己首地址和虚函数表的一个跳转,
这样就能找到自己的attack方法了。
这就是为什么
Hero* k = new Knight; k->attack();
能输出Knight_Attack的原因了。
关于析构的疑惑
首先需要了解Knight* k = new Knight; delete k2; //输出内容 //Hero_Create //Knight_Create //Knight_Destroy //Hero_Destroy
也就是说,派生类(也就是子类)的析构函数默认就会调用父类的析构函数,
但是因为我们通过基类(也就是父类)来调用,使其失去了原有的机能。
因为他找不到自己的析构函数,然后我们通过又虚析构函数,
让他找到了自己的析构,恢复了原有的机能,这一点希望大家不要迷糊。
–
总结和延伸
通过上面的讲解,从多态的实现,希望大家记住:当一个类作为基类来使用时,他的析构函数必须是虚函数,而且至少有一个成员函数是虚函数。
提到 析构函数是虚函数,那么自然会有构造函数可以是虚函数吗 的疑问。
正推:
如果构造函数是虚函数,那么他就会将存在与虚函数表中,那么我们只有通过实例中转才能找到,
但是,创建一个对象时,构造函数必须知道其确切类型,否则是无法分配内存的。
反推:
就算创建了对象找到了虚函数表,也不可能有指向构造函数的指针。因为构造函数不是普通的函数,
他是直接跟内存打交道的。
See Again~
之前
真爱无价,欢迎打赏~
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- c#中虚函数的相关使用方法
- C#与.net高级编程 C#的多态介绍
- C#中面向对象编程机制之多态学习笔记
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C#中的多态深入理解
- C#中多态、重载、重写区别分析
- 设计引导--一个鸭子游戏引发的设计理念(多态,继承,抽象,接口,策略者模式)
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题