Qunie——自我生成程序

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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章