2015-06-17 15 views

risposta

70

non ho idea di quello che l'opzione [] fa in cout

Questo non è in realtà un'opzione cout, ciò che sta accadendo è che "\n" è un string literal. Una stringa letterale ha il tipo array di n const char, il [] è semplicemente un indice in un array di caratteri che in questo caso contiene:

\n\0 

nota \0 viene aggiunto a tutti i letterali stringa.

I == risultati operatore in entrambi vero o falsa, quindi l'indice sarà:

  • 0 se falsa, se a non è uguale N conseguente \n
  • 1 se vero, se a è uguale a N risultante in \0

Questo è piuttosto criptico e avrebbe potuto essere sostituito con un semplice if.

Per riferimento lo standard C++ 14 (Leggerezza confermato il progetto corrisponde allo standard attuale) con il progetto più vicina è N3936 nella sezione 2.14.5 stringhe letterali [lex.string] dice (sottolineatura mia):

stringa letterale ha tipo “vettore di n const char”, dove n è la dimensione della stringa come definito di seguito, ed ha durata di conservazione statica (3.7).

e:

Dopo ogni concatenazione necessaria, in fase di traduzione 7 (2.2), '\ 0' viene aggiunto a ogni stringa letterale modo che i programmi che eseguono la scansione di una stringa può trovare la sua fine.

sezione 4.5[conv.prom] dice:

Un prvalue di tipo bool può essere convertito in un prvalue di tipo int, con falso diventare zero e diventare uno vero.

Scrivi carattere null per un flusso di testo

L'affermazione è stata fatta che scrivere un carattere null (\0) per un flusso di testo è un comportamento indefinito.

Per quanto posso dire che questo è un ragionevole conclusione, cout è definito in termini di flusso di C, come si può vedere dalla 27.4.2[narrow.stream.objects] che dice:

Il l'oggetto cout controlla l'output in un buffer di flusso associato allo stdout dell'oggetto, dichiarato in < cstdio> (27.9.2).

e il progetto di norma C11 nella sezione 7.21.2Streams dice:

[...] I dati letti in da un flusso di testo sarà necessariamente confrontare uguali ai dati che sono stati in precedenza scritti fuori a quel flusso solo se: i dati consistono solo nella stampa dei caratteri e nella scheda orizzontale dei caratteri di controllo e nella nuova riga;

e caratteri di stampa sono coperti nella gestione 7.4caratteri < ctype.h>:

[...] il carattere di controllo termine si riferisce a un membro di un locale-specifica set di caratteri che non vengono stampati caratteri.199) Tutte le lettere e le cifre stampano caratteri.

alla nota 199 dire:

In un'implementazione che utilizza gli USA set di caratteri ASCII a sette bit, i caratteri di stampa sono quelli i cui valori si trovano da 0x20 (spazio) attraverso 0x7E (tilde) ; i caratteri di controllo sono quelli i cui valori si trovano da 0 (NUL) a 0x1F (Stati Uniti) e il carattere 0x7F (CANC).

e, infine, possiamo vedere che il risultato di invio di un carattere null non è specificato e possiamo vedere questo è un comportamento indefinito dalla sezione 4 conformità che dice:

[...] Comportamento indefinito altrimenti indicato nella presente norma internazionale dalle parole '' comportamento non definito '' o dalla omissione di qualsiasi definizione esplicita di comportamento. [...]

Possiamo anche guardare al C99 rationale che dice:

Il set di caratteri richiesto per essere conservati nell'I/O del flusso di testo sono quelli necessari per scrivere i programmi C ; l'intento è che lo Standard dovrebbe consentire a un traduttore C di essere scritto in modo massimamente portatile. I caratteri di controllo come backspace non sono richiesti per questo scopo, pertanto la loro gestione di nei flussi di testo non è obbligatoria.

+0

@LightnessRacesinOrbit aggiunta bozza referenziata –

+0

Hmm meglio, anche se stai ancora citando qualcosa che non è uno standard :(Se si tratta di un FDIS allora potresti fingere di aver tirato il testo dallo standard internazionale immediatamente successivo (che è per definizione identico nel contenuto), ma per il resto non penso che dovresti usarlo. Se aiuta, posso confermare che C++ 14 contiene questa dicitura così com'è in sezioni con la stessa numerazione. [[edit: infatti, n3936 ** è ** il C++ 14 FDIS! quindi penso che puoi semplicemente aggiornare l'etichetta per citare C++ 14] _ –

+3

Nota, ho aggiunto questa risposta perché al momento anche se ci sono state diverse risposte, stranamente, nessuno ha spiegato che cosa una stringa era letterale e perché era valido indicizzarlo, una volta che questo è chiaro il resto segue: –

