2021年的元旦节,放假在家,闲来无事,从书架上翻到一本《枕边算法书》。翻开几页就看到了Quine问题:如何写一个可以打印出自己的源代码。忍住好奇没去看解答,想看看这类谜题自己还能不能解出来。
第一次尝试
乍看觉得好像还比较简单的样子,于是直接开写代码:
#include <stdio.h>
int main(){
printf("#include <stdio.h>\nint main(){\n printf(\"#include ... \");");
}
写着写着发现不对啊,这样写就好像两个镜子对着照一样,无限的引用啊…
恼火得很。
换个思路
绞尽脑汁花了好长时间,想到这个核心思路:
char* s="char* s=%s;printf(s,s);";printf(s,s);
利用printf函数的%s转义符,用格式化字符串自身替换掉%s所在的位置。
感觉这个思路应该没问题,但实际调试的时候卡在引号“、换行符等转义符的处理上。在定义字串s的时候是不可避免会遇到\n “这样的情况,这些转义符在被%s替换后是应该直接打印出”\n"的字符,而不是换行,为了解决这个问题,我很傻的写了下面这个很蠢的代码。
丑陋的实现
自己写了个Print函数,这个函数实现的功能是打印字符串,如果字符串中有@符号,则把@符号替换为整个字符串自身。
#include <stdio.h>
void Print(char* s){
//代码实现太丑,就不贴出来了
}
int main(){
Print("#include ...太长省略...int main(){\n Print(@);\n}");
}
代码工作是OK的,完全可以输出与自身一摸一样的源代码。但是看着这长达100多行的代码,我的内心是崩溃的。
借鉴优化
一定有更好的办法的,抱着这个想法,我开始继续尝试。最终找到了下面的方法:
#include <stdio.h>
int main(){
char* s = "#include <stdio.h>%cint main(){%c char* s = %c%s%c;%c printf(s,10,10,34,s,34,10,10);%c}";
printf(s,10,10,34,s,34,10,10);
}
核心思路其实没有变,只是避免了在定义字串的时候使用\n "等转义符,而是直接使用ASCII 码值来处理。
Apple的思路
在我尝试过程中,Apple曾经给过一个取巧的思路:直接读源代码文件,把文件内容输出就行了。我很诧异我怎么没有就没想到过这个思路呢。这里给个vbs的实现。
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile(WScript.ScriptFullName, 1)
WScript.Echo objtextfile.ReadAll
其他的思路
发现这个经典问题有太多高手的实现了,有些真的让人感叹,这是什么脑子,怎么想的出来。比如:
#define q(k)main(){return!puts(#k"\nq("#k")");}
q(#define q(k)main(){return!puts(#k"\nq("#k")");})
很晕吧。
网上搜一搜还有很多很好的思路。看了一圈就发现一个大问题:我为什么想不出来呢?哼哼!!