C语言也能面向对象(四)——继承

Technorati 标签: c, c语言, 面向对象, oo, object-oriented

本文版权所有,转载请注明出处和作者联系方式。
作者:孙华明
联系方式: wormsun at gmail.com

在C++中如果一个类有父类,那么这个类的对象中就包含了父类中定义的数据,并且可以使用父类的函数访问或操作该这些数据。在C中如何实现这样的机制呢?

animal类的定义如下:

typedef struct _animal animal;
struct _animal
{
/*class info*/
    klass_info* klass;
    
/*private data*/
    char name[256];
    int weight;
};

现在我们再定义一个dog类,除了包含animal类的属性外,还包括一个age属性,即年龄。如下:

typedef struct _dog dog ;
struct _dog 
{
/*class info*/
    klass_info* klass;
    
/*private data*/
    char name[256];
    int weight;
    int age;
};

在保持类的对象的内存布局不变的情况下,我们可以将dog类的定义变换为:

typedef struct _dog dog;
struct _dog
{
/*base*/
    animal base;
 
/*private data*/
    int age;
};

两个dog类的定义在内存布局上是完全一致的。因此,只要将dog类对象的指针强制转型为animal类的指针,那么animal类的成员函数就可以访问或操作 dog类对象了,如下:

dog* my_dog = lw_new(dog_klass);
animal_set_weight(ANIMAL(my_dog), 40);
int weight = animal_get_weight(ANIMAL(my_dog));
lw_delete(my_dog);

在C++中创建子类的对象时,要先调用父类的构造函数,然后再调用子类的函数;删除子类对象时,要先调用子类的析构函数,然后再调用父类的析构函数。

animal类是dog类的父类,在定义dog类信息时,要将dog类信息中的super属性初始化为animal类信息的地址,如下:

static klass_info local_dog_klass = 
{
    animal_klass,
    "dog_klass",
    sizeof(dog),
    dog_ctor,
    dog_dtor,
};

在实现dog的构造函数时,就可以使用类信息的super成员获取其父类的构造函数地址,如下:

static dog* dog_ctor(dog* self)
{
    ((voidf)(klass_of(self)->super->ctor))(self);
    self->age = 0;
    return self;
}

klass_of函数的功能是获取对象的类信息地址。可以看出dog类的构造函数是先调用父类的构造函数,然后再初始化子类的属性。类似,析构函数的实现如下:

static dog* dog_dtor(dog* self)
{
    ((voidf)(klass_of(self)->super->dtor))(self);
    return self;
}

目前为止,我们都是使用结构体初始化的方式来初始化类信息,这种方式有代码重复、容易犯错,难于维护的缺点,例如,animal类信息的初始化在animal.c中已经实现过,定义dog类时在dog.c中又要再次实现,如果再定义其他继承自animal类的子类,则还要实现。按顺序初始化结构体这种做法本身就很容易犯错,如果这样的代码到处都是,那么维护难度就可想而知了。

所以我们考虑使用类信息初始化函数来初始化类信息,即专门定义一个函数用来初始化类信息,该函数在程序运行后,第一次创建该类的对象时由lw_new函数调用。在类信息结构体中新加入一个属性,即类信息初始化函数的地址,如下:

typedef struct _klass_info klass_info;
struct _klass_info
{
    void* init; /*initialize function*/
    klass_info* super;    /*object's klass's super klass*/
    char* name; /*object's klass's name*/
    size_t size; /*object's size*/
    void* ctor; /*object's constructor*/
    void* dtor; /*object's destructor*/
};

init属性在定义静态类信息结构体对象时初始化,其他属性在init指向的函数中初始化,以animal类为例:

static animal_klass_info local_animal_klass = {animal_init};
animal_klass_info* animal_klass = &local_animal_klass;
 
void animal_init(void)
{
    if(animal_klass->init)
    {
        animal_klass->init = NULL;
        animal_klass->super = NULL;
        animal_klass->name = "animal_klass";
        animal_klass->size = sizeof(animal);
        animal_klass->ctor = animal_ctor;
        animal_klass->dtor = animal_dtor;
    }
}

lw_new函数修改如下:

void* lw_new(void* klass)
{
    klass_info* kls = KLASS(klass);
    if(kls->init)
    {
        ((init_fun)kls->init)();
    }
    
    void* p = malloc(kls->size);
    *((klass_info**)p) = kls;
    return ((voidf)(kls->ctor))(p);
}

类信息初始化函数中会将类信息结构体的init属性置空,所以该函数只会被调用一次,即第一次创建该类的对象时调用。

子类的类信息初始化函数可以直接调用父类的类信息初始化函数,以dog类为例:

void dog_init(void)
{
    if(dog_klass->init)
    {
        animal_init();
        memcpy(dog_klass, animal_klass, sizeof(animal_klass_info));
        dog_klass->super = animal_klass;
        dog_klass->name = "dog_klass";
        dog_klass->size = sizeof(dog);
        dog_klass->ctor = dog_ctor;
    }
}

至此我们实现了类继承,并使用类信息初始化函数来初始化类信息。

这里是相关代码。

下篇文章我们将讨论多态。

你可能感兴趣的:(C语言也能面向对象(四)——继承)