|
|
I-PROGRAMMIEREN1 WS 0203 - Vorlesung mit Übung
|
1. Eindimensionale Felder und while()
Das Thema Felder (engl. 'arrays') ist ziemlich umfangreich und wird später im Zusammenhang mit dem Datentyp Zeiger (engl. 'pointer') nochmals ausführlich aufgegriffen. Heute geht es darum, eine erste einfache Version von eindimensionalen Feldern einzuführen, mit denen man Felder von Zahlen oder Zeichen bzw. Zeichenketten darstellen kann.
Eine Feldvariable hat wie jeder andere Variable auch einen Namen (siehe im nachfolgenden Beispiel 'buchst') und eine Typangabe ('char buchst'). Zusätzlich hat eine Feldvariable gegenüber einer normalen Variablen auch noch eine Dimensionsanhabe, dargestellt durch ein Paar eckige Klammern '[]' ('char buchst[26]'). Im nachfolgenden Beispiel hat die Feldvariable 'buchst' 26 Elemente vom Typ char, d.h. der Compiler reserviert bei der Übersetzung des Quelltextes im Speicherbereich so viel Speicher, dass 26 Elemente vom Typ char (in der Regel pro char 1 Byte) abgespeichert werden.
Hätte man der Variablen 'buchst' statt dem Typ 'char' den Typ 'int' zugeordnet, dann hätte jedes der 26 Elemente den Typ 'int' und im Speicher würde für jedes Element statt nur 1 Byte (auf 32-Bit Rechnern) in der Regel 4 Bytes reserviert.
Man kann jedes Element einer Feldvariablen individuell addressieren, indem man die 'Nummer' bzw. den 'Index' des Elementes im Feld angibt. Unter Berücksichtigung der Tatsache, dass die Nummerierung von Feldelementen in C immer bei '0' beginnt, kann man also das erste Element von buchst addressieren durch 'buchst[0]', das zweite Element durch 'buchst[1]' usw.
Wenn man weiss, wie man die Elemente von Feldern addressieren kann, dann kann man diesen Elememnten individuell entweder Werte zuweisen
buchst[3] = 'a';
oder man kann die Werte solcher Elemente auslesen. Sei 'c' eine Variable vom typ char:
char c; c = buchst[3];
Aus dieser Sicht verhalten sich die Elemente von Feldvariablen wie alle anderen Variablen auch. Wichtig ist nur zu wissen, dass man die Dimension von Feldvariablen zu Beginn des Programms festlegen muss. Man kann sie nicht mehr ändern (wie man dennoch dynamische Feldvariablen erzeugen kann, das werden wir zu einem späteren Zeitpunkt kennenlernen).
Ein weiteres neues Sprachelement ist die while-Schleife. Mit dem Schlüsselwort 'while' kann man in C eine Schleife
erzeugen. Die Syntax lautet:
while(AUSDRUCK) ANWEISUNG
Falls ANWEISUNG aus mehreren Anweisungen besteht,
dann schreibt man der besseren Lesbarkeit halber gewöhnlich
{ Anweisung1, ..., Anweisungn
}
Wenn der AUSDRUCK 'wahr' (engl. 'true') ist, dann hat er einen Wert verschieden von 0, wenn der AUSDRUCK 'falsch'
(engl. 'false') ist, dann hat er einen Wert gleich 0!
Wenn es im nachfogenden Beispiel heisst
while( i< 26 ) { ... }
dann bedeutet dies, dass die Anweisungen in der while-Schleife solange ausgeführt werden, bis der Ausdruck 'i< 26' nicht mehr wahr ist, d.h. den Wert '0' hat. Dies ist dann der Fall, wenn die Variable 'i' als Wert den Wert '26' hat; '26' ist nicht kleiner wie '26'.
Natürlich muss man beim Programmieren darauf achten, dass die Programmausführung irgendwann auch wieder aus der while-Schleife 'heraus' kommen kann, ansonsten bleibt das programm 'in der Schleife stecken'. Im konkreten Beispiel muss also dafür gesorgt werden, dass der Wert der Variablen 'i' während der ausführung der Anweisungen der while-Schleife Schritt für Schritt erhöht wird.
Beispiel 1-dimensionale Variable für Buchstaben (übernommen aus den offenen Quellen zum Buch [HEROLD/ARNDT 2002]).
#include <stdio.h> int main(void) { char buchst[26]; int i, zahl; i=0; while( i<26) { /* Array mit Kleinbuchstaben belegen */ buchst[i] = 'a'+i; i++; } while (1) { printf("Geben Sie eine Zahl zw. 1 und 26 ein (Ende=Zahl 100): "); scanf("%d", &zahl); if (zahl==100) break; else if (zahl<1 || zahl>26) printf(" .......Falsche Eingabe (Eingabe wiederholen!)....\n"); else printf(" ---> %d. Kleinbuchstabe = %c\n\n", zahl, buchst[zahl-1]); } printf("------Programmende------\n"); return(0); }
Beispiel eines 1-dimensionalen Feldes für Integerzahlen (übernommen aus den offenen Quellen zum Buch [HEROLD/ARNDT 2002]).
#include <stdio.h> int main(void) { int zahl, basis, zaehler=0, i; int ziel[100]; while (1) { printf("Gib Basis des Zielsystem ein (2<=Basis<=10): "); scanf("%d", &basis); if (basis>=2 && basis<=10) break; } printf("Gib die zu wandelnde Zahl aus dem Zehnersystem ein: "); scanf("%d", &zahl); printf(" ---> %d(10) = ", zahl); while (zahl>0) { ziel[zaehler] = zahl % basis; zahl /= basis; ++zaehler; } for (i=zaehler-1 ; i>=0 ; i--) printf("%d", ziel[i]); printf("(%d)\n",basis); return(0); }
In den beiden Beispielen kommen Funktionen (Operatoren) vor, die bislang noch nicht eingeführt worden sind. Ausserdem kann man an diesen Beispielen sehen, dass es für C-Funktionen (C-Operatoren) drei verschiedene Schreibweisen gibt:
Präfixschreibweise: Funktionsname Argumente (z.B.: '++i' := 'Erhöhe den Wert von i um 1')
Infixschreibweise: Argument Funktionname Argument (z.B.: 'i < 26' := 'Wenn der Wert von i kleiner ist als 26')
Postfixschreibweise: Argumente Funktionsname (z.B.: 'i++' := 'Erhöhe den Wert von i um 1')
ARITHMETISCHER |
BEDEUTUNG |
+ |
Addition |
- |
Subtraktion |
* |
Multiplikation |
/ |
Division |
% |
Modulo-Operator |
ARITHMETISCHER |
BEDEUTUNG |
++ |
Erhöhung um 1 (Inkrement) |
-- |
Verminderung um 1 (Dekrement) |
ARITHMETISCHER |
BEDEUTUNG |
++ |
Erhöhung um 1 (Inkrement) |
-- |
Verminderung um 1 (Dekrement) |
Logische |
BEDEUTUNG |
&& |
Logisches UND (AND) (Sucht von links nach rechts das erste false. Wird keines gefunden, dann ist das logische UND true) |
|| |
Logisches ODER (OR) (Sucht von links nach rechts das erste true. Wird keines gefunden, dann ist das logische ODER false) |
Logische |
BEDEUTUNG |
! |
Logische NEGATION (NOT) |
ZUWEISUNGEN |
BEDEUTUNG |
SCHREIBWEISE |
= |
Einfache Wertzuweisung |
(z.B. a=5;) |
+= |
Addition |
"a op= b" entspricht "a = a op b" |
-= |
Subtraktion |
|
*= |
Multiplikation |
|
/= |
Division |
|
%= |
Modulo |
|
<<= |
Shift links |
|
>>= |
Shift rechts |
|
&= |
Bitwises UND |
|
^= |
Bitwises exklusiv ODER |
|
|= |
Bitwises ODER |
3. Tastaturabfragen mit fgets()
Neben der Funktion 'scanf()', die in der letzten Vorlesung vorgestellt worden ist, gibt es in C zahlreiche weitere Funktionen, um Eingaben von der Tastatur abzufragen. Eine davon ist fgets().
fgets(FELDVARIABLE, MAXIMALE_LÄNGE, STREAM)
Die Funktion 'fgets' liest aus einem Puffer, der die von der Tastatur gesendeten Zeichen sammelt, und speichert diese Zeichen in eine Feldvariab. Es werden aber nur MAXIMALE_LÄNGE-1-viele Zeichen aus dem Puffer entnommen. Automatisch wird ein Zeilenvorschub (engl. 'Linefeed') (Steuerzeichen '\n', ASCII-Code dezimal '10') angehängt.
Will man die in der Feldvariable abgespeicherten Zeichenkette nicht als einzelne Zeichenkonstanten behandeln, sondern als eine zusammenhängende 'Zeichenkette' (engl. 'string'), dann muss man hinter die abgespeicherten Zeichen eine '0' abspeichern ('0' als Zeichenkonstante wird geschrieben '\0'), d.h. im Falle von fgets(), dass man das Steuerzeichen '\n' durch '\0' ersetzen muss.
Eine einfache Methode, dies zu tun, ist die Verwendung einer der vielen Zeichenketten-Funktionen von C, die mit der headerfatei 'string.h' zur Verfügung gestellt werden. Eine dieser Funktionen ist die Funktion 'strlen()'. Sie liefert die Anzahl der Zeichen in einem Feld ohne die abschliessende '0'.
/*************************************************************** * * fgetsa.c * * author: gdh * date: oct-14, 2002 * * idea: einfaches Test-Beispiel für eine Tastaturabfrage mit fgets() * before: Keine * after: Keine * compilation: gcc -o fgetas fgetsa.c * usage: fgetsa * ***************************************************************/ #include <stdio.h> #include <string.h> int main(void) { char name[6]; int i; printf("Bitte Zeichenkette (max.6 Zeichen)\n"); while (1) { printf( "Zeichenkette: "); fgets(name, 6, stdin); printf("Laenge von name = %d\n",strlen(name)); i=0; while( i < strlen(name) ){ printf("Position %d : %c = %d \n", i, name[i], name[i]); i++; } name[ strlen(name)-1 ] = '\0'; /* \n am Ende entfernen */ if (strlen(name) == 0) break; printf("NACH ERSETZUNg\n"); i=0; while( i < strlen(name) ){ printf("Position %d : %c = %d \n", i, name[i], name[i]); i++; } printf("STRING: %s \n",name); } return(0); }
Gibt man weniger Zeichen ein, als maximal festgelegt, sind, dann wird automatisch ein '\n' angehängt und dieses wird bei der Längenbestimmung mittels strlen() auch mitgerechnet!
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./fgetsa Bitte Zeichenkette (max.6 Zeichen) Zeichenkette: abc Laenge von name = 4 Position 0 : a = 97 Position 1 : b = 98 Position 2 : c = 99 Position 3 : = 10 NACH ERSETZUNg Position 0 : a = 97 Position 1 : b = 98 Position 2 : c = 99 STRING: abc Zeichenkette:
Die Ersetzung des letzten Zeichens im String mittels strlen() funktioniert. Der Ausdruck
name[ strlen(name)-1 ] = '\0';
ist so zu lesen, dass dasjenige Element der Feldvariable 'name' vom Typ 'char' den Wert '\0' zugewiessen bekommen soll, das den Wert 'strlen(name)-1' hat. Der Wert von 'strlen(name)' ist die Anzahl der Zeichen, die von der Tastatur in die Feldvariable 'name' abgespeichert worden sind. Angenommen, dies wären '4'. Da die Feldvariablen in C ab '0' durchnummeriert werden, hätte das 4te-Element den Index '3', also '4-1'. Der Ausdruck 'name[ strlen(name)-1 ] = '\0' ' sorgt also dafür, dass genau an der letzten Stelle der abgespeicherten Zeichen
Bei der nächsten Eingabe sieht man, dass bei Überschreitung der maximalen Zahl von 6 Zeichen in der Eingabe nur 6-1 = 5 Zeichen eingelesen werden und das 6te Zeichen wird durch '\n' ersetzt. Allerdings zeigt die funktion strlen() in diesem Fall die Länge ohne (!) das Zeichen '\n' an. Das führt dazu, dass nicht das 6.Zeichen = '\n' ersetzt wird, sondern schon das vorletzte max-1-te zeichen 'e'. Zugleich sieht man aber auch, dass der 'Rest' des Puffers bei der nächsten Abfrage noch zur Verfügung steht und 'normal' bearbeitet wird. Man sollte also maximal MAXIMALE_LÄNGE-2-viele zeichen eingeben, um zeichenverluste zu vermeiden. Schliesslich sei bemerkt, dass es Aufgabe des Programmierers ist, dafür Sorge zu tragen, dass die Länge der Feldvariablen, in die hinein fgets() die eingelesenen Zeichen speichert, hinreichend gross ist.
Zeichenkette: abcdefgh Laenge von name = 5 Position 0 : a = 97 Position 1 : b = 98 Position 2 : c = 99 Position 3 : d = 100 Position 4 : e = 101 NACH ERSETZUNg Position 0 : a = 97 Position 1 : b = 98 Position 2 : c = 99 Position 3 : d = 100 STRING: abcd Zeichenkette: Laenge von name = 4 Position 0 : f = 102 Position 1 : g = 103 Position 2 : h = 104 Position 3 : = 10 NACH ERSETZUNg Position 0 : f = 102 Position 1 : g = 103 Position 2 : h = 104 STRING: fgh Zeichenkette:
Ein weiteres Beispiel mit fgets() und scanf() gemischt, leicht abgewandelt nach [HEROLD/ARNDT 2002].
/*************************************************************** * * fgets.c * * author: gdh * date: oct-14, 2002 * * idea: einfaches Beispiel für eine Tastaturabfrage mit fgets() und scanf() * before: Keine * after: Keine * compilation: gcc -o fgets fgets.c * usage: fgets * ***************************************************************/ #include <stdio.h> #include <string.h> int main(void) { char name[100], vorname[100], ort[100], plzz[7]; /* Daten */ int plz; /* */ printf("Gib deine Adressen ein (Ende mit Leerzeile fuer Name)\n"); while (1) { printf( "Name: "); fgets(name, 100, stdin); name[ strlen(name)-1 ] = '\0'; /* \n am Ende entfernen */ if (strlen(name) == 0) break; printf("Vorname: "); fgets(vorname, 100, stdin); vorname[ strlen(vorname)-1 ] = '\0'; /* \n am Ende entfernen */ printf( "Plz: "); scanf("%d", &plz); getchar(); printf( "Ort: "); fgets(ort, 100, stdin); ort[ strlen(ort)-1 ] = '\0'; /* \n am Ende entfernen */ printf( "%s,%s,%d,%s\n", name, vorname, plz, ort); } return(0); }
Die Kommunikation eines C-Programms mit der Aussenwelt geschieht über sogenannte Streams. Ein Stream ist eine Entität, die man zum Lesen und Schreiben öffnen kann. Ein Stream kann eine normale Datei sein; es kann aber auch z.B. ein Gerät sein. Einzelheiten über Streams werden behandelt, wenn das Schreiben und Lesen von Dateien besprochen wird. An dieser Stelle sei nur erklärt, dass bei Beginn eines C-programm drei Streams standardmässig geöffnet werden: stdout für den Bildschirm, stdin für die Tastatur und stderr für den Standard-Fehlerkanal. Die Funktion fgets() benutzt z.B. 'stdin' als Quelle für die Zeichen, die sie in eine Feldvariable speichert.
STREAM |
BESCHREIBUNG |
---|---|
stdout |
Standard-Ausgabe, Bildschrim |
stdin |
Standard-Eingabe, Tastatur |
stderr |
Standard-Fehler |
Der Standard-Fehlerkanal wir standardmässig auf dem Bildschirm ausgegeben; er kann aber auch z.B. auf eine Datei umgeleitet werden.
5. Tastaturabfragen mit getchar()
Mit der C-Funktion getchar() kann man die Zeichen aus dem Tastaturpuffer einzeln auslesen. Dies ist in manchen Fällen sehr hilfreich. Z.B. kann man getchar() dazu benutzen, um die eingestreuten Zeilenvorschub-Zeichen '\n' 'sichtbar' zu machen bzw. dies 'abzufangen'. Letztere Eigenschaft ist bisweilen im Zussammenspiel mit der funktion scanf() hilfreich. . Dazu die beiden folgenden Programme:
#include <stdio.h> int main(void) { int c; printf("Geben Sie etwas ueber die Tastatur ein (Ende= '#')\n"); c=getchar(); while(c != '#'){ printf("c = %d = %c \n",c,c); c=getchar(); } return(0); }
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./getchar Geben Sie etwas ueber die Tastatur ein (Ende= '#') a c = 97 = a c = 10 = b c = 98 = b c = 10 = c c = 99 = c c = 10 = ##include <stdio.h> int main(void) { int c; printf("Geben Sie etwas ueber die Tastatur ein (Ende= '#')\n"); c=getchar(); while(c != '#'){ printf("c = %d = %c \n",c,c); getchar(); c=getchar(); } return(0); }gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./getchar2 Geben Sie etwas ueber die Tastatur ein (Ende= '#') a c = 97 = a b c = 98 = b c c = 99 = c #
6. Eingaben über Befehlszeilen und Konvertierung von Strings
In C kann man beim Aufruf eines C-Programms diesem in der Befehlszeile zusätzliche Argumente übergeben. Diese werden als zeichenketten aufgefasst, die durch Leerzeichen getrennt sind. Sollen verschiedene Zeichenketten trotz trennender Leerzeichen als ein Argument aufgefasst werden, muss man diese in doppelte Anführungszeichen setzen (siehe Beispiel unten).
Will man einem C-Programm auf diese Weise Argumente übergeben, dann muss man die main()-Funktion mit zusätzlichen Parametern ausstatten:
int main(int argc, char *argv[])Man benötigt zwei Parameter, den ersten vom Typ 'int' und den zweiten vom Typ 'char' organisert als ein Feld von Zeichenketten. Standardmässig benutzt man für den ersten Parameter den Namen 'argc' zum Zählen der Argumente, und für den zweiten den Namen 'argv'. Man kann die Namen aber abändern. Wichtig ist nur zu wissen, dass das 0te-Argument immer der Name des Programms selbst ist. Der Parameter 'argc' hat also mindestens den Wert '1'.
#include <stdio.h> int main(int argc, char *argv[]) { int i; for (i=0; i<argc; i++) printf("Wort %d: %s\n", i, argv[i]); return(0); }gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./argtest aaa "bbb 111" ccc Wort 0: ./argtest Wort 1: aaa Wort 2: bbb 111 Wort 3: cccÜbergibt man als Argumente Zahlen in Form von Zeichenketten, wie z.B. '1999', dann muss man diese Zeichenketten im Programm erst in Zahlen konvertieren, bevor man mit diesen rechnen kann. Für diese Aufgabe hält C verschiedene Konvertierungsfunktionen mit der Headerdatei 'stdlib.h' bereit. Eine dieser Funktionen ist atoi(); sie konvertiert eine Zeichenkette im ASCII-Code in eine Integerzahl.
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; int zahl1, zahl2, zahl3; i=0; while (i<argc ){ printf("Wort %d: %s\n", i, argv[i]); i++; } zahl1=atoi(argv[1]); zahl2=atoi(argv[2]); zahl3= zahl1 + zahl2; printf("Arg1 %s = Zahl1 %d\n", argv[1], zahl1); printf("Arg2 %s = Zahl2 %d\n", argv[2], zahl2); printf("Zahl3 = zahl1 + zahl2: %d\n", zahl3); return(0); }gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./argtest2 12 34 Wort 0: ./argtest2 Wort 1: 12 Wort 2: 34 Arg1 12 = Zahl1 12 Arg2 34 = Zahl2 34 Zahl3 = zahl1 + zahl2: 46
7. Fallunterscheidungen mit switch()
Neben der Fallunterscheidung mit 'if()' gibt es auch die Fallunterscheidung mit 'switch()', die in der vorausgehenden Vorlesung schon vorgestellt worden ist. Im Folgenden ein Beispiel, das zeigt, dass man switch() dann sinnvoll einsetzen kann, wenn man eine Reihe von Entscheidungen 'auf der gleichen Ebene' fällen muss bzw. wenn man aus einer Reihe von Möglichkeiten eine auswählen muss. Typisches Beispiel wäre eine Menüauswahl. Im folgenden Beispiel werden auf einfache Weise Zahlen konvertiert (Dies Beispiel wurde wieder aus den offenen Quellen zum Buch [HEROLD/ARNDT 2002] übernommen):
/*******************************************************************/ /* Dieses Programm liest eine Hex-Ziffer ein und gibt */ /* die dieser Ziffer entsprechende */ /* - Dezimalzahl, */ /* - Oktalzahl und */ /* - Dualzahl am Bildschirm wieder aus */ /*******************************************************************/ #include <stdio.h> int main(void) { char hexa_ziffer; /*---- Eingabe einer Hexa-Ziffer in die char-Variable hexa_ziffer ------*/ printf("Geben Sie eine Hexaziffer ein !\n"); hexa_ziffer=getchar(); /*---- Ausgabe einer Ueberschrift und der eingegebenen Hexa-Ziffer -----*/ printf("\n\n\n%10s%10s%10s%10s\n","Hexa","Dezimal","Oktal","Dual"); printf("%10c",hexa_ziffer); /*---- Ausgabe der zugehoerigen Dezimal-, Oktal- und Dualzahl ----------*/ switch (hexa_ziffer) { case '0': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0000"); break; case '1': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0001"); break; case '2': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0010"); break; case '3': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0011"); break; case '4': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0100"); break; case '5': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0101"); break; case '6': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0110"); break; case '7': printf("%10c%10c%10s\n",hexa_ziffer,hexa_ziffer,"0111"); break; case '8': printf("%10c%10s%10s\n",hexa_ziffer,"10","1000"); break; case '9': printf("%10c%10s%10s\n",hexa_ziffer,"11","1001"); break; case 'a': case 'A': printf("%10s%10s%10s\n","10","12","1010"); break; case 'b': case 'B': printf("%10s%10s%10s\n","11","13","1011"); break; case 'c': case 'C': printf("%10s%10s%10s\n","12","14","1100"); break; case 'd': case 'D': printf("%10s%10s%10s\n","13","15","1101"); break; case 'e': case 'E': printf("%10s%10s%10s\n","14","16","1110"); break; case 'f': case 'F': printf("%10s%10s%10s\n","15","17","1111"); break; default: printf(" Das ist keine Hexa-Ziffer !\n"); break; } printf("\n\n\nAuf Wiedersehen !\n\n\n"); return(0); }gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR1/I-PROGR1-TH/VL3> ./hexziff1 Geben Sie eine Hexaziffer ein ! d Hexa Dezimal Oktal Dual d 13 15 1101 Auf Wiedersehen !Werden während der Übung gestellt.