c++虚函数表详解

一、单继承和多继承

   

 我们了解一下何为单继承,何为多继承??

      单继承:一个子类只有一个直接父类。

      多继承:一个子类有两个或多个直接父类。

二、类的虚函数表与类实例的虚函数指针
   

   首先不考虑继承的情况。如果一个类中有虚函数,那么该类就有一个虚函数表。这个虚函数表是属于类的,所有该类的实例化对象中都会有一个虚函数表指针去指向该类的虚函数表。一个类的实例要么在堆上,要么在栈上。也就是说一个类可以有很多很多个实例。但是!一个类只能有一个虚函数表。在编译时,一个类的虚函数表就确定了,这也是为什么它放在了只读数据段中。

class ClassA
{
public:
	ClassA()
	{ cout << "ClassA::ClassA()" << endl;
	}
	virtual ~ClassA()
	{ cout << "ClassA::~ClassA()" << endl;
	}

	void func1()
	{ cout << "ClassA::func1()" << endl;
	}
	void func2()
	{ cout << "ClassA::func2()" << endl;
    }

	virtual void vfunc1()
	{ cout << "ClassA::vfunc1()" << endl;
	}
	virtual void vfunc2()
	{ cout << "ClassA::vfunc2()" << endl;
	 }
private:
	int aData;
};

è¿éåå¾çæè¿°

类的内存布局

虚函数表指针

成员变量

普通函数指针

 

 

 

 

三、多态代码及多重继承情况

 

class ClassA
{
public:
	ClassA()
	{ cout << "ClassA::ClassA()" << endl;
	}
	virtual ~ClassA()
	{ cout << "ClassA::~ClassA()" << endl;
	}

	void func1()
	{ cout << "ClassA::func1()" << endl;
	}
	void func2()
	{ cout << "ClassA::func2()" << endl;
    }

	virtual void vfunc1()
	{ cout << "ClassA::vfunc1()" << endl;
	}
	virtual void vfunc2()
	{ cout << "ClassA::vfunc2()" << endl;
	 }
private:
	int aData;
};

class ClassB : public ClassA
{
public:
	ClassB()
	{ cout << "ClassB::ClassB()" << endl;
	}
	virtual ~ClassB()
	{ cout << "ClassB::~ClassB()" << endl;
	 }

	void func1()
	{ cout << "ClassB::func1()" << endl;
	}
	virtual void vfunc1()
	{ cout << "ClassB::vfunc1()" << endl;
	 }
private:
	int bData;
};

class ClassC : public ClassB
{
public:
	ClassC()
	 { cout << "ClassC::ClassC()" << endl;
	  }
	virtual ~ClassC()
	{ cout << "ClassC::~ClassC()" << endl;
	 }

	void func2()
	{ cout << "ClassC::func2()" << endl;
	}
	virtual void vfunc2()
	{ cout << "ClassC::vfunc2()" << endl;
	}
private:
	int cData;
};

查看类的内存分布情况:

在第二部分中,我们讨论了在没有继承的情况下,虚函数表的逻辑结构。
那么在有继承情况下,只要基类有虚函数,子类不论实现或没实现,都有虚函数表。
请看上面代码
(1) ClassA是基类, 有普通函数: func1() func2() 。虚函数: vfunc1() vfunc2() ~ClassA()
(2) ClassB继承ClassA, 有普通函数: func1()。虚函数: vfunc1() ~ClassB()
(3) ClassC继承ClassB, 有普通函数: func2()。虚函数: vfunc2() ~ClassB()
基类的虚函数表和子类的虚函数表不是同一个表。下图是基类实例与多态情形下,数据逻辑结构。注意,虚函数表是在编译时确定的,属于类而不属于某个具体的实例。虚函数在代码段,仅有一份。 

è¿éåå¾çæè¿°

    ClassB继承与ClassA,其虚函数表是在ClassA虚函数表的基础上有所改动的,变化的仅仅是在子类中重写的虚函数。如果子类没有重写任何父类虚函数,那么子类的虚函数表和父类的虚函数表在内容上是一致的。

ClassA *a = new ClassA();
a->func1();                     //"ClassA::func1()"
a->func2();                     //"ClassA::func2()"
b->vfunc1();                    //"ClassA::vfunc1()"
b->vfunc2();                    //"ClassA::vfunc2()"

ClassA *b = new ClassB();
b->func1();                    // "ClassA::func1()"   隐藏了ClassB的func1(),因为是ClassA的指针
b->func2();                    // "ClassA::func2()"
b->vfunc1();                   // "ClassB::vfunc1()"  重写了ClassA的vfunc1()
b->vfunc2();                   // "ClassA::vfunc2()"


