C++ 虚函数表、虚函数讲解

前言

近期有不少同学私信我询问关于C++ 虚表和虚函数的相关问题,于是就打算写一篇关于C++虚函数和虚表的原理文章有助于大家更好的去理解和学习。


虚函数

概念

虚函数是一种在基类中用virtual关键字声明的函数,并在一个或多个派生类中再定义的函数。虚函数的特点是,只要定义一个基类的指针,就可以指向派生类的对象。

[注:无虚函数时,遵循以下规则:C++规定,定义为基类的指针,也能作指向派生类的指针使用,并可以用这个指向派生类对象的指针访问继承来的基类成员;但不能用它访问派生类的成员。]

  • 使用虚函数实现运行时的多态性的关键在于:必须通过基类指针访问这些函数。

  • 一旦一个函数定义为虚函数,无论它传下去多少层,一直保持为虚函数。

  • 把虚函数的再定义称为过载(overriding)而不叫重载(overloading)。

  • 纯虚函数:是定义在基类中的一种只给出函数原型,而没有任何与该基类有关的定义的函数。纯虚函数使得任何派生类都必须定义自己的函数版本。否则编译报错。纯虚函数定义的一般形式:

  virtual type func_name(args)=0;
  • 含有纯虚函数的基类称为抽象基类。抽象基类又一个重要特性:抽象类不能建立对象。但是抽象基类可以有指向自己的指针,以支持运行时的多态性。

虚函数示例代码

#include"test.h"
#include<iostream>
using namespace std;

class Base{

public:

    void printf()
    {
        cout << "Base printf()" << endl;
    }

    virtual void func()
    {
        cout << "Base func()" << endl;
    }

};

class Derived:public Base{

public:

    void printf()
    {
        cout << "Derived printf()" << endl;
    }

    virtual void func()
    {
        cout << "Derived func()" << endl;
    }

};

示例讲解

在以上示例代码中,我们声明了一个父类 Base,和它的一个派生类 Derive,其中 printf() 实例方法是非虚函数,而func()方法被声明为了虚函数。并且在子类中我们重新实现了printf() 和 func()方法。下面我们分别构造出一个 Derive 实例和Base 实例,分别用示例对象访问各func()和printf()方法。然后构造新的Derived实例,并分别将其地址赋给 Base 指针和 Derived 指针,然后分别输出访问func()和printf()方法的结果:

int main()
{
    Base baseObj = Base();
    baseObj.func();
    baseObj.printf();
    Derived derivedObj = Derived();
    derivedObj.func();
    derivedObj.printf();

    Derived* pDerivedObj = new Derived();
    Base* pBaseObj = pDerivedObj;
    pDerivedObj->func();
    pBaseObj->func();
    pDerivedObj->printf();
    pBaseObj->printf();
    delete pDerivedObj;
    return 0;
}

运行结果

Terminal output result:

Base func()
Base printf()
Derived func()
Derived printf()

Derived func()
Derived func()
Derived printf()
Base printf()

结果描述

Base和Derived实例分别访问func()和printf()方法。运行结果为各自对应的func()和printf()方法输出。
pDerivedObj 和 pBaseObj指针分别指向了Derived实例的地址,对于 pDerivedObj 指针的操作表现出来它本身的方法输出,然而当我们把相同对象的地址赋给 pBaseObj 指针时,可以发现它的非虚函数printf()竟然表现出了父类的行为,并没有被重写的样子。那到底是什么原因造成了这样的结果呢?我们继续往下看虚函数表的介绍。

虚函数表以及内存布局

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

示例代码(一下示例代码编译环境是X86并且采用4byte对齐)

非虚函数类

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
};

内存布局情况

  class Base1   size(8):
    +---
   0    | a
   4    | c
        | <alignment member> (size=3)
    +---
+---

博主未来为了让同学们注意一下在类内存布局中常见的字节对齐问题,就专门在Base1类中添加了char c变量。可以很清晰的看出在内存中a和c成员变量依据声明的顺序进行排列(类内偏移为0开始)并且有3字节用于对齐,成员函数不占内存空间。

单继承派生类不含非虚函数

class DerivedClass : public Base1
{
    int c;
public:
    void DerivedCommonFunction() {};
};

内存布局情况

  class DerivedClass    size(12):
    +---
   0    | +--- (base class Base1)
   0    | | a
   4    | | c
        | | <alignment member> (size=3)
    | +---
   8    | c
    +---

可以看到子类DerivedClass继承了父类Base1的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节。

存在虚函数类

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

内存分布情况

  class Base1   size(12):
    +---
   0    | {vfptr}
   4    | a
   8    | c
        | <alignment member> (size=3)
    +---

  Base1::$vftable@:
    | &Base1_meta
    |  0
   0    | &Base1::VirtualFunction

