Incremento in C ++: quando utilizzare x ++ o ++ x?


91

Attualmente sto imparando C ++ e ho imparato a conoscere l'incremento qualche tempo fa. So che puoi usare "++ x" per fare l'incremento prima e "x ++" per farlo dopo.

Tuttavia, non so davvero quando usare nessuno dei due ... Non ho mai usato veramente "++ x" e le cose hanno sempre funzionato bene fino ad ora - quindi, quando dovrei usarlo?

Esempio: in un ciclo for, quando è preferibile utilizzare "++ x"?

Inoltre, qualcuno potrebbe spiegare esattamente come funzionano i diversi incrementi (o decrementi)? Lo apprezzerei molto.

Risposte:


114

Non è una questione di preferenza, ma di logica.

x++incrementa il valore della variabile x dopo l' elaborazione dell'istruzione corrente.

++xincrementa il valore della variabile x prima di elaborare l'istruzione corrente.

Quindi decidi solo la logica che scrivi.

x += ++iincrementerà i e aggiungerà i + 1 a x. x += i++aggiungerà i a x, quindi incrementerà i.


27
e per favore nota che in un ciclo for, sulle primative, non c'è assolutamente alcuna differenza. Molti stili di codifica raccomandano di non usare mai un operatore di incremento dove potrebbe essere frainteso; cioè, x ++ o ++ x dovrebbe esistere solo sulla propria riga, mai come y = x ++. Personalmente, non mi piace, ma è raro
Mikeage

2
E se utilizzato su una propria riga, il codice generato è quasi sicuramente lo stesso.
Nosredna

14
Questo può sembrare pedanteria (principalmente perché è :)) ma in C ++, x++è un valore con il valore di xprima dell'incremento, x++è un valore con il valore di xdopo un incremento. Nessuna delle due espressioni garantisce quando il valore effettivo incrementato viene memorizzato in x, è garantito solo che avvenga prima del successivo punto della sequenza. 'dopo l'elaborazione dell'istruzione corrente' non è strettamente accurato poiché alcune espressioni hanno punti di sequenza e alcune istruzioni sono istruzioni composte.
CB Bailey

10
In realtà, la risposta è fuorviante. Il momento in cui la variabile x viene modificata probabilmente non differisce nella pratica. La differenza è che x ++ è definito per restituire un rvalue del valore precedente di x mentre ++ x si riferisce ancora alla variabile x.
sellibitze

5
@ BeowulfOF: la risposta implica un ordine che non esiste. Non c'è nulla nella norma da dire quando si verificano gli incrementi. Il compilatore ha il diritto di implementare "x + = i ++" come: int j = i; i = i + 1; x + = j; "(es." i "incrementato prima di" elaborare l'istruzione corrente "). Questo è il motivo per cui" i = i ++ "ha un comportamento indefinito ed è per questo che penso che la risposta debba essere" ritoccata ". La descrizione di" x + = ++ i "è corretto in quanto non vi è alcun suggerimento di ordine:" incrementerà i e aggiungerà i + 1 a x ".
Richard Corden

53

Scott Meyers ti dice di preferire il prefisso tranne in quelle occasioni in cui la logica impone che il suffisso sia appropriato.

Elemento # 6 "C ++ più efficace" : è un'autorità sufficiente per me.

Per coloro che non possiedono il libro, ecco le citazioni pertinenti. Da pagina 32:

Dai tuoi giorni come programmatore C, potresti ricordare che la forma del prefisso dell'operatore di incremento è talvolta chiamata "incremento e recupero", mentre la forma del suffisso è spesso nota come "recupero e incremento". Le due frasi sono importanti da ricordare, perché tutte agiscono come specifiche formali ...

E a pagina 34:

Se sei il tipo che si preoccupa dell'efficienza, probabilmente hai iniziato a sudare quando hai visto per la prima volta la funzione di incremento postfisso. Quella funzione deve creare un oggetto temporaneo per il suo valore di ritorno e l'implementazione di cui sopra crea anche un oggetto temporaneo esplicito che deve essere costruito e distrutto. La funzione di incremento del prefisso non ha tali provvisori ...


