11

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

 3 years ago
source link: https://blog.popkx.com/c%E8%AF%AD%E8%A8%80%E9%99%B7%E9%98%B1%E4%B8%8E%E6%8A%80%E5%B7%A7%E7%AC%AC29%E8%8A%82-%E5%BE%88%E5%A4%9A%E7%A8%8B%E5%BA%8F%E5%91%98%E4%B8%8D%E7%9F%A5%E9%81%93-c%E8%AF%AD%E8%A8%80/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

发表于 2019-07-08 07:07:32   |   已被 访问: 316 次   |   分类于:   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语言程序员可以为各个类添加共同功能函数指针。不过更推荐的做法是再封装一次,将相同功能提取出来封装为父类,通过“继承”的方式,让各个类共享父类。当然了,本文讨论的方法与技巧仍属抛砖引玉。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK