Mai visto prima in C ++ per loop


164

Stavo convertendo un algoritmo C ++ in C #. Mi sono imbattuto in questo per loop:

for (u = b.size(), v = b.back(); u--; v = p[v]) 
b[u] = v;

Non dà alcun errore in C ++, ma lo fa in C # (impossibile convertire int in bool). Non riesco davvero a capirlo per loop, dov'è la condizione?

Qualcuno può spiegare per favore?

PS. Solo per verificare, per adattare un VETTORE a un ELENCO b.back () corrisponde a b [b.Count-1]?


35
Dov'è la condizione? Quello sarebbe u--. I punti e virgola vengono utilizzati per delimitare le varie parti fordell'istruzione.
David Heffernan,

77
Questo è un ciclo abbastanza normale. C # non converte i numeri in bool implicitamente, quindi è necessario trasformare la condizione in; u-- != 0;
R. Martinho Fernandes

28
@Jessie Good - Il buon codice non ha nulla a che fare con ciò che è consentito nella lingua, ha a che fare con quanto tempo impiega un collega non laccato a leggere il codice. Se causa confusione, non è la migliore soluzione possibile, anche se legale. Spesso una soluzione più dettagliata è molto meglio di una concisa e la maggior parte dei compilatori compilerà la stessa cosa in entrambi i modi.
Bill K,

18
Mi auguro che, dopo la conversione del codice, si danno le variabili nomi migliori rispetto b, u, v, ecc L'unica ragione per cui sono stati chiamati in questo modo è perché qualcuno ha voluto essere eleganti, rendendo il loro codice illeggibile.
Dan

33
@houbysoft: questo è un problema generale di StackOverflow. Se poni una domanda molto dettagliata, ben studiata e interessante in un settore di ricerca specifico, che porta a una soluzione a un problema difficile e interessante, e rispondi a una domanda del genere dopo una dura giornata di ricerca, otterrai solo alcuni dozzine di visitatori e uno o due voti di alcuni esperti del settore. Se vuoi ottenere rapidamente un sacco di rep, devi fare e rispondere a domande come queste. "Come faccio ad aggiungere due numeri in php", "cosa dosignifica in C ++" - otterrà migliaia di hit da principianti che cercano un tutorial.
vsz

Risposte:


320

La condizione del forciclo è nel mezzo - tra i due punti e virgola ;.

In C ++ è OK mettere quasi ogni espressione come condizione: tutto ciò che valuta zero significa false; significa zero true.

Nel tuo caso, la condizione è u--: quando converti in C #, aggiungi semplicemente != 0:

for (u = b.size(), v = b.back(); u-- != 0; v = p[v]) 
    b[u] = v; //                     ^^^^ HERE

55
A proposito, Thomas potrebbe essere stato confuso anche dall'uso della virgola, è molto diverso dal punto e virgola, ti consente di fare più cose in una sezione del ciclo for (in questo caso, ha inizializzato due variabili). L'ultima volta che ho controllato questi insoliti costrutti non sono stati considerati la soluzione più leggibile possibile e potrebbero quindi essere disapprovati da alcuni.
Bill K,

2
Se ricordo bene, e l'espressione contenente una virgola ha il valore dell'ultima sottoespressione sulla destra. Questo deriva da C e può essere utilizzato in qualsiasi espressione, non solo in un ciclo for.
Giorgio,

7
@Roger Questo non sarebbe lo stesso loop poiché stai diminuendo alla fine del loop, anziché all'inizio (ovvero dopo b [u] = v anziché prima). In realtà dovresti inizializzarlo con u = b.size() - 1invece.
Didier L

Fyi: Ti ho appena spinto oltre il limite di 500K. È come essere il milionesimo cliente da qualche parte e vincere qualcosa? In ogni caso: tanti complimenti; sempre alla ricerca di risposte precise e precise! Continua così; incontrarti facendo il milione di cose ... entro la fine del secolo.
GhostCat,

165

Molte risposte precise, ma penso che valga la pena scrivere l'equivalente ciclo while.

for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

È equivalente a:

u = b.size();
v = b.back();
while(u--) {
   b[u] = v;
   v = p[v];
}

