Kapitel 4

Ausdrücke

C Programming: A Modern Approach — K. N. King

Wahlpflichtfach Informatik

Übersicht

  • Arithmetische Operatoren — + - * / %
  • Zuweisungsoperatoren — = += -=
  • Inkrement & Dekrement — ++ --
  • Priorität & Assoziativität
  • Auswertungsreihenfolge & undefiniertes Verhalten
  • Ausdrucksanweisungen

Arithmetische Operatoren

Binäre Operatoren

OpBedeutung
+Addition
-Subtraktion
*Multiplikation
/Division
%Modulo (Rest)

Unäre Operatoren

OpBedeutung
+Vorzeichen plus
-Vorzeichen minus
"Binär" = zwei Operanden.
"Unär" = ein Operand.

Ganzzahldivision & Modulo

  • / bei Integer: Ergebnis wird zur Null hin abgeschnitten
  • % erfordert Integer-Operanden; Vorzeichen = Vorzeichen des Dividenden (C99)
AusdruckErgebnisHinweis
7 / 23abgeschnitten, nicht gerundet
-7 / 2-3zur Null hin (C99)
7 % 21Rest
-7 % 2-1Vorzeichen wie Dividend
Gilt immer: (a/b) * b + a % b == a

Priorität arithmetischer Operatoren

PrioritätOperatorenAssoziativität
höher*   /   %links
tiefer+   -   (binär)links
unär+   -   (Vorzeichen)rechts
int x = 2 + 3 * 4;    /*  x = 14, nicht 20  */
int y = -2 + 3;       /*  y = 1             */

Was ist ein UPC?

Der Universal Product Code (UPC) ist der Strichcode auf Produkten in Nordamerika.

  • 12 Stellen: Hersteller-ID + Produkt-ID + Prüfziffer
  • Die 12. Ziffer ist eine Prüfziffer — sie wird aus den ersten 11 berechnet
  • Zweck: fehlerhafte Scans erkennen (z. B. wenn der Scanner eine Ziffer falsch liest)
Europäisches Äquivalent: EAN-13 (13 Stellen) — wird in Projekt 6 berechnet.

Programm: UPC-Prüfziffer

#include <stdio.h>
int main(void)
{
    int d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5, total;
    printf("Erste Ziffer:         "); scanf("%1d", &d);
    printf("Erste Fünfergruppe:   "); scanf("%1d%1d%1d%1d%1d",
                                           &i1,&i2,&i3,&i4,&i5);
    printf("Zweite Fünfergruppe:  "); scanf("%1d%1d%1d%1d%1d",
                                           &j1,&j2,&j3,&j4,&j5);
    total = 3*d  + i1 + 3*i2 + i3 + 3*i4 + i5 +
            3*j1 + j2 + 3*j3 + j4 + 3*j5;
    printf("Prüfziffer: %d\n", 9 - ((total - 1) % 10));
    return 0;
}

UPC — Algorithmus

  • Ungerade Positionen (1., 3., 5., 7., 9., 11.) × 3
  • Gerade Positionen (2., 4., 6., 8., 10.) × 1
  • Alles addieren → total
  • Prüfziffer: 9 - ((total - 1) % 10)
Erste Ziffer: 0 Erste Fünfergruppe: 13800 Zweite Fünfergruppe: 15173 Prüfziffer: 5
Beispiel: Barilla Spaghetti — UPC beginnt mit 076808... Prüfziffer berechnen und mit dem echten Produkt vergleichen!

Zuweisungsoperatoren

Einfache Zuweisung

v = e;   /* speichert Wert von e in v */
  • v muss ein lvalue sein — eine adressierbare Speicherstelle
  • Zuweisung ist selbst ein Ausdruck und hat den Wert von e
i = j = k = 0;           /* Kette: von rechts nach links */
printf("%d\n", i = 5);   /* gibt 5 aus               */

Zusammengesetzte Zuweisungen

OperatorBeispielEntspricht
+=i += 5i = i + 5
-=i -= 5i = i - 5
*=i *= 5i = i * 5
/=i /= 5i = i / 5
%=i %= 5i = i % 5
v += e wertet v einmal aus.
v = v + e wertet v zweimal aus — bei Nebeneffekten ein Unterschied!

Inkrement & Dekrement

Präfix

  • ++i — erhöht i, gibt neuen Wert zurück
  • --i — verringert i, gibt neuen Wert zurück

Postfix

  • i++ — gibt alten Wert zurück, erhöht dann
  • i-- — gibt alten Wert zurück, verringert dann
int i = 5;
printf("%d\n", ++i);   /* 6  — i ist 6  */
printf("%d\n", i++);   /* 6  — i ist 7  */
printf("%d\n", i);     /* 7             */

Präfix vs. Postfix — Vergleich

int i = 3, j;

