51-C++对象模型分析(下)

注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。

测试环境:Ubuntu 10.10

GCC版本:9.2.0

 

一、继承对象模型

1)在C++编译器的内部类可以理解为结构体

2)子类是由父类成员叠加子类新成员得到的

编程实验
继承对象模型初探
51-1.cpp
#include <iostream>
#include <string>

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
public:
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

class Derived : public Demo
{
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;        //8    
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  //12 = 8 + 4 ,叠加得到
    
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);    //强制转换成结构体Test类型
    
    cout << "Before changing ..." << endl;
    
    d.print();
    
    p->mi = 10;   //这里可以修改数据成功,因为结构体Test和对象d内存布局相同!
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    d.print();
    
    return 0;
}

操作:

1) g++ 51-1.cpp -o 51-1.out编译正确,打印结果:

sizeof(Demo) = 8
sizeof(Derived) = 12
Before Changing ...
mi = 1, mj = 2, mk = 3
After changing ...
mi = 10, mj = 20, mk = 30

分析:

1. 子类继承父类,数据在内存中如何分布的:父类数据在前,子类数据在后。               

2. 类可以转换成结构体,因为类中的数据存储方式和结构体中数据存储方式相同。

 

二、多态对象模型

1)C++多态的实现原理

        -    当类中声明虚函数时,编译器会在类中生成一个虚函数表

        -    虚函数表是一个存储成员函数地址的数据结构

        -    虚函数表是由编译器自动生成与维护的

        -    virtual成员函数会被编译器放入虚函数表中

        -    存在虚函数时,每个对象中都有一个指向虚函数表的指针(4字节)

       多态是面向对象中的一个概念,和具体程序设计语言没有关系。多态指的是相同的行为方式,不同的行为结果。通过同一种行为方式,表现结果是不同的。(多态的概念)

       多态的这种方式,是通过虚函数来进行实现的。(多态实现的方法)

两个类生成两个虚函数表(VTABLE) ,一个给子类一个给父类。

 

红色箭头代表寻址操作。由于效率问题,对于使用虚函数要慎重。

证明虚函数表存在
#include <iostream>
#include <string>

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

class Derived : public Demo
{
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    void* p;   //虚函数列表指针放在最初位置。
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;         
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  

    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);
    
    cout << "Before changing ..." << endl;
    
    d.print();
    
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    d.print();
 
    return 0;
}

操作:

1) g++ test.cpp -o test.out编译正确,打印结果:

sizeof(Demo) = 12
sizeof(Derived) = 16
Before changing ...
mi = 1, mj = 2, mk = 3
After changing ...
mi = 10, mj = 20, mk = 30

分析:

        因为有虚函数,所以每个类大小+4(因为存储指向虚函数表的指针)。struct Test的指向虚函数表的指针放在了最前边,这证明了class在多态时内存布局。

        将子类地址给结构体p,通过修改结构体p中的数据,竟然改变了子类中的数据,证明子类内存排布方式和结构体内存排布方式相同。

 

编程实验
多态本质分析
51-2.h
#ifndef _51_2_H_
#define _51_2_H_

typedef void Demo;
typedef void Derived;    //新添加(derived:派生)

Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);    //创建对象
int Derived_GetK(Derived* pThis);            //新添加
int Derived_Add(Derived* pThis, int value);    //虚函数

#endif


51-2.c
#include "51-2.h"
#include "malloc.h"
//模拟多态:声明两个“同名”函数
static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);

struct VTable     // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);   // 3. 虚函数表里面存储什么???(函数指针)
};

struct ClassDemo
{
    struct VTable* vptr; // 1. 定义虚函数表指针  ==》 虚函数表指针类型???
    int mi;
    int mj;
};

