/
Теги: informatik programmiersprachen glaubenssprache programmiersprache c tewi verlag
ISBN: 3-89362-015-X
Год: 1988
Текст
GESAMTWERK
zu Quick C, Turbo C, MS C, DR C,
Lattice C, Intel C
H. HEROLD - W. UNGER
GESAMTWERK
ZU
QuickC, TurboC, MS C, DR C, Lattice C, Intel C
unter
MS DOS, CP/M, UNIX, ISIS
H. HEROLD * W. UNGER
/«///
IMPRESSUM
Verfasser: Helmut Herold, Werner Unger
Alle Rechte vorbehalten. Ohne ausdrückliche, schriftliche
Genehmigung des Herausgebers ist es nicht gestattet,
das Buch oder Teile daraus in irgendeiner Form durch
Fotokopie, Mikrofilm oder ein anderes Verfahren zu
vervielfältigen oder zu verbreiten. Dasselbe gilt für das
Recht der öffentlichen Wiedergabe.
ÖC /loGkj b
© Copyright 1988 by te-wi Verlag GmbH
HERAUSGEBER:
te-wi Verlag GmbH, Theo-Prosel-Weg 1
8000 München 40
Die Herausgeber übernehmen keine Gewähr für die
Funktionsfähigkeit beschriebener Verfahren,
Programme und Schaltungen.
GESAMTHERSTELLUNG:
technik marketing, München
tm 5420/62015G/4915.0
Printed in Austria
ISBN 3-89362-015-X
Inhalt
Vorwort ....................................... IX
0 EINFÜHRUNG..................................... 0-3
1 EINFÜHRENDES BEISPIEL.......................... 1-3
2 DATENTYPEN, KONSTANTEN UND VARIABLEN 2-1
2.1 Elementare Datentypen..................... 2-3
2.2 Konstanten ............................... 2-7
2.3 Variablen................................. 2-10
2.4 Vereinbarung von Variablen................ 2-14
3 AUSDRÜCKE UND OPERATOREN ...................... 3-1
3.1 Der einfache Zuweisungsoperator........... 3-3
3.2 Arithmetische Operatoren.................. 3-6
3.3 Vergleichsoperatoren...................... 3-9
3.4 Logische Operatoren....................... 3-10
3.5 Zusammengesetzte Zuweisungsoperatoren..... 3-26
3.6 Inkrement- und Dekrementoperatoren ....... 3-28
4 SYMBOLISCHE KONSTANTEN: ttdefine .............. 4-1
5 ZEIGER ........................................ 5-1
6 EIN-UND AUSGABE ............................... 6-1
6.1 Die ttinclude-Anweisung .................. 6-3
6.2 Ein/Ausgabe eines Zeichens: getchar, putchar ... 6-4
6.3 Die Ausgabe mit printf.................... 6-14
6.4 Die Eingabe mit scanf .................... 6-19
7 VERZWEIGUNGEN ................................. 7-1
7.1 Anweisungen und Blöcke.................... 7-3
7.2 Die Anweisung if ......................... 7-3
7.3 Die bedingte Bewertung ................... 7-17
7.4 Die Anweisung switch...................... 7-20
8 SCHLEIFEN-ANWEISUNGEN ......................... 8-1
8.1 Der Komma-Operator ....................... 8-3
8.2 Die Schleifenanweisung for................ 8-4
8.3 Die Schleifenanweisung while ............. 8-26
8.4 Die Schleifenanweisung do ... while ...... 8-37
8.5 Die Anweisung break....................... 8-43
8.6 Die Anweisung continue.................... 8-47
8.7 Marken und die Anweisung goto............. 8-53
V
9 ZUSAMMENFASSUNG DER AUSFÜHRBAREN
ANWEISUNGEN ..................................... 9-1
10 DATENTYP-UMWANDLUNGEN ..........................10-1
10.1 Die implizite Umwandlung des Datentyps......10-3
10.2 Die explizite Umwandlung des Datentyps .....10-8
11 FUNKTIONEN UND PROGRAMMSTRUKTUREN .11-1
11.1 Allgemeines zu Bibliotheksfunktionen .......11-3
11.2 Erstellen eigener Funktionen................11-5
11.2.1 Die Form von C-Funktionen ...........11-5
11.2.2 Funktionen, die keinen ganzzahligen
Wert liefern ............................. 11-13
11.2.3 Die Parameter von Funktionen .......11-20
11.3 Speicherklassen ..........................11-27
11.4 Blockstruktur .............................11-58
11.5 Rekursive Funktionen ......................11-63
11.6 Präprozessor-Anweisungen ..................11-70
11.6.1 Symbolische Konstanten:
Bdefine name zeichenkette .................11-70
11.6.2 Makros: ttdefine makro zeichenkette .. 11-71
11.6.3 Einfügen von Dateien: ginclude............11-73
11.6.4 Bedingte Übersetzung ....................11-74
11.6.5 Zeilennumerierung: ttline ...............11-78
12 ZEIGERUNDVEKTOREN ..............................12-1
12.1 Zusammenhänge zwischen Zeigern und Vektoren ... 12-3
12.2 char-Zeiger ................................12-34
12.3 Mehrdimensionale Vektoren ..................12-53
12.4 Initialisierungen...........................12-64
12.5 Zeigervektoren und Zeiger auf Zeiger........12-78
12.6 Parameter für die Kommandozeile ............12-89
12.7 Speicherreservierung und-freigabe ........12-104
12.8 Zeiger auf Funktionen ....................12-114
13 STRUKTUREN .....................................13-1
13.1 Einfache Strukturen ........................13-3
13.2 Initialisierung von Strukturen..............13-13
13.3 Vektoren von Strukturen.....................13-16
13.4 Strukturen und Funktionen...................13-25
13.5 Zeiger auf Strukturen.......................13-32
13.6 Das Schlüsselwort typedef ..................13-43
13.7 Rekursive Strukturen .......................13-45
13.8 Das Schlüsselwort union.....................13-65
13.9 Bitfelder...................................13-68
13.10 Die Hierarchie der Operatoren .............13-71
14 DATEIEN ........................................14-1
14.1 Höhere E/A-Operationen .....................14-4
14.1.1 Der Datentyp FILE ...................14-4
VI
14.1.2 Das Eröffnen einer Datei ................14-6
14.1.3 Das Schließen einer Datei...............14-12
14.1.4 Schreiben/Lesen von 1 Byte............14-13
14.1.5 Schreiben/Lesen von 2 Bytes...........14-19
14.1.6 Schreiben/Lesen von 4 Bytes...........14-20
14.1.7 Schreiben/Lesen von Zeichenketten ... 14-20
14.1.8 Schreiben/Lesen von Blöcken ............14-22
14.1.9 Formatierte Ein-/Ausgabe................14-23
14.1.10 Wahlfreier Zugriff ................... 14-27
14.2 Elementare E/A-Operationen ...................14-31
14.2.1 Die elementare Dateieröffnungsroutine... 14-32
14.2.2 Die elementare Lese-und Schreibroutine ...14-35
14.2.3 Das elementare Schließen von Dateien....14-43
14.2.4 Das elementare Löschen von Dateien......14-44
14.2.5 Der elementare wahlfreie Dateizugriff ..14-44
14.3 Informationen zur Datei stdio.h...............14-48
15 DIE WICHTIGSTEN BIBLIOTHEKS-
FUNKTIONEN .............................................15-1
16 DAS SCHLÜSSELWORT enum.............................16-1
17 ANSI C ............................................ 17-1
17.1 ANSI-C-Schnellkursus ......................... 17-7
17.1.1 Allgemeines............................. 17-7
17.1.2 Der Präprozessor........................ 17-11
17.1.3 Die neue Sprache ANSI C................ 17-22
ANHANG ................................................. A-1
ASCII-Code........................................... A-3
Literaturverzeichnis ..............................A-7
VII
Vorwort
Die Programmiersprache C erfreut sich aufgrund ihrer einfachen Aus-
drucksweise, ihrer leichten Handhabung von Zeigern, ihrer großen Auswahl
an Operatoren und ihrer modernen Programm- und Datenstrukturen immer
größerer Beliebtheit bei der praktischen Programmerstellung.
Das vorliegende Buch ist aufgrund seiner Vielzahl von Beispielen zum
Selbststudium ebenso geeignet wie zum Unterrichten der Sprache C.
Die angegebenen Programmbeispiele entsprechen dem von den C-
Entwicklern Brian W. Kernighan und Dennis M. Ritchie vorgegebenen Stan-
dard und sind lauffähig unter den Betriebssystemen UNIX und UNIX-
verwandten Systemen, CP/M-86, ISIS-II und MS DOS.
In diesem Buch wird nicht versucht, eine möglichst kompakte Beschreibung
der Sprache C zu geben. Vielmehr verstehen wir diese Abhandlung als ein
Lehrbuch, das jedes einzelne Sprachelement an Beispielen und einfachen
Erläuterungen dem Leser nahebringt; so ermöglicht es sowohl Anfängern als
auch Praktikern das systematisch^ und leichte Erlernen der Sprache C.
Ebenfalls bei te-wi erschienen ist das Nachfolgewerk zum C-Gesamtwerk
»Ansi C«, welches die nun endlich standardisierte C-Sprache mit den zuge-
hörigen Bibliotheksfunktionen ausführlich beschreibt. Das Buch »Ansi C«
beschreibt nicht nur anhand von anschaulichen und praxisbezogenen Pro-
grammbeispielen das C der 90er Jahre, sondern eignet sich auch als Nach-
schlagewerk beim täglichen Arbeiten mit C.
Helmut Herold
Werner Unger
IX
0-1
EINFÜHRUNG 0
Die Sprache C wurde im Jahre 1972 von Dennis M. Ritchie in den Bell-
Laboratorien (Murray Hill, USA) entwickelt und von Brian W. Kernighan
1973/74 weiter verbessert. Vorläufer von C sind die Sprachen BCPL
(Basic Combined Programming Language) und B. Von diesen sei die
noch heute verwendete Sprache BCPL erwähnt, welche sich vor allen
Dingen zur Erstellung von Compilern und Betriebssystemen eignet.
Die Sprache C selbst hingegen ist wesentlich universeller einsetzbar.
Zwar scheint C sehr eng mit dem Betriebssystem UNIX verbunden zu
sein, da diese Sprache auf UNIX entwickelt und UNIX mit allen seinen
Dienstprogrammen nahezu vollständig in C geschrieben wurde; die vie-
len Entwicklungen von C-Compilern auf den unterschiedlichsten Be-
triebssystemen beweisen aber eher das Gegenteil. Trotzdem hat C si-
cherlich das Betriebssystem UNIX und UNIX die Sprache C geprägt.
C selbst ist eine sehr "kleine” Sprache, die über nur wenige Anweisun-
gen verfügt; mit diesen wenigen Anweisungen deckt sie aber alle für
wohlstrukturierte Programme geforderten Kontrollstrukturen ab, näm-
lich:
- Verzweigungen: if
- Schleifen mit Abfrage bei Schleifenbeginn: while, for
- Schleifen mit Abfrage am Schleifenende: do
- Auswahl aus mehreren Alternativen: switch
Daneben verfügt C auch über den in der strukturierten Programmierung
viel geschmähten GOTO-Befehl.
Die Anzahl von Anweisungen konnte in C so gering gehalten werden,
weil viele Aufgaben wie Ein-ZAusgabe oder Umspeichern von Zeichen-
ketten über fest vorgegebene Bibliotheksfunktionen gelöst werden.
Zur Struktur von C-Programmen: Ein C-Programm besteht aus einer
Reihe von Funktionen, die durch das Hauptprogramm main. ebenfalls
eine Funktion, aufgerufen werden. Im einfachsten Fall existieren nur ein
Hauptprogramm main ohne zusätzliche Funktionen.
C gleicht sicher in vielen Teilen der Sprache PASCAL. Allerdings sind in
C eine Reihe von Konstruktionen vorhanden, die in PASCAL fehlen oder
dort sehr unpraktisch gelöst wurden, wie
- Initialisierung
- Zeiger
0-3
- Dateiverarbeitung
- Bitmanipulation, usw.
Während man das von Niklas Wirth konzipierte PASCAL mehr als didak-
tische Sprache bezeichnen kann, ist C mehr den praktischen Erforder-
nissen bei realistischen Programmieraufgaben angepaßt.
Dieses Buch will dem Leser unter Zuhilfenahme ungewöhnlich vieler
Beispiele die Sprache C nahebringen: Nach einem einführenden Bei-
spiel in Kapitel 1 werden dazu zunächst die Datentypen von C und die
Begriffe "Konstante" und "Variable" im Kapitel 2 erläutert.
Kapitel 3 befaßt sich dann mit Ausdrücken und Operatoren. Symboli-
sche Konstanten werden im Kapitel 4 vorgestellt.
Eine Einführung in die Philosophie der Zeiger folgt dann in Kapitel 5.
Kapitel 6 beschäftigt sich mit den wichtigsten Funktionen für Ein- und
Ausgabe am Bildschirm.
Das C-Angebot von Verzweigungsanweisungen wird in Kapitel 7 disku-
tiert. Kapitel 8 beschreibt die möglichen Schleifenanweisungen der
Sprache C.
Eine Zusammenfassung aller ausführbaren Anweisungen findet sich in
Kapitel 9. Die impliziten und expliziten Datentyp-Umwandlungen wer-
den in Kapitel 10 beschrieben.
Mit Funktionen und Programmstrukturen beschäftigt sich Kapitel 11; da-
bei werden neben den Bibliotheksfunktionen auch die syntaktischen
Regeln für die Erstellung eigener Funktionen beschrieben. Dieses Kapi-
tel befaßt sich allerdings auch mit Speicherklassen, Gültigkeitsberei-
chen und Lebensdauer von Objekten, Rekursion und Präprozessoran-
weisungen.
Der Zusammenhang zwischen Zeigern und Vektoren wird in Kapitel 12
ebenso diskutiert wie die Initialisierung von ein- und mehrdimensiona-
len Vektoren. Daneben werden dort auch noch die Möglichkeiten der
Parameterübergabe in der Kommandozeile besprochen.
Kapitel 13 behandelt die Einzelheiten von Datenstrukturen (struct.
union) und Bitfelder. Das Ende dieses Kapitels bildet eine Hierarchieta-
belle für C-Operatoren.
Die höheren und elementaren E/A-Operationen zur Dateibearbeitung
werden schließlich in Kapitel 14 vorgestellt. Kapitel 15 gibt eine Zusam-
menstellung der wichtigsten C-Bibliotheksfunktionen. Das letzte Kapitel
beschreibt das C-Schlüsselwort enum.
0-4
EINFÜHRENDES
BEISPIEL
1-1
EINFÜHRENDES BEISPIEL 1
...................................
/• EINFUEHRENDES BEISPIEL.•/
.....................................................
main()
printf(*••••••••••••••••••••••••••••••••••;
prlntf("*
printfC« Das ist »ein erstes C-Programe •\n");
prlntfi"» •Xn*);
Vorläufig müssen alle unsere C-Programme die Zeichenfolge main()
enthalten; das Schlüsselwort main kennzeichnet den Programmanfang.
Der zu main gehörige Programmteil ist in geschweifte Klammern einzu-
betten: { kennzeichnet den Anfang, } das Ende.
Kommentar wird mit / *.....* / geklammert und kann an jeder belie-
bigen Stelle im Programm stehen. Kommentare sind Informationen für
den Programmierer und werden vom C-Übersetzer nicht beachtet. Um
Ihr Programm übersichtlich und transparent zu gestalten, sollten Sie
möglichst viel Kommentar einfließen lassen.
Für Bildschirmausgaben steht das Schlüsselwort printf( ..) zur Verfü-
gung. Der auszugebende Text muß dabei innerhalb von Anführungszei-
chen stehen: "Text".
Den Sprung in die nächste Bildschirmzeile löst das Steuerzeichen \n
aus. Dieses Steuerzeichen ist ebenfalls in den Anführungszeichen des
printf-Befehls mitanzugeben, z. B. "Text \n”. Am Bildschirm wird \n
nicht als Text ausgegeben.
Um einzelne C-Befehle voneinander zu trennen, ist ein Semikolon als
Trennzeichen anzugeben.
Unser Beispiel würde also folgendes am Bildschirm ausgeben:
• Das ist mein erstes C-Prograe»
1-3
Ein weiteres C-Programm soll uns das Steuerzeichen \n etwas näher
bringen:
/• Demonstrationsöeispiel zu« Steuerzeichen \n •/
..........................*..........................
nain ()
(
printf«\n< );
printfCDas ist ein Viereck
printfc'aus Sternchen, );
printf("oder nicht ?*);
printf(H\nE\nN\nD\nE");
printfCN") ;
printf(“D") ;
printf(“E\n");
)
Dieses Programm liefert folgende Bildschirmausgabe:
Das ist ein Viereck aus Sternchen, oder nicht 7
E
N
D
ENDE
Sie sehen, daß es sich bei \n um ein nicht druckbares Steuerzeichen
handelt, das einen Zeilenvorschub bewirkt. Wird es nicht angegeben,
dann wird auch beim nächsten printf-Befehl in der gleichen Zeile mit
der Bildschirmausgabe fortgefahren. Eine Ausgabe wie:
printf(”Guten Ta
g”);
ergibt einen Fehler, da ein Zeilenwechsel innerhalb einer Zeichenkette
nicht erlaubt ist.
1-4
DATENTYPEN, KONSTANTEN
UND VARIABLEN
2-1
DATENTYPEN, KONSTANTEN o
UND VARIABLEN
2.1 ELEMENTARE DATENTYPEN
In C existieren 4 elementare Datentypen:
Da der Computer Zeichen wie z. B. Buchstaben ganz anders behandelt
als Gleitpunktzahlen wie z. B. die Zahl n-3.14..., wurde eine Klassifika-
tion dieser unterschiedlichen Daten notwendig.
Ordnet man nun in einem Programm Daten bestimmten Klassen wie
Zeichen, ganze Zahl, einfach/doppelt genaue Gleitpunktzahl usw. zu,
dann teilt man damit dem Rechner deren Datentyp mit.
2-3
char
Daten dieses Typs belegen 1 Byte Speicherplatz und repräsentieren
"Zeichen”; in 1 Byte (8 Bit) kann genau 1 Zeichen des ASCII-Zeichen-
vorrats*) gespeichert werden.
int
Dieser Datentyp repräsentiert "ganze Zahlen”; dafür sind normalerwei-
se (ist rechnerabhängig) 2 Bytes* **) vorgesehen, also doppelt soviel
Speicherplatz wie für char. Mit diesen 2 Bytes (16 Bit) können Zah-
len im Bereich -32768 ... +32767 dargestellt werden.
C bietet aber auch - durch Voranstellen von Attributen - die Möglich-
keit, diesen Zahlenbereich zu variieren:
short
Meistens wird durch dieses Attribut derselbe Zahlenbereich
von ganzen Zahlen bezeichnet wie ohne ihn (was ursprüng-
lich nicht vorgesehen war).
Über welchen Zahlenbereich Sie an Ihrem Rechner - bedingt
durch dieses Attribut - verfügen können, sollten Sie an
geeigneter Stelle selbst überprüfen.
long
Durch Voranstellen dieses Worts (long int) wird ein Spei-
cherplatz von 4 Bytes (32 Bit) reserviert, was dem Zahlenbe-
reich-2147483648 ...+2147483647 entspricht.
unsigned
Dieses Attribut (unsigned int) bezeichnet einen positiven
Zahlenbereich (0 ... 65535), wofür 2 Bytes reserviert werden.
Hier wird also auf ein Vorzeichenbit verzichtet, was zur Er-
weiterung des positiven Zahlenbereichs gegenüber dem
(short-) int-Bereich führt.
Es kann auch allein das Attribut, d. h. ohne int, angegeben werden,
was vom C-Übersetzer genauso wie mit Angabe von int interpretiert
wird: short - short int, long = long int
HINWEISE: *) ASCII = American Standard for Coded Information Interchange (siehe
Anhang)
**) Manche Rechner nehmen für Int standardmäßig schon 4 Bytes.
2-4
float
Dieser Datentyp ist für Gleitpunktzahlen mit einfacher Genauigkeit vor-
gesehen; dazu werden 4 Bytes (32 Bit) reserviert, womit dezimale Gleit-
punktzahlen im Wertbereich von etwa ±10~37 ... ±10+38 dargestellt
werden können. Hier kann auch das Attribut long verwendet werden:
long float
reserviert Speicherplatz für Gleitpunktzahlen mit doppelter
Genauigkeit. Die Angabe dieses Attributs entspricht dann
dem Datentyp double: long float - double
double
Daten dieses Typs belegen 8 Byte (64 Bit) Speicherplatz und sind Gleit-
punktzahlen mit doppelter Genauigkeit. Mit dieser Anzahl von Bytes
können dezimale Gleitpunktzahlen im Wertebereich ±1O-307 ... ±10308
dargestellt werden.
Die nachfolgende Tabelle faßt noch einmal die typische rechnerinterne
Darstellung der verschiedenen Datentypen zusammen. Abhängig vom
verwendeten Compiler können leichte Abweichungen auftreten.
Datentyp Bits Dezimaler Zahlenbereich
char 8 0 ... 255 (ASCII)
int 16 -32768 ... 32767
unsigned int 16 0 ... 65535
short int 16 -32768 ... 32767
long int 32 -2.109... 2* 109
float 32 ±1O-^7... ±1038
double 64 -IO’307... ±1O308
Hier sind nochmals die einzelnen Datentypen zusammengestellt, wobei
die Bezeichnungen von der numerischen Prozessor-Erweiterung 8087
übernommen wurden.
HINWEIS: In neueren C-Versionen ist auch der Datentyp unsigned char zugelassen.
2-5
char
int
8 Bit (In diesem Byte kann genau
1 ASCII-Zeichen gespeichert werden)
float/double
float
long float oder
double
HINWEIS: *)-2147483648 ...+2147483647
2-6
Datentyp Bits Dezimaler Zahlenbereich
float long float/double 32 64 8.43 X10*37 sä Ixl C 3.37 x 1038 4.19 x10-3°7 < Ixl < 1.67x 10308
Der 8087 speichert Gleitpunktzahlen in binärem Format, das aus 3 Fel-
dern besteht:
SIGNIFICAND-Fe\d (Mantisse)
BIASED EXPONENT-Fe\d (Charakteristik)
S/GN-Feld (Vorzeichen)
Formel zur Darstellung einer Gleitpunktzahl:
(-1)s*(2 B-BIAS) . (o . fN.fo)
----- SIGNIFICAND
N = 22 (float) (23 Stellen)
N = 51 (long float/double) (52 Stellen)
---- EXPONENT
Bias= 127 (float)
Bias = 1023 (long float/double >
B = Biased Exponent (zu speichernder Exponent) *>
----SIGN
S = 0 ; positiv
S = 1 ; negativ
2.2 KONSTANTEN
Die Eigenschaft von Konstanten ist, daß sie - im Gegensatz zu Verän-
derlichen oder Variablen — einen festen Wert besitzen, der einem der
gerade besprochenen Datentypen zugeordnet ist.
HINWEIS: *) Der BIASED EXPONENT ergibt sich aus
B = wahrer Exponent + Bias
2-7
Beispiel 1
Konstanten vom Typ:
char
’B’ '+’ '2' ’y' '/'
(Konstanten vom Datentyp char werden in ’ ’ geklammert)
int/short int/short
237 -12 +324 -32575 0
long int/long
-78547321 7L +3L OL 12974812
Das angehängte L kennzeichnet den entsprechenden Wert als long-Wert; ein solcher
Wert ist in einem 32-Bit (nicht 16-Bit) Speicherplatz unterzubringen.
unsigned/unsigned int
12764 55786 2 18
float
3.14158 -0.103 12.74e-7*> -135.76E3 +17E-2
long float/double
-9.75897443L 11 0e-2L 123.756983124L ,73e-48
Das Suffix L legt fest, daß die entsprechende Konstante als long-Wert (64 Bits) zu spei-
chern ist.
int-Konstanten können auch oktal (Basis 8) und hexadezimal (Basis 16)
angegeben werden.
Ist die erste Ziffer einer ganzzahligen Konstante eine Null, so handelt
es sich bei dieser Konstante um eine Oktalzahl.
Beispiel 2
dezimal: 241 = 2 • 102 + 4 • 10’+ 1 ♦ 10° =
= 200 + 40 + 1
Dieser Dezimalzahl würde die Oktalzahl 361 - in C 0361 - entsprechen.
oktal: 361 = 3 • 82 + 6 ♦ 81 + 1 • 8° =
=3*64+6*8+1 * 1 =
= 192 + 48+1 =
= 241 (dezimal)
HINWEIS: *) e bzw. E steht für Exponent (10 hoch...)
z. B. 2.714 e 3 = 2.71 4 * 103 = 2714
2-8
Beginnt eine int-Konstante mit der Zeichenfolge OX oder Ox, so wird
sie als hexadezimale Zahl interpretiert.
Beispiel 3
dezimal: 241
Dieser Dezimalzahl würde die hexadezimale Konstante F1*) (in C müßten Sie
0XF1 oderOxFI oderOXfl oderOxfl schreiben) entsprechen.
hexadezimal: F1 = F • 161 + 1 *16° =
= 15 • 16 + 1 • 1 =
= 241 (dezimal)
Alle erlaubten Darstellungen einer Gleitpunktkonstante können durch
ein sogenanntes Syntaxdiagramm gezeigt werden:
HINWEIS: *) Im Hexadezimalsystem reichen die Ziffern 0 ... 9 des Dezimalsystems nicht
aus; deshalb hat man für die Werte 10 ... 15 Buchstaben eingeführt:
A =10 (dezimal)
B =11 (dezimal)
C =12 (dezimal)
D =13 (dezimal)
E =14 (dezimal)
F =15 (dezimal)
a =10 (dezimal)
b =11 (dezimal)
oder c =12 (dezimal)
d =13 (dezimal)
e =14 (dezimal)
f =15 (dezimal)
In C können Sie im Hexadezimalsystem Groß- und Kleinbuchstaben verwen-
den.
2-9
Beispiel
.913
-37 e 12
.347 E + 13
-3.743-E 3
0.74 e + 02 L
+ -2.13e-12
.074 E e 02
47139 e- 52
.-437 E+12
nach Syntaxdiagramm erlaubt
erlaubt
erlaubt
verstößt gegen die Syntax, da im Syntaxdiagramm kein
Weg zu finden ist. der diese Konstante ermöglicht
erlaubt
nicht erlaubt
nicht erlaubt
erlaubt (double-Konstante)
nicht erlaubt
2.3 VARIABLEN
Die Eigenschaft von Variablen ist, daß sich ihre Werte ständig ändern
können. Um den Begriff "Variable" besser verstehen zu können, soll zu-
nächst ein Beispiel herangezogen werden:
Ein Schreibtisch habe mehrere Fächer, die jedes eine Benennung tra-
Sie können sich unter einer Variablen die Benennung eines bestimmten
Speicherbereichs, in unserem Beispiel also einer Schublade, vorstellen;
es handelt sich bei Variablen also um eine Art Beschriftung eines Spei-
cherbereichs.
Während aber die Beschriftung immer gleich bleibt, kann sich der Inhalt
eines Speicherbereichs - in der Schublade für Papier können z. B. zu
einem Zeitpunkt 100 Blatt und zu einem anderen 500 Blatt Papier liegen
- ändern.
2-10
Die "Beschriftung eines Speicherplatzes", d. h. die Vergabe eines Na-
mens an eine Variable, wird vom Programmierer - also von Ihnen - vor-
genommen. Natürlich muß festgelegt werden, um welche Art von Va-
riable (siehe bei Datentypen) es sich handelt. Diese Festlegung wird
ebenfalls vom Programmierer vorgenommen.
Um nun einen Speicherplatz zu "beschriften", muß diesem ein Name,
der Variabienname, gegeben werden. Die Vergabe von Variabienna-
men ist in C an bestimmte Regeln gebunden, die Sie sich merken soll-
ten:
Es dürfen nur Buchstaben (keine Umlaute ä, ü, ö und ß), Ziffern
und der Unterstrich (_) verwendet werden. Die C-Compiler von
Lattice gestatten zusätzlich das $(Dollar)-Zeichen in Variabien-
namen.
Das erste Zeichen eines Variabiennamens muß immer ein
Buchstabe oder ein Unterstrich (_) sein.
Ein Variabienname darf beliebig lang sein, wobei jedoch nur
die ersten acht*! Zeichen signifikant sind, d. h. der C-Compi-
ler**) läßt weitere Zeichen unberücksichtigt.
Beispiel: KARLOTTO______1 (Variabienname 1)
KARLOTTO______2 (Variabienname 2)
Beide Variabiennamen verstoßen nicht gegen die
Regeln, aber beide adressieren ein und densel-
ben Speicherplatz (KARLOTTO), da nur die ersten
8 Zeichen für den C-Compiler relevant sind.
Klein- und Großbuchstaben werden unterschieden. Lediglich
der PC-DOS-Linker trifft keine derartige Unterscheidung.
Beispiel: Bei den beiden Variabiennamen HANS und HaNS
handelt es sich um zwei verschiedene.
Sie sollten sich an die Forderung von C halten, Variabiennamen
in Kleinbuchstaben und symbolische Konstanten (dazu später)
in Großbuchstaben zu schreiben. Diese Vorgehensweise erhöht
die Übersichtlichkeit Ihres C-Programms.
Wie jede höhere Programmiersprache verfügt C über einen fe-
sten Wortschatz von Befehlen. Solche Befehle dürfen natürlich
HINWEISE: *) Von Compiler zu Compiler verschieden und rechnerabhängig.
**) Compiler (einfach erklärt):
Wie jede Programmiersprache braucht auch C einen Übersetzer (Compi-
ler), der Ihr Programm auf Verstöße gegen die Sprachregeln (z. B. mayn
statt main) überprüft und in eine für Ihren Computer verständliche Ma-
schinensprache umsetzt.
2-11
nicht als Variabiennamen verwendet werden, weil sonst der C-
Compiler nicht entscheiden kann, ob ein Befehl oder ein Varia-
blen n am e gemeint ist.
Wie schon gesagt, dürfen Schlüsselwörter, d. h. in C fest vergebene
Wörter, nicht als Variabiennamen dienen. Um Ihnen eine kleine Über-
sicht zu verschaffen, sind alle C-Schlüsselwörter nachfolgend tabella-
risch zusammengestellt. Schlüsselwörter müssen immer klein geschrie-
ben werden:
auto enum short
break extern sizeof
case float static
char for struct
continue goto switch
default if typedef
do int union
double long unsigned
eise register void
entry return while
Während die Verwendung von Schlüsselwörtern als allein stehende Va-
riablennamen verboten ist, dürfen sie jedoch eingebettet in Variabien-
namen auftreten. Z. B. wäre automobil ein erlaubter Variabienname.
Syntaxdiagramm für Variabiennamen:
2-12
Beispiel 4
hans_im__glueck
7_und_40_ELF
_______mittel—streifen
karl_IV
null—08
drei— * _ hotel
abe_ schuetze
Kündigung
KINDERGARTEN
erlaubt
unerlaubter Name, da an erster Stelle kein Buchstabe
oder Unterstrich.
erlaubt
erlaubter Variabienname, da es sich bei IV nicht um rö-
mische Ziffern - es sei denn, Sie hätten eine antike Ta-
statur mit römischen Ziffern -, sondern um Buchstaben
handelt.
erlaubt
unerlaubter Name, da das Zeichen * * gegen die Regeln
verstößt.
erlaubt
erlaubt
unerlaubt, da Umlaute in Variabiennamen nicht zugelas-
sen sind.
erlaubt, aber Sie sollten trotzdem darauf achten, daß Sie
für Variabiennamen nur Kleinbuchstaben verwenden.
Tips:
• Sie sollten sich angewöhnen, selbsterklärende Variabiennamen zu wählen, d. h. Namen
mit Aussagekraft: Wenn Sie beispielsweise in Ihrem Programm eine Zählvariable für
Buchstaben und eine für Ziffern benötigen, so nennen Sie die erste nicht a und die an-
dere b, z. B. zaehl—buchstaben und zaehl_zitter
• Natürlich erfordert das etwas mehr Schreibarbeit, aber dadurch wird Ihr Programm über-
sichtlicher und transparenter.
Wenn Sie einige Monate später Ihr Programm verändern oder erweitern wollen, dann ge-
ben Ihnen die Namen zaehl___buchstaben und zaehl____zitter wesentlich mehr Infor-
mation als solche "Faulheitsnamen” wie a und b.
• Aus denselben Gründen der Übersichtlichkeit und Lesbarkeit von Programmen wurde
auch der Unterstrich in C-Variablennamen zugelassen:
Der Variablenname hausundhofverloren ist sicher nicht so gut lesbar wie der Name
der Variablen haus—und___hof_verloren
2-13
2.4 VEREINBARUNG VON VARIABLEN *>
Bevor Sie im Programm mit Ihren Variablen arbeiten können, müssen
Sie diese vereinbaren:
Dazu ist ein Variabienname und der Datentyp der Variablen anzugeben,
um dem C-Compiler mitzuteilen, welche Werte (Zeichen, ganze Zahlen
oder einfach/doppelt genaue Gleitpunktzahlen) in dieser Variablen ge-
speichert werden sollen. Der Compiler reserviert daraufhin den notwen-
digen Speicherplatz für diese Variable.
Der Aufbau einer Variabienvereinbarung läßt sich an einem Syntaxdia
gramm zeigen:
Aus diesem Syntaxdiagramm ist zu ersehen, daß mehrere Variablen
gleichzeitig, durch Komma getrennt, vereinbart werden können und die
Vereinbarung durch Semikolon abzuschließen ist.
Beispiel 5
int zaehler;
Für die Variable zaehler werden 2 Bytes reserviert, d. h. in ihr können ganze Zahlen im
Bereich -32768 ... +32767 gespeichert werden.
char ein____zeich, antwort;
Für die beiden Variablen mit Namen ein_zeich und antwort wird genau 1 Byte reser-
viert. d. h., daß in diesen Variablen immer genau 1 Zeichen des ASCII-Zeichenvorrats ge-
speichert werden kann.
float einfach;
Es werden 4 Bytes für die Variable mit Namen einfach reserviert, einfach ist eine Gleit-
punktvariable mit einfacher Genauigkeit, d. h. in dieser Variable können Gleitpunktzahlen
mit einfacher Genauigkeit "hinterlegt” werden. * **)
HINWEISE: *)Anstelle von ”Vereinbarung von Variablen ’ spricht man oft auch von der
"Deklaration von Variablen ’.
**) Bei Datentypangabe sind die unter 2.1 besprochenen Schlüsselwörter für
Datentypen zu verwenden, z. B. long int oder unsigned oder char usw.
2-14
long int ganz.gross;
Für die Variable ganz_gross werden 4 Bytes reserviert, d. h. ihr Wertebereich erstreckt
sich über alle ganzen Zahlen -231... +231-1.
double dopp_genau;
Für diese Variable werden 8 Bytes reserviert, d. h.: es können Gleitpunktzahlen mit dop-
pelter Genauigkeit in ihr festgehalten werden.
unsigned ohne.vorzeich, zahl______positiv;
Die Variable mit Namen ohne_vorzeich kann alle ganzen Zahlen 0 ... 65535 aufnehmen.
Dasselbe gilt für die Variable zahl_positiv
2-15
AUSDRÜCKE
UND OPERATOREN
3-1
AUSDRÜCKE UND o
OPERATOREN O
Ein Ausdruck besteht aus mehreren Operanden, die durch einen oder
mehrere Operatoren miteinander verknüpft sind.
An Operatoren bietet C:
- den einfachen Zuweisungsoperator
- arithmetische Operatoren
- Vergleichsoperatoren
- logische Operatoren
- zusammengesetzte Zuweisungsoperatoren
- Inkrement- und Dekrementoperatoren
3.1 DER EINFACHE
ZUWEISUNGSOPERATOR =
Dieser Operator hinterlegt einen Wert in einer Variablen; durch die An-
weisung
zahl__var = 5;
wird in der Variablen zahl____var der Wert 5 abgelegt. Soll nun
der Wert -17 in zahl_var gespeichert werden, so ist lediglich
zahl_var = -17;
zu schreiben, wodurch der alte Wert von zahl________var überschrie-
ben wird.
Der Operator = ist hier nicht im mathematischen Sinne eines
Gleichheitszeichens zu verstehen, sondern mehr im Sinne von
"ergibt sich aus”:
zaehler = 19;
Der Wert in der Variablen zaehler ergibt sich aus 19, d. h.: Im
Speicherplatz mit dem Namen zaehler wird der Wert 19 ge-
speichert.
3-3
Beispiel 1
In der Variablen zahL_1 sei der Wert 4, in der Variablen zahl___________2 der Wert -24
gespeichert. Durch die Zuweisung:
zahl___1 = zahl__2;
(Speichere in zahl_1 den Wert von zahl__2)
gilt dann folgendes:
Vor dieser Zuweisung:
Nach dieser Zuweisung:
Der ursprüngliche Inhalt von zahl___1 wurde mit dem Inhalt der Variablen zahl____2 über-
schrieben.
C-Programm, das die Werte zweier int-Variablen vertauscht.
/••••*»•»•••••••••••.......••»•••••••••••/
/• vertauschen von zwei Variablen •/
.........................
main ()
<
int variabj, variab_2, hilf;
/* Deklaration von drei ganzzahligen
Variablen mit den Namen
variabel', variabel t hilf •/
varlab_l=105;
variabj I 105 I
I _ I
I : I •/
varlab_2=-14;
/•
variab_1 f l'Ö5~ T
1 I
vanab 2 I -14" "l
I „ I
1 : 1 •/
3-4
HINWEIS: Auf älteren Implementationen können C-Compiler hier
Schwierigkeiten wegen des unerlaubten Operators "=- ” machen
Abhilfe "= - ", d. h Trennung durch ein Leerzeichen.
vertauschen
Zwischenspeiehern des Wertes von
in der “hilfsvarlable“ hilf’
hilf=variab_1;
iös" " i
hilf
lös'
/• Speichern ces Wertes von varlab_2 in
variabel . id.h. der urspruengilche
wert von variatJj wird ueberschrieben).
Der urspruengilche Wert von vanab.1 wurde
zuvor in hilf' gesichert. “ ♦/
variabel=variab_2;
variaö.l l’"Jä3"’-14"“i
variab_2 I -14 I
I I
hilf I 103*’“ I
I I
I : I
/• Speichern des in hilf gespeicherten wertes
in variab_2’. Nach dieser Zuweisung sind die
urspruenglfchen werte von vanaDJ ' und vanab_2
vertauscht. “ •/
variab_2=nilf; /•
variab 1 I Ü? -14 I
I I
variab 2 1 105 I
I I
hilf 1 l‘D5 1
Variablen können in einer Vereinbarung auch initialisiert, d. h. mit einem
Anfangswert vorbelegt werden. Dazu ist nach dem Variabiennamen ein
Gleichheitszeichen und eine Konstante anzugeben.
Beispiel 3
int summe = 0;
Es wird für die Variable summe ein Speicherplatz von 2 Byte reserviert und dort der Wert
0 binär abgespeichert. Eine solche Vereinbarung entspricht den beiden Befehlen
int summe;
summe = 0;
3-5
char neue______zeile = ’ \ n’;
In der char-Variablen wird bei ihrer Vereinbarung gleichzeitig das Steuerzeichen \n für
Zeilenvorschub gespeichert. (Bei \n handelt es sich um lediglich 1 Zeichen, d. h. zur
Speicherung dieses Steuerzeichens genügt 1 Byte Speicherplatz)
float untere____grenze = 0.5e-4;
Bei dieser Vereinbarung wird für die Variable untere_grenze ein Speicherplatz von 4
Byte reserviert und dort auch zugleich der Wert 0.5e-4 abgespeichert.
3.2 ARITHMETISCHE OPERATOREN
Diese Operatoren benötigt man für mathematische Rechnungen:
Addition
Subtraktion
Multiplikation
Division
Modulo (Rest einer Ganzzahldivision)
Es werden immer zuerst die Operatoren auf der rechten Seite des Zu-
weisungsoperators ausgewertet und dann das Ergebnis der Variablen
auf der linken Seite zugewiesen.
Beispiel 4
kreis_umfang = 2 * kreis__________radius «3.14;
Zunächst wird 2 mit dem Wert der Variablen kreis__radius multipliziert. Das Ergebnis
dieser Operation wird dann mit 3.14 multipliziert und liefert das Ergebnis der rechten Sei-
te. Dieses Ergebnis wird abschließend in der Variable kreis_umfang gespeichert.
HINWEIS: Der Operator % ermittelt den Rest einer Ganzzahldivision.
Beispiele: 13: 3= 4 Rest 1 ( 13% 3= 1)
36: 7= 5 Rest 1 ( 36% 5= 1)
43:22= 1Rest21 (43%22 = 21)
107: 7= 15 Rest 2 (107% 7= 2)
3-6
Bei Auswertung der Operatoren auf der rechten Seite gelten bestimmte
Regeln:
1) Es gilt die mathematische Regel
"Punkt vor Strich”.
Die Operatoren + und - besitzen die gleiche Priorität, die jedoch ge-
ringer ist als die der Operatoren *, / und %, welche untereinander
aber wieder von gleicher Priorität sind.
Die Priorität der ”Punkt”-Operatoren (♦, /. %) ist jedoch geringer als
die eines negativen Vorzeichens - (das Vorzeichen + existiert
nicht).
2) Bei gleicher Priorität werden die Operatoren von links nach
rechts abgearbeitet.
3) Will der Programmierer andere Prioritäten vergeben, so sind Klam-
mern zu setzen.
Die aus Operanden und Operatoren zusammengesetzte rechte Seite
bezeichnet man auch als Ausdruck. Ein Ausdruck liegt allerdings
auch bei
variable = Ausdruck
vor. Wird ein solcher Ausdruck durch ein Semikolon begrenzt:
variable = Ausdruck;
spricht man von einer Anweisung
Beispiel 5
a) beisp_a = 7+ 3-4/6 - (8+2)/-5
Berechnungs- schritte auf der rechten Seite 7 + 12/6 -(8+2)/-5 7 + 2 -(8+2)/-5 9 -(8+2)/-5 9 -10 Z-5 9 - -2 * — 11
In der Variablen beisp__a wird also der Wert 11 gespeichert.
3-7
b)
Mathematischer Ausdruck Ausdruck in C
7 4-3 A 2 5 7/2+(4-3)/5 • 4
14 18 + 14*18 9 7 9-7 14/9 • (18/7) + (14-1 8)/(9-7) oder: 14/9 • 18/7 + 14x18/(9-7) Es könnten noch weitere Formen angegeben werden, die die Richtigkeit des Ergebnisses gewährleisten würden.
4+7-1 # 3+5 12-7 7-2-1 (4+7-1 )/(12-7) • ((3+5)/7-2-1)) oder: (4+7-1 )/(1 2-7) - (3+5)/(7-2-1) oder
Allgemein gilt, daß zuviele Klammern nicht schaden. Es müssen nur
ebenso viele öffnende wie schließende Klammern vorhanden sein, da
sonst der C-Compiler einen Syntaxfehler meldet.
Da schon des öfteren der Begriff "Syntax” verwendet wurde, soll dieser
Begriff hier erläutert werden; dazu werden die beiden Begriffe "Syntax”
und "Semantik" einander gegenübergestellt.
Syntax meint die Regeln für richtig gebildete Sätze in einer
Sprache, auch einer Programmiersprache. Beispiel: "Der
Schnee ist grün" ist in der deutschen Sprache syntaktisch
richtig, wenn auch der Sinn der Aussage fraglich ist.
Semantik behandelt den Sinn und die Logik einer Aussage.
Beispiel: "Der Schnee ist grün" wäre semantisch (vom Sinn
her) falsch, aber syntaktisch richtig.
Der C-Compiler überprüft Ihr Programm auf Richtigkeit der Syntax ent-
sprechend den Regeln der Sprache C. Wie Sie sich denken können,
kann er die Logik (Semantik) Ihres Programms natürlich nicht überprü-
fen.
3-8
3.3 VERGLEICHSOPERATOREN
Um Bedingungen formulieren zu können, benötigt man Vergleichsope-
ratoren, auch relationale Operatoren genannt; C bietet dazu folgende
Vergleichsoperatoren an:
> >= < < = 1 - — größer als größer als oder gleich kleiner als kleiner als oder gleich gleich ungleich
Diese Operatoren haben geringere Priorität als die arithmetischen Ope-
ratoren. Die Operatorengruppe: > >= < <= besitzt eine höhere Prio-
rität als die sogenannten Äquivalenzoperatoren: = = ! =. Die Operatoren
einer Gruppe haben untereinander allerdings wieder gleichen Vorrang.
Beispiel 6
haus < eva - 1 + adam * apfel
Zuerst wird der Ausdruck (eva - 1 + adam * apfel) berechnet, wie wenn er mit Klammern
umgeben wäre, und das Ergebnis wird dann mit dem Wert der Variable haus verglichen.
Ein Vergleich kann positiv oder negativ ausfallen, d. h. es gibt nur 2
Möglichkeiten:
der Vergleich ist wahr, engl.: TRUE
der Vergleich ist falsch, engl.: FALSE.
Deshalb werden Vergleiche hauptsächlich für Abfragen verwendet, ob
eine bestimmte Bedingung im Programm gilt oder nicht.
Abhängig vom Ergebnis einer solchen Abfrage (wahr oder falsch) wird
dann im Programm verzweigt. Wir werden das später noch genauer be-
handeln.
3-9
3.4 LOGISCHE OPERATOREN
C bietet folgende logische Operatoren an:
! Negation
& & UND-Verknüpfung
II ODER-Verknüpfung
< < Bit nach links schieben
> > Bit nach rechts schieben
** Einer-Komplement ((B-1 )-komplement)
& bitweises UND
I bitweises ODER
- bitweises EXKLUSIV ODER
Der Operator ! bewirkt eine Negation (Umkehrung) des Wahrheitswer-
tes. Das Symbol Ider ODER-Operatoren ist das ASCII-Zeichen 124, das
manchmal auch als Idargestellt wird.
!(bed_1 >3)
ist wahr, wenn der Wert der Variable bed_1 kleiner als oder gleich (< =) 3 ist. Bei allen
anderen Zahlenwerten dieser Variable ist die Bedingung nicht erfüllt, also falsch.
!(bed_2 = =’N’)
ist wahr, wenn die char-Variable bed__2 nicht das Zeichen ’N’ enthält; enthält bed__2
das ASCII-Zeichen ’N', so ist die Bedingung nicht erfüllt.
3-10
!(bed_3= = 4)
ist wahr, wenn der Wert der Variable bed_3 ungleich 4 ist und sonst ist sie nicht erfüllt.!
liefert 0, wenn der Operand ungleich 0 ist, und 1, wenn der Operand 0 ist, z. B. !(324) = 0
und!(0) = 1.
Sie sollten sich folgende Regel für die logischen Operationen mer-
ken:
Ist das Ergebnis einer logischen Operation gleich Null, so wird
das Ergebnis als logisch falsch angesehen; bei allen anderen,
von Null verschiedenen Werten wird das Ergebnis als logisch
richtig betrachtet.
Wir wollen anhand einer Tabelle die Operatoren &&, II und ! bespre-
chen:
Bedingung 1 Bedingung 2 ! (Bedingung 1) Bedingung 1 && Bedingung 2 Bedingung 1 11 Bedingung 2
falsch falsch wahr falsch falsch
falsch wahr wahr falsch wahr
wahr falsch falsch falsch wahr
wahr wahr falsch wahr wahr
i
negiert also die vorliegenden Wahrheitswerte, (wahr -»falsch, falsch -»
wahr).
&&
2 mit diesem logischen Operator verknüpfte Einzelbedingungen erge-
ben eine Gesamtbedingung, die nur wahr ist, wenn beide Einzelbedin-
gungen wahr sind; ansonsten ist die Gesamtbedingung falsch.
II
Eine mit diesem logischen Operator gebildete Gesamtbedingung ist nur
wahr, wenn mindestens eine der mit II verknüpften Bedingungen wahr
ist, sonst ist sie falsch (wenn alle mit II verknüpften Bedingungen falsch
sind).
3-11
Beispiel 9
a) Wenn es Weihnachten ist & & (und)
Wenn es schneit & & (und)
Wenn Sie Schnupfen haben
Die Gesamtbedingung ist nur dann wahr, wenn alle Einzelbedingungen wahr sind. Wenn
Sie z. B. keinen Schnupfen haben, dann ist auch die Gesamtbedingung nicht erfüllt.
b) bed_a < 4 Ä & bed_a > = 1
Diese Gesamtbedingung ist nur dann erfüllt, wenn der Wert der Variable bed__a kleiner
als 4, aber gleichzeitig auch größer als oder gleich 1 ist (wenn also bed__a einen Wert
aus dem Intervall [1, 4[ besitzt); bei allen anderen Werten von bed_a ist die Bedingung
nicht erfüllt, also falsch.
C) In einer Int-Variable alter soll das Alter einer beliebigen Person gespeichert sein.
Wir wollen nun wissen, ob diese Person jünger als 44 Jahre ist, aber das Alter von 18 Jah-
ren schon erreicht hat:
alter > = 18 & & alter < 44
In den Beispielen b) und c) benötigten wir keine Klammern, da der Vor-
rang von && geringer ist als der von den Vergleichsoperatoren. Bei
mehreren & A-Operatoren wird die Verknüpfung von links nach rechts
abgearbeitet. Der Negationsoperator! wiederum ist wie ein Vorzeichen
anzusehen und besitzt höhere Priorität als die Vergleichsoperatoren.
Der Operator II verknüpft ebenfalls Einzelbedingungen; eine so entste-
hende Gesamtbedingung ist dann erfüllt (wahr), wenn mindestens eine
Einzelbedingung erfüllt (wahr) ist.
a) Wenn es Weihnachten /stl I (oder)
Wenn es schneit! I (oder)
Wenn Sie Schnupfen haben
Die Gesamtbedingung ist nur wahr, wenn mindestens eine der Einzelbedingungen wahr ist.
Wenn Sie z. B. Schnupfen haben und es regnet und es ist Ostermontag, dann ist die Ge-
samtbedingung erfüllt (wahr).
b) alter < 6Halter > = 63
Wenn der Wert der int-Variable alter kleiner als 6 oder größer als oder gleich 63 ist, ist
diese Verknüpfung wahr, andernfalls falsch. Diese Verknüpfung ist also sowohl für noch
nicht schulpflichtige Kinder als auch für Personen im Rentenalter erfüllt.
Nun wollen wir den gleichen Personenkreis durch eine & 6-Verknüpfung abdecken:
.'(alter > = 6&& alter < 63)
3-12
c) geschlecht = = ’W’ & & alter = = 401 Igeschlecht == 'M' 4 4 alter = = 50
Diese Verknüpfung wäre für alle 40-jährigen Frauen und 50-jährigen Männer erfüllt. Für
Personen mit anderen Daten wäre diese Verknüpfung falsch. Der Vorrang von 11 ist gerin-
ger als der von & & (und damit natürlich geringer als der von Vergleichsoperatoren), wes-
halb in diesem Beispiel auch auf das Setzen von Klammern verzichtet werden konnte.
Die bisher noch nicht besprochenen logischen Operatoren zur Bitmani-
pulation können nicht auf die Datentypen float und double angewen-
det werden; die "Wahrheitswertoperatoren" (& &. 11 und !) dagegen las-
sen sich auf diese Datentypen anwenden.
Der &-Operator verknüpft einzelne Bits folgendermaßen:
0&0 = 0
0 & 1 = O A us der Verknüpfung ergibt sich also nur eine 1,
1 & 0 = 0 wenn alle verknüpften Bits 1 sind.
1 & 1 = 1
V=- 16384 6192 4096 2048 1024 812 256 128 84 32 16 8 4 2 1
dwi 2*» 2'1 2” 2” 2” 2* 2* 2* 2* 2* 2* 2* 21 2* 2°
0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 1
0 0 0 0 0 0 0 0 1 1 0 0 1 0 1 1
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
An dieser Stelle wollen wir kurz über die Darstellung negativer ganzer
Zahlen in Rechnern sprechen.
int-Zahlen benötigen 2 Bytes, also 16 Bits. Das höchstwertige Bit stellt
das Vorzeichen dar: 0 = positiv, 1 = negativ. Um eine negative Zahl dar-
zustellen, wird zur entsprechenden positiven Zahl das Einer-Komple-
ment gebildet, d. h. alle Bits werden invertiert: 0 -» 1 und 1 —> 0, und auf
die so erhaltene Bitkonstellation 1 aufaddiert; so erhält man das Zwei-
er-Komplement.
Wir wollen die Zahl -13 im Dualsystem darstellen;
3-13
Beispiel 12
Tain U
<
int zahll, zahl_2;
zahl is-7;
zahl" 2 = 12;
printf(“Zd",zahl l&zahl 2);
printf("\n“);
zahl 1<32;
zahlj2=743;
printf("Zd",zahl l&zahl 2);
printf(M\n");
>
Bei %d in printf handelt es sich um eine Formatangabe, nach der das Ergebnis von
zahl___1 & zahl__2 als Dezimalzahl auszugeben ist
Test:
Dieses Programm würde also am Bildschirm
8
32
ausgeben.
Mit dem 4-Operator können Bits gezielt auf 0 gesetzt werden.
HINWEIS: Die meisten Rechner arbeiten mit dem Zweier-Komplement.
3-14
Beispiel 13
a) In der Variablen vorn___nult ist der Wert -1 gespeichert. Wir wollen nun die er-
Sten 8 Bit dieser 16-Bit-int-Variablen auf 0 setzen. Die dazu erforderliche Anweisung
könnte z. B. so aussehen:
vorn_null = vorn_null & OXOOFF;
Vor dieser Anweisung:
Nach dieser Anweisung:
Bei Hexadezimalzahlen (Präfix OX oder Ox) wird immer eine Tetrade (4 Bits) direkt be-
schrieben:
0 = 0000 8 =1000
1 =0001 9 =1001
2 = 0010 A (oder a)= 1010
3 = 0011 B (oder b)= 1011
4 = 0100 C (oder c)= 1100
5 = 0101 D (oder d)= 1101
6 = 0110 E (oder e)= 1110
7 = 0111 F (oder f) = 1111
Die Hexadezimalzahl 0XA47C entspricht folgender Dualzahl:
10100100 0111 1100
b) In der Variable mitt_null ist der Wert 426 gespeichert. Wir wollen nun die mittleren
8 Bit dieser int-Variable auf 0 setzen. Die dazu erforderliche Anweisung könnte z. B. so
aussehen:
mitL_null = mitt__null & 0170017
3-15
Vor dieser Anweisung:
mitt__null
426-
Vor- 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
chen 2« 2” 2’* 2" 2” 2* 2« 2’ 2« 2* 2* 2» 2“ 2’ 2*
0 0 0 0 0 0 0 1 1 0 1 0 1 0 1 0
Nach dieser Anweisung:
4. Dreibit- 3. Dreibit- 2. Dreibit- 1. Dreibit- 0. Dreibit-
Block Block Block Block Block
Bei Oktalzahlen (Präfix 0) wird immer ein 3-Bit-Block direkt beschrieben:
0 = 000
1 =001
2 = 010
3 = 011
4=100
5 = 101
6=110
7 = 111
Die Oktalzahl 073512 entspricht folgendem Bitmuster:
0 111 011 101 001 010
C) In diesem Beispiel wollen wir den Unterschied zwischen dem & und ÄA-Operator
nochmals klar hervorheben. Dazu rufen wir uns in Erinnerung, daß das Ergebnis einer logi-
schen Operation gleich Null ist, falls sie eine logisch falsche Aussage liefert; bei allen an-
deren, von Null verschiedenen Werten gilt das Ergebnis als logisch richtig.
Entsteht nun aus einer Operation ein logisch richtiges Ergebnis, dann wird dies in C durch
den Wert 1 angezeigt; ein logisch falsches Ergebnis durch den Wert 0.
Hat nun z. B. zahl_1 den Wert 2 und zahl_2 den Wert 5, so bewirken die beiden Opera-
toren & und AA folgendes:
zahl = zahl_____1 & zahl_____2;
Vor- 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
Z»
chen 2'* 2° 2U 2" 2'° 2*2*2’2*2*2*2’2,2’2e
zahl 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 = 2
zahl_2 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 = 5
zahl 1 & zahl 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 = 0
3-16
zahl = zahl_1 && zahl___2;
zahl—1 Vor- w- ehen 18384 2” 8192 2” 4006 2” 2048 2” 1024 2.o 512 2» 256 2® 128 2® 64 2» 32 2® 16 8 2» 4 2« 2 2’ 1 2° logisch richtig (von 0 verseh I
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
zahl 2 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 logisch richtig (von 0 verseh )
zahl.1A < zahl 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 Ergebnis logisch richtig (wird zahl
auf 1 gesetzt;
Der I-Operator verknüpft einzelne Bits wie folgt:
010 = 0
011 = 1 Aus der Verknüpfung ergibt sich also eine 1, wenn mindestens
110 = 1 eines der verknüpften Bits gleich 1 ist
111 = 1
Vor- 16384 8192 4086 2048 1024 512 266 128 64 32 16 8 4 2 1
Chen 2u 2«> 2’» 2” 2’° 2* 2* 2’ 2* 2* 2* 2® 2* 2’ 2*
0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 1
0 0 0 0 0 0 0 0 1 1 0 0 1 0 1 1
0 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1
iain()
(
int zahl.1, zahl_2;
zahl l«-7;
zahl" 2 = 12;
printf("Xd",zahl.1I zahl.2);
printf(*\n*);
zahl 1=32;
zahlj2=743;
printf("Xd“,zahl.1izahl.2);
printf("\n");
Bei %d in printf handelt es sich um eine Formatangabe, nach der das Ergebnis von
zahl__11zahl__2 als Dezimalzahl auszugeben ist
3-17
Test:
Dieses Programm würde also am Bildschirm
-3
743
ausgeben.
Mit dem I-Operator können Bits gezielt auf 1 gesetzt werden.
Beispiel 15
a) In der Variablen vom_eins ist der Wert 1 und in der Variablen muster der Wert
OXffOO gespeichert; beide Variablen sind vom Datentyp Int. Mit der Anweisung
vorn__eins - vorn_eins I muster;
werden die ersten 8 Bits der Variablen vorn__eins auf 1 gesetzt.
Vor dieser Anweisung:
Vor- 16384 81»2 4096 2048 1024 612 266 128 64 32 16 8 4 2 1
eher 2” 2” 2” 2* 2» & T 2* 2* 2* 2* 2» 2* 2°
Ll 0 0 0 0 0 0 0 0 0 0 0 0 0 1
3-18
Nach dieser Anweisung
Vor. 16384 8-102 4098 2048 1 024 512 266 128 64 32 16 8 4 2 1
214 2U 2” 2” Z* 2* 2* 2’ Z Z 2* 2» 2» Z Z
vom__slns 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 = 1
muster 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 = OXffOO
vorn__elns I muster 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 = -255
Es werden in vom____eins die Bits auf 1 gesetzt, die auch in muster so gesetzt sind; die
schon in vom____eins auf 1 gesetzten Bits bleiben erhalten.
b) Der Unterschied zwischen dem I und I I-Operator soll auch hier gezeigt werden.
nain ()
(
int zahl_lf zahl_2;
zahl 1«2;
zahl.2»5;
printf("Xd", zahl.1izahl.2);
printf(*\n*);
printf("Xd",zahl_iIizahl_2);
printf("\n");
Dieses Programm würde folgendes am Bildschirm ausgeben:
7
1
Erläuterung:
zahl_1 lzahl_2
zahl_1 llzahl_2
Vor- 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
Ä 2« y» 2’» 2” 2” Z Z 2’ 2» Z Z 2» Z 2’ Z
zahl—1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 | von 0 wrun)
zahl 2 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 lopescA ncheQ (von 0 verseh )
zahl—1 llzahi_2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 Ergocms logiscn nohiifl
3-19
Der *-Operator (EXKLUSIV ODER) verknüpft einzelne Bits wie folgt:
0*0 = 0
0*1 = 1 Aus der Verknüpfung ergibt sich also nur dann eine 1, wenn nur eines der
1*0=1 verknüpften Bits auf 1 (und die anderen Bits auf 0) gesetzt sind.
1*1=0
Beispiel 16
nain()
(
int zahl_i, zahl_2;
zahl la-7;
zahl"2 = l2;
printf("Xd".zahl 1‘zahl 2);
printf C\n") ;
zahl 1-32:
zahl~2 = 743;
printf("Xd".zahl 1“zahl 2);
printf("\n");
>
Test:
3-20
Dieses Programm würde also am Bildschirm
-11
711
ausgeben.
Mit dem **-Operator kann leicht ein Bitmuster erzeugt werden, in dem alle Bits auf 0 ge-
setzt sind.
Beispiel 17
In der Variable null_setz sei der Wert -3 gespeichert; bei null__setz handelt es sich um
eine Int-Variable. Mit der Anweisung
null___setz = null__setz ~ null___setz;
werden alle Bits von null_setz auf O gesetzt.
Vor dieser Anweisung:
Vor- 16364 8192 *096 2048 1024 612 256 128 64 32 16 8 4 2 1
<*en 2»« 2’* 2” 2” 2” 2*2*2» 2*2*2*2*2»y2*
null »eta 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 =-3
Nach dieser Anweisung:
813»
2”
-813 »
0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 1
1 1 1 1 1 1 0 0 1 1 0 1 0 0 1 0
= -814
3-21
An früherer Stelle wurde gesagt, daß Rechner meist mit dem Zweier-
Komplement arbeiten. Dies wollen wir an einem Beispielprogramm zei-
gen:
Beispiel 18
nain ()
C
int zahl_pc)s, zweipr_ko«pl, einerkompl;
zahl_pos=1□;
zwe1er_kompl=-zahl_pos;
prlntfl’Xd", zweier”kompl);
printf("\n");
einer_ko«pl=*zahl_pos;
printf("Xd".einer kompl);
printf("\n");
Dieses C-Programm würde folgendes am Bildschirm ausgeben:
Mit den beiden Shift-Operationen < < und > > können Sie die Bits ei-
nes Operanden nach links « <) oder nach rechts (> » verschieben.
Um wieviele Stellen die Bits eines Operanden verschoben werden sol-
len, gibt der rechte Operand dieser beiden Operatoren an.
Beispiel 19
a) Die Int-Variable links_var enthält den Wert 5. Durch die Anweisung
links.var = links_var < < 1;
HINWEIS: Die Addition im Dualsystem geht entsprechend den bekannten Regeln des De-
zimalsystems vonstatten:
0 + 0 = 0 Übertrag 0
0+1 = 1 Übertrag 0
1+0=1 Übertrag 0
1+1=0 Übertrag 1
3-22
wird der Inhalt der Variable links_var um 1 Bit nach links geschoben und eine Null nach-
gezogen, was einer Multiplikation mit 2 entspricht.
Das Links-Verschieben um 2 Bits würde einer Multiplikation mit 22 (also 4) entsprechen.
3 Bits links verschieben = Multiplikation mit 23 (8)
4 Bits links verschieben = Multiplikation mit 24 (16)
n Bits links verschieben = Multiplikation mit 2n
b) Soll mit der Shift-Operation < < multipliziert werden, dann ist darauf zu achten, daß
das Ergebnis noch im erlaubten Wertebereich liegt, da sonst Bits verloren gehen und das
Ergebnis falsch ist:
ain ()
<
int llnks.var, stell.zahl;
links var=763;
stell”zahi=6;
llnks_var=links_var<<5tell_zahl;
printf("Xd",links.var);
printf;
)
Dieses C-Programm versucht, durch Verschieben nach links eine Multiplikation durchzu-
führen, und zwar 763 ♦ 26 = 763 * 64 Das Ergebnis wird in links________var gespeichert und
am Bildschirm ausgegeben: -16704 (falsches Ergebnis, da diese Shift-Operation den er-
laubten Wertebereich überschreitet).
links__var < < stell__zahl
Durch Linksschieben um 6 Bits wird ins Vorzeichenbit eine 1 gebracht, die dort eine nega-
tive Zahl kennzeichnet. Zusätzlich geht diese 1 auch noch dem Betrag der Zahl verloren,
was zum falschen Ergebnis -16704 führt.
3-23
rechts____var > > 2
Der Wert der Variablen rechts_var wird um 2 Bits nach rechts verschoben.
Dabei ist zu unterscheiden, ob es sich bei rechts__var um eine unsigned- oder eine int-
Variable handelt:
Bei einer unsigned-Variablen zieht der Verschiebevorgang von links her 0 nach (logi-
scher Shift).
Ist das Vorzeichenbit beim Verschieben eines Int-Wertes gesetzt (negative Zahl), so füh-
ren die uns zur Verfügung stehenden Compiler (Digital Research DRC, INTEL C86, Lattice
LC, Microsoft MSC und QuickC, Borland TurboC) den '’arithmetic shift” aus. Falls Ihr Com-
puter den "logical shift” durchführt, Sie aber gerne mit '’arithmetic shift” arbeiten möch-
ten, können Sie das durch folgende Operationen erreichen:
- Zweier-Komplement-Bildung
- Verschiebung
- Einer-Komplement-Bildung
Beispiel: Das Bitmuster der Variablen zahl (-7) soll um 1 Stelle nach rechts verschoben
werden.
ain ()
< /• Binaert »/
int zahl -7| /• 11111111 11111001 • -7 i •/
zahl *zahl ♦ 1| /* 00000000 00000111 7 i 2ar Koapl.j wird erreicht »/
/• durch 1er Koapl ♦ 1| • /
/♦ auch aoogl.t zahl -zahl) •/
zahl zahl >> 1| /• 00000000 00000011 • 3 i Verschiebung • /
zahl • *zahl| /* 11111111 11111100 -4 : Einer-Koepleaent •/
printf("ld\n", zahl)।
>
Manche Compiler wie Small-C und die meisten 8-Bit-Compiler führen allerdings nur den
"logical shift" (0 nachgezogen) durch. Hierbei findet die Einer- und die Zweier-Komple-
ment-Bildung nicht statt.
Testen Sie Ihren Compiler mit dem nachfolgenden C-Programm:
Beispiel 20
main ()
(
int var_rechts, schieö_rechts. var_test;
var_rechtss-7;
schieo_rechts*var_rechts>>8;
pr intf"( “Xd schieb_rechts) ;
printf("\n*);
var_test=var_rechts&OxOOff;
printf("Zd“tvar test);
printf(*\n"J;
3-24
An der Bildschirmausgabe können Sie erkennen, wie Ihr System arbeitet:
ARITHMETIC SHIFT
-1
249
LOGICAL SHIFT
255
249
Beispiel 21
Das nachfolgende C-Programm erzeugt folgende Bildschirmausgabe:
-1
15
1
30
1
240
-241
Versuchen Sie, diese Ausgabe durch Nachvollziehen der einzelnen Anweisungen zu erklä-
ren.
main ()
(
int zahl, zahl_l, zahl_2, zahl_3;
zanl_ls1023;
zahl 2«-1;
zahlj3=14;
zahlszahl 1izahl 2;
printf("Xd“,zahl);
printf("\n");
zahl 2?zahl 1&OxOOOf;
printf("Xd"”zahl_2);
printf("Xn");
zahi_3=zahl_3*zahl_2;
printf("Xd",zahl 37;
printf("Xn”);
zahl 2 = zahl 2«zahl_3;
printf ("Xd\zahl_2) ;
printf("Xn");
zahl l=zahl&4zahl 1&&zahl 2&&zahl 3;
printf("Xd",zahlJ ;
printf("Xn");
zahl 2 = zahl 2«(zahl 1+2);
printf ("Xd"“,zahl_2)
printf("Xn");
printf("Xd","zahl_2);
printf("Xn");
3-25
Bei der Anweisung für einen Linksshift wären die Klammern um den Ausdruck
(zahl_1 + 2) nicht notwendig. Zum Vorrang der logischen Operatoren werden wir nun so-
fort kommen.
Die Prioritäten der bisher kennengelernten Operatoren sollen in einer
Tabelle zusammengefaßt werden:
() von links her
! --(Vorzeichen) von rechts her
* / % von links her
+ - von links her
<< >> von links her
<<=>> = von links her
— i _ — — • — von links her
& von links her
von links her
1 von links her
&& von links her
II von links her
s von rechts her
höchste
Priorität
w niedrigste
Priorität
3.5 ZUSAMMENGESETZTE ZUWEISUNGS-
OPERATOREN
In der Programmierpraxis werden häufig Anweisungen der folgenden
Form benötigt:
var__a = var__a + var_b;
Um Schreibarbeit (var_a muß zweimal geschrieben werden) zu spa-
ren, kann eine solche Anweisung in C auch wie folgt geschrieben wer-
den:
var__a+=var____b;
HINWEIS: "von links her" heißt, daß diese Operatoren bei gleicher Priorität von links her
abgearbeitet werden:
z. B.: 3« 12/4-36/4-9
"von rechts her" heißt, daß diese Operatoren von rechts her abgearbeitet
werden:
z. B.; - ~ 12-----------------------13----------------------- 13
zuerst Einerkomplement Negation des Vorzeichens
3-26
Ein solcher zusammengesetzter Zuweisungsoperator <op>=‘> kann
aus folgenden dyadischen Operatoren, also Operatoren, die 2 Operan-
den links und rechts von sich benötigen wie /, gebildet werden, wobei
für <op> der entsprechende Operator einzusetzen ist:
+-*/%<<>>& ~ I
Den rechts vom zusammengesetzten Operator angegebenen Ausdruck
sollten Sie sich geklammert vorstellen:
Eine Anweisung
links_var <op> = ausdruck;
entspricht dann
links_var = links_var <op> (ausdruck);
Diese Vorstellung ist wichtig, um die Reihenfolge der Auswertung
auf der rechten Seite richtig nachvollziehen zu können.
a) var_links * = var________rechts - 3;
entspricht
var_links = var_links *(var__rechts - 3);
und nicht
var_links = var_links ♦ var__rechts - 3;
b)
am ()
{
int var_1, var_2, vara;
var_1*4;
var” a*4;
varj2=2;
var_l<<=var_1 lvar_2; /• entspricht
var_l=var_l<<(var_llvar_2);
aber nicht
var_1= var_l«var_livar_2;
•/
printf (l,Xd" ,var_1);
q printf("Kn");
var a=va r _a«var_a l var_2;
printf("Xd“,var_ä);
printf cxn“);
)
HINWEIS: *)<op> steht für Operator
3-27
Dieses C-Programm würde
256
66
am Bildschirm ausgeben.
In der Anweisung (x) wird zunächst ein bitweises ODER aus var__1 und var_2
durchgeführt. Das Ergebnis dieser Operation ist 6. d. h. die Variable var_ 1 wird
um 6 Bits nach links geschiftet, was einer Multiplikation mit 64 enspricht (4 * 64 =
256).
In der Anweisung (o) wird zunächst der Inhalt von var_a um 4 (Inhalt von var_a) Bit
nach links geschoben (4*16 = 64) und dieses Ergebnis mit var__________2 (Inhalt : 2) über den
I-Operator verknüpft, woraus das Ergebnis 66 resultiert.
3.6 INKREMENT- UND DEKREMENT-
OPERATOREN
In Programmen werden sehr oft Zählvariable benötigt, auf die ständig 1
addiert (Inkrementiervorgang) oder von denen laufend 1 subtrahiert
(Dekrementiervorgang) werden muß. C bietet zu diesen beiden Opera-
tionen eigene Operatoren an:
Inkrementieren: + +
Dekrementieren: —
Beispiel 23
+ + zaehl___a
hat die Wirkung
zaehl__a = zaehl_a + 1
oder
zaehl_a += 1
— abzieh
hat die Wirkung
abzieh = abzieh - 1
oder
abzieh -= 1
Die beiden Operatoren ++ und — können auf der rechten Seite einer
Zuweisung verwendet werden; dabei ist allerdings zu beachten, ob sie
3-28
vor einem Operanden (Präfix-Schreibweise) oder hinter einem Ope-
randen (Postfix-Schreibweise) stehen:
Präfix-Schreibweise:
Hier erhalten diese Operatoren (++ und --) die höchste Priorität, d. h.
sie werden vor allen anderen Operatoren ausgeführt.
Beispiel 24
vor = + + praefix;
entspricht der Anweisungsfolge:
praefix = praefix + 1;
vor = praefix;
Hätte vor dieser Anweisung praefix den Wert 11. dann wäre nach dieser Anweisung in
praefix und vor der Wert 12 gespeichert.
Postfix-Schreibweise:
Beide Operatoren (++ und --) erhalten die niedrigste Priorität, d. h. sie
werden zu allerletzt (auch nach der Zuweisung) ausgeführt.
Beispiel 25
nach = postfix + +;
entspricht der Anweisungsfolge:
nach = postfix;
postfix = postfix + 1;
Besitzt postfix vor dieser Anweisung den Wert 11, dann wäre danach in der Variable
nach der Wert 11 und in der Variable postfix der Wert 12 gespeichert.
Beispiel 26
nain()
{
int var_1, var_2, var_a;
HINWEIS: Die Operatoren ++ und — dürfen nur auf Variable angewendet werden; nicht
erlaubt ist z. B.: x = (a + b) ++; oder x = 4 ++;
3-29
var_a=4;
var_2»2;
var_1=++var_2+var_a;
printf("Xd",var_1);
printf(“Xn");
printf("Xd",var_2);
printf("Xn*);
var_2—;
var_a«=--var_2;
printf("Xd", var_1);
printf("Xn");
printf (“Xd",var_2);
printf("\n*);
printf(“Xd" ,var_a);
printf l "Xn");
Dieses C-Programm würde folgendes am Bildschirm ausgeben:
7
3
7
1
4
Die gleiche Ausgabe am Bildschirm liefert auch das nachfolgende Programm:
main()
<
int var_l, var_2, vara;
var_1=4;
var_a=4;
varj2=2;
var_2* = 1;
var”1=var_2+var_a;
printf("Xd",var_1);
printf("Xn");
printf(“Xd",var_2);
printfCXn");
var_2=var_2-1;
var_2-=1;
varja*=var_2;
printf(“Xd",var_1);
printf("\n");
printf("Xd",var_2);
printf("\n");
printf("Xd",var_a);
printfCXn");
>
3-30
SYMBOLISCHE KONSTANTEN
(tt define)
4-1
SYMBOLISCHE KONSTANTEN A
(8 define) 4
C sieht die Möglichkeit vor, an Konstanten Namen zu vergeben.
Dies hat mehrere Gründe:
Nehmen wir an. Sie sollen ein mathematisches Programm erstellen, in
dem die Zahl n immer mit neun Stellen Genauigkeit nach dem Komma
verwendet werden muß. Sie müßten dann bei jedem Vorkommen von n
die Zahlenschlange 3.141592654 eintippen.
Symbolische Konstanten erlauben, einen Zahlenwert am Anfang Ihres
Programms einmalig anzugeben und ihm über 8define einen symboli-
schen Namen zuzuordnen, den Sie dann im gesamten Programm ver-
wenden können.
«aefine PI J.14159Z654
main ()
Float radius» umfang, flaeche;
printf("Beden Sie den Radius ein \n"i;
scanft' Xf“,iradius); /• Es wird hier ueber Bildschirm eine
float- Zahl in die variable
radius' eingelesen.
Dass es sich bei der eingegebenen Zahl
um eine float- Zahl handelt, wird
durch das Kontrollzeichen Xf
festgelegt.
Zum ^-Zeichen vor der Variable radius
werden wir an spaeterer Stelle noch kommen • /
flaeche = radius«radius«PI;
umfang - Z*radius*Pl;
printf("\n\nDer Umfang eines Kreises mit dem angegebenen Radius ist:\n"i;
printf("Xf".umfang);
printf("\n\n");
printf("\n\nDie Flaeche eines Kreises mit dem angegebenen Radius ist:\n");
printf("Xf“,flaeche);
printf("\n“);
4-3
Dieses C-Programm könnte folgenden Bildschirmausdruck ergeben:
Geben Sie einen Radius ein:
4.23 —1 (Eingabe)
Der Umfang des Kreises mit dem angegebenen Radius ist:
26.577873
Die Fläche des Kreises mit dem angegebenen Radius ist:
56.212204
Es wird also überall im Programm für PI der Wert 3.141592654 eingesetzt.
Symbolische Konstanten sollten Sie in Großbuchstaben angeben, um
sie leichter von Variabiennamen, die in Kleinbuchstaben anzugeben
sind, zu unterscheiden.
Eine ttdefine-Angabe wird nicht mit Semikolon abgeschlossen.*)
Für die Wahl von symbolischen Konstanten gelten die gleichen Regeln
wie für die Wahl von Variabiennamen (siehe 2.3).
Neben der leichteren Lesbarkeit und Arbeitsersparnis erhalten Sie
durch die Verwendung von symbolischen Konstanten auch leicht änder-
bare Programme:
Sie haben beispielsweise ein umfangreiches Rechnungspro-
gramm geschrieben, das die aktuelle Mehrwertsteuer enthält,
und am Anfang Ihres C-Programms folgendes angegeben:
ttdefine MEHRWERTSTEUER 14
Hier müssen Sie bei einer Änderung der Mehrwertsteuer ledig-
lich diese eine Konstante ändern, und Ihr Programm ist wieder
auf aktuellem Stand; andernfalls müßten Sie das gesamte Pro-
gramm durchsuchen und die entsprechenden Stellen ändern.
Sie sehen, daß die Verwendung von symbolischen Konstanten Ihre C-
Programme übersichtlicher, transparenter und flexibler gestaltet.
HINWEIS: *)ttdefine muß, wie alle anderen Präprozessor-Anweisungen, in der 1. Spalte
beginnen.
4-4
ZEIGER
5-1
ZEIGER 5
Nehmen wir an, Sie sagen im Büro: "Geben Sie mir bitte einen Bleistift!"
und zeigen dabei mit dem Finger auf ein Schreibtischfach mit der Be-
schriftung "Stifte".
An früherer Stelle hatten wir festgestellt, daß eine Variable einer Be-
schriftung oder symbolischen Adresse eines Speicherplatzes gleich-
kommt. Nun gehen wir noch eine Stufe höher und führen Zeiger ein.
5-3
Zeiger enthalten nicht Werte wie Variablen, sondern zeigen auf Varia-
ble; da im Rechner Variable den Adressen von Speicherplätzen ent-
sprechen, enthalten Zeiger keine Werte, sondern Adressen.
Im folgenden wollen wir bildlich arbeiten:
5-4
Das Beispiel mit den Schreibtischschubladen könnte also wie folgt dar-
gestellt werden:
5-5
Nun greift die Sekretärin in das Fach mit der Adresse "Stifte" und nimmt
einen Bleistift.
Sie möchten aber auch noch ein Lineal und zeigen daher mit Ihrem FIN-
GER auch auf das Fach mit der Beschriftung "Zeichenmateriar.
Bildlich könnte dieser Vorgang nun so festgehalten werden:
5-6
In C werden Zeiger bei der Deklaration durch Voranstellen eines * vor
den Zeigernamen*) gekennzeichnet:
Beispiel 1
int «zeig___1;
Der Zeiger zeig_1 kann auf int-Variable zeigen.
double «zeig_____2, «zeig___3;
Die Zeiger zeig_2 und zeig_3 können auf double-Variable zeigen.
char «buch____zeig;
Der Zeiger buch_zeig kann auf char-Variable zeigen.
Im Programm sind Zeigernamen ohne vorangestellten * anzugeben,
wenn sie als Zeiger dienen sollen.
Sollen Zeigernamen aber die Funktion von Variablen übernehmen, so
ist ein * voranzustellen.
Der vorangestellte Adreßoperator & liefert die Adresse eines Objekts.
Durch die Anweisung
zeig___1 = & var_1;
wird bewirkt, daß der Zeiger zeig_____1 auf die Variable var____1 zeigt,
d. h., daß in zeig_1 die Adresse von var___1 gespeichert wird.
Bildlich kann man sich das auch so vorstellen:
zeig__1 enhält die
Adresse von ver_1:
zeig__1 zeigt also
auf ver_ 1.
HINWEIS: *)Für die Wahl von Zeigernamen gelten die gleichen Regeln wie für die Wahl
von Variabiennamen (siehe 2.3)
5-7
Der Adreßoperator & darf nur auf Variable angewendet werden;
&(var__1 + 17) oder &9 wären nicht erlaubt.
Ein Voranstellen des Verweisoperators * vor eine Zeigervariable er-
laubt, den Wert einer Variablen, auf die ein Zeiger momentan zeigt, zu
manipulieren.
Wenn der Zeiger zeig_1 momentan auf die Variable var_1 zeigt und
var__1 den Wert 4 enthalte, dann bewirkt die Anweisung:
var__2 = «zeig___1;
die Zuweisung des Wertes von var_1 an var_2.
Die Anweisung
*zeig_1 = 7;
bewirkt die Zuweisung des Wertes 7 an die Variable var_1 (nicht an
zeig__1), dazeig_1 auf var_1 zeigt.
♦zeig 1 entspricht also
immer der Variablen, auf
die zeig_1 momentan
zeigt.
5-8
Beispiel 2
Unter PC DOS (Lattice Compiler) muß das folgende Programm mit der Option -a compiliert
werden. Wurde dieses Programm in einer Datei zeiger.c abgelegt, dann ist der Compiler
wie folgt aufzurufen:
Ic1 zeiger.c-a
int ganz_1, ganz_2, •zeig_ganz;
char buchja, buchjb, •zeigjbuch;
ganz_1*-7;
buchJa=’x';
ganzj2=4;
zeig_ganz=&ganz_2;
ganz_2*=*zeig_ganz*l;
printf("Xd",ganz_2);
printf(“Xn");
zeig_buch=4buch_a;
buch b=*zeig buch;
printf("XcXn",buch b); /• Xc gibt an, dass es sich beim Inhalt von buch_D
um ein einzelnes Zeichen <char) handelt •/
• zeig ganz = *zeig_ganz+ganz_1;
printf(*Xd",•zeig_ganz);
pr intf(“Xn");
ganz 2=ganz 1* • zelg_ganz*1;
printf (•Xd"",ganz_2);
printf(“Xn");
(♦zeig ganz)**;
printfl"Xd",*ze1g_ganz);
printf("xn“);
Dieses C-Programm würde am Bildschirm folgendes ausgeben:
9
X
2
-13
-12
Wir wollen die Entstehung dieser Bildschirmausgabe auf den nächsten Seiten bildlich
nachvollziehen.
ain()
<
int ganz.l, ganz_2, •zeig_ganz;
char buchja, buch_b, •zeig_buch;
ganz 1«-7;
ouchJa-X ;
ganzj2=4;
zeig_ganz=&ganz_2;
5-9
-
Zeiger-
ebene
Variabien-
ebene
Werte-
ebene
ganz_2*=*zeig_ganz+l ;
printf("Xd"»ganz 2) ;
printfl"\n");
zeig_DuchsiDucn_a;
= ganz_2 = ganz 2 + ganz 2 + 1; (J)
Da nach der Operation (T) unter der Variablen ganz_____2 der Wert 9 gespeichert ist,
wird dieser Wert auch als erstes am Bildschirm ausgegeben.
buch_b-*zeig_buch; Clj*
printf ("XcXn", buch_b) ;
Xc gibt an» dass es sich
um ein einzelnes Zeichen
beim Inhalt von buch b
(char) handelt " •/
• zeig_ganz = *zeig_ganz+ganz_1;
printf("Xd",»zeig ganz);
printf("Xn") ;
= ganz 2 = ganz_2 + ganz 1; (2)
HINWEIS: Die beiden Vorzeichenoperatoren * und & haben höheren Vorrang als die
arithmetischen Operatoren.
5-10
Zeiger-
ebene
@(9-7)
Vanablen-
ebene
Werte-
ebene
durch diesen Programmteil wird also am Bildschirm
2
ausgegeben.
ganz_2 =ganz_t• •zeig.ganz+i;
printf("Xd",ganz_2);
printf("Xn");
= ganz_2 = ganz_1 * ganz_2 + 1;
Zeiger-
ebene
Vanablen-
ebene
Werte-
ebene
Dieser Programmteil würde also
-13
am Bildschirm ausgeben.
(•zeig ganz)**;
printf("Xd“.♦zelg.ganzi;
printf("Xn“);
= ganz_2 = ganz_2 + 1;
5-11
Zeiger-
ebene
Vanablen-
ebene
Werte-
ebene
Durch den letzten Programmteil würde also
-12
am Bildschirm ausgegeben.
Beispiel 3
Unter PC DOS (Lattice Compiler) muß auch das folgende Programm mit der Option -a
compiliert werden; außerdem ist die Reihenfolge der beiden gekennzeichneten (x) Anwei-
sungen umzukehren.
wain ()
(
int zahlet, zahl_2, zahl_3, «zeiger;
zahl 1=5;
zahl”2 = 9;
zeiger=&zahl_3;
•zeigerszahl 1+zanl 2;
printf("Xd*,zahl 3)^
printf ("Xn");
zeiger=&zahl 1; (x)
zahl_i =zanij2«zahl 3; (x)
printf("Xd",«zeiger);
printf (•\n*,l;
zelgpr=&zahl_2;
printf(“Xd",«zeiger);
printf("\n");
Dieses C-Programm würde
14
126
9
am Bildschirm ausgeben.
5-12
ain()
(
int
zahl_!, zahl_2, zahl_3, »zeigen;
zahl 1=5;
zahl_Z=9;
ze1gen=4zanl_3;
•zeigen=zahl 1+zahl 2;
printf<"Xd“»zahl 3);
printf(•Xn*);
= zahl__3 = zahl_1 + zahl_2;
Dieser Programmteil würde
14
am Bildschirm ausgeben, da *zeiger der Variablen zahl____3 entspricht, zeiger zeigt mo-
mentan auf die Variable zahl_____________________________3.
zeiger=4zahl_1;
zahl!=zahl_2»zahl_3;
printf i Xd\»zeigen);
printf("Xn");
5-13
Dieser Programmteil würde also
126
am Bildschirm ausgeben, da zeiger zum Zeitpunkt der Ausgabe auf die Variable zahl_1
zeigt und diese den Wert 126 enthält.
zeiger=&zahl_2;
printf("X0M,»zeiger);
printf("\n");
Zerger-
ebene
Variabien-
ebene
Werte-
ebene
Dieser Programmteil gibt
9
am Bildschirm aus, da zeiger zum Zeitpunkt der Ausgabe die Variable zahl_________2 zeigt und
diese den Wert 9 enthält.
5-14
Die Anweisungsfolge:
zeiger = & var_1; (1)
var_2 = «zeiger; (2)
entspricht der Anweisung:
var_2 = var___1;
Zeiger können wie Variable manipuliert werden:
zeig___1 = zeig_2;
Nach dieser Anweisung zeigt zeig_1 auf die gleiche Variable wie zeig_2.
Vor dieser Anweisung:
5-15
Beispiel 4
«ain()
<
char zeicha, zeicnb, »zeigi, »zeigZ;
zeichb« v ;
zeicha« a ;
zeig!«Azeicna;
zei g Z«4zeichb;
printf ("Xc",»zeigl);
printf(“Xc",«zeigZ);
•zeigZ« e;
printfl"Xc",zeichb);
printf(“Sn");
zeigt«zeigZ;
printf(“Xc“,»zeigt);
♦zeigt = ' V ;
printf("Xc“,«zeig2);
zeigZ«&zeicha;
zeigt«zeigZ;
printf("Xc“,»zeiglI;
printf(“\n“>;
Dieses C-Programm würde
ave
eva
am Bildschirm ausgeben. Den Programmablauf wollen wir wieder bildlich nachvollziehen.
char zeicha, zeichb, «zeigl, »zeigZ;
zeichb« V
zeigls&zeicha;
zeigz=&zeichp;
printf("Xc
zeig2
zeichb
(*zeig2)
Werte-
ebene
Zeiger-
ebene
Vanablen-
ebene
5-16
Dieser Programmteil würde also
av
am Bildschirm ausgeben.
•zeig2= e ;
printf(•Xc",zeichb);
printf("\n"> ;
Zeiger-
ebene
Variabien-
ebene
Werte-
ebene
Dieser Programmteil gibt in der gleichen Zeile des Bildschirms das Zeichen
e
aus und erzeugt einen Zeilenvorschub \n.
zeigl-zeig2;
printf exc",»zeig!);
’a’
Zeiger-
ebene
(•zeig'!)
zeichb
(•zeig2)
Variabien-
ebene
Werte-
ebene
Die Zuweisung
zeig! = zeig2;
bewirkt, daß die Adresse in zeig2 auch in zeigl gespeichert wird, wodurch die beste-
hende Verzeigerung von zeigl aufgehoben wird und zeigl auf die gleiche Variable, in un-
serem Fall die Variable zeichb, wie zeig2 zeigt.
5-17
Dieser Programmteil würde also das Zeichen
e
am Bildschirm ausgeben.
•zeig!« v ‘ ;
printf("Xc" ,*zeig2);
Zeiger-
ebene
Variablen-
Werte-
ebene
Mit der Anweisung *zeig1 = V; wird in der Variablen zeichb das Zeichen v abgelegt, da
zeigl auf diese Variable zeigt zeig2 zeigt ebenfalls auf die Variable zeichb, so daß das
Zeichen
V
am Bildschirm ausgegeben wird.
zeig2=Szeicha; (T)
zeiglszeigZ;
printf("Xc",»zeigt);
printf (“Xn11) ;
Zeicher-
ebene
Variabien-
ebene
Werte-
ebene
5-18
Anweisung (7) bewirkt, daß zeig2 auf die Variable zeicha zeigt. Anweisung (2) hat
dann zur Folge, daß auch zeigl auf die gleiche Variable zeicha zeigt. Dieser Programm-
teil würde also das Zeichen
a
am Bildschirm ausgeben und einen Zeilenvorschub erzeugen.
5-19
EIN- UND AUSGABE
6-1
EIN- UND AUSGABE 6
6.1 DIE ANWEISUNG 8 i n c I u d e
Jeder C-Compiler verfügt über eine Bibliothek, in der Standardfunktio-
nen hinterlegt sind.
Als eine der Funktionen haben wir bereits mainf) kennengelernt. Aus
dieser Funktion können wir lernen, daß jede Funktion vereinfacht fol-
gendermaßen aufgebaut ist:
Funktionskopf, evtl. Parameter, z. B. mainf)
Funktionsrumpf I
Der Anfang des Funktionsrumpfs wird durch { und das Ende durch } ge-
kennzeichnet.
Nun haben wir aber in den vorangehenden Programmbeispielen Funk-
tionsaufrufe wie printff) verwendet, zu denen der Funktionsrumpf fehl-
te.
Diese Funktionen sind in einer Bibliothek hinterlegt, in der sich der C-
Binder die zum entsprechenden Funktionsaufruf gehörige Funktion
sucht.
Sie können also z. B. von der Funktion printff) Gebrauch machen und
Bildschirmausgaben veranlassen, obwohl Sie den dazugehörigen Pro-
grammteil (Funktion) nicht selbst programmiert haben.
Da die Sprache C die unterschiedlichsten Funktionen kennt, verfügen
die meisten C-Systeme über eine Standard-Bibliothek dieser Funktio-
nen.
Diese Standard-Bibliothek ist im Prinzip eine Datei, die den bindbaren
Objekt-Code dieser Funktionen enthält.
Jedes C-Programm, das eine Funktion aus der Standard-Bibliothek ver-
wendet, sollte möglichst am Anfang folgende Zeile enthalten:
ttinclude <stdio.h> # include ist eine der Präprozessor-Anweisungen,
die immer mit # in der ersten Spalte beginnen.
Die Datei stdio.h definiert bestimmte Makros (dazu später) und Varia-
blen. die in der Standard-Bibliothek verwendet werden. Die Definitionen
6-3
in stdio.h werden für die Standard-Ein/Ausgabe (Standard Input/out-
put) benötigt.
Mit ttinclude <stdio.h> veranlassen Sie den Compiler, die Datei
stdio.h in Ihr Programm zu übernehmen.
Nachdem der Compiler diese Anweisung verarbeitet hat, ist die Datei
stdio.h Bestandteil Ihres Programms.
Mit dieser Anweisung können Sie auch selbstentworfene C-Programme
in einem neuem C-Programm benutzen: Haben Sie z. B. ein Programm
mit Namen steuer.c geschrieben, können Sie mit
ttinclude "steuer.c”
von diesem Programm in Ihrem neuen Programm Gebrauch machen,
steuer.c ist in diesem Fall ein Copy-Element, das mit ttinclude an
jede Stelle Ihres Programms kopiert werden kann.
6.2 EIN- UND AUSGABE EINES ZEICHENS:
getchar() UND putchar()
getchar und putchar ermöglichen die Ein- bzw. Ausgabe eines Zei-
chens über das Terminal.
getchar()
Eingabe eines Zeichens von der Standardeingabeeinheit
(normalerweise Terminal)
putchar()
Ausgabe eines Zeichens auf der Standardausgabeeinheit
(normalerweise Terminal)
Diese beiden Funktionen sind bei den meisten Compilern in der Datei
stdio.h definiert. Verwenden Sie also in einem C-Programm diese
Funktionen, so müssen Sie dort auch die Anweisung
ttinclude <stdio.h>
angeben.
HINWEIS: Die Klammerung eines Programmnamens mit <............> veranlaßt den Com-
piler, an fest gegebenen Stellen (Directories) nach diesen Dateinamen zu su-
chen.
6-4
f* Da« Programm liest »in Zeichen von der
Tastatur und gibt es am Bildschirm aus. */
•include <stdio.h>
main ()
<
char zeichenj
Zeichen • getchar();
putchar(Zeichen)|
Ist das Programm übersetzt, gebunden und gestartet, so wird bei Eingabe eines Zeichens,
z. B. ’M” und RETURN, am Bildschirm folgendes erscheinen:
* ”M” bei Systemen mit "gepufferter” Eingabe,
* ”MM” bei Systemen ohne Eingabepuffer.
Auch die Tatsache, daß die Funktion getchar() als Ergebnis einen int-Wert (!) liefert,
führt oft zu unerwünschten und nicht erwarteten Ergebnissen.
Ein weiteres Problem tritt bei Systemen mit gepufferter Eingabe auf. Hierzu werden wir un-
ser letztes Beispiel verändern:
/• Da« Programm liest ein Zeichen von der
Tastatur und gibt es am Bildschirm aus. •/
•include <»tdio.h>
main <)
<
char Zeichen.lt zexchen.2|
Zeichen.! getcharOj
putchar(zexchen.l);
if (Zeichen.! ’j’)
C
zeichen_2 getcharOj
putchar(zeichen_2)।
Ist das Programm gestartet, wartet das System auf eine Eingabe. Wird nun ein Zeichen
eingegeben (”]”), dann ist es erforderlich, auch RETURN zu betätigen, um die Übergabe
des Pufferinhaltes an die Funktion getcharf) zu veranlassen. Im Puffer befinden sich nun
2 Zeichen: das Zeichen ”j” und CR (RETURN, ODh). Das erste Zeichen (”j”) wird über
getcharf) an Zeichen___1 zugewiesen, im Puffer bleibt der Rest: CR.
Beim nächsten Aufruf der Funktion getcharf) und falls Zeichen _1= = ’j’, wird der Inhalt
des Puffers (CR = ODh) sofort an getchar() übergeben, da CR die Übergabe bewirkt. An
Zeichen___2 wird somit immer das Zeichen CR übergeben.
Soll das verhindert werden, dann ist:
- ein "dummy” getchar( )-Aufruf einzufügen: getcharfoder
- die Pufferung auszuschalten (UNIX: stty cbreak).
6-5
Wir wollen nun ein C-Programm entwerfen, das 6 Buchstaben über den
Bildschirm einliest und dann die ersten 3 Buchstaben in Großbuchsta-
ben und die nächsten 3 Buchstaben in Kleinbuchstaben am Bildschirm
ausgibt.
Für die Umwandlung von Groß- in Kleinbuchstaben und umgekehrt bie-
tet C vordefinierte Makros an:
tolower()
verwandelt einen Buchstaben in einen Kleinbuchstaben.
toupper()
verwandelt einen Buchstaben in einen Großbuchstaben.
Diese beiden "Funktionen” sind in der Datei ctype.h definiert. Wir be-
nötigen in unserem C-Programm also eine Anweisung:
tiinclude <ctype.h>
Beispiel 1
«include <stdio.h>
tinclude <ctype.h>
main ()
(
char zeich_i, zeich_2;
zeich_i=getchar();
zeichj2=toupper(zeich_l); /♦ Diese beiden Anweisungen kann man auch ♦/
putchär(zeich_?); " /• zusammenfassen: putchar(toupper(zeich_l))•/
zeich_lsgetcharf);
putchär(toupper(zeich_1)1;
zeich_lzgetchar();
putchär(toupper(zeich_i));
zeich_1xgetchar();
putchär(tolower(zeich_1)1;
zeich 1=getchar();
putchär(tolower(zeich_1));
zeich_1sgetchar(1;
putchär(tolower(zeich 1));
>
Nehmen wir an, Sie geben über die Tastatur folgendes ein:
Zimmer*-1 (Dieses Zeichen soll dem Drücken der RETURN- bzw. ENTER-Taste
entsprechen)
Diese Eingabe liefert dann folgende Bildschirmausgabe:
ZIMmer
6-6
Weitere Bildschirmdialoge:
Eingabe:
Ausgabe:
Eingabe:
Ausgabe:
Eingabe:
Ausgabe:
wEtTeR
WETter
ordNER—1
ORDner
kindER —1
KINDER
Bei tolower() und toupperf) handelt es sich eigentlich nicht um
Funktionen, sondern um sogenannte Makros.
Unter Makro versteht man eine Folge von Einzelbefehlen, die un-
ter einem Namen angesprochen werden können. Soll nun diese
Folge von Einzelbefehlen ausgeführt werden, dann ist lediglich der
Name anzugeben, der diese Befehlsfolge repräsentiert. Man
spricht auch von einem Makroaufruf. Ein Makroaufruf hat das Ko-
pieren der entsprechenden Befehle zur Folge.
Das zuletzt angegebene Programm wollen wir jetzt mit selbstdefinierten
Makros, d. h. ohne tolower() und toupper(), schreiben. Dazu benöti-
gen wir die Tabelle des ASCII-Codes (siehe Anhang).
Ausschnitt aus der ASCII-Tabelle:
Zeichen dezimal hexadezimal dual
A 65 41 0100 0001
Z 90 5A 0101 1010
a 97 61 0110 0001
Z 122 7A 0111 1010
Wenn wir uns diese Zeichen genauer anschauen, bemerken wir, daß
sich die entsprechenden Groß- und Kleinbuchstaben bei der Dualdar-
stellung nur in 1 Bit unterscheiden, und zwar im Bit mit der Nummer 5:
6-7
Diese Erkenntnis können wir uns für unser C-Programm zunutze ma-
chen; wollen wir ein Zeichen als Großbuchstaben darstellen, so müssen
wir seinen ASCII-Code über &, d. h. bitweises UND, mit dem Binärmu-
ster 1101 1111 oder Hexadezimalmuster OXdf verknüpfen. Wollen
wir ein Zeichen als Kleinbuchstaben darstellen, so müssen wir es über
I, d. h. bitweises ODER, mit dem Bitmuster 0010 0000 oder dem
Hexadezimalmuster 0x20 verknüpfen.
Beispiel 2
a) Umwandlung in Kleinbuchstaben
(Soll ein Kleinbuchstabe in einen Kleinbuchstaben umgewandelt werden, so verändert
sich durch diese Verknüpfung das ursprüngliche Bitmuster nicht.)
6-8
b) Umwandlung in Großbuchstaben
m = 0 1 1 0 1 .1 0 1
Oxdf = 1 1 0 1 1 1 1 1
m & Oxdf= 0 1 0 0 1 1 0 1 = M
R = 0 1 0 1 0 0 1 0
Oxdf = 1 1 0 1 1 1 1 1
R &Oxdf = 0 1 0 1 0 0 1 0 = R
(Soll ein Großbuchstabe in einen Großbuchstaben umgewandelt werden, so verändert sich
durch diese Verknüpfung das ursprüngliche Bitmuster nicht.)
Die soeben besprochenen Umwandlungen wollen wir in einer eigenen
Date! selbst.h als Makros definieren. Bei der Definition eines Makros
können auch "Platzhalter”, sogenannte formale Parameter angege-
ben werden.
Makrodefinition:
parksuender (X, Y, Z)
Das Auto mit der Nummer X wurde falsch geparkt.
Der Autobesitzer Y wird bestraft.
Die Strafe beträgt Z DM.
Makroaufruf (mit aktuellen Parametern)
parksuender (ZY-LL-43, MEYER-HANS, 40)
Es werden nun für
die formalen Parameter
X
Y
Z
die aktuellen Parameter
ZY-LL-43
MEYER-HANS
40
eingesetzt.
Dieser Makroaufruf würde also folgendem Text entsprechen:
Das Auto mit der Nummer ZY-LL-43 wurde falsch geparkt.
Der Autobesitzer MEYER-HANS wird bestraft.
Die Strafe beträgt 40 DM.
6-9
Der Makroaufruf parksuender (EE-IL-1, ZORN-HANS, 20) würde den gleichen Text
ausgeben, nur würde anstelle von X hier EE-IL-1, anstelle von Y hier ZORN-HANS und
anstelle von Z hier 20 eingesetzt.
Makros werden mit
ttdefine makroname (paraml, param2,...) (makrobeffehl(e))
definiert. Unsere Datei selbst.h könnte also folgenden Inhalt besitzen:
/• SELBST DEFINIERTE HAKROS •/
«define kletn(c) (c!0x20)
/• klein s Makroname ♦/
/• c = formaler Parameter ♦/
/♦ Befehl, der durcn diesen Makro- ♦/
/♦ aufruf ausgefuehrt wird, ist */
/♦ dahinter geklammert angegeben ♦/
idefine gross(c) (cÄOxdf)
Das Programm zur Umwandlung von Klein- in Großbuchstaben und um-
gekehrt hätte dann folgendes Aussehen:
ftinclude <stdlo.h>
«include "selbst.h" /♦ selbst.h steht in der benutzereigenen Bibliothek •/
maln ()
char zeichl, zeich_2;
zei ch_1 =getchar();
zei ch_2 = gross(zeich_1); /• Diese beiden Anweisungen kann man auch ♦/
putcnartzeich_2); /• zusammenfassen: putchar(gross(zeich 1)) */
/♦ noch kompakter waere die erlaubte “ •/
/• Konstruktion: putcnar(gross(getchar())) •/
zeich_l-getchar();
putchär(gross(zelch_1)) ;
zelch_!^getchar ();
putchär(gross(zelch_11);
zeich-1sgetchar();
putchär(klein(zelch_11) ;
zeichj =getchar();
putchär(klein(zeich.1));
zeich_l=getchar();
putchär(klein(zeich 1));
)
Dieses C-Programm leistet das gleiche, wie das zuletzt angegebene
Programmbeispiel mit den Makros toupper und tolower.
6-10
Beispiel 4
Um etwas mit den Makros zu arbeiten, wollen wir ein Programm schreiben, das die 3 Sei-
ten eines Quaders einliest, dann Volumen und Oberfläche berechnet, und diese Werte
ausgibt:
iinclude "selbst.h*
•am ()
float quad.flaech, hoehe, breite, laeng;
printf("Geben Sie die Breite des Quaders ein\n");
scanf("Xf",ibreite);
printf("Geben Sie die Hoehe des Quaders etn\n");
scanf("Xf".ihoehe);
printf("Geben Sie die Laenge des Quaders eln\n");
scanf("Xf",&laeng);
printf(*\n\n\n*);
printf("Das Volumen dieses Quaders ist: “);
printf("X7.3f",volumen(laeng,hoehe,breite));
printf("\n\n");
printfCDie Flaeche dieses Quaders ist: *);
quad flaech»2*(flaeche(bre1te,hoehe)+flaeche(breite,laeng)+flaeche(hoehe,laeng));
printf("X7.3f*,quad_flaech);
printf("\n");
Unsere Datei selbst.h, die mit der ttinclude-Anweisung in unser Programm übernom-
men wurde, hat nun folgendes Aussehen:
SELBST DEFINIERTE MAKROS
idefine
klemtc) (00x20)
klein = Makroname •/
c = formaler Parameter •/
Befehl, der durch diesen Makro- •/
aufruf ausgefuehrt wird, ist •/
dahinter geklamMert angegeben */
idefine
grösste) (c&Oxdf)
idefine
volumen(a,b,c) (a*b»c)
idefine
flaeche(x,y) (x«y)
Diese Datei wurde um die beiden Makros
volumen (3 formale Parameter)
flaeche (2 formale Parameter)
erweitert.
Unser C-Programm könnte folgendermaßen ablaufen:
Geben Sie die Breite des Quaders ein
2.3 —1 (Eingabe}
Geben Sie die Hoehe des Quaders ein
2.8 —1 (Eingabe)
6-11
Geben Sie die Laenge des Quaders ein
3.5—1 (Eingabe)
Das Volumen dieses Quaders ist: 22.540
Die Flaeche dieses Quaders ist: 48.580
Dieses Programm wollen wir nun etwas näher betrachten:
»include "selbst.h"
mainf)
C
float quad_flaech, hoehe, breite, laeng;
printf("Geben Sie die Breite des Quaders eln\n");
scanf(“Xf*,&breite);
printf("Geben Sie die Hoehe des Quaders ein\n");
scanf("Xf",&hoehe);
printf(“Geben Sie die Laenge des Quaders ein\n“);
scanf("Xf",&laeng);
printf("\n\n\n");
Mit der Funktion scanff) können Werte über den Bildschirm eingelesen
werden.
%f gibt an, daß es sich bei der Eingabe um eine float-Zahl handelt.
Die eingegebene float-Zahl wird dann in der entsprechenden Varia-
blen, die nach dem Komma angegeben ist, gespeichert. In obigem Bei-
spiel wird die erste eingegebene Zahl 2.3 in der float-Variablen breite
gespeichert.
Diese Variable in der Funktion scanff) ist als Zeiger anzugeben (z. B.
ftbreite). Warum hier Zeiger anzugeben sind, werden wir bald genauer
behandeln.
printf("Das Volumen dieses Quaders ist: ");
printf("X7,3f",volumen(laeng,hoehe,breite));
printf("\n\n");
Das %7.3ff in der Funktion printff) besagt, daß eine float-Zahl mit ins-
gesamt mindestens 7 Stellen, davon 3 Stellen hinter dem Punkt, auszu-
geben ist.
Welcher float-Wert nun auszugeben ist, wird durch den Makroaufruf
volumen (laeng, hoehe, breite) festgelegt.
Der Makro volumen wurde in der Datei selbst.h definiert:
ttdefine volumen (a, b, c) (a * b * c)
6-12
Für die formalen Parameter werden nun bei diesem Makroaufruf die
entsprechenden aktuellen Parameter eingesetzt:
a <- laeng
b «- hoehe
c «- breite
Es wird also der float-Wert am Bildschirm ausgegeben. Die Eingabe-
werte wurden aus dem Beispiel übernommen.
laeng * hoehe * breite
l 1 1
3.5 * 2.8 * 2.3 = 22.54
Dieser Wert wird aber folgendermaßen ausgegeben:
l—122.540
I -------
Leerzeichen 3 Stellen nach dem Punkt
insgesamt 7 Stellen (einschließlich •)
prlntfCDie Flaeche dieses Quaders ist: •);
quad_flaech«2M flaeche(brei te,hoehe)+flaeche(tjrelte,laeng)♦flaeche(hoehe,laeng));
printf<"X7.3f',quad flaech);
printf;
Hier wird der Variablen quad_____flaech (float-variable) ein float-Wert
zugewiesen. Dieser float-Wert muß allerdings erst berechnet werden;
dazu wird der Makro flaeche aufgerufen.
Das Makro flaeche wurde in der Datei selbst.h definiert:
ttdefine flaeche (x, y) (x * y)
Für die formalen Parameter werden nun bei den einzelnen Makroaufru-
fen folgende aktuellen Parameter eingesetzt:
x y
'L *
flaeche (breite, hoehe)
flaeche (breite, laeng)
flaeche (hoehe, laeng)
: 2.3*2.8 = 6.44
: 2.3*3.5 = 8.05
: 2.8 *3.5 = 9.8
In der float-Variable quad-flaech wird dann der Wert 48.58 - 2 *
(6.44 + 8.05 + 9.8) gespeichert. Dieser Wert wird aber folgendermaßen
am Bildschirm ausgegeben:
6-13
48.580
/ —
Leerzeichen 3 Stellen nach dem Punkt
insgesamt 7 Stellen (einschließlich •)
Eine solche Ausgabe wird durch %7.3f bewirkt.
6.3 DIE AUSGABE MIT printf
printf wurde in den vorhergehenden Beispielen schon oft benutzt. Hier
soll nun eine vollständige Beschreibung dieser Funktion gegeben wer-
den.
printff’kontrollzeichenkette”, argumentl, argument2,...)
Die kontrollzeichenkette gibt an, wie die einzelnen Argumente auszu-
geben sind. In der kontrollzeichenkette können sowohl normale ASCII-
Zeichen, die einfach ausgegeben werden, als auch folgende spezielle
Steuerzeichen enthalten sein:
\O - Null (keine druckbare Null, sondern 0X00)
V = einfaches Anführungszeichen
\ \ - backslash
\b - backspace (ein Zeichen zurück positionieren)
\f -Seitenvorschub
\n = neue Zeile
\r - Carriage Return (oder Enter)
\t - Tabulator (Voreingestellte Spaltenposition: 1,9,17,20)
\xxx - oktale Ziffernkombination
Beispiel: printf(”\O12”) würde einen Zeileinvorschub bewirken, da
der ASCII-Code für einen Zeilenvorschub 00 001 010 ist, was
oktal der Ziffernkombination 012 entspricht.
Die Werte der Argumente können unterschiedlich, entsprechend den
Vorgaben in der kontrollzeichenkette, z. B. als Oktalzahl oder Dezimal-
zahl ausgegeben werden.
6-14
Dazu stehen folgende Umwandlungszeichen*) mit einem vorangestell-
ten %-Zeichen zur Verfügung:
c einzelnes Zeichen
d dezimale Ganzzahl
e dezimale Gleitpunktzahl (z. B. 3.1415e+ 000)** ***))
f dezimale Gleitpunktzahl (z. B. 3.1415)
9 kürzeste Darstellung (entweder %e oder %f)
o oktale Zahl (ohne Vorzeichen)
s Zeichenkette
u dezimale Ganzzahl (ohne Vorzeichen)
X hexadezimale Zahl (Basis 16)
Folgende Formatierungszeichen*) sind möglich:
linksbündige Justierung des Arguments
(keine Angabe) rechtsbündige Justierung des Arguments
Wenn Sie das Zeichen % ausgeben wollen, müssen Sie in der Kontroll-
zeichenkette % % schreiben.
Ein Formatelement setzt sich wie folgt zusammen: Es beginnt mit
einem %-Zeichen, dann kann ein Formatierungszeichen folgen,
und am Ende ist das Umwandlungszeichen anzugeben. Zwi-
schen dem Formatierungs- und Umwandlungszeichen kön-
nen aber noch Ziffern und ein Punkt angegeben werden:
Ziffern- bei Datentyp
kette 1 long int oder
long float (kann angegeben werden)
HINWEISE: *) Sowohl die Umwandlungszeichen als auch Formatierungszeichen
sind in der Kontrollzeichenkette anzugeben.
**) Exponentendarstellung
***) Zwischen ’Ziffernkette 2’ und 'Umwandlungszeichen’ kann I angegeben
werden, wenn das entsprechende Argument vom Datentyp long int oder
long float ist.
6-15
Die Ziffernkette 1 gibt immer die Mindestanzahl der auszugebenden
Stellen an - die Ausgabe kann sich über diese minimale Feldbreite er-
strecken; falls die Ausgabe kürzer ist. werden aber dennoch soviele
Zeichen ausgegeben, wie in Ziffernkette 1 - mit Leerzeichen aufgefüllt -
angegeben sind.
Soll eine zu kurze Ausgabe nicht mit Leerzeichen, sondern mit Nullen
aufgefüllt werden, so muß die Ziffernkette 1 mit Null beginnen.
Der Punkt soll nur Ziffernkette 1 von Ziffernkette 2 trennen (ohne Ziffern-
kette 2 ist der Punkt überflüssig). Die Angabe der Ziffernkette 2 ist nur
bei den Umwandlungszeichen s, e, f und g sinnvoll.
Beim Umwandlungszeichen s gibt die Ziffernkette 2 an, wieviele
Stellen maximal auszugeben sind.
Das Argument ist eine Zeichenkette; aus dieser Zeichenkette werden die einzelnen Zei-
chen nacheinander ausgegeben, bis entweder ein Null-Zeichen (10) erreicht wird, oder
bis soviele Zeichen ausgegeben wurden, wie in Ziffernkette 2 ausgegeben sind.
Bei unseren Beispielen hier soll die Zeichenkette "Kettenglied" nach verschiedenen
Formaten ausgegeben werden:
<
printf(“\nAusgab» a« Bi 1dschlrai\n\n")।
pri»tf <“:Xv{*,“Kettengl ied“» ;
printf<"\n\n"1।
printf(“! X20« “Kett eng Hed“l|
printf(“\n\n“I।
printff“!X-2O1!**, “Kettengl ied“> |
printf <“\n\n“);
printff“IX-10*{“ ."Kettenglied">;
printf(“\n\n“)।
printfI“!X20.B«{“Kettenglied"I।
printf <“\n\n“)।
printf(“!X-2O.7»I",“Kettenglied“)|
printf(“\n\n“)j
printf<*IX020eI ",“Kettenglied"11
printf<“\n\n“>|
printf(“!X.6»:",“Kettenglied"1|
printf(“\n\n") j
printf("IXXX-020»:“,“Kettenglied")|
printf(“\n\n“)।
>
Ausgabe ae Bildechirei
{Kettenglied:
Kettenglied:
{Kettenglied
(KettengliedI
Kettengl1
IKetteng 1
lOOOOOOOOOKettenglied! ♦)
SKettenS
{»KettenglledOOOOOOOO: *)
Bei den Umwandlungszeichen f, e und g gibt Ziffernkette 1 an. wieviele Stellen die Ge-
samtzahl bei der Ausgabe mindestens einnehmen soll. Ziffernkette 2 gibt an, wieviele Stel-
len nach dem Dezimalpunkt auszugeben sind.
Möglicherweise arbeitet Ihr Rechner mit nur 2 Stellen für den Exponenten. An dieser Stelle
(x) wird eventuell IO.OOOOI ausgegeben.
HINWEIS: *) Die Nullen werden bei TurboC nicht mit ausgegeben.
6-16
ain()
(
float «inf.1, «inf_2j
doublt dopp_l, dopp_2|
• inf J»l0.0/3.0|
dopp_l«10.0/3.0|
dopp_2«-10.0/3.0|
printf ("XnAutgabt li1dichir«\n\n*);
printf <aIXI0.3f|”,einf.l>|
printf(a\n\na) ।
printfla:X10.3«la,«inf.l)|
printf <a\n\na)।
printf (a:X-10.3gla,«inf J)|
printf (a\n\na);
printf(a«X30.9fIa,dopp.l>|
printf<’\n\nal|
printf(•1X30.9«!•,dopp_2>|
printf r\n\n’l।
printf(a;X-030.9fla,«inf.l)|
printf(*\n\n\n\n’l।
•inf.l«l«5/3.0|
dopp_2"l«B/3.Oj
printf(* IX30.2f!•,«inf) ।
printf (a\n\na)|
printf (• 1X30.2«! • ,«inf_D |
printf (a\n\na)।
printf(•1X30.2g:a,«inf_1)।
printf (a\n\na)j
printf(•1X30.8fIa,dopp_2)|
printf <•\n\n">।
printf (a!X030.6Ha,«inf_D|
printf <’\n\n\n’>।
dopp.l*0.000000000730]
printf("IIgI•,dopp.1Ij
printf(a\n\na)|
printf<"IXfc.4f:a,dopp_l)|
printf(•Xna >|
Ausgabe an Bildschirm
I 3.333|
| 3.33e+OO|
|3.33 |
| 3.333333333|
| -3.333333336+00|
|3.333333254 |
33333.33|
3.36+04|
3.3e+O4|
| 33333333.33333333|
|000000000000000033333.33203125 |
|7.3e-10|
|0.0000|
Wird beim Umwandlungszeichen f keine Ziffernkette angegeben, so wird das Ausgabe-
format [-Jvvv.nnnnnn gewählt.
Wird beim Umwandlungszeichen e keine Ziffernkette angegeben, wo wird das Ausga-
beformat [-]v.nnnnnne[+-]xxx oder, bei manchen Rechnern, das Ausgabefor-
mat [-]v.nnnnnnE[+-]xx gewählt.
mclude <stdio.h>
aefine CR \r’ /• Zurueck zue Anfang der seiden Zeile •/
define ZURUECK \b /• Ein Zeichen zurueck •/
«define KLINGEL X007' /• ASCII-(Oktal-) Code fuer BEL
d.h Klingel des Terninals ansprechen •/
•am ()
(
cnar zeich_l, zeich_2, zeich_3;
int ganz_l;
lang langJl;
float gleit_f;
zeich 1 s * T*;
zeicfTZsZURUECK;
printf("XI7cestDeispilleXcXcXcele\n",zeich_l,zeich 2,zeich 2,zeich 2);
printf(aX295\n\na,ass=sssssssz=8-);
6-17
/• Bilflscm rmausgaoe:
l Testbeispiele
| 8883888888888
I
l_ <---- Cursor bleibt hier stehen
Erklaerung zu dieser Bildschirmausgabe:
Testbeispille (erste Ausgabe)
<--- Cursor wird hier positioniert wegen \b\b\b
eie (ueberschreiben von Ile durch eie
•/
printff" ahlenXcZXn",CR);
zelch_1=D';
zeich”2='0‘;
zelchj3= H ;
printf("\nX-l5cX-l5cX-l5cX-l5s\n",zeich 1,zeich 2,zeich 3,"0 vorne“);
/• Bildschirmausgabe:
I Zahlen
l
ID 0 H □ vorne
Erklaerung zu dieser Bildschirmausgabe:
l ahlen (erste Ausgabe)
- <---- Cursor wird hier positioniert wegen \r (auf Zellenanfang)
Z (ausgabe des Zeichens Z In Spalte 1, d.h. Leerzeichen wird ueberschrleben
ganz_i*13474;
printf("X-15dX-15oX-l5xX015d",ganz_1,ganz_1,ganz l,ganz 1);
printf(“\n\n\nX-2d X.2d\n\n\n"»ganz i",ganz f);
/• Bildschlrmausgabe:
13474 32242 34A2 000000000013474
13474 13474
printf("Xc",KLINGEL) ;
zeich,1=getchar();
printf(\n\nX40c\n\n\n“,zeich_l);
/• Bildschirmausgabe:
Zuerst laeutet die Terminalglocke und
X (Eingabe)
dann ist ein Zeichen einzugeben
(Z.B. XI)
X
zelch_l=e ;
zeich“2= f';
zeichj3= g‘;
printf( "X5cX1OcXi0c\n",zeich 1,zeich 2.zeich 3):
gleit 1=5.0/3.0; ” ~ ’
printf("Xe",gleit 1);
printf(" Xf"»glei t_1 );
printf("X2sXg\n\n"“" "»gleit 1);
/• Bildschirmausgabe:
le f g
I1.666667e+000 1.666667 1.666667
I
I.
• /
lang_1*-763467654;
prlntf("Xld Xlo Xlx\n",lang 1,lanq 1 ,lanq 1);
/• Bildschirmausgabe:
1-763467654 32237464172 D27E687A
I
• /
)
6-18
Das eben vorgestellte C-Programm ergäbe also folgende Bildschirmausgabe:
Testbeispiele
| 33333S33SSSSS
I Zahlen
10 0 H 0 vorne
113474 32242 34A2 000000000013474
113474 13474
(Zuerst laeutet die Terminalglocke und dann ist ein Zeichen einzugeben (z.B. X))
IX (Eingabe)
l X
le f g
11 .ö66667e*000 1.666667 1.666667 (X)
1-763467654 32237464172 D27E6B7A
(x) Abhängig vom Compiler kann für g der Wert 1.666667E + 00 angezeigt
werden.
Bei printf sollten Sie darauf achten, daß genau soviele Argumente
wie Formatelemente angegeben sind, da sich sonst unsinnige Bild-
schirmausgaben ergeben.
Als nächstes werden wir eine Funktion kennenlernen, mit der Sie
über Ihre Tastatur Werte eingeben und Variablen zuweisen können.
Diese Funktion ist printf sehr ähnlich, wie Sie sofort sehen wer-
den.
6.4 DIE EINGABE MIT scanf
scanf entspricht weitgehend der Ausgabefunktion printf, nur daß die-
se Funktion für die Eingabe zuständig ist.
scanf ("kontrollzeichenkette”, argumentl, argument2,...)
Die kontrollzeichenkette gibt an, wie die einzelnen Argumente einzuge-
ben sind; sie legt also das Eingabeformat fest.
Das %-Zeichen ist wieder vor den folgenden Umwandlungszeichen
anzugeben:
c
d
e oder f
einzelnes Zeichen
dezimale int-Zahl
Gleitpunktzahl
6-19
h dezimale short int-Zahl
o oktale int-Zahl
s Zeichenkette
x hexadezimale int-Zahl
Vor d, o und x kann jeweils das Zeichen I verwendet werden, um anzu-
zeigen, daß es sich hier um eine long int- und nicht um eine int-Zahl
bei der Eingabe handelt.
Ebenso kann vor den Umwandlungszeichen e oder f der Buchstabe I
stehen, um anzuzeigen, daß es sich hier um eine double- und nicht um
eine float-Zahl bei der Eingabe handelt.
Die Kontrollzeichenkette kann alles folgende enthalten:
Leerzeichen und Tabulatoren-Zeichen, die alle ignoriert wer-
den.
Zeichen aus dem ASCII-Code außer %, die dann, nach belie-
big viel Zwischenraum, bei der Eingabe anzugeben sind.
Formatelemente, die durch das %-Zeichen eingeleitet wer-
den. Diesem %-Zeichen kann ein * folgen, wodurch den ent-
sprechenden Argumenten kein Wert zugewiesen wird.
Weiterhin kann nach dem %-Zeichen eine Ziffernkette angegeben wer-
den, welche die maximale Anzahl von Stellen festlegt, die zu diesem
Formatelement eingegeben werden können.
Als letztes ist immer das entsprechende Umwandlungszeichen anzuge-
ben.
Die Argumente von scanf müssen Zeiger sein (Adreß-Operator & vor-
anstellen).
Beispiel 8
scanf (”%d %f %c %o”, &zahl_d, &zahl_f, Äzeich, &zahl_o);
Würde bei dieser Anweisung folgendes eingegeben:
12 3.14a
dann würde das folgenden Anweisungen entsprechen:
zahl__d = 12;
zahl__f = 3.14;
zeich = ’a’;
zahl_o = 013;
6-20
am ()
(
int zahl_1, zahl_2, zahl_3;
char zeich 1, zeich_2;
float gleitjl, gleit_2;
double doppji, dopp_2;
scanf("XSdXcXeXlf*,lzahl 1,izelcn_l,igleit_1 ,&dopp_l); <*>
printf<"\n\nXd Xc XiÖ.3e Xlf",zahl_l7zeich_l7gleit_l,dopp_1);
printf("\n\n\n");
scanf(•X3dzahlX4oX*dXcXfXlf",izahl_2,&zahl 3,izeich 2,5gleit_2,&dopp 2);
printf("\n\nXd Xd Xc Xf X6.31f",zahl_2,zanl_3,zeich_2,gleit_2,dopp_2);
printf("\n");
(x) Manche Compiler wie Lattice für IBM PC kennen nicht das Um-
wandlungszeichen e. Geben Sie dann statt %e das Zeichen %f an
Eingabe 1 mit Bildschirmausgabe:
12345Y3.14 2.12—' Eingabe
12345 Y 3.140E+000 2.120000 Ausgabe
473zahll563 567r2.33e+2 3.456789—• Eingabe
Ausgabe
473 883 r 233.000000 3.457 *>
Die Zahl 567 wird bei der 2. Eingabezeile übersprungen, da das zugehörige Formatele-
ment das Zeichen * enthält.
Da in scanf die Zeichenkette zahl angegeben ist. ist sie auch bei der Eingabe anzugeben,
sonst ergäben sich Fehler.
1563 (oktal)
= 1 ♦ 83 + 5 ♦ 82
= 1 *512 + 5*64
= 512 + 320
= 883 (dezimal)
6 ♦ 81 + 3*8° =
6*8 + 3*1 =
48 + 3
HINWEIS: *) Ausgabewert 3.457 wurde gerundet.
6-21
Eingabe 2 mit Bildschirmausgabe:
12x 3.174 1.33e-4—J Eingabe
12 X 3.140E+000 0.00013 Ausgabe
12zahl3455 84x 2.1 2.1—• Eingabe
AUSgabe
12 1837 x 2.100000 2.100
Die Zahl 84 in der 2. Eingabezeile wird übersprungen, da das zugehörige Formatelement
das Zeichen ♦ enthält.
Beim zweiten scanf ist bei der Eingabe wieder die Zeichenkette zahl anzugeben.
3455 (oktal)
= 3 ♦ 83 + 4 ♦ 82
= 3*512 + 4*64
= 1536 + 256
= 1837 (dezimal)
5 * 81 + 5*8° =
5*8 • ♦ 5*1 =
40 ♦ 5
Eingabe 3 mit Biidschirmausgabe:
3z 888.23 737.45e+3—1
Eingabe
3 Z 8.882E+002 737450.000000 • Ausgabe
444zahl777 01565897f 7.3e-2 2735.89—« Eingabe
Ausgabe
444 511 f 0.073000 2735.890
Die Zahl 01565897 in der 2. Eingabezeile wird übersprungen, da das zugehörige Format-
element das Zeichen * enthält.
Beim zweiten scanf ist wieder Zeichenkette zahl miteinzugeben.
777 (oktal)
= 7*82 + 7*8’
= 7 * 64 + 7*8
= 448 + 56
= 511 (dezimal)
+ 7*8° =
+ 7*1 =
+ 7
6-22
VERZWEIGUNGEN
7-1
VERZWEIGUNGEN 7
7.1 ANWEISUNGEN UND BLÖCKE
Fügt man zu einem Ausdruck wie schieb > >= 4 oder scanf() oder
zaehl = 7 oder nieder-- ein Semikolon hinzu, so erhält man eine C-
Anweisung:
schieb » = 4;
scanff...);
zaehl = 7;
nieder —;
Um mehrere Anweisungen zu einem Block zusammenfassen zu kön-
nen, werden die geschweiften Klammern { und } verwendet. Ein solcher
Block wird dann wie eine einzelne Anweisung interpretiert:
Beispiel 1
{ zahl__a=-7;
zahl_b=15;
sum = zahl_a + zahl_b;
printf (”%d + %d = %d”, zahl_a, zahl_b, sum);
7.2 DIE ANWEISUNG if
Die if-Anweisung wird für Programmverzweigungen benötigt. Die Syn-
tax für die vollständige if-Anweisung ist:
HINWEISE: -Wie wir später sehen werden, können in einem Block auch Variablen ver-
einbart werden.
-In C beendet ein Semikolon eine Anweisung, während z. B. in PASCAL ein
Semikolon zwischen den Anweisungen auftritt.
7-3
if (ausdruck)
anweisung 1
eise
anweisung 2
Beachten Sie, daß anweisung 1 und anweisung 2 auch ein Block
von Anweisungen, durch {...}begrenzt, sein kann.
Der nach If in Klammern () angegebene Ausdruck kann eine Bedin-
gung oder eine Variable sein.
Ist die Bedingung erfüllt bzw. der Wert einer angegebenen Variablen
von 0 verschieden, dann wird anweisung 1, sonst anweisung 2 aus-
geführt: false (falsch) - 0, true (wahr) - # 0.
Die Funktionsweise der if-Anweisung kann am besten durch einen so-
genannten Programmablaufplan veranschaulicht werden:
Wenn (ausdruck)
dann führe
anweisung 1 aus;
sonst
führe
anweisung 2 aus.
HINWEIS: if auf deutsch wenn eise auf deutsch sonst
7-4
Da wir hier weniger mit Programmablaufplänen, sondern mehr mit so-
genannten Struktogrammen*) oder "Nassi-Schneiderman-Diagrammen”
arbeiten werden, soll nun die Darstellung der vollständigen if-Anweis-
ung im Struktogramm gezeigt werden:
If (ausdruck) ।
’/wahr\ /falschX »
*i bzw.i I bzw. I!
•\'= 0/ J n \ == 0 / »
anweisung! !anweisung2 ’
Da als ausdruck nach if auch eine Variable mit der Bedeutung "ist
der Wert dieser Variable ungleich 0, so wird anweisung 1, andernfalls
anweisung 2 ausgeführt” angegeben werden kann, sind bei der prak-
tischen Programmierung oft Abkürzungen möglich
Statt
if (zahl! =O>
könnte man auch nur
if (zahl)
schreiben.
Statt
if (zahl = = O)
auch:
if (I zahl)
Sie wollen ein Auto kaufen, das folgende Kriterien erfüllt:
Türanzahl: 4
Farbe: blau
Preis: höchstens 20000,- DM
Es soll zunächst ein Struktogramm, das danach in C codiert wird, entworfen werden. Stel-
len Sie sich vor, daß Sie die Daten von einem interessanten Auto am Bildschirm eingeben
und Ihr Programm gibt dann aus, ob dieses Auto Ihre Forderungen erfüllt oder nicht; wenn
es Ihre Kriterien nicht erfüllt, so sollen die Gründe ebenfalls ausgegeben werden. Versu-
chen Sie es zunächst allein!
HINWEIS: *) Struktogramme sind wichtige Hilfsmittel beim Programmentwurf, auf die
keinesfalls verzichtet werden soll. Bevor Sie ein C-Programm schreiben,
sollten Sie immer zunächst ein Struktogramm entwerfen, was das Finden ei-
ner Lösung zu einer Programmieraufgabe wesentlich erleichtert.
7-5
Struktogramm:
'Ausgabe einer Ueberschrift
•Eingabe von tueren, färbe und preis
' zaehl=O
1 • J if (tueren==4) i N •
'♦♦zaehl •Ausgabe: keine 4 Tueren ' •
• • J If (färbe) N •
•♦♦zaehl •Ausgabe: keine blaue Farbe ' i
। ' J if (preis<-20000) • N '
• ♦♦zaehl lAusgabe: zu teuer ' !
i • J if (zaehls=3) 1 N !
•Ausgabe: Dieses Auto erfuellt ‘Ausgabe: Sie sehen ja selbst, dass '
• Ihre Vorstellungen ! • dieses Auto nicht die geforderten'
' Sie koennen dieses Auto ! Kriterien erfuellt ' •
• ohne Bedenken kaufen » ' Lassen Sie lieber '
-----------------------------------♦ die Finger davon ! •
C-Programm:
int tueren, färbe, zaehl;
lang preis;
/* Ausgabe einer Ueberschrift und Einlesen der Tueranzahl, der Farbe •/
/• und des Preises eines Autos ♦/
/
printf("X45s\n“, •'Autokauf " ) ;
printf(HX45s\n-," = = = = = = = =“);
printfCXnXnXnXnXn");
printf(""Geben Sie die Tueranzahl ein !Xn“);
scanf("Xd”,5tueren);
printf(“\n\n");
printfflst das Auto blau, dann geben Sie 1, andernfalls 0 ein 'Xn”i;
scanf(“Xd",&farbe);
printf("XnXn“);
printf(“Geben Sie den Preis des Autos ein !\n“i;
scanf("Xld",&preis);
printf("\n\nXn\nXn\n");
..
/•............................................................................•/
/• Ueberpruefung, ob das Auto die geforderten Kriterien erfuellt: •/
/• •/
/♦ Die Kriterien sind: - Tueranzahl: 4 •/
/• - Farbe: blau •/
/# - Preis: hoechst. ZODOO.-DM • /
/• •/
.............................................................................
7-6
zaehl=O; /• Die variaoie zaehl wird alt □ voröesetzt •/
if (tuerenssO
♦♦zaehl;
eise
printf("keine 4 Tueren '\n\n");
If (färbe)
♦♦zaehl;
eise
printf("keine blaue Farbe ’\n\n");
if (preis<s20000)
♦♦zaehl;
eise
printf(*zu teuer
printf C\n\n*);
if (zaehl==3) (
printf("Dieses Auto erfuellt Ihre Vorstellungen <\n\n");
printf("Sie koennen dieses Auto ohne Bedenken kaufen ’\n");
}
eise <
printf("Sie sehen 3a selbst, dass dieses Auto nicht die geforderten\n");
printf("Kriterien erfuellt ’\n\n");
printf("Lassen Sie lieber die Finger davon ’\n");
Erläuterungen:
Zunächst wird am Bildschirm die Überschrift
Autokauf
ausgegeben und danach 5 Zeilen Vorschübe printf (\n\n\n\n\n); erzeugt.
Anschließend werden mit der Funktion scanf die Werte zu den Variablen tueren, färbe,
preis eingelesen. Beachten Sie, daß diese Variablen als Zeiger in scanf anzugeben
sind. Das Formatelement %ld gibt an, daß es sich bei preis um eine long int-Variable
handelt.
Als nächstes wird die int-Variable zaehl mit Wert O vorbesetzt. Die Variable zaehl wird
im späteren Programmverlauf jedesmal um 1 erhöht, wenn ein gefordertes Kriterium erfüllt
ist.
Am Programmende kann dann abgefragt werden, ob die Variable zaehl den Wert 3 be-
sitzt. Ist dies der Fall, so wurde sie dreimal um den Wert 1 erhöht und folglich sind alle 3
Kriterien erfüllt; es kann am Bildschirm ausgegeben werden, daß dieses Auto für einen
Kauf in Frage kommt.
Ist der Wert von zaehl ungleich 3, so wurde zaehl zumindest einmal nicht erhöht und so-
mit liegt mindestens ein unerfülltes Kriterium vor; es kann am Bildschirm ausgegeben wer-
den, daß von einem Kauf dieses Autos abzuraten ist.
if(tueren = = 4)
Ist in der Int-Variablen tueren der Wert 4 gespeichert, so wird die nächste Anweisung
♦ + zaehl ausgeführt, also die Variable zaehl inkrementiert (um 1 erhöht). Ist der Wert der
Variablen tueren von 4 verschieden, so wird die Anweisung nach eise ausgeführt und
der Text: keine 4 Tueren am Bildschirm ausgegeben.
if (färbe)
Ist der Wert der int-Variablen färbe ungleich O, so wird wieder zaehl inkrementiert
(♦ + zaehl), andernfalls der Text: keine blaue Farbe am Bildschirm ausgegeben.
7-7
if (preis <=20000)
Ist der Wert der long int-Variable preis kleiner oder gleich 20000, so ist zaehl wieder
um den Wert 1 zu erhöhen (+ +zaehl). Ist in der Variable preis ein Wert größer als
20000 gespeichert, so wird der Text: zu teuer am Bildschirm ausgegeben.
if (zaehl = = 3)
Ist in der Zählvariable zaehl der Wert 3 gespeichert, so wird am Bildschirm ausgegeben,
daß dieses Auto alle geforderten Kriterien erfüllt und für einen Kauf in Frage kommt. Ent-
hält zaehl einen von 3 verschiedenen Wert, so wird durch eine entsprechende Bildschirm-
ausgabe vom Kauf dieses Autos abgeraten.
Wenn wir beim letzten Programm auf die Ausgabe der Gründe für einen Nichtkauf verzich-
ten würden, dann könnte der Programmteil, der die Kriterien überprüft, wie folgt aussehen:
Struktogramm:
I if (tueren'=4) ’
’ J NI
♦--------------------4------_--------------------------------------------------_---------4
’Ausg.: Dieses Auto ! if (»färbe) I
• wuerde ich ' J N »
• nicht kaufen ♦--------------------♦-----------------------------------------------♦
4--------------------+Ausg.: Dieses Auto • if (preis>20000l
I I wuerde ich ! J NI
। । nicht kaufen*-------------------*---------------------------♦
1 *-------------------*Ausg.: Dieses Auto 'Ausg.: Dieses Auto
I I • wuerde ich I erfuellt Ihre
• ’ I nicht kaufen» Vorstellungen ' !
• ♦-------------------—* Sie koennen dieses!
III! Auto ohne Bedenken!
' ' ! kaufen ! I
C-Programmteil:
♦/
Ueöerpruefung, ob das Auto die geforderten Kriterien erfuellt: •/
•/
Die Kriterien sind: - Tueranzahl: 4 •/
- Farbe: blau •/
- Preis: hoechst. 20000.-DM •/
•/
printf(“\n\n\n");
if (tueren'«4)
printf(“Dieses Auto wuerde ich nicht kaufenxn'1);
eise
if (Ifarbe)
printf(“Dieses Auto wuerde ich nicht kaufenxn");
eise
if (preis>20000)
printf("Dieses Auto wuerde ich nicht kaufenxn");
eise (
printf("Dieses Auto erfuellt Ihre Vorstellungen !Xn\n");
printf("Sie koennen dieses Auto ohne Bedenken kaufen !\n“);
}
7-8
Sobald eine der Bedingungen in dieser if-Verschachtelung zutrifft, d. h. ein Verstoß gegen
ein gefordertes Kriterium vorliegt, wird durch eine Bildschirmausgabe vom Kauf dieses Au-
tos abgeraten, und die gesamte if-Verschachtelung ist beendet (keine weiteren Abfragen).
Der letzte else-Block behandelt den Fall, daß alle vorhergehenden Bedingungen nicht er-
füllt waren, was bedeutet, daß alle geforderten Kriterien erfüllt sind.
Mit den logischen Operatoren hätte der Programmteil, der die Kriterien überprüft, noch
kompakter geschrieben werden können:
Struktogramm:
if (<tueren==4) && (färbe) (preis<=20000)) *
• J N •
•Ausgabe: Dieses Auto erfuellt 'Ausgabe: Dieses Auto wuerde 1
Ihre Vorstellungen ' ich nicht kaufen •
' Das Auto koennen +--------------------------------------♦
• Sie kaufen ' • !
C-Programmteil:
Z...........................................................................................
Ueberpruefung, ob das Auto die geforderten Kriterien erfuellt
Die Kriterien sind
Tueranzahl
Farbe:
Preis:
blau
hoechst. 20000.-DM
printf("\n\n\n");
if ((tueren==4J && (färbe) && (preis<=20000)) (
printf ("Dieses Auto erfuellt Ihre VorstellungenXnXn'1);
printf("Das Auto koennen Sie kaufen !\n“);
printf("Dieses Auto wuerde ich nicht kaufenXn");
Eine Empfehlung zum Autokauf wird nur dann am Bildschirm ausgegeben, wenn alle Be-
dingungen erfüllt sind:
tueren = = 4 UND
färbe (Wert verschieden von Null) UND
preis <= 20000
Ist nur eine dieser Bedingungen nicht erfüllt, so liegt ein Verstoß gegen ein gefordertes
Kriterium vor, und somit ist bei der Verknüpfung mit dem logischen Operator & & die Ge-
samtbedingung nicht erfüllt; in diesem Fall wird die Anweisung nach eise ausgeführt (Bild-
schirmausgabe, die vom Kauf dieses Autos abrät).
7-9
Bei der if-Anweisung kann der ©Ise-Teil auch entfallen. Die Syntax zu dieser reduzierten
if-Anweisung ist folgende:
if (ausdruck)
Anweisung 1;
anweisung 2;
Beachten Sie, daß sowohl anweisung 1 als auch anweisung 2 wieder ein Block von
Anweisungen sein kann.
Ist der Wert von ausdruck ungleich O. d. h. bei Angabe einer Bedingung wahr", so wird
anweisung 1, andernfalls sofort anweisung 2 ausgeführt.
Im Struktogramm stellt sich diese Form der if-Anweisung wie folgt dar:
• if (ausdruckl •
i/true\ /false\ •
• I özw. i i bzw.I •
?\!« □/ J \ ss 0/ N*
♦-------------------------------+---------4
anwelsungl • •
♦—---....-----....--------------
'anweisung? »
Im Programmablaufplan dagegen stellt sich diese Form der if-Anweisung wie folgt dar:
7-10
Beispiel 4
Uber Bildschirm sollen zwei Werte eingegeben werden, die, nach ihrer Größe geordnet, am
Bildschirm wieder auszugeben sind:
Struktogramm:
'Einlesen der beiden Variablen var_1 und var_2
if (var 2 > var 1) •
' J ' N !
♦----------------------------------------------♦---<►
'hlIf_varxvar_1 • •
'var_1xvar_2 ~ ! i
'varj2=hllf_var • i
•Ausgabe: Die geordneten Zahlen sind: •
C-Programm:
nain()
<
float var_!, var_2, hilf_var;
......................................................
/• Einlesen der beiden variablen var 1 und var 2 •/
........................................................
printf("Geben Sie bitte die erste Zahl ein ’\n“);
scanf("Xf",Svar_11 ;
printf("\n\n\nGeben Sie Ditte die zweite Zahl ein ‘\n"l;
scanf("Xf“,&var_2) ;
.............................................................
/• Ordnen der Variabienwerte var 1 und var 2 nach Groesse •/
...................................................................• •••.♦•••••/
if (var_2 > var_1) (
hlIf“var=var”l;
var_fsvar_2;~
var“2=hilf_var;
printf("\n\n\nDie geordneten Zahlen sind:\n"l;
printf("X2O.3f\n°fvar 11 ;
printf(-X20.3f\n-,var"2);
)
Die Werte von var__ 1 und var_2 werden nur vertauscht, wenn der Wert von var_________2
grösser als der Wert von var_ 1 ist.
Ist der Wert von var__1 größer oder gleich dem Wert von var_2, wird der Anweisungs-
block. der die Vertauschung der beiden Variablen vornimmt, übersprungen, und die beiden
Variablen var_1 und var_2 werden unverändert wieder ausgegeben.
7-11
Eine weitere Regel für die If-Anweisung soll an einem C-Programmausschnitt gezeigt wer-
den. monat sei eine int-Variable; urlaub und wetter seien char-Variablen).
C-Programmausschnitt:
/♦ verschachtelte if-Anweisungen •/
if ((aonat>4) && (monat<1D))
if (urlaub=='j')
If (wetter==g )
printf("\nEinen schoenen Badeurlaub wuensche ich !\n\n");
eise <
printf(“Seien Sie nicht traurig wegen des Wetters IXn");
printf("Bleiben Sie einfach zuhause und lernenSn");
printf("Sie die Programmiersprache C
printf("\nElnen schoenen Tag wuensche ich noch *\n*>;
Struktogramm:
if ((onat>4) && (monat<10)l
•Ausgabe: Einen schoenen Badeurlaub 'Ausgabe: Seien Sie nicht traurig •
• wuensche ich ! ‘ wegen des Wetters '
♦-----------------------------------♦ Bleiben Sie einfach zuhause!
’ ' und lernen Sie die
! ! Programmiersprache C ? !
•Ausgabe: Einen schoenen Tag wuensche ich noch '
Der else-Teil gehört hier zum innersten if. Es wird also von innen nach außen verschach-
telt: erstes eise zum letzten if, nächstes eise zum vorletzten If, usw..Wenn Sie einen an-
deren Bezug bewirken wollen, so müssen Sie - wie wir im nächsten Beispiel sehen werden
- dies durch { } verdeutlichen.
In unserem Beispiel hier wird also der Trostspruch zum Wetter und die Aufforderung, C zu
erlernen, nur dann am Bildschirm ausgegeben, wenn monat einen Wert größer als 4 aber
kleiner als 10, urlaub den Buchstaben j und wetter nicht den Buchstaben g enthält.
Soll sich der else-Teil auf das erste if beziehen, so müßte der entsprechende C-Pro-
grammteil wie folgt gestaltet sein:
7-12
C-Programmteil:
Verschachtelte if-Anweisungen
if ((□nat>4) && (monat<10)) {
if (urlaub-*‘J*)
if (wetters='g )
printf("XnEinen schoenen Bafleurlaut) wuensche Ich !\n\n");
eise <
printf("Ziehen Sie sich warm an l\n");
printf("Auch der Winter geht vorbelXn");
printf("XnEinen schoenen Tag wuensche ich noch »\n"i;
Struktogramm:
if ((monat>4) && (monat<10>> ’
J N !
-----------------------------------------------4-----------------------------------------f
if (urlaub== 'j* ) 'Ausgabe: Ziehen Sie sich warm an ' '
J N • Auch der Winter geht vorbei '
' if (wettersx ‘g ) • I »
' J N « • «
♦-------------------------------------+----+ I ।
'Ausgabe: Einen schoenen Badeurlaub1 • • •
• wuensche ich ! ’ ! ! «
•Ausgabe: Einen schoenen Tag wuensche ich noch !
Falls die Int-Variable monat nicht einen Wert besitzt, der größer als 4 und zugleich klei-
ner als 10 ist, so wird am Bildschirm die Empfehlung, sich warm anzuziehen und der Trost,
daß der Winter auch vorbeigeht, ausgegeben; für diesen Fall ist dann die if-Anweisung
beendet. Besitzt dagegen die int-Variable monat einen Wert, der sowohl größer als 4 als
auch kleiner als 10 ist, so wird weiter verzweigt: if (urlaub = = ’j’> und danach möglicher-
weise nochmals verzweigt: if (wetter == ’g’)
Es soll ein ’Rechen-Lernprogramm” für die 4 Grundrechenarten erstellt werden.
Der Lernende soll über Bildschirm 2 Zahlen und anschließend die Ergebnisse eingeben,
die dann vom Programm bewertet werden.
Zunächst wird ein Struktogramm entworfen.
7-13
Struktogramm:
'Ausgabe einer üeberschrift •
•Eingabe von zwei Gleitpunktzahlen ( zahl_1, zahl_2 ) •
•zaehl=O ” ” !
। i
•Fragen nach Ergebnis von zahl_1 ♦ zahl_2 (Einlesen in Variable einsuaae) '
•summe=zahl_1+zahl_2 “ '
1 If (I(ein suame-suaae)) •
! J ’ Hf
•Ausgabe: richtig ! 'Ausgabe: falsch !
'♦♦zaehl I Die Suaae ist: suaee
•Fragen nach Ergebnis von zanl_1 - zahl_2 (Einlesen in Variable einsubtr) !
'subtr=zahl_1-zahl_2 * '
if (eln_subtr-subtr «« 0)
N •
•Ausgabe: richtig ! »Ausgabe: falsch !
'♦♦zaehl • Die Subtraktion ist: subtr
{Fragen nach Ergebnis von zahl 1 • zahl 2 (Einlesen in Variable ein aultl)
1aulti=zahl_1*zahl_2
• if (ein aultl-aultl »» 0) •
! J “ N •
♦--------------------------------------♦----------------------------------------♦
•Ausgabe: richtig 1 'Ausgabe: falsch ' '
{♦♦zaehl i Die Multiplikation ist: aultl!
•Fragen nach Ergebnis von zahl 1 / zahl 2 (Einlesen in variable ein divislo) '
!divisio=zahl_1/zahl 2 “ •
! J
if (ein_divislo = = divislo)
•Ausgabe: richtig •
•♦♦zaehl
•Ausgabe: falsch '
! Die Division ist: divislo
if (zaehl-4)
N !
•Ausgabe: Sie haben zaehl* 'Ausgabe: Bravo •
Aufgaben richtig geloest • ' Sie haben alle Aufgaben
------------------------------------------- richtig geloest '
C-Programm:
aain ()
<
float zahl_1, zahl_2, suaae, subtr, aultl, divislo;
float ein_5uame, eln_subtr, ein aultl, ein divislo;
int zaehl;
.........................................................................
/• Ausgabe einer ueberschnft und Einlesen von zwei Zahlen •/
/♦♦♦•♦♦•.......................................................
printf("\nX47s\n","Rechenprograaa");
printf ("X47s\n\n\n\n\n", ==n==*«x*»»«»");
zaehl=0; /♦ Vorbesetzen der Variable zaehl alt wert 0
............................................................................
/• Eingabe von zwei Zahlen •/
.............................................................................
printf("Geben Sie zwei Gleitpunktzahlen durch Leerzeichen getrennt ein !\n"):
scanf("Xf Xf",&zahl_1,&zahl 2);
printf("\n\n");
7-14
.............................................................................
/• Summen-Elngabe, -Berechnung und -Bewertung •/
............*.................................................................
printf("\n\nUieviel ist Xf + Xf ’Xn",zahl!,zahl_2);
scanf (“Xf",&ein-Summe) ;
summe-zahl_ 1 +zalil_2;
if (•(eln_sumnie-5umme)) {
printf F"\n\nX20s\n\n","richtig •");
♦♦zaehl;
)
eise (
printf("\n\nX20s\n\n","falsch ' ");
printf("Xf ♦ Xf = Xf\n\n",zahl_1,zahl_2.summe);
.............................................................................
/• Subtraktions-Eingabe, -Berechnung und -Bewertung • /
/............................................................................
printf(‘\n\nuieviel ist Xf - Xf ’\n",zahl_l,zahl_2);
scanf("Xf",Sein subtr);
suötr=zahl_i-zahl_2;
if (eln_subtr-suötr »e 0) <
printf("\n\nX20s\n\n","richtig !");
♦♦zaehl;
>
eise (
printf("\n\nX20s\n\n","falsch •");
printf("Xf - Xf = Xf\n\n",zahl_l,zahl_2,subtr);
................................• •••....................................
/• Multiplikations-Eingabe, -Berechnung und -Bewertung • /
/............................................................................
printf(“\n\nWieviel ist Xf • Xf ?\n",zahl_1,zahl_2);
scanf("Xf", &ein_multi);
multi=zahl_l«zahl-2;
if (elnwult1-mult1 »« □) (
printf ("\n\nX20s\n\n","richtig !");
♦♦zaehl;
eise (
printf("\n\nX20s\n\n","falsch ' ");
printf("Xf • Xf = Xf\n\n",zahl_1,zahl2.multi);
/•••♦...............................................
/• Di vis Ions-Eingabe, -Berechnung und -Bewertung ♦/
.............................................................................
printf("\n\nUieviel ist Xf / Xf ?\n*,zahl_1,zahl_2);
scanf("Xf",&ein_divlsio);
dlvisio=zahl_1/zahl_2;
if (ein-divisio == divisio) <
printf("Xn\nX20s\n\n","richtig '");
♦♦zaehl;
>
eise <
printf("\n\nX20s\n\n","falsch ' " );
prtntf("Xf / Xf = Xf\n\n",zahl l.zahl 2,divisio);
..............................................................................
/•................................................................Ausgabe des Bewertungsergebnisses.•/
if (zaenl-4)
printf("Xn\n\n\nSle haben Xd Aufgaben richtig geloest ’\n",zaehl);
eise
printf("\n\n\n\nBravo •\n Sie haben alle Aufgaben richtig geloest !")
7-15
Erläuterungen:
Zunächst wird am Bildschirm die Überschrift
Rechenprogramm
ausgegeben.
Danach werden mit der Funktion scanf die Werte zu den Variablen zahl_1 und zahl_2
eingelesen - beachten Sie, daß diese beiden float-Variablen als Zeiger in scanf anzu-
geben sind - und die int-Variable zaehl mit Wert O vorbesetzt. Als nächstes wird nach
dem Ergebnis der mathematischen Operation von zahl_1 + zahl_2 gefragt. Es werden
hierbei die Werte zu diesen beiden Variablen ausgegeben.
Die vom Bediener eingegebene Antwort (Zahl) wird über die Funktion scanf in der Varia-
blen ein_summe gespeichert.
Mit der Anweisung summe = zahl__1 + zahl_2; wird das wirkliche Ergebnis dieser Addi-
tion der Variablen summe zugewiesen.
if (!(ein__summe - summe))
Ist der Wert von summe gleich dem Wert von ein__summe. so ist das Ergebnis der Sub-
traktion dieser beiden Werte O. entspricht also dem Wahrheitswert falsch. Der Negations-
operator bewirkt aber nun die Umkehrung des Wahrheitswertes: 1 oder wahr. Wenn also
die eingegebene Zahl (ein_summe) gleich der berechneten Zahl ( summe) ist, so wird
der darauffolgende Block mit Ausgabe des Textes: richtig! und Inkrementieren der int-
Variablen zaehl um 1 : + +zaehl;, andernfalls der Block nach eise mit Ausgabe des Tex-
tes: falsch! und Ausgabe des wirklichen Ergebnisses, das in der Variablen summe ge-
speichert ist, ausgeführt.
Anschließend wird mit scanf die Lösung des Benutzers zur mathematischen Operation
zahL_1 - zahl__2 in die Variable ein__subtr eingelesen.
Mit der Anweisung subtr = zahl_1 - zahl__2; wird das wirkliche Ergebnis dieser Sub-
traktion der Variablen subtr zugewiesen.
if (ein__subtr - subtr = = O)
Ist der Wert von subtr gleich dem Wert von ein_subtr, so ist das Ergebnis dieser Sub-
traktion gleich O und die Bedingung ist erfüllt (wahr); für diesen Fall wird also der darauf-
folgende Block mit Ausgabe des Textes: richtig! und Inkrementieren der int-Variable
zaehl um 1 + +zaehl; ausgeführt.
Ist der Wert von subtr nicht gleich dem Wert von ein.subtr, so wird der else-Block mit
Ausgabe des Textes: falsch! und Ausgabe des wirklichen Ergebnisses, das in der Varia-
ble subtr gespeichert ist, ausgeführt.
Wir wollen auf die weiteren Anweisungen nicht mehr eingehen, da diese weitgehend den
vorherigen entsprechen. Es soll nur noch die letzte if-Anweisung erläutert werden.
if (zaehl-4)
Ist in zaehl der Wert 4 gespeichert, so ist das Ergebnis dieses Ausdrucks O und es wird
der else-Zweig mit Ausgabe des Textes: Bravo! Sie haben alle Aufgaben richtig ge-
löst ausgeführt.
Ist in zaehl nicht der Wert 4 enthalten, so ist das Ergebnis des Ausdrucks (zaehl - 4)
verschieden von O, d. h. mindestens eine Rechenaufgabe wurde falsch angegeben, da
zaehl nicht bei allen 4 Aufgaben um 1 inkrementiert wurde. Daher wird am Bildschirm aus-
gegeben, wieviele Aufgaben richtig gelöst wurden.
7-16
Ein Hinweis ist an dieser Stelle noch erforderlich:
Einer der häufigsten Fehler ist die falsche Angabe für den Vergleichsoperator««:
(1) if (variab = 0) /«falsch«/
(2) if (variab = = O) /«richtig*/
(1) interpretiert der Compiler als Zuweisung des Wertes O an die Variable variab;
der zugewiesene Wert O ist dann das Resultat des Ausdrucks, so daß diese
Bedingung nie erfüllt, d. h. immer falsch (O) ist und der von der if-Abfrage ab-
hängige J-Zweig niemals ausgeführt wird:
Obwohl diese Version (1) sinnlos erscheint, ist sie syntaktisch korrekt und
wird vom Compiler nicht als Fehler erkannt.
(2) Diese Version ist korrekt und hätte z. B. auch mit if (! variab) angegeben wer-
den können.
7.3 DIE BEDINGTE BEWERTUNG
C erlaubt als Sonderform bedingte Bewertungen.
Wenn wir das Maximum der int-Variablen zahl_____1 und zahl___2 an die
int-Variable max zuweisen wollen, so würden wir mit unseren bisheri-
gen Kenntnissen folgenden Programmteil erstellen:
if(zahl_1 >zahl_2)
max = zahl___1;
eise
max = zahl__2;
HINWEIS: Vor eise steht in der letzten if-Anweisung, also nach printff...}, ein Semi-
kolon. Das liegt daran, daß auf if eine Anweisung folgt, und ein Ausdruck als
Anweisung wie printff ...) muß immer mit einem Semikolon abgeschlossen
werden.
7-17
Ist der Wert von zahl__1 größer als der Wert von zahl_2, so wird der
Wert von zahl____1 der Variablen max, andernfalls der Wert von
zahl__2 der Variablen max zugewiesen.
Mit der bedingten Bewertung hätten wir diesen Programmteil auch wie
folgt angeben können:
max = (zahl___1 > zahl__2) ? zahl__1 : zahl__2;
Zunächst wird der Ausdruck (zahl__1 > zahl___2) bewertet. Trifft diese
Bedingung zu, ist also der Wert dieses Ausdrucks nicht O, dann wird
der Variablen max der Wert von zahl_____1, andernfalls der Wert von
zahl__2 zugewiesen.
Formal hat die bedingte Bewertung folgendes Aussehen:
ausdrl ? ausdr2 : ausdr3
Zunächst wird dabei der ausdrl bewertet. Ist sein Wert verschieden
von O (wahr), so wird ausdr2 bewertet, andernfalls ausdr3. In jedem
Fall wird nur einer der beiden Ausdrücke ausdr2 oder ausdr3 bewer-
tet; dieser Wert ist dann das Ergebnis der bedingten Bewertung.
Da der Operator ?: eine sehr niedrige Priorität besitzt, sind runde
Klammern bei ausdrl. ausdr2 und ausdr3 nicht notwendig, erhöhen
aber die Übersichtlichkeit.
Beispiel 7
finclude <stdio.h>
am ()
(
char Zeichen; *)
printf("Geben Sie den Buchstaben m oder w ein *\n");
............................................................................
/• ♦/
/• wird von» Benutzer m eingegeben, so wird •/
/• maennlich an Bildschirm ausgegeben •/
/• wird von Benutzer ein anderes Zeichen eingegeben, so wird • /
/• weiblich an Bildschirn ausgegeben •/
/• Danach wird noch der Inhalt von Zeichen ausgegeben •/
/♦
/•••••......................................................................
printf(((zeichen-getchar()) ss m’) ’ ("maennlich") : ("weiblich")); *)
printf(“\n\n\nX20c".Zeichen);
HINWEIS: *)ßei TurboC: Warnung, daß ’zeichen’ einen Wert zugewiesen bekommt, der
nirgendwo mehr benutzt wird. Diese Warnung können Sie beseitigen, indem
Sie die Deklaration char Zeichen; entfernen und anstelle von (Zeichen =
getcharf)) nur getchar() schreiben.
7-18
In einer Anweisung werden hier
eine Eingabe
eine Zuweisung
eine bedingte Bewertung
eine Ausgabe
abgehandelt.
getcharf)
Zeichen = getcharf)
..?....:..
printff...)
Beispiel 8
Das zuvor gezeigte Rechen-Lernprogramm könnte unter Verwendung der bedingten Bewer-
tung wie folgt aussehen:
ainll
<
float zahl.1, zahl_2, suene, subtr, aulti, divisio;
float ein suwe, ein subtr, ein «ulti, ein.divisio;
int zaehl;
............................................................................./
/• Ausgabe einer ueberschrift und Einlesen von zwei Zahlen •/
printf("XnX47sXn","Rechenprogra««") ;
printft"X47s\n\n\n\n\n","ssssssasssasss");
zaehl=O; /• Vorbesetzen der Variable zaehl «it wert 0 •/
Eingabe von zwei Zahlen
printf("Geben Sie zwei Gleitpunktzahlen durch Leerzeichen getrennt ein '\nB);
scanf ("Xf Xf• ,4zahl.l ,4zahl_2);
printfi"XnXn");
Sunaen-Elngabe, -Berechnung und -Bewertung •/
printf("XnXnWieviel ist Xf ♦ Xf ’Xn",zahl.1,zahl_2);
scanf(“Xf",4ein_su««e);
sue«e»zahl.1♦zahl.2;
printfi (‘lein suene-suaae)) ’ (" richtig '\n\n"J : (" falsch 'XnXn") i;
('(ein.suaae-süame)> ’ (♦♦zaehl) : ( printf("Xf ♦ Xf - XfXnXn",zahl.1,zahl_2,suaae));
.............................................................................
/» Subtraktions-Eingabe, -Berechnung und -Bewertung •/
printf("\n\nWieviel ist Xf - Xf ’Xn",zahl.1,zahl.2);
scanf(“Xf“,aein.subtr);
subtr-zahl 1-zahl 2;
pnntf( (ein subtr-subtr -- 0) ’ (" richtig 'XnXn") : (• falsch 'Xn\n") »;
leinsubtr-subtr = z 0) ’ (♦♦zaehl) : ( printf("Xf - Xf = XfXnxn“,zahl.1.zahl.2,subtr));
/• Multiplikations-Eingabe, -Berechnung und -Bewertung •/
printf("XnXnWieviel ist Xf • Xf ’Xn",zahl.1,zahl.2);
scanf(“Xf",4ein aulti);
multiszahl l*zahl 2;
printf( (ein aultl-aulti □) ’ (* richtig 'XnXn") : (" falsch 'XnXn") I;
(ein «ult i-nulti «« 0) ’ (♦♦zaehl) : ( prmtf(’Xf • Xf = XfXnXn", zahl.1 ,zahl_2, »ulti)) ;
/• Dlvisions-Eingaoe, -Berechnung und -Bewertung ♦/
printf("\n\nWleviel ist Xf / Xf ’Xn",zahl.1,zahl_2);
scanf("Xf",4ein_divis io);
divisio=zahl_i/zahl 2;
printfl (ein divisio == divisio) ’ (" richtig 'XnXn") : (" falsch 'XnXn") )
(em divisio’»» divisio) ’ (♦♦zaenl) : ( pnntft’Xf / Xf xfXnxn",zahl 1,zahl 2,divisio));
7-19
Ausgabe des Bewertungsergeontsses
if (zaehl-4)
printf("\n\n\n\nSie haben Xö Aufgaben richtig geloest '\n",zaehli;
eise
printf(’\n\n\nXnBravo ’\n Sie haben alle Aufgaben richtig geloest •“);
7.4 DIE ANWEISUNG switch
Mit der switch-Anweisung kann unter mehreren Alternativen, nicht nur
unter zwei wie bei der if-Anweisung, ausgewählt werden:
switch (ausdruck)
case ausdrl:
anweisungenl;
case ausdr2:
anweisungen2;
case ausdrn:
anweisungenn;
default:
anweisungend;
Es wird der bei switch angegebene (ausdruck) ausgewertet und das
Ergebnis mit den einzelnen case-Ausdrücken, d. h. int- oder char-
Konstanten oder entsprechende konstante Ausdrücke, verglichen.
Wird eine Übereinstimmung gefunden, so verzweigt das Programm zu
dieser sogenannten case-Marke und fährt dort mit seiner Ausführung
fort.
Wird keine Übereinstimmung gefunden, so verzweigt das Programm zur
default-Marke, falls diese angegeben wurde; wird keine Übereinstim-
mung gefunden und ist keine default-Marke angegeben, so wird keine
Anweisung des switch-Blocks ausgeführt. Fehlt also die default-Mar-
ke, so wird der gesamte switch-Block übersprungen, falls keine Über-
einstimmung in den case-Marken vorliegt.
Die switch-Anweisung ist mit einem Programm-Schalter zu verglei-
chen und wird im Struktogramm wie folgt dargestellt:
7-20
Struktogramm:
'switch (ausdruck)
1 ausdrl
'anweisungen 1
1ausdrZ
•anweisungen 2
* ausdrn
•anweisungen n
•default:
•anweisungen d
Die Reihenfolge der case- und default-Marken ist beliebig. Die ange-
gebenen case-Konstanten müssen verschieden sein.
Um die switch-Anweisung unmittelbar verlassen zu können, ist das
Schlüsselwort break (wird später noch genauer behandelt) anzugeben.
Fehlt die break-Anweisung, so wird der Programmlauf bei der näch-
sten case-Alternative fortgeführt.
Es ist empfehlenswert, alle case-Marken mit einem break abzu-
schließen, vor allem auch die letzte. Der Grund dafür ist, daß bei spä-
teren Änderungen das break dann erforderlich werden kann, aber
nicht eingefügt wird, was unangenehme Folgen nach sich ziehen kann.
7-21
Beispiel 9
............................••••/
/• Dieses Programm liest eine Hexa-Ziffer ein und gibt •/
/• die dieser Ziffer entsprechende ♦/
/• - Dezimalzahl, •/
/• - Oktalzahl und •/
/• - Dualzahl am Bildschirm wieder aus •/
...........................................................................
tlnclude <stdio.h>
nain ()
( char hexa_ziffer;
/• Eingabe einer Hexa-Ziffer in die char-Variable hexa Ziffer •/
.......................................................................
printf("Geben Sie eine Hexaziffer ein IXn");
hexa_z1ffer=getchar();
/•••••••••••»••••••••...............................................
/• Ausgabe einer Ueberschrift und der eingegebenen Hexa-Ziffer •/
.......................................................................
printf("XnXnXnXlOsXl0sX1OsXiOsXn","Hexa",“Dezimal",*Oktal","Dual");
printfl“X1Oc",hexa_ziffer);
......................................................»••♦••»«•••»•«/
/• Ausgabe der zugehoerigen Dezimal-, Oktal- und Dualzahl •/
switch (hexa Ziffer)
(
case 0':
prlntf("X10cX10cX10sXn",hexa_ziffer,hexa_ziffer,“0000");
break;
case 1‘:
printf("X10cXl0cX10sXn",hexa_ziffer,hexa zi ffer,"0001“);
break;
case "2’:
printf("X1OcXiOcXiOsXn",hexa_ziffer,nexa ziffer,"001□");
break;
case 3':
printf("X1OcXiQcZ1Ds\n",hexa_ziffer,hexa Ziffer,"0011 *);
break;
case ’4‘:
printf(“X10cX1OcX1OsXn",hexa_ziffer,hexa Ziffer,"0100“);
break;
case ’5‘:
printf("X10cX10cX1OsXn",hexa.ziffer,hexa zlffer,"0101");
break;
case 6':
printf("X1OcXiOcXiOsXn",hexa ziffer.hexa z1ffer,"0110");
break;
case '7*:
printf(“XiDcXi OcX1OsXn", he xa_ Ziffer, hexa ziffer,*01H*);
break;
case 8":
printf("X10CX10SX1Os\n",hexa Ziffer,*10“,“1000");
break;
case *9*:
printf("X1OcXiOsXlOsXn",hexa zi ffer,"11","i001");
break;
case a ’:
case A':
printf("X10sXl0sX10sXn","10","12“,“1010");
break;
case ‘b‘:
case B‘:
printf("X10sX1OsX1OsXn","11“,"13","1011");
break;
case ‘c :
case C’:
prlntf("XlDsX10sX10sXn","12","14","l100");
break;
case 'd':
case D':
printf("X105X1OsXlOsXn","13","15","1101");
break;
7-22
case e :
case E‘:
printf("XlOsXlOsXlOs\n","14-,*16",-1110");
break;
case 'f :
case F':
printf("XI0sX10sX10s\n*,"15“,“17",-1111" ) ;
break;
default:
printf(-Das ist keine Hexa-Zlffer ’\n*);
Dreak;
printf(*\n\n\nAuf Wiedersehen ’\n\n\n">;
Struktogramm:
Ausgabe: Geben Sie eine Hexaziffer ein !
hexa.ziffer=getchar()
Ausgabe: Hexa Dezimal Oktal Dual
Ausgabe der variable hexa.zlffer
switch (hexa_zlffer)
0 : ’
Ausgabe: hexa_zlffer hexa_zlffer 0000
1 break
1 ’ :
Ausgabe: hexa_ziffer hexa.zlffer 0001 •
break " ’
2 :
Ausgabe: hexa.Ziffer hexa.zlffer 0010 l
break " “ •
3 :
•Ausgabe: hexa.zlffer hexa.zlffer 0011
break
4 :
—----------------—-------——--------------
Ausgabe: hexa.zlffer hexa.zlffer 0100
•break
5 :
'Ausgabe: hexa.zlffer hexa.zlffer 0101 •
•break ~ ” '
♦----------------------------------------------+
6 : '
♦----------------------------—-----------------4.
•Ausgabe: hexa.zlffer hexa.zlffer 0110 •
•break " ” •
♦--------------------------------------------♦
7 : •
♦--------------------------------------------♦
'Ausgabe: hexa.zlffer hexa.zlffer 0111 '
'break " ” '
♦--------------------------------------------♦
8 : I
•Ausgabe: hexa.zlffer 10 1000 ?
'break '
9 :
4——————————-+
'Ausgabe: hexa.zlffer 11 1001 '
'break ” ?
7-23
!’a',* A':
'Ausgabe: 10 12 1010
'break
r b •, ’ b •:
। +-------------------------
1 'Ausgabe: 11 13 1011
! 'break
•*c’, C':
> 'Ausgabe: 12 14 1100
• (break
I 4----------------------
•’d/D-;
i ♦----------------------
1 'Ausgabe: 13 15 1101
• 'break
• e ,’E :
•Ausgabe: 14 16 1110
•break
•f, F-:
Ausgabe: 15 17 1111
' break
default:
(Ausgabe: Das ist keine Hexa-Ziffer '
1 break
Ausgabe: Auf Wiedersehen •
Erläuterungen:
Wurde in die char-Variable hexa_Ziffer ein Zeichen eingegeben, das einer der Hexa-
Ziffern 0,9, A,F entspricht, so verzweigt das Programm zu der entsprechenden case-
Marke und gibt die zugehörigen Dezimal-, Oktal- und Dualzahlen am Bildschirm aus.
Wurde in die char-Variable hexa_Ziffer ein Zeichen eingegeben, das keiner Hexa-Ziffer
entspricht, so verzweigt das Programm zu der default-Marke und gibt den Text: Das ist
keine Hexa-Ziffer! am Bildschirm aus.
Falls keine Hexa-Ziffer vorliegt und keine default-Marke angegeben ist. wird keine An-
weisung in der switch-Anweisung ausgeführt. Der Programmblock der switchAnweisung
würde also übersprungen werden.
Die break-Anweisung sorgt dafür, daß der switch-Block sofort verlassen wird. Fehlt die-
se break-Anweisung, so wird mit der nächsten case-Marke fortgefahren.
Wäre z. B. bei den Anweisungen zur Marke ’O’ das break weggelassen worden:
case ’O’:
printff
case ’1’:
printff
break;
und in hexa__Ziffer das Zeichen ’O’ gespeichert, so wäre das Programm zunächst zur
case-Marke ’O* verzweigt und hätte die Dezimal-, Oktal- und Dualzahl zu 0 am Bildschirm
ausgegeben. Da nun hier kein break angegeben wurde, wäre auch noch die nächste
case-Anweisung (Ausgabe der Dezimal-, Oktal- und Dualzahlen zu 1) ausgeführt worden.
Für die Zeichen ’O*, ’9’ entspricht die eingegebene Hexa-Ziffer der Ziffer im Dezimal-
7-24
System; für diesen Fall kann also die eingegebene Hexa-Ziffer unverändert wieder am Bild-
schirm ausgegeben werden.
Für die Zeichen ’O’, ’7’ entspricht die eingegebene Hexa-Ziffer der entsprechenden
Ziffer im Oktalsystem; für diesen Fall kann also wieder der Inhalt der char-Variablen
hexa__Ziffer unverändert am Bildschirm ausgegeben werden.
Da als Hexa-Ziffern sowohl Groß- als auch Kleinbuchstaben zugelassen sind, werden als
case-Marken bei den Buchstaben sowohl Groß- und Kleinbuchstaben angegeben, z. B.:
case ’d’:
case ’D’s
printf
break;
Wenn in hexa__Ziffer das Zeichen ’d’ gespeichert ist, so wird zur entsprechenden case-
Marke ’d’ verzweigt. Da nach dieser case-Marke keine Anweisungen, vor allem kein
break, angegeben wurden, wird mit der nächsten case-Marke ’D’ fortgefahren; es wer-
den also die zur case-Marke ’D’ gehörigen Anweisungen ausgeführt.
Diese Vorgehensweise bewirkt, daß sowohl für Klein- als auch für Großbuchstaben die
entsprechenden Dezimal-, Oktal- und Dualzahlen am Bildschirm ausgegeben werden.
Ablauf des vorhergehenden Programms am Bildschirm:
Geben Sie eine Hexaziffer ein!
9—1
Hexa
9
Dezimal
9
Oktal
11
Dual
\00\
Auf Wiedersehen!
Geben Sie eine Hexaziffer ein!
Hexa Dezimal Oktal
f 15 17
Auf Wiedersehen!
Dual
1111
Geben Sie eine Hexaziffer ein!
X—>
Hexa
Dezimal Oktal
X Das ist keine Hexa-Ziffer!
Dual
Auf Wiedersehen!
7-25
Beispiel 10
Wenn beim vorhergehenden Beispiel auf die Ausgabe der entsprechenden Dualzahl ver-
zichtet worden wäre, hätte dieses Programm wie folgt aussehen können. Es soll hier auch
gezeigt werden, daß die Reihenfolge der case-Konstanten beliebig sein kann.
/• Dieses Programm liest eine Hexa-Ziffer ein und gibt •/
/• die dieser Ziffer entsprechende ♦/
/♦ - Dezimalzahl und ♦/
/• - Oktalzahl am Bildschirm aus •/
...........................................................................
finclude <stdio.h>
main ()
< char hexa.ziffer;
.....................................................................
/• Eingabe einer Hexa-Ziffer in die char-Variable hexa Ziffer ♦/
........................................................................
printf("Seben Sie eine Hexaziffer ein »\n");
hexa_ziffer=getchar();
<.......................................................................
/• Ausgabe einer Ueberschnft und der eingegebenen Hexa-Ziffer •/
printf("XnXnXnXIOsX10sX1OsXn","Hexa","Dezimal","Oktal");
printf(“X10c",hexa_ziffer);
/• Ausgabe der zugehoerlgen Dezimal-, Oktal- und Dualzahl •/
switch (hexa Ziffer)
<
case 'e‘:
case E’:
printf("X1OsXlOsXn","14*,"16");
break;
case a :
case ’A':
printf("XIOsX1OsXn,"10","12");
break;
case ’6’:
case '2':
case 7•:
case ’ 1 ‘:
case □*:
case ’5':
case *3':
case 4‘:
printf("X1OcXlOc",hexa ziffer.hexa Ziffer);
break;
case *8*:
prlntf("X10cXlOsXn",hexa z1ffer,"10");
break;
case 9':
printf ("X10cX1OsXn",hexa ziffer,"H");
break;
case b‘:
case B*:
printf("X1DsXlOsXn","11","13");
break;
case ’c‘:
case C':
printf("X1OsX1OsXn","12","14");
break;
case d’:
case D’:
printf(mX10sX1OsXn","13","15");
break;
7-26
case f:
case F':
printf(“X10sX10s\n•, •15",«17-);
break;
default:
printft-Das ist keine Hexa-Ziffer »\nB);
break;
printf("\n\n\nAuf Wiedersehen !\n\n\n");
Struktogramm:
'Ausgabe: Beben Sie eine Hexaziffer ein •
1 hexa_z1ffer=getchar() •
‘Ausgabe: Hexa Deziaal Oktal •
•Ausgabe der Variable hexa.ziffer •
•suitch (hexa.ziffer)
! e , E':
• +------------—.
’ 'Ausgabe: 14 16
' 'break
' a‘,’A’:
1 'Ausgabe: 10 12
• 'break
' ’6’t'2',‘7‘f 1*, 0‘,’S’,’3’, 4* :
'Ausgabe: hexa_ziffer hexa_ziffer
•break
^Ausgabe: hexa_ziffer 10
•break
•Ausgabe: hexa_ziffer 11
•break
b' , ’B
•Ausgabe: 11 13
•break
C
•Ausgabe: 12 14
•break
d’ , D
•Ausgabe: 13 15
•break
•Ausgabe: 15 17
•break
•default:
•Ausgabe: Das ist keine Hexa-Ziffer •
•break
•Ausgabe: Auf Wiedersehen '
7-27
Da die Hexa-Ziffern 0 ... 7 auch entsprechende Darstellungen im Oktal und Dezimalsystem
besitzen, konnte für alle diese Hexaziffern lediglich eine Anweisung angegeben werden
der acht verschiedene case-Marken *0’,...» ’7’ vorangestellt wurden.
Es sei jedoch darauf hingewiesen, daß bei dieser Vorgehensweise die Übersichtlichkeit
leidet.
7-28
SCHLEIFEN-ANWEISUNGEN
8-1
SCHLEIFEN-ANWEISUNGEN 8
8.1 DER KOMMA-OPERATOR
Der Komma-Operator faßt mehrere Ausdrücke syntaktisch zu einem
einzigen Ausdruck zusammen. Sind mehrere Ausdrücke durch das
Komma , getrennt, so werden sie von links nach rechts bewertet; Da-
tentyp und Wert des Gesamtausdruckes ist dann gleich dem Datentyp
und Ergebnis der letzten Operation.
Beispiel 1
zeich = ’J’, zaehl = 9, pi = 3.1415;
Alle drei Zuweisungen werden nacheinander berechnet, das Ergebnis des Gesamtaus-
drucks ist gleich 3.1415.
Im Zusammenhang mit Schleifenparametern, die wir auf den nächsten
Seiten behandeln werden, wird dieser Komma-Operator häufig verwen-
det. Der Komma-Operator erlaubt nämlich die Unterbringung von meh-
reren Zuweisungen an einer Stelle, wo sonst nur eine erlaubt wäre.
Man kann den Komma-Operator auch innerhalb eines if verwenden, um
sich die geschweiften Klammern einer Blockanweisung zu sparen:
Beispiel 2
zaehl ++, printf(”\n Zaehler erhoeht \n”)i
Sonst hätte man schreiben müssen:
»(...){
zaehl + +;
printff”\n Zaehler erhoeht \n”);
8-3
8.2 DIE SCHLEIFENANWEISUNG for
Syntaktisch sieht die for-Schleife wie folgt aus:
for (ausdruckl; ausdruck2;ausdruck3)
anweisung
Die anweisung kann selbstverständlich wieder ein Block von Anwei-
sungen sein.
ausdruckl:
initialisiert die Schleifenvariable(n)
ausdruck2:
liefert ein Abbruchkriterium für die Schleife,
wenn ausdruck2 nicht erfüllt (falsch) ist.
ausdruck3:
reinitialisiert die Schleifenvariable(n), z. B. beim
Inkrementieren von Schleifenvariable(n)
Diese drei Komponenten (ausdrücke) können einzeln oder auch ins-
gesamt fehlen, jedoch müssen die Semikolons in der Klammer an der
richtigen Stelle verbleiben.
Die Funktionsweise der for-Schleife soll anhand eines Programmab-
laufplans gezeigt werden:
8-4
mainO
(
int i;
/•____________________ ♦/
/• Deklärätlönsteil •/
/•_______________________•/
/• 1 wird als ganzzahlige Variable vereinbart.
Anwelsungsteff
printft* *\nM);
printft"
printft" •♦\n");
printfc •• ••\n");
printf(“ ;
printft" );
for (1=1;1<5;++1)
printft* •
8-5
Der Programmteil
for (l = 1;i<5; + + i)
printf(” ♦<
entspricht folgendem Programmablauf:
nächste
Anweisung
d. h. die nach der for-Zeile folgende prlntf-Anweisung wird viermal ausgeführt.
Allgemein könnte eine for-Schleife so dargestellt werden:
for (Schleifenvariable = Anfangswert:
Schleifen-Bedingung;
Schleifenvariable verändern)
In unserem Beispiel wäre
Schleifenvariable
Anfangswert
Schleifen-Bedingung
Schleifenvariable verändern
I
1
i< 5
+ + i (was i = i + 1 entspricht)
8-6
Unser Beispiel würde also folgendes am Bildschirm ausgeben:
* * * *
* ¥ < # > * ¥ ¥ ¥ ¥ ¥
¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥
* ¥ ¥ *
* ¥ * *
¥ ¥ * *
¥ * ¥
¥ ¥ ¥ ¥ ¥ ¥ * ¥ ¥ * *
Da die Abfrage der Schleifen-Bedingung am Angang durchgeführt wird, wird die
for-Schleife im Struktogramm wie folgt dargestellt:
1 For (ausdruckl ; ausdruckZ ; ausdruck3) 1
'Schleifen-AnWeisungen
Anweisung (ausserh. der for-Schleife) '
♦-------------------------------------------4
Bei der folgenden for-Schleife:
for (i = 7; i > 10; i + +)
printff’Guten”);
printf(”Tag\n”);
ist die Schleifenbedingung I > 10 sofort nicht erfüllt, d. h. die
Schleifenanweisung printf(”Guten”); wird überhaupt nicht ausgeführt.
Stattdessen wird mit der nächsten Anweisung außerhalb der for-Schlei-
fe fortgefahren und nur der Text: Tag am Bildschirm ausgegeben.
Beispiel 4
Es soll die geometrische Reihe
berechnet werden, wobei n einzugeben ist. Diese Reihe besteht aus n + 1 Summanden
(Summengliedern).
Struktogramm:
'relh.tellsl
?summe=l •
♦-------------------------------------------------------------♦
{Ausgabe einer Ueberschrlft !
i i
•Eingabe von n (ganzzahlig) !
• !
♦-------------------------------------------------------------♦
Ifor (zaehl=1 ; zaehlon ; ♦♦zaehl)
! 1reih_teil-reih_teil/2
! 'summe+=reih_tefl •
'Ausgabe: Die Summe der geometrischen Reihe bis n ist: !
(Ausgabe: summe •
I f
C-Programm:
main ()
(
int zaehl, n;
double reih_teil=i, summe=l;
printf("XnXnBerechnung der geometrischen RelheXn");
printft* = = s2a8«»»==ssssssSsSz»«xSSS=SSSssa\n\n\n\nM);
printf("Geben Sie n ganzzahlig positiv ein
scanf(•Xd”,&n);
for (zaehl=l ; zaehl<=n ; ♦♦zaehl) <
reih_tei1-relh_teil/2;
summe*»reih.teil;
printf("\n\n\nDie Summe der geometrischen Reihe bis Xd ist:\n",n):
printf("Xf\n“»summe);
Erklärung:
Die beiden double-Variablen summe und reih_______teil werden bei ihrer Deklaration mit
dem Wert 1 vorbesetzt.
In der for-Schleife wird zunächst die Schleifenvariable zaehl auf 1 gesetzt. Danach wird
gefragt, ob die Schleifen-Bedingung zaehl <= n noch erfüllt ist; wenn ja, werden die bei-
den zur Schleife gehörigen Anweisungen
reih—teil = reih— teil/2; summe+ = reih—teil;
ausgeführt und die Schleifenvariable zaehl mit + + zaehl um den Wert 1 inkrementiert.
Nun beginnt der 2. Schleifendurchlauf mit der Überprüfung der Schleifen-Bedingung;
ist sie noch erfüllt, so werden wieder die Schleifenanweisungen und die Inkrementierung
+ + zaehl durchgeführt, usw. Die Schleife wird erst verlassen, wenn die Schleifen-Bedin-
gung nicht mehr erfüllt ist.
Wird z. B. für n (Variable n) 0 eingegeben, so werden die Schleifenanweisungen kein ein-
ziges Mal ausgeführt.
8-8
Programmablaufplan:
Anhand des Programmablaufplans ist leicht zu erkennen, daß bei der Eingabe O für n die
Schleifenanweisungen nicht ausgeführt und der unveränderte Wert für summe (1) am
Bildschirm ausgegeben werden.
8-9
Wird für n der Wert 1 eingegeben, so werden die beiden Schleifenanweisungen
1 -mal ausgeführt.
Wird für n der Wert 2 eingegeben, so werden die beiden Schleifenanweisungen
2-mal ausgeführt.
Wird für n ein beliebiger Wert y eingegeben, so werden die beiden Schleifenanwei-
sungen y-mal ausgeführt.
Programmablaufbeispiele am Bildschirm:
Berechnung der geo^trischenJRbihe
Geben Sie N ganzzahlig positiv ein!
11—।
Die Summe der geometrischen Reihe bis 11 ist:
1.999512
Berechnung der geometrischen Reihe
Geben Sie N ganzzahlig positiv ein!
1—'
Die Summe der geometrischen Reihe bis 1 ist:
1.500000
Berechnung der geometrischen Reihe
Geben Sie N ganzzahlig positiv ein!
3—
Die Summe der geometrischen Reihe bis 3 ist:
1.875000
Berechnung der geometrischen Reihe
Geben Sie N ganzzahlig positiv ein!
0—1
Die Summe der geometrischen Reihe bis 0 ist:
1.000000
8-10
Berechnung der geometrischen Reihe
Geben Sie N ganzzahlig positiv ein!
90—।
Die Summe der geometrischen Reihe bis 90 ist:
2.000000
Die for-Anweisung ist ein wichtiger Anwendungsfall für den Komma-
Operator.
In vielen Fällen müssen mehrere Schleifenvariablen initialisiert und/
oder reinitialisiert werden. Werden die einzelnen Zuweisungen durch
Kommata getrennt, so gelten sie - wie wir beim Komma-Opera-
tor gehört haben - syntaktisch als ein zusammenhängender Ausdruck.
Es soll wieder die geometrische Reihe
berechnet werden, wobei n einzugeben ist.
C-Programm:
atn()
<
int zaehl, n;
double relh_teil, summe;
printf("XnXnBerechnung der geometrisehen ReiheXn");
printfI==ss===x::ss:xszszzsx\n\n\n\n");
printf("Geben Sie n ganzzahlig ein ’\n");
scanf("Xd",&n);
for (relh_tei1=0.5,suaae=l,zaehl=i ; zaehl<=n ; reih_teil/=2.♦♦zaehl)
suame+=reih_teil;
printf("\n\n\nDle Summe der geometrischen Reihe bis Xd ist:\n",n);
printf("Xf\n",summe);
Hier werden die Variablen summe und reih_teil in der for-Anweisung initialisiert; wa-
rum relh__tell mit dem Wert 0.5 vorzubesetzen ist, wird aus dem nachfolgenden Pro-
grammablaufplan ersichtlich.
Die ständige Division von reih__teil durch 2 wird im Reinitialisierungsteil der for-Anwei-
sung vorgenommen; so verbleibt eine einzige Schleifenanweisung:
summe* = reih^teil;
8-11
Programmablaufplan:
8-12
reih_teil ist hier mit 0.5 zu initialisieren, da die Division durch 2 erst im Reinitialisie-
rungsteil vorgenommen wird, also erst nach der Ausführung der Schleifenanweisung:
summe+ = reih________teil; Würde reih_______teil im Initialisierungsteil der for-Anweisung mit
Wert 1 vorbesetzt, so würde dieses Programm die Reihe
1 + 1
2 4
£
gn-1
berechnen.
Vollziehen Sie dies noch einmal im Programmablaufplan nach!
Es können auch alle Ausdrücke innerhalb einer for-Anweisung wegge-
lassen werden:
for (;;) {
Die Schleifen-Bedingung ist dann immer wahr, so daß es sich bei dieser
Konstruktion einer for-Schleife um eine Endlosschleife handeln würde,
wenn der Programmierer nicht in den Schleifenanweisungen für einen
Abbruch (break) sorgt.
Eine Schleifenkonstruktion, die einen häufigen Grund für Fehler liefert,
soll an einem Beispiel aufgezeigt werden.
Beispiel 5a
Es sollen die Quadratzahlen von 1 bis 10 am Bildschirm ausgegeben werden.
C-Programmteil:
for (i = 1; i <= 10; + + i); — Vorsicht!
printf(”%10d%10d”, i, i * i);
Dieser Programmteil würde nicht das gewünschte Ergebnis liefern, da das nach der for-
Anweisung angegebene Semikolon bewirkt, daß lediglich eine leere Anweisung zur Schlei-
fe gehört.
Die printf-Anweisung gehört also bei dieser Schreibweise nicht mehr zur for-Schleife
und wird deshalb nur 1-mal, und nicht, wie gewünscht, 10-mal ausgeführt. Dieser Pro-
grammteil ergäbe also am Bildschirm folgenden Ausdruck:
11 121
Warum dieser Programmteil einen solchen Bildschirmausdruck liefert, können Sie am
nachfolgenden Programmablaufplan erkennen.
8-13
Programmablaufplan:
Richtig wäre folgender Programmteil:
for (i = 1; I <= 10; -n-1)
printf(”%1Od%1Od”, 1.1 ♦ i);
was folgendem Programmablaufplan entspräche:
8-14
Dieser Programmteil würde also die geforderten Quadratzahlen am Bildschirm ausgeben.
Beispiel 6
Es soll erneut die geometrische Reihe
berechnet werden, wobei n wieder einzugeben ist.
8-15
Programmablaufplan:
Hier haben wir als einzige Schleifenanweisung eine leere Anweisung. Für diesen Pro-
grammablauf ist reih_teil mit dem Wert 1 vorzubesetzen.
8-16
C-Programm:
mainO
C
int zaehl, n;
double reih_teil, summe;
printf("XnXnBerechnung der geometrischen ReiheXn");
printf)J
printf("Geben Sie n ganzzahlig ein ’Xn“);
scanf("Xd",&n);
for (rein_tell=1,summe=l,zaehl=1 ; zaehlon ; relh_teil/=2,sunme* *=reih_teil,♦♦zaehl) ;
printf("XnXnxnDie Summe der geometrischen Reihe bis Xd ist:\n*,n);
printf("XfXn",summe);
Der Komma-Operator erlaubt also, mehrere Ausdrücke zu einem einzi-
gen Ausdruck zu verschmelzen. Diese Technik läßt sich vor allen Din-
gen bei der for-Schleife anwenden, wenn mehrere Schleifenparameter
zu initialisieren und/oder zu reinitialisieren sind.
Generell sollte man jedoch darauf achten, die for-Anweisung nicht zu
überladen. Um die Übersichtlichkeit eines Programms zu wahren, ist es
des öfteren besser, Variablen in eigenen Anweisungen zu initialisieren
oder zu verändern, wenn sie nicht unmittelbar zur Schleifensteuerung
gehören.
Als nächstes werden wir ineinander geschachtelte for-Schleifen ken-
nenlernen.
Beispiel 7
Wir wollen ein Programm schreiben, das das Einmaleins am Bildschirm ausgibt.
Struktogramm:
'Ausgabe einer Ueberschrlft
'for (aussen=1 ; aussen<=lO ; aussenw)
' ’for (innen=l ; innenciQ ; innenw)
• • ♦------------------------------------
1 1 'Ausgabe: aussen, innen,aussenMnnen
8-17
C-Programm:
main ()
<
int
aussen, innen;
printfi"\n\nDas Einmaleinsxn");
printf("==============\n\n\n\n");
for (aussen=l ; aussen< = W ; aussen**)
for (lnnen=i ; innen<z10 ; innen**)
printf(nX30d • X2d » X3d\n",aussen,innen,aussen«innen);
)
Erklärung:
Die Schleifenanweisung der äußeren for-Schleife ist wieder eine for-Schleife; diese inne-
re for-Schleife wird zunächst ausgeführt, d. h. am Bildschirm wird folgendes ausgegeben:
1*1=1
1*2=2
1 * 10 = 10
Die Variable aussen besitzt bei allen 10
Durchläufen der inneren for-Schleife den
Wert 1, während die Variable innen für je-
den Durchlauf um 1 erhöht wird.
Danach wird wieder zur äußeren for-Schleife zurückgekehrt, d. h. jetzt wird aussen um 1
inkrementiert, bevor wieder die innere Schleife 10-mal durchlaufen wird, d. h. am Bild-
schirm wird folgendes ausgegeben:
2*1=2
2* 2 = 4
2 * 10=20
usw.
insgesamt wird also die Schleifenanweisung printf(...) 100-mal ausgeführt.
Um ineinander geschachtelte for-Schleifen besser verstehen zu können, soll zu diesem
Beispiel noch ein Programmablaufplan angegeben werden. Vollziehen Sie anhand dieses
Ablaufplans die einzelnen Schritte des zuvor angegebenen C-Programms nochmals nach.
8-18
Programmablaufplan
8-19
Beispiel 8 |
Es soll ein C-Programm erstellt werden, das von einem Startwert, der einzugeben ist, bis
zu einem Endwert, der ebenfalls einzugeben ist, alle in diesem Intervall liegenden Zahlen in
Dezimal-, Hexa-, Oktal- und Dualdarstellung am Bildschirm ausgibt.
Dieses C-Programm soll auf die Zahlen im Intervall 0 ... 255 beschränkt sein:
Struktogramm:
(Ausgabe einer Ueberschrlft •
! •
'Einlesen der Variable start 1
i •
•Einlesen der variable ende
i i
•Ausgabe von: "Dezimal* “Hexa" "Oktal" "Binaer" '
for (zahl=start ; zahl<=ende ; ♦♦zahl)
♦ - — — ——— — — — — — — _ — — — — — — — _ — _ — — _ 4.
!printf("X18d X9x Xßo ", zahl,zahl,zahl)
♦----------------------------------------------+
•for (1=7 ; i>=0 ; 1--) »
• •printf("Xld",(zahl>>i> & i)
4---+------------------------------------------4
•printf<"Xn") ‘
C-Programm:
main ()
int zahl, start, ende, 1;
printf ("XnXnZahlenumwandlungxn" );
printf("================\n\n\n\n\n");
printf("von ’Xn");
scanf("Xd",Sstart);
printf("XnXnXnbis ?\n");
scanf("Xd“,Sende);
printf("Xn\n\nX2OsXlOsXlOsXi3sXnXn","Dezimal","Hexa","Oktal“,"Binaer");
for (zahl=start ; zahl<=ende ; ♦♦zahl) (
printf(“XiBd X9x Xßo ",zahl,zahl,zahl);
for (1 = 7 ; i> = 0 ; 1 —)
printf (MX1d",(zahl»i) S 1);
printf("Xn");
>
}
8-20
Beispiele für den Programmablaufplan am Bildschirm:
Zahlenumwandlung
von ? 4—'
BIS ? 6—1 Dezimal Hexa Oktal Dual
4 4 4 00000100
5 5 5 00000101
6 6 6 00000110
Zahlenumwandlung
von ? 25 —1
BIS ? 29— Dezimal Hexa Oktal Dual
25 19 31 00011001
26 1A 32 00011010
27 1B 33 00011011
28 IC 34 00011100
29 ID 35 00011101
ZAHLENUMWANDLUNG
VON ? 233—'
BIS ? 237—1 Dezimal Hexa Oktal Dual
233 E9 351 11101001
234 EA 352 11101010
235 EB 353 11101011
236 EC 354 11101100
237 ED 355 11101101
8-21
Erklärung:
Nach Ausgabe der Überschrift wird ein Startwert in die Variable start und ein Endwert in
die Variable ende eingelesen.
Nach erfolgter Eingabe wird der Text:
Dezimal Hexa Oktal Binaer
am Bildschirm ausgegeben.
Die beiden ineinander geschachtelten for-Schleifen sind für die Ausgabe der einzelnen
Zahlendarstellungen in den verschiedenen Zahlensystemen verantwortlich.
In der äußeren for-Schleife wird die Schleifen-Variable zahl verwendet, die zunächst mit
dem Startwert start vorbesetzt wird und in Einserschritten bis zum Endwert ende läuft.
Zur äußeren for-Schleife gehört ein Block von 3 Anweisungen. Die 1. Anweisung dieses
Blocks ist ein printf-Befehl, der den Wert von zahl dezimal, hexadezimal und oktal (über
Formatangaben) am Bildschirm ausgibt.
Die nächste Anweisung dieses Blocks ist eine for-Schleife, die für die Ausgabe von zahl
in Dualdarstellung verantwortlich ist.
Bei der letzten Anweisung dieses Blocks handelt es sich wieder um einen printf-Befehl.
der einen Zeilenvorschub bewirkt.
Die innere for-Schleife, die für die Ausgabe der Dualdarstellung verantwortlich ist, werden
wir jetzt noch etwas ganauer behandeln.
In der inneren for-Schleife wird die Schleifenvariable I verwendet, die zunächst mit 7 vor-
besetzt und dann bei jedem Schleifendurchlauf um 1 dekrementiert wird, bis die Schleifen-
bedingung I >= 0 nicht mehr erfüllt ist; insgesamt wird also die zu dieser Schleife gehörige
Anweisung 8-mal durchlaufen.
Die Schleifenanweisung
printf( ”%1d”, (zahl > > i) & 1)
wollen wir anhand eines Beispiels durchsprechen:
Wenn beispielsweise in der int-Variable zahl momentan der Wert 142 gespeichert ist,
dann wird diese Zahl rechnerintern wie folgt dargestellt:
Vor- 16384 8192 4096 2048 1024
8-22
Beim ersten Durchlauf der inneren for-Schleife ist der Wert der Laufvariable i gleich 7,
d. h. es wird durch den printf-Befehl eine Dezimalziffer ”%1d” und zwar die Ziffer 1 am
Bildschirm ausgegeben:
Beim nächsten Durchlauf der inneren for-Schleife ist der Wert der Laufvariable I gleich 6,
da er um 1 im Reinitialisierungsteil dekrementiert wurde; d. h. es wird durch den printf-
Befehl eine Dezimalziffer ”%1d”, und zwar die Ziffer 0 am Bildschirm ausgegeben:
zahl >> 6 Vor- Si- chen 16364 2*« 8192 2« 4096 2” 2048 2” 1024 2” 512 126 2» 64 2* 32 2* 16 2* 8 2» 4 2* 2 2’ 1 2° = 2
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
(zahl >>6)4 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 = 0
Für die nächsten beiden Durchläufe mit I = 5, i = 4 wird jeweils wieder Ziffer 0 am Bild-
schirm ausgegeben. Den 5. Durchlauf mit i = 3 werden wir wieder genauer betrachten:
zahl >> 3 1 Vor- 16364 8192 2” 4096 2u 2048 2” 1024 2i« 512 256 2* 128 2» 64 2* 32 2* 16 2* 8 V 4 2» 2 21 1 2° = 17
chen 2"
0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
(zahl >>3)4 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 = 1
Für den 5. Durchlauf würde also die Ziffer 1 am Bildschirm ausgegeben.
Insgesamt würde diese Ausführung der inneren for-Schleife also genau die Binärdarstel-
lung der Dezimalzahl 142 mit 8 Bitstellen am Bildschirm ausgeben:
10001110
Es wird also bei jedem Durchlauf der inneren for-Schleife der Inhalt von zahl um i Stellen
(I wird mit 7 initialisiert und nach jedem Durchlauf um 1 dekrementiert) nach rechts ge-
shiftet (geschoben).
Diese Schiebeoperation bewirkt, daß der Inhalt des I. Bits ins O. Bit gebracht wird, d. h. der
Inhalt des i. Bits wird an die letzte Stelle rechts gebracht.
Die Ä-Verknüpfung des so erhaltenen Bitmusters mit 1 bewirkt, daß alle höherwertigen
Bits außer dem 0. Bit auf 0 gesetzt werden; das so erhaltene Bitmuster kann entweder nur
den Wert 1 - wenn das l-te Bit von zahl auf 1 gesetzt war - oder den Wert O - wenn das
8-23
i-te Bit von zahl auf O gesetzt war - enthalten. Die innere for-Schleife gibt also, von links
mit dem 7. Bit beginnend, den Inhalt der einzelnen Binärstellen einer im Rechner darge-
stellten Zahl am Bildschirm aus.
Beispiel 9
Wir wollen ein C-Programm erstellen, das die interne Darstellung einer int-Zahl am Bild-
schirm ausgibt.
Dieses Programm soll unter Verwendung eines Makros, das wird in der schon erstellten
Datei selbst.h definieren, geschrieben werden.
selbst.h
/• SELBST DEFINIERTE MAKROS •/
.................................
«define klein(c) (clDxZO) /•
/•
/♦
/•
/•
idefine grösste) (c&Dxdf)
tdefine volumen(a,b,c) (a*b«c)
tdefine flaeche(x,y) (x«y)
klein = Makrona»e •/
c = formaler Parameter •/
Befehl, der durch diesen Makro- •/
aufruf ausgefuehrt wird, ist •/
dahinter geklammert angegeben •/
«define dual_ziff(zahl,1) (printf(((zahl>>i) & 1) ? "1" : "0'11
C-Programm:
«include “selbst.h"
main ()
(
Int dezl_zahl, j;
printfC’Gib eine ganze Zahl ein !\n*);
scanf("Xd“,&dezi_zahl);
printf("\n\nDiese Zahl hat folgendes Bitmuster :\n
for (J = 15 ; j>=0 ; j —)
dual zifftdezi zahl,j);
printf("\n");
8-24
Beispiel 10
Im letzten Beispiel wollen wir ein C-Programm entwerfen, das den Mittelwert von N Wer-
ten, die alle über Bildschirm einzugeben sind, berechnet. Die Anzahl N der
einzugebenden Werte ist ebenfalls einzugeben.
Mittelwert =» Malier Werte)/N
Struktogramm:
'Ausgabe einer Ueberschrift
'Eingabe von anzanl
•m xttel_wert=O
•for (1=1 ; i<=anzahl ; ♦♦!)
'Eingabe von wert
'»1ttel_wert*=wert
'm ttel_wert/*anzahl
i
'Ausgabe : “Der Mittelwert der eingegebenen werte ist: Xf",»1ttel_wert
C-Programm:
ain ()
<
float wert, Mttel.wert;
int anzahl, i;
printft"X50s\n“,-Mlttelwertberechnung");
printf("X50s\n\n\n\n",-====================“);
printf(“Wieviele werte wollen Sie eingeben ’\n");
scanf("Xd",Sanzahl);
•1ttel_wert=Q;
printfr-\n\n\n-);
for (i = 1 ; i<=anzahl ; ♦ ♦!) <
printf("\nGeben Sie den Xd Wert ein •M,i);
scanf(“Xf“,&wert);
ittel_wert+=wert;
i ttel_wert/=anzahl;
printf(“\n\n\n\nDer Mittelwert der eingegebenen Werte ist: Xf“,bittel.wert);
HINWEIS: 2 = Summenzeichen
8-25
8.3 DIE SCHLEIFENANWEISUNG while
Die while-Schleife ist der for-Schleife sehr ähnlich, denn bei beiden
findet die Abfrage der Schleifen-Bedingung am Anfang statt; deshalb
werden beide im Struktogramm auch gleich dargestellt:
•while (ausdruck) I
I 4---------------♦
• ! '
' • Schleifen-Anweisungen
! ! I
♦------------------------+---------------f
•naechste Anweisung
while (ausdruck)
Schleifenanweisung;
nächste Anweisung;
Im Programmablaufplan ist die Funktionsweise einer while-Schleife
sehr gut zu erkennen:
8-26
Vor jedem Schleifendurchlauf wird ausdruck berechnet. Ist das Ergeb-
nis ungleich 0 bzw. wahr, so wird die Schleife durchlaufen; beim Er-
gebnis 0 bzw. falsch wird die Schleife beendet, d. h. die nächste nicht
zur Schleife gehörige Anweisung wird ausgeführt.
Ist das Ergebnis von ausdruck bereits bei der ersten Berechnung
gleich 0. so wird die Schleife gar nicht durchlaufen, sondern sofort mit
der nächsten nicht zur Schleife gehörigen Anweisung fortgefahren.
Jede for-Schleife
for (ausdrl; ausdr2; ausdr3)
Schleifenanweisung;
läßt sich durch eine while-Schleife formulieren:
ausdrl;
while (ausdr2)
{ Schleifenanweisung;
ausdr3;
Es kann keine allgemeine Regel aufgestellt werden, wann die for- und
wann die while-Schleife verwendet werden soll, for ist dann zu bevor-
zugen, wenn eine einfache Initialisierung und Reinitialisierung vorge-
nommen werden kann; ein typisches Beispiel wäre:
for (i = 1; i <= anzahl; ++ i)
Natürlich kann bei der while-Schleife wieder ein Block von Anweisun-
gen - durch { ... } begrenzt - als Schleifenanweisung angegeben wer-
den.
Beispiel 11
Es soll ein C-Programm erstellt werden, das
- einen momentanen Bonbonpreis,
- eine Inflationsrate
- eine Preisgrenze
über Bildschirm einliest.
Das Programm soll dann berechnen, nach wieviel Jahren bei einer vorgegebenen Infla-
tionsrate (Eingabe) und einem momentanen Bonbonpreis (Eingabe) die ebenfalls eingege-
bene Preisgrenze erreicht bzw. überschritten ist.
8-27
Struktogramm mit for-Schleife:
•Ausgabe einer Ueberschrift
। •
•Eingabe: "Zf“,4bonbon_preis
i •
»Eingabe: "Zf",41nflatlons,rate
i •
•Eingabe: "Zf•,4prels.grenze
! ‘
•Ausgabe: "Ein Bonbon, das heute Z.Zf DM Kostet,bonbon.preis
• "Kostet bei einer Inflationsrate von Z.Zf ProzentXn",inflations rate’
•for (jahre=O ; bonbon_preis < preis.grenze ; ♦♦Jahre)
i +------------------------------------------------------------
• •bonbon_preis^=bonbon_preis*lnflatlons_rate/100
♦---♦----------------------------------------------------------
‘Ausgabe: "nach Zd Jahren mehr als Z.Zf DM",Jahre,preis.grenze
। "Der Preis waere dann Z.2f DM\n",bonbonpreis’
C-Programm mit for-Schleife:
main ()
(
float Donbon_preis, inflations_rate, preis_grenze;
int Jahre;’
printft"\nZ43s","Bonbon");
printft"\nZ43s\n\n\n\n\n" ," = = = = = = • );
printf ('•momentaner Bonbonpreis tin DM) ?"); (+)
scanf("Zf",4bonbon_prels);
printf("Xnlnflationsrate (in Prozent) ?"); (+)
scanf t"Zf",4inflations_rate);
printf("XnPrelsgrenze (in DM) ’*);
scanf("Zf",4preis.grenze);
printf("\n\n\nEin Bonbon, das heute Z.Zf DM KostetXn",oonbon preis);
printf("Kostet bei einer Inflationsrate von X.2f ProzentXn",Tnflat Ions rate);
for (jahre=O ; bonbon.preis < preis grenze ; ♦♦Jahre)
bonbon.preis+=bonbon.preis*inflatlons.rate/1DO;
printf("nach Zd Jahren mehr als Z.Zf DMXnXn",Jahre,preis.grenze);
printf("Der Preis waere dann Z.Zf DMXn",bonbon.preis);
(+) Unter MS-C und LA TTICE-C muß der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher ?" durch ? \ n”.
8-28
Struktogramm mit while-Schleife:
•Ausgabe einer ueberschrift
i
•Eingabe: “Xf*,Sbonbon preis
• "
•Eingabe: “Xf",4inflatlons rate
i
•Eingabe: "Xf",&preis_grenze
i
Ausgabe: “Ein Bonbon, das heute X.2f DM kostet",bonbon_preis
"kostet bei einer Inflationsrate von x.2f ProzentXn",inflatlons_rate
' jahre=O
•while (bonbon_preis < preis.grenze)
' 'bonbon_preiS4=bonbon_pre1s*inflatlons rate/100 !
• !♦♦jähre “ ~ •
♦—------------------------------------------------------------------------------
•Ausgabe: “nach Xd Jahren mehr als X.2f DM“,Jahre,prels_grenze '
"Der Preis waere dann X.2f DM",bonbon_preis ” !
C-Programm mit while-Schleife:
main()
(
float bonbon_preis, inflations_rate, preis_grenze;
int Jahre;"
printf("XnX43s","Bonbon");
pr i ntf(“\nX43s\nXnXn\n\n",“==== = = ");
printf (•'momentaner Bonbonpreis (in DM) ?"); (+)
scanf("Xf",&bonbon_preis);
printf("\nlnflationsrate (in Prozent) ’"); (+)
scanf("Xf",&inflations_rate);
printf("\nPreisgrenze (in DM) ?*); (+)
scanf("Xf",&pre:s_grenze);
printf("\nXnXnEin Bonbon, das heute X.2f DM kostetxn“,bonbon.preis);
printf("Kostet bei einer Inflatlonsrate von X.2f ProzentXn",rnflations_rate);
jahre=O;
while (bonbon_preis < prets_grenze) <
bonbon_preis* = bonbon_preis«inflations_rate/1 DO;
♦♦jahre;
>
printf("nach Xd Jahren mehr als X.2f DMXnxn“»Jahre,preis_grenze);
printf("Der Preis waere dann X.2f DM\n",bonbon_prels);
(+) Unter MS-C und LA TTICE-C muB der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen Ersetzen Sie
daher ?” durch ? \ n”.
Beide C-Programme, das mit for-Schleife und das mit while-Schleife, leisten das gleiche.
8-29
Programmablaufbeispiele am Bildschirm:
Bonbon
MOMENTANER BONBONPREIS (iN DM)? 0. 1 —-1
Inflationsrate (in Prozent)? 5 —J
Preisgrenze (in DM)? 10 —1
Ein Bonbon, das heute 0.10 DM kostet
KOSTET BEI EINER INFLATIONSRATE VON 5.00 PROZENT
nach 95 Jahren mehr als 10.00 DM
Der Preis wäre dann 10.30 DM
Bonbon
MOMENTANER BONBONPREIS (iN DM)? 0.2—J
Inflationsrate (in Prozent)? 130—J
Preisgrenze (in DM)? 100—J
Ein Bonbon, das heute 0.20 DM kostet
KOSTET BEI EINER INFLATIONSRATE VON 130.00 PROZENT
nach 8 Jahren mehr als 100.00 DM
Der Preis wäre dann 156.62 DM
Bonbon
MOMENTANER BONBONPREIS (iN DM)? 0.05 —1
Inflationsrate (in Prozent)? 2.3—1
Preisgrenze (in DM)? 11.60—J
Ein Bonbon, das heute 0.05 DM kostet
KOSTET BEI EINER INFLATIONSRATE VON 2.30 PROZENT
nach 240Jahren mehr als 11.60 DM
Der Preis wäre dann 11.73 DM
8-30
Beispiel 12
Es soll ein C-Programm erstellt werden, das Spiel 21 realisiert. Die Regeln dieses Spiels
sind wie folgt: Zunächst liegen 21 Streichhölzer auf dem Tisch. Insgesamt sind 2 Spieler
zugelassen. Jeder dieser beiden Spieler nimmt abwechselnd zwischen 1 und 4 Streichhöl-
zer. Der Spieler, der das letzte Streichholz vom Tisch nimmt, hat verloren.
Struktogramm:
Ausgabe einer ueoerschrift
suaae.streichhoelzer>2i
while isuaae.streicnnoelzer > 0)
'Ausgabe: "wieviele Streichnoelzer niaast du, Spieleri ?•
Eingabe: "Xd“.Sweg.streichhoelzer
•while tweg.streicnhoelzerd li weg.streichhoelzer>4)
Ausgabe: "Du darfst nur zwiscnen i und 4 Streicnhoelzer von Tisch nenaen
Ausgabe: “Alse, wiederhole deine Eingabe •
Ausgabe: *uieviele Streicnnoelzer masst du, Spielen ’•
•Eingabe: *Xd*,lweg streichhoelzer
1 suaae.streichhoelzer-«weg.streichhoelzer
if (sueee.streichnoelzer<»0)
•Ausgabe: "Es liegen keine Streich-*
•hoelzer aehr auf dea Tisch*
•Ausgabe: "Spieleri, du hast verloren •
Ausgabe: *Es liegen noch *
Xd - ,sunae_streichhoelzei
•Streichhoelzer auf aea r
Ausgabe: *wieviele Streichhoelzer*
•nieast du, Spieler? ”
Emgaoe: *Xd*,Xweg.s treichhoelzer
while (weg.streichhoelzerci li ueg.stretchhoelzer»«)
Ausgabe
* Ausgabe:
Ausgabe:
’Du darfst nur zwischen*
1 und 4 Streichhoelzer voa Tisch nehaen1
‘Wiederhole deine Eingabe •’
wieviele Streichhoelzer*
Eingabe:
Xd’,&weg.streichhoelzer
if (suaae.streichhoelzeroO)
Ausgabe: -Es liegen keine
•Streichhoelzer
Ausgabe: ‘'Spieler?, du*
• hast verloren
Ausgabe: “Es liegen noch
1 •‘Xd’.suaee stre
• Streicnhöelzei
• "auf den Tisch*
8-31
C-Programm:
.....................................................................................................
/•••••.....................................................................................
/*•
S P I E L 2 1 ••/
....................................................................................
..................................................................................
main ()
<
int summe.streichhoelzer, weg.strelchhoelzer;
..............................................................
/♦ Ausgabe einer Ueberschrift und Vorbesetzen der int-Varlablen •/
/• summe 5trelchhoelzer mit Wert 21 (Anfangswert) •/
.............................................................
printf("\nX45s","S p 1 e 1 2 1");
printf(•\nX45s\n\n\n\n\n",;
summe.streichhoelzer=21;
............................................................................• •/
/♦ Solange der Inhalt der int-Variable summe.stretchhoelzer > □ ist.........♦/
/• wird zunaechst die Anzahl der Streichhoelzer, die Spielen nimmt, ♦/
/• in die int-Vanable weg.strelchhoelzer eingelesen. •/
/• ♦/
/♦ Danach wird ueberprueft, ob die eingegebene Zahl im geforderten ♦/
/• Intervall 11,41 liegt; wenn nicht muss Spielen nochmals eine •/
/♦ Zahl eingeben, solange bis seine eingegebene Zahl im geforderten •/
/• Intervall liegt. ♦/
/♦ Nach dieser Eingabe wird der Inhalt von weg.streichhoelzer ♦/
/• vom Inhalt der Variablen summestreichhoelzer-subtrahlert. ♦ /
/• ” ♦/
/♦ Als naechstes wird ueberprueft, ob der Inhalt der Variable • /
/♦ summe_streichhoelzer <= 0 ist; •/
/• ” •/
/• wenn ja, dann wird am Bildschirm ausgegeben, dass Spieleri •/
/♦ verloren hat und die whlle-Schleife verlassen •/
/• •/
/• wenn nein, dann wird alle eben beschriebenen Schritte auch fuer •/
/♦ Spieler? vorgenommen. •/
/• ...................... •/
..................................................................
while (5umme_strelchhoelzer > Q) {
printf("Wieviele Streichhoelzer nimmst du, Spieleri ?\n");
scanf("Xd",&weg_stret chhoelzer);
while (weg_strelchhoelzer<i l1 weg streichhoelzer>4) (
printf("\n\n\nDu darfst nur zwischen 1 und 4 Streichhoelzer\n");
printf("vom Tisch nehmen !\n\n\n\n");
printf("Also, wiederhole deine Eingabe '\n\n\n");
printf("Wieviele Streichhoelzer nimmst du, Spielen ?\n");
scanf("Xd",iweg strelchhoelzer);
}
summe.streichhoelzer-=weg.strelchhoelzer;
If (summe.streichhoelzer<=0) (
printf(“\n\n\n\nEs liegen keine Strelchhoelzer mehr auf dem Tiscn");
printf("Xn\n\nSpielerl, du hast verloren 1\n\n\n\n\n");
>
eise (
printf("\n\n\n\nEs liegen noch Xd Streich",summe strelchhoelzer);
printf("hoelzer auf dem Tisch\n\n\n\n\n\n\n");
printf("Wieviele Strelchhoelzer nimmst du, Spieler? ’\n*);
scanf ("Xd",&weg.strelchhoelzer);
while (weg_streichhoelzer<i 11 weg streichhoelzer>4) (
printf ("\n\n\nDu darfst nur zwischen 1 und 4 Streichhoelzer\n•);
pnntfCvom Tisch nehmen '\n\n\n\n");
printf("Also, wiederhole deine Eingabe !\n\n\n">;
printf("Wieviele Strelchhoelzer nimmst du, Spieler? ’\n");
scanf ("Xd",Sweg strelchhoelzer);
summe.stre1chhoelzer-=weg.streichhoelzer;
8-32
if (summe_streichhoelzer<=0) {
printf("\n\n\n\nEs liegen keine Streichhoelzer «ehr auf dem Tisch");
printf("\n\n\nSpieler2, du hast verloren !\n\n\n\n\n");
)
eise (
printf("\n\n\n\nEs liegen noch Xd Streich“rsumme_streichhoelzer);
printf("hoelzer auf dem Tisch\n\n\n\n\n\n\n");
Em Fahrkartenautomat soll so gesteuert werden, daß er aus dem zu zahlenden Fahrpreis
und dem eingeworfenen Geldbetrag das Wechselgeld ermittelt. Das Wechselgeld soll je-
doch so ausgegeben werden, daß der Automat möglichst wenig Münzen zur Ausgabe be-
nötigt.
Es gelten folgende Annahmen und Einschränkungen:
-Der zu zahlende Fahrpreis ist als float-Größe einzugeben (2 Stellen hinter
dem Punkt, z. B. 1.40).
- Der Automat verfügt über folgende Münzarten:
5 DM, 2 DM, 1 DM, 50 Pf, 10 Pf, 5 Pf, 2 Pf, iPf
-Auszugeben ist die Anzahl der jeweiligen Münzart, die als Wechselgeld zu-
rückgegeben wird.
- Der eingeworfene Geldbetrag muß größer oder gleich dem zu zahlenden Fahr-
preis sein.
Ergebnisse des Programmlaufs:
Eingabe: ZV ZAHLENDER FAHRPREIS: EINGEWORFENER GELDBETRAG: 1.20DM 5.00 DM
Ausgabe: 1 * 2 DM 1 * 1DM 1 * 50 Pf 3 * 10 Pf Rückgabe: 3.80 DM
Eingabe: ZU ZAHLENDER FAHRPREIS: 17.58 DM EINGEWORFENER GELDBETRAG: 20.00 DM
Ausgabe: 1 * 2 DM 4 * 10 Pf 1 ♦ 2 Pf Rückgabe: 2.42 DM
8-33
Struktogramm:
* Ausgabe
einer Ueberschrift
1 Eingabe
von Fahrpreis
•Eingabe
von geldbetrag
•rueckgabe* fahrpreis-geldbetrag
•while (rueckgabe>O)
•Ausgabe: "Es fehlen noch X.2f DM",rueckgabe
"Bitte weiteres Geld einwerfen •"
•Eingabe von geldbetrag
! rueckgabe- = geldbetrag
► 4- gesamt^rueckgabe--rueckgabe
if (rueckgabe==0)
J N
•Ausgabe: "Keine Geldrueckgabe 'while (rueckgabe>=5)
'♦♦dm5
' rueckgabe-=5.0
'while (rueckgabe>=2)
Irueckgabe-=2.0
•while (rueckgabe> = l)
1♦♦dml
'rueckgabe-=1.0
•while (rueckgabe>=Q.5)
'♦♦pfSO
‘rueckgabe-=0.5
'while ( rueckgabe> = 0.1)
' rueckgabe-=O.1
'while (rueckgabe>=0.05)
'rueckgabe-=0.05
•while (rueckgabe> = 0.02)
•wpf2
' rueckgabe-=0.02
if (dm5)
N
•Ausgabe: "X40d • 5 DM",dm5
If (dm2)
N
»Ausgabe: "X40d • 2 DM",dm2
• J
N
•Ausgabe: "X40d • 1 DM“,dml
If (pf50)
N •
•Ausgabe: "X40d * 50 PF-,pf50
8-34
‘ If (pf1D) •
! J N '
‘Ausgabe: "X40d • 10 PF",pflO !
4-------------------------------------------4----♦
» if <pf5) !
’ J N •
♦-------------------------------------------+----+
!Ausgabe: "X40d • 5 PF",pf5 ! •
♦-------------------------------------------4----♦
• if (pf2) !
• J N •
+-------------------------------------------+----*
'Ausgabe: "X40d • 2 PF",pf2 ' •
4-------------------------------------------—4.--+
• if (rueckgabe>=0) !
’ J N •
»Ausgabe: "X40s • 1 PF","1" • •
♦----------------------------------------+ +
I !
Ausgabe: "X50sM,"__________________ •
•Ausgabe: "X40s X.2f‘DM"7"RÜeckgäbe:"»gesamt'
C-Programm:
.......................................•..............
FAHRKARTENAUTOMAT
/.......................................................
.....................................................
main ()
( float fahrpreis, geldbetrag, rueckgabe, gesamt;
int dm5=0, dm2=0, dm1=0, pf50=0, pf10:0, pf5=0, pf2=0;
/* - Ausgabe einer ueberschrlft • /
/• - Eingabe von fahrpreis und geldbetrag •/
/• - Berechnung des RueckgaDeoetrags (Ergebnis in der Var. rueckgabe) •/
printf(“XnXSOs","Fahrkartenautomat");
printf("\nX50s\n\n\n\n\n‘," = = = = = = = = = = = = = = = = = ");
printf("zu zahlender Fahrpreis (+)
scanf ("Xf ,&fahrpreis) ;
printf("\nemgeworf ener Geldbetrag ?"); (+)
scanf ("Xf ,Sgeldbetrag) ;
rueckgabe«fahrpreis-geldbetrag;
...............................................................................
/• Es muss solange Geld nachgeworfen werden» bis •/
/• geldbetrag >« fahrpreis ♦/
/...............................................................................
while (rueckgabe>01 (
printf("\n\nEs fehlen noch X.2f DM",rueckgabe);
printf("\nBitte weiteres Geld einwerfen !\n") ;
scanf("Xf"»Sgeldbetragl ;
rueckgabe-:geldbetrag;
)
gesamt«rueckgabe=-rueckgabe;
..........................................................................
/• Falls der Wert der Variablen rueckgabe gleich Null, dann wird •/
/• am Bildschirm der Text : Keine Rueckgabe ausgegeben. •/
/• •/
/• Falls eine Rueckgabe erforderlich ist (Wert von rueckgabe ungl. Null)*/
/• dann werden hier die einzelnen Variablen, die die betreffenden •/
/♦ Muenzen zaehlen, mit der ermittelten Anzahl belegt (in den while- •/
/• Schleifen wird dies vorgenommen) •/
/• Die einzelnen if-Anweisungen besorgen dann die Ausgabe der Muen- ♦/
/♦ zenanzahl am Bildschirm. •/
8-35
f
/•
Ab Ende wird noch der gasaate Rueckgababetrag aa Bildachire aut- • /
gegeben. >/
if (rueckgabe»»0)
printf(*\n\n\n\nKeine Geldrueckgabe *\n")|
elae (
while (rueckgäbe>"5) <
♦♦da5|
rueckgabe-«3.0| >
while (rueckgabe>«2l (
♦*da2|
rueckgabe-«2.0| >
while (rueckgabe>»l) (
wdal|
rueckgabe-"!.0| >
while (rueckgabe>wO.5) (
♦♦pf50j
rueckgabe*"0.5; )
while (rueckgabe>«0. II (
♦ *pf 10|
rueckgabe-»O.1; )
while (rueckgabe>>0.05) (
♦♦p<5;
rueckgabe-«O.05j >
while <rueckgabe>wO.02) (
♦♦pf2|
rueckgabe-«O.02| >
if (da5>
printf C\nX40d • 5 0Hh,de5)j
if (da2)
printf("\nX40d ♦ 2 DH",de2)|
if (dal)
printf("\nX40d • 1 DN’.dallj
if (pf50)
printf("\nX40d • 50 PF’,pf50)j
if (pflO)
printf CXnXlOd • 10 PF-,pflO) j
if (pf5)
printf("\nX40d < 5 PF-,pf3)j
if (pf2)
printf("\nX40d • 2 PF',pf2l|
if (rueckgabe>«0.01I
printf("\nX40i • 1 PF\"1")|
printf C\nX50f,'________________•) ।
printf("\nX40s X.2f DN\na,"Rueckgabei"।geaaat)।
(+) Unter MS-C und LA TTICE-C muß der Text in pnntf mit \ n abge-
schlossen werden, um die Textdarstellung auszulosen. Ersetzen Sie
daher ?” durch ? \ n".
ANMERKUNG ZU OBIGEM BEISPIEL:
Um bei floating point Ungenauigkeiten zu vermeiden, wäre es in diesem Beispiel besser
mit Int-Größen (Pfennig-Beträgen) anstelle von float-Größen (DM-Beträgen) zu arbeiten.
8-36
8.4 DIE SCHLEIFENANWEISUNG
do ... while
Im Unterschied zur for- und while-Schleife wird bei der do ... while-
Schleife die Schleifenbedingung am Ende abgefragt, d. h. die Schleifen-
anweisung wird zumindest einmal ausgeführt, was bei for- und
while-Schleifen nicht gewährleistet ist.
Die do ... while-Schleife in C ist vergleichbar mit der REPEAT ...
UNTIL-Schleife in der Programmiersprache PASCAL.
Es gilt folgende Syntax:
do
Schleifenanweisung;
while (ausdruck);
nächste Anweisung;
Im Programmablaufplan ist die Funktionsweise einer do ... while-
Schleife sehr gut zu erkennen:
Nach jedem Schleifendurchlauf wird ausdruck berechnet. Ist das Er-
gebnis ungleich O bzw. wahr, so wird die Schleife nochmals durchlau-
fen; beim Ergebnis 0 bzw. falsch wird die Schleife beendet, d. h. die
nächste, nicht zur Schleife gehörige Anweisung ausgeführt.
8-37
Da die Abfrage bei do ... while-Schleifen am Ende stattfindet, wird
diese Schleife im Struktogramm wie folgt dargestellt.
‘Schleifen-Anweisungen
‘while (ausdruckl
'naechste Anweisung
do ... while wird wesentlich seltener benutzt als for und while. Trotz-
dem ist die do ... while-Schleife in manchen Anwendungsfällen sehr
nützlich.
Auf den nächsten Seiten werden wir einige Beispiele zur Anwendung
von do ... while behandeln.
Beispiel 14
Der größte gemeinsame Teiler ggT und das kleinste gemeinsame Vielfache kgV zweier
eingegebenen Int-Zahlen soll berechnet und ausgegeben werden.
Den ggT zweier Zahlen kann man nach dem EUKLID'schen Algorithmus bestimmen:
Die größere Zahl wird der Divident, die kleinere Zahl der Divisor. Ist der Divisionsrest gleich
0, so ist der Divisor der ggT. Ist der Divisionsrest ungleich 0. so wird der Divisor zum Divi-
denden und der Divisionsrest zum Divisor. Ist der neue Divisionsrest gleich 0, so ist der
letzte Divisor der ggT. Ist der Divisionsrest ungleich 0. so wird der Divisor zum Dividenden
und der Divisionsrest zum Divisor, usw.
Das kgV der zwei eingegebenen Zahlen ist dann die Multiplikation dieser Zahlen, geteilt
durch den ggT.
HINWEIS: Wenn bei do ... while nur eine Schleifenanweisung vorliegt, kann auf die
Klammerung mit{.}verzichtet werden. Trotzdem ist es empfehlenswert, diese
anzugeben, um das Schlüsselwort while als Ende einer do ... while- und
nicht als Anfang einer while-Schleife zu kennzeichnen.
8-38
Beispiel zur Erklärung des Algorithmus:
eingegebene Zahlen: 42, 12
42 : 12 = 3 Rest 6
12: 6 = 2 Rest O-«- Endekriterium
ggT=6
kgV = 12 = 84
6
Struktogramm:
'00
1 ‘Ausgabe: "Geben Sie zwei ganze Zahlen ein '• •
♦ 'Eingabe: "Xd Xd"t÷nt,&divisor ’
’ ’getchart) /• ist notwendig, da Benutzer seine Eingabe •/ •
‘ » /♦ «it Carnage Return-Taste abschliesst •/ ’
' ' «ultipl=dlvident»dlvisor •
if (divisor>divident) •
! J N »
♦---------------------------------------------------------------4---4
• hilf=divident ‘ »
'divident=dlvisor ! I
idivisor=hllf I ’
Ido •
i 4-----------------------------------------------------------------4
• Irest s divident X dlvisor /* * X ist modulo-Operator •/ 1
• 'divident = dlvisor /• (ermittelt Rest einer Ganz •/ '
I Idivisor « rest /• zahldivlsion» •/ •
I 4-----------------------------------------------------------------4
•while (divlsorXD ’
•Ausgabe: "ggT = Xd",divident
•Ausgabe: "kgV s Xd",sultipl/dlvident
i
•Ausgabe: "Wollen Sie noch zwei Zahlen eingeben ( J/N > 7"
while ( getchar() == J' )
8-39
C-Programm:
/•eataeeaaeaeeeeaaeeeeeeaaeeeeeaeeeeeeeeaeeeeeeeeaaeeeeeeeeeeeeeeeeeeeeeeeeeee/
/•• grossster geweinsaaer Teiler (ggT) und • •/
kleinstes geeeinsaees Vielfache« (kgV) zweier eingegebener Zahlen es/
/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeecaeceeaaeeeeeeeeeeeeeeeeeeeeeeeeeee/
/•*»••«••»•»•••«••••••••••••«•»•«•••••*»•«••»»••••••»»•#•••»••••*«»••••••»•*••/
linclude <stdio.h>
(1)
int divident, divisor, aultipl, rett, hilft
do (
printf("\n\n\n\n\n\n6eben Sie zwei ganze Zahlen ein !\n">|
scanf Cid Xd•,Adivident,4dlvisor>;
getcharO; /• notwendig, da Benutzer seine Eingabe •/
/• alt Carriage-Return-Taste abschliesst • /
aultipl>diviCent»dlvisor;
if (divisor>divident) < /• Vertauschen der Werte von divident •/
hi If-divident; /• und divisor, wenn divisor > divident «/
divldent"divisor|
divisorshilf|
>
do ( /• EUKLIDsche Algoritheust «/
rest « divident X divisor;
divident • divisor; /e Nach Verlassen dieser Schleife •/
divisor rest; /• befindet sich ggT in der e/
> while (divisor>0); /« Variablen divident «/
printfC\n\n\nggT • Xd\n",divident!। /• Ausgabe des ggT •/
printfCkgV Xd\n\n\n\n\n*,aultipl/divident»| /• Ausgabe des kgV 0/
printf(*Wollen Sie noch zwei Zahlen eingeben ( J/N )
) while (getchari) J);
(1) Beachten Sie bei diesem Aufruf, in welchem Laufwerk sich die Dis-
kette mit stdio.h befindet;
u. U ist die Angabe w inciude <b: stdio.h > erforderlich.
Beispiele für den Programmablauf:
Geben Sie zwei ganze Zahlen ein!
18 552 ->
ggT = 6
kgV= 1656
Wollen Sie noch zwei Zahlen eingeben Q/N)? J
8-40
Geben Sie zwei ganze Zahlen ein!
443 63—*
ggT = 1
kgV = 27909
Wollen Sie noch zwei Zahlen eingeben (J/N)?J—*
Geben Sie zwei ganze Zahlen ein!
2331 9—*
ggT = 9
kgV = 2331
Wollen Sie noch zwei Zahlen eingeben (J/N)?J —-*
Geben Sie zwei ganze Zahlen ein!
567 54—*
ggT = 27
kgV = 1134
Wollen Sie noch zwei Zahlen eingeben (J/N)? N —1
Beispiel15
Es soll über den Bildschirm eine Folge positiver float-Zahlen eingegeben werden.
Das Programm soll nach Eingabe einer negativen Zahl das Maximum aller eingegebenen
Zahlen am Bildschirm anzeigen; nach dieser Ausgabe soll das Programm beendet sein.
Beispiele für den Programmablauf:
Ihre Eingabe können Sie durch eine negative Zahl beenden
1. Zahl? 7.3—1
2. Zahl? 129.5—*
3. Zahl?0—J
4. Zahl? 8.923—*
5. Zahl? 17.44—*
6. Zahl ? 2135.7—*
7. Zahl ?-2.0—*
Das Maximum der eingegebenen Zahlen ist: 2135.700000
8-41
Ihre Eingabe können Sie durch eine negative Zahl beenden
1. Zahl ? 12.173—*
2. Zahl ? 133.5—*
3. Zahl ? 1758.9 —•
4. Zahl ? 2.9—* 1
5. Zahl ? 1.0—1
6. Zahl ? 9.871—1
7. Zahl ? 14.947—>
8. Zahl ? 18745.3—1
9. Zahl ? 2894.735—1
10. Zahl ? 8047.0—*
11. Zahl ? 12489.73—*
12. Zahl ? 4.2—*
13. Zahl ?—3.0—1
Das Maximum der eingegebenen Zahlen ist: 18745.300000
Struktogramm:
' zaehler=l
i
•Ausgabe: "Ihre Eingabe koennen Sie durch eine negative Zahl Deenden
'Ausgabe: "Xd.Zahl ?”,zaehler
'Eingabe: "Xf",&max
do
♦---------------------------------------------------------------------------
(♦♦zaehler
•Ausgabe: "Xd.Zahl ?"1zaehler
•Eingabe: "Xf",&zahl
? if (zahl>max) '
! J N •
'Bax=zahl i i
while (zahl>=0)
(Ausgabe: -Das Maxleum der eingegebenen Zahlen ist: Xf",max
8-42
C-Programm:
MAXIMUM EINER POSITIVEN ZAHLENREIHE ERMITTELN ••/
mainf)
(
int zaehler=1;
float zahl, max;
printf("\n\n\n\n");
printf("Ihre Eingabe koennen Sie durch eine negative Zahl Beenden !\n\n-);
printf("\n\nZd.Zahl »zaehler);
scanf("Zf",&max);
do (
♦♦zaehler;
printf("Zd.Zahl zaehler); (+)
scanf(“Zf",&zahl);
if (zahl>max)
max=zahl;
} while (zahl>=0);
printf("\n\n\nDas Maximum der eingegebenen Zahlen ist: Zf\n",max);
(♦) Unter MS-C und LATTICE-C muß der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher ?“ durch ?\n".
8.5 DIE ANWEISUNG break
Nicht immer ist es sinnvoll, eine Schleife mit dem Abbruchkriterium zu
verlassen. Die break-Anweisung dient dazu, die innerste Schleife mit
for, while und do sowie eine switch-Anweisung vorzeitig zu verlas-
sen; die Syntax lautet einfach
break;
Beispiel zur Erläuterung von break:
while (ausdruck) {
• • • •
E— break;
}
nächste Anweisung;
8-43
Bei mehrfach ineinandergeschachtelten Schleifen bewirkt break nur
den Abbruch der innersten Schleife bzw. der switch-Anweisung:
while (ausdruck) {
w___anweisungen;
do{
d__anweisungen;
for(ausdr1; ausdr2; ausdr3) {
------break;
} ‘
—►nächste d____anweisung;
} while (ausdruck);
nächste w___anweisung;
} ’
Es soll wieder der größte gemeinsame Teiler ggT und das kleinste gemeinsame Vielfache
kgV zweier eingegebener int-Zahlen berechnet werden; in diesem Beispiel soll aber die
do ... while-Schleife sofort verlassen werden, wenn der Rest einer ganzzahligen Division
gleich 0 ist.
Eine solche Vorgehensweise hat zur Folge, daß sich der ggT der zwei eingegebenen Zah-
len nach Verlassen der Schleife in der Variablen divisor und nicht in der Variablen
divident (wie im letzten Beispiel) befindet.
8-44
Struktogramm:
do
•Ausgabe: "Geben Sie zwei ganze Zahlen ein *
•Eingabe: "Xd Xd",idLvident,&dlvisor
1 getcnaro
»/• notwendig, da Benutzer seine Eingabe •/
’/♦ alt Carriage Return-Taste aoschllesst •/
1 «ultipl=d i vldent»di visor
if <01visor>divident)
N
• hilfsdivident
' dlvident = divisor
' divlsorchilf
>do
divident x dlvisor
N •
1break
•divident = dlvisor
•while (divisorXJ)
•Ausgabe: "ggT = Xd"fdlvisor
•Ausgabe: "kgv = Xd*Taultipl/divisor
‘Ausgabe: "Wollen Sie noch zwei Zahlen eingeoen ( J/N )
while ( getchart)
8-45
C-Programm:
/es groesster geaeinssasr Teiler (ggT) und •
/es kleinste« geeeinsaaes Vielfache« (kgV) zweier eingegebener Zahlen • •/
ude (stdio.h)
printf(a\n\n\n\n\n\n6eben Sie zwei ganze Zahlen ein \na>|
scanf(’ld Xd*,ldivident,ldivisor)।
getcharO) /• notwendig, da Benutzer seine Eingabe •/
i* eit Carriage-Return-Taste abschliesst «/
ultipl»divident»divi «orj
(divisor>divident)
hilf"dividenti
divident"divisor।
divisor"hilf।
Vertauschen der Werte von divident
und divisor, wenn divisor > divident
do
divident X
t 0)
EUKLIDsche Algorithausi
divident • divisorf
> while (divisor>0>|
befindet sich ggT in der
Variablen divisor
printf (*\n\n\nggT • Xd\na,divisor)j
prlntf(*kgV Xd\n\n\n\n\n*,aultipl/divisor)।
Ausgabe
Ausgabe
ggT
kgV
printf<"Mol 1 en Sie noch zwei Zahlen eingeben <
> while (getcharO 'J*>|
J/N
Theoretisch läßt sich jede Problemstellung auch ohne break-Anwei-
sungen lösen, jedoch führt dies manchmal auf recht umständliche und
unübersichtliche Programmkonstruktionen.
HINWEIS: *) Alternative:
for (;;>{
rest - divident % divisor;
lf(rest==O)
break;
divident divisor, divisor = rest;
8-46
~—— ---------------------------------------------------------
In obigem Beispiel hat die Verwendung von break keine Vorteile mit
sich gebracht. Im Gegenteil: Das Laufzeitverhalten des Programms
wurde verschlechtert, da bei jedem Schleifendurchlauf eine Abfrage auf
rest == 0 stattfindet; dieses obige Programm realisiert allerdings ge-
nau den von EUKLID angegebenen Algorithmus, da sich der ggT, wie
gefordert, in der Variablen divisor befindet.
8.6 DIE ANWEISUNG conti nue
Soll während des Programmlaufs eine Schleife abgebrochen und mit
dem nächsten Durchlauf der gleichen Schleife fortgefahren werden, so
ist die continue-Anweisung zu verwenden.
Die continue-Anweisung ist also der break-Anweisung ähnlich; sie
bewirkt allerdings im Unterschied zur break-Anweisung nicht den Ab-
bruch der gesamten Schleife, sondern nur den Abbruch des aktuellen
Schleifendurchlaufs, also einen Sprung zum Schleifenende, continue
leitet somit unverzüglich den nächsten Schleifendurchlauf ein.
Auf den nächsten Seiten werden wir uns die Wirkungsweise dieser An-
weisung in den drei Schleifenarten; for, while und do ... while etwas
genauer ansehen; steht continue innerhalb einer switch-An-
weisung*)und diese innerhalb einer Schleife, so sorgt continue für
sofortige Wiederholung der Schleife.
Allgemein kann gesagt werden, daß continue-Anweisungen seltener
als break-Anweisungen benötigt werden und daß auch continue nicht
unbedingt erforderlich wäre, sich aber in manchen Situationen als sehr
nützlich erweist.
HINWEIS: *) continue bezieht sich nur auf Schleifen, nicht auf die switch-Anweisung.
8-47
while (ausdruck) {
anweisung x;
if (ausdr)
continue;
anweisung y;
} ’
nächste Anweisung;
8-48
do{
anweisung x;
if (ausdr)
continue;
anweisung y;
} while (ausdruck)
nächste Anweisung;
nächste
Anweisung
8-49
Die continue-Anweisung bewirkt also eine sofortige Wiederholung der
betreffenden Schleife.
Bei while und do ... while bedeutet dies, daß sofort die Bedingung
(ausdruck) erneut bewertet wird.
Bei for wird als nächstes die Reinitialisierung (ausdr3) durchgeführt,
danach zur Schleifenbedingung (ausdr2) verzweigt.
8-50
Beispiel 17
Es sollen alle vollkommenen Zahlen zwischen x und y am Bildschirm ausgegeben werden,
wobei x und y einzugeben sind.
Eine vollkommene Zahl liegt dann vor, wenn die Summe aller Teiler einer Zahl Z (ohne Z
selbst) gleich der Zahl Z ist. Hier Beispiele:
6 ist eine vollkommene Zahl: 1+2 + 3
28 ist eine vollkommene Zahl: 1 + 2 + 4 + 7 + 14
Beispiel für den Programmablauf am Bildschirm:
VOLLKOMMENE ZAHLEN VON? 3—-* 1
VOLLKOMMENE ZAHLEN BIS ? 1000 —1
Die vollkommenen Zahlen von 3 bis \000 sind:
6
28
496
Struktogramm:
i
'Ausgabe einer Ueberschrlft !
Eingabe: "Xd’,ivon i
' I
'Eingabe: "Xd”,&bis •
' !
'Ausgabe: “Die vollkommenen Zahlen von Xd bis Xd sind:",von,bis '
'for (aussen=von ; aussen<=bls ; *+aussen) •
' 'suimO i
! »for (innen:1 ; innen<=aussen/2 : ++innen)
' ---------------------------------------------------------------------♦
I ! I if ((aussen X innen) !« 0) •
’ ' ! J N •
' ' -----------------------________------------------—— ♦
I * 'continue i »
! * !su«i+ = innen i
. . ♦------------------------------------------------------------------------f
! ! ! if (sua > aussen) •
1 ' J N !
' • 'break
! I if (sum *8 aussen) •
J I J Ni
• 'Ausgabe: *Zd"tsua • «
8-51
C-Programm:
main ()
<
int sum, innen, aussen, von, bis;
printf("XnXnXnErmittlung von vollkommenen ZahlenXn");
printf ( " = = = = = = = = = = ::::::::::::: ":”:::“:\n\n\n\n" ) J
printf("Xnvollkommene Zahlen von ?"); (+)
scanf("Xd“ ,4von);
printf("Xnvollkommene Zahlen bis ?“); (+)
scanf("Xd",ibis);
printf("XnXnXnXnDie vollkommenen Zahlen von Xd bis Xd sind:Xn\n",von,bis);
for (aussen=von ; aussen<=bis ; ♦♦aussen) (
sum=Q;
for (innen=1 ; lnnen<=aussen/2 ; ♦♦innen) (
if ((aussen X innen) != 0)
continue;
sum+=innen;
If (sum > aussen)
break;
}
if (sum = = aussen)
printf("XdXn",sum);
1
(+) Unter MS-C und LATTICE-C muß der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher ?” durch ? \ n"
Dieses Programm hätte man selbstverständlich auch ohne break- und continue-Anwei-
sung realisieren können:
main()
<
int sum, Innen, aussen, von, bis;
printf(“XnXnXnErmittlung von vollkommenen ZahlenXn");
printf("============== = = = = = = = = = = = = = =======\n\n\n\n");
printf ("Xnvollkomfiene Zahlen von ?); (+)
scanf("Xd",&von);
printf("Xnvollkommene Zahlen bis ?"); (+)
scanf("Xd",&bis);
printf(“XnXnXnXnDie vollkommenen Zahlen von Xd bis Xd sind:XnXn“,von,bis);
for (aussen=von ; aussen<=ois ; ♦♦aussen) {
sum=0;
for (innen=l ; innen<=aussen/2 ; ++10060)
if ((aussen X innen) == 0)
sum+=innen;
if (sum == aussen)
printf("XdXn",sum);
)
69 Unter MS-C und LATTICE-C muß der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher ?” durch ? \ n".
8-52
Beispiel 18
Mit einem C-Programm soll eine Ölzentralheizung gesteuert werden.
Das Programm erhält fortlaufend von Meßstellen aktuelle Temperaturwerte, die mit einem
geforderten Sollwert verglichen werden. Bei Abweichungen ist ein Programmteil auszufüh-
ren, der für die Regelung der Anlage zuständig ist:
for < ; ; ) (
/• evtl. Tiner ablaufen lassen •/
/• Lesen der momentanen Temperatur von Messtellen •/
if (Temperatur »= soll^wert)
continue;
/• Programmteil, der bei Sollwertabweichungen die •/
/• erforderlichen Schritte (Ofen einschalten, ♦ /
/• Oelzufuhr regeln, usw—) einleitet •/
Für Programme aus diesem Anwendungsbereich, bei denen das ständige Lesen einer phy-
sikalischen Größe erforderlich ist, verwendet man am günstigsten Endlosschleifen der
Form for
Der Vorteil von continue liegt hier in der Tatsache, daß bei fehlender Sollwertabweichung
der für die Regelung verantwortliche Programmteil übersprungen wird, ohne daß die
Schleife verlassen werden muß.
Für den Fall einer Sollwertabweichung wird die continue-Anweisung nicht ausgeführt,
und der Programmteil für die Regelung tritt in Aktion.
8.7 MARKEN UND DIE ANWEISUNG goto
Die goto-Anweisung ist eine der meistdiskutierten Anweisungen in hö-
heren Programmiersprachen. Der Grund für diese Diskussion liegt an
der Unübersichtlichkeit von Programmen, in denen zu häufig goto an-
gewandt wurde.
Die Methode der strukturierten Programmierung konnte beweisen, daß
jedes Programm auch ohne die Verwendung von goto's erstellt werden
kann. Die Forderung nach goto-freier Programmierung wurde in den
letzten Jahren etwas zurückgenommen, da es Anwendungsfälle gibt, in
denen das goto einen eleganten Ausweg aus sonst sehr umständli-
chen Programmkonstruktionen bietet.
8-53
Dennoch sollten Sie sich an die Devise halten:
nicht mehr goto’s als nötig!
denn ein zu hemmungsloser Gebrauch von goto's führt zwangsläufig
zu unübersichtlichen und damit schlecht lesbaren Programmen.
Wie schon erwähnt, lassen sich mit goto-Anweisungen in Einzelfällen
zum Teil erhebliche Vereinfachungen erreichen. Der Gebrauch von
goto ist zum Beispiel zum Verlassen gleich mehrerer Schleifenebenen
durchaus vertretbar. Vor allem bei Fehlerbehandlungen während eines
Programmlaufes bietet sich der Einsatz der verpönten goto-Anweisung
an:
for(...){
for(...){
while (...){
if (chaos)
goto fehler;
fehler: printf(....);
Es wäre zwar möglich, diesen Fall auch ohne goto's zu lösen, aber si-
cherlich nicht einfach. Ein break in der innersten Schleife würde nur die
innerste Schleife verlassen. Eine Fehlerlokalisierung müßte also auf je-
der Schleifenebene erfolgen. Die Lösung mit goto-Anweisungen ist da-
gegen einfach, vor allem deshalb, weil Routinen zur Fehlerbehandlung
von beliebigen Stellen und auf unterschiedlichen Schleifenebenen an-
gesprungen werden können.
Die Syntax der goto-Anweisung lautet:
goto marke;
Eine marke hat die gleiche Form wie ein Variabienname; anschließend
folgt ein Doppelpunkt:
marke: anweisung;
Marken können vor jeder Anweisung stehen; ein goto kann jeweils nur
zu einem Punkt innerhalb der gleichen Funktion erfolgen.
8-54
ZUSAMMENFASSUNG
DER AUSFÜHRBAREN
ANWEISUNGEN
9-1
ZUSAMMENFASSUNG
DER AUSFÜHRBAREN o
ANWEISUNGEN y
Inzwischen haben wir nahezu alle ausführbaren Anweisungen der
Programmiersprache C kennengelernt:
Zuweisungen und Ausdrücke
zu einem Block {...} zusammengefaßte Anweisungen
if... bzw. if ... eise ...
while
do ... while
for
switch
case
default
break
continue
goto
Die ausführbare Anweisung return werden wir später im Zusammen-
hang mit Funktionen behandeln.
Die Zahl der ausführbaren Anweisungen konnte in C so gering gehalten
werden, da viele Aufgaben mit Funktionen oder Makros
wie z. B. tolower ()
zum Umsetzen eines Großbuchstaben in einen Kleinbuchstaben
gelöst wurden, welche in Bibliotheken bzw. Dateien vorliegen.
C selbst enthält nur Anweisungen, die zur Formulierung eines Algorith-
mus benötigt werden.
Hilfreiche Makros wie z. B. zum Umsetzen von Klein- in Großbuchsta-
ben oder umgekehrt sind in Dateien mittels ttdeffine-Anweisungen de-
finiert, und nicht Bestandteil der Anweisungen der Programmiersprache
C.
9-3
10-1
DATENTYP-
UMWANDLUNGEN
10
10.1 DIE IMPLIZITE UMWANDLUNG
DES DATENTYPS
Wenn in Ausdrücken Operanden unterschiedlicher Datentypen über
Operatoren verknüpft werden, dann werden sogenannte implizite Typ-
Umwandlungen notwendig.
Die implizite Datentyp-Umwandlung geschieht nach folgenden Regeln:
Regel 1 fchar und int/:
Es wird mit nichts kleinerem als int (2 Byte) gerechnet, d. h. char
und short werden implizit und ohne Einwirkung des Programmie-
rers nach int umgewandelt.
Beispiel 1
int ganz, g.ergeb;
char zeich;
zeichs'X*; ganz=l0; /• ASCII-Code X = 01011000 •/ ♦/
/• 10 dual s 00000000 00001010
g_ergeb*ganz+zeich; /♦ Umwandlung von i zeich nach int: •/
/• 00000000 01011000 • /
/♦ •/
/• ganz 00000000 00001010 • 10 •/
/• zeich 00000000 01011000 • 88 •/
/• •/
/• ganz+zeich 00000000 01100010 = 98 •/
/• •/
/• 98 ist der ASCII-Code fuer das Zeichen b •/
printf C*\nXd Xc\n",g ergeb,g ergeb);
)
Ausgabe am Bildschirm:
98 b
10-3
Beispiel 2
Es soll ein C-Programm erstellt werden, das den ASCII-Code dezimal, hexadezimal, oktal,
dual mit entsprechenden ASCII-Zeichen am Bildschirm ausgibt.
Für die Dualausgabe wurde ein Programmteil verwendet, der schon bei der for-Anweisung
(Beispiel 6) vorgestellt wurde.
Die Ausgabe beginnt bei 32, da die ersten 31 Zeichen des ASCII-Codes für Steuerzeichen
reserviert sind.
/♦• Ausgaoe des ASCII-Codes (ASCII - American Standard for Coded •
/•♦ Information Interchange) von 32 bis 127 ••/
main ()
<
int ascii, bit;
printfC\n\nX50s\n","ASCII-Code");
printf("\nX50s\n\n\n\n\n\n*,j;
printf("X15sX15sX15sX15sX15s\n\n","Dezi mal","Hexa","Oktal"."Dual","ASCII");
for (asciis32 ; ascil<=l27 ; ♦♦ascii) (
printf("Xl5dX!5xXl5o ",ascii,ascii,ascii);
for (öit»7 ; öit>=0 ; --ölt)
printf ("Xid", (ascil»öit) & 1);
printf("XI5c\n",asci1);
Wir hätten die int-Variable ascii auch als char-Variable vereinbaren und den Rest des
Programms unverändert übernehmen können; das Programm hätte dann das gleiche gelei-
stet. Ebenso hätten wir ohne Auswirkungen folgende äußere for-Schleife angeben kön-
nen:
for (ascii = ascii <= 127; ++ ascii)
T
Leerzeichen = 32
Regel 2 (Zuweisungen):
Werden innerhalb eines Ausdrucks Operanden mit unterschiedli-
chen Datentypen verwendet,dann wird das Ergebnis der rechts
vom Zuweisungsoperator durchgeführten Berechnung automa-
tisch (implizit) in den auf der linken Seite vorhandenen Datentyp
umgewandelt.
10-4
italn ()
(
Int ganz;
float einfach;
double doppelt;
ganz=2O.O/3.0;
doppelt=20.0/3.0;
etnfachs20.0/3.0;
pr intf(•\nXd".ganz); /♦ Ausgabe: 6 ♦/
printf("XnXf".einfach); /• 6.666667 •/
prlntf("\nX11.9f".doppelt);/♦ 6.666666667 •/
einfach-doppelt;
printf("\nXf".einfach); /• 6.666667 •/
doppelt=ganz;
prtntf("\nX11.9f-,doppelt) ;/• 6.000000000 •/
ganz=einfach;
prlntf CXnXd* »ganz); /• 6 •/
In diesem Beispiel ist zu erkennen, daß bei der Zuweisung eines Gleitkomma-Wertes an
eine int-Variable der Nachkommateil abgetrennt wird; bei der Zuweisung eines double-
Wertes an eine float-Variable findet eine Rundung statt.
Diese Erkenntnis wollen wir im nächsten Beispiel ausnützen.
Beispiel 4
Es soll ein C-Programm erstellt werden, das die Quersumme einer eingegebenen positiven
ganzen Zahl berechnet und am Bildschirm ausgibt.
Struktogramm:
quer suene-Q
»
•Eingabe: "Xd-.izahl
•Ausgabe: "Die Quersumme der Zahl Xd ist “.zahl
while (zahl>0)
♦--------------------------------------------------------------
lAddieren der letzt. Ziffer auf quer_summe
'Abtrennen der letzt. Ziffer von zahl
Ausgabe: "Xd",quer_suane
10-5
C-Programm:
taain ()
int zahl, quer_summe-0;
printf ("\n\nGeben Sie eine positive ganze Zahl ein •); (4-)
scanf(“Xd",&zahl);
printf("\n\n\nDie Quersumme der Zahl Xd ist u,zahl);
while (zahl>0) {
quersumme ♦ - zahl X 10;
zahl*/« 10;
/• Addieren der letzt. Ziffer auf quer_summe •/
/• Abtrennen der letzt. Ziffer von zahl •/
printf<"Xd\n“,quer summe);
)
(+) Unter MS-C und LA TTICE-C muß der Text in pnntf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher I" durch I \ n”.
Beispiel für den Programmablauf am Bildschirm:
Geben Sie eine ganze Zahl ein! 4398—->
Die Quersumme der Zahl 4398 ist 24
Regel 3 (float und double).
Bei Gleitpunkt-Berechnungen wird immer mit double gerechnet,
d. h. float wird also immer nach double umgewandelt; bei der
Umwandlung von double nach float wird gerundet.
Regel 4 (unterschiedliche Datentypen):
Sind die Operanden eines Ausdrucks von unterschiedlichem Da-
tentyp, so wird immer mit dem "speicheraufwendigsten” Typ ge-
rechnet, wobei folgende Reihenfolge gilt:
double (8 Byte)
long (4 Byte)
int (2 Byte)
10-6
Beispiel 5
•ain()
char byt_1;
int byt_2;
double byt_8;
Dyt_1 =
byt_2 =
bytjö =
B’ ;
-7;
559/3;
/• ASCII-Code fuer B ist 66 (dezimal) •/
byt_2 x byt_1+byt_8;
Auf der rechten Seite wird mit double •/
gerechnet, da double speicheraufwendiger ♦/
als char (Typ von byt 1) ist. ♦/
• /
byt 8 = 186.33333333... •/
byt~1 = 66 ♦ ♦/
-------------------------- •/
252.33333333... •/
♦/
Da byt_2 vom Typ int ist, wird der */
gebrochene Anteil abgetrennt und der •/
so erhaltene Ganzwert byt_2 zugewiesen ♦/
printf(“\n\nXd\n",byt_2);
byt_1 = byt_2-2*byt_1
printf("\n\nXc\n",byt_1);
/♦ Bildschirmausgabe: 252 •/
Auf der rechten Seite wird mit int •/
gerechnet, da int speicheraufwendiger •/
als char (Typ von byt 1) ist. • /
♦ /
byt 2 = 252 •/
2»byt~1 = 132 - ♦/
-----Z------------------------------- •/
120-----------------------------------•/
•/
Da byt_1 vom Typ char ist, wird nur •/
ein Byte in byt_1 uebertragen (das •/
erste Byte der int-Berechnung kann •/
nicht in byt_1 abgelegt werden. •/
/• Bildschirmausgabe: x ♦/
/• ASCII-Code fuer Zeichen x: 120 (dezimal) •/
Regel 5 (unsigned):
Ist einer der verwendeten Operanden vom Datentyp unsigned,
wird der andere Operand ebenfalls in unsigned umgewandelt.
Die hier angegebenen impliziten Umwandlungen von Datentypen wer-
den als übliche arithmetische Umwandlungen bezeichnet.
Hier eine Übersicht impliziter Datentypumwandlungen:
double --------float
long
unsigned
int -*---------char, short
10-7
Horizontale Umwandlungen werden immer durchgeführt; vertikale Um-
wandlungen nur bei Bedarf.
Neben diesen 5 Regeln gibt es Anwendungsfälle, in denen die Um-
wandlung nicht implizit (automatisch) vorgenommen wird.
Wie wir aber dennoch eine Umwandlung erzwingen können, werden wir
im nächsten Absatz zeigen. Diese dort erläuterten expliziten Typ-Um-
wandlungen werden am häufigsten im Zusammenhang mit Zeigern ver-
wendet.
10.2 DIE EXPLIZITE UMWANDLUNG
DES DATENTYPS
Explizite Typumwandlungen werden über sogenannte Casts erreicht.
Ein Cast ist eine in Klammern gesetzte Typ-Bezeichnung, die dem Ope-
randen vorangestellt wird:
(Typ-Bezeichnung) Ausdruck
Bei einer solchen Konstruktion wird der Wert des ausdrucks in den an-
gegebenen Typ umgewandelt; diese Umwandlung geschieht unter Be-
nutzung der oben angegebenen Umwandlungsregeln.
Beispiel 6
int ganz_1, ganz_2;
double dopp;
dopp s (double)ganz_1 • (long)ganz_2;
Dieser Programmteil entspricht vollständig dem nachfolgenden:
int ganz_1, ganz_2;
long langf
double dopp, dopp.2;
dopp_2 ganz_1;
lang~= ganz_2;"
dopp s dopp~2 ♦ lang;
Eine cast-Konstruktion bewirkt also dasselbe, als wenn ausdruck einer Variablen vom
angegebenen Typ zugewiesen wird, welche dann anstelle der ganzen Konstruktion einzu-
setzen ist.
10-8
FUNKTIONEN UND
PROGRAMMSTRUKTUREN
11-1
FUNKTIONEN UND
PROGRAMMSTRUKTUREN
11.1 ALLGEMEINES ZU
BIBLIOTHEKSFUNKTIONEN
In der Programmierpraxis sind häufig Probleme anzutreffen, die einan-
der von der Aufgabenstellung her ähnlich sind. Dieser Tatsache wird in
der Programmiersprache C mit sogenannten Funktionen (functions)
Rechnung getragen.
Funktionen in C sind Unterprogramme, die mit Subroutinen in FOR-
TRAN oder auch Prozeduren/Funktionen in PASCAL vergleichbar sind.
Wir haben bisher schon - ohne es ausführlich zu erwähnen - mit Funk-
tionen gearbeitet; zur Ausgabe auf dem Bildschirm haben wir z. B. die
Funktion printf aufgerufen. Hinter diesem Funktionsnamen verbirgt
sich ein Programmteil, der in der Standardbibliothek definiert ist und bei
jedem Aufruf von printf ausgeführt wird.
Am besten können wir dies an einem Beispiel nachvollziehen:
Beispiel 1
In diesem Beispiel wird nicht die Programmiersprache C, sondern - zum besseren Ver-
ständnis - die deutsche Sprache verwendet.
Wir nehmen dazu an, in der Standardbibliothek sei eine Funktion mit Namen Spesen defi-
niert (was natürlich nicht für die wirkliche Programmbibliothek von C gilt):
Es soll bei der Personalabteilung einer Firma immer ein einheitliches Papier für die Spe-
senabrechnung abgegeben werden.
11-3
Standardbibliothek:
Funktionsname
I
Liste der formalen Parameter
I
I I
V v
Spesen(name.staot,preis,vor,haupt,nach,tagzei t,waehrung,datum)
Drucke folgendes aus:
"Herr/Frau name hat am datum in Stadt
zu(b) tagzeit :
vor als Vorspeise,
naupt als Hauptspeise und
nach als Nachspeise gegessen.
Der Preis fuer dieses Essen betrug preis waehrung."
Wird im Programm jetzt die Funktion Spesen mit aktuellen Parametern aufgerufen:
Spesen(Heyer Hans,Stuttgart,20.80,Suppe,Schn 1tzel,Pudding,Mittag,DM,5.11.84)
dann wird der zu diesem Namen gehörige Programmteil aus der Standardbibliothek ausge-
führt, wobei für die formalen, als Platzhalter dienenden Parameter die entsprechenden ak-
tuellen Parameter eingesetzt werden:
formaler Parameter
aktueller Parameter
name <---------------------->
stadt <---------------------->
preis <---------------------->
vor <---------------------->
haupt <---------------------->
nach <---------------------->
tagzeit <---------------------->
waehrung <---------------------->
datum <---------------------->
Meyer Hans
Stuttgart
20.80
Suppe
Schnitzel
Pudding
Mittag
DM
5.11.84
Der obige Funktionsaufruf würde also folgenden Ausdruck bewirken:
Herr/Frau Meyer Hans hat am 5.11.84 in Stuttgart
zu(m) Mittag :
Suppe als Vorspeise,
Schnitzel als Hauptspeise und
Pudding als Nachspeise gegessen.
Der Preis fuer dieses Essen betrug 20.80 DM.
An diesem Beispiel ist zu erkennen, daß bei der Definition der Funktion Spesen in der
Standardbibliothek lediglich der Rahmen für das Spesenpapier mit "Platzhaltern” (formale
Parameter) festgelegt wurde.
Zum Aufruf dieser Funktion ist der in der Standardbibliothek verwendete Funktionsname
mit den aktuellen Parametern, die für die formalen Parameter einzusetzen sind, anzuge-
ben; ein solcher Funktionsaufruf bewirkt, daß der zu diesen Funktionsnamen gehörige Pro-
grammteil mit den angegebenen aktuellen Parametern ausgeführt wird.
Soll ein Spesenpapier für Frau Geller Inge angefertigt werden, so könnte der Funktionsauf-
ruf im Programm folgendes Aussehen besitzen:
Spesen(Geller Inge,Paris,32,nichts,Croissants,nichts»Fruehstueck,FF,3.2.85)
was folgenden Ausdruck ergeben würde:
11-4
Herr/Frau Geller Inge hat am 3.2.85
zu(m) Fruehstueck :
nichts als Vorspeise,
Croissants als Hauptspeise und
nichts als Nachspeise gegessen.
Der Preis fuer dieses Essen betrug 32
in Pans
FF.
Bisher haben wir nur von fest vorgegebenen Funktionen in der Stan-
dardbibliothek wie z. B. printf oder scanf gesprochen; diese werden
mit dem Linker dazugebunden. Daneben existieren aber auch noch vor-
definierte Funktionen und Makros wie z. B. getchar oder putchar in
der Datei stdio.h.
Werden solche vordefinierten Funktionen/Makros in einem C-Pro-
gramm verwendet, so ist der entsprechende Dateiname mit ttinclude.
z. B. als ttinclude <stdio.h>, im Programm explizit anzugeben. Wel-
che Funktionen/Makros in welchen Dateien der Form ,...H definiert sind
und was hierbei zu beachten ist, werden wir an späterer Stelle näher
behandeln.
Wir wollen uns aber nun der Erstellung eigener Funktionen zuwenden.
11.2 ERSTELLEN EIGENER FUNKTIONEN
11.2.1 Die Form von C-Funktionen
Bei der Definition einer Funktion wird lediglich ein Programmrahmen,
oder besser, eine Programmstruktur unter Verwendung von formalen
Parametern festgelegt.
Eine Funktions-Definition hat in C immer die gleiche Form:
Funktionskopf
(
Funktionsbereich
>
oder genauer:
Funktionsname([Liste der formalen Parameter]) i
(Deklaration der formalen Parameter] I
(
[Deklarationen] I
Anweisung(en) I
Funktionskopf
Funktionsbereich
11-5
Die in [...] stehenden Begriffe sind optional, d. h. diese Funktionsteile
sind nicht für jede Funktionsdefinition unbedingt erforderlich.
Diese formalen Aussagen wollen wir anhand eines Beispiels erläutern:
Da C keinen Potenzoperator wie * * in FORTRAN kennt, werden wir eine
Funktion hoch(x, y) definieren, die einen int-Wert x mit einem int-Wert
y potenziert.
Der Funktionsaufruf hoch(2, 5) würde dann den Wert
32 = 25 = 2 * 2 * 2 * 2 * 2
liefern.
Beispiel 2 *>
main() /• Funktionsname(leere Liste von formalen Parametern •/
/• Deklaration der formalen Parameter entfaellt •/
<
int zahl, exponent, ergeb; /• Deklarationen •/
printf("\n\n Geben Sie eine ganze Zahl und einen Exponenten\n");
printf«" durch Komma getrennt ein !\n");
scanf("Xd,Xd",&zahl,&exponent);
ergeb « hoch(zahl,exponent); /• Aufruf der Funktion hoch ♦/
printf("\n\n\nXd hoch Xd • Xd\n",zahl,exponent,ergeb);
hoch(a,b) /♦ Funktionsname(Liste der formalen Parameter) •/
int a, b; /• Deklaration der formalen Parameter ♦/
int zaehler, /♦ lokale Deklarationen •/
rueckgabe_wert=1; /• •/
for (zaehler=l ; zaehler<=b ; ♦♦zaehler) /♦ •/
rueckgabe_wert •= a; /♦ Anweisungen •/
return(rueckgabe_wert); /• •/
Beispiele für den Bildschirmablauf dieses Programms:
Geben Sie eine ganze Zahl und einen Exponenten
durch Komma getrennt ein!
7,4—1
7 hoch 4 = 2401
HINWEIS: *) Bei diesem Beispiel muß ein positiver Exponent (JO) eingegeben werden,
sonst ergibt sich ein fehlerhaftes Ergebnis.
11-6
Geben Sie eine ganze Zahl und einen Exponenten
durch Komma getrennt ein!
157J0 —•
157 HOCH J0= 1
Aus diesem Beispiel können wir sehr viel über Funktionen in C lernen.
• Ein C-Programm kann aus einer oder mehreren Funktionen bestehen;
unser C-Programm hier besteht aus 2 Funktionen mit den Namen
main und hoch und Aufrufen von Standardfunktionen wie printf,
scanf
• Bei main handelt es sich um eine besondere Funktion, da main den
Programmanfang kennzeichnet. Das heißt, daß in jedem C-Programm
eine Funktion mit Namen main enthalten sein muß. An dieser Forde-
rung haben wir uns ja auch - ohne die Bedeutung von Funktionen zu
kennen-in allen bisherigen Programmbeispielen gehalten.
• Von der Funktion main aus werden dann normalerweise andere Funk-
tionen aufgerufen; in unserem Beispiel ruft die Funktion main die
Funktionen printf und scanf aus der Standardbibliothek und die
selbsterstellte Funktion hoch auf, die weiter unten im Programm defi-
niert wird.
• Am leeren Klammernpaar () hinter main können wir erkennen, daß
bei dieser Funktion auf die Angabe von formalen Parametern verzich-
tet wurde, was ja erlaubt ist. Da keine formalen Parameter bei main ()
angegeben wurden, kann auch die Deklaration solcher Parameter ent-
fallen.
• Da Funktionen in beliebiger Reihenfolge definiert werden können, hät-
ten wir die Definition der Funktion hoch auch vor die von main stellen
dürfen.
Man kann sogar Funktionen für ein Programm in verschiedenen Da-
teien definieren; dann sind natürlich Bezüge zwischen diesen Dateien
herzustellen. Auf diese Möglichkeit werden wir an späterer Stelle
nochmals zu sprechen kommen.
Vorab werden wir also annehmen, daß alle in einem Programm ver-
wendeten Funktionen in einer Datei stehen.
11-7
• In der Funktion main wird nun mit der Anweisung
ergeb = hoch (zahl, exponent);
die Funktion hoch aufgerufen, d. h. es wird der Programmteil mit
Namen*) hoch ausgeführt, wobei für die
formalen Parameter die aktuellen Parameter
a zahl
b exponent
eingesetzt werden.
• In der Definition von hoch müssen die formalen Parameter deklariert
werden, damit ihre Datentypen bekannt sind:
int a, b;
Die formalen Parameter sind nach dem Funktionsnamen, aber noch
vor{zu deklarieren.
• Nach {werden sogenannte lokale Variablen deklariert:
int zaehler,
rueckgabe_______wert= 1;
Die hier deklarierten Variablen sind ebenso wie die zuvor deklarierten
formalen Parameter nur innerhalb dieser Funktion bekannt; diesen Ef-
fekt soll das Attribut lokal ausdrücken. Hätten wir z. B. in main eine
int-Variable zaehler deklariert, so würde es sich - auch wenn der
gleiche Namen angegeben wurde - um eine andere Variable handeln,
als bei zaehler in der Funktion hoch. Zu diesem Phänomen werden
wir bald noch Beispiele behandeln.
• Die Funktion hoch berechnet nun einen Wert und gibt den berechne-
ten Wert, der bei unserem Beispiel in der int-Variable rueck-
gabe____wert liegt, mit Hilfe der return-Anweisung an die aufrufende
Funktion, in unserem Fall main, zurück:
return (ausdruck);
HINWEIS: *) Für die Wahl von eigenen Funktionsnamen gelten dieselben Regeln wie für
die Wahl von Variabiennamen:
1. Zeichen muß ein Buchstabe oder ein Unterstrich sein, desweiteren dürfen
Buchstaben, Ziffern oder Unterstrich folgen, usw.
11-8
Soll eine Funktion keinen Wert an die aufrufende Funktion zurücklie-
fern, so ist kein ausdruck anzugeben, was lediglich die Beendigung
der aktuellen Funktion und die Rückkehr zur aufrufenden Funktion be-
wirkt: return;
Es kann auch ohne return-Anweisung zur aufrufenden Funktion zu-
rückgekehrt werden, und zwar, wenn das Funktionsende in der Form
der abschließenden } im Programmlauf erreicht wird.
In unserem Beispiel wird mit
return (rueckgabe___wert);
der Wert der Variablen rueckgabe__wert an die Funktion main zu-
rückgeliefert. Dieser Wert von rueckgabe_wert wird in der Varia-
blen ergeb mit
ergeb = hoch (zahl, exponent)
gespeichert und später am Bildschirm ausgegeben.
Die beiden Anweisungen in unserem Beispiel:
ergeb = hoch (zahl, exponent);
printf(” ... ”, zahl, exponent, ergeb);
hätten auch zu einer Anweisung zusammengefaßt werden können:
printff’... ”, zahl, exponent, hoch (zahl, exponent));
Als nächstes wollen wir das bisher Kennengelernte an einem Beispiel
üben. In den früheren Beispielen wurde ein Programm zu Spiel 21 er-
stellt. Dieses Spiel 21 wollen wir nun unter Verwendung von Funktio-
nen programmieren.
11-9
Beispiel 3
Beim Vorstellen der whlle-Schleife (siehe 8.3) wurde das Spiel 21 (Beispiel 2) program-
miert. Bei diesem Programm mußten wir für beide Spieler zwei nahezu identische Pro-
grammteile angeben. Dies wollen wir nochmals am entsprechenden Struktogramm hervor-
heben:
Ausgab» einer uebarschrift
suoee.streichho»lzer=21
whtle (suaaestreicnhoelzer > Ql
Ausgabe: "Uieviele Streichhoelzer
Eingabe: "Xd* *,4weg_$treichhoelzer
while (weg_5treicnnoelzer<i ii weg.strnchnoelzeo*i
‘Ausgabe: "Du darfst nur zwischen i und * Streicnnaelzer voa Tisch nenaen '
'Ausgabe: Also, wiederhole deine Eingabe '"
'Ausgabe: "uieviele Stre:cnnoelzer niaast du» Spieler: »-
Eingabe: "Xd",»weg_streichnoelzer
su«»e_stre> chhoalzar-*weg.streichhoelzer
lf isuaae_streichhoelzer<:OI
Ausgabe: "Es liegen keine Streich-* 'Ausgabe:
“noelzer mehr auf dea Tisch"
Ausgabe: ‘Spielen, du nast verloren *
'Xd *,suaae streichnoelzer
----------^uei£3hflelxer auf g?i Häur.
Ausgabe: 'Uieviele Streichhoelzer*
Eingabe: "Xd".tweg.streichhoelzer
whilc (weg_streichhoelzer<i Ii weg.streichhoelzerxi
'Ausgabe: "Du darfst nur zwischen*
* 1 und a Streichnoelzer voa Tisch nenaen'
Ausgabe: "Wiederhole deine Eingabe
Ausgabe: "Uieviele Streichnoelzer"
Eingabe: "Xd".Swegstreicnnoelzer
suaae.streichhoelzer-sweg.streichhoeller
lf (suaae_stretcnhoeizer<>O)
I
Ausgabe: ‘Es liegen keine ’ 'Ausgabe: "Es liegen noch *
"Streichnoelzer • • "Xd".suaee_streicnnoelzerI
"aehr auf den Tisch“ " Streichnoelzer •
Ausgabe: ‘SpielerZ, du* ' "auf dea Tisch"
• hast verloren •• ---------------------------------------
Hier sehen wir, daß sich der mitl 1 umrahmte Block kaum vom Block EZIj unterscheidet;
sie unterscheiden sich lediglich bei der Ausgabe der Spielernummer.
Diese Tatsache wollen wir berücksichtigen, indem wir für beide Programmteile nur eine
Funktion definieren, die die Programmstruktur festlegt. Um aber auf die unterschiedlichen
Anforderungen dieser beiden Programmteile eingehen zu können, werden wir formale Pa-
rameter angeben.
11-10
Struktogramm für main.
ain() i
wer dran=1 i
• “ ।
Ausgabe einer Ueberschrift •
i ।
15u««e_streichhoelzer=21 !
iwhile (summe-Streichhoelzer > □) »
1 * 1su««e_streichhoelzer = spiel(summe_streichhoelzer,wer dran) !
• 'wer_dran=>+wer_dran X 2 “ •
Struktogramm für spie/:
spiel(anzahl_streichhoelzer,wechsel)
Ausgabe: “Hieviele Strelchoelzer nimmst du,“ •
if (wechsel)
J N
Ausgabe: “ Spieleri ? «Ausgabe: " Spieler2 ?"
Eingabe: “Xd",&weg_streichhoelzer
while (weg_streichhoelzer<l ll wegstreichhoelzer>4)
' «Ausgabe: “Du darfst nur zwischen 1 und 4 Strelchhoelzer“ '
' ' "von Tisch nehmen • • •
• ' “Also, wiederhole’detne Eingabe «
• I "Hieviele Strelchhoelzer nimmst du,” '
I if (wechsel) •
« J n '
4----------------------------------------4---------------------------------------4
«Ausgabe: " Spieler! ? «Ausgabe: Spielerz ?• ?
«Eingabe: "Xd",&weg_stre 1chhoelzer
anzahl_stretchhoelzer-=weg-Strelchhoelzer
if (anzahl strelchhoelzeroO) •
J ‘ N !
-------——4_____________________——______—4
Ausgabe: "Es liegen keine“ «Ausgabe: "Es liegen noch“ •
"Strelchhoelzer“ ' "Xd Streich“, 1
“mehr auf dem Tisch“ ' anzahl_streichhoelzer «
+Ausgabe: “hoelzer auf dem Tisch“ '
if (wechsel) ♦ ♦
J N ! •
Ausgabe: “Spieler! ?">Ausgabe: "Spielerz ?"• !
---------------------+----------------------4 ।
Ausgabe: “, du hast verloren I“ • !
return(anzahl_stre 1chhoelzer)
11-11
C-Programm:
main ()
(
int summe_strelchhoelzer,
wer_dran=1;
....................................................................
/♦ Ausgabe einer Ueberschnft und Vorbesetzen der int-Varlablen •/
/• summe.strelchhoelzer Mit Wert 21 (Anfangswert) ♦/
printfC\nX45s*,"S p i e 1 2 1-);
printf("\nX45s\n\n\n\n\n",H===========sss“);
summe_streichhoelzer=21;
................................................................................
/• Aufruf der Funktion spiel mit den aktuellen Parametern •/
/• summe strelchhoelzer und wer dran •/
...........................................................................• •/
while (sumne_streichhoelzer >0) {
summe_streichhoelzer « spiel(summe_streichhoelzer,wer d ran);
wer_dran=++wer_dran X 2;
.............................................................................
/♦ Defintlon der Funktion spie 1 mit formalen Parameter •/
/• anzahl strelchhoelzer und wechsel •/
/...............................................................................
spiel(anzahl_streichhoelzer wechsel)
int anzahl"strelchhoelzer, wechsel;
(
int weg_strelchhoelzer;
printf("Wieviele Streichoelzer nimmst du,*);
if (wechsel)
printfi" Spielen ?\n");
eise
printfi" Spieler! ?\n");
scanf("Xd",&weg_stretchhoelzer);
while (weg_streichhoelzer<l II weg_streichhoelzer>4) (
printf("\n\n\nDu darfst nur zwischen 1 und 4 StreichhoelzerXn");
printfl'vom lisch nehmen I \n\n\n\n");
printf(“Also, wiederhole deine Eingabe !\n\n\n");
printf("Wieviele Strelchhoelzer nimmst du,");
If (wechsel)
printfi“ Spielen ?\n“);
eise
printfi" Spieler! ?\n");
scanf("Xd",4weg_stre1chhoelzer);
anzahl_streichhoelzer-=weg_stretchhoelzer;
if (anzahl-5treichhoelzer<=0) (
printf(“\n\n\n\nEs liegen keine Strelchhoelzer mehr auf dem Tisch");
if (wechsel)
printf("\n\n\nSpielerl ?");
eise
printf(“Xn\n\nSpieler! ?");
printf(", du hast verloren '\n\n\n\n\n");
11-12
eise (
printf(•\n\n\n\nEs liegen noch XO Streich",anzahl_streichhoelzer);
printf("hoelzer auf dea Tisch\n\n\n\n\n\n\n*);
return(anzahl_streichhoelzer);
In main wird die Funktion spiel solange aufgerufen, wie die while-Bedingung
summe____streichhoelzer > O
erfüllt ist. Für die Zuordnung von Parametern gilt:
aktuelle Parameter formale Parameter
summe_streichhoelzer------------ anzahL_streichhoelzer
wer_dran------------------------ wechsel
Die Int-Variable wer_dran wird mit wert 1 initialisiert und dann in der while-Schleife um
1 inkrementiert. Dann wird der Variablen wer_dran der Divisionsrest bei einer Division
durch 2 zugewiesen. Das heißt, daß in wer_dran abwechselnd eine 0 und eine 1 ge-
speichert wird.
Bei der Abfrage lf (wechsel) wird für den Fall, daß momentan in wer_dran (aktueller
Parameter zu wechsel) eine 1 gespeichert ist, Spieler 1 ?, sonst (bei einer O) Spieler 2
? am Bildschirm ausgegeben. Der modulo-Operator % berechnet den Rest einer Ganzzahl-
Division.
Die Funktion spiel gibt den im formalen Parameter anzahl_streichhoelzer gespeicher-
ten Wert an main zurück:
return (anzah|__streichhoelzer);
Das eben vorgestellte C-Programm leistet das gleiche wie das C-Programm von Beispiel 2
im Kapitel 8.3.
Bisher haben wir nur Funktionen kennengelernt, die int-Werte an die
aufrufende Funktion zurückgegeben haben. Im nächsten Absatz werden
wir Funktionen kennenlernen, die keine int-Werte zurückgeben.
11.2.2 Funktionen, die keinen
ganzzahligen Wert liefern
Bei den Funktionen hoch und spiel wurden ganzzahlige Werte an die
aufrufende Funktion main zurückgegeben, weshalb wir auf eine Funk-
tionstypangabe verzichten konnten.
Die vollständige Form von C-Funktionen ist nämlich:
11-13
[Typangabe] Funktlonsname([Liste der formalen Parameter])
[Deklaration der formalen Parameter]
<
[Deklarationen]
Anweisung(en)
)
Wird auf diese Typangabe bei der Definition einer Funktion verzichtet,
was ja erlaubt ist: [ ], dann wird angenommen, daß es sich um eine
Funktion handelt, die einen int-Wert liefert.
Da char-Werte innerhalb von Ausdrücken in int umgewandelt werden,
kann auch bei Funktionen, die char-Werte liefern, auf eine Typangabe
verzichtet werden. Von dieser Möglichkeit wollen wir im nächsten Bei-
spiel Gebrauch machen.
Beispiel 4
Ein Geheimtext soll durch folgendes Verfahren verschlüsselt werden: Jeder Buchstabe
wird durch seinen k-ten Nachfolger in der alphabetischen Reihenfolge - man denkt sich
das Alphabet zyklisch fortgesetzt - ersetzt.
Es sollen nur Großbuchstaben codiert werden; andere Zeichen werden unverändert aus-
gegeben. Bei der Eingabe des Zeichens * soll das Programm beendet werden.
Beispiel für den Programmablauf am Bildschirm:
Geheimcode
Wievielter Nachfolger?
4—'
DAS IST EIN G EH ELM-COD El
HEW MWX IMR KILIMQ-GSHI!
—1
11-14
C-Programm:
tinclude <stdlo.h>
aln (>
(
int zahl;
char zeich;
printf(“\n\nX45s", "6EHEIMCODE");
printf ( "\nX45s\n\n\n“ , * = = = * = = = = = = ••);
printf("Wievielter Nachfolger ’\n");
scanf("Xd“,6zahl) ;
printf("\n\n\n\n" 1;
while ((ze1ch=getchar(H1 = ‘• ')
putchar(codier(zeich,zahl)1;
)
codierlc zeich, nachfolger)
char cjzeich;
int nächfolger;
<
char zurueck;
if (c_zeich>=•A && c zeich<= Z )
zurueck*(((c_zelch--A fnachfolger) X 26)
zurueck=c_zelch
return(zurueck);
Erläuterungen:
Wird in einer Parameterliste gleichzeitig ein int- und ein char-Wert übergeben, dann
werden bei Berechnungen in einem Ausdruck beide in int umgewandelt; float-Parameter
werden automatisch in double umgewandelt.
Mit der Anweisung
zurueck = (((c_zeich - ’A’ + nachfolger) % 26) + ’A’);
in der Funktion codier wird der entsprechende Code-Großbuchstabe ermittelt und in der
char-Variable zurueck gespeichert; da char-Werte in Ausdrücken in int-Werte umge-
wandelt werden, wird mit c_zeich - ’A’ zunächst einmal die Position des übergebenen
Zeichens c_zeich im Alphabet ermittelt; diese Operation ist notwendig, da die Zeichen im
ASCII-Code abgespeichert sind; z. B. ist das Zeichen ’C’ in ASCII mit 67 codiert. ’C’ - ’A’
ergibt dann die Position 2, ’A’ soll die Position 0 im Alphabet besitzen.
Mit dem Ausdruck
c_zeich - ’A’ + nachfolger
wird die Alphabetposition des Code-Buchstabens, abhängig vom Wert der übergebenen
Variable zu nachfolger, bestimmt. Da eine so ermittelte Position außerhalb des Alphabets
(> 26) liegen kann, muß mit dem moduIo-Operator % (Rest einer Ganzzahldivision) die
Position in den Bereich 0 ... 25 projiziert werden.
11-15
Die anschließende Addition von ’A’ ist notwendig, da wir den entsprechenden ASCII-Wert
der so ermittelten Alphabetposition in der char-Variable zurueck ablegen müssen. Te-
sten Sie den eben durchgesprochenen Ausdruck einmal, indem Sie für c_zeich den Wert
’V’ und für nachffolger den Wert 17 annehmen. Verwenden Sie zu diesem Test die ASCII-
Tabelle aus dem Anhang und vergessen Sie dabei nicht, daß das Alphabet zyklisch fortge-
setzt gedacht wird.
c_zeich: ’V’ (ASCII-Code: 86)
nachfolger: 17
’A' (ASCII-Code: 65)
(((c_zeich - ’A’ + nachfolger) % 26) + ’A’)
| Umwandeln von char-Werte in int-Werte
((( 86 - 65 + 17 ) % 26) +65)
| Ermitteln der Alphabetposition des zu codierenden Buchstaben
((( 21 + 17 ) % 26) +65)
| Ermitteln der Alphabetposition des Code-Buchstaben
(( 38 % 26) +65)
Ermitteln der Alphabetposition des Code-Buchstabens
im Bereich (0,25): 38 : 26 = 1 Rest 12
( 12 +65)
Berechnen des ASCII-Wertes für den
gefundenen Code-Buchstaben
77 (ASCII-Code für ’M’)
In der char-Variablen zurueck wird also das Zeichen ’M’ gespeichert. Mit return (zu-
rueck) wird der so gefundene Code-Buchstabe an die aufrufende Funktion main zurück-
gegeben.
Mit der bedingten Bewertung hätten wir die Funktion codier noch kompakter, aber sicher-
lich nicht übersichtlicher definieren können:
codier(c_zeich, nachfolger)
char c_zeich;
int nächfolger;
return((c_zeich>= A i&c_zeich<» Z*)?(((c.zeich- ’A +nachfolger) X 26) ♦ A'):(c.zelch));
Hier wurde von der Möglichkeit Gebrauch gemacht, daß innerhalb von return auch ein
Ausdruck angegeben werden kann.
11-16
An diesem Beispiel können wir erkennen, daß bei der Rückgabe eines
char-Wertes auf die Typangabe bei der Funktionsdeklaration verzichtet
werden kann. Wenn eine Funktion einen Wert liefern soll, dessen Daten-
typ weder int noch char ist, muß der Datentyp der Funktion - genauer
ausgedrückt: der Datentyp des Rückgabewertes - explizit im Funk-
tionskopf angegeben werden.
Dies wollen wir wieder anhand der Potenzierung zeigen, nur soll jetzt
eine Funktion erstellt werden, die nicht nur ganzzahlige, sondern auch
Gleitpunkt-Werte berechnet.
int exponent;
double ergeb, zahl, gleithochd;
printf(•XnXn Geben Sie eine Gleitpunkt-Zahl und einen ganzzahligen\n“);
printff“ Exponenten durch Komma getrennt ein
scanf("Xlf,Xd",4zahl,iexponent);
if (exponent<0) <
zahl = 1/zahl;
exponent » -exponent;
)
/• Bel negativen Exponenten •/
/• wird der Kehrwert zu zahl gebildet •/
/• und das Vorzeichen von exponent geaend. •/
ergeb = gleithoch(zahl,exponent);-------------------------
printf(“\n\n\nX.3f hoch Xd - X.3f\n",zahl,exponent,ergeb);
double gleithoch(a,b)
int b;
double a;
(
int zaehler;
double rueckgabe_wert«1.0;
for (zaehler*! ; zaehleroo ; ♦♦zaehler)
rueckgaoe.wert •* a;
return(rueckgabe_wert);
Beispiele für den Bildschirmablauf dieses Programms:
Geben Sie eine ganze Zahl und einen Exponenten
durch ein Komma getrennt ein!
1.9,4 -J
1.900 hoch 4 = 13.032
s
11-17
Hier mußte also der Datentyp der Funktion angegeben werden, da weder ein int noch ein
char als Rückgabewert existiert. Da float innerhalb von Ausdrücken automatisch in
double umgewandelt wird, bringt es nicht viel, float gleithoch anzugeben.
Ebenso neu ist hier die Festlegung des Typs einer Funktion, die keinen int-Wert liefert, im
Deklarationsteil der aufrufenden Funktion:
double ergeb, zahl, gleithoch ();
Diese Vereinbarung besagt, daß es sich bei ergeb und zahl um double-Vanablen han-
delt und gleithoch eine Funktion ist - an () erkennbar - die einen double-Wert liefert.
Diese 'doppelte Vereinbarung" einer Funktion ist notwendig, da die aufrufende Funktion
den Datentyp des Wertes kennen muß, den die aufgerufene Funktion an sie übergibt. So-
lange der Funktions-Datentyp Int ist, erübrigt sich diese zusätzliche Deklaration. Diese
Vorgehensweise erspart eine Menge Schreibarbeit, da viele Bibliotheksfunktionen einen
int-Wert liefern; ohne diese Regelung müßten alle diese int-Bibliotheksfunktionen" bei
Verwendung im Programm explizit im Deklarationsteil vereinbart werden. Der Compiler
interessiert sich an dieser Stelle nicht für die Anzahl und den Typ der Parameter.
Das eben Kennengelernte wollen wir anhand eines Beispiels weiter ver-
tiefen.
Beispiel 6
Die Quadratwurzel einer Zahl A soll mit Hilfe der von Archimedes stammenden Iterations-
formel
XNEU: = | (XALT + A/XALT)
berechnet werden.
Als Anfangswert für XALT wird der Wert von A eingesetzt und daraus nach der obigen
Formel der Näherungswert XNEU berechnet.
Dieser Näherungswert wird anstelle von XALT wieder in die Formel eingesetzt, um einen
verbesserten Näherungswert zu berechnen.
Das Verfahren wird solange wiederholt, bis sich zwei aufeinanderfolgende Näherungswer-
te um weniger als eine vorgegebene Konstante EPSILON unterscheiden. Um das Pro-
gramm einfach zu gestalten, wurde A >= 1 vorausgesetzt.
11-18
C-Programm:
«am I)
{
double a, epsilon, archimedO;
printf("\n\n\nX57s\n"1teratlonsformel von ARCHINEDES");
printf (•Z57s\n\n\n\nu , ;
printf("Von welcher Zahl soll die Quadratwurzel bestimmt werden ?\n");
scanf("Xlf“,&a);
printf("\n\n\nGeben Sie eine Konstante EPSILON ein !\n");
scanf("Xlf"t&epsiIon);
printf("\n\nDie Quadratwurzel von X.31f ist X.31f\n",a,archimed(a.epsilon));—
double archimed(x,y)
double x, y;
(
double xneut xalt;
xneu=x;
do <
xalt=xneu;
xneu=O.5*(xalt+x/xalt);
} while ((xalt-xneu)>y);
return(xneu);
)
Beispiele für den Ablauf am Bildschirm:
Iter ATI onsformel _y ON ARCH I[ME DES
Von welcher Zahl soll die Quadratwurzel bestimmt werden?
4—*
Geben Sie eine Konstante EPSILON ein!
0.0001—1
Die Quadratwurzel von 4.000 ist 2.000
Iterationsformel von ARCH IM EDES
Von welcher Zahl soll die Quadratwurzel bestimmt werden?
Geben Sie eine Konstante EPSILON ein!
0.0001—1
Die Quadratwurzel von 12.000 ist 3.464
11-19
11.2.3 Die Parameter von Funktionen
Wie wir bereits besprochen haben, ist für jeden formalen Parameter bei
Funktionsdefinitionen ein Datentyp anzugeben (Deklaration der forma-
len Parameter); fehlen formale Parameter, dann entfällt natürlich auch
die Typvereinbarung.
Die Parameter einer Funktion - auch Argumente genannt - stellen eine
Art Schnittstelle zur aufrufenden Funktion dar, d. h. die aufrufende Funk-
tion kann über diese Parameter mit der aufgerufenen Funktion kommu-
nizieren und Werte übergeben.
Diese Übergabe von Parametern wollen wir nun genauer betrachten.
Wird in C nämlich eine Funktion mit Angabe von aktuellen Parametern
aufgerufen, dann werden nur die Werte der aktuellen Parameter an die
entsprechenden formalen Parameter übergeben. Was diese Aussage
bedeutet, soll an einem Beispiel gezeigt werden.
fdefine RABATT 3.2
main ()
<
float betrag, berechn;
betrag*!DO.0;
printf("\nNach Abzug des Rabatts von Betrag : X.Zf DM\n",betrag);
।---oerech(betrag) ;
printfClst der neue Betrag : X.Zf DM\n",betrag);
float oerech(a)
float a;
<
a=a-a*RABATT/100;
return(a);
Dieses Programm würde am Bildschirm
Nach Abzug des Rabatts vom Betrag: 100.00 DM
ist der neue Betrag: 100.00 DM
ausgeben.
Diese falsche Ausgabe hat ihre Ursache darin, daß die aufrufende Funk-
tion main nicht die Adresse, sondern lediglich den Wert von betrag an
berech übergibt.
11-20
Mit der Variablendekiaration float betrag in der Funktion main wird
ein Speicherbereich reserviert, in dem mit der Anweisung betrag =
100.00; der Wert 100.00 abgelegt wird:
100.00 betrag
Mit dem Funktionsaufruf berech (betrag); wird der Wert von betrag in
einen eigenen Speicherbereich für die Funktion berech kopiert. Dieser
eigens für die Funktion reservierte Speicherbereich wird nach Verlassen
der Funktion wieder freigegeben.
Speicherbereich für main
Speicherbereich für berech
Bei Ausführung der Funktion berech wird mit der Anweisung
a = a - a « RABATT/100;
die Kopie von betrag, d. h. a, und nicht betrag selbst verändert, was
zu folgendem Speicherbild führt:
Speicherbereich für main Speicherbereich für berech
100.00 betrag
Kopie
von
betrag
1£&05 96.8
Mit der Anweisung return (a); wird also die veränderte Kopie von
betrag an die aufrufende Funktion main zurückgegeben, während der
ursprüngliche Wert von betrag vom "Rendezvous” mit der Funktion
berech völlig unbeeinflußt blieb.
Eine Möglichkeit der Programmverbesserung wäre, den Rückgabewert
der Funktion berech an betrag zuzuweisen:
betrag = berech (betrag);
Um die interne Behandlung der Parameterübergabe besser verstehen
zu können, wird noch ein Beispiel vorgestellt.
11-21
Beispiel 8
main ()
<
int zahl_l, zahl_2;
zahl 1*10;
zahl~2*5;
Dieses Programm würde nicht - wie gewünscht - die Inhalte der beiden
int-Variablen zahl__1 und zahl__2 vertauschen sondern
10 5
am Bildschirm ausgeben.
Mit der Variabiendeklaration int zahl_1, zahl__2; wird in main Spei-
cherplatz reserviert, in dem mit den Anweisungen
zahl_1 = 10;
zahl_2 = 5;
die Werte 10 und 5 gespeichert werden:
zahl__1
10
zahl__2
Mit dem Funktionsaufruf tausch (zahl__1, zahl__2); werden die Werte
von zahl___1 und zahl__2 an die Funktion tausch übergeben, was ei-
nem Kopieren dieser Werte in einen eigenen Speicherbereich für die
Funktion tausch gleichkommt:
11-22
Speicherbereich für main
Speicherbereich für tausch
Im Funktionsbereich von tausch ist eine lokale Variable hilf deklariert,
welche nur innerhalb von tausch verwendet werden kann.
Die Anweisungen im Funktionsbereich von tausch bewirken das Ver-
tauschen der kopierten Werte, was zu folgendem Speicherbild führt:
Speicherbereich für main
Speicherbereich für tausch
zahl_1
zahl_2
Inhalte
wurden
vertauscht
Kopie von
zahl__1 (a)
Kopie von
zahl—2 (b)
An diesem Speicherbild ist zu erkennen, daß die Inhalte der Variablen
zahl_1 und zahl___2 durch die "Vertauschanweisungen" nicht verän-
dert wurden; beide int-Variablen besitzen noch dieselben Werte wie vor
dem Funktionsaufruf von tausch, was auch die Bildschirmausgaben
10 5
verdeutlichen.
Bei einem Funktionsaufruf werden also grundsätzlich nur die Werte der
aktuellen Parameter an die aufgerufene Funktion übergeben. Daraus
folgt, daß eine Funktion den Inhalt von außerhalb deklarierten Variablen
nicht verändern kann.
Wie aber kann man die außerhalb von Funktionen deklarierten Varia-
blen innerhalb von Funktionen verändern? Anwort: Wir müssen nicht
Werte, sondern Adressen als Parameter übergeben.
11-23
Wie können wir aber Adressen übergeben? Antwort: Wir müssen die
formalen Parameter bei Funktions-Definitionen als Zeiger deklarieren
und beim Funktionsaufruf Zeigerparameter übergeben.
Beispiel 9
Die Funktion tausch aus Beispiel 8 soll so umgeschrieben werden, daß die Werte der
übergebenen aktuellen Parameter (zah|_ 1 und zahl—2) Int-Zeiger sind.
main()
{
int zahl_1, zahl_2;
zahl_l=10;
zahl“2=5;
tausch(&zahl_1,4zahl_2);
printf("\nXd Xd\n",zahl_1,zahl_2);
tausch(a,ö)
int »a, »ö;
{
int hilf;
hilf=*a;
•a=*b;
•b’hilf;
}
Die Funktion tausch wird nicht durch eine return-Anweisung beendet; nach Ausführung
des Funktionsbereichs und Erreichen von} wird automatisch - ohne return - in die aufru-
fende Funktion zurückgekehrt.
Dieses Programm vertauscht die Inhalte der beiden int-Variablen zahl__1 und zahl___2
d. h. es gibt
5 10
am Bildschirm aus.
Mit der Variablendekiaration int zahl—1, zahl—2; wird in main Speicherplatz reserviert,
in dem mit den Anweisungen
zahl—1 = 10;
zahl__2 = 5;
die Werte 10 und 5 gespeichert werden:
10 zahl—1
5 zahl__________2
11-24
Wir wollen jetzt aber mit der schon früher einmal verwendeten Darstellungsweise arbeiten:
In der Definition von tausch werden mit der Deklaration int *a, *b; 2 Speicherplätze für
int-Zeiger in einem eigenen Speicherbereich reserviert.
Mit dem Funktionsaufruf tausch (&zahl_______1, &zahl____2); werden die Adressen von
zahl___1 und zahl___2 als aktuelle Parameter an die Funktion tausch übergeben. (A =
Adreßoperator).
Bei dieser Übergabe werden also in die beiden reservierten Speicherplätze a und b der
Funktion tausch die Adressen von zahL_1 und zah|_2 abgelegt:
Zeiger-
ebene
Vanablen-
ebene
Werte-
ebene
11-25
Der Ablauf innerhalb der Funktion tausch soll bildlich nachvollzogen werden:
hilf = *a;
Zeiger-
ebene
Variablen-
Bbom
Werte-
ebene
*b = hilf;
11-26
Nach der Rückkehr aus der Funktion tausch nach main sind die Werte von zahl____1 und
zah|_2 vertauscht.
Die eben kennengelernte Technik der Parameterübergabe nennt
man auch call by reference.
Ganz allgemein kann man mit Zeigerparametern eine oder mehrere au-
ßerhalb der Funktion deklarierte Variablen beeinflussen. Eine solche
Vorgehensweise birgt aber auch einige Gefahren in sich, denn ein zu
freizügiger Umgang mit Zeigerparametern läßt die Verwaltung von Va-
riablen schnell unübersichtlich werden, was zu schwer auffindbaren
Fehlern führen kann.
An dieser Stelle wird vielleicht auch deutlich, warum bei der Biblio-
theksfunktion scanf die Variablen mit Adreßoperator & anzugeben
sind. Würde nämlich keine Adresse einer Variablen an scanf überge-
ben, könnte diese Funktion den eingegebenen Wert nicht an die aufru-
fende Funktion zurückliefern.
11.3 SPEICHERKLASSEN
Es wurde schon einmal erwähnt, daß sich C-Programme aus mehreren
Dateien zusammensetzen können; jede Programmdatei - auch Modul*)
genannt - kann einzeln compiliert werden.
Diese getrennt compilierten Module können anschließend mit Hilfe des
Linkers zu einem einzigen Programm zusammengebunden werden.
Der Gültigkeitsbereich eines Objekts Funktionsname, Variabienname er-
streckt sich auf diejenigen Programmeinheiten, in denen er angespro-
chen werden kann.
Beim Gültigkeitsbereich kann zwischen folgenden Stufen unterschieden
werden:
HINWEIS: *> Ein Modul ist ein Programmteil, der getrennt compiliert werden kann, aber
für sich allein nicht ablauffähig ist.
11-27
modulglobal
Ein modulglobales Objekt ist innerhalb der gesamten Pro-
grammdatei (Modul) ansprechbar, in der es deklariert ist,
aber nicht in einer anderen Programmdatei (Modul).
Jede Variable, die außerhalb einer Funktion deklariert ist,
werden wir als modulglobal bezeichnen.
Da modulglobale Variablen außerhalb von Funktionen ver-
einbart sind und somit vielen Funktionen zur Verfügung ste-
hen, kann innerhalb von Funktionen auf modulglobale Varia-
blen zugegriffen werden, ohne daß diese als Parameter zu
übergeben sind.
Beispiel 10 zu modulglobal:
In diesem Beispiel werden wir ein C-Programm erstellen, das wieder mit Hilfe der Funktion
tausch die Werte zweier Variablen vertauscht; in diesem Programm werden die entspre-
chenden Variablen jedoch nicht als Zeigerparameter übergeben.
int zahl_1, zahl_2; /• «odulglobale Variablen
Main (1
(
zahl 1=10;
zahl”2=5;
tausch!I;
printf(*XnXd XdXn",zahl_1,zahlZ);
tausch ()
<
int hilf;
hilf-zahl_l;
zahl l=zahl_2;
zahl~1=hilff
)
Da die beiden int-Variablen zahl—1 und zahl______2 außerhalb jeder Funktion vereinbart
wurden, stehen sie allen in der Programmdatei definierten Funktionen main und tausch
zur Verfügung; ihr Gültigkeitsbereich erstreckt sich also über die gesamte Programmdatei
(Modul).
Mit der modulglobalen Deklaration int zahl_1, zahl—2; werden zwei Speicherplätze re-
serviert, auf die alle Funktionen dieser Programmdatei zugreifen können.
Mit den Zuweisungen
zah(_1 = 10;
zahL_2= 5;
werden in diese Speicherplätze Werte geschrieben:
zahl—1
zahl__2
11-28
Beim Aufruf der Funktion tausch wird mit der Deklaration int hilf; ein eigener Speicher-
platz auf dem sogenannten Stack reserviert, der nur für die Dauer der Funktionsausfüh-
rung verfügbar ist:
Gültigkeit für das
gesamte Programm
Gültigkeit nur
für tausch
hilf
Mit der Anweisung hilf = zah|__1; wird der Wert der modulglobalen Variablen zahL_1 der
lokalen Variablen hilf zugewiesen:
Gültigkeit für das
gesamte Programm
Gültigkeit nur
für tausch
Mit der Anweisung zah|__1 = zah|_2; wird der Wert der modulglobalen Variablen
zah|_2 der modulglobalen Variablen zah|_1 zugewiesen.Mit dieser Zuweisung wird also
in der Funktion tausch der Wert einer modulglobalen Variablen verändert.
Gültigkeit für das
gesamte Programm
Gültigkeit nur
für tausch
zahl__1
zahl__2
10 hilf
11-29
Mit der Anweisung zah(_2 = hilf; wird dann auch noch die zweite modulglobale Variable
von der Funktion tausch verändert:
Gültigkeit für das
gesamte Programm
Gültigkeit nur
für tausch
Nach der Rückkehr aus der Funktion tausch in die aufgerufene Funktion main werden
die Werte der modulglobalen Variablen zahl_1 und zahl_2 am Bildschirm ausgegeben:
5
10
Wir können also ohne Übergabe von Adressen innerhalb von Funktio-
nen die Inhalte von modulglobalen Variablen verändern.
Unter einer modulglobalen Variablen können wir uns eine Gemein-
schaftskasse in einer Familie (Datei) vorstellen, in die jedes Familien-
mitglied (Funktion) Geld einzahlen oder aus ihr entnehmen kann.
Vergessen Sie nicht, daß alle in einer Datei vereinbarten Funktionen
ebenso einen modulglobalen Gültigkeitsbereich besitzen, was ja be-
deutet, daß jede in einer Datei definierte Funktion eine andere Funktion,
die ebenfalls in dieser Programm-Datei vereinbart wurde, aufrufen
kann.
programmglobal
Ein programmglobales Objekt ist von mehreren Programm-
dateien aus ansprechbar, das heißt von allen Programmda-
teien, die das Objekt als extern deklarieren.
Im Unterschied zu modulglobalen Objekten, deren Gültig-
keitsbereich auf die Datei beschränkt ist, in der sie deklariert
sind, erstreckt sich der Geltungsbereich von programmglo-
balen Objekten über mehrere Programmdateien.
Unter programmglobalen Variablen können wir uns eine
Schulkasse vorstellen, auf die Schüler (Funktionen) aus ver-
schiedenen Klassen (Dateien) zugreifen können; d. h. jeder
Schüler kann dort Geld einzahlen oder entnehmen.
11-30
lokal
Das Objekt ist nur innerhalb des Programmteils, d. h. einer
Funktion oder einem Block von Anweisungen, geklammert
mit{... } ansprechbar, in dem es deklariert ist.
Bei hilf in der Funktion tausch (Beispiel 10) handelt es sich
um eine lokale Variable, d. h. auf hilf kann nur innerhalb der
Funktion tausch, aber nicht von außerhalb zugegriffen wer-
den.
Weitere Eigenschaft eines Objekts, die von seinem Gültigkeitsbereich
programmglobal, modulglobal, lokal unabhängig ist, ist seine Lebens-
dauer Bei der Lebensdauer einer Variablen unterscheidet man zwi-
schen:
static
Zu dieser Gruppe gehören Objekte, die während der gesam-
ten Programmlaufzeit einen festen Platz im Speicher besit-
zen.
automatic
Dieser Klasse werden Variablen zugeordnet, die nur für die
Dauer eines Funktionsaufrufs oder einer Ausführung eines
Blocks von Anweisungen, durch {...} geklammert, definiert
sind.
Variablen dieser Kategorie werden nicht an einem festen
Platz, sondern im sogenannten Stack gespeichert. Unter
Stack versteht man einen eigenen Speicherbereich, in dem
Werte nach dem Kellerungsprinzip LIFO = Last In First Out
gespeichert werden:
Der zuletzt gespeicherte Wert wird als erster wieder entnom-
men (wie in einem Keller, wo die obersten Kartoffeln als erste
wieder weggenommen werden).
Der Stackbereich einer Funktion wird bei Rückkehr in die auf-
rufende Funktion wieder freigegeben.
Die dritte und letzte Eigenschaft eines Objekts ist, daß es entweder im
Speicher oder in einem Prozessor-Register gehalten werden kann.
11-31
C bietet eine Reihe von Möglichkeiten, den Charakter von Variablen zu
wählen:
Gültigkeitsbereich:
Lebensdauer:
Speicherort:
programmglobal, modulglobal, lokal
static, automatic
Hauptspeicher, Prozessor-Register
Dies geschieht mit Hilfe von Schlüsselwörtern, die der jeweiligen Dekla-
ration vorangestellt werden:
extern
auto
static
register
(Lattice-C unter MS-DOS unterstützt nicht das Schlüsselwort register.
register-variablen werden wie auto-Variablen behandelt.)
Jedes Schlüsselwort repräsentiert eine sogenannte Speicherklasse
Da eine Variable nur einer Speicherklasse, zugeordnet werden kann,
darf bei einer Deklaration nur eines der Schlüsselwörter verwendet wer-
den.
Wir wollen nun besprechen, was für die einzelnen Schlüsselwörter be-
züglich Gültigkeitsbereich, Lebensdauer und Speicherort gilt:
extern
Lebensdauer:
Gültigkeitsbereich:
Speicherort:
static
programmglobal
im Hauptspeicher
Zu dieser Speicherklasse wollen wir jetzt ein Beispiel behandeln.
11-32
L
Beispiel 11
Es soll eine Reihe von positiven Meßwerten über Bildschirm eingegeben werden. Die Ein-
gabe soll bei einem negativen Meßwert beendet sein.
Unser Programm soll dann den Mittelwert der eingegebenen Meßwerte berechnen und am
Bildschirm ausgeben:
Datei 1 (Modul 1)
extern int zaehler;
double «ittel_wert»O;
nalnD
{
einlesO;
—zaehler;
ittel.wert /- zaehler;
printf(“\n\n\n\nDer Mittelwert öer eingegebenen Zahlen Ist ");
printf("X.31f\n"ttel_wert);
Datei 2 (Modul 2):
extern double «ittel.wert;
Int zaehler=1;
einleso
(
double mess.wert;
printf(“XnSlb Xd. Messwert ein '"»zaehler); (+)
scanf(“XIf",&mess_wert);
if (mess.wert>0) ~(
mittel.wert ♦ - mess.wert;
zaehler+4; }
) while (mess_wert>0);
H Unter MS-C und LAT77C5-C muß der Text in
printf mit \ n abgeschlossen werden, um die
Textdarstellung auszulösen. Ersetzen Sie daher
I” durch ' \ n”.
Nachdem beide Module kompiliert wurden, müssen sie mit dem Linker zu einem Programm
zusammengebunden werden.
Beispiel für den Programmablauf am Bildschirm:
Gib 1. Messwert ein! 4.0*•
Gib 2. Messwert ein! 3.2—*
Gib 3. Messwert ein! 5.1 —J
Gib 4. Messwert ein! —2.1 —1
Der Mittelwert der eingegebenen Zahlen ist 4.100
11-33
Veranschaulichung der Zugriffsrechte
ERKLÄRUNG
a-----b
a darf auf
b zugreifen
(T) Die beiden Funktionen main und einles dürfen einander gegenseitig aufrufen.
(2) Beide Funktionen main, einles dürfen auf die programmglobalen Variablen zaehler
und mittel_______wert zugreifen, d. h. beide Funktionen können die Inhalte dieser Varia-
blen lesen oder auch verändern.
(3) Auf die Variable mess.wert darf nur dur die Funktion einles zugreifen, da diese
Variable nur Gültigkeit innerhalb der Funktion einles besitzt.
Der Gültigkeitsbereich einer modulglobalen Variablen ist auf diejenige Programmdatei be-
schränkt, in der sie deklariert wurde; ohne extern-Deklarationen könnte auf mit-
tel__wert nur in Datei 1. auf zaehler nur in Datei 2 zugegriffen werden.
11-34
Um den modulglobalen Gültigkeitsbereich einer Variablen auf pro-
grammglobalen Gültigkeitsbereich zu erweitern (Zugriffsmöglichkeit auf
eine Variable, die in einer anderen Datei deklariert wurde), muß das
Schlüsselwort extern verwendet werden.
Es ist hier allerdings zwischen Extern-Deklarationen und Extern-Refe-
renzen. d. h. Herstellen eines Bezugs auf eine bereits deklarierte Varia-
ble in einer anderen Programm-Datei, zu unterscheiden:
Extern-Deklarationen:
int zaehler = 1; (in Datei 2)
double mittel___wert = O; (in Datei 1)
Extern-Referenzen:
extern int zaehler; (in Datei 1)
hier wird Bezug auf die deklarierte Variable zaehler in Datei 2
hergestellt.
extern double mittel____wert; (in Datei 2)
bewirkt, daß die in Datei 1 deklarierte Variable mittel_wert in-
nerhalb von einles verändert werden kann.
Extern-Deklarationen definieren eine Variable und reservieren einen
Speicherplatz.
Extern-Referenzen teilen dem Compiler lediglich mit, daß irgendwo eine
Variable mit solchem Namen und Datentyp existiert; bei Referenzen
werden also die Variablen nicht erzeugt und es wird kein Speicherplatz
reserviert. An genau einer Stelle muß eine programmglobale Variable
definiert sein; alle anderen Deklarationen dürfen dann nur Referenzen
sein.
Eine programmglobale Variable darf nur bei Extern-Deklarationen initia-
lisiert werden:
int zaehler = 1; (in Datei 2)
Eine Initialisierung bei Extern-Referenzen, z. B. extern int zaehler =
1; (in Datei 1), ist nicht erlaubt.
Da die Programmiersprache C nicht die Definition einer weiteren Funk-
tion innerhalb einer Funktion erlaubt, sind C-Funktionen zumindest
modulglobal. Werden Funktionen aus verschiedenen Dateien mit dem
Linker zu einem Programm - was ja für Beispiel 11 zutrifft - zusammen-
gebunden, dann ist der Gültigkeitsbereich jeder dieser Funktionen pro-
grammglobal.
Es verbleibt lediglich die Forderung, daß in jeder Funktion alle dort auf-
gerufenen Funktionen, welche keine int-Werte zurückliefern, mit Typan-
gabe im Deklarationsteil zu vereinbaren sind.
11-35
Wenn eine große Zahl von Variablen mehreren Funktionen zur Verfü-
gung stehen soll, so ist es besser, mit globalen Variablen und nicht mit
langen Parameterlisten zu arbeiten.
Eine Anmerkung zu globalen Variablen:
Sie sollten nicht mit allzu vielen globalen Variablen arbeiten, weil
dadurch - wie bei call by reference (Parameterübergabe) - die
Verwaltung von Variablen schnell unübersichtlich werden kann,
was wieder zu schwer auffindbaren Fehlern führen kann.
auto
Lebensdauer:
Gültigkeitsbereich:
Speicherort:
automatic
lokal
im Hauptspeicher (Stack)
auto-Variable sind in einer Funktion lokal, d. h, es kann nur innerhalb
der Funktion, in der sie deklariert sind, auf sie zugegriffen werden.
Wird eine Funktion aufgerufen, so wird ein Speicherplatz für diese loka-
len Variablen im Stack reserviert, der nach Rückkehr aus der Funktion
wieder aufgegeben wird.
Da der Stack-Bereich einer Funktion beim Verlassen wieder freigege-
ben wird, haben solche Variablen beim Eintritt in die Funktion zunächst
einen Undefinierten Wert.
11-36
Beispiel 12
Wir wollen den größten gemeinsamen Teiler ggT nach dem Verfahren EUKLID (siehe Bei-
spiel 14, Kap. 8.4) ermitteln.
Iinclude <stdio.h>
•ain()
<
luto int dlvident, divleor, hilf ।
do (
printf(*\n\n\n\n\n\n6oben Sie zwei ganze Zahlen ein !\n")|
ecanf("Xd Xd‘ ,kdividont,1dlvltor)।
getchar(l| /« notwendig, da Benutzer «eine Eingabe •/
/• eit Carriage-Return-Taete abechlieeet • /
lf (divieor>dividont) < /• Vortauechon der Morte von dlvident • /
hllf»dividonti /• und divieor, wenn divieor > dlvident */
divident"divitor।
divieor’hi lf|
>
printf<a\n\n\nDor grooeite goaeineaae Teiler von •))
printf(aXd und Xd ist Xd\na,divident,divieor,ggt(dlvident,divieor)»;
printf("Mol len Sie noch zwei Zahlen eingeben ( J/N I ?\na>|
> whilo (gotchard •» ’J*>|
ggt(a, b)
int a,b|
(
auto int reet|
do <
rett • a X bj
a b|
b reet|
) whilo (roet>O)|
return(a) |
/• EUKLlDeche Algorithauei • /
/• Nach Verleiten dieser Schleife • /
/♦ befindet eich ggT in der •/
Ze lokalen Variablen a •/
Beispiel für den Bildschirmablauf:
Geben Sie zwei ganze Zahlen ein!
18 552-J
Der groesste gemeinsame Teiler von 552 und 18 ist 6
Wollen Sie noch zwei Zahlen eingeben (J/N)? j —1
Geben Sie zwei ganze Zahlen ein!
24 47—>
Der groesste gemeinsame Teiler von 47 und 24 ist 1
Wollen Sie noch zwei Zahlen eingeben (J/N)? N—1
11-37
Ablauf dieses Programms im Speicher
Mit den Deklarationen
auto int divident, divisor, hilf;
in der Funktion main werden lokale Variablen für diese Funktion festgelegt:
Stackbereich für
main
divident
divisor
hilf
Nach Aufruf der Bibliotheksfunktionen scanf und getchar könnte - abhängig von der Be-
nutzereingabe-folgende Speicherbelegung vorliegen.
Stackbereich für
main
divident divisor hilf
18
552
•
Da der Inhalt von divisor größer als der Inhalt von divident ist, werden die drei zur if-
Abfrage gehörigen Vertauschungsanweisungen ausgeführt, was zu folgendem Speicherbild
führt:
Stackbereich für
main
552 divident
18 divisor
18 hilf
•
11-38
In einem darauffolgenden printf-Befehl wird die Funktion ggT mit den aktuellen Parame-
tern divident und divisor aufgerufen, was eine Erzeugung eines eigenen Stack-Bereichs
für die Funktion ggT nach sich zieht:
Stackbereich für
main
Stackbereich für
ggT
• kopieren
552 divident 552
18 divisor kopieren 18
18 hilf
•
Mit der Deklaration auto int rest; wird im Stack-Bereich der Funktion ggT ein weiterer
Speicherplatz reserviert, der nur für die Dauer der Ausführung von ggT zur Verfügung
steht.
Stackbereich für
main
Stackbereich für
99T
•
552 divident 552 a
18 divisor 18 b
18 hilf rest
•
Nach einem Durchlauf der do .. while-Schleife in der Funktion ggT ergibt sich folgendes
Speicherbild:
Stackbereich für main Stackbereich für ggT
552 divident 18
18 divisor 12 b
18 hilf 12 rest
•
11-39
Nach dem zweiten Durchlauf der do.. while-Schleife der Funktion ggT liegt folgende
Speicherbelegung vor:
Stackbereich für main Stackbereich für ggT
552 divident 12 a
18 divisor 6 b
18 hilf 6 rest
•
Der dritte Durchlauf zieht folgendes Speicherbild nach sich:
Stackbereich für
main
Stackbereich für
•
552 divident
18 divisor
18 hilf
•
Nach diesem Durchlauf ist die do .. while-Schleife beendet, da die Bedingung rest > 0
nicht mehr erfüllt ist.
Mit der Anweisung return (a); wird die Funktion ggT verlassen und der Wert von a an die
aufrufende Funktion main zurückgegeben, wo er am Bildschirm ausgegeben wird.
Mit dieser Rückkehr in die aufrufende Funktion main wird auch der Stack-Bereich der
Funktion ggT wieder freigegeben, d. h. die Inhalte der formalen Parameter (a und b) und
die Werte aller auto-Variablen sind nicht mehr erreichbar.
In main wird dann der Benutzer gefragt, ob er eine weitere Berechnung eines ggT
wünscht. Gibt der Benutzer beispielsweise J ein, so gilt noch die alte Speicherbelegung:
Stackbereich für
main
divident divisor hilf
552
18
18
•
Dieser Stackbereich
ist nicht mehr ansprechbar.
11-40
Jetzt gibt der Benutzer wieder neue Zahlenwerte an, von denen der ggT zu bestimmen ist.
Wird dann später die Funktion ggT im printf-Befehl aufgerufen, so wird wieder ein neuer
Stack-Bereich für diese Funktion generiert, d. h. die alten Werte aus dem letzten Aufruf
von ggT sind nicht mehr vorhanden.
Fehlt bei einer Deklaration innerhalb einer Funktion die Angabe einer
Speicherklasse, so wird implizit die Speicherklasse auto angenommen,
d. h. in unserem Beispiel hätten wir anstelle der Deklarationen
auto int divident, divisor, hilf;
auto int rest;
auch
int divident, divisor, hilf;
int rest;
schreiben können, was keine Auswirkung auf das Programm nach sich
gezogen hätte; der Ablauf in den Stack-Bereichen der beiden Funktio-
nen main und ggT wäre gleich gewesen.
Da man das Schlüsselwort auto auch innerhalb von Funktionen weg-
lassen darf, wird man es selten in C-Programmen entdecken.
Wir wollen jetzt untersuchen, was passiert, wenn man externe und au-
to-Variable mit gleichen Namen versieht.
Beispiel 13
ain()
C
int zahl;
zani * 1;
printf("XnXd".zahl);
unter_progr();
printf("\nXd".zahl);
unter.progr()
<
int zahl;
zahl * 2;
printf CXnXd",zahl);
11-41
Bildschirmausgabe:
1
2
1
Mit der Deklaration int zahl; und der Zuweisung zahl = 1; in der Funktion main ergibt
sich folgende Speicherbelegung:
Stack-Bereich für
main
1 zahl
Nach Ausgabe dieses Wertes am Bildschirm wird die Funktion unter_progr aufgerufen.
Mit der Deklaration Int zahl; wird ein eigener Speicherplatz im Stack-Bereich für un-
ter__progr reserviert, der mit der Zuweisung zahl = 2; den Wert 2 erhält; nach dieser Zu-
weisung ergibt sich folgendes Speicherbild:
Stack-Bereich für
main
Stack-Bereich für
untor_ progr
1 zahl
zahl
Mit dem printf-Befehl in unter_progr wird dann der Wert der auto-Variablen aus dem
Stack-Bereich von unter_progr (also 2) am Bildschirm ausgegeben.
Nach Rückkehr aus der Funktion unter_____progr ist der Stack-Bereich dieser Funktion
nicht mehr verfügbar, d. h. jetzt kann nur wieder auf den Stack-Bereich von main zugegrif-
fen werden.
Mit dem zweiten printf-Befehl in main wird also der Wert 1, nicht 2 am Bildschirm ausge-
geben:
Stack-Bereich für
main
1 zahl
Variablen mit gleichen Namen, die innerhalb von verschiedenen Funktionen deklariert sind,
sind voneinander unabhängig. Das gleiche gilt auch für die Parameter einer Funktion.
11-42
Beispiel 14 nicht compilierbares Programm:
main()
t
int zahl;
zahl s 1;
printf("\nXdM.zahl);
unterprogr();
printf("XnXd".zahl);
unter progrO
<
zahl » 2;
printf C\nXdM,zahl);
Für dieses Programm würde der Compiler bei der Übersetzung einen
Fehler melden, da in der Funktion unter__progr auf eine Variable zahl
zugegriffen wird, die dieser Funktion völlig unbekannt ist.
Es liegt nämlich weder eine externe Deklaration noch eine lokale Dekla-
ration innerhalb von unter_progr vor.
Beispiel 15
int zahl;
nain ()
{
zahl = 1;
printf("\nXd",zahl) ;
unter progr();
printf("\nXd“.zahl);
unter_progr()
(
zahl = 2;
printf("\nXdw,zahl);
Bildschirmausgabe:
1
2
2
Mit der Deklaration Int zahl; wird hier für die Variable zahl ein modulglobaler Gültigkeits-
bereich festgelegt, da diese Deklaration sich außerhalb einer Funktion befindet, zahl ist
also innerhalb dieser Programmdatei überall, auch innerhalb von Funktionen ansprechbar;
der Speicherplatz für zahl steht also für die gesamte Programmlaufzeit - das Programm
besteht hier aus lediglich einer Datei - zur Verfügung.
11-43
Mit der Anweisung zahl = 1; ergibt sich folgendes Speicherbild:
1 zahl
(modulglobal)
Nach Ausgabe dieses Wertes mit dem printf-Befehl wird die Funktion unter___progr auf-
gerufen. Die Zuweisung zahl = 2; führt zu folgender Speicherbelegung:
X 2 zahl
(modulglobal)
Da innerhalb von unter_progr keine auto-Variable mit gleichen Namen deklariert ist,
greift diese Funktion auch auf die modulglobale Variable zahl zu und gibt dann deren Wert
2 am Bildschirm aus.
Nach der Rückkehr aus unter_progr wird mit dem printf-Befehl der Wert der modulglo-
balen Variablen zahl, der jetzt 2 ist, am Bildschirm ausgegeben.
static
Während das Schlüsselwort auto nur innerhalb von Funktionen
verwendet werden darf, können sich static-Deklarationen inner-
halb oder außerhalb einer Funktion befinden:
innerhalb einer Funktion
Lebensdauer:
Gültigkeitsbereich:
Speicherort:
static
lokal
im Hauptspeicher
static-Variablen sind ähnlich zu auto-Variablen. Der Unterschied be-
steht darin, daß static-Variablen nach Rückkehr aus einer Funktion er-
halten bleiben. Erinnern Sie sich daran, daß auto-Variablen bei jedem
Funktionsaufruf neu im Stack-Bereich generiert werden.
Für static-Variablen bleibt während des Programmlaufs ein fester
Speicherplatz reserviert; angesprochen werden kann eine static-Varia-
ble allerdings nur in derjenigen Funktion, in der sie deklariert ist.
Bei static-Variablen innerhalb einer Funktion handelt es sich also um
eine "private” und ständig vorhandene Variable dieser Funktion.
11-44
Beispiel 16
Wir wollen ein C-Programm erstellen, das bei Ballspielen (Fußball, Handball, usw.) die
Tore zählt.
Fällt ein Tor, soll der Bediener dieses Programms entweder den Buchstaben H (Heim-
mannschaft) oder den Buchstaben G (Gastmannschaft) am Bildschirm eingeben. Es soll
dann der momentane Spielstand am Bildschirm angezeigt werden.
Das Programm soll bei Eingabe des Buchstaben E das Endergebnis ausgeben und sich
beenden.
Iinduda <stdio.h>
eher •annschaftj
aaiaO
(
printf(•\n\n\n\n\n\n")|
do (
ainlaso ।
oaant.ergeb();
> whilo taannschaft ! E)|
>
aoaant ergeb!)
(
static int tor_l 0;
static int tor_2 0|
>
swi tcMaannschaf t) (
casa ’H* i ♦♦tor_l|
printf(*\n\n\n\n\n\n
printf(•
brtakj
casa S* i ♦♦tor_2|
printf(,\n\n\n\n\n\n
printf (*
braak|
casa E’ i printf("\n\n\n\n\n\n
printf <’
breakf
dafault i pnntf(’\nF a 1 s c h a
braaki
>
oaantanas Ergebnis i\n\n*)|
Xd i Xd\n',tor_l,tor_2)|
oaantanas Ergebnis :\n\n*)|
Xd i Xd\n*,tor_l,tor_2)|
Endergebnis i\n\n*)|
Xd i Xd\n“ itorj ,tor_2» j
E i n g a b
emlasi)
<
printf("\n\n\n\n\n\n H • Meiaoannschaft")j
printf(*\n\n 6 Bastaannochaft")|
printf("\n\n E Endargebnis">j
prmtfCXn Gib gawuanschtan Buchstaban ain '\n’)|
annschaft gatchar()|
gatchar()।
11-45
Hätten wir anstelle von
static int tor__1 = 0;
static int tor__2 = 0;
die Deklarationen
int tor_1 = 0;
int tor_2 = 0;
in der Funktion moment____ergeb angegeben, dann hätte es sich bei
tor__1 und tor___2 um zwei auto-Variablen gehandelt, die bei jedem
Aufruf von moment____ergeb neu generiert worden wären; für diesen
Fall wären also die bisher berechneten Tore nach Verlassen der Funk-
tion moment____ergeb verloren gewesen.
Um beim Wiederaufruf auf die zuletzt berechneten Werte von tor___1
und tor__2 zugreifen zu können, sind diese beiden Variablen als static
zu deklarieren; dann sind die Werte dieser Variablen in einem eigenen
Speicherbereich untergebracht, der nach Verlassen der entsprechen-
den Funktion nicht aufgegeben wird.
außerhalb einer Funktion
Lebensdauer:
Gültigkeitsbereich:
Speicherort:
static
modulglobal
im Hauptspeicher
Eine static-Variable, die außerhalb einer Funktion deklariert wurde, be-
sitzt innerhalb des Moduls, in dem sie deklariert ist, Gültigkeit. In die-
sem Fall handelt es sich um eine modulglobale Variable, auf die in kei-
ner anderen Programmdatei zugegriffen werden kann.
Allerdings kann jede Funktion aus dieser Programmdatei eine derart
deklarierte Variable benutzen, d. h. lesen oder verändern.
Modulglobale static-Variablen reservieren nicht nur für die gesamte
Programmlaufzeit einen Speicherplatz, sondern ermöglichen auch eine
Art von "Privatsphäre” für einen Modul.
Eine modulglobale static-Variable gilt nur für denjenigen Modul, in
dem sie deklariert ist. Falls in anderen Programmdateien globale Varia-
blen mit gleichem Namen deklariert sind, entsteht dadurch kein Konflikt,
da eine static-Variable ihr eigenes "Revier" gegen derartige externen
Variablen verteidigt.
11-46
Beispiel 17
Der Zugang zu einer Firma soll durch ein automatisches Uberwachungssystem kontrolliert
werden. Bei Inbetriebnahme dieses Systems wird für den Zugang eine bestimmte Geheim-
nummer vergeben.
Wir wollen ein Programm erstellen, das in nur einer Datei den Zugriff auf diese anfangs
festgelegte Geheimnummer zuläßt. Will ein Besucher durch diesen Zugang eintreten, so
hat er diese Nummer einzugeben.
Das Einlesen dieser Nummer geschieht in einer eigenen Programmdatei. Wird 3mal hinter
einander eine falsche Nummer eingegeben, so ist ein Alarm auszulösen.
Datei 1:
extern int zaehler;
long nuamer;
nuB_vergaoe();
printf ("Xf"); (+) /♦ Xf = Seitenvorschub •/
printfi" Gib Nummer ein ’XnXn*);
scanf("Xld“,inummer);
vergleich();
) while (zaehler<3);
printf("Xf\n\n\n\n\nXn\n A L A R H\n\n\n\n");
Datei 2:
extern long nuamer;
int zaehler = 0;
static long gehela_nummer;
nua vergäbe!)
(
printf("\f"); (+)
printf("Xn\n\n\n\n INBETRIEBNAHME !‘\n\nXn*);
printf("\n\n Geben Sie die Geheianummer ein »Xn*);
scanf("Xld"»igehela nuamer);
)
vergleiche)
(
if (nummer 's geheim.nummer)
♦♦zaehler;
eise <
printf("Xn\nXn\n Tuere oeffnen •’XnXn");
zaehler = □;
W Unter MS-C und LA TTICE-C muß der Text in printf mit ^ab-
geschlossen »erden, um die Textdarstellung auszulosen.
11-47
Auf die static-Variable geheim___nummer kann nur innerhalb der Datei2 von den Funk-
tionen num_vergabe und vergleich zugegriffen werden.
Die Funktion num___vergäbe wird nur einmal, am Anfang von main. aufgerufen, um in die
static-Variable geheim^nummer einen Wert einzulesen. Nach der Rückkehr nach
main ist der Wert von geheim____nummer nicht verfügbar.
Mit dem Aufruf der Funktion vergleich in Datei2 steht die static-Variable ge-
heim___nummer wieder zur Verfügung und besitzt noch den zuletzt dort abgespeicherten
Wert.
Auf die Variablen zaehler und nummer können alle Funktionen aus den beiden Dateien
zugreifen, da deren Gültigkeitsbereich programmglobal ist.
Veranschaulichung der Zugriffsrechte
ERKLÄRUNG:
a—b
a darf auf
b zugreifen
11-48
Hier können wir erkennen, daß der Gültigkeitsbereich von
main
num____vergäbe
vergleich
zaehler
nummer
programmglobal ist. Dagegen ist der Gültigkeitsbereich von geheim_nummer lediglich
modulglobal. Auf diese Variable dürfen also nur die Funktionen num_______vergäbe und
vergleich zugreifen.
Neben dem modulglobalen Gültigkeitsbereich ist noch zu beachten, daß es sich bei ge-
heim_nummer um eine «tatic-Variable handelt und ihr Wert nicht mit Verlassen von
Datei2 verloren geht.
Zum Beweis, daß sich mit static-Variablen und externen Variablen
konfliktfrei arbeiten läßt, soll folgendes Programmbeispiel dienen:
Beispiel 18
Datei 1:
char funk;
ain ()
(
funkz'm ;
printf r\nXc\n’, funk);
dat.ZO;
printf(“Xc\n",funk) ;
Datei 2:
static char funk;
dat 2(1
<
funki’d ;
printf("Xc\n*,funk) ;
Werden diese beiden Programmdateien mit dem Linker zusammengebunden und dann das
Programm gestartet, so ergibt sich folgende Bildschirmausgabe:
m
d
m
Diese Bildschirmausgabe ist wie folgt zu erklären: Obwohl wir mit 2 Variablen gleichen Na-
mens (funk) arbeiten, werden doch 2 verschiedene Speicherplätze reserviert.
11-49
funk
(globale Variable)
funk (static-Variable)
Mit der Anweisung funk = ’m’; wird in main auf die globale Variable zugegriffen. Nach
Aufruf der Funktion dat_2 wird mit der Zuweisung funk = ’d’; auf die static-Variable
und nicht auf die globale Variable zugegriffen.
Nach der Rückkehr aus dat_2 in die aufrufende Funktion main ist die static-Variable
funk nicht mehr ansprechbar; jetzt wird wieder auf die globale Variable funk zugegriffen,
was zur Ausgabe von d führt.
Werden mehrere Programmdateien zu einem Programm zusammenge-
bunden, so ist der Gültigkeitsbereich aller definierten Funktionen pro-
grammglobal. Wird nun eine Funktion als static vereinbart, so wird der
Gültigkeitsbereich dieser Funktion modulglobal, d. h. auf die Pro-
grammdatei beschränkt, in der die Funktion definiert ist. Funktionen aus
anderen Programmdateien können eine solche static-Funktion dann
nicht aufrufen.
Im nachfolgenden Beispiel wollen wir lediglich die Handhabung von
static-Funktionen aufzeigen, wobei wir hierbei - zugegeben - auch
ohne die Angabe von static-Funktionen auskommen könnten.
11-50
Beispiel 19
In einer Stadt wie Mannheim seien alle Straßen senkrecht und waagrecht zueinander an-
gelegt. Jedem Treffpunkt zweier Straßen ist ein Zahlenwert - entsprechend einem Koordi-
natensystem - zugeordnet. Die Stadt besitzt maximal 99 (senkrecht) • 99 (waagrecht)
Straßen:
So entspricht 0203 2 Straßen hoch und 3 Straßen rechts, von 0000 aus gesehen:
I
I
Es soll nun ein Programm erstellt werden, das von einem Besucher dieser Stadt seinen
momentanen Aufenthaltsort und seinen Zielpunkt über Bildschirm einliest und ihm den zu
fahrenden Weg ausgibt. Glaubt der Besucher, sich verirrt zu haben, so gibt er seinen mo-
mentanen Aufenthaltspunkt ein, und das Programm meldet ihm einen neuen Weg zum Ziel-
ort.
Beim Struktogramm und beim Kommentar im C-Programm wird lediglich die Struktur der
Wegausgabe vorgesehen, nicht die wirkliche Anzahl von senkrechten und waagrechten
Strichen:
I
11-51
Die Anzahl von senkrechten Strichen I wird durch den Betrag des Wertes von vertikal
festgelegt. Die Anzahl von waagrechten Strichen — wird durch den Betrag des Wertes von
horizontal festgelegt.
Der Weganfang wird durch Punkt . und das Ziel durch > oder < oder V oder A ge-
kennzeichnet. um die Wegrichtung festzulegen.
Beispiel für den Bildschirmablauf:
Geben Sie den Zielpunkt ein!
0305—1
Geben Sie den momentanen Aufenthaltsort ein!
0605—*
i
i
i
v
Geben Sie den momentanen Aufenthaltsort ein!
0810—1
Geben Sie den momentanen Aufenthaltsort ein!
0706—1
I
I
I
«-1
Geben Sie den momentanen Aufenthaltsort ein!
305—1
Ziel erreicht!
11-52
Struktogramm für die Funktion main (Dateil):
inlesen von Zielpunkt (Aufruf einer Funktion aus DateiZ)
t
do
•Einlesen von momentan (Aufruf einer Funktion aus DateiZ)
•Berechnen der Anzahl von senkrechten Strassen (in var. vertikal)
•(vertikal - zielpunkt/100 - momentan/!00)
Berechnen der Anzahl von waagrechten Strassen (in Var. horizontal)
•(horizontal = Zielpunkt«!00 - momentan«!00)
♦ 1 » J if ((vertikal == 0) && (horizontal == 0)) ♦ N '
•Ausgabe: Ziel continue erreicht • ! i l l
i » J ♦ if (horizontal 0) t N • 4
if (vertikal < 0)
N •
•Ausgabe
•Ausgabe: A
V
1continue
N !
if (horizontal < □)
N •
•Ausgabe: <
•Ausgabe
»continue
if ((vertlkaKO) && (horlzontal>0))
N !
• Ausgabe
' continue
if ((vertlkal>0) (hörizontal>O))
N
•Ausgabe
'continue
if ((vertlkaKO) && (horizontaKD))
N •
1 Ausgabe
4
' continue
if ((vertikal>0) && (horizontaKD))
N •
11-53
Ausgabe: <-------
I
l
continue
while t(zielpunkt-momentan) !* 0)
Datei 1:
int Zielpunkt, momentan;
main ()
C
int vertikal, horizontal, i, j;
ein_ziel ();
do {
ein_momentan ();
vertikal = zielpunkt/100 - momentan/100;
horizontal = zielpunktXl00 - momentanX!00;
lf ((vertikal == 0) && (horizontal = = 0)1 <
} if } if printf("XnXnXn Ziel erreicht 'XnXnXn") continue; (horizontal = = 0) < if (vertikal < 0) ( printf(*.Xn"); for (1=-1 ; i>=vertlkal ; —1) printf("1Xn"); printf("VXnXnXn“); } eise ( printf("AXn"); for (1=1 ; i<=vertikal ; printf (''l\n*); printf(*.XnXnXn“); > continue; (vertikal == 0) { /♦ Ausgabe: /• I /♦ l /• V /♦ Ausgabe: A /♦ l /• 1 /♦ /• Ausgabe: < .
) lf if (horizontal < □) { printf(*<•); for (1=-1 ; i>=horizontal ; —1) printf("-"l; printf(•.XnXnXn">; 1 eise < printf (*."); for (1=1 ; i<=horizontal ; ♦♦!) printf("-"); printf(">\n\nXn“); ) continue; ((vertikaKO) && (horlzontal>0)) ( /♦ /• Ausgabe: . > /♦ /• Ausgabe:
} lf printf("XnXn.xn"); for (1«-1 ; i> = vertikal ; —1) printf("l\n“); printf(* for (1=1 ; i<=horIzontal ; *♦!) printf("-"); printf(">\n\nxn"); continue; ((vertikal>0) && (hörizontal>0)) ( /• l /• 1 /• > /• Ausgabe: >
} if printf(“XnXn "); for (1=1 ; i<=horizontal ; ++1) printf ("-"); printf("»Xn"); for (1=1 ; i<=vertlkal ; ++1) printf("l\n"); printf (•' .XnXnXn“); continue; ((vertikaKO) && (honzontaKOl) < /• 1 /• 1 /• /• Ausgabe:
11-54
printf(M\n\n");
for (1=0 ; i>=vertlkal ; --D {
for cj = O ; j> = honzontal ; —j)
pnntfC "J;
if (1 = = 0)
printf(".Xn");
eise
printf("IXn");
>
printf(“<") ;
for (is-1 ; i>=horizontal ; —i)
printf(“-*);
printf("XnXnXn" );
continue;
)
if ((vertikal>0) t>t> ihonzontaKO)) (
printf(“Xn\n<“) ;
For (1»-1 ; l>=horizontal ; —1)
printf(•-•);
printf(-\n->;
for (1=0 ; icvertlkal ; Hll {
for (j=0 ; j>=horizontal ; —j)
printf(“
if (1 == vertikal)
printf("An") ;
eise
printf("IXn");
>
)
> while ((zlelpunkt-«>D«entan) ' = 0);
Ausgabe:
Datei 2:
extern int Zielpunkt, «oaentan;
static falsch eint)
<
printf(“\n\n\n\n\n FALSCHE EINBABE ! '\n\n”);
printf("\n\n\n Nur Werte aus dee Intervall [0000,9999] erlaubt !\n\n\n“>|
printf(“Eingabe wiederholen '1\n\n\n\n\n\n")j
)
ein.ziel ()
(
do (
printf(“\n\n\n\n\n\n Geben Sie den Zielpunkt ein !\n")|
scanf("Xd",kzielpunkt)j
if ((zielpunkt>9999) II (zielpunkt<0))
falsch_ein()j
> while ((zielpunkt>9999) I! (zielpunkt<0)) |
em eoeentanO
(
do <
printf (“\n\n\n\n\n\n Geben Sie den eoeentanen Aufenthaltsort ein !\n")|
scanf("Xd*,fceoeentan)j
if ((eoeentan>9999) II (aoeentan<0))
falsch_ein()।
> while ((eoeentan>9999) II (eoeentan<0)>(
>
Die static-Funktion falsch___ein hat einen modulglobalen Gültigkeitsbereich, d. h., diese
Funktion kann nur von den Funktionen ein___ziel und ein__momentan, aber nicht von der
Funktion main aus Datei 1 aufgerufen werden.
11-55
Wir wollen uns noch einmal kurz mit globalen Variablen beschäftigen:
Falls bei Deklarationen außerhalb von Funktionen keine explizite
Speicherklasse angegeben wird, so ist automatisch die Voreinstel-
lung extern. Innerhalb von Funktionen ist die Voreinstellung auto.
Von den 4 in C möglichen Speicherklassen fehlt uns jetzt nur noch eine:
register
Lebensdauer:
Gültigkeitsbereich:
Speicherort:
automatic
lokal
im Register
Eine register-Definition dient hauptsächlich der Erhöhung der Ablauf-
geschwindigkeit eines Programms.
Der Speicherplatz einer register-Variablen wird in der Zentraleinheit
oder CPU = Central Processor Unit reserviert. In der CPU können Daten
wesentlich schneller verarbeitet werden als im Arbeitsspeicher.
Deshalb wird man Variablen, die besonders oft verwendet werden, die-
ser Speicherklasse zuordnen. Da eine CPU nur über eine begrenzte An-
zahl von Registern verfügt, kann eine register-Definition lediglich als
Vorschlag für den Compiler gelten; trotzdem wird der Compiler versu-
chen - wo dies möglich ist - einen solchen Vorschlag zu realisieren.
Es kann also keine Garantie gegeben werden, daß jede Variable, die
der register-Speicherklasse zugeordnet wird, sich während des Pro-
grammlaufs ständig in der CPU befindet.
register-Vereinbarungen dürfen nur für Zeiger, int- oder char-Varia-
blen vorgenommen werden. Eine register-Definition hat z. B. folgende
Form:
register int zahl;
register char buchstabe;
Der Datentyp int darf dabei auch weggelassen werden:
register zahl;
11-56
Beispiel 20
Es sollen Potenzen für die Zahl e mit Hilfe einer Reihenentwicklung berechnet werden:
(n! = 1 ♦ 2 • 3 • 4 •.......n)
Es ist über Bildschirm eine Startpotenz und eine Endpotenz einzugeben; das C-Programm
soll dann alle Potenzen von e berechnen, die zwischen dem Start- und Endwert liegen.
Die Reihe soll nur bis zum 31. Glied berechnet werden:
ain()
<
register int y, i, j;
int start,ende;
double su«, faku, mal;
printf(“\n\n\nGeben Sie einen Start- und Endwert durch \n");
printf('Komma getrennt ein
scanf("Xd.Xd",istart,&ende);
printf("\n\n\n');
for (i=start ; i<=ende ; <
suB«1*i;
for (j = 2 ; j<x30 ; +4J) <
faku = 1;
for (y=2 ; y<=j ; ♦♦y)
faku«=y;
mal = l;
for (yx2 ; y<=j ; ♦♦y)
al««!;
sum+=mal/faku;
)
printfCXn e hoch Xd - Xg“,l,sum);
>
>
Hier wird dem Compiler vorgeschlagen, die Laufvariablen y, i und j während des Pro-
grammlaufs ständig in den Registern zu halten.
11-57
Hier wollen wir kurz noch einmal auf Variabiennamen zu sprechen kom-
men:
Bei nicht-globalen Namen kann man sich darauf verlassen, daß
mindestens 8 Zeichen signifikant sind und Groß- und Kleinbuch-
staben unterschieden werden: zaehler und zaeHler sind ver-
schiedene Namen.
Bei globalen Namen sind immer Begrenzungen durch den Linker
des jeweiligen Betriebssystems vorgegeben. Auf manchen Syste-
men sind beispielsweise globale Variabiennamen auf 6 Zeichen
begrenzt, und es wird nicht zwischen Groß- und Kleinschreibung
unterschieden. Der Unterstrich, welcher ja ein erlaubtes Zeichen
in Namen ist, wird häufig von Systemroutinen als erstes Zeichen
verwendet. Um Konflikte mit solchen vom System vorgegebenen
Namen zu vermeiden, sollten Sie bei der Festlegung von globalen
Namen auf den Unterstrich als erstes Zeichen verzichten.
11.4 BLOCKSTRUKTUR
Da in C keine Funktionen innerhalb von Funktionen definiert werden
können, ist die Blockstruktjjr von C nicht mit der von PASCAL oder AL-
GOL vergleichbar.
Auf eine solche Verschachtelung von Funktionen wurde in C aus
Übersichtlichkeitsgründen verzichtet; in C können Funktionen nur
streng hintereinander deklariert werden.
Da sich dieses Prinzip der Schachtelung aber bei der Deklaration
von Daten sehr bewährt hat, können in C an jedem Anfang eines
Blocks, d. h. unmittelbar nach{, Variablen deklariert werden:
11-58
Jedes C-Programm setzt sich aus Funktionen zusammen, und jede
einzelne Funktion innerhalb eines Programms besitzt zumindest ei-
nen Block, in dem weitere Blöcke verschachtelt sein können.
Innerhalb eines jeden solchen Blocks können nun unmittelbar nach {
weitere Variablen definiert werden; diese sind dann allerdings nur
innerhalb dieses Blocks bekannt.
Falls solche "blocklokalen" Variablen der Speicherklasse auto zu-
geordnet sind, gehen ihre Werte beim Verlassen des Blocks verlo-
ren.
Werden innerhalb eines Blocks bei der Variabiendeklaration Na-
men angegeben, die schon außerhalb vergeben wurden, so be-
deutet dies keinen Verstoß gegen die Regeln von C. Wird inner-
halb eines Blocks eine Variable deklariert, deren Name schon au-
ßerhalb vorhanden ist, so ist die "innere" Variable völlig unabhän-
gig von der "äußeren" Variablen; innerhalb des Blocks kann dann
nur auf die "innere" Variable, nicht aber auf die "äußere" zugegrif-
fen werden. Die "äußere" Variable kann erst nach Verlassen des
Blocks wieder angesprochen werden.
Wird innerhalb eines Blocks auf eine Variable zugegriffen, deren
Name nicht im Deklarationsteil dieses Blocks vereinbart wurde, so
wird damit eine Variable angesprochen, die außerhalb dieses
Blocks vereinbart wurde.
Der Gültigkeitsbereich von Variablen beim Blockkonzept soll anhand ei-
nes Beispiels nochmals verdeutlicht werden:
11-59
—
Beispiel 21
Ein Unternehmen habe folgende Organisationsform:
In dieser Firma arbeiten nun mehrere Personen mit dem Namen Meier. Wie diese Personen
nun verteilt sind, soll durch nachfolgende Blockstruktur gezeigt werden, wobei für den Vor-
stand und die Leitung des Unternehmensbereichs (UB) kein eigener Block gebildet wurde:
r-Firma X
11-60
• Spricht man nun in der UB1 -Leitung von Herrn Meier, so ist der Meier aus der UB1-Leitung
gemeint
• Wird in der Abt. 11 von einem Herrn Meier gesprochen, so bezieht sich dies auf den abtei-
lungsintemen Meier.
• ist in der Abt. 12 von einem Herrn Meier die Rede, so betrifft dies den Meier aus der UB1-Lei-
tung, da in Abt 12 kein Meier arbeitet
• Ist in der UB2-Leitung die Rede von einem Herrn Meier, so wird der Meier aus dem Vorstand
gemeint, da in UB2-Leitung kein Herr dieses Namens vorhanden ist
• Redet man in Abt.21 von einem Herrn Meier, so bezieht sich das auf den Meier aus der Vor-
standsetage, da weder ein Meier in Abt21 noch in der direkt übergeordneten Ebene UB2
existiert
• Wird in der Abt22 von einem Meier gesprochen, so betrifft dies den Herrn Meier aus dersel-
ben Abteilung.
• Wenn man im Vorstand über Herrn Meier spricht, so betrifft dies den Herrn Meier aus der
Vorstandschaft.
An diesem Beispiel können wir erkennen, daß bei Vorhandensein von
blocklokalen und blockexternen Variablen gleichen Namens zunächst
auf die blocklokalen Variablen zugegriffen wird.
Wird eine Variable angesprochen, die nicht im betreffenden Block de-
klariert wurde, so wird auf die Variable zugegriffen, die im übergeordne-
ten Block vereinbart ist; wurde auch im direkt übergeordneten Block
keine Variable mit solchem Namen definiert, so bezieht sich der Zugriff
auf die Variable im nächst höheren Block, falls sie dort deklariert wurde.
Ist sie auch hier nicht vereinbart worden, dann wird noch eine Ebene
höhergestiegen usw.
Der Gültigkeitsbereich von Variablen erstreckt sich also auf den
Block, in dem sie deklariert wurden und alle untergeordneten Blöcke,
in denen der gleiche Name nicht neu vereinbart wurde.
Vergessen Sie nicht, daß blocklokale Variablen außerhalb des entspre-
chenden Blocks nicht mehr zur Verfügung stehen.
11-61
Gültigkeitsbereich der einzelnen ’Meier” aus Beispiel 21:
ERKLÄRUNG:
[ ''] Gültigkeitsbereich für Meier aus Abt. 11
2] Gültigkeitsbereich für Meier aus Abt. 22
11-62
Wir wollen den Gültigkeitsbereich von Variablen bei der Blockstruktur
noch an einem kleinen C-Programm zeigen:
Beispiel 22
main () ( int x; /• x im aeusseren Block •/
x»10; /♦ aeusseres x wird benutzt •/
< int x; int sum; /• x im inneren Block •/
sum=D; for (x = 1 ; x<=100 ; t sum ♦= x; x=x+2) /• inneres x •/ /♦ wird benutzt •/
printf("\nDie Summe der ungeraden Zahlen von 1 Dis 100 ist Xd\n",sum)
printf("\n\nx = Xd\n* *,x); /• inneres x wird ausgegeben */
) /• Ausgabe: x « 101 •/
printf("\n\nx « Xd\n“,x); /• aeusseres x wird ausgegeben • / /♦ Ausgabe: x = 10 •/
)
Da eine neue Deklaration einer Variablen mit schon vorhandenen Namen in einem unter-
geordneten Block eine völlig unabhängige Variable festlegt, wäre es möglich, auch einen
anderen Datentyp anzugeben, etwa:
int v;
funkl ()
{ double v;
11.5 REKURSIVE FUNKTIONEN
C-Funktionen können rekursiv aufgerufen werden; das heißt, eine Funk-
tion darf sich selbst wieder aufrufen. Ein solcher rekursiver Aufruf kann
entweder direkt oder auf Umwegen über andere Funktionsaufrufe erfol-
gen.
Beispiel 23
Von einer Zahl, die einzugeben ist. soll die Fakultät n! = 1 • 2 * 3 * ... n (Ausnahme 0! = 1)
berechnet werden.
11-63
Beispiele für den Bildschirmablauf
Fakultaet von ? 3—J
3 ! = 6
Fakultaet von ? 0—1
0! = 1
Fakultaet von ? 5 —J
5! = 120
C-Programm:
long fakult);
main ()
C
int bis;
printf("XnXnFakultaet von ?); (+)
scanf("Xd",Abis);
printf("XnXnXn Xd ' = XldXn",bis»fakul(bis));
long fakul(zahl)
int zahl;
long int ergeb;
lf (zahl>D)
ergeb = zahl«fakul(zahl-1) ;
eise
ergeb » 1;
return(ergeb);
W Unter MS-C und LA TTICE-C muß der Text in
pnntf mit \ n abgeschlossen werden, um die
Textdarstellung auszulösen. Ersetzen Sie da-
her ?'• durch ? \ n“.
11-64
Erläuterung für bis = 3:
printf (..,[ fakulQ)
t«kuK3)
Ausgabe des
Wertes 6
ergeb =
3- I fakul(2)
fakul(2)
5 return'ergeb'
hier. «
ergeb = 3*2 = 6 2
ergeb =
2- [ fakul(l)
ergeb =
ergeb =2*1=2
fakul(O)
fakul(O)
\
-•-|-fakul(1)
\ return (ergeb!
hier
ergeb =1*1
ergeb = 1
return (ergeb)
hier:
ergeb = 1
Während des Programmlaufs wird eine Verschachtelung aufgebaut: bei jedem Aufruf der
Funktion fakul wird die gerade arbeitende Funktion unterbrochen und neu gestartet, aller-
dings eine Ebene tiefer.
Hier wird also eine Bearbeitung eines Problems wiederholt, ohne daß eine Wiederholungs-
anweisung wie z. B. while auftritt: Bei der Rekursion handelt es sich um eine Wiederho-
lung durch Schachtelung.
Alle auto-Variablen werden bei jedem Funktionsaufruf neu angelegt, so daß jeder Aufruf
der Funktion in einer rekursiven Aufruffolge seine "privaten” Variablen besitzt; in unserem
Beispiel werden also bei jedem Aufruf von fakul neue Speicherplätze für ergeb und zahl
angelegt, d. h. z. B ergeb in fakul(3) ist unabhängig von ergeb in fakul(2)
Natürlich wäre die Berechnung der Fakultät auch unter Verwendung von Schleifen (Wie-
derholungsanweisungen) möglich gewesen:
11-65
Beispiel 24
main ()
(
long ergeb;
int bis, i;
printf("\n\nFakultaet von ’*); (+)
scanf t“Xd",&bis);
ergeb = 1;
for u=bis ; i>0 ; —1)
ergeb • = 1;
printf(“\n\n\n Xd ' = Xld\n“,bis,ergeb);
(+J Unter MS-C und LA TTICE-C muß der Text
in printf mit \ n abgeschlossen werden, um
die Textdarstellung auszulösen. Ersetzen
Sie daher ?” durch ? \ n".
Wir können nun das bisher Kennengelernte über rekursive Funktionen
zusammenfassen und durch Neues ergänzen:
Eine Funktion heißt rekursiv, wenn sie sich selbst direkt aufruft oder
wenn sie eine Funktion aufruft, in der sie ihrerseits wieder aufgerufen
wird.
Jede rekursive Funktion kann mittels Wiederholungsanweisungen
realisiert werden: das Umgekehrte gilt natürlich auch.
Ein rekursiver Funktionsaufruf erfordert Zeit und Speicherplatz, da für
jeden neuen Aufruf neue "private” Variablen zu vereinbaren, d. h.
neue Speicherplätze anzulegen sind.
Der Programmierer muß dafür sorgen, daß eine rekursive Schachte-
lung gegen ein definiertes Ende läuft: in Beispiel 23:
if (zahl > O)
ergeb = zahl * fakul (zahl - 1);
eise
ergeb = 1;
Wie bei jeder Wiederholung, muß auch bei der Rekursion für einen
Abbruch gesorgt werden.
Bei rekursiven Funktionsaufrufen können gleichzeitig mehrere Ver-
sionen einer Variablen auf dem Stack (Keller) liegen, die den einzel-
nen Verschachtelungsebenen entsprechen; dies wollen wir wieder
anhand eines Beispiels verdeutlichen:
11-66
Beispiel 25
Es sollen nacheinander Zeichen eingelesen werden, die dann in umgekehrter Reihenfolge
wieder auszugeben sind. Das Ende einer solchen Zeichenkette wird durch Eingabe eines
Leerzeichens und der CR-Taste gekennzeichnet.
Beispiele für den Bildschirmablauf:
Geben Sie eine Zeichenkette ein (Ende = Leerzeichen + CR)!
NEGER
l-. REGEN
Geben Sie eine Zeichenkette ein (Ende = Leerzeichen + CR)!
HALLO—1
X-J
X
OLLAH
Geben Sie eine Zeichenkette ein (Ende = Leerzeichen + CR)!
HER l-.—1
i_iREH
C-Programm:
• am ()
(
printf("\n\nBeben Sie eine Zeichenkette ein (Ende - Leerzeichen+CR) '\n\fi*);
□drehen();
□drehen()
(
char Zeichen; (+)
scanfi"Xc".izeichen);
tf (Zeichen':' ’)
□drehen();
printf("Xc",Zeichen);
}
M Unter LATTICE-C verwendet man besser ”int" statt "char*
11-67
Erläuterung für Eingabe der Zeichenkette HER
Ebene 0
Ebene 4
main
Zeichen
umdrehen()«
umdrehen()
Zeichen
umdrehen ( )
Ausgabe von
reichen (’W)
umdrehenj)
Zeichen
Zeichen
umdrehen ( )
Ausflaoe von
xolchcnf _. )
Stackbereich:
’x’ —> Zeichen bedeutet die Eingabe des Zeichens ’x’ beim Befehl
•canf (”%c”, * Zeichen);
Wir sehen hier, daß mit jedem Aufruf von umdrehen ein neuer Spei-
cherplatz für die Variable reichen im Stackbereich angelegt wird. Der
Stackpointer zeigt dann auf die aktuelle Version von Zeichen; mit der
Rückkehr in eine höhere Ebene wird der Stackpointer zurückgesetzt
und der vorherige Wert von Zeichen ist nicht mehr verfügbar.
In unserem Beispiel werden die eingegebenen Zeichen - durch ständi-
gen Aufruf von umdrehen - aufeinandergestapelt.
Durch die wiederholte Rückkehr in die nächsthöhere Aufrufebene wird
der Stapel dann in umgekehrter Reihenfolge wieder abgebaut (Ausga-
be: lüREH).
Rekursive Lösungen von Problemen sparen im allgemeinen keinen
Speicherplatz, da sie Speicherungen ihrer Werte im Stack benötigen.
Ein weiterer Nachteil rekursiver Funktionen ist, daß infolge der Stack-
verwaltung der Programmablauf langsamer wird.
11-68
Die Vorteile der Rekursion sind, daß Programme kompakter und leichter
verständlich werden. Besonders vorteilhaft wirkt sich die Verwendung
von rekursiven Funktionen bei Baumstrukturen aus.
Beispiel 26
Es soll ein C-Programm erstellt werden, das nach Eingabe der Zeilenzahl Dreiecke der fol-
genden Form
1
121
12321
1234321
rekursiv erzeugt.
C-Programm:
int t;
ain ()
(
Int anzahl, j;
printf("\n\nUleviel Zellen ? *); (+)
scanf("Xd".ianzahl);
for (1»1 ; l<=anzahl ; ♦♦!) C /•
for (j=1 ; J<=4O-1; /•
prlntfC" •); /♦
druckt!); /•
printf<*\n"); /•
}
Schleife zu« Zaehlen der Zeilen ♦/
Einruecken in Blldschlrmaitte • /
•/
Erzeugen einer Zelle •/
Zeilenvorschub •/
druck(zelle)
int zelle;
<
printf(•Xd",zelle);
if (zeileci)
druck(zeile+l);
if (zeile'=l)
printf("Xd",zeile);
)
/♦ Ausgabe der linken Haelfte •/
/• einer Zeile •/
/• einschliesslich der Mitte •/
/• Ausgabe der rechten Haelfte •/
/♦ einer Zeile •/
Unter MS-C und LA TT ICE-C muß der Text in printf mit \ n abge-
schlossen werden, um die Textdarstellung auszulösen. Ersetzen Sie
daher ?” durch ? \ n ".
Die Variable i wurde extern deklariert, damit sie nicht nur main, sondern auch der rekursi-
ven Funktion druck zur Verfügung steht.
11-69
11.6 PRÄPROZESSOR-ANWEISUNGEN
Präprozessor-Anweisungen beginnen alle in Spalte 1 mit dem Zeichen
8 und gehören nicht zum eigentlichen Sprachumfang von C, sondern
werden in einer eigenen Compiler-Phase zu Beginn verarbeitet.
Präprozessor-Anweisungen werden vor allem für folgende Zwecke ge-
braucht:
Namensvergabe an Konstanten wie
n define PI 3.14159265
Namensvergabe an Makros wie
8 define klein(c) (c I O x 20)
Einkopieren von sogenannten include-Date/en
Bedingte Übersetzung von Programmstücken
Übersicht über Präprozessor-Anweisungen
8define name zeichenkette
8define makro zeichenkette
8include "Dateiname”
8include <Dateiname>
8if konstanter_ausdruck
8ifdef name
8ifndef name
8else
8endif
8undef
8line konstante dateiname
11.6.1 Symbolische Konstanten
8 define name zeichenkette
In Kapitel 4 haben wir schon erfahren, daß C die Möglichkeit bietet.
Konstanten Namen zu geben; eine solche Namensvergabe erfolgt mit
der Präprozessor-Anweisung 8define
Die Verwendung von symbolischen Konstanten gestaltet unsere C-Pro-
gramme übersichtlicher und flexibler (siehe Kapitel 4).
11-70
Da das ttdefine eine rein textliche Ersetzung des logischen Namens
name mit der angegebenen zeichenkette durchführt, kann es auch
zu anderen Zwecken verwendet werden, zum Beispiel zum Ersetzen
von Funktions- oder Variabiennamen.
Will man die Funktion strich durch die Funktion waag___recht erset-
zen, so genügt dazu
ttdefine strich waag___recht
Kommt man bei der Zeichenkette nicht mit einer Zeile aus, so ist das
Fortsetzungszeichen \ am Ende der Zeile anzugeben.
Beispiel 27
idefine ueberschrift •
Programm *
sainl)
(
printf("\n\nXs\n"»ueberschrift >;
)
Das ist leoiglich ein Demonstrat 1ons\
Bildschirmausgabe:
Das ist lediglich ein Demonstrationsprogramm
Mit ttdefine definierte Namen gelten für die ganze Datei (Modul), in
der sie definiert sind, d. h. von der Definition bis zum Dateiende.
Bei der Definition kann auch auf früher definierte Namen zurückgegrif-
fen werden.
11.6.2 Makros:
tt define makro zeichenkette
In manchen Fällen ist eine in sich abgeschlossene Teilaufgabe derart
kurz, daß es nicht lohnt, diese als Funktion anzugeben und einen Ein-
und Rücksprung erforderlich zu machen.
C bietet nun die Möglichkeit an, solche kurzen Programmteile als Ma-
kros (siehe Kapitel 6.2) zu definieren:
ttdefine makroname (paraml, param2,...) (makrobefehl(e))
11-71
In den Klammern nach makroname kann dann eine Liste von formalen
Parametern angegeben werden, die beim Makroaufruf durch die aktuel-
len Parameter ersetzt werden.
Beispiel 28
•define max(A,B) < (A) > (B) 7 (A) : (B) )
»am ()
(
int zahl_l, zahl_2;
printf(“\n\nGeöen Sie 2 ganze Zahlen durch Konna getrennt ein ’\n*);
scanf("Xd,Xd*,4zahi_i,4zahl_2);
printf("\n\nDas Maximum dieser beiden Zahlen ist Xd\n’,max(zahl 1»zahl 2)1:
) •
Bildschirmablauf:
Geben Sie 2 ganze Zahlen durch Komma getrennt ein!
27,1254—1
Das Maximum dieser beider Zahlen ist 1254
Makros haben den Vorteil, daß die Parameter von beliebigem Typ sein
können; wir hätten z. B. das Makro max an späterer Stelle mit float-Pa-
rametern aufrufen können, und es hätte ebenfalls den größten Wert die-
ser float-Parameter bestimmt.
Anders als bei Funktionen genügt also eine einzige Definition von max
für beliebige Datentypen.
ödefine hoch_drei(x) x»x*x
main (I
int zahl;
zahl « 4;
printf(“\nXd«Xd*Xd - Xd\n\nzahl,zahl,zahl,hoch_drei(zahl) );
printf("Xo*Xd*Xd » Xd\n*,zahl+i ,zahl*l,zahl+1,hochdrei(zahl-H ));
11-72
Bildschirmablauf:
4 * 4 * 4 = 64
5 * 5 * 5= 13
Wie kommt die falsche Berechnung im zweiten printf-Befehl zustande?
Der Makroaufruf hoch___drei (zahl + 1) entspricht folgender textlichen
Ersetzung:
zahl +1 * zahl +1 * zahl +1
l 4 + 1 ♦ l 4 + 1 * l 4 + 1 (Punkt vor Strich)
4 + 4 + 4 + 1 - 13
Um die gewünschte Reihenfolge der Bewertung zu erhalten, hätten wir
folgende Makrodefinition angeben müssen;
ttdefine hoch___drei (x) ((x) * (x) * (x))
Jetzt hätte ein Makroaufruf hoch_drei (zahl + 1) folgender textlichen
Ersetzung entsprochen:
((zahl + 1) * (zahl+1) * (zahl + 1))
((4 + 1) * (4I1) * (4 + 1))
(5 * 5 « 5 ) -125
Solche falschen Berechnungen lassen sich also durch eine konsequen-
te Klammerung aller formalen Parameter vermeiden.
11.6.3 Einfügen von Dateien: tt include
Umfangreichere Programme besitzen meist eine größere Anzahl
von ttdeflne-Anweisungen und Deklarationen, die in mehreren Da-
teien (Modulen) benötigt werden. Diese Bdefine-Anweisungen und
Deklarationen faßt man dann zweckmäßigerweise zu einer eigenen Da-
tei zusammen.
11-73
C bietet nun eine Möglichkeit zum Einfügen solcher Dateien, die aus
ttdefine-Anweisungen und Deklarationen bestehen:
ttinclude "Dateiname”
ttinclude <Dateiname>
Bei der ersten Version ttinclude "Dateiname” wird zuerst in der be-
nutzereigenen Bibliothek und anschließend eventuell in den systemei-
genen Standardbibliotheken gesucht.
Bei ttinclude <Dateiname> wird nur in den Standardbibliotheken
gesucht.
Jede Programmdatei, in der eine Funktion aus der Standardbibliothek
vorkommt, sollte möglichst zu Beginn ttinclude <stdio.h> enthalten
Die Datei stdio.h enthält Makros, Variable und symbolische Konstan-
ten, die für Ein- und Ausgabeoperationen gebraucht werden. In dieser
Datei sind beispielsweise die Makros getchar () und putchar () ent-
halten.
11.6.4 Bedingte Übersetzung
Soll ein Programm für unterschiedliche Aufgaben eingesetzt werden, so
ist es oft erforderlich, ganze Programmteile abhängig vom Einsatzge-
biet auszutauschen.
Statt nun mehrere Programme in unterschiedlichen Dateien zu erstel-
len, bietet die bedingte Übersetzung die Möglichkeit, solche Versions-
unterschiede in einer Programm-Datei unterzubringen.
«if konstanter_____ausdruck
Hiermit kann die Übersetzung ganzer Programmteile vom Wert eines
zur Übersetzungszeit bekannten Ausdrucks abhängig gemacht werden.
Es wird hier untersucht, ob konstanter_ausdruck einen von 0 ver-
schiedenen Wert liefert; falls ja, wird der zugehörige Programmteil com-
piliert, sonst nicht.
ttifdef name
Hier wird untersucht, ob name dem Präprozessor als Makroname be-
kannt ist, d. h. ob name also zuvor mit ttdefine vereinbart wurde.
Falls ja, dann wird wieder der zugehörige Programmabschnitt compi-
liert, sonst nicht.
11-74
gifndef name
Hier handelt es sich um eine umgekehrte Logik zu ttifdef name; das
heißt, hier wird überprüft, ob name zu diesem Zeitpunkt nicht definiert
ist, also dem Präprozessor nicht bekannt ist.
Alle diese drei Präprozessor-Anweiungen sind mit ttendif abzuschlie-
ßen.
Durch die Programmkonstuktion
•if...
Programmteil A (uebersetzen);
leise
Pragrammte11 B (uebersetzen);
•enöif
besteht auch die Möglichkeit, den Compiler zwischen 2 Programmtei-
len, die zu übersetzen sind, wählen zu lassen:
♦ — ' J «If... ♦ N '
'Prag rammte 11 A i (uebersetzen)'Programmtell B i (uebersetzen) ’ ♦
Es ist auch erlaubt, ttif-Konstruktion zu schachteln:
«if...
........... /• 1 •/
•If...
................. f 11 •/
ProgrammteiH 1 (uebersetzen)
• eise
............... /• 12 •/
ProgrammteiH2 (uebersetzen)
•enflif
•eise
........... /♦ 2 •/
•If...
................. /♦ 21 •/
Pragrammtell21 (uebersetzen)
•endi f
•endif
Seachten S/e. daß das Präprozessor-
Zeichen # am Anfang einer Zeile an-
zugeben ist, Einrücken zum Erhöhen
der Übersichtlichkeit ist also bei Prä-
prozessoranweisungen nicht möglich,
außer nif
♦ if
♦ endif
w endif
11-75
Struktogramm hierzu:
«if... • «if... !
J N ’ J M •
Programmtel111 (uebersetzen)1Programutei112 (uebersetzen)’ProgrammteilZI (uebersetzen) 1
...... I ....... • ..... •
Die bedingte Übersetzung läßt sich gut für Testphasen anwenden. Übli-
cherweise werden zum Testen größerer Software-Pakete Programmtei-
le "eingehängt”, die z. B. die Zwischenwerte von Variablen ausgeben
oder den Pfad verfolgen, den ein Programmlauf benutzt.
Da solche "Hilfsprogrammteile” nur zur Überprüfung benötigt werden
und nicht zur eigentlichen Aufgabe gehören, liegt es nahe, diese nur
während der Testphase übersetzen zu lassen und nach Testende nicht
mehr im Programmcode "mitzuschleppen".
Es soll ein C-Programm erstellt werden, das den Inhalt einer int-Variablen, also ihr Bitmu-
ster. umdreht. Um dieses Programm auszutesten, sollen das ursprüngliche und das umge-
drehte Bitmuster während der Testphase am Bildschirm ausgegeben werden.
«aerine test
main ()
int zahl, hilf, umkehr,1;
printf("\n6eben Sie eine ganze Zahl ein ’Xn“);
scanf("Xd“,&zanl);
u«Kehr*O;
for (1=15 ; l>=0 ; --1) {
hilf = (zahl>>i) b 1;
umkehr = umkehr l (hilf«(15-i));
tifdef TEST
printf("\n\n\neigentliches Bltmuster\n*):
for (1 = 15 ; i>=0 ; —1)
printf ("Xd", (zahl>>i )&1 );
printf("\n\n\numgedrehtes BitmusterXn“):
for (1=15 ; l>=0 ; --1)
printf(‘’Xd",(umkehr>>i)&1 );
send 1 f
Testölock
printf CXnXnDas umgedrehte Bitmuster ergibt die Zahl: Xd\n",umkehr);
Vollziehen Sie am Schreibtisch den "Umdrehvorgang” nochmals nach, um den Programm-
ablauf zu verstehen.
11-76
Beispiel für den Bildschirmablauf:
Geben Sie eine ganze Zahl ein!
10
eigentliches Bitmuster
00000000 00001010
umgedrehtes Bitmuster
01010000 00000000
Testausgabe
Das umgedrehte Bitmuster ergibt die Zahl: 20480
Wir sollten natürlich dieses Programm noch mit anderen Werten te-
sten. Nach der Testphase wird die Testausgabe nicht mehr benötigt;
entfernen wir dann ttdefine TEST oder setzen diese Anweisung auf
Kommentar, so wird bei einer erneuten Übersetzung der Testblock, ge-
klammert mit ttifdef TEST....ttendif, nicht mehr kompiliert.
Wir können die Übersetzung des Testblocks aber auch durch ttundef
TEST verhindern, denn mit ttundef name wird ein bereits existieren-
des Makro oder eine Konstante wieder gelöscht. Beim nachfolgenden
Programm würde ebenso keine Testausgabe (Bitmuster) am Bildschirm
erzeugt, (ttifdef wurde in ttifndef geändert).:
tdefme TEST
main (J
(
int zahl, hilf, umkehr,i;
printf("XnGeben Sie eine ganze Zahl ein
scanf("Xd",izahl);
umkehrsQ;
for (1=15 ; i>-0 ; --D <
hilf = (zahl»i) & 1 ;
umkehr = umkehr l (hllf<<(i5-l));
«ifndef TEST
printf("\n\n\neigentl1ches Bitmusterxn");
for (1«15 ; i>=0 ; --1)
printff"Xd“,(zanl>>i)) ;
prin tf("\n\n\numgedrehtes Bi tmuster\n“);
for (I = 15 ; i>-0 ; — i)
printff" Xd", (u«kenr»i)&l ) ;
tendif
Xd\n",umkehr);
printf(M\n\nDas umgeörehte Bitmuster ergibt die Zahl:
11-77
11.6.5 Zeilennumerierung:
8 line konstante dateiname
Mit der ttline-Anweisung kann die automatische Numerierung, die zu
Testzwecken in den Programmcode übernommen wird, beeinflußt wer-
den.
Durch die Anweisung
ttline 112 fehler.c
werden die aktuelle Zeilennummer auf 112 und der aktuelle Dateiname
auf fehler.c gesetzt.
Dies hat keinen Einfluß auf das Programm selbst, sondern nur auf die
Numerierung der einzelnen Programmschritte.
Da sich bei gebräuchlichen C-Programmen kein richtiger Anwendungs-
fall für diese Anweisung findet, wollen wir sie nicht tiefergehend behan-
deln.
Der Anwendungsbereich für solche Anweisungen ist vor allen Dingen
bei sogenannten Programmgeneratoren, die C-Programme erzeugen,
zu suchen.
11-78
ZEIGER UND VEKTOREN
12-1
ZEIGER UND VEKTOREN 1 2
12.1 ZUSAMMENHÄNGE ZWISCHEN
ZEIGERN UND VEKTOREN
Zeiger wurden ausführlich in Kapitel 5 beschrieben; sie dienen bevor-
zugt zum Ansprechen von Vektoren oder Feldern. Ein Vektor ist die na-
mentliche Zusammenfassung einer Anzahl gleichartiger Objekte wie
zum Beispiel int- oder char-Variable. Ein Vektor mit 26 Elementen vom
Typ char könnte wie folgt vereinbart werden:
char buchst[26]
Mit dieser Deklaration wird ein Vektor buchst mit 26 Elementen vom
Typ char definiert; die Anzahl der Elemente wird bei der Deklaration in
eckigen Klammern angegeben.
Unter char buchst [26] können wir uns die Reservierung eines Spei-
cherblocks mit Namen buchst und mit 26 aufeinanderfolgenden Ele-
menten, hier also 26 char-Variablen, vorstellen, wie die Darstellung auf
der folgenden Seite zeigt.
Weitere Beispiele für Vektordeklarationen sind:
int tage [32]
womit ein Vektor tage aus 32 int-Variablen tage [O] ... tage [31]
gebildet wird, und:
int * zeig [20]
womit ein Vektor zeig aus 20 Zeigern zeig [O] ... zeig [19] auf int-
Variable gebildet wird.
Zeiger ermöglichen ein äußerst flexibles Verfahren, Daten in Vektorform
zu bearbeiten, wie die folgenden Beispiele und Bilder zeigen.
12-3
Werden n Elemente (hier: n - 26) deklariert, so erfolgt die Adressierung
über sogenannte Indizes von Element 0 bis Element n - 1; beachten
Sie, daß jeder Vektor mit der Elementnummer O und nicht mit 1 beginnt.
Mit der Deklaration char buchst [26] werden also, wenn Sie so wol-
len, auf einmal 26 Variablen (buchst [O], buchst [1], buchst [2].
buchst [25]) festgelegt.
Mit den Anweisungen
buchst [3] = ’a’;
buchst [0] = ’!’;
buchst [24] = ’H’;
ergäbe sich dann folgendes Speicherbild:
12-4
Beispiel 1
Wir wollen nun ein C-Programm erstellen, das im Vektor buchst alle Kleinbuchstaben
speichert. Nach dieser Feldbelegung kann der Benutzer eine Nummer eingeben, zu der ihm
der entsprechende Kleinbuchstabe am Bildschirm ausgegeben wird. Unser Programm soll
zur Ermittlung des Buchstabens auf das Feld buchst zugreifen.
main ()
C
char buchstt261;
Int 1, zahl;
for (1=0 ; 1<=25 ; ♦
buchstCU = 97+i; /♦ 97 » ASCII-Code fuer 1.Kleinbuchst.: a •/
do {
printf("XnXnXnGeben Sie eine Zahl zw. 1 und 26 ein (Ende=Zahl 100) IXn");
scanf("Xd"r6zahl);
If UzahKI II zahl>26) && zanl'slOO) (
printf("XnXnXnXnXn Falsche Eingabe ’XnXnXn");
printf("Wiederholen Sie Ihre Eingabe 'XnXnXnXnXn;
>
printf("XnXnlii Alphabet der Kleinbuchstaben steht an Stelle Xd“,zahl);
printf(”Xn der Buchstabe: XcXnXnXn"rbuchsttzahl-13);
)
) while (zahl'=lOO);
printf("XnXnXnProgrammendeXn");
12-5
In der for-Schleife
’7 * <»scii-co<)e fuer a v
werden im Feld buchst die Kleinbuchstaben abgespeichert: anstelle von buchst [i] = 97
+1 hätte man auch buchst [i] - ’a’ +1 angeben können:
Da der 1. Buchstabe a in buchst [O], der 2. Buchstabe b in buchst [1], und der 26
Buchstabe in buchst [25] liegt, müssen wir beim Zugriff auf das Feld buchst als Index
die eingegebene zahl minus 1 verwenden, um den richtigen Buchstaben zu erhalten Des-
halb wurde auch bei
printf ("xnXnlwi Alphabet per Kleinbuchstaben steht an Stelle Xd’.zahl);
printf("\n der Buchstabe: Xc\n\n\n",buchstezahl-1));
nicht buchst [zahl], sondern buchst [zahl - 1] angegeben.
12-6
Im nächsten Beispiel werden wir nicht nur eine Begründung für die Not-
wendigkeit von Feldern, sondern auch etwas über Zahlensysteme er-
fahren:
Beispiel 2|
Unser Zehnersystem arbeitet nach dem Prinzip, daß jeder Position einer Zahl eine be-
stimmte Wertigkeit zugeordnet ist.
2 100 (10*) t T 10 1 (10’) (10°) Zahl Wertigkeit
Die Zahl 275 entspricht also im Zehnersystem:
275(10> = 2 • 102 + 7*10’ + 5*10° =
= 2*100 + 7*10 + 5*1
Andere Zahlenbeispiele:
17589,1O> = 1 . 104 + 7 • 103 + 5 • 102 + 8.10’ + 9-10°
= 1 . 10000 + 7* 1000 + 5-100 + 8* 10 + 9*1
7293no) = 7 • 103 + 2*102 4- 9*10’ + 3-10°
= 7*1000 + 2.100 + 9-10 + 3*1
Die Basis des Zehnersystems ist 10 Arbeiten wir z. B. mit dem Oktalsystem C’Achter”-
System), so ist die Basis 8 275 im Oktalsystem ist:
2 7 5(ö) Zahl
t T
64 8 1 Wertigkeit
(82) (8’) (8°)
Die Zahl 275 im Oktalsystem:
275(8) = 2 * 82 + 7-8' + 5-8° =
X 2-64 + 7-8 + 5*f = 189(1O)
entspricht also der Zahl 189 im Dezimalsystem. Dieses Stellenwertsystem läßt sich auch
auf andere Zahlensysteme anwenden:
4123(5j = 4 • 53 4 • 125 + 1 ♦ 52 1 • 25 + + 2*5’ 2-5 + + 3 * 5° = 3-1 538(10)
641 (7) — 6 * 72 + 4 • 7’ ♦ 1 *7°
X 6 * 49 + 4.7 + 1 • 1 = 323(10)
1101(2) 1 • 23 + 1 • 22 + 0 • 2’ + 1*2° =
— 1 • 8 + 1 • 4 ♦ 0 • 2 + 1*1 13(10)
Wir wollen nun einen Algorithmus angeben, der Zahlen aus dem Zehnersystem in ein an-
deres System umwandelt; diesen Lösungsweg wollen wir am Beispiel der Zahl 54 zeigen:
12-7
Rest 0
Rest 1
Rest 1
Rest 0
Rest 1
Rest 1
54(W) =
Wir müssen also ständig durch die Basis des Zielsystems dividieren und uns den Rest
merken. Bei erneuter Division wird das Ergebnis der vorausgehenden Division zum Divi-
denden. Die Umwandlung ist beendet, wenn bei der Division der Quotient 0 lautet.
Wir wollen nun ein C-Programm entwerfen, das zunächst das Zielsystem anfordert und
dann nach der umzuwandelnden Zahl aus dem Zehnersystem fragt.
Nach diesen Benutzereingaben soll unser Programm die entsprechende Zahl im Zielsy-
stem ermitteln. Für die Basis B des Zielsystems soll gelten: B <=10.
C-Programm:
inaint)
(
int zahl, basis, zaehler, i;
int zlelMOO); /• maximal 100 Elemente fuer Feld ziel •/
/• Eingabe des Zielsystems •>
...............................................................
do (
printf ("\n\nGeöen Sie die Basis des Zielsyste« an 12<=Basis<=10) !\n");
scanf("Xd",&basis);
if (basis<2 I I basisMO) <
printf (“\n\n\n\n\nFalsche Eingabe ' (2< = Bas1s< = 10)\n\nXn\n"1;
printf("\n\nUiederholen Sie Ihre Eingabe ’\n\n\n\n“);
> while (basis<2 II basis>10);
/«••••••«»••••...........••••••••••••••••••••«•••••.••••.......•••..........•/
/• Eingabe der umzuwandelnden Zahl ♦/
.............................................................*•...............
printf("XnGeben Sie die zu wandelnde Zanl aus de« Zehnersystem ein ?\n");
scanf("Xd",&zahl);
..............................................................................
/♦ Umwandlungsalgorithmus •/
..............................................................................
zaehlersQ;
printf("\n\nXd (10) = -.zahl);
while (zahl>0) (
z1 eitzaehler] = zahl X basis;
zahl /= basis;
♦♦zaehler;
12-8
..............................................................................
/• Ausgabe der umgewanöelten Zahl •/
for U = zaehler-1 ; i> = 0 ; —1)
printf(“Xd“.zielt 13);
printff (Xd)\n“ , basis ) ;
)
Beispiele für den Bildschirmablauf:
Geben Sie die Basis des Zielsystems an (2 < = Basis < = 10)!
Geben Sie die zu wandelnde Zahl aus dem Zehnersystem ein!
25 —*
25(10)= 11001 (2)
Geben Sie die Basis des Zielsystems an (2 < = Basis < = 10)!
Geben Sie die zu wandelnde Zahl aus dem Zehnersystem ein!
2536—«
2536 (10) = 40121 (5)
Das angegebene C-Programm ist nur lauffähig mit eingegebenen posi-
tiven Startzahlen (> 0).
Da in diesem Beispiel nicht von vornherein klar ist, wieviele Stellen die
umgewandelte Zahl benötigt, wäre die Programmierung dieser Aufgabe
ohne Verwendung des Felds ziel, also nur mit einfachen Variablen,
sehr umständlich.
Die Ausgabe der umgewandelten Zahl erfolgt in umgekehrter Reihenfol-
ge, d. h. die durch den Umwandlungsalgorithmus zuerst gefundenen
Ziffern sind zuletzt auszugeben. Dies macht die Speicherung in einem
Feld notwendig; zur Ausgabe der gespeicherten Ziffern wird, mit dem
höchsten Index beginnend, das Feld "rückwärts" durchlaufen.
Wurde z. B. für die Basis der Wert 5 und für die zu wandelnde Zahl
2536 angegeben, so hat das Feld ziel, nachdem der Umwandlungsal-
gorithmus ausgeführt wurde, folgende Belegung:
12-9
ziel[0] 1
ziel[1] 2
ziel[2] 1
ziel[3] 0
ziel[4] 4
Um die konvertierte Zahl richtig am Bildschirm auszugeben, müssen wir
also zuerst ziel [4], dann ziel [3], usw., bis ziel [0] ausgeben; diese
Ausgabe wird durch folgende for-Schleife vorgenommen:
...............................................................................
/• Ausgabe der uegewanoelten Zahl •/
...................................................•...........................
for (ixzaehler-1 ; i>=0 ; —1)
printf(MXd",zielt 1;
Der Startwert für den rückwärtslaufenden Index i ist zaehler - 1, da
zaehler - bedingt durch den Umwandlungsalgorithmus - um 1 zu hoch
gezählt wurde. In unserem Beispiel hat zaehler den Wert 5.
Am Anfang dieses Kapitels wurde gesagt, daß Vektoren in engem Zu-
sammenhang mit Zeigern stehen:
Wie wir soeben besprochen haben, definiert die Vereinbarung int Vek-
tor [5] einen Vektor vektor mit den 5 Elementen vektor [O], vektor
[1], vektor [4], vektor [i] liegt dann i Elemente vom Vektoran-
fang entfernt.
Wenn man nun den Vektornamen vektor ohne nachgestellten Index
angibt, so erhält man die Anfangsadresse von vektor. Will man also ei-
nen Zeiger, z. B. als int «zeiger deklariert, auf den Anfang eines Vek-
tors vektor positionieren, so kann eine der beiden folgenden, gleich-
wertigen Anweisungen angegeben werden:
zeiger = &vektor [0] (& = Adreßoperator; liefert Adresse
des entspr. Objekts)
zeiger = vektor
12-10
Soll nun das erste Element von vektor einer int-Variablen ganz zuge-
wiesen werden, so könnten wir, da zeiger auf das erste Element
zeigt, wieder zwischen zwei Anweisungen wählen:
ganz = «zeiger
ganz = «vektor
Beide Anweisungen entsprechen
ganz = vektor [O]
lassen sich die eben beschriebenen Operationen in einer Bildsequenz
anschaulich machen:
12-11
int vektor [5]
Bei der Darstellung dieser Deklaration wird davon Gebrauch gemacht,
daß ein Zeiger mit Namen vektor bereits existiert und auf das O. Ele-
ment des Vektors vektor zeigt:
int «zeiger
Deklaration eines noch Undefinierten Zeigers.
12-12
int ganz
Deklaration einer weiteren Variablen.
zeiger = vektor (entspricht zeiger = & vektor [OP
Den Zeiger zeiger auf den Vektoranfang positionieren.
for (i = 0; i <=4;+ + i)
vektor [i] = i + 1;
Den Elementen des Vektors Werte zuweisen.
12-13
ganz = «zeiger (entspricht ganz = «vektor
oder ganz = vektor [0])
Das Element vektor [0] der Variablen ganz zuweisen.
Der Name eines Vektors ohne Angabe von Indizes entspricht also ei-
nem Zeiger auf den Anfang des Vektors, ohne daß dieser als Zeiger de-
klariert wurde; d. h. wir können z. B. über einen Zeiger verfügen, ohne
daß für diesen Zeiger ein Speicherplatz reserviert wird.
Dies wiederum bedeutet, daß ein solcher implizit fest vorgegebener
Zeiger nicht verändert werden kann und immer auf dieselbe Adresse
(Anfangsadresse eines Vektors) zeigt.
Zeigt zeiger nun auf ein bestimmtes Element eines Vektors elem.
dann zeigt zeiger + 1 auf das nachfolgende Element und zeiger - 1
auf das Vorhergehende:
12-14
zeiger = &elem [1]
«(zeiger- 1)= 10
* zeiger = 20
«(zeiger + 1) = 30
(entspricht elem [0] = 10)
(entspricht elem [1] = 20)
(entspricht elem [2] = 30)
12-15
«(zeiger -1) = «zeiger + «(zeiger + 1)
Diese Anweisung entspricht also:
elem [0] = elem [1] + elem [2]
Dieses gezeigte Beispiel wollen wir nun mit fiktiven Adressen auf Ma-
schinenebene einmal durchspielen:
int elem [3], «zeiger;
12-16
zeiger = &elem [1]
«(zeiger - 1) = 10
* zeiger = 20
«(zeiger + 1) = 30
12-17
(zeiger - 1) = «zeiger + «(zeiger + 1)
Allgemein können wir also feststellen, daß zeiger + I auf das l-te Ele-
ment nach zeiger und zeiger - i auf das i-te Element vor zeiger zeigt.
Wenn also zeiger auf vektor [O] zeigt, dann entspricht «(zeiger + 1)
dem Element vektor [1] und «(zeiger + i) dem Element vektor [i]
zeiger + i ist also die Adresse des Elements vektor [i] (&vektor [i]J
und «(zeiger + i) entspricht dem Feldelement vektor [i]
Beispiel 3
Wir wollen den Konvertierungsalgorithmus aus Beispiel 2 so umschreiben, daß die Feld-
elemente nicht mehr mit Angabe eines Index wie in ziel [i], sondern über den Vektoma-
men ziel mit Distanzangabe +i angesprochen werden.
Das nachfolgende Programm leistet dann dasselbe wie das Programm aus Beispiel 2.
12-18
*aln()
C
int zahl, basis, zaenler, i;
int zlel(lOO); /• maximal 100 Elemente fuer Feld ziel ♦/
...............................................................................
/• Eingabe des Zielsystems •/
...............................................................................
do (
printf("\n\nGeben Sie die Basis des Zielsystem an (2<=Basis<=10) ’\n">;
scanf("Xd",&basis);
if (basis<2 ii basis>lO) (
printf("\n\n\n\n\nFal5cne Eingabe ' (2< = Basis< = 10)\n\n\n\n" );
printf(*\n\nWiederholen Sie Ihre Eingabe ’\n\n\n\n“);
} while (basls<2 II basls>lO);
/• Eingabe der umzuwandelnden Zahl •/
...............................................................................
printf("XnGeben Sie die zu wandelnde Zahl aus dem Zehnersystem ein '\n"l;
scanf("Xd",&zahl);
/..............................................................................
/• Umwandlungsalgorithmus • /
...............................................................................
zaehlerxQ;
printf("\n\nXd (10) = ".zahl);
while (zahl>0) <
•(ziel+zaehler) « zahl x basis; /• statt zieltzaehier] nun •(ziel+zaehler) •/
zahl /s basis;
♦♦zaehler;
)
.................................................................................
/• Ausgabe der umgewandelten Zahl •/
.................................................................................
for (l*zaehler-1 ; i>=0 ; —i)
printf ("Xd" ,• (zlelM)); /• statt zielUl nun »(ziel+ll •/
printf(" (Xd)\n",basis);
)
Wie wir gezeigt haben, zeigt ein um i inkrementierter Zeiger zeiger + i
auf das nachfolgende i.te Element. Da auf Maschinenebene aber ledig-
lich einzelne Bytes adressiert werden können, bedeutet dies - abhängig
vom Datentyp des Zeigers - eine Multiplikation von I mit der Anzahl der
Bytes des entsprechenden Objekttyps, auf den der Zeiger zeigt. Eine
anschließende Addition auf den Wert von zeiger positioniert dann an
die entsprechende Stelle.
Wiederholen wir vielleicht an dieser Stelle, wieviele Bytes die einzelnen
Datentypen benötigen:
char 1 Byte
int 2 Byte
long int 4 Byte
float 4 Byte
double 8 Byte
12-19
Den Unterschied zwischen C-Ebene und Maschinenebene bei der
Adressierung soll das nachfolgende Beispiel aufzeigen:
int *zeiger, feld [4];
C-Ebene Maschinenebene
• •
feld [ 0 ] — 100 101
feld [ 1 ] — 102 103
feld ( 2 ] — 104 105
feld [ 3 ] — 106 107
♦
zeiger = feld;
zeiger feld [ 0 ] feld [ 1 ] feld [ 2 ] feld [ 3 ] C-Ebene I Uaschinenebenc >
— ioo zeiger 101
— 102 103
— 104 1C6
— 106 107
• •
zeiger = zeiger + 3;
C-Ebene I Waschinenebene
feld [ 0 ] — 100 101
feld [ 1 ] — 102 103
feld [ 2 ] — 104 105
zeiger feld [ 3 ] — 106 zeiger .19?
♦
12-20
zeiger + 3 auf C-Ebene entspricht auf Maschinenebene zeiger + 3*2.
da int-Variablen 2 Bytes belegen; war zeiger zuvor auf die Adresse
100 gerichtet, so zeigt er nach der Zuweisung zeiger = zeiger + 3 auf
die Adresse 106. da auf Maschinenebene 100 + 2 * 3 (= 106) gerech-
net wird.
Zwischen einem Vektornamen ohne Indexangabe und einem deklarier-
ten Zeiger besteht ein Unterschied, den es zu beachten gilt:
int «zeig, vektor [5];
Bei einem explizit deklarierten Zeiger (in unserem Beispiel: zeig)
handelt es sich um eine Variable, deren Wert beliebig verändert wer-
den kann, oder besser: eine solche Zeigervariable kann ständig auf
andere Variablen ausgerichtet werden:
12-21
zeig = zeig + 3; oder zeig = vektor + 3; oder zeig = Ävektor [3]
Bei einem implizit vorhandenen Zeiger (in unserem Beispiel: vektor;
handelt es sich dagegen um eine Konstante, deren Wert nicht mani-
puliert werden kann (im Bild durch gekennzeichnet). Das heißt
also, daß mit der Deklaration eines Feldes ein Zeiger (Feldname
ohne Indexangabe) implizit angelegt wird, der immer auf den Feldan-
fang zeigt.
Ausdrücke wie
vektor = zeiger;
+ + vektor;
vektor = Ävektor [3];
sind also nicht erlaubt.
Es sollen alle Primzahlen zwischen 1 und 100 bestimmt werden; dazu verwenden wir das
sogenannte Sieb des Eratosthenes:
Zunächst wird auf die Zahl 2 positioniert, und dann alle Vielfachen von 2 herausgestrichen:
1 2 3 < 5 «T 7 9 11 W 13 14 15 W 17 ...
t
Im nächsten Schritt wird um eine Zahl weiterpositioniert, in unserem Fall also auf 3; ist die-
se Zahl noch vorhanden, so handelt es sich um eine Primzahl, von der wiederum alle Viel-
fachen zu streichen sind:
1 2 3 < 5 < 7 J | jö 11 >0 13 M 3« 17 X ...
12-22
Die nächste Primzahl nach 3 wäre dann 5 (noch nicht gestrichen), von der wieder alle Viel-
fachen zu streichen sind, usw. Dieses Verfahren wird wiederholt, bis 100 erreicht ist.
C-Programm:
int prim[lO13, 1, j;
for (1=1 ; l<«100 ; /• prim[03 wird nicht benutzt •/
pnmCl1=1;
for (1=2 ; 1<=1OO ;
if (prlatiJ) /• entspricht if (primt13!=0) •/
For (j=2»i ; j<=100 ; j + =i)
priaLJ1=0;
printf("\n\n\nDie Primzahlen von 1 bis 100 sind:\n\n*);
for (1=1 ; 1<=1DO ;
If (primCll)
printf (•Xd\n"fprimt13);
Erläuterung:
Im Programmteil
for (1=1 ; l<=100
primt13=1;
/* priitQ] wird nicht benutzt •/
wird das Feld prim mit den Werten 1 bis 100 vorbesetzt:
12-23
Der Programmteil
for (1-2 ; 1<=1OO ; ++1)
if (prlmCll) /• entspricht if (primC1]•=0) •/
for (J=2*l ; j<x100 ; =
primtj1=0;
realisiert das Sieb des Eratosthenes und streicht durch Nullsetzen alle Nicht-Primzahlen
im Feld prim
Der Programmteil
printf("\n\n\nDie Primzahlen von 1 Dis 100 sind:\n\n");
for (1>1 ; i<=100 ;
lf (priatin
printf<"Xd\n",prlmC i]);
gibt alle Primzahlen zwischen 1 und 100 am Bildschirm aus; dazu wird der Vektor prim
von 1 bis 100 durchlaufen und jeder Inhalt eines Vektorelements prim [i]. der von Nul
verschieden ist, wird ausgegeben, denn jede von Null verschiedene Zahl in prim ist eine
Primzahl.
Mit Zeigern können die Elemente eines Feldes sehr gut in einer Schleife
bearbeitet werden.
Beispiel 5
Es ist ein Programm zu erstellen, das in einem Feld quad die ersten 20 ganzzahligen
Quadratzahlen (ohne 0) ablegt, und dann alle geraden Quadratzahlen in das Feld gerad
und alle ungeraden Quadratzahlen in das Feld ungerad abspeichert.
Danach soll der Benutzer steuern können, ob er
(1) alle Quadratzahlen oder
(2) alle geraden Quadratzahlen oder
(3) alle ungeraden Quadratzahlen zwischen 1 und 20
am Bildschirm aufgelistet haben möchte.
12-24
C-Programm:
ain()
(
int quad[20], gerad[2Q], ungeradCZO];
int »zeigqu, »zeigge, »zeigun, »pointer, 1;
int wähl;
for (1=0 ; i<=19 ;
quadCi)«(iM)*(iM);
for (zeigqu=quad,zeigge=gerad,zeigun=ungerad ; zeigqu<-&quad[ 19] ; )
if (»zeigqu X 2 «= □)
•zeigge++ = »zelgqu++; /♦ entspricht: »zeigge=»zelgqu; •/
/• ♦♦zeigqu; ++zeigge •/
eise
• zeigunw = •zeigqu++;
do (
printf("\n\n\n\n\n 0 - Programmende\n\n");
printfC 1 = gerade Quadratzahlen\n\n 2 = ungerade Quadratzahlen\n\n")
printfi" 3 - alle Quadratzahlen\n\n\n\n");
printf("\n6eben Sie Ihre gewuenschte Ziffer ein ’\n“);
scanf("Xd",&wahl);
switch(wanl) {
case 0:
printf("\n\n\n\n Progranmende '• \n\n\n\n");
break;
case i:
for (pointer = gerad ; pointer<zeigge ; ♦♦pointer)
printf("XnXd".•pointer);
break;
case 2:
for (pointer = ungerad ; pointer<zelgun ; ♦♦pointer)
printf(“XnXd“,»pointer);
break;
case 3:
for (pointer = quad ; pointerczeigqu ; ♦♦pointer)
printf("XnXd",»pointer);
break;
default:
printf("\n\n\nFalsche Eingabe •");
printf("\n\nuiederholen Sie Ihre Eingabe !\n");
break;
}
1 while (wähl'=0);
12-25
Erläuterung:
Mit der ersten Schleife
for Ci=0 ; 1<=19 ;
quaflC13s(iw*UW,
werden im Vektor quad die ersten 20 Quadratzahlen gespeichert:
In dieser Schleife wird mit Indizierung gearbeitet; dies erfordert bei jedem Schleifendurch-
gang eine Adreßberechnung der Art:
Anfangsadresse
t
&quad [0]
2
t
2 Bytes
für int
i
Index
für quad [i]
Bei der zweiten Schleife
for (zeigqu=quad,zeiggp=gerad>zpigun=ung?raa ; zeigqu< = iquad[i 93 ; )
if (»zeigqu X 2 == 0)
•zpiggp+* = •zeigqu++; /• entspricht: •zeigge=«zeigqu; •/
/♦ ++zpigqu; *+zelgge •/
eise
•zeigunw = «zeigquw;
dagegen arbeiten wir mit Zeigern, die bei jedem Zugriff hochgezählt werden, d. h. auf das
nächste Element ausgerichtet werden. Hier sind bei jedem Schleifendurchlauf - im Gegen-
satz zur ersten Schleife - nur 2 Additionen: für zeigqu (+2 Bytes) und zeigge (+2 Bytes»
oder zeigun (+2 Bytes), erforderlich, welche durch den Operator + + ausgeführt werden.
Im Initialisierungsteil der for-Schleife werden die Zeiger
zeigqu auf die Anfangsadresse des Vektors quad,
zeigge auf die Anfangsadresse des Vektors gerad,
zeigun auf die Anfangsadresse des Vektors ungerad
gesetzt.
12-26
Der Schleifendurchlauf ist beendet, wenn zeigqu auf ein Element zeigt, das nicht mehr
zum Vektor quad gehört.
Mit der Abfrage
if («zeigqu % 2= = 0)
wird überprüft, ob die momentan betrachtete Quadratzahl aus quad. auf die zeigqu au*
genblicklich zeigt, durch 2 teilbar ist, also ob es sich um eine gerade Zahl handelt.
Wenn ja, dann wird mit der Zuweisung
«zeigge + + = «zeigqu + +;
zunächst die entsprechende Quadratzahl aus quad in den Vektor gerad übernommen
(«zeigge « «zeigqu). In dieser Zuweisung wird allerdings auch noch die Erhöhung von
zeigqu (++ zeigqu) und von zeigge (++ zeigge) vorgenommen.
Hätten wir zuerst die Zeiger weiterpositionieren und dann die Zuweisung des Wertes vor-
nehmen wollen, so wäre folgende Anweisung erforderlich gewesen:
« + + zeigge = * + + zeigqu
entsprechend folgender Anweisungsfolge:
++ zeigqu;
♦ + zeigge;
«zeigge = «zeigqu;
Ist die gerade betrachtete Quadratzahl ungerade, so wird sie über den Zeiger zeigun mit
«zeigun + + = «zeigqu + +; im Vektor ungerad aufgenommen.
Der Programmteil
do (
printf(“\n\n\nXn\n 0 « Prograi«iiende\n\n");
printf(" 1 » gerade Guadratzahlen\n\n 2 = ungerade Guadratzahlenxnxn");
printf(“ 3 = alle Quadratzahlen\n\n\n\n");
printf("XnGeöen Sie !nre gewuenschte Ziffer ein ’Xn");
scanf(“Xd“,&wahl);
switch(wahl) (
case 0:
printf("\n\n\n\n Prograanende •• \n\n\n\n");
break;
case i:
for (pointer s gerad ; potnter<zeigge ; ««polnter)
printf(“XnXd“,«pointer);
öreak;
case 2:
for (pointer s ungerad ; pointerczeigun ; ««polnter)
printf(“\nXd",«polnter) ;
öreak;
case 3:
for (pointer = quad ; pomterczeigqu ; ««pointeri
printf(“\nXd",«potnter);
öreak;
default:
printf<“\n\n\nFalsche Eingabe •");
pr intf(“\n\nUiederholen Sie Ihre Eingabe '\n“);
öreak;
)
) while (wahl'=0);
12-27
wird solange durchlaufen, bis der Benutzer die Ziffer 0 eingibt.
Bei Eingabe einer der Ziffern 1, 2 oder 3 wird der Zeiger pointer inn Imtialisierungster oe
entsprechenden for-Schleife auf den Anfang des jeweiligen Vektors gerad, ungerad
oder quad gesetzt. Bei jedem Schleifendurchlauf wird dann das Vektorelement, auf das
pointer momentan zeigt («pointen, am Bildschirm ausgegeben, und danach wird de
Zeiger pointer mit ++ pointer auf das nächste Vektorelement positioniert, de for
Schleife wird solange durchlaufen, bis das Ende des entsprechenden Vektors erreicht ist.
Grundsätzlich können wir festhalten, daß man jeden Zugriff auf ein Feid-
element sowohl mit Indexangabe als auch über Zeiger durchführe^
kann. So ist es z. B. gleichgültig, ob man bei der Deklaration von forma-
len Parametern einer Funktion einen Zeiger oder einen Vektor definiert.
Bei der Übergabe eines Vektornamens an eine Funktion wird nämlich in
Wirklichkeit die Adresse des Vektoranfangs übergeben. Innerhalb einer
Funktion ist ein solcher übergebener Parameter dann eine Zeigervaria-
ble, die auch verändert werden kann, was aber nicht zur Änderung der
Anfangsadresse des Vektors führt.
Bei der Übergabe eines Vektornamens als aktueller Parameter an eine
Funktion wird lediglich seine Anfangsadresse auf den Stack in eine ei-
gene funktionslokale Zeigervariable kopiert; der Inhalt von Zeigervaria-
blen darf ja - im Unterschied zu Feldnamen, die den festen Anfang ei-
nes Feldes kennzeichnen - manipuliert werden.
Beispiel 6
Es sollen über den Bildschirm maximal 100 ganze Zahlen eingegeben werden; bei Eingabe
der Zahl -30000 ist die Eingabe beendet.
Wir wollen ein C-Programm erstellen, das die eingegebenen Zahlen sortiert am Bildschirm
wieder ausgibt; dazu wollen wir em einfaches Sortierverfahren entwickeln;
Wenn wir z. B. folgende Zahlen sortieren müßten:
2
3
4
1
könnten wir folgendermaßen vorgehen:
1) Wir stellen einen Zeiger auf das erste Element und vergleichen dieses Element mit aller
anderen Zahlen; wird bei einem Vergleich ein kleineres Element gefunden, so ist es mit
dem momentan an erster Stelle stehenden Element zu vertauschen.
Der Vergleich der restlichen Elemente soll rückwärts erfolgen, d. h. zuerst wird das letzt-
Element mit dem ersten Element, dann das vorletzte Element mit dem ersten Element, usw
verglichen:
12-28
4'
3
4
2
Nach einem Durchlauf ist sichergestellt, daß das erste Element auch das kleinste Element
ist
2) Für den zweiten Durchlauf positionieren wir den Zeiger auf das nächste (zweite) Element
und vergleichen dieses wieder rückwärts mit allen Elementen:
4
2
1
—-2
4
3
Nach dem zweiten Durchlauf steht fest, daß das zweite Element an seiner richtigen Position
steht
3) Für den 3. Durchlauf positionieren wir deshalb den Zeiger auf das dritte Element und ver-
gleichen dieses mit dem vierten Element:
1
2
—^4^
3'
1
4
Allgemein können wir also zu diesem Sortierverfahren*) sagen, daß wir einen Zeiger 1 auf
die zu sortierende Stelle benötigen; zusätzlich wird noch ein Zeiger 2 benötigt, der vom
Vektorende beginnend rückwärts läuft. Es ist dann das mit Zeiger 1 festgehaltene Element
sequentiell mit allen Elementen zu vergleichen (und eventuell zu vertauschen), auf die der
schneller laufende" Zeiger 2 zeigt.
for • zeiger1=vekt anfang ; zeigerl<=vekt_ende-1 ; ♦♦zeigerl ) ♦
♦---------------------------------------------------------------------------4
for i zeiger2=vekt ende ; zeiger2>=zeiger1+l ; --zeiger? ) •
» 4-----------------------------------------------------------------------4
» ' • if (»zeigen > »zeigerZ) ’
• I • J N f
i i 4---------------------------------------------------------------------4-----4
• 'Vertausche die Elenente, auf die zeigen und zeigerZ zeigen 1 '
♦ 4 4---------------------------------------------------------------------4-----4
• ?
zeiger 1 muß also vom Vektoranfang bis zum vorletzten Vektorelement vekt_ende - 1
laufen. Der rückwärts laufende zeiger 2 muß immer bei vekt__ende beginnen und bis
zeigerl + 1 laufen, d. h. daß jede erneute Ausführung der inneren Schleife einen Schlei-
fendurchlauf weniger besitzt, was ja sinnvoll ist. da ein weiteres Element an seine richtige
Position gebracht wurde.
HINWEIS: *) Es gibt weit bessere Sortierverfahren; dies soll hier aber nicht Gegenstand
unserer Betrachtung sein.
12-29
C-Programm:
main()
<
int feidtiOO), •zeigen;
zelgersfeld;
printf("\n\nGeben Sie Ihre Zahlen ein (Ende--3D000)\n\n");
do (
printf("Xd.Zahl , zeiger-feld* 1);
scanf("Xd",ze1ger**);
) while (♦(zeiger-1) I« -30000);
zeiger-s?;
Erläuterung:
In main wird mit zeiger = feld; in der Zeigervariabien zeiger die Anfangsadresse des
Vektors feld gespeichert.
Im Programmteil
printf(*\n\nGeben Sie Ihre Zahlen ein (Ende=-30000)\n\n-);
do <
printf ("Xd.Zahl ?•.zeiger-feld+1);
scanf("Xd"»zeiger**);
) while (•(zeiger-l) -30000);
12-30
werden solange Zahlen eingelesen, bis die Zahl -30000 eingegeben wird. Die Abbruchbe-
dingung lautet «(zeiger - 1)! = -30000, da zeiger nach der Eingabe schon ein Element
weiterpositioniert wurde.
Die Nummer der einzugebenden Zahl lassen wir uns mit
zeiger
t
momentane
Zeigerposition
feld +1
t
Anfangsadresse
des Vektors feld
berechnen (+1. um das Zählen bei 1 beginnen zu lassen).
Solche Ausdrücke, in denen 2 Zeiger subtrahiert werden, sind erlaubt;
die Addition, Multiplikation oder Division zweier Zeiger ist ebensowenig
erlaubt wie Shift-Operationen oder andere logische Verknüpfungen mit
zwei Zeigern.
Erlaubte Arithmetik mit Zeigern:
int «zeig 1, *zeig2, wert; wert = 3;
Addition einer M-Konstante oder einer M-Variablen:
zeigt+ = 4; /• Positioniere zeigt 4 Elemente */
/» weiter */
zeigt += wert;/* Positioniere zeigt um wert (3) •/
/* Elemente weiter */
Subtraktion einer M-Konstante oder einer M-Variablen:
zeigt - = 4; /* Positioniere zeigt um 4 Elemente */
/* zurück */
zeigt -= wert;/* Positioniere zeigt um wert (3) */
/* Elemente zurück */
Subtraktion zweier Zeiger:
zeigt -zeig2
Vergleiche zweier Zeiger:
► zeig2
12-31
Unerlaubte Arithmetik mit Zeigern:
Addition, Division und Multiplikation von Zeigern:
zeigl = zeig2 + zeig*!; zeig2 = zeigl/zeig2; zeigl * = 4;
float- oder double-Werte dürfen nicht auf Zeiger addiert werden
zeigl + = 3.37;
Zeiger dürfen nicht mit Shift-Operationen oder anderen logischen
Operatoren behandelt werden:
zeig2 >>= 4; zeigl = ! zeig2; zeig2 = zeigl & zeig2;
Doch nun weiter mit der Programmerläuterung:
Im Programmteil
zeiger-=2;
sortiere•feld»zeiger);
printf("\n\nDie sortierten Zahlen sind :\n"J;
ausgabelfeld »zeiger);
wird mit zeiger - = 2; die Zeigervariable zeiger auf die letzte relevam-
Ziffer positioniert. Die Subtraktion von 2 wird notwendig, da beim letzte^
Durchlauf der do ... while-Schleife mit scanf(”%d”, zeiger ++) zum
einen die nicht zur Zahlenreihe gehörige Zahl -30000 eingelesen wur-
de, zum anderen der zeiger auf das nächste Element (zeiger+ +) posi-
tioniert wurde.
Danach wird die Funktion sortiere mit den aktuellen Parametern
feld zeigt auf die erste eingegebene Zahl
zeiger zeigt auf die letzte relevante Zahl
aufgerufen.
Mit dem Aufruf der Funktion ausgabe werden dann die mit sortiere
sortierten Zahlen am Bildschirm ausgegeben. Zur Ausgabe wird wieder
die Adresse der ersten Zahl (feld) und die Adresse der letzten Zab
(zeiger) benötigt; diese beiden Adressen werden als aktuelle Parame-
ter an ausgabe übergeben.
Bei der Deklaration der formalen Parameter einer Funktion sind
int vekt [ ]; und int *vekt;
völlig äquivalent. In Beispiel 6 hätten wir also - ohne Auswirkungen au'
den Programmlauf - die formalen Parameter vekt_anfang unp
12-32
vekt__ende in den Funktionen ausgabe ud sortiere wie folgt dekla-
rieren können:
int vekt_anfang [ ], vekt__ende [ ];
Welche Schreibweise man angibt, hängt im wesentlichen davon ab. wie
man dann in einer Funktion auf die Feldelemente zugreift:
int vekt [ ]; —> vekt [i],
int *vekt; —> «(vekt + i)
Wenn ein Vektorname an eine Funktion übergeben wird, so sind jeden-
falls 2 Betrachtungsarten möglich:
Übergabe eines Vektors: Übergabe eines Zeigers: int vekt [ ]; int «vekt;
Welche dieser beiden Betrachtungen in einem konkreten Fall sinnvoll
ist. muß in der jeweiligen Anwendung entschieden werden. Unabhängig
von der Deklaration, ob int «vekt; oder int vekt [ ], können natürlich
beide Zugriffsarten in einer Funktion verwendet werden, wenn dies sinn-
voll erscheint.
Es gibt natürlich auch die Möglichkeit, lediglich einen Teil eines Feldes
an eine Funktion zu übergeben. Dazu setzt man beim Funktionsaufruf
einen Zeiger auf den Anfang des zu übergebenden Teilfeldes.
Mit der Deklaration
int feld [5];
würde auf C-Ebene folgendes Speicherbild entstehen:
feld ► feld[O] feld[1] feld[2] feld[3] feld[4]
12-33
Mit dem Funktionsaufruf
ausgabe (& feld [2]); oder ausgabe (feld + 2);
würde dann an die Funktion ausgabe als Startwert von feld das dritte
Element von feld übergeben:
feld --------
Für ausgabe
beginnt das
Feld feld
be/feld[2]
Innerhalb der Funktion ausgabe bestehen wieder 2 Möglichkeiten, den
formalen Parameter zu deklarieren:
ausgabe (vekt)
int vekt [];
}
ausgabe (vekt)
int «vekt;
oder {
)
12.2 char-ZEIGER
Bisher haben wir nur die Möglichkeit kennengelernt, einzelne Zeichen
in Variablen abzuspeichern. Was ist aber nun zu tun, wenn nicht nur ein
Zeichen, sondern eine ganze Zeichenkette (auf engL: string) zusam-
menhängend abzuspeichern ist. Eine Zeichenketten-Konstante wäre
z. B.
”lch lerne C”
Eine solche String-Konstante kann dann als char-Vektor aufgefaßt und
über einen char-Zeiger angesprochen werden.
12-34
i -40]
c ....[1]
h .[2]
LJ ...43]
1 .44]
e ...45]
r • 46]
n [7]
e ...48]
LJ ....[9]
c ...410]
10 ...411]
Der C-Compiler markiert das Ende des char-Vektors mit dem Null-Zei-
chen 10, damit beim Programmlauf das Ende der Zeichenkette erkannt
werden kann. Deshalb ist die Länge eines solchen Vektors um 1 größer
als die Anzahl der relevanten Zeichen.
Sehr oft stehen String-Konstanten als aktuelle Parameter in Funktions-
aufrufen, z. B.:
printff’Gib eine ganze Zahl ein \n”)>
An die Funktion printf selbst wird dann beim Aufruf nicht die String-
Konstante Gib eine ganze Zahl ein \n. sondern ein Zeiger auf den
Anfang dieses char-Vektors übergeben. Das Ende dieses Zeichen-
Vektors wird von printf dann am \O-Zeichen, das vom Compiler bei
der Übersetzung angefügt wurde, erkannt.
Zeichenketten können auch Zeigervariablen zugewiesen werden:
gruss = "Guten Tag”;
HINWEISE: Eine string-Konstante ist eine Folge von beliebig vielen Zeichen, die mit
”... ” geklammert sind. (” ” wäre eine leere Zeichenkette).
Die Anführungsstriche gehören nicht zur Zeichenkette, sondern sind lediglich
Begrenzer.
Das Anführungszeichen ist in einer Zeichenkette durch \” darzustellen
(Beispiel: "V’VorsichtV’ ” entspräche dem String "Vorsicht").
Es ist zwischen Zeichen und Zeichenkette zu unterscheiden. ’A' ist nicht das
gleiche wie "A". Bei A' handelt es sich um ein einzelnes Zeichen, das durch
seinen ASCII-Wert im Rechner dargestellt wird. Bei "A" handelt es sicht um
eine Zeichenkette. die im Rechner als Vektor A \0 (2 Zeichen) dar-
gestellt wird.
12-35
Um diese Zuweisung zu ermöglichen, sollte gruss als char «gruss;
definiert sein, d. h. als Zeiger auf einen char-Vektor.
Mit der Zuweisung gruss = "Guten Tag”; wird dann an gruss nicht
die ganze Zeichenkette Guten Tag, sondern lediglich ein Zeiger aut
den Beginn dieser Zeichenkette übergeben.
C verfügt über keine Operatoren, die es ermöglichen, eine Zeichenkette
als Einheit zu behandeln.
Wollten wir z. B. die Zeichenkette Guten Tag. auf die gruss zeigt,
nach begruessung (zuvor mit char «begruessung deklariert) kopie-
ren, so könnten wir die Funktion strcpy aus der Standardbibliothek auf-
rufen:
strcpy (begruessung, gruss)
Obwohl die Funktion strcpy in der Standardbibliothek schon vorhan-
den ist, wollen wir versuchen, strcpy selbst zu entwerfen. Wir geben
hierbei verschiedene mögliche Versionen an:
Versionen mit Vektoren:
strcpy(ziel,quelle) /• 1.Vers Ion «it Vektoren •/
char zielt], quellet];
<
int zaehl;
zaehl=O;
while (quelletzaehl]) < /• Solange gerade betracht. Zeichen •/
zieltzaehl]=quelletzaehl];/♦ (quelletzaehlJ) ungleich 0, da letzt. •/
zaehl++; /• Zeichen einer Zeichenkette \0 Ist. •/
zieltzaehl]=‘\0•;
>
strcpy(ziel,quelle) /• 2.Version eit Vektoren •/
char zielt], quellet];
(
int zaehl;
zaehlsO;
while ((zleltzaehlHquelletzaehl]) •= *\0*> /• Zuweisung und vergleich •/
zaehl++; /• m einer Einheit •/
Manche Deklarationen entsprechen einer Reservierung von Speicher-
platz, andere informieren den Compiler lediglich über die Beschaffen-
heit eines Objekts. Bei der Deklaration von formalen Parametern einer
Funktion wird z. B. kein Speicherplatz reserviert, da solche Deklaratio-
nen lediglich die Eigenschaften der aktuellen Parameter angeben, wel-
che beim Funktionsaufruf ja schon existieren.
12-36
Deshalb darf bei den Vektor-Deklarationen innerhalb von Funktionen
auf die Angabe einer Dimensionsgrenze verzichtet werden:
strcpy (ziel, quelle)
char ziel [ ], quelle [ ]; /* keine Angabe einer oberen Grenze */
} *”
Den Compiler interessiert nämlich nur der Datentyp der Vektorelemente
und nicht die Vektorlänge.
Versionen mit Zeigern:
strcpy(ziel,quelle)
char »ziel, «quelle;
while ((«z1el=«quelle) • =
ziel**;
quelle**;
/• 1.Version mit Zeigern •/
\0) < /• Zuweisung und Vergleich wieder •/
/• in einer Einheit <•/
strcpy(ziel,quelle)
char «ziel, «quelle;
(
while ((<ziel**s<quelle**) •=
; /• leere Anweisung •/
/• 2.Version eit Zeigern •/
'\0 ) /• entspricht folg. Anweisungsfolge: •/
/• «ziel=«quelle; •/
/• («ziel »« \0) = Vergleich •/
/• quelle**; ziel**; •/
strcpy(ziel,quelle)
char «ziel, «quelle;
<
while («ziel**=«quelle*+)
; /• leere Anweisung •/
/♦ 3.Version mit Zeigern
/• entspricht 2.Version eit Zeigern; •/
/• nur wurde auf expliziten Vergleich •/
/♦ mit \0’ verzichtet, da beim •/
/• Ausdrucksergebnis \Q automatisch •/
/• die while-Schleife abgebroch. wird •/
HINWEISE: Bedenken Sie bei allen Beispielen, daß es sich bei einem einfachen = Zei-
chen nicht um einen Vergleich = =, sondern um eine Zuweisung handelt.
Eine while-Schleife wird solange ausgeführt, wie eine Schleifenbedingung
erfüllt, bzw. ein Ausdruck ungleich 0 ist.
12-37
Beispiel 7
Wir wollen ein C-Programm entwerfen, das zunächst den Benutzer auffordert, eine Ze -
chenkette einzugeben und ihn dann nach einem zu zählenden Zeichen fragt.
Nach dieser Eingabe soll unser Programm ausgeben, wie oft dieses Zeichen in der einge-
gebenen Zeichenkette vorkommt.
C-Programm:
«include <stdio.h>
«define LAENGE ao /• maximale Laenge der eingegebenen Zeichenkette •/
main (I
C
char zeichenketteCLAENGE+11, such_zeichen;
printf("\n\n\nGeben Sie eine Zeichenkette (max. SO Zeichen) ein «\nXn">;
-----Iles zeile em (zei chenkette); /• Einlesen einer Zeichenkette in einer •/
/• Zeile in den vektor zeichenkette • /
printf("\n\n\n\n\nzu zaehlendes Zeichen ’\n\n");
such_zeichen=getchar();
printf("\n\n\nDas Zeichen Xc ist in der obigen Zei chenkette\n-, such_zeichen);
printft“ Xd mal enthalten\n",zaehl zeichen(zeichenkette,such Zeichen));
) \-------------------------Z----------------
lies_zelle_em(strlng)
char *string;
(
- int 1;
1=0;
do ( /• hier werden die Zeichen einzeln eingelesen •/
stringt1J=getchar(); /• und zwar in den Vektor strlng, •/
i*+; /• bis 80 Zeichen oder RETURN (* \n-Zeichen) • /
) while (strmgt 1-11' * \n ’ && KLAEN6E); /• eingegeben wurde •/
stringC1]= \0‘; /• Vektorende kennzeichnen •/
>
zaehl_Zeichen (stnng , zei chen)
char «string, Zeichen;
int zaehler;
zaehler=0;
do (
lf («strlng «x Zeichen) /• Wenn ein uedereinst1mmendes Zeichen •/
♦♦zaehler; /• gefunden wird, dann zaehler erhoehen •/
} while («string++); /• bis strlng -Zeiger auf Null zeigt •/
return(zaehler);
)
12-38
Den Programmteil
d0 < /• hier werden die Zeichen einzeln eingelesen •/
string[1]=getchar(); /• und zwar in den Vektor string, •/
/♦ Dis 80 Zeichen oder RETURN <» Xn-Zeichen) */
) while (itflngn-IP »An- && KLAENGE) ; /• elngegeben wurde •/
hätten wir noch kompakter angeben können:
while ((string [!♦♦] = getchar ())! = ’ \ n’ & & i < LAENGE);
/♦ leere Anweisung */
Beim Aufruf der Funktionen lies_zeile_ein und zaehl_Zeichen wird nicht der Inhalt,
sondern lediglich die Anfangsadresse des Vektors zeichenkette übergeben.
Beispiel 8
Es soll nochmals das Spiel 21 programmiert werden (siehe Beispiel 3, Kap. 11.2.1); dies-
mal sollen aber die Namen der Spieler eingegeben werden. Dazu benötigen wir zwei char-
Vektoren:
char spiel_1C50]; /• Vektor zum Speichern des Namens von Spieler! ♦/
char spiel_2£5O3; /• Vektor zum Speichern des Namens von Spielerz •/
In diese Vektoren können dann die Namen der beiden Spieler eingelesen werde.
printf(“\n\nXn\nUle heisst der Spieler! ’xnxnxnxn“);
scanf("Xs*,spiel_1);
printf("XnXnXnXnWie heisst der SplelerZ ’XnXnXnXn");
scanf("Xs",spiel_2);
An früherer Stelle wurde gesagt, daß bei scanf die Adresse des Speichers anzugeben ist,
in dem die Eingabe abgelegt wird; dazu verwendeten wir bisher immer den Adreßoperator
& Dies ist hier nicht notwendig, da es sich bei einem Vektornamen um einen Zeiger han-
delt, der die Adresse des ersten Vektorelements enthält; das Formatelement %s gibt an,
daß hier nicht nur ein einzelnes Zeichen, sondern eine Zeichenkette eingegeben werden
kann.
Mit dem Programmteil
while (summe.strelchhoelzer
if (wer_dran)
summe_strelchhoelzer =
eise
5umme_streichhoelzer =
wer_dran=+*wer_dran X 2;
> 0) <
spiel(summe_stretchhoelzer.spiel_1);
spiel(surnMB-Strelchhoelzer,spiel_2);
wird die Funktion spiel aufgerufen und ihr abwechselnd die Anfangsadresse von spiel_1
und von spiel_2 übergeben; der zugehörige formale Parameter wurde in spiel mit char
»name deklariert.
Aus diesen Überlegungen ergibt sich dann folgendes
12-39
C-Programm:
.......*..................................................................................
/••
S P I E L 2 1
..........................................................................................
..........................................................................................
main()
<
int summestretchhoelzer,
wer_dran=1;
char spiel_l(50]; /• Vektor zu« Speichern des Naaens von Spieleri •/
char spielj2t50i; /• Vektor zum Speichern des Namens von Spieler? •/
...............................................................................
/• Ausgabe einer ueberschrift und vorbesetzen der int-Varlaölen •/’
/• summe streichhoelzer mit Uert 21 (Anfangswert) •/
......................................................•.....................
printf("\nX45s","S p 1 e 1 2 1*1;
printf t"\nX45s\n\nXn\n\n"
summe_streichhoelzer=21;
printf("\n\n\n\nwie heisst der Spielen ’\n\n\n\n"i;
scanf("Xs",spiel_1);
printf("\n\n\n\nWle heisst der Spieler? ?\n\n\n\n");
scanf("Xs" ,spiel_2);
printf C\n\n\n\n\n\n\n");
/• Aufruf der Funktion spiel mit den aktuellen Parametern •/
/• summe stretchhoelzer und wer dran •/
..........................................•................................
wnile (sum«e_streichhoelzer > □) (
If (wer_drän)
summe_streichhoelzer = spiel(summe_streichhoelzer, spiel 1);
eise
summe.strelchhoelzer » spiel(summe_streichhoelzer,spiel_2);
wer_dran=>+wer_dran X 2;
.................................................................................
/• Defintion der Funktion spie 1 alt formalen Paraaeter •/
/• anzahl strelchhoelzer und wechsel ' ♦/
/...............................................................................
spiel(anzahl_stretchhoelzer,name)
int anzahl_streichhoelzer;
char »name;”
<
int weg_streichhoelzer;
printf(“Wieviele Streichoelzer nimmst du,");
printfC'Xs ’\n*,namel;
scanf("Xd",&weg_streichhoelzer);
while (weg_5treichhoelzer<l II weg_streichhoelzer>4) (
prmtf<"\n\n\nDu darfst nur zwischen 1 und 4 StrelchhoelzerXn*);
printfCvom Tisch nehmen '\n\n\n\n");
printf("Also, wiederhole deine Eingabe '\n\n\n">;
printf("Wieviele Strelchhoelzer nimmst du, *);
printf("Xs ’\n",name);
scanf("Xd",4weg_streichhoelzer);
12-40
anzahlstre 1chhoelzer-=weg,stret chhoelzer;
if (anzahl_streichhoelzer<=0) (
printf("\n\n\n\nEs liegen Keine Streichhoelzer menr auf dem Tisch");
printf(“\n\n\nXs",name);
printf (", du hast verloren »\n\n\n\n\n");
3
eise <
pr 1ntf("\n\n\n\nEs liegen noch Xd Streich",anzahl_strelchhoelzer) ;
printf("hoelzer auf dem Tisch\n\n\n\n\n\n\n");
)
return(anzahl_streichhaelzer);
Die Programmiersprache C bietet in ihrer Standardbibliothek einige Funktionen zur Mani-
pulation von Zeichenketten an; wir wollen nun versuchen, einige dieser schon vorgegebe-
nen Funktionen selbst zu entwerfen. Unsere Funktionen müssen natürlich nicht exakt de-
nen aus der Standardbibliothek entsprechen:
strcat (kett 1, kett2)
Diese Funktion kopiert die Zeichenkette kett2 an das Ende der Zeichenkette kettl;
strcat setzt voraus, daß kettl genug Platz besitzt, um beide Zeichenketten (kettl und
kett2) aufnehmen zu können. Eine mögliche Lösung wäre:
strcat(kettl,kett?)
cnar »kettl, »Kett2;
(
while («kettl**) /• Ende von kettl suchen •/
; /• leere Anweisung •/
kettl—; /♦ kettl um 1 zu hoch gezaehlt ♦/
while («kettl** - «kett2**) /• kettZ ans Ende von kettl kopieren •/
; /♦ leere Anweisung •/
strncat (kettl, kett2, laenge)
Diese Funktion ist nahezu identisch zu strcat: der Unterschied ist, daß lediglich laenge
Zeichen von kett2 an kettl angehängt werden:
strncat(kett!,kett2,laenge)
char «kettl, «kett2;
int laenge;
(
int 1;
while («kettl**) /• Ende von kettl suchen ♦/
; /♦ leere Anweisung •/
kettl—; /• kettl um 1 zu hoch gezaehlt •/
for (1=0 ; Xlaenge && «kett2 •= ‘\0 ; **1)
•kettl** = »kett?**;
•kettl«*\0‘ ;
>
12-41
strcmp (kettl, kett2)
Diese Funktion vergleicht die beiden Zeichenketten kettl und kett2 zeichenweise ur 2
von links beginnend. Als Resultat liefert strcmp
- den Wert 0 bei Übereinstimmung der beiden Zeichenketten,
- einen negativen Wert, wenn das erste nicht übereinstimmende Zeichen von
kettl kleiner (nach ASCII-Code? als das von kett2
ist und
- einen positiven Wert, wenn das erste, nicht übereinstimmende Zeichen von
kettl größer (nach ASCII-Code? als das von kett2
ist
Diesmal wollen wir eine mögliche Lösung unter Verwendung von Vektoren (anstelle vor
Zeigern) angeben:
main ()
<
char »stringlt *string2;
stringl »"hallo";
string2=*hallo";
printf ("\nXd\n",strcmp(stringl »stnng2) );
stringl="halLo";
stnng2 = "hallo“;
printf("\nXd\n",strcmp(stringl, strlngZ));
stringl»"hallo";
string2-"halLo";
printf("\nXd\n"»strcmp(stringl,string?));
strcmp(Kettl»kettZ)
char kettUl, kettZCJ;
<
int zaehl=O;
while (Kettl[zaehl] == KettZCzaehl])
if (kettl tzaehlw] == \0)
return(0);
re turn(kettltzaenl]-kett ZEzaehl]);
HINWEIS:
Eine Anweisung
feld [zaehler + +] = x; entspricht
Eine Anweisung
feld [+ + zaehler] = x; entspricht
feld [zaehler] = x;
zaehler = zaehler + 1;
zaehler = zaehler + 1;
feld [zaehler] = x;
12-42
Bildschirmausgabe:
-32
32
(-L- - ’L’)
(76 - 108)
(’L’ - ’L’)
(108-76)
strncmp (kettl, kett2, laenge)
Diese Funktion ist nahezu identisch zu strcmp. unterwirft aber nur laenge Zeichen einem
Vergleich.
Hier geben wir eine mögliche Zeiger-Version an:
main()
(
char «stnngl , «strlng?;
int wieviel;
stringl= ’hallo“;
5tnng2s"halLo";
wieviels3;
printf("\nXd\n“,strncmp(stringl,strlng?,wieviel)); /• 3 Zeichen vergleichen ♦/
wieviel=5;
printf<"\nXd\n"»strncmp(strlng!»stringZ,wieviel)); /• 5 Zeichen vergleichen •/
strncmp(kett!,kett2,laenge)
char kettlt], kettZCl;
int laenge;
(
int zaehl;
for (zaehl=l ; «kettl
if (zaehl=slaenge)
return(0);
» «kett? ; kett1++,kett2*+|Zaehl++)
return(«kett 1-«kett2);
Bildschirmausgabe:
0
32
12-43
strlen (kette)
Diese Funktion liefert die Länge, d. h. Anzahl der Zeichen der Zeichenkette kette ohne
\O-Zeichen.
Hierzu wollen wir 2 Versionen angeben:
strlen(kette) /• 1. Version
char «kette;
(
int zaehl;
for (zaehl=0 ; «kette+* 's \0‘ ; zaehlw)
; /• leere Anweisung •/
return(zaehl);
strlen(kette) /• 2. Version •/
char «kette;
<
char «lauf_zeiger;
lauf_zeiger=kette;
while («lauf-zelger !« -\0‘)
lauf_zeiger+«;
return(lauf_zeiger-kette); /• Zeichenkettenenue - Zeicnenkettenanfang •/
Die beiden Anweisungen (in einer anderen Funktion)
string)=Zeichen;
printf("\nXd\n“,strlen(stringl)) ;
würden die Ausgabe der Zahl 7 am Bildschirm bewirken, da stringl 7 Zeichen enthält.
strchr (kette, Zeichen)
Im ursprünglichen C hatte diese Funktion den Namen Index
Diese Funktion gibt einen Zeiger an die aufrufende Funktion zurück, der auf das erste Vor-
kommen von Zeichen in kette zeigt. Wird Zeichen in kette nicht gefunden, dann wird
ein NULL-Zeiger an das aufrufende Programmteil zurückgegeben.
Da diese Funktion keinen einfachen char-Wert, sondern einen Zeiger auf einen char-
Wert zurückliefert, ist folgender Funktionskopf erforderlich:
char * strchr (kette, Zeichen)
Um der aufrufenden Funktion mitzuteilen, daß strchr einen Zeiger auf einen char Wert zu-
rückliefert, muß strchr in der Funktion, von der aus es aufgerufen wird, mit
char «strchr ();
vereinbart werden.
12-44
Das nachfolgende Programm:
aain()
(
char »stringl, such;
char *strchr(); /♦ legt fest, dass Funktion strchr einen Zeiger */
/* auf einen char-Wert liefert */
stringl«="Zeichen";
such-’h1;
printf("\n%s\n",strchr(stringl,such));
char »strchr(kette,Zeichen)
char »kette, Zeichen;
I
char »zeiger;
for (zeiger-kette ; »zeiget ! ’\0' &£ »zeiget Zeichen ; zeiger++)
; /* leere Anweisung ♦/
if (»zeiger “«’\0')
return(0);
return(zeiger);
)
gibt die Zeichenkette hen am Bildschirm aus.
Das zu suchende Zeichen ist nämlich h. und der Aufruf von strchr liefert einen Zeiger auf
das erste Vorkommen dieses Zeichens zelchenXO
Da der Aufruf von strchr in einem printf-Befehl erfolgt
printf(”\n^bs\n”, strchr (stringl, such));
wird eine Zeichenkette (%s) ab dem zurückgelieferten Zeiger bis zum \O-Zeichen ausge-
geben; also wird die Zeichenkette hen ausgeben.
strrchr (kette, Zeichen)
Im ursprünglichen C hatte diese Funktion den Namen rindex
Diese Funktion gibt einen Zeiger an die aufrufende Funktion zurück, der auf das letzte Vor-
kommen von Zeichen in kette zeigt.
Wird Zeichen m kette nicht gefunden, dann wird ein NULL-Zeiger an das aufrufende
Programm zurückgegeben.
12-45
main()
char «stringl, such;
char «strrchr(); /* legt fest, dass Funktion strrchr
/♦ auf einen char-Wert liefert
stringl-"Zeichen";
such-’ e •;
printf ("\nU\n",strrchr(stringl,such)) ;
einen Zeiger */
*/
char «strrchr(kette,Zeichen)
char «kette, Zeichen;
<
char «zeiger;
zeiger-kette;
while (*zeiger++) /* Ende der Zeichenkette kette finden ♦/
; /« leere Anweisung ♦/
while (—zeiger 1- kette frfc «zeiger !- Zeichen) /« rueckwaerts suchen«/
; /* leere Anweisung •/
if (zeiger — kette && «zeiger !- Zeichen)
return(0);
return(zeiger);
)
Da die Funktion strrchr ebenso wie strchr keinen einfachen char Wert, sondern einen
Zeiger auf einen char-Wert liefert, muß dies wieder im Funktionskopf hier als
char • strrchr (kette, selchen),
und in der aufrufenden Funktion, hier als char «strrchr () angegeben werden
Das obige Programm würde die Zeichenkette en am Bildschirm ausgeben. Das zu
suchende Zeichen ist hier e und der Aufruf von strrchr liefert einen Zeiger auf das letzte
Vorkommen dieses Zeichens zelch0n\O
Mit dem Befehl
prlntf(”\n^a\n”, strrchr (stringl, such));
wird also eine Zeichenkette (%s) ab dem zurückgelieferten Zeiger bis zum \0-Zeichen
ausgegeben. Dies erklärt dann die Ausgabe der Zeichenkette en am Bildschirm.
Beispiel 10
Wir wollen ein C-Programm erstellen, das zunächst über Bildschirm eine Zeile Text und
dann eine zu suchende Zeichenkette einliest; danach soll unser C-Programm die Position
des zu suchenden Textteils in der eingegebenen Zeile ermitteln. Ist die gesuchte Zeichen-
kette in der eingegebenen Zeile nicht vorhanden, so wird dies durch den Wert -1 ausge-
drückt.
12-46
Beispiele für den Ablauf am Bildschirm:
Geben Sie eine Zeichenkette (max. 80 Zeichen) ein!
Der Schnee ist weiss—1
Geben Sie den zu suchenden Text (max. 80 Zeichen) ein!
ist—1
Der Text
ist
BEGINNT IN DER ZEICHENKETTE
Der Schnee ist weiss—*
A
Geben Sie eine Zeichenkette (max. 80 Zeichen) ein!
Der Schnee ist weiss
Geben Sie den zu suchenden Text (max. 80 Zeichen) ein!
Das—1
Der Text
Das
ist in der Zeichenkette
Der Schnee ist weiss
NICHT ENTHALTEN
12-47
C-Programm:
«include <stdio.h>
«deflne LAENGE 80 /• maximale Laenge der eingegebenen Zeichenkette •/
main ()
(
char zeichenkette[LAEN6E>13, such_ketteCLAENGE+13;
int wo, 1;
printf("XnXnXnGeben Sie eine Zeichenkette (max. 80 Zeichen) ein ?\n\n“);
lies_zeile(zelchenkette); /• Einlesen einer Zeichenkette in einer •/
/• Zeile In den Vektor zeichenkette •/
printf("XnXnXnGeben Sie den zu suchenden Text (max. 80 Zeichen) ein '\n\n">
11es_zeile(such_kette); /• Einlesen einer Zeichenkette in einer •/
/• Zelle in den Vektor such_kette •/
wo=such-text(Zeichenkette,such_kette);
printf ("XnxnXnDer Textxn Xs\n""Jsuch_kette);
if (wo>=0) (
printf("beginnt in der Zeichenkettexn");
printf(“ XsXn".zeichenkette);
for (1=0 ; l<=wo ; !♦♦) /• öis zur gefundenen Position ♦/
printfi'1 “); /• Leerzeichen ausgeoen •/
printf(" "XnXn"); /• Ausgabe des Markierungszeichens ' •/
eise (
printfCist in der Zeichenkettexn");
printfi" XsXnnicht enthaltenXnXn",zeichenkette);
lles_zeile(string)
char »string;
(
Int 1=0;
do ( /• hier werden die Zeichen einzeln eingelesen •/
stringti3=getchar(); /♦ und zwar in den Vektor string, •/
/• bis 80 Zeichen oder RETURN (= Xn-Zeichen) •/
} while (string[i-i3! ='Xn• && 1<=LAENGE); /• eingegeoen wurde •/
String[i-1]=X0 ;
>
/• Vektorende kennzeichnen (\n uenerschreiben)
such_text(kette,teil)
char ketten, tellC3;
(
int i, j, k;
for (i=o ; kette[13 •= \0' ; (
for (j = i,k=O ; teiltk3 ' = \0 && ketteCj3 = = telltkl ; jw,kf + )
if (telltk] == ’\Q‘)
return(1);
3
return(-1);
3
12-48
Erläuterung:
Mit der Funktion lies—zeile werden alle eingegebenen Zeichen einer Zeile bis RETURN =
\n eingelesen. Diese Funktion entspricht weitgehend der Funktion lies_zeile_ein aus
Beispiel 7; der einzige Unterschied ist, daß bei lies_zeile das \n-Zeichen nicht in die
entsprechende Zeichenkette mitaufgenommen wird, sondern durch das \O-Zeichen er-
setzt wird.
Im Programmteil
printf("XnXnXnßeben Sie eine Zeichenkette (wiax. 80 Zeichen) ein
11es_ze1 le(ze1chenkette); /• Einlesen einer Zeichenkette in einer */
/• Zelle in den Vektor zeichenkette •/
printf("XnXnXnGeben Sie den zu suchenden Text (»ax. 80 Zeichen) ein
lies_zeile(such_kette); /• Einlesen einer Zeichenkette in einer •/
/• Zelle in den Vektor such_kette •/
werden mit dem Aufruf lies—zeile (zeichenkette); alle eingegebenen Zeichen der ak-
tuellen Bildschirmzeile im Vektor zeichenkette gespeichert.
Der Aufruf lies_zeile (such___kette); bewirkt die Speicherung der eingegebenen Zei-
chen aus der aktuellen Bildschirmzeile im Vektor such—kette
Mit der Zuweisung wo = such___text (zeichenkette, such___kette); wird die von der
Funktion such___text gelieferte ganze Zahl an die int-Variable wo übergeben. Die von
such—text ermittelte ganze Zahl gibt entweder die Position der gesuchten Zeichenkette
such—kette in zeichenkette an, oder sie zeigt an, daß such__kette in zeichenkette
nicht enthalten ist (-1).
Im Programmteil
wo=such_text(zeichenkette,such_kette);
printf ('“XnXnXnDer Text\n Xs\n°,such_kette);
if l«o>=0) (
printf("beginnt in der ZeichenketteXn");
printf(• Xs\n",zeichenkette);
for (1=0 ; i<=wo ; 1+*) /• bis zur gefundenen Position •/
printf(“ "1; /• Leerzeichen ausgeben •/
printf("\n\n"); /• Ausgabe des Markierungszeichens ‘ •/
)
eise (
printf("lst in der ZeichenketteXn*);
printf(" XsXnnlcht enthalten\n\n",zeicnenkette);
wird nun bei
wo >=O
der Inhalt von zeichenkette mit einer Markierung * unter demjenigen Zeichen, bei dem
such—kette beginnt, ausgegeben.
wo < 0
die Meldung ausgegeben, daß such—kette nicht in zeichenkette enthalten ist.
12-49
Beispiel 11
Aus einer eingegebenen Zeichenkette sollen alle Vertreter eines angegebenen Zeichens
entfernt werden:
Beispiel für den Bildschirmablauf:
Geben Sie eine Zeichenkette (max. 80 Zeichen) ein!
DAS IST DAS HAUS VOM NIKOLAUS
Geben Sie das zu loeschende Zeichen ein!
A —>
Die neue Zeichenkette ist dann:
DS IST DS HUS VOM NIKOLUS
C-Programm:
«include <stdlo.h>
«define LAENGE 80 /• maximale Laenge der eingegebenen Zeichenkette •/
r.ain ()
<
char zeichenketteCLAENGE*!], zeichloesch;
printf("\n\n\nGeben Sie eine Zeichenkette (max. 80 Zeichen» ein *\n\n“);
lies_zeile(zeichenkette)*; /• Einlesen einer Zeichenkette in einer •/
/• Zeile in den vektor zeichenkette •/
printf("\n\n\nGeben Sie das zu loeschende Zeichen ein »\n\n"i;
zeich_loesch=getchar(); /• Einlesen des zu loeschenden Zeichens •/
/♦ in die Variable zeich_loesch •/
loesch(zeichenkette>zeich_loesch); /• Loeschen aller Zeichen(zeichloesch)•/
/• aus zeichkette “ •/
printf (’'\n\n\n\nDle neue Zeichenkette ist dann :\n\n"i;
printf!" Xs\n*,zeichenkette);
lles_zeile(string)
char •strlng;
(
int 1=0;
do < /•
strlngt1J-getchar(); /•
!♦♦; /•
} while (stringCl-1]•-'\n*
hier werden die Zeichen einzeln eingelesen •/
und zwar in den Vektor strlng, •/
bis 80 Zeichen oder RETURN (» \n-Zeichen) •/
&& 1<=LAEN6E); /• eingegeben wurde •/
stringl1-1J=’\0'; /• Vektorende kennzeichnen (\n ueberschreiben) •/
}
12-50
loesch(kette,zeich)
char »kette, zeich;
(
int 1, j;
for (1=0,j=0 ; ketteln • = \0' ; !♦♦)
if (kettel)] • = zeich)
kettetj+*J=kettel1]; /• entspricht: kettelj]=kettell]; ♦/
/• ]♦♦; •/
kettetjJ='\0‘; /• Ende der neu entstand. Zeichenkette kennzeichnen •/
In der Funktion
loeschikette,zeich)
char »kette, zeich;
(
int 1, J;
for (1=0,j=0 ; kettelt] 1= \0 ; !♦♦)
if (ketteli] •= zeich)
kettet jw] = kettetlJ; /• entspricht: kettetj]=ketteli]; */
/♦ jw; •/
kettetj]=’\0’; /• Ende der neu entstand. Zeichenkette kennzeichnen •/
wird mit der Schleife
for (i = 0, j = O; kette [i] ! = ’ \O ; i + +)
die Zeichenkette kette Zeichen für Zeichen durchlaufen, i ist dabei der Laufindex für die
"alte” kette, die eingegeben wurde, und j für die "neue" kette, in der das zu streichende
Zeichen zeich nicht mehr enthalten ist.
Ein Zeichen wird nur dann in die neue kette übernommen, wenn es sich beim gerade be-
trachteten Zeichen nicht um das zu löschende Zeichen handelt:
if (kette [I]!-zeich)
kette [j++] = kette [i];
Wurde ein Zeichen aus der alten kette in die neue kette übernommen, so ist der Laufin-
dex j um 1 zu inkrementieren: kette □ + +] = ••..
Vor Rückkehr in die aufrufende Funktion ist das Ende der neuen kette zu kennzeichnen:
kette [j] »’\O’;
Wir haben in diesem Absatz vor allen Dingen die Äquivalenz von Zei-
gern und Vektoren hervorgehoben; aber auch hier gilt: "Keine
Regel ohne Ausnahme”:
HINWEIS: kette [++j] = kette [I];
entspricht:
++J; kette [j] = kette (I);
12-51
Man muß zwischen Zeiger-Deklaration und Vektor-Deklaration un-
terscheiden:
char «buchstab; Zeiger-Deklaration
char buchstab [ ]; Vektor-Deklaration
Diese beiden Deklarationen haben unterschiedliche Bedeutung:
Bei der Zeiger-Deklaration char «buchstab; nimmt der Compiler an,
daß buchstab die Adresse eines Zeigers auf ein Zeichen oder einen
Zeichenvektor darstellt:
buchstab
Adr. von
1. char-Element
Adr. von buchstab
1. char-Element
2. char-Element
3. char-Element
Adr. von 1. char-Element
Adr. von 2. char-Element
Adr. von 3. char-Element
(schematische Darstellung, keine
tatsächliche Speicherbelegung)
Bei der Vektor-Deklaration char buchstab [ ]; dagegen nimmt der
Compiler an, daß buchstab die Adresse des Zeichens oder des ersten
Zeichens des Zeichenvektors selbst ist:
buchstab
Adr. von 1. char-Element
Adr. von 2. char-Element
Adr. von 3. char-Element
Bei der Zeiger-Deklaration char «buchstab; muß der Compiler also
zur Adressierung des ersten char-Elements einen zusätzlichen Adres-
sierungsschritt vornehmen.
12-52
12.3 MEHRDIMENSIONALE VEKTOREN
Bisher haben wir nur mit eindimensionalen Vektoren gearbeitet; in der
Praxis benötigt man gelegentlich aber auch mehrdimensionale Vekto-
ren (mehrdimensionale Felder).
In der Mathematik werden zweidimensionale Vektoren auch Matrizen
genannt.
Um uns mehrdimensionale Vektoren besser vorstellen zu können, wol-
len wir wieder ein Beispiel heranziehen: Eine Firma A bestehe aus 3 Ab-
teilungen, wobei sich für die Jahre 1981,1982, 1983 und 1984 folgende
Investitionsgelderverteilung ergibt:
Jahr Abteilungen^"— 1981 1982 1983 1984
Abteilung 1 10 532.- 8 955.- 9 374 - 11 783.-
Abteilung 2 9 743.- 12 377.- 11 539.- 13 893.-
Abteilung 3 3 747.- 5 988 - 10 782- 12 977.-
Soll diese Tabelle innerhalb eines C-Programms in einem zusammen-
hängenden Speicherbereich abgelegt werden, müßte folgende Deklara-
tion angegeben werden:
int Investition [3] [4];
Diese Deklaration würde einen zusammenhängenden Speicherbereich
reservieren, den wir uns wie folgt vorstellen können:
12-53
Wollten wir nun die Investition der Abteilung 1 im Jahr 1984 abspei-
chern, dann würde dies folgender Zuweisung entsprechen:
Investition [0] [3] = 11783;
was folgendes Speicherabbild ergäbe:
0 12 3
11783
An der Zuweisung
Investition [O] [3] = 11783;
(nicht Investition [0,3]= 11783; wie in anderen Sprachen)
können wir erkennen, daß in C ein zweidimensionaler Vektor in Wirk-
lichkeit ein eindimensionaler Vektor
12-54
ist, bei dem jedes Element wieder einen Vektor darstellt:
Die Ordnung der Elemente im Speicher sieht dann wie folgt aus, ohne
unserer zweidimensionalen Vorstellung zu entsprechen:
Wir wollen nun ein C-Programm erstellen, das diese Tabelle speichert
und am Bildschirm ausgibt:
12-55
main ()
<
Int
int
investition!33C43;
i» j;
Investitionen = 10532;
1nvestltion[03!13 - 8955;
investition!03 (23 = 9374;
investitlon!03[33 = 11783;
investitlon!13[03 = 9743;
investitionl13(13 = 12377;
investition(13(23 = 11539;
investition(13(33 ~ 13893;
investitlon[23(03 = 3747;
Investitionen3 = 5988;
investitlon(23(23 = 10782;
investition[23(33 = 12977;
printf(“\n\nxi5sl" “3;
for (1=1981 ; 1<=1984 ; !♦♦) /• Ausgabe der Jahre •/
printf("X7d l",i);
printf(•\n");
strichel's*);
for (1=0 ; l<=2 ; !♦♦) {
printf("Xi3sXd I*,"Abteilung",141);
for (j=0 ; J<=3 ; Jh)
printf ("X7d Iu,investition[13tj31;
printf("\n");
lf (1==2)
strichet‘ ;
eise
strichet ;
str1 ehe tzeich)
char zeich;
<
int i;
for (1=1 ; i<=52 ;
printf("Xc",zeich) ;
printf("\n");
Im Programmteil
investition(03(03 = 10532;
Investitionen 3 - 8955;
investltion(03C23 = 9374;
investition(03[33 = 11783;
investition(13(03 = 9743;
investltion[13(13 = 12377;
Investition!!3123 = 1)539;
Investition!!3(33 = 13893;
investitionC23(03 = 3747;
Investitionen = 5988;
investition[23!23 = 10782;
Investitionen = 12977;
wird der zweidimensionale Vektor Investition mit Werten belegt. Die
Anweisungen:
12-56
printft*\n\nXl5sI " ");
for (1=1981 ; I<=1904 ; >♦♦)
printf("X7d l“,l);
/• Ausgabe der Jahre ♦/
printf;
bewirken die Ausgabe der Zeile:
uu ...uluuul981uluuu1982 uluuu1983 uluuul984 u I
15 Leerzeichen
Mit dem Aufruf der Funktion
striche (”=”>;
wird folgende Zeile am Bildschirm ausgegeben:
52 Gleichheitszeichen
Mit dem Programmteil
for (1=0 ; 1< = 2 ; (
printf("XI3sXd I ","Abteilung",1*1 );
for (j=0 ; j<=3 ;
printf ("X7d I", Investitionen jD ;
printf("\n");
If (i==2)
strichet’s*i;
eise
strichet ;
wird dann die eigentliche Tabelle am Bildschirm ausgegeben. Mit der
äußeren Schleife werden die Zeilen von Investition durchlaufen (Lauf-
index I); die innere Schleife (Laufindex j) sorgt dann dafür, daß zu jeder
Zeile alle Spalten (eigentliche Werte) durchlaufen werden.
Somit wird auf die einzelnen Elemente von Investition in folgender
Reihenfolge zugegriffen:
0 1 2 3
10 532.-” r 8 955.-’ 9 374.-” _1_1_783£)
< J 9 743.- 12 377.- 11 539.- 13 893.-))
k
3 747 - 5 988- 10 782.- 12 977.-
12-57
Beispiel 12
Um eine Nachricht zu verschlüsseln, kann man sie in einen mehrdimensionalen Vektor zei-
lenweise eintragen und spaltenweise wieder auslesen; z. B. die Nachricht
EINEu KATZE u JAGTu DIE u MAUS
zeilenweise eintragen:
und spaltenweise auslesen (verschlüsselte Nachricht):
EAAEITGu NZTMEEu Auu DUKJIS
Es soll nun ein geeignetes Programm für diese sogenannte hebräische Methode entwickelt
werden.
Zuerst muß der Benutzer seinen zu verschlüsselnden Satz eingeben. Nach dieser Eingabe
wird der Benutzer nach der Zeilenzahl und Spaltenzahl für die Eintragung des zu ver-
schlüsselnden Satzes gefragt.
Bevor die Eintragung der eingegebenen Nachricht in das Verschlüsselungs-Rechteck er-
folgt, muß dieses mit Leerzeichen vorbesetzt werden.
Wenn dann beispielsweise die Nachricht
ICHu LERNE UC
zu verschlüsseln ist und folgende Zeilen- und Spaltenzahl
Zeilen: 3
Spalten: 5
vom Benutzer angegeben wurde, so ergibt sich aus
I C H u L
E R N E u
C u u u u
die verschlüsselte Nachricht:
lECCRu HNuu Eli Luu
12-58
C-Programm:
ainclude <stdio.h>
«define LAENGE 80
am ()
<
cnar rechtecklLAENGEHLAENGEl;
char zeich_kettetLAENGE+i J, ♦zeiger;
int lang,“ 1, J, zellen, spalten;
printf("XnGeben Sie den zu verschluesselnden Satz ein ’Xn“);
----lies_zeile(zelchkette);
langxstrlen(zelch_kette); /• In lang wird die Laenge der eingegebenen •/
/• zelchkette gespeichert •/
......• ........................••••••••••.........••••••••/
/• Eingabe der Zeilen-und Spaltenzahl fuer das Ver- •/
/♦ schluessel.-Rechteck; zusaetzlich wird ueberprueft, •/
/• ob fuer zeich_kette genuegend Platz 1» verschlues •/
/• sei.-Rechteck."wenn nicht, dann nuss eine neue ♦/
/• Zeilen-und Spaltenzahl eingegeben werden. •/
do <
printf("\n\n\nwieviel Zellen soll verschluessel.-Rechteck besitzen’Xn“);
scanf("Xd“,izeilen);
printf("XnXnWieviel Spalten soll Verschluessel.-Rechteck besitzen’Xn");
scanf("Xd",8spalten);
lf (zeilen*spalten < lang) (
printf("XnXnZu wenig Platz 1« verschluessel.-Rechteck »'XnXnXn*);
printf("XnXnUiederholen Sie Ihre Eingabe IXnXnXn");
)
} while (zeilen»spalten < lang);
/.....................................................♦••••♦/
/• Vorbesetzen d. Verschluessel.-Rechtecks alt Leerzeichen*/
/•••«••••«•••••••••••••....•••••»•••••••••••••••••••••••••/
for (1=0 ; i<zellen ; !♦♦)
for (j = 0 ; jcspalten ; ]>♦)
rechtecktiltj]=’ ';
...........................................................
Eintrag, von zeich kette in das verschluessel.-Rechteck«/
/• (zeilenweise) •/
/••••••••....................•................................
for (zelger=zeich_kette,1=0 ; Kzeilen && »zeiger?« X0' ; !♦♦)
for (j = 0 ; j<spalten && »zeiger! =X0‘ ; j*f>)
rechteckE1][j]=*zeiger++;
.............................................................
/• Auslesen von zelchkette aus Verschluessel.-Rechteck •/
/• (spaltenweise) ♦/
/•••••••••••»••••••••......♦•«•................•.....••••••/
printf("XnXnXnXnDle verschluesselte Nachricht ist:\nXn •);
for (]«0 ; 1<spalten ; jwj
for (1=0 ; i<zellen ; )♦♦)
printf("Xc* , rechtecküiHj1);
printfCXn");
lles_zeile(string)
char »strlng;
(
int 1=0;
while ((stringt l+4-] = getchar ()) » = #Xn && 1<=LAENGE)
stringl1-11='XO’;
12-59
Erläuterung:
Mit
lang=strlen(zeich kette); /• !n lang wird die Laenge der eingegeoenen •/
/• zeich_kette gespeichert •/
wird die Bibliotheksfunktion strlen aufgerufen, welche ja bekanntlich die Anzahl der Ze
chen der übergebenen Zeichenkette, hier von zeich— kette, ermittelt.
Im Programmteil
............................................................♦/
/• voroesetzen d. Verschluessel.-Rechtecks mit Leerzeichen*/
for (i=0 ; Kzeilen ; ihi
for (j=0 ; jcspalten ; j*+)
rechteckl1 ifjl = ‘ *;
wird der vom Benutzer durch zeilen und spalten begrenzte Bereich von rechteck mit
Leerzeichen vorbesetzt.
Im Programmteil
/• Eintrag, von zeichkette in das Verschluessel.-Rechteck«/
/• (zeilenweise) • /
.............................................................
for (zeiger=zeich_kette,1=0 ; Kzeilen && »zeiger's'xo ; !♦♦)
for (j=0 ; j<spalten && »zeigen\Q' ; j++)
rechtecktiHj1=»zeiger++;
werden die Zeichen aus zeich______kette zeilenweise in den zweidimensionalen Vektor
rechteck eingetragen; zeich_kette wird hierbei mit Hilfe einer Zeigervariablen (zeiger
durchlaufen.
Im Programmteil
..............................................................
/• Auslesen von zeichkette aus verschluessel.-Rechteck ♦ /
/• (spaltenweise)
printf("\n\n\n\nDie verschluesselte Nachricht ist:\n\n
for (j = D ; j<spalten ; j++)
for (1=0 ; Kzeilen ; !♦♦)
printf(“Xc,‘, recht ecktiltjl);
pr intf(“\n“);
wird der durch zeilen und spalten begrenzte Bereich von rechteck spaltenweise am
Bildschirm ausgegeben.
Bei QuickC und MSC kann es durch die Feldvereinbarung char rechteck [LAENGE]
[LAENGE] in der main-Funktion zum Laufzeitfehler 'stack overflow' kommen. Abhilfen
sind: modulglobale Feldvereinbarungen oder durch Optionsangabe den Stack beim Linken
vergrößern.
Für die Funktion lies_zeile wurde hier eine Version angegeben, die aber das gleiche lei-
stet wie die Version aus Beispiel 11 (Xn wird mit \O überschrieben):
12-60
Ues.zeiie (String)
char •string;
int i=0;
while ((string11♦♦]=getchar())'= \n && i<=LAENGE)
J
stringCi-1]='\0';
Tritt ein mehrdimensionaler Vektor als formaler Parameter einer Funk-
tion auf, so kann die erste Dimensionierung - wie bei eindimensionalen
Vektoren - offengelassen werden:
int vekt [] [10];
char kreuz___wort [ ] [45];
Die zweite und alle weiteren Dimensionen müssen jedoch angegeben
werden.
Beispiel 13
Es soll ein C-Programm erstellt werden, das 2 zweidimensionale Vektoren (2 Matrizen)
durch Benutzereingaben mit Werten belegt und dann die Summe dieser beiden eingegebe-
nen Matrizen wieder ausgibt.
Eine Summe aus zwei Matrizen kann nur dann gebildet werden, wenn beide Matrizen glei-
che Zeilen- und Spaltenzahl besitzen.
Bei der Summenbildung werden die einzelnen Elemente, die in beiden Matrizen an der glei-
chen Position stehen, addiert:
Matrix 1 + Matrix 2 = Ergebnismatrix
Die Matrizen in unserem Programm dürfen maximal 10 Zeilen und 10 Spalten besitzen.
HINWEIS: Bezüglich der Anzahl von Dimensionen ist keine Einschränkung vorgegeben.
12-61
C-Programm:
main ()
<
int matl (10H10J , matzt 1 OHIO], summatllOJtltJ];
int 1, j, zellen, spalten;
printf("\n\nAddltion zweier Matrizen*);
printf("\n====================*===\n\n\n");
/.............................................
/♦ Eingabe der Zellen- und Spaltenzahl •/
/••••••••••••••••••••••••••••••••••••••••••••••••••••«•/
lies(SzeHen, "Zeilen");
lies(Sspalten,"Spalten");
/• Eingabe der Werte fuer matl und aat2 •/
printf("\n\nEingabe der werte fuer Matrix 1:\n\n");
eingabetmatl,zellen,spalten);
printf("\n\nElngabe der Werte fuer Matrix Z:\n\n“);
eingabe(matz,zellen,spalten);
/• Addition von matl und mat2 •/
for (i«0 ; Kzeilen ; !♦♦)
for (j=0 ; j<spalten ; j*+)
sum_mat[1Hj] = mat! [ 1 ] [ j HmatZC 1 H j ];
/•••♦••••♦•••...........»•••«••••••••••••••••••••••••••/
/• Ausgabe der Ergebnis-Matrix •/
/•••♦........................
printf("\n\nDie Summe der beiden eingegebenen Matrizen ist:\n\n*)
for (1-0 ; Kzeilen ; :♦♦) (
for (j = 0 ; jopalten ; j++)
printf("X6d“,sum mattlH jl);
printf("\n");
>
lies(anzahl,was)
int •anzahl;
char «was;
{
do <
printf("\n\nWleviele Xs fuer Ihre Matrizen ’\n*,was);
scanf ("Xd1*,anzahl);
if (*anzahl<1 II *anzahl>10) (
printf("\n\n\nFalsche Eingabe»\n\n");
printf("Wiederholen Sie Ihre Eingabe !\n\n");
} while (•anzahl<1 II «anzahl>lO);
eingabe(matrjx,zell,spal)
int matrixCHIOJ;
int zell, spal;
<
int i, j;
for (i=o ; Kzeil ; !♦♦) {
for (j=o ; j<spal ; j*+) {
printf("Element Xd,Xd ?",i,j); (+)
scanf ("Xd",Sma tri xCUCJ]) ;
printf("\n");
}
(♦) Unter MS-C und LATTICE-C muß der Text in printf mit \ n
abgeschlossen werden, um die Textdarstellung auszulösen.
Ersetzen Sie daher ? durch ? \ n".
12-62
Erläuterungen:
Im Programmteil
Eingabe der Zeilen- und Spaltenzahl •/
.......................................................
lies(izeilen,"Zeilen“);
lies(^spalten,"Spalten");
wird die Zeilen- und Spaltenzahl der beiden zu addierenden Matrizen eingelesen; dazu
wird die Funktion lies aufgerufen, die in die Variable zeilen bzw spalten einen Wert aus
dem Intervall [1,10] (ganzzahlig) einliest:
lies(anzahl,was)
int »anzahl;
char «was;
do <
printf("\n\nWieviele Xs fuer Ihre Matrizen ’\n",was)
scanf("Xd",anzahl);
if (»anzanKl li *anzahl>l0) C
printf("\n\n\nFalsche Eingabe'\n\n");
printf("Wiederholen Sie Ihre Eingabe ’\n\n");
} while (*anzahl<l ll *anzahl>lO);
Im Programmteil
/• Eingabe der Werte fuer ®atl und mat2 •/
printf("\n\nEingabe der Werte fuer Matrix 1;\n\n">;
eingabe(matl,zellen,spalten);
printf("\n\nElngabe der werte fuer Matrix 2:\n\n"l;
eingabe(«at2,zeilen,spalten);
werden die einzelnen Elemente der Matrizen mat1 und mat2 eingelesen; dazu wird die
Funktion eingabe aufgerufen. Wird diese Funktion aufgerufen, so ist neben der vorgege-
benen Zeilen- und Spaltenzahl noch die Anfangsadresse der entsprechenden Matrix
(mat1 oder mat2) als aktueller Parameter zu übergeben.
In eingabe wird der zugehörige formale Parameter matrix als zweidimensionaler Vektor
deklariert, wobei von der Möglichkeit Gebrauch gemacht wird, auf die Angabe der ersten
Dimension zu verzichten:
eingabe(matrlx,zell,spal)
int matr1x[j[101;
Int zell, spal;
<
tnt 1, j;
for (1=0 ; l<zeil ; 1») <
for (j=0 ; j<spal ; j**) (
printf("Element Zd,Zd (+)
scanf("Xd",&®atrix[1Hjl);
)
printf cxn");
>
Unter MS-C und LATTICE-C muß
der Text in printf mit \ n abge-
schlossen werden, um die Textdar-
stellung auszulosen Ersetzen Sie
daher ?" durch ? \ n”.
12-63
Im Programmteil
.........................................................
/• Addition von mati und mat2 •/
for (1*0 ; i<zeilen ; i++)
for (j=0 ; j«spalten ;
summatCi][j] = natl C t JC j J+«iatZt i Jt j 3;
werden die beiden Matrizen mati und mat2 addiert. Das Ergebnis wird in der Matrix
sunumat festgehalten.
Mit der Anweisungsfolge
/• Ausgabe der ErgehnIs-Katrix •/
..........................................................
printf("\n\nDie Summe der beiden eingegebenen Matrizen ist:\n\n");
for (1*0 ; («zellen ; {
for (J=0 ; j«spalten ; j++)
printf("X6d",sum matCiICj 11;
printf("\n");
wird die Ergebnismatrix sum_mat entsprechend der mathematischen Darstellung zeilen-
weise am Bildschirm ausgegeben.
12.4 INITIALISIERUNGEN
Des öfteren haben wir schon von der Möglichkeit Gebrauch gemacht,
eine Variable schon bei der Deklaration mit einem Initialwert zu verse-
hen; dies ist ein Wert, den die Variable bereits beim Laden des Pro-
gramms erhält, d. h. es wird kein Programmcode benötigt, um diese Va-
riable mit einem Wert vorzubesetzen.
Die Initialisierung einer einfachen Variablen wird bei der Deklaration
vorgenommen, indem an den Variabiennamen ein Gleichheitszeichen
und der gewünschte Initialwert angehängt wird.
Hier einige Beispiele:
int i = 0;
double pi = 3.14159265, e = 2.7182818;
char Zeichen = ’N*;
int buchstaben_zahl = ’Z’ - ’A’;
char «falsch = "Falsche Eingabe’’;
Die Initialisierung wirkt sich vor allen Dingen bei ein- und mehrdimen-
sionalen Vektoren sehr vorteilhaft aus; hierbei muß die Werteliste in ge-
schweiften Klammern eingebettet sein:
12-64
(1) int investition [3] [4] ={
10532, 8955, 9374,11783, 9743, 12377,
11539, 13893, 3747, 5988, 10782, 12977};
(2) int investition [3] [4] = {
10532, 8955, 9374, 11783
9743, 12377, 11539, 13893},
3747, 5988, 10782, 12977}
(3) char vokale [5] = {’ A’, ’E’, T, ’O’, ’U’};
(4) char falsch [ ] ={
’F’, ’A’, ’L’, ’S’, ’C’, ’H’, ’E’, ’u’, ’E’, T, ’N’, ’G’, ’A’, ’B’, ’E’, ’\0’
Bei den ersten beiden Deklarationen (1) und (2) werden die zwei Me-
thoden gezeigt, mit denen mehrdimensionale Felder initialisiert werden
können: Bei (1) werden die einzelnen Werte in der Reihenfolge genannt,
in der sie im Speicher abgelegt werden. Bei (2) werden durch das Set-
zen von geschweiften Klammern die verschiedenen Ebenen des defi-
nierten Objekts nachgebildet.
Die Deklaration (4) entspricht der letzten Zeile aus den vorhergehenden
Beispielen. Wird die Länge eines Vektors nicht angegeben, so berech-
net der Compiler diese Länge, indem er die Initialisierungen zählt. In (4)
ist die Vektorlänge also 16 (15 relevante Zeichen und das abschließen-
de \O-Zeichen).
Externe und static-Variablen sind auch ohne explizite Angabe von Ini-
tialwerten mit Wert 0 vorbesetzt, auto- und register-Variablen sind da-
gegen ohne Initialisierung Undefiniert.
auto- und register-Variablen werden bei Eintritt in die Funktion bzw. in
den Block jedesmal neu initialisiert.
Beispiel
bspl_funk (...)
{”
int i = 0;
} "
Bei jedem Aufruf von bspl___funk wird die lokale Variable i auf 0 gesetzt.
12-65
Bei auto und register-Variablen muß der Initialwert keine Konsta"
sein, sondern er kann auch durch einen beliebigen Ausdruck, der sicr
auf vorher definierte Werte bezieht, dargestellt werden.
Beispiel 14
Wir wollen eine int-Variable. die ja im Rechner dual dargestellt wird, durch ein C-Pro-
gramm in den sogenannten BCD*)-Code umwandeln lassen. Ein Beispiel:
Eingabegröße: 345 = 0000 0000 0001 0011 0101 0100 1001 0101 dual BCD-Code
3 4 5
Im BCD-Code wird jede Ziffer einzeln codiert und in 4 Bits gespeichert.
Unser Programm soll nur positive ganze Zahlen aus dem ASCII-Code in den BCD-Code
umwandeln.
C-Programm:
main()
(
int 1, binaer, bcd_zahl;
do (
sie elne P°51tlve ganze Zahl (0 BIS 9999) ein »\n*>;
scanr(Xd.&oinaer);
IF (blnaer<0 11 öinaer>9999) <
Zahlen aus Intervall 0 bis 9999 erlaubt '\n*>;
printft Xnwiederbolen Sie Ihre Eingabe '\n\n*>;
) while (blnaer<0 II blnaer>9999);
printf("\n\nDualdarstellunq von Xd
for (1»15 ; i> = 0 ; 1 — )
printf(“Xd",(binaer>>i) & 1);
printf(*\n\nBCD-Darstellung von Xd
bcd_zahl = bcd_umwandel(binaer) ;
for (1«1S ; 1> = O ; 1 —)
printf("Xd",(bcd_zahl>>i) & 1);
:\n",binaer);
:\n“,binaer);
bcd umwandel(dual)
int dual:
(
int bcd_code - dualXlO;
lf (dual/=lO) (+)
return(bcd_code+(bcd umwandel(dual)<<<))•
eise “ ’
return(bcd_code);
(+) Die Warnung von Turbo C kann mit if ((dual U 10) !=O) beseitigt
HINWEIS: *) BCD = Binary Coded Decimal
12-66
Erläuterungen:
Anstelle von int bcd__code = dual % 10; in bcd______umwandel hätten wir auch
int bcd___code;
bcd____code = dual % 10;
angeben können.
Wir sehen, daß wir mit der Initialisierung eine Deklaration und eine Zuweisung zusammen-
fassen können.
Bei dieser Initialisierung wird zur Berechnung des Initialwertes auf eine schon zuvor defi-
nierte Variable (dual) zugegriffen.
Die Arbeitsweise der Funktion bcd___umwände! soll an einem Beispiel
aufgezeigt werden:
Wenn z. B. die Zahl 394 binaer eingelesen wurde, um sie in den
BCD-Code umwandeln zu lassen, dann wird mit der Anweisung
bcd___zahl = bcd___umwandel (binaer); die Funktion bcd_umwan-
del mit Wert 394 aufgerufen. Wir wollen den Ablauf dieses Programm-
teils mit dem Wert 394 nachvollziehen:
12-67
bcd_JEahl = bed _um wandel (394);
’BCOI
001110010100
int bed—code - 394 % 10; (U)
(da 394/10 = 39. also ungleich 0)
return (4 +(bed—umwandel(39) < < 4));
0100=4
1110010000= 57< <4
1110010100 - 916
int bcd code - 39 % 10; (a 9)
(da 39/10 = 3, also ungleich 0)
return (9 ♦ (bcd_umwandel (3) < < 4));
int bcd_code - 3 % 10; (a 3)
(da 3/10 = 0)
return (3);
111001
/ = 57
1001 =9
110000 = 3<<4
111001 =57
auto-Vektoren können bei der Deklaration nicht initialisiert werden.
Externe und static-Vektoren können initialisiert werden; die Initialisie-
rung solcher Vektoren erfolgt durch Angabe der durch Komma getrenn-
ten Initialwerte, die in Klammern eingeschlossen werden.
Werden weniger Werte als verfügbare Vektorelemente angegeben, so
werden die restlichen Elemente mit 0 vorbesetzt:
int vektor [80] = {O};
Oft wird eine Initialisierung bei externen Variablen vorgenommen, ob-
wohl dies nicht zwingend notwendig ist. Hierfür gibt es zwei Gründe:
• Es soll eine Deklaration als eine extern-Deklaration und nicht als eine
extern-Referenz gekennzeichnet werden.
12-68
• Es entspricht gutem Programmierstil, implizit vorbesetzte Variable trotz-
dem explizit zu initialisieren.
Beispiel 15
Es ist ein C-Programm zu entwerfen, das ein magisches Quadrat am Bildschirm ausgibt.
Ein magisches Quadrat liegt dann vor, wenn alle Summen über die einzelnen Zeilen, über
die einzelnen Spalten und über die beiden Diagonalen das gleiche Resultat ergeben.
Das Verfahren, irgendein Quadrat mit einer ungeraden Anzahl von Zeilen/Spalten zu kon-
struieren, wurde durch De La Loubere, einem Mathematiker des 17. Jahrhunderts, entwik-
kelt.
Im Folgenden wird dieses Verfahren gezeigt.
Die Zahl 1 wird in das mittlere Feld der obersten Zeile eingetragen:
eingetragen. Ist dies nicht möglich, weil dort schon eine Zahl eingetragen wurde oder man
sich außerhalb des Quadrates befindet, dann ist eine der nachfolgenden Regeln anzuwen-
den:
Regel 1:
Verläßt man das Rechteck oben, so ist die Zahl am entgegengesetzten Ende der Spalte
einzutragen:
12-69
Regel 2:
Verläßt man das Rechteck auf der rechten Seite, so ist die Zahl in das Feld am entge-
gengesetzten Ende der Zeile einzutragen:
Regel 3:
Trifft man auf eine Zahl ungleich Null, dann ist die einzutragende Zahl unter der gerade ge-
schnebenen Zahl anzugeben:
1
3 1
t 4 2
Regel 4:
Verläßt man im oberen rechten Eck das Quadrat, dann ist die Zahl - wie bei Regel 3 - un-
ter der zuletzt geschriebenen Zahl einzutragen:
12-70
Als magisches Quadrat ergibt sich dann für den Fall (3 Zeilen und 3 Spalten):
Der Anfang ist mit (J) gekennzeichnet und das Ende bei 25 mit x.
C-Programm:
Wir benötigen hier ein zweidimensionales Feld, das wir mag quadrat nennen wollen.
Dieses Feld wird mit
int «iag_quadrat C30H30D s (0)
extern deklarieren, um diesen Vektor
initialisieren zu koennen.
Obwohl nicht alle Elemente angegeben
wurden, ist der gesamte zweidimen.
Vektor mit 0 vorbesetzt.
extern deklariert, um es mit Null initialisieren zu können. An dieser Deklaration können wir
erkennen, daß unser Programm maximal ein magisches Quadrat mit 29 Zeilen und 29
Spalten aufstellen kann.
12-71
Diese maximale Zeilen- und Spaltenzahl muß bei Eingabe der Dimension für das zu erstel-
lende magische Quadrat ebenso überprüft werden wie die Vorgabe, daß die Dimension ei-
ner ungeraden Zahl entsprechen muß:
/......................................................
/• Eingabe und Ueberpruefung ber Dimension
do <
printf("\nwieviele Zeilen/Spalten (ungerade positive ganze Zahl < 30) ’\n");
scanf(“Xd",&dimens Ion);
if (dimensionXZ -- 0 ll dimension > = 30 II dimension <- 0) (
printf(“\n\nFalsche Eingabe''\n\n");
printf("\nEingabe wiederholen '\n\n");
>
1 while (dimensionXZ == □ II dimension >=30 II dimension <- 0);
Das vollständige C-Programm zu dieser Aufgabenstellung wäre dann:
int
mag_quadratC301t30] = €03;
/• extern deklarieren, um diesen Vektor •/
/• initialisieren zu koennen. •/
/• Obwohl nicht alle Elemente angegeben ♦ /
/• wurden, ist der gesamte zweidimen. • /
/• Vektor mit 0 vorbesetzt. ♦ /
main ()
int 1, J, zelle, spalte, dimension;
/.........................
/♦ Eingabe und Ueberpruefung der Dimension • /
.................................................................
do (
printf(“\nWieviele Zeilen/Spalten (ungerade positive ganze Zahl < 30) ?\n");
scanf("Xd",&dimension);
if (dimensionXZ = = 0 II dimension >=30 II dimension <= □) (
printf(“\n\nFalsehe Eingabe''\n\n“);
printf("XnEingabe wiederholen !\n\n");
)
1 while (dimensionXZ == 0 II dimension >=30 ll dimension <= 0);
/• Belegen des magischen Quadrats mit Werten •/
/♦♦♦•♦................................♦..................♦/
zelle = 1;
spalte = (dimension+1)/2;
for (1=1 ; ; >♦♦) (
mag_quadrattzeileJCspalte] = i;
if Fi == dimension*dimenslon) /• wenn letzte Zahl eingetragen, • /
break; /• dann Schleife verlassen •/
if (zeile-1 = = 0 && spalte ’ dimension) < /• Regel 1 •/
zeile = dimension;
spalte**;
continue;
if (spalte «• dimension && zelle-1 ! 0) < /* Regel 2 •/
zeile--;
spalte s 1;
continue;
if ((mag quadrat(zeile-1Hspalte+1) != 0) II /• Regel 3 •
(spalte =s dimension && zeile-1 «« 0)) (/• Regel 4 •/
zeile**;
continue;
1
zeile—;
spalte**;
1
normale Vorgehensweise:
diagonal nach rechts oben
12-72
/• Ausgabe des magischen Quadrats •/
/««•••••••••••........................................• ••/
for (1=1 ; i<=dimension ; 1**) {
strlene(4*dimension*l);
for (j=1 ; j<=dimension ; j**)
printf("lX3d“,mag quadratU H j]);
printf<"l\n"1;
striche(4»dimenslon+1);
)
striche(zahl)
int zahl;
(
int 1;
for (1=0 ; Kzahl ; !♦♦>
printf("-);
printf("\n");
Mit den beiden Anweisungen
zeile x 1;
spalte = (dlmension+1)Z2;
wird die mittlere Position der obersten Zeile des magischen Quadrats festgelegt.
Beim Programmabschnitt
for (1=1 ; ; 1**) C
nag_quadrattzeile]tspalte! = i;
lf "(1 == dlmension*d1 mens ton) /• Wenn letzte Zahl eingetragen, •/
break; /• dann Schleife verlassen ♦/
lf (zeile-1 = = 0 && spalte ! = dlmension) ( /♦ Regel 1 ♦/
zeile = dlmension;
spalte**;
continue;
if (spalte xx dlmension && zeile-1 •« 0) C /• Regel 2 •/
zeile—;
spalte > 1;
continue;
lf ((mag_quadrat[zeile-1ltspalte+1J '= D) II/* Regel 3 ♦/
(spalte x* dlmension && zeile-1 = = 0)) (/• Regel 4 ♦/
zelle**;
continue;
zeile—; /• normale vorgehenswelse: ♦/
spalte**; /• diagonal nach rechts oben •/
handelt es sich um eine Endlosschleife, die mit break verlassen wird, sobald die letzte
Zahl eingetragen wurde (i = = dimension ♦ dimensionr
Wurde noch nicht die letzte Zahl eingetragen,so werden mit den nachfolgenden if-Blöcken
zunächst die einzelnen Regeln überprüft. Kann eine Regel angewendet werden, dann wird
mit continue sofort zum Schleifenende verzweigt und somit ein neuer Schleifendurchlauf
gestartet.
Konnte keine der 4 Regeln auf eine einzutragende Zahl angewendet werden, dann ist die-
se Zahl rechts oben (zeile spalte ++;) einzutragen.
12-73
Im Programmteil
/• Ausgabe des magischen Quadrats
/«•••«••••••»»••••<«••••••••............
for Ci=1 ; i<=dlaension ; >♦♦) {
stri ehe(4*01mension + 1);
for <J=1 ; j<=dimension ; J + *)
printf(- lX3d" ,mag quadratClH j ]);
printf("IXn“);
striehe(4«dimension*l);
wird der Inhalt des zweidimensionalen Vektors mag_quadrat am Bildschirm in Tabellen
form ausgegeben.
Beispiel 16
In Terminkalendern findet sich häufig folgender Dauerkalender:
Dauerkalender von 1901 bis >2050
Jahre Monate
1901 - 200C 1 2001 -205C I J F M A M J J A S 0 N D
25 53 81 09 37 4 0 0 3 5 1 3 6 2 4 0 2
26 54 82 10 38 5 1 1 4 6 2 4 0 3 5 1 3
27 55 83 11 39 6 2 2 5 0 3 5 1 4 6 2 4
28 56 84 12 40 0 3 4 0 2 5 0 3 6 1 4 6
01 29 57 85 13 41 2 5 5 1 3 6 1 4 0 2 5 0
02 30 58 86 14 42 3 6 6 2 4 0 2 5 1 3 6 1
03 31 59 87 15 43 4 0 0 3 5 1 3 6 2 4 0 2
04 32 60 88 16 44 5 1 2 5 0 3 5 1 4 6 2 4
OS 33 61 89 17 45 0 3 3 6 1 4 6 2 5 0 3 5
06 34 62 90 18 46 1 4 4 0 2 5 0 3 6 1 4 6
07 35 63 91 19 47 2 5 5 1 3 6 1 4 0 2 5 0
08 36 64 92 20 48 3 6 0 3 5 1 3 6 2 4 0 2
09 37 65 93 21 49 5 1 1 4 6 2 4 0 3 5 1 3
10 38 66 94 22 50 6 2 2 5 0 3 5 1 4 6 2 4
11 39 67 95 23 0 3 3 6 1 4 6 2 5 0 3 5
12 40 68 96 24 1 4 5 1 3 6 1 4 0 2 5 0
13 41 69 97 25 3 6 6 2 4 0 2 5 1 3 6 1
14 42 70 98 26 4 0 0 3 5 1 3 6 2 4 0 2
15 43 71 99 27 5 1 1 4 6 2 4 0 3 5 1 3
16 44 72 00 28 6 2 3 6 1 4 6 2 5 0 3 5
17 45 73 01 29 1 4 4 0 2 5 0 3 6 1 4 6
18 46 74 02 30 2 5 5 1 3 6 1 4 0 2 5 0
19 47 75 03 31 3 6 6 2 4 0 2 5 1 3 6 1
20 48 76 04 32 4 0 1 4 6 2 4 0 3 5 1 3
21 49 77 05 33 6 2 2 5 0 3 5 1 4 6 2 4
22 50 78 06 34 0 3 3 6 1 4 6 2 5 0 3 5
23 51 79 07 35 1 4 4 0 2 5 0 3 6 1 4 6
24 52 80 08 36 2 5 6 2 4 0 2 5 1 3 6 1
Wochentage Anwendung:
M D 2 3 9 10 16 23 17 24 30 31 37 Beispiel Auf welchen Wochentag fiel der 25. Juli 1954?
M 4 11 18 25 32 Lösung: Man gehe von der Jahrestafel aus und suche für das Jahr 1954 in
D 5 12 19 26 33 der Monatstafel unter Juli die zugehörige Monatskennzahl (4); zuzüglich der
F 6 13 20 27 34 Zahl des gesuchten Wochentages (25) ergibt sich die Schlüsselzahl (4 ♦ 25
s 7 14 21 28 35 « 29), für die man m der Wochentagstafel den Sonntag als den gesuchten
S 1 8 15 22 29 36 Wochentag findet.
12-74
Diesen Dauerkalender wollen wir nun als C-Programm angeben; dabei bietet es sich an,
die entsprechenden Tabellen schon bei der Deklaration zu initialisieren. Dazu ist es natür-
lich erforderlich, die entsprechenden zweidimensionalen Vektoren extern zu deklarieren:
int jahre<28H61 = (0); /• Jahrtabelle mit Nullen initialisieren •/
int monateC281 Ci 2) = (
<4,0,0,3,5,1,3,6,2,4,0,21, /• Initialisieren der Monatstabelle •/
<5,1,1,4,6,2,4,0,3,5,1,3), /• entsprechend der Vorgabe aus de» ♦/
<6,2,2,5,0,3,5,1,4,6,2,41, /• DauerKalender •/
<0,3,4,0,2,5,0,3,6,1,4,61,
<2,5,5,!,3,6,1,4,0,2,5,0),
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,0,3,5,1,3,6,2,4,0,21,
<5,1,2,5,0,3,5,1,4,6,2,41,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1,4,4,0,2,5,0,3,6,1,4,61,
(2,5,5,1,3,6,1,4,0,2,5,01,
<3,6,0,3,5,1,3,6,2,4,0,21,
<5,1,1,4,6,2,4,0,3,5,1 ,31,
<6,2,2,5,0,3,5,1,4.6,2,41 ,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1 ,4,5,1,3,6,1,4,0,2,5,01,
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,0,3,5,1,3,6,2,4,0,21 ,
<5,1,1,4,6,2,4,0,3,5,1,31,
<6,2,3,6,1,4,6,2,5,0,3,51,
<1 ,4,4,0,2,5,0,3,6,1,4,61,
<2,5,5,1,3,6,1,4,0,2,5,01,
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,1,4,6,2,4,0,3,5,1,31,
<6,2,2,5,0,3,5,1,4,6,2,41,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1,4,4,0,2,5,0,3,6,1,4,61,
<2,5,6,2,4,0,2,5,1,3,6,11
>;
int wachen tage(7H71 * <01; /• Wochentag-Tabelle mit 0 initialisieren • /
char tagC7][ll] = ( /• zweidim. Vektor tag mit erster •/
"Montag", /• Dimension 7 (wegen 7 Wochentage) und •/
"Dienstag", /♦ zweiter Dimension 11 (well laengster •/
"Mittwoch", /• Wochentag Donnerstag 10 Zeichen ♦ /
"Donnerstag", /• lang ♦ 1 Zeichen fuer das abschlies ♦/
"Freitag", /♦ sende \0-Zeichen) mit den Wochentagen»/
“Samstag", /• vorbesetzen •/
•Sonntag“
12-75
C-Programm:
int jahre[283[63 = <□>; /• Jahrtabelle mit Nullen initialisieren •/
int monateC283(123 = <
<4,0,0,3,5,1,3,6,2,4,0,23, /♦ Initialisieren der Monatstabelle •/
<5,1,1,4,6,2,4,0,3,5,1,33, /• entsprechend der Vorgabe aus dem •/
<6,2,2,5,0,3,5,1,4,6,2,43, /• Dauerkalender •/
<0,3,4,0,2,5,0,3,6,1,4,61,
12,5,5,1,3,6,1,4,0,2,5,01,
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,0,3,5,1,3,6,2,4,0,21,
<5,1,2,5,0,3,5,1,4,6,2,41,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1,4,4,0,2,5,0,3,6,1,4,61,
<2,5,5,1,3,6,1,4,0,2,5,01,
<3,6,0,3,5,1,3,6,2,4,0,21,
<5,1,1,4,6,2,4,0,3,5,1,31,
<6,2,2,5,0,3,5,1,4,6,2,41,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1,4,5,1,3,6,1,4,0,2,5,01 ,
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,0,3,5,1,3,6,2,4,0,21,
<5,1,1,4,6,2,4,0,3,5,1,31,
<6,2,3,6,1,4,6,2,5,0,3,51,
<1,4,4,0,2,5,0,3,6,1,4,61,
<2,5,5,1,3,6,1,4,0,2,5,01,
<3,6,6,2,4,0,2,5,1,3,6,11,
<4,0,1,4,6,2,4,0,3,5,1,31,
<6,2,2,5,0,3,5,1,4,6,2,41,
<0,3,3,6,1,4,6,2,5,0,3,51,
<1,4,4,0,2,5,0,3,6,1,4,6),
<2,5,6,2,4,0,2,5,1,3,6,11
Int wochen_tage<7][73 = <01; /• Wochentag-Tabelle mit 0 imtialisieren • /
char tag<73<1l3 = < /• zweldlm. Vektor tag mit erster • /
“Montag", /• Dimension 7 (wegen 7 Wochentage) und •/
•Dienstag", /• zweiter Dimension 11 (well laengster •/
"Mittwoch'*, /♦ Wochentag Donnerstag 10 Zeichen ♦/
"Donnerstag", /• lang ♦ 1 Zeichen fuer das abschlies •/
•Freitag“, /• sende \0-Zeichen) mit den Wochentagen»/
"Samstag", /• vorbesetzen •/
Sonntag“
main()
<
int 1, j, zahl,ges_tag, ges monat, ges Jahr, zeile;
int mon_kennzahl, schluessel_zahl;
for (1 = 1 ; i< = 24 ; 1+4) /• Belegen der ersten Spalte des •/
jahreC1+33COJ = 1900+1; /• zweidimensionalen Vektors Jahre •/
for (j = l ; j<6 ; J + + ) /• Belegen der restlichen Spalten •/
for (i=0,zahl=l925+(J-1)*28 ; 1<28 ; zahl++,l++)
jahretiltJ) = zahl;
wochen_tageC63C03 - 1; /• Belegen der 1. und letzten Spalte •/
wochen_tageI03C63 » 37; /• des zweidim. Vektors wochen_tage •/
for (jsl ; j<6 ; J++) /• Belegen der restlichen Spalten von •/
for (i=0,zahl=2+(j-1)»7 ; i<7 ; zahl++,l++) /• wochen tage •/
wocnen_tage<13CJ3 = zanl;
printf("\n\nBeben Sie den Tag Ihres Datums ganzzahlig ein !\n’);
scanf("Xd",&ges_tag);
printf("\n\nBeben Sie den Monat Ihres Datums ganzzahlig ein ?\n");
scanf("Xd",8ges monat);
printf("\n\nSeben Sie das Jahr Ihres Datums ganzzahlig ein !\n*);
scanf("Xd",&ges_jahr);
for (j=0,i=4 ; j<6
if (jahrellKjl
break;
if (i<27)
1++;
eise <
1=0;
; > t
ss ges_jahr)
/• Durchsuchen der Jahrestabelle •/
/♦ nach eingegeb- Jahreszahl; wird •/
/• die gesuchte Jahreszahl •/
/• gefunden, dann wird die Schleife»/
/• mit break verlassen. ♦/
12-76
zeile = 1; /• Merken der Zeile, in der Jahreszahl gefunden wurde •/
mon_kennzahl s monate(zeile](ges_monat-1I;
schluesselzahl = mon_kennzahl+ges_tag;
for (j=0,i=6 ; J<7 ; ) {
if (Wochentage!1H]1 == schluessel zahl)
break;
if (i<6)
1 ♦*;
eise {
1=0;
printf("\nX2d.X2d.X4d ist ein xs\n", ges_tag,ges_monat,ges_jahr,tagtiD;
Erläuterungen:
Im Programmteil
for (1x1 ; i<=24 ; !♦♦) /•
Jahreli + 3HOJ = 1900+1; /•
for (jxl ; j<6 ; ]♦♦) /•
for (i = 0,zahl = 1925+(j-1)»2fi
jahretlHjl = zahl;
Belegen der ersten Spalte des •/
zweidimensionalen Vektors Jahre ♦/
Belegen der restlichen Spalten •/
1<2S ; zahl++,i++)
wochen tagel6M0) = 1; /• Belegen der 1. und letzten Spalte •/
wochen5tage(0H6J = 37; /♦ des zweidim. Vektors wochen_tage •/
for (j = 1 ; j<6 ; j++) /♦ Belegen der restlichen Spalten von •/
for (i=0,zahl=2+(j-i)»7 ; i<7 ; zahl++,l++) Wochentage •/
Wochentage!iHj] - zahl;
werden die Tabellen jahre und wochen_____tage entsprechend der Vorgabe aus dem
Dauerkalender mit Werten besetzt.
Im Programmteil
printf("\n\nSeben Sie den Tag Ihres Datums ganzzahlig ein '\n“i;
scanf("Xd",&ges tag);
printf (,,\n\nSeben Sie den Monat Ihres Datums ganzzahlig ein '\n");
scanf(“Xd“,iges_monat);
printf("\n\nGeben Sie das Jahr Ihres Datums ganzzahlig ein ‘\n");
scanf(“Xd",&ges Jahr);
wird das Datum für den zu bestimmenden Wochentag eingelesen.
Mit der for-Schleife
for (j=0,i=4 ; J<6 ; ) <
if (jahreClHJI = = ges.jahr)
break;
if (i<27)
eise <*
1=0;
>
/• Durchsuchen der Jahrestabelle •/
/♦ nach eingegeb. Jahreszahl; Uird •/
/• die gesuchte Jahreszahl •/
/♦ gefunden, dann wird die Schleife«/
/• mit break verlassen. ♦/
wird in der Jahrestabelle jahre das vom Benutzer eingegebene Jahr ges__jahr gesucht.
Wenn es gefunden wurde, wird die for-Schleife sofort mit break verlassen.
12-77
Der Zeilenindex i zeigt dann die Zeile an, in der die gesuchte Jahreszahl gefunden wurde.
Mit der anschließenden Anweisung zeile = i; wird die Zeilennummer in der int-Variablen
zeile festgehalten.
Da jetzt die relevante Zeilennummer ermittelt wurde, kann in der Monatstafel unter dem
entsprechenden Monat die Monatskennzahl ermittelt werden:
mon_kennzahl = monate [zeile] [ges_monat- 1];
Der Spaltenindex ges___monat - 1 ist notwendig, da in C die Vektorelemente mit O begin-
nend gespeichert werden.
Mit der Addition von ges_tag auf mon_Kennzahl erhalten wir dann die Schlüsselzahl:
schluesseL-zahl = mon____kennzahl + ges_tag;
In der for-Schleife
for (j=0,i=6 ; j<7 ; ) (
if (wochen_tage[1HjJ « schluessel_zahl»
break;
if (i<6)
eise (
1=0;
wird die Schlüsselzahl in der Tabelle für Wochentage gesucht. Die Suche erfolgt hierbei
-genau wie bei der Suche in der Jahrestabelle - spaltenweise.
Wenn eine Spalte durchsucht ist (es gilt nicht mehr I < 7). wird auf die oberste Zeile I = 0;
in der nächsten Spalte j + + positioniert.
Wird bei dieser spaltenweise Suche in der Tabelle wochen_____tage die Schlüsselzahl ge-
funden, dann wird mit break sofort die for-Schleife abgebrochen.
Die Laufvariable i zeigt dann die Zeilennummer an, in der die Schlüsselzahl gefunden wur-
de. Es muß jetzt lediglich die entsprechende l-te Zeile des Vektors tag. der ja die ganzen
Wochentage enthält, ausgegeben werden:
printf("\nZ2d.Z2d.X4d ist ein Zs\n", gestag,ges_«onat,ges_jahr,tagtU);
12.5 ZEIGERVEKTOREN UND
ZEIGER AUF ZEIGER
Bisher wurde nur von einfachen Zeiger-Deklarationen Gebrauch ge-
macht; es sind aber auch z-. B. Zeigervektoren möglich:
int «zeigvektor [6];
zeigvektor ist dann ein Zeigervektor mit 6 Vektorelementen (Vektor
mit 6 Zeigern), die alle auf int-Variable zeigen können.
12-78
Da die eckigen Klammern stärker als * * binden, ist zeigvektor [... ]
ein Zeiger auf int-Variable, und der implizite Zeiger zeigvektor zeigt auf das
1. Element dieses Zeigervektors, zeigvektor enthält folglich die Anfangs-
adresse des Feldes von Zeigern:
Durch das Setzen von Klammern können diese Vorrangsregeln von *
und [ ] durchbrochen werden:
Int («zeigvektor) [6];
Mit dieser Deklaration wird nun ein Zeiger zeigvektor festgelegt, der
auf ein Feld mit 6 int-Variablen zeigt:
Somit verfügen wir über verschiedene Möglichkeiten, einen zweidimen-
sionalen Vektor in Funktionen zu deklarieren:
Int matrix [10] [10];
oder int matrix [ ] [10];
oder Int «matrix [10];
HINWEIS:
•) Vorsicht, hierfür ist noch kein Speicherplatz reserviert.
12-79
Wir können diese Variationen noch weiter treiben:
int ««zeig__zeig;
««zeig___zeig ist dann eine int-Variable.
* zeig__zeig ist ein Zeiger auf eine int-Variable und
zeig____zeig ist ein Zeiger auf einen Zeiger, der auf eine
int- Variable zeigt:
Die Anwendungen solcher mehrfachen Verzweigungen (Zeiger auf Zei-
ger auf...) liegen mehr im Bereich maschinennaher Aufgaben.
Die Verwendung zweidimensionaler Vektoren besitzt manchmal Nach-
teile gegenüber eindimensionalen Zeigervektoren.
Beispiel 17
Es soll ein kleines C-Programm erstellt werden, das eine ganze Zahl im Bereich 0 ... 13
über Bildschirm einliest und das entsprechende englische Zahlwort dazu ausgibt.
12-80
C-Programm 1:
char engl_wortC14H9] = {
"zero*,
"one" ,
"two“,
"three“,
"four",
•fixe*,
"Six",
“seven",
"eight",
"nine* 9
"ten“,
"eleven",
*twelve",
•thirteen"
ain()
<
int zahl;
printf("\n\n6eben Sie eine ganze Zahl zwischen □ und 13 ein ’\n">;
scanf("Xd"»fczahl);
while (zahl<0 ll zahl>l3) £
printfC"\n\n\nFalsche Eingabe I\n\n\nWlederholen Sie Ihre Eingabe
scanf("Zd",&zahl);
printf("\n\n\nXd ist in englisch: Xs\n",zahl,engl_worttzahl]);
Bei der externen Deklaration von engl_wort wurden gleichzeitig die Vektorelemente ini-
tialisiert. Hier beträgt der Speicherbedarf von
char engl_wort [14] [9] = {..
14 • 9 = 126 Bytes. Jedes Zeichen belegt bekanntlich ein Byte.
z e r 0 \0
0 n e \0
t w o \0
t h r e e \0
f 0 u \0
f i V e \0
s i X \O
s e V e n \O
e ij 9 h t \0
n i n e \0
t e n \0
e I e V e n \0
t w e I V e \O
t h i r t e e n \0
Wir benötigen 14 • 9 Bytes, da das längste
Zahlwort (thirteen) 8 Zeichen lang ist + 1 Byte
für das \ O-Zeichen, um das Wortende durch
den Compiler kennzeichnen zu lassen.
12-81
Wie wir sehen, wird nur beim englischen Zahlwort thirteen die volle Kapazität des reser-
vierten Speicherplatzes in Anspruch genommen. Die anderen Zeichenketten verschwen-
den Speicherplatz. Dieses Problem läßt sich mit Hilfe eines Zeigervektors umgehen
C-Programm 2:
char ♦engl_WDrttl = (
"zero",
"one",
"two“,
"thre® ’•,
"four",
"fiv»*,
"six",
"seven",
"eight",
"nlne",
"ten",
-eleven",
"twelve"t
"thlrteen *,
0
>;
main ()
(
int zahl;
printf("\n\n6eben Sie eine ganze Zahl zwischen □ und 13 ein
scanf(“Xd“,&zahl);
while (zahl<0 II zahl>l3) <
printf("\nXn\nFalsche Eingabe '\n\n\nuiederholen Sie Ihre Eingabe ’Xn");
scanf("Xd",&zahl);
printf("\n\n\nXd ist in englisch: Xs\n"fzahl,engl_wort(zahin;
Hier kann bei der Deklaration von engl___wort auf die Größenangabe verzichtet werden
(engl—wort [ ]); der Compiler legt die Größe von engl__wort anhand der Anzahl der Ini-
tialisierungswerte fest.
Der am Ende des Vektors angefügte Zeiger mit Wert 0 ist nicht erforderlich und wird auch
automatisch vom Compiler erzeugt.
Hier wird dann für jedes Zahlwort seine echte Länge plus ein NULL-Byte plus je ein Zeiger
(2 Byte*) für einen Zeiger) reserviert:
HINWEIS: *) Auf manchen Systemen werden 4 Bytes für einen Zeiger reserviert.
12-82
Zeichenanzahl
Zeigervektor
engl_wort[ 0 ] engl_wort[ 1 ] engl_wort[ 2 ] engl_wort[ 3 ] engl wort[ 4 ] engl_wort[ 5 ] engl_wort[ 6 ] engl_wort[ 7 ] engl wort[ 8 ] engl_wort[ 9 ] engl_wort[10] engl wort[11] engl wort[12] engl__wort[13] » z e r o po| | 4 + 1
o n e \o 3 + 1
► h t W o \0 3 + 1
t h r e e \°l 5 + 1
p
) • 0 u r \0 4+1
* K t i V e 0 4 + 1
s i X \o
P ö + i
=w s e V e n \0 5+1 5 + 1
e i 9 h t 0
—P
n i n e \o 4 + 1
k t e n \o
p 3+ 1
H e I e v e n \0 6+1 __ 6+1
s t w e I V e \0
t h i r t e e n |\o| 8+1
F
28 Bytes
63 + 14
Insgesamt werden hier 28 + 63 + 14 = 105 Bytes besetzt. Liegen noch gravierendere Un-
terschiede bei der Wortlänge vor, so ergeben sich entsprechend größere Speichereinspa-
rungen. Werden also Zeigervektoren anstatt mehrdimensionaler Vektoren benutzt, so wird
kein Speicherplatz mehr verschwendet.
Ein weiterer Vorteil der Zeiger-Arithmetik ist der einfache Zugriff auf die Vektorelemente.
Auf die Zeichenketten eines char-Zeigervektors kann innerhalb einer for-Schleife leicht
zugegriffen werden:
for (i = 0; I <= 13; I++)
printf(”%d Ist In englisch: %s\n”, I, engl_wort [I];
Wie kann man aber nun auf ein beliebiges Zeichen innerhalb der einzelnen Zeichenketten,
z. B. auf das h des Zahlworts eight. zugreifen?
Auch das ist wieder mit Zeigern möglich:
engl_wort [8] liefert den Zeiger auf das erste Zeichen der neunten Zeichenkette:
engl.wort[8]
Wir können nun durch einfache Arithmetik einen Zeiger verschieben. Also ist es möglich,
z. B. auf das vierte Zeichen h der Zeichenkette zuzugreifen; dabei ist auf die richtige An-
ordnung der Klammern zu achten:
♦(engL-Wort [8] + 3)
engl_wort[8]
12-83
Anstelle von *(engL_wort [8] + 3)*> könnten wir auch mit:
engl wort [8] [3]
auf den Buchstaben h zugreifen, obwohl engL-.wort bei der Deklaration nicht explizit als
mehrdimensionales Feld angegeben wurde.
Beispiel 18
Das bekannte Lied ' Drei Chinesen mit dem Kontrabaß saßen auf der Straße ...” soll von
einem Programm "gesungen” werden.
In der zweiten Strophe werden alle Vokale durch den Vokal a ersetzt (Dra Chanasan mat
dam Kantrabaß saßen af dar Straßa ...) und in der dritten Stophe durch e. dann durch i o
und noch u.
Es bietet sich hier an, die 1. Strophe in einem zweidimensionalen Vektor abzulegen; dies
wird am besten schon bei der Deklaration vorgenommen:
char »satzCJ = (
"Drei Chinesen mit dem Kontrabass sassen auf der Strasse",
"und erzaehlten sich was. Da Kam die Polizei und sagt:",
“Was ist denn das
Ebenso sollen die 5 Vokale in einem eindimensionalen char-Vektor schon bei der Dekla-
ration gespeichert werden:
char vohalell « "aeiou";
Um die beiden Vektoren satz und vokale - wie angegeben - initialisieren zu können, sind
beide extern zu deklarieren.
Unser C-Programm gliedert sich dann in 3 Aufgaben:
(1) Alle Vokale in satz durch einen vorgegebenen Vokal ersetzen
(2) Alle doppelten Vokale durch einen einzelnen ersetzen
(3) Den neuen Inhalt von satz ausgeben
Die Aufgabe (2) ist nur beim ersten Ersetzen auszuführen, da nach einem Streich-
durchgang für alle doppelten Vokale nur noch einfache Vokale vorkommen und beim nach-
folgenden Ersetzen auf den neuen Inhalt von satz zugegriffen wird.
HINWEIS: *> auch möglich: *(*(engl_wort + 8) + 3)
12-84
C-Programm 1 (Version mit Zeigern):
char -satzC] = <
"Drei Chinesen Bit den Kontrabass sassen auf der Strasse**,
'und erzaehlten sich was. Da kam die Polizei und sagt:**,
"Was ist denn das ?**
char vokaleC3 s “aelou";
nain (I
C
int
j. 1;
........................................................
/• Alle vokale durch einen Vokal ersetzen • /
.........................................................
for (1=0 ; Kstrlen(vokale) ; !♦♦) <
for (j=0,k=0 ; j<3 ; k = 0,jw)
while (•(satzCJ3+k)) C
for (1=0 ; 1<5 ;
if (•(satzCj3*k) *« ♦ ivokale+D) <
•(satzCJ3*k) = »(vokale+i);
öreak;
>
/• Alle doppelten Vokale durch einen ersetzen •/
..................................................♦•••♦/
if (i~0) <
for (j=D,k=O,l=O ; j<3 ; k=O,l=O,Jf+) {
while (•(satzCJ3*k+1))
lf (»(satzC jHfc)= = » (satzCj]*k + l) && vokpruef (• (satzC j Hk) )
•(satzCjHl) - • (satzC jHk*+);
eise
• (satzCjHlw) x • (satzC JHk*+);
♦ (satzCjH1++) = •isatzCJHk);
• (satzCJRl) = \0';
/• Ausgabe des neuen Textes •/
for (j=0 ; j<3 ; j+*)
printf ("XnXs",satzCj3);
printf (**\n\n** >;
vokpruef(Zeichen)
char Zeichen;
<
int lauf;
for (laufxQ ; lauf<5 ; lauf**)
lf (♦(vokale+lauf) Zeichen)
return(1);
return(0);
Erläuterungen:
Die äußerste Schleife
for (i = 0; i < strlen (vokale); i + +)
sorgt dafür, daß für jeden Vokal eine eigene Strophe generiert wird.
12-85
Hier wird die Funktion strlen aus der Standardbibliothek aufgerufen, um die Länge ce
char-Vektors vokale zu ermitteln; die Länge von vokale ist 5. d. h. diese Schleife w '
5mal durchlaufen, und bei jedem Schleifendurchlauf wird eine andere Strophe mit eine*T
anderem Vokal am Bildschirm ausgegeben, wie wir gleich noch sehen werden
Im Programmteil
for (j=O,k=O ; j<3 ; k=O,j++)
while (•(satztj) {
for (1=0 ; 1<5 ;
if (• (satzCjJ + k) == «(vokale*!))
•(satz(jl*k) = «(vokale*!);
break;
1
K++;
ist die Laufvariable j der Zeilenindex für den mehrdimensionalen Vektor satz. satz [j]
zeigt dann immer auf den Anfang der gerade betrachteten Zeile. Auf die einzelnen Zeichen
in einer Zeile wird fortlaufend mit der Laufvariable k zugegriffen:
Das entsprechende Zeichen, auf das satz [j] + k zeigt erhält man mit:
«(satz ÜJ + k)
Solange nicht das Endezeichen \O der Zeichenkette satz Li] erreicht ist
while («(satz [j] + k)).
wird die for-Schleife mit der Laufvariablen I ausgeführt und daran anschließend k um 1 in-
krementiert: k ++, um auf das nächste Zeichen in satz [j] zuzugreifen.
In der for-Schleife mit der Laufvariablen 1 = 0 ... 4 wird bei jedem Durchlauf das gerade
betrachtete Zeichen aus satz Li] mit einem Zeichen aus dem Vektor vokale verglichen:
if («(satz [j] + k) == «(vokale +1))
Wird bei dieser Überprüfung eine Übereinstimmung festgestellt, dann muß es sich beam
Zeichen «(satz [j] + k) um einen Vokal handeln; dieser Vokal wird dann durch den ent-
sprechenden Vokal, abhängig von der Strophe, ersetzt:
«(satz [j] + k) = «(vokale + i)
i ist die Laufvariable der äußersten for-Schleife. die bei jeder neuen Strophe um 1 erhöht
wird. Nach dieser Ersetzung kann die Überprüfung für das gerade betrachtete Zeichen aus
satz [j] abgebrochen werden (break).
12-86
Im Programmteil
• ••••...............................••••«••......
Alle doppelten vokale durch einen ersetzen •/
.................-......................
If 115=0) (
for (j=O,k=O,l=O ; j<3 ; k=O,l=O,j*«) <
while <•(satztj]+k+l))
if («(sätzCj]+k)==•(satzC) && vokpruef(•(satztjJ*k)))
•(satzCJHl) = • <satz[ j J + R+ + );
eise
•Isatzt= • (satzCjHk4+);
• (satzCJH14+) = ♦(satzejHk);
•(satzej] + l) = ‘\D* ;
)
wird der gesamte Text des zweidimensionalen Vektors satz nochmals durchlaufen und
alle doppelten Vokale durch einen Vokal ersetzt; dies geschieht allerdings nur bei der er-
sten Ersetzung, d. h. bei if (i == O), da bei der nächsten Ersetzung alle doppelten Vokale
schon durch einen einzelnen ersetzt wurden.
In der for-Schleife werden zunächst die drei Zählvariablen:
j Zeilenindex für das zweidimensionale Feld satz
k '’Spaltenindex” für das ' alte” Feld satz
I ’ 'Spaltenindex ’ für das ' neue ’’ Feld satz
mit 0 vorbesetzt.
Solange nicht das Ende einer Zeile erreicht ist:
while («(satz [j] + k+1))
wird mit
if («(satz [j] + k) = = «(satz [j] + k + 1) & & vok_pruef(«(satz [j] + k)))
überprüft, ob das gerade betrachtete Zeichen aus satz [j] mit dem nächsten Zeichen
übereinstimmt (Doppelbuchstaben?); zusätzlich wird durch den Aufruf der Funktion
vok___pruef*) geprüft, ob es sich bei «(satz [j] + k) um einen Vokal handelt. Liegt ein
Doppelvokal vor, dann wird er in satz an die Stelle satz [j] + I geschrieben (I wird aber
nicht erhöht):
«(satz UJ + 0 = «(satz [j] + k + +)
Handelt es sich nicht um einen Doppelvokal, dann wird mit
«(satz [j] +1 + +) = «(satz [j] + k + +)
auch das Zeichen an der Position satz [j] + k an die Stelle satz [j] + I geschrieben; für
diesen Fall wird aber der ”Spaltenindex” I für das "neue” Feld um 1 inkrementiert:
•(satz []] + !++).
HINWEIS: *) Die Funktion vok___pruef liefert an die aufrufende Funktion den Wert 1
(wahr), wenn es sich beim an Zeichen übergebenen aktuellen Parameter
um einen Vokal handelt, sonst gibt sie den Wert 0 (falsch) an die aufrufende
Funktion zurück.
12-87
Mit den letzten beiden Anweisungen nach der while-Schleife wird das letzte Zeichen aus
dem "alten’ satz [j] in den "neuen" übernommen, und das Ende vom "neuen satz [j]
mit dem ”\O”-Zeichen gekennzeichenet.
Im Programmteil
.............................................................
/• Ausgabe des neuen Textes •/
.........................................
for (j=0 ; j<3 ; j**)
printf(*\nXs“,satzLj]>;
printf(*\n\n*J;
>
wird der veränderte Text des zweidimensionalen Feldes satz am Bildschirm ausgege-
ben.
C-Programm 2 (Version mit Vektoren):
Hier wird die gleiche Aufgabe unter Verwendung von Vektoren, auf deren Elemente mit
Zeilen- und Spaltenindex zugegriffen wird, angegeben:
char satzC3H701 = <
"Drei Chinesen mit dem Kontrabass sassen auf der Strasse",
"und erzaehlten sich was. Da kam die Polizei und sagt:*,
"Uas ist denn das ?
>;
char vokaleC] = "aeiou";
main ()
(
int 1, J, k, 1;
............................................................
/• Alle vokale durch einen Vokal ersetzen •/
for (1=0 ; icstrlen(vokale) ; 1**> (
for (j=O,k=O ; J<3 ; k=O,j**)
while (satzCjHkD (
for (1=0 ; 1<5 ; 1**)
lf (satzCjHk] == vokaleCU) (
satzCjHk] = vokalen J;
break;
1
k+*;
)
...........................................................
/♦ Alle doppelten Vokale durch einen ersetzen •/
................................*.....................
if (l=«0l C
for (j=O,k=O,l=O ; J<3 ; k=O,l=O,j*+) (
while (satzC JHk + 1 ])
lf (satzC jHk] = = satzCjHk*i ] && vok pruef (satzlJHK3H
satzCjHU = satzCjHk**];
eise
satzCjHl**] - satzCJHk**];
satzCJ Hl**] = satzCjHk];
satzCjHlJ = \0 ;
)
/♦ Ausgabe des neuen Textes •/
for (j=0 ; j<3 ; j**)
printf("\nXs",satzCJ]);
printf("\n\n");
)
>
12-88
vokpruef(Zeichen)
char Zeichen;
(
int lauf;
for (lauf=O ; lauf<5 ; lauf**)
if (♦(vokale*lauf) == Zeichen)
returnci);
return(0);
12.6 PARAMETER FÜR DIE
KOMMANDOZEILE: arge, argv
Jedes C-Programm muß - wie wir wissen - einen Hauptprogrammteil
main besitzen. Dieses Hauptprogramm ist aber nichts anderes als eine
normale Funktion, die beim Start des C-Programms vom Betriebssy-
stem oder C-Laufzeitsystem aufgerufen wird. Wir gingen bisher davon
aus, daß main() ohne Parameter arbeitet; in Wirklichkeit werden aber
bei jedem Aufruf der Funktion main() 2 Parameter mitgegeben, die wie
folgt definiert sind:
mainlarge,argv)
int arge;
char *argv(];
/♦ Anzahl der Parameter auf der Kommandozelle, •/
/• mit denen das Programm aufgerufen wird. •/
Zeigervektor auf Zeichenketten; die in der Kommando-
zeile angegebenen einzelnen Zeichenketten (durch
Leerzeichen getrennt) sind dann die aktuellen
"Parameterelemente", auf die die einzelnen
Zeiger von argv (argvCO],argvC11,...) zeigen
Die beiden Parameter arge und argv ermöglichen also die Übergabe
von Zeichenketten an das aufgerufene Programm. Dabei gibt der erste
Parameter arge die Anzahl der übergebenen Zeichenketten an. Der
zweite Parameter ist ein Vektor von Zeigern; jeder dieser Zeiger zeigt
auf den Anfang einer Zeichenkette aus der Kommandozeile.
Wenn wir z. B. ein Programm mit Namen test erstellt haben, dann wird
dieses Programm üblicherweise mit test—1 in der Kommandozeile ge-
startet.
Nun wollen wir es aber mit folgendem Kommando
test start eins zwei drei vier—i
aufrufen.
12-89
Dieses Kommando ruft dann die Funktion main mit folgenden Parar-
tern auf:
aktueller Parameter 6 test start eins zwei drei vier
formaler Parameter arge argv[O] argv[1] argv[2] argv[3] argv[4] argv(5]
argv [3] ist z. B. ein char-Zeiger auf das erste Zeichen des Wortes
zwei: die einzelnen Wörter sind dann gemäß der C-Konvention mit de~
\O-Zeichen abgeschlossen:
”test\O”, ”start\O”, ”eins\O”,...
Beispiel 19
Ein kleines C-Programm soll nun seine übergebenen Parameter aus der Kommandoze e
(Zeichenketten durch Leerzeichen getrennt) am Bildschirm ausgegeben.
main(arge,argv)
int arge;
char «argvCJ; /• auch noeglich: char »»argv; •/
C
int zaehl;
for (zaenl=O ; zaenlorgc ; zaehl**)
printf("XnUort Xd : Xs",zaehl,argvCzaehlD;
Wenn wir dieses Programm mit
test start eins zwei drei vier^-i
aufrufen, so ergibt sich folgende Bildschirmausgabe:
Wort 0: test (+)
Wort 1: start
Wort 2: eins
Wort 3: zwei
W ort 4: drei
Wort 5: vier
(*) Digttal-Research-C unter CP/M und LA TTICE-C unter PC-DOS
geben hier Wort 0: C aus.
Da der Aufruf eines Programms bei vielen Betriebssystemen durch Angabe des Programm-
namens erfolgt, ist es zur Regel geworden, daß argv [0] ein Zeiger auf den Programme-
men, in unserem Beispiel: argv [0] —fr test *) ist.
HINWEIS: *) Vollständige Ausgabe: Pfadangabe + Programmname ♦ Extension .exe.
12-90
Wir wollen mit Übergabe von Parametern aus der Kommandozeile einen Count Down bei
einem Raketenstart simulieren. Die Ausgabe der Wörter soll dabei in nur einer Zeile erfol-
gen, wobei die einzelnen Wörter durch Leerzeichen getrennt sind:
am (arge,argv)
int arge;
char ••argv;
printf(*\nCount Down :\n\n“);
while (—arge) #
printfCXs Xc“ »argvtargcl, (argc>2) 7 ' * : \n);
)
Der Aufruf dieses Programms mit
test start eins zwei drei vier-^-l
ergäbe folgende Bildschirmausgabe:
Count Down:
VIER DREI ZWEI EINS
START
Zu dieser Aufgabenstellung soll eine weitere Version angegeben werden:
nain(arge,argv)
int arge;
char «*argv;
<
printf(’VnCount Down :\n\n");
argv**argc; /• Zeiger von argv hinter aas letzte Wort positionieren •/
while (--arge)
printf((argc>2) ? “Xs " : "Xs\n*,»—argv);
Mit der Anweisung
argv+ = arge; (entspricht argv = argv + arge;)
wird der zeiger argv hinter das letzte Wort aus der Kommandozeile positioniert:
HINWEIS: *) abhängig vom Wert der Int-Variablen arge wird bei
pnntf(*Xs Xc-»argvCargc], (argc>2) ? * ' : An);
zum Formatzeichen %c entweder ein Leerzeichen oder ein Zeilenvorschub
ausgegeben.
12-91
test \0
start \0
eins \0
zwei \0
drei \0
vier \0
arge besitzt für den Aufruf
test start eins zwei drei vier
den Wert 6
In der Anweisung
while (-- arge)
wird zunächst der Wert von arge um 1 dekrementiert und dann geprüft, ob der neue
von arge ungleich 0 ist. Wenn ja, dann wird die Anweisung
printf((argc > 2) ? ”%su” : ”%s\n”, ♦ — argv);
ausgeführt.
In dieser Anweisung wird unter anderem gezeigt, daß die Formalparameter bei der Funk
tion printf als Ausdrücke angegeben werden können:
12-92
Bei der Übergabe von Parametern aus der Kommandozeile ist auch die
Angabe sogenannter optionaler Parameter erlaubt.
Optionale Parameter müssen - im Gegensatz zu obligaten Parametern -
beim Aufruf nicht angegeben werden.
Um beim Aufruf optionale Parameter von obligaten unterscheiden zu
können, wird in 0*1 üblicherweise den optionalen Parametern ein Mi-
nus-Zeichen vorangestellt.
Beispiel 21
Wir werden ein C-Programm schreiben, das über Bildschirm einen Text einliest und diesen
Text über Bildschirm wieder ausgibt.
Abhängig von den Parametern in der Kommandozeile wird dieser Text nach folgenden Kri-
terien ausgewertet:
Anzahl der gelesenen Zeichen -c (c für character)
Anzahl der gelesenen Wörter -w
Anzahl der eingegebenen Zeilen -I (I für line)
Unser Programm soll auf maximal 50 Zeilen begrenzt werden und den Namen auswert
besitzen. Rufen wir dann z. B dieses Programm mit auswert -L -w-*J auf, so ergibt sich
beispielsweise folgender Bildschirmablauf:
Geben Sie Ihren Text ein!
Dies ist eine Testeingabe—1
zum Programm auswert—1
Der Text wird durch ein CR —J
in einer Leerzeile —1
abgeschlossen —1
Der eingegebene Text war:
Dies ist eine Testeingabe
zum Programm auswert
Der Text wird durch ein CR
in einer Leerzeile
abgeschlossen
17 W OERTER
5 Zeilen
Eingabe
Ausgabe
HINWEIS: *) bezieht sich auf Kommandos (Dienstprogramme) im Betriebssystem UNIX
12-93
Die optionalen Parameter sollten in beliebiger Reihenfolge angegeben werden dürfen; zu-
sätzlich sollten als optionale Parameter sowohl Groß- als auch Kleinbuchstaben erlauf
sein:
aus wert -W -C -I—।
Geben Sie Ihren Text ein!
Dies ist eine Testeingabe—j
zum Programm auswert—-1
Der Text wird durch ein CR—1
in einer Leerzeile—1
abgeschlossen —1
Der eingegebene Text war:
Dies ist eine Testeingabe
zum Programm auswert
Der Text wird durch ein CR
in einer Leerzeile
abgeschlossen
102 Zeichen (ohne CR)
17 Woerter
5 Zeilen
Eingabe
Ausgabe
Schließlich sollte es dem Benutzer noch erlaubt sein, die optionalen Parameter aneman
derzufügen:
auswert -Lc—J
Geben Sie Ihren Text ein!
Dies ist eine Testeingabe—1
zum Programm auswert—j
Der Text wird durch ein CR —J
in einer Leerzeile —1
abgeschlossen —J
Der eingegebene Text war:
Dies ist eine Testeingabe
zum Programm auswert
Der Text wird durch ein CR
in einer Leerzeile
ABGESCHLOSSEN
102 Zeichen (ohne CR)
5 Zeilen
Eingabe
► Ausgabe
12-94
C-Programm:
•include <stdio.h>
♦define MAXZEILEN 50
Idefine MAXZEICHEN 80
int lies_zeile(string)
char stringf);
(
int i=0;
while ((string(i++]=getchar()) •» 'Xn')
string[i-l]-*\0*;
retum(i-l) ;
I
main(argc,argv)
int arge;
char *argv[);
(
char zeich_kette(MAXZEILEN](MAXZEICHEN), *zeiger;
int zeich«=0, wort=0, zeil=0, richtig-1;
int zeil_zaehl-0, wort_zaehl=0, zeich_zaehl-0;
int laenge[MAXZEILEN], i, j;
while (—argc>0 £4 (*++argv)[0]==•-',
for (zeiger=argv(0]+l ; *zeiger !- •\0* ; zeiger++)
switch(*zeiger) (
case ’C’:
case ’c':
zeich-1;
break;
case 'W’x
case ’w’:
wort-1;
break;
case ’L':
case '1':
zeil-1;
break;
default:
printf("\n\nnicht erlaubte Paraneterangabe: %c\n",*zeiger) ;
richtig«0;
break;
>
if (richtig !- 1)
printf("\nerlaubte Parameter sind : -c -w -l\n");
eise |
printf("\n\nGeben Sie Ihren Text ein !\n\nM);
while ((laenge[zeil_zaehl]-lies_zeile(zeich_kette(zeil_zaehl])) I« 0)
zeil_zaehl*+;
printf("\n\nDer eingegebene Text war:\n");
for (i-0 ; i<zeil_zaehl ; i++)
printf (**\n%s*,zeich_kette[i]);
printf("\n\n");
if (zeich) (
for (i-0 ; i<zeil_zaehl ; i»)
zeich_zaehl+=laenge[i];
printf("%d Zeichen (ohne CR)\n",zeich_zaehl);
)
if (wort) (
for (i-0,j-0 ; i<zeil_zaehl ; j-0,i++)
while (•(zeich_kette[i)+j)) (
j-M-;
if (*(zeich kette(i]+j-l) 1- • • )
if (*(zeich_kette[i]+j) — ' ’ || *(zeich_kette[i]+j)~’\0')
wort_zaehl++;
I
HINWEIS: In jeder Zeile dürfen nur 80 Zeichen eingegeben werden.
12-95
printf(M%d Woerter\n",wort_zaehl);
if (teil)
printf("%d Zeilen\n",zeil_zaehl);
)
}
Erläuterungen:
Erklärung der Variablen aus dem main-Deklarationsteil.
char ze1chkettetMAXZEILEN1CMAXZEI CHEN], »zeiger;
int zeich=0, wort=0, zeil=O, richtlg=l;
int zeil zaehl=O, wort zaehlsO, zeich zaehlxO;
int laengetHAXZElLENl, ‘1, j;
Im zweidimensionalen Vektor zeich_kette wird der eingegebene Text abgespeichert
Die Variable zeich wird auf 1 gesetzt, wenn der Benutzer in der Kommandozeile der op-
tionalen Parameter -c bzw. -C angegeben hat.
Die Variable wort wird auf 1 gesetzt, wenn der Benutzer in der Kommandozeile den optio-
nalen Parameter -w bzw. -W angegeben hat.
Die Variable zeil wird auf 1 gesetzt, wenn der Benutzer in der Kommandozeile den optio-
nalen Parameter-I bzw. -L angegeben hat.
Die mit 1 vorbesetzte Int-Variable richtig wird auf 0 gesetzt, wenn in der Kommandoze. e
unerlaubte Parameter angegeben wurden.
Bei den Variablen zeil_zaehl. wort_zaehl und zeich___zaehl handelt es sich um Zan-
lervariablen für die Zeilen, Wörter und Zeichen des eingegebenen Textes.
Im int-Vektor laenge wird die Zeichenzahl für jede einzelne Zeile festgehalten.
Im Programmteil
While l-argoO && (•♦♦argv)(0)««'-’)
for (zeiger=argvCOHi ; -zeiger '= '\D' ; zeiger+4)
switch(»zeiger) <
case C ;
case c':
zeich=t;
break;
case u :
case w :
wort«i;
break;
case L :
case 1 :
zeil-1;
break;
befault:
printf(-\n\nnicht erlaubte Parameterangabe: Xc\n“,»zeigerj;
richtig=O;
break;
)
werden die optionalen Parameter aus der Kommandozeile ausgewertet.
12-96
Wurde z. B. in Kommandozeile
auswert -IW -c-*J
angegeben, dann gilt für die beiden Parameter arge und argv:
arge = 3
argv [O] -- ► auswert\O
argv [1] => -IW\O
argv [2]----> -c\O
Beim Eintritt in die while-Schleife ergibt sich mit -- arge > 0 der neue Wert 2 für arge.
Dieser Wert ist größer 0, d. h. die 1. Bedingung ist erfüllt.
Mit der 2. Bedingung
(* + +argv) [0]==’-’
soll überprüft werden, ob es sich beim 1. Zeichen der entsprechenden Zeichenkette um
em Minus-Zeichen handelt, was diese als optionalen Parameter kennzeichnet.
Das Setzen von Klammern ist hierbei notwendig:
argv ist ein Zeiger auf einen anderen Zeiger. Zu Programmbeginn zeigt argv auf argv [O]
während argv [O] auf das erste Wort aus der Kommandozeile auswert zeigt.
Mit + + argv wird der Zeigerzeiger argv auf den Zeiger argv [1] positioniert, argv [1]
zeigt auf das zweite Wort aus der Kommandozeile.
Mit *++ argv gelangen wir also zum Zeiger argv [1]. Um das 1. Zeichen aus der Zei-
chenkette, auf die argv [1] zeigt, zu erhalten, müssen wir
(*++argv) [O]
argv[1][0]
angeben.
12-97
Ohne Angabe von Klammern würde der Ausdruck
« + + argv[O]
dem Ausdruck
• + + (argv [O])*)
entsprechen, der etwas völlig anderes und zudem falsches bedeutet.
Eine andere mögliche Formulierung für
(« + +argv) [O]
wäre
♦ ♦ + + argv:
Nach dem Ausdruck (*+ + argv) [O] in der while-Anweisung zeigt also argv auf einen
Zeiger, der auf das zweite Wort aus der Kommandozeile zeigt; dieser Zeiger auf das zwei-
te Wort ist nun nicht mehr argv [1] entsprechend *++argv oder «(argv + 1), sondern
argv [0] entsprechend «argv oder «(argv +0)
HINWEIS: *) Eckige Klammern binden stärker als •
12-98
Zeiger-
Zeiger-
ebene
Mit der Anweisung
for (zeiger = argv [0] + 1; «zeiger ! = ’\0’; zeiger ++)
wird die Zeigervariable zeiger zunächst initialisiert (zeiger = argv [0] + 1):
Zeiger-
Zeiger-
ebene
Da «zeiger gleich dem Zeichen I ist, wird in der «witch-Anweisung die int-Variable zeil
auf 1 gesetzt. Mit zeiger ++ in der for-Anweisung wird die Zeigervariable zeiger auf
argv [0] [2] positioniert:
12-99
Zeiger-
Zeiger-
ebene
Da »zeiger nun gleich dem Zeichen W ist, wird in der switch-Anweisung die int-Variable
wort auf 1 gesetzt. Mit dem darauffolgenden zeiger ++ wird die Zeigervariable zeiger
auf argv [O] [3] positioniert, so daß «zeiger gleich dem \O-Zeichen ist. was zum Ab-
bruch der for-Schleife führt.
Als nächstes werden dann wieder die Bedingungen der whlle-Schleife ausgewertet:
Mit - - arge > O ergibt sich für arge der neue Wert 1, und dieser Wert ist grö-
ßer 0(1. Bedingung erfüllt).
Die 2. Bedingung (♦ + + argv) [0] == ist - wie wir im folgenden Bild sehen -
ebenfalls erfüllt:
12-100
Nach dem Ausdruck (* ++ argv) [O] in der while-Schleife zeigt also argv auf einen Zei-
ger, der auf das dritte Wort aus der Kommandozeile zeigt; dieser Zeiger auf das dritte Wort
ist nun nicht mehr argv [1], sondern argv [O]
Zeiger-
Zeiger-
ebene
Zeiger-
ebene
Vanablen-
ebene
Werte-
ebene
Mit der Anweisung
for (zeiger = argv [0] + 1; «zeiger ! = ’\0*; zeiger + +)
wird zunächst die Zeigervariable zeiger initialisiert (zeiger = argv [O] + 1):
Zeiger-
Zetger-
ebene
Zeiger-
ebene
Variablen-
ebene
Werte-
ebene
12-101
Da «zeiger gleich dem Zeichen c ist, wird in der switch-Anweisung die ini-Variac -
zeich auf 1 gesetzt.
Mit dem darauffolgenden zeiger ++ in der for-Anweisung wird die Zeigervariable zeiger
auf argv [0] [2] positioniert, so daß «zeiger gleich dem \O-Zeichen ist, was zum Ab-
bruch der for-Schleife führt.
Als nächstes werden dann wieder die Bedingungen der while-Schleife ausgewertet M •
— arge > 0 ergibt sich für arge der neue Wert O, so daß diese Bedingung nicht mehr er-
füllt ist und die while-Schleife nicht noch einmal ausgeführt wird.
Im Programmteil
if (richtig !- 1)
printf(*\nerlaubte Parameter sind : -c -w -l\n");
eise (
printf("\n\nGeben Sie Ihren Text ein l\n\n");
while ((laenge[zeil_zaehl]-lies_zeile(zeich_kette[zeil_zaehlJ)) !• 0)
zeil_zaehl++;
printf("\n\nDer eingegebene Text war:\nw);
for (i-0 ; i<zeil_zaehl ; i++)
printf("\ntsw,zeich_kette[i]);
printf("\n\n")r
if (zeich) (
for (i-0 ; i<zeil_zaehl ; 1++)
zeich_zaehl+=laenge[i];
printf("%d Zeichen (ohne CR)\n",zeich_zaehl);
)
if (wort) (
for (i-0,j-0 ; i<zeil_zaehl ; j-0,i++)
while (*(zeich_kette[i)+j)) (
if (*(zeich_kette[ij+j-l) 1- • • )
if (*(zeich_kette[i]+j)-®‘ ' || *(zeich_kette[!]♦j)—*\0’)
wort_zaehl++;
I
printf("td Woerter\n",wort_zaehl);
if (zeil)
printf("%d Zeilen\n",zeil_zaehl);
)
)
wird zunächst überprüft, ob die mit 1 vorbesetzte Int-Variable richtig noch diesen Wen
besitzt; wenn nicht, dann wurden in der Kommandozeile unerlaubte Parameter angegeben
was dem Benutzer über Bildschirmausgabe mitgeteilt wird.
Wurden nur erlaubte Parameter in der Kommandozeile angegeben (richtig = 1), so wird
der Benutzer aufgefordert, seinen Text einzugeben.
Das Einlesen des Textes erfolgt mit
while ((laenge [zell__zaehl] = lles_zeile (zeich_Jiette [zell_zaehl]))! = O)
zeil_zaehl ++;
Die Funktion lies__zeile liest eine Textzeile in zeich_kette ein und liefert die Zeichen-
zahl dieser Textzeile zurück; diese Zeichenzahl wird im eindimensionalen Vektor laenge
abgespeichert. Der Index zeil_zaehl zeigt hierbei die Zeile an, deren Zeichenzahl in Vek-
tor laenge abgespeichert wird. Die while-Anweisung wird verlassen, wenn eine leere
Zeile eingegeben wurde, da dann die Funktion lies.zeile den Wert 0 liefert.
12-102
Mit
printf("XnXnDer eingegeöene Text war:\n");
fQr (i=o ; Kzeil zaehl ; !♦♦)
printf(”\nXs"tzeich ketteCiD;
printf("\n\n");
wird der vom Benutzer eingegebene Text nochmals am Bildschirm ausgegeben.
Ist die int-Variable zeich auf 1 gesetzt, wobei der optionale Parameter c bzw. C angege-
ben wurde, dann wird mit einer for-Schleife das eindimensionale Feld laenge durchlaufen
und die int-Werte der einzelnen Feldelemente auf die Variable zeich_zaehl addiert:
if (zeich) {
for (i=0 ; i<zeil_zaehl ; 1**)
zeich_zaehl*=läengeti];
pnntf("Xd Zeichen (ohne CR) \nM ,zeich_zaehl);
zeick—zaehl erhält so die Gesamtzahl der eingegebenen Zeichen (ohne die CR-Zei-
chen).
Im Programmteil
lf (wort) <
for (1=0,J=0 ; i<zeil zaehl ; j=0,i*+)
while (• (zeich ketteC 1 ]-»•])) (
lf («(zeich kettel U*j-1) ' = * )
if (• (zefch_ketteClHj) = = ' ’ ll • (zei ch_kettet 13+j) ««' \0')
wort zaehl**;
)
printfCXd Woer ter\n", wort zaehl);
1
wird die Wörterzahl des eingegebenen Benutzertextes bestimmt und am Bildschirm ausge-
geben, wenn die Int-Variable wort auf 1 gesetzt ist. Der optionale Parameter w bzw. W
wurde angegeben.
Mit der Laufvariable I wird auf die einzelnen Textzeilen des eingegebenen Textes zugegrif-
fen. Der Zugriff auf die einzelnen Zeichen innerhalb einer Textzeile erfolgt mit Zählvaria-
blen j in *(zeich__kette [i] + j)
Die Zählvariable für die einzelnen Wörter wort_zaehl wird immer dann um 1 inkremen-
tiert, wenn ein Wortende erkannt ist.
Ein Wortende liegt immer dann vor, wenn es sich beim vorausgehenden Zeichen nicht um
ein Leerzeichen handelt «(zeich_kette [I] + J - 1)! = ’u und das gerade betrachtete Zei-
chen ein Leerzeichen oder ein \ O-Zeichen ist:
♦(zeich—kette [I] + j) == ’u’ 11 *(zeich_kette [i] + j) = = ’\O’
Wenn die Int-Variable zell auf 1 gesetzt ist - ein optionaler Parameter I bzw. L wurde an-
gegeben - dann wird die schon zuvor bestimmte Zeilenzahl zeit_zaehl am Bildschirm
ausgegeben:
lf (zell)
printf ("Xd Zeilenxn",zeil_zaehl);
12-103
12.7 SPEICHERRESERVIERUNG UND
-FREIGABE
Wir wollen jetzt eine einfache Speicherverwaltung unter Verwendung
von Zeigern erstellen; dazu benötigen wir zwei Funktionen, die wir
selbst entwerfen:
reserviere (n)
liefert einen Zeiger auf n zusammenhängende Speicherzellen für ein-
zelne Zeichen (char = 1 Byte).
Beispiel:
zeig = reserviere (80)
Dieser Befehl besagt: "Stelle einen zusammenhängenden Speicherbe-
reich von 80 Bytes zur Verfügung und speichere die Anfangsadresse
dieses Bereichs in zeig.
frei (zeig)
gibt einen mit reserviere reservierten Speicherplatz wieder frei.
Es sollen über Bildschirm Textzeilen eingelesen und in einem zusammenhängenden Spei-
cherbereich gesichert werden. Danach sollen diese Zeilen bei Angabe des optionalen Pa-
rameter - x in umgekehrter Reihenfolge, sonst in der eingegebenen Reihenfolge wieder am
Bildschirm ausgegeben werden.
Wird der optionale Parameter -n angegeben, so sind die Textzeilen bei der Ausgabe mit
Nummern zu versehen.
Bei der Ausgabe soll auf den reservierten Speicherplatz zugegriffen werden. Nach der
Ausgabe soll der reservierte Speicherplatz wieder freigegeben werden.
Bei der Lösung dieses Problems wollen wir nach dem Top-Down-Verfahren vorgehen:
12-104
Struktogramm für main:
•nueeer=0
(rueckwaerts«O
•richtige
(zeigerxargvEOJ*!
\0 , zeiger**)
1 rueckwaerts=1
'break
(nueeersl
'break
'default
Ausgabe: nicht erlaubte Paraeeterangabe
!richtig=O
•break
if (richtig ‘8 1)
N (
Ausgabe: erlaubte Parameter 'Aufruf einer Funktion, die die Zeilen einliest und sichert
sind : -x -n 'Diese Funktion soll die Anzahl der eingegebenen Zeilen liefern 1
'Ausgabe: Prograee abgebroch. '(Speichern der gelieferten Zeilenzahl m der variablen
• (fehlerh. Para«.) I zell.zahl ) ( 1 ) •
• I if (zeil zahl>0) •
• ! J N •
• 'Ausgeben der Zeilen entsprech.'Ausgabe: Text konnte - wegen '
1 'den angeg. Option. Parae.( Z )' Speicherplatznangel- 1
! ( ! nicht zusaeeennaengend'
( (Freigeben des reservierten 1 gespeichert werden '
• 'Speicherplatzes ( 3 ) (Ausgabe: Prograee abgebrochen '
Wir haben zunächst eine grobe Programmstruktur entworfen, um das Gesamtproblem dar-
zustellen; es bleiben aber noch 3 Teilaufgaben zu lösen: 1 2 3
(1) Aufruf einer Funktion, die die Zeilen einliest und sichert. Diese Funktion soll die Anzahl
der eingegebenen Zeilen liefern und in der Variablen zeil_zahl speichern.
(2) Ausgeben der Zeilen entsprechend den angegebenen optionalen Parametern.
(3) Freigeben des reservierten Speicherplatzes.
12-105
Wir wollen nun eine Lösung für Problem (1) finden:
•zell anzani=O
i
^Ausgabe: Geben Sie Ihren Text ein I
•while (Zeile vorhanden und zeil_anzahl<MAXZEILEN) I
I 'Zelle einlesen •
I I !
' .‘Sichern der Zelle und «erken der Anfangsadresse '
• l( zelg=reservlere(laenge*1) ) 1
i ----------------------------------------------------------------♦
I 1 if (zelg==NULL) •
! I J N !
• !return(-1> 'Kopiere Zeile nach zeig
i +-------------------------strcpy(zeig,zeile) ) '
I • I I
! • 'Merke Anfangsadresse von zeig *
I ‘ »in eine» eigenen Vektor '
II !( zell_zetger[zell_anzahl++]=zeig )!
•return(zeil_anzahl)
Bei dieser Programmstruktur ergeben sich zwei Aufgaben, die sich nicht durch eine C-An-
weisung realisieren lassen:
(1.1) Zeile einlesen
(1.2) Sichern der Zeile und merken der Anfangsadresse
Wir können die bisherigen Entwurfsschritte zur Aufgabenlösung in einem Baum angeben:
12-106
Eine Lösung des vorgegebenen Problems wurde also nach dem top-down-Verfahren ent-
worfen; das Umsetzen in die Programmiersprache C erfolgt nun in umgekehrter Reihenfol-
ge, d. h. bottom up = "von unten nach oben".
Zunächst wollen wir also eine Realisierung als Struktogramm und Programm für die Aufga-
be (1.11 Zeile einlesen finden:
Struktogramm zu (1.1):
»zaehl=0 I
• »
♦ — — — — — — — — ~ — — — — — *
•while (—grenze>0 && (zeich=getchar())•* ‘\n > 1
• -------------------------------------------------------——
' 1zelch_kettCzaehl*+]szelch I
’zeich kett[zaehll= \0‘ !
i ~ •
'return(zaehl> '
-----------------------------------------------------------♦
C-Funktion in Datei eingab.c:
•include <stdio.h>
zell einlestzeich kett,grenze) /♦ liest von Bildschirm eine Zelle nach •/
char zelchkettl]; /• zeich_Kett und liefert die Anzahl •/
int grenze; /• der Zeichen dieser Zeichenkette •/
(
int zaehl=O;
char zeich;
while (—grenze>0 && (zeich-getcnart))’z\n‘)
zeich_ketttzaehl**]=zeich;
zeich_kettezaehl]s•\0‘•
return(zaehl);
)
Diese Funktion wurde in einer eigenen Programmdatei eingab.c untergebracht, um sie
losgelöst von den anderen Teilaufgaben übersetzen und austesten zu können.
Mit Hilfe des Linkers binden wir dann später diesen Modul eingab, den Modul, der die
Funktion main enthält, und eventuell weitere Module zu einem Gesamtprogramm zusam-
men.
Das Modulkonzept ist sehr vorteilhaft, da man Teilprogramme losgelöst vom Gesamtpro-
gramm übersetzen kann.
Nun zur Realisierung der Teilaufgabe (1.2) Sichern der Zeile
12-107
C-Funktion in Datei speich.c:
tdefine RES_GROESSE 5000 /• verfuegöarer Platz •/
«define NULL 0
static char res_buffer(RES_GROES5E3; /• Speicherplatz fuer reserviere •/
static char •res_zeig=res_öuffer; /• naechste freie Position •/
char »reservieretni /• Diese Funktion liefert einen Zeiger auf einen • /
int n; /♦ zusaemenhaengenden Speicherplatz fuer n Zeichen ♦/
<
lf (resze1g+n<=res_öuffer+RES_SROESSE) { /• Passen n Zeichen noch • /
resjzeig+sn; ~ “ /»in den zusaemenhaengend.♦/
retürnlres zeig-n); /• alter Zeiger*/ /• Speicherplatz •/
)
eise
return(NULL);
)
res__butter und res__zeig werden hier modulglobal deklariert, so daß sie zwar in allen
Funktionen des Moduls speich.c bekannt sind, aber außerhalb dieses Moduls nicht auf
sie zugegriffen werden kann. Es ist notwendig, diese beiden Variablen als static zu dekla-
rieren, damit beim Verlassen dieses Moduls die berechneten Werte der Variablen bis zum
Wiedereintritt erhalten bleiben.
Mit static char res_butter [RES_GROESSE]; wird ein zusammenhängender Spei-
cherplatz von 5000 Bytes (RES_GROESSE hat den Wert 5000) reserviert:
Mit der Deklaration
static char *res_zeig = res_butter;
wird der Zeiger res_________zeig initialisiert; res____zeig wird auf das erste Element des Vektors
res____butter positioniert:
12-108
res—butter
res__zeig
Haben wir beispielsweise die Textzeile HAUS am Bildschirm eingegeben, so wird die
Funktion reserviere (5) aufgerufen; reserviere soll nun Speicherplatz für die 5 Bytes
HAUSYO reservieren.
Dazu wird zunächst geprüft, ob in res__butter noch genug Platz vorhanden ist:
res__zeig + n <= res_butter + RES_GROESSE
um n (hier n = 5) Zeichen speichern zu können.
Wenn ja, dann wird zunächst res_zeig um n Position weitergesetzt res_zeig +«n; und
dann an die aufrufende Funktion die Anfangsadresse des momentan reservierten Spei-
cherplatzes zurückgeliefert: return (res_zeig - n);:
> reserviert
> frei
res_zeig zeigt also nach Verlassen der Funktion reserviere auf das erste Speicherele-
ment des noch frei verfügbaren Platzes, so daß beim nächsten Aufruf der Funktion reser-
viere ab dieser Position Speicherplatz reserviert wird. An die aufrufende Funktion wird im-
mer der alte Wert von res_zeig. d. h. return (res_zeig - n); und nicht der neue Wert
zurückgegeben.
12-109
Reicht beim Aufruf der Funktion reserviere der noch frei verfügbare Speicherplatz nicht
aus, um die geforderten n Bytes zu reservieren, d. h. es ist
res_zeig + n > res__buffer + RES__GROESSE
so wird ein NULL-Zeiger an die aufrufende Funktion zurückgegeben.
Da im Normalfall C-Zeiger niemals den Wert 0 besitzen, zeigt ein NULL-Zeiger an, daß
eine Abweichung vom Normalfall vorliegt. Hier also wird durch den NULL-Zeiger signali-
siert, daß die Forderung, n Bytes zu reservieren, nicht erfüllt werden konnte. Normalerwei-
se dürfen int-Werte nicht Zeigervariablen zugewiesen werden; 0 bildet die Ausnahme.
Die Funktion reserviere liefert also immer - wie wir sehen - einen Zeiger an die aufrufen-
de Funktion return (res_zeig -n); oder return (NULL); was auch durch die Definition
char «reserviere (n)
als Funktionskopf ausgedrückt wird.
Auf den vorhergehenden Seiten wurden Realisierungen zum Teilproblem (1) angegeben.
Als nächstes finden wir eine Lösung zum Teilproblem (3):
Wir werden eine Funktion frei entwerfen, die ebenfalls zum Modul speich gehören soll
und Speicherplatz ab der übergebenen Adresse (aktueller Parameter zu zeig) freigibt.
Die vollständige Datei speich.c weist dann folgenden Inhalt auf:
»define RES 6R0ESSE 5000
fdefine NULL 0
static char resbufferCRESGROESSE);
static char •reszelg=res_buffer;
/• verfuegbarer Platz •/
/• Speicherplatz fuer reserviere •/
/• naechste freie Position •/
char «reserviere(n) /• Diese Funktion liefert einen Zeiger auf einen ♦/
int n; /• zusaaaenhaengenden Speicherplatz fuer n Zeichen •/
<
if (res zeig+n<=res öuffer«RES GROESSE) < /♦ Passen n Zeichen noch •/
res"zeig«=n; ’ ’ /«in den zusaaaenhaengend.•/
retürntres zeig-n); /• alter Zeiger«/ /• Speicherplatz •/
)
eise
return(NULL);
>
frei(zeig)
char «zeig;
<
if (zeig>=res_öuffer && zeig<res_buffer«RES_GROESSE)
res zeig=zeig;
>
Nun ist noch eine Realisierung für die Teilaufgabe (2) Ausgeben der Zeilen entsprechend
der angegebenen Option zu finden; sie soll in Form eines Struktogramms angegeben wer-
den:
12-110
1 ausgab_zeilen(zeilze1ger,zell anzahl) •
• char *zell_zelger[1; " »
• int zeil_änzahl; •
if (rueckwaerts) •
’ J N •
' zell_zeiger+=zeil_anzahl-1 ’rlchtung=l •
1richtung=-1 “ ♦--------------------------------------------+
'zeil_nua»erx(rueckwaerts) ? zeil_anzahl : 1
•while (zaehl++«=zeil_anzahl) i
I 4 —- — 4
! ! if (nueeer) •
! ! J N •
• 4—————————————————————————————————————————————————44
• 'Ausgabe von zeil.nueeer • •
! 1zeil_numeer+=richtung I t
'Ausgabe: "Xs\n",*zei1 zeige
'zeil_zeiger*=richtung“
Nun können wir unter Zuhilfenahme schon zuvor entwickelter Struktogramme und Pro-
gramme das gesamte C-Programm angeben:
Datei speich.c
idefine RES.GROESSE 5000
idefine null 0
/• verfuegöarer Platz •/
static char res_bufferCRES_GROESSEJ;
static char •res_zeig = res_buffer;
/• Speicherplatz fuer reserviere •/
/• naechste freie Position •/
char «reserviere(n) /• Diese Funktion liefert einen Zeiger auf einen ♦/
int n; /• zusameenhaengenden Speicherplatz fuer n Zeichen ♦/
(
If (res zeig+n<=res buffer+RES_GROESSE) € /• Passen n Zeichen noch •/
res*zeig+=n; ” “ /«in den zusaweennaengend.•/
retürn(res zeig-n); /• alter Zeiger*/ /• Speicherplatz •/
return(NULL);
>
frei(zeig)
char *zeig;
(
if (zeig>=res_buffer && zeig<res_buffer+RES_6R0ESSE)
res.zeigszeig;
12-111
Datei eingab.c
«include <stdlo.h>
zeil.elnles(zeich kett,grenze) /• liest vorn Bildschirm eine Zeile nach • /
char zei ch ketü 3; /• zeich.kett und liefert die Anzahl ♦/
int grenze; /• der Zeichen dieser Zeichenkette •/
<
int zaehlxQ;
char zeich;
while (—grenze>0 && (zeichsgetchar())!s*\n*)
zeich.kettlzaehl**3 = zeich;
zeich.kettlzaehl3s \O";
return(zaehl);
}
Datei main.c:
•define MAXZEILEN 50 /• maximal 50 Zeilen koennen eingegeben werden
•define MAXZEICHEN BO /• pro Zeile maximal BO Zeichen •/
•define NULL 0
int nummersO, rueckwaerts=O;
main(arge,argv)
int arge;
ehar »argvCD;
(
char -zell vektorCMAXZEILEN3, »zeiger;
int richt”ig = 1, zeil.zahl»O;
while (--argc>0 4B <»**argv)C03==‘-')
for (zeiger=argvl□)♦) ; »zeiger •» \0' ; zeiger**)
switch(»zeiger) C
case X’:
case x :
rueckwaerts= 1;
break;
case ’N :
case n :
nummerxl;
break;
default:
printf("\n\nnicht erlaubte Parameterangabe: Xc\n’,»zeiger) ;
richtigsQ;
break;
>
if (richtig 1) <
printf("Xnerlaubte Parameter sind : -x -n\n");
printfC\n\nProgramm abgebrochen (fehlerhafte Parameter) !\n\n");
)
eise <
zell zahl=lies zeilen(zeil vektor);--------------------------------
if (zeil_zahi>Ö) <
------ ausgab zeilen(zei1.vektor,zei1 zahl);
frei(ze11.vektorCOT);
pr intf("\n\nText konnte - wegen Speicherplatzmangel- nicht \n");
printf(“zusammenhaengend gespeichert werden ‘\n\n\n“);
printf("\nProgramm wurde abgebrochen •\n•) ;
lies zeilen(zeil zeiger)
cnar »zell zeigerCl;
C
char zeileCMAXZEICHEN1, »zeig;
char »reserviere«);
int zeil.anzahlsQ, laenge;
12-112
printf(•\n\nBeöen Sie Ihren Text ein ’\n\n");
while ((laenge=zeil_elnles(zeile.MAXZEICHEN))>0 zeil_anzahl<NAXZEILEN) {
zeig*reserviere ("laenge*!) ;
if (zeig==NULL)
return(-!);
eise <
strcpy(zeig,zelle);
zeil_zeigertzeil_anzahl**]=zeig;
>
)
return(zeil_anzahl);
ausgab.zeilen(zell.zelger,zeil.anzani)
char " •zeil.zeigert];
int zeil änzahl;
int richtung, zell_num«er, zaehl=O;
if (rueckwaerts) (
zeil_zeiger+=zeil_anzahl-l;
richtungs-1;
richtung»!;
zeil_nuw«ers(rueckwaerts) ? zeil_anzanl : 1;
while (zaehl**'=zell_anzahl) (
if (nunter) {
printf ("Xd:" ,zeil_nu»tner) ;
zeil nuater*=richtung;
>
printf("Xs\n“,•zeil_zeiger);
zeil zeiger+=richtung;
)
Zur Reservierung und Freigabe von Speicherplatz können auch die fol-
genden Funktionen aus der Standardbibliothek verwendet werden:
calloc (n, s)
Mit dem Aufruf dieser Funktion wird ein zusammenhängender Speicher-
bereich reserviert, der groß genug ist, um n Ojekte mit je s Bytes aufzu-
nehmen; zusätzlich wird der so reservierte Speicherplatz überall mit
Nullen vorbesetzt.
calloc liefert dann einen Zeiger auf den Anfang des reservierten Spei-
cherbereichs; wenn diese Funktion nicht genügend freien Speicherplatz
reservieren kann, so liefert sie einen NULL-Zeiger.
malloc (n)
Diese Funktion reserviert einen zusammenhängenden Speicherbereich
für n Bytes; im Gegensatz zu calloc besetzt malloc den reservierten
Speicherbereich nicht mit Nullen vor. malloc liefert an die aufrufende
Funktion die Anfangsadresse (Zeiger) des reservierten Speicherbe-
reichs; konnte die Funktion malloc nicht genügend zusammenhängen-
den Speicherplatz reservieren, so liefert sie einen NULL-Zeiger an die
aufrufende Funktion.
12-113
free (p)
Mit dieser Funktion kann ein mit calloc oder malloc reservierter Spei-
cherbereich wieder für andere Zwecke freigegeben werden. Dabei wird
der Speicherbereich ab der Adresse, die der Zeiger p enthält, freigege-
ben. Sie sollten mit free nur Speicherplatz freigeben, der zuvor mit cal-
loc oder malloc reserviert wurde; andernfalls können schwerwiegende
Fehler auftreten.
Wir hätten also beim vorhergehenden Beispiel die Funktionen reser-
viere und frei nicht benötigt und auch ohne diese beiden Funktionen
Speicherplatz reservieren und freigeben können:
Speicherplatz reservieren
anstelle von: zeig = reserviere (laenge + 1);
hätten wir: zeig = malloc (laenge + 1); angeben können
Speicherplatz freigeben
anstelle von: frei (zeil_vektor [O]);
hätten wir: free (zei(_vektor [0]); angeben können
Anstelle von char «reserviere müßten wir dann char «malloc dekla-
rieren.
12.8 ZEIGER AUF FUNKTIONEN
Ein Zeiger kann nicht nur auf Variable, sondern auch auf Funktionen po-
sitioniert werden; ein Zeiger, der auf eine Funktion gerichtet wird, zeigt
auf den Anfang des Funktions-Codes.
Beispiel 23
Wir wollen ein C-Programm erstellen, bei dem der Benutzer steuern kann, ob er sich das
Volumen eines Zylinders oder einer Kugel berechnen läßt.
HINWEIS: Zylindervolumen: r2 * n * hoehe
Kugelvolumen: 4/3 r3 ♦ n
12-114
C-Programm:
Unclude <stdio.h>
idefine PI 3.141592654
«am ()
C
float radius, hoehe;
float berechn, zyl_vol(), kug_vol();
char wähl;
do (
printf<"XnVolumen eines Zylinders oder einer Kugel ( Z/K )
wahl = getchar ();
if (wähl'; Z && wahl'=K ) (
printf("XnXnXnFaIsche Eingaoe1\n\n"1;
pnntfCSie duerfen nur Z oder k eingeben !\n\n-);
printf("Wiederholen Sie Ihre Eingabe f\n\n\n">;
)
} while (wahl'.= Z && wahl's’K );
printf("\n\n\nGeben Sie den Radius ein ’Xn");
scanf("Xf",&radius);
if (wahl== Z ) (
printf("\n\nGeben Sie die Hoehe Ihres Zylinders ein !\n*>;
scanf("Xf",Shoehe);
printf ("\n\nDas Volumen dieses Zylinders ist Xf\n",hoehe*berech(radius,zyl_vol));
printf("\n\nDas Volumen dieser Kugel ist Xf\n“,berech(radlus,kug_vol)1;
3)
Mit dem Aufruf berech (radius, zyL_vol); wird an berech die Adresse der Funktion
zyl_vol übergeben.
Mit dem Aufruf berech (radius, kug___vol); wird an berech die Adresse der Funktion
kug_vol übergeben.
Mit der Deklaration float («welch_funktionX ); in der Funktion berech wird ein Zeiger
welch___funktion auf eine Funktion vereinbart, die einen float-Wert liefert.
Mit dem Aufruf berech (radius, zyL-Vol); wird der Zeiger welch_funktion auf den An-
fang des Codes von Funktion zyl_vol positioniert:
HINWEIS: Bei TurboC und QuickC 2) entfernen und dafür 3) bei 1) einfügen.
12-115
Mit dem Funktionsaufruf berech (radius, kug_____vol); dagegen wird der Zeiger
welch—funktion auf die Anfangsadresse von kugu_vol gesetzt:
1. Befehl
der Funktion
iyl_vol
2. Befehl
der Funktion
wo!
Zeiget-
ebene
VanaWen-
ebene
2 Befehl
der Funktion
kug vol
Werte-
ebene
Hätten wir bei der Deklaration von welch_funktion in der Funktion berech die Klam-
mern weggelassen:
float «welch—funktion();
dann hätte dies bedeutet, daß welch—funktion eine Funktion ist, die einen Zeiger auf ei-
nen float-Wert an die aufrufende Funktion zurückliefert.
Den Unterschied zwischen
float («welch—funktlonX);
und
float «welch—funktion();
sollten Sie sich klarmachen.
/• welch__funktion = Zeiger auf eine
Funktion, die einen float-Wert liefert ♦/
/* welch___funktion = Funktion, die einen
Zeiger auf einen float-Wert liefert ♦/
Will man nun über den Zeiger welch_funktion eine Funktion aufrufen, so muß man
«welch—funktion angeben, da welch________funktion ein Zeiger und folglich
«welch—funktion die eigentliche Funktion ist.
12-116
Wenn z. B. welch__funktion auf die Funktion zyl_vol zeigt, dann entspricht die Anwei-
sung
return (rad*(*welch__funktionXrad));
der Anweisung
return (rad * zyl_vol (rad));
float ♦(♦welch—funktionX);
Hier wäre welch___funktion ein Zeiger auf eine Funktion, die einen Zeiger auf einen
float-Wert liefert.
12-117
13-1
STRUKTUREN 13
Hier lernen wir nun eine Methode der Programmiersprache C zum
Strukturieren von Daten kennen.
Unter einer Struktur versteht man die Zusammenfassung von einer oder
mehreren Variablen, die untereinander auch unterschiedliche Datenty-
pen besitzen dürfen, zu einer Einheit, die sich dann durch einen Namen
ansprechen läßt. Den Strukturen in C entsprechen in der Programmier-
sprache PASCAL die RECORDS.
13.1 EINFACHE STRUKTUREN
Eine Struktur verbindet mehrere verschiedene Objekte zu einer Einheit.
Ein typisches Beispiel wäre die Zusammenfassung der Daten eines Stu-
denten aus einer Studentenkartei zu einer einzigen Einheit, die wir Stu-
dent nennen.
Eine solche Zusammenfassung wird in C folgendermaßen ausgedrückt:
struct Student (
char naaatiO];
char vornaeetZOli
int postleit.zahlt
char wohnortCZO];
char strass.nr[20]|
char geburt_dat(9);
lang int eatrxkel.nrj
int notenClOlt
h
/• Nachname • /
/• Vorname • /
/• Postleitzahl • /
/• Wohnort • /
f Strass*, Hausnuemer • /
!• Geburtsdatum tt.es.jj • /
f hatr1keInuamer • t
f Pruefungsnoten
Das Schlüsselwort struct ist zu Beginn einer Strukturdefinition anzuge-
ben.
Eine Strukturdefinition besteht aus einer Liste von Deklarationen, die
mit{... }zu klammern sind.
Mit einer solchen Strukturdefinition wird noch kein Speicher reserviert,
sondern nur der Aufbau einer Datengruppe beschrieben.
Der Compiler merkt sich unter dem Namen Student lediglich den Auf-
bau einer solchen Dateneinheit:
13-3
relative Adresse (in Bytes)
(Offset):
□ i name
I ...
20 I vorname
I ...
40 i postlelt_zahl
41 I
int beansprucht 2 Bytes
42 I wohnort
I ...
62 I strass nr
l
82 l gehurt dat
I ...
91 • matrlkel nr
I ..
93 I noten
I ...
114 I
long int beansprucht 4 Bytes
da int 2 Bytes benoetigt, werden
fuer int noten[10] 2*10 » 20
Bytes vorgesehen.
Mit dieser Strukturdefinition wurde ein neuer Datentyp mit Namen Stu-
dent definiert; dieser Datentyp benötigt 115 Bytes.
Wird nun z. B. eine Variable stu_daten von diesem Datentyp benötigt,
so ist sie mit
struct Student stu____daten;
zu deklarieren.
Diese Vereinbarung veranlaßt den Compiler, eine Variable mit dem Na-
men stu____daten anzulegen und dafür die Datenstruktur Student als
Datentyp zu verwenden.
Unter dem Namen stu_daten werden jetzt 115 Bytes im Speicher re-
serviert. Die Variable stu_daten setzt sich dann aus 8 Elementen zu-
sammen:
name
vorname
postleit_zahl
wohnort
strass__nr
geburt_dat
matrikel__nr
noten
13-4
Sie sollten sich klarmachen, daß mit dem Ausdruck
struct Student <
char n*a«[20]j
char vornaae(20)|
int postleit.zah)।
char HOhnortC20)|
char strass_nrC2O3|
char geburt_dat(9]j
long int aatrikal.nr।
int notentlOli
/• Nachnaae •/
/ ♦ Vornaae • /
/• Postleitzahl «/
/♦ Wohnort • /
/• Strasse, Hausnuaaer •/
/♦ Geburtsdatua tt.aa.jj ♦/
/• Hatrikelnuaaer
/• Prue*ungsnoten ♦/
lediglich eine Strukurbeschreibung angefertigt wurde, mit welcher noch
kein realer Speicherplatz reserviert wird.
Eine wirkliche Speicherplatzreservierung erfolgt erst durch die Deklara-
tion
struct Student stu_daten;
Es ist allerdings auch möglich, eine Variable sofort bei der Strukturbe-
schreibung zu deklarieren:
> stu.daten)
«truct studant <
char naaeC20)| /• Nachnaaa • /
char vornaaeC201| /• Vornaae • /
int post 1eit.zahlj /a Postlei tzahl • /
char wohnort(201; /• Wohnort • /
char strass.nrC2O3; /• Strasse, Hausnuaaer • /
char geburt.dat(93| /• Saburtsdatua tt.aa.jj • /
long int aatrikel.nrj /• Hatrikelnuaaer •/
int notanC101| Pruefungsnoten • /
Mit dieser Konstruktion wurde also die Strukturbeschreibung:
struct Student {
};
und die Deklaration: struct Student stu_daten; zusammengefaßt.
Will man nun auf eine einzelne Strukturkomponente zugreifen, so ist fol-
gende Vorgehensweise zu wählen:
Strukturvariable.Strukturkomponente
Dabei bewirkt der Punkt-Operator (.) den Abstieg um eine Ebene vom
Strukturnamen zu einer Strukturkomponente.
Als Beispiel wollen wir den Strukturkomponenten name, geburt_dat
und noten Werte zuweisen:
strcpy (stu_daten.name, "Mayer”);
strcpy (stu_daten.gebürt_dat, ”07.12.59”);
stu__daten.noten [0] = 2;
stu_daten.noten [3] = 4;
13-5
An diesem Beispiel wird ersichtlich, daß Strukturkomponenten wie ein-
fache Variablen des entsprechenden Datentyps behandelt werden.
Da es sich bei stu_daten.name und stu__daten.gebürt__dat um
char-Vektoren handelt, können nur den einzelnen Vektorelementen di-
rekt Werte zugewiesen werden:
stu_daten.name [0] = ’M’;
stu_daten.name [1] = ’a’;
stu_daten.geburt__dat [0] = ’0’;
stu_daten.geburt__dat [11 = *7’;
Da diese Vorgehensweise etwas langwierig ist, haben wir auf die Funk-
tion strcpy aus der Standardbibliothek zurückgegriffen; wie Sie sich er-
innern, kopiert strcpy (x, y) die Zeichenkette y in den char-Vektor x.
Betrachten wir aber nochmals genauer den Punkt-Operator: Der Com-
piler addiert zur Adresse, bei der die Struktur beginnt, die relative An-
fangsadresse des entsprechenden Strukturelements.
Wir nehmen an, daß »tu_daten bei Adresse 1600 beginnt: stu_daten.Wohnort wird
dann umgesetzt in
Adresse von stu_daten 1600
+ relative Adresse (Offset) von wohnort 42
Adresse von stu__daten.wohnort 1642
Es ist nicht notwendig, jeder Strukturbeschreibung einen Namen zu geben. Auf die Angabe
eines Namens kann verzichtet werden, wenn schon bei der Strukturbeschreibung alle Va-
riablen, die diese Struktur als Datentyp besitzen sollen, deklariert werden:
struct {
long auftrag_nummer;
float Einzahlung;
float auszahlung;
) firiaj, flr«a_2;
/• kein Strukturname «/
Mit dieser Deklaration werden 2 Strukturvariablen firma_____1 und ffirma__2 angelegt, wel-
che beide den gleichen Aufbau besitzen.
Da an die Struktur
long auftrag numner;
float Einzahlung;
float auszahlung;
kein Name vergeben wurde, kann auf diese Strukturbeschreibung bei anderen Deklaratio-
nen natürlich nicht zugegriffen werden.
13-6
Es können auch Strukturen definiert werden, deren Komponenten wie-
derum Strukturen bilden.
Beispiel 1
Von 10 Personen einer Firma sollen
Name
Vorname
Geburtsdatum
Einstelldatum
über Bildschirm eingelesen und dann die älteste der Personen am Bildschirm ausgegeben
werden; bei Gleichaltrigen soll die längere Firmenzugehörigkeit entscheiden.
Hier geben wir folgende Strukturbeschreibung
struct datu» <
int tag;
int «onat;
int jahr;
struct person <
char na«eC2D];
cnar vornamet20J;
struct datu« geb;
struct datu« anst;
Wir sehen, daß die Struktur person zwei Komponenten besitzt: geb und anst, deren Da-
tentyp wiederum eine Struktur, hier datum ist. Es ist also möglich, mehrere Strukturen
ineinander zu schachteln.
Wenn wir nun mit struct person eing, aelt; 2 Strukturvariablen deklarieren, so kann auf
deren einzelne Strukturkomponenten wie folgt zugegriffen werden:
eing
eing.name
eing.vorname
eing.geb
eing.geb.tag
eing.geb.monat
eing.geb.jahr
eing.anst
eing.anst.tag
eing.anst.monat
eing.anst.jahr
aelt
aelt.name
aelt.vorname
aelt.geb
aelt.geb.tag
aelt.geb.monat
aelt.geb.Jahr
aelt.anst
aelt.anst.tag
aelt.anst.monat
aelt.anst.jahr
13-7
C-Programm:
linclude <stdio.h>
struct datu* (
int tag;
int aonat;
int Jahr;
struct person {
char na*e(2O3;
char vornameCZOJ;
struct datun geb;
struct datun anst;
struct person eing, aelt;
long aelt_geb_zahl=999999, aelt_anst_zahl«999999;
long eing_geb_zahl, elng_anst_zähl;
aaln ()
<
int 1, J, k;
for (i=O,J=O,k=O ; i<10 ; j-O,k«O,l*4) (
printf("\n\nEingabe der Xd.Person\n\n",1+1);
pr1ntf("Naae 7\n");
do (
eing.naneCj+*]=getchar(I;
) while (elng.naaetj-1);
eing.naaetj-11«’\0*;
printf("XnVornaae ’Xn");
do (
eing.vornaaeCk**]3getcnarO;
) while (eing.vornametk-11•='\n*);
eing.vornaaeCk-1J«’ \0 ;
printf("XnGeburtsdatua (tt.aa.JJ) ?\n*>;
scanf( "Xd.Xd.Xd",&eing.geb.tag,Seing.geb.monat,Seing.geb.Jahr);
printf("XnAnstelldatua (tt.mm.jj) ?\n");
scanf(“Xd.Zd.Xd”,Seing.anst.tag,Seing.anst.monat,Seing.anst.Jahr);
getcharo;
eing_geb_zahl=(long)((eing.geb.jahr«100)+eing.geb.monat)«lOO*eing.geb.tag;
eing“anstzahlx(long)((eing.anst.Jahr«100)♦eing.anst.aonat)«100«eing.anst.tag;
if (eing_geb_zahl<aelt_geb_zahl)
uaspeicherO ;
if (eing_geb_zanl==aelt_geb_zahl && elng_anst_zahl<aelt_anst_zahl)
umspeieher();
printf("\n\n\n\nVon den angegebenen Personen wurde ausgewaehlt:\n\n");
printf("Nane : Xs\n",aelt.naae);
printf("Vornaae : Xs\n",aelt.vornaae);
printf("Geburtsdatum : X02d.X02d.",aelt.geb.tag,aelt.geb.aonat);
printf("X02d\n",aelt.geb.jahr);
printf("AnstelIdatua : X02d.X02d.•,aelt.anst.tag,aelt.anst.nonat);
printf(“XO2d\n",aelt.anst.jahr);
uaspeicher ()
<
strcpy(aelt.naae,eing.naae);
strcpy(aelt.vornaae,eing.vornaae);
aelt.geb.tag=eing.geb.tag;
aelt.geb.monat<eing.geb.aonat;
aelt.geb.jahrzeing.geb.jahr;
aelt.anst.tagtetng.anst.tag;
aelt.anst.monat=eing.anst.aonat;
13-8
aelt.anst.jahr-eing.anst.jahr;
aelt_geö_zahl=eing_geD_rahl;
aeltanstzahl-einganst_zahl;
Erläuterung:*
Im Programmteil
for (i = 0,j*0,M0 ; i<10 ; jxO,k=Ot !♦♦) (
printf("\n\nEingaöe der Xd.PersonxnXn*,1*1);
printf("Naue
do (
eing.nametj++3=getchar();
) while (eing.na*e[j-13'=‘\n’);
eing.naaelj-1]«'\0‘ ;
printf(“XnVornawe ?Xn"J;
do <
eing.vornamelk**J=getchar ();
} while (eing.vornameCk-i 1•= Xn );
eing.vornawet k-1 3« XO ;
printf("XnGeburtsdatuw (tt.an.jj) ?\n");
scanf("Xd.Xd.Xd",&eing.geb.tag,&eing.geb.«onat,&eing.geb.jahr);
printf("XnAnstelldatu« (tt.mm.jj) ?\n");
scanf("Xd.Xd.Xd",leing.anst.tag,Seing.anst.Bonat,ieing.anst.Jahr);
getcnar();
eing geo zahl=(long)((eing.geb.jahr-100)>eing.geh.«onat)*100+eing.ged.tag;
eing^anstzahl;(long)((eing.anst.jahr»i0Q)*eing.anst.»onat)*l00*eing.anst.tag;
if (eing_geb_zahl<aelt_geb_zahl)
umspelcher();
if (eing_geb_zahl==aelt_geb_zahl && emg_anst_zahl<aelt_anst.zahl)
«Speicher();
werden zunächst die Daten der 10 Personen emgelesen:
Der Name wird im Teil:
printf("Nawe ?\n");
do (
eing.nametj + *J-getchar ();
) while (eing.nameCj-1J!sXn );
eing.naweC j-1] = *\0‘ ;
in das Feld eing.name eingelesen und das Ende der Zeichenkette durch \O-Zeichen ge-
kennzeichnet.
Der Vorname wird im Teil:
printf(“XnVorname ?\n");
do (
eing.vornaneCk+ +3 = getchar <);
} while (eing.vorna«e[k-1l’s’Xn’);
eing.vorna«e[k-11 = *\0' ;
in das Feld eing.vomame eingelesen und das Ende der Zeichenkette ebenfalls durch \O
gekennzeichnet.
HINWEIS: *) Die Eingabe von Namen und Vornamen sollte auch eine Prüfung auf Vek-
torlänge < 20 enthalten.
13-9
Das Geburtsdatum wird im Teil:
printf("\nGeburtsdatum (tt.mm.jj) ?\n");
scanf("Xd.Xd.Xd",&eing.geb.tag,fteing.ged.monat,&eing.geb.Janr);
eingelesen und zwar
der Tag in die Strukturkomponente eing.geb.tag,
der Monat in die Strukturkomponente eing.geb.monat
das Jahr in die Strukturkomponente eing.geb.jahr
Das Anstelldatum wird im Teil:
printf("XnAnstelldatum (tt.mm.jj) ’Xn”);
scanf ('‘Xd.Xd.Xd",&eing.anst. tag,lemg.anst.monat,ieing.anst. Jahr);
cr=getchar();
eingelesen, und zwar
der Tag in die Strukturkomponente eing.anst.tag,
der Monat in die Strukturkomponente eing.anst.monat
das Jahr in die Strukturkomponente eing.anst.jahr.
Mit der Anweisung getchar () wird das RETURN-Zeichen verarbeitet, das bei der Einga-
be des Anstelldatums als abschließender Tastendruck anfiel.
Nehmen wir an, daß sich bei der Eingabe der Daten einer Person folgender Bildschirmab-
lauf ergab:
Name ?
Malter—1
Vorname ?
Benno 1
Geburtsdatum (tt.mm.jj) ?
04.06.53-»
Anstell datum (tt.mm.jj) ?
09.07.81
13-10
dann sind die einzelnen Komponenten der Strukturvariablen eing folgendermaßen belegt:
eing.vorname
eing.geb
eing.geb.tag
eing.geb.monat
eing.gab.jahr
eing.anst
eing.anst.tag
eing.anst.monat
eing.anst.jahr
eing
eing.name
Nach dieser Eingabe wird mit
eing_geö_zahl=(long)((eing.geb.jahr*100)♦eing.geb.monat)»100*eing.geb.tag;
eing anst_zahl=(long)((eing.anst.jahr*!00)+eing.anst.monat)«100+eing.anst.tag;
das Geburtsdatum in einen long-Wert umgewandelt und dieser Wert in eing_geb_zahl
gespeichert; ebenso wird auch das Anstelldatum in einen long-Wert umgewandelt, und
dieser Wert wird in eing—anst—zahl gespeichert.
Für unsere Eingaben bedeutet dies also:
eing_geb—zahl = (long) ((53 ♦ 1OO) + 6) ♦ 100 + 4;
v v '
530604
eing_anst_zahl = (long) ((81 ♦ 1OOJ + 7)* 100 + 9;
810709
Unter CP/M-86 wäre dieses Programm so nicht lauffähig; dort müßte man folgendes an-
geben:
eing geb zahl=(((long)e1ng.geb.jahr*l00)♦eing.geb.sonat)*100 + eing.geb. tag;
eing2anst_zahl=(((long) eing .anst. jahr»100)♦emg.anst.monat)*100+eing.anst.tag;
Hier bezieht sich die cast-Operation nicht auf den Gesamtausdruck, sondern lediglich auf
den direkt folgenden Ausdruck.
Testen Sie die Implementierung von casf-Operationen anhand dieses Beispiels an Ihrem
Rechner.
HINWEIS: *) (long) ausdruck
besagt, daß der Wert von ausdruck in den Datentyp long umzuwandeln ist.
Dies ist in unserem Beispiel erforderlich, da z. B eing_geb—zahl vom Typ
long ist und die Strukturkomponenten eing.geb.jahr, eing.geb.monat
und eing.geb.tag vom Datentyp Int sind.
13-11
Mit
lf (eing_geb_zahl<aelt_geb_zahl)
umspeicher();
if (eing_geb_zahl==aelt_geb_zahl && eing.anst_zahl<aelt_anst.zahl)
umspeieher();
wird geprüft, ob die berechnete Zahl für das Geburtsdatum eing_geb_^zahl kleiner als
die bisher kleinste Zahl für ein Geburtsdatum aelt_geb_~zahl ist. Wenn ja. dann handelt
es sich bei der gerade eingegebenen Person um die älteste der bisher angegebenen Per-
sonen, und mit dem Aufruf der Funktion umspeicher werden alle Daten aus der Struktur
eing in die Struktur aelt übernommen.
Die Funktion umspeicher wird auch aufgerufen, wenn die gerade angegebene Person
das gleiche Alter wie die bisher älteste Person besitzt:
eing__geb_zahl == aelt_geb_zahl,
aber schon früher in die Firma eingetreten ist:
eing anst zahl < aelt_ansL__zahl
Da die long-Variable aelt_geb____zahl mit dem Wert 999999 initialisiert wurde, ist si-
chergestellt. daß zunächst die erste eingegebene Person in die Struktur aelt übernommen
wird, da hierfür in jedem Fall gilt: eing_geb_zahl < aelt—geb_zahl
Nach dem Durchlauf dieser for-Schleife befinden sich in der Struktur aelt die Daten der
gesuchten Person, die dann mit
printf("\n\n\n\nvon den angegebenen Personen wurde ausgewaehlt:\n\n");
printf (“Name : XsXn“,aelt.name);
printf(“Vorname : XsXnaelt.vorname);
printf("Geburtsdatum XO2d.XO2d."raelt.geb.tag,aelt.geb.monat);
printf("X02d\n",aelt.geb.jahr);
printf("Anstelldatum : XO2d.XO2d.",aelt.anst.tag,aelt.anst.monat);
pnntf("X02d\n", aelt. anst. jahr);
ausgegeben werden.
In der Funktion
umspeicher()
(
strcpy(aelt.name,eing.name);
strcpy(aelt.vorname,eing.vorname);
aelt.geb.tag-eing.geb.tag;
aelt.geb.monat=eing.geb.monat;
aelt.geb.jahr=elng.geb.jahr;
aelt.anst.tag=eing.anst.tag;
aelt.anst.monat=eing.anst.monat;
aelt.anst.jahr=eing.anst.jahr;
aelt.geb.zahlte 1nggeb.zahl;
aelt’anst.zahl=eing_anst.zahl;
werden alle Daten aus der Struktur eing in die Struktur aelt übernommen und zusätzlich
noch die Werte der Variablen eing_geb_zahl und eing_anst_zahl in den Variablen
aelt__geb_zahl und aelt_anst_zahl festgehalten.
13-12
13.2 INITIALISIERUNG VON STRUKTUREN
Es existieren zwei Möglichkeiten, Strukturen mit Werten vorzubesetzen:
1. Möglichkeit: direkt bei der Strukturdefinition
struct <
int tag;
char monatClO);
int jahr;
) jahr_tag = <1,“Januar",1965);
/• Hier wird eine Strukturvariaöle •/
/• jahrtag deklariert (Strukturdef.♦/
/• und Deklaration in einem Schritt);*/
/• Zusaetzlich wird die Strukturvariaö*/
/• noch mit werten vorüesetzt. ♦/
2. Möglichkeit: bei der Deklaration einer Strukturvariablen
struct datum {
int tag;
char monatClO);
int Jahr;
Definition einer Struktur (eigener
Datentyp) mit Namen datum
struct datum jahr_tag = <1,"Januar",1985);
/♦ Deklaration einer •/
/• Strukturvärlaölen ♦/
/♦ jahrtag vom Typ datum*/
/• und Voröesetzen dieser*/
/♦ Variaölen mit Werten */
Eine Einschränkung gilt es hier allerdings zu beachten: Es dürfen nur
global oder static definierte Strukturen im Anschluß an die Definition
initialisiert werden.
Beispiel 2
Es soll ein Programm erstellt werden, das den Zählvorgang einer digitalen Stoppuhr simu-
liert. Dazu wird vom Benutzer die Anzahl der Hundertstel Sekunden verlangt.
Möglicher Ablauf am Bildschirm:
Wieviele Hundertstel Sekunden wurden benötigt?
7033 —'
000 : 01 : 10.33
-j— J
wurde erst hochgezählt;
Uhr bleibt beim Endergebnis stehen
13-13
C-Programm:
struct uhr <
int stunden;
char std.trenn;
int Minuten;
char «in trenn;
int Sekunden;
char sek.trenn;
Int hunderstel;
struct uhr zelt s <0,*:‘,0,':*,0,’.’
main ()
(
long hund.sek, zaeni=0;
printf(“\n\nWieviele Hunderstel Sekunden wurden benoetigt ?\n");
scanf("Xld",inund sek);
printf(“\n\n\n\n\n\n\n\n “>»
do (
do <
do (
do <
printf(“XOldXc“,zeit.stunden,zeit.std.trenn);
printf ("X02dXc"»zeit.Minuten,zelt.Min.trenn);
printf("X02dXc",zeit.Sekunden,zeit.sek.trenn);
printf("X02d“,zeit.hunderstel);
printf (“\b\b\b\b\b\b\b\b\b\b\b\b");
zaehl++;
) while (♦♦zei t.hundersteKiQO && zaehl<=hund.sek);
zeit.hunderstel=O;
) while (♦♦zeit.sekunden<60 && zaehiohund.sek);
ze1t.Sekunden=0;
> while o+zeit.«inuten<60 && zaehl<=hund_sek);
zeit.Minuten=0;
} while o+zeit.stunden<1□□□ && zaehl<=hund_sek);
printf(*\n\n*);
Erläuterungen:
Die Strukturbeschreibung
struct uhr <
Int stunden;
char std.trenn;
int Minuten;
char mln.trenn;
int Sekunden;
char sek.trenn;
int hunderstel;
wird global angegeben. Mit der globalen Deklaration:
struct uhr zeit « ,0,,0,;
wird eine Strukturvariable zeit vereinbart, die schon bei der Deklaration mit Werten vort~
setzt wird:
13-14
zeit.stunden
zeit.std_trenn
zeit.minuten
zeit.min__trenn
zeit.Sekunden
zeit.sek_trenn
zeit.hundertstel
Im Programmteil
printf("XO3dXc"»zeit.stunden,zeit.std trenn);
printf("XO2dXc"»zelt.Minuten,zelt.»injtrenn);
printf(“XO2dXc",zeit.Sekunden,zeit.sei trenn);
printf("XO2d"»zeit.nunderstel);
printf ("\b\b\b\b\b\b\b\b\b\b\b\b");
zaehl**;
werden die Werte der einzelnen Strukturkomponenten am Bildschirm ausgegeben. Nach
dieser Ausgabe der Uhrzeit wird der Cursor mit 12 \b-Zeichen wieder auf den Anfang der
Uhrzeit positioniert.
Zudem wird die Zählvariable zaehl, welche mitzählt, wieviele Hundertstel Sekunden schon
vergangen sind, um 1 inkrementiert (zaehl + +).
Dieser Programmteil befindet sich in 4 ineinander geschachtelten do ... while-Schleifen:
Die innerste dieser Schleifen wird verlassen, wenn 99 Hundertstel Sekunken angezeigt
wurden, oder die geforderten Hundertstel Sekunden schon alle in eine Uhrzeit umgesetzt
wurden: zaehl <= hund____sek. Nach dem Verlassen dieser innersten Schleife wird die
Strukturkomponente zeit.hundertstel auf 0 gesetzt, um bei der nächsten Zeitangabe bei
den Hundertstel Sekunden wieder mit 0 zu beginnen.
Dieses Verfahren gilt auch für die weiteren do... while-Schleifen, wobei eine Schleife
für das Zählen von Minuten und schließlich noch eine Schleife für das Zählen von Stunden
existiert.
Unser Programm kann als größtmögliche Zeit
999:59:59:99
angeben.
Bezüglich der Initialisierung von Strukturen gelten die gleichen Ein-
schränkungen wie für Vektoren: Es dürfen lediglich globale Strukturen
oder static Strukturen initialisiert werden; Strukturen der Speicherklas-
se auto dürfen nicht bei der Deklaration mit Werten vorbesetzt werden.
13-15
13.3 VEKTOREN VON STRUKTUREN
Neben einfachen Strukturen sind auch Vektoren erlaubt, deren Elemen-
te Strukturen sind.
Es sollen nacheinander die Namen von bis zu 100 Personen mit Telefonnummer über den
Bildschirm eingegeben werden. Die Eingabe soll mit dem Zeichen • statt eines Nachna-
mens beendbar sein.
Diese Personendaten sind dann alphabetisch zu sortieren und wieder auszugeben; es wird
also eine Art Telefonbuch erstellt.
Programmentwicklung:
Diese Aufgabe soll wieder nach der top-down-Vorgehensweise gelöst werden.
Dazu entwerfen wir zunächst eine grobe Struktur der Gesamtaufgabe des Programms:
Wir sehen, daß die Gesamtaufgabe in 3 Teilaufgaben zerfällt:
Einlesen
Sortieren
Drucken
Nun sind zu diesen Teilaufgaben Lösungen zu finden. Zunächst wollen wir die Aufgabe
Einlesen realisieren.
Struktogramm für Einlesen:
! Einlesen j
+ 2BBS23XSE3 222 2 2 2SZ22S2222SSZZSZ S SXZSS 33 S S2S 2 SS S2S SSS3SSS22 2 2S2SS2S2S +
IzaehlsQ •
'Ausgabe: Sie koennen nun bis zu 100 Personen eingeben 1
<Programmende: Eingabe von • bei Nachname) 1
i
•Ausgabe: Bitte Nachnamen von zaehl+l' .Person eingeben
•
'Eingabe: Nachname * *
! !
13-16
• while Nachname •« •
। *-----------------------------------------------------------------
i »
' ‘Ausgabe: Bitte Vornamen von zaehl*l .Person eingeben
' 'Eingabe: Vorname
i i
' 'Ausgabe: Bitte Telefonnummer von *zaehl*l .Person eingeben
‘ 'Eingabe: Telefonnummer
• •
• 'zaehl**
! i
' ^Ausgabe: Bitte Nachnamen von zaehl+1 .Person eingeben
! 'Eingabe: Nachname
i i
♦—♦-----------------------------------------------------------------
' zahl=—zaehl
'Ausgabe: Es wurden zahl*l ’ Personen eingegeben ’
Funktion einlesenf):
einlesenO
<
int jsO, zaehl=O;
printf(“\n\nSie koennen nun bis zu 100 Personen eingeben '\n*i;
printf(“(Programmende: Eingabe von • bei Nachname)\n\n\n");
printf(•\nBitte Nachnamen von Xd.Person eingebenxn",zaehl*11 ;
ro;
do (
adressetzaehlJ.nachnametj**J=getchar();
) while (adressetzaehlJ.nachnametj-11'=’\n );
ad ressetzaehl].nachnametj-1J=’\O’;
while (adressetzaehlJ.nachnameCO]‘= •') t
printf("XnBitte Vornamen von Xd.Person eingebenxn",zaehl*l) ;
j=0;
do <
adressetzaehlJ.vornametj** J=getchar ();
) while (adressetzaehlJ.vornametj-1]•=’\n');
adressetzaehlJ.vornametj-1J=\0;
printf("XnBitte Telefonnummer von Xd.Person eingebenxn",zaehl*1);
scanf("Xld",&adressetzaehlJ.telefonnummer);
getchart); /• Einlesen der RETURN-Taste •/
zaehl**;
printf("\nBltte Nachnamen von Xd.Person eingebenxn",zaehl*l );
ad resset zaehlJ.nachnametj**J = getchar();
J while (adressetzaehlJ.nachnametj-1J's\n );
adressetzaehlJ.nachnametj-1J=’\0‘;
)
zahl=--zaehl;
printf("Xn\n\n\nEs wurden Xd Personen eingegeben »\n\n\n\n\n",zahl*l );
13-17
Erläuterung:
Wenn wir folgende globale Deklaration angeben:
struct person C
char nachnameLZOD;
char vornametZO];
long telefonnummer;
struct person aoresset1003;
zeigt adresse auf folgenden Speicherblock:
Die Daten der zuerst eingegebenen Person werden also in den Strukturkomponenten des
Vektorelements adresse [O] gespeichert; Daten der zweiten Person werden dann in den
Strukturkomponenten des Vektorelements adresse [1] gespeichert, usw ...
13-18
Struktogramm für Sortieren:
' Sortieren
'for (1=0 ; l<=zahl-1
for (]=i+i ; j<=zahl
• J
Nachn. an Stelle i > Nachn. an Stelle j
N
'vertausche die beiden'
•Personen, die sich '
•an Stelle 1 und ♦
(Stelle j befinden
Nachn. an Stelle 1 = = Nachn. an Stelle j '
N •
Vorn, an Stelle 1 > Vorn, an Stelle j '
N •
‘Vertausche die beiden
(Personen, die sich
'an Stelle 1 und
•Stelle j befinden
Funktionen sortieren!), vertausche^, y):
sortieren()
(
int 1, J, nach_vergl, vor_vergl;
for (1=0 ; i<=zahl-1 ; 1**1
for (j=i*1 ; J<=zahl ; <
nach_vergl = strcmp(ad resset 11.nachname,ad ressetj].nachname);
if (nach_vergl>0)
vertausche(1,j);
eise (
if (nach vergl==0l t
vor_vergl = strcmp(ad resset 1].vorname,ad ressetj J.vorname);
if lvor_vergl>0)
vertauschet 1, J >;
vertausche(x,y)
int x, y;
(
strcpy(hilf.nachname,ad ressetx].nachname);
strcpy(hilf.vornawe,ad resset*].vorname);
hilf . telefonnummersadresset x]. telefonnummer;
strcpy(adresset *].nachname,adressety].nachname);
strcpy(ad ressetx1.vorname,adressety].vorname);
ad ressetx].telefonnummersadressety1.telefonnummer;
strcpy(adressety].nachname,hilf.nachname);
strcpy(adressety].vorname,hilf.vorname);
ad ressety1.telefonnummer=hllf.telefonnummer;
In der Funktion vertausche werden die Daten der Person i mit den Daten der Person j
vertauscht; dies geschieht unter Zuhilfnahme der Strukturvariablen hilf, die global wie
folgt zu deklarieren ist:
struct person hilf;
13-19
Struktogramm für Drucken:
•Drucken •
+33333333S3ZZZ333S8ZZ3333Z33S8S38«S3ZZ*3ZZ38Z3328ZSSZZXZZZZZ3ZZ33ZaZZ*
•Ausgabe: Personen sortiert: •
! i
'Ausgabe: "Nachname“ “Vornaae“ -Telefonnummer •
! •
•for (i«o ; i<=zani ;
' 'Ausgabe: Nachname Vorname Telefonnummer der Person an Stelle 1
!
Funktion drucken!):
drucken()
{
int i;
printf(“Xn\n\nPersonen sortiert:\n“J;
printf(XnSnXZOsXZOsXZOsXnVn","Nachname“,-Vorname“,-Telefonnummer -);
for (isO ; i<=zahl ; >♦♦) {
printf("X2OsX2Os",ad resset 11.nachname,ad resset i1.vorname);
printf(“X201d\n",ad resset 11.telefonnummer);
)
HINWEIS: Hier wird die Funktion strcmp aus der Standardbibliothek aufgerufen; diese
liefert hier einen
negativen Wert,
wenn die als 1. Parameter angegebene Zeichenkette alphabetisch vor der als
2. Parameter angegebene Zeichenkette einzuordnen ist,
positiven Wert,
wenn die als 1. Parameter angegebene Zeichenkette alphabetisch nach der
als 2. Parameter angegebene Zeichenkette einzuordnen ist.
Wert 0.
wenn die beiden übergebenen Zeichenketten völlig übereinstimmen.
13-20
Nun können wir das vollständige Programm zur Lösung der gestellten Aufgabe angeben:
•include «stdio.h»
struct person (
char nachnamet201;
char vornaaetZO];
long telefonnunmer;
struct person adresset100], hilf;
int zahl»O;
aln()
{
einlesen ();
if (zahl>0) C
sortieren ();
drucken ();
einlesen ()
<
.... (siehe vorher)
)
sortieren ()
<
.— (siehe vorher)
drucken()
<
.... (siehe vorher)
)
vertauschet«,y)
int x( y;
(
.... (siehe vorher)
Es soll ein C-Programm erstellt werden, das die in einem Text enthaltenen bestimmten Ar-
tikel (der, die, das) zählt.
Wir benötigen hierzu einen Vektor von Zeichenketten, um die Artikel zu speichern, und ei-
nen Vektor von int-Werten, um die Häufigkeit der entsprechenden Artikel festzuhalten.
Eine Möglichkeit wäre, zwei logisch zusammengehörige Vektoren zu definieren:
char «artikel [3]; »
int zaehl_artikel [3];
13-21
Da diese beiden Vektoren logisch zusammengehören, können sie auch durch folgende
Struktur ausgedrückt werden:
struct such.artikel{
char «artikel;
int zaehl—artikel;
}art—tabelle [3];
Jedes Element von art__tabelle, d. h.
art—tabelle [O] art—tabelle [1]. art—tabelle [2],
ist eine Struktur.
Eine andere mögliche Formulierung wäre:
struct such—artikel {
char »artikel;
. int zaehl_artikel;
struct such—artikel art—tabelle [3];
Da die bestimmten Artikel schon zu Programmbeginn bekannt sind und die entsprechen-
den Zählvariablen mit 0 vorzubesetzen sind, kann die Strukurvariable art—tabelle schon
bei der Deklaration initialisiert werden:
struct such___artikel {
char »artikel;
int zaehl.artikel;
}art—tabelle [ ] ={
”der”, O,
"die”, O,
”das ”,0
};
Übersichtlicher ist die andere Initalisierungsmethode, bei der jede Einzelstruktur durch
Setzen von zusätzlichen geschweiften Klammern vorbesetzt wird:
struct such—artikel {
char «artikel;
int zaehl__artikel;
}art_tabelle [ ]-{
i”der”, O },
"die”, O},
"das”, 0 }
Die Angabe der Dimension kann bei art__tabelle [ ] entfallen, da der Übersetzer diese
aus der Anzahl der Einträge (hier 3) ermitteln kann.
13-22
C-Programm:
nnclude <stdio.h>
»include <ctype.h>
»deflne MAXZEILEN 50
«define maxzeichen 80
struct such_artikel (
char »artikel;
int zaehl artikel;
} art_tabelle!J = {
("der’ ,0>,
Cdie",0),
Cdas",0)
main ()
<
char zeichkettelMAXZElLENHMAXZEICHEN];
char wort!5J;
int zeil zaenl=0;
int laengeCNAXZEILEN], 1, j, k, 1;
printf("\n\nGeben Sie Ihren Text ein '\n\n"J;
while (laenge!zeil_zaehl] = lies_zeileizeich_kette!zeil_zaehl+ + ]))
zeil^zaehl—;
printf("\n\nDer eingegeöene Text war:\n*);
for (1=0 ; Kzeil zaehl ;
printf("\nXs",zeich_kette!i ]);
printf <"\n\n“I;
for (i=0,j=0 ; l<zell_zaehl ; j=0,
while (»(zeich kette!1]*j)) ( *)
if (•izeich_kette!1]+j-1) •= • )
if (»(zeich kette!11+j)== ll »(zeich kettetl]♦])==•\0’j {
if (J-4<Ö ll »izelch ketteliHJ-4)= =r ) (
for (k=J-3,l=0 ; k<j ; k**,l**)
wort!l] = tolower(•(zeich kette£i]*k) );
wort!l]= \Q ;
for 11=0 ; 1<3 ;
if (strcmp(wort,art_tabelle!l).artikel) «« 0) !
art tabelletll.zaehl artikel+>;
break;
1
>
)
>
printf("\n\nIn diesem Text sind die Uoerter\n"i;
foriisO ; i<3 ;
printf("Xs:X3d mal\n",art tabelle!i].artikel,art taöelle!11.zaehl artikel);
printf Centhaltenxn“);
lieszeile(string)
char strmgll;
C
int 1=0;
while ((st r ing! i 4-4-] = getchar ()) •= \n )
string[l-1]s\0’;
returnd-1);
HINWEIS: *) Tauschen Sie diese Zeile gegen while (* (zeich—kette [I] + j)! = O) aus,
falls Sie die Warnung von TurboC stört.
13-23
Erläuterungen:
Die beiden Anweisungen
«include <stdio.h>
sinclude <ctype.h>
veranlassen den Compiler, die beiden Dateien stdio.h und ctype.h mit ins Programm zu
übernehmen; dies ist notwendig, da wir auf die Funktion getchar (aus stdio.h) und auf
tolower (aus ctype.h) zurückgreifen.
Das Einlesen des Textes erfolgt im Programmteil:
printf(“XnXnGeüen Sie Ihren Text ein «\n\n*);
while i laenge!zeil.zaehl]=lies.zeile(zeich.kettelzei1.zaehl**n)
zell.zaehl—;
Die Funktion lies_zeile liest eine Textzeile in zeich_kette ein und liefert die Zeichen-
zahl dieser Textzeile zurück; diese Zeichenanzahl wird im eindimensionalen Vektor laen-
ge abgespeichert. Der Index zeit_zaehl zeigt hierbei die Zeile an, deren Zeichenzahl im
Vektor laenge abgespeichert wird. Die while-Anweisung wird verlassen, wenn eine leere
Zeile eingegeben wurde, denn dann liefert die Funktion lies^zeile den Wert 0.
Mit
printf("\nxnDer eingegeöene Text war:xn");
for (1-0 ; Kzeil zaehl ; i*+)
printf (“XnXs*, zeich kettefiD;
printf(*Xn\nH) ;
wird der vom Benutzer eingegebene Text nochmals am Bildschirm ausgegeben.
Der Programmteil, der für das Zählen der bestimmten Artikel zuständig ist, soll unter Zuhil-
fenahme eines Struktogramms erläutert werden:
13-24
* Durchlaufe alle Zeilen von Zelle 0 bis Zeile zeil zaehl
•( for (1=0,j=0 ; i<zeil_zaehl ; j=0,i++) )
Solange in betreffender Zeile nicht das Endezelchen (\0) gefunden wird
: while (•(zeich-kettetiHj)) )
lErhoehe den Zaehler j, der die einzelnen Zeichen in einer Zeile
• durchlaeuft ( )
Ist das vorausgenenoe Zeichen kein Leerzeichen ?
( if (• (zeicn_kette[ i Hj-i ) j
N '
1 Ist das gerade betrachtete Zeichen ein Leerzeichen oder •
• ein \0-Zeichen (Ende der Zeichenkette) ? I
•(if (»(zeich kettel iHj)= = li »(zeich kettel 1 Hj) == \0* ) 1!
! J " N '
Ist es das erste Wort in der Zelle (j-4<0) ’ oder
Ist das Wort 3 Zeichen lang (»(zeich kettetlHj-4)
( if (j-4<0 li »(zeich kettet 1 Hj-4) = = > )
N •
' Speichere die letzten 3 Zeichen aus zeich_kette nach '
• wort und wandle sie zuvor in Kleinbuchstaben u» •
'wortCl]=tolower(»(zeich_ketteCi Hk))
• Kennzeichne das wort -Ende mit \0-Zeichen
• ( worttlH *\Q* )
• Durchlaufe mit der Laufvarlablen 1 den Struktur-
' vektor art_tabelle
? Ist Inhalt von worf gleich dem Inhalt von <
' art.tabellell].artikel •
• ( If"(strc»p(wort,art tabelletll.artikel) = = 0) )‘
! J " N !
•Erhoene art_tabelle!1J.zaehl_art1kel um 1
•und verlasse"»!t break die 1-for-scnleife
' ( art.tabelletll.zaehl_artikel*+ )
• ( break )
13.4 STRUKTUREN UND FUNKTIONEN
In früheren Versionen von C waren nur zwei Operationen mit Strukturen
erlaubt (Lattice-C-Compiler unter MS-DOS sind auf diese ältere Version
ausgerichtet):
• Adresse einer Struktur mit Adreßoperator & ermitteln
• Zugreifen auf eine Komponente einer Struktur
In den neueren Verionen von C sind folgende weiteren Operationen mit
Strukturen erlaubt:
• Zuweisen einer Struktur an eine andere Struktur gleichen Typs.
• Übergabe einer Struktur als Parameter einer Funktion
• Rückgabe einer Struktur als Ergebniswert einer Funktion
13-25
Beispiel 5
Es ist ein C-Programm zu erstellen, mit dem komplexe Zahlen addiert und subtrahiert wer-
den können.
Eine komplexe Zahl besteht aus einem Real- und einem Imaginärteil; der Imaginärteil wird
durch Angabe von j (j = V-1) gekennzeichnet:
b = 7 + 6 j
a = 2 + 5 j
Addition:
a + b = (2 + 7) + (5 + 6)j = 9 + 11 j
Subtraktion:
a - b = (2 - 7) + (5 - 6)j =-5 - 1 j
Wir wollen zu dieser Aufgabenstellung mehrere Lösungen, die die Unterschiede zwischen
dem "alten” und dem "neuen” C aufzeigen, angeben:
Bei der Lösung mit der neueren Version von C werden wir beispielsweise
(1) die Übergabe einer Struktur als Parameter einer Funktion,
(2) die Rückgabe einer Struktur als Ergebniswert einer Funktion und
(3) das Zuweisen einer ganzen Struktur an eine andere Struktur
zeigen.
Da wir bei der älteren Version von C keine ganze Struktur an eine andere zuweisen dürfen,
keine komplette Struktur als Parameter an eine Funktion übergeben dürfen, und keine
Struktur als Ergebniswert einer Funktion an die aufrufende Funktion zurückgeliefert wer-
den darf, müssen wir entweder die Strukturkomponenten einzeln oder aber einen Zeiger
auf eine Struktur als Parameter an eine Funktion übergeben.
13-26
hrograusvs«S1OR ait "aauarse* C •(
>1
f Pragraaaversion ait "oeltoroo* C • / (+)
Struct koaplaa.zahl (
(lost real.teilt
float laag.lolll
>1
eainII
(
struct «oaploa.zahl alt.tahl, nau.zahii
int aahl, zaohl l|
alt.tahl • lie«(:«a*l)|
•Ml« ((uahlopuahl<)> «0> (
i»eu.tahl • 1i»s(**zaaM•|
printfi’ \n\n')|
ausgsb (alt.tahl >|
printfi* 1C ♦, («4hi"»l> 1 •' I
ausgab(nau.iahli|
printfi* • *>|
alt.iah] • «alt.tahl ,n»u.zahl>|
4usgab(4lt.Mhl»|
liasitathl.balt.zai l.raal tat 1,halt.zahl. isag tall)|
ahiie t(»ahl>ep.ahl (M '•«> (
lies(**:*ehl,bntu.tahl.roal.t»il,bnau.tahl.iaag.t»tl>|
printfi* \n\n*)|
ausgab(alt.zanl.r»al.toi 1,alt.iahl.ioag toi 111
printfi* tc *, (»ahl-.ii 1 , - ij
ausgob(neu.zahl.real.teil.nau.tahl.iaig teil))
printfi* - *>|
baroch <»ahl,balt.tohl,treu.zahl>j
ausgab(alt.tahl.real.toll,alt.tahl.iaag.tail>|
struct koaploa.zaM lloolil
< ’
struct kaaplst.ashl Struktur।
printf Ito «an O»altoil *>|
priatf(*dar 14. koaploaon labt 4" '\n*,i>|
scanf(*Xf*,bttrübtur.rsal.toil>|
trist«(*tn\R»aban li» d»n laaginasrtoi1 ’lj
print«<’aar Xd.k»»p!•«•« lahl an f\a*,i)|
scanfbetrübter. isag.toll 1i
return«Struktur)।
liesd ,tahl 1,sahl2»
tat H
float aiahll, atahl2|
(
printf(*\n\»Saben lia den >aalt»ila>|
printfCter za. koapiaeen lahl an »\a*,il|
scanf <*Xf*,taMD|
printf(’\n\n8eben Sie den laaginaerteil *>|
priatf(*der Xd.haapleaen lahl an \n*,il|
scanf(*1<*,iah!2)|
printf (*«*>|
printf(’l.Jfj)*,Struktur, inag.tsll)|
ausgabfroell.laaginaer)
Hast reell, iaaginaeri
<
printf CI«.JC, reelDt
if liaagtnaer>«0)
printf<•••»!
printf(•X.Jfjl•,iaaginner)।
Serech(buch.etrut1,stru»2)
int bucht
struct kauple«.zahl Mtrukl, ettruk2|
<
Seitchlbuch) (
case |i
(estruhl).real.tat I♦•(•etrut2>.real.tat 11
tastrub11.laug.toi 1••(eatruk2l.inag'tel1|
C40» 2l
(•Strubl).real.teil*»!astrukti.raal.teil।
(•strahl).laag.tsil-•(•struk2>.leag'teil।
br»ak|
printf (*\f*\n\n\aO • Pragraaaandetnl • »\n2 a -*||
PlKl (*\n\n\n\|obsn Sie Operator ata
acaaf(*Xd* «bcpt'at)|
If (»parat >0 bk »parat*»! bb oparat >2) (
printf<*tn\ntnFalschs Eingabe '\n\n*)|
prlnt*<•Oiederholen Sie Ihre Eingabe 1* tn\n\n*)|
>
) ahiio (Oparat -c bb aperat'M bb sparst'* 2>|
Beide Programme laufen unter Microsoft MSC.
Quick C und Turbo C. Das Programm (+J läuft unter
LATTICEC.
Hier werden die beiden Lösungen einander gegenübergestellt. Testen
Sie an Ihrem Rechner, welche C-Version für Sie gilt.
In beiden Programmversionen werden zunächst Real- und Imaginärteil
der 1. komplexen Zahl eingegeben. Dann wird in beiden Versionen ge-
fragt, mit welchem Operator (0 = Programmende, 1 -+, 2 --) die bereits
eingegebene Zahl mit der noch einzugebenen komplexen Zahl zu ver-
knüpfen ist.
13-27
Nach Eingabe der nächsten komplexen Zahl wird das Ergebnis der
Operation am Bildschirm ausgegeben. Danach wird wieder nach dem
Operator gefragt, wobei das berechnete Ergebnis als 1. Operand heran-
gezogen wird. Das Programm wird beendet, wenn bei der Operatorein-
gabe 0 angegeben wird.
Nehmen wir an, daß wir folgenden komplexen Ausdruck berechnen wol-
len:
(-7 + 3.5j) - (3.21 - 4.7j) + (-13.12- 4.37j) = ?
Hierfür ergäbe sich dann folgender Bildschirmablauf:
Geben Sie den Realteil der 1. komplexen Zahl an!
-7—'
Geben Sie den Imaginaerteil der 1. komplexen Zahl an!
3.5—।
0 = Programmende
1 = +
2 = -
Geben Sie Operator ein!
2—1
Geben Sie den Realteil der 2. komplexen Zahl an!
3.21—>
Geben Sie den Imaginaerteil der 2. komplexen Zahl an!
-4.7—1
(-7.000 + 3.500j) —(3.210- 4.700J) = (-10.210+ 8.200j)
0 = Programmende
1 = +
2 = -
Geben Sie Operator ein!
1—'
13-28
Geben Sie den Realteil der 3. komplexen Zahl an!
-13.12—1
Geben Sie den Imaginaerteil der 3. komplexen Zahl an!
-4.37—•
(-10.210 + 8.200J) + (-13.120 - 4.370j) = (-23.330 + 3.830j)
0 = Programmende
1 = +
2 = -
Geben Sie Operator ein!
0-1
In Beispiel 5 greifen wir in der Funktion berech über Zeiger auf Struktu-
ren zu (ältere Programmversion):
(«struk 1 ).rea|_teil+ = (*struk2).real_teil;
Die Klammern sind dabei notwendig, da der Punktoperator höhere Prio-
rität besitzt als *.
Hier handelt es sich um einen Zeiger
auf eine Struktur:
struct komplex_zahl * struk 1
struk 1 ist also ein Zeiger; um nun
auf die einzelnen Strukturkomponen-
ten zuzugreifen, muß zuerst «struk 1
ausgewertet werden. Dies wird durch
das Setzen der Klammern erreicht.
13-29
Wird der Ausdruck «struk 1.real_tell = ... angegeben, so müßte fol-
gende Deklaration vorliegen:
struct komplex__zahl {
float «real_teil;
} struk 1;
float-
Wert
Hier handelt es sich dann um eine
Struktur, bei der die Komponente
struk 1.real__teil ein Zeiger ist.
struk 1 ist bei der Deklaration
struct komplex_zahl struk 1
lediglich ein Strukturname und kein
Zeiger.
Hier entspricht
«struk 1 .real_teil
der Angabe
«(struk 1 .real_teil)
Ein häufiger Fehler ist, daß bei Zeigern auf Strukturen statt
(«struk 1).real_teil die falsche Konstruktion «struk 1.real_teil an-
gegeben wird.
strukl bezeichnet nämlich nicht die Struktur, sondern ist nur ein Zei-
ger darauf.
Da diese Art des Zugriffs auf eine Strukturkomponente über Zeiger in
der Form:
(*strukturname).kom ponente
sehr oft verwendet wird, gibt es einen speziellen Operator dafür:
- > (Minuszeichen, gefolgt von einem "GröBer’-Zeichen)
Anstelle der obigen Formulierung können wir dann
strukturname -> komponente
angeben.
Die Programmversion mit älterem C aus Beispiel 5 könnten wir dann
auch so angeben:
/• Programmversion ult "aeltereur C •/
struct komplex_zahl <
float realteil;
float laagteil;
13-30
ma 1 n ()
<
struct komplex_zahl alt zahl, neu zahl;
int wähl, zaehlst;
lies(zaehl,Salt_zahl.real teil,Sait zahl.mag teil);
while ((wahl=opwahl()) >= 0) (
lles(*+zaehl,Sneu zahl.real teil,Sneu zahl.mag teil);
printf("\n\n");
ausgab(alt_zahl.real_tell,alt zahl.mag teil);
printf(" Xc ",(wahl=sl) ? -');
ausgab(neu.zahl.real teil,neu zahl.mag teil);
printf(" * ");
berech(wähl,Sait zahl.Sneu zahl);
ausgab(alt.zahl.real teil,alt zahl.mag teil);
lies(1»zahlt,zahl2)
int i;
float «zahlt, «zahl?;
<
printf(“\n\nGeoen Sie den Realteil");
printft" der Xd. komplexen Zahl an !\n",l);
scanf("Xf",zahlt);
printf("\n\nGeben Sie den Imaginaerteil ");
printf("der Xd. komplexen Zahl an !\n",i);
scanf("Xf",zahl?);
ausgab(reell,1maginaer)
float reell, maginaer;
printf("(X.3f",reell);
if (maginaer>=0)
printf("4");
printf("X.3fJ)",maginaer);
berech(buch,strukt,struk2)
int buch;
struct komplex zahl «strukt, «struk2;
<
switch(buch) (
case 1:
5truk1->real tell+=struk2->real teil; /♦ mit -> Operator
strukl->mag" teil + = struk2->mag'teil; /• realisiert
break;
case 2:
strukt->real_teil-=struk2->real_teil; /• mit -> Operator
strukt ->mag_teil- = struk2->mag2teil; /• realisiert
break;
□pwahl ()
(
int operat;
do C
printf ("\n\n\n\nO = Programi»ende\ni = +\n2 -
printf("\n\n\nGeben Sie Operator ein ’\n");
scanf("Xd",Soperat);
if (operat‘=0 SS operat'st SS operat'sZ) (
printf("\n\n\nFalsche Eingabe ’XnXn");
printf(“Wiederholen Sie Ihre Eingabe ?'\n\n\n">;
)
) while (operat'zO SS operat'=1 SS operat!=2);
return(operat);
13-31
13.5 ZEIGER AUF STRUKTUREN
Zur Erläuterung von Zeigern auf Strukturen werden wir ein Beispiel her-
anziehen. In diesem Beispiel werden wir den sizeof-Operator kennen-
lernen. Mit diesem Operator kann die Größe eines Objekts in Bytes er-
mittelt werden. Der sizeof-Operator kann also sehr gut dazu verwendet
werden, um festzustellen, wieviele Bytes Ihr Rechner für die Grundda-
tentypen benutzt:
•am ()
<
printf("XnLaenge einer char-Varlablen: Xd Byte",sizeof(cnar));
printf("XnLaenge einer short int-variablen: Xd Bytes“,sizeof(short int));
printf("XnLaenge einer int-Variablen: Xd Bytes*»sizeof<tnt));
printf("XnLaenge einer long int-variablen: Xd Bytes"»sizeof(long int));
printf("XnLaenge einer float-Varlablen: Xd Bytes",sizeof(float));
printf("XnLaenge einer double-varlablen: Xd Bytes",sizeof(double)i;
printf("XnLaenge einer long float-Varlablen: Xd Bytes".sizeof(long float)»;
Mit oder ohne Klammernpaar kann der sizeof-Operator auch direkt auf
Variablen angewendet werden; das nachfolgende Programm ergäbe
also den gleichen Bildschirmausdruck wie das Vorherige:
main()
<
char zeich;
short int kurz;
int ganz;
long int lang;
float einfach;
double doppel;
long float gebr_lang;
printf("XnLaenge einer char-Variablen: Xd Byte",sizeof zeich);
printf("XnLaenge einer short int-variablen: Xd Bytes",sizeof kurz);
printf ("XnLaenge einer int-vanablen: Xd Bytes", s 1 zeof ganz);
printf("XnLaenge einer long int-Variablen: Xd Bytes".sizeof lang);
printf("XnLaenge einer float-varlablen: Xd Bytes",sizeof einfach);
printf("XnLaenge einer double-variablen: Xd Bytes",sizeof doppel);
printf("XnLaenge einer long float-varlablen: Xd Bytes",sizeof gebr_lang);
Beispiel 6
Es ist ein C-Programm zu erstellen, das dem Benutzer auf Anfrage eines der Symbole ei-
nes Programmablaufplans (PAP) am Bildschirm zeigt.
13-32
struct syrabole (
char buchstab;
char •blld(7];
) sy®b tabellet] - (
CÄ ______________________",
/Ausgabef 7",
’ / /%
-/ /• ),
cb," •,
•< ’ >• >,
ce," *,
/Eingabe: 7",
CI’," ”7\V^ /♦ \\ entspricht de® Zeichen \
Idef1 re NULL 0
soefine ANZAHL sizeof symb_tabelle / sizeof(struct Symbole)
aln()
<
struct syabole »binaersuchet), »zeig;
char eingab;
int i;
do (
einles zeich(Seingab);
if ((zeig=binaer suche(eingab,syab_tabelle,ANZAHL))’-NULL) <
printf ("\n\n*)”;
1*0;
while (zeig->bild(11)
printf(“XsXn*,zeig->biIdCi+♦]);
)
eise
printf("\n\nlhr eingegeb. Zeichen steht nicht fuer ein Syabol\n\n-)
) while (eingab!»-P*);
struct syabole *binaer_suche(reich,tabelle,laenge)
char zeich;
struct syabole tabelletl;
int laenge;
(
struct syabole •unten»&tabelle(O);
struct syabole »oben=&tabelleClaenge-1 ];
struct syabole »Bitte;
while (unten<=oben) (
®itte=unten*(oben-unten)/Z;
if (zeich<®itte->buchstab)
oben=altte-l;
eise
if (zeich>aitte->buchstab)
untensaltte*1;
eise
return(Bitte);
)
return(NULL);
13-33
einles_zeich(Zeichen)
char «Zeichen;
<
char er;
printf("\n\n\nA(usgabe)-SywbolXn“);
printfCB(eginn)-Symbol\n") ;
printf("E(ingabe»-SymbolXn");
printfCI Cf-Vergleich)-SymbolXn");
printf("5(top)-SymbolXn");
printf CT(aetigkeits)-SymbolXn“);
printf (“U(nterprogramm) -SymbolXn");
printf CP(rogrammende)XnXn");
printf("Geben Sie Ihren gewuenschten Buchstaben ein IXnXn");
scanf("XcXc".Zeichen,er); +)
Erläuterungen:
Mit der Anweisung
tdefine ANZAHL slzeof symb.taöelle / sizeof(struct Symbole)
definieren wir eine symbolische Konstante ANZAHL, welche die Anzahl der PAP-Symbole
im Vektor symb__tabelle enthält. Diese Zahl, hier 7, könnten wir natürlich von Hand aus-
zählen und # define ANZAHL 7 angeben. Wenn später die Liste der PAP-Symbole ein-
mal geändert, d. h. erweitert oder gekürzt wird, müßte diese Konstante 7 ebenso geändert
werden, was leicht vergessen werden kann.
Um die Zahl der Feldelemente von symb_tabelle zu ermitteln, ist die Division
sizeof symb__tabelle / sizeof (struct Symbole)
notwendig, denn der slzeof-Operator liefert bei Vektoren die Größe des Gesamtvektors in
Bytes.
Der Teilausdruck sizeof (struct Symbole) liefert uns die Größe der Struktur Symbole in
Bytes:
1 Byte für buchstab
7*4 Bytes für char «bild [7],
da für Zeiger immer 4 Bytes*) reserviert werden und wir hier einen Vektor mit 7 Zeigern
vorliegen haben.
Da sich der Vektor symb_tabelle aus 7 Elementen vom Typ struct Symbole zusam-
mensetzt. liefert der Teilausdruck sizeof symb_tabelle den Wert 203 (7 ♦ 29).
Mit dem Ausdruck
sizeof symb.tabelle / sizeof (struct Symbole)
HINWEIS: *) gilt für C-Compiler, die im LARGE-Modell einen Maschinencode für die
8086-Mikroprozessor-Familie generieren; hier setzt sich ein Zeiger aus ei-
nem Segmentselektor (2 Bytes) und einer Offset-Komponente (2 Bytes) zu-
sammen.
+)Die Warnung von Turbo C läßt sich durch scanf (”%c %c”, Zeichen,
Ä er); beheben.
13-34
wird also die Dimension, hier 7, des eindimensionalen Vektors symb_tabelle ermittelt.
Mit der Deklaration
struct Symbole *binaer_suche ()
in der Funktion main wird angezeigt, daß die Funktion binaer_suche einen Zeiger auf
eine symbol-Struktur und keinen Int-Wert liefert.
Mit dem Funktionsaufruf einles__zeich (Äeingab) wird in die char-Variable eingab das
vom Benutzer eingegebene Kurzzeichen für ein PAP-Symbol eingelesen.
In der if-Abfrage
if ((zeig = binaer_suche (eingab, symb_tabelle, ANZAHL))! = NULL)
wird die Funktion binaer_suche aufgerufen:
struct syabole »binaer_sucne(zeich,tabelle,laenge)
char zeich;
struct syabole tabelletl;
int laenge;
(
struct syabole »unten=&tabelleLQ];
struct syabole •oben = &tabelle(laenge-1 1;
struct syabole »Bitte;
while (unten<=oben) {
aittesunten+toben-untenl/2;
lf (zeich<aitte->buchstab)
oben=eltte-l;
eise
lf (zeich>aitte->buchstab)
unten=altte+i;
eise
returntaitte);
)
return(NULL);
Die Definition von binaer_suche:
struct Symbole *binaer_____suche (zeich, tabelle, laenge)
zeigt an, daß sie als Ergebniswert einen Zeiger auf eine symbol-Struktur liefert.
Nehmen wir an, die Funktion binaer_suche wird mit folgenden aktuellen Parametern
auf gerufen:
zeich »»
tabelle
laenge
eingab (Inhalt ist das Zeichen ’S’)
symb___tabelle
ANZAHL
dann werden mit den Initialisierungen
struct syabole »unten=&tabelleCO];
struct syabole ♦obens&tabellellaenge-11;
die Zeiger unten und oben auf den Anfang und das Ende von symb_tabelle gesetzt.
Die angegebenen physikalischen Adressen sind rein fiktiv:
13-35
unten — > 20000 ’A’ symb_tabelle [ 0 ]
20029 ’B’ symb.tabelle [ 1 ]
20058 ’E’ symb_tabelle [ 2 ]
20087 t symb.ta belle [ 3 ]
20116 ’S' symb.ta belle [ 4 ]
20145 T symb.ta belle [ 5 ]
oben => 20174 ’U’ symb_tabelle [ 6 ]
20203
Da die Bedingung unten <= oben der while-Schleife erfüllt ist, wird als nächstes der
Zeiger mitte mit der Anweisung
mitte = unten + (oben - unten)/2;*)
(20000 + (20174 - 20000)/2 = 20087)
auf die Mitte der Liste symb__tabelle positioniert:
unten = -> 20000 ’A’ symb.tabeile [ 0 ]
20029 ’B’ symb.ta belle [ 1 ]
20058 ’E’ symb.ta belle [ 2 ]
mitte = * 20087 T symb.tabelle [ 3 ]
20116 'S’ symb.tabelle [ 4 ]
20145 T symb.tabelle [ 5 ]
oben = =► 20174 v symb_tabelle [ 6 ]
20203
Da zeich > mitte -> buchstab**)(’S’> T) ist, wird der Strukturzeiger unten mit
unten = mitte + 1;
wie folgt positioniert: * **)
HINWEISE: *> Einfacher wäre sicher die Anweisung mitte = (unten + oben)/2, um den
Zeiger mitte in der Tabellenmitte zu positionieren. Da aber die Addition
zweier Zeiger nicht erlaubt ist, mußten wir eine Formulierung finden, die
das gleiche (Zeiger in Tabellenmitte positionieren) bewirkt:
mitte = unten + (oben - unten)/2
**) mitte — buchstab entspricht: (*mitte).buchstab
13-36
20000 ’A’ symb.tabelle [ 0 ]
20029 ’B’ symb.tabelle [ 1 ]
20058 ’E’ symb.tabelle [ 2 ]
mitte = => 20087 T symb.tabelle [ 3 ]
unten = => 20116 ’S’ symb_tabelle [ 4 ]
20145 t symb.tabelle [ 5 ]
oben = => 20174 ’U' symb.tabelle [ 6 ]
20203
Da die while-Bedingung unten <= oben immer noch erfüllt ist. wird mit
mitte * unten + (oben - unten)/2;
der Strukturzeiger mitte auf das Element in der Mitte zwischen unten und oben positio-
niert:
20000 ’A' symb_ta belle [ 0 ]
20029 ’B’ symb_tabelle [ 1 ]
20058 ’E’ symb_tabelle [ 2 ]
20087 T symb.tabelle [ 3 ]
unten = =► 20116 ’S' symb.tabelle [ 4 ]
mitte = => 20145 ’T’ symb.tabelle [ 5 ]
oben = => 20174 v symb.tabelle [ 6 ]
20203
13-37
Da nun zeich < mitte -> buchstab (’S* < ’T’) ist, wird der Strukturzeiger oben mit
oben = mitte - 1;
wie folgt positioniert:
20000 ’A’ symb.tabelle [ 0 ]
20029 symb.tabelle [ 1 ]
20058 ’E’ symb_tabelle [ 2 ]
unten 20087 T symb.tabelle [ 3 ]
oben = > 20116 ’S’ symb.tabelle [ 4 ]
mitte = > 20145 T symb.tabelle [ 5 ]
20174 v symb.tabelle [ 6 ]
20203
Da die while-Bedingung unten <= oben auch hier noch erfüllt ist, wird der Strukturzei-
ger mitte mit
mitte = unten + (oben - unten)/2;
hier nun wie folgt positioniert:
20000 ’A’ symb.tabelle [ 0 ]
20029 ’B’ symb.tabelle [ 1 ]
20058 ’E’ symb.tabelle [ 2 ]
unten < 20087 T symb.tabelle [ 3 ]
mitte = oben > 20116 ’S’ symb.tabelle [4 ]
20145 ’T’ symb.tabelle [ 5 ]
20174 ’U’ symb.tabelle [ 6 ]
20203
13-38
Da nun zeich == mitte -> buchstab (’S’ == ’S’) gilt, wird mit return (mitte) ein Zeiger
auf das entsprechende Feldelement von symb_________tabelle an die aufrufende Funktion
main zurückgeliefert. Für den Fall, daß das entsprechende, vom Benutzer angegebene,
Kurzzeichen in symb____tabelle gefunden werden konnte (wie bei unserem Beispiel), wird
mit dem Programmteil:
if ((zeig = oinaer_suche (eingabtsy«iD tabelle,ANZAHL))1-NULL) {
printf("\n\n*);
1=0;
while (zeig->bild(i])
printf("Xs\n",zeig->biId;
das zugehörige PAP-Symbol am Bildschirm ausgegeben.
Konnte das vom Benutzer eingegebene Kurzzeichen in symb____tabelle nicht gefunden
werden, so wird von binaer_suche ein NULL-Zeiger an die Funktion main zurückgelie-
fert, und mit
printf«"\n\nlhr eingegeb. Zeichen steht nicht fuer ein Symbol\n\n”);
wird am Bildschirm der Hinweis gegeben, daß zu dem eingegebenen Zeichen kein PAP-
Symbol zur Verfügung steht.
Erläuterungen:
(1) Beim binären Suchen wird vorausgesetzt, daß eine Tabelle bereits sortiert vorliegt; bei
diesem Suchverfahren wird zuerst die Tabelle in 2 Teile aufgeteilt, indem ein Zeiger auf
die Tabellenmitte, siehe (2), positioniert wird. Danach wird geprüft, ob das mittlere Ele-
ment größer, kleiner oder gleich dem gesuchten Element ist. Bei Übereinstimmung wird
der Suchvorgang abgebrochen, da das gesuchte Element gefunden ist. Ist das ge-
suchte Element dagegen größer (nach ASCII-Tabelle), so ist nur noch in der oberen Li-
stenhälfte zu suchen. In der unteren Listenhälfte muß weiter gesucht werden, wenn
das zu suchende Element kleiner als das mittlere Element ist.
In der jeweiligen Listenhälfte wird das binäre Suchverfahren fortgesetzt, indem auch
dieser Listenteil wieder in 2 Hälften unterteilt wird, usw.
Das binäre Suchverfahren wird beendet, wenn entweder das gesuchte Listenelement
gefunden wurde: zeich == mitte -> buchstab oder festgestellt wird, daß das ge-
suchte Listenelement nicht in der Liste vorhanden ist: unten > oben.
Die möglichen Positionierungen vom Zeiger mitte beim binären Suchverfahren sollen
für die Liste unseres Beispiels anhand einer Baumstruktur*) nachgebildet werden:
HINWEIS: *) In der Informatik werden Bäume meist so gezeichnet, daß die Wurzeln oben
und die Blätter unten zu liegen kommen.
13-39
'nicht möglich' bedeutet, daß ein unmittelbarer Vorgänger oder Nachfolger (nach AS-
CII-Code) zuvor geprüft wurde:
nicht möglich, da
zwischen ’S’ und T
kein weiteres Zeichen
liegt.
(2) Sollte eine vorgegebene Tabelle keine ungerade Anzahl von Elementen besitzen, dann
kann mit
mitte = unten + (oben - unten)/2;
nicht genau auf die Tabellenmitte positioniert werden.
Da Zeiger aber immer ganze Zahlen sein müssen, trifft für diesen Fall die C-Konvention
zu, daß bei einem eventuell gebrochenen Divisionsergebnis immer abgerundet wird.
Nehmen wir an, daß z. B. die Liste symb__tabelle aus dem vorhergehenden Beispiel
nur 6 Feldelemente ohne ”’u’-Teil” enthält, dann würde mit der ersten Ausführung der
Anweisung
mitte = unten + (oben - unten)/2;
der Strukturzeiger mitte wie folgt positioniert:
13-40
unten = =► 20000 ’A’ symb.tabelle [ 0 ]
20029 ’B’ symb.tabelle [ 1 ]
mitte = => 20058 ’E’ symb.tabelle [ 2 ]
20087 T symb.tabelle [ 3 ]
20116 ’S’ symb.tabelle [4 ]
oben — =► 20145 t symb.tabelle [ 5 ]
20174
(3) Um Ihr Programm übersichtlicher zu gestalten, könnten Sie den Funktionsnamen vom
Datentyp des gelieferten Ergebniswerts beispielsweise wie folgt absetzen:
struct Symbole •
binaer_suche (zeich, tabelle, laenge)
(4) Im vorhergehenden Beispiel taucht z. B. folgende Zuweisung auf: unten = mitte + 1;
Da unten und mitte Zeiger auf die Struktur Symbole sind, wird bei der Addition +1
die tatsächliche Größe der Struktur symbole berücksichtigt mitte + 1 entspricht der
Addition: Adresse von mitte plus entsprechender Wert, damit unten auf das nächste
Strukturelement im Vektor symb—tabelle zeigt.
Verlassen Sie sich übrigens nicht darauf, daß die Elemente einer Struktur lückenlos
aneinander gefügt werden. Manchmal erzeugt der Compiler zwischen den Elementen
Füll-Bytes, um damit notwendige Justierungen auf Wort- und Doppelwortgrenzen zu
erreichen.
(5) Wenn wir z. B. folgende Deklaration vorliegen haben
struct {
int ganz;
int «Komponente
} «struk_zeiger;
dann entspricht*)
+ + struk—seiger -> ganz
der Formulierung + + (struk_zeiger -> ganz) oder ♦+ ((«struk_zeiger).ganz)
d. h., es wird der Inhalt der ganzzahligen Strukturkomponente ganz um den Wert 1
inkrementiert.
(+ + struk_zeiger) -> ganz
der Formulierung (*(++ struk_zeiger)).ganz,
d. h., es wird zuerst der Zeiger struk—zeiger um eine Position weitergesetzt, bevor
auf die ganzzahlige Strukturkomponente ganz zugegriffen wird.
struk^zeiger + + - > ganz
der Angabe (struk—zeiger + + -> ganz) oder («struk—zeiger + +).ganz, d. h., es
wird zunächst auf die Strukturkomponente ganz in der Struktur, auf die struk_zeiger
zeigt, zugegriffen, bevor dann struk_zeiger um eine Position weitergesetzt wird.
13-41
Dann entspricht
(*struk___zeiger + +). ganz
der Formulierung («struk-jeeiger + +).ganz, d. h., auch hier wird zunächst auf die
Komponente ganz in der Struktur, auf die struk _zeiger zeigt, zugegriffen, bevor
dann struk__zeiger um eine Position weitergesetzt wird. Folglich entspricht diese
Formulierung der zuletzt vorgestellten Formulierung struk—zeiger+ + -> ganz.
*struk____zeiger-> komponente
der Formulierung «(struk_zeiger -> komponente) oder «(«struk_zeigerj.kom-
ponente, d. h. es wird auf das int-Objekt zugegriffen, auf das komponente zeigt.
«struk____zeiger -> komponente + +
der Angabe «((struk—zeiger -> komponente) ++) oder «((«struk—zeiger).kom-
ponente + +), d. h., es wird zunächst auf das int-Objekt zugegriffen, auf das der Zei-
ger komponente zeigt, bevor dieser um eine Position weitergesetzt wird.
(«struk___zeiger -> komponente) + +
der Formulierung («(«struk_zeiger).komponente) ++, d. h., es wird das int-Objekt,
auf das der Zeiger komponente zeigt, um 1 inkrementiert.
♦struk____zeiger + + -> komponente
der Formulierung «((«struk_zeiger ++).komponente), d. h., es wird struk—zeiger
um eine Position weitergesetzt, nachdem auf das int-Objekt zugegriffen wurde, auf
das komponente zeigt.
HINWEIS: *) Benutzen Sie hier die Hierarchietabelle für Operatoren aus Kapitel 13.10.
13-42
13.6 DAS SCHLÜSSELWORT typedef
Mit dem Schlüsselwort typedef können in C neue*) Datentypen verein-
bart werden. Die mit typedef definierten Namen können dann als neue
Datentypen verwendet werden:
typedef int INTEGER
Mit dieser Deklaration wird der Datentyp INTEGER als Synonym für int
definiert. Mit der Großschreibung von INTEGER soll angedeutet wer-
den, daß es sich hierbei um eine eigene Typdefinition handelt.
Nach der obigen typedef-Vereinbarung kann der Datentyp INTEGER
genauso verwendet werden wie der Datentyp int:
INTEGER zaehler, I; /• entspricht: int zaehler, I; •/
INTEGER «zeig__vektor [10]; /• entspricht: int.zeig_vektor [10]; »/
Die Wirkung des obigen typedef hätten wir auch mit
tidefine INTEGER int
erreicht.
Eine weitere neue Datentypvereinbarung, die auch mit tidefine mög-
lich wäre, ist beispielsweise:
typedef char «STRING /« tidefine STRING char • •/
STRING ist nach dieser Deklaration ein Synonym für char *, also ein
Zeiger auf Zeichen, den man bei Deklarationen beispielsweise wie folgt
verwenden kann:
STRING text [IO]; /• entspricht: char «text [10];«/
STRING satz; /* entspricht: char «satz; «/
Im nachfolgenden wollen wir zeigen, daß typedef bei neuen Datentyp-
definitionen weitergehendere Möglichkeiten bietet als tidefine:
typedef int MONATE [12];
Hier wird der Datentyp MONATE als int... [12] definiert; eine solche
Datentypvereinbarung wäre mit Udefine nicht möglich gewesen.
HINWEIS: *) typedef erzeugt eigentlich keinen neuen Datentyp, sondern es faßt einen
oder mehrere Datentypen in einem Wort zusammen.
typedef bietet so die Möglichkeit, komplexere Datentypvereinbarungen mit
einem Wort zu benennen.
13-43
Die Deklaration
MONATE einnahmen, ausgaben;
entspricht also nach dieser typedef-Vereinbarung der Deklaration:
int einnahmen [12], ausgaben [12];
In Beispiel 6 dieses Kapitels hätten wir typedef wie folgt anwenden
können:
typedef struct Symbole {
char buchstab;
char «bild [7];
} PAPSYMBOL;
PAPSYMBOL symb_tabelle [ ] = {
Durch die Typdefinition von PAPSYMBOL erspart man sich also, in
weiteren Deklarationen immer
struct Symbole ...
schreiben zu müssen und kann weitere Variablen einfach durch
PAPSYMBOL
PAPSYMBOL
PAPSYMBOL
PAPSYMBOL
PAPSYMBOL
PAPSYMBOL
«binaer__suche (), «zeig;
«binaer__suche (zeich, tabelle, laenge)
tabelle [ ];
«unten = Atabelle [O];
«oben = & tabelle [laenge - 1 ];
«mitte;
deklarieren. Den gleichen Effekt hätten wir auch mit
ttdefine PAPSYMBOL struct Symbole
erreichen können.
Beide Methoden werden häufig benutzt und erhöhen die Lesbarkeit der
Programme erheblich.
Das Schlüsselwort typedef können Sie wie eine Speicherklasse ver-
wenden; statt extern oder static schreiben Sie einfach typedef vor
eine Deklaration und ordnen so einem oder mehreren Datentypen einen
Namen zu. Sie sollten sich klarmachen, daß mit typedef kein neuer
Datentyp generiert wird, sondern lediglich ein neuer Name für einen be-
reits existierenden Datentyp eingeführt wird.
13-44
Der Unterschied zwischen typedef und ttdefine ist: typedef wird
vom Übersetzer selbst ausgewertet und folglich kann mit diesem
Schlüsselwort Textersatz vorgenommen werden, was mit ttdefine
nicht möglich ist, da es sich hierbei um eine C-Präprozessor-Anwei-
sung handelt.
Ein abschließendes Beispiel soll unser typedef-Verständnis weiter ver-
tiefen:
typedef float (*FUNK_ZEIG) ();
Hiermit wird ein "neuer” Datentyp FUNK_ZEIG (Zeiger auf eine Funk-
tion, die einen float-Wert liefert) vereinbart.
Die Deklaration
FUNK_ZEIG welch_funktion;
entspricht dann der Deklaration
float («welch__funktion) ();
HINWEIS: Oft entstehen schwer auffindbare Fehler, wenn eine Datentypvereinbarung mit
ttdefine erfolgte:
ttdefine STRING char •
Die Variablendekiaration STRING zeig___1 ,zeig_2; vereinbart nicht, wie erwartet, zwei Zei-
ger, sondern nur einen Zeiger und eine char-Variable; der C-Präprozessor nimmt nur Text-
ersatz vor, so daß folgende Deklaration resultiert:
char *zeig_1 .zeig__2;
Deshalb ist typedef bei einer Datentypvereinbarung gegenüber ttdefine vorzuziehen:
typedef char • STRING
In diesem Fall würde die Deklaration STRING zeig_1 ,zeig_2; der beabsichtigten Verein-
barung
char *zeig__1 /zeig__2;
entsprechen.
13.7 REKURSIVE STRUKTUREN
Rekursive Strukturen in C zeigt folgendes Beispiel:
Beispiel 7
Über Bildschirm sollen Namen eingegeben werden, die in umgekehrter Reihenfolge wieder
auszugeben sind; die Namenseingabe soll bei Eingabe des Zeichens * beendet sein.
Natürlich könnten wir die Namen in einen Vektor einlesen, der bei der Ausgabe vom Ende
her wieder ausgelesen wird. Hierzu verwenden wir rekursive Strukturen.
13-45
C-Programm:
tinclude <stdio.h>
struct llst.element <
char nämeCZOl;
struct list_eleraent »naechster;
main C)
(
struct llst_element •knoten, »anfang, »reserviere();
int 1;
char worttZDJ, »strcpyO;
anfang=NULL;
do (
printf("XnGeöen Sie einen Namen ein •\n");
1=0;
while ((wortCi+4J=getchar(1) •= \n)
wort(l-1]=’\0’;
knotens reserviere();
strcpy(knoten->name,wort);
knoten->naechster=anfang;
anfang=knoten;
) while (wortCOJ •=»•);
printf("\n\n\nAusgaoe der Namen in umgekehrter Reihenfolge:\n");
while ((knoten=knoten->naechster) l= NULL)
printfC\nXs", knoten->name);
struct list element »reservieret)
<
char »mallocO;
return((struct list element») malloc(sizeof(struct list_element)));
>
Erläuterungen:
Ein möglicher Bildschirmablauf wäre:
Geben Sie einen Namen ein!
HANS-J
Geben Sie einen Namen ein!
FRANZ—1
Geben Sie einen Namen ein!
HARALD-J
Geben Sie einen Namen ein!
* I
Ausgabe der Namen in umgekehrter Reihenfolge
HARALD
FRANZ
HANS
13-46
Die Strukturdefinition:
struct list element <
char nämetZOl;
struct li5t_eiement «naechster;
können wir uns wie folgt vorstellen:
1.Ebene 2. Ebene 3. Ebene
Hier können wir den rekursiven Charakter der Struktur list_element erkennen. Eine
Struktur darf sich zwar nicht selbst enthalten, wohl aber einen Zeiger auf sich selbst.
Mit der Vereinbarung struct list_element «naechster; in der Struktur list_element
wird die Strukturkomponente naechster als ein Zeiger auf die Struktur lisL_element in
der sie ja selbst auch enthalten ist, vereinbart.
Mit der Anweisung anfang = NULL; wird der lisL_element-Zeiger mit O vorbesetzt.
In der do ... while-Schleife wird mit
printf("XnGeben Sie einen Namen ein !Xn"J;
1:0;
while ((wortCl**]=getchar<)) • = ’ \n ’)
wortt1-11«'\0‘;
ein Name in das char-Feld wort eingelesen und dessen Ende dort mit \O gekennzeich-
net.
Mit der Anweisung knoten = reserviere (); wird ein zusammenhängender Speicherbe-
reich reserviert, der groß genug ist, um die Struktur list_element aufzunehmen. Die An-
fangsadresse dieses Speicherbereichs wird in der Zeigervariablen knoten festgehalten.
Mit der Anweisung
return ((struct list—element«) malloc (sizeof(struct list—element)));
wird nämlich in der Funktion reserviere ein Zeiger auf den Anfang des mit der Biblio-
theksfunktion malloc reservierten Speicherbereichs an die aufrufende Funktion zurückge-
geben. Die vorangestellte Formulierung
(struct list—element«)
ist ein Cast und besagt, daß der von malloc gelieferte Zeiger in einen list—element-
Zeiger” umzuwandeln ist. Die Anzahl der Bytes, die von malloc zu reservieren sind, wird
mit sizeof (struct list—element) festgelegt.
13-47
Mit den beiden Anweisungen
strcpy(knoten->name,wort);
knoten->naechster=anfang;
wird der in wort eingegebene Name in die Strukturkomponente name übernommen und
die Strukturkomponente naechster auf 0 gesetzt, da anfang mit 0 vorbesetzt wurde:
knoten
HANS \0
knoten-* > name
1 Ebene
knoten - > naechster
Mit der Anweisung anfang = knoten; wird dann die Adresse der momentan behandelten
Struktur in der Zeigervariablen anfang festgehalten, so daß sich dann folgendes Bild er-
gibt:
anfang
HANS \0
knoten - > name
knoten - > naechster
1. Ebene
Da die Bedingung wort [O] ! «’** noch erfüllt ist, wird erneut die do ... while-Schleife
durchlaufen, wobei wieder zunächst die eingegebenen Zeichen in das eindimensionale
char-Feld wort eingelesen werden.
Mit der Anweisung knoten = reserviere (); wird wieder ein zusammenhängender freier
Speicherplatz reserviert, der groß genug ist, um die Struktur list__element aufzunehmen,
die Anfangsadresse dieses Speicherbereichs wird wieder in der Zeigervariablen knoten
festgehalten, so daß sich folgendes Bild ergibt:
knoten - > name
knoten - > naechster
1.Ebene
2. Ebene
Mit den beiden Anweisungen
strcpy(knoten->name,wort);
knoten->naechster=anfang;
ergibt sich dann folgendes veränderte Bild:
13-48
knoten - > name
knoten - > naechster
1 Ebene
2. Ebene
Mit der darauffolgenden Anweisung anfang = knoten; wird dann die Zeigervariable an-
fang auf die gleiche Adresse wie die Zeigervariable knoten positioniert:
knoten - > name
knoten - > naechster
1.Ebene
2. Ebene
Da die Bedingung wort [0] ! = weiterhin erfüllt ist, wird die do ... while-Schleife wie-
derum durchlaufen, wobei wieder zuerst der char-Vektor wort mit den eingegebenen Zei-
chen gefüllt wird.
Mit der Anweisung knoten = reserviere (); wird wieder ein zusammenhängender Spei-
cherplatz reserviert, der groß genug ist, um die Struktur list_element aufzunehmen. Die
Anfangsadresse dieses Speicherbereichs wird wieder in der Zeigervariablen knoten fest-
gehalten.
Nach Ausführung der Anweisungen
strcpy(knoten->na«ie,wort);
knotpn->naechstersanfang;
anfang=knoten;
ergibt sich folgendes Bild:
13-49
knoten - > name
knoten - > naechster
1.Ebene
2. Ebene
3.Ebene
Nach dem letzten Durchlauf der do ... while-Schleife ergibt sich folgendes Bild:
Adresse der
Struktur aus
Ebene 1
Adresse der
Struktur aus
Ebene 2
Adresse der
Struktur aus
Ebene 3
HANS \0
FRANZ \0
HARALD \0
knoten - > name
knoten - > naechster
1.Ebene
2. Ebene
3. Ebene
4. Ebene
13-50
Im Programmteil
printf("\n\n\nAusgabe der Namen in umgekehrter Reihenfolge:\n");
while ((knoten = knoten-»naechster) ’= NULL)
printf(*\nXs*,knoten-»name);
werden die eingegebenen Namen in umgekehrter Reihenfolge ausgegeben, indem über die
Strukturkomponente naechster auf die nächste auszugebende Strukturkomponente
name zugegriffen wird.
Die while-Schleife wird solange durchlaufen, bis der Zeigervariablen knoten mit
knoten = knoten -> naechster
der Wert NULL zugewiesen wird, was ja bedeutet, daß man sich am Anfang der Liste be-
findet.
Bei Eintritt in die while-Schleife wird also knoten über knoten -> naechster auf Ebe-
ne 3 positioniert:
HANS \0
1.Ebene
FRANZ \0
2. Ebene
Adresse der
Struktur aus
Ebene 1
knoten - > name
knoten - > naechster
Mit printf wird
knoten-> name
am Bildschirm aus-
gegeben. hier
HARALD
3. Ebene
4.Ebene
13-51
Beim nächsten Durchlauf der while-Schleife wird knoten über knoten -> naechster
auf Ebene 2 positioniert:
knoten - > name
knoten - > naechster
Mit printf wird
knoten-> name
am Bildschirm aus-
gegeben. hier
FRANZ
1.Ebene
2. Ebene
3. Ebene
4.Ebene
13-52
Beim darauffolgenden Durchlauf der while-Schleife wird knoten über knoten ->
naechster auf Ebene 1 positioniert:
knoten - > name
knoten - > naechster
1.Ebene
Adresse der
Struktur aus
Ebene 1
FRANZ \0
Mit printf wird
knoten-> name
am Bildschirm aus*
gegeben, hier.
HANS
2. Ebene
HARALD \0
3. Ebene
Adresse der
Struktur aus
Ebene 2
4. Ebene
Da nun knoten -> naechster == NULL, wird die while-Schleife nicht mehr durchlaufen
und das Programm ist beendet.
Beispiel 8
Beim Skilanglauf werden die einzelnen Läufer in 30-Sekunden-Abständen auf die Strecke
geschickt. Beim Zieleinlauf eines Läufers soll immer ein aktueller Zwischenstand angege-
ben werden.
Bei der Lösung dieser Aufgabenstellung könnten wir nun mit einem Vektor arbeiten; dies
würde aber bei jedem Zieleinlauf erhebliche Umspeicherungen auslösen, um den Läufer im
Vektor an die entsprechende Position zu bringen, die er zum Zeitpunkt seiner Ankunft ein-
nimmt.
Um derart aufwendige Speicherverschiebungen zu vermeiden, werden wir hier mit einem
binären Baum arbeiten.
Dieser Baum enthält einen Knoten für jeden Läufer, der das Ziel schon erreicht hat; ein sol-
cher Knoten enthält dabei folgende Informationen:
13-53
(1) Name des Läufers
(2) Nationalität des Läufers
(3) benötigte Zeit
(4) Zeiger auf den linken Nachkommen im binären Baum
(5) Zeiger auf den rechten Nachkommen im binären Baum
Die Philosophie eines Binärbaumes ist:
Ein Knoten kann nicht mehr als zwei Nachkommen besitzen; ein oder gar kein
Nachkomme ist allerdings möglich.
Die Knoten sollen so angeordnet werden, daß bei jedem Knoten der linke Unterbaum nur
Läufer enthält, die eine schnellere Zeit gelaufen sind, und der rechte Unterbaum nur lang-
samere Läufer aufweist:
Trifft nun ein Läufer im Ziel ein. so wird dessen Zeit zunächst mit der des Läufers in der
Wurzel verglichen. Ist der angekommene Sportler eine kürzere Zeit gelaufen, so wird die
Suche mit dem linken Nachkommen fortgesetzt; andernfalls wird beim rechten Nachkom-
men weiter untersucht.
Es handelt sich hier um einen rekursiven Suchvorgang, da bei jedem Knoten auf einen der
beiden möglichen Nachkommen verzweigt werden muß, wenn keine Übereinstimmung
festgestellt wird. Es bietet sich folglich an, rekursive Funktionen bei der Realisierung die-
ser Aufgabe zu verwenden.
Bevor wir das C-Programm angeben, sollen noch einige Modalitäten geklärt werden:
• Bei Zieleinlauf eines Sportlers sind dessen Daten über Bildschirm einzugeben (Name,
Nationalität, Zeit); nach dieser Eingabe wird der neue Zwischenstand eingeblendet.
• Nach Ankunft des letzten Läufers ist bei der Frage nach dem Namen des nächsten Läu-
fers das Zeichen ♦ einzugeben; daraufhin wird die endgültige Platzverteilung ausgege-
ben.
13-54
• Sind zwei oder mehrere Langläufer exakt die gleiche Zeit gelaufen, so sind sie alphabe-
tisch geordnet in der Zeittabelle anzugeben.
finclude <stdio.h>
typedef struct knoten (
char nawetlSJ; /• Nane des Laeufers • /
char natloC51; /♦ National!taet (Abkuerzung) •/
cnar zelttlZJ; /• gelaufene Zeit •/
struct knoten «links; /• linker Nachkonne •/
struct knoten «rechts;/« rechter Nachkoexie •/
> KNOTEN;
ain ()
(
KNOTEN «wurzel, «elnordnen();
char wertlSJ, woherti], uhrtlZl;
int 1«1 , pruef, platz;
wurzelsNULL;
do (
printf(“XnXnNawe des gerade eingelaufenen Sportlers ’Xn");
eingaQ(wer);
if (werCO] •= «•) <
printf(-\n\nNationalitaet des Sportlers ’\n">;
eingap(woher);
do t
printf("\n\ngelaufene Zelt (hh/we/ss.nn) ?\n“>;
if ((pruefxelngaö(uhr)J 's 11)
printf("\n\n\nFal5che Zeitangaoe\n\nElngabe wiederholenl\n");
) while (pruef !* 11);
wurzel=einoronen(wurzel,wer.woher,uhr);
printf("\n\n\n\n\nZwiscnenstand nach Xd Laeufer\n",i♦♦);
)
eise
printf (•\.n\n\n\n\nEndergeDnis:\n“);
printf("XnXl2s X-i5sX5s Xs\n-,-Nr-,-Naee-,'Land*,-Zeit");
platz»l;
druckebaue(wurzel.frplatz);
) while (werCDJ f« •’);
eingaö(text)
char «text;
(
int 1»O;
while ((textei**]=getchar()) •« \n)
textCi-i]s \0 ;
returnt i-1);
KNOTEN «einordnen(knot zeig,Kennung,Staat,laufzeit)
KNOTEN «knot.zeig;
char «Kennung, «Staat, «laufzeit;
(
KNOTEN «reservieret);
int zeit vergl;
if (knot_zeig==NULL) <
knot_zeig = reserviere();
strcpy(knot.zeig->naee,Kennung);
strcpy(knot2zeig->natio,Staat);
strcpy(knotlzeig->zeit,laufzeit);
knot_zeig->links = knot_zeig->rechts = NULL;
if (((zeit verglxstrcep(laufzeit,knot_zeig->zelt)) < 0) Il
((zeit vergl =s 0) && (streep(Kennung.knot zeig->naee) < 0) ))
knot zeig->1inks=e1nordnen(knot_zeig->links,kennung,Staat,laufzeit);
eise
knot_zeig->rechts=einordnen(knot.zeig->rechts,kennung,Staat,laufzelt);
return(knotJzeig);
13-55
druckebau«(Knot,zeig,nr)
KNOTEN «knot.zeig;
int »nr;
(
if (knot.zeig '= NULL) (
0 rucke bau» (knot.zeig->links ,nr);
printf (“XI 2b X-15sX5sX1 Zsxn“, (*nr)♦♦,knot_zeig->na«e,knot.zeig->natio,knot_zeig->zeit);
druckebau«(knot zeig->rechts,nr);
KNOTEN ♦reservieret)
<
char •nallocl);
return((KNOTEN •)
malloc(sizeof(KNOTEN)));
Erläuterungen:
Nehmen wir an, von 100 gestarteten Läufern laufen lediglich 7 in folgender Reihenfolge im
Ziel ein:
MALLONI ITA 01/29/37.19
MUELLER SUI 01/12/48.17
HANSON SWE 01/20/10.33
BICHLER AUT 01/11/59.12
NICOLS USA 01/32/12.04
JUNG AUT 01/34/23.47
DORNON CAN 01/12/45.57
In main wird zunächst der Strukturzeiger wurzel mit NULL initialisiert.
In der darauffolgenden do ... while-Schleife, die bei Eingabe von * (bei der Namensan-
gabe) verlassen wird, werden durch Aufruf der Funktion eingab die entsprechenden Läu-
ferdaten eingelesen.
Für unsere Läuferdaten ergibt sich nach dem ersten Durchlauf des Programmteils:
printf("\n\nName des gerade eingelaufenen Sportlers ?\n">;
eingao(wer);
lf twerCQ] »s • ) (
printf("\n\nNationalitaet des Sportlers ?\n“);
eingab(woher);
do {
printf("\n\ngelaufene Zeit (hh/»«/ss.hh)
if ((pruef=eingaö(uhr)) '= 11)
printf ("XnXnXnFalsche Zeitangabe\n\nEingabe wiederholen?\n");
) while (pruef 's 11) ;
folgende Zuordnung:
wer
woher
uhr
MALLONIXO
ITAXO
01/29/37.19X0
Mit der nächsten Anweisung
wurzel = einordnen (wurzel, wer, woher, uhr);
wird die Funktion einordnen aufgerufen und dort der Programmteil
13-56
if (knot_zeig*=NULL) <
knot_zeig=reserviere();
strcpy(knot_zeig->name,kennung);
strcpy(knot~zeig->natiot5taat);
strcpy(knot_zeig->zeit,laufzei t);
knot_zeig->link5 s knot_zeig->rechts = NULL;
ausgeführt, da der zu knot_zeig gehörige aktuelle Parameter gleich NULL ist:
Mit knot_zeig = reserviere ();*) wird ein zusammenhängender Speicherplatz reserviert,
der groß genug ist, um die Struktur knoten aufzunehmen, und die Anfangsadresse dieses
Speicherbereichs in knot__zeig festgehalten.
Mit den nachfolgenden 3 strcpy-Aufrufen werden die Daten des Läufers Malloni in den
reservierten Speicherbereich kopiert.
Da ein neuer Knoten im Binärbaum generiert wurde und für diesen Knoten noch keine
Nachkommen vorliegen, müssen die Zeiger für den linken und den rechten Nachkommen
mit NULL besetzt werden:
knoC_zeig -> links = knot_zeig -> rechts = NULL;
Mit return (knot_zeig); wird ein Zeiger auf den gerade generierten Knoten an die aufru-
fende Funktion, hier main, zurückgegeben, so daß sich folgendes Bild ergibt:
Nach der Rückkehr nach main wird dort zunächst der Text
Zwischenstand nach 1 Laeufer
Nr Name
Land Zeit
als Tabellenüberschrift ausgegeben.
Nachdem die int-Variable platz auf 1 gesetzt wurde, wird die Funktion druckebaum mit
den aktuellen Parametern wurzel und &platz aufgerufen:
oruckeoau«(knot zeig,nr)
KNOTEN ♦knot.zeig;
int »nr;
(
if (knot.zeig ’s NULL) (
druckeöau«(knot_zeig->links,nr);
printfi"11 2d X-TSsXSsXI2s\n",(•nr)*+,knot_zeig->nawe,knot_zeig->natio,Knot_zeig->zei t);
druckedau« <knot_zeig->rechts,nr);
Diese Funktion druckt die Läuferdaten der einzelnen Knoten in der sogenannten inorder-
Folge aus, das heißt zu jedem Knoten werden zunächst der linke Unterbaum (alle bezüg-
lich des gerade betrachteten Knoten schnelleren Läufer), dann die Läuferdaten des Kno-
ten selbst, und schließlich der rechte Unterbaum (alle langsameren Langläufer) ausgege-
ben.
HINWEIS: *) Die Funktion reserviere entspricht weitgehend der aus Beispiel 7.
13-57
Da für unser Beispiel bisher lediglich ein Knoten vorliegt, kann auch nur dieser am Bild-
schirm ausgegeben werden.
Da die Bedingung der do ... while-Schleife: wer [O] ! = ’♦* in der Funktion main erfüllt
ist, werden die zugehörigen Schleifenanweisungen ein weiteres mal durchlaufen.
Dabei werden zunächst die Daten des zweiten Läufers, der das Ziel erreichte, eingelesen:
wer => MUELLER\O
woher ► SUI\O
uhr ► 01/12/48.17X0
Nach der Eingabe der Daten für den 2. Läufer wird mit
wurzel = einordnen (wurzel, wer, woher, uhr);
wieder die Funktion einordnen aufgerufen; diesmal wird allerdings der Programmteil:
if (((zelt vergl=fitrc®p(laufzeit,knot_zeig->zeit)) < 0) II
((zeit vergl == 0) && (strc«p(kennüng,knot_zeig->na«ie) < 0)))
knot_zeig->links=elnordnen(knot_zeig->links,kennung,Staat»laufzeitl;
eise
knot_zelg->rechts=elnordnen(knot_zeig->rechts,kennung,Staat,laufzeit)
ausgeführt, da der zu knot—zeig akutelle Parameter wurzel nun von NULL verschieden
ist.
In dieser If-Abfrage hier sind nun 2 Bedingungen verknüpft:
(1) Ist der gerade eingetroffene Läufer eine kürzere Zeit gelaufen als der Läufer des gera-
de betrachteten Knoten?
«zeit—vergl = st rem p (laufzeit, knot_zeig -> zeit)) < O) oder (II)
(2) Ist der gerade eingetroffene Läufer die gleiche Zeit gelaufen wie der Läufer des gerade
betrachteten Knoten? Und (Ä &) ist der Name des gerade angekommenen Läufers al-
phabetisch vor dem Namen des Läufers aus dem gerade betrachteten Knoten zu stel-
len?
«zeit—vergl = = O) & Ä (strcmp(kennung, knot—zeig -> name) < O))
Für unseren Fall ist die Bedingung (1) erfüllt, und somit auch die Gesamtbedingung, so daß
die Anweisung
knot—zeig -> links = einordnen (knot—zeig -> links, kennung, Staat, laufzeit);
ausgeführt wird; einordnen ruft sich also selbst wieder auf.
Dieser erneute Aufruf bewirkt folgendes Bild:
13-58
wurzel
Mit return (knot_zeig); wird immer die Adresse des gerade behandelten Knoten an die
übergeordnete Ebene übergeben. Von der 1. rekursiven Ebene wird hier z. B. die Adresse
des Knotens 'MUELLER' zurückgegeben; diese Adresse wird wegen
knot__zeig -> links = einordnen (knot__zeig -> links, kennung, Staat, laufzeit);
im 'MALLONI’-Knoten unter der Komponente knot__zeig -> links, eigentlich im zuge-
hörigen aktuellen Parameter wurzel -> links, gespeichert.
Nach Ausgabe einer Tabellenüberschrift wird wieder die Funktion druckebaum aufgeru-
fen; der Aufruf dieser Funktion bewirkt zu diesem Zeitpunkt folgende Bildschirmausgabe:
1 MUELLER
2 MALLONI
SUI 01/12/48.17
ITA 01/29/37.19
Da die Bedingung der do ... while-Schleife: wer [0] ! = in der Funktion main erfüllt
ist, werden die zugehörigen Schleifenanweisungen ein weiteres mal durchlaufen.
Dabei werden zunächst die Daten des dritten Läufers, der das Ziel erreichte, eingelesen:
wer
woher
uhr
HANSONXO
SWEXO
01/20/10.33X0
Nach der Eingabe der Daten für den 3. Läufer wird mit
wurzel = einordnen (wurzel, wer, woher, uhr);
die Funktion einordnen aufgerufen; in dieser Funktion wird nach Überprüfung der ent-
sprechenden Bedingungen die Anweisung:
knot_zeig -> links = einordnen (knot_zeig > links, kennung, Staat, lauf-
zeit);
ausgeführt.
13-59
Bei diesem rekursiven Aufruf von einordnen wird nach der Überprüfung der vorgegebe-
nen Bedingungen die Anweisung
knot___zeig -> rechts = einordnen (knot____zeig -> rechts, kennung, Staat,
laufzeit);
ausgeführt, so daß sich folgendes Bild ergibt:
wurzel
if (knot_ze1g==NULL) (
knotzeigsreserviere();
strcpy(knot_zeig->name,kennung);
strcpy(knot”zeig->natio,Staat) ;
strcpy(knotJzeig->zeit,laufzeit);
knot zeig->Iinks » knot zeig->rechts = NULL;
)
Nach Ausgabe einer Tabellenüberschrift wird wieder die Funktion druckebaum aufgeru
fen, was zu folgender Bildschirmausgabe fürht:
1 MUELLER
2 HANSON
3 MALLONI
SUI 01/12/48.17
SWE 01/20/10.33
ITA 01/29/37.19
Da die Bedingung der do ... while-Schleife: wer [O] ! = in der Funktion main weiter-
hin erfüllt ist, werden die Daten des 4. Läufers eingelesen:
wer
woher
uhr
BICHLERXO
AUTXO
01/11/59.12X0
13-60
Nach der Eingabe der Daten für den 4. Läufer wird mit
wurzel = einordnen (wurzel, wer, woher, uhr);
wieder die Funktion einordnen aufgerufen; der rekursive Ablauf dieser Funktion soll
durch nachfolgendes Bild erläutert werden:
wurzel
if <knot_zelg==NULL) {
knot_£eig = reserviere();
strcpy (knot_zeig->nawie, kennung) ;
strcpy(knot2zeig->natlo,Staat) ;
strcpy(knotlzeig->zeit,laufzeit);
knot_zeig->links « knot_zeig->rechts = NULL;
Nach Ausgabe einer Tabellenüberschrift wird wieder die Funktion druckebaum aufgeru-
fen, was zu folgender Bildschirmausgabe führt:
1 BICHLER
2 MUELLER
3 HANSON
4 MALLONI
AUT 01/11/59.12
SUI 01/12/48.17
SWE 01/20/10.33
ITA 01/29/37.19
Hier können wir die Arbeitsweise der Funktion druckebaum sehr schön erkennen:
Es wird immer zuerst der vollständige linke Unterbaum ausgegeben, bevor ein Knoten
selbst und dann sein rechter Unterbaum ausgegeben wird.
Da die Bedingung der do ... while-Schleife: wer [O] ! = in der Funktion main weiter-
hin erfüllt ist, werden die Daten des 5. Läufers eingelesen:
13-61
wer
woher
uhr
NICOLSXO
USA\O
01/32/12.04
Nach der Eingabe der Daten für den 5. Läufer wird mit
wurzel = einordnen (wurzel, wer, woher, uhr);
wieder die Funktion einordnen aufgerufen; der rekursive Ablauf dieser Funktion soll
durch nachfolgendes Bild erläutert werden:
wurzel
Nach Ausgabe einer Tabellenüberschrift wird wieder die Funktion druckebaum aufgeru-
fen, was zu folgender Bildschirmausgabe führt:
1 BICHLER AUT 01/11/59.12
2 MUELLER SUI 01/12/48.17
3 HANSON SWE 01/20/10.33
4 MALLON! ITA 01/29/37.19
5 NICOLS USA 01/32/12.04
Nachdem die beiden Läufer JUNG und DORNON noch eingegeben und im Binärbaum mit
der Funktion einordnen an der entsprechenden Stelle "eingehängt" wurden, ergibt sich
folgender Binärbaum:
13-62
Wurzel
Wird dann bei der Namensfrage das Zeichen * eingegeben, dann wird das Endergebnis am
Bildschirm ausgegeben:
Endergebnis:
Nr Name Land Zeit
1 BICHLER AUT 01/11/59.12
2 DORNON CAN 01/12/45.57
3 MUELLER SUI 01/12/48.17
4 HANSON SWE 01/20/10.33
5 MALLONI ITA 01/29/37.19
6 NICOLS USA 01/32/12.04
7 JUNG AUT 01/34/23.47
Eine Baumstruktur ist abhängig von der Reihenfolge der Eingabe: Wären die Langläu
fer z. B. in folgender Reihenfolge ins Ziel gekommen:
13-63
DORNON
JUNG
NICOLS
BICHLER
MUELLER
HANSON
MALLONI
dann hätte sich folgende Baumstruktur ergeben:
Die Zahlen hinter den Knoten geben die Reihenfolge an, in der die Funktion druckebaum
die Knoten ausgeben würde. Es ist hierbei wieder sehr gut zu erkennen, daß drucke-
baum zuerst den vollständigen linken Unterbaum ausgibt, bevor es einen Knoten selbst
und dessen rechten Unterbaum ausgibt.
13-64
Wir haben in diesem Beispiel mit einem geordneten Binärbaum gearbeitet; dabei wurde der
binäre Baum nach folgenden Regeln aufgebaut:
(1) Jeder linke Unterbaum eines Knotens enthält nur Läufer, die eine schnellere Zeit bzw. eine
gleichschnelle Zeit liefen (und alphabetisch weiter vorne einzuordnen sind).
(2) Jeder rechte Unterbaum eines Knotens enthält nur Läufer, die eine langsamere Zeit bzw.
eine gleichschnelle Zeit liefen (und alphabetisch weiter hinten einzuordnen sind).
Die Ausgabe der Knoten erfolgte in unserem Beispiel nach der inorder-Folge:
(1) alle linken Nachfolger eines Knotens (falls vorhanden)
(2) Knoten selbst
(3) alle rechten Nachfolger eines Knotens (falls vorhanden)
13.8 DAS SCHLÜSSELWORT Union
unions*> und Strukturen sind eng verwandt; ihre Syntax unterscheidet
sich lediglich im Schlüsselwort: Statt struct schreibt man union.
Der funktionelle Unterschied besteht darin, daß die Elemente einer
union alle an der relativen Adresse 0 beginnen, also denselben Spei-
cher belegen:
union {
int zahl;
char buchstab;
double doppelt;
}beisp;
Die Komponenten beisp.zahl. beisp.buchstab und beisp.doppelt
nehmen denselben Speicherplatz ein:
HINWEIS: *) LATTICE-C unter MS-DOS folgt der älteren Version von C. unter der folgen-
de Operationen nicht erlaubt sind:
- Zuweisung einer union-Variable an eine andere union-Variable
- Übergabe einer union als Funktionsparameter
- Rückgabe einer union als Ergebniswert einer Funktion
13-65
Die Länge einer union richtet sich nach dem längsten Element, in un-
serem Beispiel also nach beisp.doppelt . Hätten wir dagegen folgen-
de Deklaration angegeben:
struct {
int zahl;
char buchstab;
double doppelt;
} beisp;
dann hätte sich folgende Speicherbelegung ergeben:
relative
Adresse
(OFFSET):
o---------------
zahl
2 buchstab
4 “
6 ” doppelt
8
10 ~
Hier wird für jede einzelne
Strukturkomponente ein eigener
Speicherplatz reserviert.
13-66
union benötigt man normalerweise, um Varianten*) im Aufbau einer
Struktur auszudrücken.
Nehmen wir als Beispiel eine Struktur, die den Satzaufbau einer Perso-
naldatei beschreibt. Da ein Teil der Firmenangehörigen ein festes Mo-
natsgehalt erhält und der andere Teil nach geleisteten Stunden bezahlt
wird, werden unterschiedliche Satzarten benötigt.
Bei Mitarbeitern, die nach geleisteten Stunden bezahlt werden, müssen
die Stundenzahl und der Stundenlohn gespeichert werden, während
beim anderen Teil auf diese Angaben verzichtet werden kann, aber da-
für deren Gehalt zu speichern ist. Die zugehörige Satzbeschreibung
sieht dann wie folgt aus:
struct pers_satz <
int pers_nr;
char nameCZOJ;
char vorna«e[2Q];
char adresseUQ];
char std_oö_gehalt; /• ’S fuer Gehalt oder ’S* fuer Stundenlohn •/
union (
float genalt;
struct <
float std_zahl;
float std’lohn;
) lohn;
> geld;
) person;
Soll dann an späterer Stelle im Programm der Monatsverdienst eines
Mitarbeiters, der nach Stunden bezahlt wird, am Bildschirm ausgege-
ben werden, so müßte man folgende Anweisung angeben:
printf(”\n%f”, person.geld.lohn.std_zahl • person.geld.lohn.std_lohn);
Soll dagegen der Monatsverdienst eines Mitarbeiters mit festem Gehalt
am Bildschirm ausgegeben werden, so müssen wir folgende Anweisung
schreiben:
printf(” \ n%f”, person.geld.gehalt);
Eine union ist tatsächlich eine Struktur, in der alle Komponenten die re-
lative Adresse 0 besitzen. Eine solche Struktur ist groß genug, um die
"größte” Komponente aufnehmen zu können.
Wie bei Strukturen gilt für die älteren Versionen von C, daß nur auf eine
Komponente zugegriffen werden darf; ebenso darf in den älteren Ver-
sionen union nicht als Ganzes zugewiesen, an Funktionen übergeben
oder als Ergebniswerte von Funktionen geliefert werden.
Zeiger auf union können jedoch genauso verwendet werden wie Zeiger
auf Strukturen.
HINWEIS: *) union entspricht den case-Varianten RECORD in PASCAL.
13-67
LATTICE-C unter MS-DOS folgt der älteren Version von C, unter der fol-
gende Operationen nicht erlaubt sind:
• Zuweisen einer Struktur an eine andere Struktur gleichen Typs.
• Übergabe einer Struktur als Parameter einer Funktion.
• Rückgabe einer Struktur als Ergebniswert einer Funktion.
13.9 BITFELDER
Bisher haben wir mit Elementen gearbeitet, die immer das Vielfache ei-
nes Bytes lang waren.
In C gibt es auch die Möglichkeit, mit Bitfeldern zu arbeiten, wenn dies
auch nicht so oft benötigt wird. Das Arbeiten mit Bitfeldern ist zum
einen dann vorteilhaft, wenn mit Speicherplatz sparsam zu verfahren
ist; zum anderen werden Bitfelder noch in zwei weiteren Bereichen ein-
gesetzt: Bei der Beschreibung von Hardware-Registern und zur Defini-
tion von Flag-Worten.
Es soll ein Programm erstellt werden, in dem zunächst folgende Daten eingelesen werden
sollen:
Name
Vorname
Familienstand (ledig = 0, verheiratet = 1, geschieden = 2, verwitwet = 3)
Geschlecht (männlich = 0, weiblich = 1)
Alter
Kinderzahl
Religion (rk = 0, ev = 1, islam. = 2, andere = 3)
Unser Programm soll dann alle Personen unter Angabe des Namens und Vornamens ausli-
sten, die folgende Daten besitzen:
verwitwet
weiblich
55 Jahre alt
3 Kinder
evangelisch
Die Deklaration
struct (
unsigned Stand : 2;
unsigned geschlecht : 1;
unsigned alter • 7;
unsigned kindzahl ; 4;
unsigned religion : 2;
} Mustert 100];
enthält 5 Bitfelder der Länge 2, 1, 7, 4 und 2 Bits. Jedes einzelne Bitfeld darf die Länge ei-
nes Rechnerworts (16 Bits) nicht überschreiten. Ist der Rest eines Wortes nicht mehr groß
genug, um das nächste Bitfeld aufzunehmen, so wird dieses Bitfeld Im nächsten Wort un-
tergebracht.
Die Anordnung der Bitfelder (von rechts nach links oder umgekehrt) ist rechnerabhängig.
13-68
C-Programm:
struct <
unsigned stand : 2;
unsigned geschlecht : 1;
unsigned alter : 7;
unsigned kindzanl : 4;
unsigned religlon : 2;
> austertlOOl;
ain()
unsigned 1, j, zahl;
char na«etlOO3t2O3, vornamet1001C203;
i«0;
printf("\n\nProgra«mende bei Eingabe von • bei» Namen !\n\n\n"l;
printf("\nName ?\n">;
scanf ("Xs".nameCl1) ;
while (naneti3tO3 != •') (
printf("XnVornane ?\n");
scanf("Xs",vornaaet13) ;
printf("\nStand (ledig=O, verheir.=1, geschieden=2, verwitwet=3)
scanf("Xd",&zahl);
usterti1.stand = zahl & 03;
printf("XnGeschlecht (aennlich=o, weiblich=l) ?\n“);
scanf ("Xd",&zahl);
ustert1].geschlecht = zahl & 01;
printf("\nAlter ?\n-);
scanf("Xd",&zahl);
usterti3.alter = zahl & 0177;
printf("XnKinderzahl ’Xn");
scanf("Xd"t&zahl);
usterti3.klndzahl » zahl i 017;
printf ("XnReligion (rk=0, evd, islaw=2, andere = 3) ’\n"J;
scanf("Xd",&zahl);
mustert 1].religlon * zahl & 03;
printf("\n\n\nNa«e 7\n");
scanf("Xs",name[iJ);
>
printf("\n\n\nDie betreffenden Personen sind:\n");
for (j=0 ; j<i ; J++)
if (mustertjJ.stand == 3 && »ustertj].geschlecht »» 1 &&
ustertj].alter «« 55 »ustertj3.klndzahl == 3 &&
ustertj3.religlon «« 1)
printf("Xs Xs\n",na«etj3 »vornaaetJ3);
Erläuterungen:
In diesem Beispiel wurden mehrere Informationen in einem Maschinenwort untergebracht:
geschlecht klndzahl
oder
13-69
stand
religion
A
alter
kindzahl
geschlecht
Die Anordnung der Bitfelder von rechts nach links (2) oder von links nach rechts (1) ist
rechnerabhängig.
In unserem Beispiel wurden die Bitfelder mit unsigned vereinbart; d. h.: die in den jeweili-
gen Feldern enthaltenen Bitmuster werden ohne Vorzeichen bewertet.
Daneben ist meist noch der Datentyp int für Bitfelder erlaubt *)
Weitere Besonderheiten, die es im Zusammenhang mit Bitfeldern zu
beachten gibt, sind:
• Vektoren von Bitfeldern sind nicht erlaubt
* Bitfelder besitzen keine Adresse; der Adreßoperator & darf folglich
nicht auf sie angewendet werden
HINWEIS: *) In ISIS-II sind auch noch die Datentypen char und unsigned char erlaubt.
13-70
13.10 DIE HIERARCHIE DER OPERATOREN
Die folgende Tabelle faßt die Prioritätsregeln aller Operatoren zusam-
men. Alle in einer Zeile angegebenen Operatoren besitzen den glei-
chen Rang. Die Zeilen sind in der Reihenfolge absteigender Priorität an-
geordnet; also haben zum Beispiel == und != den gleichen Rang, und
dieser Rang ist größer als der von & &
Operator 1 XXX3XSX3+38Z 1 bei gleicher Prioritaet
(1 C 1 ~> . von
links her
! • — - /• Minuszeichen •/ (cast) 1 von rechts her
• / • Verweisoper.•/ & /• Adressoper.»/ 1
sizeof 1
• / X 1 von links her
♦ - /♦ Subtraktion •/ 1 von links her
<< » 1 von links her
< <3 > >3 I von links her
s s 1 von links her
& /• bitweises UND •/ 1 von links her
• 1 von links her
1 1 von links her
&& l von links her
>—
11 1 von links her
— ——- »**-*«•«* —ob a» « «m • —— — — _ ——
?: 1 von rechts . her
3 ♦ 3 -8 • = / = X = » = « = S = 1= *» 1 ♦— von rechts » her
, /• Konna-Operator ♦/
von links her
SSSSS=SS33S«S*S 3*XS BSSSXSezaSfiSZZZS SZXZS SSSSZXSSZZZS 3 ZSZ ZS S S3 5 3S3SSSS3S = £S88SS s
Der in der rechten Spalte angegebene Hinweis von links her oder von
rechts her besagt, in welcher Reihenfolge die in dieser Zeile angegebe-
nen Operatoren ausgewertet werden, wenn sie gemeinsam in einem
Ausdruck auftreten. Z. B.: *++ zeig bedeutet, daß zunächst die Zeiger-
variable zeig um eine Position weitergesetzt wird, und dann erst auf
das Objekt zugegriffen wird, auf das jetzt zeig zeigt; diese Vorgehens-
weise liegt darin begründet, daß * und ++ gleiche Priorität besitzen, und
bei dieser Prioritätsstufe von rechts her ausgewertet wird. + + * zeig da-
gegen bedeutet, daß das Objekt, auf das zeig im Augenblick zeigt, um
den Wert 1 erhöht wird.
13-71
Besonders hervorzuheben ist, daß die Priorität der Operatoren zur Bit-
manipulation &, “ und I geringer ist als die der beiden Vergleichsopera-
toren = = und !=. Daraus folgt, daß beim Überprüfen bestimmter Bits,
wie etwa
if ((zahl I MASKE)! = O)
die Bit-Verknüpfungen in Klammern eingeschlossen werden müssen,
damit richtige Ergebnisse entstehen.
Vorsichtig sollten Sie auch mit Konstruktionen wie
(1)feld [!] = ! + +;
(2) printf(”%d %d \ n”, + + zahl, wurzel (zahl));
umgehen, da solche Anweisungen von den verschiedenen Compilern
unterschiedlich realisiert werden.
Im Fall (1) ist es beispielsweise nicht eindeutig, ob der alte Wert von I
oder der neue Wert als Index benutzt wird; sie sollten niemals die Be-
wertungsreihenfolge dem Compiler überlassen. Eine bessere Formulie-
rung von (1) wäre:
++I; feld[i] = i;
feld [i] — i; oder ++i;
Für Fall (2) gilt nahezu dasselbe; hier ist es nicht eindeutig, ob zahl vor
oder nach dem Funktionsaufruf um 1 inkrementiert wird. Dies ist wieder
von Maschine zu Maschine verschieden. Die nachfolgende Anweisung
ist dagegen eindeutig und zeugt von einem besseren Programmierstil:
+ + zahl;
printf(”%d %d\n”, zahl, wurzel (zahl));
13-72
14-1
DATEIEN 14
Jede in der Praxis einsetzbare höhere Programmiersprache muß in der
Lage sein, mit Dateien*) zu arbeiten. Die konkrete Zugriffsart auf Da-
teien hängt vom benutzten Betriebssystem ab.
Die meisten C-Compiler verwenden dabei das Ein-/Ausgabe (E/A)-Kon-
zept von UNIX, da UNIX das Betriebssystem ist, auf dem C entwickelt
wurde.
An dieser Stelle sei betont, daß das E/A-Konzept keineswegs durch die
Sprache C vorgegeben wird, sondern allein von Erfordernissen der Pra-
xis. So könnte man beispielsweise seine eigenen E/A-Routinen entwer-
fen und dabei Eigenschaften realisieren, die man für eine spezielle An-
wendung benötigt.
Im folgenden sollen die wichtigsten Unterschiede der Dateibearbeitung
für die Betriebssysteme
MS-DOS
UNIX
CP/M-86
ISIS-II
angegeben werden.
Beispielsweise verfügt CP/M nicht über die gleichen Zugriffsfunktionen
wie UNIX, deshalb emulieren C-Compiler, die für CP/M geschrieben
wurden, einige der UN/X-Dateizugriffsbefehle und -Funktionen.
Es läßt sich jedoch nicht ausschließen, daß Ihr Compiler einige Unter-
schiede aufweist. Sie sollten deshalb nicht auf einen Vergleich zwi-
schen den hier angegebenen Erläuterungen und Ihrem Compiler-Hand-
buch verzichten.
Beim Abspeichern werden Dateien in Blöcke unterteilt, deren Länge
dem Speichervermögen der Sektoren einer Diskette entspricht und mit
dem Betriebssystem variieren kann:
CP/M-86, UNIX, MS-DOS: 512 Bytes
ISIS-II: 128 Bytes
Von dieser Blockung merkt Ihr Programm allerdings nichts. Es betrach
tet eine Datei als eine durchgehende Kette von Bytes.
HINWEIS: *) Das englische Wort für Datei ist File
14-3
14.1 HÖHERE E/A-OPERATIONEN
Elementare E/A-Routinen, wie sie in Kapitel 14.2 behandelt werden, sol-
len dem Benutzer größtmögliche Kontrolle über den Ablauf von Datei-
zugriffen bieten. Höhere E/A-Routinen dienen dagegen eher dem Kom-
fort bei Dateizugriffen.
Der Benutzer höherer E/A-Routinen muß sich beispielsweise nicht um
die Pufferung der von Dateien gelesenen Daten kümmern. Unter Ver-
wendung der Bibliotheksfunktion malloc legen höhere E/A-Routinen
dynamisch einen programminternen Pufferbereich an, während andere
Bibliotheksfunktionen die Variablen für die Verwaltung des Pufferbe-
reichs anlegen.
Die Länge des Pufferbereichs variiert unter den einzelnen Betriebssy-
stemen und entspricht der Blockgröße:
ISIS-II:
MS-DOS, CP/M, UNIX:
128 Bytes
512 Bytes
14.1.1
Der Datentyp FILE
Höhere E/A-Funktionen arbeiten für ihre Datentransfers mit Datenpuf-
fern, für deren Verwaltung sie folgende typischen Informationen benöti-
gen:
- aktueller Pufferzeiger
- aktuelle Anzahl zu übertragender Bytes
- Anfangsadresse des Puffers
- Kontrollflags
- Dateinummer
Die Struktur dieser Informationen wurde mit Hilfe von struct definiert
und über ttdefine. in manchen Systemen auch über typedef, als Da-
tentyp FILE festgelegt. FILE befindet sich in jeder Systembibliothek
stdio.h, ist aber unter den einzelnen Betriebssystemen unterschiedlich
definiert:
14-4
Beispiel für CP/M-86 (Auszug aus Datei stdio.h)
struct _lobuf{
NORD fd;
NORD .Ha?;
BYTE i.base;
BYTE t.ptr;
NORD ent}
>1
•ifndef FILE
extern struct _iobuf _lobthAIFILES 1} /I HAXFILES > 1b • /
•define FILE struct _iobuf
•endif
Beispiel für ISIS-II (Auszug aus Datei stdio.h)
typedef struct FILE <
unsigned
unsigned
int
uns)gned
unsigned
unsigned
unsigned
) FUE;
int _flag;
int _countj
.ugetc;
char l.ptr;
char l.buf;
int _conn;
int _resp;
fl Flags 1/
ft Count reeaining •/
ft Ungot character •/
ft Pointer lf
ft Buffer ti
ft Connection lf
ft Response sailbox token for RMI lf
extern FILE _filet.NFILEJ; fl File structures, .NFILE»10 lf
Beispiel für UNIX (aus /I/)
typedef struct _iobuf (
char t_ptr; II Position des naechsten Zeichens lf
int .ent; /> Anzahl noch uebriger Zeichen tf
char t.base; /• Adresse des Puffers tf
int _flag; II Art des Zugriffs, etc. 1/
int flieg fl File Deskriptor 1/
1 FILE;
extern FILE .iobC.NFILE]; /I .MFILE - 20 II
Beispiel für MS-DOS (Auszug aus Datei stdio.h)
struct .iobuf
< char t ptr; /« current buffer pointer • /
mt . _cnt; /• current byte count H
char i.base; /• base address of I/O buffer 1/
char fl control flags 1/
char >; JHt; n file nueber 1/
extern struct _iobuf _iobC.NFILEl; /• .NFILE-Ib lf
•define FILE struct _iobuf
H shorthand 1/
Wurde mit der Compilerdirektive ttinclude <stdio.h> die Datei
stdio.h zum Bestandteil des Programms gemacht, dann können wir mit
der Deklaration
FILE «dateizeiger
dateizeiger als Zeiger auf eine FILE-Struktur vereinbaren. (Derartige
Zeiger heißen auch Filepointer.) Der Filepointer dateizeiger wird sei-
14-5
nerseits über fopen (siehe folgendes Kapitel) mit der gewünschten Da-
tei dateiname in Verbindung gebracht.
14.1.2 Das Eröffnen einer Datei
Jede Datei muß eröffnet werden, bevor auf sie zugegriffen, d. h. von ihr
gelesen bzw. auf sie geschrieben werden kann.
Eine Datei kann durch die Funktion fopen aus der Systembibliothek
stdio.h eröffnet werden. Die Funktion fopen besitzt zwei Parameter:
dateizeiger = fopen (dateiname, modus)
Unter dateiname ist der Name der zu eröffnenden Datei anzugeben;
der zweite Parameter, modus, gibt die gewünschte Zugriffsart auf die
Datei dateiname an:
”r” reiner Lesezugriff (engl. read = lesen)
”w” reiner Schreibzugriff (engl. weite = schreiben)
”a” Schreibzugriff am Ende der Datei, um die Datei
zu erweitern, (engl. append = anfügen)
”r + w”*> Lese- und Schreibzugriff (engl. auch update')
Wird der Modus ”w” oder ”a” angegeben, ohne daß eine Datei mit
Namen dateiname existiert, so wird eine neue Datei mit Namen da-
teiname angelegt. Bei Angabe des Modus ”w” wird eine eventuell
schon vorhandene Datei dateiname zum Schreiben eröffnet, womit
deren alter Inhalt verloren geht.
Unter UNIX, MS-DOS und CP/M-86 können Dateien für simultane Lese-
und Schreibzugriffe, genannt ’updates’, mit folgenden Angaben für den
Parameter modus angesprochen werden:
”r+” eröffnen einer Datei für ein update
”w+” neue Datei: anlegen einer Datei für ein update
alte Datei: löschen der Datei und eröffnen für ein
update
”a+” neue Datei: anlegen für ein update am Dateiende
alte Datei: eröffnen für ein update am Dateiende
HINWEIS: *) Dieser Modus ist nur bei ISIS-II möglich.
14-6
Außerdem sind im MS-C-Compiler unter MS-DOS Anfügungen an den
Parameter modus mit folgender Form und Wirkung verfügbar:
eröffnen einer Datei im Textmodus
b
eröffnen einer Datei im Binärmodus
Soll eine nicht vorhandene Datei dateiname zum Lesen eröffnet wer-
den, so ergibt dies einen Fehler. Im Fehlerfall liefert die Funktion fopen
immer den Zeigerwert NULL, der als symbolische Konstante in stdio.h
definiert ist.
Die Routine fopen aus der Systembibliothek erfüllt eigentlich die fol-
genden beiden Aufgaben:
(1) Dynamisches Anlegen einer FILE-Struktur
(2) Eröffnen einer Datei dateiname durch
Aufruf von maschinennahen Routinen.
Mit dem Start eines Programms - Startpunkt ist immer die Funktion
main - werden automatisch 3 FILE-Objekte mit zugehörigen Filepoin-
tern erzeugt, und zwar für:
Standard-Eingabe (engl.: STanDard INput)
Standard-Ausgabe (engl.: STanDard OUTput)
Standard-Fehlermeldungen (engl.: STanDard ERRor)
mit den zu diesen FILE-Objekten gehörigen Filepointern:
stdin
stdout
stderr
Normalerweise sind diese Filepointer mit dem Benutzerterminal verbun-
den; ihre Namen sind in der Datei stdio.h als Makros definiert:
CP/M-86, UNIX, MS-DOS:
ftdefine
•defin»
•define
stdin
stdout
stderr
(& lobLOJ)
(&_lobClJ)
<&_iobC23)
ISIS-II:
»define stdin
idefine stdout
idefine stderr
<&_fileCOJ)
(Ä.f UeCI D
(&_fileC2])
14-7
Die Routinen printf, scanf. getchar und putchar arbeiten wie d e
Routinen gets und puts, die wir noch kennenlernen werden, immer m t
diesen Standard-Dateien; d. h. Ein- oder Ausgaben, die unter Zuhilfe-
nahme dieser Funktionen erfolgen, beziehen sich immer auf das Termi-
nal, falls nichts anderes angegeben wird.
Es soll hier noch eine weitere Routine zum Eröffnen von Dateien vorge-
stellt werden, die unter allen 4 Betriebssystemen vorhanden ist:
neu___datzeig = freopen (dateiname, modus, dat____offen)
Die Routine freopen ist nahezu identisch zu fopen, besitzt aber noch
einen 3. Parameter dat__offen; bei diesem Parameter handelt es sich
um den Zeiger auf eine offene Datei, die bei Ausführung von freopen
geschlossen wird. Stattdessen wird eine neue Verbindung zwischen
dem Filepointer neu_datzeig und der Datei dateiname aufgebaut.
Die Routine freopen wird normalerweise zum Umlenken der Standard-
Ein/Ausgabe stdin/stdout in eine andere Datei benötigt.
Beispiel 1
Im Betriebssystem UNIX besteht die Möglichkeit, die Standardein- bzw. ausgabe von der
Kommandozeile aus auf andere Dateien umzulenken. Haben Sie beispielsweise ein C-Pro-
gramm cprogr erstellt, und rufen es in UNIX mit
cprogr < eindat.c
auf, dann holt sich cprogr seine Eingabedaten nicht vom Bildschirm, sondern von der Da-
tei eindat.c.
Mit dem Kommando
cprogr < eindat.c > ausdat.c
liest das Programm seine Eingabedaten von eindat.c und gibt seine Ausgabedaten auf
die Datei ausdat.c und nicht auf den Bildschirm aus.
Wir wollen nun ein einfaches C-Programm erstellen, das Zeichen bis zum Zeichen x ein-
liest und diese eingelesenen Zeichen mit ihrem dezimalen ASCII-Code wieder ausgibt.
Das Programm soll anhand der Kommandozeile erkennen, woher die Eingabe und wohin
die Ausgabe erfolgen soll.
14-8
Struktogramm:
Der Aufruf der Bibliotheksroutine exit (1); bewirkt, daß das Programm unverzüglich been-
det wird und alle noch geöffneten Dateien geschlossen werden.
C-Programm:
tmcluda <stdio.h>
••in (arge,argv)
int argci
char »argv[)|
<
FILE »fraopanUi
char iaieh|
if (arge ! 1)
if (argc>3)
printf ("XnZuvitla Paraaatar in dar Koaaandoxtila
•lat
«Mit (--arge)
if (••♦♦argv ‘<*1 C
if (fraopen(♦argv+!, •r’,«tdin> NULL) <
printf("tnDatai la kann nicht aroaffnat «trdan\n* ,»argvH) ।
•Mit <1>|
1
>
tlat (
if (»«argv ’>’) (
if (fraopan(»argv«l,*w*,atdout> NULL) (
printf("XnDatai Xa kann nicht aroaffnat wardan\n*,»argv+i)j
exit(l)j
>
1
tlaa (
printf("\nFalacht Paraaatarangaba •>!
printf(’(1.Zaichan auaa < od. > ttin) *\n")|
eiit(l)|
)
>
«teilt ((xaich-gatchar()) ! m*)
printf CXc Xd\n*,xaich,xaich) ।
14-9
Erläuterungen:
(1) Programmaufruf mit
cprogr—l
hallox—।
h 104
a 97
I 108
l 108
o 111
Eingabe am Bildschirm
Ausgabe am Bildschirm
(2) Programmaufruf mit
cprogr > ausgab.c-*-l
ASCII-Codex—।
Eingabe am Bildschirm
(Unter Lattice/MS-DOS ist diese Eingabe nicht
am Bildschirm sichtbar.)
Es erfolgt keine Bildschirmausgabe, sondern eine Ausgabe in die Datei ausgab.c
A 65
S 83
C 67
I 73
I 73
45
C 67
o 111
d 100
e 101
Diese Datei können Sie nach Programmende mit Hilfe des Editors anschauen und
bearbeiten.
(3) Die Datei eingab.c hat beispielsweise folgenden Inhalt:
testausgabexende
Der Programmaufruf cprogr < eingab.c > ausgab.c —। bewirkt dann keinerlei Bild-
schirmein- oder ausgabe; dafür enthält die Datei ausgab.c folgenden Text:
t 116
e 101
s 115
t 116
a 97
u 117
s 115
g 103
a 97
b 98
e 101
14-10
(4) Der folgende Aufruf bezieht sich auf das Betriebssystem ISIS-II. Die Symbole : LP:
stehen für den Drucker:
progr > : LP I
ASCII-Codex-—1
A 65
S 83
C 67
I 73
I 73
45
C 67
O 111
d 100
e 101
Eingabe am Bildschirm
Ausgabe am Drucker
Mit der Routine freopen kann also die Standardein- bzw. ausgabe
auf eine Datei umgelenkt werden.
Die Datei stderr für die Fehlermeldungen sollte allerdings immer
dem Terminal zugeordnet bleiben. Das Terminal ist für alle Fehler-
meldungen vorgesehen, und einige Bibliotheksfunktionen geben au-
tomatisch ihre Fehlermeldungen dorthin aus.
Wurde aber die Standardeingabe mit freopen auf eine Datei datei-
name umgelenkt, so lesen die Funktionen scanf. getchar und
gets nicht mehr vom Bildschirm, sondern von der Datei dateina-
me
Wurde die Standardausgabe mit freopen auf eine Datei dateina-
me umgelenkt, so schreiben die Bibliotheksroutinen printf, put-
char und puts nicht mehr auf den Bildschirm, sondern auf die Datei
dateiname
Unter CP/M-86. etwa im C-Compiler von DR, existieren noch zwei wei-
tere Funktionen zum Eröffnen von Dateien:
dateizeiger = fopena (dateiname)
fopena entspricht der Funktion fopen. Beide Funktionen
eröffnen eine Datei, in der ASCII-Zeichen gespeichert sind,
fopena ersetzt Zeilenvorschub/Wagenrücklauf-Zeichen,
englisch LF/CR*>, wie folgt: beim Schreiben wird die Zei-
chenfolge CR/LF durch LF ersetzt; beim Lesen aus der Datei
wird LF wieder in die Folge CR/LF verwandelt. Sollen diese
HINWEIS: *) Zeilenvorschub = engl.: Line Feed (LF); ASCII-Code OA
Wagenrücklauf = engl.: Carnage Return (CR); ASCII-Code OD
14-11
Operationen vermieden werden, ist die nachfolgende Funk-
tion fopenb für Binärdateien zu verwenden. Beispiel 4 in Ka-
pitel 14.1.4 zeigt praktische Unterschiede zwischen Text-
end Binärdateien.
dateizeiger = fopenb (dateiname)
Diese Funktion eröffnet eine Binärdatei ohne den Zeichen-
tausch für LF bzw. CR/LF wie bei fopena. Sollen in ISIS-II
und MS-DOS Binärdateien eröffnet werden, so ist dem Mo-
dusparameter z. B. der Funktion fopen der Buchstabe b an-
zufügen (siehe auch Kapitel 15, Funktionsübersicht).
neu_datzeig = freopa (dateiname, modus, dat_offen)
Die Funktion freopa entspricht der zuvor beschriebenen
Funktion freopen
neu__datzeig = freopb (dateiname, modus, dat_offen)
Die Funktion freopb eröffnet im Gegensatz zu freopa nicht
eine ASCII-Datei, sondern eine Binärdatei.
14.1.3 Das Schließen einer Datei
Offene Dateien werden bei Programmende automatisch geschlossen;
es gehört jedoch zu einem sauberen Programmierstil, offene Dateien
bereits nach Abschluß des Datenverkehrs zu schließen, da nicht belie-
big viele Dateien gleichzeitig offengehalten werden können.*
fclose hebt eine mit fopen hergestellte Verbindung zwischen dem Fi-
lepointer dateizeiger und der vom Benutzer bezeichneten Datei da-
teiname wieder auf. Der Filepointer dateizeiger ist damit für eine an-
dere Datei frei.
Eine bestehende Verbindung zwischen dateizeiger und dateiname
wird wie folgt mit fclose aufgehoben:
fclose (dateizeiger)
Beim automatischen Schließen von Dateien am Programmende wird
fclose systemintern aufgerufen.
HINWEIS: *) CP/M-86, MS-DOS 16 Dateien
ISIS-II: 10 Dateien
UNIX: abhängig von jeweiliger UNIX-Version (zwischen 15 und 25)
14-12
Bei Aufruf von fclose werden vor Unterbrechung der Verbindung zwi-
schen dateizeiger und dateiname die noch im Puffer enthaltenen
Zeichen auf die Datei dateiname geschrieben. Dieses Leeren von Puf-
fern und Schließen noch eröffneter Dateien wird beim Programmende
auch ohne explizite Angabe von fclose. d. h. automatisch vorgenom-
men.
Mit der Funktion fflush wird wie bei fclose der Puffer geleert, aber die
entsprechende Datei nicht geschlossen, d. h. die Verbindung zwischen
dateizeiger und dateiname wird mit
fflush (dateizeiger)
nicht unterbrochen.
Beide Funktionen, fflush und fopen. liefern den Wert O. falls ihre Aktio-
nen erfolgreich verliefen, ansonsten den Funktionswert-1 zurück.
14.1.4 Schreiben/Lesen von 1 Byte
Der Makrocode getc wird verwendet, um 1 Byte von einer Datei zu le-
sen:
zeich = getc (dateizeiger)
Bei diesem Makro ist nur 1 Parameter anzugeben: der Filepointer auf
die zu lesende Datei, getc liefert dann das nächste Zeichen aus der zu
lesenden Datei; mit obiger Anweisung wird also der char-Variablen
zeich das nächste Zeichen aus der Datei, auf die dateizeiger ver-
weist, zugewiesen.
Falls allerdings vor dieser Anweisung das Dateiende (engl.: End Of File)
bereits erreicht war, liefert getc den Wert EOF, der dann zeich zuge-
wiesen wird. Bei EOF handelt es sich um eine symbolische Konstante,
die in der Datei stdio.h*) mit
Odefine EOF(-1)
definiert ist und den Wert-1 repräsentiert.
Neben getc existiert noch die wirkungsgleiche Leseroutine fgetc.
HINWEIS: *) Die symbolische Konstante EOF ist in CP/M-86 in der Datei portab.h defi-
niert; diese Datei wird in CP/M-86 dann allerdings mit ttinclude <por-
tab.h> in die Datei stdio.h übernommen. Dagegen ist EOF etwa bei Micro-
soft MS-C bereits in stdio.h enthalten.
14-13
Das Makro putc wird verwendet, um 1 Byte auf eine Datei zu schrei-
ben:
putc (zeich, dateizeiger)
Dieses Makro putc besitzt zwei Parameter: der 1. Parameter zeich
enthält das Byte, das auf eine Datei zu schreiben ist. Der 2. Parameter
dateizeiger ist ein Filepointer, der mit der entsprechenden Ausgabe-
datei verbunden ist, z. B. durch folgende vorangehende Anweisung:
dateizeiger = fopen (dateiname, ”w”)
Die Funktion putc liefert für den Fall, daß beim Schreibvorgang kein
Fehler auftrat, den Parameter zeich als Funktionswert, andernfalls lie-
fert sie den Funktionswert EOF
Auch hier existiert noch eine weitere Schreibroutine, fputc. die völlig
wirkungsgleich zum Makro zu putc ist.
Beispiel 2
Von einer zuvor mit Hilfe des Editors beschriebenen Datei Spruch.c soll jedes Zeichen
einzeln gelesen und am Bildschirm ausgegeben werden.
C-Programm:
•include <stdio.h>
main (I
(
FILE •dat_zeiger, »fopenO;
char Zeichen; (+)
dat_zeiger=fopen(•Spruch.c","r");
if (dat zeiger == NULL)
printf("XnDatel sprach.c kann nicht eroeffnet werden
eise
while ((zeichen=getc(dat_zeiger)) '= EOF)
putchar(Zeichen);
fclose(datzelger);
W In Lattice-C und MS-C unter MS-DOS lautet diese Zeile
int Zeichen;
Erläuterung:
Die beiden Anweisungen
dat_zeiger = fopen ("spruch.c”, ”r”);
if (dat_zeiger = = NULL)
14-14
hätten wir auch zusammenfassen können zu:
if ((dat_zeiger = fopen (”spruch.c”, ”r”)) = = NULL)
Beispiel 3
Es ist ein C-Programm mit Namen kette zu entwerfen, das den Inhalt von den Dateien, die
beim Aufruf dieses Programms in der Kommandozeile angegeben werden, am Bildschirm
ausgibt.
Mit dem Aufruf
kette datl.c dat2.c—l
soll also zunächst der Inhalt von Datei datl.c und danach der Inhalt von dat2.c am Bild-
schirm ausgegeben werden.
Das zu entwerfende C-Programm kette ist vergleichbar mit dem UNIX-Dienstprogramm
cat.
C-Programm:
«include «stdio.h»
am (arge,argv)
int arge;
char «argvCl;
(
FILE «datzelg, • fopenO;
if (arge =« 1)
Kopie re datei(stdin);
eise
while (—arge > □) ,
if ((datzeigsfopen(•♦♦argv,*r")) == NULL) C
printf("\nDatei Xs kann nicht eroeffnet werden IXn",«argv);
exit(1);
>
eise <
kopiere datei(datzeig);
fclose(datzeig);
}
kopiere.datei(fllepointer)
FILE *filepointer;
cnar zeich; (+)
while ((zeich=getc(filepolnter)) EOF)
putc(zeich,stdout);
H In Lattice-C und MS-C unter MS-DOS lautet diese Zeile
int Zeichen;
14-15
Erläuterung:
Der Aufruf putc (zeich, stdout) bewirkt die Ausgabe von zeich auf der Standardausga-
be (Bildschirm); wir hätten hier auch putchar (zeich) angeben können. Die Filepointer
stdin und stdout sind bekanntlich in stdio.h für Standardeingabe und -ausgabe vordefi-
niert; Sie können folglich diese beiden Filepointer überall dort verwenden, wo ein Objekt
vom Typ FILE * erlaubt ist. Diese beiden Filepointer sind ebenso wie der Filepointer
stderr Konstanten und keine Variablen. Sie sollten deshalb nicht versuchen, diesen File-
pointern Werte zuzuweisen.
Die Funktionen getchar und putchar können wir als Spezialfälle der Makros getc und
putc auffassen:
ttdefine getchar () getc (stdin)
ttdefine putchar (zeich) putc (zeich, stdout)
Diese Formulierungen sind möglich, da getchar Zeichen von der Standardeingabe (Ter-
minal) liest, und putchar Zeichen auf die Standardausgabe (Bildschirm) schreibt.
Zu getchar() hier einige Beobachtungen und praktische Hinweise:
Bei Einlesefehlern liefert getchar() statt eines char-Werts einen int-Wert zurück, so
in MS-C/DR-C den Wert -1, in INTEL-C den Wert NULL. Zur Fehlerprüfung kann also erfor-
derlich sein, den Rückgabewert von getchar() einer Int-Variablen zuzuweisen.
Bei Mehrfachaufrufen von getchar() zum Einlesen einer Folge von Tastatureingaben
können Probleme dadurch auftreten, daß getcharf) Zeichen aus dem Tastaturpuffer au-
tomatisch - also nicht ausgelöst von der RETURN-Taste (CR) - an das System übergibt
Daher kann das Zeichen CR (hexadezimal OD) einer vorangegangenen Eingabe im Tasta-
turpuffer stehengeblieben sein und vom nachfolgenden getchar() fälschlich statt der ge-
wünschten neuen Eingabe gelesen werden. Hier ein Programm zur Erprobung des Verhal-
tens von getcharf) auf Ihrem System:
finclude <stdlo.h>
• ain ()
(
char zeichen_1, zeichen_2;
zeichen_1 x getcharO;
putchar(zei chen_1);
if (reichen 1 «
<
zeichen_2 = getcharO;
putchar(Zeichen 2>;
>
Nach dem Start wartet das Programm auf Ihre Eingabe, beispielsweise den Buchstaben
”c”. gefolgt von RETURN (CR). Das erste Zeichen ’ c” wird an zeich_1 übergeben; das
zweite ’CR’’ bleibt im Puffer. Beim nächsten Aufruf von getchar() wird der vorliegende In-
halt des Puffers, also das Zeichen ”CR”, sofort an zeich__2 übergeben. Um dies zu ver-
hindern, kann nach jedem getcharf) ein blinder getcharf );-Aufruf eingefügt oder die
Pufferung ausgeschaltet werden, beispielsweise bei UNIX mit stty cbreak
14-16
Oft stellt ein Programm bei der Eingabeverarbeitung erst dann fest, daß
es genug Zeichen eingelesen hat, wenn bereits ein Zeichen zuviel ein-
gelesen wurde. Für solche Probleme wäre es also vorteilhaft, wenn das
zuviel eingelesene Zeichen wieder in die Eingabe zurückgestellt wer-
den könnte. Die Standardbibliothek bietet mit der Funktion ungetc eine
solche Möglichkeit. Mit
ungetc (zeich, dateizeiger)
wird das Zeichen zeich in den Puffer zurückgestellt. Der nächste Lese-
befehl beginnt seinen Lesevorgang dann bei diesem Zeichen im Puffer.
Diese Funktion ungetc kann in Verbindung mit allen Eingabefunktionen
und Makros wie scanf. getchar oder getc verwendet werden.
Mit getc kann folglich das nächste Zeichen aus der Eingabedatei "vor-
ausgelesen” und, falls nicht benötigt, wieder zurückgestellt werden; es
kann aber lediglich 1 Zeichen mit ungetc wieder zurückgestellt wer-
den.
In den meisten C-Implementierungen können binäre Dateien eröffnet
werden, wie bereits in Kapitel 14.1.2 beschrieben wurde. Das nachfol-
gende Beispiel soll Unterschiede zwischen einer ASCII-Textdatei
und einer binären Datei zeigen:
Uber Bildschirm sollen Zeichen eingelesen werden, bis EOF (Control + Z-Taste) gesetzt
wird. Jedes einzelne eingelesene Zeichen soll sowohl auf eine binäre als auch auf eine
ASCII-Datei geschrieben werden; nach der Ausgabe eines Zeichens soll in beide Dateien
ein Zeilenvorschub-Zeichen \n ausgegeben werden.
C-Programm:
linclude <stdlo.h>
«am ()
(
FILE *dat ascli, ♦dat_binaer, •fopenf);
/• in CP/H-86 zusaetzlich nach: FILE •fopenüO; •/
char Zeichen; (+)
lf ((dat_ascii*fopen("ascli.c","w")) = = NULL)
printf("\nDatei ascll.c kann nicht eroeffnet werden !\n");
eise
if ((dat blnaersfopen(“blnaer.c","wb")) «= NULL)
/• in CP/M-86: if ((dat_blnaer=fopenö("binaer.cB,"w")) == NU11) •/
printf("\nDatei binaer.c “kann nicht eroeffnet werden !\n");
eise
while ((zeichen = getchar()) '» EOF) <
putc(Zeichen,dat.asci1);
putc('\n‘,dat ascli);
putc(Zeichen,dat_binaer);
putct’\n ,dat binaer);
>
fclose(datascii);
fclosetdat binaer);
}
(+) In Lattice-C und MS-C unter MS-DOS lautet diese Zeile
int Zeichen;
14-17
Erläuterung:
Dieses Programm habe den Namen bintest; die Eingabe laute wie folgt:
bintest^-l _______
Unterschied CTRL +[z]—J /* Eingabe •/ *)
Wenn Sie sich nun die beiden Dateien ascii.c und binaer.c am Bildschirm auflisten las-
sen. so besitzen sie folgenden Inhalt:
ascii.c:
U
N
T
E
R
S
C
H
I
E
D
binaer.c:
u
N
T
T
E
R
S
C
H
I
E
D
Vom Betriebssystem wurden alle \n (LF) zu
<CR> <LF> ergänzt.
Es wurden lediglich die \ n (LF)Zeichen abge-
speichert und vom Betriebssystem kein CR-
Zeichen davorgestellt
(Lattice-C, MS-C,TurboC und QuickC unter MS-DOS zeigen keine Unterschiede in der
Darstellungsform, da hier die beim Schreiben erfolgende Transformation LF —> CR. LF
beim Lesen rückgängig gemacht wird.
Alle höheren E/A-Routinen, die wir im folgenden noch behandeln wer-
den, verwenden die eben vorgestellten ”Byte-für-Byte”-Routinen getc.
putc oder ungetc
HINWEIS: *) Nach dieser Eingabe ist bei Turbo C unter MS-DOS nochmals
CTRL + [jp einzugeben.
14-18
14.1.5 Schreiben/Lesen von 2 Bytes
Es besteht auch die Möglichkeit, Worte, d. h. 16-Bit-Objekte, von einer
Datei zu lesen:
ganzzahl = getw (dateizeiger)
Diese Routine benötigt als Parameter lediglich einen Filepointer. Das
gelesene Wort wird als Funktionswert geliefert und der int-Variablen
ganzzahl zugewiesen.
Alle Bitmuster, die getw liefert, sind wirkliche Werte ohne Interpreta-
tion. Ein spezielles Zeichen wie EOF am Ende einer Datei wird also von
getw nicht decodiert. Eine Dateiendeprüfung kann hier nur unter Ver-
wendung des Makros feof (dateizeiger) vorgenommen werden.
Das Makro feof untersucht die Position der Datei, auf die momentan
dateizeiger zeigt, feof liefert den Wahrheitswert wahr, einen von Null
verschiedenen Wert, falls mit dem letzten Aufruf von getw das Ende
der Datei erreicht wurde.
Eine Besonderheit gilt es hier noch zu beachten: Entspricht die gelese-
ne Datenmenge einer ungeraden Anzahl von Bytes, dann liefert der
letztmögliche Aufruf von getw das noch verbliebene Einzel-Byte und
setzt eine Fehler- (Error-) Marke. Diese Fehlermarke kann durch Aufruf
des Makros ferror (dateizeiger) überprüft werden.
Es bestehen folglich bei der Verwendung von getw 2 Möglichkeiten,
das Ende einer Datei zu erkennen:
(1)EOF -Marke wird beim letzten Versuch, ein Wort zu lesen, gesetzt;
zuvor wurden aber schon alle Daten gelesen. Diese Sachlage ergibt
sich, wenn in der betreffenden Datei eine gerade Anzahl von Bytes
enthalten ist. Die Überprüfung, ob die EOF-Marke gesetzt ist, wird
mit feof durchgeführt.
(2) Die Fehlermarke wird beim letzten getw-Aufruf gesetzt, da lediglich
noch 1 Byte (kein Wort) gelesen werden kann. Die Überprüfung, ob
die Fehlermarke gesetzt ist, wird mit dem Makro ferror durchge-
führt.
ferror überprüft lediglich, ob die Fehlermarke für den angegebenen Fi-
lepointer gesetzt ist; dieses Makro setzt aber nicht die Fehlermarke zu-
rück. Soll die Fehlermarke wieder gelöscht werden, so ist
clearerr (dateizeiger)
anzugeben. Das Löschen der Fehlermarke ist besonders dann wichtig,
wenn im weiteren Programmlauf das ferror-Makro erneut für eine Prü-
fung der Fehlermarke aufgerufen wird.
14-19
Natürlich existiert auch die Möglichkeit, ein Wort (2 Bytes) auf eine Da-
tei zu schreiben:
putw (wort, dateizeiger)
Hier wird das Makro ferror zur Überprüfung des Schreibvorgangs auf
Fehler verwendet.
14.1.6 Schreiben/Lesen von 4 Bytes
Im Betriebssystem CP/M-86, etwa unter dem C-Compiler von Digital
Research, können auch 4 Bytes (32 Bits) gelesen und geschrieben wer-
den:
long_zahl = getl (dateizeiger)
Die getl-Funktion liefert hierbei die gelesenen 4 Bytes in der
vom Mikroprozessor 8086 vorgegebenen Reihenfolge.
putl (long^zahl, dateizeiger)
Die 4 zu schreibenden Bytes werden hierbei wieder in der
vom 8086 vorgeschriebenen Reihenfolge auf die mit datei-
zeiger verbundene Datei ausgegeben.
14.1.7 Schreiben/Lesen von Zeichenketten
Die einfachste Routine zum Lesen einer ganzen Zeichenkette von einer
Datei ist
fgets (vektor, maxzeichen, dateizeiger)
Diese Routine liest die nächste Zeile bis zum Endezeichen
\n (Zeilenvorschub) aus der Datei, auf die der Filepointer
dateizeiger weist, in das char-Feld vektor; dabei werden
allerdings höchstens maxzeichen - 1 Zeichen gelesen, ab-
hängig davon, was zuerst zutrifft: \n angetroffen oder max-
zeichen - 1 Zeichen gelesen, fgets fügt im Puffer, der mit
vektor adressiert ist, automatisch ein \O-Zeichen an die
gelesene Zeichenkette an.
Wurde beim Lesevorgang mit fgets nicht das Dateiende erreicht
(EOF-Marke nicht gesetzt), so liefert fgets als Funktionswert den Zei-
ger vektor; ansonsten liefert diese Routine den Zeigerwert NULL.
14-20
Die einfachste Routine zum Schreiben einer ganzen Zeichenkette auf
eine Datei ist:
fputs (vektor, dateizeiger)
Diese Routine schreibt den Inhalt von vektor auf die Datei,
mit der dateizeiger momentan verbunden ist.
Beispiel 5
Die beiden Funktionen fgets und fputs sollen mit den bisher kennengelemten E/A-Rou-
tinen realisiert werden. Die beiden Realisierungen*) wurden /1 / entnommen.
C-Programm:
«include <stdlo.h>
char »fgets(puffer,zahl,dat_zeig) /• hoechstens zahl Zeichen •/
char »puffer; ~ /• ueöer dat_zeig einlesen •/
int zahl;
register FILE »dat zeig;
<
register int zeich;
register char »zeiger;
zeiger=puffer;
while (—zahl>0 && (zeich=getc(datzetg)l1aEOF)
if ((»zeiger+*=zeich) = = \n’i
break;
•zeigers * \0' ;
return((zelchssEOF ii zeiger*«puffer) ’ NULL : puffer);
fputs(puffer,dat_zeig)
register char »puffer;
register FILE »dat zeig;
<
register int zetch;
while (zei chs»puf f ero) (+)
putc(zelch,dat_zeig);
(♦) Warnung von Turbo C kann mit while ((zeich =
• puffer + +) I = O) beseitigt werden
Es existieren noch 2 weitere Funktionen zur Ein- und Ausgabe von Zei-
chenketten:
gets (vektor)
Diese Routine liest eine Zeichenkette bis zum \n-Zeichen
von der Standardeingabe, also vom Terminal, und speichert
diese Zeichenkette im char-Vektor vektor. Im Unterschied
zur Routine fgets wird hier das \n-Zeichen nicht in vektor
übernommen, sondern mit dem \O-Zeichen überschrieben.
HINWEIS: *) Diese Realisierung setzt eine nicht gepufferte Implementierung von getc
voraus.
14-21
puts (vektor)
Diese Routine ist mit der Funktion printf vergleichbar; sie
gibt den Inhalt von vektor auf dem Bildschirm aus. Im Un-
terschied zur Routine printf erzeugt sie nach der Ausgabe
automatisch einen Zeilenvorschub, puts unterscheidet sich
noch in weiteren Punkten von printf:
• Ausgabe nur von Zeichenketten möglich (bei printf auch an-
dere Datentypen)
• keine formatierte Ausgabe wie bei printf möglich.
printf ist folglich universeller einsetzbar als puts
Es soll der Name einer Datei eingegeben werden, deren Inhalt dann am Bildschirm auszu
geben ist.
C-Programm:
finclude <stdio.n>
idefine MAX 100
main ()
€
char vektCMAX];
char dateinameLIOJ;
FILE »dateizeiger;
put5("\nGeben Sie einen Dateinamen ein »Sn*);
gets(dateiname);
if ((dateizeigerxfopen (dateiname, ** r*)) = x NULL)
printfC\nDatei Xs konnte nicht eroeffnet werden\n"»dateiname);
eise
while (fgets(vekt,MAX,dateizeiger) ' = NULL)
puts (vekt);
Bedenken Sie, daß puts automatisch einen Zeilenvorschub durchführt;
das heißt, daß dieses Programm den Inhalt einer Datei mit eingescho-
benen Leerzeilen am Bildschirm ausgibt, dafgets auch die in der Datei
enthaltenen \n-Zeichen im char-Vektor vekt speichert.
14.1.8 Schreiben/Lesen von Blöcken
Die C-Standardbibliothek sieht Möglichkeiten vor, Speicherblöcke auf
Dateien zu schreiben und umgekehrt. Mit der Funktion.
14-22
fread (vektor, byte___zahl, block__zahl, dateizeiger)
werden block___zahl Blöcke, wobei jeder Block aus byte_zahl Bytes
besteht, aus der Datei, die mit dateizeiger verbunden ist, gelesen und
in den Speicherbereich gebracht, der bei Adresse vektor beginnt.
Mit der Funktion
fwrite (vektor, byte__zahl, block__zahl, dateizeiger)
werden block___zahl Blöcke, wobei jeder Block aus byte_zahl Bytes
besteht, von einem Speicherbereich, der mit vektor adressiert ist, auf
die Datei geschrieben, die aktuell mit dateizeiger verknüpft ist.
Die Makros feof und ferror können hierbei benutzt werden, um das
Dateiende bzw. Übertragungsfehler zu erkennen.
Die beiden Routinen fread und fwrite liefern als Funktionswert die An-
zahl der Blöcke, die wirklich gelesen bzw. geschrieben wurden; diese
Zahl kann natürlich kleiner sein als die geforderte block_zahl.
fread und fwrite werden allerdings nicht sehr häufig verwendet,
da man zum Lesen bzw. Schreiben einer bestimmten Bytezahl vorzugs-
weise die elementaren Schreib- und Leseroutinen, die wir noch kennen-
lernen werden, verwendet.
14.1.9 Formatierte Ein-/Ausgabe
Zwei hierzu gehörige Funktionen haben wir bereits besprochen:
printf
scanf
Beide Funktionen benutzen die Standardein- bzw. ausgabe (Terminal).
Neben diesen beiden Funktionen existieren noch weitere Funktionen
zur fomatierten Ein- und Ausgabe:
Ausgabe:
fprintf (dateizeiger, kontrollzeichenkette, paraml,...)
wirkt wie printf, nur daß die Ausgabe auf diejenige Datei erfolgt, auf die
dateizeiger zeigt.
14-23
sprintf (zeichenvektor, kontrollzeichenkette, paraml,.. •)
Jede Zeichenfolge paraml, ... wird entsprechend den Formatanga-
ben in kontrollzeichenkette formatiert und in zeichenvektor abge-
speichert. Die Formatangaben erfolgen wie in printf.
Beispiel 7
Es ist ein Programm zu entwerfen, das über Bildschirm 10 Personen mit Nachname, Vor-
name, Postleitzahl. Wohnort einliest und diese Daten dann formatiert auf eine Datei, deren
Name der Programmbenutzer ebenfalls eingeben kann, ausgibt.
C-Programm:
finclude <stdio.h>
main ()
<
FILE «fopenO, •dateizeiger;
char dateinameE101;
char nachnaaieC201, vornametl 51, plz(51, ort(20], ueberschr(801;
int t;
puts("Dateiname ’>;
gets(dateiname);
if ((datelzeiger=fopen(dateiname,"w“)) == NULL)
printf("\nDatel zs kann nicht eroeffnet werden !\n",dateiname);
eise (
sprintf(ueöerschr,*Z20sXi5sX5sZ20s\n","Nachname","Vorname","Plz","Ort");
fputs(ueöerschr,dateizeiger) ;
for (1=0 ; 1<1O ; !♦♦) (
puts("Nachname ?");
scanf("Zs".nachname);
puts("Vorname ?");
scanf("Xs".vorname);
puts("Postleitzahl ’);
scanf("Xs",plz);
puts("Wohnort ’");
scanf ("Xs",ort);
fprintf(dateizeiger,"Z20sX15sX5sX20s\n".nachname,vorname,plz,ort);
1
fclose(dateizeiger);
Erläuterung:
Hat der Programmbediener beispielsweise den Dateinamen namen.c angegeben, dann
ergibt sich - nachdem der Benutzer alle Personendaten über Bildschirm eingegeben hat -
für die Datei namen.c z. B. folgender Inhalt:
Nachname Vorname Plz Ort
Heyer Hans 8520 Erlangen
Feierle Fritz 8000 Muenchen
Felser Franz 1000 Berlin
Scharier Guenther 8500 Nuernöerg
Haller Ute 8520 Erlangen
Seiler Dieter 8521 Hessdorf
Mueller sandra 8000 Muenchen
Wähler Hans 8500 Nuernöerg
Gertler Josef 8700 Wuerzöurg
Jollen Michaela 1000 Berlin
14-24
Beispiel 8
Im Betriebssystem UNIX existiert ein Kommando wc; bei Aufruf dieses Kommandos mit
beispielsweise
wc datl.c dat2.c
wird am Bildschirm ausgegeben, wieviele Zeilen, Wörter und Zeichen in den beiden einzel-
nen Dateien datl.c und dat2.c enthalten sind. Zudem wird noch angegeben, wieviele
Zeilen, Wörter und Zeichen die in der Kommandozeile angegebenen Dateien insgesamt
enthalten.
C-Programm:
sinclude <stöio.h>
main(arge,argv)
int arge;
cnar »argvCl;
(
int zeich, 1, lawort;
FILE »fopend, »datzeig;
long zeil_zaehler, wort_zaehler, zeich_zaehler;
long all_zeil=O, all_wort=0, all_zelchsQ;
1=1;
datzeig=stdin;
do <
lf (argc>‘ && (datzeig=fopen(argv[1r")) «« NULL) <
fprintf(stderr,"zaehl: Kann Xs nicht eroeffnenxn",argvt11);
continue;
)
zeil_zaehler = wort zaehler = zeich zaehler ® imwort * 0;
while ((zeichsgetctdatzeig)) ! = EOF)’ (
zelch_zaehler**;
if (zeich *= ’\n’J
zeil_zaehler*+;
if (zeich««- ii zeich®» \n ii zeich««-.* ii zeich««,' ii
zeich««’;’ ii zeich««’’’ li zeich««’!’)
i»wort=0;
eise
if (i«wort«=0) (
iewort=1;
wort zaehler**;
)
}
printf(argc>l ’ "Xs:\n" : "\n",argvtt]);
pnntf(“X20s X10s X1Os\n","Zeilen","Uoerter","Zeichen");
printf("X201d XlOld Xi01d\n\n",zeil_zaehler,wort_zaenler,zeich_zaehler);
all_zell*«zeil_zaehler;
all"wort*=wort“zaehler;
all_zeich*«zelch_zaehler;
} while’ (**i < arge)*;
lf (argc>2) <
printf("\nXs\n","Alle untersuchten Dateien zusammen:");
printf("X20s X10s X1Ds\n","Zeilen","Uoerter","Zeichen*);
printf("X201d X10ld X10ld\n",all_zeil,all_wort,all zeich);
14-25
Erläuterung:
Werden in der Kommandozeile keine Dateien als Parameter angegeben (arge = = 1), wird
die Standardeingabe analysiert; d. h.: es wird untersucht, wieviele Zeilen, Wörter und Zei-
chen Sie am Bildschirm eingeben. Diese Voreinstellung der Standardeingabe wird mit
datzeig = stdin; erreicht.
Anstelle von
fprintffstderr, ”zaehl:*) kann %s nicht eroeffnen\n”, argv [i]);
hätten wir auch
printf(”zaehl: kann %s nicht eroeffnen \ n”, argv [i]);
angeben können, da die Voreinstellung für Fehlermeldungen (stderr) das Terminal ist.
In unserem Programm sind folgende Zeichen als Wortbegrenzer vorgegeben: Leerzei-
chen, \n,,,?,!
Wollen Sie andere Wortbegrenzer festlegen, so müssen Sie lediglich die entsprechende
if-Abfrage ändern:
if (zeich««' ’ li zeich««*\n II zeich««*.* II zeich««,* li
zeich** li zeich««*’’ li zeich««*’*)
Eingabe:
fscanf(dateizeiger, kontrollzeichenkette, paraml,...)
wirkt wie scanf, nur daß als Eingabemedium nicht der Bildschirm, son
dern die Datei, die mit dateizeiger verbunden ist, genommen wird.
sscanffzeichvektor, kontrollzeichenkette, paraml,...)
Diese Funktion liest nicht vom Bildschirm, sondern aus der Zeichenket-
te zeichvektor ihre Daten.
Beispiel 9
char datenstringt] = ("Mayer wuroe aa 6.Maerz 1951 geboren”);
nain < I
C
char naael6], datumt81, jahrESJ;
sscanf (datenstr ing, "X5sX«5sX«2sX7sX5sX»7s" , naise,datue, jahr ) ;
printfi"\nGeöurtsdatua von Herrn Xs ist Xs Xs\n",name,datum,jahr);
}
HINWEIS: *) zaehl ist unser Programmname für das UNIX-Kommando wc
14-26
Enthält ein Formatelement das * Zeichen, so wird wie bei scanf das entsprechende Ein-
gabefeld einfach übersprungen. Für dieses Programm ergibt sich dann folgende Bild-
schirmausgabe:
Geburtsdatum von Herrn Mayer fist 6. Maerz 1951
14.1.10 Wahlfreier Zugriff (RANDOM-Zugriff)
Alle bisher behandelten höheren E/A-Funktionen waren nur für sequen-
tielle Dateizugriffe geeignet; d. h.; eine Datei wurde geöffnet und dann
fortlaufend von vorne nach hinten gelesen bzw. beschrieben. Bei Anga-
be des Modus a wurde ab Dateiende sequentiell geschrieben.
Diese Vorgehensweise ist vergleichbar mit einer Bandaufnahme und
hat den Nachteil, daß das Auffinden einer bestimmten Information sehr
lange dauern kann: Sequentielle Dateien zwingen nämlich dazu, alles
von vorne zu lesen und auszuwerten, bis die gesuchte Information ge-
funden ist.
Schnelleren Zugriff auf eine gesuchte Information bieten hier die Da-
teien mit wahlfreiem (RANDOM)-Zugriff. Bei solchen Dateien besteht
die Möglichkeit, mitten in der Datei stehende Information zu holen, ohne
die davor stehenden Daten lesen zu müssen. Dadurch wird die Zugriffs-
zeit auf bestimmte Daten wesentlich verkürzt.
C unterstützt diesen wahlfreien Zugriff auf Dateien; dabei muß aller-
dings dem Programmierer bekannt sein, als wievieltes Byte die ge-
wünschten Daten in der Datei stehen.
Mit der Funktion
fseek (dateizeiger, offset, wie)
kann man den Schreib/Lesezeiger innerhalb einer Datei verschieben,
ohne tatsächlich zu lesen oder zu schreiben. Mit fseek kann der
Schreib/Lesezeiger in einer Datei, die mit dateizeiger verbunden ist,
wie folgt verschoben werden:
wie = 0: vom Dateianfang O um offset Bytes auf Position
offset
wie = 1: von der augenblicklichen Position moment um off-
set Bytes auf Position moment + offset
wie = 2: vom Dateiende ende um offset Bytes auf Position
ende - offset
14-27
Zu erwähnen ist hier, daß der Parameter offset 32 Bit benötigt, wes-
halb entweder long-Variablen bzw. long-Konstanten als aktuelle Para-
meter zu verwenden sind.
Beispiel 10
Es ist ein Programm zu erstellen, bei dem der Benutzer sich 8 Byte lange Blöcke aus einer
Datei am Bildschirm anzeigen lassen kann. Dabei kann der Benutzer angeben, den wieviel-
ten Block aus der Datei er am Bildschirm haben möchte. Die Inhalte der 8 Bytes sind im
ASCII-Code (hexadezimal) am Bildschirm auszugeben:
C-Programm:
finclude <stdlo.h>
char rec(83;
main ()
FILE *datzeig;
FILE ♦ fopenO; /• In CP/M-86:
Int nummer;
char PC201, dateinamefl0);
FILE •fopenöt); •/
puts(“Dateiname ?•);
gets(dateiname);
if ( (datzeig^fopentdateiname^rb'’)) = * NULL) (
/• in CP/M-86: if C(datzeigsfopenbtdateiname,"r")) == NULL) < •/
printf(“\n\nDatei Xs kann nicht eroeffnet werden '\n“.dateiname);
exit(1) ;
)
while (puts(*\n\nBlock ?•),gets<b) I« NULL) <
nummer » atoi(b);
fseek(datzeig,(long)nummer«B,0) ;
fread(rec,sizeof(rec),1.datzeig);
printf("\nBlock Xd:"fnummer);
print() ;
)
exlt(O);
atoi(s)
char *s;
(
int c,n;
n = 0;
while 0)
n=iQ*n+c-0’;
return <n);
char hex[J « {
□ ,* 1 , 2 , 3', 4 ,*5 ,*6 ’ , *7 ,
8 * , ‘ 9 ’ , ’ A * , ‘ B ‘ ' C * , D ' , E * , ‘ F '
>•
print ()
<
int 1;
int byte;
14-28
for (1=0 ; Kfi ; ♦ ♦!) {
If (1 ! = 0)
putchart* *);
byte « recCiJ;
putcnar(hext(byte>>4)&0x0fJ);
putchar(hex[byte&OxOf]);
putcharC\n’);
Erläuterungen:
Mit
if ((datzeig = fopen (dateiname, "rb")) = = NULL)
versucht man in ISIS-II, die binäre Datei dateiname zu eröffnen; falls dies nicht möglich
ist (NULL), folgt eine Bildschirmausgabe; mit exit (1) wird das Programm beendet.
In CP/M-36 hätten wir
if ((datzeig = fopenb (dateiname, ”r”)) = = NULL)
angeben und «fopenb () statt «fopen () deklarieren müssen.
Der Programmteil
while (puts("\n\nBlock ?“),gets(b) •» NULL) <
nuniaer = atoi Cb);
fseek (datzeig, (long) numiuer^a,0);
fread(rec,sizeof(rec),1»datzeig);
printf ("\nBlock Xd: • »nuaieer);
printo;
)
sorgt zunächst für die Eingabe der Nummer des Blocks gets(b), der aus der Datei datei-
name zu lesen ist; die vom Benutzer über Bildschirm eingegebene Nummer wird als Zei-
chenkette behandelt und in das char-Feld b eingelesen.
Mit der Anweisung
nummer = atoi (b);
wird die Funktion atoi aufgerufen, die die Zeichenkette b in einen int-Wert umwandelt;
dieser int-Wert wird dann von dieser Funktion als Resultat geliefert und in der int-Varia-
blen nummer gespeichert.
Mit der Anweisung
fseek (datzeig, (long) nummer • 8, O);
wird der Schreib/Lesezeiger für die Datei, die mit datzeig verbunden ist, auf das Byte, das
vom Dateianfang (O) an gerechnet an der Stelle 8 * nummer steht, positioniert.
Mit der Anweisung
fread (rec, sizeof (rec), 1, datzeig);
werden dann ab der Position nummer * 8 in der Datei, die mit datzeig identifiziert wird,
sizeof (rec) Bytes gelesen und in das char-Feld rec gespeichert.
Mit der nachfolgenden Anweisung
printf ("Block %d:’’, nummer);
14-29
wird die Blocknummer ausgegeben, bevor mit dem Aufruf print (); der hexadezimale AS-
CII-Code zu den in rec gespeicherten Zeichen am Bildschirm ausgegeben wird.
Die Funktion
atol< s >
char ♦$;
<
int c,n;
n=0;
while ((c=*s++) ’s 0)
n = 1 On*c-’ □' ;
return(n >;
wandelt die Zeichenkette, die ab Adresse s gespeichert ist. in eine int-Zahl um. Wäre bei-
spielsweise im char-Vektor b die Zeichenkette ”384\O” gespeichert, so würde der Auf-
ruf nummer = atoi (b); folgendes bewirken:
n = 0;
Schleifendurchlaufnr.
n = 10 * n + c - ’O’
1.
2.
3.
n = 10 • 0 + 51 - 48 = 3
n = 10 *3 + 56-48 = 38
n = 10 • 38 + 52 - 48 = 384
Erklärung:
Dezimal-ASCII-Code zu ’0‘ ist 48
Dezimal-ASCII-Code zu 3’ ist 51
Dezimal-ASCII-Code zu '8' ist 56
Dezimal-ASCII-Code zu *4' ist 52
Es würde also 384 als Funktionswert geliefert und in der int-Variablen nummer gespei-
chert.
Die Funktion
print ()
<
int i;
int byte;
for (1=0 ; i<8 ; {
lf (i ’s □)
putchart* ');
byte = rectil;
putchar(hext(byte>>4)&OxOf]);
putchar(hextbyte&DxOf]);
>
putchart’\n');
gibt zu den im char-Feld rec gespeicherten 8 Zeichen den hexadezimalen ASCII-Code
am Bildschirm aus.
Dieses Programm benutzt die Funktion exit aus der Standardbibliothek, die bei Aufruf die
Programmausführung beendet. Eine übliche Vereinbarung ist, daß ein exit-Parameter mit
Wert O anzeigt, daß kein Fehler aufgetreten ist, und alle von 0 verschiedenen Werte auf
einen Fehler hinweisen. Unabhängig von möglichen Fehlern werden alle aktiven Dateien
durch den Aufruf von fclose geschlossen, um die möglichen Pufferinhalte noch in die ent-
sprechenden Dateien zu schreiben.
Anschließend ruft exit noch eine Funktion mit Namen_____exit auf, welche unverzüglich -
ohne die Puffer in die jeweiligen Dateien zu übernehmen - ein Programm beendet.
14-30
Selbstverständlich können Sie auch diese Funktion____exit direkt aufrufen, falls die Kon-
trolle ans Betriebssystem übergeben werden soll, ohne eventuell noch offene Puffer zu-
rückzuschreiben und offene Dateien zu schließen.
Im Zusammenhang mit der Routine fseek sind noch die Funktionen re-
wind und ftell zu erwähnen:
Mit rewind (dateizeiger) wird der Schreib/Lesezeiger auf den Anfang
der Datei, die mit dateizeiger verknüpft ist, gesetzt.
rewind (dateizeiger) entspricht fseek (dateizeiger, OL, O)
Mit ftell (dateizeiger) kann die momentane Position eines Schreib-
/Lesezeigers innerhalb einer Datei ermittelt werden; ftell liefert die Po-
sition als 'Anzahl Bytes vom Dateianfang’ zurück.
Natürlich können die hier vorgestellten Funktionen zum wahlfreien Da-
teizugriff nicht auf alle Dateien angewandt werden; beispielsweise nicht
auf die Standardeingabe oder -ausgabe.
14.2 ELEMENTARE E/A-ROUTINEN
Bis jetzt haben wir nur höhere E/A-Routinen kennengelernt, die aber auf
elementaren E/A-Routinen aufbauen. Die elementaren E/A-Routinen
greifen direkt in das jeweilige Betriebssystem ein.
Die einfachsten elementaren E/A-Routinen bieten weder Pufferung
noch andere Dienstleistungen. Verwendet der Programmierer solche
Funktionen, so muß er sich selbst um die Verwaltung kümmern, die bei
der Verwendung von höheren E/A-Routinen entfällt.
Andererseits ermöglichen die elementaren E/A-Routinen die größtmög-
liche Einflußnahme auf das Ablaufgeschehen bei Dateizugriffen; zudem
sind diese in C recht einfach zu handhaben.
14-31
14.2.1
Die elementaren Dateieröffnungs-
routinen
Bevor eine Datei gelesen bzw. beschrieben werden kann, ist sie zu er-
öffnen. Wird eine Datei mit einer elementaren Eröffnungsroutine eröffnet
und es tritt hierbei kein Fehler auf, so liefert die entsprechende Eröff-
nungsroutine als Funktionswert eine positive ganze Zahl, einen soge-
nannten Filedeskriptor zurück. Filedeskriptoren sind nach Funktion und
Inhalt zu unterscheiden von Filepointern, die Zeiger auf eine FILE-Struk-
tur darstellen.
Soll nun die eröffnete Datei gelesen bzw. beschrieben werden, so wird
bei den Lese- bzw. Schreiboperationen der Filedeskriptor anstelle des
entsprechenden Dateinamens, der durch eine Eröffnungsroutine mit
dem Filedeskriptor verbunden wurde, angegeben. Alle Verwaltungen,
die die Verbindung zwischen Datei und Programm betreffen, werden
vom System übernommen; der Benutzer verweist auf eine Datei nur mit
Hilfe des Filedeskriptors.
Beim Start eines Programms werden automatisch 3 Dateien mit den Fi-
ledeskriptoren:
O Standardeingabe
1 Standardausgabe
2 Standard-Fehler
eröffnet.
Eröffnen bestehender Dateien
Die elementare Routine zum Eröffnen schon bestehender Dateien be-
sitzt in den verschiedenen Betriebssystemen eine unterschiedliche Syn-
tax:
CP/M-86, UNIX, MS-DOS
filedeskriptor = open (dateiname, modus)
Die elementare Funktion open ist ähnlich zu handhaben wie die höhere
Routine fopen, außer daß anstelle eines Filepointers ein Filedeskriptor,
d. h. eine positive ganze Zahl (int filedeskriptor), als Funktionswert
geliefert wird. In DR-C unter CP/M-86 nimmt der Filedeskriptor Werte im
Bereich 0 ... 15 an.
Wie bei fopen ist der Parameter dateiname eine Zeichenkette, die
den Namen der zu eröffnenden Datei enthält.
Der 2. Parameter modus gibt die Zugriffsart an:
14-32
0 Datei zum Lesen eröffnen
1 Datei zum Schreiben eröffnen
2 Datei zum Lesen und Schreiben eröffnen (Update)
Falls bei der Dateieröffnung ein Fehler auftritt, liefert open den Wert-1,
andernfalls den filedeskriptor als Funktionswert zurück. Wichtig ist
hier noch, daß der Versuch, eine nicht existierende Datei mit open zu
eröffnen, auf einen Fehler führt.
In MS-DOS kennzeichnet Bit 15 des int-Parameters modus die Art der
zu eröffnenden Datei:
Bit 15 = 1: eröffnen einer Binärdatei
Bit 15 = 0: eröffnen einer Textdatei
Soll beispielsweise eine Binärdatei zum Lesen und Schreiben eröffnet
werden, dann ist folgender Aufruf erforderlich:
filedeskriptor = open (dateiname, 0x8002)
I
hiermit wird Bit 15 gesetzt
CP/M-86 bietet auch zum Eröffnen einer Datei 2 weitere Routinen an:
filedeskriptor = opena (dateiname, modus)
entspricht vollständig der Anweisung:
filedeskriptor = open (dateiname, modus)
filedeskriptor = openb (dateiname, modus)
openb entspricht weitgehend open, nur daß keine ASCII-Datei, son-
dern eine binäre Datei eröffnet wird.
ISIS-II
In ISIS-II benötigen wir zum Eröffnen einer Datei und Festlegen der Zu-
griffsart zwei Routinen aus der Datei udi.h*):
(1) filedeskriptor = dqSattach (dateiname, fehler)
(2) dqSopen (filedeskriptor, modus, puff___zahl, fehler)
Mit der Anweisung (1) wird eine Verbindung zwischen dateiname und
dem Programm hergestellt; dazu liefert die Routine dqSattach einen
filedeskriptor, der hier einen vordefinierten Typ connection besitzt, in
udi.h: typedef unsigned connection (vorzeichenlose Ganzzahl). Für
HINWEIS: *) udi = universal development Interface
14-33
den Parameter fehler ist ein int-Zeiger anzugeben; der Wert zu die-
sem Zeiger ist von 0 verschieden, falls bei der jeweiligen Operation ein
Fehler auftrat.
Mit der Anweisung (2) wird für die Datei, der der fiiedeskriptor zu-
geordnet ist, die Zugriffsart festgelegt; dies geschieht über den 2. Para-
meter modus:
1 Datei zum Lesen eröffnen
2 Datei zum Schreiben eröffnen
3 Datei zum Lesen und Schreiben eröffnen
Der 3. Parameter puff__zahl gibt die Zahl der Puffer an, die beim Zu-
griff auf die Datei, die mit fiiedeskriptor verbunden ist, zu verwenden
sind; ein Vorschlag hier ist:
puff___zahl = O: bei Bildschirmein-Zausgabe
puff___zahl = 1: bei wahlfreiem (RANDOM-) Dateizugriff
puff___zahl = 2: bei sequentieller Dateiverarbeitung
Auch hier ist zu beachten, daß die Funktion dqSattach einen Fehler
liefert, wenn die Datei dateiname nicht bereits existiert.
Eröffnen nicht bestehender Dateien
Soll eine noch nicht existierende Datei eröffnet werden, so ist sie zuerst
im System neu anzulegen; die dazu erforderlichen E/A-Routinen unter-
scheiden sich wieder in den jeweiligen Betriebssystemen:
UNIX, MS-DOS
fiiedeskriptor = creat (dateiname, schütz)
Dieser Aufruf liefert einen fiiedeskriptor. wenn die Datei dateiname
angelegt werden konnte, und -1, falls dies nicht möglich war. Wird
creat auf eine Datei angewendet, die bereits existiert, so geht deren al-
ter Inhalt verloren und sie kann von Beginn an neu beschrieben werden.
UNIX und MS-C unter MS-DOS bieten die Möglichkeit, eine Datei mit
Zugriffsschutz anzulegen; dazu dient der Parameter schütz Als Zu-
griffsschutz für eine Datei gibt es 9 Bits, weshalb man diesen normaler-
weise als Oktalzahl mit 3 Ziffern angibt. Beispielsweise legt 0755 als
Zugriffsschutz für eine Datei fest, daß der Besitzer Lese-, Schreib- und
Ausführungsrechte hat, während alle anderen diese Datei lediglich le-
sen oder ausführen können.
In MS-DOS ist die Auswertung der Int-Variablen schütz systemabhän-
gig. Ist Bit 15 der int-Variablen gesetzt, d. h. 0x8000, dann wird eine Bi-
närdatei, andernfalls eine Textdatei angelegt (vgl. auch vorangegange-
ne Hinweise).
14-34
CP/M-86
filedeskriptor = creat (dateiname, schütz)
Dieser Funktionsaufruf entspricht weitgehend dem von UNIX; der Unter-
schied besteht lediglich darin, daß der Parameter schütz in CP/M-86
zwar angegeben werden kann, aber vom System nicht ausgewertet
wird. Der Grund dafür, daß dieser Parameter trotzdem existiert, ist in
der Kompatibilität zum UNIX-System zu suchen.
CP/M-86 stellt noch 2 weitere Funktionen zum Anlegen von Dateien
zur Verfügung:
filedeskriptor = creata (dateiname, schütz)
entspricht vollständig der Anweisung:
filedeskriptor = creat (dateiname, schütz)
filedeskriptor = creatb (dateiname, schütz)
creatb entspricht nahezu creat. nur daß keine ASCII-Datei, sondern
eine binäre Datei angelegt wird.
ISIS-II
filedeskriptor = dqScreate (dateiname, fehler)
Diese Anweisung legt eine neue Datei an. Wenn die Datei dateiname
bereits existiert, wird sie gelöscht (alter Inhalt geht verloren) und neu
angelegt.
Als Funtkionswert liefert diese Routine einen filedeskriptor zurück.
Bevor die Datei dateiname allerdings bearbeitet werden kann, ist sie
mit dqSopen zu eröffnen.
14.2.2 Die elementaren Lese- und Schreib-
routinen
Nachdem eine Datei eröffnet wurde, kann man entsprechend dem bei
open (bei UNIX. CP/M-86, MS-DOS) bzw. dqSopen (bei ISIS-II) ange-
gebenen Modus entweder aus ihr lesen, in sie hineinschreiben oder sie
verändern (update).
Diese elementaren Routinen zum Lesen bzw. Schreiben unterschei-
den sich wieder in den einzelnen Betriebssystemen:
14-35
Elementares Lesen von Dateien
UNIX, CP/M-86, MS-DOS
gelesen = read (filedeskriptor, puffer, byte__zahl)
Der 1. Parameter filedeskriptor identifiziert die Datei, aus der gelesen
werden soll; wieviele Bytes dort zu lesen sind, wird durch den 3. Para-
meter byte___zahl angegeben. Der 2. Parameter puffer gibt die An-
fangsadresse des Speicherbereichs an, in dem die gelesenen Bytes ab-
zulegen sind. Jeder Aufruf von read liefert als Funktionswert die Anzahl
der wirklich gelesenen Bytes, die dann in gelesen festgehalten wird;
dabei kann der von read gelieferte Wert kleiner sein als die geforderte
byte__zahl, d. h.: es können weniger Bytes gelesen worden sein als ge-
wünscht, weil eben weniger als byte_zahl Bytes noch zum Lesen ver-
blieben sind. Speziell beim Lesen vom Terminal liest read nur bis zum
nächsten CR-Zeichen, was normalerweise weniger als die geforderten
Zeichen sein können. Falls read den Funktionswert O liefert, so bedeu-
tet dies, daß das Dateiende erreicht wurde. Wird der Wert -1 geliefert,
so ist beim Lesen ein Fehler aufgetreten.
Die Angabe des Dateiendes mit Wert O vereinfacht das Lesen einer Da-
tei:
while (gelesen = read (filedeskriptor, puffer, byte_zahl)) {
}
Diese while-Schleife wird solange durchlaufen, bis die Bedingung nicht
mehr erfüllt ist: gelesen = O; gelesen erhält ja genau dann den Wert 0
wenn das Dateiende erreicht ist.
Die häufigsten Werte für den Parameter byte_zahl sind 1 (Lesen ei-
nes Bytes) oder die vorgegebene Pufferlänge (UNIX, CP/M-86, MS-
DOS: 512). Die Wahl der Pufferlänge für byte__zahl ist hierbei die
effizientere Vorgehensweise.
ISIS-II
gelesen = dqSread (filedeskriptor, puffer, byte_zahl, fehler)
Die Funktion dqSread arbeitet wie die eben beschriebene Routine
read. Die Puffergröße ist bei ISIS-II 128 Byte.
14-36
Elementares Schreiben auf Dateien
UNIX, CP/M-86, MS-DOS
write (filedeskriptor, puffer, byte____zahl)
Diese Routine schreibt byte___zahl Bytes ab der Speicheradresse puf-
fer auf eine Datei, die mit filedeskriptor identifiziert wird.
Sie können hier eine beliebige ganze positive Zahl für byte______zahl
angeben. Wieviele Bytes wirklich auf eine Datei geschrieben wurden,
wird als Funktionswert geliefert; wenn diese wirklich geschriebene Zahl
von Bytes nicht mit der geforderten Zahl (byte___zahl) übereinstimmt,
liegt im allgemeinen ein Schreibfehler vor. Diese Tatsache kann man
mit folgendem Programmcode prüfen:
if (write(filedeskriptor, puffer, byte_zahl)! = byte_zahl)
printf(”schreibfehler\n”);
exit(1);
}
Im Fehlerfall liefert write den Wert -1 zurück.
Die häufigsten Werte für byte___zahl sind wieder 1 (Schreiben eines
Einzelzeichens) oder die vom System vorgegebene Pufferlänge (512).
ISIS-II
dqSwrite (filedeskriptor, puffer, byte___zahl, fehler)
Die Routine dqSwrite arbeitet wie die eben beschriebene Funktion
write. Zu beachten ist wieder die Puffergröße 128 bei ISIS-II.
Fassen wir alles zusammen, so können wir ein einfaches Programm an-
geben, das die Eingabe am Terminal auf die Standardausgabe (Termi-
nal) kopiert:
ttdefine PUFF_ LAENG 512
main ()
{
char puffer [PUFF_LAENGJ;
int n;
while ((n = read (O, puffer, PUFF__LAENG)) > O)
if (write(1, puffer, n)! = n) {
printf(”\n\n SchreibfehlerVn”);
exit(1);
}
}
14-37
Der Fiiedeskriptor 0 ist mit der Standardeingabe (Terminal) verbunden
Dieses Programmbeispiel ist auf den Betriebssystemen UNIX, CP/M-86
und MS-DOS lauffähig. CP/M-86 puffert allerdings bei der Eingabe von
stdin höchstens 256 Zeichen.
Die Routine getchar aus stdio.h soll unter Verwendung der elementaren Leseroutine
read bzw. dqSread realisiert werden.
•difini MASKE OXFF /• ui char Marti poaitlv tu nehm •/
Hefim EOF -l
gitchar()
<
char ziich|
return((read<0,Heich,1)>0) ? dich k MASKE : EOF»j /• in ISI9-II1 dqfread«/
zeich muß als char-Variable definiert werden, da der 2. Parameter von read em Zeiger
auf ein char-Element ist. Es ist sinnvoll, das von read gelieferte Zeichen zeich mit dem
Bitmuster 000000001*1111111 (OXFF) zu verknüpfen, damit sichergestellt ist, daß
ein positives Resultat geliefert wird; sonst könnte eventuell durch Interpretation des Vor-
zeichenbits ein negativer Wert entstehen.
Die 2. Version von getchar. die 71/ angibt, ist gepuffert, d. h. sie liest beim Aufruf von
read eine größere Anzahl von Zeichen; jeder Aufruf von getchar liest dann allerdings aus
dem Puffer, der zuvor mit read gefüllt wurde. Ist der Puffer leer und getchar wird wie-
der aufgerufen, so wird vor der Rückgabe eines Zeichens der Puffer mit read zunächst
wieder gefüllt.
/♦ aus 71/ •/
toefine MASKE 0377 /• 0377 entspricht OXFF ♦/
Ifleflne BUFSIZE 512 /• in ISIS-II: tdefine BUFSIZE 1 2fi •/
fdefifte EOF -1
getchar!) /• gepufferte Eingabe einzelner Zeichen •/
static cnar bufCBUFSIZEJ;
static cnar «bufp=buf;
static int n = 0;
if (n=-0) < /• Puffer leer •/
n = read(O,buf»BUFSIZE); /• in ISIS-II: dqtread •/
bufp = buf;
1
returntl—n >= 0) ? ♦bufp++ & MASKE : EOF); /• in ISIS-II: oqtread •/
14-38
Beispiel 12
Es ist ein C-Programm zu erstellen, das die Inhalte zweier Dateien vergleicht; solange die
beiden Dateien übereinstimmen, soll der Inhalt am Bildschirm ausgegeben werden.
Es soll für alle 4 Betriebssysteme eine Lösung dieser Aufgabenstellung angegeben wer-
den.
ISIS-II
•include <stdio.h>
•include <udi.h>
•define PUFF LAENG 128
•define KIN(x,y) C((x) < Cy>> ’ (x) : (yJ)
int «fehler;
main ()
(
unsigned zahll, zahl2, laeng;
connection nrl, nrZ; /• connection in udi.h vordefiniert: ♦/
/• entspricht unsigned ♦/
char namlClOJ, namZtlOl;
char puff1(PUFF.LAENGJ, puff2tPUFF.LAENG] ;
puts ("XnGeben Sie den Namen der 1.Datei ein '");
gets(nam1);
puts(*Xn6eben Sie den Namen der 2.Datei ein !•);
gets(namZ);
nrl=dq*attach(nam1,fehler);
if (♦fehler » 0x21) <
printf("XnDatei Xs ist nicht vorhandenXn",naml);
exltd);
nr2 = dqsattach(nam2,fehler);
if («fehler == 0x21) <
printf("XnDatei Xs ist nicht vorhandenXn",nam2);
exltd);
dq*open(nri,1,2,fehler);
if («fehler »= 0) C
printf("XnFehler beim versuch die Datei Xs mit dqtopen zu eroeffnenXn",naml);
exitll);
dqtopen(nr2,1,2,fehler);
lf («fehler •= 0) (
printf("XnFehler beim versuch die Datei Xs mit dqsopen zu eroeffnenXn",nam2);
exltd);
do (
zahllspqsread(nrl, puffl, PUFF LAENG,fehler) ;
if («fehler •- 0) (
printf("XnLesefehler bei Datei XsXn",nam1);
exltd );
}
zahl2=dq»read(nr2,puff2,PUFF LAENG,fehler);
if («fehler I» 0) <
printf("XnLesefehler bei Datei Xs\n*,nam2);
exltd);
J
laengsrtIN(zahll,zahl2);
if (strncmpfpuffi,puff2,laeng) = = 0 laeng = = PUFFLAENG)
dqtmnted,puffl,laeng,fehler);
eise (
zeich ausgabdaeng,puffl ,puff2);
exit(Ö);
)
14-39
) while (zahli>0 && zahl2>0);
zeich_ausgab(wiev,zeigl,zeig2)
int " wiev;
char »zeigl, »zeig2;
int i»1;
while (»zeigl==»zeig2 && i**<=wievi (
dqswritetl,zeigl+4,1,fehler);
zeiglw;
>
In diesem Programm ist eine Abfrage
if (»fehler = = O x 21)
enthalten.
In ISIS-II sind die Fehlercodes in der Datei udi.h mit symbolischen Konstanten benannt;
dies zeigt der folgende Auszug aus der Datei udi.h:
«define
«define
«define
«define
«define
idefine
«define
«define
idefine
•define
•define
•define
•define
•def ine
•define
•defIne
«define
•define
•define
•define
«define
«define
«define
«define
«define
•define
Esok
ESCONTEXT
ESCROSSFS
ESEXI5T
ESFACCESS
ESFEXIST
ESFNEXIST
ESNEM
ESNOPEN
ESOPEN
ESOREAD
ESOURITE
ESPARAM
ESPTR
ESSHARE
ESSIX
ESSPACE
0x0000
0x0101
0x0102
0x0103
0x0026
0x0020
0x0021
0x0002
0x0104
0x0105
0x0106
0x0107
0x0108
0x0109
0x0028
0x01OA
0x0029
ESSTRINGSBUF 0x0081
ESSUPPORT 0x0108
ESSYNTAX OxOlOC
ESUNSAT OxOlOE
ESADDRESS OxOlOF
ESBADSFILE 0x0110
ESZEROSDIVI DE 0x8000
ESOVERFLOU 0x8001
ES8087 0x8007
/♦ All ok ♦/
/• Call in illegal context •/
/♦ Cross volume • /
/• Something did not exist •/
/• Access error •/
/• File exists, and shouldn't •/
/• The file does not exist •/
/• Not enough aeaory ♦/
/• Connection not opened •/
/• Connection opened •/
/• Urite to a read channel •/
/♦ Read to a write channel •/
/• Illegal parameter •/
/• Illegal pointer •/
/• Illegal Operation on shared thlng •/
/• Six files open •/
/• The dlrectory is full •/
/• String too long •/
/• Function not supported •/
/• Path name syntax •/
/• ünsatisfled externals in overlay •/
/• Overlay attempted over systea •/
/• Not an object flle ♦/
/• Divide by 0 •/
/• INTO •/
/• An 8087 error •/
Wir hätten folglich die obige Abfrage auch mit
if (»fehler = = ESFNEXIST)
angeben können.
Im Programmteil
laeng=NIN(zahli,zah!2);
if lstrncap(puff1,puff2,laeng) == 0 && laeng -- PUFF LAENG)
dqSwritell»puffl,laeng,fehler);
eise (
zeich ausgab(laeng,puffl,puff2);
ex 11(0>;
wird zunächst bestimmt, aus welcher Datei die wenigsten Zeichen gelesen wurden; das
Minimum von zahH und zahl2 wird dann in der int-Variablen laeng abgespeichert.
14-40
Falls die Puffer vollständig gefüllt wurden: laeng == PUFF_LAENG. und alle Zeichen in
puffl mit denen in puff2 übereinstimmen: strncmp (puffl, puff2, laeng) == 0. wird
der vollständige Puffer puffl mit
dq$write (1, puffl, laeng, fehler)
am Bildschirm ausgegeben, andernfalls wird die Funktion zeich_ausgab aufgerufen,
welche alle Zeichen aus puffl bis zum ersten nicht übereinstimmenden Zeichen in puff2
am Bildschirm ausgibt.
CP/M-86, UNIX, MS-DOS:
• mclude «stdio.h»
idefine PUFF LAENG 512
idefine HIN(x,y) <(4x) < (y>J ’ <x) : (y)>
aain ()
<
int zahn , zahl?, nri, nr2, laeng;
char nam1C10J, naaZCIO);
Char puffltPUFF.LAENG], puff2CPUFF.LAENG1;
puts("XnGeben Sie den Naaen der 1.Datei ein !•);
gets(naal);
puts("XnGeben Sie den Naaen der 2.Datei ein '•);
gets(naa2);
if ((nri =open(naal,0)) ss -1 ) (
printf("XnDatei Xs kann nicht eroeffnet werdenXn",naal);
exitd);
If ((nr2=open(naa2,0)) «= -1) {
printf("XnDatei Xs kann nicht eroeffnet werdenXn",naa2);
exitd);
do <
if ((zanl1=read(nr1,puffl.PUFFLAENG)) =s -i)
printf("XnLesefehler bet Datei XsXn*,naal);
exitd);
>
if ((zahl2 = read<nr2,puff2,PUFF.LAENG)) = = -1)
printf("XnLesefehler bei Datei ZsXn",naa2);
exitd);
)
laeng=NIN(zahl!,zah!2);
If (strncmp(puff1,puff2,laeng) == 0 laeng == PUFF.LAENG) (+)
write(1,puff1,laeng);
eise {
zeich.ausgab(laeng,puffl,puff2);
closeinri); /• siehe 14.2.3 •/
close(nr2); /• ♦/
exit(O);
)
1 while (zahl1>0 && zahl2>0);
zeich.ausgab(wiev,zeigl,zeig2)
int " wiev;
char *zeigl, •zeig2;
(
int 1=1;
14-41
while i «zeig1 = = *zeig2 && l++<=wiev) {
write(1,zeig1++,1) ;
zeig?**;
>
>
H In C-Compilem älterer Version kann etrnemp nicht definiert
sein; in diesem Fall wird das Programm mit stremp statt
itmcmp lautfähig.
Beispiel 13
In UNIX existiert ein Dienstprogramm cp. welches eine Datei in eine andere kopiert. Das
hier angegebene Programm ist aus /1/ entnommen und ist auf den Betriebssystemen
UNIX, CP/M-86 und MS-DOS lauffähig; der mit PMODE festgelegte Zugriffsschutz wird al-
lerdings von CP/M-86 und MS-DOS ignoriert.
Das nachfolgende Programm ist nur auf den Betriebssystemen CP/M-86, UNIX und MS-
DOS lauffähig:
•define NULL 0
»difini PUFF LAENG 512
•difini ZUGRIFF 0644
•ain(argc,argvl
int argej
char «argv(]|
(
int 41, 42, nj
char pufferIPUFF_LAENG)|
/• Besitzer darf lesen und schreibeni •/
/• Rest darf nur lesen. •/
/• (in CP/M-86 wird kein Zugriffsschutz fuer ♦/
/• eine Datei vergeben! e/
if farge !«3)
error('Es auessen 2 Dateien vorhanden lein", NULL 11
if (lfl"open(argvt1],0)) -1)
error('Fehler beie Eroeffnen der Datei Zs",argv(13i)
if ((42-creat(argvC21,ZUGRIFF)I — -1)
error('Fehl er bei« Kreieren der Datei Xs',argvC23)?
while <(n-read(f1.puffer,PUFF.LAENG)) > 0)
if (write<f2,puffer,n) n)
error<'Schreibfehler bei« Kopieren aufgetreten”.NULL)।
cloself1)| /• siehe 14.2.3 •/
close(f2); /• • /
error(sl,s2) /• Fehler««ldüng ausgeben • /
char »si, »s2|
C
printf(sl,s2);
printf(*\n”)।
exitlDi
14-42
14.2.3 Das elementare Schließen von
Dateien
Da die Anzahl von Dateien, die in einem Programm gleichzeitig eröffnet
sein können, begrenzt ist, sollten nichtmehr benötigte Dateien ge-
schlossen werden. Schließen bedeutet, daß die Verbindung zwischen
einer offenen Datei und einem Filedeskriptor aufgelöst wird, und somit
der Filedeskriptor für eine andere Dateiverbindung freigegeben wird.
Alle eröffneten Dateien werden automatisch geschlossen, wenn ein Pro-
gramm durch exit oder return in der main-Funktion beendet wird.
Will der Programmierer allerdings während des Programmlaufs eine of-
fene Datei schließen, so muß er in den jeweiligen Systemen folgende
Routinen verwenden:
CP/M-86, UNIX, MS-DOS:
close (filedeskriptor)
Danach wird die Verbindung zwischen Datei und Filedeskriptor unter-
brochen, welche an früherer Stelle mit open oder creat aufgebaut wur-
de. Anschließend kann filedeskriptor für eine neue Verbindung mit ei-
ner anderen Datei verwendet werden.
ISIS-II:
dq$close (filedeskriptor, fehler)
Diese Routine sorgt dafür, daß alle noch vorhandenen Pufferinhalte auf
die Datei geschrieben und diese Pufferbereiche im Speicher wieder frei-
gegeben werden.
Zusätzlich hebt diese Funktion die mit dqSopen vereinbarte Zugriffsart
auf die Datei, die mit filedeskriptor verbunden ist, auf. Allerdings
löscht sie nicht die Verbindung zwischen der Datei und filedeskriptor;
dazu benötigt man die folgende Routine.
dqSdetach (filedeskriptor, fehler)
Diese Routine hebt eine mit dqSattach oder dqScreate hergestellte
Verbindung zwischen einer Datei und dem filedeskriptor auf. Falls die
entsprechende Datei noch nicht mit dqSclose behandelt wurde, wird
sie zunächst automatisch geschlossen (Puffer leeren usw ), bevor die
Verbindung zwischen Datei und filedeskriptor gelöst wird.
In den Betriebssystemen UNIX, CP/M-86, MS-DOS liefert die gerade
vorgestellte elementare Funktion zum Schließen von Dateien den Wert
O, falls die jeweiligen Aktionen erfolgreich verliefen, ansonsten geben
sie den Funktionswert-1 zurück.
14-43
14.2.4 Das elementare Löschen von Dateien
Während der Programmausführung können Dateien auch vollständig
aus dem Dateisystem entfernt werden, so daß sie nicht mehr zur Verfü-
gung stehen.
UNIX, CP/M-86, MS-DOS:
unlink (dateiname)
ISIS-II:
dqSdelete (dateiname, fehler)
unlink (aus UNIX, CP/M, MS-DOS) liefert als Funktionsresultat den
WertO, falls die Löschaktion erfolgreich verlief, ansonsten den Wert-1.
dqSdelete verändert den Wert, auf den fehler zeigt, so daß dieser von
O verschieden ist, wenn ein Fehler bei Funktionsausführung auftrat.
Ist nach Aufruf von dqSdelete der Wert, auf den der int-Zeiger fehler
zeigt, gleich O, so deutet dies auf fehlerfreie Ausführung der Funktion.
14.2.5 Der elementare, wahlfreie (RANDOM)
Dateizugriff
Wie bei den höheren E/A-Routinen, besteht auch bei den elementaren
E/A-Routinen die Möglichkeit des wahlfreien Dateizugriffs durch Ver-
schieben eines Schreib/Lesezeigers.
Verschieben des Schreib/Lesezeigers
CP/M-86, UNIX, MS-DOS:
Iseek (fiiedeskriptor, offset, ausgangsbasis)
Hierbei ist fiiedeskriptor der von open bzw. creat gelieferte Fiiedes-
kriptor der gewünschten Datei. Der Schreib/Lesezeiger wird von der an-
gegebenen Ausgangsposition ausgangsbasis um offset Bytes auf
eine neue Position verschoben. Ein nachfolgender Dateizugriff bezieht
sich dann auf diese neue Position des Schreib/Lesezeigers. offset ist
ein long-Wert, ausgangsbasis ein int-Wert. Folgende 3 Ausgangspo-
14-44
sitionen des Schreib/Lesezeigers können mit ausgangsbasis gewählt
werden:
0 = Datei anfang
1 = momentane Position des Schreib/Lesezeigers
2 = Dateiende
Beispiele:
Iseek (filedeskriptor, OL, O)
Schreib/Lesezeiger auf Dateianfang setzen.
Iseek (filedeskriptor, (long) 5, 1)
Schreib/Lesezeiger von der momentanen Position aus um 5 Bytes vor-
rücken.
Iseek (filedeskriptor, -1L, 2)
Schreib/Lesezeiger auf das letzte relevante Byte, also nicht auf die
EOF-Marke. setzen.
Iseek (filedeskriptor, -2L, O)
nicht erlaubt
Iseek (filedeskriptor, 3L, 2)
nicht erlaubt
Die Iseek-Funktion liefert die momentane Position des Schreib/Lese-
zeigers als 'Anzahl Bytes vom Dateianfang' zurück, falls die Positionie-
rung erfolgreich verlief, ansonsten den Wert -1L (der Ergebniswert die-
ser Funktion ist vom Datentyp long).
Beispiel 14
Mit Iseek ist es möglich, eine Datei wie em großes Feld (Vektor) zu behandeln, allerdings
mit einem langsameren Zugriff. Die nachfolgende Funktion liest eine beliebige Zahl von By-
tes ab einer bestimmten Position in einer Datei:
getfnr,poeition,puffer,n) /• nicht unter ISIS leuffaehig •/
int nrj
long poeitioni
Cher »puffen
int nj
(
int geleeeni
leeek <nr.Petition,0)।
if CCgele»en»read(nr,puffer,n)) -1) C
printf i’Lete-Fehler\n*l।
eui t < l >|
>
puffertgeleeen] ’kO'i
14-45
Beispiel 15
Es ist ein C-Programm zu erstellen, das eine Datei ans Ende einer anderen Datei kopiert.
Lauffähig auf UNIX, CP/M-86, MS-DOS:
•defin« NULL 0
idifin« PUFF.LAENB 512
•xtorn long larak {);
nain (arge,argv)
int arge)
Char «argv(]|
<
int fl, 12, n|
char puff#r (PUFF.lAENGI j
if (arge !»3)
•rror(“Ea «uessan 2 Dataian verhandln »ein",NULL):
if < (fl«=optn (*rgv( 11,2)1 •• -U
trror("Fahlar bet« Eroeffnen der Date: Xs",argvtl]l।
if ((f2>open(argv(21,0>I — -l>
error("Fehler bei« Eroeffnen der Date: Xs”,argv(2])|
if <lseek<f1,0L,2> •• -1L1 /♦ ans DatOianea poiltioni»r»n • /
error l "Fehler ba:a Positioniaran in Date: Xs", argv(|])|
whilt (In«read»f2,puffer,PUFF LAENG)) 0! {
pufferen] » ’\0 |
if (wr:te<f1.puffer,ni '• nl
error (“Fehler bei« Schreiben auf die Date: Xs*',argvf 1 ]);
>
if (close(fl) -1)
error("Fehler bei« Schliessen der Date: Xs",argvt 1 ]) j
if tclose(f2) -1»
error ("Fehler beie Schliessen der Datei Xs"largvC21)|
error(si,s2) /»Fehlereeldüng ausgebens/
char e«i, «s2j
C
printf(si,s2)।
printf <"\n*)।
exitllli
Dieses Programm ist für UNIX, MS-DOS, CP/M-86 geschrieben.
ISIS-II:
dqSseek (fiiedeskriptor, ausgangsbasis, offset, fehler)
Die Bedeutung der Parameter ist wie zuvor; die 4 Werte für ausgangs*
basis lassen jedoch neben der Wahl einer Ausgangsposition noch die
Wahl der Richtung zu, in die der Schreib/Lesezeiger um offset Bytes
verschoben wird:
1 = momentane Position, um offset Bytes zurücksetzen
2 = Dateianfang
3 = momentane Position, um offset Bytes vorrücken
4 = Datei ende
Beispiele:
dqSseek (fiiedeskriptor, 1,4L, fehler)
Schreib/Lesezeiger um 4 Bytes zurücksetzen.
14-46
dqSseek (filedeskriptor, 2, (long) 10, fehler)
Schreib/Lesezeiger auf das 10. Byte vom Dateianfang an setzen.
dqSseek (filedeskriptor, 3, 3L, fehler)
Schreib/Lesezeiger um 3 Bytes weiterrücken.
dqSseek (filedeskriptor, 4, 1L, fehler)
Schreib/Lesezeiger auf das letzte relevante Byte, nicht EOF-Zeichen,
setzen.
Es soll in einer Datei ab einer vorgegebenen Position des Schreib/Lesezeigers der Rest
der Datei ausgegeben werden.
•incluoe <stdio.h>
ttnclude <udi.h>
«define PUFF_LAEN5 128
ain ()
{
int nu inner, »fehler;
unsigned zahl;
long abwo;
char puffer[PUFF.LAENG), dateinameClOl;
puts("Dateiname ?•);
gets(dateiname) ;
numaer=dqtattach(dateiname,fehler);
if (»fehler = = 0x21) <
printf("XnDatei Xs ist nicht vorhanden >\n“,dateiname);
exitd);
1
dqSopen(nummer,1,2,fehler);
if (»fehler != 0) {
printf("XnFehler beim versuch Xs mit dqSopen zu eroeffnenxn".dateiname);
exitd );
>
putsdAb welcher Position ist Rest der Datei auszugeben");
scanf(“Xld",iabwo);
printf("XnXnDer Rest der datel ab Bytenr Xld ist:\n\n",abwo);
dqsseek(numner,2,abwo,fehler);
if (»fehler «= 0) <
printf("XnFehler bei dqtseek in der Datei XsXn".dateiname);
exitd);
>
while ((zahl-dqSread(nummer,puffer.PUFF LAENG.fehler)) >0) <
if (»fehler '= 0) <
printf("\n\nLesefehler aufgetreten ’Xn“);
exitd);
)
dqSwrlted ,puf fer,zahl,fehler);
}
Dieses Programm ist für ISIS-II geschrieben.
14-47
Position des Schreib/Lesezeigers ermitteln
Es kann auch die momentane Position des Schreib/Lesezeigers ermit-
telt werden, falls diese Information verlorengegangen ist.
CP/M-86, UNIX, MS-DOS:
teil (filedeskriptor)
Diese Funktion liefert einen long-Wert für die momentane Position des
Schreib/Lesezeigers, ausgedrückt als 'Anzahl Bytes vom Dateianfang'.
Liefert diese Funktion einen negativen Wert, so liegt ein Fehler vor.
14.3 INFORMATIONEN ZUR DATEI
stdio.h
Wie schon erläutert wurde, enthält die Datei stdio.h die Definition der
FILE-Struktur (siehe 14.1.1). Ein FILE-Zeiger ist ein Zeiger auf eine
Verwaltungsstruktur, in der Pufferzeiger, Puffergröße usw. enthalten
sind.
In den folgenden Auszügen von stdio.h beginnen Namen, die nur von
Bibliotheksfunktionen verwendet werden sollten, mit einem Unterstrich,
damit sie sich möglichst von den (vom Programmierer) freigewählten
Namen unterscheiden.
Die Datei stdio.h in CP/M-86:
«include <portab.h>
»defin. BUFSIZ 512
«deFine MAXFILES 16
struct iobuf <
“WORD _fd;
WORD Flag;
BYTE »_base;
BYTE *_ptr;
WORD ent;
>1
«ifndef FILE
extern struct _iobuf _iobLMAXFTLES3;
«define FILE struct _iobuf
Wend i f
«define _IOREAD 0x01
«define _IOWRT 0x02
«define _I0ABUF 0x04
»deFine I0NBUF OxOfl
«define .IOERR 0x10
»define IOEOF 0x20
«define .I0LBUF 0x40
•define _I0STRI OxfiO
«define _I0ASCI 0x100
14-48
»define stdin (& lobCDJ)
«define stdout (i'iobEU)
«define stderr (& iobC2J)
»define dearerr(p) <(p)->_flag & ~ IOERR)
»define feof(p) (<p)->_flag & IOEOF)
»define ferror(p> <<p)->_flag & _IOERR)
»define fileno(p) ((p)->_Fd)
»define getcharO getc(stdin)
»define putchar(c) putclc,stdout)
»define putc fputc
«define getc fgetc
«define abs(K) (<x) < 0 ? -<x> i (x))
«define max(x,y)
«define min(x,y)
(( <x)
(<<x)
> (y>) ? (x)
< Cy) > ? (x>
<y>)
<y))
Mit ttinclude <portab.h> wird die Datei portab.h in die Datei
stdio.h übernommen; diese Datei portab.h unterstützt die Kompatibi-
lität von CP/M-86 zum Betriebssystem UNIX. In CP/M-86 können 16
Dateien - an ttdefine MAXFILES 16 erkennbar - gleichzeitig eröffnet
sein. Für frei wählbare Dateien stehen allerdings nur 13 Dateien zur
Verfügung, da mit
•define stdin (&_iabCDJ)
»define stdout (&_iobC13)
»define stderr <6_iobC23)
bereits 3 Dateien festgelegt sind. Beispielsweise ruft getchar das Ma-
kro getc mit stdin auf:
«define getcharO getc(stdln)
«define putchar(c) putc(c,stdout)
Daß die Makros getc und putc identisch mit den Funktionen fgetc und
fputc sind, ist an
«define putc Fputc
»define getc Fgetc
zu erkennen.
14-49
Auszug aus der Datei stdio.h in UNIX (aus /1/):
*define BUFSLZ 512
idefine _NFILE 20 /» maximal moegliche Anzahl Dateien ♦/
typedef struct _iobuf (
char ♦_ptr; /* Position des naechsten Zeichens ♦/
int _cnt; /♦ Anzahl noch uebriger Zeichen */
char «_base; /♦ Adresse des Puffers ♦/
int flag; /♦ Art des Zugriffs, etc. »/
int _file; /♦ File Deskriptor */
I FILE;
extern FIl^ iob| NFILE),
idefine stdin (&Job[0]l
idefine stdout (&_iob(ljl
idefine stderr (&_iobl2))
idefine IOREAD 01
idefine IOWRT 02
idefine 1ONBF 04
idefine .1OMYBUF 010
idefine 1OEOF 020
/* Datei zum Lesen eroeffnet ♦/
/♦ Datei zum Schreiben eroeffnet ♦/
/♦ nicht gepuffert ♦/
/* grosser Puffer angelegt «/
/* Datei Ende wurde bereits erreicht ♦ /
«define _1OERR 040 /«ein Fehler ist schon passiert ♦/
«dehne NULL 0
«dehne EOF (-1)
«dehne getcfp) (— (p)->_cnt >- 0 \
? ♦(p)->ptr + + A 0377 : -fllbuflp))
odefine getcharf) getc(stdin)
#define putc(x.p) I — (p)->_cnt >- 0 \
? ♦ip)->_ptr++ - (xl : -flsbufitxi.pl)
idefine putchar(x) putclx, stdout)
Hier ist anzumerken, daß eine längere ttdefine-Anweisung in der
nächsten Zeile fortgesetzt werden kann, wenn die vorhergehende Zeile
mit \ abgeschlossen wird.
14-50
Die Datei stdio.h in ISIS-II:
/•
• File: stdio.h
• Last edlt: 14-0CT-1982 18:19:20.31
• COPYRIGHT 1983 INTEL CORPORATION.
• COPYRIGHT 1982, 1983 MARK WILLIAMS COMPANY, CHICAGO.
• This header flle contalns
• typedefs, structures and nacros for the
• Standard I/O llbrary running on
• the Intel 1APX-86 under UDI.
•/
tdefine NULL ((char •) 0) /• Null pointer •/
«define EOF (-1) /• End of flle •/
tdefine BUFSIZ 128 /• Size of a buffer •/
tdefine NBUF 0 /♦ t of buffers in dqSopen call •/
tdefine *NFILE 10 /• l of FILE structures • /
• This is a FILE; there is a FILE for
• every open strea®. An external array of these
• flies is used by the I/O package. If the flags
• are 0 the FILE is not in use.
typedef struct FILE (
unsigned
unsigned
int
unsigned
unsigned
unsigned
unsigned
) FILE;
int _flag;
int Jcount;
_ugetc;
char «_ptr;
char •”buf;
int _cönn;
int ^resp;
/• Flags •/
/• Count remainlng ♦/
/• Ungot character •/
/• Pointer ♦/
/• Buffer •/
/• Connection •/
/• Response nailbox token for RMX
extern extern FILE int _fc flleC NFILEI; Dl; ~ /• /• The flle structures •/ Console colusn •/
/• Flag; i •/
ttdefine FWR 0x01 /• Wrlting •/
tdefine “fwrok 0x02 /• Wrlting is ok •/
ttdefine “frdok 0x04 /• Reading is ok •/
tdefine FB1N 0x08 /• 8 Olts •/
ttdefine 'fubuf 0x10 /• Buffer frotti setbuf •/
ttdefine ‘FERR 0x20 /• Error •/
ttdefine "FEOF 0x40 /• End of flle •/
tdefine ’FRMXTH 0x80 /• File is RMX teroinal handler •/
ttdefine JFCI 0x200 /• File is :ci: •/
ttdefine FCO 0x400 /• File is :co: •/
ttdefine "FSTR 0x800 /• String strea® •/
char *gets();
char »fgetsO;
FILE • fopenO;
FILE ♦freopenl);
int fflushl);
int fclose();
long ftellt);
void setbuf () ;
char • allocO;
char «reallocl);
char • callocd;
tdefine
tdefine
tdefine
tdefine
tdefine
tdefine
ttdefine
ttdefine
ttdefine
tdefine
«define
tdefine
tdefine
stdin i&.flleton
stdout (&_fileCl3>
stderr (& fileC23)
getcharO fgetc(stdin)
getc(fp) fgetct(fp))
putchar(c) fputc((c), stdout)
putclc, fp) fputcl(c), (fp))
feof(fp) ((fp)->_flagst.FEOFI.FERR))
ferror(fp) ((fp)->’flag& FERR)
clearerr(fp) (<fp)->_flag 8 = “(.FERRI.FEOF))
fileno(fp) ((fp)-> conn)
wdleng() (16)
.exlt(s) dqSexit((s))
ttundef COHERENT
ttdefine UDI 1
14-51
Da die Datei stdio.h alle wichtigen Funktionen für Ein- und Ausgabe
enthält, kann man allgemein festhalten, daß jedes C-Programm, das Sie
erstellen werden, die Zeile
ttinclude <stdio.h>
enthalten sollte.
Die Datei stdio.h in MS-DOS:
/II
• This header file defines • package. 1 11/ •define .BUFSIZ 512 •define .NFILE 16 the inforaation used by the Standard 1/0 /• Standard buffer siie •/ /• oaxiaua nuaber of files 1/
struct .iobuf ( char l.ptrj int _cnt| char •.bauet char .flogt char flieg >1 /I current buffer polnter 1/ /• current byte count 1/ /I base address of I/O buffer •/ /• control flags 1/ /• file nuaber •/
extern struct _iobuf _iob!.NFlLEJf
•define .10READ 1 •define .I0VRT 2 •define .I0NBF 4 •define .10MYBUF B •define .10E0F 16 • define .I0ERR 32 •define I0STR6 64 •define _I0Rtf 128 /• read flag •/ /• urite flag •/ /• non-buffered flag • / /• end-of-file flag •/ /• error flag •/
•define MULL 0 •define FILE struct .iobuf •define EOF <-11 /I null pointer value •/ /• shorthand 1/ /• end-of-file Code •/
•define stdin (l.ioblOl) •define stdout (b.iob(ll) •define stderr (L_iob(21l /• Standard input file pointer •/ fl Standard Output file pointer •/ /• Standard error file pointer •/
•define getc(p) ( —(p)->_cnt>-0? •(p>->_ptr**4255> .filbf <p>>
•define getchard getclstdin)
•define putc(c,p> < — (p)->_cnt>-0? ((int)11(p>->_ptr**«(cl)Ii_f1sbf(<c),pJ)
•define putchar(c) putc(c,stdout)
•define feof(p) ((<pI->_flag4_I0E0F)'»0)
•define ferror(p) (((p>->_flagk_IOERR)!*0)
•define fileno(p> (pl->_file
•define reuind(fp) fseek(fp,OL,O)
•define fflush(fp) _f1»bf(-1,fp1
FILE Ifopentij
FILE Ifreopentii
long ftell<>}
char lfgets(>|
• define abs(x) <(x)<0?-(«>i<»>)
• define na>(a,b) ((»IXbPlil: (b))
•define ainta.b) ((a)<-<b>?(a)i<b>I
14-52
DIE WICHTIGSTEN
BIBLIOTHEKSFUNKTIONEN
15-1
DIE WICHTIGSTEN 4 c
BIBLIOTHEKSFUNKTIONEN 10
Wie schon früher erwähnt, bietet C eine Bibliothek bereits vordefinierter
Funktionen an; bevor wir uns mit solchen Bibliotheksfunktionen be-
schäftigen, werden wir noch einen neuen Datentyp, void. kennenler-
nen. (Dieser Datentyp ist unter MS-DOS nicht verfügbar, läßt sich aber
mit Bdefine void int nachbilden.) Der Datentyp void kann nur als Da-
tentyp von Funktionen vereinbart werden; es existieren keine Datenob-
jekte von diesem Typ. Funktionen von diesem Typ können daher keinen
Funktionswert liefern. Solche Funktionen sind mit procedure in PAS-
CAL vergleichbar, welche ebenfalls keinen Wert an die aufrufende
Funktion zurückliefern.
Alle wichtigen C-Bibliotheksfunktionen sollten in der mit Ihrem C-Com-
piler gelieferten Beschreibung aufgeführt sein. Dabei wird neben dem
Funktionsnamen auch eine Beschreibung der erforderlichen Parameter
angegeben.
Beschreibungen von Bibliotheksfunktionen haben oft folgendes Ausse-
hen:
Binclude <stdio.h>
char «fgets (s, n, dateizeiger)
char *s;
int n;
FILE «dateizeiger;
Dies würde bedeuten:
Die Funktion fgets ist in der Systemdatei stdio.h vordefiniert und
liefert als Funktionswert einen Zeiger auf ein char-Element: char
«fgets (...). Als 1. Parameter für s ist ein char-Zeiger: char *s
an die Funktion fgets zu übergeben. Der 2. Parameter n ist als
int-Variable oder int-Konstante int n beim Aufruf dieser Funktion
anzugeben. Als 3. Parameter dateizeiger ist ein Filepointer:
FILE «dateizeiger zu übergeben.
Manchmal wird ttinclude <stdio.h> angegeben, obwohl die be-
treffende Funktion nicht in stdio.h vordefiniert ist. In einem sol-
chen Fall ist dann z. B. ein Parameter von einem Datentyp, z. B.
FILE, in stdio.h vordefiniert.
Die hier zusammengestellten Bibliotheksfunktionen geben wir in alpha-
betischer Reihenfolge an:
15-3
abs (i)
Die abs-Funktion liefert den Absolutwert des ganzzahligen Argu-
ments i.
Da diese Funktion in CP/M-86 und MS-DOS in der Systemdatei
stdio.h definiert ist, muß vor ihrer Verwendung ttinclude
<stdio.h> angegeben werden.
double atof (s)
char *s;
h in CP/M-86: float atof (s) */
Die atof-Funktion wandelt eine Zahl, die als ASCII-Zeichenkette
gespeichert ist, in einen double- (in CP/M-86: float-) Wert um
und meldet diesen als Funktionswert zurück.
Sind in der Zeichenkette, auf die der zu s übergebene aktuelle Pa-
rameter zeigt, zu Beginn Leerzeichen enthalten, so werden diese
ignoriert. Führende Vorzeichen werden von dieser Funktion ak-
zeptiert und korrekt interpretiert. Die Umwandlung wird beendet,
wenn das erste "Nicht-Ziffernzeichen" in der Zeichenkette auf-
taucht (ausgenommen das Zeichen .); in manchen IW/X-Versio-
nen ist die Angabe von Exponenten (gekennzeichnet mit E oder e)
erlaubt. (Diese Funktion ist in LATTICE-C unter MS-DOS nicht ver-
fügbar.)
Eine mögliche Version für atof wäre z. B.:
double atof(zeichk) /• Zeichenkette zeichk nach double umwandeln •/
char »zeichk;
<
double wert, nachkomma;
int Vorzeichen;
for ( ; »zeichk:: ' II »zeichk:: • \n ‘ II »zelchk = :'\t' ; zeichkw)
; /• Zwischenraeume uebergehen •/
Vorzeichens ;
if (»zeichk=:II »zeichk:=‘)
Vorzeichen = (•(zeichk+f)ss'♦ ) ? i : -i;
for (wert=0 ; »zeichk>s'O && •zeichk<«‘9' ; zeichkw)
wert = io»wert+ »zeichk- ;
lf (•zeichk ss ’. ’)
zeichk++;
for (nachkoaffia:10 ; »zeichk>= 0 && »zelchkc 9 ; nachkonmia» = 10,zelchk++)
wert « wert*(»zeichk- 0‘)/nachkomma;
return(vorzeichen»wert);
int atoi (s)
char *s;
Die atoi-Funktion wandelt eine Zahl, die als ASCII-Zeichenkette
gespeichert ist, in einen Int-Wert um und liefert ihn als Funktions-
wert zurück.
15-4
Sind in der Zeichenkette, auf die der zu s übergebene aktuelle Pa-
rameter zeigt, führende Leerzeichen vorhanden, so werden diese
ignoriert. Führende Vorzeichen werden von dieser Funktion ak-
zeptiert und korrekt ausgewertet. Die Funktion atoi beendet die
Umwandlung, wenn in der zu wandelnden Zeichenkette das erste
"Nicht-Ziffernzeichen" auftaucht.
Eine mögliche Realisierung dieser Funktion wäre:
atoi(zeichk) /• Zeichenkette zeichk nach int umwandeln •/
char »zeichk;
int Vorzeichen, wert;
for ( ; »zeichk—- ll »zeichk—-\n' ll »zeichk==\f ; zeichk**)
; /• Zwischenraeume ueoerlesen •/
vorzeichen=i;
if (»zeichk— *' ll »zei chks= ’ - ’ I
Vorzeichen = (• (zeichk**) == + ’) ? 1 : -1;
for (wertsO ; »zeichk>« Q && »zeichk*«-?' ; zeichk**)
wert - 10»wert* »zelchk-0-;
return(vorzeichen»wert);
LATTICE-C unter MS-DOS enthält in seiner Bibliothek statt atoi
die Funktion stcd___i. Diese wandelt eine aus Dezimalziffern be-
stehende Zeichenkette in einen int-Wert um:
int >tcd_i («, r)
char -s;
int -c
s zeigt auf diejenige Zeichenkette, die in einen int-Wert umzu-
wandeln ist. r ist der Zeiger auf den zurückgegebenen int-Wert.
In s sind die Werte 0 ... 9 erlaubt; das erste Zeichen in s darf ein
Vorzeichen ’+’ bzw. sein. Ein Rückgabewert ’0’ der Funktion
stcd_i zeigt an, daß das erste Zeichen in s kein erlaubtes Dezi-
malzeichen war. Ein von ’0’ verschiedener Rückgabewert zeigt an,
wieviele Zeichen in s von stcd_i in einen int-Wert umgewandelt
wurden.
long atoi (s)
char >s;
Die atol-Funktion entspricht weitgehend der atoi-Funktion, nur
wandelt sie eine Zahl, die als ASCII-Zeichenkette gespeichert ist,
nicht in einen int-Wert, sondern in einen long-Wert um (siehe
atoi). (Diese Funktion ist nicht in LATTICE-C unter MS-DOS ver-
fügbar.)
15-5
char «calloc (laeng, objgroes)
unsigned int laeng, objgroes;
Die Funktion calloc liefert einen Zeiger auf genügend Speicher-
platz für laeng Objekte der Größe objgroes. Kann nicht genü-
gend Speicherplatz reserviert werden, so ist der Funktionswert
NULL. Der reservierte Speicherplatz wird mit dem Wert O initiali-
siert.
Beispiel: Deklarationen
char «t; int n;
unsigned int groesse;
if (t = calloc ((unsigned int) n, groesse) == NULL)
printf(”\n zu wenig Speicherplatz \n”);
int close (filedeskriptor)
(unsigned) int filedeskriptor;
Diese Funktion löst die Verbindung zwischen einem Filedeskriptor
und einer offenen, zuvor mit open oder creat eröffneten Datei
und gibt den entsprechenden Filedeskriptor zur Wiederverwen-
dung mit einer anderen Datei frei. Diese Funktion liefert den Wert
O, wenn eine Datei korrekt geschlossen werden konnte, und -1,
falls ein Fehler bei der Ausführung dieser Routine auftrat.
In ISIS-II heißt die entsprechende Funktion
void dqSclose (filedeskriptor, fehler)
unsigned int filedeskriptor;
int 'fehler;
Diese Routine aus ISIS-II hebt die für eine Datei mit dq$-
open festgelegte Zugriffsart wieder auf. Soll zusätzlich die
Verbindung zwischen einem Filedeskriptor und der Datei auf-
gehoben werden, so ist folgende Funktion aufzurufen:
void dqSdetach (filedeskriptor, fehler)
unsigned int filedeskriptor;
int 'fehler;
Ist der Wert, auf den der int-Zeiger fehler zeigt, von O ver-
schieden, so ist bei der Ausführung der jeweiligen Routine
dqSclose oder dqSattach ein Fehler aufgetreten.
15-6
int creat (dateiname, schütz)
char -dateiname;
int schütz;
creat dient dazu, neue Dateien zu schaffen oder bereits existie-
rende Dateien zu überschreiben. Dieser Aufruf liefert einen File-
deskriptor, wenn die Datei dateiname angelegt werden konnte,
und -1, falls dies nicht möglich war. Mit dem Parameter schütz
kann in UNIX ein Zugriffsschutz für die Datei dateiname festge-
legt werden; der Parameter schütz ist aus Kompatibilitätsgrün-
den in CP/M-86 ebenfalls vorhanden, dort aber ohne jegliche Be-
deutung. Unter MS-DOS dient Bit 15 zur Festlegung des Dateityps;
Bit 15 - T kennzeichnet eine Binärdatei, Bit 15 - ’0’ eine Textdatei.
CP/M-86 kennt noch die beiden Funktionen:
int creata (dateiname, schütz)
char -dateiname;
int schütz;
creata ist identisch mit creat. Die zweite Funktion lautet:
/nf creatb (dateiname, schütz)
char Dateiname;
int schütz;
creatb ist mit creat vergleichbar, legt jedoch keine ASCII-
Datei, sondern eine Binärdatei an.
In ISIS-II heißt die entsprechende Funktion zum Anlegen ei-
ner Datei:
unsigned int dq$create (dateiname, fehler)
ehr Dateiname;
int .fehler;
Diese Funktion liefert den Filedeskriptor, der mit der Datei
dateiname verbunden wird. dqScreate legt allerdings kei-
ne Zugriffsart für die entsprechende Datei fest; dazu muß
noch explizit die Funktion dqSopen (siehe open) aufgerufen
werden. Ist der Wert, auf den der int-Zeiger fehler zeigt,
von O verschieden, so ist ein Fehler aufgetreten.
void exit (nummer) /* in LATTICE-C: int exit (nummer) *!
int nummer;
Der Aufruf dieser Funktion bewirkt, daß ein Programm sofort been-
det wird. Für nummer wird für gewöhnlich der Wert 0 angegeben,
15-7
falls das Programm korrekt beendet wurde. Alle von 0 verschiede-
nen Werte zeigen dann an, daß ein Fehler den Programmabbruch
erforderte.
exit bewirkt, daß vor Erreichen des Programmendes alle noch ge-
füllten Puffer geleert, alle geöffneten Dateien geschlossen und alle
reservierten Speicherblöcke freigegeben werden. Erst dann ruft
exit zur Beendigung des Programms die Funktion_____exit auf.
Diese Funktion _exit kann der Programmierer ebenfalls direkt
aufrufen; dabei sollte er allerdings bedenken, daß_exit das Pro-
gramm unmittelbar beendet, ohne noch gefüllte Puffer zu verarbei-
ten oder noch geöffnete Dateien zu schließen.
# include < stdio.h >
inf fclose (dateizeiger)
FILE ‘dateizeiger;
Die fclose-Routine schließt eine Datei, die mit fopen geöffnet
wurde. Bevor fclose die Verbindung zwischen einer Datei und
dem Filepointer dateizeiger auflöst, überträgt sie allerdings alle
Inhalte von noch nicht geleerten E/A-Puffern in die entsprechende
Datei, fclose liefert als Funktionsergebnis den Wert 0, wenn eine
Datei ohne Fehler geschlossen werden konnte, ansonsten den
Wert -1. Die Funktion exit ruft zum Schließen aller noch geöffne-
ten Dateien fclose auf.
# include < stdio.h >
int fflush (dateizeiger)
FILE ‘dateizeiger;
Die fflush-Routine überträgt alle Inhalte von noch nicht geleerten
E/A-Puffern in die Datei, die mit dem Filepointer dateizeiger ver-
bunden ist. fflush wird auch von der Funktion fclose verwendet,
um noch gefüllte Puffer in eine Datei zu übertragen. Im Gegensatz
zu fclose unterbricht fflush allerdings nicht die Verbindung zwi-
schen einer Datei und einem dateizeiger. Die Funktion fflush
bleibt ohne Wirkung auf Dateien, die nicht zum Schreiben geöffnet
sind, fflush liefert den Wert 0. falls keine Fehler bei der Ausfüh-
rung dieser Routine aufgetreten sind, andernfalls wird der Wert-1
zurückgegeben.
15-8
# include < stdio.h >
int fgetc (dateizeiger)
FILE 'dateizeiger;
Die Funktion fgetc liefert aus der Datei, die mit dem Filepointer
dateizeiger verbunden ist, das nächste Zeichen (Byte). Der spe-
zielle Wert EOF (-1) wird geliefert, wenn das Ende einer Datei er-
reicht wurde.
# include <stdio.h>
char «fgets (s, n, dateizeiger)
char -s;
int n;
FILE -dateizeiger;
fgets liest von der Datei, die mit dateizeiger verbunden ist, in
den Speicherplatz, der mit s adressiert ist, entweder (n - 1) Zei-
chen oder eine Zeichenkette bis zum \n-Zeichen ein, je nachdem,
was zuerst zutrifft. Hinter dem (n - 1). Zeichen oder dem \n-Zei-
chen wird im Speicher ein \O-Zeichen angehängt, fgets liefert
den Zeiger s oder den Zeigerwert NULL, wenn das Ende der Da-
tei erreicht wurde oder beim Lesevorgang ein Fehler auftrat, fgets
unterscheidet sich von der Funktion gets darin, daß es nicht nur
von einer Standardeingabe lesen kann, sondern auch automa-
tisch das \n-Zeichen am Ende der gelesenen Zeichenkette an-
hängt, wenn die Länge der Zeichenkette größer oder gleich n ist.
# include < stdio.h >
FILE «fopen (dateiname, modus)
char -dateiname, -modus;
fopen eröffnet die Datei dateiname und liefert als Funktionswert
einen Filepointer, der ab diesem Zeitpunkt mit der Datei dateina-
me verbunden ist. Mit dem Parameter modus wird die Zugriffsart
festgelegt:
”r” Datei zum Lesen öffnen
”w” Datei zum Schreiben öffnen
”a” Datei zum Schreiben ab Dateiende öffnen
”r + w” Datei zum Lesen und Schreiben öffnen (nur in
ISIS-II möglich)
In ISIS-II kann noch b angehängt werden: ”rb”, "wb”, ”ab”,
”r + wb”, wodurch eine binäre Datei und keine ASCII-Datei eröff-
net wird.
Unter MS-DOS und in einigen UN/X-Systemen stehen noch folgende
Parameter für modus zur Verfügung:
15-9
”r+” Datei zum Lesen öffnen und zugleich Schreiben er-
lauben.
”w +” Datei zum Schreiben öffnen und zugleich Lesen er-
lauben; mit diesem modus wird der Inhalt einer
existierenden Datei gelöscht oder eine neue Datei
angelegt.
”a +” Datei zum Schreiben ab Dateiende öffnen und zu-
gleich Lesen erlaubt.
Die neueste C-Implementation von Microsoft bietet in Abweichung von
UNIX-XENIX-Systemen außerdem folgende Anfügungen an den Para-
meter modus
t Datei im Textmodus eröffnen
b Datei im Binärmodus eröffnen
Wurde einer dieser Parameter angegeben, so kann man die Routi-
nen fseek oder rewind verwenden, um den Schreib/Lesezeiger in der
Datei zu versetzen und so von Lesen auf Schreiben, oder umgekehrt,
umzuschalten.
fopen liefert als Funktionswert den NULL-Zeiger, wenn die Datei
dateiname nicht geöffnet werden kann. Drei FILE-Objekte (File-
pointer) werden schon beim Programmstart automatisch angelegt:
stdin Standardeingabe (Terminal)
stdout Standardausgabe (Terminal)
stderr Standard-Fehlermeldungen (Terminal)
CP/M-86 kennt noch 2 weitere Funktionen:
# include < stdio.h >
FILE «fopena (dateiname, modus)
char -dateiname, -modus;
Diese Funktion ist identisch mit fopen
# include < stdio.h >
FILE «fopenb (dateiname, modus)
char -dateiname. -modus;
fopenb ist mit der Routine fopen vergleichbar, nur daß
durch diese Funktion keine ASCII-Datei, sondern eine binäre
Datei eröffnet wird.
15-10
# include < stdio.h >
intfprintf (dateizeiger, kontrollzeichenkette, paraml,...)
FILE 'dateizeiger;
char 'kontrollzeichenkette;
fprintf formatiert die Parameter paraml. ... usw. entsprechend
der in kontrollzeichenkette angegebenen Formatierungszei-
chen (siehe 6.3) und gibt dann die formatierten Elemente auf die
Datei, die mit dateizeiger verbunden ist, aus. Für formatierte
Ausgaben auf dem Bildschirm steht die Routine printf zur Verfü-
gung. Tritt bei Ausführung der Funktion fprintf ein Fehler auf, so
liefert diese Routine den Wert -1, andernfalls die Anzahl der aus-
gegebenen Zeichen.
# include < stdio.h >
int fputc (zeich, dateizeiger)
int zeich;
FILE •dateizeiger;
fputc schreibt das Zeichen zeich in die Datei, die mit dateizei-
ger verbunden ist. Diese Routine liefert den Wert -1, wenn ein
Fehler auftrat, ansonsten gibt sie als Funktionswert das geschrie-
bene Zeichen zeich zurück.
# include <stdio.h>
int fputs (zeichk, dateizeiger)
char 'zeichk;
FILE 'dateizeiger;
fputs kopiert die Zeichenkette zeichk in die Datei, die mit datei-
zeiger verbunden ist; dazu verwendet sie die Funktion putc. Die
Funktion fputs unterscheidet sich in 2 Punkten von puts:
-fputs wirkt nicht nur auf den Bildschirm
- fputs hängt nicht automatisch ein \n-Zeichen an die kopierte
Zeichenkette zeichk an.
# include <stdio.h>
int fread (puff, byte__zahl, anzahl, dateizeiger)
char -puff;
int byte_zahl, anzahl;
FILE •dateizeiger;
Die Funktion fread liest anzahl Objekte, jedes mit byte____zahl
Bytes, von der Datei, die mit dem Filedeskriptor dateizeiger ver-
bunden ist, in den Speicherbereich, der mit puff adressiert ist.
fread liefert als Funktionswert die wirklich gelesene Anzahl von
15-11
Objekten; wenn ein Fehler auftrat oder das Ende der Datei erreicht
wurde, liefert fread den Wert O.
# include < stdio.h >
FILE «freopen (neuedatei, modus, dateizeiger)
char -neuedatei, -modus;
FILE •dateizeiger;
freopen schließt die Datei, mit der dateizeiger verbunden ist,
und verbindet dann dateizeiger mit der neuen Datei neuedatei
Die Funktion freopen ermöglicht die Verbindung der Filepointer
stdin, stdout, stderr mit einer Datei. Tritt bei Ausführung dieser
Routine ein Fehler auf, so liefert sie den Zeigerwert NULL, an-
dernfalls den Filepointer. CP/M-86 kennt noch 2 weitere Routinen
mit identischen Parametern: freopa (entspricht vollständig freo-
pen) und freopb (entspricht nahezu freopen, nur daß keine
ASCII-Datei, sondern eine binäre Datei eröffnet wird). Die
modus-Angaben sind bei fopen beschrieben.
# include < stdio.h >
intfscanf (dateizeiger, kontrollzeichenkette, paraml,...)
char •kontrollzeichenkette;
FILE •dateizeiger;
fscanf liest Daten aus der Datei, die mit dateizeiger verbunden
ist, entsprechend dem Format kontrollzeichenkette in die mit
paraml, ... adressierten Speicherplätze. Die Formatspezifikatio-
nen für kontrollzeichenkette sind in 6.4 beschrieben, fscanf
unterscheidet sich von scanf darin, daß es nicht nur von der Stan-
dardeingabe stdin lesen kann, fscanf liefert als Funktionswert
die Zahl der wirklich gelesenen Formatelemente, oder EOF (-1),
wenn das Ende der Datei erreicht wurde oder ein Lesefehler auf-
getreten ist.
# include < stdio.h >
int fseek (dateizeiger, offset, wie)
FILE •dateizeiger;
long offset;
int wie;
fseek ermöglicht das Verschieben des Schreib/Lesezeigers in-
nerhalb der Datei, die mit dateizeiger verbunden ist. Hat wie
den Wert:
15-12
0, so wird der Schreib/Lesezeiger vom Beginn der Datei an um off-
set Bytes versetzt.
1, so wird der Schreib/Lesezeiger von der momentanen Position an
um offset Bytes versetzt.
2, so wird der Schreib/Lesezeiger vom Dateiende an um offset By-
tes zurückgesetzt.
fseek liefert den Wert 0, falls, wie gefordert, die Positionierung
durchgeführt werden konnte, ansonsten den Wert -1.
# include < stdio.h >
long ftell (dateizeiger)
FILE -dateizeiger;
ftell wird verwendet, um die aktuelle Position des Schreib/Lese-
zeigers in der Datei, die mit dateizeiger verbunden ist, zu ermit-
teln. Die Position wird als long-Funktionswert geliefert und gibt
den Abstand zum Dateianfang in Bytes an.
# include <stdio.h>
int fwrite (puffer, puff_groesse, anzahl, dateizeiger)
char •puffer;
int puff—groesse. anzahl;
FILE •dateizeiger;
fwrite schreibt ab Adresse puffer im Speicher anzahl Blöcke
von der Größe puff___groesse auf die Datei, die mit dateizeiger
verbunden ist. fwrite liefert als Funktionsergebnis die Zahl der
wirklich geschriebenen Blöcke.
# include < stdio.h >
char «gets (zeichk)
char 'zeichk;
gets ist sehr ähnlich zu fgets, liest aber immer von der Standard-
eingabe (stdin).
gets liest eine Zeichenkette bis zum \n-Zeichen und legt die ge-
lesenen Zeichen ab Adresse zeichk im Speicher ab. Das \n-Zei-
chen wird dort allerdings mit \0 überschrieben, gets liefert die
Adresse zeichk als Funktionswert, wenn die Funktion fehlerfrei
ablief. Tritt bei der Ausführung der Funktion ein Fehler auf oder
wird das Dateiende erreicht, so gibt gets den Zeigerwert NULL
zurück.
15-13
# include < stdio.h >
int getw (dateizeiger)
FILE •dateizeiger;
getw liest das nächste Wort von der Datei, die mit dateizeiger
verbunden ist. Auf manchen Rechnern werden mit getw 4 Bytes,
auf anderen nur 2 Bytes gelesen. Normalerweise liefert getw das
gelesene Wort als Funktionswert; bei Fehlern oder Erreichen des
Dateiendes wird EOF (-1) zurückgegeben (getw ist nicht in LAT-
TICE-C unter MS-DOS verfügbar).
char *index (zeichk, zeich) /• in ANSI C: char »strchr (zeichk, reich) ./
char ‘zeichk, zeich;
Die Routine index liefert einen Zeiger auf das erste Vorkommen
von zeich in der Zeichenkette zeichk. Ein NULL-Zeiger wird als
Funktionswert zurückgegeben, wenn das Zeichen zeich nicht in
der Zeichenkette zeichk vorkommt.
# include <ctype.h>
int isalnum (zeich)
int zeich; /* oder: char zeich •/
Das Makro isalnum überprüft, ob es sich bei zeich um ein alpha-
numerisches Zeichen (A ... Z, a ... z, 0 ... 9) handelt, isalnum lie-
fert den Wert "FALSE" (O), wenn es sich bei zeich um kein alpha-
numerisches Zeichen handelt, ansonsten den Wert "TRUE" (ein
von 0 verschiedener Wert).
# include <ctype.h>
int isalpha (zeich)
int zeich; /* oder: char zeich •/
Das Makro isalpha überprüft, ob es sich bei zeich um ein Zei-
chen aus dem Alphabet (A ... Z, a ... z) handelt, isalpha liefert den
Wert 0, wenn es sich bei zeich um kein alphabetisches Zeichen
handelt, ansonsten wird ein von 0 verschiedener Wert geliefert.
15-14
# include <ctype.h>
int isascii (zeich)
int zeich; /• oder: char zeich */
Das Makro isascii überprüft, ob es sich bei zeich um ein ASCII-
Zeichen handelt; zeich ist ein ASCII-Zeichen, wenn sein Hexa-
Wert zwischen OxOO und 0x7f liegt, isascii liefert den Wert 0.
wenn zeich kein ASCII-Zeichen ist, ansonsten liefert es einen von
0 verschiedenen Wert.
# include <ctype.h>
int iscntrl (zeich)
int zeich; /• oder: char zeich •/
Das Makro iscntrl überprüft, ob es sich bei zeich um ein Steuer-
zeichen (hexadezimaler ASCII-Code: 0x00 ... Ox1f und Ox7f)
handelt, iscntrl liefert den Wert 0, wenn zeich kein Steuerzei-
chen ist, ansonsten einen von 0 verschiedenen Wert.
# include <ctype.h>
int isdigit (zeich)
int zeich; /'Oder: char zeich •/
Das Makro isdigit überprüft, ob es sich bei zeich um eine Ziffer
handelt. Wenn ja, dann liefert isdigit einen von 0 verschiedenen
Wert, ansonsten wird 0 zurückgegeben.
# include <ctype.h>
int islower (zeich)
int zeich; /• oder: char zeich •/
Das Makro islower überprüft, ob es sich bei zeich um einen
Kleinbuchstaben (a ... z) handelt, islower liefert den Wert 0, wenn
es sich bei zeich um keinen Kleinbuchstaben handelt, ansonsten
einen von 0 verschiedenen Wert.
# include <ctype.h>
int isprint (zeich)
int zeich; /* oder: char zeich '/
Das Makro isprint überprüft, ob es sich bei zeich um ein druck-
bares Zeichen (hexadezimaler ASCII-Code: 0x20 ... 0x7e) handelt,
isprint liefert den Wert 0. wenn es sich bei zeich um ein nicht
druckbares Zeichen handelt, ansonsten einen von 0 verschiede-
nen Wert, isprint überprüft das Gegenteil von iscntrl.
15-15
# include < ctype.h >
int ispunct (zeich)
int zeich; /* oder: char zeich
Das Makro ispunct überprüft, ob zeich weder ein Steuerzeichen
noch ein alphanumerisches Zeichen ist; wenn ja, dann liefert
ispunct einen von 0 verschiedenen Wert, ansonsten 0
# include <ctype.h>
int isspace (zeich)
int zeich; h oder: char zeich •!
Das Makro isspace überprüft, ob es sich bei zeich um einen
Zwischenraum (Leerzeichen, Tabulator, Zeilentrenner) handelt;
wenn ja, so liefert isspace einen von 0 verschiedenen Wert, an-
sonsten O.
# include < ctype.h >
int isupper (zeich)
int zeich; /• oder: char zeich •!
Das Makro isupper überprüft, ob es sich bei zeich um einen
Großbuchstaben (A - Z) handelt, isupper liefert den Wert 0. wenn
es sich bei zeich um keinen Großbuchstaben handelt, ansonsten
einen von 0 verschiedenen Wert.
long Iseek (filedeskriptor, offset, ausgangsbasis)
int filedeskriptor, ausgangsbasis;
long offset;
filedeskriptor ist der von open bzw. creat gelieferte Filedes-
kriptor. offset gibt die Zahl der ab Position ausgangsbasis zu
überspringenden Bytes an. ausgangsbasis kann 3 verschiedene
Werte annehmen:
O bedeutet: Ausgangsbasis ist Dateianfang
1 bedeutet: Ausgangsbasis ist der momentane Positionszeiger
2 bedeutet: Ausgangsbasis ist Dateiende
Bei der Iseek-Funktion handelt es sich um eine elementare E/A-
Routine, die das Versetzen des Schreib/Lesezeigers innerhalb ei-
ner Datei erlaubt.
Iseek liefert als Resultat die absolute Position ab Dateianfang des
Schreib/Lesezeigers, falls die Positionierung erfolgreich verlief,
ansonsten den Wert -1L
15-16
In den UNIX-Versionen 1 ... 6 heißt der Systemaufruf nur seek,
wobei seek sich genauso verhält wie fseek, abgesehen davon,
daß offset vom Typ int und nicht vom Typ long ist.
In ISIS-II ergibt sich für die entsprechende elementare Bi-
bliotheksfunktion folgende Beschreibung:
void dqSseek (filedeskriptor, ausgangsbasis, offset, fehler)
unsigned int filedeskriptor;
int ausgangsbasis, -fehler;
long offset;
Hier kann ausgangsbasis 4 verschiedene Werte anneh-
men:
1 bedeutet: Schreib/Lesezeiger ausgehend von momentaner
Position um offset Bytes zurücksetzen.
2 bedeutet: Schreib/Lesezeiger auf Position offset (vom Da-
teianfang gesehen) setzen.
3 bedeutet: Schreib/Lesezeiger ausgehend von momentaner
Position um offset Bytes vorrücken
4 bedeutet: Schreib/Lesezeiger vom Dateiende an um offset
Bytes zurücksetzen.
Tritt hier bei der Ausführung von dqSseek ein Fehler auf, so
besitzt «fehler einen von O verschiedenen Wert.
char «malloc (laenge)
unsigned int laenge;
malloc stellt einen zusammenhängenden Speicherbereich von
laenge Bytes zur Verfügung und liefert für den Fall, daß kein Feh-
ler auftrat, die Anfangsadresse des reservierten Speicherbereichs.
Konnte der geforderte Speicherplatz von laenge Bytes nicht re-
serviert werden, so wird als Funktionswert ein NULL-Zeiger zu-
rückgegeben.
int open (dateiname, modus)
char •dateiname;
int modus;
Die elementare Funktion open ist ähnlich zu handhaben wie die
höhere Routine fopen. außer daß anstelle eines Filepointers ein
Filedeskriptor, d. h. eine positive ganze Zahl int filedeskriptor,
als Funktionswert geliefert wird.
Wie bei fopen ist der Parameter dateiname eine Zeichenkette,
die den Namen der zu eröffnenden Datei enthält. Der 2. Parameter
modus gibt die Zugriffsart an:
15-17
0 Datei zum Lesen öffnen
1 Datei zum Schreiben öffnen
2 Datei zum Lesen und Schreiben öffnen (Update)
Falls bei der Dateieröffnung ein Fehler auftritt, liefert open den
Wert -1, andernfalls den filedeskriptor als Funktionswert zu-
rück. Wichtig ist hier noch, daß der Versuch, eine nicht existieren-
de Datei mit open zu eröffnen, auf einen Fehler führt. Unter MS-
DOS wird mit Bit 15 festgelegt, ob es sich bei der zu öffnenden
Datei um eine Binärdatei (Bit 15 - '1') oder eine Textdatei (Bit 15 =
’0’) handelt.
In ISIS-II benötigen wir zum Öffnen einer Datei und Festle-
gen der Zugriffsart zwei Routinen
(1) connection dqSattach (dateiname, fehler)
char -dateiname;
int -fehler;
(2) void dqSopen (filedeskriptor, modus, puff_______zahl,
fehler)
connection filedeskriptor
int modus, -fehler;
char -dateiname;
Mit Anweisung (1) wird eine Verbindung zwischen dateiname
und dem Programm hergestellt; dazu liefert die Routine dqSat-
tach einen filedeskriptor, der hier einen vordefinierten Typ
'connection' besitzt (in udi.h:*> typedef unsigned connection.
vorzeichenlose Ganzzahl). Für den Parameter fehler ist ein int-
Zeiger anzugeben; der Wert zu diesem Zeiger ist von O verschie-
den, falls bei der jeweiligen Operation ein Fehler auftrat.
Mit Anweisung (2) wird für die Datei, der der filedeskriptor zu-
geordnet ist, die Zugriffsart festgelegt; dies geschieht über den 2.
Parameter modus:
1 Datei zum Lesen öffnen
2 Datei zum Schreiben öffnen
3 Datei zum Lesen und Schreiben öffnen
Der 3. Parameter puff_zahl gibt die Anzahl der Puffer an, die
beim Zugriff auf die Datei, die mit filedeskriptor verbunden ist,
zu verwenden sind; ein Vorschlag hier ist:
HINWEIS: *) udl = universal development interface
15-18
puff__zahl = O:
puff__zahl = 1:
puff__zahl = 2:
bei Bildschirmein-Zausgabe
bei wahlfreiem (RANDOM-) Dateizugriff
bei sequentieller Dateiverarbeitung
Auch hier ist zu beachten, daß die Funktion dqSattach einen
Fehler liefert, wenn die Datei dateiname nicht bereits existiert.
# include < stdio.h >
int printf (kontrollzeichenkette, paraml,...)
char -kontrollzeichenkette;
printf formatiert die paraml,... entsprechend den Vorgaben in
kontrollzeichenkette und gibt sie dann am Bildschirm aus. Der
Rückgabewert ist in den unterschiedlichen C-Versionen verschie-
den: Im Fehlerfall wird bei einigen Versionen ein von 0 verschiede-
ner Wert und sonst der Wert 0 geliefert. Andere Versionen geben
als Funktionsresultat die Zahl der formatierten Zeichen und im
Fehlerfalle einen negativen Wert zurück. Die möglichen Formatie-
rungszeichen für kontrollzeichenkette sind in 6.3 beschrieben.
# include < stdio.h >
int puts (zeichk)
char -zeichk;
Die Funktion puts kopiert bis zum \O-Zeichen diejenige Zeichen-
kette, die mit dem char-Zeiger zeichk adressiert ist, auf die Stan-
dardausgabe (Bildschirm), puts fügt automatisch ein \n-Zeichen
an das Ende der Zeichenkette zeichk an; puts gibt also die Zei-
chenkette zeichk am Bildschirm aus und führt einen Zeilenvor-
schub auf den Anfang der nächsten Bildschirmzeile «CR>
<LF>) aus.
# include < stdio.h >
int putw (wort, dateizeiger)
FILE -dateizeiger;
int Wort;
putw schreibt das Wort wort (16 Bits) auf die Datei, die mit da-
teizeiger verbunden ist. Auf manchen Rechnern werden von
putw 4 Bytes (32 Bits) auf eine Datei geschrieben, putw liefert
wort als Funktionswert, wenn kein Fehler auftrat, ansonsten -1.
Da EOF beim Erreichen des Dateiendes ein zulässiger Wert ist,
muß die Funktion ferror verwendet werden, um einen Schreibfeh-
ler bei dieser Funktion erkennen zu können. (Diese Funktion ist
nicht in LATTICE-C verfügbar).
15-19
void qsort (vekt, anzahl, laenge, vergl)
char *vekt;
int anzahl, laenge, vergiß
Die Routine qsort ist eine Sortierroutine, die den Quicksort-Algo-
rithmus von C. A. R. Hoare verwendet. Dabei gibt vekt die An-
fangsadresse des zu sortierenden Datenblocks an. anzahl enthält
die Anzahl der zu sortierenden Elemente; laenge gibt die Byte-
zahl an, die ein Element umfaßt. Der letzte Parameter ist der
Name einer Vergleichsroutine mit 2 Parametern, welche Zeiger
auf die Elemente sind, die zu vergleichen sind. Diese Vergleichs-
routine müssen Sie selbst entwerfen; dabei sollten sie folgendes
beachten:
vergl (a, b) muß einen Wert < 0, wenn a < b
> 0, wenn a > b
- 0, wenn a - b
liefern.
In CP/M-86 wird von der Funktion qsort immer der Wert 0 gelie-
fert, so daß dort eigentlich int qsort (...) gilt. (Diese Funktion ist
nicht in LATTICE-C verfügbar).
int read (filedeskriptor, puffer, byte_______zahl)
(unsigned) int filedeskriptor, byte_zahl;
char -puffer;
Die read-Funktion versucht, eine angegebene Anzahl von Bytes
von einer deklarierten Datei in den Pufferspeicher des Rechners
zu lesen.
Der 1. Paramter filedeskriptor identifiziert die Datei, aus der ge-
lesen werden soll; wieviele Bytes dort zu lesen sind, wird durch
den 3. Parameter, byte___zahl, angegeben. Der 2. Parameter
puffer gibt die Anfangsadresse des Speicherbereichs an, in dem
die gelesenen Bytes abzulegen sind.
Jeder Aufruf von read liefert als Funktionswert die Anzahl der
wirklich gelesenen Bytes; dabei kann der von read gelieferte Wert
kleiner sein als die geforderte byte_zahl, d. h.: es können weni-
ger Bytes gelesen worden sein, als gewünscht, weil weniger als
byte__zahl Bytes zum Lesen verblieben sind. Speziell beim Le-
sen vom Terminal liest read nur bis zum nächsten CR-Zeichen,
was normalerweise weniger als die geforderten Zeichen sein
kann. Falls read den Funktionswert 0 liefert, bedeutet dies, daß
das Dateiende erreicht wurde. Wird der Wert -1 geliefert, so ist
beim Lesen ein Fehler aufgetreten.
Die häufigsten Werte für den Parameter byte_zahl sind 1 (Lesen
eines Bytes) oder die vorgegebene Pufferlänge (UNIX, MS-DOS,
CP/M-86: 512 Byte). Die Wahl der Pufferlänge für byte_zahl ist
hierbei die effiziente Vorgehensweise.
15-20
In ISIS-II ist die elementare Leseroutine wie folgt angegeben:
unsigned dqSread (filedeskriptor, puffer, byte____zahl, fehler)
Diese Funktion dqSread arbeitet wie die eben beschriebene Rou-
tine read aus CP/M-86 und UNIX. Die Puffergröße hat bei ISIS-II
den Wert 128 Bytes.
# include < stdio.h >
int rewind (dateizeiger)
FILE •dateizeiger;
rewind setzt den Schreib/Lesezeiger der Datei, die mit dateizei-
ger verbunden ist, auf den Anfang der Datei, rewind (dateizei-
ger) ist identisch mit fseek (dateizeiger, OL, 0). rewind liefert
als Funktionswert O, falls die Positionierung auf den Dateianfang
erfolgreich durchgeführt werden konnte, ansonsten den Wert -1.
Char *rindex (zeichk, Zeich) /• in ANSI C: Char .«trrchr (zeichk, zeich) ./
char -zeichk, zeich;
Die Funktion rindex liefert einen Zeiger auf das letzte Vorkom-
men von zeich in der Zeichenkette zeichk Ein NULL-Zeiger wird
als Funktionswert zurückgegeben, wenn das Zeichen zeich nicht
in der Zeichenkette zeichk vorkommt.
# include <stdio.h>
int scanf (kontrollzeichenkette, paraml,...)
char •kontrollzeichenkette;
scanf liest Daten von der Standardeingabe (Terminal) entspre-
chend dem Format kontrollzeichenkette in die mit paraml,
... adressierten Speicherplätze. Die Formatspezifikationen sind in
6.4 beschrieben, scanf liefert als Funktionswert die Zahl der wirk-
lich gelesen Formatelemente, oder-1, wenn ein Lesefehler aufge-
treten ist.
# include <stdio.h>
/ntsprintf (zeichk, kontrollzeichenkette, paraml,...)
char ’zeichk, -kontrollzeichenkette;
sprintf formatiert die Parameter paraml, usw. entsprechend der
in kontrollzeichenkette angegebenen Formatierungszeichen
(siehe 6.3) und gibt die formatierten Elemente in der Zeichenkette
zeichk zurück. Für formatierte Ausgabe auf dem Bildschirm steht
15-21
die Routine printf zur Verfügung, sprintf hängt ein \O-Zeichen
an das Ende von zeichk an. Tritt bei Ausführung der Funktion
sprintf ein Fehler auf, so liefert diese Routine den Wert 0. anson-
sten die Adresse von zeichk.
# include <stdio.h>
int sscanf (zeichk, kontrollzeichenkette, paraml,...)
char -zeichk, -kontrollzeichenkette;
sscanf liest Daten aus der Zeichenkette zeichk entsprechend
dem Format kontrollzeichenkette in die mit paraml, ...
adressierten Speicherplätze. Die Formatspezifikationen für kon-
trollzeichenkette sind in 6.4 beschrieben, sscanf liefert als
Funktionswert die Zahl der wirklich gelesenen Formatelemente,
oder-1, wenn ein Lesefehler aufgetreten ist.
char «strcat (kettl, kett2)
char -kettl, -kette;
strcat kopiert die Zeichenkette kett2 an das Ende der Zeichen-
kette kettl Beide Zeichenketten müssen mit \0 abgeschlossen
sein, strcat geht davon aus, daß in kettl genügend Platz vor-
handen ist, um kett2 aufzunehmen strcat liefert als Funktions-
wert den Zeiger kettl auf den Anfang der gesamten Zeichenket-
te. Die resultierende Zeichenkette ist mit \0 abgeschlossen. Die
Version int strcat (kettl, kett2) älterer C-Implementationen lie-
fert keinen Zeiger zurück.
char «strchr (zeichk, zeich)
char -zeichk, zeich;
siehe Index
int strcmp (kettl, kett2)
char -kettl, -kette;
strcmp vergleicht die Zeichenketten kettl und kett2 byteweise.
Diese Funktion liefert einen positiven Wert, wenn kettl größer
(nach ASCII-Tabelle) ist als kett2 und einen negativen Wert, wenn
kettl kleiner (nach ASCII-Tabelle) ist als kett2 Sind kettl und
kett2 völlig gleich, so liefert diese Funktion den Wert 0. Der
Funktionswert entsteht als Differenz aus den beiden ersten nicht
übereinstimmenden Zeichen in kettl und kett2.
15-22
char «strcpy (kettl, kett2)
char 'kettl, 'kett2;
strcpy kopiert die Zeichenkette kett2 einschließlich \O in die
Zeichenkette kettl. Diese Funktion liefert den Zeiger kettl als
Funktionswert. Die ältere Implementierung int strcpy (kettl,
kett2) liefert keinen char-Zeiger zurück.
int strlen (zeichk)
char 'zeichk;
strlen liefert als Funktionswert die Länge der Zeichenkette
zeichk ohne das abschließende \O-Zeichen.
char «strncat (kettl, kett2, n)
char 'kettl, 'kette2;
int n;
Die strncat-Funktion ist mit strcat vergleichbar, nur daß sie
nicht mehr als n Zeichen von kett2 an kettl anhängt; die so zu-
sammengefügte Zeichenkette wird mit \O abgeschlossen. Als
Funktionswert liefert strncat den Zeiger kettl auf den Anfang
der gesamten Zeichenkette. (In einigen älteren C-Implementierun-
gen nicht verfügbar).
int strncmp (kettl, kett2, n)
char 'kettl, 'kett2;
int n;
Die strncmp-Funktion arbeitet wie strcmp, nur daß sie nicht
mehr als n Zeichen in kettl und kett2 vergleicht. (In einigen äl-
teren C-Implementierungen nicht verfügbar).
char «strncpy (kettl, kett2, n)
char 'kettl, 'kett2;
int n;
strncpy kopiert n Zeichen aus kett2 in die Zeichenkette kettl.
Der char-Zeiger kettl, der auf die neue Zeichenkette zeigt, wird
als Funktionswert geliefert. (In älteren Versionen von MS-C und
LATTICE-C in der Form mt strcpy (kettl, kett2, n). Diese Funk-
tion liefert die Anzahl kopierter Zeichen statt des char-Zeigers zu-
rück).
15-23
char * strrchr (zeichk, zeich)
char -zeichk. zeich;
siehe rindex
# include <ctype.h>
int toascii (zeich)
int zeich; /• auch char zeich möglich •/
toascii wandelt den int-Wert zeich in ein ASCII-Zeichen um.
# include <ctype.h>
int tolower (zeich)
int zeich; /• auch char zeich möglich •/
Wenn der Parameter zeich ein Großbuchstabe ist, so liefert tolo-
wer den entsprechenden Kleinbuchstaben. Ist zeich kein Groß-
buchstabe, so wird zeich unverändert zurückgegeben.
# include <ctype.h>
int toupper (zeich)
intzeich; /• auch char zeich möglich •/
Wenn der Parameter zeich ein Kleinbuchstabe ist, so liefert
toupper den entsprechenden Großbuchstaben. Ist zeich kein
Kleinbuchstabe, so wird zeich unverändert zurückgegeben.
# include <stdio.h>
int ungetc (zeich, dateizeiger)
int zeich;
FILE •dateizeiger;
ungetc schreibt das Zeichen zeich zurück in die Datei, die mit
dateizeiger verbunden ist. Es kann aber lediglich 1 Zeichen
(nicht EOF) in die Datei zurückgeschrieben werden. Wenn un-
getc ohne Fehler abläuft, so liefert es als Funktionswert zeich.
andernfalls -1.
Beispiel:
while (is*pace(zeich = getc (stdin)))
»
ungetc (zeich, stdin)
Dieser Programmausschnitt stellt das erste Zeichen, bei dem
es sich nicht um einen Zwischenraum (siehe isspace) han-
delt, wieder in die Standardeingabe zurück.
15-24
int unlink (dateiname)
char -dateiname;
unlink entfernt die Datei dateiname aus dem Dateisystem, so
daß dateiname nicht mehr zur Verfügung steht.
In ISIS-II ist die entsprechende Funktion wie folgt beschrie-
ben:
void dqSdelete (dateiname, fehler)
char -dateiname;
int -fehler;
unlink (aus UNIX, MS-DOS, CP/M) liefert als Funktionsresultat
den Wert 0 falls die Löschaktion erfolgreich verlief, ansonsten
den Wert -1.
dqSdelete (aus ISIS-II) verändert den Wert, auf den fehler zeigt,
so daß dieser von O verschieden ist, wenn ein Fehler bei
Funktionsausführung auftrat. Ist nach Aufruf von dqSdelete der
Wert, auf den der int-Zeiger fehler zeigt, gleich O. so deutet dies
daraufhin, daß die Funktion fehlerfrei ausgeführt werden konnte.
int write (filedeskriptor, puffer, byte_______zahl)
int filedeskriptor, byte_zahl;
char -puffer;
Diese Routine schreibt byte__zahl Bytes ab der Speicheradresse
puffer in eine Datei, die mit filedeskriptor identifiziert wird. Sie
können eine beliebige ganze positive*) Zahl für byte___zahl an-
geben. Wieviele Bytes auf eine Datei geschrieben wurden, wird
als Funktionswert zurückgeliefert; wenn diese wirklich geschriebe-
ne Zahl von Bytes nicht mit der geforderten Zahl (byte____zahl)
übereinstimmt.liegt im allgemeinen ein Schreibfehler vor; als
Funktionswert wird in diesem Fall gewöhnlich -1 zurückgeliefert.
Diese Tatsache macht man sich zur Anzeige von Schreibfehlern
häufig mit folgendem Programmcode zunutze:
if (writeffiledeskriptor, puffer, byte_zahl)I« byte_zahl)
printf ("Schreibfehler \n’’);
exlt(1);
Die häufigsten Werte für byte___zahl sind wieder 1 für "Schreiben
eines Einzelzeichens” oder die vom System vorgegebene Puffer-
länge 512.
HINWEIS: *> jedoch nur im positiven int-Bereich Ihres Rechners, üblicherweise bei 2
Byte bis zum Wert 32767.
15-25
In ISIS-II ist die entsprechende Funktion wie folgt beschrie-
ben:
void dqSwrite (filedeskriptor, puffer, byte_zahl, fehler)
unsigned int filedeskriptor, byte_zahl;
char •puffer;
int -fehler;
Die Routine dqSwrite arbeitet wie die eben beschriebene
Funktion write. Zu beachten ist wieder die Puffergröße 128
bei ISIS-II.
Diese Zusammenstellung ist bei weitem nicht vollständig, gibt
aber die wichtigsten Routinen wieder.
15-26
DAS SCHLÜSSELWORT
e n u m
16-1
In einem solchen Programm würde eine Anweisung wie
if (Wetterlage == bewoelkt)
unmittelbar interpretierbar sein, nicht dagegen die Angabe:
if (Wetterlage = = 3).
Beispiel 1
enuii färbe (kreuz,pik,herz,Karo); /• neuer Datentyp färbe •/
enun wert (s1 eben,acht,neun,zehn,as,bauer,öaase,koentg); /• neuer Datentyp wert •/
aln ()
(
enu« färbe kart.farbe; /• variable kart.farbe ist vom Typ färbe ♦/
enum wert karCwert; /• Variable kart.wert ist vom Typ wert •/
for (kart_farbe=kreuz ; kart_farbe<=karo ; ♦♦kart_farbe) (
printf("\n\n”);
ausg.färbe(kart.farbe);
for ~(kart_wert = sleben ; kart.wert<=koen1g ; ♦♦kart.wert) <
printf("X4s", ");
ausg wert(kart.wert);
if (kart.wert «« zehn)
printf(*\n">;
ausg.farbe(karte)
enurn färbe karte;
<
switch (karte) (
case kreuz:
printf("Kreuz \n");
break;
case herz:
printf("Herz \n");
break;
case pik:
prmtf("Pik \n");
break;
case karo:
printf("Karo \n*);
break;
>
ausg.wert(karte)
enum wert karte;
(
switch (karte) (
case sieben:
printf("Sieben *);
break;
case acht:
printf("Acht •);
break;
case neun:
printf("Neun ");
break;
case zehn:
printfCZehn ");
break;
case as:
printfCAs *);
break;
16-4
case bauer:
printf("Bauer );
break;
case dame:
printf("Dame
break;
case koemg:
printf("Koenig
break;
>
)
Bildschirmausgabe dieses Programms:
Kreuz
Sieben As Acht Bauer Neun Dame Zehn Koenig
Pik Sieben As Acht Bauer Neun Dame Zehn Koenig
Herz Sieben As Acht Bauer Neun Dame Zehn Koenig
Karo Sieben As Acht Bauer Neun Dame Zehn Koenig
maln ()
<
enu» tag (montag,dlens tag = 17,mlttwoch.donnerstag,frei tag,samstag,sonntag} ;
enum tag 1;
for (ismontag ; iosonntag ;
printf rXd\n",i);
}
Dieses Programm würde folgende Zahlenreihe am Bildschirm ausgeben:
16-5
0
1
2
3
4
5
16
17
18
19
20
21
22
Das Schlüsselwort enum ist inzwischen in neueren C-Versionen imple-
mentiert.
16-6
17-1
Einführung
Hinweis zu diesem Kapitel:
Die weltweite Standardisierung der C-Sprache wurde vom "American National
Standards Institute" begonnen und ist unter der Bezeichnung "ANSI C" bekannt.
Zusammen mit der Sprache wurde auch eine äußerst umfangreiche "C-Biblio-
thek" standardisiert.
Das vorliegende Kapitel beschreibt die wesentlichen Neuheiten in ANSI C,
jedoch nicht die ANSI-C-Standardbibliothek und die Praxis der Programmierung
in ANSI C unter Einsatz dieser Bibliothek.
Weiterführende Literatur zu ANSI C:
Alle neuen Aspekte der Sprache ANSI C sowie die gesamte ANSI-C-Standard-
bibliothek werden ausführlich und anhand von Anwendungen in dem kommen-
den Buch des Autor Helmut Herold dargestellt und für professionelle C-Program-
mierer kommentiert:
Helmut Herold
ANSI C
tewi-Verlag, ISBN 3-89362-040-0
(erscheint IV/1989)
17-3
ANSI C 17
Das ANSI*)-Komitee X3J11 begann im Juni 1983 mit dem Unterfangen, die
Sprache C zu standardisieren. Mit der Verabschiedung eines endgültigen C-
Standards ist bald zu rechnen. Bisher galt die 1. Ausgabe des Buches "The C
Programming Language" von Kernighan und Ritchie (Prentice-Hall, 1978) als
die Bibel für alle C-Fragen. Diese "Bibel” ließ jedoch einige Fragen offen. So
wurde bereits in den frühen 80er Jahren die Notwendigkeit für einen wirklichen
C-Standard erkannt.
Es sollten nun Standard-Vorgaben für alle möglichen C-Aspekte geschaffen
werden. Bei dieser Untersuchung haben sich 3 unterschiedliche Schwerpunkte
herausgebildet, für die es galt, eine Standardisierung zu finden:
- Sprache
- Präprozessor
- Bibliothek
Mit der Einführung von ANSI C können nun portable C-Programme geschrieben
werden; d. h. solange sich ein Programm an die ANSI-Vorgaben hält, sollte es
ohne Schwierigkeit auf allen ANSI C-Compilern fehlerfrei übersetzbar und dann
auch ablauffähig**) sein.
Das ANSI-Komitee hat sich allerdings nicht nur um die "Portabilität von C-Pro-
grammen" gekümmert, sondern hatte auch den Mut, einige Neuheiten in C
einfließen zu lassen, wobei wohl die "Funktions-Prototypen” die wichtigste Neu-
heit sind. Diese Neuheit wurde der von Bjarne Stroustrup neuentwickelten Spra-
che C++ abgeschaut und wird viele Argumente der C-Gegnerschaft wie z. B.
”C macht keine Typüberprüfung wie PASCAL oder ADA beim Funktionsauf-
ruf" entkräften. Mit dieser Neuheit erhofft man sich allerdings auch ein Umdenken
und etwas disziplinierteren Programmierstil in den "C-Hacker-Kreisen”.
Sein oberstes Ziel hat das X3J11-Komitee folgendermaßen beschrieben: "Erar-
beiten einer klaren, widerspruchsfreien und unzweideutigen Definition der
Sprache C”
HINWEISE: *) ANSI (American National Standards Institute) ist eine amerikanische Organisa-
tion, welche ein Mitglied der International Standards Organisation (ISO) ist.
1985 entschied das Komitee X3J11, daß nur ein C-Standard geschaffen werden
soll, welcher von beiden Organisationen ANSI und ISO verabschiedet wird.
**) setzt weitgehende Maschinenunabhängigkeit voraus
17-5
Neben diesem Oberziel gibt es noch grundlegende Richtlinien, welchen das
ANSI-C-Komitee folgte:
Den "Geist von C” erhalten
Viele alte C-Hasen sind der Meinung, daß C überhaupt keinen neuen Anstrich
braucht, da bereits das bisherige Alt-C äußerst erfolgreich eingesetzt wird und
von den C-Programmierern wegen seiner Einfachheit und Mächtigkeit, die seine
Sprachkonstrukte bieten, geschätzt wird.
Mit "Geist von C" sind typische Charakteristika gemeint, welche die Sprache C
prägen, wie z. B.:
- effizienter Zugriff auf die darunterliegende Maschine
- kleiner Sprachumfang von C (nur sehr wenige Schlüsselwörter)
- Für die Ein- und Ausgabe ist nicht die Sprache, sondern sind Bibliotheks-
routinen zuständig, z. B. ist printf kein C-Schlüsselwort, sondern eine
solche Bibliotheksroutine.
Bereits laufende Programme sollen weiterhin ablauffähig bleiben
Benutzerprogramme, welche nach den Vorgaben aus K&R*) geschrieben sind,
sollten auch weiterhin kompilierbar und ausführbar sein, soweit sie nicht
"Schmiercode" (wie z. B. Zeigerwerte in int-Variablen abspeichern) beinhalten
C soll weiterhin maschinennahe Sprache bleiben
Effizienter Code ist ein Markenzeichen der C-Sprache. Ebenso wurde C in den
letzten Jahren immer mehr in typischen "Assembler-Einsatzgebieten" (wie z. B.:
Gerätetreiber) eingesetzt. Diese C-Benutzergemeinde möchte auch weiterhin
ihr liebgewonnenes C verwenden.
Portabilität
Unter Portabilität eines C-Programms versteht man ein leichtes Übertragen eines
C-Programms von C-Compiler A auf Maschine X zum C-Compiler B auf Maschine
Y, ohne daß dies Änderungen (oder zumindest nur geringfügige) an diesem C-
Programm erfordert**). Das Schaffen eines C-Standards fördert natürlich dieses
Ziel. Viele Grauzonen, welche nicht genau von K&R beschrieben waren und von
vielen Compilern unterschiedlich gehandhabt wurden, sollten aufgehoben sein.
Trotzdem können gewisse Grauzonen nicht ganz vermieden werden. ANSI C
spricht dann bei solchen Konstrukten von implementations-definiertem, unspezi-
fiziertem oder Undefiniertem Verhalten***).
HINWEISE: *) K & Rwird auf den nächsten Seiten immer verwendet, wenn auf den bisherigen
C-Standard "The C Programming Language" von Kernighan und Ritchie
(Prentice Hall, 1978) hingewiesen werden soll.
**) wie z. B. ein Programm, welches mit Turbo C auf einem PC mit Betriebssystem
MSDOS entwickelt wurde: Das entsprechende C-Quellprogramm sollte ohne
größere Schwierigkeiten auf einem C-Compiler unter dem Betnebssystem
UNIX auf einer VAX-Maschine übersetzbar und dann auch ablauffähig sein.
***) siehe Abschnitt Begriffsklärung
17-6
Bisherige C-Schwächen beseitigen
Eine große C-Schwäche beispielsweise war es, daß der Typ und die Anzahl der
aktuellen Parameter*) bei einem Funktionsaufruf nicht überprüft wurden, was
z. B. in anderen Sprachen wie PASCAL oder ADA selbstverständlich ist. Solche
Schwächen sollten in ANSI C nicht mehr vorkommen.
Internationalisierung von C
C ist zwar amerikanischen Ursprungs, aber für eine Sprache von Welt gehört
es sich nun auch, Gepflogenheiten aus anderen Kultur- und Sprachkreisen zuzu-
lassen.
17.1 ANSI-C-SCHNELLKURSUS
Dieser Absatz soll einen "schnellen Blick in die ANSI-C-Küche" erlauben und
sein Ziel ist die Beantwortung der Frage, die sich jeder C-Programmierer stellt:
Was sind die wichtigsten Neuheiten/Unterschiede beim Vergleich
ANSI C <-> Alt-C ?
Zur Beantwortung dieser Frage wurde dieses Kapitel in verschiedene Unter-
absätze geteilt.
17.1.1 Allgemeines
Begriffsklärung
Es sollen hier die wichtigsten Begriffe geklärt werden, welche im ANSI-C-Papier
erwähnt sind:
Implementation
Ein bestimmtes Software-Paket, welches C-Programme übersetzt (kompiliert)
und für ein bestimmtes Betriebssystem ablauffähig macht.
Beispiele für Implementationen wären:
- Turbo C für MSDOS
- Microsoft C für MSDOS
- C-Compiler für UNIX
HINWEIS: *) ANSI C spricht nicht mehr von aktuellen Parametern, sondern von Argumenten
17-7
Objekt
Ein Speicherbereich, der Daten auf nehmen kann. Außer für Bitfelder sind Objekte
aus einer zusammenhängenden*) Folge von einem oder mehreren Bytes**)
zusammengesetzt. Ein Beispiel für ein Objekt wäre eine float-Variable.
Argument
steht für die altbekannten Begriffe "aktuelles Argument" oder "aktueller Parame-
ter". In ANSI C werden also Parameter, die beim Aufruf einer Funktion oder eines
Makros angegeben werden. Argumente genannt.
Parameter
steht für die altbekannten Begriffe "formales Argument" oder "formaler Parame-
ter". ANSI C spricht also beim Funktionsaufruf von Argumenten und bei Funk-
tions-Deklarationen oder -Definitionen von Parametern
Unspezifiziertes Verhalten
Verhalten einer korrekten C-Konstruktion, für welche ANSI C keine Vorschriften
macht.
Beispiel:
Reihenfolge, in welcher Funktionsargumente ausgewertet werden; wenn beispielsweise eine
Funktion zwei int-Parameter besitzt, dann ist für das folgende Programmstück
a = 100;
funktionfa* = 2, a+ = 500);
nicht festgelegt, ob funktion mit (200,700) oder (1200,600) aufgerufen wird.
Undefiniertes Verhalten
Verhalten beim Auftreten einer fehlerhaften oder Nicht-ANSI-C-konformen
Sprachkonstruktion, oder beim Zugriff auf fehlerhafte Daten oder nicht explizit
mit Werten belegten Objekten, für welches ANSI C keine Vorschriften macht.
Wenn Undefiniertes Verhalten vorliegt, so ist ein C-Compiler nicht verpflichtet,
es zu erkennen und zu melden***)
Beispiele:
- Eine arithmetische Operation, welche zu einer Division durch 0 führt.
- Der Betrag eines Wertes wird während einer Berechnung größer als der maximale
Betrag, den der dafür vorgesehene Speicherbereich aufnehmen kann (engl.: Overflow
s dt.: Überlauf)
HINWEISE: *) Die Betonung liegt hier auf zusammenhängend; somit kann ein Objekt wie
ein Vektor von char-Elementen betrachtet werden, was zur Folge hat, daß
seine Größe mit dem sizeof-Operator bestimmt werden kann.
**) Für ein Byte schreibt ANSI C vor. daß er mindestens 8 Bit "breit" ist und daß
der Datentyp char (vorzeichenbehaftet oder nicht) genau ein Byte belegt.
***) wäre aber nett, wenn er es trotzdem (wenn möglich) tun würde
17-8
Implementations-definiertes Verhalten
Verhalten einer korrekten C-Konstruktion, die von der Auslegung durch die ent-
sprechende C-Realisierung (Compiler) abhängt. Der ANSI-C-Standard schreibt
für jedes implementations-definierte Verhalten vor, daß es in der begleitenden
Compiler-Beschreibung dokumentiert sein muß.
Beispiel:
Bewirkt Bit-Schiebeoperation » bei negativen int-Werten
- linkes Nachziehen von Nullen (logical shift)
- linkes Nachziehen von Einsen (arithmetic shift)?
Lokal-spezifisches Verhalten
Verhalten, welches von lokalen Eigenheiten (wie Nationalität, Kultur oder Spra-
che) abhängig ist; dieses ist ebenfalls zu dokumentieren.
Beispiel:
Das Verhalten der Bibliotheksroutine isupper*). wenn diese auf Umlaute wie ä oder Ü ange-
wendet wird.
Trigraphs
Andere Länder, andere Zeichen: So ist z. B. den Franzosen das ö aus der deut-
schen Sprache unbekannt. C wurde in den USA entwickelt und setzt den amerika-
nischen Zeichensatz voraus. ANSI C nun möchte sich gerne eine "Weltsprache”
nennen. Damit alle Nicht-Amerikaner ebenso die Möglichkeit haben, den von C
vorgegebenen Grund-Zeichensatz darstellen zu können, wurden die Trigraphs
eingeführt:
??= #
?? (
?? /
??)
99 >
[
\
]
??!
??>
99 _
Trigraphs sind 3-Zeichen-Sequenzen, welche mit ?? beginnen; Trigraphs wer-
den vom Compiler durch das entsprechende "repräsentierte Zeichen" ersetzt.
Es ist anzumerken, daß Trigraphs sogar innerhalb von Zeichenketten (Strings)
durch ihr "repräsentiertes Zeichen" ersetzt werden.
HINWEIS: *) eigentlich nur für das anglo amerikanische Alphabet definiert
17-9
Beispiele:
printf(”Was Ist 3 * 4 ???/n”);
printf(”3 * 4 = ??=12, oder nicht ???”);
würde als
printff’Was ist 3 * 4 ?”);
printf(”3 ‘4 = 112, oder nicht ???”);
interpretiert.
Namen, die mit___anfangen
sind für den Gebrauch in Bibliotheken reserviert und sollten nicht vom Benutzer
verwendet werden*)
Minimal garantierte Größe für die unterschiedlichen Typen
char >= 8 Bits
short >=16 Bits
int >= short
long >= 32 Bits
Vielbyte-Zeichen
Manche Sprachen benötigen mehr als 1 Byte, um ein Zeichen zu speichern.
Solche Vielbyte-Zeichen sind in ANSI C erlaubt. Es wurde sogar ein eigener
Datentyp wchar____t eingeführt, um solch ein Vielbyte-Zeichen aufzunehmen
Erweiterung der nichtdruckbaren Zeichen
ANSI C hat die Menge der "Fluchtsymbor-Sequenzen (Folge von Zeichen, wel-
che mit Backslash (Gegenschrägstrich) starten) erweitert. Diese Fluchtsymbol-
Sequenzen erlauben es, nichtdruckbare Zeichen (wie z. B. den Piepston \a) in
Zeichenketten unterzubringen.
Die vollständige Tabelle dieser ANSI-C-Fluchtsymbole ist:
HINWEIS: *) Eigentlich legt ANSI C diese Restriktion nur für globale Namen fest: für andere
vom Benutzer gewählte Namen gilt nur die Einschränkung, daß sie nicht mit _
oder________________G (G steht für Großbuchstabe) beginnen sollten.
17-10
\a alert: akustisches oder visuelles Aufmerksamkeits- signal (meist die Klingel), aktive Position*) wird in diesem Fall nicht verändert, (neu in ANSI C)
\b backspace: Zurücksetz-Zeichen versetzt die aktive Position auf die vorherige Position in der entsprechenden Zeile; wenn sich die aktive Position bereits am Zeilenanfang befand, dann liegt "unspezifiziertes Verhalten" vor.
\f form feed: Seitenvorschub versetzt die aktive Position auf den Anfang der näch- sten Seite.
\n new line: Neue Zeile versetzt die aktive Position auf den Anfang der näch- sten Zeile.
\r carriage return: Wagenrücklauf
versetzt die aktive Position auf den Anfang der momentanen Zeile.
\t horizontal tab: Horizontales Tabulatorzeichen versetzt die aktive Position zur nächsten horizontalen Tabulatorposition in der momentanen Zeile; falls sich die aktive Position bereits an der letzten horizontalen Tabulatorposition oder dahinter befindet, dann liegt "unspezifiziertes Verhalten" vor.
\v vertical tab: Vertikales Tabulatorzeichen versetzt die aktive Position zur nächsten vertikalen Tabulatorposition; falls sich die aktive Position bereits an der letzten vertikalen Tabulatorposition oder dahinter befindet, dann liegt "unspezifiziertes Verhal- ten” vor. (neu in ANSI C)
17.1.2 Der Präprozessor
Während K&R die Funktionsweise des Präprozessors am umgenauesten vom
ganzen C-Sprachumfang beschrieben haben, hat das ANSI-C-Komitee umso
mehr Aufwand betrieben, um die Rolle des Präprozessors genau festzulegen.
HINWEIS: *) Die aktive Position ist die Stelle auf einem Aufzeichnungsgerät (z. B. Cursor auf
dem Bildschirm), wo die nächste Ausgabe eines Zeichens erfolgen würde.
17-11
Der Präprozessor verarbeitet den Quelltext einer Programmdatei, wobei alle
Präprozessor-Kommandos (Präprozessor-Direktiven) mit dem Zeichen # be-
ginnen*).
Üblicherweise ruft der Compiler automatisch den Präprozessor auf, bevor er mit
der Übersetzung beginnt.
ANSI C schreibt vor, daß der Präprozessor wie ein eigener Schritt vor dem
eigentlichen Compilerlauf zu verstehen ist**)
Der Präprozessor bietet nun die folgenden Dienstleistungen an:
- «define (Ersetzen von Zeichenketten, Funktions-Makros, ...)
- «include (Einkopieren ganzer Dateien)
- Bedingte Kompilierung
- Restliche Präprozessor-Direktiven
- Von ANSI C vordefinierte Makros
«define
Textersatz- und Funktion-Makros (Alt-C)
Meist wird «define verwendet, um die Lesbarkeit eines Programms zu erhöhen:
«define MEHRWERT—STEUER 0.14 /«Textersatz-Makro*/
«define MAXIMUM(a,b) (a) > (b) ? (a) : (b) /«Funktion-Makro*/
Anweisungen wie
end—betrag = betrag + betrag * MEHRWERT—STEUER;
max = MAXIMUM(zahl1,zahl2);
werden dann vom Präprozessor durch
end__betrag = betrag + betrag * 0.14;
max » (zahll) > (zah!2) ? (zahll): (zahl2);
ersetzt.
HINWEISE: *) Zwischenraum-Zeichen (engl. whitespace: Leerzeichen, \f, \n, \r, \t oder
\v) sind vor # zugelassen; zwischen « und Anfang der restlichen Präprozessor-
Direktive sind nur Leerzeichen oder \t zugelassen.
**) Das heißt nicht, daß der Präprozessor-Lauf als eigener Durchgang (wie es in
heutigen Compilern oft der Fall ist) realisiert sein muß. sondern sich nur so
verhalten muß.
17-12
Konkatenation von hintereinander
angegebenen Zeichenketten
ANSI C legt fest, daß hintereinander angegebene Zeichenketten (Leer- und
Tabulatorzeichen dazwischen zählen nicht) zu einer Zeichenkette zusammen-
gefaßt werden:
char adresse[100]=”Sascha ” "Kimmel, ” "Lohestr. 10, ”
”8722 Gressthal”;
wird umgewandelt nach
char adresse[100]=”Sascha Kimmel, Lohestr. 10,
8722 Gressthal”;
ttdefine geschichte(jahr,ereignis)
printff’lm Jahre ” jahr ” war ** ereignis”\n”);
Ein Aufruf
geschichte(”1492”, ”die Entdeckung Amerikas durch
Kolumbus”);
wird vom Präprozessor zunächst in
printff’lm Jahre ” ”1492” ” war ” "die Entdeckung Amerikas
durch Kolumbus””\n");
umgewandelt und dann wird die Zeichenketten-Konkatenation angewandt, was
schließlich zu folgender Darstellung führt:
printf(”lm Jahre 1492 war die Entdeckung Amerikas durch
Kolumbus \n”);
Ersetzung von Makroparametern durch
Zeichenketten-Konstanten (Operator #)
Oft wäre es günstig, wenn man den Wert von Variablen zu Testzwecken in
bestimmten Programmphasen ausgeben lassen könnte. Für einen solchen
Anwendungsfall eignet sich das folgende Makro:
ttdefine wertvon(variable) printf(”variable=%d\n”, variable)
17-13
Ein späterer Aufruf wertvon(steuer); könnte nun vom Präprozessor durch
(a) printf(”variable=%d\n”,steuer); oder
(b) printf(”steuer=%d \n”,steuer);
ersetzt werden.
Wahrscheinlich ist (b) in 90 % der Fälle erwünscht, aber darauf konnte man sich
in der Vergangenheit nicht verlassen. ANSI C brachte nun Licht in diese etwas
nebulöse Situation:
Wenn bei einer Makrodefinition ein formaler Parameter im Ersetzungstext mit
vorangestelltem # angegeben wird, dann wird beim nachfolgenden Aufruf dieses
Makros das entsprechende aktuelle Argument als Zeichenketten-Konstante dar-
gestellt.
Nach der Präprozessor-Anweisung
#define wertvon(variable) printff#variable” « %d\n”, variable)
würde ein Aufruf von wertvon(steuer); zunächst in
printff’steuer”” = %d \n", steuer);
und dann in
printff’steuer = %d\n”, steuer);
umgewandelt.*)
Zusammensetzen neuer Namen
mit Operator ##
Der Operator ## ermöglicht es, neue Namen aus anderen Namen "zusammen-
zukleben”:
HINWEIS: *) Noch allgemeingültiger wäre #define wertvon(var,format) printf(#var" = for-
mat"\n",var). Dann könnte man sich sogar Werte von Variablen mit unterschied-
lichen Datentypen ausgeben lassen, z. B. wertvon(ganz, "%d"); oder wertvon
(name, "%s");.
17-14
# define y(a,b) x ## a ## b
int x12;
printf (”%dln”,y (1,2));
würde vom Präprozessor in
printf (”%dln”,x12);
umgewandelt.
#define x___var__test(zahl) printf(”x”#zahl” = %d”,x##zahl)
Ein späterer Aufruf
x___var_test(7);
wurde vom Präprozessor zunächst in
prlntf(”x””7”” = %d”, x7);
umgewandelt, und nach Konkatenation der Zeichenketten ergäbe sich
printf(”x7 = %d”, x7);
#define a(n) nummer##n
#define x 3
Ein Aufruf a(x) wird dann durch nummerx und nicht durch nummert oder nummern ersetzt.
Rekursive Makrodefinitionen
Definitionen wie
#define char unsigned char
bringen ANSI-C-Compiler nicht mehr in Verlegenheit. Manche frühere C-Compi-
ler (besser: C Präprozessoren) haben sich dann bei Angaben wie
17-15
char zeich;
/ \
unsigned char
/ \
unsigned char
unsigned char
/ \
... "tot geschachtelt".
Um solche Schachtel-Kaskaden zu vermeiden, stellte ANSI C folgende Regel auf:
Ein Makroname, der selbst wieder in seiner eigenen Definition angege-
ben wird, wird nicht wieder ersetzt, sondern unverändert übernom-
men.
Somit wären Makroangaben wie
#define sqrt(x) printff’Die Wurzel von %lf ist
%lf \n”,x,sqrt(x))
möglich, da ein späterer Aufruf
sqrt(7.5);
vom Präprozessor durch
printff’Die Wurzel von %lf ist %lf \n”, 7.5, sqrt(7.5));
ersetzt würde.
#include - Einkopieren ganzer Dateien
Üblicherweise haben die bei #include angegebenen Dateien die Endung h
und werden Header-Dateien genannt. Man unterscheidet zwei Arten von Header-
Dateien:
C-Standard-Header-Dateien
ANSI C legt genau fest, welche Header-Dateien existieren müssen:
assert.h
ctype.h
errno.h
float.h
limits.h
17-16
locale.h
math.h
setjmp.h
signal.h
stdarg.h
stddef.h
stdio.h
stdlib.h
string.h
time.h
ANSI C legt darüber hinaus noch weitgehend den Inhalt dieser Standard-Header-
Dateien fest, indem es angibt, welche Datentypen, Konstanten, Makros und
Funktionen in den einzelnen Dateien zu deklarieren oder zu definieren sind.
Die Deklarationen geben ein genaues Bild, welche Rückgabe-Datentypen von
den einzelnen Bibliotheksfunktionen bereitgestellt werden; zudem geben sie
Anzahl und Typ der geforderten Funktionsargumente (siehe Prototypen) an.
Wenn nun eine Bibliotheksfunktion wie z. B. sqrt in einem Programm verwendet
wird, dann sollte die entspr. Standard-Header-Datei (hier math.h) mit ffinclude
zum Bestandteil des Programms gemacht werden: Dies ist zwar nicht notwendig,
aber der sichere Weg, da die Header-Datei festlegt, wie die Argumente beim
Aufruf auszusehen haben und eine falsche Verwendung (wie z. B. sqrt(a,b))
würde bereits vom Compiler gemeldet.
Standard-Header-Dateien werden üblicherweise in spitzen Klammern*) beim
#include angegeben, z. B :
#include <math.h>
Benutzereigene Header-Dateien
Solche Header-Dateien beinhalten üblicherweise nützliche Konstanten- und
Makrodefinitionen, aber auch eigene Datentypfestlegungen, z. B. könnte eine
Konstruktion wie
typedef struct |
float real____teil;
float imag_teil;
| complex;
in einer Header-Datei complex.h stehen. Jeder Programmteil, welcher diese
Datei einkopiert, kann dann von diesem Datentyp Gebrauch machen.
HINWEISE: *) Spitze Klammern veranlassen den Präprozessor, in fest vorgegebenen Pfaden
nach der entsprechenden Header-Datei zu suchen (in UNIX z. B. in dem Stan-
dard-Directory für Header-Dateien /usr/include).
17-17
Neben ihrer Funktion als Sammelplatz für nützliche Konstanten-, Makro- und
Datentyp-Definitionen werden die Header-Dateien in der Praxis auch für die
Schnittsteilen-Vereinbarungen zwischen mehreren Programmteilen (Module)
verwendet (siehe Prototypbeschreibung).
Benutzereigene Header-Dateien werden üblicherweise in Anführungszeichen*)
beim #include angegeben, z. B.:
tfinclude "complex.h”
Neben der Angabe von Header-Dateien in < > und ” " können diese auch in
Form von Makronamen angegeben werden:
«ifdef UNIX
«define INC_DATEI "unix kdo.h”
«eise
#define INC_DATEI ”dos_kdo.h”
«endif
«include INC_DATEI
In allen Fällen ersetzt der Präprozessor die entsprechende #include-Zeile
durch den vollständigen Inhalt der entsprechenden Header-Datei.
Bedingte Kompilierung
Mit den Präprozessor-Konstrukten dieser Klasse kann man die Übersetzung
einzelner Programmteile von zur Präpozessor-Zeit auswertbaren Bedingungen
abhängig machen.
Die bedingte Kompilierung macht es somit möglich, nur eine Quelldatei zu unter-
halten, welche dann von unterschiedlichen Compilern und sogar auf unterschied-
lichen Maschinen übersetzt werden kann.
HINWEIS: *) Anführungszeichen veranlassen den Präprozessor, in dem momentanen Directory
nach der entsprechenden Header-Datei zu suchen; wird diese dort nicht gefunden,
so wird in denselben Pfaden gesucht, als wenn hier spitze Klammern < .. >
angegeben wären.
17-18
«if defined BIT32
«define ANZAHL 32
«elif defined BITI6
«define ANZAHL 16
«eise
«define ANZAHL 8
«endif
Auch kann man die bedingte Kompilierung dazu verwenden, um aus einer Quell-
datei zu unterschiedlichen Zeitpunkten unterschiedliche ablauffähige Pro-
gramme zu erzeugen.
«define wertvon(var) printf(«var” = %s\n”, var)
«ifdef TEST
wertvon(zeich_kette);
«endif
Während der Testphase wird TEST definiert: entweder mit «define TEST oder
beim Aufruf des Compilers in der Kommandozeile (z B. cc-DTEST... bei UNIX-
C-Compilern oder tcc-DTEST... bei TurboC-Direktaufruf)
Folgende Schlüsselwörter stehen für die bedingte Kompilierung zur Verfügung:
#if ausdruck Abhängig davon, ob ausdruck erfüllt ist (Auswertung ergibt einen von 0 verschiedenen Wert), wird der dar- auffolgende Programmteil ausgeführt.
# ifdef name Wenn name definiert ist, dann wird der darauffol- gende Programmteil ausgeführt; entspricht #if defi- ned name oder #if defined(name)
«ifndef name Wenn name nicht definiert ist, dann wird der darauf- folgende Programmteil ausgeführt; entspricht #if !de- fined name oder #if !defined(name)
#elif ausdruck Abhängig davon, ob ausdruck erfüllt ist (Auswertung ergibt einen von 0 verschiedenen Wert), wird der dar- auffolgende Programmteil ausgeführt.
Helse leitet else-Programmteil zu den 4 vorherigen Kon- struktionen (#if, «ifdef, «ifndef, #elif) ein.
17-19
#endif
zeigt das Ende einer bedingten Kompilierungs-Kon-
struktion an.
Zu #if, #elif und #else
Der Programmteil
#if bed1
progteiH
#elif bed2
progteil2
#elif bed3
progteil3
#else
progteiH
#endif
kann durch den folgenden Programmablauf plan beschrieben werden:
17-20
Restliche Präprozessor-Direktiven
#line zahl
Die hierbei als zahl angegebene Zeilennummer wird als neue Zeilennummer für
die Quelldatei angenommen; solche Anweisungen sind z. B. wichtig, nachdem
Header-Dateien durch den Präprozessor Bestandteil der Quelldatei wurden,
welche dem Compiler vorgelegt wird; die Hauptverwendung für diese Direktive
liegt im Bereich des Compilerbaus.
Es ist hier auch die Angabe
#line zahl dateiname
möglich, was bewirkt, daß als neue Zeilennummer zahl und als neuer Dateiname
dateiname genommen wird.
# pragma spezielle-compiler-anweisung
Pragmas sind compilerspezifisch und von Compiler zu Compiler verschieden.
Beispiel:
Der Intel-C-Compiler 4.0 hat das Pragma
#pragma large
um das LARGE-Modell aut den Intel-Prozessoren 8086, 8088,... auszuwählen.
Kommt in einem Programm eine Wpragma-Direktive vor, welche der Compiler
nicht kennt, so wird diese einfach ignoriert.
#undef name
erlaubt die "Rücknahme" eines zuvor definierten Symbols (Umkehrung zu
ftdefine)
fferror zeichenkette
Es wird die angegebene zeichenkette am Bildschirm ausgegeben.
Beispiel:
#error "Sie haben sowohl die Praeprozessor-Konstanten TEST wie FREIGABE definiert (Wider-
spruch III)"
17-21
Von ANSI C vordefinierte Makros
Die folgenden Makros muß jeder ANSI-C-Compiler (Präprozessor) verstehen und
auflösen können:
__LINE___ Zeilennummer in der momentanen Quelldatei (ganzzah-
lige Konstante)
__FILE___ Name der momentanen Quelldatei (Zeichenketten-Kon-
stante)
__DATE____ Übersetzungs-Datum der momentanen Quelldatei (Zei-
chenketten-Konstante der Form "mmm tt jjjj"; z. B. "Jun
14 1989" oder "Jun 4 1989")
__TIME___ Übersetzungs-Zeit der momentanen Quelldatei (Zeichen-
ketten-Konstante der Form "hh:mm:ss"; z. B.: "14:32:53")
_STDC_ Erkennungsmerkmal für einen ANSI-C-Compiler: Ist diese
ganzzahlige Konstante mit Wert 1 gesetzt, so handelt es
sich um einen ANSI-C-Compiler.
linclude <stdio.h>
main()
<
printf("Zeile %d in Datei %s (um %s Uhr am %s)\n",
___________LINE , FILE , TIME , DATE );
# line 100 "test.c"
printf("Zeile %d in Datei %s\n", __LINE___, __FILE__);
/* wuerde z.B. folgende Ausgabe ergeben:
Zeile 7 in Datei pp_line.c (um 11:05:00 Uhr am Apr 1 1989)
Zeile 100 in Datei test.c
*/
17.1.3 Die neue Sprache ANSI C
Datentypen
Grunddatentypen
Hier wurde ein neues Schlüsselwort signed (Gegenstück zu unsigned) einge-
führt, um explizit festlegen zu können, daß ein Wert mit Vorzeichen dargestellt
werden soll.
17-22
char-Typ*)
Objekte von diesem Datentyp können genau 1 Zeichen aufnehmen.
vorzeichenbehaftete Ganzzahltypen
(a) signed char
(b) short, signed short, short int, signed short int
(c) int, signed, signed int, (keine Typ-Angabe)
(d) long, signed long, long int, signed long int
Bezüglich der Wertebereiche muß folgende Forderung erfüllt sein:
(a) < (b) < (c) S (d)
nicht vorzeichenbehaftete Ganzzahltypen
unsigned char
unsigned short, unsigned short int
unsigned, unsigned int
unsigned long, unsigned long int
3 Gleitpunkttypen
(a) float
(b) double
(c) long double
Bezüglich der Wertebereiche muß folgende Forderung erfüllt sein:
(a) < (b) < (c)
long float ist in ANSI C nicht mehr erlaubt
Der genaue Wertebereich, welcher von den einzelnen Datentypen abgedeckt
wird, ist von Compiler und Maschine abhängig.
ANSI C legt lediglich fest, daß diese Grenzen in den zwei Header-Dateien
limits.h und float.h definiert sein müssen.
enum-Angabe
Der Aufzählungs-Datentyp enum.ist zwar keine Neuerfindung vom ANSI-
Komitee, aber doch brachte ANSI C es mit sich, daß enum nun ein fester
Bestandteil der Sprache C ist, was in der Vor-ANSI-Zeit nicht immer der Fall war.
HINWEIS: *) Es ist der jeweiligen Implementation überlassen, ob char vorzeichenbehaftet ist
oder nicht.
17-23
ANSI C gibt zudem eine umfassende Beschreibung des Aufzählungs-Schlüssei-
worts enum wieder, welches vor allem dazu eingesetzt werden kann, um C-
Programme lesbarer zu machen:
enum erlaubt es, Werten Namen zu geben, und woran erinnert das?
An #define, oder nicht?
enum hunde_____art (schaeferhund, dackel, pudel|;
Mit dieser Deklaration wird ein neuer Datentyp enum hunde_art festgelegt,
welcher genau 3 gültige Werte umfaßt: schaeferhund, dackel und pudel
Mit
enum hunde_____art ausgeh____hund;
würde dann eine Variable ausgeh_hund deklariert, welche genau diese 3 Werte
annehmen kann*).
Jedenfalls wurden die Werte 0,1 und 2 benannt, ebenso wie wenn
# define schaeferhund 0
# define dackel 1
# define pudel 2
angegeben worden wären.
enum-"Werte-Namen" dürfen nur einmal angegeben werden
Beispiel:
int variable;
enum hunde_art ( schaeferhund, dackel, pudel };
enum haustiere { kanarien.vogel, papagei, schaeferhund |;
wäre nicht erlaubt, denn eine spätere Zuweisung
variable = schaeferhund; /* ist erlaubt */
würde den Compiler in Schwierigkeit bringen.
HINWEIS: *) vielleicht auch ein paar mehr, da ANSI C vorgibt, daß enum-’ Werte-Namen” vom
Datentyp Int sind und somit die meisten Compiler für ausgeh_hund zwei oder
gar vier Bytes reservieren werden.
17-24
enum-'Werte-Namen” dürfen nicht als Variabiennamen verwendet werden
Beispiel:
enum hunde____art {schaeferhund, dackel, pudel);
int dackel = 5;
wäre verboten, denn was wäre z. B. als Argument beim Funktionsaufruf hundesteuer(dackel):
zu übergeben: Der dackel-Wert 1 aus hunde_art oder der Wert der Variablen dackel (5).
enum-Variable oder enum-Werte können überall dort verwendet werden, wo
ganzzahlige Werte erlaubt sind:
Beispiel:
enum hunde_art (schaeferhund, dackel, pudel};
int durchschnitts.hoehe[3] = {80, 20, 20);
printf(”Ein Schaeferhund ist durchschnittl. %d cm hoch\n”,
durchschnitts__hoehefschaeferhund]);
enum- ’Werte-Namen” können auch Werte zugewiesen werden.
Beispiel:
enum stellen___wert { null = 1, eins = 2, zwei = 4, drei = 8,
vier =16, fuenf = 32, sechs = 64, sieben = 128,
byte__max = 128 |;
Aus diesem Beispiel ist zu ersehen, daß jeder enum-Konstante ein eigener Wert zugewiesen
werden kann, wobei unterschiedlichen Werte-Namen auch gleiche Werte zugewiesen werden dür-
fen.
17-25
Beispiel: Grunddatentypen von TurboC
mit Größe und Wertebereich
Datentyp-Bezeichnung Größe (Bits) Wertebereich
unsigned char 8 0.. 255
char signed char 8 -128 .. 127
unsigned short unsigned short int 16 0 .. 65535
short signed short short int signed short int 16 -32768 .. 32767
unsigned int unsigned 16 0. 65535
int signed signed int 16 -32768 .. 32767
unsigned long unsigned long int 32 0.. 4294967295
long signed long long int signed long int 32 -2147483648 .. 2147483647
float 32 3.4E-38 .. 3 4E+38
double 64 1.7E-308 .. 1.7E+308
long double enum 80 16 3.4E-4932 .. 1.1E+4932 -32768 . 32767
17-26
Datentyp void
ANSI C führt nun endgültig den Datentyp void (deutsch: Nichts, Wertlos) ein,
welcher sich auf 3 Gebieten nützlich verwenden läßt:
1) Rückgabedatentyp für Funktionen
Dieses neue Schlüsselwort erlaubt nun auch in C die Unterscheidung von
Prozeduren*) und Funktionen.
void exitfint nummer);
bedeutet, daß die Funktion exit keinen Wert zurückgibt. Im ursprünglichen C mußte eine solche
Prozedur” mit
int exit( nummer)
int nummer;
angegeben werden, woraus nicht klar erkennbar ist, ob diese Funktion nun einen int-Wert liefert
oder als Prozedur zu betrachten ist.
2) Zeiger auf void (Zeiger auf Nichts)
a) void *allg_zeiger;
Mit einer solchen Deklaration wird lediglich ein Zeiger festgelegt; es wird
noch nicht angegeben, auf welchen Datentyp dieser Zeiger einmal zeigen
wird.
Jedesmal, wenn die in einem void-Zeiger angegebene Adresse verwendet
wird, um auf den unter dieser Adresse liegenden Wert zuzugreifen, muß dann
casting verwendet werden:
Ein void-Zeiger ist eine Art "Joker-Zeiger", welcher im Programm verwendet
werden kann, um auf Daten unterschiedlichster Typen zu zeigen.
T) finclude <stdio.h>
main()
t
void *allgemein_zeiger;
int i, i_sunune^O,
ganz_zahl(] = ( 2,3,5,6,2,8,0 };
f1oat f, f_summe=0,
gebr_zahl(] • ( 2.3, 3.4, 5.6, 7.68, 23.1, 0.13, 0.0 };
HINWEIS: *) procedure in PASCAL und Funktionen ohne Rückgabewert in C
17-27
allgemein_zeiger = ganz_zahl;
while (*(Tnt *)allgemeinzeiger !- 0) (
i_summe +- *(int *)allgemein_zeiger;
++(int *)allgemein_zeiger;
)
printf(" Die Summe der ganzen Zahlen ist %d\n", i_sumne);
allgemein_zeiger = gebr_zahl;
while (»(float *)allgemeinzeiger !- 0) (
f__summe +« »(float *)allgemein_zeiger;
++(float *)allgemein_zeiger;
)
printf(" Die Summe der gebrochenen Zahlen ist %.3f\n", f summe)
)
/* wuerde folgendes ausgeben:
Die Summe der ganzen Zahlen ist 26
Die Summe der gebrochenen Zahlen ist 42.210
♦/
/* Hier wird eine Funktion binaer_nuster bereitgestellt, welche
» die Binaerdarstellung von beliebigen Daten (4 Bytes) ausgibt
*/
*include <stdio.h>
typedef struct {
unsigned b0:4;
unsigned bl:4;
unsigned b2:4;
unsigned b3:4;
unsigned b4:4;
unsigned b5:4;
unsigned b6:4;
unsigned b7:4;
) dual;
char *vier_stell[]-{ "0000",
"0100",
"1000",
"1100",
/♦ Anlegen der Bitfelder ist naschinenabh.: */
/* entweder von links nach rechts •/
/♦ oder von rechts nach links •/
/» Falls dieses Programm die Werte in der */
/♦ in der falschen Reihenfolge ausgibt, ♦/
muesste hier die Reihenfolge */
uns i gned b7:4; */
unsigned b6:4; */
/* dann
/*
/*
/*
/*
"0001",
"0101",
"1001",
"1101",
: : •/
unsigned b0:4; gewaehlt werden ♦/
"0010", "0011",
"0110", "0111",
"1010", "1011",
"1110", "1111"
void
binaernuster( void *v )
{
printf("%s%s %s%s %s%s %s%s\n", vier_stell[((dual *)v)->b7],
vier_ateil[((dual *)v)->b6),
vierstell[((dual *)v)->b5],
vier_stell[((dual *)v)->b4],
vier_stell[((dual *)v)->b3],
vier_stell[((dual *)v)->b2],
vier_stell[((dual *)v)->bl],
vier_stell[((dual *)v)->bOJ);
main() (
long int ganz;
float gebr;
printf("Gib ganze Zahl ein: ");
scanf("%ld", &ganz);
binaer_muster(fcganz); /* Argument ist: Adresse einer long int Variable »/
printf("Gib gebrochene Zahl ein: ");
scanf("%f", igebr);
binaer_muster(ftgebr); /* Argument ist: Adresse einer float Variable */
)
/♦ Moeglicher Programmablauf waere:
Gib ganze Zahl ein: 12
00000000 00000000 00000000 00001100
Gib gebrochene Zahl ein: 34.25
01000010 00001001 00000000 00000000
*/
17-28
b) void *funktions_name(..)
Mit dieser Deklaration wird festgelegt, daß die entsprechende Funktion einen
void-Zeiger zurückgibt. Ein typisches Beispiel hierfür ist
void *malloc(size_t laenge)
malloc stellt einen zusammenhängenden Speicherbereich von laenge
Bytes zur Verfügung. Wie dieser Speicherbereich dann zu nutzen ist*), ist
Sache des Aufrufers, welcher dann casting verwendet, um diesem struktur-
losen Speicherplatz seine Struktur zu geben. Aus der Sicht von malloc ist
nur die Anfangsadresse wichtig und die ist datentypfrei (void *).
Das nachfolgende Beispiel zeigt eine ”neue Methode”, um Vektoren mit
variabler Länge in C verwenden zu können.
Auf einer Datei sei eine Folge von Werten vorhanden, wobei der 1. Wert die Anzahl der restlichen
Werte angibt, z. B.:
5
10
20
30
40
50
Die Aufgabe des nachfolgenden Programms ist es nun, den Mittelwert aus dieser Zahlenfolge
zu bestimmen. Daneben soll noch die Streuung ermittelt werden, welche sich als
E (Abweichung jedes einzelnen Werts vom Mittelwert)2
Werteanzahl - 1
ergibt.
Wenn ein zeitaufwendiges zweites Lesen der Datei von Beginn an verhindert werden soll, so
müssen die gelesenen Werte in einem Vektor "aufgehoben' werden, um beim 2. Durchlauf die
Abweichung der einzelnen Werte vom zuvor berechneten Mittelwert zu ermitteln:
HINWEIS: *) mit int oder char-Werten oder vielleicht mit einer vom Benutzer vorgegebenen
Struktur?
17-29
finclude <stdio.h>
finclude <stdlib.h>
typedef struct ( /* eine Typdefinition */
int zahlen[10000];
} int_vektor;
main()
(
char dateiname[30];
FILE *dz?
int i,
anzahl;
float mittelwert - 0.0,
Streuung - 0.0,
abweichung;
int_vektor *vekt_zeiger; /* Zeiger auf den selbstdefinierten Typ */
printf("Gib Namen der Datei, aus deren Inhalt\n");
printf(" der Mittelwert und Streuung zu berechnen ist : ") ;
gets(dateiname);
/♦ Eroeffnen der Datei 'dateiname' zum Lesen ♦/
if ((dz=fopen(dateiname,"r")) =- NULL) (
printf("Datei Is kann nicht zum Lesen eroeffnet werden\n", dateiname);
exit(EXIT_FAILURE);
)
if (fscanf(dz, "Id", &anzahl) != 1) ( /* Lesen der Werteanzahl */
printf("Es konnte kein Wert von Datei Is gelesen werden\n",dateiname);
exit(EXIT_FAILURE);
/♦ Reservieren von Speichern mit malloc; der so reservierte Speicher-*/
/* bereich ist datentyplos (lediglich eine Folge von Bytes). Durch */
/* die casting-Operation (int_vektor *) wird diesem strukturlosen */
/♦ Speicherbereich eine Struktur 'int_vektor' "aufgepresst". */
if ((vekt_zeiger=(int_vektor *)malloc(anzahl*sizeof(int))) « NULL) {
printf("Mangel an Speicherplatz (Zuviele Werte: ld)\n", anzahl);
exit(EXIT_FAILURE);
/* Nun kann ueber die Zeiger-"Kuchenform" dieser ehemals Struktur- */
/♦ lose Speicherbereich wie ein int-Vektor behandelt werden */
for (i=0 ; i<anzahl ; i++) {
if (fscanf(dz, "Id", fc(vekt_zeiger->zahlen[i])) != 1) (
printf("Beim Lesen des Id. Werts (Datei Is) ist Fehler aufgetreten\n
i+1, dateiname);
exit(EXIT—FAILURE);
) /♦ Jeder gelesene Wert wird */
mittelwert vekt_zeiger->zahlen[i]; /* auf 'mittelwert’ aufaddiert */
}
fclose(dz); /* Endgueltiger Mittelwert ergibt sich aus der */
mittelwert /- anzahl; /* Division durch die Anzahl der Werte */
/* Nachdem der Mittelwert berechnet wurde, muss nochmals der ganze */
/* int-Vektor durchlaufen werden, um zu jedem einzelnen Wert die *,
/* Abweichung zu ermitteln, welche dann quadratiert auf 'Streuung' */
/* aufaddiert wird. */
if (anzahl ! 1) {
for (i»0 ; i<anzahl ; i++) (
abweichung = vekt_zeiger->zahlen[i] - mittelwert;
Streuung +- abweichung*abweichung;
} /* Endgueltige Streuung ergibt sich aus der */
Streuung /= anzahl-1; /* Division durch die werteanzahl-1 */
)
printf(*\nld Werte in Datei Is: Mittelwert—> If, Streuung—> lf\n",
anzahl, dateiname, mittelwert, Streuung);
17-30
3) Funktionen ohne Parameter
Wenn eine Funktion keine formalen Parameter besitzt, dann kann dies in
ANSI C mit Angabe von void in den Funktions-Klammern angegeben werden,
z. B.:
int
funk_name(void)
Ein anderes Beispiel wäre die Bibliotheks-Funktion
void
abort(void)
welche das sofortige Verlassen eines Programms (ohne sonstige Aktivitäten
wie Übertragen noch nicht geleerter Puffer in noch offene Dateien) veranlaßt.
Die neuen Schlüsselwörter const und volatile
Die beiden neuen Schlüsselwörter const und volatile werden bei Variablen-
Deklarationen und -Definitionen verwendet.
const
const teilt dem Compiler mit, daß das zugehörige Objekt nicht modifiziert wer-
den darf, d. h.: nach dieser Deklaration darf einem solchen Objekt weder ein
Wert zugewiesen werden, noch darf es inkrementiert oder dekrementiert werden.
Dieses neue Schlüsselwort bringt 3 Vorteile mit sich:
besserer Code
kann vom Compiler generiert werden. Da ein solcher Wert niemals modifiziert
werden darf, kann der Compiler sich für die gesamte Programmlaufzeit auf die
Unveränderlichkeit dieses Werts verlassen und benötigt deshalb nicht die "auf-
wendige” Verwaltung wie für Variablen.
Beispiel:
const int kilo_byte = 1024;
Der Compiler muß keinen Code generieren, um den Wert von kilo__byte aus dem Speicher zu
laden. Anstelle dessen kann er überall, wo kilo_byte verwendet wird, den Wert 1024 einsetzen.
früheres Finden von Logikfehlern
Die Einführung des Schlüsselworts const ermöglicht es nun, "nur lesbare"
(Read-only) Daten auch explizit als solche zu kennzeichnen. Wird hierbei diszipli-
niert gearbeitet, dann kann bereits der Compiler den Fehler erkennen, wenn
versucht wird, "nur-lesbare” Daten zu beschreiben. Solche Fehler könnten
ansonsten erst zur Programmlaufzeit entdeckt werden.
17-31
bessere Verständlichkeit der Programme
Ein Programm ist sicherlich besser verständlich, wenn bereits bei der Deklaration
von Daten erkennbar wird, daß gewisse Daten nur zur Information dienen (lesbar
sind) und niemals ihre Werte verändern werden.
Noch einige Besonderheiten, welche es im Zusammenhang mit Zeigern und
const zu beachten gibt:
const int *zgr___auf_konstante;
int *const konstanter____zgr;
Der Inhalt des Speicherplatzes, auf welchen zgr_auf_konstante zeigt, darf
beim Zugriff über zgr_auf_____konstante nicht verändert werden,
zgr__auf__konstante selbst dagegen darf verändert werden.
Im Gegensatz dazu darf sehr wohl der Inhalt, auf den konstanter_zgr zeigt,
verändert werden, aber konstanter_zgr selbst darf nicht modifiziert werden.
- - ------------------------1
_______________________Beispiel 11________________________I
typedef enum ( mon, die, mit, don, fre, sam, son ) tag_name_typ;
const tag_name_typ *montag = mon; /* montag -----> Zeiger auf eine Konstante ♦/
tag_name_typ *const naechst_tag; /* naechst_tag ------> Konstanter Zeiger ♦/
tag_name_typ wochentag[5] - ( mon, die, mit, don, fre };
main()
(
♦montag « die; /* nicht moeglich ♦/
montag = fcwochentagf0];
*naechst_tag = die;
naechst_tag++; /♦ nicht moeglich ♦/
)
volatile
Dieses Schlüsselwort kann als Gegenstück zu const verstanden werden: Es
sollte für Variablen verwendet werden, welche nicht nur durch das Programm
selbst, sondern auch jederzeit von "außerhalb” (z. B. durch Interrupts) verändert
werden können*). Bei Angabe dieses Schlüsselworts muß der Compiler sicher-
stellen, daß jedes - vom Programmierer vorgegebene - Lesen und Beschreiben
eines volatile-Objekts genau so wie vorgegeben stattfindet. Ein Compiler darf
also vorgegebene Lese- oder Schreiboperationen auf volatile-Objekte nicht
"wegoptimieren".
HINWEIS: *) volatile bedeutet ins Deutsche übersetzt: flatterhaft, unbeständig; es warnt
den Compiler davor, sich bei seiner Codegenerierung nicht darauf zu verlassen,
daß der Inhalt des entsprechenden Objekts konstant bleibt, sondern sich jederzeit
ändern kann.
17-32
Beispiel 12 ~ |
f
main() /* Summe aller ungeraden Zahlen berechnen */
I
int sum=O, i, n;
printf(”Gib N ein:
scanf(&n);
for (i = 1 ; I <- n ; I = I + 2)
sum + =1;
I
Dieses Beispiel könnte einen Optimierer in einem Compiler dazu bringen, nicht für jeden Schleifen-
durchlauf sum und i auf den wirklichen Wert zu setzen, sondern stattdessen die entsprechende
Werte zu diesen beiden Variablen in den Registern zu halten und erst mit dem Abschluß der
Schleife die ermittelten Werte aus den Registern in den Speicher und damit in die Variablen sum
und I zu schreiben. In diesem Beispiel würde diese Vorgehensweise keinen Schaden anrichten.
Bei hardwarenaher Programmierung (wie Gerätetreiber oder Zeiger auf ein E/A-Port) kann aller-
dings eine solche Optimierung ‘'tödlich” sein:
short *bildschirm__port = TTYADDR;
for (i = 0 ; I < n ; 1+ +)
♦bildschirm_port = vektorfi];
Da hier nicht garantiert ist. daß wirklich ein Code - wie vorgegeben - generiert wird, muß das
Schlüsselwort volatile angegeben werden:
volatile short *bildschirm_port = TTYADDR;
for (i = 0 ; i < n ; i+ +)
*bildschirm_port = vektorfi];
Die Kombination beider Schlüsselwörter ist auch möglich: Die Deklaration
extern const volatile int real_time_clock;
bedeutet, daß der Inhalt von real_time_clock zwar von der Hardware verändert werden
darf, aber es kann dieser Variablen weder ein Wert zugewiesen werden, noch kann sie inkremen-
tiert oder dekrementiert werden.
Deklaration- und Definition von Funktionen
Bisherige Konventionen bei der Deklaration
In Alt-C teilte eine Funktionsdeklaration dem Compiler lediglich den Datentyp
des Rückgabewerts mit:
17-33
float hoch();
char *strcpy();
int abort();
Wurde eine Funktion nicht vor ihrem Aufruf deklariert, so wurde vom Compiler
der Rückgabe-Datentyp int angenommen, was dazu führte, daß Funktionen,
welche int-Werte zurücklieferten erst gar nicht mehr deklariert werden mußten.
C bot auch keine Möglichkeit, den Typ und die Anzahl der Funktionsargumente
anzugeben, was in anderen Programmiersprachen wie PASCAL schon immer
möglich war. Auf diese mangelnde Typüberprüfung hat auch ein Großteil der
C-Kritiker "eingehauen", denn hier lag ganz offensichtlich eine Schwäche.
Funktions-Prototypen -
Die große Neuheit von ANSI C
ANSI C entkräftete viele der Argumente gegen C, indem es Funktions-Prototypen
einführte. Dies ist wahrscheinlich die bedeutenste und revolutionärste Neuheit
von ANSI C:
Funktions-Prototypen ermöglichen es, bei der Deklaration einer Funktion nicht
nur den Rückgabe-Datentyp, sondern auch die Typen der einzelnen formalen
Parameter anzugeben.
Beispiele:
float hochffloat, int);
char *strcpy(char *, const char *);
void abort(void);
Es ist sogar möglich, neben dem Typ eines formalen Arguments noch einen
Namen anzugeben
float hoch(float zahl, int potenz);
char *strcpy(char *ziel, const char *quelle);
void abort(void); /* hier kein Name waehlbar */
Eine Kombination beider Methoden wäre auch möglich:
float hochffloat zahl, int );
char *strcpy(char *, const char *quelle);
void abort(void); /* hier kein Name waehlbar */
Die Einführung von Funktions-Prototypen bringt die folgenden Vorteile mit sich:
Lesbarkeit und Verständlichkeit von Programmen wird erleichtert.
Die Lesezeit für Programme übersteigt bei weitem die benötigte Schreibzeit.
17-34
] Beispiel 13 ~~
Die Funktion memcpy aus der Standard-Bibliothek könnte die folgende Prototyp-Deklaration
haben:
void *memcpy( void *ziel, const void *quelle, size_t n );
Der Leser des Programms kann bereits aus der Deklaration von memcpy erkennen, daß diese
Funktion einen datentyplosen Zeiger zurückgibt und genau mit drei aktuellen Argumenten versorgt
werden muß: Die beiden ersten (ziel und quelle) sind datentyplose Zeiger, und der letzte ist
eine ganze Zahl*). Zusätzlich ist aus dieser Deklaration zu erkennen, daß der 2. Parameter ein
Zeiger auf einen ”nur-lesbaren” Speicherbereich (darf von dieser Funktion nicht verändert werden)
ist.
Falsche Argument-Angaben bei einem Funktionsaufruf können bereits vom
Compiler entdeckt werden.
strncpy ist in string.h mit
char *strncpy( char *s1, const char *s2, size__t n );
deklariert.
Beim Übersetzen des folgenden Programms
linclude <stdio.h>
iinclude <string.h>
char *ziel_zgr « "Das ist eine Test-Zeichenkette";
char *quell_zgr= "Diese Zeichenkette wird spaeter nach ziel_zgr kopiert";
unsigned int anzahl;
main()
printf("Wieviele Bytes sind zu kopieren ?\n");
scanf("td", fcanzahl);
ziel_zgr = strncpy(ziel_zgr, quell_zgr); /* Anzahl der zu kopierenden */
/* Bytes wurde vergessen. */
HINWEIS: *) size____t ist ein Ganzzahl-Datentyp und wird für Größenangaben verwendet.
17-35
müßte bereits der Compiler "meckern”, denn es liegen Unterschiede zwischen der aktuellen
(char *, char *)
und der formalen Parameterliste
(char *, const char *, size_t)
vor, da der 3. Parameter beim Aufruf nicht angegeben wurde*).
Automatische Anpassung der aktuellen Argumenttypen an die erwarteten
formalen Parameter-Typen durch den Compiler
_______________________Beispiel 15_____________________
finclude <stdio.h>
float
add(float a, float b)
(
return(a+b);
)
nain()
(
int gzl, gz2;
float fzl, fz2;
printf("Gib 2 ganze Zahlen ein :
scanf("%d %d", &gzl, &gz2);
printf("%d + %d = %.Of\n\n", gzl, gz2, add(gzl,gz2)); /♦ 1. Aufruf ♦/
printf("Gib 2 gebrochene Zahlen ein :
scanf("%f %f", tfzl, &fz2);
printf("%f + %f = %.2f\n\n", fzl, fz2, add(fzl,fz2)); /♦ 2. Aufruf */
Nachdem die Prototyp-Definition**)
float addffloat a, float b);
dem Compiler mitteilte, welche Argument-Typen die Funktion add erwartet, kann dieser auto-
matisch vor jedem Funktionsaufruf eine entsprechende Konvertierung durchführen (z. B. beimt
Aufruf: Int -> float). Ohne diese Konvertierung würden zwei int-Werte (z. B. jeweils zwei Bytes)
auf den Stack gelegt; innerhalb von add würden aber zwei float-Werte (z. B. jeweils vier Bytes)
gelesen, was unweigerlich zu fehlerhaften Ergebnissen führen muß.
HINWEIS: *) Der Unterschied zwischen char * bei der aktuellen und const char ♦ bei der
formalen Parameterliste ist allerdings nicht von Bedeutung.
17-36
Schnittsteilen-Überprüfung bereits durch Compiler möglich
Größere Software-Pakete setzen sich meist aus mehreren Modulen zusammen,
wobei die einzelnen Module von unterschiedlichen Personen oder Teams entwik-
kelt werden. Meist kommunizieren diese einzelnen Module über gegenseitige
Funktionsaufrufe miteinander. Solche Kommunikationspunkte nennt man übli-
cherweise Schnittstellen. Es ist nun gängige C-Praxis, daß jeder Modul seine
Schnittstellen zur Außenwelt in einer eigenen Header-Datei mit extern deklariert.
Jeder andere Programmteil, der nun Funktionen aus diesem Modul aufrufen
möchte, verleibt sich diese extem-Deklarationen mittels
#include “Modul-Header-Datei”
ein.
HINWEIS: **) Dieser Prototyp-Mechanismus gefiel dem Komitee so gut, daß es ihn neben der
Deklaration auch bei der Definition von Funktionen zuließ. Die traditionelle Dekla-
ration
size_t fread(puffer, groesse, anzahl, datei)
char ‘puffer;
size_t groesse;
slze_t anzahl;
FILE ‘datei;
{
}
kann von nun an mit
size_t fread(
char ‘puffer,
size_t groesse,
size_t anzahl,
FILE ‘datei
{
}
angegeben werden.
17-37
Beispjel 16______________________
(Aus Alt-C):
Modul main
♦include <stdio.h>
♦include "rechaltc.h"
main()
(
int operator;
double a,b,ergeb;
printf("Gib 2 Werte ein : );
scanf("%lf %lf", fca, fcb) ;
/* Endlos-Schleife, welche durch BREAK zu verlassen ist */
while (1) (
printf(«Gib Operator ein (+ - 0, - - 1, * = 2, / - 3) : •)•
scanf("%d", ^operator);
if (operator>«0 && operator<=»3) (
break;
) eise (
printf(« Falsche Eingab e\n\a«);
)
switch (operator) (
case 0: ergeb - add(a.b); break;
case 1: ergeb « subtr(a,b); break;
case 2: ergeb - mult(a,b); break;
case 3: ergeb « divi(a,b); break;
printf(«Das Ergebnis ist %lf\n«, ergeb);
Modul rechaltc
/••••••••••••••/
/• rechaltc.h */
extern double add( );
extern double subtr( );
extern double mult( );
extern double divi( );
/* addiert die beiden Werte a und b •/
/* Parameter: double a
double b */
/* subtrahiert Wert b von Wert a */
/* Parameter: double a
double b ♦/
/* multipliziert die Werte a und b ♦/
/♦ Parameter: double a
double b ♦/
/* dividiert Wert a durch Wert b */
/* Parameter: double a
double b */
17-38
/* rechaltc.c */
/*****••*•****•/
double
add( a, b)
double a,b;
retum (a+b) ;
double
subtr( a, b)
double a,b;
retum (a-b)i
double
eult( a, b )
double a,b;
retum (a*b) ;
double
divi( a, b )
double a,b;
return(a/b);
Hier kann der Compiler lediglich den Rückgabe-Datentyp einer Schnittstellen-Funktion beim Aufruf
überprüfen.
Nun könnte es vorkommen, daß die Schnittstellen von Modul rechaltc (ohne Wissen von main*))
geändert** *) würden: z. B. die Funktion add erwartet nicht mehr zwei zu addierende Werte,
sondern einen Vektor von zu addierenden double-Werten, wobei die Anfangsadresse dieses
Vektors als 1. Argument und die Elementzahl als 2. Argument anzugeben ist:
Modul main
finclude <stdio.h>
♦inelüde "rechaltZ.h"
uain()
int operator;
double a, b,ergeb;
printffGib 2 Werte ein : •) ;
scanf(**%lf %lf", 4a, 4b);
/* Endlos-Schleife, welche durch BREAK zu verlassen ist */
vhile (1) (
printf("Gib Operator ein (+ « 0, - • 1, * = 2, / » 3) : «);
scanf(*td", 4operator);
if (operator>-0 44 operator<-3) (
break;
) eise (
printff" Falsche Eingab e\n\a");
I
I
switch (operator) (
case 0: ergeb « add(a,b); break;
case 1: ergeb « subtr(a,b); break;
case 2: ergeb » mult(a,b); break;
case 3: ergeb - divi(a,b); break;
I
printffDas Ergebnis ist tlf\n*, ergeb);
HINWEISE: *) Zum Beispiel eine bilaterale Absprache zwischen den rechaltc-Entwicklern
und einer anderen Entwicklungsgruppe.
**) Leider kommen nicht abgesprochene Schnittsteilen-Änderungen immer noch
sehr häufig vor, obwohl die möglicherweise verheerenden Auswirkungen zur
Genüge bekannt sind.
17-39
Modul rechaltc *)
/*«***«**•••***/
/♦ rechalt2.h */
/•*******«*»•*«/
extern double add( );
axtern double subtr( );
extern double mult( );
extern double divi( );
/* addiert alle Werte aus Vektor a; Laenge dieses ♦/
/* Vektors wird mit 2. Parameter b festgelegt •/
/♦ Parameter: double *a
int b */
/♦ subtrahiert Wert b von Wert a */
/* Parameter: double a
double b ♦/
/* multipliziert die Werte a und b */
/* Parameter: double a
double b */
/• dividiert wert a durch Wert b ♦/
/• Parameter: double a
double b »/
/**••****•**•«*/
/* rechalt2.c */
/****»****«*«*•/
double
add( a, b)
double *a;
int b;
int i;
double sum-O;
for (i-0 ; i<b ; i-M-)
sum ♦- a(i];
return(sum);
)
double
subtr( a, b)
double a,b;
(
return(a-b);
)
double
mult( a, b )
double a,b;
(
return(a*b);
)
double
divi( a, b )
*» double a,b;
(
return(a/b);
1
Hier könnte der Compiler die Schnittstellen-,'Verletzung’* nicht entdecken, was dazu führt, daß
zur Programmlaufzeit Fehler bei der Addition auftreten.
Mit ANSI-C-Prototyping ist nun eine vollständige Schnittsteilen-Überprüfung bereits durch den
Compiler möglich. Wenn das vorherige Beispiel unter Verwendung von Prototypen realisiert wird,
so würde bereits der Compiler die Schnittstellen-Verletzung bemerken, und das Absetzen eines
logisch fehlerhaften Codes kann bereits zur Compilezeit bemerkt werden:
HINWEIS: *) Um eine Unterscheidung zu erlauben, wurden die entsprechenden Dateien rech-
alt2.c und rechalt2.h genannt.
17-40
Modul main
(include <stdio.h>
(include "rechansi.h"
sain()
(
int operator:
double a,b,ergeb:
printf("Gib 2 Werte ein : •) ;
scanf("%lf llf", 4a, 4b);
/♦ Endlos-Schleife, welche durch BREAK zu verlassen ist */
while (1) (
printf("Gib Operator ein (♦ • 0, - « 1, * - 2, / 3) : ;
scanf("td", 4operator);
if (operator>-0 44 operator<>3) (
break;
) eise (
printf(• Falsche Eingab e\n\a»);
I
switch (operator) (
case 0: ergeb - add(a.b); break;
case 1: ergeb - subtr(a.b); break;
case 2: ergeb - nult(a,b); break;
case 3: ergeb - divi(a,b); break;
I
printf("Das Ergebnis ist llf\n", ergeb);
Modul rechansi*)
/**•*••*••••**•/
/• rechansi.h */
/»«*****•*••••«/
extern double
add( double *a, int b ); /* addiert alle Werte aus Vektor a; Laenge */
/• dieses Vektors wird Mit 2. Parameter b •/
/• festgelegt •/
extern double
subtr( double a, double b ); /* subtrahiert Wert b von Wert a */
extern double
ault( double a, double b )r /• Multipliziert die Werte a und b •/
extern double
divi( double a, double b ); /* dividiert Wert a durch Wert b */
/♦ rechansi.c •/
.......••♦•*••*/
double
add( double »a, int b )
int i;
double sun-0;
for (i-0 ; i<b ; i+t)
sun a(i);
return(sus);
I
double
subtr( double a, double b )
(
return(a-b);
>
HINWEIS: *) Um eine Unterscheidung zu erlauben, wurden die entprechenden Dateien rech-
ansi. c und rechansi.h genannt.
17-41
doubl«
«ult( double a, double b )
<
retum (a*b) ;
»
double
divi( double a, double b )
(
retum (a/b) ;
>
Nachdem beim Übersetzen die Schnittsteilen-Abweichung gemeldet wurde, kann eine Schnittstei-
len-Anpassung erfolgen, so daß sich dann das richtige Programm ergibt:
Modul main
#include <stdio.h>
finclude "rechansi.h"
main()
(
int operator;
double a,b,vekt[2]»ergeb;
printf("Gib 2 Werte ein : ");
scanf("%lf %lf", &a, &b) ;
/* Endlos-Schleife, welche durch ein BREAK zu verlassen ist */
while (1) (
printf("Gib Operator ein (+ - 0, - * 1, ♦ « 2, / « 3) : ") ;
scanf("%d", Äoperator);
if (operator>«0 && operator<-3) {
break;
) eise (
printf(" Falsche Eingab e\n\a");
)
>
switch (operator) (
case 0: vekt[0)=a; vekt[l]=b; ergeb = add(vekt,2); break;
case 1: ergeb = subtr(a,b); break;
case 2: ergeb = mult(a,b); break;
case 3: ergeb = divi(a,b); break;
printf("Das Ergebnis ist %lf\n", ergeb);
Somit können Schnittstellenabweichungen bereits zur Übersetzungszeit festgestellt werden Frü-
her führte dies zuerst zu einem Programm-Fehlverhalten, was tagelange Fehlersuche (vor allem
bei größeren Projekten) nach sich ziehen konnte.
Ellipsen-Prototypen für Funktionen
mit variabler Parameterzahl
Im früheren C wurden alle übergebenen Parameter eines Funktionsaufrufs 'von
rechts nach links" auf den Stack abgelegt. Der Vorteil dieser Methode ist, daß
eine variabel lange Liste von aktuellen Parametern beim Aufruf von Funktionen
wie printf möglich war. Der Nachteil dieser Vorgehensweise war, daß manchmal
von C-Compilern nicht so effizienter Code beim Funktionsaufruf erzeugt werden
konnte, wie z. B. von PASCAL-Compilern, wo die Anzahl und der Typ der Argu-
mente zum Aufrufzeitpunkt bekannt ist.
17-42
Ein weiterer Nachteil dieser Methode war. daß sie nicht den standardisierten
Aufruffolgen einiger Betriebssysteme entsprach. Nichtsdestotrotz mußte bei allen
Funktionsaufrufen die ineffizientere Aufrufsequenz gewählt werden, um gelegent-
lichen printf-Aufrufen gerecht zu werden.
Das Linken von Modulen aus anderen Sprachen wurde durch diese C-speziellen
Aufruffolgen ebenfalls nicht erleichtert.
Das ANSI-C-Komitee war über diese Nachteile nicht besonders glücklich und
stellte folgende Regel auf:
Funktionen, welche eine variable Anzahl von Argumenten erwarten, müssen mit
sogenannten Ellipsen-Prototypen deklariert werden:
int printffconst char *format, ..
Die drei Punkte (Ellipse) bei einer Deklaration deuten an. daß beim Aufruf von
printf neben einem fest vorgeschriebenen Parameter format beliebig weitere
aktuelle Parameter angegeben werden können.
Mit der Einführung von Ellipsen kann nun der Compiler bei jedem Funktionsaufruf
ohne vorheriger Ellipsen-Prototyp-Deklaration annehmen, daß diese Funktion
eine feste Anzahl von Parametern hat. In solchen Fällen kann also immer der
effizientere Aufrufmechanismus (Argumente "von links nach rechts" ablegen)
gewählt werden.
Der weniger effizientere Mechanismus (Argumente "von rechts nach links" auf
den Stack legen) müßte somit nur noch gewählt werden, wenn zu der entspre-
chenden Funktion ein "Ellipsen-Prototyp" vorliegt.
Abarbeiten variabel langer Argumentlisten
Um eine variable Anzahl von Argumenten innerhalb einer Funktion abarbeiten
zu können, sind die folgenden Schritte notwendig:
1. Zugriff auf die fest vorgegebenen Parameter über deren Namen ist wie bisher
möglich.
2. Deklaration einer Zeigervariablen des (in der Standard Header-Datei stdarg.h
definierten) Typs va_list innerhalb der Funktion:
va___list argument___list_zeiger;
3. Aufruf des Makros va_start mit 2 Argumenten: dem Namen des zuvor
deklarierten Zeigers (Typ va_list) und dem Namen des letzten fixen Para-
meters:
va_start(argument_list____zeiger, letzter_benamter_param);
17-43
Dieser Aufruf ermittelt anhand des letzten fixen Arguments, wo das erste
variable Argument (auf dem Stack) gespeichert ist, und setzt argu-
ment_list_zeiger auf den Anfang dieser variablen Argumentenliste.
4. Wiederholter Aufruf des Makros va_arg. um so die variable Argumentenliste
"Stück für Stück" abzuarbeiten; dieses Makro schaltet argu-
ment___list_zeiger immer ein Argument weiter in dieser Liste. Als erstes
Argument ist bei va_arg der argument_list_zeiger anzugeben; das
zweite Argument muß den Typ des zu erwartenden Arguments festlegen, um
so va__arg die Größe des entsprechenden variablen Arguments mitzuteilen.
Das Ende einer variabel langen Argumentenliste muß über getroffene Verein-
barungen erkannt werden: z. B. erstes Argument gibt die Anzahl der aktuellen
Argumente an, oder letztes Argument ist -1*>, usw.
5. Vor Rückkehr aus dieser Funktion muß noch das Makro va_end aufgerufen
werden:
va_end(argument_list_zeiger);
Dieser Aufruf setzt argument__list_zeiger auf NULL und versetzt den
Stack wieder in einen "sauberen" Zustand. Ohne diesen Aufruf kann der
weitere Programmablauf ein seltsames Verhalten zeigen.
Bei größeren Softwareprodukten ist es üblich, einen Modul (z. B. fehler) zu entwerfen, welcher
für die Ausgabe von Fehlermeldungen zuständig ist.
Dazu definiert man einen Zeichenketten-Vektor, in welchem alle Fehlermeldungen untergebracht
werden. Zu diesem Vektor wird dann eine korrespondierende Liste von Fehlernummern in der
dazugehörigen Header-Datei (z. B fehler.h) definiert. Jeder andere Modul, welcher Fehlermeldun-
gen ausgeben möchte, kann sich nun mit #include "fehler.h"diese Liste von Fehlernummern ein-
verleiben.
Will er nun eine Fehlermeldung ausgeben, so ruft er die zentrale Fehlermeldungs-Routine (aus
Modul fehler} mit der entsprechenden Fehlernummer auf.
Da Fehlermeldungen meist variable Komponenten (wie z. B. Dateinamen) enthalten, bietet es
sich bei der Realisierung der zentralen Fehlerbehandlungs-Routinen an. mit variabel langen
Argumentenlisten zu arbeiten. Die Länge der variabel langen Argumentenliste könnte dabei über
ein printf-ähnliches Format gesteuert werden.
HINWEIS: *) nurmöglich, wenn alle Argumente numerische Werte von einem Typ(z. B. int) sind.
17-44
f fehler.h •/
»define DATEI_OK FFNUNGS_NR 0
»define DATEI ZUG»IFFS_NR 1
»define DATEI SCHLIESS NR 2
•define INT RECHEN NR ~ 3
»define FLOÄT_RECHEN_NR 4
»define FXT AÜFRUFNR 5
»define SPEICHER XANGEL_NR *
»define FALSCHE_EINGABE_NR 7
extern void
fehler_au»gabe( unsigned int fehlernr, ... );
»include <stdio.h>
»include <stdarg.h>
•include "fehler.h"
char •fehlerne1düng{) • <
"Datei «a kann nicht (tue 3a) eroeffnet werden\n",
"Fehler beis %s-Zugrlff auf Datei %e\n",
"Datei Ws kann nicht geschlossen werden\n",
"Bei Ib der int-Werte »d und Id trat ts euf\n",
•Bei %a der float-Werte »f und »f trat ts auf\n",
"Bels Aufruf von ts(tlf) trat ein ts auf\n",
Angeforderter Speicher (tld Bytes) konnte nicht reserviert werden\n"
\aDie Eingabe von »c ist falsch\n Erlaubte Eingaben vaeren ts\n"
void
fehler_ausgabe( unsigned int fehler_nr, ... )
I
char S, "foraat - fehleraeldungffehlernr);
va_llst argzeiger;
va_start( argxeiger, fehlernr );
while (•foraat) (
if ("foraat !• ♦%•) (
putcharf"foraat) :
) eise (
switch(•♦♦foraat) <
case *c' : fprintf(stderr, "tc", va_arg(arg.zeiger, int ));
break;
case ’d‘ s fprintf(stderr. "Id", vaargfargzelger, int ))1
break;
case »f ; fprintf(stderr. Mt". va_arg(erg_zeiger. double));
break;
case 's* : fprintf(stderr. "ls", va_arg(arg_zeiger, char • ));
break;
case '!• r if ((z-»**foraat)— 'd')
fprintf(stderr. "tld", va_arg(arg.zeiger, long));
fprintf(stderr. "Hf", va_arg(arg_zeiger, double))»
break;
>
)
foraat«-*;
)
vaend(arg.zelger)t
»ifdef TEST
int a-10000, b-2Z000;
long int c-640000;
float fl-37.125. f2-0.0;
double y»-23.456;
char z-'t'j
fehler_ausgabe(DATEI_OEFFNUNG5 NR. "neu.dat", "Lesen");
fehlerausgabe(DATEI ZUGRIFFSNR, -Schreib", -test.txt")»
fehler ausgabe(DATEI~SCHLIES$_NR, "ende.txt");
fehler_ausgabe(INT RECHEN NR. "Multiplikation", a. b. "OVERFLOW");
fehler_ausgabe(FLOÄT RECHEN NR. "Division", fl, f2, "Division durch o-);
fehler_ausgabe(FKT_AÜFRUF_WR, "log", y, "Doaainfehler");
fehler^ausgabe)SPEICHER MANGEL.NR, c);
fehler~ausgabe(FALSCHE EINGABE NR, z, "'j' oder ’y* oder 'n'");
)
»endlf
/• Wen TEST definiert wird
(enveder alt »define TEST oder Bein Coepileraufruf ...-DTEST),
dann vuerde sich folgende Bilöschiraausgabe ergeben:
Datei neu.dat kann nicht (zua Lesen) eroeffnet werden
Fehler beia Schreib-Zugriff auf Datei teat.txt
Datei ende.txt kann nicht geschlossen werden
Bel Multiplikation der int-Werte 30000 und 28000 trat OVERFLOW auf
Bei Division der float-Werte 37.125000 und 0.000000 trat Division durch 0 auf
Beie Aufruf von log(-23.456000) trat ein Doaainfehler auf
kngeforderter Speicher (640000 Bytes) konnte nicht reserviert werden
(Pieps)Die Eingabe von t ist falsch
Erlaubte Eingaben waeren oder *y* oder 'n'
•/
17-45
Im vorher angegebenen Beispiel wird zugleich über eine #ifdef TEST.
#end/Y-Klammer eine Möglichkeit zum Testen der zentralen Fehlerbehandlungs-
Routine fehler_ausgabe gegeben; dazu muß nur der Name TEST definiert
werden. Aus diesem Beispiel ist noch ein wichtiger Punkt zu ersehen, den es
beim Abarbeiten von variabel langen Argumentenlisten zu beachten gilt: Wenn
beim Aufruf einer Funktion ein char- oder ein float-Argument übergeben wird,
so wird trotzdem die Byteanzahl von int (bei char) oder double (bei float)
für entsprechendes Argument auf dem Stack hinterlegt.
Deswegen gilt:
Es ist bei char-Argumenten der Typ int und bei float-Argumenten der Typ
double als 2. Argument beim Makro va_arg anzugeben.
Die alte Form von Funktions-Definitionen und -Deklarationen ist zwar auch weiter-
hin erlaubt, um den vielen Alt-C-Programmen gerecht zu werden. Nichtsdesto-
trotz sollte man ab jetzt die Prototypen-Funktionen verwenden. Die daraus resul-
tierenden Vorteile sollten auch alte C Hasen überzeugt haben.
Neuer monadischer Operator +
Wahrscheinlich ist es vielen C-Programmierern gar nicht aufgefallen, aber bisher
war der monadische Operator + kein fest vorgeschriebener Bestandteil der
Sprache C.
Von nun ab ist er es, und ein C-Programm, welches Konstruktionen wie
zuwachs = +10;
rate = +4.5;
verwendet, darf sich portables C-Programm nennen. Es ist auch zu erwähnen,
daß die Bibliotheksfunktionen (wie z. B scanf, atoi, ...) diesen Operator von
nun ab kennen müssen.
Weiterführende Literatur zu ANSI C
Alle neuen Aspekte der Sprache ANSI C sowie die gesamte ANSI-C-Standardbi-
bliothek werden ausführlich und anhand von Anwendungen in dem kommenden
Buch des Autors Helmut Herold dargestellt und für professionelle C-Programmie-
rer kommentiert:
Helmut Herold
ANSI C
tewi-Verlag, ISBN 3-89362-040-0
17-46
Programmierbeispiele auf Diskette
Alle im "C-Gesamtwerk” angegebenen Programmbeispiele sind auf einer 360
KB Diskette (Betriebssystem MS-DOS) zum Preis von DM 29 90 erhältlich
Bestellungen sind zu richten an:
M Melber
- Disketten zu C-Gesamtwerk -
Jaminstraße 39
D-8520 Erlangen
Bestellen können Sie entweder per Postkarte oder indem Sie den vorgedruckten
Bestellschein auf der letzten Seite ausschneiden und in einem Fensterkuvert ein-
senden
17-47
ANHANG
(ASCII-Tabelle und Literaturverzeichnis)
A-1
NUL A8 OOOü 00h 0000 0000b
© SOB AA OOlo 01h 0000 OOOlß
STX AB 002d 02h 0000 0010b
ETX *C ÖOSd 03h 0000 0011b
♦ EOT AD 004q 04h 0000 0100b
* ENQ AE 005d 05h 0000 0101b
ACK AF 006o 06h 0000 0110b
• BEL AG 007d 07h 0000 OIIIq
0 BS AB 008o 08h 0000 1000b
o BT AI 009o 09h 0000 1001b
S LF AJ 010o OAh 0000 1010B
<? VT AK Ollo OBh 0000 1011b
9 FF AL 012q OCh 0000 1100b
CR AM 013d ODh 0000 1101b
4 SO AN 014o OEh 0000 1110b
0 SI AO 015o 0Fh 0000 1111b
► DLE AP 016d 10h 0001 0000b
◄ DC1 AQ 017d 11h 0001 0001b
DC2 AR 018o 12h 0001 00108
II • • DC3 AS 019d 13« 0001 0011b
11 DC4 AT 020d 14h 0001 0100b
coo NAK Aü 021d 15« 0001 0101b
1 SYN AV 022d 16h 0001 0110b
1 ETB AW 023d 17h 0001 0111b
t CAN AX 024q 18h 0001 1000b
EM AX 025o 19« 0001 1001b
SUB AZ 026o IAh 0001 1010B
<- ESC A[ 027d IBh 0001 1011b
1 FS A\ 028d IC« 0001 1100b
e GS *] 029o Wh 0001 1101b
▲ RS 030d IEh 0001 1110b
▼ OS 031d 1Fh 0001 1111b
032d 20h 0010 0000b
1 • 033d 21h 0010 0001b
II 034o 22h 0010 0010b
# 035d 23h 0010 0011b
$ 036o 24h 0010 0100B
(L "0 037d 25h 0010 0101b
& 038d 26h 0010 0110b
9 039d 27h 0010 0111b
040o 28h 0010 1000b
041o 29h 0010 1001b
★ 042d 2Ah 0010 1010b
-f- 043d 2Bh 0010 1011b
9 044o 2Ch 0010 1100b
— 045q 2Dh 0010 1101b
• 046q 2Eh 0010 1110b
! 047d 2Fh 0010 1111b
0 048D 30h 0011 0000b
1 049d 31h 0011 0001b
2 050d 32h 0011 0010b
3 051d 33h 0011 0011b
4 052d 34h 0011 0100B
5 053d 35h 0011 0101b
6 054d 36h 0011 0110b
y 055d 37h 0011 0111b
8 056d 38h 0011 1000b
9 057d 39h 0011 1001b
• • 058d 3Ah 0011 IOIOq
• 9 059o 3Bh 0011 1011b
060o 3Ch 0011 1100b
061q 3Dh 0011 1101b
062d 3Eh 0011 1110B
? • 063d 3Fh 0011 1111b
Ä-3
064o 40h 0100 0000b
A 065d 41h 0100 0001b
I 3 066d 42h 0100 0010B
C 067d 43h 0100 0011b
D 068d 44h 0100 0100b
1 - 069p 45h 0100 0101b
1 - 070d 46h 0100 0110b
G 071d 47h 0100 0111b
H 072d 48h 0100 1000B
I 073d 49h 0100 1001b
J 074d 4Ah 0100 1010b
K 075d 4Bh 0100 1011b
076d 4Ch 0100 1100b
M 077o 4Dh 0100 1101b
1 1 078p 4Eh 0100 1110B
0 079d 4Fh 0100 1111b
p 080d 50h 0101 0000b
Q 081c 51h 0101 0001b
R 082d 52h 0101 0010B
S 083o 53h 0101 0011b
084p 54h 0101 0100b
U 085d 55h 0101 0101b
V 086p 56h 0101 0110b
VI 087d 57h 0101 0111b
X 088p 58h 0101 1000B
Y 089d 59h 0101 1001b
Z 090p 5Ah 0101 1010b
[ 091d 5Bh 0101 IOIIq
\ 092d 5Ch 0101 1100B
] 093d 5Dh 0101 1101b
/\ 094d 5Eh 0101 1110b
095o 5Fh 0101 1111b
6 096p 60h 0110 0000b
a 097p 61h 0110 0001B
b 098o 62h 0110 0010B
c 099d 63h 0110 0011b
d 100p 64h 0110 0100b
e 101D 65h 0110 0101b
f 102p 66h 0110 0110b
g 103p 67h 0110 0111b
h 104p 68h 0110 1000b
i 105d 69h 0110 1001b
j 106p 6Ah 0110 1010b
k 107p 6Bh 0110 1011b
1 108p 6Ch 0110 1100b
m 109p 6Dh 0110 1101b
n 110p 6Eh 0110 1110b
0 111d 6Fh 0110 1111b
1 p 112d 7h 0111 0000b
q 1130 71h 0111 0001B
r 1140 72h 0111 0010b
s 1150 73h 0111 0011s
t 116p 74h 0111 0100b
u 1170 75h 0111 0101b
V 118p 76h 0111 0110b
w 119 77h 0111 0111b
X 120p 78h 0111 1000B
y 1210 79h 0111 1001b
z 122d 7Ah 0111 1010b
123o 7Bh 0111 1011B
124d 7Ch 0111 1100b
125o 7Dh 0111 1101b
ru 126d 71« 0111 1110b
ö 127O 7Th 0111 1111B
A-4
G 128d 80h 1000 00006
ü 129d 81h 1000 0001B
e 130o 82h 1000 OOIOq
XV a 131d 83h 1000 0011b
ä 132d 84h 1000 0100b
a 133d 85h 1000 0101b
o a 134d 86h 1000 0110b
135d 87h 1000 0111b
XV e 136o 88h 1000 1000b
e 137d 8}h 1000 1OO1B
e 138d 8Ah 1000 1010b
i 138c 8Bh 1000 10118
XV 1 140d 8Ch 1000 1100b
1 141d 8Dh 1000 1101b
Ä 142d 8Eh 1000 1110b
Ä 143d 8Fh 1000 1111b
E 144d 90h 1001 0000b
ae 145d 91h 1001 0001b
/E 146d 92h 1001 0010b
<o 147O 93h 1001 0011b
Ö 148d 94h 1001 0100b
'O 149O 95h 1001 0101b
XV u 150d 96h 1001 0110b
ü 1510 97h 1001 0111b
y 152d 98h 1001 1000B
ö 153d 99h 1001 1001b
ü 154d 9Ah 1001 1010b
155O 9Bh 1001 1011b
£ 156d 9Ch 1001 1100b
¥ 157d 9Dh 1001 1101b
Pt 158d 9B„ 1001 1110b
159O 9Fh 1001 1111b
a 1S0d X0h 1010 0000b
1 1610 Ä1H 1010 0001b
Öl 162d A2h 1010 0010b
u 163d A3h 1010 0011b
n 164d A4h 1010 0100B
N 1«5d A5h 1010 0101b
a 166q A6h 1010 0110b
0 16?d A7h 1010 0111b
• c 168q A8h 1010 1000b
1“ 169d A9h 1010 1001b
—1 170o Uh 1010 1010B
L, ^2 1710 ABh 1010 1011b
'A 172d ACh 1010 1100b
• I 173d ADh 1010 1101b
« 174d AIh 1010 1110b
» 175d AFh 1010 1111b
111 III III 176d B0h 1011 0000b
1 177o B1h 1011 0001b
1 178q B2h 1011 0010B
179d B3h 1011 0011b
J 180o B4h 1011 0100B
181d B5h 1011 0101b
182d B6h 1011 0110b
71 183O B7h 1011 0111b
1 1 184q B8h 1011 1000B
J 1 1 185O B9h 1011 1001b
186o BAh 1011 1010b
“] 1 187O BBh 1011 1011B
J 188d BCh 1011 1100b
u 189d BDh 1011 1101b
=1 190o BEh 1011 1110b
1 1910 BFh 1011 1111b
A-5
L 192d COh 1100 0000b
1 193O C1h 1100 0001b
194d C2h 1100 0010B
F 195d C3h 1100 0011b
— 196d C4h 1100 0100B
+ 197d C5h 1100 0101b
F 198d C6h 1100 0110b
11» 199d C7h 1100 0111b
11 200o C8h 1100 1000b
If 201d C9h 1100 1001b
JL 202d CAh 1100 1010b
ir 203d CB« 1100 1011B
IL Ir 204d CCh 1100 1100b
— 205o CDh 1100 1101b
JL ir 2060 CEh 1100 1110b
1 207d CFh 1100 1111b
11 20Bo DOh 1101 0000b
"Y“ 209o D1h 1101 0001b
TT 210q D2h 1101 0010b
1 211d D3h 1101 0011b
L 212d D4h 1101 0100b
F 213d D5h 1101 0101b
[T 214d D6h 1101 0110b
215d D7h 1101 0111b
216d D8h 1101 1000b
J 217d D9h 1101 1001b
p 218d DAh 1101 1010b
1 219d DBh 1101 1011b
220o DCh 1101 1100b
1 221d DDh 1101 1101b
1 222o DEh 1101 1110b
223d DFh 1101 1111b
a 224d 10h 1110 0000b
ß_ 225o 11h 1110 0001b
r 226O 12h 1110 OOIOq
n 227d 13h 1110 0011b
1 228d I4h 1110 0100b
o 229d 15h 1110 0101b
230d 14h 1110 0110b
T 231o B7h 1110 0111b
0 232d 18h 1110 1000b
233d E9h 1110 1001b
0 234d Uh 1110 1010b
5 235q EBh 1110 1011b
00 236d ICh 1110 1100b
0 237O EDh 1110 1101b
e 238d EEh 1110 1110b
n 239o EFh 1110 1111b
— 240o FOh 1111 0000b
+ 241d Hh 1111 0001b
> 242d F2h 1111 0010b
< 243d F3h 1111 0011b
244d F4h 1111 0100b
245d F5h 1111 0101b
• 246O F6h 1111 0110b
247d F7h 1111 0111b
o 248d F8h 1111 10008
• 249d F9h 1111 1001b
• 250o Fäh 1111 1010b
251O FBh 1111 10UB
n 252O FCh 1111 1100b
2 253O FDh im noiB
254O FE« 1111 1110b
255o FFh 1111 1111b
A-6
Literaturverzeichnis
71/ The C Programming Language, B. W. Kernighan and D. M. Ritchie.
Prentice-Hall, 1978.
72/ UNIX-Anwenderhandbuch, R. Thomas, J. Yates. te-wi, 1983.
A-7 ‘
Stichwortverzeichnis
Abfrage, am Schleifenanfang 8-4, 8-26
am Schleifenende 8-37
abs (Funktion) 15-4
Addition 3-6
Adressierung C-Ebene/Maschinen-
ebene 12-20
Adreß-Operator A 5-7,12-10
aktuelle Parameter 11 -8,11 -20
Anfang einer Datei (rewind) 14-31
Anweisung 7-3
arge 12-89
Argument 11-20
argv 12-89
arithmetic shift 3-24
Arithmetik mit Zeigern 12-31
arithmetische Datentypen 2-4,10-7
ASCII-Code 2-4, A-1
atof (Funktion) 15-4
atoi (Funktion) 15-4
atol (Funktion) 15-5
Aufzählung 16-3
Ausdruck 3-3, 7-4
ausführbare Anweisungen 9-3
Ausgabe, am Bildschirm 6-4
-, einzelner Zeichen 6-4, 6-14
formatiert 6-14,14-23
-.umlenken 14-23
Auswahl einer Strukturkomponente
13-30
auto 11 -36
auto-Variable, Initialisierung 12-64,
13-13
automatlc-Speicherklasse 11 -31
Backslash-Zeichen \ \ 6-14
Backspace-Zeichen \b 6-14
bedingte Bewertung 7-17
Übersetzung 11 -74
Biased Exponent 2-7
Bibliotheks-Standardfunktionen 6-3,
11-4
Binärbaum 13-32
binäres Suchen 13-32
binäre Datei 14-6,14-13,15-3
Bitfelder 13-68
Bitmanipulation, Operatoren 3-10
-,A 3-13
: 3-12,3-17
- , - 3-21
- , ~ 3-21
- , << 3-23
-, > > 3-24
Block 7-3,11-58
blocklokale Variable 11 -59
Blöcke lesen/schreiben 14-22,15-3
bottomup 12-104
break-Anweisung 7-21,8-43
Byte lesen/schreiben 14-13, 15-3
call by reference 11 -27, 11 -36
-.value 11-20
calloc (Funktion) 12-113, 15-6
carriage return \r 6-14
case-Marke 7-20
cast-Operation 10-8
char-Datentyp 2-4, 10-3
char-Vektor 12-34
char-Zeiger 12-34
clearerr (Funktion) 14-19
close (Funktion) 14-43, 15-6
continue-Anweisung 8-47
creat (Funktion) 14-34, 15-7
creata (Funktion) 14-35
creatb (Funktion) 14-35
ctype.h 6-6
Dateien 14-3
Dateianfang (rewind) 14-31, 15-21
Dateien einfügen ( »Include) 6-3,
11-5, 11-73
Datei eröffnen 14-6
Dateiende 14-13
Datei schließen 14-12
Dateizeiger 14-4
Datentyp, char 2-3, 2-5, 10-3
double 2-3,2-5,10-6
des Rückgabewerts 11-17
-enum 16-3
-FILE 14-4
-.Int 2-3,2-5
-.float 2-3,2-5,2-7,10-6
- , long 2-5
-.short 2-5,10-3
- , unsigned 2-5, 10-7
- void 15-3
- , explizite Umwandlung 10-8
- , implizite Umwandlung 10-3
default-Marke 7-20
»define 4-1,11-70
Deklaration 11-41,11 -56
Dekrement-Operator — 3-28
A-9
Division / 3-6
do ... while-Anweisung 8-37
Doppelwort lesen/schreiben 14-20
double-Datentyp 2-5
double-Konstante 2-7
double-float Umwandlung 10-6
dqSattach (Funktion) 14-33.15-18
dqSclose (Funktion) 14-43, 15-3
dq$create (Funktion) 14-35
dqSdelete (Funktion) 14-44
dqSdetach (Funktion) 14-43
dqSopen (Funktion)
dqSread (Funktion)
dqSseek (Funktion)
dqSwrite (Funktion)
dyadische Operatoren 3-27
14-33. 15-18
14-36
14-47, 15-17
14-37, 15-26
E/A-Operationen 6-1
E/A umlenken 14-23
Einerkomplement 3-13
Eingabe umlenken 14-26
Ein- und Ausgabe am Bildschirm 6-1
Einfügen von Dateien 11 -73
Eingabe, zurückstellen (ungetc)
14-13, 15-3
einzelner Zeichen 6-4, 6-19
- , formatiert 6-19. 7-3,11 -20. 14-6
elementare Datentypen 2-7
- E/A-Operationen 14-31
elementares Lesen/Schreiben 14-35,
15-3
- Löschen von Dateien 14-44, 15-3
- Schließen von Dateien 14-12, 15-3
elementarer wahlfreier Dateizugriff
14-44, 15-3
elementares Eröffnen von Dateien
14-32, 15
olso-Anweisung 2-10. 7-3, 9-3
ttendif 11-75
Endlosschleife 8-13
enum-Datentyp 16-3
EOF 14-13,14-19
Eröffnen einer Datei 14-6
exit (Funktion) 15-7
_exit (Funktion) 14-30,15-8
exklusive ODER-Verknüpfung von
Bits 3-10
explizite Datentyp-Umwandlung 10-8
extern-Deklaration 11-32
extern-Referenz 11 -35
%f 6-12
fclose (Funktion) 14-12.15-8
Fehlercodes 14-35
fflush (Funktion) 14-13, 15-8
fgetc (Funktion) 14-13, 15-9
fget« (Funktion) 14-20, 15-9
Feld 12-3,12-28
feof (Funktion) 14-19
ferror (Funktion) 14-19
File, siehe "Datei”
FILE-Datentyp 14-4
Filepointer, siehe "Dateizeiger"
float-Datentyp 2-5, 10-6
Filedeskriptor 14-31.15-3
float-double Umwandlung 10-6
Fluchtsymbol 6-14, 12-34
fopen (Funktion) 14-6,15-9
fopena (Funktion) 14-11
fopenb (Funktion) 14-12
for-Anweisung 8-4
formale Parameter 11 -8
formatierte Ausgabe 6-15
-Eingabe 6-19
Formatierungzeichen 6-15
fprintf (Funktion) 14-23, 15-11
fputc (Funktion) 14-14, 15-11
fputs (Funktion) 14-21,15-11
fread (Funktion) 14-23, 15-11
free (Funktion) 12-114
freopen (Funktion) 14-8,15-12
freopa (Funktion) 14-6, 15-3
freopb (Funktion) 14-6,15-3
fscanf (Funktion) 14-26, 15-12
fseek (Funktion) 14-27, 15-12
ftell (Funktion) 14-31,15-13
Funktionen 11-3, 11-13
Funktionsaufruf 11-5
Funktions-Deklaration 11 -5
Funktionsname 11-5
Funktionsparameter 11-20
Funktion, rekursiv 11 -63, 12-64
Funktionsresultat 11-13
fwrite (Funktion) 14-23,15-13
geschachtelte Schleifen 8-17
getc (Funktion, Makro) 14-13
getchar (Funktion, Makro) 6-4
getl (Funktion, Makro) 14-20
gets (Funktion) 14-21,15-13
getw (Funktion) 14-19.15-14
Gleichheitsrelation == 3-9
Gleitpunktzahlen 2-7
globale Objekte 11 -27
-Variable 11-27
- Variable, Deklaration 11 -27. 12-64
- Variable. Initialisierung 11 -27.
12-64, 13-13
A-10
goto-Anweisung 8-53
Größer-Relation > 3-9
Größer gleich-Relation >= 3-9
Gültigkeitsbereich 11-27
Hexadezimale Konstante 2-9
Hierarchie von Operatoren 13-71
Höhere E/A-Operationen 14-4
if-Anweisung 7-3,11-74
«if, «ifdef «ifndef 11-74
illegale Arithmetik mit Zeigern 12-3
implizite Initialisierung 12-64
«include 6-3,11 -5, 11 -73
index (Funktion) 12-44. 15-14
Indexoperation und Zeiger 12-4, 12-10
Initialisierung 3-3,11 -27,12-64
-, automatic-Variable 3-3,12-64
-, erlaubte 12-64
implizit 12-64
-, globale Variable 11 -27,12-64
-, register 11-56
static 11 -44
-.Struktur 13-13
Vektor 12-64
Zeichen-Vektor 12-64
Zeiger 5-3
zweidim. Vektor 12-64
inklusive ODER-Verknüpfung von Bits I
3-10, 13-71
Inkrement-Operator++ 3-28
inorder 13-45
int-char Umwandlung 10-3
int-Datentyp 2-4, 10-3
int-Konstante 2-7
int-long Umwandlung 10-3
int-unsigned Umwandlung 10-3
isalnum (Makro) 15-14
isalpha (Makro)
isascii (Makro)
iscntrl (Makro)
isdigit (Makro)
islower (Makro)
isprint (Makro)
ispunct (Makro)
isspace (Marko)
isupper (Makro)
15-14
15-15
15-15
15-15
15-15
15-15
15-16
15-16
15-16
Keller 11-31
Kleiner-Relation < 3-9
Kleiner-gleich-Relation <= 3-9
Komma-Operator 8-3
Kommandoparameter 12-89
-, Anzahl (arge) 12-89
-.Vektor (argv) 12-89
Kommentar 1 -3
Komplement-Operator - 3-10
Komponente einer Struktur -> 13-30
Komponente einer Struktur. 13-5
Konstante 2-7
-char 2-4, 10-3
-double 2-5
-enum 16-3
- ganzzahlig 2-7
-Gleitkomma- 2-9
- hexadezimal 2-7
-int 2-4.10-3
- long 2-4
- oktal 2-8
- symbolisch 4-3,11 -70
- Zeichenkette 12-34, 13-3, 13-45
Kontrollzeichenkette 6-14
Kopieren von Zeichenketten 12-34,
13-3, 13-45
Lebensdauer 11-31
leere Anweisung 8-4
LIFO 11-31
«line 11-78
Links Shift-Operator < < 3-10,3-23
logical shift 3-24
logische Negation ! 3-10
- ODER-Verknüpfung II 3-12
-Operatoren 3-10
-Umkehrung! 3-10
- UND-Veknüpfung & & 3-11
-Verknüpfung 3-10
lokale Variable 11-8,11-23,11-31
long-Datentyp 2-4
long-Konstante 2-7
long-int Umwandlung 10-3
Iseek (Funktion) 14-44, 15-16
main 1-3,11-7
Makro-Definition 6-7, 6-10, 11 -71
Makros mit Parametern 6-12, 11 -72
malloc (Funktion) 12-113,15-17
Mantisse 2-7
Marken 8-53
Matrizen 12-53
mehrdimensionaler Vektor 12-53
Modul 11-27
modulglobal 11 -28
Modulo-Operator % 3-6
Multiplikation * 3-6
Name, symbolische Konstante 4-3
Variable 2-10, 11 -27, 14-48
A-11
Nassi-Schneidermann 7-5
Negation! 3-10
Null-Zeichen \O 6-14
NULL-Zeiger 12-34, 12-104
ODER-Verknüpfung (bei Bits).
exklusiv Ä 3-10
(bei Bits), inklusiv : 3-10
logisch 11 3-10
oktale Konstante 2-7, 7-20, 12-3
oktale Ziffernkombination Vxxx 6-14
open (Funktion) 14-32.15-17
opena (Funktion) 14-33
openb (Funktion) 14-33
Operator. Adreß- & 5-7,12-10
-. Addition 3-6
-.Bitkomplement ~ 3-10
-, Dekrement - - 3-28
Division / 3-6
-, Inkrement ++ 3-28
-, Komma 8-3
-. Links Shift < < 3-10
Modulo 3-6
Multiplikation ♦ 3-6
Rechts Shift > > 3-10
-. sizeof 2-3,13-32, 13-71
Strukturverweis -> 13-30
-, Strukturverweis. 13-5
-, Verweis * 5-3
Operatoren, arithmetisch 3-6
-.Bitmanipulation 3-10
-. logische Verknüpfungen 3-9,3-10
-.Relation 3-9,13-71
-, Shift 3-22
-, Vergleichs- 3-9
-. Vorrang 3-26
Zuweisung 3-3, 3-26
Präprozessor-Anweisung, »ifdef
11-74
- »ifndef 11-74
-. »include 6-3, 6-4, 11 -48,11 -73
»line 11-78
-, »undef 11-74
printf (Funktion) 1 -3, 6-14. 15-19
Priorität, Operatoren 3-26. 13-71
Programmablaufplan 7-4
programmglobal 11-30
Programmstrukturen 11-3
Prozessor-Register 11 -31
putc (Funktion. Makro) 14-14
putchar (Funktion. Makro) 6-4
putl (Funktion) 14-20
puts (Funktion) 14-22, 15-19
putw (Funktion) 14-20, 15-19
qsort (Funktion) 15-20
read (Funktion) 14-36. 15.20
Rechts Shift Operator > > 3-10, 3-24
register. Speicherklasse 2-10, 11-27,
12-64
Rekursion 11 -63
rekursive Datenstruktur 13-45
- Funktionen 11 -63
Relation, gleich = = 3-9
- , größer > 3-9
- . größer gleich >= 3-9
- . kleiner < 3-9
kleiner gleich <= 3-9
ungleich ! = 3-9
reservierte Worte 2-12
Rest nach Division % 3-3. 13-71
return-Anweisung 11-8
rewind (Funktion) 14-31, 15-21
rindex (Funktion) 12-45,15-21
Rückgabewert 11-17
Parameter 11-8
Parameterübergabe, Adresse 11 -24
-.Werte 11-27
Parameter, »define 6-4. 11-71
-.Funktion 11-20
-.Zeiger 11-20
portab.h 14-13. 14-48
Postfix (++,--) 3-29
Positionssysteme 3-10, 7-20, 12-3
Präfix (++,—) 3-29
Präprozessor 11 -70
Präprozessor-Anweisung, »define
11-70
- »eise 11-74
-.»endif 11-74
scanf (Funktion) 6-12. 6-19, 11 -27
Schleife, do ... while 8-37
-.endlos 8-13
for 8-4
- , Abfrage am Anfang 8-5, 8-26
-, Abfrage am Ende 8-37
vorzeitig abbrechen 8-43, 8-47
while 8-26
Schleifenvariable 8-6
Schlüsselwörter 2-12
Schreiben/Lesen
- von 1 Byte 14-13
-von 2 Byte 14-19
- von 4 Byte 14-20
- von Zeichenketten 14-20
A-12
Semikolon 7-3
Seitenvorschub \f 6-14
Shift nach links << 3-22
- nach rechts >> 3-22
short-Datentyp 2-4, 10-3
Sign 2-7
Significand 2-7
sizeoff-Operator 2-10, 13-32, 13-71
Sortierverfahren 12-3
Speicher reservieren 12-104
-freigeben 12-104
Speicherklasse auto 11-31,11 -36
-extern 11-32
- register 11-32,11-56
-static 11-31
sprintf (Funktion) 14-24, 15-21
sscanf (Funktion) 14-26, 15-22
Stack 11-31
Stackpointer 11 -68
Standard-Ausgabe 14-6, 14-13, 14-48
Standard-Bibliothek 6-3,11-4
Standard-Eingabe 14-6,14-13. 14-48,
15-3
static-Speicherklasse 11-31,11 -44
static-Variable, Initialisierung 12-64,
13-13
stderr (Standard-Fehler) 14-6, 14-13,
14-48. 15-3
stdin (Standard-Eingabe) 14-6, 14-13,
14-48, 15-3
stdio.h 6-3,14-48
stdout (Standard-Ausgabe) 14-6,
14-13, 14-48, 15-3
sizeof 13-32
Steuerzeichen 6-14
strcat (Funktion) 12-41,15-22
strcmp (Funktion) 15-22
strcpy (Funktion) 12-36, 15-22
String 12-34
strlen (Funktion) 12-44. 15-23
strncat (Funktion) 12-41,15-23
strncmp (Funktion) 12-42,15-23
strncpy (Funktion) 15-23
struct 13-3
Struktogramm 7-5
Struktur 13-3
Strukturname 13-4
Strukturbeschreibung 13-5
Strukturvektor 13-16
-Initialisierung 13-1
Struktur, Auswahl einer Komponente
—* 13-30
Auswahl einer Komponente . 13-5
-, Initialisierung 13-13
rekursiv 13-45
- , und Funktion 13-25
-»Varianten 13-67
- , verschachtelt 13-3
Zeiger 13-32
- , Subtraktion 3-6
switch-Anweisung 7-20
Symbolische Konstante 4-3, 11 -70
Syntaxdiagramm 2-12
Tabulatorzeichen \t 6-14
teil (Funktion) 14-48
Textersatz 11-71
toascii (Makro) 15-23
tolower (Makro) 6-6, 15-24
top down 12-104. 13-16
toupper (Makro) 6-6, 15-24
typedef 13-43
Übergabe von Adressenparametern
12-33
- von Wertparametern 11 -20
udi 14-32,15-3
Umkehrung, logischer Operator! 3-10,
13-71
Umlenken, Ausgabe 14-6
-.Eingabe 14-6
Umwandlung, arithmetische Werte
10-3
- , char-int 10-1
- , double-float 10-3
-explizit 10-8
float-double 10-3
- , Gleitkomma-Int 10-3
- , int-char 10-3
int-Gleitkomma 10-3
- , int-long 10-3
- , int-unsigned 10-3
- , long-int 10-3
unsigned-int 10-3
Umwandlungszeichen 6-15
UND-Verknüpfung & & 3-10
- (Bits) 4 3-10
ungetc (Funktion) 14-17
ungleich-Relation != 3-9
union 13-65
unlink (Funkion) 14-44, 15-24
unsigned-Datentyp 10-7,2-4
unsigned-int-Umwandlung 10-7
Unterstrich-Zeichen__ 2-10,14-48
Variable, Adresse 5-3, 6-19. 11 -20,
12-3, 13-25, 13-71
A-13
auto 11-31.1136
Datentyp 2-14,10-3, 12-3
global 11 -28
Initialisierung, auto 11 -41
Initialisierung, globale 11 -35
-. Initialisierung, static 11 -46
lokal 11 -8
-modulglobal 11-30
programmglobal 11 -30
static 11-31, 11-44
Vanablenname 2-11,2-12, 11 -58
Variabienparameter 11-20
Variabienvereinbarung 2-14,11-58
Variable, Zugriffsrechte 11 -34
Vektor 12-3
-, als Parameter 12-61
-. mehrdimensionale 12-53
Übergabe an Funktion 12-33
Vektorvereinbarung 12-3
Vektor, von Strukturen 13-16
von Zeichen 12-34
Indexoperation 12-3, 12-34,
12-53, 12-78, 12-89
Initialisierung 12-64
-, mehrdimensional 12-53
-, Zeichen- 12-34
Vektoren und Zeiger 12-3
Vereinbarung, Bitfelder 2-10, 13-65
-t extern 11 -27. 12-34, 12-64
FILE 14-4
-.Funktion 11-5
-, register 11 -27. 12-64
Speicherklasse 11-27
static 11-31. 11-44
Struktur 13-3. 13-13,13-16
- , typedef 2-10. 13-43, 14-4
-.union 2-10,13-65
-.Variable 2-14
- . Vektor 12-3, 12-53
- , Zeiger 5-3, 12-53
Vergleichsoperatoren 3-9
Verschiebung, bitweise 3-10
Verweisoperator * 5-8
-, Strukturkomp. 13-30
-, Strukturkomp., 13-5
Verzweigungen 7-3
void-Datentyp 15-3
Vorrang, Operatoren 3-26, 13-71
wahlfreier Dateizugriff 14-27
while-Anweisung 8-26,8-37
Wort lesen/schreiben 14-19
write (Funktion) 14-37. 15-25
Zahlendarstellungen 12-7
Zahlensysteme 12-7
Zeichenausgabe 6-14
Zeicheneingabe 6-4
Zeichen. Backspace Xb 6-14
Ende einer Anweisung ; 7-3
Fluchtsymbol 6-14, 12-34
-, Null-6-14
Tabulator- \t 6-14
- Unterstrich_ 2-10, 14-48
- , Zeilentrenner \n 1-3,6-14
Zeichenkette 12-34
- . Datentyp 12-34
- . Zeigervariable auf 12-35
Zeichenkettenende 12-34
Zeichenkettenlänge 12-34
Zeichenkette lesen/schreiben 14-20
Zeichenketten-Konstante 12-34
Zeichenvorrat. ASCII Anhang
Zeiger 5-1. 12-3. 12-11,12-21
-auf Funktion 12-114
- auf Struktur 13-25. 13-32
-auf Zeiger 12-78
-Initialisierung 5-3,12-3
- Übergabe an Funktion 12-33
Zeigerparameter 11 -20, 12-3
Zeiger und Index 12-3, 12-34, 12-53,
12-78, 12-89
- und Vektor 12-3
Zeiger-Differenz 12-3
Zeiger. FILE 14-3, 14-4
illegale Arithmetik 12-32
NULL - 12-34, 12-104. 14-4
- -Operationen 12-31
- -Parameter 11 -24
- -Subtraktion 12-31
--Vektor 12-3, 12-78
- -Vereinbarung 5-7
--Vergleich 12-31
Zeilennumerierung (ttline) 11 -78
Zeilentrenner \n 1-3
Zugriffsrechte auf Variable 11 -48
zusammengesetzte Zuweisungsoperato-
ren 3-26
Zuweisungen 10-4
weisungsoperatoren 3-3
eierkomplement 3-13
A-14
Dargestellt werden die wichtigen Neuheiten in ANSI C, die Programmstrategien
in C und Musterprogramme in C. Das Buch ist eine umfassende Ergänzung
zum C-Gesamtwerk. Wobei auch Lösungen für die tägliche Programmierarbeit
angeboten werden.
ANSI C
von Helmut Herold
448 Seiten, Hardcover, DM 79,—
Bestell-Nr. 62040
te-wi Verlag GmbH
Telefon 089/126992-1
Theo-Prosel-Weg 1
8000 München 40
TURBO PASCAL
IOIIIT llOlll
Turbo Pascal Programmiermethoden
Ein Praxisbuch für Leser, die bereits Grundkenntnisse in Turbo Pascal besitzen.
Anhand des Aufbaus einer Datenbank, die auch auf der beiliegenden Diskette
enthalten ist, werden die verschiedenen Programmiertechniken in Turbo Pascal
ab Version 5.5 vorgestellt, eine ausführliche Kommentierung der Listings er-
möglicht eine Anpassung auf individuelle Bedürfnisse.
Hier lernen Sie das Programmieren mit Methode, objektorientiert und strukturell.
von Robert Grübel
304 Seiten, Hardcover, DM 59,—
inkl. 1 Diskette 5 1/4", 1,2 MByte
Bestell-Nr. 62064
Objektorientierte und modulare Softwareentwicklung mit Turbo Pascal 5.5
Der Übergang vom Programmieren zur Entwicklung von Softwaresystemen ist
ein Übergang zu neuen Arbeitstechniken.
Das Buch beschreibt, wie die Aspekte der Softwareentwicklung - Objekt-
orientierung und Modularisierung — unter Turbo Pascal 5.5 realisiert werden.
von Klaus-Dieter Thies
160 Seiten, Hardcover, DM 59,—
Bestell-Nr. 62054
tarn
te-wi Verlag GmbH
Telefon 089/126992-1
Theo-Prosel-Weg 1
8000 München 40
Die Zukunft liegt in UNIX:
Die neue UNIX Buchreihe, die nach und nach das ganze Gebiet UNIX kompetent und
umfassend abdeckt. Ein Muß für jeden UNIX-Anwender und für jeden, der es werden will.
Den Anfang macht der leicht verständliche Einstieg in die effektive Shell-Programmierung
der Boume- und Korn-Shell. Auch Subshells und Shell-Varianten von UNIX System V
werden ausführlich beschrieben.
irtnia Hiiictmi
UNIX Shell-Programmierung
von Stephen Kochan und Patrick Wood
512 Seiten, Hardcover, DM 79,—
Bestell-Nr. 62084
UNIX Systemverwaltung
von David Fiedler und Bruce Hunter
ca. 400 Seiten, Hardcover, DM 79,—
Bestell-Nr. 62085
urm
UNIX Systemsicherheit
von Patrick Wood und Stephen Kochan
ca. 400 Seiten, Hardcover, DM 79,—
Bestell-Nr. 62086
UNIX C-Programmierung
von Bernhard Davignon
ca. 600 Seiten, Januar 1991, Hardcover,
DM 79,-, Bestell-Nr. 62087
te-wi Verlag GmbH
Telefon 089/1269S2-1
Theo-Prosel-Weg 1
8000 München 40
PC/XT/AT ASSEMBLER-BUCH
PC/XT/AT
ASSEMBLER-BUCH
Alk CPU-BefeMe
B0*b
so'»s
so»»7
SO»»6
80®7
SO'»6
so*»6
S03»7
Hier eine anspruchsvolle Darstellung der Assemblerbefehle und
Assembler-Sprachübersetzer zu der gesamten INTEL-Familie 8086,
8087, 8088, 80186. 80188, 80286, 80287, 80386, 80387.
Das Buch erklärt die Wirkung der Assemblerbefehle durchgehend
anhand von anschaulichen Speicherdiagrammen und Anwendungs-
beispielen. Ein Werk für PC/XT/AT-Assemblerprogrammierer und
ASM/MASM-Systemprogrammierer.
Von Klaus-Dieter Thies
656 Seiten, Hardcover, DM 98,-
NUMERIK-I
PC/XT/AT-Numerik-Buch
Hochgenaue Gleitpunktarithmetik mit 8087/80287/80387 unter
Nutzung mathematischer Bibliotheksfunktionen in ’C* und "Assembler".
Zeigt CPU-/Coprozessor-Hardware; das lEEE-System hochgenauer Zah-
lendarstellungen; die 80X87-Programmierung und Fehlerbehandlung
sowie die 58 Bibliotheksfunktionen reeller und komplexer Argumente.
Von Klaus-Dieter Thies
728 Seiten, Hardcover, DM 98,-
Die innovativen 80286/80386-Arch‘rtekturen
Teil!: Der 80286
Exakte Analyse für Bedürfnisse von Entwicklern und
PC-Systemprogrammierern. Themen: virtuelle Adressierung;
Systemschutz; Multitasking; MM; 8087 Management;
ASM/BLD 286-Anwendungen
Von Klaus-Dieter Thies
320 Seiten, Hardcover, DM 79,-
DIE INNOVATIVEN 80286/80386 ARCHITEKTUREN
Teil 2: Der 80386
Zeigt detailliert die neu hinzugekommenen
32-Bit-Operationen. Behandelt Probleme realer
80386-Programmierung. Keine bloße Befehlssammlung!
Von K.-D. Thies
560 Seiten, Hardcover, DM 89,—
fern
Theo-Prosel-Weg 1
8000 München 40
te-wi Verlag GmbH
Telefon 089/126992-1
M Melber
- Disketten zu C-Gesamtwerk -
Jaminstraße 39
D-8520 Erlangen
Ich bestelle hiermit zur Lieferung per Rechnung
___Diskette(n) "Programmierbeispiele C-Gesamt-
werk” (Betriebssystem MS-DOS) zum Preis von
DM 29.90 pro Stück.
Anschrift:
Datum:
Unterschrift:
Zentral- und Landesbibliothek Berlin
Nll<18145759109
IIIIIHIIIIIIIIIII -
”C” Gesamtwerk
Das Buch behandelt unge-
wöhnlich anschaulich sämtliche
C-Konstrukte, Sprachkonzepte
und C-Implementierungen.
Es ist sowohl als Lehrbuch als
auch zum Selbststudium für
Einsteiger geeignet, denn jedes
einzelne Sprachelement wird
anhand von Beispielen und
verständlichen Erläuterungen
erklärt. Über 100 praxisnahe
Beispiellistings zeigen die Reali-
sierung moderner Programmier-
strategien in C.
Die angegebenen Programm-
beispiele entsprechen dem von
Kernighan und Ritchie vorgege-
benen Standard und laufen unter
den Betriebssystemen MS-DOS,
CP/M-86, ISIS-II. UNIX und
damit verwandten Systemen.
Ein Anhang über Ansi C be-
schreibt den neuen C-Standard.
»Das C-Gesamtwerk von Herold
und Unger ist wirklich »»das« C-
Buch. Es ist [...] nicht nur quan-
titativ [...] der Spitzenreiter,
sondern auch, was Ausführlich-
keit und Beispielfülle anbe-
langt.« (UNIX-Magazin, 1/1990)
Die Autoren, Helmut Herold
und Werner Unger, arbeiten in
der Industrie an Systempro-
grammierungen zukünftiger
Computerarchitekturen. Das
Buch entstand aufgrund ihrer
Erfahrungen im Schulungs-
bereich für Ingenieure und
C-Anfänger. Ebenfalls bei te-wi
erschienen ist das Buch
»»Ansi C« von Helmut Herold.
C ist aufgrund der einfachen
Ausdrucksweise, der leichten
Handhabung von Zeigern, der
großen Auswahl von Operatoren
und der modernen Programm-
und Datenstrukturen eine der
am meisten verwendeten
Programmiersprachen.
ISB N 3-89362-015-X
4005739 620150
sFr 74,—/öS 616,- DM 79,-