為什麼很多人編程喜歡用typedef?如何避免濫用?

2021-03-02 嵌入式ARM
1. typedef 的基本使用1.1 typedef與結構體的結合使用

typedef 是 C 語言的一個關鍵字,用來給某個類型起個別名,也就是給C語言中已經存在的一個類型起一個新名字。大家在閱讀代碼的過程中,會經常見到 typedef 與結構體、聯合體、枚舉、函數指針聲明結合使用。比如下面結構體類型的聲明和使用:

struct student{char name[20];int  age;float score;};struct student stu = {"wit", 20, 99};

在C語言中定義一個結構體變量,我們通常的寫法是:

前面必須有一個struct關鍵字打前綴,編譯器才會理解你要定義的對象是一個結構體變量。而在C++語言中,則不需要這麼做,直接使用:結構體名 變量名就可以了

struct student{char name[20];int age;float score; };int main (void){  student stu = {"wit", 20, 99};return 0;}

如果我們使用typedef,就可以給student聲明一個別名student_t和一個結構體指針類型student_ptr,然後就可以直接使用student_t類型去定義一個結構體變量,不用再寫struct,這樣會顯得代碼更加簡潔。

#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;}程序運行結果:witwit

1. 2 typedef 與數組的結合使用

typedef除了與結構體結合使用外,還可以與數組結合使用。定義一個數組,通常我們使用int array[10];即可。我們也可以使用typedef先聲明一個數組類型,然後再使用這個類型去定義一個數組。

typedef int array_t[10]; array_t array;int main (void){array[9] = 100;printf ("array[9] = %d\n", array[9]);return 0;}

在上面的demo程序中,我們聲明了一個數組類型array_t,然後再使用該類型定義一個數組array,這個array效果其實就相當於:int array[10]。

1.3 typedef 與指針的結合使用

typedef char * PCHAR;int main (void){//char * str = "學嵌入式";  PCHAR str = "學嵌入式";printf ("str: %s\n", str);return 0;}

在上面的demo程序中,PCHAR 的類型是 char *,我們使用PCHAR類型去定義一個變量str,其實就是一個char *類型的指針。

1.4 typedef與函數指針的結合使用

定義一個函數指針,我們通常採用下面的形式:

int (*func)(int a, int b);

我們同樣可以使用typedef聲明一個函數指針類型:func_t

typedef int (*func_t)(int a, int b);func_t fp;  // 定義一個函數指針變量

寫個簡單的程序測試一下,運行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;}

為了增加程序的可讀性,我們經常在代碼中看到下面的聲明形式:

typedef int (func_t)(int a, int b);func_t *fp = sum;

函數都是有類型的,我們使用typedef給函數類型聲明一個新名稱:func_t。這樣聲明的好處是:即使你沒有看到func_t的定義,也能夠清楚地知道fp是一個函數指針,代碼的可讀性比上面的好。

1.5 typedef與枚舉的結合使用
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的結合使用方法跟結構體類似:可以使用typedef給枚舉類型color聲明一個新名稱color_t,然後使用這個類型就可以直接定義一個枚舉變量。

2. 使用typedef的優勢

不同的項目,有不同的代碼風格,也有不同的代碼「癖好」。看得代碼多了,你會發現:有的代碼喜歡用宏,有的代碼喜歡使用typedef。那麼,使用typedef到底有哪些好處呢?為什麼很多人喜歡用它呢?

2.1 可以讓代碼更加清晰簡潔
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,我們可以在定義一個結構體、聯合、枚舉變量時,省去關鍵字struct,讓代碼更加簡潔。

2.2 增加代碼的可移植性

C語言的int類型,我們知道,在不同的編譯器和平臺下,所分配的存儲字長不一樣:可能是2個字節,可能是4個字節,也有可能是8個字節。如果我們在代碼中想定義一個固定長度的數據類型,此時使用int,在不同的平臺環境下運行可能會出現問題。為了應付各種不同「脾氣」的編譯器,最好的辦法就是使用自定義數據類型,而不是使用C語言的內置類型。

#ifdef PIC_16typedef  unsigned long U32#elsetypedef unsigned int U32  #endif

在16位的 PIC 單片機中,int一般佔2個字節,long佔4個字節,而在32位的ARM環境下,int和long一般都是佔4個字節。如果我們在代碼中想使用一個32位的固定長度的無符號類型,可以使用上面方式聲明一個U32的數據類型,在代碼中你可以放心大膽地使用U32。將代碼移植到不同的平臺時,直接修改這個聲明就可以了。

