C语言陷阱与技巧第29节,很多程序员不知道,C语言也能“继承”父类

之前两节探讨了C语言进行“面向对象”编程的可能性。现在已经了解,结合C语言的指针和结构体语法,基本能够实现对象语法最核心的部分,即成员函数和成员变量。另外,上一节讨论了如何利用指针,将公开的成员变量,封装成 private(私有)变量,由此也可以看出C语言指针语法的强大。

共同功能

上一节已经讨论,将不同的模块封装成独立的类是方便的,不过在实际的C语言项目开发中,即使是独立的类之间也是极有可能存在通用功能的。例如各个类都用得到时间,所以在定义类时,需要为各个类都添加获取时间的成员函数:

struct class1{
    //...
    long (*get_time)();
};
struct class2{
    //...
    long (*get_time)();
};

上面这段C语言代码定义了 class1 和 class2 两个类作为示例,如果 class1 和 class2 对时间的要求并没有什么不同,那我们可以让二者的 get_time 函数指针指向同一个函数,例如:

long get_time()
{
    //...
}
struct class1 c1 = {..., get_time};
struct class1 c2 = {..., get_time};

这么做无可厚非,反正实现了 class1 和 class2 的共同功能。不过,在实际的C语言项目开发中,更常见的情况是,有些类拥有的共同功能不止一个。

例如class1 和 class2 不仅都需要获取时间,也都需要打印功能,这样一来,在定义 class1 和 class2 时,就需要再新增一个成员函数,相关C语言代码如下:

struct class1{
    //...
    long (*get_time)();
    void (*print)(void *data);
};
struct class2{
    //...
    long (*get_time)();
    void (*print)(void *data);
};

如果 class1 和 class2 需要的打印功能没有差异,那么在初始化对象时,就可以像上面的 get_time 一样,让 print 指向已经实现的打印函数就可以了。

long get_time()
{
    //...
}
void myprint(void *data)
{
    //...
}
struct class1 c1 = {..., get_time, myprint};
struct class1 c2 = {..., get_time, myprint};

C语言“类”的继承

乍一看,上面这种操作方法没有什么毛病,相同功能实际由一个函数完成,没有造成资源浪费。但是对于类 class1 和 class2 本身来说,将自己的独有特性和与其他类共同特性封装在一起,就不太明智了。另外,各个类中有重复的函数指针也是在实际C语言项目开发中应该尽力避免的。

那该怎么办呢?

既然决定进行“面向对象”编程,要解决上述问题,自然应该参考其他具有原生对象语法的高级编程语言。在开发C++程序封装类时,遇到各个类的相同特性,常常将这些相同特性提取出来,作为一个新的类。这个新的类常被称作“父类”,并且通过C++的继承语法,将“父类”的成员函数和成员变量共享给需要的子类。

C语言没有提供原生的对象语法,也没有提供继承语法。但是我们仍然可以使用C语言的指针和结构体语法模拟“父类”概念和“继承”特性。

首先,将各个类的相同特性提取出来,并将这些特性封装为“父类”是简单的。还是以 class1 和 class2 为例,它们有两个相同功能:获取时间和打印功能。

struct father{
    long (*get_time)();
    void (*print)(void *data);
};

上述C语言代码将 class1 和 class2 的共同功能封装成一个新的类 father,也即所谓的“父类”。接下来,只要让 class1 和 class2 继承 father 就可以了,可是C语言没有原生的“继承”语法,该怎样实现这一过程呢?

应明白,继承的目的是为了让子类能够访问父类提供的成员函数和成员变量,虽然C语言没有像C++那样完善的继承语法,但是像提供子类访问父类这种需求还是比较容易实现的:

struct class1{
    //...
    struct father father;
};
struct class2{
    //...
    struct father father;
};

正如上述C语言代码,直接将 father 塞入 class1 和 class2 其实就可以了。访问父类的成员函数是简单的:

struct class1 c1;
c1.father.print(data);

一个值得说明的小技巧是,如果 father 类比较大,占用资源比较多,而C语言程序又没有必要建立多个 father 副本,则可以将 class1 和 class2 中的父类修改为指针:

struct class1{
    //...
    struct father *father;
};
struct class2{
    //...
    struct father *father;
};

这样一来,无论 father 有多大,class1 和 class2 都能使用一个指针大小的内存空间去索引它。访问父类的成员函数与之前有些许差异:

struct class1 c1;
c1.father->print(data);

到这里,相信读者应该能够发现,结合C语言的结构体和指针,模拟“面向对象”编程的父类继承语法也是轻而易举的,这也从侧面说明了C语言指针的强大。

小结

本节主要讨论了在使用C语言“面向对象”编程中,遇到不同类拥有相同功能的情况。在这种情况下,C语言程序员可以为各个类添加共同功能函数指针。不过更推荐的做法是再封装一次,将相同功能提取出来封装为父类,通过“继承”的方式,让各个类共享父类。当然了,本文讨论的方法与技巧仍属抛砖引玉。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif