Nie zuvor gesehen C ++ for Schleife

164

Ich habe einen C ++ - Algorithmus in C # konvertiert. Ich bin auf diese for-Schleife gestoßen:

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

Es gibt keinen Fehler in C ++, aber in C # (int kann nicht in bool konvertiert werden). Ich kann das für die Schleife wirklich nicht herausfinden, wo ist die Bedingung?

Kann mir bitte jemand erklären?

PS. Nur um zu überprüfen, um einen VEKTOR an eine LISTE anzupassen, entspricht b.back () b [b.Count-1]?

Thomas
quelle
35
Wo ist der Zustand? Das wäre u--. Die Semikolons werden verwendet, um die verschiedenen Teile der forAnweisung abzugrenzen .
David Heffernan
77
Dies ist eine ganz normale Schleife. C # konvertiert Zahlen nicht implizit in Bools, daher müssen Sie die Bedingung in; u-- != 0;
R. Martinho Fernandes
28
@Jessie Gut - Guter Code hat nichts mit dem zu tun, was in der Sprache zulässig ist. Er hat damit zu tun, wie lange ein nicht erfahrener Mitarbeiter benötigt, um den Code zu lesen. Wenn es zu Verwirrung führt, ist es nicht die bestmögliche Lösung, auch wenn es legal ist. Oft ist eine ausführlichere Lösung viel besser als eine knappe, und die meisten Compiler kompilieren in beiden Fällen auf dieselbe Weise.
Bill K
18
Ich hoffe , dass nach dem Code umwandelt, den Variablen bessere Namen geben , als b, u, vetc. Der einzige Grund , warum sie so genannt wurden, weil jemand , indem sie ihr Code nicht lesbar suchen Smart wollte.
Dan
33
@houbysoft: Dies ist ein allgemeines Stackoverflow-Problem. Wenn Sie eine sehr detaillierte, gut recherchierte und interessante Frage in einem bestimmten Forschungsbereich stellen, die zu einer Lösung für ein schwieriges und interessantes Problem führt, und eine solche Frage nach einem anstrengenden Forschungstag beantworten, erhalten Sie nur wenige Dutzend Besucher und ein oder zwei positive Stimmen von einigen Experten auf diesem Gebiet. Wenn Sie schnell eine Menge Wiederholungen erzielen möchten, müssen Sie Fragen wie diese stellen und beantworten. "Wie füge ich zwei Zahlen in PHP hinzu?", "Was bedeutet dodas in C ++?" - erhalten Tausende von Treffern von Anfängern, die nach einem Tutorial suchen.
vsz

Antworten:

320

Der Zustand der forSchleife liegt in der Mitte - zwischen den beiden Semikolons ;.

In C ++ ist es in Ordnung, fast jeden Ausdruck als Bedingung zu setzen: Alles, was als Null ausgewertet wird, bedeutet false; Nicht-Null-Mittel true.

In Ihrem Fall lautet die Bedingung u--: Wenn Sie in C # konvertieren, fügen Sie einfach Folgendes hinzu != 0:

for (u = b.size(), v = b.back(); u-- != 0; v = p[v]) 
    b[u] = v; //                     ^^^^ HERE
dasblinkenlight
quelle
55
Übrigens war Thomas möglicherweise auch durch die Verwendung des Kommas verwirrt. Es unterscheidet sich stark vom Semikolon. Sie können mehrere Dinge in einem Abschnitt der for-Schleife ausführen (in diesem Fall wurden zwei Variablen initialisiert). Als ich das letzte Mal nachgesehen habe, wurden diese ungewöhnlichen Konstrukte nicht als die am besten lesbare Lösung angesehen und können daher von einigen missbilligt werden.
Bill K
2
Wenn ich mich richtig erinnere, hat ein Ausdruck, der ein Komma enthält, den Wert des letzten Unterausdrucks rechts. Dies kommt von C und kann in jedem Ausdruck verwendet werden, nicht nur in einer for-Schleife.
Giorgio
7
@Roger Dies wäre nicht dieselbe Schleife, da Sie u am Ende der Schleife anstelle des Anfangs dekrementieren (dh nach b [u] = v statt vor). In der Tat müssten Sie es u = b.size() - 1stattdessen mit initialisieren .
Didier L
Zu Ihrer Information: Ich habe Sie gerade über das 500K-Limit geschoben. Ist das so, als wäre man irgendwo der einmillionste Kunde und würde etwas gewinnen? Auf jeden Fall: viele Glückwünsche; Immer auf Ihre präzisen, scharfen Antworten schauen! Mach weiter; Treffen Sie sich mit der 1-Millionen-Sache ... später in diesem Jahrhundert.
GhostCat
165

Viele genaue Antworten, aber ich denke, es lohnt sich, die entsprechende while-Schleife aufzuschreiben.

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

Ist äquivalent zu:

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

Bei der Übersetzung in C # können Sie eine Umgestaltung in das while () -Format in Betracht ziehen. Meiner Meinung nach ist es klarer, für neue Programmierer weniger eine Falle und ebenso effizient.

Wie andere bereits betont haben - aber um meine Antwort zu vervollständigen -, müssten Sie zu C # wechseln while(u--), damit sie in C # funktioniert while(u-- != 0).

... oder while(u-- >0)nur für den Fall, dass du negativ anfängst. (OK, b.size()wird niemals negativ sein - aber betrachten Sie einen allgemeinen Fall, in dem vielleicht etwas anderes u initialisiert wurde).

Oder um es noch deutlicher zu machen:

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

Es ist besser klar zu sein als knapp zu sein.

schlank
quelle
29
Diese Antwort verdeutlicht nicht nur den schlechten Code, sondern bietet auch eine gute Alternative. 1 bis !!
Polvoazul
2
Abgesehen davon würde ich mit dem while (u-- >0)Formular vorsichtig sein . Wenn der Abstand durcheinander gerät, kann es zu einer Schleife "bis Null" kommen while (u --> 0), die alle auf den ersten Blick verwirrt. ( Ich bin nicht sicher, ob es gültig ist C #, aber es ist in C, und ich denke, es kann auch in C ++ sein? )
Izkata
9
Ich denke nicht, dass Ihr Code notwendigerweise klarer ist. Der Punkt von forstatt whileist genau, dass Sie die Initialisierung und das Inkrement / Dekrement in einer Anweisung zusammenfassen, und das macht den Code nicht unbedingt schwieriger zu verstehen. Andernfalls sollten wir überhaupt nicht verwenden for.
Musiphil
1
Es ist auch wichtig, so wenig Code wie möglich neu zu schreiben. (Die for-Schleife könnte sich schließlich ändern.) Sie haben "u--" an zwei verschiedenen Stellen platziert, und die Schleife ist nicht wirklich klarer (ich kann auf einen Blick sehen, was die for-Schleife tut; ich muss scannen mit mehreren Zeilen). Knapp zu sein hat auch Vorteile. Spiele sie nicht herunter. Trotzdem ein gutes Beispiel dafür, wie es sonst geschrieben werden könnte. Dies erleichtert das Verständnis für alle, die nicht an Anweisungen in C ++ (oder vielleicht sogar überhaupt) gewöhnt sind.
NotKyon
2
@Izkata: Es ist NIEMALS ein Operator, es wird als --Token gefolgt von einem >Token analysiert . Zwei separate Operatoren. Eine "bis auf Null" -Schleife ist nur eine einfache Kombination aus Nachdekrementierung und Größer als. Durch das Überladen von C ++ - Operatoren werden keine neuen Operatoren erstellt, sondern nur vorhandene neu verwendet.
Ben Voigt
66

Die Bedingung ist u--;, weil es sich an der zweiten Position der for- Anweisung befindet.

Wenn sich der Wert von u--;von 0 unterscheidet, wird er interpretiert als true(dh implizit in den booleschen Wert umgewandelt true). Wenn stattdessen der Wert 0 ist, wird er in umgewandelt false.

Das ist sehr schlechter Code .

Update: Ich habe das Schreiben von "for" -Schleifen in diesem Blog-Beitrag besprochen . Ihre Empfehlungen können in den folgenden Absätzen zusammengefasst werden:

Eine for-Schleife ist ein praktisches, lesbares (sobald Sie sich daran gewöhnt haben) und knappes Konstrukt, aber Sie müssen es gut verwenden. Aufgrund seiner ungewöhnlichen Syntax ist es keine gute Idee, es zu einfallsreich zu verwenden.

Alle Teile der for-Schleife sollten kurz und lesbar sein. Variablennamen sollten ausgewählt werden, um das Verständnis zu erleichtern.

Dieses Beispiel verstößt eindeutig gegen diese Empfehlungen.

Daniel Daranas
quelle
3
Oder es wird wahrscheinlich bei u == 0 aufhören ...?
Thomas
2
Nein; In C ++ gibt es eine implizite Konvertierung von int nach bool, wobei 0 in false konvertiert wird. Die Schleife wird beendet, wenn u-- == 0. In C # gibt es keine solche implizite Konvertierung, sodass Sie explizit u-- == 0 sagen müssen. BEARBEITEN: Dies ist eine Antwort auf Ihren ersten Kommentar.
Chris
48
Dies ist aus einem sehr einfachen Grund schrecklicher Code. Sie konnten es nicht leicht verstehen, wenn Sie es lesen. Es ist "klug", ein "Hack"; Es nutzt eine Kombination aus Codierungsstrukturen und dem Wissen darüber, wie sie hinter den Kulissen funktionieren, um eine Struktur zu erstellen, die die Aufgabe erfüllt, aber dem Verständnis widerspricht, da sie nicht in der Form vorliegt, die die Sprachautoren sich vorgestellt haben und die den meisten mitgeteilt wurde Sprachbenutzer.
KeithS
4
Hervorragende Antwort. Ich würde es +1 ... wenn nicht das "Dies ist ein sehr schlechter Code." Aussage;)
Sandman4
14
Ich verstehe nicht, warum so viele Leute denken, dass u-- wirklich schlechter Code ist, einfach wegen des Fehlens (implizit in C ++) ! = 0 . Sicherlich wird jeder, der mit Code arbeitet, genau wissen, dass 0 = falsch, jeder andere Wert = wahr . Es gibt weit mehr Spielraum für Verwirrung in Bezug auf Pre- / Post-Inkrementieren von u oder Personen unter der Annahme , vielleicht , dass u = B.Size () wird immer ausgeführt werden, bevor v = b.back () (mein Verständnis die Ausführungssequenz wird ist nicht definiert, aber ich muss korrigiert werden).
FumbleFingers
23

