vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Heute ist es soweit: die dritte Woche C-Programmierung unter Linux liegt hinter Ihnen. Begonnen haben Sie die Woche mit so fortgeschrittenen Themen wie Dateien und Textstrings. In der Mitte der Woche haben Sie einige der vielen Funktionen aus der Standard-C-Bibliothek kennen gelernt und danach einige eher Unix-spezifische Funktionen für die Prozess- und Signalverarbeitung. Abschluss der Woche bildete die Auseinandersetzung mit den Feinheiten rund um den C-Compiler und den komplexen Problemen der Programmierung grafischer Benutzerschnittstellen. Im folgenden Programm finden sich viele dieser Themen wieder.

1 : /* Programmname:  woche3.c                                   */
2 : /* Programm, das Namen und Telefonnummern verwaltet          */
3 : /* Die Informationen werden in eine Datei geschrieben, die   */
4 : /* mit einem Befehlszeilenparameter angegeben wird           */
5 : 
6 : #include <stdlib.h>
7 : #include <stdio.h>
8 : #include <time.h>
9 : #include <string.h>
10: 
11: /*** definierte Konstanten ***/
12: #define JA   1
13: #define NEIN    0
14: #define REC_LAENGE  54
15: 
16: #define NAME_LAEN   24
17: #define PHONE_LAEN  11
18: 
19: /*** Variablen ***/
20: 
21: struct datensatz {
22:    char vname[NAME_LAEN];              /* Vorname + NULL       */
23:    char nname[NAME_LAEN];              /* Nachname + NULL      */
24:    char mname[NAME_LAEN];              /* Mittelname + NULL    */
25:    char telefon[PHONE_LAEN];           /* Telefonnummer + NULL */
26: } rec;
27: 
28: /*** Funktionsprototypen ***/
29: 
30: void verwendung_anzeigen(char *dateiname);
31: int  menu_anzeigen(void);
32: void daten_einlesen(FILE *fp, char *progname, char *dateiname);
33: void bericht_anzeigen(FILE *fp);
34: int  fortfahren_funktion(void);
35: int  adr_suchen( FILE *fp );
36: void mein_gets(char*str, int len);
37: 
38: /* Beginn des Programms */
39: 
40: int main(int argc, char *argv[])
41: {
42:     FILE *fp;
43:     int  cont = JA;
44: 
45:     if( argc < 2 )
46:     {
47:        verwendung_anzeigen(argv[0]);
48:        return 1;
49:     }
50: 
51:     /* Datei öffnen. */
52:     if ((fp = fopen( argv[1], "a+")) == NULL)
53:     {
54:         fprintf( stderr, "%s(%d)Fehler beim Öffnen der Datei %s",
55:                              argv[0],__LINE__, argv[1]);
56:         return 1;
57:     }
58: 
59:     while( cont == JA )
60:     {
61:        switch( menu_anzeigen() )
62:        {
63:          case '1': daten_einlesen(fp, argv[0], argv[1]); /* Tag 18 */
64:                    break;
65:          case '2': bericht_anzeigen(fp);
66:                    break;
67:          case '3': adr_suchen(fp);
68:                    break;
69:          case '4': printf("\n\nAuf Wiedersehen!\n");
70:                    cont = NEIN;
71:                    break;
72:          default:  printf("\n\nUngültige Option, 1 bis 4 wählen!");
73:                    break;
74:        }
75:     }
76:     fclose(fp);       /* Datei schließen */
77:     return 0;
78: }
79: 
80: /* menu_anzeigen() */
81: 
82: int menu_anzeigen(void)
83: {
84:     char ch, puf[20];
85: 
86:     printf( "\n");
87:     printf( "\n     MENU");
88:     printf( "\n   ========\n");
89:     printf( "\n1.  Namen eingeben");
90:     printf( "\n2.  Bericht ausgeben");
91:     printf( "\n3.  Name suchen");
92:     printf( "\n4.  Ende");
93:     printf( "\n\nAuswahl eingeben  ==> ");
94:     mein_gets(puf, 20);
95:     ch = *puf;
96:     return ch;
97: }
98: 
99: /****************************************************
100:   Funktion:  daten_einlesen()
101: *****************************************************/
102: 
103: void daten_einlesen(FILE *fp, char *progname, char *dateiname)
104: {
105:    int cont = JA;
106: 
107:    while( cont == JA )
108:    {
109:       printf("\n\nBitte geben Sie die Daten ein: " );
110: 
111:       printf("\n\nGeben Sie den Vornamen ein: ");
112:       mein_gets(rec.vname, NAME_LAEN);
113:       printf("\nGeben Sie den zweiten Vornamen ein: ");
114:       mein_gets(rec.mname, NAME_LAEN);
115:       printf("\nGeben Sie den Nachnamen ein: ");
116:       mein_gets(rec.nname, NAME_LAEN);
117:       printf("\nGeben Sie die Telefonnr im Format 1234-56789 ein: ");
118:       mein_gets(rec.telefon, PHONE_LAEN);
119: 
120:       if (fseek( fp, 0, SEEK_END ) == 0)
121:          if( fwrite(&rec, 1, sizeof(rec), fp) != sizeof(rec))
122:          {
123:          fprintf(stderr,"%s(%d) Fehler beim Schreiben in die Datei %s",
124:                           progname,__LINE__, dateiname);
125:          exit(2);
126:          }
127:       cont = fortfahren_funktion();
128:    }
129: }
130: 
131: /********************************************************
132: Funktion:  bericht_anzeigen()                             
133: Zweck:     Die Namen und Telefonnummern der Personen in
134:            der Datei formatiert ausgeben.                      
135: *********************************************************/
136: 
137: void bericht_anzeigen(FILE *fp)
138: {
139:     time_t btime;
140:     int anz_an_dats = 0;
141: 
142:     time(&btime);
143: 
144:     fprintf(stdout, "\n\nLaufzeit: %s", ctime( &btime));
145:     fprintf(stdout, "\nListe der Telefonnummern\n");
146: 
147:     if(fseek( fp, 0, SEEK_SET ) == 0)
148:     {
149:        fread(&rec, 1, sizeof(rec), fp);
150:        while(!feof(fp))
151:        {
152:           fprintf(stdout,"\n\t%s, %s %c %s", rec.nname,
153:                                   rec.vname, rec.mname[0], 
154:                                   rec.telefon);
155:           anz_an_dats++;
156:           fread(&rec, 1, sizeof(rec), fp);
157:        }
158:        fprintf(stdout, "\n\nGesamtzahl der Datensätze: %d",  
159:                anz_an_dats);
160:        fprintf(stdout, "\n\n* * * Ende des Berichts * * *");
161:     }
162:     else
163:        fprintf( stderr, "\n\n*** FEHLER IM BERICHT ***\n");
164: }
165: 
166: /************************************************** 
167: *  Funktion:  fortfahren_funktion()
168: **************************************************/
169: 
170: int fortfahren_funktion( void )
171: {
172:     char ch, puf[20];
173:     do
174:     {
175:      printf("\n\nMöchten Sie fortfahren? (J)a/(N)ein ");
176:         mein_gets(puf, 20);
177:         ch = *puf;
178:     } while( strchr( "NnJj", ch) == NULL );
179: 
180:     if(ch == 'n' || ch == 'N')
181:         return NEIN;
182:     else
183:         return JA;
184: }
185: 
186: /**********************************************************
187: *  Funktion:  verwendung_anzeigen()
188: ***********************************************************/
189: 
190: void verwendung_anzeigen( char *dateiname )
191: {
192:     char *cptr ;
193:     
194:     cptr = strrchr(dateiname, '/');
195:     if (cptr == NULL)
196:         cptr = dateiname;
197:     else
198:         cptr++;
199:         
200:     printf("\n\nVERWENDUNG: %s dateiname", cptr );
201:     printf("\n\n wobei dateiname eine Datei ist, in der Namen und");
202:     printf("\n Telefonnummer der Personen gespeichert werden.\n\n");
203: }
204: 
205: /************************************************
206: *  Funktion:  adr_suchen()
207: *  Rückgabe:  Anzahl der übereinstimmenden Namen
208: *************************************************/
209: 
210: int adr_suchen( FILE *fp )
211: {
212:     char tmp_nname[NAME_LAEN];
213:     int  ctr = 0;
214: 
215:     fprintf(stdout,"\n\nGeben Sie den gesuchten Nachnamen ein: ");
216:     mein_gets(tmp_nname, NAME_LAEN);
217: 
218:     if( strlen(tmp_nname) != 0 )
219:     {
220:        if (fseek( fp, 0, SEEK_SET ) == 0)
221:        {
222:           fread(&rec, 1, sizeof(rec), fp);
223:           while( !feof(fp))
224:           {
225:              if( strcmp(rec.nname, tmp_nname) == 0 ) 
226:              /* bei Übereinstimmung */
227:              {
228:                 fprintf(stdout, "\n%s %s %s - %s", rec.vname, 
229:                                                rec.mname,
230:                                                rec.nname, 
231:                                                rec.telefon);
232:                 ctr++;
233:              }
234:              fread(&rec, 1, sizeof(rec), fp);
235:           }
236:        }
237:        fprintf( stdout, "\n\n%d Namen stimmen überein.", ctr );
238:     }
239:     else
240:     {
241:         fprintf( stdout, "\nEs wurde kein Name eingegeben." );
242:     }
243:     return ctr;
244: }
245: 
246: 
247: /*--------------------------------------------------------------------*
248:  *  Funktion:  mein_gets()                                            *
249:  *  Zweck:     Diese Funktion liest mit fgets() einen String von der  *
250:  *             Tastatur ein und löscht das Neue-Zeile-Zeichen am Ende.*
251:  *  Rückgabe:  Nichts                                                 *
252:  *--------------------------------------------------------------------*/
253: void mein_gets(char*str, int len)
254: {
255:     int index;
256: 
257:     fgets(str, len, stdin);
258: 
259:     for(index = 0; index < len; index++)
260:         if (str[index] == '\n')
261:         {
262:             str[index] = 0;
263:             return;
264:         }
265: }

