C语言88年乱码大赛经典作品完全解析

都说西藏是一种病,对于一个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 < 1不成立,跳到第33行,1 < 0 不成立,跳到第48行,0 < 1 成立,调用main(2,2,“%s”)
  2. 1 < 2成立, 跳到16行, 2 < 3 成立, 顺次进去调用 main(-86,0,a+1)
  3. 1 < -86不成立,跳到33行,-86 <0成立,跳到35行,-86 < -72成立,调用main(_,t,p2),即main(0,-86,p2)
  4. 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
    这里写图片描述

  5. 这里分析不在脑算了,直接单步调试看call Stack窗口,不难看出进入第17行第三个main控制输出了A1,第二个顺次输出了B1–B12,第三个输出了A2,第二个控制B的顺序,主要是用_,需要我们知道他的call by的参数值,第一第三固定值,可以略过了。 顺次执行完这三个main后,执行逗号表达式后面语句,
  6. 这里就是C系列的控制逻辑了。用第19行的main控制C的个数级顺次,20行的main调用输出字符,这里参数t和都用上了。用携带信息是输出几个C,t所标示输出C?(? in 1,2,3……12)
  7. 由于这里是与逻辑,前面部分永真语句,所以后面的必会执行。这里,也就是调用的总源头了t==2,第一次执行,我们手算分析那儿t是等于2的然后调用这里的。所以条件成立的,进去
    _ < 13 这句话也就是12次递归循环的起源了。当_大于等于13后,后面只是常数直接返回,然后一步步结束这个递归。
    这里写图片描述
    好了。至此,我三次接触这段代码,总算有了一个善了的结果。写出来纪念一下,也分享给喜欢这段程序的你们。有什么疑问评论区交流。一起学习。
  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值