Operatoren
Zuweisung
a = 5
- Linke Seite der Zuweisung
→ Variable (lvalue) - Rechte Seite der Zuweisung
→ Ausdruck dessen Ergebnis zugewiesen wird (rvalue) - Unterschiedliche Typen rechts und links ⇒ Typkonvertierung
- Ergebnis der Zuweisung ist wieder ein Wert
a = b = c = d = e = 0;a = (b = (c = (d = (e = 0))));
Die Unterscheidung in lvalue und rvalue ist bei C eine recht wichtige, weil der C-Standard sich häufig auf diese Begriffe bezieht. Außerdem neigt der Compiler dazu, Fehlermeldungen ebenfalls mit einem Hinweis auf diese zu garnieren.
Folgendes (fehlerhafte) C-Programm-Fragment
int k = 0;
k++ = 3;
führt beim gcc zu der Fehlermeldung
<source>:4:6: error: lvalue required as left operand of assignment
4 | k++ = 3;
| ~^~
Interessanterweise ist ++k = 3; korrekt: k++ ist kein lvalue, ++k aber schon.
Folgende Ausdrücke sind in C ein lvalue:
- Name einer Variable (
k) - Zuweisung, wenn rechts wieder ein lvalue steht (
k = j) - Pre-Increment oder Pre-Dekrement (
++k,--k) - dereferenzierter Pointer (
*p) - Array-Elementzugriff (
a[3]) - Zugriff auf ein
struct-Member (s.m) - Zugriff auf ein
struct-Member über einen Pointer (s->moder(*s).m) - Ternärer Ausdruck, wenn alle Rückgaben lvalues sind (
(x < y ? y : x))
Die Regel ist, dass nur diese lvalues auf der linken Seite einer Zuweisung vorkommen dürfen. Auf der rechten Seite der Zuweisung dürfen rvalues und lvalues stehen.
Ausdruck
Ein Ausdruck (expression) verknüpft Operanden mithilfe eines Operators
- Operand (operand) ist der Wert (Variablen oder Literale) der verknüpft werden soll
- Operator (operator) legt die Art der Verknüpfung fest (z. B. Addition mit
+)
19 + 7
vermoegenVonBillGates + 1000000
Variablen an sich sind nutzlos, solange man mit den Werten, die darin gespeichert sind nicht rechnen kann. Daher benötigt man, wie in der Mathematik Operatoren, die es erlauben aus bekannten Werten, neue Werte zu berechnen. Die Berechnung eines neuen Wertes erfolgt mit Hilfe eines Ausdrucks, z. B. 2 + 7. Ein Ausdruck besteht aus
- Operanden die Werte miteinander verknüpfen (im Beispiel
2und7) - Operatoren welche die Art der Verknüpfung festlegen (im Beispiel
+)
Bei typsicheren Sprachen, wie C, ist eine der wichtigen Fragen, welchen Typ das Ergebnis der Operation hat. Bei vielen Operatoren hängt der Typ des Ergebnisses vom Typ der Operanden ab. So ist z. B. der Typ von 5 + 2 int, weil beide Operanden vom Typ int sind. Der Typ von 5 + 2.0 ist allerdings vom Typ double, weil ein Operant vom Typ double ist.
Arten von Operatoren
Drei Arten von Operatoren
- Unäre Operatoren haben nur einen Operanden
- Syntax:
op Operand - Beispiel: Negation einer Zahl (
a = -b) - Binäre Operatoren verknüpfen zwei Operanden
- Syntax:
Operand1 op Operand2 - Beispiel: Multiplikation zweier Zahlen (
a = b * c) - Ternäre Operatoren verknüpfen drei Operanden (selten)
- Syntax:
Operand1 op Operand2 op Operande3 - Beispiel: Bedingter Ausdruck (
x = a > b ? a : b)
Man kann Operatoren danach klassifizieren, wie viele Operanden sie haben. Am häufigsten sind Operatoren mit zwei Operanden (binäre Operatoren). Deutlich seltener sind solche mit nur einem Operanden (unäre Operatoren). Mit drei Operanden (ternärer Operator) existiert in C nur ein einziger Operator, der sogenannte Fragezeichen-Operator, der später noch behandelt werden wird.
In C gibt es folgende unären Operatoren:
- Negation (
-)a = -b - Increment(
++)a++ - Decrement (
--)a-- - NOT (
!)b = !a - Complement (
~)b = ~a - Address-Operator (
&)p = &a - Indirektion (
*)*p = 7 sizeof()x = sizeof(int)- Cast (
(TYPE))a = (int) 2.0
Rangfolge der Operatoren
- Rangfolge (priority) legt fest, welche Operatoren zuerst ausgewertet werden (vgl. „Punkt vor Strichrechnung“ in der Mathematik)
- Assoziativität legt fest, in welcher Richtung Operatoren mit gleicher Rangfolge ausgewertet werden
- links-assoziativ: Auswertung erfolgt von Links nach Rechts
3 + 5 + 6⇔(3 + 5) + 6 - rechts assoziativ: Auswertung erfolgt von Rechts nach Links
a = b = c⇔a = (b = c) - Durch Klammern kann die Rangfolge geändert werden
(2 + 2) * 2 = 8 - Im Zweifelsfall sollte man lieber Klammern setzen, als sich auf die Rangfolge zu verlassen:
2 + (2 * 2)
Da häufig mehrere Operatoren aufeinandertreffen, muss man festlegen, welche Operatoren zuerst ausgewertet werden. Diese Reihenfolge wird durch die Rangfolge der Operatoren eindeutig bestimmt, d. h. für jeden Operator ist festgelegt, vor welchen anderen Operatoren er ausgewertet wird.
Wenn ein Ausdruck mehrere Operatoren mit identischer Rangfolge enthält, wird die Richtung der Ausführung durch die Assoziativität bestimmt.
Zum Beispiel sind Multiplikations- und Modulo-Operator linksassoziativ. Daher sind die beiden folgenden Anweisungen austauschbar:
int result = 5 * 70 % 6; /* ergibt 2 */
int result = (5 * 70) % 6; /* ergibt 2 */
Nicht aber
int result = 5 * (70 % 6); /* ergibt 20 */
Rechtsassoziativ sind zum Beispiel die Vorzeichenoperatoren (+, -) oder die Zuweisung. Hier wird der Ausdruck von rechts nach links ausgewertet.
int summe;
int result = summe = 12; /* summe ist 12, result ist 12 */
Bitweise Operatoren
In C wird sehr viel mit maschinennahen Daten operiert, weswegen die bitweisen Operatoren eine besondere Bedeutung haben.
bitweisen Operatoren
- arbeiten auf den Bits eines Wertes
- Eingabe: zwei Zahlen
&,^,<<,>>und|bzw. eine Zahl bei~ - Ergebnis: eine Zahl
| Operator | Bedeutung | Beispiel | Beispiel | Ergebnis |
|---|---|---|---|---|
& | Bitweise UND | x & y | 13 & 1 | 1 |
| | Bitweise ODER | x | y | 13 | 1 | 13 |
^ | Bitweise XOR | x ^ y | 13 ^ 11 | 6 |
~ | Bitweise NOT | ~ x | ~13 | -14 |
<< | Shift left | x << y | 13 << 1 | 26 |
>> | Shift right | x >> y | 13 >> 1 | 6 |
Die Operatoren &, ^ und |arbeiten auf den einzelnen Bits einer Zahl, d. h. man kann es sich so vorstellen, als ob jede einzelne Stelle einer Zahl in ihrer Binärdarstellung des einen Operanden mit dem korrespondierenden Bit des anderen Operanden verknüpft wird.
1101 (13) 1101 (13) 1101 (13)
& 0001 ( 1) | 0001 ( 1) ^ 1011 (11)
------------ ------------ ------------
= 0001 ( 1) = 1101 (13) = 0110 ( 6)
Der Not-Operator (oder auch Complement Operator) bildet das 1er-Komplement seines Operanden. Für das 2er-Komplement muss noch 1 addiert werden.
0000000000001101 (13)
~ 1111111111110010 (-14)
Die Shift-Operatoren verschieben die Bits nach rechts bzw. links.
1101 >> 1 = 0110 (6) 1101 << 1 = 11010 (26)
Logische Operatoren
- Logische Operatoren verknüpfen Wahrheitswerte miteinander
- Eingabe: zwei Wahrheitswerte (true oder false)
&&und||bzw. ein Wahrheitswert bei! - Ergebnis: eine Wahrheitswert (true oder false)
| Operator | Bedeutung | Beispiel | Ergebnis |
|---|---|---|---|
&& | und | true && false | false |
|| | oder | true || false | true |
! | nicht | !false | true |
Wie bereits erläutert, kennt C keine wirklichen logischen Datentypen, sondern bildet – selbst im neusten Standard – wahr und falsch auf 1 und 0 ab. Deswegen kann man anstelle der vordefinierten Werte true und false (aus stdbool.h) auch einfach mit den Zahlen 0 und 1 hantieren:
printf("1 || 0 = %d\n", 1 || 0);
printf("1 && 0 = %d\n", 1 && 0);
printf("1 && 1 = %d\n", 1 && 1);
printf("!1 = %d\n", !1);
printf("!0 = %d\n", !0);
1 || 0 = 1
1 && 0 = 0
1 && 1 = 1
!1 = 0
!0 = 1
Sie können sogar beliebige Zahlen einsetzen, d. h. auch Ausdrücke wie 33 && 99 schreiben. Allerdings geben moderne C-Compiler hier eine Warnung aus, dass man möglicherweise nicht weiß, was man tut.
Man kann die bitweisen Operatoren auch zur Auswertung von logischen Ausdrücken verwenden, weil bei der Verwendung von 0 und 1 für false und true, dasselbe Ergebnis heraus kommt: 1 & 0 → 0, 1 && 0 → 0 etc. Trotzdem sollte man das nicht tun, da die logischen Operatoren eine spezielle Eigenschaft haben: Sie sind als Kurzschlussoperatoren implementiert.
&& und || sind sogenannte Kurzschlussoperatoren
&&und||brechen die Auswertung ab, sobald das Ergebnis feststeht- bei
&&ist ein Ausdruckfalse⇒ gesamter Ausdruck istfalse - bei
||ist ein Ausdrucktrue⇒ gesamter Ausdruck isttrue &und|werten den gesamten Ausdruck aus und berechnen dann erst das Ergebnis- Normalerweise braucht man
&und|nicht und sollte immer&&und||verwenden
Es gibt in C zwei Arten von Operatoren für Und und Oder, die bitweisen | und & und die sogenannten Kurzschlussoperatoren || und &&. Bei den bitweisen Operatoren werden alle (Teil-)Ausdrücke ausgewertet und erst dann wird der Operator angewandt. Bei den Kurzschlussoperatoren wird die Auswertung streng von links nach rechts durchgeführt und sie wird abgebrochen, sobald der logische Wert des Gesamtausdrucks feststeht.
Da ein Abbruch der Auswertung in nahezu allen Fällen im Interesse des Programmierers liegt, sollten immer die Kurzschlussoperatoren zum Einsatz kommen.
Das folgende Beispiel zeigt, warum die bitweisen Operatoren problematisch für logische Tests sind:
int *p = NULL;
if (p != NULL & *p > 7) {
/* Absturz, weil p dereferenziert wird */
printf("%s\n", "Wert ist größer als 7");
}
Das Programm stürzt ab, weil im Ausdruck p != NULL & *p > 7 erst beide Operanden ausgewertet werden, bevor die bitweise Operation durchgeführt wird. Da p aber NULL ist, stürzt das Programm bei dem Versuch, den Pointer über *p* zu dereferenzieren ab. Korrekt müsste die Bedingung p != NULL && *p > 7 heißen. Durch den Kurzschlussoperator wird der zweite Teil des Ausdrucks nie ausgewertet, wenn p den Wert NULL hat.
Wie fügen sich die logischen Operatoren in die Rangfolge der anderen Operatoren ein? Alle stehen vor der Zuweisung und Negation und Vergleich haben einen höheren Rang als die UND- bzw. ODER-Verknüpfung.
| Rang | Operator | Assoziativität |
|---|---|---|
| 1. | ! | R |
| 2. | == | L |
| 2. | != | L |
| 3. | && | L |
| 4. | || | L |
| 5. | = | R |
Diese Rangfolge hat den Vorteil, dass man in den gängigsten Fällen auf Klammern verzichten kann. Insbesondere kann man das Ergebnis einer logischen Verknüpfung einer Variable zuweisen, ohne klammern zu müssen: int a = !b == c;
Arithmetische Operatoren
Arithmetische Operatoren für ganze Zahlen
- Eingabe: zwei ganze Zahlen (
char,short,int,long) - Ergebnis: eine ganze Zahl (
int,long) - Division durch 0 führt zu einem Laufzeitfehler (
5 / 0)
| Operator | Bedeutung | Beispiel | Ergebnis |
|---|---|---|---|
+ | Addition | 39 + 3 | 42 |
- | Subtraktion | 26 - 3 | 23 |
* | Multiplikation | 19 * 7 | 133 |
/ | Division | 19 / 7 | 2 |
% | Modulo | 19 % 7 | 5 |
Die arithmetischen Operatoren für ganze Zahlen sind alle bereits aus der Mathematik bekannt, werden nur teilweise durch andere Zeichen repräsentiert.
Der Modulo-Operator kommt in der Schulmathematik selten zum Einsatz, hat in der Informatik aber eine überragende Rolle, da viele Problemlösungen auf Module-Arithmetik beruhen, z. B. die Hashtabellen.
Arithmetische Operatoren für Fließkommazahlen
- Eingabe: zwei Zahlen (davon mindestens eine Fließkommazahl)
- Ergebnis: eine Fließkommazahl
| Operator | Bedeutung | Beispiel | Ergebnis |
|---|---|---|---|
+ | Addition | 39.1 + 3.3 | 42.4 |
- | Subtraktion | 26.0 - 3 | 23.0 |
* | Multiplikation | 19.3 * 7.8 | 150.54 |
/ | Division | 37.41 / 4.3 | 8.7 |
% | Modulo | 19.0 % 7 | 5.0 |
Zusammengefasste Operatoren
Zuweisung und Rechnung können zusammengefasst werden (häufig unübersichtlich)
| Operator | Bedeutung |
|---|---|
a += b | a = a + b |
a -= b | a = a - b |
a *= b | a = a * b |
a /= b | a = a / b |
a %= b | a = a % b |
Man kann den Zuweisungsoperator mit den arithmetischen Operatoren kombinieren. Die so entstehenden Konstrukte sind manchmal unübersichtlich und sollten mit Vorsicht eingesetzt werden.
Vergleichsoperatoren
Numerische Vergleichsoperatoren verknüpfen Zahlen miteinander
- Entsprechen den bekannten Operatoren aus er Mathematik
- Eingabe: zwei ganze Zahlen oder Fließkommazahlen
- Ergebnis: eine Wahrheitswert (true oder false)
| Operator | Bedeutung | Beispiel | Ergebnis |
|---|---|---|---|
< | kleiner | 8 < 9 | true |
> | größer | 9 > 4 | true |
<= | kleiner gleich | 7 <= 3 | false |
>= | größer gleich | 7 >= 3 | true |
== | gleich | 6 == 5 | false |
!= | ungleich | 6 != 5 | true |
Die Tabelle für die Fließkommazahlen ist identisch – abgesehen von den Beispielen.
| Operator | Bedeutung | Beispiel | Ergebnis |
|---|---|---|---|
< | kleiner | 8.0 < 9.0 | true |
> | größer | 9.0 > 4.0 | true |
<= | kleiner gleich | 7.0 <= 3.0 | false |
>= | größer gleich | 7.0 >= 3.0 | true |
== | gleich | 6.0 == 5.0 | false |
!= | ungleich | 6.0 != 5.0 | true |
Vergleich zweier Fließkommazahlen mit ==
- Fließkommazahlen sind mit Ungenauigkeit behaftet
- Vergleich mit
==schlägt häufig wegen Rundungsfehlern fehl - Daher besser mit Epsilon-Umgebung vergleichen
statta == bbesser(a + epsilon > b) && (a - epsilon < b)
double a = 3.0;
double b = 0.1;
double c = a * b; /* -> 0.30000000000000004 */
/* schlägt wegen Rundungsfehler fehl */
printf("%d\n", c == 0.3); /* false (0) */
/* besser *
printf("%d\n", (c - 0.00001 < 0.3) && (c + 0.00001 > 0.3)); /* true (1) */
Inkrement und Dekrement-Operator
Für die sehr häufige Operation des Erhöhen und Erniedrigen einer Zahl um den Wert 1 gibt es spezielle Operatoren (Inkrement-Operator und Dekrement-Operator) → Nur auf lvalue anwendbar
- Präfix-Operator (
++aund--a) verändern den Wert der Variable und geben diesen zurück - Postfix-Operator (
a++unda--) geben den Wert der Variable zurück und verändern sie erst danach
| Operator | Bedeutung |
|---|---|
a++ | a = a + 1 |
++a | a = a + 1 |
a-- | a = a - 1 |
--a | a = a - 1 |
Eine der häufigsten arithmetischen Operationen ist das Erhöhen oder Erniedrigen einer ganzen Zahl um den Wert Eins. Daher gibt es für diesen Fall zwei (genaugenommen vier) Operatoren, nämlich ++ und --, die auf eine Variable (nicht auf einen Wert) angewendet werden können.
Eine für Einsteiger schwer verständliche Eigenschaft der Operatoren ist, welchen Wert der Ausdruck selber zurückliefert. In jedem Fall wird die Variable erhöht oder erniedrigt. Von der Position des Operators (vor oder hinter der Variable) hängt aber ab, welchen Wert der Ausdruck zurückgibt. Steht der Operator vor der Variable, wird zuerst die Variable verändert und dann wird das Ergebnis zurückgeben. Steht der dahinter, wird erst der Wert der Variable zurückgegeben und dann der Wert erhöht.
Steht der Operator nicht in einer Zuweisung oder einer Bedingung, ist es egal, welche Form man verwendet. Es hat sich aber bei den meisten Programmierern eingebürgert, dann die Postfix-Schreibweise (a++) zu verwenden.
int a = 7;
int b = a++;
printf("a=%d, b=%d\n", a, b); /* a=8, b=7 */
int a = 7;
int b = a--;
printf("a=%d, b=%d\n", a, b); /* a=6, b=7 */
int a = 7;
int b = ++a;
printf("a=%d, b=%d\n", a, b); /* a=8, b=8 */
int a = 7;
int b = --a;
printf("a=%d, b=%d\n", a, b); /* a=6, b=6 */
Komma-Operator
Ein verwirrender Operator in C ist der Komma-Operator. Er erlaubt es an Stellen, die nur einen Ausdruck erlauben, mehrere Ausdrücke zu verwenden, die alle ausgewertet werden.
- Syntax:
Ausdruck1, Ausdruck2 Ausdruck1wird ausgewertet, das Ergebnis wird weggeworfenAusdruck2ausgewertet und dessen Ergebnis wird verwendet- nur sinnvoll, wenn
Ausdruck1einen Seiteneffekt hat
int x = 42;
int y = 23;
int z;
z = (x++, y++);
printf("x=%d, y=%d, z=%d\n", x, y, z); /* x=43, y=24, z=23 */
Man braucht den Komma-Operator nicht sehr häufig, er ist aber ausgesprochen nützlich, wenn man in einer for-Schleife mit mehreren Laufvariablen arbeiten möchte, wie das folgende Beispiel zeigt.
int x, y;
for (x = 0, y = 1; x < 10; x++, y *= 2) {
printf("x=%d, y=%d\n", x, y);
}
x=0, y=1
x=1, y=2
x=2, y=4
x=3, y=8
x=4, y=16
x=5, y=32
x=6, y=64
x=7, y=128
x=8, y=256
x=9, y=512