4
Se il compilatore non si rende conto che il valore prima dell'incremento non è necessario, potrebbe implementare l'incremento del suffisso in diverse istruzioni: copia il vecchio valore e poi incrementa. L'incremento del prefisso dovrebbe sempre essere una sola istruzione.
gnud

8
Mi è capitato di testarlo ieri con gcc: in un ciclo for in cui il valore viene buttato via dopo l'esecuzione i++o ++i, il codice generato è lo stesso.
Giorgio

Provalo fuori dal ciclo for. Il comportamento in un'assegnazione deve essere diverso.
duffymo

Non sono esplicitamente in disaccordo con Scott Meyers sul suo secondo punto: di solito è irrilevante poiché il 90% o più dei casi di "x ++" o "++ x" sono tipicamente isolati da qualsiasi assegnazione e gli ottimizzatori sono abbastanza intelligenti da riconoscere che non è necessario essere creato in questi casi. In tal caso, le due forme sono completamente intercambiabili. L'implicazione di questo è che le vecchie basi di codice piene di "x ++" dovrebbero essere lasciate da sole - è più probabile che tu introduca errori sottili cambiandole in "++ x" piuttosto che migliorare le prestazioni ovunque. Probabilmente è meglio usare "x ++" e far riflettere le persone.
omatai

2
Puoi fidarti di Scott Meyers quanto vuoi, ma se il tuo codice è così dipendente dalle prestazioni che qualsiasi differenza di prestazioni tra ++xe è x++davvero importante, è molto più importante che tu effettivamente utilizzi un compilatore in grado di ottimizzare completamente e correttamente entrambe le versioni, indipendentemente dal contesto. "Dato che sto usando questo schifoso vecchio martello, posso infilare chiodi solo con un angolo di 43,7 gradi" è un argomento infelice per costruire una casa piantando chiodi a soli 43,7 gradi. Usa uno strumento migliore.
Andrew Henle

28

Da cppreference quando si incrementano gli iteratori:

Si dovrebbe preferire l'operatore pre-incremento (++ iter) all'operatore post-incremento (iter ++) se non si intende utilizzare il vecchio valore. Il post-incremento viene generalmente implementato come segue:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

Ovviamente, è meno efficiente del pre-incremento.

Il pre-incremento non genera l'oggetto temporaneo. Questo può fare una differenza significativa se il tuo oggetto è costoso da creare.


8

Voglio solo notare che il codice geneated è offensivo lo stesso se usi l'incremento pre / post dove la semantica (di pre / post) non ha importanza.

esempio:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"

5
Per un tipo primitivo come un numero intero, sì. Hai controllato per vedere quale sia la differenza per qualcosa come un std::map::iterator? Ovviamente i due operatori sono diversi, ma sono curioso di sapere se il compilatore ottimizzerà il suffisso in prefisso se il risultato non viene utilizzato. Non credo sia consentito, dato che la versione postfissa potrebbe contenere effetti collaterali.
sabato

Inoltre, ' il compilatore probabilmente si renderà conto che non hai bisogno dell'effetto collaterale e lo ottimizzerà ' non dovrebbe essere una scusa per scrivere codice sciatto che utilizza gli operatori postfissi più complessi senza alcun motivo, a parte presumibilmente il fatto che così tanti il presunto materiale didattico utilizza suffisso senza motivo apparente e viene copiato all'ingrosso.
underscore_d

6

La cosa più importante da tenere a mente, imo, è che x ++ deve restituire il valore prima che l'incremento abbia effettivamente avuto luogo, quindi deve fare una copia temporanea dell'oggetto (pre incremento). Questo è meno efficiente di ++ x, che viene incrementato sul posto e restituito.

Un'altra cosa degna di nota, tuttavia, è che la maggior parte dei compilatori sarà in grado di ottimizzare queste cose non necessarie quando possibile, ad esempio entrambe le opzioni porteranno allo stesso codice qui:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)

5

Sono d'accordo con @BeowulfOF, anche se per chiarezza suggerirei sempre di dividere le affermazioni in modo che la logica sia assolutamente chiara, ovvero:

i++;
x += i;

o

x += i;
i++;

Quindi la mia risposta è che se scrivi un codice chiaro, raramente dovrebbe importare (e se è importante, il tuo codice probabilmente non è abbastanza chiaro).


