如何理解 C 語言中的 typedef ?

看到這樣的一段代碼:

typedef void (* Func) (void)

不是十分明白,這裡http://blog.pfan.cn/asun/11983.html 雖然有解釋,但本人比較愚鈍,感覺理解的還不是很清楚,最好可以從編譯器的角度闡釋


重點是要明白聲明的「typedef名稱」和什麼同義。

比如在typedef void (* Func)(void) 中,要被聲明的「typedef名稱」是Func,也就是說,Funcvoid (*變數名)(void) 同義。而void (*變數名)(void)是一種函數指針形式,那麼Func也就是這種函數指針的同義了。


題主既然要從編譯器層面上來解釋,那就從編譯器上來解釋。

Dennis Ritchie 說C語言要有指針,並且要有*和兩個符號用來取值和取址。

如果有人寫下這行代碼

void (* func) (void);

編譯器就知道這一行聲明了一個函數指針,其指向的函數沒有參數,也沒有返回值。

編譯器作者苦思很久,終於做出了這些功能。

現在 Dennis Ritchie 又說,我們要有一個 typedef 功能,要能對某個類型指定別名。編譯器作者兩眼一瞪:你不早說!那就這麼來吧

typedef
void (* Func) (void);

Dennis Ritchie 說,你這不就和之前的聲明變數的時候一模一樣的嗎?

編譯器作者回答說:對呀,這樣省力呀,我已經寫了解析類型聲明的代碼了,現在我只要複製代碼就能用了!

Dennis Ritchie 仔細一想,也挺好:這樣不光你方便了,c語言的使用者也方便,因為typedef和普通的變數聲明的規則是一樣一樣的,好記,要是制定兩套規則,怕大家記不過來呀。

比如

int * a; // 聲明一個指向整型的指針
typedef
int * int_ptr; // 和上面的看起來一模一樣。

----

當然,Dennis Ritchie 就是寫C語言編譯器的人。所以上面的故事純屬虛構。

但是,這種盡量保持一致性的設計思想是很有用的。

最吊的是,C語言變數聲明的方式和使用的方式也是一致的,請看我在另一道題下面的回答:

C++中如何定義指向函數指針的指針? - 王霄池的回答


先熟悉指針函數的聲明形式:數據類型 (* 指針變數名) (形參列表);

例如: int (* func)(void);//這是一個變數聲明語句(指針函數的聲明),聲明了一個函數指針變數func,注意區別指針函數和函數指針。 func = test; //變數賦值(給指針函數賦值);

那麼現在來理解typedef int (* func)(void)語句;

1.去掉"typedef"得到一個正常的變數聲明語句(指針函數聲明): int (*func)(void);這裡變數func的類型為一個函數類型,而當加上」typedef"後,就得到一個新的類型, 原來的變數名稱func就變成新類型的名稱,func不再是變數名稱,而是一個新類型名--func,用這個新的類型就可以去定義和原來func變數相同類型的變數,注意看去除和加上"typedef「時func的變化,沒有"typedef"時,func為變數名,加上"typedef"時,func為類型名(這個類型等同於原來func變數的類型),因此下面正確:

typedef int (* t_func)(void);//定義了一個新類型 t_func;

int (*func)(void); 變數聲明語句(指針函數聲明):

//....

t_func func2 = func; //func2和func是相同類型,這就好理解加上"typedef"產生的變化了。

可以這麼想,加上"typedef"關鍵字後,原來語句聲明的變數變成類型,以下正確:

1. int id; ==&> typedef int ID; //在加上"typedef"後,變數id就變成類型ID(為了清楚,把類型變大寫),這個新類型等同於原來變數對應的類型; id &<==&>ID id ;

2. int a[10]; ==&> typedef int A[10];//加上"typedef"後,變數a就成了類型A(為了清楚,把類型變大寫),這個新類型等同於原來變數對應的類型; a &<=&> A a;

因此typedef就好理解。


僅供參考:typedef 用法總結


typedef其實很好理解,你知道如何聲明變數/函數/指針函數/結構體 等等C語言中的各種對象吧?

