2008-09-09 14 views
118

Was macht der , Operator in C?Was macht der Komma-Operator?

+0

mögliche Duplikate von [Was ist die ordnungsgemäße Verwendung des Komma-Operators?] (Http: // stackoverflow.com/questions/17902992/Was ist der richtige Gebrauch des Komma-Operators? –

+1

Wie ich in meiner Antwort bemerke, gibt es einen Sequenzpunkt nach der Auswertung des linken Operanden. Dies ist anders als das Komma in einem Funktionsaufruf, der nur grammatisch ist. –

Antwort

97

Der Ausdruck:

(expression1, expression2) 

Erste expression1 ausgewertet wird, dann wird expression2 ausgewertet und der Wert von expression2 ist für den gesamten Ausdruck zurückgegeben.

+1

dann, wenn ich i = (5,4,3,2,1,0) schreibe, dann sollte es idealerweise 0 zurückgeben, richtig? aber mir wird ein Wert von 5 zugewiesen? Kannst du mir bitte helfen zu verstehen, wo ich falsch liege? – Jayesh

+12

@James: Der Wert einer Kommaoperation ist immer der Wert des letzten Ausdrucks. Zu keinem Zeitpunkt wird 'i' die Werte 5, 4, 3, 2 oder 1 haben. Es ist einfach 0. Es ist praktisch nutzlos, wenn die Ausdrücke keine Nebenwirkungen haben. –

+4

Beachten Sie, dass zwischen der Auswertung der LHS des Kommaausdrucks und der Auswertung der RHS ein vollständiger Sequenzpunkt liegt (siehe [Shafik Yagmour] (http://stackoverflow.com/users/1708801/shafik-yaghmour)) [Antwort] (http://stackoverflow.com/a/18444099/15168) für ein Zitat aus dem C99-Standard). Dies ist eine wichtige Eigenschaft des Komma-Operators. –

8

Es verursacht die Auswertung mehrerer Anweisungen, aber nur die letzte als Ergebniswert (rvalue, denke ich).

So ...

int f() { return 7; } 
int g() { return 8; } 

int x = (printf("assigning x"), f(), g()); 

in x führen sollte wird auf 8 gesetzt

3

Wie bereits Antworten angegeben haben es alle Anweisungen auswertet, sondern nutzt die letzte als der Wert des Ausdrucks. Persönlich habe ich fand es nur in Schleife Ausdrücke nützlich:

for (tmp=0, i = MAX; i > 0; i--) 
2

Der einzige Ort, den ich je gesehen habe es nützlich sein, wenn Sie eine funky Schleife schreiben, wo man mehrere Dinge in einer der Ausdrücke tun wollen (wahrscheinlich die init Ausdruck oder Schleife Ausdruck etwas wie:.

bool arraysAreMirrored(int a1[], int a2[], size_t size) 
{ 
    size_t i1, i2; 
    for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--) 
    { 
    if(a1[i1] != a2[i2]) 
    { 
     return false; 
    } 
    } 

    return true; 
} 

Pardon, wenn es irgendwelche Syntaxfehler oder wenn ich in etwas gemischt, das ist nicht streng C. ich streite nicht, dass der Operator eine gute Form, aber das ist, wofür Sie es verwenden könnten.Im obigen Fall würde ich wahrscheinlich stattdessen eine while-Schleife verwenden, so dass die mehreren Ausdrücke auf init und loop b wären Das ist offensichtlich. (Und ich würde i1 und i2 inline initialisieren, anstatt zu erklären und dann die Initialisierung .... bla bla bla.)

+0

Ich nehme an, du meinst i1 = 0, i2 = Größe -1 – frankster

+0

In der Tat! Dually korrigiert. – Owen

85

Ich habe die meisten in while Schleifen verwendet gesehen:

string s; 
while(read_string(s), s.len() > 5) 
{ 
    //do something 
} 

Es wird das tun Operation, dann einen Test auf der Grundlage eines Nebeneffekts. Der andere Weg wäre es, wie dies zu tun:

string s; 
read_string(s); 
while(s.len() > 5) 
{ 
    //do something 
    read_string(s); 
} 
+12

Hey, das ist schick! Ich musste oft unorthodoxe Dinge in einer Schleife machen, um das Problem zu lösen. – staticsan

+4

Obwohl es wahrscheinlich weniger unklar und lesbarer wäre, wenn Sie etwas wie: 'while (read_string (s) && s.len()> 5)'. Offensichtlich würde das nicht funktionieren, wenn 'read_string' keinen Rückgabewert hat (oder keinen aussagekräftigen). (Bearbeiten: Sorry, habe nicht bemerkt, wie alt dieser Beitrag war.) – jamesdlin

+8

@staticsan Habe keine Angst, 'while (1)' mit einer 'break;' Anweisung im Körper zu verwenden. Der Versuch, den Break-Out-Teil des Codes in den While-Test oder in den Do-While-Test zu zwingen, ist oft eine Verschwendung von Energie und macht den Code schwieriger zu verstehen. – potrzebie

26

Der Komma-Operator kombiniert die beiden Ausdrücke auf beiden Seiten davon in eine, sie beide in von links nach rechts, um zu bewerten. Der Wert der rechten Seite wird als Wert des gesamten Ausdrucks zurückgegeben. (expr1, expr2) ist wie { expr1; expr2; }, aber Sie können das Ergebnis von expr2 in einem Funktionsaufruf oder einer Zuweisung verwenden.

Es ist oft in for Schleifen zu sehen ist, um mehrere Variablen wie folgt zu initialisieren oder zu erhalten:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh) 
{ 
    /* do something with low and high and put new values 
     in newlow and newhigh */ 
} 

Abgesehen davon, habe ich es nur „im Zorn“, die in einem anderen Fall, wenn zwei Einwickeln Operationen, die immer zusammen in einem Makro gehen sollten.Wir hatten Code, der für das Senden in einem Netzwerk verschiedene binäre Werte in einem Byte-Puffer kopiert, und ein Zeiger beibehalten, wo wir bis zu bekommen hatte:

unsigned char outbuff[BUFFSIZE]; 
unsigned char *ptr = outbuff; 

*ptr++ = first_byte_value; 
*ptr++ = second_byte_value; 

send_buff(outbuff, (int)(ptr - outbuff)); 

Wo waren die Werte short s oder int s wir dies taten:

*((short *)ptr)++ = short_value; 
*((int *)ptr)++ = int_value; 

Später lesen wir, dass diese C nicht wirklich gültig war, weil (short *)ptr ist nicht mehr ein l-Wert und kann nicht erhöht werden, obwohl unser Compiler zu der Zeit nichts ausmachte. Um dies zu beheben, teilen wir den Ausdruck in zwei:

*(short *)ptr = short_value; 
ptr += sizeof(short); 

Doch dieser Ansatz auf alle Entwickler verlassen Erinnerung beide Aussagen setzen in der ganzen Zeit. Wir wollten eine Funktion, in der Sie den Ausgabezeiger, den Wert und den Typ des Werts übergeben können. Das C ist, nicht C++ mit Vorlagen, können wir nicht eine Funktion übernimmt einen beliebigen Typen haben, so entschieden wir uns für einen Makro:

#define ASSIGN_INCR(p, val, type) ((*((type) *)(p) = (val)), (p) += sizeof(type)) 

Durch den Komma-Operator wir diese nutzen konnten in Ausdrücken oder als Anweisungen wie wir wünschten:

if (need_to_output_short) 
    ASSIGN_INCR(ptr, short_value, short); 

latest_pos = ASSIGN_INCR(ptr, int_value, int); 

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff)); 

Ich schlage nicht vor, dass irgendwelche dieser Beispiele guter Stil sind! In der Tat, ich erinnere mich an Steve McConnells Code Complete Ratschläge gegen sogar die Verwendung von Komma-Operatoren in einer for Schleife: für Lesbarkeit und Wartbarkeit sollte die Schleife durch nur eine Variable gesteuert werden, und die Ausdrücke in der for Zeile selbst sollte nur Schleife enthalten - Steuercode, keine anderen zusätzlichen Bits der Initialisierung oder Schleifenwartung.

+0

Danke! Es war meine erste Antwort auf StackOverflow: Seitdem habe ich vielleicht gelernt, dass Prägnanz zu schätzen ist :-). –

+0

Manchmal schätze ich ein bisschen Ausführlichkeit, wie es hier der Fall ist, wenn Sie die Entwicklung einer Lösung beschreiben (wie Sie dorthin gekommen sind). –

20

Die comma operator wird den linken Operanden auswerten, das Ergebnis verwerfen und dann den rechten Operanden auswerten, und das wird das Ergebnis sein. Die idiomatische Verwendung als in der Verbindung festgestellt wird, wenn die Variablen in einem for Schleife verwendet Initialisierung und es gibt folgendes Beispiel:

void rev(char *s, size_t len) 
{ 
    char *first; 
    for (first = s, s += len - 1; s >= first; --s) 
     /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
     putchar(*s); 
} 

Ansonsten gibt es nicht viele große Verwendungen des Komma-Operator Obwohl es easy to abuse ist, Code zu generieren, der schwer zu lesen und zu warten ist.

Vom draft C99 standard die Grammatik ist wie folgt:

expression: 
    assignment-expression 
    expression , assignment-expression 

und Absatz 2 sagt:

Der linke Operand eines Komma-Operator als nichtig Ausdruck ausgewertet wird; gibt es einen Sequenzpunkt nach seiner Auswertung. Dann wird der rechte Operand ausgewertet; Das Ergebnis hat seinen Typ und Wert.97) Wenn versucht wird, das Ergebnis eines Kommaoperators zu ändern oder nach dem nächsten Sequenzpunkt darauf zuzugreifen, ist das Verhalten nicht definiert.