2

Volevo solo ribadire che ++ x dovrebbe essere più veloce di x ++, (specialmente se x è un oggetto di qualche tipo arbitrario), quindi a meno che non sia richiesto per ragioni logiche, ++ x dovrebbe essere usato.


2
Voglio solo sottolineare che questa enfasi è più che probabile fuorviante. Se stai guardando un ciclo che termina con una "x ++" isolata e pensi "Aha! - questo è il motivo per cui funziona così lentamente!" e lo cambi in "++ x", quindi non aspettarti alcuna differenza. Gli ottimizzatori sono abbastanza intelligenti da riconoscere che non è necessario creare variabili temporanee quando nessuno utilizzerà i loro risultati. L'implicazione è che le vecchie basi di codice piene di "x ++" dovrebbero essere lasciate da sole - è più probabile che tu introduca errori modificandole piuttosto che migliorare le prestazioni ovunque.
omatai

1

Hai spiegato correttamente la differenza. Dipende solo da se si desidera che x aumenti prima di ogni esecuzione di un ciclo o dopo. Dipende dalla logica del programma, da cosa è appropriato.

Una differenza importante quando si ha a che fare con gli iteratori STL (che implementano anche questi operatori) è che ++ crea una copia dell'oggetto a cui punta l'iteratore, quindi incrementa e quindi restituisce la copia. ++ invece esegue prima l'incremento e poi restituisce un riferimento all'oggetto a cui punta ora l'iteratore. Questo è per lo più rilevante solo quando ogni bit delle prestazioni conta o quando si implementa il proprio iteratore STL.

Modifica: corretto il miscuglio di notazione di prefisso e suffisso


Il discorso di "prima / dopo" l'iterazione di un ciclo è significativo solo se nella condizione si verifica l'incremento / decremento pre / post. Più spesso, sarà nella clausola di continuazione, dove non può cambiare alcuna logica, anche se potrebbe essere più lento per i tipi di classe usare suffisso e le persone non dovrebbero usarlo senza motivo.
underscore_d

1

Forma suffissa di ++, l'operatore - segue la regola usa-poi-cambia ,

La forma del prefisso (++ x, - x) segue la regola cambia-quindi-usa .

Esempio 1:

Quando più valori vengono sovrapposti a << usando cout, i calcoli (se presenti) vengono eseguiti da destra a sinistra ma la stampa avviene da sinistra a destra, ad esempio (se val se inizialmente 10)

 cout<< ++val<<" "<< val++<<" "<< val;

risulterà in

12    10    10 

Esempio 2:

In Turbo C ++, se in un'espressione vengono trovate più occorrenze di ++ o (in qualsiasi forma), prima vengono calcolate tutte le forme del prefisso, quindi viene valutata l'espressione e infine vengono calcolate le forme del suffisso, ad es.

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Il suo output in Turbo C ++ sarà

48 13

Considerando che il suo output nel compilatore moderno sarà (perché seguono rigorosamente le regole)

45 13
  • Nota: l'uso multiplo di operatori di incremento / decremento sulla stessa variabile in un'espressione non è consigliato. La gestione / i risultati di tali
    espressioni variano da compilatore a compilatore.

Non è che le espressioni contenenti più operazioni di incremento / decremento "variano da compilatore a compilatore", ma piuttosto peggio: tali modifiche multiple tra punti di sequenza hanno un comportamento indefinito e avvelenano il programma.
underscore_d

0

La comprensione della sintassi del linguaggio è importante quando si considera la chiarezza del codice. Valuta la possibilità di copiare una stringa di caratteri, ad esempio con post-incremento:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

Vogliamo che il ciclo venga eseguito incontrando il carattere zero (che verifica falso) alla fine della stringa. Ciò richiede il test del valore pre-incremento e anche l'incremento dell'indice. Ma non necessariamente in quest'ordine: un modo per codificarlo con il pre-incremento sarebbe:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

È una questione di gusti che è più chiara e se la macchina ha una manciata di registri entrambi dovrebbero avere lo stesso tempo di esecuzione, anche se una [i] è una funzione costosa o ha effetti collaterali. Una differenza significativa potrebbe essere il valore di uscita dell'indice.

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.