C++虚函数表解析及作用

转载:http://blog.csdn.net/haoel/article/details/1948051/

转载的内容是具有很高的参考价值,但是这里,我们需要寻求另外的一些事情,就是虚函数表存在的意义。

其实,如果你在学习虚函数的时候感到迷茫,那是因为很多的书籍并没有解释虚函数的实现原理和意义,原因我想是因为如果展开来说这部分的内容可能会涉及的编译器实现的部分,所以这里,我也只是简单的阐述我的个人猜想,读者只能作为一种借鉴。

首先我要吹下牛B,C++真是一门好语言,初学的时候痛苦万分,学会了真的觉得自己鼠目寸光。

实例1代码如下:

#include <iostream>
using namespace std;

class A 
{
public:
    void Output() { cout << "A::Output()" << endl; }
};

int main(int argc, char ** argv) 
{
    A a;
    a.Output();
    return 0;
}
实例1输出

这是一个很基本的C++程序,我假设读者能够明白这个简单程序。
我们现在对这个程序做一个很小的改动

实例2代码如下:

#include <iostream>
using namespace std;

class A 
{
public:
    void Output() { cout << "A::Output()" << endl; }
};

int main(int argc, char ** argv) 
{
    A a;
    a.A::Output();
    return 0;
}
实例2输出

我们发现,我们在main函数中仅仅是使用了一个‘A::’,但是输出结果和实例1是相同的。这个说明一个问题,实例1的写法是实例2的写法的一个简单写法,或者这么说,实例1的写法默认了含有‘A::’,也就是说实例1含有一种默认行为。

有了上面的一个基本假设,我们现在来看看当子类和父类含有相同的函数名的时候会是一种什么现象。

实例3代码如下:

#include <iostream>
using namespace std;

class A 
{
public:
    void Output() { cout << "A::Output()" << endl; }
};

class B : public A
{
public:
    void Output() { cout << "B::Output()" << endl; }
};

int main(int argc, char ** argv) 
{
    B b;

    b.Output();              // 1
    b.B::Output();           // 2
    b.A::Output();           // 3
    b.B::A::Output();        // 4

    return 0;
}
实例3输出

我们看到1和2的输出是一致的,3和4的输出是一致的,我们可以推测,1是2的简写形式,3是4的一种简写形式(其实3不是4的一种简化形式,目前而言我只能猜测)。
我们可以从4的形式上发现,B是继承自A的,否则不可能会有这种形式的存在。
我们还可以从2的形式上发现,函数Output一定是一个成员函数,否则不会有此种形式的存在。
C++语言为了简化我们的行为,做了很多后台的行为,用来简化形式,这是我觉得C++语言伟大的地方,但是它隐藏的东西又没有人告诉你,所以初学的时候只能依葫芦画瓢,不知道怎么回事,看到一些C++代码总感到疑惑,觉得这种范式我并没有见过。

实例4代码如下:

#include <iostream>
using namespace std;

class A 
{
public:
    virtual void Output() { cout << "A::Output()" << endl; }
};

class B : public A
{
public:
    void Output() { cout << "B::Output()" << endl; }
};

int main(int argc, char ** argv) 
{
    B b;
    A * a = &b;

    (*a).Output();              // 1
    // (*a).B::Output();        // 2
    (*a).A::Output();           // 3
    (*a).B::A::Output();        // 4

    return 0;
}
实例4输出

我们发现2是没有通过编译的,原因我猜测是因为Output是成员函数,而成员函数的第一个形参是一个this指针,this指针的类型是B *,a的类型是A *,但是 A *是无法自动转换成B *的,所以语句2是无法通过编译的。
不过3和4的输出是一致的。现在我们来查看一下3和4的函数指针的值

实例5代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;

class A 
{
public:
    virtual void Output() { cout << "A::Output()" << endl; }
};

class B : public A
{
public:
    void Output() { cout << "B::Output()" << endl; }
};

