Perché usare le nuove righe finali anziché le prime con printf?


79

Ho sentito che dovresti evitare di guidare le nuove linee quando usi printf. In modo che al posto printf("\nHello World!")tuo dovresti usareprintf("Hello World!\n")

In questo esempio particolare sopra non ha senso, poiché l'output sarebbe diverso, ma considera questo:

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

rispetto a:

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

Non riesco a vedere alcun beneficio con le nuove righe finali, tranne per il fatto che sembra migliore. C'è qualche altro motivo?

MODIFICARE:

Mi rivolgerò ai voti stretti qui e ora. Non penso che questo appartenga allo overflow dello Stack, perché questa domanda riguarda principalmente il design. Direi anche che, sebbene possano essere opinioni in merito, la risposta di Kilian Foth e la risposta di cmaster dimostrano che ci sono davvero vantaggi molto oggettivi con un approccio.


5
Questa domanda è al confine tra "problemi con il codice" (che è fuori tema) e "progettazione concettuale del software" (che è su argomento). Potrebbe chiudersi, ma non prenderlo troppo duro. Penso che l'aggiunta di esempi concreti di codice sia stata comunque la scelta giusta.
Kilian Foth,

46
L'ultima riga si fonderebbe con il prompt dei comandi su Linux senza una nuova riga finale.
GrandmasterB,

4
Se "sembra migliore" e non ha alcun aspetto negativo, questa è una buona ragione per farlo, IMO. Scrivere un buon codice non è diverso dallo scrivere un buon romanzo o un buon documento tecnico: il diavolo è sempre nei dettagli.
alephzero,

5
Fare init()e process_data()stampare qualcosa da soli? Come ti aspetteresti che il risultato sembrasse se lo facessero?
Bergi,

9
\nè un terminatore di linea , non un separatore di linea . Ciò è dimostrato dal fatto che i file di testo, su UNIX, finiscono quasi sempre con \n.
Jonathon Reinhart,

Risposte:


222

Una buona quantità di I / O del terminale è bufferizzata , quindi terminando un messaggio con \ n si può essere certi che verrà visualizzato in modo tempestivo. Con un \ n iniziale, il messaggio può essere visualizzato o meno in una volta. Spesso, ciò significherebbe che ogni passaggio visualizza il messaggio di avanzamento del passaggio precedente , che non provoca confusione e perdite di tempo quando si tenta di comprendere il comportamento di un programma.


20
Ciò è particolarmente importante quando si utilizza printf per il debug di un programma che si arresta in modo anomalo. Mettere la nuova riga alla fine di una stampa significa che lo stdout sulla console viene scaricato a ogni stampa. (Si noti che quando lo stdout viene reindirizzato a un file, le librerie std di solito eseguono il buffering a blocchi anziché il buffering di linea, quindi questo rende il debug di printf un arresto piuttosto difficile anche con newline alla fine.)
Erik Eidt

25
@ErikEidt Nota che dovresti usare fprintf(STDERR, …)invece, che generalmente non è bufferato per l'output diagnostico.
Deduplicatore,

4
@Deduplicator La scrittura di messaggi diagnostici nel flusso di errori ha anche i suoi lati negativi: molti script presumono che un programma non sia riuscito se qualcosa viene scritto nel flusso di errori.
Voo

