为什么使用尾随换行符而不使用printf开头?


79

我听说您在使用时应避免使用换行符printf。因此,而不是printf("\nHello World!")你应该使用printf("Hello World!\n")

在上面的特定示例中,这没有意义,因为输出将有所不同,但请考虑以下事项:

printf("Initializing");
init();
printf("\nProcessing");
process_data();
printf("\nExiting");

相比:

printf("Initializing\n");
init();
printf("Processing\n");
process_data();
printf("Exiting");

我觉得尾随换行符没有任何好处,除了看起来更好。还有其他原因吗?

编辑:

我现在就在这里进行点票表决。我不认为这属于堆栈溢出,因为这个问题主要与设计有关。我也要说,尽管对此事可能有意见,但Kilian Foth的回答cmaster的回答证明了一种方法的确确实有非常客观的好处。


5
这个问题在“带有代码的问题”(离题)和“概念软件设计”(离题)之间的边界上。它可能会关闭,但不要太用力。我认为添加具体的代码示例仍然是正确的选择。
吉莲·芙丝

46
最后一行将与linux上的命令提示符合并,而没有尾随换行符。
GrandmasterB

4
如果它“看起来更好”并且没有负面影响,那就是这样做的一个充分的理由,IMO。编写出色的代码与编写出色的小说或出色的技术论文没有什么不同-魔鬼总是细节。
alephzero

5
难道init()process_data()打印任何自己?如果期望的话,您期望结果如何?
Bergi '18

9
\n是行终止符,不是行分隔符。事实证明,在UNIX上,文本文件几乎总是以结尾\n
乔纳森·莱因哈特

Answers:


222

相当数量的终端I / O是行缓冲的,因此通过用\ n结束一条消息,可以确定它会及时显示。带有\ n开头的消息可能一次也可能不会显示。通常,这意味着每个步骤都会显示一步的进度消息,这在您试图了解程序行为时不会造成混乱和浪费时间。


20
当使用printf调试崩溃的程序时,这一点尤其重要。将换行符放在printf的末尾意味着控制台的stdout在每个printf处都被刷新。(请注意,当将stdout重定向到文件时,std库通常将执行块缓冲而不是行缓冲,因此即使在使用换行符的情况下,printf调试崩溃也非常困难。)
Erik Eidt

25
@ErikEidt请注意,您应该fprintf(STDERR, …)改用它,它通常根本不会缓冲用于诊断输出。
Deduplicator

4
@Deduplicator将诊断消息写入错误流也有其缺点-如果将某些内容写入错误流,许多脚本会假定程序已失败。
Voo

54
@Voo:我认为任何假定写入stderr的程序都表明失败本身就是错误的。进程的退出代码表明它是否失败。如果失败,则stderr输出将说明原因。如果该过程成功退出(退出代码为零),则应将stderr输出视为人类用户的信息输出,没有特定的机器可解析的语义(例如,它可能包含人类可读的警告),而stdout是以下内容的实际输出:该程序,可能适合进一步处理。
Daniel Pryden

23
@Voo:您正在描述什么程序?我不知道任何行为像您描述的那样被广泛使用的软件包。我确实知道有些程序可以执行此操作,但是这并不是像我刚才所描述的那样:这是Unix或类Unix环境中绝大多数程序的工作方式,而据我所知,绝大多数程序总是有。我当然不会提倡任何程序都避免仅仅因为某些脚本不能很好地处理而向stderr编写程序。
Daniel Pryden '18年

73

在POSIX系统(基本上是任何linux,BSD,无论您能找到基于开源的系统)上,一行定义为由换行符终止的字符串\n。这是基本的假设,所有标准的命令行工具建立在,包括(但不限于)wcgrepsedawk,和vim。这也是为什么某些编辑器(如vim)总是\n在文件末尾添加a 的原因,以及为什么早期的C语言标准要求标头以\n字符结尾的原因。

顺便说一句:\n终止行使文本处理变得更加容易:您肯定知道在拥有终止符后已经有了完整的行。而且您可以肯定,如果您尚未遇到该终止符,则需要查看更多的字符。