struct ClassDerived      //定义结构对象
{
    struct ClassDemo d;    //继承
    int mk;
};

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向父类累加函数
static struct VTable g_Demo_vtbl =     //父类虚函数表
{
    Demo_Virtual_Add
};

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向子类累加函数
static struct VTable g_Derived_vtbl =     //子类虚函数表
{
    Derived_Virtual_Add
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 

    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表(父类虚表)
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_GetI(Demo* pThis)
{
     struct ClassDemo* obj = (struct ClassDemo*)pThis;    

     return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;

    return obj->mj;
}

// 6. 定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi + obj->mj + value;
}

// 5. 分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;

    return obj->vptr->pAdd(pThis, value);
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)    //创建对象
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

int Derived_GetK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->mk;
}
//定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Derived_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis; 

    return obj->mk + value;
}
//分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Derived_Add(Derived* pThis, int value)
{   
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->d.vptr->pAdd(pThis, value);
}


main.c
#include "stdio.h"
#include "51-2.h"

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);    //这里调用父类累加函数,模拟子类退化成父类(Add函数内部会进行强制转换,对父类没有影响,但是会使子类丢失自己专属的数据(地址截断),对遗留下的数据没有任何破坏。不过虚函数表总是放在内存分布的最前边,不会影响找到想调用的子类函数。)
    
    printf("r = %d\n", r);
}

int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));      //父类Add函数
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));   //子类Add函数
    
    run(pb, 3);     //6
    run(pd, 3);     //336
    
    Demo_Free(pb);
    Demo_Free(pd);
    
    return 0;
}

操作:

1) gcc main.c 51-2.c -o 51-2.out编译正确,打印结果:

pb->add(3) = 6
pd->add(3) = 336
r = 6
r = 336

 

分析:

1. 头文件:

#ifndef _51_2_H_
#define _51_2_H_

typedef void Demo;
typedef void Derived;    //新添加(derived:派生)

Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);    //创建对象
int Derived_GetK(Derived* pThis);            //新添加
int Derived_Add(Derived* pThis, int value);    //虚函数

#endif

         void是空类型,void*可以被任意类型指针赋值,但是用void指针之前必须被强制转换成原来类型指针,否则无法访问数据。

        这里设计上对外提供void*类型,因为内存模型不同,导致void*指针不能正常访问数据(模拟私有数据不能被外界直接访问),有如下测试:

#include <stdio.h>
#include "51-2.h"

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);

    printf("r = %d\n", r);
}

int main()
{ 
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);  

    printf("%d\n", pb->mi);    //编译器这里报错

    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));

    run(pb, 3);
    run(pd, 3);
  
    Demo_Free(pb);
    Demo_Free(pd);

    return 0;
}

编译其报错:

main.c:16:20: warnging: dereferencing 'void *' pointer [enabled by default]
    printf("%d\n", pb->mi);
警告:对void *指针取值
main.c:16:19: error: request for member 'mi' in someting not a structure or union
错误:在一些不是结构体或者联合体类型中请求成员'mi'    

        如果想正常访问数据,必须将void*指针强制转换成数据原来的指针,还原内存模型,进而访问数据。但是这个操作只能在函数中进行(封装、私有数据能被成员函数访问,不能被外界直接访问),这个函数模拟的是公有成员函数,如:Create和数据获取等函数。

 

2. .C文件:

#include "51-2.h"
#include "malloc.h"
//模拟多态:声明两个“同名”函数
static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);

struct VTable     // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);   // 3. 虚函数表里面存储什么???(函数指针)
};

struct ClassDemo
{
    struct VTable* vptr; // 1. 定义虚函数表指针  ==》 虚函数表指针类型???
    int mi;
    int mj;
};

struct ClassDerived      //定义结构对象
{
    struct ClassDemo d;    //继承
    int mk;
};

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向父类累加函数
static struct VTable g_Demo_vtbl =     //父类虚函数表
{
    Demo_Virtual_Add
};

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向子类累加函数
static struct VTable g_Derived_vtbl =     //子类虚函数表
{
    Derived_Virtual_Add
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 

    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表(父类虚表)
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_GetI(Demo* pThis)
{
     struct ClassDemo* obj = (struct ClassDemo*)pThis;    

     return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;

    return obj->mj;
}

// 6. 定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi + obj->mj + value;
}