54
@Voo: direi che qualsiasi programma che presuppone che scrive a stderr indichi che un errore è di per sé errato. Il codice di uscita del processo è ciò che indica se non è riuscito. Se si è verificato un errore, l'output stderr spiegherà il perché . Se il processo è terminato correttamente (codice di uscita zero), l'output di stderr dovrebbe essere considerato output informativo per un utente umano, senza particolari semantiche analizzabili in macchina (ad esempio potrebbe contenere avvisi leggibili dall'uomo), mentre stdout è l'output effettivo di il programma, eventualmente adatto per ulteriori elaborazioni.
Daniel Pryden,

23
@Voo: quali programmi stai descrivendo? Non sono a conoscenza di alcun pacchetto software ampiamente utilizzato che si comporti come descritto. So che ci sono programmi che lo fanno, ma non è come se avessi appena inventato la convenzione che ho descritto sopra: è così che funziona la stragrande maggioranza dei programmi in un ambiente Unix o simile a Unix e, per quanto ne so, il modo in cui la stragrande maggioranza dei programmi ha sempre. Certamente non difenderei nessun programma per evitare di scrivere a stderr semplicemente perché alcuni script non lo gestiscono bene.
Daniel Pryden,

73

Sui sistemi POSIX (praticamente qualsiasi Linux, BSD, qualunque sistema basato su open source che puoi trovare), una linea è definita come una stringa di caratteri che termina con una nuova riga \n. Questo è l'assunto di base di tutti gli strumenti a riga di comando standard di costruire su, tra cui (ma non solo) wc, grep, sed, awk, e vim. Questo è anche il motivo per cui alcuni editor (come vim) aggiungono sempre \na alla fine di un file e perché gli standard precedenti di C richiedevano che le intestazioni terminassero con un \ncarattere.

Btw: Avere \nterminate le righe rende molto più semplice l'elaborazione del testo: sai per certo che hai una riga completa quando hai quel terminatore. E sai per certo che devi guardare più personaggi se non hai ancora incontrato quel terminatore.

Ovviamente, questo è sul lato dell'input dei programmi, ma l'output del programma viene molto spesso usato come input del programma. Pertanto, l'output dovrebbe attenersi alla convenzione per consentire l'input continuo in altri programmi.


25
Questo è uno dei dibattiti più antichi nell'ingegneria del software: è meglio usare newline (o, in un linguaggio di programmazione, un altro indicatore di "fine dell'istruzione" come un punto e virgola) come terminatori di riga o separatori di riga ? Entrambi gli approcci hanno i loro pro e contro. Il mondo di Windows si è basato principalmente sull'idea che la sequenza newline (che in genere è CR LF lì) è un separatore di riga , e quindi l'ultima riga in un file non deve finire con essa. Nel mondo Unix, tuttavia, una sequenza newline (LF) è un terminatore di linea e molti programmi sono costruiti attorno a questo presupposto.
Daniel Pryden,

33
POSIX definisce persino una riga come "Una sequenza di zero o più caratteri non di nuova riga più un carattere di fine riga che termina ".
pipe

6
Dato che come dice @pipe, è nelle specifiche POSIX, possiamo probabilmente chiamarlo de jure invece di fatto come suggerisce la risposta?
Baldrickk,

4
@Baldrickk Right. Ho aggiornato la mia risposta per essere più affermativa, ora.
cmaster

C fa anche questa convenzione per i file sorgente: un file sorgente non vuoto che non termina con una nuova riga produce un comportamento indefinito.
R ..

31

Oltre a ciò che altri hanno già detto, penso che ci sia una ragione molto più semplice: è lo standard. Ogni volta che qualcosa viene stampato su STDOUT, si presume quasi sempre che sia già su una nuova linea e quindi non sia necessario avviarne una nuova. Presuppone inoltre che la riga successiva da scrivere agirà allo stesso modo, quindi termina in modo utile avviando una nuova riga.

Se si generano linee di newline in primo piano interlacciate con linee di trailing-newline standard ", finirà per apparire così:

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

... che presumibilmente non è quello che vuoi.

Se usi solo le nuove linee guida nel tuo codice e lo esegui solo in un IDE, potrebbe andare bene. Non appena lo esegui in un terminale o introduci il codice di altre persone che scriverà su STDOUT insieme al tuo codice, vedrai output indesiderabili come sopra.


2
Qualcosa di simile accade con i programmi che sono interrotti in una shell interattiva: se viene stampata una linea parziale (manca la sua nuova riga), la shell viene confusa su quale colonna si trova il cursore, rendendo difficile modificare la riga di comando successiva. A meno che non si aggiunga una nuova riga principale alla propria $PS1, che sarebbe quindi irritante seguendo i programmi convenzionali.
Toby Speight,

17

Dato che le risposte molto votate hanno già fornito eccellenti ragioni tecniche per cui si dovrebbero preferire le nuove linee finali, mi approverò da un'altra prospettiva.

Secondo me, i seguenti rendono un programma più leggibile:

  1. un elevato rapporto segnale-rumore (alias semplice ma non più semplice)
  2. le idee importanti vengono prima di tutto

Dai punti di cui sopra, possiamo sostenere che le nuove righe finali sono migliori. Le nuove righe stanno formattando il "rumore" rispetto al messaggio, il messaggio dovrebbe risaltare e quindi dovrebbe venire prima (l'evidenziazione della sintassi può aiutare anche).


19
Sì, "ok\n"è molto meglio di "\nok"...
cmaster

@cmaster: mi ricorda di aver letto di MacOS usando le API di stringa Pascal in C, che richiedeva il prefisso di tutti i letterali di stringhe con un codice di escape magico come "\pFoobar".
Grawity il

16

L'uso di nuove righe finali semplifica le modifiche successive.

Come esempio (molto banale) basato sul codice dell'OP, supponiamo che sia necessario produrre un output prima del messaggio "Inizializzazione" e che l'output provenga da una diversa parte logica del codice, in un diverso file di origine.

Quando esegui il primo test e scopri che "Inizializzazione" è ora aggiunto alla fine di una riga di qualche altro output, devi cercare attraverso il codice per trovare dove è stato stampato, e quindi sperare di cambiare "Inizializzazione" in "\ nInizializzazione "non rovina il formato di qualcos'altro, in circostanze diverse.

Ora considera come gestire il fatto che il tuo nuovo output sia effettivamente facoltativo, quindi la tua modifica a "\ nInizializzazione" a volte produce una riga vuota indesiderata all'inizio dell'output ...

Impostate un flag globale ( shock horror ?? !!! ) che dice se c'era un output precedente e lo testate per stampare "Inizializzazione" con un carattere iniziale facoltativo "\ n", oppure producete "\ n" insieme a l'output precedente e lasciare che i futuri lettori di codice si chiedano perché questa "inizializzazione" non ha un "\ n" iniziale come tutti gli altri messaggi di output?

Se si producono costantemente nuove righe finali, nel punto in cui si sa di aver raggiunto la fine della riga che deve essere terminata, si evitano tutti questi problemi. Si noti che ciò potrebbe richiedere un'istruzione put ("\ n") separata alla fine di una logica che emette una riga pezzo per pezzo, ma il punto è che si genera la nuova riga nella prima posizione nel codice in cui si sa che è necessario fallo, non altrove.


1
Se si suppone che ogni elemento di output indipendente appaia sulla propria riga, le nuove righe finali possono funzionare correttamente. Se più elementi dovrebbero essere consolidati quando possibile, le cose diventano più complicate. Se è pratico alimentare tutto l'output attraverso una routine comune, un'operazione per inserire una riga di fine riga se l'ultimo carattere era un CR, nulla se l'ultimo carattere era una nuova riga e una nuova riga se l'ultimo carattere era qualcos'altro, può essere utile se i programmi devono fare qualcosa di diverso dall'uscita di una sequenza di linee indipendenti.
supercat

7

Perché usare le nuove righe finali anziché le prime con printf?

Abbina da vicino le specifiche C

La libreria C definisce una riga che termina con un carattere di nuova riga '\n' .

Un flusso di testo è una sequenza ordinata di caratteri composta da righe , ciascuna riga composta da zero o più caratteri più un carattere di nuova riga che termina. Se l'ultima riga richiede un carattere di nuova riga che termina è definita dall'implementazione. C11 §7.21.2 2

Il codice che scrive i dati come linee corrisponderà quindi al concetto di libreria.

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

Ri: l' ultima riga richiede un carattere di nuova riga che termina . Consiglio di scrivere sempre un finale '\n'in uscita e tollerarne l'assenza in ingresso.


Controllo ortografico

Il mio correttore ortografico si lamenta. Forse anche a te.

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

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

Una volta ho migliorato ispell.elper farcela meglio. Ammetto che era più spesso \til problema, e poteva essere evitato semplicemente spezzando la stringa in più token, ma era solo un effetto collaterale del lavoro "ignora" più generale, per saltare selettivamente parti non HTML di testo o corpi multipart MIME e parti di codice senza commenti. Intendevo sempre estenderlo al cambio di lingue in cui vi sono metadati appropriati (ad es. <p lang="de_AT">O Content-Language: gd), ma non ho mai ricevuto una tuta rotonda. E il manutentore ha rifiutato completamente la mia patch. :-(
Toby Speight,

@TobySpeight Ecco un toit tondo . Non vedo l'ora di provare il tuo nuovo miglioramento ispell.el.
chux,

4

Le nuove righe principali possono spesso semplificare la scrittura del codice in presenza di condizionali, ad esempio,

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

(Ma come è stato notato altrove, potrebbe essere necessario svuotare il buffer di output prima di eseguire qualsiasi operazione che richieda molto tempo della CPU.)

Quindi un buon caso può essere fatto per entrambi i modi di farlo, tuttavia personalmente non mi piace printf () e userei una classe personalizzata per costruire l'output.


1
Potresti spiegare perché questa versione è più facile da scrivere rispetto a quella con nuove righe finali? In questo esempio non mi è chiaro. Invece ho potuto vedere i problemi che sorgono con l'output successivo che viene aggiunto alla stessa riga di "\nProcessing".
Raimund Krämer,

Come Raimund, posso anche vedere problemi derivanti dal lavorare in questo modo. È necessario considerare le stampe circostanti quando si chiama printf. E se volessi condizionare l'intera linea "Inizializzazione"? Dovresti includere la riga "Elaborazione" in quella condizione per sapere se dovresti aggiungere un prefisso o meno. Se è in arrivo un'altra stampa e devi condizionare la riga "Elaborazione", dovrai anche includere la stampa successiva in quella condizione per sapere se devi aggiungere un prefisso con un'altra riga e così via per ogni stampa.
JoL

2
Sono d'accordo con il principio, ma l'esempio non è valido. Un esempio più pertinente sarebbe con il codice che dovrebbe generare un numero di elementi per riga. Se l'output dovrebbe iniziare con un'intestazione che termina con una nuova riga e ciascuna riga dovrebbe iniziare con un'intestazione, potrebbe essere più facile dirlo, ad esempio if ((addr & 0x0F)==0) printf("\n%08X:", addr);e aggiungere incondizionatamente una nuova riga all'output alla fine, piuttosto che utilizzare codice separato per l'intestazione di ogni riga e la nuova riga finale.
supercat

1

Le nuove linee principali non funzionano bene con le altre funzioni della libreria, in particolare puts()e perrornella Libreria standard, ma anche con qualsiasi altra libreria che probabilmente utilizzerai.

Se vuoi stampare una riga pre-scritta (una costante o una già formattata - ad es. Con sprintf()), allora puts()è la scelta naturale (ed efficiente). Tuttavia, non è possibile puts()terminare la riga precedente e scrivere una riga non terminata: scrive sempre il terminatore di riga.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.