C - Kurs Hadersbeck

Transcrição

C - Kurs Hadersbeck
C - Kurs
Hadersbeck
Literaturhinweise:
Kernighan, Ritchie : The C Programming Language, Prentice Hall, 1988
Darnell, Margolis : Software Engineering in C, Springer Verlag, 1988.
1
1.
Einleitung
1.1.
Historische Bemerkungen
Entwickelt von Kernighan, Ritchie (AT&T Bell Laboratoriers)
Relativ geringer Sprachumfang : Kontrollstrukturen, wenig Variablentypen, Funktionen, Modulares
Programmieren, Kein Input Output.
Erste Version 1983 von C veröffentlicht in "The C Programming Language" (Kernighan, Ritchie) (1983)
UNIX geschrieben in C.
Neuere ANSI Version (1988) wird zum Standard (Besseres Funktionen Konzept, definierte Library),
veröffentlicht in "The C Programming Language" , second Edition (Kernighan, Ritchie) (1988)
2.
Grundlagen
2.1.
Allgemeine Programmstruktur
Ein C-Programm kann aus beliebigen Funktionen, die über verschiedene Module verteilt sind bestehen. Eine
Funktion innerhalb des Programms entspricht der Hauptfunktion und heißt main(). Sie stellt den
Programmeinsprungspunkt dar.
Jedes C-Programmodul hat folgende Struktur :
[ Praeprozessor Direktiven ]
[ Typ - Deklarationen ]
[ Definition und Deklaration von Variablen (globale Variablen) ]
[ Deklaration von Funktionen ]
Definition von Funktionen (nur eine Funktion wird main genannt )
Jede Funktion hat folgende Struktur :
[ Funktionstyp ]
Funktionsname ( [
Parameter 1 ,...,
Parameter n ] )
Funktionsrumpf
Ein Funktionsrumpf hat folgende Struktur :
{
[ Deklarationen von Variablen ]
}
Statements
Beispiel :
Funktion mit Hauptprogramm
2
Praeprozessor - Direktiven
funktion
{
/* Anweisungen der Funktion_1 */
}
main ()
{
/* Hauptprogramm */
}
dazu Programmbeispiele :
1. Programm :
#include <stdio.h>
hallo ()
{
puts("Hallo");
}
main ()
{
hallo();
}
2. Programm :
#include <stdio.h>
#define readint(Z) scanf("%d",&Z)
/* Minimum, Produkt
berechnet die kleinere und das Produkt zweier Zahlen, die der Benutzer
eingegeben
hat.
*/
int mult (int a, int b)
{
return (a*b);
}
main()
{
int kleiner,prod;
int zahl1, zahl2;
/* das Ergebnis */
/* Eingabe Werte */
/* prompt fuer die Benutzereingabe */
puts(" Bitte geben Sie die erste Zahl ein ");
readint(zahl1);
puts(" Bitte geben Sie die zweite Zahl ein ");
readint(zahl2);
/* suche die kleinere Zahl */
if (zahl1 < zahl2)
kleiner = zahl1;
else
kleiner = zahl2;
printf("Die kleinere der Zahlen %d und %d ist :%d\n",zahl1,zahl2,kleiner);
/* berechne das Produkt */
prod = mult(zahl1,zahl2);
printf("Das Produkt der Zahlen %d und %d ist : %d \n",zahl1,zahl2,prod);
3
}
2.1.1. Aufgaben
1. Kreieren Sie in Ihrem Homeverzeichnis einen Ordner Uebung1
2. Implementieren Sie in diesem Ordner das Programm 1 und Programm 2 aus diesem Kapitel.
Der Source-Filename für Programm 1 ist : prog-1.c
Der Source-Filename für Programm 2 ist : prog-2.c
2. Schreiben Sie ein Programm addiere, das drei ganze Zahlen einliest, die größte und die Summe der drei
Zahlen ausgibt.
Dazu verwenden Sie die Funktion add :
int add(int a, int b, int c)
{
return (a+b+c)
}
2.2.
Kommentare in Programmen
Kommentare können an beliebiger Programmstelle stehen und werden von den Zeichen
/* und */ eingerahmt. Die Länge der Kommentare ist unbeschränkt, wobei die Kommentare jedoch nicht
verschachtelt sein dürfen.
2.3.
Konstanten
2.3.1. ganze Zahlen
integer Konstanten : 1234
long integer : suffix l oder L 234L,
oktal Zahl : '\013' oder prefix 0
(Hochkomma auf der Appletastatur : alt ´ (links von der DELETE Taste) )
hexadezimal Zahl : '\xhh' oder prefix 0x
unsigned integer : postfix u oder U
2.3.2. floating Zahlen
float Konstanten : 2.3, 1e-2
2.3.3. Zeichen und Zeichenketten
string Konstanten : "abc"
character Konstanten : 'c'
4
2.4.
Variablen
2.4.1. Was sind Variablen
Eine Variable besteht aus zwei Teilen : Einem Namen und einem Objekt. Jede Variable zeigt genau auf ein
Objekt und kann zu einem Zeitpunkt nur einen Wert annehmen.
x
=
Variablenname
10
Wert der Variable
int x;
Adresse
Adresse
Adresse
Beispiel :
Häuser werden mit Hausnummern verwaltet. Der Variablenname entspricht der Hausnummer. Jede
Hausnummer zeigt auf ein Haus (Objekt) und kann zu einem Zeitpunkt nur einen Wert (Nummer)
annehmen. Das Objekt steht an einer bestimmten Stelle (Speicheradresse), die sich nicht ändern
kann.
5
2.4.2. Regeln für Variablen in C
–
–
–
-
Jede Variable muß vor dem Gebrauch deklariert werden
Variablennamen beginnen mit Buchstaben
Groß/Kleinschreibung ist signifikant
Namen sollten nicht mit Underscore beginnen (reserviert für Systemvariablen)
31 Buchstaben sind signifikant bei lokalen Variablen
2.4.3. Grundtypen von Variablen
2.4.3.1.
Ganze Zahlen
int
... Integer, implementationsabhängig 16, 32 oder 64 Bit
short
... Integer, 16 Bit
long
... Integer, 32 oder 64 Bit
unsigned int, unsigned short, unsigned long ... kein Vorzeichen
2.4.3.2.
Zeichen
char
unsigned char
2.4.3.3.
... Character 7 Bits
... Character 8 Bits
enum
enum
... Aufzählung einer Menge durch ihre Symbole
enum boolean { YES, NO};
2.4.3.4.
float
2.4.3.5.
double
floating
... Reelle Zahl, 32 Bit
double
... Reelle Zahl, 64 Bit
6
2.4.4. Deklaration von Variablen
Jede Variable muß mit ihrem Namen und ihrem Typ vor dem Gebrauch definiert werden: Daraufhin reserviert
der Kompiler Speicherplatz für sie. Die Definition einer Variablen nennt man Deklaration.
Bei einer Deklaration wird der Typ und die mit Komma getrennten Namen der Variablen angegeben.
Variablen können außerhalb oder innerhalb von Funktionen, oder am Anfang eines Blocks deklariert werden.
Beispiel :
int lower;
float x,y;
char esc;
2.4.5. Lebensdauer von Variablen
Die Lebensdauer einer Variable ist abhängig von der Deklarationsstelle.
Liegt die Deklarationsstelle :
1. Fall : außerhalb einer Funktion (globale Variable)
Lebensdauer innerhalb aller Funktionen des gesamten Programms.
2. Fall : innerhalb einer Funktion (lokale Variable)
Lebensdauer innerhalb der Funktion
3. Fall : innerhalb eines Blocks
Lebensdauer innerhalb des Blocks
2.4.6. Gültigkeit von Variablen
Die Gültigkeit einer Variable entspricht solange der Lebensdauer der Variablen, bis nicht innerhalb einer
Funktion oder eines Blocks eine Variable mit dem gleichen Namen definiert wird.
Beispiel :
#include <stdio.h>
#define readint(Z) scanf("%d",&Z)
int a,b,c,d;
int add(int a,b)
{
int c;
c = a + b;
return c;
}
main()
{
int c;
puts( " Bitte geben Sie zwei Zahlen ein");
readint(a); readint(b);
c = add(a,b);
printf(" Die Addition lautet = %d\n",c);
}
7
2.4.7. Schlüsselwörter
Folgende Schlüsselwörter sind reserviert und dürfen nicht als Variablennamen verwendet werden :
auto
break else
case
char
const float
continue
default
do
double
long
enum
extern
short
for
goto
if
int
switch
register
return
unsigned
signed
sizeof
static
struct
typedef
union
void
volatile
while
2.4.8. Initialisierung von Variablen
Variablen können bei der Deklaration auch Initialisierungswerte zugewiesen werden.
Beispiel :
char esc = '\\';
int grenze = MAXLINE + 1;
int upper=100, abc[100];
float x,y,z=1.0e-5;
2.4.9. Zuweisen von Werten an eine Variable
lvalue
lvalue
lvalue
= rvalue oder
<op> = rvalue ist äquivalent zu
= value(lvalue) <op> rvalue
Achtung :
lvalue hat : Typ, Adressenklasse, Namen, Adresse
rvalue hat : Typ, Namen, Wert
Beispiel :
x = 2 + 3;
x += 1;
entspricht
x = x + 1;
8
2.4.10. Konditionale Zuweisung
expr1 ? expr2 : expr3 entspricht :
if expr1 then expr2 else expr3;
Beispiel :
z = (a>b) ? a : b
entspricht :
z = max(a,b)
oder
if (a>b) z=a; else z=b;
2.4.11. Funktionen zum Einlesen und Ausgeben von Variablenwerten
Zum Einlesen von Variablenwerten gibt es die Funktion
scanf(" Kontrollstring ",Adresse_Variable1,Adresse_Variable2 .... )
Beim Einlesen muß die Adresse der Variable in scanf eingetragen werden (& Operator vor dem
Variablennamen).
Zum Ausgeben der Werte von Variablen gibt es die Funktion
printf(" Kontrollstring ",Variable1,Variable2 .... )
Im Kontrollstring wird der Text der Zeile und die Formatierungsanweisung (% Operator für
Formatierungsbuchstaben) für jede Variable angegeben. Soll am Ende der Zeile ein Zeilenvorschub eingefügt
werden, muß dafür ein explizites Zeilenvorschub Zeichen eingefügt werden.
Die wichtigsten Formatierungsbuchstaben sind :
d für dezimal Zahlen
f für reelle Zahlen
c für Buchstaben
9
Beispiel :
int m;
float x;
char ant;
printf("Bitte geben Sie eine ganze Zahl ein >>>");
scanf("%d",&m);
printf("Die Eingabe war %d\n",m);
printf("Bitte geben Sie eine reelle Zahl ein >>>");
scanf("%f",&x);
printf("Es wurde die ganze Zahl %d und die reelle Zahl %f
eingegeben\n",m,x);
printf("Es wurde die ganze Zahl >>>%d<<< und die reelle Zahl >>>%f<<<
eingegeben\n",m,x);
printf("Weitermachen y/n >>>");
fflush(stdin);
scanf("%c",&ant);
2.4.12. Aufgaben
1. Schreiben Sie ein Programm das eine ganze und eine reelle Zahl einliest. Geben Sie das Quadrat und die
Summe der Zahlen aus.
2. Schreiben Sie ein Programm, das einen Buchstaben einliest und den Buchstaben mit seinem ASCII -Wert
ausgibt. Ändern Sie den Buchstaben jenachdem in seinen äquivalenten Groß/Kleinbuchstaben.
10
2.5.
Operatoren
2.5.1. Arithmetische Operatoren
Binäre arithmetische Operatoren sind :
+
*
/
/
%
plus
minus
Multiplikation
Division, reellwertig
Division, ganzzahlig
Modulo Operator
2.5.2. Typenkonvertierung bei arithmetischen Ausdrücken
In einem arithmetischen Ausdruck mit Variablen verschiedener Datentypen wird eine Variable mit schwächerem
Typ automatisch in einen stärkeren konvertiert :
schwach
char -> int
->
->
stark
float -> double.
Explizite Konvertierung muß mit dem cast Operator erzwungen werden. Der cast Operator besteht aus den
Zeichen ( und ), den gewünschten Type eingeschlossen.
Beispiel :
x = (int) y; cast operator mit dem Typen int
int a,b;
(a/b)*b + (a%b) == a;
float kreisfläche(float radius)
{
float pi = 3.14;
return 2 * radius * radius *pi;
}
11
2.5.3. Aufgabe
Welches Ergebnis und welcher Typ wird erzwungen:
short a = 10;
long b = 1L;
float x = 9.0;
double y = 2.3d-2;
a
b
b
x
x
y
=
=
=
=
=
=
(short) b;
______
a;
______
a + x;
______
sqrt ((double) x); ______
y + x;
______
a + b + x + y
______
__________
__________
__________
__________
__________
__________
2.5.4. Relationale und Logische Operatoren
Es gibt die logischen Werte : wahr (jede Zahl ungleich Null) und falsch (nur die ganze Zahl Null).
logische Operatoren sind :
<
<=
>
>=
==
!=
&&
||
!
kleiner
kleiner gleich
größer
größer gleich
gleich
ungleich
und
oder
nicht
Achtung :
Die Priorität von && ist größer als die von ||, sowohl && als auch || haben aber eine niedrige Priorität als > <,
>= und <= (z.B. x < y && y < x )
Die Negation ( ! ) ist einstellig und konvertiert TRUE (nicht Null) in FALSE (Null) und FALSE (Null) in TRUE
(nicht Null).
Komplexe logische Ausdrücke werden von links nach rechts abgearbeitet, die Abarbeitung ist beendet, wenn das
Ergebnis bestimmt ist.
Beispiel :
if (j < MAX && ((c=getchar()) != '\n'))
if ((1<lim-1) || (!valid) && (x!=4)) x=0
12
2.5.5. Die Wahrheitstabelle bei logischen Ausdrücken
int p,q;
p
q
p && q
p || q
! p
———————————————————————————————————————
0
0
0
0
1
0
1
0
1
1
1
0
0
1
0
1
1
1
1
0
2.5.6. Aufgabe
Was ist der Wert :
2 == 7
_____
1 >= 0
4 > 4
_____
_____
1 = 2
_____
Beispiel :
float reziprokwert(float x)
{
if (x != 0)
return 1/x;
else
puts(" Achtung Eingabewert ist gleich Null")
}
13
2.5.7. Increment und Decrement Operatoren
++
--
addiert eins auf eine Variable,
subtrahiert eins von einer Variable.
++ und -- können bei Variablen als postfix und als prefix verwendet werden, haben aber dann bei der
Auswertung des Ausdrucks unterschiedliche Bedeutungen.
als prefix : verändere den Wert der Variable und verwende den neuen Wert im Ausdruck
als postfix: verwende zuerst den Wert der Variable innerhalb des Ausdrucks und verändere ihn dann.
Beispiel :
int a = 1;
b = ++a;
Der Wert von a ist 2
Der Wert von b ist 2
int a = 1;
b = a++;
Der Wert von a ist 2
Der Wert von b ist 1
2.5.8. Aufgabe
Was ist der Wert des Ausdrucks :
int j = 0,m=1,n=1;
x = m++ - --j;
___________
m += ++j +2;
___________
j = m++ * m++;
___________
14
2.5.9. Arithmetische Zuweisungsoperatoren
Operator
Zuweisung
Additionszuweisung
Subtrakt. Zuweisng.
Multipliz. Zuweis.
Divisions Zuweisung
Modulo Zuweisung
Symbol
=
+=
-=
*=
/=
%=
Form
a=b
a += b
a -= b
a *= b
a /= b
a %= b
Operation
Speichere den Wert von b in a
Speichere den Wert von a+b in a
Speichere den Wert von a-b in a
Speichere den Wert von a*b in a
Speichere den Wert von a/b in a
Speichere den Wert von a%b in a
Achtung bei Zuweisungen, wenn die Priorität von Operatoren ins Spiel kommt.
Beispiel :
a = a * 3 + 4;
ist ungleich zu
a *= 3 + 4;
2.5.10. Aufgaben
Welche Werte werden bei folgenden Ausdrücken berechnet :
int m = 3,n= 4;
float x = 2.5,y=1.0;
a)
b)
m +=n+x-y;
m /= x+n+y;
____________
____________
c)
m %= y+m;
____________
d)
x+= y -=m;
____________
2.5.11. Bitweise Operatoren
& (bitwise AND), | (bitwise inclusive OR),
^ (bitwise exclusive OR), << left shift, >> right shift,
~ (one komplement),
z.B. : ausblenden von Bits : n = n & 0177;
z.B. : setzen von Bits : n = n | 0101;
z.B. : shift von Bits : 007 << 3 ist 070;
07 >> 1 ist 03
15
2.5.12. Prioritäten der Operatoren
Innerhalb von Ausdrücken gelten folgende Prioritäten bei der Auswertung des Ausdrucks. Je höher der Wert,
desto früher wird der Operator ausgewertet.
Level
Operatoren
Assoziativität
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
()
[] -> .
! ~ ++ -- + - * & (type) sizeof
+
*
/ %
>> <<
<
<= > >=
== !=
&
^
|
&&
||
?:
= += -= *= /= %= &= ^= |= <<=
,
links nach rechts
rechts nach links
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
links nach rechts
rechts nach links
rechts nach links
links nach rechts
Beispiel :
a == b && x || !y
(a == b) && (x || !y)
( x=getchar() != EOF)
( (x=getchar()) != EOF)
16
>>=
2.5.13. Aufgaben
1. Welche Werte werden von folgenden Programmen ausgedruckt ?
#include <stdio.h>
#define readint(Z) scanf("%d",&Z)
#define readfloat(Z) scanf("%f",&Z)
int arith (void)
/* 1. Programm :
Arithmetik */
{
int a,b,c,d;
int erg;
puts(" Geben Sie vier ganze Zahlen ein \a: ");
readint(a); readint(b); readint(c); readint(d);
erg = a + b;
printf(" a + b = %d\n",erg);
erg = a / b;
printf(" a / b = %d\n",erg);
erg = a % b;
printf(" a mod b = %d\n",erg);
erg = c++;
printf("Nach erg = c++ ist der Wert von erg = %d, von c = %d \n",erg,c);
erg = --a;
printf("Nach erg = --a ist der Wert von erg = %d, von a = %d \n",erg,a);
a += 2;
printf(" Nach a += 2 ist der Wert von a = %d\n",a);
}
int logik (void)
/* 2. Programm
Logische Ausdruecke */
{
int a,b,c,d;
float x;
puts(" Geben Sie vier ganze Zahlen ein : ");
readint(a); readint(b); readint(c); readint(d);
if ( b != 0 )
{
x = a/b;
printf(" Der Reziprokwert = %f\n",x);
};
if ( a==b )
puts( " a ist gleich b ");
else
puts(" a ist ungleich b");
if ( a != b && ( a != c || a != d) )
printf( "jawohl \n");
if ( a != b + c )
printf(" a != b + c \n");
}
main (void)
{
arith();
logik();
}
17
2. Schreiben Sie ein Programm, das eine ganze Zahl einliest und ausdruckt ob die Zahl gerade oder ungerade ist.
3. Schreiben Sie ein Programm das eine ganze Zahl einliest, die Quersumme berechnet und ausgibt. Das
Programm soll auch die Oktal und Hexadezimaldarstellung der Zahl ausgeben Hinweis :
(Formatierungsanweisung)
4. Schreiben Sie ein Wechselkursprogramm für Amerikanische Dollars
Das Programm liest den Betrag bzw. Dollar Betrag ein und berechnet den Wert in der jeweils anderen
Währung.
5. Schreiben Sie ein Programm das eine ganze und eine reelle Zahl einliest. Geben Sie das Quadrat und die
Summe der Zahlen aus.
6. Schreiben Sie ein Programm, das einen Buchstaben einliest und den Buchstaben mit seinem ASCII -Wert
ausgibt. Ändern Sie den Buchstaben je nach dem in seinen äquivalenten Groß/Kleinbuchstaben.
18
3.
Strukturierung von Programmen
3.1.
Kontrollstrukturen
3.1.1. Statements und Blöcke
Jeder Ausdruck, abgeschlossen von einem Semikolon ist ein Statement. Somit ist das Semikolon kein
Trennsymbol sondern das Abschlußzeichen eines Statements.
Mehrere Statements können mit geschweiften Klammern zu einem Block zusammengefaßt werden. Der Block
(=Block-statement) gilt nach außen wie ein Statement und wird nicht mit einem Semikolon abgeschlossen.
Es gibt folgende Statements :
expression-statement
compound-statement
selection-statement
iteration-statement
jump-statement
labeled-statement
3.1.2. expression statement
Dem expression statement entsprechen Wertzuweisungen, Wertvergleiche oder Funktionsaufrufe.
Expressions werden sequentiell der Reihe nach ausgewertet und von einem Semikolon abgeschlossen. Die leere
expression heißt Nullstatement.
Beispiel :
x = 1;
i <= a;
a = b + x;
;
3.1.3. compound statement
Die geschweiften Klammern gruppieren statements zu einem compound statement (=Block). Innerhalb eines
compound statements können Variablen deklariert werden. Die Lebensdauer der Variablen ist nur auf diesen
Block geschränkt.
Beispiel :
{
}
int a;
a=x;
/* Hier kein Semikolon notwendig, da Block-statement */
oder :
if (a==0) { b=1; c=1; d=1; printf(“%d”,i);
oder :
if (a==0) { int i;
}
i= a + 1; printf(“%d”,i); }
19
3.2.
selection-statements
3.2.1. selection statment : if , if - else
if (expression)
statement
if (expression)
statement1
else
statement2
Das if statement testet den numerischen Wert der Bedingung (expression). Falls der Wert ungleich Null ist
(TRUE Fall), wird statement1 ausgeführt.
Es wird nur ein einziges statement nach der Bedingung ausgeführt. Sollen mehrere statements zusammengefaßt
werden, so müssen sie mit geschweiften Klammern geklammert werden.
Bei geschachtelten if .. else gehört das else immer zum näheren if.
Achtung :
expression muß immer in runden Klammern eingschlossen werden.
Beispiel :
if (i==2)
else
oder
puts("i ist gleich zwei");
puts(" i ist ungleich zwei") ;
if ((i==2) || (j!=0))
{ puts(" Der Fall ist eingetreten");
i = 3; }
else
puts(" Ja du Glückspilz ");
Achtung, folgendes ist zulässig :
Ausdruck
if (i=2)
if (++i)
if (i=j<n)
if (i)
if (2)
Bedeutung
Zuweisung an i, dann Test : immer true
Erhöhen des Wertes von i, dann Test
Zuerst errechnen und zuweisen des Wertes an i, dann test ob i TRUE (!= 0) oder
FALSE (=0)
Test ob i TRUE (!= 0) oder FALSE (=0)
immer TRUE (!= 0)
20
3.2.2. selection-statement : switch
switch (expression)
{
case constant-expr: statements1
case constant-expr: statements2
default : statementsn
}
Jeder Label hat eine oder mehrere ganzzahlige Konstanten oder Konstanten Ausdrücke. Entsprechend dem Wert
von expression werden sequentiell alle case constant-expr. ausgewertet und verglichen, ob sie mit dem Wert
übereinstimmen. Bei Übereinstimmung werden die nachfolgenden statements ausgeführt und beim nächsten case
Statement weiter getestet. Soll die Abarbeitung der sequentiellen Tests nach der Ausführung eines statements
abgebrochen werden, so muß dieses statement mit break abgeschlossen werden. Die Abarbeitung wird dann
hinter dem gesamten switch statement fortgesetzt.
Wird keine Übereinstimmung zwischen dem Wert von expression und constant-expr. gefunden, so wird auf
einen eventuell vorhandenen default Fall verzweigt.
Beispiel :
switch (c)
{
case ´0´
case ´2´
case ´2´
default
}
3.3.
: case ´1´ :
:
: case ´3´:
:
puts("0,1"); break;
puts("2");
puts("2,3");
puts(" Nichts davon");
Iteration statements
3.3.1. iteration statement : while
while (expression)
statement
Solange expression TRUE (ungleich Null) ist , wird statement ausgeführt. Es wird genau ein statement
ausgeführt. Sollen mehrere statements ausgeführt werden, müssen sie zu einem compound-statement
zusammengefaßt werden.
Statement wird solange wiederholt, bis der Wert von expression FALSE (gleich Null) ist.
21
Beispiel :
int while_1(void)
{
int num;
puts("Gib den Countdown Wert ein >>>");
readint(num); /* Achtung bei Endlosschleifen */
while ( num > 0 )
{
num --;
printf(" Countdown laeuft >>%d<<\n",num);
};
printf(" Start \n");
}
Achtung vor Unendlichschleifen :
i = 5;
while (i) ;
i --;
while (i > 0)
printf(" Countdownwert ist = %d\n",i);
i--;
3.3.2. iteration statement : for
for (expr1; expr2; expr3)
statement
expr1 : Wird vor dem Durchlauf der Schleife ausgeführt (Initialisierung)
expr2 : Testbedingung der Scheife : Wiederholung falls expr2 ungleich Null
expr3 : Wird nach einem Durchlauf von statement ausgeführt (Increment)
entspricht :
expr1;
while (expr2)
{
statement;
expr3;
}
Achtung : Im Gegensatz zu anderen Programmiersprachen ist die for Schleife bei C viel flexibler, da die
Komponenten expr1, expr2, expr3 einer for loop beliebige Ausdrücke sein können, deren Werte auch innerhalb
der Komponenten verändert werden können.
22
Beispiele :
Es dürfen auch expr's der for Schleife fehlen :
Die endlos Schleife :
for (;;) ;
Der Index einer for Schleife muß nicht unbedingt ein ganzzahliger Wert sein :
int for_2(void)
{
char a;
puts(" Die Buchstaben von a bis g lauten : ");
for (a='a'; a < 'g'; a ++)
printf(" %c ",a);
printf("\n");
int for_3(void)
{
float x;
puts(" Die Reihe von x - 1/x lautet : ");
for (x=3; x > 0.00005; x = x - 1/x)
printf(" %6.3f\n ",x);
printf("\n");
}
for (i=0; i<n; i++)
{
printf(" Der Schleifendurchlauf Nummer %d\n",i);
m= n+i;
}
komplexe Ausdrücke in expr1, expr2 und expr3, als Rumpf der for Schleife wird die leere Ausweisung
verwendet. Sämtliche Berechnungen werden innerhalb der for Schleife ausgeführt. Dies ist jedoch ein sehr
komplizierter Programmstil und eigentlich abzulehnen.
for (i=0,j=1,x=10; i=j*2*x; i = i*j,x--) ;
3.3.3. Aufgabe :
Welche Werte werden bei folgendem Programmausschnitt ausgedruckt :
int i,j;
int n,m;
n=m=4;
for (i=0;j<n;i++)
for (j=0;j<m;j++)
printf("Der Matrixindex lautet : i = %d
23
j = %d \n",i,j);
3.3.4. iteration statement : do - while
do
statement
while (expression);
Das statement wird ausgeführt und die expression ausgewertet. Falls die expression TRUE ist (ungleich Null)
wird das statement erneut ausgeführt. Wird expression gleich Null (FALSE) terminiert die Schleife und es wird
hinter das Programm wird hinter dem do statement fortgesetzt.
3.4.
jump statements
3.4.1. jump statement : break
Das break statement stellt ein vorzeitiges exit aus einem for, while, do oder switch statement dar. Das break
statement veranlaßt, die innerste Schleife sofort zu verlassen.
Das continue statement beendet eine Iteration innerhalb einer Schleife und springt direkt zum nächsten
Schleifendurchlauf.
3.4.2. jump statement : goto, label
label :
und goto label;
Es wird empfohlen, auf die Programmierung mit Lables zu verzichten (Spagetticode). Es gibt genügend
Kontrollanweisungen um Sprünge innerhalb des Programms zu vermeiden.
3.5.
Die Vor- und Nachteile von for, do und while
Die for Loop wird dann verwendet :
- wenn der Schleifenindex im Vordergrund steht. (z.B. Durchlaufen von Arrays).
- Bei einfachen Interationsschritten
- Falls die Iteration und das Abbruchkriterium eng zusammenhängen
Die while ( ) Loop wird verwendet :
- wenn das Abbruchkriterium im Vordergrund steht
- es soll zuerst getestet werden, bevor die Statements im while Block ausgeführt werden, da sonst Fehler
auftreten könnten (z.B. Arithmetischer Ausdruck um Division durch Null zu vermeiden)
Die do () Loop wird verwendet :
- wenn die Statements im do Block im Vordergrund stehen.
- Es sollen zuerst die Statements ausgeführt werden und dann die Bedingung getestet werden. (z.B : Einlesen von
Abbruchkriterien)
24
Beispiel :
#include <stdio.h>
main()
/* Demonstration of blocks and statements */
{
char in,buffer[10];
int len,i=0;
while ( ( (buffer[i++]=getchar() ) != '\n') && (i < 10)) ;
len=i;
for (i=0;i<len;i++)
{
char c;
switch (c = buffer[i])
{
case '0':case '1':case '2':case '3':case '4':case '5' :
case '6':case '7':
puts(" Die Zahl ist kleiner 8"); break;
case '8':case '9':
puts(" Die Zahl ist größer 8"); break;
default :
if (( c >= 'A' && c <= 'Z') || ( c >= 'a' && c <= 'z'))
puts(" Wir haben einen Buchstaben");
else
puts(" Wir haben ein Sonderzeichen");
}
}
}
3.5.1. Aufgaben
1. Schreiben Sie ein Programm, das 20 Buchstaben ein liest und zählt wieviele Groß bzw. Kleinbuchstaben und
Umlaute darunter waren.
2. Schreiben Sie ein Programm, das bis zu 5 mal eine nur im Programm bekannte Geheimzahl abfagt. Bei jedem
Durchlauf wird dem Benutzer ein neuer Tip gegeben, der einen neuen Hinweis zu dieser Geheimzahl gibt. Wird
die Zahl gefunden, dann werden die Anzahl der Versuche ausgegeben.
3. Schreiben Sie ein Programm, das eine Zahl einliest und in Abhängigkeit der Zahl einen beliebigen Spruch auf
das Terminal ausgibt. Nur bei einer bestimmten Zahl soll das Program terminieren. Verwenden Sie zum beenden
des Programms dazu die Lösung aus Aufgabe 2
25
4.
Zusammengesetzte Datentypen
4.1.
Arrays
Um Elemente gleichen Typs indiziert ansprechen zu können gibt es in C Arrays. Arrays werden mit ihrem Typ,
dem Variablennamen und der Dimension deklariert.
Beispiel :
int zahlen[10];
float x[5],y[1000];
Mit der Deklaration eines Arrays wird Speicherplatz für die festgelegten Elemente zur Verfügung gestellt. Die
einzelnen Elemente werden über Indizies angesprochen :
erstes Element :
zweites Element :
usw.
Index
0
1
......
n
Index 0 : z.B.: x[0]
Index 1 : z.B.: x[1]
Wert
Wert des ersten Elements
Wert des zweiten Elements
......
Wert des n+1 Elements
Achtung : Das erste Element hat den Index 0 nicht den Index 1.
Beispiel :
int i;
for (i=0;i<5;i++) x[i] = 0.0;
Mit der Deklaration eines Arrays können die einzelnen Elemente auch initialisiert werden. Dabei muß entweder
die Dimension groß genug sein um die einzelnen Elemente abspeichern zu können, oder die Dimension wird bei
der Deklaration weggelassen. Im zweiten Fall errechnet der C Compiler die Dimension des Arrays selbst:
Beispiel :
int z[5] = {1,2,3,4,5};
oder :
int z[] = {1,2,3,4,5}; (Errechnete Dimension [5])
Dies bedeutet:
z[0]=1,z[1]=2,z[2]=3,z[3]=4, z[4]=5
Wird die Dimension größer gewählt als die Anzahl der abzuspeichernden Elemente, werden globale und
statische Arrays automatisch mit Nullen aufgefüllt. Lokale Arrays werden nicht mit Nullen aufgefüllt, der Inhalt
der nicht initialisierten Elemente ist zufällig (Achtung!).
Werden Arrays als Funktionsargumente übergeben, können sie in der Argumentliste mit oder ohne Dimension
aufgeführt werden.
Beispiel :
26
int sort(int zahlen[])
oder
int sort(int zahlen[10])
Innerhalb der Funktion wird die Dimension weder zur Kompilationszeit, noch zur Laufzeit überprüft, bzw.
überwacht. Der Programmierer muß auf den richtigen Zugriff selbst achten.
4.2.
Strings
In C gibt es keinen Standardtyp string. Strings können in C in einem Array von Buchstaben gespeichert werden.
Beispiel :
char str[10];
definiert Array von Buchstaben der Länge 10 mit dem Namen str.
Werden in einem solchen Array Buchstaben abgespeichert und als letzter Buchstabe eine Null gespeichert, dann
spricht man von einem String.
Einem String in C entspricht also in C eine mit Null terminierte Zeichenkette.
Beispiel :
Als Characterarry :
char str[5];
str[0] = 'a';
str[1] = 'b';
str[2] = 'c';
str[3] = 'd';
a
b
c
d
Als String :
char str[5];
str[0] = 'a';
str[1] = 'b';
str[2] = 'c';
str[3] = 'd';
str[4] = 0; /* oder '\0' */
a
b
c
d
\0
Die Programmiersprache C erwartet, daß Strings mit dem Eintrag Null als Terminierungszeichen abgeschlossen
werden. Die Initialisierung eines Strings zieht diese Tatsache in Betracht und stellt für das Terminierungszeichen
einen zusätzlichen Speicherplatz zur Verfügung. Das Array von Buchstaben kann aber auch ohne Dimension
deklariert werden.
Beispiel :
richtig :
char str[6] = "Hallo"
oder :
27
char str[] = "Hallo"
dies entspricht folgendem Eintrag im Speicher :
h
a
l
l
o
\0
Der Kompiler laesst folgendes zu, meldet aber keinen Fehler :
char str[5] = "Hallo"
Die Tatsache, daß die Zahl Null bei strings immer als Terminierungszeichen verwendet wird, und der Zahl Null
der logische Ausdruck FALSE entspricht, wird in C-Programmen ständig ausgenutzt :
Beispiel :
/* kopieren des Inhalts von Strings */
char a[5] = "abcd",b[5];
int i;
/* 1. Lösung ohne Ausnutzung des Terminierungszeichens : */
for (i=0;i<5;i++) b[i] = a[i];
/* 2. Lösung unter Ausnutzung des Terminierungszeichens: */
for (i=0;a[i];i++) b[i] = a[i];
b[i]=0;
/* 3. Lösung unter Ausnutzung des Terminierungszeichens: */
i=0;
while (a[i] )
{ b[i] = a[i];
i++;
};
b[i]=0;
Beispiel :
/* die Länge eines Strings : */
int length(char str[])
{
int erg;
erg = 0;
while (str[erg]) erg++;
return erg;
}
28
4.2.1. Aufgabe
Ist die Initialisierung zulässig und falls ja, was steht im Speicher :
char s1[6] = "Hallo";
char s2[5] = "Hallo";
char s3[5] = "H";
char s4[] = "Hallo";
int a1[5] = {1,2,3,4,5,6};
int a2[] = {1,2,3,4,0};
int a3[5] = {1,2}; /* lokale Variable */
int a3[5] = {1,2}; /* globale Variable */
29
4.3.
Character Eingabe und Ausgabe Funktionen
Bei der Ein/Ausgabe von Buchstaben dient das Terminal sowohl als Eingabemedium (stdin) und als
Ausgabemedium (stdout)
int getchar(void);
getchar liest einen Buchstaben vom Terminal z.B. buchstabe = getchar();
char *gets(char *s);
gets liest die nächste Zeile von stdin in den Vektor s und ersetzt dabei den abschließenden Zeilentrenner
durch '\0'. Die Funktion liefert s oder NULL bei Dateiende oder bei einem Fehler.
int putchar(int c);
putchar schreibt einen Buchstaben auf das Terminal
int puts(char *s);
puts schreibt die Zeichenkette s und einen Zeilentrenner in stdout. Die Funktion liefert EOF, wenn ein
Fehler passiert, andernfalls einen nicht-negativen Wert.
int ungetc(int c, FILE *stream);
ungetc stellt c (umgewandelt in unsigned char) in stream zurück, von wo das Zeichen beim nächsten
Lesevorgang wieder geholt wird. Man kann sich nur darauf verlassen, daß pro Datenstrom ein Zeichen
zurückgestellt werden kann. EOF darf nicht zurüchgestellt werden. ungetc liefert das zurückgestellte
Zeichen oder EOF bei einem Fehler.
4.4.
Aufgaben
1. Lese von der Tastatur 10 verschiedene Buchstaben ein.
Lehne dabei doppelt eingegebene Buchstaben mit dem Hinweis
"Buchstabe doppelt" ab.
Gib alle 10 Buchstaben auf dem Bildschirm aus.
2. Schreiben Sie ein Programm, mit folgenden Funktionen :
- berechnet die Länge eines Strings
- kopiert einen String in einen anderen
- liest vom Terminal Sätze ein und bestimmt an Hand der Satzzeichen
(. ! und ? ) die Anzahl der eingelesenen Sätze.
- liest einen String ein und sucht das Vorkommen eines Buchstaben, der über das Terminal eingegeben wurde
- vergleicht ob zwei strings identisch sind
- liest einen String ein und bestimmt ob er ein palindrom ist.
- die Reihenfolge der Buchstaben eines strings umdreht.
3. Schreiben Sie ein Programm das zwei strings einliest und feststellt, ob der zweite String im ersten enthalten
ist.
30
5.
Funktionen
Eine Funktion besteht aus einem Funktionskopf und einem Funktionsrumpf. Im Funktionskopf stehen der
Funktionstyp, Funktionsname und falls gewünscht die Funktionsargumente.
5.1.
Funktionstyp
Eine Funktion kann falls gewünscht ein Ergebnis zurückgeben, wobei der Typ des Funktionsergebnisses dann
den Typ der Funktion definiert. Funktionen die kein Ergebnis zurückgeben sind vom Typ void.
Beispiel :
int test(), char test(), long test(), float test(), struct test(),
enum test(),
pointer test(),
void test()
Das Ergebnis einer Funktion wird mit dem return - Statement zurückgegeben. Der zurückgegebene Wert wird
entsprechend dem Funktionstyp konvertiert.
Beispiel :
return;
return a;
return (a*b)
/* Kein Ergebnis */
/* Übergabe des Werts einer Variable */
/* Übergabe des Werts eines Expressions */
int pow(int x)
{ return (x*x) }
5.2.
Funktionsname
Bei Namen von Funktionen sind die gleichen Regeln gültig wie bei Namen von Variablen.
5.3.
Funktionsargumente
Die Funktionsargumente stehen eingeschlossen in runden Klammern hinter dem Funktionsnamen. Nach dem
ANSI Standard wird jedes Argument mit seinem Typ und dem Argumentnamen aufgeführt. Der Nicht - ANSI
Standard führt nur die Argumentnamen auf, der Typ mit den Argumentnamen wird in den Folgezeilen
aufgeführt. Eine Funktion kann auch ohne Argumente definiert werden.
Beispiel :
ANSI :
äquivalent dazu ist :
Nicht ANSI :
test (int a, char y, char x)
test (a,y,x)
int a;char y;char x;
31
Beispiel :
Funktionen ohne Argument :
ANSI :
test (void)
Nicht ANSI :
test ()
Nach dem ANSI Standard sollte jede Funktion vor ihrem Aufruf mit ihrem Funktionskopf, abgeschlossen von
einem Semikolon, deklariert werden. Die Deklaration nennt man Funktionsprototyp.
Beispiel :
char gross (char a);
äquivalent dazu ist :
char gross (char);
Achtung:
Der Unterschied zwischen Funktionsprototyp und Funktionsdefinition besteht darin, daß der Prototyp mit einem
Semikolon abgeschlossen wird, also ein Statement ist. Die Funktionsdefinition wird nicht von einem Semikolon
abgeschlossen, da hinter der Deklaration sofort der Funktionsrumpf beginnt oder beim Nicht-ANSI Standard die
Argumenttypen aufgeführt werden.
5.4.
Funktionsrumpf
Der Funktionsrumpf ist in geschweiften Klammern eingeschlossen und entspricht somit einem compound
statement. Am Anfang des Blocks stehen die Deklarationen lokaler Variablen. Die Argumente der Funktion
stehen als lokale Variablen zu Verfügung.
5.5.
Die Argumentübergabe : call-by-value <-> call-by-reference
Bei C-Funktionen werden die Argumente immer mit ihrem Wert (=rvalue) übergeben (=call-by-value). Soll der
Wert einer Variablen innerhalb der Funktion geändert werden und diese Änderung außerhalb der Funktion
Bestand haben, muß der geänderte Wert der Variable auch in die Speicheradresse der Variable außerhalb der
Funktion eingetragen werden. Dies kann nur erreicht werden, wenn als Funktionsargument die Adresse der
Variable außerhalb der Funktion übergeben wird (=call-by-reference).
Damit die Funktion weiß, daß es sich bei dem Argument um eine Speicheradresse handelt, muß im
Funktionsheader dem Argumentnamen der * Operator vorgesetzt werden.
Beispiel :
int test (float *x, int *y) ;
Bei jedem Zugriff auf diese Variable muß der Variable dieser * Operator ebenfalls vorgesetzt werden, sonst wird
die Adresse der Variable als ganzzahliger Wert behandelt !
32
Beispiele :
int test (int *double, int *y)
{
*double = 2 * *y;
}
Hauptprogramm:
main()
{
int a,b;
a=10,
b=20;
quadrat(&a,&b);
}
int quadrat(int *x,*y)
{
*y = *x * *x;
}
Hauptprogramm:
Variablen
a
b
Adresse
1000
1002
Wert
10
20
Stack:
Variable
x
y
Adresse
6000
6002
Wert
(int)
(int)
nun der Aufruf: quadrat(&a,&b);
Stack:
Variable
x
y
Adresse
6000
6002
Wert
1000
1002
(Adresse)
(Adresse)
Verlassen von main und weiterarbeiten in der Funktion mit den Variablen
unter den Adressen 1000 und 1002
nach dem Abarbeiten der Funktion quadrat im Hauptprogramm:
Variable
Adresse
Wert
a
1000
10
(int)
b
1002
100
(int)
33
5.5.1. call-by-value
Das Funktionsargument ist eine lokale Variable der Funktion. Sie wird auf dem Stack abgespeichert und
bekommt als Wert den Übergabewert. Dem Argument kann innerhalb der Funktion kein Wert zugewiesen
werden, da sie die Adresse der Variable mit der sie aufgerufen wird nicht kennt.
Die Übergabeart call-by-value wird nur beim Herauslesen von Variablenwerten verwendet.
Beispiel :
call-by-value
int schreiben (int a)
{
printf(" Der Wert lautet %d \n",a)
}
Aufruf :
schreiben(x);
5.5.2. call-by-reference (= by name)
Der Funktion wird als Argument die Adresse der Variable über den Stack übergeben. So kann die Funktion auf
das im aufrufenden Programm gespeicherte Argument zugreifen. Innerhalb der Funktion lebt das Argument als
Adresse. Zur Unterscheidung ob die Funktion mit der Adresse oder dem Wert der Adresse arbeitet muß
innerhalb der Funktion der * Operator verwendet werden. Damit beim Aufruf der Funktion die Adresse der
Variable übergeben wird und nicht der Wert, muß der & Operator vorgestellt werden.
Die Übergabeart call-by-reference wird hauptsächlich beim Einlesen oder Verändern von Variablenwerten
verwendet.
Beispiel :
call-by-name
int rechnen
(int *a,int *b)
{
*b = *a + 100;
}
Aufruf : rechnen(&x,&y);
Vorsicht : Wird auf den * Operator verzichtet, so arbeitet man nicht mit der Variable als Verweis auf einen
Speicherplatz, sondern mit einer Variablen, deren Wert der übergebenen Speicheradresse entspricht.
Beispiel :
Achtung Problem :
int problem
{
a = 100;
(int *a)
/* setzt die Adresse in a auf 100 */
34
5.6.
Beispiele zum Aufruf von Funktionen
5.6.1. Aufruf call-by-name und call-by-reference
#include <stdio.h>
void header(void);
void lesen (float *wert);
void calc_diff(float wert,float *result);
main ()
{
float wert,result;
int I,anz;
header();
printf(" Bitte geben Sie die Anzahl der Durchläufe ein >>>");
scanf("%d",&anz);
for (i=0;i<anz;i++)
{
lesen(&wert);
calc_diff(wert,&result);
printf(" resultat : %f \n",result);
};
}
void header(void)
{
puts("");
puts("****************************************");
puts("Hallo Hier ist das Programm call-by-name");
puts(" Warte was kommt");
puts("****************************************");
puts("");
}
void lesen(float *wert)
{
puts("Bitte geben sie eine Zahl ein (float) ");
scanf("%f",wert);
printf(" Die Eingabe war %f\n",*wert);
}
void calc_diff(float wert,float *result)
{
float dividend;
if (wert == 0)
{
puts(" Achtung Division durch Null ! Der Wert wird
verändert !");
wert = 1.0;
};
}
printf(" Bitte geben Sie den Dividenden ein (float) >>>>");
scanf("%f",&dividend);
*result = dividend / wert;
35
5.6.2. Vertauschen der Werte von zwei Variablen
Da die Inhalte der zwei Variablen ausgetauscht werden sollen, ist call-by-reference notwendig :
Beispiel :
int swap(int *zahl1,int *zahl2)
{
int help;
help = *zahl1;
*zahl1 = *zahl2;
*zahl2 = help;
return;
}
Aufruf : swap(&x,&y);
5.6.3. Übergabe von Arrays an Funktionen
Arrayelmente werden als Argument entweder mit einer festen Dimension oder einer offenen Dimension
übergeben:
Beispiel :
oder
int init_array(int numbers[],int len)
int init_array(int numbers[20],int len)
Bei der Übergabe eines Arrays an eine Funktion muß als Argument die Adresse des ersten Arrayelements
übergeben werden. Defaultmäßig wird bei der Angabe eines Variablenarray als Funktionsargument die Adresse
des ersten Elements übergeben.
Beispiel :
int zahlen[20];
init_array(zahlen,20);
/* defaultmäßig die Adresse des ersten
Elements */
init_array(zahlen[0],20);
/* Falscher Aufruf !!! */
init_array(&zahlen[0],20);
/* Äquivalent zum ersten Aufruf */
init_array(&zahlen[5],20-5); /* Initialisiert ab dem 6. Element */
Initialisierung aller Werte eines Arrays auf Null :
int init_array(int numbers[],int length_of_numbers)
{
int i;
for (i=0;i< length_of_numbers;i++)
numbers[i] = 0;
return;
}
Aufruf :
init_array(x,10);
oder
init_array(&x[0],10); /* falls gilt : int x[10] */
36
5.6.4. Austausch des Inhalts zweier Arrayelemente
Es wird die Funktion swap verwendet. Damit die Werte vertauscht werden können muß die Funktion die
Adressen kennen. Deshalb ist folgender Aufruf notwendig :
(Austausch von x[0] und x[1])
swap(&x[0],&x[1]);
5.6.5. Funktionen als Argumente von Funktionen
In C können Funktionen als Argumente übergeben werden. Als Argument wird ein Pointer auf eine Funktion
definiert und beim Aufruf mit der jeweiligen Funktion versorgt.
Beispiel :
/* Funktionen als Argumente */
#include <stdio.h>
int
int
int
int
add(int x, int y);
sub(int x, int y);
mul(int x, int y);
rechne (int (*operation) (int,int),int x, int y);
main_funktion_arg()
{
int erg;
int x,y;
puts(" Eingabe von zwei ganzen Zahlen ");
scanf("%d %d",&x,&y);
erg = rechne(add,x,y);
printf(" Die Addition von x und y ist : %d\n",erg);
erg = rechne(sub,x,y);
printf(" Die Subtraktion von x und y ist : %d\n",erg);
erg = rechne(mul,x,y);
printf(" Die Multiplikation von x und y ist : %d\n",erg);
}
int add(int x, int y)
{ return (x + y); }
int sub(int x, int y)
{ return (x - y); }
int mul(int x, int y)
{ return (x * y); }
int rechne (int (*operation) (int,int),int x, int y)
{int ergebnis;
return (*operation) (x,y);
}
37
5.7.
Aufgabe
1. Es sollen zwei Funktionen geschrieben werden, die die Buchstaben in einem String, der vom Terminal
eingegeben wurde, in Groß- bzw. Kleinbuchstaben konvertiert. Der String wird als Argument übergeben
2. Es soll eine Funktion str_sort geschrieben werden, das einen String vom Terminal einliest und die Buchstaben
im string alphabetisch sortiert. Der String wird als Argument übergeben.
3. Es sollen zehn ganze Zahlen in ein Array eingelesen werden. In einer Funktion my_sort soll dieses Array
linear geordnet werden und anschliessend die Elemente sortiert ausgegeben werden.
Tip : Zum Sortieren eines Array empfiehlt es sich eine Funktion zu schreiben die den Index des kleinsten
Arrayelements in einem Array, beginnend bei einem Startindex sucht :
int find_index_of_min(int numbers[],
int length_of_numbers_array,int from,int initial_value)
Eingabe :
int numbers[] ...
Array aller gespeicherten Zahlen
int length_of_numbers_array ... Number of Elements in numbers
int from
...
start from Index within array
int initial_value
...
Startvalue, min should be smaller
Ausgabe ;
return value ...
Index des kleinesten Elements innerhalb
des array.
38
6.
Pointer
6.1.
Unterschied zwischen Pointer - und Standardvariablen
Der Unterschied liegt darin, daß Standardvariablen auf eine Speicherzelle zeigen in der der primitive Wert der
Variable gespeichert ist. Eine Pointervariable zeigt auf eine Speicherzelle in der die Adresse einer anderen
Variable gespeichert ist. Pointer können auf primitve oder auch wieder auf Pointervariablen zeigen. Mit der
Technik der Pointer können dann sehr einfach Adressen und Inhalte von Adressen verwaltet werden.
Variable
int x = 10;
int y = -300;
Variablenname Adresse der
Speicherzelle Wert=primitiver Wert
x <=> 1002
y <=> 1004
10
-300
Pointer
int *p;
p = &x;
Pointername
p <=>
Adresse der
Speicherzelle
2002
Wert=Adresse
1002
39
6.2.
Deklaration eines Pointers
Durch Vorstellen eines Sternes vor den Variablennamen wird bei der Deklaration der Variable automatisch eine
Pointervariable deklariert:
Beispiel :
int *p;
float *xp,*yp,*zp;
Pointer sind Variablen, deren Wert eine Adresse ist, sie zeigen somit auf eine beliebige Speicheradresse. Die
Verwendung von Pointern wird dahingegen eingeschränkt, daß jeder Pointer nur auf Variablen bestimmten Typs
zeigen kann. Dieser Typ wird bei der Deklaration eines Pointers in Form des Pointertyps festgelegt.
Bei der Arbeit mit Pointern werden zwei Operatoren wichtig : & (Adreß-Operator), und * (DereferenzierungsOperator). Der Adreß Operator liefert die Adresse einer Variable, der Dereferenzierungs Operator liefert den
Wert der Adresse auf die die Pointervariable zeigt.
Beispiel :
int x;
int *y;
/* Deklaration einer int Variable */
/* Deklaration eines Pointers auf int */
ergibt :
Variable
x
y
x
&x
y
*y
...
...
...
...
Adresse der Speicherzelle Wert
1004
1008
entspricht
entspricht
entspricht
entspricht
dem
der
der
dem
Wert einer Variablen,, bisher nicht definiert
Adresse der Variablen, also 1004
Adresse des Pointers, also 1008
Wert der Variable auf die der Pointer zeigt
40
Die Zuweisung
x
y
= 10;
= &x;
/* Zuweisung eines Wertes*/
/* Zuweisung der Adresse einer Variable auf
die der pointer zeigen soll */
ergibt :
Variable
x
y
Die Zuweisung
*y = *y + 10;
ergibt :
Variable
x
y
Die Zuweisung
y = y + 10;
Adresse der Speicherzelle Wert
1004
10
1008
1004
/* Ändere den Wert der Adresse auf die y
zeigt */
Adresse der Speicherzelle Wert
1004
20
1008
1004
/* Erhöhe die Adresse auf die y zeigt */
ergibt :
Variable
x
y
Adresse der Speicherzelle Wert
1004
20
1008
1014
Die Zuweisung ergibt keinen Sinn
y = *y + 10;
/* Erhöhe die Adresse auf die y zeigt */
(Vorher y = &x )
ergibt :
Variable
x
y
Adresse der Speicherzelle Wert
1004
20
1008
30
41
6.3.
Adreßarithmetik mit Pointern
6.3.1. Addition und Subtraktion einer Konstante
Bei Pointern ist eine eigenschränkte Arithmetik erlaubt. Wird ein Pointer um eine konstante Zahl erhöht, bzw.
erniedrigt, dann wird der Adreßwert des Pointers um die Anzahl von Bytes erhöht, wie die Elemente des
Grundtyps belegen.
Beispiel :
char *pchar;
short *px;
char str[3];
short zahlen[3]
/*
/*
/*
/*
Pointer auf char */
Pointer auf short */
Array of character, 3 Elemente = 3 * 1 Byte*/
Array of short, 3 Elemente = 3 * 2 Bytes*/
str[0] = 'a'; str[1] = 'b'; str[2] = 0;
zahlen[0]=1; zahlen[1]=2; zahlen[2]=3;
pchar = str;
px = zahlen;
/* Der Adreßwert nimmt den Wert der Adresse des
/* ersten Elements von str an */
putchar(*pchar);
/* ergibt die Ausgabe 'a' */
printf("%d",*px);
/* ergibt die Ausgabe von 1 */
Variable
str
zahlen
pchar
px
Adresse der Speicherzelle
1000
1001
1002
1003
1005
1007
1008
1009
Wert
a
b
0
1
2
3
1000
1003
pchar = pchar +1;
/* Der Adresswert wird um 1 byte erhöht,
/*der Pointer zeigt auf das nächste Element
des strings */
putchar(*pchar);
/* ergibt die Ausgabe 'b' */
pchar = pchar -1;
/* Der Adresswert wird um 1 byte (da char)
erniedrigt, der Pointer zeigt auf das vorherige
Element des strings */
putchar(*pchar);
/* ergibt die Ausgabe 'a' */
px = px + 2;
/* Der Adresswert wird um 2*2 Bytes erhöht */
printf("%d",*px);
/* ergibt die Ausgabe von 3 */
42
6.3.2. Autoincrement und Autodecrement
Bei Pointern kann man auch mit den Autoincrement ( ++ ) und Autodecrement ( -- ) Operatoren arbeiten.
Autoincrement einer Pointervariablen bedeutet: zeige auf das nächste Element. Dementsprechend wird die
Adresse des Pointers um die Anzahl der belegten Bytes eines Elements erhöht.
Beispiel :
char *pchar;
/* Pointer auf char, es wird 1 byte belegt */
short *px;
/* Pointer auf short, es werden 2 bytes belegt */
char str[3];
/* Array of character, 3 Elemente */
str[0] = 'a'; str[1] = 'b'; str[2] = 0;
pchar = str;
/* Der Adresswert nimmt den Wert der Adresse des
/* ersten Elements von str an */
pchar++;
/* autoincrement : */
pchar--;
/* autodecrement : */
6.3.3. Anzahl der belegten Bytes der Standardtypen
Diese Angaben sind Implementationsabhängig. Bei den folgenden Angaben geht man von einem 16 bzw. 32 Bit
Prozessor aus :
char *p;
short *p;
int
*x;
long *x;
float *y;
double *z;
/*
/*
/*
/*
/*
/*
belegt
belegt
belegt
belegt
belegt
belegt
1
2
2
4
4
8
Byte */
Bytes */
Bytes */
Bytes */
Bytes */
Bytes */
p++;
x++;
y++;
z++;
/*
/*
/*
/*
erhöht
erhöht
erhöht
erhöht
den
den
den
den
Wert
Wert
Wert
Wert
von
von
von
von
p
x
y
z
um
um
um
um
2
4
4
8
*/
*/
*/
*/
6.3.4. Pointerarithmetik und die Prioritäten von Operatoren
Bei gemeinsamer Verwendung arithmetischer und address Operatoren ist zu beachten, daß die Priorität der
address Operatoren, es handelt sich um die Operatoren & und *, höher ist als die der arithmetischen Operatoren
(+ und -).
Die Priorität der Increment und Decrement Operatoren (-- und ++) ist aber genauso hoch wie die der address
Operatoren. Stehen die Operatoren nebeneinander, so ist die Auswertungsreihenfolge von rechts nach links
definiert. Eine Klammerung ist nötig um diese Reihenfolge zu verändern, um zu definieren, ob man auf den
Adresswert oder auf den Wert auf den die Adresse zeigt verändern will.
Beispiel :
++*p
identisch zu
++(*p)
erhöhe den Wert auf den der
Pointer p zeigt um eins
(* steht rechts von ++)
43
Steht zwischen den Operatoren der Variablenname, so ist die Reihenfolge der Auswertung wie gewöhnlich von
links nach rechts:
*p++
(*p)++
nimm den Wert auf die die Adresse zeigt und erhöhe die Adresse
auf die p zeigt um eins. (++ steht rechts von der Variable)
erhöhe den Wert um eins auf den p zeigt.
p = p + 1
identisch zu p++
erhöhe die Adresse von p um 1
p = p - 2
identisch zu p -= 2
erniedrige die Adresse von p um 2
*p++ = *q++ weise den Wert von q an den Wert von p und erhöhe
anschließend die Adressen von p und q
Arithmetik mit Pointern :
long x,*y;
x = 20;
y = &x;
a) Welchen Wert haben x und y nach der folgenden Zuweisung :
*y = *y + 10;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
b) Welchen Wert haben x und y nach der folgenden Zuweisung :
x = *y + 1;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
c) Welchen Wert haben x und y nach der folgenden Zuweisung :
*y += 1;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
d) Welchen Wert haben x und y nach der folgenden Zuweisung :
++*y;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
e) Welchen Wert haben x und y nach der folgenden Zuweisung :
(*y)++;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
44
f) Welchen Wert haben x und y nach der folgenden Zuweisung :
*y++;
Variable
Adresse der Speicherzelle Wert
x
1004
y
1008
1012
6.4.
Pointer und Arrays
In C sind die Konzepte Pointer und Arrays sehr ähnlich: Jeder indizierte Zugriff auf ein Array kann auch mit
Pointeroperationen erreicht werden. Der Zugriff mit Pointern ist schneller als Arrayindizierung, aber oft nicht
leicht zu durchschauen. Jeder Zugriff auf ein Array wird im Maschinencode als Pointerzugriff realisiert.
6.4.1. Die Deklaration eines Arrays und Pointer
Beispiel :
int a[3],i;
int *pa;
/* Schaffen eines Arrays für 3 Elemente */
/* Schaffen eines Pointers auf int Elemente */
for (i=0;i<3;i++) a[i] = i;
Variable
a
pa
Adresse der Speicherzelle
1000
1002
1004
1006
Wert
0
1
2
6.4.2. Pointer Adresswertzuweisung eines Arrays
Der Variablenname eines Array entspricht der Anfangsadresse des Arrays.
Beispiel :
pa = a
oder
pa = &a[0]
trägt in den Pointer pa die Adresse des ersten Elements von a ein, pa zeigt somit auf das Array a.
Variable
a
pa
Adresse der Speicherzelle
1000
1002
1004
1006
Wert
0
1
2
1000
45
6.4.3. Zugriff auf Arrayelemente über Pointer
Jeder Zugriff auf die Arrayelemente a[i] kann auch als Pointerzugriff *(a+i) realisiert werden.
Beispiel :
Initialisierung eines Array über Pointer :
int
int
int
for
oder
zahlen[5];
i;
*px;
(i=0;i<5;i++)
zahlen[i] = 0;
px = zahlen;
/* Übertragen der Anfangsadresse des Arrays */
for (i=0;i<5;i++)
*(px+i) = 0;
6.4.4. Wertzuweisung an das Array über Arrayindizees und über Pointeradressen
Jeder Wertzuweiung an Arrayelemente kann über Arrayindizees oder Pointer realisiert werden.
Beispiel :
a[2] = -100;
pa = a;
*pa = 50;
*(pa+1) = 10;
trägt in das dritte Element den Wert -100 ein. Der Wert auf den der Adresswert von pa zeigt wird auf 50 gesetzt,
der auf den die Adresse pa+1 zeigt auf 10.
Variable
a
pa
pa+1
pa+2
Adresse der Speicherzelle
1000
1002
1004
1006
1008
10010
Wert
50
10
-100
1000
1002
1004
Zuweisung des Wertes von der Speicheradresse auf die der Pointers zeigt an eine Variable :
x = *pa
x = *(pa+1)
x = *(pa+2)
entspricht x = a[0]
entspricht x = a[1]
entspricht x = a[2]
46
6.4.5. Arraynamen als Funktionsargument
Bei einem Arrayname als Funktionsargument wird automatisch die Adresse des ersten Elements übergeben.
Innerhalb der Funktion wird ein Arrayargument als Adresse auf ein Element gesehen. Somit kann man
Arrayargumente auch als Pointer betrachten.
Beispiel :
char str[10];
char *ptr;
/* strlen, Rückgabe die Länge eines strings s */
int strlen(char *s)
{
int n;
for (n=0; *s != '\0'; s++) n++;
/* Falsch wäre : for (n=0; s != '\0'; s++) n++; */
/* Falsch wäre : for (n=0; s != '\0'; *s++) n++; */
return n;
}
Die Funktion hat als Argument einen Pointer auf char. Die interne Inkrementierung ist somit legal. Wird strlen
aufgerufen, so hat die Inkrementierung von s keinen Einfluß auf das Funktionsargument, da die Funktion strlen
auf einer lokalen Kopie von s arbeitet.
Aufrufe sind zulässig wie :
strlen("hello world");
strlen(str);
strlen(ptr);
/* string Konstante */
/* char array str[10] */
/* char *ptr */
6.4.6. Arrays als Funktionsparameter
Ein Array kann als Funktionsparameter auf zwei Arten als Argument deklariert werden :
char a[] oder char *xa
Die Deklaration als Pointer ist vorzuziehen, da sie die Adresscharakteristik mehr in den Vordergrund stellt.
Innerhalb der Funktion muß der Programmierer analog der Definition mit dem Argument arbeiten.
Beispiel :
int strlen_pointer(char *s)
{
int n;
for (n=0;*s != '\0'; s++) n++;
return n;
}
oder
int strlen_array(char a[])
{
int n;
for (n=0;a[n] != '\0'; n++) ;
return n;
}
Einer Funktion kann auch die Adresse eines Subarrays übergeben werden. Innerhalb der Funktion beginnt dann
das Array auf der Adresse des Subarrays.
47
Beispiel :
char str[] = "guten";
char *pa;
int len;
pa = str;
len = strlen(str);
len = strlen(&str[4]);
len = strlen(pa);
len = strlen(pa+2);
/*
/*
/*
/*
Ausgabe 5 */
Übergabe "n" : Ausgabe 1 */
Ausgabe 5 */
Übergabe "ten" : Ausgabe 3 */
*(pa+1)
*(pa+6)
str[0] str[1]
str[6]
*pa
g
u
1024
1025
t
e
1026 1027
n
\0
1028
1029
1030
Beispiel :
len = strlen(pa+2);
/* Übergabe "ten" : Ausgabe 3 */
int strlen (char *p) erhält als Argument die Adresse des dritten Elements ( Adresse 1026). Also einen Pointer auf
das zweite Element :
p
p+1
p+2
intern entspricht
intern entspricht
intern entspricht
str[2] extern.
str[3] extern.
str[4] extern.
In diesem Zusammenhang kann man auch mit negativen Indizees arbeiten :
p-1
intern entspricht
str[1] extern.
Achtung : Der Kompiler überwacht nicht das Überschreiten von Arraygrenzen !
48
Beispiel :
/* count number of words in a string */
#include <ctype.h>
#include <stdio.h>
char zeile[132] = {0};
int wrd_count(char *s);
main()
{
puts(" Eingabe Zeile ");
gets(zeile);
printf(" Die Anzahl der Wörter ist : %d \n",wrd_count(zeile));
}
6.4.7. Aufgabe
Schreibe eine Funktion, die zwei Stringargumente konkateniert:
function conc(char *s1,char *s2,char *s1s2);
ja
isspace
ja
nein
falls string Ende
ja
fertig
49
nein
char
nein
cnt++
6.4.8. Pointer Arrays und Pointer to Pointer
Da Pointer Variablen sind, können sie wie normale Variablen in Arrays gespeichert werden. Bei einem Beispiel
zeigt sich der Vorteil : Sortieren von strings. Jeder Pointer im Array zeigt auf einen string, soll die Reihenfolge
der strings geändert werden, muß nur die Adresse im richtigen Arrayindex geändert und kein Stringinhalt
umkopiert werden. Das geordnete Array wird der Reihe nach über die Indizes angesprochen.
Beispiel :
char *lines[5];
Index
0
1
2
3
4
Adresse
1024
1030
1036
1042
1048
Wert
2000
2006
2011
2014
2020
Wert
Hallo\0
Leute\0
wie\0
gehts\0
euch\0
Adresse
1024
1030
1036
1042
1048
Wert
2000
2006
2011
2014
2020
Index
0
1
2
3
4
Wert
2000
2006
2020
2014
2011
nach dem alphabetischen Sortieren :
Die Zuweisung zum Umkopieren von Pointern lautet hier z.B. :
lines [4] = lines[2];
char *phelp;
char *lines [5];
phelp
lines [5]
lines [3]
= lines [5];
= lines [3];
= phelp;
50
6.4.9. Schaffen von Speicherplatz bei Array of Pointer
Arbeitet man mit Arrays von pointern, werden zur Deklarationszeit die Arrrayelemente als Pointer definiert. Es
werden lediglich Pointerarrayelemente im Speicher abgelegt.
Damit die einzelnen Pointer auf Elemente zeigen können muß für diese Elemente erst Speicher beschafft werden.
Diese Speicherbeschaffung nennt man dynamische Speicheroperationen. Dazu gibt es in der Standardlibrary
Funktionen.
Beschaffen von Speicher :
#include <stdlib.h>
void *calloc(size_t numberofmembers, size_t numberofbytes);
oder :
void *malloc(size_t numberofbytes);
Ergebnis : Anfangsadresse des Speicherbereichs oder NULL,
falls kein Speicher mehr zur Verfügung ist.
Verändern der Größe von beschafftem Speicherplatz :
void realloc(void *pntr, size_t numberofbytes);
numberofbytes definiert die neue Anzahl der Bytes.
*pntr darf nur auf Speicherplatz zeigen, der mit malloc oder calloc beschafft wurde.
Freimachen von Speicherplatz :
#include <stdlib.h>
free(void *pntr);
Ergebnis : Speicher wird wieder frei für die nächste alloc Funktion.
Achtung : Nur Speicher, der mit alloc, malloc oder realloc beschafft wurde, kann mit free freigegeben
werden.
51
Beispiel :
char *strings[5]
int i;
Index
0
1
2
3
4
Adresse
1024
1026
1028
1030
1032
Nun wird für jeden Pointer auf char ein String der Länge 3 beschafft :
for (i=0;i<5i++)
strings[i] = malloc (3);
Adresse
1024
1026
1028
1030
1032
Adresse
2000
2001
2002
2003
2004
2005
2006
usw.
Wert
2000
2003
2006
2009
2012
Wert
erster Buchstabe, String 1
zweiter Buchstabe, String 1
dritter Buchstabe, String 1
erster Buchstabe, String 2
zweiter Buchstabe, String 2
dritter Buchstabe, String 2
ersterBuchstabe, String 3
usw.
52
6.5.
Mehrdimensionale Arrays
C bietet die Möglichkeit mehrdimensionale Arrays zu definieren, die analog der aus der Mathematik bekannten
Matrixschreibweise entsprechen :
Beispiel :
Ein Array mit 3 Zeilen und 6 Spalten :
static float x[3][6] =
{
{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
{ 10.0, 20.0 30.0, 40.0, 50.0, 60.0}
{ 100.0, 200.0, 300.0, 400.0, 500.0, 600.0}
Zugriffe :
}
x [2][5] = 600.0; /* zuerst Zeile, dann Spalte */
Da Arraynamen und Pointer äquivalent sind, kann man auf Elemente in einem mehrdimensionalen Array auf
verschiedene Art zugreifen:
x [i][j] ist äquivalent zu :
*(x[i] + j)
(* (x + i)) [j]
*((*(x+i)) + j)
*(&(x[0][0] + 6*i + j)
Die Klammern sind notwendig, da die eckigen Klammern [] höhere Wertigkeit haben als der Wert-Operator * .
Werden mehrdimensionale Arrays als formale Parameter von Funktionen übergeben, müssen alle Dimensionen,
außer der ersten angegeben werden. :
int max (float x [][6]) oder
int max (float *x [6]) oder
int max (float x [3][6])
53
6.6.
Argumente an das Hauptprogramm übergeben :
An das Hauptprogramm main() können bei der Programmiersprache C beim Starten des Programms Argumente
übergeben werden :
Beispiel :
Der Programmfile heißt auswertung,
dann kann das Programm gestartet werden mit z.B.:
auswertung 1 2 3 4.
Im Hauptprogramm stehen die Werte der externen Parameter zur Verfügung, wenn die Hauptfunktion main
folgendermaßen definiert wurde :
main(int argc, char *argv[]);
in argc wird die Anzahl der Elemente in der Aufrufzeile gespeichert (Im Beispiel 5), der zweite Parameter ist ein
Array von Pointern, wobei jedes Element des Arrays auf ein Element in der Aufrufzeile zeigt. Die Elemente der
Aufrufzeile werden als string übergeben.
im Beispiel :
argc = 5
argv[0] = auswertung
argv[1] = 1
argv[2] = 2
argv[3] = 3
argv[4] = 4
54
6.7.
Aufgaben
Beispiele zu Pointer :
/* Aus C-Puzzlebuch von A.R. Feuer, Hanser Verlag,1985, S.65)
1. Was gibt das folgende Programm aus :
#define PR(x) printf("%d \n",x);
int a[] = {0,1,2,3,4};
main()
{
int i,*p;
for (i=0;i<=4;i++) PR(a[i]);
for (p = &a[0];p<&a[4];p++) PR(*p);
for (p = &a[0],i=1;i<=5;i++) PR(p[i]);
for (p = a,i=0;p+i<=a+4;p++,i++) PR(*(p+i));
for (p=a+4;p>=a;p--) PR(*p);
for (p=a+4,i=0;i<=4;i++) PR(p[-i]);
for (p=a+4,p>=a;p--) PR(a[p-a]);
}
/* Aus Darnell, C A Software Engeniering approach */
Folgende Deklarationen seien gegeben :
static int a[2][3] = { { -3,14,5},{1,-10,8}};
static int *b[] = { a[0],a[1]};
int *p = b[1];
a) *b[1]; PR(*b[1]);
b) *(++p)
c) *(*a+1)+1
d) *(--p-2)
2 . Welche Ausdrücke sind gleich :
a) *(a[j]+k)
b) **(a[j+k])
c) (*(a+j))[k]
d) (*(a+k))[j]
e) *((*(a+j))+k
f) **(a+j)+k
g) *(&a[0][0] + j + k)
55
6.8.
Beispiele für Arrays of Strings
Im nachfolgenden Beispielen die Arbeit mit Arrays of Strings vorgeführt. Außerdem wird das Lesen von Strings
aus einem File gezeigt.
Zuerst werden alle Zeilen eines Files in ein String Array gelesen und anschließend die Reihenfolge der strings
umgedreht
Bsp:
/* read strings from a file */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define MAX_NUM_OF_LINES 20
#define STRSIZE 80
char *in_buf[MAX_NUM_OF_LINES];
char line[STRSIZE];
main()
{
char filename[STRSIZE];
FILE *in_file;
int num_of_lines=0;
int i;
printf(" Eingabe filein >>>");
gets(filename);
if ((in_file = fopen(filename,"r")) == NULL)
{
printf ("Error opening filename %s \n",filename); exit (1);
}
while (fgets(line,STRSIZE,in_file) != 0 && num_of_lines < MAX_NUM_OF_LINES)
{
printf("line= %s",line);
in_buf[num_of_lines] = (char *) malloc (strlen(line));
line[strlen(line)-1]=0; /* remove \n, like chop operator */
strcpy(in_buf[num_of_lines],line);
num_of_lines++;
}
puts(" Der File wurde eingelesen ...");
for(i=0;i<num_of_lines;i++)
printf("in_buf[%d]=%p -> %p = %s\n",i,&in_buf[i],in_buf[i],in_buf[i]);
reverse_elements(in_buf,num_of_lines);
puts(" Die Reihenfolge der Zeilen wurde umgedreht ...");
for(i=0;i<num_of_lines;i++)
printf("in_buf[%d]=%p -> %p = %s\n",i,&in_buf[i],in_buf[i],in_buf[i]);
puts(" End of Progam ");
}
56
int reverse_elements(char *in_buf[],int num_of_lines)
{
/*
Die Routine dreht die Reihenfolge aller Strings im in_buf um
IN:
char *in_buf[]
int int num_of_lines
... Array of strings
... anzahl der Zeilen
OUT:
char *in_buf[]
... ungedrehtes Array of strings
*/
int index;
for (index=0; index < num_of_lines/2; index ++)
{
/* 1. Fall …. */
printf(" Swap Element %d with %d \n",index,num_of_lines-index-1);
swap_array_elem(in_buf,index,num_of_lines-index-1);
/* Ende 1. Fall */
/* 2. Fall …. */
printf(" Swap Address %p with %p \n",
&in_buf[index],&in_buf[num_of_lines-index-1]);
swap_strings(&in_buf[index],&in_buf[num_of_lines-index-1]);
/* Ende 2. Fall */
}
}
/**********************************************************/
int swap_array_elem(char *array[],int from,int to)
{
char *temp;
temp = array[from];
array[from] = array[to];
array[to] = temp;
}
/**********************************************************/
int swap_strings(char **a,char **b)
{
char *help;
printf(" swap Inhalt von Pointer %p mit %p \n",a,b);
help = *a;
printf(" save %p\n",help);
*a = *b;
*b = help;
return;
}
/**********************************************************/
int swap_void(void **a,void **b)
{
char *help;
help = a;
a = b;
b = help;
}
57
Ausgabe
1. Fall:
swap_array_elem(in_buf,index,num_of_lines-index-1);
line=
line=
line=
line=
line=
1
2
3
4
5
abc
def
ghi
jkl
mno
in_buf[0]=1400002e0
in_buf[1]=1400002e8
in_buf[2]=1400002f0
in_buf[3]=1400002f8
in_buf[4]=140000300
->
->
->
->
->
in_buf Element
0
1
2
3
4
Die Reihenfolge der
in_buf[0]=1400002e0
in_buf[1]=1400002e8
in_buf[2]=1400002f0
in_buf[3]=1400002f8
in_buf[4]=140000300
in_buf Element
0
1
2
3
4
140000940
140000960
140000980
1400009a0
1400009c0
=
=
=
=
=
1
2
3
4
5
abc
def
ghi
jkl
mno
Adresse of Array
1400002e0
1400002e8
1400002f0
1400002f8
140000300
Zeilen wurde
-> 1400009c0
-> 1400009a0
-> 140000980
-> 140000960
-> 140000940
Adresse of Array
1400002e0
1400002e8
1400002f0
1400002f8
140000300
Zeigt auf
140000940
140000960
140000980
1400009a0
1400009c0
Inhalt
1 abc
2 def
3 ghi
4 jkl
5 mno
umgedreht ...
= 5 mno
= 4 jkl
= 3 ghi
= 2 def
= 1 abc
Zeigt auf
1400009c0
1400009a0
140000980
140000960
140000940
58
Inhalt
5 mno
4 jkl
3 ghi
2 def
1 abc
2. Fall:
swap_strings(&in_buf[index],&in_buf[num_of_lines-index-1]
Zweiter Fall:
printf(" Swap Address %p with %p
\n",&in_buf[index],&in_buf[num_of_lines-index-1]);
swap_strings(&in_buf[index],&in_buf[num_of_lines-index-1]);
Swap
Swap
Swap
Swap
Element
Address
Element
Address
0 with 4
1400002e0 with 140000300
1 with 3
1400002e8 with 140000ef8
Die Reihenfolge der
in_buf[0]=1400002e0
in_buf[1]=1400002e8
in_buf[2]=1400002f0
in_buf[3]=1400002f8
in_buf[4]=140000300
in_buf Element
0
1
2
3
4
Zeilen wurde
-> 1400009c0
-> 1400009a0
-> 140000980
-> 140000960
-> 140000940
Adresse of Array
1400002e0
1400002e8
1400002f0
1400002f8
140000300
umgedreht ...
= 5 mno
= 4 jkl
= 3 ghi
= 2 def
= 1 abc
Zeigt auf
1400009c0
1400009a0
140000980
140000960
140000940
59
Inhalt
5 mno
4 jkl
3 ghi
2 def
1 abc
}
6.9.
Beispiel für die Verwendung von Pointer of Pointer
Im nächsten Beipiel wird ein Programm vorgestellt, das die Notwendigkeit der Parameterübergabe von Pointer
auf Pointer zeigt.
Wird das Programm mit der Routine
get_memory_falsch(str)
aufgerufen, stürzt das Programm ab.
Der Aufruf mit :
get_memory_richtig(&str)
funktioniert.
Im weiteren zeigt das Programm, daß auch ein falsches Programm funktionieren kann.
Wird die Deklaration der Integervariable int i auskommentiert, dann funktioniert das Programm in beiden Fällen.
Beispiel
/* Dieses Programm zeigt, wieso beim Beschaffen von Speicher in einer Subroutine
** verwendet werden muß.
Die Routine
get_memory_falsch(str)
Die Routine
get_memory_richtig(&str)
funktioniert.
*/
#include <stdio.h>
main()
{
int i;
/* Bemerkung: Falls die vorherige Zeile auskommentiert wurde, funktioniert das
Programm in beiden Fällen */
char *str;
//
}
get_memory_falsch(str);
get_memory_richtig(&str);
printf(" Read String >>>>");
fgets(str,132,stdin);
printf(" String is = %s\n",str);
get_memory_falsch(char *string)
{
string = (char *) malloc(132);
/* Hier wurde Speicher für die STACK Variable string besorgt */
}
get_memory_richtig(char **string)
{
*string = (char *) malloc(132);
/* Hier wurde Speicher besorgt, der unter der Adresse von String abgespeichert wird
*/
}
60
6.9.1. Beschreibung des Programmablaufs
6.9.1.1.
//
Falscher Aufruf:
get_memory_falsch(str);
Das Programm legt folgendes Adressbuch an:
int i;
char *str;
Typ
Variable
int
char *
i
str
Adresse der
Variable
628
624
Wert
Adresse der
Variable
800
Wert
Adresse der
Variable
800
Wert
0
600
Aufruf der Routine:
get_memory_falsch(str);
Innerhalb der Rouitine wird folgendes Adressbuch angelegt:
get_memory_falsch(char *string)
Typ
Variable
char *
string
Argumentübergabe
get_memory_falsch(str);
liefert folgendes Adressbuch:
Typ
Variable
char *
string
600
Zweisung innerhalb der Routine::
string = (char *) malloc(132);
besorgt Speicher für 132 Bytes, beginnend mit der Adresse 2000 und überschriebt den Wert 600
Typ
Variable
char *
string
Adresse der
Variable
800
Wert
2000
Da der Wert der Adresse 2000 nur unter der Temporären Variable string abgespeichert wurde, hat sich die das
Hauptprogramm diese Adresse nicht gemerkt.
Die Anweisung:
fgets(str,132,stdin);
versucht im Speicher unter der Adresse 600 die eingelesenen Buchstaben abzuspeichern.
61
6.9.1.2.
//
Richtiger Aufruf:
get_memory_richtig(&str);
Das Programm legt folgendes Adressbuch an:
int i;
char *str;
Typ
Variable
int
char *
i
str
Adresse der
Variable
628
624
Wert
Adresse der
Variable
800
Wert
Adresse der
Variable
800
Wert
0
600
Aufruf der Routine:
get_memory_richtig(&str);
Innerhalb der Rouitine wird folgendes Adressbuch angelegt:
get_memory_richtig(char *string)
Typ
Variable
char *
string
Argumentübergabe
get_memory_richtig(&str);
liefert folgendes Adressbuch:
Typ
Variable
char **
string
624
Zweisung innerhalb der Routine::
*string = (char *) malloc(132);
besorgt Speicher für 132 Bytes, beginnend mit der Adresse 2000 und speichert diese Adresse unter *string, also
der Adresse 624 ab:
Zweisung:
*string = (char *) malloc(132);
ergibt Speicher für 132 Bytes, beginnend mit der Adresse 2000
Typ
Variable
char *
string
Adresse der
Variable
800
Wert
Adresse der
Variable
628
624
Wert
624
Im Hautprogramm:
Typ
Variable
int
char *
i
str
Der Wert der Adresse 2000 ist nun im Hauptprogramm unter der Variable str gespeichert.
Die Anweisung:
fgets(str,132,stdin);
speichert problemlos unter der Adresse 2000 die eingelesenen Buchstaben ab.
62
0
2000
6.10. Beispiel zum Blockweisen Einlesen einer Textdatei und Erzeugen
eines Lexikons
/**
Aufgabe:
Programm soll einen Text zeilenweise einlesen
die Zeile in einzelne "Wörter" aufspalten.
Die Wörter sollen für die ganze Datei in
einer Datenstruktur gespeichert werden, so dass
dann sortiert werden könnte oder dergleichen.
Wir werden nach dem Durchlauf durch die Datei
die aufgespaltenen Wörter lediglich auf eine Datei schreiben.
Vorgestellt wird ein Verfahren dynamisch Speicher für
die Pointer auf die Strings und für die Strings selbst
zu allozieren.
1.) für jedes Wort wird mit malloc Speicher bereitgestellt
in seiner Groesse.
2.) für die Stringliste wird ein Doppelpointer verwendet,
an den ein Anfangspuffer von 20 Einträgen mit
malloc zugewiesen wird. Nach dem Abarbeiten einer Zeile
wird der Speicherbereich um die Anzahl der gelesenen
Wörter erweitert.
**/
#include <stdio.h>
#include <string.h>
#define MAXLETTERS 132
#define PUFFERGROESSE 20
void split(char * temp_string);
char **lexikon;
int speicher_groesse;
int lexikon_zeiger;
//globale Variablen
int main(void){
FILE * in_file;
FILE * out_file;
char in_filename[MAXLETTERS];
char out_filename[MAXLETTERS];
char temp_string[MAXLETTERS];
int i;
//Pufferspeicher für Lexikon besorgen,
//Puffer ist bei PUFFERGROESSE 20, 20 Pointer,
//sprich 20 Lexikoneinträge
lexikon = (char**) malloc(sizeof(char*) * PUFFERGROESSE);
speicher_groesse = PUFFERGROESSE * sizeof(char*);
printf("speichergroesse: %d\n",speicher_groesse);
printf("give me the name of the infile>>>");
fgets(in_filename,MAXLETTERS,stdin);
in_filename[strlen(in_filename) -1] = 0;
//chomp
63
//Filehandele auf Filepionter oeffnen
if ((in_file = fopen(in_filename,"r")) == NULL){
printf("File %s konnte nicht geöffnet werden\n",in_filename);
exit(1);
}
printf("give me the name of the outfile>>>");
fgets(out_filename,MAXLETTERS,stdin);
out_filename[strlen(out_filename) -1] = 0;
//chomp
//Filehandele auf Filepionter oeffnen
if ((out_file = fopen(out_filename,"w")) == NULL){
printf("File %s konnte nicht geöffnet werden\n",in_filename);
exit(1);
}
//Infile einlesen:
while( fgets(temp_string,MAXLETTERS,in_file)){
if(temp_string[strlen(temp_string) -1] == 10)
//chomp
temp_string[strlen(temp_string) -1] = 0;
printf("in main before split call\n");
split(temp_string);
//Zeile zerlegen
i =0;
while(i<(speicher_groesse - PUFFERGROESSE * sizeof(char*))/sizeof(char*)){
//nur Kontrollausgabe nach split
printf("%d: %s\n",i,*(lexikon+i));
//ueberlegen warum Schleifenbed.
i++;
// funktioniert
}
}
/********Wortliste auf Datei schreiben
***/
i =0;
while(i < lexikon_zeiger){
fprintf(out_file,"%s\n",*(lexikon+i));
i++;
//formatierte Ausgabe
}
}
void split(char * temp_string){
char *temp_word;
char *zeiger_temp_word;
int inword = 1;
int test;
int i = 0;
int wort_zahl = 0;
temp_word = (char*) malloc(sizeof(char) * MAXLETTERS);
zeiger_temp_word = temp_word;
//zeiger für Pointerarithmetik
printf("in split before while\n");
while(*temp_string){
//waren innerhalb bleiben innerhalb
if(inword ==1 && (!isspace(*temp_string))){
64
*temp_word = *temp_string;
temp_word++;
temp_string++;
}
}
else
//waren auserhalb des Wortes kommen jetzt innerhalb
if(inword == 0 && !isspace(*temp_string)){
inword = 1;
*temp_word++ = *temp_string++;
}
else
//waren ausserhalb bleiben ausserhalb
if(inword == 0 && isspace(*temp_string)){
temp_string++;
}
//waren innerhalb, kommen heraus, schreiben!
else{
printf("in split before writing to lexikon\n");
wort_zahl++;
*temp_word = 0;
temp_word = zeiger_temp_word; //pointer zurücksetzen
*(lexikon+lexikon_zeiger) = (char*) malloc(strlen(temp_word) +1);
strcpy(*(lexikon+lexikon_zeiger),temp_word);
printf("in split after copy string\n");
//lexikon++;
lexikon_zeiger++;
inword = 0;
//sind ausserhalb
temp_string++;
}
//nach while, muessen noch das letzte Wort schreiben
printf("in split before writing last to lexikon\n");
*temp_word = 0;
temp_word = zeiger_temp_word; //pointer zurücksetzen
*(lexikon + lexikon_zeiger) = (char*) malloc(strlen(temp_word) +1);
strcpy(*(lexikon+lexikon_zeiger) ,temp_word);
printf("in split after copy string\n");
free(temp_word);
//tempword brauchen wir nicht mehr malloc Bereich freigeben
wort_zahl++;
lexikon_zeiger++;
//lexikon++;
//pufferspeicher wieder auffüllen
printf("Speichergroesse Pointer to strings vorher: %d\n",
speicher_groesse);
printf("Anzahl eingelesener Woerter, %d\n",wort_zahl);
lexikon = (char **) realloc(lexikon, (speicher_groesse +sizeof(char*) *
wort_zahl));
speicher_groesse = speicher_groesse + wort_zahl * sizeof(char*);
printf("Speichergrosse Pointer to strings jetzt:
%d\n",speicher_groesse);
return ;
}
65
6.11. Aufgaben
Aufgabe (Das Meisterstück!) :
Bemerkung: Verwenden sie zur Lösung die Beispiele 6.8 und 6.9 im Script.
1. Lesen Sie zwei Strings ein und vergleichen Sie, die Strings mit Hilfe
der Systemroutine strcmp() alphabetisch.
Geben Sie aus, welcher String alphabetisch größer oder kleiner ist.
Data:
char string1[132];
char string2[132];
2. Implementieren Sie die Aufgabe 1 erneut, jedoch mit Pointer of Characters. Bei diesem Programm muß der
Speicher für die Strings mit malloc besorgt werden.
Data:
char *string1;
char *string2;
3. Erweitern Sie die Aufgabe 2 um eine Vertauschfunktion: swap_strings
Es sollen der Inhalt beider Strings vertauscht werden.
int swap_strings(char **s1,char **s2)
{
char *help;
printf(" swap Strings s1= %p = %s mit s2= %p = %s \n",s1,s1,s2,s2);
help = *s1;
printf(" save s1: %p = %s\n",help,help);
*s1 = *s2;
*s2 = help;
return;
}
3. Definieren Sie sich ein Array of Pointer to Character mit maximal 5 Einträgen und lesen Sie 5 Strings in dieses
Array vom Terminal ein.
Auch bei diesem Programm muß der Speicher für jedes Array Element mit malloc besorgt werden.
Data:
#define NUM_OF_WORDS 5
char *words[NUM_OF_WORDS];
4. Nachdem Sie in Aufgabe 2 gesehen haben, wie Strings verglichen werden, erweitern Sie die Aufgabe 3 um
eine Sortierfunktion.
Der Sortieralgorithmus soll wie in der Aufgabe "Sortieren von 10 Zahlen" implementiert werden.
Zum Vertauschen von Arrayelementen können Sie die Funktion swap_strings aus der Aufgabe 3 verwenden.
66
7.
Strukturen
Der Strukturmechanismus erlaubt Variablen verschiedenen Typs zu einem eigenen, neudefinierten Typ
zusammen zufassen. Nun kann man Variablen von diesem neuen Typ deklarieren, ihnen Werte zuweisen,
verändern oder herauslesen.
Beispiel :
Gegeben sei der Basistyp punkt, der sich mit den ganzzahligen x und y Koordinaten beschreiben läßt :
struct point
{
int x;
int y;
};
Das Schlüsselwort struct kündigt einen selbstdefinierten Variablentyp an, sein Name, auch tag genannt entspricht
der in geschweiften Klammern aufgeführten Definition. Die Elemente in der Typdefinition heißen member.
Jeder Member kann für sich wieder eine Struktur sein :
struct rect
{
struct point p11;
struct point p12;
} area ;
Eine Struktur, ein Variablenname oder der Name eines Members können gleich lauten, da der Kontext im
Programm genau definiert um welche Art von Variablennamen es sich handelt.
7.1.
Variablendeklaration einer Struktur
Nachdem ein tag definiert wurde, können Variablen von diesem Typen deklariert werden.
1. Möglichkeit :
struct point
{
int x;
int y;
} p1, p2, p3;
2. Möglichkeit :
struct point p1,p2,p3; /* Falls vorher point definiert wurde */
67
7.2.
Wertzuweisung an eine Strukturvariable
Eine Struktur kann bei der Deklaration initialisiert werden :
struct point p1 = { 100, 200 };
Ihr kann ein Wert später zugewiesen werden :
p1 = { 100,200 };
Der Typ einer Funktion kann auch eine Struktur sein :
struct point add_value_to_koordinate(struct point erg)
{
erg.x = erg.x + 1;
erg.y = erg.y + 1;
erg = { x,y };
return erg;
}
7.3.
Zugriff auf einen Member
struktur_variablen_name.member
Der Punkt Operator verbindet eine Variable mit einem member.
Bei einem member der selbst eine Struktur darstellt, wird der Punktoperator sukzessive angewendet:
Beispiel :
p1.x = 300;
area.p11.y = 100;
7.4.
Definition eines Namens (Synonym) für eine neue Struktur
Für jede definierte Struktur kann man mit typedef ein Synonym einführen. Nach dieser Definition kann man
Variablen von diesem Typ unter Angabe des Synonyms deklarieren.
Beispiel :
typedef int length;
typedef char * string;
length a,b,c;
string str;
68
7.5.
Pointer auf Strukturen
Wie bei primitiven Variablen kann man auch Pointer auf Strukturen definieren. Der Adressoperator & liefert die
Anfangsadresse einer Struktur. Um auf den Wert eines members zu zeigen verwendet man den
Dereferenzierungsoperator -> (zwei Zeichen : minus gefolgt von einem größer Zeichen).
Beispiel :
struct point *pp;
pp->member; /* entspricht (*pp).member) */
7.6.
Pointer als Funktionsargument
Werden bei Funktionen Variablen von Strukturen als Argument übergeben, so wird beim Aufruf der Funktion
der gesamte Inhalt der Struktur auf den Stack kopiert. Aus Performance Gründen sollen deshalb die Adressen
der Strukturvariablen übergeben werden.
Beispiel :
struct point
{ int x;
int y;
} *pp;
int init_point(struct point *)
{
pp->x = 0;
pp->y = 0;
return;
}
Aufruf : init_point(pp);
7.7.
Ausdrucken von Strukturen
Es ist nicht möglich Strukturen direkt mit ihrem Variablennamen auszudrucken. Die printf Funktion kann nur
den Wert primitiver Objekte ausdrucken (int, float, char). Deshalb müssen die member der Struktur vom
Programmierer direkt spezifiert, deren Wert er ausdrucken will.
Beispiel :
printf(" x Wert = %d, y Wert = %d \n",p1.x,p1.y);
7.8.
Operationen auf Strukturen
Zulässige Operationen auf Strukturen sind :
a) Kopieren ganzer Strukturen
b) Zuweisung an die gesamte Struktur
c) Bestimmen seiner Anfangsadresse mit dem & Operator
d) Zugreifen auf seine Member.
69
Beispiel :
struct point
{ int x;
int y;
} x1,x2,x3,*px1;
x1.x = x1.y = 100;
x2 = x1;
px1 = &x1;
printf(" x1 : x Wert = %d, y Wert = %d \n",x1.x,x1.y);
printf(" x1 : x Wert ueber Pointer = %d, y Wert = %d \n",px1->x,px1->y);
7.9.
Beispiele für Strukturen :
#include <stdio.h>
#define XMAX 512
#define YMAX 360
struct point
{int x;
int y;};
struct rect
{struct point pt1;
struct point pt2;};
typedef struct point stpoi;
typedef struct rect * pstrec;
typedef struct rect strec;
main()
{
struct point middle,tripple,sum;
struct rect screen,screen1;
pstrec pointer_screen;
/* Prototypen : */
stpoi makepoint(int,int);
stpoi addpoint(struct point, struct point);
pstrec screen_relative(pstrec);
screen.pt1 = makepoint(0,0);
screen.pt2 = makepoint(XMAX,YMAX);
screen1 = screen;
pointer_screen = &screen;
middle = makepoint((screen.pt1.x + screen.pt2.x)/2,
(screen.pt1.y + screen.pt2.y)/2);
tripple = makepoint((screen.pt1.x + screen.pt2.x)/3,
(screen.pt1.y + screen.pt2.y)/3);
sum = addpoint(middle,tripple);
screen_relative(&screen);
}
printf(" Das Ergebnis = \n");
printf(" middle, x = %d, y = %d \n",middle.x,middle.y);
printf(" tripple, x = %d, y = %d \n",tripple.x,tripple.y);
printf(" sum, x = %d, y = %d \n",sum.x,sum.y);
printf(" screen p1(x,y) = (%d %d), p2(x,y) = (%d %d)\n",
screen.pt1.x,screen.pt1.y,screen.pt2.x,screen.pt2.y);
stpoi makepoint(int x,int y)
{
stpoi temp;
70
temp.x = x;
temp.y = y;
return temp;
}
stpoi addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
pstrec screen_relative(pstrec screen)
{
strec help_screen;
int maximumx,maximumy;
float relationx,relationy;
maximumx = screen->pt2.x;
maximumy = screen->pt2.y;
relationx = 512./ maximumx;
relationy = 256./ maximumy;
screen->pt2.x = (int) screen->pt2.x * relationx;
screen->pt2.y = (int) screen->pt2.y * relationy;
71
8.
Dynamische Datenstrukturen
Die elementaren Datenstrukturen legen mit der Deklaration einer Variable den Wertebereich und seine Struktur
im Speicher fest. Solche Datenstrukturen nennt man statisch.
Strukturen, die sich zur Ausführungszeit verändern können, nennt man dynamisch.
Das Schema der Rekursion ermöglicht dynamische Strukturen zu realisieren, indem der Wert einer
Strukturkomponente einem Verweis auf die gleiche Datenstruktur entspricht.
Beispiel Ausdrücke:
Ein Ausdruck bestehe aus einem Term, gefolgt von einem Operanden und einem weiteren Term. Ein Term ist
entweder ein Variable oder ein geklammerter Ausdruck:
z.B.:
x + y, oder (x + y) * z
Die Struktur dazu kann lauten:
struct term
{
struct term *op1;
operator op;
struct term *op2;
}
8.1.
Strukturart: Verkettete Liste
Jedes Element zeigt auf seinen Nachbarn. Somit besteht jedes Listenelement aus seinem Listenwert und einer
Adresse, die auf das nächste Element zeigt. Dazu gibt es Operationen, die ein neues Listenelement schaffen,
bzw. löschen.
Vorteile:
- Die Dimension der Liste muß vorher nicht festgelegt werden
- Umorganisation der Liste ist einfacher
Nachteile:
- Direkter Zugriff auf einzelne Elemente ist schwerer, da die verkettete Liste nur von Element
zu Element durchlaufen werden kann.
Bsp Konstruktion:
struct knoten
{
int inhalt;
struct knoten *nachfolger;
};
struct knoten *kopf,*z,*x;
Selektion:
kopf.inhalt = 100;
kopf->next = z;
72
8.2.
Beispiel für Listen
/*
D(yn)amische Datenstrukturen
Max
*/
#include <stdio.h>
struct knoten
{
int inhalt;
struct knoten *nachfolger;
};
int fill_queue(struct knoten *position);
int print_queue(struct knoten *position);
struct knoten * init_queue();
struct knoten *fuege_neues_element_an(int eingabe,struct knoten *curr_pos);
int print_queue(struct knoten *curr_pos);
int print_queue_rek(struct knoten *curr_pos,int number);
int free_queue_rek(struct knoten *curr_pos);
main()
{
struct knoten *kopf;
puts(" Hallo, hier ist das Programm struc_rek.c ");
kopf = init_queue();
fill_queue(kopf);
print_queue(kopf);
printf(" Ausgabe rekursiv ... \n");
print_queue_rek(kopf,1);
free_queue_rek(kopf);
}
struct knoten * init_queue()
{
struct knoten *curr;
curr = (struct knoten *) malloc(sizeof *curr);
curr->inhalt = 1;
curr->nachfolger = NULL;
return curr;
}
int fill_queue(struct knoten *position)
{
struct knoten *curr_pos;
int eingabe;
curr_pos = position;
while (printf("Enter zahl >>>") && scanf("%d",&eingabe) != EOF)
{
curr_pos = fuege_neues_element_an(eingabe,curr_pos);
}
}
73
struct knoten *fuege_neues_element_an(int eingabe,struct knoten *curr_pos)
{
struct knoten *neu;
printf( " Fuege %d an \n",eingabe);
// get new memory for a new node
neu = (struct knoten *) malloc (sizeof *neu);
// initialize node
neu->inhalt = eingabe;
neu->nachfolger = NULL;
// connect current_position with new node
curr_pos->nachfolger = neu;
// increment current_position to created node
curr_pos = neu;
return curr_pos;
}
int print_queue(struct knoten *curr_pos)
{
printf(" Drucke queue ... \n");
while (curr_pos->nachfolger != NULL)
{
printf(" Element %d \n",curr_pos->inhalt);
curr_pos = curr_pos->nachfolger;
}
printf(" letztes Element %d \n",curr_pos->inhalt);
return;
}
int print_queue_rek(struct knoten *curr_pos,int number)
{
printf(" Elementnummer %d = %d \n",number,curr_pos->inhalt);
if (curr_pos->nachfolger != NULL)
print_queue_rek(curr_pos->nachfolger,number+1);
}
int free_queue_rek(struct knoten *curr_pos)
{
struct knoten *pos ;
printf(" Delete %d \n",curr_pos->inhalt);
if (curr_pos->nachfolger != NULL)
{
pos = curr_pos->nachfolger;
free (curr_pos);
free_queue_rek(pos);
}
}
8.3.
Strukturart: Stapel
Die wichtigste Datenstruktur mit beschränktem Zugriff ist der Stapel. Es gibt nur zwei Grundoperationen beim
Stapel:
a) ein Element auf den Stapel legen, das Element wird oberstes Element
(Operation push)
b) das obenliegende Element vom Stapel nehmen
(Operation pop)
Bsp: Arithmetische Ausdrücke:
(3+4)*3
push(3)
push(4)
push (pop() + pop())
push(3)
push (pop() * pop())
Vorteil:
74
- sehr effizient und auf der Hardwareebene einfach zu programmieren
- kann sich Zwischenergebnisse leicht merken
- für eine kleine Anzahl von Operatoren braucht man nur einen kleinen Stapel.
Der Stapel wird am einfachsten mit Hilfe von verketten Listen implementiert.
8.4.
Strukturart: Schlange
Wie der Stapel ist die Schlange eine Datenstruktur mit beschränktem Zugriff. Sie erlaubt nur die zwei
Operationen:
Element am Anfang in die Schlange einfügen
Element vom Ende wieder entfernen.
Die Strukturart Schlange wird über verkettete Listen implementiert.
8.5.
Strukturart: Bäume
Bisher hatten die Elemente einer rekursiven Datenstruktur jeweils nur maximal eine Nachfolgerkomponente.
Bei der Strukturart Baum kann jedes Element auf mehrere andere Elemente, nicht jedoch auf sich selbst zeigen.
(siehe das Beispiel bei den rekursiven Datenstrukturen)
Ein Baum besteht aus einer Menge von Knoten und Kanten. In den Knoten des Baumes liegt die Information der
jeweiligen Instanz. Die Kanten stellen die Verbindung zwischen zwei Knoten dar. Ein Pfad ist eine Liste von
unterschiedlichen Knoten, die über Pfade verbunden sind.
Der Knoten des Baumes, von dem alle Kanten ausgehen, heißt Wurzel. Knoten ohne Nachfolger werden Blätter
genannt.
Es liegt dann die Struktur eines Baumes vor, wenn es
- zwischen der Wurzel und den Knoten genau einen Pfad gibt.
Daraus folgt:
- jeder Knoten hat genau einen Vorgänger.
- kein Knoten zeigt auf sich selbst
Die Anzahl der Kanten eines Baums definieren die Art des Baumes:
- bei zwei Kanten spricht man von einem binären Baum,
- bei n Kanten spricht man von einem n-ären Baum.
75
Bsp. Konstruktion:
struct node
{
char info;
struct node *left,*right;
};
struct node *x,*z;
8.6.
Beispiel Bäume
#include <stdio.h>
#define NUM_OF_STUDENTS 20
struct type_info_student
{
char name[20];
char adress[100];
int
age;
int
id;
};
struct type_info_student class[NUM_OF_STUDENTS];
void fill_struct(struct type_info_student *student);
void print_struct(struct type_info_student student);
void init_class(struct type_info_student class[]);
void fill_class(struct type_info_student class[]);
void print_class(struct type_info_student class[]);
void save_class(char *filename);
void load_class(char *filename);
int check_student(char *name,char *adress,int age);
int dichot_rek(struct type_info_student class[],int laenge,char *name);
int dichot_lin(int left,int right,char *name);
int acces_ID(int id,struct type_info_student *student);
int print_id(void);
main()
{
/* main_ex1(); */
main_ex2();
}
/* Begin
example 1 ***************/
main_ex1()
{
struct type_info_student student;
puts(" Here is structure, exercise 1 ");
fill_struct(&student);
print_struct(student);
}
void fill_struct(struct type_info_student *student)
{
puts(" Fill structure student ");
printf(" Enter name of the student >>>");
fflush(stdin);
fgets(student->name,20,stdin);
student->name[strlen(student->name) -1] = 0; /* Delete newline at the end */
printf(" Enter adress of the student >>>");
fflush(stdin);
fgets(student->adress,100,stdin);
student->adress[strlen(student->adress) -1] = 0; /* Delete newline at the end */
printf(" Enter age of the student >>>");
fflush(stdin);
scanf("%d",&student->age);
/* printf(" Enter ID of the student >>>");
scanf("%d",&student->id); */
}
void print_struct(struct type_info_student student)
{
printf(" Name of the student is : %s\n",student.name);
76
}
printf(" Adress of the student is : %s\n",student.adress);
printf(" Age of the student is : %d\n",student.age);
printf(" ID of the student is : %d\n",student.id);
/*
End
/*
Begin
example 1 ***************/
example 2 ***************/
main_ex2()
{
char filename[30];
char ant;
}
puts(" Examples structures Number 2 ");
init_class(class);
fill_class(class);
print_class(class);
ask_for_check();
print_id();
puts(" Enter Filename for output ");
scanf("%s",filename);
save_class(filename);
void init_class(struct type_info_student class[])
{
int i;
for (i=0; i< NUM_OF_STUDENTS; i++)
class[i].id = 0;
}
void fill_class(struct type_info_student class[])
{
struct type_info_student student;
int i=0,index=0;
int field_id = 0;
char ant;
char filename[20];
puts(" Do you want to read the Input from a file (y/n) ");
scanf("%c",&ant);
if (ant == 'y' || ant == 'Y')
{
puts(" Enter Filename for input ");
scanf("%s",filename);
load_class(filename);
return;
};
/* read from the terminal */
student.name[0] = 0;
fill_struct(&student);
while ((student.name[0]) && (field_id < NUM_OF_STUDENTS))
{
while ((class[index].id) &&
(strcmp(class[index].name,student.name)<0) )
index++;
printf(" For the new Student I found the index %d \n",index);
student.id = index + 1;
/* start to shift every class entry */
for (i=field_id;i > index; i--)
{
class[i+1] = class[i];
class[i+1].id ++;
}
field_id ++;
class[index] = student;
}
index = 0;
student.name[0] = 0;
fill_struct(&student);
77
}
void print_class(struct type_info_student class[])
{
}
int i=0;
while ((class[i].id) && (i<NUM_OF_STUDENTS))
{
printf(" \n\n ----> Student number = %d <-----\n",i);
print_struct(class[i]);
i++;
}
void save_class(char *filename)
{
FILE *out;
int i;
out = fopen(filename,"w");
i=0;
while ((class[i].id) && (i<NUM_OF_STUDENTS))
{
fprintf(out,"%s\n",class[i].name);
fprintf(out,"%s\n",class[i].adress);
fprintf(out,"%d\n",class[i].age);
fprintf(out,"%d\n",class[i].id);
i++;
};
fclose(out);
}
void load_class(char *filename)
{
FILE *in;
int i;
in = fopen(filename,"r");
i=0;
while (! feof(in) && (i<NUM_OF_STUDENTS))
{
fgets(class[i].name,20,in);
class[i].name[strlen(class[i].name) -1] = 0; /* Delete newline at the end */
fgets(class[i].adress,100,in);
class[i].adress[strlen(class[i].adress) -1] = 0; /* Delete newline at the end */
fscanf(in,"%d\n",&class[i].age);
fscanf(in,"%d\n",&class[i].id);
i++;
};
fclose(in);
}
int check_student(char *name,char *adress,int age)
/* Check if a student is present in the class */
{
int i=0;
int laenge;
while (class[i].id && (i<NUM_OF_STUDENTS) &&
strcmp(class[i].name,name) &&
strcmp(class[i].adress,adress) &&
class[i].age != age)
i++;
if (class[i].id && (i<NUM_OF_STUDENTS))
printf("Found id=%d\n",class[i].id);
else
printf("Not Found \n");
puts(" Now look with rekursion ");
/* decide numbers od allready stored students */
for (laenge=0;class[laenge].id && (laenge < NUM_OF_STUDENTS);laenge++);
if (i=dichot_rek(class,laenge,name))
printf("Found id=%d\n",i);
else
printf("Not Found \n");
78
puts(" Now look iterative ");
if (i=dichot_lin(0,laenge,name))
printf("Found id=%d\n",i);
else
printf("Not Found \n");
}
ask_for_check()
{
struct type_info_student student;
char ant;
int id;
puts(" Do you want to check a new student (y/n) ");
fflush(stdin);
scanf("%c",&ant);
while (ant == 'y' || ant == 'Y')
{
fill_struct(&student);
if (id=check_student(student.name,student.adress,student.age))
{
printf(" Student found\n");
}
else
{
printf(" Student not found \n");
};
puts(" Do you want to check a new student (y/n) ");
fflush(stdin);
scanf("%c",&ant);
};
}
int dichot_rek(struct type_info_student class[],int laenge,char *name)
{
int mitte;
mitte = laenge/2;
if (!strcmp(class[mitte].name,name))
return class[mitte].id;
else
if (laenge == 1)
return 0;
}
if (strcmp(name,class[mitte].name) < 0)
return dichot_rek(class,mitte,name);
if (strcmp(name,class[mitte].name) > 0)
return dichot_rek(&class[mitte],laenge-mitte,name);
int dichot_lin(int left,int right,char *name)
{
int mitte;
mitte = (left + right)/2;
while ((left <= right ) && (strcmp(name,class[mitte].name)) )
{
if (strcmp(name,class[mitte].name) < 0)
right = mitte - 1;
if (strcmp(name,class[mitte].name) > 0)
left = mitte + 1;
mitte = (left + right)/2 ;
};
if (! strcmp(name,class[mitte].name) )
return class[mitte].id;
else
return 0;
}
int acces_ID(int id,struct type_info_student *student)
{
int i;
for (i=0;class[i].id && class[i].id != id && (i < NUM_OF_STUDENTS) ;i++);
if (class[i].id == id)
79
}
{
*student = class[i];
return 1;
}
else
return 0;
int print_id()
{
int i;
struct type_info_student student;
for (i=1;i < NUM_OF_STUDENTS;i++)
{
if (acces_ID(i,&student))
print_struct(student);
};
}
80
9.
Standard Ein/Ausgabe
Im Sprachumfang von C sind keine Befehle für die E/A enthalten. In der standardisierten Run-Time Library
werden jedoch eine große Zahl von E/A Funktionen zur Verfügung gestellt, unter denen der Programmierer die
für sein E/A Problem geeignetste auswählen kann.
Das der Run-Time Library zugrundeliegende Konzept entstammt der UNIX-Umgebung. Dort wird eine Datei als
geordnete Folge oder "Strom" stream von Zeichen (Bytes) betrachtet. Die einzelnen Datensätze sind durch ein
Zeilentrennzeichen ('\n') getrennt. Eine Unterscheidung zwischen Text- und Binärdateien existiert nicht. Auch
ein physikalisches Gerät kann ein stream sein.
9.1.
Überblick über die Standard Ein/Ausgabe
9.1.1. Die Datenstruktur stream
Das der Run-Time Library zugrundeliegende Konzept entstammt der UNIX-Umgebung. Dort wird eine Datei als
geordnete Folge oder "Strom" stream von zwei Bytes betrachtet. Die einzelnen Datensätze sind durch ein
Zeilentrennzeichen ('\n') getrennt. Eine Unterscheidung zwischen Text- und Binärdateien existiert nicht. Auch
ein physikalisches Gerät kann ein stream sein.
9.1.2. Die Datenstruktur File
Jede Datei wird über seinen Dateinamen eindeutig identifiziert. Innerhalb eines Programms kann eine Datei nur
als stream bearbeitet werden. Die Zuordnung eines stream zu einer Datei erfolgt beim Öffnen der Datei.
Die in stdio.h definierte Datenstruktur FILE entspricht innerhalb eines C - Programms der Datenstruktur des
stream. In der Datenstruktur FILE werden alle Informationen abgespeichert, die für den Zugriff auf die Datei
notwendig sind
Positionszeiger (File position indicator),
Zeiger auf den mit der Datei verknüpften Puffer,
Platz für das Registrieren des Fehlerstatus.
Beispiel :
FILE
eingabe,ausgabe; /* Es werden 3 Filepointer auf streams definiert */
9.1.3. Vordefinierte Dateien
Drei bestimme Typen von Ein/Ausgabestreams sind bei jedem C-Programm geöffnet und in stdio.h vordefiniert.
Die Ein/Ausgabestreams sind vom Typ FILE und ihre Variablennamen lauten :
stdin,
stdout,
stderr.
Sie sind die Standardkanäle für Eingabe, Ausgabe und Fehlermeldungen. Defaultmäßig zeigen alle drei Kanäle
auf das Terminal.
81
9.1.4. Möglichkeiten der Organisation und des Zugriffs
In Standard C werden streams sequentiell organisiert und als lückenloser stream von Zeichen (Bytes)
sequentiell zeichenweise (byteweise) bearbeitet. Darüber hinausgehend stellt die Run-Time Library Funktionen
zur Verfügung, die es erlauben, auf eine bestimmte Stelle des stream zu positionieren
Der Zugriff auf einen stream kann ungepuffert (unbuffered) oder gepuffert ablaufen. Bei der Pufferung wird
noch einmal unterschieden zwischen zeilenweiser Pufferung (linebuffered) und voller Pufferung (fully buffered).
Bei ungepufferter E/A wird jedes Zeichen (Byte) getrennt verarbeitet, bei gepufferter E/A dagegen werden die
Zeichen (Bytes) in einem Puffer so lange angesammelt, bis dieser gefüllt ist. Bei zeilenweiser Pufferung wird das
Zwischenspeichern abgebrochen, bevor der Puffer vollständig gefüllt ist, oder wenn ein Zeilenende-Zeichen
auftritt. Auf die Pufferung kann der Programmierer evtl. durch die Wahl der E/A- Funktionen Einfluß nehmen,
innerhalb des Standards mit Hilfe der Funktion setvbuf.
Das Ausmaß der möglichen Beeinflussung hängt vom Betriebssystem ab. Der Standard schlägt zwar diese drei
Mechanismen vor, zwingt eine konkrete Implementierung aber lediglich, entsprechende Anweisungen zu
akzeptieren, nicht jedoch, sie auch tatsächlich zu realisieren
9.1.5. Terminal-E/A
Da stdin und stdout Pointennamen sind die auf streams zeigen, kann man auf sie prinzipiell mit allen Funktionen
zur Streambearbeitung zugreifen, es sei denn, physikalische Merkmale verbieten dies (so ist etwa der wahlfreie
Zugriff mit Hilfe von fseek oder lseek bei einem Terminal nicht möglich).
Folgende Funktionen gibt es für die Terminal-E/A:
Zeichen-E/A:
String-E/A
Formatierte E/A
getc,getchar, putc,putchar
gets,puts
scanf,printf
Häufig sind sie in stdio.h als Macros implementiert, mit denen sie auf andere E/A Funktionen für
Dateiverarbeitung zurückgeführt werden, z.B.:
#define getchar()
#define putchar(x)
fgetc(stdin)
fputc(x,stdout)
82
9.1.6. Interne E/A
Bei der internen E/A geht es nicht um Ein- oder Ausgabe von Daten, sondern um das formatierte "Herauslesen"
von Daten aus einem String oder das formatierte "Hineinschreiben" beliebiger Daten in einen String. Dazu bietet
die Standardlibray zwei E/A Funktionen, deren Namen sich von denen der Standard E/A Routinen nur durch den
Anfangsbuchstaben 's' unterscheiden.
int
int
sscanf (const char *str, const char *format_spec, ...)
sprintf (char *str, const char *format_spec, ...)
Die interne E/A wird hauptsächlich verwendet, um Strings in numerische Datentypen zu konvertieren und
umgekehrt, außerdem, um einzelne Teile aus größeren Datensätzen herauszulösen. Für solche
Typumwandlungen gibt es jedoch neben den 's' Funktionen meist spezielle Konvertierroutinen.
Bsp.:
1
2
3
4
5
6
7
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*************************************************************
* Das Programm liest beliebig viele reelle Zahlen ein und errechnet
* das arithmetische Mittel. Als Abbruchkriterium dient die Eingabe
* von "ENDE".
**************************************************************/
#include
#define INPUT
main ()
{
double
char
int
printf ("Betrag: "); \
gets
(string)
zahl, summe = 0;
string[20];
count = 0;
INPUT;
while ( strcmp(string,"ENDE") != 0 )
{
sscanf (string,"%f",&zahl);
summe += zahl;
/* oder für Zeile 161 und 162 :
summe += atof (string) */
count++;
INPUT;
}
printf ("Mittelwert: %5.2f\n",summe/count);
}
83
9.1.7. Standard-E/A
Die Standard E/A umfaßt Funktionen, die die Daten im allgemeinen gepuffert bearbeiten. Die Art und Größe der
Puffer kann mit speziellen Routinen beeinflußt werden. Für spezielle Anwendungen kann die Pufferung sogar
ausgeschalten werden.
9.2.
Allgemeine Dateioperationen
9.2.1. Öffnen und Schließen von Dateien
Innerhalb eines Programms werden bei der Standard E/A alle Dateien über sogenannte Filepointer
angesprochen.
Die Funktion fopen ordnet einer externen Datei einen internen Filepointer zu :
FILE *fopen (const char *file_spec, const char *a_mode)
Die Funktion fopen ordnet einer externen Datei einen internen Filepointer zu :
Die möglichen Argumente für die Zugriffsweise (a_mode) ergeben sich folgendermaßen:
1.
Grundsätzlich gibt es die drei Möglichkeiten "r" (read), "w" (write) und "a" (append). Öffnet man eine
Datei mit einem dieser Argumente, so ist ausschließlich der angegebene Zugriffsmodus möglich.
2.
Will man zugleich lesend und schreibend auf eine Datei zugreifen, muß man dies durch Anfügen eines
Pluszeichens bekanntmachen: "r+", "w+", "a+". Die Unterschiede zwischen diesen Argumenten zeigt
die folgende Tabelle.
3.
Die bisher aufgeführten Argumente sind beim Zugriff auf text streams zu verwenden. Beim Zugriff auf
binary streams wird jeweils noch ein "b" angehängt. So entstehen "rb", "wb", "ab", "r+b", "w+b", "a+b"
(oder "rb+", "wb+", "ab+").
Die Unterschiede zwischen diesen Zugriffsarten zeigt folgende Tabelle :
Datei muß existieren
Alter Inhalt verloren
Lesen möglich
Schreiben möglich
Anfügen möglich
r
ja
w
a
r+
ja
ja
ja
ja
ja
ja
ja
ja
w+
ja
ja
ja
a+
ja
ja
ja
Die bisher aufgeführten Argumente sind beim Zugriff auf text streams zu verwenden. Beim Zugriff auf binary
streams wird jeweils noch ein "b" angehängt.
So entstehen :
"rb", "wb", "ab", "r+b", "w+b", "a+b" (oder "rb+", "wb+", "ab+").
84
9.2.2. Schließen von Dateien
Geöffnete Dateien werden geschlossen durch die Funktion
int fclose (FILE *file_ptr)
wobei file_ptr der file pointer ist, den fopen geliefert hat. Da fopen den Nullpointer als Ergebnis liefert, wenn ein
Fehler aufgetreten ist, sehen die Anweisungen typischerweise so aus:
Beispiel :
FILE
*fp;
if ( (fp = fopen("datei_x.txt","r")) == NULL )
{
printf ("Fehler beim Öffnen von DATEI_X.TXT\n");
exit (1);
}
fclose (fp);
9.2.3. Kombination von Öffnen und Schließen von Dateien
Eine Kombination aus fopen und fclose stellt die Funktion freopen dar :
FILE *freopen (const char *file_spec, const char *a_mode,
FILE *file_ptr)
dar. Sie verknüpft den file pointer mit der Datei file_spec und schließt die Datei, die evtl. vorher mit dem file
pointer verknüpft war (z.B. zum Umlenken eines E/A-Kanals benutzt).
9.2.4. Anlegen von temporären Dateien
Temporäre Dateien sind Arbeitsdateien, die zum Speichern von Zwischenergebnissen verwendet werden und
nach dem Programmende wieder gelöscht werden. Die Funktion tmpfile kreiert eine temporäre Datei, die im
binary update Mode geöffnet wird, da man davon ausgeht, daß auf die Datei zuerst geschrieben und dann von ihr
gelesen wird.
Einen Pointer auf einen solchen temorären FILE stream erhält man mit der Funktion tmpfile :
FILE *tmpfile (void)
Reicht dem Benutzer der binary update Mode einer temporären Datei nicht aus, kann er sich mit der Funktion
tmpnam einen Filenamen generieren lassen, der mit keinem existierenden Filenamen übereinstimmt und eine
Datei unter diesem Namen mit der Funktion fopen im gewünschten Modus öffnen. Damit die Datei jedoch nach
dem Programmende nicht permanent gespeichert bleibt, muß der Programmierer die Datei explizit löschen. Dazu
hat er die Funktion remove.
char *tmpnam (char *str)
Die Funktion tmpnam generiert einen Filenamen, der in str abgelegt wird. Der String str muß mindestens die in
stdio.h definierte Länge L_tmpnam haben.
85
Beispiel :
FILE
char
*my_tmp_file,*fp;
my_file_name[132];
my_tmp_file = tmpfile(); /* oeffnet temporaere Datei */
tmpnam(my_tmp_file_name); /* generiert neuen Filenamen */
fp = fopen(my_tmp_file_name,"r")) == NULL /* oeffnet Datei */
fclose (fp);
9.2.5. Dateiverwaltung
Mit Hilfe der Funktion
int remove (const char *file_spec)
kann eine Datei vom Programm aus gelöscht werden. Wie sich die Funktion verhält, wenn die angegebene Datei
geöffnet ist, legt der Standard nicht fest.
Zum Umbenennen einer Datei dient die Funktion rename.
int rename (const char *old_file_spec, const char
*new_file_spec)
Zu beachten ist, daß beide Funktionen nicht mit dem file pointer arbeiten, sondern mit dem Dateinamen.
Das nachfolgende Beispiel zeigt wie man mit tmpnam und remove arbeiten kann.
Beispiel :
#include <stdio.h>
char filnam[L_tmpnam];
FILE *file;
tmpnam(filnam);
file=fopen(filnam,"w+");
fclose(file);
remove(filnam);
86
9.3.
Text-E/A
Der Standard beschreibt Funktionen, mit denen Textdateien entweder zeichenweise oder in String-Einheiten
bearbeitet werden können. Bedingt sind für diesen Zweck auch noch andere Funktionen geeignet
9.3.1. Zeichenorientierte E/A
Für die zeichenweise Bearbeitung von Textdateien stehen zwei Funktionen zur Verfügung:
int fgetc (FILE *file_ptr)
int fputc (int c, FILE *file_ptr)
Als Alternative für die beiden Funktionen gibt es zwei Macros, die im allgemeinen schneller als die Funktionen
arbeiten :
int getc (FILE *file_ptr)
int putc (int c, FILE *file_ptr)
Zu beachten ist, daß das bearbeitete Zeichen vom Typ int ist. Dies ist notwendig, weil fgetc beim Erreichen des
Dateiendes den als char nicht darstellbaren Wert EOF (definiert in stdio.h) liefert. Auch fputc liefert als Ergebnis
den Wert EOF, wenn ein Fehler aufgetreten ist.
9.3.2. Stringorientierte E/A auf Datei
Ganze Strings können mit den Funktionen
char
int
*fgets (char *str, int maxchar, FILE *file_ptr)
fputs (const char *str, FILE *file_ptr)
eingelesen und ausgegeben werden. Die Funktion fgets versucht alle Zeichen bis zum nächsten ZeilenendeZeichen in die Stringvariable str zu übertragen, bricht jedoch ab, falls sie dieses nach maxchar-1 übertragenen
Zeichen noch nicht erreicht hat. An das eingelesene Zeichenkette hängt fgets automatisch den
stringterminierenden Nullcharacter an und wandelt diese Zeichenkette somit in einen string.
Da der Ergebnistyp ein Zeiger auf char ist, kann fgets im Gegensatz zu fgetc beim Erreichen des Dateiendes
nicht EOF liefern. In diesem Fall gibt fgets vielmehr den Nullpointer zurück,
87
9.3.3. Stringorientierte E/A auf das Terminal
Ganze Strings können mit den Funktionen
char
int
*gets (char *str)
puts (const char *str)
vom Terminal eingelesen und ausgegeben werden. Die Funktion gets liest vom Terminal bis zu einem EOF
Buchstaben oder zum nächsten Zeilenende-Zeichen in die Stringvariable str. Das Zeilenende-Zeichen wird nicht
in den String übernommen, jedoch der stringterminierende Nullcharacter an den String angehängt. Der Vorteil
bei der Benutzung der Funktion gets liegt daran, daß die Funktion nicht mehr Buchstaben vom Terminal liest, als
der String inklusiv dem Terminierungsbuchstaben speichern kann.
Achtung :
Die Funktion puts schreibt den String str auf das Terminal und hängt automatisch einen newline Buchstaben an.
9.3.4. Löschen von Zwischenbuffern bei der Eingabe
Beim Lesen von Strings kommt es öfters vor,daß Zeichen noch auf dem Zwischenbuffer gespeichert sind, da sie
in keine Variablen übertragen wurden. Damit diese Zeichen vor den erneuten Lesen gelöscht werden gibt es die
Funktion fflush :
int fflush(FILE *stream)
Sie löscht eventuell gepufferte oder noch nicht geschriebene Daten werden vom Eingabestrom.
9.3.5. Formatierte String - E/A
Von formatierter String - E/A spricht man, wenn ein String eingelesen, aber nicht als solcher abgelegt, sondern
vorher in eine andere Darstellungsform (z.B. binäre int-Darstellung) umgewandelt wird. Entsprechend werden
bei der formatierten Ausgabe binär abgespeicherte Daten nicht als solche ausgegeben, sondern zuvor in Strings
umgewandelt. Die C Run-Time Library stellt dafür zwei Funktionen bereit:
int
int
fscanf (FILE *file_ptr, const char *format_spec, ...)
fprintf (FILE *file_ptr, const char *format_spec, ...)
Die Funktion fscanf erwartet, wenn mehrere Elemente bei einem Aufruf einzulesen sind, Trennzeichen zwischen
diesen. Was Trennzeichen sein können, legt das Macro isspace fest :
int
isspace (char character)
Da das Leerzeichen ebenfalls ein Trennzeichen ist, sollte man Strings nicht mit fscanf einlesen, wenn man nicht
sicher ist, daß diese keine Leerzeichen enthalten, bzw. man ihre Länge nicht vorhersagen kann. Für Strings
eignen sich die Funktionen fgets und fputs oder gets und fputs.
Die Funktion fscanf liefert den Wert EOF, falls sie auf das Dateiende stößt. In allen anderen Fällen liefert sie die
Anzahl der Argumente, denen beim Einlesen Werte zugewiesen wurden.
88
9.4.
Allgemeine Formatierte Ausgabe
Zur formatierten Ausgabe von Variablen gibt es die zwei Funktionen fprintf und printf. Sie unterscheiden sich
dadurch, daß mit fprintf auf jede beliebige Datei geschrieben werden kann, printf nur auf stdout schreibt.
int fprintf(FILE *datei_stream, char *format, ...variablen...);
int printf(char *format, ...variablen......);
Die Funktionen wandeln die variablen in einen Ausgabestring unter der Kontrolle von format um und schreiben
den string auf datei_stream bzw. auf stdout.
Als Resultat liefern die Funktionen die Anzahl der geschriebenen Zeichen; es ist negativ, wenn ein Fehler
passiert ist.
Die Format-Zeichenkette enthält zwei Arten von Objekten: gewöhnliche Zeichen, die in die Ausgabe kopiert
werden, und Umwandlungsangaben, die jeweils die Umwandlung und Ausgabe des nächstfolgenden Arguments
von fprintf veranlassen. Jede Umwandlungsangabe beginnt mit dem Zeichen % und endet mit einem
Umwandlungszeichen.
Beispiel :
printf("Die Ausgabe ist %d",erg);
printf(" Die Ergebnisse sind a= %d und b = %f\n",a,b);
fprintf(datei,"Das Ergebnis ist %f\n",x);
89
9.4.1. Formatanweisungen für die Ausgabe
Es gibt folgende Möglichkeiten eine Variable in einen Ausgabestring zu konvertieren :
Zeichen
Eingabedaten; Argumenttyp
d,i
int; dezimal mit Vorzeichen.
o,
int; oktal ohne Vorzeichen.
x, X
int; hexadezimal ohne Vorzeichen
u
int; dezimal ohne Vorzeichen
c
int; einzelnes Zeichen, nach Umwandlung in unsigned char.
s
char *; aus der Zeichenkette werden Zeichen ausgegeben bis vor '\0' oder so viele Zeichen, wie die
Genauigkeit verlangt.
f
double; dezimal als [-]mmm.ddd, wobei die Genauigkeit die Anzahl der d festlegt. Voreinstellung ist 6;
bei 0 entfällt der Dezimalpunkt.
e,E
double; dezimal als [-]m.dddddde±xx oder [-]m.ddddddE±xx, wobei die Genauigkeit die Anzahl der d
festlegt. Voreinstellung ist 6; bei 0 entfällt der Dezimalpunkt.
g,G
double; %e oder %E wird verwendet, wenn der Exponent kleiner als -4 oder nicht kleiner als die
Genauigkeitist; sonst wird %f benutzt. Null und Dezimalpunkt am Schluß werden nicht ausgegeben.
p
void *; als Zeiger (Darstellung hängt von Implementierung ab).
n
int *; die Anzahl der bisher von diesem Aufruf von printf ausgegebenene Zeichen wird im Argument
abgelegt. Ein Argument wird nicht umgewandelt.
%
kein Argument wird umgewandelt; ein % wird ausgegeben.
90
9.4.2. spezielle escape Character
In Ausgabestrings können sogenannte escape Character eingestreut werden um die Ausgabe zu formatieren oder
spezielle Vorschub oder Notationszeichen auszugeben. Es gibt folgende escape Character :
Name
newline
horizontal tab
vertical tab
backspace
carriage return
formfeed
bell
backslash
question mark
single quote
double quote
octal number
hex number
Kürzel
Notation in C
\n
\t
\v
\b
\r
\f
\a
\\ (alt shift 7 )
\?
\' (alt ´ )
\"
\ooo
\xhh
NL
HT
VT
BS
CR
FF
BEL
\
?
'
"
ooo
hh
Beispiel :
printf("\a\a Achtung ! Fehler ");
*/
9.5.
/* gibt zwei Bell-Töne vor dem Text aus
Allgemeine Formatierte Eingabe
Zur formatierten Eingabe von Variablen gibt es die zwei Funktionen fscanf und scanf. Sie unterscheiden sich
dadurch, daß mit fscanf von jeder Textdatei lesen kann, scanf nur von stdin liest.
int fscanf(FILE *stream, char *format, &variablen,...);
int scanf(char *format, &variablen,...);
liest vom stream unter Formatierungskontrolle von format die Variablen ein und speichert die konvertierten
Werte unter den nachfolgenden Argumenten ab, die alle Zeiger sein müssen. Die Funktion wird beendet, wenn
format abgearbeitet ist. fscanf liefert EOF, wenn vor der ersten Umwandlung das Dateiende erreicht wird oder
ein Fehler passiert. Andernfalls liefert die Funktion die Anzahl der umgewandelten und abgelegten Eingaben.
Die Format-Zeichenkette enthält normalerweise Umwandlungsangaben, die zur Interpretation der Eingabe
verwendet werden. Die Format-Zeichenkette kann folgendes enthalten:
- Leerzeichen oder Tabulatorzeichen, die ignoriert werden.
- gewöhnliche Zeichen (nicht aber %), die dem nächsten Zeichen nach Zwischenraum im Eingabestrom
entsprechen müssen.
- Umwandlungsangaben, bestehend aus %, einem optionalen Zeichen *, das die Zuweisung an ein
Argument verhindert,
- einer optionalen Zahl, die die maximale Feldbreite festlegt,
- einem optionalen Buchstaben h, l oder L, der die Länge des Ziels beschreibt;
- einem Umwandlungszeichen.
Das Umwandlungszeichen gibt die Interpretation des Eingabefelds an. Das zugehörige Argument muß ein Zeiger
sein. Die erlaubten Umwandlungszeichen zeigt die nächste Tabelle.
Den Umwandlungszeichen d, i, n, o, u und x kann h vorausgehen, wenn das Argument ein Zeiger auf short statt
int ist, oder der Buchstabe l, wenn das Argument ein Zeiger auf long ist. Vor den Umwandlungszeichen e, f und
g kann der Buchstabe l stehen, wenn ein Zeiger auf double und nicht auf float in der Argumentliste steht, und L,
wenn es sich um einen Zeiger auf long double handelt.
91
9.5.1. Formatanweisungen für die Eingabe
Buchstabe
Eingabe Daten, Argument Typ
d
dezimal, ganzzahlig; int *.
i
ganzzahlig; int *. Der Wert kann oktal (mit 0 am Anfang) oder hexadezimal (mit 0x oder 0X am
Anfang) angegeben sein.
o
oktal ganzzahlig (mit oder ohne 0 am Anfang); int *.
u
dezimal ohne Vorzeichen; unsigned int *.
x
hexadezimal ganzzahlig (mit oder ohne 0x oder 0X am Anfang); int *.
c
ein oder mehrere Zeichen; char *. Die nachfolgenden Eingabezeichen werden im angegebenen Vektor
abgelegt, bis die Feldbreite erreicht ist;Voreinstellung ist 1. '\0' wird nicht angefügt. In diesem Fall wird
Zwischenraum nicht überlesen; das nächste Zeichen nach Zwischenraum liest man mit %1s.
s
Folge von Nicht-Zwischenraum-Zeichen (ohne Anführungszeichen); char *,der auf einen Vektor zeigen
muß, der die Zeichenkette und das abschließende '\0' aufnehmen kann, das dazukommt.
e,f,g
Gleitpunkt; float *. Das Eingabeformat erlaubt für float ein optionales Vorzeichen, eine Ziffernfolge,
die auch einen Dezimalpunkt enthalten kann, und einen optionalen Exponenten, bestehend aus E oder e
und einer ganzen Zahl, optional mit Vorzeichen.
Zeiger, wie ihn printf("%p") ausgibt; void *.
p
n
legt im Argument die Anzahl Zeichen ab, die bei diesem Aufruf bisher gelesen wurden; int *.Vom
Eingabestrom wird nicht gelesen, die Zählung der Umwandlungen bleibt unbeeiflußt.
erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen in der angegebenen Klasse; char *.
Dazu kommt '\0' . Die Klasse []...] enthält auch ].
[...]
erkennt die längste nicht-leere Zeichenkette aus den Eingabezeichen nicht in der angegebenen Klasse;
char *. Dazu kommt '\0' . Die Klasse [^]...] enthält auch ].
[^...]
erkennt %; eine Zuweisung findet nicht statt.
%
92
9.6.
Blockorientierte E/A
Bei der blockorientierten E/A wird eine vom Programmierer festgelegte Anzahl von Bytes ohne Rücksicht auf
deren Inhalt übertragen. Sie eignet sich einerseits für das Bearbeiten von Binärdateien, die aus Datensätzen eines
struct-Typs aufgebaut sind, andererseits für das bloße übertragen großer Datenblöcke, bei dem der Inhalt keine
Rolle spielt. Für diese Art der E/A benötigt man die Funktionen
size_t
size_t
fread (void *ptr, size_t size_of_item, size_t
number_items, FILE *file_ptr)
fwrite(void *ptr, size_t size_of_item, size_t
number_items, FILE *file_ptr)
Das Argument ptr ist ein Zeiger auf einen Bereich im Arbeitsspeicher (z.B. ein Array), in den das Gelesene
kopiert bzw. aus dem das zu Schreibende geholt werden soll. Die Größe dieses Bereichs (in Byte) gibt das
zweite Argument an. Bei Dateien, die aus Komponenten eines struct-Typs bestehen, arbeitet man hier
sinnvollerweise mit dem sizeof-Operator. Das dritte Argument legt fest, wieviele Elemente zu übertragen sind.
Der file pointer schließlich gibt Auskunft darüber, mit welcher Datei gearbeitet werden soll.
Beim Erreichen des Dateiendes liefert fread den Wert 0, in allen anderen Fällen liefert fread als Ergebnis die
Anzahl der erfolgreich gelesenen Elemente. (nicht EOF!).
9.7.
Positionierung und wahlfreier Zugriff
Wie bereits erwähnt, kennt Standard-C nur stream-Dateien, d.h. Dateien, in denen lückenlos aufeinanderfolgende
Zeichen abgelegt sind. Auf der Grundlage dieses Konzepts lassen sich Relativdateien im eigentlichen Sinne nicht
realisieren. Geboten ist lediglich die Möglichkeit, in einem stream frei zu positionieren. Nachdem man in einer
Datei an eine bestimmte Stelle positioniert hat, kann je nach erlaubtem Dateizugriff ab dieser Stelle gelesen oder
geschrieben werden.
Dies geschieht mit Hilfe der Funktionen fseek, fgetpos oder fsetpos.
int fgetpos(FILE *stream, fpos_t *ptr)
fgetpos speichert die aktuelle Position für stream bei *ptr. Der Wert kann später mit fsetpos verwendet werden.
Der Datentyp fpos_t eignet sich zum Speichern von solchen Werten. Bei Fehler liefert fgetpos einen von Null
verschiedenen Wert.
int fsetpos(FILE *, fpos_t *)
positioniert den Stream
int fseek(FILE *stream, long offset, int origin)
Außer dem file pointer benötigt die Funktion einen offset, d.h. einen Abstand in Byte und eine Startposition, von
wo aus der offset aufaddiert werden soll, um an die spezielle Stelle zu positionieren. Der Abstand ist bei
Datensätzen eines struct-Typs wiederum am einfachsten mit Hilfe des sizeof-Operators anzugeben.
93
Für die Startposition zur Positionierung gibt es drei Möglichkeiten.
Argumentwerte
0 oder SEEK_SET
1 oder SEEK_CUR
2 oder SEEK_SET
Position
Dateianfang
die aktuelle Position
Dateiende
Die drei symbolischen Konstanten
SEEK_SET,
SEEK_CUR und
SEEK_END werden
in stdio.h definiert. Der offset wird stets zur Startposition hinzuaddiert, es ist aber auch möglich, negative Werte
anzugeben.
Beispiele :
1 : Positionieren auf den Anfang einer Datei :
fseek(fp,0,SEEK_SET)
einfacher ist dies mit der Funktion
rewind(fp)
erreichbar.
2. Positionieren auf den fünften Datensatz einer Binärdatei :
offset = 5 * sizeof (struct xyz);
fseek (fp, offset, SEEK_SET);
3. Bestimmen der Dateilänge mit der Funktion ftell).
fseek(fp,0,SEEK_END)
length_in_bytes=ftell(fp)
4. Merken einer bestimmten Stelle und zurückspringen an diese Position.
cur_pos=ftell(fp);
/*
/* Bestimmen der Fileposition */
beliebige Fileoperationen, wie fread, fwrite, fgets, fputs ... */
fseek(fp,cur_pos,SEEK_SET)
/* Zurückspringen an diese Stelle */
5. Positionieren hinter das Dateiende bei der Bearbeitung von Relativdateien
fseek(fp,20,SEEK_END)
Folgt auf einen solchen Aufruf eine Schreibanweisung (z.B fwrite), so wird der neue Datensatz 20 Byte hinter
dem bisherigen Dateiende abgelegt. Die dabei entstehende Lücke wird mit Null-Bytes gefüllt. Dieses Verhalten
wird häufig ausgenutzt, um Relativdateien zu simulieren.
9.8.
Anzahl der Bytes einer Datei
94
Nur bei Dateien, die als binary stream geöffnet sind, liefert die Funktion ftell die Anzahl der Bytes vom
Dateianfang bis zur jetzigen Stelle.
int ftell (FILE *fp)
Bei Dateien, die als text stream geöffnet sind, liefert die Funktion einen implementationsabhängigen Wert, der
nur dann die richtige Bedeutung hat, wenn
offset den Wert 0 hat oder
offset durch einen vorhergehenden Aufruf von ftell
ermittelt wurde und startposition den Wert SEEK_SET hat.
9.9.
Fehler Behandlung bei E/A Operationen
Es gibt eine Reihe von Fehlerfunktionen, die den Zustand eines streams abfragen lassen :
9.9.1. Feststellen und Analysieren eines E/A Fehlers
Wie in früheren Abschnitten gezeigt wurde, wird jeder File, der in einem Programm bearbeitet wird, einem
internen Filestream zugeordnet. Dieser Filestream entspricht einem Record vom Typen FILE und dient als
Organisationseinheit zur Bearbeitung der Datei.
Im nachfolgenden werden Auszüge aus dem Record von Typen FILE gezeigt :
typedef#struct {
...............
unsigned#std #: 1;
unsigned#binary #: 1;
unsigned#eof #: 1;
unsigned#err #: 1;
unsigned#dirty #: 1;
...............
} FILE
Die zwei wichtigsten Recordkomponenten der Fileorganisationseinheit sind eof, der sogenannte "end-of-file
indicator" und die Komponente err, der "error indicator". Stößt eine Leseoperation auf das Fileende, wird der
"end-of-file indicator" gesetzt, bei sonstigen Fehlern wird der ""error indicator" gesetzt. Bei erfolgreicher
Funktionsausführung werden die Werte dieser Komponenten nicht verändert und die Lese-Routinen liefern ihre
erwarteten Resultate.
Die Lese-Routinen fgetc, getw, gets liefern als Rückgabewert den gelesenen Buchstaben, das gelesene Wort oder
den eingelesenen String.
Die Funktion fscanf liefert die Anzahl der Variablen, denen beim Einlesen ein Wert zugewiesen wurde.Die
Funktionen fread und read haben die Anzahl der eingelesenen Bytes als Rückgabewert.
In der folgenden Tabelle werden die Rückgabewerte der E/A Funktionen gezeigt:
95
Funktion
fgetc
getw
fgets
fscanf
fread
read
Erfolgreich
gelesener Character
gelesenes Wort
gelesener String
Anzahl der eingelesenen
Variablen
n (Bytes)
n (Bytes)
Dateiende
EOF
EOF
NULL
EOF
andere Fehler
EOF
EOF
NULL
EOF
< n (Bytes)
0
< n (Bytes)
-1
Bei den Routinen fread und read bezieht sich der Wert der Variablen n auf die Anzahl der einzulesenden Bytes.
Um zu testen, ob eine Leseoperation an das Fileende gestoßen ist,muß der "end-of-file" Indikator getestet
werden. Dazu hat man folgende Funktion :
int
feof (FILE *file_ptr)
Sie liefert in Abhängigkeit von der E/A Funktion den in der obigen Tabelle aufgeführten Wert.
Ist beim Lesen oder Schreiben irgendein anderer Fehler aufgetreten, wird der "error-indicator" gestetzt und die
Art des Fehlers wird in der externen Variable errno dokumentiert. Die externe Variable errno, wird nach dem
Standard im header file stddef.h definiert. Die möglichen Werte, die in errno abgespeichert werden, sind
implementierungsabhängig.
Zum Testen ob der "error-indicator" gestetzt wurde hat man folgende Funktion :
int
ferror (FILE *file_ptr)
Will man die Art des Fehlers ausgeben, muß der Wert von errno interpretiert werden, was durch folgende
Funktion realisiert wird :
int
perror (const char *str)
Die Funktion greift auf die Variable errno zu und druckt auf dem Terminal die zugehörige Fehlermeldung aus.
Das Argument von perror, ein String, wird automatisch vor der Fehlermeldung auf das Terminal geschrieben.
Beispiel :
if ( (fp_source = fopen ("test.dat","r")) == FOPEN_ERR )
perror ("fopen_error/test.dat");
Bei Nichtvorhandensein der Datei test.dat wird folgende Meldung ausgegeben:
fopen_error/zahlen.dat: no such file or directory
ausgegeben wird.
96
9.9.2. Beseitigen des E/A Fehlerstatus
Ist ein Fehler aufgetreten, so bleibt der Fehlerstatus im Organisationsblock des File-streams erhalten, bis eines
der folgenden Ereignisse eintritt:
Das Programm wird beendet.
Die Datei wird neu geöffnet.
Die Funktion rewind wird aufgerufen
Die Funktion clearerr wird aufgerufen.
Dei Funktion clearerr funktioniert folgendermaßen :
void clearerr (FILE *file_ptr)
Durch sie wird sowohl der "end-of-file indicator"- als auch der "error indicator" beseitigt (die entsprechenden
Speicherinhaltewerden auf 0 gesetzt, so daß das Programm weiter ablaufen kann.
9.10. UNIX-E/A
Die in diesem Abschnitt vorgestellten Funktionen sind UNIX-spezifische Routinen. Sie sind
nicht in den ANSI Standard aufgenommen worden, aber dennoch in vielen Run-Time
Libraries enthalten. Innerhalb eines Programms werden bei UNIX E/A Dateistreams im
Gegensatz zum Standard E/A nicht mit einem file pointer vom Typen FILE angesprochen,
sondern mit einem file descriptor vom Typen int. Außerdem arbeiten die UNIX E/A
Funktionen ungepuffert.
9.10.1. Öffnen und Schließen einer Datei :
Die Zuordnung des Filenamens zu einem file descriptor realisiert die Funktion
int open (char *file_spec, int flags, unsigned int mode )
Das Argument flags hat die gleiche Aufgabe wie die Angabe des Modus bei fopen. Die
möglichen Werte lauten:
flags
r
w
a
r+
w+
a+
Beschreibung
O_RDONLY
O_WRONLY | O_TRUNC | O_CREAT
O_WRONLY | O_APPEND | O_CREAT
O_RDWR
O_RDWR | O_TRUNC | O_CREAT
O_RDWR | O_APPEND | O_CREAT
Aus der Tabelle läßt sich erkennen, daß die einzelnen flags durch bitweises ODER ( | )
kombiniert werden können. Der gleiche Mechanismus wird (allerdings mit Oktalzahlen,
nicht mit symbolischen Konstanten) beim dritten Argument angewandt, um den Schutz
(protection) einer neu geschaffenen Datei zu spezifizieren. Der folgende
Programmausschnitt soll dies verdeutlichen:
97
#include
#include
main ()
{
int
unsigned int
fdesc, flags;
mode;
flags = O_WRONLY | O_TRUNC | O_CREAT;
mode
=
|
|
|
|
0004
0040
0100
0200
0400
/*
/*
/*
/*
/*
WORLD:
GROUP:
OWNER:
OWNER:
OWNER:
READ */
READ */
EXECUTE */
WRITE */
READ */;
if ( (open ("bsp.c",flags,mode)) == -1 )
{
printf ("Datei BSP.C konnte nicht geöffnet werden!);
exit (1);
}
Kann die Datei nicht geöffnet werden, so liefert open den Wert -1 als Ergebnis (im
Unterschied zur Funktion fopen, die in diesem Fall 0 zurückgibt!).
9.10.2. Schließen einer Datei
Eine geöffnete Datei wird geschlossen durch die Funktion
int close (int file_desc )
9.10.3. Lesen und Schreiben
Unter den UNIX-Routinen gibt es nur zwei E/A-Funktionen:
int read (int file_desc, void *buffer, size_t nbytes))
int write (int file_desc, void *buffer, size_t nbytes))
Diese beiden Funktionen sind vergleichbar mit fread und fwrite, insofern auch sie nur
Blöcke von Bytes übertragen, ohne sich um deren Inhalt zu kümmern (allerdings kann dies
unter Nicht-UNIX- Systemen bei text streams anders sein!).
Beide Funktionen liefern den Wert -1, wenn ein Fehler aufgetreten ist, read den Wert 0,
wenn das Dateiende erreicht wurde. Bei erfolgreicher Lese- bzw. Schreiboperation liefern die
Funktionen die Anzahl der tatsächlich übertragenen Bytes, (siehe file_result)
98
9.10.4. Positionierung in der Datei
int lseek (int file_desc, int offset, int direction))
Diese Funktion verhält sich wie fseek, ermöglicht also ebenfalls die Positionierung über das Dateiende hinaus
und somit die Simulation von Relativdateien.
9.11. Untersuchung des Laufzeitverhaltens bei E/A Funktionen
9.11.1. Bearbeitung von Textdateien
Zur Bearbeitung von Textdateien bieten sich folgende Funktionen an :
int fread (void *ptr, size_t size, size_t numobj,FILE *stream)
int fwrite (void *ptr, size_t size, size_t numobj,FILE *stream)
char *fgets (char *s, int n, FILE *stream)
int fputs (const char *s, int n, FILE *stream)
int fscanf (FILE *stream, const char *format, ... )
int fprintf (FILE *stream, const char *format, ... )
int getw (FILE *stream)
int putw (int word, FILE *stream)
int getc (FILE *stream)
int putc (char c, FILE *stream)
In Tests zeigt sich folgendes Zeitverhalten :
fread/fwrite ist am schnellsten.
fgets/fputs sind schneller als fscanf/fprintf
fscanf/fprintf sind schneller als getw/putw
getw/putw und fgetc/fputc,
getc/putc sind schneller als fgetc/fputc
Daß das Bearbeiten einer Datei mit fgets/fputs schneller ist als das mit getw/putw, und dieses wiederum schneller
als das mit fgetc/fputc, ist zu vermuten, weil die Verkleinerung des bearbeiteten Blocks zu mehr Aufrufen und
damit zu längerer Laufzeit führt.
Da die Funktionen fgetc/fputc intern im allgemeinen auf die beiden Macros getc/putc zurückgeführt werden, sind
getc/putc schneller als fgetc/fputc. Da fread/fwrite Blocks fester Größe kopieren und sich nicht um Stringgrenzen
kümmern, arbeiten diese Funktionen noch schneller als fgets/fputs. Bis auf fscanf/fprintf zeigen alle Funktionen
das erwartete Zeitverhalten. Allerdings ist sowohl zu fread/fwrite als auch zu fscanf/fprintf eine Einschränkung
notwendig.
Die Funktionen fread und fwrite eignen sich nur bedingt für das Bearbeiten von Textdateien, da ihre eigentliche
Aufgabe darin besteht, Blocks fester Größe zu bearbeiten, sie also nicht auf String-bzw. Recordgrenzen achten
müssen.
Ähnliches gilt für fscanf. Hier ist die Formatangabe "%s" sehr gefährlich, weil sie veranlaßt, daß bis zum
nächsten Begrenzer white space gelesen wird. Wenn nun der String Leerzeichen enthält, liest fscanf jeweils nur
bis zum nächsten Leerzeichen, was dazu führt, daß der String in mehreren Teilen eingelesen wird und die
Laufzeit sich verschlechtert. Enthält andererseits die ganze Datei keinen einzigen Begrenzer, kann es geschehen,
daß fscanf die ganze Datei als einen String zu behandeln versucht. Selbst wenn keiner dieser Fälle auftritt, erhält
man aber das unerwünschte Resultat, daß die Kopie kürzer ist als das Original, weil der Begrenzer nicht in die
Kopie übertragen wird. Der einzige Ausweg wäre, Blocks fester Größe einzulesen (z.B. mit "%80s"), doch geht
damit wiederum die String-Struktur verloren. Die Funktion fscanf sollte also nur beim Einlesen von Datensätzen
fester Länge und gleicher Struktur benutzt werden.
9.11.2. Bearbeiten von Binärdateien
99
Beim Lesen und Beschreiben von Binärdateien bieten sich folgende
Schreib und Lesefunktionen an :
gepufferte Standard-E/A,
ungepufferte Standard-E/A,
UNIX-E/A
Gepufferte Standard-E/A
Folgende Programmzeilen öffnen, lesen und beschreiben eine Binärdatei gepuffert :
fp_source = fopen("unform.dat","rb");
fp_copy
= fopen("kopie.dat","wb");
.....
while ((status = fread (memory,SIZE,REC_IN_BLOCK,fp_source)) != NULL )
fwrite (memory,SIZE,REC_IN_BLOCK,fp_copy);
Ungepufferte Standard-E/A
Folgende Programmzeilen öffnen, lesen und beschreiben eine Binärdatei ungepuffert :
fp_source=fopen("unform.dat","rb");
fp_copy=fopen("kopie.dat","wb");
.....
setvbuf(fp_copy,0,_IONBF,0);
while ((status = fread (memory,SIZE,REC_IN_BLOCK,fp_source)) != NULL )
fwrite (memory,SIZE,REC_IN_BLOCK,fp_copy);
UNIX E/A Funktionen :
fp_source=fopen("unform.dat","O_RDONLY,0");
fp_copy=creat("kopie.dat",0);
.....
while ( (status = read (fp_source,memory,SIZE)) != NULL )
write (fp_copy,memory,SIZE);
100
Bemerkungen:
Unter UNIX läuft die gepufferte Standard-E/A sehr schnell, die ungepufferte dagegen außerordentlich langsam.
Auch die UNIX-E/A ist unter UNIX unerwartet schlecht.
Der Grund für dieses Verhalten könnte darin zu suchen sein, daß immer nur ein Datensatz übertragen wird, der
noch dazu verhältnismäßig kurz ist.
Bei der Bearbeitung von Binärdateien mit Standard E/A und UNIX E/A kann die Anzahl der Bytes, die mit
einem Schreib- oder Lesevorgang bearbeitet einen großen Einfluß auf die Dauer der E/A Vorgänge haben. Unter
UNIX verbessern sich gepufferte und ungepufferte Standard-E/A, wenn die Buffer vergrößert werden.
Die UNIX-E/A arbeitet bei größen Buffern am schnellsten, bei kleineren Buffern ist die gepufferte Standard-E/A
schneller.
Faustregel:
Es ist besser, wenige große Blöcke zu übertragen als viele kleine. Das heißt: Je größer die Blöcke, desto kleiner
die Laufzeit. Hat man aber große Blöcke zu übertragen, dann ist UNIX-E/A schneller als Standard-E/A.
Ungepufferte Standard-E/A ist annähernd so schnell wie UNIX-E/A.
Der Grund liegt darin, daß größere Blöcke zu weniger Funktionsaufrufen führen und damit zu geringerer
Laufzeit. Das ist besonders effizient an Maschinen, die Befehle für blockweises Verschieben kennen. Bei großen
Datenblöcken ist aber Pufferung nicht mehr notwendig, kann sogar die Performance verschlechtern.
9.12. Aufgabe
Erzeugen Sie mit dem Editor eine beliebige Textdatei und schreiben Sie ein Programm, das eine solche Datei
(zeilenweise) rückwärts auf den Bildschirm ausgibt.
101
10. Die Standard Library
Die ANSI Kommision hat eine Reihe von nützlichen Bibliotheksfunktionen definiert, die der Benutzer bei ANSI
C kompatiblen Kompilern benutzen kann. Diese Funktionen sind rechnerunabhängig und für jeden ANSI-C
kompatiblen Kompiler genau festgelegt. Hält sich der Programmierer an die Definition der Funktionen, wird sein
Programm ohne Schwierigkieten auf andere Rechner transportiert werden können.
Die Funktionen sind in einer RUN-TIME-Library (RTL) gespeichert. Jede Funktion ist mit ihrem Header oder
als MACRO in einem bestimmten Includefile definiert.
Funktionen, die als MACRO definiert sind werden vom Praeprocessor zur Übersetzungszeit dem Programm
hinzugefügt, Funktionen aus der RTL werden zur Linkzeit ins Programm gebunden.
10.1. Die Includefiles der ANSI Biblotheksfunktionen
Diagnose :
Character Handling :
Testen eines Buchstabens
Verändern eines Buchstabens
<assert.h>
<ctype.h>
Fehler :
<errno.h>
Parameterwerte, die die Eigenschaften der <floats.h>
reellen Zahlen definieren
Parameterwerte, die die
<limits.h>
Programmierumgebung definieren
Setzen von lokalen Variablen :
<locale.h>
Mathematische Funktionen :
<math.h>
Umgehen des normalen Aufrufs und
<setjmp.h>
Rückkehrmechanismus von Funktionen :
Signal Handling :
<signal.h>
Standard Definitionen :
<stddef.h>
Ein/Ausgabe
<stdio.h>
Generelle Utilities :
<stdlib.h>
Speicher und Stringverarbeitung :
<string.h>
Datum und Zeitfunktionen
<time.h>
10.2. Benutzen von Biblotheksfunktionen
Um eine Bilblothektsfunktion benutzen zu können, muß der zugehörige header-file in das Programm vor der
eigentlichen Verwendung der Biblotheksfunktion mit #include hereingezogen werden. In den Include Dateien
werden neben den Funktionsprototypen auch Werte für Symbole definiert, die für die gesamte
Programmumgebung wichtig sind.
Beispiel :
gets() aus stdio wird benötigt, deshalb am Anfang des Programmoduls :
#include <stdio.h>
102
10.3. Funktionsnamen
Alle Biblotheksfunktionsnamen sind als externe Identifiers deklariert. Deshalb ist es nicht möglich eine eigene
Funktion mit dem Namen einer Biblotheksfunktion zu deklarieren. Außerdem sind alle Funktionsnamen die mit
einem Underscore Buchstaben beginnen für sogenannte "behind-the-screen" Macros definiert.
10.4. Routinen zum Bearbeiten von Strings und Buffern : <string.h>
Es gibt in der RTL-Library eine Reihe von Funktionen zur Bearbeitung von Strings und Buffern. Der
Unterschied zwischen Stringhandling und Bufferhandling Funktionen ist, daß die Stringfunktionen nur bei
Stringargumenten, das heißt bei Null-terminierten Character-arrays arbeiten. Buffer Funktionen arbeiten bei
jedem Array von beliebigen Datenobjekten, dafür muß aber die Bytegröße des Arrays, bzw. die Anzahl der
Bytes, spezifiziert werden.
10.4.1. Die Stringfunktionen
Mit diesen Funktionen kann man :
Die Länge eines Strings bestimmen
Zwei Strings vergleichen
Kopieren, anhängen und duplizieren von Strings
Buchstaben in einem String finden
Trennsymbole von einem String herausziehen
In der nächsten Tabelle werden die Funktionen zur Verarbeitung von
Strings beschrieben:
Funktionsname
size_t strlen
(char *cs)
Beschreibung
liefert Länge von cs, ohne den terminierenden
Character '\0'.
int strcmp
(char *cs,
char *ct)
Zeichenketten cs und ct vergleichen; liefert <0
wenn cs kleiner ct, 0 wenn
cs gleich ct, oder
>0 wenn cs größer ct.
char *strncpy
(char *s,
char *ct,
size_t n)
wie die Funktion strcpy, es werden aber nur n
Zeichen aus ct in s kopiert. Falls der String ct
kürzer als n Characters ist, wird das Array mit
'\0' aufgefüllt. Falls ct mehr als n Zeichen
hat werden nur n Zeichen kopiert, es fehlt dann
der terminierende Nullcharacter (Achtung !!).
103
char *strstr
(char *cs,
char *ct)
sucht das Vorkommen des strings ct in cs. Die
Funktion liefert einen Zeiger auf die erste
Kopie von ct oder NULL, falls nicht
vorhanden.
char *strcat
(char *s,
char *ct)
Die Zeichenkette ct wird hinten an die
Zeichenkette s angefügt. Es werden so viele
Character von ct kopiert, bis die Kopierfunktion
auf den terminierenden Nullcharacter von ct
stößt. Achtung, falls dieser Character fehlt !
Das Ergebnis der Funktion ist der String s.
char *strncat
(char *s,
char *ct,
size_t n)
höchstens n Zeichen von ct werden hinten an die
Zeichenkette s angefügt und s wird automatisch
mit '\0' abgeschlossen.
Das Ergebnis der Funktion ist der String s.
char *strcpy
(char *s,
char *ct)
Zeichenkette ct wird in den Vektor s kopiert.
Der String ct muß mit einen terminierenden
Nullcharacter abgeschlossen sein, der dann auch
kopiert wird. Fehlt dieser Abschlußcharacter bei
ct, wird das Ergebnis s ebenfalls keinen
Abschlußchararcter haben.
int strncmp
(char *cs,
char *ct,
size_t n)
es werden höchstens n Zeichen von cs mit der
Zeichenkette ct vergliechen Die Funktion liefert
einen Wert
<0 wenn
cs kleiner ct,0 wenn cs gleich ct, oder größer 0
wenn cs größer ct ist.
char *strchr
(char *cs,
int*c)
Sucht das Vorkommen des ersten c in cs. Die
Funktion liefert Zeiger darauf oder NULL, falls
nicht vorhanden.
char *strrchr
(char *cs,
int*c)
Sucht das Vorkommen des letzten c in cs. Die
Funktion liefert Zeiger darauf oder NULL, falls
nicht vorhanden.
size_t strcspn
(char *cs,
char *ct)
zählt die Anzahl der Zeichen am Anfang
die alle nicht im String ct
vorkommen.
size_t strspn
(char *cs,
char *ct)
zählt die Anzahl der Zeichen am Anfang von cs,
die sämtliche im string ct
vorkommen.
char *strpbrk
(char *cs,
char *ct)
Die Funktion ist die inverse Funktion von
strpbrk). Sie liefert einen Zeiger auf die
Position in cs, an der irgendein Zeichen aus ct
erstmals vorkommt, oder NULL, falls keines
vorkommt.
int strcoll
(char *t,
size_t n,
char *f)
Die Funktion transformiert den string f, so daß
es ein
passendes Argument
für memcmp() und strcmp() ist. Das Ergebnis wird
in t
abgespeichert(nur maximal
n characters).
char *strtok
(char *s,
char *c)
strtok durchsucht s nach Zeichenfolgen, die
durch Zeichen aus ct
begrenzt sind.
104
von cs,
char *strerror
(int*n)
liefert Zeiger auf Zeichenkette,
Implementierung für Fehler
der Nummer n definiert
ist.
105
die in der
Routinen zum Bearbeiten von Buffern :
void *memchr
(void *cs,
int *c,
size_tn)
liefert Zeiger auf das erste c in cs oder NULL,
wenn das Zeichen in den
ersten n Zeichen
nicht vorkommt.
void *memcpy
(void *s,
void *ct,
size_t n)
kopiert n Zeichen von ct nach s; liefert s. Die
Strings dürfen nicht überlappen.
void *memmove
(void *s,
void *ct,
size_t n)
wie memcpy. Da der zu kopierende Buffer intern
in einem temporären Buffer zwischengespeichert
wird, funktioniert die Funktion auch wenn die
Objekte überlappen.
int memcmp
(void *cs,
void *ct,
size_t n)
vergleicht die ersten n Zeichen von cs mit ct.
Das Ergebnis ist analog zu dem von strcmp). Die
Funktion ist primär für den Vergleich von
Strings entwickelt worden, deshalb arbeitet sie
nicht bei Strukturen, die "Löcher" (Null-Werte)
in ihrer Struktur haben. Die Funktion arbeitet
ebenfalls fehlerhaft bei "high-order-bit"
gesetzten Objekten.
void *memset
(void *s,
int*c,
size_t n)
#Die Funktion initialisiert die ersten n Zeichen
von s mit dem Wert c.
Das Ergebnis ist ein Pointer s auf den
initialiserten Buffer.
10.5. Routinen zur Diagnose : <assert.h>
Dieses Macro wird gebraucht um Diagnosepunkte in das Programm einzubauen.
void assert(int expr)
Wenn der Ausdruck expr gleich Null ist (entspricht FALSE), dann wird folgende Meldung auf stderr ausgegeben
:
Assertion failed: expression, file filename, line nnn
Anschließend wird das Programm abgebrochen.
Beispiel :
assert(fx=fopen("bac","r"))
Falls die Datei "bac" nicht existiert, liefert die Routine fopen den Wert Null und testet den Wert fx . Da er Wert
gleich Null ist, gibt es folgende Meldung aus :
assert error: expression = fp = fopen("bac","r") ,
in file openfile.c at line 20
106
10.6. Character Bearbeitung : <ctype.h>
Funktionen zum Testen und Konvertieren eines Characterarguments. Jede Funktion hat einen int Wert als
Argument. Das Ergebnis ist ebenfalls von Typ int. Die Funktionen liefern als Ergebnis der Wert TRUE (=Null)
oder FALSE (ungleich Null).
isalnum
isalpha
iscntrl
isdigit
isgraph
islower
isprint
ispunct
isspace
isupper
isxdigit
tolower
toupper
Alphabetische oder numerische Buchstaben :
isalpha()oder isdigit() ist true
Alphabetische Buchstaben : isupper() oder
islower() ist true, oder
iscntrl(), isdigit(), ispunct(), und isspace()
ist false
control character, Asciicode von 0
bis 1F, dazu 7F
Dezimalzahl : 0 bis 9
sichtbares Zeichen, außer Leerzeichen
Kleinbuchstabe
Druckbarer Buchstabe, mit Leerzeichen
Druckbarer Buchstabe, außer
Leerzeichen, Buchstaben oder
Zahlen für die isalnum() ist wahr
Leerzeichen (' '), form feed ('\f'), newline
('\n'), carriage return ('\r'),
horizontal tab ('\t') oder vertical tab ('\v')
Großbuchstabe oder irgendeine
implementationsdefinierte Untermenge von
Buchstaben, für die iscntrl(), isdigit(),
ispunct(), und isspace() false ist
Hexadezmalzahl
Konvertiert Buchstabe in Kleinbuchstaben
Konvertiert Buchstabe in Großbuchstaben
Bemerkung :
Im 7 Bit ASCII-Zeichensatz haben die sichtbaren Zeichen ASCII-Codes zwischen 0x20 (' ') und 0x7E , die
Steuerzeichen liegen zwischen 0 und 0x1F, sowie das Zeichen 0x7F.
10.7. Error Funktionen : <error.h>
Alle beim Programmablauf sich ereignenden Fehler setzen entsprechend dem eingetretenen Fehler einen
entsprechenden Wert in der globalen Variablen errno. Innerhalb des Include-Files <error.h> werden eine Reihe
von Errornummern für bestimmte Fehler definiert:
Beispiel :
#define EDOM 33
#define ERANGE 34
/* Verlassen des Definitionsbereichs einer
Funktion,z.B. sqrt(c) nur falls c>0 */
/* Funktionsergebnis zu groß für eine double
Zahl */
107
10.8. Floating Point Funktionen : <float.h>
In diesem Modul werden Konstanten, die sich auf Gleitpunktarithmetik beziehen, definiert.
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
FLT_ROUNDS
FLT_RADIX
FLT_DIG
FLT_EPSILON
FLT_MANT_DIG
FLT_MIN
FLT_MIN_EXP
FLT_MAX
FLT_MAX_EXP
DBL_DIG
DBL_EPSILON
DBL_MANT_DIG
DBL_MIN
DBL_MIN_EXP
DBL_MAX
DBL_MAX_EXP
2
6
1
1E-5
1E-37
1E+37
10
1E-9
1E-37
1E+37
10.9. Impementationsabhängige Grenzwerte : <limits.h>
Diese Include-Datei definiert Konstanten für den Wertumfang der ganzzahligen Typen.
#define
#define
#define
#define
#define
#define
CHAR_BIT
CHAR_MIN
CHAR_MAX
SCHAR_MAX
SCHAR_MIN
UCHAR_MAX
8
(~127)
127
#define SHRT_MIN
#define SHRT_MAX
#define USHRT_MAX
(~32767)
32767
0xFFFF
#define INT_MIN
#define INT_MAX
#define UINT_MAX
(~32767)
32767
0xFFFF
#define LONG_MIN
#define LONG_MAX
#define ULONG_MAX
(~2147483647)
2147483647
0xFFFFFFFF
108
10.10.Routinen für nationale Eigenheiten : <locale.h>
Diese Include Datei liefert Routinen, die die Programmierumgebung auf nationale Eigenheiten einer Sprache
einstellt, bzw. abfrägt, welche Eigenheiten der Programmierumgebung auf nationale Eigenheiten rücksicht
nimmt.
Beispiel :
Das deutsche Datumsfomat ist ein anderes als das Amerikanische.
char *setlocale(int, char *)
setzt, bzw, frägt nach der lokalen
Programmierumgebung.
Einstellung der
10.11.Mathematische Bibliothek : <math.h>
#define HUGE_VAL
double sin(double x)
double cos(double x)
double tan(double x)
double asin(double x)
double acos(double x)
double
double
x)
double
double
double
double
double
double
atan(double x)
atan2(double y,double
sinh (double x)
cosh (double x)
tanh (double x)
exp (double x)
log (double x)
log10 (double x)
double pow (double x,double
y)
double sqrt (double x)
double ceil (double x)
double floor (double x)
double fabs (double x)
double ldexp (double x,int n)
double frexp (double x, int
*exp)
double modf (double x, double
*ip)
(* (double *) __HUGE_VAL)
Sinus von x
Kosinus von x
Tangens von x
arcsin(x) im Bereich [-•/2, •/2], x Œ[1, 1].
arccos(x) im Bereich [0, •], x Œ [-1,
1].
arctan(x) im Bereich [-•/2, •/2]
arctan(y / x) im Bereich [-•, •]
Sinus Hyperbolicus von x
Cosinus Hyperbolicus von x
Tangens Hyperbolicus von x
Exponentialfunktion ex
natürlicher Logarithmus Ln(x), x > 0
Logarithmus zur Basis 10 log10(x), x >
0
xy. Ein Argumentfehler liegt vor bei
x=0 und y•0, oder bei x<0 und y ist
nicht ganzzahlig
•x, x•0
kleinster ganzzahliger Wert, der nicht
kleiner als x ist, als double
größter ganzzahliger Wert, der nicht
größer als x ist, als double
absoluter Wert | x |
x * 2n
zerlegt x in eine normalisierte
Mantisse im Bereich [1/2, 1], die als
Resultat geliefert wird, und eine
Potenz von 2, die von *exp abgelegt
wird. Ist x null, sind beide Teile des
Resultats null.
zerlegt x in einen ganzzahligen Teil
und einen Rest, die beide das gleiche
Vorzeichen wie x besitzen. Der
ganzzahlige Teil wird bei *ip abgelegt,
der Rest ist das Resultat
109
double fmod (double x, double
y)
Gleitpunktrest von x/y, mit dem
gleichen Vorzeichen wie x. Wenn y null
ist, hängt das Resultat von der
Implementierung ab.
10.11.1.Lokale Sprünge : <setjmp.h>
Mit den Vereinbahrungen in <setjmp.h> kann man die normale Folge von Funktionsaufruf und -ende umgehen.
Typischerweise erlaubt das einen direkten Rücksprung von einem tief verschachtelten Funktionsaufruf.
int setjmp(jmp_buf env)
Der Makro setjmp speichert Zustandsinformation in env zur Auswertung durch
longjmp.
void longjmp(jmp_buf env, int value)
stellt den Zustand wieder her, der beim letzten Aufruf von setjmp gespeichert wurde.
10.11.2.Signal Handling : <signal.h>
In diesem File sind Konstruktionen, die es dem Programmierer ermöglichen, bestimmte Bedingungen oder
Signale zu behandeln. Außerdem werden Werte für Signalsymbole definiert.
#define
#define
#define
#define
#define
#define
SIGABRT
SIGFPE
SIGILL
SIGINT
SIGSEGV
SIGTERM
22
11
15
/* Abbruch */
8
/* Floating point exeption */
4
/* Illegale Konstruktion */
2
/* Interrupt */
/* Segmentverletzung */
/* asynchroner Abbruch */
Die nachfolgenden Macros können als zweites Argument in der Funktion signal benutzt werden :
#define SIG_DFL
#define SIG_ERR
#define SIG_IGN
((void (*) (int)) 0)
((void (*) (int)) -1)
((void (*) (int)) 1)
/* default */
/* error */
/* ignore */
void (*signal(int sig, void (*func) (int))) (int) )
Diese Funktion ordnet dem Signal sig zum Signal Handler func(). Die Funktion func() wird mit dem Argument
sig aufgerufen.
int raise(int)
Bei dieser Funktion wird das signal sig ausgelöst.
10.11.3.Standarddefinitionen : <stddef.h>
In diesem Includefile werden Systemweite Größen definiert. Es enthält die Definition von vier Macros :
ptrdiff_t
size_t
NULL
errno
/* Speicherplatz um die Differenz zweier
Pointer zu speichern */
/* unisgned integer type als Ergebnis des
sizeof Operators */
/* Wert für den Error Fall */
/* globale Variable für den Errorcode */
110
10.11.4.Variable Argumentsanzahl bei Funktionsaufrufen : <stdarg.h>
Diese Include-Datei bietet die Möglichkeit, portable Funktionen zu schreiben, die eine Liste von
Funktionsargumenten besitzen, deren Anzahl und Datentyp nicht bekannt ist. (vgl die Funktion printf( ... ) ).
Angenommen, lastarg ist der letzte benannte Parameter einer Funktion f, die mit einer variablen Anzahl von
Argumenten aufgerufen wird. Dann vereinbart man in f eine Varialbe ap vom Datentyp va_list, die der Reihe
nach auf jedes Argument zeigen wird:
typedef char * va_list;
Als erstes muß ap mit dem Makro va_start initialisiert werden, bevor auf die unbekannten Argumente
zugegriffen wird:
va_start(va_list ap, lastarg)
Daran anschließend liefert jede Ausführung des Makros va_arg einen Wert, der den Datentyp und den Wert des
nächsten unbenannten Arguments besitzt. Außerdem ändert der Makro den Wert von ap so, daß der nächste
Aufruf von va_arg das nächste Argument liefert:
va_arg(va_list ap, type)
Der Makro
void va_end(va-List ap)
muß einmal aufgerufen werden, nachdem die Argumente bearbeitet wurden. Dieses Macro entspricht einer Art
"Cleanup" Funktion.
Beispiel :
void f (arg1, arg2,. . . . )
int arg1, arg2;
111
10.11.5. Generelle
Utilities : <stdlib.h>
Diese Gruppe von Funktionen fällt in folgende Untergruppen :
Dynamisches Beschaffen von Speicher
Such und Sortierverfahren
Zufallszahlen
Kommunikation mit der Programmierumgebung
Ganzzahlige Arithmetik
Verlassen des Programms
Multibyte - Character - Funktionen
Multibyte - String - Funktionen
In stdlib.h werden folgende Typen definiert :
div_t
...
Eine Struktur, als Ergebnis der div()
Funktion
ldiv_t ...
Eine Struktur, als Ergebnis der ldiv()
Funktion
size_t ...
Datentyp als Ergebnis des sizeof-Operators
wchar_t ... Datentyp, der alle vom Standard
unterstützten Buchstaben speichern kann
Außerdem werden folgende MACROS definiert :
ERANGE
Wert der Errorvariable errno, falls der
Wertebereich einer Variablen
verlassen wurde.
HUGE_VAL
Wert der überschritten wird verlassen wurde.
RAND_MAX
Größter Wert der rand() Funktion
EXIT_FAILURE
Rückgabewert der
exit - Funktion bei einem auftretenden
Fehler
EXIT_SUCCESS
Rückgabewert der
exit - Funktion bei erfolgreicher
Durchführung
112
10.11.5.1. Datenkonvertierroutinen
double atof
(const char *s)
wandelt s in double um; die Funktion ist
äquivalent zu: strtod(s, (char**) NULL)
int atoi
(const char *s)
wandelt s in int um; die Funktion ist äquivalent
zu: (int)strtol(s, (char**) NULL, 10)
long atol
(const char *s)
wandelt s in long um; die Funktion ist
äquivalent zu: strtol(s, (char**) NULL, 10)
double strtod
(const char *s,
char **endp)
wandelt den Anfang der Zeichenkette s in double
um, dabei wird Zwischenraum am Anfang ignoriert.
Die Funktion speichert einen Zeiger auf einen
nicht umgewandelten Rest der Zeichenkette bei
*endp, falls endp nicht Null ist. Falls das
Ergebnis zu groß ist, (also bei overflow), wird
als Resultat HUGE_VAL mit dem korrekten
Vorzeichen geliefert; liegt das Ergebnis bei
Null (also bei underflow), wird Null geliefert.
In beiden Fällen erhält errno den Wert ERANGE.
long strtol
(const char *s,
char **endp,
int base)
wandelt den Anfang der Zeichenkette s in long
um, dabei wird Zwischenraum am Anfang ignoriert.
Die Funktion speichert einen Zeiger auf einen
nicht umgewandelten Rest der Zeichenkette bei
*endp, falls endp nicht Null ist. Hat base einen
Wert zwischen 2 und 36, erfolgt die Umwandlung
unter der Annahme, daß die Eingabe in dieser
Basis repräsentiert ist. Hat base den Wert Null,
wird als Basis 8, 10 oder 16 verwendet; eine
führende Null bedeutet dabei oktal und 0x oder
0X zeigen eine hexadezimale Zahl an. In jedem
Fall stehen Buchstaben für die Ziffern von 10
bis base-1; bei Basis 16 darf 0x oder 0X am
Anfang stehen. Wenn das Resultat zu groß werden
würde, wird je nach Vorzeichen LONG_MAX oder
LONG_MIN geliefert und errno erhält den Wert
ERANGE.
unsigned long
strtoul
(char *,
char **, int)
funktioniert wie strtol, nur ist der Resultattyp
unsigned long und der Fehlerwert ist ULONG_MAX.
10.11.5.2. Zufallsgenerator
int rand
(void)
liefert eine ganzzahlige Pseudo-Zufallszahl im
Bereich Null bis RAND_MAX; dieser Wert ist
mindestens 32767.
void srand
(unsigned int
seed)
benutzt seed als Ausgangswert für eine neue
Folge von Pseudo-Zufallszahlen. Der erste
Ausgangswert ist 1.
10.11.5.3. Memory - Management Funktionen
113
void *calloc
(size_t nobj,
size_t size)
liefert einen Zeiger auf einen Speicherbereich
für einen Vektor von nobj Objekten, jedes mit
der Größe size, oder NULL, bei Fehler.
void free
(void *p)
void *malloc
(size_t)
gibt den Bereich frei, auf den p zeigt.
void *realloc
(void *p,
size_t size)
liefert einen Zeiger auf einen Speicherbereich
für ein Objekt der Größe size, oder NULL bei
Fehler.
ändert die Größe des Objekts, auf das p zeigt in
size ab.
10.11.5.4. Programmierumgebungsfunktionen
void abort
(void)
sorgt für eine abnormale Beendigung des
Programms.
int atexit
(void (*fcn)
(void) )
hinterlegt die Funktion fcn, damit sie
aufgerufen wird und das Programm normal endet,
und liefert einen von Null verschiedenen Wert,
wenn die Funktion nicht hinterlegt werden kann.
void exit
(int status)
beendet das Programm normal. Die Werte
EXIT_SUCCESS und EXIT_FAILURE können ebenfalls
angegeben werden.
char *getenv(char
*name)
liefert die zu Name gehörige Zeichenkette aus
der Programmierumgebung oder Null, wenn keine
Zeichenkette existiert.
int system(char *s)
liefert die Zeichenkette s an die
Betriebssystem-Umgebung zur Ausführung.
114
10.11.5.5. Suchen und Sortieren
void *bsearch
(void *key,
void *base,
size_t n,
size_t size,
int (*cmp)
(const void *keyval,
const void *datum) )
durchsucht base[0] ... base[n-1]
nach einem Eintrag, der gleich *key
ist.
void qsort
(void *base,
size_t n,
size_t size,
int (*cmp)(const void *,
const void *) )
sortiert einen Vektor base[0] ...
base[n-1] von Objekten der Größe
size in aufsteigender Reihenfolge.
10.11.5.6. Integer Arithmetik
int abs
(int n)
liefert den Absolutwert eines intArguments.
long labs
(long)
liefert den Absolutwert eines longArguments.
div_t div
(int num, int denom)
berechnet Quotient und Rest von
num/denum. Die Resultate werden in
int Komponenten quot und rem einer
Struktur vom Typ div_t abgelegt.
ldiv_t ldiv(long num, long denom)
berechnet Quotient und Rest von
num/denum. Die Resultate werden in
long Komponenten quot und rem einer
Struktur vom Typ ldiv_t abgelegt.
115
10.11.6.Datum und Zeitfunktionen : <time.h>
struct tm
{
int
int
int
int
int
int
int
int
int
};
tm_sec;
/*Sekunden nach der vollen Minute (0 bis 59)*/
tm_min;
/*Minuten nach der vollen Stunde (0 bis 59)*/
tm_hour;
/*Stunden seit Mitternacht (0 bis 23)*/
tm_mday;
/*Tage im Monat (1 bis 31)*/
tm_mon;
/*Volle Monate seit Januar (0 bis 11)*/
tm_year;
/*Jahre seit 1900*/
tm_wday;
/*Tage seit Sonntag (0 bis 6)*/
tm_yday;
/*Tage seit dem 1. Januar (0 bis 365)*/
tm_isdst; /*Kennzeichen für Sommerzeit
positiv, wenn Sommerzeit gilt,
Null, wenn Sommerzeit nicht gilt,
negativ, wenn die Information nicht zur
Verfügung steht.*/
116
Dazu gibt es folgende Routinen :
clock_t clock
(void)
liefert die Rechnerzeit, die das Programm
seit Beginn seiner Ausführung verbraucht
hat, oder -1, wenn diese Information nicht
zur Verfügung steht. clock()/CLK_TCK ist
eine Zeit in Sekunden
double difftime
(time_t time2,
time_t time1)
liefert time2-time1 ausgedrückt in Sekunden
time_t mktime
(struct tm *)
wandelt die Ortszeit in der Struktur *tp in
Kalenderzeit um, die so dargestellt wird
wie bei time. mktime liefert -1, wenn die
Kalenderzeit nicht dargestellt werden kann
time_t time
(time_t *tp)
liefert die aktuelle Kalenderzeit oder -1,
wenndiese nicht zur Verfügung steht. Wenn
tp von NULL verschieden ist, wird der
Resultatwert auch bei *tp abgelegt
char *asctime
(struct tm *tp)
konstruiert aus der Zeit in der Struktur
*tp eine Zeichenkette in der Form Sun Jan
3 15:14:13 1988\n\0
char *ctime
(time_t *tp)
verwandelt die Kalenderzeit *tp in
Ortszeit; dies ist äquivalent zu
asctime(localtime(tp))
struct tm *gmtime
(time_t *tp)
verwandelt die Kalenderzeit *tp in
Coordinated Universal Time (UTC). Die
Funktion liefert NULL, wenn UTC nicht zur
Verfügung steht
struct tm *localtime
(time_t *tp)
verwandelt die Kalenderzeit *tp in
Ortszeit.
size_t strftime
(char *s,
size_t size,
char *format,
struct tm *timepr)
Erlaubt einen String zu erzeugen mit der
Spezifischen Information aus timeptr
117
11. Der C Praeprocessor
Zu Beginn des Übersetzungsvorgangs wird ein C-Programm von einem Praeprocessor bearbeitet, der das CProgramm zur Kompilierung aufbereitet. Der Praeprocessor sucht nach bestimmten Direktiven und verändert
entsprechend dieser Anweisungen die Programmzeilen des C-Programms. Die zwei meist genutzten
Anweisungen für den Praeprocessor sind das Einfügen (include) eines Fileinhalts an eine bestimmte Stelle des
zu kompilierenden Files und die Ersetzung (define) beliebiger Buchstabenfolgen durch vorher definierte
Buchstabenfolgen.
Jede Praeprocessordirektive beginnt mit dem Zeichen # (Hash).
11.1. Fileinhalt einfügen : #include
#include "filename"
sucht die Datei filename im selben Dateiverzeichnis wie das
Sourceprogramm
oder
#include <filename>
sucht die Datei filename nach Implementationsabhäniger Art in verschiedenen Dateiverzeichnissen, meist
im Library Verzeichnis.
Der Default filetyp einer Include Datei ist .h (z.B.:stdio.h).
Beispiel :
#include <stdio.h>
#include "header"
118
11.2. MACRO Ersetzung : #define, #undefine
#define
#undef name
name
zu ersetzender Text
nimmt die Definition von name zurück
Im ganzen Sourcefile wird name durch zu ersetzender Text ersetzt. Normalerweise entspricht der zu ersetzende
Text dem Rest der Zeile, eine Zeile kann jedoch durch das Zeichen \ am Ende einer Zeile verlängert werden.
Ersetzungen von name werden nur bei sogenannten Tokens vorgenommen, nicht innerhalb von quoted Strings.
Beispiel :
#define Fehler
#define maximal
#define ewig
im Programm :
"Achtung ein Fehler ist aufgetreten"
128
for (;;)
x = maximal + 10;
printf(Fehler);
ewig;
/* Achtung :*/ printf("Fehler");
Ein MACRO kann auch mit Argumenten definiert werden. Beim Aufruf werden die Argumente ihrer Definition
entsprechend eingesetzt.
Beispiel :
#define max(A,B) ((A) > (B) ? (A) : (B))
im Programm :
ergibt :
x = max(p+q,r+s);
x = ((p+q) > (r+s) ? (p+q) : (r+s))
Mit dem # Operator gibt es eine Möglichkeit, formale Parameter als strings ausdrucken zu lassen oder mit dem
## Operator an ein anderes Argument anzuhängen :
Beispiel :
#define
im Programm :
ergibt :
druck(expr)
printf("#expr=%f\n",expr)
druck(x/z)
printf("x/y=%f\n",expr);
und daraus :
printf("x/y=%f\n",expr);
#define
paste (one,two,three)
im Programm :
paste(a,b,c)
ergibt : Definition des Eintrags abc.
119
one ## two ## three
11.3. Konditionales Praeprocessing #if
In Abhängigkeit des Wertes eines Ausdrucks (Null = FALSE, Ungleich Null = TRUE), werden die nachfolgend
geklammerten Direktiven vom Praeprocessor ausgeführt.
#if expression
.....
#endif
#if expression
.....
#elif
...
#endif
#if expression
.....
#else
...
#endif
wobei : expression : ==, !=, <=, <, >, >=, defined(name)
statt #if gibt es auch :
#ifdef
name
if name is defined
#ifndef
name
if name is not defined
Beispiel :
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
120
Beispiel :
/* demonstration von Praeprocessordirektiven */
#include "pre-header.h"
#include <stdio.h>
#ifdef CIS
#define TEXT "guten Tag am CIS \n"
#define ANZAHL 3
#endif
#ifndef CIS
#define CIS 0
#define TEXT "Alles schläft am CIS \n"
#define ANZAHL 0
#endif
#define max(a,b) ((a) > (b) ? (a) : (b))
main()
{
if (CIS)
{
int a,b,i;
puts("wir arbeiten");
printf("Eingabe von zwei Zahlen >>>");
scanf("%d %d",&a,&b);
for (i=0;i<ANZAHL;i++)
printf(TEXT);
printf("die Größere Zahl ist >%d\n",max(a,b)); };
if (!CIS)
{
puts("wir arbeiten nicht ");
printf(TEXT); }
}
121
12. Lösungen
12.1. Beispiele und Lösungen zur Stringverarbeitung
12.1.1. Beispiel zum Ausgeben von Strings:
/* Strings und Arrays */
#include <stdio.h>
char s1[5] = "Hallo";
char s2[4] = "Hallo";
char s3[] = "Hallo";
main_str()
{
puts(s1);
puts(s2);
puts(s3);
}
12.1.2. Beispiel zum Initialisieren von Strings:
/***************************
filename : string_array_init.c
autor : max
Version : 1.0
Datum : jun 94
Thema : Strings und Arrays, C - Kurs
***************************/
#include <stdio.h>
char s1[6] = "Hallo";
char s2[5] = "Hallo";
char s3[5] = "H";
char s4[] = "Hallo";
int a1[5] = {1,2,3,4,5};
int a2[] = {1,2,3,4,0};
int a3[5] = {1,2};
main_str_array_init()
{
int i;
char s5[] = "Hallo";
int a4[] = {1,2};
puts(s1);
puts(s2);
puts(s3);
puts(s4);
puts(s5);
printf("\n a1 >>");
for (i=0;i<5;i++)
printf("%d ",a1[i]);
printf("\n a2 >>");
for (i=0;i<5;i++)
printf("%d ",a2[i]);
printf("\n a3 >>");
for (i=0;i<5;i++)
printf("%d ",a3[i]);
printf("\n");
printf("\n a4 >>");
122
for (i=0;i<5;i++)
printf("%d ",a4[i]);
printf("\n");
}
/* Ergebnis :
Hallo
HalloH
H
Hallo
Hallo
a1 >>1 2 3 4 5
a2 >>1 2 3 4 0
a3 >>1 2 0 0 0
a4 >>1 2 18529 27756 28416
*/
12.1.3. Anagramm
/* Projektname : anagramm
Filename : anagramm.c
Autor : max
Datum : 22.Jun.99
Beschreibung : Es werden vom Terminal zwei strings eingelesen und getestet
ob es Anagramme sind
*/
#include <stdio.h>
#define STRSIZE 132
/* Globale Variablen */
/* Funktionsprototypen */
int str_compare(char str1[],char str2[]);
int str_sort1(char str1[],char str2[]);
int str_sort2(char str[]);
int swap(char *i,char *j);
int find_index_of_min(char str[],int Anzahl_der_Elemente,char referenz_char);
/**************** Hauptprogramm ***************************/
main()
{
char str1[STRSIZE];
char str2[STRSIZE];
char strsort1[STRSIZE];
char strsort2[STRSIZE];
printf("Geben Sie den ersten String ein >>>");
gets(str1);
printf("Geben Sie den zweiten String ein >>>");
gets(str2);
strcpy(strsort1,str1);
strcpy(strsort2,str2);
str_sort2(strsort1);
printf(" string sortiert %s ,StringlŠaenge = %d \n ",strsort1,strlen(strsort1));
str_sort2(strsort2);
printf(" string sortiert %s ,StringlŠaenge = %d \n ",strsort2,strlen(strsort2));
if (str_compare(strsort1,strsort2))
{
printf("die Strings %s und %s sind Anagramme\n",str1,str2);
}
else
123
{
printf("die Strings %s und %s sind keine Anagramme\n",str1,str2);
}
}
/**********************************************************************/
int str_sort1(char str[],char strsort[])
/* Sortiert alphabetisch buchstaben, gespiechert in str uns speichert sie in
strsort
Eingabe : str[]
...
Array of Buchstaben
Ausgabe : strsort [] ... Array of Buchstaben
*/
{
int i,j,index_kleiner;
char hlp;
for(i=0;i<strlen(str);i++)
{
hlp = strsort[i];
index_kleiner = i;
for (j=i+1;j<strlen(strsort);j++)
if (strsort[j] < hlp)
{
hlp = strsort[j];
index_kleiner = j;
}
hlp = strsort[i];
strsort[i] = strsort[index_kleiner];
strsort[index_kleiner] = hlp;
}
return;
}
/**********************************************************************/
int str_compare(char str1[],char str2[])
/* vergleicht alphabetisch strings
Eingabe : str[]
...
Array of Buchstaben
strsort [] ...
Array of Buchstaben
Ausgabe : Return Wert = 1 falls Strings gleich sind
*/
{
int i,j;
char hlp;
if (strlen(str1) != strlen(str2)) return 0;
i=0;
while ((i<strlen(str1)) && (str1[i] == str2[i]))
i++;
if (i == strlen(str1))
return 1;
else
return 0;
}
/**********************************************************************/
int str_sort2(char str[])
/*
/* Sortiert alphabetisch buchstaben, gespeichert in str
Eingabe : char str[] ... gespeicherte Buchstaben
*/
{
char kleinster_char;
int i,j,Anzahl_der_Elemente;
Anzahl_der_Elemente = strlen(str);
124
for (i=0; i< strlen(str); i++)
{
kleinster_char = find_index_of_min(&str[i],Anzahl_der_Elemente,str[i]);
swap(&str[i],&str[kleinster_char+i]);
Anzahl_der_Elemente -- ;
}
}
/**********************************************************************/
int swap(char *i,char *j)
/* Vertauscht die Buchstaben, die unter den Adressen i und j abgespeichert sind
{
int help;
help = *i;
*i = *j;
*j = help;
return;
}
/**********************************************************************/
int find_index_of_min(char str[],int Anzahl_der_Elemente,char referenz_char)
/*
Eingabe :
int str[]
...
Character String
int Anzahl_der_Elemente ...
Anzahl Elemente von liste
int referenz_char
...
char muss kleiner sein als referenz_char
Ausgabe :
return value .... Index des kleinsten Elements innerhalb von str
*/
{
int i;
int index_kleinstes_element=0;
for (i=0;i<Anzahl_der_Elemente;i++)
if (str[i] < referenz_char)
{
index_kleinstes_element = i;
referenz_char = str[i];
}
return
index_kleinstes_element;
}
125
*/
12.1.4. Vorkommen eines Strings in einem Anderen
/* Projektname : strcompare
Filename : strcomp.c
Autor : max
Datum : 10.jul.94
Beschreibung : Es werden vom Terminal zwei strings eingelesen, wobei
festgestellt wird, ob der zweite String im ersten enthalten ist */
#include <stdio.h>
#define NUM_OF_CHAR 80
char str[5] = "abcnsd";
/* Globale Variablen */
/* Funktionsprototypen */
int my_strcmp(char str1[],char str2[]);
/********************** main ***********************************/
main()
{
char string1[NUM_OF_CHAR],string2[NUM_OF_CHAR];
int i;
puts(" Hallo hier ist das Programm strcomp");
puts(" Lese zwei strings ein und stelle fest, ob der zweite im ersten enthalten
ist");
puts(" Bitte geben Sie den ersten string ein ");
fflush(stdin);
scanf("%s",string1);
puts(" Bitte geben Sie den zweiten string ein ");
fflush(stdin);
scanf("%s",string2);
if (my_strcmp_pointer(string1,string2))
printf(" String2 >>>%s<<< ist in String1 >>>%s<<<
enthalten\n",string2,string1);
else
printf(" String2 >>>%s<<< ist NICHT in String1 >>>%s<<<
enthalten\n",string2,string1);
}
/********************** Funktionen ***********************************/
int my_strcmp(char str1[],char str2[])
/*
Diese Funktion ueberprueft ob str2 in str1 vorhanden ist.
Eingabe :
char str1[] ...
String1
char str2[] ...
String2
Ausgabe :
return - value = 1, falls str2 in str1
return - value = 0, sonst
*/
{
int ist_enthalten,i,j;
ist_enthalten = 0;
i=0;
do
{
if (str1[i] != str2[0]) i++; /* finde Vorkommen des ersten Elements aus str2
in str1 */
126
else
{
j=0;
/* erstes Element str2 in str1 gefunden */
while ( str1[i] && str2[j] && str1[i] == str2[j] ) /* vergleiche Reststring
str2 */
{ i++;
j++;
};
if (str2[j] == 0) ist_enthalten = 1; /* Falls str2 am Ende dann gefunden */
};
}
/* Falls str1 nicht abgearbeitet dann Suche fortsetzen */
while (str1[i] && ! ist_enthalten);
return ist_enthalten;
}
/********************** Funktionen ***********************************/
int my_strcmp_pointer(char *str1,char *str2)
/*
Diese Funktion ueberprueft ob str2 in str1 vorhanden ist.
Eingabe :
char *str1
...
String1
char *str2
...
String2
Ausgabe :
return - value = 1, falls str2 in str1
return - value = 0, sonst
*/
{
int ist_enthalten,i,j;
char *beg_of_str2;
ist_enthalten = 0;
i=0;
beg_of_str2 = str2;
do
{
if (*str1 != *str2) str1++; /* finde Vorkommen des ersten Elements aus str2
in str1 */
else
{
str2 = beg_of_str2;;
/* erstes Element str2 in str1 gefunden,
postioniere str2
wieder auf den Anfang */
while ( *str1 && *str2 && *str1 == *str2 ) /* vergleiche Reststring str2 */
{ str1++;
str2++;
};
if (*str2 == 0) ist_enthalten = 1; /* Falls str2 am Ende dann gefunden */
};
}
/* Falls str1 nicht abgearbeitet dann Suche fortsetzen */
while (*str1 && ! ist_enthalten);
return ist_enthalten;
}
127
12.1.5. Einlesen von 10 Buchstaben ohne Mehrfachnennungen
/* Projektname : einlesen_zehn_buchstaben
Filename : readtenchar.c
Autor : max
Datum : 5.jul.94
Beschreibung : Es werden vom Terminal 10 Buchstaben eingelesen, wobei
mehrfachnennungen abgelehnt werden */
#include <stdio.h>
#define NUM_OF_CHAR 10
/* Globale Variablen */
/* Funktionsprototypen */
int ist_in_liste(char liste[],int Anzahl_der_bisherigen_Listenelemente,char
Buchstabe);
char Einlesen(void);
/********************** main ***********************************/
main()
{
char liste[NUM_OF_CHAR];
int Anzahl_der_bisherigen_Listenelemente;
int i;
char neuer_buchstabe;
puts(" Hallo hier ist das Programm readtenchar");
puts(" Lese 10 verschiedene Buchstaben von Terminal und gebe sie wieder aus");
Anzahl_der_bisherigen_Listenelemente=0;
do
{
neuer_buchstabe=Einlesen();
if (! ist_in_liste(liste,Anzahl_der_bisherigen_Listenelemente,neuer_buchstabe))
liste[Anzahl_der_bisherigen_Listenelemente++] = neuer_buchstabe;
else
puts(" Buchstabe ist doppelt, Bitte neue Eingabe ");
}
while (Anzahl_der_bisherigen_Listenelemente < NUM_OF_CHAR);
for (i=0;i<NUM_OF_CHAR;i++)
printf("Buchstabe Nummer %d = %c\n",i,liste[i]);
}
/********************** Funktionen ***********************************/
int ist_in_liste(char liste[],int Anzahl_der_bisherigen_Listenelemente,char
Buchstabe)
/*
Diese Funktion ueberprueft ob in der
liste[0]...liste[Anzahl_der_bisherigen_Listenelemente]
der Buchstabe vorhanden ist
Eingabe :
char liste[] ...
bisher eingelesene Buchstaben
int Anzahl_der_bisherigen_Listenelemente ... Anzahl der bisher eingelesenen
Elemente
char Buchstabe
... zu testender Buchstabe, ob in liste
Ausgabe :
return - value = 1, falls Buchstabe in der Liste ist
return - value = 0, sonst
*/
{
int gefunden,i;
gefunden = 0;
for (i=0;i< Anzahl_der_bisherigen_Listenelemente;i++)
if (liste[i] == Buchstabe) gefunden = 1;
128
return gefunden;
}
char Einlesen(void)
/*
Liest einen Alpha - Character vom Terminal ein
Ausgabe :
return - value : eingelesener Buchstabe
*/
{
char Buchstabe;
do
{
printf(" Bitte naechsten Buchstaben eingeben ");
fflush(stdin);
Buchstabe = getchar();
if (! isalpha(Buchstabe))
puts(" Bitte eine Buchstaben und keinen Numerischen Wert eingeben");
}
while (! isalpha(Buchstabe));
return Buchstabe;
}
129
12.1.6. Zählt Sätze in der Eingabe
/***************************
filename : sentence.c
autor : max
Version : 1.0
Datum : jun 94
Thema : programm zum zählen von eingegebenen Sätzen
***************************/
#include <stdio.h>
void check_sent(char ch); /* funktions declaration (=prototyp) */
int num_of_sentence = 0;
/* globale Variable */
/* ======================================================== */
main_sentence()
{
char ch;
puts(" Bitte geben Sie beleibige Saetze ein, Ende der Eingabe ^D ");
while ( (ch=getchar() ) != EOF)
{
check_sent(ch);
};
printf(" Die Anzahl der gelesenen Saetze ist %d \n",num_of_sentence);
}
/* ======================================================== */
void check_sent(char ch) /* funktions definition */
{
}
if ((ch == '.') || (ch == '!') || (ch == '?'))
num_of_sentence ++;
130
12.2. Sortiert zehn Zahlen
/* Projectname : sort_zehn_zahlen
Filename : sort_zehn_zahlen.c
Autor : max
Datum : 17.jul.94
Beschreibung : Es werden vom Terminal 10 Zahlen eingelesen, wobei
Mehrfachnennungen abgelehnt werden, dann werden diese zehn Zahlen
numerisch sortiert */
#include <stdio.h>
#define NUM_OF_ZAHLEN 10
/* Globale Variablen */
/* Funktionsprototypen */
int
int
int
int
ist_in_liste(int liste[],int Anzahl_der_bisherigen_Listenelemente,int zahl);
find_index_of_min(int liste[],int Anzahl_der_Elemente,int referenz_wert);
swap(int *i,int *j);
my_sort(int liste[]);
int Einlesen(void);
/********************** main ***********************************/
main()
{
int liste[NUM_OF_ZAHLEN];
int Anzahl_der_bisherigen_Listenelemente;
int i;
int neue_zahl;
puts(" Hallo hier ist die Routine readtennumbers");
puts(" Lese 10 verschiedene Zahlen von Terminal und gebe sie wieder aus");
Anzahl_der_bisherigen_Listenelemente=0;
do
{
neue_zahl=Einlesen();
if (! ist_in_liste(liste,Anzahl_der_bisherigen_Listenelemente,neue_zahl))
liste[Anzahl_der_bisherigen_Listenelemente++] = neue_zahl;
else
puts(" Zahl ist doppelt, Bitte neue Eingabe ");
}
while (Anzahl_der_bisherigen_Listenelemente < NUM_OF_ZAHLEN);
for (i=0;i<NUM_OF_ZAHLEN;i++)
printf("Zahl Nummer %d = %d\n",i,liste[i]);
my_sort(liste);
puts(" Sortiertes Array ");
for (i=0;i<NUM_OF_ZAHLEN;i++)
printf("Zahl Nummer %d = %d\n",i,liste[i]);
}
/********************** Funktionen ***********************************/
int ist_in_liste(int liste[],int Anzahl_der_bisherigen_Listenelemente,int zahl)
/*
Diese Funktion ueberprueft ob in der
liste[0]...liste[Anzahl_der_bisherigen_Listenelemente]
die Zahl vorhanden ist
Eingabe :
int liste[] ...
bisher eingelesene Zahlen
int Anzahl_der_bisherigen_Listenelemente ... Anzahl der bisher eingelesenen
Elemente
int zahl
... zu testende Zahl, ob in liste
Ausgabe :
return - value = 1, falls Zahl in der Liste
return - value = 0, sonst
131
*/
{
int gefunden,i;
gefunden = 0;
for (i=0;i< Anzahl_der_bisherigen_Listenelemente;i++)
if (liste[i] == zahl) gefunden = 1;
return gefunden;
}
int Einlesen(void)
/*
Liest eineganze Zahl vom Terminal ein
Ausgabe :
return - value : eingelesene Zahl
*/
{
int zahl;
printf(" Bitte naechste Zahl eingeben ");
fflush(stdin);
scanf("%d",&zahl);
return zahl;
}
/*********************************************/
int find_index_of_min(int liste[],int Anzahl_der_Elemente,int referenz_wert)
/*
Eingabe :
int liste[] ... Liste aller Zahlen
int Anzahl_der_Elemente ... Anzahl Elemente von liste
int referenz_wert ...
Element muss kleiner sein als referenz_wert
Ausgabe :
return value .... Index des kleinsten Elements innerhalb von liste
*/
{
int i;
int index_kleinstes_element=0;
for (i=0;i<Anzahl_der_Elemente;i++)
if (liste[i] < referenz_wert)
{
index_kleinstes_element = i;
referenz_wert = liste[i];
}
return
index_kleinstes_element;
}
/*********************************************/
int swap(int *i,int *j)
/* Vertauscht die Werte unter den Adressen i und j abgespeichert sind
{
int help;
help = *i;
*i = *j;
*j = help;
return;
}
/*********************************************/
int my_sort(int liste[])
/*
Eingabe :
int liste[] ...
gespeicherte Zahlen
*/
{
int kleinstes_element,i,j,Anzahl_der_Elemente;
Anzahl_der_Elemente = NUM_OF_ZAHLEN;
132
*/
for (i=0; i< NUM_OF_ZAHLEN; i++)
{
kleinstes_element =
find_index_of_min(&liste[i],Anzahl_der_Elemente,liste[i]);
swap(&liste[i],&liste[kleinstes_element+i]);
Anzahl_der_Elemente -- ;
}
}
133
12.3. FileIO : Zählt Wörter in einem String
/* count number of words in a string */
#include <ctype.h>
char zeile[132] = {0};
int wrd_count(char *s);
main_wrd_count()
{
int i,wrd=0;
char filename[132];
FILE *input;
}
puts(" Eingabe Filename ");
gets(filename);
input=fopen(filename,"r");
while ((fgets(zeile,132,input) != EOF) && (wrd <= 100))
{
puts(" nŠchste Zeile ");
wrd =+ wrd_count(zeile);
for (i=0;zeile[i];i++) zeile[i]=0;
};
printf(" Die Anzahl der Wšrter ist : %d",wrd);
int wrd_count(char *s)
{
int cnt = 0;
while (*s != '\0')
{
while (isspace(*s)) ++s;
/* skip white space */
if (*s != '\0')
{
++cnt;
while (!(isspace(*s) && *s != '\0' ) ++s; /*skip the word */
}
}
return cnt;
}
134
12.4. Erzeugen Sie mit dem Editor eine beliebige Textdatei und schreiben
Sie ein Programm, das eine solche Datei (zeilenweise) rückwärts auf
den Bildschirm ausgibt.
1
2
3
4
5
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*********************************************************************
* Lösung zu Kapitel File I/O
*********************************************************************/
#include <stdio.h>
#define CLEARSCREEN
main ()
{
FILE
char
char
int
printf ("\033[2J\033[3;1H")
*input;
datnam[80];
*file_in_array;
offset;
/******************************************************************
* Erfragen und Öffnen der Eingabedatei
******************************************************************/
printf ("Eingabedatei: ");
scanf ("%s", datnam);
if ((input = fopen(datnam,"r")) == NULL)
{
perror ("Fehler beim ÷ffnen der Datei");
exit (1);
}
/******************************************************************
* Positionieren auf das Dateiende und Ermitteln des Abstands
* vom Dateianfang. Zurücksetzen auf den Anfang der Datei.
******************************************************************/
fseek (input, 0, SEEK_END);
offset = ftell (input);
rewind (input);
/******************************************************************
* Einlesen der gesamten Datei in den Arbeitsspeicher.
******************************************************************/
file_in_array = (char *) malloc (offset);
fread (file_in_array, offset-1, 1, input);
/******************************************************************
* Durchsuchen der im Arbeitsspeicher befindlichen Datei auf
* '\n' vom Ende her und Ausgabe der isolierten Strings.
******************************************************************/
}
file_in_array += offset;
CLEARSCREEN;
while (offset >= 0)
{
while (*file_in_array != '\n' && offset >= 0)
{
file_in_array--;
offset--;
}
*file_in_array = '\0';
printf ("%s\n", file_in_array + 1);
}
printf ("\n\n");
135

Documentos relacionados