在 C 语言之 typedef 自定义类型基础 中介绍了 typedef
的使用,下面介绍一些 typedef
的实践。
typedef
是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。
当 const
与 typedef
相结合时,如定义:
1 | typedef char* PSTR; |
const PSTR
实际上相当于指针常量 const char*
吗?
不是的,它实际上相当于 char* const
。这是因为 const
给予了整个指针本身以常量性,也就是形成了常量指针。简单来说,记住当 const
和 typedef
一起出现时,typedef
不会是简单的字符串替换就行。
typedef
在语法上是一个 存储类关键字,虽然它并不真正影响对象的存储特性,但变量只能被一种储类的关键字修饰。
1 | typedef static int SINT_t; |
上述代码中,有两个存储类关键字,会编译将失败,报错 error: multiple storage classes in declaration specifiers
1 | typedef int INT_t; |
这种写法是合法的。
其它存储类关键字有
auto
、extern
、mutable
、static
、register
等。
示例 1:通常讲,typedef
要比 define
要好,特别是在有指针的场合。
1 |
|
在上述的变量定义中,s1
、s2
、s3
都被定义为 char *
,而s4
则定义成了 char
,不是我们所预期的指针变量,根本原因就在于define
只是简单的字符串替换而 typedef
则是为一个类型起新名字。
示例 2:当 const
和typedef
一起出现时,typedef
不会是简单的字符串替换。
1 | typedef char* pStr; |
示例中,
const char *p1
是限定数据类型为 char *
的指针变量可变,指针变量指向的对象不可变,所以 p1++
正确;p2++
出错了,这个问题再一次提醒我们:typedef
和 define
不同,它不是简单的文本替换;const pStr p2
并不等于 const char *p2
,const pStr p2
和const long x
本质上没有区别,都是对变量进行只读限制,只不过此处变量 p2
的数据类型是我们自己定义的,而不是系统固有类型。因此,const pStr p2
的含义是:限定数据类型为 char *
的指针变量 p2
为只读,因此 p2++
错误。人们常常使用 typedef
来编写更美观和可读的代码。所谓美观,意指 typedef
能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性以及未来的可维护性。在编程中使用 typedef
目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
typedef
使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 typedef
关键字右边。例如:
1 | typedef int size; |
此声明定义了一个 int
的同义字,名字为 size
。注意 typedef
并不创建新的类型,它仅仅为现有类型添加一个同义字。因此,你可以在任何需要 int
的上下文中使用 size
:
1 | void measure(size *pSize); |
typedef
还可以掩饰复合类型,如指针和数组。例如,你不用像下面这样重复定义有 81 个字符元素的数组:
1 | char line[81]; |
定义一个 typedef
,每当要用到相同类型和大小的数组时,可以这样:
1 | typedef char Line[81]; |
同样,也可以像下面这样隐藏指针语法:
1 | typedef const char *pstr_t; |
记住,不管什么时候,只要为指针声明 typedef
,都要在最终的 typedef
名称中加一个 const
,比如:
1 | typedef const char *cpstr_t; // 指针常量, 使得该指针可变, 而指针指向的对象不可变(只读) |
上面讨论的 typedef
行为有点像 define
宏,用其实际类型替代同义字。不同点是 typedef
在编译时被解释,因此可以让编译器来应付超越预处理器能力的文本替换。例如:
1 | typedef int (*PF) (const char *, const char *); |
这个声明引入了 PF
类型作为函数指针的同义字,该函数有两个 const char *
类型的参数以及一个 int
类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef
是不可或缺的:
1 | PF Register(PF pf); |
Register()
的参数是一个 PF
类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。下面我展示一下如果不用 typedef
,我们是如何实现这个声明的:
1 | int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *); |
很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef
不是一种特权,而是一种必需。
持怀疑态度的人可能会问:“OK,还会有人写这样的代码吗?”,快速浏览一下揭示 signal()
函数的头文件 <csinal>
,有一个同样接口的函数:
1 | // signal 原型 |