下面是我学习过程中归纳的一点方法,希望可以抛砖引玉,得到大家的批评指导。
C语言不是OOP的,但是可以从OOP的语言里借鉴些东西,来使我们的C程序更加模块化。
说到底,使用OO技术,无非是要使我们的代码更易维护,更易编写。我不喜欢用C去模拟C++,在一个结构体内塞进所谓的成员变量和成员函数(实质是一个函数指针)。就算这样做效率上没问题,我也觉得那样很难看,这样做还不如直接用C++。C++效率不如C?这点让C++ fans来说明好了。
学习数据结构的时候,我们都知道有个ADT的概念,如链表,那么就有单链表这样的数据实现和链表插入、删除、反转这样的操作。用C写程序时,我们也可以采用这样的思想,把数据和相关这类数据的操作集合在一起,用.c文件处理其数据的存储和对数据各种操作的实现,而用.h文件引出关于这类数据的接口函数。看过《c语言接口与实现》后,会对这种思想领悟的更深刻。
C语言对函数和变量作用域限制提供的功能实在太少了,它没有namespace,没有package。唯一可以给我们使用的就一个static而已。没有用static定义的函数和外部变量在各个文件中模块均可访问,只需要使用前用extern声明即可;而static定义的函数和外部变量,则只能在该C文件中的模块被访问。如果我们把一个C文件中的外部变量和函数看做一个类的成员变量和方法,那么我们的C程序里,使用static至少可以区分public和private。MINIX源代码中这样定义了两个宏:
#define PUBLIC
#define PRIVATE static
我觉得这种做法非常不错,写程序的时候强制要求自己把每一个函数和外部变量加上这样的声明。另外在.h文件里,把extern定义为PUBLIC,也可使我们的头文件表达含义更清晰。
看个小例子:
table.h:
#define T Table_T
#define PUBLIC extern
typedef struct T *T;
PUBLIC T Table_new (int hint,
int cmp(const void *x, const void *y),
unsigned hash(const void *key));
PUBLIC void Table_free(T *table);
PUBLIC int Table_length(T table);
......
#undef T
#undef PUBLIC
......
table.c:
......
PRIVATE int cmpatom(const void *x, const void *y) {
return x != y;
}
PRIVATE unsigned hashatom(const void *key) {
return (unsigned long)key>>2;
}
PUBLIC T Table_new(int hint,
int cmp(const void *x, const void *y),
unsigned hash(const void *key)) {
T table;
......
}
我们用一个.c文件来假设为C++里的一个类,这个类虽然只有PUBLIC和PRIVATE方法和变量,不过如果我们用ADT的思想来组织我们的代码,完全可以使我们的程序改观。对于全局变量,我们也可以采用这种方法处理,如果另外一个.c文件里的函数一定要使用另外一个文件定义的外部变量,那么学习实现类的方法,不妨给一个get或者set函数。如果觉得函数调用的花费太高(不会那么小气吧?),inline函数和宏都是好东西。《重构》一书里《重新组织你的函数》那章有许多有趣的方法,我们同样可以采纳用来改善C函数。方法就是inline函数和宏,或者直接提炼为函数,当然,记得加上PRIVATE。
来一段代码,来自《c语言接口与实现》:
void *Table_get(T table, const void *key) {
int i;
struct binding *p;
assert(table);
assert(key);
//hash函数散列关键字,并对表大小取余
//(使i在表大小范围内)
i = (*table->hash)(key)%table->size;
//下面for循环在列表中查找与key相等的关键字
for (p = table->buckets[i]; p; p = p->link)
if ((*table->cmp)(key, p->key) == 0)
break;
return p ? p->value : NULL;//如找到返回值,否则返回NULL
}
void *Table_put(T table, const void *key, void *value) {
int i;
struct binding *p;
void *prev;
assert(table);
assert(key);
//查找列表看是否有已存在关键字
i = (*table->hash)(key)%table->size;
for (p = table->buckets[i]; p; p = p->link)
if ((*table->cmp)(key, p->key) == 0)
break;
//没有已经存在的关键字,则在该列表插入该关键字
if (p == NULL) {
......
}
//如有关键字,则用新值代替原来值
else
prev = p->value;
p->value = value;
table->timestamp++;//改变了表格增加timestamp
return prev;
}
可以看到列表中查找与key相等的关键字这段代码两个函数都用到了,那么,提出来吧:
PRIVATE struct binding *SearchBindByKey(Table_T table,const void *key)
{
int i=0;
struct binding *p=NULL;
if(table && key){
i=(table->hash(key)) % table->size;
for(p=table->buckets[i];p;p=p->next)
if(0 == (table->cmp)(key,p->key))
break;
}
return p;
}
PUBLIC void *Table_Get(Table_T table,const void *key)
{
struct binding *p=SearchBindByKey( table,key);
return p ? p->value : NULL;
}
PUBLIC void *Table_Put(Table_T table,const void *key,void *value)
{
int i=0;
void *prev=NULL;
struct binding *p=SearchBindByKey(table,key);
if(NULL == p){
......
}else
prev=p->value;
p->value=value;
return prev;
}