Inhalt des Kurses zurück weiter

C Kurs Tag 5: Felder und Adressen

Heute benutzen wir die Include-Datei 'stdlib.h' in ihr wird die Fuktion 'exit' deklariert.

void exit( int status );

Diese Funktion beendet das Progamm mit einem Fehlercode (status). Sie macht reinen Tisch mit den System-Resorcen, und ist in jeder Tiefe von Unterprogrammen anwendbar. Wir benutzen sie um bei einem schwerwiegendem Fehler das Programm kontrolliert abzubrechen.

Die Division vom letzten mal, stecken wir nun in ein eigenes Unterprogramm mit drei Parametern. Neu ist, daß wir die Gültigkeit aller Parameter prüfen. Dabei wird im Fehlerfall eine Klartext-Meldung ausgeben.

Die Berechnung des Restes haben wir mit dem Modulo Operator optimiert. 'a % b' (sprich: a modulo b).

rest = zaehler % nenner;

In der Schleife kürzen wir den Ausdruck: 'i = i + 1' zu 'i ++'. Dieser Operator heißt Increment. Sein Gegenstück das Decremet wird durch '--' dargestellt. Beide Operatoren können vor oder nach dem Ausdruck stehen. Dazu ein Beispiel:

a = 4; /* a = 4 */

b = -- a; /* a = 3, b = 3 */

c = a ++; /* a = 4, c = 3 */

d = a; /* a = 4 */

Wird der Operator vorangestellt so wird er vor der Zeile berechnet.

Wird der Operator nachgestellt so wird er nach der Zeile berechnet.

Mehrfache Operatoren auf die gleiche Varaible innerhalb der gleichen Zeile sind nicht portabel!

Es gibt in C keine echten Konstanten, dafür wir ein MACRO verwendet. ein MACRO begint mit '#define' gefolgt von einem Namen. Dahinter schreibt man den Wert. Der Compliler ersetzt alle Vorkommen des Names und setzt dort den angegebenen Text (unser Konstante) ein. Diese Konstante hat keinen definierten Typ!

#define MAX_ZEICHEN 80

Ein Macro kann auch Argumente haben. In diesem Falle sollte man darauf achten, daß die Argumente dadurch auch mehrmals bewertet werden können. Hier ist immer eine potenzielle Fehlerquelle.

#define MAXIMUM(a,b) (( a > b ) ? a : b )

ergebnis = MAXIMUM( eins, ++zwei );

Hier wird der Ausdruck '++zwei' zwei mal bewertet. Das erste mal vor dem Vergleich und das zweite mal bei der Zuweisung zum Ergebnis.

Hier ein Beispiel beim Aufruf:

c = MAXIMUM( i, j );

dies enspricht folgendem Programmteil:

if ( a > b ) {

c = a;

} else {

c = b;

}

Ausdrücke innerhalb des Macros sollte man immer klammern.

Anderenfalls kann der Ausdruck durch einen Operator mit hoher

Priorität ausserhalb des Macros falsch berechnet werden. z.B.

#define SUMME(a,b) a + b

ergebnis = SUMME( eins, zwei ) * 4;

Da hier das Macro keine Klammer hat, berechnet der Compiler hier '( zwei * 4 ) + eins ', dies ist nich das erwartete Ergebnis. Richtig wird es erst mit folgender Definition:

#define SUMME(a,b) ( a + b )

Es gibt allerdings auch konstante Speicherbereiche. Der Compiler kann Schreibzugriffe darauf erkennen. Zur Definition verwendet man das Attribut 'const'. Damit definierte Strukturen dürfen nicht verändert werden. Ein Zeiger mit diesem Attribut kann nicht zum Schreiben verwendet werden.

