多一连、虚基类、虚函数、多态

C++多态篇3——虚函数表详解之多一连、虚函数表的打字与印刷

在上上一篇C++多态篇1一静态联编,动态联编、虚函数与虚函数表vtable中,小编最后简短明白析了瞬间虚函数表以及vptr。
而在上一篇文章C++多态篇2——虚函数表详解之从内部存款和储蓄器布局看函数重载,函数覆盖,函数隐蔽中本身详细介绍了虚函数的函数重载,函数覆盖以及函数遮蔽的主题素材,其实在那一篇著作中,对单承继的虚函数已经做了老大详实的解答了,若是对前边有意思味的人可以先看一下那篇小说。
在这一篇中,小编会具体的分析一下在分歧承袭中(单传承,多三翻五次)关于虚函数表在内部存储器中的布局以及怎么样打印虚函数表。不过关于在虚承接也等于有名的包罗虚函数的菱形承接,小编会放到前面继续写一篇文章详细剖判它在内部存款和储蓄器中的布局。
一、虚函数表的打字与印刷
在前面包车型客车文章中自个儿偏偏经过内部存款和储蓄器以及汇编代码中解析了虚函数表,未来本身就再介绍一种查看虚函数表的措施,
即打字与印刷虚函数表
咱俩都精通假若类中一旦有了虚函数,那么编写翻译器会自动给类中加七个字节的数额,那三个字节为指向虚函数表的指针,贮存的是虚函数表在内存中的地址。
要是用代码表示即:

void* vftable_of_A[] = {A::v_fn, ...}; 
//A类的虚函数表
class A 
{
    const void* vftable = vftable_of_A;
    //指向虚函数表的指针vftable
    virtual void v_fn() 
    {}
};
void* vftable_of_B[] = {B::v_fn,...};
//B类的虚函数表
class B 
{ 
    const void *vftable = vftable_of_B; 
    //指向B类虚函数表的指针vftable
    vritual void v_fn() 
    {} 
};

其实上边的代码大家还索要注意七个难点:
1.为什么虚函数表指针的花色为void*?
答:上边vftable的种类之所以用void*意味着,实际上一个类中持有虚函数的地方都被安置那些表中,分裂虚函数对应的函数指针类型不尽一样,所以那一个表的档案的次序不可能鲜明,可是在机器级里都是进口地址,即贰个叁拾贰个人的数字(32人系统),等到调用时,因为编写翻译器预先了然了函数的参数类型,重回值等,能够自动做好管理。
2.为啥虚函数表前要加const?
答:因为虚函数表是贰个常量表,在编写翻译时,编写翻译器会自动生成,何况不会退换,所以一旦有两个B类的实例,每一种实例中都会有三个vftable指针,但是它们对准的是同二个虚函数表。
那正是说大家什么样技术打印虚函数表呢?
第一大家用上一篇用过的例证。

class Base
{
public:
    Base(int data = 1)
        :b(data)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    virtual void Test1()
    {
        cout << "Base::Test1()" << endl;
    }
    virtual void Test2()
    {
        cout << "Base::Test2()" << endl;
    }
    virtual void Test3()
    {
        cout << "Base::Test3()" << endl;
    }
    int b;
};
class Derive :public Base
{
public:
    Derive(int data = 2)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl;
    }
    void Test1()
    {
        cout << "Derive::Test1()" << endl;
    }   
    void Test2()
    {
        cout << "Derive::Test2()" << endl;
    }
    int d;
};

基类和派生类的概念如上:
这正是说大家再定义多个函数,分别用来打字与印刷基类虚函数表以及派生类虚函数表。

typedef void(*VTable)();//定义函数指针
void PrintBase(Base &b)
{
    VTable vtb = (VTable)(*((int *)*(int *)&b));
    //vtb就是函数的地址
    int i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*(int *)&b)) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
}
void PrintDerive(Derive &b)
{
    VTable vtb = (VTable)(*((int *)*(int *)&b));
    //vtb就是函数的地址
    int i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*(int *)&b)) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
}

在main函数中开展调用:

int main()
{
    Base b;
    Derive d;
    PrintBase(b);
    PrintDerive(d);
    return 0;
}

打断点一步一步运维获得:
图片 1
后天自己对地点的代码进行一下表达:
第一大家要定义三个函数指针用来调用函数。
即:

typedef void(*VTable)();//定义函数指针

跟着我们将基类对象b的首多个字节给vtb变量。

VTable vtb = (VTable)(*((int *)*(int *)&b));

图片 2vc/C0ru49rqvyv21xLXY1rfBy6GjPGJyIC8+DQrL+dLUo7o8L3A+DQo8cHJlIGNsYXNzPQ==”brush:java;”>
vtb();//先调用函数 vtb = (VTable)*(((int*)(*(int *)&b)) + i); //再偏移四个字节

那样通晓vtb指向的是NULL,即到了虚函数的终结部分,就跳出循环。
与此相类似就能够打字与印刷出虚函数表的剧情了。
派生类的虚函数表打字与印刷同理,因为虚函数表在派生类中积存的体制同样。只不过形参类型变了而已。

二、单继承
因为上一篇小说中本身已经讲了众多关于单承继时虚函数表的主题材料,在那边笔者不再赘言相当多基础的标题,借使对那边不熟稔的人方可去看笔者上一篇小说,在此处本人举多少个简便的例子表达一(Wissu)下在单承继时,虚函数表的内部存款和储蓄器布局。