在Linux內核、驅動、BSP 等跟底層架構平臺密切相關的源碼中,我們會經常看到這樣的數據類型,如size_t、U8、U16、U32。在一些網絡協議、網卡驅動等對字節寬度、大小端比較關注的地方,也會經常看到typedef使用得很頻繁。

2.3 比宏定義更好用

C語言的預處理指令#define用來定義一個宏,而typedef則用來聲明一種類型的別名。typedef跟宏相比,不僅僅是簡單的字符串替換,可以使用該類型同時定義多個同類型對象。

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個指向char類型的指針變量,然而運行結果卻是:

sizeof pch1: 4sizeof pch2: 4sizeof pch3: 4sizeof pch4: 1

本來我們想定義4個指向char類型的指針,但是 pch4 經過預處理宏展開後,就變成成了一個字符型變量,而不是一個指針變量。而 PCHAR1 作為一種數據類型,在語法上其實就等價於相同類型的類型說明符關鍵字,因此可以在一行代碼中同時定義多個變量。上面的代碼其實就等價於:

char *pch1, *pch2;char *pch3, pch4;

2.4 讓複雜的指針聲明更加簡潔

一些複雜的指針聲明,如:函數指針、數組指針、指針數組的聲明,往往很複雜,可讀性差。比如下面函數指針數組的定義:

int *(*array[10])(int *p, int len, char name[]);

上面的指針數組定義,很多人一瞅估計就懵逼了。我們可以使用typedef優化一下:先聲明一個函數指針類型func_ptr_t,接著再定義一個數組,就會更加清晰簡潔,可讀性就增加了不少:

typedef int *(*func_ptr_t)(int *p, int len, char name[]);func_ptr_t array[10];


3. 使用typedef需要注意的地方

通過上面的示例代碼,我們可以看到,使用typedef可以讓我們的代碼更加簡潔、可讀性更強一些。但是typedef也有很多坑,稍微不注意就可能翻車。下面分享一些使用typedef需要注意的一些細節。

3.1 typedef在語法上等價於關鍵字

我們使用typedef給已知的類型聲明一個別名,其在語法上其實就等價於該類型的類型說明符關鍵字,而不是像宏一樣,僅僅是簡單的字符串替換。舉一個例子大家就明白了,比如const和類型的混合使用:當const和常見的類型(如:int、char) 一同修飾一個變量時,const和類型的位置可以互換。但是如果類型為指針,則const和指針類型不能互換,否則其修飾的變量類型就發生了變化,如常見的指針常量和常量指針:

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;}

當typedef 和 const一起去修飾一個指針類型時,與宏定義的指針類型進行比較:

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;}

運行程序,你會發現跟上面的示例代碼遇到相同的編譯錯誤,原因在於宏展開僅僅是簡單的字符串替換:

const PCHAR1 p1 = &b; //宏展開後是一個常量指針const char * p1 = &b; //其中const與類型char的位置可以互換

而在使用PCHAR2定義的變量p2中,PCHAR2作為一個類型,位置可與const互換,const修飾的是指針變量p2的值,p2的值不能改變,是一個指針常量,但是*p2的值可以改變。

const PCHAR2 p2 = &b; //PCHAR2此時作為一個類型,與const可互換位置PCHAR2 const p2 = &b; //該語句等價於上條語句char * const p2 = &b; //const和PCHAR2一同修飾變量p2,const修飾的是p2!

3.2 typedef是一個存儲類關鍵字

沒想到吧,typedef在語法上是一個存儲類關鍵字!跟常見的存儲類關鍵字(如:auto、register、static、extern)一樣,在修飾一個變量時,不能同時使用一個以上的存儲類關鍵字,否則編譯會報錯:

typedef static char * PCHAR;


3.3 typedef 的作用域

跟宏的全局性相比,typedef作為一個存儲類關鍵字,是有作用域的。使用typedef聲明的類型跟普通變量一樣遵循作用域規則:包括代碼塊作用域、文件作用域等。

typedef char CHAR;
void func (void){#define PI 3.14typedef 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;}

宏定義在預處理階段就已經替換完畢,是全局性的,只要保證引用它的地方在定義之後就可以了。而使用typedef聲明的類型則跟普通變量一樣遵循作用域規則。上面代碼的運行結果為:

sizeof CHAR in main: 1sizeof CHAR in func: 2sizeof CHAR in main: 4PI:3.140000

4 如何避免typedef的濫用?

通過上面的學習我們可以看到:使用typedef可以讓我們的代碼更加簡潔、可讀性更好。在實際的編程中,越來越多的人也開始嘗試使用typedef,甚至到了「過猶不及」的濫用地步:但凡遇到結構體、聯合、枚舉都要用個typedef封裝一下,不用就顯得你low、你菜、你的代碼沒水平。

其實typedef也有副作用,不一定非得處處都用它。比如上面我們封裝的STUDENT類型,當你定義一個變量時:

不看STUDENT的聲明,你知道stu的含義嗎?未必吧。而如果我們直接使用struct定義一個變量,則會更加清晰,讓你一下子就知道stu是個結構體類型的變量:

一般來講,當遇到以下情形時,使用typedef可能會有用,否則可能會適得其反:

在閱讀Linux內核源碼過程中,你會發現大量使用了typedef,哪怕是簡單的int、long都使用了typedef。這是因為:Linux內核源碼發展到今天,已經支持了太多的平臺和CPU架構,為了保證數據的跨平臺性和可移植性,所以很多時候不得已使用了typedef,對一些數據指定固定長度:如U8/U16/U32等。但是內核也不是到處到濫用,什麼時候該用,什麼不該用,也是有一定的規則要遵循的,具體大家可以看kernel Document中的 CodingStyle 中關於typedef的使用建議。(在此感謝「裸機思維」的推薦)

-END-

免責聲明:整理文章為傳播相關技術,版權歸原作者所有,如有侵權,請聯繫刪除

 

相關焦點

  • struct和typedef struct
    分三塊來講述:  1 首先://注意在C和C++裡不同    在C中定義一個結構體類型要用typedef:    typedef struct Student    {    int a;    }Stu;    於是在聲明變量的時候就可:Stu stu1;(如果沒有typedef就必須用
  • C語言中typedef用法總結,看完就能像編程老手一樣熟練運用
    事實上,我們可以用typedef來定義自己習慣使用的數據類型名稱,可以替代自己所熟悉的基本類型、數組類型、指針類型以及自己定義的結構體類型、共用體類型、枚舉類型等。一旦我們在程序中定義了自己的數據類型名稱,我們就可以像使用int、float和double等基本數據類型一樣來使用它。實際使用中,typedef有以下幾種主要形式。
  • C語言中typedef與define的這些區別值得關注
    1、 從功能上來說有不同define指令用於宏定義,可以提高原始碼的可讀性,為編程提供方便,一般放在源文件的前面部分。詳細用法請見作者的另一篇文章,名為「C語言中的define預處理指令老手都是這樣用,你全都掌握了嗎?」,本文不再贅述。
  • typedef和#define的用法、區別以及陷阱!
    在C語言編程中,typedef和#define是最常用語句,可能很多工作過兩三年的工程師都沒有去深究過它們的一些用法和區別。
  • typedef和#define的用法、區別,以及陷阱
    素材來源:網絡編輯整理:strongerHuang在C語言編程中,typedef 和 #define是最常用語句,可能很多工作過兩三年的工程師都沒有去深究過它們的一些用法和區別
  • C語言typedef VS define,孰優孰劣?
    typedefC 語言提供了 typedef關鍵字,您可以使用它來為類型取一個新的名字。,但您也可以使用小寫字母,如下:typedef unsigned char byte;您也可以使用 typedef 來為用戶自定義的數據類型取一個新的名字。
  • 關於typedef的用法總結
    ,而用typedef char* PCHAR就不會出現這樣的問題,減少了錯誤的發生。  用途三:  用typedef來定義與平臺無關的類型。為什麼?  //因為hello是被定義了的對象實例了。  Q: 用struct和typedef struct 定義一個結構體有什麼區別?
  • typedef用法 與#define
    首先我們仔細看看typedef。本文引用地址:http://www.eepw.com.cn/article/201612/324896.htm一、typedef的用法人說typedef的使用可以編寫更加美觀和可讀的代碼,原因是typedef可以隱藏笨拙的語法結構以及平臺相關的數據類型,從而增加可移植性及未來的可維護性。
  • C語言#define和typedef的用法區別,以及陷阱
    ,typedef 和 #define是最常用語句,可能很多工作過幾年的工程師都沒有去深究過它們的一些用法和區別。有時很容易搞不清楚 #define 與 typedef 兩者到底該用哪個好,如#define INT int這樣的語句,用typedef一樣可以完成,用哪個好呢?我主張用typedef,因為在早期的許多C編譯器中這條語句是非法的,只是現今的編譯器又做了擴充。
  • 為什麼很多人喜歡用蘋果電腦辦公?
    Mac下的辦公軟體不如Windows好用,為什麼還有這麼多人推薦Mac來辦公呢?很多時候都是自我認為的,蘋果電腦的Max系統下有很多便利的地方,也有很多功能都要比windows的軟體使用更加方便,你看就連剪映軟體,在推出PC版本的時候率先是上架MAC版本,MAC系統下我雖然自己也用不習慣,但是據我的朋友說的,MAC系統下的視頻剪輯軟體、編程測試能力還是比windows更加優秀一點!
  • 兒科醫生說-如何避免給寶寶濫用抗生素?
    兒科門診中,經常碰到有些家長要求我給寶寶開抗生素,並認為寶寶不吃抗生素炎症就好不了,而在我國確實存在著抗生素濫用的情況,那麼什麼是抗生素呢?什麼情況下寶寶需要應用抗生素呢?如何避免給寶寶濫用抗生素呢?
  • 快速上手系列-C語言之typedef
    那具體是什麼類型的數據,它們可能長這個樣子:typedef int INT32;typedef short INT16;既然typedef的作用是給已有的類型重新定義一個新的名字,那麼如何使用呢?先說說typedef定義類型的方法:1、用typedef聲明新的類型名代替已有類型名2、typedef語句的的一般形式:typedef 原數據類型 新的類型名如在32位平臺我們給int重新取名INT32,形如:typedef int INT32;嗯,大概就是這個樣子的。
  • C語言之類型定義(typedef)
    typedef關鍵字可以用於給數據類型定義一個別名,比如說你本名叫關穀神奇,我嫌棄這個名字太長了,所以給你取一個別名,叫關谷,以後我叫關谷的時候你就知道在叫你了。當你定義了一個結構體時,每次創建一個結構體都要使用struct+結構體名的方式,而用了typedef之後,只要一個結構體別名就可以創建了。
  • C語言編程核心要點
    雖然C有抽象不足的缺點,但我更喜歡它的精巧,只需要花少量的時間,研究清楚它每一個知識點,看任何C源碼就不會存在語法上的障礙,大家需要建立的知識共識足夠少,少即是多,少好於多。我教過6個人編程,教過HTML,教過JAVA,也教過C++。最近,我在教我小孩編程,他只有十歲,很多人建議我選擇Python,但我最終選擇了C,因為C簡單且強大,現在看來,好像是個不錯的選擇。
  • 為什麼函數式編程在Java中很危險?
    但奇怪的是,我和我的同事並沒有為Haskell、Scheme、Lisp、Clojure、Scala而編程,這個行業裡的絕大部分人都會使用Python、 Ruby、Java或C#等編程,因為它們用起來比較順手。但在Java中,函數式編程卻是低效且危險的。
  • C語言結構體用法很多,坑也很多
    結構體同時也是一些元素的集合,這些元素稱為結構體的成員(member),且這些成員可以為不同的類型,成員一般用名字訪問。,typedef是一個關鍵字。當然,也可以這麼用:上面這種定義就失去了typedef的意思,所以不推薦。方法5:使用typedef定義結構體時,省掉結構體第一個別名stu,直接在後面加STU,使用方法同上。
  • 一步步分析:C語言如何面向對象編程
    那麼沒有想過,當初為什麼要擴展出C++?C語言有什麼樣的缺點導致C++的產生?其實最後一個優點是最重要的:使用的人越多,生命力就越強。就像現在的社會一樣,不是優者生存,而是適者生存。這篇文章,我們就來聊聊如何在C語言中利用面向對象的思想來編程。也許你在項目中用不到,但是也強烈建議你看一下,因為我之前在跳槽的時候就兩次被問到這個問題。
  • 為什麼說選擇正確的程式語言很重要,以及如何正確的選擇
    我學會了很多前所未聞的髒話,也有所得–即便是追求精簡的初創企業也傾向於把問題過份複雜化。將真正領悟精簡精神的人甄別出來並不困難。谷歌,Facebook以及Akamai的程師們的講座魅力十足。他們從一個更宏觀的角度思考和解決問題。這跟公司的財力,規模沒有關係,他們特意剪除細枝末節,以便將注意力集中在問題的根本。
  • 如何規範C語言編程?
    雖然這些的確可能不會影響到程序的運行,但作為嚴謹嚴肅的程式設計師,優質的程序需要精心的雕琢,應該儘量避免這種情況。網友表示,這種三流程式設計師就不要多說了,可能連阿里一面都過不了。那麼該如何規範C語言編程?3 標識符命名3-1:標識符的命名要清晰、明了,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
  • 編程人的「對象」長啥樣?
    作者 | 編程指北 責編 | 張文頭圖 | CSDN 下載自視覺中國別誤會,今天要寫的不是我對象,畢竟我還沒有......這篇文章主要是聊聊我對於程式語言中「對象」的一些簡單認識,面向過程 VS 面向對象為什麼 C 叫面向過程(Procedure Oriented)的語言,而 Java、C++ 之類叫面向對象