5.12复杂声明(下)

这节分了两部分,前面主要是别人总结的经验,这章就是书上的内容.
话说这个复杂声明简直日了狗了了.多么强大的逻辑思维才能捋明白,其实我很想跳过这节.
这里先写一下优先级顺序
离名字越近的修饰符最先解读.
()优先级最高.
[]优先级高于*.
*优先级最低.
前面的介绍部分不需要多说.还是看具体的内容.
那几行例子.
首先就是char **argv
这个应该算比较熟悉的了.之前也说到过,读作指向指针的指针.多级指针.第一个*指向的是一个另一个指针(*argv)所在的位置.
然后是int (*daytab)[13]
看过上一部分的应该知道,这种复杂声明都是按照运算符的优先级来排的,搞明白优先级对于了解复杂声明会好很多.(原文下面也有英文注释.)
首先解读离名字近的部分,daytab外面是()左边是,说明它是一个指针(这里是因为()的优先级比高,所以先解读()证明daytab是个整体,而这个整体是一个指针),然后跳出这个括号.右边是具有13个元素的数组[13],刚才那个指针(daytab)指向的就是是这个数组,最后右边的int修饰的是这个数组的元素(说明数组元素为int类型).
SO结合起来就是指针daytab指向一个具有13个int类型元素的数组.
这里其实不应该这么写读法,但是这么一步步的分解后就知道这个声明的意思了.知道后就可以用自己的语言进行解读.
int *daytab[13]:
首先[]的优先级最高,所以首先解读daytab为一个具有13个元素的数组.然后左边是*,这个*是用来修饰daytab这个具有13个元素的数组用的.所以综上所述daytab数组的内容是指针,最后int说明这个数组的指针指向的是int类型.
SO结合起来就是daytab是一个具有13个int类型指针的数组.
解析:daytab是一个有13个元素的数组,它的元素是int类型的指针.
void *comp()
首先()的优先级最高,这样就说明comp是一个函数,(之后的部分有点绕,这里我感觉最好的写法是void* comp()这样的),既然是函数都需要表明是否需要返回值,这个就是返回值,表明返回值是一个指针,void修饰这个指针,表明返回值这个指针是void类型.
SO解读为comp是一个没有参数的函数,返回值为void类型的指针.(这里到底是没有返回值还是返回值的类型是void类型的指针我也有点懵)
解析:comp是一个没喊参数的函数,它返回一个值,这个值是void类型的指针.
void (*comp)()
首先看离comp最近的部分,comp外面是一个括号,说明为
comp一个整体,comp左边是所以(*comp)是一个指针,跳出括号往下看,右边是一个(),说明这个指针指向的是一个函数.void最后修饰这个函数,所以表明这个函数没有返回值.
SO解读为.comp是一个指向没有返回值的函数的指针.
char (*(*x())[])():
这个难度跟之前不是一个数量级啊.
首先从名字附近开始解读,x附近()优先级最高,那么首先x是一个没有参数的函数,然后左边是*,说明这个函数的返回值为指针.跳出括号,右边[]优先级最高,说明返回值是一个指向数组的指针,然后左边还是一个*,这个*修饰这个数组,那么说明,这个数组的元素是指针.跳出这个函数,右边又是一个括号,那么说明这个数组的指针指向的是函数,最后char修饰这个函数,说明返回值为char类型
SOx是一个没有参数的函数返回一个指向返回值为char类型函数的指针数组的指针.(这个光看这个非常难理解,还是需要上面的推理一步一步来看.)
解析:x是一个没有参数的函数,它返回个指针,这个指针指向一个一维数组,这个一维数组的元素是指针,这些指针分别指向各个函数.这些函数的返回值为char类型.
char (*(*x[3])())[5]
这种复杂声明如果不是天赋异禀,还是一步步推理容易一些.
首先看名字旁边的修饰符.外面一层括号,说明
x[3]是一个整体,[]的优先级在这里最高,所以最先解读.x是有三个元素的数组.然后*修饰这些数组元素,表明这些元素是指针,所以(*x[3])是具有三个元素的指针数组,现在跳出括号,右边一个()左边一个*,()优先级比*高,说明之前的指针数组指向的是函数,而左边的*表明这些函数的返回值是指针,再跳出括号右边一个[5]那么之前的返回的指针指向的就是这个具有5个元素的数组,最后char修饰这些数组,表明返回的指针指向的是具有5个char类型元素的数组.
SO总结一下就是x是具有三个指向返回值为指向五个char类型数组的函数的指针的元素的数组.
解析:x是一个有三个元素的数组,这些元素是指针,这些指针分别指向多个函数,这些函数的返回值为指针,这些指针分别指向5个char类型数组.
这种复杂声明在炫技的时候还是挺屌的,虽然没什么卵用.
下面那一部分需要提一点的就是dcl和dir-dcl具体是什么东西.
首先dir-dcl就是直接可以解释的东西,不用通过他去指向别的地方.
而dcl基本都是带有*,意思就是间接引用,需要通过这个指针指向其他的东西,去引申到其他东西.
这就是两者的区别.

