16. Operatoren und Ausdrücke

Ausdrücke sind in C weitgehend wie in Python definiert. Eine wichtige Abweichung ist die Zuweisung – sie ist in Python ein Statement, in C ein Operator. Damit kann man Sachen wie

b = (c=4)+3;
c += (b*=2);

schreiben, wonach b 14 ist und c 18.

b*=2 ist dabei eine Kurzschreibweise für b = 2*b. Das geht auch für die anderen Operatoren. Auch Python hat diese inkrementierten Zuweisungen.

Casts

C weigert sich in der Regel, Variablen verschiedenen Typs einander zuzuweisen. Wenn das doch sinnvoll ist, muss man in der Regel den cast-Operator anwenden. Ein Cast ist wörtlich übersetzt eine Gussform oder ein Gips, “zwingt” also einen Typ in einen anderen. Dazu wird einfach der erwünschte Typ in Klammern vor den Ausdruck geschrieben: (int)(1/2.) oder 1/(double)2.

Im Augenblick ist für uns das casten zwischen int und double noch das Interessanteste: Das Programm

#include <stdio.h>

int main(void)
{
  float a=2.7182818;

  printf("%f %d\n", a, (int)a);
  return 0;
}

gibt 2.718282 2 aus – es werden also einfach die Nachkommastellen abgeschnitten (die übliche Rundung kann für positive Zahlen etwa mit (int)(a+0.5) erreicht werden).

Bei einer Zuweisung macht C das Abschneiden der Nachkommastelle übrigens von selbst. Trotzdem ist es gut, auch bei Umwandlungen, die C automatisch macht, Casts zu schreiben, insbesondere, um deutlich zu machen, dass der/die ProgrammiererIn sich bewusst ist, dass hier Typumwandlungen stattfinden.

Wirklich wichtig wird der Cast erst im Zusammenhang mit Pointern.

Casts scheinen etwas Ähnliches wie die “Umwandlungsfunktionen” in Python (int, str, list, ) zu tun.

Diese Ähnlichkeit ist aber nur oberflächlich. Der wichtigste Unterschied ist, dass die Konstruktoren von Pythons Datentypen (nichts anderes sind ja diese Umkwandlungsfunktionen) tatsächlich immer neue Werte erzeugen, während Casts eigentlich nur Vorschriften zur nicht dem Typ der Variable entsprechenden Interpretation ihrer Bits sind.

Prä- und Postinkrement

Den ++-Operator (analog für --) erhöht seinen Operanden um eins, und zwar, bevor (++a) oder nachdem (a++) der Ausdruck bewertet wird. Beispiel:

a=4;
printf("%d\n", ++a);
printf("%d\n", a++);
printf("%d\n", a);
gibt 5, 5 und 6 aus.

Hintergrund dieser Operatoren ist nicht nur, dass man damit toll kompakten Code schreiben kann, sondern auch, dass die meisten Prozessoren Instruktionen wie inc und dec haben, die sehr schnell inkrementieren und dekrementieren können – Compiler haben es leichter, diese Instruktionen zu verwenden, wenn sie von vorneherein wissen, dass das Programm sie haben möchte.

Heute ist das relativ irrelevant – einerseits ist es für praktisch alle heute üblichen CPUs egal, ob man eins oder eine andere Zahl addiert, sie brauchen immer die gleiche Zeit dafür, andererseits sind die Compiler auch schlau genug, ein add durch ein inc zu ersetzen, wenn das vorteilhaft ist.

Präzedenz

Wie schon in Python, so lassen sich auch die C-Operatoren der Präzedenz nach ordnen. In der folgenden Tabelle “binden” die Operatoren, die weiter oben stehen, stärker als die weiter unten stehenden. + steht beispielsweise unter dem binären *, also ist 4+5*6 äquivalent zu 4+(5*6), während >> noch unter + steht, 1+1>>4 also (1+1)>>4 entspricht. Letzteres ist bereits eine der Fallen in dieser Präzedenztabelle, da die meisten Menschen die Shiftoperatoren eher als eine Art Multiplikation im Kopf haben und die “Punkt vor Strich”-Regel hier gerade zum falschen Ergebnis führt.

Assoziativität heißt, ob C Ausdrücke mit den entsprechenden Operatoren von links nach rechts oder umgekehrt liest. a+b-c wird beispielsweise linksassoziativ, also als (a+b)-c gelesen, während a=b=c rechtsassoziativ ist, also a=(b=c) entspricht. Wer von solchen Feinheiten abhängt, möge in der Hölle schmoren. Eine goldene Regel des C-Programmierens ist, nicht allzu oft in diese Tabelle zu gucken und lieber rechtzeitig Klammen zu setzen.

Nicht alle hier erwähnten Operatoren haben wir schon gesehen.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Betrachtet das folgende Programm:

#include <stdio.h>

int main(void)
{
  int a=8;

  printf("%d\n", a&8==(1<<3));
  return 0;
}

Versucht, vorherzusagen, was das Programm tut, probiert es dann aus. Kompiliert es mit dem -Wall-Flag (oder dem Äquivalent eures Compilers). Könnt ihr die (hoffentlich) erscheinende Warnung erklären? Was gibt das Programm wirklich aus und warum?

(2)

Klammert die folgenden C-Ausdrücke nach den Präzedenzregeln vollständig (d.h. alle Zugehörigkeiten sind explizit durch Klammern markiert).

Beispiel: 5+b%8 wird zu (5+(b%8))

  1. 1<<3+1
  2. a = b = c
  3. a < b < c
  4. a = b==a*b/5
  5. a=15 || a&8==1<<3 && b<=5

(3)

Ich habe oben behauptet, dass sich C in der Regel weigert, Variblen verschiedenen Typs zuzuweisen. Wenn wir also einen Typ string und einen Typ int haben, dann wäre das folgende Programm inkorrekt:

string a="bla";
int b=5;
b = a;

(nehmt vorläufig einfach an, es gebe einen Typ string). Demgegenüber hindert einen natürlich niemand daran, in Python

a = "bla"
b = 5
b = a

zu schreiben.

Erklärt, was diese Differenz mit den unterschiedlichen Variablenkonzepten von C und Python zu tun hat.


Markus Demleitner

Copyright Notice