j = ++i;   /* i wird 4, j = 4  (Präfix: erst erhöhen)      */
j = i++;   /* j = 4, dann i wird 5  (Postfix: erst lesen)  */
Als eigenständige Anweisung: kein Unterschied.
i++; und ++i; beide erhöhen i um 1.
Merken: ++i(i += 1) — beide geben neuen Wert zurück.
i++ gibt den alten Wert zurück.

Priorität & Assoziativität (Vollständig)

PrioritätNameOperatorenAssoz.
1 (hoch)Postfix++   --links
2Präfix / Unär++   --   +   -rechts
3Multiplikativ*   /   %links
4Additiv+   -links
5 (tief)Zuweisung=   *=   /=   %=   +=   -=rechts
Vollständige Tabelle: Anhang A im Buch (C hat insgesamt 15 Prioritätsstufen).

Ausdrücke vollständig klammern

Ausgangsausdruck:

a = b += c++ - d + --e / -f
  • Postfix c++: a = b += (c++) - d + --e / -f
  • Präfix / Unär: a = b += (c++) - d + (--e) / (-f)
  • Division: a = b += (c++) - d + ((--e) / (-f))
  • Subtraktion, Addition: a = b += (((c++) - d) + ((--e) / (-f)))
  • Zuweisung (rechts): (a = (b += (((c++) - d) + ((--e) / (-f)))))

Auswertungsreihenfolge

Priorität legt die Struktur fest. Die Reihenfolge der Auswertung ist meist undefiniert.

(a + b) * (c - d)
/* C wertet (a+b) und (c-d) in unbekannter Reihenfolge aus! */
Undefiniertes Verhalten wenn eine Variable im selben Ausdruck gleichzeitig gelesen und verändert wird:
a = 5;
c = (b = a + 2) - (a = 1);  /* Nicht vorhersagbar! */
Lösung: In getrennte Anweisungen aufteilen.
b = a + 2; a = 1; c = b - a;c ist immer 6.

Undefiniertes Verhalten

C kennt drei Kategorien:

  • Definiert — Ergebnis ist immer vorhersagbar
  • Implementierungsdefiniert — Compiler entscheidet, aber muss dokumentieren
  • Undefiniert — Alles kann passieren: falsches Ergebnis, Absturz, Datenverlust
i = 2;
j = i * i++;   /* SCHLECHT: j könnte 4 oder 6 sein */
Faustregel: Nie dieselbe Variable in einem Ausdruck sowohl lesen als auch mit ++, -- oder = verändern.

Ausdrucksanweisungen

In C kann jeder Ausdruck durch ; zur Anweisung werden.

i = 1;       /* sinnvoll: Zuweisung hat Nebeneffekt     */
i--;         /* sinnvoll: Dekrement hat Nebeneffekt     */
i * j - 1;  /* sinnlos:  kein Nebeneffekt, Wert weg    */
Der Wert einer Ausdrucksanweisung wird verworfen.
Sinnvoll nur wenn der Ausdruck einen Nebeneffekt hat.

Typische Tippfehler-Falle

i = j;    /* gemeint: Zuweisung       */
i + j;    /* versehentlich: sinnlos!  */

= und + liegen auf derselben Taste — leicht vertauscht.

Manche Compiler warnen: “statement with no effect”
Immer alle Warnungen lesen! Compiler-Flag: gcc -Wall -Wextra

Fragen & Antworten

Potenzieren in C?

  • Kein **-Operator in C (anders als Python)
  • Kleine Potenzen: wiederholte Multiplikation — i * i * i = i³
  • Nichtganzzahlige Potenzen: pow() aus <math.h>
double x = i * i * i;           /* i hoch 3  */
double y = pow(2.0, 0.5);       /* Wurzel aus 2 ≈ 1.414  */

% auf float?

  • % erfordert Integer-Operanden
  • Für Gleitkomma: fmod() aus <math.h>

Fragen & Antworten

lvalues und rvalues

  • lvalue: Ausdruck, der links von = stehen kann (hat eine Speicheradresse)
  • rvalue: Ausdruck auf der rechten Seite (Wert, keine Adresse)
i = 5;        /* i ist lvalue, 5 ist rvalue           */
i + 1 = 5;   /* FEHLER: "lvalue required as..."      */
Der C-Standard nennt rvalues einfach "expressions" — aber der Begriff "rvalue" ist im Alltag verbreitet und taucht in Fehlermeldungen auf.

Fragen & Antworten

Wann wird bei i++ inkrementiert?

  • Spätestens am Ende der Ausdrucksanweisung (Sequenzpunkt)
  • Funktionsaufrufe sind Sequenzpunkte: alle Argumente vollständig ausgewertet vor dem Aufruf
  • Innerhalb desselben Ausdrucks: Reihenfolge undefiniert
printf("%d %d\n", i++, i);   /* UNDEFINIERT */
i++;
printf("%d\n", i);            /* OK          */
++ und -- von Ken Thompson's B-Sprache geerbt. Kein Geschwindigkeitsvorteil in modernen Compilern — nur Kürze.

Übungen — Arithmetik (4.1)

Welche Ausgabe erzeugen diese Fragmente? (i, j, k sind int)

