為什么很多人編程喜歡用typedef?如何避免濫用?
1. typedef 的基本使用
1.1 typedef與結(jié)構(gòu)體的結(jié)合使用
typedef 是 C 語(yǔ)言的一個(gè)關(guān)鍵字,用來(lái)給某個(gè)類型起個(gè)別名,也就是給C語(yǔ)言中已經(jīng)存在的一個(gè)類型起一個(gè)新名字。大家在閱讀代碼的過(guò)程中,會(huì)經(jīng)常見(jiàn)到 typedef 與結(jié)構(gòu)體、聯(lián)合體、枚舉、函數(shù)指針聲明結(jié)合使用。比如下面結(jié)構(gòu)體類型的聲明和使用:
struct student
{
char name[20];
int age;
float score;
};
struct student stu = {"wit", 20, 99};
在C語(yǔ)言中定義一個(gè)結(jié)構(gòu)體變量,我們通常的寫法是:
struct 結(jié)構(gòu)體名 變量名;
前面必須有一個(gè)struct關(guān)鍵字打前綴,編譯器才會(huì)理解你要定義的對(duì)象是一個(gè)結(jié)構(gòu)體變量。而在C++語(yǔ)言中,則不需要這么做,直接使用:結(jié)構(gòu)體名 變量名就可以了
struct student
{
char name[20];
int age;
float score;
};
int main (void)
{
student stu = {"wit", 20, 99};
return 0;
}
如果我們使用typedef,就可以給student聲明一個(gè)別名student_t和一個(gè)結(jié)構(gòu)體指針類型student_ptr,然后就可以直接使用student_t類型去定義一個(gè)結(jié)構(gòu)體變量,不用再寫struct,這樣會(huì)顯得代碼更加簡(jiǎn)潔。
#include <stdio.h>
typedef struct student
{
char name[20];
int age;
float score;
}student_t, *student_ptr;
int main (void)
{
student_t stu = {"wit", 20, 99};
student_t *p1 = &stu;
student_ptr p2 = &stu;
printf ("name: %s\n", p1->name);
printf ("name: %s\n", p2->name);
return 0;
}
程序運(yùn)行結(jié)果:
wit
wit
1. 2 typedef 與數(shù)組的結(jié)合使用
typedef除了與結(jié)構(gòu)體結(jié)合使用外,還可以與數(shù)組結(jié)合使用。定義一個(gè)數(shù)組,通常我們使用int array[10];即可。我們也可以使用typedef先聲明一個(gè)數(shù)組類型,然后再使用這個(gè)類型去定義一個(gè)數(shù)組。
typedef int array_t[10];
array_t array;
int main (void)
{
array[9] = 100;
printf ("array[9] = %d\n", array[9]);
return 0;
}
在上面的demo程序中,我們聲明了一個(gè)數(shù)組類型array_t,然后再使用該類型定義一個(gè)數(shù)組array,這個(gè)array效果其實(shí)就相當(dāng)于:int array[10]。
1.3 typedef 與指針的結(jié)合使用
typedef char * PCHAR;
int main (void)
{
//char * str = "學(xué)嵌入式";
PCHAR str = "學(xué)嵌入式";
printf ("str: %s\n", str);
return 0;
}
在上面的demo程序中,PCHAR 的類型是 char *,我們使用PCHAR類型去定義一個(gè)變量str,其實(shí)就是一個(gè)char *類型的指針。
1.4 typedef與函數(shù)指針的結(jié)合使用
定義一個(gè)函數(shù)指針,我們通常采用下面的形式:
int (*func)(int a, int b);
我們同樣可以使用typedef聲明一個(gè)函數(shù)指針類型:func_t
typedef int (*func_t)(int a, int b);
func_t fp; // 定義一個(gè)函數(shù)指針變量
寫個(gè)簡(jiǎn)單的程序測(cè)試一下,運(yùn)行OK:
typedef int (*func_t)(int a, int b);
int sum (int a, int b)
{
return a + b;
}
int main (void)
{
func_t fp = sum;
printf ("%d\n", fp(1,2));
return 0;
}
為了增加程序的可讀性,我們經(jīng)常在代碼中看到下面的聲明形式:
typedef int (func_t)(int a, int b);
func_t *fp = sum;
函數(shù)都是有類型的,我們使用typedef給函數(shù)類型聲明一個(gè)新名稱:func_t。這樣聲明的好處是:即使你沒(méi)有看到func_t的定義,也能夠清楚地知道fp是一個(gè)函數(shù)指針,代碼的可讀性比上面的好。
1.5 typedef與枚舉的結(jié)合使用
typedef enum color
{
red,
white,
black,
green,
color_num,
} color_t;
int main (void)
{
enum color color1 = red;
color_t color2 = red;
color_t color_number = color_num;
printf ("color1: %d\n", color1);
printf ("color2: %d\n", color2);
printf ("color num: %d\n", color_number);
return 0;
}
枚舉與typedef的結(jié)合使用方法跟結(jié)構(gòu)體類似:可以使用typedef給枚舉類型color聲明一個(gè)新名稱color_t,然后使用這個(gè)類型就可以直接定義一個(gè)枚舉變量。
2. 使用typedef的優(yōu)勢(shì)
不同的項(xiàng)目,有不同的代碼風(fēng)格,也有不同的代碼“癖好”??吹么a多了,你會(huì)發(fā)現(xiàn):有的代碼喜歡用宏,有的代碼喜歡使用typedef。那么,使用typedef到底有哪些好處呢?為什么很多人喜歡用它呢?
2.1 可以讓代碼更加清晰簡(jiǎn)潔
typedef struct student
{
char name[20];
int age;
float score;
}student_t, *student_ptr;
student_t stu = {"wit", 20, 99};
student_t *p1 = &stu;
student_ptr p2 = &stu;
如示例代碼所示,使用typedef,我們可以在定義一個(gè)結(jié)構(gòu)體、聯(lián)合、枚舉變量時(shí),省去關(guān)鍵字struct,讓代碼更加簡(jiǎn)潔。
2.2 增加代碼的可移植性
C語(yǔ)言的int類型,我們知道,在不同的編譯器和平臺(tái)下,所分配的存儲(chǔ)字長(zhǎng)不一樣:可能是2個(gè)字節(jié),可能是4個(gè)字節(jié),也有可能是8個(gè)字節(jié)。如果我們?cè)诖a中想定義一個(gè)固定長(zhǎng)度的數(shù)據(jù)類型,此時(shí)使用int,在不同的平臺(tái)環(huán)境下運(yùn)行可能會(huì)出現(xiàn)問(wèn)題。為了應(yīng)付各種不同“脾氣”的編譯器,最好的辦法就是使用自定義數(shù)據(jù)類型,而不是使用C語(yǔ)言的內(nèi)置類型。
#ifdef PIC_16
typedef unsigned long U32
#else
typedef unsigned int U32
#endif
在16位的 PIC 單片機(jī)中,int一般占2個(gè)字節(jié),long占4個(gè)字節(jié),而在32位的ARM環(huán)境下,int和long一般都是占4個(gè)字節(jié)。如果我們?cè)诖a中想使用一個(gè)32位的固定長(zhǎng)度的無(wú)符號(hào)類型,可以使用上面方式聲明一個(gè)U32的數(shù)據(jù)類型,在代碼中你可以放心大膽地使用U32。將代碼移植到不同的平臺(tái)時(shí),直接修改這個(gè)聲明就可以了。
在Linux內(nèi)核、驅(qū)動(dòng)、BSP 等跟底層架構(gòu)平臺(tái)密切相關(guān)的源碼中,我們會(huì)經(jīng)??吹竭@樣的數(shù)據(jù)類型,如size_t、U8、U16、U32。在一些網(wǎng)絡(luò)協(xié)議、網(wǎng)卡驅(qū)動(dòng)等對(duì)字節(jié)寬度、大小端比較關(guān)注的地方,也會(huì)經(jīng)常看到typedef使用得很頻繁。
2.3 比宏定義更好用
C語(yǔ)言的預(yù)處理指令#define用來(lái)定義一個(gè)宏,而typedef則用來(lái)聲明一種類型的別名。typedef跟宏相比,不僅僅是簡(jiǎn)單的字符串替換,可以使用該類型同時(shí)定義多個(gè)同類型對(duì)象。
typedef char* PCHAR1;
#define PCHAR2 char *
int main (void)
{
PCHAR1 pch1, pch2;
PCHAR2 pch3, pch4;
printf ("sizeof pch1: %d\n", sizeof(pch1));
printf ("sizeof pch2: %d\n", sizeof(pch2));
printf ("sizeof pch3: %d\n", sizeof(pch3));
printf ("sizeof pch4: %d\n", sizeof(pch4));
return 0;
}
在上面的示例代碼中,我們想定義4個(gè)指向char類型的指針變量,然而運(yùn)行結(jié)果卻是:
sizeof pch1: 4
sizeof pch2: 4
sizeof pch3: 4
sizeof pch4: 1
本來(lái)我們想定義4個(gè)指向char類型的指針,但是 pch4 經(jīng)過(guò)預(yù)處理宏展開(kāi)后,就變成成了一個(gè)字符型變量,而不是一個(gè)指針變量。而 PCHAR1 作為一種數(shù)據(jù)類型,在語(yǔ)法上其實(shí)就等價(jià)于相同類型的類型說(shuō)明符關(guān)鍵字,因此可以在一行代碼中同時(shí)定義多個(gè)變量。上面的代碼其實(shí)就等價(jià)于:
char *pch1, *pch2;
char *pch3, pch4;
2.4 讓復(fù)雜的指針聲明更加簡(jiǎn)潔
一些復(fù)雜的指針聲明,如:函數(shù)指針、數(shù)組指針、指針數(shù)組的聲明,往往很復(fù)雜,可讀性差。比如下面函數(shù)指針數(shù)組的定義:
int *(*array[10])(int *p, int len, char name[]);
上面的指針數(shù)組定義,很多人一瞅估計(jì)就懵逼了。我們可以使用typedef優(yōu)化一下:先聲明一個(gè)函數(shù)指針類型func_ptr_t,接著再定義一個(gè)數(shù)組,就會(huì)更加清晰簡(jiǎn)潔,可讀性就增加了不少:
typedef int *(*func_ptr_t)(int *p, int len, char name[]);
func_ptr_t array[10];
3. 使用typedef需要注意的地方
通過(guò)上面的示例代碼,我們可以看到,使用typedef可以讓我們的代碼更加簡(jiǎn)潔、可讀性更強(qiáng)一些。但是typedef也有很多坑,稍微不注意就可能翻車。下面分享一些使用typedef需要注意的一些細(xì)節(jié)。
3.1 typedef在語(yǔ)法上等價(jià)于關(guān)鍵字
我們使用typedef給已知的類型聲明一個(gè)別名,其在語(yǔ)法上其實(shí)就等價(jià)于該類型的類型說(shuō)明符關(guān)鍵字,而不是像宏一樣,僅僅是簡(jiǎn)單的字符串替換。舉一個(gè)例子大家就明白了,比如const和類型的混合使用:當(dāng)const和常見(jiàn)的類型(如:int、char) 一同修飾一個(gè)變量時(shí),const和類型的位置可以互換。但是如果類型為指針,則const和指針類型不能互換,否則其修飾的變量類型就發(fā)生了變化,如常見(jiàn)的指針常量和常量指針:
char b = 10;
char c = 20;
int main (void)
{
char const *p1 = &b; //常量指針:*p1不可變,p1可變
char *const p2 = &b; //指針常量:*p2可變,p2不可變
p1 = &c; //編譯正常
*p1 = 20; //error: assignment of read-only location
p2 = &c; //error: assignment of read-only variable`p2'
*p2 = 20; //編譯正常
return 0;
}
當(dāng)typedef 和 const一起去修飾一個(gè)指針類型時(shí),與宏定義的指針類型進(jìn)行比較:
typedef char* PCHAR2;
#define PCHAR1 char *
char b = 10;
char c = 20;
int main (void)
{
const PCHAR1 p1 = &b;
const PCHAR2 p2 = &b;
p1 = &c; //編譯正常
*p1 = 20; //error: assignment of read-only location
p2 = &c; //error: assignment of read-only variable`p2'
*p2 = 20; //編譯正常
return 0;
}
運(yùn)行程序,你會(huì)發(fā)現(xiàn)跟上面的示例代碼遇到相同的編譯錯(cuò)誤,原因在于宏展開(kāi)僅僅是簡(jiǎn)單的字符串替換:
const PCHAR1 p1 = &b; //宏展開(kāi)后是一個(gè)常量指針
const char * p1 = &b; //其中const與類型char的位置可以互換
而在使用PCHAR2定義的變量p2中,PCHAR2作為一個(gè)類型,位置可與const互換,const修飾的是指針變量p2的值,p2的值不能改變,是一個(gè)指針常量,但是*p2的值可以改變。
const PCHAR2 p2 = &b; //PCHAR2此時(shí)作為一個(gè)類型,與const可互換位置
PCHAR2 const p2 = &b; //該語(yǔ)句等價(jià)于上條語(yǔ)句
char * const p2 = &b; //const和PCHAR2一同修飾變量p2,const修飾的是p2!
3.2 typedef是一個(gè)存儲(chǔ)類關(guān)鍵字
沒(méi)想到吧,typedef在語(yǔ)法上是一個(gè)存儲(chǔ)類關(guān)鍵字!跟常見(jiàn)的存儲(chǔ)類關(guān)鍵字(如:auto、register、static、extern)一樣,在修飾一個(gè)變量時(shí),不能同時(shí)使用一個(gè)以上的存儲(chǔ)類關(guān)鍵字,否則編譯會(huì)報(bào)錯(cuò):
typedef static char * PCHAR;
//error: multiple storage classes in declaration of `PCHAR'
3.3 typedef 的作用域
跟宏的全局性相比,typedef作為一個(gè)存儲(chǔ)類關(guān)鍵字,是有作用域的。使用typedef聲明的類型跟普通變量一樣遵循作用域規(guī)則:包括代碼塊作用域、文件作用域等。
typedef char CHAR;
void func (void)
{
#define PI 3.14
typedef short CHAR;
printf("sizeof CHAR in func: %d\n",sizeof(CHAR));
}
int main (void)
{
printf("sizeof CHAR in main: %d\n",sizeof(CHAR));
func();
typedef int CHAR;
printf("sizeof CHAR in main: %d\n",sizeof(CHAR));
printf("PI:%f\n", PI);
return 0;
}
宏定義在預(yù)處理階段就已經(jīng)替換完畢,是全局性的,只要保證引用它的地方在定義之后就可以了。而使用typedef聲明的類型則跟普通變量一樣遵循作用域規(guī)則。上面代碼的運(yùn)行結(jié)果為:
sizeof CHAR in main: 1
sizeof CHAR in func: 2
sizeof CHAR in main: 4
PI:3.140000
4 如何避免typedef的濫用?
通過(guò)上面的學(xué)習(xí)我們可以看到:使用typedef可以讓我們的代碼更加簡(jiǎn)潔、可讀性更好。在實(shí)際的編程中,越來(lái)越多的人也開(kāi)始嘗試使用typedef,甚至到了“過(guò)猶不及”的濫用地步:但凡遇到結(jié)構(gòu)體、聯(lián)合、枚舉都要用個(gè)typedef封裝一下,不用就顯得你low、你菜、你的代碼沒(méi)水平。
其實(shí)typedef也有副作用,不一定非得處處都用它。比如上面我們封裝的STUDENT類型,當(dāng)你定義一個(gè)變量時(shí):
STUDENT stu;
不看STUDENT的聲明,你知道stu的含義嗎?未必吧。而如果我們直接使用struct定義一個(gè)變量,則會(huì)更加清晰,讓你一下子就知道stu是個(gè)結(jié)構(gòu)體類型的變量:
struct student stu;
一般來(lái)講,當(dāng)遇到以下情形時(shí),使用typedef可能會(huì)有用,否則可能會(huì)適得其反:
創(chuàng)建一個(gè)新的數(shù)據(jù)類型
跨平臺(tái)、指定長(zhǎng)度的類型:如U32/U16/U8
跟操作系統(tǒng)、BSP、網(wǎng)絡(luò)字寬相關(guān)的數(shù)據(jù)類型:如size_t、pid_t等
不透明的數(shù)據(jù)類型:需要隱藏結(jié)構(gòu)體細(xì)節(jié),只能通過(guò)函數(shù)接口訪問(wèn)的數(shù)據(jù)類型
在閱讀Linux內(nèi)核源碼過(guò)程中,你會(huì)發(fā)現(xiàn)大量使用了typedef,哪怕是簡(jiǎn)單的int、long都使用了typedef。這是因?yàn)椋篖inux內(nèi)核源碼發(fā)展到今天,已經(jīng)支持了太多的平臺(tái)和CPU架構(gòu),為了保證數(shù)據(jù)的跨平臺(tái)性和可移植性,所以很多時(shí)候不得已使用了typedef,對(duì)一些數(shù)據(jù)指定固定長(zhǎng)度:如U8/U16/U32等。但是內(nèi)核也不是到處到濫用,什么時(shí)候該用,什么不該用,也是有一定的規(guī)則要遵循的,具體大家可以看kernel Document中的 CodingStyle 中關(guān)于typedef的使用建議。(在此感謝“裸機(jī)思維”的推薦)
-END-
推薦閱讀
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!