这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看一下。从上图可以看出虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,因为博主只写了一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。

通过上面这个例子有同学就问了虚表指针以及虚表是什么时候创建的呢? 构造函数创建的时候即类对象实例化的时候就创建的。那么如何利用虚表指针与虚表来实现多态的呢? 当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)从而可以实现多态。

单继承派生类中也有虚函数并且存在覆盖继承

class DerivedClass : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

内存分布情况

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &DerivedClass::VirtualFunction

上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和c,最后是子类的成员变量d,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的由于子类将父类的虚函数方法重写了即产生的虚表序号只有一个。

单继承派生类中也有虚函数并且不存在覆盖继承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

内存布局情况

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass::VirtualFunction1

此种情况内存分布中上半部分也只有一个虚表指针变量内存分布依次排列,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction1,那么会从子类中寻找对应的虚函数。

单继承派生类中即存在覆盖虚函数也存在非覆盖虚函数继承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass : public Base1
{
    int c;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
    void virtual VirtualFunction1() {};
};

内存布局情况

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | c
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &DerivedClass::VirtualFunction
   1    | &DerivedClass::VirtualFunction1

根据上面的内存布局情况,我们既重写了父类的虚函数,也有新添的虚函数,最终虚函数表0号和1号都是子类对应的虚函数地址。

多继承派生类中存在覆盖虚函数继承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass2 : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

内存布局

class Base1 size(12):
+---
0   | {vfptr}
4   | a
8   | c
    | <alignment member> (size=3)
+---

Base1::$vftable@:
| &Base1_meta
|  0
0   | &Base1::VirtualFunction

class DerivedClass1 size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | b
    +---

  DerivedClass1::$vftable@:
    | &DerivedClass1_meta
    |  0
   0    | &DerivedClass1::VirtualFunction

  DerivedClass1::VirtualFunction this adjustor: 0

  class DerivedClass2   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass2::$vftable@:
    | &DerivedClass2_meta
    |  0
   0    | &DerivedClass2::VirtualFunction

  DerivedClass2::VirtualFunction this adjustor: 0

  class DerivedDerivedClass size(36):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | +--- (base class Base1)
   0    | | | {vfptr}
   4    | | | a
   8    | | | c
        | | | <alignment member> (size=3)
    | | +---
  12    | | b
    | +---
  16    | +--- (base class DerivedClass2)
  16    | | +--- (base class Base1)
  16    | | | {vfptr}
  20    | | | a
  24    | | | c
        | | | <alignment member> (size=3)
    | | +---
  28    | | d
    | +---
  32    | e
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &DerivedDerivedClass::VirtualFunction

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -16
   0    | &thunk: this-=16; goto DerivedDerivedClass::VirtualFunction

根据上面的内存分布情况,此多继承覆盖情况,我分别把每个类的内存分布都打了出来,下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量b,以及Base1,Base1有一个0地址偏移的虚表指针,然后是成员变量a和c;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base1。
我们再来看看虚表继承情况,我们看到了有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量0也是DerivedClass1中的{vfptr}虚函数指针在DerivedDerivedClass的内存偏移,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。

DerivedDerivedClass()的虚表的VirtualFunction()指针
[站外图片上传中...(image-a847ce-1565342394654)]

多继承派生类中不存在覆盖虚函数继承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

class DerivedClass2 : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction2() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction3() {};
};

内存布局情况

    class Base1 size(12):
    +---
    0   | {vfptr}
    4   | a
    8   | c
        | <alignment member> (size=3)
    +---

    Base1::$vftable@:
    | &Base1_meta
    |  0
    0   | &Base1::VirtualFunction

  class DerivedClass1   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | b
    +---

  DerivedClass1::$vftable@:
    | &DerivedClass1_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass1::VirtualFunction1

  DerivedClass1::VirtualFunction1 this adjustor: 0

  class DerivedClass2   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass2::$vftable@:
    | &DerivedClass2_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass2::VirtualFunction2

  DerivedClass2::VirtualFunction2 this adjustor: 0

  class DerivedDerivedClass size(36):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | +--- (base class Base1)
   0    | | | {vfptr}
   4    | | | a
   8    | | | c
        | | | <alignment member> (size=3)
    | | +---
  12    | | b
    | +---
  16    | +--- (base class DerivedClass2)
  16    | | +--- (base class Base1)
  16    | | | {vfptr}
  20    | | | a
  24    | | | c
        | | | <alignment member> (size=3)
    | | +---
  28    | | d
    | +---
  32    | e
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass1::VirtualFunction1
   2    | &DerivedDerivedClass::VirtualFunction3

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -16
   0    | &Base1::VirtualFunction
   1    | &DerivedClass2::VirtualFunction2

