Ausdrücke

Ein Ausdruck, als deutsche Übersetzung von Expression, ist ein Stück Programmcode, was einen Wert repräsentiert und so geschrieben ist, dass ein Compiler den Wert als konstante Zahl erkennen kann oder ausrechnen kann, wenn das Programm läuft.

atomare Ausdrücke

Atomar bedeutet unteilbar, also nicht weiter zerlegbar. Der Wert ist direkt ablesbar und es muss nicht mehr gerechnet werden:

Einige Beispiele:

4
32
040
0x20
32.
47.11
' '
'a'
'0'
'\0'
"Hallo Welt"

Die Zahl 4 repräsentiert den Wert 4, Ziffern hintereinander gesetzt entsprechend dezimale Zahlen, zum Beispiel 32 repräsentiert den Wert 32. Im Dezimalzahlensystem (Zehnerzahlensystem) bedeutet 32, dass 3 * 10^1 + 2 * 10^0 ist, also 30 + 2 = 32. Alle diese Werte sind vom Datentyp int. Fließkommazahlen enthalten einen Punkt, wie es der amerikanischen Zahlenschreibweise entspricht. Dezimale Zahlen beginnen in C nie mit 0, die 0 kennzeichnet andere Zahlensysteme. Folgt auf die 0 eine Ziffer, so ist das Oktalzahlensystem (Oktal = 8) gemeint: 4 * 8^1 + 0 + 0 * 8^0 ergibt 4 * 8 + 0 = 32. Folgt der 0 ein 'x', so ist das Hexadezimalsystem gemeint (Hexadezimal = 16): 2 * 16^1 + 0 * 16 = 32. Als weitere Möglichkeiten existieren die einfachen Hochkommas, die aus einem einzelnen Zeichen den ASCII-Wert zurückliefern: ' ' ist als ASCII-Wert 32. Das 'a' besitzt den Wert 65 und die '0' besitzt den Wert 48. Sonderzeichen sind erlaubt, ein Nullbyte ist entsprechend mit '\0' zu erreichen. Die doppelten Anführungszeichen liefern die Adresse auf das erste Zeichen zurück, also hier auf das 'H'. Im Speicher liegt auf den nachfolgenden Bytes der nachfolgende Text.

Ein atomarer Ausdruck hat einen festen Datentyp. Alle derartigen Werte gelten als konstant, das bedeutet, dass die Werte nicht überschrieben werden dürfen.

Funktionsaufrufe

Funktionen fassen Operationen zusammen und geben ein Ergebnis zurück. Auch ein Funktionsaufruf ist damit ein atomarer Ausdruck, dessen Ergebnis aber frei programmiert werden kann:

int AddiereUndErhoeheUmZwei( int a, int b )
{
  return a + b + 2;
}
 
int main( void )
{
  AddiereUndErhoeheUmZwei( 2, 4 );      // Atomarer Ausdruck: Wert 8
}

Zusammengesetzte Ausdrücke

Mit Hilfe von Operatoren werden atomare Ausdrücke zusammengesetzt. Auch hierzu einige Beispiele:

4 + 32
4 * 0x20
'4' - '0'
1 & 0
7 > 8
'0' == 48
x == 7
!1
!2
!0
1 && 0
x = 7
x += 7

mathematische Ausdrücke

Der Ausdruck 4 + 32 ist nicht mehr atomar, der Wert des Ausdrucks ist nicht mehr direkt abzulesen, da der Computer muss ihn zunächst noch berechnen. Der Wert ist entsprechend 36. Das wird ein moderner Compiler während des kompilierens einmal ausrechnen und nicht während das Programm abläuft, wichtig ist jedoch, dass der Ausdruck erst zusammengefasst werden muss, er also einen Operator enthält. Bei 4 + 32 werden zwei Integerzahlen mit Hilfe der Addition miteinander verknüpft, das Ergebnis ist wieder ein Integer: 36. Die Schreibweise, wie ein Integer geschrieben ist, spielt dabei keine Rolle: 4 * 0x20 entspricht 4 * 32, das Ergebnis ist also ein Integer mit dem Wert 128. Auch ASCII-Zeichen lassen sich so verbinden: Weiß man, dass man ein ASCII-Zeichen hat, das eine Ziffer enthält, so kann man einfach '0' abziehen. '4' hat den Wert 52, zieht man davon '0', also 48, ab, so erhält man den Integer-Wert 4.