39
cout<<"\n"[a==N]; 

non ho idea di quello che l'opzione [] fa in cout

In C++ operator Precedence table, operator [] si lega più stretto di operator <<, in modo che il codice è equivalente a:

cout << ("\n"[a==N]); // or cout.operator <<("\n"[a==N]); 

O in altre parole, operator [] non fa nulla direttamente con cout. Viene utilizzato solo per l'indicizzazione di stringhe letterali "\n"

Ad esempio for(int i = 0; i < 3; ++i) std::cout << "abcdef"[i] << std::endl; stamperà i caratteri a, bec da righe consecutive sullo schermo.


Poiché string literals in C++ sono sempre terminato con carattere nullo ('\0', L'\0', char16_t(), ecc), una stringa letterale "\n" è un const char[2] tiene i caratteri '\n' e '\0'

In layout della memoria questo assomiglia a:

+--------+--------+ 
| '\n' | '\0' | 
+--------+--------+ 
0  1   <-- Offset 
false true  <-- Result of condition (a == n) 
a != n a == n  <-- Case 

Quindi se a == N è true (promosso a 1), l'espressione "\n"[a == N] produce '\0' e '\n' se il risultato è falso.

È funzionalmente simile (non uguale) a:

char anonymous[] = "\n"; 
int index; 
if (a == N) index = 1; 
else index = 0; 
cout << anonymous[index]; 

valueOf "\n"[a==N] è '\n' o '\0'

typeof "\n"[a==N] è const char


Se l'intenzione è quella di stampare nulla (Che potrebbe essere diverso dalla stampa '\0' dipendente su piattaforma e finalità), preferiscono la seguente riga di codice:

if(a != N) cout << '\n'; 

Anche se la vostra intenzione è quella di scrivere sia '\0' o '\n' sul torrente, preferiscono un codice leggibile per esempio:

cout << (a == N ? '\0' : '\n'); 
+2

In che modo "non è uguale" a quell'altro esempio? E 'solo la copia, l'errore di battitura e le perdite di visibilità? –

+2

Se l'intenzione è di stampare una nuova riga o un carattere null, si dovrebbe comunque preferire qualcosa di diverso dalla linea di codice originale! – Hurkyl

+0

@LightnessRacesinOrbit Sì, hai ragione, è copy + scope leak :) Grazie per aver segnalato l'errore, lo aggiusterò ora. La mia intenzione quando ho detto simili è, * Anche se l'anonimo non è usato altrove, un compilatore può decidere di generare un codice diverso *. –

9

E 'probabilmente inteso come un modo bizzarro di scrivere

if (a != N) { 
    cout<<"\n"; 
} 

Il [] l'operatore seleziona un elemento da una matrice. La stringa "\n" è in realtà una matrice di due caratteri: una nuova riga '\n' e una stringa terminatore '\0'. Quindi cout<<"\n"[a==N] stamperà un carattere '\n' o un carattere '\0'.

Il problema è che non è consentito inviare un carattere '\0' a un flusso I/O in modalità testo. L'autore di quel codice potrebbe aver notato che non sembrava accadere nulla, quindi ha pensato che cout<<'\0' fosse un modo sicuro per non fare nulla.

In C e C++, si tratta di un'ipotesi molto scarsa a causa della nozione di comportamento non definito . Se il programma fa qualcosa che non è coperto dalle specifiche dello standard o della piattaforma particolare, può succedere di tutto. Un risultato abbastanza probabile in questo caso è che lo stream smetterà di funzionare completamente - non verrà più visualizzato alcun output su cout.

In sintesi, l'effetto è,

"Stampa un ritorno a capo, se a non è uguale a N. In caso contrario, non lo so. Crash o qualcosa del genere."

... e la morale è, non scrivere le cose in modo così criptico.

+4

Non c'è nulla nello standard C++ o C sull'invio di '\ 0' a un flusso I/O in modalità testo che è un comportamento non definito. "Modalità testo" è un concetto di Windows. Non c'è distinzione tra modalità testo e modalità binaria su sistemi basati su Unix. –

+0

@DavidHammen La mancanza di specifiche è ciò che lo rende indefinito. Vedi C11 (N1570) §7.21.2/2: "I dati letti da un flusso di testo saranno necessariamente paragonabili ai dati precedentemente scritti su quel flusso solo se: i dati consistono solo di caratteri di stampa e caratteri di controllo orizzontali tab e new-line, nessun carattere di nuova riga è immediatamente preceduto da caratteri di spazio e l'ultimo carattere è un carattere di nuova riga.Se i caratteri di spazio che vengono scritti immediatamente prima che un carattere di nuova riga compaia quando vengono letti è l'implementazione- definito." – Potatoswatter

