最近發現了一段C程式很有意思,與大家分享。來源程式如下:
#include <stdio.h>
main(int _){char*x="*b#**000**I#*******2*0***#-.****5.*-#-.****54.#*******2**6#****00**0.#";while(_=*x/4)_-=8,printf("\n%*s"+!!_,_+_,"_/_/_/"+*x++%4*2);}
以上代碼在VC6.0中能正常編譯和連結。啟動並執行結果如下:
是用”_/”組成的單詞“knocker”。
下面我們來分析這個程式:
#include <stdio.h>
main(int _){
char*x="*b#**000**I#*******2*0***#-.****5.*-#-.****54.#*******2**6#****00**0.#";
while(_=*x/4)
{
_-=8;
printf("\n%*s"+!!_,_+_,"_/_/_/"+*x++%4*2);
}
}
首先來看看main函數參數,這個參數的名稱比較奇怪”_”,我們看著有些不習慣,但它確實是合法的變數名稱。另外,本來我們常見的main函數一般都不帶參數,如果有參數是因為程式希望處理命令列格式下運行這個程式所需要的參數,一般是這樣的:main(int argc, char **argv),第一個參數argc表示參數表中參數的個數,argc是一個char型二維數組,儲存著參數字串。舉個例子,如果我們在命令列下輸入命令:dir –s c:\
此時,argc的值為3(包括命令本身),argv[0]指向字串:dir,arg[1] 指向字串:-s,arg[2]指向字串c:\。那麼既然main函數的參數通常要不沒有,要不就是兩個,而這個程式只有一個,這樣做是否合法呢?答案是肯定的!函數中的參數是儲存在堆棧中的,所以這就涉及到一個誰來平衡堆棧的問題,是調用者還是被調用者。在使用VC6編譯器的時候,如果函數沒有特別的聲明,預設是調用者清理堆棧。換句話說,執行階段程式庫(runtime)調用main函數的時候,只傳遞一個參數給main函數,這一點它自己是知道的,當main函數調用結束以後,它在平衡堆棧的時候,只清除掉一個函數,因此不會出現任何問題。
我們接著往下看,下面定義了一個char型指標變數x,指向一個字串。接下來是while語句,while(_=(*x)/4),但運算式_=(*x)/4的值不為0的時候就執行while的迴圈體,然而運算式_=(*x)/4是一個指派陳述式,它的值又是多少呢?在這種情況下,通常賦值號右面的運算式的值就是整個運算式的值。第一次執行這個語句的時候,x指向字元’*’,對應的值為42,以此類推,當x=0時,也就是字串結束的時候,迴圈結束。
接下來在原來的程式中是:_-=8,printf("\n%*s"+!!_,_+_,"_/_/_/"+*x++%4*2);這裡的逗號作用有些類似分號,但是逗號兩側組成的是一條語句,而分號則是兩條語句。
我們仔細看printf這條語句,它的格式字串為:”\n%*s”,其中%*s很少見,很多人不知道這是什麼格式,不過我們可以在MSDN中找到這樣的描述:
If the width specification is an asterisk (*), an int argument from the argument list supplies the value. The width argument must precede the value being formatted in the argument list.
意思是說如果寬度用星號(*)來指定,則應該在參數列表中提供一個int型參數作為寬度的值。這樣就清楚了,原來這個printf語句是用運算式_+_來控制字元串”%*s”的寬度,也就是替換其中的(*)。
在仔細一看,原來格式字串還沒有完!完整的應該是:"\n%*s"+!!_。這就奇怪了,字串怎麼和!!_相加呢?其實也不奇怪,在這裡!!_是對變數_作了兩次“非”的操作,結果應該是0或1。要明確的是,字串作為參數傳遞給函數的時候,只是把字串的首地址傳遞給了函數,所以字串"\n%*s"的首地址就是字元’\n’的地址,當這個地址加1的時候,傳遞給printf函數的格式字串就變成了"%*s",所以這個+!!_的奧妙就在於控制換行!是不是很有創意?
當然了,下面的代碼同樣有創意,就是printf語句要列印的字串參數:"_/_/_/"+*x++%4*2。經過剛才的分析,我們不難理解"_/_/_/"加上後面的運算式的用意了,同樣是控制輸出字元的個數,是一個”_/”,兩個,還是三個。那麼關鍵就是後面這個運算式了:*x++%4*2。其實慢慢的分析也不困難,先執行*x%4*2,然後執行x++。所以,當在執行while語句的時候,x會指向下一個字元!
綜上所述,這個小程式就是利用一個字串來控制輸出的例子,裡面運用了很多技巧,對於初學者需要很好的理解,對今後的編程是很有協助的!