bool'sche Ausdrücke

Auch Vergleichsoperationen liefern Werte zurück, die als Integer lesbar sind. 7 > 8 ist false und die Integerentsprechung von false ist 0. 7 > 8 liefert also 0 zurück. '0' == 48 ist true, als Integer wird 1 zurückgegeben. Das funktioniert selbstverständlich auch mit Variablen, so dass x == 7 entweder 1 oder 0 zurückgibt, je nachdem ob x den Wert 7 besitzt. Auch bool'sche Ausdrücke werden so berechnet: 0 entspricht false, 1 entspricht true und true && false ergibt false als Wert für den Ausdruck. Der not-Operator dreht den Wahrheits-Wert um: als true wird false und umgekehrt. !1 entspricht 0. Da alles, was nicht 0 ist als true gewertet wird, entspricht 2 dem Wert true. !2 ergibt daher false. 0 wird als false aufgegriffen, !0 wird daher zu true, was als 1 zurückgegeben wird.

Ausdrücke, die mit Wahrheitswerten arbeiten werden bool'sche Ausdrücke genannt. Bool'sche Ausdrücke geben bool'sche Werte zurück (true oder false), die aber als Integer abgebildet werden können (1 oder 0).

Zuweisungen

Auch die Zuweisung ist ein normaler Ausdruck und gibt daher einen Wert zurück. Der Wert entspricht dem, was zugewiesen wurden. Der Ausdruck x == 7 weist der Variablen x den Wert 7 zu, also gibt der Gesamtausdruck 7 zurück. Bei Operatoren, die eine Zuweisung mit einem anderen Operator kombiniert, wie +=, gilt dies ebenso. Nehmen wir an, die Variable x hat den Wert 2, bei x += 7, wird 2 mit 7 addiert und entsprechend 9 auf x zugewiesen. Der zusammengesetzte Ausdruck x += 7 hat damit den Rückgabewert 9.

Setter- und Getter-Operatoren

Operatoren, die eine Zuweisung durchführen werden als Setter-Operatoren bezeichnet. Ausschließlich lesende Operatoren, wie + oder - werden als Getter-Operatoren bezeichnet.

Kombinationen von Ausdrücken

Ausdrücke lassen sich beliebig kombinieren, auch hierzu zunächst einige Beispiele:

1 + 2 + 3
1 + 2 * 3
x = 4 + 3
x = y = 0
0 && x
0 || y
1 < x < 7

Der erste Ausdruck 1 + 2 + 3 wird der Reihe nach ausgeführt: (1 + 2) + 3 ergibt (3) + 3 und das wiederum ergibt 6. Hier sind alle Operatoren gleichberechtigt, also wird von links nach rechts gearbeitet. Nicht alle Operatoren sind allerdings gleichberechtigt, so gilt auch in C 'Punktrechnung vor Strichrechnung'. Bei 1 + 2 * 3, wird dies berücksichtigt und 1 + ( 2 * 3 ) gerechnet. 1 + (6) entspricht 7.

Besonderheit Zuweisungs-Operator

Die Zuweisung arbeitet wertet jedoch von rechts nach links aus: Der Ausdruck x = 4 + 3 berechnet zuerst, wieviel 4 + 3 ist, anschließend wird der Wert 7 auf x zugewiesen. In alten Programmiersprachen, wie Basic, wurde diese Zuweisung mit einem expliziten Befehl durchgeführt:

