Operatoren

Video zum Kapitel

Link zu YouTube

Ausdruck

Ein Ausdruck ist eine Kombination von Variablen, Literalen, Operatoren und Funktionen, die zusammen einen Wert ergeben. Ein Ausdruck kann eine Berechnung, einen Vergleich oder eine andere Art der Wertzuweisung darstellen.

Ein Ausdruck (expression) verknüpft Operanden mithilfe eines Operators

  • Operand ist der Wert (Variablen oder Literale) der verknüpft werden soll
  • 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 oder arbeiten 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 mithilfe eines Ausdrucks, z. B. 2 + 7. Ein Ausdruck besteht aus

  • Operanden die Werte miteinander verknüpfen (im Beispiel 2 und 7)
  • 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.

Das Spannende an den Ausdrücken sind also die Operatoren, die man in ihnen verwenden kann.

Operatoren

Operatoren in Programmiersprachen werden verwendet, um bestimmte Operationen auf Variablen oder Werten auszuführen. Sie ermöglichen es, Berechnungen anzustellen, Vergleiche zu ziehen oder logische Ausdrücke zu evaluieren. Es gibt verschiedene Arten von Operatoren.

  • Zuweisungsoperatoren (z. B. =, +=, -=)
    Weisen Variablen Wert zu oder verändern sie
  • Arithmetische Operatoren (z. B. +, -, *, /)
    Führen mathematische Berechnungen durch
  • Vergleichsoperatoren (z. B. ==, !=, <, >)
    Vergleichen zwei Werte miteinander und geben Wahrheitswert zurück
  • Logische Operatoren (z. B. &&, ||)
    Verknüpfen logische Bedingungen miteinander

In C gibt es auch kombinierte Operatoren, wie +=, die sowohl arithmetische als auch Zuweisungsoperatoren sind.

Zuweisung

Der einfachste Operator ist die Zuweisung, welche einer Variable einen Wert zuweist.

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; in C ebenfalls kein lvalue, in C++ aber korrekt.

Folgende Ausdrücke sind in C ein lvalue:

  • Name einer Variable (k)
  • Zuweisung, wenn rechts wieder ein lvalue steht (k = j)
  • dereferenzierter Pointer (*p)
  • Array-Elementzugriff (a[3])
  • Zugriff auf ein struct-Member (s.m)
  • Zugriff auf ein struct-Member über einen Pointer (s->m oder (*s).m)

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.

Wenn Sie einen C++-Compiler verwenden, erweitert dieser die möglichen lvalues noch um:

  • Pre-Increment oder Pre-Dekrement (++k, --k)
  • Ternärer Ausdruck, wenn alle Rückgaben lvalues sind ((x < y ? y : x))

Ein reiner C-Compiler würde diese beiden Formen aber abweisen. Deswegen sollte man sie vermeiden, auch weil sie ohnehin recht obskur und normalerweise nicht sinnvoll sind.

Dadurch, dass die Zuweisung selbst wieder einen Wert liefert, nämlich den Wert des rechten Ausdrucks, kann man sie in C verketten. Allerdings ist die hier gezeigte Verkettung nicht der Hauptanwendungsfall für diesen Effekt, sondern man nutzt ihn, wenn man Zuweisungen in Kontrollstrukturen verwendet.

int fd;
char buffer[1024];

if ((fd = open("file", O_RDONLY)) < 0) {
    /* Fehlerbehandlung */
}

read(fd, buffer, 1024);

Hier signalisiert die Funktion open einen Fehler mit einem Rückgabewert kleiner als 0 (Fehlerbehandlung in C wird später noch besprochen). Wenn kein Fehler auftritt, braucht man aber den Rückgabewert für die weiteren Operationen. Durch die Kombination von Zuweisung und Vergleich kann man dies elegant lösen.

Arten von Operatoren

Wenn man etwas Licht in den Dschungel der Operatoren bringen will, kann man sie in einem ersten Schritt nach der Anzahl der möglichen Operanden aufteilen. Manche Operatoren wirken nur auf einen Operanden, die meisten auf zwei und ein einziger Operator kann drei Operanden haben.

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
  • Adress-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 = ca = (b = c)
  • Durch Klammern kann die Rangfolge geändert werden
    (2 + 2) * 2 = 8

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 */

