博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言--typedef
阅读量:6865 次
发布时间:2019-06-26

本文共 3265 字,大约阅读时间需要 10 分钟。

hot3.png

typedef是一种特殊的声明方式,不过它与普通声明 的含义大不相同。普通声明的主角是“变量”,它或是创建一个新变量或是对外文件变量使用前的声明;而typedef声明的主角则是“类型”,通过这个声明对一种数据类型引入新的名字。从引入新名字这个角度来说,typedef声明又和宏定义有些相似:用新名字代替已有的名字。接下来的叙述会看到这两者之间的区别。

typedef是特殊的

  正如一开始所说的那样,typedef是特殊的声明。最常见以及常用的方式如下:

复制代码

1 /* 代码段1 */

2 struct stuinfo

3 {

4 char id[20];

5 char name[20];

6 int age;

7 };

8 typedef struct stuinfo stu; /* 语句1 */

复制代码

  通过typedef声明为stuinfo结构体引入了一个新的名字stu。现在stuinfo结构和stu属于同一种数据类型,只不过两者在声明一个变量时使用的名字不同:

1 /* 代码段2 */

2 stu mystu1;

3 struct stuinfo mystu2; /* 语句2 */

  通过上述两个代码段,可以再一次的理解typedef声明和普通声明的区别。代码段1通过typedef声明为stuinfo引入了一个新的名字stu;而代码段2则通过同一种数据类型的不同名称分别声明了两个同类型的变量。注意到语句1和语句2,除了语句1在声明前多了typedef关键字外,两者在形式上几乎一样,因此都可以通过上文所述的声明规则进行阅读。正是由于typedef这个关键字,使得这两种声明的含义有着巨大差异。

  像上面的举例那样通过typedef声明而省去一个struct并没有多大的意义。使用typedef声明的最大优点是可以简洁的表达一个指针。比如ANSI C中的signal(),它的定义如下:

1 void ( *signal(int signum, void (*handler)(int)) ) (int);

  考验你的时刻到了!你是否能快速说出这个声明的含义?

  这个复杂的语句声明了signal函数,这个函数有两个参数signum和handler;signum参数是一个整型变量。handler是一个函数指针,指向一个拥有整型参数并且返回空值的函数;signal函数的返回值是一个函数指针,该指针同样指向一个拥有整型参数并且返回空值的函数。

  对于这个复杂声明的解读的确很令人费劲。但是经过typedef的改进,它的阅读过程就简化了很多:

typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

  通过typedef的声明,使得sighandler_t是这样一种类型:它是一个函数指针,该函数拥有一个整型参数并且返回空值。第二条语句则声明了函数signal,它拥有两个参数signum和handler。并且这个函数的返回值和参数handler都是sighandler_t类型的。

  虽然这样的声明在形式上简洁许多,不过和普通声明一样,此时阅读声明时仍然要记住声明符号的优先级规则。这种困扰在C语言中是难以避免的。

typedef int x和#define x int是不一样的

  typedef和宏定义看似都是文本替换,但其实质不同。typedef表面上是对已有数据类型引入新名称,实则是对数据类型的严格封装。这种封装体现在下述两个方面。

  首先,经过宏定义后的类型名可以进行再次扩展,但是经过typedef引入的类型名则不能进行扩充。比如:

#define myint1 intunsigned myint1 x; /* 正确 */ typedef int myint2;unsigned myint2 x; /* 语法错误! */

  由于typedef是一种严格的数据封装,它只引入了myint2类型而没有引入unsigned myint2类型。也就是说,通过typedef的声明,编译器只能识别myint2类型。而unsigned myint2既不是基本类型也不是经过typedef声明过的类型,编译器就无法识别。

  其次,在连续的几个变量声明中,使用typedef定义的类型能够保证所有变量均为相同类型,而用宏定义的变量则无法保证统一性。比如:

