Qunie是一段沒有輸入,但輸出和它本身源碼相同的程序。本文無任何高深技術,純屬娛樂!
最近看到wikipedia的一個詞條——Quine,簡介部分摘錄於此,並簡要翻譯:
A quine is a non-empty computer program which takes no input and produces a copy of its own source code as its only output. The standard terms for these programs in the computability theory and computer science literature are “self-replicating programs”, “self-reproducing programs”, and “self-copying programs”.
Qunie是一段計算機程序,它沒有輸入,但輸出和它本身的源碼相同。在可計算理論和計算機科學領域的標準定義,叫做“自我重複程序”、“自我生成程序”和“自我拷貝程序”。
從上面這段簡介中可以看到,Qunie是一段沒有輸入,但輸出和它本身源碼相同的程序。
從hello world開始
經典的Hello world程序如下:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
現在,就從這個程序開始,常識寫出Quine。輸出源碼文本,又有輸入,只能用hard-coding一個string array了;一旦開始嘗試,你會發現,string array寫到下面的時候,寫不下去了:
#include <stdio.h>
int main()
{
int i;
char* text[] = {
"#include <stdio.h>",
"",
"int main()",
"{",
" char* text[] = {",
// ...
};
for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
printf("%s\n", text[i]);
}
// printf("Hello, World!\n");
return 0;
}
不能繼續重複,否則就是“無限遞歸”,string array就寫不完了。現在,只能輸出到第6行。
“山裏有個廟”的故事是講不完的
必須想出——如何輸出第6行之後的這個string array的內容?回顧一下,printf("%s\n", text[i]);
只輸出了引號內的內容,加上前綴(" \"", TAB鍵可以直接寫在字符串裏面
)和後綴("\","
)即可,即printf(" \"%s\",", text[i]);
於是,可以繼續寫:
#include <stdio.h>
int main()
{
int i;
char* text[] = {
"#include <stdio.h>",
"",
"int main()",
"{",
" char* text[] = {",
// ...
};
for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
printf("%s\n", text[i]);
}
for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
printf(" \"%s\",", text[i]);
}
return 0;
}
這樣,程序可以輸出到第11行了。
第一個看似正確的解答
剩下的源碼也是要寫到text
裏,同時要保證——string array的輸出在兩者中間,於是第一個循環也要被拆爲兩個部分:
#include <stdio.h>
int main()
{
int i;
char* text[] = {
"#include <stdio.h>",
"",
"int main()",
"{",
" char* text[] = {",
" };",
"",
" for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
" printf("%s\n", text[i]);",
" }",
" for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
" printf(" \"%s\",", text[i]);",
" }",
" return 0;"
"}",
};
for(i = 0; i < 6; ++i) {
printf("%s\n", text[i]);
}
for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
printf(" \"%s\",", text[i]);
}
for(i = 6; i < sizeof(text)/sizeof(text[0]); ++i) {
printf("%s\n", text[i]);
}
return 0;
}
用工具驗證
這個版本就是正確的麼?細微的差別,眼睛看起來費勁,且文本比較本來就該是工具乾的。
編譯:
[email protected]:~/test$ gcc quine-printf.c
運行:
[email protected]:~/test$ ./a.out > out
比較:
[email protected]:~/test$ git diff quine-printf.c out
用git diff
一比,發現還是有點差別的:
diff --git a/quine-printf.c b/out
index ec3ecf9..d48d2ab 100644
--- a/quine-printf.c
+++ b/out
@@ -13,13 +13,13 @@ int main()
" };",
" ",
" for(i = 0; i < 6; ++i) {",
- " printf(\"%s\\n\", text[i]);",
+ " printf("%s\n", text[i]);",
" }",
" for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
- " printf(\" \\\"%s\\\",\\n\", text[i
+ " printf(" \"%s\",\n", text[i]);",
" }",
" for(i = 6; i < sizeof(text)/sizeof(text[0]); ++i) {",
- " printf(\"%s\\n\", text[i]);",
+ " printf("%s\n", text[i]);",
" }",
" return 0;",
"}",
可以看到有三處差異,都是因爲轉義字符的問題;大體有兩類,格式化字符串的開頭和結尾的引號,以及前後綴字符串中間的特殊字符。
更正
直接用printf看起來像是沒辦法,因爲不可避免的要用格式化字符串,自然就有引號,對應的字符串裏面就會有轉義字符。對於第一、第三處的printf("%s\n", text[i]);
很容易想到,可以用puts(text[i]);
替換,既簡單又優雅(對於的字符串也要更改,這裏只是對於的代碼部分)的解決了格式字符串的開頭和結尾的引號("
)和換行符(\n
)。這樣解決了格式化字符的問題。
對於第二處,有前後綴需要拼接,C的標準庫的strcat
提供了該功能(需要注意strcat
要求左邊的字符串有足夠的空間),於是可以將第二處改爲:
char prefix[128] = " \"";
char postfix[] = ",";
puts(strcat(strcat(prefix, text[i]), postfix));
這樣仍然沒有解決前後綴中的特殊字符,看到[]
,我們自然想到,可以直接寫成員的值(別拿村長不當幹部,別當字符數組不是數組,查一下ASCII碼錶):
char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table, 0x22 quote
char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma
puts(strcat(strcat(prefix, text[i]), postfix));
好了,現在完美了(__LINE__
是行號的預定義宏,爲了消除後面6那個magic number,讓程序看起來更優雅):
#include <stdio.h>
#include <string.h>
int main()
{
int i;
int header = __LINE__ + 1;
char* text[] = {
"#include <stdio.h>",
"#include <string.h>",
"",
"int main()",
"{",
" int i;",
" int header = __LINE__ + 1;",
" char* text[] = {",
" };",
" ",
" for(i = 0; i < header; ++i) {",
" puts(text[i]);",
" }",
" ",
" for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
" char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table",
" char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma",
" puts(strcat(strcat(prefix, text[i]), postfix));",
" }",
" ",
" for(i = header; i < sizeof(text)/sizeof(text[0]); ++i) {",
" puts(text[i]);",
" }",
" ",
" return 0;",
"}",
};
for(i = 0; i < header; ++i) {
puts(text[i]);
}
for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table
char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma
puts(strcat(strcat(prefix, text[i]), postfix));
}
for(i = header; i < sizeof(text)/sizeof(text[0]); ++i) {
puts(text[i]);
}
return 0;
}
轉載註明原文鏈接,及出處:http://blog.csdn.net/xusiwei1236
wikipedia的Qunie詞條,可以看到更多其他語言版本的Qunie:https://en.wikipedia.org/wiki/Quine_(computing)