面向对象编程中比较好的特性就是有继承和函数多态,从而使得对于具有相同父类的子类对象能够调用相同的接口实现对应的功能,举例说明,文本输出类TxtWriter和二进制输出类BinWriter都继承自父类Writer,父类Writer定义了接口Open、Write、Close,两个子类分别实现各自的方法,但是对应的输出格式却分别为文本和二进制,这样其他类使用Writer时会根据实际对象为TxtWriter还是BinWriter而选择对应的方法实现,从而不需要修改大段的代码即可实现输出格式为文本还是二进制。
在实际工程应用中,类似的例子还有很多,比如工程中需要使用不同类型的数据库,如果能够采用上述方式进行接口封装,则基本无需改动原先的代码即可实现一种数据库到另一种数据库的转变,再比如视频分析应用前端输入可能为摄像机视频流或本地文件等多种,亦可采用这样的方法取消差异性。
但是,C语言不是面向对象的语言,其没有对象和封装的概念,甚至使用相同的函数名都是不行的,那么如何才能够实现上面类似的功能?经过思考,主要可以采用如下两种方式:使用宏定义指向和函数指针。
仍以上面Writer为例,使用宏定义可以如下:
1.TxtWriter.h/.c分别定义和实现txt_writer_open,txt_writer_write,txt_writer_close三个函数,BinWriter.h/.c分别定义和实现bin_writer_open,bin_writer_write,bin_writer_close三个函数;
2.定义头文件Writer.h如下(仅主要部分):
#define TXT_MODE 1
#define BIN_MODE 2
#define WRITER_MODE TXT_MODE
#if WRITER_MODE==TXTMODE
#include "TxtWriter.h"
#define writer_open txt_writer_open
#define writer_write txt_writer_write
#define writer_close txt_writer_close
#elif WRITER_MODE==BIN_MODE
#include "BinWriter.h"
#define writer_open bin_writer_open
#define writer_write bin_writer_write
#define writer_close bin_writer_close
#endif
从而,在具体使用时引入Writer.h头文件并通过writer_open、writer_write、writer_close调用对应的功能,而改变WRITER_MODE并重新编译,即可实现对应输出模式的切换。
该方式下虽然在一定程度屏蔽了差异性,实现了对不同功能函数的调用而不需要修改代码,但是却需要修改宏和重新编译,从而部分情况下是不适应的。比如模式识别训练过程配置文件的读取通常采用文本方式,而模型输出则希望采用二进制,那么就不能采用上面的方法,则需要使用另一种方式。
C++的对象也是包含函数表的,所以在C语言中采用函数指针+结构体来实现继承和多态与C++比较接近。以上面的例子来说明具体实现方式:
1.定义结构体WriterFunc并包含三个与open、write、close一致的函数指针
2.TxtWriter.h/.c分别定义和实现txt_writer_open,txt_writer_write,txt_writer_close三个函数,BinWriter.h/.c分别定义和实现bin_writer_open,bin_writer_write,bin_writer_close三个函数,同时在.c文件中分别定义const WriterFunc XXX={对应的三个函数},在.h中通过extern提供对两个常量的使用;
3.定义结构体Writer如下
typedef struct tagWriter
{
内容
WriterFunc* func
} Writer;
在初始化时根据要初始化为Txt还是Bin分别将func指针指向其函数指针结构,从而Writer的对象就拥有了属于自己的函数指针,并能够通过weiter.func->write的方式调用不同的功能。这种方式与C++是比较类似的,虽然有点绕,但是使用起来却与面向对象最为接近,不知道这样的方式下效率和移植DSP是否会受到影响。