模块化编程向来不是面向对象语言的专利,即使是C语言,为了降低文件、模块间的耦合度,依然要注意对变量、函数进行封装。
以下举例对C语言模块化编程进行浅析:项目中包含a.c和b.c文件,其中a.c中定义了变量/结构体,而b.c中需要用到这些变量/结构体,那么有如下几种方法可选:
- get/set封装
在a.c种为每个需要被外部引用的变量/结构体编写get/set函数,并在a.h中声明get/set函数,b.c include a.h,需要使用变量/结构体的值时调用get函数,需要设置变量/结构体时调用set函数:
//a.c
static int intA;
static struct structType structData;
int getA()
{
return intA;
}
void setA(int a)
{
intA = a;
return;
}
struct structType getStructData()
{
return structData;
}
void setStructData(struct structType data)
{
structData = data;
return;
}
//a.h
struct structType{
int c1;
int c2;
};
int getA();
void setA(int a);
struct structType getStructData();
void setStructData(struct structType data);
//b.c
#include "a.h"
void funcB()
{
int tmpA = getA();
tmpA += 1;
setA(tmpA);
struct structType structTmp = getStructData();
structTmp.c1 = 0;
setStructData(structTmp);
return;
}
写起来比较繁琐,但是是规规矩矩的封装。
- get进阶封装
get/set封装使用时需要先get到变量值,加以修改后再set回去,相比之下,获取变量的地址之后直接进行修改要更方便些,写法如下:
//a.c
static int intA;
static struct structType structData;
int *getPA()
{
return &intA;
}
struct structType *getPStructData()
{
return &structData;
}
//a.h
struct structType{
int c1;
int c2;
};
int *getPA();
struct structType *getPStructData();
//b.c
#include "a.h"
void funcB()
{
int *tmpA = getPA();
(*tmpA) += 1;
struct structType *structTmp = getPStructData();
structTmp->c1 = 0;
return;
}
缺点是指针操作易发生段错误。
- 如果“b.c和a.c联系十分紧密,以至于b.c中用到的intA变量和a.c中用到的一样频繁”,这时也可以在get/set封装的基础上,在b.c中extern int intA;从而可以在b.c中直接使用intA,其他文件使用intA时借助get/set封装。而为了能让b.c extern,a.c中的intA就不能再加static,也就失去了get/set封装的意义,这是十分矛盾也是十分奇怪的写法。归根结底,“b.c和a.c联系十分紧密,以至于b.c中用到的intA变量和a.c中用到的一样频繁”这种情况是不应该出现的,如果两个文件联系紧密,那么该想想这两个文件是不是该合并成一个,或者把两者关于intA的操作提取出来单独存放。
模块化编程成功的前提是对模块有好的拆分,没有好的拆分,封装的工作将变得十分棘手。