let x = 4 + 3

was später zu

x = 4 + 3

verkürzt wurde und so auch in C Einzug fand. Das ganze funktioniert dann allerdings nur, wenn die Setter-Operatoren erst die rechte Seite auswerten.

Besonderheit logische Verknüpfungen

Eine weitere Besonderheit bieten die logischen und (&&) und oder (||) Operatoren. Sie berechnen zunächst die linke Seite. Je nach Wert der linken Seite wird die rechte Seite allerdings nicht mehr berechnet. Entsprechend dürfen hinter || und && keine Berechnungen stehen, die grundsätzlich ausgeführt werden müssen, sondern nur Berechnungen, die ausgeführt werden sollen, falls die linke Seite dies zulässt. Ein Beispielprogramm:

#include <stdio.h>
 
int main( void )
{
  0 || printf( "0 ||\n" );
  1 || printf( "1 ||\n" );
  0 && printf( "0 &&\n" );
  1 && printf( "1 &&\n" );
 
  return 0;
}

Dieses Programm ergibt folgende Ausgabe:

0 ||
1 &&

Bei 0 || muss die rechte Seite ausgerechnet werden, da der Ausdruck noch wahr werden kann. bei 1 || ist der Ausdruck grundsätzlich wahr, vollkommen egal, wie das Ergebnis der rechten Seite ist, daher wird das Ergebnis der rechten Seite auch nicht mehr ausgerechnet. Beim logischen Und ist es ebenso, nur muss bei '0 &&' die rechte Seite nicht mehr ausgerechnet werden, da das Resultat unabhängig der rechten Seite false ist. Nur wenn die linke Seite true zurückgibt, muss weiter geprüft werden. Dieses Verhalten ist gewünscht, denn so lassen sich überflüssige Berechnungen vermeiden.

In manchen Programmiersprachen wird das Verhalten ausgenutzt, um if-Abfragen zu verkürzen. In C wird das Beispielprogramm aus diesem Kapitel häufig als schlechter Programmierstil angesehen, eine ausformulierte if-Abfrage ist für die meisten Entwickler leichter zu lesen:

#include <stdio.h>
 
int main( void )
{
  if( !0 ) printf( "0 ||\n" );
  if( !1 ) printf( "1 ||\n" );
  if(  0 ) printf( "0 &&\n" );
  if(  1 ) printf( "1 &&\n" );
 
  return 0;
}

Fehlerquelle mathematische Schreibweise

Der letzte Ausdruck 1 > x > 7 ist eine beliebte Fehlerquelle, da er nicht der mathematischen Schreibweise entspricht. Schreibt man ihn in eine if-Abfrage

if( 1 < x < 7 )

so könnte man daraus lesen, dass der Ausdruck wahr wäre, wenn 1 < x ist und x < 7. Aber erinnern wir uns daran, dass Ausdrücke von links nach rechts ausgewertet werden. Wählen wir x gleich 4, dann wäre 1 < x < 7 mathematisch wahr. In C wird gerechnet (1 < 4) < 7, das entspricht (true) < 7, also 1 < 7, Endergebnis true. Wählen wir x gleich 0, dann ist 1 < x < 7 mathematisch falsch. In C rechnen wir: ( 1 < 0 ) < 7, ergibt (false) < 7, also 0 < 7, Endergebnis true. Kurz: Egal, wie x gewählt wird, in C bedeutet 1 < x < 7 grundsätzlich true und das kann nicht gemeint sein. In C muss dieser Ausdruck wirklich deutlich ausgeschrieben werden:

if( 1 < x && x < 7 )

Prioritäten von Operatoren

Wie bei Punkt-vor-Strichrechnung, werden C-Operatoren in einer fest geregelten Reihenfolge abgearbeitet. Die Prioritäten der einzelnen Operatoren sind der Prioritätentabelle zu entnehmen.


Diskussionsthread