C模拟多态

4.1k 词

【问题】
多态的虚函数调用,含虚函数对象大小计算,字节对齐,函数覆盖,构造与析函数的执行顺序,This指针。

【简介】
首先,需要介绍一下用C语言实现C的单根继承,然后分析一下构造函数和析构函数的执行顺序。看看C都在背后做了什么,这也是C的基本内容,不涉及哲学和C软件的复用性讨论,立足于,C++是对C的扩展,Class是对结构体的扩展,用struct和变量的讨论,代替class和属性。

【概念】

1.继承:子类继承父类,就是被叫做子类的结构体内,含有称为父类类型的结构体变量。例:drived结构体内含有base结构类型的变量。

2.多态:在具有包含关系的结构体间的强类型转换,通过修改结构体内,指向虚表的,指针变量的内容,使其指向不同的虚表(虚表:指针函数结构体),来实现虚函数调用的功能。

【代码】
[code]
#include<stdlib.h>
#include<stdio.h>

typedef void(*F_BASE_A)(void *obj);
typedef void(*F_BASE_B)(void *obj);
typedef void(*F_DRIVED_B)(void *obj);

void f_base_a(void *obj) {
printf(“base class function a is called!\n”);
}

void f_base_b(void *obj) {
printf(“base class function b is called!\n”);
}

void f_drived_b(void *obj) {
printf(“drived class function b is called!\n”);
}

typedef struct base_vt {
unsigned int rtti;
F_BASE_A base_a;
F_BASE_B base_b;
}base_vt;

typedef struct drived_vt {
unsigned int rtti;
F_DRIVED_B drived_b;
}drived_vt;

typedef struct Base {
void *vtr;
int b_data;
}Base;

typedef struct Drived {
Base b;
int s_data;
}Drived;

base_vt g_base_vt;
drived_vt g_drived_vt;

void complier_init() {
g_base_vt.rtti = 6;
g_base_vt.base_a = f_base_a;
g_base_vt.base_b = f_base_b;

    g_drived_vt.rtti = 8;
    g_drived_vt.drived_b = f_drived_b;

}
void t_base_call() {
printf(“### test base begin ###.\n”);
Base b;
b.vtr = &g_base_vt;

    //call stlye 1
    F_BASE_A function =  (F_BASE_A)(g_base_vt.base_a);
    function(&b);

    function = (F_BASE_B)(g_base_vt.base_b);
    function(&b);

    //call style 2
    F_BASE_A fun_a = (F_BASE_A)(( (base_vt*)b.vtr )->base_a);
    fun_a(&b);

    F_BASE_B fun_b = (F_BASE_B)(( (base_vt*)b.vtr )->base_b);
    fun_b(&b);

    //call style 3
    unsigned int *ptr = (unsigned int*)(&b);
    printf("%d\n", *ptr);

    ptr = (unsigned int*)(*ptr);
    printf("%d\n", *ptr);

    F_BASE_A fun = (F_BASE_A)(*(ptr+1));
    fun(&b);

    fun = (F_BASE_B)(*(ptr+2));
    fun(&b);
    printf("### test base end ###.\n");

}

void t_drived_call() {
printf(“### test drived begin. ###\n”);
Drived d;
d.b.vtr = &g_drived_vt;

    unsigned int *ptr = (unsigned int*)(&d);
    ptr = (unsigned int*)(*ptr);
    F_BASE_A function = (F_BASE_A)(*(ptr+1));
    function(&d);

    function=(F_DRIVED_B)(*(ptr+2));
    function(&d);
    printf("### test drived end. ###\n");

}

void t_poly()
{
printf(“### test poly begin. ###\n”);

    Drived d;
    d.b.vtr = &g_drived_vt;

    Base* base = (Base*)&d;
    unsigned int *ptr = (unsigned int*)(base);
    ptr = (unsigned int*)(*ptr);
    F_BASE_A function = (F_BASE_A)(*(ptr+1));

    function(base);

    function = (F_DRIVED_B)(*(ptr+2));
    function(base);
    printf("### test poly end. ###\n");

}

int main(int argc, char** argv)
{

    complier_init();
    t_base_call();
    return 0;

}

[/code]

【多态】
如果说class是struct的加强版本的话,class相比struct有了虚表的管理和对成员变量权限管理(public, protected, private).对于stuct来说,struct没有成员变量的权限管理,默认所有的struct成员变量都默认为是public属性,可以被其他函数访问。这篇主要是描述,C如何模拟C++对class虚表的模拟。

模拟虚表管理采用的方式是,用两个struct模拟class,一个struct用于存储class的数据,一个struct用于存储class中接口函数的函数指针(函数指针集合。)
【模拟类的定义】
[code]
//附属结构体(虚表):用于存储,指向函数(接口)的函数指针。
typedef struct base_vt {
unsigned int rtti;// 是一个存储继承信息的变量。
F_BASE_A base_a;//函数指针
F_BASE_B base_b;//函数指针
}base_vt;

//主结构体(数据):用于存储数据。
typedef struct Base {
void *vtr; //1.vtr指针用于指向base_vt结构体。2.vtr一定要是struct的第一个成员变量,这是之后实现多态的关键。
int b_data;
}Base;
[/code]

并且这两个struct之间通过一个”void* vtr“的空类型指针进行联系。vtr是主stuct的一个指针类型的成员变量,用于指向附属类所在的内存空间。

在定义函数指针的时候,使用了自定义的宏。
[code]
typedef void(*F_BASE_A)(void *obj);
typedef void(*F_BASE_B)(void *obj);
typedef void(*F_DRIVED_B)(void *obj);
[/code]

【this指针】
在C++中,非静态的成员函数的形参列表中,有一个被隐藏的参数,就是”this“
[code]
class Sample {
public:
void foo(int i);
}
[/code]

实际上foo的参数列表会被编译器翻译成,foo(Sample* this, int i);
这也是为什么,在成员函数中,可以访问Sample类的数据。

[code]
class Sample {
public:
void foo(Sample* this, int i);
}
[/code]
而我们在使用函数指针宏的时候,typedef void(*F_BASE_A)(void *obj),指定了void *obj,相当于this指针,让用宏定义的这些成员函数,都可以访问主结构体的成员数据。

[code]
typedef void(*F_BASE_A)(void *this) //obj和this作用类似
[/code]
【static方法】
C++类,有一种叫做static的成员函数,类的static函数,可以在类不被实例化前,允许调用,但是类的static方法不能访问类的成员变量,类没有对象实例化,在内存中就类成员的空间,也无从访问其数据。但是,如果类成员变量也是static类型的话,static函数就可以,因为static变量被分别在常量存储区。

[code]
typedef void(*F_BASE_A)(void *obj);
[/code]
如果把上面的函数定义的参数”void *obj“删除,那么用F_BASE_A定义的函数,就类似于static方法,因为没有obj指针,对于函数来说,是不能访问主struct的数据的。

[code]
typedef void(*F_BASE_A)();
[/code]

【字节对齐】

【对象的size】

【选读】
市面上往往出现过很多的大部头书,大部头书更多的时候被当做工具书进行查阅,但是如果书的组织形式不好,就很难高效的找到自己主要想看的内容,更多的视野被重复的内容的占据,做重复的阅读工作。

参考文档:
http://blog.chinaunix.net/uid-20940095-id-66146.html
http://club.topsage.com/thread-2263309-1-1.html