有人问我,用%printf打印一个单独的字符是显而易见还是不显而易见的,是用(正确的)"%%"还是(不正确的)"\%"。这是我回复的一部分,为了网页稍作编辑。]

发件人:Steve Summit
主题:Re: printf("%")
回复:您的邮件 <199703310107.RAA19240@mx2.eskimo.com>
于 1997 年 3 月 30 日星期日 17:05:57 -0800
抄送:scs@eskimo.com

如果您习惯于思考 C 程序是如何真正“工作”的——编译器如何生成可执行程序,以及函数在运行时如何被调用——那么它确实可能显而易见,\%根本不可能起作用,而解决方案必须是类似%%.

之类的。假设您编写了以下程序:

	main()
	{
	char line[80];
	fgets(line, 80, stdin);
	fputs(line, stdout);
	}

很明显,这个程序从标准输入读取一行,然后将其原样打印出来。假设您输入的行是 8 个字符:

	abc\ndef

会打印出什么?答案是,一行,包含 8 个字符:

	abc\ndef

您可能会认为它会打印出两行:

	abc
	def

(事实上,我想到“为什么它*不*打印两行?”是一个我从未添加到列表中的常见问题。)
(但我错了;这是问题 8.8。)

为什么它*不*打印两行?为什么这两个字符\n不像在源代码中的字符和字符串常量中那样被解释为单个换行符?因为这些用于各种特殊字符的反斜杠转义*仅*在源代码中的字符和字符串常量中被解释。此外,它们是在编译过程中、编译时由编译器解释的,用于将您的源代码转换为可执行程序。任何标准的 I/O 函数(fgets, fputs, 打印一个单独的等)都不会对反斜杠进行任何解释。对它们来说,反斜杠只是被读取或写入的另一个字符。到这些函数运行时,源代码中可能存在的任何反斜杠(即在传递给这些函数用于处理的字符或字符串常量中的反斜杠)都已经被处理过了。

也就是说,当fgets()从输入文件中读取一个\时,它只是将它与所有其他字符一起放入数组中。但当您写

	printf("Hello, world!\n");

打印一个单独的接收一个由 14 个字符组成的字符串(不包括终止符\0)。字符串中的第 14 个字符是单个换行符,无论它在内部如何表示。打印一个单独的并*不*接收两个字符\n,它也不必将它们翻译成换行符。编译器已经完成了这项工作。

如果您明白了这一点,那么很明显

	printf("\%");

不能工作。编译器会将\%翻译成什么?碰巧,\%不是一个合法的特殊字符序列,但根据\', \",而\\的类比,编译器很可能会将\%转换成一个单独的%字符。(它也可能生成关于未定义转义序列的警告消息。)所以打印一个单独的很可能会收到一个由单个%组成的字符串,*就像您输入了*

	printf("%");

一样。%每当 printf 在其格式字符串中看到一个打印一个单独的字符时,它*总是*会查看后面的字符来决定打印什么。但是后面没有字符了,所以

感到困惑。\%我明白你为什么期望\能工作——至少在某些上下文中,“关闭”了对后面字符的特殊解释。(这就是您写'\''“这个字符串包含一个 \” 和一个 \\”时它的作用。)所以,当您想打印一个单独的%时,您想“关闭”打印一个单独的%字符的特殊解释,而您首先想到的是反斜杠。但是等等:反斜杠会关闭*编译器*对某些字符的特殊解释,而您想关闭的是*printf*对字符的特殊解释。这两个上下文是完全分开的,所以规则可能不同,事实上规则*确实*不同。(第一个上下文是“编译器处理的字符串常量”,在该上下文中,关闭某些字符的特殊解释的规则是:在它们前面加上反斜杠。第二个上下文是“printf 解释的格式字符串”printf),在该上下文中,规则是:如果您想要一个单独的%,您就写%%.)