Warum nachgestellte Zeilenumbrüche verwenden, anstatt mit printf zu beginnen?

79

Ich habe gehört, dass Sie bei der Verwendung Zeilenumbrüche vermeiden sollten printf. Damit printf("\nHello World!")solltest du stattdessen verwendenprintf("Hello World!\n")

In diesem speziellen obigen Beispiel ist dies nicht sinnvoll, da die Ausgabe unterschiedlich wäre. Beachten Sie jedoch Folgendes:

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

verglichen mit:

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

Ich kann keinen Nutzen mit nachgestellten Zeilenumbrüchen sehen, außer dass es besser aussieht. Gibt es noch einen anderen Grund?

BEARBEITEN:

Ich werde hier und jetzt auf die engen Abstimmungen eingehen. Ich glaube nicht, dass dies zum Stapelüberlauf gehört, da es sich bei dieser Frage hauptsächlich um Design handelt. Ich würde auch sagen , dass , obwohl es Meinungen in dieser Angelegenheit sein kann, Kilian Foth Antwort und cMaster Antwort beweist , dass es in der Tat sehr objektive Vorteile mit einem Ansatz.

klutt
quelle
5
Diese Frage bewegt sich an der Grenze zwischen "Probleme mit Code" (das nicht zum Thema gehört) und "Konzeption von Software" (das zum Thema gehört). Es kann geschlossen werden, aber nehmen Sie es nicht zu schwer. Ich denke, dass das Hinzufügen konkreter Codebeispiele dennoch die richtige Wahl war.
Kilian Foth
46
Die letzte Zeile wird mit der Eingabeaufforderung unter Linux zusammengeführt, ohne dass eine neue Zeile nachgestellt wird.
GroßmeisterB
4
Wenn es "besser aussieht" und keinen Nachteil hat, ist das ein guter Grund, IMO. Das Schreiben von gutem Code unterscheidet sich nicht vom Schreiben eines guten Romans oder eines guten technischen Papiers - der Teufel steckt immer im Detail.
Alephzero
5
Alles selber machen init()und process_data()drucken? Wie würde das Ergebnis dann aussehen?
Bergi
9
\nist eine Linie Terminator , keine Linie Separator . Dies wird durch die Tatsache belegt, dass Textdateien unter UNIX fast immer auf enden \n.
Jonathon Reinhart

Antworten:

222

Eine angemessene Anzahl von Terminal-E / A -Vorgängen wird zeilenweise gepuffert. Wenn Sie also eine Nachricht mit \ n beenden, können Sie sicher sein, dass sie rechtzeitig angezeigt wird. Mit einem führenden \ n kann die Nachricht sofort angezeigt werden oder nicht. Dies bedeutet häufig, dass in jedem Schritt die Fortschrittsmeldung des vorherigen Schritts angezeigt wird , was zu erheblicher Verwirrung und Zeitverschwendung führt, wenn Sie versuchen, das Verhalten eines Programms zu verstehen.