Potresti considerare il refactoring nel formato while () mentre traduci in C #. Secondo me è più chiaro, meno una trappola per i nuovi programmatori ed ugualmente efficiente.

Come altri hanno fatto notare - ma per fare la mia risposta completa - per farlo funzionare in C # si avrebbe bisogno di cambiamento while(u--)a while(u-- != 0).

... o while(u-- >0)nel caso in cui inizi in modo negativo. (OK, b.size()non sarà mai negativo - ma considera un caso generale in cui forse qualcos'altro ti ha inizializzato).

Oppure, per renderlo ancora più chiaro:

u = b.size();
v = b.back();
while(u>0) {
   u--;
   b[u] = v;
   v = p[v];
}

È meglio essere chiari che essere concisi.


29
Questa risposta non solo chiarisce il codice errato, ma offre anche una buona alternativa. 1 su !!
polvoazul,

2
Per inciso, starei attento con il while (u-- >0)modulo. Se la spaziatura viene incasinata, potresti finire con un ciclo "down to zero": while (u --> 0)che tende a confondere tutti a prima vista. ( Non sono sicuro che sia C # valido, ma è in C, e penso che potrebbe essere anche in C ++? )
Izkata

9
Non credo che il tuo codice sia necessariamente più chiaro. Il punto forinvece di whileè proprio quello di mettere l'inizializzazione e l'incremento / decremento in un'istruzione, e ciò non rende necessariamente più difficile la comprensione del codice. Altrimenti, non dovremmo usare foraffatto.
musiphil,

1
È anche importante riscrivere il minor codice possibile. (Il ciclo for potrebbe cambiare, dopo tutto.) Hai messo "u--" in due punti separati e il loop non è molto più chiaro (posso vedere cosa fa il ciclo for in un colpo d'occhio; devo scansionare con più righe). Essere concisi ha anche dei benefici. Non minimizzarli. Tuttavia, un buon esempio di come potrebbe essere scritto altrimenti. Rende più facile la comprensione per chiunque non sia abituato alle dichiarazioni in C ++ (o forse addirittura a tutte).
NotKyon,

2
@Izkata: non è MAI un operatore, viene analizzato come --token seguito da un >token. Due operatori separati. Un ciclo "down to zero" è solo una combinazione semplice di post-decremento e maggiore di. Il sovraccarico dell'operatore C ++ non crea nuovi operatori, ma ripropone solo quelli esistenti.
Ben Voigt,

66

La condizione è u--;, perché si trova nella seconda posizione dell'istruzione for .

Se il valore di u--;è diverso da 0, verrà interpretato come true(ovvero, implicitamente inserito nel valore booleano true). Se, invece, il suo valore è 0, verrà eseguito il cast su false.

Questo è un pessimo codice .

Aggiornamento: ho discusso la scrittura di loop "for" in questo post del blog . Le raccomandazioni possono essere riassunte nei seguenti paragrafi:

Un ciclo for è un costrutto pratico, leggibile (una volta che ti ci abitui) e conciso, ma devi usarlo bene. A causa della sua sintassi non comune, usarlo in un modo troppo fantasioso non è una buona idea.

Tutte le parti del ciclo for dovrebbero essere brevi e leggibili. I nomi delle variabili dovrebbero essere scelti per facilitarne la comprensione.

Questo esempio viola chiaramente queste raccomandazioni.


3
O probabilmente si fermerà a u == 0 probabilmente ...?
Thomas,

2
No; in C ++ c'è una conversione implicita da int a bool, con 0 che converte in falso. Il ciclo termina quando u-- == 0. In C #, non esiste una conversione implicita, quindi dovresti dire esplicitamente u-- == 0. EDIT: questo è in risposta al tuo primo commento.
Chris,

48
Questo è un codice terribile , per una ragione molto semplice; non puoi capirlo facilmente quando lo leggi. È "intelligente", un "hack"; utilizza una combinazione di strutture di codifica e conoscenza di come funzionano dietro le quinte, per creare una struttura che fa il lavoro ma che sfida la comprensione, perché non è nella forma che gli autori del linguaggio hanno immaginato e che è stata comunicata alla maggior parte utenti della lingua.
KeithS

4
Risposta eccellente. Lo farei +1 ... se non il "Questo è un codice molto cattivo." dichiarazione;)
Sandman4

14
Non capisco perché così tante persone sembrano pensare che tu sia un codice davvero cattivo semplicemente per la mancanza (implicita in C ++) ! = 0 . Sicuramente chiunque lavori con il codice sarà perfettamente consapevole del fatto che 0 = false, ogni altro valore = true . C'è molto più spazio per la confusione riguardo al pre / post-incremento di u , o le persone forse assumono che u = b.size () verrà sempre eseguito prima di v = b.back () (la mia comprensione è la sequenza di esecuzione che non è definita, ma sto per essere corretto).
FumbleFingers

23

Questa sarà la forma C # del tuo loop.

// back fetches the last element of vector in c++.
for (u = b.size(), v = b.back(); (u--) != 0; v = p[v]) 
{      
  b[u] = v;      
}

Basta sostituire l'equivalente per size () e back ().

Quello che fa è invertire l'elenco e memorizza in un array. Ma in C # abbiamo direttamente una funzione definita dal sistema per questo. Quindi non è necessario scrivere anche questo ciclo.

b = b.Reverse().ToArray();

1
prendendo v = b.back();fuori dal per intializer non hai semplicemente cambiare il modo in cui funziona, dal momento che il v = p[v]prevalga
João Portela

v = p [v] non avrà alcun effetto sul risultato finale poiché questa riga verrà eseguita alla fine. E dopo che v non viene indicato nel loop. Questa riga è lì solo per mostrare come il ciclo viene convertito da c ++ a C #.
Narendra,

1
nel codice c ++ è v = b.back();stato eseguito una volta prima dell'inizio delle iterazioni ed è v = p[v]stato eseguito all'inizio di ogni iterazione. In quella versione C # v = p[v]viene comunque eseguita all'inizio di ogni iterazione ma v = b.back();viene eseguita subito dopo, modificando il valore di vper l'istruzione successiva b[u] = v;. (forse la domanda è stata modificata dopo averla letta)
João Portela

5
@Rain Il problema è v = b.back(). Lo fai eseguire ad ogni iterazione del loop anziché solo al primo - non sappiamo cosa back()fa (ci sono effetti collaterali? Cambia la rappresentazione interna di b?), Quindi questo loop non è equivalente a quello in la domanda.
Izkata,

1
@Rain Esatto, guardalo più da vicino. Il passaggio di inizializzazione avviene solo una volta prima dell'inizio del ciclo, non all'inizio di ogni iterazione . Il tuo codice sarebbe corretto se v = b.back()fosse spostato al di fuori del ciclo, sopra di esso. (Inoltre, se stai cercando di rispondere a qualcuno, usa @davanti al loro nome, quindi riceviamo una notifica)
Izkata


14

La condizione è il risultato di u--, che è il valore di uprima che fosse diminuito.

In C e C ++, un int è convertibile in bool implicitamente facendo un != 0confronto (0 è false, tutto il resto è true).

b.back()è l'ultimo elemento in un contenitore, ovvero b[b.size() - 1]quando size() != 0.


11

In C tutto ciò che è diverso da zero è truein contesti "booleani", come la condizione di fine del ciclo o un'istruzione condizionale. In C # si deve fare quel controllo esplicito: u-- != 0.


Questa non è la questione dell'OP. Sta chiedendo informazioni sulla valutazione delle condizioni terminali (cioè 'u--' in questo caso).
ApplePie,

6

Come affermato da altri, il fatto che C ++ abbia il cast implicito in booleano significa che il condizionale è u--, il che sarà vero se il valore è diverso da zero.

Vale la pena aggiungere che hai un falso presupposto nel chiedere "dov'è il condizionale". In C ++ e C # (e in altri linguaggi con sintassi simile) puoi avere un condizionale vuoto. In questo caso restituisce sempre vero, così il ciclo continua sempre, o fino a qualche altra condizione uscite (tramite return, breako throw).

for(int i = 0; ; ++i)
  doThisForever(i);

In effetti, qualsiasi parte dell'istruzione for può essere esclusa, nel qual caso non viene eseguita.

In generale, for(A; B; C){D}o for(A; B; C)D;diventa:

{A}
loopBack:
if(!(B))
  goto escapeLoop;
{D}
{C}
goto loopBack;
escapeLoop:

Uno o più di A, B, C o D possono essere esclusi.

Di conseguenza, qualche favore for(;;) per infiniti loop. Lo faccio perché mentre while(true)è più popolare, leggo che "fino a quando la verità non finisce per essere vera", che suona in qualche modo apocalittico rispetto alla mia lettura for(;;) come "per sempre".

È una questione di gusti, ma poiché non sono l'unica persona al mondo a cui piace for(;;), vale la pena sapere cosa significa.


4

tutte le risposte sono corrette: -

for loop può essere utilizzato in vari modi come segue:

Single Statement inside For Loop
Multiple Statements inside For Loop
No Statement inside For Loop
Semicolon at the end of For Loop
Multiple Initialization Statement inside For
Missing Initialization in For Loop
Missing Increment/Decrement Statement
Infinite For Loop
Condition with no Conditional Operator.

4
for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

Nel codice sopra, ue vsono inizializzati con b.size()e b.back().

Ogni volta che viene verificata la condizione, viene eseguita anche un'istruzione di decremento, ovvero u--.

Il forloop uscirà quando udiventerà 0.


3

L'errore riscontrato in C # stesso cancella il dubbio. Il ciclo for cerca un

FALSE

condizione da terminare. E come sappiamo,

(BOOL) FALSE = (int) 0

ma C # non può elaborarlo da solo, diversamente da C ++. Quindi la condizione che stai cercando è

u--

ma devi dare esplicitamente la condizione in C # come

u--! = 0

o

u--> 0

Ma cerca ancora di evitare questo tipo di pratica di codifica. Il

mentre loop

indicato sopra in risposta è una delle versioni più semplificate del tuo

for-loop.


@Downvoter: il downvoting va bene fino a quando non sei soddisfatto della soluzione, ma allo stesso tempo, ti preghiamo di prendere il tempo per dichiarare il motivo, in modo che le risposte possano essere migliorate.
Abhineet,

3

Se sei abituato a C / C ++ questo codice non è così difficile da leggere, anche se è piuttosto conciso e non eccezionale. Quindi lasciami spiegare le parti che sono più cismiche di ogni altra cosa. Prima di tutto, la sintassi generale di un ciclo C per appare così:

for (<initialization> ; <condition>; <increment>)
{
    <code...>
}

Il codice di inizializzazione viene eseguito una volta. Quindi la condizione viene testata prima di ogni ciclo e infine l'incremento viene chiamato dopo ogni ciclo. Quindi nel tuo esempio troverai che la condizione èu--

Perché u--funziona come condizione in C e non in C #? Perché C converte implicitamente molte cose anche bool e può causare problemi. Per un numero tutto ciò che è diverso da zero è vero e zero è falso. Quindi farà il conto alla rovescia da b.size () - da 1 a 0. Avere l'effetto collaterale nella condizione è un po 'fastidioso e sarebbe preferibile metterlo nella parte incrementale del ciclo for, anche se molto C il codice fa questo. Se lo scrivessi lo farei più in questo modo:

for (u = b.size() - 1, v = b.back(); u>=0; --u) 
{
    b[u] = v;
    v = p[v]
}

La ragione di ciò è, almeno per me, più chiara. Ogni parte del ciclo for fa il suo lavoro e nient'altro. Nel codice originale la condizione stava modificando la variabile. La parte di incremento stava facendo qualcosa che dovrebbe essere nel blocco di codice ecc.

L'operatore virgola potrebbe anche lanciarti per un ciclo. In C qualcosa di simile x=1,y=2sembra un'istruzione per quanto riguarda il compilatore e si inserisce nel codice di inizializzazione. Valuta solo ciascuna delle parti e restituisce il valore dell'ultima. Quindi per esempio:

std::cout << "(1,2)=" << (1,2) << std::endl;

stamperebbe 2.


Il problema con la tua riscrittura è che se b.size () non è firmato, ripeterà per molto tempo. (Inoltre, ti manca un punto e virgola.) Ma almeno non hai adottato l'approccio "Dick and Jane" è un buon inglese "che hanno fatto così tante altre risposte e commenti, lodando assurdamente prolissi riscritture che sono solo più facili leggere da neofiti e altri programmatori non qualificati.
Jim Balter,
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.