Wie füge ich zwei Inkrementanweisungen in eine C ++ 'for'-Schleife ein?

93

Ich möchte zwei Variablen in einer for-loop-Bedingung anstelle einer erhöhen.

Also so etwas wie:

for (int i = 0; i != 5; ++i and ++j) 
    do_something(i, j);

Wie lautet die Syntax dafür?

Peter Smit
quelle

Antworten:

154

Eine gebräuchliche Redewendung ist die Verwendung des Kommaoperators, der beide Operanden auswertet und den zweiten Operanden zurückgibt. So:

for(int i = 0; i != 5; ++i,++j) 
    do_something(i,j);

Aber ist es wirklich ein Kommaoperator?

Nachdem ein Kommentator dies geschrieben hatte, schlug er vor, es handele sich tatsächlich um einen speziellen syntaktischen Zucker in der for-Anweisung und überhaupt nicht um einen Kommaoperator. Ich habe das in GCC wie folgt überprüft:

int i=0;
int a=5;
int x=0;

for(i; i<5; x=i++,a++){
    printf("i=%d a=%d x=%d\n",i,a,x);
}

Ich hatte erwartet, dass x den ursprünglichen Wert von a aufnimmt, also hätte es 5,6,7 .. für x anzeigen sollen. Was ich bekam war das

i=0 a=5 x=0
i=1 a=6 x=0
i=2 a=7 x=1
i=3 a=8 x=2
i=4 a=9 x=3

Wenn ich jedoch den Ausdruck in Klammern setze, um den Parser zu zwingen, einen Kommaoperator wirklich zu sehen, wird dies angezeigt

int main(){
    int i=0;
    int a=5;
    int x=0;

    for(i=0; i<5; x=(i++,a++)){
        printf("i=%d a=%d x=%d\n",i,a,x);
    }
}

i=0 a=5 x=0
i=1 a=6 x=5
i=2 a=7 x=6
i=3 a=8 x=7
i=4 a=9 x=8

Anfangs dachte ich, dass dies zeigte, dass es sich überhaupt nicht als Kommaoperator verhält, aber wie sich herausstellt, ist dies einfach ein Vorrangproblem - der Kommaoperator hat die niedrigstmögliche Vorrangstellung , daher ist der Ausdruck x = i ++, a ++ effektiv analysiert als (x = i ++), a ++

Vielen Dank für alle Kommentare, es war eine interessante Lernerfahrung und ich benutze C seit vielen Jahren!