下面的那个例子都需要用到gettoken函数.
这个程序声明很多外部变量.需要留意一下.
那么就先看gettoken函数的作用.
getch和ungetch就是通过缓冲区防止误读入,但是我没看到缓冲区的声明.应该是略过了直接用的.
其实也没什么难的.
首先就是跳过前面没用的空格和制表符.
然后就是进行判断输入的声明和判断后的各种相应操作.
判断是否为'('
如果是'('那么判断下一个输入是否是')',
如果是则执行下列操作.
将'()'拷贝到数组token中,
并且向调用函数返回PARENS.并用tokentype进行记录表明读入的是一对括号.
如果不是则将读取得多余的那个字符放回缓冲区.
并且用tokentype记录后向调用函数返回当前的字符'('.
然后是遇到'['的情况.
(这里有个问题,就是*p这个指针指向的是token这个数组.而对p操作与对token操作是等价的,但是为什么要用*p.这里我感觉应该是因为token声明为一个数组而不是指针,数组名虽然可以看过是一个地址但是不可以自增,而指针可以.引用数组从而进行元素的自增的话会增加额外的开销.所以这里用的是指针p.只是个人理解.)
通过getch读取输入直到']',将读取的内容保存到指针*p所指向的token数组中.
然后在末尾添上空白符'\0'.表明读入结束.
然后向调用函数返回读入的内容是BARCKETS.
(这个程序本身就有问题,所以一些细节不用太深究,比如下面这个判断字母的,它并不能判断各种类型的声明).
然后判断遇到字母的情况.
依然是将读入的字符c保存到*p所指向的token数组中.判断式的终止条件是,读入的字符c不是数字或字母.
遇到非字母和数字后,再最后加上空白符'\0'表示结束读入.
将多读入的字符放回到缓冲区.
然后将读入的类型保存在tokentype中后向调用函数返回读入的是NAME的信息
最后是以上都不满足的情况下.将其保存在tokentype中并将读入的字符直接返回给调用函数
没什么难度,回到主函数.
首先就是用gettoken函数的返回值作为判断是否终止循环的依据.
将token的内容复制到datatype中.(这里的token数组一直都是最后读取的那一串字符,因为每次用指针*p对token数组进行写入之后,都会跳出函数.而下次调用,指针*p依然是初始化为token数组开头的地址.所以每次都是对token进行覆盖写入.)
之后这个out[0] = '\0'.
然后是函数dcl,对后续的声明进行分析.

转到dcl部分.
这里应该重点讲一下,后面也要用到.
这里用个栗子说明一下能更直观的理解这两个函数.
用书上那个栗子就不错.char (*(*x())[])().
dcl和dirdcl都是从左向右检查每个字符的,因为C的声明是嵌套的,所以需要递归调用很多次dcl和dirdcl来一层一层的进行分析.所以看起来比较麻烦.

首先类型名通过主函数最外层的while调用的gettoken,赋值到datatype数组中.
gettoken通过下列程序块进行处理,将类型名保存到数组token的位置.并返回NAME.
while判断返回值不是EOF,进入循环体.
第一条语句将类型名保存到datatype中.然后向下走,调用dcl分析之后的部分.

每次检查都是先通过dcl进行分析的.
而函数dcl和dirdcl都是通过gettoken的返回值去检查和分析的,

dcl首先通过gettoken的返回值检查'',通过循环检查有几个'*',然后遇到'*'号则计数,如果遇到不是'*'的则跳过(这里的检查是每个字符依次检查的,如果前一个是'*'后一个不是,会马上终止循环.).这里开头不会遇到'*',但是这个for循环的gettoken函数依然是读取了一个输入的修饰符的,读取的修饰符保存在tokentype中,完成这样的操作后程序才继续向下走. (dcl = 1)
char (
(x())[])()
然后是调用dirdcl.转到dirdcl. (dirdcl = 1)
首先判断当前的修饰符(tokentype)是什么(之前主函数已经将char类型记录过了,所以这里直接就开始分析之后的部分了.),刚才提到了tokentype保存的是'(',
那么调用dcl分析之后的部分.(dcl = 2)
然后gettoken函数再次读取一个修饰符,这次读取的事一个'
',而gettoken会把这个'*'直接作为返回值返回给调用函数.所以这里循环体会被执行一次,ns++计数变量会自增1.
循环一次后,判断式会再次求值,gettoken函数会再次读取一个输入的修饰符,这次是'(',
那么将tokentype变量的内容变为'(',然后终止循环,
调用dirdcl分析之后的部分.(dirdcl = 2)
dirdcl判断tokentype为'(',
再次调用dcl函数分析后续.(dcl = 3)

