结合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};
乍一看,上面这种操作方法没有什么毛病,相同功能实际由一个函数完成,没有造成资源浪费。但是对于类 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语言指针的强大。