 |
Programmieren in C
von Prof. Jürgen Plate |
1 Datenstrukturen
Programm-Dokumentation ist wie Sex
- ist er gut, dann ist es
sehr, sehr gut;
ist er schlecht, ist es besser als gar nichts.
1.1 Datenorientierte Programmentwicklung
In der kommerziellen Datenverarbeitung treten sehr oft
Problemstellungen auf, bei denen es weniger auf die Algorithmen
ankommt (weil diese sehr einfach sind), sondern auf die Umformung
der gegebenen Daten. So gibt es zum Beispiel Listengeneratoren;
das sind Programme, die Listen erzeugen, also nichts anderes tun
als Daten, die bereits bekannt sind, in eine übersichtliche Form
zu bringen. Für die Aufgabenstellung ist der Algorithmus selbst
von geringer Bedeutung. Hier kommt es in erster Linie auf die
Ein- und Ausgabedaten sowie deren Umformung an. So hat sich neben
der problemorientierten Programmentwicklung die datenorientierte
Programmentwicklung herausgebildet. Hier wird zuerst die
Datenstruktur für jede zu verarbeitende Datenmenge definiert.
Danach stellt man fest, welche Arten der Zuordnung existieren.
Dabei sollen
- Sequenzen (der Datentyp A besteht aus B gefolgt von C
gefolgt von D),
- Wiederholungen (der Datentyp E besteht aus einem oder
mehreren Datentypen F) und
- Auswahlen (der Datentyp G besteht entweder aus dem Typ H
oder J)
unterschieden werden. Dann werden alle Einzeloperationen in
ungeordneter Reihenfolge aufnotiert und anschließend jede
Operation den Datenstrukturdiagrammen und den daraus entwickelten
Programmstrukturen zugeordnet. Dieses Entwurfsprinzip ist aber
ausschließlich für umformende Problemstellungen
geeignet.
Wichtig für die allgemeinen Probleme ist, daß die Eingabe-
und Ausgabedaten eines Programms genau analysiert und
strukturiert werden. Das führt meist auch zu kürzeren und
übersichtlicheren Programmen. Bei der Datenanalyse können Sie
folgendermaßen vorgehen:
- Welche Daten sollen ausgegeben werden?
- Welche Daten werden zur Berechnung benötigt? (Je weniger
das sind, desto besser! Das Prinzip "am besten nehme
ich alle erreichbaren Daten und suche mir nachher die
benötigten raus!" führt nämlich in den meisten
Fällen zu einem Datenmüll, der nicht mehr zu
bewältigen ist.)
- Welche Werte können die Daten annehmen?
- Welche der so gefundenen Daten (die sogenannten
Datenelemente) gehören zusammen?
- Welche Struktur haben zusammengehörende Daten:
- Ist die Anzahl fest oder variabel?
- Sind stets alle Daten relevant?
- Bilden verschiedene Datenelemente einen neuen
Datentyp?
Es hat sich auch beim Entwurf von Programmiersprachen schnell
herausgestellt, die zu verarbeitenden Daten nach ihrer Art, ihrem
Typ, zu unterscheiden. In strukturierten Sprachen ist es
üblich, alle verwendeten Variablen am Anfang eines Programmes
summarisch aufzuführen. Das trägt zu dessen Verständlichkeit
und Leserlichkeit wesentlich bei. Insbesondere gehört zu einer
guten Dokumentation die explizite Angabe des Wertebereiches einer
jeden Variablen. Die wichtigsten Gründe dafür sind die
folgenden:
- Die Kenntnis des Wertebereiches einer Variablen ist
entscheidend für das Verständnis eines Algorithmus.
Ohne explizite Angabe des Wertebereiches ist es meistens
schwierig, festzustellen, welche Art von Objekten eine
Variable repräsentiert, und die Ermittlung möglicher
Fehler wird erheblich erschwert.
- Die Zweckmäßigkeit und die Korrektheit eines Programms
sind in den meisten Fällen abhängig von den
Anfangswerten der Argumente, und sie sind nur für
bestimmte Bereiche garantiert. Daher gehört es zur
Dokumentation eines Algorithmus, daß diese Bereiche
explizit aufgeführt sind.
- Der Speicherbedarf für die Repräsentation einer
Variablen im Speicher eines Computers hängt von deren
Wertebereich ab. Damit ein Compiler die nötigen
Speicherzuordnungen vornehmen kann, ist die Kenntnis der
Wertebereiche unerläßlich.
- Operatoren, die in Ausdrücken vorkommen, sind nur für
gewisse Wertebereiche ihrer Argumente wohldefiniert. Ein
Compiler kann auf Grund der angegebenen Wertebereiche
prüfen, ob die vorkommenden Kombinationen von Operatoren
und Operanden zulässig sind. Die Angabe der
Wertebereiche der Variablen dient also als Redundanz
zur Überprüfung des Programms während seiner
Übersetzung.
- Die Realisierung von Operatoren in Rechenanlagen hängt
unter Umständen von den Wertebereichen der Operanden ab.
Ihre Kenntnis ist daher oft zu einer zweckmäßigen und
effizienten Realisierung unerläßlich. So werden z. B.
in den meisten Rechenanlagen verschiedene interne
Darstellungsarten für ganze und für reelle Zahlen
verwendet, und die detaillierten Abläufe von
arithmetischen Operationen unterscheiden sich stark für
die beiden Darstellungsweisen.
- Eine automatische Prüfung von Grenzverletzungen während
das Programm abläuft durch das Laufzeitsystem ist
möglich --> sichere Programme (auf Kosten der
Geschwindigkeit).
1.2 Datentypen
An dieser Stelle wird nur allgemain auf Datentypen sowie
deren Bedeutung und Eigenschaften eingegangen, wobei aber
teilweise schon die Notation der Sprache C verwendet wird. In
einem späteren Kapitel werden die Besonderheiten der Datentypen
in C ausführlicher behandelt.
Der Wertebereich einer Variablen spielt eine derart wichtige
Rolle in der Charakterisierung einer Variablen, daß er als deren
Typ bezeichnet wird. Die Werte eines Bereichs werden als
Konstanten von diesem Typ bezeichnet. Es wird daher die bindende
Konvention eingeführt, daß in jedem Programm alle Variablen
vereinbart werden. Dies geschieht durch Angabe einer Liste der
gewählten Variablen-Bezeichnungen und deren Typen. Eine
Variablen-Vereinbarung habe allgemein die Form
TYP variable
wobei "variable" eine Variablen-Bezeichnung und
"TYP" der Typ der Variablen ist. Falls mehrere
Variablen vom gleichen Typ vereinbart werden sollen, wird die
Kurzform
TYP v1, v2
,..., vn (n > 1)
verwendet, wobei v1 ... vn
Variablenbezeichnungen sind und TYP wiederum entweder eine
Typenbezeichnung oder eine Typenbeschreibung ist. Die Konvention,
alle verwendeten Bezeichnungen am Anfang eines Programms zu
vereinbaren, hat zudem den großen Vorteil, daß ein Compiler bei
jeder im Programm vorkommenden Bezeichnung prüfen kann, ob sie
überhaupt vereinbart ist. Im negativen Fall, der z. B. durch
Schreibfehler verursacht wird, kann der Compiler durch eine
entsprechende Fehlermeldung auf den Lapsus aufmerksam machen. Die
zusätzliche Redundanz wird also wiederum zur Erhöhung der
Programmiersicherheit ausgenutzt. Wie werden Datentypen in einem
Programm eingeführt, und welche Wertbereiche können durch
Computer zweckmäßig dargestellt werden? Vorerst sei bemerkt,
daß es üblich ist, Datentypen in verschiedene Arten
einzuteilen. Das wesentliche Merkmal eines Typs ist die Struktur
seiner Werte. Ist ein Wert unstrukturiert, also nicht in einzelne
Komponenten zerlegbar, so wird er - und damit auch sein Typ - als
skalar bezeichnet. Ist er dagegen in einzelne Komponenten
zerlegbar, nennt man ihn strukturiert.
Konstante
Konstante sind Datenwerte, die direkt in der Anweisungsfolge
eines Programms eingetragen werden können (Standardbezeichnung,
z. B. Zahlen). Als Konstante bezeichnet man üblicherweise auch
Namen für Datenwerte (frei wählbare Bezeichnung). Durch die
Vereinbarung von Konstanten-Namen wird ein ganz bestimmter
Datenwert mit einem Namen versehen und ist über diesen Namen
jederzeit zugreifbar. Der Wert einer Konstanten kann nicht
geändert werden. Konstante dienen der Übersichtlichkeit und
Lesbarkeit von Programmen; z. B.:
Pi = 3,1415
Maximum = 10000
Autor = "Johann Wolfgang von Goethe"
Bei der Programmiersprache C findet man zwei Varianten der
Konstantenvereinbarungen:
const float Pi = 3.1415
#define Pi 3.1415
Die erste Form entspricht dem aktuellen ANSI-Standard und ist
zu bevorzugen. Die zweite Variante ist älter und definiert
eigentlich nur ein Makro. Vor der eigentlichen Übersetzung wird
im gesamten Text die Zeichenkette "Pi" durch
"3.1415" ersetzt.
Variablen
Anstelle der behandelten Datengrößen werden im Programm
Namen eingesetzt, die als variable Größen oder kurz als
"Variablen" bezeichnet werden. Jede Variable besitzt
also einen Namen und einen Wert. Sie stellt somit einen
"Behälter" für ihren Wert dar. Der Compiler
reserviert entsprechend dem Typ eine bestimmte Menge
Speicherplatz für den "Behälter". Die Variable kann
als Benennung von einem oder mehreren Speicherworten aufgefaßt
werden.
In C gibt es zwei verschiedene Arten von Vereinbarungen, Definitionen und
Deklarationen.Der Begriff der Vereinbarung umfasst sowohl die Definition als
auch die Deklaration.
Definitionen
- legen die Art der Variablen fest:
- den Typ,
- die Speicherklasse,
- Typ-Attribute (type qualifier) wie const und volatile fest
- sorgen gleichzeitig für die Reservierung des Speicherplatzes.
Deklarationen legen nur die Art der Variablen bzw. die
Schnittstelle der Funktionen, d. h. die Funktionsköpfe, fest.
Während Definitionen von Variablen und Funktionen dazu
dienen, Datenobjekte bzw. Funktionen im Speicher anzulegen,
machen Deklarationen Datenobjekte bzw. Funktionen bekannt,
die in anderen Übersetzungseinheiten definiert werden oder in
derselben Übersetzungseinheit erst nach ihrer Verwendung
definiert werden.
Eine Deklaration umfasst stets den Namen eines Objektes und
seinen Typ. Damit weiß der Compiler, mit welchem Typ er einen
Namen verbinden muß. Kurz ausgedrückt:
|
Definition = Deklaration + Reservierung des Speicherplatzes
|
Die Zuweisung eines Wertes an eine Variable ist eine
fundamentale Operation in Computerprogrammen. Eine Variable zeigt
eine Verhaltensweise, die einer Wandtafel ähnlich ist: Sie kann
jederzeit gelesen werden (sie liefert den Wert) andererseits
ausgewischt und überschrieben werden. In fast allen höheren
Programmiersprachen müssen Variablen vor ihrer Verwendung
deklariert werden - so auch in C. Es wird dabei der Typ der
Variablen (siehe unten: Datentypen) und ein Bezeichner angegeben.
Z. B.:
int Anzahl, Summe, I, J;
float DM_Betrag;
Die Zuweisung eines Wertes an eine Variable wird im
allgemeinen durch das Operatorzeichen = bezeichnet (zur
Unterscheidung vom Gleichheitszeichen verwenden manche
Programmiersprachen auch einen Linkspfeil oder ":="):
Wichtig ist die Unterscheidung zwischen Wertzuweisung und
Gleichung im mathematischen Sinn. So würde die mathematische
Gleichung
X = X + 1
wenig Sinn machen (es gibt nämlich keine Lösung), in einer
Programmiersprache bedeutet der Ausdruck jedoch "Addiere 1
zum Wert von X und speichere das Ergebnis wieder in X" oder
kürzer "Erhöhe X um 1". Noch ein Beispiel:
In C ist eine Wertzuweisung auch bei der Definition erlaubt.
Es handelt sich um ein sehr sinnvolles Feature, denn eine
Variable besitzt nach der Deklaration noch keinen definierten
Wert. Nicht initialisierte Variablen (d. h. Variablen ohne
Anfangswert) sind oft die Ursache von Programmfehlern. Beispiel:
int I = 0, Anzahl = 111;
float X = 0.0;
Merke: Steht die Variable auf der rechten Seite einer
Zuweisung, bezeichnet sie ihren Inhalt. Steht die Variable auf
der linken Seite einer Zuweisung bezeichnet Sie einen Behälter.
Ausdrücke
Ein Ausdruck (engl. Expression) ist eine Formel (eine
Rechenregel), die stets einen (Resultat-)Wert liefert. Der
Ausdruck besteht aus Operanden (Konstante, Variablen und
Funktionen) und Operatoren. Operatoren werden üblicherweise
eingeteilt in
- monadische Operatoren = einstellige Operatoren
(z. B.
Vorzeichen +/- vor Zahlen, NOT-Operator vor Binärwerten)
- dyadische Operatoren = zweistellige Operatoren
(z. B. die
Operatoren der Grundrechenarten ( + - * / ), AND, OR bei
Binärwerten)
Sofern Ausdrücke mit mehr als einem Operator vorkommen, ist
die Reihenfolge ihrer Ausführung eindeutig festzulegen. Dies
kann durch drei Möglichkeiten erfolgen:
- Reihenfolge der Aufschreibung von links nach rechts
- allgemeine Vorrangregeln (beim Rechnen "Punkt vor
Strich")
- Klammerung um die einzelnen Operanden
Die Hierarchie nimmt in der oben aufgeführten Liste von oben
nach unten zu, d. h. Klammern binden stärker als Vorrangregeln
und diese stärker als die Reihenfolge. Der Begriff
"Ausdruck" beschränkt sich nicht auf arithmetische
Ausdrücke (z. B. a * y + b), es gibt auch logische oder
mengentheoretische Ausdrücke oder Ausdrücke, die Zeichen,
Zeichenfolgen oder Speicheradressen zum Ergebnis haben.
Ausdrücke und Anweisungen
Anweisungen und Ausdrücke sind nicht das Gleiche. Sie unterscheiden
sich durch den Rückgabewert:
- Ausdrücke haben immer einen Rückgabewert. Sie können damit
Teil eines größeren Ausdrucks sein.
- Anweisungen können keinen Rückgabewert haben. Sie können
damit nicht Teil eines größeren Ausdrucks sein.
Was ist aber nun genau der Rückgabewert? Das soll anhand des Ausdrucks
13 + 16.5 erklärt werden. Durch die Anwendung des Additionsoperators
+ auf seine Operanden 13 und 16.5 ist der Rückgabewert
des Ausdrucks eindeutig festgelegt. Aus den Typen der Operanden ergibt sich auch
der Typ des Rückgabewertes. Werden wie in diesem Beispiel unterschiedliche
Datentypen in einem Ausdruck verwendet, führt der Compiler
eine sogenannte implizite Typumwandlung nach vorgegebenen Regeln durch. Als erstes
prüft der Compiler die Typen der Operanden. Der eine Operand ist vom Typ int,
der andere vom Typ float. Damit ist eine Addition zunächst nicht
möglich. Es muß zuerst vom Compiler eine für den Programmierer
unsichtbare sogenannte implizite Typumwandlung der Zahl 13 in den Typ float
(also zu 13.0) durchgeführt werden. Erst dann ist die Addition möglich.
Der Rückgabewert der Addition ist die Zahl 29.5 vom Typ float.
Jeder Rückgabewert hat also auch einen Typ.
In C gibt es
- Selektionsanweisungen,
- Iterationsanweisungen,
- Sprungananweisungen und
- Ausdrucksanweisungen.
Die ersten drei Anweisungen werden im Kapitel 2 ausführlich behandelt, auf die
letze Form wird hier eingegangen.In C kann man durch Anhängen eines Semikolons
an einen Ausdruck erreichen, daß dieser zu einer Anweisung wird. Man spricht
dann von einer sogenannten "Ausdrucksanweisung". In einer solchen Ausdrucksanweisung
wird der Rückgabewert eines Ausdruckes nicht verwendet. Lediglich wenn Seiteneffekte
zum Tragen kommen, ist eine Ausdrucksanweisung sinnvoll. Dazu ein Beispiel:
int i = 0; /* Zuweisung eines Anfangswertes an die Variable */
5 * 5; /* zulaessig, aber nicht besonders sinnvoll */
/* Der Rueckgabewert von 5 * 5 wird nicht verwendet */
i++; /* Sinnvoll - i wird um 1 erhoeht (siehe spaeter) */
Generell gilt:
|
In C kann jeder Ausdruck eine Anweisung werden und einen Rückgabewert haben.
|
Bezeichner
Wie haben gesehen, daß man Konstante und Variablen mit frei
wählbaren Bezeichnungen (Namen) versehen kann. Daneben gibt es
in jeder Programmiersprache noch Bezeichner für Unterprogramme
(Prozeduren und Funktionen, siehe später) und sogenannte Standardbezeichner,
welche die Schlüsselworte (engl. "reserved words") der
Programmiersprache und vordefinierte Unterprogramme
(Standardfunktionen, Standardprozeduren) benennen und die in der
Regel nicht anderweit verwendet werden dürfen.
L-Werte und R-Werte
Ausdrücke habe eine unterschiedliche Bedeutung, je nachdem, ob sie links oder
rechts vom Zuweisungsoperator stehen. Wie schon erwähnt steht beim Ausdruck
a = b die rechte Seite für einen Wert, während der Ausdruck auf
der linken Seite die Stelle angibt, an der der Wert zu speichern ist. Wenn wir das
Beispiel noch etwas modifizieren, wird der Unterschied noch deutlicher:
a = a + 5*c
Beim Namen a ist rechts vom Zuweisungsoperator der Wert gemeint, der in der
Speicherzelle a gespeichert ist, und links ist die Adresse der Speicherzelle
a gemeint, in der der Wert des Gesamtausdrucks auf der rechten Seite
gespeichert werden soll. Aus dieser Stellung links oder rechts des Zuweisungsoperators
wurden auch die Begriffe L-Wert und R-Wert abgeleitet.
|
Ein Ausdruck stellt einen L-Wert ("lvalue" oder "left value") dar, wenn er sich
auf ein Speicherobjekt bezieht. Ein solcher Ausdruck kann links und rechts des
Zuweisungsoperators stehen.
Ein Ausdruck, der keinen L-Wert darstellt, stellt einen R-Wert ("rvalue" oder
"right value") dar. Er darf nur rechts des Zuweisungsoperators stehen. Einem
R-Wert kann man also nichts zuweisen.
|
Es gilt:
- Ein Ausdruck, der einen L-Wert darstellt, darf auch rechts vom Zuweisungsoperator
stehen. In diesem Fall wird dessen Namen bzw. Adresse benötigt, um an der
entsprechenden Speicherstelle den Wert der Variablen abzuholen. Dieser Wert wird dann
zugewiesen.
- Links des Zuweisungsoperators muss immer ein L-Wert stehen, da man den Namen bzw.
die Adresse einer Variablen braucht, um an der entsprechenden Speicherstelle den
zugewiesenen Wert abzulegen.
- Weiter wird zwischen modifizierbarem und nicht modifizierbarem L-Wert
unterschieden. Ein nicht modifizierbarer L-Wert ist z.B. eine Konstante oder der Name
eines Arrays. Dem Namen entspricht zwar eine Adresse. Diese ist jedoch konstant und
kann nicht modifiziert werden. Auf der linken Seite einer Zuweisung darf also nur ein
modifizierbarer L-Wert stehen.
- Bestimmte Operatoren können nur auf L-Werte angewandt werden. So kann man
den Inkrementoperator ++ oder den Adressoperator & nur auf L-Werte anwenden.
5++ ist falsch, i++ ist korrekt (sofern i eine Variable ist).
1.3 skalare Standardtypen
Wie schon erwähnt, ist die summarische Auflistung der
Variablen und Konstanten wichtig für die Verständlichkeit des
Programms. Insbesondere gehört zu einer guten Dokumentation die
explizite Angabe des Wertebereichs. Nachfolgend werden vier
äußerst häufige Standardtypen vorgestellt, die in jedem
Datenverarbeitungssystem als bekannt vorausgesetzt werden.
Trotzdem sind nicht alle Typen auch in jeder Programmiersprache
verfügbar. So kennt C nicht den Typ "Boolean" und
Basic kennt nur "Real" und "String". Man kann
die fehlenden Typen jedoch in fast allen höheren
Programmiersprachen "nachempfinden".
- Boolean
Dieser Typ bezeichnet den Bereich der logischen Werte,
bestehend aus zwei Elementen "wahr"
("TRUE", 1, Wert != 0) und "falsch"
("FALSE", 0, Wert = 0). Auf den Werten dieses
Typs sind vier Operatoren definiert: OR (logisches Oder),
AND (logisches Und), EXOR (logische Antivalenz), NOT
(logisches Nicht). Die Gesetze für diese Operatoren
entsprechen jenen der Schaltalgebra bzw. Aussagelogik.
Außerdem sind Vergleichsoperatoren definiert (=, #,
<, >, <=, >=), die beim Vergleich von
skalaren Operatoren einen Wert vom Typ Boolean liefern.
In C nicht vorhanden.
- Integer
Dieser Typ bezeichnet den Bereich der ganzen Zahlen. Es
sind die folgenden Operatoren definiert:
- + Addition
- - Subtraktion
- * Multiplikation
- / Ergebnis der ganzzahligen Division
- % Rest der ganzzahligen Division
Bei jedem Computer spezifiziert der Typ
"Integer" nur eine Untermenge der ganzen Zahlen
im mathematischen Sinn, und zwar die Menge aller Zahlen,
die betragsmäßig kleiner als ein bestimmter Maximalwert
sind. Dies bedeutet, daß die Axiome der Arithmetik nur
innerhalb dieser Untermenge gelten. Normalerweise wird
eine Länge gewählt, die einem Vielfachen der Wortlänge
des Prozessors entspricht. Es gilt für die Wortlänge:
short <= int <= long int.
In ANSI-C wird als Minimum definiert:
| Typ |
Wertebereich |
| int |
-32768 ... 32767 |
| long int |
-2147483648 ... 2147483647 |
| short int |
-32768 ... 32767 |
| unsigned int |
0 ... 65535 |
| unsigned long int |
0 ... 4294967295 |
| unsigned short int |
0 ... 65535 |
- Character
Dieser Typ bezeichnet eine endliche, geordnete Menge von
Zeichen (Characters). Der Typ Character stützt sich auf
den im jeweiligen Computer verwendeten Code zur
Darstellung von Zeichen (ASCII, EBCDIC, ANSI). Er ist
also nur insofern Standard, als der zugrundeliegende
Zeichensatz Standard ist. Mit diesem Typ werden auch zwei
vom Zeichensatz abhängige Konversionsmöglichkeiten
zwischen "Character" und "Integer"
eingeführt (In C "Typecasting", siehe später).
In ANSI-C wird als Speichergröße ein Byte definiert:
| Typ |
Wertebereich |
| char |
-127 ... 128 oder 0 ... 255 |
| unsigned char |
0 ... 255 |
Achtung: Um die verschiedenen nationalen Zeichensätze unter einen
Hut zu bringen, wurde UNICODE entwickelt. Das hat zur Folge, daß
char auch 16 Bit lang sein kann.
- Real bzw. Float
Dieser Datentyp stellt nicht Variablen aus dem
Wertebereich der reellen Zahlen, sondern Gleitpunktzahlen
dar. Die Repräsentation dieser Werte hängt auch wieder
von der Realisierung im jeweiligen DVS ab (Zahl der
gültigen Stellen, Wertemaxima). Bei vielen Systemen wird
dieser Datentyp auch durch Nachbildung per Software
(Emulation) realisiert. Insbesondere sind die
assoziativen und distributiven Gesetze nicht mehr
gültig! Auf dem Datentyp Real sind vier Operatoren
definiert:
- + Addition
- - Subtraktion
- * Multiplikation
- / Division
Anmerkung: Eine explizite Konversion von Integer nach
Real existiert nicht. Diese Konversion wird implizit bei
der Übersetzung des Ausdrucks vorgenommen. Es gilt für
die Wortlänge: float <= double <= long double.
In ANSI-C wird als Minimum definiert:
| Typ |
Wertebereich
Vorzeichen beliebig |
Mantissen- stellen |
| float |
3,4*10-38 ... 3,4*10+38 |
>=6 |
| double |
1,7*10-308 ... 1,7*10+308 |
>=10 |
| long double |
1,7*10-308 ... 1,7*10+308 |
>=10 |
1.4 strukturierte Typen
Feld (Array)
Variablen dieses Typs besitzen eine Menge von Komponenten
gleichen Typs und haben folgende Eigenschaften:
- Jedes Element ist einzeln referenzierbar (benennbar). Zu
Bezeichnung einer bestimmten Array-Komponente wird der
Name des Arrays mit einen Zusatz, genannt Index,
versehen, der die Komponente eindeutig bezeichnet. Als
Indexwerte werden berechenbare Größen eines
aufzählbaren, skalaren Typs verwendet (z. B. Integer,
Character).
- Die Anzahl der Elemente wird bei der Vereinbarung
festgelegt und bleibt danach im Allgemeinen
unveränderlich (es gibt auch Ausnahmen). Bei der
Vereinbarung eines Array-Typs wird sowohl der
Komponententyp als auch der Indextyp spezifiziert. Es
besteht eine eindeutige Zuordnung zwischen Indexwerten
und Array-Komponenten, sie hat allgemein die Form:
Komponententyp
Variablenname [Dimension]
Der Index wird dabei immer von 0 gezählt. Die
Dimension gibt an, wieviele Komponenten das Feld haben
soll. Die Indexmenge reicht also von 0 bis (Dimension -
1). Beispiel:
int A [100]
Danach besitzt die Variable A 100 Komponenten vom Typ
Integer: A[0], A[1], A[2], A[3], ..., A[98], A[99]
Wie man sieht, werden die Komponenten der Array-Variablen A
mit dem Index I durch A[I] bezeichnet. Anstelle des Indexwertes
kann, abhängig von Indextyp, auch ein beliebiger Ausdruck
stehen, z. B.: A[i + j].
Zwei Array-Variablen werden als gleich bezeichnet, wenn sie
vom selben Typ sind (d. h. identische Vereinbarung besitzen) und
alle Komponenten paarweise gleich sind (d. h. A = B, wenn A[i] =
B[i] für alle i des Indexbereichs).
Als Komponententyp eines Array kann auch wieder ein Arraytyp
verwendet werden --> mehrdimensionale Arrays. Die Vereinbarung
wird in den Programmiersprachen zu einer verkürzten Vereinbarung
zusammengezogen:
int A [100][50]
Verbund (Record, Structure)
Im Gegensatz zu Array besitzen Verbunde Komponenten
unterschiedlichen Typs und stellen damit die flexibelste
Datenstruktur dar. Ein Record besteht aus einer festen Anzahl von
Komponenten, wobei jede Komponente mit einem eigenen Namen
bezeichnet wird, die Vereinbahrung stellt sich damit
folgendermaßen dar:
struct Recordbezeichner
{
Komponententyp Komponentenbezeichner;
Komponententyp Komponentenbezeichner;
Komponententyp Komponentenbezeichner;
Komponententyp Komponentenbezeichner;
};
Man kann nach der schließenden Klammer auch gleich noch eine
Variable definieren. Beispiele:
struct Kundensatz
{
int Kundennummer;
char Name[20];
char Vorname [20];
float Umsatz;
} Kunde;
struct Datumstyp
{
int Tag;
int Monat;
int Jahr;
} Datum;
Definiert man die Variablen getrennt von der Record-Definition, sieht das
so aus:
sruct Recordbezeichner Variablenbezeichner;
Als Record-Komponenten können wieder beliebige
Datentypen stehen, also auch wieder Array- oder Recordtypen.
Umgekehrt kann auch ein Array Records als Komponenten besitzen
(z. B. ein Array mit Komponenten vom oben gezeigten Kundentyp).
Auf einzelne Komponenten des Records wird über die
Kombination des Recordnamens und der gewünschten
Recordkomponente zugegriffen. Als Trennzeichen zwischen den Namen
wird ein Punkt verwendet (hier gibt es Unterschiede bei den
einzelnen Programmiersprachen), z. B.:
Datum.Tag oder Kunde.Name
Auch hier kann - je nach Aufbau des Records - eine mehrstufige
Referenz notwendig sein.
1.5 Dateien
Alle bisher betrachteten Datentypen haben sich dadurch
ausgezeichnet, daß der Wert einer Variablen durch ihren Namen
referenziert wurde und die Zahl der Komponenten bei der
Vereinbarung festgelegt wurde. Es stellt sich die Frage, welcher
Datentyp verwendet werden soll, wenn die Zahl der Komponenten
nicht von Anfang an festliegt. Ein zweiter Aspekt ist die
Ein-/Ausgabe. Die bisher behandelten Variablen liegen im
Arbeitsspeicher. Es muß also eine Möglichkeit zur permanenten
Speicherung von Daten ausserhalb des Hauptspeichers geboten
werden - und zur Ein-/Ausgabe über beliebige Geräte.
Dateien sind ein allgemeines Konzept für die permanente
Speicherung bzw. Ein-/Ausgabe. Dateien bestehen aus beliebig
vielen Komponenten eines bestimmten Datentyps. Je nach Dateiart
können die einzelnen Komponenten sequentiell oder wahlfrei
gelesen werden. Am Ende einer Datei können weitere Komponenten
hinzugefügt werden. Die Abbildung der abstrakten Dateistruktur
auf reale Speichergeräte (Platte, Band, Drucker, etc.) erfolgt
durch das Betriebssystem.
Eine Datei besitzt also lauter Komponenten gleichen Typs.
Zusammen mit der Dateivariable wird implizit auch ein
Pufferbereich im Arbeitsspeicher definiert, der mindestens eine
Dateikomponente (--> aktueller Wert) aufnehmen kann. Auf
diesen Datenwert wird dann über die Dateivariable (Filehandle) zugegriffen.
Zusammen mit dem Typ "Datei" müssen einige
Standardoperationen definiert sein, die den Zugriff auf die Datei
erlauben:
- Open
Eröffnen einer Datei zur Inspektion des Inhalts (Lesen)
oder zum Anfügen neuer Komponenten am Ende (Schreiben).
Mit dieser Prozedur wird nicht nur der Zugriffsmodus
festgelegt, sondern gegebenenfalls auch eine leere Datei
generiert (beim Modus "Schreiben" bei einer
noch nicht vorhandenen Datei).
- Close
Beenden des Zugriffs auf eine Datei. Das Betriebssystem
wird angewiesen, die Datei zu schließen. Damit verbunden
sind Verwaltungsvorgänge des Betriebssystems, z. B.
Wegschreiben des Inhalts des Pufferbereichs.
- Read, Write
Lesen und Schreiben von Komponenten einer Datei. Die
Arbeitsweise dieser Operationen hängt von der Art der
Datei ab. Auf diese Arbeitsweise wird in einem folgenden
Kapitel noch näher eingegangen.
Nach Art des Zugriffs auf die Komponenten wird unterschieden
in sequentielle Dateien und Dateien mit wahlfreiem Zugriff.
Textdateien
Dateien, dessen Komponenten Schriftzeichen sind (Typ
Character), nehmen eine Schlüsselrolle ein, da die Eingabe- und
Ausgabedaten der meisten Computerprogramme Textfiles sind
(darunter fällt beispielsweise auch die Druckausgabe). Ein
Programm kann vielfach allgemein als eine Datentransformation von
einer Textdatei in eine andere aufgefaßt werden (z. B. höhere
Sprache --> Assembler).
Nun sind Texte i. a. in Zeilen unterteilt, und es stellt sich
die Frage, wie diese Zeilenstruktur auszudrücken ist. In der
Regel enthält der in einem DVS verwendete Zeichencode spezielle
Steuerzeichen, von denen eines als Zeilenende-Zeichen verwendet
werden kann. Bedauerlicherweise verhindert die Realisierung von
Textdateien auf Betriebssystemebene eine einfache Realisierung
der Textdatei als "FILE OF Character".
- Bei einigen Betriebssystemen wird ein einziges
Steuerzeichen als Zeilenende interpretiert (z. B. bei
UNIX: Linefeed, beim Macintosh: Carriage Return). Das verwendete
Zeichen ist jedoch nicht einheitlich festgelegt.
- Bei anderen Systemen besteht das Zeilenende aus zwei
Zeichen (in der Regel Carriage Return und Linefeed, z. B.
bei Windows).
Übersicht Datentypen
Die folgende Übersicht zeigt eine Einordnung der Datentypen
nach ihren Eingenschaften. Nicht alle Typen sind in jeder
Programmiersprache vorhanden. Einige der Typen werden später
besprochen, einige auch gar nicht.
Copyright © FH München, FB 04, Prof. Jürgen Plate