C++中的多态与虚函数的内部实现

1、什么是多态
        多态性可以简单概括为“一个接口,多种方法”。
        也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。这是一种泛型技术,即用相同的代码实现不同的动作。这体现了面向对象编程的优越性。
        多态分为两种:
        (1)编译时多态:主要通过函数的重载和运算符的重载来实现。
        (2)运行时多态:主要通过虚函数来实现。
2、几个相关概念
        (1)覆盖、重写(override)
        override指派生类拥有和基类同名的函数。这其中又分为普通函数的override和虚函数的override。只有对虚函数的override才能体现出运行时多态
        (2)重载(overload)
        指同一个作用域出生多个函数名相同,但是形参不同的函数。编译器在编译的时候,通过实参的个数和类型,选择最终调用的函数。
        (3)隐藏(hide)
        分为两种:
            1)局部变量或者函数隐藏了全局变量或者函数
            2)派生类拥有和基类同名的成员函数或成员变量。
        产生的结果:使全局或基类的变量、函数不可见。
3、几个简单的例子

/******************************************************************************************************
* File:PolymorphismTest
* Introduction:测试构造函数、析构函数、虚构造函数与纯虚构造函数的一些特性。
* Author:AndyQ
* Date:20141114
* LastModifiedDate:20160113
*******************************************************************************************************/
#include "stdafx.h"
#include <iostream>
using namespace std;
class A
{
public:
	void foo()
	{
		printf("1\n");
	}
	virtual void fun()
	{
		printf("2\n");
	}
};
class B : public A
{
public:
	void foo()  //隐藏。重写
	{
		printf("3\n");
	}
	void fun()  //隐藏。重写
	{
		printf("4\n");
	}
};
int main(void)
{
	A a;
	B b;
	A *p = &a;
	p->foo();  //输出1。
	p->fun();  //输出2。
	p = &b;
	p->foo();  //输出1。因为p是基类指针,p->foo指向一个具有固定偏移量的函数。也就是基类函数
	p->fun();  //输出4。多态。虽然p是基类指针,但实际上指向的是一个子类对象。p->fun指向的是一个虚函数。按照动态类型,调用子类函数    
	B* pB = (B*)&a;
	pB->foo();   //输出3
	pB->fun();   //输出2。pB->fun是虚函数,按照动态类型指向基类。
	return 0;
}

4、运行时多态以及虚函数的内部实现
看了上边几个简单的例子,我恍然大悟,原来这就是多态,这么简单,明白啦!
好,那我们再看一个例子:
class A
{
public:
	virtual void FunA()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cout << "FunA1" << endl;
<span style="white-space:pre">	</span>};
<span style="white-space:pre">	</span>virtual void FunAA()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cout << "FunA2" << endl;
<span style="white-space:pre">	</span>}
};
class B
{
public:
	virtual void FunB()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cout << "FunB" << endl;
<span style="white-space:pre">	</span>}
};
class C :public A, public B
{
public:
<span style="white-space:pre">	</span>virtual void FunA()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cout << "FunA1C" << endl;
<span style="white-space:pre">	</span>};
};


int _tmain(int argc, _TCHAR* argv[])
{
<span style="white-space:pre">	</span>C objC;
<span style="white-space:pre">	</span>A *pA = &objC;
<span style="white-space:pre">	</span>B *pB = &objC;
<span style="white-space:pre">	</span>C *pC = &objC;

<span style="white-space:pre">	</span>printf("%d  %d\n", &objC, objC);
<span style="white-space:pre">	</span>printf("%d  %d\n", pA, *pA);
<span style="white-space:pre">	</span>printf("%d  %d\n", pB, *pB);
<span style="white-space:pre">	</span>printf("%d  %d\n", pC, *pC);

<span style="white-space:pre">	</span>return 0;
}

运行结果:
5241376  1563032
5241376  1563032
5241380  1563256
5241376  1563032
细心的同志一定发现了pB出了问题,为什么明明都是指向objC的指针,pB跟别人的值都不一样呢?
是不是编译器出了问题呢?

