ANSI C - TU Berlin

Werbung
Kurzskript
zum Thema:
ANSI C
Alexander Rehbein
Prof. Jörn Sesterhenn
20. November 2012
Stanford CS Education Library
Dieses
Kurzskript basiert auf dem frei erhältlichen Dokumenten #101, Essential C sowie #102, Pointers
and Memory der Stanford CS Education Library,
welche zusammen mit anderen Dokumenten unter
http://cslibrary.stanford.edu frei erhältlich sind.
1
Vorwort
Dieses C-Kurzskript ist als Begleitmaterial für die Lehreveranstaltung Einführung
in die Informationstechnik für Ingenieure (EDVI) bei Prof. Sesterhenn an der TU
Berlin zu sehen. Es sollen alle bedeutenden Themen in der relevanten Tiefe enthalten
sein. Ggf. ist dieses Skript etwas ausführlicher, um das Verständnis zu fördern.
Zusammen mit den unten erwähnten Kurzskripten bilden Programming in ANSI C
1
2
von E . Balagurusamy, The C Programming Language von Kernighan und Ritchie
sowie Wikipedia den gesamten Quellliteraturbestand. Zum Teil steht hier auch nur
die halbe Wahrheit, da die ganze Wahrheit zu umfassend für diese Vorlesung wäre.
Dieses Skript ist kein Ersatz für den Gang zu Vorlesung, Übung oder
Tutorium! Es soll als Lern- und vor allem Verständnisstütze dienen.
Inhaltsverzeichnis
1
Vorwort
1
2
Einführung
5
2.1
3
4
Motivation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.1.1
Wichtiger Hinweis vorab . . . . . . . . . . . . . . . . . . . . .
5
2.1.2
Der erste Quelltext . . . . . . . . . . . . . . . . . . . . . . . .
5
Datentypen, Konstanten, Variablen und Operatoren
6
3.1
Grundlegende Datentypen . . . . . . . . . . . . . . . . . . . . . . . .
6
3.2
Konstanten
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.3
Variablen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
3.4
Kommentare
3.5
Operatoren
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
3.5.1
Zuweisungs-Operator `=`
3.5.2
Mathematische Operatoren
3.5.3
Einserinkrement/-dekrement-Operatoren ++ --
3.5.4
Vergleichs-Operatoren
3.5.5
Logische Operatoren
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
11
. .
12
. . . . . . . . . . . . . . . . . . . .
13
. . . . . . . . . . . . . . . . . . . . .
13
Kontrollstrukturen
14
4.1
Geschweifte Klammern
4.2
Verzweigungen
4.3
10
. . . . . . . . . . . . . . . . . . . . . . . . .
15
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
4.2.1
Die
if-Anweisung
4.2.2
Die
switch-Anweisung
. . . . . . . . . . . . . . . . . . . . . . . .
15
. . . . . . . . . . . . . . . . . . . . . .
16
Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
4.3.1
Die
while-Anweisung
4.3.2
Die
do-while-Anweisung .
4.3.3
Die
for-Anweisung
. . . . . . . . . . . . . . . . . . . . . .
17
. . . . . . . . . . . . . . . . . . . .
17
. . . . . . . . . . . . . . . . . . . . . . . .
17
3
5
6
7
Pointer
18
5.1
Der Adress-Operator
&
. . . . . . . . . . . . . . . . . . . . . . . . . .
18
5.2
Der Inhalts-Operator
*
. . . . . . . . . . . . . . . . . . . . . . . . . .
20
Array von Elementen vom Datentyp
<Datentyp>
21
6.1
Der Name eines Arrays . . . . . . . . . . . . . . . . . . . . . . . . . .
23
6.2
Mehrdimensionale Arrays
. . . . . . . . . . . . . . . . . . . . . . . .
24
6.3
Strings
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
Funktionen
26
7.1
Funktionsdenition . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
7.1.1
Der Funktionsheader . . . . . . . . . . . . . . . . . . . . . . .
27
7.1.2
Der Funktionskörper . . . . . . . . . . . . . . . . . . . . . . .
29
7.2
Funktionsaufrufe
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
7.2.1
Funktionsdeklaration . . . . . . . . . . . . . . . . . . . . . . .
29
7.2.2
Der Funktionsaufruf
. . . . . . . . . . . . . . . . . . . . . . .
31
7.2.3
Call by Value
. . . . . . . . . . . . . . . . . . . . . . . . . .
33
7.2.4
Call by Reference . . . . . . . . . . . . . . . . . . . . . . . .
34
A Schlüsselwörter
35
B Einige der Standardfunktionen aus
B.1
B.2
B.3
Bildschirmausgaben printf
stdio.h
36
. . . . . . . . . . . . . . . . . . . . . .
36
B.1.1
Zeichenketten mit Platzhalter der 1. Parameter von
printf
37
B.1.2
Zeichenketten mit Platzhalter der 2. Parameter von
printf
38
B.1.3
Wie die Zeichen ausgegeben werden
. . . . . . . . . . . . . .
40
fgets . . . . . . . . . . .
40
Einlesen in
char-Arrays
Einlesen in
char-Arrays oder Variablen von Strings oder char-Arrays
von der Tastatur sscanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
char-Arrays
B.4
Einlesen in Variablen oder
B.5
Önen und Schlieÿen von Dateien FILE*
40
von Tastatur . . . . . . . . .
41
fopen und fclose . . . . . . . .
43
B.5.1
Der Datentyp
. . . . . . . . . . . . . . . . . . . . . .
43
B.5.2
fopen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
B.5.3
fclose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
4
fprintf . . . . . . . . . . . . . . . . . . . . . .
B.6
Ausgabe in Dateien B.7
Einlesen von Dateien fscanf
. . . . . . . . . . . . . . . . . . . . .
45
46
5
2
2.1
Einführung
Motivation
Die Programmiersprache C wurde vor mittlerweile mehr als 20 Jahren von Kernighan und Ritchie erstmalig in ihrem Buch The C Programing Language deniert und daraufhin 1989 erstmalig vom American National Standards Institute
als ANSI-Standard adoptiert. Seitdem sind einige neuere C-Standards eingeführt
worden; zum Erlernen der Grundlagen reicht es jedoch, sich an den einschlägigen
ANSI-Standard aus dem Jahr 1990 zu halten. Die Neuerungen seitdem sind für Anfänger wohl eher uninteressant.
Für diejenigen, welche sich fragen, wofür sie C mal gebrauchen können: Bei eingebetteten Systemen, in der Systemprogrammierung und nicht zuletzt in der OpenSource-Programmierung wird die C-Programmiersprache gerne genutzt. Ein Vorteil
ist, dass sie auf verschiedenen Rechnerarchitekturen leicht zu implementieren ist sie sollte per Design leicht kompilierbar sein. Dies ist wohl mit einer der Gründe,
warum C zu den am weitesten verbreiteten Programmiersprachen zählt. Gleichzeitig orientiert sich die Syntax sehr vieler Programmiersprachen an jener von C.
Das heiÿt, wer einmal C gelernt hat, wird sich zumindest von der Syntax einiger
neuer Programmiersprachen nicht allzu schnell verwirren lassen und ggf. ein paar
Anweisungen schon verstehen, ohne die Sprache zu kennen.
2.1.1
Wichtiger Hinweis vorab
Manche Programmiersprachen können leicht genutzt werden, auch wenn man kaum
versteht, was man schreibt. Man schreibt ein paar Zeilen, schaut, ob es funktioniert
- und wenn nicht, dann probiert man nochmal ein bisschen rum, bis es endlich
klappt. Ggf. korrigiert der Compiler bestimmte Fehler schon von selbst oder die
Fehler werden behoben, während das Programm läuft (man nennt diese Zeitspanne
auch Laufzeit ). Für C ist das wohl eine der schlechtesten Herangehensweisen. C ist
eine Programmiersprache, die davon ausgeht, dass der Programmierer genau weiÿ,
was er tut. Daher ist sehr viel erlaubt und vieles, was man gar nicht möchte, passiert
einfach.
Genau aus diesem Grund ist C schwer für Anfänger. Daher ist der beste Ratschlag,
den man bei C geben kann, nichts einzugeben, wovon man nicht weiÿ, was es tut.
Die Fehlersuche (das sog. Debugging ) kann sehr mühselig sein und daher ist es
besser, sich vorab beim Schreiben der jeweiligen Anweisungen etwas mehr Zeit zum
Überlegen zu nehmen.
Da im weiteren Verlauf des Textes erstmal viel (wichtige) Theorie behandelt wird,
ohne dass man sie sofort ausprobieren könnte, folgt:
2.1.2
Der erste Quelltext
Ein C-Programm wird erstellt, indem man Quelltext schreibt und diesen dann in
eine ausführbare Datei übersetzt. Quelltext entspricht vom Datenformat her einer
gewöhnlichen Textdatei, in welcher die Anweisungen stehen, welche der Computer
ausführen soll. Was für Anweisungen es gibt, wann und wie man diese schreibt etc.,
das soll in diesem Skript erklärt werden. Hier das Beispiel es sei nahe gelegt, dieses
abzutippen und in einer Textdatei zu speichern:
6
#i n c l u d e < s t d i o . h>
1
2
i n t main ( void ) {
3
p r i n t f ( " Ich
4
5
return 0 ;
6
}
bin
eine
Z e i l e \n" ) ;
Quelltext 1: Ein erstes Beispiel
In ANSI-C werden fast alle Anweisungen in Funktionen zusammengefasst. Damit
die Anweisungen einer Funktion durchgeführt werden, muss die Funktion ausgeführt
bzw. aufgerufen werden. Funktionsaufrufe selbst sind Anweisungen. Damit immer
klar ist, welche Anweisungen zuerst durchgeführt werden, ist in C festgelegt, dass
zu allererst die Funktion
main
aufgerufen wird. Diese muss in jedem C-Programm
deniert sein!
Diese Denition geschieht hier durch die Zeilen 3 bis 6, wobei der Text zwischen
dem geschweiften Klammerpaar, welches in Zeile 3 geönet wird, die Anweisun-
main-Funktion festlegt. Zeile 4 beinhaltet den Befehl, die Buchstabenkette
Ich bin eine Zeile mit einem darauf folgenden Zeilenumbruch darzustellen (dafür steht das '\n'), denn automatisch geschieht das hier nicht.
gen der
Dieser Befehl oder genauer die verwendete Funktion muss jedoch irgendwo deniert
sein, da er keine elementare Anweisung der Programmiersprache C darstellt. Die
Anweisung in Zeile 1 sorgt dafür, dass ein vorgeschriebener Quelltext (die Datei
stdio.h), welcher diese Denitionen mitbringt,
return 0; berichtet dem Betriebssystem, dass
eingebunden wird. Die Anweisung
das Programm fehlerfrei durchge-
laufen ist (sofern die Zeile des Quelltextes auch ausgeführt werden konnte).
Wichtig: Hinter jeder Anweisung (also auch jedem Funktionsaufruf ) muss ein Semikolon stehen um das Ende der Anweisung bzw. des Funktionsaufrufes zu markieren.
Auf einem Linux-basierten Betriebssystem kann man den Quelltext 1 mit folgendem
Befehl in der Kommandozeile in die ausführbare Datei
gcc <Quelltextdatei>
−o
und dann durch Eingabe von
programmEins
übersetzen
programmEins
programmEins ausgeführt werden. Das Zeichen `
' soll
dabei andeuten, dass an dieser Stelle ein Leerzeichen stehen muss.
Hinweis: Für das gesamte Skript gilt genau wie bei allen Unterlagen zur EDV1,
Namen in eckigen Klammern müssen für die Implementierung durhc sinnvolle Werte
ersetzt werden.
3
Datentypen, Konstanten, Variablen und Operatoren
3.1
Grundlegende Datentypen
Wer programmiert, möchte Daten bzw. Werte durch den Computer verarbeiten,
bearbeiten und erstellen lassen dieser kennt wiederum nur jene Informationen aus
dem ihm zur Verfügung stehenden Speicher. Dort müssen also die zu verarbeitenden
Werte gespeichert sein.
7
Jeder Speicherbereich im Computer kann logisch als einfache, endliche Folge von
Bits betrachtet werden. Bits
1 sind Unbestimmte, welche die Werte Eins oder Null
annehmen können. Worauf es ankommt, ist, wie diese Einsen und Nullen interpretiert werden. Diese Vorgabe wird gemacht, indem man Datentypen zuordnet.
C kennt nur ein paar grundlegende Datentypen (aus diesen kann man sich bei Bedarf
jedoch komplexere Datenstrukturen basteln). Wichtig sollen erstmal nur folgende
sein:
int2
Das C-Standardformat für Ganzzahlen. Je ein
int wird in mindestens 16 Bits
gespeichert. Normal sind 32 Bits.
char3
Dieser Datentyp steht für Buchstaben aus der ASCII-Kodierung.
float
Der Datentyp für Gleitkommazahlen
5 In der Regel wird ein
6
Bits gespeichert. Die Genauigkeit liegt bei circa 6 Ziern.
double
4
float
in 32
Der Datentyp für Gleitkommazahlen mit doppelter Genauigkeit ; es stehen
also normalerweise 64 Bits zur Verfügung. Die Genauigkeit liegt bei circa 15
6
Ziern.
Hinweis: Wie viel Speicherplatz zum jeweiligen Datentypen gehört, hängt vom Compiler dem Programm, mit welchem der vom Programmierer geschriebene Quelltext
(dieser ist eine gewöhnliche Textdatei) in eine ausführbare Datei (diese ist im allgemeinen keine gewöhnliche Textdatei) übersetzt wird ab.
Der ANSI-Standard legt für die Gröÿe der obigen Datentypen keine genauen Zahlen, sondern nur Untergrenzen fest. Dadurch ist es einfacher, C-Compiler auf vielen
verschiedenen Hardwaresystemen umzusetzen. Deshalb ist für den Programmierer
besondere Vorsicht geboten, wenn sein Programm auf verschiedenen Systemarchitekturen laufen soll. Wie man diese Probleme umgehen kann, soll hier nicht weiter
behandelt werden.
Zu den Datentypen
float und double sollte man sich merken, dass sie alles andere
float- oder double-Werten
als genau sind. Daher sollte man bei Vergleichen von
7
nie auf Gleichheit prüfen es sei auf die Vorlesung verwiesen.
3.2
Konstanten
Konstanten sind feste Werte. Abstrakt besteht kein Erklärungsbedarf dazu, was
ein Wert ist; in der Programmierung sollte man wissen, dass ein Wert aus einer
endlichen Abfolge von Einsen und Nullen (bzw. `gesetzten' Bits) zusammen mit der
1 Abkürzung für `binary digit' (engl.: binäre Zier)
2 Abkürzung für `integer' (engl.: Ganzzahl)
3 Abkürzung für `character' (engl.: Buchstabe)
4 Es handelt sich um Ganzzahlen, welche nur für Ein- und Ausgaben auf dem Bildschirm über die
ASCII-Codierung ein Buchstabe zugeordnet wird. Man kann also theoretisch in C mit Buchstaben
rechnen. Je ein
5 Dieser
char
wird in einem Byte, also acht Bits, gespeichert.
Ausdruck beschreibt die Tatsache, dass bei Gleitkommazahlen die signikanten Ziern
einer Zahl unabhängig davon, wo das Komma liegt, im Speicher abgelegt sind. Wo das Komma,
dann hingehört, wird in den vorhergehenden Bits festgelegt das Komma `gleitet' also in die
Ziern rein.
6 Diese
werden nicht ab dem Komma, sondern ab der ersten Zier die ungleich Null ist von links
gezählt.
7 Statt
direkt die Gleichheit zu überprüfen, würde man in der Praxis schauen, wie weit zwei
Gleitkommazahlen auseinander liegen. Würde der Unterschied klein genug ausfallen, könnte man
die Gleichheit annehmen und den Unterschied der mangelhaften Rechengenauigkeit zuschieben.
8
Vorgabe, wie diese interpretiert werden (also dem Datentypen) besteht. Eine Folge
gesetzter Bits alleine legt noch keinen Wert fest, es gehört immer ein Datentyp dazu!
Kurz gesagt: Ein fester Wert, oder auch eine Konstante besteht aus einer endlichen
Folge gesetzter Bits und dem zugehörigen Datentypen. Aus den in Abschnitt 3.1
gelisteten Datentypen entstehen:
char-Konstanten: char-Konstanten
stehen immer in einzelnen Anführungszei-
chen. Zum Beispiel wäre das Alphabet (in Konstanten geschrieben):
'a'
'b'
'c'
'y'
...
'z'.
Dabei wird zwischen Groÿ- und Kleinbuchstaben unterschieden, d.h.
nicht gleich
'a'.
'A'
ist
Zu den Buchstaben werden auch Sonderzeichen gezählt, wie
z.B.
Zeilenumbrüche:
Tabstops:
'\n'
8
'\t'
das Nullzeichen:
'\0'. Es handelt sich dabei nicht um die Null als Buch'0'). Vielmehr ist das Nullzeichen '\0'
staben (das wäre die Konstante
ein Kontrollzeichen, welches das Ende von Zeichenketten markiert aber
dazu später mehr.
int-Konstanten:
Diese Konstanten werden in der gewöhnlichen Dezimalschreib-
weise geschrieben, wobei es sich natürlich um ganze Zahlen handeln muss, also
z.B.:
-3
-2
-1 0
1
2
3.
Gleitkommazahl-Konstanten: Konstanten für Gleitkommazahlen werden fast
wie in der gewöhnlichen Dezimalschreibweise geschrieben. Jedoch wird der
angelsächsischen Konvention entsprechend statt eines Kommas ein Punkt
double-Konstante. Möchte man stattdessen
float-Konstante, so ist ein `f ' dahinterzuschreiben. Beispielsweise würde
man für die Zahl 2, 71
gesetzt. Dann ist die Zahl eine
eine
2.71
double. Möchte man unbedingt eifloat mit float-Werten arbeitet man eigentlich nur,
wenn die verfügbare Hardware zu wenig Speicher zum Arbeiten mit doubleWerten hat so schreibt man stattdessen 2.71f. Es sei nochmals angemerkt,
schreiben, das ist eine Konstante vom Typ
ne Konstante vom Typ
dass Gleitkommazahlen im allgemeinen ungenau sind (siehe weiter oben).
Zum Beispiel kann die Zahl
0.4 nicht
exakt als
float dargestellt werden. Dies
soll jedoch nicht weiter Thema des Skriptes sein. Hier sei auf die Vorlesung
zur Zahlen- und Zeichendarstellung verwiesen.
3.3
Variablen
Der Begri `Variable' erweckt in der Regel erstmal eine mathematische Assoziation.
Diese sollte man jedoch zunächst vergessen. In ANSI-C (und in den meisten anderen Programmiersprachen) ist eine Variable nichts weiteres, als ein während der
8 `n'
wie newline
9
Ausführung des Programmes
9 reservierter Speicherbereich, dem ein Datentyp und
ein Name zugeordnet wurden.
Hat man eine Variable, welche auf einem der grundlegenden Datentypen
10 basiert,
dann kann man den aktuellen Inhalt dieses Speicherbereiches eine endliche Folge
von Einsen und Nullen über den Namen der Variablen ansprechen. Dieser Inhalt
wird dann dem Datentypen der Variablen gemäÿ interpretiert. Über den Namen der
Variablen erhält man also einen Wert
11 .
Erstellen (man sagt oft denieren ) kann man sich Variablen der grundlegenden
Datentypen wie folgt:
<Datentyp> <Name>;
Dies geschieht in der
main-Funktion oder innerhalb der jeweiligen anderen Funktion.
Die Variable wird dann zunächst nur der Funktion, in welcher sie deniert wurde,
12
bekannt sein.
Der
<Name>
der Variablen muss ein sogenannter Bezeichner sein siehe Abbildung
1. Es handelt sich dabei um einen durch den Programmierer denierten Namen,
welcher aus einer Abfolge von Buchstaben und Ziern besteht, wobei Unterstriche
auch erlaubt sind diese werden gerne als Ersatz für Leerzeichen genutzt, welche
nicht erlaubt sind. Es müssen alle Regeln aus Abbildung 1 erfüllt sein. Groÿ- und
Kleinbuchstaben werden unterschieden!
1. Das erste Zeichen muss ein lateinischer Buchstabe, eine Zier oder ein Unterstrich sein.
2. Die Zeichenfolge darf nur lateinische Buchstaben, Ziern oder Unterstriche
enthalten.
3. Maximal 31 Zeichen. Alles ab dem 32. Zeichen wird ignoriert.
4. Darf kein Schlüsselwort
a
sein.
5. Darf kein Leerzeichen enthalten.
a Eine
Liste von Schlüsselwörtern bendet sich in Anhang A.
Abbildung 1: Regeln für Bezeichner
Es folgt ein Beispiel mit dem Datentypen
int
und dem Wort `zahl' als Namen:
int zahl ; .
Andere gültige Beispiele sind:
char c;
char platzhalter ;
char Platzhalter;
oat f ;
double z;.
9 dieser Zeitraum wird auch Laufzeit bzw. im Englischen runtime genannt
10 Siehe Abschnitt 3.1
11 siehe Abschnitt 3.2
12 In ANSI C gibt es weitere Variablentypen, die aber den Rahmen von der EDV1
Veranstaltung
sprengen würden. Für interessierte Studenten sei auf die Literatur zum Thema scope verwiesen
10
Was mit diesen Anweisungen bezweckt wird, sollte sich der Leser an Hand der bisherigen Ausführungen verständlich machen können. Dies sei zur Übung empfohlen.
Insbesondere mache man sich klar, warum die Variable
zur Variablen
Platzhalter
platzhalter nicht identisch
ist.
Nach der Denition einer Variablen kann bei den grundlegenden Datentypen ihr
Name als Platzhalter für ihren aktuellen Wert aufgefasst werden.
Wichtig: Man beachte, dass nach einer Variablendenition nicht klar ist, welchen
Wert die Variable hat! Es wurde ein Speicherbereich reserviert, aber nicht beschrieben; d.h. in dem Speicherbereich steht irgendeine Sequenz von Einsen und Nullen,
welche unbekannt ist.
Es ist ein typischer Anfängerfehler, sich eine Gleitkommazahl- oder Ganzzahlvariable zu denieren und später dann anzunehmen, der Wert sei 0. Dies ist jedoch im
allgemeinen nicht gegeben; man muss der Variable diesen oder ggf. einen anderen
ersten Wert zuweisen; sie initialisieren. Wie man dies macht, wird im Folgenden
noch beschrieben.
3.4
Kommentare
In den nächsten Abschnitten kommen Code-Beispiele, welche Kommentare enthalten, das heiÿt Text, welcher Anmerkungen des Programmierers enthält, aber beim
Übersetzen des Quelltextes in eine ausführbare Datei ignoriert wird. Kommentare
benden sich zwischen den Zeichenpaaren
/*
und
*/
nach dem Schema
/* beliebiger Kommentar */
Achtung: Viele Compiler erlauben einzeilige Kommentare mit
C++-Syntax
//
obwohl diese
sind und nicht zu ANSI-C gehören.
//Kommentar
Kommentare sind zusammen mit gut gewählten Variablen- und Funktionsnamen ein
wichtiger Bestandteil von gutem Programmierstil. Der Zweck ist nicht, die einzelnen
Anweisungen in eigene Worte zu übersetzen.
13 Vielmehr geht es darum, komplizierte
Stellen zu erläutern oder das, was nicht oensichtlich ist, hervorzuheben.
3.5
Operatoren
Operatoren sind Grundbausteine von Anweisungen.
3.5.1
Zuweisungs-Operator `=`
Der Zuweisungs-Operator ist das einzelne Gleichheitszeichen `=` . Eine komplette
Zuweisungsanweisung sieht so aus:
<Variable> = <2. Variable oder beliebige Konstante>;
wobei die erste und die zweite Variable identisch sein können. Der ersten Variablen
wird dann der Wert des Speicherbereiches der zweiten Variablen bzw. die Konstante
auf der rechten Seite zugewiesen. Dabei müssen die Datentypen auf beiden Seiten
13 Anfängern
kann dies natürlich beim Lernen der Sprache helfen. Es sollte jedoch keine Dauer-
angewohnheit darstellen.
11
des Operators zueinander passen.
Wichtig: Der Operator `=` ist nicht zu verwechseln mit dem Gleichheitszeichen in der
Mathematik. So ist eine Anweisung
i = i + 1
mathematisch falsch, als Operator
in der Programmierung aber sehr verbreitet.
1
2
3
4
i n t i , j ; / * m e h r e r e V a r i a b l e n vom g l e i c h e n D a t e n t y p * /
char c ;
double f ;
f l o a t I n i = 0 ; /* V a r i a b l e b e i d e r D e f i n i t i o n i n i t i a l i s i e r e n
*/
5
6
/*
Leerzeilen ,
Leerzeichen ,
im C− Q u e l l t e x t
werden
7
i
mit
Tabs und
Zeilenumbrueche
w e n i g e n Ausnahmen
beliebig
koennen
gesetzt
*/
= 4;
/*
8
j =
i ;
9
c =
'x ' ;
10
f =
2.45;
j
wird
damit
den Wert 4
haben
*/
Quelltext 2: Beispiele von Zuweisungen
Hinweis: Der Zuweisungsoperator zusammen mit beiden Operanden ist ein Ausdruck, welcher den zugewiesenen Wert zurückgibt. Das heiÿt, der Zuweisungsoperator mit seinen beiden Operanden kann im Quelltext wie eine Konstante vom
Datentyp des zugewiesenen Wertes genutzt werden. Sie sollte aber in Klammern
gesetzt werden, z.B. mit den Variablen aus Quelltext 2 so:
j = ( i = 4 ) ;
Damit erreicht man das gleiche für die Variablen
sungen betreend
i
und
j
i und j wie in den beiden Anwei-
in Quelltext 2. Allerdings ist die Variante in Quelltext
2 uneingeschränkt vorzuziehen, da sie übersichtlicher ist. Dieses Beispiel soll nur
zeigen, was das `Zurückgeben' bzw. die `Rückgabe' eines Wertes bedeutet.
Wie schon zuvor erwähnt, haben Variablen, nachdem sie deniert wurden, keinen
14 Variablen der grundlegenden Datentypen kann
Wert, welcher uns bekannt ist.
man entsprechend Zeile 4 in Quelltext 2 gleich bei der Denition initialisieren oder
getrennt von der Denition in den darauolgen Zeilen vom Wert her festlegen. Dies
sollte man immer tun, denn dies erleichtert die Fehlersuche.
Mathematische Operatoren
3.5.2
Es gibt folgende mathematische Operatoren, mit welchen Zahlenwerte verknüpft
werden können:
+
Addition
-
Subtraktion
*
Multiplikation
/
Division
14 Siehe
S. 10.
12
Verknüpft man Werte mit unterschiedlichen (Zahl-)datentypen mit diesen Operatoren, so macht der Compiler aus dem Wert mit dem `niederen' Datentypen den
entsprechenden Wert des `höheren' Datentypen und rechnet mit diesem weiter. Die
Hierarchie, aufsteigend von links nach rechts, ist:
char, int , oat , double.
Wichtig: Der Divisionsoperator verhält sich je nach Datentyp der Operanden un-
int-Werte, so wird eine Integer-Division durchgeführt,
7/4 gibt dann den Wert 1 zurück, da
die Vier einmal ganz in der Sieben enthalten ist, analog gibt der Ausdruck 1/3 den
terschiedlich. Teilt man zwei
d.h. eine Division ohne Rest: Der Ausdruck
Wert 0 zurück, da die Drei kein Teiler der Eins ist.
Möchte man die `gewöhnliche' Division durchgeführt haben, so muss mindestens einer der Operanden eine Gleitkommazahl-Konstante sein. Dies erreicht man dadurch,
dass man die Zeichen
.0
oder nur einen Punkt hinter die ganze Zahl schreibt. Also
z.B.
1.0/3
oder
1./3
an Stelle von
3.5.3
1/3.
Einserinkrement/-dekrement-Operatoren ++ --
Bei der Programmierung nutzt man Integer-Variablen oft zum Zählen. In diesem
Zusammenhang möchte man häug den Wert eines
sei
i
ein
int):
int um den Wert 1 erhöhen (es
i = i + 1 ; /* Zuweisung und Additionsoperator zusammen */
Alternativ kann man auch den Operator für das Einserinkrement nutzen, das spart
Schreibarbeit:
i ++ ;
Weitere derartige Operatoren und was sie bewirken:
1
/*
v a r = v a r + 1 * nach *
Benutzung
des
Wertes
von
v a r : */
Benutzung
des
Wertes
von
v a r : */
*
Benutzung
des
Wertes
von
v a r : */
*
Benutzung
des
Wertes
von
v a r : */
v a r ++;
2
3
4
/*
var = var
−
1 * nach *
v a r −−;
5
6
7
/*
var = var + 1 * vor
++v a r ;
8
9
10
/*
var = var
−
1 * vor
−−v a r ;
11
12
definiere
und
int
i =4
j =0 ,
15
j =
i ++;
16
j_ = ++i ;
13
14
/*
,
initialisiere
i , j , j_ * /
j_ =0;
j
hat
nun den Wert
4,
i
den Wert 5
*/
/ * j_
/*
hat
nun den Wert
5,
i
den Wert 5
*/
13
3.5.4
Vergleichs-Operatoren
Mit diesen Operatoren kann man Werte von Zahlendatentypen sowie
char-Werte
untereinander vergleichen. Ist der entstehende Ausdruck wahr, so wird an Stelle des
Ausdrucks der Integer-Wert 1 zurückgegeben, ansonsten eine 0.
==
gleich
!=
ungleich
<
kleiner
>
gröÿer
<=
kleiner oder gleich
=>
gröÿer oder gleich
Wichtig: `=` (Zuweisung) und `==` (Vergleich) sind unbedingt auseinanderzuhalten!
Beide zu verwechseln ist eine typische Fehlerquelle beim Programmieren. Eine genauere Erklärung des resultierenden Problems folgt später.
3.5.5
Logische Operatoren
In ANSI-C gibt es wie in anderen Programmiersprachen eine
1
if
( <l o g .
Ausdruck> )
<Anweisung ( en )
2
if-Anweisung:
{
1>
3 }
4
else {
<Anweisung ( en )
5
1>
6 }
Die
<Anweisung(en) 1>
wird/werden dabei ausgeführt, wenn der logische Aus-
druck wahr ist, ansonsten werden die
Anweisunge(en) 2
ausgeführt. Als logischer
Ausdruck kann irgendein Ausdruck eingesetzt werden, welcher einen Wert vom Datentyp
int
zurückgibt, z.B. für eine Integer-Variable
i
der Ausdruck
(i <= 100).
Zum Verknüpfen mehrerer solcher Ausdrücke zu einem neuen logischen Ausdruck
kann man die logischen Operatoren:
!
Logische Verneinung, meistens genutzt in der Form
&&
Logisches `und'.
||
Logisches `oder' (nicht exklusives `oder').
!(<logischer Ausdruck>)
verwenden. Ein logischer Ausdruck in C gilt als falsch, wenn er eine 0 zurückgibt
int-Wert ungleich 0 zurückgibt. Möchte man beii==4 (sei i eine int-Variable) eine Anweisung durchführen,
und als wahr, wenn er irgendeinen
spielsweise für den Fall
so schreibt man
14
if
(
i == 4
)
<Anweisung>
{
}
(den
else-Teil darf man weglassen, wenn für Nichtzutreen der if-Bedingung nichts
zu tun ist). Schreibt man aus Versehen stattdessen
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
DAS IST EIN FEHLERBEISPIEL
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
if
(
i
= 4
)
{
<Anweisung>
}
so passiert folgendes: Zuerst wird der Variablen
Die Zuweisung gibt dann den Wert
zwischen den
4
i
die
int-Konstante 4
zugewiesen.
zurück und daher ist der logische Ausdruck
if-Klammern wahr, weil ein Wert ungleich
<Anweisung> ausgeführt.
0 zurückgegeben wird.
Damit wird also jedes Mal
Wichtig: Ein typischer Fehler ist, aus Versehen statt zwei Kaufmannsund-Zeichen
(&&) ein einzelnes (&) zu schreiben, selbiges gilt für die Pipes (statt zwei Pipes (||)
eine einzelne (|) ). Als Einzelzeichen geschrieben handelt es sich aber nicht mehr um
logische Operatoren, sondern um Operatoren mit einer anderen Funktion d.h. es
entsteht ein gültiger Ausdruck, aber mit einer anderen Bedeutung welche an dieser
Stelle nicht weiter erläutert werden soll.
Zusätzlich gibt es noch folgende Erweiterungen zum Zuweisungs-Operator:
/*
1
2
3
4
5
Anweisung
<Variable> += < <Wert>
<Wert> > * /
<Variable> −= < <Wert>
<Wert> > * /
<Variable> *= < <Wert>
<Wert> > * /
<Variable> /= < <Wert>
<Wert> > * /
/*
was
die
Anweisung
macht
>;
/*
<Variable> = <Variable> +
<
>;
/*
<Variable> = <Variable> −
<
>;
/*
<Variable> = <Variable>
* <
>;
/*
<Variable> = <Variable>
/ <
*/
6
/*
7
konkretes
8
float
Beispiel :
*/
dieser_float_ist_gut ;
9
dieser_float_ist_gut = 50;
10
d i e s e r _ f l o a t _ i s t _ g u t += 5 ;
/ * Wert
der
Variablen :
55 * /
11
dieser_float_ist_gut
/ * Wert
der
Variablen :
110 * /
*=
2;
Sie sollten jedoch aus Übersichtlichkeitsgründen nur dann eingesetzt werden, wenn
der Variablenname sehr lang ist.
4
Kontrollstrukturen
Beim Programmieren möchte man oft eine bestimmte Sequenz von Anweisungen
nur unter bestimmten Bedingungen ausführen, mehrfach ausführen oder mehrfach
ausführen bis eine gewisse Bedingung zutrit ggf. auch eine Kombination des
gerade genannten. Kontrollstrukturen sind Anweisungen, welche dies ermöglichen.
15
4.1
Geschweifte Klammern
Damit klar ist, welche Sequenz von Anweisungen gemeint ist, setzt man die betroenen Anweisungen in geschweifte Klammern. Zusammen mit den geschweiften
Klammern bilden diese Anweisungen dann einen Anweisungsblock.
4.2
Verzweigungen
4.2.1
Die
if-Anweisung
einfache Verzweigung
1
/*
1.
2
if
( <l o g .
*/
Ausdruck> )
{
<Anweisungen>
3
4
Moeglichkeit
}
5
6
/*
2.
7
if
( <l o g .
9
{
}
else {
<Anweisungen>
11
12
*/
Ausdruck> )
<Anweisungen>
8
10
Moeglichkeit
}
15 Ist der logische
Dabei steht `log. Ausdruck' für irgendeinen logischen Ausdruck.
Ausdruck wahr, so werden die Anweisungen in den geschweiften Klammern direkt
nach dem `if' ausgeführt, ansonsten ggf. diejenigen nach dem `else''.
Handelt es sich bei den Anweisungen nur um eine einzelne, so können die geschweiften Klammern weggelassen werden.
mehrfache Verzweigung
Man kann die
if-Anweisung natürlich ineinander verschachteln, wenn man möchte,
also nach dem Muster
1
if
( <l o g .
A u s d r u c k 1>
)
{
<A n w e i s u n g e n 1>
2
3
}
4
else
if
( <l o g .
A u s d r u c k 2>
)
{
A u s d r u c k N> )
{
<A n w e i s u n g e n 2>
5
6
}
7
else
if
(
...
)
{
8
...
9
10
}
11
else
if
13
}
14
else {
<Anweisungen>
15
16
( <l o g .
<A n w e i s u n g e n N> / *
12
}
15 Siehe
S. 13
d i e N− t e n
An w e i su n g e n
*/
16
Ein Spezialfall davon wäre
if
1
<Variable> == <Wert 1>
(
)
{
<A n w e i s u n g e n 1>
2
3
}
4
else
if
(
<Variable> == <Wert 2>
)
{
<A n w e i s u n g e n 2>
5
6
}
7
else
if
(
...
){
8
...
9
10
else
11
if
(
<Variable> == <Wert N> ) {
d i e N− t e n
<A n w e i s u n g e n N> / *
12
13
}
14
else {
An w e i su n g e n
*/
<Anweisungen>
15
}
16
Quelltext 3: Spezialfall der
if-else-Verzeigung
Man prüft hier immer wieder den Wert ein- und derselben Variablen. Das lässt sich
1
mit der
switch-Anweisung
4.2.2
Die
vereinfachen.
switch-Anweisung
switch (
<Variable>
)
{
2
3
case
<Wert 1> :
<Anweisungen 1>
case
<Wert 2> :
<Anweisungen 2>
4
break ;
5
6
7
break ;
8
9
...
10
11
case
12
13
<Wert N> :
<Anweisungen N>
break ;
14
15
default :
16
<Anweisungen>
17
18
}
Quelltext 4: der
switch
als Alternative zu Quelltext 3
In den Quelltexten 3 und 4 ist dabei der selbe Wert für den jeweiligen Platzhalter
einzusetzen.
Wichtig: Die
break-Anweisung
(siehe Zeile 5, 8 und 14) darf nicht vergessen wer-
den, ansonsten werden die Anweisungen aus dem darunter liegenden `case' ebenfalls
abgearbeitet!
17
4.3
Schleifen
4.3.1
Die
while-Anweisung
Es handelt sich hierbei um eine sogenannte Schleife, da Vorgänge ggf. wiederholt
werden.
<log. Ausdruck>
<Anweisungen>
while (
)
{
}
Die
while-Anweisung
prüft den logischen Ausdruck und wenn dieser wahr ist, so
werden die Anweisungen ausgeführt. Danach wird erneut geprüft, ob der logische
Ausdruck zutrit und wenn dieser wahr ist, werden die Anweisungen wieder ausgeführt etc. Man beachte, dass die Anweisungen ggf. kein einziges Mal ausgeführt
werden.
Wichtig: Die
while-Schleife ist kein
Ersatz für die
if-Anweisung! Man bastelt sich
sehr schnell eine Endlosschleife.
4.3.2
Die
do-while-Anweisung
while-Anweisung
Diese Anweisung funktioniert wie die
mit dem Unterschied, dass
der logische Ausdruck erst nach der letzten Anweisung geprüft wird. Die Anweisungen werden also mindestens einmal ausgeführt.
do {
}
4.3.3
<Anweisungen>
<log. Ausdruck>
while (
Die
for (
{
) ;
for-Anweisung
<Initialisierung>
;
<log. Ausdruck>
;
<Anweisung>
)
<Anweisungen>
}
Es wird zuerst die Initialisierungsanweisung ausgeführt.
Danach wird der log. Ausdruck überprüft.
Wenn dieser wahr ist, werden die Anweisungen zwischen den
danach
<Anweisung>
{}-Klammern
und
ausgeführt.
Im nächsten Schritt wird der
<log. Ausdruck> erneut geprüft. Ist er wahr, werden
{}-Klammern und danach <Anweisung> ausgeführt.
die Anweisungen zwischen den
So wird weiter verfahren, bis irgendwann der logische Ausdruck nicht mehr wahr
ist. Sehr oft wird diese Schleife genutzt, um einen Anweisungsblock eine xe Anzahl
oft zu wiederholen:
1
/*
Definition
die
( mit
funktioniert
2
int
i ;
Initialisierung )
f o r −Anweisung .
die
Sehr
wichtig !
f o r −Anweisung
des
' Zaehlers '
Sonst
n i c h t */
fuer
18
3
/* w i e d e r h o l e
4
for (
5
i =1
;
100 Mal ,
i <=100
<Anweisungen>
;
was
in
i++ )
den {}−Klammern
s t e h t */
{
}
6
Wie bei der
if-Anweisung
{}-Klammern
können die
im Fall einer einzelnen An-
weisung weggelassen werden. In Zeile 2 wird die Zählvariable
deniert. Dies ist sehr wichtig, damit die
for-Anweisung
i
für die for-Schleife
diese Variable zur Verfü-
gung hat.
5
Pointer
Die deutsche, etwas unbeholfene Übersetzung für `Pointer' lautet Zeiger
16 . Pointer
sind Variablen die die Speicheradresse einer Variablen enthält. Einem Pointer ist
genau wie einer Variablen, immer ein Name und ein Datentyp zugeordnet.
Das Konzept des Pointers ermöglicht die Manipulation ein und derselben Variablen
17 .
in verschiedenen `Programmbausteinen'
Man deniert sich eine Pointer-Variable wie folgt:
18
<Datentyp>* <Name>;
Der
*
macht hier den Unterschied zur schon bekannten Art und Weise, Variablen
zu denieren. Quelltext 5 liefert ein Beispiel.
double * m o e c h t e _ a u f _ d o u b l e _ z e i g e n ;
Quelltext 5: Beispiel eines Pointers für den Datentyp
5.1
double
Der Adress-Operator &
19 noch
Nach der Denition eines Pointers ist sein Wert wie bei den Variablen
nicht festgelegt. Das bedeutet bei Pointern, dass noch nicht klar ist, welche Speicheradresse er enthält bzw. auf welche Variable er zeigt. Quelltext 6 liefert zwei
Beispiele für einen Pointer, dem ein Wert zugewiesen wird ein allgemeines und
ein konkretes:
1
2
3
4
/*
allgemein :
*/
<Datentyp>* <Name 1> ; / * e r s t e l l t
<Datentyp> <Name 2> ; / * e r s t e l l t
<Name 1> = &<Name 2> ;
den
die
Pointer
Variable
<Name 1> * /
<Name 2> * /
5
6
/*
7
double * z e i g t _ g l e i c h _ a u f _ d o u b l e V a r ;
double * z a h l ;
8
9
10
konkret :
*/
z e i g t _ g l e i c h _ a u f _ d o u b l e V a r = &z a h l ;
/* z e i g t _ g l e i c h _ a u f _ d o u b l e V a r
Variable
' zeigt '
jetzt
Quelltext 6: Beispiel für einen Pointer für den Datentyp
16 Von `to point' (engl.: zeigen, verweisen)
17 Siehe Abschnitt 7.2.4
18 Pointer auf Arrays siehe Abschnitt 6 S. 10
die
double
werden anders deniert, aber solche Pointer werden
Sie im Rahmen dieser Vorlesungen auch nicht benötigen.
19 Siehe
auf
z a h l */
19
In den Zeilen 4 bzw. 9 ndet dabei die Zuweisung der Speicheradresse der Variablen
<Name>
statt. Dazu wird der Adress-Operator `&' direkt vor den jeweiligen
Variablennamen geschrieben. Er wird immer nach dem Schema
&<Variable>
genutzt. Der obige Ausdruck gibt dann als Wert die Speicheradresse der Variablen
zurück, kann also als Synonym für diese Speicheradresse verstanden werden. Er
steht also für eine Konstante vom Datentyp `Pointer auf Datentyp von
Konvention: Der Adressoperator soll nur vor Variablen der
Datentypen siehe Abschnitt 3.1 gestellt werden.
a Alles
a
<Variable>'.
grundlegenden
andere wird im Rahmen dieser Lehrveranstaltung nicht benötigt werden und gilt
als falsch, sofern Sie nicht eine gute Erklärung geben können, warum sie was gemacht haben.
Das konkrete Beispiel aus Quelltext 6 hat verbildlicht die Situation aus Abbildung
2 geschaen.
Abbildung 2: Stark vereinfachte Verbildlichung eines Zeigers mit zugewiesenem
Wert. Die Rechtecke stellen den jeweiligen Speicherbereich für die Variable dar.
Die Rechtecksegmente stehen für die Bytes des Speicherbereiches ihre Anzahl soll
keinesfalls als repräsentativ für die tatsächliche Anzahl der Bytes stehen.
20
5.2
Der Inhalts-Operator *
Hat man eine Pointer-Variable, so ist man an dem Wert der Variablen, dessen Speicheradresse gemeint ist man spricht auch von dem referenzierten Wert bzw. der
referenzierten Variablen interessiert. Als Anfänger gerät man schnell in Versu-
20 als Synonym für den Namen der referenzierten
chung, den Namen eines Zeigers
Variablen zu sehen.
Dies ist jedoch falsch, denn der Name des Zeigers steht für seinen Wert, das heiÿt,
die Speicheradresse der referenzierten Variablen. Möchte man sich auf den Wert bzw.
den Inhalt der referenzierten Variablen beziehen, so stellt man den Inhalts-Operator
*
vor den jeweiligen Zeiger; Quelltext 7 liefert ein konkretes Beispiel, welches man
zur Übung nachtippen und kompilieren kann.
1
2
#include < s t d i o . h>
i n t main ( void ) {
/* D e f i n i t i o n
3
Datentyp
eines
5
int * i n t Z e i g e r ;
int i ;
/* D e f i n i t i o n
6
i
7
i n t Z e i g e r = &i ; /* j e t z t
4
/*
= 34;
jetzt
Zeigers
f " ur
den
i n t */
eines
wissen
zeigt
wir
Integers
*/
den Wert von
' intZeiger
auf
' i ' */
' i ' */
8
if
9
(
==
* intZeiger
i
)
p r i n t f ( "* i n t Z e i g e r
10
/* I n h a l t s o p .
==
34\ n" ) ;
!=
34)
angewendet
*/
11
if
12
(
intZeiger
!=
i
)
printf ("( intZeiger
13
, denn
( intZeiger
==
&i ) \n" ) ;
14
* intZeiger
15
/* d e r
= 50;
referenzierten
'i '
if
16
(
der
von
intZeiger
betraegt
jetzt
50 ,
also
hat
den Wert 50 * /
i
==
50
printf (" i
17
Wert
Variablen
)
==
50\ n" ) ;
return 0 ;
18
19 }
Quelltext 7: Beispielprogramm zur Anwendung und Wirkung des Inhaltsoperators
Als Erstes nach den Variablendenitionen von intZeiger und i wird der Variablen
intZeiger die Speicheradresse von i zugewiesen. Diese Anweisung ist einwandfrei,
denn i ist genau von dem Datentypen, auf dem intZeiger basiert (welcher Datentyp ist das?).
Jetzt `zeigt'
intZeiger also auf i. Wenn wir jetzt den Inhaltsoperator auf intZeiger
i zurückgegeben. Dieser ist jedoch noch nicht bekannt!
anwenden, wird der Wert von
Das wird in Zeile 6 behoben.
Nun wissen wir, dass die Variable
i
intZeiger referenziert
*intZeiger schreiben.
von der Variablen
und wissen zudem, was herauskommt, wenn wir
wird
In Zeile 9 wird der Inhaltsoperator angewendet, um seinen Wert auszulesen. In
Zeile 15 wird er genutzt, um der referenzierten Variablen einen Wert zuzuweisen.
Allgemeines Schema:
*<Zeiger>
20 Ab
jetzt wird statt `<Datentyp>-Variable einfach nur
Zusammenhang hervorgeht, was gemeint ist.
<Datentyp>
geschrieben, sofern aus dem
21
Quelltext 8 zeigt, was man nicht machen darf.
1
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2
DAS IST EIN FEHLERBEISPIEL
3
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
4
int * Z e i g e r ;
5
* Zeiger
/* n e i n n e i n n e i n !
= 10;
IRGENDWO h i n ,
da
haben
wissen
und
wir
wir
Zeiger
den Wert
nicht ,
referenziert
nicht
festgelegt
wo h i n ! * /
Quelltext 8: Typischer Fehler mit Zeigern: Es wird versucht, den Wert für die
Speicheradresse zu setzen, obwohl noch gar nicht festgelegt wurde, welche Variable
referenziert werden soll.
Der panische Kommentar in Zeile 5 ist berechtigt. Das resultierende Problem wird
in der Regel der bekannte `Speicherzugrisfehler' sein das Programm versucht
in irgendeine Speicheradresse des Betriebssystems zu schreiben, und das wird im
allgemeinen keine sein, welche ihm vom Betriebssystem zugeordnet wurde. Das Betriebssystem beendet in dem Fall das Programm.
Wenn man Pech hat, war die Speicheradresse legal, das heiÿt für das eigene Programm reserviert, und das Programm läuft weiter, aber liefert falsche Ergebnisse.
Dies ist natürlich nur ein einfaches Fehlerbeispiel; so direkt wird man das Problem
nicht immer sehen, da der Inhaltsoperator oft viel später genutzt wird, als direkt
nach der Pointer-Denition.
Wichtig: Damit man den Inhaltsoperator sinnvoll nutzen kann, muss dem Zeiger
eine Speicheradresse einer Variablen vom Datentyp des Zeigers zugewiesen sein und
für den Fall, dass man Werte auslesen möchte, dieser Variablen auch schon ein Wert
zugewiesen worden sein
6
21 !
Array von Elementen vom Datentyp
<Datentyp>
char,
int, float und double behandelt. Wenn man jedoch eine groÿe Anzahl von Daten
Abgesehen von Zeigern wurden bislang nur die grundlegenden Datentypen
verarbeiten möchte, stöÿt man schnell an seine Grenzen mit diesen einfachen Datentypen.
Möchte man beispielsweise mit 1000
den bislang bekannten Mitteln 1000
double-Variablen rechnen, so müsste man mit
double-Variablen denieren mit 1000 ver-
schiedenen Namen. Denn die Variablen der grundlegenden Datentypen können zu
jedem Zeitpunkt nur einen einzelnen Wert speichern.
Damit dies nicht notwendig ist, gibt es Arrays, zu Deutsch: (Daten-)felder. Ein
Array ist eine Sequenz von Variablen des gleichen Datentypes, also eine Art von
Gruppierung von Variablen des gleichen Typs.
Anstatt sich also die 1000 Variablen des eben angeführten Beispieles zu denieren
kann man sich ein Array vom Datentyp
double
mit der Gröÿe 1000 denieren und
ist fertig.
Wichtig: Es sei vorab davor gewarnt, Arrays wie Variablen von einem der grundlegenden Datentypen zu behandeln. Der Name eines Arrays steht nur für die Spei-
cheradresse des ersten Elementes des Arrays, ist insbesondere eine (Pointer-) Kon-
21 Siehe
Hinweis auf S.10.
22
stante und steht in keinem direkten Zusammenhang mit den Werten der Elemente
22
des Arrays.
Man deniert sich ein eindimensionales Array d.h. ein Array, dessen Elemente
über nur einen Index angesprochen werden nach der Syntax
<Datentyp> <Name>[n];
wobei `n' eine natürliche Zahl mit
n>0 ist und kein
Array-Datentyp eingesetzt wer-
den darf.
Die einzelnen Variablen dieses Arrays kann man dann über
<Name>[ i ]
ansprechen.
i
ist dabei der (ganzzahlige) Index der Variablen an der Stelle
Arrays, d.h.
i+1
des
0 ≤ i ≤ n-1
Andere Werte von
i
in obiger Zeile führen zu einer Referenz auf Speicherbereich
auÿerhalb des Arrays und führen ggf. zum Abbruch des Programmes oder zu unvorhersehbarem Verhalten. Abbildung 3 veranschaulicht die Situation.
Abbildung 3: Schematische Veranschaulichung eines Arrays im Speicher. Nur
<Name>[0]
`bis'
<Name>[n-1]
gehören zum Array!
<Name>[n] beispielsweise gehört
<Name>[0], siehe Abschnitt
nicht zum Array. &<Name>[0] ist die Speicheradresse von
5.1.
Ein konkretes Beispiel wäre
int k [100];
Damit deniert man ein Array, das 100
int-Variablen
hält, siehe Abbildung 4.
Abbildung 4: Schematische Veranschaulichung des Arrays
adresse von
22 Siehe
k[0].
Abschnitt 6.1.
k. &k[0] ist die Speicher-
23
Mit der Denition des Arrays allein ist jedoch noch nicht klar, welche Werte diese
Variablen haben, sondern nur, wo sie im Speicher (hintereinander) abgelegt sind.
Deshalb sollte man Arrays immer initialisieren damit ist eine Initialisierung der
Elemente des Arrays gemeint. Dies kann man bei kleineren Arrays wie in der nächsten Zeile gleich zu Beginn der Initialisierung machen:
<Datentyp> <Name>[n]={<Wert
1>,<Wert 2>,...,<Wert N>};
Die Werte müssen dabei Konstanten vom Datentyp des Arrays sein.
Z.B. kann man ein
int-Array
int-Konstanten
mit den
1,2,3,4 wie folgt denieren
und initialisieren:
int zahlen [4]={1,2,3,4};
zahlen[0]
Dann ist der Wert von
1, der von
zahlen[1]
2 usw.. Auch hier beachte
man die Indexverschiebung der erste Wert hat den Index 0.
Wichtig: Diese Art von Initialisierung kann nur in einer Anweisung mit der Denition stattnden!
Für groÿe Arrays ist dies jedoch oensichtlich zu umständlich. Man kann sich die
Aufgabe hier mit einer
for-Anweisung vereinfachen. Quelltext 9 zeigt das allgemeine
Muster.
1
2
3
<Datentyp> <Name> [ n ] ;
int i ;
for ( i = 0 ;
<Name> [
4
i <n
;
/ * Def .
eines
Arrays
/ * Def .
von
fuer
i
n
E l e m e n t e n */
f o r −S c h l e i f e */
i++ )
/ * A n we i s u ng e n
i ]=0;
mit
die
der
f o r −S c h l e i f e */
Quelltext 9: Initialisierung eines eindimensionalen Arrays
Für das Array
k,
welches in Abbildung 4 dargestellt ist, würde dieser Quelltext so
aussehen:
int k [ 1 0 0 ] ;
i n t i ; // D e f i n i t i o n
1
2
der
Variablen
i
fuer
die
for−
Schleife
for (
3
i
= 0
;
i <100
;
i++ )
k [ i ]=0;
4
6.1
Der Name eines Arrays
Die Speicheradresse des ersten Elements spielt eine besondere Rolle bei Arrays.
In den Abbildungen 3 und 4 wurde diese daher hervorgehoben. Nach ANSI-C ist
nämlich der Wert eines Arrays
array[0],
array
nichts weiteres, als die Speicheradresse von
also ein Verweis auf den ersten Eintrag von
array.
Dennoch ist ein Array nicht mit einer Pointer-Variablen zu verwechseln. Die sogenannte Basisadresse
array bzw. &array[0] des Arrays ist schon mit der Denition
23
sinnvoll festgelegt und kann zudem nicht verändert werden.
23 Bei
einer Pointer-Variablen hingegen ist nach ihrer Denition nicht klar, wohin sie zeigt und
man kann jederzeit verändern, wohin sie zeigt.
24
1
2
3
4
#include < s t d i o . h>
i n t main ( void ) {
double b e i s p i e l A r r a y [ 2 0 ] ; / * Array m i t 20 E i n t r a e g e n * /
i f ( b e i s p i e l A r r a y == &b e i s p i e l A r r a y [ 0 ] )
p r i n t f ( " Das
5
Skript
hat
Recht ! \ n" ) ;
else
6
p r i n t f ( " Diese
7
Zeile
wird
sowieso
nicht
ausgegeben . " ) ;
return 0 ;
8
9 }
`beispielArray'
`double'. Für andere Datentypen funktioniert dieses Beispiel natürlich genauso
Quelltext 10: Ein kleines Beispielprogramm. Der Datentyp von
ist
Abbildung 5: Das Array aus Quelltext 10 verbildlicht
Dabei ist zu beachten, dass die Speicheradresse
`Pointer auf
<Datentyp von array>'
array eine Konstante
vom Datentyp
24 Quelltext 10
ist, sie ist nicht veränderbar.
und Abbildung 5 geben ein Beispiel.
6.2
Mehrdimensionale Arrays
Man kann sich neben eindimensionalen auch zweidimensionale oder gar N-dimensionale
Arrays erstellen (`N' sei dabei eine natürliche Zahl), d.h. Arrays, deren Elemente
über zwei oder n Indizes angesprochen werden. Zweidimensionale Arrays kann man
sich dann wie eine Matrix von Elementen des Datentyps des Arrays vorstellen; man
sollte nur wie immer im Kopf behalten, dass ab dem Index
0
adressiert wird.
Man deniert sich zweidimensionale Arrays mit folgender Syntax:
<Datentyp> <Name>[n1][n2];
n1 und n2 natürliche Zahlen sind mit 0 < n1 und 0 < n2 auch hier dürfen
<Datentyp> alle Datentypen mit Ausnahme von Arrays eingesetzt werden.
wobei
für
Bemerkung: N -dimensionale Arrays für N∈ N können analog deniert werden:
<Datentyp> <Name>[n1][n2]...[nN];
Je nach Compiler liegt die obere Grenze für
N
zwischen 7 und 10.
Im Speicher sind die Elemente dabei alle hintereinander angeordnet, und zwar so,
dass beim Durchlaufen des Arrayelemente entsprechend der Speicherstruktur zuerst
der letzte Index durchlaufen wird, dann der vorletzte usw.. Bei zweidimensionalen
Arrays entspräche dies dem Durchlaufen der Spaltenindizes für die Zeile 0, dann
24 Wo
das Array im Speicher abgelegt ist, wird beim Kompilieren festgelegt.
25
dem Durchlaufen der Spaltenindizes für die Zeile 1 usw..
Daher kann man ein zweidimensionales Array auch als ein eindimensionales Array
von eindimensionalen Arrays des jeweiligen Datentypen auassen. Analoges gilt für
höherdimensionale Arrays.
Auch bei Arrays höherer Dimensionen ist es sinnvoll, die Elemente zu initialisieren,
bevor man mit dem Array arbeitet:
1
<Datentyp> <Name> [ n1 ] [ n2 ] ;
i n t i ; /* i f u e r d i e f o r −S c h l e i f e */
i n t j ; /* j f u e r d i e f o r −S c h l e i f e */
f o r ( i = 0 ; i <n1 ; i++ )
f o r ( j = 0 ; j <n2 ; j++ )
2
3
4
5
<Name> [
6
i ] [ j ]=0;
/ * A n we i s u ng e n
f o r −S c h l e i f e */
der
Quelltext 11: Initialisierung eines zweidimensionalen Arrays während der Laufzeit
Man braucht hier zwei Zählschleifen, wobei die Zählschleife für die Variable
Anweisung innerhalb der Zählschleifen für die Variable
i
j
die
25 Analog kann man
ist.
mit entsprechend vielen Zählvariablen höherdimensionale Arrays initialisieren.
Ein konkretes Beispiel:
double m a t r i x [ 2 0 ] [ 4 0 ] ;
i n t i ; /* i f u e r d i e f o r −S c h l e i f e */
i n t j ; /* j f u e r d i e f o r −S c h l e i f e */
f o r ( i = 0 ; i <20 ; i++ )
f o r ( j = 0 ; j <40 ; j++ )
1
2
3
4
5
matrix [ i ] [ j ]=0;
6
Wie beim eindimensionalen Array steht der Wert eines Arrays `array' wieder für
die Speicheradresse des ersten Eintrags:
&<array>[0][0]\... [0]
Die Punkte deuten an, dass die Anzahl der Nullen der Dimension des Arrays entspricht.
6.3
Strings
Oft möchte man ein Programm schreiben, das Menüs enthält oder dem Nutzer bestimmte Fragen stellt, Antworten einliest etc. Man möchte in dem Fall Zeichenketten
oder zu Englisch Strings ausgeben, einlesen oder anders verarbeiten.
Strings werden in C durch Abfolgen von
char-Konstanten,
welche auf das Spezial-
zeichen `\0' enden, repräsentiert. Dies wurde vereinbart um das Ende der Zeichen-
kette zu markieren und dies ist auch notwendig. Denn beispielsweise kann ein
char-Array
der Gröÿe 100 einen String mit 99 Buchstaben speichern der letzte
Eintrag des Arrays enthält dann das Zeichen `\0'. Es kann aber auch einen String
mit 50 Buchstaben speichern. Damit klar ist, bis zu welchem Element des Arrays
der String gehen soll, hat man den Marker `\0'.
Die Denition ggf. mit Initialisierung muss nicht nach dem Schema auf S. 23 erfolgen. Möchte man das Array gleich mit einem String initialisieren, so kann man
schreiben:
25 Indem die innerste Schleife den letzten Index des Arrays durchläuft, wird hier auch die Struktur
des Arrays im Speicher berücksichtigt.
26
char <Name>[n]="<Zeichenkette mit n−1 Buchstaben>"
Es folgt ein Beispiel:
char bsp[5]="Hey!";
Abbildung 6: Das Bild zu obiger Zeile
Es sei angemerkt, dass die Syntax
array="<String>"
getrennt von der Denition
eines Arrays nicht gültig ist, d.h. man kann bei obigem Beispiel nach der Denition
nicht schreiben
bsp="Neu"! Das geht schon allein deshalb nicht, weil man versucht,
einer Konstante (bsp) einen Wert zuzuweisen. Für Operationen wie das Setzen einer
Sequenz von Buchstaben in ein Array hat man spezielle Funktionen für Strings,
welche jedoch nicht Gegenstand dieses Skriptes sein werden. Es werden im Anhang
nur Funktionen zum Einlesen und Ausgeben von Strings behandelt.
char-Array mit der passenden Gröÿe deniert. Die Gröÿe
a+1 betragen, wobei `a' der Anzahl der Zeichen des längsten
Wichtig ist, dass man das
des Arrays sollte immer
zu verarbeitenden Strings entspricht.
Wichtig: Für irgendein ausgebbares Zeichen
"x"
x gilt:
x, \0.
ist die String-Konstante mit den Zeichen
Sie ist nicht zu verwechseln mit der
char-Konstanten 'x',
welche einen einzelnen
Buchstaben darstellt! Diese wird mit einzelnen Anführungsstrichen geschrieben.
7
Funktionen
Alle Programmiersprachen haben Konstrukte, welche die Separierung von QuellcodeBlöcken in Quellcode-Einheiten ermöglichen. C benutzt hierzu das Konzept der
Funktion. Jedes Programm in C muss eine Funktion mit dem Namen
main haben;
denn dort stehen die ersten Anweisungen, welche ausgeführt werden.
Es ist zwar möglich, alle Anweisungen des Programmes nur in die
main-Funktion zu
schreiben. Dies würde jedoch bei groÿen Programmen zu sehr unübersichtlichem,
groÿem und komplexem Quelltext führen, so dass die Fehlersuche das sogenannte
Debugging , das Testen und die Wartung des Programmes viel zu schwer werden
würden.
Wenn man ein Programm in funktionale Module mit kleineren Quelltexten aufteilen kann, dann werden diese Prozesse leichter. Nach dem Schreiben der Quelltexte
können diese Module zu einem Programm zusammengefügt werden. Diese Module
enthalten in der Regel eine (manchmal auch mehrere) Funktionen.
Ein weiterer Vorteil der Modularisierung ist, dass man einen öfter wiederkehrenden Quelltextabschnitt als Funktion schreiben kann diese kann dann beliebig oft
genutzt man sagt auch aufgerufen werden. Der Funktionsaufruf steht dann in
einer einzigen Zeile. Natürlich kann diese Funktion dann ggf. auch für neue, andere
Programme genutzt werden.
27
7.1
Funktionsdenition
Funktionen sind innerhalb eines Programmes eindeutig identizierbar durch ihren
Namen. Dieser muss durch einen Bezeichner
26 gegeben sein.
Man kann eine Funktion so schreiben, dass sie einen Wert zurück gibt dann kann
man den Funktionsaufruf im Quelltext wie eine Konstante von diesem Datentypen
nutzen.
27 Dies ist beispielsweise sinnvoll, wenn man die Berechnung eines bestimm-
ten Wertes in einer Funktion modularisiert und das Ergebnis in der aufrufenden
Funktion weiter benutzen möchte. Damit Fehler vermieden werden, muss klar gemacht werden, von welchem Datentyp dieser Rückgabewert ist. Diesen Datentyp
nennt man auch den Funktionstyp oder Datentyp der Funktion.
Analog zu gewöhnlichen Variablen haben Funktionen also einen Namen und einen
Datentypen. Die Ähnlichkeit geht sogar weiter: Funktionen müssen vor ihrer Verwendung auch deniert werden. Die Denition sieht bei Funktionen jedoch etwas
anders aus.
In einer Funktionsdenition müssen folgende Informationen enthalten sein:
ˆ Funktionstyp
ˆ Name der Funktion
ˆ Parameterliste
ˆ Denition von Variablen der Funktion
ˆ Anweisungen der Funktion
ˆ ggf. die Rückgabeanweisung
Die ersten drei Elemente werden im sogenannten Funktionsheader zusammengefasst.
Die letzten drei Elemente stehen im sogenannten Funktionskörper.
4
<Funktionstyp> <Name>( <Parameterliste> )
<Variable>n d e f i n i t i o n e n
<Anweisungen>
<Rueckgabeanweisung>
5
}
1
2
3
{
Quelltext 12: Allgemeines Schema der Funktionsdenition
In Zeile 1 steht der Funktionsheader nur die
{-Klammer gehört nicht dazu. In den
drei Zeilen ab Zeile 2 steht der Funktionskörper.
7.1.1
Der Funktionsheader
Der Funktionsheader besteht aus Funktionstyp, Name und Parameterliste. Die Anordnung ist in Quellcode 12, Zeile 1 beschrieben.
26 Siehe Abschnitt 3.3, S. 9
27 Ausgenommen sind hierbei
Arrays, siehe Abschnitt 6.
28
Parameterliste
Die Parameterliste zählt nacheinander die Parameter auf, welche
beim Aufruf der Funktion übernommen (und erwartet) werden:
<Parameter 1>, <Parameter 2>,
...,
<letzter Parameter>
Dabei wird jeder Parameter in der Form
<Datentyp> <Name>
oder
<Datentyp> <Name> [ n ]
bzw.
<Datentyp> <Name> [ n1 ] [ n2 ]
für Variablen bzw. ein- und zweidimensionale Arrays geschrieben. Die Syntax ähnelt
also stark der Denition von Variablen bzw. Arrays, mit dem Unterschied, dass am
Ende kein Semikolon steht.
Wie so oft folgt ein konkretes Beispiel:
double p1, int* p2, int p3 [30], char p4
doubleint' , als dritten
Eine Funktion mit dieser Parameterliste würde als ersten Parameter eine
Variable, als zweiten Parameter eine Variable vom Typ `Zeiger auf
Parameter ein eindimensionales Array von
eine
char-Variable
int-Variablen und als vierten Parameter
übernehmen.
Ein kompletter Funktionsheader mit dieser Parameterliste sieht so aus:
<Funktionstyp> <Name>( double
p1 ,
i n t * p2 , i n t p3 [ 3 0 ] , char p4 )
Quelltext 13: Fast komplettes Beispiel für einen Funktionsheader
Man kann sich auch dafür entscheiden, dass die Funktion gar keine Parameter übernimmt, das schreibt man so:
<Funktionstyp> <Name>(
Funktionstyp
void )
Als Funktionstyp können wir alle bekannten Datentypen mit Aus-
nahme von Arrays nehmen. Warum es nicht notwendig ist, Arrays zurückzugeben,
wird in Abschnitt 7.2.3 erläutert. Für den Fall, dass man gar keinen Wert zurückgeben möchte, gibt es noch den Datentyp
Funktionstypen
double
double
<Name>( double
Name der Funktion
void.
Quelltext 13 würde mit dem
so aussehen:
p1
,
i n t * p2
,
i n t p3 [ 3 0 ]
,
char p4 )
Der Name muss wie bei Variablen durch einen Bezeichner
28
gegeben sein. Beim gerade aufgeführten Beispiel könnte die Funktion zum Beispiel
`funk' heiÿen:
double f u n k ( double p1
Quelltext
14:
vervollständigt
28 Siehe
S. 9
Das
Beispiel
aus
,
i n t * p2
Quelltext
13
,
i n t p3 [ 3 0 ]
zu
einem
,
char p4 )
konkreten
Beispiel
29
7.1.2
Der Funktionskörper
Im Funktionskörper stehen alle Anweisungen, welche die Funktion durchlaufen soll.
Gewöhnlicherweise ordnet man die Anweisungen so, dass zuerst alle Variablendenitionen geschrieben sind und dann alle anderen Anweisungen folgen. So hat man
oben im Quelltext eine Übersicht über alle Variablen der Funktion.
Die Parameter aus der Parameterliste können im Funktionskörper ohne jegliche Denition wie gewöhnliche Variablen bzw. Arrays genutzt werden.
die
return-Anweisung.
29 Am Ende steht
return <Wert vom Datentyp der Funktion>;
in welcher angegeben wird, welcher Wert zurückgegeben werden soll. Beim Funktionstyp
void
kann man diese weglassen oder schreibt einfach
return ;
Die
return-Anweisung
bewirkt neben der ggf. durchzuführenden Rückgabe eines
Wertes, dass die Funktion beendet wird und die Kontrolle an die aufrufende Funktion zurückgegeben wird.
Quelltext 15 liefert ein einfaches Beispiel für eine komplette Funktionsdenition. Es
werden zwei Integer-Parameter übernommen und ihre Summe wird in der Variablen
summe
gespeichert. Danach gibt die Funktion diese Summe zurück. Das kann
gemacht werden, weil der Funktionstyp
1
int
addition (
int
i
,
int
)
/*
hier
3
/*
V a r i a b l e n d e f i n i t i o n ( en )
4
i n t summe ;
/*
6
summe =
A n we i s u ng e n
i
der
j
2
5
beginnt
int
ist, so wie der Datentyp von
/*
{
Funktionsheader
Funktionskoerper
summe.
*/
*/
*/
*/
+ j ;
r e t u r n −Anweisung
7
/*
8
return summe ;
*/
9 }
Quelltext 15: Einfaches Beispiel für eine komplette Funktionsdenition
7.2
Funktionsaufrufe
Als nächstes wird man sich evtl. fragen, wie denn jetzt beispielsweise das in Quelltext 15 beschriebene Modul genutzt werden kann. Dies geschieht in einer anderen,
aufrufenden Funktion durch eine bestimmte Anweisung, den Funktionsaufruf.
7.2.1
Funktionsdeklaration
Bevor der Funktionsaufruf stattnden kann, muss dem Modul der aufrufenden Funktion erklärt werden, wie dieser Aufruf auszusehen hat denn diese Information ist
zunächst nur im Modul der Unterfunktion enthalten. Diese Deklaration ist nicht
Bestandteil der Funktionsdenition des Moduls, sie steht wie die
in Quelltext 1 über der Funktionsdenition.
29 Siehe
auch Abschnitt 7.2.3
#include-Zeile
30
Das Beispiel aus Quelltext 1 könnte man mit dem Modul aus Quelltext 15 wie im
folgenden Quelltext 16 exemplarisch verbinden:
1
2
#i n c l u d e < s t d i o . h>
int
addition
int
(
i
int
,
j
);
/* F u n k t i o n s d e k l a r a t i o n */
3
4
5
i n t main ( void ) {
i n t summerueck , z a h l =3 ;
6
printf (
7
summerueck = a d d i t i o n
" Ich
8
printf (
eine
" Variable
und
in
Z e i l e \n"
);
(
15
zahl
summerueck
zahl
und
,
) ; /* F u n k t i o n s a u f r u f */
Konstante
g e s p e i c h e r t \n"
15
wurden
addiert
);
return 0 ;
9
10
bin
}
Quelltext 16: Das erste Beispiel abgewandelt
In Zeile 2 ndet man die Funktionsdeklaration. Sie sieht immer aus wie der Funktionsheader der aufgerufenen Funktion in diesem Beispiel ist das die Funktion
addition
mit dem Unterschied, dass ein Semikolon dahinter steht. Das ist das
allgemeine Schema für Funktionsdeklarationen.
Die Picht, solche Deklarationen einzubinden, soll den Programmierer daran erinnern, wie er die Unterfunktion aufzurufen hat auÿerdem wird durch Abgleich
mit dem Funktionsheader geprüft, ob das aufrufende und das aufgerufene Modul
`sich darüber einig sind`, wie die aufgerufene Funktion zu funktionieren hat.
In Zeile 7 ndet der Aufruf statt. Wie schon erwähnt kann und wird hier der Aufruf
wie ein Wert vom Funktionstyp benutzt der
Wert
zahl+15
int-Variablen summerueck
wird der
zugewiesen.
Würde man die Module aus den Quelltexten 15 und 16 in zwei Textdateien, z.B.
add.c
und
main.c
speichern, so könnte man diese in der Kommandozeile mit dem
Befehl
−o
gcc add.c main.c
in die ausführbare Datei
programmEinsB
programmEinsB kompilieren. Es folgen einige Beispiele für
Funktionsdeklarationen verschiedener Art.
1
/*
2
double f u n k 1 ( void ) ;
gibt
gibt
double
3
/*
4
void f u n k 2 ( void ) ;
gibt
nichts
zurueck ,
5
/*
6
void f u n k 3 ( double ) ;
7
/*
gibt
nichts
zurueck ,
char
ganzzahl ,
zurueck ,
zurueck ,
als
*/
uebernimmt
nichts
*/
uebernimmt
double
*/
uebernimmt
char f u n k 4 ( i n t g a n z z a h l
9
/*
bei
nichts
zweites
8
nur
uebernimmt
ein
,
* Deklarationen *
weggelassen
werden
duerfen
f l o a t funk4b ( int
/*
12
void f u n k 5 ( char z e i c h e n [ 1 0 0 ]
[100][100]
,
Argument
doubles
*/
Bezeichner
auch
*/
11
denken
erstes
von
double z a h l e n f e l d [ 1 0 0 ] ) ;
10
selbst
als
Array
double [ 1 0 0 ]
) ;
*/
,
double z a h l e n f e l d 2 x 2
);
Quelltext 17: Beispiele für Funktionsdeklarationen
31
Es sei nochmals betont, dass die Funktionsdeklarationen von aufgerufenen Funktionen im Quelltext des aufrufenden Modules über der Funktionsdenition der auf-
rufenden Funktion zu stehen hat, siehe Beispiel 16.
7.2.2
Der Funktionsaufruf
Hat man eine Funktion deniert und in dem aufrufenden Modul auch schon deklariert, so kann man die Funktion guten Gewissens aufrufen. Die Funktionsdeklaration
ist dabei maÿgeblich dafür, wie der Aufruf auszusehen hat.
Wenn der Funktionstyp nicht
void
ist, dann wird ein Wert zurückgegeben. Man
kann diesen wie in Quelltext 16 dann in einer Variablen der aufrufenden Funktion
speichern. Dabei muss darauf geachtet werden, dass die Datentypen übereinstimmen im Beispiel von Quelltext 16 sind beide Datentypen
int
und daher gibt es
keine Probleme.
Stimmen die Datentypen nicht überein, so wird der Rückgabewert falsch interpretiert und man wundert sich unter Umständen, warum das Programm merkwürdige
Ergebnisse liefert sofern es nicht schon beim Kompilieren Fehlermeldungen gibt.
Hat man eine
void-Funktion
oder ist gar nicht am Rückgabewert interessiert, so
braucht man den Funktionsaufruf nicht mit einer Zuweisung zu verbinden. Der
Aufruf sieht schematisch so aus:
<Name der Funktion>( <Wert 1>
,
<Wert 2>
,
...
,
<Wert N> ) ;
Arrays werden mit ihrem Namen übergeben. Das ist auch ein Wert (Was für einer?).
N entspricht dabei der Anzahl der Parameter in der Funktionsdeklaration
<Wert 1> entspricht vom Datentypen her dem Wert des ersten Parameters in
der Parameterliste, <Wert 2> entspricht vom Datentypen her dem Wert des zweiten
Die Zahl
und
Parameters in der Parameterliste etc. für die Übergabe von Arrays schreibt man
ihren Namen hin.
32
Vorsichtshalber nochmal ein paar konkrete Beispiele. Angenommen, man hat die
Deklarationen aus Quelltext 17. Dann könnte man die Funktionen aus der Funktion
beispiel
wie folgt aufrufen:
1
void
2
/*
der
Variablen ,
welche
3
double z a h l R u e c k ,
zahlPara1 ,
zahlPara2
4
int zahlParaGanz ;
char b u c h s t R u e c k ,
beispiel
Definition
(
void ) {
uebergeben
,
werden
*/
feldPara [100] ,
feldPara2x2 [ 1 0 0 ] [ 1 0 0 ] ;
5
charArray [ 1 0 0 ] ;
6
7
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
8
Vor
9
Initialisierungen
Uebergabe
die
der
Werte
Variablen
folgen
der
natuerlich
sinnvollerweise
welche
11
irgendwie
12
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
Das
uebergebenen
erstmal
Anweisungen ,
10
aendern .
zu
sollten
und
wird
hier
Variablen
ausgelassen .
13
/*
Funktionsaufrufe
16
/*
funk1
17
/*
Parameterliste
18
funk1 (
19
/*
Funktionstyp
20
/*
in
21
zahlRueck = funk1 (
14
entsprechend
der
Deklarationen
*/
15
*/
)
ist
void ,
also
nichts
double ,
also
kann
in
den Klammern * /
;
einer
ist
d o u b l e −V a r i a b l e n
)
der
gespeichert
Rueckgabewert
werden
*/
*/
;
22
23
/*
24
funk2 (
funk2
25
/*
*/
);
zahlRueck = funk2 (
zurueck
)
;
geht
nicht !
Denn f u n k 2
gibt
nichts
*/
26
27
/*
28
funk3 (
funk3
zahlPara1
*/
29
funk3 (
zahlPara2
) ;
30
funk3 (
2.4562
/*
) ;
) ;
hier
wird
eine
Konstante
uebergeben
*/
31
32
/*
funk4
33
/*
Arrays
34
funk4
35
/ * s o NICHT :
funk4 ( zahlParaGanz ,
36
/ * s o NICHT :
funk4 ( i n t
37
/* m i t
38
buchstRueck = funk4 (
(
*/
werden * mit
zahlParaGanz
Speichern
des
i h r e m Namen *
,
feldPara
)
u e b e r g e b e n : */
;
feldPara [100]) ;
zahlParaGanz ,
double
Rueckgabewertes :
zahlParaGanz
,
*/
feldpara
);
39
40
/*
41
funk5 (
funk5
*/
charArray
,
feldPara2x2
*/
f e l d p a r a [ 1 0 0 ] ) ; */
);
Quelltext 18: Funktionsaufrufe gemäÿ der Deklarationen aus Quelltext 17
33
7.2.3
Call by Value
In C werden Parameter mit ihrem Wert übergeben. In C geschieht dabei in der aufgerufenen Funktion mehreres (es sei
1≤i≤N
und
N
die Anzahl der Parameter):
ˆ im Funktionsheader steht in der Parameterliste an
i-ter
Stelle so etwas ähn-
30 . Tatsächlich
liches wie eine Variablendenition oder Denition eines Arrays
werden alle Parameter bis auf Arrays zu Variablen der aufgerufenen Funktion (ihr Variablenname steht in der Parameterliste).
ˆ diese Variablen werden automatisch mit dem übergebenen Wert initialisiert.
Sie sind nur dieser Funktion bekannt. Selbst wenn sie den gleichen Namen
haben wie Variablen einer anderen Funktion, so ist ihr Wert unabhängig von
dem Wert der Variablen der anderen Funktion.
Man kann sich das so vorstellen, dass jede Funktion ihren lokalen Speicherbereich
hat. Da jede Variable nichts anderes als ein Speicherbereich mit zugeordnetem Namen und Datentyp darstellt, kann man sich die Situation wie in Abbildung 7 visualisieren.
(a) ueberFunk
#include
void
void
int
double
if
int
void
unterFunk
(
ueberFunk
(
i =0;
==
i
printf
/*
);
)
{
zahl ;
unterFunk
(
(b) unterFunk
< s t d i o . h>
i
)
("
hat
else
(
0
);
Die
den
Pfad
void
char
unterFunk
Variable
Wert
0\ n" ) ;
unnoetig
i
*/
}
= buchst ;
i ++;
int
(
i
)
{
/*
Inkrementieren
v.
i
*/
}
(c) ueberFunk Quelltext
(d) ueberFunk Quelltext
Abbildung 7: Die Anweisung `i++' im Quelltext der Funktion
nur die Variable
i
im Speicherbereich von
unterFunk
unterFunk
Diese Übergabe von Parametern mit ihrem Wert wird Call by Value C kennt eigentlich nichts anderes, als dieses Konzept.
30 Siehe S. 28.
31 engl.: Aufruf
nach Wert
verändert
31 genannt.
34
7.2.4
Call by Reference
Man hat nach den Ausführungen aus vorigem Abschnitt folgendes:
ˆ Jede Funktion kann
nur einen Wert zurückgeben
ˆ Arrays können nicht zurückgegeben werden
ˆ Variablen können nur mit ihrem Wert übergeben werden
In Quelltext 18, Zeilen 34, 38 und 41 wurden Funktionen aufgerufen, welche laut
Deklaration als Parameter Arrays empfangen. Die Arrays wurden übergeben, indem
der jeweilige Name des Arrays übergeben wurde. Dieser ist nach 6.1, S. 23 ein Zeiger
auf das erste Element des Arrays.
Man übergibt in C Arrays, indem man den Namen des Arrays übergibt. Da es sich
hier um eine Speicheradresse handelt, übergibt man also eine Referenz bzw. einen
Verweis. Dies nennt man Call by Reference .
Im Speicherbereich der Unterfunktion wird also kein lokales Array erstellt, sondern
die Unterfunktion manipuliert den Speicherbereich des Arrays in der aufrufenden
Funktion, siehe Abbildung 8.
(a) ueberFunk
#include <s t d i o . h>
void unterF ( int [ 3 ] , double ) ;
void ueberFunk ( void ) {
int f [ 3 ] = { 1 , 2 , 3 } ;
double z a h l =0;
unterFunk ( f , z a h l ) ;
if ( f [ 0 ] == 1 )
(b) unterFunk
void
/* else Pfad unnoetig */
(c) ueberFunk Quelltext
int
f [3] ,
double
zahl ){
/* erste Element von f
auf 5 setzen */
z a h l ++;
/* Inkrementieren von
lokalem i */
f [ 0 ] = 5;
p r i n t f ( " Die V a r i a b l e f [ 0 ] hat
den Wert 1\n" ) ;
}
unterF (
}
(d) ueberFunk Quelltext
zahl++; verändert nur den Wert der zu unterFunk
zahl und lässt die Variable gleichen Namens in ueberFunk unverändert. Gleichzeitig wird durch die Anweisung f[0]=5; das Array von ueberFunk
verändert es besteht gar kein lokales Array f.
Abbildung 8: Die Anweisung
lokalen Variablen
Bei Arrays hat man also in C im Gegensatz zu Variablen standardmäÿig Call by
Reference. Man kann sich auch überlegen, dass das Kopieren eines ganzen Arrays
bei entsprechend groÿen Arrays schnell zu einem sehr groÿen Schreibaufwand und
damit zu einer Verlangsamung des Programms führen kann.
35
Es kann aber vorkommen, dass man mehrere Variablen, ggf. auch unterschiedlichen
Datentypes, übergeben und es der Unterfunktion ermöglichen möchte, die Werte
der Variablen in der aufrufenden Funktion zu verändern. Da man standardmäÿig
nur eine einzige Variable zurückgeben kann und auf Variablen immer Call by Value angewandt wird, muss man sich eines kleinen Knies bedienen, um die Werte
in der aufrufenden Funktion zu verändern.
Die Lösung ist, dass man an Stelle der Variablen selbst ihre Speicheradresse übergibt. Das erreicht man, indem man den Adress-Operator
32 vor den Variablenna-
men stellt und sowohl Funktionsheader als Funktionsdeklarationen entsprechend
schreibt. Dabei muss man beachten, dass statt dem Datentypen
Datentyp
<Datentyp>*33
<Datentyp>
der
benutzt wird. Es folgt ein kleines Beispiel in Abbildung
9.
(a) ueberFunk
#include <s t d i o . h>
void unterF ( double * ) ;
void ueberFunk ( void )
double z a h l =0;
(b) unterFunk
{
/* es wird Speicheradresse von zahl
uebergeben diese i s t vom Typ
double , erreicht wird dies
durch Vorstellen von & */
void
double *
Adr_von_zahl ) {
/* Nutzung des Inhaltsoperators , um
den Wert an der
Speicheradresse Adr_von_zahl
zu veraendern */
unterFunk ( &z a h l ) ;
( z a h l >2.4 )
p r i n t f ( " z a h l hat den Wert
2 . 4 5 \ n" ) ;
if
}
unterF (
/* else Pfad unnoetig */
}
(c) ueberFunk Quelltext
*Adr_von_zahl = 2 . 4 5 ;
(d) ueberFunk Quelltext
&zahl wird übergeben in unterFunk wird
Adr_von_zahl gemacht. Durch Anwendung des
Abbildung 9:
daraus die lokale Zeiger-
Variable
Inhaltsoperators können
wir den Inhalt der referenzierten Adresse verändern also die Variable
zahl
der
Überfunktion. Man beachte auch, dass in Funktionsheader und -deklaration von
unterFunk
A
statt dem Datentyp
double
der Datentyp
double*
verwendet wurde.
Schlüsselwörter
Im Englischen `keywords'. Jedes Wort in ANSI-C ist entweder ein Schlüsselwort
oder ein Bezeichner. Allen Schlüsselwörtern ist durch den ANSI-C-Standard schon
eine Bedeutung zugeordnet und diese kann nicht geändert werden. Schlüsselwörter gehören zu den Grundsteinen für Programmanweisungen. Alle Schlüsselwörter
32 Siehe 5.1
33 Also der Datentyp
`Pointer auf
<Datentyp>'.
36
sind in Kleinbuchstaben gehalten in ANSI-C wird zwischen groÿen und kleinen
Buchstaben genau unterschieden. Es folgt eine Auistung der Schlüsselwörter nach
ANSI-C in Tabelle 1. Je nach Compiler können Schlüsselwörter hinzukommen.
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
oat
short
unsigned
continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
Tabelle 1: Schlüsselwörter in ANSI-C
B
Einige der Standardfunktionen aus
stdio.h
In diesem Abschnitt soll eine Auswahl der Standardfunktionen aus der Standardbibliothek
stdio vorgestellt werden. Es wird die jeweilige Deklaration vorgestellt um
zu erklären, wie die Funktion aufgerufen werden soll. Sämtliche Deklarationen der
Funktionen dieses Anhangs werden ausgeführt, indem man die Zeile
#include <stdio.h>
ganz oben im Quelltext eingibt.
B.1
Bildschirmausgaben printf
Die Deklaration lautet:
int printf ( const char* format, ...) ;
und ist
wie alle Funktionen dieses Anhangs
#include <stdio.h>
printf verwenden möchte
in der Anweisung
enthalten diese Anweisung schreibt man also, wenn man
dahin, wo man sonst gewöhnliche Deklarationen hinschreibt (über der Funktionsdenition der aufrufenden Funktion).
Funktionstyp:
Die Funktion ist vom Datentypen
int,
aber die Rückgabewerte
sollen im Rahmen dieser Vorlesung nicht interessieren.
1. Parameter const char*:
Der erste Parameter ist vom Typen
const char*.
Erwartet wird als erster Parameter ein Ausdruck der Form
"<Zeichenkette>"
(wobei die Anführungszeichen tatsächlich dazu gehören). Die Zeichenkette entspricht einfach dem, was ausgegeben werden soll. Man darf nur nicht zwischendurch
ein
RETURN
getippt haben:
37
p r i n t f ( " Das
1
das
2
Ausgabe
die
mit
Zeilenumbruch \n" ) ;
/*
Z e i l e um * /
NICHT w i e
in
den
naechsten
Zeilen ! ! ! !
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
5
6
eine
bricht
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3
4
ist
'\n '
hier
p r i n t f ( " Anfang
der
geht
w e i t e r . . . . . \ n" ) ;
die
Zeile
Fehlermeldung
und
Zeil . . . . .
ist
falsch !
/*
das
fuehrt
zu
einer
*/
Die Ausgabe von konstanten Zeichenketten ist ziemlich einfach, man muss ggf. nur
wissen, dass es Sonderzeichen gibt wie z.B.
\n,
welches nicht als `\n' ausgegeben
wird, sondern als Zeilenumbruch. Möchte man aus irgendwelchen Gründen tatsächlich mal die Zeichenkette `\n' und keinen Zeilenumbruch ausgeben lassen, muss man
einen Backslash vorstellen:
p r i n t f ( " Ich
/*
Ausgabe
ist :
zeige
Ich
ein
zeige
\\ n
ein
an \ n " ) ;
\n an * /
Das Komma nach dem ersten Parameter sowie der zweite Parameter
...
werden
bei konstanten Zeichenketten einfach weggelassen.
B.1.1
Zeichenketten mit Platzhalter der 1. Parameter von
printf
Zunächst etwas komplizierter sind die Ausgaben von Zeichenketten, deren Inhalt
variabel sein soll; er hängt von irgendwelchen Variablen oder Werten ab. Zunächst
einmal schreibt man den 1. Parameter fast wie bei konstanten Zeichenketten. Der
Unterschied ist, dass an den Stellen, wo variable Inhalte ausgegeben werden sollen,
Platzhalter hingeschrieben werden.
Platzhalter
Die Platzhalter sind von der Form
%<− oder +><Mindestbreite><Praezision><Umwandlungszeichen>
Das liest sich sehr kompliziert, aber ist gar nicht so schwer. Man muss nur richtig
einsetzen:
<Umwandlungszeichen>: Legt fest, was für eine Art von Wert bzw. ob ein
String ausgegeben wird. Dieses muss immer gegeben sein, wenn man einen
Platzhalter mit
Zeichen
i
f
lf
e
E
c
s
%
anfängt.
Datentyp
int
float
double
double/float
double/float
char
Array von char-Variablen
Anmerkung
Standard: 6 Nachkommastellen
Standard: 6 Nachkommastellen
wissenschaftl. mit
wissenschaftl. mit
e
E
String siehe 6.3, S. 25
Tabelle 2: Umwandlungszeichen für Formatangaben
38
Wissenschaftliche Schreibweise: Der
double-Wert oder float-Wert wird
mit einer Zehnerpotenz multipliziert, so dass genau eine Vorkommastelle
ungleich Null geschrieben steht.
exp wird
eexp bzw. Eexp mit mindestens zwei Ziern und Vorzeichen es wird
Der Exponent vom multiplikativ Inversen dieser Zehnerpotenz
als
mit Nullen aufgefüllt hinten angestellt.
Man kennt dies eventuell von Taschenrechnern bei sehr groÿen oder sehr
kleinen Zahlen. Die Gleitkommazahl 103.4536473 würde z.B. (mit der
Standardpräzision) als
1.034536+e02 dargestellt bzw. als 1.034536+E02.
Optional:<- oder +>: Man setzt keines, eines oder beide dieser Zeichen. Das
Zeichen
-
sorgt dafür, dass der Wert oder der String bzw. Zeichenkette links-
gbündig ausgegeben werden (standardmäÿig wird er rechtsbündig ausgegeben). Das Zeichen
+
sorgt dafür, dass Zahlenausgaben immer mit Vorzeichen
stattnden.
Optional:<Mindestbreite der Ausgabe>: Mindestanzahl der Zeichen, mit denen der Wert oder String ausgegeben wird. Es handelt sich hierbei um alle
Zeichen, welche zur Ausgabe des Platzhalters gehören. Hat der auszugebende
Wert oder String in der Ausgabe gemäÿ Umwandlungszeichen normalerweise
weniger Zeichen, so wird die Dierenz mit Leerzeichen aufgefüllt bei linksbündiger Ausgabe nach rechts und standardmäÿig nach links.
Optional:<Präzision>: Ganzzahl mit einem vorgestellten
über die Umwandlungszeichen
f, lf
,
e
oder
E
..
Bei der Ausgabe
wird über die Präzision die
Anzahl der angezeigten Nachkommastellen eingestellt. Bei der Ausgabe über
i wird damit die Mindestanzahl von Ziern angeint-Wert ausgegeben werden soll. Hat der int-Wert
das Umwandlungszeichen
geben, mit welcher der
weniger Ziern, so wird die Dierenz nach links mit Nullen aufgefüllt.
B.1.2
Zeichenketten mit Platzhalter der 2. Parameter von
34
printf
Hat man Zeichenketten mit Platzhaltern, so wird der 2. Parameter der
printf-
Funktion benötigt. Er listet auf, welche Variablen bzw. Werte / Strings ausgegeben
werden sollen. Es handelt sich beim 2. Parameter von
printf selbst also wieder um
eine Parameterliste. Die Reihenfolge dieser Sub-Parameter
henfolge der Platzhalter im 1. Parameter von
printf.
entspricht der Rei-
Sie werden wie gewöhnliche
Parameter mit Kommata voneinander getrennt. Das Umwandlungszeichen
tet ein Array von
char-Variablen
s erwar-
dieses übergibt man einfach mit dem Namen.
Quelltext 19 liefert ein Beispiel.
34 Ist
die Präzision beim Umwandlungszeichen
i
kleiner als die Mindestbreite der Ausgabe und
wird rechtsbündig ausgegeben, so wird die Dierenz zwischen Mindestbreite der Ausgabe und
Präzision mit Nullen aufgefüllt. Ansonsten wird nur mit Nullen nach links aufgefüllt.
39
1
/ * angenommen ,
2
i n t i =0;
double z a h l = 0 . 0 2 5 4 3 4 5 , z a h l A r r [ 3 ] = { 1 , 2 . 5 6 , 3 . 0 0 } ;
char z K e t t e [ 2 0 ] = " Das s i n d 1 9 Z e i c h e n " ; / * mehr B u c h s t a b e n
3
4
passen
man h a t
i n Form
folgende
eines
Definitionen
Strings
auch
nicht
*/
in
dieses
Array
*/
5
6
i =19;
/*
setze
i
auf
19 * /
7
8
p r i n t f ( " zKette
enthaelt
f o l g e n d e %i
Z e i c h e n : \ n%s \ n " ,
i ,
zKette ) ;
9
/*
in
10
in
" Parameter
" Parameter
dann
1"
2"
finden
folgen
sich
in
die
P l a t z h a l t e r %i ,
entsprechender
analog
12
printf (" zahl
hat
Wert % l f \ n " ,
13
printf (" zahl
hat
Wert %+.7 l f \ n " ,
14
printf (" zahl
hat
Wert
15
printf (" zahl
hat
Wert % − 11.7 l f ! \ n " ,
16
printf (" zahl
hat
Wert
%.7 e \ n " ,
zahl ) ;
17
printf (" zahl
hat
Wert
%.7E\ n " ,
zahl ) ;
18
printf (" zahl
hat
Wert
%10.2E\ n " ,
19
p r i n t f ( " der
geht
es
printf (" int
Quelltext
19:
zahl ) ;
zahl ) ;
%11.7 l f ! \ n " ,
von
zahl ) ;
zahl ) ;
zahl ) ;
zKette
ist
%c \ n " ,
zKette [ 2 ] ) ;
*/
1.
0!
*/
Buchstabe
2!
printf (" die
Index
weiter . . .
3.
/* I n d e x
21
i ,
zKette
11
20
dann %s .
Reihenfolge
Zahl
von
zahlArr
ist
%l f \n" ,
z a h l A r r [ 0 ] ) ; /*
*/
hat
den
Wert
%5.3 i \ n " ,
Beispiel für Aufrufe von
i) ;
printf
für variable Ausgaben. Siehe
Abbildung 10 für die resultierenden Ausgaben
Man hat dann die Ausgaben aus Abbildung 10.
zKette
Das
enthaelt
sind
19
folgende
19
Zeichen :
Zeichen
zahl
hat
Wert
0.025434
zahl
hat
Wert
+0.0254345
zahl
hat
Wert
zahl
hat
Wert
0.0254345
zahl
hat
Wert
2 . 5 4 3 4 5 0 0 e −02
zahl
hat
Wert
2 . 5 4 3 4 5 0 0 E−02
zahl
hat
Wert
0.0254345!
der
3.
Buchstabe
die
1.
Zahl
von
!
2 . 5 4 E−02
von
zKette
zahlArr
ist
ist
s
1.000000
Abbildung 10: Die aus den Aufrufen in Quelltext 19 resultierenden Ausgaben.
Möchte man tatsächlich ein
%-Zeichen
ausgeben, so kann man dieses wie in B.1
beschrieben mit einem vorgestellten \ tun (also statt `%' die Zeichen `\%').
40
B.1.3
Wie die Zeichen ausgegeben werden
Die vorgegebene Ausgabe ndet Zeichen für Zeichen von links nach rechts statt.
Zeilenumbrüche müssen mit dem Spezialzeichen
B.2
\n
manuell vorgenommen werden.
Einlesen in char-Arrays von der Tastatur fgets
char-Arrays eingelesen
RETURN mit eingelesen, so dass der String im char-Array
Mit dieser Funktion können Strings von der Tastatur in
werden. Allerdings wird das
35 Die Eingabe soll von der Tastatur kommen.
nicht direkt verwendet werden soll.
Die Deklaration lautet:
char* fgets(char* s, int size, FILE* stream);
Funktionstyp:
Die Funktion ist vom Datentypen
char* , aber auch hier soll das
nicht weiter von Bedeutung sein und Rückgabewerte nicht übernommen werden.
1. Parameter char* s:
Hier soll ein Array von
char-Variablen
übergeben
werden. In dieses wird ein String eingelesen.
2. Parameter int size:
Hier soll der Wert die Gröÿe des Arrays sein, welches
als 1. Parameter übergeben wird.
array, welches mit der Anchar array[20] deniert wurde, so ist der 2. Parameter die int-Konstante
Übergibt man zum Beispiel als 1. Parameter das Array
weisung
20
. Diese Angabe verhindert Schreibversuche des Programms in ihm nicht zuge-
ordneten Speicherbereich.
3. Parameter FILE* stream:
Bei fgets soll hier immer
stehen. Das bewirkt das Einlesen von Tastatur.
B.3
36
stdin
geschrieben
Einlesen in char-Arrays oder Variablen von Strings oder
char-Arrays sscanf
Diese Funktion wird genutzt, um aus einem String oder
riablen oder
char-Arrays
char-Array
Werte in Va-
einzulesen. Die Deklaration lautet:
int sscanf(const char* str, const char* format, ...);
Funktionstyp:
Die Funktion ist vom Datentypen
int,
aber das soll in diesem
Skript nicht von Bedeutung sein. Die Rückgabewerte können Sie ignorieren.
35 Es
gibt Knie, dies zu vermeiden, aber diese werden hier nicht beschrieben, sondern es wird
auf B.3 verwiesen.
36 Zusatz-Anmerkung:
Dateien, einzulesen.
Es besteht auch die Möglichkeit von anderen Quellen, wie z.B. geöneten
41
1. Parameter const char* str:
Hier soll ein Array von
char-Variablen über-
geben werden, welches einen String enthält. Aus diesem werden der String oder die
Variablen eingelesen. Man übergibt auch hier das Array mit seinem Namen.
const char* format:
2. Parameter printf
Es handelt sich hier ähnlich wie bei
um einen Ausdruck der Form
<Zeichenkette>
Mit dieser Zeichenkette gibt man vor, nach was man in dem aus dem 1. Parameter erhaltenen sucht. Für die Zwecke dieser Vorlesung werden hier nur einfache
Konstrukte erörtert. Erlaubt sind Leerzeichen, Tabs und Platzhalter, welche wie in
printf mit einem %-Zeichen anfangen. Ein Leerzeichen zwischen zwei Platzhaltern
gibt an, dass nach einem dem 1. Platzhalter entsprechenden Wert beliebig viele
Leerzeichen, Tabs oder Zeilenumbrüche folgen können auch gar keines dieser Zeichen und dann ein Wert, welcher dem 2. Platzhalter entspricht.
Die Platzhalter fangen wie bei
printf
mit einem
%-Zeichen
an. Danach folgt eines
der Zeichen
i , c , f ,
lf
, s
Diese sind stellvertretend für die Datentypen entsprechend Tabelle 2 auf S. 37.
Wichtig: Strings aus Strings korrekt einzulesen ist etwas komplizierter, wenn sie
Leerzeichen enthalten. Das wird in dieser Vorlesung auch nicht vorkommen. Möch-
s gemäÿ
charArray, in welches eingelesen wird, muss dabei min37
destens so groÿ sein, wie das char-Array, aus welchem eingelesen wird. . Möchte
man also aus dem String des 1. Parameters einen int einlesen, so schreibt man im
2. Parameter "%i" und sscanf wird den ersten Integer in der Zeichenkette des 1.
te man einzelne Wörter einlesen, so kann man das Umwandlungszeichen
Tabelle 2 benutzen. Das
Parameters einlesen.
3. Parameter:
printf handelt es sich auch hier um eine Sub-Parameterprintf so geordnet, wie die Platzhalter des vorherigen Para-
Wie bei
liste. Sie ist wie bei
meters. Anstatt aber die Datentypen der einzulesenen Werte hinzuschreiben, muss
char-Arrays werden mit
int einlesZahl; de&einlesZahl, damit in die
man Pointer auf die jeweiligen Datentypen hinschreiben ihrem Namen angegeben. Hat man z.B. eine Variable mit
niert, in welche man einlesen möchte, so schreibt man
Speicheradresse von
B.4
einlesZahl
38
eingelesen wird.
Einlesen in Variablen oder char-Arrays von Tastatur
Man möchte des Öfteren Eingaben des Nutzers von der Tastatur in Variablen oder
char-Arrays einlesen können.
char-Array zu machen:
Die beste Methode ist, dazu den Umweg über ein
1. String von Tastatur einlesen (in ein Array von
char-Variablen),
siehe B.2
2. Werte für die Variablen aus dem gerade eingelesenen String einlesen, siehe B.3
37 Es gibt auch
38 Siehe 5.1
andere Lösungen, diese ist jedoch einfacher...
42
und man hat, was man wollte. Abbildung 11 verdeutlicht dies. Quelltext 20 liefert
ein Beispiel.
Abbildung 11: Das Einlesen von Tastatur verdeutlicht
43
1
#include <s t d i o . h>
2
3
4
5
i n t main ( void ) {
char b u f f e r [ 1 0 0 ] ,
double z a h l ;
papagei [ 1 0 0 ] ;
6
p r i n t f ( " Geben
7
Sie
eine
Gleitkommazahl
zum
Einlesen
ein :
"
) ;
/* E i n l e s e n
8
von
der
fgets ( buffer
9
kompletten
Eingabe
b i s RETURN i n
buffer
T a s t a t u r */
,
10
/* E i n l e s e n
11
sscanf ( buffer ,
12
p r i n t f ( "% l f
100
,
vom Wert
stdin
fuer
);
zahl
aus
"% l f "
, &z a h l ) ;
die
eingegebene
ist
b u f f e r */
Zahl \n" ,
zahl ) ;
13
/* E i n l e s e n
14
der
kompletten
Eingabe
bis
Return
in
das
Array
name * /
p r i n t f ( " Ich
15
bin
ein
Papagei .
Schreiben
Sie
mir
ein
Wort . \
n" ) ;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16
17
papagei
soll
18
Groesse
100
mind .
Groesse
100
19
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
20
fgets ( buffer ,
21
s s c a n f ( papagei ,
100
,
da
buffer
stdin ) ;
,
s t d i n ) ; /*
22
p r i n t f ( "BWAAAAK! %s " ,
papagei ) ;
23
return 0 ;
100
haben ,
hat
100
Groesse
von
Papagei
ist
*/
24 }
Quelltext 20: Kompilierbares Beispiel für das Einlesen einer Variable und eines
Strings von Tastatur
B.5
Önen und Schlieÿen von Dateien fopen und fclose
fopen önet Dateien, fclose schlieÿt sie. Man sollte nie vergessen, geönete Dateien
zu schlieÿen. Gewöhnlicherweise tut man dies, nachdem man sie nicht mehr braucht.
B.5.1
Der Datentyp FILE*
Man benötigt für den Zugri auf Dateien einen neuen Datentypen, den Dateizeiger
FILE*
welcher mit der
stdio-Bibliothek
kommt. Sie brauchen nur zu wissen, wie
man sich Variablen diesen Typs deniert. Und zwar mit
FILE *
<Name> ;
/* k o n k r e t e s
FILE *
B e i s p i e l : */
datZeiger ;
datZeiger
/*
erstellt
Dateizeiger
m i t dem Namen
*/
Innerhalb eines Programmes ist jede Datei eindeutig identiziert durch ihren Dateizeiger.
44
B.5.2
fopen
Die Deklaration lautet für
fopen
FILE* fopen(const char* path, const char* mode);
FILE*:
Funktionstyp Die Funktion gibt bei erfolgreichem Önen den Datei-
zeiger auf die geönete Datei zurück. Konnte die angegebene Datei nicht geönet
werden (Gründe wären da z.B. falscher Pfad, fehlende Rechte etc.), dann wird als
FILE*
-Wert die Konstante
NULL
zurückgegeben.
Damit man was mit der geöneten Datei anfangen kann, muss der Rückgabewert
immer in einer Variable vom Typen
1. Parameter FILE*
const char* path:
gespeichert werden!
Hier soll zwischen doppelten Anführungs-
inputDat.inp z.B. bei ei/home/studiname/programm/inputDat.inp
strichen der Pfad zu einer Datei stehen. Hat die Datei
nem Linux-basierten System den Pfad
, so übergibt man die Zeichenkette
"/home/studiname/programm/inputDat.inp"
2. Parameter const char* mode:
Hier wird festgelegt, ob die Datei
zum Lesen geönet wird dann übergeben Sie die Zeichenkette
"r"
(inklusive
der Anführungszeichen)
oder
zum Schreiben geönet wird dann übergeben Sie einfach
"w"
Ein Beispiel folgt in B.5.3.
B.5.3
fclose
Die Deklaration lautet für
fclose
int fclose( FILE* );
Funktionstyp int:
Soll hier nicht von Bedeutung sein, d.h. Rückgabewerte
können ignoriert werden.
Einziger Parameter FILE*
Hier wird der Dateizeiger für die zu schlieÿende
Datei angegeben.
Wichtig:
Jedes Önen mit
fopen
im Schreibmodus, also mit
"w"
als zweiten
Parameter löscht die Inhalte der Datei, wenn sie schon existiert! Existiert sie noch
45
nicht, wird sie angelegt.
Ein exemplarischer Aufruf mit Prüfung, ob die Datei erfolgreich geönet werden
konnte, könnte so aussehen:
1 FILE *
datLes ,
//
datSchreib ;
Definition
der
D a t e i <Zeiger>
2
3
"<Datei
datLes = fopen (
1>"
,
"r"
//<Datei
)
1>
zum L e s e n
oeffnen
4
5
i f ( d a t L e s != NULL ) {
/* d i e
6
folgenden
A n we i s u ng e n
werden
nur dann
ausgefuehrt ,
wenn
7
das
8
printf (
Oeffnen
" Oeffnen
erfolgreich
/*
wird
der
war * /
Datei
e r f o l g r e i c h \n"
) ;
9
10
hier
<Anweisungen>
11
irgendwas
mit
der
Datei
<Datei 1>
gemacht
*/
12
fclose (
13
datLes
)
/* s c h l i e " s e n
;
der
Datei
*/
14 }
15
else
16
p r i n t f ( " Datei
konnte
ohne
*/
nicht
geoffnet
werden " ) ;
17
18
/*
19
datSchreib = fopen (
Pruefung
Schreiben
"<Datei
2>"
,
"w"
)
//<Datei
2>
zum
oeffnen
20
21
/*
hier
wird
22
<Anweisungen>
23
fclose (
B.6
irgendwas
datSchreib
) ;
mit
/*
der
Datei
schliessen
<Datei 2>
von
gemacht
<Datei 2>
*/
*/
Ausgabe in Dateien fprintf
Die Deklaration lautet für
fprintf
int fprintf(FILE *stream, const char *format, ...);
Funktionstyp int:
1. Parameter FILE* stream:
Für diese Vorlesung nicht weiter von Bedeutung.
Gibt an, in welche (geöneten) Datei eingelesen
werden soll (diese muss im Schreibmodus geönet worden sein).
2. Parameter const char *format:
Wie der 2. Parameter in B.1, nur wird
hier nicht auf dem Bildschirm, sondern in eine Datei ausgegeben.
3. Parameter const char *format:
Genau wie der 3. Parameter in B.1.
46
Die vorgegebene Ausgabe ndet in die Textdatei Zeichen für Zeichen von links
nach rechts statt solange die Datei geönet geblieben ist, wird weiter ab dem
letzten ausgegebenen Zeichen des vorherigen Aufrufes von
Zeilenumbrüche müssen mit dem Spezialzeichen
\n
fprintf
ausgegeben.
manuell vorgenommen werden.
Quelltext 21 gibt ein Beispiel für die Ausgabe in eine Datei.
1
#include < s t d i o . h>
2
3
i n t main ( void ) {
4 FILE *
datout ;
char b u f f e r [ 1 0 0 ] ,
int zahl ;
speicher [100]
8
printf (" Bitte
eine
9
fgets ( buffer
5
6
,
wort [ 1 0 0 ] ;
7
Einlesen
gib
,
10
/*
11
wie
12
sscanf ( buffer ,
buffer
100
,
Datei
c h a r −Array
in
Groesse
100
"%s "
zum
Hineinschreiben
an :
") ;
stdin ) ;
,
speicher ,
wort
hat
! */
speicher ) ;
13
14
printf (" Bitte
15
fgets ( buffer
gib
16
sscanf ( buffer ,
,
i n t −Z a h l
eine
100
s t d i n ) ; /*
,
"%i "
, &z a h l
);
ein :
") ;
Einlesen
/*
in
Einlesen
buffer
in
Zahl
*/
*/
17
18
p r i n t f (" Schreib
19
/*
Einlesen
in
20
fgets ( buffer
21
/*
22
wie
23
sscanf ( buffer ,
Einlesen
buffer
ein
Wort :
buffer
,
100
in
,
stdin ) ;
Array
Groesse
wort ,
100
"%s "
") ;
*/
,
wort
hat
! */
wort
) ;
24
25
/ * zum
26
datout = fopen
Schreiben
(
27
/*
Zahl
Ausgabe
28
fprintf
29
/*
(
fprintf
31
/*
32
fclose
(
der
(
,
,
der
datout
mit
mit
"w"
)
;
,
zahl
)
;
)
;
\n * /
"%s \ n "
Datei
,
\n * /
"%i \ n "
Zahl
datout
schliessen
*/
speicher
datout
Ausgabe
30
der
oeffnen
,
wort
*/
) ;
33
34
return 0 ;
35 }
Quelltext 21: Kompilierbares Beispiel für
B.7
fprintf.
Einlesen von Dateien fscanf
Die Deklaration lautet für
fscanf
int fscanf(FILE* stream, const char *format, ...);
1. Parameter FILE* stream:
Gibt an, von welcher (geöneten) Datei einge-
lesen werden soll (diese muss im Lesemodus geönet worden sein).
47
2. Parameter const char *format:
Wie der 2. Parameter in B.3, nur wird
hier Text aus einer Datei eingelesen.
3. Parameter const char *format:
int:
Funktionstyp Genau wie der 3. Parameter in B.3 .
Die Funktion gibt an, in wie viele der durch die Sub-
Parameter des 3. Parameters referenzierten Variablen bzw.
char-Arrays
erfolgreich
eingelesen werden konnte. Es handelt sich also um die Anzahl der erfolgreichen
Einlesevorgänge des Funktionsaufrufes.
Es folgt das letzte Beispiel dieses Skriptes. Angenommen, die Inhalte der Datei
textdatei,
welche (auf einem Linux-System) im Programmverzeichnis liegt, sind
Fabian 1.8
Wenn das Programm nach folgendem Quelltext liefe, würden Name und Körpergröÿe eingelesen:
1
#include < s t d i o . h>
2
3
i n t main ( void ) {
4
/*
Variablendefinitionen
5 FILE *
6
7
*/
datin ;
char <Name> [ 1 0 0 ] ;
double g r o e s s e ;
8
9
/*
O e f f n e n zum Lesen ,
des
10
DateiZeigers
11
datin = fopen
12
/* E i n l e s e n
Es muss
13
14
gemacht
15
<Name>
16
groesse
17
fscanf
18
printf (
(
des
hier
Uebernahme
*/
" ./ textdatei "
Strings
und
der
k e i n Umweg u e b e r
,
"r"
);
Zahl
fgets
werden !
ist
schon
noch
(
eine
nicht ,
datin
,
Speicheradresse ,
daher
"%s % l f "
"Name : %s ,
d a s &−Z e i c h e n
,
<Name>
*/
, &g r o e s s e )
K o e r p e r g r o e s s e : %l f \n" ,
;
<Name>
,
groesse
)
;
19
20
return 0 ;
21 }
Quelltext 22: Aufruf von fscanf für das Einlesen der Werte aus der Datei
textdatei
Herunterladen