class Base
{
public:
    Base(int data = 1)
        :b(data)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    virtual void Test1()
    {
        cout << "Base::Test1()" << endl;
    }
    virtual void Test2()
    {
        cout << "Base::Test2()" << endl;
    }
    virtual void Test3()
    {
        cout << "Base::Test3()" << endl;
    }
    int b;
};
class Derive :public Base
{
public:
    Derive(int data = 2)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl;
    }
    void Test1()
    {
        cout << "Derive::Test1()" << endl;
    }   
    void Test2()
    {
        cout << "Derive::Test2()" << endl;
    }
    int d;
};

int main()
{
    Base b;
    b.Test1();
    b.Test2();
    b.Test3();
    Derive d;
    d.Test1();
    d.Test2();
    d.Test3();
    return 0;
}

从代码能够观看在基类定义了四个虚函数,依照大家在此在此以前所说的文化,大家清楚基类会生成一个虚函数表,那么派生类中我们定义了七个同名同参数的函数,为了让函数覆盖的场景更为同理可得,作者特意未有将Test3()定义,那么我们明日看一上周转结果:
图片 3
由结果可见,基类对象调用的是基类的函数。派生类对象调用的是怎么样呢?
咱俩步入内部存款和储蓄器中查看一下:
图片 4
三、多继承
1.不带函数覆盖
先看一下底下的例子:

class Base1
{
public:
    Base1(int data = 1)
        :b1(data)
    {
        cout << "Base1()" << endl;
    }
    ~Base1()
    {
        cout << "~Base1()" << endl;
    }
    virtual void Test1()
    {
        cout << "Base1::Test1()" << endl;
    }
    virtual void Test2()
    {
        cout << "Base1::Test2()" << endl;
    }
    virtual void Test3()
    {
        cout << "Base1::Test3()" << endl;
    }
    int b1;
};
class Base2
{
public:
    Base2(int data = 2)
        :b2(data)
    {
        cout << "Base2()" << endl;
    }
    ~Base2()
    {
        cout << "~Base2()" << endl;
    }
    virtual void Test4()
    {
        cout << "Base2::Test4()" << endl;
    }
    virtual void Test5()
    {
        cout << "Base2::Test5()" << endl;
    }
    virtual void Test6()
    {
        cout << "Base2::Test6()" << endl;
    }
    int b2;
};
class Derive :public Base1,public Base2
{
public:
    Derive(int data = 3)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl;
    }
    int d;
};

我们看一下这些多一连中派生类d的内部存储器布局:
图片 5
最近我们精晓了内部存款和储蓄器布局,大家就可以打字与印刷出派生类的虚函数表了。
因为在多三回九转中有七个虚函数表,所以大家现在要改一下打字与印刷函数,上面包车型大巴代码是作者曾经济体制改善过的。

void PrintDerive(Derive &b)
{
    VTable vtb = (VTable)(*((int *)*(int *)&b));
    //打印Derive中Base1的虚函数表
    //vtb就是函数的地址
    int i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*(int *)&b)) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
    /*************************/
    //打印Derive中Base2的虚函数表
    vtb = (VTable)(*((int *)*((int *)&b+2)));
    //vtb就是函数的地址
     i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*((int *)&b+2))) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
}

因为我们已经理解了派生类的内存布局,所以我们打字与印刷Base2的虚函数表的时候,改一下vtb的值就能够,小编以后以内部存款和储蓄器的角度深入分析一下什么转移vtb的值。
图片 6
笔者们将打字与印刷函数出席main函数中,打字与印刷出来的结果为:
图片 7
2.带函数覆盖
下边咱们再来看看带函数覆盖的多承继的境况:

class Base1
{
public:
    Base1(int data = 1)
        :b1(data)
    {
        cout << "Base1()" << endl;
    }
    ~Base1()
    {
        cout << "~Base1()" << endl;
    }
    virtual void Test1()
    {
        cout << "Base1::Test1()" << endl;
    }
    virtual void Test2()
    {
        cout << "Base1::Test2()" << endl;
    }
    virtual void Test3()
    {
        cout << "Base1::Test3()" << endl;
    }
    int b1;
};
class Base2
{
public:
    Base2(int data = 2)
        :b2(data)
    {
        cout << "Base2()" << endl;
    }
    ~Base2()
    {
        cout << "~Base2()" << endl;
    }
    virtual void Test1()
    {
        cout << "Base2::Test1()" << endl;
    }
    virtual void Test2()
    {
        cout << "Base2::Test2()" << endl;
    }
    virtual void Test3()
    {
        cout << "Base2::Test3()" << endl;
    }
    virtual void Test4()
    {
        cout << "Base2::Test4()" << endl;
    }
    virtual void Test5()
    {
        cout << "Base2::Test5()" << endl;
    }
    virtual void Test6()
    {
        cout << "Base2::Test6()" << endl;
    }
    int b2;
};
class Derive :public Base1,public Base2
{
public:
    Derive(int data = 3)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl;
    }
    void Test1()
    {
        cout << "Derive::Test1()" << endl;
    }   
    void Test4()
    {
        cout << "Derive::Test4()" << endl;
    }
    int d;
};