Dies ist die C # -Form Ihrer Schleife.

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

Ersetzen Sie einfach das Äquivalent für Größe () und Rückseite ().

Es kehrt die Liste um und speichert sie in einem Array. Aber in C # haben wir direkt eine systemdefinierte Funktion dafür. Sie müssen diese Schleife also auch nicht schreiben.

b = b.Reverse().ToArray();
Narendra
quelle
1
Durch das Herausnehmen v = b.back();aus dem for-Intializer haben Sie nicht nur die Funktionsweise geändert, da der v = p[v]von
João Portela
v = p [v] hat keine Auswirkung auf das Endergebnis, da diese Zeile zuletzt ausgeführt wird. Und danach wird v nicht mehr in der Schleife referenziert. Diese Zeile soll nur zeigen, wie die Schleife von c ++ nach C # konvertiert wird.
Narendra
1
im c ++ Code v = b.back();wurde einmal vor dem Start der Iterationen v = p[v]ausgeführt und wurde zu Beginn jeder Iteration ausgeführt. In dieser C # -Version v = p[v]wird zu Beginn jeder Iteration noch ausgeführt, jedoch v = b.back();direkt danach ausgeführt, wobei der Wert von vfür die nächste Anweisung geändert wird b[u] = v;. (Vielleicht wurde die Frage bearbeitet, nachdem Sie sie gelesen haben)
João Portela
5
@ Regen Das Problem ist v = b.back(). Sie haben es bei jeder Schleifeniteration ausgeführt, anstatt nur bei der ersten - wir wissen nicht, was es back()tut (gibt es irgendwelche Nebenwirkungen? Ändert es die interne Darstellung von b?), Daher entspricht diese Schleife nicht der in die Frage.
Izkata
1
@Rain Genau, schauen Sie es sich selbst genauer an. Der Initialisierungsschritt erfolgt nur einmal vor Beginn der Schleife, nicht zu Beginn jeder Iteration . Ihr Code wäre korrekt, wenn v = b.back()er außerhalb der darüber liegenden Schleife verschoben würde . (Auch wenn Sie versuchen, auf jemanden zu antworten, verwenden Sie @vor seinem Namen, damit wir eine Benachrichtigung erhalten)
Izkata
15
u = b.size(), v = b.back()

ist Initialisierung.

u--

ist die Bedingung.

v = p[v]

ist die Iteration

huseyin tugrul buyukisik
quelle
14

Die Bedingung ist das Ergebnis von u--, was der Wert ist, ubevor er dekrementiert wurde.

In C und C ++, eine int ist umwandelbar in Bool durch implizit einen tun != 0Vergleich (0 ist false, alles andere ist true).

b.back()ist das letzte Element in einem Container, dh b[b.size() - 1]wann size() != 0.

Bo Persson
quelle
11

In C befindet sich alles, was nicht Null ist, truein "booleschen" Kontexten, wie z. B. der Schleifenendbedingung oder einer bedingten Anweisung. In C # müssen Sie diese Prüfung explizit machen : u-- != 0.

Joey
quelle
Dies ist nicht die Frage von OP. Er fragt nach der Bewertung des Endzustands (dh 'u--' in diesem Fall).
ApplePie
6

Wie von anderen angegeben, bedeutet die Tatsache, dass C ++ implizites Casting in Boolesche u--Werte hat, die Bedingung , die wahr ist, wenn der Wert nicht Null ist.

Es lohnt sich hinzuzufügen, dass Sie eine falsche Annahme haben, wenn Sie fragen, wo die Bedingung ist. Sowohl in C ++ als auch in C # (und anderen ähnlich syntaxierten Sprachen) können Sie eine leere Bedingung haben. In diesem Fall wertet es immer wahr, so dass die Schleife für immer weitergeht, oder bis zu einigen anderen Zustand verlässt sie (über return, breakoder throw).

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