Kilian Foth
quelle
20
Dies ist besonders wichtig, wenn printf zum Debuggen eines abstürzenden Programms verwendet wird. Wenn Sie den Zeilenumbruch an das Ende eines Ausdrucks setzen, wird bei jedem Ausdruck die Ausgabe an die Konsole gelöscht. (Beachten Sie, dass wenn stdout in eine Datei umgeleitet wird, die std-Bibliotheken normalerweise Blockpufferung statt Zeilenpufferung ausführen, so dass das Debuggen von printf selbst mit Zeilenumbruch am Ende einen ziemlich schweren Absturz verursacht.)
Erik Eidt,
25
@ErikEidt Beachten Sie, dass Sie fprintf(STDERR, …)stattdessen verwenden sollten, was für die Diagnoseausgabe im Allgemeinen überhaupt nicht gepuffert wird.
Deduplizierer
4
@Deduplicator Das Schreiben von Diagnosemeldungen in den Fehlerstrom hat auch seine Nachteile - viele Skripte gehen davon aus, dass ein Programm fehlgeschlagen ist, wenn etwas in den Fehlerstrom geschrieben wurde.
Voo
54
@ Voo: Ich würde argumentieren, dass jedes Programm, das Schreibvorgänge in stderr annimmt, einen Fehler anzeigt, der selbst falsch ist. Der Beendigungscode des Prozesses gibt an, ob der Vorgang fehlgeschlagen ist oder nicht. Wenn es ein Fehler war, wird die stderr-Ausgabe erklären, warum . Wenn der Prozess erfolgreich beendet wurde (Beendigungscode Null), sollte die Ausgabe von stderr als Informationsausgabe für einen menschlichen Benutzer betrachtet werden, ohne dass eine bestimmte maschinenparsbare Semantik vorliegt (sie kann beispielsweise für den Menschen lesbare Warnungen enthalten), während stdout die tatsächliche Ausgabe von ist das Programm, möglicherweise zur Weiterverarbeitung geeignet.
Daniel Pryden
23
@ Voo: Welche Programme beschreiben Sie? Mir ist kein weit verbreitetes Softwarepaket bekannt, das sich so verhält, wie Sie es beschreiben. Ich weiß, dass es Programme gibt, die dies tun, aber es ist nicht so, als hätte ich die oben beschriebene Konvention erfunden: So funktioniert die überwiegende Mehrheit der Programme in einer Unix- oder Unix-ähnlichen Umgebung, und meines Wissens nach auch die Die überwiegende Mehrheit der Programme hat dies immer getan. Ich würde auf keinen Fall dafür plädieren, dass kein Programm in stderr schreibt, nur weil einige Skripte nicht gut damit umgehen.
Daniel Pryden
73

Auf POSIX-Systemen (im Grunde jedes Linux- oder BSD-System, unabhängig davon, welches Open-Source-System Sie finden) wird eine Zeile als Zeichenfolge definiert, die durch einen Zeilenumbruch abgeschlossen wird \n. Dies ist die grundlegende Annahme , alle Standard - Kommandozeilen - Tools bauen auf, einschließlich (aber nicht beschränkt auf) wc, grep, sed, awk, und vim. Dies ist auch der Grund, warum einige Editoren (wie vim) immer ein \nam Ende einer Datei einfügen und frühere Standards von C forderten, dass Header mit einem \nZeichen enden .

Übrigens: Durch das \nBeenden von Zeilen wird die Verarbeitung von Text erheblich vereinfacht: Sie wissen mit Sicherheit, dass Sie mit diesem Abschlusszeichen eine vollständige Zeile haben. Und Sie wissen sicher, dass Sie sich mehr Charaktere ansehen müssen, wenn Sie diesen Terminator noch nicht kennen.

Natürlich ist dies auf der Eingabeseite von Programmen, aber die Programmausgabe wird sehr oft wieder als Programmeingabe verwendet. Ihre Ausgabe sollte sich also an die Konvention halten, um eine nahtlose Eingabe in andere Programme zu ermöglichen.

cmaster
quelle
25
Dies ist eine der ältesten Debatten in der Softwareentwicklung: Ist es besser, Zeilenumbrüche (oder in einer Programmiersprache einen anderen "End of Statement" -Marker wie ein Semikolon) als Zeilenabschluss oder Zeilentrennzeichen zu verwenden ? Beide Ansätze haben Vor- und Nachteile. Die Windows-Welt hat sich größtenteils auf die Idee geeinigt, dass die Zeilenumbruchsequenz (in der Regel CR LF) ein Zeilentrenner ist und die letzte Zeile in einer Datei nicht damit enden muss. In der Unix - Welt, ist aber eine Neuzeilensequenz (LF) eine Linie Terminator und sind viele Programme um diese Annahme gebaut.
Daniel Pryden
33
POSIX definiert eine Zeile sogar als "Eine Folge von null oder mehr Nicht-Zeilenumbruchzeichen plus einem abschließenden Zeilenumbruchzeichen ".
Pipe
6
Angesichts der Tatsache, dass es sich, wie @pipe sagt, um die POSIX-Spezifikation handelt, können wir es wahrscheinlich als de jure und nicht als de facto bezeichnen, wie die Antwort nahelegt.
Baldrickk
4
@ Baldrickk Richtig. Ich habe meine Antwort aktualisiert, um jetzt bejahender zu sein.
cmaster
C macht diese Konvention auch für Quelldateien: Eine nicht leere Quelldatei, die nicht mit einem Zeilenumbruch endet, erzeugt ein undefiniertes Verhalten.
R ..
31