typedef void(*VTable)();//定义函数指针
void PrintDerive(Derive &b)
{
    VTable vtb = (VTable)(*((int *)*(int *)&b));
    //打印Derive中Base1的虚函数表
    //vtb就是函数的地址
    int i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*(int *)&b)) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
    /*************************/
    //打印Derive中Base2的虚函数表
    vtb = (VTable)(*((int *)*((int *)&b+2)));
    //vtb就是函数的地址
     i = 0;
    cout << "Vtable is " << endl;
    while (vtb != NULL)
    {
        cout << "NUM " << ++i << "Function " << endl;
        cout << "------->";
        vtb();
        vtb = (VTable)*(((int*)(*((int *)&b+2))) + i);
        //向后偏移四个字节
    }
    cout << "End" << endl;
}
int main()
{
    Derive d;
    PrintDerive(d);
    return 0;
}

运营结果为:
图片 8
由结果能够拿走,Test1()不只有覆盖了Base第11中学的Test1()函数,也覆盖了Base2类中的Test1()函数。
今后关于单承袭和多承袭中虚函数表的标题,应该都表达的大都啦,假若还会有标题,迎接留言建议~

在上上一篇C++多态篇1一静态联编,动态联编、虚函数与虚函数表vtable中,笔者最后简…

要是定义了以下的基类和派生类:

1.保持已有类的特点而构造新类的历程称为承继。
2.在已有类的基本功上新扩张本身的表征而发出新类的经过称为派生。
3.被持续的已有类称为基类(或父类)。
4.派生出的新类称为派生类(或子类)。

多态是C++的三大特点之一,是透过虚函数表来兑现的。关于虚函数表:

//若无一点名继承方式,对与结构体来讲正是公有承袭,对于类正是私有承继

class shape
{
    public:
    shape(int a){};             //基类构造
    int length(){};             //基类中与子类重名函数
    void display(){};           //基类中独有
    vitual int area(){};        //基类中虚函数
}

class rectangle:public shape
{
    public:
    rectangle(int);
    int length();
    int area();

}

  • 每种含有虚函数的类都有一张虚函数表(vtbl),表中各种是三个虚函数的地方,
    也正是说,虚函数表的每一类是二个虚函数的指针。
  • 假设继续的父类中有虚函数,那么子类中也是有虚函数表。
  • 从没有过虚函数的C++类,是不会有虚函数表的。
  • 多少个含有虚函数表的类实例化对象后,对象并不会间接存款和储蓄虚函数表,而是在早先的地方存放了二个对准虚函数表的指针。

//公有成员函数称为接口,公有承袭,基类的接口成为派生类的接口

1. 派生类的定义

rectangle::rectangle(int a):shape(int a){};     //派生类中使用基类构造函数
void rectangle::length()
{
    shape::length();                            //派生类中使用重名的父类函数
    display();                                  //派生类中使用父类中独有函数
}

概念及办事措施

接轨的目标:完结代码重用。

派生的目标:当新的主题素材应际而生,原有程序不能够化解(或无法完全消除)时,供给对本来程序开展改动。

例1,未有虚函数的情景

//承袭与重定义。重定义隐蔽了基类的积极分子。

2. 派生类实例的运用

rectangle rect();
shape theshape();
rect.length();          //调用rectangle::length()
rect.shape::length();   //显式指定调用基类函数shape::length()
rect.display();         //display()被继承,调用shape::display()

后续(代码重用 )

#include <iostream>
using namespace std;
class Shop
{
public:
    int m_a;
    void saleDailyGoods()
    {
        m_a=40;
        cout<<"卖日用品"<<m_a<<endl;
        return ;
    }   
};
class Market:public Shop//继承:代码重用 
{
    public:
        void saleFood()
        {
            m_a=10;
            cout<<"食品"<<m_a<<endl;
            return;
        }   
};
class Supermarket:public Market
{
    public: 
        void pay()
        {
            cout<<"代缴水费"<<m_a<<endl;
            return;
        }
}; 

int main()
{
    Supermarket m;
    m.saleDailyGoods();
    m.saleFood();
    m.pay();
    return 0;
}
运行结果:
卖日用品40
食品10
代缴水费10

————————————————————————————————————————————————————————————————————————————

#include <iostream>
using namespace std;
class base
{
     void f(){cout<<"base::f"<<endl;};
     void g(){cout<<"base::g"<<endl;};
     void h(){cout<<"base::h"<<endl;};
};

int main(int argc, const char * argv[]) {
    cout<<"size of Base: "<<sizeof(Base)<<endl;
    return 0;
}

//可以重定义基类数据和基类成员函数。

3. 指标的积极分子变量与成员函数的分歧

分子变量在指标开始化之后,依然能够进行赋值,更换。事实上,对目的的别的操作,也单独是对它的积极分子变量进行操作。
成员函数在对象开头化那一刻使明确下来,成员函数是依附该目的的连串实行绑定的,是编写翻译期已经规定的,静态绑定。

梯次实例的积极分子变量的地方是不一致的,与实例的首地址有关。
各类实例的成员函数其实是国有的,因为只与他们的连串相关。

分子变量的地址(满含虚表变量)是比照各实例的首地址给的,各实例之间分歧等。
分子函数的地方是依据实例的体系给的,各实例间同样。

