编写程序输出其源代码。
奇怪的要求
有没有一种程序能够将自己的源代码丝毫不差地输出呢?比如最常见的 Hello World
1 2 3 4 5 6
| #include "stdio.h" int main() { printf("Hello world!"); return 0; }
|
输出的是
如果要达到题目要求应该还要把 #include "stdio.h"
int main()
{}
等内容输出。也就是符合要求的输出是
1 2 3 4 5 6
| #include "stdio.h" int main() { printf("Hello world!"); return 0; }
|
如果要输出以上内容,程序可以是这样
1 2 3 4 5 6 7 8 9 10 11
| #include "stdio.h" int main() { printf("#include \"stdio.h\"\n"); printf("int main()\n"); printf("{\n"); printf(" printf(\"Hello world!\");\n"); printf(" return 0;\n"); printf("}\n"); return 0; }
|
但是这么做的话,符合要求的输出就变了。变成了要输出这样的内容
1 2 3 4 5 6 7 8 9 10 11
| #include "stdio.h" int main() { printf("#include \"stdio.h\"\n"); printf("int main()\n"); printf("{\n"); printf(" printf(\"Hello world!\");\n"); printf(" return 0;\n"); printf("}\n"); return 0; }
|
似乎陷入了自我指涉的循环。因为要输出源代码的非输出语句(如 main()
, return 0
)所以又得增加输出这些代码的语句。结果源代码发生变化,所以又要增加输出变化部分的代码,无穷无尽。
那么是否存在可以输出自身源码的代码呢?
答案是肯定的
Quine
先来看一段 Java
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Quine { public static void main(String[] args) { char q = 34; String[] l = { "public class Quine", "{", " public static void main(String[] args)", " {", " char q = 34; // Quotation mark character", " String[] l = { // Array of source code", " ", " };", " for(int i = 0; i < 6; i++) // Print opening code", " System.out.println(l[i]);", " for(int i = 0; i < l.length; i++) // Print string array", " System.out.println(l[6] + q + l[i] + q + ',');", " for(int i = 7; i < l.length; i++) // Print this code", " System.out.println(l[i]);", " }", "}", }; for(int i = 0; i < 6; i++) System.out.println(l[i]); for(int i = 0; i < l.length; i++) System.out.println(l[6] + q + l[i] + q + ','); for(int i = 7; i < l.length; i++) System.out.println(l[i]); } }
|
先来分析一下它的输出。代码中有三个输出语句
1 2 3 4 5 6
| for(int i = 0; i < 6; i++) System.out.println(l[i]); for(int i = 0; i < l.length; i++) System.out.println(l[6] + q + l[i] + q + ','); for(int i = 7; i < l.length; i++) System.out.println(l[i]);
|
第一个 println
很简单,就是打印 l
数组中的前六行。输出的内容是
1 2 3 4 5 6
| public class Quine { public static void main(String[] args) { char q = 34; // Quotation mark character String[] l = { // Array of source code
|
第二个 println
相对复杂, l[6]
是 “ ” 也就是空白符。 q
是 "
双引号。l[i]
是每一行的内容,最后附上一个 ,
。所以这个 println
主要做的是输出 l
中的每一行,并用双引号括起来,最后拼接上逗号。输出的内容是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| "public class Quine", "{", " public static void main(String[] args)", " {", " char q = 34; // Quotation mark character", " String[] l = { // Array of source code", " ", " };", " for(int i = 0; i < 6; i++) // Print opening code", " System.out.println(l[i]);", " for(int i = 0; i < l.length; i++) // Print string array", " System.out.println(l[6] + q + l[i] + q + ',');", " for(int i = 7; i < l.length; i++) // Print this code", " System.out.println(l[i]);", " }", "}",
|
第三个 println
打印 l
第7行之后的所有内容。输出的内容是
1 2 3 4 5 6 7 8 9
| }; for(int i = 0; i < 6; i++) // Print opening code System.out.println(l[i]); for(int i = 0; i < l.length; i++) // Print string array System.out.println(l[6] + q + l[i] + q + ','); for(int i = 7; i < l.length; i++) // Print this code System.out.println(l[i]); } }
|
三个输出凑一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Quine { public static void main(String[] args) { char q = 34; // Quotation mark character String[] l = { // Array of source code "public class Quine", "{", " public static void main(String[] args)", " {", " char q = 34; // Quotation mark character", " String[] l = { // Array of source code", " ", " };", " for(int i = 0; i < 6; i++) // Print opening code", " System.out.println(l[i]);", " for(int i = 0; i < l.length; i++) // Print string array", " System.out.println(l[6] + q + l[i] + q + ',');", " for(int i = 7; i < l.length; i++) // Print this code", " System.out.println(l[i]);", " }", "}", }; for(int i = 0; i < 6; i++) // Print opening code System.out.println(l[i]); for(int i = 0; i < l.length; i++) // Print string array System.out.println(l[6] + q + l[i] + q + ','); for(int i = 7; i < l.length; i++) // Print this code System.out.println(l[i]); } }
|
与源码比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Quine { public static void main(String[] args) { char q = 34; String[] l = { "public class Quine", "{", " public static void main(String[] args)", " {", " char q = 34; // Quotation mark character", " String[] l = { // Array of source code", " ", " };", " for(int i = 0; i < 6; i++) // Print opening code", " System.out.println(l[i]);", " for(int i = 0; i < l.length; i++) // Print string array", " System.out.println(l[6] + q + l[i] + q + ',');", " for(int i = 7; i < l.length; i++) // Print this code", " System.out.println(l[i]);", " }", "}", }; for(int i = 0; i < 6; i++) System.out.println(l[i]); for(int i = 0; i < l.length; i++) System.out.println(l[6] + q + l[i] + q + ','); for(int i = 7; i < l.length; i++) System.out.println(l[i]); } }
|
完全一样。
这就是能够输出自身源码的代码。
通常把这类能够输出自身源码的代码称为 Quine
。Quine 一词来源于逻辑学家,哲学家 Willard_Van_Orman_Quine
当然不仅是 Java
可以编写 Quine
,C
也可以编写 Quine
1
| main(){char q=34,n=10,*a="main(){char q=34,n=10,*a=%c%s%c;printf(a,q,a,q,n);}%c";printf(a,q,a,q,n);}
|
这段代码的原理跟之前 Java
版的差不多。此外还有不同的做法,比如使用 C
的宏来实现
1 2
| #define T(a) main(){printf(a,#a);} T("#define T(a) main(){printf(a,#a);}\nT(%s)")
|
还有很多很多的玩法。通常一种语言的 Quine
不是唯一的。
除了编译型语言,解释形语言也可以编写 Quine
。比如 Python
是这样的
1 2
| s = 's = %r\nprint(s%%s)' print(s%s)
|
Lua
是这样的
1 2
| x = [["x = [" .. "[" .. x .. "]" .. "]\nprint(" .. x)]] print("x = [" .. "[" .. x .. "]" .. "]\nprint(" .. x)
|
Javascript
是这样的
1 2
| Quine = function () {var str = arguments.callee.toString(); Quine = console.log(str.substring(52, 60) + str + str.substring(32, 37) + str.substring(9, 11));}.call()
|
Ouroboros
输出源码还不够,有些人想到了将不同语言的 Quine
嵌套到一起。用一个语言的代码输出另一个语言的源码,该源码又可以输出之前语言的源码。于是就出现了这样的 Java
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class Quine { public static void main(String[] args) { char q = 34; String[] l = { " ", "=============<<<<<<<< C++ Code >>>>>>>>=============", "#include <iostream>", "#include <string>", "using namespace std;", "", "int main(int argc, char* argv[])", "{", " char q = 34;", " string l[] = {", " };", " for(int i = 20; i <= 25; i++)", " cout << l[i] << endl;", " for(int i = 0; i <= 34; i++)", " cout << l[0] + q + l[i] + q + ',' << endl;", " for(int i = 26; i <= 34; i++)", " cout << l[i] << endl;", " return 0;", "}", "=============<<<<<<<< Java Code >>>>>>>>==========", "public class Quine", "{", " public static void main( String[] args )", " {", " char q = 34;", " String[] l = {", " };", " for(int i = 2; i <= 9; i++)", " System.out.println(l[i]);", " for(int i = 0; i < l.length; i++)", " System.out.println( l[0] + q + l[i] + q + ',' );", " for(int i = 10; i <= 18; i++))", " System.out.println(l[i]);", " }", "}", }; for(int i = 2; i <= 9; i++) System.out.println(l[i]); for(int i = 0; i < l.length; i++) System.out.println( l[0] + q + l[i] + q + ',' ); for(int i = 10; i <= 18; i++) System.out.println(l[i]);
} }
|
代码输出的是一段 C++
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <string> using namespace std;
int main(int argc, char* argv[]) { char q = 34; string l[] = { " ", "=============<<<<<<<< C++ Code >>>>>>>>=============", "#include <iostream>", "#include <string>", "using namespace std;", "", "int main(int argc, char* argv[])", "{", " char q = 34;", " string l[] = {", " };", " for(int i = 20; i <= 25; i++)", " cout << l[i] << endl;", " for(int i = 0; i <= 34; i++)", " cout << l[0] + q + l[i] + q + ',' << endl;", " for(int i = 26; i <= 34; i++)", " cout << l[i] << endl;", " return 0;", "}", "=============<<<<<<<< Java Code >>>>>>>>=============", "public class Quine", "{", " public static void main(String[] args)", " {", " char q = 34;", " String[] l = {", " };", " for(int i = 2; i <= 9; i++)", " System.out.println( l[i] );", " for(int i = 0; i < l.length; i++)", " System.out.println(l[0] + q + l[i] + q + ',');", " for(int i = 10; i <= 18; i++)", " System.out.println(l[i]);", " }", "}", }; for(int i = 20; i <= 25; i++) cout << l[i] << endl; for(int i = 0; i <= 34; i++) cout << l[0] + q + l[i] + q + ',' << endl; for(int i = 26; i <= 34; i++) cout << l[i] << endl; return 0; }
|
这段 C++
代码反过来生成上面的 Java
代码。
这样的代码被称为 Quine Relay
或者 Ouroboros
。
大家一看,原来还可以这么玩。于是一发不可收拾,更多语言的版本出现了。
最后有一个聪明绝顶的人做出了100个语言的Quine 。好了好了你赢了。
后记
限于篇幅,很多语言的 Quine
还没有给出。感兴趣的童鞋可以去维基查看。编程语言除了这种玩法之外,还有另外一种玩法,也是涉及到多个语言的。我懒得写了就不多说了请相信我一定会在下次更新的!