当然,这是在程序的输入端,但是程序输出经常再次用作程序输入。因此,您的输出应遵循惯例,以便允许无缝输入其他程序。


25
这是软件工程中最古老的争论之一:使用换行符(或者在编程语言中,使用另一个“语句结束”标记,如分号)作为行终止符或行分隔符更好吗?两种方法各有利弊。Windows世界大多数人都认为换行序列(这里通常是CR LF)是一个行分隔符,因此文件中的最后一行不需要以此结尾。但是,在Unix世界中,换行符(LF)是行终止符,并且许多程序都是基于此假设而构建的。
Daniel Pryden

33
POSIX甚至一行定义“零个或多个非换行符加上一个终止换行符的序列”。
管道

6
既然正如@pipe所说,它在POSIX规范中,我们可能可以将其称为法律上的,而不是答案所暗示的事实
Baldrickk

4
@鲍德里克(Baldrickk)对。现在,我将答案更新为更加肯定。
cmaster

C也对源文件采用此约定:不以换行符结尾的非空源文件会产生未定义的行为。
R.,

31

除了其他人提到的内容之外,我觉得还有一个更简单的原因:这是标准。每当将任何内容打印到STDOUT时,它几乎总是假定它已经在新行上,因此不需要启动新行。它还假定要写入的下一行将以相同的方式起作用,因此它以开始新行来结束。

如果输出与标准尾随换行符交织的前导换行符行,则其结果将如下所示:

Trailing-newline-line
Trailing-newline-line

Leading-newline-line
Leading-newline-line
Leading-newline-lineTrailing-newline-line
Trailing-newline-line

Leading-newline-lineTrailing-newline-line
Trailing-newline-line
Trailing-newline-line

...可能不是您想要的。

如果您仅在代码中使用前导换行符,并且仅在IDE中运行它,那可能就可以了。一旦在终端中运行它或引入将与您的代码一起写入STDOUT的其他人的代码,您将看到如上所示的不良输出。


2
在交互式外壳程序中中断的程序也会发生类似的情况-如果打印了部分行(缺少其换行符),则外壳程序会迷惑光标所在的列,从而难以编辑下一个命令行。除非您将换行符添加到您的$PS1,否则将对常规程序产生刺激。
Toby Speight

17

由于获得高度评价的答案已经给出了为什么应首选尾随换行符的出色技术原因,因此我将从另一个角度进行探讨。

在我看来,以下内容使程序更具可读性:

  1. 高信噪比(又简单又不简单)
  2. 重要思想优先

从以上几点,我们可以认为尾随换行符更好。与消息相比,换行符在格式化“噪声”,消息应突出显示,因此应优先出现(语法突出显示也可以帮助您)。


19
是的,"ok\n""\nok"…… 要好得多
cmaster18年

@cmaster:让我想起了使用C语言中的Pascal-string API阅读过MacOS的知识,该API需要在所有字符串文字前加上一个神奇的转义码,例如"\pFoobar"
grawity

16

使用结尾的换行符可以简化以后的修改。

作为基于OP代码的(非常琐碎的)示例,假设您需要在“ Initializing”消息之前产生一些输出,并且该输出来自代码的不同逻辑部分,位于不同的源文件中。

当您运行第一个测试并发现“ Initializing”现在被添加到其他输出行的末尾时,您必须搜索代码以找到打印位置,然后希望将“ Initializing”更改为“ \ nInitializing” ”不会在不同情况下搞乱其他格式。

现在考虑如何处理您的新输出实际上是可选的事实,因此您对“ \ nInitializing”所做的更改有时会在输出开始时产生不必要的空行...

您是否设置一个全局(震惊的恐怖?!!)标记来说明是否存在任何先前的输出,并对其进行测试以使用可选的前导“ \ n”打印“正在初始化”,还是将“ \ n”与您以前的输出,而让以后的代码阅读者想知道为什么此“ Initializing” 没有所有其他输出消息那样的前导“ \ n”吗?

如果您始终输出尾随换行符,那么在您知道已经到达需要终止的行的末尾的时候,您就可以回避所有这些问题。请注意,在某些逻辑的末尾可能需要一个单独的puts(“ \ n”)语句,该逻辑逐段输出一行,但是重点是您在代码中最早知道需要的地方输出换行符。做到这一点,而不是其他地方。