Zusätzlich zu dem, was andere erwähnt haben, gibt es meines Erachtens einen viel einfacheren Grund: Es ist der Standard. Wenn etwas auf STDOUT gedruckt wird, wird fast immer davon ausgegangen, dass es sich bereits in einer neuen Zeile befindet und daher keine neue Zeile beginnen muss. Es wird auch davon ausgegangen, dass die nächste zu schreibende Zeile auf die gleiche Art und Weise verhält, sodass sie hilfreich endet, wenn eine neue Zeile beginnt.

Wenn Sie Zeilen mit Zeilenvorschub und Zeilen mit Zeilenvorschub "verschachtelt" mit Standardzeilen mit Zeilenvorschub ausgeben, sieht dies am Ende so aus:

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

... was vermutlich nicht das ist, was du willst.

Wenn Sie in Ihrem Code nur führende Zeilenumbrüche verwenden und ihn nur in einer IDE ausführen, ist dies möglicherweise in Ordnung. Sobald Sie es in einem Terminal ausführen oder den Code anderer Personen eingeben, der neben Ihrem Code in STDOUT geschrieben wird, werden unerwünschte Ausgaben wie oben angezeigt.

Der Typ mit dem Hut
quelle
2
Ähnliches gilt für Programme, die in einer interaktiven Shell unterbrochen sind. Wenn eine Teilzeile gedruckt wird (ohne Zeilenumbruch), ist die Shell verwirrt, in welcher Spalte sich der Cursor befindet, und es ist schwierig, die nächste Befehlszeile zu bearbeiten. Es sei denn, Sie fügen Ihrem Text einen führenden Zeilenumbruch hinzu $PS1, der bei herkömmlichen Programmen ärgerlich wäre.
Toby Speight
17

Da die hochgestimmten Antworten bereits hervorragende technische Gründe dafür geliefert haben, warum Zeilenumbrüche vorzuziehen sind, werde ich sie aus einem anderen Blickwinkel betrachten.

Meiner Meinung nach verbessern die folgenden Punkte die Lesbarkeit eines Programms:

  1. ein hohes Signal-Rausch-Verhältnis (auch bekannt als einfach, aber nicht einfacher)
  2. Wichtige Ideen stehen an erster Stelle

Aus den obigen Punkten können wir argumentieren, dass nachgestellte Zeilen besser sind. Zeilenumbrüche formatieren "Noise" im Vergleich zur Nachricht, die Nachricht sollte hervorstechen und daher an erster Stelle stehen (Syntax-Hervorhebung kann ebenfalls hilfreich sein).

Alex Vong
quelle
19
Ja, "ok\n"ist viel besser als "\nok"...
cmaster
@cmaster: Erinnert mich daran, dass ich über MacOS unter Verwendung von Pascal-String-APIs in C gelesen habe, bei denen allen String-Literalen ein magischer Escape-Code wie vorangestellt werden musste "\pFoobar".
Grawity
16

Die Verwendung von nachgestellten Zeilenumbrüchen vereinfacht spätere Änderungen.

Angenommen, Sie müssen als (sehr einfaches) Beispiel, das auf dem OP-Code basiert, vor der Meldung "Initializing" eine Ausgabe erstellen, die von einem anderen logischen Teil des Codes in einer anderen Quelldatei stammt.