int main(int argc, char ** argv) 
{
    B b;
    A * a = &b;

    (*a).Output();              // 1
    // (*a).B::Output();        // 2
    (*a).A::Output();           // 3
    (*a).B::A::Output();        // 4

    printf("%p\n", &(A::Output));
    printf("%p\n", &(B::A::Output));

    return 0;
}
输出5实例

我们发现他们的指针值是相同的。我们可以明白同一个函数的如果地址只有一个副本,但是他的访问方式可以有多种形式。

实例6代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;

class A 
{
public:
    virtual void Output() { cout << "A::Output()" << endl; }
};

class B : public A
{
public:
    void Output() { cout << "B::Output()" << endl; }
};

class C : public A
{

};

int main(int argc, char ** argv) 
{
    B b;
    A * a = &b;

    (*a).Output();              // 1
    // (*a).B::Output();        // 2
    (*a).A::Output();           // 3
    (*a).B::A::Output();        // 4
    (*a).C::A::Output();        // 5

    return 0;
}
实例6输出

从4和5的输出是一致的情况下,我们可以推断函数4和5的函数地址是一致的。

以上所有的代码仅仅是针对成员函数的情况,对于成员变量的情况后文继续阐述。
我们可以总结,虚函数的出现,仅仅是为了实现语句2,而虚函数是通过虚函数表实现的,也就是说,虚函数表是多态机制的根本。
我们可以看出基类对象是无法访问子类对象的特有的成员函数!!!这是语句2编译不通过的主要原因。基类不包含子类,但是子类是包含基类的。


下面让我们来看看类的成员变量。

实例7代码如下

#include <iostream>
#include <stdio.h>
using namespace std;

class A 
{
public:
    A() : m_mem(0) {}
    A(int x) : m_mem(x)  {}

    int m_mem;
};

class B : public A
{
public:
    B() : A(1) {}
};

class C : public A
{
public:
    C() : A(2) {}
};

class D : public B, public C
{
};

int main(int argc, char ** argv) 
{
    D b;
    
    // cout << b.m_mem << endl;     // 1
    cout << b.A::m_mem << endl;     // 2
    cout << b.B::m_mem << endl;     // 3
    cout << b.B::A::m_mem << endl;  // 4
    cout << b.C::m_mem << endl;     // 5
    cout << b.C::A::m_mem << endl;  // 6

    return 0;
}
实例7输出

1是无法通过编译的,因为存在二义性,2,3,4的结果是一致的,5,6的结果是一致的,从这个例子,我们知道,如果出现多次继承同一个基类的时候,我们会产生多个成员变量的副本,这个在实现上我们可以通过域名访问去除二义性,但是在实际的使用的时候,我们可能会觉得这是一个冗余的副本,我们并不希望存在这种情况,所以我们会有虚继承的方式来去达到我们的目的

实例8代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;

class A 
{
public:
    A() : m_mem(0) {}
    A(int x) : m_mem(x)  {}

    int m_mem;
};

class B : virtual public A
{
public:
    B() : A(1) {}
};

class C : virtual public A
{
public:
    C() : A(2) {}
};

class D : public B, public C
{
};

int main(int argc, char ** argv) 
{
    D b;
    
    cout << b.m_mem << endl;        // 1
    cout << b.A::m_mem << endl;     // 2
    cout << b.B::m_mem << endl;     // 3
    cout << b.B::A::m_mem << endl;  // 4
    cout << b.C::m_mem << endl;     // 5
    cout << b.C::A::m_mem << endl;  // 6

    return 0;
}
实例8输出

我们发现1是可以通过编译的了,而且所有的输出都是0,说明此时的类A的构造函数是直接通过D发起的,并不是间接的通过B或者C发起的。相反,实例7上面的结果说明类A的构造函数一共执行了两次,一次是通过B发起的,一次是通过C发起的。另外实例8说明了一个问题,如果类A已经执行过构造函数,B或者C不在调用类A的构造函数了。

这两个例子都有点绕,主要的原因是多重继承对于基类的构造函数的调用顺序,在有虚继承和没有虚继承的情况下是不同的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容