1.4.4 善用類型定義
Linux內核強烈要求慎用類型定義(typedef),但在某些情形下使用類型定義可以帶來很多便利。根據筆者多年的工作經驗,應考慮在下列場合使用類型定義。
1.當需要隱藏類型的實現細節時
可以在函數庫的接口定義中使用類型定義,尤其當需要隱藏類型的實現細節時。也就是說,使用接口的程序員不需要關心類型的內部細節。比如,在Win32 API中,存在很多稱為句柄(handle)的類型,比如HWND
表示窗口句柄,代表一個窗口對象的值。在內部實現中,窗口句柄可能是一個指針,也可能是一個表示索引的整數。使用HWND
的程序員不需要關心窗口句柄的內部實現,也不允許應用程序通過窗口句柄直接訪問內部的數據結構,而只需要傳遞某個API返回的句柄給其他API使用即可。這種情況是使用類型定義的絕佳場合。比如HWND
就可以用一個和指針等寬的無符號整數類型(uintptr_t
)來定義:
typedef uintptr_t HWND
假定在Windows操作系統的內部實現中,HWND
可直接作為指針使用,那么在具體使用時,只要做一次強制類型轉換即可,例如:
static void foo(HWND hWnd) { WINDOW *pWin = (WINDOW *)hWnd; pWin->spCaption = strdup("Hello, world!"); ... }
2.對結構體指針類型使用類型定義
可以對結構體指針使用類型定義,并使用_p
或者_t
后綴,例如:
struct list_node { const char *title; struct list_node *next; }; typedef struct list_node *list_node_p;
使用_p
后綴和_t
后綴的區別是,當結構體的內部細節暴露在外時,意味著外部代碼可以訪問結構體內的成員,此時使用_p
后綴;反之,當結構體的內部細節被隱藏時,意味著外部代碼不可以訪問結構體內的成員,此時結構體指針的作用類似于上面提到的句柄,對外部代碼而言,結構體指針相當于一個普通的無符號整數值,因而使用_t
后綴。
相比使用句柄的情形,若對結構體指針使用類型定義,則可以帶來一個額外的優勢:在內部使用時,不用進行強制類型轉換。為此,我們在頭文件中作如下聲明和定義:
struct list_node; typedef struct list_node *list_node_t; /* Returns the title in the specific node */ const char *list_node_get_title(list_node_t node);
然后在內部的頭文件或者源文件中,定義結構體的細節并實現相應的接口:
struct list_node { const char *title; struct list_node *next; }; const char *list_node_get_title(list_node_t node) { return node->title; }
對結構體指針使用類型定義,即使頭文件中聲明的結構體名稱不變,我們也可以在不同的源文件中為結構體定義不同的內部細節。這將帶來極大的靈活性,詳見第6章。
另外,這種做法在C標準庫中十分常見,比如C標準庫中全部大寫的FILE
、DIR
等結構體,其內部細節不會暴露給應用程序。但用于描述目錄項的結構體的細節則暴露給應用程序,并沒有定義新的數據類型。
3.對枚舉類型使用類型定義
對枚舉類型使用類型定義并使用_k
后綴,就可以和后綴為_t
或_p
的類型區分開來。例如,下面的代碼定義了一個名為purc_document_type_k
的枚舉類型來表示文檔的類型:
typedef enum { PCDOC_K_TYPE_FIRST = 0, PCDOC_K_TYPE_VOID = PCDOC_K_TYPE_FIRST, PCDOC_K_TYPE_PLAIN, PCDOC_K_TYPE_HTML, PCDOC_K_TYPE_XML, PCDOC_K_TYPE_XGML, /* XXX: change this when you append a new operation */ PCDOC_K_TYPE_LAST = PCDOC_K_TYPE_XGML, } purc_document_type_k;
4.對結構體類型使用特別的命名規則
如果確實需要對結構體進行類型定義,則可以對類型定義名稱采用全大寫且不帶下畫線的命名法,以便提示它是一個結構體的類型定義名稱,如LINKEDLIST
。這樣就不會與采用全小寫加下畫線形式的變量名或函數名,以及采用全大寫形式但使用下畫線的常量名或宏名產生混淆了。
typedef struct LINKEDLIST { const char *title; struct linked_list *next; } LINKEDLIST;
如果能接受駝峰命名規則,那么也可以使用首字母大寫的駝峰命名法來定義結構體的類型名稱,例如:
struct LinkedList { const char *title; struct LinkedList *next; }; typedef struct LinkedList LinkedList;
但這里更推薦不使用后綴來定義結構體的類型名稱,因為前面已經對整數類型、枚舉類型和結構體指針類型使用了_t
或者_k
等后綴:
typedef struct linked_list { const char *title; struct linked_list *next; } linked_list; typedef struct linked_list *linked_list_t;
在早期的C代碼中,由于當時的編譯器不允許新的類型名稱和已有的結構體類型名稱相同,因此我們經常會看到下面的代碼:
struct _LINKEDLIST { const char *title; struct linked_list *next; }; typedef struct _LINKEDLIST LINKEDLIST;
或者
struct tagLINKEDLIST { const char *title; struct linked_list *next; }; typedef struct tagLINKEDLIST LINKEDLIST;
上述代碼在結構體的類型名稱中使用下畫線和tag
作為前綴以示區別,但現在已經不需要這樣做了。
作為一個不建議自定義數據類型的例子,我們在新的C語言項目中,應避免對整數做類型定義。C99標準已經在<stdint.h>
頭文件中針對不同寬度的整數類型定義了新的數據類型,比如uint8_t
、intptr_t
、intmax_t
等,因此我們沒有必要再自行針對不同的整數類型自定義新的數據類型。
- Functional Python Programming
- Flask Web全棧開發實戰
- 大學計算機基礎(第三版)
- Java開發入行真功夫
- Java程序設計與實踐教程(第2版)
- Instant RubyMotion App Development
- 軟件測試技術指南
- Visual C#通用范例開發金典
- Learning OpenStack Networking(Neutron)(Second Edition)
- 區塊鏈底層設計Java實戰
- SQL Server 2016 從入門到實戰(視頻教學版)
- 深入解析Java編譯器:源碼剖析與實例詳解
- Implementing Microsoft Dynamics NAV(Third Edition)
- Android嵌入式系統程序開發(基于Cortex-A8)
- 自己動手構建編程語言:如何設計編譯器、解釋器和DSL