C语言typedef应用场景大全(附带实例)
Linux 内核强烈要求慎用类型定义(typedef),但在某些情形下使用类型定义可以带来很多便利。根据笔者多年的工作经验,应考虑在下列场合使用类型定义。
比如,在 Win32 API 中,存在很多称为句柄(handle)的类型,比如 HWND 表示窗口句柄,代表一个窗口对象的值。在内部实现中,窗口句柄可能是一个指针,也可能是一个表示索引的整数。使用 HWND 的程序员不需要关心窗口句柄的内部实现,也不允许应用程序通过窗口句柄直接访问内部的数据结构,而只需要传递某个 API 返回的句柄给其他 API 使用即可。这种情况是使用类型定义的绝佳场合。
比如 HWND 就可以用一个和指针等宽的无符号整数类型(uintptr_t)来定义:
假定在 Windows 操作系统的内部实现中,HWND 可直接作为指针使用,那么在具体使用时,只要做一次强制类型转换即可,例如:
相比使用句柄的情形,若对结构体指针使用类型定义,则可以带来一个额外的优势:在内部使用时,不用进行强制类型转换。为此,我们在头文件中作如下声明和定义:
然后在内部的头文件或者源文件中,定义结构体的细节并实现相应的接口:
另外,这种做法在 C标准库中十分常见,比如 C标准库中全部大写的 FILE、DIR 等结构体,其内部细节不会暴露给应用程序。但用于描述目录项的结构体的细节则暴露给应用程序,并没有定义新的数据类型。
例如,下面的代码定义了一个名为 purc_document_type_k 的枚举类型来表示文档的类型:
如果能接受驼峰命名规则,那么也可以使用首字母大写的驼峰命名法来定义结构体的类型名称,例如:
但这里更推荐不使用后缀来定义结构体的类型名称,因为前面已经对整数类型、枚举类型和结构体指针类型使用了 _t 或者 _k 等后缀:
在早期的 C 代码中,由于当时的编译器不允许新的类型名称和已有的结构体类型名称相同,因此我们经常会看到下面的代码:
作为一个不建议自定义数据类型的例子,我们在新的 C 语言项目中,应避免对整数做类型定义。C99 标准已经在 <stdint.h> 头文件中针对不同宽度的整数类型定义了新的数据类型,比如 uint8_t、intptr_t、intmax_t 等,因此我们没有必要再自行针对不同的整数类型自定义新的数据类型。
当需要隐藏类型的实现细节时
可以在函数库的接口定义中使用类型定义,尤其当需要隐藏类型的实现细节时。也就是说,使用接口的程序员不需要关心类型的内部细节。比如,在 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!"); ... }
对结构体指针类型使用类型定义
可以对结构体指针使用类型定义,并使用 _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; }对结构体指针使用类型定义,即使头文件中声明的结构体名称不变,我们也可以在不同的源文件中为结构体定义不同的内部细节,这将带来极大的灵活性。
另外,这种做法在 C标准库中十分常见,比如 C标准库中全部大写的 FILE、DIR 等结构体,其内部细节不会暴露给应用程序。但用于描述目录项的结构体的细节则暴露给应用程序,并没有定义新的数据类型。
对枚举类型使用类型定义
对枚举类型使用类型定义并使用 _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;
对结构体类型使用特别的命名规则
如果确实需要对结构体进行类型定义,则可以对类型定义名称采用全大写且不带下画线的命名法,以便提示它是一个结构体的类型定义名称,如 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 等,因此我们没有必要再自行针对不同的整数类型自定义新的数据类型。