听说使用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的回答证明了一种方法的确确实有非常客观的好处。


评论

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

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

如果它“看起来更好”并且没有不利之处,那就是这样做的充分理由,IMO。编写好的代码与编写好的小说或好的技术论文没什么两样-魔鬼总是在细节中。

init()和process_data()是否自己打印任何内容?如果期望的话,您希望结果如何?

\ n是行终止符,而不是行分隔符。事实证明,在UNIX上,文本文件几乎总是以\ n结尾。

#1 楼

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

评论


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

– Erik Eidt
18-11-19在14:33



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

–重复数据删除器
18年11月19日在16:29

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

– Voo
18-11-19在16:32



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

–丹尼尔·普赖登(Daniel Pryden)
18年11月19日在16:58

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

–丹尼尔·普赖登(Daniel Pryden)
18年11月19日在18:05



#2 楼

在POSIX系统(基本上是任何linux,BSD,无论您可以找到基于开源的系统)上,一行都定义为由换行符\n终止的字符串。这是所有标准命令行工具所基于的基本假设,包括(但不限于)wcgrepsedawkvim。这也是为什么某些编辑器(例如vim)总是在文件末尾添加\n的原因,也是为什么早期的C标准要求标头以\n字符结尾的原因。
Btw:具有\n终止行使文本处理变得更加容易:您肯定知道在拥有该终止符后已经有了完整的行。而且您可以肯定知道,如果您还没有遇到该终止符,则需要查看更多的字符。

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

评论


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

–丹尼尔·普赖登(Daniel Pryden)
18-11-19在13:54

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

–烟斗
18-11-19在14:52



就像@pipe所说的那样,它在POSIX规范中,我们可能可以将其称为法律上的,而不是答案所暗示的事实上的?

–巴尔德里克
18年11月19日在16:11

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

–cmaster-恢复莫妮卡
18年11月19日在16:18

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

–R .. GitHub停止帮助ICE
18年11月21日在18:26

#3 楼

除了其他人提到的内容外,我觉得还有一个更简单的原因:这是标准。每当将任何内容打印到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以及您的代码,您将看到上面不想要的输出。

评论


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

– Toby Speight
18-11-21在23:16



#4 楼

由于投票率很高的答案已经给出了为什么应首选尾随换行符的绝妙技术原因,因此我将从另一个角度进行探讨。

我认为,以下内容使程序更具可读性:


信噪比高(又名简单但又不简单)
重要的思想优先出现

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

评论


是的,“ ok \ n”比“ \ nok”好得多...

–cmaster-恢复莫妮卡
18年11月19日在22:17



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

–user1686
18年11月23日在9:46

#5 楼

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

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

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

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

是否设置了全局(震惊)?说是否有前面的输出,并测试它是否以可选的前导“ \ n”打印“正在初始化”,还是您将“ \ n”与之前的输出一起输出并离开未来的代码阅读者想知道为什么“初始化”不像所有其他输出消息那样带有前导“ \ n”?到达需要终止的行的末尾,您回避所有这些问题。注意,在某些逻辑的末尾可能需要一个单独的puts(“ \ n”)语句,该逻辑逐段输出一条线,但是重点是您在代码中最早知道需要的地方输出换行符。做到这一点,而不是其他任何地方。

评论


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

–超级猫
18年11月20日在20:40

#6 楼


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


紧密匹配C规范。

C库将一行定义为以new结尾行字符'\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',并容忍输入中不存在。


拼写检查

我的拼写检查器抱怨。也许您也这样做。

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

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


评论


我曾经做过改进ispell.el来更好地解决这一问题。我承认更多的是\ t问题,可以通过将字符串分成多个标记来避免,但是这只是更通用的“忽略”工作的副作用,可以有选择地跳过非文本部分HTML或MIME多部分主体,以及代码的非注释部分。我确实一直打算将其扩展到具有适当的元数据的切换语言(例如

或Content-Language:gd),但是从来没有获得Round Tuit。维护者彻底拒绝了我的补丁。 :-(

– Toby Speight
18年11月21日在23:28



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

–chux-恢复莫妮卡
18年11月21日在23:33

#7 楼

在有条件的情况下,例如,

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


(如果已在其他地方提到过,您可能需要刷新输出)缓冲区,然后再执行任何占用大量CPU时间的步骤。)

因此可以很好地说明这两种方法,但是我个人并不喜欢printf(),而是会使用自定义类建立输出。

评论


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

– RaimundKrämer
18年11月21日在15:18

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

– JoL
18年11月21日在16:20

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

–超级猫
18年11月23日在20:23

#8 楼

前导换行符不能与其他库函数很好地配合使用,特别是标准库中的puts()perror,但也可能与您可能使用的任何其他库一起使用。
行(一个常数,或者已经格式化的常数-例如,使用sprintf()),那么puts()是自然的(有效的)选择。但是,puts()无法结束前一行并写入未终止的行-它总是写入行终止符。