Im Zweifelsfall sollte man lieber Klammern setzen, als sich auf die Rangfolge zu verlassen: 2 + (2 * 2).

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.

16bit-Integer
      0000000000001101 (13)
~     1111111111110010 (-14)

Die Shift-Operatoren verschieben die Bits nach rechts bzw. links.

1101 >> 1 = 0110 (6)        1101 << 1 = 11010 (26)

Der Shift-Operator muss sich bei vorzeichenbehafteten Zahlen speziell verhalten, damit er weiterhin einer Multiplikation oder Division mit 2 entspricht.

Der C-Standard legt nicht fest, wie vorzeichenbehaftete Zahlen bei bitweisen Shift-Operatorn behandelt werden müssen. Die meisten C-Compiler behandeln vorzeichenbehaftete Zahlen allerdings speziell: Bei vorzeichenbehafteten Zahlen (z. B. int) wird bei einem Shift nach rechts (>>) oder links (<<) ein arithmetischer Shift verwendet. Das bedeutet, dass das Vorzeichenbit (das höchste Bit) während der Verschiebung beibehalten wird, um das Vorzeichen der Zahl zu bewahren: -8 >> 1 ergibt -4, -8 << 1 ergibt -16.

Handelt es sich um eine vorzeichenlose Zahl (z. B. unsigned int), wird ein logischer Shift durchgeführt und alle Bits werden verschoben: 8 >> 1 ergibt 4, 8 << 1 ergibt 16.

Man kann sich aber auf dieses Verhalten nicht verlassen.

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);
Ausgabe
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 herauskommt: 1 & 00, 1 && 00 etc. Trotzdem sollte man das nicht tun, da die logischen Operatoren eine spezielle Eigenschaft haben: Sie sind als Kurzschluss-Operatoren implementiert.

&& und || sind sogenannte Kurzschluss-Operatoren

  • && und || brechen die Auswertung ab, sobald das Ergebnis feststeht
    • bei && ist ein Ausdruck false ⇒ gesamter Ausdruck ist false
    • bei || ist ein Ausdruck true ⇒ gesamter Ausdruck ist true
  • & 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 (z. B. char, short, unsigned int, long)
  • Ergebnis: eine ganze Zahl (z. B. int, long, unsigned int)
  • 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.

Integer Promotion

Ganzzahlige Typen, die kleiner als int sind (char, short), werden bei der Durchführung einer Operation erweitert (Integer Promotion). Wenn alle Werte des ursprünglichen Typs als int dargestellt werden können, wird der Wert des kleineren Typs in ein int konvertiert; andernfalls wird er in ein unsigned int konvertiert.

char c1, c2;
c1 = c1 + c2;

Hier werden c1 und c2 zu einem int erweitert, addiert und das Ergebnis wird wieder zu einem char verkürzt. Hierdurch werden Fehler durch einen Überlauf bei der Addition vermieden, sodass der folgende Ausdruck korrekt ausgewertet wird:

signed char cresult, c1, c2, c3;
c1 = 100;
c2 = 3;
c3 = 4;
cresult = c1 * c2 / c3; /* -> 75 */

Integer Conversion Rank

Für die Umwanlung zwischen den Integer-Typen, muss man das Konzept des Integer Conversion Rank heranziehen.

Jeder ganzzahlige Typ hat einen sogenannten ganzzahligen Umwandlungsrang. Dieser basiert auf dem Konzept, dass jeder ganzzahlige Typ mindestens so viele Bits enthält wie die Typen, die darunter eingestuft sind. (C-Standard 2011, Unterabschnitt 6.3.1.1).

  • Keine zwei unterschiedlichen signed Integer Typen dürfen denselben Rang haben, auch wenn sie dieselbe binäre Darstellung haben.
  • Bei zwei signed Typen, bedeutet höhere Präzision (mehr Bit) einen höheren Rang.
  • Es muss für den Rang der signed Typen gelten
    • signed long long int vor
    • signed long int vor
    • signed int vor
    • signed short int vor
    • signed char.
  • Der Rang der unsigned Typen muss dem Rang des entsprechenden signed Typen entsprechen.
  • Der Rang von char muss dem Rang von signed char und unsigned char entsprechen.
  • Der Rang von _Bool muss kleiner sein als der Rang aller anderen Ganzzahltypen.