接二连三的探问权限

     class Base�    
{�     
public:�      
  int m1;�     
protected:�      
  int m2;�     
private:�      
  int m3;�    
};�
class PrivateClass:private Base //私有继承�    
{�     
public:�      
  void test()�      
  {�       
    ml=1; //ok:将m1据为private�       
    m2=2; //ok:将m2据为private�       
    m3=3; //不可访问�      
  }�    
};�
                 
class DerivedFromPri:public PrivateClass�    
{ �     
public:�      
  void test()�      
  {�       
    m1=1; //不可访问基类的私有成员�       
    m2=2; //不可访问�       
    m3=3; //不可访问�      
  }�    
};

    class ProtectedClass:protected Base //保护继承�    
{�     
public:�      
  void test()�      
    {�       
        m1=1;    //ml据为protected�       
        m2=2; //m2据为protected�       
        m3=3; //不可访问�      
    }�    
};�    
class DerivedFromPro:public ProtectedClass�    
{�     
public: �      
  void test()�      
    {�       
        m1=1; //ml 仍为protected�       
        m2=2; //m2 仍为protected�       
        m3=3; //不可访问�      
    }�    
};

运转结果:size of Base: 1。这里大概说雅培(Abbott)下,C++的积极分子方法不占用内部存款和储蓄器,size为1是因为实例化对象供给在内部存款和储蓄器中分红一块地点,所以编写翻译器就分配了贰个字节给空类。

//重写基类成员函数有二种状态:1、与基类完全同样2、与基类成员函数名同样,参数不一样

4. 类型的转移

要区分指标的类型调换目的指针的类型转变:

  1. 实则,指针的花色能够Infiniti制调换,只是指针指向的地方产生变化而已。
  2. 目的的类型调换,实际上是将右值的具有成员变量的值赋给左值。注意:右值的虚表变量不会赋值给左值!
  3. 就地转移,实际上是隐含地调用了一遍转变后项目的构造函数,产生了三个暂且对象,将那几个一时对象作为左值。

    rectangle rect;
    shape shp;
    shp = (shape)rect;          //合法,将rect中与shp相同的那部分成员变量赋给shp
                                //等价于 shp.a = rect.a, shp.b = rect.b
                                // shp.vtable并没有被rect.vtable覆盖! 仍然是原来的。

    rect = (rectangle)shp;      //不合法,因为rect的一些成员变量,shp并没有,所以赋值时要出错
                                //rect.a  = shp.a, rect.b = shp.b,rect.c = shp.?
    rectangle* prect;
    shape* pshp;
    pshp = &rect;               //pshp指向了rect的首地址
                                //pshp指向的成员变量是rect的成员变量
                                //(pshp可以指向一些shape类型没有的成员变量)
                                // 但pshp绑定的成员函数还是shape类型的成员函数。
                                //pshp->vtable就是rect.vtable, 但pshp->length()调用的是 shape::length();
    prect = &shp;        //合法,但是没有实际意义
#include <iostream>
using namespace std;
class B0
{
public:
    void display()
    {
        cout<<"B0 display"<<endl;
    }
};
class B1:public B0//可以调用B0 
{
public:
    void display()
    {
        cout<<"B1 display"<<endl;
    }
};
class D1:public B1//可以调用B0,B1 
{
public:
    void display()
    {
        cout<<"D1 display"<<endl;
    }
};
int main()
{
    B0 b0;
    b0.display();
    B1 b1;
    b1.display();
    D1 d1;
    d1.B0::display();
    d1.B1::display();
    d1.display();
    return 0;
}
运行结果:
B0 display
B1 display
B0 display
B1 display
B1 display

例2,有虚函数的境况

重载:要在同二个类中,爆发再功用域一样的界定

5. 虚成员函数与平日成员函数

地点已提了,二个实例的家常成员函数在编写翻译期已经依据它的体系确实了。而虚函数不是那样的,它的调用是根椐实例的成员变量虚表查到的。

rectangle rect;
shape shp;
shp.area();                 //其实是shp.vtable->area();
rect.area();                //rect.vtable->area();

shape* pshp;
pshp = ▭
pshp->area();               //实际上是pshp->vtable->area(),而pshp->vtable == rect.vtable,所以调用的是 rectangle::area();
pshp->length();             //length是普通的成员函数,根据pshp的类型确定,所以调用的是shape::length();


((shape)rect).area();       //就地转换并调用,相当于下面的的两行代码
shape shp2 = rect;
shp2.area();                //shp2的成员变量被rect的成员变量覆盖,但shp2.vtable并没有变成rect.vtable,仍然调用shape::area();

多态(基类指针指向子类对象 )

#include <iostream>
using namespace std;
class B0
{
public:
    void display()
    {
        cout<<"B0 display"<<endl;
    }
};
class B1:public B0//可以调用B0 
{
public:
    void display()
    {
        cout<<"B1 display"<<endl;
    }
};
class D1:public B1//可以调用B0,B1 
{
public:
    void display()
    {
        cout<<"D1 display"<<endl;
    }
};

int main()
{
    B0 b0,*p0;//多态:基类指针指向子类对象 
    p0=&b0;
    p0->display();
    B1 b1;
    p0=&b1;
    p0->display();  
    D1 d1;
    p0=&d1;
    p0->display();
    return 0;
}
运行结果:
B0 display
B0 display
B0 display
class Base {
public:

