• Geekband C++面向对象高级程序设计-第五周课程1


    本文是学习笔记整理。主要参考C++对象模型博客,原作者:吴秦,链接如下:

    http://www.cnblogs.com/skynet/p/3343726.html

    #C++对象模型简要

      C++通过抽象数据类型(Abstract Data Type,ADT),类中定义数据和函数实现数据和函数的绑定。

    1. 数据成员。(static,nonstatic)
    2. 成员函数。(static,nonstatic,virtual)

    #基本C++对象模型

    1. 非静态数据成员存放在对象内部。
    2. 静态数据成员,静态和非静态函数成员存放在对象外部。
    3. 虚函数用以下机制完成。  
      1. 虚函数表中(Virtual Table,vtbl),逐次存放指向类中虚函数的指针。
      2. 每个对象中拥有且仅拥有一个虚指针(vptr),指向相关虚函数表。

      #补充:

    1. 虚指针(vptr)的设定和重置均由类中构造函数,析构函数和拷贝赋值运算符自动完成。
    2. 虚函数表地址前面设置一个指向type_info的指针,RTTI(Run TIme Type Identification)是为多态实现而生成的信息,所以具有虚函数的对象才会生成。RTTI(Run TIme Type Identification)运行时类型识别是由编译器在编译时生成的特殊类型信息,包括对象继承关系,对象本身描述。  

      #示例代码:

    #include<iostream>
    
    using namespace std;
    
    class A{
        public:
            A(int);
            virtual ~A();
            
            void get(); 
            virtual void getdata() const;
            static int getcount() const;
        protected:
            int dataA;
            static int count;
    };

       #测试实例:

     

    void testA() {
        A a(10);
        cout << "A.                   = " << &a << endl;
        cout << "vfptr                = " << (int*)*(int*)&a << endl;
        cout << "type_info            = " << (int*)*(int*)*(int*)&a - 1 << endl;
        cout << "vfptr[0]             = " << (int*)*((int*)*(int*)&a) << endl;
        cout << "vfptr[1]             = " << (int*)*((int*)*(int*)&a + 1) << endl;
        cout << "dataA                = " << (int*)&a + 1 << endl;
        cout << "dataA                = " << *((int*)&a + 1) << endl;
    }

      #验证实例:

       #模型图:

     #对象模型中加入单继承

      C++对象模型中,继承的基类数据成员扩充到子类对象内存中。对于非虚继承扩充已有存在的虚函数表,对于虚继承添加一个虚函数表指针。

      #示例代码,不重写虚函数

    class B :public A {
    public:
        B(int _dataA,int _dataB) : A(_dataA),dataB(_dataB) {}
        virtual void resetdata() const { cout << "reset" << endl; }
    protected:
        int dataB;
    };

     

       #测试代码

    void testB() {
        B b(10,100);
        cout << "B.                   = " << &b << endl;
        cout << "vfptr                = " << (int*)*(int*)&b << endl;
        cout << "vfptr[0]             = " << (int*)*(int*)*(int*)&b << endl;
        cout << "vfptr[1]             = " << (int*)*((int*)*(int*)&b + 1) << endl;
        cout << "vfptr[2]             = " << (int*)*((int*)*(int*)&b + 2) << endl;
        cout << "dataA                = " << *((int*)&b + 1) << endl;
        cout << "dataA                = " << (int*)&b + 1 << endl;
        cout << "dataB                = " << *((int*)&b + 2) << endl;
        cout << "dataB                = " << (int*)&b + 2 << endl;
    }

     

       #示例验证:

      #模型图

     

       #实例代码,重写虚函数

    class B :public A {
    public:
        B(int _dataA,int _dataB) : A(_dataA),dataB(_dataB) {}
        virtual void getdata() const { cout << "getdataB" << endl; }
        virtual void resetdata() const { cout << "reset" << endl; }
    
    protected:
        int dataB;
    };

      #测试代码

    void testB() {
        B b(10,100);
        cout << "B.                   = " << &b << endl;
        cout << "vfptr                = " << (int*)*(int*)&b << endl;
        cout << "vfptr[0]             = " << (int*)*(int*)*(int*)&b << endl;
        cout << "vfptr[1]             = " << (int*)*((int*)*(int*)&b + 1) << endl;
        cout << "vfptr[2]             = " << (int*)*((int*)*(int*)&b + 2) << endl;
        printf("getdata              = %x
    ", &B::getdata);
        printf("coutdata             = %x
    ", &B::coutdata);
        printf("resetdata            = %x
    ", &B::resetdata);
        cout << "dataA                = " << *((int*)&b + 1) << endl;
        cout << "dataA                = " << (int*)&b + 1 << endl;
        cout << "dataB                = " << *((int*)&b + 2) << endl;
        cout << "dataB                = " << (int*)&b + 2 << endl;
    }

      #测试实例

     

       #补充说明:这里面可以看到vfptr[0] vfptr[1] vfptr[2]所存储的函数地址与实际打印出的函数地址,有着差别,总是相差几个字节。并且,通过一些方法想要通过函数地址打印出函数使用方法也并没有如愿以偿。

       于是只能做出猜测,虚表中并非直接存在函数地址,而是内存添加特殊事物标记后再去查找函数地址。或者说,自己所打印出的并非函数地址,而是其他这个只能暂做考虑。另外为补充相关知识,需要深入去读C++ primer与深入理解C++对象模型。需要提上日程了。

      #重新整理,验证测试

    • 测试代码

      #补充说明:本次测试为回答上节问题,通过虚表中地址获取相应函数经过验证符合第一猜想。但是对于重载虚函数指针报出异常无法打印,指针所在断点为B的覆盖虚函数上面。通过验证,归结为子类所覆盖父类的虚函数替换原始父类的虚函数,仍旧搁置在虚表的原始位置。并且,父类的虚函数在虚函数表的前面,子类的虚函数在虚函数表的后面。

    void testB() {
        B b(10, 100);
        typedef void(*B)(void);
    
        cout << "B.                   = " << &b << endl;
        cout << "vfptr                = " << (int*)*(int*)&b << endl;
        //0.
        printf ("getdata              = %x
    ", &B::getdata);
        cout << "vfptr[0]             = " << (int*)*(int*)*(int*)&b << endl;
        B ptr0 = (B)*((int*)*(int*)&b);
        cout << "getdata              = "<<endl; //ptr0();
        //1.
        printf ("coutdata             = %x
    ", &B::coutdata);
        cout << "vfptr[1]             = " << (int*)*((int*)*(int*)&b + 1) << endl;
        B ptr1 = (B)*((int*)*(int*)&b + 1);
        cout << "coutdata             = "; ptr1();
        //2.
        printf ("resetdata            = %x
    ", &B::resetdata);
        cout << "vfptr[2]             = " << (int*)*((int*)*(int*)&b + 2) << endl;
        B ptr2 = (B)*((int*)*(int*)&b + 2);
        cout << "resetdata            = "; ptr2();
        
        cout << "dataA                = " << *((int*)&b + 1) << endl;
        cout << "dataA                = " << (int*)&b + 1 << endl;
        cout << "dataB                = " << *((int*)&b + 2) << endl;
        cout << "dataB                = " << (int*)&b + 2 << endl;
    }
    • 运行实例    

      

    • 图模型

    #对象模型中加入多继承

       #内容提示:单继承中,派生类扩充基类的虚函数表,并且将父类的数据成员扩充到子类对象的内存中。本节考虑对于多继承扩充有何差别?

    1. 基类均由自己的虚表。
    2. 子类的成员函数被放到第一个基类的表中。
    3. 内存布局中,父类布局依次按声明顺序排列。
    4. 每个基类虚表中的虚函数(虚函数被子类所重写的)都被重写成子类的覆盖形式。这样做为解决不同基类类型的指针指向同一个子类的实例。 
    • 基类示例代码
    class A {
    public:
        A(int _dataA) :dataA(_dataA) {}
        virtual void getdata() const { cout << dataA << endl; }
        virtual void coutdataA() const { cout << "dataA" << endl; }
    protected:
        int dataA;
    };
    
    class A2 {
    public:
        A2(int _dataA2) :dataA2(_dataA2) {}
        virtual void getdata() const { cout << dataA2 << endl; }
        virtual void coutdataA2() const { cout << "dataA2" << endl; }
    protected:
        int dataA2;
    };
    • 子类示例代码
    class B :public A ,public A2{
    public:
        B(int _dataA, int _dataA2, int _dataB) : A(_dataA), A2(_dataA2), dataB(_dataB) {}
        virtual void getdata() const { cout << dataB << endl; }
        virtual void resetdata() const { cout << "resetdata" << endl; }
    
    protected:
        int dataB;
    };
    • 测试示例代码
    void testB() {
        B b(10, 20, 100);
        cout << "B.                           = " << &b << endl;
        cout << "_vptr_Base                   = " << (int*)(&b) << endl;
        
        cout << "vptr[0]                      = " << (int*)*(int*)(&b) << endl;
        printf("getdata                      = %x 
    ", &B::getdata);
        typedef void(*B)(void);
        B ptra = (B)*((int*)*(int*)(&b));
        cout << "getdata                      = " << endl;// ptra();
    
        cout << "vptr[1]                      = " << ((int*)*(int*)(&b) + 1) << endl;
        printf("coutdataA                    = %x 
    ", &B::coutdataA);
        ptra = (B)*((int*)*(int*)&b + 1);
        cout << "coutdataA                    = "; ptra();
    
        cout << "vptr[2]                      = " << ((int*)*(int*)(&b) + 2) << endl;
        printf("resetdata                    = %x 
    ", &B::resetdata);
        ptra = (B)*((int*)*(int*)&b + 2);
        cout << "resetdata                    = "; ptra();
    
        cout << "dataA                        = " << (int*)&b + 1 << endl;
        cout << "dataA                        = " << *((int*)&b + 1) << endl;
    
        cout << endl;
    
        cout << "vptr_Base2                   = " << ((int*)(&b) + 2) << endl;
        
        cout << "vptr[0]                      = " << ((int*)*((int*)(&b) + 2)) << endl;
        printf("getdata                      = %x 
    ", &B::getdata);
        ptra = (B)*((int*)*((int*)&b + 2)+0);
        cout << "getdata                      = " << endl; //ptra();
    
        cout << "vptr[1]                      = " << ((int*)*((int*)(&b) + 2) + 1) << endl;
        printf("coutdataA2                   = %x 
    ", &B::coutdataA2);
        ptra = (B)*((int*)*((int*)&b + 2) + 1);
        cout << "coutdataA2                   = " ; ptra();
    
        //cout << "vptr[2]                      = " << ((int*)*((int*)(&b) + 2) + 2) << endl;
        //printf("resetdata                    = %x 
    ", &B::resetdata);
        //ptra = (B)*((int*)*((int*)&b + 2) + 2);
        //cout << "resetdata                    = "; ptra();
    
        cout << "dataA2                         = " << (int*)(&b) + 3 << endl;
        cout << "dataA2                         = " << *((int*)(&b) + 3) << endl;
    
        cout << "dataB                          = " << (int*)(&b) + 4 << endl;
        cout << "dataB                          = " << *((int*)(&b) + 4) << endl;
    }
    • 运行结果示例

     

    • 模式图

     

    #虚继承

      虚继承为解决重复继承中多个间接父类的问题,不能使用简单的扩充并为每个虚基类提供一个虚函数指针(导致重复继承的基类会有多个虚函数表)形式。

      虚继承的派生类的内存结构和普通继承不同。虚继承的子类有单独的虚函数表,另外也单独保存一份父类的虚函数表,两部分之间用一个四字节0x000000作为分界。派生类的内存中,首先是自己的虚函数表,然后是派生类的数据成员,然后是0x000000之后是基类的虚函数表,最后是基类的数据成员。

      另外,如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间需要用0x000000来间隔。

      最后,总结为虚继承中派生类和基类的数据是完全间隔的。先存放派生类自己的虚函数表和数据,中间以0x0000分界最后保存基类的虚函数和数据。如果派生类重载了父类的虚函数,那么将派生类内存中基类虚函数表的相应函数替换。

     #虚继承(无重复继承)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    最近很烦心情糟糕透了
    面试心得
    CSS之按钮过滤
    JavaScript之动态背景登陆表单
    VsCode自定义快捷键,一次运行两个或多个Command命令
    Go优化浅谈
    go语言圣经第六章笔记
    go 语言圣经第六章习题
    go 内存对齐
    go语言圣经第五章部分习题
  • 原文地址:https://www.cnblogs.com/SKY-ZL/p/8407372.html
Copyright © 2020-2023  润新知