用途一:简单类型
定义一种类型的别名,而不只是简单的宏替换。
1 | typedef unsigned int UNIT; |
可以用作同时声明指针类型的多个对象。
1 | char *pa, pb; // wrong |
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如你可以定义 1 为 ONE。
- typedef 是由
编译器
执行解释的,#define 语句是由预编译器
进行处理的。 - typedef 如果放在所有函数之外,作用域是从定义开始直到文件结尾,反之则到函数结尾;#define 不管放在哪里,作用域都是从定义开始直到整个文件结尾。
用途二:结构体
在以前的 C 语言版本中,声明 struct 新变量时,必须要带上 struct,即形式为:struct 结构名 变量名
。
1 | struct Point { |
而在 C99 和 C++ 中,则可以直接写:结构名 变量名
。
1 | typedef struct point { |
注意:以下某些写法是错误的,应该尽量避免。
1 | // wrong |
还有这一种:
1 | typedef struct tagNode { |
用上面代码定义一个结构时,在 C 和 C++ 中都是错误的定义,编译器会报错,为什么呢?莫非 C 语言不允许在结构中包含指向它自己的指针吗?
C 语言当然允许在结构中包含指向它自己的指针(上一个例子),我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于 typedef 的应用。
根据我们上面的阐述可以知道:新结构体建立的过程中遇到了 pNext 域的声明,类型是 pNode,要知道 pNode 表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识通过 typedef 定义的 pNode。
解决办法有以下两种。可以不用 typedef 创建的新类型:
1 | // 1 |
或者先让 typedef 语句执行:
1 | // 2 |
用途三:支持平台无关
用 typedef 来定义与平台无关的类型。比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
1 | typedef long double REAL; |
在不支持 long double 的平台二上,改为:
1 | typedef double REAL; |
在连 double 都不支持的平台三上,改为:
1 | typedef float REAL; |
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如 size_t。另外,因为 typedef 是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏更加稳健,编译器会对类型进行检查。
用途四:复杂类型
给复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。
1 | typedef int MyIntArray[100]; // 数组类型 |
理解复杂声明可用「右左法则」:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。如:
规律:一般情况下,和定义变量的语法一样,前面加上 typedef,变量名即变成了类型名。
1 | typedef void (*MyFuncPoint)(int); |
通过上面的例子,我们发现直接定义复杂类型的变量非常麻烦,特别是在需要重复定义多次的时候。因此,我们可以善用 typedef 进行简化:
1 | // default method - define variable signal |
signal
是一个函数指针。
1 | // typedef method |