Wenn Sie den ersten Test ausführen und feststellen, dass "Initialisierung" jetzt am Ende einer Zeile einer anderen Ausgabe angehängt ist, müssen Sie den Code durchsuchen, um herauszufinden, wo er gedruckt wurde, und dann hoffen, dass "Initialisierung" in "\ nInitialisierung" geändert wird "vermasselt nicht das Format von etwas anderem unter anderen Umständen.

Überlegen Sie nun, wie Sie mit der Tatsache umgehen sollen, dass Ihre neue Ausgabe tatsächlich optional ist, sodass Ihre Änderung an "\ nInitialisieren" manchmal zu einer unerwünschten Leerzeile am Anfang der Ausgabe führt ...

Setzen Sie ein globales Flag ( Schock Horror ?? !!! ), das angibt, ob zuvor eine Ausgabe erfolgt ist, und testen Sie es, um "Initializing" mit einem optionalen führenden "\ n" zu drucken, oder geben Sie das "\ n" zusammen mit aus Ihre frühere Ausgabe und lassen zukünftige Codeleser sich fragen, warum diese "Initialisierung" kein führendes "\ n" hat wie alle anderen Ausgabenachrichten?

Wenn Sie konsequent nachgestellte Zeilenumbrüche ausgeben und wissen, dass Sie das Ende der zu terminierenden Zeile erreicht haben, umgehen Sie alle diese Probleme. Beachten Sie, dass am Ende einer Logik, die eine Zeile Stück für Stück ausgibt, möglicherweise eine separate Anweisung puts ("\ n") erforderlich ist. Der Punkt ist jedoch, dass Sie die neue Zeile an der frühesten Stelle im Code ausgeben, an der Sie dies wissen Mach es, nicht woanders.

Alephzero
quelle
1
Wenn jedes unabhängige Ausgabeelement in einer eigenen Zeile angezeigt werden soll, funktionieren nachfolgende neue Zeilen möglicherweise einwandfrei. Wenn jedoch mehrere Elemente konsolidiert werden sollen, wird es komplizierter. Wenn es praktisch ist, die gesamte Ausgabe durch eine gemeinsame Routine zu führen, eine Operation zum Einfügen eines Zeilenumbruchs, wenn das letzte Zeichen ein CR war, nichts, wenn das letzte Zeichen ein Zeilenumbruch war, und eine neue Zeile, wenn das letzte Zeichen ein Zeilenumbruch war war alles andere, kann hilfreich sein, wenn Programme etwas anderes tun müssen als eine Folge von unabhängigen Zeilen auszugeben.
Supercat
7

Warum nachgestellte Zeilenumbrüche verwenden, anstatt mit printf zu beginnen?

Übereinstimmung mit C-Spezifikation

Die C - Bibliothek definiert eine Linie als endend mit einer neuen Zeilenwechselzeichen '\n' .

Ein Textstrom ist eine geordnete Folge von Zeichen, die zu Zeilen zusammengefasst sind , wobei jede Zeile aus null oder mehr Zeichen plus einem abschließenden Zeichen für eine neue Zeile besteht. Ob die letzte Zeile ein abschließendes Zeichen für eine neue Zeile erfordert, ist implementierungsspezifisch. C11 §7.21.2 2

Code, der Daten als Zeilen schreibt , entspricht dann dem Konzept der Bibliothek.

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

Betreff: Letzte Zeile erfordert ein abschließendes Zeichen für eine neue Zeile . Ich würde empfehlen, immer ein Finale '\n'für die Ausgabe zu schreiben und dessen Fehlen bei der Eingabe zu tolerieren.


Rechtschreibprüfung

Meine Rechtschreibprüfung beschwert sich. Vielleicht auch.

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

 v--------v OK