Paul Dixon
quelle
1
Ich habe mehrmals gelesen, dass das Komma im ersten oder dritten Teil einer for-Schleife nicht der Kommaoperator ist, sondern nur ein Komma-Trennzeichen. (Ich finde jedoch keine offizielle Quelle dafür, da ich besonders schlecht darin bin, den C ++ - Sprachstandard zu analysieren.)
Daniel Daranas
Ich dachte zuerst, Sie wären falsch, aber ich habe einen Testcode geschrieben und Sie sind richtig - er verhält sich nicht wie ein Komma-Operator. Wird meine Antwort ändern!
Paul Dixon
19
Es ist ein Komma - Operator in diesem Zusammenhang. Der Grund, warum Sie nicht das bekommen, was Sie erwarten, ist, dass der Befehlsoperator eine niedrigere Priorität als der Zuweisungsoperator hat. Ohne die Klammern wird er als (x = i ++), j ++ analysiert.
Café
6
Es ist ein Kommaoperator. Die Zuweisung ist stärker als der Kommaoperator, daher wird x = i ++, a ++ analysiert (x = i ++), a ++ und nicht x = (i ++, a ++). Dieses Merkmal wird von einigen Bibliotheken missbraucht, so dass v = 1,2,3; führt die intuitiven Dinge aus, aber nur, weil v = 1 ein Proxy-Objekt zurückgibt, für das der überladene Komma-Operator ein Anhängen vornimmt.
AProgrammer
3
OK. Aus open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf Abschnitt 6.5.3 ist der letzte Teil ein "Ausdruck". (Obwohl 1.6 # 2 eine "Ausdrucksliste" als "durch Kommas getrennte Liste von Ausdrücken" definiert, erscheint dieses Konstrukt in 6.5.3 nicht.) Dies bedeutet, dass wenn wir "++ i, ++ j" schreiben, es ein Ausdruck in a für sich sein muss und daher "," der Kommaoperator sein muss (5.18). (Dies ist keine "Liste der Initialisierer" oder "Liste der Argumente für Funktionen". Dies sind Beispiele, bei denen "Komma eine besondere Bedeutung erhält", wie in 5.18 # 2 angegeben.) Ich finde es allerdings etwas verwirrend.
Daniel Daranas
55

Versuche dies

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);
yeyeyerman
quelle
17
+1 Sie können im ersten Teil auch j deklarieren. für (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Daniel Daranas
1
+1 Als Randnotiz funktioniert dieselbe Syntax in C # (ich bin von einer Google-Suche nach "C # für Schleifeninkrament 2-Zähler" hierher gekommen, also dachte ich, ich würde dies erwähnen).
CodingWithSpike
@CodingWithSpike: Nun, in C # das Komma ist speziell, es ist nicht wirklich legal , dass ein Ausdruck Komma - Operator , dort zu erscheinen. Beispiel, das die legale Verwendung des Kommaoperators in C ++ ist, aber von C # abgelehnt wird:for( ; ; ((++i), (++j)) )
Ben Voigt
@BenVoigt hat nichts mit dem Komma zu tun. Dies ist auch kein legales C #: for(int i = 0; i != 5; (++i)) {Die zusätzliche Klammer täuscht den Compiler vor, es sei keine "Inkrement" -Operation mehr.
CodingWithSpike
@CodingWithSpike: Das stimmt, aber die Klammern ändern auch , wie C # das Komma sieht, und verhindern die besondere Bedeutung innerhalb der for-Aktion.
Ben Voigt
6

Versuche es nicht zu tun!

Von http://www.research.att.com/~bs/JSF-AV-rules.pdf :

AV-Regel 199
Der Inkrementausdruck in einer for-Schleife führt keine andere Aktion aus, als einen einzelnen Schleifenparameter auf den nächsten Wert für die Schleife zu ändern.

Begründung: Lesbarkeit.

Squelart
quelle
4
Das stimmt, aber um fair zu sein, bin ich mir ziemlich sicher, dass der Standard der Regeln für eingebettete Software in einem Kampfjet geschrieben wurde, nicht für das Programm Garden Varieté C (++). Davon abgesehen ist es wahrscheinlich eine gute Angewohnheit, sich darauf einzulassen, und wer weiß, vielleicht entwerfen Sie F-35-Software, und es wird eine Angewohnheit weniger sein, diese zu brechen.
Galois
3
for (int i = 0; i != 5; ++i, ++j) 
    do_something(i, j);
malaiisch
quelle
2

Ich bin hierher gekommen, um mich daran zu erinnern, wie man einen zweiten Index in die Inkrementklausel einer FOR-Schleife codiert. Ich wusste, dass dies hauptsächlich durch Beobachtung in einem Beispiel geschehen kann, das ich in ein anderes Projekt integriert habe, das in C ++ geschrieben wurde.

Heute arbeite ich in C #, aber ich war mir sicher, dass diesbezüglich die gleichen Regeln eingehalten werden, da die FOR-Anweisung eine der ältesten Kontrollstrukturen in der gesamten Programmierung ist. Zum Glück hatte ich kürzlich mehrere Tage damit verbracht, das Verhalten einer FOR-Schleife in einem meiner älteren C-Programme genau zu dokumentieren, und mir wurde schnell klar, dass diese Studien Lektionen enthielten, die sich auf das heutige C # -Problem bezogen, insbesondere auf das Verhalten der zweiten Indexvariablen .

Für Unvorsichtige folgt eine Zusammenfassung meiner Beobachtungen. Alles, was ich heute gesehen habe, bestätigte meine Erwartung, dass sich eine C # FOR-Anweisung genau wie eine C- oder C ++ FOR-Anweisung verhält, indem ich die Variablen im Fenster Locals sorgfältig beobachtete.

  1. Bei der ersten Ausführung einer FOR-Schleife wird die Inkrementklausel (die dritte ihrer drei) übersprungen. In Visual C und C ++ wird das Inkrement als drei Maschinenbefehle in der Mitte des Blocks generiert, der die Schleife implementiert, sodass der erste Durchlauf den Initialisierungscode nur einmal ausführt und dann über den Inkrementblock springt, um den Beendigungstest auszuführen. Dies implementiert die Funktion, die eine FOR-Schleife je nach Status ihrer Index- und Grenzwertvariablen null oder mehrmals ausführt.
  2. Wenn der Hauptteil der Schleife ausgeführt wird, ist seine letzte Anweisung ein Sprung zum ersten der drei Inkrementbefehle, die bei der ersten Iteration übersprungen wurden. Nachdem diese ausgeführt wurden, fällt die Steuerung natürlich in den Grenzwerttestcode, der die mittlere Klausel implementiert. Das Ergebnis dieses Tests bestimmt, ob der Hauptteil der FOR-Schleife ausgeführt wird oder ob die Steuerung nach dem Sprung am Ende ihres Bereichs zum nächsten Befehl übergeht.
  3. Da die Steuerung vom unteren Rand des FOR-Schleifenblocks zum Inkrementblock übertragen wird, wird die Indexvariable inkrementiert, bevor der Test ausgeführt wird. Dieses Verhalten erklärt nicht nur, warum Sie Ihre Limit-Klauseln so codieren müssen, wie Sie es gelernt haben, sondern wirkt sich auch auf jedes sekundäre Inkrement aus, das Sie über den Komma-Operator hinzufügen, da es Teil der dritten Klausel wird. Daher wird es bei der ersten Iteration nicht geändert, sondern bei der letzten Iteration, bei der der Body niemals ausgeführt wird.

Wenn eine Ihrer Indexvariablen am Ende der Schleife im Gültigkeitsbereich bleibt, ist ihr Wert um eins höher als der Schwellenwert, der die Schleife stoppt, im Fall der echten Indexvariablen. Wenn beispielsweise die zweite Variable vor dem Eingeben der Schleife auf Null initialisiert wird, ist ihr Wert am Ende die Iterationszahl, vorausgesetzt, es handelt sich um ein Inkrement (++), nicht um ein Dekrement, und es ist nichts in Der Körper der Schleife ändert seinen Wert.

David A. Gray
quelle
1

Ich stimme Squelart zu. Das Inkrementieren von zwei Variablen ist fehleranfällig, insbesondere wenn Sie nur eine davon testen.

Dies ist der lesbare Weg, um dies zu tun:

int j = 0;
for(int i = 0; i < 5; ++i) {
    do_something(i, j);
    ++j;
}

ForSchleifen sind für Fälle gedacht, in denen Ihre Schleife mit einer ansteigenden / abnehmenden Variablen ausgeführt wird. Ändern Sie für jede andere Variable diese in der Schleife.

Wenn Sie jgebunden sein müssen i, lassen Sie die ursprüngliche Variable unverändert und fügen Sie sie hinzu i.

for(int i = 0; i < 5; ++i) {
    do_something(i,a+i);
}

Wenn Ihre Logik komplexer ist (zum Beispiel müssen Sie tatsächlich mehr als eine Variable überwachen), würde ich eine whileSchleife verwenden.

Ran Halprin
quelle
2
Im ersten Beispiel wird j noch einmal inkrementiert als i! Was ist mit einem Iterator, an dem für die ersten x Schritte eine Aktion ausgeführt werden muss? (Und die Sammlung ist immer lang genug) Sie können dann den Iterator bei jeder Iteration aufsteigen, ist aber imho viel sauberer.
Peter Smit
0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}
Arkaitz Jimenez
quelle
1
Was bringt es, nicht zu machen iund alokal für die Schleife zu sein?
sbi
2
Keine, nur um zu zeigen, wie man beide Inkremente im for macht, es ist nur ein Beispiel für den Sytnax
Arkaitz Jimenez
0

Verwenden Sie Mathematik. Wenn die beiden Operationen mathematisch von der Schleifeniteration abhängen, warum nicht die Mathematik?

int i, j;//That have some meaningful values in them?
for( int counter = 0; counter < count_max; ++counter )
    do_something (counter+i, counter+j);

Oder genauer gesagt unter Bezugnahme auf das Beispiel des OP:

for(int i = 0; i != 5; ++i)
    do_something(i, j+i);

Insbesondere wenn Sie eine Funktion nach Wert übergeben, sollten Sie etwas erhalten, das genau das tut, was Sie wollen.

xaviersjs
quelle