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函數和宏都是好東西?!吨貥嫛芬粫铩吨匦陆M織你的函數》那章有許多有趣的方法,我們同樣可以采納用來改善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;
}