"Processing\n");
chux
quelle
Ich habe mich einmal verbessert ispell.el, um damit besser fertig zu werden. Ich gebe zu, es war häufiger \tdas Problem, und es konnte einfach durch Aufteilen der Zeichenfolge in mehrere Token vermieden werden, aber es war nur ein Nebeneffekt der allgemeineren "Ignorier" -Arbeit, Nicht-Text-Teile von HTML selektiv zu überspringen oder MIME-Body mit mehreren Teilen und kommentarlose Codeteile. Ich wollte es immer auf das Wechseln der Sprache ausweiten, wenn geeignete Metadaten (z. B. <p lang="de_AT">oder Content-Language: gd) vorhanden sind, habe aber nie einen Round Tuit erhalten. Und der Betreuer lehnte meinen Patch sofort ab. :-(
Toby Speight
@TobySpeight Hier ist eine runde Sache . Freuen Sie sich auf eine Verbesserung ispell.el.
chux
4

Führende Zeilenumbrüche erleichtern häufig das Schreiben des Codes, wenn Bedingungen vorliegen, z. B.

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

(Wie bereits an anderer Stelle erwähnt, müssen Sie möglicherweise den Ausgabepuffer leeren, bevor Sie Schritte ausführen, die viel CPU-Zeit in Anspruch nehmen.)

Daher kann für beide Methoden ein guter Fall gemacht werden, allerdings mag ich printf () persönlich nicht und würde eine benutzerdefinierte Klasse verwenden, um die Ausgabe aufzubauen.

Ian
quelle
1
Können Sie erklären, warum diese Version einfacher zu schreiben ist als eine mit nachgestellten Zeilenumbrüchen? In diesem Beispiel ist es mir nicht klar. Stattdessen konnte ich Probleme beim Hinzufügen der nächsten Ausgabe zur gleichen Zeile wie sehen "\nProcessing".
Raimund Krämer
Wie Raimund sehe ich auch Probleme, wenn ich so arbeite. Sie müssen Umgebungsdrucke berücksichtigen, wenn Sie anrufen printf. Was wäre, wenn Sie die gesamte Zeile "Initializing" konditionieren wollten? Sie müssten die Zeile "Procesing" in diese Bedingung aufnehmen, um zu wissen, ob Sie eine neue Zeile voranstellen sollten oder nicht. Wenn ein weiterer Druck voraus ist und Sie die Zeile "Verarbeitung" konditionieren müssen, müssen Sie auch den nächsten Druck in diesen Zustand einbeziehen, um zu wissen, ob Sie für jeden Druck einen anderen Zeilenumbruch vorschreiben sollten, und so weiter.
JoL
2
Ich stimme dem Prinzip zu, aber das Beispiel ist nicht gut. Ein relevanteres Beispiel wäre Code, der eine bestimmte Anzahl von Elementen pro Zeile ausgeben soll. Wenn die Ausgabe mit einer Überschrift beginnen soll, die mit einer neuen Zeile endet, und jede Zeile mit einer Überschrift beginnen soll, ist es möglicherweise einfacher, z. B. if ((addr & 0x0F)==0) printf("\n%08X:", addr);eine neue Zeile am Ende der Ausgabe zu sagen und sie bedingungslos hinzuzufügen, als sie zu verwenden Separater Code für die Kopfzeile jeder Zeile und die nachfolgende Zeile.
Supercat
1

Führende Zeilenumbrüche funktionieren nicht gut mit anderen Bibliotheksfunktionen, insbesondere nicht puts() und perrorin der Standard - Bibliothek, aber auch jede andere Bibliothek , die Sie wahrscheinlich zu verwenden sind.

Wenn Sie eine vorab geschriebene Zeile drucken möchten (entweder eine Konstante oder eine bereits formatierte - z. B. mit sprintf()), puts()ist dies die natürliche (und effiziente) Wahl. Es gibt jedoch keine Möglichkeit puts(), die vorherige Zeile zu beenden und eine nicht abgeschlossene Zeile zu schreiben - es wird immer der Zeilenabschluss geschrieben.

Toby Speight
quelle