1
如果每个独立的输出项都应该出现在其自己的行上,则尾随新行可以正常工作。但是,如果在实践中应合并多个项目,则事情将变得更加复杂。如果通过通用例程提供所有输出是可行的,则在最后一个字符为CR的情况下插入行尾清除符的操作,如果最后一个字符为换行符则为空,如果最后一个字符为换行,则为空还有其他事情,如果程序需要执行一些操作而不是输出一系列独立的行,则可能会有所帮助。
超级猫

7

为什么使用尾随换行符而不使用printf开头?

紧密匹配C规范。

C库定义线结束新行字符 '\n'

文本流是由组成的有序字符序列,每行包含零个或多个字符以及一个换行符。最后一行是否需要终止换行符是实现定义的。C11§7.21.22

将数据写为行的代码将与该库的概念匹配。

printf("Initializing"); // Write part of a line
printf("\nProcessing"); // Finish prior line & write part of a line
printf("\nExiting");    // Finish prior line & write an implementation-defined last line

printf("Initializing\n");//Write a line 
printf("Processing\n");  //Write a line
printf("Exiting");       //Write an implementation-defined last line

回复:最后一行需要以换行符结尾。我建议始终'\n'在输出中写一个final ,并容忍输入中的final 。


拼写检查

我的拼写检查器抱怨。也许你也是。

  v---------v Not a valid word
"\nProcessing"

 v--------v OK
"Processing\n");

我曾经做过改进,ispell.el以更好地应对。我承认更多的\t是问题所在,只需将字符串分成多个标记就可以避免,但这只是更通用的“忽略”工作的副作用,可以有选择地跳过HTML的非文本部分或MIME多部分主体,以及代码的非注释部分。我确实一直打算将其扩展到具有适当元数据(例如<p lang="de_AT">Content-Language: gd)的交换语言,但是从未获得Round Tuit。维护者直接拒绝了我的补丁。:-(
Toby Speight,

@TobySpeight这是一个圆头。期待尝试您的重新改进ispell.el
chux

4

有条件的情况下,换行符通常可以使编写代码变得更加容易,例如,

printf("Initializing");
if (jobName != null)
    printf(": %s", jobName);
init();
printf("\nProcessing");

(但正如其他地方所指出的那样,在执行任何需要占用大量CPU时间的步骤之前,您可能需要刷新输出缓冲区。)

因此,可以为两种方法都做一个很好的例子,但是我个人并不喜欢printf(),而是会使用自定义类来建立输出。


1
您能解释一下为什么这个版本比带有尾随换行符的版本更容易编写吗?在这个例子中,这对我来说并不明显。相反,我可以看到下一个输出添加到与相同的行时出现的问题"\nProcessing"
RaimundKrämer'18

像Raimund一样,我也可以看到像这样工作引起的问题。致电时,您需要考虑周围的印刷品printf。如果您想对整个“ Initializing”行进行条件处理,该怎么办?您必须在这种情况下包括“正在处理”行,才能知道是否应在换行符前添加前缀。如果前面还有另一个打印,并且您需要对“ Processing”行进行条件化,则还需要在该条件下包括下一个打印,以了解是否应在另一个换行符之前添加前缀,依此类推。
JoL

2
我同意这个原则,但是这个例子不是一个好例子。一个更相关的示例是使用代码,该代码应该每行输出一些项目。如果输出应该以换行符开头的标头开头,并且每行都应该以标头开头,则说起来容易(例如if ((addr & 0x0F)==0) printf("\n%08X:", addr);,无条件地在输出末尾添加换行符),而不是使用每行标题和尾随换行符的单独代码。
超级猫

1

领导换行不与其他库函数,特别是良好的工作puts()perror标准库中,而且你很可能会使用任何其他库。

如果要打印预写的行(常量或已格式化的行,例如使用sprintf()),则puts()是自然的(高效的)选择。但是,没有办法puts()结束上一行并写入未终止的行-它总是写入行终止符。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.