zoukankan      html  css  js  c++  java
  • C语言实现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!
    ");
    	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
    ", text[i]);
    	}
    	// printf("Hello, World!
    ");
    	return 0;
    }
    

    不能继续重复,否则就是“无限递归”,string array就写不完了。现在,只能输出到第6行。

    “山里有个庙”的故事是讲不完的

    必须想出——如何输出第6行之后的这个string array的内容?回顾一下,printf("%s ", 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
    ", 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
    ", 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
    ", 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
    ", text[i]);
    	}
    	return 0;
    }
    

    用工具验证

    这个版本就是正确的么?细微的差别,眼睛看起来费劲,且文本比较本来就该是工具干的。
    编译:
    xusiwei1236@csdn.net:~/test$ gcc quine-printf.c
    运行:
    xusiwei1236@csdn.net:~/test$ ./a.out > out
    比较:
    xusiwei1236@csdn.net:~/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
    ", text[i]);",
                    "       }",
                    "       for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
    -               "               printf("               \"%s\",\n", text[i
    +               "               printf("                "%s",
    ", text[i]);",
                    "       }",
                    "       for(i = 6; i < sizeof(text)/sizeof(text[0]); ++i) {",
    -               "               printf("%s\n", text[i]);",
    +               "               printf("%s
    ", text[i]);",
                    "       }",
                    "       return 0;",
                    "}",
    

    可以看到有三处差异,都是因为转义字符的问题;大体有两类,格式化字符串的开头和结尾的引号,以及前后缀字符串中间的特殊字符。

    更正

    直接用printf看起来像是没办法,因为不可避免的要用格式化字符串,自然就有引号,对应的字符串里面就会有转义字符。对于第一、第三处的printf("%s ", text[i]);很容易想到,可以用puts(text[i]);替换,既简单又优雅(对于的字符串也要更改,这里只是对于的代码部分)的解决了格式字符串的开头和结尾的引号(")和换行符( )。这样解决了格式化字符的问题。

    对于第二处,有前后缀需要拼接,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)

  • 相关阅读:
    HOJ 2139 Spiderman's workout(动态规划)
    FZU 2107 Hua Rong Dao(dfs)
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 无向连通图最少包含多少条边
    Java 第十一届 蓝桥杯 省模拟赛 无向连通图最少包含多少条边
  • 原文地址:https://www.cnblogs.com/xusw/p/13758760.html
Copyright © 2011-2022 走看看