In der Tat kann jeder Teil der for-Anweisung weggelassen werden. In diesem Fall wird er einfach nicht ausgeführt.

Im Allgemeinen for(A; B; C){D}oder for(A; B; C)D;wird:

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

Ein oder mehrere von A, B, C oder D können weggelassen werden.

Infolgedessen einige Vorliebe for(;;) für Endlosschleifen. Ich mache das, weil ich zwar while(true)populärer bin, aber das als "bis die Wahrheit nicht mehr wahr ist" lese, was im Vergleich zu meiner Lektüre for(;;) als "für immer" etwas apokalyptisch klingt .

Es ist Geschmackssache, aber da ich nicht die einzige Person auf der Welt bin, die es mag for(;;), lohnt es sich zu wissen, was es bedeutet.

Jon Hanna
quelle
4

Alle Antworten sind richtig: -

Die for-Schleife kann auf verschiedene Arten wie folgt verwendet werden:

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.
birubisht
quelle
4
for (u = b.size(), v = b.back(); u--; v = p[v]) 
   b[u] = v;

Im obigen Code uund vwerden mit b.size()und initialisiert b.back().

Jedes Mal, wenn die Bedingung überprüft wird, führt sie auch eine Dekrementanweisung aus, dh u--.

Die forSchleife wird verlassen , wenn usein wird 0.

Apte
quelle
3

Der in C # selbst aufgetretene Fehler löscht den Zweifel. Die for-Schleife sucht nach a

FALSCH

Bedingung zu beenden. Und wie wir wissen,

(BOOL) FALSE = (int) 0

Im Gegensatz zu C ++ kann C # dies jedoch nicht alleine verarbeiten. Die Bedingung, nach der Sie suchen, ist also

u--

Sie müssen die Bedingung jedoch explizit in C # als angeben

u--! = 0

oder

u--> 0

Versuchen Sie dennoch, diese Art der Codierungspraxis zu vermeiden. Das

while-Schleife

Die oben als Antwort angegebene ist eine der einfachsten Versionen von Ihnen

for-Schleife.

Abhineet
quelle
@Downvoter: Downvoting ist in Ordnung, sofern Sie mit der Lösung nicht zufrieden sind. Nehmen Sie sich jedoch bitte Zeit, um den Grund anzugeben, damit die Antworten verbessert werden können.
Abhineet
3

Wenn Sie an C / C ++ gewöhnt sind, ist dieser Code nicht so schwer zu lesen, obwohl er ziemlich knapp und nicht so großartig ist. Lassen Sie mich also die Teile erklären, die mehr Cism als alles andere sind. Zunächst sieht die allgemeine Syntax einer C for-Schleife folgendermaßen aus:

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

Der Initialisierungscode wird einmal ausgeführt. Dann wird die Bedingung vor jeder Schleife getestet und zuletzt wird das Inkrement nach jeder Schleife aufgerufen. In Ihrem Beispiel finden Sie also die Bedingungu--

Warum u--arbeitet als Bedingung in C und nicht in C #? Weil C implizit viele Dinge zu bools konvertiert und es Probleme verursachen kann. Für eine Zahl ist alles, was nicht Null ist, wahr und Null ist falsch. Es wird also von b.size () - 1 bis 0 heruntergezählt. Der Nebeneffekt in der Bedingung ist etwas ärgerlich und es wäre vorzuziehen, ihn in den inkrementellen Teil der for-Schleife zu setzen, obwohl viel C. Code macht das. Wenn ich es schreiben würde, würde ich es eher so machen:

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

Der Grund dafür ist zumindest für mich klarer. Jeder Teil der for-Schleife erledigt seinen Job und sonst nichts. Im ursprünglichen Code wurde die Variable durch die Bedingung geändert. Der Inkrement-Teil hat etwas getan, das sich im Codeblock usw. befinden sollte.

Der Kommaoperator wirft Sie möglicherweise auch für eine Schleife. In C x=1,y=2sieht so etwas wie eine Anweisung für den Compiler aus und passt in den Initialisierungscode. Es wertet nur jedes der Teile aus und gibt den Wert des letzten zurück. Also zum Beispiel:

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

würde ausdrucken 2.

Matt Price
quelle
Das Problem beim Umschreiben besteht darin, dass b.size (), wenn es nicht signiert ist, sehr lange iteriert. (Außerdem fehlt Ihnen ein Semikolon.) Aber zumindest haben Sie nicht den Ansatz "Dick und Jane ist gutes Englisch" gewählt, den so viele andere Antworten und Kommentare hatten, und lobten absurd ausführliche Umschreibungen, die nur einfacher sind von Neulingen und anderen ungelernten Programmierern zu lesen.
Jim Balter