    virtual void f() {cout<<"base::f"<<endl;}
    virtual void g() {cout<<"base::g"<<endl;}
    virtual void h() {cout<<"base::h"<<endl;}

};

int main(int argc, const char * argv[]) {
    cout<<"size of Base: "<<sizeof(Base)<<endl;
    return 0;
}

覆盖(override):须要虚函数才方可

6. 虚函数的用途:统同样式的调用

shape* pshps = [&triangle(), &rectangel()];
for(auto ipshp: pshps)
    ipshp->area();          //分别调用了各个不同类型实例的area();

纯虚函数(virtual)

#include <iostream>
using namespace std;
class B0
{
public:
    virtual void display()=0;//纯虚函数,下面的函数一定要重写
};
class B1:public B0//可以调用B0 
{
public:
    void display()
    {
        cout<<"B1 display"<<endl;
    }
};

int main()
{
    B0 *p0;
    B1 b1;
    p0=&b1;
    p0->display();  
}

运作结果:size of Base: 8。这8个字节,正是虚函数表的指针。

一而再与构成:

多一连(不提议选取)

#include <iostream>
using namespace std;
class Father
{
public:
    void money ()
    {
        cout<<"有钱"<<endl;
    }
};
class Mother
{
public:
    void face ()
    {
        cout<<"漂亮,高挑"<<endl;
    }
};
class Son:public Father,public Mother//多继承
{

};

int main()
{
    Son s;
    s.money();
    s.face();
}
运行结果:
有钱
漂亮,高挑

例3,下边,大家品尝通过地点偏移的秘籍来调用虚函数:

无论承接如故结合,本质是都以把子对象放在新类型中,两个都采纳构造函数的开始化列表去组织这一个子对象。

虚函数 ,用在菱形承接结构

#include <iostream>
using namespace std;
class Person
{
public:
    int id;
};
class  Father:virtual public Person//虚函数 ,用在菱形继承结构 
{
public:
    void money ()
    {
        cout<<"有钱"<<endl;
    }
};
class Mother:virtual public Person
{
public:
    void face ()
    {
        cout<<"漂亮,高挑"<<id<<endl;
    }
};
class Son:public Father,public Mother//多重继承 
{
public:
    void aa()
    {
        cout<<id<<endl;
    } 

};

int main()
{
    Son s;
    s.money();
    s.id=3;
    Person*p=&s;
    s.aa();
    Mother m;
    m.id=5;
    m.face();

}
运行结果:
有钱
3
漂亮,高挑5
class Base {
public:

    virtual void f() {cout<<"base::f"<<endl;}
    virtual void g() {cout<<"base::g"<<endl;}
    virtual void h() {cout<<"base::h"<<endl;}

};

int main(int argc, const char * argv[]) {
    typedef void(*Func)(void);
    Base b;
    Base *d = &b;    
    long* pvptr = (long*)d; //获取指向虚函数表的指针,即b对象的首地址
    cout << "vtable address:" << *pvptr << endl; //打印虚函数表的地址

    long* vptr = (long*)*pvptr; //虚函数表的首地址
    Func f = (Func)(*vptr); //虚函数表首地址的值为第一个虚函数的函数指针
    Func g = (Func)(*(vptr+1)); //继续偏移指针,找到第二个虚函数指针
    Func h = (Func)(*(vptr+2)); //以此类推


//  下面这种写法与上面实际效果相同,只是指针偏移方式不同而已
//  Func f = (Func)vptr[0];  
//  Func g = (Func)vptr[1]; 
//  Func h = (Func)vptr[2]; 

    f();
    g();
    h();

    return 0;
}

重组常常是在盼望新类内部有着已存在的类的职能时利用,而并非愿意已存在类作为它的接口。组合通过内置一个指标以促成新类的功力,而新类顾客观察的是新定义的接口,实际不是缘于老类的接口.(has-a)

运营结果:

借使希望新类与已存在的类有平等的接口(在那基础上能够扩大和煦的积极分子)。那时候需求承袭.(is-a)

vtable address:4294975712
base::f
base::g
base::h

无法自动承继的积极分子函数:

参照他事他说加以考察文章:

下边两篇小说都讲的极细,从无三回九转到多三番两次,图文都要有,有意思味的同窗能够学习一下!
http://www.cnblogs.com/Ripper-Y/archive/2012/05/15/2501930.html
http://www.cppblog.com/dawnbreak/archive/2009/03/10/76084.html

构造函数、析构函数、=运算符    

基类的构造函数不被三番五次,派生类中须求申明本身的构造函数。评释构造函数时,只要求对本类中新添成员举办初步化,对一而再来的基类成员的初阶化调用基类构造函数完结。

派生类的构造函数要求给基类的构造函数字传送递参数。

 

只好在构造函数开首化列表中初叶化的地方:

const成员    const int a=10;

引用成员     int n2=100;  int& rn=n2;

基类未有私下认可构造函数的时候,基类的构造函数要在派生类构造函数的发轫化列表中调用。

 

派生类对象的构造次序:

先调用基类的对象成员的构造函数、然后调用基类构造函数、再然后是派生类对象成员的构造函数、最后是派生类自个儿的构造函数。析构次序,正好与之相反。

 

