5 Datenstrukturen 5.1 Basis

Werbung
S. d. I.: Programieren in C
Folie 5 - 1
5 Datenstrukturen
5.1
•
•
Basis-Typen
C unterstützt die folgenden grundlegenden Typen
Integral Types
Floating-Point
Types
Other
char
int
short
long
signed
unsigned
enum
float
double
long double
void
const
volatile
die Schlüsselwörter signed und unsigned können mit Ausnahme von
enum jedem Integral Type vorangestellt werden
•
der Aufzählungstyp enum wird in einem eigenen Kapitel behandelt
•
void kann an drei Stellen verwendet werden:
1) als "Rückgabewert" einer Funktion, die keinen Wert zurückliefert
(entspricht in Pascal einer Prozedur)
2) als "Parameterliste" einer Funktion, die keine formalen Parameter
besitzt
3) zur Kennzeichnung eines Zeigers, dessen Typ zur Zeit noch nicht
festgelegt ist
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 2
die Typangaben können außerdem durch die folgenden Angaben
modifiziert werden
-
const
-
nach der Initialisierung darf die Variable nicht mehr verändert
werden
-
Beispiele:
const long double pi = 3.1415926535897932385;
const int a = 3, b = 4;
int x, y;
const int *ptr;
variabler Zeiger auf eine Konstante
(ptr = &a; ptr = &b; ist erlaubt)
int *const ptr
konstanter Zeiger auf Variable
(ptr = &x; ptr = &y; ist nicht erlaubt.
ptr = &x; x = 3; x = 5; ist erlaubt)
-
-
Vorteil: Der Compiler kann einen effizienteren Code generieren, da er den Wert direkt einsetzen kann und nicht erst aus
dem Speicher laden muß (wie bei einer Variablen)
volatile
-
die Variable kann auf eine Art und Weise verändert werden,
auf die der Compiler keinen Einfluß hat (z.B. über eine
Unterbrechungsroutine)
⇒ der Compiler muß alle Ausdrücke / Anweisungen, die diese
⇒
Variable betreffen genau in der vorgeschriebenen Reihenfolge
durchführen, d.h. er darf keine Umorganisationen zur
Optimierung durchführen
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
-
Folie 5 - 3
Beispiele (KEYBOARD sei ein Geräte-Register):
1) for (i = 0; i < 10; ++i)
{
c = KEYBOARD;
copy (c);
};
Der Compiler darf die Schleifen-Invariante in der Optimierungsphase nicht vor die Schleife ziehen ⇒ volatile ist
notwendig, damit das obige Programm für den Compiler
nicht nur ineffizient programmiert ist.
2) extern const volatile int real_time_clock;
"real_time_clock" kann auf eine Art und Weise verändert
werden, auf die der Compiler keinen Einfluß hat, aber ihr
darf weder ein Wert zugewiesen werden noch darf ihr
Wert geändert werden. Die Variable ist außerhalb dieser
Datei definiert.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
•
•
Folie 5 - 4
Größe und Wertebereich der Basistypen
Name
Alternative
Größe (Bits)
Wertebereich
char
signed char
8
-128 - 127
unsigned char
-
8
0 - 255
int
signed int
signed
mindestens 16
mindestens
-32.768 - 32.767
unsigned int
unsigned
mindestens 16
mindestens
0 - 65.535
short int
signed short int
signed short
short
16
-32.768 - 32.767
unsigned short int
unsigned short
16
0 - 65.535
long int
signed long int
signed long
long
32
-2.147.483.648 2.147.483.647
unsigned long int
unsigned long
32
0 - 4.294.967.295
enum
-
wie int
wie int
float
-
32
mindestens
1.0 E-37 - 1.0 E+37
double
-
mindestens 32
mindestens
1.0 E-37 - 1.0 E+37
long double
-
mindestens 32
mindestens
1.0 E-37 - 1.0 E+37
(unsigned) int hat normalerweise die Größe, die eine CPU in einem
Befehl bearbeitet und ist damit implementierungs-abhängig (die
aktuelle Größe kann der Datei limits.h entnommen werden)
double und long double sind implementierungs-abhängig (die
aktuellen Größen können der Datei float.h entnommen werden)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 5
Anfangsinitialisierung
1) direkt bei der Deklaration
2) eigene Anweisung nach der Deklaration
⇒ wird vom Compiler u.U. unterschiedlich gehandhabt
⇒
Beispiel:
;
;
;
;
;
;
;
;
;
Programmausschnitt:
static int a = 3,
b;
/* Fall 1
*/
b = 5;
/* Fall 2
*/
Microsoft C Compiler. Übersetzung mit: cl /Fc <Dateiname>
liefert hierfür die folgenden Assembleranweisungen:
;
;
$S165_a EQU
$S166_b EQU
$S165_a DW
$S166_b DW 01H
;
Adresse
*** 000005
*** 000008
a
b
03H
DUP (?)
Code
b8 05 00
a3 00 00
; Fall 1
; Fall 2
Anweisung
mov ax,5
mov WORD PTR $S166_b,ax
⇒ im ersten Fall wird der Wert direkt einer Speicherstelle zugeord⇒
net, während er im zweiten Fall einer Speicherstelle zugewiesen
wird
⇒ falls das Programm später im (E)PROM laufen soll, dürfen die
⇒
Anfangswerte i.a. nicht bei der Deklaration zugewiesen werden,
da sie in diesem Fall verloren gehen können !
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
5.2
•
Folie 5 - 6
Felder
Sammlung von Objekten desselben Typs, die im Speicher zusammenhängend abgelegt werden
•
jedes Element des Feldes kann über den Namen des Feldes plus einem
Indexausdruck adressiert werden
•
•
das erste Element des Feldes hat den Index 0
bei der Anfangsinitialisierung muß das Feld entweder global definiert
werden oder der Speicherklasse static angehören, wenn kein ANSI-C
Compiler verwendet wird
•
Syntax:
1)
Typ Feldname [ konstanter Ausdruck ] {= Anf. init.};
-
der konstante Ausdruck muß eine positive ganze Zahl liefern
-
und gibt die Anzahl der Feldelemente an
als Typ ist jeder Typ erlaubt, der von "void" verschieden ist
-
die Feldelemente dürfen keine Funktionen sein
-
Beispiel:
1)
int xyz [10];
Feld für 10 ganze Zahlen
2)
char *string [20];
Feld für 20 Zeiger auf Zeichenfolgen
3)
struct {float re, im;} complex [100];
4)
int xyz [4] = {1, 2, 3, 4};
vollständige Initialisierung
5)
int xyz [4] = {1, 2};
⇒ automatische Initialiunvollständige Initialisierung ⇒
sierung von xyz[2] und xyz[3] mit dem Wert 0
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
2)
Folie 5 - 7
Typ Feldname [ ] {= Anfangsinitialisierung};
-
diese Form kann nur für initialisierte Felder, für formale
Parameter von Funktionen oder für Verweise auf Felder, die
an anderer Stelle definiert sind, verwendet werden
-
Beispiel:
1)
int xyz [] = {1, 2, 3, 4};
aufgrund der Initialisierung kann der Compiler die erforderliche Größe des Feldes selbst berechnen
3)
2)
const char meldung [] = "Fehler: ...";
3)
void quick_sort (int liste [], int lg, int rg);
4)
extern char name [];
Typ Feldname [ konst. Ausdr. ][ konst. Ausdr. ]...;
-
Definition mehrdimensionaler Felder
-
jeder Index erhält seine eigenen Klammern
(siehe auch "häufige Fehler neuer C Programmierer" im Kapitel 1)
-
der konstante Ausdruck vom ersten Index kann bei initialisierten Feldern und bei Verweisen auf Felder entfallen
-
Beispiel:
1)
float matrix [4][4];
2)
int x [3][4] = { {1, 2, 3, 4},
{5, 6}
};
liefert:
•
x00 = 1, x01 = 2, x02 = 3, x03 = 4,
x10 = 5, x11 = 6, x12 = 0, x13 = 0,
x20 = 0, x21 = 0, x22 = 0, x23 = 0,
mehrdimensionale Felder werden zeilenweise gespeichert, d.h. der
letzte Index ändert sich am schnellsten
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
•
Folie 5 - 8
Zugriff auf einzelne Feldelemente:
1)
xyz = abc [3];
2)
xyz = abc [3][4];
3)
abc [3]
4)
abc [3][4] = 2.78;
= 3.0;
in C werden i.a. keine Feldgrenzen überprüft, so daß bei Programmierfehlern u.U. der ganze zugreifbare Speicherinhalt überschrieben
wird
5.3
Zeiger
•
Zeiger sind Objekte, deren Wert die Adresse eines anderen Objekts ist
•
Syntax:
Typ * {const, volatile} name;
•
Zeiger können auf beliebige Objekte eines bestimmten Typs zeigen
(inkl. Felder, Strukturen, Funktionen, andere Zeiger, gar nichts (void), ...)
•
Zeiger vom Typ void können auf jedes Objekt zeigen
Falls mit solchen Zeigern eine Operation durchgeführt werden soll, muß der aktuelle Typ des
Zeigers vorher mit Hilfe der cast-Operation festgelegt werden, damit die entsprechende
Operation korrekt ausgeführt wird (z.B. muß bei p++ zwei addiert werden, wenn p auf ein
Objekt vom Typ short int zeigt und vier beim Typ long int)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 9
const und volatile können wieder verwendet werden, um dem Compiler mitzuteilen, daß der Zeiger im Programm nicht verändert wird
bzw. durch etwas verändert werden kann, was außerhalb der Kontrolle des Programms ist
•
Zeiger auf Objekte vom Typ struct, union oder enum können definiert
werden, bevor das Objekt selbst definiert ist
(vor einer Operation in einem Ausdruck muß das Objekt allerdings definiert worden sein)
•
Zeiger benötigen nur den Speicherplatz für eine Adresse
•
Beispiel:
•
1)
char *string = "Dies ist eine Zeichenfolge";
2)
int *ptr [10];
ptr ist ein Feld von Zeigern auf Typ int
3)
int (*ptr) [10];
ptr ist ein Zeiger auf ein Feld vom Typ int
4)
const int *ptr;
int const *ptr;
ptr ist ein Zeiger auf einen konstanten Wert
5)
int *const ptr;
aber: ptr ist ein konstanter Zeiger auf int
6)
int *const volatile ptr;
ptr ist ein konstanter Zeiger auf int, der
außerhalb des Programms modifiziert werden darf
Operationen mit Zeigern
Deklaration:
int a[10], *p1, *p2;
char *p3;
&a[3] - &a[0]
p1 = &a[0];
liefert 3 (die Anzahl der Elemente)
weist p1 die Adresse des Elements a[0] zu
*p1
p2 = p1 + 3;
p2 - p1
liefert den Wert von a[0] (dereferencing, indirekter Zugriff)
weist p2 die Adresse des Elements a[3] zu
p3 = p1 + 2;
Fehler !!! (unterschiedliche Zeigertypen)
liefert 3
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 10
aus dem obigen wird deutlich:
a[i] entspricht *(p1 + i) , d.h. zur Adressierung der Feldelemente kann sowohl der Feldname als auch ein Zeiger verwendet
werden
(während Feldnamen nicht modifiziert werden können, können die Werte der Zeiger verändert werden)
•
geg.:
long int a[5][6];
ges.: Zeigerdarstellung und Adresse von
a[2][3]
a[2][3] = *(a[2] + 3)
= *(*(a + 2) + 3)
1) da a ein Feld von Feldern ist, muß die 2 in *(a + 2) an
die Objektgröße angepaßt werden, d.h. ein 6-elementiges Feld
vom Typ long int
⇒ es werden 2 * 6 * 4 Byte zur Anfangsadresse des Feldes
⇒
addiert (Speicherbedarf für die 0-te und 1-te Zeile)
2) die Objektgröße für die 3 ist long int (Abstand innerhalb einer Zeile)
⇒ es werden noch einmal 3 * 4 Byte zur Anfangsadresse der
⇒
2-ten Zeile addiert (Speicherbedarf für den 0-ten bis 2-ten Wert)
⇒ falls a ab Adresse 1000 gespeichert ist, befindet sich das Ele⇒
ment a[2][3] bei Adresse
1000 + 2*6*4 + 3*4 = 1060
•
die obige Umwandlung ist wichtig, wenn Funktionen für mehrdimensionale Felder mit beliebigen Matrixgrößen geschrieben
werden sollen
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 11
Zeiger auf Funktionen
-
Zeiger können die Adressen von Funktionen aufnehmen
-
solche Zeiger können als Parameter an Funktionen übergeben
werden
(für die Bibliotheks-Routine qsort (stdlib.h) wird auf diese Weise z.B. die Adresse einer
Routine zum Vergleichen zweier Datensätze übergeben)
-
Beispiel:
char *(*ptr1)(void);
ptr1 zeigt auf eine Funktion, die einen Zeiger auf
char zurückliefert und keine Parameter benötigt.
Aufruf einer Funktion über Zeiger: (*ptr1)()
aber: ptr2 ist eine Funktion, die einen Zeiger auf
char **ptr2(void);
einen Zeiger auf char zurückliefert und keine
Parameter benötigt
-
es können auch Felder von Zeigern auf Funktionen definiert
werden
•
Kombinationen von "*", "++" und "--"-Operatoren bei Zeigern
Deklaration:
int a[2] = {0, 5},
*p1 = &a[0],
*p2 = &a[1],
z;
Ergebnisse nach Ausführung der jeweiligen Operation
(jede Operation geht von der obigen Deklaration aus ! )
Fall
Operation
z
a[0]
a[1]
p1 zeigt auf
1)
2)
3)
4)
5)
6)
7)
z
z
z
z
z
z
z
0
0
0
5
5
1
1
0
0
1
0
0
1
1
5
5
5
5
5
5
5
a[1]
a[1]
a[0]
a[1]
a[1]
a[0]
a[0]
=
=
=
=
=
=
=
*p1++
*(p1++)
(*p1)++
*++p1
*(++p1)
++*p1
++(*p1)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
Folie 5 - 12
Fall
Operation
z
a[0]
a[1]
p2 zeigt auf
1)
2)
3)
4)
5)
6)
7)
z
z
z
z
z
z
z
5
5
5
0
0
4
4
0
0
0
0
0
0
0
5
5
4
5
5
4
4
a[0]
a[0]
a[1]
a[0]
a[0]
a[1]
a[1]
-
=
=
=
=
=
=
=
*p2-*(p2--)
(*p2)-*--p2
*(--p2)
--*p2
--(*p2)
folgende Fälle sind identisch, da die Auswerterichtung der Operatoren von rechts nach links ist und die Klammern in diesen
Fällen keine andere Gruppierung des Operanden an den Operator
bewirken:
Fall 1 und Fall 2
Fall 4 und Fall 5
Fall 6 und Fall 7
-
im Fall 3 bewirken die Klammern, daß p1 bzw. p2 stärker an den
"*"-Operator gebunden wird, wodurch die Auswertereihenfolge
geändert wird
-
Auswertung des Ausdrucks im Fall 1) beim "++"- Operator:
* und ++ haben die gleiche Priorität und Auswerterichtung (von
rechts nach links). Daraus folgt:
a) Auswertung des post-increment-Operators
⇒ p1 wird zunächst unverändert an den nächsten Operator
⇒
übergeben
⇒ es wird vermerkt, daß p1 erhöht werden muß, sobald der
⇒
ganze Ausdruck ausgewertet ist
b) Auswertung des "*"-Operators und Wertzuweisung an z
c) erhöhen von p1 (und damit beenden der Operation)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 13
Beispiel:
/* Das Programm kopiert einen Speicherbereich auf verschiedene Arten
* um und misst die jeweiligen Kopierzeiten.
*
* Datei: memcpy.c
Autor: S. Gross
* Datum: 30.08.1993
*
*/
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<stddef.h>
<string.h>
<time.h>
#define BUF_SIZE
#define NUM_COP
#define ODD(x)
16 * 1024
64
((x) & 1 ? 1 : 0)
/* Puffergroesse
/* Anzahl Kopiervorgaenge
*/
*/
/* Test, ob x ungerade ist
*/
char *memcpy_1 (char *, char *, size_t);
char *memcpy_4 (char *, char *, size_t);
char *memcpy_16 (char *, char *, size_t);
/* 1-Byte-Transfer
/* 4-Byte-Transfer
/* 16-Byte-Transfer
*/
*/
*/
char dest [BUF_SIZE],
source [BUF_SIZE];
/* Zielpuffer
/* Quellpuffer
*/
*/
int main (void)
{
clock_t duration;
int
i;
/* Zeitdauer
/* Schleifenvariable
*/
*/
printf ("Zeitdauern fuer \"memcpy\" zum Kopieren von %ld KByte:\n\n",
(long) NUM_COP * BUF_SIZE / 1024);
/* !!!!!!!!!!!!!!!!!!!!! 1-Byte-Transfer !!!!!!!!!!!!!!!!!!!!!
*/
duration = clock ();
for (i = NUM_COP; i > 0; --i)
{
memcpy_1 (dest, source, (size_t) BUF_SIZE);
};
duration = clock () - duration;
printf ("Byte-Transfer:
%0.2f Sekunden\n",
(double) duration / CLOCKS_PER_SEC);
...
/* !!!!!!!!!!!!!!!!!!!!! Bibliotheks-Routine !!!!!!!!!!!!!!!!!!!!
duration = clock ();
for (i = NUM_COP; i > 0; --i)
{
memcpy (dest, source, (size_t) BUF_SIZE);
};
duration = clock () - duration;
printf ("Bibliotheks-Routine: %0.2f Sekunden\n",
(double) duration / CLOCKS_PER_SEC);
return 0;
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
S. d. I.: Programieren in C
Folie 5 - 14
/* Umkopieren eines Speicherbereichs in Byte Portionen.
*
* Parameter:
source
Adresse des Quellpuffers
*
dest
Adresse des Zielpuffers
*
count
Anzahl Bytes, die kopiert werden sollen
*
* Ergebnis:
char *
Zeiger auf Anfang des Zielpuffers
*
*/
char *memcpy_1 (char *dest, char *source, size_t count)
{
register char *dest_b, *src_b;
long int cnt;
dest_b = dest;
src_b = source;
cnt
= (long int) count;
while (--cnt >= 0)
{
*dest_b++ = *src_b++;
};
return (char *) dest;
}
/* Umkopieren eines Speicherbereichs in 4-Byte Portionen.
*
* Parameter:
source
Adresse des Quellpuffers
*
dest
Adresse des Zielpuffers
*
count
Anzahl Bytes, die kopiert werden sollen
*
* Ergebnis:
char *
Zeiger auf Anfang des Zielpuffers
*
*/
char *memcpy_4 (char *dest, char *source, size_t count)
{
register unsigned long *dest_l, *src_l;
register char *dest_b, *src_b;
int remain;
/* remainder
*/
long int cnt;
cnt = (long int) count;
if (ODD ((ptrdiff_t) source ) || ODD ((ptrdiff_t) dest))
{
/* bei ungeraden Puffer-Adressen: Byte Transfer
src_b = source;
dest_b = dest;
while (--cnt >= 0)
{
*dest_b++ = *src_b++;
};
}
else
{
/* bei geraden Puffer-Adressen: Langwort Transfer und nur den
*
Rest im Byte Transfer
*/
remain = (int) cnt % 4;
cnt
= cnt / 4;
src_l = (unsigned long *) source;
dest_l = (unsigned long *) dest;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
S. d. I.: Programieren in C
Folie 5 - 15
while (--cnt >= 0)
{
*dest_l++ = *src_l++;
};
src_b = (char *) src_l;
dest_b = (char *) dest_l;
while (--remain >= 0)
{
*dest_b++ = *src_b++;
};
};
return (char *) dest;
}
...
Aufgabe:
1) Schreiben Sie eine Routine, die in einem Puffer mit der Anfangsadresse buf und der Größe n das erste Vorkommen des Zeichens chr
findet. Falls das Zeichen im Puffer vorkommt, liefert sie einen Zeiger
auf das Zeichen zurück. Andernfalls liefert sie einen NULL-Zeiger
zurück. Die Funktion sei durch folgenden Prototypen definiert:
char *memchr (const char *buf, int chr, size_t n);
Zur Lösung dürfen keine Routinen der C Bibliothek verwendet
werden, wie z.B. strchr, strstr, memchr usw.
2) Schreiben Sie eine Routine, die die ersten count Bytes eines Speicherbereichs mit der Anfangsadresse buf1 mit den ersten count Bytes
eines Speicherbereichs mit der Anfangsadresse buf2 vergleicht. Als
Ergebnis liefert die Funktion einen Wert kleiner als Null, wenn buf1
lexikographisch kleiner als buf2 ist, gleich Null, wenn buf1 gleich
buf2 ist und einen Wert größer als Null, wenn buf1 lexikographisch
größer als buf2 ist. Die Funktion sei durch folgenden Prototypen
definiert:
int memcmp (const char *buf1, const char *buf2, size_t count);
Zur Lösung dürfen keine Routinen der C Bibliothek verwendet
werden, wie z.B. strcmp, strncmp, memcmp usw.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
5.4
•
Folie 5 - 16
Strukturen
Sammlung von Objekten, die eigene Namen und u.U. verschiedene
Typen haben
•
Syntax:
1)
2)
struct [tag] {member declaration;} [decl [, decl [, ...]]];
-
optional kann ein Typname (tag) festgelegt werden
-
optional können bereits Variablen definiert werden
struct tag decl [, decl [, ...]];
-
eine Struktur mit dem entsprechenden Typnamen muß vorher
deklariert worden sein
-
•
es werden Variablen dieses Typs definiert
Strukturen können analog zu Feldern Anfangswerte bei der Definition
zugewiesen werden
•
die Komponenten der Struktur werden sequentiell gespeichert
•
im Gegensatz zum Verbund werden die einzelnen Komponenten nicht
überlagert
⇒ der Speicherbedarf einer Struktur ergibt sich aus der Summe des
⇒
Speicherbedarfs aller Komponenten plus der Summe aller Bytes,
die ggf. für die Ausrichtung der Komponenten an Wortgrenzen
(alignment) notwendig sind
•
die Komponenten einer Struktur folgen im Speicher u.U. nicht direkt
aufeinander (ggf. Lücken aufgrund der Ausrichtung an Wortgrenzen)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
Folie 5 - 17
•
eine Struktur kann sich selbst nicht als Komponente enthalten
•
eine Struktur kann einen Zeiger auf sich selbst als Komponente
enthalten (rekursive Datenstruktur)
•
Beispiel:
1)
struct {double re,
im;
} zahl;
Definition einer Variablen zahl, die aus zwei Komponenten vom
Typ double besteht. Die Struktur hat keinen Typnamen und ist
daher namenlos.
2)
struct complex {double re,
im;
};
Deklaration eines Typnamens complex, der aus zwei Komponenten vom Typ double besteht. Da kein Variablenname angegeben
ist, wird noch kein Speicherplatz reserviert (nur Typdeklaration).
3)
struct complex zahl;
Definition einer Variablen vom Typ struct complex.
4)
struct complex {double re,
im;
} zahl;
Definition eines Typnamens complex und einer Variablen zahl.
Für die Variable wird Speicherplatz reserviert.
5)
struct complex zahl = {3.1, 4.0};
Definition einer Variablen vom Typ struct complex mit
Anfangsinitialisierung.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
die Komponenten einer Struktur werden mit den Operatoren "." (bei
normalen Variablen)
•
Folie 5 - 18
oder "->" (bei Zeigern auf strukturierte Variablen) selektiert.
struct complex zahl = {3.1, 4.0},
*ptr = &zahl;
zahl.re = 5.7;
ptr->re = 5.7;
•
entspricht
(*ptr).re = 5.7;
verkettete Listen
... next
... next
... next
NIL
Anfang
typedef struct element
{ int
key;
char
*info;
struct element *next;
} LISTE;
#define NIL ((LISTE *) NULL)
LISTE *Anfang, *Ende;
1) leere Liste initialisieren
Anfang = NIL;
Ende
= NIL;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Ende
S. d. I.: Programieren in C
Folie 5 - 19
2) Element am Ende der Liste einfügen
LISTE *tmp;
tmp = (LISTE *) malloc (sizeof (LISTE));
if (tmp == NIL)
{
fprintf (stderr, "Kein Speicher verfuegbar!");
exit (1);
};
tmp->key = ...;
tmp->info = ...;
tmp->next = NIL;
if (Anfang == NIL)
/* leere Liste ?
{
Anfang = tmp;
}
else
{
Ende->next = tmp;
};
Ende
= tmp;
*/
−
"Anfang", "Ende" und "Ende->next" sind Speicherzellen, die
die Adresse eines Listenelements aufnehmen können
−
die Unterscheidung zwischen leerer und nicht leerer Liste
kann in C entfallen, wenn es eine Speicherzelle gibt, die die
Adresse der Speicherzelle enthält, die die Adresse des
Listenelements aufnehmen soll
LISTE **ptr;
ptr = &Anfang;
for (...)
{
*ptr = (LISTE *) malloc (sizeof (LISTE));
if (*ptr == NIL)
{
fprintf (stderr, "Kein Speicher verfuegbar!");
exit (1);
};
...
/* Daten erstellen
(*ptr)->key = ...;
(*ptr)->info = ...;
(*ptr)->next = NIL;
ptr
= ???;
};
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
S. d. I.: Programieren in C
−
Folie 5 - 20
Ablauf des Listenaufbaus
Adresse
Speicherinhalt
Bemerkung
&Anfang
20
40
108
60
60
68
80
−
68,
88,
Anfang
108
?
info
next
1. Element
der Liste
key
100
100
108
40,
key
80
88
ptr
info
next
2. Element
der Liste
key
NIL
info
next
3. Element
der Liste
Realisierungsmöglichkeiten für eine Initialisierungsroutine
1) LISTE *init (void)
Aufruf: Anfang = init ();
2) void init (LISTE **ptr)
Aufruf: init (&Anfang);
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
Folie 5 - 21
Aufgaben:
1) Gegeben sei der folgende Programmausschnitt:
#define NUM_EL
10
/* Anzahl Elemente der Liste
*/
/* Ende der Liste
*/
typedef struct elem { int
key;
char
*info;
struct elem *next;
} LIST;
#define NIL
((LIST *) NULL)
void init (LIST **head);
void print (LIST *head);
Erstellen Sie ein Programm, das eine Liste mit zufälligen Werten
erzeugt und ausgibt.
2) Schreiben Sie ein Programm, das die Mandelbrotmenge für die
folgenden Parametersätze ausgibt:
p_min
p_max
q_min
q_max
-2.25
-0.19920
-0.713
0.75
-0.12954
-0.4082
-1.5
1.01480
0.49216
1.5
1.06707
0.71429
Algorithmus zur Ausgabe einer Mandelbrotmenge:
Bildschirmauflösung:
Anzahl Farben:
Farbe "schwarz":
MaxX mal MaxY Punkte
MaxColor
0
Schritt 0:
initialisiere p_min, p_max, q_min, q_max
M = 100
delta_p = (p_max - p_min) / MaxX
delta_q = (q_max - q_min) / MaxY
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
(Bildausschnitt)
(Iterationstiefe)
(Schrittweite)
S. d. I.: Programieren in C
Folie 5 - 22
führe die folgenden Schritte für alle Rasterpunkte (np, nq) des
Bildschirms aus
Schritt 1 (Anfangsinitialisierung):
p0 = p_min + np * delta_p
q0 = q_min + nq * delta_q
k = 0
xk = 0.0
yk = 0.0
Schritt 2 ((k+1)-ten Wert bestimmen):
xk1 = xk * xk - yk * yk + p0
yk1 = 2 * xk * yk + q0
k
= k + 1
(aktuelle reelle Koordinaten)
(Schleifenvariable)
((xk, yk): k-ter Wert)
((xk1, yk1): (k+1)-ter Wert)
Schritt 3 (Farbe bestimmen):
r = xk1 * xk1 + yk1 * yk1
falls r > M, wähle als Farbe Nummer k und gehe zu Schritt 4
falls k = MaxColor, wähle die Farbe "schwarz" und gehe zu
Schritt 4
iteriere Schritt 2
Schritt 4:
ordne dem Punkt (np, nq) die ermittelte Farbe zu und gehe
zum nächsten Punkt (Schritt 1)
Zur Lösung der Aufgabe dürfen nur die Dateien "gra_*.c" und
"vga.h" benutzt werden, die im Rahmen dieser LVA zur Verfügung
gestellt werden. Der Benutzer darf die Mandelbrotmenge auswählen,
die dargestellt werden soll. Die Bildausschnitte der obigen Tabelle
sollen als Feld einer geeigneten Struktur implementiert werden.
Programmaufbau:
Deklarationen und Bildauswahl;
InitGraphic (...);
Mandelbrotmenge ausgeben (SetPixel (...);)
wait_for_input ();
CloseGraphic ();
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
5.5
•
Folie 5 - 23
Bit-Felder
ein Bit-Feld ist eine Komponente einer Struktur mit folgender Syntax:
Typ {name} : konstanter Ausdruck;
Der Typ kann int, signed int oder unsigned int sein. Falls der optionale Name fehlt, kann auf das Bit-Feld nicht zugegriffen werden
(solche Felder werden zu Füllzwecken verwendet). Der konstante
Ausdruck muß eine positve ganze Zahl liefern, die die Größe des
Typs nicht überschreiten darf.
•
Bit-Felder hängen von der jeweiligen Implementierung ab und sind
i.a. nicht portabel. Folgende Details werden vom Standard nicht
festgelegt:
1) der Typ int kann als signed int oder unsigned int implementiert
werden
2) falls zwei aufeinanderfolgende Bit-Felder in einem Wort vom Typ
int untergebracht werden können, müssen sie in dieses Wort
gepackt werden
aber: falls sie nicht in einem Wort vom Typ int untergebracht
werden können, hängt es von der Implementierung ab, ob das
zweite Bit-Feld teilweise im ersten und teilweise in einem zweite
Wort oder ob es vollständig in einem eigenen Wort untergebracht
wird
3) die Bitfelder können von rechts nach links oder von links nach
rechts angeordnet werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
•
•
Folie 5 - 24
ein namenloses Bit-Feld der Länge 0 erzwingt, daß die nächste
Komponente der Struktur an einer int-Grenze beginnt
folgendes ist nicht möglich:
-
Bestimmung der Adresse eines Bit-Feldes
-
Zeiger auf Bit-Felder (Bitfelder haben keine Adresse)
-
Deklaration eines Feldes von Bit-Feldern
-
Funktionen, die Bit-Felder als Ergebnis liefern
Beispiel:
1)
struct { unsigned int Zeichen
unsigned int Farbe
unsigned int unterstreichen
unsigned int blinken
} Bildschirm [25][80];
Zugriff:
Bildschirm
Bildschirm
Bildschirm
Bildschirm
:
:
:
:
8;
4;
1;
1;
[3][12].Zeichen
[3][12].Farbe
[3][12].unterstreichen
[3][12].blinken
2)
struct { char name [30];
int alter;
unsigned int geschlecht : 1;
} person;
3)
struct { unsigned int select
unsigned int reset
unsigned int ld_time_const
unsigned int trigger
unsigned int slope
unsigned int range
unsigned int mode
unsigned int enable_int
} z80_ctc_ch_ctrl_reg;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
:
:
:
:
:
:
:
:
1;
1;
1;
1;
1;
1;
1;
1;
=
=
=
=
'a';
0x0a;
0;
1;
S. d. I.: Programieren in C
4)
struct { unsigned int member_1
unsigned int
unsigned int member_2
unsigned int
int member_3;
};
Folie 5 - 25
:
:
:
:
3;
5;
3;
0;
drei Bit für member_1 reservieren; fünf Bit sind undefiniert; drei
Bit für member_2 reservieren; int-Grenze wählen
5.6
•
Verbund
enthält (zu verschiedenen Zeiten) Objekte verschiedenen Typs
unterschiedlicher Größe (entspricht in etwa dem variant record in
Pascal)
•
alle Komponenten beginnen an derselben Speicheradresse, d.h. im
Gegensatz zur Struktur werden die Komponenten überlagert
⇒ der Speicherbedarf eines Verbunds ergibt sich aus dem Spei⇒
cherbedarf der größten Komponente
•
der Programmierer muß zu jedem Zeitpunkt wissen, über welchen
Komponentennamen er den Speicherbereich ansprechen darf, damit er
sinnvolle Daten erhält
•
Syntax:
1)
union [tag] {member declaration;} [decl [, decl [, ...]]];
-
optional kann ein Typname (tag) festgelegt werden
-
optional können bereits Variablen definiert werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
2)
Folie 5 - 26
union tag decl [, decl [, ...]];
-
ein Verbund mit dem entsprechenden Typnamen muß vorher
deklariert worden sein
-
•
es werden Variablen dieses Typs definiert
bei einem Verbund kann nur der ersten Komponente ein Anfangswert bei der Definition zugewiesen werden (neu bei ANSI-C)
•
ein Verbund kann sich selbst nicht als Komponente enthalten
•
ein Verbund kann einen Zeiger auf sich selbst als Komponente
enthalten (rekursive Datenstruktur)
•
•
die Komponenten eines Verbunds werden analog zur Struktur mit den
Operatoren "." (bei normalen Variablen) oder "->" (bei Zeigern) selektiert.
Beispiel:
1)
struct {char *name;
int flags;
int utype;
union {int
ival;
float fval;
char *sval;
} u;
} symtab [NSYM];
Definition einer Symboltabelle. Eine Variable kann nur einen Typ
haben, so daß hier ein Verbund zur Aufnahme des Wertes
geeignet ist.
Zugriff:
symtab [i].u.ival;
symtab [i].u.sval [0];
*symtab [i].u.sval;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
Folie 5 - 27
2) der Zugriff auf die Register des Prozessors ist für die verschiedenen Möglichkeiten als Verbund realisiert (siehe Datei dos.h)
3)
union { struct {int
i;
float f;
} val;
char name [6];
} test = {2, 3.3};
Dies ist bei alten C Compilern nicht erlaubt !
•
ein Verbund hängt u.U. von der jeweiligen Implementierung ab und
ist dann nicht portabel
/* Bit, Byte und Wort Zugriff auf Hardware Register.
*
* Folgende Compiler verarbeiten die folgenden Datenstrukturen korrekt:
*
*
Mark Williams C fuer Motorola 68000
*
* Fuer folgende Compiler muss der Byte Zugriff getauscht werden, d.h.
* "lower byte" vor "higher byte":
*
*
VAX/VMS C
*
Microsoft C fuer 8086/88
*
Lattice C
fuer 8086/88
*
Turbo C
fuer 8086/88
*
* Fuer folgende Compiler muss der Bit Zugriff getauscht werden, d.h.
* "higher bit" vor "lower bit" (Bits werden von links nach rechts
* angeordnet):
*
*
Lattice C fuer 8086/88
*
* Alle anderen oben erwaehnten Compiler verarbeiten die angegebenen
* Bitstrukturen korrekt, da sie Bits von rechts nach links anordnen.
*
* Datenstruktur fuer BIT, BYTE und WORD Zugriff definieren.
*
* Datei: bit_byte.h
Autor: S. Gross
* Datum: 30.08.1993
Stand: Compiler im Jahr 1986
*
*/
typedef union
{ struct
{ unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
int
int
int
int
int
int
int
int
int
int
bit_0
bit_1
bit_2
bit_3
bit_4
bit_5
bit_6
bit_7
bit_8
bit_9
:
:
:
:
:
:
:
:
:
:
1;
1;
1;
1;
1;
1;
1;
1;
1;
1;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
} bit_access;
Folie 5 - 28
bit_10
bit_11
bit_12
bit_13
bit_14
bit_15
:
:
:
:
:
:
1;
1;
1;
1;
1;
1;
struct
{ unsigned char high_byte;
unsigned char low_byte;
} byte_access;
unsigned int
} BIT_BYTE_WORD;
/* Bit 8 bis 15
/* Bit 0 bis 7
*/
*/
word_access;
Aufgabe:
1) Zeigen Sie, daß die obigen Behauptungen für den Microsoft C
Compiler und den Turbo C Compiler von Borland richtig sind.
2) Wie verhält sich der GNU C Compiler beim Bit-Byte-Wort-Zugriff ?
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
5.7
•
Folie 5 - 29
Aufzählungen
Syntax:
1)
enum [tag] {id [= konst. Ausdruck] [, id [= ...]]}
[decl [, decl [, ...]]];
2)
-
optional kann ein Typname (tag) festgelegt werden
-
optional können bereits Variablen definiert werden
enum tag decl [, decl [, ...]];
-
eine Aufzählung mit dem entsprechenden Typnamen muß
vorher deklariert worden sein
-
es werden Variablen dieses Typs definiert
•
eine Aufzählung ist eine Menge von Konstanten vom Typ int
•
eine enum-Variable kann zu jedem Zeitpunkt genau einen Wert der
Aufzählung speichern
•
enum-Variablen können wie int-Variablen in Index-Ausdrücken und
als Operanden bei allen arithmetischen und relationalen Operatoren
verwendet werden
•
jeder Name in der Aufzählungsliste repräsentiert einen Wert der Liste
•
dem ersten Namen wird automatisch der Wert 0 zugeordnet und
jedem weiteren Namen der nächste Wert in fortlaufender Reihenfolge
•
die automatische Zuweisung wird unterbrochen, wenn einem Namen
explizit ein Wert zugewiesen wird (der konstante Ausdruck muß
einen positiven oder negativen Wert vom Typ int liefern)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
S. d. I.: Programieren in C
•
Folie 5 - 30
falls der nächste Name keine explizite Zuweisung besitzt, erhält er
den Wert seines Vorgängers plus 1
•
eine Aufzählung kann mehrere Namen mit demselben Wert besitzen
(z.B. können null und zero beide den Wert 0 erhalten)
•
Beispiel:
1)
enum antwort { nein, ja = -5, vielleicht = 20} frage;
frage = nein;
2) Test von Sortierverfahren bei verschiedenen Datenbeständen
typedef struct { long key;
char *info;
} SATZ;
/* Aufbau eines Satzes
*/
typedef enum { sorted,
inv_sorted,
block = 3,
multiple,
random,
no_more
} LISTE;
...
/* zu untersuchende Listen
*/
/* Schleifenvariable
*/
int main (void)
{
LISTE
nr;
...
for (nr = sorted; nr < no_more; ++nr)
{
init (nr, original);
/* original ist global def.
...
};
return (0);
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
S. d. I.: Programieren in C
void init (LISTE nr, SATZ *r)
{
long i;
switch (nr)
{
case sorted:
for (i = 0; i < n; ++i)
{
r[i].key = i;
r[i].info = "sorted";
};
break;
...
};
Folie 5 - 31
/* Listen initialisieren
*/
/* n ist global definert
*/
}
5.8
•
Definition neuer Datentypen
mit Hilfe von typedef kann ein Synonym für einen Datentyp definiert
werden
•
Syntax:
typedef Typ Synonym
•
Beispiel: siehe oben
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Herunterladen