In mancher Hinsicht ähnelt dieses Programm den Programmen aus den Rückblicken der ersten und zweiten Woche. Es werden zwar weniger Datenelemente verwaltet, dafür wurde das Programm um neue Optionen erweitert. Mit diesem Programm kann der Anwender die Namen und Telefonnummern von Freunden, Verwandten, Geschäftspartnern und so weiter verwalten. So wie das Programm geschrieben ist, verwaltet es nur den Vor- und Nachnamen sowie die Telefonnummer. Es sollte jedoch keine Schwierigkeiten bereiten, das Programm so auszubauen, dass weitere Informationen aufgenommen werden können (ich empfehle Ihnen das als kleine Übung). Der Hauptunterschied zwischen diesem Programm und den Programmen aus den vorangegangenen Wochenrückblicken liegt darin, dass es hinsichtlich der Anzahl der einzugebenden Personen keine Beschränkungen gibt. Diese Freiheit verdanken Sie der Tatsache, dass hier die Speicherung in einer Datei erfolgt.

Sie starten das Programm, indem Sie den Namen für die Datei in der Befehlszeile eingeben. Die main()-Funktion beginnt in Zeile 40 mit den Argumenten argc und argv, die dazu dienen, die Befehlszeilenparameter zu ermitteln. Wie das genau geht, haben Sie am Tag 20, »Compiler für Fortgeschrittene«, gesehen. Zeile 45 prüft den Wert von argc, um festzustellen, wie viele Parameter in der Befehlszeile eingegeben wurden. Wenn argc kleiner als 2 ist, wurde nur ein Parameter eingegeben (der Befehl, das Programm auszuführen), so dass klar ist, dass der Anwender keine Datei angegeben hat. In diesem Fall ruft das Programm die Funktion verwendung_anzeigen() mit argv[0] als Argument auf. argv[0] - der erste Parameter, der auf der Befehlszeile eingegeben wurde - ist der Name des Programms.