#define myint1 int *myint1 x,y; /* 经过宏替换后为: int *x,y; */ typedef int * myint2;myint2 x,y;

  由于宏定义只是直接的文本替换,因此只能保证x是整型的指针变量而y为整型变量。而typedef定义过的类型myint2则是对int *的完全封装,所以x和y均为整型的指针变量。

C语言中的名字空间

  在说明名字空间之前,先阅读下面的代码:

复制代码

1 #include < stdio.h >

2 #include < string.h >

3

4 struct id

5 {

6 int id;

7 }id;

8 

9 typedef struct name

10 {

11 char name[20];

12 }name;

13

14 struct name name1;

15 name name2;

16

17 int main()

18 {

19 id.id = 1;

20 strcpy(name1.name,"hello,");

21 strcpy(name2.name,"edsionte!");

22 printf("id.id = %d, %s%s\n",id.id,name1.name,name2.name);

23 return 0;

24 }

25

26 /* 运行结果 */

27 id.id = 1, hello,edsionte!

复制代码

  你可能已经发现在上述代码中出现了多个id和name,并且这样的代码可以成功的编译。这些相同的名字标签为何可以同时出现?每个标签代表什么含义?这些问题将是下面的重点。 

  以上述代码中4到7行的代码为例,这条语句中包含三个id标签。它们分别对应C语言中三种常见的名字空间:

  • 结构标签:这种标签用于结构体、联合体和枚举类型;struct后的id即为此类型的名字空间;
  • 成员名:每个结构体或联合体内部都与属于自己的名字空间;struct内部的成员id即为此类型;
  • 标签名:声明中的标示符;比如最后一个id,他是struct id类型的变量;

  由于这三种标签所处的名字空间不同,因此它们可以同时存在。但是在同一个名字空间中不能出现多个同名的标签。常见的例子就是一个结构体内不可能出现同名的变量。

  根据上面对名字空间的划分,9到12行的代码的解释为:struct后的name属于结构体标签;结构体内部的name属于成员名;而最后一个name属于声明的标示符;整条语句的含义是通过typedef声明将name结构体重命名为name。

  通过上面对typedef的分析,你应该对于struct name和name均可以声明一个变量不再陌生。此处我们用名字空间的来理解他们的区别,14句中的name属于结构标签,15句中的name属于一种类型的名称。

  上述同名的情况在日常的代码中实属罕见,这里只是为了说明名字空间而特别的举例。一般为了提高代码的可阅读性,最好对容易产生混淆的标签加上特别标记。比如VFS中inode和dentry结构体,两者内部均有flag一字段。尽管不同的结构体内有各自的名字空间,但是实际命名时仍然采用i_flag和d_flag。

参考:

《C专家编程》

转载于:https://my.oschina.net/u/239287/blog/69274

你可能感兴趣的文章
重构代码(应如写诗)
查看>>
Vue混入mixins
查看>>
前阿里 P9 级员工称离婚是模拟测试,已回滚复婚!
查看>>
衡阳a货翡翠,南平a货翡翠
查看>>
大姨太入场,EtcGame全线升级为Coingame,开启ETH投注倒计时……
查看>>
阿里云HBase推出全新X-Pack服务 定义HBase云服务新标准
查看>>
通过Auto Layout深入了解SizeClasses的好处和使用
查看>>
Spring scope解惑
查看>>
BCH与BCE共享比特币之名
查看>>
js脚本 处理js注入
查看>>
A potentially dangerous Request.Form value was detected from the client
查看>>
测试过程之过分关注功能性测试
查看>>
SQL Server -- LIKE模糊查询
查看>>
centos7.0 docker安装部署
查看>>
批处理中setlocal enabledelayedexpansion
查看>>
重要的与关键的
查看>>
ORA-32004错误的解决方法
查看>>
SCCM 2012系列1 服务器准备上
查看>>
PHP: chr和pack、unpack那些事
查看>>
编程十诫
查看>>