C++的多态性体现在两个方面,一个函数重载,一个虚函数,重载的多态性是在编译器编译期的时候早已经决定了。编译器实现函数重载的时候,也就是进行了名称粉碎。
而虚函数则是运行期的多态。多态有什么用? 可能你会有此疑惑。最普遍的说法是“提高代码的重用性”。
如果大家对逆向感兴趣的话,虚函数的内存结构那是必须得掌握的,下面我们来慢慢剖析类中虚函数的内存结构。
class Ca{public: Ca() { } virtual ~Ca() { } virtual void Fun1() { } virtual void Fun2() { } void Fun3() { }};class Cb : public Ca{public: virtual void Fun1() { }};
现在我们讨论单继承的虚表情况。首先我们定义2个对象:
int main(int argc, char* argv[]){ Ca theA; Cb theB; return 0;}
F10单步调试,虚表情况如下:
Ca虚表如下:
Ca::~Ca Ca::Fun1 Ca::Fun2
Cb的虚表呢?
首先拷贝一份父类的虚表,然后在把自己的与父类同名的虚函数覆盖上去,则是子类的虚表
Ca::~Ca Ca::Fun1 Ca::Fun2
最后Cb的虚表如下:
Cb::~Cb Cb::Fun1 Ca::Fun2
前提是子类必须有同名虚函数,则覆盖上去,这也就是为什么叫覆盖,而不叫隐藏的道理。
下面我们在来剖析多继承的虚表会是什么样的情况。
class Ca{public: Ca() { } virtual ~Ca() { } virtual void Fun1() { } virtual void Fun2() { }};class Cb{public: virtual void Fun1() { } virtual void Fun2() { }};class Cc : public Ca, public Cb{public: virtual void Fun1() { }};
int main(int argc, char* argv[]){ Ca theA; Cb theB; Cc theC; theC.Fun1(); return 0;}
看了前面单继承的剖析,我们来写出Ca和Cb的虚表
Ca::~Ca Ca:Fun1 Ca::Fun2
Cb::Fun1 Ca::Fun2
Cc的虚表呢?
前面已经说过,先拷贝父类的虚表,然后如果子类存在和父类同名的虚函数,则覆盖上去。
编译器该怎么覆盖?
我们先来看看虚表的安排情况:
可以看出。对应内存关系如下:
编译器按照声明类的前后关系,依次拷贝虚表。
先拷贝:
Ca::~Ca Ca:Fun1 Ca::Fun2
在依次在拷贝Cb。
Cb::Fun1 Ca::Fun2
然后。由于Cc由Fun1和~Cc虚函数,产生覆盖。
最后。覆盖后的Cc的虚表如下:
Cc::~Cc Cc:Fun1 Ca::Fun2
Cc::Fun1 Ca::Fun2
如果对虚表的结构了如执掌了。那么我们就可以通过数组下标的方式访问虚表,就可以突破编译器的限制! 当然不建议这么做!
现在,我们来实战一把。
题目如下:
“不能使用virtual关键字 ,模拟虚函数来表现出多态性:
写一基类Person 有sayHello,sayGoodbye函数
有一子类student 它也有自己的sayHello, sayGoodbye函数
请在这两个类里加入函数 vsayHello, vsayGoodbye函数
来表现出对象的多态性(分别调用自己的对应的sayHello和sayGoodbye)“
附带源码:
// Person.h: interface for the CPerson class.////#if !defined(AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_)#define AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000class CPerson;typedef void (CPerson::*CPERSON_PFUN)();class CPerson {public: void vsayGoodbye(); void vsayHello(); void sayGoodbye(); void sayHello(); CPerson(); CPerson(const int* pFun); ~CPerson();protected: static CPERSON_PFUN m_g_cPerpFun[2]; CPERSON_PFUN m_cpFun[2];};#endif // !defined(AFX_PERSON_H__F2DA095E_4153_409D_B7B0_BBEBCF4B9B63__INCLUDED_)
// Person.cpp: implementation of the CPerson class.////#include "stdafx.h"#include "Person.h"#include#include //// Construction/Destruction//CPERSON_PFUN CPerson::m_g_cPerpFun[2] = {CPerson::sayHello, CPerson::sayGoodbye};CPerson::CPerson(){ // 模拟虚表赋值 memcpy(m_cpFun, m_g_cPerpFun, sizeof(m_cpFun));}// 派生类构造前先构造父类虚表CPerson::CPerson(const int* pFun){ memcpy(m_cpFun, pFun, sizeof(m_cpFun));}CPerson::~CPerson(){ // 模拟还原虚表 memcpy(m_cpFun, m_g_cPerpFun, sizeof(m_cpFun));}void CPerson::sayHello(){ cout << "CPerson::sayHello()" << endl;}void CPerson::sayGoodbye(){ cout << "CPerson::sayGoodbye()" << endl;}void CPerson::vsayHello(){ (this->*(m_cpFun[0]))();}void CPerson::vsayGoodbye(){ (this->*(m_cpFun[1]))();}
// Student.h: interface for the CStudent class.////#if !defined(AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_)#define AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include "Person.h"class CStudent;typedef void (CStudent::*STUDENT_PFUN)();class CStudent : public CPerson {public: void vsayGoodbye(); void vsayHello(); void sayGoodbye(); void sayHello(); CStudent(); // VC6.0测试: // 发现编译器一个BUG,在析构函数前写上virtual关键字,则报错,会 // 导致m_g_cStudentpFun成员数组在分配总空间上为16个字节。 // Visual Stodio2005加和不加virtual关键字都测试正常. ~CStudent();private: static STUDENT_PFUN m_g_cStudentpFun[2];};#endif // !defined(AFX_STUDENT_H__4C004418_D79D_4809_900D_675FADEB905C__INCLUDED_)
// Student.cpp: implementation of the CStudent class.////#include "stdafx.h"#include "Student.h"#include//// Construction/Destruction//// 模拟编译器分配虚表信息STUDENT_PFUN CStudent::m_g_cStudentpFun[2] = {CStudent::sayHello, CStudent::sayGoodbye};CStudent::CStudent() : CPerson((int*)CStudent::m_g_cStudentpFun){ }CStudent::~CStudent(){}void CStudent::sayHello(){ cout << "CStudent::sayHello()" << endl;}void CStudent::sayGoodbye(){ cout << "CStudent::sayGoodbye()" << endl;}void CStudent::vsayHello(){ (this->*(m_cpFun[0]))();}void CStudent::vsayGoodbye(){ (this->*(m_cpFun[1]))();}
// Work2.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include "Person.h"#include "Student.h"int main(int argc, char* argv[]){ CStudent theStu; CPerson* pObj = &theStu; // 多态性 pObj->vsayHello(); pObj->vsayGoodbye(); return 0;}