友元关系不可能被持续:A是B的友元类    C是A的派生类  那么C并非B的友元类。

友元关系是单向的,A是B的友元类,B并不是A的友元类

友元关系是不能被传送的。

静态成员与承继:

静态成员无所谓承继,被抱有指标分享,独有一份。

 

改动与后续:

派生类到基类的转移

1、 派生类以public方式继续基类时,编写翻译器能够活动推行的转会

派生类对象指针自动转化为基类对象指针

派生类对象援引自动转载为基类对象引用

派生类对象活动转发为基类对象(特有成员未有)

2、 当派生类以private/protected格局继续基类时

派生类对象指针(援引)转化为基类对象指针(援引)需用强制类型转化,但不能够用static_cast,要用reinterpret_cast.

static_cast用于编写翻译期承认的静态调换,比如说从char到int,从double到int。或然具备调换构造函数。也许重载了类型转换运算符。

reinterpret_cast用于编译器不料定的静态调换。比方从int* 转换为int.

const_cast去除常量性

无法把派生类对象强制转化为基类对象。

改动构造函数(带三个参数构造函数):将其余类型转换为类项目

类型调换运算符重载:将目前类类型转变为别的门类

3、 多种承接

多种承继,二个派生类能够有五个基类

派生类同期继续多少个基类的成员,越来越好的软件重用。

恐怕会有大气的二义性,八个基类大概带有同名变量或函数

多三番两次中国化工进出口总集团解歧义的法子:基类名::数据成员名

4、 虚传承与虚基类

当派生类从八个基类派生,而这个基类又从三个基类派生,则在拜谒此一并基类的积极分马时将生出二义性——采纳虚基类来化解。

虚基类的引进:用于有协同基类的场合

证明:以virtual修饰表明基类 class B1: virtual public BB

职能:首要用来缓和多承继时或然爆发的对同一基类承袭数十次而爆发的二义性难题。为最远的派生类提供独一的基类成员,而不重复产生多次正片。

5、 虚基类及其派生类构造函数。

虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行开头化的。在任何继承结构中,直接或直接接轨虚基类的有所派生类,都不能够不在构造函数的成员伊始化列表中提交对虚基类的构造函数的调用。要是未列出,则意味着调用该虚基类的私下认可构造函数。每一种承继类都必得对虚基类的数据成员实行伊始化构造

 1 #include<iostream>
 2 using namespace std;
 3 class Furniture
 4 {
 5 public:
 6     Furniture(int weight):weight_(weight){ cout << "Furniture..." << endl; }
 7     ~Furniture() { cout << "~Furniture..." << endl; }
 8     int weight_;
 9 };
10 class Bed:virtual public Furniture
11 {
12 public:
13     Bed(int weight) :Furniture(weight) { cout << "Bed..." << endl; }
14     ~Bed() { cout <<"~Bed..." << endl; }
15     void Sleep() { cout << "sleep..." << endl; }
16     
17 };
18 class Sofa:virtual public Furniture
19 {
20 public:
21     Sofa(int weight) :Furniture(weight) { cout << "Sofa..." << endl; }
22     ~Sofa() { cout << "~Sofa..." << endl; }
23     void Watch() { cout << "watch..." << endl; }
24 
25 };
26 class SofaBed :public Bed, public Sofa
27 {
28 public:
29     //需要提供对虚基类成员的构造
30     //每个继承类都需要在构造函数中给出对虚基类成员的构造
31     SofaBed(int weight):Bed(weight),Sofa(weight), Furniture(weight)
32     {
33         cout << "SofaBed..." << endl;
34         FoldIn();
35     }
36     ~SofaBed() { cout << "~SofaBed..." << endl; }
37     void FoldOut() { cout << "FoldOut..."<< endl; }
38     void FoldIn() { cout << "FoldIn..." << endl; }
39 };
40 int main(void)
41 {
42     SofaBed sofabed(5);
43     sofabed.weight_ = 10;
44     //SofaBed sb;
45     sofabed.Watch();
46     sofabed.FoldOut();
47     return 0;
48 }
49 /*
50 Furniture...
51 Bed...
52 Sofa...
53 SofaBed...
54 FoldIn...
55 watch...
56 FoldOut...
57 ~SofaBed...
58 ~Sofa...
59 ~Bed...
60 ~Furniture...
61 */

在创制目标时,独有最尾部派生类的构造函数调用了虚基类构造函数,别的类调用虚基类构造函数被忽视。

 

类/对象大小的总括:

类大小计算遵守前面学过的结构体对其尺度。

类大小与数据成员有关与成员函数无关

类大小与静态数据成员非亲非故

虚传承对类大小的熏陶

虚函数对类大小的影响