gettoken函数再次读取一个修饰符,这次是'',那么执行循环体,ns自增1(这里的ns自增后还是1,因为每次调用ns都是会初始化为0的.),
判断式再次求值,gettoken函数再次读取修饰符.这次是x这个字符.
gettoken将其保存在数组token,将tokentype改成NAME的值,并向调用函数返回NAME的值,表明读入了一个名称.返回的值不是'
',终止循环,
程序向下走调用dirdcl函数. (dirdcl = 3)

前面的判断式不会执行,这时的判断式tokentype == NAME,会判断为真,执行后面的语句.
将保存在token的名字,转移到name中(token需要重复利用).

然后会执行while循环体,这个循环体会通过gettoken函数检测名字之后的部分是什么,如果之后跟的是PARENS或者BRACKETS.
会将相应的字符串放入out中,这个栗子返回的是PARENS,一对括号,执行循环体后不要忘了gettoken依然会再次读取下一个修饰符,读取到的是右括号,将其保存在tokentype中.然后结束循环.
开始一层层的退出递归.这时的dirdcl执行完毕, (dirdcl = 2)
返回dcl函数执行后面的部分.
刚才的计数变量ns自增为1.那么执行循环体将字符串放入out中,这时dcl执行完毕,返回调用dcl函数的dirdcl函数中.(dcl = 2)
判断tokentype是否是')',刚才已经被gettoken函数赋值为')',所以判断为假,不执行之后的错误报告.后面的多路判定都为假,不执行.
while判断式再次通过gettoken函数读取修饰符.这次读入的是一对方括号'[]',而这个方括号中间没有任何内容,所以token数组中也只有一对方括号,这样就将那几个字符串一次放入out中,然后gettoken回再次读取下一个修饰符')'并放入tokentype中,
判断式为假函数结束,返回dcl.(dirdcl = 1)

ns最开始自增了1.所以这次依然是将该字符串放入out中.
dcl函数结束.返回dirdcl.(dcl = 1.必须记一下调用和dcl和dirdcl各几次.不然根本弄不清.)

判断式tokentype为假,不执行错误报告.程序向下走.多路判定全部为假到while循环体.
再次读取下一个修饰符,是一对圆括号(),这样将相应的字符串放入out中.并再次读取下一字符,应该是换行符'\n',保存到tokentype 中,函数结束,回到dcl.(dirdcl = 0)

dcl没什么可执行的(dcl = 0).
返回主函数
主函数判断最后是否是换行符(因为是按最标准的格式判断,所以声明最后不会有别的东西.)不是则输出错误报告.
然后用相应的格式输出内容.

这一部分只能自己去理解,完全是逻辑上的东西,而且还有很多细节的部分,比如while循环体执行一次后gettoken会再次读取输出.这个不要忘了.

之后就是这个反向的翻译了.
看这个主函数就知道这个undcl函数没什么难度,就是判断条件执行相应操作.主要是要把之前的那个gettoken函数弄明白这里就简单多了.(其实gettoken也没啥难度.)
主旨是为了说明问题,也确实没什么必要深究.
先看这个函数.
用gettoken这个函数的返回值为依据去进行这个循环体.
一般情况第一个遇到的一串字符都是这个声明的名字,所以直接用单独的一个strcpy函数将其赋值到out中(应该是这样,要不然也没法解释).
然后又通过一个循环对gettoken返回的值进行分析.
通过返回值的类型,对数组out写入相应的修饰符.
如果是括号或方括号就直接将其写入out数组.
如果是'*'那么将其按sprintgf所指定的格式写入缓冲区temp,然后将temp的内容写入out中.(sprintf的功能是将第三个参数的内容按第二个参数的格式写入第一个参数的缓冲区中,说起来好像很麻烦,但理解也没什么困难,)
然后将缓冲区的内容通过strcpy写入到out中.
情况都不符合输出错误报告.
最后输出字符串.
结束函数.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,729评论 0 38
  • 这章的复杂声明我并没有想去深入的研究,暂时,只要能看懂,稍微用一些有用的部分即可.因为这个复杂声明实在是复杂的过分...
    Hy_Slin阅读 207评论 0 0
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • 今天是个好日子,9月9日,寓意长长久久,很多新人选择这一天登记领证,很多新郎新娘在这一天摆喜宴,很多男女在这一天告...
    三十二月阅读 208评论 0 0
  • “创业”很多人都觉得不陌生,或许正在创业的路上!但真的理解创业吗?如何是创业呢?我的理解是:创业就是对于自己喜欢的...
    陌离TZM阅读 148评论 0 0