Das Attribut 'static' hat zwei Bedeutungen. Bei Funktionen und ausserhalb der Funtionen definierten Variablen sind diese nicht mehr ausserhalb des Moduls bekannt. Innerhalb von Funktionen wird ein fester Speicherbereich benutzt, dessen Inhalt beim nächsten Funktionsaufruf erhalten bleibt. Damit gibt es allerdings Probleme wenn die Funktion gleichzeitig aufgerufen wird. Viele Funktionen liefern einen Zeiger auf ihr Ergebins zurück. Das Ergebnis muß innerhalb der Funktion mit dem Attribut 'static' definiert sein, damit es nach Aufruf der Funktion nocht existiert. Ruft man die Funktion mehrfach auf so bleibt nur das letze Ergebnis erhalten.

Für Treiber und HW-Zugiffe gibt es das Attribut 'volatile'. Volatile bedeutet flüchtig, der Inhalt einer Addresse ist daher nicht mehrfach zu verwenden. Variablen mit diesem Attribut werden nicht optimiert. Für jeden Zugriff im Source erzeugt der Compiler auch einen Zugriff auf die Speicheraddresse.

Wir haben ja die Text oder String-Konstanten bereits gehabt. Jezt brauchen wir aber eine String-Variable. Nun es gibt keinen extra Datentyp String, aber wir kommen auch so damit zurecht.

Zuerst brauchen wir ein Array, Feld oder Tabelle von Werten. Dazu schreibt man in C einfach eckige Klammern mit der Größe dazwischen. Wir definieren ein Feld mit 20 Elementen:

int feld[ 20 ];

Nun wollen wir die Felder benutzen, dazu wird der Index in die Klammern geschrieben, der kleinste Index ist 0 der größte 19

for ( i = 0; i < 20; i ++ )

feld[ i ] = 0;

Ist der Index fehlerhaft, d.h. ausserhalb der Grenzen so wird wild im Speicher zugegriffen. Das kann bis zu einem Systemfehler führen. Dieser Fehler ist einer der häufigsten in C. Hier muß der Programmierer sehr sorgfältig sein.

Doch nun zurück zum String (der Zeichenkette = Feld von Zeichen) ganz logisch ist ein String mit max 20 Zeichens so definiert:

char string[ 20 ];

Merke: Der Name eines Feldes ist der Zeiger auf das erste Element.

string == &( string[ 0 ] )

Erinnern wir uns an der Stern bei 'printf'. Dort ist die Zeichenkette als 'char *name' definiert. Genauso könnte man schreiben 'char name[]' Ein Zeiger enthält nichts anderes als die Nummer der Speicherzelle im Rechner. Wenn diese Nummer einer 0 entspricht so ist diese Adresse nicht gültig. Man spricht von einem NULL oder NIL-Zeiger.

Unser zweites Unterprogramm ist für den Dialog des Programms mit dem Benutzer. Es gibt einen Anforderungstext aus, und wartet auf einen String von der Standardeingabe (der Tastatur). Dazu nehmen wir die Funktion 'fgets'. Sie liest eine Zeichenkette aus einem Stream oder File. 'stdin' ist vordefiniert als Tastatur und kann vom Betriebssystem verändert werden.

status = fgets( eingabe, MAX_ZEICHEN - 1, stdin );

if ( status == NULL ) /* Fehler */

exit( 1 ); /* Programm beenden */

Wenn die Eingabe abgebrochen wird ( CTRL-Z oder CTRL-D ), so bekommt man einen NULL-Zeiger zurück. In diesem Fall darf man den Zeiger nicht weiterbenutzen. Wir brechen hier unser Programm ab.

sscanf( eingabe, "%ld", &zahl );

Diese Funktion ist das Gegenstück zu 'printf' hier wird aus einer Zeichkette Parameter gelesen. Der Formatstring enthält die gleichen Steuerkommandos.

Im neuen Hauptprogramm benutzen wir den &-Operator. Er liefert die Adresse einer Variablen. Diese wird dazu benutzt um die Variable des Hauptprogramms im Unterprogramm zu verändern ( call by reference ).

Die Parameter der Funktion 'rechne' übergeben nur ihren Wert. Sie können durch das Unterprogramm nicht verändert werden ( call by value ).

Nun das fertige Porogramm

Download der Datei "division2.c"


/*

DIVISION.C

Version 2

02.02.92

*/

#include <stdio.h>

#include <stdlib.h>

/* Unterprogramm zur Berechnung */

void rechne( long zaehler, long nenner, long stellen )

{ /* Ganzahlige Division auf belibig viele Stellen genau */

long quotient; /* ganzzahliger Anteil */

long ziffer; /* aktuelle Dezimalstelle */

long rest; /* aktueller Rest */

long i; /* Laufvariable der Schleife */

/* pruefe ob Zaehler erlaubt */

if ( zaehler < 0 ) {

printf( " Fehler: Zaehler ist negativ.\n" );

printf( " Bitte nur mit posiven Zaehlern rechnen.\n" );

return;

};

/* pruefe ob Division durch 0 */

if ( nenner == 0 ) {

printf( " Fehler: Nenner ist gleich Null.\n" );

printf( " Berechnung ist nicht sinnvoll.\n" );

return;

};

/* pruefe ob Stellen erlaubt */

if ( stellen < 0 ) {

printf( " Fehler: Anzahl der Stellen zu klein.\n" );

printf( " Bitte nur positive Anzahlen verwenden.\n" );

return;

};

/* hier eine ganzahlige Division, Ergebnis: Vorkommastellen */

quotient = zaehler / nenner;

/* hier wird der ganzzahlige Rest ermittelt */

/* OPTIMIERT, das Prozentzeichen steht fuer Modulo */

rest = zaehler % nenner;

/* Ausgabe des Vorkommaanteils */

printf( " %ld / %ld = %ld,", zaehler, nenner, quotient );

/* Schleife mit i bis Anzahl der Stellen erreicht */

/* OPTIMIERT, i = i + 1 wird zu i ++ */

for ( i = 1; i <= stellen ; i ++ )

{

/* Rest mal 10 fuer neue Division, naechste Ziffer */

rest = rest * 10;

/* Ziffer ist gesuchte Nachkommastelle */

ziffer = rest / nenner;

/* anhaengen der Ziffer an die Ausgabe */

printf( "%ld", ziffer );

/* neuer Rest, (OPTIMIERT) */

rest = rest % nenner;

}

/* Ende der Schleife */

/* Hier gehts weiter, wenn die Schleife fertig ist */

/* und Zeilenende ausgeben */

printf( "\n" );

/* und Ende! */

return;

}

#define MAX_ZEICHEN 80

void lese_eine_zahl(

const char *text, /* Ausgabetext */

long *zahl /* Adresse der Variablen */

)

{

char eingabe[ MAX_ZEICHEN ]; /* Platz fuer Eingabezeile */

char *status; /* Zeiger auf Eingabe */

printf( text );

status = fgets( eingabe, MAX_ZEICHEN - 1, stdin );

if ( status == NULL ) /* Fehler */

exit( 1 ); /* Programm beenden */

sscanf( eingabe, "%ld", zahl ); /* wie atod() */

printf( "\n" );

}

int main ( void )

{

/* lokale Varibalen, nur hier bekannt */

long ein_zaehler; /* Zaehler des Bruches */

long ein_nenner; /* Nenner des Bruches */

long ein_stellen; /* Anzahl der Nachkommastellen */

/* Voreinstellung der einzugebenden Werte */

ein_zaehler = 1;

ein_nenner = 1;

ein_stellen = 60;

lese_eine_zahl(

"\n Bitte den Zaehler eingeben ( 1 ): ",

&ein_zaehler );

lese_eine_zahl(

"\n Bitte den Nenner eingeben ( 1 ): ",

&ein_nenner );

lese_eine_zahl(

"\n Bitte die Nachkommastellen eingeben ( 60 ): ",

&ein_stellen );

/* Ganzahlige Division auf belibig viele Stellen genau */

rechne( ein_zaehler, ein_nenner, ein_stellen );

/* und Ende! */

return 0;

}

/* ENDE DER DATEI */