CodeAusgabe
i=5; j=3;
printf("%d %d", i/j, i%j);
1 2
i=2; j=3;
printf("%d", (i+10)%j);
0
i=7; j=8; k=9;
printf("%d", (i+10)%k/j);
1
i=1; j=2; k=3;
printf("%d", (i+5)%(j+2)/k);
0

Übungen — Division mit negativen Zahlen (C99)

AusdruckErgebnisErklärung
8 / 51Standard
-8 / 5-1Zur Null hin (nicht -2)
8 / -5-1Zur Null hin
-8 / -51Zwei Negative = Positiv
-8 % 5-3Vorzeichen wie Dividend
8 % -53Vorzeichen wie Dividend

Übungen — Zusammengesetzte Zuweisung (4.2) ★

CodeErgebnis
i=7; j=8; i*=j+1; i=63, j=8
i = 7×(8+1) = 63
i=j=k=1; i+=j+=k; i=3, j=2, k=1
j+=k→j=2; i+=j→i=3
i=1; j=2; k=3; i-=j-=k; i=2, j=-1, k=3
j-=k→j=-1; i-=j→i=2
i=2; j=1; k=0; i*=j*=k; i=0, j=0, k=0

Übungen — Inkrement/Dekrement (4.3)

Codeij
i=5; j=++i*3-2; 6 16 6×3-2
i=5; j=3-2*i++; 6 -7 3-2×5
i=7; j=3*i--+2; 6 23 3×7+2
i=7; j=3+--i*2; 6 15 3+6×2

Übungen — Inkrement/Dekrement ★ (4.3)

Zwei separate printf-Aufrufe — Sequenzpunkte trennen die Effekte.

Code1. printf2. printf
i=1;
printf("%d ",i++-1);
printf("%d",i);
0 2
i=10; j=5;
printf("%d ",i++-++j);
printf("%d %d",i,j);
4 11 6
i=7; j=8;
printf("%d ",i++---j);
printf("%d %d",i,j);
0 8 7

Übungen — Klammern & Anweisungen

Klammern setzen (4.4)

AusdruckGeklammert
a*b-c*d+e((a*b)-(c*d))+e
a/b%c/d((a/b)%c)/d
-a-b+c-+d((-a)-b)+(c-(+d))
a*-b/c-d((a*(-b))/c)-d

Ausdrucksanweisungen (4.5)

i=1, j=2:

Anweisungi   j
i += j;3 2
i--;0 2
i * j / i;1 2
i % ++j;1 3

Programmierprojekte

Projekt 1 & 2: Ziffern umkehren

Zweistellige Zahl eingeben: 28 Umgekehrt: 82
  • Letzte Ziffer: n % 10
  • Erste Ziffer: n / 10
  • Ausgabe: printf("%d%d\n", n%10, n/10)
Projekt 2 ★: gleiche Idee für dreistellige Zahlen (drei Ziffern).

Programmierprojekte

Projekt 3: Umkehren ohne Arithmetik

Ziffern einzeln mit %1d einlesen, dann umgekehrt ausgeben:

Gleiche Technik wie in upc.c — einzelne Ziffern ohne Arithmetik trennen.

Programmierprojekte

Projekt 4: Oktaldarstellung

Zahl zwischen 0 und 32767 eingeben: 1953 In Oktal: 03641
  • Letzte Oktalziffer: n % 8
  • Nächste Stelle: n / 8 wiederholen
  • 32767 = 2¹&sup5; − 1 — passt in 15 bit
In Kapitel 7 lernen wir %o — dann geht es mit einem einzigen printf.

Programmierprojekte

Projekt 5: UPC auf einmal eingeben

Erste 11 Stellen des UPC eingeben: 01380015173 Prüfziffer: 5
scanf("%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d",
      &d, &i1, &i2, &i3, &i4, &i5,
      &j1, &j2, &j3, &j4, &j5);

Programmierprojekte

Projekt 6: EAN-Prüfziffer

Europäische Artikelnummer (13 Stellen) — auf jedem deutschen Produkt:

  • Summe 1: gerade Positionen (2., 4., 6., 8., 10., 12.) × 3
  • Summe 2: ungerade Positionen (1., 3., 5., 7., 9., 11.) × 1
  • Prüfziffer: 9 - ((gesamt - 1) % 10)
Erste 12 Stellen der EAN eingeben: 869148426000 Prüfziffer: 8

Zusammenfassung

ThemaKernpunkte
Arithmetik / schneidet ab; % nur Integer; (a/b)*b+a%b==a
Zuweisung v = e ist ein Ausdruck; Ketten möglich; += etc.
++ / -- Präfix = neuer Wert; Postfix = alter Wert; als Anweisung: kein Unterschied
Priorität Postfix > Unär > */% > +- > Zuweisung
Undefiniert Variable lesen und schreiben im selben Ausdruck → Gefahr!
Anweisungen Nur mit Nebeneffekt sinnvoll; Warnungen lesen!

Ende Kapitel 4

Fragen?