Sie sind auf Seite 1von 384

Sandini Bib

Programmieren lernen fr Teens

Sandini Bib

Sandini Bib

Bernd Brgmann

Programmieren lernen
fr Teens
Mit C

ADDISON-WESLEY
An imprint of Pearson Education
Mnchen Boston San Francisco Harlow, England
Don Mills, Ontario Sydney Mexico City
Madrid Amsterdam

Sandini Bib

Die Deutsche Bibliothek CIP-Einheitsaufnahme


Ein Titelsatz fr diese Publikation ist bei
Der Deutschen Bibliothek erhltlich

Die Informationen in diesem Buch werden ohne Rcksicht auf einen eventuellen
Patentschutz verffentlicht. Warennamen werden ohne Gewhrleistung der freien
Verwendbarkeit benutzt.
Bei der Zusammenstellung von Texten und Abbildungen wurde mit grter Sorgfalt
vorgegangen. Trotzdem knnen Fehler nicht vollstndig ausgeschlossen werden. Verlag,
Herausgeber und Autoren knnen jedoch fr fehlerhafte Angaben und deren Folgen weder
eine juristische Verantwortung noch irgendeine Haftung bernehmen. Fr
Verbesserungsvorschlge und Hinweise sind Verlag und Herausgeber dankbar.
Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und Speicherung in
elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle
und Arbeiten ist nicht zulssig. Fast alle Hardware- und Softwarebezeichnungen, die in
diesem Buch erwhnt werden, sind gleichzeitig eingetragene Warenzeichen oder sollten als
solche betrachtet werden.
Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt. Die
Einschrumpffolie zum Schutz vor Verschmutzung ist aus umweltvertrglichem und
recyclingfhigem PE-Material.

10

04 03 02 01
ISBN 3-8273-1802-5
c 2001 Addison-Wesley Verlag,

ein Imprint der Pearson Education Deutschland GmbH
Martin-Kollar-Strae 1012, D-81829 Mnchen/Germany
Alle Rechte vorbehalten
Lektorat: Christina Gibbs, cgibbs@pearson.de
Korrektorat: Simone Burst, Groberghofen
Produktion: Anna Plenk, aplenk@pearson.de
Satz: Hilmar Schlegel, Berlin gesetzt in Linotype Aldus/Palatino, Berthold Poppl College
Umschlaggestaltung: Barbara Thoben, Kln
Druck und Verarbeitung: Ksel, Kempten
Printed in Germany

Sandini Bib

Fr Veronika, Daniel und Gisela.

Sandini Bib

Sandini Bib

V
Vorwort
V.0
V.1
V.2
V.3

Programmieren geht ber Studieren


Was du zum Programmierenlernen mit diesem Buch brauchst
Allgemeine Hinweise fr den Leser, Eltern und Erwachsene
Webseite

VIII
IX
X
XI

Sandini Bib

V.0

Programmieren geht ber Studieren

Programmieren ist kein Kinderspiel. Oder etwa doch? Was ist eigentlich Programmieren?
Ich nehme einmal an, dass du, der Leser, mit der Verwendung von Computern
vertraut bist. Wenn du einen Computer anschaltest, laufen ein oder mehrere
Programme ab. Wenn du mit der Maus klickst, verarbeitet irgendein Programm
dieses Ereignis, z. B. um eine Datei zu lschen oder dein Lieblingsspiel zu starten. Du kannst am Computer Rennwagen steuern, Monster kloppen oder Schach
spielen, im Internet surfen oder Briefe schreiben. All das bewerkstelligt der Computer mit Programmen.
Ein Computerprogramm ist eine Liste von Anweisungen, die der Computer ausfhren kann. Die Anweisungen werden in einer besonderen Sprache verfasst,
einer Programmiersprache. In diesem Buch verwenden wir C (gesprochen wie
Zeh). C ist eine einfache und berschaubare Sprache mit nur sehr wenigen Wrtern, die recht leicht zu lernen ist und trotzdem ungeheuer vielseitig ist. Man
kann Rennwagen-, Monster- und Schachprogramme damit schreiben!
Welche Programmiersprache man als erste lernt, ist gar nicht so wichtig. Viel
wichtiger ist es zu lernen, wie man eine Aufgabe wie Schach spielen in computergerechte Hppchen zerlegt. Es gibt keine Programmiersprache, die den Befehl
spiele Schach mit mir enthlt. Das Geniale an Programmiersprachen und Computern ist jedoch, dass du mit simplen Befehlen komplizierteste Aufgaben lsen
kannst wenn du gelernt hast, computergerecht zu denken und die Aufgabe in
kleine Schritte zu zerlegen.
Ist Programmieren ein Kinderspiel? Ich wrde sagen, ja und nein. Ja, weil eine
Sprache wie C aus einfachen Befehlen besteht, die jeder lernen kann, auch wenn
C sich zunchst wie eine Geheimsprache liest. Und nein, weil man sich nichts
vormachen darf: Komplizierte Probleme wie Schach oder aufwndige Grafiksimulationen erfordern auch aufwndige Programme und Algorithmen. Sowas
wrde auf jeden Fall den Rahmen dieses Buches sprengen. Es lohnt sich aber auf
alle Flle, zumindest etwas Programmierluft geschnuppert zu haben, und dieses
Buch mchte dir zeigen, wie viel du spielerisch lernen kannst.
Vorneweg noch ein paar Bemerkungen:
Programmieren geht ber Studieren. Natrlich darfst und sollst du dieses
Buch grndlich lesen, am besten von vorne bis hinten. Aber das Wichtigste
sind die vielen Aufforderungen zum Selbermachen und zum Ausprobieren!
Ich muss mich fr das deutsch-englische Kauderwelsch entschuldigen. C verwendet englische Worte und berhaupt kommen die meisten Computerfachwrter aus dem Englischen (z. B. Computer, Rechner).
Ziel des Buches ist es nicht, dich in einen 3D-Spieleprogrammierguru oder
etwas hnlich Fortgeschrittenes zu verwandeln.
VIII

Vorwort

Sandini Bib

Ziel des Buches ist es, dir eine spielerische Einfhrung in die Grundlagen
der C-Programmierung zu geben. Denn wenn du erst einmal einige Grundlagen gelernt hast, erffnen sich dir viele Wege in die fantastische Welt der
Programmierung.
Jetzt gibt es nur noch eins: Computer anschalten, und los geht es mit unserem ersten Beispiel. Programmieren kannst du nur lernen, indem du Programme
schreibst.

V.1 Was du zum Programmierenlernen mit


diesem Buch brauchst
Um die Beispiele in diesem Buch ausprobieren zu knnen, brauchst du einen
Computer, Microsoft Windows und einen C-Compiler. Fr alle groen Betriebssysteme gibt es C-Compiler. Das liegt nicht zuletzt daran, dass Windows und
Unix zum grten Teil in C programmiert wurden. Die Beispiele zur Windowsprogrammierung laufen nur unter Windows, aber die Grundlagen zu C folgen
dem ANSI-Standard und funktionieren mit jedem ANSI-C-Compiler.
Dem Buch liegt eine CD mit der Vollversion des Borland C++Builder Standard
(Version 1.0) von 1997 bei. Zwar ist die aktuelle Version des Borland C++Builders
bei Version 5 angekommen (siehe www.borland.com), aber fr die Grundlagen
der Programmierung leistet selbst Version 1.0 noch ausgezeichnete Dienste. Alle
Bedienungsanweisungen im Buch zum Compiler und Windows beziehen sich
daher auf den beiliegenden Borland C++Builder Standard 1.0, kurz BCB. Die
wesentlichen Voraussetzungen fr die Verwendung von BCB sind:
Windows 95 oder neuer
Ungefhr 100 MB Platz auf der Festplatte
Und natrlich brauchst du ein CD-Laufwerk, eine Maus, einen Stromanschluss
und so weiter.
Die Beispiele des Buches findest du ebenfalls auf der CD. Die Programmtextbeispiele sind direkt aus den Programmdateien in den Buchtext eingebunden worden, also hoffe ich, dass sich keine Tippfehler eingeschlichen haben. Korrekturen
und Verbesserungsvorschlge sind immer willkommen.

V.1 Was du zum Programmierenlernen mit diesem Buch brauchst

IX

Sandini Bib

V.2 Allgemeine Hinweise fr den Leser, Eltern und Erwachsene


Nur zu, jeder darf diese Anmerkungen lesen, also auch du. Zur Erhhung der
Seriositt schalte ich vorbergehend um auf Sie.
Dieses Buch richtet sich an Kinder und Jugendliche ab ungefhr 15 Jahren ohne
Vorkenntnisse im Programmieren, die die Motivation zum selbststndigen Lernen mitbringen. Wesentliche Teile des Buches sind aber sicher ab 12 Jahren zugnglich, wenn ab und zu jemand Hilfestellung leisten kann. Begonnen hat dieses Projekt mit Programmbeispielen fr meinen damals 9-jhrigen Sohn und
der Beobachtung, wie viel Spa elementare Programmierung bei der richtigen
Hilfestellung machen kann. Dazu einige allgemeine Bemerkungen:
Programme knnen beliebig kompliziert sein, aber in ihrem Kern bestehen
sie aus verblffend wenigen, grundlegenden Bausteinen: Zahlen, Variablen,
Bedingungen, Schleifen, Funktionen, . . . genau diese Themen finden Sie in
den Kapitelberschriften wieder. Was uns allen von kommerziellen Computerprogrammen vertraut ist, die grafische Oberflche in Windows, Textverarbeitung, Internetzugang oder Computerspiele, wurde Schritt fr Schritt aus
einer kleinen Zahl simpler Anweisungen zusammengesetzt. Was die Welt der
Programme im Innersten zusammenhlt, sind Programmanweisungen wie
setze Variable gleich 1, wiederhole bis Zahl gleich 10 usw., und genau um
diese Grundlagen geht es in diesem Buch.
Verwendet wird die Programmiersprache C. C ist eine kleine, aber gleichzeitig
sehr mchtige und flexible Programmiersprache. C kann als Vorstufe zu C++
gelernt werden, denn C++ enthlt C als Untermenge. C++ ist heutzutage
die wichtigste Programmiersprache im professionellen Bereich. Viele C++Programmierer verwenden zu 90 % C.
Grafik und Farbe sind ein groer Motivationsfaktor. Die Kapitel 4, 8 und 11
widme ich deshalb einigen elementaren Elementen der Windowsprogrammierung, auch wenn die Windowsprogrammierung an und fr sich nichts
mit C zu tun hat. Wir lernen ein Fenster aufzumachen, darin zu malen und
die Maus zu verwenden. Richtige Windowsprogrammierung bedeutet, mit
Knpfen, Mens und Scrolleisten zu programmieren. Das wird in BCB sehr
schn mit einer grafischen Oberflche untersttzt, wrde aber den Rahmen
dieses Buches sprengen.
Ich habe versucht, das Buch zu schreiben, das ich mit 15 oder so gerne ber das
Programmieren gelesen htte: Kurz gefasst, aber nicht trivialisierend. Auf das
Wesentliche ausgerichtet, aber ohne groe Lcken. Jugendgerecht (also keine
Beispiele zur Steuererklrung), aber nicht Spa haben um jeden Preis in dem
Sinne, dass nur anspruchslose Beispiele besprochen werden.
Selbststudium ist so eine Sache. Was tun, wenn es nicht mehr weiter geht?
Wenn die Eltern, ein Lehrer oder irgendein anderer Experte helfen kann, ist

Vorwort

Sandini Bib

das natrlich ideal. Nehmen Sie sich Zeit fr Ihr Kind, lernen Sie mit. Vielleicht sollten Sie ihm auch einen anstndigen Computer spendieren, aber die
Hardware ist eigentlich nie das Problem.
Ganz entscheidend fr die erfolgreiche Verwendung dieses Buches ist es, sich
ernsthaft mit den vielen, vielen Beispielen auseinander zu setzen. Wenn die Beispiele irgendwann zu kompliziert aussehen, liegt das vielleicht daran, dass die
ersten Kapitel zu schnell abgehakt wurden. Dabei ist es nicht ntig, gleich beim
ersten Lesen alle Beispiele eines Kapitels vollstndig zu verstehen, aber:
Programmieren geht ber Studieren. Der Text regt unzhlige Male dazu an, das
Besprochene selber auszuprobieren. Gerade beim Programmieren lernen kommt
es ganz entscheidend darauf an, selber aktiv zu werden. Denn selbst eine berschaubare Programmiersprache wie C steckt voller berraschender Mglichkeiten Mglichkeiten, Fehler zu machen, aber auch Mglichkeiten, Aufgaben auf
verschiedene Weisen zu lsen. Das muss man erlebt haben, um es nachvollziehen zu knnen. Ein paar Stunden nach einem selbst gemachten subtilen Fehler
zu suchen kann wertvoller sein, als 100 Seiten gut gemeinte pdagogische, aber
graue Theorie zu studieren. Also, ermuntern Sie den Leser dieses Buches zum
Ausprobieren, Selbermachen, wieder Ausprobieren, Experimentieren.

V.3 Webseite
Im Internet findest du unter
www.proglernen.de

aktuelle Hinweise zum Buch, schau mal vorbei.

V.3 Webseite

XI

Sandini Bib

Sandini Bib

Inhaltsverzeichnis
V Vorwort
V.0 Programmieren geht ber Studieren
V.1 Was du zum Programmierenlernen mit diesem Buch brauchst
V.2 Allgemeine Hinweise fr den Leser, Eltern und Erwachsene
V.3 Webseite
0 Die
0.0
0.1
0.2
0.3
0.4
0.5
0.6

Welt und das Hallo


Hallo, Welt?
Vom Programmtext zum ausfhrbaren Programm
Installation von Borland C++Builder
Ein neues Projekt
Programmtexteingabe mit dem Editor
Ausfhren des Programms
Beispiele auf CD-ROM

VII
VIII
IX
X
XI
1
2
2
3
4
4
7
8

0.7

10

0.8

11

1 Hallo, Welt!
1.0 Der printf-Befehl
1.1 Zeichenketten
1.2 Die Funktion main
1.3 Der Befehlsblock
1.4 Ein Befehl nach dem anderen
1.5 #include
1.6 Krze ohne Wrze?
1.7 Eine Stilfrage

13
14
15
16
17
18
19
20
21

1.8

22

1.9

23

2 Zahlen, Variable, Rechnen


2.0 1 + 2 zum Aufwrmen
2.1 Definition von Variablen
2.2 Nenn das Kind beim Namen

25
26
29
30

Sandini Bib

2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12

Wertzuweisung mit =
Ausdruck und Befehl
Initialisierung von Variablen
Zahleneingabe mit der Tastatur
Die Grundrechenarten fr ganze Zahlen
Die Grundrechenarten fr Kommazahlen
Zahlen im berfluss
Wer wem den Vortritt lsst
Zuweisungsoperatoren
Inkrement- und Dekrement-Operatoren

2.13

41

2.14

42

3 Felder und Zeichenketten


3.0 Felder fr Zahlenlisten
3.1 Felder initialisieren
3.2 Mehrdimensionale Felder
3.3 Zeichen
3.4 Zeichenketten und Stringvariablen
3.5 Eine erste Unterhaltung mit deinem Computer
3.6 Ganze Zeilen lesen und schreiben
3.7 Kommentare
3.8
Geschichtenerzhler

43
44
46
47
47
49
51
53
54
56

3.9

58

3.10

59

4 Grafik
4.0 Hallo, Welt im Grafikfenster
4.1 Ein Hallo wie gemalt
4.2 Fensterkoordinaten
4.3 Pixel und Farbe
4.4 Linien
4.5 Rechtecke und Kreise
4.6 Fonts
4.7 Hilfe zu BCB und Windows SDK
4.8
Geradlinige Verschiebung
4.9
XIV

30
31
32
33
34
36
37
38
39
40

Inhaltsverzeichnis

Bunte Ellipsen

61
62
64
65
68
69
73
76
78
79
81

Sandini Bib

4.10

TextOut mit Format

83

4.11

85

4.12

87

5 Bedingungen
5.0 Bedingungen mit if
5.1 Vergleichsoperatoren, Wahr und Falsch
5.2 Logische Verknpfungen
5.3 Bedingungen mit mehreren ifs
5.4 Bedingungen mit if und else
5.5 Bedingungen mit else if

89
90
92
95
97
98
101

5.6

Entscheidungen fr den Bauch

102

5.7

Zufallszahlen

103

5.8

Ein Held des Zufalls

108

5.9

Bisektion

111

5.10

114

5.11

115

6 Schleifen
6.0 Zhlerschleife mit while
6.1 Schleife im Geschwindigkeitsrausch
6.2 Endlosschleifen
6.3 Schleifenkontrolle mit break und continue
6.4 Schleifen mit while, for und do

117
119
120
121
121
124

6.5

Summe ganzer Zahlen

128

6.6

Schleifen und Felder

128

6.7

#define und Felder

130

6.8

Eingabeschleife

131

6.9

Das doppelte Schleifchen

134

6.10

Primzahlen

135

6.11

Zeit in Sekunden

139
Inhaltsverzeichnis

XV

Sandini Bib

6.12

Bisektion mit Schleife

141

6.13

Grafik die hundertste Wiederholung

146

6.14

Linien aus Pixeln

148

6.15

Schachbrett

150

6.16

Histogramme

152

6.17

157

6.18

158

7 Funktionen
7.0 Funktionen
7.1 Funktion mit einem Argument
7.2 Funktion mit Rckgabewert
7.3 Funktion mit Argumenten und Rckgabewert
7.4 Prototypen von Funktionen
7.5 Headerdateien und Programmaufbau
7.6 Lokale Variable
7.7 Externe Variable
7.8 Statische und automatische Variablen
7.9 Statische Funktionen und externe Variablen
7.10
Zufallszahlen selbst gemacht
7.11

Rekursion

181

7.12

Rekursion mit Grafik

184

7.13

Labyrinth

187

7.14

202

7.15

203

8 Fensternachrichten
8.0 Achtung, Nachricht: Bitte malen!
8.1 Nachrichtenwarteschlage und Nachrichtenschleife
8.2 WM PAINT
8.3
Fenster auf, zu, gro, klein
8.4
XVI

161
162
165
165
167
169
170
173
175
177
179
180

Inhaltsverzeichnis

Klick mich links, klick mich rechts

205
206
210
211
212
215

Sandini Bib

8.5

Na, wo luft sie denn, die Maus?

218

8.6

Tastatur

221

8.7

Die Uhr macht tick

227

8.8

233

8.9

234

9 Datentypen
9.0 Von Bits und Bytes
9.1 Bit fr Bit
9.2 Datentypen
9.3 Datentypen klein und gro
9.4 Mein Typ, dein Typ: die Typenumwandlung
9.5 Konstante Variable mit const
9.6 Neue Namen fr alte Typen: typedef

235
236
238
239
240
243
247
247

9.7

Mathematische Funktionen

248

9.8

Die Sinusfunktion

249

9.9

Kreis und Rotation

256

9.10

Farbtiefe

264

9.11

Mandelbrot-Menge

265

9.12

274

9.13

274

10 Zeiger und Strukturen


10.0 Zeiger
10.1 Zeiger und malloc
10.2 Zeiger, Felder und wie man mit Adressen rechnet
10.3 Zeiger, Variablen, Funktionen
10.4 Strukturen
10.5 Zeiger auf Strukturen
10.6
10.7

277
279
282
285
286
290
292

Bibliotheksfunktionen fr Zeichenketten
und Felder

296

Zeichenketten kopieren

297
Inhaltsverzeichnis

XVII

Sandini Bib

10.8

Felder aus Zeigern und main mit


Argumenten

299

10.9

Dateien lesen und schreiben

301

10.10

WinMain

304

10.11

309

10.12

310

11 Bitmaps
11.0 Bitmaps erzeugen und laden
11.1 BitBlt
11.2 Bitmapfilmchen
11.3 Tonausgabe mit PlaySound
11.4

Bitmappuffer

326

11.5

Gekachelte Landkarte

334

11.6

Mit Pfeiltasten ber Land

340

11.7

Bitmaps mit transparenter


Hintergrundfarbe

344

Ein Held auf weiter Flur

347

11.8

XVIII

311
312
316
317
324

11.9

348

11.10

349

A Anhang
A.0 Rangordnung von Operatoren
A.1 Der Preprocessor
A.2 Die Schlsselwrter von C
A.3 Buch gelesen, was nun?

351
352
352
355
357

Stichwortverzeichnis

359

Inhaltsverzeichnis

Sandini Bib

0
Die Welt und
das Hallo
0.0
0.1
0.2
0.3
0.4
0.5
0.6

Hallo, Welt?
Vom Programmtext zum ausfhrbaren Programm
Installation von Borland C++Builder
Ein neues Projekt
Programmtexteingabe mit dem Editor
Ausfhren des Programms
Beispiele auf CD-ROM

2
2
3
4
4
7
8

0.7

10

0.8

11

Sandini Bib

0.0 Hallo, Welt?


Unser erstes Programmbeispiel ist ein absoluter Klassiker. Wir knnen gar nicht
anders anfangen, weil die Erfinder von C in ihrem ersten Buch so angefangen
haben. Das Programm soll
Hallo, Welt!

ausgeben. Hier ist das C-Programm dazu:


Hallo.c

#include <stdio.h>
int main()
{
printf("Hallo, Welt!\n");
getchar();
return 0;
}

So sieht also ein Programmtext (the code) aus, viele sonderbare Zeichen. Aber
lass dich nicht abschrecken, am besten liest du noch mal langsam jeden Buchstaben und jedes Zeichen. Mittendrin kannst du die Zeichen Hallo, Welt! entdecken, das Drumherum werde ich im nchsten Kapitel erklren.
Zuvor mssen wir aber eine groe Hrde nehmen. Wie verwandeln wir einen
solchen Text in etwas, das der Computer ausfhren kann? Wie lassen wir unser selbst gemachtes Programm ablaufen? Auf welche Art wird Hallo, Welt!
am Bildschirm ausgegeben? Diese Probleme mssen erst einmal gelst werden,
bevor wir fortfahren knnen.

0.1 Vom Programmtext zum ausfhrbaren Programm


Die einfachste Lsung ist: Wir verwenden eine Integrierte Entwicklungsumgebung, also ein spezielles Programm, mit dem wir alle ntigen Schritte durchfhren knnen. Wen wunderts, natrlich verwendet man Programme um Programme zu schreiben. (Wie war das gleich noch mit der Henne und dem Ei, was
war zuerst da?) Das Programm Borland C++Builder fr Microsoft Windows ist
eine gute Wahl und als Vollversion 1.0 auf der CD des Buches enthalten, siehe
Kapitel V.1. Ich werde oft von dem Compiler sprechen, und damit ist der Borland C++Builder Standard (Version 1.0) gemeint.
Eine typische Entwicklungsumgebung hat mehrere Teile, insbesondere eine Projektverwaltung, einen Editor, einen Compiler, einen Linker und einen Debugger:
1. Mit Projektverwaltung ist die Verwaltung verschiedener Programmierpro-

jekte gemeint. So halten wir Ordnung, denn jedes Projekt verwendet mehrere
Dateien.
2

Kapitel 0 Die Welt und das Hallo

Sandini Bib

2. Der Editor dient zur Eingabe und Speicherung des Programmtextes.


3. Der Compiler bersetzt einzelne Dateien mit Programmtext in Maschinen-

sprache, die der Mikroprozessor deines Computers ausfhren kann. Fr jede


Datei wird das Ergebnis in einer Objektdatei gespeichert.
4. Der Linker erzeugt das ausfhrbare Programm (das Executable). Dazu kom-

biniert er die Objekte, die du hergestellt hast, mit Objekten aus der Bibliothek
des Compilers. Viele oft bentigte Funktionen, z. B. fr mathematische Funktionen oder fr die Textausgabe, kannst du in mitgelieferten Bibliotheken
finden.
5. Der Debugger hilft bei der Fehlersuche im Programm. Welche Fehler? Na

ja, jeder macht Fehler, selbst Profis, und mit dem Debugger kannst du ein
Programm Zeile fr Zeile und Anweisung fr Anweisung ablaufen lassen
und untersuchen, was genau vor sich geht.
Und hier sind noch ein paar Erluterungen zu englischen Begriffen. Die Schritte 3 und 4 werden unter dem Befehl make (machen)
oder build (bauen) zusammengefasst. Oft nennt man den gesamten
Prozess einfach Kompilieren. brigens heit compile zusammenstellen, link
verketten oder verknpfen, Objekte sind objects und Bibliothek heit library.
Ein bug ist ein lstiges Insekt, und so heien bei Programmierern die Programmierfehler. Fehler entfernen nennt man dann debug, also entwanzen.

0.2 Installation von Borland C++Builder


Im Folgenden beschreibe ich kurz die ntigen Schritte, unser Hallo-Programm
zum Laufen zu bringen. Borland C++Builder (kurz BCB) erledigt viele Dinge
automatisch. Mehr Details findest du in dem Begleitmaterial von BCB. Die Installation und Bedienung von BCB ist einfach und logisch, aber wenn du erst
wenig Erfahrung mit der Bedienung von Windowsprogrammen hast, bittest du
am besten jemanden um Hilfe. Hier ist der erste Schritt:
Installiere BCB.
Lege die Buch-CD ins Laufwerk. In Windows ffne unter Arbeitsplatz mit einem Doppelmausklick das Verzeichnis mit den Daten auf
der CD. Doppelklicke auf das Verzeichnis Cbuilder. Die Installation startest du
mit einem Doppelklick auf Setup.exe im Verzeichnis Cbuilder. Insbesondere
solltest du bei Setup-Typ die vollstndige Installation whlen, damit alle Hilfedateien kopiert werden. Die vollstndige Installation bentigt ungefhr 100 Megabyte Festplattenplatz. Also immer schn auf Weiter und Ja klicken, bis das
Fenster Installation abgeschlossen erscheint. Die angebotene Hilfe zum Borland
C++Builder kannst du jetzt oder spter lesen. (Das dort erwhnte Verzeichnis
SAMS gibt es leider in dieser Version nicht.)

0.2

Installation von Borland C++Builder

Sandini Bib

0.3 Ein neues Projekt


Als Erstes will ich dir zeigen, wie du mit BCB ein neues Projekt von Anfang bis
Ende selber machst. So beginnen wir ein neues Programmprojekt:
Starte BCB.
Wenn du die Voreinstellungen nicht gendert hast, msstest du BCB unter
Start, Programme, Borland C++Builder finden. Auf C++Builder klicken.
Mehrere Fenster erscheinen. Beim ersten Start fragt BCB, ob gewisse Standarddateien erstellt werden sollen. Klicke Ja.
Starte ein neues Projekt.
Oben links in der Menleiste von BCB auf Datei klicken, dann auf Neu.
Ein Fenster mit berschrift Neue Eintrge erscheint. Hier ist es wichtig, die
Textbildschirm-Anwendung auszuwhlen (anklicken, dann OK klicken).
Speichere das Projekt mit neuem Namen ab.
In der Menleiste von BCB Datei, dann Projekt speichern unter whlen.
Ein Dateidialog erscheint. Wie du siehst, bietet dir BCB an, dein Projekt
mit Dateiname Project1.mak im Verzeichnis C:\Programme\Borland\
CBuilder\Projects abzuspeichern. Tippe unter Dateiname HalloWelt
.mak ein. Klicke auf Speichern. Du kannst aber auch ein neues Verzeichnis
erzeugen, dieses auswhlen und dann das Projekt im neuen Verzeichnis
abspeichern.
Starte BCB neu, probehalber.
Beende BCB (unter Datei, oder klicke das Kreuz rechts oben). Starte BCB
neu und suche unter Datei, Projekt ffnen oder Neu ffnen nach deinem
Projekt HalloWelt.mak. Anklicken. Hat es geklappt?
Entweder war dir alles sonnenklar und zu ausfhrlich, weil du schon ein
Windowskenner bist, oder meine schnen Erluterungen haben dich zum Erfolg
gefhrt, oder es ging daneben. Falls es nicht geklappt hat, nicht aufgeben. Auf
deinem Rechner kann es kleine Unterschiede geben, z. B. bei den Namen der
Verzeichnisse. Normalerweise wird die linke Maustaste verwendet. Manchmal
muss man mit der Maus doppelklicken, um eine Aktion auszulsen, manchmal
reicht ein Einfachklick. Bis zu diesem Punkt war alles eine bung in Windowsbedienung und du findest sicher jemanden, der dir weiterhelfen kann. Das
gilt auch fr den nchsten Schritt, denn jetzt wollen wir den Text unseres HalloProgramms eingeben.

0.4 Programmtexteingabe mit dem Editor


Wenn du unser Projekt HalloWelt.mak neu geffnet hast, siehst du drei Fenster.
Oben findest du die groe Menleiste vom BCB mit den vielen Knpfen. Links
ist ein Fenster Objektinspektor. Das brauchen wir nicht, du kannst es mit dem
Kreuz rechts oben verschwinden lassen. Und dann gibt es noch ein Fenster fr
eine Datei namens
4

Kapitel 0 Die Welt und das Hallo

Sandini Bib

HalloWelt.cpp

Dies ist das Editorfenster, das auf deine Texteingaben und Textnderungen wartet. Die Endung .cpp steht fr C++-Dateien. C++ ist eine Erweiterung der Programmiersprache C. berhaupt kann Borland C++Builder mehr als nur reines
C oder C++, z. B. enthlt BCB alles, um grafische Programme mit Fenstern und
Knpfen zu schreiben.
Im Prinzip knnten wir die Datei HalloWelt.cpp fr unseren Programmtext
verwenden, und das kannst du spter einmal ausprobieren. Beim Kompilieren
erzeugt BCB fr jedes Projekt aber um die 5 MB zustzliche Dateien auf der
Festplatte. Weil wir zu jedem Projekt typischerweise mehrere Beispiele besprechen wollen, werden wir BCB die Datei HalloWelt.cpp zur Projektverwaltung
berlassen und unseren Programmtext in zustzlichen Dateien speichern. Die
verschiedenen Dateien mit dem Programmtext knnen wir dann je nach Bedarf
in ein und demselben Projekt verwenden.
Wir werden deshalb eine neue Datei
Hallo.c

fr unser C-Programm erzeugen und die Datei HalloWelt.cpp so ndern, dass


BCB unser C-Programm kompilieren kann:
Vereinfache HalloWelt.cpp.
Lsche alle Zeilen in HalloWelt.cpp bis auf die vier Zeilen
Project1.cpp

//
#include <vcl\condefs.h>
#pragma hdrstop
//

Falls du noch nie mit einem Editor gearbeitet hast, gibt es hier ein paar Tipps:
Per Mausklick oder mit den Pfeiltasten kannst du den Textcursor im Text
bewegen. Der Textcursor markiert die Stelle, an der Text eingegeben und gelscht werden kann. Einzelne Buchstaben kannst du mit den Tasten Entf
und der Zurcktaste lschen. Ganze Zeilen verschwinden problemlos, wenn
du mit der Maus oder den Pfeiltasten erst Text markierst und dann die Tastenkombination Strg + X bettigst. Die Maus markiert Text, wenn du sie bei
gedrckter linker Maustaste im Text bewegst. Die Pfeiltasten markieren Text,
wenn du gleichzeitig die  Taste drckst (wie fr groe Buchstaben). Praktisch: Mit Strg + Z kannst du die letzte Eingabe oder Lschung rckgngig
machen!
Also, ran an die Arbeit, HalloWelt.cpp bekommt einen Zeilenschnitt. Lsche
alle bis auf die vier gezeigten Zeilen. (Wenn du mit der Maus ganz links in
den Text klickst, erscheint ein rotes Stoppschild fr den Debugger. Klicke auf
das Stoppschild, um es zu entfernen.)
0.4 Programmtexteingabe mit dem Editor

Sandini Bib

Speichere ab.
Mit Datei, Speichern oder mit Strg + S kannst du die neue Version von
HalloWelt.cpp abspeichern. fter mal abspeichern. Stell dir vor, du tippst
fleiig eine Stunde lang, dann strzt der Rechner ab und alles ist weg.
Erzeuge eine leere Datei Hallo.c
Dazu klickst du unter Datei auf Neue Unit.
Unit heit Einheit, in
diesem Fall bekommen wir eine Datei namens Unit1.cpp. Der Editor hat jetzt
am oberen Rand zwei Auswahlflchen, die du anklicken kannst, um zwischen
verschiedenen Dateien umzuschalten. BCB versucht wieder, uns mit Text in
Unit1.cpp zu helfen. Lsche bitte den gesamten Text, alle Zeilen. Speichere
die leere Datei mit Datei, Speichern unter unter dem Namen Hallo.c. An
der Endung .c erkennt BCB, dass es sich um C-Code handelt.
Gebe den Text des Hallo-Programms ein.
Hier ist der Text fr die Datei Hallo.c:
Hallo.c

#include <stdio.h>
int main()
{
printf("Hallo, Welt!\n");
getchar();
return 0;
}

Achtung, wichtige Durchsage: Jedes Zeichen muss stimmen! Du


musst auf der Tastatur die richtigen Zeichen finden, insbesondere
# { } \ ;. Auf meiner Tastatur muss ich die 7 zusammen mit der
AltGr Taste rechts von der Leertaste drcken, um { zu erhalten. Der Editor
versucht dir zu helfen, indem er zur besseren bersicht manche Wrter farbig
oder fett darstellt.
Speichere ab.
Der Compiler bezieht sich auf die abgespeicherte Version. Im Men unter
Datei auf Speichern klicken (oder Strg + S drcken). Wenn der Text im
Editor nicht mehr mit der gespeicherten Datei bereinstimmt, steht in der
linken unteren Ecke des Editors Gendert.
Damit ist dein Projekt vollstndig. Soweit die Theorie, wir schreiten zur Praxis.

Kapitel 0 Die Welt und das Hallo

Sandini Bib

0.5 Ausfhren des Programms


Jetzt wird es spannend, denn als Nchstes wird sich herausstellen, ob der Compiler von BCB mit unserem Programm etwas anfangen kann.
Kompiliere das Programm.
Im BCB-Men unter Projekt findest du den Eintrag Projekt neu compilieren. Nur zu! BCB wird versuchen, unseren Programmtext in Hallo.c in
ein ausfhrbares Programm zu bersetzen. Es erscheint ein Fenster mit der
berschrift Compilieren.
Wenn es funktioniert hat, werden 0 Warnungen und 0 Fehler angegeben.
Wenn es Probleme gibt, werden die Warnungen und Fehler gezhlt. Quit-

tiere diese Meldung mit OK und gehe auf Fehlersuche. Den Programmtext bitte genau berprfen, korrigieren, abspeichern und erneut komwarning) ausgegeben
pilieren! Es knnte auch eine Warnung (
werden. Auch diese solltest du in unserem Beispiel durch kleine Korrekturen im Code zum Verschwinden bringen. Am unteren Rand des Editorfensters gibt BCB die Liste der Fehlermeldungen aus. Diese sind nur
mit etwas bung richtig zu verstehen. Wenn du auf eine Meldung mit
[C++ Error] doppelklickst, springt der Textcursor in die Nhe des ProError heit
blems, manchmal springt er aber in die falsche Zeile.
Fehler. Nicht lockerlassen, wir haben es fast geschafft.
Kompiliere das Programm und lass es laufen: F9 .
exWenn keine Fehler gemeldet werden, hast du ein ausfhrbares (
ecutable) Programm erzeugt. Das Programm steht als HalloWelt.exe im
Projektverzeichnis. Du kannst es durch Doppelklicken in Windows ausfhren oder in BCB einfach F9 drcken. Diese Abkrzung findest du im
BCB-Men unter Start und dann nochmals Start. F9 berprft erst, ob
eine nderung des Programms gespeichert wurde, kompiliert neu, falls ntig, und fhrt dann das Programm aus. Mit Strg + F9 werden nur die zuletzt
genderten Dateien neu kompiliert. Wir haben mit der Kompilierung unter
Menpunkt Projekt angefangen, damit du auf jeden Fall die Erfolgsmeldung
siehst.
Drcke F9 !
Was macht das Programm, wenn es aufgerufen wird? Es sollte ein Fenster
erscheinen mit der triumphalen Ansage
Hallo, Welt!

0.5 Ausfhren des Programms

Sandini Bib

Und weil es so schn ist so sieht das Fenster bei mir aus:

Drcke die Eingabetaste (die Enter- oder Neuezeiletaste), und das Fenster
verschwindet. Da kann ich nur sagen: Herzlichen Glckwunsch!
Es kann sein, dass bei dir kein Fenster erscheint und stattdessen das HalloProgramm den ganzen Bildschirm einnimmt. In Windows kannst du einstellen,
ob eine Textbildschirmanwendung wie unsere in einem Fenster oder als Vollbild abluft. Das Textfenster wird unter Windows 95 mit C:\Windows\System\
Conagent.exe erzeugt. Wenn du mit der rechten Maustaste auf diese Datei
klickst, kannst du unter Eigenschaften, Bildschirm zwischen Vollbild und
Normal whlen. Mit der Tastenkombination Alt + kannst du bei laufendem
Programm zwischen Vollbild und Fenster umschalten, wenn dies nicht unter Eigenschaften von Conagent.exe abgestellt wurde.
Wenn ein Projekt erst einmal angelegt ist, wirst du immer wieder nderungen am Programmtext vornehmen, dein Programm laufen lassen, Text ndern,
wieder ausprobieren, usw. BCB kann vor dem Kompilieren automatisch alle nderungen abspeichern. Diese Option solltest du unbedingt im Men unter Optionen, Umgebung, Vorgaben anschalten, indem du Autospeichern fr Editordateien und Desktop per Mausklick abhakst.

0.6

Beispiele auf CD-ROM

Jetzt weit du, wie du mit BCB ein neues Projekt fr eine TextbildschirmAnwendung anlegen kannst. Bevor ich unsere kleine Einfhrung in BCB beende,
mchte ich dir noch einige allgemeine Hinweise zur Verwendung von BCB mit
diesem Buch geben. Auf der CD des Buches findest du im Verzeichnis
ProgLernen

Kapitel 0 Die Welt und das Hallo

Sandini Bib

den Programmtext fr die meisten Beispiele. Die Namen der dazugehrigen Dateien auf der CD werden im Buch rechts oben vom Programmtext angezeigt, z.B.
Hallo.c. Als Erstes solltest du die Datei ProgLernen\LiesMich.txt lesen, um
herauszufinden, wie die Dateien in ProgLernen organisiert sind. Unter Arbeitsplatz in Windows findest du dein CD-Laufwerk. Mit Doppelmausklick kannst du
dir die Dateien auf der CD anzeigen lassen. Mit Doppelklick auf LiesMich.txt
wird der Text in dieser Datei angezeigt.
Am besten kopierst du die Programmbeispiele auf deine Festplatte. Doppelklicke
auf Setup.bat im Verzeichnis ProgLernen, um dieses Verzeichnis nach
C:\ProgLernen

zu kopieren. Die CD wird dann nur noch bentigt, falls eine Datei ungewollt
abgendert wurde. Du kannst das Verzeichnis auch per Hand mit der Maus kopieren, aber die Dateien von der CD bleiben dann schreibgeschtzt.
Die Beispiele habe ich auf der CD in drei Gruppen eingeteilt, und zwar in Beispiele ohne Grafik im Textfenster wie das Hallo-Welt-Beispiel, und in einfache
und kompliziertere Beispiele mit Grafik. Dazu gibt es dreierlei vorgefertigte Projektdateien, und zwar jeweils eine .mak-Datei und eine .cpp-Datei mit demselben Namen.
Die meisten Beispiele bentigen auer den .mak- und .cpp-Dateien noch genau
eine zustzliche .c-Datei, bevor sie kompiliert werden knnen. In der Menleiste unter Projekt kannst du Dateien zum Projekt hinzufgen oder aus dem
Projekt entfernen. Im Dateidialog zur Auswahl einer neuen Datei werden nur
die Dateien angezeigt, die du unter Dateityp ausgewhlt hast. Um eine C-Datei
gegen eine andere innerhalb desselben Projekts auszutauschen, entfernst du die
eine und fgst die andere hinzu. Mit F9 kannst du das neue Beispiel dann testen.
Wenn du ein fertiges Projekt als Vorlage fr eigene Projekte verwenden mchtest, musst du normalerweise nur die .mak-, .cpp- und .c-Dateien in ein neues
Verzeichnis kopieren. Es kann aber vorkommen, dass dabei falsche Verzeichnisangaben bernommen werden. Im BCB-Men findest du unter Datei die Option Projekt speichern unter. Dabei werden die .mak- und die .cpp-Datei kopiert. Zustzliche .c-Dateien werden aber nicht kopiert, sondern bleiben mit der
alten Verzeichnisangabe im Projekt stehen. Mit Datei, Speichern unter kannst
du diese Dateien dann in das neue Verzeichnis kopieren.
Fr Textbeispiele habe ich dir gezeigt, wie du mit BCB ein neues Projekt erzeugst,
ohne irgendwelche Dateien aus ProgLernen zu bentigen. Falls du aber aus irgendeinem Grund stecken bleibst, kannst du es mit dem TextFenster-Projekt auf
der CD versuchen (TextFenster.mak und TextFenster.cpp). Bei den Grafikbeispielen werden wir auf die entsprechenden Projekte auf der CD zurckgreifen,
weil die Projekte dann etwas komplizierter sind, siehe Kapitel 4.
Auch wenn du die Projektdateien von der CD verwendest, mchte ich dich doch
ermuntern, einige der Programmtexte fr die C-Dateien selber einzutippen.
0.6

Beispiele auf CD-ROM

Sandini Bib

Beim Eintippen bekommst du bung fr deine eigenen Programme und du liest


jede Zeile sehr genau. Wenn du ohne nachzudenken jeden Programmtext von
der CD in deine Projekte kopierst, bersiehst du wahrscheinlich einige wichtige
Einzelheiten. Bei langen oder komplizierten Beispielen brauchst du dich aber
auch nicht unntig mit Tippbungen aufhalten. Hole dir den Programmtext
von der CD und verwende das fertige Beispiel als Ausgangspunkt fr eigene
Experimente.

0.7
Hoffentlich ging alles glatt. Wenn nicht, solltest du wie gesagt einfach jemanden
um Hilfe bitten. Falls du dich mit der Bedienung von Windows (Programminstallation, Verzeichnisse, Dateien usw.) und Windowsprogrammen wie BCB noch
nicht gut auskennst, kann dir sicher jemand helfen. Als Nchstes wollen wir mit
dem eigentlichen Programmieren anfangen und endlich erklren, warum unser Programm tut, was es tut! Hier noch mal das Wichtigste dieses Kapitels in
Stichpunkten:
Immer alles ganz genau lesen! In C hat jedes Zeichen seine eigene Bedeutung,
z. B. drfen \ und /, oder , . : ; nicht verwechselt oder weggelassen
werden.
C-Programme werden als Text in eine Datei eingegeben (mit Endung .c), der
dann von einem Compiler in ein ausfhrbares Programm bersetzt wird.
Selbst die Standardversion 1.0 vom Borland C++Builder kann viel mehr, als
wir in diesem Buch besprechen knnen oder wollen, denn es geht uns schlielich um die Grundlagen von C.
Lass dich nicht von anfnglichen Schwierigkeiten bei der Bedienung des Editors oder des Compilers abschrecken. Bald wird dir das einfach und logisch
vorkommen.

10

Kapitel 0 Die Welt und das Hallo

Sandini Bib

0.8
1. Im BCB-Men findest du unter Optionen, Umgebung, einige ntzliche

und lustige Sachen. Unter Editor habe ich die Tabstops auf 3 5 gesetzt,
was zwei Leerzeichen pro Tab bedeutet (die Tabulator-Taste findest du links
vom Q). Die Voreinstellung ist vier. Unter Anzeigen kannst du die Schriftart
und Schriftgre im Editor ndern und unter Farben die, na ja, das kannst
du raten. Jedes Optionsfenster hat seine eigene Hilfefunktion rechts unten.
2. Werfe von Windows aus einen Blick in das Verzeichnis, in dem du dein Projekt
gespeichert hast, z. B. C:\Programme\Borland\CBuilder\Projects. Dort
wirst du jede Menge zustzlicher Dateien finden. Mit markiert BCB Si-

cherheitskopien von deinen Dateien. So findest du die vorhergehende Version, falls es Probleme mit der neuesten Version gibt. Per Doppelklick auf
HalloWelt.exe kannst du das Programm unabhngig von BCB starten. Mit
Doppelklick auf HalloWelt.mak kannst du das Hallo-Projekt in BCB ffnen.
3. Im BCB-Men kannst du dir mit Ansicht, Projektverwaltung ein prakti-

sches Fensterchen aufmachen lassen, in dem du mit dem Symbol Plus Dateien
hinzufgen und mit Minus Dateien entfernen kannst. Zum Entfernen musst
du als Erstes die Datei per Mausklick anwhlen. Ein Doppelklick auf eine
Datei in der Projektverwaltung holt diese in den Editor. Nach Projekt speichern unter kann es sein, dass die Projektverwaltung nicht die neuen Namen
anzeigt. In diesem Fall BCB neu starten.
4. Eine nderung der C-Dateien eines Projekts werden von BCB automatisch
mit USEUNIT in der .cpp-Datei notiert. Wenn du per Hand die Zeile mit
USEUNIT entfernst oder den Dateinamen in USEUNIT nderst, wird das in der
Projektverwaltung richtig angezeigt. Das kannst du mit HalloWelt.cpp aus-

probieren. Diese Art und Weise, Projekte zu verwalten, ist eine Eigenart von
BCB und hat mit C nichts zu tun.

0.8

11

Sandini Bib

Sandini Bib

1
Hallo, Welt!
1.0
1.1
1.2
1.3
1.4
1.5
1.6
1.7

Der printf-Befehl
Zeichenketten
Die Funktion main
Der Befehlsblock
Ein Befehl nach dem anderen
#include
Krze ohne Wrze?
Eine Stilfrage

14
15
16
17
18
19
20
21

1.8

22

1.9

23

Sandini Bib

In diesem Kapitel nehmen wir unser erstes Programmbeispiel Buchstabe fr


Buchstabe und Zeichen fr Zeichen auseinander, um herauszufinden, wie es
funktioniert.

1.0

Der printf-Befehl

Schauen wir uns das Hallo-Programm einmal genau an:


Hallo0.c

#include <stdio.h>
int main()
{
printf("Hallo, Welt!\n");
getchar();
return 0;
}

Die Zeile
printf("Hallo, Welt!\n");

ist fr die Ausgabe verantwortlich. print heit drucken, und Hallo, Welt! ist
auch zu sehen, also leuchtet es ein, dass dies ein C-Befehl ist, der Hallo, Welt!
auf dem Bildschirm ausdruckt. Dieser Befehl setzt sich wie folgt zusammen: Du
), zwischen den Klammern
siehst das Wort printf, zwei runde Klammern, (
den Text "Hallo, Welt!\n" und am Ende der Zeile einen Strichpunkt ; .
Machen wir doch gleich ein Experiment. Starte BCB und lade dein Hallo-Projekt
aus Kapitel 0. Gehe ins Editorfenster von Hallo.c und ndere Welt in Pizza.
Drcke F9 . Dann ndere dieses Wort wieder in Welt. Drcke F9 . In der Tat,
dies ist der Text, der ausgegeben wird.
Als Nchstes entferne den Strichpunkt am Ende der Zeile mit printf. Weg damit. F9 drcken. Was sagt der Compiler dazu? Bei mir meldet er
[C++ Error] Hallo.c(6): Call of nonfunction.

So etwas passiert fter, man bekommt eine mehr oder weniger verstndliche
Fehlermeldung. Error bedeutet, dass ein Fehler vorliegt. Der Strichpunkt wird
bentigt, um Befehle zu trennen. Deshalb kommt BCB durcheinander. In C mssen Befehlszeilen wie diese immer mit einem Strichpunkt beendet werden. Der
Compiler nimmt das sehr genau. Also fge den Strichpunkt wieder ein, kompiliere und berzeuge dich davon, dass das Programm wieder luft.

14

Kapitel 1 Hallo, Welt!

Sandini Bib

1.1 Zeichenketten
Jetzt nehmen wir den Ausdruck
"Hallo, Welt!\n"

unter die Lupe. C kennt verschiedene Datenformate. Hier haben wir es mit einer
string (Faden, Schnur) genannt, zu tun. Die AnfhZeichenkette, auch
rungszeichen dienen dazu, mehrere Buchstaben und Zeichen (
characters)
zu einem String zusammenzufassen. Lass das Programm noch einmal laufen,
die Anfhrungszeichen erscheinen nicht in der Ausgabe. Aber was ist mit dem
backslash, Gegenschrgstrich)
\n geschehen? Mit dem Schrgstrich \ (
werden spezielle Sonderzeichen und Steuerzeichen eingegeben. \n ist das Neunewline). Kannst du raten, wie
ezeilezeichen (
"\nHallo,\nWelt!\n"

ausgegeben wird? Gleich ausprobieren. \n zhlt als genau ein Zeichen.


print heit drucken. printf ist der Name einer Funktion,
Wie gesagt,
die in unserem Beispiel auf dem Bildschirm druckt. Das f in printf steht fr
formatiertes Drucken. Sie wird aufgerufen, indem das, was gedruckt werden soll,
zwischen runde Klammern geschrieben wird. Der Begriff Funktion wird in C
und anderen Programmiersprachen verwendet, wenn verschiedene Operationen
unter einem Namen zusammengefasst sind. In C sieht ein Funktionsaufruf im
Allgemeinen so aus:
Name (Argumente)

In unserem Fall ist der Name der Funktion printf. Das Argument der Funktion,
also das, was an die Funktion zur Bearbeitung bergeben wird, ist der String
"Hallo, Welt!\n".
Somit kannst du jetzt die Befehlszeile
printf("Hallo, Welt!\n");

lesen. Sie ruft die Funktion printf auf, um einen String zu drucken, das Sonderzeichen \n sorgt fr den Zeilenumbruch und der Strichpunkt ; beendet die
Befehlszeile.

1.1 Zeichenketten

15

Sandini Bib

1.2 Die Funktion main


Unser Programm besteht aber aus mehr als einer Zeile:
Hallo0.c

#include <stdio.h>
int main()
{
printf("Hallo, Welt!\n");
getchar();
return 0;
}

Betrachte die Zeile mit


main()

Sieht das nicht verdchtig nach einer Funktion aus, einer ohne Argumente? Richmain bedeutet wichtigst und ist der Name der wichtigsten Funktion
tig!
in jedem C-Programm. Wenn du dein kompiliertes Programm startest, geschehen verschiedene Dinge, z. B. wird von BCB dafr gesorgt, dass Windows ein
Fenster aufmacht. Aber dann kommt der Punkt, an dem die Kontrolle an dein
Programm bergeben wird: Es wird die Funktion main aufgerufen. Dazu muss
main definiert sein und du, der Programmierer, musst die Funktion main erfinden. Sie ist die wichtigste Funktion berhaupt, weil ohne sie der Compiler nicht
wei, wo er mit dem Ausfhren des Programmes anfangen soll. Natrlich htte
man dieses Problem auch anders lsen knnen (z. B. knnte man immer in der
ersten Zeile anfangen), aber in C fngt das Programm immer mit einem Aufruf
von main an.
Klarer Fall, das muss ausprobiert werden. Verwandle den Namen main in hallo.
Bei mir beschwert sich der Compiler mit
[Linker Error] Undefined symbol _main

Der Linker, der das ausfhrbare Programm erzeugen soll, teilt uns mit, dass ihm
die Funktion main fehlt. Was passiert, wenn du den Namen Main statt main verwendest? Du wirst feststellen, dass C groe und kleine Buchstaben unterscheidet.
Z.B. gibt es auch Probleme mit Printf oder pRiNtF.
In unserem Beispiel wird die Funktion main nach folgendem Schema definiert:
int main()
{

Befehle
}

Die runden Klammern umklammern die Argumente, in unserem Fall wird main
ohne Argumente aufgerufen. Danach folgen die Befehle in geschweiften Klammern. Allermeistens gilt: Klammer auf braucht Klammer zu. Klammern sind
16

Kapitel 1 Hallo, Welt!

Sandini Bib

schlielich dazu da, Objekte zu Gruppen zusammenzufassen. Wir kennen jetzt


schon drei Beispiele, { ... }, ( ... ) und " ... ".

1.3 Der Befehlsblock


Man nennt
{

Befehle
}

den Krper oder


body der Funktion oder auch den Befehlsblock der
Funktion. Eine Funktion ausfhren bedeutet, die Befehle in ihrem Befehlsblock
einen nach dem anderen abzuarbeiten.
In unserem Beispiel gibt es drei Befehle. Das printf haben wir schon besprochen, der zweite Befehl ist
getchar();

Brauchen wir den? Ich schlage vor, du entfernst diese Zeile einmal. Wenn du jetzt
mit F9 das Programm ausfhrst, siehst du kurz ein Fenster aufblitzen und wieget chader verschwinden. Der Befehl getchar bedeutet hole Zeichen (
racter). Diese Funktion rufen wir ohne Argumente auf. Es werden Zeichen von
der Tastatur angenommen, bis du auf die Eingabetaste drckst. Ausprobieren, du
kannst ins Programmfenster hineintippen, bis du die Eingabetaste drckst. In anderen Worten, damit unser Programm nach der Textausgabe nicht gleich wieder
das Fenster zumacht, lassen wir es mit getchar auf die Eingabetaste warten.
Der dritte und letzte Befehl ist
return 0;

Funktionen knnen so definiert werden, dass sie nach Beendigung ihrer Arbeit
return heit hier zurckgeben und die Zahl ist 0.
eine Zahl abliefern.
Im Moment spielt der Wert keine Rolle. Aber dem Compiler muss im Voraus
gesagt werden, welche Art von Ergebnis main berechnet. Das Wrtchen int in
int main()

integer) Zahl berechnet. Eine


bedeutet, dass diese Funktion eine ganze (
eingehende Diskussion von Funktionen vertagen wir auf Kapitel 7.
Somit ist klar, was die Zeilen
int main()
{
printf("Hallo, Welt!\n");
getchar();
return 0;
}

1.3 Der Befehlsblock

17

Sandini Bib

bedeuten. Sie enthalten die Definition der Funktion main. Wird main aufgerufen, wird der Befehl printf("Hallo, Welt!\n"); ausgefhrt. Dann wird
der Befehl getchar(); ausgefhrt, der auf die Eingabetaste wartet. Der Befehl
return 0; beendet die Funktion main und damit das Programm.
Wie raffiniert! Eine Funktion kann also andere Funktionen wie z. B. printf aufrufen. Und wer wei, wie viele untergeordnete Funktionen printf enthlt? Egal,
irgendwann ist die Arbeit getan, die Pixel fr das groe Hallo sind auf den Bildschirm gemalt und die Funktion printf hat ihre Arbeit erledigt. Mit return
wird die Funktion main beendet und die Kontrolle wird wieder dem Betriebssystem (oder BCB) bergeben.

1.4 Ein Befehl nach dem anderen


Jetzt wollen wir unser Programm Schritt fr Schritt mit dem Debugger ablaufen
lassen. So gehts:
Statt F9 drcke F8 . Ein leeres Textfenster erscheint und im Editorfenster
wird die Zeile int main() eingefrbt und durch einen Pfeil markiert.
Drcke F8 noch mal. Der Pfeil springt in die Zeile mit dem printf-Befehl!
Du musst darauf achten, dass das Editorfenster das aktive Windowsfenster
ist und nicht etwa das Textfenster (weil du gerade wie ich das Textfenster mit
der Maus verschoben hast). Klicke das Editorfenster an, damit F8 auch an
dieses Fenster weitergereicht wird.
Drcke F8 noch einmal. Der Pfeil springt in die nchste Zeile. Im Textfenster
erscheint Hallo, Welt!. Die berschrift der BCB-Menleiste zeigt an, dass
das Programm Angehalten hat.
Drcke F8 noch mal. Hoppla, die Pfeilmarkierung ist weg. Das Programm ist
in die Funktion getchar hineingesprungen und die wartet auf die Eingabetaste. Jetzt steht in der berschrift Luft.
Klicke mit der Maus das Textfenster an und drcke die Eingabetaste. Die Pfeilmarkierung erscheint vor dem return-Befehl.
Drcke F8 zweimal hintereinander. Das Textfenster verschwindet und unser
Programm ist beendet.
Wenn du das Programm mit Start laufen lsst, werden die Befehle genauso
Schritt fr Schritt ausgefhrt. Nur ist der Befehlstakt nicht nur ein Befehl pro
Tastendruck, sondern eben viele Millionen oder Milliarden Befehle pro Sekunde,
je nachdem, was der Mikroprozessor deines Rechners so leistet.
Im BCB-Men unter Start findest du noch verschiedene andere Anweisungen
fr die Programmausfhrung. Wenn du als Erstes F7 drckst, erscheint nicht
nur das Textfenster, sondern auch ein Fenster mit berschrift CPU. Hier siehst
18

Kapitel 1 Hallo, Welt!

Sandini Bib

du die Maschinensprache, in die das Programm bersetzt wurde. Schliee das


Fenster mit dem Kreuz rechts oben.
Die berschrift der BCB-Menleiste zeigt an, ob das Programm Angehalten
wurde oder gerade Luft. Mit Strg + F2 (siehe auch Men Start) kannst
du das Programm zurcksetzen und somit seine Ausfhrung beenden. Drcke
Strg + F2 , falls du mit F7 das Programm erneut gestartet hattest.

1.5 #include
Alles klar bis auf die Zeile
#include <stdio.h>

die in unserem Programm auftaucht. Das Wort


include bedeutet hier
hineinnehmen. Diese Zeile ist kein C-Befehl. Bevor der Compiler den Code
Preprocessor (Vornach C-Befehlen durchsucht, ruft er den so genannten
prozessor) auf. Preprocessorbefehle beginnen mit #. Die #include-Anweisung
fhrt dazu, dass der Preprocessor diese Zeile durch den Inhalt der Datei stdio.h
ersetzt.
standard input outUnd was soll das Ganze? stdio kannst du dir als
put merken, also Standard Eingabe Ausgabe. Im Moment ist nur wichtig, dass
der Compiler die Information in stdio.h braucht, um die Funktionen printf
und getchar zu erkennen. Diese Funktionen werden freundlicherweise mit C
mitgeliefert (du musst sie nicht selber programmieren, sie stehen in der stdioBibliothek), aber sie werden nicht in jedem Fall vom C-Compiler automatisch
erkannt.
Testen wir das doch gleich einmal. Lsche die Zeile mit der #include-Anweisung
in dem Hallo-Programm, und kompiliere mit Projekt, Projekt neu kompilieren. Du bekommst zwei Warnungen, z. B.
[C++ Warning] Hallo.c(5): Call to function printf with no prototype.

Das heit Aufruf von Funktion printf ohne Prototyp. Solche Prototypen lernen
wir in Kapitel 7 kennen. BCB gibt nur Warnungen aus, das Programm luft
trotzdem mit F9 . Manche Compiler verweigern in diesem Fall die Arbeit und
melden einen Error. Selbst wenn das Programm luft, sollte man Warnungen
immer restlos beseitigen, sonst bersieht man irgendwann vor lauter harmloser
Warnungen ein schwerwiegendes Problem.

1.5 #include

19

Sandini Bib

1.6 Krze ohne Wrze?


Schade, dass unser Programm so kurz ist. Was fehlt, sind mehr Befehle. Wie wre
es mit
Hallo1.c

#include <stdio.h>
int main()
{
printf("Hallo, Welt!\n");
printf("Schoenes Wetter heute.\n");
printf("Lass uns baden gehen.\n");
getchar();
return 0;
}

Beachte, wie mehrere Befehlszeilen durch Strichpunkt getrennt untereinander


gesetzt werden knnen. Kannst du raten, was das folgende Programm bewirkt?
Hallo2.c

#include <stdio.h>
int main()
{
printf("\n");
printf("Hallo, ");
printf("Welt!");
printf("\n");
getchar();
return 0;
}

Genau lesen, denken(!), dann erst eintippen. Ohne Neuezeilezeichen macht der
zweite printf-Befehl auf der Zeile weiter, wo der erste aufgehrt hat. Lass das
letzte Programm auch einmal Schritt fr Schritt laufen, dann kannst du genau
sehen, wann die Ausgabe zur neuen Zeile springt.
Wie dem auch sei, vielleicht gefallen dir kurze Programme noch besser als lange?
Eine Funktion macht auch ohne Befehle (Un-)Sinn. Das Folgende ist ein vollwertiges C-Programm:
Hallo3.c

main()
{
}

20

Kapitel 1 Hallo, Welt!

Sandini Bib

Der Compiler gibt eine Warnung aus, weil wir int und return weggelassen
Function should return a value heit Funktion sollte einen Wert
haben.
liefern. Funktionieren sollte es aber trotzdem. Das krzeste C-Programm ist
Hallo4.c

main(){}

1.7 Eine Stilfrage


brigens ist es im Wesentlichen eine Frage des persnlichen Stils, wie man
den Programmtext formatiert. Nach einer Stunde Achterbahnfahren schreibe
ich wahrscheinlich so:
Hallo5.c

#include <stdio.h>
int
main
(
)
{
(
"Hallo, Welt!\n"
)

printf
;

getchar();return
;
}

Und auch so kompiliert und luft unser Hallo-Beispiel einwandfrei. Unbedingt


ausprobieren, dem Compiler ist es ziemlich egal, wo man Leerzeichen oder Leerzeilen einfgt. Wichtigste Ausnahme ist, dass Namen wie main keine Leerstellen
enthalten drfen. Der Compiler liest dann zwei verschiedene Namen nebeneinander, was nicht erlaubt ist. Hauptsache, deine Programme sind gut lesbar und
ordentlich aufgeschrieben, denn das erleichtert die Fehlersuche enorm.

1.7 Eine Stilfrage

21

Sandini Bib

1.8
Wer htte gedacht, dass es in diesem Kapitel mit dem simplen Hallo, Welt! so
viel zu erzhlen gibt. Wir haben einige Funktionen von C kennen gelernt und
einige Elemente der Syntax von C besprochen. Syntax nennt man die Regeln,
nach denen aus verschiedenen Zeichen und Sonderzeichen legaler C-Code zusammengesetzt wird. Hier ist noch mal das Wichtigste:
Jedes C-Programm fngt mit einem Aufruf der Funktion main an.
Im Programmtext sieht die Funktion main z. B. so aus:
int main()
{

Befehle
}

Die Befehle stehen im Befehlsblock zwischen geschweiften Klammern und


werden mit Strichpunkt voneinander getrennt.
C unterscheidet kleine und groe Buchstaben: main und Main sind verschiedene Namen.
Strings sind Zeichenketten wie z. B. "Hallo, Welt!\n". Sie knnen Sonderzeichen wie \n (newline) enthalten.
Leerzeichen und Zeilenumbrche spielen im C-Code oft keine Rolle.
Mit printf kann man Strings auf dem Bildschirm ausgeben. Mit getchar
kann man auf das Bettigen der Eingabetaste warten. Diese Funktionen bentigen die Preprocessoranweisung #include <stdio.h>, bevor sie im Programm aufgerufen werden knnen.
Mit F9 startet man in BCB die Kompilierung und die Ausfhrung des Programms. Mit F8 wird das Programm Schritt fr Schritt ausgefhrt. Falls BCB
auf F9 nicht reagiert, kann es sein, dass gerade ein Programm luft (siehe Titelzeile von BCB). Mit Strg + F2 kann es beendet werden.

22

Kapitel 1 Hallo, Welt!

Sandini Bib

1.9
bung macht den Meister, und nur selber denken macht schlau. Die folgenden
Progrmmlein kannst du jetzt schreiben:
1. Schreibe ein Programm, das Ach, wie schn! ausgibt. Zu einfach? Na, dann
alles in Hallo.c lschen und es ohne Buch versuchen.
2. Schreibe ein Programm, das Hallo, Welt! ausgibt, aber jeden Buchstaben auf

eine neue Zeile schreibt.


3. Schreibe ein Programm, das eine Schlangenlinie aus Sternchen (*) von links

oben nach rechts unten ausgibt.


Ein Tipp fr Leute mit wenig Editorerfahrung: Wie schon erwhnt kannst du
Text mit der Maus oder den Pfeiltasten markieren. Mit Strg + X und Strg + C
kannst du den markierten Text in einem Zwischenspeicher ablegen. Siehe
auch das BCB-Men Bearbeiten. Strg + X (Auschneiden) entfernt den markierten Text aus dem Editorfenster, Strg + C (Kopieren) kopiert den Text in
die Zwischenablage, ohne ihn aus dem Editor zu entfernen. Mit Strg + V (Einfgen) lsst du den Text aus der Zwischenablage am momentanen Ort des
Textcursors wieder erscheinen.
Also: Schreibe eine Zeile printf("*\n");. Markiere diese Zeile. Kopiere sie
mit Strg + C . Drcke zwanzigmal Strg + V . Jetzt hast du zwanzig Ausgabebefehle, in die du nur noch Leerzeichen einfgen musst.

1.9

23

Sandini Bib

Sandini Bib

2
Zahlen, Variable,
Rechnen
2.0
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12

1 + 2 zum Aufwrmen
Definition von Variablen
Nenn das Kind beim Namen
Wertzuweisung mit =
Ausdruck und Befehl
Initialisierung von Variablen
Zahleneingabe mit der Tastatur
Die Grundrechenarten fr ganze Zahlen
Die Grundrechenarten fr Kommazahlen
Zahlen im berfluss
Wer wem den Vortritt lsst
Zuweisungsoperatoren
Inkrement- und Dekrement-Operatoren

26
29
30
30
31
32
33
34
36
37
38
39
40

2.13

41

2.14

42

Sandini Bib

Kapitel 0 und 1 haben uns die ersten wichtigen Schritte beim Programmieren
vorgefhrt. Wir haben besprochen, wie wir aus einem Programmtext ein ausfhrbares Programm machen knnen (mit BCB). Mit dem Hallo-Programm habe
ich dir vorgefhrt, wie ein Programmtext in C typischerweise aufgebaut ist. Mit
Hilfe der Funktion printf hat unser Programm mit uns am Bildschirm Kontakt
aufgenommen.
In diesem Kapitel wollen wir uns mit Zahlen beschftigen, wie man sie in Variablen speichern kann und wie man mit Zahlen und Variablen rechnen kann.
Computer heit Rechner. Obwohl wir nicht vorhaben, groe Rechnungen durchzufhren, werden wir kleine Rechnereien sehr oft bentigen. Das liegt daran,
dass einfach alles im Computer mit Zahlen zu tun hat.
Die wichtigsten Teile in deinem Computer sind der Prozessor, der Befehle ausfhrt, und der Speicher, in dem Befehle und Daten gespeichert werden. Der Speicher besteht aus einzelnen Speicherzellen, in denen Zahlen als Bits und Bytes
gespeichert werden. Der Prozessor macht nichts anderes, als je nach Zusammenhang die vielen Bits und Bytes als Befehl oder Zahl zu verarbeiten. Auch
Buchstaben werden als Zahlen gespeichert, die dann erst bei der Ausgabe am
Bildschirm als Buchstaben dargestellt werden. Und auch Grafik und Musik sind
digitalisiert, stehen also als Zahlenkolonnen im Speicher.
Wie es sich fr eine Programmiersprache gehrt, gibt es in C viele Befehle und
Funktionen, die all diese Zahlen auf komfortable Weise als Text (z. B. printf!)
oder Grafik verarbeiten. Was ein angehender Programmierer von Bits und Bytes
wissen sollte, werden wir in Kapitel 9 genauer besprechen. Als Erstes wollen wir
aber vorfhren, dass in C mit Zahlen und Variablen auf einfache und natrliche
Weise gerechnet werden kann. Also schn der Reihe nach, was ist 1 + 2?

2.0 1 + 2 zum Aufwrmen


Unser erstes Beispiel zeigt, dass wir in einer Variablen Zahlen speichern knnen:
Zahlen0.c

#include <stdio.h>
int main()
{
int i;
i = 1;
printf("i ist %d", i);
getchar();
return 0;
}

26

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

i ist 1

Starte wie in Kapitel 0 beschrieben ein neues Projekt. Tippe den Programmtext
ein, oder hole ihn dir von der Buch-CD. Lass das Programm mit F9 laufen. Die
Zeilen
i = 1;
printf("i ist %d", i);

bewirken
Setze Variable i gleich 1.
Gib den Wert der Variablen i am Bildschirm aus.

Bevor wir ins Detail gehen, gleich noch ein Beispiel, in dem mit Variablen gerechnet wird. Das folgende Programm berechnet 1 + 2 und teilt uns das Ergebnis
mit:
Zahlen1.c

#include <stdio.h>
int main()
{
int i;
int j;
int summe;
i = 1;
j = 2;
summe = i + j;
printf("\n %d + %d ist gleich %d \n\n", i, j, summe);
getchar();
return 0;
}

1 + 2 ist gleich 3

Die Zeilen
i = 1;
j = 2;
summe = i + j;

kannst du wie folgt lesen:


Setze Variable i gleich 1.
Setze Variable j gleich 2.
Setze Variable summe gleich i+j, also gleich 1 + 2, was 3 ergibt.

2.0

1 + 2 zum Aufwrmen

27

Sandini Bib

Wie gewhnlich wird jede Anweisung mit ; beendet.


Lass mich kurz die printf-Anweisungen erklren. Schau genau hin, das Neue
an
printf("i ist %d", i);

im ersten Beispiel ist, dass die Funktion printf mit zwei Argumenten aufgerufen wird statt wie bisher mit einem. Das erste Argument ist der String
"i ist %d", dann kommt ein Komma, dann als zweites Argument die Variable i. Des Rtsels Lsung hat mit dem f im Namen printf zu tun, was auf
formatiertes Drucken hindeutet. Im Allgemeinen wird printf als
printf(String, Argumente)

aufgerufen. Der String kann normalen Text enthalten, aber auch Formatierungsanweisungen, die mit % beginnen. Das % gefolgt von d bedeutet, dass das nchste
Argument von printf als Zahl ausgegeben werden soll. In unserem Beispiel erscheint deshalb statt %d der Wert der Variable i als Zahl am Bildschirm. Wenn
du statt i eine 5 als zweites Argument schreibst, erscheint die 5 am Bildschirm.
In
printf("\n %d + %d ist gleich %d \n\n", i, j, summe);

verwenden wir %d gleich dreimal. Fr jedes %d greift sich printf das nchste
Argument und setzt die entsprechende Zahl in den ausgegebenen Text ein.
In den Beispielen haben wir drei verschiedene Variablen i, j und summe verwendet. Variablen sind dir sicher aus der Schule gelufig. Eine Variable ist eine
Vernderliche und man nennt Variablen auch Platzhalter. Zahlen, die wie 1
oder 2 im Programm stehen, heien Konstanten. Eine Variable wird dann eingesetzt, wenn man von einer bestimmten Zahl reden will, ohne sich auf ihren
Wert festzulegen. Das ist der Fall, wenn du mathematische Formeln wie
summe = i + j

verwendest. Hier soll die Zahl summe durch das Summieren der Zahlen i und j
berechnet werden. Das Praktische ist, dass diese Formel fr beliebige Werte der
Variablen i und j angewendet werden kann. Falls i gleich 1 ist und j gleich 2
ist, sagt diese Formel, dass summe gleich 3 sein soll. Fr 3 und 7 erhlt man 10
und so weiter.
Auch in einer Programmiersprache wie C werden Formeln zum Rechnen verwendet und meistens ist die bersetzung der mathematischen Schreibweise nach
C ganz logisch. Allerdings gibt es einige wichtige Unterschiede zur Mathe mit
Papier und Bleistift. Damit du wirklich verstehst, wie die Beispiele funktionieren,
wollen wir jetzt die Einzelheiten besprechen.

28

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

2.1 Definition von Variablen


Jede Variable muss ausdrcklich mit dem Compiler vereinbart werden, bevor wir
sie zum Rechnen verwenden knnen. Die Definition der Variablen in unserem
zweiten Beispiel geschieht in den Zeilen
int i;
int j;
int summe;

Dies sind drei Anweisungen, die wie blich mit einem Strichpunkt ; beendet
werden.
Tatschlich bewirkt eine Anweisung wie int summe; dreierlei:
Wir vereinbaren einen Datentyp. int bedeutet, dass summe eine ganze Zahl
(integer) bezeichnen soll. C unterscheidet ganze Zahlen von Kommazahlen,
die wir spter besprechen werden.
Wir vereinbaren einen Namen. Beim Programmieren verwendet man oft
ganze Wrter wie summe statt einzelner Buchstaben als Namen fr Variable,
damit sich Formeln einfacher lesen lassen.
Wir vereinbaren, dass bei Programmablauf Platz im Speicher fr eine ganze
Zahl reserviert werden soll.
In anderen Worten: Nach int summe; steht uns Speicherplatz fr eine ganze
Zahl zur Verfgung, auf den wir mit dem Namen summe zugreifen knnen. Das
ist der entscheidende Unterschied zur abstrakten Mathematik. Eine Variable in C
ist immer an einen bestimmten Speicherplatz, d.h. eine gewisse Anzahl von Bytes irgendwo im Speicher deines Computers, gebunden. Speicherplatz und Bytes
besprechen wir in Kapitel 9.
brigens kannst du mehrere Definitionen vom selben Datentyp in eine Zeile
schreiben, z. B.
int i, j, summe;

Die Variablen werden durch Komma getrennt, und die Definition mit einem
Strichpunkt beendet.
Die Definition einer Variablen muss immer vor ihrer ersten Verwendung in einer
Rechnung stattfinden. Zudem mssen alle Definitionen am Anfang eines Befehlsblocks stehen. Bisher haben wir nur den Befehlsblock kennen gelernt, der
den Krper der Funktion main ausmacht, also alle Befehle zwischen { und }.
Wenn du zurckbltterst, wirst du feststellen, dass in allen unseren Beispielen
die Definitionen in den ersten Zeilen auftauchen und niemals im Programm verstreut sind. Versuche einmal int summe; im obigen Rechenbeispiel weiter nach
unten zu verschieben.
Auf jeden Fall gilt: Eine Variable muss definiert werden, bevor sie
verwendet werden kann.
2.1 Definition von Variablen

29

Sandini Bib

2.2 Nenn das Kind beim Namen


Variablen und auch Funktionen drfen nicht beliebig genannt werden. Das ist
vllig einleuchtend. Einerseits sind gewisse Namen schon vergeben, wie z. B. int
und return, die zu den reservierten Schlsselwrtern von C gehren, siehe Anhang A.2. Ein weiteres Beispiel ist printf, also der Name einer Funktion aus
einer der C-Bibliotheken, der durch eine #include-Anweisung dem Programm
bekannt gemacht wurde.
Andererseits drfen nicht beliebige Zeichen in Namen verwendet werden, weil
sie ganz bestimmte Aufgaben in C erfllen. Z.B. bentigen wir runde Klammern
fr Funktionen und das Pluszeichen zum Rechnen.
Namen bestehen aus Buchstaben und Ziffern, wobei das erste Zeichen ein Buchstabe sein muss. summe1 ist erlaubt, 1summe nicht. Das Zeichen _ zhlt als Buchunderscore, Unterstreichung). Es kann dazu verwendet werden,
stabe (
lange Namen besser lesbar zu machen. Andere Zeichen sind nicht erlaubt. Z.B.
kann man eine Variable langer_name nennen, aber langer+name wird als Plusoperation zwischen zwei Variablen interpretiert. Groe und kleine Buchstaben
werden unterschieden.
Umlaute drfen in BCB nicht in Namen verwendet werden. Wenn dein Computer nicht auf deutsche Sonderzeichen eingestellt ist, kann es auch in Zeichenketten zu Schwierigkeiten mit Umlauten kommen. Deshalb schreibe ich in allen
Programmbeispielen ae, oe, ue und ss.
Je nach Compiler gibt es noch die Einschrnkung, dass nur Namen einer bestimmten Lnge zugelassen sind. Mindestens 31 Zeichen sind jedoch erlaubt.

2.3 Wertzuweisung mit =


Wollen wir auf eine Variable zugreifen, nennen wir sie einfach beim Namen. Mit
dem Gleichheitszeichen = kann man einer Variablen einen Wert zuweisen, wie
in
i = 1;

Die Variable steht links und eine Zahl steht rechts. Nach Ausfhrung dieser Zeile
ist i = 1, weil der Wert rechts vom Gleichheitszeichen in den Speicherplatz
links vom Gleichheitszeichen kopiert wurde. Andersherum geht es nicht, es wird
immer von rechts nach links kopiert.
Ersetze einmal j = 2 im letzten Beispiel durch
j = i;

Hier wird erst der Wert von i aus dem Speicher geholt und dann in den Speicherplatz von j kopiert. Also ist j nach dieser Anweisung 1.
30

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

In
summe = i + j;

geschieht Folgendes. Erst werden alle Operationen rechts vom Gleichheitszeichen ausgewertet, dann das Endergebnis in summe gespeichert.
Wir knnen in die Definition von Variablen gleich noch die erste Wertzuweisung
einbauen, wie in
Zahlen2.c

#include <stdio.h>
int main()
{
int i = 1, j = 2;
int summe = i + j;
printf("\n %d + %d ist gleich %d \n\n", i, j, summe);
getchar();
return 0;
}

Entscheidend ist, dass zum Zeitpunkt einer Zuweisung alle bentigten Variablen
schon definiert wurden.

2.4 Ausdruck und Befehl


Mit Ausdruck werden wir ganz allgemein eine Verkettung von Operationen in
C bezeichnen. Das kann eine einzelne Zahl sein oder eine Rechenoperation wie
i + j oder eine Zuweisungsoperation wie in i = 1. Auch ein Funktionsaufruf
wie printf(...) ist ein gltiger Ausdruck. Der Strichpunkt macht aus einem
Ausdruck eine Befehlszeile, z. B. i = 1;.
Die Syntax von C erlaubt es, Ausdrcke an vielen Stellen einzusetzen, an denen
ein einziger Wert verlangt wird. C berechnet den Ausdruck, bis das Endergebnis
verfgbar ist. Ersetze einmal den printf-Befehl im letzten Beispiel durch
printf("\n %d + %d ist gleich %d \n\n", i, j, i + j);

Wie zu erwarten erhalten wir das richtige Ergebnis, denn C wertet auch in Funktionsargumenten erst Rechenoperationen aus, bevor es die Funktion aufruft. Beachte, dass wir keinen Strichpunkt hinter i + j geschrieben haben. An dieser
Stelle bentigen wir einen Ausdruck, und nicht eine Befehlszeile. Ausprobieren,
der Compiler meldet einen Syntaxerror.

2.4 Ausdruck und Befehl

31

Sandini Bib

2.5 Initialisierung von Variablen


Welchen Wert hat eigentlich die Variable i nach der Zeile int i;? Fr einen
Test brauchst du nur in unserem allerersten Beispiel eine Zeile zu lschen:
Zahlen3.c

#include <stdio.h>
int main()
{
int i;
printf("i ist %d\n", i);
getchar();
return 0;
}

i ist 134517884

Vielleicht ergibt dieses Programm bei dir, dass i gleich 0 ist oder
1 oder etwas hnlich Harmloses. Aber im Allgemeinen gilt: Nach
int i; zu Beginn des Befehlsblocks enthlt i einfach nur Mll!
In anderen Worten, int i; definiert Typ, Name und Speicherplatz einer neuen
Variablen, der Wert von i ist aber noch undefiniert. Das heit, in den Bytes
dieser Variable stehen irgendwelche Zahlen, die zuflligerweise von der letzten
Verwendung dieser Bytes brig sind. Die Wortwahl ist hier nicht ganz gelungen,
weil man bei Definition vielleicht schon an Wertzuweisung denkt. Es werden
aber nur Typ, Name und Speicherplatz definiert.
Eine Variable enthlt im Allgemeinen erst dann einen sinnvollen Wert, nachdem
ihr ein Wert ausdrcklich und offiziell zugewiesen wurde. Diese erste Wertzuweisung einer Variablen nennt man Initialisierung.
Der Compiler gibt normalerweise eine Warnung aus, wenn der Wert der Variable
vor der Initialisierung ausgelesen wird. Bei mir meldet BCB
Possible use of i before definition.

Das bedeutet: Mgliche Verwendung von i vor der Definition.

32

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

2.6 Zahleneingabe mit der Tastatur


Variablen kannst du nicht nur direkt im Programmtext auf einen bestimmten
Wert setzen, du kannst auch Zahlen von der Tastatur einlesen:
Zahlen4.c

#include <stdio.h>
int main()
{
int i;
printf("\nSag mir eine ganze Zahl:

");

scanf("%d", &i);
printf("%d", i);
printf(", was fuer eine schoene Zahl!\n");
getchar();
getchar();
return 0;
}

Sag mir eine ganze Zahl:


5
5, was fuer eine schoene Zahl!

In
scanf("%d", &i);

scan heit
wird offensichtlich eine ganze Zahl in die Variable i eingelesen.
genau prfen oder abtasten. scanf ist das Gegenstck zu printf. Die Formatregeln entsprechen sich sinngem, nur dass scanf Zeichen von der Tastatur
einliest, statt sie am Bildschirm auszugeben. Wenn scanf aufgerufen wird, wartet das Programm so lange auf Zeichen, die mit der Tastatur eingegeben werden,
bis ein \n-Zeichen mit der Enter- oder Eingabetaste eingegeben wird. Der Unterschied zu getchar ist, dass die Zeichen wegen der Formatanweisung %d in eine
ganze Zahl umgewandelt werden. Diese Zahl kann mehrere Ziffern haben (ohne
Leerstellen). Anders als bei der Ausgabe ganzer Zahlen mit printf mssen wir
&i

schreiben, damit die eingelesene Zahl in der Variable i gespeichert wird. Den
Grund fr das Zeichen & werden wir in Kapitel 10 erklren.
Ein kleines Problem mit scanf ist, dass zwar eine Zahl von der Tastatur gelesen
wird, dass aber das Neuezeilezeichen nicht gelesen wird, mit dem wir die Eingabe beendet haben. Kein Zeichen, das mit der Tastatur eingegeben wird, geht
2.6 Zahleneingabe mit der Tastatur

33

Sandini Bib

verloren. Es steht in einer Warteschlange (dem Tastaturpuffer), bis es z. B. mit


getchar abgeholt wird. Deshalb mssen wir getchar zweimal aufrufen. Beim
ersten Mal holt getchar das Neuezeilezeichen von der Zahleneingabe und erst
das zweite getchar wartet dann auf ein zweites Neuezeilezeichen. Erst nach dem
zweiten Neuezeilezeichen beenden wir das Programm.

2.7 Die Grundrechenarten fr ganze Zahlen


Ganze Zahlen (
integers) haben wir schon mehrfach verwendet. Sie erhalten den Typennamen int. Ganze Zahlen sind die positiven Zahlen
1, 2, 3, . . .,

die 0, und auch die negativen Zahlen


1, 2, 3, . . ..

Zwei ganze Zahlen knnen durch die Operatoren


+ * / %

verknpft werden. Plus, Minus und Malnehmen funktioniert, wie du es gewhnt


bist. Das Minuszeichen kann auch einzeln einem Ausdruck vorausgestellt werden, um sein Vorzeichen zu ndern. Nach
i = 5;
j = i;

ist j gleich 5. Manchmal schreiben wir +1, um deutlich zu machen, dass wir
nicht etwa 1 meinen.
In der Mathematik schreibt man beim Rechnen mit Variablen oft das Malzeichen
nicht, aber in C darf das Malzeichen nicht fehlen:
2j + 1 wird in C zu 2 * j + 1.

Die Division wird mit / geschrieben. Bei ganzen Zahlen ergibt die Division eine
ganze Zahl plus Rest. Z.B. ist 9/4 eigentlich 2.25, der Operator / ignoriert aber
die Stellen hinter dem Komma, damit das Ergebnis wieder eine ganze Zahl ist:
9 / 4 ist 2.

Den Divisionsrest erhlt man mit dem Operator %:


9 % 4 ist 1.

Das ist, als ob du 9 Bonbons an 4 Freunde verteilst. Jeder erhlt 2, aber eins
bleibt brig. Kennst du das Kartenspiel Skat? Es gibt 32 Karten und 3 Spieler.
Jeder bekommt 32/3 = 10 Karten und 32 % 3 = 2 bleiben brig. Und ein letztes
Minibeispiel: 50 % 10 ergibt 0, weil 50 durch 10 teilbar ist.
34

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

Am besten probierst du das folgende Programm mit verschiedenen Eingaben


aus:
Zahlen5.c

#include <stdio.h>
int main()
{
int i, j;
printf("\nSag mir eine Zahl:
scanf("%d", &i);
printf("Sag mir noch eine Zahl:
scanf("%d", &j);
printf("%d + %d
printf("%d %d
printf("%d * %d
printf("%d / %d
printf("%d %% %d
printf("\n");

=
=
=
=

");

");

%d\n", i, j, i + j);
%d\n", i, j, i j);
%d\n", i, j, i * j);
%d\n", i, j, i / j);
= %d\n", i, j, i % j);

getchar(); getchar(); return 0;


}

Ich habe mich in der Zeile


printf("%d %% %d

%d\n", i, j, i % j);

nicht etwa vertippt, wir brauchen wirklich das doppelte %%, obwohl nur ein %
ausgegeben werden soll. Der Grund ist, dass % als Kennzeichen fr Formatanweisungen wie %d verwendet wird, selbst aber unsichtbar bleibt. Wie machen wir
es sichtbar? Siehe oben.
Das Programm bittet dich um Zahleneingaben mit der Tastatur, die du mit der
Eingabetaste beenden musst. Wenn ich 5 und 7 eingebe, erhalte ich
Sag
Sag
5 +
5
5 *
5 /
5 %

mir eine Zahl:


mir noch eine Zahl:
7 = 12
7 = 2
7 = 35
7 = 0
7 = 5

5
7

Mit der Formatanweisung %3d oder %5d erreichst du, dass printf 3 oder 5 Zeichen Platz fr eine ganze Zahl reserviert. Probier das mal aus. Auf diese Weise
kannst du im Beispiel die Spalten fr die Zahlen gleich breit machen und erreichen, dass in jeder Spalte die Zahlen ganz nach rechts gerckt werden.
2.7 Die Grundrechenarten fr ganze Zahlen

35

Sandini Bib

Bei der Division mit / oder % darf nicht durch Null geteilt werden. Was passierst,
wenn du alle Warnungen in den Wind schlgst und versuchst durch Null zu
teilen? Ausprobieren.

2.8 Die Grundrechenarten fr Kommazahlen


Wenn ganze Kerle mit ganzen Zahlen rechnen, mit welchen Zahlen rechnen
halbe Kerle? Eine halbe 1 ist 0.5, also eine Kommazahl. Statt von Kommazahlen
spricht man auch von Fliekommazahlen oder reellen Zahlen.
floating point numbers) werden mit dem Datentyp

Fliekommazahlen (

float definiert. Fr hhere Genauigkeit kannst du den Datentyp double ver-

wenden (mehr als doppelt so viele Stellen hinter dem Komma). Kommazahlen
sind Zahlen wie
1.55
0.00432

Hoppla, aufgepasst. Auf Deutsch sagen wir Komma,


auf Englisch Point
(Punkt), und tatschlich mssen wir in C einen Punkt statt einem Komma machen!
Das Rechenprogramm lsst sich fr Kommazahlen umschreiben:
Zahlen6.c

#include <stdio.h>
int main()
{
float x, y;
printf("\nSag mir eine Zahl:
scanf("%f", &x);

");

printf("Sag mir noch eine Zahl:


scanf("%f", &y);
printf("%f
printf("%f
printf("%f
printf("%f

*
/

%f
%f
%f
%f

=
=
=
=

%f\n",
%f\n",
%f\n",
%f\n",

x,
x,
x,
x,

y,
y,
y,
y,

getchar(); getchar(); return 0;


}

36

Kapitel 2 Zahlen, Variable, Rechnen

");

x
x
x
x

*
/

y);
y);
y);
y);

Sandini Bib

Sag mir eine Zahl:


Sag mir noch eine Zahl:
5.000000 + 7.000000 =
5.000000 7.000000 =
5.000000 * 7.000000 =
5.000000 / 7.000000 =

5
7
12.000000
2.000000
35.000000
0.714286

Hier verwende ich float statt int und %f statt %d. Mit %.3f kannst du die
Anzahl der Stellen hinter dem Punkt auf 3 setzen. Mit %10.3f kannst du fr die
Kommazahl inklusive der Stellen hinter dem Punkt 10 Zeichen Platz reservieren.
Dass ich die Namen der Variablen gendert habe, ist eine reine Stilfrage. Der
Rest-Operator % funktioniert fr Floats nicht.
Wie du siehst, liest scanf eine ganze Zahl wie 5 als Kommazahl 5.0, wenn das
Format %f ist. Du kannst natrlich auch Kommazahlen wie 1.55 und 0.00432
eingeben.
Ein eher unerwartetes Ergebnis liefert
float x = 1/2;

Nach dieser Zuweisung ist x gleich 0, denn 1 und 2 sind ganze Zahlen, und
deshalb wird mit der ganzzahligen Division gerechnet. Wenn man mit Kommazahlen rechnen will, muss man 1 und 2 wie in
float x = 1.0/2.0;

als Kommazahlen ins Programm schreiben und erhlt x = 0.5. Wir werden in
Kapitel 9.4 die Umwandlung von Datentypen besprechen.

2.9 Zahlen im berfluss


In Kapitel 9 werden wir der Sache auf den Grund gehen, aber an dieser Stelle
erst mal eine Warnung. In Integervariablen kannst du nicht beliebig groe Zahlen speichern! Dasselbe gilt fr Kommazahlen und ist compiler- und computerabhngig. Typischerweise kannst du in Integervariablen problemlos Zahlen von
2 Milliarden bis +2 Milliarden speichern. Aber bei 3 Milliarden kann es zu
folgendem Bldsinn kommen:

2.9 Zahlen im berfluss

37

Sandini Bib
Zahlen7.c

#include <stdio.h>
int main()
{
int i = 2000000000;
int j = 3000000000;
printf("i = %d, j = %d\n", i, j);
getchar();
return 0;
}

i = 2000000000, j = 1294967296

Autsch. Du solltest also aufpassen, dass du beim Rumprobieren nicht einfach so


eine 1 mit beliebig vielen Nullen eingibst. Der Hintergrund ist, dass pro Variable nur eine bestimmte Anzahl von Bytes im Speicher verwendet werden, und
wenn eine Zahl zu gro ist, passt sie einfach nicht mehr rein. Man spricht von
Overflow (berflieen), siehe Kapitel 9.

2.10 Wer wem den Vortritt lsst


Aus den Rechenoperationen, die wir besprochen haben, lassen sich lngliche Ausdrcke wie
i
i
i
i

+
+
*
*

j
j
j
j

+
*
+

k
k
k
2*k + 100/i

bilden. Dabei spielt es oft eine Rolle, in welcher Reihenfolge gerechnet wird.
Dafr gibt es Regeln wie Punkt vor Strich, wobei mit Punkt Malnehmen und
Teilen gemeint ist, und mit Strich Summe und Differenz. Zum Beispiel:
1 + 3 * 4 ist gleich 13,

weil erst malgenommen, dann addiert wird. In anderen Worten, das * bindet
strker als das +.
Was in runden Klammern steht, wird zuerst ausgerechnet. Um erst zu addieren
und dann zu multiplizieren schreiben wir
(i + j) * k

38

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

Ein Beispiel mit Zahlen:


(1 + 3) * 4 ist gleich 16,

weil erst addiert, dann malgenommen wird.


Klammern braucht man aus demselben Grund auch, wenn man Brche aus der
mathematischen Schreibweise bersetzen will:
i+j
k

als (i + j)/k eingeben, denn i + j/k ist i + kj .

Beim Punktrechnen wird von links nach rechts vorgegangen:


40
40
40
40

/
/
/
/

4 * 2
(4 * 2)
4 / 2
(4 / 2)

ist gleich 20,


ist gleich 5,
ist gleich 5,
ist gleich 20.

In Anhang A.0 findest du eine Tabelle, der du entnehmen kannst, in welcher


Reihenfolge verschiedene Operatoren in C abgearbeitet werden (auch fr solche,
die wir erst spter besprechen werden).

2.11 Zuweisungsoperatoren
Oft mchte man mit einer Variable rechnen, und das Ergebnis gleich wieder in
der Variable abspeichern. Wie wird
i = i + 1

ausgewertet? Der Ausdruck auf der rechten Seite wird ausgerechnet und das
Ergebnis in i gespeichert. Das heit, erst wird der Wert von i ausgelesen, dann
wird gerechnet, dann der neue Wert nach i geschrieben. Das Ergebnis von
i = 5;
i = i + 1;

ist, dass i gleich 6 ist. Ausprobieren!


Neben = gibt es noch weitere Zuweisungsoperatoren, die eine Variable verndern
knnen. Elementare Rechenoperationen kann man wie folgt mit einer Zuweisung kombinieren:
i += j

entspricht

i = i + j

i = j

i = i j

i *= j

i = i * j

i /= j

i = i / j

Falls i auf 5 und j auf 7 gesetzt sind, fhrt der Ausdruck i *= j dazu, dass i
gleich 35 ist. Die Variable j bleibt auf jeden Fall unverndert. Fr ganze Zahlen
gibt es auch %=.
2.11 Zuweisungsoperatoren

39

Sandini Bib

2.12 Inkrement- und Dekrement-Operatoren


Eine sehr hufig vorkommende Operation ist, dass man zu einer Variable 1 dazuincrement) oder von einer Variablen 1 abziehen mchte
zhlen mchte (
(
decrement). Dazu kann man die Operatoren ++ und verwenden. Dies
sind spezielle Zuweisungsoperatoren, die den Wert von i ndern, obwohl kein =
in Sicht ist. Man kann sowohl ++i als auch i++ schreiben.
Achtung, aufgewacht! Womglich fandest du die letzten Erluterungen mehr oder weniger offensichtlich. Hier kommt eine kleine Feinheit:
Zahlen8.c

#include <stdio.h>
int main()
{
int i, j;
i = 5;
j = ++i;
printf("i ist %d, j ist %d\n", i, j);
i = 5;
j = i++;
printf("i ist %d, j ist %d\n", i, j);
getchar();
return 0;
}

i ist 6, j ist 6
i ist 6, j ist 5

Was ist hier los? i wird um eins hochgesetzt, wie zu erwarten. Jedoch wird in j
= ++i die Addition vor der Zuweisung durchgefhrt, whrend in j = i++ die
Addition nach der Zuweisung erfolgt. In einem Fall steht ++ vor i, im anderen
Fall kommt ++ nach i.

40

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

2.13
Wie du siehst, kann man in C auf natrliche Weise mit Zahlen und Variablen
umgehen. Obwohl wir nicht vorhaben, groe Rechnungen durchzufhren, ist
das erfreulich. Denn kleine Rechnereien gehren zum Programmieren wie das
Brtchen zum Hamburger. In Krze:
In C gibt es verschiedene Datentypen. Wichtige Datentypen fr Zahlen sind
int fr ganze Zahlen und float und double fr Kommazahlen.
Zahlkonstanten wie 1 oder 20 erhalten den Typ int. Kommazahlen bentigen einen Punkt wie in 2.5 und erhalten den Typ double.
Man muss 1.0/2.0 schreiben, wenn das Ergebnis 0.5 sein soll.
Eine Variable kann Zahlen unter einem Namen speichern.
Jede Variable muss definiert werden, bevor ihr im Programm ein Wert zugewiesen werden kann. Beispiele: int i; oder float x;.
Einer Variablen i kann mit z. B. i = 1; ein Wert zugewiesen werden.
Wenn eine Variable noch nicht initialisiert wurde, dann steht in ihr womglich Mll. (Es gibt Ausnahmen, siehe Kapitel 7.)
C kennt die Grundrechenarten + * / %.
Bei den Inkrement- und Dekrement-Operatoren muss man z. B. zwischen ++i
und i++ unterscheiden.
Operatoren werden in einer bestimmten Reihenfolge ausgefhrt, siehe Anhang A.0.

2.13

41

Sandini Bib

2.14
1. Frage nach der Anzahl der Karten in einem Kartenspiel und nach der Anzahl

der Mitspieler. Gib das Ergebnis einer ganzzahligen Division mit Rest aus.
2. Schreibe ein Programm, das eine ganze Zahl i abfragt und dann das Quadrat
i*i und die dritte Potenz i*i*i berechnet. Und i*i*i*i, und i*i*i*i*i.

Diese Zahlen werden leicht sehr gro.


3. Welche Formeln aus der Geometrie fallen dir ein? Frage nach den Kantenln-

gen eines Rechtecks und berechne die Flche. Wie steht es mit der Oberflche
oder dem Volumen von Wrfeln oder Kugeln?
4. Denke dir eine Formel selber aus, z. B.
a =

xy
x+y+z (x

y)(x z)

xyz

und schreibe ein Programm dazu.


5. In Kapitel 2.4 habe ich behauptet, dass i = 1 ein Ausdruck ist. Teste die fol-

genden Befehle:
j = (i = 1);
j = i = 1;
k = (j += i);

6. Experimentiere mit ++. Was ist (i++) + (++i) (i++)? Bentigst du die

Klammern?

42

Kapitel 2 Zahlen, Variable, Rechnen

Sandini Bib

3
Felder und
Zeichenketten
3.0
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8

Felder fr Zahlenlisten
Felder initialisieren
Mehrdimensionale Felder
Zeichen
Zeichenketten und Stringvariablen
Eine erste Unterhaltung mit deinem Computer
Ganze Zeilen lesen und schreiben
Kommentare
Geschichtenerzhler

44
46
47
47
49
51
53
54
56

3.9

58

3.10

59

Sandini Bib

Bisher haben wir in jeder Variable genau eine Zahl gespeichert. In diesem Kapitel mchte ich dir zeigen, wie du in C mehrere Zahlen in so genannten Feldern
arrays) speichern kannst. Der Variablenname bezeichnet dann das ganze
(
Feld. Insbesondere lassen sich so ganze Reihen oder Listen von Zahlen abspeichern.
Einen Spezialfall stellen die Zeichenketten dar, die wir schon in Kapitel 1.1 kurz
besprochen haben. Eine Zeichenkette kann in C wie eine Liste von Zahlen behandelt werden, denn Buchstaben werden intern als Zahlen verarbeitet.
Felder machen es mglich, Variablennamen fr vernderliche Zeichenketten zu
verwenden. Damit knnen wir dann auch die Tastatureingabe von Text besprechen. Im Laufe des Kapitels wird klar werden, dass nicht jede Eingebung zu einer
genialen Ausgebung fhrt, aber unterhaltsam ist es trotzdem.

3.0 Felder fr Zahlenlisten


In einem Feld werden mehrere Objekte desselben Datentyps hintereinander in
einem zusammenhngenden Speicherbereich gespeichert. Hier ist ein Beispiel:
Felder0.c

#include <stdio.h>
int main()
{
int a[5];
int i;
a[0]
a[1]
a[2]
a[3]
a[4]

=
=
=
=
=

0;
2;
4;
6;
8;

i = 0;
printf("i
i++;
printf("i
i++;
printf("i
i++;
printf("i
i++;
printf("i

= %d

a[%d] = %d\n", i, i, a[i]);

= %d

a[%d] = %d\n", i, i, a[i]);

= %d

a[%d] = %d\n", i, i, a[i]);

= %d

a[%d] = %d\n", i, i, a[i]);

= %d

a[%d] = %d\n", i, i, a[i]);

getchar();
return 0;
}

44

Kapitel 3 Felder und Zeichenketten

Sandini Bib

i
i
i
i
i

=
=
=
=
=

0
1
2
3
4

a[0]
a[1]
a[2]
a[3]
a[4]

=
=
=
=
=

0
2
4
6
8

Als Erstes nehmen wir die Definition der Variablen a unter die Lupe. Mit
int a[5];

erhalten wir Speicherplatz fr 5 ganze Zahlen. Diese stehen hintereinander im


Speicher, aber im Moment kann es uns egal sein, wie die Zahlen im Speicher
stehen. Entscheidend ist, wie wir auf diese Zahlen zugreifen. In der Definition
haben wir den Variablennamen a fr unser Feld eingefhrt. Bei einer Feldvariablen darfst du nicht einfach a = 0 schreiben (auch wenn es vielleicht logisch
wre, auf diese Weise alle Zahlen im Feld a auf Null zu setzen). Richtig ist, wie
in
a[0]
a[1]
a[2]
a[3]
a[4]

=
=
=
=
=

0;
2;
4;
6;
8;

jeder Zahl im Feld a einzeln einen Wert zuzuweisen. Der Name a gibt an, welches Feld gemeint ist, und die Zahl zwischen eckigen Klammern bestimmt, die
wievielte Zahl in der Liste gemeint ist. Die Zahlen in einem Feld nennt man
auch die Elemente des Feldes. In Ausdrcken wie a[2] = 4 bestimmt die Zahl
zwischen den eckigen Klammern die Nummer oder den Index des Elements,
whrend in int a[5]; die Gre des Feldes auf 5 gesetzt wird.
Die Reihenfolge der Zuweisungen spielt in unserem Beispiel keine Rolle. Wie
bei einer Kommode mit Schubladen kannst du die Zahlen in einer beliebigen
Reihenfolge einsortieren und dann mit dem richtigen Index gezielt darauf zugreifen. Ein anderes Bild, das du dir vom Feld a machen kannst, ist eine Reihe
von 5 Kstchen auf einem Blatt Karopapier, die mit 0, 1, 2, 3, 4 durchnummeriert
sind.
Achtung: Lustigerweise haben die Erfinder von C beschlossen, beim
Zhlen mit 0 anzufangen! Unser Beispiel zeigt, dass das 1. Element
den Index 0 hat, das 2. Element den Index 1 und das letzte Element
hat den Index 4. Macht genau 5 Elemente. Ein Element a[5] gibt es nicht, denn
dann htten wir ja 6 Zahlen, aber unser Feld wurde mit int a[5]; nur fr 5
ausgelegt. Daran musst du dich sicher erst einmal gewhnen. Warum wir beim
Zhlen mit 0 anfangen, wird uns erst in Kapitel 10.2 klar werden. brigens,
welche Nummer hat das erste Kapitel dieses Buches?
Wenn du nach int a[5]; ber das Ende des Feldes hinausliest oder gar hinausschreibst (z. B. mit a[10] = 0;), machst du einen schrecklichen Fehler:
3.0

Felder fr Zahlenlisten

45

Sandini Bib

Du darfst niemals ber das letzte Element eines Feldes hinaus Werte
lesen oder Werte zuweisen!
Du kannst dabei Speicherplatz berschreiben, der wahrscheinlich fr andere
Zwecke reserviert ist. Dein Programm kann mit einer unerfreulichen Fehlermeldung von BCB oder Windows abstrzen. Leider kontrolliert dein C-Programm
nicht, ob Felderindizes innerhalb des Feldes bleiben. Auf Kosten der Geschwindigkeit knnte im Prinzip intern jeder Index auf Korrektheit geprft werden,
aber das geschieht nicht. Mehr dazu in Kapitel 10.
Unser Beispiel zeigt auch, dass du eine ganzzahligen Variable wie i als Index
verwenden kannst. Die Zeilen mit den printf-Befehlen sind alle gleich, aber
weil mit i++ die Indexvariable zwischen den printf-Befehlen hochgezhlt wird,
ergibt die Ausgabe von
a[i]

jeweils das nchste Element im Feld a. Nach der Definition kann man einen Ausdruck wie a[i] berall dort verwenden, wo auch eine einzelne Variable stehen
darf. In der Definition von Feldern sind Variablen wie i allerdings nicht erlaubt.
Hier muss auf jeden Fall eine Konstante stehen.

3.1 Felder initialisieren


Einer Integervariablen kannst du bekanntlich schon bei der Definition einen
Wert zuweisen, int i = 1;. So hnlich geht das auch bei Zahlenlisten. Mit
int a[5] = {0, 2, 4, 6, 8};

erhltst du genau das gleiche Ergebnis wie im letzten Beispiel, probiere es aus.
Der Strichpunkt darf nicht fehlen. Diese Art der Initialisierung ist praktisch,
allerdings darf eine solche Kommaliste nur in der Definition, nicht aber an beliebigem Ort im Programm stehen. Bei
int a[] = {0, 1, 2, 3, 4};

habe ich zwischen den eckigen Klammern die Gre des Feldes weggelassen.
Weil aber eine Liste mit 5 Anfangswerten fr das Feld angegeben ist, erhlt a
automatisch die Gre 5.
Wenn du in int a[5] = {...}; weniger als 5 Zahlen in die Liste schreibst,
wird von Element 0 an das Feld beschrieben, die brigen Pltze im Feld werden
nicht initialisiert.

46

Kapitel 3 Felder und Zeichenketten

Sandini Bib

3.2 Mehrdimensionale Felder


Eine Definition wie int a[10]; erzeugt ein eindimensionales Feld. Vielleicht
weit du aus der Geometrie, dass ein Punkt nulldimensional ist, eine Gerade
eindimensional (weil sie Ausdehnung nur in einer Richtung besitzt), eine Ebene
zweidimensional und ein Volumen dreidimensional. Auf einer Geraden muss
man eine Zahl (eine Koordinate) angeben, um einen Ort festzulegen, genau wie
wir einen Index fr ein eindimensionales Feld brauchen. In der Ebene braucht
man zwei Koordinaten, hnlich wie man bei einer Tischplatte Lnge und Breite
angibt. Wenn man dann die Tiefe hinzufgt, ist man in drei Dimensionen. Wenn
man jetzt noch die Zeit hinzufgt, will man wahrscheinlich Einsteins Relativittstheorie diskutieren.
Felder knnen auch mehrdimensional sein und oft hat das nichts mit rumlichen
Dimensionen zu tun:
int
int
int
int
int

einmaleins[10][10];
grosseseinmaleins[10][20];
vektor[3];
matrize[3][3];
raum[500][300][200];

Das Feld einmaleins ist sowas wie ein Rechteck auf einem Blatt Karopapier mit
10 mal 10 Kstchen. Wie bei einem Rechteck die Kantenlngen musst du hier
die Zahlen in der Definition in [ ] miteinander malnehmen, um die Anzahl der
Elemente zu erhalten.
Eindimensionale Felder knnten wir genauso gut Vektoren oder Listen nennen,
aber der Name Feld ist gebruchlich, weil Felder eine unterschiedliche Anzahl
von Dimensionen haben knnen. In Kapitel 7.13 werden wir soweit sein, ein
interessantes Beispiel mit mehrdimensionalen Feldern durchfhren zu knnen.
Jetzt wollen wir erst einmal den wichtigsten Spezialfall von eindimensionalen
Feldern besprechen, nmlich Zeichenketten. Dazu erklren wir als Erstes, wie
einzelne Zeichen in C gehandhabt werden.

3.3 Zeichen
Die Datentypen int und float kennst du schon. Sie werden fr ganze und reelle
Zahlen verwendet. In Variablen des Datentyps char kannst du einzelne Zeichen
characters) speichern. Das geht z. B. so:
(
char c;
c = a;

Aufgepasst, einzelne Zeichen werden mit dem hochgestellten


Komma eingegeben, dem Apostroph, und nicht mit ". Die Anfhrungszeichen oder Gnsefchen " sind fr Zeichenketten
reserviert. Spezielle Zeichen knnen mit \ angegeben werden:
3.2 Mehrdimensionale Felder

47

Sandini Bib

\a
\b
\f
\n
\r
\t
\v

Klingelzeichen
Zeichen zurck
Neue Seite
Neue Zeile
Wagenrcklauf
Tabulator
Vertikaltabulator

\"
\
\?
\\
\0

Anfhrungszeichen
Apostroph
Fragezeichen
Schrgstrich
Zahlencode Null

Deutlich hrst du hier einen alten elektrisch-mechanischen Drucker aus der Urzeit der Computertechnik rattern. Wie wir wissen, zhlt z. B. \n als ein Zeichen.
In Kapitel 9 werden wir besprechen, dass Buchstaben intern als Zahlencode mit
ganzen Zahlen gespeichert werden und dass sich der Datentyp char im Wesentlichen nur in der Anzahl der im Speicher belegten Bytes von Datentyp int
unterscheidet. Weil wir dauernd schon die Funktion getchar verwenden, ist ein
kleines Beispiel angebracht:
Felder1.c

#include <stdio.h>
int main()
{
int c;
printf("Gib mir ein Zeichen: ");
c = getchar();
printf("c = %d oder %c\n", c, c);
getchar(); getchar(); return 0;
}

Gib mir ein Zeichen heit nicht, dass du dem Computer zuwinken sollst, sondern dass er auf einen Tastendruck gefolgt von wartet, z. B.
Gib mir ein Zeichen:
c = 97 oder a

Wie du siehst, ist getchar eine Funktion, die wie eine Funktion in der Mathematik einen Wert liefert, den wir einer Variablen zuweisen knnen:
c = getchar();

Diesen Wert haben wir bisher nur immer ignoriert. Wir knnen ihn in einer
Integervariablen c speichern. Die ganze Zahl in c ist der Code fr das Zeichen,
das getchar gelesen hat. Der printf-Befehl gibt den Wert der Variablen c einmal als ganze Zahl mit %d und dann als Buchstaben bzw. Zeichen mit %c aus
48

Kapitel 3 Felder und Zeichenketten

Sandini Bib

(c wie
character). Wie nach scanf rufen wir dann getchar noch zweimal auf, damit das Fenster nicht einfach zuklappt. Diese zeichenweise Eingabe
ist unpraktisch, aber wie wir gleich sehen werden, kannst du mit scanf ganze
Zeichenketten einlesen. Also, wie kommen wir von Zeichen zu Zeichenketten?

3.4 Zeichenketten und Stringvariablen


Eine Variable fr Zeichenketten (eine Stringvariable) ist nichts weiter als ein
eindimensionales Feld fr den Datentyp char, z. B.
char s[100];

Auf diese Weise erhalten wir eine Stringvariable namens s mit Speicherplatz
fr 100 Zeichen. Wenn wir wollten, knnten wir dieser Variablen gleich einen
Text zuweisen, indem wir genau wie bei Integervariablen jedes Zeichen einzeln
in eine Liste schreiben, z. B.
char s[] = {H, a, l, l, o, \n, 0};

Achtung, ein richtiger String bentigt in C die Null als letztes Zeichen! Die Stringvariable s ist ein Feld mit genau sieben Zeichen,
denn die letzte 0 zhlt mit. Die Zahl 0 kannst du der Einheitlichkeit
halber auch als \0 schreiben.
Weil du jetzt sicher denkst, oh Graus, wie umstndlich, will ich dir gleich zeigen,
wie es viel einfacher geht. Eine Stringvariable wirst du normalerweise nie mit
einzelnen Zeichen, sondern immer wie in
char s[] = "Hallo\n";

initialisieren. Die Zeichenkette


"Hallo\n"

ist eine Stringkonstante. Frage: Was ist a, was ist "a"? Das eine ist ein einzelnes Zeichen, das andere ist eine Zeichenkette aus zwei Zeichen, a und \0.
Lange Rede, kurzer String, hier ist endlich ein Programmbeispiel:
Felder2.c

#include <stdio.h>
int main()
{
char meldung[100] = "Hallo, Welt!\n";
printf("%s", meldung);
getchar();
return 0;
}

3.4 Zeichenketten und Stringvariablen

49

Sandini Bib

Hallo, Welt!

In der Zeile
char meldung[100] = "Hallo, Welt!\n";

definieren wir die Variable meldung. In


printf("%s", meldung);

verwenden wir %s zur Ausgabe des Strings. Wie du siehst, erwartet printf als
zweites Argument den Namen der Stringvariablen ohne [ ]!
Kannst du dir denken, was das Folgende bewirkt?
Felder3.c

#include <stdio.h>
int main()
{
char gruesse[100] = "Hallo";
char wendenn[100] = "Welt";
printf("\nAlle zusammen: %s %s %s !\n",
gruesse, wendenn, gruesse);
getchar();
return 0;
}

Pass auf, dass du \n an der richtigen Stelle eingibst. Dieses Programm schreibt
Alle zusammen:

Hallo Welt Hallo !

printf gibt den Text in


"\nAlle zusammen:

%s %s %s !\n"

Zeichen fr Zeichen aus. Als Erstes kommt ein Neuezeilezeichen,


\n

Dann wird der Text


Alle zusammen:

gefolgt von zwei Leerzeichen ausgegeben. Jetzt trifft printf auf das erste %s.
Diese Formatanweisung besagt, dass an dieser Stelle der Inhalt der ersten Stringvariablen, nmlich der Variablen gruesse, in den Ausgabetext hineingeflickt
werden soll. Also wird
50

Kapitel 3 Felder und Zeichenketten

Sandini Bib

Hallo

ausgegeben. Dann wird ein Leerzeichen ausgegeben. Dann wird an der Stelle des
zweiten %s der Inhalt der zweiten Stringvariablen ausgegeben. Und so weiter.
Wie bei der Zahlenausgabe mit %d greift sich printf fr jedes %s das nchste
Argument. Du kannst Zahlen und Strings auch mischen, Hauptsache die Reihenfolge der Argumente stimmt mit der Reihenfolge der Formatanweisungen
berein.

3.5 Eine erste Unterhaltung mit deinem Computer


Hier ist ein kleines Frage-und-Antwort-Spiel:
Felder4.c

#include <stdio.h>
int main()
{
char frage[100] = "Wie heisst du?";
char antwort[100] = "Bernd";
printf("\n%s\n", frage);
printf("%s\n", antwort);
printf("\nHallo, %s!\n", antwort);
getchar();
return 0;
}

Bitte eintippen und ausprobieren, ausgegeben wird:


Wie heisst du?
Bernd
Hallo, Bernd!

Natrlich stehen meine Chancen schlecht, dass ich deinen Namen richtig geraten
habe. Also ndere das Programm wie folgt:

3.5 Eine erste Unterhaltung mit deinem Computer

51

Sandini Bib
Felder5.c

#include <stdio.h>
int main()
{
char frage[100] = "Wie heisst du?";
char antwort[100];
printf("\n%s\n", frage);
scanf("%99s", antwort);
printf("\nHallo, %s!\n", antwort);
getchar(); getchar(); return 0;
}

Die entscheidende Neuerung ist, dass


printf("%s", antwort);

durch
scanf("%99s", antwort);

ersetzt wurde. Im Gegensatz zu


scanf("%d", &i);

im letzten Kapitel wird kein & geschrieben. Das hat damit zu tun, dass i eine
Integervariable ist, whrend antwort eine Feldvariable ist (siehe Kapitel 10).
Wenn scanf aufgerufen wird, wartet das Programm. Es werden so lange Zeichen
von der Tastatur im Tastaturpuffer gespeichert, bis ein \n-Zeichen eingegeben
wird (mit der Enter- oder Eingabetaste). Der Unterschied zu getchar ist, dass
nach \n die Zeichen nicht einzeln abgeliefert werden, sondern mehrere Zeichen
auf einmal in einen String geschrieben werden knnen. Nachdem die Eingabe
mit beendet wurde, verarbeitet scanf den Formatstring. Fr jedes %s schreibt
scanf ein Wort aus der Eingabe in die dazugehrige Stringvariable. Wrter sind
Zeichenfolgen, die durch Leerzeichen getrennt sind, aber auch \n und \t zhlen als Leerzeichen. Gegebenenfalls liest scanf mehrere Zeilen, um Wrter fr
mehrere %s zu finden.
Beachte, dass wir die Variable antwort mit char antwort[100]; eingefhrt
haben. Damit nicht mehr als 100 Zeichen in antwort gespeichert werden, geben
wir in der Formatanweisung mit %99s an, dass hchstens 99 Zeichen gelesen
werden sollen (eine 0 fr das Ende des Strings kommt noch hinzu).

52

Kapitel 3 Felder und Zeichenketten

Sandini Bib

Das Ergebnis sieht dann z. B. so aus:


Wie heisst du?
Rumpelstilzchen
Hallo, Rumpelstilzchen!

Ich freue mich immer wieder, wenn ich sehe, wer heutzutage so alles programmieren lernt.

3.6 Ganze Zeilen lesen und schreiben


Ein kleines Problem ist, dass das Programm durcheinander kommt, wenn mehr
als ein Wort pro Zeile eingegeben wird. Gib einmal deinen vollstndigen Namen ein, z. B. Bernd Brgmann. Das Fenster klappt trotz der zwei Aufrufe von
getchar sofort zu, denn jetzt kehrt getchar mit dem Leerzeichen zurck, das
den Vornamen vom Nachnamen trennt. Das ist unschn. Das gewnschte Ergebnis liefert
Felder6.c

#include <stdio.h>
int main()
{
char frage[100] = "\nWie heisst du?";
char antwort[10000];
puts(frage);
gets(antwort);
printf("\nHallo, %s!\n", antwort);
getchar();
return 0;
}

Ausprobieren. Die Funktion puts (put string) gibt eine Zeichenkette plus ein
zustzliches Neuezeilezeichen aus. Die Funktion gets (get string) liest Zeichen
aus dem Tastaturpuffer, nachdem die Eingabetaste gedrckt wurde. Im Gegensatz zu getchar holt gets nicht nur ein, sondern alle Zeichen inklusive dem
Neuezeilezeichen aus dem Tastaturpuffer und speichert alle Zeichen bis auf das
Neuezeilezeichen (aber plus einer 0 am Ende) in einer Zeichenkette.
Kurz gesagt, gets liest eine komplette Eingabezeile minus \n von der Tastatur, puts gibt eine komplette Zeile plus \n im Textfenster aus. gets und puts
sind wesentlich weniger flexibel als scanf und printf, weil sie mit nur einem
Argument und ohne Formatanweisungen arbeiten.
3.6

Ganze Zeilen lesen und schreiben

53

Sandini Bib

Ein ernstes Problem von gets ist, dass diese Funktion nicht darauf achtet, wie
viele Zeichen gelesen werden. In einem richtigen Programm solltest du niemals
Zeichenketten abspeichern, ohne sicherzustellen, dass gengend Speicherplatz
reserviert wurde. Falls du gets fr kleine Experimente verwenden mchtest,
solltest du das Feld fr die Zeichenkette lnger als die lngste mgliche Eingabezeile machen. Bei mir sind das ungefhr 256, aber wer wei, ob und wann sich
das ndert. Den Puffer von gets berlaufen zu lassen ist eine beliebte Hackermethode. In Kapitel 10.9 lernen wir die Funktion fgets kennen, die nicht mehr
als eine gegebene Anzahl von Zeichen liest.

3.7 Kommentare
Bei dieser Gelegenheit besprechen wir, wie man Kommentare in seinen Programmtext einbaut. Das folgende Programm enthlt genau dieselben Befehle
wie das vorletzte Beispiel, aber auch viele ntzliche Bemerkungen:
Kommentare.c

/* Wie heisst du?


Beispiel fuer Texteingabe mit scanf
BB 23.8.2000
*/
#include <stdio.h>

// Headerdatei fuer printf, scanf, getchar

/* Funktion main
Hier geht es los!
*/
int main()
{
char frage[100] = "Wie heisst du?";
char antwort[100];

// Text fuer Frage


// Speicherplatz fuer Antwort

printf("\n%s\n", frage);
// gebe die Frage aus
scanf("%99s", antwort);
// lese die Antwort ein
printf("\nHallo, %s! \n\n", antwort); // gebe die Antwort aus
getchar();
getchar();
return 0;

// das erste Newline kommt von der Namenseingabe


// warte auf ein zweites Newline
// fertig!

Alles, was zwischen


/*

...

*/

steht, wird vom Compiler ignoriert, d.h. bersprungen, auch ber mehrere Zeilen hinweg. An dieser Stelle kannst du Briefe schreiben oder Bldsinn eintip54

Kapitel 3 Felder und Zeichenketten

Sandini Bib

pen oder auch ntzliche Bemerkungen zum Programmtext unterbringen. Diesen


Text schaut sich der Compiler nicht an.
Auch mit
// ...

kannst du Kommentare eingeben. In diesem Fall gilt die Regel, dass alles bis
zum Ende der Zeile zum Kommentar wird. Genau genommen gehrt // zu C++,
aber die meisten C-Compiler kennen diese Variante. Wenn du mal schnell testen
willst, was ohne eine bestimmte Zeile Programmtext passiert, musst du sie nicht
lschen. Du kannst die Zeile mit // abschalten.
Der Editor von BCB zeigt Kommentare in einer besonderen Farbe an. Ich verwende gerne Rot, damit meine Augen die wichtigen Stellen im Programm leichter finden.
Kommentare sind sehr, sehr ntzlich. Sie helfen anderen, deine Programme zu
lesen und zu verstehen. Aber das gilt auch fr dich selbst, wenn du nach lngerer
Zeit eines deiner eigenen Programm wieder lesen willst. Wahrscheinlich kannst
du dich dann nicht mehr genau daran erinnern, was in deinem Programm los war.
Ein ntzlicher Hinweis hier und da kann Wunder der Verstndlichkeit wirken.
Bei manchen Leuten findest du mehr Kommentar als Code! Man sollte aber
versuchen, nur wirklich ntzliche Kommentare anzubringen. Kommentare wie
getchar(); // rufe getchar auf

fhren nur dazu, dass man irgendwann den Wald vor lauter Bumen nicht mehr
sieht. Je komplizierter unsere Programme werden, desto mehr werden wir Kommentare benutzen.

3.7 Kommentare

55

Sandini Bib

3.8

Geschichtenerzhler

Es folgt unser bisher lngstes Programm:


Geschichte.c

/* Erzaehle mir eine Geschichte


BB 23.8.00
*/
#include <stdio.h>
int main()
{
// diese Strings benoetigen wir
char lebewesen[100], farbe[100], teil[100], verb[100], name[100];
// bitte den Nutzer um einige Eingaben
printf("\n");
printf("Sag mir eine Farbe:
");
scanf("%99s", farbe);
printf("Sag mir ein Lebewesen:
scanf("%99s", lebewesen);
printf("Sag mir ein Verb:
scanf("%99s", verb);

der ");

");

printf("Sag mir einen Koerperteil: der ");


scanf("%99s", teil);
printf("Sag mir einen Namen:
scanf("%99s", name);

der ");

// gib die Geschichte aus


printf("\n");
printf("Es war einmal ein %s.\n", lebewesen);
printf("Er hatte einen %sen %s.\n", farbe, teil);
printf("Auf dem Weg zur Schule sah er %s.\n", name);
printf("Da musste der %s vor Freude so %s,\n", lebewesen, verb);
printf("dass sein %s wackelte und %s auch ganz %s wurde.\n",
teil, name, farbe);
printf("\n");
getchar(); getchar(); return 0;
}

Kannst du beim Lesen erkennen, was das Programm tun soll? Als Erstes stellen
wir mit

56

Kapitel 3 Felder und Zeichenketten

Sandini Bib

char lebewesen[100], farbe[100], teil[100], verb[100],


name[100];

Speicherplatz fr einige Strings bereit. Wir haben dazu ein char angegeben und
dann eine Liste mit Kommas, htten aber genauso gut
char
char
char
char
char

lebewesen[100];
farbe[100];
teil[100];
verb[100];
name[100];

schreiben knnen. Diese Zeilen sind dann durch Strichpunkt zu trennen. Danach
fragen wir nacheinander nach verschiedenen Worten, die wir in den Stringvariablen speichern. Schlielich rhren wir die Antworten zu einer Geschichte zusammen, die am Bildschirm ausgegeben wird.
Also, ich wei nicht. Ist das nun Prosa oder nicht? Mein erster Versuch ergab das
Folgende am Bildschirm:
Sag
Sag
Sag
Sag
Sag

mir
mir
mir
mir
mir

eine Farbe:
blau
ein Lebewesen:
der Hund
ein Verb:
schwimmen
einen Koerperteil: der Bauch
einen Namen:
der Peter

Es war einmal ein Hund.


Er hatte einen blauen Bauch.
Auf dem Weg zur Schule sah er Peter.
Da musste der Hund vor Freude so schwimmen,
dass sein Bauch wackelte und Peter auch ganz blau wurde.

Oder gefllt dir das vielleicht besser?


Sag
Sag
Sag
Sag
Sag

mir
mir
mir
mir
mir

eine Farbe:
affig
ein Lebewesen:
der Affe
ein Verb:
Computerspielen
einen Koerperteil: der Nasenspitze
einen Namen:
der Apfelmus

Es war einmal ein Affe.


Er hatte einen affigen Nasenspitze.
Auf dem Weg zur Schule sah er Apfelmus.
Da musste der Affe vor Freude so Computerspielen,
dass sein Nasenspitze wackelte und Apfelmus auch ganz affig wurde.

Aus Versehen hatte ich hier Computer spielen nicht getrennt geschrieben, wie
es sich gehrt. Wenn du Computer spielen korrekt als zwei Wrter eingibst,
kommt das Programm wegen scanf wie besprochen durcheinander:
3.8

Geschichtenerzhler

57

Sandini Bib

Sag
Sag
Sag
Sag

mir
mir
mir
mir

eine Farbe:
affig
ein Lebewesen:
der Affe
ein Verb:
Computer spielen
einen Koerperteil: der Sag mir einen Namen:

der Nasenspitze

Es war einmal ein Affe.


Er hatte einen affigen spielen.
Auf dem Weg zur Schule sah er Nasenspitze.
Da musste der Affe vor Freude so Computer,
dass sein spielen wackelte und Nasenspitze auch ganz affig wurde.

Als Programmierer(in) musst du immer versuchen, narrensichere Programme zu


schreiben. Stell dir vor, ein Affe benutzt dein Programm. Dass dann die Grammatik nicht richtig stimmt, mag ja noch angehen, aber das Durcheinander mit
der Worttrennung kann so nicht bleiben. Deshalb solltest du scanf durch gets
ersetzen.

3.9
Zeichenketten werden Zeichen fr Zeichen in Feldern gespeichert. Mit scanf
und printf knnen wir uns nett mit dem Computer unterhalten, auch wenn
man behaupten knnte, dass die knstliche Intelligenz unseres Geschichtenerzhlers noch unter Null liegt. Kurz und gut:
Felder werden z. B. mit int a[5]; definiert, was Speicherplatz fr genau fnf
ganze Zahlen reserviert.
Die Elemente eines Feldes sind von 0 an durchnummeriert, d.h. int a[5];
ergibt ein Feld mit den fnf Elementen a[0], a[1], a[2], a[3], a[4].
a[i] kann berall dort stehen, wo eine einzelne Integervariable verwendet
werden kann, z. B. a[i] = 2;.

Stringvariable sind Felder fr eine bestimmte Anzahl von Zeichen, z. B.


char meldung[100] = "Hallo\n";.
Mit printf("Hallo %s", name); kann man Zeichenketten ausgeben. In
diesem Beispiel wird die Formatanweisung %s durch den Text in der Stringvariablen name ersetzt.
Mit scanf("%99s", name); kann man Zeichenketten von der Tastatur einlesen. In diesem Beispiel wird ein Wort in der Stringvariable name gespeichert.
Der Compiler ignoriert alles zwischen /* und */ und er ignoriert alles zwischen // und dem Zeilenende. Schreibe lieber zu viel als zu wenig Kommentare in deinen Programmtext.

58

Kapitel 3 Felder und Zeichenketten

Sandini Bib

3.10
1. Schreibe ein Programm, das alle Zeichen in "abc 123" einzeln mit %c und
mit %d ausgibt.
2. Schreibe ein Programm, das Wrter mit fnf Buchstaben rckwrts ausgibt.

Dazu definierst du eine Zeichenkette mit fnf Buchstaben plus Null, z. B.


char name[6] = "string";, und vertauschst die Elemente des Feldes paarweise.
3. Erfinde deine eigene Geschichte. Probiere sie mit Freunden aus.
4. Nachdem ihr eine Weile deinen Geschichtenmacher ausprobiert habt, setze

deinen Freund oder deine Freundin vor ein Programm, das Fragen stellt, auf
jede Frage irgendwie antwortet, in Wirklichkeit aber die Antworten vllig
ignoriert. Das Ergebnis knnte so aussehen:
Hallo, wie heisst du?
Grete
Schoen dich zu sehen. Wie gehts?
prima
Das ist schlimm. Hast du Probleme?
nein
Ging mir letztes Jahr auch so.
Was ist deine Lieblingsfarbe?
gruen
Na denn, tschuess, Hugo!

3.10

59

Sandini Bib

Sandini Bib

4
Grafik
4.0
4.1
4.2
4.3
4.4
4.5
4.6
4.7

Hallo, Welt im Grafikfenster


Ein Hallo wie gemalt
Fensterkoordinaten
Pixel und Farbe
Linien
Rechtecke und Kreise
Fonts
Hilfe zu BCB und Windows SDK

62
64
65
68
69
73
76
78

4.8

Geradlinige Verschiebung

79

4.9

Bunte Ellipsen

81

4.10

TextOut mit Format

83

4.11

85

4.12

87

Sandini Bib

Alles, was auf dem Computerbildschirm erscheint, sind einzelne bunte Punkte, so
genannte Pixel. Bilder setzen sich aus vielen Pixeln zusammen, aber auch Linien
und Buchstaben setzen sich aus einzelnen Pixeln zusammen.
Bisher haben wir nur sehr indirekt mit Pixeln zu tun gehabt. Unsere Programme
waren Textbildschirmanwendungen, die von einem speziellen Windowsprogramm, der DOS-Konsole, in einem Textfenster angezeigt werden. In diesem
Fall geben wir Text mit der Funktion printf aus. printf ist eine Funktion aus
der Standard Ein-/Ausgabe-Bibliothek von C. Aber wie erhalten wir Zugriff auf
all diese schnen, bunten Pixel auf deinem Bildschirm?
Grafik ist nicht Teil der Programmiersprache C und es gibt auch keine Standardbibliothek fr Grafik in C. Aber fr Windowsrechner steht uns Microsofts
Windows SDK, das Windows Software Development Kit (Entwicklungspaket),
zur Verfgung. Windows ist im Wesentlichen ein riesiges C-Programm. Und
mit dem Borland C++Builder haben wir Zugang zu allen Funktionen des Windows SDK. Bei der Installation von BCB hast du auch die Hilfe fr das Win32
SDK installiert. Hier findest du eine ausfhrliche und klare Beschreibung des
Windows SDK, allerdings auf Englisch. Im Win32 SDK finden wir eine groe
Auswahl an Funktionen fr Grafik, aber auch fr Sound, und all diesen Windowskleinkram wie Fenster, Knpfe, Mens, Scrolleisten und so weiter.
Wir wollen uns in diesem Kapitel auf das Einmaleins der Grafikausgabe konzentrieren. Weil Windows so umfangreich ist, lsst es sich nicht vermeiden, dass
unsere Programme einige umstndliche Details enthalten. Ich werde nicht alle
diese Details auf der Stelle vollstndig erklren, sondern dich bitten, zunchst
wie bei einem Kochrezept gewisse Zutaten einfach so zu verwenden.
Aber am Ende des Buches wirst du mir sicher zustimmen, dass der Kuchen trotzdem schmeckt und dass sich der Aufwand auf jeden Fall gelohnt hat. Besonders
im Vergleich zu manchen Textbeispielen ist Grafik einfach cool.

4.0 Hallo, Welt im Grafikfenster


Auf der CD des Buches findest du im Verzeichnis ProgLernen\
WindowsMalen alles, was wir fr unsere ersten Grafikexperimente
brauchen. Das Projekt heit WinHallo.mak. ffne dieses Projekt mit
BCB und fge mit Projekt, Zum Projekt hinzufgen die Datei WinHallo0.c
dem Projekt hinzu. Speichere das Projekt in einem neuen Verzeichnis ab und
speichere auch die Datei WinHallo0.c in diesem Verzeichnis ab (siehe BCBMen Datei). Du kannst aber auch im Verzeichnis ProgLernen\WindowsMalen
arbeiten, wenn du es von der Buch-CD auf die Festplatte kopiert hast. Vielleicht
solltest du an dieser Stelle nochmals Kapitel 0.6 und die Datei ProgLernen\
LiesMich.txt lesen.

62

Kapitel 4 Grafik

Sandini Bib

Kompiliere das Programm und lass es laufen ( F9 ). Ein Fenster mit berschrift
WinHallo geht auf:

Hallo, Welt!
Nach dem ffnen des Projekts zeigt das Editorfenster die Datei WinHallo.cpp.
Im Gegensatz zu unseren Textfensteranwendungen enthlt diese Datei nicht nur
die Anweisungen, die BCB fr C-Programme braucht. Weil ich unseren C-Code
nicht mit den recht verwickelten Details der Windowsverwaltung belasten wollte,
habe ich die Funktionen fr das Fenster in WinHallo.cpp versteckt. Halt, nicht
anschauen!
Zu spt, schon wieder einen Leser verloren. Spa beiseite, so schrecklich ist die
Datei nun auch wieder nicht. Natrlich darfst du dir WinHallo.cpp anschauen,
auch wenn dir dabei vieles unverstndlich bleiben wird. Geduld. Im Moment
gengt es uns zu wissen, dass die Programmausfhrung in Windows mit der
Funktion
WinMain

beginnt. Hier erzeugen wir ein Fenster. Ich habe es so eingerichtet, dass, wann
immer das Fenster neu gemalt werden soll, eine Funktion namens malen aufgerufen wird.
Die Funktion malen ist keine Standardfunktion von C oder Windows. Ich definiere malen in der Datei WinHallo0.c. Wie wir Funktionen selber machen, besprechen wir in Kapitel 7. Die Funktion malen habe ich mir ausgedacht, um dir
die ersten Gehversuche mit Grafikfenstern zu erleichtern. Alles, was ich so weit
vorne in diesem Buch noch nicht erklren kann, steht in WinHallo.cpp, aber
die einfachen Sachen in WinHallo0.c werden wir jetzt besprechen. Alle unsere
Experimente werden wir mit WinHallo.cpp plus einer zustzlichen C-Datei fr
die Funktion malen durchfhren.
4.0

Hallo, Welt im Grafikfenster

63

Sandini Bib

4.1 Ein Hallo wie gemalt


ffne die Datei WinHallo0.c:
WinHallo0.c WinHallo.cpp

#include <windows.h>
/* Male im Fenster */
void malen(HDC hdc)
{
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
}

Bei Programmbeispielen mit Grafik gebe ich nicht nur den Namen der .c-Datei,
sondern auch den der .cpp-Datei an. Als Erstes fllt dir sicher auf, dass wir
statt der Headerdatei stdio.h die Headerdatei windows.h verwenden. Klarer
Fall, schlielich wollen wir ja die Windowsfunktionen verwenden. Es folgt die
Definition einer Funktion namens malen:
void malen(HDC hdc)
{
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
}

void heit nichts oder leer. Die Funktion malen liefert kein Ergebnis und
bentigt auch kein return 0;. Andererseits soll malen mit einer Variablen hdc
vom Typ HDC aufgerufen werden. Dieser Datentyp wird in windows.h definiert.
Solche Definitionen, die nicht direkt Teil von C sind, erhalten zur besseren Kennzeichnung einen Namen aus lauter Grobuchstaben.
Handle heit Griff und das Verb dazu
Das h bzw. H steht fr handle.
ist handhaben. Handle werden uns noch oft begegnen. In diesem Fall geht es
Device Context (Gerteumfeld). Die Variable hdc
um einen DC, einen
ist alles, was gewisse Grafikfunktionen fr die Ausgabe in einem bestimmten
Fenster bentigen. hdc wird uns in die Funktion hereingereicht und wir reichen
es an die Funktion TextOut weiter.
Wir sind also in der Zeile
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);

angekommen. Vergleiche einmal mit


printf("Hallo, Welt!");

64

Kapitel 4 Grafik

Sandini Bib

Die Funktion TextOut (


Text Raus) bentigt neben dem String, der ausgegeben werden soll, noch vier zustzliche Parameter. Das Schema sieht so aus:
TextOut(hdc, x-Koordinate, y-Koordinate, String, Anzahl Zeichen);

Mit hdc legen wir das Ausgabefenster fest. Fr Textfensteranwendungen gibt


es keine Wahl, alle Ausgaben von printf werden in das Textfenster geschickt.
Seltsamerweise bentigt TextOut im Gegensatz zu printf die Anzahl der Zeichen (die Lnge des Strings). Die Koordinatenangaben bestimmen, bei genau
welchem Pixel auf dem Bildschirm die Textausgabe beginnt. Bei printf wird
ein Zeichen neben dem anderen ausgegeben, und unsere Kontrolle beschrnkt
sich darauf, den Beginn neuer Zeilen mit \n festzulegen. TextOut erkennt \n
nicht (aber siehe DrawText in der Hilfe zum Windows SDK, fr die du Zeiger
kennen musst, Kapitel 10).
Alles in allem ist die Textausgabe in einem Grafikfenster also auch nicht viel
schwieriger als bei einer Textfensteranwendung. Weil wir mehr Freiheiten haben,
mssen wir auch mehr Angaben machen. Wir bestimmen das Fenster, den Ort
im Fenster, den String und die Anzahl der auszugebenen Zeichen.

4.2 Fensterkoordinaten
Koordinaten? Vielleicht erinnerst du dich nur vage daran, was du in der Schule
ber Koordinaten gehrt hast. Definieren wir also erst einmal, welches Koordinatensystem wir fr Fenster verwenden. Auf dem Bildschirm sind Fenster rechteckige Felder, die sich aus einzelnen Pixeln (farbigen Punkten) zusammensetzen.
Jedes Pixel hat zwei Koordinaten, sagen wir, x und y. Hier ist ein Bild:
x=0
y=0

x=9
y=0
x-Achse

x=0
y=7

y-Achse

x=9
y=7

Die obere linke Fensterecke hat die Koordinaten x = 0 und y = 0. Dies ist der
Ursprung des Koordinatensystems. Angenommen, jemand gibt uns zwei ganze
Zahlen x und y als Koordinaten. x gibt an, wie viele Pixel wir vom Ursprung aus
nach rechts gehen sollen, y gibt an, wie weit wir nach unten gehen sollen. In der
Mathematik zeigt die y-Achse blicherweise nach oben!
4.2

Fensterkoordinaten

65

Sandini Bib

Gleich ausprobieren. Experimentiere mit


TextOut(hdc, 0, 0, "Hallo, Welt!", 12);
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
TextOut(hdc, 50, 70, "Hallo, Welt!", 12);

und anderen Koordinatenangaben.


In Windows muss man darauf achten, welcher Ursprung gerade gemeint ist:
Die linke obere Bildschirmecke.
Die linke obere Ecke eines Fensters.
Die linke obere Ecke im Anzeigebereich (

Client Area) des Fensters.

In unserem TextOut-Beispiel beziehen sich die Koordinaten auf den Anzeigebereich innerhalb des Fensters. Das ist der Normalfall und wird in fast allen unseren Beispielen so sein. In Windows darf nicht ohne weiteres irgendwo auf dem
Bildschirm herumgemalt werden, jedes Programm verwendet seine eigenen Fenster. Zudem ist es in den allermeisten Fllen verboten, in die Umrandung eines
Fensters, d.h. in den Rahmen oder die Titelleiste, hineinzumalen.
Wenn du das Fenster verschiebst, bleibt die Position des Texts in Bezug auf die
linke obere Ecke unverndert. Dasselbe gilt, wenn du das Fenster maximierst
(zweiter Knopf rechts oben im Fenster).
Probiere mal
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
TextOut(hdc, 400, 400, "Hallo, Welt!", 12);

Bei Programmstart ist die erste Nachricht abgeschnitten, die zweite ist gar nicht
sichtbar. Maximiere das Fenster, und die zweite Nachricht wird sichtbar. Mache
die Maximierung des Fensters rckgngig. ndere die Gre des Fensters, indem du den Rand anklickst und verschiebst. So kannst du die zweite Nachricht
teilweise verdecken. Jedoch fhren solche nderungen nicht dazu, dass negative
x-Koordinaten sichtbar werden, und die erste Nachricht bleibt abgeschnitten.
Die Ausgabe auerhalb des erlaubten Bereichs abzuschneiden, nennt man Clipping. Clipping ist sehr ntzlich, weil es automatisch verhindert, dass irgendwelche Programme, zum Beispiel deine, irgendwo auf dem Bildschirm rumkritzeln
und die Anzeige anderer Programme durcheinander bringen.
Ansonsten steht es uns frei, unseren Text an beliebiger Stelle im Fester auszugeben! Weit du, was das heit?

66

Kapitel 4 Grafik

Sandini Bib

Wir sind den Zwngen der Textfensterausgabe entkommen! Das Programm


dazu:
WinHallo1.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
char s1[50] = "Ich bin frei?";
char s2[50] = "Ich bin frei!";
char s3[50] = "FREI ! ! !";
char s4[50] = "UUUHahahaaa !";
TextOut(hdc, 20, 20,
TextOut(hdc, 50, 50,
TextOut(hdc, 50, 100,
TextOut(hdc, 110, 105,
TextOut(hdc, 170, 110,
TextOut(hdc, 50, 180,
TextOut(hdc, 10, 200,
TextOut(hdc, 900, 600,
TextOut(hdc,9000,6000,

s1,
s2,
s3,
s3,
s3,
s4,
s4,
s3,
s3,

strlen(s1));
strlen(s1));
strlen(s3));
strlen(s3));
strlen(s3));
strlen(s4));
strlen(s4));
strlen(s3));
strlen(s3));

Wir definieren vier Stringvariable, s1 bis s4. Damit wir nicht fr TextOut die
Anzahl der Zeichen zhlen mssen, verwenden wir die Funktion strlen (string
length), die mit einem String als Argument aufgerufen wird und dessen Lnge
liefert. Falls du strlen in Textanwendungen ohne windows.h verwenden mchtest, bentigst du #include <string.h>.

4.2

Fensterkoordinaten

67

Sandini Bib

4.3 Pixel und Farbe


Einzelne Pixel kannst du mit der Funktion SetPixel setzen:
Grafik0.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
SetPixel(hdc, 100, 100, 0);
}

Schreibe den Programmtext in die Datei WinHallo0.c (oder verwende die Datei
Grafik0.c) und lass das Programm laufen. Ziemlich mickrig, so ein Pixel.
Kannst du es nahe x = 100 und y = 100 erkennen?

So sieht das bei mir aus. Ich zeige mit der Maus auf das Pixel. Der Schnappschuss rechts zeigt eine Vergrerung des Pixels. In der Vergrerung ist das
Pixel deutlich zu sehen und auch, dass der Mauszeiger wie alles andere aus Pixeln aufgebaut ist.
Das Schema von SetPixel ist
SetPixel(hdc, x, y, farbe)

Als Farbe kannst du eine ganze Zahl angeben. In dieser Zahl wird auf spezielle
Weise ein Farbwert codiert, und dazu verwendet man RGB:
RGB(rot, gruen, blau)

Die Werte fr rot, gruen und blau sind ganze Zahlen von 0 bis 255, die die
Intensitt fr die jeweilige Farbe angeben. Eine Intensitt von 255 bedeutet volle
Intensitt, 0 bedeutet kein Beitrag in dieser Farbe:
68

Kapitel 4 Grafik

Sandini Bib

RGB( 0,
0,
0)
RGB(255,
0,
0)
RGB( 0, 255,
0)
RGB( 0,
0, 255)
RGB(255, 255, 255)

schwarz
rot
grn
blau
wei

RGB(0,0,0) ist gleich der Zahl 0, und darum erhalten wir im Beispiel mit
SetPixel einen schwarzen Punkt. Experimentiere mit verschiedenen Farben

und Intensitten, z. B.
SetPixel(hdc,
SetPixel(hdc,
SetPixel(hdc,
SetPixel(hdc,

100,
105,
100,
105,

100,
100,
105,
105,

RGB(200,
0,
0));
RGB( 0, 200, 200));
RGB(200,
0, 200));
RGB(200, 200,
0));

Was, du bist immer noch nicht beeindruckt? Du sagst, ein mickriges Pixel in Farbe
ist immer noch mickrig? Wer das Pixel nicht ehrt, ist die Grafik nicht wert, wie
schon mein Grovater so hnlich zu sagen pflegte. Aber etwas besser wird es
noch in diesem Kapitel.

4.4 Linien
Unter den Windowsfunktionen fr Grafik gibt es auch die folgenden Funktionen
fr elementare Liniengrafik. Das Programm
Grafik1.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
//
x
MoveToEx(hdc, 50,
LineTo(hdc, 200,

y
50, 0);
50);

//
x
y
MoveToEx(hdc, 50, 100, 0);
LineTo(hdc, 150, 100);
//
x
y
MoveToEx(hdc, 50, 150, 0);
LineTo(hdc, 100, 150);
}

zeichnet drei waagrechte Linien wie folgt:


LineTo(hdc, x, y) bewegt den Stift von der momentanen Position zum
Punkt (x, y) und zeichnet dabei eine gerade Linie.
MoveToEx(hdc, x, y, 0) bewegt den Stift zum Punkt (x, y), ohne dabei

eine Linie zu zeichnen.


4.4 Linien

69

Sandini Bib

Wie ist das zu verstehen? Die Linien werden mit einem Zeichenstift (
Pencil) gezeichnet. Dieser Stift besitzt eine Position (x, y) und er kann mit LineTo
und MoveToEx geradlinig verschoben werden. Bei LineTo wird der Stift auf dem
Blatt (Bildschirm) bewegt und zieht eine gerade Linie, bei MoveToEx wird der
Stift durch die Luft bewegt und zieht keine Linie. Das vierte Argument von
MoveToEx verwenden wir nicht und setzen es auf 0.
Beachte, dass bei waagrechten Linien die y-Koordinate konstant ist (siehe Beispiel). Bei senkrechte Linien ist die x-Koordinate konstant.
Kannst du dir denken, was passiert, wenn du jedes MoveToEx in unserem Beispiel
durch ein entsprechendes LineTo ersetzt?

Das erste Bild entspricht sicher deiner Erwartung. Wir sehen, wie der Stift links
oben anfngt und dann einen sichtbaren Strich hinterlsst, wenn er zum Anfang
der waagrechten Linien geht.
70

Kapitel 4 Grafik

Sandini Bib

Was aber ist im zweiten Bild geschehen? Dieses Bild erhltst du, wenn du das
Fenster einmal mit der Maus vergrerst oder verkleinerst. In diesem Fall verlangt Windows, dass der Inhalt des Fensters neu gezeichnet wird, und unsere
Funktion malen wird erneut aufgerufen! Und beim zweiten Malen fngt der
Stift da wieder an, wo wir beim ersten Malen aufgehrt hatten.
Bei Linien knnen wir nicht nur die Farbe whlen, sondern auch die Breite in
Pixeln vorgeben. Wenn die Breite 1 ist, haben wir auch noch die Mglichkeit,
gestrichelte Linien zu zeichnen. Hier ist ein Beispiel:
Grafik2.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
COLORREF rosa, gelb, hellblau;
HPEN stift1, stift2, stift3;
// Farben
rosa
= RGB(255,
0, 255);
gelb
= RGB(255, 255,
0);
hellblau = RGB( 0, 255, 255);
// Stifte
stift1 = CreatePen(PS_SOLID, 5, rosa);
stift2 = CreatePen(PS_SOLID, 30, gelb);
stift3 = CreatePen(PS_DOT, 1, hellblau);
// Bewege Stift zum Ausgangspunkt
MoveToEx(hdc, 130, 50, 0);
// Zeichne drei Linien mit verschiedenen Stiften
SelectObject(hdc, stift1);
LineTo(hdc, 200, 200);
SelectObject(hdc, stift2);
LineTo(hdc, 20, 200);
SelectObject(hdc, stift3);
LineTo(hdc, 130, 50);
}

4.4 Linien

71

Sandini Bib

Am Bild erkennst du, in welcher Reihenfolge die Linien gezeichnet werden. Damit sich ein geschlossenes Dreieck bildet, beenden wir das letzte LineTo mit den
Koordinaten, bei denen wir durch das MoveToEx angefangen haben.
Um das Programm bersichtlicher zu machen, speichern wir den Zahlencode,
den RGB liefert, in Variablen vom Typ COLORREF mit den treffenden Namen
rosa, gelb, hellblau. Diese Variablen werden wie immer am Anfang des Befehlsblocks definiert. Wir verwenden diese Variablen statt einer RGB-Anweisung,
wann immer wir einen bestimmten Farbenwert bentigen.
Die Funktion CreatePen (erzeuge Stift) wird als
CreatePen(strichart, strichbreite, farbe)

aufgerufen. Die Strichart (


pencil style) wird mit Namen angegeben, die
in windows.h definiert sind, z. B.
PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT.

solid heit solide (durchgezogen), dash heit Strich, dot heit Punkt.
Wenn die Strichbreite grer als 1 ist, zeichnet der Stift unabhngig von der
Strichart eine durchgezogene Linie.
Das Ergebnis von CreatePen ist ein Handle zu einem Stift mit Datentyp HPEN.
Wir erzeugen drei verschiedene Stifte und speichern die Handles in stift1,
stift2, stift3. Diese Stifte drcken wir dem Zeichner in die Hand, indem wir
den jeweiligen Stift mit der Funktion
SelectObject(hdc, handle)

fr den Device Context hdc auswhlen.

72

Kapitel 4 Grafik

Sandini Bib

4.5 Rechtecke und Kreise


Punkt, Punkt, Komma, Strich, fertig ist das Mondgesicht:
Grafik3.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
Rectangle(hdc, 10, 10, 100, 100);
TextOut(hdc, 15, 45, "Hallo, Welt!", 12);
Ellipse(hdc, 100, 100, 200, 200);
Arc(hdc, 120, 120, 180, 180,
100, 200, 200, 200);
SetPixel(hdc, 130, 140, 0);
SetPixel(hdc, 170, 140, 0);
MoveToEx(hdc, 150, 100, 0);
LineTo(hdc, 145, 90);
LineTo(hdc, 155, 80);
LineTo(hdc, 150, 75);
}

Die folgenden Funktionen ermglichen das Zeichnen von Rechtecken, Kreisen


und Kreisteilen:
Rectangle(hdc, links, oben, rechts, unten)

Zeichne Rechteck mit waagrechten und senkrechten Kanten.


Ellipse(hdc, links, oben, rechts, unten)

Zeichne eine Ellipse, die in das angegebene Rechteck passt. Einen Kreis erhlt
man durch Angabe eines Quadrats.
4.5 Rechtecke und Kreise

73

Sandini Bib

Arc(hdc, links, oben, rechts, unten, x1, y1, x2, y2)

Zeichne den Teil einer Ellipse. Die zwei Punkte (x1, y1) und (x2, y2) definieren
zwei Linien vom Zentrum der Ellipse aus, die den Ellipsenbogen begrenzen.
In diesen Funktionen definieren wir mit vier Zahlen links, oben, rechts und
unten ein Rechteck.
Moment, im Allgemeinen hat ein Viereck vier Ecken (warum?). Fr jede Ecke
brauchen wir 2 Zahlen, also insgesamt 8. Ist dir schon aufgefallen, dass in Windows alle Fenster waagrechte Rechtecke sind, die nicht gekippt oder gedreht sind?
Die Kanten dieser Rechtecke sind parallel zu den x und y-Achsen. Dafr brauchen wir nur 4 Zahlen:
left

right

x-Achse
top

bottom

y-Achse

Die x-Koordinate der linken Kante nennen wir links (left) und die x-Koordinate der rechten Kante plus 1 rechts (right). Die y-Koordinate der oberen
Kante nennen wir oben (top) und die y-Koordinate der unteren Kante plus 1
unten (bottom). Weil wir 1 dazuzhlen, ist z. B. die Breite des Rechtecks genau
rightleft Pixel. Solche Rechtecke knnen ohne berlappung nebeneinander
gelegt werden, wenn left des zweiten Rechtecks gleich right des ersten Rechtecks ist usw.
Gleich ausprobieren. Lege zwei Rechtecke so nebeneinander, dass z. B. die xKoordinaten des ersten von 0 bis 100 und des zweiten von 100 bis 200 reichen.
Oder von 99 bis 200, oder von 101 bis 200.
Hier ist noch ein Experiment. Vertausche einmal die Zeilen mit TextOut und
Rectangle. Wenn du erst den Text und dann das Rechteck zeichnest, bleibt der
Text unsichtbar! Das liegt daran, dass bei Rechtecken und brigens auch bei
Ellipsen das Innere wei ausgemalt wird, wenn wir das nicht ndern.
In dem folgenden Beispiel kannst du die verschiedenen Funktionen in Farbe ausprobieren:
74

Kapitel 4 Grafik

Sandini Bib
Grafik4.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
HPEN hpblau;
HBRUSH hbrot, hbgruen;
// Stift
hpblau = CreatePen(PS_SOLID, 1, RGB(0,0,255));
SelectObject(hdc, hpblau);
// Pinsel
hbrot
= CreateSolidBrush(RGB(255,0,0));
hbgruen = CreateSolidBrush(RGB(0,255,0));
// Rechteck und Text
SelectObject(hdc, hbrot);
Rectangle(hdc, 10, 10, 100, 100);
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
// Mondgesicht
SelectObject(hdc, hbgruen);
Ellipse(hdc, 100, 100, 200, 200);
Arc(hdc, 120, 120, 180, 180,
100, 200, 200, 200);
SetPixel(hdc, 130, 140, 0);
SetPixel(hdc, 170, 140, 0);
}

Wie du siehst, werden Rechtecke und Ellipsen genau wie Linien mit dem momentanen Stift gezeichnet. Rechtecke und Ellipsen knnen farbig ausgefllt wer-

4.5 Rechtecke und Kreise

75

Sandini Bib

den. Dazu wird der momentan gewhlte Pinsel (


brush) verwendet. Einen
deckend malenden Pinsel, Datentyp HBRUSH, bekommst du mit
CreateSolidBrush(farbe);

und genau wie den Stift musst du diesen Pinsel mit z. B.


SelectObject(hdc, hbrot);

im Device Context auswhlen. Die Voreinstellung fr die Stiftfarbe ist schwarz,


die Voreinstellung fr den Pinsel wei.
Vertausche auch in diesem Beispiel TextOut und Rectangle. Der Text wird verdeckt. Aber mit der Anweisung
SelectObject(hdc, GetStockObject(NULL_BRUSH));

kannst du den Pinsel auf durchsichtig umschalten. GetStockObject liefert vorstock objects) von Windows, in diesem Fall
definierte Standardobjekte (
einen Handle zu einem Pinsel, der das Ausmalen von Rechtecken und Kreisen
verhindert. Ausprobieren, der Text wird wieder sichtbar.

4.6 Fonts
Auffallend ist, dass im letzten Beispiel der Schriftzug Hallo, Welt! einen weien Hintergrund behlt. Text wird in einem Rechteck mit eigener Farbe ausgegeben. Auch wird die Farbe von Text nicht vom momentanen Stift bestimmt.
Ntzliche Funktionen fr TextOut sind:
SetTextColor(hdc, farbe) setzt die Farbe von Text.
SetBkColor(hdc, farbe) setzt die Farbe des Texthintergrunds.
SetBkMode(hdc, TRANSPARENT) macht den Texthintergrund durchsichtig,
whrend mit SetBkMode(hdc, OPAQUE) der Texthintergrund sichtbar wird.

Das probieren wir im nchsten Beispiel gleich aus. Hier verndern wir auch die
Textart, den so genannten Font:

76

Kapitel 4 Grafik

Sandini Bib
Grafik5.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
HFONT hfont;
hfont = CreateFont(
80,
// Hoehe
0,
// Breite
300,
// Winkel in 1/10 Grad
0,
// Orientierung in 1/10 Grad
FW_BOLD,
// Gewicht: 0 bis 900, oder FW_NORMAL, FW_MEDIUM, FW_BOLD, ...
0,
// Kursiv (0,1)
1,
// Unterstreichung (0,1)
0,
// Durchgestrichen (0,1)
0,
// Character Set
0, 0, 0,
// Praezision
0,
// Familie
"Times"
// Fontname, z.B. "Times", "Courier", "Symbol" oder 0
);
SelectObject(hdc, hfont);
SetTextColor(hdc, RGB(255,0,0));
SetBkColor(hdc, RGB(200,200,200));
TextOut(hdc, 20, 120, " Hallo ", 7);
}

Fonts sind dir sicher schon begegnet. Viele Programme, BCB und auch Windows
selbst, bieten dir an, die verwendete Schriftart zu ndern. Das kannst du auch
mit der Funktion CreateFont erreichen, die als Ergebnis einen Handle vom Typ
HFONT liefert. Auch in diesem Fall verwenden wir SelectObject, um die neue
Schriftart zu aktivieren. Die Funktion CreateFont wird mit 14 Argumenten
aufgerufen. Eine 0 bedeutet fr die meisten Argumente, dass die Voreinstellung
verwendet werden soll. Nur zu, mach das Hallo BILDSCHIRMFLLEND riesig,
indem du die Fonthhe auf 700 oder so setzt. Fr die Fontbreite verwendest du
4.6

Fonts

77

Sandini Bib

am besten die Voreinstellung durch Angabe der 0, es sei denn, du willst den Text
stauchen. Den Effekt des Winkels zeigt das Beispiel. Mach den Text fett oder
dnn, indem du das Fontgewicht nderst.
Eine weitere wichtige Unterscheidung ist, ob der Text pro Buchstabe denselben
Platz verwendet oder jeder Buchstabe nur so viel Platz verwendet wie ntig.
Letzteres ist in Buchtexten normal, weil sich das besser lesen lsst. In Programmtexten verwenden wir fixed width fonts, damit sich saubere Spalten schreiben
lassen. Schau dir den Text in diesem Buch einmal genau an. In den Programmbeispielen stehen die Buchstaben tatschlich in Spalten bereinander, aber im normalen Text wie in diesem Abschnitt sind die Buchstaben unterschiedlich breit
und stehen nicht in Spalten. Auch bei Textfensteranwendungen wird ein Font
mit konstanter Breite verwendet. So wei man im Voraus, dass in jede Zeile genau 80 Zeichen passen oder was immer man eingestellt hat. Bei Textausgabe im
Grafikfenster hat man die Wahl.

4.7 Hilfe zu BCB und Windows SDK


Mehr Informationen zu den Funktionen des Windows SDK findest du unter
Start, Programme, Borland C++Builder, MS Help, Win32 SDK. Im BCBEditor kannst du auch jederzeit die Hilfe zu dem Wort am Textcursor aufrufen,
indem du F1 drckst. Nach der Installation funktioniert das jedoch nur fr die
Borland Hilfedateien. Die Microsoft-Hilfe kannst du wie folgt aktivieren: Du
findest unter dem Eintrag Borland C++Builder das Programm OpenHelp. Starte
OpenHelp. Unter Suchbereiche whle All Help Files, klicke auf Auswhlen,
dann auf OK. Das Aufrufen der Microsoft-Hilfe mit F1 hat bei mir trotzdem
nicht immer funktioniert. Bei Bedarf kannst du mit Start, Programme usw.
ein extra Fenster fr die Hilfe zum Win32 SDK aufmachen und unter Index
den Namen der Funktion eintippen.
Teste die Hilfe gleich fr main, SetPixel, SetBkMode, TextOut und Rectangle.
Mit etwas geduldigem Lesen kann diese Hilfe sehr ntzlich sein. Z.B. bietet dir
das Hilfefenster rechts oben unter Group eine Liste aller verwandten Funktionen an. So findest du heraus, dass es auch eine Funktion GetPixel gibt. Stbere
ruhig in dieser Hilfe. Je weiter wir in dem Buch fortschreiten, desto mehr kannst
du davon verstehen. Hilfe zur Selbsthilfe ist immer gut. Alle Funktionen, die wir
in diesem Buch verwenden, werde ich aber auch an Ort und Stelle erklren.

78

Kapitel 4 Grafik

Sandini Bib

4.8

Geradlinige Verschiebung

Das folgende Windowsbeispiel (Projekt WinHallo.mak laden!) zeigt einen Kreis


an verschiedenen Orten:
Grafik6.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x, dx, y, dy, r;
x
y
dx
dy
r

= 100;
= 100;
= 20;
=
0;
=
2;

Ellipse(hdc, xr, yr, x+r, y+r);


x += dx;
y += dy;
Ellipse(hdc, xr, yr, x+r, y+r);
x += dx;
y += dy;
Ellipse(hdc, xr, yr, x+r, y+r);
x += dx;
y += dy;
Ellipse(hdc, xr, yr, x+r, y+r);
x += dx;
y += dy;
Ellipse(hdc, xr, yr, x+r, y+r);
}

4.8

Geradlinige Verschiebung

79

Sandini Bib

In
int x, dx, y, dy, r;

definieren wir fnf Variable fr ganze Zahlen, die wir dann in


x
y
dx
dy
r

= 100;
= 100;
= 20;
=
0;
=
2;

initialisieren. Mit x und y bestimmen wir den Mittelpunkt eines Kreises, der den
Radius r haben soll. Einen solchen Kreis malen wir mit
Ellipse(hdc, xr, yr, x+r, y+r);

Wie in Kapitel 4.5 besprochen, werden Kreise und Ellipsen durch die Ausmae
eines Rechtecks bestimmt. Wenn (x, y) der Mittelpunkt eines Kreises sein soll,
dann ist die linke Kante des Rechtecks bei xr, die rechte Kante bei x+r, die
obere Kante bei yr und die untere Kante bei y+r.
Schon bei der Ausgabe nur eines Kreises hat sich die Verwendung von Variablen
gelohnt. Ohne die Variable r mssten wir vier Zahlen im Ellipse-Befehl ndern, wenn wir den Radius des Kreises ndern wollen. Das Gleiche gilt, wenn wir
die Position des Kreises ndern mchten. So mssen wir nur die Initialisierung
der Variablen ndern und das Programm berechnet anhand der Variablen alle
bentigten Koordinaten.
Nach der Initialisierung und der Ausgabe des ersten Kreises wiederholen wir
viermal die folgenden Befehle:
x += dx;
y += dy;
Ellipse(hdc, xr, yr, x+r, y+r);

Schau dir das Programm genau an, die Befehle sind identisch. Indem wir die xKoordinate des Mittelpunkts vor jeder Ausgabe um immer dieselbe Differenz dx
erhhen, verschieben wir den Kreis um konstante Schrittweiten in einer geraden
Linien. Gleichzeitig knnen wir eine Verschiebung in der y-Richtung um die
Differenz dy durchfhren. Die drei Schnappschsse habe ich mit den folgenden
Parametern erzeugt:
1. r =

2; dx = 20; dy =

2. r = 10; dx =

0;

0; dy = 20;

3. r = 30; dx = 20; dy =

21;

// horizontal nach rechts


// vertikal nach oben
// schraeg nach rechts unten

Dieses Beispiel schreit geradezu nach einer Verbesserung. Es muss doch mglich
sein, identische Programmteile wie die Verschiebebefehle plus Ausgabe beliebig
80

Kapitel 4 Grafik

Sandini Bib

oft zu wiederholen. Zwar knnen wir die Befehle einfach kopieren, aber dann
ist die Anzahl der Wiederholungen von vornherein festgelegt. Wir wrden z. B.
gerne n Wiederholungen anfordern, wobei n gleich 5 oder gleich 1000 sein kann.
Solche Wiederholungen macht man mit so genannten Schleifen, siehe Kapitel 6.

4.9

Bunte Ellipsen

Hier ist ein nettes Beispiel, in dem Ellipsen verschiedener Gre durch die Wiederholung desselben Befehls, aber mit unterschiedlichen Werten der Variablen
ausgegeben werden:
Grafik7.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x, y, rx, ry, drx, dry;
COLORREF gelb, rot, schwarz;
HBRUSH hbgelb, hbrot, hbschwarz, hbalt;
// Farben
gelb
= RGB(255, 255,
rot
= RGB(255,
0,
schwarz = RGB( 0,
0,

0);
0);
0);

// Pinsel
hbschwarz = CreateSolidBrush(schwarz);
hbgelb
= CreateSolidBrush(gelb);
hbrot
= CreateSolidBrush(rot);
// Waehle neuen Pinsel, aber merke dir den alten
hbalt = SelectObject(hdc, hbschwarz);
// Hintergrund
Rectangle(hdc, 0, 0, 1600, 1200);
// Malen
x
= 120;
y
= 110;
rx = 100;
ry = 80;
drx = 20;
dry =
0;

4.9

Bunte Ellipsen

81

Sandini Bib
Grafik7.c WinHallo.cpp

SelectObject(hdc, hbrot);
Ellipse(hdc, xrx, yry, x+rx,
rx += drx;
ry += dry;
SelectObject(hdc, hbgelb);
Ellipse(hdc, xrx, yry, x+rx,
rx += drx;
ry += dry;
SelectObject(hdc, hbrot);
Ellipse(hdc, xrx, yry, x+rx,
rx += drx;
ry += dry;
SelectObject(hdc, hbgelb);
Ellipse(hdc, xrx, yry, x+rx,
rx += drx;
ry += dry;
SelectObject(hdc, hbrot);
Ellipse(hdc, xrx, yry, x+rx,
rx += drx;
ry += dry;
SelectObject(hdc, hbgelb);
Ellipse(hdc, xrx, yry, x+rx,

y+ry);

y+ry);

y+ry);

y+ry);

y+ry);

y+ry);

// Aufraeumen
SelectObject(hdc, hbalt);
DeleteObject(hbschwarz);
DeleteObject(hbgelb);
DeleteObject(hbrot);
}

In
hbalt = SelectObject(hdc, hbschwarz);

82

Kapitel 4 Grafik

Sandini Bib

zeigen wir, dass SelectObject als Ergebnis einen Handle liefert. Dieser Handle
gibt an, welches Objekt aus der Hand gelegt wurde, als das neue Objekt ausgewhlt wurde. In hbalt merken wir uns den vorhergehenden Pinsel, damit wir
ihn vor dem Verlassen von malen mit
SelectObject(hdc, hbalt);

wieder aktivieren knnen.


Eine weitere gute Angewohnheit ist, mit
DeleteObject(hbschwarz);

und so weiter die Handle wieder freizugegeben und zu lschen (


delete),
die wir erzeugt hatten. Jedem create sollte im Programm ein delete folgen,
sonst muss Windows irgendwann zu viele Pinsel und so weiter verwalten.
Zur Abwechslung malen wir diesmal auf einen schwarzen Hintergrund, den wir
mit
Rectangle(hdc, 0, 0, 1600, 1200);

erzeugen. Die Gre des Rechtecks derart zu fixieren ist ungeschickt. In Kapitel
8 besprechen wir eine Mglichkeit, die Gre des Fensters zu erhalten.
Beim Malen verndern wir diesmal nicht die Position, sondern den Radius in der
x- und y-Richtung der Ellipsen. Auerdem ndern wir zwischen den Malbefehlen die Farbe. Die beiden Bilder erhltst du mit
1. rx = 100; ry =
2. rx =

80; drx = 20; dry =

20; ry = 100; drx =

0;

20; dry = 20;

4.10

TextOut mit Format

Bei der Funktion printf knnen wir in den Text, der ausgegeben werden soll,
auch den Wert von Variablen einsetzen: mit %d ganze Zahlen, mit %f Fliekommazahlen und mit %s Strings. TextOut ist nicht so vielseitig, aber betrachte einmal das folgende Beispiel:

4.10

TextOut mit Format

83

Sandini Bib
Grafik8.c WinHallo.cpp

#include <windows.h>
#include <stdio.h>
void malen(HDC hdc)
{
HFONT hfalt, hfneu;
char text[100];
int x, y, dy, yfont;
yfont = 20;
dy = yfont + yfont/5;
x = 20;
y = 50;
hfneu = CreateFont(yfont, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Courier");
hfalt = SelectObject(hdc, hfneu);
SetTextColor(hdc, RGB(255,0,0));
SetBkColor(hdc, RGB(200,200,200));
sprintf(text, "Fontgroesse: %d", yfont);
TextOut(hdc, x, y, text, strlen(text));
y += dy;
sprintf(text, "Neue Zeile: +%d", dy);
TextOut(hdc, x, y, text, strlen(text));
SelectObject(hdc, hfalt);
DeleteObject(hfneu);
}

Das erste Bild zeigt Ausgabe mit dem Font "Courier" und das zweite Bild den
voreingestellten Font, den du mit 0 statt "Courier" erhltst. "Courier" benutzt
gleich viele Pixel pro Zeichen. Wie in Beispiel 4.9 verwenden wir Variablen, hier
84

Kapitel 4 Grafik

Sandini Bib

hfontalt und hfontneu vom Typ HFONT, um die Handles zu speichern. Vor dem
Verlassen der Funktion malen stellen wir mit
SelectObject(hdc, hfontalt)

den ursprnglichen Font wieder her.


Dieses Beispiel fhrt dir die Funktion sprintf vor. sprintf funktioniert genau
wie printf, auer dass der Text nicht am Textbildschirm ausgegeben, sondern
in einen String geschrieben wird. Das Schema ist
printf(Formatstring, Argumente . . .)
sprintf(Stringvariable, Formatstring, Argumente . . .)

Beide Funktion kommen aus der stdio, deshalb #include <stdio.h>. Dort findest du auch die Funktion sscanf, die nicht wie scanf von der Tastatur, sondern
aus einem String liest.
Ohne Textfenster ist printf nutzlos. Und weil TextOut keine Formatstrings
kennt, knnen wir mit TextOut nicht ohne weiteres Variablen ausgeben. Aber
wie das Beispiel zeigt, knnen wir den Ausgabetext mit sprintf im String text
zusammenbasteln und dann mit TextOut ausgeben.
Weil TextOut keinen Zeilenvorschub durchfhrt, machen wir das mit den Zeilen
dy = yfont + yfont/5;
y += dy;

selbst. Fr die neue Zeile erhhen wir y um dy. Bei dy = yfont; sitzen die
Zeilen genau untereinander, bei dy = yfont 1; berlappen sie sich. Probier
das mal aus. Wir knnten dy = yfont + 2; oder hnliches verwenden, aber
wenn sich dann die Fontgre ndert, bleibt der Zwischenraum konstant 2. In
unserem Beispiel berechnen wir den Zwischenraum mit yfont/5, so dass der
Zwischenraum fr beliebige Fontgren 20% betrgt.

4.11
In diesem Kapitel haben wir wenig ber C, aber dafr umso mehr ber die einfachsten Grafikfunktionen des Windows SDK gelernt. Punkte, Linien, Kreise,
verschiedene Fonts und verschiedene Farben: alles kein Problem. Jedes einzelne
Pixel knnen wir per Koordinatenangabe auswhlen und dadurch die verschiedenen Grafikelemente an einem beliebigen Ort im Fenster positionieren. Davon
werden wir in den nachfolgenden Kapiteln ausgiebig Gebrauch machen.
Genau genommen will sich niemand die Mhe machen, fr ein Rechteck jede Koordinate durch Ausprobieren hinzufummeln. Wenn die Aufgabe lediglich darin
4.11

85

Sandini Bib

besteht, ein einzelnes Rechteck zu zeichnen, ist man mit einem fertigen Malprogramm besser bedient. Der Witz ist natrlich, dass mit jedem Sprachelement
von C, das wir dazulernen, auch unsere Mglichkeiten anwachsen, kompliziertere Grafiken zu berechnen und zu automatisieren das heit Grafik zu programmieren! In Krze:
Wir verwenden das Projekt WinHallo.mak, welches in WinHallo.cpp fr
uns die Fenstererzeugung in Windows erledigt. Wir experimentieren mit der
Funktion malen in WinHallo0.c und anderen Beispielen, weil uns noch einige Sprachelemente von C fehlen, um Fenster selber zu verwalten.
Die folgenden Windowsfunktionen frs Zeichnen und fr Text haben wir
kennen gelernt:
SetPixel zeichnet ein Pixel.
LineTo zeichnet eine Linie.
MoveToEx bewegt den Zeichenstift, ohne eine Linie zu zeichnen.
Rectangle zeichnet ein Rechteck.
Ellipse zeichnet Ellipsen und Kreise.
Arc zeichnet den Teil einer Ellipse.
TextOut schreibt Text mit verschiedenen Fonts.

Zum Zeichnen und fr die Textausgabe mit TextOut bentigen wir den
Handle fr einen Device Context, Typ HDC. Dieses Gerteumfeld verndern
wir mit
SelectObject(hdc, objekt)

Neue Objekte fr den Device Context erzeugen wir mit:


CreatePen fr Zeichenstifte fr Linien
CreateSolidBrush fr Pinsel zum Ausmalen
CreateFont fr Fonts

Farbangaben machen wir mit


RGB(rot, gruen, blau),

wobei jede der drei Zahlen die Intensitt fr die jeweilige Farbe angibt.
Die Ausgabe von Text beeinflussen wir mit SetTextColor, SetBkColor und
SetBkMode.

86

Kapitel 4 Grafik

Sandini Bib

4.12
1. Komponiere deine eigene Grafik.
2. Spiele in den Beispielen mit allen Koordinaten, um ein Gefhl fr Koordina-

ten zu bekommen.
3. Male vier ineinander gesetzte Rechtecke, deren rote Farbe nach innen immer

heller wird. Das grte Rechteck soll den ganzen Bildschirm ausfllen. Wie
erhltst du mehrere konzentrische Kreise (Kreise mit demselben Mittelpunkt,
aber unterschiedlichen Radien)?
4. Suche nach Windowsanwendungen, die es erlauben, den Font einzustellen
(z. B. BCB). Versuche, mit CreateFont diesen Font nachzubilden.
5. In Beispiel 4.8 wird eine Figur in gleich groen Schritten versetzt. Das heit
dx und dy bleiben konstant. ndere das Programm derart, dass bei jedem
Schritt dx und dy grer (oder kleiner) werden.
6. Der Punkt in der Mitte einer Linie von (x1, y1) nach (x2, y2) ist gegeben

durch
xmitte =

x1+x2
2

ymitte =

y1+y2
2

bersetze diese Formeln nach C. Schreibe ein Testprogramm, das mehrere


Linien zeichnet und jeden Mittelpunkt durch einen kleinen Kreis mit Radius
2 Pixel oder so markiert. Weil du fr Pixelkoordinaten mit ganzen Zahlen
rechnest, kann es sein, dass die Markierung die Linie nicht genau trifft, denn
halbe Pixel gibt es nicht.
Du knntest dann auch die Mittelpunkte durch Linien verbinden. Was erhltst du, wenn du mit einem Quadrat beginnst, dann die Mittelpunkte verbindest und anschlieend die Mittelpunkte der neuen Linien verbindest?

4.12

87

Sandini Bib

Sandini Bib

5
Bedingungen
5.0
5.1
5.2
5.3
5.4
5.5

Bedingungen mit if
Vergleichsoperatoren, Wahr und Falsch
Logische Verknpfungen
Bedingungen mit mehreren ifs
Bedingungen mit if und else
Bedingungen mit else if

90
92
95
97
98
101

5.6

Entscheidungen fr den Bauch

102

5.7

Zufallszahlen

103

5.8

Ein Held des Zufalls

108

5.9

Bisektion

111

5.10

114

5.11

115

Sandini Bib

In allen Beispielen, die wir bisher betrachtet haben, wurden die Anweisungen
Zeile fr Zeile schn der Reihe nach ausgefhrt. In diesem Kapitel besprechen
if und else (wenn und sonst), mit denen du
wir die C-Anweisungen
die Ausfhrung von Befehlen an Bedingungen knpfen kannst. Das heit, wir
wollen besprechen, wie man Befehle berspringt oder ausfhrt, je nachdem ob
eine Bedingung zutrifft oder nicht. Mit dem nchsten Beispiel wird klar werden,
was ich damit meine.

5.0 Bedingungen mit if


Wenn du weit, dass
raten, was hier los ist:

if auf Deutsch wenn oder falls heit, kannst du


If0.c

#include <stdio.h>
int main()
{
int i = 10;
if (i > 0)
printf("%d ist groesser als 0.\n", i);
getchar();
return 0;
}

Dies ist ein Beispiel ohne Grafik und wird als Textfensteranwendung eingegeben.
Die Zeilen
if (i > 0)
printf("%d ist groesser als 0.\n", i);

bedeuten:
wenn i grer als 0 ist,
teile das der Welt mit.

Wir erhalten
10 ist groesser als 0.

Wenn du i = 10 statt i = 10 einsetzt, wird der printf-Befehl bersprungen


und der Textbildschirm bleibt leer:

Im Allgemeinen verwendet man if so:


90

Kapitel 5 Bedingungen

Sandini Bib

if (Ausdruck)

Befehl

Wenn Ausdruck wahr ist, wird Befehl ausgefhrt. Und wenn Ausdruck falsch
ist, wird Befehl bersprungen. Lass das Beispiel Schritt fr Schritt im Debugger
laufen! Tatschlich wird je nach Wert von i der printf-Befehl ausgefhrt oder
bersprungen.
In anderen Worten: Mit if knnen wir die Ausfhrung von Befehlen von der Erfllung einer Bedingung abhngig machen. Manche Programmiersprachen verwenden if und then, um ausdrcklich wenn/dann zu sagen. In C wird kein
then geschrieben.
Praktischerweise kannst du mehrere Befehle mit geschweiften Klammern
zu einem Befehl zusammenfassen. Ein solcher Befehlsblock darf in die ifKonstruktion eingesetzt werden:
If1.c

#include <stdio.h>
int main()
{
int i;
printf("Sag mir eine positive ganze Zahl:
scanf("%d", &i);

");

if (i > 0) {
printf("%d ist groesser als 0.\n", i);
printf("Vielen Dank.\n");
}
getchar(); getchar(); return 0;
}

Bitte merken: hinter einem Block kein ; schreiben.


Je nachdem, ob die eingegebene Zahl positiv ist (i > 0) oder nicht, werden entweder alle Befehle zwischen den Klammern ausgefhrt oder der ganze Befehlsblock wird bersprungen. Zum Beispiel
Sag mir eine positive ganze Zahl:
3 ist groesser als 0.
Vielen Dank.

Sag mir eine positive ganze Zahl:

Ist dir aufgefallen, dass ich alle Befehle, die vom if abhngen, nach rechts verschoben eingetippt habe? Das ist eine gute Angewohnheit, denn so kann man
5.0

Bedingungen mit if

91

Sandini Bib

auf einen Blick feststellen, wo die Ausfhrung des Programms nach dem if weitergeht. Was wird geschehen, wenn du in
if (i > 0) {
printf("%d ist groesser als 0.\n", i);
printf("Vielen Dank.\n");
}

die geschweiften Klammern weglsst, also


if (i > 0)
printf("%d ist groesser als 0.\n", i);
printf("Vielen Dank.\n");

/* falsch eingerueckt! */

schreibst? Dass wir den Befehl printf("Vielen Dank.\n"); so schn nach


rechts gerckt haben, ist dem Compiler vllig egal:
Sag mir eine positive ganze Zahl:
Vielen Dank.

Denn if bezieht sich auf genau einen Befehl oder Befehlsblock, daher wird
printf("Vielen Dank.\n"); ausgefhrt, egal welche Zahl eingegeben wurde.
Einrcken mit Leerzeichen ist dem Compiler egal.
Es ist auf jeden Fall eine gute Idee, Befehle, die im selben Block stehen, gleich
weit einzurcken. Man muss es nur richtig machen. Das Beispiel schreiben wir
nach unserer Einrckregel korrekt als
if (i > 0)
printf("%d ist groesser als 0.\n", i);
printf("Vielen Dank.\n");

Du kannst zur Verdeutlichung auch


if (i > 0) {
printf("%d ist groesser als 0.\n", i);
}
printf("Vielen Dank.\n");

verwenden. Eine kompaktere Schreibweise ist


if (i > 0) printf("%d ist groesser als 0.\n", i);
printf("Vielen Dank.\n");

if bezieht sich immer auf genau einen nachfolgenden Befehl oder Befehlsblock.

5.1 Vergleichsoperatoren, Wahr und Falsch


In unseren ersten Beispielen fr if haben wir das Grerzeichen > verwendet,
um die Aussage i > 0 zu formulieren. Zahlen knnen mit den folgenden Operatoren verglichen werden:
92

Kapitel 5 Bedingungen

Sandini Bib

==

gleich

!=

ungleich

>

grer

<=

kleiner oder gleich

>=

grer oder gleich

<

kleiner

Diese Vergleichsoperatoren verndern den Wert von Variablen nicht. Insbesondere musst du = und == unterscheiden:
i == j
i = j

vergleiche: teste, ob i gleich j ist


setze gleich: kopiere Wert von j nach i

Fr zwei Variablen i und j ist eine Aussage wie i ist gleich j entweder wahr
oder falsch. Mit i == j knnen wir das testen.
Manche Programmiersprachen definieren einen neuen Datentyp, der nur die
zwei Werte wahr und falsch annehmen kann. Man spricht von Booleschen Variablen, und wenn man mit solchen Variablen rechnet, von der Booleschen Algebra
C ist sparsam: Die Zahl 0 hat
(nach dem Mathematiker George Boole).
auch die Bedeutung falsch und jede Zahl ungleich 0 hat die Bedeutung wahr.
Also bedeuten 1, 1 oder 99 wahr.
Tatschlich funktioniert i == j in C wie eine Rechenoperation, die zwei Zahlen verarbeitet und als Ergebnis eine neue Zahl liefert! Falls wahr, ergibt eine
Vergleichsoperation den Wert 1, falls falsch den Wert 0. Hier ist dazu ein Testprogramm fr ganze Zahlen, die Vergleichsoperatoren sind aber auch fr Kommazahlen verwendbar:
If2.c

#include <stdio.h>
int main()
{
int i, j;
printf("\nSag mir eine Zahl:
scanf("%d", &i);
printf("Sag mir noch eine Zahl:
scanf("%d", &j);
printf("%d
printf("%d
printf("%d
printf("%d
printf("%d
printf("%d

==
!=
>
<=
<
>=

%d
%d
%d
%d
%d
%d

ist
ist
ist
ist
ist
ist

%d\n",
%d\n",
%d\n",
%d\n",
%d\n",
%d\n",

");

");

i,
i,
i,
i,
i,
i,

j,
j,
j,
j,
j,
j,

i
i
i
i
i
i

==
!=
>
<=
<
>=

j);
j);
j);
j);
j);
j);

getchar(); getchar(); return 0;


}

5.1 Vergleichsoperatoren, Wahr und Falsch

93

Sandini Bib

Sag mir eine Zahl:


Sag mir noch eine Zahl:
5 == 7
ist
0
5 != 7
ist
1
5 > 7
ist
0
5 <= 7
ist
1
5 < 7
ist
1
5 >= 7
ist
0

5
7

Sag mir eine Zahl:


Sag mir noch eine Zahl:
10 == 10
ist
1
10 != 10
ist
0
10 > 10
ist
0
10 <= 10
ist
1
10 < 10
ist
0
10 >= 10
ist
1

10
10

Weil das Ergebnis jeder Vergleichsoperation die ganze Zahl 0 oder 1 ist, verwenden wir das printf-Format %d. Schau dir das Ergebnis genau an. Das Ergebnis
1 bedeutet wahr, das Ergebnis 0 bedeutet falsch.
In der Tabelle am Anfang dieses Unterkapitels steht in der zweiten Spalte das
logische Gegenteil von der ersten. Wenn die Aussage i ist gleich j wahr ist, ist
die Aussage i ist ungleich j falsch. Beachte, dass das Gegenteil von > nicht etwa
< ist. Denn wenn z. B. eine Zahl i nicht grer als j ist, muss sie nicht notwendigerweise kleiner als j sein. Die Zahl i knnte auch gleich j sein. berprfe in
unserem Beispiel, dass das logische Gegenteil von 0 die 1 ist, und von 1 die 0.
Da wir jetzt wissen, dass Nicht-Null und Null die Rolle von wahr und falsch
spielen, knnen wir die if-Anweisung besser verstehen. if (Ausdruck) kannst
du auf verschiedene Weisen lesen:
wenn (Ausdruck != 0),
wenn (Aussage wahr),
wenn (Bedingung erfllt).

Was aber tatschlich geschieht, ist, dass die Ausfhrung von Befehlen nach if
nur davon abhngt, was fr eine Zahl Ausdruck liefert. Insbesondere wird der
Befehl in
if (99)

Befehl

immer ausgefhrt, der Befehl in


if (0)

Befehl

wird niemals ausgefhrt.


94

Kapitel 5 Bedingungen

Sandini Bib

5.2 Logische Verknpfungen


Viele Entscheidungen beruhen auf mehr als einer Bedingung:
Wenn ich Zeit habe und wenn ich Geld brig habe, gehe ich ins Kino.
Wenn ich Zeit habe und wenn mich jemand einldt, gehe ich ins Kino.
Wenn ich Zeit habe und wenn ich Geld brig habe oder wenn mich jemand
einldt, gehe ich ins Kino.

An der Kinokasse gibt es Gruppenrabatt:


Wenn es weniger als 10 Leute sind, kostet es 8 Euro.
Wenn es 10 oder mehr Leute sind, kostet es 5 Euro.
Wenn es 2 Leute sind, kostet es auch 5 Euro.

In der automatisierten Kasse luft vielleicht das folgende Programm:


If3.c

#include <stdio.h>
int main()
{
int n;
printf("Wie viele Leute seid ihr?
scanf("%d", &n);

");

if (n <= 0)
printf("Naechster bitte.\n");
if (n == 2 || n >= 10)
printf("Das macht 5 Euro pro Nase.\n");
if (n == 1 || n >= 3 && n <= 9)
printf("Das macht 8 Euro pro Nase.\n");
getchar(); getchar(); return 0;
}

Wie viele Leute seid ihr? 2


Das macht 5 Euro pro Nase.

Hier mchte ich dir zeigen, wie sich zwei Aussagen mit dem logischen Oder- und
dem logischen Und-Operator verknpfen lassen:
||
&&

logisches Oder
logisches Und

5.2 Logische Verknpfungen

95

Sandini Bib

Diese funktionieren wie folgt:


Aussage1 || Aussage2 ist wahr, falls mindestens eine der beiden Aussagen
wahr ist, sonst falsch.
Aussage1 && Aussage2 ist wahr, falls beide Aussagen wahr sind, sonst falsch.

Im Beispiel ist
n == 2 || n >= 10

wahr, wenn n gleich 2 ist oder wenn n grer gleich 10 ist. Dann gilt das Sonderangebot. Das Sonderangebot gilt nicht, wenn man alleine ist,
n == 1

oder wenn 3 bis 9 Freunde zusammen ins Kino gehen,


n >= 3 && n <= 9

d.h. mindestens 3 und hchstens 9. So muss man einen Zahlenbereich in C eingeben, denn 3 <= n <= 9 funktioniert nicht.
Im Beispiel habe ich diese Bedingungen als
n == 1 || n >= 3 && n <= 9

verknpft. Genau wie beim Rechnen mit Punkt und Strich stellt sich die Frage, in
welcher Reihenfolge Aussagen verknpft werden. Bei den logischen Operatoren
gilt Und vor Oder und Klammern werden wie immer zuerst ausgerechnet:
i || j && k entspricht i || (j && k)
i || j && k ist nicht dasselbe wie (i || j) && k

Aussagen lassen sich auch ins Gegenteil umkehren. Der Nicht-Operator ! macht
aus wahr falsch und aus falsch wahr. Man sprich von Verneinung oder Negation.
Wie oft habe ich mir einen solchen Zauberstab schon gewnscht! Genauer gesagt
wird aus 0 eine 1 und aus jeder Zahl, die nicht 0 ist, eine 0. Zum Beispiel knnen
wir statt mit if (n <= 0) mit
if (!(n > 0))
printf("Naechster bitte.\n");

den Fall behandeln, dass 0 oder eine negative Zahl eingegeben wird. Das kannst
du als
wenn nicht n grer als 0 ist

lesen.
Beachte, dass ! strker als > und die anderen Vergleichsoperatoren
bindet, deshalb schreiben wir !(n > 0).
Die Operatoren Und (&&), Oder (||) und Nicht (!) sind wichtige Operationen in
der Booleschen Algebra. Mit !, || und && kann man jede denkbare Verknpfung
von Aussagen formulieren, z. B. auch das Entweder-Oder, fr das es in C keinen
eigenen Aussagenoperator gibt, siehe 5.11.
96

Kapitel 5 Bedingungen

Sandini Bib

5.3 Bedingungen mit mehreren ifs


Je nachdem, was uns klarer vorkommt, knnen wir die Operatoren || und && in
vielen Fllen umgehen:
If4.c

#include <stdio.h>
int main()
{
int n;
printf("Wie viele Leute seid ihr?
scanf("%d", &n);

");

if (n <= 0)
printf("Naechster bitte.\n");
if (n == 1)
printf("Das macht 8 Euro pro Nase.\n");
if (n == 2)
printf("Das macht 5 Euro pro Nase.\n");
if (n >= 10)
printf("Das macht 5 Euro pro Nase.\n");
if (n >= 3)
if (n <= 9)
printf("Das macht 8 Euro pro Nase.\n");
getchar(); getchar(); return 0;
}

Statt mit
if (n == 2 || n >= 10)
printf("Das macht 5 Euro pro Nase.\n");

das Oder in wenn n gleich 2 oder n grer gleich 10 zu bewirken, knnen wir
einfach zwei if-Anweisungen hintereinander schreiben. Allerdings mssen wir
dann den printf-Befehl wiederholen.
Das Und in n >= 3 && n <= 9 knnen wir als
if (n >= 3)
if (n <= 9)
printf("Das macht 8 Euro pro Nase.\n");

schreiben. Was geht hier vor? Bei


5.3 Bedingungen mit mehreren ifs

97

Sandini Bib

if (n <= 9)
printf("Das macht 8 Euro pro Nase.\n");

handelt es sich um einen Befehl, der nur ausgefhrt wird, wenn das erste
if (n >= 3) erfllt ist. Oder andersherum, wir knnen Bedingungen verschrfen, indem wir mehrere ifs ineinander setzen. Um das noch deutlicher zu
machen, kannst du
if (n >= 3) {
if (n <= 9) {
printf("Das macht 8 Euro pro Nase.\n");
}
}

schreiben. Im Befehlsblock des ersten ifs knnen beliebige Befehle stehen, in


diesem Fall eben noch ein if.

5.4 Bedingungen mit if und else


Wir haben jetzt erste Erfahrungen gesammelt, wie man mit if die Ausfhrung
von Programmteilen von Bedingungen abhngig machen kann. Oft mchten wir
je nach Erfllung der Bedingung die eine oder die andere Aktion ausfhren. Das
knnte so aussehen:
if (i > 0)
printf("%d ist groesser als 0.\n", i);
if (!(i > 0))
printf("%d ist nicht groesser als 0.\n", i);

Dafr gibt es in C die if-else-Konstruktion.


else schreiben wir Alternativen z. B. als

else heit sonst. Mit ifIf5.c

#include <stdio.h>
int main()
{
int i;
i = 10;
if (i > 0)
printf("%d ist groesser als 0.\n", i);
else
printf("%d ist nicht groesser als 0.\n", i);
getchar();
return 0;
}

98

Kapitel 5 Bedingungen

Sandini Bib

10 ist nicht groesser als 0.

Das liest sich so: Wenn i grer als 0 ist, teile das der Welt mit, sonst ist unsere
Botschaft, dass i nicht grer als 0 ist. Die Syntax sieht so aus:
if (Ausdruck)

Befehl1
else

Befehl2

Der Ausdruck wird ausgewertet. Wenn das Ergebnis wahr ergibt, wird Befehl1
ausgefhrt und Befehl2 ignoriert. Wenn hingegen der Ausdruck falsch ergibt,
wird Befehl1 ignoriert und Befehl2 ausgefhrt. Der Debugger kann dir helfen,
den Programmablauf zu verfolgen.
brigens zhlt die gesamte if-else-Konstruktion ebenfalls als ein Befehl. Du
kannst also wie in
If6.c

#include <stdio.h>
int main()
{
int i;
printf("Sag mir eine positive ganze Zahl:
scanf("%d", &i);

");

if (i <= 0) {
if (i < 0) {
printf("Diese Zahl ist negativ!\n");
} else {
printf("Knapp daneben ist auch daneben.\n");
}
}
getchar(); getchar(); return 0;
}

eine Bedingung mit aufeinander folgenden ifs verschrfen und gleichzeitig Alternativen mit else behandeln. Dabei ist zu beachten, dass else sich immer
auf das if bezieht, das dem else vorausgeht. In unserem Beispiel knnten die
geschweiften Klammern auch weggelassen werden (und zwar alle Klammern,
probier es mal aus).
Gerade weil else sich immer auf das if direkt ber sich bezieht, sind im nchsten
Beispiel geschweifte Klammern erforderlich:

5.4 Bedingungen mit if und else

99

Sandini Bib
If7.c

#include <stdio.h>
int main()
{
int i;
printf("Sag mir eine positive ganze Zahl:
scanf("%d", &i);

");

if (i <= 0) {
printf("Diese Zahl ist nicht > 0.\n");
if (i < 0)
printf("Diese Zahl ist negativ!\n");
}
else
printf("Vielen Dank.\n");
getchar(); getchar(); return 0;
}

Ausprobieren! Im Zweifelsfall einfach Klammern setzen.


Manchmal bentigt man if-else nur, um sich zwischen zwei Zahlen zu entscheiden. Dafr gibt es auch den ?:-Operator:
maximum = (i > j) ? i : j;

weist der Variable maximum den greren der beiden Werte i und j zu. Das
Schema ist
Ausdruck0 ? Ausdruck1 : Ausdruck2

was zusammen einen einzigen Ausdruck ergibt. Wenn Ausdruck0 wahr ist,
ergibt der gesamte Ausdruck den Wert von Ausdruck1. Wenn Ausdruck0 falsch
ist, ergibt der gesamte Ausdruck den Wert von Ausdruck2. Welchen Wert
erhlt maximum, wenn i gleich j ist? Dann wird Ausdruck2 zugewiesen, also
maximum = j;, was wiederum gleich i ist.

100

Kapitel 5 Bedingungen

Sandini Bib

5.5 Bedingungen mit else if


Eine Verkettung von if-else wie in
If8.c

#include <stdio.h>
int main()
{
int i;
printf("Wieviele Hamburger moechtest du?
scanf("%d", &i);

");

if (i < 0)
printf("Quatsch.\n");
else if (i == 0)
printf("Na gut.\n");
else if (i == 1)
printf("Bitte schoen.\n");
else if (i == 2 || i == 3)
printf("Na ausnahmsweise.\n");
else
printf("Prost Bauchweh.\n");
getchar(); getchar(); return 0;
}

ist ntzlich, wenn man einen von mehreren mglichen Fllen behandeln mchte.
else if ist kein neuer Befehl, sondern ein else mit einem Befehl, der wieder
mit if anfngt. In diesem Fall sieht man vom Einrcken nachgeordneter Blcke
ab, damit man den Code besser lesen kann. Wie wrde unser Beispiel aussehen,
wenn jedes if und jedes else seine eigene Zeile bekme und um zwei weiter
eingerckt wrde?
Die Bedingungen werden eine nach der anderen getestet, bis eine erfllt ist, und
nur deren Befehl wird ausgefhrt. Das letzte else stellt sicher, dass auch dann etwas Sinnvolles geschieht, wenn keine einzige der Bedingungen zutrifft. In einer
Fallunterscheidung nennt man so etwas auch den Default. Das ist sozusagen
die Voreinstellung, die bestimmt, was geschieht, wenn sonst nichts unternommen wird. Auch bei Variablen bezeichnet man oft den Wert, den eine Variable
als Voreinstellung erhlt, als ihren Default.
In C kann man Fallunterscheidungen statt mit if und else if auch mit den
Schlsselwrtern switch und case konstruieren (falls in den Bedingungen nur
mit Konstanten verglichen wird). Falls dir das irgendwo begegnet, kannst du in
Anhang A.2 darber nachlesen.

5.5 Bedingungen mit else if

101

Sandini Bib

5.6

Entscheidungen fr den
Bauch

Hier ist eine noch realistischere Computersimulation eines Restaurantbesuchs:


If9.c

#include <stdio.h>
int main()
{
int trinken, essen;
// bestelle Getraenke
printf("\nWillkommen in unserem Restaurant.\n\n");
printf("Was moechtest du trinken?\n");
printf("1 Wasser\n");
printf("2 Brause\n");
printf("3 Kaffee\n");
printf("Bitte waehle eine Nummer: ");
scanf("%d", &trinken);
printf("\n");
// und was sagt die Bedienung dazu?
if (trinken < 1 || trinken > 3)
printf("Haben wir nicht, auch egal.");
else
printf("Gut.");
// bestelle das Essen
printf(" Was moechtest du essen?\n");
printf("1 Pommes\n");
printf("2 Schnitzel mit Pommes\n");
printf("3 Pommes mit Schnitzel\n");
printf("4 Schnommes mit Pitzel\n");
printf("Bitte waehle eine Nummer: ");
scanf("%d", &essen);
printf("\n");
// und was sagt die Bedienung dazu?
if (essen < 1 || essen > 4) {
if (trinken < 1 || trinken > 3) {
printf("Sag mal, willst du mich veraeppeln? Raus mit dir!\n");
} else {
printf("Haben wir nicht. Ich bringe erst mal die Getraenke.\n");
}
}
else {
printf("Gut. Kommt sofort!\n");
}
getchar(); getchar(); return 0;
}

102

Kapitel 5 Bedingungen

Sandini Bib

Dies ist ein Beispiel fr eine Reihe von Entscheidungen, mit denen berprft
wird, ob gewisse Eingaben Sinn machen. Es werden zwei Fragen gestellt, die sinnig oder unsinnig beantwortet werden knnen. Jede der vier Mglichkeiten ergibt eine unterschiedliche Reaktion. Alles klar? Im Zweifelsfall mit dem Debugger das Programm durchlaufen. Gib auch mal 1 und 99999 ein. Ein mglicher
Ablauf ist
Willkommen in unserem Restaurant.
Was moechtest du trinken?
1 Wasser
2 Brause
3 Kaffee
Bitte waehle eine Nummer: 2
Gut. Was moechtest du essen?
1 Pommes
2 Schnitzel mit Pommes
3 Pommes mit Schnitzel
4 Schnommes mit Pitzel
Bitte waehle eine Nummer: 0
Haben wir nicht. Ich bringe erst mal die Getraenke.

Zufallszahlen

5.7

In diesem Beispiel stelle ich dir Zufallszahlen vor. Das hat zunchst berhaupt
nichts mit Bedingungen zu tun, aber im nchsten Beispiel kann ich dann vorfhren, wie ein Programm mit if und else auf den Zufall reagieren kann. Zudem
tut es gut, die strenge Logik dieses Kapitels mit dem chaotischen Zufall zu konfrontieren.
Vielleicht denkst du bei dem Wort Zufall als Erstes an Spiele. Jedes Wrfelspiel
verwendet Wrfel als Zufallsgenerator. Du wrfelst und das Ergebnis ist 1, 2,
3, 4, 5 oder 6. Aber welche Zahl gewrfelt wird, ist dem Zufall berlassen. Du
kannst diese Zahl nicht vorhersagen, aber du weit, welche Zahlen mglich sind
und dass bei hufigem Wrfeln jede Zahl ungefhr gleich oft erscheint.
Aber natrlich ist das ganze Leben voller Zuflle. Du hattest sicher schon
Glckstrhnen und Phasen rabenschwarzen Pechs. Nichts ist sicher, und wenn
der Zufall berhand nimmt, herrscht Chaos. Denke nur an den tgliche Wetterbericht. Viele Spiele und Computerspiele leben vom Zufall, darum wollen wir
dieses Thema ausfhrlich diskutieren.

5.7

Zufallszahlen

103

Sandini Bib

Der Computer ist geradezu ein Vorbild an Zuverlssigkeit. Seine ganze Konstruktion ist darauf angelegt, Programme unter allen Umstnden identisch auszufhren. Auch beim abermillionsten Mal muss 1 plus 1 gleich 2 sein. Ab und
zu strzt der Computer unerwartet ab, d.h. ein Programm benimmt sich nicht
wie erwartet. Nur in den seltensten Fllen liegt das daran, dass zuflligerweise
irgendein Elektron in den Schaltkreisen des Computers einen Anfall von Chaos
hatte. Nein, entweder ist der Computer defekt oder es war alles Deine Schuld,
ich meine, die Schuld des Programmierers oder der Programmiererin. Das heit,
es liegt ein subtiler, gut versteckter Programmierfehler vor. Du solltest immer
davon ausgehen, dass der Computer 100000-prozentig logisch vorgeht.
Wie kann dann der Computer Zufallszahlen berechnen? Die kurze Antwort ist:
Er kann es nicht. Aber es gibt simple Rechenvorschriften, mit denen man Pseudozufallszahlen generieren kann, also Folgen von Zahlen, die zufllig aussehen,
es aber gar nicht sind. Trotzdem erfllen diese Zahlen in den allermeisten Fllen ihren Zweck. Solange ein Programm nicht auf sehr spezielle Weise versucht,
Regelmigkeiten zu entdecken, sind diese Zahlen gleichmig verteilt wie bei
einem Wrfel und auch in langen Zahlenfolgen gibt es keine Wiederholungen.
Lange Rede, kurzer Sinn: Obwohl zwar im Prinzip ein Paradox vorliegt, knnen
Computer den Zufall sehr gut simulieren.
In C berechnen wir Zufallszahlen mit der Funktion rand:
Zufall0.c

#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
getchar();
return 0;
}

rand());
rand());
rand());
rand());
rand());

346
130
10982
1090
11656

Der Name der Funktion rand kommt von


Der Prototyp dieser Funktion wird mit

104

Kapitel 5 Bedingungen

random, was hier zufllig heit.

Sandini Bib

#include <stdlib.h>

geladen. stdlib steht fr

standard library (Standardbibliothek).

Lass das Beispiel jetzt bei dir laufen. Welche Zahlen bekommst du? Wenn du
denselben Compiler BCB verwendest wie ich, wirst du rufen: Nein, solch ein
Zufall, ich bekomme genau dieselben Zahlen! Lass das Programm noch mal
laufen und noch mal. Egal, ob du dieselben Zahlen wie ich erhltst oder nicht,
die fnf Zahlen werden sich auch bei dir von Mal zu Mal nicht ndern. Wie kann
das angehen?
Der Grund ist, dass rand Zahlenfolgen berechnet. Jeder Aufruf von rand liefert
die nchste Zahl. Diese Zahlenfolge ist fr einen gegebenen Startwert immer
gleich. Diesen Startwert, der alle anderen Zahlen in der Folge von vornherein
seed) der Zahlenfolge. Die Zufallszahlen
festlegt, nennt man den Samen (
sind nur insofern zufllig, als die Zahlen gut gemischt sind!
Den Samen fr den Zufallszahlengenerator rand kannst du mit der Funktion
srand shen (seed random number generator):
Zufall1.c

#include <stdio.h>
#include <stdlib.h>
int main()
{
srand(2);

// 1 ist der Default

printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
getchar();
return 0;

rand());
rand());
rand());
rand());
rand());

692
32682
21834
23967
22222

berzeuge dich davon, dass srand(1) der Default ist und du dieselben Zahlen
wie ohne srand erhltst und dass srand(1) und srand(2) verschiedene Zufallsfolgen ergeben. Wenn du srand(1) nach einigen rand() noch mal aufrufst,
startet die Reihe wieder von vorne.
5.7

Zufallszahlen

105

Sandini Bib

In der Testphase eines Programmes ist es praktisch, dass man die Zahlenreihen wiederholen kann, indem man denselben Startwert verwendet. Aber drehen
wir uns vielleicht im Kreis? Brauchen wir womglich eine Zufallszahl, um den
Generator so zu initialisieren, dass bei jedem Programmablauf eine andere Zufallsfolge verwendet wird? Nein, wir knnen die Zeitfunktion time wie folgt
verwenden (Prototyp in time.h):
srand(time(0));

Der Aufruf von time(0) liefert die Zeit seit dem 1. Januar 1970 in Sekunden. Wie
seltsam. Aber ntzlich, denn selbst nach einer Sekunde sehen die Zufallszahlen
schon ganz anders aus. Denn eine nderung des Seeds um lediglich 1 reicht
aus, verschiedene Reihen zu erzeugen (die insbesondere nicht blo um eine Zahl
gegeneinander verschoben sind oder so). Ausprobieren!
In der Hilfe von BCB findest du den Hinweis, dass rand() die Werte von 0 bis zu
einer Konstanten namens RAND MAX liefert. Bei mir ist RAND MAX gleich 32767.
Solche groen Zufallszahlen kannst du leicht z. B. mit
rand() % 6

in kleinere verwandeln. Der Rest bei der Division durch 6 ist 0, 1, 2, 3, 4 oder 5.
Also zhlen wir 1 dazu,
1 + rand() % 6

und fertig ist unser Wrfel. Jetzt weit du also, wie man in C wrfelt.
An dieser Stelle kann ich es mir nicht verkneifen, ein wenig Unfug mit Grafik
anzustellen. Was hltst du von folgendem Programm (du bentigst das WinHallo
Beispiel aus Kapitel 4):
Zufall2.c WinHallo.cpp

#include <windows.h>
#include <stdio.h>
void malen(HDC hdc)
{
char text[100];
sprintf(text, "%d", 1 + rand()%6);
TextOut(hdc, 100, 100, text, strlen(text));
}

Lass es laufen. Du bekommst eine einsame, kleine Zufallszahl in einem Fenster.


Bei mir ist es die 5. Jetzt knntest du den Trick mit srand anwenden, damit du
bei jedem Programmstart eine neue Zahl bekommst.
Jetzt halte dich fest. Oder besser, halte die Maus fest. Ich meine, klicke mit der
linken Maustaste auf einen Rand des Fensters, halte die linke Maustaste gedrckt
106

Kapitel 5 Bedingungen

Sandini Bib

und bewege die Maus. Du wirst die Gre des Fensters verndern. Und weil nach
jeder Grennderung das Fenster neu gemalt wird (unter Umstnden musst du
die Maustaste dazu wieder loslassen), wird jedes Mal eine neue Zufallszahl angezeigt. Nanana, so programmiert ein ernsthafter Windowsprogrammierer nicht.
Aber das sind wir ja (zum Glck?) nicht. Wie wre es mit
Zufall3.c WinHallo.cpp

#include <windows.h>
#include <stdio.h>
void malen(HDC hdc)
{
HFONT hfont, hfalt;
char text[100];
hfont = CreateFont(5 + rand()%400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
hfalt = SelectObject(hdc, hfont);
sprintf(text, "%d", 1 + rand()%6);
TextOut(hdc, 70, 20, text, strlen(text));
SelectObject(hdc, hfalt);
DeleteObject(hfont);
}

Hier setzen wir gleich auch noch die Fontgre zufllig, alles andere ist dir schon
aus den Kapiteln 4.6 und 4.10 bekannt. Und wenn wir schon dabei sind, warum
nicht Ellipsen mit einer zuflligen Farbe und zuflligen Koordinaten:

5.7

Zufallszahlen

107

Sandini Bib
Zufall4.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
HBRUSH hbrush, hbalt;
int max = 250;
hbrush = CreateSolidBrush(RGB(rand()%256, rand()%256, rand()%256));
hbalt = SelectObject(hdc, hbrush);
Ellipse(hdc, rand()%max, rand()%max, rand()%max, rand()%max);
SelectObject(hdc, hbalt);
DeleteObject(hbrush);
}

Die Bilder habe ich mit max gleich 500 gemacht, nachdem ich im Editor die 5
Programmzeilen fr die Zufallsellipse mehrmals kopiert hatte (markiere die 5
Zeilen, drcke einmal Strg + C , drcke zehnmal Strg + V ).

5.8

Ein Held des Zufalls

Kennst du so genannte Rollenspiele? Du spielst einen Fantasiehelden, dessen


Fhigkeiten mit dem Wrfel festgelegt werden. Oft bentigt man fr einen bestimmten Beruf wie Dieb, Kmpfer oder Zauberer eine gewisse Mindestpunktzahl in bestimmten Fhigkeiten. Hier ist unser Beispiel:
108

Kapitel 5 Bedingungen

Sandini Bib
ZufallsHeld.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int stae, bewe, inte, ausd, mana;
int ok;
srand(time(0));
stae = 3 + rand()%6
bewe = 3 + rand()%6
inte = 3 + rand()%6
ausd = 3 + rand()%6
mana = 3 + rand()%6

+
+
+
+
+

rand()%6
rand()%6
rand()%6
rand()%6
rand()%6

+
+
+
+
+

rand()%6;
rand()%6;
rand()%6;
rand()%6;
rand()%6;

printf("\nNeuer Held mit %d Punkten: \n",


stae + bewe + inte + ausd + mana);
printf(" %2d Staerke\n",
stae);
printf(" %2d Ausdauer\n",
ausd);
printf(" %2d Beweglichkeit\n", bewe);
printf(" %2d Intelligenz\n",
inte);
printf(" %2d Mana\n",
mana);
ok = 0;
printf("\n> ");
if (bewe >= 14) {
printf(" Dieb");
ok = 1;
}
if (stae >= 13 && ausd >= 10) {
printf(" Kaempfer");
ok = 1;
}
if (inte >= 8 && mana >= 8 &&
printf(" Zauberer");
ok = 1;
}

inte + mana >= 22) {

if (!ok)
printf(" noch mal wuerfeln");
printf("\n");
getchar();
return 0;
}

5.8

Ein Held des Zufalls

109

Sandini Bib

Neuer Held mit 61 Punkten:


13 Staerke
12 Ausdauer
13 Beweglichkeit
8 Intelligenz
15 Mana
>

Zauberer

Fr jede Fhigkeit definieren wir eine Variable, in der wir die Punktezahl speichern wollen. Fr jede Fhigkeit wird mit drei Sechserwrfeln 1 + rand()%6
gewrfelt, also ist das Ergebnis
3 + rand()%6 + rand()%6 + rand()%6

Nach dem Wrfeln geben wir das Ergebnis am Bildschirm aus.


Als Nchstes soll entschieden werden, welche Berufe unserem Helden zur Wahl
stehen. Beachte, dass die drei if-Bedingungen nicht eine dieser Fallunterscheidungen ist, bei denen sich die Mglichkeiten gegenseitig ausschlieen! Unser
Held darf gerne zwei Berufe gleichzeitig lernen oder einen von zwei auswhlen.
Deshalb testen wir nacheinander, ob verschiedene Voraussetzungen fr einen
Beruf erfllt sind, und melden das mit printf. Alle drei Bedingungen werden
getestet und keine, eine, zwei oder alle drei knnen erfllt sein und sich mit
printf melden.
Dabei taucht ein kleines Problem auf, das wir mit der Variable ok lsen. Wie
erzeugen wir eine Meldung, wenn kein Beruf erlaubt ist und noch mal gewrfelt
werden soll? Das soll der Default sein, falls kein Beruf erlaubt ist. Kein Beruf ist
erlaubt, wenn die Bedingung fr Dieb Nicht erfllt ist Und die Bedingung fr
Kmpfer Nicht erfllt ist Und die Bedingung fr Zauberer Nicht erfllt ist. Das
ist eine groe Bedingung, die wir erhalten, wenn wir die einzelnen Bedingungen
kopieren und mit Und und Nicht richtig verknpfen:
if (

!(bewe >= 14)


&& !(stae >= 13 && ausd >= 10)
&& !(inte >= 8 && mana >= 8 && inte + mana >= 22))
printf(" nochmal wuerfeln");

Das ist unschn, weil kompliziert, und wenn du z. B. die Anforderungen an den
Zauberer ndern willst, musst du daran denken, zwei Stellen im Programm zu
ndern. Eine elegantere Lsung ist, die Variabe ok einzufhren. Wie du siehst,
initialisieren wir ok mit 0. Dann folgen die drei Bedingungen fr die verschiedenen Berufe. Falls ein Beruf erlaubt ist, setzen wir ok auf 1. Falls noch ein Beruf
erlaubt ist, schadet es nicht, ok noch mal auf 1 zu setzen. Abschlieend mssen
wir nur noch mit if (!ok) testen, ob das Wrfelergebnis vielleicht nicht ok war,
und knnen dann unsere Defaultmeldung ausgeben.

110

Kapitel 5 Bedingungen

Sandini Bib

5.9

Bisektion

Angenommen, du spielst mit einem Freund oder einer Freundin Zahlenraten.


Der eine denkt sich eine der Zahlen
0, 1, 2, 3, 4, 5, 6, 7.

Der andere darf Fragen stellen wie Ist die Zahl kleiner als 4?, auf die er die
Antwort richtig oder falsch bekommt. Aha, hier wird dir also wieder eine Fallunterscheidung vorgefhrt.
Eine gut Ratestrategie ist, mit jeder Frage die Anzahl der Mglichkeiten zu halbieren. So etwas nennt man Bisektion (Zweiteilung). Das knnte so ablaufen:
1. Frage: Zahl < 4? Richtig! Mglich sind noch 0, 1, 2, 3.
2. Frage: Zahl < 2? Falsch! Mglich sind noch 2, 3.
3. Frage: Zahl < 3? Falsch! Mglich ist nur noch 3.
4. Du hast dir die 3 gedacht. Richtig!

Hier ist ein Programmbeispiel, zur Abwechslung mit Textausgabe in einem Grafikfenster (siehe Kapitel 4). Die Sache ist viel einfacher, als sie aussieht:

5.9

Bisektion

111

Sandini Bib
Bisektion0.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x =
5, dx = 60;
int y = 100, dy = 50;
int zz;
zz = rand()%8;
TextOut(hdc, x, y, "zz < 8 ", 7);
if (zz < 4) {
TextOut(hdc, x+=dx, y=dy, "zz < 4 ", 7);
dy /= 2;
if (zz < 2) {
TextOut(hdc, x+=dx, y=dy, "zz < 2 ", 7);
dy /= 2;
if (zz < 1)
TextOut(hdc, x+=dx, y=dy, "zz == 0", 7);
else
TextOut(hdc, x+=dx, y+=dy, "zz == 1", 7);
}
else {
TextOut(hdc, x+=dx, y+=dy, "zz >= 2", 7);
dy /= 2;
if (zz < 3)
TextOut(hdc, x+=dx, y=dy, "zz == 2", 7);
else
TextOut(hdc, x+=dx, y+=dy, "zz == 3", 7);
}
}
else {
TextOut(hdc, x+=dx, y+=dy, "zz >= 4", 7);
dy /= 2;
if (zz < 6) {
TextOut(hdc, x+=dx, y=dy, "zz < 6 ", 7);
dy /= 2;
if (zz < 5)
TextOut(hdc, x+=dx, y=dy, "zz == 4", 7);
else
TextOut(hdc, x+=dx, y+=dy, "zz == 5", 7);
}
else {
TextOut(hdc, x+=dx, y+=dy, "zz >= 6", 7);
dy /= 2;
if (zz < 7)
TextOut(hdc, x+=dx, y=dy, "zz == 6", 7);
else
TextOut(hdc, x+=dx, y+=dy, "zz == 7", 7);
}
}
}

112

Kapitel 5 Bedingungen

Sandini Bib

Der Computer besorgt sich eine Zufallszahl zz kleiner als 8 mit der Funktion
rand und bestimmt diese dann durch eine Reihe von ineinander gesetzten, verschachtelten Fallunterscheidungen. Mit TextOut geben wir nach jedem Test der
Variable zz den Stand der Dinge aus.
Konzentriere dich als Erstes auf den Fall, dass zz gleich 0 ist. Der Ablauf wird
wie folgt sein:
zz < 4 ist wahr, nchster Befehlsblock
zz < 2 ist wahr, nchster Befehlsblock
zz < 1 ist wahr, nchster Befehlsblock

also muss die Zahl gleich 0 sein.

Alle anderen Befehlsblcke werden bersprungen und die Ausgabe ist fertig. Im
Fall zz gleich 3:
zz < 4 ist wahr, nchster Befehlsblock
zz < 2 ist falsch, springe zum Befehlsblock des else
zz < 3 ist falsch, springe zum Befehlsblock des else

also muss die Zahl gleich 3 sein.

Was soll die Rechnerei mit den Koordinaten bewirken? Wir wollen die Entscheidungsfindung wie einen Weg mit Abzweigungen von links nach rechts aufmalen.
Das sieht dann so aus:

Die erste Textausgabe geschieht fr x = 5, bei jeder weiteren Textausgabe erhhen wir x um dx = 60 mit x += dx. Dadurch wird der neue Text immer weiter
nach rechts gerckt.
5.9

Bisektion

113

Sandini Bib

Der Witz ist, dass wir gleichzeitig die Textposition in der y-Richtung nach oben
(y = dy) oder unten (y += dy) verschieben, je nachdem ob die Aussage wahr
oder falsch war. Und bei jedem Schritt nach rechts halbieren wir die Schrittgre
dy fr die y-Richtung. Wie du an den Beispielfensterchen siehst, findet dadurch
jede Zahl ihren eigenen Platz am rechten Fensterrand!
Wenn du jetzt mit der Maus den Fensterrand verschiebst (bei gedrckter linker Maustaste), wird das Fenster immer wieder mit einer neuen Zufallszahl neu
gezeichnet, und du bekommst den gesamten Entscheidungsbaum zu sehen.

5.10
Statt Schritt fr Schritt blindlings geradeaus durchs Programm zu stapfen, wissen wir jetzt, wie C je nach den gegebenen Bedingungen einen Programmteil
ausfhrt oder berspringt. Wie schn, dass wir das jetzt knnen. Was offensichtlich noch fehlt, ist die Mglichkeit, im Programm nicht nur vorwrts, sondern
auch rckwrts zu springen. So knnten wir Programmteile wiederholen. Wiederholungen, auch Schleifen genannt, sind das Thema von Kapitel 6. Zur Zusammenfassung:
Zahlen kann man mit == != > < >= <= vergleichen.
Null wird auch als falsch, Nicht-Null als wahr interpretiert.
Wahre und falsche Aussagen kann man mit dem Verneinungsoperator !
(Nicht) umkehren und mit || (Oder) und && (Und) verknpfen.
Der Befehl in
if (i < 10)

Befehl

wird nur ausgefhrt, wenn i kleiner als 10 ist, ansonsten wird er bersprungen. In
if (i < 10)

Befehl1
else

Befehl2

wird genau einer der beiden Befehle ausgefhrt, und zwar der erste, wenn i
kleiner als 10 ist, und der zweite, wenn i nicht kleiner als 10 ist.
Pseudozufallszahlen erhltst du mit der Funktion rand. Mit srand kannst
du den Startwert ndern. Diese Zufallszahlen durchlaufen bei gleichem Startwert immer dieselben Werte. Aber mit srand(time(0)) kannst du den Startwert abhngig von der Uhrzeit ndern.

114

Kapitel 5 Bedingungen

Sandini Bib

5.11
1. Schreibe ein Testprogramm fr ||, &&, und !. Teste verschiedene Verknp-

fungen.
2. Wahr oder falsch? Erst denken, dann mit deinem Testprogramm testen. Nimm
an, dass i = 1; j = 10; k = 0; ausgefhrt wurde:
i < 0
(i > 0) && (j < 1)
i k > 0
i k > 0 && j i <= 0
i >= 0 && j >= 0 && k >= 0
i && k
i || k
i && !k
i || j || k
!(i || j || k)
i && !i
i || !i

3. Wie erzeugst du einen Entweder-Oder-Operator? Es soll entweder Aussage1

oder Aussage2 wahr sein, aber nicht beide zugleich. Betrachte


(a || b) && !(a && b)
(a || b) && (!a || !b)
a && !b || !a && b

Von der zweiten zur dritten Zeile kommst du, wenn du die Klammern mit &&
ausmultiplizierst.
4. Schreibe ein Programm, das dir die Entscheidung, ins Kino zu gehen, ab-

nimmt, siehe Kapitel 5.2. Dazu soll das Programm drei Fragen stellen, hast
du Zeit, hast du Geld, darf ich dich einladen. Antworten darf man mit 0 oder
1 (oder einer Zahl ungleich Null). Schau dir die dritte Bedingung genau an.
Ist die Umgangssprache mit und/oder eindeutig?
5. Programmiere ein kleines Grafikprogramm, das eine knallige Zufallsfarbe fr
den Hintergrund auswhlt und dich mit einem Zufallstext begrt ("Hallo",
oder "Schon wieder du", oder "Mein Chip schlaegt nur fuer dich",

oder . . .).

5.11

115

Sandini Bib

Sandini Bib

6
Schleifen
6.0
6.1
6.2
6.3
6.4

Zhlerschleife mit while


Schleife im Geschwindigkeitsrausch
Endlosschleifen
Schleifenkontrolle mit break und continue
Schleifen mit while, for und do

119
120
121
121
124

6.5

Summe ganzer Zahlen

128

6.6

Schleifen und Felder

128

6.7

#define und Felder

130

6.8

Eingabeschleife

131

6.9

Das doppelte Schleifchen

134

6.10

Primzahlen

135

6.11

Zeit in Sekunden

139

6.12

Bisektion mit Schleife

141

Sandini Bib

6.13

Grafik die hundertste Wiederholung

146

6.14

Linien aus Pixeln

148

6.15

Schachbrett

150

6.16

Histogramme

152

6.17

157

6.18

158

In allen Beispielen, die wir bisher betrachtet haben, wurden die Anweisungen
Zeile fr Zeile in der Reihenfolge ausgefhrt, in der sie im Programm stehen.
Zwar haben wir in Kapitel 5 gelernt, wie man Befehle kontrolliert berspringen
kann, aber der Programmablauf ging unerbittlich von oben nach unten. Wenn
ein Befehl einmal ausgefhrt war, kam er nie wieder an die Reihe. Jetzt geht
solange,
es rund. In diesem Kapitel besprechen wir while, do und for (
mache, fr), mit denen du Befehle beliebig oft wiederholen kannst. Wenn ein
Programm mehrmals dieselben Befehle wiederholt, spricht man davon, dass es
loop) ausfhrt.
eine Schleife (
Man knnte sagen, Computer wurden erfunden, damit man Schleifen programmieren kann. Insbesondere die Wiederholung von Befehlen mit hoher Geschwindigkeit macht Computer so ntzlich. Ein Algorithmus ist eine Methode zur
schrittweisen Lsung einer Aufgabe und Schleifen spielen dabei eine wichtige
Rolle. Algorithmen knnen formuliert werden, ohne dass auf eine Programmiersprache Bezug genommen wird. Andererseits kann man Algorithmen als
Methoden bezeichnen, die man niemals ohne Computer verwenden wrde, weil
die vielen Wiederholungen zu viel Arbeit machen!

118

Kapitel 6 Schleifen

Sandini Bib

6.0 Zhlerschleife mit while


Hier ist unser erstes Beispiel:
While0.c

#include <stdio.h>
int main()
{
int i;
i = 10;
while (i > 0) {
printf("%d\n", i);
i = i 1;
}
getchar();
return 0;
}

Das kannst du wie folgt lesen: Setze i gleich 10. Solange (while) i grer als 0
ist, drucke den Wert von i, dann verkleinere i um 1. Wenn i nicht mehr grer
als 0 ist, fahre mit dem ersten Befehl nach dem Befehlsblock fort.
Also erhltst du einen
tenstart:

Count-down (Zhl-runter) wie bei einem Rake-

10
9
8
7
6
5
4
3
2
1

Unbedingt ausprobieren!
Die Konstruktion einer Schleife mit while erinnert an das if:
while (Ausdruck) {

Befehle
}

und fr einen Befehl geht auch


while (Ausdruck)

Befehl

6.0 Zhlerschleife mit while

119

Sandini Bib

Ausdruck wird genau wie beim if als Aussage gelesen, die entweder wahr oder
falsch ist. Alles, was wir in Kapitel 5 ber die Konstruktion von wahren und
falschen Aussagen und erfllten und unerfllten Bedingungen gelernt haben,
gilt auch hier.
Der entscheidende Unterschied ist aber, dass nach einem if der Befehl einmal oder keinmal ausgefhrt wird und nach einem while
wird der Befehl solange ausgefhrt, wie der Ausdruck wahr ergibt:
keinmal, einmal oder viele Male!
Einmal? Setze in userem Beispiel i = 1. Keinmal? Setze i = 0 oder auf irgendeine Zahl kleiner 0.

6.1 Schleife im Geschwindigkeitsrausch


Die Zahlen von 10 bis 1 werden so schnell nacheinander ausgegeben, dass es
aussieht, als ob sie von Anfang an im Textfenster stehen. Starte den Debugger
mit F8 und drcke wiederholt F8 . So kannst du genau verfolgen, wie die Schleife
abluft.
Gib die Variable i mit Strg + F5 oder Start, Ausdruck hinzufgen als Ausdruck ein, der berwacht werden soll. Jetzt kannst du sehr schn sehen, wie die
Variable i erst ausgegeben und dann heruntergezhlt wird. Du siehst auch, wie
bei i gleich 0 der Befehlsblock bersprungen wird und so die Schleife verlassen
wird.
Versuche es doch mal mit i = 100000; ohne Debugger ( F9 ). Die Zahlen sollten
nach oben aus dem Fenster flitzen. Experimentiere mit dem Startwert, bis der
Computer viele Sekunden hart arbeiten muss.
Und was fr eine Ironie des Schicksals. Das Programm zhlt womglich einen
Count-down bei einer Million angefangen in Richtung 0 doch der Zhler bleibt
bei 1 stehen, die Rakete hebt nicht ab. Das kannst du leicht korrigieren, indem
du die Schleifenbedingung in
while (i >= 0)

umnderst.
Womglich hast du es bertrieben und es sieht so aus, als wolle das Programm
nun stundenlang zhlen? Normalerweise msste es dir gelingen, mit der Tastenkombination Strg + C das Programm zu beenden. Notfalls kannst du ein Programm auch beenden, indem du das Fenster mit  rechts oben schliet.
Vermutlich ist dir schon aufgefallen, dass es Knpfe unterhalb der BCB-Menleiste gibt, die wie bei einem CD-Spieler aussehen. Der grne Pfeil ist gleichbedeutend mit Start ( F9 ) und gleich rechts davon ist die Pausentaste. Whrend
die Zahlen flitzen, kannst du das Programm mit der Pausentaste anhalten. Das
120

Kapitel 6 Schleifen

Sandini Bib

CPU-Fenster, welches dann erscheint, hilft uns nicht viel, aber jetzt kannst du
die Variable i untersuchen.

6.2 Endlosschleifen
Der springende Punkt an jeder Schleife ist, dass die Schleifenbedingung irgendwann nicht mehr erfllt ist. Sonst erhltst du eine Endlosschleife. Versuche einmal
While1.c

#include <stdio.h>
int main()
{
int i;
i = 10;
while (i > 0) {
printf("%d\n", i);
}
getchar();
return 0;
}

Hier ndert sich der Wert von i nie, die Bedingung ist immer erfllt, das Programm luft und luft und luft. Eine endlose Pause kann das Programm auch
mit
while (1);

(eine Befehlszeile mit Strichpunkt) einlegen! Wie gesagt, mit


kommst du dieser Falle.

Strg + C

ent-

6.3 Schleifenkontrolle mit break und continue


Oft ist es ntzlich, aus einer Schleife unabhngig von der Schleifenbedingung
ausbrechen zu knnen. Dies kann mit dem break-Befehl geschehen:

6.2

Endlosschleifen

121

Sandini Bib
While2.c

#include <stdio.h>
int main()
{
int i;
i = 10;
while (1) {
if (i < 0)
break;
printf("%d\n", i);
i = i 1;
}
getchar();
return 0;
}

Dies ist quivalent zu unserem Beispiel, in dem bis 0 gezhlt wurde. In der
Schleife wird als Erstes getestet, ob i < 0 ist. Falls ja, wird die break-Anweisung
ausgefhrt und die Programmausfhrung springt zum ersten Befehl nach dem
while-Block. break kann an beliebiger Stelle in der Schleife stehen und darf
auch mehrmals auftreten.
Statt die Schleife mit break zu beenden, gibt es auch die Mglichkeit, mit
continue zum Anfang der Schleife zurckzukehren, siehe
While3.c

#include <stdio.h>
int main()
{
int i;
i = 100;
while (i >= 0) {
if (i % 10 != 0) {
i = i 1;
continue;
}
printf("%d\n", i);
i = i 1;
}
getchar();
return 0;
}

Als Erstes wird getestet, ob i durch 10 teilbar ist. Wenn das nicht der Fall ist (also
wenn der Divisionsrest, den wir mit % erhalten, nicht gleich 0 ist), wird der Block
122

Kapitel 6 Schleifen

Sandini Bib

hinter dem if ausgefhrt. Wrden wir in diesem Block nicht i = i 1 ausfhren, wrde sich das Programm bei i gleich 99 in einer Endlosschleife verlaufen! (Versuch das mal.) Durch das continue; wird in die Zeile mit dem while
gesprungen. Weil wir wie zuvor rckwrts zhlen, aber nur jedes zehnte Mal
printf aufrufen, erhalten wir
100
90
80
70
60
50
40
30
20
10
0

Das knnten wir auch wie folgt programmieren:


While4.c

#include <stdio.h>
int main()
{
int i;
i = 100;
while (i >= 0) {
if (i % 10 == 0)
printf("%d\n", i);
i;
}
getchar();
return 0;
}

Zur Abwechslung habe ich hier den Operator verwendet, um den Inhalt der
Zhlervariablen um 1 zu erniedrigen. Den Effekt von continue (und brigens
auch den von break) kann man normalerweise auch anders erhalten. Verwende
die Version, die dir klarer vorkommt.

6.3 Schleifenkontrolle mit break und continue

123

Sandini Bib

Unser Beispiel lsst sich noch weiter vereinfachen:


While5.c

#include <stdio.h>
int main()
{
int i;
i = 100;
while (i >= 0) {
printf("%d\n", i);
i = 10;
}
getchar();
return 0;
}

6.4 Schleifen mit while, for und do


Das letzte Beispiel knnen wir auch mit Hilfe der Anweisung for schreiben:
For0.c

#include <stdio.h>
int main()
{
int i;
for (i = 100; i >= 0; i = 10)
printf("%d\n", i);
getchar();
return 0;
}

In der Tat ist


for (Ausdruck1; Ausdruck2; Ausdruck3)

Befehl

gleichbedeutend mit
Ausdruck1;
while (Ausdruck2) {
Befehl
Ausdruck3;
}

124

Kapitel 6 Schleifen

Sandini Bib

Schau dir unser Beispiel fr for genau an. Fr Zhlerschleifen kannst du dir die
for-Schleife auch so merken:
for (Initialisierung; Bedingung; Weiterzhlen)

Befehl

was du wie folgt in eine while-Schleife zerlegen kannst:


Initialisierung;
while (Bedingung) {
Befehl
Weiterzhlen;
}

Besonders in Zhlschleifen ist for praktisch, weil alle Schleifenanweisungen


bersichtlich in der ersten Zeile versammelt sind, wie z. B. auch in
For1.c

#include <stdio.h>
int main()
{
int i, n;
printf("Zaehle bis:
scanf("%d", &n);

");

for (i = 1; i <= n; i++)


printf("%d\n", i);
getchar(); getchar(); return 0;
}

Manchmal mchte man zwei Zhlervariablen haben, vielleicht wie in

6.4 Schleifen mit while, for und do

125

Sandini Bib
For2.c

#include <stdio.h>
int main()
{
int i, j, n;
printf("Zaehle bis:
scanf("%d", &n);

");

j = 2;
for (i = 1; i <= n; i++) {
printf("%d %d\n", i, j);
j += 2;
}
getchar(); getchar(); return 0;
}

Zaehle bis:
1 2
2 4
3 6
4 8
5 10
6 12
7 14
8 16
9 18
10 20

10

Natrlich knntest du auch in der Schleife j = 2*i schreiben oder gleich 2*i
als Argument in printf einsetzen. Um die zwei Zahlen ordentlich in Spalten zu
stellen, kannst du
printf("%10d %10d\n", i, j);

verwenden. Dies reserviert 10 Zeichen Platz fr die Ausgabe von i und j.


Die Anweisungen fr j kannst du auch in die Zeile mit for hineinschreiben:

126

Kapitel 6 Schleifen

Sandini Bib
For3.c

#include <stdio.h>
int main()
{
int i, j, n;
printf("Zaehle bis:
scanf("%d", &n);

");

for (i = 1, j = 2; i <= n; i++, j += 2)


printf("%d %d\n", i, j);
getchar(); getchar(); return 0;
}

Die Regel ist, dass einfache Anweisungen wie diese durch Komma getrennt zwischen die Strichpunkte geschrieben werden drfen. Diese Befehlslisten mit Kommas sind auch an anderen Stellen erlaubt. Weil darunter aber die bersichtlichkeit leiden kann, empfehle ich dir, das hchstens bei for-Schleifen zu nutzen.
brigens kann man die Anweisungen auch teilweise oder ganz bei for weglassen, z. B.
for (; i < 10; i++)
for (; i < 10;)

Man schreibt nur die Anweisungen, die man braucht. for (;;) ergibt eine Endlosschleife.
Eine weitere Mglichkeit fr Schleifen ist mit do gegeben. Bei while und for
wird erst getestet, und nur wenn die Bedingung erfllt ist, wird der Schleifenblock durchlaufen. Mit
do {

Befehle
} while (Ausdruck);

wird der Schleifenblock garantiert einmal ausgefhrt, bevor die Bedingung getestet wird.
Bleibt nur noch anzumerken, dass break und continue nicht nur fr while,
sondern genauso fr for und do funktionieren. Weil Schleifen mit while, for
und do derart wichtig, ntzlich und unterhaltsam sind, folgen viele Beispiele.

6.4 Schleifen mit while, for und do

127

Sandini Bib

6.5

Summe ganzer Zahlen

Hier ist ein Programm, das alle ganzen Zahlen von 1 bis 10 aufaddiert:
For4.c

#include <stdio.h>
int main()
{
int i, n, summe;
n = 10;
summe = 0;
for (i = 1; i <= n; i++)
summe = summe + i;
printf("Die Summe aller ganzen Zahlen von 1 bis %d ist %d\n", n, summe);
getchar();
return 0;
}

Die Summe aller ganzen Zahlen von 1 bis 10 ist 55

Beachte, dass wir erst summe auf 0 setzen mssen, bevor wir mit dem Dazuzhlen
anfangen knnen. Die Summation kann auch mit
summe += i;

durchgefhrt werden.

6.6

Schleifen und Felder

In Kapitel 3.0 haben wir ganzzahlige Felder kennen gelernt. Die Elemente der
Felder haben wir eins nach dem anderen gesetzt, z. B. a[0] = 0, a[1] = 2,
a[2] = 4 und so weiter. Klarer Fall, dieses und so weiter verlangt nach einer
Schleife:

128

Kapitel 6 Schleifen

Sandini Bib
For5.c

#include <stdio.h>
int main()
{
int i;
int a[10];
int summe, min, max;
for (i = 0; i < 10; i++)
a[i] = 1 + rand()%6;
summe = 0;
for (i = 0; i < 10; i++)
summe += a[i];
min = max = a[0];
for (i = 1; i < 10; i++) {
if (a[i] < min) min = a[i];
if (a[i] > max) max = a[i];
}
printf("\n %dmal gewuerfelt, min = %d, max = %d, summe = %d\n",
10, min, max, summe);
getchar();
return 0;
}

10mal gewuerfelt, min = 1, max = 6, summe = 29

Mit int a[10]; definieren wir ein Feld aus zehn ganzen Zahlen, auf die wir mit
Indizes 0, 1, 2, 3, 4, 5, 6, 7, 8 und 9 zugreifen knnen. Mit
for (i = 0; i < 10; i++)
a[i] = 1 + rand()%6;

setzen wir die zehn Elemente des Feldes a auf eine Zufallszahl von 1 bis 6, als
ob wir gewrfelt htten. Die Schleifenbedingung
i < 10

ist genau, was wir brauchen, um jeden der zehn Indizes von 0 bis 9 zu erzeugen.
Insbesondere wre i <= 10 falsch, denn dann wrden wir um genau ein Element
ber das Ende des Feldes a hinausgehen. Die Schleife in
summe = 0;
for (i = 0; i < 10; i++)
summe += a[i];

durchlaufen wir ebenfalls zehnmal, um alle Zahlen im Feld a aufzuaddieren. In


der letzten Schleife
6.6

Schleifen und Felder

129

Sandini Bib

min = max = a[0];


for (i = 1; i < 10; i++) {
if (a[i] < min) min = a[i];
if (a[i] > max) max = a[i];
}

suchen wir nach der kleinsten und der grten gewrfelten Zahl. Dazu verwenden wir zwei Variablen, min fr das Minimum und max fr das Maximum. Jede
der Zahlen a[i] ist ein Kandidat, also knnen wir beide Variablen zunchst gleich
a[0] setzen. Dann durchsuchen wir das ganze Feld von Index 1 bis 9 nach Zahlen,
die
das Minimum unterbieten, a[i] < min, oder
das Maximum berbieten, a[i] > max.

Falls wir eine solche Zahl gefunden haben, merken wir uns ihren Wert als die
bisher kleinste beziehungsweise grte Zahl.

6.7

#define und Felder

Eigentlich ist es ziemlich unpraktisch, dass wir die Zahl 10 im letzten Beispiel an
mehreren Stellen im Programm stehen haben. Das ist lstig, wenn du ihren Wert
ndern mchtest. Eigentlich ist das ein Fall fr eine Variable, z. B. int n = 10;,
aber hier macht uns die Definition des Feldes int a[10]; einen Strich durch die
Rechnung. Wie in 3.0 schon erwhnt, darf in dieser Definition nur eine Konstante
stehen!
Es gibt verschiedene Auswege. In Kapitel 10.1 besprechen wir, wie man in C
Felder definiert, deren Gre durch eine Variable gegeben ist. Wenn du auf einfachere Weise eine Variable n in den Schleifen verwenden willst, knntest du das
Feld ausreichend gro machen, sagen wir int a[100000], und aufpassen, dass
n niemals grer als diese 100 000 wird. Auerdem kann dir der Preprocessor
mit der Anweisung #define helfen:

130

Kapitel 6 Schleifen

Sandini Bib
For6.c

#include <stdio.h>
#define N 1000
int main()
{
int i;
int a[N];
int summe, min, max;
for (i = 0; i < N; i++)
a[i] = 1 + rand()%6;
summe = 0;
for (i = 0; i < N; i++)
summe += a[i];
min = max = a[0];
for (i = 1; i < N; i++) {
if (a[i] < min) min = a[i];
if (a[i] > max) max = a[i];
}
printf("\n %dmal gewuerfelt, min = %d, max = %d, summe = %d\n",
N, min, max, summe);
getchar();
return 0;
}

10mal gewuerfelt, min = 1, max = 6, summe = 29

In der Zeile
#define N 1000

definieren wir eine so genannte symbolische Konstante. Wichtig: Solche Preprocessoranweisungen werden nicht mit einem Strichpunkt beendet. Bevor der
Compiler den Programmtext zu sehen bekommt, geht der Preprocessor an die
Arbeit und ersetzt im Programmtext berall dort, wo N wie eine Variable verwendet wird, dieses N durch die Zeichen 1000. Der Compiler bekommt also statt
N die Konstante 1000 zu sehen. Deshalb ist int a[N]; in diesem Fall erlaubt.
Es ist blich, symbolische Konstanten mit Grobuchstaben zu schreiben. Mehr
dazu in Anhang A.1.

6.8

Eingabeschleife

Eine sehr hufige Anwendung von Schleifen ist die Eingabeschleife. Das Programm wartet auf eine Eingabe (Text oder auch einen Mausklick). Wenn eine
6.8

Eingabeschleife

131

Sandini Bib

Eingabe erfolgt ist, wird diese verarbeitet. Nach getaner Arbeit kehrt das Programm zum Beginn der Schleife zurck und wartet auf die nchste Eingabe. Das
kann so aussehen:
Zoo.c

#include <stdio.h>
int main()
{
int i;
// begruesst wird nur einmal
printf("\nWillkommen im Zoo!\n\n");
// Eingabeschleife, wird mit break verlassen
while (1) {
// Eingabemenue
printf("Was moechtest du sehen?\n");
printf(" 1: Affe\n");
printf(" 2: Affenfloh\n");
printf(" 0: keine Lust mehr\n");
printf("\nGib die Nummer ein: ");
scanf("%d", &i);
// Bedingung fuer den Ausstieg
if (i == 0)
break;
// die Tiere
else if (i == 1) {
printf("\n\n");
printf("
xxx
\n");
printf("
doxob
b \n");
printf("
xxx
b \n");
printf("
xxxxx b \n");
printf("
xxxxx xx b \n");
printf(" x xxxx
\n");
printf(" x xx xx
\n");
printf("\n\n");
}
else if (i == 2) {
printf("\n\n");
printf("
. \n");
printf("\n\n");
}
// der Default
else
printf("Wie bitte? Dieses Tier kenne ich nicht.\n\n");
}
// fertig
return 0;
}

132

Kapitel 6 Schleifen

Sandini Bib

Willkommen im Zoo!
Was moechtest du sehen?
1: Affe
2: Affenfloh
0: keine Lust mehr
Gib die Nummer ein:

xxx
doxob
b
xxx
b
xxxxx b
xxxxx xx b
x xxxx
x xx xx

Was moechtest du sehen?


1: Affe
2: Affenfloh
0: keine Lust mehr
Gib die Nummer ein:

Das Willkommen steht auerhalb der Schleife, damit es nur einmal zu Beginn
des Programms angezeigt wird. Die Schleifenbedingung ist immer erfllt,
while (1)

aber die Schleife kann durch eine bestimmte Eingabe verlassen werden. Die Fallunterscheidung mit if und else kennt vier Flle: 0 fr Ausstieg mit break,
1 oder 2 fr die Anzeige eines Tieres, und alle anderen Eingaben ergeben eine
entsprechende Meldung als Default.
Wenn es dir nicht gefllt, dass das Textfenster bei 0 einfach zuklappt, kannst du
ja noch ein Auf Wiedersehen! und zwei getchar vor dem return, aber nach
dem Ende des Schleifenblocks einfgen.

6.8

Eingabeschleife

133

Sandini Bib

6.9

Das doppelte Schleifchen

In den Beispielen fr Zhlerschleifen haben wir die Zahlen ordentlich eine unter
der anderen ausgegeben. Nebeneinander geht natrlich auch, wir mssen nur
das Neuezeilezeichen weglassen. Eine Tabelle mit Reihen und Spalten erhalten
wir mit
ForFor0.c

#include <stdio.h>
int main()
{
int i, j;
for (j = 0; j < 10; j++) {
for (i = 0; i < 10; i++) {
printf(" %d*%d", j, i);
}
printf("\n");
}
getchar();
return 0;
}

0*0
1*0
2*0
3*0
4*0
5*0
6*0
7*0
8*0
9*0

0*1
1*1
2*1
3*1
4*1
5*1
6*1
7*1
8*1
9*1

0*2
1*2
2*2
3*2
4*2
5*2
6*2
7*2
8*2
9*2

0*3
1*3
2*3
3*3
4*3
5*3
6*3
7*3
8*3
9*3

0*4
1*4
2*4
3*4
4*4
5*4
6*4
7*4
8*4
9*4

0*5
1*5
2*5
3*5
4*5
5*5
6*5
7*5
8*5
9*5

0*6
1*6
2*6
3*6
4*6
5*6
6*6
7*6
8*6
9*6

0*7
1*7
2*7
3*7
4*7
5*7
6*7
7*7
8*7
9*7

0*8
1*8
2*8
3*8
4*8
5*8
6*8
7*8
8*8
9*8

0*9
1*9
2*9
3*9
4*9
5*9
6*9
7*9
8*9
9*9

Hier steht eine Schleife im Befehlsblock einer anderen. Fr jeden Wert von j
werden alle Werte von i durchlaufen.
Es ist deutlich zu erkennen, dass i die Spalten zhlt und j die Reihen. In jeder
Spalte ist i konstant, in jeder Reihe ist j konstant. Fr jeden Wert von j durchluft i einmal die Zahlen von 0 bis 9. Entscheidend dafr ist, dass die innere
Schleife jedes Mal von neuem mit i = 0 initialisiert wird. Mit for ist es schwer,
diese Initialisierung zu vergessen, bei while kann das schon eher passieren. Was
liefert das folgende doppelte Schleifchen?

134

Kapitel 6 Schleifen

Sandini Bib
ForFor1.c

#include <stdio.h>
int main()
{
int i, j;
for (j = 0; j < 10; j++) {
for (i = 0; i <= j; i++) {
printf(" %d*%d", j, i);
}
printf("\n");
}
getchar();
return 0;
}

Erst denken, dann ausprobieren. Mit F8 und i und j unter Beobachtung wird
es noch klarer, was hier vor sich geht.
Wie verhalten sich break und continue in doppelten Schleifen? Ein break in
der inneren Schleife beendet nur die innere Schleife, nicht aber die uere. Du
kannst mit break die momentane Schleife beenden, nicht aber aus mehreren
verschachtelten Schleifen gleichzeitig ausbrechen. continue bezieht sich ebenfalls nur auf die nchstuere Schleife. Ein Beispiel fr break in einer doppelten
Schleife begegnet uns in Kapitel 6.10.

6.10

Primzahlen

Eine Primzahl ist eine positive ganze Zahl, die nur durch 1 und sich
selbst ohne Rest teilbar ist. Diese Definition enthlt schon alles, was
wir fr einen Algorithmus brauchen. Wir mssen nur alle diese Zahlen durchprobieren. Genauer gesagt, gegeben eine Zahl zahl, probiere alle Zahlen i von 2 bis zahl 1 durch. Wenn sich eine Zahl i findet, fr die bei der
Division kein Rest bleibt, wissen wir, dass es keine Primzahl sein kann, und wir
brauchen die anderen Zahlen nicht mehr zu testen. Wenn sich kein solcher Teiler
findet, handelt es sich bei zahl um eine Primzahl, denn wir haben ja die trivialen
Teiler 1 und zahl von vornherein ausgeschlossen. Gesagt, getan:

6.10

Primzahlen

135

Sandini Bib
Primzahlen0.c

#include <stdio.h>
int main()
{
int i, zahl;
for (zahl = 2; zahl < 500; zahl++) {
for (i = 2; i < zahl; i++)
if (zahl % i == 0)
break;
if (i == zahl)
printf("%4d", zahl);
}
printf("\n");
getchar();
return 0;
}

2
3
5
7 11 13 17
73 79 83 89 97 101 103
179 181 191 193 197 199 211
283 293 307 311 313 317 331
419 421 431 433 439 443 449

19
107
223
337
457

23
109
227
347
461

29
113
229
349
463

31
127
233
353
467

37
131
239
359
479

41
137
241
367
487

43
139
251
373
491

47 53 59 61 67 71
149 151 157 163 167 173
257 263 269 271 277 281
379 383 389 397 401 409
499

Und weil es so schn funktioniert, testen wir gleich alle Zahlen von 2 bis 499. Das
ist die uere Schleife in zahl. Die innere Schleife kann auf zweierlei Weise zu
Ende gehen, durch die Schleifenbedingung i < zahl (kein Teiler gefunden) oder
durch ein break wegen zahl % i == 0 (Teiler gefunden). Lass das Programm
Schritt fr Schritt laufen und versuche den nchsten Schritt zu erahnen!
Wie kommt es, dass wir nach Beendigung der Schleife mit i == zahl diese beiden Flle unterscheiden knnen? Weil die erste Zahl i, fr die die Schleifenbedingung falsch wird, zahl ist. Wenn mir bei einer Schleife nicht ganz klar ist,
was passiert, nehme ich mir einfach ein konkretes Beispiel her. Angenommen
zahl ist 11. Kurz vor Schluss testen wir 9 < 11, kein Teiler, dann 10 < 11, kein
Teiler, dann 11 < 11, stopp.
Dies ist eine typische Situation fr for-Schleifen. Nach Beendigung der Schleife
ist die Zhlervariable um 1 grer als beim letzten Durchgang im Schleifenblock.
Die Variable i enthlt nicht den letzten gltigen Wert, sondern den ersten ungltigen. Wie sollte es auch anders sein. Fr 10 wird noch im Block gearbeitet,
dann wird 10 auf 11 erhht, dann fhrt 11 < 11 zum Ende der Schleife, und mit
diesem Wert kommen wir unten an.
Es ist immer angebracht, beide Endpunkte einer Schleife sorgfltig zu prfen.
Und Doppelschleifen mssen auch noch auf wechselseitige Beeinflussung berprft werden. In der Tat, was geschieht fr zahl gleich 2? Die innere Schleife
136

Kapitel 6 Schleifen

Sandini Bib

wird kein einziges Mal durchlaufen, aber weil die Initialisierung von i auf 2
trotzdem stattfindet, wird 2 korrekt als Primzahl ausgegeben. Denke einmal
scharf nach, wie musst du die Schleifen ndern, damit 1 als Primzahl angegeben wird? zahl = 1 in der ueren Schleife?
Primzahlen spielen eine wichtige Rolle in der Verschlsselungstechnik. Dabei
wird ausgenutzt, dass es enorm aufwndig ist, groe Zahlen in ihre Primfaktoren
zu zerlegen. Die Primfaktorenzerlegung einer Zahl ist eindeutig, wenn man von
der Anordnung der Faktoren absieht. Es kann nicht vorkommen, dass z. B. 3 bei
einer Mglichkeit der Zerlegung auftritt und bei einer anderen nicht. (Warum?
Die Antwort erfordert etwas Nachdenken.) Wie dem auch sei, hier ist ein Beispiel
mit drei verschachtelten Schleifen:
die innere Schleife fhrt den Primzahltest durch wie im letzten Beispiel und
teilt zahl durch jeden neu gefundenen Teiler, so dass zahl immer kleiner
wird,
die mittlere Schleife wiederholt die innere Schleife, bis kein nicht trivialer
Teiler mehr mglich ist wegen zahl gleich 1, und
die uere Schleife ist die mittlerweile vertraute Eingabeschleife.

6.10

Primzahlen

137

Sandini Bib
Primzahlen1.c

#include <stdio.h>
int main()
{
int i, zahl;
while (1) {
printf("Gib mir eine Zahl > 1:
scanf("%d", &zahl);
if (zahl <= 1) {
printf("Na denn nicht.\n");
break;
}

");

while (zahl > 1) {


for (i = 2; i <= zahl; i++) {
if (zahl % i == 0) {
printf("%d", i);
zahl = zahl / i;
break;
}
}
if (zahl > 1)
printf(" * ");
}
printf("\n");
}
getchar(); getchar(); return 0;
}

Das Ergebnis sieht dann z. B. so aus:


Gib mir
2
Gib mir
2 * 2 *
Gib mir
17 * 71
Gib mir
1657
Gib mir
Na denn

eine Zahl > 1:

eine Zahl
2 * 2 * 2
eine Zahl
* 1657
eine Zahl

1657

> 1: 256
* 2 * 2 * 2
> 1: 1999999
> 1:

eine Zahl > 1:


nicht.

Ich berlasse es dir, herauszufinden, wie die Schleifen im Detail funktionieren.


Was passiert fr die Eingabe 2 oder 11 oder 10? Per Hand durchrechnen und
dann auch im Debugger ausprobieren! Auch die Ausgabe des * ist nicht vllig
trivial. Wir knnten zwar mit jedem gefundenen Teiler gleich ein * ausgeben,
138

Kapitel 6 Schleifen

Sandini Bib

htten am Ende dann aber ein * zu viel! Schleifenbinden will gelernt sein, aber
keine Sorge, dass war beileibe nicht die letzte Schleife, die uns begegnen wird.

6.11

Zeit in Sekunden

Nachdem das letzte Beispiel dir vielleicht recht schwierig vorkam, folgt ein kleines Beispiel zur Zeitausgabe. Wir besprechen die Funktionen time und clock,
siehe auch 8.7. Hier ist unser erstes Beispiel:
Zeit0.c

#include <stdio.h>
#include <time.h>
int main()
{
while (1)
printf("%d\n", time(0));
return 0;
}

Lass das Programm laufen und beende die Endlosschleife mit


ein Schnappschuss von meinem Bildschirm:

Strg + C .

Hier ist

977133334
977133334
977133334
977133334
977133334
977133334
977133334
977133335
977133335
977133335
977133335
977133335

Die Zahlen sausen nach oben, nur ab und zu ndern sich die letzten Ziffern
und die Zahl wird um eins grer. Der Funktionsaufruf time(0) liefert die Anzahl der Sekunden seit dem 1.1.1970, 0 Uhr, 0 Minuten, 0 Sekunden, mittlere
Greenwich-Zeit.
Klarer Fall, wir beobachten, wie der Computer die Sekunden zhlt. Wenn du wolltest, knntest du jetzt auf die Sekunde genau ausrechnen, wann ich die Textausgabe fr das Buch produziert habe. (Beziehungsweise, was die Uhr in meinem
Computer fr die momentane Zeit hielt.) Dann habe ich mich zurckgelehnt,
zum Fenster rausgeguckt, nachgedacht und etwas spter das Programm fr die
folgende Ausgabe laufen lassen:
6.11

Zeit in Sekunden

139

Sandini Bib

977133959
977133960
977133961
977133962
977133963
977133964
977133965
977133966

Wie habe ich es angestellt, dass das Programm mit der Ausgabe wartet, bis der
Sekundenzhler auf die nchste Zahl gesprungen ist? Hier ist der Code:
Zeit1.c

#include <stdio.h>
#include <time.h>
int main()
{
int t = 0;
while (1) {
if (t != time(0)) {
t = time(0);
printf("%d\n", t);
}
}
return 0;
}

Erst wenn t != time(0) ist, also erst wenn die Variable t eine andere Zeit gespeichert hat, als time(0) liefert, wird die neue Zeit in t gespeichert und ausgegeben. Um die Sekunden seit Programmstart anzuzeigen, knnen wir die Startzeit speichern und von der momentanen Sekundenzahl abziehen:

140

Kapitel 6 Schleifen

Sandini Bib
Zeit2.c

#include <stdio.h>
#include <time.h>
int main()
{
int tstart = time(0);
int t = 0;
while (1) {
if (t != time(0)) {
t = time(0);
printf("%d\n", t tstart);
}
}
return 0;
}

Eine weitere ntzliche Funktion ist clock. Der Funktionsaufruf clock() liefert
die seit Programmstart verbrauchte CPU-Zeit. Das ist die Zeit, die der Mikroprozessor zur Ausfhrung dieses Programms gebraucht hat. Ignoriert wird die Zeit,
die andere Programme, die vielleicht gleichzeitig liefen, bentigt haben. Um die
Zeit in Sekunden zu erhalten, muss der Wert von clock durch CLK_TCK geteilt
werden, z. B.
t = clock() / CLK_TCK;

Die symbolische Konstante CLK_TCK wird in time.h definiert. Bei mir steht in
CBuilder\Include\Time.h die Zeile
#define CLK_TCK

1000.0

clock ticks per second, also Uhrsignale pro Sekunde.


Der Name bedeutet
Obwohl in BCB die Zahl CLK_TCK gleich 1000 ist, bedeutet das nicht, dass
clock die Zeit auf eine tausendstel Sekunde (gleich eine Millisekunde) genau
zhlt. Der Hardwaretimer, auf dem diese Zeitmessung beruht, tickt gewhnlich
nur 18.2-mal pro Sekunde. Fr Windowsanwendungen gibt es die Funktion
GetTickCount, mit der man die Anzahl der Millisekunden seit dem Start von
Windows abfragen kann.

6.12

Bisektion mit Schleife

Lass uns Zahlenraten spielen, diesmal mit einer Eingabeschleife. Als Erstes darfst
du eine Zahl raten, die sich der Computer ausgedacht hat, spter darf der Computer raten. Zur Abwechslung habe ich die Erklrung des Programms in die Kommentare verlegt:
6.12

Bisektion mit Schleife

141

Sandini Bib
Zahlenraten0.c

/* Zahlenraten
Demonstriert typische Fallunterscheidung mit
ifelse und eine whileSchleife
BB 18.12.00
*/
/* fuer den Preprocessor */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* hier geht es los */
int main()
{
int klein = 0;
int gross = 100;
int zahl;
int meinezahl;

//
//
//
//

untere Schranke
obere Schranke
die vermutete Zahl
die zu ratende Zahl

/* Zahl zufaellig auswaehlen */


srand(time(0));
meinezahl = 1 + rand()%99;
// meinezahl = 1, 2, ..., 99
/* lass den Nutzer wissen, worum es geht */
printf("Hallo, ich habe mir eine ganze Zahl > %d und < %d ausgedacht.\n",
klein, gross);
/* die Rateschleife, wird mit break verlassen */
while (1) {
/* lass den Nutzer eine Zahl eingeben */
printf("Rate mal: ");
scanf("%d", &zahl);
/* Eingabe auswerten */
if (zahl == meinezahl)
break;
else if (zahl < meinezahl)
printf("Zu klein! ");
else if (zahl > meinezahl)
printf("Zu gross! ");
else
printf("Unmoeglich.");

// Zahl geraten, verlasse whileSchleife

// diese Zeile wird nie erreicht

}
/* die Zahl wurde geraten */
printf("Richtig!\n");
getchar(); getchar(); return 0;
}

142

Kapitel 6 Schleifen

Sandini Bib

Das Programm enthlt eine Endlosschleife zur Wiederholung des Spieles, die bei
einer bestimmten Eingabe mit break verlassen wird. Der Computer denkt sich
eine Zahl, der Spieler rt und wir entscheiden mit if-else, ob der Computer
mit zu gro, zu klein und so weiter antwortet. Ausprobieren:
Hallo, ich
Rate mal:
Zu klein!
Zu klein!
Zu gross!
Zu gross!
Zu gross!
Zu gross!
Richtig!

habe
5
Rate
Rate
Rate
Rate
Rate
Rate

mir eine ganze Zahl > 0 und < 100 ausgedacht.


mal:
mal:
mal:
mal:
mal:
mal:

7
50
30
20
10
9

Wenn wir den Spie umdrehen und den Computer eine Zahl des Spielers raten
lassen, mssen wir dem Programm so etwas wie knstliche Intelligenz verleihen.
Die einfachste Lsung wre, den Computer jede Zahl einzeln probieren zu lassen.
Ist es die 1? Grer. Ist es die 2? Grer. Ist es die 3? Grer. Ist es die 4? Grer.
Ist es die 5? Grer. Ist es die 6? Grer. Ist es die 7? Grer. Ist es die 8? Grer.
Ist es die 9? Grer. Ist es die 10? Grer. Ist es die 11? Grer. Ist es die 12?
Grer. Ist es die 13? Grer. Ist es die 14? Grer. Ist es die 15? Grer. Meine
Gte, mir ist schon beim Tippen langweilig geworden. Im Prinzip knntest du
diese Strategie aber leicht mit einer Schleife programmieren.
Wie bist du denn vorgegangen, als der Computer dich mit dem ersten Programm
raten lie? Hast du dich an Kapitel 5.9 und die Bisektion erinnert? Die ntigen
Fallunterscheidungen in Kapitel 5.9 sahen recht verwickelt aus, und fr Zahlen
bis 100 ist es sicher keine gute Idee, den ganzen Entscheidungsbaum auszuschreiben.
Aber weil wir immer wieder dieselbe Art von Frage stellen (Ist die Zahl kleiner oder grer . . .), knnen wir unseren Algorithmus sehr elegant als Schleife
schreiben. Und weil es so schn ist, spielen wir mit Zahlen bis 1000! Also los:

6.12

Bisektion mit Schleife

143

Sandini Bib
Zahlenraten1.c

/* Rate eine Zahl mit Bisektion


Netter Algorithmus
Zeigt typische Fallunterscheidung mit ifelse und zwei whileSchleifen
BB 23.8.00
*/
#include <stdio.h>

/* hier geht es los */


int main()
{
int klein = 0;
int gross = 1000;
int zahl;
int c;

//
//
//
//

untere Schranke
obere Schranke
die vermutete Zahl
die eingetippte Antwort

/* lass den Nutzer wissen, worum es geht */


printf("\nDenk dir eine ganze Zahl > %d und < %d.\n", klein, gross);
printf("Lass mich raten.\n");
printf("Bitte antworte mit\n");
printf(" > wenn deine Zahl groesser ist,\n");
printf(" < wenn deine Zahl kleiner ist,\n");
printf(" = wenn ich deine Zahl geraten habe.\n");
/* die Rateschleife, wird mit break verlassen */
while (1) {
/* die naechste Vermutung soll genau zwischen den Schranken liegen */
zahl = (klein + gross)/2;
/* der Nutzer soll uns sagen, wie wir mit unserer Zahl liegen */
printf("Zwischen %4d und %4d ... ist es %4d? ", klein, gross, zahl);
c = getchar();
while (getchar() != \n); // leere Eingabepuffer
/* Eingabe auswerten */
if (c == =)
break;
else if (c == <)
gross = zahl;
else if (c == >)
klein = zahl;
else
printf("Wie bitte?\n");

// Zahl geraten, verlasse whileSchleife


// kleiner? dann muss die obere Schranke runter
// groesser? dann muss die untere Schranke hoch
// wenn c nicht den Wert = oder < oder > hat

}
/* wir haben die Zahl geraten */
printf("AHA! Deine Zahl ist %d.\n", zahl);
getchar();
return 0;
}

144

Kapitel 6 Schleifen

Sandini Bib

Denk dir eine ganze Zahl > 0 und < 1000.


Lass mich raten.
Bitte antworte mit
> wenn deine Zahl groesser ist,
< wenn deine Zahl kleiner ist,
= wenn ich deine Zahl geraten habe.
Zwischen
0 und 1000 ... ist es 500?
Zwischen
0 und 500 ... ist es 250?
Zwischen 250 und 500 ... ist es 375?
Zwischen 375 und 500 ... ist es 437?
Zwischen 375 und 437 ... ist es 406?
Zwischen 375 und 406 ... ist es 390?
Zwischen 390 und 406 ... ist es 398?
Zwischen 398 und 406 ... ist es 402?
Zwischen 398 und 402 ... ist es 400?
AHA! Deine Zahl ist 400.

<
>
>
<
<
>
>
<
=

Der Computer rt eine Zahl in der Mitte zwischen einer unteren und oberen
Schranke (anfangs 0 und 1000):
zahl = (klein + gross)/2;

Falls klein + gross nicht gerade ist, ergibt die ganzzahlige Division durch 2
den auf die nchste ganze Zahl abgerundeten Mittelwert.
Der Computer bekommt dann von uns gesagt, ob die Zahl in der unteren oder
oberen Hlfte liegt. Dazu lesen wir ein einzelnes Zeichen von der Tastatur. Erinnerst du dich, was getchar genau macht? Jeder Aufruf von getchar liefert
ein Zeichen aus dem Tastaturpuffer, aber wenn der Tastaturpuffer leer ist, wartet
das Programm, bis die Eingabe von neuen Zeichen mit einem Neuezeilezeichen
beendet wurde, und diese Zeichen werden dann wiederum einzeln von getchar
abgeliefert. Im Beispiel verwenden wir
c = getchar();

um das erste Zeichen im Tastaturpuffer in c zu speichern. Dann leeren wir den


Tastaturpuffer, indem wir mit
while (getchar() != \n);

so lange Zeichen lesen, bis das Neuezeilezeichen angibt, dass die Eingabe mit
\n beendet wurde. Auf diese Weise stellen wir sicher, dass das nchste getchar
wieder beim ersten Zeichen der nchsten Zeile anfngt. Ausprobieren, wenn du
mehr als ein Zeichen eingibst, spielt nur das erste eine Rolle. Weil am Ende des
Programms jetzt keine berflssigen \n im Tastaturpuffer stehen, bentigen wir
deshalb auch nur noch ein einziges getchar, um das Textfenster offen zu halten.
Unter der berschrift Eingabe auswerten findest du eine typische Fallunterscheidung. berzeuge dich davon, dass alles ganz logisch ist. Bei = ist die Zahl
6.12

Bisektion mit Schleife

145

Sandini Bib

erraten und die Eingabeschleife wird mit break beendet. Falls ein falsches Zeichen eingeben wurde, sagt der Computer Wie bitte? und wartet auf eine neue
Eingabe. Entscheidend sind die Flle c == < und c == >. Wenn die Zahl
zahl, die der Computer geraten hat, zu klein ist, setzt er die obere Schranke
auf diese Zahl runter. Die tatschliche Zahl liegt dann nach wie vor im Intervall
zwischen klein und gross. Aber dieses Intervall ist um die Hlfte kleiner geworden! Dadurch, dass der Computer immer eine Zahl in der Mitte zwischen
klein und gross rt, teilt er die mglichen Antworten in zwei Hlften, und die
Angabe kleiner oder grer erlaubt es ihm, die Hlfte der Mglichkeiten auszuschlieen.
Dieser Algorithmus ist verblffend effizient. Fr Zahlen zwischen 0 und 1000
werden maximal zehn Schritte bentigt, denn wenn man 1024 zehnmal durch 2
teilt, erhlt man 1. Nach zehn Schritten ist also nur noch eine Mglichkeit brig,
wenn die Zahl nicht schon vorher geraten wurde.
Vielleicht kennst du die Geschichte vom Reiskorn und dem Schachbrett? Ein
Mann, der seinem Knig einen groen Dienst erwiesen hatte, durfte sich als
Lohn dafr etwas wnschen. Und so wnschte er sich auf dem ersten Feld eines Schachbretts ein Korn, auf dem zweiten zwei Krner, dann vier, immer das
Doppelte bis zum 64. Feld. Die Zahl wchst exponentiell mit der Anzahl der
Felder, so viele Krner gibt es auf der ganzen Welt nicht! Das war frech und
soll ihn den Kopf gekostet haben. Die Bisektion lsst dieses Spielchen rckwrts
ablaufen. Statt eine Zahl zu verdoppeln, halbieren wir bei jedem Schritt unsere
Mglichkeiten. Das geht genauso rasant, aber in die andere Richtung. Implosion
statt Explosion.

6.13

Grafik die hundertste


Wiederholung

Wie war das gleich noch mit den Zufallsellipsen in Kapitel 5.7? Aus einem einzigen, vereinsamten Rechteck machen wir mit einer Schleife mir nichts dir nichts
satte 100:

146

Kapitel 6 Schleifen

Sandini Bib
ZufallsRechtecke.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
HBRUSH hbrush, hbalt;
int max = 250;
int i;
for (i = 0; i < 100; i++) {
hbrush = CreateSolidBrush(RGB(rand()%256, rand()%256, rand()%256));
hbalt = SelectObject(hdc, hbrush);
Rectangle(hdc, rand()%max, rand()%max, rand()%max, rand()%max);
SelectObject(hdc, hbalt);
DeleteObject(hbrush);
}
}

Oder wie wre es mit einer Million bunten Punkten? Hier habe ich meine
Bildschirmdaten eingetragen, damit bei maximiertem Fenster alles vollgepixelt
wird:
ZufallsPixel.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int xmax = 1024, ymax = 768;
int i;
for (i = 0; i < 1000000; i++)
SetPixel(hdc, rand()%xmax, rand()%ymax,
RGB(rand()%256, rand()%256, rand()%256));
}

Beides finde ich hbsch:

Grafik die hundertste Wiederholung

147

Sandini Bib

6.14

Linien aus Pixeln

Mit einer Schleife und SetPixel kannst du Geraden zeichnen:


PixelLinie0.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x;
for (x = 0; x < 200; x++)
SetPixel(hdc, x, 100, 0);
}

Die x-Koordinate durchluft Werte von 0 bis 199, whrend die y-Koordinate bei
100 konstant bleibt. Das ergibt eine waagrechte (horizontale) gerade Linie:

Wie habe ich die gepunktete Linie im rechten Bild erzeugt? Einfach x++ durch
x += 5 ersetzen. Auch das folgende Programm erzeugt eine gepunktete gerade
Linie:

148

Kapitel 6 Schleifen

Sandini Bib
PixelLinie1.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x, y;
for (x = 0; x < 200; x++) {
y = 3*x;
SetPixel(hdc, x, y, 0);
}
}

Hier wird x mit x++ in Einerschritten hochgezhlt und die Punkte sind in xRichtung um jeweils ein Pixel versetzt. Aber wenn sich x um eins ndert, ndert
sich y um drei. Wie du siehst, ist es gar nicht so einfach, durchgezogene Linien in
beliebigem Winkel mit einzelnen Pixeln zu zeichnen. Eine durchgezogene Linie
fr
y = 3*x;

erhltst du, wenn du


x = y/3;

zeichnest, indem du y als Schleifenvariable mit y++ verwendest und x aus y berechnest. Durch die ganzzahlige Division bekommst du horizontale Linienstckchen aus 3 Pixeln, weil z. B. 3/3, 4/3 und 5/3 alle gleich 1 sind. Ausprobieren.
Auf jeden Fall ist es einfacher, die Grafikfunktion LineTo des Windows SDK
zu verwenden. Zudem kannst du davon ausgehen, dass eine solche Bibliotheksfunktion wesentlich schneller ist, weil sie in Maschinensprache optimiert wurde.
Womglich werden Linien sogar von deiner Grafikhardware beschleunigt.
6.14

Linien aus Pixeln

149

Sandini Bib

6.15

Schachbrett

Ein typisches Beispiel fr eine doppelte Schleife ist das Schachbrettmuster:


ForFor2.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
HBRUSH hbrot, hbgruen, hbalt;
int i, j;
int x, x0 = 16, dx = 26;
int y, y0 = 8, dy = 26;
hbrot
= CreateSolidBrush(RGB(255,0,0));
hbgruen = CreateSolidBrush(RGB(0,255,0));
hbalt
= SelectObject(hdc, hbrot);
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
if ((i+j)%2)
SelectObject(hdc, hbrot);
else
SelectObject(hdc, hbgruen);
x = i*dx + x0;
y = j*dy + y0;
Rectangle(hdc, x, y, x+dx, y+dy);
}
}
SelectObject(hdc, hbalt);
DeleteObject(hbrot);
DeleteObject(hbgruen);
}

150

Kapitel 6 Schleifen

Sandini Bib

Wie du an
Rectangle(hdc, x, y, x+dx, y+dy);

siehst, malen wir Rechtecke, deren rechte obere Ecke die Koordinaten x und y
haben. Die Breite der Rechtecke ist dx, die Hhe ist dy
Um ein Schachbrett zu erhalten, lassen wir i von 0 bis 7 laufen, und fr jedes i
lassen wir j ebenfalls von 0 bis 7 laufen. Denke ein wenig darber nach, warum
(i+j)%2

die Bedingung ist, die uns fr jede Reihe und Spalte die richtige Farben fr das
Schachbrettmuster auswhlt. Die Koordinaten fr die Rechtecke berechnen wir
mit
x = i*dx + x0;
y = j*dy + y0;

Die Variablen x0 und y0 geben an, um wie viel das gesamte Schachbrett verschoben werden soll. Ausprobieren.
Mit dem Debugger kannst du dir ansehen, wie ein Quadrat nach dem anderen
gemalt wird. (Setze den Textcursor im Editor auf die Zeile mit dem RectangleBefehl und halte F4 gedrckt). Dazu solltest du auch i, j, x und y mit Strg + F5
in das Beobachtungsfenster setzen. Vergiss nicht, vor weiteren Experimenten das
Programm mit Strg + F2 zu verlassen. Unbedingt ausprobieren!
Bleibt noch anzumerken, dass bei mir auf dem Bildschirm die Trennungslinien
zwischen den roten und grnen Quadraten verschieden dick sind. Das liegt daran,
dass grne und rote Pixel auf einem typischen Farbbildschirm ein kleines bisschen gegeneinander versetzt sind. Farben werden ja sowieso im Allgemeinen
aus roten, grnen und blauen Pixeln zusammengemischt, und weil die Pixel dieser Grundfarben gegeneinander verschoben sind, ist immer eine gewisse Unschrfe vorhanden. Wenn du im Programmbeispiel statt Grn, RGB(0,255,0),
6.15

Schachbrett

151

Sandini Bib

ein dunkles Rot verwendest, RGB(150,0,0), sehen die Zwischenrume gleichmig breit aus. Es werden nur noch die roten Pixel verwendet, die gleichmig
verteilt sind, und das Schachbrettmuster kommt durch die unterschiedliche Helligkeit der Pixel zustande.

6.16

Histogramme

In diesem Beispiel wollen wir mehrere Schleifen kombinieren, um die Verteilung


von Zufallszahlen grafisch sichtbar zu machen. Betrachte als Erstes
Wuerfeln0.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int x, y, i, zz;
int dx = 40;
int n[6];
for (i = 0; i < 6; i++)
n[i] = 0;
for (i = 0; i < 1000; i++) {
zz = rand()%6;
n[zz] += 1;
}
for (i = 0; i < 6; i++) {
x = i*dx;
y = n[i];
Rectangle(hdc, x, 0, x+dx+1, y);
}
}

Wie in 3.0 besprochen, definiert int n[6]; ein Feld aus 6 ganzen Zahlen, auf
die wir mit Indizes 0, 1, 2, 3, 4, 5 zugreifen knnen. Kein Problem mit Schleifen.
Mit
for (i = 0; i < 6; i++)
n[i] = 0;

setzen wir alle 6 Elemente des Feldes n auf 0. Die Schleife in


for (i = 0; i < 1000; i++) {
zz = rand()%6;
n[zz] += 1;
}

152

Kapitel 6 Schleifen

Sandini Bib

durchlaufen wir 1000-mal. Wir berechnen 1000-mal eine Zufallszahl zz, die die
Werte 0, 1, 2, 3, 4, 5 annehmen kann. Und jetzt kommt der entscheidende Schritt
des Programms. Mit
n[zz] += 1;

zhlen wir 1 zum Element n[zz] dazu. Das ist, als ob du eine Strichliste beim
Wrfeln fhrst. Wenn du eine 0 gewrfelt hast, erhhst du n[0] um eins (ein
Strich kommt dazu). Wenn du eine 1 gewrfelt hast, erhhst du n[1] um 1.
Und so weiter. Um bei 0 mit dem Zhlen anzufangen, haben wir als Erstes alle
Elemente von n auf 0 gesetzt.
Nachdem 1000-mal gewrfelt und gezhlt wurde, geben wir das Ergebnis grafisch aus. Dazu malen wir sechs Rechtecke mit
for (i = 0; i < 6; i++) {
x = i*dx;
y = n[i];
Rectangle(hdc, x, 0, x+dx+1, y);
}

Die Rechtecke sind alle gleich breit, nmlich dx Pixel. Ausprobieren: Was geschieht, wenn du im Rectangle-Befehl statt x+dx+1 z. B. x+dx5 oder nur x+dx
verwendest?
Die y-Koordinaten der Rechtecke gehen von 0 bis n[i], das heit ihre Lnge
nach unten gibt an, wie oft eine bestimmte Zahl gewrfelt wurde. Das sieht
dann z. B. so aus:

Wenn jede Zahl genau gleich oft vorkme, msste jedes Rechteck gerade 1000/6mal vorkommen, also ziemlich genau 133-mal. Die Rechtecke wren dann genau
133 Pixel lang. Aber weil beim Wrfeln die Ergebnisse schwanken, ndert sich
auch die Lnge der Rechtecke von Mal zu Mal. Wenn du die Fenstergre nderst, wird neu gewrfelt und du bekommst einen Eindruck davon, wie sehr das
Ergebnis selbst bei 1000-mal wrfeln schwankt.
Ein solches Diagramm aus Rechtecken nennt man auch Balkendiagramm oder
Histogramm. Lass uns das Programm noch verbessern:
6.16

Histogramme

153

Sandini Bib
Wuerfeln1.c WinHallo.cpp

#include <windows.h>
#include <stdio.h>
#define WUERFEL 2
#define AUGEN
6
#define MAXWURF (WUERFEL*AUGEN)
void malen(HDC hdc)
{
char text[1000];
int x, y, i, j, zz;
int dx = 40;
int n[MAXWURF+1];
int nmal = 100000;
for (i = 0; i <= MAXWURF; i++)
n[i] = 0;
for (i = 0; i < nmal; i++) {
zz = 0;
for (j = 0; j < WUERFEL; j++)
zz += 1 + rand()%AUGEN;
n[zz] += 1;
}
for (i = WUERFEL; i <= MAXWURF; i++) {
x = (iWUERFEL)*dx;
y = 1000*n[i]/nmal;
y = 200y;
Rectangle(hdc, x, 200, x+dx+1, y);
sprintf(text, "%d", i);
TextOut(hdc, x+dx/3, 200, text, strlen(text));
}
if (WUERFEL == 1)
sprintf(text, "%d mal mit einem Wuerfel mit %d Augen gewuerfelt",
nmal, AUGEN);
else
sprintf(text, "%d mal mit %d Wuerfeln mit %d Augen gewuerfelt",
nmal, WUERFEL, AUGEN);
TextOut(hdc, 5, 5, text, strlen(text));
}

Erst wollen wir das Programm besprechen und dann ein paar Experimente mit
verschiedenen Wrfeln machen. Zu Beginn des Programms definieren wir eine
symbolische Konstante WUERFEL fr die Anzahl der Wrfel, und eine Konstante
AUGEN fr die Anzahl der Augen pro Wrfel (ein Sechser-Wrfel hat sechs Augen). Diese Konstanten drfen wir in der Definition einer Konstanten MAXWURF
verwenden, die wir spter in der Grenangabe des Feldes n verwenden wollen.
154

Kapitel 6 Schleifen

Sandini Bib

Wir haben auch eine gewhnliche Variable nmal eingefhrt, die angibt, wie
oft mit WUERFEL Wrfeln gewrfelt wird. Schner Zungenbrecher. Findest
du die doppelte Schleife, in der dies getan wird? Hier verwenden wir jetzt
1 + rand()%AUGEN, so dass, falls AUGEN = 6, Zufallszahlen von 1 bis 6 erzeugt
werden.
Damit die Rechtecke in der Anzeige nicht zu gro werden, setzen wir nicht wie
vorher y = n[i], sondern
y = 1000*n[i]/nmal;

Als weiteren Trick setzen wir y = 200 y, damit die Rechtecke nicht nach unten, sondern nach oben grer werden. Denn y geht in die entgegengesetzte
Richtung von y. Die 200 Pixel geben wir auch im Rectangle-Befehl an, damit
von y = 200 nach oben gezeichnet wird. berleg dir einfach mal, was bei y gleich
0, 50 und 150 passiert.
Des Weiteren habe ich die Ausgabe mit etwas Text verschnert. Unter jedem
Rechteck geben wir mit TextOut den Index des Feldes aus, der ja zugleich die
Summe des Wurfs angibt. Zum Schluss schreiben wir noch eine Zeile, die angibt,
wie gewrfelt wurde. Mit if und else unterscheiden wir, ob Wrfel in Einzahl
oder Mehrzahl geschrieben wird. Der Compiler gibt eine Warnung aus. Weil
WUERFEL eine Konstante ist, wird immer nur einer der beiden Flle zutreffen.
Lass die Wrfel rollen! Wenn du zwei Sechser-Wrfel nimmst, aber nur 120-mal
wrfelst, ist das Ergebnis z. B.

6.16

Histogramme

155

Sandini Bib

Bei so wenigen Wrfen ist noch nicht klar, wie das durchschnittliche Ergebnis
aussieht. Aber bei nmal = 100000 sieht das Bild ziemlich genau so aus:

Bei einem Wrfel sind alle Zahlen gleich wahrscheinlich, aber bei zwei Wrfeln
ist das anders. Denn fr eine 2 mssen beide Wrfel die 1 zeigen, whrend es
fr die 3 schon zwei Mglichkeiten gibt, und so weiter:
2 = 1+1
3 = 1+2 = 2+1
4 = 1+3 = 2+2 = 3+1
5 = 1+4 = 2+3 = 3+2 = 4+1
6 = 1+5 = 2+4 = 3+3 = 4+2 = 5+1
7 = 1+6 = 2+5 = 3+4 = 4+3 = 5+2 = 6+1
8 = 2+6 = 3+5 = 4+4 = 5+3 = 6+2
9 = 3+6 = 4+5 = 5+4 = 6+3
10 = 4+6 = 5+5 = 6+4
11 = 5+6 = 6+5
12 = 6+6

Hier habe ich alle Mglichkeiten aufgeschrieben, die Zahlen von 2 bis 12 mit
zwei Sechser-Wrfeln zu erzielen. Von 2 bis 7 wird die Anzahl der Mglichkeiten
jeweils um eins grer, whrend von 7 bis 12 die Anzahl um jeweils 1 abnimmt.
Und wie du siehst, spiegelt unser Wrfelprogramm diese Dreiecksform wider,
wenn wir nur oft genug wrfeln!
Was passiert bei drei Sechser-Wrfeln? Kannst du das Ergebnis mit Papier und
Bleistift vorhersagen? Unser Programm malt

156

Kapitel 6 Schleifen

Sandini Bib

Die Rechtecke ergeben keinen dreieckigen Berg, sondern einen abgerundeten


Berg. Je mehr Wrfel du nimmst, desto runder der Berg, und desto hnlicher
wird unser Balkendiagramm der so genannten Glockenkurve. Zurzeit kannst du
diese Glockenkurve noch auf dem Zehnmarkschein finden. Der Berg wird breiter,
grob gesprochen, wenn du statt drei 6er-Wrfeln zwei 9er-Wrfel oder einen
18er-Wrfel nimmst. Ausprobieren. Der Berg wird schmaler, wenn du statt drei
6er-Wrfeln sechs 3er-Wrfel nimmst oder im Extremfall achtzehn 1er-Wrfel.
Zum Abschluss zeige ich dir ein Histogramm fr zwanzig 2er-Wrfel:

Du kennst keine 2er-Wrfel? Doch, du kannst eine Mnze werfen. Wie dem auch
sei, solltest du irgendwann einmal in einem deiner Programme eine interessantere Zufallsverteilung brauchen als die Gleichverteilung, kennst du jetzt einige
Alternativen.

6.17
Endlich haben wir die Kontrolle ber den Programmablauf bernommen. Wir
knnen anhand von Schleifenbedingungen entscheiden, ob ein Programmteil
keinmal, einmal, viele Male oder unendlich oft ausgefhrt wird! (Na ja, eben
so lange, wie der Computer berhaupt luft.) Mit Schleifen fngt der Programmierspa erst so richtig an! Zur Erinnerung:
Die folgenden drei Zhlerschleifen drucken alle die Ziffern 0, 1, 2:
for (i = 0; i < 3; i++) {
printf("%d\n", i);
}
i = 0;
while (i < 3) {
printf("%d\n", i);
i++;
}
i = 0;
do {
printf("%d\n", i);

6.17

157

Sandini Bib

i++;
} while (i < 3);

Wenn in diesen Beispielen die Initialisierung in i = 10 umgendert wird,


dann drucken die for- und while-Schleifen nichts und die do-Schleife einmal
die 10.
Mit break kann eine Schleife beendet und zum nchsten Befehl nach der
Schleife gesprungen werden. Mit continue wird zum Anfang der Schleife
gesprungen und der nchste Durchgang begonnen.
Gerade bei Schleifen ist die Schritt-fr-Schritt-Methode im Debugger sehr
hilfreich. Mit Strg + F5 kannst du dir die Werte der Zhlervariablen anzeigen
lassen.
Aber niemand sagt, dass du alles im Kopf oder mit dem Debugger analysieren
musst! Mir hilft es, bei verzwickten Fllen ein paar konkrete Beispiele per
Hand mit Papier und Bleistift zu berprfen.

6.18
1. Das Count-down-Beispiel luft sehr flott. Die meiste Zeit verbringt die
Schleife jedoch mit der printf-Anweisung. Stoppe die Zeit fr 100 000
Iterationen (Schleifendurchlufe) mit time oder clock (Beispiel 6.11).
Dann entferne das \n und stoppe wieder die Zeit. Es kostet bei mir ei-

nige Zeit, den Bildschirm nach oben zu schieben. Schlielich entferne die
printf-Anweisung und stoppe die Zeit. Vielleicht musst du jetzt mehr

Schleifendurchlufe einplanen. Wenn du verschiedene Operationen wie


Addition und Multiplikation von Ints und Doubles in die Schleife einsetzt,
kannst du bestimmen, was mehr und was weniger Zeit bentigt.
2. Erweitere das Beispiel mit der Summe. Frage nach der Zahl n, bis zu der

die Zahlen addiert werden sollen. Vergleiche verschiedene Ergebnisse mit der
Formel
summe2 = n * (n + 1) / 2;

die du auch am Bildschirm ausgibst. Das msste dasselbe Ergebnis liefern!


Die Moral von der Geschichte: Oft gibt es einfache Algorithmen, die man
sofort programmieren kann, wie hier fr die Summe. Manchmal gibt es aber
auch verblffende Vereinfachungen. Die Formel ist natrlich viel schneller
berechenbar als eine lange Schleife bis 10 000.
3. Erweitere den Zoo. Setze drei Affen untereinander, indem du die printfAnweisungen in den Befehlsblock einer for-Schleife schreibst. Den langen
Hals einer Giraffe kannst du auch mit einer for-Schleife programmieren.
4. Schreibe ein Programm, das eine schrge Linie aus Sternchen (*) im Textfen-

ster ausgibt. In der Ausgabe gibst du dazu vor dem Sternchen eine wachsende
158

Kapitel 6

Schleifen

Sandini Bib

Anzahl von Leerzeichen aus (siehe das Beispiel zum doppelten Schleifchen).
Im nchsten Schritt soll das Programm eine Zickzacklinie ausgeben, also erst
eine Linie nach rechts, dann nach links, und immer so weiter. Du solltest
das Sternchen hin- und herflitzen sehen, weil die Zeilen sich ja nach oben
bewegen. Wenn das bei dir zu schnell luft, warte mit getchar nach jedem
Sternchen auf die Eingabetaste. Und wenn dir so was Spa macht, kannst du
noch per Zufallszahl bestimmen, wie viele Pltze sich das Sternchen in eine
Richtung bewegt, bevor es seine Richtung umkehrt.
5. Fallen dir noch weiter Grafikmotive ein, die Wiederholungen enthalten? An-

regungen findest du z. B. in den Kapiteln 4.8 und 4.9. Male nicht 5, sondern
100 farbige Ellipsen. Male eine Zielscheibe aus 500 bunten konzentrischen
Ringen. ndere dein Zielscheibenprogramm ab, so dass alle Ringe dieselbe
Farbe haben, aber mit nach innen abnehmender Helligkeit. Wenn du jetzt
den Mittelpunkt von Ring zu Ring etwas verschiebst, sieht das fast wie ein
Tunnel aus. Wenn du die Ellipsen mit einem dicken, farbigen Stift malst, das
Innere aber durchsichtig lsst, kannst du den Tunnel noch schrfer abbiegen
lassen.
6. Schreibe ein Programm, das wie bei einem richtigen Wrfel verschieden viele
Punkte anzeigt. Du knntest clock oder GetTickCount verwenden, um die

Anzeige eine gewisse Zeit einzufrieren, bevor eine neue Zahl angezeigt wird.
Das knnte aussehen, als ob der Wrfel erst schnell und dann immer langsamer neue Augen zeigt.

6.18

159

Sandini Bib

Sandini Bib

7
Funktionen
7.0
7.1
7.2
7.3
7.4
7.5
7.6
7.7
7.8
7.9

Funktionen
Funktion mit einem Argument
Funktion mit Rckgabewert
Funktion mit Argumenten und Rckgabewert
Prototypen von Funktionen
Headerdateien und Programmaufbau
Lokale Variable
Externe Variable
Statische und automatische Variablen
Statische Funktionen und externe Variablen

162
165
165
167
169
170
173
175
177
179

7.10

Zufallszahlen selbst gemacht

180

7.11

Rekursion

181

7.12

Rekursion mit Grafik

184

7.13

Labyrinth

187

7.14

202

7.15

203

Sandini Bib

Die meisten Programmieraufgaben lassen sich auf natrliche Weise in Teilaufgaben zerlegen. In C geschieht dies mit Funktionen. Ein C-Programm besteht aus
einer (main!) oder mehreren Funktionen. Diese knnen ber mehrere Dateien
verteilt sein, die getrennt kompiliert werden. Oft ist es besser, viele kleine statt
weniger groer Funktionen zu schreiben.
Funktionen sind praktisch, weil sie Programmteile wiederverwendbar machen.
Statt dieselben Zeilen Code an verschiedenen Stellen zu wiederholen, verpackst
du sie in einer Funktion und rufst diese mehrmals auf. Ein gutes Beispiel sind
die Funktionen in den Standardbibliotheken.
Funktionen sind auch deshalb praktisch, weil sie verschiedene Aufgaben sauber
voneinander trennen knnen. Im aufrufenden Programm kannst du dich mit
Befehlen wie print oder erzeuge Zufallszahl auf die eigentliche Aufgabe konzentrieren, weil du von einer gut gemachten Funktion nur ihre Funktion, nicht
aber ihr Innenleben kennen musst. In der Funktion selbst kannst du dich dann
auf ein eingeschrnktes Problem konzentrieren und die Auenwelt ignorieren.
Ein wichtiger Punkt dabei ist, wie eine Funktion Daten mit dem aufrufenden
Programm austauscht. Dazu gehrt auch, dass man externe und lokale Variablen
definieren kann. Externe Variablen sind von allen Funktionen aus zugnglich,
whrend auf lokale Variablen nur innerhalb ihres Blocks zugegriffen werden
kann.
In diesem Kapitel besprechen wir, wie du deine eigenen Funktionen schreiben
und verwenden kannst.

7.0

Funktionen

Anhand der Funktion main haben wir schon in Kapitel 1 diskutiert, wie die Definition einer Funktion aussieht. Im Allgemeinen geht das so:
Datentyp Funktionsname(Argumente)
{

Befehle
}

Dabei wird den Befehlen im Block zwischen { } der Name Funktionsname zugewiesen.
Im folgenden Beispiel definieren wir eine neue Funktion namens ansage, die wir
in main aufrufen, um einen Text auszugeben:

162

Kapitel 7 Funktionen

Sandini Bib
Funktion0.c

#include <stdio.h>
void ansage(void)
{
printf("Hier spricht der Captain.\n");
}
int main()
{
ansage();
getchar();
return 0;
}

Hier spricht der Captain.

Als Erstes definieren wir die Funktion ansage mit


void ansage(void)
{
printf("Hier spricht der Captain.\n");
}

leer,
Wenn eine Funktion keine Argumente annimmt, schreibt man void (
nichts) in die Argumentenliste. Und wenn keine Daten zurckgeliefert werden,
schreibt man als Datentyp ebenfalls void.
Nach der Definition der Funktion ansage folgt die Definition der Funktion main.
In main rufen wir mit
ansage();

unsere selbst gemachte Funktion auf. Beim Aufruf schreiben wir kein void, sondern schreiben einfach keine Argumente zwischen die Klammern (). So haben
wir das auch bisher mit getchar() gemacht.
Kannst du im Programmtext verfolgen, wie das Programm abluft? Lasse das
Programm im Debugger laufen. Wenn du wiederholt F8 drckst, wird die Funktion main Zeile fr Zeile ausgefhrt. Bei ansage(); erscheint der Text und der
Cursor springt in die Zeile getchar();.
Den Debuggerbefehl nchster Befehl gibt es in BCB in zwei Versionen. Mit
F8 (Gesamte Routine) werden Funktionen als ein Schritt ausgefhrt, aber mit
F7 (Einzelne Anweisung) wird in eine Funktion hineingesprungen. Von der Zeile
mit dem Funktionsaufruf ansage() springt die Anzeige zur Definition der Funktion, void ansage(void). Nachdem die Befehle in ansage ausgefhrt worden
sind, springt die Anzeige auf die Zeile nach dem Funktionsaufruf. Das klingt
7.0 Funktionen

163

Sandini Bib

komplizierter, als es ist, einfach ausprobieren. Starte unser Beispiel mit F8 und
dann drcke wiederholt F7 . Oder klicke die entsprechenden Knpfe unterhalb
der BCB-Menleiste.
Wenn eine Funktion erst mal definiert ist, knnen wir sie auch mehrmals aufrufen:
Funktion1.c

#include <stdio.h>
void ansage(void)
{
printf("Hier spricht der Captain.\n");
}
int main()
{
ansage();
ansage();
ansage();
getchar();
return 0;
}

Hier spricht der Captain.


Hier spricht der Captain.
Hier spricht der Captain.

Das ist auf jeden Fall krzer, als dreimal den printf-Befehl zu wiederholen, und
wenn du die Ansage ndern mchtest, brauchst du das nur an einer Stelle zu tun.

164

Kapitel 7 Funktionen

Sandini Bib

7.1 Funktion mit einem Argument


Wir wissen schon, dass Funktionen mit Argumenten aufgerufen werden knnen.
So definieren wir eine Funktion mit genau einem Argument:
Funktion2.c

#include <stdio.h>
void warp(int i)
{
printf("Geschwindigkeit Warp %d.\n", i);
}
int main()
{
warp(5);
getchar();
return 0;
}

Geschwindigkeit Warp 5.

Weil die Funktion warp mit warp(5) aufgerufen wird, wird die 5 in die Variable
i kopiert. In der Funktion warp kann dann i wie eine gewhnliche Variable
verwendet werden. Das ist, als ob wir zu Beginn des Befehlsblocks von ansage
die Zeile int i; geschrieben htten, auer dass bei Programmablauf eben diese
Variable i gleich mit der Zahl 5 initialisiert wird.

7.2 Funktion mit Rckgabewert


Eine Funktion kann mit dem Befehl return Zahlen zurckliefern:

7.1 Funktion mit einem Argument

165

Sandini Bib
Funktion3.c

#include <stdio.h>
int warpmax(void)
{
int wmax;
int pi = 3;
int daumen = 4;
wmax = pi * daumen 5;
return wmax;
}
void warp(int i)
{
int imax;
imax = warpmax();
printf("Geschwindigkeit Warp %d.\n", i);
printf("Hoechstgeschwindigkeit ist Warp %d.\n", imax);
}
int main()
{
warp(5);
getchar();
return 0;
}

Geschwindigkeit Warp 5.
Hoechstgeschwindigkeit ist Warp 7.

Wir werden immer mutiger und rufen in main die Funktion warp auf, die wiederum die Funktion warpmax aufruft. Die Definition der Funktion warpmax beginnt mit
int warpmax(void)

Also wird warpmax ohne Argumente aufgerufen, und das int bedeutet, dass als
Ergebnis eine ganze Zahl zurckgegeben wird. Welche Zahl das ist, wird durch
return wmax;

bestimmt.
Wie knnen wir den Wert verwenden, den warpmax() berechnet? Zum Beispiel
wie in
imax = warpmax();

166

Kapitel 7 Funktionen

Sandini Bib

Der Ausdruck rechts vom = wird als Erstes ausgewertet. In diesem Fall steht
rechts der Funktionsaufruf warpmax(). Das Ergebnis ist der Rckgabewert der
Funktion. Dieser Wert wird nach imax kopiert.
Weil in diesem Fall der Funktionsaufruf eine ganze Zahl liefert, kannst du genauso gut
printf("Hoechstgeschwindigkeit ist Warp %d.\n", warpmax());

verwenden. Du musst nur aufpassen, dass zu jedem Klammer auf ein Klammer
zu an der richtigen Stelle steht. Diese Eigenschaft von Funktionen, dass sie dort
geschrieben werden drfen, wo du ihren Rckgabewert bentigst, haben wir in
den vorangegangenen Kapiteln schon fleiig benutzt (z. B. bei rand()).
Eine Funktion darf mehrere return-Befehle enthalten. Du kannst Funktionen
an beliebiger Stelle mit return verlassen, nicht nur in der letzten Zeile. Falls
kein Rckgabewert bentigt wird, verwendet man den Befehl
return;

Das erinnert an den break-Befehl, mit dem eine Schleife verlassen werden kann.
Whrend break aber nur die momentane Schleife beendet, kannst du mit return
eine Funktion mitten in einer doppelten Schleife oder einem dreifachen if verlassen. Ein einfaches Beispiel findest du in Kapitel 7.11.

7.3 Funktion mit Argumenten und Rckgabewert


Im nchsten Beispiel definieren wir eine Funktion differenz, die sinnigerweise
zwei Zahlen als Argument annimmt und als Ergebnis eine Zahl liefert:

7.3 Funktion mit Argumenten und Rckgabewert

167

Sandini Bib
Funktion4.c

#include <stdio.h>
int differenz(int i, int j)
{
int ergebnis;
ergebnis = i j;
return ergebnis;
}
int main()
{
int i = 7;
printf("Hier spricht der Captain. Warp %d.\n", i);
printf("Captain, wir schaffen nur Warp %d.\n", differenz(i, 1));
getchar();
return 0;
}

Hier spricht der Captain. Warp 7.


Captain, wir schaffen nur Warp 6.

Wie du siehst, knnen bei der Definition einer Funktion mehrere Argumente
in einer Kommaliste angegeben werden. Fr return mssen wir das Ergebnis
nicht erst in einer Variable zwischenspeichern. Wir knnen auch
int differenz(int i, int j)
{
return i j;
}

verwenden.
Vielleicht fragst du dich jetzt, wie man eine Funktion definiert, die je nach Bedarf eine unterschiedliche Anzahl von Argumenten annimmt. printf ist dafr
ein Beispiel. Solche Funktionen werden eher selten verwendet, deshalb verweise
ich an dieser Stelle auf die BCB-Hilfe (Stichwort va_arg). Ein Grund, warum
Variable Argumentenlisten nur selten tatschlich bentigt werden, ist, dass in
den meisten Fllen Felder, Zeiger und Strukturen fr die bergabe von komplexen Argumenten ausreichen (Kapitel 10). Mit diesen Mitteln lsst sich auch das
Problem lsen, wie eine Funktion mehr als genau eine Zahl als Ergebnis liefern
kann.

168

Kapitel 7 Funktionen

Sandini Bib

7.4 Prototypen von Funktionen


Bevor wir eine Funktion im Programmtext aufrufen knnen, muss der Prototyp
der Funktion bekannt sein. Betrachte das folgende Beispiel:
Funktion5.c

#include <stdio.h>
void ansage(void);
int main()
{
ansage();
getchar();
return 0;
}
void ansage(void)
{
printf("Hier spricht der Captain.\n");
}

Hier spricht der Captain.

Das hnelt dem Beispiel aus Kapitel 7.0, aber diesmal steht im Programmtext
erst die Definition von main und dann die Definition von ansage. Die Zeile
void ansage(void);

nennt man den Prototyp der Funktion ansage.


Der Prototyp einer Funktion ist alles vor dem Funktionsblock in ihrer Definition,
plus einen Strichpunkt. Das ist keine vollstndige Definition der Befehlsblock
fehlt! Aber diese Zeile enthlt die Vereinbarungen, die der Compiler bentigt,
um den Funktionsaufruf von ansage in main handhaben zu knnen. Tipp das
Beispiel ein oder ndere das Beispiel aus Kapitel 7.0, indem du die Reihenfolge
der Definitionen vertauschst. Ausprobieren, ohne die Zeile mit dem Prototyp
von hallo beschwert sich der Compiler mit sowas wie
Call to function hallo with no prototype.
Type mismatch in redeclaration of hallo.

Das heit sinngem, dass erstens hallo ohne Prototyp aufgerufen wurde, und
zweitens, dass der Typ von hallo nicht mit einer vorausgegangenen Deklaration
(Vereinbarung) zusammenpasst. Was geht hier vor?
Der Compiler liest den Programmtext von oben nach unten (und von links nach
rechts). Wenn ihm der Prototyp oder die Definition einer Funktion begegnet,
7.4 Prototypen von Funktionen

169

Sandini Bib

bevor sie verwendet wird, ist alles in Ordnung. Das ist genau wie bei der Vereinbarung von Variablen. Wenn aber der Funktionsaufruf vor dem Prototypen
oder der Definition steht, nimmt der Compiler an, dass alle Datentypen int sind.
Weil aber weiter unten ansage mit dem Typ void definiert wird, beschwert sich
der Compiler. All das klingt unntig umstndlich, ist aber so.
Man sollte bei der Vereinbarung von Funktionen des Typs int nicht versuchen,
mit dem Default des Compilers zu tricksen, und immer alle Typen explizit
angeben. Das gilt auch fr die Vereinbarung der Funktion main. Genau genommen sollten wir int main(void) schreiben, aber main ist ein Sonderfall,
denn main kann auch bestimmte Argumente erhalten, siehe 10.8. Im Prototypen kannst du die Variablennamen allerdings auch weglassen und z. B.
int differenz(int, int); schreiben. Mit aussagekrftigen Variablennamen
sind Prototypen aber viel lesbarer.
brigens drfen Funktionen nicht innerhalb von anderen Funktionen definiert
werden. Im Programmtext wird eine Funktion nach der anderen definiert, aber
jede fr sich und nicht etwa innerhalb des Blocks einer anderen Funktion.

7.5 Headerdateien und Programmaufbau


In kleinen Programmen kann man oft die Funktionen in der richtigen Reihenfolge anordnen, so dass keine Prototypen gebraucht werden. Oder man sammelt
die Prototypen von allen Funktionen, die man verwendet, und schreibt sie an
den Anfang des Programmtextes.
Und genau das machen wir mit den Prototypen fr Funktionen wie printf schon
die ganze Zeit! Die Datei stdio.h enthlt die Prototypen fr alle Funktionen
head heit
in der Standard-Input-Output-Library. .h steht fr Header,
Kopf, und ein Header ist etwas, das vorausgeschickt wird.
Erinnerst du dich an Kapitel 0 und daran, dass ein ausfhrbares Programm in
zwei Schritten erzeugt wird? Der Compiler bersetzt den Programmtext, und
dazu bentigt er die Prototypen. Der Linker setzt die verschiedenen kompilierten Programmteile zusammen. Dazu verbindet er deine selbst geschriebenen
Programmteile mit kompilierten Objekten aus den Standardbibliotheken. Und
der Prototyp von printf in stdio.h stellt sicher, dass alles zusammenpasst.
Wenn deine Programme so gro werden, dass eine lange .c-Datei zu unbersichtlich ist, kannst du deine Funktionen auf mehrere .c-Dateien verteilen. Jede
Funktion muss dabei komplett in einer Datei stehen, aber es mssen eben nicht
alle Funktionen in einer Datei stehen. Hier ist ein Beispiel:

170

Kapitel 7 Funktionen

Sandini Bib
AnsageMain0.c

/* AnsageMain0.c */
#include <stdio.h>
void ansage(void);
int main()
{
ansage();
getchar();
return 0;
}

Ansage0.c

/* Ansage0.c */
#include <stdio.h>
void ansage(void)
{
printf("Hier spricht der Captain.\n");
}

Das Programm ist auf zwei Dateien verteilt. Damit der Compiler wei, dass eine
aufgerufene Funktion in einer anderen Datei definiert ist, schreibt man ihren
Prototyp an den Anfang von jeder Datei, in der die Funktion aufgerufen wird. Der
Compiler erzeugt fr jede Datei ein Objekt. Der Linker durchsucht alle Objekte
nach den bentigten Funktionen.
Deine Prototypen kannst du in einer selbst gemachten Headerdatei sammeln, die
mit #include gelesen wird:
Ansage1.h

/* Ansage1.h */
void ansage(void);

AnsageMain1.c

/* AnsageMain1.c */
#include <stdio.h>
#include "Ansage1.h"
int main()
{
ansage();
getchar();
return 0;

// Prototyp in Ansage1.h
// Prototyp in stdio.h

7.5 Headerdateien und Programmaufbau

171

Sandini Bib
Ansage1.c

/* Ansage1.c */
#include <stdio.h>
#include "Ansage1.h"
void ansage(void)
{
printf("Hier spricht der Captain.\n");
}

// Prototyp in stdio.h

Weil #include "Ansage1.h" einfach nur den Text aus der Datei Ansage1.h in
die Datei AnsageMain1.c einsetzt, funktioniert dieses Beispiel genauso wie das
erste.
In Ansage1.c wird das #include "Ansage1.h" eigentlich nicht bentigt. Es ist
aber eine gute Idee, alle Prototypen in einer Headerdatei zu sammeln und diese
Headerdatei in allen Programmdateien einzulesen. Solltest du irgendwann die
Definition von ansage um ein Argument erweitern, bekommst du dann eine
Fehlermeldung, dass die Definition nicht mehr mit dem Prototyp in der Headerdatei bereinstimmt. Jede Funktion darf nur einmal definiert werden (siehe
jedoch 7.9), aber ihr Prototyp kann beliebig oft angegeben werden.
In Kapitel 0.6 haben wir besprochen, wie man Dateien in Projekten verwaltet. In
BCB findest du unter dem Menpunkt Projekt die Eintrge Zum Projekt hinzufgen und Aus dem Projekt entfernen. BCB muss wissen, welche C-Dateien
kompiliert und gelinkt werden sollen. Unter dem Menpunkt Ansicht findest
du Projektverwaltung, was ein praktisches Fensterchen zur Verwaltung aller
deiner Dateien aufmacht.

172

Kapitel 7 Funktionen

Sandini Bib

7.6 Lokale Variable


Ein weiteres Thema, das direkt im Zusammenhang mit Funktionen und Programmaufbau steht, ist der Gltigkeitsbereich von Variablen. Schau dir mal das
folgende Beispiel an:
VarLokal0.c

#include <stdio.h>
void hallo(int i)
{
printf("i ist %d\n", i);
}
int main()
{
int n;
n = 3;
hallo(n);
getchar();
return 0;
}

i ist 3

Klarer Fall, hallo bekommt eine ganze Zahl bergeben. Diese heit in main
zwar n, aber in hallo erhlt sie den Namen i. Versuche einmal, in hallo die
Zeile
printf("n ist %d\n", n);

zu verwenden. Der Compiler sagt dir, dass er an dieser Stelle keine Variable n
kennt. Es hilft auch nicht, die Definition von hallo hinter main zu schreiben
(Prototyp nicht vergessen). n bleibt unerkannt. Ausprobieren!
Eine Variable ist nur in dem Block sichtbar, in dem sie definiert wurde. Das kann
am Anfang eines Funktionsblocks geschehen wie in int n; oder durch die Definition in der Argumentenliste einer Funktion wie in void hallo(int i). Das
ist sehr sinnvoll, denn so kommt die Information in verschiedenen Funktionen
nicht durcheinander. Solche Variable nennt man lokal, denn sie sind nur lokal
innerhalb des jeweiligen Befehlsblocks verwendbar.
Wir knnen sogar den Namen n mehrmals fr eine Variable vereinbaren, denn
jede Variable hat einen lokalen, wohldefinierten Gltigkeitsbereich:

7.6 Lokale Variable

173

Sandini Bib
VarLokal1.c

#include <stdio.h>
void hallo(int i)
{
int n = 0;
printf("i ist %d\n", i);
printf("n in hallo ist %d\n", n);
}
int main()
{
int n;
n = 3;
hallo(n);
printf("n in main ist %d\n", n);
getchar();
return 0;
}

i ist 3
n in hallo ist 0
n in main ist 3

In diesem Beispiel gibt es zwei verschiedene Variable mit Namen n, aber jede hat
ihren eigenen Speicherplatz. Daher ist n in der Funktion main nach dem Aufruf von hallo immer noch gleich 3, obwohl zwischendurch n = 0; in hallo
ausgefhrt wurde. Die Gltigkeitsbereiche dieser zwei Variablen sind durch die
Befehlsblcke sauber voneinander getrennt. Jede Variable hat ihren eigenen lokalen Gltigkeitsbereich.
Der Vollstndigkeit halber will ich erwhnen, dass lokale Variable zu Beginn jedes beliebigen Befehlsblocks definiert werden knnen, also z. B. auch innerhalb
des Befehlsblocks einer if-Bedingung. Dabei gelten dieselben Regeln wie bei lokalen Variablen zu Beginn eines Funktionsblocks. Weil Befehlsblcke innerhalb
von Befehlsblcken vorkommen knnen, stellt sich die Frage, was passiert, wenn
eine Variable n in mehreren verschachtelten Befehlsblcken definiert wird. Jede
Variable n erhlt ihren eigenen Speicherplatz, und es ist immer nur die nchstgelegene Definition von n sichtbar. Auf die anderen Variablen n kann nicht zugegriffen werden. Dieselbe Frage der Sichtbarkeit stellt sich auch in Kapitel 7.7.

174

Kapitel 7 Funktionen

Sandini Bib

7.7 Externe Variable


Hier besprechen wir eine neue Idee. Eine Variable kann auch auerhalb von
Funktionen definiert werden. Solche Variablen nennt man extern oder global,
im Gegensatz zu internen oder lokalen Variablen innerhalb eines Blocks. Hier
ist ein Beispiel:
VarExtern0.c

#include <stdio.h>
int n;
void hallo(void)
{
printf("n in hallo ist %d\n", n);
n = 0;
}
int main()
{
n = 3;
hallo();
printf("n in main ist %d\n", n);
getchar();
return 0;
}

n in hallo ist 3
n in main ist 0

Alle Funktionen, die nach der Zeile int n; definiert werden, haben Zugriff auf
n. Im Beispiel muss deshalb n nicht extra an hallo bergeben werden.
Zudem kann hallo genauso wie main den Wert von n ndern! Eine Funktion
kann mehrere Zahlen berechnen und diese der aufrufenden Funktion in externen
Variablen zur Verfgung stellen.
Wenn wir in unserem Beispiel trotz externer Variable eine direkte bergabe
vornehmen, erhalten wir

7.7 Externe Variable

175

Sandini Bib
VarExtern1.c

#include <stdio.h>
int n;
void hallo(int n)
{
printf("n in hallo ist %d\n", n);
n = 0;
}
int main()
{
n = 3;
hallo(n);
printf("n in main ist %d\n", n);
getchar();
return 0;
}

n in hallo ist 3
n in main ist 3

In der Funktion hallo haben wir eine zweite Variable namens n definiert. Beim
Funktionsaufruf wurde der Wert des ersten n in den Speicherplatz des zweiten n
kopiert. Diese lokale Variable n hat nichts mit anderen Variablen dieses Namens
auerhalb der Funktion hallo zu tun. Der Befehl n = 0; ndert die externe
Variable n nicht.
In anderen Worten, innerhalb des Funktionsblocks von void hallo(int n)
berschattet die lokale Variable n die externe Variable n. Das bedeutet auch, dass
die externe Variable n innerhalb von hallo nicht sichtbar ist. Auf die externe
Variable n kann innerhalb von hallo nicht zugegriffen werden, weil der Variablenname n sich auf die lokale Variable bezieht.
Im Prinzip knnte man alle Variablen zu externen Variablen machen. In dem
Fall bentigt jede Variable einen eigenen Namen, aber man muss dann nicht
ber Argumentenlisten nachdenken.
Vorsicht, das ist nicht in jedem Fall eine gute Idee! Denn dadurch
geht die Trennung von Innenleben und Aufruf einer Funktion verloren. Zum Beispiel mssten wir dann vor dem Aufruf einer Funktion
differenz(void) ohne Argumente immer erst die Zahlen in die richtigen Variablen kopieren, was sehr ungeschickt ist. Es knnte auch leicht passieren, dass
aus Versehen zwei Funktionen ein und dieselbe Variable i verwenden, obwohl
eigentlich zwei unabhngige Variablen gebraucht werden. Was fr ein Durcheinander. Externe Variablen sind sehr ntzlich, aber du solltest ihre Verwendung
auf Flle beschrnken, wo sie tatschlich ntzlich sind.
176

Kapitel 7 Funktionen

Sandini Bib

7.8 Statische und automatische Variablen


Eine statische lokale Variable wird eingesetzt, wenn eine Funktion zwischen den
Funktionsaufrufen den Wert von lokalen Variablen nicht vergessen soll:
VarStatic0.c

#include <stdio.h>
void hallo(void)
{
static int wieoft = 1;
printf("Hallo! (jetzt ruft der mich schon zum %d. Mal auf)\n",
wieoft++);
}
int main()
{
int i;
for (i = 0; i < 5; i++)
hallo();
getchar();
return 0;
}

Hallo!
Hallo!
Hallo!
Hallo!
Hallo!

(jetzt
(jetzt
(jetzt
(jetzt
(jetzt

ruft
ruft
ruft
ruft
ruft

der
der
der
der
der

mich
mich
mich
mich
mich

schon
schon
schon
schon
schon

zum
zum
zum
zum
zum

1.
2.
3.
4.
5.

Mal
Mal
Mal
Mal
Mal

auf)
auf)
auf)
auf)
auf)

Das Schlsselwort static bewirkt, dass zwischen den wiederholten Aufrufen


der Funktion hallo der Speicherplatz samt Inhalt fr die Variable wieoft erhalten bleibt, obwohl wieoft auerhalb der Funktion gar nicht sichtbar ist.
Unbedingt ausprobieren! Wenn du das static weglsst, wird bei
jedem Aufruf der Funktion hallo in int wieoft = 1; die Variable
wieoft erneut auf 1 gesetzt. Die Ausgabe ist
Hallo!
Hallo!
Hallo!
Hallo!
Hallo!

(jetzt
(jetzt
(jetzt
(jetzt
(jetzt

ruft
ruft
ruft
ruft
ruft

der
der
der
der
der

mich
mich
mich
mich
mich

schon
schon
schon
schon
schon

zum
zum
zum
zum
zum

1.
1.
1.
1.
1.

Mal
Mal
Mal
Mal
Mal

auf)
auf)
auf)
auf)
auf)

7.8 Statische und automatische Variablen

177

Sandini Bib

und das ist nicht, was wir wollen. Der Witz ist, dass bei static int wieoft = 1;
die Initialisierung nur beim ersten Mal geschieht und sich die Funktion hallo
so den letzten Wert von wieoft von Aufruf zu Aufruf merken kann.
Anders ausgedrckt: Lokale Variablen sind normalerweise automatisch. Das ist
der Default. Beim Eintritt in die Funktion werden sie angelegt, beim Verlassen
verschwinden sie wieder. Das kann man mit static fr lokale Variablen verhindern und gleichzeitig diese Variable vor der Auenwelt verstecken.
Externe Variable sind auf jeden Fall statisch. Sie werden beim Start des Programms angelegt und sind unabhngig vom Eintritt oder Verlassen irgendwelcher Funktionen verfgbar. Das kannst du mit der folgenden Variante von unserem Beispiel testen, in dem ebenfalls korrekt gezhlt wird:
VarStatic1.c

#include <stdio.h>
int wieoft = 1;
void hallo(void)
{
printf("Hallo! (jetzt ruft der mich schon zum %d. Mal auf)\n",
wieoft++);
}
int main()
{
int i;
for (i = 0; i < 5; i++)
hallo();
getchar();
return 0;
}

Hier ist wieoft eine externe Variable. Diese erhlt einmal vor Beginn des Progamms Speicherplatz zugewiesen, der nur einmal mit 1 initialisiert wird. Wie
schon besprochen ist es jedoch normalerweise eine gute Idee, Variablen in Funktionen von der Auenwelt abzuschirmen, und deshalb gibt dir C mit static die
Mglichkeit, innerhalb von Funktionen den Wert von Variablen zwischen den
Funktionsaufrufen zu erhalten.
In Kapitel 2.5 habe ich betont, dass nach der Definition, aber vor der Initialisierung von automatischen Variablen diese Variable Mll enthalten. Externe und
statische interne Variable erhalten aber immer den Anfangswert 0. Trotzdem
neige ich dazu, ausdrcklich static int wieoft = 0; zu schreiben, wenn ein
Programm diesen Anfangswert tatschlich bentigt. Dann sehe ich auf einen
Blick, dass die Initialisierung hier und nicht anderswo im Programm erfolgt.
178

Kapitel 7 Funktionen

Sandini Bib

7.9 Statische Funktionen und externe Variablen


Bevor wir uns den Beispielen zuwenden, will ich der Vollstndigkeit halber noch
eine weitere Verwendung des Schlsselwortes static besprechen. Verwirrenderweise wird static nicht nur bei statischen und automatischen Variablen verwendet, sondern auch, wenn Variablen und Funktionen ber mehrere Dateien
verteilt sind (siehe Kapitel 7.5). Funktionen, die in einer anderen Datei definiert
werden, werden durch ihren Prototyp sichtbar gemacht. Wenn
void hallo(void);

in einer Datei steht, kannst du die Funktion hallo aufrufen, auch wenn hallo
in einer anderen Datei definiert wurde. Um auf eine externe Variable in einer
anderen Dateien zuzugreifen, schreibst du z. B.
extern int wieoft;

Das Schlsselwort extern gibt an, dass die nachfolgende Variable auerhalb dieser Datei definiert wird. Mit extern kann also zweierlei gemeint sein: auerhalb
einer Datei und auerhalb von Funktionen. Die Variable wieoft muss als externe Variable auerhalb von Funktionen in einer der Dateien des Programms
definiert sein, damit man sie mit dem Schsselwort extern in anderen Dateien
sichtbar machen kann.
extern int wieoft; fhrt lediglich Namen und Typ der Variable ein, stellt
aber keinen Speicherplatz bereit. In diesem Fall spricht man nicht von einer Definition, sondern von der Deklaration der Variablen wieoft. Das entspricht genau dem Zweck eines Prototypen von Funktionen. Wie Prototypen kann man
alle extern-Deklarationen in einer Headerdatei sammeln. So erhlt man externe
Variablen, die im gesamten Programm sichtbar sind.

Wenn eine externe Variable oder eine Funktion nicht in anderen Dateien sichtbar
sein soll, benutzt man in ihrer Definition das Schlsselwort static:
static void hallo(void);

teilt dem Compiler mit, dass der Funktionsname hallo nur in dieser Datei gelten
soll. Das ist praktisch, wenn man in verschiedenen Programmteilen den Funktionsnamen hallo fr verschiedene Aufgaben verwenden mchte. Um verschiedene Versionen einer Variablen wieoft in mehreren Dateien verwenden zu knnen, definiert man eine externe Variable auerhalb der Funktionen mit
static int wieoft;

Bei lokalen Variablen wird static verwendet, um den Inhalt von Variablen zwischen Funktionsaufrufen zu erhalten. Jede externe Variable ist in diesem Sinne
statisch, aber fr externe Variable gibt es genau wie fr Funktionen eine andere
Verwendung des Schlsselwortes static, die die Sichtbarkeit auf die jeweilige
Datei beschrnkt.
7.9 Statische Funktionen und externe Variablen

179

Sandini Bib

7.10

Zufallszahlen selbst gemacht

In Kapitel 5.7 habe ich die etwas paradoxe Idee diskutiert, dass dein beraus
zuverlssiger Computer scheinbar zufllige Zahlenfolgen ausspucken kann. Die
Funktion rand haben wir jetzt schon einige Male verwendet, jetzt gehen wir ins
technische Detail. Die Funktion rand und ihre Artgenossen berechnen Pseudozufallszahlen mit folgender Formel (oder einer ihrer Varianten):
zzneu = (a * zzalt + c) % m

Eine neue Pseudozufallszahl zzneu wird aus der vorhergehenden Zufallszahl


zzalt nach immer derselben Vorschrift berechnet. Wir multiplizieren mit a und
addieren c. Als Ergebnis verwenden wir den Rest, den die Division durch m ergibt.
Du kannst dir sicher vorstellen, dass bei krummen Zahlen das Ergebnis auch
krumm ist. Beachte, dass wegen % m die Zahlen immer kleiner als m sind. Eine
Eigenschaft der Methode ist, dass sich die Zahlen sptestens nach m Schritten
wiederholen, d.h. die Periode ist maximal m. Jede Zahl von 0 bis m1 kommt
dann genau einmal vor.
Eine selbst gemachte Funktion fr Zufallszahlen ist ein schnes Beispiel fr die
Verwendung von Funktionen:
Zufall5.c

#include <stdio.h>
int zz(void)
{
static int zufallszahl = 0;
zufallszahl = (106 * zufallszahl + 1283) % 6075;
return zufallszahl;
}
int main()
{
printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
printf("%d\n",
getchar();
return 0;
}

180

zz());
zz());
zz());
zz());
zz());

Kapitel 7 Funktionen

Sandini Bib

1283
3631
3444
1847
2665

Das Schne ist, dass die ganze Rechnerei fr die Zufallszahlen in der Funktion
zz versteckt und verpackt ist. In einem lngeren Programm wrde es nur vom
Zweck der Zufallszahlen ablenken, wenn du jedes Mal diese Formel einsetzen
wrdest.
Die Funktion zz liefert bei jedem Aufruf eine neue Zahl. Diese Zahl wird in einer
statischen Variable zufallszahl lokal gespeichert, damit beim nchsten Aufruf
die nchste Zahl daraus berechnet werden kann. Das erledigen wir in einer Zeile:
zufallszahl = (106 * zufallszahl + 1283) % 6075;

Also ist hier a = 106, c = 1283 und m = 6075. Erst wird der Wert von
zufallszahl aus dem Speicher geholt, dann wird gerechnet, dann das Ergebnis
wieder in zufallszahl gespeichert (genau wie bei i = i + 1). Zum Experimentieren kannst du das static entfernen oder auch das % 6075 weglassen.
Die schwierige Arbeit ist, gute Werte a, c und m zu finden. Z.B. mssen wir einen
Overflow vermeiden. (Ganze Zahlen drfen nicht beliebig gro sein, Kapitel 2.9.)
Wenn du mchtest, kannst du die folgenden Zahlen ausprobieren:
a

106
211
8121
4561

1283
1663
28 411
51 349

6075
7875
134 456
243 000

Oder einfach selbst welche erfinden und die Formel mit kleineren Zahlen testen.
Eine Warnung: Ein solcher Generator liefert erst dann richtig gute
Ergebnisse, wenn man die berechneten Zufallszahlen noch zustzlich
untereinander vertauscht oder mehrere Generatoren kombiniert.
Insbesondere solltest du in unserem Beispiel zz()%6 nicht verwenden, weil
diese Zahlen nicht besonders zufllig sind. Deshalb werden wir die Funktion
rand aus der Standardbibliothek von C verwenden, die normalerweise bessere
Pseudozufallseigenschaften hat.

7.11

Rekursion

Wenn eine Funktion sich selbst (!) aufruft, spricht man von Rekursion. Eine
kleine Aufgabe, fr die sich das anbietet, ist die Berechnung von Fakultten. An7.11

Rekursion

181

Sandini Bib

genommen, du fragst dich, wie viele Mglichkeiten es gibt, 5 Geburtstagsgste


auf 5 Sthle zu verteilen. Die Antwort erhltst du, wenn du dir vorstellst, die
Gste einen nach dem anderen auf einen Stuhl zu setzen. Beim ersten Gast hast
du 5 Mglichkeiten. Beim zweiten Gast 4, denn ein Stuhl ist ja schon besetzt.
Insgesamt macht das 5 4 = 20 Mglichkeiten. Der dritte Gast hat noch 3 Mglichkeiten, macht 20 3 = 60. Dann rechnest du 60 2 = 120 und schlielich
120 1 = 120 fr den letzten Gast. Du hast also alle Zahlen von 5 bis 1 miteinander malgenommen. Dieses Produkt nennt man 5 Fakultt und schreibt in der
Mathematik 5!. Im Allgemeinen gilt also
n! = n (n 1) . . . 1.

Genauso gut kannst du definieren, dass


n! = n (n 1)! falls n > 1, und
1! = 1.

Dabei wird ausgenutzt, dass man, wenn die Fakultt fr die um 1 kleinere Zahl
n 1 schon bekannt ist, nur noch mit n malnehmen muss. Diese Definition
nennt man rekursiv, weil man in der Definition das Objekt verwendet, das man
eigentlich definieren mchte (nmlich die Fakultt von n 1). Das wre Quatsch
und wrde unendlich immer so weitergehen, wenn die Rekursion nicht durch
die Bedingung 1! = 1 beendet wrde.
Hier ist ein Programm, das die Fakultt fr alle Zahlen von 1 bis 13 berechnet
(einen Fakulttsoperator ! gibt es in C nicht):
Fakultaet0.c

#include <stdio.h>
int fakultaet(int n);
int main()
{
int n;
printf(" n n!\n");
for (n = 1; n <= 12; n++)
printf("%2d %d\n", n, fakultaet(n));
getchar();
return 0;
}
int fakultaet(int n)
{
int i, ergebnis = 1;
for (i = 1; i <= n; i++)
ergebnis *= i;
return ergebnis;
}

182

Kapitel 7 Funktionen

Sandini Bib

n
1
2
3
4
5
6
7
8
9
10
11
12

n!
1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600

Dies ist die nicht-rekursive Version. Die Funktion fakultaet enthlt eine
Schleife, mit der wir alle Zahlen von 1 bis n miteinander malnehmen. Nichts
Besonderes.
Rekursiv wird es mit:
Fakultaet1.c

int fakultaet(int n)
{
if (n > 1)
return n * fakultaet(n1);
return 1;
}

Wenn du diese Funktion anstatt der mit der Schleife verwendest, erhltst du
dasselbe Ergebnis. Falls n grer als 1 ist, ruft sich die Funktion fakultaet mit
n1 als Argument selber auf und kehrt mit n * fakultaet(n1) zurck. Bei
jedem Aufruf von fakultaet wird eine neue Variable n angelegt, die eine um 1
kleinere Zahl enthlt. Ausprobieren, setze
printf("%d\n", n);

als ersten Befehl in die Funktion fakultaet.


Die Bedingung if (n > 1) verhindert, dass die Funktion sich unendlich oft
selbst aufruft. Sobald n gleich 1 ist, ruft sich die Funktion nicht noch mal selbst
auf, sondern kehrt mit 1 zurck. Beachte, dass wir in diesem Beispiel die Funktion fakultaet an zwei verschiedenen Stellen mit return und unterschiedlichen Rckgabewerten verlassen.
Wenn wir von vornherein eine Tabelle von 1! bis 12! berechnen wollen, sollten
wir das printf in die Funktion fakultaet einbauen. Sonst multiplizieren wir
die Zahlen fter miteinander als ntig (wie kommt das?). ndere das Programm
entsprechend um. Warum hren wir bei 12! auf? Ausprobieren, diese Funktion
7.11

Rekursion

183

Sandini Bib

explodiert schneller als jede Exponentialfunktion. Z.B. wird bei 2n immer wieder
mit 2 multipliziert, bei n! kommen mit greren n immer grere Multiplikatoren hinzu. Und irgendwann kommt es zum Overflow der Integervariablen.
Auch bei Rekursionen hilft es oft, ein paar Durchgnge mit dem Debugger durchzufhren, um die einzelnen Schritte genau zu verfolgen. Um zu erreichen, dass
ein bestimmtes Zwischenergebnis vom Debugger angezeigt wird, kann man falls
ntig einfach eine zustzliche Variable einfhren, wie in
Fakultaet2.c

int fakultaet(int n)
{
int ergebnis;
if (n > 1)
ergebnis = n * fakultaet(n1);
else
ergebnis = 1;
return ergebnis;
}

Und wieder brauchen wir uns um die mehrfache Verwendung einer Variablen wie
ergebnis keine Gedanken zu machen. Bei jedem Aufruf der Funktion wird eine
neue lokale Version angelegt und das Programm speichert alle diese Variablen
getrennt ab.

7.12

Rekursion mit Grafik

Hier ist ein Beispiel fr Rekursion mit Grafik. Die Idee ist, dass wir ein groes
Rechteck zeichnen wollen und auf jede Ecke ein kleineres Rechteck und auf jede
Ecke der kleinen Rechtecke noch kleinere Rechtecke und so weiter. Nahe einer
Ecke kann das Ergebnis so aussehen:

184

Kapitel 7 Funktionen

Sandini Bib

Du siehst ein Rechteck in der Mitte und 4 kleinere Rechtecke, die ich mit ihren
Mittelpunkten auf die Ecken des ersten Rechtecks gesetzt habe. Wenn wir das
fnfmal so machen, also mit einem groen Rechteck anfangen und dann noch
viermal verkleinern, sieht das z. B. so aus:

Schau dir das Bild an und folge der Konstruktion in Gedanken. Fange mit der
linken oberen Ecke des grten Rechtecks an, gehe zum nchstkleineren links
oben, dann wieder zum nchstkleineren, bis du den kleinen Ausschnitt findest,
den ich als Erstes gezeigt habe. Nett, und so schn regelmig, und diese Farben
... .
Hier ist das Programm:

7.12

Rekursion mit Grafik

185

Sandini Bib
RekursionsRechteck.c WinHallo.cpp

#include <windows.h>
void rechtecke(HDC hdc, int n, int x, int y, int dx, int dy)
{
int dxneu, dyneu;
HBRUSH hbrush, hbalt;
hbrush = CreateSolidBrush(RGB(29540*n,0,30*n));
hbalt = SelectObject(hdc, hbrush);
Rectangle(hdc, xdx, ydy, x+dx, y+dy);
DeleteObject(SelectObject(hdc, hbalt));
n = n 1;
if (n == 0) return;
dxneu = 5*dx/10;
dyneu = 5*dy/10;
rechtecke(hdc,
rechtecke(hdc,
rechtecke(hdc,
rechtecke(hdc,

n,
n,
n,
n,

xdx,
x+dx,
xdx,
x+dx,

ydy,
ydy,
y+dy,
y+dy,

dxneu,
dxneu,
dxneu,
dxneu,

dyneu);
dyneu);
dyneu);
dyneu);

}
void malen(HDC hdc)
{
int n = 5;
int xmax = 1024;
int ymax = 768;
rechtecke(hdc, n, xmax/2, ymax/2, xmax/4, ymax/4);
}

Wie gewhnlich beginnt das Zeichnen mit der Funktion malen. Die Variable n
gibt die Anzahl der Rekursionen an, die ausgefhrt werden sollen. Die Gre
meines Fensters habe ich mit xmax und ymax angegeben. Dann geht es los.
Wir rufen die selbst gemachte Funktion rechtecke auf, die weiter oben in der
Datei steht. Wie du sofort siehst, ruft rechtecke einmal die Zeichenfunktion
Rectangle auf und genau viermal sich selbst. Der Aufruf von Rectangle ist
Rectangle(hdc, xdx, ydy, x+dx, y+dy);

also wird ein Rechteck mit Mittelpunktkoordinaten x und y und Kantenlngen,


die das Doppelte von jeweils dx und dy sind, gezeichnet. Diese Variablen wurden
direkt an rechtecke bergeben. Das heit unsere Funktion rechtecke zeichnet
als Erstes ein Rechteck. Die Farbe hngt von n ab. Dann wird n um eins erniedrigt.
Wenn n gleich 0 ist, wars das. Ein Rechteck wurde mit Rectangle gezeichnet
und die Funktion wird mit return verlassen.
186

Kapitel 7 Funktionen

Sandini Bib

Falls aber n nicht 0 ist, ruft sich rechtecke selber auf, nur diesmal mit den
Koordinaten der Ecken des momentanen Rechtecks. Die Kantenlngen machen
wir aber kleiner, damit die Rechtecke sich nicht berlappen. Ausprobieren, wie
sieht das Bild fr eine Verkleinerung um 3/10, 4/10, 6/10, 7/10 aus?
Beim zweiten Aufruf von rechtecke muss die Funktion nichts von all dem wissen, was vorher passiert ist. Die Funktion erhlt einen neuen Satz von Parametern n, x, y, dx und dy (hdc hat sich nicht gendert) und wei genau, was zu tun
ist. Bei jedem Aufruf ist n um eins kleiner, also wird die Rekursion irgendwann
abbrechen.

7.13

Labyrinth

In diesem Kapitel wollen wir ein Programm schreiben, das ein Labyrinth baut.
Das Endergebnis wird z. B. so aussehen:
######################################
##......##..##......................##
##..######..######################..##
##..##..............##..##..##..##..##
##..##############..##..##..##..##..##
##......##..##......##..##......##..##
##..######..##..##..##..######..##..##
##......##..##..##......##..##..##..##
######..##..##########..##..##..##..##
##......##..##..##..........##..##..##
######..##..##..##########..##..##..##
##..##......##......##..##..##..##..##
##..######..######..##..##..##..##..##
##......##......##..##..##..##..##..##
######..######..##..##..##..##..##..##
##......##..........##......##......##
######..##########..######..######..##
##..............................##..##
##################..##########..##..##
##..##..............##......##..##..##
##..######..##############..##..##..##
##..........##........................
######################################

Oder so, denn wir verwenden den Zufallsgenerator:

7.13

Labyrinth

187

Sandini Bib

######################################
##..##......##..............##......##
##..######..##############..##..######
##..##......##......##..........##..##
##..######..######..######..######..##
##..........##..##..................##
##########..##..######..##############
##......##......##..........##......##
##..##########..######..######..######
##..##..##..##..............##......##
##..##..##..##########..##########..##
##......##..........##......##......##
######..##########..##..##########..##
##..........##..............##......##
##########..##############..######..##
##..##......##..##..##..............##
##..######..##..##..##############..##
##..##..##..........................##
##..##..##########################..##
##..................................##
##..######..##########..##..######..##
##......##..##..........##..##........
######################################

Die Gnge sind durch . und die Wnde durch # dargestellt. Natrlich liegt es
nahe, statt der Textdarstellung in einem Grafikfenster die Wnde als Rechtecke
zu zeichnen, das Ganze mit Monstern und Schtzen zu fllen und einen Abenteurer auf die Reise zu schicken. Aber in diesem Beispiel wollen wir uns nicht
auf Grafik, sondern auf das Programmieren mit Funktionen konzentrieren.
Das Besondere an unserem Labyrinth ist, dass der gesamte Platz in einem Rechteck ausgentzt wird und es von jedem Raum aus nur genau einen Weg zum
Ausgang gibt!
Denke einmal darber nach, wie der Computer das anstellen knnte. Das Problem ist, dass wir nicht einfach Rume und Gnge zufllig anlegen knnen.
Wenn du auf einem Blatt Karopapier einfach einmal zufllig Linien zeichnest,
die Gnge sein sollen, werden sich die Gnge schneiden, und Abkrzungen wollen wir ja nicht erlauben. Oder es gibt Gnge, die berhaupt keine Verbindung
zum Ausgang haben! Angenommen, ein Abenteurer soll in unserem Labyrinth
nach einem Schatz suchen, dann kann die Aufgabe entweder zu leicht oder vllig
unmglich sein.
Machen wir uns also an die Arbeit bzw. das Vergngen, dem Computer das durchdachte Labyrinthebasteln beizubringen.

188

Kapitel 7 Funktionen

Sandini Bib

Als Erstes denken wir uns eine Mglichkeit aus, das Labyrinth zu speichern. Wir
entscheiden uns zunchst einmal, rechteckige Labyrinthe zu bauen. Bei Rechteck
fllt uns ein, dass diese sehr einfach in zweidimensionalen Feldern gespeichert
werden knnen. Wie wre es mit
int labyrinth[100][100];

Der Platz sollte frs Erste reichen. Jedes Feld kann dann mit Indizes i und j
als labyrinth[j][i] angesprochen werden, wobei j die Zeile und i die Spalte
angibt.
Zwar knnten wir fr jedes Feld vier Wnde definieren und diese extra abspeichern, aber der Einfachheit halber beschlieen wir, Wnde genauso wie Felder zu
behandeln. Wir stellen uns vor, dass in einen Berg Gnge gegraben werden und
dass die Wnde genauso dick wie die Gnge sind. Wie speichern wir Gnge und
Wnde im Feld labyrinth ab? Lass uns vereinbaren, dass 0 fr Gang und 1 fr
Wand steht.
Diese Idee gefllt uns:
1. Wir fangen mit einem rechteckigen Berg ohne Gnge an, d.h. wir schreiben

1 in das Feld.
2. Dann graben wir Gnge irgendwie zufllig, d.h. das Programm macht aus

einer 1 eine 0, wenn es einen Gang gegraben hat.


3. Am Schluss zeigen wir das Labyrinth, und damit wir Gnge besser von Felsen
unterscheiden knnen, geben wir statt 0 und 1 die Zeichen # und . aus, siehe

oben.
Guter Plan.
Ein solches Programm schreibe ich nicht in einem Rutsch, d.h. ich tippe nicht
einfach alle Funktionen und Variablen ein, die ich zu brauchen meine, sondern
als Erstes erstelle ich ein Skelett. Darin lasse ich vieles aus, aber trotzdem soll es
ein lauffhiges Programm sein. Wenn du in kleinen Schritten vorgehst, vermeidest du, dass dir zum Schluss dutzende kleine Fehler beim Debuggen das Leben
schwer machen.
Hier ist ein Anfang:

7.13

Labyrinth

189

Sandini Bib
Labyrinth0.c

/* Labyrinth0.c
Erzeuge ein Labyrinth durch zufaelliges Graben.
Jeder Raum soll nur auf eine Weise mit dem Ausgang verbunden sein.
*/
/* Header */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Prototypen */
int zz(int n);
void initlab(void);
void baulab(void);
void zeiglab(void);
int graben(int i, int j);
/* Definitionen fuer Feldertypen */
#define GANG
0
#define FELSEN
1
#define AUSSENWAND 1
/* externe Variable */
int labyrinth[100][100];
int imax = 10;
int jmax = 12;

// ein Array, Eintraege fuer Gang, Felsen, Wand


// Anzahl Raeume in x Richtung
// Anzahl Raeume in y Richtung

/* main */
int main()
{
/* wiederholen solange nur auf Eingabe gedrueckt wird */
while (1) {
initlab();
baulab();
zeiglab();
if (getchar() != \n) break;
}
return 0;
}

/* unfertig */
void initlab(void){}
void zeiglab(void){}
void baulab(void){}

Als Erstes lesen wir alle bentigten Headerdateien, dann geben wir die Prototypen unserer selbst geschriebenen Funktionen an. Die Funktion zz soll Zufallszahlen berechnen, also brauchen wir stdlib.h und time.h. Fr 1., 2. und 3. in
unserem Plan haben wir jeweils eine Funktion eingefhrt: initlab fr initialisiere Labyrinth, baulab fr baue Labyrinth und zeiglab fr zeige Labyrinth.
190

Kapitel 7 Funktionen

Sandini Bib

Damit ich in dieser Beschreibung nicht zu oft sagen muss, und jetzt bitte da und
dort einfgen, taucht hier gleich ein Prototyp graben auf, den wir spter bentigen. Gewhnlich wirst du nach und nach fr jede neue Funktion wie bauen
oder zz die Prototypen an dieser Stelle eintragen.
Dann kommt eine Reihe von Definitionen. Damit wir nicht zwischen 0 und 1
fr Gang und Felsen durcheinander kommen, definieren wir zunchst GANG und
FELSEN:
#define GANG 0
#define FELSEN 1
#define AUSSENWAND 1

Spter stellt sich heraus, dass es praktisch ist, zustzlich einen Felsentyp namens
Auenwand zu verwenden. Es folgen die Variablen
int labyrinth[100][100];
int imax = 10;
int jmax = 12;

Wir verwenden externe Variable, damit alle Funktionen auf labyrinth zugreifen
knnen, ohne dass dieses Feld bergeben werden muss. Wir definieren ein ausreichend groes Feld labyrinth und verwenden die Variablen imax und jmax, um
die tatschliche Gre des Labyrinths anzugeben. Das ist nicht gerade elegant,
soll uns aber reichen.
Es folgt die Definition von main. In main bauen wir gleich eine einfache Schleife
ein, damit wir einfach nur drcken mssen, um ein neues Labyrinth zu erzeugen. In der Schleife wiederholen wir immer wieder die drei Schritte, in die
wir unser Problem zerlegt haben:
initlab();
baulab();
zeiglab();

Durch die Verwendung von Funktionen ist main schn bersichtlich geblieben.
Es folgt fr jede dieser Funktionen ein Dummy, und fertig ist das Skelett fr die
erste Runde Debuggen. Eintippen (oder von der CD holen) und ausprobieren!
Noch passiert natrlich nicht viel beim Ausfhren des Programms. Aber du hast
jetzt alle Tippfehler beseitigt, und du kannst dich davon berzeugen, dass die
Eingabetaste eine neue Zeile gibt und dass q das Programm beendet.
Als Nchstes beschlieen wir, eine erste Version von initlab und zeiglab zu
schreiben. Wir erinnern uns an das doppelte Schleifchen in 6.9 und schreiben

7.13

Labyrinth

191

Sandini Bib
Labyrinth1a.c

/* Initialisiere Labyrinth:
setze alle Felder auf FELSEN
*/
void initlab(void)
{
int i, j;
/* doppelte Schleife ueber alle Felder */
for (j = 0; j < jmax; j++)
for (i = 0; i < imax; i++)
labyrinth[j][i] = FELSEN;
}

Nichts Besonderes, auer dass wir beschlossen haben, wie in der folgenden Ausgabeschleife in der Funktion zeiglab die Indizes in der Reihenfolge [j][i] zu
verwenden:
Labyrinth1b.c

/* Zeige das Labyrinth mit printf */


void zeiglab(void)
{
int i, j;
printf("\n");
for (j = 0; j < jmax; j++) {
printf("
");
for (i = 0; i < imax; i++) {
if (labyrinth[j][i] == FELSEN)
printf("#");
else
printf(".");
}
printf("\n");
}

// fuer Zeilen
// fuer Spalten
// FELSEN
// GANG

In unser Programm eingebaut (Dummyfunktionen ausfllen) erhalten wir

192

Kapitel 7 Funktionen

Sandini Bib

##########
##########
##########
##########
##########
##########
##########
##########
##########
##########
##########
##########

Wie schn, das erste Anzeichen von intelligentem Leben in unserem Programm!
Weil wir sowieso vorhaben, Zufallszahlen zu verwenden, spendieren wir unserem Programm eine Funktion zz. Und weil wir der Versuchung nicht widerstehen knnen, statische lokale Variablen auszuprobieren, schreiben wir
Labyrinth2a.c

/* Liefere Zufallszahl von 0 bis n1.


Wird beim ersten Aufruf initialisiert.
*/
int zz(int n)
{
static int ersteraufruf = 1;
/* initialisiere Zufallszahlen */
if (ersteraufruf) {
ersteraufruf = 0;
srand(time(0));
}
/* Zufallszahl von 0 bis n1 */
return rand() % n;
}

Der Trick ist, den Generator beim ersten Durchgang mit srand zu initialisieren
und dann mit der statischen lokalen Variable ersteraufruf zu verhindern, dass
srand nochmals aufgerufen wird. Das ist hbsch, weil wir dann alle Funktionen
des Zufallsgenerators und auch time in zz verpackt haben. Der Rest des Programms muss einzig und allein zz kennen. Oft schreibt man den Aufruf von
srand einfach an den Anfang von main, weil das etwas einfacher ist.
Versuche einmal, in inilab mit zz(2) statt FELSEN zu initialisieren:
labyrinth[j][i] = zz(2);

Damit whlst du zufllig 1 (FELSEN) oder 0 (GANG) aus. Das Endergebnis sieht
z. B. so aus:
7.13

Labyrinth

193

Sandini Bib

.#######.#
#....#.##.
#...#.....
.#...###.#
...#.#..#.
.#..#.##.#
#.##...###
##.#..###.
.###.####.
###.##...#
..#...###.
#.#.#..###

Nicht schn, aber selten.


Wir bemerken wie so oft erst beim Ausprobieren etwas, das uns in unserem groben Plan noch nicht so klar war. Schau dir das Zufallsbild an. Wir mssen uns
entscheiden, ob wir schrge und diagonale Gnge zulassen wollen. Einfach so beschlieen wir, ein rechtwinkliges Labyrinth ohne diagonale Gnge zu erzeugen,
wie zu Beginn dieses Unterkapitels eines gezeigt ist.
Jetzt ist es an der Zeit, sich genau zu berlegen, wie die Gnge berhaupt gegraben werden sollen. Nimm ein Blatt Karopapier zur Hilfe. Stell dir vor, wir fangen
auf einem Gangfeld an. Wir graben ein Feld weit in eine zufllig gewhlte Richtung. Dann graben wir noch mal ein Feld weit in eine zufllig gewhlte Richtung.
Wenn wir beim zweiten Schritt links oder rechts abbiegen, gibt es schrge Kanten! Das wre auch nett, aber ich berlasse es dir, diese Variante auszuprobieren.
Dann sind wir gezwungen, mindestens zwei Felder geradeaus zu graben. Und
der Einfachheit halber beschlieen wir, immer in Zweierschritten zu graben. Das
Labyrinth sieht dann in jeder Richtung ungefhr so aus: Gang, Wand, Gang,
Wand, Gang, Wand, . . ., wobei eine Wand natrlich auch abgegraben sein kann.
Im Grunde gibt es also auf jedem zweiten Feld Rume und dazwischen entweder
einen Gang oder eine Wand.
Wenn wir von einem Punkt aus nur Zweierschritte machen, ergibt sich ein regelmiges Gitter. ndere inilab wie folgt. Statt
labyrinth[j][i] = zz(2);

schreiben wir
labyrinth[j][i] = (i%2 || j%2) ? FELSEN : GANG;

berlege mal. Die Bedingung ist wahr, wenn i oder j ungerade sind. Sie ist
falsch, wenn sowohl i als auch j gerade sind. Wir mssten alle zwei Schritte
einen Raum erhalten. Wir erhalten tatschlich:

194

Kapitel 7 Funktionen

Sandini Bib

.#.#.#.#.#
##########
.#.#.#.#.#
##########
.#.#.#.#.#
##########
.#.#.#.#.#
##########
.#.#.#.#.#
##########
.#.#.#.#.#
##########

Das macht alles viel klarer, oder? Wir mssen eigentlich nur noch einige Wnde
entfernen, und schon haben wir ein schnes Labyrinth.
Das ist eine gute Idee, die du auch ausprobieren solltest. Das Problem ist, wie
verhindert man, dass sich Gnge kreuzen? Bevor eine Wand durchbrochen wird,
musst du berprfen, dass es nicht schon einen zweiten Weg zu dem Raum auf
der anderen Seite der Wand gibt.
Wir werden dieses Problem umgehen, indem wir die Rume nicht von Anfang
an, sondern einen nach dem anderen aushhlen. Die Regel ist einfach:
1. Fange mit einem Labyrinth mit nur einem ausgehhlten Raum an. Dieser

Raum ist unsere Startposition.


2. Grabe zwei Felder geradeaus in eine zufllig gewhlte Richtung, aber nur

dann, wenn der Raum auf der anderen Seite des Wandfeldes noch aus Felsen
besteht.
3. Whle einen der vorhandenen ausgehhlten Rume zufllig aus und fahre

mit 2. fort, bis alle Rume hohl sind.


Punkt 2 stellt sicher, dass sich unsere Gnge niemals kreuzen. Vom ersten Raum
ausgehend, wuchern sie zufllig in alle Richtungen, aber Kreuzungen kann es
wegen 2 nicht geben. Also ist garantiert, dass es zu jedem Punkt nur einen Weg
gibt.
Wie verhindern wir, dass wir beim Graben die Auenwand des Labyrinths durchbrechen? Wir knnten bei jedem Schritt testen, ob einer der Indizes i und j zu
gro oder zu klein wird. Etwas einfacher ist es, den Feldtyp AUSSENWAND zu erfinden. Das soll ein FELSEN sein, der nicht abgegraben werden kann. Hier ist die
entgltige Version von inilab:

7.13

Labyrinth

195

Sandini Bib
Labyrinth3a.c

/* Initialisiere Labyrinth:
setze auessere Felder auf AUSSENWAND
innere Felder auf FELSEN
setze drei Felder unten rechts auf GANG als Eingang
*/
void initlab(void)
{
int i, j;
/* doppelte Schleife ueber alle Felder */
for (j = 0; j <= jmax; j++)
for (i = 0; i <= imax; i++)
/* ein minimaler oder maximaler Index bedeutet Aussenwand */
if (i == 0 || i == imax || j == 0 || j == jmax)
labyrinth[j][i] = AUSSENWAND;
/* ansonsten sind wir im Inneren */
else
labyrinth[j][i] = FELSEN;
/* lege einen Zugang an */
for (i = 0; i <= 2; i++)
labyrinth[jmax2][imaxi] = GANG;
}

Wir graben nach der Initialisierung mit FELSEN und AUSSENWAND einen Zugang,
von dem aus die Gnge sich ausbreiten sollen. Ausgabe mit
Labyrinth3b.c

/* Zeige das Labyrinth mit printf.


*/
void zeiglab(void)
{
int i, j;
printf("\n");
for (j = 0; j <= jmax; j++) {
printf("
");
for (i = 0; i <= imax; i++) {
if (labyrinth[j][i] == FELSEN)
printf("#");
else if (labyrinth[j][i] == GANG)
printf(".");
else
printf("@");
}
printf("\n");
}
}

196

Kapitel 7 Funktionen

// fuer Zeilen
// fuer Spalten
// FELSEN
// GANG
// AUSSENWAND

Sandini Bib

ergibt
@@@@@@@@@@@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#######...
@#########@
@@@@@@@@@@@

Wir stehen in den Startlchern, lasst das Graben beginnen! Der Plan ist, einen
ausgehhlten Raum zufllig auszuwhlen und von dort aus zwei Felder weit zu
graben. Hier ist die Funktion baulab:
Labyrinth4a.c

/* Baue das Labyrinth.


Felder mit geraden i und j sind Raeume.
Felder mit ungeraden i oder j sind Waende.
Es werden so lange Gaenge zufaellig gegraben, bis alle
Raeume ausgehoehlt und verbunden sind.
*/
void baulab(void)
{
int i, j, n;
int ni = imax/2 1;
int nj = jmax/2 1;
int nzugraben = ni * nj 1;
/* solange es was zu graben gibt */
for (n = 0; n < nzugraben;) {
/* finde einen leeren Raum durch Rumprobieren */
do {
i = 2*zz(ni) + 2;
j = 2*zz(nj) + 2;
} while (labyrinth[j][i] != GANG);
/* buddel drauf los, vermerke das Ergebnis */
n += graben(i, j);
}
}

Wir probieren so lange mit zufllig gewhlten Rumen herum, bis wir einen
ausgehhlten finden. Das ist natrlich sehr ineffizient. Schreibe bei Gelegenheit
7.13

Labyrinth

197

Sandini Bib

eine Funktion, die alle unfertigen Rume in einer Liste speichert. Dann kann das
Programm aus dieser Liste einen Raum auswhlen. Nach dem Graben muss der
Raum aus der Liste entfernt werden.
Lies die Funktion baulab genau durch. Das Schwierige ist, die Indizes genau
so zu setzen, dass die Zweierabstnde stimmen. Die innere do Schleife probiert
herum. Wenn ein leerer Raum gefunden wurde, buddeln wir drauf los, aber in
einer Extrafunktion graben, damit wir die bersicht behalten. Der Rckgabewert von graben soll 1 sein, wenn tatschlich ein neuer Gang angelegt und ein
Raum ausgehhlt wurde. Beim Rumprobieren finden wir aber auch Rume, von
denen aus nicht mehr gegraben werden darf! In diesem Fall soll graben 0 liefern.
Die Variable n zhlt, wie viele Rume fertig sind. Die uere for-Schleife testet mit n < nzugraben, ob alle Rume ausgegraben sind. Im Rechteck gibt es
ni*nj Rume, aber wir setzen nzugraben = ni*nj 1;, weil ein Raum als
Startpunkt schon in inilab ausgehhlt wurde.
Diese Indexgymnastik ist nicht ganz einfach. Und es kommt darauf an, sie exakt
richtig hinzubekommen. Du kannst das Beispiel mit Papier und Bleistift durchgehen oder den Debugger laufen lassen, wenn es Probleme gibt. Um baulab zu
testen, schreiben wir eine vorlufige Funktion graben:
Labyrinth4b.c

int graben(int i, int j)


{
if (labyrinth[j][i2] == AUSSENWAND)
return 0;
labyrinth[j][i2] = GANG;
labyrinth[j][i1] = GANG;
return 1;
}

So graben wir nach links, denn labyrinth[j][i2] ist das Feld zwei Schritte
nach links. Als Erstes stellen wir sicher, dass wir nicht die Auenwand durchbrechen. Wenn das Feld zwei Positionen weiter die Auenwand ist, kehren wir
mit 0 zurck. Das bedeutet, nichts gegraben. Ansonsten graben wir den Raum
mit Indizes [j][i2] und die dazwischen liegende Wand mit [j][i1] ab. Das
heit, wir setzen diese Felder auf den Wert GANG. Das Ergebnis ist:

198

Kapitel 7 Funktionen

Sandini Bib

@@@@@@@@@@@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#########@
@#.........
@#########@
@@@@@@@@@@@

Gut! Jetzt fehlt noch zweierlei. Wir wollen die Richtung, in der wir graben, zufllig auswhlen und wir graben nur dann, wenn das Feld zwei Positionen weiter FELSEN ist, also weder GANG noch AUSSENWAND. Unsere endgltige Funktion
graben ist

7.13

Labyrinth

199

Sandini Bib
Labyrinth4c.c

/* Grabe einen Gang von Raum i,j zu einem Nachbarraum.


Es wird in eine zufaellig ausgewaehlte Richtungen gebuddelt.
Wichtig:
Es wird nur gegraben, wenn der Nachbarraum noch voller Felsen ist,
damit keine Querverbindungen entstehen.
return:
0 falls kein neuer Gang und neuer Raum gegraben
1 sonst
*/
int graben(int i, int j)
{
int ineu, jneu;
int richtung;
/* return 0 falls keiner der Nachbarr"aume aus FELSEN */
if (labyrinth[j][i+2] != FELSEN && labyrinth[j+2][i] != FELSEN &&
labyrinth[j][i2] != FELSEN && labyrinth[j2][i] != FELSEN)
return 0;
/* finde eine Richtung mit Felsen durch Rumprobieren
das vorhergehende if stellt sicher, dass es die gibt! */
do {
richtung = zz(4);
if (richtung == 0) {
ineu = i + 2;
jneu = j;
}
if (richtung == 1) {
ineu = i;
jneu = j + 2;
}
if (richtung == 2) {
ineu = i 2;
jneu = j;
}
if (richtung == 3) {
ineu = i;
jneu = j 2;
}
} while (labyrinth[jneu][ineu] != FELSEN);
/* hoehle den neuen Raum aus */
labyrinth[jneu][ineu] = GANG;
/* grabe die Wand ab */
ineu = i + (ineui)/2;
jneu = j + (jneuj)/2;
labyrinth[jneu][ineu] = GANG;
/* Erfolg */
return 1;
}

200

Kapitel 7 Funktionen

Sandini Bib

Als Erstes stellen wir sicher, dass es berhaupt eine Richtung gibt, in die
noch gegraben werden darf. Wenn nicht, return 0. Dann whlen wir so
lange Richtungen zufllig aus, bis wir eine der erlaubten Richtungen mit
labyrinth[jneu][ineu] == FELSEN gefunden haben. Wir graben den Raum,
wir graben die Wand ab und melden den Erfolg mit return 1;. Das Praktische
ist, dass wir alle Aufgaben, die mit verschiedenen Richtungen im Labyrinth
zu tun haben, in bauen versteckt haben. Na ja, fast alle, denn inilab grbt
den Eingang. Jedenfalls mssen wir uns nur an wenigen Stellen mit der etwas
verwirrenden [j+2][i] Rechnerei herumschlagen.
Mit dieser Funktion baulab erhalten wir
@@@@@@@@@@@
@#########@
@#...#.#.#@
@#.#.#.#.#@
@#.#.#...#@
@###.###.#@
@#.....#.#@
@#####.#.#@
@#.#...#.#@
@#.###.#.#@
@#.........
@#########@
@@@@@@@@@@@

Super! Geschafft! Toll!


Eine nette Variante ist, die Zeilen
zeiglab();
getchar();

direkt vor return 1; einzufgen. So wird das Labyrinth nach jedem Grabeschritt angezeigt. Das ist auch beim Debuggen sehr ntzlich. Einfach zustzliche
Ausgabebefehle einbauen, wenn die Schritt-fr-Schritt-Methode im Debugger
nicht weiterhilft.
Als Letztes polieren wir unser Programm noch ein bisschen. Die endgltige Version findest du in Labyrinth.c. Wahrscheinlich sieht die Ausgabe auch bei dir
quadratischer aus, wenn du ## und .. fr die Ausgabe in zeiglab verwendest.
Tu das. Mir gefiel das Ergebnis ohne die Auenwand etwas besser, also habe ich
in zeiglab die Schleifen von 1 bis imax1 bzw. jmax1 laufen lassen.
In unserem Programm gibt es noch zwei versteckte Fehlerquellen, die wir zum
Schluss beseitigen wollen. Das Labyrinth muss eine ungerade Anzahl von Feldern pro Kante haben, damit wir, wenn wir in Zweierschritten graben, nicht die
Auenwandmarkierung berspringen. Weil die typische Schleife in z. B. inilab
von i = 0 bis einschlielich i = imax luft, mssen imax und jmax gerade Zahlen sein. Fge die Zeilen
7.13

Labyrinth

201

Sandini Bib
Labyrinth.c

imax *= 2;
jmax *= 2;
if (imax >= 100 || jmax >= 100) {
printf("imax oder jmax sind zu gross.\n");
return 0;
}

am Anfang von main ein. Wir verdoppeln imax und jmax, damit sie gerade sind.
Wir initialisieren also mit der Anzahl der Rume und arbeiten aber intern mit
der Anzahl der Felder.
Und besonders wichtig: Wir testen, dass diese Werte noch in die gegebene Gre
von int labyrinth[100][100]; passen. Solche Speicherprobleme muss man
auf jeden Fall vermeiden! Jetzt kannst du imax und jmax beliebig initialisieren,
ohne dass das Programm berraschend abstrzt oder in eine Endlosschleife gert.
Blicke einmal an den Anfang dieses Unterkapitels zurck. Nach und nach haben
wir uns an die endgltige Lsung des Problems herangetastet. Erst haben wir allgemein ber Labyrinthe und ein mgliches Datenformat nachgedacht. Dann haben wir ein Skelett mit Dummies fr unfertige Funktionen geschrieben. Schritt
fr Schritt haben wir die fehlenden Programmteile ausgefllt, ausprobiert, umgeschrieben. Oft gab es Entscheidungen zu treffen, z. B. ob wir rechtwinklige
Gnge wollen oder Rume, die grer als ein Feld sind. Beim ersten Durchgang
haben wir die einfachste Lsung gewhlt, statt gleich den optimalen Algorithmus
anzustreben. Und wir hatten Erfolg!

7.14
Die Idee der Funktion macht es mglich, selbst komplizierte Probleme in berschaubare Einheiten zu zerlegen. Dabei werden Befehle zu Gruppen zusammengefasst und gleichzeitig die Sichtbarkeit und Gltigkeitsdauer von Variablen geregelt. Das ist, als ob wir die ganze Zeit mit Bausteinen gespielt haben (printf,
getchar, . . .) und pltzlich herausfinden, dass wir aus einfachen Bausteinen neue
Bausteine zusammenkleben knnen, die innen kompliziert sind, aber auen genauso schn einfach wie die alten. Und dann die Sache mit der Rekursion, dass
wir einen Baustein in sich selbst hineinsetzen knnen nicht nur Zweijhrige
kommen da ins Staunen. Wir fassen zusammen:
Eine Funktion wird im Allgemeinen wie folgt definiert:
Datentyp Funktionsname(Datentyp Variablenname, . . .)
{

Befehle
}

202

Kapitel 7 Funktionen

Sandini Bib

Ihr Prototyp ist


Datentyp Funktionsname(Datentyp Variablenname, . . .);

Eine Funktion wird entweder durch ihre vorangehende Definition oder ihren
Prototyp bekannt gemacht, bevor sie aufgerufen wird.
Die Typbezeichnung void wird verwendet, falls eine Funktion keinen Rckgabewert liefert oder keine Argumente annimmt.
Die Definition einer Variable gilt ab der Zeile, in der die Definition erfolgt,
und zwar
fr den Rest der Datei, falls sie auerhalb einer Funktion definiert wird

(externe Variable),
fr den nachfolgenden Block, falls sie zu Beginn eines Funktionsblocks
definiert wurde oder falls sie als Argument an eine Funktion bergeben
wird (lokale Variable).
Die Definition einer Variablen n kann zeitweise auer Kraft gesetzt werden,
indem eine Variable mit gleichem Namen eingefhrt wird. Bei jeder Verwendung des Namens n wird auf die momentan gltige Variable zugegriffen.
Trotz gleichen Namens wird unterschiedlicher Speicherplatz bereitgestellt.
Lokale Variablen sind normalerweise automatisch, d.h. sie werden bei Aufruf der Funktion angelegt und verschwinden bei Beendigung der Funktion
wieder. Damit eine lokale Variable nicht verschwindet und ihren Wert behlt,
definiert man sie als statische lokale Variable, z. B. static int i;. Externe
Variablen sind bei Programmablauf immer verfgbar, d.h. in diesem Sinne
sind externe Variablen statisch.

7.15
1. Schreibe eine bersichtlichere Version des Zooprogramms in Kapitel 6.8, in-

dem du jedes Tier in seiner eigenen Funktion malst.


2. Schreibe eine bersichtlichere Version der Primzahlprogramme in Kapitel

6.10. Insbesondere kannst du den Primzahltest in einer Funktion verpacken.


So manche Mehrfachschleife kann vereinfacht werden, indem eine innere
Schleife in eine Funktion ausgelagert wird.
3. Die Headerdateien mit den Prototypen fr verschiedene Bibliotheken stehen

bei BCB im Verzeichnis


Borland\CBuilder\Include

Bei mir sind das mehr als 300 Dateien! Wenn du den Textcursor auf eine
Dateibezeichnung im Editor setzt und auf Strg + drckst, erscheint die
Datei im Editor. Hier kannst du mit Strg + F nach einem Prototypen von
getchar oder printf suchen. Diese Headerdateien sind sehr unbersichtlich.
7.15

203

Sandini Bib

4. Wenn du den Prototypen einer Bibliotheksfunktion wissen mchtest, kannst

du die Hilfefunktion aufrufen. Textcursor auf Funktion bewegen, F1 drcken.


Noch wirst du nicht alle Prototypen verstehen knnen, weil wir Zeiger erst
in Kapitel 10 besprechen. Zeiger erkennst du in Prototypen am Sternchen *.
Schau dir einmal die Hilfe zu rand und srand an. Dort findest du Beispiele,
und unter Siehe auch weitere Funktionen, die von Interesse sind.
5. Schreibe eine rekursive Version des Flutfll-Algorithmus (

flood fill).
In einem Grafikfenster soll eine Flche ausgemalt werden, die durch eine bestimmte Farbe begrenzt ist. Gemalt wird mit SetPixel, und mit GetPixel
wird berprft, ob der Rand erreicht wurde. Dazu verwendest du eine Funktion, die mit den Koordinaten eines Pixels aufgerufen wird. Wenn dieses Pixel
die Randfarbe hat, kehrt die Funktion sofort zurck. Wenn das Pixel nicht die
Randfarbe hat, wird es angemalt und die Funktion ruft sich selbst viermal fr
die benachbarten vier Pixel auf. Teste die Funktion in einem Fenster, in das du
ein Rechteck oder etwas Komplizierteres gemalt hast. Was passiert am Rand?

6. Hier sind ein paar Vorschlge fr das Labyrinth-Programm:


Statt in baulab rumzuprobieren, bis ein leerer Raum gefunden ist, verwalte

eine Liste mit leeren Rumen.


Statt Gnge und Rume zu graben, initialisiere mit ausgehhlten Rumen
und entferne die Wnde.
Manchmal ist es zu einfach, den Weg vom Eingang in die linke obere Ecke
zu finden. Grabe in zwei Stufen. In der ersten Stufe blockierst du das Zentrum (oder sonstwo), indem du einige Felder auf AUSSENWAND setzt. Wenn
drumherum alles ausgegraben ist, gebe das Zentrum frei, indem du wieder
auf FELSEN umstellst. So erhltst du im Zentrum lauter Sackgassen, die nicht
nach links oben fhren.
Schreibe eine Funktion, die den Weg von rechts unten nach links oben findet. Mit WEG oder so kann er von zeiglab ausgegeben werden. Eine simple
Regel ist: 1. Bei Abzweigungen immer rechts halten. 2. Falls Sackgasse, um
180 Grad drehen. Solche Schatzsucher kannst du hereinlegen, indem du nach
Erzeugung des Labyrinths ein paar Wnde zustzlich entfernst, so dass die
obige Regel im Kreis herumfhren kann. Der Schatzsucher muss dann um
Brotkrumen bzw. Steinchen erweitert werden, die er fallen lsst, um nicht
einen Weg zweimal zu gehen.

204

Kapitel 7 Funktionen

Sandini Bib

8
Fensternachrichten
8.0
8.1
8.2

Achtung, Nachricht: Bitte malen!


Nachrichtenwarteschlage und Nachrichtenschleife
WM PAINT

206
210
211

8.3

Fenster auf, zu, gro, klein

212

8.4

Klick mich links, klick mich rechts

215

8.5

Na, wo luft sie denn, die Maus?

218

8.6

Tastatur

221

8.7

Die Uhr macht tick

227

8.8

233

8.9

234

Sandini Bib

Was wohl mit Fensternachrichten gemeint ist? Mit Fenster meine ich hier die
Fenster, die von einer typischen Windowsanwendung auf deinem Bildschirm aufgemacht werden. Bisher hat sich unsere Handhabung von solchen Fenstern auf
ein absolutes Minimum beschrnkt. Das Fenster wird in WinHallo.cpp erzeugt,
und nur wenn es was zu malen gibt, wird unsere selbst gemachte Funktion malen
aufgerufen.
Das soll jetzt anders werden! Wie man Fenster aufmacht, werden wir in Kapitel
10 besprechen, weil man dazu erst die Sprache der Zeiger und Strukturen in C
lernen muss. Aber in diesem Kapitel werde ich dir einfache Mglichkeiten zeigen,
wie dein Windowsprogramm auf Maus und Tastatur reagieren kann.
Die verschiedenen Ereignisse, auf die ein Fenster reagieren kann, werden als
messages) an eine bestimmte Funktion deines Programms
Nachrichten (
bergeben, die so genannte Fensterprozedur (
windows procedure). Klickst
du mit der Maus in dein Fenster, wird deine Fensterprozedur mit der Nachricht
Maus-geklickt aufgerufen und es werden z. B. auch die Koordinaten des Mauszeigers bermittelt. Taste gedrckt, die Fensterprozedur erhlt die Nachricht Tastegedrckt zusammen mit der Information, welche Taste das war.
Es gibt auch Nachrichten fr Ereignisse, an die du vielleicht nicht im Zusammenhang mit Nachrichten gedacht hast, z. B. wenn sich die Gre deines Fensters
timer) aktivieren, der alle zehnndert. Du kannst auch einen Zeitgeber (
tel Sekunde eine Nachricht verschickt, um zu veranlassen, dass ein Ball 5 Pixel
weiter links gezeichnet wird. Aber wie knnte es anders sein, als Erstes kramen
wir das gute alte Hallo-Welt-Beispiel hervor, um Fensternachrichten Schritt fr
Schritt einzufhren.

8.0 Achtung, Nachricht: Bitte malen!


Fr die Beispiele mit Nachrichten verwenden wir das BCB-Projekt WinMain.mak.
Du findest es im Verzeichnis ProgLernen\Windows auf der Buch-CD.
Starte das Projekt WinMain.mak. Fge die Datei Nachricht0.c zum Projekt
hinzu. Speichere das Projekt und die C-Datei in einem Verzeichnis deiner Wahl.
Drcke F9 , und nach dem erfolgreichen Kompilieren erhltst du

206

Kapitel 8 Fensternachrichten

Sandini Bib

Der Programmtext sieht so aus:


Nachricht0.c WinMain.cpp

#include <windows.h>
/* Male im Fenster */
void malen(HDC hdc)
{
TextOut(hdc, 50, 50, "Hallo, Welt!", 12);
}
/* Fensterprozedur unseres Programms
hwnd:
Handle des Fensters
m:
Nummer der Nachricht
wParam, lParam: verschiedene Parameter je nach Art der Nachricht
*/
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
// Malen
if (m == WM_PAINT) {
hdc = GetDC(hwnd);
malen(hdc);
ReleaseDC(hwnd, hdc);
ValidateRect(hwnd, 0);
}
// Fenster zu
else if (m == WM_DESTROY)
PostQuitMessage(0);
// Default
else
return DefWindowProc(hwnd, m, wParam, lParam);
// falls wir die Nachricht bearbeitet haben
return 0;
}

8.0

Achtung, Nachricht: Bitte malen!

207

Sandini Bib

Am Anfang steht die Funktion malen, wie du sie schon aus vielen Beispielen
kennst. Aber jetzt lften wir das Geheimnis, wie malen aufgerufen wird. In
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)

beginnen wir die Definition der Windows Procedure namens WindowProc.


Die Wrter in Grobuchstaben werden durch #include <windows.h> definiert. Windows gibt sich sehr oft nicht mit den simplen Datentypen in C
zufrieden, sondern definiert alles mgliche fr seine eigenen Zwecke. LRESULT
steht fr eine bestimmte Art von Ergebnis (
result), das die Funktion
WindowProc liefern soll. Es folgt ein zweites Wort, CALLBACK, was ebenfalls mit
Call back heit rufe zurck: Hast
dem Typ der Funktion zu tun hat.
du eine Nachricht? Ruf mich zurck! Deshalb nennt man WindowProc auch die
Rckruffunktion des Fensters.
Die Funktion WindowProc wird mit vier Argumenten aufgerufen. HWND steht fr
einen Fenster-Handle. Auch Fenster haben einen Griff, mit dem sie identifiziert
werden knnen! Das zweite Argument ist die Nachricht, die bearbeitet werden
soll. UINT legt nahe, dass es sich dabei einfach um eine ganze Zahl handelt. Es folgen zwei weitere Argumente, jeweils mit eigenem Typ, die Zusatzinformationen
zur Nachricht enthalten.
Lass dich von den vielen grobuchstabigen Namen und Typen nicht aus der Ruhe
bringen. Fr deine eigenen Programme musst du meistens gar nicht genau wissen, was dahinter steckt, denn du kannst die entsprechenden Variablen blindlings
weiterreichen. Man muss nur wissen, welche Funktion welchen Typ erwartet. So
haben wir es schon die ganze Zeit mit HDC und der Variable hdc gemacht.
Also begeben wir uns frohen Mutes ins Innere des Funktionsblocks. Schau genau
hin, die Funktion besteht im Wesentlichen aus einer Fallunterscheidung, mit der
wir verschiedene Nachrichten sortieren und entsprechend reagieren. In
HDC hdc;
// Malen
if (m == WM_PAINT) {
hdc = GetDC(hwnd);
malen(hdc);
ReleaseDC(hwnd, hdc);
ValidateRect(hwnd, 0);
}

siehst du, wie man mit Nachrichten umgeht. Die Variable m ist die Nummer der
Windows
Nachricht. Mit m == WM_PAINT testen wir, ob wir die Nachricht
Message Paint (Fensternachricht Malen) bekommen haben. Das ist gut lesbar,
weil Windows fr uns eine symbolische Konstante WM_PAINT definiert hat. Es
spielt berhaupt keine Rolle, welcher Zahlenwert sich hinter WM_PAINT versteckt,
du musst nur den Namen kennen.
208

Kapitel 8 Fensternachrichten

Sandini Bib

Das Malen selbst ist einfach. Mit


hdc = GetDC(hwnd);

besorgst du dir den Handle hdc, den du zum Malen brauchst. Dann rufst du
unsere Funktion malen auf, die, wie du aus unseren Beispielen weit, alles mgliche mit hdc anstellen kann. Ich verwende die Funktion malen hier nur, weil wir
seit Kapitel 4 mit dieser Funktion gemalt haben. Ausprobieren, du kannst malen
entfernen und TextOut gleich in der Zeile nach GetDC aufrufen.
Nach dem Malen gibst du den Device Context mit
ReleaseDC(hwnd, hdc);

wieder frei. Dann rufst du


ValidateRect(hwnd, 0);

auf.
validate heit gltig machen. Wir teilen Windows mit, dass wir das
Innere des Fensters auf den neuesten Stand gebracht haben. Falls nur bei der
Nachricht WM_PAINT gemalt werden soll, verwendet man gewhnlich die Funktion BeginPaint, um sich den Device Context zu holen, und EndPaint, um das
Malen zu beenden, siehe die Windows SDK-Hilfe. Mit der gezeigten Methode
knnen wir aber jederzeit ins Fenster malen.
Die Nachricht WM_PAINT wird immer dann erzeugt, wenn Windows mchte, dass
dein Programm den Inhalt seines Fensters ausmalt. Zu bedenken bei Windowsanwendungen ist, dass ein solches WM_PAINT aus ganz verschiedenen Grnden ntig werden kann. Wenn dein Programm den ganzen Bildschirm fr sich htte,
knntest du, wann immer du willst, die Pixel auf dem Bildschirm ndern. In
Windows gibt es aber viele Fenster. Was, wenn ein anderes Fenster deines verdeckt und spter wieder aufdeckt? Was, wenn dein Fenster minimiert oder maximiert wird? Die grundlegende Strategie in Windows ist, dass jedes Programm in
der Lage sein sollte, zu einem beliebigen Zeitpunkt den gesamten Inhalt seines
Fensters neu zu malen, und zwar immer dann, wenn WM_PAINT verschickt wird.
Zurck zum Beispiel. Falls die Nachricht WM_PAINT war, berspringt das Programm nach dem Malen alle anderen Flle in der Fallunterscheidung und kehrt
mit return 0; aus der Funktion WindowProc zurck.
Die zweite Nachricht, auf die wir in unserem Beispiel achten, ist WM_DESTROY
(Fensternachricht Zerstren):
// Fenster zu
else if (m == WM_DESTROY)
PostQuitMessage(0);

Diese Nachricht wird zum Beispiel erzeugt, wenn du ein Fenster mit dem Kreuz
rechts oben schlieen mchtest. Die normale Reaktion auf diese Nachricht ist,
mit PostQuitMessage selbst eine Nachricht zu verschicken, und zwar WM_QUIT.
8.0

Achtung, Nachricht: Bitte malen!

209

Sandini Bib

Wie nett, du kannst dir selbst Nachrichten schicken! Diese Nachricht wird nicht
in WindowProc verarbeitet, fhrt aber dazu, dass das Fenster zugemacht wird,
siehe Kapitel 8.1.
Alle Nachrichten, die wir nicht selbst bearbeiten, mssen wir an die Funktion
DefWindowProc weitergeben:
// Default
else
return DefWindowProc(hwnd, m, wParam, lParam);

Wir rufen DefWindowProc gleich im return auf, um die Funktion WindowProc


mit dem Rckgabewert von DefWindowProc zu verlassen. Das heit entweder
wurde eine Nachricht in der Fallunterscheidung erkannt und WindowProc kehrt
mit return 0; zurck, oder keine Nachricht wurde erkannt und wir berlassen
die Arbeit DefWindowProc.
Du kannst ausprobieren, was passiert, wenn du einen der Flle auskommentierst.
Wenn du WM_PAINT oder WM_DESTROY nicht in der Fallunterscheidung abfngst,
gibt es Probleme, weil DefWindowProc nur wenig automatisch macht. Wenn du
WM_PAINT nicht abfngst, wird wie zu erwarten nichts gemalt.
Wenn du
WM_DESTROY nicht abfngst, geht das Fenster beim Klick aufs Kreuzchen zwar
zu, aber das Programm luft weiter. Das siehst du in der berschrift des BCBFensters. Drcke Strg + F2 , um das Programm zurckzusetzen (oder im Notfall Strg + Alt + Entf , drei Tasten gleichzeitig). Wenn du den Default entfernst,
geht das Fenster erst gar nicht auf, aber das Programm luft trotzdem. Drcke
Strg + F2 .

8.1 Nachrichtenwarteschlage und Nachrichtenschleife


Erinnerst du dich an unsere Diskussion des Tastaturpuffers bei Textbildschirmanwendungen? Dort warten alle eingegebenen Zeichen, bis sie der Reihe nach
abgeholt werden. hnlich ist es mit den Nachrichten einer Windowsanwendung.
Als Erstes ist anzumerken, dass unsere Programme nicht direkt auf Maus oder
Tastatur zugreifen, sondern dass das normalerweise die Aufgabe des Betriebssystems ist, in unserem Fall Windows. Windows verwaltet verschiedene Eingabegerte und verschiedene Fenster und Anwendungen gleichzeitig. Im Normalfall
bekommt ein Fenster nur dann Nachrichten, wenn es das aktive Fenster ist (man
sagt auch, wenn es den Fokus hat). Wenn du ein Fenster anklickst, bekommt es
den Fokus und erhlt z. B. eine andere Farbe im berschriftsbalken.
Alle Nachrichten an eine bestimmte Anwendung werden bei ihrem Eintreffen
message queue) eingereiht. Eine Textin eine Nachrichtenwarteschlange (
bildschirmanwendung erwartet Eingaben von der Tastatur, die dann im besagten
Tastaturpuffer darauf warten, mit getchar oder scanf gelesen zu werden. Jetzt
riskiere einmal einen Blick auf WinMain.cpp. Wie in 4.0 schon erwhnt, beginnt
210

Kapitel 8 Fensternachrichten

Sandini Bib

mit dem Aufruf der Funktion WinMain die Ausfhrung unseres Windowsprogramms. Wenn du einfach nur die Kommentare liest, wird dir klarwerden, dass
allerlei Kleinkram zu erledigen ist, bis ein Fenster aufgemacht wird. Bevor das
Programm mit dem Verlassen von WinMain beendet wird, findest du die folgende
Schleife:
while (GetMessage(&msg, 0, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Das ist die Nachrichtenschleife eines typischen Windowsprogramms. GetMessage


holt eine Nachricht aus der Warteschlange und speichert sie in der Variablen msg
(message). Wie bei scanf steht hier das Und-Zeichen & vor dem Variablennamen (siehe Kapitel 10). Die Schleife wird beendet, wenn der Rckgabewert
von GetMessage gleich 0 ist, und das ist der Fall, wenn die Nachricht WM_QUIT
gelesen wurde.
transIn der Schleife wird mit TranslateMessage die Nachricht bersetzt (
late heit bersetzen). Das ist ntig, falls Tastatureingaben in Zeichen vom Typ
char umgewandelt werden sollen, siehe Beispiel 8.6.
Der Aufruf von DispatchMessage liefert die Nachricht an die zustndige
Fensterprozedur oder Rckruffunktion ab. In unserem Fall fhrt das dazu,
dass WindowProc aufgerufen wird. Im Allgemeinen ist es mglich, dass es
verschiedene Rckruffunktionen fr verschiedene Zwecke gibt, deshalb wird
DispatchMessage als Verteiler und nicht WindowProc direkt aufgerufen. Davon
abgesehen, dass bei einem Windowsprogramm mehr Mglichkeiten der Eingabe
bestehen, funktioniert die Nachrichtenschleife also genauso wie eine typische
Eingabeschleife bei einer Textbildschirmanwendung.

8.2 WM PAINT
Die Nachricht WM_PAINT spielt eine Sonderrolle in der Nachrichtenschlange. Falls
ein Fenster aufgedeckt oder seine Gre gendert wird, verschickt Windows die
Nachricht WM_PAINT nicht auf der Stelle, sondern wartet, bis die Nachrichtenschlange leer ist. Solange noch andere Nachrichten zu bearbeiten sind, merkt
sich Windows, fr welche rechteckigen Teile eines Fensters Malwnsche eingehen. Mit dem Funktionsaufruf
InvalidateRect(hwnd, 0, 0);

kannst du selber veranlassen, dass der ganze Inhalt des Fensters mit Handle hwnd
Invalid heit ungltig.
fr das Malen vorgemerkt wird.
Falls die Nachrichtenschlange leer ist und falls ein Fenster ganz oder teilweise
neu zu malen ist, liefert GetMessage die Nachricht WM_PAINT ab, die wir dann in
8.2 WM PAINT

211

Sandini Bib

WindowProc bearbeiten. In unseren Beispielen kmmern wir uns nicht darum,

ob ganz oder teilweise neu gemalt werden soll, und malen immer das ganze
Fenster. Nach dem Malen erklren wir mit
ValidateRect(hwnd, 0);

den gesamten Fensterinhalt wieder fr gltig. Wenn wir das nicht tun, denkt
Windows, dass noch nicht gemalt wurde, und GetMessage liefert bei leerer Nachrichtenschlange immer wieder ein neues WM_PAINT.
Normalerweise ist es sinnvoll, mit dem Malen zu warten, bis die Nachrichtenschlange leer ist, damit dein Programm vor der Ausgabe auf alle Eingaben reagieren kann. Du hast aber sicher auch schon Fenster gesehen, die einfach wei
bleiben, weil irgendeine Aufgabe unerwartet lange dauert, und die Ausgabe erst
weiter hinten in der Nachrichtenschlange drankommt. Mit
UpdateWindow(hwnd);

kannst du die Nachrichtenschlange umgehen und ein WM_PAINT direkt an deine


Fensterprozedur schicken. UpdateWindow verschickt WM_PAINT aber nur, wenn
die Vormerkliste von Windows nicht leer ist.
In Windows gibt es auer WM_PAINT noch jede Menge andere Nachrichten. Einige besonders interessante Nachrichten werden wir jetzt in mehreren Beispielen
besprechen.

8.3

Fenster auf, zu, gro, klein

Zwei Nachrichten, die mit der Verwaltung von Fenstern zu tun haben, sind
WM_CREATE und WM_SIZE:

212

Kapitel 8 Fensternachrichten

Sandini Bib
Nachricht1.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc;
char text[1000];
int n = 0;
int xmax = 0;
int ymax = 0;
/* Rueckruffunktion unseres Fensters */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Beispiel fuer WM_SIZE");
hdc = GetDC(hwnd);
SetTextAlign(hdc, TA_CENTER);
}
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}
// Malen
else if (m == WM_PAINT) {
sprintf(text, "Aufruf %d: xmax = %d, ymax = %d", n++, xmax, ymax);
TextOut(hdc, xmax/2, ymax/2, text, strlen(text));
ValidateRect(hwnd, 0);
}
// Standard
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Als Erstes beachte, dass wir fnf externe Variablen definieren. Mit der Variable
n zhlen wir, wie oft wir die Nachricht WM_PAINT bekommen haben. Der Wert
8.3

Fenster auf, zu, gro, klein

213

Sandini Bib

in n bleibt wie bei allen externen Variablen unabhngig von Funktionsaufrufen


erhalten. Siehst du das n++? Wenn du die Gre des Fensters nderst, wird in
der Mitte des Fensters die neue Gre ausgegeben.
In WindowProc behandeln wir als Erstes den Fall m == WM_CREATE. Diese Nachricht wird genau einmal bermittelt, und zwar bevor das Fenster zum ersten
Mal gezeichnet werden soll. Hier knnen wir verschiedene Initialisierungen fr
unser Programm vornehmen. Mit
SetWindowText(hwnd, "Beispiel fuer WM_SIZE");

ndern wir die berschrift unseres Fensters, wozu wir hwnd und nicht hdc bentigen. Den Device Context speichern wir bei Beginn des Programms in der
externen Variablen hdc und geben ihn nicht wieder her (kein ReleaseDC). Das
ist unhflich, aber in unserem Beispiel in Ordnung. Mit
SetTextAlign(hdc, TA_CENTER);

legen wir fest, dass sich die Koordinaten in TextOut auf die Mitte des Textes
beziehen sollen.
Die Nachricht WM_DESTROY ist das Gegenstck zu WM_CREATE. Wenn du, bevor das Programm beendet wird, noch aufrumen musst, kannst du das bei
WM_DESTROY erledigen.
Wenn sich die Gre des Fensters ndert, aber auch bevor es zum ersten Mal
angezeigt wird, wird die Nachricht WM_SIZE verschickt:
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}

Hier merken wir uns die neuen Maximalwerte fr die x- und y-Koordinaten des
Anzeigebereichs des Fensters in externen Variablen. Sieht etwas seltsam aus, aber
so musst du mit Hilfe von LOWORD und HIWORD die Variable lParam in xmax und
ymax zerlegen. Windows 3.0 oder noch lter lsst gren. Wir knnten die Nachricht WM_SIZE auch zum Anlass nehmen, das Fenster neu zu zeichnen. Aber das
ist nicht ntig, denn bei unserem Fenster wird bei Grennderungen auch ein
WM_PAINT verschickt. Bei WM_PAINT verwenden wir in TextOut die Koordinaten
xmax/2 und ymax/2, um den Text in die Mitte des Fensters zu zeichnen.
Jetzt kannst du gut ausprobieren, wozu ValidateRect da ist. Weg damit, oder
setze // davor. Das Programm zhlt, was das Zeug hlt. Der Grund ist, dass
WindowProc so lange immer wieder mit WM_PAINT aufgerufen wird, bis dein
Programm mitteilt, dass das Fenster wieder auf dem neuesten Stand ist.

214

Kapitel 8 Fensternachrichten

Sandini Bib

8.4

Klick mich links,


klick mich rechts

Knnen Muse Briefe schreiben? In Windows gibt es z. B. die folgenden Mausnachrichten:


Maustaste

heruntergedrckt

losgelassen

doppelgeklickt

Links
Mitte
Rechts

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MBUTTONDBLCLK

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_RBUTTONDBLCLK

Button heit Knopf oder Taste. Weil nicht jede Maus eine mittlere Taste
hat, werden wir diese Mglichkeit ignorieren. Die Doppelklicknachrichten werden verschickt, wenn die Taste nach kurzer Zeit erneut runtergedrckt wurde.
Hier ist ein erstes Beispiel, zur Abwechslung mit statischen lokalen Variablen:
RasendesHallo.c WinMain.cpp

#include <windows.h>
/* Male ins Fenster */
void malen(HDC hdc, int xmax, int ymax)
{
char text[100] = "Hallo, Welt!";
int x, y;
COLORREF farbe;
x = rand() % xmax 30;
y = rand() % ymax 10;
farbe = RGB(rand()%256, rand()%256, rand()%256);
SetTextColor(hdc, farbe);
TextOut(hdc, x, y, text, strlen(text));
}

Klick mich links, klick mich rechts

215

Sandini Bib
RasendesHallo.c WinMain.cpp

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
static HDC hdc;
static int xmax = 0, ymax = 0;
static int warten = 1;
static int textbkmode = OPAQUE;
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Hallo Zufall: Klick mich links/rechts");
hdc = GetDC(hwnd);
}
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}
// Malen
else if (m == WM_PAINT) {
malen(hdc, xmax, ymax);
if (warten) ValidateRect(hwnd, 0);
}
// Maus
// Linke Maustaste schaltet um zwischen warten und nicht warten
else if (m == WM_LBUTTONDOWN) {
warten = !warten;
InvalidateRect(hwnd, 0, 0);
}
// Rechte Maustaste schaltet Durchsichtigkeit des Texthintergrunds
else if (m == WM_RBUTTONDOWN) {
if (textbkmode == TRANSPARENT)
textbkmode = OPAQUE;
else
textbkmode = TRANSPARENT;
SetBkMode(hdc, textbkmode);
}
// Standard
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Lass das Programm laufen. Fngt harmlos an, ein buntes Hallo. Maximiere das
Fenster, und klicke mit der linken Maustaste irgendwo ins Fenster. Nach dem
Mausklick fngt das Programm an, fortlaufend in groer Geschwindigkeit Hallo,
Welt! zu schreiben. Das rasende Hallo wird berall hingeschrieben:
216

Kapitel 8 Fensternachrichten

Sandini Bib

Wenn du mit der rechten Maustaste klickst, wird der Texthintergrund auf durchsichtig geschaltet:

Klicke mehrmals. Die linke Taste ist die Start- und Stopptaste, die rechte schaltet
zwischen durchsichtigem und weiem Hintergrund hin und her.
Im Programm steht als Erstes die Funktion malen. Hier wird ein Text mit zuflligen Koordinaten und zuflliger Farbe ausgegeben. Die Variablen xmax und ymax
bestimmen, wo berall hingeschrieben wird. Die Korrekturen in x und y um 30
beziehungsweise 10 Pixel bewirken, dass das Hallo auch jenseits des linken und
oberen Randes des Fensters beginnen kann und deshalb das Fenster gleichmig
ausgemalt wird. Wie in Kapitel 4.2 besprochen, mssen wir uns keine Sorgen
Klick mich links, klick mich rechts

217

Sandini Bib

machen, wenn beim Malen der Anzeigebereich des Fensters verlassen wird. Dafr sorgt das automatische Clipping des Fensters.
In der Funktion WindowProc werden unter anderem auch die Nachrichten
WM_LBUTTONDOWN und WM_RBUTTONDOWN behandelt. In
else if (m == WM_LBUTTONDOWN) {
warten = !warten;
InvalidateRect(hwnd, 0, 0);
}

verwenden wir die Variable warten wie einen Schalter, den wir bei jedem Linksklick mit der Maus von wahr auf falsch und von falsch auf wahr schalten. Dazu
muss warten natrlich eine statische Variable sein. Nach jedem Linksklick soll
das Fenster neu ausgemalt werden. Dazu rufen wir
InvalidateRect(hwnd, 0, 0);

auf, was dazu fhrt, dass eine WM_PAINT-Nachricht erzeugt wird, wenn die Nachrichtenwarteschlange leer ist.
In unserem Beispiel wird WM_PAINT mit
else if (m == WM_PAINT) {
malen(hdc, xmax, ymax);
if (warten) ValidateRect(hwnd, 0);
}

bearbeitet. Also wird nach dem Malen ganz normal der Fensterinhalt mit
ValidateRect fr gltig erklrt, wenn die Variable warten wahr ergibt. Wenn
warten falsch ergibt, wird ValidateRect nicht aufgerufen, WM_PAINT wird
immer noch mal erzeugt und es kommt zum rasenden Hallo. Aber wenn du
zwischen all den WM_PAINT mit der linken Maustaste klickst, erzeugst du ein
WM_LBUTTONDOWN und kannst so wieder auf Warten umschalten. Ausprobieren,
verhlt sich WM_LBUTTONUP wie erwartet?
Die Situation fr die rechte Maustaste (WM_RBUTTONDOWN) ist viel einfacher, weil
background mode) fr TextOut umlediglich der Hintergrundmodus (
geschaltet wird. Weil wir hier InvalidateRect nicht aufrufen, hat die rechte
Maustaste keinen sofortigen sichtbaren Effekt, wenn das Programm auf Abwarten geschaltet ist.

8.5

Na, wo luft sie denn,


die Maus?

Das folgende Programm zeigt an, bei welchen Koordinaten sich der Mauszeiger
gerade im Fenster befindet:
218

Kapitel 8 Fensternachrichten

Sandini Bib
Mausbewegung.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc;
char text[1000];
int n = 0;
int xmax = 0, ymax = 0;
int xmaus = 0, ymaus = 0;
/* Rueckruffunktion unseres Fensters */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Beispiel fuer WM_MOUSEMOVE");
hdc = GetDC(hwnd);
}
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}
// Malen
else if (m == WM_PAINT) {
sprintf(text, "xmaus = %d, ymaus = %d
TextOut(hdc, 20, 20, text, strlen(text));
ValidateRect(hwnd, 0);
}

", xmaus, ymaus);

// Maus
else if (m == WM_MOUSEMOVE) {
xmaus = LOWORD(lParam);
ymaus = HIWORD(lParam);
InvalidateRect(hwnd, 0, 0);
}
// Standard
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Jede Mausbewegung im Fenster wird registriert und unsere Rckruffunktion


erhlt die Nachricht WM_MOUSEMOVE. Die Koordinaten stehen in der Variable
lParam, genau wie bei WM_SIZE. So sieht das bei mir aus:

Na, wo luft sie denn, die Maus?

219

Sandini Bib

Hier wird deutlich, wo der Ursprung der Fensterflche ist, in die gezeichnet werden darf (xmaus und ymaus gleich 0), und dass die Koordinaten in TextOut sich
auf die linke obere Ecke des Textes beziehen. Beachte, wie die Mauskoordinaten
sich sprunghaft verndern, wenn der Mauszeiger das Fenster verlsst und von
einer anderen Seite wieder ins Fenster bewegt wird.
Die Bewegung des Mauszeigers kann auf einfache Weise Spuren im Fenster hinterlassen. Setze mal
LineTo(hdc, xmaus, ymaus);

in den Befehlsblock der WM_PAINT-Nachricht, und siehe da, du hast ein ultraprimitives Zeichenprogramm:

Du kannst es auch mit


SetPixel(hdc, xmaus, ymaus, 0);

versuchen. Dann siehst du Lcken in deinen Kritzeleien, weil die Mausnachrichten zwar sehr schnell, aber doch nicht schnell genug fr eine Linie aus lauter
Pixeln kommen. Stelle wieder auf LineTo um und maximiere das Fenster. Versuche durch schnelle Mausbewegungen Ecken in die Linie zu bekommen. Wenn
sich die Maus nicht zu schnell bewegt, ergeben die vielen geraden Stcke mit
LineTo eine gute Nherung einer runden Kurve.
220

Kapitel 8 Fensternachrichten

Sandini Bib

Und noch ein Spielchen: Wenn du LineTo verwendest und WM_MOUSEMOVE durch
WM_LBUTTONDOWN ersetzt, kannst du durch mehrfaches Klicken Zickzacklinien
zeichnen:

8.6

Tastatur

Nachrichten von der Tastatur gibt es wie bei den Maustasten in einer DOWN- und
UP-Version. Wenn du die Taste C drckst und wieder loslsst, erhltst du
WM_KEYDOWN

wParam ist gleich VK_C

WM_CHAR

wParam ist Zeichen c

WM_KEYUP

wParam ist gleich VK_C

Key heit hier Taste. Die Variable wParam beschreibt, welche Taste gemeint ist. Der Name VK_C ist der Name einer Konstanten, die in
#include <windows.h> definiert wurde. VK bedeutet Virtual-Key Code,
also virtueller oder scheinbarer Tastencode. Eine Tabelle aller virtuellen Keycodes fr alle Tasten findest du in der Hilfe zum Win32 SDK unter Virtual-Key
Codes. Schau sie dir bei dieser Gelegenheit gleich an. Du findest die Buchstabentasten, aber auch die Umstelltaste (  ) und die Pfeiltasten.
Nicht jede Taste erzeugt auch eine WM_CHAR-Nachricht. Aber insbesondere
die Zeichen, die in C den Typ char erhalten, werden durch WM_CHAR bergeben. WM_KEYDOWN bezieht sich auf einzelne Tasten der Tastatur, whrend fr
WM_CHAR Tastenkombinationen in einzelne Zeichen bersetzt werden (durch
TranslateMessage, siehe Kapitel 8.1). Wenn du im Gegensatz zu c den
Grobuchstaben C eingeben willst, musst du  und C gleichzeitig drcken
und erhltst
8.6

Tastatur

221

Sandini Bib

WM_KEYDOWN

wParam ist VK_SHIFT

WM_KEYDOWN

wParam ist VK_C

WM_CHAR

wParam ist C

WM_KEYUP

wParam ist VK_C

WM_KEYUP

wParam ist VK_SHIFT

Die Shifttaste selbst erzeugt kein WM_CHAR.


Wir wollen in diesem Buch keine Textverarbeitung selber programmieren, also
werden wir WM_CHAR nicht weiter besprechen. Aber die Pfeiltasten sind auf jeden
Fall ntzlich:

222

Kapitel 8 Fensternachrichten

Sandini Bib
Tastatur0.c WinMain.cpp

#include <windows.h>
/* externe Variable */
HDC hdc;
int xmax, ymax;
int xball, yball;
int r = 15;
int dx = 5;
int dy = 5;
/* Rueckruffunktion unseres Fensters */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
hdc = GetDC(hwnd);
xball = yball = 100;
}
// Tastatur
else if (m == WM_KEYDOWN) {
if
(wParam == VK_LEFT) xball = dx;
else if (wParam == VK_RIGHT) xball += dx;
else if (wParam == VK_UP)
yball = dy;
else if (wParam == VK_DOWN) yball += dy;
else if (wParam == VK_SPACE)
Rectangle(hdc, 1, 1, xmax+1, ymax+1);
else if (wParam == VK_ESCAPE)
SendMessage(hwnd, WM_CLOSE, 0, 0);
InvalidateRect(hwnd, 0, 0);
}
// Malen
else if (m == WM_PAINT) {
Ellipse(hdc, xballr, yballr, xball+r, yball+r);
ValidateRect(hwnd, 0);
}
// Verschiedenes
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Hier malen wir einen Kreis, dessen Radius in r und dessen Koordinaten in xball
und yball stehen. Mit if (m == WM_KEYDOWN) fangen wir die Nachricht ab,
dass eine Taste runtergedrckt wurde. Wann die Taste wieder losgelassen wird, ist
8.6

Tastatur

223

Sandini Bib

uns egal. Falls eine Taste gedrckt wurde, untersuchen wir die Variable wParam,
um entsprechend zu reagieren. Falls es eine der Pfeiltasten war, verndern wir
die Position des Kreises entsprechend, z. B.

Wenn du eine der Pfeiltasten gedrckt lsst, wird die automatische Wiederholfunktion der Tastatur aktiv und dieselbe WM_KEYDOWN-Nachricht wird wiederholt
verschickt: Der Kreis wird immer noch mal versetzt gezeichnet! Das sieht ja
schon fast nach einer richtigen Animation aus, aber in Kapitel 8.7 wird es noch
besser. Falls sich der Kreis zu langsam bewegt, kannst du die Variablen dx und
dy vergrern.
Falls die Leertaste (VK_SPACE) gedrckt wird, lschen wir mit
Rectangle(hdc, 1, 1, xmax+1, ymax+1);

das Bild. Die Koordinaten habe ich hier so gewhlt, dass der Rand des Rechtecks
gerade auerhalb des sichtbaren Bereichs des Fensters liegt.
Die Escapetaste (VK_ESCAPE) kann verwendet werden, um das Programm fluchtescape heit entkommen). Dazu rufen wir
artig zu verlassen (
SendMessage(hwnd, WM_CLOSE, 0, 0);

auf. Die Funktion SendMessage ruft direkt die Rckruffunktion des Fensters
hwnd auf und kann beliebige Nachrichten bergeben. Im dritten und vierten Argument stehen die Werte fr wParam und lParam. Die Nachricht WM_CLOSE,
Fenster schlieen, wird in unserem Beispiel von DefWindowProc verarbeitet.
WM_CLOSE wird verschickt, bevor das Fenster zuklappt, whrend WM_DESTROY bedeutet, dass das Fenster schon zugemacht wurde. Bei WM_CLOSE knntest du z. B.
noch fragen: Bist du sicher? Schaus dir an, so ein schnes Fenster, wirklich zumachen?
Eine ausgebaute Version desselben Beispiels zeigt
224

Kapitel 8 Fensternachrichten

Sandini Bib
Tastatur1.c WinMain.cpp

#include <windows.h>
/* externe Variable */
HDC hdc;
HBRUSH hbnull, hblila, hbgelb, hbtemp;
int xmax, ymax;
int xball, yball;
int
int
int
int

r = 15;
dx = 5;
dy = 5;
shift;

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
hdc = GetDC(hwnd);
hbnull = GetStockObject(NULL_BRUSH);
hblila = CreateSolidBrush(RGB(230,0,230));
hbgelb = CreateSolidBrush(RGB(230,230,0));
SelectObject(hdc, hblila);
xball = yball = 100;
}
// Tastatur
else if (m == WM_KEYDOWN) {
if (GetAsyncKeyState(VK_SHIFT)) shift = 3;
else
shift = 1;
if
(wParam ==
else if (wParam ==
else if (wParam ==
else if (wParam ==

VK_LEFT)
VK_RIGHT)
VK_UP)
VK_DOWN)

xball
xball
yball
yball

=
+=
=
+=

shift*dx;
shift*dx;
shift*dy;
shift*dy;

else if (wParam == VK_NEXT) {


r = 9*r/10 1;
if (r < 1) r = 1;
}
else if (wParam == VK_PRIOR) {
r = 10*r/9 + 1;
if (r > xmax) r = xmax;
}
else if (wParam == VK_INSERT)
SelectObject(hdc, hbnull);
else if (wParam == VK_DELETE)
SelectObject(hdc, hblila);
else if (wParam == VK_SPACE) {
hbtemp = SelectObject(hdc, hbgelb);
Rectangle(hdc, 1, 1, xmax+1, ymax+1);
SelectObject(hdc, hbtemp);
}
else if (wParam == VK_ESCAPE)
SendMessage(hwnd, WM_CLOSE, 0, 0);
InvalidateRect(hwnd, 0, 0);
}

8.6

Tastatur

225

Sandini Bib
Tastatur1.c WinMain.cpp

// Malen
else if (m == WM_PAINT) {
Ellipse(hdc, xballr, yballr, xball+r, yball+r);
ValidateRect(hwnd, 0);
}
// Verschiedenes
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
xball = xmax/2;
yball = ymax/2;
}
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Jetzt verwenden wir zwei Farben und einen wahlweise durchsichtigen Hintergrund (Einfgen VK_INSERT und Entfernen VK_DELETE). Der Kreis kann
kleiner und grer gemacht werden (nchste und vorherige Seite, VK_NEXT
und VK_PRIOR). Und ich meine wirklich gro. Maximiere das Fenster und halte
VK_PRIOR gedrckt. Dann verschiebe den Kreis. Beachte, wie wir verhindern,
dass der Kreis zu gro oder zu klein wird.
Mit GetAsyncKeyState(VK_SHIFT) erfragt das Programm, ob in dem Moment,
wenn WM_KEYDOWN verarbeitet wird, die Shifttaste gedrckt ist. Bei gedrckter
Shifttaste ist die Schrittweite das Dreifache von dx und dy.
226

Kapitel 8 Fensternachrichten

Sandini Bib

8.7

Die Uhr macht tick

timer) starMit der Funktion SetTimer knnen wir einen Zeitgeber (


ten, der in regelmigen Zeitabstnden eine WM_TIMER-Nachricht verschickt. Mit
KillTimer knnen wir diesen Zeitgeber wieder abschalten. Im folgenden Beispiel verwenden wir einen Zeitgeber, um einen Kreis automatisch zu verschieben:
Zeitgeber0.c WinMain.cpp

#include <windows.h>
/* externe
HDC hdc;
int stop =
int xmax =
int xball,
int
int
int
int

r
dx
dy
dt

=
=
=
=

Variable */
1;
0, ymax
yball;

= 0;

15;
2;
2;
10;

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Ball");
SetTimer(hwnd, 0, dt, 0);
hdc = GetDC(hwnd);
xball = yball = r;
}
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
}
// Uhr macht tick
else if (m == WM_TIMER) {
xball += dx;
yball += dy;
if (xball < 0 || xball >= xmax) dx = dx;
if (yball < 0 || yball >= ymax) dy = dy;
InvalidateRect(hwnd, 0, 0);
}
// Malen
else if (m == WM_PAINT) {
Ellipse(hdc, xballr, yballr, xball+r, yball+r);
ValidateRect(hwnd, 0);
}

8.7

Die Uhr macht tick

227

Sandini Bib
Zeitgeber0.c WinMain.cpp

// Start/Stopp
else if (m == WM_LBUTTONDOWN) {
if (stop)
KillTimer(hwnd, 0);
else
SetTimer(hwnd, 0, dt, 0);
stop = !stop;
}
// Aufraeumen
else if (m == WM_DESTROY) {
KillTimer(hwnd, 0);
PostQuitMessage(0);
}
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Die linke Maustaste funktioniert als Start- und Stopptaste der Animation. Bei
WM_DESTROY schalten wir den Zeitgeber ab. In den Zeilen
xball += dx;
yball += dy;
if (xball < 0 || xball >= xmax) dx = dx;
if (yball < 0 || yball >= ymax) dy = dy;

bewegen wir erst den Ball weiter und dann drehen wir die Bewegungsrichtung
um, falls der Mittelpunkt des Balls den sichtbaren Bereich verlassen hat. Wir
rufen SetTimer mit
SetTimer(hwnd, nummer, dt, 0);

auf. Falls wir mehrere Zeitgeber in einem Programm verwenden mchten, geben
wir jedem seine eigene Nummer. In unserem Beispiel ist die Nummer 0. Diese
Nummer wird bei WM_TIMER in wParam bergeben.
228

Kapitel 8 Fensternachrichten

Sandini Bib

Mit dt bestimmen wir, wie viele tausendstel Sekunden (also Millisekunden) der
Zeitgeber zwischen seinen Signalen wartet. Die Zeit, nach der sich ein Signal
wiederholt, nennt man auch seine Periode. Bei SetTimer ist die kleinste Periode
normalerweise ungefhr 55 Millisekunden, was einer Frequenz von ungefhr
18-mal pro Sekunde entspricht. Alle anderen Perioden werden mit einem ganzzahligen Vielfachen dieser Periode angenhert. Wenn du eine hhere Auflsung
bentigst, kannst du die Multimediatimer von Windows verwenden, siehe die
Microsoft Hilfe, Multimedia Reference.
Hier ist eine Version des Beispielprogramms, in der du mit der rechten Maustaste
whlen kannst, ob vor dem Zeichnen des neuen Balls der alte gelscht wird:

8.7

Die Uhr macht tick

229

Sandini Bib
Zeitgeber1.c WinMain.cpp

#include <windows.h>
/* externe Variable */
HDC hdc;
HBRUSH hbschwarz, hbrot;
char text[1000];
int n = 0;
int xmax = 0, ymax = 0;
int xball, yball;
int xalt, yalt;
int stop = 1;
int spur = 0;
int
int
int
int

r
dx
dy
dt

=
=
=
=

50;
3;
4;
10;

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Ball");
SetTimer(hwnd, 0, dt, 0);
hdc = GetDC(hwnd);
hbschwarz = CreateSolidBrush(RGB( 0, 0, 0));
hbrot
= CreateSolidBrush(RGB(255, 0, 0));
xball = yball = xalt = yalt = r;
}
// Neue Fenstergroesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
SelectObject(hdc, hbschwarz);
Rectangle(hdc, 0, 0, xmax, ymax);
}
// Uhr macht tick
else if (m == WM_TIMER) {
xalt = xball;
yalt = yball;
xball += dx;
yball += dy;
if (xballr < 0 || xball+r >= xmax) dx = dx;
if (yballr < 0 || yball+r >= ymax) dy = dy;
InvalidateRect(hwnd, 0, 0);
}

230

Kapitel 8

Fensternachrichten

Sandini Bib
Zeitgeber1.c WinMain.cpp

// Malen
else if (m == WM_PAINT) {
if (!spur) {
SelectObject(hdc, hbschwarz);
Ellipse(hdc, xaltr, yaltr, xalt+r, yalt+r);
}
SelectObject(hdc, hbrot);
Ellipse(hdc, xballr, yballr, xball+r, yball+r);
ValidateRect(hwnd, 0);
}
// Start/Stopp
else if (m == WM_LBUTTONDOWN) {
if (stop)
KillTimer(hwnd, 0);
else
SetTimer(hwnd, 0, dt, 0);
stop = !stop;
}
// Spur an/aus
else if (m == WM_RBUTTONDOWN) {
spur = !spur;
SelectObject(hdc, hbschwarz);
Rectangle(hdc, 0, 0, xmax, ymax);
InvalidateRect(hwnd, 0, 0);
}
// Aufraeumen
else if (m == WM_DESTROY) {
KillTimer(hwnd, 0);
PostQuitMessage(0);
}
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

8.7

Die Uhr macht tick

231

Sandini Bib

Die Bilder habe ich ohne Lschen gemacht, damit man sieht, welcher Bahn der
Ball gefolgt ist.
Das Ergebnis mit Lschen ist etwas enttuschend. Maximiere das Bild. Typischerweise siehst du eine Art Flackern und nicht etwa die glatte Bewegung von Bllen
oder Raumschiffen, die du aus Computerspielen kennst. Das liegt nicht oder nur
unwesentlich daran, dass die Schrittweite in x- und y-Richtung grer als ein
Pixel ist oder dass die Zeitauflsung trotz der Angabe dt = 10 in Wirklichkeit
wahrscheinlich nur 55 Millisekunden betrgt.
Ein Problem ist, dass wir erst den Kreis mit Schwarz bermalen und dann
den neuen Kreis am neuen Ort ausgeben. Das flackert, weil zwischendurch der
schwarze Hintergrund aufblitzt, obwohl wir den schwarzen und den roten Kreis
direkt nacheinander malen. Um das neue Bild mit nur einem Ausgabebefehl zu
malen, gibt es fr Kreise einen einfachen Trick. Fge die Zeile
SelectObject(hdc, CreatePen(PS_SOLID, 5, 0));

als letzte Zeile dem Befehlsblock von WM_CREATE hinzu. Die Kreise haben jetzt
einen 5 Pixel breiten schwarzen Rand, weil der Rand mit dem angegebenen Stift
(pen) gezeichnet wird. Du kannst zum Ausprobieren statt der Farbe 0 (Schwarz)
auch RGB(0,0,255) einsetzen (Blau). Der breite Rand lscht alles, was vom alten
Kreis unter dem neuen hervorguckt. Lass das Programm noch mal laufen. Jetzt
232

Kapitel 8 Fensternachrichten

Sandini Bib

kannst du mit der rechten Maustaste zwischen Flackern und deutlich weniger
Flackern umschalten! In Kapitel 11.4 werden wir besprechen, wie du mit Hilfe
von so genannten Bitmaps beliebige Grafik in einem Rutsch zeichnen kannst,
ohne dass beim Lschen zwischendurch der Hintergrund sichtbar wird.

8.8
In diesem Kapitel haben wir Nachrichtenmanager gespielt. Alle Eingaben und
Ereignisse, die unser Programmfenster angeht, werden an die Fensterprozedur
WindowProc als Nachricht bergeben. In Krze:
Diese Nachrichten haben mit der Fensterorganisation zu tun:
WM_CREATE wird einmal vor der Erzeugung des Fensters aufgerufen und

kann zur Initialisierung des Programms verwendet werden.


WM_CLOSE wird vor dem Zumachen des Fensters verschickt und kann abgefangen werden, um das Schlieen des Fensters zu verhindern.
WM_DESTROY wird nach dem Zumachen des Fensters verschickt und kann
zu Aufrumarbeiten verwendet werden.
WM_SIZE wird bei der Grennderung erzeugt, die neue Gre steht in
lParam.
Wichtige Nachrichten der Maus sind z. B.
WM_LBUTTONDOWN fr das Runterdrcken der linken Maustaste
WM_MOUSEMOVE fr Bewegungen des Mauszeigers im Fenster
WM_LBUTTONUP fr das Loslassen der linken Maustaste

Die Koordinaten des Mauszeigers fr diese Ereignise erhltst du mit


x = LOWORD(lParam) und y = HIWORD(lParam).

Wichtige Nachrichten der Tastatur sind z. B.


WM_KEYDOWN fr das Drcken einer Taste, wParam ist der virtuelle Tasten-

code
WM_CHAR fr Zeichen wie C, die ebenfalls in wParam bergeben werden.

Den momentanen Zustand einer Taste (gedrckt oder nicht gedrckt) kannst
du mit GetAsyncKeyState erfahren.
Einen Zeitgeber startest du mit SetTimer und stoppst du mit KillTimer. Die
dazugehrige Nachricht ist WM_TIMER.
Der Eindruck von glatter Bewegung und Animation wird durch die schnelle
Abfolge von einzelnen Bildern erzeugt.

8.8

233

Sandini Bib

8.9
1. Schreibe ein Programm, das auf Mausklick das ganze Fenster ausmalt. ndere

das Programm so ab, dass ein grnes Fensters rot wird, solange du eine der
Maustasten gedrckt hltst. Mache die farbige Flche kleiner und schreibe
einen Text hinein, wobei sich Farbe und Text beim Mausklick ndern. Du
hast deinen eigenen, primitiven Windowsknopf programmiert.
2. Schreibe ein Programm, mit dem du mit der Maus ein Rechteck aufziehen

kannst. Also, wenn die Maustaste runtergedrckt wird, merkt sich das Programm die Koordinaten, und nach jeder Mausbewegung wird das alte Rechteck gelscht und das neue Rechteck zwischen den momentanen und den Anfangskoordinaten gezeichnet.
3. Einen netten Effekt gibt es, wenn du die Koordinaten fr jeden Mausklick
in zwei Feldern int x[1000], y[1000]; speicherst und beim Malen dann
jeden Punkt (x, y) mit jedem anderen Punkt verbindest (doppelte Schleife).
4. Warum steht im zweiten Beispiel in Kapitel 8.6 im Kreisezeichner r = 10*r/
9 + 1 und nicht einfach nur r = 10*r/9? Wie knntest du es erreichen, dass

beim wiederholten Verkleinern und Vergrern Machen immer dieselben Radien erzeugt werden? Wie musst du das Programm ndern, damit immer nur
ein Kreis und nicht auch die vorhergehenden Kreise sichtbar sind?
5. Baue einen Zeitgeber in das rasende Hallo ein. Lass die Hallos trpfeln. Die

Zeitrate knntest du per Tastatur einstellbar machen.


6. In den Beispielen mit dem Zeitgeber kannst du die Schrittweite der Bewegung

und die Zeitabstnde ndern. Probiere aus, wie glatt die Bewegung bei verschiedenen Werten von dx, dy und dt aussieht. Entferne den Zeitgeber und
lass das Programm so schnell zeichnen, wie es kann (kein ValidateRect).
Notfalls kannst du das Programm bremsen, indem du den Computer nach
jedem Zeichnen bis 1000 000 zhlen lsst.
7. Schreibe ein Programm fr N Blle, die alle in verschiedene Richtungen los-

fliegen und vom Fensterrand abprallen. Wie erreichst du, dass die Blle sich
nicht verdecken, sondern voneinander im richtigen Winkel abprallen? Das
ist nicht einfach. Fange mit zwei Bllen an, die waagrecht fliegen, aber in der
Senkrechten gegeneinander versetzt sind.

234

Kapitel 8 Fensternachrichten

Sandini Bib

9
Datentypen
9.0
9.1
9.2
9.3
9.4
9.5
9.6

Von Bits und Bytes


Bit fr Bit
Datentypen
Datentypen klein und gro
Mein Typ, dein Typ: die Typenumwandlung
Konstante Variable mit const
Neue Namen fr alte Typen: typedef

236
238
239
240
243
247
247

9.7

Mathematische Funktionen

248

9.8

Die Sinusfunktion

249

9.9

Kreis und Rotation

256

9.10

Farbtiefe

264

9.11

Mandelbrot-Menge

265

9.12

274

9.13

274

Sandini Bib

Viele Eigenheiten von Zahlen und Variablen in C und anderen Programmiersprachen werden erst einsichtig, wenn man sich mit Bits und Bytes vertraut
gemacht hat. Das wollen wir in diesem Kapitel tun. Zahlen werden in Bits und
Bytes im Computer abgespeichert. Fr verschiedene Zahlenformate definiert C
verschiedene Datentypen, z. B. werden ganze Zahlen anders abgespeichert als
Kommazahlen. Zudem werden je nach Datentyp unterschiedlich viele Bytes pro
Zahl verwendet.
Die Diskussion von Datentypen bietet eine gute Gelegenheit, uns mit Fliekommazahlen zu beschftigen. Mathematische Funktionen wie die Wurzelfunktion
oder die Sinusfunktion rechnen mit Kommazahlen. Auch zweidimensionale oder
dreidimensionale Grafik wird typischerweise in Kommazahlen berechnet, die
dann fr die Bildschirmausgabe in ganze Zahlen umgewandelt werden mssen.
Also, was ist ein Bit, was ist ein Byte?

9.0 Von Bits und Bytes


Ein Bit (
Stckchen) ist die kleinste Speichereinheit in jedem digitalen
Computer. Ein Bit kann genau zwei Werte annehmen, 0 oder 1. Das kannst du
dir auch als An und Aus eines elektronischen Schalters vorstellen. Der Name
binary digit (Ziffer des Binrsystems, des Zweiersystems).
Bit steht fr
Ein Byte besteht aus 8 Bits. In einem Byte kann man 256 verschiedene Zahlen
speichern. Wie das? Das erste Bit kann zwei Werte annehmen:
0, 1

Fr jedes Bit, das hinzukommt, verdoppelt sich die Anzahl der Mglichkeiten,
denn wir knnen das neue Bit auf 0 setzen und haben all die vorherigen Mglichkeiten oder auf 1 setzen und haben wieder all die vorherigen Mglichkeiten.
Zwei Bit knnen also 2 2 = 4 Zahlen speichern:
00,

01,

10,

11

Das ergibt fr acht Bit


2 2 2 2 2 2 2 2 = 256

Mglichkeiten.
Acht Zweier mit sich malgenommen schreibt man in der Potenzschreibweise als
28 (gesprochen zwei hoch acht). brigens ist 102 = 10 10 = 100 (zehn hoch
zwei). Man muss aufpassen, welche Zahl oben und welche unten steht, z. B. ist
210 = 1024.
Wenn du mit 1 anfngst und immer wieder mit 10 malnimmst, erhltst du die
Zehnerpotenzen:
236

Kapitel 9

Datentypen

Sandini Bib

100

101

102

103

104

...

10

100

1000

10000

...

Hoch 0 ergibt 1. Wenn du mit 1 anfngst und immer wieder mit 2 malnimmst,
kannst du leicht die folgenden Zweierpotenzen ausrechnen:
20

21

22

23

24

25

26

27

28

29

210

...

16

32

64

128

256

512

1024

...

Normalerweise rechnen wir im Zehnersystem (Dezimalsystem). Wenn der


Mensch nur zwei Finger htte, wrde er vielleicht genau wie der Computer im Zweiersystem (Binrsystem) rechnen. Die Zweierpotenzen spielen im
Zweiersystem dieselbe Rolle wie die Zehnerpotenzen im Zehnersystem. Im
Zehnersystem erhalten die Ziffern 0 bis 9 ihren Wert durch ihre Stellung: 2501
sind 2 Tausender, 5 Hunderter, 0 Zehner und 1 Einser.
Eine Zahl im Zweiersystem wird nur mit den Ziffern 0 und 1 geschrieben. Die
Zahl 10110 im Zweiersystem enthlt 1 Sechzehner, 0 Achter, 1 Vierer, 1 Zweier,
0 Einser. Das aufaddiert ergibt 22 im Zehnersystem. Um die Zahl 22 in einem
Byte zu speichern, werden die acht Bits wie folgt gesetzt:
00010110

Der Vollstndigkeit halber mchte ich auch noch das Sechzehnersystem erwhnen (das Hexadezimalsystem). Hier verwendet man 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A,
B, C, D, E, F, um die Zahlen von 0 bis 15 als eine einzelne Ziffer darzustellen. In
C kannst du Zahlen im Hexadezimalsystem angeben, indem du 0x voranstellst:
0x01
0x0A
0x0F
0x10
0x100
0xFF

ist 1
ist 10
ist 15
ist 1 161 = 16
ist 1 162 = 256
ist 15 161 + 15 160 = 255

Das Hexadezimalsystem ist manchmal praktisch, weil ein Byte genau zwei
Ziffern entspricht. Im Binrsystem ist 0xFF gleich 11111111. Du kannst z. B.
int i = 0xFF; schreiben, wenn du aus irgendeinem Grund diese acht Bits in
i gleich eins setzen mchtest.
Wenn dir das alles zu kompliziert vorkommt, keine Sorge. Obwohl der Computer
intern im Zweiersystem rechnet, steht dir in C, wie wir schon gesehen haben,
ein vollstndiger Satz an Rechenoperationen im Dezimalsystem zur Verfgung.
Das Wort Bit ist dir sicher schon in Angaben wie 32-Bit-Mikroprozessor, 64Bit-Datenbus oder 16 Bit Farbtiefe aufgefallen. Zwei Bytes oder 16 Bit nennt
word), vier Bytes oder 32 Bit ein Doppelwort (double
man auch ein Wort (
9.0 Von Bits und Bytes

237

Sandini Bib

word). Ein 32-Bit-Prozessor ist typischerweise schneller als ein 8-Bit-Prozessor,


weil er mehr Daten auf einmal verarbeiten kann.
Der Speicher fr Daten in Computern wird in Bytes angegeben, wobei ein Kilobyte (kB) ungefhr 1000 Bytes und ein Megabyte (MB) ungefhr 1000 000
Bytes sind. Leider herrscht hier eine gewisse Verwirrung. Ein Kilometer ist exakt 1000 Meter, aber mit Kilobytes sind mal 1000 Bytes, mal 1024 Bytes gemeint.
Zum Glck ist der Unterschied gering. Mein erster Computer hatte 8 kB RAM
(Arbeitsspeicher), neue PCs haben heutzutage 128 MB oder mehr. Festplattenspeicher wird zurzeit in Gigabyte (GB, 1 GB ist ungefhr 1000 MB) gemessen.
Immer wieder tauchen Zweierpotenzen auf, weil der Computer als kleinste Einheit das Bit mit seinen zwei Zustnden kennt.

9.1 Bit fr Bit


Wenn dich die Bits faszinieren, werden dir die folgenden Rechenoperationen gefallen, die Zahlen Bit fr Bit (bitweise) bearbeiten. Diese sind
bitweise Negation
bitweises Oder
bitweises Und
bitweises Entweder-Oder
bitweise links verschieben
bitweise rechts verschieben

|
&

<<
>>

Die ersten vier Operatoren kommen aus der Logik. Ein einzelner Strich | bezieht
sich auf Bits, whrend der doppelte Strich || die Verknpfung von Aussagen
beschreibt, und genauso verhlt es sich bei & und &&. Mit Negation erreicht
man, dass aus 0 die 1 wird und aus 1 eine 0:
0 ist 1,
1 ist 0.

Mit Oder ist das Ergebnis von i | j gleich 1, wenn i oder j gleich 1 ist, d.h.
wenn mindestens eine der beiden Variablen 1 ist:
0|0
0|1
1|0
1|1

ist 0,
ist 1,
ist 1,
ist 1.

Was passiert bei & und ? Bei den Verschiebungsoperatoren werden die Ziffern
im Binrsystem nach links oder rechts verschoben:
1 << 3 ist 1000 im Zweiersystem und 8 im Zehnersystem.

238

Kapitel 9

Datentypen

Sandini Bib

Am besten schreibst du ein kleines Programm, mit dem du mit diesen Operatoren
experimentieren kannst. Verwende zwei Variablen int i, j;, die du auf 0 oder
1 setzt.

9.2 Datentypen
Wie schon erwhnt, unterscheidet C zwischen ganzen Zahlen und reellen Zahlen (Kommazahlen). Um effizient mit Zahlen umgehen zu knnen, sind in C
bestimmte Datentypen fr Zahlen festgelegt, die immer dieselbe Anzahl von
Bytes im Speicher belegen. Es gibt vier elementare Datentypen in C:
char
int
float
double

1 Byte
4 Bytes (2 oder mehr)
4 Bytes (4 oder mehr)
8 Bytes (4 oder mehr)

ein Zeichen oder eine kleine ganze Zahl


eine ganze Zahl
eine Kommazahl
Kommazahl mit hherer Genauigkeit

Weil die Anzahl von Bytes vom Rechner und vom Computer abhngig sind,
gebe ich in der Tabelle an, wie viele Bytes auf meinem Pentium II PC mit BCB
verwendet werden: int 4 Bytes, float auch 4 Bytes und double 8 Bytes. In
Klammern steht die fr C festgelegte Mindestgre.
Ganze Zahlen (
integers) haben wie schon besprochen den Typennamen
int. Mit ihnen kann man gut zhlen, 0, 1, 2, 3, . . ., und auch die negativen Zahlen gehren dazu. Brche mit Zhler und Nenner aus ganzen Zahlen kennt C
nicht, aber fr reelle Zahlen wie 1.5 und 0.001 gibt es die Datentypen float
und double. Zeichen (
characters) sind uns auch schon begegnet, sie erhalten den Typennamen char. Dieser Typ spielt eine Doppelrolle, denn er steht
gleichzeitig fr 1 Zeichen oder Buchstaben und fr die Speichereinheit 1 Byte.
berhaupt stellt sich die Frage, auf welche Weise ganze Zahlen, reelle Zahlen
und Zeichen in den Bytes gespeichert werden.
Buchstaben und Zeichen werden nicht irgendwie auf geheimnisvolle Weise als
etwas anderes als eine Zahl abgespeichert. Alles was der Computer kennt, sind
Bits und Zahlen, sonst nichts! Jedem Zeichen wird nach einem Code (namens
ASCII) eine Zahl zugewiesen. Weil ein char nur 1 Byte Speicherplatz beansprucht, kann es also nur 256 verschiedene Characters geben. Diesen Code musst
du nicht kennen, denn mit dem Apostroph kannst du direkt Buchstaben in Chars
speichern, z. B.
char buchstabe = a;
char klammerauf = (;

Du erinnerst dich, Zeichenketten schreibt man mit "...", aber zwischen .


darf immer nur ein Zeichen stehen. Wie man mit char s[10] = "abc"; mehrere Zeichen zu einer Zeichenkette zusammensetzt, haben wir in Abschnitt 3.3
besprochen.
9.2 Datentypen

239

Sandini Bib

Bei Kommazahlen ist es hnlich. Nach einem Code, der uns nicht zu interessieren
braucht, werden die Ziffern und der Ort des Kommas abgespeichert.
Die elementaren Datentypen knnen wie folgt modifiziert werden. Fr char und
int kann man bestimmen, ob negative Zahlen erlaubt sind, z. B.
unsigned char
signed char

Werte von 0 bis 255


Werte von 128 bis 127

sign heit hier Vorzeichen.


Des Weiteren gibt es
long (lang) und short (kurz), um die Anzahl der
Bytes fr int zu erhhen oder zu erniedrigen. long kann man auch fr double
verwenden. Es gibt
short int
int
long int

fr Integers und
float
double
long double

fr Kommazahlen. Womglich kennt dein Compiler noch weitere Datentypen.


Als Abkrzung kannst du short i; und long j; schreiben, um die entsprechenden Integertypen zu erhalten.

9.3 Datentypen klein und gro


Je mehr Bytes zum Speichern von ganzen Zahlen verwendet werden, desto grer knnen ganze Zahlen sein. Bei Kommazahlen bedeuten mehr Bytes, dass
nicht nur grere, sondern auch Zahlen mit mehr Stellen hinter dem Komma
gespeichert werden knnen.
Leider hngt die Anzahl der Bytes vom Rechner und vom Compiler ab. Mal
ist ein int 2 Bytes, mal 4, mal 8. Was fr ein Durcheinander. C zeichnet sich
im Allgemeinen durch groe Klarheit und Eleganz aus, aber die Sache mit den
Bytes pro Datentyp ging daneben. Die Erfinder von C liefern aber meiner Meinung nach immerhin die zweitbeste Lsung des Problems in Form des Operators
sizeof (Gre von). Damit kannst du in deinen Programmen die Gre
verschiedener Datentypen erfahren:

240

Kapitel 9 Datentypen

Sandini Bib
Datentypen0.c

#include <stdio.h>
int main()
{
printf("sizeof(char)
printf("sizeof(int)
printf("sizeof(long)
printf("sizeof(float)
printf("sizeof(double)
getchar();
return 0;
}

ist
ist
ist
ist
ist

%d
%d
%d
%d
%d

Byte\n",
Bytes\n",
Bytes\n",
Bytes\n",
Bytes\n",

sizeof(char));
sizeof(int));
sizeof(long));
sizeof(float));
sizeof(double));

Trotz der runden Klammern ist sizeof keine Funktion, sondern eine C-Anweisung wie return und if. Dieses Programm schreibt auf meinem PC mit BCB
sizeof(char)
sizeof(int)
sizeof(long)
sizeof(float)
sizeof(double)

ist
ist
ist
ist
ist

1
4
4
4
8

Byte
Bytes
Bytes
Bytes
Bytes

Das heit, long ist nicht lnger als int. In C gibt es die Dateien limits.h und
limit heit Grenze), in der verschiedene Grenzwerte fr Zahlen
festgehalten sind. Hier ist ein Beispiel:

float.h (

Datentypen1.c

#include <stdio.h>
#include <limits.h>
#include <float.h>
int main()
{
printf("char: Minimum %d, Maximum %d\n", CHAR_MIN, CHAR_MAX);
printf("int: Minimum %d, Maximum %d\n", INT_MIN, INT_MAX);
printf("float: Minimum %.2e, Maximum %.2e\n", FLT_MIN, FLT_MAX);
printf("double: Minimum %.2e, Maximum %.2e\n", DBL_MIN, DBL_MAX);
getchar();
return 0;
}

ergibt
char: Minimum 128, Maximum 127
int: Minimum 2147483648, Maximum 2147483647
float: Minimum 1.18e038, Maximum 3.40e+038
double: Minimum 2.23e308, Maximum 1.80e+308

9.3 Datentypen klein und gro

241

Sandini Bib

Die Zeile #include <limits.h> fgt die Definition der symbolischen Konstanten CHAR_MIN und so weiter ein. Wie du siehst, knnen wir Ints und Chars mit
der Formatanweisung fr ganze Zahlen %d drucken. Kommazahlen kannst du
mit %e oder %f drucken (ausprobieren). Mit %f kannst du Kommazahlen wie
z. B. 0.001 ausgeben. Mit %e erhlt man die Exponentenschreibweise. Mit %.2e
begrenzt man die Stellen nach dem Punkt auf 2.
Vielleicht ist dir die Exponentenschreibweise noch nicht begegnet und sie ist
auch fr dieses Buch nicht wichtig, besonders schwierig ist sie aber auch nicht.
Z.B. ist
5.1e+3 = 5.1 103 = 5.1 1000 = 5100,
5.1e3 = 5.1 103 = 5.1/1000 = 0.0051.

Wie wir sehen, kann man in Chars bis etwas ber 100 mit positiven und negativen Zahlen rechnen und mit Ints bis 2 Milliarden. Kommazahlen knnen auch
negativ sein. Das Minimum bedeutet in diesem Fall die kleinste positive Zahl.
Wir knnen mit Kommazahlen rechnen, die bis zu 38 bzw. 308 Nullen vor oder
hinter dem Komma haben. Eine 1 mit 308 Nullen! Das sollte reichen. Fr Kommazahlen ist es meistens viel wichtiger, auf wie viele Stellen das Ergebnis genau
ist. Fr float sind es 6 Stellen, fr double sind es 15. Wenn Speicherplatz keine
Rolle spielt, verwenden wir double. Normalerweise werden Floats sowieso beim
Rechnen in Doubles umgewandelt, weil die FPU (floating point unit) deines
Prozessors nur mit Doubles rechnet.
Lass uns eine von diesen Grenzen testen:
Datentypen2.c

#include <stdio.h>
int main()
{
char i, j, summe;
i = 10;
j = 10;
summe = i + j;
printf("%d + %d ist gleich %d \n", i, j, summe);
i = 100;
j = 100;
summe = i + j;
printf("%d + %d ist gleich %d \n", i, j, summe);
getchar();
return 0;
}

ergibt
10 + 10 ist gleich 20
100 + 100 ist gleich 56

242

Kapitel 9 Datentypen

Sandini Bib

Wau (
Wow). Das Problem kennst du schon aus Kapitel 2.9 fr Integers.
Wenn eine ganze Zahl zu gro wird, luft der Speicherplatz wie ein Fass ber
overflow). Die hohen Ziffern werden einfach weggeschmissen und auch
(
die Codierung des Minuszeichens kommt durcheinander. Bei Kommazahlen erhltst du bei einem Overflow eine Fehlermeldung, bei Ints und Chars normalerweise nicht. Immerhin warnt uns BCB beim Kompilieren, dass wir in den Zeilen
mit summe = i + j Ziffern verlieren knnten.
Die letzten drei Beispiele solltest du unbedingt ausprobieren, damit
du im wahrsten Sinne des Wortes weit, womit du zu rechnen hast!
Warum rechnet C nicht mit beliebig groen Zahlen? Das dem Computer beizubringen ist gar nicht so schwer, obwohl es bei nur sehr wenigen Programmiersprachen zur Grundausstattung gehrt. Ein Grund ist, dass dann alle Rechenoperationen langsamer werden und im Schnitt womglich mehr Speicher
verbraucht wird.
Zum Abschluss dieses Themas mchte ich zweierlei festhalten:
1. Bei allen Rechenoperationen muss man beachten, dass jeder Datentyp nur

einen beschrnkten Zahlenbereich zulsst.


2. Man kann normalerweise mit char, int und double auskommen.

9.4 Mein Typ, dein Typ: die Typenumwandlung


Was passiert wohl, wenn wir in einer Rechnung Datentypen mischen? Jede ganze
Zahl wie 1 ist zugleich eine reelle Zahl. Aber eine reelle Zahl wie 2.9 ist nicht
zugleich eine ganze Zahl. Also leuchtet es ein, dass Ints in Doubles gespeichert
werden knnen. Was aber geschieht in
int i;
i = 2.9;

Wird womglich aufgerundet? Gleich ausprobieren:

9.4 Mein Typ, dein Typ: die Typenumwandlung

243

Sandini Bib
Datentypen3.c

#include <stdio.h>
int main()
{
int i;
double x;
i = 1;
x = 2.9;
printf("i = %d

x = %f\n", i, x);

i = 2.9;
x = 1;
printf("i = %d

x = %f\n", i, x);

getchar();
return 0;
}

i = 1
i = 2

x = 2.900000
x = 1.000000

Wie du siehst, erhlt i durch i = 2.9; den Wert 2. Bei der Umwandlung von
Kommazahl in Integerzahl wird der Kommateil einfach weggelassen.
Auch in x = 1; geschieht etwas nicht Triviales. Obwohl natrlich 1 und 1.0
gleich viel ist, mssen die vier Byte in einer typischen int erst einmal in die
acht Byte einer double Variable umgeschrieben werden, und das im richtigen
Format.
Wenn mit zwei verschiedenen Datentypen gerechnet wird, werden Zahlen automatisch ineinander umgewandelt, wenn erforderlich:
Datentypen4.c

#include <stdio.h>
int main()
{
int i = 1;
double x = 2.9;
double summe;
summe = i + x;
printf("summe = %f\n", summe);
getchar();
return 0;
}

244

Kapitel 9 Datentypen

Sandini Bib

summe = 3.900000

Das heit, wenn zwei Datentypen verknpft werden, wird im Allgemeinen vor
der Rechnung der kleinere Datentyp in den greren verwandelt. Ist ein Wert
double und der andere int, wird zunchst die int in double umgewandelt.
Handelt es sich um int und char, wird char zu int gemacht.
Manchmal mchte man Typen auf Befehl umwandeln (eigentlich wie im richticast heit hier Gussform,
gen Leben). Dafr gibt es den Cast Operator.
casting nennt man die Auswahl von Schauspielern fr bestimmte Rollen beim
Film. Das sieht dann so aus:
Datentypen5.c

#include <stdio.h>
int main()
{
double x, y;
x = (int) 2.9;
y = (int) 2.9;
printf("x = %f, y = %f\n", x, y);
getchar();
return 0;
}

x = 2.000000, y = 2.000000

Die Anweisung lautet


(Typname) Ausdruck

In unserem Beispiel ergibt


(int) 2.9

die ganze Zahl 2, die wir dann wie in


x = 2;

als Kommazahl in x speichern. Das heit, wir haben mit (int) den ganzzahligen
Anteil der Kommazahl 2.9 berechnet.
Eine kleine Falle ist im folgenden Beispiel versteckt:

9.4 Mein Typ, dein Typ: die Typenumwandlung

245

Sandini Bib
Datentypen6.c

#include <stdio.h>
int main()
{
int i = 1, j = 2;
double x = 10.0;
printf("%f\n", x * i / j);
printf("%f\n", i / j * x);
getchar();
return 0;
}

5.000000
0.000000

Die erste Antwort ist richtig, 10*1/2 ist 5. In der zweiten Zeile wird 1/2*10
gerechnet. Als Erstes musst du bedenken, dass von links nach rechts gerechnet
wird, also erst , dann wird mit 10 malgenommen. Man kann die Abfolge auch
als (1/2)*10 schreiben. Aber warum soll das Ergebnis 0 sein?!? Achtung, die
automatische Typenumwandlung luft paarweise ab: Als Erstes wird i/j betrachtet. Fr ganze Zahlen 1 und 2 ergibt diese Division, wie wir wissen, 0. Dann wird
0/x gerechnet, und weil x eine double ist, wird 0 zu 0.0, dann wird 0.0 durch 10.0
geteilt, was 0.0 ergibt. Alles ganz logisch und berhaupt nicht, was wir wollten.
Um mit die Kommazahl 0.5 zu berechnen, kannst du z. B. 1.0/2.0, 1.0/2 oder
1/2.0 schreiben. Fr Variablen bentigen wir ein Cast:
((double) i) / ((double) j)

Ein (double) ist aber genug, denn die zweite Umwandlung ist automatisch:
((double) i) / j
i / ((double) j)
(double) i / j
i / (double) j

Probier das mal aus. Obwohl (double) Vorrang vor / hat (siehe Anhang A.0),
ist es klarer, ((double) i) zu schreiben.
Whrend Variablen immer ausdrcklich mit einem Typ definiert werden, gilt bei
Zahlkonstanten:
Konstanten, die ohne Punkt geschrieben sind wie 1 oder 150, erhalten den
Typ int.
Konstanten mit Punkt erhalten den Typ double. Die Zahl Eins vom Typ
double wird als 1.0 eingegeben.
246

Kapitel 9 Datentypen

Sandini Bib

9.5 Konstante Variable mit const


In Kapitel 9.2 haben wir besprochen, dass sich aus den elementaren Datentypen
wie int durch Voranstellen von bestimmten Schlsselwrtern wie long neue
Typen ableiten lassen. Wenn einer Variablen bei der Definition ein Wert zugewiesen wird, der sich nicht mehr ndern darf, kann man const vorausstellen,
z. B.
const int wochentage = 7;
const double pi = 3.141;

Obwohl konstante Variable nach einem Widerspruch in sich klingt, ist das dennoch sinnvoll, denn jetzt kann man einen Namen statt der Zahlen verwenden.
ndert sich die Zahl, braucht man nur diese eine Stelle zu ndern.
Namen fr Konstanten kannst du auch mit der Preprocessoranweisung #define
definieren, siehe Anhang A.1. Weil const int n aber eine echte C-Variable bezeichnet, kann sie auch in Funktionsprototypen verwendet werden. Ein Prototyp
wie
int f(const int n);

verhindert, dass innerhalb der Funktion f der Wert von n gendert werden kann.

9.6 Neue Namen fr alte Typen: typedef


Mit typedef kannst du einem schon vorhandenen Datentyp einen neuen Namen
geben. Nach
typedef int INT;

kannst du berall dort, wo int erlaubt ist, INT schreiben. Wird berall INT verwendet, knnte mit typedef long INT; das ganze Programm auf long umgestellt werden. Windows macht von typedef im groen Stil Gebrauch. In BCB
findest du die Headerdatei windef.h, die von windows.h eingelesen wird. Hier
stehen sinnige Typdefinitionen wie
typedef
typedef
typedef
typedef
typedef

int INT;
unsigned
unsigned
unsigned
unsigned

int UINT;
char BYTE;
short WORD;
long DWORD:

Solche Definitionen kann man ineinander einsetzen, z. B.


typedef UINT WPARAM;

Ich erklre dir das eigentlich nur, damit der Typenwirrwarr in Windows nicht so
mysteris aussieht. Vielleicht findest du aber auch in deinen eigenen Programmen einen guten Grund, typedef zu verwenden. Besonders ntzlich ist typedef
fr Strukturen, siehe 10.4.
9.5 Konstante Variable mit const

247

Sandini Bib

9.7

Mathematische Funktionen

Ein wichtiges Beispiel fr die Verwendung des Datentyps double sind mathematische Funktionen wie die Wurzelfunktion, die in der Math Library von C
enthalten sind. Dort findest du viele Rechenoperationen, die ber die Grundrechenarten hinausgehen, zum Beispiel
double sqrt(double x);
double pow(double x, double y);
double sin(double x);
double cos(double x);

Quadratwurzel von x (square root)


x hoch y (power, Potenz)
Sinus von x
Cosinus von x

Alle diese Funktionen berechnen Doubles und ihre Argumente x und y mssen ebenfalls Doubles sein. Weitere mathematische Funktionen findest du in der
BCB-Hilfe unter Bibliotheksreferenz, Bibliotheksroutinen, nach Kategorien
sortiert, Mathematische Routinen.
Weil wir hart im Nehmen sind, geben wir uns nicht vllig mit den abgepackten
Mathefunktionen zufrieden. Das folgende Programm berechnet Wurzeln per Bisektion:
WurzelZiehen.c

#include <stdio.h>
#include <math.h>
int main()
{
double zahl = 2;
double klein = 0;
double gross = zahl;
double d, x;
double genauigkeit = 1e4;
printf("\nDie Wurzel aus %10.6f ist %10.6f\n", zahl, sqrt(zahl));
printf("
x
x*x %.6f
x*x\n", zahl);
while (1) {
x = (klein + gross) / 2;
d = x*x zahl;
printf("%10.6f
%10.6f
%10.6f", x, d, x*x);
if (d < genauigkeit && d > genauigkeit)
break;
if (d > 0)
gross = x;
else
klein = x;
getchar();
}
printf("\n");
getchar();
return 0;
}

248

Kapitel 9 Datentypen

Sandini Bib

Die Wurzel aus


2.000000 ist
x
x*x 2.000000
1.000000
1.000000
1.500000
0.250000
1.250000
0.437500
1.375000
0.109375
1.437500
0.066406
1.406250
0.022461
1.421875
0.021729
1.414062
0.000427
1.417969
0.010635
1.416016
0.005100
1.415039
0.002336
1.414551
0.000954
1.414307
0.000263
1.414185
0.000082

1.414214
x*x
1.000000
2.250000
1.562500
1.890625
2.066406
1.977539
2.021729
1.999573
2.010635
2.005100
2.002336
2.000954
2.000263
1.999918

Ausprobieren! Genau wie beim Zahlenraten in Kapitel 6.12 setzen


wir uns als Erstes eine untere und eine obere Schranke. Die Wurzel
aus 2 kann nicht grer als 2 sein und nicht kleiner als 0.
Dann fangen wir an, eine Zahl x zu raten, fr die x*x gleich zahl gelten soll.
Je nachdem ob die Differenz d = x*x zahl zu gro oder zu klein ist, ndern
wir unsere Schranken und raten als Nchstes wieder die Zahl in der Mitte.
Charakteristisch fr das Rechnen mit Kommazahlen ist, dass wir nicht mit beliebiger Genauigkeit rechnen knnen. Deshalb beschrnken wir uns von vornherein auf eine Genauigkeit bis zur vierten Stelle hinterm Komma und beenden
die Schleife, wenn d entsprechend klein geworden ist.
Wie musst du das Programm ndern, falls die Zahl zahl kleiner als 1 ist?

Die Sinusfunktion

9.8

Die Sinusfunktion berechnen wir in C mit z. B.


y = sin(x);

Genau diese Zeile wollen wir verwenden, um ein Bild von der Sinusfunktion in
ein Grafikfenster zu malen. Das wird bei uns so aussehen:

9.8

Die Sinusfunktion

249

Sandini Bib

Lass dich im Folgenden nicht entmutigen. Du kannst dich auch dann mit der
Sinusfunktion vergngen (siehe Bilder weiter unten), wenn dir einige mathematische Einzelheiten in den Erklrungen unverstndlich sind.
Die Frequenz f ist fr dieses Bild gleich 1.00, was in unserem Fall bedeutet,
dass eine Schwingung pro Fensterbreite ausgegeben wird. Die Sinusfunktion ist
eine periodische Funktion. Wenn x Werte zwischen 0 und 2 annimmt, schwankt
y = sin(x) einmal zwischen 1 und +1. Wenn x noch grer wird, wiederholt
sich alle 2 dieselbe Wellenform.
Wir verwenden dieselbe Datei WinMain.cpp wie in den Beispielen in Kapitel 8. In
der .c-Datei stehen die Funktionen malen und WindowProc. Hier ist die Funktion malen des Programms:

250

Kapitel 9 Datentypen

Sandini Bib
Sinus.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
#include <math.h>
/* externe Variable */
char text[1000];
int imax, jmax;
const double pi = 3.14159265358979323846;
double frequenz = 1.0;
int n = 100;

/* Male im Fenster */
void malen(HDC hdc)
{
double x, y, dx, xmin, xmax, ymin, ymax;
double xfaktor, yfaktor;
int i, j, i0, j0;
// Bereich in x und y, der gezeigt werden soll
xmin = 0;
xmax = 2*pi*frequenz;
ymin = 1;
ymax = +1;
// Intervall in x wird in n Schritte zerlegt
dx = (xmax xmin)/n;
// Zoomfaktor fuer Bildschirmfuellung
xfaktor = imax/(xmaxxmin);
yfaktor = jmax/(ymaxymin);
// Verschiebung im Fenster
i0 = 0;
j0 = jmax/2;
// Ausgangspunkt
MoveToEx(hdc, i0, j0, 0);
// fuer alle x
for (x = xmin; x <= xmax + dx/2; x += dx) {
// Funktion
y = sin(x);
// Umrechnung in Pixel
i = i0 + xfaktor * x;
j = j0 yfaktor * y;
// Linie und Kaestchen
LineTo(hdc, i, j);
Rectangle(hdc, i2, j2, i+3, j+3);
}
}

9.8

Die Sinusfunktion

251

Sandini Bib

Wie malt man Funktionen? In der Schule und ohne Computer geht das z. B. so:
Man macht sich eine Tabelle mit mehreren Werten x und rechnet fr jedes x ein
y aus. Dann nimmt man sich ein Karopapier und malt ein Koordinatensystem
mit x- und y-Achse. Fr jedes x geht man x Schritte auf der x-Achse nach rechts,
und dann y Schritte in Richtung der y-Achse nach oben.
Im Programm siehst du eine Schleife, die genauso vorgeht, nur verwenden wir
statt Karos das Pixelgitter zum Malen. Die Schleife durchluft verschiedene
Werte von x in gleich groen Abstnden dx. Weil wir uns wegen Rundungsfehlern bei Kommazahlen nicht darauf verlassen knnen, dass irgendwann x exakt
gleich xmax wird, fhren wir den Vergleich mit x <= xmax + dx/2 durch. Fr
jedes x berechnen wir y = sin(x). In
i = i0 + xfaktor * x;
j = j0 yfaktor * y;

rechnen wir die Kommazahlen x und y in ganze Zahlen i und j um, die wir
als Koordinaten auf dem Bildschirm verwenden knnen. Gemalt wird eine Linie
zu diesen Koordinaten und ein kleines Kstchen, dass den Punkt bei i und j im
Fenster einrahmt. Das Bild der Funktion entsteht, indem wir fr jeden Wert von
x ein Kstchen malen, dessen Koordinaten von x und y bestimmt werden.
Das Drumherum in malen dient nur dazu, die Koordinaten so hinzubiegen, dass
die Sinusschwingung genau ins Fenster passt. Zunchst legen wir mit xmin,
xmax, ymin und ymax fest, welcher Wertebereich in x und y gezeigt werden soll.
Falls die Variable frequenz gleich 1 ist, ist xmax = 2*pi. Die Variable pi ist eine
konstante externe Variable, die wir auf 3.14... setzen. Falls die Frequenz 2 ist,
wird xmax doppelt so gro. Deshalb erwarten wir, zwei vollstndige Schwingungen zu sehen, doch mehr davon spter.
Die ganzzahlige Variable n bestimmt, mit wie vielen Punkten wir die Sinusfunktion malen wollen. Dazu zerlegen wir das Intervall von xmin bis xmax in n gleich
groe Stcke, die dx = (xmaxxmin)/n lang sind. dx ist die Schrittweite in der
Schleife.
Die Faktoren xfaktor und yfaktor dienen dazu, das Bild auf die Gre des
Bildschirms zu skalieren. In
xfaktor = imax/(xmaxxmin);
yfaktor = jmax/(ymaxymin);

enthalten die externen Variablen imax und jmax die Gre des Fensters in Pixeln.
Schau dir noch mal
i = i0 + xfaktor * x;
j = j0 yfaktor * y;

an. Der Ursprung des x-y-Koordinatensystems, also x = 0 und y = 0,


wird auf i = i0 = 0 und j = j0 = jmax/2 abgebildet. Weil wir y mit
252

Kapitel 9 Datentypen

Sandini Bib

yfaktor = jmax/2 multiplizieren, denn ymaxymin ist 2, wird der Sinus


im Bild zwischen j = 0 und j = jmax schwingen. In j = j0 yfaktor * y
bentigen wir ein Minuszeichen, weil die y-Achse in der Mathematik nach oben
zeigt, im Fenster zeigt die j-Achse aber nach unten.

Mit dem Programm kannst du einige nette Experimente machen, denn in der
Fensterprozedur reagieren wir auf die Pfeiltasten:
Sinus.c WinMain.cpp

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
// Tastatur
if (m == WM_KEYDOWN) {
if (wParam == VK_UP
&& n < 5000)
n = 3*n/2;
if (wParam == VK_DOWN && n > 2)
n = 2*n/3;
if (wParam == VK_LEFT && frequenz < 1024) frequenz *= 2;
if (wParam == VK_RIGHT && frequenz > 0.25) frequenz /= 2;
InvalidateRect(hwnd, 0, 0);
}
// Malen
else if (m == WM_PAINT) {
hdc = GetDC(hwnd);
Rectangle(hdc, 1, 1, imax+1, jmax+1);
malen(hdc);
ReleaseDC(hwnd, hdc);
ValidateRect(hwnd, 0);
sprintf(text, "Sinus: f = %.2f n = %d", frequenz, n);
SetWindowText(hwnd, text);
}
// Verschiedenes
else if (m == WM_SIZE) {
imax = LOWORD(lParam);
jmax = HIWORD(lParam);
}
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Mit / kannst du die Frequenz ndern. Es werden f Schwingungen ins Fenster gezeichnet, z. B. 2 Schwingungen oder oder Schwingung:

9.8

Die Sinusfunktion

253

Sandini Bib

Mit / vernderst du die Anzahl n der Punkte:

Klarer Fall, du musst eine minimale Auflsung einstellen oder der Sinus ist nicht
als Sinus zu erkennen. Je grer die Frequenz, desto mehr Punkte sind ntig:

254

Kapitel 9 Datentypen

Sandini Bib

Wenn die Frequenz zu hoch fr die Punktezahl ist, kannst du hbsche Muster
erzeugen:

Angenommen, die Schwingung wre ein Tonsignal, das du in digitaler Form abspeichern mchest. Unser Beispiel fhrt dir vor Augen, dass eine minimale Absampling rate) bentigt wird, sonst hrt man Tne, die mit dem
tastrate (
eigentlichen Signal nicht mehr viel zu tun haben.
9.8

Die Sinusfunktion

255

Sandini Bib

9.9

Kreis und Rotation

Die Sinusfunktion hat eine Schwester, die Kosinusfunktion. Baue gleich einmal
y = cos(x) in das vorhergehende Beispiel ein. Das Ergebnis ist eine Schwingung, die um eine Periode verschoben ist. Der Witz ist, dass sin und cos
kombiniert werden knnen, um einen Kreis zu zeichnen. Die Formel fr einen
Kreis mit Radius r ist
x = r * cos(alpha);
y = r * sin(alpha);

Hier nennen wir den Winkel alpha. Wenn alpha von 0 bis 2 luft, durchlaufen
x und y Punkte auf einem Kreis. Was, das glaubst du nicht? Hier ist die Funktion
malen einer nur leicht abgenderten Version des Programms aus Kapitel 9.8:

256

Kapitel 9

Datentypen

Sandini Bib
Kreis0.c WinMain.cpp

/* Male im Fenster */
void malen(HDC hdc)
{
double alpha, dalpha;
double x, y, xmin, xmax, ymin, ymax;
double xfaktor, yfaktor;
int i, j, i0, j0;
// Bereich in x und y, der gezeigt werden soll
xmin = 1;
xmax = +1;
ymin = 1;
ymax = +1;
// Einmal rum wird in n Schritte zerlegt
dalpha = 2*pi/n;
// Zoomfaktor fuer Bildschirmfuellung
xfaktor = imax/(xmaxxmin);
yfaktor = jmax/(ymaxymin);
// Verschiebung im Fenster
i0 = imax/2;
j0 = jmax/2;
// Ausgangspunkt
MoveToEx(hdc, i0+xfaktor*r, j0, 0);
// fuer alle alpha
for (alpha = 0; alpha <= 2*pi + dalpha/2; alpha += dalpha) {
// Funktion
x = r * cos(alpha);
y = r * sin(alpha);
// Umrechnung in Pixel
i = i0 + xfaktor * x;
j = j0 yfaktor * y;
// Linie und Kaestchen
if (maleradius)
MoveToEx(hdc, i0, j0, 0);
LineTo(hdc, i, j);
Rectangle(hdc, i2, j2, i+3, j+3);
}
}

Diesmal durchlaufen wir eine Schleife in alpha. Das Programm malt Kreise wie
in
9.9

Kreis und Rotation

257

Sandini Bib

Weil wir nach wie vor das Bild auf fensterfllend skalieren, wird der Kreis zur
kannst du
Ellipse, wenn das Fenster nicht quadratisch ist. Mit der Leertaste
umschalten, ob die Linien im Kreis herum fhren oder ob nach jedem Schritt der
Stift wieder ins Zentrum bewegt wird, was Sterne aus vielen Linien ergibt. Mit
/ nderst du die Anzahl der Punkte, mit / den Radius. Versuche einmal
einen Stern zu zeichnen, dessen Radius grer als das Fenster ist. Bei sehr vielen
Punkten bilden die Pixel der Linien interessante Muster.
Mit Kreisen geht es rund. Wenn du den Kreis nicht auf einmal mit einer Schleife
ber alle alpha ausgibst, sondern alpha mit einem Zeitgeber schrittweise grer
machst, bekommst du eine Animation eines Zeigers, der gegen den Uhrzeigersinn luft:

258

Kapitel 9 Datentypen

Sandini Bib

Hier ist das Programm dazu:


Kreis1.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
#include <math.h>
/* externe Variable */
char text[1000];
int imax, jmax;
const double pi = 3.14159265358979323846;
double alpha = 0.0;
double dalpha = 0.0;
double r = 1.0;
int n = 100;
int timeran;
int dt = 100;

9.9

Kreis und Rotation

259

Sandini Bib
Kreis1.c WinMain.cpp

/* Male im Fenster */
void malen(HDC hdc)
{
double x, y, xmin, xmax, ymin, ymax;
double xfaktor, yfaktor;
int i, j, i0, j0;
// Bereich in x und y, der gezeigt werden soll
xmin = 1;
xmax = +1;
ymin = 1;
ymax = +1;
// Einmal rum wird in n Schritte zerlegt
dalpha = 2*pi/n;
// Zoomfaktor fuer Bildschirmfuellung
xfaktor = imax/(xmaxxmin);
yfaktor = jmax/(ymaxymin);
// Verschiebung im Fenster
i0 = imax/2;
j0 = jmax/2;
// alpha soll im Intervall 0 bis 2*pi bleiben
if (alpha < dalpha/2)
alpha = 2*pi dalpha;
if (alpha > 2*pi + dalpha/2) alpha = dalpha;
// Funktion
x = r * cos(alpha);
y = r * sin(alpha);
// Umrechnung in Pixel
i = i0 + xfaktor * x;
j = j0 yfaktor * y;
// Linie und Kaestchen
MoveToEx(hdc, i0, j0, 0);
LineTo(hdc, i, j);
Rectangle(hdc, i2, j2, i+3, j+3);
}

260

Kapitel 9 Datentypen

Sandini Bib
Kreis1.c WinMain.cpp

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
// Fenster auf
if (m == WM_CREATE) {
SetTimer(hwnd, 0, dt, 0);
timeran = 1;
}
// Timer
else if (m == WM_TIMER) {
alpha += dalpha;
InvalidateRect(hwnd, 0, 0);
}
// Tastatur
else if (m == WM_KEYDOWN) {
if (wParam == VK_PRIOR) alpha += dalpha;
if (wParam == VK_NEXT) alpha = dalpha;
if (wParam == VK_UP
&& n < 5000)
n = 3*n/2;
if (wParam == VK_DOWN && n > 2)
n = 2*n/3;
if (wParam == VK_LEFT && r < 10000) r *= 1.1;
if (wParam == VK_RIGHT && r > 0.0001) r /= 1.1;
if (wParam == VK_SPACE) {
timeran = !timeran;
if (timeran) SetTimer(hwnd, 0, dt, 0);
else
KillTimer(hwnd, 0);
}
InvalidateRect(hwnd, 0, 0);
}
// Malen
else if (m == WM_PAINT) {
hdc = GetDC(hwnd);
Rectangle(hdc, 1, 1, imax+1, jmax+1);
malen(hdc);
ReleaseDC(hwnd, hdc);
ValidateRect(hwnd, 0);
sprintf(text, "Sinus: r = %.3f n = %d", r, n);
SetWindowText(hwnd, text);
}
// Verschiedenes
else if (m == WM_SIZE) {
imax = LOWORD(lParam);
jmax = HIWORD(lParam);
}
else if (m == WM_DESTROY) {
if (timeran) KillTimer(hwnd, 0);
PostQuitMessage(0);
}
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

9.9

Kreis und Rotation

261

Sandini Bib

In der Funktion malen schreiben wir


if (alpha < dalpha/2)
alpha = 2*pi dalpha;
if (alpha > 2*pi + dalpha/2) alpha = dalpha;

damit der Winkel alpha das Intervall von 0 bis 2 nicht verlsst. Wie du siehst,
habe ich die Schleife aus malen entfernt. In der Funktion WindowProc kannst
du ablesen, mit welchen Tasten das Programm bedient werden kann. Insbesondere kannst du den Zeitgeber mit der Leertaste anhalten und starten. Mit
Bild / Bild kannst du per Hand den Zeiger vorwrts oder rckwrts kreisen
lassen (Taste gedrckt halten).
Eigentlich ist so ein Zeiger nicht besonders aufregend. Aber jetzt kommt der
Clou. Mit demselben Programm kannst du nach kleinen nderungen beliebige
Strichgrafiken rotieren lassen! Mit C gehts rund:

Der Trick ist der folgende. Angenommen, jemand gibt dir die Koordinaten eines
Punktes, x0 und y0. Wie bekommst du die Koordinaten dieses Punktes, wenn du
die Zeichenebene um den Winkel alpha um den Ursprung drehst? Die Formel
ist
x1 =
y1 =

cos(alpha) * x0
sin(alpha) * x0

sin(alpha) * y0;
cos(alpha) * y0;

Das ist auch nicht viel schwieriger, als was wir bisher gerechnet haben. Wenn du
x0 = r und y0 = 0 setzt, bekommst du
x1 =
y1 =

cos(alpha) * r;
sin(alpha) * r;

Das ist die Formel, die wir fr den Kreis verwendet haben! Hier sind die wesentlichen nderungen im Programm fr den Zeiger:

262

Kapitel 9 Datentypen

Sandini Bib
Rotation.c WinMain.cpp

/* Polygon */
#define N 11
double x0[N] = { 0.0, 1.0, 1.0,0.5,1.5,1.5,0.5, 1.0, 1.0, 0.0,0.5};
double y0[N] = { 1.0, 1.0, 2.0, 2.0, 1.0,1.0,2.0,2.0,1.0,1.0, 0.0};
double x1[N];
double y1[N];
/* Transformation */
void transformation(void)
{
int npunkt;
for (npunkt = 0; npunkt < N; npunkt++) {
x1[npunkt] = cos(alpha) * x0[npunkt] sin(alpha) * y0[npunkt];
y1[npunkt] = sin(alpha) * x0[npunkt] + cos(alpha) * y0[npunkt];
}
}
/* Male im Fenster */
void malen(HDC hdc)
{
...
// transformiere Polygon
transformation();
// Stift auf letzten Punkt
i = i0 + faktor * x1[N1];
j = j0 faktor * y1[N1];
MoveToEx(hdc, i, j, 0);
// fuer alle Punkte
for (npunkt = 0; npunkt < N; npunkt++) {
// Umrechnung in Pixel
i = i0 + faktor * x1[npunkt];
j = j0 faktor * y1[npunkt];
// Linie und Kaestchen
LineTo(hdc, i, j);
Rectangle(hdc, i2, j2, i+3, j+3);
}
}

Ein Polygon ist ein Vieleck. Wir definieren Felder x0 und y0 fr die Koordinaten von 11 Punkten. Diese initialisieren wir gleich in der Definition mit einer
Liste aus Zahlen, die die Koordinaten fr den Buchstaben C ergeben. In den Feldern x1 und y1 wollen wir die Koordinaten nach der Rotation speichern. Diese
Transformation erledigen wir in der Funktion transformation, die wir in malen
aufrufen, bevor mit dem eigentlichen Malen begonnen wird. Mit einer Schleife
9.9

Kreis und Rotation

263

Sandini Bib

ber alle Punkte transformieren wir jeden Punkt nach der oben angegebenen
Formel.
Beim Malen verwenden wir die tranformierten Punkte x1[npunkt] und
y1[npunkt]. Probehalber kannst du auch die Felder x0 und y0 einsetzen. Damit
sich eine geschlossene Kurve ergibt, setzen wir den Stift vor dem Beginn der
Schleife ber alle Punkte auf den Punkt mit Index N1.
Und dann geht es mit C rund!

9.10

Farbtiefe

Ein Beispiel, bei dem die Anzahl der verwendeten Bits und Bytes eine Rolle
spielt, ist die Farbdarstellung in Windows. Mit einer Schleife knnen wir leicht
alle verschiedenen Farben durchprobieren, also schauen wir uns im folgenden
Beispiel alle Rot- und Grn-Mischungen auf einmal an, die wir mit RGB erzeugen
knnen:
Farbtiefe.c WinHallo.cpp

#include <windows.h>
void malen(HDC hdc)
{
int i, j;
for (i = 0; i < 256; i++)
for (j = 0; j < 256; j++)
SetPixel(hdc, i, j, RGB(i,j,0));
}

264

Kapitel 9 Datentypen

Sandini Bib

Hier zeige ich nur die Funktion malen. Wie kommt es, dass die Abbildung im
Buch und womglich auch die auf deinem Bildschirm kleine Kstchen zeigt? Das
hngt davon ab, wieviele Bit pro Pixel verwendet werden. Die Anzahl der Bit pro
Pixel nennt man auch die Farbtiefe der Bildschirmdarstellung.
Wenn nur 8 Bit pro Pixel verwendet werden, sind nur 256 verschiedene Farben
mglich. Bei 16 Pixeln sind es 256 mal 256 gleich 65536 verschiedene Farben.
Im Beispiel fordern wir 256 mal 256 verschiedene Rot- und Grn-Mischungen
an. Selbst bei 16 Bit Farbtiefe wird das ein Problem ergeben, denn Blau soll es ja
auch noch geben.
Bei Farbwerten in Windows, die du mit RGB erzeugst, gibst du drei Byte an, die
die Helligkeitsabstufungen der drei Grundfarben bestimmen. Bei 8 Bit Farbtiefe
stehen aber z. B. nur 3 plus 3 plus 2 Bit pro Farbe zur Verfgung. Fr eine direkte Farbdarstellung bentigst du bei drei Bytes fr RGB mindestens 24 Bit. Bei
32 Bit Farbtiefe werden gewhnlich noch 8 Bit fr die Durchsichtigkeit, den so
genannten Alpha Channel, verwendet.
Eine weitere Methode ist die Verwendung von Farbpaletten. Farben werden dann
nicht durch Helligkeitswerte beschrieben, sondern durch einen Zahlencode. Eine
Farbpalette ist im Wesentlichen eine bersetzungstabelle, die z. B. dem Code 1
die Farbe RGB(255,0,0) zuordnet und dem Code 2 die Farbe RGB(0,255,0).
Auf diese Weise kann man erreichen, dass selbst bei 8 Bit Farbtiefe 256 beliebige
Farben aus einer viel greren Palette mit allen RGB Farben ausgewhlt werden knnen. Bei Bildern aus Pixeln, so genannten Bitmaps, ist das eine beliebte
Methode.
Wir werden weiterhin darauf vertrauen, dass Windows automatisch fr jede Grafikeinstellung eine vernnftige Nherung fr die angeforderten RGB-Werte bereitstellt. Wenn du unternehmungslustig bist und dich mit der Bedienung von
Windows auskennst, kannst du das Beispiel bei verschiedenen Farbeinstellungen
von Windows laufen lassen. Ich bin mir nicht sicher, ob ich dich dazu ermuntern
sollte nachher beschwert sich jemand, dass du den Computer vllig durcheinander gebracht hast. Bitte nicht vergessen, den Urzustand wieder herzustellen.

9.11

Mandelbrot-Menge

Fraktale hat jeder schon gesehen und ein schnes Beispiel dafr ist die so genannte Mandelbrot-Menge, die von Benoit Mandelbrot 1980 entdeckt wurde. In
diesem Beispiel mchte ich die Mandelbrot-Menge aus der Sicht eines Programmierers beschreiben.
Nichts, aber wirklich gar nichts, lsst einen erahnen, was sich hinter dieser einfachen Anweisung versteckt:
x = x*x + c

9.11

Mandelbrot-Menge

265

Sandini Bib

Wir geben uns eine Konstante c vor, setzen x gleich 0, und dann wiederholen
wir diese Anweisung viele Male.
Denke ein wenig darber nach. Die ersten drei Werte von x sind
x
x
x
x

=
=
=
=

0
c
c*c + c
(c*c + c)*(c*c + c) + c

Klarer Fall, wenn c gleich 0 ist, berechnen wir x = 0*0 + 0 = 0, also bleibt x
gleich 0. Wenn c gleich 1 ist, durchluft x die Werte 0, 1, 2, 5, 26, 677, . . ., das
heit, die Zahlen werden immer schneller immer grer.
Wenn c kleiner als 1, aber grer als 0 ist, ist nicht ganz klar, was passieren wird.
Einerseits wird eine solche Zahl beim Quadrieren kleiner, z. B. ist fr c = 0.5
das Quadrat c*c = 0.25. Andererseits sollen wir dann noch c dazuzhlen, also
wird die Zahl wieder grer. Wir erhalten
x = c*c + c = 0.5*0.5 + 0.5 = 0.75

Wie du leicht siehst, werden fr c = 0.5 die Zahlen immer grer, aber fr
c = 0.1 sieht es so aus, als ob die Zahlenfolge klein bleibt, weil das Quadrat nur
0.01 ist.
Wie steht es mit Zahlen c < 0? Bei c = 1 ist die Sache klar, x durchluft 0,
1, 0, 1, 0, 1, und so weiter, denn
x = c*c + c = (1)*(1) 1 = 0

Wenn aber c = 1.1, dann Moment mal, wozu sind wir Programmierer?
Sptestens an dieser Stelle schreibst du dir ein Minitextprogramm zum Testen:
Mandelbrot0.c

#include <stdio.h>
int main()
{
double c = 0.5;
double x = 0.0;
while (1) {
x = x*x + c;
if (x > 100000) break;
printf("c = %.3f,
getchar();
}
return 0;
}

266

Kapitel 9 Datentypen

x = %10.3f

", c, x);

Sandini Bib

c
c
c
c
c
c
c
c
c
c
c
c
c
c
c

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,
1.100,

z
z
z
z
z
z
z
z
z
z
z
z
z
z
z

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

1.100
0.110
1.088
0.084
1.093
0.095
1.091
0.090
1.092
0.092
1.092
0.091
1.092
0.092
1.092

Oder auch:
c
c
c
c
c
c
c
c

=
=
=
=
=
=
=
=

0.500,
0.500,
0.500,
0.500,
0.500,
0.500,
0.500,
0.500,

z
z
z
z
z
z
z
z

=
=
=
=
=
=
=
=

0.500
0.750
1.062
1.629
3.153
10.444
109.567
12005.476

Was passiert bei c = 2? Was bei c = 10? Wenn die Zahl zu negativ wird,
macht das Quadrieren x so gro, dass die Zahlen wieder beliebig gro werden.
Wir fassen zusammen: Fr manche c hpft die Zahlenfolge munter in Richtung
unendlich, fr andere c bleiben die Zahlen klein.
So weit, so gut. Benoit Mandelbrot hat aber nicht mit reellen Zahlen gerechnet, sondern mit den so genannten komplexen Zahlen. Fr unser Beispiel musst
du komplexe Zahlen nicht kennen, denn die Iteration ist fast so einfach wie
x = x*x + c, nur dass wir zwei Zahlenfolgen gleichzeitig berechnen. Wir geben
uns zwei Konstanten c und d vor, setzen x und y gleich 0 und iterieren
xneu = x*x y*y + c
yneu = 2*x*y
+ d

wobei im nchsten Schritt x = xneu und y = yneu sein soll.


Na gut, wirst du sagen, das Ergebnis kannst du erahnen. Fr manche c und d
hpfen die Zahlen x und y auf und davon, fr andere bleiben sie klein. Das
knntest du mit einem kleinen Programm testen. Aber hier ist ein viel besserer
Vorschlag:
Verwende c und d als die Koordinaten eines Punktes.
9.11

Mandelbrot-Menge

267

Sandini Bib

Fr jeden Punkt male ein schwarzes Pixel auf dem Bildschirm, wenn die Zahlenfolge klein bleibt, und male ein buntes Pixel, wenn die Zahlenfolge nach
unendlich hpft.
Das ergibt ein Bild der so genannten Mandelbrot-Menge. Die schwarzen Punkte
sind in der Menge, die bunten nicht. Und was du ohne spezielles Vorwissen und
trotz unserer netten kleinen Vorbereitung nicht erahnen kannst, ist das verrckte
Bild, das dich erwartet:

Wie seltsam hbsch. Das Programm, das ich gleich beschreiben werde, hat eine
Zoomfunktion. Wenn du in das Bild klickst, wird ein Ausschnitt vergrert. Das
mache ich bei kleinem Fenster, weil die Rechnerei eine Weile dauert. Mit den
Pfeiltasten kannst du Schrfe und Farbgebung einstellen. Das sieht dann z. B. so
aus:

268

Kapitel 9 Datentypen

Sandini Bib

Ein Merkmal dieser Fraktale ist, dass sich Strukturen auf kleineren Skalen immer
wieder wiederholen. Hier ist ein etwas anderes Bild,

und es gibt jede Menge schne Ansichten.


Das Programm ist nicht besonders kompliziert. Wir verwenden wieder die
WinMain Funktion aus Kapitel 8 zusammen mit einer selbst gemachten
WindowProc und verschiedenen Helferfunktionen. In

9.11

Mandelbrot-Menge

269

Sandini Bib
Mandelbrot.c WinMain.cpp

#include <windows.h>
#include <math.h>
#include <stdio.h>
/* externe Variable */
HDC hdc;
char text[1000];
int imax, jmax;
double xmin, ymin, dpixel;
/* Parameter fuer MandelbrotMenge */
double xmitte = 0.7;
double ymitte = 0.0;
double breite = 3.5;
int itermax
int zoomfaktor
int nzoom
double pfarbe

=
=
=
=

10;
2;
0;
1.0;

siehst du die ntigen Includeanweisungen und die externen Variablen. Es folgt


die Mandelbrot-Iteration, die wir in der Funktion mandelbrot verpackt haben:
Mandelbrot.c WinMain.cpp

/* MandelbrotIteration */
int mandelbrot(double c, double d)
{
int i;
double x = 0, y = 0, x2, y2;
for (i = 0; i
x2 = x*x;
y2 = y*y;
if (x2 + y2
y = 2*x*y +
x = x2 y2
}
return i;

< itermax; i++) {

> 1000000) break;


d;
+ c;

Wir fhren mehrere Iterationen aus. Als Erstes berechnen wir das Quadrat von
x und y. Falls die Summe der Quadrate zu gro wird, verlassen wir die Schleife.
Falls nicht, berechnen wir die neuen Werte von x und y und die Iteration beginnt
von vorne. Verstehst du, warum wir auf temporre Variablen xneu und yneu
verzichten knnen? Insbesondere drfen die Zeilen x = ... und y = ... nicht
vertauscht werden.
Die Schleife wird entweder beendet, wenn x und y zu gro werden oder wenn die
maximale Anzahl von itermax Iterationen erreicht wurde. In beiden Fllen ist
270

Kapitel 9 Datentypen

Sandini Bib

der Rckgabewert von mandelbrot die Anzahl der Iterationen, die ausgefhrt
wurden. Diese Anzahl verwenden wir, um die Farbe des Pixels fr x und y zu
bestimmen. Eine Mglichkeit wre
/* Farbgebung abhaengig von Anzahl der Iterationen */
COLORREF farbe(int i)
{
if (i >= itermax) return RGB(0, 0, 0);
return RGB(256*i/itermax, 0, 0);
}

Wenn itermax Iterationen ausgefhrt wurden, soll die Farbe schwarz sein. Natrlich knnte es sein, dass erst nach noch mehr Iterationen die Zahlen zu gro
werden und der Punkt zu Unrecht schwarz wurde. Deshalb kann im Programm
die maximale Anzahl der Iterationen mit den Pfeiltasten eingestellt werden.
Falls weniger Iterationen als itermax ausgefhrt wurden, soll das Pixel auf jeden
Fall bunt werden. 256*i/itermax ist eine Zahl von 0 bis 255 (trotz ganzzahliger
Division, weil erst malgenommen wird). Je nher i der Zahl itermax kommt,
desto heller das Rot. Dadurch wird der bergang von Weghpfen zu Kleinbleiben
scharf hervorgehoben. Eine etwas raffiniertere Version der Farbfunktion ist
Mandelbrot.c WinMain.cpp

/* Farbgebung abhaengig von Anzahl der Iterationen */


COLORREF farbe(int i)
{
int rot;
if (i >= itermax) return RGB(0, 0, 0);
rot = 256 * pow((double) i/itermax, pfarbe);
return RGB(rot, 0, 0);
}

Hier kann mit der externen Variablen pfarbe die Farbgebung ber die Tastatur
gesteuert werden. Beachte, wie wir aus der ganzen Zahl i eine Double machen,
dann die Funktion pow aufrufen, die eine Double liefert, und das Ergebnis wird
durch Zuweisung an die Integervariable rot wieder zur ganzen Zahl.
Die Pixel malen wir mit

9.11

Mandelbrot-Menge

271

Sandini Bib
Mandelbrot.c WinMain.cpp

/* Male MandelbrotMenge */
void malen(HDC hdc)
{
int i, j, m;
double x, y;
dpixel = breite/imax;
xmin = xmitte imax*dpixel/2;
ymin = ymitte jmax*dpixel/2;
for (i = 0; i < imax; i++) {
for (j = 0; j < jmax; j++) {
x = i * dpixel + xmin;
y = j * dpixel + ymin;
m = mandelbrot(x, y);
SetPixel(hdc, i, j, farbe(m));
}
}
}

In diesem Beispiel nennen wir die Gre des Fensters imax und jmax. Wie
du siehst, malen wir mit der doppelten Schleife jedes Pixel des Fensters mit
SetPixel in einer Farbe, die durch die Anzahl der Iterationen in mandelbrot
bestimmt wird.
Das Drumherum ist ein typisches Problem der Koordinatenumrechnung. Jedes
Pixel im Fenster mit ganzzahligen Koordinaten i und j soll einen Punkt in den
reellen Koordinaten x und y darstellen. Betrachte die Zeilen
x = i * dpixel + xmin;
y = j * dpixel + ymin;

Hier wird munter von Int nach Double umgewandelt. Wenn i in einer
Schleife von 0 bis imax luft, luft x von xmin bis zu einem xmax von
imax * dpixel + xmin, und entsprechend fr j und y. Wir haben also ein
Rechteck in i-j-Koordinaten (das Fenster) auf ein Rechteck in x-y-Koordinaten
abgebildet.
Der Abstand von Pixel zu Pixel ist demnach dpixel, und wir erhalten ihn, indem
wir die Breite breite des Ausschnitts der Mandelbrot-Menge durch die Anzahl
der Pixel in der i-Richtung teilen. Den Ursprung xmin und ymin bestimmen wir
aus zwei Variablen xmitte und ymitte, denn wir wollen ja in das Bild klicken
knnen und die angeklickten Koordinaten sollen die Mitte des neuen Ausschnitts
sein.
Die Fensterprozedur des Programms bietet nichts Besonderes, auer dass beim
Linksklick und beim Rechtsklick mit der Maus die Mauskoordinaten nach derselben Formel wie in malen in die Koordinaten fr die Mitte des neuen Rechtecks
umgerechnet werden:
272

Kapitel 9 Datentypen

Sandini Bib
Mandelbrot.c WinMain.cpp

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
hdc = GetDC(hwnd);
SelectObject(hdc, CreateSolidBrush(0));
}
// Malen
else if (m == WM_PAINT) {
sprintf(text, "Mandelbrot Menge, nzoom = %d, itermax = %d",
nzoom, itermax);
SetWindowText(hwnd, text);
malen(hdc);
ValidateRect(hwnd, 0);
}
// Maus
else if (m == WM_LBUTTONDOWN) {
xmitte = LOWORD(lParam) * dpixel
ymitte = HIWORD(lParam) * dpixel
breite /= zoomfaktor;
nzoom++;
InvalidateRect(hwnd, 0, 0);
}
else if (m == WM_RBUTTONDOWN) {
xmitte = LOWORD(lParam) * dpixel
ymitte = HIWORD(lParam) * dpixel
breite *= zoomfaktor;
nzoom;
InvalidateRect(hwnd, 0, 0);
}
// Tastatur
else if (m == WM_KEYDOWN)
if (wParam == VK_UP)
if (wParam == VK_DOWN)
if (wParam == VK_LEFT)
if (wParam == VK_RIGHT)
InvalidateRect(hwnd, 0,
}

{
itermax
itermax
pfarbe
pfarbe
0);

+ xmin;
+ ymin;

+ xmin;
+ ymin;

*=
/=
*=
/=

2;
2;
1.5;
1.5;

// Verschiedenes
else if (m == WM_SIZE) {
imax = LOWORD(lParam);
jmax = HIWORD(lParam);
}
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

9.11

Mandelbrot-Menge

273

Sandini Bib

In diesem Kapitel kann ich auch erklren, was in


imax = LOWORD(lParam);
jmax = HIWORD(lParam);

vor sich geht. Wie wir jetzt wissen, bezeichnet ein Wort (word) 2 Bytes und
lParam besteht wohl aus 4 Bytes. Mit LOWORD holen wir uns die zwei niedrigen (low) Bytes aus lParam, mit HIWORD die zwei hohen (high) Bytes. Fr
die Fensternachricht wurden die zwei Mauskoordinaten in nur einer Variablen
verpackt.

9.12
Jetzt weit du womglich mehr ber Datentypen in C, als du jemals wissen
wolltest. Dein Computer rechnet mit Bits und Bytes, also begegnen sie dir auch
in der Programmiersprache C, wenn es um den Zahlenbereich von Variablen
oder die Typenumwandlung geht. Auf jeden Fall erlaubt aber die Kombination
von Integerzahlen und Kommazahlen jede Menge interessante Grafikbeispiele.
Zur Zusammenfassung:
Wichtige Datentypen in C sind char fr kleine Zahlen und Zeichen, int fr
ganze Zahlen und double fr Kommazahlen.
Jeder Datentyp unterliegt Einschrnkungen in Gre und Genauigkeit.
Zahlkonstanten wie 1 oder 20 erhalten den Typ int. Kommazahlen bentigen eine Punkt wie in 2.5 und erhalten den Typ double.
Man muss 1.0/2.0 schreiben, wenn das Ergebnis 0.5 sein soll.
Datentypen wie int und double werden bei Bedarf automatisch ineinander umgewandelt. Man kann die Umwandlung durch z. B. (int) x oder
(double) i erzwingen.

9.13
1. Schreibe ein Programm, das jedes Zeichen in einem vordefinierten String
einzeln ausgibt, und zwar als Zahl und als Zeichen. Die printf Formatanweisung fr char ist %c. Schreibe auch eine Schleife, die alle Chars von 0 bis
255 ausgibt. Darunter sind einige Steuerzeichen wie \n.
2. Beschleunige den Primzahltest aus Kapitel 6.10 durch folgenden Trick: Teste
nur solche i, die kleiner gleich der Wurzel von zahl sind. Falls die Zahl einen
Teiler i hat, gilt zahl = i * j. Entweder ist i gleich j und zahl ist eine
echte Quadratzahl. Oder eine der Zahlen i und j ist grer und die andere
kleiner als die Wurzel x von zahl. Wenn wir alle ganzen Zahlen i kleiner
274

Kapitel 9 Datentypen

Sandini Bib

gleich x schon durchprobiert haben, ohne einen Teiler zu finden, kann es


fr grere i auch keinen Teiler mehr geben, denn dann htten wir ja den
dazugehrige Faktor j schon unter den Zahlen gefunden, die kleiner als die
Wurzel sind. Funktioniert dein Programm bei 49 = 7 * 7?
3. Stelle die rekursive Berechnung von n Fakultt in Kapitel 7.11 auf den Datentyp double um. Bei welchem Wert findet der Overflow statt? Ein kleiner
Tipp: mit i = x + 0.5; kannst du erreichen, dass eine Kommazahl x, die

wegen Ungenauigkeiten der Kommarechnung knapp unter der gewnschten


ganzen Zahl i liegt, tatschlich diese Zahl ergibt.
4. Mit einer kleinen nderung des Programms zum Kreisezeichnen bekommst

du Bilder wie dieses:

Die Titelleiste gibt einen Hinweis. Hier habe ich nicht einen konstanten Radius r verwendet, sondern
r = r0 * pow(cos(4*alpha), 2.0)

Experimentiere mit verschiedenen Parametern und erzeuge hnliche Bilder.


5. Lass das C um seine rechte obere Ecke rotieren. Dazu verschiebst du vor der
Rotation die Punkte in x0 und y0 um dx0 und dy0, so dass diese Ecke im

Ursprung liegt. Dann rotierst du. Dann machst du die Verschiebung wieder
rckgngig:

9.13

275

Sandini Bib

6. Lese in Anhang A.1 darber nach, wie Funktionsmakros definiert werden.


RGB ist ein solches Makro, mit dem drei Bytes in einer ganzen Zahl verpackt

werden. Seine Definition findest du in der Hilfe zum Windows SDK unter
RGB. Jetzt kannst du versuchen herauszufinden, wie die Bitoperatoren aus 9.1

die drei Bytes verpacken.


7. Denke dir andere Farben fr das Mandelbrot-Programm aus. Eine nette Va-

riante ist, in bestimmten Abstnden zwischen zwei oder mehr Farben umzuschalten.
8. ndere das Mandelbrot-Programm wie folgt. Fhre die erste Iteration fr alle

Punkte aus, dann male alle Punkte, die weggehpft sind. Dann fhre die nachfolgende Iteration nur noch fr die Punkte aus, die brig sind, und so fort.
Auf diese Weise wird das Bild nicht linienweise aufgebaut, sondern kristallisiert sich nach und nach heraus. Per Mausklick knntest du die Rechnerei
anhalten, wenn du mit dem Ergebnis zufrieden bist. Vorsicht, du bentigst
einigen Speicherplatz, um die Zwischenergebnisse fr verschiedene Punkte
abzuspeichern.
9. Eine andere Zeichenvariante im Mandelbrot-Programm ist, fr einzelne

Punkte den Weg der Zahlenfolge durch Linien zu verfolgen. Es gibt brigens wesentlich effizientere Methoden, die Mandelbrot-Menge und ihren
Verwandten zu berechnen. Dazu kannst du sicher im Internet oder in einer
Bcherei Informationen finden.

276

Kapitel 9 Datentypen

Sandini Bib

10
Zeiger und Strukturen
10.0
10.1
10.2
10.3
10.4
10.5
10.6

Zeiger
Zeiger und malloc
Zeiger, Felder und wie man mit Adressen rechnet
Zeiger, Variablen, Funktionen
Strukturen
Zeiger auf Strukturen

279
282
285
286
290
292

Bibliotheksfunktionen fr Zeichenketten
und Felder

296

10.7

Zeichenketten kopieren

297

10.8

Felder aus Zeigern und main mit


Argumenten

299

10.9

Dateien lesen und schreiben

301

10.10

WinMain

304

10.11

309

10.12

310

Sandini Bib

C zeichnet sich dadurch aus, dass dem Programmierer so gut wie keine Grenzen
gesetzt sind. Er kann per Software fast alles mit dem Computer anstellen, was
er will. Natrlich hat da das Betriebssystem, in unserem Fall Windows, noch
ein Wrtchen mitzureden, aber davon abgesehen kann C fast alles. Und das mit
Abstand praktischste, mchtigste, aber auch gefhrlichste Werkzeug, das C dem
pointer).
Programmierer in die Hnde gibt, sind Zeiger (
Ein Zeiger erlaubt es, direkt auf den Speicher zuzugreifen. Abstrakt kannst du
dir den Arbeitsspeicher deines Computers (das RAM, random access memory)
so vorstellen, dass alle Bytes vom ersten bis zum letzten durchnummeriert sind.
Konkret fngt man mit 0 an und zhlt 0, 1, 2, 3 usw. bis zum letzten der 128 MB
oder so. Der Mikroprozessor greift auf einzelne Bytes zu, indem er sie per Nummer beim RAM abfragt. Man redet nicht von Nummern wie beim Telefon, sondern von Adressen. Der Prozessor liest und schreibt im RAM unter Verwendung
der Adressen von verschiedenen Speicherzellen. Bisher haben wir Variable verwendet, um auf gespeicherte Daten zuzugreifen. Wir vereinbaren mit dem Compiler einen Namen fr einen bestimmten Speicherbereich, und wenn wir den Variablennamen schreiben, bersetzt der Compiler diesen intern in eine Adresse.
Und warum rede ich von Adressen, wenn unser Thema doch Zeiger sein sollen?
Ein Zeiger ist eine spezielle Variable, in der man Adressen speichern kann.
All das kann dir zu diesem Zeitpunkt nur verwirrend vorkommen. Aber lass mich
noch andeuten, dass Zeiger sehr ntzlich sind. Mit einem Zeiger p (p wie Pointer)
kannst du nicht nur auf den Inhalt einer einzelnen Variable zeigen, sondern auch
auf groe zusammenhngende Speicherbereiche. Damit lassen sich dann leicht
Felder aufbauen, in denen du mit p[50] auf das soundsovielte Element zugreifen
kannst. Und wenn du in einer Funktion ein solches Feld verwenden willst, musst
du nicht den ganzen Speicherbereich kopieren. Du brauchst nur einen Zeiger auf
das erste Byte zu bergeben.
In diesem Kapitel wollen wir auch die so genannten Strukturen einfhren. Mit
Strukturen kannst du verschiedene Variablen unter einem Namen zusammenfassen. Das hrt sich nicht sehr aufregend an, erlaubt es uns aber, in C Datenstrukturen zu verwalten, die ber einfache Zahlen oder Felder weit hinausgehen.
Dabei spielen Zeiger eine wichtige Rolle, weil du wie bei Feldern fr gewhnlich
nur einen Zeiger zur Struktur bentigst.
Zeiger sind gefhrlich, weil der C-Compiler es nicht verhindert, wenn du auf Bytes zugreifst, die nicht angerhrt werden sollten. Wenn du nicht aufpasst, kannst
du mit Zeigern ungewollt Daten berschreiben und damit dein Programm (und
wenn das Betriebssystem nicht aufpasst, den ganzen Computer) vllig durcheinander bringen. Der Computer kann abstrzen. Ein bekannter Witz stellt fr
verschiedene Programmiersprachen ein und dieselbe Frage, die dann geistreich
beantwortet wird. In unserem Fall:
Frage: Wie stellst du dir selbst ein Bein?
C: Du stellst dir selbst ein Bein.

278

Kapitel 10 Zeiger und Strukturen

Sandini Bib

Aha. Andere Antworten sind so was wie Pascal: Der Compiler lsst dich nicht,
oder C++: Du definierst Bein als Unterklasse von Krperteil als Unterklasse von
Krper, und nach zwei, drei Fehlern stellst du dir ein Bein.
Die Moral von der Geschichte soll sein, dass C dir von allen Programmiersprachen die wenigsten unntigen Hindernisse in den Weg legt. Das ist gut so. C wre
nicht C, wenn sich Probleme mit Zeigern nicht leicht vermeiden lieen. Unterm
Strich sind Zeiger eine elegante und flexible Lsung, dem Programmierer freie
Hand bei der Verwendung des Speichers zu lassen.
Jetzt habe ich weit ausgeholt, um Zeiger gebhrend einzufhren. Zeiger sind
ein wesentliches Merkmal der Programmierung mit C. Aber lass dich nicht abschrecken! Wir werden nur die einfachen, wirklich wichtigen Merkmale von Zeigern und von Strukturen besprechen. Das reicht auf alle Flle aus, um z. B. mit
dem Windows SDK zu programmieren.

10.0 Zeiger
Ein Zeiger ist eine Variable, in der die Adresse einer Variablen gespeichert werden
kann. Als Erstes wollen wir die elementaren Operatoren & und * fr Zeiger
besprechen. Aus dem Zusammenhang muss klar sein, dass wir mit & und * nicht
das Und-Zeichen oder das Mal-Zeichen meinen.
Angenommen, wir haben mit int i; eine Integervariable definiert. Der Ausdruck
&i

ergibt die Adresse des Speicherplatzes der Variablen i. Diese Adresse knnen wir
in einer geeigneten Zeigervariablen p speichern, z. B. mit
p = &i;

Gut, jetzt enthlt p die Adresse (die Telefonnummer) der Bytes, in denen der
Inhalt der Variablen i gespeichert ist. Wie knnen wir mit dem Zeiger p auf
diese Bytes zugreifen? Das geschieht mit
*p

Der Ausdruck *p kann wie eine Integervariable verwendet werden. Mit


j = *p;

kannst du den Inhalt der Bytes, auf die p zeigt, in eine Integervariable j kopieren.
Aber *p kann auch wie eine richtige Integervariable auf der linken Seite einer
Zuweisung stehen,
*p = 2;

10.0 Zeiger

279

Sandini Bib

Hier wird die Zahl 2 in die Bytes kopiert, auf die p zeigt.
Damit das mit dem Auslesen und dem Zuweisen mit *p klappt, muss klar sein,
auf was fr einen Datentyp p zeigt. Sonst wei das Programm ja nicht, wie viele
Bytes es auslesen soll. Deshalb wird eine Variable p, die einen Zeiger auf eine
ganze Zahl enthlt, wie in
int *p;

definiert. Der Datentyp Zeiger auf Int wird also wie


int *

geschrieben und die Definition lsst sich so lesen: p ist etwas, was mit dem *
davor eine int ergibt. Wie es sich fr die Definition einer Variablen gehrt, wird
ausreichend Speicherplatz reserviert, um eine Adresse zu speichern.
Hier ist ein Beispiel, in dem & und * wie eben besprochen verwendet werden:
Zeiger0.c

#include <stdio.h>
int main()
{
int i;
int *p;
i = 1;
p = &i;
printf("\nNach p = &i; ist\n");
printf(" i = %d\n", i);
printf(" p = %d\n", p);
printf("*p = %d\n", *p);
*p = 2;
printf("\nNach *p = 2; ist\n");
printf(" i = %d\n", i);
printf(" p = %d\n", p);
printf("*p = %d\n", *p);
printf("\nsizeof(int *) = %d\n", sizeof(int *));
getchar();
return 0;
}

280

Kapitel 10 Zeiger und Strukturen

Sandini Bib

Nach
i =
p =
*p =

p = &i; ist
1
6553064
1

Nach
i =
p =
*p =

*p = 2; ist
2
6553064
2

sizeof(int *) = 4

Nach der Zeile


p = &i;

hat sich der Wert von i nicht gendert, aber p enthlt eine Zahl, die die Adresse
der Variable i angibt. Der Ausdruck *p in dem printf-Befehl ergibt die Zahl,
die unter dieser Adresse gespeichert ist.
Hallo, Byte Nummer 6553064! Nett, deine Bekanntschaft zu machen. Was ist
in dir gespeichert? Eigentlich ist es blich, Zeiger mit der Formatanweisung %p
als Hexadezimalzahl auszugeben. Aber ich wollte dir vorfhren, dass der Zeiger
p nichts weiter als eine ganze Zahl enthlt. Genau genommen hat diese Zahl
kein Vorzeichen und wir sollten zumindest die Formatanweisung %u (unsigned)
verwenden.
Nach der Zeile
*p = 2;

hat sich der Wert von *p gendert, wie nach der Zuweisung mit = zu erwarten,
whrend p unverndert geblieben ist. Und weil p auf den Speicherplatz von i
zeigt, hat sich auch i gendert!!
Lese den letzten Satz laut vor und lass ihn dir auf der Zunge zergehen. Keine
zwei Variablen verwenden normalerweise denselben Speicherplatz. Zwei Integervariablen i und j erhalten nach int i, j; Speicherplatz unter verschiedenen
Adressen. Aber die Adresse von i kann in einem Zeiger p gespeichert werden,
und i und *p drfen sehr wohl auf denselben Speicherplatz zugreifen. Im Prinzip
knntest du die Adresse auch noch in einen Zeiger q kopieren,
q = p;

Jetzt hast du drei Mglichkeiten, den Inhalt von i zu ndern, nmlich mit i = 2,
*p = 2 und *q = 2.

In der letzten Ausgabezeile des Beispiels sehen wir, dass bei mir
sizeof(int *)

10.0 Zeiger

281

Sandini Bib

gleich 4 ist. Den sizeof-Operator haben wir in Kapitel 9.3 eingefhrt und wie du
siehst, knnen wir sizeof auch fr Datentypen von Zeigern verwenden. Adressen sind also in diesem Beispiel 4 Bytes lang, was wiederum bedeutet (siehe 9.3),
dass hchstens bis zu 4 GB an Daten adressiert werden knnen. Das spiegelt die
Architektur meines Pentium II PCs wider.
brigens nennt man & auch Adressenoperator oder Referenzoperator (Referenz
im Sinne von verweisen auf). * nennt man Inhaltsoperator oder Dereferenzoperator.

10.1 Zeiger und malloc


Mit int i; erhltst du Platz fr genau eine ganze Zahl. Mit int a[10]; erhltst du Platz fr 10 Zahlen, aber die Anzahl 10 muss im Voraus festgelegt werden. Mit der Funktion malloc kannst du dir whrend der Programmausfhrung
Speicherplatz besorgen, dessen Gre von einer Variablen festgelegt wird:
Zeiger1.c

#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p;
int n = 10;
int i;
p = (int *) malloc(n * sizeof(int));
if (!p) {
printf("Fehler: konnte keinen Speicherplatz bekommen.\n");
getchar();
return 0;
}
for (i = 0; i < 10; i++)
p[i] = 2*i;
for (i = 0; i < 10; i++)
printf("i = %d
p[%d] = %d\n", i, i, p[i]);
free(p);
getchar();
return 0;
}

282

Kapitel 10 Zeiger und Strukturen

Sandini Bib

i
i
i
i
i
i
i
i
i
i

=
=
=
=
=
=
=
=
=
=

0
1
2
3
4
5
6
7
8
9

p[0]
p[1]
p[2]
p[3]
p[4]
p[5]
p[6]
p[7]
p[8]
p[9]

=
=
=
=
=
=
=
=
=
=

0
2
4
6
8
10
12
14
16
18

Vergleiche dieses Beispiel mit dem Beispiel aus 3.0. Genau wie bei Feldern kann
p[i] wie eine Variable verwendet werden, mit der das i-te Element einer Reihe
von Zahlen gesetzt und gelesen werden kann. Damit das funktioniert, brauchen
wir Platz im Speicher, der fr uns reserviert ist. Hier verwenden wir die Funktion
malloc (
memory allocation, Speicherzuweisung):
p = (int *) malloc(n * sizeof(int));

Das Argument von malloc ist die Anzahl der Bytes, die wir bentigen. Im Gegensatz zur Definition eines Felder darf hier eine Variable wie n verwendet werden. Um von der tatschlichen Gre einer Integerzahl unabhngig zu sein, benutzen wir sizeof. Du kannst ein Feld mit zehn Elementen mit int a[10];
anlegen, aber int a[n]; ist nicht erlaubt.
Man knnte sich denken, dass es fr jeden Datentyp eine eigene mallocFunktion gibt, so dass man sich die sizeof-Aktion sparen kann. Stattdessen
bedient sich C des folgenden Tricks: Zeiger, fr die der Datentyp nicht festgelegt
ist, erhalten den Typ
void *

void heit nichts, leer. Eine Deklaration wie void x; gibt es nicht, denn
eine Variable ohne Typ ist sinnlos. Mit einer Deklaration wie void *p; kann
man jedoch eine Variable erzeugen, in der ein Zeiger auf einen beliebigen Datentyp gespeichert werden kann. Wenn du dich in der BCB-Hilfe mit F1 nach
der Funktion malloc erkundigst, findest du heraus, dass malloc einen solchen
Zeiger zum Undefinierten liefert:
void *malloc( ... );

Dort steht auch, dass malloc die Headerdatei stdlib.h bentigt.


In 9.4 haben wir besprochen, wie wir Datentypen mit dem Castoperator ineinander umwandeln. Man schreibt den gewnschten Typ einfach in runden Klammern davor, z. B. (double) i. Der Castoperator funktioniert auch fr Zeigertypen. Der Ausdruck
(int *) malloc( ... )

10.1 Zeiger und malloc

283

Sandini Bib

verwandelt den Zeiger, den malloc liefert, in einen Zeiger auf ganze Zahlen.
Was malloc intern macht, ist, dass es sich Speicherblcke vom Betriebssystem (Windows) besorgt. Bei jedem Aufruf von malloc erhltst du einen neuen
Speicherbereich zugewiesen. Dabei ist zu beachten:
Unter Umstnden ist kein Speicherplatz mehr frei! In diesem Fall bringt
malloc dir den Wert 0 zurck. Dies ist der so genannte Nullzeiger. Zur Verdeutlichung schreibt man fr diesen Zeiger oft die symbolische Konstante
NULL. Du musst also immer mit
if (p == 0)

oder
if (p == NULL)

oder einfach mit


if (!p)

Code vorsehen, der ausgefhrt wird, falls kein neuer Speicherplatz erhalten
werden konnte. !p kannst du wie nicht p oder kein p lesen. Ausprobieren,
wie gro darfst du n auf deinem Computer machen?
Wenn du den Speicher nicht mehr bentigst, gib ihn mit der Funktion free
wieder frei:
free(p);

Natrlich musst du dann den Zeiger p erst wieder neu setzen, bevor du ihn
verwenden darfst. Bei Beendigung des Programms wird der verwendete Speicher automatisch ans Betriebssystem zurckgegeben.
Weitere interessante Speicherfunktionen sind:
Mit z. B. realloc(p, (n+100) * sizeof(int)) kann man nachtrglich
einen von malloc gelieferten Speicherblock vergrern oder verkleinern.
Mit z. B. calloc(n, sizeof(int)) erhlt man Speicher, der mit 0 initialisiert ist (beachte das Komma).
Moment Mal, was steht denn sonst in dem Speicher drin, den malloc liefert? Die
Sache ist genau wie bei automatischen lokalen Variablen. Nach der Definition,
aber vor der Initialisierung steht dort im Allgemeinen Mll, also irgendwelche
Zahlen, die vielleicht vom letzten Programm brig sind. In unserem Programmbeispiel kannst du probehalber die Schleife mit p[i] = 2*i; weglassen. Vielleicht erhltst du Nullen, vielleicht irgendwas.
Das gleiche gilt auch fr Zeigervariablen. Vor der Initialisierung mit p = &i;
steht in p womglich Mll, was fr Zeiger bedeutet, dass sie sonstwohin zeigen!

284

Kapitel 10 Zeiger und Strukturen

Sandini Bib

10.2 Zeiger, Felder und wie man mit Adressen rechnet


Angenommen, p ist ein Zeiger, der in einen brauchbaren Speicherbereich zeigt.
Gibt es sinnvolle Rechenoperationen, die wir mit p und q durchfhren knnen?
Das Folgende ist in der Tat erlaubt:
p + 5
p 1

Die Adresse wird nach oben oder nach unten gesetzt, wobei man sicherstellen
muss, dass man den zugewiesenen Speicherbereich nicht verlsst. Schlauerweise
sind + und fr Zeiger und Zahl so definiert, dass z. B. +1 die Adresse auf das
nchste Element setzt und nicht blo auf das nchste Byte! Wenn also p auf
Integers zeigt, ergibt p+1 eine Adresse, die um 4 grer ist (wenn Ints 4 Bytes
verwenden).
Wenn du darber etwas nachdenkst, wird dir auffallen, dass man also die Zahl
mit Index 1 sowohl mit p[1] als auch mit *(p+1) auslesen kann. In der Tat sind
das lediglich verschiedene Schreibweisen ein und derselben Zugriffsoperation.
Fr einen Zeiger p und eine ganze Zahl i gilt:
p[i] und *(p+i) sind gleichwertig.
&p[i] und

p+i sind gleichwertig.

Fr i gleich 0 erhltst du
p[0] und *p sind gleichwertig.
&p[0] und p sind gleichwertig.

Das ist ganz offensichtlich der Grund, warum die Erfinder von C beschlossen
haben, beim Durchzhlen von Elementen mit 0 anzufangen. Wenn man bei 1
anfngt, wre die Adressenrechnerei nicht so schn einfach.
Wirf einmal einen Blick auf die Rangordnungstabelle der Operatoren in Anhang
A.0. Die runden Klammern in *(p+i) sind ntig, weil der Dereferenzoperator
* strker bindet als die Addition mit +. Mit *p+i rechnest du die Summe der
Zahl i und der Zahl aus, auf die p zeigt. Und weil [ ] strker bindet als &, wird
in &p[i] erst p[i] ausgewertet, was sich wie gesagt wie eine Variable benimmt,
und dann mit &p[i] die Adresse der i-ten Variable berechnet.
Es wird dich jetzt sicher nicht mehr wundern, dass nach
int a[10];

der Variablenname a in vielen Fllen wie ein Zeiger funktioniert. In C sind Felder
so definiert, das der Name des Feldes, a, gleichbedeutend mit der Adresse ist, bei
der der Speicherplatz beginnt. Mit
p = a;

10.2 Zeiger, Felder und wie man mit Adressen rechnet

285

Sandini Bib

kann man diese Adresse z. B. in einen Zeiger kopieren, und genau wie eben besprochen kann man mit a[i], aber auch mit *(a+i) Elemente auslesen.
Wodurch unterscheiden sich Felder und Zeiger? a kann nicht gendert werden! Z.B. kannst du nicht a = p; schreiben oder den
Speicherbereich vergrern. Andererseits ist es trivial, ein mehrdimensionales Feld mit int a[10][10] zu definieren, was bei Zeigern nicht ohne
weiteres mglich ist.
Das ist also das Geheimnis, das Felder und Zeiger verbindet: Der Name eines
Feldes a ist so was wie ein konstanter Zeiger. Er kann wie ein Zeiger verwendet
werden, nur dass man die Adresse, die zu a gehrt, nicht ndern kann. Andererseits wird der Speicherplatz bei einem Feld a gleich mitgeliefert, whrend
du einen Zeiger per Hand mit p = a, p = &i oder p = malloc(...) auf einen
gltigen Speicherplatz richten musst.
Jetzt knnen wir das & in der Funktion scanf verstehen, welches in Kapitel 2.6
noch ohne Erklrung akzeptiert werden musste. scanf bentigt die Adressen der
Variablen, in denen die gelesenen Werte gespeichert werden sollen:
int i;
char text[1000];
scanf("%d", &i);
scanf("%s", text);

Die Feldvariable text wird ohne & angegeben, weil sie fr die Adresse des Feldes
steht, whrend wir von der Integervariablen i erst mit & die Adresse erzeugen
mssen.
Der Vollstndigkeit halber sei noch erwhnt, dass, wenn zwei Zeiger zum selben
Speicherblock gehren, ihre Differenz (also Zeiger Zeiger) und die Vergleichsoperatoren == != < > <= >= definiert sind.

10.3 Zeiger, Variablen, Funktionen


In Kapitel 7 ber Funktionen haben wir den Gltigkeitsbereich von Variablen
besprochen und wie Variablen an Funktionen bergeben werden knnen. Als
Erstes ist festzustellen, dass fr Zeigervariablen genau dieselben Regeln gelten
wie fr andere Variablen auch. Zeiger knnen extern oder intern und als statisch
oder automatisch definiert werden. Mal sind sie sichtbar, mal nicht.
Hier ist ein Beispiel fr eine Funktion, die einen Zeiger als Argument annimmt:

286

Kapitel 10 Zeiger und Strukturen

Sandini Bib
Zeiger2.c

#include <stdio.h>
void hallo(char *s);
int main()
{
char *name = "Welt";
hallo(name);
getchar();
return 0;
}
void hallo(char *s)
{
printf("Hallo, %s!\n", s);
}

Hallo, Welt!

Hier siehst du, dass char *name = "Welt"; wie char name[] = "Welt";
funktioniert. Die Variable name ist ein Zeiger auf ein char, und in diesem Fall
zeigt name auf das erste Zeichen einer Stringkonstanten. Jede Stringkonstante
im Programmtext steht fr die Adresse ihres ersten Zeichens.
Der Prototyp der Funktion hallo ist
void hallo(char *s);

das heit, ein Zeiger als Argument wird wie in der Definition des Zeigers mit
dem Sternchen * geschrieben. In der Funktion hallo ist die Variable name nicht
sichtbar, aber die Adresse in name wird in eine neue char * Variable s kopiert, die
wir dann verwenden knnen. Beachte, dass wir nicht etwa die ganze Zeichenkette
kopieren, sondern nur den Zeiger zum ersten Zeichen. Das ist auf jeden Fall
effizienter.
Die Verwendung eines Zeigers als Argument sieht im letzten Beispiel nicht viel
Aber trotzdem gibt es einen riesigen
anders aus als bei anderen Variablen.
Unterschied zwischen der bergabe eines Zeigers und einer normalen Variable.
Am besten schauen wir uns das folgende Beispiel an:

10.3 Zeiger, Variablen, Funktionen

287

Sandini Bib
Zeiger3.c

#include <stdio.h>
void machzurnull_falsch(int i);
void machzurnull_richtig(int *p);
void machzurnull_falsch(int i)
{
i = 0;
}
void machzurnull_richtig(int *p)
{
*p = 0;
}
int main()
{
int n = 1;
printf("n ist %d\n", n);
machzurnull_falsch(n);
printf("n ist %d\n", n);
machzurnull_richtig(&n);
printf("n ist %d\n", n);
getchar();
return 0;
}

n ist 1
n ist 1
n ist 0

Hier soll offensichtlich eine Funktion geschrieben werden, die die Variable n auf
0 setzt. Einmal klappts, einmal nicht.
Nach Kapitel 7 kann die erste Variante, machzurnull_falsch, nicht funktionieren, denn lokale Variablen sind fr die Auenwelt unsichtbar. n wird in die lokale
Variable i kopiert. Egal, wie wir i verndern, n bleibt unverndert. Und das ist
ganz im Sinne des Erfinders, denn oft will man Daten zwischen Funktionen sauber trennen.
Hufig mchte man jedoch, dass eine Funktion Daten in der aufrufenden Funktion ndert, z. B. n gleich 0 setzt. Wir kennen bisher zwei Mglichkeien, Daten an
die aufrufende Funktion zu bergeben: mit einer externen Variablen oder einfach
mit return.
Die dritte Mglichkeit ist, einen Zeiger zu bergeben, wie in machzurnull_
richtig vorgefhrt wird. In main wird die Adresse von n, also &n, an die Funktion machzurnull_richtig bergeben und in p gespeichert. Also hat *p = 0;
288

Kapitel 10 Zeiger und Strukturen

Sandini Bib

denselben Effekt, als wenn wir in main n = 0; ausgefhrt htten. Das funktioniert, weil Adressen ihre Gltigkeit behalten und nicht wie Variablennamen
gewissen Sichtbarkeitsregeln unterliegen.
Vielleicht mache ich zu viele Worte um etwas, das dir schon sonnenklar ist, aber
diese Idee ist wichtig. Du kannst den Zugriff auf Daten in verschiedenen Funktionen genau steuern, ohne auf die in Kapitel 7 besprochenen Sichtbarkeitsregeln
angewiesen zu sein, indem du Zeiger bergibst. Egal, wie oft ein Zeiger herumgereicht wird, mit * kannst du jederzeit den Inhalt des Speicherplatzes ndern,
auf den p zeigt. Denn auch bei Zeigern wird wie bei Funktionsaufrufen blich das
Argument in eine lokale Variable kopiert. Und weil das Argument eine Adresse
ist, kennt machzurnull_richtig die Adresse von n. Und das ist alles, was gebraucht wird, um n zu ndern.
Gleich noch ein Beispiel. Angenommen, wir mchten den Inhalt zweier Variablen
vertauschen. Das geht mit
Zeiger4.c

#include <stdio.h>
int main()
{
int i = 0, j = 5;
int temp;
printf("%d %d\n", i, j);
temp = i;
i = j;
j = temp;
printf("%d %d\n", i, j);
getchar();
return 0;
}

0 5
5 0

Wir knnen nicht gleich mit


i = j;
j = i;

loslegen, denn dann berschreiben wir als Erstes den Wert von i, und nachher
haben beide Variablen den Wert von j. Falls unklar, ausprobieren! Der Wert von
i wre fr immer verloren.
10.3 Zeiger, Variablen, Funktionen

289

Sandini Bib

Kein Problem, wir speichern i in einer temporren Variable (also einer Variable,
die wir nur vorrbergehend benutzen), berschreiben i mit j und holen uns
dann den Wert fr j aus temp.
Dieses Vertauschen ist an vielen Stellen ntzlich, lass uns eine Funktion dafr
schreiben. Die folgende Funktion ist sinn- und zwecklos:
void swap_falsch(int i, int j)
{
int temp;
temp = i;
i = j;
j = temp;
}

Swap heit austauschen. Lokales swappen bringt nichts. Aber mit


void swap(int *pi, int *pj)
{
int temp;
temp = *pi;
*pi = *pj;
*pj = temp;
}

funktioniert es. Verwende diese Funktion in main im letzten Beispiel. Wie musst
du die Funktion swap aufrufen? Ausprobieren!

10.4 Strukturen
In Feldern wird eine bestimmte Anzahl von Zahlen abgespeichert, die alle denselben Datentyp haben. In C ist es darber hinaus mglich, Zahlen unterschiedlichen Datentyps zu einem neuen Datentyp zusammenzufassen. Diese Konstrukstructure). Strukturen sind ntzlicher, als du vieltion heit Struktur (
leicht im ersten Moment denkst, und nicht nur im Windows SDK werden Strukturen oft verwendet. Im nchsten Unterkapitel 10.5 besprechen wir, warum Zeiger auf Strukturen praktisch sind. Aber erst mchte ich dir erklren, wie du
Strukturen definieren kannst.
Denke einmal darber nach, welche Information wir fr ein Rechteck in Windows bentigen. Das sind vier ganze Zahlen fr die Koordinaten der linken, oberen, rechten und unteren Kante. Diese Zahlen knnen wir wie folgt zusammenfassen:
struct rechteck {
int left;
int top;

290

Kapitel 10 Zeiger und Strukturen

Sandini Bib

int right;
int bottom;
};

Mit dem Schlsselwort struct definieren wir einen neuen Datentyp, der
den Namen struct rechteck erhlt. Zwischen die geschweiften Klammern
schreiben wir alle Variablen, die in der Struktur enthalten sein sollen. Beachte den Strichpunkt nach den Klammern. Nach dieser Strukturdefinition
kann struct rechteck genau wie andere Datentypen verwendet werden. Eine
Variable r vom Datentyp struct rechteck erhltst du mit
struct rechteck r;

Du kannst auch die Definition des Strukturdatentyps und die Definition von
Strukturvariablen in einer Anweisung zusammenfassen:
struct rechteck {
int left;
int top;
int right;
int bottom;
} fenstergroesse, r1, r2;

Hier wird der Datentyp struct rechteck eingefhrt, und wir definieren gleich
noch drei neue Variablen.
Bei einem Feld verwenden wir Indizes, um auf die Elemente des Feldes zuzugreifen. Bei einer Struktur verwenden wir den Punkt . als Zugriffsoperator wie
in
xmax = r.right;

Wir rufen die Elemente der Struktur mit ihrem Namen auf, und nicht mit einer
Indexzahl wie bei Feldern. In der Strukturdefinition haben wir den einzelnen Elementen der Struktur Namen gegeben, aber Namen wie right mssen in Verbindung mit einer Strukturvariablen verwendet werden. r.right kannst du genau
wie eine Integervariable verwenden.
Weil die Bezeichnung right eindeutig mit der Struktur struct rechteck verknpft ist, knntest du zustzlich noch eine Variable int right; einfhren und
right = r.right;

schreiben, um den Inhalt von r.right zwischenzuspeichern. Der Name fr ein


Element einer Struktur kollidiert nicht mit Variablennamen und es ist manchmal
ntzlich, als Gedchtnissttze dieselben Namen zu verwenden.
Vielleicht findest du es etwas umstndlich, den Datentyp struct rechteck immer in zwei Wrtern zu schreiben. Mit typedef kannst du neue Namen fr
Datentypen definieren, siehe Kapitel 9.6. Fr Strukturen sieht das so aus:
10.4 Strukturen

291

Sandini Bib

typedef struct {
int left;
int top;
int right;
int bottom;
} RECHTECK;
RECHTECK r;

Schau genau hin, wegen typedef ist RECHTECK nicht etwa eine Variable, sondern eine Typenbezeichnung, die genau wie int zur Definition von Variablen
verwendet werden kann. In diesem Fall kann der Name zwischen struct und
den geschweiften Klammern weggelassen werden. Wir schreiben RECHTECK mit
groen Buchstaben, um daran zu erinnern, dass ein neuer Datentyp dahinter
steckt. Im Windows SDK gibt es eine sehr hnliche Struktur namens RECT.
Dass wir den vier ganzen Zahlen in struct rechteck oder RECHTECK Namen
geben knnen, ist nett. Wichtiger ist aber, dass wir wie angekndigt verschiedene
Datentypen in einer Struktur zusammenfassen knnen. Ein Beispiel ist
typedef struct {
char *name;
int waffe[3];
int trefferpunkte;
int lebendig;
} MONSTER;
MONSTER troll[5], ork[10], goblin[20];

Hier siehst du, dass man in die Definition einer Struktur auch Zeiger und Felder
schreiben darf. Zudem knnen wir Felder aus identischen Strukturen definieren!
Waffe 0 von Troll 3 erhltst du mit troll[3].waffe[0].
Du kannst sicher erahnen, welche enorme Flexibilitt durch Strukturen erreicht
wird. Eine Programmieraufgabe bedeutet auch immer, dass irgendwelche Daten
verarbeitet werden mssen. Strukturen machen es dir einfach, selbst komplizierte Daten zu organisieren.

10.5 Zeiger auf Strukturen


Richtig ntzlich werden Strukturen erst, wenn du Zeiger auf Strukturen verwendest. Denn genau wie bei Feldern bentigt man nur den Zeiger auf eine
Struktur, um auf alle ihre Elemente zugreifen zu knnen. Normalerweise wirst
du nicht ganze Strukturen an Funktionen bergeben, sondern lediglich Zeiger zu
Strukturen. Angenommen, RECHTECK ist wie im letzten Beispiel definiert worden. Mit
RECHTECK r, *pr;

292

Kapitel 10 Zeiger und Strukturen

Sandini Bib

kannst du dir eine Strukturvariable r und eine Zeigervariable rp vom Typ Zeiger
auf Strukturvariable definieren. Nach
pr = &r;

zeigt rp auf r. Jetzt hast du drei Mglichkeiten, auf Elemente von r zuzugreifen:
xmax = r.right;
xmax = (*rp).right;
xmax = rp>right;

Klarer Fall, weil rp die Adresse von r enthlt, kannst du *rp wie r verwenden.
Achtung, weil der Zugriffsoperator . enger bindet als der Dereferenzoperator
* (siehe Anhang A.0), mssen wir in (*rp).right; Klammern setzen. Weil
Zeiger auf Strukturen hufig vorkommen, gibt es in C den speziellen Zugriffsoperator > (ein Pfeil aus Minus und Grer). Der Pfeil bewirkt dasselbe wie
(*rp).right, aber rp>right ist einfacher zu schreiben und zu lesen.
Im folgenden Beispiel verwenden wir Strukturen, um die Anzahl der Pixel in
einem Fenster zu berechnen. Wir verwenden Zeiger auf Strukturen, aber der
Vollstndigkeit halber fhre ich dir auch vor, dass Strukturen auch ohne Zeiger
bergeben werden knnen:

10.5 Zeiger auf Strukturen

293

Sandini Bib
Struktur0.c

#include <stdio.h>
/* definiere einen neuen Typ fuer eine Struktur:
Rechteck parallel zu Bilschirmkoordinaten
*/
typedef struct {
int left;
/* links, kleinster
int right;
/* rechts, groesster
int top;
/* oben,
kleinster
int bottom;
/* unten, groesster
} RECHTECK;

/* Flaeche = Breite mal Hoehe */


/* alles kopiert, Zugriff mit . */
int flaeche(RECHTECK r)
{
int hoehe, breite;
breite = r.right r.left;
hoehe = r.bottom r.top;
return breite * hoehe;
}
/* Adresse kopiert, Zugriff mit > */
int flaechemitzeiger(RECHTECK *r)
{
int hoehe, breite;
breite = r>right r>left;
hoehe = r>bottom r>top;
return breite * hoehe;
}

294

Kapitel 10 Zeiger und Strukturen

x
x
y
y

Wert
Wert + 1
Wert
Wert + 1

*/
*/
*/
*/

Sandini Bib
Struktur0.c

/* Berechne und vergleiche Anzahl von Pixeln von Fenstern */


int main()
{
RECHTECK fenster1, fenster2;
int npixel1, npixel2;
/* Bildschirmaufloesung */
fenster1.left
= 0;
fenster1.top
= 0;
fenster1.right = 1024;
fenster1.bottom = 768;
/* kopiere die Rechteckangabe und verkleinere die Groesse um 2 */
fenster2 = fenster1;
fenster2.right /= 2;
fenster2.bottom /= 2;
/* berechne Flaeche, kopiere alle Daten in Struktur */
npixel1 = flaeche(fenster1);
/* berechne Flaeche, uebergebe nur einen Zeiger zur Struktur */
npixel2 = flaechemitzeiger(&fenster2);
/* erzaehl mal */
printf("Das kleine Fenster benoetigt %d Pixel.\n",
npixel2, npixel1);
printf("Das doppelt so grosse Fenster benoetigt %d, also %d mal so viel!\n",
npixel1, npixel1/npixel2);
getchar();
return 0;
}

Das kleine Fenster benoetigt 196608 Pixel.


Das doppelt so grosse Fenster benoetigt 786432, also 4 mal so viel!

Als Erstes definieren wir den Strukturtyp RECHTECK. Diese Definition gilt fr
den nachfolgenden Programmtext in dieser Datei. Solche Definitionen knnen
in einer Headerdatei gesammelt werden. Es folgen zwei Funktionen, die fr ein
gegebenes Rechteck die Flche berechnen. Die eine Funktion verwendet einen
Zeiger, die andere nicht. Ohne Zeiger wird die gesamte Struktur kopiert. In der
Funktion main rufen wir diese Funktionen auf, um die Anzahl der Pixel in zwei
verschiedenen Fenstern zu berechnen.
Im Gegensatz zu Feldern knnen wir Strukturen praktischerweise wie in
fenster2 = fenster1;

kopieren. Die Anzahl der Bytes in einer Struktur kann grer als die Summe
der Bytes fr die einzelnen Variablen sein, weil z. B. bei einem 32-Bit-Prozessor
der Compiler fr ein einzelnes Zeichen wie char c; 8 Bit plus ungenutztem
Zwischenraum verwenden darf. Also solltest du wie in
rp = (RECHTECK *) malloc(sizeof(RECHTECK));

10.5 Zeiger auf Strukturen

295

Sandini Bib

fr malloc die Anzahl der Bytes in einer Struktur immer mit sizeof erfragen.
Bleibt nur noch anzumerken, dass doppelt so gro in jede Richtung
viermal so viele Pixel bedeutet. Das heit, wenn du die Qualitt deiner Grafik verdoppeln mchtest, muss der Computer viermal so
hart arbeiten!

10.6

Bibliotheksfunktionen fr
Zeichenketten und Felder

Endlich ist es so weit. Mit deinen neuen Kentnissen ber Zeiger kannst du all
diese Funktionsprototypen mit dem Sternchen * in der BCB-Hilfe verstehen!
ffne die BCB-Hilfe. Unter Bibliotheksreferenz, Bibliotheksroutinen, nach
Kategorien sortiert findest du einige interessante Funktionen. Unter dem etwas
nichts sagenden Titel Bearbeitungsroutinen findest du Funktionen fr Zeichenketten (Headerdatei string.h), z. B.
char *strcpy(char *ziel, char *start);

Kopiere (copy) alle Zeichen in der Zeichenkette start einschlielich der 0


am Ende nach ziel. Rckgabewert ist ziel.
char *strstr(const char *s, const char *gesucht);
Suche die Zeichenkette gesucht in der Zeichenkette s. Rckgabewert ist ein
Zeiger auf die Stelle in s, bei der gesucht gefunden wurde, oder NULL.
int strcmp(const char *s1, const char *s2);

Vergleiche (compare) zwei Zeichenketten alphabetisch. Rckgabewert ist


< 0, == 0, > 0, je nachdem ob s1 kleiner, gleich oder grer als s2 ist. Z.B.
ist "Bernd" kleiner als "Bruegmann". Der Vergleich beruht auf den Zahlenwerten des ASCII Codes, also liefert strcmp auch bei Sonderzeichen ein
sinnvolles Ergebnis.
Klarer Fall, wenn du Funktionen fr Zeichenketten definieren willst, tauchen
berall Zeiger auf Chars auf.
Falls du es nicht mit Zeichenketten, sondern z. B. mit Feldern zu tun hast, kannst
memory copy, Speicher kopieren), memcmp
du Funktionen wie memcpy (
(Speicher vergleichen) oder memset (Speicher setzen) benutzen.
Die Funktionen fr String- und Speicherbearbeitung sind praktisch, nicht nur,
weil du sie nicht selber programmieren musst, sondern auch weil diese Funktionen fr die Bibliotheken mit speziellen Maschinesprachebefehlen programmiert
werden knnen, die schneller als Schleifen in C ablaufen.

296

Kapitel 10 Zeiger und Strukturen

Sandini Bib

10.7

Zeichenketten kopieren

Natrlich ist es nicht schwierig, Zeichenketten zu kopieren. Wir knnen dabei


das Rechnen mit Zeigern ben. Hier ist die erste Version einer selbst gemachten
Funktion zum Kopieren von Strings:
StringCopy0.c

#include <stdio.h>
void stringcopy(char *s, char *t);
int main()
{
char s[10], t[10] = "Hallo";
stringcopy(s, t);
printf("%s %s\n", s, t);
getchar();
return 0;
}
void stringcopy(char *s, char *t)
{
int i = 0;
while (t[i] != 0) {
s[i] = t[i];
i++;
}
s[i] = 0;
}

Hallo Hallo

Beachte, dass wir mit char s[10], t[10]; Speicherplatz fr beide Strings bereitstellen. Mit char *s; kopieren wir sonstwohin, wenn s nicht auf gltigen
Speicherplatz zeigt. Aufgepasst, nach der while-Schleife ist die Null noch nicht
kopiert! Wir machen das nach der Schleife.
Das war recht bersichtlich, jetzt machen wir mal etwas Denksport. Wie knnen
wir diese Schleife verkrzen? Hier ist ein Trick:

10.7

Zeichenketten kopieren

297

Sandini Bib
StringCopy0a.c

void stringcopy(char *s, char *t)


{
int i = 0;
while ((s[i] = t[i]) != 0)
i++;
}

Die Zuweisung mit = ist ein Ausdruck, der den Wert der zugewiesenen Zahl
liefert, und
(s[i] = t[i])

ist ein Ausdruck, der alle Chars von String t durchluft und sie gleichzeitig in
String s speichert. Und weil erst die Null gespeichert und dann die Bedingung !=
getestet wird, brauchen wir die Null nicht extra nach der Schleife zu speichern.
Die runden Klammern sind ntig, damit die Operation = vor != durchgefhrt
wird, siehe Anhang A.0.
Jetzt wollen wir einmal ausntzen, dass s und t lokale Kopien sind, mit denen
wir rechnen drfen. In
StringCopy0b.c

void stringcopy(char *s, char *t)


{
while ((*s = *t) != 0) {
s++;
t++;
}
}

sehen wir eine typische Verwendung von ++ in Verbindung mit Zeigern. Statt
den Index hochzuzhlen, setzen wir einfach die Zeiger eins weiter! Wir drfen
das, weil es sich nur um Kopien handelt.
Und auch das geht:
StringCopy0c.c

void stringcopy(char *s, char *t)


{
while ((*s++ = *t++) != 0);
}

Hier nutzen wir aus, dass *t++ das Zeichen ergibt, das t enthielt, bevor ++ ausgefhrt wurde (Inkrement NACH dem Auslesen, 2.12). Unbedingt ausprobieren,
wie unterscheiden sich *t++, *(t++) und (*t)++? Der letzte Ausdruck ist offensichtlich nicht, was in unserem Beispiel geschieht, denn wir addieren nicht 1
zu dem Char *t, sondern 1 zum Zeiger auf Char hinzu. *t++ und *(t++) sind
gleichwertig.
298

Kapitel 10 Zeiger und Strukturen

Sandini Bib

Nein, wir sind noch nicht fertig, hier ist noch ein stringcopy:
StringCopy0d.c

void stringcopy(char *s, char *t)


{
while (*s++ = *t++);
}

Diesen Trick kennen wir schon. Ungleich Null steht schlielich fr wahr, und
die Schleife soll laufen, bis die Bedingung falsch, also Null, ergibt. BCB gibt
eine Warnung aus, denn meistens ist es ein bler Tippfehler, statt == in einer
Bedingung nur = zu schreiben!
Wie du siehst, lsst sich mit Zeigern knapp und elegant rechnen. Man muss sich
nur an die Schreibweise gewhnen.

10.8

Felder aus Zeigern und


main mit Argumenten

Wir haben es bisher noch nicht diskutiert, aber nichts spricht dagegen, einen
Zeiger auf einen Zeiger zu definieren, z. B.
int **pp;

Weil solche Zeiger auf Zeiger recht kompliziert werden knnen, wollen wir uns
in diesem Buch nicht auf eine Diskussion einlassen. Aber ein verwandtes Beispiel
kommt hufig vor, nmlich eine Liste aus Zeichenketten, z. B.
char *wochentag[] = {
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Spasstag",
"Schlaftag"
};

Die Variable wochentag ist ein Feld, das als Elemente Zeiger auf Zeichen
(char *) enthlt. Zur Initialisierung schreiben wir eine Liste von Stringkonstanten zwischen die geschweiften Klammern. Stringkonstanten liefern die
Adresse zu einer konstanten Zeichenkette. Bei einem einzelnen String knnen
wir nach char *s;
s = "Hallo";

schreiben, um die Adresse von "Hallo" in s zu speichern. Fr das Feld


wochentag schreiben wir
Felder aus Zeigern und main mit Argumenten

299

Sandini Bib

wochentag[0] = "Schon wieder Montag?";

um die Adresse fr eine neue Stringkonstante im 0-ten Element zu speichern. Das erste Zeichen in s ist s[0], das erste Zeichen in wochentag[0] ist
wochentag[0][0].
Eine wichtige Verwendung fr eine Liste von Zeichenketten ist der Aufruf von
main mit Argumenten. Hier ist ein Beispiel:
MainArgs.c

#include <stdio.h>
int main(int narg, char *arg[])
{
int i;
printf("Guten Tag, ich wurde mit %d Argument(en) aufgerufen:\n", narg);
for (i = 0; i < narg; i++)
printf("%3d: %s\n", i, arg[i]);
getchar();
return 0;
}

Weil wir unsere Beispiele normalerweise von BCB aus starten, bietet es sich
nicht an, Argumente an main zu bergeben. Aber zwischen die runden Klammern in main() gehren eigentlich zwei Argumente. int narg ist die Anzahl
der Elemente des Feldes char *arg[], das eine Liste aus Zeigern auf Zeichenketten enthlt. Die i-te Zeichenkette erhalten wir mit arg[i] und knnen sie
mit printf ausgeben. Das Feld wochentag knntest du genauso ausgeben, ausprobieren.
Aber wo kommen die Argumente fr main her? Jede andere Funktion wird von
main aufgerufen, aber wer ruft main auf? Wenn du das Beispiel in BCB laufen
lsst, erhltst du z. B.
Guten Tag, ich wurde mit 1 Argument(en) aufgerufen:
0: C:\MAINARGS.EXE

Wie du siehst, ist arg[0] hier der Name des Executables, das BCB als Endprodukt der Kompilierung erzeugt. Wenn du in Windows eine DOS-Konsole startest
(z. B. mit Start, Programme, MS-DOS-Eingabeaufforderung), kannst du das
Beispielprogramm als Befehl ablaufen lassen:
C:\>mainargs Hallo Welt
Guten Tag, ich wurde mit 3 Argument(en) aufgerufen:
0: C:\MAINARGS.EXE
1: Hallo
2: Welt

300

Kapitel 10 Zeiger und Strukturen

Sandini Bib

Die Zeichenketten, die main bergeben bekommt, sind die Wrter nach dem
Namen unseres Programms in der DOS-Befehlszeile (.exe wird nicht bentigt).
Diese Wrter sind die Befehlszeilenargumente, die an die Funktion main in zwei
Funktionsargumenten bergeben werden. Textbildschirmanwendungen kannst
du auch unter Windows-Startmen mit Ausfhren starten. Diese Option ffnet
ein kleines Fenster, in das du den Namen der .exe-Datei gefolgt von Argumenten
fr main eintragen kannst.

10.9

Dateien lesen und schreiben

Bisher haben wir noch kein einziges Wort darber verloren, wie man in CDateien lesen und schreiben kann. Die Standard I/O-Bibliothek enthlt dazu
einige Funktionen, die alle einen Zeiger auf eine Struktur FILE bentigen. Hier
ist ein Beispiel:
DateiLesen.c

#include <stdio.h>
int main()
{
FILE *fp;
int c;
fp = fopen("DateiLesen.c", "r");
if (!fp) {
printf("Fehler: Konnte DateiLesen.c nicht oeffnen.\n");
getchar();
return 0;
}
while (1) {
c = fgetc(fp);
if (c == EOF)
break;
if (c != && c != \t && c != \n)
printf("%c", c);
}
fclose(fp);
getchar();
return 0;
}

Mit FILE *fp; definieren wir eine Zeigervariable fr die Struktur FILE, die alle
ntigen Details fr die Filebehandlung enthlt. In diesem Fall brauchen wir diese
Details gar nicht zu kennen! Im Allgemeinen ffnest du zuerst eine Datei fr
10.9

Dateien lesen und schreiben

301

Sandini Bib

das Lesen oder Schreiben mit fopen. Dann folgen einige Ein- und Ausgabeoperationen, z. B. mit fgetc, das einzelne Zeichen liest. Dann schliet du die Datei
mit fclose wieder. Offene Dateien werden bei Ende der Funktion main automatisch geschlossen, aber es ist eine gute Angewohnheit, Dateien zu schlieen,
wenn sie nicht mehr bentigt werden. Es knnen mehrere Dateien geffnet sein,
aber nur eine begrenzte Anzahl zur gleichen Zeit. Die Sache mit Dateien ffnen
und schlieen ist auch deshalb ntig, weil dein Betriebssystem nicht mehreren
Programmen gleichzeitig erlauben sollte, in dieselbe Datei hineinzuschreiben.
In unserem Beispiel versuchen wir mit
fp = fopen("DateiLesen.c", "r");

die Datei DateiLesen.c im momentanen Verzeichnis zu ffnen. Falls du ein


anderes Verzeichnis angeben willst, musst du daran denken, dass der Schrgstrich
\ fr spezielle Zeichen verwendet wird, und z. B.
"C:\\MeinCode\\DateiLesen.c"

schreiben (Kapitel 3.3).


Das Ergebnis von fopen ist ein file pointer, den wir in fp speichern. Falls fp
gleich NULL ist, konnte die Datei nicht geffnet werden. Man bezeichnet fp
auch als
stream (Strom), und verschiedene Datenstrme sind mglich.
Das zweite Argument von fopen ist eine Zeichenkette, die die Art des Datenstroms angibt:
"r" fr Lesen (read)
"w" fr Schreiben (write)
"a" fr Schreiben am Ende der Datei (append)

Mit "w" berschreibst du unter Umstnden eine vorhandene Datei, whrend


du mit "a" neue Zeichen am Ende einer Datei anfgen kannst. Falls bei "a"
die Datei noch nicht existiert, wird eine neue Datei angelegt. Wenn nicht nur
gewhnliche Zeichen und Buchstaben gelesen oder geschrieben werden sollen,
verwendet man "rb", "wb" und "ab" (b fr binary data).
Die Datei zu schlieen ist einfacher, als sie zu ffnen. Du rufst einfach fclose
mit dem Filepointer auf, den fopen dir geliefert hatte.
In unserem Beispiel lesen wir Zeichen in
c = fgetc(fp);

Den Funktionsnamen kannst du als file get character lesen. Ist dir aufgefallen,
dass wir c nicht als char, sondern als int definiert haben? fgetc liest in der
Tat den Datentyp unsigned char, wandelt diese Zeichen aber in den Datentyp int um. Der Grund ist, dass fgetc unter Umstnden den Wert EOF liefert,
der das Ende der Datei signalisiert (end of file). Die Konstante EOF ist kein
ASCII-Zeichen, und weil alle 256 ASCII-Zeichen schon reserviert sind, ist der
302

Kapitel 10 Zeiger und Strukturen

Sandini Bib

Rckgabewert von fgetc ein grerer Datentyp als char. Wie dem auch sei, wir
lesen so lange Zeichen, bis EOF auftritt. Du knntest auch
while ((c = fgetc(fp)) != EOF)

schreiben (auf die richtigen Klammern achten).


Und was macht unser Programm mit den Zeichen? Es druckt alle Zeichen, sofern
es sich nicht um Zwischenraumzeichen handelt. Das sieht z. B. so aus:
#include<stdio.h>intmain(){FILE*fp;intc;fp=fopen("datei1.c","r");if(!fp){printf
("Fehler:Konntedatei1.cnichtoeffnen.\n");getchar();return0;}while(1){c=fgetc(fp
);if(c==EOF)break;if(c!=&&c!=\t&&c!=\n)printf("%c",c);}fclose(fp);getchar
();return0;}

Es gibt mehrere Funktionen zum Testen von Zeichen (Headerdatei ctype.h),


isspace(c) (ist Zwischenraum) verwenden, siehe die
z. B. knnten wir
BCB-Bibliotheksreferenz unter Klassifizierungsroutinen.
ndere dieses Beispiel doch so um, dass du das Programm mit einem Argument
fr main aufrufen kannst, das die Datei benennt (siehe Kapitel 10.8). Dann kannst
du dir die verschiedensten Dateien im verdichteten Stil anschauen.
Zeichen schreiben und lesen kannst du mit verschiedenen Funktionen, siehe insbesondere die BCB-Bibliotheksreferenz zu Ein-/Ausgaberoutinen:
int fgetc(FILE *fp);

Lese nchstes Zeichen. Rckgabewert ist das gelesene Zeichen oder EOF bei
Dateiende oder Fehler.
int fputc(int c, FILE *fp);
Schreibe c als unsigned char. Rckgabewert ist c oder EOF beim Auftreten

eines Fehlers.
char *fgets(char *s, int n, FILE *fp);

Lese Zeichen bis einschlielich des nchsten Neuezeilezeichens \n und speichere sie im Feld s ab, plus einer 0 am Ende. Rckgabewert ist s oder NULL,
falls ein Fehler oder EOF auftrat.
int fputs(const char *s, FILE *fp);
Schreibe die Zeichenkette s. Rckgabewert ist grer gleich 0 oder EOF beim

Auftreten eines Fehlers.


int fscanf(FILE *fp, const char *format, ...);

Wie scanf, nur dass von fp gelesen wird. Rckgabewert EOF bei Fehler.
int fprintf(FILE *fp, const char *format, ...);

Wie printf, nur dass nach fp geschrieben wird. Rckgabewert EOF bei Fehler.
Alle diese Funktionen kannst du auch fr die Texteingabe von der Tastatur bzw.
fr die Textausgabe im Textfenster verwenden. Die folgenden Streams sind bei
10.9

Dateien lesen und schreiben

303

Sandini Bib

Programmablauf automatisch verfgbar und knnen als Filepointer verwendet


werden:
stdin
fr Standardeingabe von der Tastatur
stdout fr Standardausgabe zum Textfenster
stderr fr Standardfehlerausgabe, normalerweise zum Textfenster

Insbesondere kannst du mit fgets und stdin das Problem lsen, dass gets
womglich zu viele Zeichen liest, siehe 3.6.
Es gibt noch einige andere Funktionen in den Standardbibliotheken von C, die
mit Dateien zu tun haben. Weil wir uns nicht weiter mit der Dateibehandlung
befassen wollen, empfehle ich dir an dieser Stelle bei Bedarf in der BCB-Hilfe zu
stbern. Die genannten Funktionen reichen auf jeden Fall aus, um eine Bestenliste oder einen Spielstand als Datei zu speichern und zu lesen.

10.10

WinMain

Der einzige Grund, warum wir die WinMain-Funktion noch nicht besprochen
haben, die wir in unseren Grafikbeispielen einsetzen, ist die Verwendung von
Zeigern und Strukturen in dieser Funktion. Also, Augen auf und hingeguckt:

304

Kapitel 10 Zeiger und Strukturen

Sandini Bib
WinMain.cpp

/* main fuer Windows */


int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE dummy0, PSTR dummy1, int dummy2)
{
WNDCLASS wc; // Fensterklasse
HWND hwnd;
// Handle des Fensters
MSG msg;
// Message (Nachricht)
/* Initialisiere
wc.lpfnWndProc
wc.lpszMenuName
wc.cbClsExtra
wc.cbWndExtra
wc.hInstance
wc.hIcon
wc.hCursor
wc.hbrBackground
wc.style
wc.lpszClassName

Fensterklasse */
= WindowProc;
// Zeiger auf WindowProc, siehe oben
= NULL;
= 0;
= 0;
= hInstance;
= LoadIcon(NULL, IDI_APPLICATION);
// Icon
= LoadCursor(NULL, IDC_ARROW);
// Cursor
= GetStockObject(WHITE_BRUSH);
// Hintergrund
= CS_OWNDC | CS_VREDRAW | CS_HREDRAW; // Stilparameter
= "KlasseHallo";
// Klassenname

/* Registriere Fensterklasse */
RegisterClass(&wc);
/* Erzeuge Fenster, merke dir den Handle */
hwnd = CreateWindow("KlasseHallo",
"WinHallo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
250, 250,
NULL, NULL, hInstance, NULL);

//
//
//
//
//

Klassenname
Titel
Fenstertyp
Ursprung x,y
Breite, Hoehe

/* Zeige Fenster */
ShowWindow(hwnd, SW_SHOW);
/* Nachrichtenschleife, Ausstieg falls GetMessage 0 liefert */
while (GetMessage(&msg, 0, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
// Ruft WindowProc auf
}
/* beende WinMain und damit das Programm */
return msg.wParam;
}

Du erinnerst dich, bei WinMain fngt die Programmausfhrung einer Windowsanwendung an. Der Prototyp ist
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE dummy0, PSTR dummy1, int dummy2);

Die Typbezeichner WINAPI, HINSTANCE und PSTR stammen wie alle anderen
Typen aus windows.h. Wir bentigen nur das erste Argument, HINSTANCE
hInstance.
Instance heit hier Verwirklichung, und hInstance ist
10.10

WinMain

305

Sandini Bib

ein Handle zur Identifizierung unserer Windowsanwendung. Z.B. knnte unser


Programm ja mehrmals aufgerufen werden und somit mehrere Kopien derselben
Anwendung gleichzeitig ablaufen.
Was wir in der Funktion WinMain an Befehlen verpacken, ist uns freigestellt. In
unserem Beispiel erledige ich in WinMain verschiedene Standardaufgaben, wie
sie in vielen Windowsanwendungen anzutreffen sind. Eigentlich ist ein Windowsprogramm sehr einfach:
1. Fenster auf.
2. Warte auf Fensternachrichten und bearbeite, was da so kommt.
3. Fenster zu.

Weil das Windows SDK ein sehr groes Softwarepaket ist, das komplizierte Aufgaben zu erledigen hat, ist es vergleichsweise kompliziert, ein Fenster auf den
Bildschirm zu bekommen. Das liegt eigentlich nur an den vielen Einzelheiten, die
wir fr unser Fenster festlegen drfen. Fenster auf erfordert mehrere Schritte:
1. Initialisiere Struktur wc fr Fensterklasse.
2. Registriere Fensterklasse wc.
3. Erzeuge Fenster der neuen Klasse, speicher Fensterhandle hwnd.
4. Zeige Fenster hwnd.

Als Erstes bestimmen wir die Fensterklasse fr unser Fenster, indem wir die
Struktur
WNDCLASS wc;

initialisieren und anschlieend mit


RegisterClass(&wc);

bei Windows registrieren lassen. Das ist eine typische Anwendung von Strukturen und Zeigern. Wir geben nur die Adresse &wc der Struktur wc weiter. Die
Initialisierung von wc ist wie das Ausfllen eines Formulars oder eines Wunschzettels.
Bei den folgenden Windowsfunktionen solltest du auch mal einen Blick in die
Windows SDK-Hilfe werfen. Dabei findest du sicher interessante Zusatzinformationen, die ich im Rahmen dieses Buches nicht besprechen kann. Nicht nur Funktionen, sondern auch Strukturen wie WNDCLASS werden ausfhrlich beschrieben.
Einige Elemente von wc setzen wir einfach auf 0. Die Variable hInstance
wurde uns als Argument an WinMain bergeben, und wir tragen sie unter wc.hInstance ein. Mit LoadIcon bestimmen wir, welches Bildchen
icon) gezeigt werden soll, wenn unser Fenster minimiert wird. Mit
(
306

Kapitel 10 Zeiger und Strukturen

Sandini Bib

LoadCursor whlen wir einen Mauszeiger. Mit GetStockObject(WHITE_BRUSH)

bestimmen wir einen Pinsel, der fr die Defaulthintergrundfarbe des Fensters


verwendet werden soll. Als Namen unserer Fensterklasse geben wir den String
KlasseHallo an.
style) des Fensters wird mit einer ganzen Zahl in wc.style
Der Stil (
beschrieben. Hier begegnet uns der Oder-Bitoperator |, der verschiedene Konstanten verknpft:
CS_OWNDC | CS_VREDRAW | CS_HREDRAW

Der Witz ist, dass in jeder Konstante nur ein Bit auf 1 gesetzt ist, und durch die
Verknpfung mit Oder ist das Ergebnis eine Zahl, in der drei Bits gesetzt sind.
In anderen Worten, statt viele verschiedene Variablen fr verschiedene Wahr/
Falsch-Optionen zu definieren, verwenden wir einzelne Bits in der Variablen
wc.style. Mit CS_OWNDC stellen wir sicher, dass jedes Fenster in unserer Fenown) Device Context erhlt. Das ist fr unsere
sterklasse seinen eigenen (
Beispielprogramme praktisch. Mit CS_VREDRAW und CS_HREDRAW teilen wir Winredraw) werden soll,
dows mit, dass das Fenster jedes Mal neu gemalt (
wenn sich seine vertikale oder horizontale Gre ndert.
Besonders interessant ist
wc.lpfnWndProc = WindowProc;

Hier geben wir den Namen unserer Fensterprozedur an. Dazu muss der Prototyp
der Funktion WindowProc bekannt sein. Du knntest genauso gut einen anderen
Namen fr die Fensterprozedur verwenden und diesen dann hier angeben. Wie
kann das funktionieren?
WindowProc ist die Adresse der Funktion! Das ist hnlich wie bei Feldern, bei
denen der Name des Feldes verwendet werden kann, um die Adresse des Feldes
in einen Zeiger zu kopieren. Und genau das machen wir hier fr eine Funktion. wc.lpfnWndProc ist ein Zeiger auf eine Funktion! Jetzt weit du, wie es
dazu kommt, dass WindowProc aufgerufen wird, obwohl nirgends in WinMain
ein Funktionsaufruf von WindowProc zu sehen ist. Windows wei, wohin die
Fensternachrichten geschickt werden sollen, weil wir in unserer Fensterklasse
wc die Funktion WindowProc angegeben haben. Mit dieser Erklrung wollen wir
uns zufrieden geben, ohne zu diskutieren, wie man Funktionen als Elemente von
Strukturen definiert.

Nach RegisterClass(&wc); knnen wir mit der Funktion CreateWindow


ein Fenster erzeugen. Erzeugen bedeutet, dass wir als Rckgabewert von
CreateWindow ein Fensterhandle bekommen. Das ist derselbe Handle, den
wir spter in der Fensterprozedur wiedersehen. CreateWindow rufen wir
mit dem Namen der Fensterklasse im ersten Argument auf, "KlasseHallo".
Das zweite Argument ist der Name des Fensters, der z. B. im Titelbalken des
Fensters angezeigt wird. Das dritte Argument ist der Stil des Fensters. Mit
10.10

WinMain

307

Sandini Bib

WS_OVERLAPPEDWINDOW bekommen wir, was du als normales Fenster in un-

seren Beispielen kennen gelernt hast. Es gibt auch Fenster ohne Titelbalken,
Fenster mit Scrolleisten und vieles, vieles mehr. Du wirst staunen, wenn du dir
die Hilfe zu CreateWindow anschaust. Windows heit nicht umsonst Fenster.
Mit CW_USEDEFAULT im vierten und fnften Argument von CreateWindow bestimmen wir, bei welchen Koordinaten die linke obere Ecke des Fensters auf dem
Bildschirm erscheinen soll. Wir whlen den Default von Windows, das heit,
neue Fenster erhalten automatisch neue Koordinaten, damit sie sich nicht berdecken. Hier knntest du zweimal die 0 angeben, damit das Fenster immer ganz
links oben erscheint. Mit den nchsten zwei Argumenten bestimmen wir Breite
und Hhe des Fensters. Die restlichen Argumente sind im Moment uninteressant.
Damit das Fenster sichtbar wird, rufen wir
ShowWindow(hwnd, SW_SHOW)

auf. Und nach einigen Fensternachrichten wie WM_CREATE und WM_SIZE, die du
schon kennst, erscheint tatschlich das endgltige Fenster auf dem Bildschirm.
Die Diskussion der Nachrichtenschleife hatten wir schon in Kapitel 8.1 vorweggenommen, um das Konzept der Nachrichtenwarteschlange erklren zu knnen.
Jetzt wird klar, dass MSG msg; eine Struktur definiert, die alle ntigen Informationen zur Nachricht enthlt. In
while (GetMessage(&msg, 0, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

rufen wir GetMessage mit der Adresse von msg auf, um die Struktur mit der
nchsten Nachricht zu fllen. TranslateMessage bekommt die Adresse von
msg, um etwaige Zeichenbersetzungen vorzunehmen. DispatchMessage wei
aufgrund von msg, welche Fensterprozedur aufgerufen werden soll.
Zu guter Letzt, wenn die Nachrichtenschleife wegen WM_QUIT verlassen wurde,
verwenden wir das Element namens wParam der letzten Nachricht als Rckgabewert von WinMain,
return msg.wParam;

Und wieder hat ein Programm sein Ende gefunden.


Du wirst mir Recht geben: bis auf die vielen Auswahlmglichkeiten bei der Fenstererzeugung und die Verwendung eines Zeigers auf eine Funktion, die den
Aufruf von WindowProc vor uns versteckt, ist WinMain schn einfach.

308

Kapitel 10 Zeiger und Strukturen

Sandini Bib

10.11
Alles halb so wild mit den Zeigern und Strukturen. Strukturen sind eine Art von
Formular, in der verschiedene Variablen zusammengefasst sind. Zeiger speichern
die Adressen von Speicherbereichen und knnen gut fr den Austausch von Feldern und Strukturen zwischen Funktionen verwendet werden. Jetzt, da du Zeiger
und Strukturen kennst, darfst du dich als waschechter C-Programmierer fhlen!
Wichtige Punkte in diesem Kapitel waren:
Der Adressenoperator & liefert die Adresse, unter der der Inhalt einer Variablen im Speicher steht, z. B. ist &i die Adresse, unter der der Inhalt der
Variablen i gespeichert ist.
Der Inhaltsoperator * liefert die Daten, auf die ein Zeiger zeigt. Wenn z. B. p
ein Zeiger auf eine ganze Zahl ist, kann mit *p diese Zahl ausgelesen werden.
Wenn mehrere Zahlen vom selben Typ hintereinander im Speicher stehen
und p auf den Beginn dieses Speicherblocks zeigt, erhlt man mit p[0] den
Wert der 0. Zahl, mit p[1] den Wert der 1. Zahl und so weiter.
Einen Zeiger zu einem zusammenhngenden Speicherblock kann man mit
der Funktion malloc erhalten (memory allocation, Speicherzuweisung).
Strukturen definiert man mit struct. Auf die Elemente einer Strukturvariable greift man mit dem Operator . zu. Im Falle eines Zeigers auf eine
Strukturvariable kann man den Operator > verwenden.

10.11

309

Sandini Bib

10.12
1. Schreibe eine Funktion, deren Rckgabewert die Lnge einer Zeichenkette ist.
Vergleiche die Zeit, die deine Funktion und die Bibliotheksfunktion strlen

fr sehr lange Zeichenketten bentigt.


2. Schreibe ein Programm, das ein Feld sortiert. Dazu gibt es schlaue Algorithmen, z. B. den Quicksort-Algorithmus, den die Funktion qsort in stdlib.h
verwendet (siehe das Beispiel zu qsort in der BCB-Hilfe). Aber bei kleinen

Feldern kannst du wie folgt vorgehen. Initialisiere ein Feld mit ganzen Zahlen. Suche mit einer Schleife das kleinste Element. Vertausche das kleinste
Element mit dem ersten Element. Suche das kleinste Element ab dem zweiten Element. Vertausche dieses Element mit dem zweiten, und so weiter. Weil
immer das kleinste Element des restlichen Feldes an den Anfang geschrieben
wird, ist am Ende das Feld sortiert. Verpacke die Sortierbefehle in einer Funktion, die einen Zeiger auf ein Feld bergeben bekommt.
3. Kannst du dein Sortierprogramm fr Zeichenketten umschreiben? Dazu bentigst du die Funktion strcmp zum Vergleichen von Strings. Du knntest

die Liste der Wochentage sortieren. Natrlich kannst du zum Vertauschen


von Elementen die Zeichenketten kopieren, dazu muss aber jeder String ausreichend Platz fr den lngsten bieten, sagen wir char *wochentag[100] =
{ ... };. Du kannst auch ein Feld int index[N]; mit Indizes fr die Liste
der Strings anlegen, das du in einer Schleife mit index[i] = i initialisierst.
Statt die Zeichenketten zu sortieren, sortierst du die Indizes.
4. Schreibe ein Programm, das die Anzahl der Zeichen, der Wrter und der Zei-

len in einer Datei zhlt. Eine etwas andere Version deines Programms knnte
zhlen, wie hufig einzelne Zeichen vorkommen. Dazu kannst du wie bei dem
Balkendiagramm fr das Wrfeln in Kapitel 6.16 ein Feld einfhren, in dem
du fr alle 256 mglichen Zeichen eine Strichliste fhrst.
5. Verwende die Funkion DrawText, um einen lngeren Text in einem Rechteck

im Fenster auszugeben (siehe Hilfe zum Windows SDK). Das Rechteck wird
als Zeiger auf eine Struktur vom Typ RECT bergeben.
6. Nach unserer Diskussion von WinMain in Kapitel 10.10 kannst du jetzt

mit der Erzeugung von Windowsanwendungen in BCB experimentieren.


Starte ein neues Projekt im BCB-Men mit Datei, Neue Anwendung.
Entferne Unit1.cpp und Project1.res aus dem neuen Projekt. ffne
Project1.cpp im Editor. Ersetze die Funktion WinMain durch die aus Kapitel 10.10, und fge die Funktionen WindowProc und malen aus Kapitel 8.0
zur Datei Project1.cpp hinzu. Jetzt kannst du abspeichern und kompilieren.
Vergleiche mit den Windowsprojekten auf der Buch-CD.

310

Kapitel 10 Zeiger und Strukturen

Sandini Bib

11
Bitmaps
11.0
11.1
11.2
11.3

Bitmaps erzeugen und laden


BitBlt
Bitmapfilmchen
Tonausgabe mit PlaySound

312
316
317
324

11.4

Bitmappuffer

326

11.5

Gekachelte Landkarte

334

11.6

Mit Pfeiltasten ber Land

340

11.7

Bitmaps mit transparenter Hintergrundfarbe

344

11.8

Ein Held auf weiter Flur

347

11.9

348

11.10

349

Sandini Bib

Als Bitmaps bezeichnet man im Allgemeinen Bilder, die sich aus einzelnen Pimap heit Landkarte). Bitmaps sind berall. Alles,
xeln zusammensetzen (
was auf deinem Computerbildschirm dargestellt wird, setzt sich aus Pixeln zusammen. Obwohl am Ende alles als Pixel auf deinem Bildschirm erscheint, wird
z. B. Text und Liniengrafik nicht als Pixel gespeichert, sondern wird erst fr die
Ausgabe in Pixel umgewandelt. Bitmaps aber sind so abgespeichert, dass sie im
Wesentlichen Pixel fr Pixel aus einer Datei oder dem Arbeitsspeicher in den
Bildspeicher deiner Grafikkarte kopiert werden knnen. Im engeren Sinne ist
mit Bitmap ein bestimmtes Datenformat fr rechteckige Bilder unter Windows
gemeint. Bitmaps werden in Dateien mit der Endung .bmp gespeichert. Andere
Datenformate fr Pixelbilder sind z. B. .gif oder .jpg, aber diese werden wir
nicht verwenden.
Computerspiele sind ohne Bitmaps nicht denkbar. Es ist erstaunlich, wie viele
Spiele mit der einfachsten Verwendung von Bitmaps auskommen. Dabei denke
ich an 2D-Spiele. Alles, was diese Programme machen, ist, kleine bunte Bildchen
ber den Bildschirm zu bewegen und zu animieren. Eine kleine Figur, die luft,
setzt sich z. B. aus 5 einzelnen Bitmaps zusammen. Wie wir schon in Kapitel 8
ausprobiert haben, kommen Animationen durch die schnelle Abfolge einzelner
Bilder zustande, und das funktioniert fr Bitmaps genauso gut. In all diesen Plattformspielen, in denen du mit einer Figur durch die Gegend hpfst, ist deine Figur
eine animierte Bitmap, die Gegner sind animierte Bitmaps, der Hintergrund ist
eine Bitmap, die Schtze sind Bitmaps. Das Gleiche gilt z. B. fr Abenteuer- oder
Strategiespiele, bei denen du von oben auf eine flache Landkarte schaust. Die
Bume, Bitmaps. Der Ritter, eine Bitmap. Die Burg, eine Bitmap.
In diesem Kapitel wollen wir einige der grundlegenden Techniken fr Bitmapgrafik ausprobieren.

11.0

Bitmaps erzeugen und laden

Woher bekommst du Bitmaps? Die meisten Malprogramme erlauben es dir, dein


Bild als Bitmap abzuspeichern. Falls du einen Scanner hast, kannst du auf Papier
zeichnen, das Bild einscannen, das Ergebnis in einem Malprogramm nacharbeiten
und dann als Bitmap abspeichern. Es gibt da sehr viele schne Mglichkeiten.
In diesem Kapitel werde ich mich auf Bitmaps beschrnken, die ich mit dem Malprogramm im Windowszubehr gemacht habe. Bei mir steht das unter Start,
Programme, Zubehr, Paint. Das Programm Paint ist recht primitiv, bietet aber eine gute Zoomfunktion (siehe Ansicht, Zoomfaktor, Benutzerdefiniert), mit der du das Bild so vergrern kannst, dass du einzelne Pixel bearbeiten kannst. Bei benutzerdefiniertem Zoomfaktor whle unter Ansicht die
Option Miniaturansicht einblenden, um ein Fensterchen mit der Bitmap in Originalgre zu erhalten.
Ich habe so die Bitmapdatei held_0.bmp erzeugt, die du auch auf der Buch-CD
findest. Diese kannst du mit Paint ffnen. Der Einfachheit halber sollte dein
312

Kapitel 11 Bitmaps

Sandini Bib

Bildschirm auf mindestens 16 Bit Farbtiefe eingestellt sein, siehe Kapitel 9.10.
Sonst sehen die Farben bei dir anders aus, weil ich 16-Bit-Farben verwendet habe.
Wie bekommst du diese Bitmap in das Fenster eines selbst gemachten Programms? Hier ist ein Beispiel, das die WinMain-Funktion aus Kapitel 8 und
10.10 benutzt:
Bitmap0.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc;
char text[1000];
char *dateiname = "held_0.bmp";
/* male Bitmap */
void malebitmap(HDC hdc, int x, int y)
{
HDC hdcmem;
HBITMAP hbitmap;
BITMAP bitmap;
hbitmap = LoadImage(0, dateiname, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbitmap) {
sprintf(text, "Fehler beim Laden von %s", dateiname);
TextOut(hdc, 10, 30, text, strlen(text));
return;
}
GetObject(hbitmap, sizeof(BITMAP), &bitmap);
hdcmem = CreateCompatibleDC(hdc);
SelectObject(hdcmem, hbitmap);
BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, hdcmem, 0, 0, SRCCOPY);
DeleteDC(hdcmem);
}

11.0 Bitmaps erzeugen und laden

313

Sandini Bib
Bitmap0.c WinMain.cpp

/* Malen */
void malen(HDC hdc)
{
malebitmap(hdc, 10, 10);
malebitmap(hdc, 20, 40);
malebitmap(hdc, 40, 70);
malebitmap(hdc, 80, 100);
malebitmap(hdc, 160, 130);
}
/* Rueckruffunktion unseres Fensters */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "Bitmap");
hdc = GetDC(hwnd);
}
// Malen
else if (m == WM_PAINT) {
malen(hdc);
ValidateRect(hwnd, 0);
}
// Verschiedenes
else if (m == WM_DESTROY)
PostQuitMessage(0);
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

In WindowProc geschieht nichts Besonderes. Bei WM_PAINT rufen wir die Funktion malen auf. In malen rufen wir die selbst gemachte Funktion malebitmap
mit den Koordinaten auf, bei denen die Bitmap ausgegeben werden soll. Das Ergebnis sieht so aus:

314

Kapitel 11 Bitmaps

Sandini Bib

Toll gezeichnet, vielleicht htte ich doch Knstler werden sollen? Die eigentliche
Arbeit wird in malebitmap erledigt. Die Bitmap laden wir aus der Datei mit
hbitmap = LoadImage(0, dateiname, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

Image heit Bild, und wir rufen die Funktion LoadImage mit den richtigen Argumenten auf, um eine Bitmap aus einer Datei zu laden, deren Namen
als Zeichenkette in dateiname steht. Der Rckgabewert ist ein Handle fr eine
Bitmap, den wir in der Variable HBITMAP hbitmap; speichern. Nun knnte es
sein, dass aus irgendwelchen Grnden der Ladevorgang nicht erfolgreich war.
Vielleicht hast du dich beim Namen vertippt. In diesem Fall ist hbitmap gleich
0, und wir geben eine Fehlermeldung aus und beenden die Funktion malebitmap
mit return;.
Im weiteren Verlauf bentigen wir die Abmessungen der Bitmap, die wir wie
folgt erhalten. In
GetObject(hbitmap, sizeof(BITMAP), &bitmap);

verwenden wir den Handle der Bitmap, hbitmap, um eine Struktur mit Information ber die Bitmap zu fllen. Die Strukturvariable definieren wir in
BITMAP bitmap;. GetObject bentigt den Handle, die Gre der Struktur in
Bytes (sizeof(BITMAP)) und die Adresse der Strukturvariablen (&bitmap).
Weiter unten in der Funktion malebitmap siehst du, dass wir die zwei Elemente
bitmap.bmWidth und bitmap.bmHeight der Struktur bentigen.

11.0 Bitmaps erzeugen und laden

315

Sandini Bib

11.1 BitBlt
Wir fahren mit der Diskussion unseres ersten Bitmapbeispiels fort. Falls die Bitmap erfolgreich geladen wurde, wollen wir sie in das Fenster malen. Das geschieht in
BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, hdcmem, 0, 0, SRCCOPY);

Dieser Aufruf der Funktion BitBlt (bit block transfer) kopiert Bitmaps. Der
Inhalt unseres Fensters ist nichts weiter als eine groe, rechteckige Bitmap, und
wir mchten mit der Bitmap, die wir geladen haben, einen Teil der Fensterbitmap
bermalen.
source copy,
Die Arbeitsweise von BitBlt haben wir mit SRCCOPY (
Quelle kopieren) ausgewhlt. In der Windows SDK-Hilfe findest du auch andere
Operationen, mit denen du Bitmaps z. B. berlagern kannst. BitBlt spricht man
brigens bit blit.
Das erste Argument von BitBlt ist hdc, also der Handle zum Device Context, in
den wir malen mchten. Jetzt knnte man erwarten, dass eine Bitmap als Argument auftaucht, aber stattdessen wird die Bitmap indirekt durch einen zweiten
Handle zu einem Device Context namens hdcmem bergeben. Der Grund ist, dass
eine Bitmap genau wie ein Stift oder ein Pinsel erst in einem Device Context aktiviert werden muss, bevor sie verwendet werden kann. hdc knnen wir nicht
verwenden, denn hdc ist fr den Fensterinhalt reserviert. Deshalb erzeugen wir
compatible) zum HDC, in den wir
einen neuen Device Context passend (
malen mchten:
hdcmem = CreateCompatibleDC(hdc);

Weil dieser Device Context sich nicht auf das Fenster bezieht, sondern im Speimemory) steht, nennen wir ihn hdcmem. Im nchsten Schritt vercher (
fahren wir wie bei Stiften und Pinseln und aktivieren mit
SelectObject(hdcmem, hbitmap);

die Bitmap im Speicher Device Context. Jetzt kann BitBlt die Speicherbitmap
in hdcmem in die Fensterbitmap in hdc kopieren.
Dazu bergeben wir sechs weitere Parameter. BitBlt kopiert nicht einfach die
ganze Bitmap, sondern einen rechteckigen Ausschnitt, den wir allerdings in unserem Beispiel gleich der ganzen Bitmap setzen. Dazu geben wir die Breite und
Hhe des Rechtecks als bitmap.bmWidth und bitmap.bmHeight an. Die Koordinaten der linken oberen Ecke der Bitmap im Fenster bestimmen wir mit x
und y. Falls du nur einen Teil der Bitmap kopieren mchtest, kannst du die Breite
und Hhe des Rechtecks kleiner als die Ausmae der Bitmap machen und statt
der zwei Nullen die Koordinaten angeben, bei denen die linke obere Ecke des
Ausschnitts in der Bitmap liegen soll. Ausprobieren.
316

Kapitel 11 Bitmaps

Sandini Bib

Nach getaner Arbeit lschen wir den Speicher-Device-Context mit


DeleteDC(hdcmem);

So weit, so gut. Bei dem Schnappschuss in Kapitel 11.0 mit den fnf Kopien
der kleinen Figur ist dir sicher aufgefallen, dass sich die Bildchen gegenseitig
berdecken. Das ist genau das, was wir zu erwarten haben, wenn wir mit einem
Rechteck aus Pixeln pixelweise die Pixel in der Fensterbitmap berschreiben.
Weil ich beim Zeichnen der Bitmap einen grauen Hintergrund gewhlt habe,
steht jede Figur in ihrem eigenen grauen Kstchen, denn die Pixel des Hintergrunds werden genauso kopiert wie die Pixel der Figur im Vordergrund. BitBlt
kopiert einfach alle Pixel des Rechtecks und wei nichts davon, dass der Knstler
manche Pixel als Hintergrund und manche als Vordergrund betrachtet.
Ein durchsichtiger Hintergrund ist mglich, aber mit dem Windows SDK etwas
mhsam, siehe Kapitel 11.7. Als Nchstes wollen wir erst mal den Bitmaps das
Laufen beibringen.

11.2 Bitmapfilmchen
Animationen und der Eindruck von Bewegung entstehen durch die schnelle
Abfolge von Bildern. In Kapitel 8 haben wir so die Bewegung eines Balles
simuliert. Mit Bitmaps geht das genauso einfach. In diesem Kapitel wollen wir
ein Minifilmchen aus drei Einzelbildern (monster_0.bmp, monster_1.bmp,
monster_2.bmp) zum Laufen bringen. Hier sind drei Schnappschsse, die unser
Programm in Aktion zeigen:

Damit das Filmchen noch etwas lustiger wird, habe ich statt der Funktion BitBlt
die Funktion StretchBlt verwendet (
stretch heit dehnen). Diese funktioniert genau wie BitBlt, auer dass die Bitmap nicht eins zu eins kopiert wird,
sondern auch gedehnt oder gestaucht werden kann:

11.2 Bitmapfilmchen

317

Sandini Bib

Und wenn es schon um Filmchen geht, will ich dir auch gleich noch die einfachste
Mglichkeit der Tonausgabe zeigen.
Gehen wir das Programm also Schritt fr Schritt durch. Wir verwenden wieder
unsere Standard WinMain-Funktion. In
Bitmap1.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc, hdcmem;
char text[1000];
int xmax, ymax;
int stretch = 0;
double zoomfaktor0 = 0.1;
double zoomfaktor;
int ntimer = 0;
int dt = 140;
#define NBITMAPS 100
HBITMAP hbitmap[NBITMAPS];
int nbitmaps = 0;
char bitmapname[] = "monster";

siehst du verschiedene externe Variablen. Insbesondere gibt es eine Variable stretch fr Dehnen an/aus. In zoomfaktor speichern wir den momentanen Vergrerungsfaktor fr das Stretching, dessen kleinster Wert
zoomfaktor0 = 0.1 ist, also ein Zehntel der normalen Gre. In ntimer
zhlen wir die Ticks eines Zeitgebers, dessen Periode dt die Geschwindigkeit
bestimmt, in der unser Filmchen abluft.
Weil wir mehrere Bitmaps fr die Animation verwenden, speichern wir diese in
einem Feld HBITMAP hbitmap[NBITMAPS]; ab. Falls du die unglaubliche Geduld
aufbringst, mehr als 100 Bitmaps zu zeichnen, musst du den Wert von NBITMAPS
erhhen. Wie wir mehrere Bitmaps laden, siehst du in

318

Kapitel 11 Bitmaps

Sandini Bib
Bitmap1.c WinMain.cpp

/* lade mehrere Bitmaps: name_0.bmp, name_1.bmp, ... */


void ladebitmaps(char *name)
{
int i;
for (i = 0; i < NBITMAPS; i++) {
sprintf(text, "%s_%d.bmp", name, i);
hbitmap[i] = LoadImage(0, text, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbitmap[i])
break;
}
nbitmaps = i;
}

Die Funktion ladebitmaps rufen wir in unserem Beispiel mit der Zeichenkette
"monster" auf. In der for-Schleife erzeugen wir im String text nacheinander
die Dateinamen monster_0.bmp, monster_1.bmp und so weiter. Wie schon besprochen verwenden wir LoadImage, um die Bitmaps zu laden. Den Handle fr
die i-te Bitmap speichern wir in hbitmap[i].
Wir beenden die Schleife mit break, falls keine Bitmap geladen werden
konnte (und deshalb hbitmap[i] gleich 0 ist), oder falls i zu gro fr
unser Feld mit NBITMAPS Elementen geworden ist. Natrlich ist es nach
HBITMAP hbitmap[NBITMAPS]; offensichtlich, dass hchstens NBITMAPS Elemente erlaubt sind, aber wer garantiert uns, dass nicht mehr Bitmapdateien
vorhanden sind? Wir mssen unbedingt verhindern, dass zu viele Elemente in
ein Feld geschrieben werden. In so einem Fall ist Vertrauen viel zu wenig und
Kontrolle ein Muss. Also lassen wir i von 0 bis NBITMAPS1 laufen.
Den letzten Wert von i merken wir uns in nbitmaps, denn das ist die Anzahl
der Bitmaps, die wir laden konnten. Falls berhaupt keine Bitmapdateien fr
den angegebenen Namen gefunden werden konnten, ist nbitmaps gleich 0. Je
nach Lust und Laune kannst du also eine unterschiedliche Anzahl von Bitmaps
fr das Filmchen zeichnen. ladebitmaps ldt so viele Bitmaps, wie unter der
fortlaufenden Nummer 0, 1, . . ., NBITMAPS1 gefunden werden knnen.

11.2 Bitmapfilmchen

319

Sandini Bib

In der nchsten Funktion malen wir eine Bitmap mit StretchBlt:


Bitmap1.c WinMain.cpp

/* male Bitmap */
void malebitmap(HBITMAP hbitmap, int x, int y, double faktor)
{
BITMAP bitmap;
int dx, dy;
SelectObject(hdcmem, hbitmap);
GetObject(hbitmap, sizeof(BITMAP), &bitmap);
dx = bitmap.bmWidth * faktor;
dy = bitmap.bmHeight * faktor;
StretchBlt(hdc, xdx/2, ydy/2, dx, dy,
hdcmem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
if (dx > 3*xmax || dy > 3*ymax) {
zoomfaktor = zoomfaktor0;
Rectangle(hdc, 1, 1, xmax+1, ymax+1);
}
}

Die Bitmap bergeben wir als Handle in hbitmap, zudem bergeben wir die
Zielkoordinaten und einen Vergrerungsfaktor in faktor. Beachte, dass in diesem Beispiel hdc und hdcmem externe Variablen sind, die vor dem Aufruf von
malebitmap initialisiert werden mssen.
Die Gre der Bitmap entnehmen wir der BITMAP-Struktur, die wir mit
GetObject erhalten. Im Fenster soll die Bitmap dx Pixel breit und dy Pixel
hoch sein, wobei wir Breite und Hhe in bitmap mit dem Faktor faktor
multiplizieren. In
StretchBlt(hdc, xdx/2, ydy/2, dx, dy,
hdcmem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);

siehst du, wie StretchBlt verwendet wird. Genau wie bei BitBlt kopieren wir
von hdcmem nach hdc, aber diesmal geben wir Breite und Hhe fr zwei Rechtecke an. Der Ausschnitt in hdcmem hat seine linke obere Ecke bei Koordinaten
0 und 0, und Breite und Hhe sind die der geladenen Bitmap. Das heit, wir
verwenden die ganze Bitmap. Der Ausschnitt in hdc erhlt die skalierte Breite
dx und Hhe dy. Weil im Beispiel x und y die Mitte der Bitmap im Fenster angeben sollen, aber StretchBlt genau wie BitBlt die linke obere Ecke erwartet,
berechnen wir die Koordinaten der linken oberen Ecke als xdx/2 und ydy/2.
Das Endergebnis ist, dass eine um den Faktor faktor vergrerte bzw. verkleinerte Kopie der Bitmap hbitmap in das Fenster gemalt wird, wobei x und y die
Mitte der gezeichneten Bitmap im Fenster angeben.
320

Kapitel 11 Bitmaps

Sandini Bib

Die Zeilen
if (dx > 3*xmax || dy > 3*ymax) {
zoomfaktor = zoomfaktor0;
Rectangle(hdc, 1, 1, xmax+1, ymax+1);
}

haben nichts mit der allgemeinen Verwendung von StretchBlt zu tun, sondern
sollen in unserem Beispiel verhindern, dass die skalierte Bitmap zu gro wird.
Falls wir die Fenstergre xmax oder ymax um mehr als einen Faktor 3 berschreiten, setzen wir zoomfaktor auf seinen Minimalwert zoomfaktor0 zurck
und lschen das Fenster mit einem Aufruf von Rectangle.
Es folgt die Funktion malen:
Bitmap1.c WinMain.cpp

/* Malen */
void malen(HDC hdc)
{
int n;
double faktor;
if (nbitmaps == 0) {
sprintf(text, "Fehler: keine Bitmap %s_0.bmp", bitmapname);
TextOut(hdc, 10, 10, text, strlen(text));
return;
}
n = ntimer % nbitmaps;
faktor = stretch ? zoomfaktor : 1.0;
malebitmap(hbitmap[n], xmax/2, ymax/2, faktor);
}

Falls keine Bitmaps geladen werden konnten, ist nbitmaps gleich 0. In diesem
Fall geben wir eine Fehlermeldung aus und verlassen die Funktion malen mit
return;. Mit
malebitmap(hbitmap[n], xmax/2, ymax/2, faktor);

wollen wir die n-te Bitmap des Filmchens in die Mitte des Fensters (Koordinaten
xmax/2 und ymax/2) mit dem Vergrerungsfaktor faktor malen.
Die externe Variable ntimer zhlt die Ticks des Zeitgebers, den wir fr die
Animation verwenden. Der Ausdruck ntimer % nbitmaps durchluft also bei
nbitmaps gleich 3 die Werte 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 und so weiter.
Den Vergrerungsfaktor bestimmen wir in Abhngigkeit vom Schalter
stretch. Falls stretch wahr ist, setzen wir faktor gleich der externen
Variable zoomfaktor. Falls stretch falsch ist, setzen wir faktor gleich 1.0, das
heit, wir knnten genauso gut BitBlt verwenden.

11.2 Bitmapfilmchen

321

Sandini Bib

Die nchste Funktion ist eine kleine Helferfunktion, in der wir die Befehle zur
Initialisierung des Filmchens gesammelt haben:
Bitmap1.c WinMain.cpp

/* Initialisierung */
void init(HWND hwnd)
{
if (stretch) PlaySound("mami.wav", 0, SND_FILENAME | SND_LOOP | SND_ASYNC);
else
PlaySound("hilfe.wav", 0, SND_FILENAME | SND_LOOP | SND_ASYNC);
zoomfaktor = zoomfaktor0;
Rectangle(hdc, 1, 1, xmax+1, ymax+1);
InvalidateRect(hwnd, 0, 0);
}

Den Befehl PlaySound wollen wir in Unterkapitel 11.3 etwas nher besprechen,
damit du die Beschreibung leichter wiederfindest. Aber offensichtlich geht es
darum, je nachdem, ob gestretcht wird oder nicht, eine unterschiedliche Tonausgabe zu initialisieren. Des Weiteren initialisieren wir den Zoomfaktor, lschen
das Fenster und veranlassen mit InvalidateRect, dass das Fenster neu gemalt
wird.
Die letzte Funktion des Beispiels ist die Fensterprozedur:

322

Kapitel 11 Bitmaps

Sandini Bib
Bitmap1.c WinMain.cpp

/* Rueckruffunktion unseres Fensters */


LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)
{
// Fenster auf
if (m == WM_CREATE) {
SetTimer(hwnd, 0, dt, 0);
SetWindowText(hwnd, "Bitmap (mit Sound, klick mich)");
hdc = GetDC(hwnd);
hdcmem = CreateCompatibleDC(hdc);
ladebitmaps(bitmapname);
if (nbitmaps > 0) {
SelectObject(hdcmem, hbitmap[0]);
SelectObject(hdc, CreateSolidBrush(GetPixel(hdcmem, 0, 0)));
}
}
// Malen
else if (m == WM_PAINT) {
malen(hdc);
ValidateRect(hwnd, 0);
}
// Timer
else if (m == WM_TIMER) {
ntimer++;
if (stretch) zoomfaktor *= 1.15;
InvalidateRect(hwnd, 0, 0);
}
// Tastatur oder Maus
else if (m == WM_KEYDOWN || m == WM_LBUTTONDOWN || m == WM_RBUTTONDOWN) {
stretch = !stretch;
init(hwnd);
}
// Groesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
init(hwnd);
}
// Aufraeumen
else if (m == WM_DESTROY) {
KillTimer(hdc, 0);
PlaySound(0, 0, 0);
PostQuitMessage(0);
}
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

11.2 Bitmapfilmchen

323

Sandini Bib

Beim Erzeugen des Fensters, WM_CREATE, starten wir den Zeitgeber fr die
Animation, ndern den Titel des Fensters und speichern den Fenster-DC und
den Speicher-DC in den externen Variablen hdc und hdcmem. Dann rufen wir
ladebitmaps auf, um alle verfgbaren Bitmaps mit Namen bitmapname zu laden. bitmapname haben wir zu Beginn des Programmtextes als externe Variable
definiert, damit der Name leichter zu finden ist, falls wir ein neues Filmchen
ausprobieren wollen.
Falls mindestens eine Bitmap geladen werden konnte (nbitmaps > 0), erzeugen
wir mit
SelectObject(hdcmem, hbitmap[0]);
SelectObject(hdc, CreateSolidBrush(GetPixel(hdcmem, 0, 0)));

den Pinsel fr den Fensterhintergrund, den wir wiederholt mit Rectangle malen
werden. Als Farbe des Pinsels wollen wir die Farbe des Pixels in der linken oberen
Ecke der nullten Bitmap verwenden. Also aktivieren wir hbitmap[0] in dem
Speicher DC hdcmem, so dass wir dann mit GetPixel(hdcmem, 0, 0) die Farbe
dieses Pixels aus der Bitmap hbitmap[0] auslesen knnen.
Ansonsten ist WindowProc recht einfach zu verstehen. Bei WM_PAINT rufen wir
wie gewohnt die Funktion malen auf. Bei einem Tick des Zeitgebers, WM_TIMER,
zhlen wir ntimer eins weiter, vergrern den Zoomfaktor um den Faktor 1.15
und veranlassen mit InvalidateRect die nchste WM_PAINT-Nachricht. Das bedeutet, dass wir ein und denselben Zeitgeber verwenden, um die Figur ihre Bildchen und verschiedene Vergrerungsstufen durchlaufen zu lassen.
Auf Tastendruck oder Mausklick links oder rechts bettigen wir den Schalter
stretch und starten die Grafikausgabe mit init(hwnd); neu. Wenn sich die
Bildschirmgre ndert, wird bei WM_SIZE neu gestartet.
Vor Beendigung des Programms stoppen wir bei WM_DESTROY den Zeitgeber und
beenden die Tonausgabe.
Probiere verschiedene kleine nderungen aus. Z.B. kannst du die Periode des
Zeitgebers ndern und mit dem Zoomfaktor spielen. Kann dein Computer bei
groem Zoomfaktor noch mit dem Zeitgeber Schritt halten? Meiner nicht. Ich
sehe deutlich, wie die Animation langsamer als der Zeittakt abluft. Fr ernsthafte Programme oder Spiele ist das natrlich ein Problem.
Die eigentliche Herausforderung ist natrlich, neue Filmchen zu machen. Dabei
wirst du feststellen, dass das ziemlich mhsam ist, aber trotzdem Spa macht.
Zur Abwechslung kannst du statt "monster" als Bitmapname "held" angeben.

11.3 Tonausgabe mit PlaySound


Tonausgabe hat an und fr sich nichts mit Bitmaps zu tun. Aber weil Tonausgabe
bei Filmchen oder Spielen mit Bitmaps zusammen auftritt, will ich die Funktion
324

Kapitel 11 Bitmaps

Sandini Bib

PlaySound (spiele Gerusch oder Klang) kurz beschreiben. Sound kann Sprache, Musik oder einfach nur Krach bedeuten. Schne Soundeffekte findest du in
Spielen. Die Funktion PlaySound wird in der Microsoft-Hilfe unter Multimedia
Reference beschrieben.

Sprache, Musik usw. kann im so genannten Wave-Format in digitaler Form in


einer Datei gespeichert werden (Endung .wav).
Wave heit Welle. Es
versteht sich von selbst, dass du nur dann etwas mit solchen Dateien anfangen
kannst, wenn dein Computer ber eine Soundkarte oder einen Soundchip verfgt. Wenn dem so ist, kannst du normalerweise ber ein Mikrofon mit mitgelieferter Software dich selbst oder irgendwelche Gerusche aufnehmen. Im letzten
Beispiel hrst du Vorhang auf mich. Allerdings habe ich meine Stimme mit
einem Klangbearbeitungsprogramm verfremdet. Wave-Dateien mit Soundeffekten und verschiedene Soundsoftware findest du auch im Internet.
Es gibt also wie bei Bitmaps bestimmte Dateiformate (.bmp, .wav) und es geht
darum, den Inhalt dieser Dateien zu laden und auszugeben. Einen typischen
Funktionsaufruf von PlaySound hast du im letzten Beispiel gesehen:
PlaySound("hilfe.wav", 0, SND_FILENAME | SND_LOOP | SND_ASYNC);

Das erste Argument ist die Adresse einer Zeichenkette, das zweite ist fr uns
uninteressant und muss Null sein und das dritte Argument whlt verschiedene
Optionen aus. Die Optionen werden durch Konstanten angegeben, die durch den
Bitoperator | kombiniert werden.
Mit SND_FILENAME legen wir fest, dass das erste Argument den Namen der Datei
angibt, die geladen werden soll. Die Wave-Datei wird nicht nur geladen, sondern
es wird auch auf der Stelle mit der Wiedergabe des Sounds begonnen, falls kein
Fehler auftritt.
Bei SND_ASYNC kehrt die Funktion PlaySound sofort nach Beginn des Sounds
zurck und die Wiedergabe luft im Hintergrund ab (deshalb die Bezeichnung
asynchron). Dein Programm kann gleichzeitig neue Befehle bearbeiten. Bei
SND_SYNC (synchron) kehrt PlaySound erst nach Beendigung der Wiedergabe
zurck und dein Programm muss warten.
Wegen SND_LOOP wird der Sound wiederholt, bis er gezielt abgeschaltet wird. Im
Beispiel tun wir dies mit
PlaySound(0, 0, 0);

Ein Sound, der gerade wiedergegeben wird, wird auch dann gestoppt, wenn
PlaySound erneut mit einer gltigen Wave-Datei aufgerufen wird. Falls du
nicht mchtest, dass ein Sound von einem neuen Sound unterbrochen und
abgewrgt wird, kannst du SND_NOSTOP angeben.
PlaySound hat eine wesentliche Einschrnkung. Es kann immer nur eine WaveDatei wiedergegeben werden, d.h. Wave-Dateien knnen mit PlaySound nicht

gemischt werden.
11.3 Tonausgabe mit PlaySound

325

Sandini Bib

11.4

Bitmappuffer

Angenommen, du willst ein Programm schreiben, das viele Bitmaps durch die
Gegend schiebt. Aus zweierlei Grnden ist es ntzlich, nicht direkt in die Fensterbitmap zu malen, sondern eine extra Bitmap derselben Gre als Zwischenspeicher oder Puffer zu verwenden. Erst malen wir den Hintergrund in den Puffer, dann malen wir verschiedene kleinere Bitmaps fr Objekte und Spielfiguren darber, die sich gegenseitig teilweise verdecken knnen. Nachdem wir die
Szene zusammengestellt haben, knnen wir mit einem einzigen BitBlt-Befehl
das ganze Fenster auf einmal bermalen. Ein offensichtlicher Vorteil ist, dass ein
einziges schnelles BitBlt weniger Flackern erzeugt, als wenn ein Objekt erst mit
dem Hintergrund gelscht wird, sagen wir mit Schwarz, und dann in Rot wieder
bermalt wird. Diese Erfahrung haben wir schon ohne Bitmaps in Kapitel 8.7
mit dem bewegten Ball gemacht.
Besser geht es nur, wenn du statt der Funktionen des Windows SDK Zusatzsoftware wie DirectX oder OpenGL verwendest. DirectX oder OpenGL knnen
wir hier nicht besprechen, aber zwecks Motivation will ich ein paar Worte dazu
sagen. Die Pixel, die du auf dem Bildschirm siehst, stehen als Bitmap im Videospeicher deiner Grafikhardware. Die Bildschirmanzeige wird Pixel fr Pixel
aufgebaut, zeilenweise von links oben nach rechts unten. Das geht sehr schnell,
normalerweise mit mindestens 60 Hertz (60-mal pro Sekunde). Wenn ein Programm schnell genug wre, den nchsten Bildschirminhalt in den Grafikspeicher
zu kopieren, whrend die Grafikhardware nicht mit der Bildausgabe beschftig
ist, wrde man berhaupt kein Flackern sehen. Die gebruchlichste Methode
ist, zwei Bitmappuffer im Grafikspeicher anzulegen. Der Videopuffer wird 60mal pro Sekunde gelesen, wenn die Grafikhardware das nchste Bild braucht.
Der Zwischenpuffer wird vom Programm mit den Pixeln fr das nchste Bild
vollgeschrieben. Wenn dann das Signal kommt, dass die nchste Seite aus dem
Grafikspeicher auf den Bildschirm gemalt werden soll, kann man die beiden Puffer austauschen, ohne irgendwelche Pixel kopieren zu mssen. Man vertauscht
sozusagen die Zeiger in den Grafikspeicher, die angeben, welcher Puffer wie verwendet werden soll. Das Ergebnis sind perfekte Bildbergnge. Wenn ein Programm nur fnf und nicht 60 Bilder pro Sekunde berechnen kann, wartet es
Page Flip (Seitenwecheinfach auf das nchste Bildwechselsignal fr den
sel), um wenigstens gleichmige Bildbergnge zu erzeugen.
Also gut. Dieses Buch behandelt nicht DirectX oder OpenGL, aber wenn du ernsthaft Grafik programmieren mchtest, stehst du an dieser Stelle sozusagen in den
Startlchern. Du solltest jetzt gengend Programmiererfahrung haben, um in
DirectX oder OpenGL einzusteigen. Wir wollen aber trotzdem ein Experiment
mit dem Windows SDK und mit Bitmappuffern veranstalten, um herauszubekommen, wie schnell BitBlt aus dem Windows SDK berhaupt ist. Auch in
DirectX wird BitBlt fr viele Aufgaben verwendet.
326

Kapitel 11 Bitmaps

Sandini Bib

Hier ist der Plan. Wir wollen ein Programm besprechen, das den ganzen Bildschirm mit kleinen Bitmaps vollschreibt. Dabei soll wahlweise direkt in den Videopuffer gemalt werden oder erst in einen Bitmappuffer, der dann mit einem
einzigen BitBlt ausgegeben wird. Mit einem Zeitgeber wollen wir zhlen, wie
frames per second, fps) bei verschiedenen Fenviele Bilder pro Sekunde (
stergren erzeugt werden knnen.
Unser Beispiel beginnt mit
Bitmap2.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc, hdcfenster;
HDC hdcpuffer = 0;
HBITMAP hbmpuffer = 0;
char text[1000];
int xmax, ymax;
int fehler = 0;
int nmalen = 0;
int ntimer = 0;
int npixel = 0;
HBITMAP hbmheld[3];
char heldname[] = "held";
/* Breite und Hoehe der Bitmaps */
int bmw = 32;
int bmh = 32;
/* lade eine Bitmap */
HBITMAP ladeeinebitmap(char *dateiname)
{
HBITMAP hbm;
hbm = LoadImage(0, dateiname, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbm)
fehler = 1;
return hbm;
}
/* lade alle Bitmaps */
void ladebitmaps(void)
{
int i;
for (i = 0; i < 2; i++) {
sprintf(text, "%s_%d.bmp", heldname, i);
hbmheld[i] = ladeeinebitmap(text);
}
hbmheld[2] = ladeeinebitmap("schwarz.bmp");
}

11.4

Bitmappuffer

327

Sandini Bib

Hier siehst du gleich am Anfang mehrere Device Contexts, die wir fr unsere
verschiedenen Bitmappuffer brauchen. Das Laden der Bitmaps haben wir in zwei
Funktionen aufgeteilt. Die Funktion ladeeinebitmap ldt eine Bitmap (was fr
ein schlauer Name) und setzt die externe Variable fehler auf wahr, falls ein
Fehler auftrat. Mit ladebitmaps laden wir drei Bitmaps, deren Handles wir uns
in hbmheld merken. Aus Faulheit haben wir die Breite bmw und die Hhe bmh
der Bitmaps von vornherein auf 32 festgelegt. Die Bildschirmausgabe erledigen
wir in
Bitmap2.c WinMain.cpp

/* Malen */
void malen(HDC hdc)
{
HDC hdctmp;
int i, dx, x, y;
if (fehler) {
sprintf(text, "Fehler beim Laden der Bitmaps.");
TextOut(hdc, 10, 10, text, strlen(text));
return;
}
hdctmp = CreateCompatibleDC(hdc);
for (x = 3*bmw; x < xmax; x += bmw) {
for (y = 0; y < ymax; y += bmh) {
dx = nmalen % (3*bmw);
i = (3 + x/bmw + y/bmh)%3;
SelectObject(hdctmp, hbmheld[i]);
BitBlt(hdc, x+dx, y, bmw, bmh, hdctmp, 0, 0, SRCCOPY);
npixel += bmw*bmh;
}
}
DeleteDC(hdctmp);
if (hdc != hdcfenster) {
BitBlt(hdcfenster, 0, 0, xmax, ymax, hdc, 0, 0, SRCCOPY);
npixel += xmax*ymax;
}
}

Falls ein Fehler vorliegt, wird nicht gemalt. Meine erste Version der Doppelschleife war
for (x = 0; x < xmax; x += bmw) {
for (y = 0; y < ymax; y += bmh) {
i = 0;
SelectObject(hdctmp, hbmheld[i]);

328

Kapitel 11 Bitmaps

Sandini Bib

BitBlt(hdc, x, y, bmw, bmh, hdctmp, 0, 0, SRCCOPY);


}
}

Wie bei einem Schachbrett durchluft diese Schleife alle Felder im Abstand der
Bitmapgre. Wir whlen die Bitmap hbmheld[0] fr einen temporren Device
Context hdctmp und kopieren die Bitmap mit BitBlt nach Device Context hdc.
Ausprobieren, mit dieser Schleife bekommst du lauter identische kleine Figuren, als ob du deine Badezimmerwand etwas abgedreht gekachelt httest. Dann
habe ich die Schleife so hingetrickst, dass drei verschiedene Bitmaps ausgegeben
werden (siehe Definition von i). Das neue Kachelmuster bewegt sich um ein
Pixel nach rechts, wenn sich die Variable nmalen um eins erhht. Nach jedem
BitBlt zhlen wir mit npixel += bmw*bmh; die Anzahl der Pixel, die geblittet
wurden. Weil ein kleiner Teil der Pixel auerhalb des Fensters liegen und wegen dem Fensterclipping nicht gemalt werden (siehe Kapitel 4.2), ist npixel am
Ende etwas zu gro, aber so genau wollen wir es nicht nehmen. Ich verwende
drei Bitmaps, die sich bewegen, aus rein psychologischen Grnden, denn dann
wird offensichtlich, dass tatschlich geblittet wird, was das Zeug hlt. Fr die
geplante Geschwindigkeitsmessung ist es ziemlich egal, ob wir eine Kachelwand
aus statischen oder bewegten Bitmaps verwenden.
Richtig verdchtig sehen die Zeilen
if (hdc != hdcfenster) {
BitBlt(hdcfenster, 0, 0, xmax, ymax, hdc, 0, 0, SRCCOPY);
npixel += xmax*ymax;
}

aus. Hier wird klar, dass hdc unter Umstnden gar nicht die Fensterbitmap bezeichnet! In der Tat rufen wir die Funktion malen mit verschiedenen Device
Contexts auf. Und wenn hdc zum Bitmappuffer und nicht zum Fensterpuffer
hdcfenster gehrt, kopieren wir die gesamte Bitmap in hdc in Fenstergre
(xmax und ymax) nach hdcfenster. Und zhlen auch diese Pixel in npixel. Bei
mir sieht das Ergebnis jedenfalls so aus:

11.4

Bitmappuffer

329

Sandini Bib

Jede Menge Bitmapklone, wie niedlich. Jetzt stellt sich die Frage, wie wir die
verschiedenen Bitmappuffer verwalten und wie wir die Bilder und Pixel pro Sekunde messen, die in der Titelleiste des Fensters angezeigt werden. Das geschieht
in WindowProc:
Bitmap2.c WinMain.cpp

LRESULT CALLBACK WindowProc(HWND hwnd, UINT m, WPARAM wParam, LPARAM lParam)


{
MSG msg;
// Fenster auf
if (m == WM_CREATE) {
SetWindowText(hwnd, "BitBlt um die Wette");
SetTimer(hwnd, 0, 1000, 0);
hdcfenster = GetDC(hwnd);
ladebitmaps();
sprintf(text, "");
}
// Malen
else if (m == WM_PAINT) {
malen(hdc);
nmalen++;
ntimer++;
if (PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_NOREMOVE))
ValidateRect(hwnd, 0);
}
// Timer
else if (m == WM_TIMER) {
sprintf(text, "%s: %d Bilder/s, %.1f Megapixel/s",
(hdc == hdcfenster) ? "Direkt" : "Puffer",
ntimer, npixel/1000000.0);
SetWindowText(hwnd, text);
ntimer = npixel = 0;
InvalidateRect(hwnd, 0, 0);
}

330

Kapitel 11 Bitmaps

Sandini Bib
Bitmap2.c WinMain.cpp

// Tastatur
else if (m == WM_KEYDOWN) {
hdc = (hbmpuffer && hdc == hdcfenster) ? hdcpuffer : hdcfenster;
}
// Groesse
else if (m == WM_SIZE) {
xmax = LOWORD(lParam);
ymax = HIWORD(lParam);
if (hbmpuffer) {
DeleteDC(hdcpuffer);
DeleteObject(hbmpuffer);
}
hbmpuffer = CreateCompatibleBitmap(hdcfenster, xmax, ymax);
if (hbmpuffer) {
hdcpuffer = CreateCompatibleDC(hdcfenster);
SelectObject(hdcpuffer, hbmpuffer);
if (hdc != hdcfenster)
hdc = hdcpuffer;
}
if (!hbmpuffer)
hdc = hdcfenster;
}
// Aufraeumen
else if (m == WM_DESTROY) {
if (hbmpuffer) {
DeleteDC(hdcpuffer);
DeleteObject(hbmpuffer);
}
KillTimer(hwnd, 0);
PostQuitMessage(0);
}
else
return DefWindowProc(hwnd, m, wParam, lParam);
return 0;
}

Bei WM_CREATE starten wir einen Zeitgeber mit einer Periode von 1 Sekunde, und
wenn die Uhr mit der Nachricht WM_TIMER tick macht, geben wir die Anzahl der
Bilder und Pixel aus, die wir in ntimer und npixel gezhlt haben. Nach der
Ausgabe setzen wir diese Variablen wieder auf Null, damit bis zum nchsten
Tick aufs Neue gezhlt werden kann.

11.4

Bitmappuffer

331

Sandini Bib

Gezhlt wird bei WM_PAINT. Dort rufen wir wie gewohnt die Funktion malen auf.
ntimer verwenden wir fr die Messung der Bilder pro Sekunde, whrend wir
nmalen immer weiter hochzhlen, damit die Grafikausgabe nicht im Sekundentakt einen Sprung macht. Mit nmalen verschieben wir die Bitmaps nach rechts,
siehe Funktion malen. In
if (PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_NOREMOVE))
ValidateRect(hwnd, 0);

begegnet dir zum ersten Mal die Funktion PeekMessage (


peek heit
gucken). Im Gegensatz zu GetMessage (Kapitel 10.10) entfernt PeekMessage
nicht in jedem Fall eine Nachricht aus der Nachrichtenwarteschlange. Wir gucken
nur nach, ob vielleicht die Nachricht WM_TIMER darauf wartet, abgeholt zu werden. Falls ja, beenden wir das Malen ohne Ende mit ValidateRect(hwnd, 0);.
Mit anderen Worten, wir verwenden den uns schon vertrauten Trick, so schnell
wie mglich zu malen, indem wir WM_PAINT nicht mit ValidateRect aus der
Nachrichtenschlange entfernen. Es sei denn, eine WM_TIMER-Nachricht soll bedient werden.
Jetzt ist nur noch die Sache mit dem Bitmappuffer zu erklren. Bei WM_CREATE
holen wir uns wie blich den Device Context fr das Fenster hwnd mit GetDC.
Den Handle nennen wir hdcfenster, um ihn von dem Handle hdcpuffer fr
den Bitmappuffer zu unterscheiden.
Bevor wir einen Device Context fr einen Bitmappuffer erzeugen knnen, mssen wir aber erst einmal eine neue Bitmap in Fenstergre anlegen! Das erledigen wir bei WM_SIZE mit CreateCompatibleBitmap. Der Funktionsaufruf ist
hbmpuffer = CreateCompatibleBitmap(hdcfenster, xmax, ymax);

Hier erzeugen wir eine neue Bitmap mit Speicherplatz und allen ntigen Zusatzinformationen, die zum Fenster kompatibel ist (also z. B. dieselbe Farbtiefe
besitzt). Die Gre knnen wir frei whlen, solange noch gengend Speicherplatz frei ist. Falls keine neue Bitmap in der gewnschten Gre erzeugt werden
konnte, ist hbmpuffer Null. An vier Stellen in WindowProc berprfen wir, ob
hbmpuffer existiert, und reagieren entsprechend. Das Entscheidende ist, dass wir
die Bitmap hbmpuffer genauso zum Malen verwenden knnen wie jede Fensterbitmap.
(WM_KEYDOWN und VK_SPACE) zwischen PufferBeachte auch, dass wir mit
verwendung und direktem Zugriff umschalten knnen, sofern ein Puffer berhaupt existiert.
Jetzt kannst du munter drauf los testen. Fenster klein, Fenster gro, verschiedene
Computer. Ich war angenehm berrascht, wie schnell BitBlt arbeitet. Selbst bei
einer Auflsung von 1024 768 Pixeln bei 16 Bit Farbtiefe ist das Testprogramm
auf meinem Pentium II PC recht flott (ein 433 MHz Celeron Prozessor mit 66
MHz Bustakt und GeForce 2 MX PCI Grafikkarte, falls dir das was sagt):
332

Kapitel 11 Bitmaps

Sandini Bib

Das Programm luft schneller, wenn du es auerhalb von BCB startest. Wenn
ich die Maus ins Fenster bewege, wird das Programm einen Moment langsamer.
Typischerweise geht die Bilderrate etwas runter und die Pixelrate hoch, wenn
ich von Direkt auf Puffer umschalte. Das liegt daran, dass dann ein zustzliches
groes BitBlt ausgefhrt wird. Dieses eine groe BitBlt verbraucht wenig
Zeit, aber es werden fast doppelt so viele Pixel gezhlt. Ausprobieren, wie schnell
wird das Programm, wenn du keine kleinen Bitmaps, sondern nur Bitmaps in
Fenstergre kopierst?
Fr deine eigenen Programme kannst du jetzt abschtzen, bei wie vielen kleinen Bitmaps dein Rechner in die Knie geht und keine vernnftigen Bilderraten
mehr erreicht. Der nchste Schritt wre z. B. DirectX. Dort gibt es eine Funktion
BltFast, die Bitmaps innerhalb des Grafikspeichers kopiert. Das ist schneller
als das Kopieren vom Arbeitsspeicher des Computers in den Grafikspeicher. Also
ldt man mglichst viele Bitmaps in den Grafikspeicher und fhrt schnelle lokale
Kopien innerhalb des Grafikspeichers aus. Unterm Strich wrde ich aber sagen,
dass sich die Windows SDK-Funktion BitBlt sehr wacker schlgt.

11.4

Bitmappuffer

333

Sandini Bib

11.5

Gekachelte Landkarte

In Kapitel 11.8 wollen wir eine animierte Figur auf einer Landkarte herumlaufen
lassen. Sehr wahrscheinlich ist dir dieses Prinzip schon in Spielen begegnet.
Als Erstes wollen wir uns berlegen, wie wir die Landkarte erzeugen. Natrlich
knntest du eine groe Bitmap zeichnen und diese als Hintergrund verwenden. Sehr hufig werden aber Landkarten verwendet, die sich aus kleinen, gleich
tiles) zusammensetzen. Der Vorteil ist, dass nur wenige
groen Kacheln (
kleine Bitmaps gezeichnet werden mssen, und so wollen wir es machen. Bei
meinen Zeichenknsten sieht das dann so aus:

Normalerweise passt die Landkarte oder der Hintergrund nicht auf einmal ins
Programmfenster. Das Fenster stellt nur einen Ausschnitt dar, der sich verschiebt,
wenn die Hauptfigur des Spieles sich bewegt oder wenn der Spieler die Landkarte
mit der Maus bewegt. Das geht am einfachsten, wenn der Hintergrund tatschlich als eine einzige groe Bitmap im Speicher steht. Wir wissen schon, dass in
diesem Fall die Ausgabe sehr einfach ist, weil BitBlt es erlaubt, Ausschnitte zu
kopieren.
Etwas schwieriger ist es, einen Ausschnitt aus einer gekachelten Landkarte zu
zeichnen, indem nur die Kachelfelder gezeichnet werden, die tatschlich sichtbar
sind. Der Vorteil ist, dass wesentlich grere Landkarten gestaltet werden knnen. In unserem Beispiel verwenden wir Kacheln mit 32 mal 32 Pixeln, also 1024
Pixel pro Kachel. Statt dieser 1024 Pixel knnen wir aber auch 1 Nummer pro
334

Kapitel 11 Bitmaps

Sandini Bib

Kachel speichern, die wir anhand einer Liste in die entsprechende Bitmap bersetzen. Unsere Landkarte ist dann ein zweidimensionales Feld aus Kachelindizes.
In diesem Kapitel wollen wir als Erstes eine gekachele Landkarte implementieren.
In Kapitel 11.6 machen wir den Ausschnitt beweglich und in 11.7 kommt eine
animierte Bitmap fr die Figur dazu.
Fangen wir also mit den Datenstrukturen fr unsere gekachelte Landkarte an:
Bitmap3.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* externe Variable */
HDC hdc, hdcmem;
char text[1000];
int xmax, ymax;
/* Breite und Hoehe der Bitmaps */
int bmw = 32;
int bmh = 32;
/* Kacheln fuer die Karte */
typedef struct {
char c;
int block;
char *name;
HBITMAP hbm;
} KACHEL;
/* Legende fuer die Karte */
KACHEL kachel[] = {
{0, 1, "schwarz"},
{w, 1, "wasser"},
{m, 1, "mauer"},
{g, 0, "gras"},
{s, 0, "sand"},
{0}};
int nkacheln;
/* 2d Feld fuer Karte, speichert Indizes fuer Kachelliste */
#define KI 100
#define KJ 100
int karte[KI][KJ];
int ki, kj;

Die Struktur vom Typ KACHEL enthlt die folgende Informationen: Ein Zeichen
c, das wir als Krzel fr die Kachel verwenden wollen. Eine Integerzahl block,
die angibt, ob Figuren hier laufen drfen. Einen Zeiger auf eine Zeichenkette
11.5

Gekachelte Landkarte

335

Sandini Bib

char *name fr den Dateinamen der Kachel. Und schlielich den Handle zur

Bitmap.
In KACHEL kachel[] = { ... }; definieren wir ein Feld aus Kacheln, das
gleich noch initialisiert wird. Wegen [ ] wird die Anzahl der Elemente des
Feldes genau wie bei Zeichenketten automatisch auf die tatschliche Anzahl der
Elemente in der Initialisierung festgelegt. Die Elemente sind als Listen zwischen
geschweiften Klammern angegeben, z. B.
{w, 1, "wasser"}

Jedes Element ist eine Struktur vom Typ KACHEL und w, 1 und "wasser" werden der Reihe nach den Strukturelementen c, block und name zugewiesen. Falls
weniger Initialisierer als Strukturelemente vorhanden sind, bleiben die restlichen
Strukturelement uninitialisiert. In unserem Beispiel wird der Handle hbm erst bei
Aufruf der Funktion ladebitmaps initialisiert. Der letzten Kachel geben wir den
Code 0. Auf diese Weise knnen wir in ladebitmaps das Ende der Kachelliste
bestimmen. Die Anzahl der Elemente werden wir in nkacheln speichern. Das ist
praktisch, weil wir die Liste der Kacheln leicht erweitern knnen, ohne stndig
die Kacheln nachzhlen zu mssen. Hier ist die Funktion ladebitmaps:
Bitmap3.c WinMain.cpp

/* lade Bitmaps */
void ladebitmaps(void)
{
int k;
for (k = 0; kachel[k].c; k++) {
sprintf(text, "%s.bmp", kachel[k].name);
kachel[k].hbm = LoadImage(0, text, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!kachel[k].hbm) {
nkacheln = 0;
return;
}
}
nkacheln = k;
}

Wir fhren eine Schleife ber alle Elemente in kachel aus, die beendet wird,
wenn das Zeichen c der k-ten Kachel (kachel[k].c) gleich 0 ist. Den Namen
der k-ten Kachel (kachel[k].name) verwenden wir als Dateiname und speichern
den Handle zur Bitmap in kachel[k].hbm.
Wie speichern wir die Karte? Wir definieren ein zweidimensionales Feld
int karte[KI][KJ];, in dem wir die Indizes der Kacheln speichern wollen. Im
Prinzip knnten wir das Feld karte direkt in int karte[KI][KJ] = { ... };
initialisieren, hnlich wie wir das gerade bei der Kachelliste gemacht haben.
Stattdessen verwenden wir

336

Kapitel 11 Bitmaps

Sandini Bib
Bitmap3.c WinMain.cpp

/* Karte als Liste von Strings */


char *initkarte[] = {
"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww",
"wwwwwwwggggggggggggggggwwwwwwwwwwwwwwwww",
"wwwwwssggggggggggggggggggwwwwwwwwwwwwwww",
"wwwwwswwwwwwwwwwwwwwwwgggwwwwwwwwwwwwwww",
"wwwwwswwwwgggggggwwwwwwggwwwwwwwwwwwwwww",
"wwwwwswwwwgggggggwwwwwwggwwwwwwwwwwwwwww",
"wwwwwswwwwgggggggwwwwwwggwwwwwwwwwwwwwww",
"wwwwwswwwwgggggggwwwwwwggwwwwwwwwwwwwwww",
"wwwwwswwwwgggggggwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwswwwwwwwgwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwswwwwwwwgwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwsssssssggwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwwwwwwwww",
"wwwwwwwwggggggggggggggggggggggggggwwwwww",
"wwwwwwgggggggggggwwwwwwwwwwwwwwwwgwwwwww",
"wwwwggggggggggggggwwwwwwwwwwwwwwwgwwwwww",
"wwwwwgggggggggggggwwwwwwwwwwwwwwwgwwwwww",
"wwwgggggggggggggggggwwwwwwwwwwwwwgwwwwww",
"wwgggggmmmmssmmmggggwwwwwwwwwwwwwgwwwwww",
"wwwggggmsssssssmgggwwwwwwwwwwwggggggwwww",
"wwwggggmsssssssmggggwwwwwwwwwggggggggsww",
"wwwggggmsssssssmgggwwwwwwwwwwggggggggssw",
"wwwwgggmmmmmmmmmgggwwwwwwwwwwwggggggsssw",
"wwwwwggggggggggggggwwwwwwwwwwwwwgggssssw",
"wwwwwwwggggggggggwwwwwwwwwwwwwwwwsssssww",
"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwssssww",
"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww",
""};

Die Variable initkarte ist ein Feld, das als Elemente Zeiger auf Zeichen
(char *) enthlt (Kapitel 10.8). Schau dir die Initialisierung an. Zwischen den
geschweiften Klammern steht eine lange Liste von Stringkonstanten. Das erste
Element htten wir auch mit
initkarte[0] = "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww";

initialisieren knnen. initkarte[0] ist vom Typ char *, also knnen wir genau
wie bei char *s = "Hallo"; mit einer Stringkonstante initialisieren. Das erste
Zeichen von s ist s[0], das erste Zeichen von initkarte[0] ist
initkarte[0][0] = w;

11.5

Gekachelte Landkarte

337

Sandini Bib

Dieses Format fr die Karte habe ich gewhlt, weil ich dann auf primitive, aber
einfache Weise Karten entwerfen kann. Ich habe mir eine Zeile mit "www..."
wie Wasser erstellt und diese mehrmals kopiert. Dann habe ich den Editor mit der
Einf-Taste auf berschreiben umgestellt und mit der Tastatur drauf los gemalt.
Eine etwas bessere Lsung wre, diese Zeichen in einer Textdatei abzuspeichern
und die Datei dann bei Bedarf vom Programm einlesen zu lassen, siehe Kapitel
10.9. Die beste Idee ist natrlich, sich einen kleinen Karteneditor fr Windows
zu programmieren, in dem du mit der Maus Bitmapkacheln anklicken und auf
der Karte platzieren kannst. Auch dann musst du dir ein Format ausdenken, in
dem du die Karte abspeichern und wieder einlesen kannst.
In der Funktion ladekarte bersetzen wir die Buchstaben in initkarte in Indizes fr die Kachelliste:
Bitmap3.c WinMain.cpp

/* lade Karte */
void ladekarte(void)
{
int i, j, k;
char c;
for (ki = 0; ki < KI; ki++)
if (initkarte[0][ki] == 0) break;
for (kj = 0; kj < KJ; kj++)
if (initkarte[kj][0] == 0) break;
for (i = 0; i < ki; i++) {
for (j = 0; j < kj; j++) {
c = initkarte[j][i];
for (k = 0; k < nkacheln; k++) {
if (c == kachel[k].c)
karte[i][j] = k;
}
}
}
}

In den ersten beiden Schleifen bestimmen wir die Anzahl der Zeichen in
initkarte in jede Richtung. initkarte muss ein Rechteck sein. Die Kantenlngen nennen wir ki und kj. In der Doppelschleife lesen wir mit
c = initkarte[j][i]

das Zeichen bei Kachelkoordinaten j und i. Wie schon besprochen ist die Reihenfolge der Indizes bei zweidimensionalen Feldern anders herum als die Konvention bei Koordinaten. Dann folgt eine Schleife, mit der wir die Kachelliste von
vorne durchsuchen, bis wir das Zeichen c gefunden haben. Den Index k speichern
wir in karte[i][j].
338

Kapitel 11 Bitmaps

Sandini Bib

Das Endergebnis dieser Datengymnastik ist eine schn einfache Ausgabeschleife


in malen:
Bitmap3.c WinMain.cpp

/* male Bitmap */
void malebitmap(HDC hdc, int x, int y, HBITMAP hbitmap)
{
SelectObject(hdcmem, hbitmap);
BitBlt(hdc, x, y, bmw, bmh, hdcmem, 0, 0, SRCCOPY);
}
/* Malen */
void malen(HDC hdc)
{
int i, j, k, x, y;
if (nkacheln == 0) {
sprintf(text, "Fehler beim Laden der Kacheln");
TextOut(hdc, 10, 10, text, strlen(text));
return;
}
for (i = 0; i < ki; i++) {
for (j = 0; j < kj; j++) {
k = karte[i][j];
x = bmw * i;
y = bmh * j;
malebitmap(hdc, x, y, kachel[k].hbm);
}
}
}

Die Doppelschleife in i und j luft ber alle Felder in karte. Mit k =


karte[i][j] erhalten wir den Index fr die Kachelliste, und kachel[k].hbm
liefert die dazugehrige Bitmap. Im Fenster erscheint die gekachelte Landkarte,
die das Bild zu Beginn dieses Unterkapitels zeigt. Du kannst das Fenster maximieren, aber die Karte ist bei dir wahrscheinlich auch nicht ganz sichtbar.
Wir malen blindlings alle Bitmaps, auch die, die auerhalb des Fensters liegen.
Clipping machts mglich.
Auf dieses Beispiel trifft sicher zu, dass, wenn erst mal geeignete Datenstrukturen gefunden worden sind, der Rest ziemlich einfach ist.

11.5

Gekachelte Landkarte

339

Sandini Bib

11.6

Mit Pfeiltasten ber Land

In diesem Kapitel wollen wir unsere Landkarte wenigstens mit einem Menschen
bevlkern. Unser Held beziehungsweise unsere Heldin soll mit den Pfeiltasten
gesteuert werden knnen. Dabei soll sich der Ausschnitt der Landkarte, den das
Fenster zeigt, ndern. In diesem Kapitel besprechen wir die Pfeiltastensteuerung,
im nchsten kommt eine animierte Bitmap fr den Held dazu.
Mit Scrolling bezeichnet man am Computer das Abrollen oder Verschieben des
scroll heit Schriftrolle). Wenn die ganze Grafik in einer
Fensterinhalts (
groen Bitmap steht, gengt ein einziges BitBlt mit den richtigen Koordinaten.
Fr unsere gekachelte Landkarte wollen wir die Ausgabeschleife so abndern,
dass eine Verschiebung der Koordinaten x und y um einen Betrag x0 und y0
mglich ist. Zudem wollen wir nur solche Kartenfelder malen, die im Fenster
ganz oder zumindest teilweise sichtbar sind. Das ist nicht weiter schwierig.
Etwas verwickelter ist es, den Held mit den Pfeiltasten zu bewegen, weil es zwei
Flle zu behandeln gibt. Angenommen, die ganze Landkarte wrde ins Fenster
passen. Dann bedeutet jeder Pfeiltastendruck, dass sich die Koordinaten des Heldes im Fenster ndern. Der Held bewegt sich im Fenster. Wenn aber alle vier
Rnder der Landkarte auerhalb des Fensters liegen, soll sich die Landkarte bewegen, whrend der Held im Fenster stillsteht. Das sieht trotzdem so aus, als ob
er sich bewegt, denn die Landkarte rollt unter ihm weg. Erst wenn der Rand der
Landkarte erreicht ist, soll die Bewegung der Landkarte aufhren (ohne das ber
den Rand hinausgescrollt wird) und der Held darf im Fenster die Ecken auslaufen.
Das hrt sich viel komplizierter an, als es ist, einfach mit dem Beispielprogramm
ausprobieren.
Wir erweitern das Programm fr die gekachelte Landkarte in zwei Stufen. Als
Erstes programmieren wir die Pfeiltastensteuerung und malen den Held einfach
als weien Kreis. Im zweiten Schritt wollen wir den Kreis durch eine animierte
Bitmap mit durchsichtigem Hintergrund ersetzen.
Fr die Bewegung des Heldes verwenden wir die Funktion bewegeheld:

340

Kapitel 11 Bitmaps

Sandini Bib
Bitmap4.c WinMain.cpp

/* bewege Held */
void bewegeheld(int wParam)
{
int i, j, k, x, y;
xheldalt = xheld;
yheldalt = yheld;
if
if
if
if

(wParam
(wParam
(wParam
(wParam

==
==
==
==

VK_LEFT)
VK_RIGHT)
VK_UP)
VK_DOWN)

xheld
xheld
yheld
yheld

=
+=
=
+=

dschritt;
dschritt;
dschritt;
dschritt;

//
if
if
if
if

Held darf die Karte nicht verlassen


(xheld < 0) xheld = 0;
(yheld < 0) yheld = 0;
(xheld > kxmax) xheld = kxmax;
(yheld > kymax) yheld = kymax;

// Weg blockiert?
for (x = xheld; x < xheld + bmw; x += bmw1) {
for (y = yheld; y < yheld + bmh; y += bmh1) {
i = x/bmw;
j = y/bmw;
k = karte[i][j];
if (kachel[k].block) {
xheld = xheldalt;
yheld = yheldalt;
return;
}
}
}
}

Diese Funktion rufen wir bei der Nachricht WM_KEYDOWN auf. Mit xheld und
yheld bezeichnen wir die Koordinaten des Heldes auf der Landkarte (und nicht
etwa im Fenster). Der Held bewegt sich auf der Landkarte, und erst in der Funktion malen werden wir entscheiden, welcher Ausschnitt der Landkarte im Fenster
gezeigt und wo der Held im Fenster gemalt wird.
Als Erstes speichern wir die momentanen Koordinaten des Helden in xheldalt
und yheldalt. Dann ndern wir die Koordinaten des Heldes um den Betrag
dschritt in die Richtung, die von den Pfeiltasten angegeben wird. Falls eine
Taste, aber keine Pfeiltaste gedrckt wurde, ndert sich die Position des Helden
nicht. Die Schrittgre dschritt kann sehr wohl kleiner als die Breite bmw oder
die Hhe bmh der Kacheln sein. In unserem Beispiel sind die Kacheln quadratisch
und wir whlen dschritt so, dass nach einer ganzzahligen Anzahl von Schritten
genau eine Kachel berquert wurde.
11.6

Mit Pfeiltasten ber Land

341

Sandini Bib

Der Held darf die Karte nicht verlassen. Also berprfen wir die unteren und die
oberen Schranken (kxmax und kymax) der Kartenkoordinaten und schubsen den
Held bei Bedarf zurck.
Eine andere Art von Blockade soll durch die Art des Untergrunds bestimmt
werden. Auf Wasser darf der Held nicht laufen und auch Mauern sollen
undurchdringlich sein. Diese Eigenschaft finden wir fr die k-te Kachel in
kachel[k].block. Weil wir Schrittweiten erlauben, die kleiner als einzelne Kacheln sind, befindet sich der Held meistens auf mehreren Kacheln gleichzeitig.
Das heit, nach einem Schritt kann der Held mit einem Fu im Gras, mit dem
anderen im Wasser stehen. Deshalb testen wir mit einer kleinen Doppelschleife,
ob eine der vier Ecken der Heldenbitmap blockiert ist. Das erste Pixel in xRichtung hat Koordinate xheld. Das letzte Pixel hat Koordinate xheld+bmw1,
denn insgesamt gibt es bmw Pixel in x-Richtung. Wie bei Feldern wird von 0 bis
bmw1 gezhlt.
Durch eine ganzzahlige Division erhalten wir aus x den Index fr die Karte mit
i = x/bmw. Fr jede der vier Ecken des Helden berechnen wir die Kachel, in der
sich diese Ecke nach dem Schritt befinden wrde. Falls eine Blockade vorliegt,
erlauben wir den Schritt nicht, stellen die Koordinaten vor dem Schritt wieder
her und verlassen die Funktion bewegeheld. Wir fhren also den gewnschten
Schritt probehalber aus, und wenn er nicht erlaubt ist, machen wir ihn rckgngig, bevor die Bewegung im Fenster angezeigt wurde. Als ob der Held erst einen
Schritt weit mit dem Kopf durch die Wand rennt, bevor er einsieht, dass hier
kein Durchkommen ist.
Was wo im Fenster angezeigt wird, bestimmen wir in

342

Kapitel 11 Bitmaps

Sandini Bib
Bitmap4.c WinMain.cpp

/* Malen */
void malen(HDC hdc)
{
int i, j, k;
int imin, jmin, imax, jmax;
int x, y, x0, y0, w0, h0;
if (fehler) {
sprintf(text, "Fehler beim Laden der Bitmaps.");
TextOut(hdc, 10, 10, text, strlen(text));
return;
}
// Breite und Hoehe des Fensters in ganzzahligen Vielfachen der Kacheln
w0 = (xmax/bmw)*bmw;
h0 = (ymax/bmh)*bmh;
//
//
x0
y0

linke obere Ecke des Fensters auf der Karte


setze Held in die Mitte
= xheld w0/2;
= yheld h0/2;

//
if
if
if
if

am Rand bewegt sich der Held weg von der Mitte


(x0 > kxmax w0) x0 = kxmax w0;
(y0 > kymax h0) y0 = kymax h0;
(x0 < 0) x0 = 0;
(y0 < 0) y0 = 0;

// male alle Kartenfelder, die im Fenster ganz oder teilweise sichtbar sind
imin = x0/bmw;
jmin = y0/bmh;
imax = (x0 + w0)/bmw + 1;
jmax = (y0 + h0)/bmh + 1;
if (imax >= ki) imax = ki1;
if (jmax >= kj) jmax = kj1;
for (i = imin; i <= imax; i++) {
for (j = jmin; j <= jmax; j++) {
x = bmw * i x0;
y = bmh * j y0;
k = karte[i][j];
malebitmap(hdc, x, y, kachel[k].hbm);
}
}
// male Held
Ellipse(hdc, xheldx0, yheldy0, xheldx0+bmw, yheldy0+bmh);
}

In w0 und h0 speichern wir die Breite und Hhe des Fensterbereichs, der durch
ein ganzzahliges Vielfaches der Kacheln abgedeckt werden kann. Denke einmal
darber nach, wir knnten statt (xmax/bmw)*bmw auch xmaxxmax%bmw schreiben.

11.6

Mit Pfeiltasten ber Land

343

Sandini Bib

Die linke obere Ecke des Fensters soll auf der Karte Koordinaten x0 und y0 haben,
so dass der Held in der Mitte des Kartenausschnitts mit Breite w0 und h0 steht.
Also setzen wir z. B. x0 = xheld w0/2.
Jetzt kommt der Schritt, bei dem wir zwischen den zwei Mglichkeiten der Anzeige unterscheiden. Falls der Kartenausschnitt mit dem Held in der Mitte bedeutet, dass das Fensters ber den Rand der Karte hinauszeigt, setzen wir den
Rand der Karte gleich dem Fensterrand, z. B. mit if (x0 < 0) x0 = 0;. Dann
steht der Held auch nicht mehr in der Mitte des Fensters, aber das ist genau das,
was wir wollen.
Es folgt die Ausgabeschleife mit der berechneten Verschiebung des Ausschnitts
um x0 und y0. Zudem schleifen wir nicht wie zuvor ber die gesamte Karte von
0 bis ki und von 0 bis kj, sondern berechnen Indizes imin, imax, jmin und
jmax, die den sichtbaren Bereich abdecken.
Zu guter Letzt malen wir einen Kreis, der den Helden darstellen soll. Lass diese
Version des Programms laufen. Du kannst den Kreis auf der Karte mit den Pfeiltasten bewegen. Der Kreis befindet sich entweder in der Mitte des Fensters, wenn
der Rand der Karte auerhalb des Fensters liegt, oder die Bewegung der Karte
stoppt und der Kreis kann in die Ecken bewegt werden:

11.7

Bitmaps mit transparenter


Hintergrundfarbe

Zum Abschluss wollen wir den Kreis durch eine animierte Bitmap fr den Helden oder die Heldin ersetzen. Fr die Animation verwenden wir einen Zeitgeber,
der alle 300 Millisekunden oder so auf die nchste Bitmap fr den Helden umschaltet. Ich habe zwei Bitmaps gezeichnet, held_0.bmp und held_1.bmp, die als
344

Kapitel 11 Bitmaps

Sandini Bib

Minifilmchen so aussehen, als ob der Held luft. Wenn der Held mit den Pfeiltasten bewegt wird, sieht es so aus, als ob er tatschlich luft. Wenn der Held sich
nicht fortbewegt, sieht es aus, als ob er auf der Stelle tritt. Klarer Fall, hier fehlen
unterschiedliche Filmchen fr Bewegung nach links, rechts, oben und unten und
fr das gelangweilt Rumstehen.
Das eigentliche Problem ist, dass wir nicht darum herumkommen, fr unseren
Held einen durchsichtigen Hintergrund zu implementieren. Dummerweise liefert das Windows SDK eine solche Option fr BitBlt nicht mit. In DirectX und
OpenGL sind Bitmaps mit transparenter Hintergrundfarbe Standard. Zur Not
knnten wir das wie folgt programmieren. Statt mit BitBlt kopieren wir in
einer Doppelschleife mit GetPixel und SetPixel die Pixel einzeln, sozusagen
per Hand, weil das im Vergleich mit BitBlt sehr langsam geht (denke an die
Pixelmalerei in Kapitel 9.11). Mit GetPixel lesen wir ein Pixel aus der Quellenbitmap, die kopiert werden soll. Dann vergleichen wir die Farbe des Pixels
mit dem Farbwert, der die durchsichtige Farbe darstellen soll. Das kann Schwarz,
Wei, Grau oder auch Hellblau sein. Wenn der Farbwert nicht die durchsichtige Farbe ist, schreiben wir das Pixel mit SetPixel in die Zielbitmap, ansonsten
schreiben wir kein Pixel. Auf diese Weise lassen wir in der Zielbitmap alle die
Pixel unverndert, die in der Quellenbitmap durchsichtig sein sollen.
BitBlt kann zwar keine Bitmaps mit Transparenz kopieren, aber immerhin gibt
es noch andere Operationen als SRCCOPY, siehe die Windows SDK-Hilfe. Wir verwenden verschiedene solche Operationen in einer selbst gemachten Funktion
void malebitmap_transparent(HDC ziel, int x, int y, int w, int h,
HBITMAP bitmap);

um Bit Blits mit Transparenz zu simulieren. Als transparente Farbe wird automatisch das Pixel in der linken oberen Ecke der Quellenbitmap gewhlt. Diese Funktion steht in ihrer eigenen Datei, BitBltTransparent.c, die wir dem Projekt in
BCB wie in Kapitel 0.6 besprochen hinzufgen. Um malebitmap_transparent
zu verwenden, schreiben wir den Prototyp an den Anfang der Datei, in der wir
die Landkarte und so weiter programmiert haben:
Bitmap5.c BitBltTransparent.c WinMain.cpp

#include <windows.h>
#include <stdio.h>
/* Prototyp fuer Funktion in Datei BitBltTransparent.c */
void malebitmap_transparent(HDC ziel, int x, int y, int w, int h,
HBITMAP bitmap);
/* externe Variable */
HDC hdc, hdcmem;
char text[1000];
int xmax, ymax;
...

Bitmaps mit transparenter Hintergrundfarbe

345

Sandini Bib

Das ist ein Beispiel dafr, wie du Funktionen auf verschiedene Dateien verteilen
kannst, wenn deine Programme zu lang und unbersichtlich werden. So lassen
sich Funktionen auch leicht in verschiedenen Programmen wiederverwenden.
Was geschieht in malebitmap_transparent? Statt der direkten Kopie mit
SRCCOPY stehen verschiedene Bitoperationen fr BitBlt zur Verfgung, insbesondere SRCPAINT. Bei SRCPAINT werden die Pixel mit dem Oder-Operator
verknpft. Normalerweise ist das Ergebnis eine seltsame Mischung der beiden
Bitmaps. Ausprobieren! Nur wenn eines der beiden Pixel schwarz ist, ndert sich
die Farbe nicht, denn bei der Oder-Operation werden dann keine zustzlichen
Bits gesetzt.
Die Idee ist, in einer Zweifarbenbitmap eine Maske fr die Bitmap herzustellen,
die kopiert werden soll. In dieser Maske steht dort eine 0, wo ein Pixel der Bitmap nicht kopiet werden soll, und eine 1, wenn das Pixel kopiert werden soll.
Am einfachsten kann ich dir die Verwendung von Masken anhand unseres Programmbeispiels erklren. Hier sind zwei typische Beispiele:

Die Ausgabe am linken oberen Rand erhltst du, wenn du die Variable testen in
malebitmap_transparent auf wahr setzt. Du siehst sieben Bitmaps. Die erste
ist die Zielbitmap, also z. B. einfach nur Rasen oder Rasen und Sand. Die zweite
ist die Quellenbitmap, unser Held mit dem grauen Rand. Die dritte Bitmap ist
eine Maske der Quellenbitmap. Diese erhalten wir, wenn wir die Quellenbitmap
auf die richtige Weise in eine Schwarzweibitmap kopieren. Bei dieser Kopie
knnen wir Grau als Hintergrundfarbe bestimmen, die schwarz wird, whrend
alle anderen Farben (inklusive dem Schwarz im Schatten um die Fe) wei
wird!
Die vierte Bitmap zeigt die Quellenbitmap, nachdem wir mit Hilfe der Maske
alle grauen Pixel auf Schwarz gesetzt haben. Die fnfte Bitmap ist die negierte
Version der Maske. Mit dieser umgekehrten Maske stanzen wir sozusagen ein
schwarzes Loch in die Zielbitmap, wo die undurchsichtigen Teile der Quellenbitmap hinsollen. Die sechste Bitmap ist die Zielbitmap mit ausgestanztem Loch.
346

Kapitel 11 Bitmaps

Sandini Bib

Die siebte Bitmap zeigt, dass wir mit der Oder-Operation Bitmap 4 und Bitmap
6 zum Endergebnis kombinieren knnen. Geschafft!
Wenn du dir die Funktion malebitmap_transparent anschaust, wirst du acht
Aufrufe von BitBlt finden. Das ist natrlich nicht besonders effizient, sollte aber
nicht weiter ins Gewicht fallen, wenn du sowieso schon 500 oder 1000 Bitmaps
fr die Landkarte blittest. Du knntest alle Masken gleich nach dem Laden der
Bitmaps berechnen und zusammen mit den Bitmaps in einer Struktur abspeichern. Dann bentigt die Ausgabe der Bitmaps nur 2 oder 3 Bit Blits.

11.8

Ein Held auf weiter Flur

Nachdem du eine Weile mit unserem Held auf der gekachelten Landkarte herumgelaufen bist, solltest du den Blockadetest abschalten (bewegeheld vorher mit
return verlassen). Jetzt schwebt der Held wie ein Geist berall hin. Male deine
eigene Karte mit neuen Kacheln. Oh, die ist viel schner geworden als meine.
Aber natrlich ist unser Held viel zu einsam in dieser leeren Welt. Was uns zu
einem Minibitmapabenteuer noch fehlt, ist eine Liste von Objekten, Personen
oder Monstern, die wie der Held aus animierten Bitmaps bestehen. Wir knnten
eine Struktur definieren, die alle ntigen Informationen zusammenfasst, insbesondere die Bitmaps und die momentanen Koordinaten des Objekts auf der
Landkarte. Bei der Ausgabe wird erst die gekachelte Landkarte gemalt und dann
folgt eine Schleife zur Ausgabe der Objekte. Fr Tiere oder Monster knnten
wir ein Bewegungsmuster programmieren. Auerdem wre es schn, wenn unser Held oder unsere Heldin sich mit der Welt unterhalten knnte. So knnte
das aussehen:

11.8

Ein Held auf weiter Flur

347

Sandini Bib

Das ist ein Vorschlag, wie du das Bitmapabenteuer verbessern knntest.


Mit Bedauern stelle ich fest, dass dies das letzte Beispiel im letzten Kapitel dieses Buches ist. Dieses letzte Beispiel ist ein typisches Beispiel aus dem richtigen
Leben. Du hast eine Idee, aber niemand sagt dir, wie genau sie zu verwirklichen
ist. Egal, ob du diese Idee oder irgendeine andere Idee verwirklichst ich wrde
mich freuen, wenn du gengend ber das Programmieren gelernt hast, um lustige kleine Programme zu schreiben. Viel Spa!

11.9
Bitmaps beflgeln die Fantasie. Ich sehe die kleine Figur aus dem letzten Beispiel
schon durch Labyrinthe kriechen auf der Suche nach dem Schatz und auf der
Flucht vor Monstern. Wo immer ein Bild mehr sagt als tausend Worte, sind in
Computerprogrammen Bitmaps angesagt. Das haben wir besprochen:
Bitmaps sind rechteckige Bilder, die sich aus einzelnen Pixeln zusammensetzen. Windows-Bitmaps werden in .bmp-Dateien gespeichert.
Bitmaps knnen mit LoadImage aus einer Datei in den Arbeitsspeicher geladen werden. Das Ergebnis ist ein Handle zu einer Bitmap vom Typ HBITMAP.
Unabhngig von Bitmapdateien kannst du Bitmaps mit CreateCompatible
Bitmap im Arbeitsspeicher erzeugen.

Die Funktion BitBlt wird verwendet, um eine Bitmap in eine andere hineinzukopieren. Insbesondere ist der Fensterinhalt eine Bitmap und du kannst
einen (rechteckigen) Teil der Fensterbitmap mit einer Bitmap aus dem Arbeitsspeicher berschreiben.
Bitmaps kannst du animieren, indem du bei jedem Tick eines Zeitgebers die
nchste in einer Reihe von Bitmaps ausgibst.
348

Kapitel 11 Bitmaps

Sandini Bib

Sprache, Musik und Soundeffekte knnen mit PlaySound aus Wave-Dateien


geladen und im Hintergrund abgespielt werden, whrend das Programm andere Aufgaben erledigt.

11.10
1. Programmiere eine Tonmaschine mit PlaySound, die den Satz C ist cool

zerhackt. Die Ausgabe sollte sowas wie C C C C ist C ist cool C C ist C C ist
cool sein. Vielleicht durch Zufallszahlen gesteuert. Du knntest verschiedene
Sounds per Mausklick abspielen. Der Musiker klickt auf verschiedene Stellen
im Fenster, um einen bestimmten Sound zu starten.
2. Verwende einen Bitmappuffer in dem Bitmapabenteuer, um das Flackern zu

verringern. Vielleicht strt dich das Flackern auch in anderen Beispielen?


3. Programmiere ein Spiel. Eine gute Idee ist es, ein mglichst einfaches, aber

trotzdem lustiges Spiel nachzuprogrammieren. Dazu solltest du dir eine genaue und vollstndige Liste von allen bentigten Spielelementen machen.
Ohne Witz. Versuche aufzuschreiben, was genau die Mechanik des Spiels und
die Ausgabe ausmacht.
Falls dir Computerspiele Spa machen, hast du sicher einige auf deinem Computer. Schau sie dir genau an, auch die alten, die du gar nicht mehr spielst.
Wie wurde wohl die Grafik gemacht? 2D oder 3D, oder vielleicht nur 2DBitmaps, die aber im 3D-Stil gezeichnet wurden? Wie viele verschiedene Bitmaps kannst du erkennen? Setzt sich der Hintergrund vielleicht aus einzelnen
Bitmapkacheln zusammen? Wie viele einzelne Bitmaps werden fr die Animation von Figuren verwendet? Wie viele Animationen laufen gleichzeitig
ab? Du kannst viel lernen, wenn du Spiele aus der Sicht eines Programmierers analysierst. Manche Spiele haben komplizierte Spielideen und womglich gute knstliche Intelligenz, aber wie sieht es mit der Grafik aus? Manche
Spiele haben eine sehr schne Grafik, aber die Spielmechanik ist sehr simpel
gestrickt.

11.10

349

Sandini Bib

Sandini Bib

A
Anhang
A.0
A.1
A.2
A.3

Rangordnung von Operatoren


Der Preprocessor
Die Schlsselwrter von C
Buch gelesen, was nun?

352
352
355
357

Sandini Bib

A.0

Rangordnung von Operatoren

Wenn mehrere Operatoren in einem Ausdruck verwendet werden, muss geklrt


sein, in welcher Reihenfolge die Operationen ablaufen. Typisches Beispiel ist die
Regel Punkt vor Strich fr die Grundrechenarten. Multiplikation * und Division / binden strker als Addition + und Subtraktion . Oft spielt es auch eine
Rolle, in welcher Reihenfolge man eine Verkettung von gleichrangigen Operatoren auswertet. In folgender Tabelle nimmt der Rang der Operatoren von oben
nach unten ab und die Auswertungsrichtung ist mit Pfeilen angegeben. Die Bedeutung der Operatoren wird nach und nach im Text erklrt:
() [] > .
!

++ + * & (typ) sizeof

* / %

>> <<

< <= > >=

== !=

&

&&

||

?:

= += = *= /= %= &= = |= <<= >>=

Manche Zeichen tauchen in dieser Tabelle mehrmals auf. Die Zeichen + und
binden als Vorzeichen von Zahlen strker, als wenn sie die Grundrechenarten
Plus und Minus bezeichnen. Der Dereferenzoperator * fr Zeiger bindet strker
als der Multiplikationsoperator *. Der Adressenoperator & bindet strker als der
Bitoperator &.
Mit der Hilfe von Klammern ( ) kann man die Auswertungsreihenfolge beliebig
festlegen.

A.1 Der Preprocessor


Eine wichtige Rolle bei der Programmorganisation spielt der Preprocessor. Preprocessoranweisungen beginnen mit # und werden vor der Kompilierung ausgefhrt. Mit #include kann man eine Datei in eine andere einfgen. Mit #define
ist es mglich, Konstanten einen neuen Namen zu geben oder Makros zu definieren. Mit #if kann man die Kompilierung von Programmteilen steuern. Der
352

Anhang

Sandini Bib

Preprocessor fhrt diese Anweisungen vor der Kompilierung des Programmtextes aus. Preprocessoranweisungen zielen darauf ab, den Text in einer C-Datei zu
verndern, bevor der Compiler diesen Text als C-Programm interpretiert und in
Maschinensprache bersetzt.
Im Folgenden bespreche ich kurz einige Details zu den Preprocessoranweisungen.
Mit
#include Datei

kann man beliebigen Text einlesen. Das wird haupschlich fr Headerdateien von Bibliotheken oder selbst gemachte Headerdateien verwendet. Mit
#include <...> und #include "..." werden diese Flle unterschieden. Mit
#define Name

Text zum Einsetzen

erzeugt man einen Namen fr ziemlich beliebige Zeichenfolgen. Das bezeichnet


man auch als makro. Der Preprocessor durchsucht den gesamten Programmtext und ersetzt Name durch den Text. Es ist blich, aber nicht notwendig, die
Definitionen des Preprocessors durch Namen aus lauter Grobuchstaben hervorzuheben. Ein typisches Beispiel ist
#define NMAX 1000
int a[NMAX];

void initfeld()
{
int i;
for (i = 0; i < NMAX; i++)
a[i] = 0;
}

In diesem Teilstck eines Progamms wird vor der Kompilierung NMAX durch die
Zeichen 1000 ersetzt. NMAX ist der einfachste Spezialfall eines Makros, eine so
genannte symbolische Konstante. Wenn sich die Gre des Feldes a ndert, die
ja eine Konstante sein muss, braucht man nur das #define zu ndern. Es ist auf
jeden Fall besser, solchen oft beliebigen Konstanten einen Namen zu geben.
Der Preprocessor beachtet beim Einsetzen von NMAX die C-Syntax, das heit,
NMAX muss z. B. als Name einer Variablen auftreten. Falls im Programm
NMAXSTRING oder printf("NMAX = ") auftaucht, findet kein Einsetzen statt.
Aber #define kann mehr, als neue Namen fr Konstanten zu definieren. Man
kann Programmtext wie in
#define CHARZEIGER char *

A.1 Der Preprocessor

353

Sandini Bib

zusammenfassen (siehe Kapitel 10). Das erste Wort ist der Name des Makros,
der Rest inklusive aller Leerstellen ist der Text, der eingesetzt werden soll. Dieses Makro kann in CHARZEIGER string; wie ein neuer Datentyp verwendet
werden, wenn man das * nicht schreiben mchte.
Ein Makro kann Argumente verwenden. In
#define MAX(A,B) (((A) > (B)) ? (A) : (B))

wird ein Makro definiert, das das Maximum zweier Zahlen berechnet. Zur Erinnerung, der ?: Operator funktioniert wie ein if-else fr Ausdrcke. A und
B sind die Argumente. Weil A und B ganze Ausdrcke enthalten knnen, muss
man durch Klammern sicherstellen, dass A und B ausgewertet werden, bevor die
Anweisung im Makro ausgefhrt wird. Nochmals zum Verstndnis: Alles, was
der Preprocessor tut, ist Ausdrcke wie MAX(x,y+1) zeichenweise durch (((x)
> (y+1)) ? (x) : (y)) zu ersetzen. Ein Vorteil ist, dass ein solches Makro zu
schnellerem Code fhrt, weil ein Funktionsaufruf immer mit Overhead (zustzlicher Arbeit) verbunden ist. Zudem msste eine Funktion sich auf einen Datentyp festlegen, whrend das Makro MAX fr alle Zahlen gilt. Auf ein Problem
muss man achten: Funktionsargumente werden nur einmal ausgewertet. Wenn
man aber x oder so in MAX einsetzt, wird dieser Ausdruck unter Umstnden
zweimal ausgewertet!
Jedes #define kann mit einem #undef fr den Rest der Datei aufgehoben werden.
Dann gibt es noch Preprocessoranweisungen wie
#if TEST > 0

Textzeilen
#endif

Wenn die Konstante TEST grer als 0 ist, wird der Text zwischen #if und
#endif verwendet, sonst wird er vllig ignoriert. Nach #if 0 kann Bldsinn
stehen, weil der Compiler den nachfolgenden Text nie zu sehen bekommt. Mit
#ifdef und #ifndef testet man, ob ein Name definiert ist bzw. ob er nicht
definiert ist. #else wird wie else verwendet und #elif spielt die Rolle von
else if. Mit diesen Anweisungen lsst sich durch das Definieren von Konstanten steuern, welche Programmteile kompiliert werden.
Preprocessoranweisungen, die mit #pragma beginnen, drfen von verschiedenen
Compilern bzw. Entwicklungsumgebungen zu unterschiedlichen Dingen verwendet werden. BCB z. B. verwendet #pragma hdrstop, um die Kompilierung
von Headern zu steuern.
Im Gegensatz zum Compiler nimmt es der Preprocessor genauer, wenn es um
Leerzeichen und neue Zeilen geht. Anweisungen mssen am Zeilenanfang stehen (Leerzeichen vor dem # werden normalerweise ignoriert). In der Definition
354

Anhang

Sandini Bib

eines Makros wie MAX(A,B) darf zwischen MAX und (A,B) kein Leerzeichen stehen, weil ab dem ersten Leerzeichen der Text frs Einsetzen beginnt. Falls eine
Preprocessoranweisung lnger als eine Zeile sein soll, muss man die Zeilen mit
\ beenden, und nach dem \ darf kein Leerzeichen stehen.
Der Prepocessor ist fr manche Zwecke unentbehrlich. Man sollte aber stets ein
Auge darauf haben, dass die Verstndlichkeit der Programme durch bermiges
Herumdefinieren nicht leidet.

A.2 Die Schlsselwrter von C


Die reservierten Schlsselwrter (
auto
break
case
char
const
continue
default

do
double
else
enum
extern
float
for

goto
if
int
long
register
return
short

keywords) von C sind:


signed
sizeof
static
struct
switch
typedef
union

unsigned
void
volatile
while

Diese 32 Wrter bilden das Vokabular von C und knnen nicht als Name fr
Variablen oder Funktionen verwendet werden.
Im Text werden die meisten Sprachelemente von C ausfhrlich diskutiert oder
zumindest erwhnt, doch einige der eher seltenen oder obskuren Konstruktionen
habe ich an dieser Stelle gesammelt:
Mit den Schlsselwrtern auto und register definiert man automatische
(im Gegensatz zu statischen) Variablen innerhalb von Funktionen. Eine Definition wie int i; erzeugt sowieso eine automatische Variable. Mit register
kann man dem Compiler vorschlagen, die Variable in einem der Register des
Mikroprozessors zu speichern, was dieser aber ignorieren darf. Das Schlsselvolatile (flchtig) ist fr Optimierungszwecke das Gegenteil
wort
von const.
Das Schlsselwort enum (
enumerate, aufzhlen) wird zur Definition
einer Reihe von ganzzahligen Konstanten verwendet. In
enum {EINS = 1, ZWEI, DREI};

werden drei Konstanten EINS, ZWEI, DREI definiert, die die Werte 1, 2
und 3 haben. Das ist manchmal praktischer als eine Reihe von #defineAnweisungen, bei denen jede Konstante einzeln angegeben werden muss.
Das Schlsselwort
union (Vereinigung) wird so hnlich wie struct
verwendet. Die Elemente einer Union belegen alle ein und denselben Speicherplatz, berschreiben sich also gegenseitig.

A.2

Die Schlsselwrter von C

355

Sandini Bib

Bit-fields werden im Zusammenhang mit struct verwendet, um einzelnen


Bits Namen zu geben. Nach
struct {
unsigned int meinbit : 1;
} schalter;

ist die Variable schalter ein 1-Bit-Feld. Ziemlich ungebruchlich, ich erwhne es nur wegen der Verwendung von : innerhalb von struct.
Die Anweisung
goto (gehe nach) erlaubt es, die Programmausfhrung an beliebiger Stelle innerhalb einer Funktion fortzusetzen. Man schreibt
z. B.
goto hoppla;

und das Programm wird in der Zeile


hoppla:

mit der Markierung (


label) hoppla fortgesetzt. Den Namen der Markierung kann man frei whlen. Nach solchen Markierungen schreibt man
einen Doppelpunkt : und nicht etwa einen Strichpunkt.
Auf den ersten Blick sieht goto recht ntzlich aus, in der Praxis kann die
Verwendung von goto aber sehr leicht zu Spaghetti-Code fhren: Das Programm springt wild umher und man verliert vllig den berblick. goto wird
nur von wenigen C-Programmierern und dann auch nur in seltenen Fllen als
guter C-Stil akzeptiert. Viel bersichtlicher ist es, den Programmablauf mit
if und while und mit Befehlsblcken zu organisieren, wie wir es durchweg
in diesem Buch machen.
Mit der switch-Anweisung und den Schlsselwrtern case und default
lassen sich hnliche Fallunterscheidungen durchfhren wie mit if und else
switch heit schalten, hier im Sinne von verteilen.
(siehe Kapitel 5.5).
Jedes switch lsst sich auch mit if und else schreiben, aber mit switch
kann man nur Fallunterscheidungen fr ganzzahlige Konstanten durchfhren. Hier ist ein Beispiel:
#include <stdio.h>
int main()
{
int i;
printf("Welche Note hast du bekommen?
scanf("%d", &i);
switch (i) {
case 1:
case 2:
case 3:
printf("Gut.\n");

356

Anhang

");

Sandini Bib

break;
case 4:
printf("In Ordnung.\n");
break;
default:
printf("Eine Woche kein Computer.\n");
break;
}
getchar();
return 0;
}

Hier wird berprft, ob der Ausdruck in switch (Ausdruck) gleich einer


der ganzzahligen Konstanten ist, die mit dem Schlsselwort case angegeben
Case heit Fall und der Ausdruck mit case wird wie bei den
werden.
Markierungen fr das goto mit einem Doppelpunkt beendet. Nur ganzzahlige Konstanten sind fr case zugelassen. Wenn der Fall zutrifft, werden die
dazugehrigen Befehle ausgefhrt, ansonsten kommen die Befehle nach dem
ausbrechen) wird der Block
Schlsselwort default dran. Mit break (
von switch verlassen.

A.3 Buch gelesen, was nun?


In diesem kurzen Anhang am Ende des Buches benenne ich ein paar Bcher
und Webseiten zu C, C++, Windowsprogrammierung, Spielen und Grafik. Weil
die Computerwelt und das Internet Englisch spricht, mchte ich dich ermutigen,
auch englischsprachige Titel zu lesen.
C fr Fortgeschrittene
Bcher ber C gibt es viele, aber das beste ist meiner Meinung nach das Buch
der Erfinder von C:
The C Programming Language, Brian W. Kernighan, Dennis M. Ritchie
(Second Edition, Prentice Hall, 1998, 272 Seiten)

Dieses Buch gibt es auch auf Deutsch. Ich bin ein groer Fan von diesem
Klassiker. Fr Anfnger, die noch nie programmiert haben, ist es sicher nicht
zu empfehlen, aber das Buch ist kurz, klar und komplett. Dieses Buch ist
definitiv ein Beispiel dafr, dass Originalliteratur verstndlicher sein kann
als so manche gut gemeinte Nachahmung.
C++ fr Einsteiger
C++ ist eine Erweiterung von C. Das doppelte Plus steht natrlich fr den Inkrementoperator von C, der die Variable C um eins grer macht. Die ersten
Schritte in der objektorientierten Programmierung mit C++ sind gar nicht so
schwer. Falls du deine Kenntnisse in C vertiefen und gleichzeitig C++ lernen
mchtest, kann ich dir das folgende Buch empfehlen:
A.3 Buch gelesen, was nun?

357

Sandini Bib

C++-Programmierung lernen, Andre Willms (Addison-Wesley, 1998, 392


Seiten)

Von den 392 Seiten beziehen sich ungefhr 60 Seiten schwerpunktmig auf
die objektorientierte Programmierung. Dieses Buch ist fr (erwachsene) Anfnger geschrieben.
Windowsprogrammierung
In diesem Buch konnte ich aus verschiedenen Grnden nicht auf die Windowsprogrammierung mit Knpfen, Mens, Scrolleisten, Textelementen und
vielem mehr eingehen. Ein professionelles Standardwerk zur Programmierung von Windows mit C (ohne C++ und ohne komfortable Entwicklungsumgebung) ist:
Programming Windows, Charles Petzold (Fifth Edition, Microsoft Press,
1999, 1479 Seiten)

Dieses Buch ist als Nachschlagewerk erste Wahl, wenn du es in einer Bibliothek finden kannst. Sehr hilfreich ist aber auch die Webseite von Microsoft
(www.microsoft.com). Dort findest du die neueste Version der Windows SDKReferenz und viele Artikel zu verschiedenen Themen.
Neuere Entwicklungsumgebungen wie der Borland C++Builder erlauben es
dir, Fenster und ihre Elemente wie bei einem Malprogramm mit der Maus
zurechtzuklicken. Wenn du in BCB ein neues Projekt fr eine Windowsanwendung startest, bekommst du eine Form. In BCBs groer Menleiste findest du Knpfe und andere Fensterelemente, die du mit der Maus in die Form
hineinsetzen kannst. Programmiert wird in C++.
Grafik und Spiele
Wer Computerspiele spielt, wei, dass kaum ein kommerzielles Spiel ohne
DirectX oder OpenGL auskommt. Einige Grnde habe ich in Kapitel 11
erwhnt. DirectX und OpenGL sind kostenlos (www.microsoft.com/directx,
www.opengl.org). Bevor du mit DirectX oder OpenGL programmieren
kannst, musst du einige Erfahrung mit C gesammelt haben. Eine gute Idee
ist es, sich eine der sehr schnen Einfhrungen (Tutorials) anzuschauen,
die man im Internet finden kann (www.gamedev.net, www.opengl.org). Das
Microsoft DirectX SDK (zurzeit um die 140 MB) muss man nicht heruntergeladen haben, um mit dem Borland C++Builder z. B. Direct-Draw-Beispiele
fr 2D-Bitmapgrafik kompilieren zu knnen.

358

Anhang

Sandini Bib

Stichwortverzeichnis
Symbole
! Negation, Verneinung 96, 110, 284
!= ungleich 93
" Anfhrungszeichen 15, 47
# Preprocessoranweisung 19, 352
#define 130, 141, 191, 353

und const 247


#include 19, 171, 208, 353
% Divisionsrest 34, 37, 106, 180
% Formatanweisung 28
und TextOut 83
%% 35
%c Zeichen 48
%d ganze Zahl 28, 33, 35, 242
%e Kommazahl mit Exponent

242
Kommazahl 37, 242
Zeiger 281
String 50, 52
ganze Zahl ohne Vorzeichen
281
& Adressenoperator 279
und scanf 33, 286
& bitweises Und 238
&& Und 95
Apostroph 47, 49
( ) runde Klammern 15, 38, 90, 119,
124
* Inhaltsoperator 279
* Multiplikation 34
+ Addition 34
++ Inkrement 40, 125, 298
, Komma 28, 29, 46, 127, 168
Subtraktion 34
Dekrement 40
> Strukturelement 293
. Strukturelement 293
. statt Komma 36
/ Division 34
/* */ Kommentar 54
// Kommentar 54
; Strichpunkt 14, 20, 29, 31, 124
%f
%p
%s
%u

< kleiner 93
<< bitweise links verschieben 238
<= kleiner oder gleich 93
= Zuweisung 30, 31
+=, =, *=, /=, %= 39
== gleich 93
> grer 93
>= grer oder gleich 93
>> bitweise rechts verschieben 238
?: Auswahloperator 100
[ ] eckige Klammern 4547, 285
\ fr Sonderzeichen 15, 47
\0 Nullzeichen 47, 49
\\ Schrgstrich 47, 302
\n Neuezeilezeichen 15, 47
bitweises Entweder-Oder 238
_ Unterstreichung 30
{ } geschweifte Klammern 16, 91,

162, 291, 299, 336


| bitweises Oder 238, 307, 325
|| Oder 95

Entweder-Oder 96, 115


bitweise Negation 238
0x Hexadezimalzahl 237

A
Abtastrate 255
Adresse 278, 281
Rechenarten 285
Algorithmus 118
Alternative 98
Animation 228, 233, 258, 312
von Bitmaps 317
ANSI-C IX
Anweisung siehe Befehl
Argument 15, 165, 168
in Befehlszeile 301
variable Argumentenlisten 168
Array siehe Feld
ASCII Code 239, 296
Ausdruck 31
mathematisch 34, 39

Sandini Bib

wahr und falsch 93


Auswertungsreihenfolge 38
auto 355
B
Balkendiagramm 152
BCB siehe Borland C++Builder
Bedingung 89
Befehl 17, 31
berspringen 89
wiederholen 118
Befehlsblock 17, 91, 119, 162, 173
Beispiele auf CD-ROM 8, 62
Bibliothek 3, 248
Bildschirmausgabe 14
Binrsystem 237
Bisektion 111, 141, 248
Bit 236
Bit-Feld 356
Bitoperator 238
Bitmap 311
Animation 317
erzeugen 332
Gre 315
laden 315
malen mit Paint 312
Maske 346
Puffer 326
transparenter Hintergrund 344
Bitoperator 238
Block siehe Befehlsblock
Boolesche Algebra 93
Borland C++Builder IX, 2
Compiler IX, 3, 19
Debugger 3, 18, 120, 163
Editor 3, 4, 11, 23, 203
Hilfe 78
Installation IX, 3
Linker 3, 16, 170
neues Projekt 4, 310
Programm beenden 120
Programm kompilieren 7
Programm starten 7
Programmtexteingabe 4
Projektverwaltung 2, 11, 172
Textbildschirmanwendung 4
Windowsanwendung 310
break 121, 135
Buchstabe 47
klein und gro 16

360

Stichwortverzeichnis

Byte 236
C
C VIII, X, 278, 357
ANSI-C IX
Schlsselwrter 355
C++ X, 5, 278, 357
case 356
Cast-Operator 245
char 239
Character 47, 239
Clipping 66, 329
Compiler IX, 3, 19
const 247
continue 121, 135
D
Datei 2
.bmp 312
.c 5, 9
.cpp 5, 9, 63, 210
.exe 7
.h 19
.mak 4, 9
.wav 325
lesen und schreiben 301
Namen mit Schrgstrich 302
Datentyp 235, 239
const 247
struct 291
typedef 247
char, int, float, double 239
short, long 240
signed, unsigned 240
sizeof 240
Anzahl der Bytes 239, 240
Grenzwert 241
Umwandlung 243
Zeiger auf 280
Debugger 3, 18, 120, 163
default in switch-Anweisung 356
Default 101, 210, 307
Definition
Feld 45
Funktion 162
grobuchstabig 208, 353
Makro 353
Struktur 291
und Deklaration 179

Sandini Bib

Variable 29
Zeiger 280
Definitionen, grobuchstabige
BYTE 247
CALLBACK 208
CLK_TCK 141
COLORREF 72
CS_HREDRAW 307
CS_OWNDC 307
CS_VREDRAW 307
CW_USEDEFAULT 308
DWORD 247
EOF 302
FILE 301
HBITMAP 315
HBRUSH 76
HDC 64
HFONT 77
HINSTANCE 305
HIWORD 214, 274
HPEN 72
HWND 208
INT 247
LOWORD 214, 274
LRESULT 208
MSG 308
NULL 284
NULL_BRUSH 76
OPAQUE 76
PS_SOLID 72
RAND_MAX 106
RECT 292, 310
RGB 68, 264
SND_ASYNC 325
SND_FILENAME 325
SND_LOOP 325
SND_NOSTOP 325
SND_SYNC 325
SRCCOPY 316, 346
SRCPAINT 346
TA_CENTER 214
TRANSPARENT 76
UINT 208, 247
USEUNIT 11
VK_... 221
WHITE_BRUSH 307
WM_CHAR 221
WM_CLOSE 224
WM_CREATE 212
WM_DESTROY 209

WM_KEYDOWN 221
WM_LBUTTONDOWN 215
WM_MOUSEMOVE 219
WM_PAINT 208, 211
WM_QUIT 210, 211
WM_RBUTTONDOWN 215
WM_SIZE 212
WM_TIMER 227
WNDCLASS 306
WORD 247
WS_OVERLAPPEDWINDOW 308

Deklaration von Variablen 179


Dekrement 40
Device Context 64, 307
erzeugen 316
Dezimalsystem 237
DirectX 326, 358
do 124
DOS-Konsole 62
Befehlszeile 301
double 36, 239
Drehung 256
E
Editor 3, 4, 11, 23, 203
Eingabe 33, 34, 52
Eingabeschleife 131, 143
einrcken von Programmtext 91
Element 45, 291
Ellipse 73
else 98
else if 101
enum 355
Executable 7
Exponent 242
extern 179
F
Fakultt 182
Fallunterscheidung 101, 111, 208, 356
falsch 93
Farbe 68
Farbpalette 265
Farbtiefe 264, 313
Feld 44
Definition 45, 130
Elemente 45
Initialisierung 46
mehrdimensional 47, 189

Stichwortverzeichnis

361

Sandini Bib

Name als konstanter Zeiger 286


und Schleife 128
Fenster 61, 205
ffnen 306
Fensterklasse 306
Fensternachricht siehe Nachricht
Fensterprozedur 206, 307
Fensterstil 307
Fliekommazahl 36
float 36, 239
Font 76, 84
for 124
Formatanweisung siehe %d usw.
Fraktal 265
Frequenz 250
Funktion 161
Argument 15, 165
variable Argumentenlisten 168
Aufruf 15, 163
beenden 165
Definition 162
erlaubte Namen 30
Mathematik 248
mehrere Argumente 168
ohne Argumente 163
Prototyp 169
Rckgabewert 165
statisch 179
Zeiger auf 307
Funktionen, Standard C
calloc 284
clock 139
cos 248
fclose 301
fgetc 301
fgets 303
fopen 301
fprintf 303
fputc 303
fputs 303
free 284
fscanf 303
getchar 17, 34, 48
und Tastaturpuffer 34, 145
gets 53
isspace 303
main 16
mit Argumenten 300
malloc 282
memcmp 296

362

Stichwortverzeichnis

memcpy 296
memset 296
pow 248
printf 14, 15, 28, 50, 85
puts 53
qsort 310
rand 104
realloc 284
scanf 33, 52
und & 33, 286
sin 248
sizeof 240
sprintf 85
sqrt 248
srand 105
sscanf 85
strcmp 296
strcpy 296
strlen 67
strstr 296
time 106, 139

Funktionen, Windows SDK


Arc 74
BeginPaint 209
BitBlt 316
CreateCompatibleBitmap 332
CreateCompatibleDC 316
CreateFont 77, 84
CreatePen 72
CreateSolidBrush 76
CreateWindow 307
DefWindowProc 210
DeleteDC 317
DeleteObject 83
DispatchMessage 211, 308
DrawText 65, 310
Ellipse 73
EndPaint 209
GetAsyncKeyState 226
GetDC 209, 332
GetMessage 211, 308
GetObject 315
GetPixel 78
GetStockObject 76, 307
GetTickCount 141
InvalidateRect 211
KillTimer 227
LineTo 69
LoadCursor 307
LoadIcon 306

Sandini Bib

LoadImage 315
MoveToEx 69
PeekMessage 332
PlaySound 324
PostQuitMessage 209
Rectangle 73
RegisterClass 306
ReleaseDC 209
SelectObject 72

Rckgabewert 83
SendMessage 224
SetBkColor 76
SetBkMode 76
SetPixel 68
SetTextAlign 214
SetTextColor 76
SetTimer 227
SetWindowText 214
ShowWindow 308
StretchBlt 317
TextOut 64, 76
mit Format 83
TranslateMessage 211, 221, 308
UpdateWindow 212
ValidateRect 209, 214, 332
WindowProc 208, 211, 307
WinMain 63, 304
G
GB, Gigabyte 238
goto 356
Grafik 61, 62
Gltigkeitsbereich von Variablen 173,
175, 177
H
Handle 64
Headerdatei 170, 203, 353
Hexadezimalsystem 237
Hilfe 78, 204
Histogramm 152
I

Variable 32
Zeiger 286
Inkrement 40, 357
und Zeiger 298
int 29, 239
Integerzahl siehe Zahl, ganze Zahl
K
kB, Kilobyte 238
Klammer
eckig siehe [ ]
geschweift siehe { }
rund siehe ( )
Kommazahl 36
Kommentar 54
kompilieren 7
Konstante, symbolische 131
Koordinate 65
umrechnen
ganzzahlig nach reell 272
reell nach ganzzahlig 252
Kosinus 256
Kreis 73, 256
L
Labyrinth 187
Lnge
Name 30
Zeichenkette 67
Landkarte 334
Liniengrafik 69
Linker 3, 16, 170
Logik 95
long 240
M
Makro 276, 353, 354
Mandelbrot-Menge 265
Markierung fr goto 356
Maus 205, 215, 218
Maximum 130
MB, Megabyte 238
Minimum 130

if 90, 97

Index 45
Initialisierung 32
Feld 46
Struktur 336

N
Nachricht 205, 308
Nachrichtenschleife 210, 308

Stichwortverzeichnis

363

Sandini Bib

Name von Variablen und Funktionen


30
Nullzeiger 284
O
OpenGL 326, 358
Operatoren, Rangordnung von 352
Overflow 37, 184, 243
P
Paint, Malprogramm 312
Palette 265
Parameter siehe Argument
Periode 229
Pfeiltaste 222, 340
Pinsel 76
Pixel 62, 68, 147, 312
Pointer siehe Zeiger
Potenz 236, 248
Preprocessor 19, 352
Anweisung siehe #
Primzahl 135
Programm VIII
ausfhrbar 7
beenden 120
kompilieren 7
Programmiersprache VIII, 278
Programmtext 2
einrcken 91
Leerstellen 21
Projektverwaltung 2, 11, 172
Prototyp 169, 204
und * 296
R
Rangordnung von Operatoren 352
Rechenarten 34, 36
Rechteck 73
register 355
Rekursion 181, 184
return 17, 166
ohne Argument 167
Rollenspiel 108
Rotation 256
Rckgabewert 165
Rckruffunktion 208

364

Stichwortverzeichnis

S
Schachbrett 150
Schleife 118
doppelt 134
endlos 121, 133
und Feld 128
Schlsselwort 355
Scrolling 340
short 240
signed 240
Sinus 249
sizeof 240
und Zeiger 282
Sonderzeichen 47
Sound 324
Speicherplatz 26, 29, 44, 46, 238, 278
und malloc 282
static 177, 179
stderr 304
stdin 304
stdout 304
Stift 69
Stream 302
String siehe Zeichenkette
struct 291
Struktur 278, 290
Bit-Feld 356
Definition 291
Element mit > 293
Element mit . 291, 293
Feld von Strukturen 292
Gre 296
Initialisierung 336
kopieren 295
und typedef 291
Zeiger auf 292
switch 356
Syntax 22
T
Tastatur 205, 221
Tastaturpuffer 34, 52
leeren 145
Textbildschirmanwendung 4, 62, 301
TextFenster.mak 9
Timer 227
Tonausgabe 324
Typ siehe Datentyp
typedef 247, 291

Sandini Bib

Typenumwandlung 243

Wrfel 103, 106, 153


Wurzel 248

U
Umlaut 30
union 355
Union 355
unsigned 240
Ursprung 65
V
Variable 28
automatisch 177
Definition 29
erlaubte Namen 30
extern 175, 178, 179
Gltigkeitsbereich 173, 175
Initialisierung 32, 178
lokal 173, 178
statisch 177
Vektor siehe Feld
Vergleichsoperator 92
virtueller Tastencode 221
void 163
volatile 355
Vorzeichen 34
W
wahr 93
Wave-Datei 325
while 119, 124
Windows, Software Development Kit
62
Windowsanwendung 310
Windowsprogrammierung 358
WinHallo.mak 62
WinMain.mak 206

Z
Zahl
Eingabe mit Tastatur 33
ganze Zahl 34, 239
Integerzahl siehe ganze Zahl
Kommazahl 36
Overflow 37
reelle Zahl 239
Zehnersystem 237
Zeichen 47, 239
Zeichenkette 15, 49
Eingabe mit Tastatur 52
Feld aus Zeichenketten 299
kopieren 296, 297
Zeichenstift 69
Zeiger 277
Adressenoperator 279
als Funktionsargument 286
auf Funktion 307
auf Stringkonstante 287
auf Struktur 292
Definition 280
Inhaltsoperator 279
Initialisierung 284, 286
Nullzeiger 284
Rechenarten 285
Speicherplatz 286
und Feld 283, 285
und Inkrement 298
Zeigervariable 279
Zeit 139
Zeitgeber 227
Zufall 103, 152, 180
Zuweisungsoperator 30, 39
Zweiersystem 237

Stichwortverzeichnis

365

Sandini Bib

Copyright
Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen
eBook-Zusatzdaten sind urheberrechtlich geschtzt. Dieses eBook stellen wir
lediglich als persnliche Einzelplatz-Lizenz zur Verfgung!
Jede andere Verwendung dieses eBooks oder zugehriger Materialien und
Informationen, einschliesslich

der Reproduktion,

der Weitergabe,

des Weitervertriebs,

der Platzierung im Internet,


in Intranets, in Extranets,

der Vernderung,

des Weiterverkaufs

und der Verffentlichung

bedarf der schriftlichen Genehmigung des Verlags.


Insbesondere ist die Entfernung oder nderung des vom Verlag vergebenen
Passwortschutzes ausdrcklich untersagt!
Bei Fragen zu diesem Thema wenden Sie sich bitte an: info@pearson.de
Zusatzdaten
Mglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die
Zurverfgungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung
des Verlags. Der Rechtsweg ist ausgeschlossen.
Hinweis
Dieses und viele weitere eBooks knnen Sie rund um die Uhr
und legal auf unserer Website

http://www.informit.de
herunterladen