#include<iostream>
using namespace std;
class BB
{
public:
    int bb_;
};
class B1:virtual public BB
{
public:
    int b1_;
};
class B2 :virtual public BB
{
public:
    int b2_;
};
class DD :public B1, public B2
{
public:
    int dd_;
};
int main(void)
{
    cout << sizeof(BB) << endl;//4
    cout << sizeof(B1) << endl;//12
    cout << sizeof(DD) << endl;//24

    B1 b1;
    cout << &b1 << endl;//00CFFADC  存放的是虚基类表指针
    cout << &b1.bb_ << endl;//00CFFAE4  bb_
    cout << &b1.b1_ << endl;//00CFFAE0  b1_
    long **p;
    p = (long **)&b1;
    cout << p[0][0] << endl;//表内容  0  本类地址与虚基类表地址的差
    cout << p[0][1] <<endl;//表内容 8   虚基类地址与虚基类表地址的差

    DD dd;
    cout << &dd << endl;
    cout << &dd.b1_ << endl;
    cout << &dd.b2_ << endl;
    cout << &dd.bb_ << endl;
    cout << &dd.dd_ << endl;

    p =(long**) &dd;
    cout << p[0][0] << endl;//0
    cout << p[0][1] << endl;//20
    cout << p[2][0] << endl;//0
    cout << p[2][1] << endl;//12
    return 0;
}

BB* pp;
    pp = &dd;//pp指向的地址实际上并不是dd首地址,实际上pp会根据偏移指向bb_
    pp->bb_;//间接访问。需要运行时支持

虚函数与多态:

         多态性是面向对象程序设计的根本特点之一。

多态性是指发生一样的新闻被分化的类型的对象接受时有十分大希望引致全盘两样的作为。调用同名的函数导致差异的行事。

多态的贯彻:

         函数重载

         运算符重载

         模板

         虚函数(动态绑定,动态多态)

静态绑定与动态绑定:

        
静态绑定,绑定进程出现在编写翻译阶段,在编写翻译期就曾经规定要调用的函数了。

        
动态绑定,绑定进度职业在程序运营时实行,在程序运营时才明确将在调用的函数。

虚函数的定义:在基类中冠以注重字virtual的分子函数。

虚函数的概念:virtual 函数类型  函数名称(参数列表)

万一三个函数在基类中被声称为虚函数,则他所在派生类中都以虚函数。唯有因而基类指针也许引用调用虚函数技巧引发动态绑定。虚函数无法声称为静态。

 1 #include<iostream>
 2 using namespace std;
 3 class Base
 4 {
 5 public:
 6     virtual void Fun1()
 7     {
 8         cout << "Base::Fun1..." << endl;
 9     }
10     virtual void Fun2()
11     {
12         cout << "Base::Fun2..." << endl;
13     }
14     void Fun3()
15     {
16         cout << "Base::Fun3..." << endl;
17     }
18 };
19 class Derived:public Base
20 {
21 public:
22     virtual void Fun1()
23     {
24         cout << "Derived::Fun1..." << endl;
25     }
26     virtual void Fun2()
27     {
28         cout << "Derived::Fun2..." << endl;
29     }
30     void Fun3()
31     {
32         cout << "Derived::Fun3..." << endl;
33     }
34 };
35 int main(void)
36 {
37     Base* p;
38     Derived d;
39     p = &d;
40     /*
41     Derived::Fun1...
42     Derived::Fun2...
43     Base::Fun3...
44     */
45     p->Fun1();
46     p->Fun2();//虚函数,基类指针指向派生类对象,调用派生类成员函数
47     p->Fun3();//不是虚函数,根据指针类型来确定
48     return 0;
49 }

 

虚析构函数:

        
析构函数可以是虚函数。若无虚析构函数,派生类指针调用基类析构函数,不会调用派生类析构函数。派生类对象的析构应该调用派生类析构,所以能够将析构函数宣称为虚函数。

 1 #include<iostream>
 2 using namespace std;
 3 class Base
 4 {
 5 public:
 6     virtual void Fun1()
 7     {
 8         cout << "Base::Fun1..." << endl;
 9     }
10     virtual void Fun2()
11     {
12         cout << "Base::Fun2..." << endl;
13     }
14     void Fun3()
15     {
16         cout << "Base::Fun3..." << endl;
17     }
18     Base()
19     {
20         cout << "Base..." << endl;
21     }
    //如果一个类要作为多态基类,要将析构函数定义成虚函数。否则可能存在内存泄漏
22     virtual ~Base()
23     {
24         cout << "~Base..." << endl;
25     }
26 };
27 class Derived:public Base
28 {
29 public:
30     virtual void Fun1()
31     {
32         cout << "Derived::Fun1..." << endl;
33     }
34     virtual void Fun2()
35     {
36         cout << "Derived::Fun2..." << endl;
37     }
38     void Fun3()
39     {
40         cout << "Derived::Fun3..." << endl;
41     }
42     Derived()
43     {
44         cout << "Derived..." << endl;
45     }
46     virtual ~Derived()
47     {
48         cout << "~Derived..." << endl;
49     }
50 };
51 int main(void)
52 {
53     Base* p;
54     Derived d;
55     p = &d;
56     /*
57     Derived::Fun1...
58     Derived::Fun2...
59     Base::Fun3...
60     */
61     p->Fun1();
62     p->Fun2();//虚函数,基类指针指向派生类对象,调用派生类成员函数。
63     p->Fun3();//不是虚函数,根据指针类型来确定
64 
65     Base* q;
66     q = new Derived;
67     q->Fun1();
68     delete q;//如果析构函数不是虚的,则delete析构函数就会调用指针所对应类的析构函数,不会调用派生类析构函数
69     return 0;
70 }
71 /*
72 Base...
73 Derived...
74 Derived::Fun1...
75 Derived::Fun2...
76 Base::Fun3...
77 Base...
78 Derived...
79 Derived::Fun1...
80 ~Derived...
81 ~Base...
82 ~Derived...
83 ~Base...
84 请按任意键继续. . .
85 */