Arithmetische Umwandlung

Der ganzzahlige Umwandlungsrang wird bei den üblichen arithmetischen Umwandlungen verwendet, um zu bestimmen, welche Umwandlungen durchgeführt werden müssen, um eine Operation auf gemischte Ganzzahltypen zu unterstützen.

Nach der Erweiterung auf den größeren Typ werden folgende Regeln auf den arithmetischen Ausdruck angewendet (Notation \( t_1 == typ(op_1) \):

  1. \( t_1 = t_2 \)
    ⇒ keine Konvertierung.
  2. \( sign(t_1) = sign(t_2) \wedge rang(t_1) > rang(t_2) \)
    ⇒ \( t_2 \) → \( t_1 \)
  3. \( t_1 = unsigned \wedge t_2 = signed \wedge rang(t_1) \ge rang(t_2) \)
    ⇒ \( t_2 \) → \( t_1 \)
  4. \( |t_1| = |t_2| \wedge t_1 = unsigned \wedge t_2 = signed \wedge t_1 \subseteq t_2 \)
    ⇒ \( t_1 \) → \( t_2 \)
  5. \( t_1 = unsigned \wedge t_2 = signed \wedge t_1 \nsubseteq t_2 \)
    ⇒ \( t_1 \) → \( |t_2|_{unsigned} \) und \( t_2 \) → \( |t_2|_{unsigned} \)

Das folgende Beispiel passiert, wenn man diese Konvertierungsregeln nicht beachtet:

Typumwandlung führt zu unerwartetem Ergebnis
unsigned int a = 1;
signed int b = -1;
printf("%s\n", (b < a) ? "Smaller" : "Not smaller"); /* Not smaller */

Wie kann das Ergebnis passieren? Beide Datentypen sind gleich groß, unterscheiden sich aber im Vorzeichen. signed int kann nicht den gesamten Bereich von unsigned int erfassen, also greift Regel 4 und b wird in einen unsigned int konvertiert. -1 wird dann zu UINT_MAX und das ist größer als 1, sodass die Bedingung falsch ist.

Das Verhalten ändert sich, wenn man die Datentypen ändert:

Integer Promotion führt zu erwartetem Ergebnis
unsigned short a = 1;
signed short b = -1;
printf("%s\n", (b < a) ? "Smaller" : "Not smaller"); /* Smaller */

Hier wird zuerst eine Integer-Promotion durchgeführt und der Datentyp short wird zu int. Da sowohl ein unsigned short, als auch ein signed short in einen signed int passen, werden sie entsprechend umgewandelt. Da die Typen dann gleich sind, greift Regel 1 und es wird keine weitere Typkonvertierung mehr durchgeführt. Damit ist das Ergebnis wie erwartet.

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.

Da es sich um eine Zuweisung handelt, hat sie selbst wieder einen Wert, den man weiterverwenden kann.

int a = 3;
int b;
b = (a *= 3);
printf("%d\n", b); /* -> 9 */

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

Wie bereits oben beschrieben, muss man bei den Vergleichsoperatoren die Umwandlung der Integer-Werte berücksichtigen, um nicht auf ungewöhnliche Ergebnisse hereinzufallen.

Eine weitere Falle lauert bei den Fließkommazahlen, wenn man einen Vergleich durchführt.

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
    statt a == b besser (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

Ein berühmter Operator in C, der sogar namensgebend für C++ geworden ist, dient dem Erhöhen oder Erniedrigen einer Zahl um den Wert 1.

Inkrement-Operator und Dekrement-Operator → Nur auf lvalue anwendbar

  • Präfix-Operator (++a und --a) verändern den Wert der Variable und geben diesen zurück
  • Postfix-Operator (a++ und a--) geben den Wert der Variable zurück und verändern sie
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 Operator hinter der Variable, wird erst der Wert der Variable zurückgegeben und dann der Wert erhöht.

Außerhalb 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
    • Ausdruck1 wird ausgewertet, das Ergebnis wird weggeworfen
    • Ausdruck2 ausgewertet und dessen Ergebnis wird verwendet
  • nur sinnvoll, wenn Ausdruck1 einen 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);
}
Ausgabe
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

Copyright © 2025 Thomas Smits