ClassB *b = new ClassB();
b->func1();                    //"ClassB::func1()"    ClassB的指针就调用自己的func1()
b->func2();                    //"ClassA::func2()" 
b->vfunc1();                   //"ClassB::vfunc1()"   重写了ClassA的vfunc1()
b->vfunc2();                   //"ClassA::vfunc2()"

    这个结果不难想象,看上图,ClassA类型的指针b能操作的范围只能是黑框中的范围,之所以实现了多态完全是因为子类的虚函数表指针与虚函数表的内容与基类不同.这个结果已经说明了C++的隐藏、重写(覆盖)特性。ClassB类型的指针b就找自己对应的函数

同理,也就不难推导出ClassC的逻辑结构图了

è¿éåå¾çæè¿°
类的继承情况是: ClassC继承ClassB,ClassB继承ClassA
这是一个多次单继承的情况。(多重继承)

ClassA* a = new ClassC;
a->func1();          // "ClassA::func1()"   隐藏ClassB::func1()               
a->func2();          // "ClassA::func2()"    隐藏ClassC::func2()
a->vfunc1();         // "ClassB::vfunc1()"    ClassB把ClassA::vfunc1()覆盖了
a->vfunc2();         // "ClassC::vfunc2()"    ClassC把ClassA::vfunc2()覆盖了

ClassB* b = new ClassC;
b->func1();                // "ClassB::func1()"    有权限操作时,子类优先
b->func2();                // "ClassA::func2()"    隐藏ClassC::func2()
b->vfunc1();            // "ClassB::vfunc1()"    ClassB把ClassA::vfunc1()覆盖了
b->vfunc2();            // "ClassB::vfunc2()"    ClassC把ClassA::vfunc2()覆盖了



四、多继承下的虚函数表 (同时继承多个基类)

多继承是指一个类同时继承了多个基类,假设这些基类都有虚函数,也就是说每个基类都有虚函数表,那么该子类的逻辑结果和虚函数表是什么样子呢?

class ClassA1
{
public:
    ClassA1() { cout << "ClassA1::ClassA1()" << endl; }
    virtual ~ClassA1() { cout << "ClassA1::~ClassA1()" << endl; }

    void func1() { cout << "ClassA1::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassA1::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassA1::vfunc2()" << endl; }
private:
    int a1Data;
};

class ClassA2
{
public:
    ClassA2() { cout << "ClassA2::ClassA2()" << endl; }
    virtual ~ClassA2() { cout << "ClassA2::~ClassA2()" << endl; }

    void func1() { cout << "ClassA2::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassA2::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassA2::vfunc2()" << endl; }
    virtual void vfunc4() { cout << "ClassA2::vfunc4()" << endl; }
private:
    int a2Data;
};

class ClassC : public ClassA1, public ClassA2
{
public:
    ClassC() { cout << "ClassC::ClassC()" << endl; }
    virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }

    void func1() { cout << "ClassC::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassC::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
    virtual void vfunc3() { cout << "ClassC::vfunc3()" << endl; }
};


ClassA1是第一个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2()。
ClassA2是第二个基类,拥有普通函数func1(),虚函数vfunc1() vfunc2(),vfunc4()。
ClassC依次继承ClassA1、ClassA2。普通函数func1(),虚函数vfunc1() vfunc2() vfunc3()。

内存分布图:

å¨è¿éæå¥å¾çæè¿°

在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数才算上这个基类。
如图,虚函数表指针01指向的虚函数表是以ClassA1的虚函数表为基础的,子类的ClassC::vfunc1(),和vfunc2()的函数指针覆盖了虚函数表01中的虚函数指针01的位置、02位置。当子类有多出来的虚函数时,添加在第一个虚函数表中
当有多个虚函数表时,虚函数表的结果是0代表没有下一个虚函数表。" * "号位置在不同操作系统中实现不同,代表有下一个虚函数表。
注意:
1.子类虚函数会覆盖每一个父类的每一个同名虚函数。
2.父类中没有的虚函数而子类有,填入第一个虚函数表中,且用父类指针是不能调用。
3.父类中有的虚函数而子类没有,则不覆盖。仅子类和该父类指针能调用。

最后给出代码和结果    