此种情况的内存分布和覆盖多继承一样,唯一注意的就是在多继承中成员虚函数地址会保存到第一个继承父类的虚函数表。

多继承之虚继承派生类中存在覆盖虚函数继承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : virtual public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

class DerivedClass2 : virtual public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction2() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction3() {};
};

内存分布情况

    class Base1 size(12):
    +---
    0   | {vfptr}
    4   | a
    8   | c
        | <alignment member> (size=3)
    +---

    Base1::$vftable@:
    | &Base1_meta
    |  0
    0   | &Base1::VirtualFunction

  class DerivedClass1   size(24):
    +---
   0    | {vfptr}
   4    | {vbptr}
   8    | b
    +---
    +--- (virtual base Base1)
  12    | {vfptr}
  16    | a
  20    | c
        | <alignment member> (size=3)
    +---

  DerivedClass1::$vftable@DerivedClass1@:
    | &DerivedClass1_meta
    |  0
   0    | &DerivedClass1::VirtualFunction1

  DerivedClass1::$vbtable@:
   0    | -4
   1    | 8 (DerivedClass1d(DerivedClass1+4)Base1)

  DerivedClass1::$vftable@Base1@:
    | -12
   0    | &Base1::VirtualFunction

  DerivedClass1::VirtualFunction1 this adjustor: 0
  vbi:     class  offset o.vbptr  o.vbte fVtorDisp
             Base1      12       4       4 0

  class DerivedClass2   size(24):
    +---
   0    | {vfptr}
   4    | {vbptr}
   8    | d
    +---
    +--- (virtual base Base1)
  12    | {vfptr}
  16    | a
  20    | c
        | <alignment member> (size=3)
    +---

  DerivedClass2::$vftable@DerivedClass2@:
    | &DerivedClass2_meta
    |  0
   0    | &DerivedClass2::VirtualFunction2

  DerivedClass2::$vbtable@:
   0    | -4
   1    | 8 (DerivedClass2d(DerivedClass2+4)Base1)

  DerivedClass2::$vftable@Base1@:
    | -12
   0    | &Base1::VirtualFunction

  DerivedClass2::VirtualFunction2 this adjustor: 0
  vbi:     class  offset o.vbptr  o.vbte fVtorDisp
             Base1      12       4       4 0

  class DerivedDerivedClass size(40):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | {vfptr}
   4    | | {vbptr}
   8    | | b
    | +---
  12    | +--- (base class DerivedClass2)
  12    | | {vfptr}
  16    | | {vbptr}
  20    | | d
    | +---
  24    | e
    +---
    +--- (virtual base Base1)
  28    | {vfptr}
  32    | a
  36    | c
        | <alignment member> (size=3)
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &DerivedClass1::VirtualFunction1
   1    | &DerivedDerivedClass::VirtualFunction3

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -12
   0    | &DerivedClass2::VirtualFunction2

  DerivedDerivedClass::$vbtable@DerivedClass1@:
   0    | -4
   1    | 24 (DerivedDerivedClassd(DerivedClass1+4)Base1)

  DerivedDerivedClass::$vbtable@DerivedClass2@:
   0    | -4
   1    | 12 (DerivedDerivedClassd(DerivedClass2+4)Base1)

  DerivedDerivedClass::$vftable@Base1@:
    | -28
   0    | &Base1::VirtualFunction

上面虚继承的内存分布不做过多的叙述,下来总结一下:
虚继承的作用是减少了对基类的重复(在一般多继承中会造成二义性编译时出错,虚继承可以消除二义性),但是代价是增加了虚表指针的负担(更多的虚表指针)。根据以上示例当基类有虚函数时:

  • 1 每个类都有虚指针和虚表;

  • 2 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;

  • 3 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。

博客著作权归本作者所有,任何形式的转载都请联系作者获得授权并注明出处。

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

推荐阅读更多精彩内容

  • 1. 析构函数和虚析构函数 如果基类的析构函数是虚的,那么它的派生类的析构函数都是虚的 这将导致:当派生类析构的时...
    杰伦哎呦哎呦阅读 2,486评论 0 2
  • C++虚函数 C++虚函数是多态性实现的重要方式,当某个虚函数通过指针或者引用调用时,编译器产生的代码直到运行时才...
    小白将阅读 1,732评论 4 19
  • 几种语言的特性 汇编程序:将汇编语言源程序翻译成目标程序编译程序:将高级语言源程序翻译成目标程序解释程序:将高级语...
    囊萤映雪的萤阅读 2,871评论 1 5
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • 二十.多态与虚函数 多态:多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到...
    b83dcb2e8b71阅读 595评论 0 1