理会动态联编(上篇)
副标题#e#
文章摘要
多态性是C++最主要的特征,多态性的实现得益于C++中的动 态联编技能。文章通过对动态联编的要害技能虚拟函数表举办深入的分解,理会 的动态联编的进程极其技能方式。
要害字
多态性 动态联编 VTABLE 虚函数
文章正文
一 从多态性谈动态联编的须要性
在进入主题之前先先容一下联编的观念。联编就是将模块可能函数归并在一起生 成可 执行代码的处理惩罚进程,同时对每个模块可能函数挪用分派内存地点,而且对 外部会见也分派正确的内存地点。凭据联编所举办的阶段差异,可分为两种差异 的联编要领:静态联编和动态联编。在编译阶段就将函数实现和函数挪用关联起 来称之为静态联编,静态联编在编译阶段就必需相识所有的函数或模块执行所需 要检测的信息,它对函数的选择是基于指向工具的指针(可能引用)的范例。反 之在措施执行的时候才举办这种关联称之为动态联编,动态联编对成员函数的选 择不是基于指针可能引用,而是基于工具范例,差异的工具范例将做出差异的编 译功效。C语言中,所有的联编都是静态联编。C++中一般环境下联编也是静态联 编,可是一旦涉及到多态性和虚函数就必需利用动态联编。
多态性是面向 工具的焦点,它的最主要的思想就是可以回收多种形式的本领,通过一个用户名 字可能用户接口完成差异的实现。凡是多态性被简朴的描写为"一个接口, 多个实现。在C++内里详细的表示为通过基类指针会见派生类的函数和要领。
下面我们看一个静态联编的例子,这种静态联编导致了我们不但愿的功效 。
//1.cpp
1.#include <iostream.h>
2.class shape{
3.public:
4.void draw(){cout<<"I am shape"<<endl;}
5.void fun(){draw();}
6.};
7.class circle:public shape{
8.public:
9.void draw() {cout<<"I am circle"<<endl;}
10.};
11.main(){
12.class circle oneshape;
13.oneshape.fun();
14.}
#p#副标题#e#
措施的输出功效我们但愿是"I am circle",但事实上 却输出了"I am shape"的功效,造成这个功效的原因是静态联编。静 态联编需要在编译时候就确定函数的实现,但事实上编译器在仅仅知道shape的地 址时候无法获取正确的挪用函数,它所知道的仅是shape::draw(),最终功效只能 是draw操纵束缚到shape类上。发生"I am shape"的功效就不敷为奇了 。
为了可以或许引起动态联编,我们只需要将需要动态联编的函数声明为虚函 数即可。动态联编只对虚函数起浸染。我们在通过基类并且只有通过基类会见派 生类的时候,只要这个基类中直接的可能间接(从上上层担任)的包括虚函数, 动态联编将自动叫醒。下面我们将上面的措施稍微改一下。
//2.cpp
1.#include <iostream.h>
2.class shape{
3.public:
4.virtual void draw(){cout<<"I am shape"<<endl;}
5.void fun(){draw();}
6.};
7.class circle:public shape{
8.public:
9.void draw() {cout<<"I am circle"<<endl;}
10.};
11.main(){
12.class circle oneshape;
13.fun (&oneshape);
14.}
措施执行获得了正确的功效"I am circle"。代码在VC6.0中执行。
到今朝为止我们不清楚动态联编的 执行机制,但我们可以做个揣摩。正如上面所说,对付函数的实际的工具范例不 同,联编功效也应该差异。在静态联编中,执行的坚苦在于无法通过基类知道需 要联编的子工具简直切范例。在1.cpp中shape的派生类既大概是circle,也大概是 其余的rectangle可能square等等,到底应该静态联编哪一个呢。疑惑正在于此。 动态联编在编译的时候应该也是不知道联编简直切工具范例的,(假如知道的话 就成了静态联编了),因此它只能通过必然的机制,使得在执行时候可以或许找到和 挪用正确的函数体。可以想象,为了到达这个目标,一些相关信息应该封装在对 象自身中。这些信息有点象身份证明,标识本身,这样在动态联编的时候,编译 器可以按照这些标志找到相应的函数体,"不要跑,就是你了"。
实际上的动态联编进程是什么样的呢。
二 工具范例信息
为了证明我们的意料,我们用下面的一个措施举办测试,下面的措施将获取普通 的类和包括虚函数的类的字节巨细。措施代码如下。
//3.cpp
1.#include <iostream.h>
2.class shape_novirtual{
3.int a;
4.public:
5.void draw() {cout<<"shape_novirtual::draw()"<<endl;}
6.};
7.class shape_virtual1{
8.int a;
9.public:
10.virtual void draw(){cout<<"shape_virtual::draw() "<<endl;}
11.};
12.class shape_virtual2{
13.int a;
14.public:
15.virtual void draw() {cout<<"shape_virtual2::draw()"<<endl;}
16.virtual void draw1(){cout<<"shape_virtual2::draw1() "<<endl;}
17.};
18.main(){
19.cout<<"sizeof(int)"<<sizeof(int) <<endl;
20.cout<<"sizeof(class shape_novirtual):"<<sizeof(shape_novirtual)<<endl;
21.cout<<"sizeof(void*):"<<sizeof(void*) <<endl;
22.cout<<"sizeof(class shape_virtual):"<<sizeof(shape_virtual)<<endl;
23.cout<<"sizeof(class shape_virtual2):"<<sizeof(shape_virtual2)<<endl;
24.}
VC6.0中运行功效如下:
#p#分页标题#e#
sizeof(int)4
sizeof (class shape_novirtual):4
sizeof(void*):4
sizeof(class shape_virtual1):8
sizeof(class shape_virtual2):8
Press any key to continue
从上面可以看出,没有虚函数的类shape_novirtual 的巨细为4,正好为int a的巨细。而带有虚函数的类shape_virtual1和 shape_virtual2的巨细除了int a的巨细还多出了4格个字节的巨细,这个巨细正 好是void*指针的巨细。到此刻为止我们根基上可以说带有虚函数的工具自身确实 插入了一些指针信息,并且这个指针信息并不跟着虚函数的增加而增大。
假如我们将每个类的成员变量int a去掉,VC6.0运行功效就会酿成下面的环境。
sizeof(int)4
sizeof(class shape_novirtual):1
sizeof (void*):4
sizeof(class shape_virtual1):4
sizeof(class shape_virtual2):4
Press any key to continue
上面的运行结 果应该让人感想破例。既然size(int)为4,此刻没有了这个成员变量,类 shape_novirtual应该字节巨细为0,但事实上C++编译器不答允工具为零长度。试 想一个长度为0的工具在内存中怎么存放?怎么获取它的地点?为了制止这种环境 ,C++强制给这种类插入一个缺省成员,长度为1。假如有自界说的变量,变量将 代替这个缺省成员。