ClassA1 *a1 = new ClassC;
a1->func1();          // "ClassA1::func1()"    隐藏子类同名函数
a1->vfunc1();        // "ClassC::vfunc1()"    覆盖父类ClassA1虚函数
a1->vfunc2();        // "ClassC::vfunc2()"    覆盖父类ClassA1虚函数
ClassA2 *a2 = new ClassC;
a2->func1();        // "ClassA2::func1()"    隐藏子类同名函数
a2->vfunc1();        // "ClassC::vfunc1()"    覆盖父类ClassA2虚函数
a2->vfunc2();        // "ClassC::vfunc2()"    覆盖父类ClassA2虚函数
a2->vfunc4();        // "ClassA2::vfunc4()"   未被子类重写的父类虚函数
ClassC *c = new ClassC;
c->func1();        // "ClassC::func1()"
c->vfunc1();        // "ClassC::vfunc1()"
c->vfunc2();        // "ClassC::vfunc2()"
c->vfunc3();        // "ClassC::vfunc3()"
c->vfunc4();        // "ClassA2::func4()"

六、对于类的内存布局(继承情况看子类的布局情况)

C++对象的内存布局

要想知道C++对象的内存布局, 可以有多种方式, 比如:

  1. 输出成员变量的偏移, 通过offsetof宏来得到
  2. 通过调试器查看, 比如常用的VS

1.只有数据成员的对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
};

对象大小及偏移:

sizeof(Base1)8
offsetof(Base1, base1_1)0
offsetof(Base1, base1_2)4

可知对象布局:

可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
类对象的大小就是所有成员变量大小之和.

2.没有虚函数的对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    void foo(){}
};

结果如下:

sizeof(Base1)8
offsetof(Base1, base1_1)0
offsetof(Base1, base1_2)4

和前面的结果是一样的? 不需要有什么疑问对吧?
因为如果一个函数不是虚函数,那么他就不可能会发生动态绑定,也就不会对对象的布局造成任何影响.
当调用一个非虚函数时, 那么调用的一定就是当前指针类型拥有的那个成员函数. 这种调用机制在编译时期就确定下来了.

 

3.拥有仅一个虚函数的类对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
};

结果如下:

sizeof(Base1)12
offsetof(Base1, base1_1)4
offsetof(Base1, base1_2)8

咦? 多了4个字节? 且 base1_1 和 base1_2 的偏移都各自向后多了4个字节!
说明类对象的最前面被多加了4个字节的"东东", what's it?
现在, 我们通过VS2013来瞧瞧类Base1的变量b1的内存布局情况:
(由于我没有写构造函数, 所以变量的数据没有根据, 但虚函数是编译器为我们构造的, 数据正确!)
(Debug模式下, 未初始化的变量值为0xCCCCCCCC, 即:-858983460)

看到没? base1_1前面多了一个变量 __vfptr(常说的虚函数表vtable指针), 其类型为void**, 这说明它是一个void*指针(注意:不是数组).

再看看[0]元素, 其类型为void*, 其值为 ConsoleApplication2.exe!Base1::base1_fun1(void), 这是什么意思呢? 如果对WinDbg比较熟悉, 那么应该知道这是一种惯用表示手法, 她就是指 Base1::base1_fun1() 函数的地址.

可得, __vfptr的定义伪代码大概如下:


void*   __fun[1] = { &Base1::base1_fun1 };
const void**  __vfptr = &__fun[0];

值得注意的是:

  1. 上面只是一种伪代码方式, 语法不一定能通过
  2. 该类的对象大小为12个字节, 大小及偏移信息如下:
    sizeof(Base1)12
    offsetof(__vfptr)0
    offsetof(base1_1)4
    offsetof(base1_2)8
  3. 大家有没有留意这个__vfptr? 为什么它被定义成一个指向指针数组的指针, 而不是直接定义成一个指针数组呢?

    我为什么要提这样一个问题? 因为如果仅是一个指针的情况, 您就无法轻易地修改那个数组里面的内容, 因为她并不属于类对象的一部分.
    属于类对象的, 仅是一个指向虚函数表的一个指针__vfptr而已, 下一节我们将继续讨论这个问题.

  4. 注意到__vfptr前面的const修饰. 她修饰的是那个虚函数表, 而不是__vfptr.

现在的对象布局如下:

虚函数指针__vfptr位于所有的成员变量之前定义.

注意到: 我并未在此说明__vfptr的具体指向, 只是说明了现在类对象的布局情况.
接下来看一个稍微复杂一点的情况, 我将清楚地描述虚函数表的构成.

4.拥有多个虚函数的类对象

和前面一个例子差不多, 只是再加了一个虚函数. 定义如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};

大小以及偏移信息如下:

有情况!? 多了一个虚函数, 类对象大小却依然是12个字节!

再来看看VS形象的表现:

呀, __vfptr所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.

现在, 虚函数指针以及虚函数表的伪定义大概如下:

void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun2 };
const void** __vfptr = &__fun[0];

通过上面两张图表, 我们可以得到如下结论:

  1. 更加肯定前面我们所描述的: __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
  2. 增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况

前面已经提到过: __vfptr只是一个指针, 她指向一个数组, 并且: 这个数组没有包含到类定义内部, 那么她们之间是怎样一个关系呢?
不妨, 我们再定义一个类的变量b2, 现在再来看看__vfptr的指向:

通过Watch 1窗口我们看到:

  1. b1和b2是类的两个变量, 理所当然, 她们的地址是不同的(见 &b1 和 &b2)
  2. 虽然b1和b2是类的两个变量, 但是: 她们的__vfptr的指向却是同一个虚函数表

由此我们可以总结出:

同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

是时候该展示一下类对象的内存布局情况了:

不出意外, 很清晰明了地展示出来了吧? :-) hoho~~

那么问题就来了! 这个虚函数表保存在哪里呢? 其实, 我们无需过分追究她位于哪里, 重点是:

  1. 她是编译器在编译时期为我们创建好的, 只存在一份
  2. 定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表

5.单继承且本身不存在虚函数的继承类的内存布局

前面研究了那么多啦, 终于该到研究继承类了! 先研究单继承!

依然, 简单地定义一个继承类, 如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};
 
class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;
};

我们再来看看现在的内存布局(定义为Derive1 d1):

没错! 基类在上边, 继承类的成员在下边依次定义! 展开来看看:

经展开后来看, 前面部分完全就是Base1的东西: 虚函数表指针+成员变量定义.
并且, Base1的虚函数表的[0][1]两项还是其本身就拥有的函数: base1_fun1() 和 base1_fun2().

现在类的布局情况应该是下面这样:

 

6.本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局

标题`本身不存在虚函数`的说法有些不严谨, 我的意思是说: 除经过继承而得来的基类虚函数以外, 自身没有再定义其它的虚函数.

Ok, 既然存在基类虚函数覆盖, 那么来看看接下来的代码会产生何种影响:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};
 
class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;
 
    // 覆盖基类函数
    virtual void base1_fun1() {}
}

可以看到, Derive1类 重写了Base1类的base1_fun1()函数, 也就是常说的虚函数覆盖. 现在是怎样布局的呢?

特别注意我高亮的那一行: 原本是Base1::base1_fun1(), 但由于继承类重写了基类Base1的此方法, 所以现在变成了Derive1::base1_fun1()!

那么, 无论是通过Derive1的指针还是Base1的指针来调用此方法, 调用的都将是被继承类重写后的那个方法(函数), 多态发生鸟!!!

那么新的布局图:

7.定义了基类没有的虚函数的单继承的类对象布局

说明一下: 由于前面一种情况只会造成覆盖基类虚函数表的指针, 所以接下来我不再同时讨论虚函数覆盖的情况.

继续贴代码:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};
 
class Derive1 : public Base1
{
public:
    int derive1_1;
    int derive1_2;
 
    virtual void derive1_fun1() {}
};

和第5类不同的是多了一个自身定义的虚函数. 和第6类不同的是没有基类虚函数的覆盖.

咦, 有没有发现问题? 表面上看来几乎和第5种情况完全一样? 为嘛呢?
现在继承类明明定义了自身的虚函数, 但不见了??
那么, 来看看类对象的大小, 以及成员偏移情况吧:

居然没有变化!!! 前面12个字节是Base1的, 有没有觉得很奇怪?

好吧, 既然表面上没办法了, 我们就只能从汇编入手了, 来看看调用derive1_fun1()时的代码:

 

要注意: 我为什么使用指针的方式调用? 说明一下: 因为如果不使用指针调用, 虚函数调用是不会发生动态绑定的哦! 你若直接d1.derive1_fun1(); , 是不可能会发生动态绑定的, 但如果使用指针: pd1->derive1_fun1(); , 那么 pd1就无从知道她所指向的对象到底是Derive1 还是继承于Derive1的对象, 虽然这里我们并没有对象继承于Derive1, 但是她不得不这样做, 毕竟继承类不管你如何继承, 都不会影响到基类, 对吧?

 

汇编代码解释:

