灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:3622回复:0

理解C语言刁钻定义语句的斩麻快刀:自内向外读

楼主#
更多 发布于:2012-09-06 12:32

理解C语言刁钻定义语句的斩麻快刀:自内向外读
今天在论坛见到 A_Zhao 发表于2012-06-18 06:41:16《关于C语言声明、指针、数组、函数、typedef...》引起论坛推荐和朋友们的热议。A_Zhao的文章很长,因为健康欠佳,力不从心,我大概看了一下,没怎么看懂。我感觉他的方法不容易,我知难而退了。
其实早有书籍讲解过这个问题,方法很简单,看来朋友们没看到这类的书。至于我是从哪本书看到的,可想不起来了,大概有10年之久了吧。
这个方法的基本原则是:
1. 从标识符开始读起,即从内向外读。
2. 左右都有类型符时,读的次序按照它们是运算符时的级别决定。
3. 用后面步骤读到的类型符去解释前面步骤新出现的那个类型符(陈树振原创)。
依据这个原则,任何复杂的定义语句都能迎刃而解,相信吗?咱们先从简单的练起,逐步加难。复杂的例子我准备借用A_Zhao朋友的题材,也好做个对比。
例1:char *a[3]; // A_Zhao的题目为char *a[n];,有错。
这是一个定义语句,因为类型名开头。从标识符a读起,a的左右各有一个类型符,分别是*和[ ],二者做运算符时取下标运算符[ ]高于取内容运算符*,所以先读[ ]。即:○1a是一个有3个元素的数组;○2数组的元素是指针;○3这个指针是指向char型变量的。
这里有个前提,大家要理解。在C语言里,同一个符号出现在不同点环境(context)其意义是不一样的,这和人类的自然语言是一样的。例如这里的*和[ ]就既是类型符又是运算符。
例2:char* (foo())[3];// A_Zhao用的是char* (foo())[n];,n是错的
我们从标识符出发开始读。○1先读小括号, foo是一个函数(函数就要有返值类型)○2函数返值的类型是数组(这是因为取下标运算符[ ]高于取内容运算符*)...。到这里就有错了 ,因为函数的返值是不允许是数组的。这个定义语句是错误的。
我们看到,这个定义语句去掉一个小括号,即char* foo()[3];,与原定义语句的含义保持不变。为什么?因为取下标运算符[ ]高于取内容运算符*,对吧?
   例3:char* (*(*ptr2bar)(int *))[3];// A_Zhao用的是char* (*(*ptr2bar)(int *))[n];,注意,n不行。
我们从标识符出发开始读。○1先读小括号(*ptr2bar), ptr2bar是一个指针○2再读(*ptr2bar)(int *),是一个指向带有一个int *参数的函数的指针 ○3再读(*(*ptr2bar)(int *))[3],这个函数的返值是指向3个元素的一维数组的指针○4最后读作,这3个元素的数据类型是char*。
   定义语句char* (*(*ptr2bar)(int *))[3];里的小括号一个都不能省略,否则含义就变了。
看出规律了吗?后面步骤读到的类型符去解释前面步骤新出现的那个类型符。我说这话有点难懂,不过您掌握这个窍门,多练习几次就理解我说的话了。
下面我要给大家介绍使用typedef的小窍门,这可是我自己琢磨出来的,有专利权。
大家知道,typedef的功能是给一种类型起一个别名, typedef实际上不能产生任何新的数据类型。我们在MFC类库及其应用程序框架里经常见到的LPSTR、Dword、WPARAM、LPARAM都是用typedef起的别名,原来的名称其实我们在学C语言时基本上都见过,没什么新东西。
通常对一种复杂的、组合的数据类型才会起一个新名字,这样会使得程序见名知意。但这时初学者往往分不清哪个是别名的含义。我的窍门是:
1. 先去掉看关键字typedef,那么此语句一定是一个定义语句,找出所定义的变量或数
2. 加上关键字typedef,这个变量或数组名就成为类型名了,其数据类型含义就是刚才作变量时的那个含义。
3. 用这个类型名去定义变量或数组。
例4:
我们把前面例1加上typedef 就成为:
typedef char *a[3];
现在a就是一个类型名了。用a去定义一个数组array,即:  
a array;
现在array就是一个数组,一个有3个元素的数组,每个元素是指向char型的指针。
其实typedef更常用来定义结构或联合。
例5:typedef struct {
   int numNo;
   unsigned int age;
   char sex;
   int exam[5];
   }STUDENT;
   STUDENT zhang3;
○1先去掉typedef,STUDENT是结构变量名;○2加上typedef,STUDENT就是类型名了。
下面来一个比较复杂点的,从侯捷的《深入浅出MFC》的Frame8源程序里获得。
例6:
typedef void (CCmd::*AFX_PMSG) (void);
○1先把typedef去掉;○2从标识符AFX_PMSG开始向外读,AFX_PMSG是一个指针;○3这个指针是指向CCmd类成员的指针;○4这个指针是指向无参函数的指针(当然是成员函数);○5函数的返值是void;○6加上typedef,上面的AFX_PMSG就成了一个类型,一个具有刚才作变量时属性的数据类型。
据说,从内向外读的原则是与编译器对定义语句的语义分析原则是一致的。这个原则对于理解C++出现在定义语句的修饰符也非常重要。见如下例子:
例7:对下面第二条定义语句:
    
我们从标识符开始从内向外读:①先读p,读做:“p是一个标识符”;②再读类型符*,读做:“是一个指针变量”;③再读类型符int,读做:“它指向int类型的变量”;④再读修饰符const,读做:“这个int型变量不能通过指针p去改变它的值”。
在上面的程序段里,我们可以通过var去改写上述写有整型数5的那小块内存,如var=6;;但是,不能通过指针p去改变写有整型数5的那小块内存。
例8:再如,可以如下定义一个指针p:
    
我们这样读第二个定义语句:从标识符开始读起,从内向外读,①先读p,读作:“p是一个标识符”;②再读修饰符const, 读作:“p被const” ;③再读类型符*,读作:“p是一个指针变量” ;④再读类型符int, 读作:“p指向int类型的变量”;⑤再读=号,p被初始化为;var。
   陈树振写于 2012/6/19

喜欢0 评分0
游客

返回顶部