+0

(Il comportamento degli iostreams C++ è definito in termini di flussi C stdio.) – Potatoswatter

8

Non è un'opzione di cout ma un indice di matrice di "\n"

L'indice di matrice [a==N] viene valutato come [0] o [1], e indici la matrice carattere rappresentato dal "\n" che contiene una nuova riga e un nul carattere.

Tuttavia passando nul al iostream avrà risultati indefiniti, e sarebbe meglio passare una stringa:

cout << &("\n"[a==N]) ; 

Tuttavia, il codice in entrambi i casi non è particolarmente consigliabile e serve nessun particolare scopo diverso offuscare; non considerarlo come un esempio di buone pratiche. Quanto segue è preferibile nella maggior parte dei casi:

cout << (a != N ? "\n" : "") ; 

o solo:

if(a != N) cout << `\n` ; 
+0

Non hai bisogno di parentesi nel tuo primo esempio: 'cout << &" \ n "[a == N]' – eush77

+0

@ eush77: lo so, ma la chiarezza ci è servita senza la conoscenza della relativa precedenza di & e [] . – Clifford

8

Ciascuno dei seguenti linee genererà esattamente lo stesso output:

cout << "\n"[a==N];  // Never do this. 
cout << (a==N)["\n"]; // Or this. 
cout << *((a==N)+"\n"); // Or this. 
cout << *("\n"+(a==N)); // Or this. 


Come le altre risposte hanno indicato, questo non ha nulla a che fare con std::cout. E invece è una conseguenza di

  • Come il primitivo (non sovraccaricato) operatore di indicizzazione è implementata in C e C++.
    In entrambe le lingue, se array è una matrice di primitivi in ​​stile C, array[42] è zucchero sintattico per *(array+42). Ancora peggio, non c'è differenza tra array+42 e 42+array. Questo porta a un'interessante offuscazione: usa 42[array] invece di array[42] se il tuo obiettivo è quello di offuscare completamente il tuo codice. Va da sé che scrivere 42[array] è una pessima idea se il tuo obiettivo è scrivere codice comprensibile e gestibile.

  • Come i booleani vengono trasformati in numeri interi.
    Dato un'espressione del modulo a[b], sia a o deve essere un'espressione di puntatore e l'altro; l'altro deve essere un'espressione intera. Data l'espressione "\n"[a==N], lo "\n" rappresenta la parte puntatore di tale espressione e a==N rappresenta la parte intera dell'espressione. Qui, a==N è un'espressione booleana che restituisce false o true. Le regole di promozione intera specificano che false diventa 0 e true diventa 1 in promozione su un numero intero.

  • Come i letterali di stringa si degradano in puntatori.
    Quando è necessario un puntatore, gli array in C e C++ si degradano facilmente in un puntatore che punta al primo elemento dell'array.

  • Come vengono convertiti i letterali di stringa.
    Ogni stringa letterale stile C viene aggiunta al carattere nullo '\0'. Ciò significa che la rappresentazione interna del tuo "\n" è la matrice {'\n', '\0'}.


luce di quanto sopra, supponiamo a==N viene valutato come false. In questo caso, il comportamento è ben definito in tutti i sistemi: otterrai una nuova riga.Se, d'altra parte, a==N valuta true, il comportamento dipende molto dal sistema. Sulla base dei commenti alle risposte alla domanda, a Windows non piacerà. Sui sistemi Unix in cui std::cout viene collegato alla finestra del terminale, il comportamento è piuttosto benigno. Non accade nulla.


Solo perché è possibile scrivere codice come quello non significa che si dovrebbe. Non scrivere mai un codice del genere.

+0

@MarkHurd - Tutte e quattro le affermazioni fanno ** esattamente ** la stessa cosa. Si prega di leggere come funziona l'indicizzazione su array primitivi in ​​C e in C++. Per quanto riguarda la scrittura di ''\ 0'' sull'output in modalità testo, è perfettamente su macchine unix e linux. Succede tutto il tempo. Modalità binaria, modalità testo? Cos'è quello? Unix e Linux non distinguono tra i due. Alcune parti degli standard C e C++ portano a Windows, altre parti si uniscono a unix e linux, altre ancora a altre architetture. Non essere così incentrato su Windows. –

+1

Non ho visto il tuo '*' nelle tue seconde dichiarazioni. Scusate. –

+0

@MarkHurd - Aggiungerò un po 'di spaziatura per renderlo evidente. –