C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 1 13. Gleichung 2. Grades Aufgabe Berechne x aus 2 x 2 + 5 x − 3 = 0 Algorithmus Nach der ‘‘Mitternachtsformel’’ gilt x 1,2 = −5 ± √ 52 − 4 (−3) 2 2×2 Programm — scalar/qgl0.c #include <math.h> #include <stdio.h> int main (void) { printf("Loesungen:\n"); printf("%g\n", (−5.0 + sqrt(5.0 * 5.0 − 4.0 * (−3.0) * 2.0)) / (2.0 * 2.0)); printf("%g\n", (−5.0 − sqrt(5.0 * 5.0 − 4.0 * (−3.0) * 2.0)) / (2.0 * 2.0)); return 0; } Hilfsmittel #include <math.h> printf("%g\n", ... ); \n 5.0 * 5.0 sqrt( positiv ) gcc ... -lm mathematische Büchereifunktionen deklarieren double Ausgabe Zeilentrenner (in Ausgabetext) Potenzieren geht nicht berechnet Quadratwurzel (von double) mathematische Büchereifunktionen einbinden Analyse math.h deklariert double sqrt(double); folglich könnte man (in ANSI C) auch mit Integer-Konstanten arbeiten (siehe scalar/qgl1.c). Die Lösung ist viel zu speziell! • Unabhängigkeit des Programms von Koeffizienten. • Erkennung besonderer Fälle: keine quadratische Gleichung; keine reellen Wurzeln; zwei gleiche Wurzeln. 14. Gleichung 2. Grades Allgemeinere Aufgabe Wir lösen a x2 + b x + c = 0 Algorithmus 2 15. Arithmetik-Begriffe Festlegen der Zahlen a, b, c Bestimmen der Wurzel w=√ b2 − 4ac (denn sie kommt in beiden Lösungen vor) Ausgabe der Lösungen x1 = (-b + w) / 2a x2 = (-b - w) / 2a Programm — scalar/qgl2.c #include <math.h> #include <stdio.h> int main (void) { double a, b, c; double w; double x1, x2; /* Koeffizienten */ /* Wurzel */ /* Loesungen */ a = 2.0; b = 5.0; c = − 3.0; printf("Koeffizienten %g %g %g\n", a, b, c); w = sqrt(b * b − 4 * a * c); x1 = (−b + w) / (2 * a); x2 = (−b − w) / (2 * a); printf("Loesungen %g %g\n", x1, x2); return 0; } Funktionen Siehe $ man sqrt $ man sin 15. Arithmetik-Begriffe Variable Eine modifizierbare Größe, deren Namen und Typ vor Gebrauch vereinbart werden: double a, b, c; a = 1.2; printf("%g\n", a); /* Vereinbarung */ /* Zuweisung */ /* Gebrauch */ Name beginnt mit Buchstabe, besteht aus Buchstaben und Ziffern. Kann kein reserviertes Wort sein (double, o.ä.). Große und kleine Buchstaben sind verschieden, _ (Unterstrich) ist Buchstabe. Traditionellerweise verwendet man in C Kleinbuchstaben für Variablennamen und Großbuchstaben für symbolische Konstanten. C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 3 Konstante Eine unveränderliche Größe, deren Name sich oft direkt aus ihrem Wert ergibt: 1 2.3 ’a’ "abc" Mit dem Attribut const können Konstanten auch als nicht modifizierbare Variablen vereinbart werden: const double a = 1.2; /* muss initialisiert sein */ Zuweisung Aktion, durch die eine Variable einen neuen Wert erhält: double a; a = 1.3; An Konstanten (const-Variablen) darf man nicht zuweisen. Zuweisung ist keine Gleichung double a = 1.0; /* initialisiert */ a = a + 1.0; /* anschliessend gilt a == 2.0 */ Bewertet wird immer der arithmetische Ausdruck rechts, und zwar mit den alten Variablenwerten; erst dann wird an die Variable links zugewiesen. Das Resultat der Zuweisung ist der zugewiesene Wert. 16. Arithmetik-Begriffe Definition Vereinbarung eines Namens (frei erfunden), Verknüpfung des Namens mit einem Datentyp und mit Attributen wie const, Erzeugung der Datenfläche für den Wert, evtl. Initialisierung. Das Ganze steht meistens am Anfang einer Funktion, vor Aktionen, die darauf Bezug nehmen: int main (void) { double a; const double b = 1.3; a = b; Definitionen in C++ — scalar/qgl3.c In C++ kann man Definitionen und Anweisungen mischen. Eine Definition gilt dann bis zum Ende des Blocks. #include <math.h> #include <stdio.h> int main (void) { const double a = 2.0, b = 5.0, c = − 3.0; printf("Koeffizienten %g %g %g\n", a, b, c); const double w = sqrt(b * b − 4 * a * c); const double x1 = (−b + w) / (2 * a); const double x2 = (−b − w) / (2 * a); printf("Loesungen %g %g\n", x1, x2); return 0; 4 17. Ausgabe } Da man Konstanten initialisieren darf (nur nicht später verändern!), kommt man hier total mit Konstanten aus. Vorteil: Man kann Variablen direkt am Ort der Tat vereinbaren. $ g++ qgl3.c −o qgl3 −lm 17. Ausgabe printf() Mit dieser Funktion kann man formatiert ausgeben, das heißt, (konstanten) Text vermischt mit den Werten von arithmetischen Ausdrücken: const double a = 1.2; const int b = 10; printf("a hat den Wert %g\n", a); printf("b hat den Wert %d\n", b); Formatelemente printf(Format, Wert ...); Format ist ein Text, der unverändert erscheint, mit Formatelementen dort, wo jeweils ein Wert erscheinen soll: %c %d %g %s %% ’a’ int o.ä. double "abc" Zeichen dezimal, ganzzahliger Wert dezimal, Gleitkomma-Wert Text % selber Man kann dabei Breite, Ausrichtung, Auffüllen mit führenden Nullen, Anzahl Dezimalstellen, usw. kontrollieren. Siehe $ man printf Vorsicht Für jedes Formatelement muß unbedingt ein Wert als Argument für printf() vorhanden sein — sonst gibt es im Ernstfall Tränen. 18. Eingabe Kochrezept int i; double d; scanf("%d", & i); scanf("%lf", & d); scanf() wandelt Text aus der Eingabe um und weist die Werte an Variablen zu, die (mit &) als Argumente angegeben sind. Zwischenraum und Zeilentrenner werden ignoriert, andere Zeichen müßten im Format vorkommen. Formatelemente C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl Element Wert-Typ %c %d %lf char int o.ä. int o.ä. double 5 Umwandlung Zeichen (auch Zwischenraum) dezimal, ganzzahliger Wert dezimal, Gleitkomma-Wert Man kann dabei Breite u.ä. kontrollieren. Siehe $ man scanf Vorsicht Für jedes Formatelement muß unbedingt ein Name (mit &) als Argument für scanf() vorhanden sein — sonst gibt es im Ernstfall Tränen. 19. Gleichung 2. Grades Analyse Der bisherige Algorithmus hat einen massiven Fehler. Die Gleichung x2 + x + 1 = 0 hat keine reellen Lösungen, die Eingabe 1.0 1.0 1.0 führt in w = sqrt(b * b − 4 * a * c); zur Berechnung der Wurzel von -3.0, also zu einer Fehlermeldung. Verbesserter Algorithmus Festlegen der Zahlen a, b, c Bestimmen der Diskriminante d = b2 − 4ac Entscheidung: gilt d≥0 ja nein w=√ d Lösungen x1 = (-b - w) / 2a x2 = (-b + w) / 2a Es gibt keine reellen Lösungen 6 20. Gleichung 2. Grades 20. Gleichung 2. Grades Programm — scalar/qgl4.c #include <math.h> #include <stdio.h> int main (void) { double a, b, c; double d; double x1, x2; /* Koeffizienten */ /* Diskriminante */ /* Loesungen */ printf("Koeffizienten ? "); scanf("%lf %lf %lf", & a, & b, & c); d = b * b − 4 * a * c; if (d < 0.0) printf("Es gibt keine reellen Loesungen\n"); else { d = sqrt(d); x1 = (−b + d) / (2 * a); x2 = (−b − d) / (2 * a); printf("Loesungen %g %g\n", x1, x2); } return d < 0.0; /* 0: ok, 1: keine Loesungen */ } Weitere Schwachstellen a == 0.0 Dann liegt eine lineare Gleichung vor — mit einer Lösung. a und b == 0.0 Dann sollte c Null sein — Lösungen gibt’s keine. d == 0.0 Dann gibt es eine (zweifache) Nullstelle. b ungefähr == d Dann löscht sich die Differenz einmal fast aus, d.h., dann ist eine Lösung arg falsch. Beispiel: x 2 + 10n x + 1 = 0 21. Kontrollstrukturen Zusammenfassung Aktion 1 Aktion 2 Aktion 3 C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 7 { Aktion 1 ; Aktion 2 ; Aktion 3 ; } Die Kontrollstruktur { Aktionen } faßt viele Aktionen (Anweisungen) in eine zusammen. Entscheidungen Bedingung ja nein Aktion if ( Bedingung ) Aktion ; Die if-Kontrollstruktur führt eine Aktion in Abhängigkeit vom Zutreffen einer Bedingung (Vergleich o.ä.) aus, oder auch nicht. Bedingung ja nein Aktion 1 Aktion 2 if ( Bedingung ) Aktion 1 ; else Aktion 2 ; Die if-else-Kontrollstruktur führt eine von zwei Aktionen in Abhängigkeit vom Zutreffen einer Bedingung (Vergleich o.ä.) aus. Hinweis Die abhängige Anweisung kann immer eine Zusammenfassung sein, kann also aus mehreren Aktionen in { } bestehen. 22. Kommandozeile Koeffizienten im Aufruf — scalar/qgl5.c #include <math.h> #include <stdio.h> #include <stdlib.h> /* sqrt */ /* atof */ int main (int argc, char * argv[]) { double a, b, c; /* Koeffizienten */ double d; /* Diskriminante */ double x1, x2; /* Loesungen */ if (argc < 4) printf("Aufruf: %s a b c\n", argv[0]); else { a = atof(argv[1]); 8 23. Quadratwurzeln bestimmen b = atof(argv[2]); c = atof(argv[3]); printf("Koeffizienten %g %g %g\n", a, b, c); d = b * b − 4 * a * c; if (d < 0.0) printf("Es gibt keine reellen Loesungen\n"); else { d = sqrt(d); x1 = (−b + d) / (2 * a); x2 = (−b − d) / (2 * a); printf("Loesungen %g %g\n", x1, x2); return 0; } } return 1; } Redewendungen int main (int argc, char * argv[]) Jedes C Programm erhält die Worte der Kommandozeile als Parameter des Hauptprogramms. argc ist die Anzahl der Worte (also wenigstens 1 für den Kommandonamen). argv ist der Vektor der Worte als ‘‘Strings’’. 23. Quadratwurzeln bestimmen Geometrische Idee C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 9 y y0 x x1 x0 −A Gesucht ist eine Nullstelle der Funktion y = x2 − A Ausgehend von einem beliebigen Punkt x0 ≥ √ A ersetzen wir die Kurve durch ihre Tangente y − y 0 = y 0 ′ (x − x 0 ) und finden als Schnittpunkt mit der x-Achse y1 = 0 x1 = x0 − y0 y0 ′ Hier gilt also x1 = x0 − (Newton Verfahren) 24. Quadratwurzeln bestimmen Algorithmus x02 − A A = (x 0 + ) / 2 2 x0 x0 10 25. Quadratwurzeln bestimmen Argument der Wurzel: A Genauigkeit: t Erste Approximation w1 = A wiederholen Approximation merken w0 = w1 Neue Approximation berechnen w1 = ( w0 + A / w0 ) / 2 bis w0 - w1 < t Lösung ist (etwa) w1 Mathematisches Natürlich gilt für A als erste Approximation A ≥ √ A (falls A ≥ 1). Man kann zeigen, daß immer w0 > w1 > √ A und w0 − w1 > w1 − √ A gilt — damit kann man den Fehler abschätzen. 25. Quadratwurzeln bestimmen Programm — scalar/newton.c int main (void) { double A, t; double w0, w1; /* Argument, Fehler */ printf("Argument der Wurzel ? "); scanf("%lf", & A); printf("Fehlergrenze ? "); scanf("%lf", & t); w1 = A; do { w0 = w1; w1 = (w0 + A / w0) / 2.0; } while (w0 − w1 > t); printf("Wurzel: %g\n", w1); printf("Quadrat: %g\n", w1 * w1); return 0; } C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 11 26. Kontrollstrukturen Wiederholungen wiederholen Aktion während Bedingung erfüllt do Aktion ; while ( Bedingung ); Die do-while-Kontrollstruktur führt eine Aktion wenigstens einmal aus, und dann noch solange eine Bedingung erfüllt ist. während Bedingung erfüllt Aktion while ( Bedingung ) Aktion ; Die while-Kontrollstruktur führt eine Aktion aus, falls und dann solange eine Bedingung erfüllt ist. (Also auch gar nicht.) Vorsicht while und do-while unterscheiden sich nur in einem Semikolon. Nur ein Semikolon (keine Aktion) ist auch eine Aktion! 27. Euklid’s Algorithmus Aufgabe Größter gemeinsamer Teiler zweier natürlicher Zahlen. Idee: Primzahlzerlegung ggT besteht aus den Primfaktoren, die beide Zahlen gemeinsam haben. 54 ≡ 2 × 3 × 3 × 3 36 ≡ 2 × 2 × 3 × 3 Sehr problematisch zu berechnen. Idee: Differenzen Jeder Teiler von a und b teilt auch die (positive) Differenz: 12 28. Euklid’s Algorithmus a und b festlegen a und b verschieden a ?? b > < neue Aufgabe: b und (a − b) neue Aufgabe: a und (b − a) ggT ist a Abbruchkriterium Alte Aufgabe: a und b, verschieden. Neue Aufgabe: a und (b − a) falls b > a. Die Summe der Zahlen fällt monoton gegen Null: a + (b − a) ≡ b < a + b Das geht nur für eine endliche Anzahl von Schritten. 28. Euklid’s Algorithmus Programm — scalar/euklid0.c int main (void) { int x, y; scanf("%d %d", & x, & y); printf("ggT(%d, %d) == ", x, y); while (x != y) if (x > y) x = x − y; else y = y − x; printf("%d\n", x); return 0; } Robustere Eingabe — scalar/euklid1.c while (1) { printf("Bitte zwei Zahlen eingeben: "); if (scanf("%d %d", & x, & y) < 2) break; if (x <= 0 || y <= 0) printf("beide muessen positiv sein\n"); else { ... } } C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 13 Nicht-positive Werte werden abgewehrt. Immer neue Aufgaben — Abbruch nur mit Interrupt-Taste oder am Dateiende (control-D). scanf() liefert die Anzahl der zugewiesenen Werte. break break; bricht die umgebende Schleife ab. break vereinfacht oft Schleifenbedingungen. 29. Rekursion Lösung mit rekursiver Funktion — scalar/euklid2.c #include <stdio.h> #include <stdlib.h> /* atoi */ int ggT (int x, int y) { if (x == y) return x; if (x > y) return ggT(x−y, y); return ggT(x, y−x); } int main (int argc, char * argv[]) { int x, y; if (argc < 3) return printf("Aufruf: %s x y\n", argv[0]); x = atoi(argv[1]); y = atoi(argv[2]); if (x <= 0 || y <= 0) return printf("Zahlen muessen positiv sein\n"); printf("ggT(%d, %d) == %d\n", x, y, ggT(x, y)); return 0; } Funktionen können rekursiv aufgerufen werden. 30. Bit-Operationen Wertebereich der Integer-Typen Verschiebt man Bits nach links und schiebt Bits mit Wert 1 nach, wird der repräsentierte Wert zunächst immer größer. 0 0 1 1 3 0 0 1 1 1 7 Wenn das letzte Bit erreicht wird, entsteht entweder ein kleiner Wert (0 bei 1-Komplement oder -1 bei 2-Komplement), oder der letzte Wert ist positiv und wird nicht mehr größer. 14 31. Bit-Operationen 1 1 1 1 1 ≤0 Gibt es negative Werte, so kann man dann Bits mit Wert 0 nachschieben, bis der kleinste mögliche Wert entsteht. 1 0 0 0 0 ←0 int — scalar/int0.c int main (void) { int i, j = 1; do { i = j; j = i << 1 | 1; } while (j > i); printf("max: %d, ", i); do { i = j; j = i << 1; } while (j < i); printf("min: %d\n", i); return 0; } << schiebt nach links. | ist die Bit-ODER-Verknüpfung. Vorrang: Arithmetik vor Verschieben vor Vergleichen vor Bit-Operationen. 31. Bit-Operationen unsigned — scalar/int1.c unsigned stellt nur natürliche Zahlen dar. Die negativen Werte von int repräsentieren dabei entsprechend größere Zahlen. int main (void) { unsigned i, j = 1; ... if (i == j) i = 0; else ... } Integer-Typen Ganzzahlige Werte gibt es als char, short und long, jeweils signed und unsigned. Ob char ein Vorzeichen hat oder nicht, hängt von der Maschine ab. Mit signed kann man es erzwingen, mit unsigned kann man es verhindern. C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 15 int und unsigned sind je nach Maschine entweder long oder short. Falls die Anwendung es zuläßt (Wertebereich klein genug, als char nur ASCII-Zeichen) sollte man aus Effizienzgründen int, unsigned und char einsetzen. Konstanten Vorsicht, die Regeln sind trickreich: • dezimale Konstanten sind je nach Größe und Maschine entweder int, long oder unsigned long. • hexadezimale und oktale Konstanten können dazwischen auch noch unsigned sein. • nnnU ist unsigned oder auch unsigned long. • nnnL ist long oder auch unsigned long. • nnnUL ist unsigned long. Im Zweifelsfall sollte man mit et üben... 32. Bit-Operationen Umwandlung Kombiniert man verschiedene Integer-Typen, wird jeweils der ‘‘größere’’ Typ verwendet. Beim Übergang dazu ändern sich Null und positive Werte nicht. Bei negativen Werten wird das Vorzeichen propagiert. Bei short zu int zu long ändert sich der Wert nicht, bei Übergang zu unsigned resultieren stark positive Werte! Leider ist der Begriff des ‘‘größeren’’ Typs sehr trickreich: char short ushort int unsigned long ulong int int int ? ? ? int int ? int unsigned unsigned unsigned unsigned unsigned long long long long ? long ulong ulong ulong ulong ulong ulong ulong char short ushort int unsigned long ulong • short und alle char werden immer in int verwandelt. • unsigned short wird in int oder in unsigned verwandelt (wenn int nicht den kompletten Wertebereich von unsigned short umfaßt, d.h. bei kleinen int). • werden unsigned und long Operanden verknüpft, ist das Resultat unsigned long oder auch long (wenn long den kompletten Wertebereich von unsigned umfaßt; kleine int). Das kann böse Tränen geben: $ et −v short −3L < 2U − (long) 3 −> (long) −3 (unsigned) 2 −> (long) 2 (long) −3 < (long) 2 −> (int) 1 −> (int) 1 long −3L < 2U − (long) 3 −> (long) −3 (long) −3 −> (unsigned long) 4294967293 (unsigned) 2 −> (unsigned long) 2 (unsigned long) 4294967293 < (unsigned long) 2 −> (int) 0 −> (int) 0 16 33. Bit-Operationen 33. Bit-Operationen Alle Wertebereiche — scalar/int2.c Prinzipiell kann man scalar/int1.c für die verschiedenen Integer-Typen vervielfältigen. Einfacher geht das mit dem C Preprozessor: int main (void) { TEST(char); TEST(unsigned char); TEST(signed char); TEST(short); TEST(unsigned short); TEST(int); TEST(unsigned); TEST(long); TEST(unsigned long); return 0; } TEST hat einen Datentyp als Argument, kann also keine C Funktion sein. 34. Bit-Operationen Der C Preprozessor #define name ersatztext #define name(parm, ...) ersatztext Mit #define vereinbart man einfachen oder auch parametrisierten Ersatztext. Zeilen setzt man mit \ fort. #define TEST(INTEGER) { INTEGER i, j = 1; printf("%−20s", #INTEGER ":"); do i = j, j = i << 1 | 1; while (j > i); printf("max: %lu, ", (unsigned long) i); if (i == j) i = 0; else do i = j, j = i << 1; while (j < i); printf("min: %ld\n", (long) i); \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } Bei ANSI C werden Parameter-Namen im Ersatztext nur außerhalb von Strings erkannt. Mit # kann man sie aber in Strings verwandeln. Aufeinanderfolgende String-Konstanten werden bei der Übersetzung verkettet. %-20s gibt eine Zeichenkette auf 20 Spalten linksbündig aus. C-Kurs −− © 1991, 1997 Axel T. Schreiner, © 1998 Bernd Kühl 17 Damit die Ausgabe unabhängig von der Maschine korrekt funktioniert, muß man die Werte in (long) oder (unsigned long) verwandeln und mit %ld oder %lu formatieren. ( typ ) ausdruck ist eine unitäre Umwandlungsoperation (mit hohem Vorrang). 35. Bit-Operationen Weitere Bit-Operationen Für alle Integer-Typen gibt es folgende Operationen: ˜ << >> & ˆ | Bit-Komplement nach links schieben nach rechts schieben UND-Verknüpfung exklusive ODER-Verknüpfung (inklusive) ODER-Verknüpfung Bei unsigned-Typen wird bei >> von links her 0 nachgeschoben, das heißt, es resultiert eine Division durch Zweierpotenzen. Andernfalls kann auch 1 nachgeschoben werden, das heißt, das Resultat ist bei negativen Werten maschinenabhängig. Versuche mit et: >>1 signed (char) 128 >> 3 −> (int) −16 unsigned (char) 128 >> 3 −> (int) 16 // 1 nachschieben // char ist signed // char ist unsigned >>0 // 0 nachschieben signed // char ist signed long // int ist long (char) 128 >> 3 −> (int) 536870896 hex // hexadezimale Ausgabe + // mit Ablaufverfolgung (char) 128 >> 3 (char) (int) 0x80 −> (int) 0xffffff80 (int) 0xffffff80 >> (int) 0x3 −> (int) 0x1ffffff0 −> (int) 0x1ffffff0 36. Bit-Operationen Wertebereiche als Definitionen — scalar/int3.c Mit den Bit-Operationen kann man die Wertebereiche ohne Schleifen definieren: #define max(type, utype) \ ((type) ˜0L > 0 ? (type) ˜0L : (utype) ˜0L >> 1) #define min(type, utype) \ ((type) ˜0L > 0 ? 0 : (type) ˜ ((utype) ˜0L >> 1)) #define TEST(type, utype) \ 18 36. Bit-Operationen printf("%−20smax: %lu, min: %ld\n", #type ":", (unsigned long) max(type, utype), (long) min(type, utype)) \ \ int main (void) { TEST(char, unsigned char); TEST(unsigned char, unsigned char); TEST(signed char, unsigned char); TEST(short, unsigned short); TEST(unsigned short, unsigned short); TEST(int, unsigned); TEST(unsigned, unsigned); TEST(long, unsigned long); TEST(unsigned long, unsigned long); return 0; } Das Maximum besteht aus Bits mit Wert 1; bei signed-Typen muß man das Vorzeichen löschen. Das Minimum ist Null; bei signed-Typen muß man das Maximum genau komplementieren. Das Vorzeichen löscht man durch Verschieben eines unsigned-Typs. Die 1-Bits erhält man als Komplement von Null. Ein signed-Typ liegt vor, wenn dieses Komplement nicht positiv ist. bedingung ? ausdruck1 : ausdruck2 Diese bedingte Bewertung liefert den Wert eines der beiden Ausdrücke in Abhängigkeit von der Bedingung.