typedef是一种有趣的声明形式:它为一种类型引入新的名字,而不是为变量分配空间。在某些方面,typedef类似于宏文本替换——它并没有引入新的类型,而是为现有类型取个新名字,但它们之间存在一个关键性的区别。
typedef关键字可以是一个常规声明的一部分,可以出现在靠近声明开始部分的任何地方。事实上,typedef 的格式与变量声明完全一样,只是多了这个关键字,向你提醒它的实质。由于typedef看上去跟变量声明完全一样,它们读起来也是一样的。普通的声明表示“这个名字是一个指定类型的变量”,而typedef关键字并不是创建一个变量,而是宣称“这个名字是指定类型的同义词”。
一般情况下,typedef用于简洁的表示指向其他东西的指针。典型的例子是signal()原型的声明。signal()是一种系统调用,用于通知运行时系统:当某种特定的“软件中断”发生时调用特定的程序。调用signal()并通过传参告诉它中断的类型以及用于处理中断的程序。在ANSI C标准中,signal()的声明如下:
void (*signal(int sig,void(*func)(int)))(int);
用分析C语言的声明(2)介绍的方法分析这个声明:
首先:
void (*signal( ))(int);
signal是一个函数,它返回一个函数指针,后者所指向的函数接受一个int参数并返回void。
其中提取出来的通用的部分是:
void (*func)(int);
这个表示一个函数指针,所指向的函数接受一个int参数,返回值是void。下面用typedef来“代表”通用部分,从而进行简化:
typedef void (*ptr_to_func) (int);
/* 它表示ptr_to_func是一个函数指针,该函数接受一个int参数,返回值为void。*/
ptr_to_func signal(int, ptr_to_func);
/* 它表示signal是一个函数,它接受两个参数,
其中一个是int,另一个是ptr_to_func,返回值是ptr_to_func。*/
然而,说到typedef就不能不说一下它的缺点。它同样具有其他声明一样的混乱语法,同样可以把几个声明器塞到一个声明中去。对于结构体,除了可以在书写时省掉struct关键字之外,typedef并不能提供显著的好处,而少些一个struct其实并没有多大帮助。在任何typedef声明中,甚至不必把typedef放在声明的开始位置。
typedef和#define的区别
typedef和define之间有一个关键性的区别。正确思考这个问题的方法就是把typedef看成是一种彻底的“封装”类型——在声明它之后不能再往里面增加别的东西。它和define的区别体现在两个方面:
1.可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。如下所示:
#define peach int
unsigned peach i ; //没有问题
typedef int banana;
unsigned banana i;//错误,非法
2.在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示:
#define int_ptr int *
int_ptr chalk, cheese;
经过宏扩展,第二行变为:
int * chalk, cheese;
这使得chalk和cheese成为不同的类型:chalk是一个指向int的指针,而cheese则是一个int。
typedef char * char_ptr;
char_ptr Bentley, Rolls_Royce;
使用typedef定义的类型能够保证它们的类型相同,都是指向char的指针。
typedef struct foo{ ... foo;}的含义
C语言存在多种名字空间:
- 标签名(label name)。
- 标签(tag):这个名字空间用于所有的结构体、枚举和联合。
- 成员名:每个结构体或联合都有自身的名字空间。
- 其他。
在同一个名字空间里,任何名字必须具有唯一性,但在不同的名字空间里可以存在相同的名字。由于每个结构体或联合具有自己的名字空间,所以同一个名字可以出现在许多不同的结构内。有时可以看到这样的代码:
struct foo { int foo;} foo;
这显然会让将来维护这段代码的程序员感到困惑和沮丧,你说sizeof(foo)到底是表示哪个foo呢?
有些东西更加稀罕,像下面这样的声明竟然也是合法的:
typedef struct baz { int baz; } baz;
struct baz variable_1;
baz variable_2;
太多的“baz”了!让我们换一些清楚的名字,整理一下:
typedef struct my_tag { int i; } my_type;
struct my_tag variable_1;
my_type variable_2;
这个typedef声明引入了my_type这个名字作为"struct my_tag { int i; }"的简写形式。如果你用typedef声明一个标识符表示struct 结构类型,那么以后使用这个标识符时前面就不必加上关键字struct 了。但这个typedef声明同时也引入了结构标签my_tag,在它面前加个关键字struct可以表示同样的意思。
下面两个声明具有相似的形式:
typedef struct fruit { int weight , price_per_lb; } fruit; // 语句1
struct veg { int weight , price_per_lb; } veg; //语句2
但它们代表的意思却完全不一样,语句1声明了结构体标签”fruit“和由typedef声明的结构体类型”fruit“,其实际效果如下:
struct fruit mandarin; //使用结构体标签”fruit“
fruit mandarin; //使用结构体类型”fruit“
语句2声明了结构体标签”veg“和变量veg。只有结构体标签能够在以后的声明中使用,如:
struct veg potato;
如果试图使用veg cabbage这样的声明,将是一个错误。
使用typedef的一些提示:
1.不要在一个typedef中放入几个声明器,如下所示:
typedef int *ptr, (fun()), arr[5];
/* ptr是“指向int的指针”类型,
* fun是“指向返回值为int函数的指针”类型
* arr是“长度为5的int型数组”类型
*/
2.千万不要把typedef嵌到声明的中间部分,如下所示:
unsigned const long typedef int volatile *kumquat;
3.typedef为数据类型创建别名,而不是创建新的数据类型,可以对任何类型进行typedef声明。
typedef int (*array_ptr)[100];
4.应该只对所希望的变量类型进行typedef声明,为变量类型取一个喜欢的别名。关键字typedef应该如前所述出现声明的开始位置。在同一个代码块中,typedef引入的名字不能与其他标识符同名。
5.不要为了方便起见对结构体使用typedef。这样做唯一的好处是能使你不必书写”struct“关键字,但这个关键字可以向你提示一些信息,你不应该把它省掉。应该始终在结构体的定义中使用结构标签,即使它并非必须。这种做法可以使代码更为清晰。
6.typedef应该用在:
- 数组、结构体、指针以及函数的组合类型。
- 可移植类型。比如当你需要一种至少20bit的类型时,可以对它进行typedef操作。这样,当把代码移植到不同的平台时,要选择正确的类型如short,int,long时,只要在typedef中进行修改就可以了,无需对每一个声明都加以修改。
- typedef也可以为后面的强制类型转换提供一个简单的名字,如:
typedef int (*ptr_to_int_fun)(void);
char * p;
...;
... = (ptr_to_int_fun) p;