typedef 的几种用法总结

用途一:简单类型

定义一种类型的别名,而不只是简单的宏替换。

1
typedef unsigned int UNIT;

可以用作同时声明指针类型的多个对象。

1
2
3
char *pa, pb;   // wrong
typedef char *pchar;
pchar pa, pb; // both are pointers to char

#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如你可以定义 1 为 ONE。
  • typedef 是由 编译器 执行解释的,#define 语句是由 预编译器 进行处理的。
  • typedef 如果放在所有函数之外,作用域是从定义开始直到文件结尾,反之则到函数结尾;#define 不管放在哪里,作用域都是从定义开始直到整个文件结尾。

用途二:结构体

在以前的 C 语言版本中,声明 struct 新变量时,必须要带上 struct,即形式为:struct 结构名 变量名

1
2
3
4
5
struct Point {
int x;
int y;
};
struct Point p1;

而在 C99 和 C++ 中,则可以直接写:结构名 变量名

1
2
3
4
5
typedef struct point {
int x;
int y;
} Point;
Point p1;

注意:以下某些写法是错误的,应该尽量避免。

1
2
3
4
5
6
7
8
9
10
11
12
13
// wrong
struct Point1 {
int x;
int y;
struct Point1 next; // 不能在定义中使用自己
};

// right
struct Point2 {
int x;
int y;
struct Point2 *next; // 可以使用指向自己的指针,因为不需要分配空间
};

还有这一种:

1
2
3
4
typedef struct tagNode {
char *item;
pNode next;
} *pNode;

用上面代码定义一个结构时,在 C 和 C++ 中都是错误的定义,编译器会报错,为什么呢?莫非 C 语言不允许在结构中包含指向它自己的指针吗?

C 语言当然允许在结构中包含指向它自己的指针(上一个例子),我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于 typedef 的应用。

根据我们上面的阐述可以知道:新结构体建立的过程中遇到了 pNext 域的声明,类型是 pNode,要知道 pNode 表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识通过 typedef 定义的 pNode。

解决办法有以下两种。可以不用 typedef 创建的新类型:

1
2
3
4
5
// 1
typedef struct tagNode {
char *item;
struct tagNode *next;
} *pNode;

或者先让 typedef 语句执行:

1
2
3
4
5
6
7
// 2
typedef struct tagNode *pNode; // 先说明 typedef

struct tagNode {
char *item;
pNode next;
}; // 注意:在这个例子中,你用 typedef 给一个还未完全声明的类型起新名字。C 语言编译器支持这种做法。

用途三:支持平台无关

用 typedef 来定义与平台无关的类型。比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:

1
typedef long double REAL;

在不支持 long double 的平台二上,改为:

1
typedef double REAL;

在连 double 都不支持的平台三上,改为:

1
typedef float REAL;

也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改

标准库就广泛使用了这个技巧,比如 size_t。另外,因为 typedef 是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏更加稳健,编译器会对类型进行检查。

用途四:复杂类型

给复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。

1
2
typedef int MyIntArray[100];  // 数组类型
MyIntArray abc; // abc 是一个整型数组,长度为 100

理解复杂声明可用「右左法则」:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。如:

规律:一般情况下,和定义变量的语法一样,前面加上 typedef,变量名即变成了类型名。

1
2
3
4
5
6
7
8
typedef void (*MyFuncPoint)(int);
// 我们定义 void (*MyFuncPoint)(int); 时,MyFuncPoint 是一个变量
// MyFuncPoint 是一个指向返回值为 void、参数为整型类型的函数的指针类型

typedef int (*func[5])(int *);
// 注意 [] 的优先级比 * 高
// func 是一个数组,数组里面可以存放 5 个元素,这些元素是指向返回值为整型、参数为指向整型的指针的函数的指针
// X[]:X 是数组,X():X 是函数,X 中没有解引决定不是指针

通过上面的例子,我们发现直接定义复杂类型的变量非常麻烦,特别是在需要重复定义多次的时候。因此,我们可以善用 typedef 进行简化:

1
2
// default method - define variable signal
void (*signal)(int);

signal 是一个函数指针。

1
2
3
4
5
6
7
// typedef method
typedef void (*HANDLER)(int); // 先定义 HANDLER 为该类型的函数指针
HANDLER signal;
// 通过 typedef 还可以简写函数声明。如果没有 HANDLER,将需要很冗长的代码去表达这个意思
HANDLER MyFunc(int, HANDLER);
// 或者用在数组
HANDLER MyArray[5];

参考

关于 typedef 的用法总结