第2行: 由于pd1是指向d1的指针, 所以执行此句后 eax 就是d1的地址
第3行: 又因为Base1::__vfptr是Base1的第1个成员, 同时也是Derive1的第1个成员, 那么: &__vfptr == &d1, clear? 所以当执行完 mov edx, dword ptr[eax] 后, edx就得到了__vfptr的值, 也就是虚函数表的地址.
第5行: 由于是__thiscall调用, 所以把this保存到ecx中.
第6行: 一定要注意到那个 edx+8, 由于edx是虚函数表的地址, 那么 edx+8将是虚函数表的第3个元素, 也就是__vftable[2]!!!
第7行: 调用虚函数.

结果:

最新的类对象布局表示:

 

8.多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局

真快, 该看看多继承了, 多继承很常见, 特别是接口类中!

依然写点小类玩玩:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
 
    virtual void base2_fun1() {}
    virtual void base2_fun2() {}
};
 
// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;
 
    // 基类虚函数覆盖
    virtual void base1_fun1() {}
    virtual void base2_fun2() {}
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};

代码变得越来越长啦! 为了代码结构清晰, 我尽量简化定义.

初步了解一下对象大小及偏移信息:

貌似, 若有所思? 不管, 来看看VS再想:

哇, 不摆了! 一丝不挂啊! :-)

结论:

  1. 按照基类的声明顺序, 基类的成员依次分布在继承中.
  2. 注意被我高亮的那两行, 已经发生了虚函数覆盖!
  3. 我们自己定义的虚函数呢? 怎么还是看不见?!

结论:

Derive1的虚函数表依然是保存到第1个拥有虚函数表的那个基类的后面的.

看看现在的类对象布局图:

如果第1个基类没有虚函数表呢? 进入第9节!

 

9.如果第1个直接基类没有虚函数(表)

这次的代码应该比上一个要稍微简单一些, 因为把第1个类的虚函数给去掉鸟!


class Base1
{
public:
    int base1_1;
    int base1_2;
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
 
    virtual void base2_fun1() {}
    virtual void base2_fun2() {}
};
 
// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
}

来看看VS的布局:

这次相对前面一次的图来说还要简单啦! Base1已经没有虚函数表了! (真实情况并非完全这样, 请继续往下看!)

现在的大小及偏移情况: 注意: sizeof(Base1) == 8;

 

我们可以通过对基类成员变量求偏移来观察:

 

所以不难验证: 我们前面的推断是正确的, 谁有虚函数表, 谁就放在前面!

现在类的布局情况:

那么, 如果两个基类都没有虚函数表呢?

 

10.What if 两个基类都没有虚函数表

代码如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
};
 
// 多继承
class Derive1 : public Base1, public Base2
{
public:
    int derive1_1;
    int derive1_2;
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};

前面吃了个亏, 现在先来看看VS的基本布局:

可以看到, 现在__vfptr已经独立出来了, 不再属于Base1和Base2!

看看求偏移情况:

Ok, 问题解决! 注意高亮的那两行, &d1==&d1.__vfptr, 说明虚函数始终在最前面!

不用再废话, 相信大家对这种情况已经有底了.

对象布局:

11.如果有三个基类: 虚函数表分别是有, 没有, 有!

这种情况其实已经无需再讨论了, 作为一个完结篇....

上代码:

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};
 
class Base2
{
public:
    int base2_1;
    int base2_2;
};
 
class Base3
{
public:
    int base3_1;
    int base3_2;
 
    virtual void base3_fun1() {}
    virtual void base3_fun2() {}
};
 
// 多继承
class Derive1 : public Base1, public Base2, public Base3
{
public:
    int derive1_1;
    int derive1_2;
 
    // 自身定义的虚函数
    virtual void derive1_fun1() {}
    virtual void derive1_fun2() {}
};

只需要看看偏移就行了:

只需知道: 谁有虚函数表, 谁就往前靠!

总结:

     首先不考虑继承的情况。如果一个类中有虚函数,那么该类就有一个虚函数表。这个虚函数表是属于类的,所有该类的实例化对象中都会有一个虚函数表指针去指向该类的虚函数表。

     考虑在有继承情况下,只要基类有虚函数,子类不论实现或没实现,都有虚函数表基类的虚函数表和子类的虚函数表不是同一个表,在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类要有虚函数才算上这个基类。当子类有多个虚函数表的指针也就是有多个虚函数表时,子类有多出来的虚函数时,添加在第一个虚函数表中。

     对于一个类的对象的内存分布,对于继承情况来说,一个子类继承父类,哪个父类有虚函数表,哪个父类就在前面。虚函数始终在最前面(指向虚函数表的指针__vfptr)! 如果父类都没有虚函数,子类有虚函数,那么对于子类的内存分布,就是子类虚函数表指针在前,然后是父类的部分,最后是子类的部分

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值