那是一个阴风阵阵,天气严寒的夜晚。刚刚看完鬼故事的我正准备去会一会周公之女,突然手机传来一阵铃声把我吓了一跳,拿来一看是宫神在这个寂寞的夜晚爱特了我一下,遂打开人人瞅一眼。看起来是宫神看不懂某一段代码,于是我打开网页一瞅,我次奥,把代码写的这么难看,是要坑爹么?本来我是最不愿意看别人的代码,无奈这段代码实在是其丑无比,激起了我为民除害的欲望。于是我鬼使神差的打开了电脑,开始分析。
不扯了,扯不下去了...先把代码放上来,再说别的。
1 #include <stdio.h> 2 3 void _(int __, int ___, int ____) 4 { 5 ((___ / __) <= 1) ? _(__,___+1,____) : !(___ % __) ? _(__,___+1,___ % __) : 6 ((___ % __)==(___ / __) && !____) ? (printf("%d ",(___ / __)), 7 _(__,___+1,____)) : ((___ % __) > 1 && (___ % __) < (___ / __)) ? 8 _(__,___+1,____ + !((___ / __) % (___ % __))) : (___ < __ * __) ? 9 _(__,___+1,____) : 0; 10 } 11 12 int main() 13 { 14 _(100,0,0); 15 }
这个是求100以内素数的代码,很是明显,作者是想通过下划线这种不触犯命名规范但是又看起来略显高端的东西来达到他装X的目的。这我怎么能让他得逞,于是我通过IDE自带的替换功能,毁了这段代码...
void f(int a, int b, int c) { ((b / a) <= 1) ? f(a,b+1,c) : !(b % a) ? f(a,b+1,b % a) : ((b % a)==(b / a) && !c) ? (printf("%d ",(b / a)), f(a,b+1,c)) : ((b % a) > 1 && (b % a) < (b / a)) ? f(a,b+1,c + !((b / a) % (b % a))) : (b < a * a) ? f(a,b+1,c) : 0; }
事情看起来突然没有那么简单,原作者用了大量的条件表达式来混淆视听,于是我大概整理了一下代码的流程:
void f(int a, int b, int c) { if( (b/a) <= 1) f( a, b+1,c); else if( b%a==0) f( a, b+1, b%a); else if( b%a==(b/a)&&!c) { printf( "%d ", (b/a)); f( a, b+1, c); } else if( (b%a)>1 && (b%a) < (b/a)) f( a, b+1, c+!((b/a)%(b%a))); else if( b<a*a) f( a,b+1,c); }
注意,源代码里最后的一个0,完全是用来填补条件表达式的没有意义的东西,是1还是100都无所谓,所以我的代码里就没管。代码整理到这种程度,其实就看出来除了条件表达式和下划线,作者其实还是加了很多鼓弄玄虚的东西,于是我又简单修改了一下。
void f( int a, int b, int c) { if( b<a+a) f( a, b+1,c); else if( b%a ==0) f( a, b+1, 0); else if( b%a==(b/a) && c==0) { printf( "%d ", (b/a)); f( a, b+1, 0); } else if( b%a>1 && (b%a)<(b/a)) f( a, b+1, c+!((b/a)%(b%a))); else if( b<a*a) f( a, b+1, c); }
这一份代码跟原来的那个代码是等价的,这点我已经测试过了。代码整理到这种程度,已经基本明确了。于是我开始分析它的思路。而在这之前,奉劝C语言基本功不扎实的读者,去补习一下运算符的优先级,我刚刚在分析的时候,差点在这点上阴沟里翻船。由于分析过程波澜壮阔,跌宕起伏,我就略去不讲了,只写出结论。
首先,可以看到代码用了递归的机制。但是有一点可以确定,每一次递归,第一个参数的值都没有变过,而之前主函数里调用的时候,这里传入的是100,所以大概可以分析得出来,这个参数是范围,而且是固定不变的。经过分析之后,得出第二个参数b和a共同决定着当前计算的值是哪一个。素数的定义可以简单归纳如下:对于自然数d,任意的自然数e(1<e<d), d%e!=0,则d是一个素数。而这段代码中,b/a代表着上文中的d,b%a代表着上文的e。这段代码的机制就是在1到d中的每个自然数都要进行枚举。原作者用递归实现了一个两层循环,外层是从1到a来枚举d,内层是从2到d来枚举e。第三个参数c是一个标识变量,用它来判断d是不是素数。
函数体里第一个判断语句,是用来保证0和1不会被计算的。这一点很简单,脑子稍微转个弯就能看出来。
第二个判断语句,是来保证e不会出现等于0的情况,因为后文有用到对e来求余,而对0求余会导致程序出现运行期错误。同时这句也是内层循环的变量c的初始化。
第三个判断语句是内层循环的边界条件。在e==d时如果c等于0,那么b/a,即上文中的d就是素数。
第四个判断语句是内层循环的重要判断,这里b%a>1使得内层循环的开始从2开始,而不是1。(b%a)<(b/a)其实就是e<d,即保证e是在2至d-1这个区间里枚举。而这个函数最核心的一点就是这一层的递归调用,其中第三个参数是重中之重,c+!((b/a)%(b%a))确实很巧妙。这句其实就是c+!(d%e)。如果d可以被e整除,即余数为0,那么取非后+c,就可以改变c的值,无论之前c是否为0,递归的结果就是使得c不再等于0,即标识出d不是素数。而如果d不能被e整除,取非后为0,不会改变c的值。
第五个语句是外层循环的边界条件,这个语句稍微变形一下就是(b/a)<a。如果这个式子不成立,就代表着d超过了参数a所确定的范围,整个递归过程结束。
再说几个实现的细节。每一次递归调用,参数b的值都会加1,这样可以让e的值加1或者清0,以达到用第三个语句作为边界条件的目的。最后的判断语句没有使用<=符号,但是其实如果出现两值相等的情况,程序会在第二个判断语句处被截断,所以不影响最后结果。
原网页里所说的VC下编译通过,而gcc不能,是因为这个函数被定义为了void类型,而函数体中的条件表达式不知一次只递归地调用本身,这样使得条件表达式最后的运算结果无法计算,因为void类型不代表任何有数学意义的类型。将函数的返回类型改成int,float或者其他类型,只要不是void类型,gcc都可以编译通过无压力......至于VC,估计是针对这一点做了优化或者压根没考虑到这种情况。MS的东西,谁知道呢......
写完收工。大被一盖,闭眼睡觉。