都说西藏是一种病,对于一个c程序员来说,我觉得乱码大赛的经典之作也是一种病,不把他完全理解通透总是不自在。至少对于我来说,是这样的。前前后后这是第三次看这份代码,第一次时候刚在学校学完C语言,只是下载到运行了一把,想看懂。结果看了一会。一脸懵逼的放弃。第二次拿起他,分析了一部分,只是简单的把每个三目运算符对号入座了一下。又放弃了。怂在了他69K+次递归和2.3K+次输出。这次,再次拿起这份代码。也想检验一下自己的逻辑能力。切入正题,开始分析这令我叹为观止的递归。
首先上第一道菜,这基本是没改动的原版,只是把其中一点不符合现在编程语法这类的东西做了修改,至于原版,网上多的是。
#include <stdio.h>
main(int t,int _,char* a)
{
return !0<t?t<3?main(-79,-13,a+main(-87,1-_,
main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s %d %d\n"):9:16:t<0?t<-72?main(_,t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/")
:t<-50?_==*a?putchar(a[31]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"),a+1);
}
这副样子一眼看过去是很头疼的,我们先来简单处理一下,把其中的字符串取出来,顺便把A?B:C
语句用缩进来整理下,对于短的直接放到一行,也容易看,对于长的,我采用下面这种格式:
A? //每个字母表示一个表达式
(
B
):
(
C?
(
D
):
()
)
上代码
#include <stdio.h>
char *p1 = "%s %d %d\n";
char *p2 = "@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,\
/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/\
+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!\
/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w\
{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',\
#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/";
char *p3 = "!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry";
main(int t,int _,char* a)
{
return 1 < t ? //!0就是真值,即 1 ,这里为了不迷惑,直接写成1
(
(
(t < 3?
main(-79,-13,a+main(-87,1-_, main(-86,0,a+1)+a)):1
),
(t<_?
main(t+1,_,a):3),
main(-94,-27+t,a)
)&&
( (
(t==2?
(
_<13? main(2,_+1,p1):9
):
16
)
)
)
):
(
t<0 ?
(
t<-72?
(
main(_,t,p2)
):
(
t<-50?
(
_==*a?putchar(a[31]):main(-65,_,a+1)
):
main((*a=='/')+t,_,a+1)
)
):
(
0<t?
main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,p3),a+1)
)
);
}
现在,至少看起来容易点了,但是实际的运行,也就是递归过程,还是一脸懵逼态。
接下来开始一步步分析,看程序是怎么递归的。
可以看到main带了三个参数,这是合法的,具体什么意思自行百度。这不是我们今天的重点。第一次进入时候不需要关注第三个参数,第一二个参数值分别是 1, 1. 开始判断
- 1 < 1不成立,跳到第33行,1 < 0 不成立,跳到第48行,0 < 1 成立,调用main(2,2,“%s”)
- 1 < 2成立, 跳到16行, 2 < 3 成立, 顺次进去调用 main(-86,0,a+1)
- 1 < -86不成立,跳到33行,-86 <0成立,跳到35行,-86 < -72成立,调用main(_,t,p2),即main(0,-86,p2)
1 < 0不成立,跳到33行, 0 < 0不成立,跳到48行,0 < 0不成立,判断*a == ‘/’,*a即p2[0],不成立,所以执行 || 后面的 main(-61,*a,p3) 接下来不具体说了直接说相应的行。这次递归跳到42行 后面的main,每次调用这个main p3加1,直到_==*a 即p2[0] == *a,也就是匹配到p3中第一个和p2[0]相等的字符,就输出这个字符后第31个字符
至此,终于把第一个字符分析出来了。
这里有一个重点地方,第一个是*a ==’/’ ||根据程序偷懒规则,当前面为真,或后面的不执行直接返回结果。所以仔细一分析可以看出,程序每次以“/”作为分割,顺次输出每段字符(哪些段后面说)。第二个就是
_
==*a?putchar(a[31]):main(-65,_,a+1),当_
==a[0]时候,程序会输出一个字符,简单分析一下不难发现,这里的两个字符引自的字符串是固定的,_
恒为p2,此处的a[0]恒为p3,也就是说,输出字符来自p3,顺序来自p2,我们写个简单的程序,把p3字符按照p2规定的顺序输出,并且以 ‘/’ 分割开。
第三,控制程序跳转循环的是第一第二个参数,控制程序输出的是第三个参数,记住这些,我们后面会用到
这是我的写好的代码#include <stdio.h> #include <stdlib.h> #include <string.h> char *p2 = "@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,\ /n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/\ +k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!\ /n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w\ {%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/"; char *p3 = "!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"; int main() { int i = 0,j = 0,len = -1; len = strlen(p3); while(*(p2+i) != '\0') { if(*(p2+i) == '/') { printf("\n"); i++; continue; } for(j = 0;j < len;j++) { if(p3[j] == *(p2+i)) { printf("%c",p3[31+j]); break; } } i++; } return 0; }
结合上面两点重要信息,这里就是这段代码的27条 ‘短语’(为了描述方面,姑且这么叫吧)
再来对比一下原程序的输出。短语都能吻合上,证明我们的路是对的。
现在就走出了一大步了,至少,我们把无厘头的递归循环,简化成27条短语组合,接下来分析他们怎么组合的。
我们可以发现格式是这样的
A1 B1 A2 C1
A1 B2 A2 C2 C1
A1 B3 A2 C3 C2 C1
…
A1 B12 A2 C12 C11 …… C2 C1
- 这里分析不在脑算了,直接单步调试看call Stack窗口,不难看出进入第17行第三个main控制输出了A1,第二个顺次输出了B1–B12,第三个输出了A2,第二个控制B的顺序,主要是用_,需要我们知道他的call by的参数值,第一第三固定值,可以略过了。 顺次执行完这三个main后,执行逗号表达式后面语句,
- 这里就是C系列的控制逻辑了。用第19行的main控制C的个数级顺次,20行的main调用输出字符,这里参数t和都用上了。用携带信息是输出几个C,t所标示输出C?(? in 1,2,3……12)
- 由于这里是与逻辑,前面部分永真语句,所以后面的必会执行。这里,也就是调用的总源头了t==2,第一次执行,我们手算分析那儿t是等于2的然后调用这里的。所以条件成立的,进去
_ < 13 这句话也就是12次递归循环的起源了。当_大于等于13后,后面只是常数直接返回,然后一步步结束这个递归。
好了。至此,我三次接触这段代码,总算有了一个善了的结果。写出来纪念一下,也分享给喜欢这段程序的你们。有什么疑问评论区交流。一起学习。