曾几何时供给虚析构函数?

1、当你也许通过基类指针删除派生类对象时

2、假如你希图允许别的人通过基类指针调用对象的析构函数(通过delete那样做很符合规律),何况被析构的目标是有注重的析构函数的派生类的靶子,就要求让基类的析构函数作为虚函数。

上边包车型大巴例证表明了虚函数的内存模型:

 1 #include<iostream>
 2 using namespace std;
 3 class Base
 4 {
 5 public:
 6     virtual void Fun1()
 7     {
 8         cout << "Base::Fun1..." << endl;
 9     }
10     virtual void Fun2()
11     {
12         cout << "Base::Fun2..." << endl;
13     }
14     
15     /*Base()
16     {
17         cout << "Base..." << endl;
18     }
19     virtual ~Base()
20     {
21         cout << "~Base..." << endl;
22     }*/
23     int data1_;
24 };
25 class Derived:public Base
26 {
27 public:
28     //覆盖了基类的fun2,但是并没有覆盖基类的fun1
29     virtual void Fun2()
30     {
31         cout << "Derived::Fun2..." << endl;
32     }
33     virtual void Fun3()
34     {
35         cout << "Derived::Fun3..." << endl;
36     }
37     /*Derived()
38     {
39         cout << "Derived..." << endl;
40     }
41     virtual ~Derived()
42     {
43         cout << "~Derived..." << endl;
44     }*/
45     int data_2;
46 };
47 typedef  void(*Func)();
48 int main(void)
49 {
50     Base b;//base头四个字节存放的是虚表指针
51     Derived d;
52     cout << sizeof(b) << endl;//8
53     cout << sizeof(d) << endl;//12
54     long **p = (long **)&b;
55     Func fun = (Func)p[0][0];//指向基类的虚函数
56     fun();//Base::Fun1...调用到了基类的虚函数
57     fun = (Func)p[0][1];
58     fun();
59     /*
60     Base::Fun1...
61     Derived::Fun2...
62     Derived::Fun3...
63     */
64     p = (long**)&d;
65     fun = (Func)p[0][0];//虚表内容,并未覆盖基类fun1
66     fun();
67     fun = (Func)p[0][1];//覆盖了基类fun2
68     fun();
69     fun = (Func)p[0][2];
70     fun();
71     //实施动态绑定
72     Base* pp = &d;
73     pp->Fun2();//应该调用的是派生类成员函数。
74     return 0;
75 }

 

 

overload、overwrite、override

分子函数重载的特征:

1、 同样的功能域范围(在同三个类中)

2、 函数名同样

3、 参数分化

4、 Virtual关键字可有可无

覆盖是指派生类函数覆盖基类函数,特征是:

1、 区别的限量(分别放在派生类与基类)

2、 函数名字同样

3、 参数一样

4、 基类函数必得有virtual

重定义(派生类与基类)

1、 分化的限定(分别放在派生类与基类)‘

2、 函数名与参数都同样,无virtual关键字

3、 函数名一样,参数差别,virtual关键字可有可无

 

 

 

 

虚函数:基类指针指向派生类对象,调用的是派生类的虚函数,那就使得大家得以以平等的理念来看待差异的派生类对象。动态绑定的。

虚函数是落实多态的前提:须要在基类中定义共同的接口、接口要定义为虚函数。

设若基类不知晓怎样落到实处接口,要求将这一个接口定义为纯虚函数,具有纯虚函数的接口类称为抽象类。

在基类中无法交付有含义的虚函数定义,那时可以把它表达成纯虚函数,把她的定义留给派生类来做。

概念纯虚函数:

         Virtual  再次来到值类型  函数名(参数表)=0;

抽象类:

功用:抽象类为架空和统一打算的目标而注明,将有关的数据和行事协会在七个持续档次结构中,保险派生类具备供给的一举一动。

对于不经常不能够落到实处的函数,能够评释为纯虚函数,留给派生类去贯彻。

只顾:抽象类只可以作为基类来使用、无法声称抽象类的对象、构造函数不可能是虚函数,析构函数能够是虚函数。多态基类的虚函数就该注明为虚析构函数。

 虚函数是动态绑定的,先要构造出目的,对象中的虚表指针才可到虚表中的查找虚函数。

虚析构函数:

析构函数能够注解为虚函数:

         delete 基类指针;

         程序会基于基类指针指向的对象的种类鲜明要调用的析构函数

         基类的析构函数为虚函数,全体派生类的析构函数都以虚函数

构造函数不得是虚函数。

设若要操作具有持续关系的类的动态目的,最棒使用虚析构函数。特别是在析构函数须要产生都部队分有含义的操作,比方释放内部存款和储蓄器时。

析构函数还是能是纯虚的。

//若是贰个类没有其他成员函数,如果未有其它接口,又想把它表明为抽象类

class Base

{

public:

//能够将析构函数宣称为纯虚的

    virtual ~Base() = 0 {}//达成了纯虚析构函数

};    

在意:一般纯虚函数无需贯彻,可是对于上述纯虚析构函数,要求实现,因为派生类析构函数会调用基类析构函数。

相关文章