Die Funktion verwendung_anzeigen() finden Sie in den Zeilen 190 bis 203. Immer wenn Sie ein Programm schreiben, das Befehlszeilenargumente übernimmt, ist es ratsam, eine Funktion wie verwendung_anzeigen() vorzusehen, die dem Anwender aufzeigt, wie das Programm korrekt aufzurufen ist. Warum aber wird dabei der Name des Programms nicht im Programm selbst hartkodiert, anstatt ein Befehlszeilenargument zu verwenden? Die Antwort ist einfach. Wenn Sie den Programmnamen von der Befehlszeile erhalten, brauchen Sie sich keine Gedanken darüber zu machen, wenn der Anwender das Programm umbenennt, denn die Beschreibung des Programmaufrufs ist immer korrekt. Auch möchte ich Sie darauf hinweisen, dass die Funktion verwendung_anzeigen() jegliche Pfadinformation aus argv[0] entfernt. Wenn zum Beispiel das Programm vom aktuellen Verzeichnis aus mit dem Befehl ./woche3 namen.daten ausgeführt würde, würde die Funktion strrchr() in Zeile 194 das letzte '/'-Zeichen in dem String finden und den Rest als Namen verwenden. Wenn es kein '/'-Zeichen gibt, verwendet verwendung_anzeigen() den Programmnamen, wie er erhalten wurde.

Die meisten der neuen Konzepte in diesem Programm stammen vom Tag 15, »Mit Dateien arbeiten«. Zeile 42 deklariert eine Dateizeiger fp, der das ganze Programm hindurch verwendet wird, um auf die Datei zuzugreifen. Zeile 52 versucht, diese Datei im »+a«-Modus zu öffnen (zur Erinnerung, argv[1] ist als Name der Datei das zweite Element aus der Befehlszeile). Der »+a«-Modus wird verwendet, um die bestehende Datei nicht nur zu lesen, sondern auch etwas daran anzuhängen. Falls sich die Datei nicht öffnen lässt, wird in den Zeilen 54 und 55 eine Fehlermeldung ausgegeben, bevor das Programm in Zeile 56 beendet wird. Beachten Sie, dass die Fehlermeldung deskriptiven Charakter hat. So wird zum Beispiel mit __LINE__ (siehe Tag 20) angezeigt, in welcher Zeile der Fehler aufgetreten ist.

