官术网_书友最值得收藏!

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標準庫中全部大寫的FILEDIR等結構體,其內部細節不會暴露給應用程序。但用于描述目錄項的結構體的細節則暴露給應用程序,并沒有定義新的數據類型。

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_tintptr_t、intmax_t等,因此我們沒有必要再自行針對不同的整數類型自定義新的數據類型。

主站蜘蛛池模板: 阿拉尔市| 博罗县| 色达县| 神农架林区| 寻乌县| 三原县| 翼城县| 余干县| 息烽县| 闻喜县| 普兰店市| 田林县| 廉江市| 左贡县| 安顺市| 曲水县| 商洛市| 岑巩县| 石台县| 郁南县| 云安县| 漳平市| 信丰县| 保定市| 仁怀市| 嘉兴市| 迁安市| 定日县| 莱阳市| 凉城县| 怀化市| 遵化市| 尉氏县| 同江市| 名山县| 耿马| 应城市| 南郑县| 七台河市| 习水县| 宕昌县|