当然不是!我们先讲结论:
(1)每一个含有虚函数的类对象,都会在构造函数执行的时候,生成虚表(virtual table)。这个表,记录了对象的动态类型,决定了执行此对象的虚成员函数的时候,真正执行的那一个成员函数。
(2)对于有多个基类的类对象,会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。
        (3)在每一个类对象所占用的内存中,虚表位于最前边。
先从简单的单个基类说起:
class A
{
public:
<span style="white-space:pre">	</span>virtual void FunA()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>cout << "FunA1" << endl;
<span style="white-space:pre">	</span>};
<span style="white-space:pre">	</span>virtual void FunA2()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">	</span>cout << "FunA2" << endl;
<span style="white-space:pre">	</span>}
};

class C :public A
{
<span style="white-space:pre">	</span>virtual void FunA()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">	</span>cout << "FunA1C" << endl;
<span style="white-space:pre">	</span>};
};
int _tmain(int argc, _TCHAR* argv[])
{
<span style="white-space:pre">	</span>A *pA = new A;
<span style="white-space:pre">	</span>C *pC = new C;
<span style="white-space:pre">	</span>typedef void (*Fun)(void);

<span style="white-space:pre">	</span>Fun fun= (Fun)*((int*)(*(int*)pA));
<span style="white-space:pre">	</span>fun();//pA指向的第一个函数
<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*(int*)pA) +1);
<span style="white-space:pre">	</span>fun();//pA指向的第二个函数

<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*(int*)pC));
<span style="white-space:pre">	</span>fun();//pC指向的第一个函数
<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*(int*)pC) + 1);
<span style="white-space:pre">	</span>fun();//pC指向的第二个函数
<span style="white-space:pre">	</span>return 0;
}
运行结果:
FunA1
FunA2
FunA1C
FunA2

是不是有点晕?没关系。我一点一点解释:pA对应一个A的对象,我们可以画出这样的一个表:

这就是对象*pA的虚表,两个虚函数以声明顺序排列。pA指向对象*pA,则*(int*)pA指向此虚拟表,则(Fun)*((int*)(*(int*)pA))指向FunA,同理,(Fun)*((int*)(*(int*)pA) + 1)指向FunA2。所以,出现了前两个结果。
根据后两个结果, 我们可以推测*pC的虚表如下图所示:

也就是说,由于C中的FunA重写(override)了A中的FunA,虚拟表中虚拟函数的地址也被重写了。
就是这样,这就是多态实现的内部机制。

我们再回到最初的问题:为什么*pB出了问题。
根据上边的结论,我们大胆地进行猜测:由于C是由A、B派生而来,所以objC有两个虚拟表,而由于表的顺序,pA、pC都指向了对应于A的虚拟表,而pB则指向了对应于B的虚拟表。做个实验来验证我们的猜想是否正确:
我们不改变A、B、C类,将问题中的main改一下:
int _tmain(int argc, _TCHAR* argv[])
{
<span style="white-space:pre">	</span>C objC;
<span style="white-space:pre">	</span>A *pA = &objA;
<span style="white-space:pre">	</span>B *pB = &objC;
<span style="white-space:pre">	</span>C *pC = &objC;

<span style="white-space:pre">	</span>typedef void (*Fun)(void);

<span style="white-space:pre">	</span>Fun fun = (Fun)*((int*)(*(int*)pC));
<span style="white-space:pre">	</span>fun();//第一个表第一个函数
<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*(int*)pC)+1);
<span style="white-space:pre">	</span>fun();//第一个表第二个函数
<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*((int*)pC+1)));
<span style="white-space:pre">	</span>fun();<span style="white-space:pre">	</span>//第二个表第一个函数
<span style="white-space:pre">	</span>fun = (Fun)*((int*)(*(int*)pB));
<span style="white-space:pre">	</span>fun();//pB指向的表的第一个函数
<span style="white-space:pre">	</span>return 0;
}
哈哈,和我们的猜测完全一致:
FunA1C
FunA2
FunB
FunB
我们可以画出这样的虚函数图:
是这样!
说了这么多,我们把虚函数内部实现机制都讲完了。多态作为虚函数的一个应用,也就是这么实现的

理解好多态,对理解面向对象编程的思想有很大的帮助!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值