Informatik für Physiker Lesender: Prof. Dr. Süÿe Autoren Kapitel 1: Silvio Fuchs, Mario Chemnitz Autoren Kapitel 2: Mario Chemnitz Autoren Kapitel 3: Silvio Fuchs, Erik Buchholz, Mario Chemnitz Grundlage: Vorlesungsmitschriften der jeweiligen Studenten Keine Prüfung durch Vorlesenden erfolgt. Fehler vorbehalten. 22. Juli 2008 Inhaltsverzeichnis 1 Einführung in die Informatik 1.1 1.2 1.3 1.4 1.5 Algorithmen . . . . . . . . . . . . . . . . Schritte zum Programmieren . . . . . . Compiler vs. Interpreter . . . . . . . . . Syntax . . . . . . . . . . . . . . . . . . . Informationen und Zahlendarstellungen 1.5.1 Zahlendarstellung . . . . . . . . 1.5.2 Codes (Allgemein) . . . . . . . . 1.5.3 Blackcodes . . . . . . . . . . . . 1.5.4 Greycodes . . . . . . . . . . . . . 1.5.5 Ganze Zahlen kodieren . . . . . . 1.5.6 Gebrochene Zahlen kodieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einführung: Numerische Umsetzung der Newton'schen Iteration Mittelwert und Standardabweichung . . . . . . . . . . . . . . . Entropie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kodierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 Datenkomprimierung ohne Datenverlust . . . . . . . . . 2.4.2 Konstruktion eine Human-Codes . . . . . . . . . . . . 2.4.3 Kodierungsarten . . . . . . . . . . . . . . . . . . . . . . 2.4.4 Kryptographie-Arten . . . . . . . . . . . . . . . . . . . . Sortieralgorithmen . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.1 Distribution-Sort . . . . . . . . . . . . . . . . . . . . . . 2.5.2 Selection-Sort . . . . . . . . . . . . . . . . . . . . . . . . 2.5.3 Insert-Sort . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.4 Bubble-Sort . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.5 Tree-Sort . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.6 Merge-Sort . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.7 Quick-Sort . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.8 Permutation . . . . . . . . . . . . . . . . . . . . . . . . Rekursivität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6.1 Kellerung . . . . . . . . . . . . . . . . . . . . . . . . . . Bewertung von Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Algorithmen zur Bildanalyse 2.1 2.2 2.3 2.4 2.5 2.6 2.7 3 Die Sprache C/C++ 3.1 3.2 3.3 3.4 3.5 Geschichtlicher Überblick . Einfache Syntaxübersicht . 3.2.1 Quellcodebeispiel . . Der Zeichensatz in C/C++ Der Präprozessor . . . . . . Kleines Programmbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 4 4 4 7 8 8 8 9 10 10 11 11 12 13 14 16 19 21 21 22 23 23 24 24 25 25 25 28 28 31 31 31 31 31 32 32 INHALTSVERZEICHNIS 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 Einfache Ein -und Ausgaben . . . . . . . . . . . . . . . . . . . . . . . Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gültigkeitsbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prioritäten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einfache Steuerstrukturen . . . . . . . . . . . . . . . . . . . . . . . . Casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Felder (Arrays) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.13.1 Eindimensionale Felder (Vektoren) . . . . . . . . . . . . . . . 3.13.2 C-Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . 3.13.3 Zwei- und mehrdimensionale Felder (Matrizen und Tensoren) Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.14.1 Adressierung und Dereferenzierung . . . . . . . . . . . . . . . 3.14.2 Pointer-Arithmetik . . . . . . . . . . . . . . . . . . . . . . . . 3.14.3 Pointer und Felder . . . . . . . . . . . . . . . . . . . . . . . . 3.14.4 Pointer-Konstanten und Speicherzugri . . . . . . . . . . . . 3.14.5 Besondere Pointer . . . . . . . . . . . . . . . . . . . . . . . . Filesysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.15.1 Beispiel zum Umgang mit Files . . . . . . . . . . . . . . . . . 3.15.2 Verarbeitung von Files . . . . . . . . . . . . . . . . . . . . . . 3.15.3 Praktische Probleme der Dateiverwaltung . . . . . . . . . . . Spezielle Spezikationen in C++ . . . . . . . . . . . . . . . . . . . . 3.16.1 Einfache Erweiterungen gegenüber C . . . . . . . . . . . . . . 3.16.2 Objektorientierung . . . . . . . . . . . . . . . . . . . . . . . . Nützliche Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 33 34 34 35 36 37 37 37 37 38 38 39 40 40 40 41 42 43 43 44 45 45 47 52 Kapitel 1 Einführung in die Informatik 1.1 Algorithmen Algorithmen sind Abfolgen von Anweisungen, die in endlicher Zeit ausfürbar sind. 1.2 Schritte zum Programmieren 1. Aufgabe 2. Entwurf des Algorithmus 3. Darstellung mittels Pseudokode, Struktogrammen usw. 4. Kodierung in eine dem Computer verständliche Form. z.B Kodierung in C/C++, Java (a) Erfassen mittels eines Editors (b) Compilierung: vom Präprozessor zum Compiler (c) Debugging (d) in C entsteht jetzt ein le.o also ein Objektcode (e) Der Linker verlinkt im Programm gebrauchte Funktionen mit den Laufzeitbibliotheken (f) Ausführung des fertigen Programms und Testung 1.3 Compiler vs. Interpreter Wärend C einen dem Computerverständlichen Kode mit Hilfe des Compilers produziert, benutz z.B. Java einen Interpreter der einen Bytecode produziert und dann abgearbeitet wird. Um JavaProgramme auszuführen benötig man eine Java-Laufzeitumgebung. Das C Programm läuf jedoch schneller, deshalb ist C eine weitverbreitete Entwicklersprache. Compiler - Komplettübersetzung des Algorithmus in maschinennahe Sprache → dann Interpreter - Schneller als pure Interpretation - Bsp.sprache: C/C++ - Laufzeitsystem nicht notwendig, aber plattformabhängig Interpreter - Zeilenweise Ausführung (Interpretation) - Übersetzung in eine Art Zwischensprache - Bsp.sprache: Java, C# - Laufzeitsystem muss installiert werden, dafür plattformunabhängig 3 KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK 1.4 4 Syntax Die Grammatik und Orthographie einer Programmiersprache bezeichnet man als Syntax. Zum Vergleich werden hier der Syntax einer for-Schleife in Pascal und C gegenüber gestellt: Pascal: for i:=0 to 100 begin ANWEISUNG; end; C: for(i=0;i<=100;i++){ANWEISUNG} Zur anschaulichen Darstellung des Syntax, gibt es verschiedene Möglichkeiten: 1. Bachau-Nauer-Form EBNF z.B.: • A = [B] : Option • A = B|C Auswahl 2. Syntaxdiagramme (PAPs, Struktogramme etc.) 1.5 Informationen und Zahlendarstellungen Computerintern existieren nur binäre Ausdrücke und Variablen. Die Aufgabe der Benutzerschnittstelle ist es nun, Informationen so zu kodieren, dass die CPU und das Speichersystem binär mit diesen Arbeiten kann. Dabei lassen sich mit n binären Variablen m= 2n Zustände darstellen. Aufzeichnungen fehlen!! [...] 1.5.1 Zahlendarstellung • Darstellung mittels EBCDI-Code: +454 +454 4 Byte ungepackt ⇒ E4|F 4 F 5 F 4 ↓ +⇒C ↓ −⇒D ⇒ 45 | {z4C} 2 Bytes gepacktes dezimales Zahlenformat • Darstellung mittels ASCII-Code: ± ∧ 9Byte → 18 Ziern aneinander gereiht (darstellbar?) Ziernverwaltung - BCD-Zahlen(?) 454 ↓ 13.4 ↓ Umwandlung 45400 & 1340 . +− ∗/ Operationen −→ Rückwandlung KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK 5 Im wissenschaftlich-technischen Bereich werden Zahlen im B-adischen Zahlensystem ausgedrückt. Regel: z=± n X ak · B k k=−m k wobei ak die Ziern und B die Basis ist. a) Dualsystem B = 2, ai ∈ {0, 1} b) Oktalsystem B = 8, ai ∈ {0, 1, . . . , 7} c) Dezimalsystem B = 10, ai ∈ {0, 1, . . . , 9} d) Hexadezimalsystem B = 16, ai ∈ {0, . . . , 9, A, . . . , F } Sonderfälle: n X z = ± ak · B k Linkspunktzahl (Integer) z = ± k=0 −m X ak · B k Rechtspunktzahl (gibt es nicht) k=−1 Bsp.: z = 14.75 ist eine dezimale Festpunktzahl, die nun mit Hilfe des Probierverfahrens in eine binäre Darstellung umgewandelt werden soll: z = 1 · 23 + 1 · 22 + 1 · 21 + 0 · 20 + 1 · 2−1 + 1 · 2−2 = 1110(.)11 →Abbruch nach erreichen der Genauigkeitsgrenze Bsp.: z = 0.1 z = 0 · 2−1 + 0 · 2−2 + 0 · 2−3 + 0 · 2−4 + 0 · 2−5 + . . . →Unendliche Fortsetzung y Abschneiden erforderlich y Konvertierungsfehler • Algorithmen: Divisionsrestverfahren Allgemein: zdez = an · 2n + an−1 · 2n−1 + . . . + a1 · 21 + a0 z1 z2 = = .. . = z/2 z1 /2 Rest a0 zi /2 Rest ai ⇒ zbin ≡ an an−1 . . . a1 a0 zi+1 Rest a1 Beispiele: zdez = 28 28/2 = 14 14/2 = 7 Rest 0 7/2 = 3 3/2 = 1 Rest 1 1/2 = 0 Rest 1 Rest 0 Rest 1 ⇒ 11100 ≡ 28 KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK Bsp.: bin → hex 6 1 0 1001 | {z} 0010 | {z} ≡ 9 · 16 + 2 · 16 ≡ 92 Bsp.: bin → okt 010 |{z} 010 ≡ 2 · 82 + 2 · 81 + 2 · 80 ≡ 222 10 |{z} |{z} Multiplikationsverfahren Allgemein: zdez = a−1 · 2−1 + a−2 · 2−2 + . . . + a−n · 2−n z1 = z·2 Wenn z1 ≥ 1 ⇒ a−1 = 1 ∧ z2 = z1 − 1 Wenn z1 < 1 ⇒ a−1 = 0 .. . Beispiel: zdez = 0.1 0.1 · 2 0.2 · 2 0.4 · 2 0.8 · 2 (1.6 − 1 =) 0.6 · 2 (1.2 − 1 =) 0.2 · 2 = = = = = = .. . = z 0.2 0.4 0.8 1.6 1.2 0.4 → → → → → → 0 0 0 1 1 0 da da da da da da <1 <1 <1 >1 >1 <1 → Periodizität 0001100110011001100 . . . → Abbruch nach bestimmter Länge • Operationen in Bitschreibweise: Addition: 1+1 1 + 1 ⇒ [0| 1 ][0|1][0|1] . . . =⇒ [0|1][ 0 |1][0|1] . . . ⇒ = 1 0 −−−→ Der Anfangsindex steht auf einer Eins (Zier oben durch Rahmen markiert). 1 + 1 bedeutet nun Rücke eine Indexstelle weiter und zwar in einer Folge von binären Gruppen ([0|1]). Das Ergebnis ist die Zier an der Stelle, an der der neue, um Eins erhöhte Index steht (hier also Null). Wenn bei der Indexverschiebung der ursprüngliche binäre Bereich überschritten wurde (wie es oben der Fall ist), erhält man einen Übertrag (oben als hochgestelle Zahl vor dem Ergebnis geschrieben), der nur für weitere Rechnungen eine Rolle spielt (s. Bsp. unten). Analog erhält man also: 1+0 ⇒ [0| 1 ][0|1][0|1] . . . =⇒ [0| 1 ][0|1][0|1] . . . ⇒ 1+0 = 0 1 0+1 ⇒ [ 0 |1][0|1][0|1] . . . =⇒ [0| 1 ][0|1][0|1] . . . ⇒ 0+1 = 0 1 0+0 0+0 = 0 0 ⇒ [ 0 |1][0|1][0|1] . . . =⇒ [ 0 |1][0|1][0|1] . . . ⇒ Bsp.: 1 + 1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 0 0 0 KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK 7 Subtraktion: 1−1 1−1 ⇒ [0| 1 ][0|1][0|1] . . . =⇒ [ 0 |1][0|1][0|1] . . . ⇒ ←−− = 0 0 Der Ablauf der Subtraktion ist äquivalent zu dem der oben beschrieben Addition, nur mit dem Unterschied, dass wir aufgrund des Minusoperators die Indizes nun rückwärts laufen lassen. Wird nun bei diesem Rückwärtsschritt ein binäerer Bereich überschritten, muss man mit einem negativen Übertrag weiterrechnen. Analog haben wir also: 1−0 1−0 ⇒ [0| 1 ][0|1][0|1] . . . =⇒ [0| 1 ][0|1][0|1] . . . ⇒ 0−1 0−1 ⇒ [0|1][ 0 |1][0|1] . . . =⇒ [0| 1 ][0|1][0|1] . . . ⇒ 0−0 0−0 ⇒ [ 0 |1][0|1][0|1] . . . =⇒ [ 0 |1][0|1][0|1] . . . ⇒ = = = 0 1 −1 0 1 0 ACHTUNG: Der negative Übertrag ist auf das darauolgende erste Element draufzuaddieren. Bsp.: −1 −1 −1 1 1 1 0 0 1 1 1 1 (+) 1 − 0 0 Multipliaktion: Bsp.: 1 0 1 1 1 1 1 · 1 1 0 1 1 0 1 0 1 1 1 0 1 1 0 1 0 0 0 0 1 0 Perioden: z = 0.9̄ ∧ z · 10 = 9.9̄ ⇒ z · 10 − z = 9 = z · (10 − 1) = z · 9 ⇒ z=1 Beispiel: z z · 26 6 z · (2 − 20 ) = = = ⇒ 1.5.2 0.111010 111010.111010 111010 ≡ 58 58 z= 63 Codes (Allgemein) • Repräsentation von Informationen • Einfache Codes: Blockcodes, alle Codewörter haben gleiche Länge a) EBCDI-Code (8bit) Aufteilung eines Bytes in Bsp.: Ziern 1 1 1 1 1 1 1 1 .. . 0 0 0 | 0 · · · · · · · · {z } | {z } Zonenteil Zonenteil − F0 Vier Einser Zonenbits sind für Ziern charakteristisch. 0 1 0 1 − F9 KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK 8 b) ASCII-Code (8bit) Ersten 7bit enthalten Standard-Kodierung und 1bit die variable Länderkodierung. c) UNICODE (4 Byte) d) Multi-Byte-Code Bsp.: UTF8 1.5.3 0 0 0 0 1 0 0 1 Blackcodes 2 0 1 0 3 0 1 1 4 1 0 0 5 1 0 1 6 1 1 0 7 1 1 1 1.5.4 Greycodes 0 0 0 0 Bei 1 2 3 4 5 6 7 0 0 0 1 1 1 1 0 1 1 1 1 0 0 1 1 0 0 1 1 0 zwei benachbarten Codes ändert sich nur ein bit. 1.5.5 Ganze Zahlen kodieren B-adisches System → B-Komplement-Codes Häug: 1.bit 0 → positive Zahl ∨ 1 → negative Zahl Positive Zahlen sind somit direkt darstellbar, negative Zahlen nur als B-Komplement-Code. Bsp.: 3bit ±z z* = Code +0 000 z>0 +1 001 → ⇒ +2 010 +3 011 -4 100 -3 101 ( +z = z ∗ ; 1.bit ⇒ 0 ∗ 3 −z = z − 2 ; 1.bit ⇒ 1 z ∗ = −z + 23 Komplement zu 2n − 1 Bsp.: −3 z=3 z = −3 + 23 = 5 ∗ ⇒ Vorteile: a) Null eindeutig b) Subtraktion auf Addition zurückführen −3 ↔ 011 ↔ 101 ↔ 101 -2 110 -1 111 KAPITEL 1. EINFÜHRUNG IN DIE INFORMATIK z = = = = = 9 z1 − z2 z1 , z2 > 0 z1 + (−z2 ) z1 + z2∗ z1 + 2n − z2 z1 − z2 + 2n Bsp.: 5bit Bsp.: Zahlenbereiche − 0 0 0 −1 −1 −1 1 0 0 0 0 1 0 0 1 1 0 1 1 8 −1 7 0 1 0 0 0 + 1 1 1 1 1 [1] 0 0 1 1 1 2 Byte = 16 bit −215 ≤ z ≤ 215 − 1 −32768 ≤ z ≤ 32767 4 Byte = 32 bit −231 ≤ z ≤ 231 − 1 1.5.6 Gebrochene Zahlen kodieren Darstellung: z = Mantisse · BasisExponent Standard-Code: IEEE Standard 754 → 32bit Gleitkommazahl: • Vorzeichen → 1bit • Exponent → 8bit • Mantisse → 23bit → normalisiert, d.h. 1.bit vorm Komma ist 1; somit eektiv 24bit Mantisse • Null → Sonderdarstellung • NAN → Über-/Unterlauf • Gleitkommazahlen nicht äquidistant (Abstand zwischen oat (4 Byte) Zahlen gröÿer als zwischen double (8 Byte) Zahlen) Kapitel 2 Algorithmen zur Bildanalyse 2.1 Einführung: Numerische Umsetzung der Newton'schen Iteration Das Newtonsche Iterationsverfahren wird oft zur Nullstellenbestimmung genutzt. Es zeichnet sich aus, durch hohe Konvergenzgeschwindigkeit, jedoch niedrige Konvergenzsicherheit. Mathematisch lässt sich dieses wie folgt herleiten: . f (x) = f (x0 ) + f 0 (ξ) (x − x0 ) = 0 f (x0 ) ⇒ x = x0 − 0 f (ξ) f (x0 ) → x1 = x0 − 0 → mit ξ = x0 macht man einen Fehler! → Ausgleichen! f (x0 ) ⇒ xi+1 = xi − f (xi ) f 0 (xi ) Anwendung der Newton-Iteration zur Bestimmung der Wurzel aus z ≥ 0: √ Ausgangsfunktion: z = x → x2 − z = 0 = f (x) µ ¶ f (xi ) x2 − z 1 z ⇒ xi+1 = xi − 0 = xi − i = xi + f (xi ) 2xi 2 xi Für die nummerische Umsetzung wähle man sich nun ein Auösungsgrenze ε, die als Abbruchbedingung eingesetzt wird 1 · |xi+1 − xi | ≤ ε. |xi | double eps=0.01,z,X,Y; //X->x_i; Y->x_i+1 scanf("%lf",&z); Y=z; do { X=Y; Y=(X+z/X)/2.0; }while(fabs(Y-X)>=eps); //Absolutwertabfrage Ein weiteres Iterationsverfahren ist das sog. Lernverfahren, welches man bspw. zu Mittelwertbildung verwenden kann 1 n x̄n + xn+1 . x̄n+1 = n+1 n+1 10 KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.2 11 Mittelwert und Standardabweichung Der Mittelwert über die Farbwerte eines Bildes ist ein Maÿ für dessen Helligkeit, die Standardabweichung demenstprechend für dessen Kontrast. Wenn mit g(i, j) die Grauwerte einer Bildes bezeichnet werden, so ergibt sich für den Mittelwert ḡ = X 1 g(i, j) dimx · dimy i,j und für die Standardabweichung s Sg = X 1 2 (g(i, j) − ḡ) . dimx · dimy − 1 i,j Um die Standardabweichung zu Berechnen wird in der Regel eine zweite Schleife benötigt, da vorerst der Mittelwert brechnet werden muss. Unter Akzeptanz eines geringen Fehlers lässt sich die Berechnung beider Werte auf eine Schleife zurückführen: x̄ := 1X xi n i ⇒ S2 = = = = = = 2.3 1 X 2 (xi − x̄) n−1 i ¢ 1 X¡ 2 xi − 2xi x̄ + x̄2 n−1 i ! à X X nX 1 2 2 1 xi − 2x̄ xi + x̄ n−1 n i i i ! à X 1 2 2 xi − 2x̄nx̄ + nx̄ n−1 i ! à X 1 2 2 xi − nx̄ n−1 i à ! ³ X ´2 X 1 1 x2i − xi ¤ n−1 n i i Entropie Die Entropie wird auch in der Informatik als Maÿ für die Ordnung eines Systems genutzt. Auf die Bildanalyse angewandt, erlaubt sie Aussagen über die mögliche verlustfreie Komprimierungsrate eines Bildes. Die verschiedenen Zustände entsprechen dann bspw. den Grauwerten des Bildes, aus denen man ein Histogramm entwickeln kann (Zustand i → Häugkeit pi mit i = 0, . . . , 255). Aus diesem Grauwerthistogramm lässt sich die Entropie H0 dann näherungsweise berechnen. Erhält man bspw. den Entropiewert H0 = 3.9 (bit), bedeutet dies, bei einer Grauwertkodierung von 8 bit, eine Komprimierungsmöglichkeit des Bildes von 50%. Die Entropie ist wie folgt deniert H0 = − n X pi log2 pi , i=1 wobei die pi die jeweiligen Wahrscheinlichkeiten eines Grauwertes sind, also Anzahl des Grauwertes x geteilt durch die Gesamtanzahl der Grauwerte. Da der Logarithmus aus Null nicht deniert ist, wird eine Konvention getroen 0 · log2 0 =: 0. KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 12 Im einfachsten Fall hat man bei der Arbeit mit Grauwerten ein Zwei-Zustandsbild (nur schwarze und weiÿe Werte), welches sich auf eine 0-1-Verteilung zurückführen lässt. Für die Entropie ergibt sich dann für m Grauwerte: 0 ≤ H0 ≤ log2 m Im schlimmsten Fall rechnet man mit einer idealen Gleichverteilung der Werte → In diesem Fall ergibt sich für die Entropie: pi = 1 m ∀i. m X 1 1 1 H0 = . log2 = − log2 = log2 m = H0 m m m i=1 Speziell fà 14 r die Entropieberechnung eines Bildes deniert man sich zwei Zufallsvariablen X und Y , wobei diese die jeweilige Menge der xi bzw. der yi darstellen. Spaltenweise Entropie H0 (Y ) = X P [yj ] log2 P [yj ] j Zeilenweise Entropie H0 (X) = X P [xi ] log2 P [xi ] i Bedingte Entropie von Y bzgl. X H0 (Y |X) = = − − XX j i j i XX P [xi ]P [yj |xi ] log2 P [yj |xi ] P [yj , xi ] log2 P [yj |xi ] Es gilt zudem H(X, Y ) = H(X) + H(Y |X) = H(Y ) + H(X|Y ). An dieser Stelle wird eine weitere Gröÿe als Maÿ für die Transportqualität eingeführt. Diese Gröÿe I(X|Y ) = H(X) − H(X|Y ) = H(Y ) − H(Y |X) heiÿt mutual information oder auch Transinformation. Gilt I(X|Y ) = H0 (Y ) sagt man der Kanal sei ok und bei I(X|Y ) = 0 herrsche Übertragunschaos. Man hat zudem einen Klassikator (Zuweisungsfunktion), den man so entwerfe, dass die Transinformation maximal wird. Bsp.: (∗) Buchstabe A −→ Buchstabe A Klassikator (∗) bildet optimal identisch ab y optimale Transinfo. Weitere Informationen zur Entropie (u.a. Dierentielle Entropie) entnehme man bitte dem Skript Grundbegrie der Infromationstheorie von Prof. Süÿe. 2.4 Kodierung • Repräsentation von Informationen • Ziele: Eziente Algorithmen Datensicherheit → Kryptographie (Verschlüsselung) Datensicherheit → Minimierung von Fehlern bei Datenübertragungen (Bsp.: GreyCode) Datenkompression → ohne/mit Verlust KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.4.1 13 Datenkomprimierung ohne Datenverlust Ausgangssitution: A B = = Quell-Alphabet {a1 , a2 , . . . , an } {b1 , b2 , . . . , bn } Code-Alphabet Zu kodieren ist eine Zeichenkette/Zahlenfolge ai1 , ai2 , ai3 , . . . (Bsp.: Grauwerte eines Bildes). Kodierung bedeutet nun, dass wir jedem Element ai aus dem Quell-Alphabet ein Codewort zuweisen. Eine Zeichenkette o.ä. wird somit in eine Folge von Codeworten umgesetzt, aus der dich die ursprüngliche Zeichenkette wiederherstellen lassen sollte. Dention eines I-Codes: Ein Code heiÿt I-Code, wenn kein Codewort Präx eines anderen Codewortes ist. Für einen I-Code muss die Ungleichung von Kraft gelten: k −d1 + k −d2 + . . . + k −dn ≤ 1 mit di als Länge des i-ten Codewortes. Bsp.: Blockcodes k = 2, di = 8 ∀i ⇒ 2−8 · (256) = 28 · 2−8 = 1 k1 = 0, k2 = 00 k3 = 000 ⇒ 2−1 + 2−2 + 2−3 < 1 Bsp. für präxbelasteten Code: a = 00, b = 10, c = 101, d = 110 Eine zu verschlüsselnde Symbolkette sei nun 10110. Diese lässt sich mittels des gegebenen CodeAlphabets (a,b,c,d) nicht eindeutig verschlüsseln y Mehrfachinterpretation möglich und somit kein Code! Bsp. für Code, der kein I-Code ist: ai = {1, 10, 100, 1000, 10000, . . .} Die Einsen sind in diesem Beispiel-Code eine Art Trennzeichen. Der Code ist somit präxfrei. Ein Beispiel für einen nicht-binären I-Code ist der Morse-Code. Im Folgenden arbeiten wir Hilfe eines binären Code-Alphabets, d.h. B = {0, 1}. Für eine geeignete Einschätzung einer optimalen Kodierung, werden an dieser Stelle Elementen ai aus dem Quell-Alphabet (Informationsquelle) Wahrscheinlichkeiten P (ai ) = pi zugeordnet. Gedächtnisfreie Informationsquellen zeichnen sich durch folgenden Zusammenhang aus P (ai1 . . . ain ) = P (ai1 ) · . . . · P (ain ). Mit Hilfe der Wahrscheinlichkeiten lä sich zudem die mittlere Länge aller Codewörter errechnen L= n X i=1 di · pi = X di · P (ai ), i welche ein Maÿ für die minimal mögliche Codelänge zu sein scheint. An dieser Intention angelehnt, schein die Denition des Human-Codes zu sein. KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 14 Dention eines Human-Codes: Ein Human-Code nennt man einen I-Code mit kleinst möglicher mittlerer Länge L. Bsp.: ai pi bi Ungleichung von Kraft ist erfüllt: ⇒ L 2.4.2 = = = {A, B, C, D, E, F } {0.4, 0.2, 0.1, 0.1, 0.1, 0.1} {0, 10, 1100, 1101, 1110, 1111} 1 1 1 + 2 + 4 ≤1 1 2 2 2 = 0.4 · 1 + 0.2 · 2 + 0.4 · 4 = 2.4 Konstruktion eine Human-Codes Um eine Human-Code zu entwickeln, ordnen wir zuerst o.B.d.A das Quell-Alphabet derart um, dass für die zugehörigen Wahrscheinlichkeiten gilt p1 ≥ . . . ≥ pn . Die weitere Umordnung erfolgt rekursiv. Rekursionsaufruf: a1 . . . an−1 , an | {z } a1 . . . an−1,n → p1 . . . pn−1,n = pn−1 + pn Nun ordnet man die Elemente ai um, sodass gilt p1 ≥ . . . ≥ pn−1 , und fährt mit dem nächsten Aufruf fort (?). Als Abbruchbedingung hat man dann, dass die Anzahl der ai gleich Eins sei, sodass man schlussendlich nur ein Element a1,2,...,n−1,n mit der Wahrscheinlichkeit p1,2,...,n−1,n hat. Die Wiederaufsplittung, welche den eigentlichen Human-Code ergibt, erfolgt in folgender Weise: p2,3 0 . p2 ↓ 10 →1 &1 p3 ↓ 11 Bsp.: 1. Umordnung A 0.4 E 0.2 B 0.1 C D 0.1 0.1 A 0.4 {D, F } 0.2 E 0.2 B C 0.1 0.1 A 0.4 {B, C} 0.2 {{D, F }, E} 0.4 A 0.4 {A, {B, C}} 0.6 {{D, F }, E} 0.4 {D, F } E 0.2 0.2 F 0.1 1.Schritt 2.Schritt 3.Schritt {B, C} 0.2 4.Schritt KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 15 2. Wiederaufsplittung {A, {B, C}} {{D, F }, E} →0 →1 {{D, F }, E} A {B, C} 1 00 01 A {B, C} {D, F } 00 01 10 A {D, F } E 00 10 11 A E B 00 11 010 E 11 B 010 C 011 C 011 D 100 F 101 Die eingerahmten Code-Elemente bilden einen Human-Code. Ein weiteres Verfahren, dass oft in einem Hufmann-Code mündet, ist das Shannon-Fano-CodeVerfahren. Diese Verfahren schlieÿt nach und nach die Werte hoher Wahrscheinlichkeit aus. Bei Werten gleicher Wahrscheinlichkeit kommt die Intervall-/Feldhalbierung zum Einsatz. Ausgangssituation ist wieder ein nach den Wahrscheinlichkeiten sortiertes Quell-Alphabet. Bsp.: A 0.4 1 | | | | | | | | E 0.2 B 0.1 C 0.1 D 0.1 F 0.1 0 0 0 00 00 1.Schritt 0 0 01 011 | 01 | | 010 | | | | | | | 001 0011 | 001 | | 0010 | 00 | | 000 | | 2.Schritt 3.Schritt 4.Schritt Die eingerahmten Code-Elemente bilden anscheinbar eine Art Human-Code. Nun wissen wir aber L → Lmin ≥ 1. Ist A = {a1 , a2 } binär, so gilt fà 41 r die dazugehörigen Wahrscheinlichkeiten p1 + p2 = 1. Somit folgt Lmin = 1 · p1 + 1 · p2 = p1 + (1 − p1 ) = 1 aber L > 1. Der vermeintliche Human-Code ist also gar keiner. Was können wir nun tun? (Folgende Sym- boliken können leider nicht ausformuliert werden, da ich sie selber nicht verstehe. Wer helfen kann, melde sich bitte!) Annahme: Unabhängige Informationsquelle Bsp.: Human-Code für S 2 Symbol S P (S) Symbol S2 00 01 P (S 2 ) 0.81 0.09 0 10 0 0.9 110 1 0.1 10 0.09 11 0.01 111 Lmin = 0.81 · 1 + 0.09 · 2 + 0.09 · 3 + 0.01 · 3 = 1.29 ≥ 1 Allerdings: bit 1.29 = 0.645 2 Symbol aus S KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 16 und somit Human-Code (?). Gehts vielleicht noch besser? → Shannon Entropie: n X H(S) = − pi log2 pi i=1 → für Bsp.: H(S) = 0.469 bit Für gedächtnisfreie Informationsquellen gilt: H(S k ) = k · H(S) Theorem: H(S) ≤ Lmin ≤ H(S) + 1 Anwendung für gedächtnisfreie Informationsquellen: H(S k ) ≤ Lmin (S k ) k · H(S) ≤ Lmin (S k ) H(S) ≤ Lmin (S k ) k ≤ H(S k ) + 1 ≤ k · H(S) + 1 1 ≤ H(S) + k ↓k→∞ = H(S) 2.4.3 Kodierungsarten • Human S, S 2 , S 3 , S 4 → H(S) = 0.1 mit 0.1 ≤ Lmin ≤ 1.1. Wir wissen aber nun Lmin ≥ 1, somit haben wir 1 ≤ Lmin ≤ 1.1, was eine sehr genaue Angabe der benötigten Kompressionsgröÿe für H(S) < 1 zulässt. Der Code sei also gut für H(S) < 1. Um dies zu realisieren hatte man die Idee, kein Codebuch mit zu übertragen. Dies ist bspw. der Fall beim • Wörterbuchverfahren Verfahren die nach diesem Prinzip arbeiten sind die Verfahren von Lempel, Ziv (LZ) und Lempel, Ziv und Welch (LZW), welche u.a. bei dem Bildformat .gif angewandt werden. Bsp.: Annahme: Grundcode besteht aus 256 Dualzahlen (bspw. ASCII - 8bit) Zu kodieren: Symbolkette: ABABABACD... Codebuch 0 .. . 255 2. 256 5. 257 8. 258 .. . Sendet: 1. A (∗) 3. B 6. AB 9. ABA .. . Sender .. . .. . .. . AB BA ABA .. . 8bit 9bit 9bit 9bit .. . Codebuch 0 .. . 255 4. AB 7. BA 10. ABA .. . Empfängt: A B AB A B A (∗∗) .. . Empfänger ... ... ... 8bit ASCII Grundcode 9bit 9bit 9bit Ende Codebuch 8bit 9bit 9bit 9bit ... KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 17 Die eingerahmten Ordnungsnummern kennzeichnen die Reihenfolge des Übertragungsvorgangs. Der Übertargungsvorgang läuft prinzipiell wie folgt ab: 1. Senden des ersten Symbols mit n bit 2. Erweiterung des Codebuchs des Senders (s. auch (∗)) 3. Gleichzeitig empfängt der Empfänger das mit n bit kodierte Segment und vergleicht es mit seinem Codebuch. 4. Kommt kein vergleichbarer Eintrag im Empfänger-Codebuch vor, versucht er das empfangene Segment zu interpretieren, indem er den vordersten Buchstaben hinten nochmal anhängt. 5. Kann er es dekodieren, schreibt er einen neuen Eintrag in das Codebuch, welcher das vorher empfangenen Segmente und das aktuelle empfangene Segmente enthält (s. dazu Schrittfolge 3. zu 4.). Anmerkungen: (∗) Der gesendete Buchstabe und der in der Symbolkette Darauolgende werden in das Codebuch des Senders augenommen, sofern diese Kombination noch nicht im Codebuch des Senders enthalten ist. (∗∗) Ausnahme: Das letzte A wird gleich anhangen (also beim Empfangen erkannt), weil es sich am Anfang der Zeichenkette bendet, nicht weil die Kombination schon im Codebuch steht. Die Kombination ABA wird erst im Nachhinein in das Codebuch des Empfängers übernommen. Hier wird also eine Annahme des Empfängers getroen, die scheinbar funktioniert. Wir haben somit ein selbstlernendes System. • Arithmentische Kodierung Huf f manA B C D E F Bsp.: (Quell-Alphabet mit Wahrscheinlichkeiten) 0.4 0.1 0.1 0.1 0.2 0.1 Kontruktion einer Art Histogramm: A B C D E F → → → → → → [0.0; 0.4] [0.4; 0.5] [0.5; 0.6] [0.6; 0.7] [0.7; 0.9] [0.9; 1.0] Zu kodierende Symbolkette: EBAD... 1. E ∈ [0.7; 0.9] → 0.8 → Zier 8 + Länge 1 2. EB ∈ [A; B] mit A = 0.7 + 0.4 · (0.9 − 0.7) = 0.78 und B = 0.7 + 0.5 · (0.9 − 0.7) = 0.8 → Zier 78 + Länge 2 3. EB ∈ [A; B] mit A = 0.78 + 0 · (0.8 − 0.78) = 0.78 und B = 0.78 + 0.4 · (0.8 − 0.78) = 0.788 → Zier 78 + Länge 3 → Untescheidung von 2. durch Länge 4. usw. für längere Zeichenketten Für das Beispiel EBA wird die Zahl 78 und separat die Länge 3 übermittelt. Die jeweils neuen Intervallgrenzen ergeben sich aus folgenden Formeln: neu alt A = Galt · (Galt u + Gu o − Gu ) KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 18 neu alt B = Galt · (Galt u + Go o − Gu ) mit Galt u als untere Grenze des Wahrscheinlichkeitsintervalls der vorherigen (schon kodierten) Gruppierung, Galt o als obere Grenze des Wahrscheinlichkeitsintervalls der vorherigen (schon kodierten) Gruppierung, Gneu als untere Grenze des Wahrscheinlichkeitsintervalls des neuen u (noch zu kodierenden) Symbols und Gneu als obere Grenze des Wahrscheinlichkeitsintervalls o des neuen (noch zu kodierenden) Symbols. Für längere Zeichenketten wird eine rieÿige Zahl entstehen, welche nur mittels partieller Speicherung bearbeitet und übermittelt werden kann. Bspw. kann sie mit Hilfe mehrerer long int's gespeichert werden: 1.long 78124 . . . + 2.long 93842 . . . → Zusammensetzung zum Gesamtcode • Burrows-Wheeler-Kompressionsalgorithmus Bsp.: Zeichenkette: BESEN 1. Rotieren N E S E 2. Sortieren 1. 2. 3. 4. 5. B N E S B E E N E S N B S E (∗) E B N E S B E E N S E B N E E N S B E S E B N S B E E (∗∗) 3. Letzte Spalte (∗) kodieren 4. Aus letzter Spalte (∗) wird erste Spalte (∗∗) durch Sortieren erzeugt 5. Einstrittspunkt mit abspeichern (NS ...) 6. Move-to-Front-Stufe Zu kodieren: NSBEE E N S ⇒ Codenr. für ersten Buchstaben N: 2 1 2 3 N B E S Nun N nach vorne holen: ⇒ Codenr. für S: 3 0 1 2 3 Alphabetisch sortiert: B 0 Nun S nach vorne holen: S 0 N 1 B 2 E 3 ⇒ Codenr. für B: 2 Nun B nach vorne holen: B 0 S 1 N 2 E 3 ⇒ Codenr. für E: 3 E B S 0 1 2 B E E 2 3 0 N 3 ⇒ Codenr. für E: 0 Nun E nach vorne holen: =⇒ kodiert: N 2 S 3 Das gewünschte Ziel dieses Verfahrens ist ein Ausdruck, bestehend aus Ziern und möglichst vielen Nullen, bspw. 23200000004000000 . . .. Dies ist eine Folge, die noch mit einem beliebigen Kompressionsalgorithmus zu kodieren ist. Viele Nullen sind erwünscht, da diese eine Art Ordnung erzeugen, was eine geringe Entropie und somit eine hohe Kompressionsrate zur Folge hat. KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 19 • Kodierung generell Ein Kompressionsalgorithmus beinhaltet ein Vorhersagemodell und einen universellen Kodierer → Prädiktor - Korrektor. Ein Beispiel für einen der einfachsten Prädiktoren ist der Identische Operator I, der u.a. bei dem PNG-Bildformat Anwendung ndet. Weitere Kompressionsarten: Verlustfreie Kompression Bsp.: JPEG-, MPEG-Bildformat → Clusterbildung Transformationen Bsp.: JPEG → DCT oder Wavelets (+Quantisierung,+Human) Vektorquantisierung Bsp.: TIFF-, EPS-Bildformat Fraktale Kodierung Approximation 2.4.4 Kryptographie-Arten Die Kryptographie hat zum Ziel die sichere Verschlüsselung von Daten. a) Kryptographie mit privatem Schlüssel Standards: DES, AES Bsp.: Symmetrisches Verfahren mit privatem Schlüssel Kodierung: v =u+k mit v als verschlüsselte Nachricht, u als ursprüngliche Nachricht, k als Key und + als bspw. bitweise Addition (XOR). Dekodierung: v+k =u+k+k =u Diese Verfahren wird als one-time-pad (G.S.Vernam 1917) bezeichnet, da nur bei einmaliger Anwendung eines zufällig gewählten Schlüssels k dieser auch absolut sicher ist. Ein Problem dabei besteht in der Anwendung nur eines Schlüssels zur Kodierung eines ganzen Bildes. Dies hat bei zeilenweise Verschlüsselung zur Folge, dass die Kodierung nach unten hin immer unsicherer wird. Ist der Key einmal gefunden, lässt sich die Kodierung problemlos zurückführen. Bsp.: ENIGMA • Sehr sicheres Verschlüsselungssystem aus dem 2.Weltkrieg • Verwendung eines Schlüssels, der sich im Laufe der Verschlüsselung noch verändert y für jeden Text völlig anderer Schlüssel b) Public-Key-Kryptographie Wichtig: Einweg-Funktion, Einweg-Hashfunktion H(M) h = H(M ) mit M als Nachricht und h als Hashwert. Zudem gilt: (a) Zu gegebenem M ist es leicht, h zu berechnen (ca. 1ns Laufzeit) (b) Zu gegebenem h ist es schwer, ein M zu berechnen (ca. 2-3 Tage Laufzeit) (c) Zu gegebenem M ist es schwer, eine Nachricht M' zu berechnen mit H(M)=H(M') KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 20 Bsp.: Passwort-Verwaltung am PC Intern legt das System das Passwort als h ab und bedient sich zudem eines weiteren Tricks, nämlich der SALT-Liste. Die SALT-Liste ist eine Liste mit weiteren Dekodierungsschlüsseln, aus der das System sich einen auswählt um h noch weiter zu kodieren und M somit noch mehr abzusichern. Bsp.: RSA-Algorithmus Dieser wurde 1978 von Rivest, Shamir und Adelman erfunden. Kodierung: • Wähle zufällig zwei groÿe Primzahlen p und q • Bilde n = p·q und nutze 0, 1, . . . , n−1 (Folge von bits) als Quellalphabet (Bsp.: n = 3 immer 3bit Blöcke → Alphabet: 0 . . . (23 − 1)) → • Wähle zufällig eine Zahl i mit 0 ≤ i ≤ n − 1, die relativ prim zu φ(n) ist, wobei φ(n) die Euler'sche φ-Fkt. ist (φ(n) ≡ Anzahl der Zahlen kleiner n, die relativ prim zu n sind) → φ(p) = p − 1, φ(q) = q − 1 → φ(n) = φ(p · q) = (p − 1) · (q − 1) ⇒ Wir können nun eine Zahl j = 0, . . . , n − 1 nden, sodass i · j ≡ 1(mod φ(n)). • Kodiere ein Symbol v = 0, 1, . . . , n − 1 w ≡ vi mod n und sende w statt v! Dekodierung: v ≡ wi mod n M kann somit leicht berechnet werden. Der Algorithmus arbeitet folglich mit zwei Schlüsseltypen, dem public key (hier: n und i), der öentlich zur Verfügung steht, und dem private key (hier: j, p und q), den nur der Empfänger benötigt. Diese Verschlüsselung kann nur geknackt werden, wenn φ(n) gefunden wird. Dies ist nur möglich wenn man n = p · q so lösst, dass φ(n) = (p − 1) · (q − 1). Dies ist jedoch ein technisches Problem, da bisherige Primzahlfaktorisierungsalgorithmen sehr langsam sind und zudem p und q sehr groÿ gewählt wurden. Bemerkung: I) lim k→∞ π(k) · log k =1 k wobei π(k) der Anzahl der Primzahlen kleiner k entspricht. → π(1050 ) ≈ 1050 ≈ 5 · 1047 log 1050 y Es gibt auf jeden Fall genug Primzahlen, um die Sicherheit dieser Kodierung zu gewährleisten. II) Generierung von Primzahlen: Wähle zufällig 50 stellige Zahlen (hohe Primzahlwahrscheinlichkeit) und teste diese auf Primzahleigenschaften. Hierfür existieren bereits einige sehr schnelle Algorithmen. KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 21 III) Derzeitiger Standard: n → 1024bit ⇒ p, q → 512bit c) Digitale Signaturen Kodierung: • Wende auf Text eine Einweg-Hash-Fkt. an und füge h dem Text hinzu • Kodiere den Hashwert h mit einem privatem Schlüssel • Sende Text in Form von dem verschlüsselten h zum Empfänger Dekodierung: • Dekodiere h mit public key • Berechne h und vergleiche → Umgekehrter RSA-Algorithmus 2.5 Sortieralgorithmen Beispiele: • Distribution-Sort • Selection-Sort • Insert-Sort • Bubble-Sort • Quick-Sort • Merge-Sort • Tree-Sort • Heap-Sort 2.5.1 Distribution-Sort (Counting-Sort, Histogramm-Methode) Idee: Adressraum (Anzahl theoretisch möglicher Zustände) ←→ Anzahl tatsächlich zu sortierender Zustände Voraussetzung: Adressraum sei "klein" Bspw. Adressraum aus 256 verschiedenen Zuständen 0 0 0 0 0 0 t t t t t t ... 0 1 2 3 4 5 0 t 255 Bsp.: 3 3 5 7 255 255 0 0 ,→ 2x 3, 1x 5, 1x 7, 2x 255, 2x 0 ⇒ Anlegen eines Histogramms / Adressraums: 2 0 0 2 0 1 0 1 0 t t t t t t t t t ... 0 1 2 3 4 5 6 7 8 2 t 255 → nun in richtiger Reihenfolge (sortiert) ausgeben: 0 0 3 3 5 7 255 255 → fertig! Problem: Adressraum muss klein sein! Da hier nur Arbeit mit int (256x 4 Byte) ist diese Bedingung erfüllt. Aber bei Arbeit mit gröï¾ 21 eren Objekten erfolgt Stack-overow / Speicherplatzüberschreitung. Bsp.: Grauwerte eines Bildes KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE • Festlegung von Clustern • Sortierung der Grauwerte in dem Cluster • Übertragung des clusterzentralen Pixels in ein neues Bild • Cluster wandern lassen Abbildung 2.1: Arbeitsschema des Distribution-Sort-Algorithmus ⇒ Medianlterung (Median: Der bzgl. der Sortierung in der "Mitte" stehende Wert) Bsp.: • 5 3 200 7 16 • Sortierung • 3 5 7 16 200 Der umrahmte Wert wird als Median bezeichnet. Bsp.: Zeichenkette mit max. fünf Zeichen, Kleinbuchstaben und Leerzeichen • Zustände: 26 Kleinbuchstaben + Leerzeichen = 27 Zustï¾ 21 nde pro Zeichen • Adressraum: 275 max. mï¾ 21 gliche verschiedene Zustände Zustand ≡ Zeichenkette ←→ Nummer → 27 adisches Zahlensystem a b c .. . z 2.5.2 → → → → → → 0 1 2 3 .. . 27 Selection-Sort Bsp.: Z A D B E 1. Suche Maximum und Vertausche mit dem hintersten Wert des aktuellen Bereichs →EADBZ 2. Suche Maximum des um Eins kleineren Bereichs und Vertausche →BADEZ →BADEZ → A B D E Z → Fertig! 22 KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.5.3 Insert-Sort Bsp.: Z A D B E 1. Nehme erstes Element und Vergleiche mit Folgewert → A Z D B E → Umrahmte Werte bereits sortierte Liste 2. Nun nehme Folgeelement und füge es an richtiger Position in die sortierte Liste ein → ADZ BE → ABDZ E → A B D E Z → Fertig! 2.5.4 Bubble-Sort Bsp.: Z A D B E 1. Nehme erstes Element und Vergleiche stückweise mit den Folgeelementen → Z↔A D B E → A Z↔D B E → A D Z↔B E → A D B Z↔E → A D B E Z → Erster Durchlauf fertig! 2. Nächster Durchlauf mit dem nun ersten Element → A↔D B E Z → ... 3. So oft wiederholen bis Liste wirklich sortiert! Bsp. in C/C++: int i, n, anz, result; char namen[20][16], hname[16]; ... //Eingabe von n<=20 Strings do { anz=0; //Prï¾ 12 fvariable for(i=0; i<n-1; i++) { if(strcmp(namen[i],namen[i+1])>0) { ++anz; strcpy(hname, namen[i]); strcpy(namen[i], namen[i+1]); strcpy(namen[i+1],hname); } } }while(anz>0) Anwendungsbeispiel: Aufgabe: Der zu sortierende Datenbestand besteht aus Datensätzen, die enorm viel Speicher benötigen. Wunsch: Sortieren ohne die Datensätze zu bewegen. Lösung: Lege ein Feld von Pointern an, diese zeigen jeweils auf die Datensätze. Sortierung durch Umordnung der Pointer. Ordnung k 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Datensatz a[k] A S O R T I N G E X A M P L E Pointer p[k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Nach Sortierung: Pointer p[k] 1 11 9 15 8 6 14 12 7 3 13 4 2 5 10 23 KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.5.5 24 Tree-Sort Bsp.: Zeichenkette: ASORTINGEXAMPLE Ziel: Anlage eines Baumes (tree), der auf einfach Art und Weise ausgewertet werden kann und somit die korrekte Sortierung zurückliefert. 1. Erster Buchstabe (A) bildet den ersten Knoten 2. Vergleiche zweiten Buchstaben (S) mit dem Knoten (A) 3. Je nach Vergleichsergebnis Buchstaben dementsprechend an den Baum hängen • Buchstabe ≤ Knoten ⇒ Buchstabe wird ein neuer linker Knoten • Buchstabe > Wurzel ⇒ Buchstabe wird ein neuer rechter Knoten • Ist die zugewiesene Position an dem Baum schon durch einen Knoten besetzt, wird der neue Buchstabe wiederrum mit diesem verglichen und dementsprechend weitergeschoben Am Bsp.: A<S⇒Buchstabe S wird eine Knoten auf der rechten Seite 4. Vergleiche dritten Buchstaben (O) mit dem ersten Knoten (A): A<O⇒Soll ein Knoten auf der rechten Seite werden 5. Rechte Seite schon besetzt (durch S) y Vergleiche neuen Buchstaben mit zweiten Knoten: S>O⇒O wird linker Knoten bzgl. des Knotens S 6. Vergleiche vierten Buchstaben (R) mit dem ersten Knoten (A) 7. . . . Ergebnis:(funktioniert noch nicht) Der zweite Teil der Sortierung erfolgt nun durch die geeignete Ausgabe. Diese wird rekursiv vorgenommen (s. dazu 2.6): 1. Ausgabe des linken Astes 2. Ausgabe des Knotens 3. Ausgabe des rechten Astes 2.5.6 Merge-Sort Merge-Sort ist ein rekursiver Algorithmus (s.a. 2.6), der hier nur kurz in Form eines Pseudo-Codes wiedergegeben werden soll: Modul Sort(Liste) IF (n>1) THEN Sort(erste Listenhaelfte) Sort(zweite Listenhaelfte) Mische beide zusammen Schema zum Mischen der beiden sortierten Listen: Teilliste 1 A 1.Vgl. ←→ 2. Vgl. Teilliste 2 A C D ←→ Ausgabe:A 2. → C > A Ausgabe:A .% 3.Vgl. 1. → A = A B ... F 3. → C > B Ausgabe:B KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.5.7 25 Quick-Sort Der Quick-Sort ist ein rekursiver Algorithmus (s.a. 2.6), der hier wiederum nur an einem Beispiel gezeigt werden soll. Bsp.: Quick-Sort mit Median-Partitionierung void quicky(double a[], int l, int r) { int i; if(r>l) { i=partition(a,l,r); quicky(a,l,i-1); quicky(a,i+1,r); } } Schema zur Funktion partition Ursprüngliche Liste 5 Nach Medianndung 5 Median: 6 1 1 3 3 7 6 9 6 8 9 7 8 Die Aundung des Medians ist jedoch im Vgl. sehr zeitaufwendig. Aus diesem Grund sind andere Quick-Sort Algorithmen günstiger. 2.5.8 Permutation Die Permutationen zerfallen in Zyklen: µ 1 2 3 4 3 2 1 4 5 5 ¶ = (1 3) · (2) · (4) · (5) Die Sortierung des obigen Beispiels zerfällt in folgende Zyklen: p[k] → (1)(2 11 13)(3 9 7 14 5 8 12 4 15 10)(6) Mit Hilfe dieser Zyklen, kann man die Datensätze ezient (d.h. maximal einmal) bewegen und sortieren, wobei die Einerzyklen erhalten bleiben. Der Rest wandert gemäÿ der obigen Zyklen. 2.6 Rekursivität Fast jeder Algorithmus, der iterativ (in Schleifen etc.) ausgeführt werden kann, kann auch rekursiv geschrieben werden. Rekursive Funktionen arbeiten nach dem Prinzip "Teile und Herrsche". Es sind Funktionen, die sich, bis eine bestimmte Abbruchbedignung erfüllt ist, selber aufrufen (in der Regel mit anderen Parametern). Dies soll anhand eines kurzen Pseudocodes dargestellt werden: Modul A(D) IF "triviales Problem" THEN return(explizite, triviale Loesung) ELSE a) Teile D in D1,D2,...,Dn und Berechne A(D1),A(D2),...,A(Dn) b) Setze Teilloesung zur gesamten Loesung A(D) zusammen und return (A(D)) Wie immer erfolgt die weitere Erläuterung an Beispielen. Bsp. 1: Türme von Hanoi KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 26 Schema der Tuerme von Hanoi hanoi(a,b,c,n); /* a-Quellstapel; b-Zielstapel; c-Zwischenstapel (Arbeitsspeicher); n-Anzahl der Scheiben */ void hanoi(int a[], int b[], int c[], int n) { if(n==1) //Nehme oberste Scheibe von a und lege sie auf b oben ab else { hanoi(a,c,b,n-1); hanoi(c,b,a,n-1); } } Bsp. 2: Summe von n Zahlen → a1 + a2 + . . . + an−2 + an−1 → D2 ; Vorherige Summanden in D1 int summe_iterativ(int a, int n) { sum:=0; for(int i=0; i<n; i++) { sum+=a[i]; } return(sum); } int summe_rekursiv(int a, int n) { if(i==1) return a[0]; else return(summe(a,n-1)+a[n-1]); } Bsp. 3: Fakultät n! = n · (n − 1)! int fak(int n) { int fakult; if(n==0) fakul=1; else fakul=fak(n-1)*n; return(fakul); } Bsp. 4: Umkehrung von Strings void reverse(void) { char z; z=GetChar(); if(z!=" ") { reverse(); PutChar(z); } } //Umkehrung aufgrund der Stack-Abarbeitungsreihenfolge der CPU --> Schema!! KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 27 Bsp. 5: Umwandlung einer Dezimalzahl in eine Hexadezimalzahl void hex(int x, char a[16]) { if(x<16) PutChar(a[x]) else { hex(x/16,a); PutChar(a[x%16]); } } Bsp. 6: Fibonacci-Zahlenfolge a0 = 0, a1 = 1, a2 = 1, . . . , an = an−2 + an−1 int fib(int n) { int fibon; if(n<2) { if(n==0) fibon=0; if(n==1) fibon=1; } else fibon=fib(n-1)+fib(n-2); return(fibon); } Problem: an an−1 an−2 an−3 an−2 an−3 an−4 ... ... ... ... Mehrfachberechnung von Zweigen y fehlende Ezienz, Rekursion sollte hier nicht genutzt werden. Bsp. 7: Urnenmodell Startkonguration: Urne mit weiÿen (◦) und schwarzen (•) Kugeln. Mit jedem Zug wird entweder eine schwarze oder eine weiÿe Kugel entfernt, bis die Urne völlig leer ist. Gesucht sind alle möglichen Kombinationen der Zugreihenfolge. ◦◦• ◦• ◦◦ • ◦ ◦ leer leer leer wws wsw sww Vorgehensweise: Anzahl der Blätter ist die Anzahl der möglichen Kombinationen ⇒ Blätter rekursiv zählen, d.h.u.a. Baum in linken und rechten Teilbaum aufteilen. Bsp. Bsp. Bsp. Bsp. 8: Tree-Sort (s. 2.5.5) 9: Merge-Sort (s. 2.5.6) 10: Quick-Sort (s. 2.5.7) 11: Matrixmultiplikation (folgt erst später) KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 2.6.1 28 Kellerung Die Rekursion im numerischen Sinne basiert auf dem Kellerprinzip, welches unmittelbar mit der Abarbeitungsreihenfolge des Stacks (temporärer Datenstapel) durch die CPU zusammenhängt. Daten, mit denen momentan gearbeitet wird, werden auf den Stack (Stapel) abgelegt. Dies erfolgt logischerweise von unten nach oben. CPU nimmt zur Bearbeitung die Daten von oben weg, was dazu führt, dass zuletzt abgelegte Daten zuerst bearbeitet werden. Die wird bei der Rekursion ausgenutzt. Ein groÿes Problem wird auf die Trivialfälle heruntergebrochen, welche zuletzt auf dem Stack abgelegt werden. (Die Trivialfälle sind meist die, bei denen nur noch wenige Einzelelemente bearbeitet werden müssen.) Nach der Aufsplitung fängt die CPU an mit auswerten und nimmt sich zuerst die obersten Daten vom Stapel, die Trivialfälle. Daraufhin kommen die Probleme mit der nach unten hin (bzgl. des Stacks) zunehmenden Komplexität. Da die Trivialfälle schon berechnet wurden sind, können die komplexeren Probleme nun mit deren Hilfe u.U. schneller bearbeitet werden. Der Geschwindigkeitsvorteil ist jedoch bei vielen einfachen Algorithmen hinfällig. Berechnet man bspw. eine Summe von Zahlen rekursiv und iterativ, wird man keinen Geschwindigkeitsgewinn feststellen, da immer eine bestimmte feste Anzahl an Summationen gemacht werden muss, egal ob iterativ oder rekursiv. Bei der Multiplikation zweier rieÿiger Matrizen sieht dies jedoch ganz anders aus. Fortsetzung folgt in den Semesterferien 2.7 Bewertung von Algorithmen Wir haben zwei Funktionen f (n) und g(n), wobei n die Anzahl der eingehenden Daten ist. f (n) sei eine Testfunktion, die die Zeitkomplexität eines Algorithmus' wiederspiegelt. g(n) dagegen beinhaltet Vergleichswerte für den Algorithmus (bspw. Rechenzeit). Wir denieren nun drei Ordnungsmengen: g ∈ O(f ) ≡ ∃c > 0 ∃n0 > 0 ∀n ≥ n0 : g(n) ≤ c · f (n) g ∈ Ω(f ) ≡ ∃c > 0 ∃n0 > 0 ∀n ≥ n0 : g(n) ≥ c · f (n) g ∈ Θ(f ) ≡ g ∈ O(f ) ∧ g ∈ Ω(f ) Beispiel: g(n) = n2 + 3n ≤ 2n2 ∀n ≥ n0 = 3 → f (n) falsch, da f (n) < g(n) für n À 0 g(n) = 2n2 + 5n ≤ c n2 → nun f (n) richtig, da f (n) > g(n) für n À 0 Je nach Anwendung können verschiedene Fälle bei dem Durchlauf eines Algorithmus' eintreten. Man unterscheidet "best", "average" und "worst case". Übliche Klassen von f • 1 ↔ g(n) ≤ c · 1 → logarithmische Laufzeit • log n • n → konstante Laufzeit → lineare Laufzeit • n log n • n2 , n 3 , nt → fast lineare Laufzeit → quadratische, kubische, polynomiale Laufzeit Um g(n) zu bestimmen, muss noch festgelegt werden, was die "Elementarbausteine" sind (was auch immer das heiÿen mag). Bsp. 1: Selection Sort KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE 29 • Grundoperation: Vergleich if(a[i]>max) max=a[i]; • 1. Durchlauf t ... 1 • 2. Durchlauf t ... 1 → g(n) = n X i= i=1 t n t t n−1 n → n-Operationen → (n-1)-Operationen n2 n n(n + 1) = + ≤ c · n2 2 2 2 ⇒ g(n) ∈ O(n2 ) Bsp. 2: • Distribution Sort: O(n) • Skalarprodukt: O(n) • Matrixmultiplikation: O(n3 ) • Bubble Sort: "best case" O(n), "worst case" O(n2 ) • Insert Sort: O(n2 ) Bsp. 3: Binäres Suchen ↔ Sequenzielles Suchen (O(n)) Voraussetzung: Datenbestand muss bereits sortiert sein n = 2m Elemente ¶ µ 1 1 1 · · ... · = 1 ⇒ m = log2 n n 2 2 2 m mal Komplexität • g ∈ O(f ) höchstens • g ∈ Ω(f ) niedrigstens • g ∈ Θ(f ) genau Bsp.: Merge Sort T (n) = h ³n´ ni +c + cn 2 2T 2 ³ n ´4 4T + 2cn h 4³ n ´ ni +c + 2cn 4 2T 4 ³ n ´8 8T + 3cn ³8 n ´ 16T + 4cn 16 n · T (1) + (log2 n)cn ⇒ T (n) ∈ O(n log2 n) = = = = = Bsp.: Quicksort KAPITEL 2. ALGORITHMEN ZUR BILDANALYSE "worst case" T (n) = = T (n − 1) + T (1) + bn + c b n · T (1) + (n2 + n − 2) + (n − 1) · c 2 ⇒ T (n) ∈ O(n2 ) = n−1 1 X (T (k) + T (n − k)) + bn + c n−1 "average case" T (n) k=1 ⇒ T (n) ∈ O(n log n) 30 Kapitel 3 Die Sprache C/C++ 3.1 Geschichtlicher Überblick Die Programmiersprache C/C++ entstand 1973 um Programme für UNIX zu entwerfen. Dem entsprechend ist sie stark an UNIX angelehnt. Entwickelt wurde C/C++ von Brian Kerningham und Dennies Ritchie. 1978 erreichte die Sprache den Quasi-Standart K und R C und 1988 den Standart ANSI-C. Nach Better C folgte die Standartisierung unter ISO und die objektorientierte Weiterentwicklung C++. 3.2 Einfache Syntaxübersicht 3.2.1 Quellcodebeispiel #include<stdio.h> #include<math.h> #include<image.h> /* Kommentar */ int main() { int i,j,k; c=d; } //standart input/output //Mathebibliothek //ICE Bibliothek //Kommentar //initiieren des Hauptprogrammes //Anfang Block //Variablendeklaration als Integer //c ergibt sich aus d //Ende Block 3.3 Der Zeichensatz in C/C++ Zum C-Zeichensatz gehören Klein- und Groÿbuchstaben, Ziern und die "üblichen Sondereichen". Kommentare werden mit /*...*/ vom restlichen Quelltext getrennt. Mit \\ kann EINE Zeile auskommentiert werden. Variablennamen bestehen aus Buchstaben, Ziern und _, wobei der Name mit einem Buchstaben oder _ beginnen muss. Schlüsselwörter sind zum Beispiel: double, if, while, void, else,... . Sie haben eine feste Bedeutung für den C-Compiler. Mit Makronamen werden vom System feste Grössen deniert. Die Zahl π ist so bspw. unter M_PI und die eulersche Zahl unter M_E gespeichert. Makronamen bestehen aus Groÿbuchstaben und _ . 31 KAPITEL 3. DIE SPRACHE C/C++ 3.4 32 Der Präprozessor Der Präprozessor bereitet den Quelltext für den Compiler auf. So werden zum Beispiel Kommentare gelöscht sowie include's ausgeführt. Also der Quelltext der eingefügten Dateien und Funktionen kopiert. So bedeuten: 1. #include<datei.h> \\suche in speziellen Systemverzeichnissen /usr/include 2. #include "studio.h" \\suche im aktuellen Verzeichnis 3.5 Kleines Programmbeispiel #include<stdio.h> #include<math.h> #include<image.h> int main() { float radius,height volume, surface_area; printf("\nBerechnet Volumen und Oberflaeche eines Zylinders\n"); \\Ausgabe des Strings \\"\n" bedeutet newline printf("\n\nGib den Radius ein: "); scanf("%f",&radius); \\liest die Variable radius formatiert in Float ein printf("\nGib die Hoehe ein: "); scanf("%f",&height); volume=M_PI * radius*radius*height; surface_area= 2.0 * M_PI*radius*(radius+height); /*Die Verwendung einer normalen 2 statt 2.0 kï¾ 12 nnte u.U. zu einem int statt float fï¾ 21 hren*/ printf("\nVolumen ist:%10.4f",volume); } \\Formatierung in 10 Vorkommastellen \\und 4 Nachkommastellen (Float) printf("\nOberflaeche ist:%10.4f",surface_area); 3.6 Einfache Ein -und Ausgaben • printf("controlstring",argument list); Argumente sind also: Zeichenketten + Formatbeschreiber + Escapesequenzen Formatbeschreiber sind u.a.: %f oat, %lf double, %i %d integer, &li long, %s String, %c char • scanf("%f%f%f",&e,&b,&h); In der Eingabe werden die Variable mit Leerzeichen getrennt. Der Adressoperator & sagt dem Programm, dass es den eingelesenen Wert, der bswp. fï¾ 21 r b bestimmt ist, auf die Adresse von b schreiben soll. ffslush(stdin) leert den Standardein-/-ausgabepuer. • Spezielle C++ Ausgabefunktion cout<<a<<b<<h<<endl; cout<<'\n'<<variable<<"Gib Text ein:"<<endl; • Spezielle C++ Eingabefunktion KAPITEL 3. DIE SPRACHE C/C++ 33 cin>>a>>b>>c; • Bsp.: Ein kleines Programm welches sich selbst ausdruckt #include<stdio.h> main() { char*c="main(){char*c=%c%s%c;printf(c,34,c,34,10);}%c"; printf(c,34,c,34,10); } Ausgabe: main()char*c="main()char*c=%c%s%c;printf(c,34,c,34,10);%c";printf(c,34,c,34,10); 3.7 Datentypen 1. integer: • int a,b,c,D=34; • long int a,b=-5,c; • short int c; • unsigned int A,B=5; (ohne Vorzeichen) 2. oat: • oat c=1.15; • oat d=3.1e-15; • double c,d; (bevorzugt verwenden) • long double e; 3. characters: • char c1,c2; (Länge 1 Byte • char d='F';(ASCII-code des Zeichens F) • char → integer mit 1 Byte • signed char a; (vorzeichenbehaftet) • unsigned char d; (volle Bytes ohne Vorzeichen 4. strings: • char name [20]; + "Nullbyte" (Array mit chars) • wchar_t a='a'; (2 Byte) 5. Konstanten: • direkt • #dene PI 3.14 (Textersetzung) (#Erkennungszeichen für Präprozessor) • const double pi=3.14; Gibt man im Programm Zahlen ein, so werden diese entsprechend interpretiert. Alle Zahlen ohne Dezimalpunkt werden als integer und alle mit als double behandelt. Desweiteren kann man integer in Oktal- bzw. Hexadezimalschreibweise angeben, indem man eine führende 0 bzw. 0x voranstellt. KAPITEL 3. DIE SPRACHE C/C++ 34 6. Typumwandlungen: • Problem: a=(1/3)*h*g //=0, da Argumente int sind und 1/3=0 • Lösung 1: a=(1.0/3.0)*h*g //richtiges Ergebnis, da 1.0 nun als float interpretiert • Lösung 2: a=1.0/3.0*h*g //auch richtig • Lösung 3: a=1*h*g/3 //auch richtig, aber nicht schï¾ 12 n • Lösung 4: a=(float)1/(float)3*h*g //Beste Lï¾ 21 sung • Typumwandlung (oat) wandelt "int 1" in "oat 1.0" Man unterscheidet ASCII-Kode und UNICODE (4 Byte) um Kompatibilität zu gewährleisten und Nullbytes zu vermeiden, benuzt man UTF8 (Zeichen mit variabler Länge). 3.8 Gültigkeitsbereiche Variablen innerhalb eines Blockes {. . .} sind ausserhalb (davor) nicht bekannt. int f; \\global gueltig int main() { double c,d; \\im gesamten Block main() gï¾ 21 ltig { int i,j,k; char c; \\fï¾ 21 r diesen Unterblock gï¾ 21 ltig c='P'; \\geschrieben wird auf zweitem c (char) } for(int i=0,i<n;i++)\* i wird direkt in den Konditionen der for-Schleife deklariert und ist auch nur in dieser gï¾ 21 ltig */ {...} } 3.9 Operationen 1. Zuweisung (=): a=c*d; (a ergibt sich aus c mal d) a=b=c=d=100; (Mehrfachzuweisungen erlaubt) 2. Vergleichs-Operatoren: < > (kleiner/gröÿer als) <= >= (kleiner/gröÿer gleich) == != (gleich(ist nicht dasselbe wie =)/ungleich) 3. Rechen-Operatoren: + - * / %(Modulo) Bsp.: float a,b,c; c=a/b; (Gleitkommadivision) int a,b,c; c=a/b; (Ganzzahlige Division konvertiert c in ganze Zahl) Denition Modulo (%): a≡b(mod n), a-b=k*n, k ∈ Z Bsp.: c= 7 % 2; Rest 1 c=-7 % 2; Rest -1 KAPITEL 3. DIE SPRACHE C/C++ 35 4. Inkrement,Dekrement int i; i=i+1; → i++ oder ++i i=i-1; → i-- oder --i Präxnotaion y=++x+5; → y=(x+1)+5 Postxnotation: y=x++ +5; → y=(x+5)+1 5. Zuweisungsoperatoren: += -= *= /= %= Bsp.: x=x+delta; → x+=delta; (unärer Operator +=) 6. Kommaoperator Bsp. 1: for(int i=0, int j=0; i<10, j<10;i++,j++)...; //statt for(int i=0; i<10; i++) for(int j=0; j<10; j++) ...; Bsp. 2: int i = (1,2,3,5); Bsp. 3: int a[50,30][40]; //i=5; //a[30][40] 7. Darstellung des n-ten Logarithmus: logn a := a=log(a)/log(2); ln a ln 2 8. Logische Ausdrücke: • Vergleichsausdrücke (z.B. a==1) • Nullidentitäten zahl!=0 → true zahl==0 → false Bsp.: 3-3 liefert false; 5 liefert true, da 5<>0 • Klasse Boolean - bool(nicht Standard) • Bsp.: g=GetVal(pic1,i,j); if(g>=uGrenze) PutVal(pic2,i,j,255); else PutVal(pic2,i,j,0); 3.10 Prioritäten 1. () 2. + - unäre Operatoren (Vorzeichen) 3. ++ -4. * / % 5. + 6. = += -= Bei gleichwertigen Operatoren gilt Linksassoziativität. KAPITEL 3. DIE SPRACHE C/C++ 3.11 36 Einfache Steuerstrukturen 1. while-Schleife while(condition){...} Bsp.: int n=1; while(n<=10) { m=n*n; n++; printf("Quatratzahlen%d",m); } 2. for-Schleife for(int i=0;i<=10,i++) Bsp.: Mittelwert aller Grauwerte double sum=0.0; int i,j,g; int dimx, dimy; for(i=0;i<dimx;i++) for(j=0; j<dimy; j++) { g=GetVal(pic1,i,j); sum+=g; } sum/=double(dimx)*double(dimy); 3. do-Schleife do {...} while(condition); 4. Abbruchbefehl break sorgt für das vorzeitige Verlassen einer gesamten Schleife 5. Abbruchbefehl continue sorgt für das Verlassen genau eines (des aktuellen) Schleifendurchlaufes 6. Einfache Verzweigung if(condition){"cond. true" Block} else{"cond. false" Block} 7. Mehrfachverzweigungen: switch(Bedingung) { case konst_1: Ausdruck_1; break; case konst_2: Ausdruck_2; case konst_3: Ausdruck_3; break; default: Ausdruck_normal; } // Bedingung muss ganze Zahl ergeben // im Fall 1 wird 1. gemacht // im Fall 2 wird 2. und 3. gemacht 8. Bedingte Ausdrï¾ 21 cke: Bedingung ? ausdruck_ja : ausdruck_nein; KAPITEL 3. DIE SPRACHE C/C++ 3.12 37 Casting Das Casting wandelt einen Zahlentyp in einen anderen um. In C gibt es dafür die Operatoren (type) wie z.B. (int). In C++ kann man alternativ dazu den Ausdruck klammern, der gecastet werden soll. Bei komplizierteren Ausdrücken, kann es notwendig sein beides zu klammern. Mit Casting kann man z.B. explizit runden. Beispiel: • int c; float d; c=2.6; c=int(2.6); d=Random(1); c=int(d+0.5); c=int(-d-0.5); c=floor(d); //c==2; besser ist // erzeugt eine Zufallszahl zwischen 0 und 1 // bei negativen Zahlen // oder die Funktion • (short int)(a * 1.5) 3.13 Felder (Arrays) 3.13.1 Eindimensionale Felder (Vektoren) Deklaration: type name[laenge]; Dabei muss die Variable laenge jedoch eine Konstante sein. Nicht erlaubt sind also Felder, deren Gröÿe man vorher einliest. Beispiel: #define MAX_SIZE 80 float x[MAX_SIZE]; int squares[5]={1,2,4,9,16}; int null[8]={0}; int zwei[7]={2,2}; // Initalisierung // fuellt alle Elemente mit 0 // fuellt alle weiteren mit 0 Die Indizierung der Felder beginnt bei 0. Beispiel: double x[10],y[10],skalar=0.0; for(int i=0;i<10;i++) skalar+=x[i]*y[i]; 3.13.2 C-Zeichenketten Zeichenketten in C bestehen aus einem char-Feld. Die eigentliche Zeichenkette wird von einem Nullbyte abgeschlossen. [numbers=none] char material[6]={'s','t','e','e','l','\0'}; //ergibt das gleiche wie material="steel"; Für die Verarbeitung von C-Zeichenketten gibt es spezielle Funktionen, welche mit #include <string.h> eingebunden werden müssen. 1. l=strlen(Z_kette); gibt die Länge der Zeichenkette bis zum Nullbyte 2. strcpy(ziel, quelle); kopiert die Quelle auf das Ziel 3. strcat(a,b); hängt b an a an. 4. result=srtcmp(a,b); vergleicht die Zeichenketten a und b. Dabei vergleicht es Zeichenweise beim ersten Zeichen beginnend. KAPITEL 3. DIE SPRACHE C/C++ if (result < 0); if (result == 0); if (result > 0); 38 // "a < b" // "a == b" // "a > b" 5. sprintf(Zeichenkette, "Text", arg_list); schreibt den Text, wie printf in die Zeichenkette. 3.13.3 Zwei- und mehrdimensionale Felder (Matrizen und Tensoren) Beispiel: int int int int matrix[dimx][dimy]; kmplx[2][4][8][16][32]; zwei_zwei[2][2]={{1,2},{3,4}} zaehlen[3][3]={1,2,3,4,5,6,7,8}; // Initalisierung // ein Element fehlt -- aber welches? Bei Zuweisungen in for-Schleifen, die sich nicht ändern, sollte man eine Hilfsvariable verwenden, da das Programm sonst bei jedem Zugri erst berechnen muss, wo es hinschreiben soll (Siehe Abschnitt Pointer 3.14). Beispiel: Matrixprodukt double a[10][10],b[10][10],c[10][10],sum; int i,j,k; for (i=0;i<10;i++) for (j=0;j<10;j++) { sum=0; for (k=0;k<10;k++) sum += a[i][k]*b[k][j]; c[i][j]=sum; } 3.14 Pointer Pointer sind, wie die ï¾ 12 bersetzung schon sagt, Zeiger auf Speicherbereiche, sozusagen, typisierte Adressen. Angewandt haben wir diese Variablenform schon bei dem scanf("...",&a) Befehl. Das Einlesen und anschlieï¾ 12 ende Schreiben eines Datensatzes erfolgt auf die Adresse der Variablen a, vermittelt durch den Adressoperator &. Deklariert wird ein Pointer wie folgt: type *name; Bsp.: float *ptr1; int *ptr2 = &x; //Direktinitialisierung mit einer int Variablen x //Achtung! nicht int* ptr3, ptr4; //denn nun ist ptr3 ein Pointer auf einen Integer-Speicherbereich, aber //ptr4 eine gew\"{o}hnliche Integer Variable Um den Unterschied zwischen normalen Variablen und Pointern klar zu machen, stellen wir hier beide Datenspeicherformen gegenï¾ 12 ber. Variable: • Zuweisung (Reservierung) eines festen Speicherplatzes bei der Deklaration KAPITEL 3. DIE SPRACHE C/C++ 39 • Füllen dieses Speicherplatzes (bei Adresse x) mit Daten bei Denition Pointer: • Zuweisung eines variablen Speicherplatzes, der bei Deklaration auf nil (bzw. NULL) zeigt • Zeigt auf neuen Speicherplatz bei Denition Abbildung 3.1: Funktionsweise Pointer 3.14.1 Adressierung und Dereferenzierung Die Arbeit mit Pointer wäre natürlich bei weitem nicht so attraktiv, wenn man den Speicherplätzen keine Daten zuweisen oder diese aus ihnen lesen könnte. Um dies zu tun, gibt es die beiden Operatoren * und &. Der *-Operator wird Dereferenzierungsoperator genannt und gibt sozusagen den Inhalt des Speicherbereiches zurück, auf den der Pointer, auf den der Operator angewendet wird, zeigt. int* ptr; *ptr == Wert, der unter der Adresse ptr steht. Der &-Operator wird auch Adressoperator genannt, da er die Adresse der Variablen zurück gibt, auf die er wirkt. Im Prinzip ist dieser also das Inverse des *-Operators. int zahl; &zahl == Adresse, unter der die Variable zahl im Speicher zu finden ist. Bsp. zur Initialisierung und Zuweisung: int *ptr1 = &x; //Die Adresse ptr1 (eigentlich nil) wird mit der von //Varible x initialisiert *ptr = 10; //In den Speicher (also in Var. x) der Adresse ptr1 //wird eine 10 geschrieben int y = *ptr1; //Var. y mit dem Inhalt von Adresse ptr1 initialisiert y = *ptr1*x/y**ptr1; //== x*x/y*x; unuebersichtlich!! y = (*ptr1)*x/y*(*ptr1); //besser! Bsp. zur Identitätsbeziehung der Operatoren: int a,b=2; a = *&b; int* ptr1=&a; int* ptr2=&b; ptr2=ptr1; //== a=b; //!= b=a, da hier keine Daten kopiert werden, sondern nur //Adressen ueberschrieben werden, deswegen == ptr2=&a Zudem Vorsicht auch bei Vergleichen: if((*ptr1)==10)...; KAPITEL 3. DIE SPRACHE C/C++ 3.14.2 40 Pointer-Arithmetik Bsp.: int *ptr; //Addition ptr++; ptr = ptr + 1; //erlaubt! //Inkrement um eine Adresseinheit (Typenabhaengig, //bspw. int -> Schrittweite 4 Byte) ptr += 2; //Subtraktion ptr--; ptr -= 2; Bei Pointern verboten sind jegliche Arten von Multiplikationen und Divisionen. 3.14.3 Pointer und Felder Pointer können u.a. zur Verwaltung von Feldern genutzt werden. Bsp.: int x[5]={1,2,3,4,5}; x==&x[0] → x ist eine Pointerkonstante! Übergibt man x bspw. an ein Unterprogramm, übergibt man den Zeiger auf das erste Element des obigen Arrays. Bsp.: int *ptr; x = ptr; ptr = x; //Syntaxfehler!? Wahrscheinlich weil ptr->NULL(nil)(?) //erlaubt! Mit Hilfe der Pointer-Arithmetik ermöglicht dies eine alternative Variante, um sich innerhalb von Arrays zu bewegen. Bsp.: int z = *(x+3); //== x[2]; Natürlich kann es auch Felder mit Pointer-Einträgen geben. Bsp.: int a[50]; int *p[50]; double* pq[50]; double** q; Adresse von q 4Byte //4 Byte, Ziffernliste //4 Byte, Adressliste //auch 4 Byte, weil Pointer //== p[] −→ Adresse Feld 1 ↓ double var1 Adresse Feld 2 ↓ double var2 Adresse Feld 3 . . . ↓ double var3 . . . Vor allem die letzte Beziehung kann zur dynamischen Konstruktion eines mehrdimensionalen Arrays genutzt werden. [Hier den Absatz zur Speicherplatzreservierung durch die malloc Fkt. einfügen!!!] 3.14.4 Pointer-Konstanten und Speicherzugri Eine durch das Schlüsselwort const inistialisierte Konstante ist schwächer gesichert, als ein durch char* p = "konstante" (ähnlich für andere Typen) initialisierte Pointer-Variable. Der Grund dafür ist, dass const Werte im normalen Speicher abgelegt werden und der Compiler nur darauf achtet, dass nicht auf const geschrieben wird. Wenn man den Compiler mit einem Trick umgehen kann, ist der const Wert trotzdem beschreibbar. Bsp. 1: Deklarationen KAPITEL 3. DIE SPRACHE C/C++ 41 char const *ptr; //dasselbe wie const char*, mit dem Unterschied, char* const ptr=&x; //dass gleich initialisiert werden muss const char* const ptr=&c; //const char-Var. mit const-Pointer Eintrag Bsp. 2: Konstantenlter des Compilers const int i=5; i=10; //Fehlermeldung durch Compiler! const char* ptr; char x="a"; ptr=&x; //Einmalzuweisung erfolgt, nun schreibgeschuetzt *ptr = 'A'; //Fehlermeldung durch Compiler! Bsp. 3: Casting/Compiler umgehen const int i=5; const int* ptr1=&i; int* ptr2; //normalerweise Fehler, aber ptr1 auch als const deklariert ptr2=(int*)ptr1; *ptr2 = 50; //casting //Keine Fehlermeldung, obwohl i und ptr1 schreibgeschuetzt Bsp. 4: Wirklich schreibgeschützte Konstanten char a[]="Meyer"; char* b="Meyer"; //legt Feld im Arbeitsbereich des Hauptspeichers an //legt Pointerkonstante im geschuetzten Bereich des //Hauptspeichers an *a='N'; *b='N'; //aber *a=*b; //-> a[]="Neyer" //Fehler! Falsche Adressierung zur LAUFZEIT! //moeglich! Ablage im Hauptspeicher: Arbeitsbereich M t ↑ a e t y t e t r \0 t t Schreibgeschützter Bereich M t ↑ b e t y t e t r t \0 t 3.14.5 Besondere Pointer a) NULL-Zeiger Dieser Zeiger zeigt auf einen Dummy-Speicherbereich, der nicht zur Bearbeitung, sondern nur zur Vermittelung gedacht ist, und wird oft auch als nil (not in list) Element bezeichnet. Bsp.: int *p=NULL; //== *p=0; if(p=NULL)...; //Fehlerabfrage, ob p falsch initialisiert b) Nicht-typisierte (void) Zeiger Bsp. 1: Automatisches Typcasting double* x; double y; void *p; KAPITEL 3. DIE SPRACHE C/C++ 42 x=p; x=(double*)p; //typenlos wird typisiert: void*=>double* //typcasting, hier: == x=p; p=x; y=*p; //double*=>void* //Zuweisungsfehler! (da p vom Typ void*) Bsp. 2: Feldverwaltung void *p; int x[50]; int *y; p=x; y=p; z=y[2]; //void*=>int* int z; //== z=x[2]; c) Character-Felder/Strings Bsp.: String kopieren char string_1[]="Schulze"; string_1 ist ein Array mit 8 Elementen (7 Buchst. + Schusszeichen). 1. Variante: char string_2[50]; char* pq=string_1; char* pz=string_2; while(*pq) //Solange wie *pq!=0 mache *(pz++) = *(pq++); //Uebertrage Symbole. *pq='\0'; //Dann Schlusszeichen setzen. Achtung! Postx pz++ wird erst nach dem Übertragen ausgeführt, also wird erst ganz zum Schluss inkrementiert. 2. Variante: pq--; pz--; do *(++pz) = *(++pq); while(*pq); Nun wird zuerst inkrementiert und dann Übertragen. 3.15 Filesysteme Grundsätzlich muss man Filenamen in 2 Typen unterscheiden: Den logischen Namen, der den Namen des Files im Programm angibt und den physischen Namen, welcher der wirkliche Name der Datei im Betriebssystem ist. Im Programm muss der Bezug zwischen diesen beiden Namen erst hergestellt werden. Dabei können Namen nicht nur direkt auf Dateien zeigen sondern auch auf andere Schnittstellen des Betriebssystems z.B. LPT1,COM usw. Der logische Filename ist in C ein sogenannter Filepointer. Er wird mit FILE* infile_ptr,outfile_ptr; initialisiert. Die grundlegende Programmstruktur beim Umgang mit Filesystemen gliedert sich wie folgt: KAPITEL 3. DIE SPRACHE C/C++ 43 OPEN file ... Verarbeitung ... CLOSE file Ein File wird mit FILE* fopen(char* filename,char mode) //Funktion gibt einen Filepointer zurueck aufgerufen. 3.15.1 Beispiel zum Umgang mit Files FILE* file_ptr; char filename[81]; gets(filename); file_ptr = fopen(filename,"r"); file_ptr = fopen(filename,"w"); if(file_ptr=NULL) {Verarbeitung} fclose(file_ptr); //Datei zum Lesen (r) bereitstellen //Datei zum Schreiben (w) bereitstellen //Speicherbereich freigeben und verbleibenen //Buffer physisch schreiben In C/C++ existieren vordenierte logische Dateinamen. Dies sind zum Beispiel: • stdin ... Pointer auf Tastatur • stdout ... Pointer auf Terminal bzw. Ausgabe • stdprn ... Pointer direkt an Drucker (Achtung Drucker muss Daten nicht verstehen!) • stdaux ... serielle Schnitstelle 3.15.2 Verarbeitung von Files • Formatierter Zugri Der Zugri auf die Datei erfolgt formatiert, d.h. der Inhalt der Datei wird interpretiert, beispielsweise als ASCII-Code. Allerdings werden physisch natürlich auch nur Bytes geschrieben. Beim Einlesen einer Datei wird ein internen Filepointer generiert, der auf den Anfang, der im Puerspeicherbereich liegenden Datei zeigt. Bei jeder Aktion mit der Datei wandert der interne Filepointer (iFp) ein Byte weiter. Beispiel für formatierte Zugrie sind: fgetc(file_ptr); fputc('A',file_ptr); //Liest Zeichen aus file_ptr //Schreibt Zeichen A an Postion des aktuellen //iFp in die Datei • Formatierungsprogrammbeispiel FILE* infile_ptr; FILE* outfile_ptr; infile_ptr = fopen("dateiname","r"); outfile_ptr = fopen("dateiname","w"); //Lesezugriff //Schreibzugriff while((char ch=fgetc(infile_ptr))!=EOF) //EOF als Stopzeichen -> Ende Datei fputc(ch,outfile_ptr); KAPITEL 3. DIE SPRACHE C/C++ 44 Dieses Programm kann nicht nur Textdateien kopieren wie man zunächst vermuten würde, es kann alle Dateien kopieren die nicht die Bitweise Kodierung des Zeichens EOF besitzen. Die Bits in der Datei werden zwar als Charakter interpretiert aber dennoch so in die Zieldatei geschrieben. Physisch werden also wirklich alle Bits kopiert. Falls die Datei die Bitkodierung des Zeichens EOF enthält würde das Programm hier das kopieren abbrechen. Um dies zu Umgehen bietet C/C++ die Funktion feof(file_ptr) welche angibt ob die Datei zu Ende ist. Vorher muss natürlich der iFp auch auf eine Stelle die hinter dem Ende der Datei ist rücken damit feof FALSE zurückgibt. Das Programm muss also folgendermassen verändert werden: ch = fgetc(infile_ptr); while(!=feof(infile_ptr)) { fputc(ch,outfile_ptr); ch = fgetc(infile_ptr); } Zum Einlesen von Strings können die Funktionen fgets() und fputs() benutzt werden. Beispiele sind: char buffer[10]; fgets(buffer,n,file_ptr); //n ist maximale Anzahl an Chars fscanf(infile_ptr,"Syntax wie scanf Funktion"); fprintf(outfile_ptr,"Syntax wie scanf Funktion"); • Binäre "Formatierung" Um Dateien binär auszulesen und zu bearbeiten braucht man die Funktionen fread(), fwrite(). Sie lesen und schreiben die auf unterster Ebene die physischen Bits einer Datei. Diese Funktionen sind nicht portabel! Je nach Prozessorarchitektur und interner Bauweise des Rechners werden Dateien unterschiedlich physisch gepeichert. So muss die Bitkodierung einer Integerzahl nicht immer von rechts erfolgen. Die mit fread() ausgelesenen Dateien können auch nur auf dem selben Computer wieder gelesen werden, ohne ihren Inhalt zu verändern oder nicht mehr zu verstehen. Eine Möglichkeit die Dateien dennoch portabel zu machen, ist die vollständige Standartisierung aller Bits in einer Datei. • Funktionen zur Manipulation des internen Filepointers Um den internen Filepointer zu steuern können folgende Funktionen verwendet werden: void rewind(file_ptr); int i=ftell(file_ptr); fseek(file_ptr,int i); 3.15.3 //iFp auf Anfang //Position des iFP //iFp auf Position i setzen Praktische Probleme der Dateiverwaltung Um einen Eintrag im Text zu nden gibt es mehrere Möglichkeiten: 1. sequentielle Suche 2. sortieren nach HOM dann binäres Suchen 3. direkte Addresierung KAPITEL 3. DIE SPRACHE C/C++ 3.16 45 Spezielle Spezikationen in C++ 3.16.1 Einfache Erweiterungen gegenüber C 1. Ein- und Ausgabe Spezielle Bibliotheken (ohne .h-Extension) #include<iostream> #include<string> #include<cmath> //==math.h beinhalten spezielle Befehle für C++. Bsp.: cout << a << b << endl; cout << "Meyer" << endl; cin >> a >> b; Vorteil bzgl. printf: cout erkennt den Datentyp der Ausgabevariablen (hier a und b) automatisch Nachteil bzgl. printf: Formatierung von Gleitkommazahlen (bspw. durch %10.2lf bei printf) nur schwer möglich. Bsp.: #include<fstream> //Filestream ifstream fin("eingabe.txt"); char ch, buf[80]; //input file stream --> fin fin >> ch; fin >> buf; fin.getline(buf,80); ofstream fout("ausgabe.txt"); fout << ch; //Lesen! //Ganze Zeile in buf einlesen //output file stream --> fout //Schreiben! 2. Blockkommentare /*...*/ 3. Deklarationen dürfen überall stehen. 4. Dynamische Datenobjekte In C: int *p_int; p_int = (int*)malloc(40*sizeof(int)); free(p_int); In C++: int *p; p = new int; *p=15; delete p; Felder: int[]-Operator KAPITEL 3. DIE SPRACHE C/C++ 46 int *pa = new int[14]; //auch dynamisch moeglich int **pa = new int*[10]; /*Vorsicht bei der Speicherfreigabe*/ delete []pa; //Operator [] mitnehmen delete []*pa; 5. fehlt! 6. fehlt! 7. Funktionen mit variabler Parameterzahl Bsp.: int contour(Image pic); int contour(Image pic, char* image_name); Die Funktion contour wurde mit der zweiten Deklaration überladen. Der Compiler kann nun zwischen diesen beiden Denitionen unterscheiden, indem er die übergebenen Parameter prüft und der zugehörigen Funktion zuweist. Dies ist möglich, da der Compiler den Funktionen intern anderen Bezeichner zuweist (hier bspw.: contour_Image und contour_Image_char). Beide Funktionen, die Einfache und die Überladene, müssen jedoch separat deniert werden. Möchte man diese Doppeldenition umgehen, besteht die Möglichkeit Default-Parameter in die Funktionsdeklaration und -denition einzubauen. Die Funktion ist damit nicht mehr überladen. Bsp.: //Deklaration int contour(Image pic, char* image_name="NO"); //Definition ... //Anwendung contour(pic1); //moeglich, da image_name standardmaessig auf "NO" contour(pic1, picname); //auch moeglich, image_name wird ueberschrieben 8. Überladen von Funktionen Mit dem Überladen von Funktionen bezeichnet man die Doppelverwendnung ein und desselben Funktionsnamens für zwei unterschiedliche Funktionen. Diese müssen sich in der Anzahl und/oder dem Datentyp der Parameter unterscheiden. Angewendet können sie dann ganz normal. Der Compiler entscheidet anhand der übergebenen Variablen, welcher Denition er nun dem Aufruf zuordnet. Bsp.: double max(double x, double y); int max(int x, int y); //--> separate Defintionen Wie oben schon beschrieben funktioniert dies aufgrund der internen Namensvergabe des Compilers. Diese internen Namen werden als Signaturen bezeichnet und lauten in diesem Beispiel max_double_double und max_int_int. Wie man sieht geht der Datentyp des Rückgabewertes nicht mit in die Signatur ein und kann von daher auch kein Unterscheidungsmerkmal der Funktionen sein. 9. Casting In C: a=(float)i; In C++: a=float(i); → Typumwandlungskonstruktor KAPITEL 3. DIE SPRACHE C/C++ 3.16.2 47 Objektorientierung Um groÿe Programme ezienter und übersichtlicher zu machen, wurde die prozedurale Sprache C zu der objektorientierten Sprache C++ erweitert. Bei der objektorientierten Programmierung deniert man abstrakte Datentypen (sog. Klassen), die eine Vereinigung von schon existierenden Datentypen (sog. Eigenschaften) und Funktionen (sog. Methoden/Ereignisse) darstellen. Die beinhalteten Datentypen und Funktionen können auch intern deniert sein und müssen nicht explizit extern bekannt sein. Die einzige Forderung die gestellt wird, ist, dass Daten eines Objektes (Instanzierung einer Klasse) nur funktional manipulierbar sein sollen. Klassen und Objekte Denition Klasse: Eine Klasse beschreibt einen konkreten abstrakten Datentyp in seinen Eigenschaften und Verhalten (Methoden). Anwendung: Klassen dienen dazu, Datenelemente und Elementfunktionen (Methoden) in einem Datentyp zu kapseln. Sie gestatten es, verschiedene Zugrisberechtigungen für die einzelnen Elemente zu vergeben, und können durch Vererbung Klassenhierarchien aufbauen. Dirk Louis; C/C++ - Die praktische Referenz; Verlag Markt+Technik; 2003 München. Denition Objekt: Eine Variable mit dem Datentyp einer Klasse bezeichnet man als Instanz oder Objekt dieser Klasse. Bsp.: Datum mit Tag, Monat und Jahr //Defintion: class datum { private: /* Daten-Default */ int tag, monat, jahr; //Unantastbar von aussen //int t=1; verboten public: /* Methoden, Elementfunktionen, Memberfunktionen, oeffentliche Datentypen */ void set_datum(int t, int m, int j); void aktuell(); void print(); void inc(); } //Anwendung: int main() { datum urlaub, heute, morgen; //3 Objekte vom Typ datum ... //Den Objekten koennen Auftraege erteilt werden: heute.aktuell(); //Aktuelles Datum wird in heute eingepraegt //Eigentlich vermutete Anwendung aktuell(heute) //nicht moeglich heute.print(); //Nur funktionelle Nutzung der internen Objektvariablen/-fkt. moeglich heute.tag = 18; //Syntaxfehler, da tag "private"! } KAPITEL 3. DIE SPRACHE C/C++ 48 Die Frage ist nun, ist es möglich Methoden in der Klasse zu deklarieren (Prototypen), aber diese auÿerhalb zu denieren (Implementation)? Dies ist in der Tat möglich, was hier wieder an einem Beispiel gezeigt werden soll: void datum::set_datum(int t, int m, int j) { /* Hier darf auf private Daten zugegriffen werden */ tag=t; monat=m; jahr=j; /* Aufrufe wie datum.tag=t sind jedoch verboten, weil ein Objekt vom typ datum ja noch nicht existiert. */ } Neu an dieser Implementierung ist der Scrope-Operator '::'. Mit :: kann man immer auf den nächst höheren Gültigkeitsbereich zugegreifen. Bsp.: int a=5; { int a=10,b=0; b= ::a; } //b beinhaltet nun den Wert 5 Die nächste Frage, der wir uns nun stellen wollen, lautet: Ist es möglich Methoden zu denieren, die als Parameter ein Objekt der die Methode beinhaltenden Klasse übergeben bekommt? Die Antwort lautet auch hier, ja und zwar mit Hilfe klassenbezogener Zeiger (this). this ist eine Konvention von C++ und stellt einen Zeiger dar, der immer auf das aktuelle Objekt zeigt, intern jedoch anders behandelt wird (d.h. es ist also keine einfache Schreibweise sondern essenziell für die korrekte Bearbeitung des Codes). Bsp.: set_datum(datum heute) { heute.tag=t; //Fehler!! /* Geht nicht, da Objekt datum ja durch u.a. set_datum definiert, welches an dieser Stelle noch nicht vollstaendig bekannt ist. */ this->tag=t; //korrekter Aufruf des oben Gewuenschten } Konstruktoren Konstruktoren spielen bei der Initialisierung von Objekten und anderen Daten eine entscheidende Rolle. In dem Beispiel int a[2][2]={{1,2},{3,4}}; wird im Hintergrund ein Kontruktor aufgerufen, der den Speicher für diese 2x2 Matrix reserviert und diesen mit den spezischen Werten füllt. Der Konstruktor hat die Verantwortung, dass sich ein Objekt vom Augenblick der Entstehung an in einem korrekten Zustand bendet. Spezikationen: • Spezielle Memberfunktionen (Objektfunktionen) • (System) Default-Konstruktoren existieren für Standard-Datentypen/-objekte • Regel: Denition eigener Konstruktoren für spezielle Probleme • Kontruktor hat keinen return-Wert • Name des Konstruktors ist derselbe, wie der, der zu kontruierenden Klasse KAPITEL 3. DIE SPRACHE C/C++ 49 Bsp.: class datum { ... public: datum(); datum(int t, int m, int j); ... } //Konstruktor //ueberladener Konstruktor datum::datum() { //nichts } datum::datum(int t, int m, int j) { tag=t; monat=m; jahr=j; } main() { datum heute; datum heute(2,7,2008); //!Achtung! nicht: datum heute(); } //Aufruf datum() //Aufruf datum(2,7,2008) //--> Prototypendeklaration einer Fkt. heute Initialisierung mit Listen (entfällt wegen Unverständlichkeit) Spezielle Konstruktorarten a) Typumwandlungskonstruktoren Bsp.: int i; double a; i=a; Bsp.: class date { int t,m,j; ... public: explicit date(char *s); ... } int main() { //C++-Typumwandlung, jedoch gefaehrlich KAPITEL 3. DIE SPRACHE C/C++ } ... date d=date("4.7.2008"); date d="4.7.2008"; 50 //Typumwandlung; funktioniert! //Gewuenschte Typumwandlung funktioniert nicht b) Kopierkonstruktoren Es existiert immer ein (System-) Kopierkonstruktor. Dieser sollte jedoch immer selber geschrieben werden. Bsp.: date b("..."); date a(b); date a=b; //a wird sofort mit b initialisiert erschaffen //a wird erschaffen und b wird zugewiesen Bsp.: Image pic1(dimx, dimy, 255); Image pic2(pic1); class Image { int dimx, dimy; int maxval; int* data; } public: ... Schema der Datenzuweisung der Image-Deklarationen Prototyp der Klasse X: X(const X&x); X(X x); Achtung! Der Kopierkonstruktor wird oft implizit vom System benutzt, z.B. bei der Parameterübergabe void f1(Date d){...}. Der Kopierkonstruktor wird bei diesem Beispiel immer dann aktiviert, wenn die Funktion aufgerufen wird, da bei call-by-value Funktionen immer eine Kopie der Parameter erzeugt wird. Destruktor Der Destruktor ist das Gegenstück des Konstruktors und erledigt die Aufräumarbeiten. Bsp.: class beispiel { private: int zahl; public: beispiel(int i); ~beispiel(); } beispiel::beispiel(int i); //Konstruktor //Destruktor KAPITEL 3. DIE SPRACHE C/C++ { } 51 zahl=i; beispiel::~beispiel() {;} main() { beispiel *b1 = new beispiel(3); delete b1; beispiel b1 = beispiel(3); } //<-> beispiel b1(3) Friend Klassen und Funktionen • Befreundete Klassen und Funktionen bekommen die Erlaubnis dennoch auf private Daten zugreifen zu können • Schlüsselwort (innerhalb der Fkt.Implementation; Freunde müssen der Fkt. genannt werden): friend Einige C++ Standardklassen 1. Klasse string Bsp.: #include<string> string bez1; /* Aufruf des Standardkonstruktors --> Kreiierung eines Objektes, dass leer ist, bis auf das Schlusszeichen '/0' */ string bez2("hallow"); /* Initialisierung des string-Objekts mit einer C-Zeichenkette --> Typumwandlungskonstruktor */ string bez3(bez2); //--> Kopierkonstruktor string bez4("Meyer"); bez1=bez2+bez4; //+ ist ueberladener Operator fuer strings //Weitere ueberladene Operatoren += //Achtung: bez1="ABC"+"EDF"; -->Syntaxfehler, da Summation von C-Zeichenketten nicht definiert im C++ Grundstamm String-Elementfunktionen: • size_type size() Liefert Anzahl der Zeichen im String, nicht die Anzahl der Bytes. Bsp.: string a("abc"); int len=a.size(); • cout << a << endl; 2. Rest fehlt! //Aufruf einer Memberfkt., auch moeglich: len=size(a); KAPITEL 3. DIE SPRACHE C/C++ 3.17 52 Nützliche Funktionen • int sizeof(var) - gibt die Byteanzahl(!) des Datentyps der Variable var zurück Bsp.: char a; int b=0; b = sizeof(a); b = sizeof(b); \\b ist nun gleich 1 \\b ist nun gleich 4 • double pow(double base, double exponent) - Exponetialfunktion, die das Ergebnis zu dem Ausdruck baseexponent zurückliefert (statt double können auch long double oder oat Variablen übergeben werden) • double tan(double arg) - Tangenzfunktion (statt double können auch long double oder oat Variablen übergeben werden) So nutzbar sind desweiteren die folgenden trigonometrischen Funktionen: sin(), asin() (Arkussinus), cos(), acos(), atan(), cot(), acot() • Generierung von Zufallszahlen: void Randomize() inizialisiert (erneut) den Zufallszahlengenerator, damit er wirklich verschiedene Zahlen ausgibt. int Random(int val) erzeugt eine im Interval [0, val] gleichverteilte Zufallsvariable. double RandomD() erzeugt eine im Interval [0, 1] gleichverteilte Zufallsvariable. double GaussRandom(double sigma) erzeugt eine normalverteilte Zufallsvariablen mit dem Erwartungswert 0 und der Standardabweichung sigma • double fabs(double) - Betrag einer Gleitkommazahl int abs(int) - Betrag einer ganzen Zahl