Operatoren sind die Verben von C: Sie nehmen Werte entgegen und erzeugen neue. Es gibt rund 40 davon, aber ein Dutzend deckt den meisten Code ab, und zwei davon haben Fallen, die scharf genug für eigene Abschnitte sind.
Arithmetische Operatoren
(%% in der Formatzeichenkette gibt ein wörtliches Prozentzeichen aus - ein % allein würde eine Formatangabe beginnen.)
Die fünf binären arithmetischen Operatoren sind + - * / %, dazu das unäre - zur Negation und +, das im Wesentlichen nichts tut.
Die Falle der Ganzzahldivision
a / b mit zwei Ganzzahlen führt eine Ganzzahldivision aus und wirft den Rest weg. Es wird nicht gerundet - das Ergebnis wird Richtung null abgeschnitten.
Der letzte Fall ist der Bug, der es bis in die Produktion schafft. Einem double zuzuweisen hilft nicht: Die Division total / count ist bereits abgeschlossen und bereits ein int, bevor die Zuweisung passiert. Die Lösung ist, einen Operanden innerhalb des Ausdrucks zu einem Gleitkommawert zu machen, meist mit einem Cast.
Zwei verwandte Regeln: Eine Ganzzahldivision durch null ist undefiniertes Verhalten und bringt das Programm typischerweise zum Absturz, und INT_MIN / -1 läuft über.
Der Modulo-Operator
% liefert den Rest und funktioniert nur auf Ganzzahlen:
x % 2 == 0 auf Geradheit zu prüfen und % n zu benutzen, um einen Index in einem Array umlaufen zu lassen, sind die beiden alltäglichen Verwendungen.
Bei negativen Zahlen folgt Cs % dem Vorzeichen des Dividenden:
x % 2 == 1 ist also ein kaputter Test auf Ungeradheit, wenn x negativ sein kann - -7 % 2 ist -1. Nimm stattdessen x % 2 != 0.
Für Gleitkommareste ist % ein Übersetzungsfehler; nimm fmod() aus math.h.
Zuweisung und zusammengesetzte Zuweisung
= speichert einen Wert. Die zusammengesetzten Formen verbinden eine Operation mit dem Speichern:
x += 5 bedeutet x = x + 5, wertet x aber nur einmal aus - was zählt, wenn das Ziel etwas wie arr[compute_index()] ist.
Die Zuweisung ist selbst ein Ausdruck, der den zugewiesenen Wert liefert, weshalb a = b = c = 0 funktioniert (es weist von rechts nach links zu) und weshalb der =-statt-==-Fehler stillschweigend kompiliert.
Inkrement und Dekrement
++ addiert eins, -- zieht eins ab. Jedes kommt in einer Präfix- und einer Postfixform:
Als eigenständige Anweisung sind die beiden austauschbar, und i++ ist im Kopf einer for-Schleife die übliche Wahl, aus keinem tieferen Grund als der Tradition.
Was du nicht tun darfst, ist eine Variable mehr als einmal in einem Ausdruck zu benutzen, in dem sie auch verändert wird:
int i = 5;
int x = i++ + i++; /* undefiniertes Verhalten */
arr[i] = i++; /* undefiniertes Verhalten */
printf("%d %d", i++, i);/* undefiniertes Verhalten */
C legt die Reihenfolge nicht fest, in der diese Teilausdrücke ausgewertet werden, das Ergebnis ist also nicht bloß unspezifiziert - das Verhalten des gesamten Programms ist undefiniert. Teile die Anweisung in zwei, und die Mehrdeutigkeit verschwindet.
Vergleichsoperatoren
Sechs Stück, und alle liefern ein int: 1 für wahr und 0 für falsch.
Dass das Ergebnis ein schlichtes int und kein eigener Wahrheitstyp ist, ist ein prägendes Merkmal von C - siehe Booleans in C für das, was daraus folgt, einschließlich des =-statt-==-Bugs.
Zwei Vergleichsfehler sind C-spezifisch. Verketten funktioniert nicht so, wie die Mathematik es nahelegt: if (1 < x < 10) ist immer wahr, denn 1 < x ergibt 0 oder 1, und das wird dann mit 10 verglichen. Schreibe if (x > 1 && x < 10). Und Zeichenketten mit == zu vergleichen vergleicht Zeiger, nicht Text - nimm strcmp aus string.h.
Logische Operatoren
&& ist UND, || ist ODER, ! ist NICHT. Alle drei behandeln jeden Wert ungleich null als wahr.
Sie werten abkürzend aus: && hört auf, sobald ein Operand falsch ist, und || hört auf, sobald einer wahr ist. Der Rest wird nie ausgewertet, und das ist nicht nur eine Optimierung - es ist eine Garantie, auf die du dich aus Sicherheitsgründen verlassen kannst:
Tausche die Reihenfolge dieser beiden Prüfungen, und der NULL-Aufruf dereferenziert einen Nullzeiger und stürzt ab. Zuerst absichern, dann benutzen - die Reihenfolge in einer &&-Kette gehört zur Logik.
Der Bedingungsoperator
Cs einziger dreistelliger Operator wählt zwischen zwei Werten:
bedingung ? wert_wenn_wahr : wert_wenn_falsch. Es ist ein Ausdruck, passt also dorthin, wo eine if-Anweisung nicht hinpasst - in ein printf-Argument, in einen Initialisierer. Halte ihn kurz; verschachtelte Bedingungsoperatoren werden schnell unlesbar.
Bitweise Operatoren, kurz gefasst
Diese arbeiten auf den einzelnen Bits einer Ganzzahl:
Sie werden für Flags, Masken und Hardwareregister benutzt. Zwei Warnungen: & und | sind nicht && und || (sie werten nicht abkürzend aus und arbeiten Bit für Bit), und einen vorzeichenbehafteten Wert zu schieben oder weiter als die Breite des Typs zu schieben ist undefiniert. Nimm für Bitarbeit vorzeichenlose Typen.
Vorrang
Wenn ein Ausdruck keine Klammern hat, entscheidet diese Tabelle, was woran bindet. Höchster Vorrang zuerst:
| Stufe | Operatoren | Assoziativität |
|---|---|---|
| 1 | () [] -> . x++ x-- | von vorn nach hinten |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (typ) | von hinten nach vorn |
| 3 | * / % | von vorn nach hinten |
| 4 | + - | von vorn nach hinten |
| 5 | << >> | von vorn nach hinten |
| 6 | < <= > >= | von vorn nach hinten |
| 7 | == != | von vorn nach hinten |
| 8 | & | von vorn nach hinten |
| 9 | ^ | von vorn nach hinten |
| 10 | bitweises ODER | von vorn nach hinten |
| 11 | && | von vorn nach hinten |
| 12 | logisches ODER | von vorn nach hinten |
| 13 | ?: | von hinten nach vorn |
| 14 | = += -= *= /= %= und Verwandte | von hinten nach vorn |
| 15 | , | von vorn nach hinten |
Die praktischen Folgen:
a + b * c /* bedeutet a + (b * c) */
a < b == c < d /* bedeutet (a < b) == (c < d) - selten das Gemeinte */
x & 1 == 0 /* bedeutet x & (1 == 0), also x & 0 - ein klassischer Bug */
*p++ /* bedeutet *(p++) - p dereferenzieren, dann weiterrücken */
Die dritte Zeile ist merkenswert: == bindet stärker als &, Bitmasken-Tests brauchen also Klammern - (x & 1) == 0.
Niemand merkt sich alle fünfzehn Stufen, und niemand muss es. Lerne, dass * + schlägt, dass Vergleiche && schlagen, das || schlägt, und dass alles andere Klammern bekommt:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
Diese Zeile hat überflüssige Klammern und ist dadurch besser.
Häufig gestellte Fragen
Was macht % in C?
% ist der Modulo-Operator: Er liefert den Rest der Ganzzahldivision. 17 % 5 ist 2. Er funktioniert nur auf Ganzzahlen - ihn auf einem float oder double zu benutzen ist ein Übersetzungsfehler, und fmod() aus math.h ist das Gleitkomma-Gegenstück.
Warum ergibt 5 / 2 in C den Wert 2?
Weil beide Operanden Ganzzahlen sind, führt C eine Ganzzahldivision durch und verwirft den Bruchteil. Mach eine Seite zu einem Gleitkommawert, um 2.5 zu erhalten: 5 / 2.0 oder caste einen Operanden mit (double)a / b.
Was ist der Unterschied zwischen i++ und ++i?
Beide erhöhen i um eins. ++i (Präfix) erhöht zuerst und liefert den neuen Wert; i++ (Postfix) liefert den alten Wert und erhöht danach. Als eigenständige Anweisung sind sie identisch - der Unterschied zählt nur, wenn das Ergebnis benutzt wird, wie bei int b = a++; gegenüber int b = ++a;.
Was ist Operatorvorrang in C?
Die Reihenfolge, in der Operatoren binden, wenn ein Ausdruck keine Klammern hat. *, / und % binden stärker als + und -, die stärker binden als Vergleiche, die stärker binden als &&, dann ||, dann die Zuweisung. Im Zweifel setze Klammern - sie kosten nichts und beseitigen die Frage.