Notizen zum C-Kurs am Joint College in Shanghai V 0.2

Werbung
FT
DR
A
Notizen zum C-Kurs am Joint College in Shanghai V 0.2
Prof. Dr. Bernd Kahlbrandt
26. Februar 2014
FT
DR
A
Alle in diesem Dokument enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem
Wissen zusammengestellt und sorgfältig überprüft. Dennoch sind Fehler nicht auszuschließen. Aus diesem Grund sind die im vorliegenden Dokument enthaltenen Informationen mit keiner Verpflichtung oder
Garantie irgendeiner Art verbunden. Weder der Autor noch die Hochschule übernehmen infolgedessen
irgendwelche juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen oder Teilen davon ensteht.
c 2004–2005 Bernd Kahlbrandt
FT
Inhaltsverzeichnis
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
2
3
6
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
7
7
7
10
11
13
15
18
19
20
20
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
23
23
27
32
33
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
35
35
35
38
39
40
41
42
42
44
2
3
4
Einführung
1.1 Übersicht . . . . . . . . . . . . . . . . . . . . . .
1.2 Lernziele . . . . . . . . . . . . . . . . . . . . . .
1.3 C Programme schreiben, umwandeln und ausführen
1.4 Historische Anmerkungen . . . . . . . . . . . . .
1.5 Aufgaben . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
Grundlagen
2.1 Übersicht . . . . . . . . . . . .
2.2 Lernziele . . . . . . . . . . . .
2.3 Namen . . . . . . . . . . . . . .
2.4 Struktur von C-Programmen . .
2.5 Datentypen . . . . . . . . . . .
2.6 Funktionen . . . . . . . . . . .
2.7 printf und putchar . . . . . . . .
2.8 scanf und getchar . . . . . . . .
2.9 Umlenken von Ein- und Ausgabe
2.10 Trigraphs . . . . . . . . . . . .
2.11 Historische Anmerkungen . . .
2.12 Aufgaben . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Formattierte Ein- und Ausgabe
3.1 Übersicht . . . . . . . . .
3.2 Lernziele . . . . . . . . .
3.3 Ausgabe . . . . . . . . . .
3.4 Eingabe . . . . . . . . . .
3.5 Datentypenkonvertierung .
3.6 Aufgaben . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Operatoren
4.1 Übersicht . . . . . . . . . . . . .
4.2 Lernziele . . . . . . . . . . . . .
4.3 Rechenoperatoren . . . . . . . . .
4.4 Zusammengesetzte Zuweisungen .
4.5 Vergleichoperatoren . . . . . . . .
4.6 Logische Operatoren . . . . . . .
4.7 Der ternäre Operator ?: . . . . . .
4.8 Referenzieren und Dereferenzieren
4.9 Bit-Manipulationen . . . . . . . .
4.10 Aufgaben . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iv
DR
A
Abbildungsverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
i
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
ii
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
45
45
51
53
54
54
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
57
57
57
57
59
59
59
61
62
Funktionen
7.1 Übersicht . . . . . . . . . . . . . .
7.2 Lernziele . . . . . . . . . . . . . .
7.3 Modularisierung in C . . . . . . . .
7.4 Wert- und Referenzsemantik . . . .
7.5 Rekursive Funktionen . . . . . . . .
7.6 Einige wichtige Standardfunktionen
7.6.1 Funktionen mit Strings . . .
7.6.2 Funktionen für Zeichen . . .
7.6.3 Mathematische Funktionen .
7.6.4 Datum und Uhrzeit . . . . .
7.6.5 Hilfsfunktionen . . . . . . .
7.7 Aufruf-Parameter . . . . . . . . . .
7.8 Historische Amerkungen . . . . . .
7.9 Aufgaben . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
63
63
63
63
67
69
71
71
71
71
72
72
72
73
73
Der C Präprozessor
8.1 Übersicht . . . . . . . . . . . . . . .
8.2 Lernziele . . . . . . . . . . . . . . .
8.3 Einbinden von Code . . . . . . . . .
8.4 Definieren von Eigenschaften . . . . .
8.5 Bedingte Befehle bzw. Verzweigungen
8.6 Einige Tücken . . . . . . . . . . . . .
8.7 Historische Anmerkungen . . . . . .
8.8 Aufgaben . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
75
75
75
75
75
77
77
78
78
Zeiger (Pointer)
9.1 Übersicht . . . . . . . . .
9.2 Lernziele . . . . . . . . .
9.3 Definition von Zeigern . .
9.4 Zeigerarithmetik . . . . .
9.5 Historische Anmerkungen
9.6 Aufgaben . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
79
79
79
81
82
82
.
.
.
.
.
.
.
Vektoren und Strings
6.1 Übersicht . . . . . . . . . .
6.2 Lernziele . . . . . . . . . .
6.3 Definition von Vektoren . . .
6.4 Initialisierung von Vektoren
6.5 Strings . . . . . . . . . . . .
6.6 Vektoren und Adressen . . .
6.7 Mehrdimensionale Vektoren
6.8 Aufgaben . . . . . . . . . .
DR
A
7
Kontrollstrukturen
5.1 Übersicht . . . . . . . . .
5.2 Lernziele . . . . . . . . .
5.3 Schleifen . . . . . . . . .
5.4 Verzweigungen . . . . . .
5.5 Sprünge . . . . . . . . . .
5.6 Historische Anmerkungen
5.7 Aufgaben . . . . . . . . .
FT
5
8
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INHALTSVERZEICHNIS
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
83
83
83
83
85
86
89
89
11 Umwandlung von Datentypen
11.1 Übersicht . . . . . . . . .
11.2 Lernziele . . . . . . . . .
11.3 . . . . . . . . . . . . . .
11.4 Historische Anmerkungen
11.5 Aufgaben . . . . . . . . .
.
.
.
.
.
12 Bit-Manipulationen
12.1 Übersicht . . . . . . . . .
12.2 Lernziele . . . . . . . . .
12.3 . . . . . . . . . . . . . .
12.4 Historische Anmerkungen
12.5 Aufgaben . . . . . . . . .
.
.
.
.
.
FT
10 Strukturierte Datentypen
10.1 Übersicht . . . . . . . . . . . . . . . . . . . . .
10.2 Lernziele . . . . . . . . . . . . . . . . . . . . .
10.3 Deklaration strukturierter Datentypen . . . . . .
10.4 Verwendung von strukturierten Datentypen . . .
10.5 Strukturierte Datentypen, Funktionen und Zeiger
10.6 Historische Anmerkungen . . . . . . . . . . . .
10.7 Aufgaben . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
91
91
91
91
91
91
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
93
93
93
93
93
93
13 Dynamische Speicherplatzververwaltung
13.1 Übersicht . . . . . . . . . . . . . . .
13.2 Lernziele . . . . . . . . . . . . . . .
13.3 Verschiedene Arten von Speicher . . .
13.4 Speicherreservierung und Freigabe . .
13.5 Historische Anmerkungen . . . . . .
13.6 Aufgaben . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
. 95
. 95
. 95
. 98
. 100
. 100
14 Dateizugriff
14.1 Übersicht . . . . . . . . . .
14.2 Lernziele . . . . . . . . . .
14.3 Streams . . . . . . . . . . .
14.4 Elementare Dateioperationen
14.5 . . . . . . . . . . . . . . .
14.6 Historische Anmerkungen .
14.7 Aufgaben . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
101
101
101
101
101
101
101
101
15 Anwendungen: Listen
15.1 Übersicht . . . . . . . . .
15.2 Lernziele . . . . . . . . .
15.3 . . . . . . . . . . . . . .
15.4 Historische Anmerkungen
15.5 Aufgaben . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
103
103
103
103
103
103
A Code Konventionen für C
A.1 Übersicht . . . . . . . . .
A.2 Lernziele . . . . . . . . .
A.3 Programmstruktur . . . . .
A.4 Namen . . . . . . . . . . .
A.5
. . . . . . . . . . . . . .
A.6 Historische Anmerkungen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
105
105
105
105
105
105
105
DR
A
.
.
.
.
.
INHALTSVERZEICHNIS
iv
FT
B Aufgaben
107
B.1 Rechner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
C Lösungsvorschläge
109
C.1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
111
111
111
111
111
112
112
112
113
113
114
115
115
117
117
117
117
118
118
118
119
119
120
121
122
122
123
123
124
124
124
124
126
127
E IOCCC
E.1 1984 . . . . . . . . . . . .
E.1.1 anonymous . . . .
E.1.2 decot . . . . . . .
E.1.3 Laman . . . . . .
E.1.4 mullender . . . . .
E.2 2001 . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
129
129
129
131
131
132
133
DR
A
D Lösungsvorschläge für ausgewählte Aufgaben
D.1 Kapitel 1 . . . . . . . . . . . . . . . . . . . . . . . . .
D.1.1 tunix.c . . . . . . . . . . . . . . . . . . . . .
D.2 Kapitel 2 . . . . . . . . . . . . . . . . . . . . . . . . .
D.2.1 Namensdeklarationen . . . . . . . . . . . . . .
D.2.2 signed, unsigned char . . . . . . . . . . . . . .
D.2.3 int bzw. unsigned int . . . . . . . . . .
D.2.4 Fehler in Summenfunktion . . . . . . . . . . .
D.2.5 happybirthday1 verb+>+ berndbirth.out . . . .
D.2.6 printf mit ungültigen Esacpe-Sequenzen . . . .
D.2.7 Fahrenheit - Celsius . . . . . . . . . . . . . .
D.2.8 Body-Mass-Index . . . . . . . . . . . . . . . .
D.2.9 Deklarationen und Definitionen . . . . . . . .
D.2.10 Ceiling und Floor . . . . . . . . . . . . . . . .
D.3 Kapitel 3 . . . . . . . . . . . . . . . . . . . . . . . . .
D.3.1 Falscheingaben bei formio11.c . . . . . . . . .
D.3.2 Typkonvertierung char to int und zurück . . . .
D.3.3
. . . . . . . . . . . . . . . . . . . . . . . . .
D.3.4 Dreistellige Zahl in unterschiedlichen Formaten
D.4 Artikel, Stückzahl, Preis, Gesamtpreis . . . . . . . . .
D.5 Kapitel 4 . . . . . . . . . . . . . . . . . . . . . . . . .
D.5.1 Zusammengesetzte Zuweisungen . . . . . . .
D.5.2 Tabellen 4.5–4.9 . . . . . . . . . . . . . . . .
D.5.3 Speicherbedarf der elementaren Datentypen . .
D.6 Kapitel 5 . . . . . . . . . . . . . . . . . . . . . . . . .
D.6.1 Matrix der ASCII-Zeichen . . . . . . . . . . .
D.6.2 BMI mit erläuternden Ausgaben . . . . . . . .
D.6.3 for Schleife mit Präfix-Inkrementoperator . . .
D.7 Kapitel 6 . . . . . . . . . . . . . . . . . . . . . . . . .
D.8 Kapitel 7 . . . . . . . . . . . . . . . . . . . . . . . . .
D.8.1 stringcopy . . . . . . . . . . . . . . . . . . . .
D.8.2 Palindromerkennung . . . . . . . . . . . . . .
D.8.3 Textverschlüsselung . . . . . . . . . . . . . .
D.8.4 Body-Mass Index . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
F Development Environments
135
F.1 Microsoft Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
F.2 Erläuterungen zu Fehlermeldungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Index
138
FT
Abbildungsverzeichnis
2.1
2.2
2.3
2.4
2.5
2.6
Namensbildung in C . . . . . . . . . . . . . . .
limits.h für VC++ .NET 2003 . . . . . . . . . . .
C Datentypen, Teil 1: Ganze Zahlen und Zeichen
C Datentypen, Teil 2: Fließkommazahlen . . . .
Escape- und Format-Zeichen in ANSI C . . . . .
Variablen und Adressen in C . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
9
10
10
14
16
3.1
3.2
3.3
3.4
3.5
Escape-Sequenzen in ANSI C . . . . . . . .
Ausgabe von Zeichen und ganzen Zahlen . .
Ausgabe von Gleitkommazahlen und Strings .
Datentyp double nach IEEE (64 Bit) . . . . .
Datentypen für Gleitkommazahlen . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
24
25
26
28
29
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
4.14
4.15
Binäre arithmetische Operatoren . . . . . . . . . . . . . . .
Typkonvertierungsregeln für binäre arithmetische Operatoren
Unäre arithmetische Operatoren . . . . . . . . . . . . . . .
Zusammengesetzte Zuweisungen . . . . . . . . . . . . . . .
Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . .
Logische Operatoren . . . . . . . . . . . . . . . . . . . . .
Wahrheitstafel für logische Operatoren Bool . . . . . . . . .
Wahrheitswerte Boolesch und in C . . . . . . . . . . . . . .
Wahrheitstafel für logische Operatoren im C-Stil . . . . . .
Unäre boolesche Operatoren . . . . . . . . . . . . . . . . .
Der Auswahloperator ?: . . . . . . . . . . . . . . . . . . . .
Bit-Operatoren . . . . . . . . . . . . . . . . . . . . . . . .
Wirkungsweise der Bit-Operatoren . . . . . . . . . . . . . .
Bit-Masken . . . . . . . . . . . . . . . . . . . . . . . . . .
Bit-Manipulationen, Beispiele . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
36
36
37
39
39
40
40
40
40
40
41
42
42
43
43
5.1
5.2
5.3
5.4
5.5
While und do while-Schleife
for-Schleife . . . . . . . .
if-else-Konstrukt . . . . . .
Geschachtelte if-Konstrukte
switch-Konstrukt . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
46
48
51
51
52
8.1
#include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
13.1 Speicherstruktur eines C-Programms . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
96
.
.
.
.
.
DR
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
v
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ABBILDUNGSVERZEICHNIS
vi
1. VC etwas ausarbeiten deferred
2. Index überprüfen. skipped
FT
Todo
3. Kapitel Bitmanipulationen in einer neuen Auflage wieder einblenden.
4. Kapitel Umwandlung von Datentypen in einer neuen Auflage wieder einblenden.
5. In jedem Kapitel nochmal historische Anmerkungen durchsehen, ggf. einiges schreiben oder Abschn.
streichen.
6. Analogie Camino de Santiago 2001 und C-Kurs ausarbeiten. Ein bischen wie in [Blo71].
7. IOCCC wieder einblenden.
8. << MUSS rein! put to -Operator.
Done
1. αnachV ersionentf ernen
DR
A
2. Badness in Lösungsvorschlägen beseitigen.
3. Entscheiden, ob Lösungsvorschläge gleich mit rausgeben!
4. Lösungsvorschlag Prüfziffer streichen
5. Kapitel Dateizugriff ausarbeiten oder streichen.
6. Kapitel Dynamische Speicherverwaltung weiter schreiben.
7. Kapitel Sort ausarbeiten bzw. kürzen (Java raus).
8. Glossar Reihenfolge überprüfen (nur beim Einfügen kursorisch)
9. Glossar auffüllen (bis j erledigt.)
10. Kapitel Dateizugriff über Streams und Elementare Dateioperationen zusammenfassen.
11. Kapitel Zur Auflockerung etc. ausblenden, eventuell Teile drin lassen.
12. Kapitel Pointer ausarbeiten.
13. Kapitel strukturierte Datentypen schreiben.
14. IOCCC ausblenden. Höchstens zum Schluss mal zeigen.
15. gcc streichen
16. make streichen
17. Anwendungen Binärbäume ausblenden.
18. Anwendungen Listen ausblenden.
19. Kapitel Bitmanipulationen ausblenden
20. Kapitel Umwandlung von Datentypen ausblenden
21. Übungsaufgaben mit Lösungen. Es sind inzwischen viele drin.
ABBILDUNGSVERZEICHNIS
vii
FT
22. Entscheiden ob es in Text- und oder Index „Addition“ oder „Additionsoperator“ heißen soll etc., mit
oder ohne Bindestrich.
23. Lösungsvorschläge für die bereits formulierten Aufgaben ausarbeiten.
24. Gliederung ausarbeiten
25. Für jede neu formulierte Aufgabe gleich einen Lösungsvorschlag schreiben. (Seit diesem Eintrag
fast durchgehalten, nur nicht bei einige Fleißaufgaben).
26. Einführung gestalten
27. Comics für Shanghai einscannen
28. putchar und getchar in den Abschn. 2.7 bzw. 2.8 erläutern, damit man früher mehr sinnvolles machen
kann.
29. Abb. 2.6 auf angemessenes Format bringen.
30. Herausfinden, warum die Ausgabe in formio10.c nicht rechtsbündig
erscheint. Die Antwort ist ganz einfach: Bündige Ausgabe erfolgt
immer im Rahmen der
DR
A
Feldbreite. Wird keine angegeben, so ist dies die Länge des Feldes. Im
Beispiel wurde als auf jeder Zeile das Feld rechtsbündig entsprechend
seiner Länge ausgegeben. Bei Eingabe von 1, 11, 111 also 1
rechtsbüdig in einem Feld der Länge 1, 11 in einem Feld der Länge 2
usw. Daher die linksbündige Ausgabe. Die Lösung ist also ganz einfach:
Überall die gleiche Feldbreite angeben.
31. Email an Wang Nan ([email protected]) mit aktueller Literaturliste.
32. look up, how to get & etc. displaid correctly in index:
Solution: put = (in german index style) or @ resp. at every level
where it’s needed.
ABBILDUNGSVERZEICHNIS
viii
Vorlesungsplan Soll
Inhalt
27.02.2014
02
28.02.2014
03
26.08.2004
04
31.08.2004
05
31.08.2004
02.09.2004
02.09.2004
06
07
08
07.09.2004
09
07.09.2004
10
Kontrollstrukturen
09.09.2004
11
Vektoren und Strings
09.09.2004
12
Vektoren und Strings
14.09.2004
13
Vektoren und Strings
14.09.2004
14
Vektoren und Strings
16.09.2004
15
Funktionen
16.09.2004
16
Funktionen
21.09.2004
21.09.2004
23.09.2004
23.09.2004
17
18
19
20
28.09.2004
28.09.2004
30.09.2004
30.09.2004
10.10.2004
21
22
23
24
25
Funktionen
Präprozessor
Zeiger
Zeiger
Test 2
Zeiger
Zeiger
Zeiger
Dynamische Speicherplatzverwaltung
10.10.2004
12.10.2004
12.10.2004
26
27
28
FT
25.02.2014
Doppel
stunde
01
Vorstellung, Organisatorisches, Einführung
Kapitel 1, Folien Intro
Grundlagen: Struktur von C-Programmen
Datentypen, Funktionen
Abschn. 2.4–2.6, Folien:
Wiederholung, Klärung summenfunktion
printf, scanf,
Abschn. 2.7- 2.8, Folien: 02, Nr. 6
Umlenken von Ein- und Ausgabe
2.9
Wiederholung, Fragen, Diskussion
Informationen zum Praktikum
Formattierte Ein- und Ausgabe
Kap. 3, Folien:
Operatoren
Operatoren
Test 1
Kontrollstrukturen
DR
A
Datum
Test 5
Dynamische Speicherplatzverwaltung
Dateizugriff
Dateizugriff
ABBILDUNGSVERZEICHNIS
ix
Datum
Vorlesungsplan Ist
Termin
25.02.2014
26.08.2004
31.08.2004
02.09.2004
07.09.2004
09.09.2004
Dateizugriff
Dateizugriff
Klausur
Inhalt
Kap 1, Abschn. 2.1–2.5
Bis Folie 27, S. 8 Handout
Kap. 2.7–2.9
Kap. 3
Operatoren
Kontrollstrukturen bis auf if/switch
Test 1
Besprechung des Tests
Grundlagen Rechner
if/switch
Vektoren und Strings, Anfang
Besprechung des Tests
Vektoren und String
Besprechung Praktikum, FUnktionen
Funktionen, Wiederholung, Rekursion, main
Präprozessor
Test 2
Besprechung des Tests, Teil 1
Pointer, Anfang
Besprechung des Praktikums
Noch etwas zum Praktikum, Hausaufgaben
Zeiger und Zeiger auf Funktionen
Sieb des Eratosthenes, Neue Hausaufgaben
Besprechung Hausaufgaben
Insertion Sort und Funktionszeiger
Wiederholung
Wiederholung, Erläuterung von deutschen Worten
in typischen Aufgaben.
Klausur
DR
A
09.09.2004
14.09.2004
16.09.2004
21.09.2004
Inhalt
FT
14.10.2004
14.10.2004
19.10.2004
Doppel
stunde
29
30
23.09.2004
28.09.2004
30.09.2004
10.10.2004
12.10.2004
14.10.2004
ABBILDUNGSVERZEICHNIS
DR
A
FT
x
FT
Vorwort
DR
A
Dieses Skript entstand für die Teilnehmer der Vorlesung Programmierung I am Joint College der USST und
der HAW in Shanghai im Short Semester vom 23.08.-22.10.2004. Es ist Version 0.1 und wird so noch mehr
Fehler jeder Art enthalten, als mir lieb sein kann. Allerdings sind alle Programmbeispiele ausprobiert und
mit insert file in die LATEX-Source übernommen worden. Aber auch dabei sind natürlich Fehler möglich.
Um das Skript möglichst schnell fertig zu bekommen, habe ich auf einige Feinarbeiten verzichtet. Es gibt
also Seiten (z. B. . Seite 96) die zu leer sind, an einigen Stellen wird nach oben oder unten um mehr als 9pt
oder nach rechts und linke um mehr als 6pt über den Satzspiegel hinausgeschrieben.
Der Stil ist persönlich gehalten und sicher nicht immer ganz sprödes Schriftdeutsch.
Dieses Dokument wäre ohne die aktive Mitwirkung der Studierenden im verkürzten Semester im Herbst
2004 an der USST nicht möglich gewesen. Da diese Studierenden nicht nur „gegen“ den Stoff kämpfen,
sondern auch gegen die Deutsche Sprache, habe ich begonnen, dieses Skript zu schreiben. Da es schon
hunderte oder tausende Bücher über C gibt, muss man fragen, ob sich dieser Aufwand „lohnt“! Aber
angesichts der Schwierigkeiten mit denen die Studierenden hier zu kämpfen haben, und angesichts der
Resonanz scheint es sich zu lohnen.
Viele Studierende haben durch ihre konstruktive Kritik zur Verbesserung dieses Dokuments beigetragen. Ohne diese aktive Rückkopplung hätte dieser Kurs nicht erfolgreich werden können. Ich bedanke mich
bei den Teilnehmern dieses Kurses für die konstruktive Zusammenarbeit und die Beiträge, die sie zu diesem Kurs und zu diesem Skript geliefert haben. Die Anregungen werden in die nächste Auflage eingehen.
Wenn die Qualität der Beiträge und Anregungen auch unterschiedlich war, so war doch jede Äußerung
eine für den Dozenten nützliche Rückkopplung. Beiträge haben die folgenden Teilnehmer geleistet: Cao
Bin, Chen Hao, Chen Yingjie, Dou Xiao, Du Hongbo, Fu Xiangyun, Gao Cheng, Ge Di, Geng Xiaoming,
Hu Chenjun, Jiang Chengyong, Jiang Yanfei, Li Feng, Liu Songlin, Luo Liren, Pan Enyu, Pan Xinyuan,
Pan Yiwen, Peng Bo, Qian Lingyun, Qin Chuan, Shen Zhongyuan, Shi Zheng, Sun Xiaojun, Wang Bin,
Wang Dongpeng, Wang Jiyuan, Wang Liming, Wang Liyan, Wang You, Wu Fangqi, Wu Jiejie, Xu Chao,
Xu Renyi, Yang Yifan, Yao Fusheng, Ye Weiqiang, Yu Zhongwei, Yuan Zhaoxing, Zhang Chenglei, Zhang
Fanjun, Zhang Jie, Zhang Lin, Zhang Meiyan, Zheng Haijiao, Zhou Ying, Zhu Da. Für die immer freundliche Unterstützung in allen Lebenslagen danke ich meinen chinesischen Kollegen und Kolleginnen, Xu
Fang, Xu Jingwei, Weikang Qian, Ma Xiating, Fang Zongda, Lu Ling. Mein besonderer Dank gilt meiner
Familie, vor allem meiner Frau Katja, die sofort sagte, ich solle eine solche Chance nutzen, auch wenn wir
dadurch mal wieder fast ein viertel Jahr getrennt seien.
Bernd Kahlbrandt, Shanghai, August 2004.
xi
Einführung
FT
Kapitel 1
You Oughta Know
Alanis Morisette
1.1 Übersicht
DR
A
Es ist egal, an welcher Programmiersprache Sie das Programmieren lernen:
• Man kann in der besten Programmiersprache schlechte Programme schreiben.
• Guten Programmierstil kann man auch an der schlechtesten Programmiersprache lernen.
Warum also gerade C? Dafür gibt es eine Reihe guter Gründe:
• C gibt es fast überall.
• C ist relativ einfach.
• C Programme können sehr effizient sein.
• Sie können mit C fast alles machen.
• In C können Sie Vieles lernen, was Sie dann in C++, C# oder Java auch anwenden können.
• Irgendwer muss auch die Programme schreiben, die für alles die Basis bilden: Betriebssysteme,
Utilities etc.
Gerade Ingenieure benötigen zumindest ab und zu Dinge, die etwa in Java nicht (so einfach) zu machen
sind. In diesem Kapitel möchte ich Ihnen zeigen, wie Sie auf möglichst einfache Weise drei Dinge tun
können, nämlich ein C-Programm:
1. schreiben,
2. umwandeln,
3. ausführen
Was dabei genau abläuft, was dahinter steckt und was man sonst noch alles machen kann, lernen Sie im
Laufe dieses Kurses. Es kann durchaus sein, dass Sie im Laufe des Kurse Schwierigkeiten zu überwinden
haben. Das könnten z. B. folgende sein:
• Sie verstehen mein Deutsch nicht gut genug.
• Sie verstehen den fachlichen Inhalt nicht oder nicht sofort.
1
KAPITEL 1. EINFÜHRUNG
2
FT
• Sie meinen, alles verstanden zu haben, aber sie schaffen es nicht ein Programm zu schreiben, in dem
Sie den Stoff an einer Aufgabe anwenden sollen.
• Sie schaffen es, ein Programm zu schreiben, dass Sie für richtig halten, aber der Compiler ist anderer
Ansicht.
• Sie schreiben ein Programm, das Sie für richtig halten, der Compiler findet auch keine Fehler, das
Ergebnis ist aber trotzdem falsch.
• ...
Denken Sie dann bitte an Folgendes:
• Fragen Sie unbedingt nach, wenn Sie einen deutschen Satz oder auch nur einen Begriff nicht verstanden haben. Kennt etwa jeder Chinese jedes Schriftzeichen oder versteht jeden andern Chinesen,
der vielleicht etwas Dialekt spricht?
DR
A
• Es ist Ihre Aufgabe sich ernsthaft anzustrengen, den Stoff zu verstehen. Es ist meine Aufgabe, den
Stoff so darzustellen, dass Sie ihn verstehen. Wenn Sie nicht nachfragen ist das unhöflich, weil Sie
mir keine Chance geben, die Darstellung zu verbessern. Denken Sie bitte nicht, weil die Anderen
nicht fragen, hätten alle Anderen das verstanden, nur Sie nicht. Wahrscheinlich trauen die Anderen
sich nur nicht, zu fragen. Haben Sie keine Angst vor Fehlern, wenn Sie Deutsch sprechen. Sie können
daraus nur lernen.
• Schaffen Sie es nicht, ein Programm zu schreiben, dass eine Aufgabe löst, so ist dies ein sicheres
Zeichen, dass Sie den Stoff doch nicht ganz oder nicht ganz richtig verstanden haben. Also müssen
Sie den Dozenten fragen!
• Auch ein C-Compiler mag einen Fehler haben. Bekommen Sie beim Umwandeln eines Programmes
aber Warnungen oder gar Fehlermeldungen, so ist es aber sehr, sehr wahrscheinlich, dass der Fehler
bei Ihnen liegt. Dabei kommen zwei Dinge besonders häufig vor:
1. Sie haben irdendwelche Systemeinstellungen nicht korrekt vorgenommen. Das „sollte“ an der
Hochschule nicht vorkommen. Diese Dinge sind mit Unterstützung eines fachkundigen Mitarbeiters oder Mitarbeiterin meistens leicht lösbar.
2. Der Compiler hat etwas entdeckt, das ihm eine Warnung oder eine Fehlermeldung Wert ist.
Ihre Erfahrung reicht aber nicht aus, um die Fehlermeldung richtig zu verstehen und auf den
Teil des Codes zu schließen, der die Meldung verursacht. Hierzu werden wir gemeinsam eine
Sammlung lehrreicher Beispiele zusammenstellen. Zur Erläuterung werde ich den Code, den
ich Ihnen zur Verfügung stelle ausführlich kommentieren. Ein nützliches Werkzeug dafür ist
doygen. Das ist sozusagen „javadoc“ für C und C++.
Und noch ein ganz wichtiger Hinweis:
Programme, die ein Rechner versteht kann jeder Idiot schreiben. Gute Programmierer schreiben Programme, die Menschen verstehen!
1.2 Lernziele
• Einen Editor benutzen können (um C-Programme zu schreiben)
• Einen C-Compiler nutzen können, z. B. MinGW.
• Wissen, was Sie tun sollten, um diesen Kurs mit Erfolg abzuschließen.
1.3. C PROGRAMME SCHREIBEN, UMWANDELN UND AUSFÜHREN
3
1.3 C Programme schreiben, umwandeln und ausführen
FT
Nehmen Sie sich Ihren Lieblingseditor her. Über Geschmack soll man nicht streiten, Sie können also irgend
einen nehmen, hier nur eine kleine Auswahl:
emacs Dies ist ein Lisp Interpreter, mit dem Sie auch einen Editor erhalten. An emacs ihm scheiden sich
die Geister. Es gibt nur begeisterte Fans und fanatische Gegner. Wohl für kaum ein Akronym gibt es
so viele verschiedene „Lesarten“. Dieser Editor hat für fast alle Programmiersprachen Modi, die das
Schreiben des entsprechenden Codes erleichtern.
vi In Bezug auf vi sind die Rollen der Gegner und Befürworter im Vergleich zu emacs genau vertauscht.
Windows Editor Einfaches Zubehör zu MS-Windows, das früher „notepad“ hieß.
notepad++ Einfach freier Editor für Windows, der für viele Zwecke eine gute Unterstützung bietet, z. B.
für die Programmierung in C.
wordpad Ebenfalls ein einfaches Zubehör zu MS-Windows. Dieser Editor kann aber Unicode. Sie haben
so die Chance (aber nicht die Garantie) ohne große Schwierigkeiten Unix-Dateien unter Windows
zu editieren und umgekehrt.
Netbeans Entwicklungsumgebung von Oracle für C, C++ und Java.
DR
A
Visual Studio Entwicklungsumgebung für Microsoft für C, C++,C# und Java.
Eclipse Open Source Entwicklungsumgebung für C, C++ und Java.
Ich werde hier notepad++ verwenden. Natürlich können Sie auch die Entwicklungsumgebung von Microsoft verwenden. Die bringt aber so viele Möglichkeiten und Einstellungen mit, dass es für den Anfang
vielleicht etwas zu schwierig für Sie sein könnte.
Hier nun der Code für das denkbar einfachste C-Programm, dass einfach gar nichts tut:
01
02
03
04
05
06
07
08
/**
* One of the most simple C programs.
* @author Bernd Kahlbrandt
*/
int main(void)
return 42;
Um das Programm umwandeln zu können, müssen Sie wissen, wie der Compiler heißt, d. h. welches Programm sie ausführen müssen, welche Parameter es erwartet etc.
MinGW (Minimal GNU for Windows) ist ein Open Source Compiler, der auch von Netbeans verwendet
wird. MinGW hat auch einen C-Compiler (außer dem C++, Fortran . . . ).
Am einfachsten ist es, wenn Sie sich eine einfache Batch-Datei (.bat) machen, die etwa so aussieht wie
die folgende buildc.bat:
Mingw32-gcc ./src/%1.c -std=c99 -o ./bin/%1.exe
Diese Zeile erläutere ich nun Schritt für Schritt:
1. Mingw32-gcc: Dies ist der Name des C-Compilers von MinGW. Es muss in einem Verzeichnis liegen, das in Ihrem gültigen Pfad liegt. Ich empfehle, den Source-Code, also die Dateien mit der
Endung .c, in einem Verzeichnis zu speichern, hier src/. vor dem Dateinamen. Das ./c davor heißt
einfach, dass der Name relativ zum aktuellen Verzeichnis ist.
2. ./src/%1.c: %1 ist das Symbol für den ersten Parameter. Hier steht der Name Ihres Programmes. Damit Sie weniger tippen müssen, geben Sie die Erweiterung .c nicht mit, sondern sie wird automatisch
ergänzt (.c).
KAPITEL 1. EINFÜHRUNG
4
FT
3. -std=c99: Dies ist eine Option für den Compiler. Sie besagt, das der Standard (-std) C99 verwendet
werden soll. Das ist die aktuelle Version von C.
4. -o Dieser Parameter gibt an, wie die Ausgabedatei heißen soll und was für eine Ausgabe Sie erstellen
wollen.
5. ./bin/%1.exe %1 ist wieder der Name des Programms, nun aber mit der Endung .exe. In Windows
ist das eine ausführbare Datei und eine solche wird deshalb auch von Mingw32-gcc erstellt. Ich
empfehle, die ausführbaren Programme in einem eigenen Verzeichnis, hier bin zu speichern.
Nun zur Erklärung des Programms, Zeile für Zeile:
1. Zeilen 01-04 enthalten einen mehrzeiligen Kommentar. Ein mehrzeiliger Kommentar beginnt in C
mit /* und endet mit */. In Zeile 01 beginnt mit /**. Dies bedeutet, das es sich um einen doxygenKommentar handelt. Sie müssen im Praktikum keine umfangreichen Kommentare dieser Art schreiben: Ich erwarte nur so viel, wie Sie in diesem Beispiel sehen. Die Syntax von doxygen entspricht
weitgehend der des entsprechenden Werkzeugs javadoc für Java.
Zeile 02 enthält eine Beschreibung des Programms. Ich werde hier englische Kommentare verwenden. Zeile 03 enthält den Autor des Programms. Dies wird durch @author gefolgt von dem Namen
angegeben. Für jeden Autor ein @author. Zeile 04 beendet den Kommentar.
DR
A
2. In Zeile 05 beginnt eine Funktion, die main heißt. Jedes ausführbare C-Programm muss eine Funktion dieses Namens haben. Als Ergebnis liefert main immer eine ganze Zahl zurück. Ganze Zahlen
heißen in C int. Die Funktion main kann Argumente haben, muss dies aber nicht. Da dieses Beispiel
ganz einfach sein soll, bekommt diese main-Funktion keine Parameter. Das wird hier durch das Wort
void angegeben.
3. Die öffnende geschweifte Klammer { in Zeile 06 kennzeichnet den Beginn eines Blocks. Dieser
enthält den Code der Methode und geht bis Zeile 08, in der er durch eine schließende geschweifte Klammer } beendet wird. Es ist in C üblich, solche Klammerpaare so zu setzen wie hier: DIe
Schließende Klammer steht in der gleichen Zeile wie die öffnende.
4. In Zeile 07 wird 42 zurückgegeben, nach [Ada81] die Antwort auf die „Große Frage nach dem Leben,
dem Universum und allem?“1
Mit Visual C++.NET kommt ein Programm cl.exe das dies leistet. Für unseren einfachen Fall brauchen
Sie nur eine DOS-Box zu öffnen. Damit alle Umgebungsvariablen richtig gesetzt sind, machen Sie das am
einfachsten so: Wählen Sie aus dem Startmenü
Programme
→Microsoft Visual Studio .NET 2003
→Visual Studio .NET Tools
→Visual Studio .NET Command Prompt
aus. Wechseln Sie dann in das Verzeichnis, in dem Sie das Programm tunix.c gespeichert haben. Dort
geben Sie dann Folgendes ein:
cl tunix.c
Das Ergebnis sehen Sie hier:
Microsoft (R) 32-Bit C/C++-Optimierungscompiler Version 13.10.3077 für
80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
tunix.c
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.
1 Für
diese Antwort hat Deep Thought 7,5 Millionen Jahre benötigt.
/out:tunix.exe
tunix.obj
5
FT
1.3. C PROGRAMME SCHREIBEN, UMWANDELN UND AUSFÜHREN
Lassen Sie das „.c“ weg, so passiert auch nichts Schlimmes, Sie bekommen lediglich zusätzlich die Warnung:
cl: Befehlszeile warning D4024: Typ der Quelldatei ’tunix’
nicht erkannt, Objekt datei wird angenommen
Geben Sie nun den Befehl dir ein. Im Ergebnis sollten Sie etwa dieses Ergebnis sehen: (Die Letzte Spalte
enthält von mir ergänzte Erläuterungen)
22.06.2004
21.06.2004
22.06.2004
22.06.2004
11:11
120 tunix.c
13:51
37 tunix.c~
12:06 22.016 tunix.exe
12:06
479 tunix.obj
C Source Code
Sicherungsdatei von emacs
Die ausführbare Datei
Die Objekt Datei
Sehen wir uns nun einmal an, was wir aus diesem einfachen Beispiel alles lernen können. Das Programm
tut zwar nichts (daher der Name), aber einige der Elemente werden Sie immer wieder verwenden.
DR
A
1. „/*. . . */“: Ein Schrägstrich gefolgt von einem Stern kennzeichnet die folgenden Zeichen als Kommentare bis ein Stern gefolgt von einem Schrägstrich den Kommentar abschließt. Diese Art der
Kommentare heißen „Kommentare im C-Stil“, da sie insbesondere in C üblich sind.
2. „int main(void)“: Sie können ein C-Programm in viele Programmteile zerlegen. Wie wir später sehen werden, ist die sinnvolle Zerlegung zentraler Bestandteil guten Programmierens. Ein Teil muss
aber eine Funktion enthalten, die „main“ heißt. In dem folgenden Klammerpaar „(void)“ steht eigentlich nichts. Das Schlüsselwort void bedeutet, dass die Funktion nichts übergeben bekommt
(keine Parameter). Das Schlüsselwort „int“ vor dem Funktionsnamen bedeutet, dass die Funktion
nach erfolgreicher Ausführung eine ganze Zahl zurückliefert. Ganze Zahlen heißen in C „int“ vom
englischen Wort „integer“.
3. „{ . . . }“: Geschweifte Klammern schließen einen Programmblock ein. Sie müssen ein geschweiftes
Klammerpaar setzen, um den Code einer Funktion einzuschließen. Wenn es die Lesbarkeit verbessert
können Sie beliebig viele weitere Klammerpaare setzen.
4. „return 47“: In dieser Zeile wird die Funktion beendet und die ganze Zahl 47 zurückgegeben. Üblich
ist es, beim erfolgreichen Ende eines Programmes „0“ zurückzugeben, Im Fehlerfall, wird oft eine
andere Zahl zurückgeliefert, die Informationen darüber enthält, wie schwer der Fehler war.
5. Ich habe für jede geschweifte Klammer eine Zeile „spendiert“. Ich finde Code übersichtlicher, wenn
die Klammerpaare jeweils in einer Spalte übereinanderstehen. Es gibt etwa genauso viele Programmierer, die das ebenfalls so machen wie die, die es anders machen.
Damit haben wir einige Basisbestandteile von C-Programmen gesehen. Wenden wir uns nun den Dateien
in unserem Verzeichnis zu:
tunix.c Der Sourcecode, auf meinem Rechner 120 Bytes lang.
tunix.c˜ Die Sicherungsdatei die emacs für mich angelegt hat. Ich hatte zunächst den Code geschrieben
und dann den Kommentar ergänzt. „Netto“ ist der Code also nur 37 Bytes lang.
tunix.exe Das ist die ausführbare Datei, die der Compiler erzeugt hat. Achten Sie auf die Größe von
22.016 Bytes; dies erscheint auf den ersten Blick viel für ein Programm, dass nichts tut.
tunix.obj Was zum Teufel ist das? Im Englischen heißt es object deck, im Deutschen ist Objektdatei eine
mögliche Übersetzung. Diese Datei enthält die wesentlichen Teile des Programms, allerdings sind
die Adressen, die verwendet werden, noch relativ zum Beginn des Programms. Durch den sogenannten Linker, wird diese Objektdatei mit den notwendigen Laufzeitbibliotheken zu einer ausführbaren
Datei „zusammengebaut“, die hier „tunix.exe“ heißt.
KAPITEL 1. EINFÜHRUNG
6
Was macht nun das Programm cl? Dazu betrachten wir die obige Ausgabe etwas genauer:
FT
cl Der Programmname ist eine Abkürzung für Compiler und Linker. Der Compiler prüft die Syntax und
erstellt den ausführbaren Code, der sich aus den angegebenen Source-Dateien erstellen lässt. Der
Linker zieht Module aus Bibliotheken heran, prüft, ob die Funktionen in der Bibliothek denen entsprechen, die Sie in Ihrem Programm verwenden und packt alles mit den Modulen zusammen, die
benötigt werden, um im jeweiligen Betriebssystem ein Programm zu laden und die Ausführung zu
beginnen.
C/C++ Optimierungscompiler In der Vergangenheit waren Compiler „verbesserungsfähig“: Man konnte
den erzeugten Code durch geeignete Optimierungsprogramme drastisch beschleunigen. Heute optimieren die meisten Compiler. Manche Unzulänglichkeiten in der Programmierung werden dadurch
„ausgebügelt“.
tunix.c Dieser Name gibt uns die Eingabedatei für den Compiler an.
Incremental Linker Nach dem Compile-Schritt kommt der Link-Schritt (s. o.).
DR
A
/out:tunix.exe Die Ausgabedatei des Linkers heißt tunix.exe. Die Voreinstellung ist: Heißt die SourceCode Datei xxxx.c, so heißt die ausführbare Datei xxxx.exe. Sie können ihr aber auch jeden anderen Namen geben. Geben Sie etwa cl tunix.c /Fenihao.exe ein, so wird die Ausgabedatei
nihao.exe erzeugt. /Fe ist dabei ein Parameter für cl dem der Name der Ausgabedatei folgen
muss.
tunix.obj In dieser Zeile wird die Eingabedatei des Linkers ausgegeben.
Führen Sie das Programm aus, indem Sie „tunix.exe“ eintippen und die „Enter“-Taste drücken, so sehen
Sie keinen Effekt. Kein Wunder, das Programm tut ja auch nichts.
1.4 Historische Anmerkungen
C wurde ursprünglich von Dennis Ritchie für und auf dem UNIX Betriebssystem der DEC PDP-11 entworfen und implementiert. Erwähnenswerte Vorläufer sind BCPL und die im Folgenden genannten. Viele der
maßgeblich Beteiligten an der Entwicklung von C und C++ hatten Erfahrungen mit BCPL und deren Vorläufer, CPL. Die Erfahrungen, die er mit BCPL gewonnen hatte, gingen in Ken Thompsens Entwicklung
der Sprache B für die DEC PDP-7 im Jahre 1970 ein. Böse Zungen behaupten, Dennis Ritchie habe sich
mit der Entwicklung dieser Sprache einen Scherz erlauben wollen. Er habe sich nicht vorstellen können,
dass irgend jemand ernsthaft in einer solchen Sprache würde programmieren wollen. Dies ist wohl eine
boshafte Unterstellung. Der Erfolg von C führte zur Entwicklung vieler aktueller Sprachen. Die Entwicklung von C++ wird von Bjarne Stroustrup sehr anschaulich in [Str94b] beschrieben. Java versucht einige
Schwächen von C zu beseitigen. Dabei entstand die Sprache aus einem völlig andersartigen Projekt. Die
Firma Sun kämpfte immer an zwei Fronten: Gegen Microsoft und gegen IBM. Als sich in den USA der
Trend andeutete, Internetzugang über die vorhandenen Kabelnetze für Fernsehempfang zur Verfügung zu
stellen, witterte der CEO von Sun eine Chance. Sun begann ein Betriebssystem für die dazu benötigten
Set-Top Boxen zu entwickeln. Das Projekt scheiterte, aber Java wurde ein Riesenerfolg. Microsoft bietet
im Rahmen von .NET die Weiterentwicklung C# (gesprochen: C sharp) an.
1.5 Aufgaben
1. Schreiben Sie das Programm tunix.c und versuchen Sie die Ergebnisse der Vorlesung selbst zu
erzeugen.
2. Wandeln Sie das Programm tunix.c mit allen C-Compilern um, auf die Sie Zugriff haben. Dokumentieren Sie bitte die Ergebnisse.
Grundlagen
2.1 Übersicht
FT
Kapitel 2
DR
A
Dieses Kapitel gibt Ihnen eine erste Übersicht über einige wichtige Dinge in C. So können Sie gleich
(ganz) einfache Programme schreiben, umwandeln und ausführen. Ich werde Ihnen aber nicht gleiche alle
Details „verraten“ und nicht alles genau erklären können. Das hole ich dann in den folgenden Kapiteln
nach. Insbesondere lernen Sie einige Datentypen und Funktionen kennen, die Sie immer wieder verwenden
werden.
2.2 Lernziele
• Gültige Namen in C-Programmen bilden können.
• Die verschiedenen Komponenten beschreiben können, die ein C-Programm ausmachen.
• Den Präprozessor-Befehl #include kennen.
• Einige Funktionen der
• Etwas auf der Konsole ausgeben können. Bibliothek stdio kennen.
• Variablen von der Konsole einlesen und auswerten können.
2.3 Namen
Namen für die Bezeichnung von Variablen, Funktionen, Konstanten etc. können in C aus den Buchstaben
A–Z, a–z, dem Ubnterstrich _ sowie den Ziffern 0–9 gebildet werden, wobei das erste Zeichen keine Ziffer
sein darf. In Syntaxdiagrammen dargestellt sieht das so aus:
2.4 Struktur von C-Programmen
Ein Programm ist ein in einer Programmiersprache formulierter Algorithmus. Ein Algorithmus ist eine
präzise, d. h. in einer genau definierten Sprache abgefasste, endliche Beschreibung eines allgemeinen Verfahrens unter Verwendung ausführbarer elementarer Verarbeitungsschritte. Die Bezeichnung wird aus dem
Namen des persischen Lehrbuchautors Abu Ja’far Mohammed ibn Mûsâ al-Khowârizmî hergeleitet. Genauer kann man einen Algorithmus so charakterisieren:
Eindeutigkeit Er ist eindeutig und unmissverständlich formuliert.
Endlichkeit Er ist in endlich vielen Schritten beschrieben.
7
KAPITEL 2. GRUNDLAGEN
DR
A
FT
8
Abbildung 2.1: Namensbildung in C
2.4. STRUKTUR VON C-PROGRAMMEN
9
Terminiertheit Er endet in endlichen vielen Schritten.
FT
Reproduzierbarkeit Er ist aus wohldefinierten elementaren Schritten aufgebaut.
Ein Programm kann in einer Datei stehen oder auf mehrere Dateien verteilt werden. Bisher haben wir
nur Dateien (genaugenommen nur eine) mit der Endung .c kennengelernt. Diese sind Eingaben für den
Compiler. Weitere Dateien definieren Schnittstellen. Diese werden Header-Dateien genannt und haben die
Endung .h. Einen Beispielausschnitt einer solchen Datei, limits.h zeigt Abb. 2.2 Sie zeigt die wichtigsten
/*limits.h - implementation dependent values */
#if
_MSC_VER > 1000
#pragma once
#endif
#ifndef _INC_LIMITS
#define _INC_LIMITS
CHAR_BIT
SCHAR_MIN
SCHAR_MAX
UCHAR_MAX
8
(-128)
127
0xff
#ifndef
#define
#define
#else
#define
#define
#endif
_CHAR_UNSIGNED
CHAR_MIN
SCHAR_MIN
CHAR_MAX
SCHAR_MAX
CHAR_MIN
0
CHAR_MAX
UCHAR_MAX
/* _CHAR_UNSIGNED */
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
MB_LEN_MAX
SHRT_MIN
SHRT_MAX
USHRT_MAX
INT_MIN
INT_MAX
UINT_MAX
LONG_MIN
LONG_MAX
ULONG_MAX
#endif
/* _INC_LIMITS */
/*
/*
/*
/*
number of bits in a char */
minimum signed char value */
maximum signed char value */
maximum unsigned char value */
DR
A
#define
#define
#define
#define
/* mimimum char value */
/* maximum char value */
5
/* max. # bytes in multibyte char */
(-32768)
/* minimum (signed) short value */
32767
/* maximum (signed) short value */
0xffff
/* maximum unsigned short value */
(-2147483647 - 1) /* minimum (signed) int value */
2147483647
/* maximum (signed) int value */
0xffffffff
/* maximum unsigned int value */
(-2147483647L - 1) /* minimum (signed) long value */
2147483647L
/* maximum (signed) long value */
0xffffffffUL /* maximum unsigned long value */
Abbildung 2.2: limits.h für VC++ .NET 2003
Längen der ganzzahligen Datentypen. Sie finden diese Dateien im Verzeichmis include Ihrer Entwicklungsumgebung oder Ihres Compilers.
Die Fließkomma Datentypen in C sind float, double und long double. Sie werden es vor Allem mit zwei
Arten von Header-Dateien zu tun haben. Da sind zunächst die Header-Dateien, die mit den Bibliotheken
von C kommen. Es ist guter Stil, C-Programme modular aufzubauen. Auch Sie werden also Deklarationen von Schnittstellen in eigenen Header-Dateien vornehmen. So können Sie Programme, die sie bereits
geschrieben haben beim Entwickeln neuer Programme leicht heranziehen. Für fast jedes Ihrer Programme werden Sie die C Standard-Bibliotheken heranziehen. Die ersten davon werden Sie in diesem Kapitel
kennenlernen.
KAPITEL 2. GRUNDLAGEN
10
2.5 Datentypen
Typ
unsigned char
signed char
int
unsigned int
unsigned short
signed short
unsigned long
signed long
FT
Für den Anfang beschränken wir uns auf Zahlen und Zeichen. Die folgende Abb. 2.3 zeigt die verschieSpeicherplatz
(Byte)
1
1
2
oder 4
2
oder 4
2
2
4
4
Wertebereich
von
bis
0
255
−128
127
−32.768
32.767
−2.147.483.648 2.147.483.647
0
65.535
0 4.294.967.295
0
65.535
−32.768
32.767
0 4.294.967.295
−2.147.483.648 2.147.483.647
Abbildung 2.3: C Datentypen, Teil 1: Ganze Zahlen und Zeichen
DR
A
denen ganzzahligen Datentypen in ANSI C. Die Längen in Abb. 2.3 sind die, die der Standard garantiert.
limits.h enthält die Realität „Ihres“ Compilers. Die Unterscheide in den Längen von int ergeben sich
daraus, dass die Länge eines int immer die eines Maschinenregisters ist. Auf 16-Bit Maschinen also 2
Byte und auf 32-Bit Maschinen 4 Byte.
Die ersten beiden Zeilen von Abb. 2.4 zeigt die Fließkomma-Datentypen in ANSI C. Die dritte Ziele
Typ
float
double
long double
Speicherplatz
(Byte)
32
64
80
Wertebereich
von
bis
±1, 7 · 10−38
±3, 4 · 1038
±2, 2 · 10−308
±1, 7 · 10308
−4932
±1, 7 · 10
±3, 4 · 104932
Abbildung 2.4: C Datentypen, Teil 2: Fließkommazahlen
mit long double ist kein Bestandteil des ANSI C99 Standards, wird aber von eingen Compilern auf einigen
Betriebssystemen unterstützt.
Variablen werden deklariert, indem Sie den Typ angeben, z. B. „int“, gefolgt von dem Namen. Sie
werden definiert, in dem die Deklaration mit der Zuweisung eines Wertes endet. Hier einige Beispiele:
/******************************************
datatypes02.c Deklaration von numerischen Datentypen
*******************************************/
int main(void)
{
{
int a;
long b;
}
{
int c =5;
long d = 888888L;
}
return 0;
}
Variablendeklarationen und -definitionen können fast überall verwendet werden. Es ist aber üblich diese
am Anfang eines Blocks zu tun. Ein Block ist dabei alles, was zwischen zwei geschweiften Klammer steht:
2.6. FUNKTIONEN
11
FT
{...}. Diese können die Klammern der main Funktion sein, die wir bereits in tunix.c gesehen haben
oder ein anderes von Ihnen gesetztes Klammerpaar.
Für ganzzahlige Konstanten gelten folgende Regeln:
• Eine dezimale Konstante beginnt nie mit einer 0.
• Eine oktale Konstante (Zahl zur Basis 8 beginnt immer mit einer 0 und darf nur die Ziffern 0 bis 7
enthalten.
• Eine hexadezimale Konstante (Zahl zur Basis 16 beginnt mit 0x oder 0X4. Sie darf die Ziffern 0–9
und die Buchstaben a–f oder A–F enthalten.
• Die Suffix U bzw L groß oder klein geschrieben und in beliebiger Reihenfolge definieren long bzw.
unsigned.
• Ein Minus- oder Plus Zeichen (−,+) gilt formal nicht als Bestandteil einer ganzzahligen Konstante
(siehe 4, unäre Operatoren).
Fließkommakonstanten sind double, es sei denn Sie geben durch den Suffix f bzw. F explit float an.
Aufzählungstypen definieren Namen für ganzzahlige Konstanten, wie in
enum boolean {FALSE, TRUE};
DR
A
Der erste Name in der Liste hat den Wert 0, der zweite den Wert 1 usw.
Die enum Konstanten wie FALSE oder TRUE werden ohne enum-Namen wie boolean verwendet.
Sie müssen innerhalb eines enum-Namens eindeutig sein. Verwenden Sie in einem Programm mehrere
enums, so müssen die Namen der Konstanten innerhalb aller verwendeten enums eindeutig sein.
Ein weiterer Datentyp ,den Sie oft verwenden werden, ist String. Ein String ist eine Zeichenkette.
2.6 Funktionen
Funktionen dienen zur Strukturierung von Programmen. Sie sollen dabei helfen, komplexe Abläufe in
einzelne übersichtliche Schritte zu zerlegen. Um eine Funktionen verwenden zu können, müssen Sie Folgendes wissen:
• Den Namen der Funktion.
• Die Parameter, die sie übergeben können oder müssen und deren Typ.
• Den Rückgabewert, den Sie von der Funktion erwarten können.
Das sind allerdings „nur“ formale Dinge. Ohne diese zu kennen, werden Sie ein Programm aber nicht
erfolgreich umwandeln können. Es ist guter Stil, die drei genannten Dinge in einer Header-Datei zu deklarieren. Dies geschieht in der Form eines sog. Prototyps:
[rückgabe typ] funktionsname([typ1 parameter1, typ2 parameter2, ...]);
Für Funktionen die aus einer Standard-Bibliothek oder einer von Ihnen geschriebenen Bibliothek stammen, können Sie die Prototypen dann einfach durch einen #include-Befehl für Ihr Programm verfügbar
machen.
Außer diesen Informationen müssen Sie natürlich unbedingt wissen, was die Funktion eigentlich leistet.
Hier zunächst ein Beispiel für einen Prototyp
int summe(int a, int b);
Der Name „summe“ legt nahe, dass die Funktion die Summe der beiden Parameter a und b berechnet und
zurückliefert. Die gesamte Funktion kann dann so aussehen:
KAPITEL 2. GRUNDLAGEN
12
Vollständig kann das so aussehen:
FT
int summe(int a, int b);
{
return (a + b);
}
DR
A
/******************************************
summe.c berechnet die Summe zweier ganzer Zahlen
******************************************/
#include <stdio.h>
int summe(int a, int b)
{
return (a + b);
}
int main(void)
{
int pa = 9;
int pb = 16;
printf("%d%s%d%s%d%s",pa," + ",pb," = ",summe(pa,pb),".");
return 0;
}
Als Ergebnis sollten Sie auf der Konsole folgendes sehen:
9 + 16 = 25.
Sehen wir uns, wie schon beim Programm tunix.c an, was wir daraus lernen können.
1. In der ersten Zeile wird, wie in fast jedem C Programm, die Header-Datei stdio.h deklariert,
d. h. durch eine #include Direktive eingebunden. Dies wird so häufig verwendet, dass Sie dies in
Visual Studio auch weglassen können. Es ist aber nicht garantiert, dass jeder andere Compiler das
Programm dann auch umwandeln kann.
2. Die folgenden vier Zeilen enthalten die Deklaration und die Definition der Funktion summe. Beachten Sie bitte, dass hier das Semikolon am Ende der ersten Zeile fehlt, während es bei der Deklaration
des Prototypen in der Header-Datei notwendig ist.
3. Die geschweiften Klammern eines Blocks — hier der Programmcode für summe und main — setze
ich immer so, dass sie paarweise in einer Spalte stehen. Eine andere übliche Konvention ist es, die
öffnende geschweifte Klammer { in die gleiche Zeile, wie die Deklaration der Funktion zu schreiben.
Die schließenden Klammern } werden aber genauso eingerückt, wie ich es mache.
4. Die einzige Codezeile der Funktion summe liefert den Inhalt des eingeklammerten Ausdrucks zurück. Es werden zunächst die Werte der übergebenen Parameter a und b addiert und dieser Wert
dann an Programm zurückgegeben, dass die Funktion summe aufgerufen hatte. In diesem Fall können Sie die Klammern auch weglassen, da der Additionsoperator stärker bindet. Ich empfehle Ihnen
dringend, Klammern immer dann zu setzen, wenn Sie dadurch die Lesbarkeit des Programms verbessern.
5. Die Funktion main(void) kennen wir bereits.
6. In ihren ersten beiden „richtigen“ Codezeilen werden zwei Variablen (pa und pb) als int deklariert
und auch sofort definiert; d. h. es werden ihnen die Werte 9 bzw. 16 zugewiesen.
7. Es folgt dann der Aufruf von printf mit einer etwas längeren Parameterliste. printf stammt
aus stdio. Diese beginnt mit einer Reihe von Zeichen, die in "-Zeichen eingeschlossen ist, dem
sogenannten Formattierungsstring. Der Übersichtlichkeit halber stelle ich die entsprechenden Teile
des Formattierungstrings den zu formattierenden Elementen in der folgenden Tabelle gegenüber:
2.7. PRINTF UND PUTCHAR
13
Variable/
Konstante
pa
" + "
pb
" = "
summe(pa,pb)
"."
Ergebnis
9
+
16
=
25
.
FT
Formattierung
%d
%s
%d
%s
%d
%s
Ganz analog könnten Sie Funktionen schreiben die multiplizieren („*“ statt („+“), dividieren ((„/“ statt
(„+“) oder subtrahieren ((„-“ statt („+“).
2.7 printf und putchar
Eine Basisfunktion in C ist printf. Der Prototyp von printf ist:
int printf(const char *, ...);
printf ist Bestandteil der Standard-Bibliothek und wird durch include von stdio.h verfügbar gemacht.
Diese Funktion leistet folgendes:
DR
A
• Sie erhält als ersten (eventuell einzigen) Parameter einen String mit Formattierungsangaben.
• Für jede Formattierungsangabe folgt dann eine Variable, z. B. eine Zahl, ein String oder eine Konstante. Die Formattierungsangabe bestimmt, wie die Variable etc. auf der Console ausgegeben wird.
• Die Ellipse (. . . ) gibt an, dass weitere Parameter folgen können.
• char* bezeichnet einen String, siehe hierzu später mehr in Kap. 6 und 9.
• Das Schlüsselwort const bedeutet, dass die übergebenen Parameter in der Funktion printf nicht
verändert werden können. Sie können also ganz sicher sein, dass „Ihre“ Variablen nach der Ausgabe
mit printf genauso „aussehen“, wie vorher.
• Als Rückgabewert liefert printf die Anzahl ausgegebener Zeichen.
Als einfaches Beispiel dient hier das Programm „Guten Morgen Shanghai“.
/******************************************
moinmoin01.c Gibt Guten Morgen Shanghai auf der Console aus
*******************************************/
#include <stdio.h>
int main()
{
int i = printf("%s","Guten Morgen Shanghai");
printf("%s%d%s","\nDas sind ",i," Zeichen");
return i;
}
Die Ausgabe ist:
Guten Morgen Shanghai
Das sind 21 Zeichen
Sehen wir uns, wie schon beim Programm tunix.c an, was wir daraus lernen können.
1. Das wichtigste zu erst: „Moin“, oder „Moinmoin“ ist einfach „guten Morgen“ auf Hamburgisch.
2. Sie können void beim Aufurf von main auch weglassen.
KAPITEL 2. GRUNDLAGEN
14
FT
3. Das Zeichen \n am Anfang des zweiten Aufrufs von printf definiert einen Zeilenumbruch und ist
ein Zeichen, eine sogenannte Escape-Sequenz
4. Zu den Zeichen, die bei der Ausgabe gezählt werden, gehören alle Zeichen, also auch Leerstellen,
Zeilenumbruch etc.
Die Sprache C ist so flexibel, dass man die Funktion printf hier auch etwas anders schreiben kann, z. B.
so:
/******************************************
moinmoin02.c Gibt Guten Morgen Shanghai auf der Console aus
*******************************************/
#include <stdio.h>
int main()
{
int i = printf("Guten Morgen Shanghai");
printf("\nDas sind %d Zeichen",i);
return i;
}
Was lernen wir daraus? Zumindest dieses:
DR
A
1. Man kann auf „%s“ verzichten, wenn man nur einen String ausgeben will.
2. Man kann Strings im Formatstring unterbringen
Warum braucht man denn überhaupt „%s“? Es ist kein guter Stil Stringkonstanten direkt in ein Programm
hineinzuschreiben. So muss man ja immer das Programm ändern, wenn der String sich ändert, z. B. weil
das Programm in einer anderen Sprache verwendet werden soll. Besser ist es dann, Variablen aus einer
Datei einzulesen, so dass man nur die Datei austauschen muss.
Zum Abschluss dieser Kurzvorstellung von printf nun noch einige weitere Formattierungssymbole
und Escape-Sequenzen, damit Sie sofort etwas mehr an Ihren Ausgaben gestalten können. Eine weitgehend vollständige Übersicht erhalten Sie in Kap. 3. In vielen Fällen wollen Sie aber Zeichen einzeln und
Einzelzeichen
bzw. Formatzeichen
\a
\t
\n
%c
%d
%f
Bedeutung
bzw. Typ
alert(BELL)
horizontal tab(HT)
line feed(LF)
char oder int(≤ 255)
int
float/double
ASCII-Wert bzw. Bedeutung
dezimal
hex
7
7
9
9
10
A
Einzelnes Zeichen
dezimal
Gleitpunktzahl: [−]ddd.dd
Abbildung 2.5: Escape- und Format-Zeichen in ANSI C
unmittelbar ausgeben. Dazu gibt es die Funktion
int putchar(char c);
die das Zeichen in der Variablen c ausgibt, oft auf die Konsole, aber dazu mehr in Abschn. 2.9. putchar
liefert im Erfolgsfall das Zeichen und sonst EOF. EOF ist ein Symbol für einen Wert, der angibt, dass das
Ende einer Datei erreicht ist.
Das Programm moinmoin01 können wir natürlich auch mit putchar schreiben, es ist dann aber
nicht mehr so gut lesbar und wir bekommen die Anzahl Zeiochen nicht geliefert:
/******************************************
moinmoin03.c Gibt Guten Morgen Shanghai mit
putchar auf der Console aus
2.8. SCANF UND GETCHAR
15
DR
A
FT
*******************************************/
#include <stdio.h>
void main(void)
{
putchar(’G’);
putchar(’u’);
putchar(’t’);
putchar(’e’);
putchar(’n’);
putchar(’ ’);
putchar(’M’);
putchar(’o’);
putchar(’r’);
putchar(’g’);
putchar(’e’);
putchar(’n’);
putchar(’ ’);
putchar(’S’);
putchar(’h’);
putchar(’a’);
putchar(’n’);
putchar(’g’);
putchar(’h’);
putchar(’a’);
putchar(’i’);
}
Natürlich hätte ich dass auch alles in weniger Zeilen anordnen können. Das wäre dann aber noch unübersichtlicher.
Die Funktion putchar stelle ich Ihnen bereits jetzt vor, damit Sie bald sinnvolle Programme schreiben
können.
2.8 scanf und getchar
Das Gegenstück zu printf für das Einlesen von Variablen über die Tastatur ist die Funktion scanf. Als
einfaches Beispiel betrachten wir ein Programm, dass aus Ihrem Geburtsdatum eine „Glückszahl“ erzeugt.
/******************************************
happybirthday1.c liest ein Geburtsdatum über
die Tastatur ein und "errechnet" eine Glückszahl
******************************************/
#include <stdio.h>
int main()
{
int tag;
int monat;
int jahr;
printf("\nGeben Sie bitte Ihr Geburtsdatum ein!");
printf("\nTag: ");
scanf("%d", &tag);
printf("\nMonat: ");
scanf("%d", &monat);
printf("\nJahr: ");
KAPITEL 2. GRUNDLAGEN
16
}
FT
scanf("%d", &jahr);
printf("%s%d%s%d%s%d%s","Ist Ihr Geburtsdatum wirklich der: "\
,tag,".",monat,".",jahr,"?");
srand(tag + monat + jahr);
printf("\nDann ist Ihre Glückszahl heute: %d\n",rand());
return 0;
Wie schon in früheren Abschnitten erläutere ich, was sich in diesem Programm „Neues tut“. Ich beginne
mit der ersten, Ihnen unbekannten Funktion, zumindest soweit es diesen Kurs angeht.
1. Die Funktion scanf wird gleich genauer erläutert. Hier nur so viel: Der erste Parameter enthält
einen Formattierungsstring, hier %d. scanf erwartet also eine int Eingabe. Der zweite Parameter
enthält den Namen einer Variablen. Beachten Sie aber, dass davor ein &-Zeichen steht. Dies ist der
Adressoperator. scanf erhält hier also als zweiten Parameter die Adresse der Variablen tag. Hierzu
mehr unten.
2. Die vorletzte printf-Zeile ist zu lang. Sie wird deshalb auf einer zweiten Zeile fortgesetzt. Das wird
dem Compiler durch den \ am Ende des ersten Teils des Befehls signalisiert.
3. Die Funktion srand() initialisiert einen Zufallszahlengenerator.
DR
A
4. In der vorletzen Zeile wird eine Zufallszahl ausgegeben und als Glückszahl „verkauft“.
Das für Sie zunächst geheimnisvolle ist der Adressoperator &. Um ihn zuverstehen betrachten Sie bitte
die Abb. 2.6.
Abbildung 2.6: Variablen und Adressen in C
Der Name einer Variablen bezeichnet ein Stück Speicherplatz in dem Sinne, dass Sie den Namen verwenden können, um den entsprechenden Wert in Ihrem Programm zu verwenden. Diese Variable belegt
ein Stück des Speichers. Ein Teil der Flexibilität von C kommt daher, dass Sie in Ihrem Programm Zugang
zu allen wichtigen Eigenschaften einer Variablen haben. So ermöglicht es Ihnen der Adressoperator &, die
Adresse einer Variablen abzufragen. Die Funktion scanf ist nun so „primitiv“, dass sie von Ihnen nicht
den Namen der Variablen erwartet, sondern die Adresse, an der diese Variable steht.
In C können Sie aber auch Namen für Adressen vergeben. Das geht so:
int *i;
char *name;
Hier werden durch datentyp* Namen für Adressen deklariert. Hier ist i keine Variable, die eine ganze
Zahl enthält, sondern eine Variable, die eine Adresse enthält, an der eine ganze Zahl steht. Ganz analog ist
der Inhalt von name nicht ein Zeichen, sondern eine Adresse an der ein Zeichen steht. Hier haben Sie also
Zeiger, englisch pointer, die auf Variablen „zeigen“. Haben Sie eine solche Variable, so benötigen Sie die
Umkehrung des Adressoperators. Die ist der Operator *. *i bzw. *name liefern also die Inhalte, die an
der jeweiligen Adresse stehen. Zum Beispiel liefert:
2.8. SCANF UND GETCHAR
17
}
diese Ausgabe
Var
1000
12FEDC
a
12FEDB
*Var
1000
&Var
12FEDC
12FED4
12FEDB
12FEE0
DR
A
i
zi
c
zc
FT
/* zeiger01.c Elementares Beispiel für * und &
Status: ok*/
#include <stdio.h>
void main(void)
{
int i = 1000;
int* zi = &i;
char c = ’a’;
char* zc = &c;
printf("
\tVar\t*Var\t&Var\n");
printf("i \t%d\t\t%X\n",i,&i);
printf("zi\t%X\t%d\t%X\n",zi,*zi,&zi);
printf("c \t%5c\t\t%X\n",c,&c);
printf("zc\t%X\t%c\t%X\n",zc,*zc,&zc);
a
Nach Definition enthalten zi bzw. zc die Adresse von zi bzw. zc, hier 12FEDC bzw. 12FEDB. Umgekehrt erhalten wir mit dem Operator * den Inhalt, der an der jeweiligen Adresse steht (1000 bzw. a). Die
beiden leeren Felder in der Spalte *Var sind nicht gefüllt, da der *-Operator nur bei Variablen einen Sinn
macht, die eine Adresse enthalten; dies ist bei i und c aber nicht der Fall. Der Addressoperator ist immer
anwendbar. Auch eine Variable, die eine Adresse enthält ist ja an einer Adresse gespeichert.
Wir werden diese Operatoren an einigen Stellen benötigen, damit Sie frühzeitig sinnvolle Programme
schreiben können. Die Einzelheiten des Umgangs mit Adressen in C lernen Sie aber erst im weiteren Verauf
der Vorlesung.
Für das Einlesen von Variablen über die Tastatur verwenden Sie bei scanf die bereits von printf
bekannten Formattierungszeichen.
Wollen Sie aber in ihrem Programm nicht auf das Drücken der Eingabetaste am Ende einer Zeile warten
oder lesen Sie aus einer Datei, so werden Sie in vielen Fällen die Zeichen einzeln behandeln wollen. Dazu
gibt es die Funktion
int getchar(void);
Diese Funktion liest ein Zeichen ein. Der Rückgabewert ist das gelesene Zeichen oder EOF, wenn ein
Fehler aufgetreten ist.
Ich erspare Ihnen an dieser Stelle dass unsinnige Wiederholen von getchar-Aufrufen und nutze die
Chance Ihnen eine einfache Schleifenkonstruktion vorzuführen. Wir wollen eine Reihe von Zeichen kopieren. Im nächsten Abschnitt werden Sie lernen, wie man damit eine Datei Zeichen für Zeichen einlesen und
in eine neue Datei ausgeben kann. Natürlich hat jedes Betriebssystem für so etwas eine Systemfunktion,
aber die muss ja auch mal jemand geschrieben haben.
Was wollen wir also machen:
Wir lesen ein Zeichen aus einer Datei und geben es aus. Das machen wir solange, bis das Ende
der Datei erricht ist.
In C liest sich das so:
/**********************************
copy01.c Einfachstes Kopierprogramm
KAPITEL 2. GRUNDLAGEN
18
FT
Status: incomplete
**********************************/
#include <stdio.h>
int main(void)
{
int c;
c = getchar();
while(c!=EOF)
{
putchar(c);
c = getchar();
}
}
Wie bereits mehrfach erkläre ich Ihnen nun was hier abläuft, vor allem was neu ist. Wer etwas nicht versteht,
was bereits früher erklärt wurde, frage bitte unbedingt jetzt nach.
1. Die ersten Zeilen sind bereits bekannt. Der Status ist „incomplete“, weil jede Fehlerbehandlung fehlt.
DR
A
2. Die Variable c wird als int deklariert. Dies überrascht zunächst. Allerdings werden char und int
in C gleichartig und soweit möglich austauschbar gehandhabt. Da es mehr ints als chars gibt, ist
das Programm so etwas allgemeiner.
3. c = getchar();Hieraus lernen wir zwei Dinge.
3.1. Weiter oben haben Sie gelesen, dass getchar das Zeichen oder EOF zurückliefert. Der Wert
von EOF ist implementierungsabhängig. Sie finden ihn in <stdio.h>, in MS VC7 ist er −1.
Daher also die Deklaration von c als int: Auch der Wert von EOF muss aufgenommen werden
können.
3.2. Wir stehen vor dem Beginn einer Schleife. Sehr oft müssen vor dem ersten Durchlauf einige
Dinge erledigt werden, hier eben das Lesen des ersten Zeichens.
4. In der nächsten Zeile beginnt eine Schleife. while (Bedingung) sorgt dafür, dass der auf dieses
Kommando folgende Block ausgeführt wird, wenn die Bedingung wahr ist und die Schleife verlasse
wird, wenn Sie falsch ist. Die Bedingung lautet hier c!=EOF. „!=“ bedeutet „nicht gleich“, d. h. es
wird geprüft, dass nicht das Zeichen „Dateiende“ gelesen wurd.
5. In dem folgenden Paar geschweifter Klammern steht zunächst ein Aufruf putchar(c). Im ersten
Durchlauf der Schleife wird also das vor Schleifenbeginn gelesene Zeichen ausgegeben. Anschließend wird das nächste Zeichen gelesen usw.
Wenn Sie das Programm so aufrufen, werden Sie nicht viel Freude damit haben. Probieren Sie mal aus, wie
das Programm auf Eingaben reagiert. Für Weiteres warten Sie bitte bis zum nächsten Abschnitt ab, dann
werden Sie hiermit richtig umgehen können.
2.9 Umlenken von Ein- und Ausgabe
Betrachten wir einmal die Progrämmchen, die wir bisher kennengelernt haben, unter dem Gesichtspunkt
der Benutzerfreundlichkeit und Testbarkeit. Da stört es (zumindest mich), die Eingaben — und sei es nur
zum Testen — immer wieder eintippen zu müssen. Insofern hätte es einen gewissen Charme, wenn man
die Eingabedaten aus einer Datei einlesen könnte. Das ist gar nicht so abstrus, wie es auf den ersten Blick
erscheinen mag:
• Es erleichtert das Testen: Testdaten einmal erfassen, immer wieder verwenden.
• Auch große Mengen von Daten können übernommen bzw. ausgegeben werden.
2.10. TRIGRAPHS
19
Jedes C-Programm kennt drei sogenannte „file descriptors“ oder „file handle“
FT
0 Steht für die Standard-Eingabeinheit, kurz stdin
1 Steht für die Standard-Ausgabeinheit, kurz stdout
2 Steht für die Standard-Ausgabeinheit für Fehlermeldungen, kurz stdout.
Nehmen Sie keine Änderungen vor, so ist stdin mit der Tastatur und stdout, stderr mit der Konsole
(dem Bildschirm) verbunden. Das kann man aber ändern. Bei stdin und stdout kommt das häufig vor
und geht ganz einfach. Für stderr bevorzugt man oft die Konsole, da man so direkt über Fehler informiert
wird. Möchte man den Verlauf aber protokollieren, so wird man auch stderr gerne einer Datei zuordnen.
Dazu gibt es aber keine derartige eingebaute Möglichkeit.
Sehen wir uns das beim Programm happybirthday1.c einmal an. Dies erwartet drei Eingaben,
jede einzelne durch Drücken der Enter-Taste beendet. Diese Eingaben kann ich nun in eine Datei schreiben,
die in berndbirth.in nenne:
29
11
1954
Um die Eingabe von der Tastatur auf die Datei umzulenken, rufe ich das Programm nun so auf:
happybirthday1 < berndbirth.in
DR
A
Das Zeichen < bewirkt, dass die Eingabe umgeleitet wird. Entsprechend bewirkt das Zeichen >, dass die
Ausgabe in eine Datei umgleitet wird. Rufe ich das Programm mit dem Befehl
happybirthday1 < berndbirth.in > berndbirth.out
auf, so steht in der Datei berndbirth.out
Geben Sie bitte Ihr Geburtsdatum ein!
Tag:
Monat:
Jahr: Ist Ihr Geburtsdatum wirklich der: 29.11.1954?
Ihre Glückszahl ist heute: 6550
2.10 Trigraphs
Trigraphs sind Folgen von drei Zeichen, die in C-Quelltext bestimmte Sonderzeichen ersetzen, welche auf
manchen Tastaturen nicht vorhanden sind. Die folgende Tabelle zeigt die verfügbaren Trigraph-Zeichenketten:
Trigraph
??=
??(
??)
??/
??’
??!
??<
??>
??-
Zeichen
#
[
]
\\
^
|
{
}
˜
Beispiel: ??=define arraycheck(a,b) a??(b??) ??!??! b??(a??) bedeutet #define arraycheck(a,b) a[b] || b[a].
Manche Compiler verarbeiten Trigraphs nicht direkt. Grund: Rechner, auf denen diese Compiler laufen,
erlauben die Eingabe aller Zeichen des C-Zeichensatzes. Ein Beispiel dafür sind Borland-Compiler. Trigraphs enthaltende Quelltextdateien müssen mit einem Hilfsprogramm (TRIGRAPH) vorverarbeitet werden.
Bei der Neuprogrammierung sollte - von extremen Ausnahmen abgesehen, auf den Einsatz von Trigraphs
verzichtet werden. Eine der Ausnahmen könnte die Teilnahme am IOCCC sein.
KAPITEL 2. GRUNDLAGEN
20
2.11 Historische Anmerkungen
2.12 Aufgaben
FT
Für viele erscheint es auf den ersten Blick unverständlich, dass C so elementare Dinge, wie einen Druckbefehl (u. v.˙ m) nicht direkt in der Sprache enthält, sondern dass diese Funktionalität über Bibliotheken extra
zur Verfügung gestellt wird. Durch diesen modularen Aufbau ist die Sprache im Kern aber sehr schlank und
einfach1 . Auf der anderen Seite ist sie durch Bibliotheken praktisch beliebig erweiterbar. Gerade deshalb
erfreut sie sich einer großen Verbreitung und war der Anstoß für viele weitere Entwicklungen, wie C++
und letztendlich auch Java.
1. Hier folgen einige Deklarationen für Variablen in C. Geben Sie bitte an, ob die Namen in diesen
Deklarationen gültig sind! Begründen Sie bitte Ihre Antwort!
1.1. int 1zweidrei;
1.2. int einszwei3;
1.3. char Eins_zwei_drei;
1.4. char Eins zwei drei;
1.5. char _;
DR
A
1.6. char [] void;
1.7. double pi;
1.8. Double E;
2. Finden Sie bitte heraus, ob char von Ihrem Compiler als signed oder unsigned interpretiert wird
und belegen Sie Ihr Ergebnis!
3. Finden Sie limits.h in Ihrer Installation und vergleichen Sie mit dem hier präsentierten Ausschnitt!
4. Welche Wertebereiche haben int bzw. unsigned int auf einer 64-Bit Maschine?
5. Was passiert, wenn Sie in dem Programm summe.c die Zeile
printf("%d%s%d%s%d%s",pa," + ",pb," = ",summe(pa,pb),".");
durch
printf("%d%s%d%s%d%s",pa," + ",pb," = ",summe,".");
ersetzen? Versuchen Sie, dass Ergebnis zu erklären!
6. Was passiert, wenn Sie das Programm happybirthday1 mit dem Befehl
happybirthday1 > berndbirth.out
aufrufen?
7. Versuchen Sie bitte herauszufinden, was passiert, wenn Sie printf mit einem Formattierungszeichen %c aufrufen, wenn c nicht aus diesem Kapitel bekannt ist. Dokumentieren Sie das Ergebnis.
8. Schreiben Sie ein Programm, das eine Temperatur in Celsius als Eingabe erhält, in Fahrenheit umrechnet, und das Ergebnis ausgibt.
1 Sie
ist wirklich viel einfacher, als manche andere Sprachen, auch wenn sie Ihnen zunächst als schwierig genug erscheinen mag.
2.12. AUFGABEN
21
FT
9. Schreiben Sie bitte nach dem Beispiel der Funktion summe aus Abschn. 2.6 Funktionen für Division,
Multiplikation und Subtraktion und probieren Sie diese aus!
10. Schreiben Sie ein Programm, dass eine Körpergröße h in Meter und ein Gewicht w in Kilogramm
erhält und den Body-Mass-Index (BMI) berechnet und ausgibt:
BM I :=
w
h2
11. Schreiben Sie Programme, die angelsächsischen Maßeinheiten in ihr Äquivalent im Meter-Kilogramm
System umrechnen. Angelsächsische Einheiten sind u. a.
Gewichte pound, ounce, dram, pennyweight, grain
Hohlmaße gallon, quart, pint, gill, fluidounce, fluidram, minim, barrel
US Trockenmaße bushel, peck, quart, pint
Längen mile, rod, yard, foot, inch
Die Umrechnungskoeffizienten versuchen Sie bitte zunächst selbst in Erfahrung zu bringen.
12. Schreiben Sie Funktionen, die Einheiten, die von TEX verwendet werden, in die Basiseinheit „scaled
point (sp)“ umrechnen. Die Einheiten, die in TEX verwendet werden sind:
Bezeichnung
point
pica
inch
big point
Zentimeter
Millimeter
didot point
cicero
scaled point
Wert
72.27 pt = 1 inch
1 pc = 12 pt
1 inch = 72.27 pt = 2.54 cm
72 bp = 1 inch
2.54 cm = 1 inch
10 mm = 1 cm
1157 dd = 1238 pt
1 cc = 12 dd
65536 sp = 1 pt
DR
A
Abk.
pt
pc
in
bp
cm
mm
dd
cc
sp
Rufen Sie diese Funktionen in einer main Funktion auf.
13. Welche der folgenden Variablendefinitionen ist nicht zulässig oder nicht sinnvoll?
13.1. int a = 2.5;
13.2. nt b = ’?’;
13.3. char z = 500;
13.4. int big = 40000;
13.5. double fläche = 1.23E+5
13.6. long count = 0
13.7. char c = ’\’’;
13.8. unsigned char ch = ’\201’;
13.9. unsigned int size = 40000;
13.10. float value = 12345.06789;
Wenn etwas falsch oder unsinnig ist, erklären Sie bitte warum.
14. Welche der folgenden Variablendeklarationen und -definitionen ist korrekt, welche falsch? Was liefert die Ausgabe mit printf ?
14.1.
KAPITEL 2. GRUNDLAGEN
22
15.1. ⌈3, 99⌉ =
15.2. ⌊3, 99⌋ =
15.3. ⌈−3, 11⌉ =
DR
A
15.4. ⌊−3, 11⌋ =
FT
15. In der C-Bibliothek gibt es eine Reihe mathematischer Funktionen. Sie finden Sie in der HeaderDatei math.h. Die Funktion ceil(double d) (Decke) liefert die kleinste ganze Zahl ⌈d⌉, die größer
oder gleich d ist. Entsprechend liefert floor (Fußboden) die größte ganze Zahl ⌊d⌋, die kleiner oder
gleich d ist. Beantworten Sie bitte die folgenden Fragen und bestätigen Sie durch ein C-Programm,
dass die Funktionen aus der C-Bibliothek dieses Ergebnis liefern!
FT
Kapitel 3
Formattierte Ein- und Ausgabe
3.1 Übersicht
DR
A
Die Ein- und Ausgaben waren sehr einfach. Auch für die kleinen Programme, die Sie hier zur Übung
schreiben sollen, wollen wir uns nun die Eingabe von Werten etwas einfacher machen und die Ausgaben
ansprechender gestalten. Das werden Sie auch an anderen Stellen verwenden können. Bei dieser Gelegenheit ergibt es sich auch, etwas zur Konvertierung von Zeichen in Zahlen und zurück zu erläutern.
3.2 Lernziele
• Die Escape-Sequenzen in C sicher verwenden können.
• Ausgabe von Zahlen und Strings sicher auf der Konsole gestalten können.
• Zahlen und Strings sicher von der Konsole einlesen können.
• Die Formattierungszeichen in C zur Ausgabe und Eingabe der verschiedenen C-Datentypen sicher
einsetzen können.
3.3 Ausgabe
Für die Ausgabe stehen uns zum Einen weitere Escape-Sequenzen zur Verfügung. Die folgende Abbildung
3.1 enthält eine vollständige Übersicht. Wollen wir etwa drei Zahlen eingeben und die Summe berechnen,
so können wir das wie folgt machen. Dabei werden die bereits in Kap. 2 eingeführte Escape-Sequenz \n
und die Escape-Sequenz \t verwendet:
/*formio10.c Summe von drei
Status: incomplete*/
#include <stdio.h>
int main()
{
int a,b,c;
printf("\nBitte geben Sie
scanf("%d",&a);
printf("\nBitte geben Sie
scanf("%d",&b);
printf("\nBitte geben Sie
scanf("%d",&c);
printf("\n\t%d",a);
printf("\n\t%d",b);
Zahlen
den ersten Summanden ein:");
den zweiten Summanden ein:");
den dritten Summanden ein:");
23
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
24
Bedeutung
\a
\b
\t
\n
\v
\f
\r
\"
\’
\?
\\
\0
\ooo (max. 3 Oktalziffern
\xhh (max. 2 Hex Ziffern hh
alert(BELL)
backspace(BS)
horizontal tab(HT)
line feed(LF)
vertical tab(VT)
form feed(FF)
carriage return(CR)
\"
’
?
\
NUL-Zeichen
Bitmuster
Bit-Muster
ASCII-Wert
dezimal hex
7
7
8
8
9
9
10
A
11
B
12
C
13
D
34
22
39
27
63
3F
92 5C
0
0
ooo
hh
FT
Einzelzeichen
Abbildung 3.1: Escape-Sequenzen in ANSI C
DR
A
printf("\n\t%d",c);
printf("\n\t--------");
printf("\n\t%d",a+b+c);
return 0;
}
Geben wir nach und nach 1, 10, 100 ein, so erhalten wir:
1
10
100
-------111
Das Ergebnis ist zwar schon spaltenweise angeordnet, mindestens zwei Dinge gefallen mir an der Ausgabe
aber noch nicht:
1. Ich hätte die Spalten gerne rechtsbündig, wie es bei Zahlenkolonnen üblich ist.
2. Ich möchte soviele −-Zeichen als Trennstriche haben, wie die Zahl lang ist.
Beides bekommen wir teilweise hin, wenn Sie noch Folgendes wissen:
Sie können zusätzlich noch die Breite angeben, die ein Feld haben soll, indem vor dem Prozentzeichen
die Anzahl Stellen angeben wird. Standardmäßig wird die Ausgabe rechtsbündig angeordnet, wenn Sie
noch ein −-Zeichen vor die Länge setzen, erfolgt linksbündige Ausgabe. Überzählige Stellen werden mit
Blanks aufgefüllt. Geben Sie keine Feldbreite an, so ist das Feld so lang wie der jeweilige Wert, eine
bündige Ausgabe kann also nicht erwartet werden. Mit diesen Änderungen sieht das Programm nun so aus
/*formio11.c Summe von drei Zahlen, bessere Ausgabe
Status: incomplete*/
#include <stdio.h>
int main()
{
int a,b,c;
printf("\nBitte geben Sie den ersten Summanden ein:");
scanf("%d",&a);
printf("\nBitte geben Sie den zweiten Summanden ein:");
3.3. AUSGABE
25
FT
scanf("%d",&b);
printf("\nBitte geben Sie den dritten Summanden ein:");
scanf("%d",&c);
printf("\n\t%5d",a);
printf("\n\t%5d",b);
printf("\n\t%5d",c);
printf("\n\t%5s","---");
printf("\n\t%5d",a+b+c);
return 0;
}
Das Ergebnis
1
11
111
--123
entspricht weitgehend meinen Wünschen. Folgendes fehlt aber noch:
DR
A
1. Die Anzahl −-Zeichen wird noch nicht an die längste Zahl angepasst. Wie man das macht, kann ich
Ihnen erst später erklären.
2. Die Eingabe ist völlig unsicher. Probieren Sie doch mal, eine Zahl mit Punkt oder Komma einzugeben! Deshalb ist der Status auch dieses Programmes (natürlich) „incomplete“.
Die Tabelle in Abb. 3.2 zeigt alle Formattierungsbuchstaben für ganze Zahlen, dezimal, oktal und hexadezimal. Im Formattierungsstring werden diese Buchstaben durch ein vorgestelltes %-Zeichen gekennzeichnet. Beachten Sie bitte, dass Zeichen hier wie ganze Zahlen behandelt werden und deshalb ebenfalls in
Typ
c
d
ld
u
o
x
X
Argumenttyp
char oder int(≤ 255)
int
long int
unsigned int
unsigned int
unsigned int
unsigned int
Darstellung
Einzelnes Zeichen
dezimal
dezimal
dezimal
oktal
hexadezimal mit a,b,c,d,e,f
hexadezimal mit A,B,C,D,E,F
Abbildung 3.2: Ausgabe von Zeichen und ganzen Zahlen
dieser Tabelle erscheinen. Das folgende Programm gibt die Eingaben, die wir bereits in dem Programm
formio11.c gemacht haben in einigen dieser Formate zum Vergleich aus:
/*formio12.c Summe von drei
Status: incomplete*/
#include <stdio.h>
int main()
{
int a,b,c;
printf("\nBitte geben Sie
scanf("%d",&a);
printf("\nBitte geben Sie
scanf("%d",&b);
printf("\nBitte geben Sie
scanf("%d",&c);
Zahlen, in vier Formaten
den ersten Summanden ein:");
den zweiten Summanden ein:");
den dritten Summanden ein:");
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
26
}
FT
printf("\n\tZeichen\tdez.\tokt.\thex.");
printf("\n\t%3c\t%3d\t%3o\t%3X",a,a,a,a);
printf("\n\t%3c\t%3d\t%3o\t%3X",b,b,b,b);
printf("\n\t%3c\t%3d\t%3o\t%3X",c,c,c,c);
printf("\n\t%3s\t%3s\t%3s\t%3s","-","---","---","--");
printf("\n\t%3c\t%3d\t%3o\t%3X",a+b+c,a+b+c,a+b+c,a+b+c);
return 0;
Das Ergebnis sieht im wesentlichen so aus:
Bitte geben Sie den ersten Summanden ein:1
Bitte geben Sie den zweiten Summanden ein:11
Bitte geben Sie den dritten Summanden ein:111
dez.
1
11
111
--123
okt.
1
13
157
--173
hex.
1
B
6F
-7B
DR
A
Zeichen
?
?
o
{
Aus der Ausgabe sehen wir u. a., dass der ASCII Code für den Buchstaben „o“ decimal 111 und hexadezimal 6F ist. Die beiden „?“ stehen für Zeichen, die ich nicht zur Verfügung hatte.
Ganz analog werden Gleitkommazahlen eingegeben und ausgegeben. Wie bei ganzen Zahlen kann auch
Typ
f
lf
e/le
E/lE
g/lg
G/lG
s
Argumenttyp
float/double
double
float/double
float/double
float/double
float/double
String
Darstellung
Gleitpunktzahl: [−]ddd.dd
Gleitpunktzahl: [−]ddd.dd
Exponentenschreibweise: [−]d.dde ± dd
Exponentenschreibweise: [−]d.ddE ± dd
wie e, f das jeweils kürzere
Analog zu E
Ausgabe bis ’\0’
Abbildung 3.3: Ausgabe von Gleitkommazahlen und Strings
bei Gleitkommazahlen und Strings eine Feldbreite angegeben werden. Nach der Feldbreite bei Gleitkommazahlen kann ein Punkt und dann die Anzahl Nachkommastellen folgen. Die Anzahl Nachkommastellen
und der Dezimalpunkt sind Teil der Feldbreite! Bei Genauigkeit „0“ wird kein Dezimalpunkt ausgegeben.
Sehen wir uns auch hierfür ein Beispiel an:
/*formio13.c Summe von drei
Status: incomplete*/
#include <stdio.h>
int main()
{
float a,b,c;
printf("\nBitte geben Sie
scanf("%f",&a);
printf("\nBitte geben Sie
scanf("%f",&b);
printf("\nBitte geben Sie
scanf("%f",&c);
Gleitkommazahlen,
den ersten Summanden ein:");
den zweiten Summanden ein:");
den dritten Summanden ein:");
3.4. EINGABE
27
}
FT
printf("\n\t%10f",a);
printf("\n\t%10f",b);
printf("\n\t%10f",c);
printf("\n\t%10s","----------");
printf("\n\t%10f",a+b+c);
return 0;
Dieses Programm unterscheidet sich bis auf den Datentyp float, die Typangabe f für scanf bzw.
printf und die Feldbreite 10 bei der Ausgabe nicht von dem früheren Programm formio11.c. Es
liefert die Ausgabe:
Bitte geben Sie den ersten Summanden ein:1.1
Bitte geben Sie den zweiten Summanden ein:10.10
Bitte geben Sie den dritten Summanden ein:100.100
DR
A
1.100000
10.100000
100.099998
---------111.299999
Das sollte fast alles für Sie nachvollziehbar sein. Aber warum steht in der Ausgabe 100.099998, wo ich
doch 100.1 eingegeben habe? Das ist etwas, auf dass Sie bei einem Rechner immer gefasst sein müssen.
Rechner stellen Gleitkommazahlen immer nur näherungsweise dar. Sie haben also immer Rundungsfehler. Bei wichtigen Rechnungen müssen Sie sich überlegen, ob das Ergebnis hinreichend genau ist, um
ihm „trauen“ zu können. Aber das ist ein Thema der Mathematik. Ich werde es in diesem Kurs nur dann
behandeln, wenn wir zu Themen kommen, bei denen Rundungsfehler eine Rolle spielen.
3.4 Eingabe
Auch bei der Eingabe können wir noch etwas lernen. Ersetzen Sie doch mal in dem Programm formio13.c
in der ersten „richtigen“ Codezeile float durch double. Vielleicht erreichen wir mit einer höheren Rechengenauigkeit einen Wert der näher an 100.100 liegt. Die Ausgabe ist dann:
Bitte geben Sie den ersten Summanden ein:1.1
Bitte geben Sie den zweiten Summanden ein:10.10
Bitte geben Sie den dritten Summanden ein:100.100
0.000000
0.000000
-32012345804664886000000000000000000000000000000000
0000000000000000000000000000000000000000.000000
----------32012345804664886000000000000000000000000000000000
0000000000000000000000000000000000000000.000000
Lediglich aus satztechnischen Gründen habe ich einige Nullen entfernt, aber das Ergebnis hat natürlich
nichts mit dem zu tun was erreicht werden sollte. Die Erklärung hierfür liegt in dere Funktion scanf. Der
müssen wir durch ein geeignetes Zeichen den Typ double angeben. Dies ist sozusagen „long float“ und
deshalb müssen wir statt %f hier %lf bei scanf angeben. Das neue Programm sieht dann so aus:
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
28
FT
/*formio15.c Summe von drei double Gleitkommazahlen,
Status: incomplete*/
#include <stdio.h>
int main()
{
double a,b,c;
printf("\nBitte geben Sie den ersten Summanden ein:");
scanf("%lf",&a);
printf("\nBitte geben Sie den zweiten Summanden ein:");
scanf("%lf",&b);
printf("\nBitte geben Sie den dritten Summanden ein:");
scanf("%lf",&c);
printf("\n\t%10f",a);
printf("\n\t%10f",b);
printf("\n\t%10f",c);
printf("\n\t%10s","----------");
printf("\n\t%10f",a+b+c);
return 0;
}
Das Ergebnis sieht nun schon viel besser aus:
DR
A
Bitte geben Sie den ersten Summanden ein:1.1
Bitte geben Sie den zweiten Summanden ein:10.10
Bitte geben Sie den dritten Summanden ein:100.100
1.100000
10.100000
100.100000
---------111.300000
Sehen wir uns aus diesem Anlass die Gleitkommazahlen etwas genauer an. Die Abb. 3.4 zeigt den grund-
Bitposition
v
63
Exponent
62
53
Mantisse
52
0
Abbildung 3.4: Datentyp double nach IEEE (64 Bit)
sätzlichen Aufbau einer Gleitkommazahl. Der Wert g einer Gleitkommazahl ergibt sich dann so:
g = v · M antisse · 2Exponent
In float.h finden Sie die Längen aller dieser Dinge in Ihrer Installation. Die Abb. 3.5 zeigt typische
Eigenschaften von Gleitkommazahlen. Das erklärt nun auch den Effekt mit 100.099998 bei unserem ersten
Versuch, Gleitkommazahlen von der Tastatur einzulesen (Programm formio13.c). Es ist ja
100.100 =
≈
=
1.5640625 · 26
1.564062 · 26
100.099968
Wie bereits bei den ganzen Zahlen sind die Werte in Abb. 3.5 die, die der Standard garantiert. Die Werte
Ihrer Installation finden Sie in float.h. Die Zahlen in Abb. 3.5 ergeben sich z. B. so: Auf einem 32 Bit
3.4. EINGABE
float
double
long double
Speicherplatz
Wertbereich
dezimal, ohne
Vorzeichen
kleinstmöglich größmöglich
1.2 · 10−38
3.4 · 1038
−308
2.3 · 10
1.7 · 10308
−4932
3.4 · 10
1.1 · 104932
Genauigkeit
dezimal
FT
Typ
29
4 Bytes
8 Bytes
10 Bytes
6 Stellen
15 Stellen
19 Stellen
Abbildung 3.5: Datentypen für Gleitkommazahlen
Rechner belegt eine Variable vom Typ double 64 Bit. Eines für das Vorzeichen, 10 für den Exponenten
und 53 für die Mantisse
10
22
= 21024
≈ 10308
Mit dem Operator sizeof können Sie jederzeit feststellen, wie lang Variablen bzw. Typen sind, z. B.
sizeof(short). Sehen wir uns nun einmal an, wie scanf genauer arbeitet. Auch diese Funktion kann
mehrere Zeichen im Formattierungsstring haben. Ein Beispiel:
DR
A
/*formio16.c Eingabe mehrerer Elemente in einer Zeile
Status: incomplete*/
#include <stdio.h>
int main()
{
char
c;
int
i;
float f;
double d;
printf("\nGeben Sie bitte c i f d ein:");
scanf("%c %d %f %lf",&c, &i, &f, &d);
printf("\nc = %c",c);
printf("\ni = %d",i);
printf("\nf = %12.10f",f);
printf("\nd = %12.10f",d);
return 0;
}
Dieses Programm liefert z. B. Folgendes:
Geben Sie bitte c i f d ein:A 8 8.8 8.888888888
c
i
f
d
=
=
=
=
A
8
8.8000001907
8.8888888880
Wie schon üblich hier wieder die Erläuterungen dazu.
1. In den ersten vier Zeilen im Block der main-Funktion werden vier Variablen deklariert, die später
mit Inhalt gefüllt werden sollen. Das hätte man auch in einer Zeile machen können. Es ist aber in
C++ und Java üblich, nur eine Variable pro Zeile zu deklarieren oder zumindest nicht Variablen
verschiedener Typen. Außerdem haben Sie im Ausdruck so mehr Platz für eigene Notizen.
2. In der nächsten Zeile werden Sie zur Eingabe von c i f d aufgefordert. Nehmen Sie das wörtlich, so
erhalten Sie z. B. folgende Ausgabe:
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
30
Geben Sie bitte c i f d ein:c i f d
=
=
=
=
c
1245120
0.0000000000
-66350771293251242000000000000000000000000000000000.0000000000
FT
c
i
f
d
Sie sehen: Man muss einem Benutzer (neudeutsch: User) schon genau sagen, was er machen soll.
Rechnen Sie immer mit dem DAU, dem Dümmsten Anzunehmenden User. Das sonderbare Ergebnis
werde ich Ihnen im Abschn. 3.6 erläutern.
3. Anschließend wird scanf aufgerufen, um die vier Elemente einzulesen. scanf überliest die Zwischenraumzeichen (Leer-, Tabulator oder Newline-Zeichen). Ein Eingabefeld endet mit dem ersten
Zwischenraumzeichen oder dem ersten Zeichen, das nicht mehr verarbeitet werden kann. Das ist
z. B. der Fall wenn eine numerische Variable eingelesen werden soll und ein Buchstabe direkt auf
die Ziffern folgt.
DR
A
4. Bei der Ausgabe der Gleitkommazahlen sehen Sie eine Anwendung der Angaben für Feldbreite
und Genauigkeit. Die Zahl vor dem Punkt gibt die Gesamtlänge an, die Zahl nach dem Punkt die
Genauigkeit, d. h. bei numerischen Elementen die Anzahl Nachkommastellen, bei Strings die Anzahl
Stellen des Strings, die maximal ausgegeben wird.
8 .
←−
8
8
8
8
8
8
8
12
8
8
0
−→
Der Dezimalpunkt wird dabei mitgezählt.
Was passiert aber nun, wenn eine zu lange Eingabe getätigt wird? Hier ein Beispiel:
/*formio17.c Fehlerhafte Eingabe bei scanf
Status: incomplete*/
#include <stdio.h>
int main()
{
long konto, blz;
printf("\nGeben Sie bitte die Kontonummer ein:");
scanf("%7ld",&konto);
printf("\nGeben Sie bitte die Bankleitzahl ein:");
scanf("%8ld",&blz);
printf("\nKonto = \t%8ld",konto);
printf("\nBLZ = \t%8ld",blz);
return 0;
}
Geben Sie bitte die Kontonummer ein:12345678
Geben Sie bitte die Bankleitzahl ein:
Konto =
1234567
BLZ =
8
Was ist hier schiefgelaufen?
1. Zunächsteinmal habe ich eine zu lange Kontonummer eingegeben: 8-stellig, während im Programm
eine 7-stellige erwartet wird, in scanf steht ja "%7ld".
3.4. EINGABE
31
FT
2. scanf verarbeitet also die ersten sieben Zeichen der Eingabe. Anschließend steht aber noch das
achte Zeichen, hier eine 8 im Eingabepuffer zur Verfügung. Dies wird nun von scanf gleich mitverarbeitet, so dass wir gar keine Chance mehr haben eine Bankleitzahl (BLZ) einzugeben. In der
Variablen blz steht nun als 8.
Wie löst man dieses Problem? Dazu gibt es die Funktion fflush. Diese Funktion erwartet als Parameter
einen Zeiger (siehe Kap. 9) auf eine Datei, in diesem Fall auf die Standardeingabe stdin. Diese Funktion
leert den Eingabepuffer. Fügen wir einen Aufruf dieser Funktion nach jedem Aufruf von scanf ein, so
haben wir zumindest diese eine Fehlermöglichkeit erfolgreich beseitigt.
DR
A
/*formio18.c Eingabe bei scanf und fflush
Status: incomplete*/
#include <stdio.h>
int main()
{
long konto, blz;
printf("\nGeben Sie bitte die Kontonummer ein:");
scanf("%7ld",&konto);
fflush(stdin);
printf("\nGeben Sie bitte die Bankleitzahl ein:");
scanf("%8ld",&blz);
printf("\nKonto = \t%8ld",konto);
printf("\nBLZ = \t%8ld",blz);
return 0;
}
Geben Sie bitte die Kontonummer
ein:12345678
Geben Sie bitte die Bankleitzahl ein:20070024
Konto =
1234567
BLZ =
20070024
Auf den Aufruf von fflush nach dem zweiten scanf habe ich verzichtet, da keine weitere EIngabe
beabsichtigt ist. Das Ergebnis kann natürlich noch nicht ganz befriedigen. Der Benutzer sollte bei derartigen Fehleingaben eine Nachricht bekommen und das Programm nicht einfach „eigenmächtig“ die Eingabe
abschneiden. Aber hier geht es uns ja zunächst um die Grundlagen der Syntax von C.
Wie können wir nun überprüfen, ob wir alle Felder gelesen haben? scanf liefert als Rückgabewert die
Anzahl eingelesener Felder oder den Wert EOF. Dieser Wert wird auch zurückgeliefert, wenn ein Dateiende
durch Drücken von Ctrl-Z (DOS) oder Ctrl-D (UNIX). Wie sich das auswirkt, zeigt das folgende
Beispiel:
/*formio19.c Rückgabewert von scanf und Ctrl z
Status: incomplete*/
#include <stdio.h>
int main()
{
long konto;
long rueckgabe;
printf("\nGeben Sie bitte die Kontonummer ein:");
rueckgabe = scanf("%7ld",&konto);
printf("Der Rueckgabewert von scanf ist: %ld",rueckgabe);
printf("\nKonto = \t%8ld",konto);
return 0;
}
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
32
FT
Geben Sie bitte die Kontonummer ein:^Z
Der Rueckgabewert von scanf ist: -1
Konto =
29657673
^Z steht dabei für „Ctrl-Z“. In der Kontonummer steht nun „Müll“, aber Sie könnten das erkennen, indem
Sie den Rückgabewert von scanf abfragen. Die Angabe einer Genauigkeit der Eingabe ist bei scanf
nicht möglich.
3.5 Datentypenkonvertierung
Variablen können in C mit großer Freiheit von einem Datentyp in einen anderen konvertiert werden. Das ist
mal notwendig, mal nützlich, aber nicht unbedingt notwendig und manchmal sehr gefährlich. Sehen wir uns
dochmal an, was wir bisher kennen. So haben wir im Zusammenhang mit dem Programm formio16.c
auf Seite 29 bei einer Fehleingabe ein zunächst unverständliches Ergebnis erhalten. Dies sehen wir uns
zunächst näher an.
1. Das erste Zeichen ist typgerecht ein Zeichen und wird korrekt eingelesen.
DR
A
2. Geben wir uns wie in formio19 vorgeführt den Rückgabewert von scanf aus, so sehen wir,
dass 1 Eingabefeld gelesen wurde. Die anderen drei Felder wurden also gar nicht eingelesen. In den
Variablen steht also irgenwelcher Müll, der zufällig gerade an den Speicherplätzen dieser Variablen
stand. Sie sollten daraus lernen, dass es sehr sinnvoll ist, Variablen nur dann ohne Definition zu
deklarieren, wenn man sich ganz sicher ist, dass sie vor der ersten Verwendung noch einen sinnvollen
Wert zugewiesen bekommen.
Hier hat also keine Konvertierung stattgefunden. scanf hat einfach eingelesen oder nicht. Wenn eingelesen wurde, stand auch ein sinnvolles Ergebnis in den Variablen. Etwas anders sieht es aus, wenn man
mit Variablen in einem Programm hantiert. Beispiel
/******************************************
datatypes04.c Konvertierungen int2char etc.
Status: ok
*******************************************/
int main(void)
{
char ca = ’a’;
int ia = 88;
char cb = ia;
int ib = ca;
printf("\nVar. Zeichen\tDez.\tHex");
printf("\nca = \t%c\t%d\t%x",ca,ca,ca);
printf("\nia = \t%c\t%d\t%X",ia,ia,ia);
printf("\ncb = \t%c\t%d\t%X",cb,cb,cb);
printf("\nib = \t%c\t%d\t%x",ib,ib,ib);
return 0;
}
Var. Zeichen
ca =
a
ia =
X
cb =
X
ib =
a
Dez.
97
88
88
97
Hex
61
58
58
61
In diesem Programm werden einfach je zwei int und zwei char Variablen deklariert, je einer ein Wert
zugewiesen und dann „über Kreuz“ zugewiesen. Der Compiler nimmt das ohne Warnung hin. Verwendet
man aber den Schalter /Wall, so bringt er eine korrekte Warnung für Zeile 10.
3.6. AUFGABEN
33
FT
warning C4242: ’Initialisierung’: Konvertierung von
’char’, möglicher Datenverlust
und ich erläutere Ihnen jetzt die Ausgabe:
1. In der ersten Zeile sehen Sie den Inhalt der Variablen ca als Zeichen und in Dezimal- bzw. Hexadezimaler Darstellung. Obwohl es ein Zeichen ist, wird es ohne Probleme als Zahl ausgegeben. Das ist
die ASCII-Codierung des Zeichens.
2. Umgekehrt wird die Variable ia, der der Wert 88 zugewiesen wurde ohne Probleme als Zeichen
interpretiert. Wir sehen: Dezimal 88 ist der ASCII-Code für den Buchstaben „X“.
3. Auch durch die Zuweisungen einer int Variablen zu einer char bleibt alles ok.
Generell gilt: Weisen Sie einer int Variablen eine char Variable zu und anschließend letzterer wieder
die ganzzahlige, so geht keine Information verloren. Anders herum kann aber Information verloren gehen,
siehe hierzu Aufgabe 2.
Durch den Cast-Operator erzwingen Sie eine Datentypumwandlung:
typ1 variable1;
typ2 variable2;
variable1 = (typ1) variable2;
DR
A
Wozu das brauchen, werden Sie später lernen. Hier nur noch der kleine Hinweis, dass dieser Programmausschnitt eine Umwandlungswarnung bringen würde, egal welche Typen ich für typ1 oder typ2 einsetze
und ob die Datentypumwandlung zulässig ist: Ich verwende die Variable variable2 vor einer Initialisierung.
3.6 Aufgaben
1. Führen Sie das Programm formio11 aus und geben Sie für einen oder mehrere Summanden ungültige Werte ein.
2. Probieren Sie aus, was passiert, wenn Sie im Programm datatypes04.c der Variablen ia statt
88 die Zahl 888 zuweisen und Erklären Sie, was passiert!
3. Schreiben Sie ein Programm, dass folgendes tut:
• Es gibt die Zahl 0.123456 als Gleitpunktzahl linksbündig mit Feldbreite 15 aus.
• Es gibt die Zahl 123.456 in exponentieller Schreibweise rechtsbündig mit Feldbreite 10 ausgegeben werden.
• Die Zahl 12.345678 soll auf zwei Stellen hinter dem Dezimalpunkt gerundet rechtsbündig mit
der Feldbreite 12 ausgegeben werden.
4. Schreiben Sie ein C-Programm, dass eine dreistellige ganze Zahl von der Tastatur einliest und diese
als Zeichen, dezimal, oktal und hexadezimal ausgibt.
5. Schreiben Sie ein C-Programm, dass eine achtstellige Artikelnummer, eine Stückzahl und einen
Stückpreis von der Tastatur einliest und diese Werte ergänzt um den Gesamtpreis (Stückzahl * Stückpreis) auf dem Bildschirm ausgibt.
6. In dieser Aufgabe sollen Sie lernen, welcher Speicherplatz für Variablen reserviert wird. Mit dem
sizeof-Operator kann die Anzahl der Bytes ermittelt werden, die Variablen eines bestimmten Datentyps im Hauptspeicher belegen. Beispiel: sizeof(short) hat den Wert 2. Sie können bei
sizeof aber auch einen Variablennamen oder eine Konstante angeben.
Schreiben Sie bitte ein C-Programm, das für jeden elementaren Datentyp mittels des sizeofOperators die Größe des benötigten Speicherplatzes auf dem Bildschirm ausgibt.
KAPITEL 3. FORMATTIERTE EIN- UND AUSGABE
DR
A
FT
34
Operatoren
4.1 Übersicht
FT
Kapitel 4
DR
A
In diesem Kapitel lernen Sie die grundlegenden Operatoren für Rechenoperationen, Vergleiche, etc. kennen, die Sie brauchen, um in Kap. 5 den Programmablauf zu steuern. Diese Operatoren realisieren die aus
der Schulmathematik bekannten elementaren Rechenoperationen oder logische Operationen ebenso, wie
die eher C-spezifischen Prä- und Postfix-Inkrementoperationen. In C sind darüberhinaus zusammengesetzte Zuweisungen beliebt. Außerdem gibt es in C++ Operatoren für die Manipulation auf Bit-Ebene. Da dies
für Technik-Studierende interessant sein kann, präsentiere ich Sie bereits in diesem Kapitel.
Zum Abschluss dieser Übersicht noch eine Warnung: Ich weigere mich, die Vorrangfolge von Operatoren in irgendeinem System auswendig zu lernen. Im Zweifel kann man immer Klammern setzen und
meistens ist die Aussage dann auch besser verständlich. Denken Sie also bitte auch — aber nicht nicht nur
— bei der Lösung von Aufgaben in Tests, Praktikum und Klausur darauf sorgfältig Klammern zu setzen.
Lösungen, die auf die Standardreihenfolge der Auswertung setzen und in denen keine Klammern gesetzt
werden, werden als falsch bewertet werden!
4.2 Lernziele
• Elementare Rechenoperationen mit numerischen Datentypen in C sicher durchführen können.
• Den C-Stil der Verwendung von Inkrement-Operatoren und zusammengesetzten Zuweisungen kennen.
• Vergleichsoperationen sicher anwenden können.
• Logische Operatoren sicher verwenden können.
• Operationen für Bit-Manipulation kennen.
4.3 Rechenoperatoren
Die elementaren Rechenoperationen in Abb. 4.1 sollten unmittelbar verständlich sein. Schließlich kennen
Sie die meisten aus der Schule Wenn nicht: Fragen Sie bitte!
Ungewohnt könnte der Stern (*) als Zeichen für die Multiplikation sein, wo in handschriftlichen Rechnungen eher ·, × verwendet oder die Zahlen oder Variablen einfach nebeneinander geschrieben werden.
Unbekannt könnte die Moduldivision % sein. a%b liefert den Rest, der bleibt, wenn a durch b dividiert
wird. Deshalb hierzu ein einfaches Beispielprogramm.
/*op01.c Beispielprogramm zur Moduldivision
Status: incomplete*/
35
KAPITEL 4. OPERATOREN
36
Bedeutung
Addition
Subtraktion
Multiplikation
Division
Modulodivision
Mathematisches Äquivalent
a+b
a−b
a·b
a/b
Rest von a/b
FT
Operator
+
*
/
%
Abbildung 4.1: Binäre arithmetische Operatoren
#include <stdio.h>
int main()
{
int a, b;
printf("Geben Sie bitte zwei Ganze Zahlen durch ein oder mehrere\
Leerzeichen getrennt, ein:");
scanf("%d%d",&a,&b);
printf("%d = %d mod %d",a,b,a%b);
return 0;
}
DR
A
Wie das Ergebnis aussieht, probieren Sie bitte selber aus! Damit haben wir die binären arithmetischen
Operatoren fast vollständig behandelt. Es bleibt nur noch zu klären, welchen Typ das Ergebnis hat, wenn
Elemente mit unterschiedlichen Typen verknüpft werden. Werden Operanden unterschiedlichen Typs verknüpft, so wird der jeweils „kleinere“ Oeprator in den größeren Typ konvertiert. Es gelten für diese impliziten Typkonvertierungen in C also die in der Tabelle in Abb. 4.2 dargestellten Konvertierungsregeln.
op1
long double
numerisch≤long double
float
numerisch≤ float
unsigned long
numerisch ≤ unsigned long
long
numerisch ≤ long
unsigned int
numerisch ≤ int
op2
numerisch≤long double
long double
numerisch≤ float
float
≤unsigned long
unsigned long
numerisch ≤ long
long
numerisch ≤ int
unsigned int
Umwandlung in
long double
long double
float
float
unsigned long
unsigned long
long
long
unsigned int
unsigned int
Abbildung 4.2: Typkonvertierungsregeln für binäre arithmetische Operatoren
Sie sehen daraus: Der „größere“ Datentyp gewinnt. Dies sind hoffentlich alle Fälle, bis auf den in dem geprüft werden muss, ob long int alles darstellen kann, was unsigned int kann. Für die Einzelheiten
siehe die in [KR88], S. 198, zusammengestellten Regeln. Explizite Typkonvertierungen sind ganz knapp
in Abschn. 3.5 vorgestellt worden. Alle Konvertierungen, die zu Informationsverlusten führen sollten zu
Warnungen des Compilers führen. Dies ist aber nicht gewährleistet. Es hängt davon ab, welches Niveau Sie
für Warnungen einstellen. Mit dem Befehl cl /? erhalten Sie eine Liste der Compiler-Optionen. EInige
davon sind in Abschn. F.1 beschrieben. Nun wenden wir uns den unären arithmetischen Operatoren zu,
die sie bisher wahrscheinlich noch nicht alle kennen. Die Tabelle in Abb. 4.3 zeigt die Grundzüge, lässt
aber einen wichtigen Unterschied zwischen den Präfix- un Postfix-Inkrementoperatoren nicht erkennen.
Lesen Sie also auch den übernächsten Absatz! Es gibt drei unäre Operatoren in C. De Vorzeichen Operator
(−/+) kennen Sie wahrscheinlich zumindest von Ihrem Taschenrechner. Er + liefert lediglich den Wert
des Operanden. Der Vorzeichenoperator - erhält den Wert des Operanden und ändert das Vorzeichen.
Interessanter sind da schon die Inkrement- und Dekrement-Operatoren. Von diesen gibt es jeweils eine
Präfix- und eine Postfix-Variante. Steht der Operator vor der Variablen (Präfix) so wird er auf die Variable
4.3. RECHENOPERATOREN
Bedeutung
Vorzeichenoperator
Inkrement-Operator
Dekrement-Operator
Mathematisches Äquivalent
+a, -a
++a, a++ (a =a + 1)
--a, a-- (a = a -1)
FT
Operator
+, ++
--
37
Abbildung 4.3: Unäre arithmetische Operatoren
angewandt und anschließend wird die veränderte Variable im jeweilige Ausdruck verwendet. Steht der
Operator hinter der Variablen (Postfix) so wird die Variable zunächst mit ihrem alten Wert im jeweiligen
Ausdruck verwendet und anschließend der Operator verwendet.
Zunächst ein ein ganz einfaches Beispiel hierzu:
DR
A
/*op02.c Beispielprogramm zu Inkrementoperatoren
Status: incomplete*/
#include <stdio.h>
int main()
{
int a = 8;
int b = 88;
printf("\na++ = %d",a++);
printf("\na = %d",a);
printf("\n++b = %d",++b);
printf("\nb = %d",b);
return 0;
}
a++ = 8
a = 9
++b = 89
b = 89
Spätestens jetzt sollten Sie die Wirkung nachvollziehen können: In der ersten „printf“-Zeile wird a++
ausgegeben. Dies Ausgabe lautet, wie nach Definition zu erwarten, 8, da erst der Ausdruck ausgewertet
und dann der Postfix-Inkrementoperator angewendet wird. In der nächsten Zeile, also nach Auswertung
des Ausdrucks, hat er aber den um eins erhöhten Wert 9. Die Anwendung des Präfix-Inkrementoperators
führt aber gleich zur Ausgabe von 89, da dieser vor der Ausführung des Ausdrucks ausgewertet wird.
Sehen wir uns das an einem weiteren Beispiel aus [KR88] an: Die Funktion squeeze(s,c) entfernt alle
Exemplare des Zeichens c aus dem String s.
/*squeeze delete all c from a */
void squeeze(char *s, int c)
{
int i, j;
for(i = j = 0; s[i] != ’\0’;i++)
if(s[i] != c)
s[j++]= s[i];
s[j] = ’\0’;
}
Immer, wenn ein nicht-c Zeichen vorkommt, wird es an die j-te Stelle kopiert und erst anschließend wird j
um eins erhöht. Dieser Teil ist also äquivalent zu folgendem Code:
if(s[i]!= c)
{
s[j] = s[i];
KAPITEL 4. OPERATOREN
38
j++;
FT
}
Die erstere Variante entspricht dem in C (und C++) üblichen Stil. Hier zur Illustration eine einfache Anwendung:
DR
A
/*op03.c Einfaches Beispiel Postfix-Inkrement
Status: incomplete*/
#include <stdio.h>
/*squeeze delete all c from a */
void squeeze(char *s, int c)
{
int i, j;
for(i = j = 0; s[i] != ’\0’;i++)
if(s[i] != c)
s[j++]= s[i];
s[j] = ’\0’;
}
int main()
{
char* s;
char c;
printf("\nGeben Sie bitte einen String ein:");
scanf("%s",s);
fflush(stdin);
printf("\nGeben Sie bitte ein Zeichen ein:");
scanf("%c",&c);
squeeze(s,c);
printf("\n%s",s);
return 0;
}
Dieses Programm wenden wir nun auf ein bekanntes Palindrom an:
Geben Sie bitte einen String ein:EINNEGERMITGAZELLEZAGTIMREGENNIE
Geben Sie bitte ein Zeichen ein:E
INNGRMITGAZLLZAGTIMRGNNI
Soviel zu den arithmetischen Operatoren und einer ihrer häufigsten Anwendungen, in Schleifen, loops. Die
Einzelheiten der verschiedenen Schleifenkonstruktionen in C werden in Kap. 5 besprochen.
4.4 Zusammengesetzte Zuweisungen
Außer der bereits in den ersten Kapiteln eingeführten Zuweisung, z. B. int a = 1, werden in C gerne
zusammengesetzte Zuweisungen verwendet. Einige davon und ihre „einfachen“ äquivalenten Formulierungen habe ich in Abb. 4.4 zusammengestellt. Das spart Leerzeichen und die Wiederholung des Variablennamens. Studien darüber, ob die geringere Anzahl Zeichen, es ProgrammiererInnen ermöglicht, den Code
schneller zu verstehen sind mir nicht bekannt. Nach allen meinen Beobachtungen ist es dem Compiler egal,
ob sie einfache Zuweisungen und binäre arithmetische Operationen verwenden oder die zusammengesetzte
Form. Der erzeugte Code ist der gleiche. Derartige zusammengesetzte Zuweisungen kann man mit jedem
binären arithmetischen Operator und auch den Bitoperatoren bilden. Es gibt also +=, -=, *=, /=,
%=.
Achten Sie bei den zusammengesetzten Zuweisungen bitte drauf, dass hier implizit Klammern gesetzt
werden, wie Sie es in den Zeilen 3 und 4 der Abb. 4.4 auf Seite 39 sehen.
4.5. VERGLEICHOPERATOREN
39
Äquivalente
Formulierung
i=i+3
i=i-5
i = i * (j + 2)
i = i / (k - 2)
FT
Zusammengesetze
Zuweisung
i+=3
i-=5
i*= j + 2
i/= k-2
Abbildung 4.4: Zusammengesetzte Zuweisungen
4.5 Vergleichoperatoren
In diesem Abschnitt erhalten Sie eine Übersicht über die Vergleichsoperatoren. Um diese halbwegs sinnvoll erläutern zu können, benötige ich bereits eine Kontrollstruktur if - then - else, die erst im
nächsten Kapitel systematisch eingeführt werden wird. Ich hoffe, sie ist auch so verständlich.
Abbildung 4.6 enthält eine Tabelle der (binären) Vergleichsoperatoren.
Bedeutung
kleiner
kleiner gleich
größer
größer gleich
gleich
ungleich
DR
A
Operator
<
<=
>
>=
==
!=
Abbildung 4.5: Vergleichsoperatoren
Das folgende Programm zeigt einige mehr oder weniger abstruse Anwendungsbeispiele. Es läuft in
einer Endlosschleife, bis es durch Ctrl-c abgebrochen wird.
/*op04.c Beispielprogramm zu Vergleichsoperatoren
Status: incomplete*/
#include <stdio.h>
int main()
{
int a;
int b;
srand(6);
for(;;)
{
b = rand();
printf("\nGeben Sie bitte eine ganze Zahl ein: ");
scanf("%d",&a);
if (a == b)
printf("\nTreffer");
else
if( a <= b)
printf("\nTiefflieger");
else
printf("\nUeberflieger");
}
return 0;
}
Versuchen Sie mal einen Treffer zu landen und erklären Sie, warum das so schwierig ist!.
KAPITEL 4. OPERATOREN
40
4.6 Logische Operatoren
FT
Aussagenlogik sollte einfach sein. Aber in vielen Sprachen weicht man von den mathematischen oder
logischen Regeln gravierend ab. So wird im Spanischen oft eine doppelte Negation verwendet, die dann als
Verstärkung gedacht ist. Das geht schon soweit, das eine einfache Negation gar nicht mehr wahrgenommen
wird. In C ist das eigentlich ganz einfach. Für mich als Mathematiker ist nur unverständlich, warum es keine
booleschen Variablen gibt und man immer entsprechend der Tabelle in Abb. 4.8 auf Seite 40 umdenken
muss. Mit diesen Operatoren können Sie verschiedene Aussagen verknüpfen. Achten Sie bitte darauf, dass
Operator
&&
||
!
Bedeutung
und
oder
nicht, Negation
Abbildung 4.6: Logische Operatoren
„oder“ etwas anderes ist, als „entweder oder“. Genaueres entnehmen Sie bitte der Tabelle in Abb. 4.7. In
y
W
F
W
F
x&&y
W
F
F
F
x||y
W
W
W
F
!x
F
F
W
W
DR
A
x
W
W
F
F
Abbildung 4.7: Wahrheitstafel für logische Operatoren Bool
C ist das im Prinzip genauso einfach. Sie müssen aber Folgendes berücksichtigen: „Wahr“ heißt „ungleich
0“, „Falsch“ heißt „0“. In tabellarischer Form sehen Sie dies in Abb. 4.8 Mit diesen „Ersetzungen“ sieht die
Boolesch
Wahr (true)
Falsch (false)
C
!=0
=0
Abbildung 4.8: Wahrheitswerte Boolesch und in C
Tabelle aus Abb. 4.7 aus, wie in 4.9. Statt 1 könnte dort jeder von 0 verschiedene Wert stehen. Das sollte
x
!=0
!=0
0
0
y
!=0
0
!=0
0
x&&y
1
0
0
0
x||y
1
1
1
0
!x
0
0
1
1
Abbildung 4.9: Wahrheitstafel für logische Operatoren im C-Stil
mit zu den Dingen gehören, die sie genau und ohne Nachschlagen wissen.
Den einzigen unären, logischen oder booleschen Operator in C sehen Sie in der Abb. 4.10.
Operator
!
Bedeutung
nicht
Abbildung 4.10: Unäre boolesche Operatoren
4.7. DER TERNÄRE OPERATOR ?:
41
Der ternäre Operator ?:
FT
4.7 Der ternäre Operator ?:
Ausdruck ? Ausdruck1 : Ausdruck2
heißt Auswahloperator und dient zum kompakten Forumlieren von Entscheidungen und arbeitet wie in
Abb. 4.11 visualisiert. Der Ausdruck wird ausgewertet. Ist er wahr, so wird Ausdruck1 ausgeführt, ist er
H
HH
HH
Ausdruck
HH
HH
wahr
falsch
HH H
Ausdruck2
DR
A
Ausdruck1
Abbildung 4.11: Der Auswahloperator ?:
falsch, so wird Ausdruck2 ausgeführt. Auch hierzu ein ganz einfaches Beispiel:
/*op06.c Einfaches Beispiel Auswahloperator
Status: incomplete*/
#include <stdio.h>
void main(void)
{
float x, y;
printf("\nGeben Sie bitte zwei verschiedene Zahlen ein:");
scanf("%f %f",&x, &y);
x == y ?
printf("Sie sollten doch verschiedene Zahlen eingeben!"):
printf("\nDie groessere Zahl ist: %.2f.",x > y ? x : y);
}
In diesem Programm wird der Auswahloperator gleich zweimal verwendet.
1. Zunächst wird geprüft, ob die beiden Zahlen wirklich verschieden sind. Wurden zwei gleiche Zahlen
eingegeben, so wird eine Fehlermeldung ausgegeben.
2. Waren die Zahlen verschieden, so wird mittels des Auswahloperators die größere der beiden ausgewählt und ausgegeben.
Ein anderes gutes Beispiel ist eine Ausgabe, bei der ein Wort im Singular oder im Plural benötigt wird,
z. B. „Zahl“. Soll der Benutzer eine Zahl eingeben, so möchte man ihm auch sagen „Bitte geben Sie 1 Zahl
ein!“ Soll er mehrere eingeben, z. B. 5, so möchte man ihm sagen „Bitte geben Sie 5 Zahlen ein“. Das geht
ganz einfach so:
/*op07.c Einfaches Beispiel Auswahloperator
Status: incomplete*/
#include <stdio.h>
KAPITEL 4. OPERATOREN
42
FT
void main(void)
{
int i;
printf("\nGeben Sie bitte eine positive ganze Zahl ein:");
scanf("%d",&i);
printf("\nGeben Sie bitte %d Zahl%s ein!",i,i==1? "":"en");
}
Gibt man zunächst 1 ein, so lautet die Ausgabe:
Geben Sie bitte 1 Zahl ein
Gibt man eine Zahl > 1 ein so lautet die Ausgabe z. B. :
Geben Sie bitte 1 Zahl ein
4.8 Referenzieren und Dereferenzieren
DR
A
Bereits in Kap. 2 habe ich Ihnen & und * vorgestellt. Auch das sind unäre Operatoren. Beide werden
auf Variablen oder Konstanten angewandt. & liefert in jedem Fall die Adresse, an der das Element im
Hauptspeicher abgelegt ist. Den Adressoperator & nennt man manchmal auch Referenzierungsoperator, da
er zu einer Variablen eine Referenz auf diese bzw. ihren Speicherplatz liefert. Umgekehrt nennt man den
Operator * auch Indirektions- oder Dereferenzierungsoperator. Wir werden uns mit beiden ausführlich in
den Kap. 6 und 9 beschäftigen.
4.9 Bit-Manipulationen
C kennt vier logische Bit-Operatoren, zwei Shift-Operatoren und Bit-Masken. Abbildung 4.12 Die folgenOperator
&
|
^
˜
<<
>>
Bedeutung
und
oder
entweder oder
nicht (Einer-Komplement)
Links-Shift
Rechts-Shift
Abbildung 4.12: Bit-Operatoren
den vier Tabellen in Abb. 4.13 zeigen die Wirkungsweise der logischen Bit-Operatoren. Die Operanden der
und
0&0
0&1
1&0
1&1
Ergebnis
0
0
0
1
oder
0|0
0|1
1|0
1|1
Ergebnis
0
1
1
1
entw. oder
0^0
0^1
1^0
1^1
Ergebnis
0
1
1
0
nicht
˜0
˜1
Ergebnis
1
0
Abbildung 4.13: Wirkungsweise der Bit-Operatoren
Bit-Operatoren müssen einen ganzzahligen Datentyp haben.
Das folgende Beispiel aus [KPP96] illustriert die Arbeitsweise dieser Operatoren. Sie werden entsprechend der Tabellen in Abb. 4.13 auf jedes einzelne Bit der Darstellung angewandt.
Die Shift-Operatoren verschieben die Bits um die angegebene Anzahl nach links bzw. rechts. Beim
Links-Shift << werden immer 0-Bits nachgeschoben. Beim Rechts-Shift hängt es vom Compiler ab, ob bei
4.9. BIT-MANIPULATIONEN
43
FT
Werten mit Vorzeichen mit 0 oder dem Vorzeichenbit (1) aufgefüllt wird. Bei Feldern ohne Vorzeichen,
wird in jedem Fall auch beim Rechts-Shift mit 0 aufgefüllt. Von daher ist man bei den Bit-Operatoren nur
bei der Anwendung auf vorzeichenlose Felder auf der sicheren Seite.
unsigned char a, b, c
a =5;
b = 12;
c = a & b;
c = a | b;
c = a ^ b;
c = ~a;
c = b << 3;
c = b >> 2;
Bitmuster
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
1 1 1
0 1 1
0 0 0
0
0
0
0
0
1
0
0
0
1
0
1
1
1
0
0
1
1
1
1
0
0
0
0
0
0
0
0
0
1
0
1
1
0
0
1
1
0
0
1
Spaßeshalber geben wir die Werte auch nochmal dezimal und hex auf der Konsole aus:
DR
A
a = 5, 5
b = 12, C
a & b = 4, 4
a | b = 13, D
a ^ b = 9, 9
~a = -6, FFFFFFFA
a << 3 = 96
Wie verwendet man nun diese Operatoren? Fangen wir mit einigen einfachen Dingen an. Was macht, z. B.
das folgende Codefragment?
int a;
a=<<1;
Alle Bits werden um eine Stelle nach links geschoben steht in der Definition. Aber was heißt das nun
konkret? Alle 0- und alle 1-Bits „wandern“ nach links. Wo also vorher eine 1 an Stelle i stand, von rechts,
mit 0 beginnend gezählt, steht sie nun an Stelle i + 1. Vorher stand sie also bei 2i und nun bei 2i+1 .
Solange wir nicht aus dem Zahlenbereich hinauslaufen, ist dass also einfach eine Multiplikation mit 2.
Ganz entsprechend ist ein Rechts-Shift bei vorzeichenlosen Feldern einfach eine Division durch 2.
Man verwendet in diesem Zusammenhang oft sogenannte Bit-Masken. Diese werden meistens oktal
oder definiert. Dazu beachten Sie am einfachsten die Tabelle in Abb. 4.14 Die Liste lässt sich beliebig
Dezimal
0
7
15
255
oktal
0
7
17
377
Hexadezimal
0
7
F
FF
Erläuterung
Alle Bits sind 0
Bits 0–3 des rechtesten Bytes sind 1
Das rechteste Byte hatte alle Bits auf 1
Die rechtesten beiden Bytes haben alle Bits auf 1
Abbildung 4.14: Bit-Masken
fortsetzen. Natürlich werden die besonders einprägsamen Werte am häufigsten verwendet. So kann man
sehr einfach bestimmte Bits in einem Feld ändern, wie die Abb. 4.15 Das Ganze funktioniert entsprechend
Operation
x = x | 077
x = x & ~077
Erläuerung
o’77’=X’3F’, also werden die rechtesten 6 Bit auf 1 gesetzt.
Setzt entsprechend auf 0.
Abbildung 4.15: Bit-Manipulationen, Beispiele
der Tabellen in Abb. 4.13: Ist eines der Bits 1 (wahr) so wird das entsprechende Ergebnisbit auf 1 gesetzt.
In der zweiten Zeile, wo der und-Operator verwendet wird werden entsprechend die Bits auf 0 gesetzt, bei
KAPITEL 4. OPERATOREN
44
4.10 Aufgaben
FT
denen in der Maske auf der rechten Seite des Operators eine 0 steht. Ob man derartige Dinge lieber oktal
oder hexadezimal angibt, ist Geschmackssache, ich neige zu hexadezimaler Angabe. Allerdings macht
mich doch stutzig, dass ich Dateiattribute in Unix manchmal (wenn auch sellten) durch Angabe des oktalen
Werts ändere.
Auch die binären Bit-Operatoren können in zusammengesetzten Zuweisungen verwendet werden, es
gibt also auch &=, |=, ^=, <<= und >>=
1. Finden Sie bitte heraus, ob Ihr Compiler Unterschiede zwischen einer zusammengesetzten Zuweisung im Vergleich zur äqivalenten Form macht! Wie können Sie das herausfinden und Ihre Aussage
belegen?
2. Schreiben Sie bitte eines (oder mehrere) C-Programme, die die Tabellen in den Abb. 4.5–4.9 korrekt
ausgerichtet auf der Konsole ausgibt! Sie können dabei alle Möglichkeiten von C verwenden, die Sie
bisher in dieser Veranstaltung kennen gelernt haben.
3. Erklären Sie, warum es so schwierig ist, in dem Programm op04.ceinen „Treffer“ zu landen! Was
könnte man tun, um es dem „Spieler“ etwas einfacher zu machen?
DR
A
4. Geben Sie bitte an, welchen Typ und welchen Wert das Ergebnis in den folgenden Beispielen hat
und begründen Sie Ihre Ansicht:
4.1. int i = 888;
long l;
l = i + 50;
4.2. long l = 0x654321;
short s;
s = l;
4.3. int i = -2;
unsigned int ui = 2;
i = i*ui;
4.4. double d = -2.345;
int i;
unsigned int ui;
i = d;
ui = d;
4.5. double d = 1.23456789012345;
float f;
f = d;
FT
Kapitel 5
Kontrollstrukturen
5.1 Übersicht
In diesem Kapitel lernen Sie, wie man ein Programm wirklich schreibt. bisher kennen Sie einige Befehle
der Sprache C, die nacheinander abgearbeitet werden. Nun lernen Sie Befehle kennen, für
DR
A
• Schleifen (siehe loop im Glossar).
• Verzweigungen (if-then-else, switch, ?:)
• Sprünge (goto, continue, break)
5.2 Lernziele
• Die grundlegenden Befehle zur Steuerung des Programmablaufs in C beherrschen.
• Schleifen mit for sicher verwenden können.
• while und do while sicher verwenden können.
5.3 Schleifen
Das Beispielprogramm happybirthday1.c aus Abschn. 2.8 muss jedesmal neu aufgerufen werden,
wenn man eine anderes Geburtsdatum eingeben will. Um das zu vermeiden, können wir eine Schleife
einbauen. Am einfachsten zu schreiben ist eine Endlosschleife:
for(;;);
Das braucht man eher selten. Meistens entstehen Endlosschleifen ungeplant durch Programmierfehler. Verwenden wir es aber hier mal einfach:
/******************************************
happybirthday2.c liest in einer Endlosschleife
ein Geburtsdaten über die Tastatur ein und
"errechnet" jeweils eine Glückszahl
Status: incomplete
******************************************/
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
45
KAPITEL 5. KONTROLLSTRUKTUREN
46
FT
int tag;
int monat;
int jahr;
for(;;)
{
printf("\nGeben Sie bitte Ihr Geburtsdatum ein!");
printf("\nTag: ");
scanf("%d", &tag);
printf("\nMonat: ");
scanf("%d", &monat);
printf("\nJahr: ");
scanf("%d", &jahr);
printf("%s%d%s%d%s%d%s","Ist Ihr Geburtsdatum wirklich der: "\
,tag,".",monat,".",jahr,"?");
srand(tag + monat + jahr);
printf("\nDann ist Ihre Glückszahl heute: %d\n",rand());
}
}
DR
A
Beendet werden kann das Programm nun allerdings nur durch Ctrl-C oder eine äquivalente Tastenkombination, was nicht gerade professionell ist. In solchen Fällen verwendet man besser eine Abfrage an den
Benutzer, ob er noch weiter machen möchte oder nicht. Dazu gibt es zwei Möglichkeiten, die while- und
die do while-Schleife. Die Grundprinzipien beider zeigen der linke bzw. der rechte Teil der Abb. 5.1
Bei der while-Schleife wird vor Eintritt in den Schleifenkörper ein Ausdruck ausgewertet und geprüft
Solange Ausdruck wahr ist
Anweisung
Anweisung
Solange Ausdruck wahr ist
Abbildung 5.1: While und do while-Schleife
ob er wahr (d. h. ungleich 0) oder falsch (d. h. gleich 0) ist. Ist er wahr, so werden die Anweisungen im
Schleifenkörper ausgeführt. Anschließend wird dann wieder der Ausdruck ausgewertet usw. Bei der do
while-Schleife wird zunächst der Schleifenkörper ausgeführt und anschließend der Ausdruck überprüft.
Dazu bauen wir eine weitere Variable in das Programm ein: weiter und fragen den Benutzer, ob er
weiter machen möchte oder nicht. In der while Variante sieht das so aus:
/******************************************
happybirthday3.c liest in einer while Schleife
ein Geburtsdatum über die Tastatur ein und
"errechnet" jeweils eine Glückszahl bis der
Benutzer etwas anderes, als J eingibt
Status: incomplete
******************************************/
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
5.3. SCHLEIFEN
47
DR
A
}
FT
int tag;
int monat;
int jahr;
char weiter = ’J’;
while(weiter==’J’)
{
printf("\nGeben Sie bitte Ihr Geburtsdatum ein!");
printf("\nTag: ");
scanf("%d", &tag);
printf("\nMonat: ");
scanf("%d", &monat);
printf("\nJahr: ");
scanf("%d", &jahr);
printf("%s%d%s%d%s%d%s","Ist Ihr Geburtsdatum wirklich der: "\
,tag,".",monat,".",jahr,"?");
srand(tag + monat + jahr);
printf("\nDann ist Ihre Glueckszahl heute: %d\n",rand());
printf("\nMöchten Sie weitere Glueckszahlen berechnen?\
J: Ja, alles andere: Nein. ");
scanf("%s",&weiter);
}
Gegenüber den bisherigen Varianten habe ich hier folgende Veränderungen vorgenommen:
1. Ich habe einen #include <stdlib.h> Präprozessorbefehl ergänzt.
2. Die main-Funktion hat nun den Rückgabewert void und einen vollständigen Prototyp (mit void in
den Klammern). Dadurch entfällt jetzt auch der return-Befehl.
3. Ich habe eine zusätzliche char-Variable, weiter eingebaut und sie mit ’J’ initialisiert.
4. Nach dem Berechnen der Glückszahl wird der Benutzer gefragt, ob er/sie weiter machen möchte.
Anschließend wird die Antwort mit der Funktion scanf in das Feld weiter eingelesen, das ja in
der Schleifenbedingung abgeprüft wird.
Ganz ähnlich sieht es mit der do while Schleife aus:
/******************************************
happybirthday4.c liest in einer do while Schleife
ein Geburtsdatum über die Tastatur ein und
"errechnet" jeweils eine Glückszahl bis der
Benutzer etwas anderes, als J eingibt
Status: incomplete
******************************************/
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int tag;
int monat;
int jahr;
char weiter;
do
{
printf("\nGeben Sie bitte Ihr Geburtsdatum ein!");
KAPITEL 5. KONTROLLSTRUKTUREN
48
}
while(weiter==’J’);
}
FT
printf("\nTag: ");
scanf("%d", &tag);
printf("\nMonat: ");
scanf("%d", &monat);
printf("\nJahr: ");
scanf("%d", &jahr);
printf("%s%d%s%d%s%d%s","Ist Ihr Geburtsdatum wirklich der: "\
,tag,".",monat,".",jahr,"?");
srand(tag + monat + jahr);
printf("\nDann ist Ihre Glueckszahl heute: %d\n",rand());
printf("\nMöchten Sie weitere Glueckszahlen berechnen?\
J: Ja, alles andere: Nein. ");
scanf("%s",&weiter);
DR
A
Beachte Sie bitte, dass die Schleife in diesem Fall auf jeden Fall einmal durchlaufen wird. Ich habe das
zusätzlich dadurch verdeutlicht, dass ich die Variable weiter nicht initialisiert habe. Ich hätte ihr auch den
Wert N zuweisen können, dass würde nichts ändern. Alle diese Programm liefern auch mit der CompilerOption /Wall mit nur einer einzigen Warnung durch, die sich auf eine in stdlib.h stehende Deklaration
bezieht.
Sehen wir uns nun die for Schleife etwas näher an. Den Ablauf in einer for-Schleife
for(Ausdruck1, Ausdruck2, Ausdruck3)
{
Anweisung
}
visualisiert die Abb. 5.2. Das heißt:
Ausdruck1
Solange Ausdruck2 wahr ist
Anweisung
Ausdruck3
Abbildung 5.2: for-Schleife
1. Bei Eintritt in die for-Schleife wird Audruck1 ausgeführt.
2. Ist Ausdruck2 wahr, so werden die Anweisungen im Schleifenkörper ausgeführt und anschließend
Ausdruck3 ausgewertet.
3. Das geht solange weiter, bis Ausdruck2 falsch ist, dann wird die Schleife verlassen.
Dabei „zäume ich das Pferd zur Abwechslung von hinten auf“. In diesem Beispiel könnte man den gleichen
Efekt wie bei der while-Schleife so mit for erreichen. Allerdings werde ich Ihnen hinterher detailliert
erläutern, warum dies eine abschreckende Verwendung der for-Schleife ist!
5.3. SCHLEIFEN
49
DR
A
FT
/******************************************
happybirthday5.c liest in einer for Schleife
ein Geburtsdatum über die Tastatur ein und
"errechnet" jeweils eine Glückszahl bis der
Benutzer etwas anderes, als J eingibt
Status: bad
******************************************/
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int tag;
int monat;
int jahr;
char weiter=’J’;
for(;weiter==’J’;scanf("%s",&weiter))
{
printf("\nGeben Sie bitte Ihr Geburtsdatum ein!");
printf("\nTag: ");
scanf("%d", &tag);
printf("\nMonat: ");
scanf("%d", &monat);
printf("\nJahr: ");
scanf("%d", &jahr);
printf("%s%d%s%d%s%d%s","Ist Ihr Geburtsdatum wirklich der: "\
,tag,".",monat,".",jahr,"?");
srand(tag + monat + jahr);
printf("\nDann ist Ihre Glueckszahl heute: %d\n",rand());
printf("\nMöchten Sie weitere Glueckszahlen berechnen?\
J: Ja, alles andere: Nein. ");
}
}
Der for-Befehl erwartet drei Parameter, die alle fehlen können. Im dritten Parameter steht irgendein gültiger Ausdruck in C, der am Ende der Schleife ausgewertet oder „berechnet“ wird. In diesem Fall habe ich
also die Abfrage zum Weitermachen in der for-Schleife untergebracht.
Vor einem Schleifendurchlauf wird der zweite Parameter ausgewertet. Ausdruck zwei muss eine ganze
Zahl liefern. Liefert er „0“, also „falsch“, siehe Abb. 4.8 auf 40, so wird die for-Schleife beendet, liefert
er !=0, so wird der Schleifenkörper durchlaufen.
Dies ist aber ein Beispiel für ganz schlechte Programmierung, bei der gleich gegen eine ganze Reihe
von Grundprinzipien guten Programmierstils verstoßen wird. Es reicht noch nicht für einen Sieg am International Obfuscated C Code Contest (IOCCC), könnte aber einen ersten Baustein bilden. Was ist also so
schlecht an happybirthday5.c?
1. Es ist eine unübliche Verwendung der for-Schleife, aber dazu unten mehr.
2. Die Frage „Möchten Sie weitere Glueckszahlen berechnen?“ ist von der Eingabe soweit getrennt
wie nur irgend möglich. Diese sollten im Interesse einen hohen Zusammenhalts aber mindestens in
einem Block stehen. Hier sind sie auf den Block des for-Befehl und den Block des Schleifenkörpers
verteilt.
3. Damit wird gleichzeitig eine unnötige Kopplung zwischen den genannten Blöcken eingeführt.
4. Da die Bedingung am Ende des Schleifendurchlaufs abgeprüft wird, handelt es sich eigentlich um
eine do while-Schleife, und die sollte man dann auch verwenden. Sie steht aber am Anfang, also
KAPITEL 5. KONTROLLSTRUKTUREN
50
FT
an einer ganz anderen Stelle. Wollen Sie den Codeverlauf nachvollziehen, so müssen Sie ziemlich
hin- und herspringen.
Die vollständige Syntax der for-Schleife ist wie folgt:
for(Ausdruck1;Ausdruck2;Ausdruck3)
{
...
}
Ausdruck1 wird zur Initialisierung genau einmal, nämlich beim Eintritt in die Schleife ausgeführt. Anschließend wird geprüft, ob Audruck2 als Resultat „wahr“, d. h. „!=0“ liefert. In diesem Fall wird der
Schleifenkörper durchlaufen. Handelt es sich nur um eine Zeile, besteht der Block also nur aus einer Anweisung, so kann auf die geschweiften Klammern verzichtet werden. Nach Ausführung des Schleifenkörpers wird der Ausdruck3 ausgewertet. Anschließend wird wirder der Ausdruck2 ausgewertet usw.
Das folgende Programm zeigt eine typische Verwendung der for-Schleife. Dabei wird eine Tabelle
der ersten 10 natürlichen Zahlen und deren Quadrate ausgegeben.
DR
A
/*for01.c Die ersten 10 Quadratzahlen
Status: ok*/
#include <stdio.h>
void main(void)
{
int i;
printf(" i\ti*i\n");
for(i = 1;i<=10;i++)
printf("%3d\t%3d\n",i,i*i);
}
C-Programmierer bevorzugen einen knappen Stil und so werden viele dieses Programm wie folgt schreiben:
/*for02.c Die ersten 10 Quadratzahlen
Status: ok*/
#include <stdio.h>
void main(void)
{
int i;
printf(" i\ti*i\n");
for(i = 1;i<=10;printf("%3d\t%3d\n",i++,i*i));
}
Ich halte die erstere Variante aber für die bessere, weil Schleifen-Definition und Schleifenkörper klar getrennt sind. Beachten Sie die Verwendung des Postfix-Inkremenoperators im letzten Ausdruck der forAnweisung. Was würde passieren, wenn man dort stattdessen den Präfix-Inkrementoperator einsetzen würde? Da ich davon ausgehe, dass Sie die Grundrechenarten beherrschen, habe ich darauf verzichtet, die
Ausgabe des Programms mit abzudrucken.
Um vorzeitig aus einer ganzen Schleife oder einem Durchlauf des Schleifenkörpers auszusteigen gibt es
die Befehle break und continue. break bewirkt das Verlassen des Schleifenkonstrukts; die Programmausführung wird mit dem nächsten Kommando fortgesetzt. Das kann natürlich auch ein übergeordnetes
Schleifenkonstrukt sein. continue bewirkt, dass der Rest der laufenden Iteration übersprungen und direkt die nächste begonnen wird. Das heißt
• In einer for-Schleife wird die Kontrolle an den letzten Parameter des for-Konstrukts übergeben
(z. B. i++)
• Für while und do while bedeutet es, das der Test-Teil, d. h. die Auswertung der Bedingung,
sofort vorgenommen wird.
In einem switch-Befehl können Sie continue nicht verwenden.
5.4. VERZWEIGUNGEN
51
5.4 Verzweigungen
FT
In C gibt es neben dem Auswahl-Operator, der bereits in Abschn. 4.7 vorgestellt wurde zwei Möglichkeiten, den Programmablauf in Abhängigkeit von Bedingungen zu steuern. Als Erste stelle ich Ihnen das
if-else-Konstrukt vor,
if (Bedingung)
Anweisung1
else
Anweisung2
das in Abb. 5.3 Ist die Bedingung wahr, d. h. der Ausdruck Bedingung liefert bei Auswertung mit
HH
Bedingung
HH
HH
HH
falsch
wahr
HH
H
H
Anweisung2
DR
A
Anweisung1
Abbildung 5.3: if-else-Konstrukt
den aktuellen Parameterwerten einen Wert ungleich 0, so wird der Block Anweisungen1ausgeführt,
andernfalls der Block Anweisungen2.
Natürlich können if-Konstrukte beliebig tief geschachtelt werden, wie es Abb. 5.4 exemplarisch zeigt.
H
HH Bedingung1 wahr HH
H
H
HH Bedingung2 wahr H
H
Anweisung1
H
falsch
falsch
Anweisung2
...
H
HHBedingungx wahr HH
falsch
H
Anweisungx Anweisungz
Abbildung 5.4: Geschachtelte if-Konstrukte
Achten Sie aber auf die geschweiften Klammerpaare. Das folgende Beispiel ist syntaktisch korrekt,
aber nicht auf den ersten Blick einfach zu verstehen:
if (a == 1)
if (b == 1)
a = 42;
else
b = 42;
KAPITEL 5. KONTROLLSTRUKTUREN
52
switch (Ausdruck)
case Konstante1:
[Anweisungen]
[break]
...
case Konstantek
[Anweisungen]
[break]
[default
Anweisungen]
FT
Das else bezieht sich auf das zweite if, das erste hat kein else. Fehler, die durch dieses „dangling else
problem“ entstehen, sind manchmal schwer zu finden.
Für Mehrwegentscheidungen gibt es das switch-Konstrukt. Es ist besonders für Menüauswahlen geeignet. Hier eine Zahl innerhalb eines endlichen Bereichs ganzer Zahlen die mögliche Eingabe. Je nach
Eingabe wird entsprechend verzweigt.
Visualisiert wird dies Abb. 5.5 Wichtig ist beim switch ist, dass Ausdruck als Ergebnis int liefert und
DR
A
hhh
hhhh
hhh
switch(Ausdruck)
hhhh
hhh
case Konst1:
hhhh
hhhh
case Konst2:
hhh
hhhh
hh
h
default:
...
Anweisungen
Anweisungen
Anweisungen
Anweisungen
Abbildung 5.5: switch-Konstrukt
die Konstanten ebenfalls ganze Zahlen sind.
Sehen wir uns das Ganze nun mal an einigen Beispielen an: Für eine Auswahl von Menüpunkten wird
man ein switch-Konstrukt bevorzugen, etwa wie in folgendem Code-Fragment:
/*switch01.c Eine einfache Menüauswahl
Status: ok /sumindest für die Funktionm nicht main*/
#include <stdio.h>
void main(void)
{
int auswahl;
printf("\nGeben Sie bitte eine Zahl zwischen 1 und 10 ein: ");
scanf("%d",&auswahl);
switch (auswahl)
{
case 1:
{
printf("\nSie haben File Open gewaehlt");
break;
}
5.5. SPRÜNGE
53
FT
case 10:
{
printf("\nSie haben Programm verlassen gewaehlt");
break;
}
default:
{
printf("\nSie haben eine ungueltige Auswahl getroffen");
}
}
}
DR
A
Achten Sie bitte auf die break Befehle. „Vergessen“ Sie diese, so wird nach einem „Treffer“, d. h. wenn
der Wert des Ausdrucks mit einer der Konstanten in den case-Befehlen übereinstimmt, einfach weiter
gearbeitet und insbesondere der default-Zweig durchlaufen. Die case Anweisungen sind dannnur noch
Marken ohne Bedeutung und es wird der ganze Code hinter ihnen ausgeführt. Das wird in vielen Fällen
nicht beabsichtigt sein. break sorgt dafür, dass das ganze switch-Konstrukt verlassen wird.
Allgemeine Regeln aufzustellen, wann man besser if-else und wann man besser switch verwendet, halte ich für problematisch. Da spielen auch viele persönliche Vorlieben eine Rolle. Sicher ist aber,
das gerade Entscheidungen, egal ob sie mit if-else, switch oder ?: getroffen werden, zu den fehleranfälligsten Programmteilen gehören. Achten Sie also genau auf die Aufgabenstellung, analysieren Sie sie
sorgfältig und setzen in Zweifelsfällen hinreichend viele Klammerpaare.
5.5 Sprünge
Diese Thema — Sprünge, Gotos, etc. — möchte viele gerne aussparen. Es gibt aber immer mal wieder
Situationen, in denen diese Konstrukte den Code viel einfacher machen, als die viel gerühmten „strukturierten Anweisungen“. Zu letzteren gehören z. B. alle Schleifen- und Entscheidungskonstrukte, die wir
bisher kennengelernt haben.Diese wurden deshalb auch mittels sogenannter Struktogramme visualisiert.
Hier also nun die Erläuterung der goto- und label-Befehle. Ein label-Befehl definiert einen Punkt
in einem C-Programm. Mit dem goto-Befehl kann man dann direkt an diese Stelle springen, so dass die
Programmausführung dort fortgesetzt wird. So etwas ist in verschiedenen Situationen nützlich, die hier
ohne Anspruch auf Vollständigkeit genannt seien.
1. Sie wollen aus dem innersten Konstrukt einer verschachtelten Struktur heraus, z. B. um Fehler zu behandeln. Der break-Befehl, den wir beim switch-Konstrukt kennen gelernt haben, kann auch bei
allen Schleifen verwendet werden. Er bringt einen aber nur aus dem Konstrukt heraus, in dem man
sich gerade befindet. Oft will man aber „ganz raus“. Dazu kann ein goto-Befehl wie in folgendem
Code-Fragment aus [KR88] sehr nützlich sein:
/*goto01 Ein sinnvoller Einsatz von goto
Status: Fragment*/
void main(void)
{
int desaster = 1;
for(;;)
for(;;)
{
if (desaster)
goto error;
}
error:
{
/*Betreibe Schadensbegrenzung*/
KAPITEL 5. KONTROLLSTRUKTUREN
54
}
FT
}
Code, der goto-Befehle enthält kann immer auch ohne diese geschrieben werden. In manchen Fällen kostet das zusätzliche Vergleiche oder zusätzliche Variablen. Wenn der Code mit dem gotoBefehl aber einfacher ist als der ohne, sollte man ihn trotz vieler Bedenken (siehe Abschn. 5.6)
einsetzen-
5.6 Historische Anmerkungen
Der Befehl goto und die ähnlichen continue und break galten seit Dijkstras Bemerkungen in [Dij68]
als unanständig. Dies war die große Zeit der strukturierten Programmierung. Dieses Konzept verlangte,
dass ein Programm nur aus Sequenzen, Schleifen und Entscheidungen bestehen durfte. Mir ist in meinem
ersten Programmierkurs 1973 (Algol 60) sogar erzählt worden, „gotos“ seien schlecht für die Performance.
Die Programmierer bei der damaligen Esso AG in Hamburg, wo ich damals jobte, konnten darüber nur den
Kopf schütteln. Insgesamt gab es fünf Forderungen an ein Programm das als strukturiert gelten durfte. Der
Kollege Volker Claus aus Stuttgart hat auf dem Kolloquium zur Feier des 25. Geburtstag der Technischen
Informatik an der HAW Hamburg (damals noch FH Hamburg) einen sehr launigen Vortrag gehalten, der
viele meiner Vorurteile bestätigt hat. Ich gebe hier einiges aus dem Gedächtnis wieder, habe aber keine
weiteren Belege dafür.
DR
A
Wie müsste eigentlich ein Programm aussehen, dass gegen alle guten Prinzipien der strukturierten Programmierung verstößt? Ich habe versucht so ein Programm zu konstruieren und
es ist mir gelungen. Dann habe ich mich gefragt, ob jemand wirklich so ein Programm schreiben würde. Und ich bin in einem meiner eigenen Bücher fündig geworden! Nun dachte ich
zunächst, nur ich sei so blöd, habe aber noch weiter gesucht. Da bin ich dann auch noch in
[Knu73] auf das Programm gestoßen, das den Algorithmus für Garbage Collection implementiert. Auch dieses verstößt gegen alle Prinzipien der strukturierten Programmierung. Da habe
ich dann den Begriff des „TUP“ erfunden. Ein Total Unstrukturiertes Programm.
In C finden Sie viele (alle?) Elemente der strukturierten Programmierung, aber Sie haben die Freiheit sie
zu verwenden oder nicht. Es gibt einige Situationen, in denen Code mit nicht-strukturierten Elementen sehr
viel einfacher ist als sog. strukturierter Code. Trotzdem sollten Sie goto und verwandte Befehle sparsam
einsetzen. Meine Erfahrung hat gezeigt, dass es gerade schlechte Programmierer sind, die häufiger zu
diesen Befehlen greifen und die Programme dann auch deutlich schlechter sind. Das betrifft alle Aspekte,
z. B. Performance, Wartbarkeit, Funktionserfüllung . . . .
Gerade dem goto-Befehl wird viel Spaghetti-Code angelastet. Das ist sicher richtig. Aber es sei auch
auf den eben in diesem Abschn. zitierten Vortrag verwiesen.
Meine Erfahrung ist, das man auf gotos sehr oft verzichten kann. So habe ich aus den Schulungsunterlagen für die Programmiersprache CA-IDEAL stets die beiden Folien mit dem goto-Befehl, der dort
quit transfer to hieß, herausgenommen. Alle schlechten Programme, die ich in CA-IDEAL gesehen habe, machten ausgiebig und unnötig Verwendung von diesem Befehl.
5.7 Aufgaben
1. Schreiben Sie ein Programm, dass die 256 ASCII Zeichen in einer 16 × 16-Matrix ausgibt. Geben
Sie als Zeilen- bzw. Spaltenköpfe den ersten bzw. zweiten Teil der hexadezimalen Darstellung des
entsprechenden Bytes aus.
2. Schreiben Sie ein Programm, dass eine Körpergröße h in Meter und ein Gewicht w in Kilogramm
erhält und den Body-Mass-Index (BMI)
w
BM I := 2
h
berechnet, ausgibt und je nach Wert folgende Bewertung abgibt:
5.7. AUFGABEN
55
0 < BM I < 18 : Sie sind auffällig untergewichtig und sollten mehr qualitativ hochwertige Nahrung
zu sich nehmen.
FT
18 ≤ BM I ≤ 25 Ihr Gewicht entspricht Ihrer Körpergröße. Essen Sie weiter wie gewohnt.
25 ≤ BM I Sie sind übergewichtig. Reduzieren Sie Ihre Nahrungsaufnahme und versuchen Sie auf
möglichst frische und kalorienarme Kost umzustellen.
3. Was ändert sich an Programm for02.c, wenn Sie i++ ++i ersetzen? Wie erreichen Sie, das
trotzdem die Tabelle aus Programm for01.c ausgegeben wird?
4. Schreiben Sie ein C-Programm, dass die sogenannte Collatz-Folge berechnet
3cn + 1, cn gerade
cn+1 :
cn
cn gerade
2 ,
5. Schreiben Sie ein C-Programm, dass die Prüfziffer Modulo 11 berechnet! Testen Sie das Programm
mit einigen ISBN Nummern der Bücher, die Sie für diesen Kurs verwenden bzw. empfohlen bekommen haben.
6. Schreiben Sie ein Programm, dass eine Unter- und eine Obergrenze in Grad Fahrenheit als Eingabe
erhält und eine Tabelle in 10er-Schritten der Fahrenheit und entsprechenden Celsius Werte ausgibt.
DR
A
7. Schreiben Sie ein Programm das Unter- und Obergrenzen für Größen in Meter und Gewichte in
Kilogramm erhält und eine Tabelle der Body-Mass-Indexe ausgibt.
KAPITEL 5. KONTROLLSTRUKTUREN
DR
A
FT
56
FT
Kapitel 6
Vektoren und Strings
6.1 Übersicht
DR
A
Vektoren und Strings gehören zu den ganz oft verwendeten Elementen in Programmiersprachen. Man kann
Sie als Konstruktionen auffassen, die auf den elementaren Datentypen aufbauen. So ist ein Vektor eine
Struktur, die viele Elemente eines Typs enthalten kann. Ein String kann als ein Vektor von Zeichen (char)
betrachtet werden. In C hängen Vektoren und Zeiger eng zusammen. Es ist deshalb eine nicht unwichtige
didaktische Frage, was man erst einführt. Ich habe mich hier an [KPP96] orientiert, aber einige Informationen über Zeiger schon an vielen Stellen eingestreut, bevor Zeiger in Kap. 9 systematisch behandelt
werden.
6.2 Lernziele
• Vektoren definieren und initialisieren können.
• Strings definieren und initialisieren können.
• Vektoren und Strings einlesen und auslesen können.
• Mit ein- und mehrdimensionalen Vektoren rechnen können.
6.3 Definition von Vektoren
Ein Vektor wird durch Angabe eines Typs, eines Namens und einer Anzahl von Elementen deklariert:
typ name[anzahl];
Hier einige Beispiele:
int zeile[100];
char hallo[];
char moin[5];
Hier sehe Sie bereits, warum Vektoren und Strings in einem Kapitel behandelt werden: Ein String ist im
Wesentlichen einfach ein Vektor von Zeichen (chars). Die Anzahl Elemente muss eine Konstante sein
oder ein Ausdruck, der nur Konstanten enthält.
Als ganz einfaches Beispiel lesen wir eine Zeile ein und geben Sie rückwärts aus:
/* palindrom01.c Zeile Einlesen und rückwärts ausgeben
Status: incomplete*/
#include <stdio.h>
57
KAPITEL 6. VEKTOREN UND STRINGS
58
FT
#define MAXLEN 100
void main(void)
{
int i, c;
int puffer[MAXLEN];
printf("\nGeben Sie bitte eine Zeile Text ein: \n");
for (i=0; (i < MAXLEN) && ((c = getchar()) != ’\n’);i++)
puffer[i]= c;
putchar(’\n’);
while(--i >= 0)
putchar(puffer[i]);
}
Ich ziehe Konstantendeklarationen Präprozessor-Direktiven vor. Warum, erfahren Sie in Kap. 8. Hier „mault“
aber der C-Compiler, wenn ich
#define MAXLEN 100
durch
DR
A
const int MAXLEN = 100;
ersetze. Das ist aber nicht weiter tragisch, denn so etwas sollte man in eine Konfigurationsdatei schreiben,
als Parameter übergeben, aber nicht so in ein Programm schreiben. Um zu C kompatibel zu bleiben, finden
Sie solche Deklarationen aber auch noch sehr viel in C++.
Sehen wir uns nun das Programm palindrom01.c etwas genauer an. So wie es hier steht, wandelt
es der Compiler auch mit /Wall ohne Warnungen um. Aber an diesem Beispiel sehen wir sehr gut, dass es
Unterschiede zwischen den Zielen gibt, die C Syntax peinlich genau einzuhalten und lesbare Programme
zu schreiben. Der Vektor puffer[MAXLEN] soll Zeichen vom Typ char enthalten. Deklariert man ihn
auch so, so bringt der Compiler die Warnung
palindrom01.c(11) : warning C4244: ’=’: Konvertierung von ’int’ in
’char’, möglicher Datenverlust
für den Teil der for-Anweisung, in dem c = getchar() steht. Für dieses Problem gibt es keine einfache Lösung. Der Rückgabetyp von getchar() ist nun einmal int. Das lässt sich auch nicht ohne
weiteres ändern, denn es muss a auch EOF zurückgegeben werden können, und das ist nicht notwendig
ein char. So wie das Programm hier abgedruckt ist, wird es vom Compiler ohne Warnungen /auch mit
/Wall) „verdaut“. Der Lesbarkeit wäre aber sicher mit einer Deklaration char puffer[MAXLEN]
besser gedient.
Sehen wir uns nun an, was wir aus dem obigen Programm palindrom01.c und insgesamt über
Vektoren lernen können oder müssen.
1. Die Elemente eines Vektor belegen immer einen zusammenhängenden Speicherbereich.
2. Auf die einzelnen Elemente kann über vektorname[i] zugegriffen werden, wie dies an zwei
Stellen um Beispielprogramm geschieht.
3. Die Indexwerte eines Vektors der Länge n laufen von 0–n − 1. Achten Sie nicht darauf, so sind
typische Anfängerfehler nach dem Motto „off by one“ oder „leicht daneben ist auch vorbei“ die
unweigerliche Folge.
4. Vektoren können mit jedem Datentyp gebildet werden.
6.4. INITIALISIERUNG VON VEKTOREN
59
6.4 Initialisierung von Vektoren
FT
Im Programm palindrom01.c wurde der Vektor aus Zeichen Element für Element durch Tastatureingaben gefüllt. Sie können Vektoren aber auch gleich bei der Definition initialisieren.
int zahlen[3] = {1, 2, 3};
Geben Sie die Länge nicht an, so ermittelt der Compiler bei der Definition aus der Länge des Initialisierungsvektors die Länge. Wir hätten als auch dies schreiben können:
int zahlen[] = {1, 2, 3};
Aber aufgepasst: Deklarieren Sie einen Vektor ohne Längenangabe und ohne Initialisierung, so erhalten
Sie die Compiler-Fehlermeldung „C2133 unbekannte Größe“. Das ist auch verständlich, denn wie sollte
der Compiler hier auf eine Größe des Vektors schließen?
6.5 Strings
DR
A
Nun kommen wir endlich dazu, uns die schon oft ohne Erklärung verwendeten Strings genauer anzusehen.
Ein String ist eine Zeichenfolge, die mit dem String-Endezeichen ’\0’ abschließt. Eine zeichenweise
Definition, wie oben für ganze Zahlen vorgeführt sähe also so aus:
char str[] ={’M’,’o’,’i’,’n’,’\0’};
Einfacher liest sich aber die Initialisierung mit dem Wert als String-Konstante
char str[] = "Moin";
Hier ist das String-Endezeichen Bestandteil der String-Konstante. Betrachtet man einen String also als
Vektor, so belegt ein String der Länge n die n + 1 Elemente von 0 bis n eines Vektors der Länge n + 1.
Zum Beleg dafür ein kleines Programm und seine Ausgabe.
/* vect02.c Deklaration und Definition von Vektoren*/
#include <stdio.h>
void main(void)
{
char str1[] ={’M’,’o’,’i’,’n’,’\0’};
char str2[] = "Moin, Moin";
printf("\nDie Laenge von str1 ist %d.",sizeof(str1));
printf("\nDie Laenge von str2 ist %d.",sizeof(str2));
}
Die Laenge von str1 ist 5.
Die Laenge von str2 ist 11.
6.6 Vektoren und Adressen
In C ist der Name eines Vektor ein Zeiger. Unser String str1 enhält die Adresse, an der das erste Zeichen
gespeichert ist. Es ist also egal, ob wir str1 schreiben oder &str[0], wir erhalten in jedem Fall die
Adresse des ersten Zeichens des Strings. Ist int i; so ist es ebenso egal, ob wir schreiben str1 + i
oder&str1[i]. Das heißt, addieren wir auf die Anfangsadresse eines Vektors eine ganze Zahl i, so erhalten wir die Adresse des i-ten Vektorelements. Auf den ersten Blick mag des etwas verwirrend erscheinen.
Es ist aber in C üblich eher mit den Zeigern als den Vektoren zu hantieren.
Dies hat Konsequenzen: Nehmen wir etwa einen String, der ja (auch) ein Vektor von chars ist. Hier
wieder der Hamburgische Gruß
KAPITEL 6. VEKTOREN UND STRINGS
60
FT
/*vect12.c Was passiert bei strings
*/
#include <stdio.h>
#include <string.h>
void main(void)
{
char *str1 = "Moin, Moin";
char *str2 = "
";
printf("\n%s",str1);
printf("\n%x",str1);
printf("\n%s",str2);
printf("\n%x",str2);
str2 = str1;
printf("\n%s",str2);
printf("\n%x",str2);
str1[2]=’j’;
printf("\n%s",str2);
printf("\n%x",str2);
}
DR
A
in str1 und ein leerer String in str2. Anschließend geben wir sowohl die Strings, als auch die jeweilige Adresse, an der sie beginnen aus. Was passiert nun aber bei der Zuweisung str2 = str1;?
Beides sind Adressen. Also enthält str2 anschließend die Adresse, die in str1 seht. Beide verweisen
nun also auf den gleichen Hauptspeicherbereich. Was das bedeutet macht die nächste Zuweisung deutlich:
str1[2]=’j’;, die das erste „i“ durch ein „j“ ersetzt. Dadurch wird aber auch str2 geändert, wie die
folgende Ausgabe zeigt. Das ist auch nachvollziehbar, denn beide beziehen sich auf den gleiche Hauptspeicherbereich. Dies mag in manchen Fällen gewollt sein, ist es hier aber nicht. Um einen String einem
anderen zuzuweisen können Sie keine Zuweisung verwenden, sondern müssen die Funktion strcpy aus
der String Bbiliothek, deklariert in string.h. Ebenso sieht es mit dem Vergleichoperator == aus.
Mit diesen Erläuterungen ist es nun auch klar, wie man Zeichenketten mittels scanf einliest, etwa so:
scanf("%s",str1);
Wie Sie aus Kap. 2, Abschn. 2.8 vielleicht noch erinnern, erwartet scanf Adressen von Variablen und
geanu das kriegt sie hier ja auch. Die Funktion scanf hat noch ein paar weitere Möglichkeiten, die sie im
Formattierungsstring ausnutzen können:
Feldbreite Geben Sie eine Feldbreite bei %s an, etwa %88s, so werden maximal 88 Zeichen gelesen.
Suchzeichen Statt der Typangabe s kann hinter dem %-Zeichen auch eine Liste von Suchzeichen in eckigen Klammern erscheinen. Ist auf eine Ausgabe nur die Möglichkeit gegeben, mit J für Ja oder N für
nein zu antworten, so kann man das etwa so angeben:
scanf("%1[JjNn]",antwort);
Dann werden nur diese vier Zeichen akzeptiert, anderfalls bricht scanf das Einlesen ab. Damit
haben wir gleich kleine bzw. große Eingabezeichen mit behandelt. Mehrer Suchzeichen können auch
einfach so angegeben werden: a-z erlaubt etwa nur die Eingabe von Kleinbuchstaben.
Ausschlusszeichen Wird dem Suchzeichen das Zeichen ^ vorangestellt, so wird die Eingabe beendet,
sobald eines der Suchzeichen eingegeben wird.
Nun können wir bereits eine Sache erklären, die Ihnen bisher geheimnisvoll erschienen Sein muss.
Bereits in Abschn. 2.8 haben wir gesehen, wie man Variablen deklariert, die eine Adresse enthalten. Haben
wir mittels
char moin[] = "Moin";
6.7. MEHRDIMENSIONALE VEKTOREN
61
char *moin = "Moin";
FT
einen String deklariert und definiert, so wissen wir aus diesem Abschnitt, dass moin eine Adresse enthält.
Dies ist die Adresse des ersten Zeichens des Strings. Das hätten wir aber auch so schreiben können:
Beide Schreibweisen sind äquivalent. Die letztere ist in C aber üblich.
6.7 Mehrdimensionale Vektoren
Bisher haben wir „eindimensionale“ Vektoren betrachtet. „Eindimensional“ in dem Sinne, dass es 1 × nMatrizen, eben vektoren. Bei vielen numerischen Rechnungen benötigt man aber etwa Matrizen, etwa n×n
Matrizen. In C sind nach der Definition des ANSI Standards bis zu 31 Dimensionen möglich. Sehen wir
uns das an einem einfachen Beispiel an.
DR
A
/*vect04.c Ein einfaches lineares Gleichungssystem */
#include <stdio.h>
void main(void)
{
int matrix[2][2]={{1,2},{3,4}};
int b[2] = {1,1};
int x[2];
int i,j;
for(i=0;i<2;i++)
{
printf("\n");
for(j=0;j<2;j++)
{
printf("%d ",matrix[i][j]);
}
}
}
Aus der Definition der Matrix in der ersten Code-Zeile von main sehen sie vielleicht schon wie mehrdimensionale Vektoren in C aufgebaut sind: Ein n + 1-dimensionaler Vektor ist ein 1-dimensionaler Vektor,
dessen Elemente n-dimensionale Vektoren sind. So weisen wir hier der ersten Zeile der Matrix die Werte
1 und 2 zu und der zweiten die Werte 3 und 4 und kontrollieren dies durch eine einfache Ausgabe.
Ein lineares Gleichungssystem löst man natürlich in vielen Fällen durch das Gausssche Eliminiationsverfahren. Hier tut es aber auch eine bekannte Formel.
/*vect05.c Ein einfaches lineares Gleichungssystem */
#include <stdio.h>
void main(void)
{
int matrix[2][2]={{1,2},{3,4}};
int b[2] = {1,1};
int x[2];
int i,j;
int determinante = matrix[0][0]*matrix[1][1]-matrix[1][0]*matrix[0][1];
printf("Die Determinante ist: %d",determinante);
x[0] = (b[0]*matrix[1][1]-b[1]*matrix[0][1])/determinante;
x[1] = (matrix[0][0]*b[1] - matrix[1][0]*b[0])/determinante;
for(i=0;i<2;i++)
{
printf("\n");
for(j=0;j<2;j++)
KAPITEL 6. VEKTOREN UND STRINGS
62
{
}
for(i=0;i<2;i++)
printf("\n%3d",x[i]);
}
FT
printf("%d ",matrix[i][j]);
}
Zur Lösung numerischer Aufgaben aber auch noch eine Warnung: Verlassen Sie sich nicht einfach auf
das Ergebnis. Dies wäre leichtsinnig, wie Sie schon bei der Berechnung der Fibonacci-Zahlen in Abschn.
7.5 gesehen haben. Auch bei der Lösung selbst kleiner linearer Gleichungssysteme kann es zu abstrusen
Ergebnissen kommen. Ursache hierfür ist dann meistens eine sogenannte schlechte Kondition der Matrix.
Hier geht noch alles gut, aber wenn Sie mit floats oder doubles rechnen, sollten Sie immer überlegen,
ob das Ergebnis gültig ist oder nicht.
6.8 Aufgaben
1. Schreiben Sie ein Programm, dass einen Vektor von Zeichen über die Tastatur einliest und prüft, ob
es sich um ein Palindrom handelt.
DR
A
2. Schreiben Sie ein Programm, das als Eingabe eine Zahl, eine Einheit für diese Zahl und eine Einheit,
in diese umgerechnet werden soll. Es liefert als Ausgabe die Größe in der neuen Einheit.
Funktionen
7.1 Übersicht
FT
Kapitel 7
DR
A
Die Zerlegung von Programmen gehört zu den elementaren Aufgaben um in einem großen Programmsystem den Überblick zu behalten. Einzelne Funktionen oder Gruppen von Funktionen fasst man deshalb zu
eigenen Modulen zusammen. In diesem Zusammenhang werde ich Ihnen deshalb auch einige Bewährte
Prinzipien des Software-Engineering vorstellen, so wie sie in C angewendet werden. In der Standardbibliothek von C werden ihnen viele Funktionen mitgeliefert, die sie laufend verwenden. In diesem Kapitel
werden Sie lernen, was dabei alles zu beachten ist. Außerdem werden Sie erfahren, wie man einem CProgramm bereits beim Aufruf Parameter mitgeben kann.
7.2 Lernziele
• Wissen, wie man Funktionen in separate Dateien ausgliedert.
• Wissen, wie man mehrere Dateien gleichzeitig umwandelt.
• Einige Grundprinzipien der Modularisierung kennen.
• Den Unterschied zwischen Wertsemantik und Referenzsemantik kennen.
• Einige wichtige Standardfunktionen besser verstehen und anwenden können.
• Aufrufparameter an ein Programm übergeben können.
7.3 Modularisierung in C
Ich beginne diesen Abschnitt, in dem ich ein Beispiel aus Abschn. 2.6 aufgreife. Dort hatte ich Ihnen
das Programm summe.c gezeigt. Für dieses Programm ist es natürlich nicht notwendig, eine Zerlegung
vorzunehmen. Aber bereits an diesem einfachen Beispiel kann man die Grundtechnik zeigen, wie sie CProgramme in einzeln compilierbare Einheiten zerlegen. In diesem Fall habe ich das Programm summe.c
zur Erläuterung in drei Teile zerlegt:
1. summe02.c: Das Hauptprogramm, in dem die Funktion main steht und das die Funktion summe
aufruft.
2. summe03.h: Die Header-Datei, in der die Funktion summe deklariert wird.
3. summe03.c: Die C-Datei, in der die Funktion summe implementiert wird.
Wie bereits in den vorangehenden Kapiteln sehen Sie nun die Dateien und anschließend die Erläuterungen
dazu.
63
KAPITEL 7. FUNKTIONEN
64
FT
/******************************************
summe.c berechnet die Summe zweier ganzer Zahlen
******************************************/
#include <stdio.h>
#include "summe03.h"
int main()
{
int pa = 9;
int pb = 16;
printf("%d%s%d%s%d%s",pa," + ",pb," = ",summe(pa,pb),".");
return 0;
}
/*summe03.h Header Datei für Summenfunktion*/
int summe(int a, int b);
DR
A
/* summe03.c Implemtierung der Summenfunktion*/
#include "summe03.h"
int summe(int a, int b)
{
return (a + b);
}
Nun zu den Erläuterungen
• Das Programm summe02.c kennen Sie bereits. Es sollte nun keiner weiteren Erläuterung bedürfen.
• In der Header-Datei summe03.h steht nut die Deklaration der Summenfunktion. Achten Sie bitte
darauf, das diese mit einem „;“ (Semikolon) abgeschlossen wird.
• In der Datei summe03 finden Sie die bereits aus Abschn. 2.6 bekannte Funktion.
Nun müssen wir das Ganze auch noch „zum Laufen“ bringen. Versuchen wir zunächst einmal, dass Programm summe02.c umzuwandeln.
cl summe02.c
bringt uns das aus der Vorlesung bereits bekannte Ergebnis:
Microsoft (R) 32-Bit C/C++-Optimierungscompiler Version 13.10.3077
für 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
summe02.c
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.
/out:summe02.exe
summe02.obj
summe02.obj : error LNK2019: Nicht aufgelöstes externes Symbol
’_summe’, verwiesen in Funktion ’_main’
summe02.exe : fatal error LNK1120: 1 unaufgelöste externe
VerweiseMicrosoft (R) \\
32-Bit C/C++-Optimierungscompiler Version 13.10.3077 für 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
summe02.c
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation. All rights reserved.
7.3. MODULARISIERUNG IN C
65
FT
/out:summe02.exe
summe02.obj
summe02.obj : error LNK2019: Nicht aufgelöstes externes Symbol
’_summe’, verwiesen in Funktion ’_main’
summe02.exe : fatal error LNK1120: 1 unaufgelöste externe Verweise
Das war’s wohl nicht! Was ist schiefgelaufen? Sie können in C Funktionen separat umwandeln. das geht
z. B. so.
cl /c summe03.c
Die Ausgabe lautet nun:
Microsoft (R) 32-Bit C/C++-Optimierungscompiler Version 13.10.3077
für 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
summe03.c
DR
A
Der Schalter „/c“ sagt dem Compiler (genauer: dem Programm cl) das er nur umwandeln, aber nicht linken
soll. Hier erscheint, kein Fehler, also schient alles in Ordnung zu sein. In der Ausgabe (output) wird auf
ein „Nicht aufgelöstes externes Symbol ’_summe’“ verwiesen. Irgendwie scheint der Compiler trotz des
#include Befehls unsere Funktion summe nicht zu kennen. Um diesen „Problem zu lösen“ müssen wir
noch etwas mehr über (den Microsoft) Compiler wissen. Wenn wir ein Programm umwandeln wollen, dass
mehrere Dateien verwendet, müssen wir das dem Compiler auch sagen!. Das geschieht z. B. so:
cl
summe02.c summe03.c
Die Ausgabe lautet nun (ohne die bereits hinlänglich bekannten Copyright Aussagen)
/out:summe02.exe
summe02.obj
summe03.obj
Achten Sie bitte darauf, dass zwischen den Dateinamen, die der Compiler bekommt, kein Komma steht!
Danach läuft alles wie gewohnt ab (und funktioniert).
Sie sollen sich bemühen, Ihre Programme nach folgenden Prinzipien zu strukturieren:
Hoher Zusammenhalt Alles, was Sie in ein Paar von Header- und Implementierungs-Dateien zusammenfassen, sollte auch wirklich zusammengehören. Wenn Sie etwas weglassen würden, würde etwas
fehlen.
Geringe Kopplung Sie sollten nicht zu viele verschiedene Dateien heranziehen müssen. Um so mehr dies
sind, um so anfälliger ist Ihr eigenes Programm für Probleme, wenn sich in den anderen Dateien
etwas ändert.
Zwischen den genannten Zielen — „Hoher Zusammenhalt“ und „Geringe Kopplung“ — besteht ein Konflikt! Schreiben Sie alles in eine Datei — Deklarationen, die eigentlich in Header-Dateien gehören und
Implementierungen, die eigentlich in .c-Dateien gehören — so haben sie minimale Kopplung aber meist
„lausigen“ Zusammenhalt. Schreiben Sie für jede Funktion eine Header-Datei und eine ImplementierungsDatei, so haben sie maximalen Zusammenhalt, aber maximale Kopplung. Den Weg aus diesem Konflikt
müssen Sie selber finden. Mein Motto hierzu ist der Titel eines Films von Alexander Kluge
In Gefahr und größter Not bringt der Mittelweg den Tod.
Auf das tägliche Leben und das Programmieren angewendet heißt das: Es ist besser eine eindeutige Entscheidung zu treffen, als immer nur abzuwarten. Sollte die Entscheidung falsch sein, so kann man sie
(hoffentlich) noch korrigieren.
Ein erstes Beispiel können Sie sich an der Aufteilung der C Standardbibliotheken nehmen. Aber achten
Sie bitte darauf, dass diese über Jahrzehnte gewachsen sind und viele Altlasten mit sich schleppen. Manches
würde man heute anders machen!
KAPITEL 7. FUNKTIONEN
66
FT
Für weitergehende Erläuterungen dazu, wie man C-Programme technisch in verschiedene Teile zerlegt,
muss ich Sie auf den Anhang F verweisen, die wir in diesem Semester wohl nicht beide mehr behandeln
werden können und die nur einige rudimentäre Informationen enthalten.
Fassen wir zusammen: Funktionen in C sind das was in anderen Programmiersprachen Unterprogramm,
Subroutine etc. genannt wird. Eine Funktion sollte alle Elemente enthalten, die sie zur Erledigung ihrer Aufgabe benötigt und keine weiteren. So erreichen Sie einen guten funktionalen Zusammenhalt Ihrer
Funktionen. Haben Funktionen jeweils eine wohldefinierte Aufgabe, so werden sie auch nur eine begrenzte
und übersichtliche Anzahl anderer Funktionen verwenden. Die Kopplung mit anderen Elementen wird sich
also in Grenzen halten.
Eine Art der Kopplung ist in C aber sehr häufig: Die sogenannte globale Kopplung. Durch die Definition
von Konstanten oder sogar Variablen außerhalb einer Funktion stehen diese global zur Verfügung. Die
Gefahren hierdurch liegen auf der Hand: Ändert ein Programm den Wert einer globalen Variablen, so
können davon sehr viele andere Programme betroffen sein. In C ist diese Gefahr aber dadruch eng begrenzt,
dass fast nur Konstanten auf diese Art definiert werden. Beispiele davon haben wir bereits in limits.h
gesehen. Und Konstanten ändern ihren Wert nicht, wie ja schon der Begriff angibt.
Sehen wir uns zum Schluss dieses Abschnitts noch ein einfaches Beispiel einer Funktion an. Aufgabe
ist es, aus einem Radius die Fläche eines Kreises zu berechnen. Ist r der Radius, so kennen Sie die Formel
f = π · r2
DR
A
aus der Schulmathematik. Zunächstmal müssen wir den Wert von π finden. So etwas wird es sicher in
C geben. Aber wo? Das ist ein mathematisches Symbol so dass wir mal in math.h nachsehen wollen.
Tatsächlich, dort finden wir eine Zeile
#define M_PI
3.14159265358979323846
Wenn ich Ihnen nun noch sage, dass der Radius über die Tastatur eingegeben und mit hoher Genauigkeit
berechnet werden soll, so sollte das folgende kleine Programmbeispiel auch für Sie leicht zu schreiben
sein.
/*radius01.c Berechnet aus Radius die Kreisfläche
Status: incomplete*/
#include <stdio.h>
#include <math.h>
double kreisflaeche(double radius);
void main(void)
{
double radius;
printf("Geben Sie bitte einen Radius als Gleitpunktzahl ein:");
scanf("%lf",&radius);
printf("\nDie Flaeche dieses Kreises ist: %lf",kreisflaeche(radius));
}
double kreisflaeche(double radius)
{
return (M_PI*radius*radius);
}
Eine kleine Überraschung hat der Compiler aber doch noch für uns. Obwohl wir den #include-Befehl
für math.h hingeschrieben haben, erhalten wir die Fehlermeldung
C2065: ’M_PI’: nichtdeklarierter Bezeichner.
Was haben wir falsch gemacht? Ein erneuter Blick in math.h lässt uns dies erkennen: Über dem Teil mit
den Definitionen der mathematischen Konstanten und Funktionen steht folgendes:
#ifdef
_USE_MATH_DEFINES
7.4. WERT- UND REFERENZSEMANTIK
67
FT
/* Define _USE_MATH_DEFINES before including math.h to expose these
* macro definitions for common math constants. These are placed
* under an #ifdef since these commonly-defined names are not part
* of the C/C++ standards.
*/
Wir müssen also irgendwo vor dem Präprozessor-Befehl #include <math.h> eine Zeile einnfügen,
in der steht:
#define _USE_MATH_DEFINES
Was man mit dem Präprozessor sonst noch so anstellen kann erfahren Sie in Kap. 8. Mit dieser kleinen
Ergänzung läuft das Programm nun aber wirklich.
7.4 Wert- und Referenzsemantik
DR
A
Rufen Sie eine Funktion mit Parametern auf, so verwendet C die sogenannte „Wertsemantik“, im Englischen „call by value“ genannt. Übergeben wird nicht die Variable, sondern der Wert, den sie enthält, daher
der Begriff Wertsemantik. Die aufgerufene Funktion erhält also einen Wert, der in einem Parameter gespeichert wird, der wie eine lokale Variable betrachtet werden kann. Insbesondere haben Änderungen, die
innerhalb einer Funktion an einem Parameter-Wert vorgenommen werden, keine Auswirkung auf den Wert
der Variablen außerhalb der Funktion.
Wollen Sie, dass eine Funktion den Wert einer übergebenen Variablen so ändern kann, dass dies tatsächlich den Wert der außerhalb der Funktion definierten Variablen ändert, so müssen Sie das anders machen.
C ermöglicht Ihnen die in gewissem Sinne indirekt zu tun. Statt eine Variable als Parameter zu deklarieren,
deklarieren Sie eine Adresse einer Variablen, einen sogenannten Zeiger oder Pointer. Über diese Adresse
(deren Änderung nach draußen natürlich keine Wirkung hat), kann die Funktion auf die Variable zugreifen
und ihren Wert ändern. Dies nennt man Referenzsemantik oder „call by reference“.
Zur Wert- und Referenzsemantik gibt es eine nette Anekdote. Nicklaus Wirth ist einer der bekanntesten
Informatiker und hat viel auf dem Gebiet Compiler und Programmiersprachen gearbeitet. Wirth beklagte
sich scherzhaft darüber, dass die Europäer seinen Namen als „Niklaus Wirt“, Amerikaner aber als „Nickles
Worth“ aussprechen würden. Dies heiße, die Europäer würden ihn beim Namen (by name) rufen und die
Amerikaner nach seinem Wert (by value).
Die Wertsemantik hat einige Vorteile, trotzdem kann man in vielen Fällen nur unter Verwendung der
Referenzsemantik gut modularisieren. Die wichtigsten Vorteile der Wertsemantik sind:
• Die aufgerufene Funktion kann keinen unbeabsichtigten oder ungewollten Änderungen an den Argumenten der aufrufenden Funktion vornehmen.
• Argumente einer Funktion können beliebige Ausdrücke sein.
• Die Werte der Parameter stehen sofort wie Werte lokaler Variablen zur Verfügung.
Das man auch Adressen übergeben kann hat einige interessante Konsequenzen. So kann man auch Funktionen übergeben. Sie können also ein Programm schreiben, dass eine vom Anwender angegebene Funktion
auf andere Elemente anwendet.
Manchmal haben Sie keine Wahl zwischen Wert- und Referenzsemantik. Definieren Sie eine Funktion,
die als Parameter einen Vektor erhält, so wissen wir bereits, dass tatsächlich eine Adresse übergeben wird,
die Adresse des ersten Elements. Als einfaches Beispiel hierfür lassen wir den Anwender eine Textzeile
eingeben und geben sie in der umgekehrten Reihenfolge wieder aus. Vielleicht gelingt es uns ja so neue
Palindrome zu finden.
/* palindrom02.c Wort umdrehen mit Funktion/Vektor
Status: incomplete*/
#include <stdio.h>
#include <stdlib.h>
KAPITEL 7. FUNKTIONEN
68
DR
A
FT
#define MAXLEN 100
void reverse(char s[]);
void main(void)
{
int i, c;
char puffer[MAXLEN];
printf("\nGeben Sie bitte eine Zeile Text ein: \n");
for (i=0; (i < MAXLEN-1) && ((c = getchar()) != ’\n’);i++)
puffer[i]= c;
puffer[i]=’\0’;
reverse(puffer);
printf("\n%s",puffer);
}
void reverse(char s[])
{
int c, i, j;
for(i = 0, j = strlen(s)-1;i < j; i++, j--)
{
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
Sezieren wir dieses Program einmal, um zu sehen, was hier so alles passiert:
1. Wie schon in einem anderen Programm, wird stdlib.h verwendet, diesmla um die Funktion
strlen zur bestimmung der länge eines Strings verwenden zu können.
2. Als erstes wird eine Konstante MAXLEN mit 100 definiert. Nicht schön, aber wir müssen irgendwie den Speicherplatz begrenzen, solange Sie noch nicht in Kap. 13 gerlent haben, wie man sich
dynamisch Speicher beschafft (solange noch welcher da ist).
3. Außerdem deklarieren wir vor der main-Funktion die Funktion reverse, die wir verwenden wollen
und weiter unten implementieren.
4. Als erste Aktionen wird der Benutzer aufgefordert, eine Zeile Text einzugeben.
5. Diese wird dann in einer for-Schleife eingelesen. Die Schleife wird beendet wenn Enter gedrückt
(\n, Zeilenumbruch) wird oder die maximale Anzahl Zeichen MAXLEN erreicht ist. Dabei müssen
wir den Platz für das String-Ende Symbol mit berücksichtigen. Deshalb endet die Schleife schon bei
MAXLEN-2.
6. Nach dem Verlassen der Schleife hat der Postfix-Inkrement Operator seine Aufgabe ein letztes Mal
erledigt. Die Variable i ist nun um 1 größer als die Position des Vektors an der das letzte Zeichen
unseres Textstrings steht. Da schreiben wir nun das String-Endezeichen ’\0’. Durch Ausdruck
des Strings im hexadezimalen Format können sie sich vergewissern, dass dort nun wirklich 0 steht.
Denken Sie dran: puffer[100] ist ungültig!
7. Nun wird die Funktion reverse aufgerufen und das Ergebnis ausgegeben.
8. Zum Schluss der Clou: Da Platz knapp ist drehen wir den String „in place“ um, also ohne einen
weiteren String gleicher Länge zu belegen. Das ist ein sehr typisches C-Konstrukt und Sie sollten es
sicher verstehen.
8.1. Die beiden Laufvariablen i und j brauchen wir sowieso, wie Sie in [KPP96] nachlesen können,
wo das ganze mit einem zusätzlichen String gemacht wird. Wir brauchen so nur eine zusätzliche
Hilfsvariable c.
7.5. REKURSIVE FUNKTIONEN
69
FT
8.2. In der for-Schleife verwenden wir zwei Laufvariablen. Die eine, i, wird mit 0, die andere,
j, wird mit strlen(s)-1 initialisiert. i „zeigt“ also zu Beginn der Schleife auf das erste
Zeichen und j auf das letzte Zeichen des Strings.
8.3. Die Begrenzung der Schleife erfolgt durch i < j.
8.4. Am Ende eines Schleifendurchlauf wird i inkrementiert und j dekrementiert, jeweils postfix.
8.5. Im Schleifen Körper wird als erstes s[i] in die Hilfsvariable c „gerettet“.
8.6. Anschließend wird s[i] durch s[j] ersetzt.
8.7. Als Letztes wird der gerettete Wert von s[i], der uns ja in c noch zur Verfügung steht s[j]
zugewiesen.
8.8. Das ganze ist eigentlich ein „swap“-Funktion, wir vertauschen jeweils zwei Werte. Im ersten
Schleifendurchlauf den ersten mit dem letzten, im zweiten den zweiten mit dem vorletzten usw.
Dieses Konstrukt braucht man laufend, z. B. bei Sortierverfahren.
8.9. Das String-Endezeichen fassen wir gar nicht an, es bleibt auf seiner Position.
7.5 Rekursive Funktionen
Zur Einführung in diesen Abschnitt ein altbekanntes Beispiel. Die nach der Formel
DR
A
n0 = n1 = 1
ni+2 = ni+1 + ni
gebildeten Zahlen heißen Fibonacci-Zahlen oder Fibonacci-Folge. Diese kann man natürlich sehr einfach
mit einem C-Programm berechnen: Der Algorithmus sieht im Pseudocode ganz harmlos aus:
fib(n)
if (n=0 oder n =1)
return 1
else
return (fib(n-1) + fib(n-2))
Das kann man in C genauso umsetzen
/*fibonacci02.c Berechnung der Fibonacci-Zahlen bis MAX
rekursiv.
Status: incomplete*/
#include <stdio.h>
const long int MAX = 40L;
long int fib(long int n);
void main(void)
{
long int i, obergrenze;
printf("Geben Sie bitte an bis zu welcher Fibonacci-Zahl Sie \
rechnen lassen wollen:");
scanf("%ld",&obergrenze);
printf("\nBerechnung der ersten %ld Fibonacci-Zahlen rekursiv",obergrenze);
if(obergrenze > MAX)
printf("\nDas haetten Sie besser nicht getan!");
printf("\n%15s\t%15s\n","n","fn");
for(i = 0;i<=obergrenze;i++)
{
printf("%15ld\t%15ld\n",i,fib(i));
}
KAPITEL 7. FUNKTIONEN
70
FT
}
long int fib(long int n)
{
if(n == 0 || n==1)
{
return 1;
}
else
return (fib(n-1) + fib(n-2));
}
Die kleine Bemerkung in der einen Ausgabe sollte Sie aber etwas skeptisch machen. Man kann zeigen,
das diese Implementierung des Algorithmus exponentiellen Aufwand verursacht. Die Anzahl auszuführender Rechenoperationen ist so ca. 2n . Lassen Sie sich aber davon nicht ins Bockshorn jagen, eingige der
effizientesten Algorithmen arbeiten rekursiv, wie wir später in dieser Veranstaltung sehen werden.
Hier nun noch eine effizientiere Implementierung mit einer for-Schleife.
DR
A
/*fibonacci01.c Berechnung der Fibonacci-Zahlen bis obergrenze
in einer for Schleife.
Status: started*/
#include <stdio.h>
const long int EINS = 1L;
const long int ZWEI = 1L;
void main(void)
{
long int i;
long int j = EINS;
long int k = ZWEI;
long int f;
long int obergrenze;
printf("Geben Sie bitte an bis zu welcher Fibonacci-Zahl Sie \
rechnen lassen wollen:");
scanf("%ld",&obergrenze);
printf("\nBerechnung der ersten %d Fibonacci-Zahlen\n",obergrenze);
printf("%15s\t%15s\n","n","fn");
printf("%15d\t%15d\n",0,EINS);
printf("%15d\t%15d\n",1,ZWEI);
for(i = 2;i<=obergrenze;i++)
{
f = j + k;
j = k;
k = f;
printf("%15ld\t%15ld\n",i,f);
}
}
Hier laufen wir einfach durch eine for-Schleife, bilden eine Summe und tauschen die beteiligten Variablen
nach oben. Das Laufzeitverhalten ist nun linear, d. h. wir brauchen für jede Zahl nur eine Addition und drei
Zuweisungen. Das Ergebnis überzeugt, was die Laufzeit angeht. Auch die ersten 1000 werden auf meinem
Rechner in ca. 0.8 Sekunden ausgegeben, die meiste Zeit erfordert dabei die Bildschirmausgabe.
Nicht überzeugen können die berechneten Werte, um es mal vorsichtig zu formulieren. Ab der sechsundvierzigsten Fibonacci Zahl haben wir den durch long int definierten Bereich verlassen. Wir könnten
noch einen Versuch mit long unsigned machen, aber das können Sie selber ausprobieren.
Ändert man die entsprechenden Datenttypen und Ausgabefunktionen auf long double ab, so kommt
man um einiges weiter. Dieses Programm sollte ihnen aber auch zeigen, dass man einem Ergebnis eines
Rechners nicht blind trauen darf.
7.6. EINIGE WICHTIGE STANDARDFUNKTIONEN
71
FT
Natürlich können Funktionen alle arithmetische Datentypen als Rückgabewert haben. Außerdem sind
die folgenden Typen zulässig: struct, union, pointer, oder void. Nicht zulässig als Rückgabewerte
sind Funktionen oder Vektoren.
7.6 Einige wichtige Standardfunktionen
Einige Funktionen werden Sie immer wieder verwenden. Damit Sie nicht die Header-Dateien durchsuchen
müssen, habe ich Ihnen hier einige zusammengestellt.
7.6.1 Funktionen mit Strings
char *strcpy(char* s, const char *ct) Kopiert den String ct, einschließlich String-Endezeichen
auf den String s und liefert s zurück.
char *strcat(char* s, const char* ct) Hängt den String ct an das Ende von s an und
liefert das s veränderte s zurück.
DR
A
int strcmp(const char *cs, const char *ct) Vergleicht den String cs mit dem String
ct und liefert ein Ergebnis < 0, wenn gilt cs < ct, 0, wenn cs==ct und > 0, wenn gilt cs >
ct
size_t strlen(const char *cs) Liefert die Länge des Strings cs zurück. size_t ist dabei
ein unsigned integer Typ, der von sizeof zurückgeliefert wird.
Diese Funktionen und weitere sind in string.h deklariert und Bestandteil des Standards.
7.6.2 Funktionen für Zeichen
int isalnum(int c) isalpha(c) oder isdigit(c) ist wahr.
int isalpha(int c) islower oder isupper ist wahr.
int isctrl(int c) c enthält ein Control Zeichen (0–0x1F, 0x7F).
int isdigit(int c) c enthält eine Ziffer 0 − 9.
int islower(int c) c enthält einen Kleinbuchstaben.
int isupper(int c) c enthält einen Großbuchstaben.
7.6.3 Mathematische Funktionen
double sin(double x) sin(x)
double cos(double x) cos(x)
double exp(double x) ex
double log(x) ln x
double log10(double x) log10 x
double pow(double x, double y) xy
√
double sqrt(double x) x
KAPITEL 7. FUNKTIONEN
72
7.6.4 Datum und Uhrzeit
FT
clock_t clock(void) Liefert die Prozessorzeit seit start des Programms.
time_t time(time_t *tp Liefert die aktuelle Zeit und Datum.
size_t strftime(char* s, size_t smax, const char *fmt, const struct tm *tp)
Konvertiert nach ähnlichen Regeln wie printf Datum und Uhrzeit in einen lesbaren String, wie
z. B. Montag, 30.08.2004.
7.6.5 Hilfsfunktionen
double atof(const char *s)
int atoi(const char *s)
int rand(void)
void srand(unsigned int seed)
7.7 Aufruf-Parameter
DR
A
Sie können einem Programm auch schon beim Aufruf Parameter mitgeben. Das geht aber anders als mit
„normalen“ Funktionen. Der naive Ansatz würde einfach ein Programm mit einer main-Funktionen schreiben, in der Parameter deklariert sind. Versuchen wir doch einfach mal ein Programm zu schreiben, das mit
einem Parameter aufgerufen wird und diesen auf dem Bildschirm ausgibt („echoed“).
/* func01.c zum Umgang mit Parametern in der main Funktion
Status: Wrong*/
#include <stdio.h>
void main(char *echo)
{
printf("\n%s", echo);
}
Das lässt sich zwar prima umwandeln, das Ergebnis entspricht aber nicht unseren Vorstellungen: Statt
das Gewünschte zu tun bricht das Programm gnadenlos ab. Das ist auch verständlich, denn schließlich
haben wir es hier mit einem Teil der Schnittstelle zum Betriebssystem zu tun und da kann man sich schon
vorstellen, dass dies etwas komplizierte ist.
Dazu müssen wir in der main-Funktion zwei Parameter verwenden, die üblicherweise argc (argument
count) und argv (argument vector) genannt werden. Im ersten steht dann die Anzahl Parameter und in dem
anderen stehen die übergebenen Parameter zur Verfügung. Unser Programm echo.c sieht damit nun so
aus:
/* echo01.c zum Umgang mit Parametern in der main Funktion
Status: incomplete*/
#include <stdio.h>
void main(int argc, char *argv[])
{
int i;
for(i=1;i<argc;i++)
printf("%s%s",argv[i],(i < argc-1)?" " : "");
printf("\n");
}
Nun läuft alles wie gewünscht.
Sie werden noch Gelegenheit haben, diese Sache auszuprobieren.
7.8. HISTORISCHE AMERKUNGEN
73
7.8 Historische Amerkungen
7.9 Aufgaben
FT
Funktionen gibt es in vielen Programmiersprachen. Manchmal heißen Sie Unterprogram, manchmal Routine, Procedure usw. Die Ansichten darüber, wie stark man ein Programmsystem durch Funktionen oder
Unterprogramme modularisieren sollte, haben sich im Laufe der Zeit gewandelt. In C sehen Sie aber häufig große System, die aus vielen relativ kleinen Funktionen bestehen. Jede einzelne Funktion kann dabei
relativ einfach sein. Trotzdem verliert man angesichts der vielen Funktionen in einem System dann auch
leicht den Überblick.
1. Schreiben Sie eine Funktion char *stringcopy(char *str2, char *str1), die einen
String „str1“ auf einen String „str2“ kopiert.
2. Schreiben Sie eine Funktion, die alle Großbuchstaben eines Strings in Kleinbuchstaben konvertiert!
3. Schreiben Sie eine Funktion, die in einem String nach einem Zeichen sucht und die erste Stelle, an
der das Zeichen auftritt zurückliefert.
DR
A
4. Schreiben Sie eine Funktion encode, die einen String nach folgendem einfachen Schema verschlüsselt: Jeder Buchstabe wird durch den n-ten auf ihn folgenden ersetzt. Schreiben Sie auch eine Funktion decode, die den Ursprungsstring wieder herstellt.
5. Hier sollen sie üben, wie ein Wert eingelesen werden kann beziehungsweise (bzw.) wie ein Wert
mit der Tastatur dem Programm übergeben werden kann. Schreiben Sie ein Programm, dass eine
Körpergröße h in Meter und ein Gewicht w in Kilogramm erhält und den Body-Mass-Index (BMI)
berechnet und ausgibt:
w
BM I := 2
h
Fragen Sie nach der Ausgabe des Ergebnisses den Benutzer, ob er weitermachen möchte. Versuchen
Sie bitte, dies in einer Funktion zu tun, die Sie auch für andere Aufgaben verwenden können (wie
gesagt: Gute ProgrammiererInnen sind faul).
KAPITEL 7. FUNKTIONEN
DR
A
FT
74
FT
Kapitel 8
Der C Präprozessor
8.1 Übersicht
Was Sie in diesem Kapitel lernen, werden Sie an einigen Stellen dringend benötigen. Anderes sollten Sie
vergessen, sobald sie keinen Code mehr pflegen müssen, der diese Konstrukte verwendet.
DR
A
8.2 Lernziele
Dieses Kapitel hat nur drei Lernziele:
• Die Präprozessor-Befehle anwenden können, die Sie immer wieder benötigen werden.
• Die Gefahren des Präprozessors kennen.
• Den Präprozessor gezielt da nutzen können, wo es nicht anders geht und ihn ansonsten nicht benutzen.
8.3 Einbinden von Code
Den Präprozessor-Befehl #include haben wir bereits in Abschnitt 2.6 kennengelernt. Es gibe ihn in zwei
Varianten, die in der folgenden Abb. erläutert sind. Damit ist eigentlich schon fast alles über #include
#include <hugo.h>
\#include "hugo.h"
Die Datei wird implementierungsspezifisch
gesucht, z. B. im Standardpfad
Das aktuelle Verzeichnis wird nach hugo.h durchsucht
Wird sie nicht gefunden, so wird implementierungsspezifisch
weitergesucht
Abbildung 8.1: #include
gesagt. Nur noch eine kleine Anmerkung: Ob eine .h-Datei oder etwas anderes verwendet wird, um Bibliotheken zur Verfügung zu stellen ist nicht festgelegt, sondern wird dem Hersteller des Compilers überlassen.
Achten Sie ferner darauf, dass Präprozessor-Befehle nicht mit einem Semicolon beendet werden.
8.4 Definieren von Eigenschaften
Betrachten wir ein Programm, das Temperaturen in Fahrenheit in Celsius umrechnet ([KR88], p. 13)
75
KAPITEL 8. DER C PRÄPROZESSOR
76
FT
/* fahrenheit01.c Konvertieren von Temperaturen
in Fahrenheit in Celsius */
#include <stdio.h>
main()
{
int fahr;
for(fahr = 0;fahr <= 300;fahr = fahr + 20)
printf("%d %6.1f\n",fahr,(5.0/9.0)*(fahr-32));
}
Es ist kein guter Stil, Dinge wie die untere Grenze (0), die obere Grenze (300) und die Schrittweite (20) fest
im Programm zu „verdrahten“. Eine Möglichkeit, dies etwas flexibler zu gestalten bietet der PräprozessorBefehle #define. Er ermöglichte es (möglichst sinnvolle) Namen für irgendwelche Strings zu vergeben.
Hier werden die Namen LOWER, UPPER, STEP verwendet. Die Konventionen solche symbolische Namen
oder symbolische Konstanten in Großbuchstaben zuschreiben ist sehr weit verbreitet.
DR
A
/* fahrenheit02.c Konvertieren von Temperaturen
in Fahrenheit in Celsius, #define */
#include <stdio.h>
#define LOWER 0
#define UPPER 300
#define STEP 20
main()
{
int fahr;
for(fahr = LOWER;fahr <= UPPER;fahr = fahr + STEP)
printf("%d %6.1f\n",fahr,(5.0/9.0)*(fahr-32));
}
Obwohl dies weit verbreitet ist, gibt es an diesem Vorgehen Kritik, wie wir weiter unten sehen werden.
Manche sehen es als besser an, für diese Zwecke Konstanten zu verwenden, wie im folgenden Beispiel
fahrenheit03.c
/* fahrenheit03.c Konvertieren von Temperaturen
in Fahrenheit in Celsius, const */
#include <stdio.h>
const int LOWER = 0;
const int UPPER =300;
const int STEP =20;
main()
{
int fahr;
for(fahr = LOWER;fahr <= UPPER;fahr = fahr + STEP)
printf("%d %6.1f\n",fahr,(5.0/9.0)*(fahr-32));
}
Ihren Vorteil haben die durch #define definierten symbolischen Namen aber auch: So können Sie ganze
Funktionen etc. „umbenennen“. Ein einfaches Beispiel hierfür ist:
#define forever for(;;)
Solche Makros können auch Parameter erhalten:
#define max(A, B)
((A) > (B)? (A) : (B))
Dieses Makro wird inline aufgelöst: An jeder Stelle, an der max(A, B) wird ((A) > (B)? (A) :
(B)) ersetzt. Das kann böse Folgen haben:
Sehen wir uns mal an, was passiert, wenn wir folgendes Programm ausführen:
8.5. BEDINGTE BEFEHLE BZW. VERZWEIGUNGEN
77
FT
/* preproz01 Gefahren des Präprozessors */
#include <stdio.h>
#define max(A, B) ((A) > (B)? (A) : (B))
main()
{
int i =1;
int j =2;
printf("%d ist das Maximum von %d und %d",max(i++,j++),i,j);
}
Dieses Programm liefert die Ausgabe:
3 ist das Maximum von 2 und 4
Das Maximum wird korrekt berechnet, aber die größere Variable wird zweimal inkrementiert. Dies kommt
dadurch zu Stande, dass wirklich immer die Variable ersetzt wird.
Das Gegenstück zu #define ist #undef. Damit wird der Name wieder frei und kann anderweitig
verwendet werden.
8.5 Bedingte Befehle bzw. Verzweigungen
DR
A
In vielen Fällen kann man nicht einfach Makros definieren oder Dateien inkludieren, sondern muss dass
von Bedingungen abhängig machen. Dazu gibt es die Befehle #if, #elif (else if), #else, #endif
Soweit #define betroffen ist, verwendet man noch #ifdef(if defined) und #ifndef (if not defined).
Wollen Sie z. B. sicher stellen, dass eine Header-Datei nur einmal eingebunden wird, so erreichen Sie
das so
#if !defined(HDR)
#define HDR
/* Inhalt von hdr.h */
#endif
Eine alternative Schreibweise dafür ist
#ifndef HDR
#define HDR
/* Inhalt von hdr.h */
#endif
8.6 Einige Tücken
Sehen Sie sich bitte folgendes Programm genau an:
/******************************************
summe04.c berechnet die Summe zweier ganzer Zahlen
Wirklich?
******************************************/
#include <stdio.h>
#define summe differenz
int differenz(int a, int b);
/*int summe(int a, int b);*/
KAPITEL 8. DER C PRÄPROZESSOR
78
FT
int main(void)
{
int pa = 9;
int pb = 16;
printf("%d%s%d%s%d%s",pa," + ",pb," = ",summe(pa,pb),".");
return 0;
}
int differenz(int a, int b)
{
return (a - b);
}
Sind Sie sicher, dass Sie solche Dinge in einem größeren System mit hunderten von Header- und Implementierungsdateien sicher und schnell erkennen? Wenn nicht, sollten Sie mit dem Präprozessor vorsichtig
und sorgfältig umgehen. Wenn doch, sollten Sie Ihre Ansicht nochmal überdenken.
8.7 Historische Anmerkungen
DR
A
Der Präprozessor ist nach meiner Ansicht das (vielleicht) ärgerlichste Relikt aus der Steinzeit der Datenverarbeitung in C, C++ und letzendlich sogar in Java.
8.8 Aufgaben
1. Definieren Sie ein Macro swap(t,x,y, das zwei Argumente vom Typ t vertauscht.
FT
Kapitel 9
Zeiger (Pointer)
9.1 Übersicht
DR
A
An Zeigern, Neudeutsch Pointern scheiden sich die Geister. Für die Einen sind sie eine der wichtigsten
Ursachen für Programmierfehler, für die Anderen eine unverzichtbare Möglichkeit gut lesbaren und performanten Code zu schreiben. Ich rechne mich eher dem zweiten Lager zu. Der Einsatz von Zeigern ist
keine strategische Frage sondern lediglich eine taktische oder operationelle Frage. Ein guter Programmierer wird Zeiger dort verwenden, wo die notwendig oder sinnvoll ist. Zeiger sind Variablen, wie andere auch.
Entscheidend ist, dass es für jede Deklaration eines Datentyps in C eine entsprechende Deklaration eines
Zeigers gibt. Sie können von einer Variablen, die mit einem Datentyp deklariert ist, zum Zeiger kommen
und umgekehrt. Die Operatoren hierzu haben Sie bereits in Kap. 4 gesehen. In diesem Kapitel werden sie
deren praktischen Einsatz kennenlernen.
9.2 Lernziele
• Wissen, was ein Zeiger ist.
• Den Unterschied zwischen Wert und Adresse einer „Variablen“ erläutern können.
• Mit „Zeigern rechnen“ können.
• Den Adress- und den Dereferenzierungsoperator sicher anwenden können.
9.3 Definition von Zeigern
In Abschn. 2.8 haben Sie gesehen, wie eine Variable in C „aussieht“. Hier eine kurze Wiederholung. Durch
int eineGanzeZahl = 5;
wird eine Variable deklariert die den Typ int hat und es wird ihr der Wert 5 zugewiesen. Durch den
Adressoperator & & eineGanzeZahl erhalte ich die Adresse der Variablen eineGanzeZahl. Eine
Adresse vom Typ int wird durch
int *adresseEinerGanzenZahl;
wird ein Zeiger deklariert. In diesem Beispiel könnte man sich so einen Überblick über die Lage machen
/**********************************
pointer01.c integer pointer and adress
Status: ok.
**********************************/
79
KAPITEL 9. ZEIGER (POINTER)
80
Dieses Programm liefert die Ausgabe;
eineGanzeZahl:
&eineGanzeZahl:
adresseEinerGanzeZahl:
*adresseEinerGanzenZahl:
FT
#include <stdio.h>
void main(void)
{
int eineGanzeZahl = 5;
int *adresseEinerGanzenZahl =&eineGanzeZahl;
printf("\neineGanzeZahl: \t\t\t%d", eineGanzeZahl);
printf("\n&eineGanzeZahl: \t\t%X", &eineGanzeZahl);
printf("\nadresseEinerGanzeZahl: \t\t%X", adresseEinerGanzenZahl);
printf("\n*adresseEinerGanzenZahl: \t%d", *adresseEinerGanzenZahl);
}
5
12FEE0
12FEE0
5
DR
A
Auf diese Weise haben wir gleich die Formatangabe %X wiederholt, die die hexadezimale Ausgabe bewirkt.
Ich war es gewohnt, Dumps in einem Format zu lesen, in dem nur Großbuchstaben erscheinen. Es ist
wahrscheinlich, dass Sie so etwas nicht mehr werden machen müssen. Falls doch: Ihr Taschenrechner
oder PC kann Hex Rechnen oder Sie können sich ein kleines C-Programm schreiben, dass dies erledigt.
Vielleicht ergibt sich das sogar in diesem Kurs.
Fassen wir zusammen: In C können Sie sowohl Variablen mit einem bestimmten Datentyp deklarieren
als auch Variablen, die einen Zeiger auf einen Wert enthalten.
Aus diesen Definitionen ergeben sich sofort einige offene Punkte bzw. Fragen:
1. Ok, wenn ich eine Variable deklariere und definiere reserviere ich mir Speicher und weise diesem bei
Definition auch einen Wert zu. Was ist, wenn ich einen Zeiger deklariere? Woher weiß der Compiler,
wieviel Platz er braucht? Bei einem Zeiger auf ein int mag das offensichtlich sein, aber was ist bei
char *s oder char *s[]
2. Wie weise ich einem Zeiger einen Wert zu?
3. Wie stelle ich sicher, dass die Adresse, die ich einem Zeiger zuweise
• Frei ist?
• Einen Datentyp des gewünschten Typs aufnehmen kann?
Um die erste Frage zu beantworten müsste ich Ihnen zunächst erklären, wie Sie Speicher reservieren, sonst
würde Alles schief gehen. Ich beschränke mich deshalb in diesem Abschnitt auf den Umgang mit festem
Speicher, der wärend der Laufzeit des Programms nicht wächst oder schrumpft. Den Rest hole in Kap. 13
nach.
Die zweite Frage ist da schon einfacher zu beantworten, muss aber zunächst präzisiert werden. Soll
dem Zeiger selber ein Wert zugewiesen werden? Das muss man machen, wenn ein Zeiger deklariert aber
noch nicht definiert wurde. Wie das geht erfahren Sie in Kap. 13. Einem bereits initialisierten Zeiger einen
neuen Wert zuweisen? Er zeigt dann auf einen anderen Teil des Speichers. Sie müssen also sicherstellen,
dass er weiter auf etwas zu seinem Typ kompatibles zeigt. So etwas kann sehr wohl nützlich und sinnvoll
sein, aber Sie sollten dann immer genau wissen, was Sie tun, damit nichts schief geht. Wollen Sie dem
Speicherbereich, auf den der Zeiger verweist einen neuen Wert zuweisen? Nichts leichter als das. Nehmen
Sie den *-Operator und ab geht die Post. Das haben wir ja gerade schon gemacht, hier noch ein kleines
Beispiel:
/* zeiger02.c Elementares Beispiel für * Operator und
Zuweisung zu Zeigern
Status: ok*/
#include <stdio.h>
9.4. ZEIGERARITHMETIK
81
9.4 Zeigerarithmetik
FT
void main(void)
{
char *palindrom = "RELIEFPFEILER";
printf("\n%s",palindrom);
}
In C gibt es einen engen Zusammenhang zwischen Zeigern und Vektoren. Jede Operation, die mittels der
Indexe in einem Vektor durchgeführt werden kann, kann auch mit Zeigern ausgeführt werden. Sehen wir
uns das mal im Vergleich an:
DR
A
int a[] = {0,1,2,3,4,5,6,7,8,9};
int *za;
int x, y, z;
int i = 5;
za = &a[0];
za = a;
x = *za;
y = *(za+1);
z = *(a+i);
Was passier hier alles?
1. In der ersten Zeile wird ein Vektor von 10 ganzen Zahlen deklariert und mit den Zahlen von 0 bis 9
initialisiert.
2. In der nächsten Zeile wird zur Abwechslung ein Zeiger za auf int deklariert. Ein solcher Zeiger
zeigt auf eine Adresse im Speicher, ab wo ganze Zahlen gespeichert werden können.
3. Die nächsten beiden enthalten nur Deklarationen und eine Definition von später verwendeten Hilfsvariablen.
4. In der nächsten Zeile weise ich dem Zeiger za die Adresse des ersten Vektorelements a[0] zu.
5. Wer sich noch an Kap. 6 erinnert, sollte wissen, dass das auch einfacher geht, Ich hätte es einfach
so wie der nächsten Zeile machen können. Der Name eines Vektors ist ja ein Zeiger auf das erste
Element des Vektors.
6. Was bewirkt nun die Zuweisung x = *za? Daduch wird x = a[0], denn so haben wir ja gerade
eben zugewiesen.
7. Hier wird es jetzt spannender: za+1 setzt den Zeiger um eine Adresse weiter. Da der Zeiger typisiert
ist, „weiß“ er, wie lang die Daten sind, auf die er verweist. Daher kann er mit dieser Anweisung auf
das nächste Element verweisen. Das ist nach der erfolgten Zuweisung gerade a[1] und das wird jetzt
y zugewiesen.
8. Ganz analog wird in der nächsten Zeile das i-te (hier das 5-te) Element von a der Variablen z zugewiesen.
Der erfahrene C-Programmierer bevorzugt nach meinem Eindruck immer die Arbeit mit Zeigern gegenüber
der Arbeit mit Vektoren. Auch Sie sollten sich daran gewöhnen. Ich bin zwar überzeugt davon, dass die
Vektorschreibweise in vielen Fällen
• besser lesbar und einfacher verständlich ist.
• ein Compiler aus beiden Schreibweisen den gleichen ausführbaren Code erzeugen kann.
KAPITEL 9. ZEIGER (POINTER)
82
FT
Das würde mich dazu bewegen immer, die leichter lesbare Variante zu schreiben, aber ich bin ja auch
nicht in einer C-Umgebung aufgewachsen, sondern mit anderen Sprachen. Denken Sie bei Ihren eigenen
Programmen bitte an das, was ich Ihnen schon früher gesagt habe:
Programme, die ein Computer versteht, kann jeder Idiot schreiben.
Gute Programmierer schreiben Programme, die Menschen verstehen.
Auch C-Programmierer sind Menschen und man sollte ihre Marotten ernstnehmen.
Betrachten wir zum Abschluss einmal die folgende Code-Zeile, die ein wohlbekannte Funktion implementiert (vgl. [KR88], S. 105, [Str97]).
while (*s++ = *t++);
s und t seien dabei char *. Dies erscheint zunächst etwas kryptisch, ist aber ein Stil, den Sie in CProgrammen häufig sehen werden. Meine Ansicht zu diesem Code verrate ich Ihnen am Ende dieses Abschnitts. Sehen wir uns nun an, was dieser Code eigentlich tut.
1. Die while-Schleife wird so lange durchlaufen, bis der Ausdruck, der dort steht, ungleich 0 ist. Ist
er 0, so wird die Schleife verlassen. Etwas redundant, aber für Anfänger leichter zu lesen könnte man
also auch schreiben:
while ((*s++ = *t++)!= 0);
DR
A
2. Das bringt uns auf eine weitere Idee: Was hier abgefragt wird, ist ja offenbar das Stringendesymbol.
Wir können also auch schreiben
while ((*s++ = *t++) != ’\0’);
Damit haben wir das „Geheimnis“ dieses Codes fast schon gelüftet.
3. Die Postfix-Inkrementoperatoren werden erst nach Auswertung des Ausdrucks wirksam. Es wird
also der Wert an der alten Position von t auf die entsprechende Position von s gesetzt. Dieser Wert
ist auch der,m der mit ’\0’ verglichen wird. Also läuft das Ganze, bis der String t auf den String s
kopiert worden ist, einschließlich des Endesymbols.
Was Sie oben gesehen haben, ist also einfach die Implementierung der strcpy-Funktion. Der Stil ist in C
üblich. Sie sollten ihn aber nicht einfach kopieren. Diese Code-Zeile brauchen Sie nie zu schreiben, dafür
gibt es die Funktion strcpy, die Sie einfach aufrufen können.
9.5 Historische Anmerkungen
9.6 Aufgaben
FT
Kapitel 10
Strukturierte Datentypen
10.1 Übersicht
Die strukturierten Datentypen in C stellen einen ersten Schritt zu Klassen in objektorientierten Systemen
dar. Die wesentlichen Unterschiede zwischen einer struct in C und einer class in C++ sind:
• Eine class kann auch Funktionen als Elemente haben.
DR
A
• Die Sichtbarkeit von Elementen ist bei einer struct public und bei einer class private, wenn
nicht explizit etwas anderes angegeben wird.
Dieses Kapitel soll Ihnen auch helfen, Ihr Abstraktionvermögen zu schärfen. Viele Dinge lassen sich einfacher erledigen, wenn man einige der Dinge, die zu erledigen sind, systematisch strukturiert.1
Die wesentliche Anwendung von structs wird für Sie sein, den Inhalt von Datensätzen, die Sie mit
einem Programm aus einer Datei lesen einfach und mit einfacher Fehlerbehandlung lesen zu können.
10.2 Lernziele
• structs definieren und verwenden können.
• Mit structs als Parameter in Funktionen arbeiten können (call by value).
• Mit Zeigern auf structs umgehen können.
10.3 Deklaration strukturierter Datentypen
Eine struct ist eine Zusammenfassung einer oder mehrerer Variablen unter einem gemeinsamen Namen.
Sie definieren also so eine Art neuen Typs in C. Hier eine Reihe von Beispielen:
1. Um mit Punkte auf einem Bildschirm zu hantieren können Sie eine Struktur „Punkt“ definieren:
struct punkt
{
int x;
int y;
};
2. In der Elektrotechnik (natürlich nicht nur dort) benötigen Sie ab und zu komplexe Zahlen. Die könnten sie so definieren
1 Das ist eine grundlegende Einsicht der Erkenntnistheorie, es sprengt aber den Rahmen einer Programmiervorlesung, dies genau
auszuführen.
83
KAPITEL 10. STRUKTURIERTE DATENTYPEN
struct complex
{
double re;
double im;
};
FT
84
3. Adressen bestehen in vielen Ländern aus Straße, Hausnummer, Postleitzahl und Ort. Das könnte man
so in C nachbilden.
struct
{
char
char
char
char
};
adresse
*strasse;
*nummer;
*plz;
*ort;
DR
A
Die Deklaration einer struct definiert einen neuen Typ. Die Variablen in den geschweiften Klammern
heißen Elemente (englisch member oder data memmber) der struct. In den obigen drei Beispielen hat
etwa punkt die Elemente x und y, complex die Elemente re und im und adresse die vier Elemente
strasse, nummer, plz und ort.
Da structs Typen definieren, können Sie nach einer Deklaration einer struct Variablen mit diesem
Typ definieren und auch mit konstanten Ausdrücken initialisieren. Dies machen wir nun mal für die drei
eben deklarierte Beispiele.
1. Durch die Punkte anfang und ende wird eine Strecke definiert, die vom Punkt mit den Koordinaten (0,0) zu (10,10) geht.
struct punkt anfang = {0,0};
struct punkt ende
= {10,10};
2. Durch
struct complex i = {0, 1};
definieren wir eine Variable i, deren Wert die „imaginäre Einheit“ ist.
3. Das folgende ist die Adresse des Fuxing Campus der USST.
struct adresse usst = {"Fuxing Zhonglu","1195","200031","Shanghai"};
Da structs Typen definieren, können Sie auch wieder in struct Deklarationen verwendet werden. So
kann man aufbauend auf der struct punkt Strukturen für Strecken oder Kreise aufbauen.
struct strecke
{
struct punkt anfang;
struct punkt ende;
};
struct kreis
{
struct punkt mittelpunkt;
int radius;
};
Bei Variablen, wie wir sie bisher kennengelernt haben, können Sie die Initialisierung so machen, wie hier
beschrieben. Sie können dazu auch eine Funktion aufrufen, die diesen strukturierten Datentyp zurückliefert.
10.4. VERWENDUNG VON STRUKTURIERTEN DATENTYPEN
85
10.4 Verwendung von strukturierten Datentypen
FT
Haben Sie strukturierte Datentypen definiert, so können Sie mit ihnen arbeiten. Wie man lokale Variable
initialisiert haben Sie bereits im vorstehenden Abschnitt gesehen. Dazu wird einfach für jedes Element ein
konstanter Ausruck mitgeliefert. Anschließend können wir mit den Variablen arbeiten. An ein Element einer Struktur kommen wir mit dem Operator „.“, dem structure member operator, heran. So können wir dann
einfach die eben initialisierte Werte in gewohner Weise ausgeben, um uns persönlich davon zu überzeugen,
dass die Variablen so initialisiert wurden, wie wir es beabsichtigten.
printf("\nanfang = (%d,%d).",anfang.x,anfang.y);
printf("\nende = (%d,%d).",ende.x,ende.y);
printf("\ni = %lf + i%lf).",i.re,i.im);
printf("\nAnschrift USST:\n%s %s\n%s %s",\
usst.nummer,usst.strasse,usst.ort,usst.plz);
Und tatsächlich erhalten wir das gewünschte Ergebnis:
DR
A
anfang = (0,0).
ende = (10,10).
i = 0.000000 + i1.000000.
Anschrift USST:
1195 Fuxing Zhonglu
Shanghai 200031
Allerdings sehen wir nun, dass die dritte Zeile etwas irritierend zu lesen ist. Einerseits habe ich die Variable
i genannt. Das erschien auf den ersten Blick sinnvoll, weil der Wert ja die imaginäre Einheit i sein sollte.
So liest sich die Zeile etwas sonderbar. Ferner ist es in C üblich, mit Buchstaben wie i, j etc. int-Variablen
zu bezeichnen. Dies alles zeigt, dass ich die Varaible besser imag für imaginäre Einheit oder ähnlich
hätte nennen sollen. Gerade in größeren Projekten ist es wichtig gute Namenskonventionen konsequent
umzusetzen, damit die Programme lesbar bleiben.
Wie anderen Variablen können Sie auch Variablen strukturierter Datentypen andere Varaiblen des gleichen Typs zuweisen:
struct punkt a = ende, e=anfang;
struct complex imag = i;
struct adresse usstfuxing = usst;
Mit dem sizeof-Operator können Sie feststellen, wie viele Bytes die von Ihnen definierten strukturierten Datentypen belegen.
printf("\n\t\tBytes");
printf("\npunkt:\t\t%d",sizeof(struct
printf("\ncomplex:\t%d",sizeof(struct
printf("\nadresse:\t%d",sizeof(struct
printf("\nstrecke:\t%d",sizeof(struct
printf("\nkreis:\t\t%d",sizeof(struct
Hier das Ergebnis:
punkt:
complex:
adresse:
strecke:
kreis:
Bytes
8
16
16
16
12
punkt));
complex));
adresse));
strecke));
kreis));
86
KAPITEL 10. STRUKTURIERTE DATENTYPEN
FT
Dieses Ergebnis ist bei den Typen punkt, complex, strecke und Kreis unmittelbar einleuchtend.
Die Länge ergibt sich durch Addition der Längen der Elemente. Bei adresse stutzen Sie vielleicht zunächst: Die Strings der Variablen usst sind doch viel länger als 16 Bytes. Hier müssen Sie sich an die
Kap. 6 und 9 zurückerinnern. Stringvariablen sind Zeiger. Ein Zeiger enthält eine Adresse. Adressen sind
auf einem 32-Bit-Rechner 4 Bytes lang und damit ist auch dieses Ergebnis klar. Außer den bisher vorgeführten Operationen können Sie von einer Variablen eines strukturierten Datentyps nur noch die Adresse
über den Adressoperator & bekommen. Sie kann nicht in Vergleichsoperationen verwendet werden. Solche Variablen können aber auf verschiedene Arten an Funktionen übergeben werden, wie Sie im nächsten
Abschnitt sehen werden.
10.5 Strukturierte Datentypen, Funktionen und Zeiger
Haben wir zwei Punkte a = (a1 , a2 ) und b = (b1 , b2 ), so können wir nach dem Lehrsatz des Pythagoras
ihren Abstand leicht ermitteln:
p
Abstand = ((b1 − a1 )2 + (b2 − a2 )2 )
DR
A
In Abschn. 7.6 haben Sie die C-Funktion sqrt gesehen. Mittels dieser Funktion können wir die Abstandsberechnung für zwei Punkte leicht implementieren. Ich werde das zwar nicht ganz ausführen, aber im
Ansatz so machen, wie Sie bei ernsthaften C-Projekten vorgehen würden.
Die Deklaration des strukturierten Datentyps Punkt schreibe ich in eine Header-Datei punkt.h. In
diese Datei schreibe ich auch alle Deklarationen der Funktionen, die ich für die Arbeit mit diesem Datentyp
schreibe. Die Implementierungen dieser Funktionen kommen in eine Datei punkt.c. In einem weiteren
Programm teste ich dann, was ich dort geschrieben habe.
Außer der Funktion abstand schreibe ich mir gleich eine Funktion machpunkt, die mir aus zwei
ganzen Zahlen einen Punkt erstellt. Hier das Zwischenergebnis:
#ifndef PUNKT
#define PUNKT
/*punkt.h Deklarationen für die struct punkt*/
struct punkt
{
int x;
int y;
};
double abstand(struct punkt a, struct punkt b);
struct punkt machpunkt(int x, int y);
#endif
/*punkt.c Implementierung von Funktionen für struct punkt
Status: incomplete */
#include "punkt.h"
#include <math.h>
double abstand(struct punkt a, struct punkt b)
{
return sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));
}
struct punkt machpunkt(int x, int y)
{
struct punkt temp;
temp.x = x;
temp.y = y;
return temp;
}
10.5. STRUKTURIERTE DATENTYPEN, FUNKTIONEN UND ZEIGER
87
Hier wieder wie gewohnt einige Erläuterungen dazu.
FT
1. Die Header-Datei punkt.h beginnt mit einem Präprozessor Befehl #ifndef PUNKT. Dieser Befehl überprüft, ob Konstant PUNKT bereits definiert wurde. Ist dies des Fall, so geht es nach dem
#endif weiter, es werden also gar keine Deklarationen aus punkt.h übernommen, wenn irgendwo #include s"punkt.h" steht. Ist PUNKT noch nicht definiert, so wird als erstes PUNKT definiert und dann alle Zeilen in die includende Datei übernommen. Auf diese Weise wird verhindert,
das punkt.h mehrfach includet wird.
2. Anschließend wird der strukturierte Datentyp punkt deklariert und die beiden Funktionen abstand
und machpunkt.
3. abstand gibt double zurück, denn obwohl wir Punkte mit ganzzahligen Koordinaten betrachten,
ist der Abstand zweier Punkte nicht notwendig ganzzahlig.
4. In machpunkt wird eine temporäre Variable vom Typ punkt deklariert, ihren Elementen werden die übergebenen Parameter zugewiesen. Anschließend wird die so nun auch definierte Variable
zurückgegeben.
DR
A
In dem Beispiel der Abstandsberechnung habe ich der Funktion die Strukturen übergeben. Das ist aber nur
eine der Möglichkeiten.
Eine andere Möglichkeit wäre es, die einzelnen Elemente des strukturierten Datentyps separat zu übergeben. Dies bringt uns aber etwas in Konflikt mit dem eigentlichen Ziel der strukturierten Datentypen.
Wir wollen mit diesen ja gerade die Komponenten zusammen behandeln können und gerade nicht jede
einzelne. Außerdem steht es in Widerspruch zum bewährten Prinzip der Kapselung. Diese ist zwar bei
structs noch nicht besonders ausgeprägt, wohl aber bei der Weiterentwicklung des Konzepts zu Klassen
z. B. in C++. Die einzelnen eines strukturierten Datentyps zu übergeben, kann meines Erachtens höchstens
in Implementierungsdateien für structs sinnvoll sein, nicht in Programmen, die diese structs nutzen.
Eine weitere Möglichkeit besteht darin einen Zeiger auf die struct zu übergeben. Dies ist insbesondere für große Strukturen oft effizienter. Ich zeige Ihnen as an einer einfachen Funktion für den strukturierten Datentyp adresse. Muss man in mehreren Programmen eine Adresse drucken, so schreibt man sich
dafür besser eine Funktion. Die macht das dann einheitlich und Sie müssen nicht jedesmal eine mehr oder
weniger komplexe printf-Konstruktion eintippen oder kopieren. Hier das Ergebnis
/*struct03.c Einige Beispiele für structs*/
#include <stdio.h>
struct adresse
{
char *strasse;
char *nummer;
char *plz;
char *ort;
};
int print(const struct adresse* adr);
void main(void)
{
struct adresse usst = {"Fuxing Zhonglu","1195","200031","Shanghai"};
print(&usst);
}
int print(const struct adresse *adr)
{
return printf("\n%s %s\n%s %s",\
(*adr).nummer,(*adr).strasse,(*adr).ort,(*adr).plz);
}
und die Erläuterungen dazu.
88
KAPITEL 10. STRUKTURIERTE DATENTYPEN
1. Die Deklaration der Struktur kennen wir ja bereits.
FT
2. Danach folgt die Deklaration der Funktion print. Ich habe hier einen englischen Namen gewählt, um
die Ähnlichkeit mit der C-Funktion printf zu betonen. Wie printf gibt auch print die Anzahl
ausgegebener Zeichen zurück.
3. Als einzigen Parameter erhält die Funktion einen Zeiger auf eine adresse.
4. Der Parameter ist als const deklariert. Auch dies entspricht der Deklaration in printf. Sie können
sich höchstens fragen, ob die Adresse, oder der ab dieser Adresse gespeicherte Inhalt durch print
nicht geändert werden kann. Die Adresse zu ändern ist nicht sinnvoll, also kann wohl nur der Inhalt
vor irrtümlicher Verwendung geschützt sein. Ausprobieren liefert folgendes Ergebnis: Versuchen Sie
in print die übergebene Adresse zu verändern, etwa durch eine neue Zuweisung, so bekommen
Sie die Compiler-Fehlermeldung.
error C2166: L-Wert gibt ein konstantes Objekt an
DR
A
Das ist eher eine Auskunft für Informatiker als für Programmierer. Mit L-Wert ist die linke Seite
einer Zuweisung gemeint. Steht dort ein Parameter der mit const deklariert wurde, so weist der
Compiler diesen Versuch entrüstet zurück.
5. In der main-Funktion initialisieren wir eine Variable mit einer bekannten Adresse und rufen dann
print auf.
6. Da print eine Adresse erwartet, müssen wir vor die zu übergebende Varaible den Adress-Operator
& schreiben.
7. Die Implementierung der Funktion print enthält nur einen Befehl return printf( ....
8. Dadurch wird zunächsteinmal die Anzahl ausgegebene Zeichen zurückgeliefert, wie es oben beschriebne wurde.
9. Der Formattierungsstring von printf sieht genauso aus wie in dem obigen Beispiel.
10. Stolpern werden Sie über die Angaben der Elemente einer Adresse. Aber auch das ist halb so
schlimm. Der Parameter heißt adr und ist ein Zeiger. *adr ist also das was an der Stelle steht,
auf die der Zeiger verweist. Sicherheitshalber klammer ich so etwas immer ein: (*adr). Die Klammern sind in diesem Fall auch nötig, ohne die Klammern würde das nicht funktionieren, da der
Punkt-Operator stärker bindet als der *-Operator. Aber dafpr gibt es ja gerade die Klammerpaare.
Dann folgt der Punkt-Operator mit dem wir auf die Elemente einer Struktur zugreifen können und
der Name des Elements und das war’s dann auch schon.
Zugriffe auf die Elemente einer Struktur, auf die man einen Zeiger übergeben bekommen hat, sind so
häufig, das es dafür einen eigenen Operator gibt, den „structure pointer operator“, oder „Stuktur Zeiger
Operator“ „->“. Mit Hilfe dieses Operators schreibe ich nun den Befehl in print in der in C üblichen
Form um:
int print(const struct adresse *adr)
{
return printf("\n%s %s\n%s %s",\
adr->nummer,adr->strasse,adr->ort,adr->plz);
}
10.6. HISTORISCHE ANMERKUNGEN
89
10.6 Historische Anmerkungen
FT
Ich sehe struct als das Konstrukt an, das den Übergang von C su objektorientierten Sprachen, wie C++
maßgeblich vorbereitet hat. Wenn Sie von C auf C++ umsteigen, gibt es meines Wissens nur zwei (kleine)
Unterschiede gegenüber Klassen zu beachten:
1. Eine Klasse (class) kann im Unterschied zu einer struct auch eigene Funktionen, nun Operationen
oder in C++ member functions genannt.
2. Die Default-Sichtbarkeit der Elemente ( Attribute, Operationen und Oberklassen) in C++ ist public.
Für eine Klasse ist die Default-Sichtbarkeit privat.
Zwischen structs in C und C++ gibt es aber einen deutlichen Unterschied: In C können Sie die Operatoren überladen. So könnten Sie viele der Operationen, die wir auf structs als Funktionen realisiert
haben, durch Operatoren, wie „+“, „+=“ etc. realisieren.
DR
A
10.7 Aufgaben
KAPITEL 10. STRUKTURIERTE DATENTYPEN
DR
A
FT
90
FT
Kapitel 11
Umwandlung von Datentypen
11.1 Übersicht
11.2 Lernziele
DR
A
11.3
11.4 Historische Anmerkungen
11.5 Aufgaben
91
KAPITEL 11. UMWANDLUNG VON DATENTYPEN
DR
A
FT
92
FT
Kapitel 12
Bit-Manipulationen
12.1 Übersicht
12.2 Lernziele
DR
A
12.3
12.4 Historische Anmerkungen
12.5 Aufgaben
1. Schreben Sie bitte Funktionen isEven(int i) und isOdd(int i), die möglichst effizient ermitteln, ob
eine ganze Zahl vom Typ int gerade (even) bzw. ungerade (odd) ist!
93
KAPITEL 12. BIT-MANIPULATIONEN
DR
A
FT
94
FT
Kapitel 13
Dynamische
Speicherplatzververwaltung
13.1 Übersicht
DR
A
Bisher haben wir Speicher statisch reservert. Eine Variable wurde zu Beginn eines Blocks vollständig
deklariert und definiert. In vielen Fällen ist das aber nicht möglich:
• Wir wissen nicht, wieviele Zeichen oder Zeilen der Benutzer eingeben will.
• Wir wissen nicht, wie groß eine Datei ist, die wir mit dem Programm einlesen müssen.
In beiden Situationen ist es unsinnig dafür große Speicherbereiche statisch zu reservieren:
• In vielen Fällen belegt man so viel zu viel Speicher, der eigentlich gar nicht benötigt wird und so
anderer Nutzung entzogen wird.
• Irgendwann wird diese statische Grenze doch überschritten werden.
Sie lernen in diesem Kapitel Speicher dynamisch zu reservieren und wieder freizugeben. Das erfordert ein
bischen Disziplin, aber das haben Sie an früheren Beispielen ja auch schon erlebt. Um diesw tun zu können,
werden wir aber sehr viele der in den früheren Kapiteln eingeführten Konzepte benötigen. Darüber hinaus
enthält dieses Kap. in Abschn. 13.3 eine Übersicht über die Speicherarten und -klassen von.
13.2 Lernziele
• Die verschiedenen Arten von Speicher kennen, die in C-Programmen vorkommen.
• Speicher dynamischen reservieren können.
• Speicher verwalten können.
• Speicher wieder freigeben können.
• Mit möglichst wenigen Ressource-Leaks programmieren können.
13.3 Verschiedene Arten von Speicher
Sehen wir uns mal an, wie ein C-Programm intern aufgebaut ist. Bisher haben wir ja nur Variablen am
Anfang eines Blocks und einige Deklarationen und Definitionen in Header-Dateien kennengelernt, die der
Compiler mitliefert. Aber wo werden diese Daten nun tatsächlich abgelegt. Ich will Ihnen hier keine ganz
95
KAPITEL 13. DYNAMISCHE SPEICHERPLATZVERVERWALTUNG
FT
96
Gesamtprogramm
Modul 1
lokal
modulglobal
lokal
Funktion
DR
A
Funktion
programmglobal
Modul 2
Funktion lokal
modulglobal
Dynamischer Speicher
Abbildung 13.1: Speicherstruktur eines C-Programms
13.3. VERSCHIEDENE ARTEN VON SPEICHER
97
FT
genaue Darstellung mit vielen technischen Details des Rechneraufbaus geben. Der Einfachheit halber belasse ich es bei einer stark vereinfachten Darstellung. Schematisch zeigt dies Abb. 13.1. Denken Sie bei
einem Modul bitte an an eine .c-Datei die zu einer .obj umgewandelt wird und bei dem Gesamtprogramm an die durch den Linker erstellte .exe-Datei. Auf Englisch heißt das, was in dieser Abbildung
als Modul bezeichnet wird „translation unit“, Umwandlungseinheit. Das ist eine Menge von .c- und .hDateien, die alleine umgewandelt werden kann. In einem solchen Programm kommen verschiedene Arten
von Speicher vor. Je nach Art hat der Speicher eine unterschiedlich lange „Lebensdauer“. Es gibt auch
Unterschiede, von welchen Teilen des Programms auf welche Speicherteile zugegriffen werden kann und
ob die Speicherinhalte und ggf. von woher aus, geändert werden können. Diese verschiedene Arten von
Speicherplatz, werde ich Ihnen nun von von „innen“ (lokal) nach „außen“ (global) vorstellen.
Lokale Variablen Dies sind die Variablen, die Sie am Anfang eines Blocks innerhalb einer C-Funktion,
z. B. der main-Funktion deklarieren. Ohne weitere Vorkehrungen werden sie und ihre Werte werden
am Anfang des Blocks angelegt und beim Verlassen des Blocks wieder zerstört.
Modulglobale Variable Das sind Variablen, die in einem Modul, z. B. einer .c oder .h-Datei außerhalb
einer Funktion deklariert werden. Mit diesen Variablen können alle Funktionen des eines Moduls
arbeiten.
Globale Variable Diese Variablen stehen allen Funktionen eines Programmsystems zur Verfügung.
DR
A
Sowohl globale als auch modulglobale Variablen sind schon bei Programmstart vorhanden und bleiben bis
Programmende bestehen.
Wie lange eine Variable „lebt“, wer darauf zugreifen darf wird durch die Schlüsselworte const,
volatile, external, static, auto und register festgelegt, die wir nun im einzelnen diskutieren.
const Wird der Deklaration einer Variablen const vorangestellt, so kann der Wert der Variablen vom
Programm nicht verändert werden. So könnte man die Definition von PI auch so schreiben
const double PI = 3.14159265358979323846;
volatile Wird der Deklaration einer Variablen volatile vorangestellt, so kann der Wert der Variable von außen geändert werden. Das kann z. B. durch Interrupts geschehen. Die Deklarationen
volatile und const können auch zusammen eingesetzt werden.
external Externe Variablen sind zunächst einmal solche, die außerhalb einer Funktion deklariert oder
definiert werden. Sie stehen ohne weitere Maßnahmen nicht für andere Quelldateien zur verfügung
sondern nur für die, die sie definiert haben. Sollen solche Variablen aber nach ihrer Deklaration
und vor ihrer Definition verwendet werden, so muss der Deklaration das Schlüsselwort external
vorangestellt werden. In einem solchen Fall wäre es möglich, dass die die Deklaration in einer Datei
und die Definition in einer anderen steht. Externe Variablen können nur mit Konstanten initialisiert
werden.
static Wieder etwas ganz anderes erledigt die Deklaration static für uns. Diese sorgt dafür, dass
die Variable einer anderen Speicherklasse angehört. Geben Sie nichts an, so ist die Speicherklasse
automatic und es handelt sich einfach um Variablen, wie wir sie bisher kennengelernt haben. Das
Schlüsselwort static kann bei lokalen Variablen innerhalb eines Blocks und bei globalen Variablen außerhalb eines Blocks verwendet werden. Innerhalb eines Blocks bewirkt das Schlüsselwort
static, das der Speicher beim Verlassen des Blocks nicht freigegeben wird. Er steht so lange zur
Verfügung, bis das Gesamtprogramm beendet wird. Variablen, die außerhalb eines Block deklariert
werden, sind automatisch „static“, der Speicher wird also erst dann freigegeben, wenn das Gesamtprogramm oder das Modul beendet wird. Bei derartigen Variablen und auch Funktionen bewirkt das
Schlüsselwort static, dass die Elemente nur für Funktionen innerhalb der Datei, in denen ihre
Deklaration steht, sichtbar und damit ggf. bearbeitbar sind.
KAPITEL 13. DYNAMISCHE SPEICHERPLATZVERVERWALTUNG
98
FT
auto Elemente, die innerhalb eines Blocks deklariert werden sind automatic, wenn nichts anderes angegeben wird, d. h. sie werden automatisch beim Verlassen des Blocks wieder vernichtet.
register Objekte, die als register deklariert werden sind automatic und der Compiler sollte das
Schlüsselwort als Anregung nehmen, die Objekte in schnellen Maschinenregistern zu speichern. Wie
auch Sie, ist der Compiler frei diesen gut gemeinten Rat in den Wind zu schlagen.1
13.4 Speicherreservierung und Freigabe
C stellt in stdlib.h einige Funktionen zur Verfügung, um vom Betriebssystem Speicher anzufordern,
zu verwalten und wieder freizugeben. Dies geschieht in einem Speicherbereich der Stack2 genannt wird.
Da ist zunächst die Funktion malloc, „memory allocation“, mit dem folgenden Prototyp:
void *malloc(unsigned size);
DR
A
Diese Funktion reserviert einen zusammenhängenden Speicherbereich und liefert einen Zeiger auf dessen
Anfang zurück. Der Typ der Daten die dort gespeichert werden können ist void. Sie können dort also
beliebige Daten ablegen. Achten Sie darauf, dass ein zusammenhängender Speicherbereich angefordert
wird. Selbst wenn insgesamt noch soviel Speicher verfügbar ist, wie Sie benötigen, kann es sein, dass es
kein hinreichend großes zusammenhängendes Stück mehr gibt. In diesem Fall kann Ihnen auch malloc
nicht helfen und liefert den Zeiger 0 (NULL) zurück. In C ist garantiert, dass 0 nie eine gültige Adresse
sein kann. Nach einem Aufruf von malloc müssen Sie also immer überprüfen, ob er auch erfolgreich war.
Achten Sie bitte außerdem darauf, dass dieser Speicher nicht initialisiert ist. Wenn Sie also nicht selber in
Ihrem Programm sinnvolle Werte zuweisen, so steht dort irgendwelcher „Müll“!
Nun endlich ein kleines Beispiel.
/* alloc01.c Ein erstes Beispiel für dynamische Speicherreservierung
Status: incomplete*/
#include <stdio.h>
#include <stdlib.h>
char *stringToRead;
void main(void)
{
int i;
printf("Wieviele Zeichen wollen Sie eingeben?");
scanf("%d",&i);
stringToRead = malloc((++i)*sizeof(char));
if (stringToRead!=0)
{
printf("\nBitte geben Sie jetzt Ihren String ein.");
scanf("%s",stringToRead);
printf(stringToRead);
free(stringToRead);
}
else
printf("\n Nicht mehr genug Speicher vorhanden.\nKaufen Sie sich einen\
groesseren Rechner\n oder beenden Sie einige Programme.");
}
Hier wieder die gewohnten Erläuterungen zu diesem Programm.
1. Zunächst werden wie schon oft die Standard Header-Dateien stdio und stdlib eingebunden.
Letztere für die Funktionen zur dynamischen Speicherverwaltung (malloc und free).
1 Vielleicht
2 Auf
wusstes Sie dass noch nicht: Ratschläge sind immer auch Schläge.
Deutsch heißt ein Stack „Keller“. Ich weigere mich diese Übersetzung zu verwenden.
13.4. SPEICHERRESERVIERUNG UND FREIGABE
99
FT
2. Anschließend definiere ich einen Zeiger auf einen String stringToRead. Derartig lange Namen
sind in C nicht sehr verbreitet, sondern erst mit C++ und Java populär geworden. Ich versuche aber
immer Namen zu nehmen, die klar sagen, wozu die Variable oder die Funktion da ist. Das spart viele
Kommentare, die dann noch viel länger wären.
3. Dann zeige ich grenzenloses Vertrauen in den Anwender und bitte ihn, anzugeben, wieviele Zeichen
er oder sie eingeben möchte.
4. Sträflicherweise prüfe ich überhaupt nicht ab, ob eine sinnvolle, zumindest, numerisch positive, Eingabe gemacht wurde.
5. In der nächsten Zeile reserviere ich Speicher für die einzugebenden Zeichen.
6. In der nächsten Zeile finden Sie (vielleicht zum ersten Mal) eine Fehlerbehandlung. Die ist hier so
wichtig, dass ich nicht einmal Programme zur Erläuterung der Speicherverwaltung ohne diese zeige!
Wenn genug Speicher auf dem Stack vorhanden war, geht die Verarbeitung weiter, anderfalls wird
das Programm mit feundlichem Bedauern und einem (guten?) Rat beendet.
7. Im Erfolgsfall haben wir genug Speicher zugeteilt bekommen und können die Eingabe wie gewohnt
einlesen.
DR
A
8. Anschließend gebe ich den String zur Kontrolle aus. Dabei mache ich mir zu Nutze, dass printf
als Parameter nur const char * erwartet. Ich kann also einfach den namen des Strings angeben.
Nach den Erläuterungen aus Abschn. 6.6 auf Seite 59 ist das ja gerade ein char *.
9. Anschließend gebe ich mit der Funktion free den reservierten Speicher wieder frei.
Viel mehr kann man zu diesem kleinen Programm wohl kaum sagen.
Verwenden Sie malloc so müssen Sie wissen, wieviel Speicher Sie brauchen. Haben Sie einige Objekte, für die Sie Speicher brauchen, so ist es manchmal praktischer, die Funktion calloc zuverwenden.
void *calloc(size_t n, size_t size);
Wie schon an anderer Stelle erwähnt ist size_t einfach ein unsigned Typ, der mit Größen von Speicherbereichen zu tun hat und deshalb quasi als Synonym für unsigned erfunden wurde, um diese Tatsache
auch explizit zum Ausdruck zu bringen.
calloc reserviert untypisierten Speicher für n Objekte der Größe size, also insgesamt n·size Bytes.
Im Unterschied zu malloc initialisiert calloc den Speicher mit binär 0. Hier ein ganz kleines Beispiel:
/* alloc02.c Ein weiteres Beispiel für dynamische Speicherreservierung
Status: incomplete*/
#include <stdio.h>
#include <stdlib.h>
char *stringToRead;
void main(void)
{
int i;
printf("Wieviele Zeichen wollen Sie eingeben?");
scanf("%d",&i);
stringToRead = calloc(i,sizeof(char));
if(stringToRead)
{
int j;
for(j=0;j<i;j++)
printf("%X",*(stringToRead+j));
printf("\nBitte geben Sie jetzt Ihren String ein.");
scanf("%s",stringToRead);
printf(stringToRead);
100
KAPITEL 13. DYNAMISCHE SPEICHERPLATZVERVERWALTUNG
}
FT
free(stringToRead);
}
else
printf("\n Nicht mehr genug Speicher vorhanden.\n Kaufen Sie sich einen\
groesseren Rechner\n oder beenden Sie einige Programme.");
Die Ausgabe zeigt, dass der Speicher nun tatsächlich mit Nullen initialisiert wird.
13.5 Historische Anmerkungen
Die Deklaration static finden Sie auch in den objektorientierten Programmiersprachen C++ und Java.
Dort bezeichnet sie sogenannte Klassenelemente. Für diese gibt es nur einen gemeinsamen Wert („Instanz“)
für alle Objekte der Klasse. Dies ist eine konsistente Erweiterung des Konzepts aus C: static deklarierte
Elemente sind ebenfalls in einem gewissen Sinne global. In einem objektorientierten Kontext ist die zu
betrachtende Einheit nicht mehr die Programm-Quellcodedatei, wie in C, sondern die Klasse. Eine Klasse
wird in Java ja auch in jeweils einer Datei definiert. Insofern hat static hier auch in diesem Sinne die
gleiche Bedeutung, wie in C.
DR
A
13.6 Aufgaben
1. Schreiben Sie eine Funktion, die zwei Strings zu einem neuen zusammenfügt, indem Sie neuen
Speicherplatz reserviert, die beiden Strings zu einem neuen String in dem neuen Speicherbereich
zusammenkopiert.
Dateizugriff
14.1 Übersicht
14.2 Lernziele
DR
A
14.3 Streams
FT
Kapitel 14
14.4 Elementare Dateioperationen
14.5
14.6 Historische Anmerkungen
14.7 Aufgaben
101
KAPITEL 14. DATEIZUGRIFF
DR
A
FT
102
FT
Kapitel 15
Anwendungen: Listen
15.1 Übersicht
DR
A
Listen sind eine ganz wichtige und grundlegende Datenstruktur, die es ermöglicht viele wichtige andere
Strukturen, wie Stacks (LIFO — last in first out Speicher), Queues (FIFO — first in last out Speicher) und
viele weitere effizient zu implementieren.
15.2 Lernziele
• Einfach und doppelt verkettete Listen implementieren können.
• Listen mit antizipativer Indizierung implementieren können.
15.3
15.4 Historische Anmerkungen
15.5 Aufgaben
103
KAPITEL 15. ANWENDUNGEN: LISTEN
DR
A
FT
104
FT
Anhang A
Code Konventionen für C
A.1
Übersicht
DR
A
Dieses Kapitel enthält bewährte Regeln, nach denen Code in der Programmiersprache C geschrieben wird.
Viele davon sind nicht C-spezifisch sondern gelten sinngemäß auch für andere Programmiersprachen. Diese
Regeln haben nicht nur eine formale Bedeutung, sondern fördern auch die Qualität des Codes.
A.2
Lernziele
• C Programme strukturieren können.
• Regeln zur Namensbildung in C beherrschen.
A.3
Programmstruktur
Ganz feste Regeln für die Strukturierung von C Programmen gibt es nicht.
A.4
Namen
Variablennamen: Kleinbuchstaben Kein _ am Anfang von Variablennamen und Funktionsnamen, wegen
Verwechslungsgefahr mit Systemvariablen und Bibliotheksfunktionen, die oft mit diesem Zeichen beginnen. Konstanten: Großbuchstaben
A.5
A.6
Historische Anmerkungen
105
ANHANG A. CODE KONVENTIONEN FÜR C
DR
A
FT
106
Aufgaben
FT
Anhang B
1. In welchen Header-Dateien werden die Grenzen für die numerischen Datentypen definiert?
2. Schreiben Sie bitte ein C-Programm, dass die minimalen und maximalen Werte für die numerischen
Datentypen aus den Header-Dateien ausgibt!
DR
A
3. Schreiben Sie bitte ein C-Programm, dass die minimalen und maximalen Werte für die numerischen
Datentypen in der jeweiligen Installation durch geeignete Operationen ermittelt!
B.1 Rechner
In [KR88] finden Sie Code für einen einfachen Rechner, der arithmetische Ausdrücke in rechtspolnischer
Notation auswertet (main.c, calc.h,stack.c, getop.c, getch.c). Hierzu die folgenden Aufgaben:
1. Modifizieren Sie bitte getop so, das es ungetch nicht benutzt! Hinweis: Verwenden Sie eine geeignete
statische Variable. ([KR88], ex 4-11)
2. Ergänzen Sie bitte den Rechner so, dass er auch den modulo-Operator „%“ verwenden kann. Achten
Sie auf positive und negative Zahlen! ([KR88], ex 4-3)
3. Ergänzen Sie bitte den Stack um Funktionen um das erste Elemente auszugeben ohne es zu entfernen,
es zu duplizieren und eine um die beiden obersten Elemente zu vertauschen! ([KR88], ex 4-4)
4. Ergänzen Sie bitte den Rechner um die Funktionen sin, cos, exp, log und pow! ([KR88], ex 4-5)
5. Die Funktionen getch und ungetch behandeln EOF korrekt. Warum? Ändern Sie die Funktionen bitte
so, dass sie sicehr funktionieren! ([KR88], ex 4-9)
6. Statt zeichenweise kann der arithmetische Ausdruck auch als eine Zeile eingelesen werden. Ändern
Sie den Code bitte auf diese Vorgehensweise um! ([KR88], ex 4-10)
107
ANHANG B. AUFGABEN
DR
A
FT
108
FT
Anhang C
Lösungsvorschläge
C.1
1. In welchen Header-Dateien werden die Grenzen für die numerischen Datentypen definiert?
Antwort: Zumindest in der MinGW Installation sind dies die Dateien limits.h, float.h und stdint.h.
DR
A
2. Schreiben Sie bitte ein C-Programm, dass die minimalen und maximalen Werte für die numerischen
Datentypen aus den Header-Dateien ausgibt!
Lösung: Siehe limits01.c.
3. Schreiben Sie bitte ein C-Programm, dass die minimalen und maximalen Werte für die numerischen
Datentypen in der jeweiligen Installation durch geeignete Operationen ermittelt!
Lösung: Siehe limits02.c.
4.
109
ANHANG C. LÖSUNGSVORSCHLÄGE
DR
A
FT
110
FT
Anhang D
Lösungsvorschläge für ausgewählte
Aufgaben
D.1 Kapitel 1
DR
A
D.1.1 tunix.c
Die Lösung dieser Aufgabe ist Bestandteil des Scripts.
D.2 Kapitel 2
D.2.1 Namensdeklarationen
Hier folgen einige Deklarationen für Variablen in C. Geben Sie bitte an, ob die Namen in diesen Deklarationen gültig sind! Begründen Sie bitte Ihre Antwort!
1. int 1zweidrei;
2. int einszwei3;
3. char Eins_zwei_drei;
4. char Eins zwei drei;
5. char _;
6. char [] void;
7. double pi;
8. Double E;
Lösungsvorschlag:
1. Der Name „1zweidrei“ ist ungültig, da er mit einer Ziffer („1“) beginnt.
2. Der Name „einszwei3“ ist gültig, da er nur aus Buchstaben aus dem Bereich a–z und einer Ziffer am
Ende besteht, also nicht an erster Stelle, wo eine Ziffer nicht zulässig ist.
3. Der Name „Eins_zwei_drei“ ist gülti, da er nur aus Buchstaben a–z, A–Z, sowie _ besteht, die alle
zulässig sind
4. Der Name „Eins zwei drei“ ist nicht gültig, da er zwei Leerzeichen
111
enthält.
112
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
5. Der Name „_“ ist
FT
6. Der Name „void“ ist ungültig, da „void“ ein reserviertes Wort ist.
7. Der Name „pi“ ist gültig, da er nur aus Zeichen aus dem Bereich a–z besteht.
8. Der Name „E“ istgültig, da er nur aus einem Zeichen aus dem Bereich a–z besteht. Die Deklaration
ist aber fehlerhaft, da es den Typ Double nicht in C gibt.
D.2.2 signed, unsigned char
Zur Beantworung dieser Frage schreiben wir das Programm
DR
A
/******************************************
datatypes.c Testen der Läge eingebauter Datentypen
*******************************************/
#include <stdio.h>
#include <limits.h>
int main()
{
{
char lowChar = CHAR_MIN;
char highChar= CHAR_MAX;
printf("%s%d%s%d","Untere Grenze char: \
",lowChar,", obere Grenze char: ",highChar);
}
{
int lowInt = INT_MIN;
int highInt= INT_MAX;
printf("%s%d%s%d","\nUntere Grenze int: \
", lowInt, ", obere Grenze int: ", highInt);
}
return 0;
}
In meiner Umgebung (MinGW bzw. Visual Studio .NET 2003) liefert dieses Programm die Ausgabe:
Untere Grenze char: -128, obere Grenze char: 127
Untere Grenze int: -2147483648, obere Grenze int: 2147483647
Also wird char i dieser Umgebung als signed char interpretiert.
D.2.3 int bzw. unsigned int
Die Länge einer int-Variablen ist die eines Registers, auf einer 64 Bit Maschine also 8 Byte. Die Wertebereiche sind also in diesem Fall 0 bis 264 − 1 = 18.446.744.073.709.551.616 − 1 bzw. −263 + 1 =
−9.223.372.036.854.775.808 + 1 bis 263 − 1 = 9.223.372.036.854.775.808 − 1.
D.2.4 Fehler in Summenfunktion
Das Ergebnis lautet nun
9 + 16 = 4198400.
Was bedeutet das nun? In der geänderten Zeile steht, dass ein Funktion im dezimalen Format (als ganze
Zahl) ausgegeben werden soll. Geben wir das Ergebnis im Hex-Format aus mittels:
printf("%d%s%d%s%x%s",pa," + ",pb," = ",summe,".");
D.2. KAPITEL 2
113
9 + 16 = 401000.
FT
So erhalten wir:
Rufen wir den Compiler mit der Option /Fm auf, die dafür sorgt, das er eine sog. map-Datei erzeugt, so
finden wir dort die Zeilen:
Address
0001:00000000
0001:00000010
0001:0000007c
Publics by Value
_summe
_main
_printf
Rva+Base
00401000 f
00401010 f
0040107c f
Lib:Object
summefalsch.obj
summefalsch.obj
LIBC:printf.obj
Auf Adresse 401000 steht also unsere kleine Summenfunktion. Wir lernen hieraus: Dezimal oder Hexadezimal dargestellt erhalten wir mittels printf die Adresse einer Funktion.
D.2.5 happybirthday1 verb+>+ berndbirth.out
Die Befehlszeile bewirkt, dass alle Ausgaben, die wir zuvor auf der Konsole gesehen haben in die Datei
berndbirth.out geschrieben werden. Insbesondere bekommen wir die Zeilen:
DR
A
Geben Sie bitte Ihr Geburtsdatum ein!
Tag:
und die folgenden nicht zu sehen. Allerdings wartet das Programm auf eine Eingabe von der Tastatur. Wir
geben also ein Datum ein in der Form
tt ENTER
mm ENTER
jjjj ENTER
und können anschließend alle Ausgaben in der Datei berndbirth.out ansehen.
D.2.6 printf mit ungültigen Esacpe-Sequenzen
Ein ganz naiver Versuch nimmt einfach einige Zeichen her und protokolliert die Ergebnisse:
/* printf01 Was passiert bei printf mit falschen Escape-Sequenzen
Naiver Ansatz ohne Argumente
Status: incomplete*/
void main(void)
{
int ai = 6;
int bi = 9;
char ac =’6’;
char bc = ’9’;
printf();
}
Der Microsoft Compiler wandelt dies bedenkenlos um. Gibt man allerdings /Wall an, so erhält man eine
Reihe Warnungen, die einen schon skeptisch machen sollten. Bei Ausführung stürzt das Programm aber
gnadenlos ab. Mir sagten die Debug-Infos wenig, ich erspare sie Ihnen deshalb an dieser Stelle.
Der cc Compiler auf einem meiner Linux-Rechner, genauer gcc version egcs-2.91.66 19990314/Linux
(egcs-1.1.2 release) brachte keine Warnung wegen print. Bei Ausführung stürzte das Programm aber mit
der Meldung „Speicherzugriffsfehler“ ab.
Einen leeren String „verdaut“ der Microsoft Compiler schon besser:
114
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
FT
/* printf02 Was passiert bei printf mit falschen Escape-Sequenzen
Naiver Ansatz mit leerem String
Status: incomplete*/
void main(void)
{
int ai = 6;
int bi = 9;
char ac =’6’;
char bc = ’9’;
printf(" ");
}
Hier haben wir ein „Erfolgserlebnis“, Compiler läuft durch und zur Laufzeit gibt es keinen Fehler. Kein
Wunder, das Programm tut ja auch nicht mehr als das altbekannte tunix.c.
Gibt man nun eine ungültige Escape-Sequenz ein, so erhält man eine Warnung, die da sinngemäß lautet:
„ungültige Escape-Sequenz“.
D.2.7 Fahrenheit - Celsius
DR
A
/* fahrenheit04.c Konvertieren von Temperaturen
* in Fahrenheit in Celsius, const
* Status: incomplete
*/
#include <stdio.h>
#include "funktionen.h"
void main(void)
{
double fahr, celsius;
do
{
printf("\nGeben Sie bitte eine Temperatur in Fahrenheit ein:\n");
scanf("%lf",&fahr);
fflush(stdin);
celsius = (5.0/9.0)*(fahr-32);
celsius >= -274.1 ? printf("Das sind %lf Grad Celsius.",celsius):printf("Temper
}
while (weiter());
}
Der Vollständigkeit hier noch funktionen.h und .c
#ifndef FUNKTIONEN
#define FUNKTIONEN
/*funktionen.h Header für eine Sammlung von mehr oder
weniger nützlicher Hilfsfunktionen*/
/*Abfrage, ob eine Schleife nochmals durchlaufen werden soll,
*am besten in einer do while Schleife zuu verwenden.*/
int weiter(void);
#endif
/*funktionen.c Sammlung von mehr oder weniger nützlichen Hilfsfunktionen
Status: tested*/
#include "funktionen.h"
#include <stdio.h>
#include <stdlib.h>
int weiter(void)
{
D.2. KAPITEL 2
115
}
D.2.8 Body-Mass-Index
FT
char w=’N’;
printf("\nMoechten Sie weitermachen? Dann geben Sie bitte J ein.");
scanf("%c",&w);
fflush(stdin);
return (toupper(w)==’J’)? 1:0;
DR
A
/* bmi01.c Errechung des Body-Mass-Index
Status: incomplete*/
#include <stdio.h>
int main(void)
{
float h,w;
printf("Geben Sie bitte Ihre Körpergröße in Metern ein:");
scanf("%f",&h);
printf("Geben Sie bitte Ihr Gewicht in Kilogramm ein:");
scanf("%f",&w);
printf("\nIhr Body-Mass-Index ist: %f\n",w/(h*h));
return 0;
}
D.2.9 Deklarationen und Definitionen
Gibt man diese Deklarationen ein, wobei ich diesmal zur besseren Übersicht die Zeilennummern angebe
01
02
03
04
05
06
07
08
09
10
11
12
13
}
#include <stdio.h>
void main(void)
{
int a =2.5;
nt b = ’?’;
char z = 500;
int big = 40000;
double fläche = 1.23+5
long count = 0
char c = ’\’’;
unsigned char ch = ’\201’;
unsigned int size = 40000;
float value = 12345.0679;
so erhält man das folgende Ergebnis
test.c(4) : warning C4244: ’Initialisierung’: Konvertierung von ’double’ in ’int
’, möglicher Datenverlust
test.c(5) : error C2065: ’nt’: nichtdeklarierter Bezeichner
test.c(5) : error C2146: Syntaxfehler: Fehlendes ’;’ vor Bezeichner ’b’
test.c(5) : error C2144: Syntaxfehler: ’<Unbekannt>’ sollte auf ’<Unbekannt>’ fo
lgen
test.c(5) : error C2144: Syntaxfehler: ’<Unbekannt>’ sollte auf ’<Unbekannt>’ fo
lgen
test.c(5) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’Bezeichner’
116
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
FT
test.c(5) : warning C4555: Der Ausdruck hat keine Auswirkungen; Ausdruck mit Neb
eneffekt erwartet
test.c(5) : error C2065: ’b’: nichtdeklarierter Bezeichner
test.c(6) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
test.c(7) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
test.c(8) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
test.c(11) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
test.c(12) : error C2143: Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
Zunächst korrigiere ich die auf der Hand liegenden Fehler, wie fehlende Semikolons am Zeilenende und
mache aus nt den gültigen Typ int. Dann verbleiben noch folgende Warnungen
A16corrected.c(4) : warning C4244: ’Initialisierung’: Konvertierung von ’double’
in ’int’, möglicher Datenverlust
A16corrected.c(11) : warning C4245: ’Initialisierung’: Konvertierung von ’int’ i
n ’unsigned char’, signed/unsigned-Konflikt
A16corrected.c(13) : warning C4305: ’Initialisierung’: Verkürzung von ’double’ i
n ’float’
Das erläutere ich nun im Einzelnen.
DR
A
int a =2.5; Hier wird eine Gleitpunktzahl (float) einer int Variablen zugewiesen. Dabei geht der
gebrochene Anteil verloren und der COmpiler weist völlig lorrekt darauf hin.
nt b = ’?’; Hier sollte es offenbar int heißen. Das ist dann ganz und gar korrekt. in b steht dann ’?’
bzw. der entsprechende dezimale Wert 63 =X’3F’.
char z = 500; Dies mag zwar nicht sinnvoll sein, ist aber syntaktisch korrekt und in z steht hinterher
’c’ = -12. Die erfordert eine Erklärung: Die 256 Zeichen werden – da nichts von unsigned hier steht –
durch -128 –127 verschlüsselt. Nun kommen wir aber mit 500 an und weisen diese Zahl einem char
zu. Bei dieser Zuweisung wird nun in den angegeben Bereich transformiert, d.h. es wird modulo 256
hier hinheinverschoben. Ziehen wir einmal 256 ab, so erhalten wir 244, immer noch größer, als 127.
Also müssen wir nochmals 256 abziehen und landen nun bei dem angegeben Wert -12. Eine Warnung
des Compilers vor dem möglichen Datenverlust wäre aber am Platze gewesen.
int big = 40000; Die Antwort hängt von Ihrem Rechner ab: Auf 16-Bit Rechnern müsste der Compiler eine Warnung, bringen, da dort MAXINT= 32767 ist. In MS VS .Net 2003 und auch schon in in
VC98 haben wir es mit 32-Bit-Systemen zu tun.
double fläche = 1.23+5; Bis auf das ursprünglich fehlende Semikolon am Ende der Zeile ist dies
in Ordnung.
long count = 0; Bis auf das ursprünglich fehlende Semikolon am Ende der Zeile ist dies in Ordnung.
char c = ’\”; Dies ist völlig in Ordung so weisen Sie c das Zeichen ’ zu. Dezimal ist das der Wert
39.
unsigned char ch = ’\201’; So weisen Sie ch den oktalen Wert o’201’ = X’81’ = 129 zu. Dies
liegt außerhalb des 7-Bit ASCII Codes und entspricht dem deutschen Buchstaben ü. Die rechte Seite
ist aber eine Konstante, die ein Vorzeichen haben könnte, während die linke Seite unsigned ist.
Die Warnung des Compilers ist also angebracht.
unsigned int size = 40000; Dies ist zur Abwechslung mal in Ordnung.
float value = 12345.0679; Die Konstante ist länger, als float darstellen kann. Es können also Stellen abgeschnitten werden und der Compiler warnt zu Recht. Und tatsächlich: es ist value =
12345.068359.
D.3. KAPITEL 3
117
D.2.10 Ceiling und Floor
1. ⌈3, 99⌉ = 4,00
2. ⌊3, 99⌋ = 3,00
3. ⌈−3, 11⌉ = -3,00
4. ⌊−3, 11⌋ = -4,00
Programm in chap3/floorceil.c.
D.3 Kapitel 3
FT
In der C-Bibliothek gibt es eine Reihe mathematischer Funktionen. Sie finden Sie in der Header-Datei
math.h. Die Funktion ceil(double d) (Decke) liefert die kleinste ganze Zahl ⌈d⌉, die größer oder gleich d ist.
Entsprechend liefert floor (Fußboden) die größte ganze Zahl ⌊d⌋, die kleiner oder gleich d ist. Beantworten
Sie bitte die folgenden Fragen und bestätigen Sie durch ein C-Programm, dass die Funktionen aus der
C-Bibliothek dieses Ergebnis liefern!
D.3.1 Falscheingaben bei formio11.c
DR
A
Hier eine Tabelle mit einige Eingaben und deren Wirkung:
Summand 1
1,1
Eingaben
Summand 2
Wirkung
Summand 3
1.1
a
1
29650297
666772048
----696422346
1
29650297
1487151696
----1516801994
1245120
29650297
-1726560832
-----1695665415
Sie sehen, nach derartigen Fehleingaben haben Sie gar keine Chance mehr, weitere Summanden einzugeben. Damit wollen wir es für’s Erste genug sein lassen.
D.3.2 Typkonvertierung char to int und zurück
Hier das Programm
/******************************************
datatypes07.c Konvertierungen int2char etc.
Status: ok
*******************************************/
#include <stdio.h>
int main(void)
{
char ca = ’a’;
int ia = 888;
char cb = ia;
int ib = ca;
printf("\nVar.
printf("\nca =
printf("\nia =
printf("\ncb =
printf("\nib =
return 0;
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
FT
118
Zeichen\tDez.\tHex");
\t%c\t%d\t%x",ca,ca,ca);
\t%c\t%d\t%X",ia,ia,ia);
\t%c\t%d\t%X",cb,cb,cb);
\t%c\t%d\t%x",ib,ib,ib);
}
und die Fehlermeldung des Compilers
DR
A
datatypes07.c(10) : warning C4242: ’Initialisierung’: Konvertierung von ’int’ in
’char’, möglicher Datenverlust
end{verbatim}
und die Ausgabe
\begin{verbatim}
Var. Zeichen
Dez.
Hex
ca =
a
97
61
ia =
x
888
378
cb =
x
120
78
ib =
a
97
61
Hier nun die Erklärung: char ohne weitere Qualifikation ist in MS C signed character und belegt
den Bereich von −128 bis 127. Die Variable ia hat bei ihrer Initialisierung den Wert 888 weit jenseits
dieses Bereichs. Bei der Deklaration und Definition char cb = ia; wird dieser Wert in den Bereich
von char transformiert: Durch dreimaliges Subtrahieren von 256 landen wir gerade bei 78. Damit ist auch
geklärt, warum printf hier als Zeichen „x“ ausgibt.
D.3.3
Yet to be done
D.3.4 Dreistellige Zahl in unterschiedlichen Formaten
Die Aufgabenstellung ist vage. Das folgende Programm interpretiert sie so, dass eine Zahl eingegeben
werden soll.
/*datatypes08.c Eingabe einer dezimal dreistelligen Zahl
*Ausgabe dezimal, oktal und hexadezimal
*Status: incomplete */
#include <stdio.h>
void main(void)
{
int i;
printf("\nGeben Sie bitte eine dreistellige ganze Zahl dezimal ein:");
scanf("%3d", &i);
printf("Dezimal\tOktal\tHexadezimal");
printf("\n%d\t%o\t%X",i,i,i);
}
D.4 Artikel, Stückzahl, Preis, Gesamtpreis
/******************************************
D.5. KAPITEL 4
119
D.5 Kapitel 4
FT
datatypes09.c Eingaben und Rechnungen.
Status: inclomplete
*******************************************/
#include <stdio.h>
void main(void)
{
long int artikelnummer;
long int stueckzahl;
double stueckpreis;
printf("\nGeben Sie bitte eine achtstellige Artikelnummer ein:");
scanf("%8d",&artikelnummer);
printf("\nGeben Sie bitte eine ganzzahlige Stueckzahl ein:");
scanf("%d",&stueckzahl);
printf("\nGeben Sie bitte eine Stueckpreis ein:");
scanf("%lf",&stueckpreis);
printf("Sie haben % ld Stueck für insgesamt %lf eingekauft.",stueckzahl, stueckzah
}
DR
A
D.5.1 Zusammengesetzte Zuweisungen
Ich vergleiche einfach zwei Programme, die die Beispiele enthalten.
/* zuweisung1.c*/
int main()
{
int i = 0;
int j = 0;
int k = 5;
i+=3;
i-=5;
i*= j +1;
i/= k-2;
}
/* zuweisung2.c*/
int main()
{
int i = 0;
int j = 0;
int k = 5;
i = i + 3;
i = i - 5;
i = i * (j + 2);
i = i / (k - 2);
}
Ein einfaches dir zuweisung*.* /OE /ON-Kommando zeigt folgende Ausgabe:
10.07.2004
10.07.2004
10.07.2004
10.07.2004
12:32
12:32
12:32
12:32
125
148
22.016
22.016
zuweisung1.c
zuweisung2.c
zuweisung1.exe
zuweisung2.exe
10.07.2004
10.07.2004
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
12:32
12:32
556 zuweisung1.obj
556 zuweisung2.obj
FT
120
Sowohl die Objekt- als auch die ausführbaren Dateien sind genau gleich groß. Eine einfacher Vergleich mit
dem diff Befehl im Unix oder emacs zeigt als einzigen Unterschied die „1“ bzw. „2“ im Dateinamen.
Ziehen Sie hieraus bitte folgende Konsequenz: machen Sie sich mehr Gedanken über die Verständlichkeit Ihrer Programme als darüber, wie effizient der Code sein mag, den den Compiler daraus macht.
D.5.2 Tabellen 4.5–4.9
Da Ihnen das Skript (hoffentlich) im pdf-Format vorliegt, können Sie einfach damit beginnen, den Text der
Tabellen in Ihr C-Programm zu kopieren und „printf darum zu schreiben.“ Das sieht dann ungefähr so
aus:
DR
A
/*formio01.c Formattierte Ausgabe in C
Start mit einem langen String
Status: ok*/
#include <stdio.h>
int main()
{
printf("Operator Bedeutung<kleiner<=kleiner gleich>\
größer>=größer gleich==gleich!=ungleich");
return 0;
}
Die Ausgabe
Operator Bedeutung<kleiner<=kleiner gleich>größer
>=größer gleich==gleich!=ungleich
kann aber ebenfalls nnoch nicht überzeugen. Wir müssen noch einige newlines etc. einfügen. Das Ergebnis
ist natürlich noch völlig ungenügend. Im nächsten Schritt fügen wir nun eine \t Escape-Sequenz (Tabulator) zwischen den Spalten und eine \n Escape-Sequenz für „neue Zeile“ ein. Gleichzeitig ersetzen wir die
Deutschen Sonderzeichen durch ihre Ersatzausdrücke
/*formio02.c Formattierte Ausgabe in C
Nun spaltenweise
Status: ok*/
#include <stdio.h>
int main()
{
printf("Operator \tBedeutung\n<\tkleiner\n<=\tkleiner gleich\n>\
\tgroesser\n>=\tgroesser gleich\n==\tgleich\n!=\tungleich");
return 0;
}
Die Ausgabe gefällt nun schon besser:
Operator
Bedeutung
<
kleiner
<=
kleiner gleich
>
groesser
>=
groesser gleich
==
gleich
!=
ungleich
D.5. KAPITEL 4
121
FT
Hierbei fällt aber auf, dass die Spalten nicht genau ausgerichtet sind, \t verschiebt um einen konstanten
Wert. Fügen wir einfach ab Zeile 2 eine zusätzliche \t Escape-Sequenz ein, so ist „alles im Lot“. Das
Programm sieht nun so aus:
/*formio03.c Formattierte Ausgabe in C
Nun spaltenweise und korrekt ausgerichtet
Status: ok*/
#include <stdio.h>
int main()
{
printf("Operator \tBedeutung\n<\t\tkleiner\n<=\t\tkleiner gleich\n>\
\t\tgroesser\n>=\t\tgroesser gleich\n==\t\tgleich\n!=\t\tungleich");
return 0;
}
und die Ausgabe ist
Bedeutung
kleiner
kleiner gleich
groesser
groesser gleich
gleich
ungleich
DR
A
Operator
<
<=
>
>=
==
!=
korrekt ausgerichtet. Nach dem gleichen Schema können wir die restlichen Teile der Aufgabe erledigen.
Das sollte jetzt nur noch eine Fleißaufgabe sein.
Einige können wir aber auch eleganter lösen, z. B. die Reproduktion von der Tabelle in Abb. 4.9. Da
brauchen wir eine Überschrift und eine geschachtelte for-Schleife.
/*op10.c Wahrheitstafel für Boolesche Operatoren*/
#include <stdio.h>
void main(void)
{
int x, y;
printf("\nx\ty\tx&&y\tx||y\t!x");
for(x=1;x>=0;x--)
{
for(y=1;y>=0;y--)
{
printf("\n%d\t%d\t%d\t%d\t%d",x,y,x&&y,x||y,!x);
}
}
}
D.5.3 Speicherbedarf der elementaren Datentypen
Die Lösung ist geradeaus:
/*datatypes06.c Speicherbedarf der elementaren Datentypen*/
#include <stdio.h>
void main(void)
{
printf("\nType\t\tByte");
printf("\nchar\t\t%d",sizeof(char));
printf("\nunsigned char\t%d",sizeof(unsigned char));
122
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
}
FT
printf("\nshort\t\t%d",sizeof(short));
printf("\nint\t\t%d",sizeof(int));
printf("\nlong\t\t%d",sizeof(long));
printf("\nfloat\t\t%d",sizeof(float));
printf("\ndouble\t\t%d",sizeof(double));
printf("\nlong double\t%d",sizeof(long double));
und liefert im Fall von MS VS .NET 2003 das folgende Ergebnis.
Type
char
unsigned char
short
int
long
float
double
long double
DR
A
D.6 Kapitel 5
Byte
1
1
2
4
4
4
8
8
D.6.1 Matrix der ASCII-Zeichen
Ich löse diese Aufgabe wie folgt: Eine äußere Laufvariable i für die Zeilen, eine innere Laufvariable j
für die Spalten. In einer ersten Schleife werden die Spaltenköpfe ausgegeben. Dann folgen die beiden
geschachtelten Schleife. In der äußeren Schleife wird nur der Zeilenkopf ausgegeben, in der inneren die
Zeichen„16*i + j“. Zur „Kontrolle“ folgt dann noch die entsprechende Matrix mit den hexadecimalen
Darstellungen. Insgesamt sieht das dann so aus:
/* asciimatrix01.c Ausgabe einer Matrix der ASCII-Zeichen
mit hexadezimalen Zeilen und Spaltenköpfen
Status: ok*/
#include <stdio.h>
void main(void)
{
int i=0;
int j=0;
printf("\n
");
for(i=0;i<16;i++)
printf("%2X ",i);
for(i=0;i<16;i++)
{
printf("\n%2X ",i);
for(j=0;j<16;j++)
{
printf("%2c ",16*i + j);
}
printf("\n\t ");
}
printf("\n
");
for(i=0;i<16;i++)
printf("%2X ",i);
for(i=0;i<16;i++)
D.6. KAPITEL 5
123
{
}
}
FT
printf("\n%2X ",i);
for(j=0;j<16;j++)
{
printf("%2X ",j+16*i);
}
printf("\n\t ");
Aus satztechnischen Gründen verzichte ich darauf, das Ergebnis hier mit auszudrucken.
D.6.2 BMI mit erläuternden Ausgaben
DR
A
/* bmi02.c Errechung des Body-Mass-Index
Status: incomplete*/
#include <stdio.h>
#include <string.h>
#include "funktionen.h"
void main(void)
{
double h,w, bmi;
char message[200];
do
{
printf("\nGeben Sie bitte Ihre Koerpergroesse in Metern ein:");
scanf("%lf",&h);
fflush(stdin);
printf("Geben Sie bitte Ihr Gewicht in Kilogramm ein:");
scanf("%lf",&w);
fflush(stdin);
bmi = w/(h*h);
if((0 < bmi) && (bmi < 18))
strcpy(message,"Sie sind auffaellig untergewichtig und sollten mehr qualitati
if((18 < bmi) && (bmi < 25))
strcpy(message,"Ihr Gewicht entspricht Ihrer Koerpergroesse. Essen Sie weiter
if(25 < bmi)
strcpy(message,"Sie sind uebergewichtig. Reduzieren Sie Ihre Nahrungsaufnahme
printf("\nIhr Body-Mass-Index ist: %lf\n%s",bmi, message);
fflush(stdin);
}
while(weiter());
}
D.6.3 for Schleife mit Präfix-Inkrementoperator
Ich zeige gleich alle drei Varianten in einem Programm:
/*for03.c Die ersten 10 Quadratzahlen?
Status: ok*/
#include <stdio.h>
void main(void)
{
int i;
printf(" i\ti*i\t i\ti*i\t i\ti*i\n");
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
124
}
FT
for(i = 1;i<=10;i++)
{
int k = i + 1;
printf("%3d\t%3d\t",i,i*i);
printf("%3d\t%3d\t",k,k*k);
printf("%3d\t%3d\n",i,i*i);
}
Die Ausgabe gebe ich der Kürze halber in drei Spalten nebeneinander an:
i*i
1
4
9
16
25
36
49
64
81
100
i
2
3
4
5
6
7
8
9
10
11
i*i
4
9
16
25
36
49
64
81
100
121
i
1
2
3
4
5
6
7
8
9
10
i*i
1
4
9
16
25
36
49
64
81
100
DR
A
i
1
2
3
4
5
6
7
8
9
10
D.7 Kapitel 6
D.8 Kapitel 7
D.8.1 stringcopy
Nichts leichter als das:
/*funktionen.c Sammlung von mehr oder weniger nützlichen Hilfsfunktionen
Status: tested*/
#include "funktionen.h"
#include <stdio.h>
#include <stdlib.h>
...
char *stringcopy(char* str2, const char *str1)
{
int i=0;
while(str1[i]!=’\0’)
str2[i++] = str1[i];
str2[i]=’\0’;
return str2;
}
D.8.2 Palindromerkennung
Die Basis für die Lösung dieser Aufgabe bildet das Programm palindrom01.c aus Abschn. 6.3 oder
palindrom02.c aus Abschn. 7.4. Wir können auf zwei Arten an die Lösung herangehen:
1. Den String nicht „in place“ umdrehen sondern die Zeichen in der anderen Reihenfolge in einem
neuen String speichern. Anschließend strcmp verwenden.
D.8. KAPITEL 7
125
2. Zu versuchen, den Vergleich Zeichen für Zeichen am String selber vorzunehmen.
FT
Die erste Variante zeigt palindrom03.c:
DR
A
/* palindrom03.c String auf Palindromeigenschaft überprüfen
Status: incomplete*/
#include <stdio.h>
#include <string.h>
/*#include <stdlib.h>*/
#define MAXLEN 100
void reverse(char *s, char *t);
void main(void)
{
int i;
char c;
char puffer[MAXLEN];
char reversed[MAXLEN];
printf("\nGeben Sie bitte eine Zeile Text ein: \n");
for (i=0; (i < MAXLEN-1) && ((c = getchar()) != ’\n’);i++)
puffer[i]= c;
puffer[i]=’\0’;
printf("\nSie gaben ein: %s", puffer);
reverse(puffer, reversed);
printf("\nRueckwaerts: %s", reversed);
strcmp(puffer,reversed)?printf("\nDas ist kein Palindrom"):printf("\nDas ist ein Pa
}
void reverse(char *s, char* t)
{
unsigned int i;
unsigned int laenge = strlen(s)-1;
printf("\nLaenge =%d",laenge);
for(i = 0;i <= laenge; i++)
{
t[i] = s[laenge - i];
}
t[i] = ’\0’;
}
Wie schon bei der Diskussion von palindrom01.c sei jetzt nochmals auf die obere Grenze in der forSchleife hingewiesen: Wenn der String in einem Vektor der Länge n gespeichert ist, belegen seine Zeichen
die Stellen 0 bis n − 1. An der Stelle n steht ’\0’. Die Funktion strcmp liefert 0 zurück, wenn beide
Strings gleich sind, sonst etwas von 0 verschiedenes. Daher die Anordnung der beiden Ausdrücke nach
dem Fragezeichen.
Versuchen wir nun auch noch die andere Lösungsstrategie umzusetzen. Als Basis nehme ich wieder
palindrom02.c und versuche die Funktion reverse so zu verändern, dass sie vergleicht und nicht
invertiert. Ich deklariere sie mit dem Rückgabetyp int und versuche 1 zurück zu liefern, wenn es sich um
einPalindorm handelt, sonst 0. Dazu muss ich im Wesentlichen die Zuweisung s[i]=s[j] durch einen
Vergleich s[i]!=s[j] ersetzen. Beim ersten ungleich, weiß ich, dass es sich nicht um ein Palindrom
handelt und kann 0 zurückliefern. Läuft die Schleife bis zum Ende durch, so ist es ein Palindrom und ich
kann 1 zurückliefern.
/* palindrom04.c String auf Palindromeigenschaft prüfen, in place
Status: incomplete*/
#include <stdio.h>
#include <string.h>
126
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
DR
A
FT
#define MAXLEN 100
int palindrom(char s[]);
void main(void)
{
int i, c;
char puffer[MAXLEN];
printf("\nGeben Sie bitte eine Zeile Text ein: \n");
for (i=0; (i < MAXLEN-1) && ((c = getchar()) != ’\n’);i++)
puffer[i]= c;
puffer[i]=’\0’;
palindrom(puffer)?printf("\nDas ist ein Palindrom"):printf("\nDas ist kein Palindro
}
int palindrom(char s[])
{
int i, j;
for(i = 0, j = strlen(s)-1;i < j; i++, j--)
{
if (s[i] != s[j])
return 0;
}
return 1;
}
D.8.3 Textverschlüsselung
Die Idee zu der Lösung dieser Aufgabe macht Gebrauch von Modulodivision % und der Tabelle der ASCII
Codes für Großbuchstaben im Glossar. Zunächst einmal stellen wir fest, dass es 26 Großbuchstaben gibt.
Währen die von 0 bis 25 durchnummeriert, so könnten wir einfach jeden Buchstaben wie folgt ersetzen
c = (c + n)%26;
Da die Großbuchstaben im ASCII Code aber von X’41 bis 5A. Daher müssen wir erst in den Bereich von
0 bis 25 hineintransformieren:
c = ’A’ + (s[i]-’A’ + n)%26;
Für die Funktion zum Decodieren bemerken wir zunächst, dass die eine Verschiebung um 26 − n nach
vorne ist. Damit ist dies dann auf das Codieren zurückgeführt:
c = ’A’ + (s[i]-’A’ + (26-n))%26;
Damit sind wir praktisch fertig:
void encode(char *s)
{
unsigned i=0;
while(s[i]!=’\0’)
{
if(s[i]!= ’ ’)
s[i++] = ’A’ + (s[i]-’A’ + 5)%26;
}
}
void decode(char *s)
{
unsigned i=0;
while(s[i]!=’\0’)
D.8. KAPITEL 7
127
{
}
}
DR
A
D.8.4 Body-Mass Index
+ 21)%26;
FT
if(s[i]!= ’ ’)
s[i++] = (’A’ ) + (s[i]-’A’
ANHANG D. LÖSUNGSVORSCHLÄGE FÜR AUSGEWÄHLTE AUFGABEN
DR
A
FT
128
IOCCC
FT
Anhang E
Der International Obfuscated C Code Contest fand in vielen der letzten 20 Jahre seit 1984 statt. Zur Auflockerung der Vorlesung und als (mehr oder weniger) schwierige Aufgaben, erläutere ich hier einige der
Programme. Hoffentlich habe ich sie richtig verstanden.
DR
A
E.1 1984
E.1.1
anonymous
Der Autor dieses Programms
int i;main(){for(;i["]<i;++i){--i;}"];read(’-’-’-’,i+++"hell\
o, world!\n",’/’/’/’));}read(j,i,p){write(j/p+p,i---j,i/i);}
wollte seinen Namen nicht veröffentlicht haben:
Dishonorable mention:
Anonymous
The author was too embarrassed that he/she could write such trash, so I promised to protect
their identity. I will say that the author of this program has a well known connection with the
C programming language.
Das Programm lässt sich compilieren, es gibt aber eine Reihe Warnungen. In der Version 01 strukture ich
das Programm zunächst nach den üblichen Regeln.
Dazu ergänze ich ein include von io.h. In Unix-Systemen nehme ich syscalls.h.
Den ersten Schritt zum Verständnis dieses Programms machen wir, in dem wir den Code übersichtlichler formatieren:
int i;
main()
{
for(;i["]<i;++i){--i;}"];
read(’-’-’-’,i+++"hello, world!\n",’/’/’/’));
}
read(j,i,p)
{
write(j/p+p,i---j,i/i);
}
In Version 03 ergänze ich Protoypen für die (System-)Funktione read und write.
Außerdem kann ich bereits einige einfache Dinge klarer schreiben:
129
ANHANG E. IOCCC
130
’-’-’-’ = ’-’ - ’-’
FT
Das ist also einfach die Subtraktion von ’-’ von ’-’. Unabhängig davon, was ’-’ numerisch ist (es
ist übrigens (2d)16 = (45)10 ), 0.
Ebenso ist ’/’ / ’/’ = 1; Nun „sehen“ wir Folgendes: Das Programm besteht
1. aus einer main-Funktion mit einer einigermaßen kryptischen for-Schleife und
2. einer Funktion read, die drei Parameter erhält.
Analysieren wir nun bottom up, so sollten wir zunächst versuchen, die Funktion read zu verstehen. Da
die Typen der Parameter nicht angegeben sind, haben sie den default Typ int. Was die Funktion write
macht, müssen wir nachschlagen, z. B. in [KR88], S. 170. Dort finden wir die Signatur von write:
int write(int fd, char* buf, int n)
Wir brauchen uns keine großen Gedanken darüber zu machen, was ein „file descriptor“ ist (der erste Parameter). Interessant ist es zu wissen, dass der zweite Parameter ein Buffer ist, in den geschrieben wird und
dass der dritte Parameter die Anzahl Bytes ist, die geschrieben werden sollen. Der Rückgabewert ist die
Anzahl der geschriebenen Zeichen.
Damit ist der letzte Parameter von write klar: 1 = ii Byte soll geschrieben werden.
Damit kann ich den letzten Teil der for-Schleife ersetzen durch
,i+++"hello, world!\n",
1)
DR
A
write(1
Diese lautet also nun:
for(; i["]<i;++i){--i;}"]; write(1
,i+++"hello, world!\n",
Sehen wir uns nun den Rest for-Schleife an: Der erste Parameter ist leer. Die Schleifenvariable ist
global deklariert, aber nicht definiert. Die Abbruchbedingung im zweiten Parameter lautet:
i["]<i;++i){--i;}"]
Im dritten Parameter steht:
read(’-’-’-’,i+++"hello, world!\n",’/’/’/’)
Der erste Parameter der read-Funktion ist ’-’-’-’. Dies wird von links nach rechts ausgewertet, d. h.
genauer steht dort
′
−′ −′ −′
=′ −(′ −(′ −′ ))
=′ −(′ −0)
= 0.
Dabei müssen wir uns nicht einmal überlegen, wie der ASCII-Code des Zeichens „’“ ist. Also lautet der
Aufruf von read etwas vereinfacht:
read(0,(i++)+"hello, world!\n",1)
Nun können wir die kryptische for-Schleife genauer analysieren:
"hello, world!\n" ist in der Addition eine Adresse. Also wird bei einem Schleifendurchlauf das
i-te Zeichen von „hello, world!“ ausgegeben.
Solangsam wird jetzt auch die write-Funktion verständlich: Dort steht ja nichts anderes als
write(1,(i+++"hello, world!\n")--)-1,0,1);
Mit Klammern wird das wie oben vielleicht besser verständlich:
write(1,((i++)+"hello, world!\n")--)-1,0,1);
1));
E.1. 1984
131
FT
Der erste Parameter in read und write ist 0 bzw. 1, der file descriptor.
Wenden wir uns nun dem zweiten Teil des for-Konstrukts zu: i[n] = ∗(i + n). Das liefert:
for(; *(i+"]<i;++i){--i;}"); write(1
,i+++"hello, world!\n",
1));
Im letzten Teil des for-Konstrukts erfolgt die Inkrementierung von i, s. o. Im zweiten Teil laufen wir, bis
wir zum Terminierungszeichen des Strings kommen. Es kommt also nur darauf an, das wie einen String
der Länge von „hello, world!“ dort haben, z. B. „abcdefghijklm“. Am einfachesten liest es sich aber wohl
so:
for(; *(i+"hello, world!"); write(1
E.1.2
decot
,i+++"hello, world!\n",
Diese Programm von Dave Decot gewann 1984 den zweiten Platz:
#define
#define
#define
#define
x =
double(a,b) int
char k[’a’]
union static struct
DR
A
extern int floor;
double (x1, y1) b,
char x {sizeof(
double(%s,%D)(*)())
,};
struct tag{int x0,*xO;}
*main(i, dup, signal) {
{
for(signal=0;*k *x * __FILE__ *i;) do {
(printf(&*"’\",x);
/*\n\\", (*((double(tag,u)(*)())&floor))(i)));
goto _0;
_O: while (!(char <<x - dup)) { /*/*\*/
union tag u x{4};
}
}
while(b x 3, i); {
char x b,i;
_0:if(b&&k+
sin(signal)
;
}
/ *
((main) (b)-> xO));/*}
*/}}}
E.1.3
Laman
Mike Laman gewann 1984 mit diesem Programm den dritten Platz
a[900];
l)char*
0;k*k<
b;c;d=1
*l;{g=
g;b=k
;e=1;f;
atoi(*
++>>1)
g;h;O;
++l);
;for(h=
main(k,
for(k=
0;h*h<=
1));
ANHANG E. IOCCC
132
--h;c=(
<=g){
b<<5|c]
++f)a[b
=0;c<h;
<k/2)a[
a[b<<5
a[b<<5|c
putchar(
(h+=g>h
++O;for
=d++,b+=
<<5|c]=
++c){
b<<5|c]
|c]^=a[
]?"%-4d"
’\n’);}}
*(h+1))
(f=0;f<
e;for(
d++,c+=
for(b=0
^=a[(k
(k-(b+1
:"
"
/*Mike
-1)>>1;
O&&d<=g
f=0;f<O
e;e= -e
;b<k;++
-(b+1))
))<<5|c]
,a[b<<5
Laman*/
FT
g;++h);
while(d
;++f)a[
&&d<=g;
;}for(c
b){if(b
<<5|c]^=
;printf(
|c]);}
Dieses Programm ist einfach zu entschlüsseln, man bringe es nur in eine sinnvoller(?) strukturierte Form:
DR
A
a[900];
b;c;
d=1;e=1;
f;g;h;O;
main(k,l)
char*
*l;
{
g=atoi(*++l);
for(k=0;k*k<g;b=k++>>1);
for(h=0;h*h<=g;++h);
--h;
c=((h+=g>h*(h+1))-1)>>1;
while(d<=g)
{
++O;
for (f=0;f<O&&d<=g;++f)
a[b<<5|c]=d++, b+=e;
for(f=0;f<O&&d<=g;++f)
a[b<<5|c]=d++,c+=e;e=-e;
}
for(c=0;c<h;++c)
{
for(b=0;b<k;++b)
{
if(b<k/2)a[b<<5|c]^=a[(k-(b+1))<<5|c]^=a[b<<5|c]^=a[(k-(b+1))<<5|c];
printf(a[b<<5|c]?"%-4d":"
",a[b<<5|c]);
}
putchar(’\n’);
}
}
/*Mike
Laman*/
Nun schreiben wir dies noch in aktueller C-Syntax (Deklaration von main) und deklarieren/definieren jede
Variable vollständig.
E.1.4
mullender
The Grand Prize:
Sjoerd Mullender & Robbert van Renesse für das Programm
short main[] = {
277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800,
-113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004,
14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9,
E.2. 2001
133
};
FT
4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419,
0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0,
4, 0, 0, 0, 0x8, 0, 4, 0, ’,’, 0, 12, 0, 4, 0, ’#’,
0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712,
’p’, 072163, ’r’, 29303, 29801, ’e’
Sie haben keine Chance dieses Programm zum Laufen zu bekommen, wenn sie keine Vax-11 oder pdp-11
zur Verfügung haben. Sie haben eine Chance es zu verstehen, wenn Sie die Codes der Instruktionen dieser
Maschinen beherrschen. Soweit zur Portabilität, wenn derartige Maschinen im Spiel waren.
DR
A
E.2 2001
ANHANG E. IOCCC
DR
A
FT
134
FT
Anhang F
Development Environments
F.1 Microsoft Visual Studio
Der Microsoft Compiler hat sehr viele Optionen. Damit kann man am einfachsten umgehen, wenn man
diese in der IDE ändert. Da es in der IDE aber viele Dinge gibt, die Sie noch nicht kennen, beschreibe ich
hier einige nützliche Optionen und wie Sie diese in einer DOS-Box verwenden.
DR
A
/? Vielleicht ist dies der wichtigste Parameter von cl: Er sorgt dafür, dass eine Liste der Optionen auf den
Bildschirm ausgegeben wird.
/Wall alle Warnungen aktivieren. Das mag zwar für Anfänger nicht unbedingt die beste Option sein. Sie
ist aber sehr lehrreich und ich bin der Ansicht, ein Programm sollte auch mit dieser Option keine
Warnungen bringen, wenn man es ernsthaft benutzen möchte.
Arbeiten Sie in VC98 oder Visual Studio .NET, so müssen Sie ein Projekt haben, bevor Sie Dateien umwandeln (compile), linken (build) oder ausführen (execute) können. Am einfachsten ist es zunächst, einfach
die .c Datei zu öffnen und dann einen default workspace und ein default Project anlegen zu lassen. Das ist
zwar nicht sehr profesionell, aber kann schnelle gemacht werden. Weiteres in der Vorleseung
F.2 Erläuterungen zu Fehlermeldungen
C0006 Include Datei kann nicht geöffnet werden.
Auslöser: #include <summe03.h>
Ursache: Wird der Name der Include-Datei in spitzen Klammern (<, >) angegeben, so wird der
Standard-Pfad des Compilers nach der Datei durchsucht. Korrekt wäre hier gewesen
#include
"summe03.h"
dann wird zuerst im aktuellen Verzeichnis und erst dann im Standard-Pfad gesucht.
C2015 Zu viele Zeichen in der Konstanten
Auslöser: #include ’summe03.h’
Ursache: Einfache Anführungszeichen können in C nur ein Zeichen einschließen. Dies war einfach
ein Tippfehler. Korrekt wäre gewesen: #include "summe03.h"
C2065 : ’i’: nichtdeklarierter Bezeichner
C2143 : Syntaxfehler: Es fehlt ’;’ vor ’[’
Auslöser: int [2][2] = 1,2,3,4;
135
ANHANG F. DEVELOPMENT ENVIRONMENTS
136
Ursache: int [2][2] ist kein gültiger Ausdruck. Es fehlt der Name der Variablen. Korrekt wäre etwa:
int a[2][2] = 1,2,3,4;
FT
oder Syntaxfehler: Es fehlt ’;’ vor ’eingeben’
DR
A
Auslöser: Deklaration einer Variablen nicht am Anfang eines Blocks.
FT
Literaturverzeichnis
Douglas Adams. Per Anhalter durch die Galaxis. Roger & Bernhard, München, 1981. Ein
Kultbuch der 80er Jahre. Der erste Band einer Trilogie, die inzwischen aus 5 Bänden besteht.
[Blo71]
Ernst Bloch. Subjekt - Objekt. Erläuterungen zu Hegel. Suhrkamp, Frankfurt a. M., 1971.
[Cop93]
James O. Coplien. Advanced C++: Programming Styles and Idioms. Addison-Wesley, Reading, MA, 1993. Ein sehr gutes Buch! Über die dort präsentierten Ansichten, was guter Programmierstil sei, kann man natürlich streiten. Aber sie zu kennen, dürfte keinem Systementwickler schaden, der C++ als eine Zielumgebung betrachtet. Coplien war einer der ersten Benutzer des ersten C++ (pre) Compilers und begleitet die Entwicklung von C++ seit Beginn. Seit
dem Erscheinen von [GHJV95] könnte diese Buch auch „Design Patterns in C++“ heißen.
DR
A
[Ada81]
[Dij68]
Edsger W. Dijkstra. Go To Statement Considered Harmful. Communications of the ACM,
11(3):147–148, März 1968.
[GHJV95] Erich Gamma, Richard Helm, Ralph Johnson und Vlissides, John M. Design Patterns - Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994, Fourth printing, September 1995. ISBN 0-201-63361-2. Ein Katalog von Mustern, mit denen häufig vorkommenden
Designaufgaben in C++ oder anderen objektorientierten Programmiersprachen gelöst werden
können. Vieles davon ist an anderer Stelle beschrieben, aber dies ist eine kompakte Zusammenfassung, die auch die Anwendung einiger dieser Patterns an einer Fallstudie zeigt. Die Lösungen sind praxiserprobt und helfen bei der guten Strukturierung auch (aber keineswegs nur)
von C++ Software. Sicherlich eines der einflussreichsten Informatik Bücher der letzten Jahre.
Ein großer Teil der Arbeit wurde bei der Firma Taligent durchgeführt. Es gibt eine deutsche
Übersetzung [GHJV96].
[GHJV96] Erich Gamma, Richard Helm, Ralph Johnson und Vlissides, John M. Entwurfsmuster - Elemente wiederverwendbarer objektorientierter Software. Addison-Wesley, Bonn, Paris, Reading,
MA, 1996. ISBN 3-89319-950-0. Deutsche Übersetzung von [GHJV95].
[IH04]
Rolf Isernhagen und Harmut Helmke. Softwaretechnik in C und C++. Carl Hanser Verlag,
München, Wien, 2. korrigierte Auflage Auflage, 2004, xx+937 Seiten. ISBN 3-446-21584-0.
[Knu73]
Donald E. Knuth. The Art of Computer Programming I: Fundamental Algorithms. AddisonWesley, Reading, MA, 2. Auflage, 1973, xix+634 Seiten. ISBN 0-201-03821-8. Ein Klassiker,
den jeder Informatiker gelesen haben sollte. Der Stil bis hin zu den Aufgaben ist legendär.
Dieser Band ist im Gegensatz zu den weiteren auch als Paperback lieferbar.
[KPP96]
Ulla Kirch-Prinz und Peter Prinz. C für PC<s. International Thomson Publ., Bonn, Albany
and others, 1996, 655 Seiten. ISBN 3-8266-2697-4.
[KR88]
Brian Kernighan und Dennis Ritchie. The C Programming Language. Prentice-Hall, Englewood Cliffs, NJ, 2. Auflage, 1988, vii+272 Seiten. ISBN 0-13-110370-9.
[Nag93]
Eric Nagler. Learning C++ - A Hands-On Approach. West Publishing Company, Minneapolis/St. Paul, MN, 1993. Eine gute Einführung in C++. Es beginnt mit den elementarsten
137
LITERATURVERZEICHNIS
138
FT
Komponenten, behandelten Klassen, Streams, Templates und endet mit Exceptions. Wer alle
(über 500) Beispiele durchgearbeitet hat, sollte in Syntax und Semantik fit sein. Es ist aber kein
Buch, um gutes Programmieren oder gar Software-Engineering zu lernen. Wer nach diesem
Buch C++ lernen will, ist gut beraten zusätzlich z. B. das Buch von Coplien (s.o.) durchzuarbeiten.
Hans-Jürgen Scheibl. Visual C++.NET für Einstieger und Fortgeschrittene. Carl Hanser Verlag, München, Wien, 2004, xxii+1430 Seiten. ISBN 3-446-22329-0.
[Str91]
Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, Reading, MA, 2. Auflage, 1991. Eine systematische Darstellung von C++ mit dem kompletten reference manual.
Dieses Buch kann als Einführung in C++ dienen. Wer C++ im Eigenstudium und schnell lernen will, sollte zusätzlich ein Buch wie [Nag93] heranziehen und bald das Buch [Cop93] von
Coplien lesen. Auch auf Design-Prinzipien und die Entwicklung von Klassenbibliotheken wird
kurz eingegangen. Es gibt eine deutsche Übersetzung [Str94c]. Inzwischen ist die 3. Auflage
erschienen [Str97].
[Str94a]
Bjarne Stroustrup. The Design and Evolution of C++. Addison-Wesley, Reading, MA, 1994,
x+461 Seiten. ISBN 0-201-54330-3. Das „Eichenbuch“. Eine hervorragende Darstellung,
warum was in C++ wie funktioniert und nicht anders. An vielen Stellen wird der Bezug zwischen Designprinzipien und Grundprinzipien der Programmiersprache hergestellt. Viele andekdotische Details aus der Entstehungsgeschichte der Sprache. Es gibt eine deutsche Übersetzung
[Str94b].
DR
A
[Sch04]
[Str94b]
Bjarne Stroustrup. Design und Entwicklung von C++. Addison-Wesley, Bonn, Paris, Reading,
MA, 1994. Deutsche Übersetzung von [Str94a].
[Str94c]
Bjarne Stroustrup. Die C++ Programmiersprache. Addison-Wesley, Bonn, Paris, Reading,
MA, 4., korrigierter und erweiterter Nachdruck der 2. Auflage, 1994. Deutsche Übersetzung
von [Str91].
[Str97]
Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, Reading, MA, 3.
Druck der 3. Auflage, 1997. Umfangreich gegenüber der 2. Auflage [Str91] überarbeitet. Behandelt die STL und andere Eigenschaften des kommenden C++ Standards. INzwischen ist die
4. Auflage erschienen: [Str13].
[Str13]
Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, Reading, MA, 4.
Auflage, 2013, 1368 Seiten.
\t, 24
struct adresse, 84
struct complex, 83
struct punkt, 83
\xhh, 24
^-Operator, 42
_USE_MATH_DEFINES, 67
{ . . . }, 5
˜-Operator, 42
A
Ablauf
Programm-, 51
Abstraktionvermögen, 83
Adams, Douglas, 137
Addition, 12, 36
Additionsoperator, 12
Adresse, 16
Adressoperator, 16, 79
Algorithmus, 7
arithmetisch
Operator, binär, 36
arithmetischer Datentyp, 71
ASCII, 33, 54
atof, 72
atoi, 72
Attribut, 89
ausführbar
Datei, 5
ausführen, 1
Ausgabeinheit
Standard-, 19
1, 19
2, 19
Auswahloperator, 41
auto, 97
automatic, 98
DR
A
Symbole
#define, 75, 77
#define, 67
#elif, 77
#else, 77
#endif, 77
#endif, 87
#if, 77
#ifdef, 77
#ifndef, 77
#include
Präprozessor-Befehl, 7
#undef, 77
%, 36
&-Operator, 43
&-Operator, 16, 42, 79
*, 36
*-Operator, 16, 42, 88
+, 36, 37
+-Operator, 12, 13
-, 36, 37
–Operator, 13
.-Operator, 85, 88
.NET, 6
/, 36
/*. . . */, 5
/-Operator, 13
/Wall
Compiler-Option, 48
/c, 65
<, 39
=, 39
?:-Operator, 41
\", 24
#ifndef, 87
\’, 24
\0, 24
\?, 24
\\, 24
\a, 24
\b , 24
\f , 24
\n, 24
\ooo, 24
\r, 24
FT
Index
B
B, 6
BCPL, 6
Befehl
goto, 53
label, 53
Betriebssystem, 1, 98
139
INDEX
140
D
d, 14, 25
dangling else problem, 52
Datei
ausführbar, 5
Header-, 9, 63, 65
Implementierungs-, 65
map-, 113
Objekt-, 5
Datentyp, 79
arithmetischer, 71
DAU, 30
Dave Decot, 131
dd, 21
DEC
PDP-11, 6
PDP-7, 6
Deeferenzierungsoperator, 42
Default-Sichtbarkeit, 89
Deklaration, 64
Dekrement-Operator, 36, 37
Postfix, 36
Präfix, 36
descriptor
File, 19
didot point, 21
Dijkstra, Edsger Wybe, 137
Division, 13, 21, 36
Divisionsoperator, 13
do while, 50
do while-Schleife, 46, 47, 49
DOS, 31
DOS-Box, 4
Dou Xiao, xi
doxygen, 2
Kommentar, 4
Du Hongbo, xi
Dump, 80
DR
A
C
c, 14, 25
C, 1, 6, 79
C#, 1
C# , 6
C++, 1, 6, 29, 87, 89, 99, 100
C++ , 89
C-Compiler, 2
C-Programm, 7, 63
Komponente, 7
C-Stil
Kommentar, 5
C0006, 135
C2015, 135
C2065, 135
C2143, 135
C2166, 88
CA-IDEAL, 54
Cao Bin, xi
Cast-Operator, 33
Chen Hao, xi
Chen Yingjie, xi
cicero, 21
cl, 6
class, 89
Claus, Volker, 54
clock, 72
cm, 21
Collatz-Folge, 55
Compiler, 3, 6, 9, 65
Compiler-Option
/Wall, 48
const, 97
continue, 50
Coplien, James O., 137
cos, 71
CPL, 6
Ctrl-C, 46
Ctrl-D, 31
Ctrl-Z, 31
FT
Bibliothek, 9
Standard-, 63
stdio, 7
big point, 21
binär
Operator, 36
Operator, arithmetisch, 36
Bit-Manipulation, 35
Bit-Maske, 43
Bloch, Ernst, 137
Block, 97
bp, 21
break, 50, 53
Breite
Feld, 24
E
e, 26
E, 26
Eclipse, 3
Editor, 2
Eclipse, 3
Editor, 3
emacs, 3
Lieblings-, 3
Netbeans, 3
notepad++, 3
vi, 3
INDEX
FT
d, 14, 25
e, 26
E, 26
f, 14, 26
g, 26
G, 26
Hex-, 112
ld, 25
lf, 26
o, 25
s, 26
u, 25
x, 25
X, 25
Formattierungsstring, 12
Fu Xiangyun, xi
Funktion, 63, 89
main, 63
Standard-, 63
funktionaler Zusammenhalt, 66
G
g, 26
G, 26
Gamma, Erich, 137
Gao Cheng, xi
Ge Di, xi
Genauigkeit
scanf, 32
Geng Xiaoming„ xi
getchar, 17
global
Variable, 97
globale Kopplung, 66
goto, 53
DR
A
Visual Studio, 3
wordpad, 3
Eindeutigkeit, 7
Eingabeinheit
Standard-, 19
0, 19
Eingabepuffer, 31
Element, 89
emacs, 3
Endezeichen
String-, 59
Endlichkeit, 7
Endlosschleife, 45
Engineering
Software-, 63
Entscheidung
Mehrweg-, 52
EOF, 17
Escape-Sequenz, 14, 23
\’, 24
\0, 24
\?, 24
\\, 24
\a, 24
\b , 24
\f , 24
\n, 24
\ooo, 24
\r, 24
\t, 24
\v, 24
\xhh, 24
\", 24
exp, 71
external, 97
141
F
f, 14, 26
Fang Zongda, xi
Fehlermeldung, 19
Feldbreite, vii, 24
Fernsehen, 6
fflush, 31
Fibonacci-Folge, 69
Fibonacci-Zahl, 62, 69
file descriptor, 19
file handle, 19
float.h, 28
Folge
Collatz-, 55
for, 50
for-Schleife, 45, 48
Format
c, 14, 25
H
handle
file, 19
Header-Datei, 9, 63, 65
Hegel, Georg Wilhelm Friedrich, 137
Helm, Richard, 137
Helmke, Harmut, 137
Hex-Format, 112
hexadezimal, 43
Hu Chenjun, xi
I
if, 51
Implementierungs-Datei, 65
in, 21
inch, 21
Inkremenoperator
Postfix-, 50
INDEX
142
M
M_PI, 66
Ma Xiating, xi
main, 5
Funktion, 63
Makro, 76
malloc, 98
Manipulation
Bit-, 35
map-Datei, 113
Maschinenregister, 98
Maske
Bit-, 43
math.h, 66
max(A, B), 76
Mehrwegentscheidung, 52
member function, 89
Microsoft, 6
Microsoft Visual Studio, 135
Millimeter, 21
MinGW, 2, 109
mm, 21
Modul, 63
modulglobal
Variable, 97
Modulodivision, 36
Multiplikation, 13, 21, 36
Multiplikationsoperator, 13
DR
A
J
Java, 1, 6, 29, 99, 100
Jiang Chengyong, xi
Jiang Yanfei, xi
Johnson, Ralph, 137
Lisp, 3
Liste, 103
Liu Songlin, xi
log10, 71
logische Operation, 35
lokal
Variable, 97
Lu Ling, xi
Luo Liren, xi
FT
Inkrement-Operator, 36, 37
Postfix, 36
Präfix, 36
Inkrementoperator
Postfix-, 35
Präfix-, 35, 50
int, 5
Interrupt, 97
IOCCC, 49
isalnum, 71
isalpha, 71
isdigit, 71
Isernhagen, Rolf, 137
islower, 71
isupper, 71
K
Kapselung, 87
Kernighan, Brian, 137
Kirch-Prinz, Ulla, 137
Klasse, 83, 87, 89, 100
Klassenelemente, 100
Kluge, Alexander, 65
Knuth, Donald Ervin, 137
Kommentar, 5
C-Stil, 5
doxygen, 4
Komponente
C-Programm, 7
Konsole, 7, 19
Konstante
symbolische, 76
Kopplung, 49, 65
globale, 66
L
L-Wert, 88
label, 53
ld, 25
leicht daneben, 58
lf, 26
Li Feng, xi
Lieblingseditor, 3
limits.1, 9
limits.h, 9
Linker, 6
Links-Shift, 43
linksbündig, 24
N
Nagler, Eric, 137
Name
symbolischer, 76
Namenskonvention, 85
Netbeans, 3
notepad, 3
notepad++, 3
O
o, 25
Oberklasse, 89
Objekt, 100
Objektdatei, 5
objektorientiert, 100
INDEX
FT
PDP-11, 6
PDP-7, 6
Peng Bo, xi
π, 66
pica, 21
point
big, 21
didot, 21
scaled, 21
pointer, 16
pointer, 71
Pointer, 79
Postfix
Dekrement-Operator, 36
Inkrement-Operator, 36
Postfix-Inkremenoperator, 50
Postfix-Inkrementoperator, 35
pow, 71
Präfix
Dekrement-Operator, 36
Inkrement-Operator, 36
Präfix-Inkrementoperator, 35, 50
Präprozessor, 87
Präprozessor-Befehl
#include, 7
printf, 13
printf, 99
Prinz, Peter, 137
privat, 89
problem
dangling else, 52
Programm, 7, 64
alloc01.c, 98
C-, 63
copy01.c, 17
datatypes04.c, 32
formio13.c, 26
formio15.c, 27
formio16.c, 29, 32
formio17.c, 30
formio18.c, 31
formio19.c, 31
happybirthday1.c, 15
happybirthday2.c, 45
happybirthday3.c, 46
happybirthday4.c, 47
happybirthday5.c, 48, 49
moinmoin01.c, 13
moinmoin02.c, 14
moinmoin03.c, 14
op06.c, 41
op07.c, 41
palindrom01.c, 57, 59
palindrom02.c, 67
DR
A
Programmiersprache, 100
off by one, 58
oktal, 43
Operation, 89
ogische, 35
Rechen-, 35
Operatione, 89
Operator, 35
&, 16, 79
&-, 42, 43
*-, 16, 42
+, 12, 13
-, 13
., 85
.*-, 88
.-, 88
/, 13
<, 39
=, 39
?:, 41
^, 42
˜, 42
Additions-, 12
Adress, 16, 79
Adress-, 16
arithmetisch, binär, 36
Auswahl-, 41
binär, 36
Cast-, 33
Dekrement-, 36, 37
Dereferenzierungs-, 42
Inkrement-, 36, 37
Multiplikations, 13
Operator, 13
Punkt-, 88
Referenzierungs-, 42
Shift-, 42
sizeof-, 29, 33, 85, 121
Stern-, 88
structure member, 85
structure pointer, 88
Subtraktions-, 13
unär, 42
Unär, 36, 37
und-, 43
Verweis, 16
Vorzeichen-, 37
143
P
Palindrom, 38
Pan Enyu, xi
Pan Xinyuan, xi
Pan Yiwen, xi
pc, 21
INDEX
144
FT
Scheibl, Hans-Jürgen, 138
schlechtes Programm, 1
Schleife, 45, 53
do while, 46
do while-, 49
Endlos-, 45
while, 46
schreiben, 1
Semantik
Referenz-, 63
Wert-, 63
Sequenz
Escape-, 14, 23
Set-Top Bix, 6
Shen Zhongyuan, xi
Shi Zheng, xi
Shift
Links-, 43
Rechts-, 43
Shift-Operator, 42
Sichtbarkeit, 83, 89
Signatur, 130
sin, 71
sizeof-Operator, 29, 33, 85, 121
Software-Engineering, 63
sp, 21
Spaghetti-Code, 54
Speicher, 97
Speicherklasse, 97
Speicherverwaltung, 98
Sprung, 45, 53
sqrt, 71
srand, 72
Stack, 99, 103
Standard-Ausgabeinheit, 19
Standard-Bibliothek, 13
Standard-Eingabeinheit, 19
Standardbibliothek, 63
Standardfunktion, 63
static, 97, 100
stdin, 19
stdio.h, 7
stdio.h, 13
stdlib.h, 98
stdout, 19
Stern-Operator, 88
strcat, 71
strcmp, 71, 124
strcpy, 71
strftime, 72
String, 57, 59
Formattierungs-, 12
String-Endezeichen, 59
strlen, 71
DR
A
pointer01.c, 79
radius01.c, 66
schlechtes, 1
struct03.c, 87
summe.c, 63
summe02.c, 64
summe03.c, 63
summe03.h, 64
tunix.c, 3
vect05.c, 61
zeiger01.c, 16
Programmablauf, 35, 51
Programmblock, 5
Programmieren, 1
Programmiersprache, 1
objektorientiert, 100
Programmierstil, 1
Prototyp, 11
pt, 21
public, 89
Puffer
Eingabe-, 31
Punkt-Operator, 88
punkt.c, 86
punkt.h, 86
putchar, 14
Pythagoras, 86
Q
Qian Lingyun, xi
Qin Chuan, xi
Queue, 103
R
Rückgabewert, 71
rand, 72
Rechengenauigkeit, 27
Rechenoperation, 35
Rechts-Shift, 43
rechtsbündig, 24
Referenzierungsoperator, 42
Referenzsemantik, 63
register, 97, 98
Register
Maschinen-, 98
Reproduzierbarkeit, 9
Ritchie, Dennis, 6, 137
S
s, 26
scaled point, 21
scanf, 15
scanf, 27, 30
Genauigkeit, 32
INDEX
W
Wang Bin, xi
Wang Dongpeng, xi
Wang Jiyuan, xi
Wang Liming, xi
Wang Liyan, xi
Wang You, xi
Weikang Qian, xi
Wertsemantik, 63
while, 50
while-Schleife, 46, 48
wordpad, 3
Wu Fangqi, xi
Wu Jiejie, xi
X
x, 25
X, 25
Xu Chao, xi
Xu Fang, xi
Xu Jingwei, xi
Xu Renyi, xi
DR
A
T
Terminiertheit, 7
Thompsen, Ken, 6
time, 72
translation unit, 97
tunix.c, 3
Typ, 84
Visual Studio, Microsoft, 135
Vlissides, John M., 137
void, 71
volatile, 97
Vorzeichenoperator, 37
FT
Stroustrup, Bjarne, 6, 138
struct, 71, 83, 84, 89
struct kreis, 84
struct strecke, 84
structure member operator, 85
structure pointer operator, 88
Struktogramm, 53
Subroutine, 66
Subtraktion, 13, 21, 36
Subtraktionsoperator, 13
Sun, 6
Sun Xiaojun, xi
switch, 50
symbolische Konstante, 76
symbolischer Name, 76
145
U
u, 25
umwandeln, 1
Umwandlungseinheit, 97
unär
Operator, 42
Unär
Operator, 36, 37
und-Operator, 43
union, 71
unit
translation, 97
UNIX, 6, 31
Unterprogramm, 66
Utility, 1
V
Variable, 7
global, 97
lokal, 97
modulglobal, 97
Vektor, 57
Vergleich, 35
Verwaltung
Speicher-, 98
Verweisoperator, 16
Verzweigung, 45
vi, 3
Visual C++.NET, 4
Visual Studio, 3
Y
Yang Yifan, xi
Yao Fusheng, xi
Ye Weiqiang, xi
Yu Zhongwei, xi
Yuan Zhaoxing, xi
Z
Zeiger, 16, 31, 79, 80, 98, 99
Zentimeter, 21
Zerlegung, 63
Zhang Chenglei, xi
Zhang Fanjun, xi
Zhang Jie, xi
Zhang Lin, xi
Zhang Meiyan, xi
Zheng Haijiao, xi
Zhou Ying, xi
Zhu Da, xi
zusammengesetzte Zuweisung, 35, 38, 44
Zusammenhalt, 49, 65
funktionaler, 66
Zuweisung, 38
zusammengesetzte, 35, 38, 44
Zwischenraumzeichen, 30
INDEX
DR
A
Numbers
0, 19
1, 19
2, 19
FT
146
Zugehörige Unterlagen
Herunterladen