C語言typedef關鍵字
關於馬甲的笑話。有這樣一個笑話:一個獵人在河邊抓捕一條蛇,蛇逃進了水裡。過一會,一個烏龜爬到岸邊。獵人一把抓住這個烏龜,大聲的說道:小樣,別你為你穿了個馬甲我就不認識你了!
typedef 關鍵字是個偉大的縫紉師,擅長做馬甲,任何東西穿上這個馬甲就立馬變樣。它可以把狼變成一頭羊,也能把羊變成一頭狼。甚至還可以把長著翅膀的鳥人變成天使,同樣也能把美麗的天使變成鳥人。所以,你千萬不要得罪它,一定要掌握它的脾氣,不然哪天我把你當鳥人,你可別怪我。O(∩_∩)O~
一、曆史的誤會——也許應該是typerename。
很多人認為typedef 是定義新的資料類型,這可能與這個關鍵字有關。本來嘛,type 是資料類型的意思;def(ine)是定義的意思,合起來就是定義資料類型啦。不過很遺憾,這種理解是不正確的。也許這個關鍵字該被替換為“typerename”或是別的詞。
typedef 的真正意思是給一個已經存在的資料類型(注意:是類型不是變數)取一個別名,而非定義一個新的資料類型。比如:華美絕倫的芍藥,就有個別名---“將離”。中國古代男女交往,往往以芍藥相贈,表達惜別之情,送芍藥就意味著即將分離。所以文人墨客就給芍藥取了個意味深長的別名-----“將離”。這個新的名字就表達了那種依依不捨的惜別之情…這樣新的名字與原來的名字相比,就更能表達出想要表達的意思。
在實際項目中,為了方便,可能很多資料類型(尤其是結構體之類的自訂資料類型)需要我們重新取一個適用實際情況的別名。這時候typedef 就可以協助我們。例如:
typedef struct student
{
//code
}Stu_st,*Stu_pst;
A)struct student stu1;和Stu_st stu1;沒有區別。
B)struct student *stu2;和Stu_pst stu2;和Stu_st *stu2;沒有區別。
這個地方很多初學者迷惑,B)的兩個定義為什麼相等呢?其實很好理解。我們把“struct student { /*code*/}”看成一個整體,typedef 就是給“struct student {/*code*/}”取了個別名叫“Stu_st”;同時給“struct student { /*code*/} *”取了個別名叫“Stu_pst”。只不過這兩個名字同時取而已,好比你給你家小狗取了個別名叫“大黃”,同時你妹妹給小狗帶了小帽子,然後給它取了個別名叫“小可愛”。^_^。
不管實在C還是C++代碼中,typedef這個詞都不少見,當然出現頻率較高的還是在C代碼中。typedef與#define有些相似,但更多的是不同,特別是在一些複雜的用法上,就完全不同了,看了網上一些C/C++的學習者的部落格,其中有一篇關於typedef的總結還是很不錯,由於總結的很好,我就不加修改的引用過來了。
用途一:
定義一種類型的別名,而不只是簡單的宏替換。可以用作同時聲明指標型的多個對象。比如:
char* pa, pb; // 這多數不符合我們的意圖,它只聲明了一個指向字元變數的指標,和一個字元變數;
以下則可行:
typedef char* PCHAR;
PCHAR pa, pb;
這種用法很有用,特別是char* pa, pb的定義,初學者往往認為是定義了兩個字元型指標,其實不是,而用typedef char* PCHAR就不會出現這樣的問題,減少了錯誤的發生。
用途二:
用在舊的C代碼中,協助struct。以前的代碼中,聲明struct新對象時,必須要帶上struct,即形式為: struct 結構名對象名,如:
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
而在C++中,則可以直接寫:結構名對象名,即:tagPOINT1 p1;
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1;
這樣就比原來的方式少寫了一個struct,比較省事,尤其在大量使用的時候,或許,在C++中,typedef的這種用途二不是很大,但是理解了它,對掌握以前的舊代碼還是有協助的,畢竟我們在項目中有可能會遇到較早些年代遺留下來的代碼。
用途三:
用typedef來定義與平台無關的類型。
比如定義一個叫 REAL 的浮點類型,在目標平台一上,讓它表示最高精度的類型為:
typedef long double REAL;
在不支援 long double 的平台二上,改為:
typedef double REAL;
在連 double 都不支援的平台三上,改為:
typedef float REAL;
也就是說,當跨平台時,只要改下 typedef 本身就行,不用對其他源碼做任何修改。
標準庫就廣泛使用了這個技巧,比如size_t。另外,因為typedef是定義了一種類型的新別名,不是簡單的字串替換,所以它比宏來得穩健。
這個優點在我們寫代碼的過程中可以減少不少代碼量哦!
用途四:
為複雜的聲明定義一個新的簡單的別名。方法是:在原來的聲明裡逐步用別名替換一部分複雜聲明,如此迴圈,把帶變數名的部分留到最後替換,得到的就是原聲明的最簡化版。舉例:
原聲明:void (*b[10]) (void (*)());
變數名為b,先替換右邊部分括弧裡的,pFunParam為別名一:
typedef void (*pFunParam)();
再替換左邊的變數b,pFunx為別名二:
typedef void (*pFunx)(pFunParam);
原聲明的最簡化版:
pFunx b[10];
原聲明:doube(*)() (*e)[9];
變數名為e,先替換左邊部分,pFuny為別名一:
typedef double(*pFuny)();
再替換右邊的變數e,pFunParamy為別名二
typedef pFuny (*pFunParamy)[9];
原聲明的最簡化版:
pFunParamy e;
理解複雜聲明可用的“右左法則”:從變數名看起,先往右,再往左,碰到一個圓括弧就調轉閱讀的方向;括弧內分析完就跳出括弧,還是按先右後左的順序,如此迴圈,直到整個聲明分析完。舉例:
int (*func)(int *p);
首先找到變數名func,外面有一對圓括弧,而且左邊是一個*號,這說明func是一個指標;然後跳出這個圓括弧,先看右邊,又遇到圓括弧,這說明(*func)是一個函數,所以func是一個指向這類函數的指標,即函數指標,這類函數具有int*類型的形參,傳回值類型是int。
int (*func[5])(int *);
func右邊是一個[ ]運算子,說明func是具有5個元素的數組;func的左邊有一個*,說明func的元素是指標(注意這裡的*不是修飾func,而是修飾func[5]的,原因是[]運算子優先順序比*高,func先跟[]結合)。跳出這個括弧,看右邊,又遇到圓括弧,說明func數組的元素是函數類型的指標,它指向的函數具有int*類型的形參,傳回值類型為int。
這種用法是比較複雜的,出現的頻率也不少,往往在看到這樣的用法卻不能理解,相信以上的解釋能有所協助。
下面還是舉個例子吧,將來如果真的用到就算賺了O(∩_∩)O~
#include<stdio.h> typedef int (*FP_CALC)(int, int); int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return b? a/b : -1; } FP_CALC calc_func(char op) { switch (op) { case '+': return add;//返回函數的地址 case '-': return sub; case '*': return mul; case '/': return div; default: return NULL; } return NULL; } int (*s_calc_func(char op)) (int, int) { return calc_func(op); } int calc(int a, int b, char op) { FP_CALC fp = calc_func(op); //根據預算符得到各種運算的函數的地址//同樣你也可以使用下面這種方法 //int (*s_fp)(int, int) = s_calc_func(op);//用於測試 //ASSERT(fp == s_fp); // 可以斷言這倆是相等的 if (fp) return fp(a, b); else return -1; } void main() { int a = 100, b = 20; printf("calc(%d, %d, %c) = %d\n", a, b, '+', calc(a, b, '+')); printf("calc(%d, %d, %c) = %d\n", a, b, '-', calc(a, b, '-')); printf("calc(%d, %d, %c) = %d\n", a, b, '*', calc(a, b, '*')); printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/')); }
結果
calc(100, 20, +) = 120calc(100, 20, -) = 80calc(100, 20, *) = 2000calc(100, 20, /) = 5
想要具體瞭解的可以看看這段代碼,相對理解起來簡單很多
待續。。。。。。