天天看點

C和C++中的一點泛化技巧

 首先講回調機制。回調機制,C中用函數指針,C++中用函數對象。

考慮一個比較大小的函數,當然,這個例子比較傻

C中:

int cmp(void* a,void* b,int (*fun)(const void*,const void*)) 

{

return fun(a,b);

}

我希望cmp可以用于任意類型的比較。第三個參數傳函數指針,這就是C語言的回調機制。真正做事的就是用fun函數。為了完成比較任務,自己寫用于具體類型比較的代碼,比如:

int f(const void* a,const void* b)

{

if(*(int*)a>*(int*)b) return 1;

else

return *(int*)a<*(int*)b?(-1):0;

}

這樣調用:

int a=6,b=3;

cmp((void*)&a,(void*)&b,f);

如此就實作了任意類型的比較,當然我們自己為任意其它類型寫比較函數。這種技巧可以在C庫函數qsort看到。原型是:

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

第四個參數就是傳一個用于比較的函數指針。以實作泛化的比較。

再看C++:

template <class T,class op>

int cmp(const T& a,const T& b,op f) 

{

return f(a,b);

}

C++中我們還是可以像C哪樣寫個用于回調的類似函數:

int f(int a,int b)

{

if(a>b) return 1;

else

return a<b?(-1):0;

}

這樣調用:

int a=6,b=3;

comp(a,b,f);

模闆機制會将第三個參數翻譯成函數指針。不過C++不鼓勵這樣。因為C++有自己的機制--函數對象。

函數對象是提供了operator()的一個類。它是一個類,但用法卻類型函數。

class op

{

public:

int operator() (int a,int b)

{

if(a>b) return 1;

else

return a<b?(-1):0;

}

};

op就是一個函數對象了。完成和C語言中函數f一樣的功能,即比較。

C和C++都是強類型語言。可以看出來,C和C++中為了解決不同類型(實作通用類型)的比較,都使用了一定的語言上的技巧。C++是用模闆。C語言是用void*的通用性。void*可以強制類型轉化為任意類型的指針。

上面實作是回調機制相關的。C++與C誰優誰劣呢?這讓我們進入下一個讨論主題:預設參數。

為了更加的通用,通過帶預設參數的方式就可以少寫一個參數了。

先看C。C語言本身不提供預設參數支援,要實作又需要一些技巧。還是上面例子,改一下。

int cmp(void* a,void* b,int (*fun)(const void*,const void*)) 

{

if(fun==NULL)  fun=f;

return fun(a,b);

}

函數f由預設提供。這樣當我們是比較int型大小,就可以把第三個參數直接寫0了.這個技巧就可以實作預設參數了。很多C庫函數中可以看到這個技巧的應用,比如說

int setvbuf( FILE *stream, char *buffer, int mode, size_t size );

如果第二個參數傳NULL,就預設由系統幫我們申請和管理緩沖區了。

再看C++:

C++中一方面提供函數的預設參數,我們可以直接寫

int cmp(void* a,void* b,int (*fun)(const void*,const void*)=f)

{

return fun(a,b);

}

這樣調用int a=6,b=3;

cmp(&a,&b);   //可以省略第三個參數,相比C語言,連傳NULL指針都不必了

C++對預設參數的支援比C進了一大步。關鍵還不是這裡。C++中,類模闆也可以提供預設的模闆參數。這種模闆預設參數的使用使得C++模闆變得威力無窮。但也同時是太過靈活的模闆,使C++變得難以控制。下面看個實際例子:

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

template<class RanIt, class Pred> void sort(RanIt first, RanIt last, Pred pr);

STL的sort函數與C語言的qsort是一個很好的對比。sort傳二個iterator就夠了。而qsort卻要傳一大堆的參數.

sort通過預設參數減掉了用于比較的函數。再通過iterator traits技巧及模闆推演又減掉了類型參數。

qsort沒這些技巧,一個參數都不能少。這樣一比較上面我提出的問題就很明白了:通過語言技巧的完美利用,C++可以寫得相當優美,而C醜陋得多。預設參數讓C++威力無窮!還有,C語言雖然利用void*通用性實作了一定程度的泛化,但卻失去了強類型檢驗的部分好處。如果使用都不洽當使用,會使問題變得糟糕很多。而C++在泛化的同時沒有太多失去類型安全檢查。事物都有二面性。C++的模闆機制也帶來了很多問題。模闆很C++成了一問很複雜的語言。光是文法就極難駕馭。而有些C++吹捧的狂熱分子使問題再嚴重。iterator_traits,模闆的模闆參數,泛型程式設計,模闆元程式設計.....C++走向極端。

關于這些語言上的是是非非,我不再深入去研究。模闆好不好?自己在使用中去慢慢體會。

前面講了回調機制,預設參數,下面我來談第三個問題,最後一個問題,泛化技巧的實作本質。

C在泛化上的本質,就是利用void*可以向任意類型轉換。C++在泛化的本質上,就是模闆機制。C++的模闆,會為所有類型都生成一個函數。相當于把用于一種類型比較的代碼Ctrl+c Ctrl+v了一遍。當然,編譯器在上述過程有些優化。模闆沒有增加運作時開銷,但增加程式的代碼段的長度。相比之下C的泛化能力弱多了。void*失去了類型安全的優勢。所能做的也有限。回調機制增加了靈活性,增加了少量的運作切換開銷。不帶來其它損失。

大緻就是這些了。我剛接觸C語言,還在學習中。還是我以前說過的,多少優秀的語言也無法阻止垃圾的程式員寫出糟糕的代碼。C++我駕馭不了,現學習C語言中....