// 5. 分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;

    return obj->vptr->pAdd(pThis, value);
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)    //创建对象
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

int Derived_GetK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->mk;
}
//定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Derived_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis; 

    return obj->mk + value;
}
//分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Derived_Add(Derived* pThis, int value)
{   
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->d.vptr->pAdd(pThis, value);
}

 

static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);

这两个函数被修饰为static函数,只属于这个文件。这两个函数为了模拟多态的同名函数(为虚函数)。

// 6. 定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;    //必须强转
    
    return obj->mi + obj->mj + value;
}

//定义虚函数表中指针所指向的具体函数(定义静态函数:模拟类的私有属性)
static int Derived_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis; //必须强转

    return obj->mk + value;
}

       参数为Demo*(void*)类型,为了保存数据起始地址,但想访问数据,必须进行强制转换成数据原来的类型才能访问(上边已经证明)。

        这两个函数可以被再次封装成普通函数,为了对外界提供访问接口:

// 5. 分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;

    return obj->vptr->pAdd(pThis, value);
}

//分析具体的虚函数!!!!(对外提供的累加接口:模拟公有属性)
int Derived_Add(Derived* pThis, int value)
{   
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->d.vptr->pAdd(pThis, value);
}

 

struct VTable    // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);  // 3. 虚函数表里面存储什么???(函数指针)
};

这是模拟虚函数表,表中声明的是函数指针,这个指针可以指向两个专属的Add函数。

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向父类累加函数
static struct VTable g_Demo_vtbl =     //父类虚函数表
{
    Demo_Virtual_Add
};

//虚函数表里存放的是一组函数:给函数指针初始化,指针指向子类累加函数
static struct VTable g_Derived_vtbl =     //子类虚函数表
{
    Derived_Virtual_Add
};

这两个表(结构体)在对象创建之初就被绑定好:

//父类
Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 

    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表(父类虚表)
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}
//子类
Derived* Derived_Create(int i, int j, int k)    //创建对象
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

 

3. main.c(多态发生的地方:多态是发生代码运行期间)

main.c
#include "stdio.h"
#include "51-2.h"

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);    //这里调用父类累加函数,模拟子类退化成父类(Add函数内部会进行强制转换,对父类没有影响,但是会使子类丢失自己专属的数据(地址截断),对遗留下的数据没有任何破坏。不过虚函数表总是放在内存分布的最前边,不会影响找到想调用的子类函数。)
    
    printf("r = %d\n", r);
}

int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));      //父类Add函数
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));   //子类Add函数
    
    run(pb, 3);     //6
    run(pd, 3);     //336
    
    Demo_Free(pb);
    Demo_Free(pd);
    
    return 0;
}

 

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);    
    
    printf("r = %d\n", r);
}

分析:

        这里调用父类累加函数,模拟子类退化成父类(Add函数内部会进行强制转换,对父类没有影响,但是会使子类丢失自己专属的数据(地址截断),对遗留下的数据没有任何破坏。不过虚函数表总是放在内存分布的最前边,不会影响找到想调用的子类函数。),因为Demo*指针可以指向父类或者子类内存地址,又因为强制转换成统一的父类内存模型没虚函数表,仍然可以访问子类或者父类虚函数表指向的累加函数。这个过程模拟了多态的发生。

 

转换之前的子类和父类模型:

转换之后的子类和父类模型:

虚函数表并没有被破坏。

 

小结

1)继承的本质就是父子间成员变量的叠加

2)C++中的多态是通过虚函数表实现的

3)虚函数表是由编译器自动生成与维护的

4)虚函数的调用效率低于普通成员函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值