int main (void){PCHAR1 pch1, pch2;PCHAR2 pch3, pch4;printf ("sizeof pch1: %dn", sizeof(pch1));printf ("sizeof pch2: %dn", sizeof(pch2));printf ("sizeof pch3: %dn", sizeof(pch3));printf ("sizeof pch4: %dn", sizeof(pch4));return 0;}
在上面的示例代码中,我们想定义4个指向char类型的指针变量,然而运行结果却是:
sizeof pch1: 4sizeof pch2: 4sizeof pch3: 4sizeof pch4: 1
本来我们想定义4个指向char类型的指针,但是 pch4 经过预处理宏展开后,就变成成了一个字符型变量,而不是一个指针变量。而 PCHAR1 作为一种数据类型,在语法上其实就等价于相同类型的类型说明符关键字,因此可以在一行代码中同时定义多个变量。上面的代码其实就等价于:
char *pch1, *pch2;char *pch3, pch4;
2.4 让复杂的指针声明更加简洁
一些复杂的指针声明,如:函数指针、数组指针、指针数组的声明,往往很复杂,可读性差。比如下面函数指针数组的定义:
int *(*array[10])(int *p, int len, char name[]);
上面的指针数组定义,很多人一瞅估计就懵逼了。我们可以使用typedef优化一下:先声明一个函数指针类型func_ptr_t,接着再定义一个数组,就会更加清晰简洁,可读性就增加了不少:
typedef int *(*func_ptr_t)(int *p, int len, char name[]);func_ptr_t array[10];
3. 使用typedef需要注意的地方
通过上面的示例代码,我们可以看到,使用typedef可以让我们的代码更加简洁、可读性更强一些。但是typedef也有很多坑,稍微不注意就可能翻车。下面分享一些使用typedef需要注意的一些细节。
3.1 typedef在语法上等价于关键字
我们使用typedef给已知的类型声明一个别名,其在语法上其实就等价于该类型的类型说明符关键字,而不是像宏一样,仅仅是简单的字符串替换。举一个例子大家就明白了,比如const和类型的混合使用:当const和常见的类型(如:int、char) 一同修饰一个变量时,const和类型的位置可以互换。但是如果类型为指针,则const和指针类型不能互换,否则其修饰的变量类型就发生了变化,如常见的指针常量和常量指针:
char b = 10;char c = 20;int main (void){char const *p1 = &b; //常量指针:*p1不可变,p1可变char *const p2 = &b; //指针常量:*p2可变,p2不可变p1 = &c; //编译正常*p1 = 20; //error: assignment of read-only locationp2 = &c; //error: assignment of read-only variable`p2'*p2 = 20; //编译正常return 0;}
当typedef 和 const一起去修饰一个指针类型时,与宏定义的指针类型进行比较:
typedef char* PCHAR2;#define PCHAR1 char *char b = 10;char c = 20;int main (void){const PCHAR1 p1 = &b;const PCHAR2 p2 = &b;p1 = &c; //编译正常*p1 = 20; //error: assignment of read-only locationp2 = &c; //error: assignment of read-only variable`p2'*p2 = 20; //编译正常return 0;}
运行程序,你会发现跟上面的示例代码遇到相同的编译错误,原因在于宏展开仅仅是简单的字符串替换:
const PCHAR1 p1 = &b; //宏展开后是一个常量指针const char * p1 = &b; //其中const与类型char的位置可以互换
而在使用PCHAR2定义的变量p2中,PCHAR2作为一个类型,位置可与const互换,const修饰的是指针变量p2的值,p2的值不能改变,是一个指针常量,但是*p2的值可以改变。
const PCHAR2 p2 = &b; //PCHAR2此时作为一个类型,与const可互换位置PCHAR2 const p2 = &b; //该语句等价于上条语句char * const p2 = &b; //const和PCHAR2一同修饰变量p2,const修饰的是p2!
3.2 typedef是一个存储类关键字
没想到吧,typedef在语法上是一个存储类关键字!跟常见的存储类关键字(如:auto、register、static、extern)一样,在修饰一个变量时,不能同时使用一个以上的存储类关键字,否则编译会报错:
typedefstaticchar* PCHAR;//error: multiple storage classes in declaration of `PCHAR'
3.3 typedef 的作用域
跟宏的全局性相比,typedef作为一个存储类关键字,是有作用域的。使用typedef声明的类型跟普通变量一样遵循作用域规则:包括代码块作用域、文件作用域等。
typedef char CHAR;void func (void){#define PI 3.14typedef short CHAR;printf("sizeof CHAR in func: %dn",sizeof(CHAR));}
int main (void){printf("sizeof CHAR in main: %dn",sizeof(CHAR));func;typedef int CHAR;printf("sizeof CHAR in main: %dn",sizeof(CHAR));printf("PI:%fn", PI);return 0;}
宏定义在预处理阶段就已经替换完毕,是全局性的,只要保证引用它的地方在定义之后就可以了。而使用typedef声明的类型则跟普通变量一样遵循作用域规则。上面代码的运行结果为:
sizeof CHAR in main: 1sizeof CHAR in func: 2sizeof CHAR in main: 4PI:3.140000
4 如何避免typedef的滥用?
通过上面的学习我们可以看到:使用typedef可以让我们的代码更加简洁、可读性更好。在实际的编程中,越来越多的人也开始尝试使用typedef,甚至到了“过犹不及”的滥用地步:但凡遇到结构体、联合、枚举都要用个typedef封装一下,不用就显得你low、你菜、你的代码没水平。
其实typedef也有副作用,不一定非得处处都用它。比如上面我们封装的STUDENT类型,当你定义一个变量时:
STUDENT stu;
不看STUDENT的声明,你知道stu的含义吗?未必吧。而如果我们直接使用struct定义一个变量,则会更加清晰,让你一下子就知道stu是个结构体类型的变量:
struct student stu;
一般来讲,当遇到以下情形时,使用typedef可能会有用,否则可能会适得其反:
创建一个新的数据类型
跨平台、指定长度的类型:如U32/U16/U8
跟操作系统、BSP、网络字宽相关的数据类型:如size_t、pid_t等
不透明的数据类型:需要隐藏结构体细节,只能通过函数接口访问的数据类型
在阅读Linux内核源码过程中,你会发现大量使用了typedef,哪怕是简单的int、long都使用了typedef。这是因为:Linux内核源码发展到今天,已经支持了太多的平台和CPU架构,为了保证数据的跨平台性和可移植性,所以很多时候不得已使用了typedef,对一些数据指定固定长度:如U8/U16/U32等。但是内核也不是到处到滥用,什么时候该用,什么不该用,也是有一定的规则要遵循的,具体大家可以看kernel Document中的 CodingStyle 中关于typedef的使用建议。(在此感谢“裸机思维”的推荐)
免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。返回搜狐,查看更多