Fußnote 97 sagt:

Ein Komma-Operator hat kein L-Wert ergeben.

was bedeutet, dass Sie nicht auf das Ergebnis des Komma-Operator zuweisen können.

Es ist wichtig, dass der Komma-Operator die lowest precedence und deshalb Fällen gibt es hat zu beachten, wo () mit einem großen Unterschied machen können, zum Beispiel:

#include <stdio.h> 

int main() 
{ 
    int x, y ; 

    x = 1, 2 ; 
    y = (3,4) ; 

    printf("%d %d\n", x, y) ; 
} 

die folgende Ausgabe haben:

1 4 
4

Der Komma-Operator tut nichts Sinnvolles, es ist eine 100% überflüssige Funktion. Der Hauptgebrauch davon ist "Leute, die versuchen, klug zu sein" und benutzen sie deshalb, um (unabsichtlich) lesbaren Code zu verschleiern. Das Haupteinsatzgebiet ist für Schleifen, zum Beispiel zu verschleiern:

for(int i=0, count=0; i<x; i++, count++) 

Wo int i=0, count=0 ist eigentlich nicht der Komma-Operator, sondern eine Deklarationsliste (wir hier schon verwirrt). i++, count++ ist der Komma-Operator, der zuerst den linken und dann den rechten Operanden auswertet. Das Ergebnis des Kommaoperators ist das Ergebnis des rechten Operanden. Das Ergebnis des linken Operanden wird verworfen.

Aber der obige Code könnte ohne den Komma-Operator in einem viel lesbarer Weise geschrieben werden:

int count = 0; 
for(int i=0; i<x; i++) // readable for loop, no nonsense anywhere 
{ 
    ... 
    count++; 
} 

Der einzige wirkliche Verwendung des Komma-Operator ich gesehen habe, ist die künstliche Diskussionen über Sequenzpunkte , da der Kommaoperator mit einem Sequenzpunkt zwischen der Auswertung der linken und rechten Operanden kommt.

Also, wenn Sie etwas nicht definiertes Verhalten Code wie dieses:

printf("%d %d", i++, i++); 

Sie können es tatsächlich verwandeln sich in lediglich nicht näher bezeichnete Verhalten (Reihenfolge der Auswertung der Funktionsparameter) durch

printf("%d %d", (0,i++), (0,i++)); 

Schreiben Es gibt jetzt ein Sequenzpunkt zwischen jeder Auswertung von i++, so wird zumindest das Programm nicht riskieren, weiter zu crashen und zu brennen, auch wenn die Reihenfolge der Auswertung von Funktionsparametern unspezifiziert bleibt.

Natürlich würde niemand solchen Code in realen Anwendungen schreiben, es ist nur nützlich für Sprach-Anwalt Diskussionen über Sequenzpunkte in der C-Sprache.

Der Komma-Operator ist von MISRA-C: 2004 und MISRA-C: 2012 mit der Begründung verboten, dass er weniger lesbaren Code erstellt.

+7

Ihre "unspezifizierte" Version ist noch nicht definiert. Die Auswertung der Argumente kann sich überlappen, so dass zunächst die beiden 0 ausgewertet werden, dann ein Sequenzpunkt und abschließend die beiden Inkremente ausgewertet werden. Dies erfüllt die Anforderung, dass ein Sequenzpunkt zwischen dem Komma LHS und RHS liegt. Wie für Ihre umgeschriebene "for" -Schleife, betrachten Sie 'continue'. Was Sie vorschlagen, ist gleichwertig nicht. Was die Lesbarkeit anbelangt, betrachte 'srC++, dst ++', wobei das Beibehalten der beiden Inkremente im Code es für den Leser viel einfacher macht, zu überprüfen, ob beide Zeiger genau gleich inkrementiert werden. – hvd

+1

@hvd Ich glaube nicht, dass eine streng übereinstimmende Implementierung Sequenzpunkte entfernen kann, was Ihr Kommentar suggeriert. Warum sollte jemand weiter nachdenken? Es ist nur nützlich für Spaghetti-Codierung (und auch von MISRA verboten). Und die Schleife ist jedenfalls sowohl hinsichtlich des Ergebnisses als auch hinsichtlich der "abstrakten Maschine" von C gleichwertig. Und ich glaube wirklich nicht, dass 'srC++, dst ++;' lesbarer ist als 'srC++; (Newline) dst ++; '. Ich bezweifle sehr wenige. – Lundin

+4

Fein, dann 0 auswerten, 0 bewerten, Sequenzpunkt, Sequenzpunkt, i ++ auswerten, i ++ auswerten. Das gleiche Ergebnis, also glaube ich nicht, dass Unterscheidung wichtig ist. Wie für srC++, dst ++, bezog ich mich darauf, wenn das in einem for erscheint, da sich das auf deine Antwort konzentriert. – hvd