typedef那回事儿
typedef是一种特殊的声明方式,不过它与普通声明(详见这里)的含义取大不相同。普通声明的主角是“变量”,它或是创建一个新变量或是对外文件变量使用前的声明;而typedef声明的主角则是“类型”,通过这个声明对一种数据类型引入新的名字。从引入新名字这个角度来说,typedef声明又和宏定义有些相似:用新名字代替已有的名字。不过,在本文接下来的叙述中你会看到这两者之间的区别。
typedef是特殊的
正如本文一开始所说的那样,typedef是特殊的声明。我们最常见以及常用的方式如下:
/* 代码段1 */
struct stuinfo
{
char id[20];
char name[20];
int age;
};
typedef struct stuinfo stu; /* 语句1 */
通过typedef声明为stuinfo结构体引入了一个新的名字stu。现在stuinfo结构和stu属于同一种数据类型,只不过两者在声明一个变量时使用的名字不同:
/* 代码段2 */
stu mystu1;
struct stuinfo mystu2; /* 语句2 */
通过上述两个代码段,可以再一次的理解typedef声明和普通声明的区别。代码段1通过typedef声明为stuinfo引入了一个新的名字stu;而代码段2则通过同一种数据类型的不同名称分别声明了两个同类型的变量。注意到语句1和语句2,除了语句1在声明前多了typedef关键字外,两者在形式上几乎一样,因此都可以通过上文所述的声明规则进行阅读。正是由于typedef这个关键字,使得这两种声明的含义有着巨大差异。
其实,像上面的举例那样通过typedef声明而省去一个struct并没有多大的意义。使用typedef声明的最大优点是可以简洁的表达一个指针。比如ANSI C中的signal(),它的定义如下:
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 int
unsigned 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语言中的名字空间
在说明名字空间之前,请先阅读下面的代码:
/*
*Author: edsionte
*Email: edsionte@gmail.com
*Time: 2011/02/03
*/
#include < stdio.h >
#include < string.h >
struct id
{
int id;
}id;
typedef struct name
{
char name[20];
}name;
struct name name1;
name name2;
int main()
{
id.id = 1;
strcpy(name1.name,"hello,");
strcpy(name2.name,"edsionte!");
printf("id.id = %d, %s%s\n",id.id,name1.name,name2.name);
return 0;
}
/* 运行结果 */
edsionte@edsionte-laptop:~/code/expertC$ gcc tpdef.c -o tpdef
edsionte@edsionte-laptop:~/code/expertC$ ./tpdef
id.id = 1, hello,edsionte!
你可能已经发现在上述代码中出现了多个id和name,并且这样的代码可以成功的编译。这些相同的名字标签为何可以同时出现?每个标签代表什么含义?这些问题将是下面分析的重点。
以上述代码中10到13行的代码为例,这条语句中包含三个id标签。它们分别对应C语言中三种常见的名字空间:
- 结构标签:这种标签用于结构体、联合体和枚举类型;struct后的id即为此类型的名字空间;
- 成员名:每个结构体或联合体内部都与属于自己的名字空间;struct内部的成员id即为此类型;
- 标签名:声明中的标示符;比如最后一个id,他是struct id类型的变量;
由于这三种标签所处的名字空间不同,因此它们可以同时存在。但是在同一个名字空间中不能出现多个同名的标签。常见的例子就是一个结构体内不可能出现同名的变量。
根据上面对名字空间的划分,15到18行的代码的解释为:struct后的name属于结构体标签;结构体内部的name属于成员名;而最后一个name属于声明的标示符;整条语句的含义是通过typedef声明将name结构体重命名为name。
通过上面对typedef的分析,你应该对于struct name和name均可以声明一个变量不再陌生。此处我们用名字空间的来理解他们的区别,20句中的name属于结构标签,21句中的name属于一种类型的名称。
上述同名的情况在日常的代码中实属罕见,这里只是为了说明名字空间而特别的举例。一般为了提高代码的可阅读性,最好对容易产生混淆的标签加上特别标记。比如VFS中inode和dentry结构体,两者内部均有flag一字段。尽管不同的结构体内有各自的名字空间,但是实际命名时仍然采用i_flag和d_flag。
有了上面的基础,本部分一开始所举例的代码也就可以轻松阅读了。
参考:
《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;
typedef那回事儿的更多相关文章
- 【转】ArrayList其实就那么一回事儿之源码浅析
转自:http://www.cnblogs.com/dongying/p/4013271.html?utm_source=tuicool&utm_medium=referral ArrayLi ...
- HashSet其实就那么一回事儿之源码浅析
上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap, 本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...
- HashMap其实就那么一回事儿之源码浅析
上篇文章<LinkedList其实就那么一回事儿之源码分析>介绍了LinkedList, 本次将为大家介绍HashMap. 在介绍HashMap之前,为了方便更清楚地理解源码,先大致说说H ...
- ArrayList其实就那么一回事儿之源码浅析
ArrayList 算是常用的集合之一了,不知作为javaner的你有没在百忙之中抽出一点时间看看ArrayList的源码呢. 如果看了,你会觉得其实ArrayList其实就那么一回事儿,对吧,下面就 ...
- LinkedList其实就那么一回事儿之源码分析
上篇文章<ArrayList其实就那么一回儿事儿之源码分析>,给大家谈了ArrayList, 那么本次,就给大家一起看看同为List 家族的LinkedList. 下面就直接看源码吧: p ...
- Cinnamon桌面是怎么回事儿
(linux mint 18.2 用户截图) Cinnamon的由来 在GNOME 3之前,GNOME是根据传统的桌面比拟(Desktop metaphor)而设计,到了GNOME 3便被GNOME ...
- Java的并发编程中的多线程问题到底是怎么回事儿?
在我之前的一篇<再有人问你Java内存模型是什么,就把这篇文章发给他.>文章中,介绍了Java内存模型,通过这篇文章,大家应该都知道了Java内存模型的概念以及作用,这篇文章中谈到,在Ja ...
- 懂了!VMware/KVM/Docker原来是这么回事儿
云计算时代,计算资源如同小马哥当年所言,已经成为了互联网上的水和电. 虚拟主机.web服务器.数据库.对象存储等等各种服务我们都可以通过各种各样的云平台来完成. 而在云计算欣欣向荣的背后,有一个重要的 ...
- python/numpy/tensorflow中,对矩阵行列操作,下标是怎么回事儿?
Python中的list/tuple,numpy中的ndarrray与tensorflow中的tensor. 用python中list/tuple理解,仅仅是从内存角度理解一个序列数据,而非数学中标量 ...
随机推荐
- Divide Two Integers leetcode java
题目: Divide two integers without using multiplication, division and mod operator. 题解: 这道题我自己没想出来...乘除 ...
- Git 忽略规则 .gitignore文件 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- cocos2dx游戏存储举例及其注意事项
今天白白跟大家分享一下cocos2dx中游戏的存储及需要注意的事项 cocos2dx中自带了存储类:CCUserDefault ,倘若需要存储的数据量教大的话,建议使用数据库来存储 现在先给大家看一下 ...
- 如何在Ubuntu 16.04中创建GIF动图
导读 FFmpeg 是一款开源的音.视转换器,使用 FFmpeg 我们可以非常容易地转换和录制音视频文件,而 ImageMagick 是一款用于创建.编辑和合并位图图像的一款开源软件. 大家经常在新浪 ...
- jQuery cssHook的经典例子
/*--------------------------- example ------------------------------*/ $.cssHooks.foo = { get: fun ...
- jacoco统计自动化测试代码覆盖率
一.下载jacoco, ant工具 jacoco: http://www.eclemma.org/jacoco/: ant: http://ant.apache.org 并添加到环境变量: 二.不停j ...
- PCL系列——怎样逐渐地配准一对点云
欢迎訪问 博客新址 PCL系列 PCL系列--读入PCD格式文件操作 PCL系列--将点云数据写入PCD格式文件 PCL系列--拼接两个点云 PCL系列--从深度图像(RangeImage)中提取NA ...
- Eclipse.ini參数设置(Maven Integration for Eclipse JDK Warning)
安装EclipseMaven插件后,Eclipse启动问题:Maven Integration for Eclipse JDK Warning. 解决方法: 1. 设置Eclipse使用的JRE为本 ...
- Lisp分配给保护的符号…
在进行调试时,弹出"分配给保护的符号:pf,是否进入中断循环",但似乎不会影响结果. 明经版主解译说是:给受保护的符号重新赋值了,所以有此提示,此提示仅在打开了vlide 编辑器才 ...
- 使用mysql 统计函数 结果为null时返回值改为0
SELECT COALESCE(SUM(total),0) FROM test_table