在這些聲明式前加上typedef 就將你聲明的變數名轉換成該類型的代名詞。舉幾個例子吧

typedef int INT;這個例子其實比較有誤導性,好像把int換了個名字似的。但實際上,看作int INT(聲明了一個整形變數),再在前面加上typedef 把INT轉變為int的代名詞

再舉個複雜點的例子

typedef void (*fp)();這裡如果沒有typedef,fp就是一個指針函數 指向一個返回值為void, 不接收參數的函數。加了typedef後 fp就變成了這種指針函數的代名詞。也就是說

fp x;

//對x賦值

(*x)();//就可以調用到x指向的函數了


單純對typedef的小總結,如下:

C語言中一些關於typedef的歸納

typedef 的一般使用:

typedef允許你為各種數據類型定義新名字。

typedef 如何聲明:

typedef聲明的寫法和普通的聲明基本相同,只是把typedef這個關鍵字出現在聲明的前面。

例如:

cptr_to_char a;

char b = "m";

a = b;

printf("%c
",*a);//輸出m

這個聲明把ptr_to_char作為指向字元的指針類型的新名字,聲明a是一個指向字元的指針。

這樣聲明的好處:使用typedef聲明類型可以減少使聲明變得冗長的危險。

Attention

你應該使用typedef而不是#define來創建新的類型名,因為後者無法正確地處理指針類型。

例如:

d _ptr_to_char a,b;

char c = "l";

a = c;

b = "h";

printf("%c
",*a);//輸出l

printf("%c
",b);//輸出h

正確地聲明了a,但是b卻被聲明為一個字元。在定義更為複雜的類型名時,如果函數指針或指向數組的指針,使用typedef更為合適。

typedef 在結構體中的使用

我們首先來看一個typedef與struct結合使用的例子:

typedef struct TagNode

{

int age;

char name[20];

char *address;

struct TagNode *next;

}Node,*pNode;

上面的代碼實際完成了兩個操作:

1.定義了一個新的結構體類型

struct TagNode

{

int age;

char name[20];

char *address;

struct TagNode *next;

}

其中TagNode稱為"Tag",即標籤,實際上是一個臨時名字,struct關鍵字和TagNode一起,構成了這個結構類型,不論是否有typedef,這個結構都存在。我們可以用struct TagNode來定義變數,但要注意,使用TagNode來定義變數是不對的,因為struct和TagNode合在一起才能表示一個結構類型。

2.typedef為這個結構起了新的名字,叫Node和*pNode。因此,我們可以直接使用Node來定義該結構體類型變數,使用pNode來定義指向該結構體類型的指針變數。

例如:

typedef struct TagNode

{

int age;

char name[20];

char *address;

struct TagNode *next;

}Node,*pNode;

int main()

{

Node lhk = {19,"Robert"};

pNode p;

lhk.address = (char*)malloc(80);//表示查找可用連續80個位元組內存的空間賦給address存儲地址。 p = lhk;//後續引用結構體成員時,以(*p).age形式引用。

...

}

吐槽一點,回答區不能直接粘貼markdown格式文本,很難受。

歸納於2017.9.9.23.08


個人覺得這篇博文講得比較透徹 C/C++基礎知識:typedef用法小結_C/C++編程


@Skycell 解釋挺清楚。

我說點自己的觀點

去讀kr c。裡面有一小部分專門講這個,不需要從編譯器角度講。

通常我都不願意讓人去讀書,搞得好像我讀過很多書似的。再說盡信書不如無書。

可是,不讀這本書就用c,好像不背九九表就想做乘法。

最後,這麼薄的書都不讀嗎?


推薦閱讀:

int i=0,j; for(j=3;i=j=0;i++,j++)循環多少次?
請問培訓,選擇python還是unity3d?
C語言 不定長數組不能在if結構里?
為什麼 C++ 能夠源碼級兼容C語言?
C中int main()和int main(int argc,char* argv[])的區別?

TAG:編程語言 | C編程語言 |