编写程序输出其源代码。

奇怪的要求

有没有一种程序能够将自己的源代码丝毫不差地输出呢?比如最常见的 Hello World

1
2
3
4
5
6
#include "stdio.h"
int main()
{
printf("Hello world!");
return 0;
}

输出的是

1
Hello world!

如果要达到题目要求应该还要把 #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; // 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
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 数组中的前六行。输出的内容是

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; // 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]);
}
}

完全一样。

这就是能够输出自身源码的代码。

通常把这类能够输出自身源码的代码称为 QuineQuine 一词来源于逻辑学家,哲学家 Willard_Van_Orman_Quine

当然不仅是 Java 可以编写 QuineC 也可以编写 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 还没有给出。感兴趣的童鞋可以去维基查看。编程语言除了这种玩法之外,还有另外一种玩法,也是涉及到多个语言的。我懒得写了就不多说了请相信我一定会在下次更新的!