C++的模板機制,給通用數據類型編程帶來了很大的方便.那麼C語言能否實現與此類似的 功能呢?當然可以,看一看C的某些庫函數接口就知道了,比如快速排序函數便是通用數據 類型編程的一個例子:
void qsort(void *base, sizet_t nmemb, sizet_t size,
int(*compar)(const void *, const void *));該函數可以排序任意類型的數據,只要提供正確 的比較函數即可.
那麼,C語言中如何實現通用數據編程?本文將對這個問題作詳細的解析,並最終利用所述 方法,實現一個通用數據類型棧.
數據的共性
所謂通用數據類型編程,即對於不同類型的數據,完成相同的操作時所使用的代碼也相 同.比如,對於變量賦值操作,不管實際的變量類型是什麼,若均可以通過一條相同的語 句完成賦值操作,則此時的編程是通用數據類型的.此時的代碼可能為:void assign(T var2, T var1);其中assign函數完成賦值操作,var1的值賦給var2,暫且用T表示變量var1和var2的類型.
觀察assign函數,我們不禁要問:若想將var1的值賦給var2,直接使用語句:var2=var1不 就行了嗎,有必要寫一個專門的函數嗎?要回答這個問題,首先需要知道以下兩點:
"="本質上是一個函數,與var2=var1等價的函數形式為:=(var2,var1),即名為"=",且 接受兩個參數的函數.
"="只能用於常規類型數據間的賦值操作,而不能用於字符串,結構體等類型的賦值 操作.
因此,為了實現通用數據類型的賦值操作,將該操作寫為一個函數是很有必要的.那麼如 何實現該函數呢?首先分析一下已有數據類型的賦值操作是如何完成的:
常規數據類型.對於這類數據,使用"="便可完成變量間的賦值操作,但在賦值的過程 中,真正發生了哪些事情呢?實際上,雖然不同數據類型的賦值語句均相同(均為var2=var1),但其背後發生的事情並不相同.例如若var1,var2均為int型,則語句var2=var1的實際效果是:將var1所占用4個字節內存空間中的內容拷貝到var2所占 用4個字節的內存空間中;但若var1,var2均為char型,則語句var2=var1的實際效果 是:將var1所占用1個字節的內存空間中內容拷貝到var2所占用1個字節的內存空間 中.
由以上分析可知,數據賦值是通過 內存單元的復制實現的,數據類型不同,復制的內 存單元數也不同.
字符串.字符串的賦值操作通過 函數strcpy()完成.那麼strcpy()是如何完成字符 串的賦值操作呢?strcpy()的一個簡化實現為:
char *strcpy(char *dest, const char *src)
{
char *desto=dest;
while(*dest++ = *src++);
return desto;
} 從代碼可以看出,字符串的賦值實際上為將源字符串所占用內存空間內容拷貝到目 標字符串所占用內存空間.
通過以上兩點分析,可以發現一個共性:對於賦值操作,不管數據類型是什麼,均通過拷 貝內存空間內容實現,只不過對於不同的數據類型,拷貝的內存單元數目不同而已.因此,我 們可以根據相同的思路實現我們的賦值函數.那麼此時擺在面前的問題是:如何獲得變 量所占用內存空間,這又相當於以下兩個問題:
如何獲取變量所占用內存空間的起始地址;
如何獲取變量所占用內存空間的單元數.
在接下來的內容中,我們將對這兩個問題逐一解答.
void指針
獲取變量的起始地址非常容易,因為C語言提供了專門的操作符:"&",取地址操作符.我 們可以將該地址保存在一個指針中,以方便使用.但問題在於:由於我們要實現通用數據 類型編程,而實際的操作數的類型都是某一特定數據類型,那麼我們應該將這個操作數 的地址保存在什麼類型的指針變量中呢?讓我們先設想一下,這個指針不應該是任何特 定類型的指針,但它卻必須幾乎總是被賦值為另一類型的指針,因此其應該能與其它類 型指針方便的進行類型轉換.實際上,C語言提供了這樣的指針:即void指針,該指針的特 性如下:
void指針不與任何數據類型關聯,它可以不使用強制類型轉換,自動的與其它類型指 針進行轉換.
對於void指針不能使用"*"操作符,因為void指針不知道其所指數據所占用的內存單 元數.
void指針的++操作為其地址值增1.
由於不能取消引用一個void指針(第2條),因此它只能用於與其它類型指針轉換.實際上 總是利用這一點來完成通用數據類型編程.這一過程可表示為:
專用代碼->特定類型指針->void指針->通用代碼->void指針->選定類型指針->專用代碼.
從中可看出,void指針是通用代碼與專用代碼間的紐帶,對於需要通用處理的代碼,其與 其它代碼的信息傳遞必須通過void指針,因此其接口必須設計為void指針類型.
賦值函數的實現
讓我們回到之前的通用數據類型賦值函數,現在我們完全可以實現它了!實現的思路為: 將它的兩個操作數的指針傳遞給該函數,並指定數據所占用內存空間的單元數,然後在 函數內部將源操作數內存空間內容拷貝至目標操作數內存空間,即可完成賦值操作.那 麼,函數的原型應該為:void assign(void *pvdes, void *pvsrc, sizet size);其中參數的意義為:
pvdes:目標操作數指針
pvsrc:源操作數指針
size:數據字節寬度
函數的一個使用實例為:
int ia=2;
int ib;
assign(&ib,&ia,sizeof(int)) double da=2.0;
double db;
assign(&db,&da,sizeof(double));
該例子使用assign函數完成了int型變量和double型變量的賦值操作,雖然變量類型不 同,但assign的調用方式完全相同,因此該函數具有通用數據類型的性質.
以上賦值操作的本質是內存的復制,對於這一操作,C語言提供了專門的函數:
void *memcpy(void *dest, const void *src, sizet n);可以看出,assign()與memcpy()的接口基本相同,因此,在接下來的內容中,我們將直接 使用memcpy()完成賦值操作.
通用數據類型棧
在以上內容的基礎之上,離真正的實現一個通用數據類型棧已經不遠了.因為其中最關鍵 的部分是實現變量的賦值,而該問題已經被我們解決.
棧的實現思路為:在棧的初始化過程中,指定棧中元素的數據寬度,將該值保存在棧中一 個變量中;在使用push向棧中保存元素時,采用以上的賦值函數進行數據傳遞;pop操作 與push實現機理相同;棧使用單鏈表保存數據.