1 III. Programmieren mit C III.1 Struktur eines C-Programmes 1.1. Beispiel und Grobstruktur /****************************************************************/ /* Programm bsp1.c */ /* Berechnung der Fl. eines Dreicks aus den 3 Seiten nach der */ /* Heronischen Formel */ /* Autor: A.B.Zeh Datum: 1.10.07 */ /****************************************************************/ /* Anweisungen für den Präprozessor */ #include <stdio.h> #include <math.h> /* wegen Ein- und Ausgabefktn. */ /* wegen Quadratwurzel */ /* Jetzt folgt die Funktion main (Hauptfunktion) */ main( ) { float a,b,c,s,rad,F; /* Block beginnt mit { */ /* Variablenvereinbarungen, Bezeichner wie beim Dreieck üblich */ /* Prompt für die Eingabe */ /* Eingabe von a */ /* dito für b */ printf("\nSeite a: "); scanf("%f",&a); printf("\nSeite b: "); scanf("%f",&b); printf("\nSeite c: "); /* dito für c */ scanf("%f",&c); s = 0.5*(a+b+c); /* Berechnung von s */ rad = s*(s-a)*(s-b)*(s-c); /* Berechnung von rad */ F = sqrt(rad); /* Quadratwurzel aus rad */ printf("\nFlächeninhalt = %8.2f",F); /* Ausgabe des Flächeninhaltes */ } /* schließende Klammer; Ende des Programmes */ • Kommandos für den Präprozessor des C-Compilers beginnen mit # • #include veranlaßt den Präprozessor, das dahinter genannte File einzulesen. Dieses ist Bestandteil der Standard-Bibliothek von C. Das File stdio.h (standard-input-output-header) enthält u.a. die hier benutzten Funktionen printf und scanf (zur Aus- bzw. Eingabe) Das File math.h enthält mathematische Funktionen, u.a. sqrt für die Quadratwurzel. • Jedes C-Programm enthält die „Funktion“ main, mit deren Ausführung die Abarbeitung des Programmes gestartet wird. • Alle Namen (Variable, Konstanten, ...) müssen in C vereinbart werden; im Beispiel werden die Variablen a, b, c, s, rad, F als vom Typ float vereinbart (Gleitpunktzahlen) • Dann folgen Aufrufe von printf und scanf, danach Wertzuweisungen (eigentlich Ausdrücke mit „Seiteneffekt“) und schließlich die Ausgabe des Flächeninhaltes. 2 1.2. Einfache Standard-Datentypen Ein Datentyp legt den Wertebereich fest, aus dem Variable dieses Typs Werte bekommen können. Datentypen werden durch Namen bezeichnet. Man kann Typen definieren. Es gibt aber einfache Datentypen, die standardmäßig zur Verfügung stehen: A. Ganze Zahlen: Es gibt folgende Typen Name min. mögl. Wert SCHAR_MIN ≤ -127 0 CHAR_MIN SHRT_MIN ≤ -32767 0 INT_MIN ≤ -32767 0 LONG_MIN ≤ -2 147 438 647 0 signed char unsignd char char [signed] short unsigned short [signed] int unsigned int [signed] long unsigned long max mögl. Wert SCHAR_MAX ≥ 127 UCHAR_MAX ≥ 255 CHAR_MAX SHRT_MAX ≥ 32767 USHRT_MAX ≥ 65535 INT_MAX ≥ 32767 UINT_MAX ≥ 65535 LONG_MAX ≥ 2 147 483 647 ULONG_MAX ≥ 4.294.967.295 Dabei ist char implementationsabhängig entweder signed char oder unsigned char. Die Konstanten dieser Typen werden i.Allg. wie üblich dezimal geschrieben; es gibt aber auch oktale Darstellung (Präfix 0 (Null)) oder hexadezimale Darstellung (Präfix 0X); auch kann man den Typ festlegen durch einen Suffix, z.B. ist 3701L vom Typ long. Der Typ char (Zeichen) ist also in C ein „integer“-Typ, weil intern (meist nach ASCII) so (in 1 Byte) gespeichert. Bei Ein- und Ausgabe wird entsprechend Formatangabe konvertiert. Konstanten vom Typ char werden in Hochkommas eingeschlossen, z.B. ‘a’ oder ‘B’. Die oben angegebenen Konstanten SCHAR_MIN,... stehen in limits.h. B. Gebrochene Zahlen (Gleitpunktzahlen) Es gibt die Typen float, double, long double float garantiert 6 signifikante Dezimalziffern u. einen Zahlbereich zwischen 1E-37 und 1E+37 double garantiert 10 signifikante Dezimalziffern u. einen Zahlbereich wie bei float Konstanten werden (wie üblich) mit einem Dezimalpunkt geschrieben oder mit einem E (e) und nachfolgendem Exponenten, z.B. 3.078 -7.2E-10 1.0e25 Konstanten sind standardmäßig vom Typ double. 1.0f wäre eine Konstante vom Typ float. 3 Achtung! C hat keinen Typ Boolean. Boolesche Ausdrücke, z.B. x < 1, erhalten einen integer-Wert; dabei steht der Wert 0 für false und jeder andere Wert für true. 1.3. Konstanten- und Variablenvereinbarung a) Eine Konstantenvereinbarung beginnt mit dem Schlüsselwort const , z.B. const float PI = 3.14; const int MAXZAHL = 1000; const char ANTWORT = ‘J’; Die Konstante PI ist dann vom Typ float und hat den Wert 3,14 usw. • Die Namen der Konstanten mit großen Buchstaben zu schreiben (und die der Variablen mit kleinen), ist übliche Konvention. • Die definierten Werte der Konstanten sind fest; sie können nicht vom Programm her (zur Laufzeit) geändert werden! • Vorteil: Bei einer notwendigen Änderung des Wertes einer Konstanten (bei Programmänderung) braucht die Änderung nur bei der Definition zu erfolgen. b) Eine Variable wird vereinbart durch Voranstellung des Typs, z.B. int anzahl; float radius, umfang; char antwort; Man kann auch einen Wert zuweisen, z.B.: int gesamt = 100; char antwort = ’J’; c) Namen Dinge werden mit Namen (Bezeichner, identifier) bezeichnet. Im Beispiel sind a, b, c , ... Namen. Namen werden nach folgender Syntax gebildet: <letter> ::= a | b | c | ... z | A | B | C | ... | Z | _ <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 <identifier> ::= <letter> {<letter> | <digit>} (Unterstreichungsstrich!) • C ist „case-sensitive“, d.h. z.B. ZAHL, zahl und Zahl sind verschiedene Bezeichner • Die maximale Länge eines Bezeichners ist 31 • Namen sollten nicht möglichst kurz gewählt werden, sondern der Name soll die Bedeutung des bezeichneten Dinges widerspiegeln, z.B. anzahl_der_birnen 4 • Es gibt Schlüsselworte; diese können nicht als Namen benutzt werden: char, const, double, enum, float, int, long, short, signed, sizeof, struct, typedef, union, unsigned, void, break, case, continue, default, do, else, for, if, goto, return, switch, while, auto, extern, register, static, volatile d) Kommentare Werden durch /* Programmen */ eingeschlossen. Wichtiges Hilfsmittel zur Erhöhung der Lesbarkeit von 1.4. Elementare Ein- und Ausgabe 1.4.1. Elementare Ausgabe (auf dem Bildschirm) Erfolgt durch Standardfunktion printf : printf(steuerstring, ausdr1, ... , ausdrn); Es wird der steuerstring ausgegeben! Die Werte der anderen Argumente (Ausdrücke) werden sukzessive ausgegeben, wenn in steuerstring Formatangaben eingebettet sind, z.B. printf(“Die Zahl %d ist eine Unglückszahl\n“, 13); erzeugt die Ausgabe Die Zahl 13 ist eine Unglückszahl Dabei ist \ (Backslash) das sog. Fluchtsymbol, das das nachfolgende Zeichen als Sonderzeichen definiert: \n steht für Zeilenschaltung (new line). Anderes Beispiel: printf(“\“Hallo!\“ sagte er.“); erzeugt die Ausgabe “Hallo!“ sagte er. Das obige Beispiel benutzt die Formatangabe %d für den Datentyp int. Man hat allgemein Format Datentypen %d %c %f, %e, %g %ld %s signed char, int, short char float, double long string-Typ 5 Man kann weitere Formatangaben machen: a) Die Weite des Ausgabefeldes - dies ist die Anzahl der mindestens ausgegebenen Zeichen Ausgabe erfolgt rechtsbündig, z.B. printf(“;%4d;%3d;%2d;\n“, 187, 187, 187); printf(“;%4s;%3s;%2s;\n“, “hi“, “hi“, “hi“); liefert ; 187;187;187; ; hi; hi;hi; Soll die Ausgabe linksbündig erfolgen, so ein - vor der Weite: printf(“;%8s;%-8s;\n“, “rechts“, “links“); liefert ; rechts;links ; b) Die Genauigkeit folgt einem Punkt. Z.B. ist %5.3s ein solches Format. Der Effekt hängt vom Format-Spezifikator ab: • bei %s ist die Genauigkeit die maximale Anzahl von zu druckenden Zeichen: printf(“;%2.4s;%2.4s;%2.4s; %2.4s; %2.4s;\n“, “a“, “ab“, “abc“ ,“abcd“, “abcde“); ergibt ; a;ab;abc;abcd;abcd; • bei ganzen Zahlen ist die Genauigkeit die minimale Anzahl der zu druckenden Ziffern: printf(“%.2d:%.2d\n“, 18, 6); ergibt 18:06 • bei reellen Zahlen ist die Genauigkeit für %f und %e die Anzahl der Nachkommastellen; für %g die Gesamtzahl der signifikanten Ziffern. %f druckt Darstellung mit Dezimalpunkt %e druckt Darstellung mit Exponent %g druckt „optimale“ Darstellung 6 Beispiel: radius = 1.116; Avogadro = 6.02e23 printf(“%f %6.3f %8.2f\n“, radius, -12.81963, Avogadro/1e19); printf(“%e %6.3e %8.2e\n“, radius, -12.81963, Avogadro/1e19); printf(“%g %6.3g %8.2g\n“, radius, -12.81963, Avogadro/1e19); liefert 1.116000 -12.820 60200.00 1.116000e+00 -1.282e+01 6.02e+04 1.116 -12.8 6e+04 (bei Borland C++) 1.4.2. Elementare Eingabe (über Tastatur) • grundsätzlich: Alle eingetippten Zeichen werden nach dem Drücken von in einem Eingabepuffer abgelegt und dann von Eingabefunktionen verarbeitet. • Zum Einlesen von Zahlen wird scanf benutzt: scanf(steuerstring, adr1, ..., adrn); scanf ist analog zu printf aufgebaut. In steuerstring sind Formatangaben enthalten, die festlegen, wie die eingelesenen Zeichen interpretiert werden. Dabei ist Datentyp Format int long float double char %d %ld %e, %f, %g %le,%lf,%lg %c Eingabe Dez.-zahl mit Vorzeichen Dez.-zahl mit Vorzeichen Gleitpunktzahl oder ganze Zahl Gleitpunktzahl oder ganze Zahl ein Zeichen adr1, ..., adrn sind Adressen von Variablen. Der Typ dieser Variablen muß entsprechend der Tabelle zur jeweiligen Formatangabe passen! Dann werden die eingetippten Werte diesen Variablen zugewiesen (an die entsprechenden Speicherstellen geschrieben). Z.B. bei int i, j; float x; char zei1; scanf(“%d %d %f %c“, &i, &j, &x, &zei1); Wird bei der Abarbeitung dann eingetippt: 10 20 3.145 abc 7 so erhalten die Variablen i, j, x, zei1 die Werte 10, 20, 3.145 bzw. ‘ ‘. Die Zeichen abc bleiben im Puffer! - & ist der Adreßoperator: &i ist die Adresse der Variablen i. & darf nicht vergessen werden - Zahleneingaben sind durch mindestens ein Leerzeichen zu trennen - Anzahl der Formatangaben und Anzahl der Adressen sollte übereinstimmen. • Die Funktion getchar() liest ein Zeichen und gibt es als Wert zurück. • Beim Einlesen mittels scanf treten bei falsch eingetippten Werten - etwa Buchstaben statt Ziffern - Fehler auf, die zum Absturz des Programmes führen können, z.B. wird bei scanf(“%d %d %d“, &i, &j, &k); mit Variablen i, j, k vom Typ int eingetippt 12 ABC 37 so erhält i den Wert 12. Dann soll ein Wert für j eingelesen werden. Da aber A nicht dazu paßt, macht scanf hier Schluß! ABC 37 bleibt im Puffer! Beim nächsten Leseversuch wird i.a. wieder ein Fehler auftreten usw. Lösung: Benutzung des von scanf zurückgegebenen Wertes um festzustellen, ob ein Fehler aufgetreten ist! scanf gibt als Wert die Anzahl der korrekt eingelesenen Werte zurück! Beseitigung des falschen Pufferinhaltes durch sukzessiven Aufruf von getchar! Im folgenden Beispiel wird das demonstriert, wobei schon while-Schleifen benutzt werden. 8 /***********************************************************************/ /* Programm input_check */ /* Demonstration von Input-Checking */ /***********************************************************************/ #include <stdio.h> main() { int anzahl_input, i, j, k; printf("Geben Sie 3 Werte ein: "); anzahl_input = scanf("%d %d %d", &i, &j, &k); while(anzahl_input < 3) { /* Fehler ist aufgetreten */ /* Puffer wird geleert, indem jedes Zeichen bis Zeilenende gelesen wird */ while(getchar() != '\n') { /* leerer Schleifenkörper */ } /* Jetzt Nachricht ausgeben */ printf("Sie haben nur %d Werte richtig eingegeben\n", anzahl_input); printf("Noch einmal: \n"); anzahl_input = scanf("%d %d %d", &i, &j, &k); } /* Jetzt war die Eingabe richtig */ printf("Ihre richtige Eingabe war %d, %d, %d\n", i, j, k); return 0; } Bemerkung: Der Typ char ist ein „integer“-Typ; intern sind Zeichen nach ASCII-Code gespeichert. Man kann die Umwandlung von Zeichen in ASCII-Code und umgekehrt durch Formatangaben erreichen, wie das folgende Beispiel zeigt: #include <stdio.h> main() { int i; char ch; printf("Ganze Zahl bitte: "); scanf("%d", &i); getchar(); printf("Zeichen bitte: "); scanf("%c", &ch); printf("Zu %d gehört das Zeichen %c \n", i, i); printf("Zum Zeichen %c gehört der ASCII-Code %d\n", ch, ch); } Ein Beispiel für die Abarbeitung des Programmes wäre: Ganze Zahl bitte: 65 Zeichen bitte: A Zu 65 gehört das Zeichen A Zum Zeichen A gehört der ASCII-Code 65 9 wobei die unterstrichenen Zeichen gerade die eingetippten Zeichen sind. 1.5. Ausdrücke 1.5.1 Arithmetische Ausdrücke Die Werte der Operanden und die Werte der Ausdrücke sind von einem ganzzahligen oder reellen Typ. Werden gebildet aus • • • • • (paarweise) runden Klammern Operatoren Konstanten Variablen Funktionsaufrufen Die wichtigsten Operatoren sind: C - Operator + * / % = Zweck Addition Subtraktion und Negation Multiplikation Division Restbildung Zuweisung an Variable % ergibt den Divisionsrest bei Division von 2 ganzen Zahlen; z.B. 22%5 ergibt 2 Beispiele für Ausdrücke sind math. Schreibung Ausdruck in C 5+2x x(x-y+27) x+y 4xy 5+2*x x*(x-y+27) (x+y)/(4*x*y) Für die Auswertung (Berechnung) eines Ausdruckes sind entscheidend a) Vorrangregeln (für die Operatoren) b) Reihenfolge der Anwendung bei gleichem Rang; z.B. haben * , / und % den gleichen Rang, und hier werden dann die Operationen von links nach rechts ausgeführt - Linksassoziativität; z.B. a/b/c bedeutet (a/b)/c Eine Gesamtübersicht über die Vorrangregeln folgt später. 10 • Die Division von integer - Werten liefert einen integer - Wert, z.B. 22/5 ergibt 4 (ganzzahlige Division) • Der Zuweisungsoperator = ist tatsächlich ein Operator; eine Zuweisung, etwa x = 2.0 liefert den Wert 2.0 und als „Seiteneffekt“ wird der Variablen x der Wert 2.0 zugewiesen. (Auf der linken Seite von = muß immer eine Variable stehen.) Möglich ist zum Beispiel x = 3 * (y = 5); a = b = c = 1; (= ist rechtsassoziativ!) • (Zum Unglück) hat C Operatoren, die eine kurze Schreibweise ermöglichen: Das sind: ++ , -- , += , -= , *= , /= , %= Dabei sind ++ und -- postfix oder präfix. ++ erhöht den Wert einer (ganzzahligen oder reellen) Variablen um 1: i++; bedeutet i = i+1; -- erniedrigt den Wert einer solchen Variablen um 1: i--; bedeutet i = i-1; • k = ++i; bedeutet k = i++; bedeutet • x += 10; diff -= 2; prod *= 3; quot /= 7; rem %= 12; bedeutet „ „ „ „ i = i+1; k = i; k = i; i = i+1; x = x+10; diff = diff - 2; prod = prod * 3; quot = quot/7; rem = rem % 12; Mathematische Standardfunktionen (in math.h ) Name Bedeutung Arg.-typ Erg.-typ cos(x) sin(x) tan(x) cosh(x) sinh(x) tanh(x) acos(x) asin(x) atan(x) atan2(y, x) log(x) log10(x) exp(x) cos x sin x tan x cosh x sinh x tanh x arccos x arcsin x arctan x arctan(y/x) ln x log10x ex double double double double double double double double double double double double double double double double double double double double double double double double double double 11 pow(x,y) sqrt(x) fabs(x) xy x |x| double double double double double double Ferner noch in stdlib.h abs(i) labs(i) rand() |i| int int |i| long long ganze Zufallszahl zwischen 0 und RAND_MAX ≥ 32767 Eine Zufallszahl zwischen 1 und 6 erhält man durch 1+ rand()%6 Typumwandlung: Man kann integer- und real-Typen in Ausdrücken mischen; es wird bei Bedarf eine Typumwandlung vorgenommen; immer in den „höheren“ Typ: 1. Kein Ausdruck erhält den Typ char oder short; solche werden in int’s umgewandelt. 2. Wenn 2 Operanden den gleichen Typ haben, so hat das Ergebnis diesen Typ 3. Wenn Typen der Operanden verschieden sind, so wird (compilerabhängig): • Wenn int’s einen kleineren Bereich als long’s haben, so int → unsigned int → long → unsigned long → float → double →long double Z.B.: int multipliziert mit long: int wird in long umgewandelt, dann multipliziert • Wenn int’s gleich long’s sind, so int → long oder unsigned int → unsigned long → float → double → long double Z.B.: ein Wert long, der andere unsigned int, so werden beide in unsigned long umgewandelt. 1.5.2. Logische Ausdrücke In C gibt es im strengen Sinne keine Booleschen Ausdrücke, da diese Zahlenwerte liefern; 0 für falsch und 1 für wahr. Vergleichsoperatoren: < <= > >= == != kleiner kleiner oder gleich größer größer oder gleich gleich ungleich So liefert der Ausdruck 7 < 1 den Wert 0; dagegen liefert 7 != 1 den Wert 1 12 Die logischen Operatoren von C sind && || ! z.B.: und (Konjunktion) oder (Disjunktion) not (Negation) x > 0 && y < 0 !(x > 0) && y < 0 13 Vorrangregeln: Operatoren Assoziativität ( ) (Fkt.-aufruf) [ ] (Index) . (Feldauswahl) -> (indir. Auswahl) ++ -+ (unär) links ! (not) ∼ (bitweises Komplement) (cast) sizeof - (unär) * (Indirektion) & (Adressse) rechts links * / % + - (additiv) links << >> (shift) links < <= > == != (Gleichheit) & (bitweises und) links ^ (bitweises entweder oder) links | (bitweises oder) links && (und) links || (oder) links ?: (konditional) rechts = &= += ^= , (Kommaoperator) -= |= (multiplikativ) >= (Vergleiche) *= /= %= (Zuweisungen) links links <<= >>= rechts links Der Kommaoperator erlaubt die sukzessive Auswertung, z.B. i = 1, j = 2; ist ein Ausdruck; sein Wert ist 2