Wurde die Datei erfolgreich geöffnet, wird ein Menü angezeigt. Wenn der Anwender beschließt, das Programm zu verlassen, schließt Zeile 76 die Datei mit fclose(), bevor das Programm die Steuerung wieder an das Betriebssystem zurückgibt. Die anderen Menüoptionen erlauben es dem Anwender, einen Datensatz einzugeben, alle Datensätze auszugeben oder nach einer bestimmten Person zu suchen.

Die Funktion daten_einlesen() enthält ein paar bedeutende Änderungen. Die Zeile 103 enthält den Funktions-Header. Diese Funktion übernimmt drei Zeiger. Der erste ist der wichtigste: ein Handle auf die Datei, in die geschrieben werden soll. Die Zeilen 107 bis 128 enthalten eine while-Schleife, die so lange Daten einliest, wie der Anwender dies wünscht. Die Zeilen 109 bis 118 fordern die Daten im gleichen Format an wie im Programm des zweiten Wochenrückblicks. Zeile 120 ruft fseek() auf, um den Dateizeiger auf das Ende der Datei zu setzen, so dass neue Daten angehängt werden. Beachten Sie, dass das Programm nichts macht, wenn fseek fehlschlägt. Ein vollständiges Programm würde ein solches Fehlschlagen auffangen. Hier wurde aus Platzgründen darauf verzichtet. Zeile 121 schreibt die Daten mit Hilfe von fwrite() in die Datei.

Auch die Berichtfunktion wurde für diese Version des Programms überarbeitet. Ein Merkmal, das für die meisten »echten« Berichte typisch ist, ist die Ausgabe von Datum und Uhrzeit im Kopf des Berichts. Zeile 139 deklariert die Variable btime. Diese Variable wird der Funktion time() übergeben und dann mit der Funktion ctime() ausgegeben. Diese Zeitfunktionen wurden Ihnen am Tag 17, »Die Bibliothek der C- Funktionen«, vorgestellt.

Bevor das Programm damit beginnen kann, die Datensätze der Datei auszugeben, muss der Dateizeiger an den Anfang der Datei gesetzt werden. Dies geschieht in Zeile 147 mit einem Aufruf an fseek(). Nachdem der Dateizeiger an den Dateianfang gesetzt wurde, können die Datensätze einer nach dem anderen gelesen werden. Zeile 149 liest den ersten Datensatz. War das Programm damit erfolgreich, steigt das Programm in eine while-Schleife ein, die so lange fortgeführt wird, bis das Ende der Datei erreicht ist (wenn feof() einen Wert ungleich Null zurückliefert). Solange das Ende der Datei noch nicht erreicht wurde, werden in Zeile 152 die Daten ausgegeben, Zeile 155 zählt die Datensätze und Zeile 156 versucht, den nächsten Datensatz zu lesen. Ich möchte Sie darauf aufmerksam machen, dass diese Funktionen verwendet werden, ohne ihre Rückgabewerte zu prüfen, damit die Programmlänge innerhalb eines vertretbaren Rahmens bleibt. In der Regel sollten Sie jedoch das Programm vor Fehlern schützen und die Funktionsaufrufe mit einer Prüfung kombinieren, um sicherzugehen, dass keine Fehler aufgetreten sind.

Eine Funktion in diesem Programm ist neu. Die Zeilen 210 bis 244 enthalten die Funktion adr_suchen(), die alle Datensätze aus der Datei nach einem bestimmten Nachnamen durchsucht. Die Zeilen 215 und 216 fordern den Anwender auf, den zu suchenden Nachnamen einzugeben, und speichern ihn in einer lokalen Variablen namens tmp_nname. Wenn tmp_nname nicht leer ist (Zeile 218), wird der Dateizeiger auf den Anfang der Datei gesetzt. Danach werden die Datensätze gelesen. Mit strcmp() (Zeile 225) wird der Nachname des aktuellen Datensatzes mit tmp_nname verglichen. Wenn die Namen übereinstimmen, wird der Datensatz ausgegeben (Zeile 228 bis 231). So wird verfahren, bis das Ende der Datei erreicht ist. Auch hier wurde darauf verzichtet, die Rückgabewerte aller Funktionsaufrufe zu überprüfen. Sie jedoch sollten Ihre Rückgabewerte immer prüfen.

Sie sollten inzwischen in der Lage sein, dieses Programm so abzuändern, dass es Ihre eigenen Dateien erzeugt, in denen Sie jede beliebige Information speichern können. Mit den Funktionen, die Sie in Woche 3 kennen gelernt haben, und den anderen Funktionen der C-Bibliothek sollten Sie Programme schreiben können, die so gut wie jedes Problem bewältigen, das sich Ihnen stellt.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH