Programmierung für Mathematiker

Größe: px
Ab Seite anzeigen:

Download "Programmierung für Mathematiker"

Transkript

1 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

2 Mathematik und ihre Anwendung am Lehrstuhl Prof. Thomas Schuster Forschungsschwerpunkte der AG Schuster Allgemein: Inverse Probleme (Theorie und Anwendungen) Numerische Analysis Optimierung Numerik partieller Differentialgleichungen Anwendungen: Vektortomographie (2D, 3D) Parameteridentifizierungen bei anisotropen, elastischen Wellengleichungen Terahertz-Tomographie Magnetpartikelbildgebung Hyperspektrale Bildgebung

3 Algorithmenentwicklung in der Terahertz-Tomographie Zerstörungsfreie Prüfung eines Polyethylenblocks: Standardmethode und verbesserte Variante Numerisches Praktikum in der Computertomographie (9 / 12 CP)

4 Inhalt der Vorlesung Phänomen, reale Welt Experiment Hypothese Vereinfachung Abstraktion Erfahrung (Mathematisches) Modell Modellfehler (Mathematisch) exakte Formulierung eines Problems Ergebnis Vergleich Evaluierung Modellverfeinerung Parameteradaption Implementierung des Programms Syntaktische und semantische Fehler Stabilität des Algorithmus Rundungsfehler Verfahrensfehler Lösbarkeit und Kondition des Problems Datenfehler

5 Die Programmiersprache C Definition (Programmiersprache) Eine Programmiersprache ist eine künstliche Sprache, die entwickelt wurde, um Rechenvorschriften für eine Maschine, in der Regel einen Computer, zu formulieren. Kommunikationsmittel zwischen Mensch und Maschine Vokabeln und Grammatik (Syntax) >1800 Sprachen mit verschiedenen Intentionen Warum C? ursprünglich zur Entwicklung von Betriebssystemen heute auch häufig in Anwendungssoftware zu finden Compiler verfügbar für nahezu alle Prozessoren und Betriebssysteme hardwarenah, erlaubt direkten Speicherzugriff schnell!! weit verbreitet, viele kostenlose Bibliotheken oder Programmbausteine im Netz Grundlage für C++, Java, Python, Perl, PHP,...

6 Die integrierte Entwicklungsumgebung KDevelop Kostenlos verfügbar unter Linux Open Source Software Stabil von zahlreichen Nutzern getestet Viele nützliche Helferlein wie Syntax Highlighting, Auto-Vervollständigung, automatische Einrückung, integrierte Konsole, Blockausblendung, Quelltext-Browser, Hintergrund-Parser, Debugger und vieles mehr...

7 Mein erstes C-Programm $ kdevelop & hallo_welt.c 1 /* hallo_welt.c - gibt Begruessung auf dem Bildschirm aus */ 2 3 main() // Achtung: C ist case-sensitive! 4 { 5 printf("hallo Welt!\n"); 6 } $ gcc hallo_welt.c -o hallo_welt hallo_welt.c: In Funktion main : hallo_welt.c:3:3: Warnung: Unverträgliche [...] Funktion printf $ ls hallo_welt hallo_welt.c hallo_welt.c~ $./hallo_welt Hallo Welt!

8 Wie entsteht ein Programm? prog.c Quellcode Präprozessor Quellcode Compiler prog.o Objektcode Objektcode library.so Linker prog prog.exe Programm

9 Die Programmiersprache C Vokabeln reservierte Wörter: printf, scanf, struct, for, while, float,... Opertoren: +, &, %, <=,!, *, / Grammatik (Syntax) a + 3 = b; b = a + 3; printf("hallo Welt!") printf("hallo Welt!"); float 2wurzelx-1; float cmou_2bi5nuoz5o_bg6tobuzb; Bedeutung (Semantik) a = 0; b = 5 / a; a = 5; b = 5 / a;

10 Die Software Matlab von MathWorks Warum Matlab? High-Level Sprache: kurze Entwicklungszeiten Vielfältige Visualisierungsmöglichkeiten MATLAB-Programme sind vollständig portierbar. Integration zusätzlicher Toolboxen (PDE, Optimization, Wavelet) Matlab ist eine kommerzielle Software, aber es gibt eine MathWorks TAH Campuslizenz Die Software darf von allen Studierenden der Universität des Saarlandes genutzt werden. Das umfasst einerseits beliebig viele Installationen auf dem Campus und andererseits auch die Nutzung auf privaten Computern. Notwendig: Registrierung bei der Firma ASKnet AG. Weitere Informationen: Weitere Informationen: mathworks-tah-campuslizenz/

11 Literatur: Internet Skripte Gerald Kempfer: Progammieren in C - Vorlesungsbegleitendes Skript Wikibooks: C-Programmierung Springerlink: Ralf Kirsch, Uwe Schmitt (Programmieren in C, 2007) Manfred Dausmann, Ulrich Bröckl, Joachim Goll (C als erste Programmiersprache, 2008) Jörg Birmelin, Christian Hupfer (Elementare Numerik für Techniker, 2008)

12 Literatur: Hardcopy Semesterapparat Programmierung Fachschaft: Vorlesungsmitschriften Bücher Ralf Kirsch, Uwe Schmitt: Programmieren in C. Eine mathematikorientierte Einführung, Springer 2007 (zurzeit vergriffen, 2. Auflage erscheint 2013) Helmut Erlenkötter: C Programmieren von Anfang an, Rowohlt 1999 Brian Kernighan, Dennis Ritchie: Programmieren in C, Hanser 1990 Manfred Dausmann, Ulrich Bröckl, Joachim Goll: C als erste Programmiersprache, Teubner 2008

13 Vom Quellcode zum ausführbaren Programm Schritt 1: Code schreiben Quellcode ist eine Textdatei mit der Endung.c Ungeeignet: Textverarbeitungsprogramme wie MS Word oder OpenOffice Writer Geeignet: MS Notepad (Editor), Emacs, gedit, nedit, KDevelop, Eclipse... Schritt 2: Code compilieren Kommandozeile starten und in das Verzeichnis wechseln, das den Code enthält Befehl gcc Quellcode.c -o Programm in eine Kommandozeile eingeben Der Compiler gcc übersetzt den Quellcode in Maschinencode, den der Computer versteht. Schritt 3: Programm ausführen Das compilierte Programm wird mit dem Befehl./Programm aufgerufen. Ohne das vorangestellte./ liefert das System die Fehlermeldung bash: Programm: Kommando nicht gefunden.

14 Zahlendarstellung: Natürliche Zahlen = = N c j B j j=0 mit B = 10 ( Dezimaldarstellung) N = 5 c = (c 5, c 4, c 3, c 2, c 1, c 0 ) = (2, 4, 1, 0, 1, 2), c j {0,..., B 1} 24 = = = N c j B j j=0 mit B = 2 ( Binärdarstellung) N = 4 c = (c 4, c 3, c 2, c 1, c 0 ) = (1, 1, 0, 0, 0) 2, c j {0, 1}

15 Zahlendarstellung: Ganze Zahlen Binärsystem (B = 2): c N wird als Vorzeichen interpretiert. Beispiel: B = 2, N = 7 53 = ( 1) 1 ( ) c = (1, 0, 1, 1, 0, 1, 0, 1) Frühe Computer verwendeten diese Darstellung Nachteil: 2 Versionen der Null, Ganzzahlarithmetik ineffizient Heute üblich: Zweierkomplement Eindeutige Darstellung der Null Effizientere Arithmetik, da keine Fallunterscheidung nach Vorzeichen nötig Näheres unter en.wikipedia.org/wiki/two s_complement,de.wikipedia.org/wiki/zweierkomplement

16 Datentypen Definition Ein Datentyp ist festgelegt durch einen Wertebereich und die darauf anwendbaren Operationen. Datentyp char int float double "Wertebereich" Zeichen (bspw. Buchstaben und Ziffern) ganze Zahlen Gleitkommazahlen mit einfacher Genauigkeit Gleitkommazahlen mit doppelter Genauigkeit Typmodifizierer short long long long unsigned erlaubt für int int, double int int, char, short int, long int, long long int

17 Datentypen Datentyp Größe kleinster Wert größter Wert char 1 Byte (=8 Bit) 2 7 = = 127 unsigned char 1 Byte = 255 int 4 Byte unsigned (int) 4 Byte long (int) 4 Byte wie int wie int unsigned long 4 Byte wie unsigned wie unsigned long long 8 Byte unsigned long long 8 Byte Gilt für 32-Bit-Systeme (Linux/Windows/Mac) sowie 64-Bit-Windows. Auf 64-Bit-Linux- oder Mac-Systemen hat long eine Größe von 8 Byte = 64 Bit. betragsmäßig betragsmäßig Datentyp Größe kleinster Wert größter Wert float 4 Byte double 8 Byte long double 10 Byte Nicht eindeutig festgelegt. Der ISO-Standard verlangt lediglich, dass long double mindestens die gleiche Präzision aufweist wie double. Die meisten Compiler interpretieren long double als 80-Bit-Gleitkommazahl.

18 Variablendeklaration Definition Eine Variablendeklaration besteht aus der Angabe eines Datentyps sowie einer Liste von Variablennamen. Datentyp Variablenname1, Variablenname2,...,VariablennameN; vor der ersten Verwendung zu Beginn eines Anweisungsblocks Unterscheidung zwischen Groß- und Kleinschreibung keine Sonderzeichen, wie z.b. #, ß, % keine Ziffern zu Beginn max. Variablennamenlänge: 31 Zeichen Positiv-Beispiele: int i, j, k; float u, w = -3.14, x, y = 1.0, z; unsigned int N = 5; double s;

19 Variablendeklaration Negativ-Beispiele: int dummy#; Fehler: verirrtes # im Programm int dummy%; Fehler: expected =,,, ;... int i, double dummy; int i; double 2dummy; unsigned double x; Fehler: expected identifier or ( before double Fehler: ungültiger Suffix dummy an Ganzzahlkonstante Fehler: both unsigned and double in declaration specifiers Feste Variablen : const const Datentyp Variablenname = Wert; Auf die entsprechende Variable kann nur noch lesend zugegriffen werden. Bsp.: const int k = 1;. k = 2; Fehler: Zuweisung der schreibgeschützten Variable k

20 Operatoren Definition Anweisung/Manipulation/Rechnung mit festen Regeln wirkt auf einen oder mehrere Operanden unärer Operator: 1 Operand binärer Operator: 2 Operanden Stellung des Operators: Präfixform: Operator steht vor Operanden Suffixform: Operator steht hinter Operanden Infixform: Operator steht zwischen Operanden Zuweisungsoperator = int a; float x = 3.14, y, z; a = -4; z = y = x; // aequivalent: z = (y = x); binär, Infixstellung rechtsassoziativ ( )

21 Operatoren: Arithmetische Operatoren Arithmetische Opertoren ( +, -, *, /, % ) Name Verwendung Operandentyp Resultat Minus (unär) -Op1 int, float Vorzeichenwechsel Plus Op1 + Op2 int, float Summe Minus (binär) Op1 - Op2 int, float Differenz Multiplikation Op1 * Op2 int, float Produkt int ganzzahlige Division Division Op1 / Op2 < float Quotient Modulo Op1 % Op2 int Rest bei ganzzahliger Division Regeln Sind die Operanden vom gleichen Datentyp, so auch das Ergebnis Sind die Operanden von verschiedenen Typen, so ist das Resultat vom genaueren Datentyp: int + double = double Arithmetische Operatoren sind linksassoziativ ( ), d.h. a + b + c = (a + b) + c Priorität bei binären Operatoren: Punkt vor Strich

22 Operatoren: Arithmetische Zuweisung und Inkrement Arithmetische Zuweisungsoperatoren Operation Bezeichnung Äquivalent zu Op1 += Op2; Additionszuweisung Op1 = Op1 + Op2; Op1 -= Op2; Subtraktionszuweisung Op1 = Op1 - Op2; Op1 *= Op2; Multiplikationszuweisung Op1 = Op1 * Op2; Op1 /= Op2; Divisionszuweisung Op1 = Op1 / Op2; Op1 %= Op2; Modulozuweisung Op1 = Op1 % Op2; Inkrement- und Dekrementoperatoren ++ und -- Für eine Variable i vom Typ int sind äquivalent: i = i+1; i += 1; i++; i = i-1; i -= 1; i--; Unterscheidung Postfix- und Präfixnotation sum += i++; sum = sum + i; sum += ++i; i = i + 1; i = i + 1; sum = sum + i;

23 Operatoren: Arithmetische Zuweisung und Inkrement int a = 3, b, c; b = ++a * 3; // a = 4, b = 12 c = a++ * 3; // c = 12, a = 5 ist äquivalent zu int a = 3, b, c; a++; // oder ++a; b = a * 3; c = a * 3; ++a; // oder a++; Publikumsfrage: Seien a und b vom Typ int. Was ist zu b -= (a-1); äquivalent? a) -b = a - 1; b) b -= (-a); c) b = -(a--); d) b -= (--a);

24 Operatoren: Division und Modulorechnung Mathematik (Zahlentheorie): Seien a, 0 b Z. Dann existieren eindeutig bestimmte Zahlen q, r Z mit a = q b + r, 0 r < b. Computer Science: Seien a, 0 b Z. Dann existieren (eindeutig bestimmte) Zahlen q, r Z mit Es gilt: a = q b + r, b < r < b. q = a / b; r = a % b; ganzzahlige Division a modulo b Beispiele: a = 45, b = 7: 45 = q = 6, r = 3 45 = q = 7, r = 4 a = 27, b = 5: 27 = ( 6) q = 6, r = 3 27 = ( 5) 5 2 q = 5, r = 2

25 Operatoren: Implizite Typumwandlung und Casts Unterscheide! int a = -3; float c; c = a / 2; c = -1.0 Explizite Typumwandlung durch Casts (Datentyp) Term; int a = -3; float c; c = a / 2.0; c = -1.5 Beispiel: int a = 3, b = 2; float c = (float) a / b; c = 1.5 Merke: Casts besitzen höhere Priorität als binäre arithmetische Operatoren Äquivalent: float c = ((float) a) / b; Nicht äquivalent: float c = (float) (a / b);

26 Operatoren: Vergleichende und logische Operatoren Vergleichsoperatoren Überprüft werden Wahrheitswerte von Aussagen wie etwa x > 0, j N, a f (x) b usw. Notation in C a < b a > b a <= b a >= b a == b a!= b math. Notation a < b a > b a b a b a = b a b Häufig verwendet in wenn,... dann... -Konstruktionen in C: Wert 0 (auch 0.0) "falsch" (false) Werte ungleich 0 (etwa 1, -2.5) "wahr" (true) Unterscheide "a = b" (Zuweisung) und "a == b" (Vergleich) Merke: Der Wert einer Zuweisung entspricht dem zugewiesenen Wert. Vorsicht beim Test auf Gleichheit bei floats (s. arithm. Operatoren) Vergleichsoperatoren sind linksassoziativ ( )

27 Operatoren: Vergleichende und logische Operatoren Logische Operatoren: &&,,! Ausdrücke (arithmetische, vergleichende, zuweisende) werden als Aussagen miteinander verknüpft Die Werte solcher Verknüpfungen sind immer 1 (wahr) oder 0 (falsch) Notation in C math. Notation Bedeutung A && B A B Konjunktion (und) A B A B Disjunktion (oder)!a A Negation Das "oder" ist einschließend zu verstehen und nicht als "entweder oder" Logische Operatoren sind linksassoziativ ( ) Verknüpfungstafeln: && !

28 Operatoren: Vergleichende und logische Operatoren Beispiel: int A = -4, C = 1; double B = -0.5; Ausdruck Wert A -4!A 0 A - B -3.5!(A - B) 0!!(A - B) 1!A - B 0.5!A -!B 0 Ausdruck Wert A < B < C 0 A < B <= C 1 A < (B<C) 1 A<B && B<C 1 C>A && B 1 A==2 B>C 0 A=2 B>C 1

29 Ausgabe auf der Kommandozeile: printf dient zur Ausgabe von Text und numerischen Werten auf dem Bildschirm erfordert Präprozessordirektive: Syntax: Beispiel: #include <stdio.h> printf(formatstring, Parameterliste); Formatstring = Text und Platzhalter in Anführungszeichen int a = -5; float b = ; printf("a hat den Wert %d, b hat den Wert %f.\n", a, b); Ausgabe: a hat den Wert -5, b hat den Wert Zeichenkonstanten (vgl. Erlenkötter, Kap. 8.5) \n neue Zeile (new line) \t horizontaler Tabulator

30 Ein- und Ausgabe: Platzhalter %[flags][weite][.genauigkeit][modifizierer]typ typ ganzzahlig d / i 3218 unsigned integer u 3218 Gleitkomma f wissenschaftl. e e+03 modifizierer genauigkeit weite flags l, z.b. %lf bei Verwendung von double L, z.b. %Lf bei Verwendung von long double Anzahl der Nachkommastellen Mindestanzahl an Zeichen (. mitgezählt) links- oder rechtsbündig, Vorzeichen, führende Nullen Beispiel: %7.3f ist Platzhalter für eine Gleitkommazahl mit 3 Nachkommastellen und einer Feldbreite von mindestens 7 Zeichen. Hilfe zu printf: Befehl man 3 printf in die Kommandozeile eingeben Weitere Beispiele: Übung

31 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

32 Wiederholung: Ausgabe auf der Kommandozeile: printf dient zur Ausgabe von Text und numerischen Werten auf dem Bildschirm erfordert Präprozessordirektive: Syntax: Beispiel: #include <stdio.h> printf(formatstring, Parameterliste); Formatstring = Text und Platzhalter in Anführungszeichen int a = -5; float b = ; printf("a hat den Wert %d, b hat den Wert %f.\n", a, b); Ausgabe: a hat den Wert -5, b hat den Wert Zeichenkonstanten (vgl. Erlenkötter, Kap. 8.5) \n neue Zeile (new line) \t horizontaler Tabulator

33 Ein- und Ausgabe: Platzhalter %[flags][weite][.genauigkeit][modifizierer]typ typ ganzzahlig d / i 3218 unsigned integer u 3218 Gleitkomma f wissenschaftl. e e+03 modifizierer genauigkeit weite flags l, z.b. %lf bei Verwendung von double L, z.b. %Lf bei Verwendung von long double Anzahl der Nachkommastellen Mindestanzahl an Zeichen (. mitgezählt) links- oder rechtsbündig, Vorzeichen, führende Nullen Beispiel: %7.3f ist Platzhalter für eine Gleitkommazahl mit 3 Nachkommastellen und einer Feldbreite von mindestens 7 Zeichen.

34 Eingabe durch den Benutzer: scanf dient u.a. zum Einlesen von Zahlen erfordert Präprozessordirektive: #include<stdio.h> Syntax: scanf(formatstring, Parameter); Formatstring: %[modifizierer]typ Parameter: & vor Variablennamen! Beispiel: #include <stdio.h> main() { double zahl; printf("bitte geben Sie eine Zahl ein: "); scanf("%lf", &zahl); printf("%lf zum Quadrat ist %lf\n", zahl, zahl*zahl); }

35 Zeichen Zeichen (engl. character) werden intern wie (positive) ganzzahlige Werte behandelt, der Datentyp ist char. Variablen vom Typ char belegen im Speicher 1 Byte. Die standardisierte Zuordnung Zeichen Zahl erfolgt gemäß der ASCII- Tabelle. ASCII = American Standard Code for Information Interchange. Die Kodierung definiert 128 Zeichen, bestehend aus 33 nicht-druckbaren sowie 95 druckbaren: 0 Nullzeichen 1 32 Steuerzeichen Ziffern, Buchstaben, Symbole, usw. Auszug: 37 % 65 A 97 a 124 } 38 & 66 B 98 b 167 $ C 99 c 181 µ \ 123 { 223 ß Es sind äquivalent char c = 'A'; char c = 65; char c = 0101; // oktal: 65 = 1*64 + 0*8 + 1*1 char c = 0x41; // hexadezimal: 65 = 4*16 + 1*1

36 Zeichen Ausgabe mittels printf: char c = 88; printf("88 interpretiert als Zeichen: %c\n",c); printf("88 interpretiert als integer: %d\n",c); Zeichenarithmetik char c = 'A'; int diff = 'C' - c; // diff = 2 c = 'B' + diff; // c = 68 printf("das zu c korrespondierende Zeichen ist %c\n",c); Einlesen von Zeichen via der Funktion getchar int getchar(void) Beispiel int c; c = getchar();

37 Anweisungsblöcke Folge von Anweisungen, die von geschweiften Klammern eingeschlossen sind Anweisungsblock ist syntaktisch äquivalent zu einer einzelnen Anweisung eine einzelne Anweisung bedarf keiner Klammer Anweisungsblöcke können geschachtelt sein Beispiele: printf("hallo Welt!\n"); { printf("hallo Welt!\n"); printf("ich gehoere zum inneren Anweisungsblock.\n"); } { printf("ich bin im uebergeordneten Anweisungsblock!\n"); { printf("hallo Welt!\n"); printf("ich gehoere zum inneren Anweisungsblock.\n"); } }

38 if-anweisung if (Bedingung) Anweisungsblock Wenn-Dann oder if (Bedingung) Anweisungsblock_1 else Anweisungsblock_2 Wenn-Dann-Andernfalls Beispiel: Vorzeichenfunktion sign +1, falls a > 0 Mathematische Definition: sign(a) = 0, falls a = 0 1, falls a < 0 int a; if (a>0) printf("sign(a) = +1\n"); else { if (a==0) printf("sign(a) = 0\n"); else printf("sign(a) = -1\n"); }

39 Mehrfache Alternativen: else if Syntax if (Bedingung_1) Anweisungsblock_1 else if (Bedingung_2) Anweisungsblock_2. else if (Bedingung_N) Anweisungsblock_N else // optional Anweisungsblock // optional Beispiel if (a > 0) printf("sign(a) = +1\n"); else if (a == 0) printf("sign(a) = 0\n"); else // oder else if (a < 0) printf("sign(a) = -1\n");

40 Viele Alternativen: switch Syntax switch (Variable) { case Wert_1: Anweisungsblock_1 break; case Wert_2: Anweisungsblock_2. case Wert_N: Anweisungsblock_N default: Anweisungsblock } // optional // optional // optional Bei der Ausführung wird zu dem case label gesprungen, an der zum ersten Mal Variable und Wert übereinstimmt. Der gesamte folgende Code bis zum ersten break oder zum Ende des Blocks wird ausgeführt. Das schließt auch Code außerhalb des angesprungenen case labels ein. Als Wert im case label sind nur Konstanten zulässig.

41 Viele Alternativen: switch Beispiel unsigned char eingabe; printf("bitte Befehl eingeben: "); scanf("%c", &eingabe); switch (eingabe) { case 'q': printf("programm wird beendet!\n"); break; case 'p': case 'P': printf("drucken...\n"); break; case 'h': printf("hilfe wird aufgerufen.\n"); break; default: printf("eingabe nicht erkannt!\n"); break; }

42 Schleifen Schleifen wiederholen einen Anweisungsblock so lange bis ein bestimmtes Abbruchkriterium erfüllt ist Die wichtigsten Schleifen in C sind for und while while-schleife oft verwendet, wenn die Anzahl der Wiederholungen nicht vorherbestimmt ist Syntax: while (Bedingung) Anweisungsblock wiederholt Anweisungsblock bis Bedingung "falsch", d.h. gleich 0 ist Autor ist selbst verantwortlich, dass das Abbruchkriterium irgendwann erfüllt ist Gefahr einer Endlosschleife! Variablen in Bedingung müssen deklariert und ggf. initialisiert werden Anweisungsblock = "Rumpf" der Schleife Beispiel: Programm soll eine natürliche Zahl N einlesen und die Summe N ausgeben.

43 Beispiel: while-schleife 1 #include<stdio.h> 2 3 main() 4 { 5 int i = 1, sum = 0, N; 6 7 printf("geben Sie eine natuerliche Zahl ein: N= "); 8 scanf("%d", &N); 9 10 while (i <= N) 11 { 12 sum += i; 13 i++; 14 } printf("die Summe der ersten %d natuerlichen Zahlen ", N); 17 printf("betraegt %d\n", sum); 18 }

44 Beispiel: while-schleife Es sind äquivalent: int i = 1; while (i <= N) { sum += i; i++; } int i = 0; while (i < N) { i++; sum += i; } int i = 0; while (i++ < N) sum += i; int i = 0; while (++i <= N) sum += i; Kein guter Stil, da fehleranfällig!

45 do-while-schleife Anweisungsblock wird mindestens ein Mal ausgeführt beachte Semikolon am Ende do Anweisungsblock while (Bedingung); äquivalent zu: Anweisungsblock while (Bedingung) Anweisungsblock Beispiel int N; do { printf("bitte geben Sie eine ganze Zahl zwischen 5 und 15 ein:"); scanf("%i", &N); } while(n<5 N>15);

46 for-schleife vermutlich die am häufigsten verwendete Schleifenvariante kommt zum Einsatz wenn das Update immer gleich ist Anzahl der Wiederholungen ist a priori bekannt Syntax: for (Initialisierung; Bedingung; Update) Anweisungsblock äquivalent zu: Initialisierung; while (Bedingung) { Anweisungsblock Update; } Beispiel: int i; for (i=1; i<=n; i++) sum += i;

47 Stolperfallen Abbruchkriterium fehlerhaft: Zuweisung statt Vergleich for(i=1; i=n; i++) sum += i; Endlosschleife! Leerer Anweisungsblock: Falsch platziertes Semikolon for(i=1; i<=n; i++); sum += i; Unter- oder Überlauf unsigned i; for(i=n; i>=0; i--) sum += i; unsigned char i; for(i=1; i<=n; i++) sum += i; Endlosschleife für N>255!

48 Schachtelung Beispiel: 1 #include <stdio.h> 2 3 main() 4 { 5 int i, j; 6 7 for(i=1; i<=5; i++) // aeussere Schleife 8 { 9 for(j=1; j<=5; j++) // innere Schleife 10 printf("%2d ", i*j); // Feldbreite 2 -> Zahlen rechtsbuendig printf("\n"); // wieder aussen 13 } 14 } Ausgabe:

49 Steuerung von Wiederholungen break beendet aktuelle Wiederholungsansweisung continue Rest der Schleife wird übersprungen und der nächste Schleifendurchlauf gestartet Merke: break und continue sollten sparsam eingesetzt werden, da sonst das Programm unübersichtlich wird. return beendet aktuelle Funktion (später mehr!) Absolut verpönt: goto bewirkt einen Sprung im Programm an eine zurvor definierte Stelle Merke: Anwendung von goto ist verboten!

50 Steuerung von Wiederholungen Beispiel: 1 #include <stdio.h> 2 3 main() 4 { 5 unsigned eingabe; 6 7 while(1) // ohne break eine Endlosschleife! 8 { 9 printf("bitte eine natuerliche Zahl kleiner als 100"); 10 printf(" eingeben: "); 11 scanf("%u", &eingabe); // Vorsicht: Unterlauf moeglich! if (eingabe < 100) 14 break; 15 } printf("die Zahl war %u.\n", eingabe); 18 }

51 Zufallszahlen Definition Ein Zufallsexperiment ist ein Experiment, dessen Ausgang nicht aus den vorherigen Ergebnissen vorausgesagt werden kann. Eine Zufallszahl ist eine Zahl, die sich aus dem Ergebnis eines Zufallsexperiments ableitet. Echte Zufallszahlen sind prinzipiell nur solche, die von wirklich zufälligen (physikalischen) Prozessen abgeleitet werden (Beispiel: radioaktiver Zerfall). Viele physikalische Prozesse sind zwar deterministisch (vorhersagbar), jedoch nicht praktisch berechenbar (Beispiel: Temperatur am 9. Dezember des Folgejahres) Pseudo-Zufall Definition Ein Experiment gilt als pseudo-zufällig, wenn sich sein Ergebnis zwar theoretisch voraussagen lässt, ohne Kenntnis der genauen Berechnungsvorschrift jedoch eine Prognose unmöglich ist.

52 Zufallszahlen Computer kann nur Pseudo-Zufallszahlen erzeugen Ziel (zunächst): Gleichverteilte Pseudo-Zufallszahlen auf [0, 1[ oder auf [0, M[ erzeugt Gute Generatoren müssen eine Reihe von statistischen Tests bestehen Am weitesten verbreitet ist die lineare Kongruenzmethode: 1 Gib einen seed-wert n 0 vor (Benutzereingabe / anderweitige Berechnung) 2 Berechne für feste natürliche Zahlen a, b, M und k = 0, 1,...: n k+1 = (a n k + b) mod M Soll das Ergebnis eine Gleitkommazahl in [0, 1[ sein, berechne x k+1 = n k+1 /M. Die Qualität des Generators hängt entscheidend von den Parametern a, b und M ab!

53 Zufallszahlen Beispiel für einen schlechten Generator (aus Kirsch/Schmitt, Kap. 12): Wähle a = , b = 0, M = 2 31 Visualisierung: Fasse je 3 aufeinanderfolgende Zahlen zu Vektoren in R 3 zusammen. Ergebnis: Jeder Vektor liegt in einer von 15 festen Ebenen!

54 Erzeugung von Zufallszahlen in C Präprozessordirektive #include <stdlib.h> notwendig Für int-zufallszahlen auf [0, INT_MAX]: Funktion rand() Seed-Wert für den rand-generator wird mit srand(seed) festgelegt. Dabei wird seed als unsigned int interpretiert. Für double-zufallszahlen auf [0, 1]: Funktion drand48() Seed-Wert für den drand48-generator wird mit srand48(seed) festgelegt. Dabei wird seed als long int interpretiert. Verwendung: unsigned seed = ; int zz; srand(seed); zz = rand(); printf("zz = %d\n", zz); long seed = ; double zz; srand(seed); zz = drand48(); printf("zz = %f\n", zz);

55 Beispiel: (primitive) Simulation eines Aktienkurses 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 main() 5 { 6 long seed; 7 int i, periode = 20; // Anzahl Tage 8 double wert, // aktueller Wert 9 max_schwankung = 10; // Tagesschwankung maximal 10 Euro printf("bitte Seed-Wert eingeben: "); 12 scanf("%ld", &seed); 13 srand48(seed); printf("wert der Aktie zu Beginn (Euro): "); 16 scanf("%lf", &wert); for(i=0; i<periode; i++) 19 wert += 2 * (drand48() ) * max_schwankung; 20 // Skalierung auf [-0.99,1.01] printf("wert nach 20 Tagen: %.2f Euro\n", wert); 23 }

56 Monte-Carlo-Methoden Oberbegriff für mathematische Verfahren, deren Funktionsprinzip der Zufall ist Name geht zurück auf John v. Neumann und reflektiert die Tatsache, dass immer wieder gewürfelt wird. Grundlegendes Prinzip ist einfach zu verstehen bei Anwendern beliebt Trotzdem vielseitig und effizient verwendbar Kommen vor allem dann zum Einsatz, wenn ein zufälliger Prozess simuliert werden soll (z. B. Finanzmathematik, Dynamik von Gasen / Partikeln / Elektronen in Materie, Ausbreitung von Krankheiten,...) Mathematische Basis: Gesetz der großen Zahlen (einfache Fassung) Je öfter man ein Zufallsexperiment durchführt, desto mehr nähert sich die relative Häufigkeit eines Ereignisses der Wahrscheinlichkeit desselben Ereignisses an. Beispiel: Idealer Würfel P(X = 1) = 1 Wahrscheinlichkeit, dass eine Eins gewürfelt wird 6 N 1 Anzahl der gewürfelten Einsen nach N Würfen Gesetz der großen Zahlen: N 1 lim N N = 1 6

57 Monte-Carlo-Integration A Q = [0,1]x[0,1] A = Flächeninhalt von A =? A = x dy y dx γ γ Umrandungskurve von A Parametrisierung von γ notwendig } h Klassische Vorgehensweise (Quadratur): Unterteile Q in N N Quadrate der Kantenlänge h = 1/N Bestimme die Anzahl N A der Quadrate, deren Mittelpunkte in A liegen Berechne A N A h 2 Nachteil: sehr teuer in höheren Dimensionen ( 3)

58 Monte-Carlo-Integration Stochastischer Zugang: Wahrscheinlichkeit, dass ein zufällig erzeugter Punkt z Q in A landet, ist gegeben durch p A = P(z A) = A Q = A Gesetz der großen Zahlen: Für eine hinreichend große Anzahl N von zufällig erzeugten Punkten ist #(Punkte in A) #(Punkte insgesamt) = N A N p A = A Stochastische Vorgehensweise: Erzeuge N zufällige Punkte in Q Zähle die Anzahl N A der Punkte, die in A liegen Berechne A = N A N [ Q ] Vorteile: simpel, einfach zu implementieren man muss nur zwischen z A und z A unterscheiden können in höheren Dimensionen sehr effizient

59 Monte-Carlo-Integration Beispiel: Einheitskreisscheibe im 1. Quadranten 1 y A x Umgebendes Quadrat Q = [0, 1] 2 Q = 1 Einheitskreis-Viertel { } A = (x, y) Q x 2 + y 2 1 Pseudocode: 1 Lies eine Zahl N ein 2 Für i = 1,..., N: Erzeuge zufällige Zahlen x und y Falls x 2 + y 2 1: N A N A Gib Flächeninhalt = N A /N aus

60 Monte-Carlo-Integration: Ergebnisse N = 10, Fehler: (36.3 %) N = 100, Fehler: (5.8 %) N = 1000, Fehler: (2.9 %) N = 10000, Fehler: (0.6 %)

61 Monte-Carlo-Simulation: Radioaktiver Zerfall Radioaktive Nuklide (Atomsorten) sind instabil und werden unter Aussendung von Strahlung in andere, stabile Atomsorten umgewandelt. Der Zerfall eines einzelnen Atoms geschieht spontan und kann als zufälliges Ereignis angesehen werden. Das Isotop 131 I (Jod-131) ist ein Betastrahler. Es wird zum stabilen Isotop 131 Xe (Xenon-131) umgewandelt. Dabei wird ein Elektron (Beta-Teilchen) emittiert. Jod-131 besitzt eine Halbwertszeit von T 1/2 = Tagen, d. h. nach T 1/2 ist (etwa) die Hälfte einer betrachteten Menge Jod-131 zerfallen. Experimente zeigen den Zusammenhang N = λ N t mit t : kleines Zeitintervall N : Anzahl der radioaktiven Kerne N : Änderung von N im Zeitintervall t λ > 0 : Zerfallskonstante (Proportionalitätsfaktor, materialabhängig)

62 Monte-Carlo-Simulation: Radioaktiver Zerfall Klassische Herangehensweise (für große Anzahl N): N(t) = λn(t) t mit N(t) = N(t + t) N(t). Division durch t und der anschließende Grenzübergang t 0 liefern die Differentialgleichung Die (eindeutige) Lösung ist gegeben durch Ṅ(t) = λn(t). N(t) = N(0) e λt. Die Halbwertszeit T 1/2 ist der Zeitraum, nach dem die Hälfte der Kerne zerfallen ist, d. h. N(T 1/2 ) = N(0)/2. Durch eine einfache Umformung ergibt sich T 1/2 = ln 2 λ bzw. λ = ln 2 T 1/2. Halbwertszeiten radioaktiver Isotope sind üblicherweise tabelliert. Stochastische Herangehensweise: λ t = Wahrscheinlichkeit, dass ein Kern im Zeitraum t zerfällt In jedem Zeitschritt wird für jeden Kern zufällig entschieden, ob er zerfällt.

63 Monte-Carlo-Simulation: Radioaktiver Zerfall Beispiel: Jod-131 Als Zeitschritt wählen wir t = 1 min. T 1/2 = d = min λ = ln 2 T 1/2 = min 1 Die Wahrscheinlichkeit, dass ein Kern innerhalb einer Minute zerfällt, ist gegeben durch p = λ t = Pseudocode: 1 Lies eine Zahl N (Anzahl zu Beginn) und eine Zahl T (Endzeitpunkt) ein 2 Für i = 1,..., T : Für j = 1,..., N: Erzeuge eine zufällige Zahl x [0, 1[ Falls x < p, setze N N 1 3 Gib die Anzahl N der übriggebliebenen Kerne nach T Minuten aus

64 Monte-Carlo-Simulation: Radioaktiver Zerfall Ergebnisse 1 N(0)=10 N(0)=100 N(0)=10000 Analytisch 1 Realisierung 1 Realisierung 2 Realisierung Vorhandene Kerne / Anfangsbestand Vorhandene Kerne / Anfangsbestand Zeit [min] Zeit [min] Zerfallskurven für verschiedene Anfangsbestände N(0) 3 Realisierungen für N(0) = 10

65 Monte-Carlo-Simulation: Räuber-Beute-Modell Grundlegendes Modell in der Theorie der dynamischen Systeme Beschreibt die Zusammenhänge, welche die Entwicklung mehrerer interagierender Populationen (konkurrierende Spezies) bestimmen Beispiel: Anwendungsgebiete: Systembiologie, Epidemologie, Ökonomie,... Hecht vs. Karpfen Räuber sehr gefräßig mag Karpfen Beute harmlos lecker

66 Monte-Carlo-Simulation: Räuber-Beute-Modell Grundannahmen: In einem großen Weiher leben ausschließlich Karpfen (K) und Hechte (H). Der Nahrungsvorrat für Karpfen ist unbegrenzt. Ihr Wachstum in einem Zeitintervall t ist proportional zur Anzahl der vorhandenen Individuen: K K t Hechte, die keine Nahrung finden, fressen andere Hechte, d. h. der Rückgang ihres Bestandes in einem Zeitintervall t ist proportional zur Zahl der vorhandenen Exemplare: H H t Ohne Interaktion gilt für beide Spezies das gleiche Modell wie beim radioaktiven Zerfall! Bei einer Begegnung wird der Karpfen vom Hecht gefressen. Hat ein Hecht eine bestimmte Anzahl Karpfen gefressen, entsteht ein zusätzlicher Hecht. Je mehr Individuen es von beiden Sorten gibt, desto wahrscheinlicher ist solch eine Begegnung: K KH t H KH t

67 Monte-Carlo-Simulation: Räuber-Beute-Modell Gekoppeltes Gesamtmodell ( t = 1): K = λ K K p KH H = n 1 p KH λ H H K/H : Änderung des Bestandes von K/H λ K : Zuwachsrate K λ H : Sterberate H p : Wahrscheinlichkeit einer Begegnung n : Anzahl der K, die ein H zur Reproduktion fressen muss Parameterwahlen für die Simulation: λ K = , λ H = , p = , n = 5. Aus dem kontinuierlichen Modell bekannt: Ein stabiler Gleichgewichtszustand ist gegeben durch K = nλ H p = 500, H = λ K p = 400.

68 Monte-Carlo-Simulation: Räuber-Beute-Modell Pseudocode: 1. Lies die Anfangsbestände K und H sowie die Anzahl T der Zeitschritte ein 2. Für i = 1,..., T : Für k = 1,... K: Erzeuge eine zufällige Zahl x [0, 1[ Falls x < λ K : Setze K K + 1 Für k = 1,..., K: Für j = 1,..., H: Erzeuge eine zufällige Zahl x [0, 1[ Falls x < p: Setze K K 1 Setze n temp n temp + 1 Falls n temp = n: Setze H H + 1 und n temp 0 break; Für j = 1,..., H: Erzeuge eine zufällige Zahl x [0, 1[ Falls x < λ H : Setze H H 1 Gib die Zahlen K und H am Bildschirm aus

69 Monte-Carlo-Simulation: Räuber-Beute-Modell Ergebnisse 650 Karpfen Hechte 900 Karpfen Hechte Population 500 Population Zeitschritte Zeitschritte K = 500, H = 400 K = 800, H = Karpfen Hechte Karpfen Hechte Population Population Zeitschritte K = 900, H = Zeitschritte K = 800, H = 1000

70 Monte-Carlo-Simulation: Räuber-Beute-Modell Ergebnisse K=500, H=400 K=800, H=400 K=900, H=800 K=800, H= Hechte Karpfen Phasenraumdiagramm für verschiedene Startwerte

71 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

72 Funktionen Funktion Zusammenfassung eines Anweisungsblocks zu einer aufrufbaren Einheit Gehört zu den wichtigsten Konzepten fast aller Programmiersprachen Aufgaben von Funktionen: 1 Wiederverwendbarkeit von einmal geschriebenem Code 2 Strukturierung und Vereinfachung von Code bessere Übersicht und Lesbarkeit 3 einfachere Fehlersuche, separates Testen möglich 4 leichteres Hinzufügen weiterer Funktionalitäten Die sinnvolle Strukturierung eines Programms in Unterprogramme ist einer der wichtigsten Schritte bei der Programmierung!

73 Charakterisierung von Funktionen Eine Funktion besitzt einen sinnvollen (=sprechenden) Namen, mit dem sie aufgerufen wird. nimmt eine (möglicherweise leere) Liste von Parametern mit festgelegten Datentypen als Eingabe. hat einen Anweisungsblock, der bei ihrem Aufruf ausgeführt wird. liefert nichts oder einen Wert eines festgelegten Datentyps als Ausgabe Mathematische Schreibweise: Funktion : {int, float,...} N {int, float,...} oder {}, N 0 (Parameter1,..., ParameterN) Anweisungen Ausgabewert Im Gegensatz zu mathematischen Funktionen kann der Anweisungsblock auch Befehle enthalten, die nicht direkt etwas mit der Ausgabe zu tun haben (auch Funktion: {} {} kann in C sinnvoll sein).

74 Syntax Deklaration Rückgabetyp Funktionsname(ParTyp1 [Par1],..., ParTypN [ParN]); Definition Rückgabetyp Funktionsname(ParTyp1 Par1,..., ParTypN ParN) { Anweisungsblock } Unterschied zwischen Deklaration und Definition: verschiedene Abstraktionsebenen! Deklaration (= Signatur) legt fest, was eine Funktion tut. muss vor dem erstmaligen Aufruf und außerhalb von main im Code stehen. endet mit einem Semikolon. Parameter-Datentypen genügen. Definition legt fest, wie eine Funktion etwas tut. kann an einer beliebigen Stelle außerhalb von main im Code stehen. hat kein Semikolon am Ende. Parameter sind mit Typ und Name anzugeben.

75 Beispiel 1 /** Praeprozessordirektive ****/ 2 #include <stdio.h> /** Funktionsdeklarationen ****/ 6 float summe(float a, float b); // auch moeglich: 7 // float summe(float, float); /** Hauptprogramm ****/ 11 int main(void) 12 { 13 float sum; 14 sum = summe(3.5, 1); // Funktionsaufruf 15 } /** Funktionsdefinitionen ****/ 19 float summe(float a, float b) 20 { 21 float sum = a + b; // Funktionsrumpf 22 return sum; // Funktionsrumpf 23 }

76 Der Datentyp void Funktionen ohne Rückgabe: Deklaration: void Funktion(Parameterliste); Beispiel: void srand48(long seed); Verwendung: z. B. Bildschirmausgabe, Statusänderung eines externen Mechanismus In anderen Programmiersprachen (z.b. Pascal) oft als Prozedur bezeichnet. In C gibt es keine Differenzierung zwischen Funktion und Prozedur. Funktionen ohne Parameter: Deklaration: Rückgabetyp Funktion(void); Beispiel: double drand48 (void); Verwendung: z. B. Ausführung von externen Mechanismen Kombination: z. B. void abort(void); (Gewaltsames Ende des Programms)

77 return und main return Mit der Ausführung der return-anweisung wird die aktuelle Funktion sofort beendet. Enthält der Funktionsrumpf keine return-anweisung, so endet die Ausführung des Rumpfes bei Erreichen der letzten schließenden geschweiften Klammer. schlechter Stil! Beachte: Es kann immer nur ein (skalarer) Wert zurückgegeben werden. main main ist in Wirklichkeit eine Funktion (mit Rückgabetyp int). Quelltext innerhalb von main wird als Hauptprogramm bezeichnet. Es sind äquivalent: main() int main() int main(void) main liefert standardmäßig den Rückgabewert 0 wenn keine Fehler aufgetreten sind. Mit return...; können andere Werte zurückgeliefert werden. Fehlerbehandlung

78 Beispiel 1: Funktion mit Rückgabewert Die charakteristische Funktion eines Intervalls [a, b] mit a < b ist definiert als { 1, x [a, b] χ [a,b] (x) := 0, sonst. Implementierung: je nach Wert von x wird 1.0 oder 0.0 zurückgegeben. Definition im Code: // Charakteristische Funktion des Intervalls [a,b] // (1 innerhalb, 0 ausserhalb) float charfunkintervall(const float a, const float b, float x) { if(a >= b) // Hier muss eine Fehlerbehandlung hin if ((x >= a) && (x <= b)) return 1.0; else return 0.0; }

79 Beispiel 2: Funktion ohne Rückgabewert void geplapper(int zahl1, double zahl2, char c) { printf("diese Funktion erzeugt eine Menge (sinnloser) Ausgaben "); printf("am Bildschirm.\n\n"); printf("jetzt noch eine horizontale Linie, dann geht's los!\n"); printf(" \n"); printf("zahl1 = %d, Zahl2 = %f\n", zahl1, zahl2); if(c == '+') printf("die Summe der beiden Zahlen ist %f\n\n", zahl1+zahl2); printf("so, jetzt bin ich fertig!\n"); return; }

80 Mathematische Funktionen Nutzung erfordert Präprozessordirektive Compilierung gcc ProgrammName.c -o ProgrammName -lm #include <math.h> Signatur int abs(int a) float fabsf(float a) double fabs(double a) double sqrt(double x) double pow(double b, double e) double exp(double x) double log(double x) double log10(double x) Bedeutung a a a x b e e x ln(x) log 10 (x)

81 Mathematische Funktionen Signatur Bedeutung double sin(double x) sin(x) double cos(double x) cos(x) double tan(double x) tan(x) double asin(double x) arcsin(x) double acos(double x) arccos(x) double atan(double q) arctan(q) ( π 2, π 2 ) double atan2(double x, double y) arctan(y/x) ( π, π] double sinh(double x) sinh(x) double cosh(double x) cosh(x) double floor(double x) x double ceil(double x) x

82 Konstanten in math.h Name Bedeutung M_E e M_LOG2E log 2 (e) M_LOG10E log 10 (e) M_LN2 ln(2) M_LN10 ln(10) M_PI π M_PI_2 π/2 M_PI_4 π/4 M_1_PI 1/π M_SQRT2 2 M_SQRT1_2 1/ 2 Bemerkung: Die Namen der Konstanten werden vom Präprozessor im Code textuell durch die entsprechenden Werte ersetzt, z. B. M_PI durch (Gleitkommazahlen werden automatisch als double interpretiert.)

83 Call by Value Beispiel: Vertauschen zweier Werte (?) 1 #include <stdio.h> 2 3 // Funktionsdeklaration 4 void vertausche(int p, int q); // oder (int, int) // Hauptprogramm 8 int main(void) 9 { 10 int a = 1, b = 3; 11 vertausche(a, b); 12 printf("a = %d, b = %d\n", a, b); 13 return 0; // guter Stil 14 } // Funktionsdefinition 17 void vertausche(int p, int q) 18 { 19 int hilf = p; 20 p = q; 21 q = hilf; 22 return; 23 } Ausgabe: a = 1, b = 3 Die Werte wurden gar nicht vertauscht! Warum?

84 Call by value Definition Bei einem Funktionsaufruf werden nicht die Variablen als solche, sondern lediglich ihre Werte, d.h. Kopien der Variableninhalte übergeben. + Funktionsaufrufe können direkt als Parameter für eine andere Funktion verwendet werden, da der Wert und nicht die Funktion selbst übergeben wird. Beispiel: printf("wurzel von 2 = %f\n", sqrt(2.0)); + Unbeabsichtigte Manipulation der Variablen durch Funktionen wird vermieden. Der Manipulation von Variablen sind Grenzen gesetzt, da immer nur ein (skalarer) Wert zurückgeliefert werden kann. (Ausweg: Zeiger und Call by reference, später) Häufige Fehlerquelle: Annahme, dass Funktionsparameter durch die Funktion verändert werden können. Dem ist nicht so! Beispiel: float a = 2.0; sqrt(a); Hier wird der Rückgabewert von sqrt mit Parameter 2.0 nicht wieder in a gespeichert, sondern verworfen!

85 Scope und Lifetime Definition (Scope) Der Scope (Sichtbarkeit) eines deklarierten Objekts (Variable oder Funktion) ist der Bereich im Quelltext, in dem es bekannt, d. h. mit seinem Namen aufrufbar ist. Generell gilt: Variablen sind innerhalb des textuellen Codeblocks sichtbar, in dem sie deklariert wurden. Funktionen sind ab ihrer Deklaration in der gesamten Datei sichtbar. Definition (Lifetime) Die Lifetime (Lebenszeit) einer Variablen beschreibt den Zeitraum, in dem der Speicherbereich der Variablen für sie reserviert ist. Grundregel: Eine Variable existiert vom Moment ihrer Deklaration bis zu dem Zeitpunkt, an dem der Block, welcher die Deklaration umschließt, verlassen wird.

86 Lokale und globale Variablen Lokale Variablen Lokale Variablen werden zu Beginn eines Anweisungsblocks deklariert. Lifetime: Bis zum Ende des Anweisungsblocks, also auch in inneren Blöcken Scope: Innerhalb des Blocks, sofern sie nicht durch Variablen gleichen Namens in untergeordneten Blöcken überdeckt werden Funktionen liegen textuell außerhalb jedes anderen Blocks lokale Variablen sind dort generell nicht sichtbar. Globale Variablen Globale Variablen werden außerhalb aller Funktionen (einschl. main) deklariert. Namenskonvention: Unterstrich am Ende des Namens, z. B. int var_ = 42; Lifetime: Gesamte Dauer der Programmausführung (auch über Dateigrenzen hinweg später) Scope: Überall (auch in Funktionen) Gefahren: Namenskonflikte, unkontrollierte Manipulation, Chaos Nutzung globaler Variablen auf ein Minimum reduzieren!

87 Sichtbarkeit: Beispiel 1 1 #include <stdio.h> 2 3 int main(void) 4 { 5 int a = 4; 6 { 7 int a = 5; 8 printf("innen: a = %d\n", a); 9 } 10 printf("aussen: a = %d\n", a); return 0; 13 } Ausgabe: Innen: a = 5 Aussen: a = 4

88 Sichtbarkeit: Beispiel 2 1 #include <stdio.h> 2 3 int main(void) 4 { 5 int i; 6 7 for (i=5; i<10; i++) 8 { 9 int i=0; 10 i++; 11 printf("in Schleife: i = %2d\n", i); 12 } printf("nach Schleife: i = %2d\n", i); 15 return 0; 16 } Ausgabe: In Schleife: i = 1 In Schleife: i = 1 In Schleife: i = 1 In Schleife: i = 1 In Schleife: i = 1 Nach Schleife: i = 10

89 Lokale und globale Variablen 1 #include <stdio.h> 2 3 int a_ = 10; // globale Variable 4 5 int funktion(int); 6 void prozedur(void); 7 8 int main(void) 9 { 10 prozedur(); 11 prozedur(); 12 funktion(a_); 13 printf("a_ = %d\n", a_); 14 return 0; 15 } void prozedur(void) 18 { 19 a_ *= a_; 20 return; 21 } int funktion(int a_) // schlechter Stil: nur globale Variablen 24 { // sollten mit "_" enden! 25 return(--a_); 26 } Ausgabe: a_ = 10000

90 Automatische und statische Variablen Bisher waren alle Variablen automatische Variablen, d.h. sie existieren bis zu dem Zeitpunkt, an dem der Block, welcher die Deklaration umschließt, verlassen wird. Statische Variablen Möglichkeit, dass eine Funktion beim nächsten Durchlauf die Information, die in der Variablen gespeichert wurde, verwenden kann (wie in einem Gedächtnis) Sichtbarkeit: Innerhalb des Blocks, sofern sie nicht durch Variablen gleichen Namens in untergeordneten Blöcken überdeckt werden Deklaration: static Datentyp Name=Wert; Beispiel: 1 void zaehle() { 2 static int i = 1; // i wird (nur) beim ersten Aufruf von zaehle initialisiert 3 printf("%d\n", i); 4 i = i + 1; 5 }

91 Rekursive Programmierung Rekursion Aufruf einer Funktion durch sich selbst. Iteration Wiederholung eines Anweisungsblocks. Bemerkungen zur rekursiven Programmierung: Man muss die Schachtelungstiefe der Rekursion selbst überwachen, sonst kann es zum sog. Stapelüberlauf (engl. stack overflow) kommen. Meist sind Iteration und Rekursion äquivalent, aber häufig ist die Überführung in die jeweils andere Variante nicht offensichtlich. Je nach Fragestellung (Laufzeit-, Speicher-, Lesbarkeitsoptimierung) entscheidet man sich für eine der beiden Methoden. Viele effiziente Sortieralgorithmen oder Divide-and-Conquer-Techniken basieren auf dem Prinzip der Rekursion.

92 Rekursive Programmierung Einfaches Beispiel: Fakultät n! = n(n 1) 1 = fac(n) Es gilt: fac(n) = { n * fac(n-1), n > 1 1, n {0, 1} Interessanteres Beispiel: Quersumme einer natürlichen Zahl Algorithmus 1 Die Quersumme einer einstelligen Zahl ist die Zahl selbst. 2 Die Quersumme einer mehrstelligen Zahl ist die Summe der letzten Ziffer und der Quersumme der Zahl ohne ihre letzte Ziffer. Beispiel: Quersumme(5) = 5 Quersumme (31415) = Quersumme (3141) + 5

93 Quersumme iterativ 1 #include <stdio.h> 2 3 int main(void) 4 { 5 int zahl, qsumme = 0; 6 7 printf("zahl = "); 8 scanf("%d", &zahl); 9 10 printf("quersumme(%d) = ", zahl); while (zahl) 13 { 14 qsumme += zahl % 10; 15 zahl /= 10; 16 } printf("%d\n", qsumme); return 0; 21 }

94 Quersumme rekursiv 1 #include <stdio.h> 2 3 int qsumme(int); 4 5 int main(void) 6 { 7 int zahl; 8 printf("zahl = "); 9 scanf("%d", &zahl); printf("\nquersumme(%d) = %d\n",zahl, qsumme(zahl)); return 0; 14 } int qsumme(int zahl) 18 { 19 if (zahl / 10) 20 return zahl % 10 + qsumme(zahl / 10); return zahl; 23 }

95 Funktionen elementare Merkregeln Deklaration und Definition Die Funktionsdeklaration steht im Code vor main. Die Funktionsdefinition kommt ans Ende der Datei (unauffällig versteckt ). Anzahl und Datentypen der Parameter sowie Rückgabetyp müssen übereinstimmen. Guter Stil: Deklaration mit Kommentar versehen, der Parameter und Rückgabewert erläutert Der Name soll die Tätigkeit (Rückgabetyp void) bzw. den zurückgegebenen Wert (nichtleerer Rückgabetyp) widerspiegeln. Jede Funktion sollte durch ein return [Wert]; beendet werden. Aufruf Die Anzahl der Parameter muss konsistent mit der Deklaration sein. Es findet Call by value statt, d. h. die Funktion hat nicht die Parameter selbst, sondern nur die darin gespeicherten Werte zur Verfügung. Daher können diese auch nicht permanent verändert werden! Die übergebenen Werte werden automatisch in die Datentypen laut Deklaration umgewandelt ( gecastet ). Bei inkompatiblen Typen warnt der Compiler lediglich. Funktionen sehen nur globale Variablen, übergebene Parameter sowie lokale Variablen im Funktionsrumpf.

96 Statische Felder Definition Ein Feld (engl. array) ist die Zusammenfassung von Elementen gleichen Typs zu einer aufrufbaren Einheit. Deklaration: Datentyp Feldname[Anzahl]; Legt ein Feld von Anzahl Elementen des Typs Datentyp an. Achtung: Anzahl muss ein positiver ganzzahliger Wert sein. Wie bei primitiven Datentypen ist eine Initialisierung bei der Deklaration möglich: int N[4] = {1, 3, -5, 42}; double x[] = {1.9, , 5.73e+21}; // Die Groesse (3) wird hier vom Compiler automatisch bestimmt int p = 23; unsigned j[p] = {1, 0, 3}; float y[2] = {1.0, 3, -7.2}; // Wichtig: p initialisieren! // Rest bleibt uninitialisiert // Fehler: zu viele Elemente!

97 Statische Felder Die Deklaration float x[5]; legt ein Feld der Länge 5 an. Die Komponenten (Feldeinträge, Feldelemente) sind dabei alle vom Typ float. Folgerung: Das Feld mit Bezeichner x belegt im Arbeitsspeicher 5 4 = 20 Byte. Auf die Komponenten kann mittels x[0], x[1], x[2], x[3] und x[4] zugegriffen werden. Beispiel: x[0] = 11.0; x[1] = 12.0; x[2] = 13.0; x[3] = 14.0; x[4] = 15.0; Merke: Die Indizierung von Feldeinträgen beginnt in C stets mit 0! 1. Komp. 2. Komp. 3. Komp. 4. Komp. 5. Komp. 4 Byte 4 Byte 4 Byte 4 Byte 4 Byte Größe x[0] x[1] x[2] x[3] x[4] "Name" Inhalt Merke: Alle Komponenten eines Feldes werden vom Compiler direkt hintereinander im Arbeitsspeicher abgelegt.

98 Einschränkungen und Stolperfallen Einmal festgelegt, kann die Größe eines statischen Feldes nicht mehr verändert werden. Der maximalen Größe eines Feldes sind enge Grenzen gesetzt (Gefahr eines stack overflow = Stapelüberlauf). Die Rückgabe eines Feldes durch eine Funktion oder die Übergabe eines Feldes als Parameter einer Funktion ist nicht möglich, da es sich nicht um einen primitiven, sondern einen zusammengesetzten Datentyp handelt. Beim Zugriff auf ein Feldelement außerhalb des zulässigen Indexbereichs erfolgt im allgemeinen keine Fehlermeldung! Auch der Compiler warnt nicht! Die Zuweisung x[7] = schreibt in den Bereich, der (zufällig) von alpha belegt wird. Mögliche Folge: Das Programm wird völlig unberechenbar! Häufigste Fehlerquelle: int x[n];... x[n] = 1; Tritt zumeist dann auf, wenn x[1] statt x[0] als erster Eintrag interpretiert wird.

99 Beispiel: Euklidische Norm eines Vektors im R 3 1 #include <stdio.h> 2 #include <math.h> 3 4 int main(void) 5 { 6 double x[3], norm2 = 0.0, norm; 7 int i; 8 9 // Vektor einlesen 10 for (i=0; i<3; i++) // Vorsicht: Indices beginnen bei 0! 11 { 12 printf("geben Sie die %d-te Komponente ein: ", i+1); 13 scanf("%lf", &x[i]); 14 } // Vektor ausgeben 17 printf("\nder Vektor hat folgende Eintraege:\n"); 18 for (i=0; i<3; i++) 19 printf("x[%d] = % 7.4lf\n", i, x[i]); // Berechnung der Summe der Komponentenquadrate 22 for (i=0; i<3; i++) 23 norm2 += x[i]*x[i]; norm = sqrt(norm2); // euklidische Norm printf("\ndie Norm des Vektors ist %.4lf.\n\n", norm); 28 return 0; 29 }

100 Beispiel: Euklidische Norm eines Vektors im R 3 Ausgabe: Geben Sie die 1-te Komponente ein: 1 Geben Sie die 2-te Komponente ein: -2 Geben Sie die 3-te Komponente ein: 2 Der Vektor hat folgende Eintraege: x[0] = x[1] = x[2] = Die Norm des Vektors ist

101 Mehrdimensionale Felder Deklaration: Datentyp Feldname[dim1][dim2]...[dimN]; Beachte: A[4][3] wird (unabhängig vom Typ) im Speicher zeilenweise abgelegt: A[0][0], A[0][1], A[0][2], A[1][0],..., A[1][2], A[2][0],..., A[3][2] Mathematische Interpretation: A = (a ij ) 1 i 4 R j 3 A[0][0] A[0][1] A[0][2] a 11 a 12 a 13 A[1][0] A[1][1] A[1][2] a 21 a 22 a 23 A[2][0] A[2][1] A[2][2] a 31 a 32 a 33 A[3][0] A[3][1] A[3][2] a 41 a 42 a 43

102 2D-Felder als Matrizen Beispiel: Zeilenweises Einlesen der Komponenten einer 2 3-Matrix vom Typ int 1 #include <stdio.h> 2 3 int main(void) 4 { 5 int i, j; 6 int A[2][3]; 7 8 for(i=0; i<2; i++) 9 { 10 for(j=0; j<3; j++) 11 { 12 printf("a[%d][%d] = ", i, j); 13 scanf("%d", &A[i][j]); 14 } // for j 15 } // for i return 0; 18 }

103 2D-Felder als Matrizen Auch bei mehrdimensionalen Feldern ist eine direkte Initialisierung möglich, bspw. int A[3][2] = {{11,12},{21,22},{31,32}}; Da die Komponenten im Speicher in einer Reihe angeordnet sind, ist obige Zeile äquivalent zu int A[3][2] = {11,12,21,22,31,32}; Erfolgt bei der Deklaration eine partielle Initialisierung, so wird mit Nullen aufgefüllt: int A[3][3] = {{1,2},{3},{4,5}}; generiert die Matrix A = [ 1 ]

104 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

105 Zeichenketten Zeichenketten (engl. strings) sind formal nichts anderes als Felder vom Typ char. Eine Zeichenkette ("Hallo Welt!\n") wird vom Compiler automatisch als Feld von Zeichen dargestellt. Dabei wird am Schluss automatisch ein zusätzliches Zeichen \0 (Nullzeichen) angehängt, um das Stringende zu markieren. Stringverarbeitungsfunktionen benötigen unbedingt das Nullzeichen, damit sie das Ende eines Strings erkennen. Somit muss bei der Deklaration ein zusätzlicher Speicherplatz für \0 eingeplant werden. Beispiel: char wort[6] = "Hallo"; H a l l o \0 printf("der 2. Buchstabe von wort ist ein '%c'.\n", wort[1]); Ausgabe: Der 2. Buchstabe von wort ist ein a.

106 Zeichenketten Direkte Initialisierung: als String char wort[] = "Hallo"; oder als Feld von Zeichen char wort[] = {'H', 'a', 'l', 'l', 'o', '\0'}; Einlesen von Zeichenketten mittels fgets: fgets(zielstring, Anzahl + 1, stdin); liest Zeichen von der Tastatur ein, bis ein Return eingegeben wird, und speichert die ersten Anzahl Zeichen und ein abschließendes \0 im Zielstring. Bemerkung: Die einfachere Funktion gets prüft nicht die Länge des Zielstrings. Daraus ergeben sich u. U. gravierende Sicherheitsmängel (Gefahr eines Überlaufs), weshalb die Funktion unter Programmierern geächtet ist. Ausgabe via printf wie üblich. Der Platzhalter ist %s. printf("%s\n", wort);

107 Operationen mit Strings (kleine Auswahl) #include <string.h> strlen(s) liefert Länge von s, abschließendes \0 nicht mitgezählt strncpy(s, t, n) kopiert höchstens n Zeichen von t nach s (*) strncat(s, t, n) hängt n Zeichen von t an das Ende von s an (*) strcmp(s, t) { > 0, falls s lexikographisch kleiner als t ist < 0, falls s lexikographisch größer als t ist 0, falls s und t identisch sind Beachte: if (strcmp(s, t)) testet auf Ungleichheit! strncmp(s, t, n) wie strcmp, aber nur für die ersten n Zeichen (*) Vorsicht: Der Programmierer ist dafür verantwortlich, dass in s genügend Platz vorhanden ist, um n Zeichen zu speichern. Das sollte immer geprüft werden!

108 Operationen mit Strings: Beispiel 1 #include <stdio.h> 2 #include <string.h> 3 4 int main(void) 5 { 6 char str1[] = "Modellierung", vl_eigenschaft[8], zu_kurz[3], vl_name[51]; 7 8 strncpy(vl_name, str1, strlen(str1)); // okay 9 10 // simpler Test auf ausreichende Laenge 11 if (50 >= strlen(vl_name) + strlen(" und ") + strlen("programmierung")) 12 { 13 strncat(vl_name, " und ", 5); 14 strncat(vl_name, "Programmierung", strlen("programmierung")); 15 } printf("wie findest du %s? ", vl_name); 18 fgets(vl_eigenschaft, 8, stdin); printf("\n%s ist deiner Meinung nach %s\n", vl_name, vl_eigenschaft); strncpy(zu_kurz, str1, strlen(str1)); // Ueberlauf! 23 printf("inhalt von zu_kurz: %s\n", zu_kurz); 24 printf("inhalt von vl_eigenschaft: %s\n", vl_eigenschaft); return 0; 27 }

109 Operationen mit Strings: Beispiel Ausgabe: Wie findest du Modellierung und Programmierung? okay Modellierung und Programmierung ist deiner Meinung nach okay Inhalt von zu_kurz: Modellierungodellierung Inhalt von vl_eigenschaft: ellierungodellierung (Die Ausgabe der letzten beiden Zeilen hängt vom Speicherlayout ab.) Vorsicht: Offenbar warnt der Compiler nicht vor dem Überlauf in Zeile 22, und auch zur Laufzeit tritt kein Fehler auf. Die Funktion strncpy schreibt über die Feldgrenzen von zu_kurz hinaus in einen Bereich, der (möglicherweise) von einer anderen Variable belegt wird. Da ein String erst mit dem Nullzeichen als beendet gilt, wird von printf bei der Ausgabe des fehlerhaften Strings aus einem fremden Bereich gelesen! Ergebnis: Ein unberechenbares Programm mit einem äußerst schwierig zu lokalisierenden Fehler.

110 Umwandlung von Strings #include <stdlib.h> String Ganzzahl: int atoi(zeichenkette) int n; char s[11]; printf("es ist atoi(\"101\") = %d \n", atoi("101")); n = atoi("3218"); printf("es ist atoi(\"3218\") = %d \n", n); strncpy(s, "-157", 10); printf("es ist atoi(s) = %d \n", atoi(s)); Ausgabe: Es ist atoi("101") = 101 Es ist atoi("3218") = 3218 Es ist atoi(s) = -157

111 Umwandlung von Strings #include <stdlib.h> String Gleitkommazahl: double atof(zeichenkette) double x; char s[11]; printf("es ist atof("101.32") = %lf \n", atof("101.32")); x = atof(" "); printf("es ist atof(" ") = %lf \n", x); strncpy(s, " ", 10); printf("es ist atof(s) = %lf \n", atof(s)); Ausgabe: Es ist atof("101.32") = Es ist atof(" ") = Es ist atof(s) =

112 Größe von Datentypen und Speicherobjekten: sizeof Allgemein #include <stdlib.h> Rückgabetyp von sizeof ist size_t. Dabei gilt: size_t ist ganzzahlig und vorzeichenlos, entspricht unsigned (int) oder unsigned long. sizeof gibt den Speicherbedarf eines Datentyps in Byte aus: size_t sizeof(datentyp); printf("sizeof(char) = %u, ",sizeof(char)); printf("sizeof(int) = %u\n",sizeof(int)); printf("sizeof(float) = %u, ",sizeof(float)); printf("sizeof(double) = %u\n",sizeof(double)); Ausgabe: sizeof(char) = 1, sizeof(int) = 4 sizeof(float) = 4, sizeof(double) = 8

113 Größe von Datentypen und Speicherobjekten: sizeof sizeof liefert den Speicherbedarf eines deklarierten Speicherobjekts in Byte: size_t sizeof Speicherobjekt; oder size_t sizeof(speicherobjekt); Beispiel int i; sizeof i 4 int j=42; sizeof j 4 float x; sizeof x 4 float y=3.1415; sizeof y 4 double z=m_pi; sizeof z 8 char s[10]; sizeof s 10 char t[]= "Ay, caramba!"; sizeof t 13 int a[6]; sizeof a 24 int b[]={3,2,1,8}; sizeof b 16 float c[]={1.1,2.2,3.3}; sizeof c 12 double A[3][2]; sizeof A 48 int B[][] = {{11,12,13},{21,22,23}}; sizeof B error

114 Speicheradressen Adress-Operator & Erinnerung: int a; scanf("%d",&a); Beispiel: Adressen von Skalaren: int i=1, j=9; double x=2.7, y=m_pi; printf("adresse von i = %p\t",&i); printf("wert von i = %d\n", i); printf("adresse von j = %p\t",&j); printf("wert von j = %d\n", j); printf("adresse von x = %p\t",&x); printf("wert von x = %lf\n", x); printf("adresse von y = %p\t",&y); printf("wert von y = %lf\n", y); Ausgabe: Adresse von i = 0x28abf4 Wert von i = 1 Adresse von j = 0x28abf0 Wert von j = 9 Adresse von x = 0x28abe8 Wert von x = Adresse von y = 0x28abe0 Wert von y =

115 Speicheradressen Beispiel: Adressen von eindimensionalen Feldkomponenten char c[] ="Wetterwachs"; int a[] = {1,2,3}; printf("adresse von c[0] = %p\n",&c[0]); printf("adresse von c[1] = %p\n",&c[1]); printf("adresse von c[2] = %p\n",&c[2]); printf("adresse von c[3] = %p\n",&c[3]); printf("adresse von a[0] = %p\n",&a[0]); printf("adresse von a[1] = %p\n",&a[1]); printf("adresse von a[2] = %p\n",&a[2]); Ausgabe: Adresse von c[0] = 0x22ccb0 Adresse von c[1] = 0x22ccb1 Adresse von c[2] = 0x22ccb2 Adresse von c[3] = 0x22ccb3 Adresse von a[0] = 0x22cca0 Adresse von a[1] = 0x22cca4 Adresse von a[2] = 0x22cca8

116 Speicheradressen Beispiel: Adressen von mehrdimensionalen Feldkomponenten double A[3][2] = {{11,12},{21,22},{31,32}}; printf("adresse von A[0][0] = %u\n",&a[0][0]); printf("adresse von A[0][1] = %u\n",&a[0][1]); printf("adresse von A[1][0] = %u\n",&a[1][0]); printf("adresse von A[1][1] = %u\n",&a[1][1]); printf("adresse von A[2][0] = %u\n",&a[2][0]); printf("adresse von A[2][1] = %u\n",&a[2][1]); Adresse von A[0][0] = Adresse von A[0][1] = Adresse von A[1][0] = Adresse von A[1][1] = Adresse von A[2][0] = Adresse von A[2][1] = Überlegungen: Wenn man die Adresse einer Variable kennt, wird man auch ihren Inhalt, d.h. ihren Wert manipulieren können. Da man direkt auf den Arbeitsspeicher zugreift, sollte dies auch von jeder Stelle im Programm aus möglich sein - vorausgesetzt die Adresse im Speicher ist bekannt.

117 Zeiger: Definition und Prinzip Definition Ein Zeiger (engl. pointer) ist eine Variable, deren Inhalt eine Speicheradresse und der zugehörige Datentyp ist. Funktionsprinzip: Zeiger Variable Arbeitsspeicher Zugriff über Variablennamen Variablenname steht stellvertretend für einen Speicherbereich Verknüpfung ist mit Deklaration festgelegt und unveränderbar Bei Zugriff wird die Speicher-Referenz intern aufgelöst und der Wert anschließend gelesen bzw. geschrieben. Variable Arbeitsspeicher Zugriff über Zeiger Speicherbereich ist Inhalt der Zeigervariablen Dieser Inhalt ist selbstverständlich veränderbar Zeiger kann umgebogen werden Auflösung der Speicherreferenz geschieht explizit, d. h. durch den Programmierer

118 Zeiger Wozu überhaupt Zeiger? Indem man die Speicheradresse einer Variablen an eine Funktion übergibt, ermöglicht man es, den Inhalt der Variablen innerhalb der Funktion permanent zu verändern (Call by reference). Mit Zeigern lässt sich wie mit Feldern umgehen, mit dem Unterschied, dass sie als Parameter und als Rückgabewert von Funktionen verwendet werden können (dynamische Felder). Die gesamte Verwaltung von Speicherbereichen zur Laufzeit geschieht mit Hilfe von Zeigern (dynamische Speicherverwaltung). Zeiger ermöglichen es, aus einer Menge von Funktionen zur Ausführung einer bestimmten Aufgabe zur Laufzeit eine Variante auszuwählen (Callback-Prinzip).

119 Zeiger Deklaration: Datentyp *Zeigername; bzw. Datentyp *z1,..., *zn; Achtung: vor jedem Zeigername muss ein * stehen! Beispiel: int a, *p; erzeugt eine int-variable und einen Zeiger auf int. Bedeutung des Datentyps: Wird über einen Zeiger auf eine Speicheradresse zugegriffen, so gibt der Datentyp an, wie viele Byte von der (Start-)Adresse an gelesen bzw. geschrieben werden sollen. Achtung: Fehlinterpretationen, d. h. die Verwendung eines Zeigers auf einen anderen Datentyp als vorgesehen, kann zu ungewollten Ergebnissen führen. Beispiel: Zeiger auf int statt Zeiger auf char. int * char int Eine char-variable wurde deklariert Fälschlicherweise wurde ein Zeiger auf int verwendet, um über die Speicheradresse auf die char-variable zugreifen zu können. Bei einem Zugriff werden statt 1 Byte 4 Byte angesprochen, von denen 3 nicht mehr zum zulässigen Bereich gehören. Vor solchen Fällen warnt der Compiler bestenfalls!

120 Arbeiten mit Zeigern: Referenzen Adress- oder Referenzoperator: & kann auf jedes beliebige Speicherobjekt (Variablen, Funktionen,... ) angewandt werden. hat Vorrang vor Vergleichs- und arithmetischen Operatoren, nicht jedoch vor dem Array/Index-Operator []. liefert als Ergebnis einen konstanten Zeiger auf die Adresse (=Referenz) des Objekts. Beispiel: int i = 1; double x = M_E, y[] = {3.0, 0.0, 42.0}; printf("adresse von i: %p\n", &i); printf("wert von i: %d\n\n", i); printf("adresse von x: %p\n", &x); printf("wert von x: %lf\n\n", x); printf("adresse von y[2]: %p\n", &y[2]); // oder &(y[2]) printf("wert von y[2]: %lf", y[2]); Ausgabe: Adresse von i: 0x7fffb476d89c Wert von i: 1 Adresse von x: 0x7fff Wert von x: Adresse von y[2]: 0x7fff Wert von y[2]: Bemerkung: %p ist der Platzhalter für Adressen im Formatstring.

121 Arbeiten mit Zeigern: Referenzen Inhalts- oder Dereferenzierungsoperator: * lässt sich auf Zeiger anwenden. steht auf der gleichen Prioritätsstufe wie der Adressoperator &. liefert den Wert, der an der Adresse gespeichert ist, auf die der Zeiger verweist. Beispiel: int i = 1; double x = M_E, *px = &x, y[] = {3.0, 0.0, 42.0}; printf("adresse von i: %p\n", &i); printf("wert von i: %d\n\n", *(&i)); printf("adresse von x: %p\n", px); printf("wert von x: %lf\n\n", *px); printf("adresse von y: %p\n", y); printf("\"wert\" von y: %lf\n", *y); Ausgabe: Adresse von i: 0x7fffb476d89c Wert von i: 1 Adresse von x: 0x7fff Wert von x: Adresse von y: 0x7fff Wert von y: Bemerkung: Der *-Operator kann auch auf Felder angewandt werden!?

122 Arbeiten mit Zeigern: indirekter Zugriff auf Variablen Zeiger erlauben indirekten lesenden und schreibenden Zugriff auf zuvor deklarierte Variablen, indem man sie auf die entsprechenden Speicheradressen zeigen lässt. Beispiel: double x = M_PI, *p1 = &x, *p2; printf("wert von x: %lf\n\n", x); printf("zugriff via p1: x = %lf\n\n",*p1); x = -M_1_PI; printf("nach Manipulation,\n"); printf("zugriff via p1: x = %lf\n\n",*p1); p2=p1; *p2 = 0.1; printf("nach Manipulation via p2,\n"); printf("direkter Zugriff: x = %lf\n",x); Ausgabe: Wert von x: Zugriff via p1: x = Nach Manipulation, Zugriff via p1: x = Nach Manipulation via p2, direkter Zugriff: x = Achtung: In der Deklaration steht *p1 nicht für Dereferenzierung, sondern dafür, dass es sich bei der folgenden Variablen um einen Zeiger handelt!

123 Arbeiten mit Zeigern: Umbiegen Einem Zeiger kann als Variable, deren Inhalt eine Adresse ist, (fast) jeder beliebige (Adress-)Wert zugewiesen werden. Man spricht dann vom Umbiegen des Zeigers. Beispiel: int a = -5, b = 42, *p1 = &a, *p2 = &a; printf("p1 zeigt auf a:\n"); printf("&a = %p\n", &a); printf("p1 = %p\n\n", p1); p1 = &b; printf("nach Umbiegen auf b:\n"); printf("&b = %p\n", &b); printf("p1 = %p\n\n", p1); Ausgabe: p1 zeigt auf a: &a = 0x7fff72c3ecac p1 = 0x7fff72c3ecac Nach Umbiegen auf b: &b = 0x7fff72c3eca8 p1 = 0x7fff72c3eca8 Zurueckbiegen mit p2: p1 = 0x7fff72c3ecac p1 = p2; printf("zurueckbiegen mit p2:\n"); printf("p1 = %p\n", p1);

124 Typische Pointer-Fehler I Falsche Annahme, dass Name und Funktion automatisch zusammenhängen. Beispiele: int a, *a; Fehler: In Konflikt stehende Typen für a double x = 3.14, *px; printf("%lf\n", *px); Warnung: px is used uninitialized in this function Vergessenes & oder * Beispiel: int a = 42, *pa; pa = a; printf("wert von a: %d\n", pa); Bei der Zuweisung pa = a wird der Wert von a (42) als Adresse aufgefasst. In der printf-anweisung wird die (vermeintliche) Referenz nicht aufgelöst, sondern die Adresse, die in pa gespeichert wurde, als int ausgegeben. Warnung: Durch (schreibenden) Zugriff auf uninitialisierte oder wild verbogene Zeiger oder Zeiger auf einen inkompatiblen Datentyp lässt sich jedes Programm ins Chaos stürzen (z. B. wenn durch den falschen Zugriff ein anderer Zeiger verbogen wird).

125 Sonderfälle: void * und NULL Der Datentyp void * Ein Zeiger vom Typ void * ist kein Zeiger auf Nichts, sondern ein (universeller) Zeiger, der wie üblich eine Adresse speichert, dessen Typ jedoch (noch) nicht festgelegt ist. Einem Zeiger vom Typ void * können Adressen von typisierten Speicherobjekten zugewiesen werden. Durch einen korrekten Cast kann über den Zeiger auf Inhalte der Speicherobjekte zugegriffen werden. Dieser Cast sollte immer explizit sein. Der Nullzeiger NULL Formal ist NULL = (void *)0, d. h. die Zuweisungen p = NULL und p = 0 sind für einen Zeiger äquivalent. Verwendung von NULL erhöht aber die Lesbarkeit. Der Versuch, NULL mittels * zu dereferenzieren, führt unmittelbar zu einem Speicherzugriffsfehler. Bei der Deklaration eines Zeigers ist es ratsam, mit NULL (bzw. einem anderen Wert) zu initialisieren. Dadurch vermeidet man ungewollte Zugriffe auf fremde Speicherbereiche. Viele Funktionen mit einem Zeiger-Datentyp als Rückgabewert liefern bei Scheitern NULL. Dieser Fall kann zur Kontrolle abgefragt werden.

126 Typische Pointer-Fehler II Vergessener Cast Beispiel: int a; void *z = &a; printf("a = %d\n", *z); Warnung: Dereferenzierung eines void *-Zeigers Fehler: falsche Benutzung eines void-ausdruckes Cast eines void * auf einen falschen Datentyp (Fehlinterpretation des void *) Beispiel: int a; void *z = &a; double *p = (double *)z; printf("a = %lf\n", *p); a = (undefinierter Wert) Zuweisung statt Vergleich mit NULL Beispiel: int *p = NULL; p =... if (p = NULL) return 1; Der Fall, dass p den Wert NULL hat, wird garantiert nicht wie beabsichtigt abgefangen. (Wieso nicht?)

127 Call by reference Definition An eine Funktion werden Zeiger auf die Adressen von Variablen übergeben, mit deren Hilfe der Variableninhalt verändert werden kann. Zur Erinnerung: Call by value void vertausche(int a, int b) { int hilf = b; b = a; a = hilf; return; } Ergebnis: Beim Aufruf von vertausche in main passiert effektiv nichts, da nur die Werte der Funktionsparameter übergeben werden. Call by reference void vertausche(int *a, int *b) { int hilf = *b; *b = *a; *a = hilf; return; } In main: int x = 1, y = -2; vertausche(&x, &y); Ergebnis: Die Werte von x und y werden tatsächlich vertauscht!

128 Wie funktioniert Call by reference? Beispiel: Eine Funktion fkt soll zwei Gleitkommazahlen als Ergebnis einer Berechnung liefern (z. B. die Koordinaten eines berechneten Punktes in R 2 ). Mit Hilfe von return kann jedoch nur ein Wert zurückgegeben werden. Idee: Es werden zwei double-variablen erg1 und erg2 an die Funktion übergeben, in welche die Ergebnisse gespeichert werden sollen. Der Programmcode sieht wie folgt aus: Deklaration von fkt als void fkt(double *e1, double *e2); Aufruf in main in der Form fkt(&erg1, &erg2); Definition von fkt enthält Zuweisungen *e1 =...; *e2 =...; Folgendes passiert bei der Ausführung: Durch Anwendung des &-Operators entstehen zwei Zeiger auf double. Diese beiden Zeiger werden als Parameter (in Übereinstimmung mit der Deklaration) an fkt übergeben. Die Funktion fkt sieht zwar die beiden Variablen nicht, verfügt aber mit den Zeigern über ihre Adressen. Mit dem *-Operator wird die Referenz aufgelöst, und die Zuweisung von Werten an die betreffenden Speicherbereiche wird möglich. Fazit: Call by reference überwindet die Grenze des scope von Variablen.

129 Typische Pointer-Fehler III Call by reference überwindet zwar die scope-grenze, jedoch nicht die lifetime-grenze! Beispiel: int *neuer_zeiger(void) { int a; return &a; } Nach Beendigung der Funktion ist die Lebenszeit der Variablen a vorbei. Der (gültige!) erzeugte Zeiger verweist auf irgendeine Speicherstelle, die längst anderweitig vergeben sein kann. Call by value mit Pointern statt Call by reference Beispiel: void biege(int *a, int *b) { int *hilf = b; b = a; a = hilf; return; } Die Speicherreferenzen werden nicht aufgelöst. Stattdessen finden (ausschließlich lokale!) Zuweisungen von Adressen an Zeigervariablen statt. Pointer, die als Parameter übergeben werden, bleiben unverändert. (Call by value!!)

130 Zeigerarithmetik Beispiel: Erlaubte Operationen mit Zeigervariablen Addition und Subtraktion von Integer-Werten. Arithmetische Zuweisungen += und -=. Inkrement ++ und Dekrement --. Differenzbildung zweier Zeiger. double x1=5.5, x2=7, *p1=&x1, *p2=&x2; printf("&x1 = %p \n",&x1); printf("&x2 = %p \n\n",&x2); printf("&p1 = %p, p1 = %p \n*p1 = %lf\n", &p1,p1,*p1); printf("&p2 = %p, p2 = %p \n*p2 = %lf\n\n", &p2,p2,*p2); p2 = p1 + 2; printf("&p2=%p, p2=%p \n*p2=%lf\n\n", &p2,p2,*p2); p2--;p2--; printf("&p2=%p, p2=%p \n*p2=%lf\n", &p2,p2,*p2); Ausgabe: &x1 = 0x28ac08 &x2 = 0x28ac00 &p1 = 0x28abfc p1 = 0x28ac08 *p1 = &p2 = 0x28abf8 p2 = 0x28ac00 *p2 = &p2 = 0x28abf8 p2 = 0x28ac18 *p2 = &p2 = 0x28abf8 p2 = 0x28ac08 *p2 =

131 Zeiger und Felder Erinnerung: Der *-Operator lässt sich auf statische Felder anwenden und liefert das erste Feldelement. Frage: Wieso? Antwort: int main(void) { double x[] = {-3.14, 0.0, 4}; printf("werte:\n"); printf(" *x = %lf\n", *x); printf("x[0] = %lf\n", x[0]); printf("\nadressen:\n"); printf(" x = %p\n", x); printf(" &x = %p\n", &x); printf("&x[0] = %p\n", &x[0]); return 0; } Werte: *x = x[0] = Adressen: x = 0x7fffc291a0b0 &x = 0x7fffc291a0b0 &x[0] = 0x7fffc291a0b0 Fazit: Der Feldname ist zugleich ein konstanter Zeiger auf das erste Feldelement! Manipulationsversuche von x der Form x=x+2; x++; usw. scheitern. Folgerung: Mit einem Zeiger auf den Datentyp der Feldelemente kann man ebenfalls auf das Feld zugreifen!

132 Zeiger und Felder Frage: Das erste Feldelement ist schon mal ein Anfang, aber wie erhält man die restlichen mit Hilfe eines anderen Zeigers? Antwort: Genau wie beim Original-Feld mit []. int main(void) { double x[] = {-3.14, 0.1, 4}; double *p = x; // oder &x // oder &x[0] printf("x[2] = %lf\n", x[2]); printf("*(x+2) = %lf\n", *(x+2)); printf("\n*p++ = %lf\n",*p++); // dauerhafte Veraenderung von p! printf("*p = %lf\n", *p); printf(" p[0] = %lf\n", p[0]); printf(" p[1] = %lf\n", p[1]); return 0; } x[2] = *(x+2) = *p++ = *p = p[0] = p[1] = Da die Elemente hintereinander im Speicher angeordnet sind, wird x[n] intern interpretiert als springe um n Speicherpositionen weiter und gib den Wert an dieser Stelle zurück. In Pointer-Sprache ausgedrückt: *(x+n)

133 Zeigerarithmetik: Zeiger und Felder Bemerkungen Klammer bei *(x+2) ist nötig, da der Inhaltsoperator * eine höhere Priorität als der Additionsoperator besitzt. Inkrement- und Inhaltsoperator sind beide von der selben Prioritätsstufe. Wichtig: der Inkrementoperator bezieht sich auf den Zeiger und nicht auf den dereferenzierten Inhalt. Der Zeiger wird dauerhaft umgesetzt, d.h. er zeigt nun auf eine andere Adresse im Speicher. Merke Sei A ein Feld von einem beliebigen Typ. Dann sind die jeweiligen Ausdrücke in den folgenden Boxen äquivalent: A[i], *(A+i) Der zweite Ausdruck ist jedoch schwerer lesbar und wird als &A[i], A+i stilistisch schlecht angesehen. A, &A, &A[0], A+0

134 Zeigerarithmetik: Zeiger und Felder

135 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

136 Zeiger und Felder Zur Erinnerung: Der Name eines statischen Feldes ist zugleich ein Zeiger auf das erste Feldelement. Auf Zeiger kann der Index/Array-Operator [] angewandt werden, mit dem gleichen Effekt wie bei statischen Feldern. Statische Felder können anstelle von Zeigern auf den gleichen Typ an Funktionen übergeben werden. Beispiel: Der folgende Code ist semantisch korrekt und liefert wie erwartet die Ausgabe 1. Element: void zeigeersteselement( double *array) { printf("1. Element: %lf\n", array[0]); return; } int main(void) { double x[2] = {3.1415, -1.0}; zeigeersteselement(x); return 0; } Namen statischer Felder können als konstante Zeiger nicht umgebogen werden. Zuweisungen wie feld1 = feld2; schlagen fehl.

137 Ausgabe: ( 1.0, 1.0, 1.0, 1.5, 1.5, 1.5 ) ( 3.0, 3.0, 3.0, 3.5, 3.5, 3.5 ) Call by reference: Beispiel void vektor_ausgeben(double *vec,int n) // Funktion { int i; printf("("); for(i=0;i<n-1;i++) printf(" %2.1lf,",vec[i]); //alternativ: *(vec+i) printf(" %2.1lf )\n",vec[n-1]); } void vektor_erhoehen(double *vec,int n,int summand) // Funktion { int i; for(i=0;i<n;i++) vec[i] +=summand; } int main(void) // Hauptprogramm { double vec[6]={1,1,1,1.5,1.5,1.5}; vektor_ausgeben(vec,6); vektor_erhoehen(vec,6,2); vektor_ausgeben(vec,6); }

138 Dynamische Speicherverwaltung Problem: Mit den bisherigen Mitteln können nur Speicherobjekte angelegt werden, deren Größe zur Compilezeit bekannt ist. Beispiel: Einfacher Texteditor Umsetzung: der aktuell bearbeitete Text wird zeilenweise in einem zweidimensionalen Feld (z. B. char text[zeilen][spalten]) gespeichert. Frage 1: Wie kann man im Vorfeld eine sinnvolle Zeilenbreite festlegen? Antwort: Gar nicht! Es sollte berücksichtigt werden, wie viele Zeichen zum Zeitpunkt der Ausführung auf dem Bildschirm in einer Zeile darstellbar sind. Frage 2: Wie viele Zeilen sollte das Feld umfassen, damit einerseits ein Arbeiten mit Texten gängiger Länge möglich ist und andererseits das Programm nicht unnötig viel Speicher verbraucht? Antwort: Offenbar lassen sich nicht beide Bedingungen gleichzeitig erfüllen! Frage 3: Ist es sinnvoll, diese Größe festzulegen, ohne zu berücksichtigen, wie viel Speicher zur Verfügung steht, wie lang der bearbeitete Text tatsächlich ist und dass eventuell mehrere Texte gleichzeitig bearbeitet werden? Offensichtliche Antwort: Nein! Fazit: Mit diesen Speicherobjekten kommt man nicht besonders weit!

139 Dynamische Speicherverwaltung Naheliegende Lösung: Das Programm soll zur Laufzeit entscheiden, wann wieviel Speicher benötigt wird. Programmiertechnische Umsetzung: mit Pointern! Im Code wird explizit ein Speicherblock einer bestimmten Größe angefordert, indem eine speziell dafür vorgesehene Funktion aufgerufen wird. Die Funktion versucht, den Speicher zu reservieren ( allokieren ) und liefert bei Erfolg einen Zeiger auf die Startadresse des Speicherblocks, den sogenannten Basis-Zeiger (engl. base pointer). Wird der Speicher nicht mehr benötigt, ruft man eine weitere Funktion auf, um Block explizit wieder freizugeben. Der Basis-Zeiger ist die einzige Referenz auf den allokierten Speicherblock (solange man keinen anderen Pointer ebenfalls dorthin zeigen lässt). Man darf ihn auf keinen Fall verlieren (umbiegen, Lebenszeit ablaufen lassen,... ), denn sonst kann der Block nicht mehr freigegeben werden. speicherfressende Programme

140 Funktionen zur Speicherverwaltung Header: #include <stdlib.h> Anforderung von Speicherplatz void *malloc(size_t size); (memory allocation) Fordert einen (uninitialisierten!) Speicherblock der Größe size Byte an und gibt einen Zeiger auf die Startadresse zurück. Bei Scheitern wir NULL zurückgegeben. void *calloc(size_t anzahl, size_t size); (cleared memory allocation) Fordert einen Speicherblock für anzahl Elemente der Größe size an und initialisiert mit Nullen. Ansonsten wie malloc. Vergrößerung / Verkleinerung eines Speicherblocks void *realloc(void *ptr, size_t size); Ändert die Größe des Blocks zum Basis-Zeiger ptr auf size Byte. Der Inhalt des alten Blocks bleibt unverändert, neu angeforderter Speicher bleibt uninitialisiert. Rückgabewert ist der zum neuen Speicherbereich gehörige Base-Pointer, welcher sich von ptr unterscheiden kann(!). Bei Scheitern wird nichts unternommen. Freigabe eines Speicherblocks void free(void *ptr); Gibt den Speicher frei, auf den ptr zeigt.

141 Funktionen zur Speicherverwaltung Beispiel 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) 5 { 6 int i; 7 double *dyn_feld; 8 9 dyn_feld = (double *) malloc(5 * sizeof(double)); 10 // Cast! // Platz fuer 5 double-eintraege for(i = 0; i < 5; i++) // Initialisierung 13 dyn_feld[i] = 2.0 * i; 14 for(i = 0; i < 5; i++) // Ausgabe 15 printf("dyn_feld[%d] = %lf\n", i, dyn_feld[i]); dyn_feld = (double *) realloc(dyn_feld, 8 * sizeof(double)); 18 // Vergroesserung auf 8 double-eintraege 19 for(i = 5; i < 8; i++) 20 dyn_feld[i] = 2.0 * i; // Initialisierung der neuen Eintraege 21 for(i = 0; i < 8; i++) 22 printf("dyn_feld[%d] = %lf\n", i, dyn_feld[i]); free(dyn_feld); dyn_feld = NULL; // Vorsichtsmassnahme return 0; 27 }

142 Der sizeof-operator Der Speicherbedarf wird niemals direkt als magische Zahl angegeben, sondern immer mit Hilfe des sizeof-operators berechnet. Erinnerung: sizeof(datentyp) oder sizeof(speicherobjekt) liefert den Speicherbedarf eines allgemeinen Datentyps oder eines deklarierten Speicherobjekts in Byte. Achtung: Bei statischen Feldern liefert sizeof die tatsächliche Größe des Feldes, bei dynamischen Feldern hingegen nur den Speicherbedarf der Pointervariable. Mit sizeof lässt sich die Größe eines dynamischen Feldes nicht feststellen! Bemerkung: Da Pointer Adressen speichern, ist sizeof(datentyp *) unabhängig vom Datentyp immer gleich sizeof(size_t).

143 Typische Fehler bei der Speicherverwaltung Nicht mehr benötigter Speicher wird vergessen freizugeben. double berechnung(double *vektor, int n) { double ergebnis, *zw_speicher = (double *) malloc(n*n * sizeof(double)); // Hier wird etwas berechnet return ergebnis; } Folge: Speicher bleibt bis zum Ende des Programms belegt, schlimmstenfalls wird der gesamte Speicher aufgebraucht und das System geht in die Knie. Speicher so früh wie möglich und in jedem Fall wieder freigeben Auf bereits freigegebenen Speicher wird zugegriffen. int *arr = (int *) malloc(2 * sizeof(int)); free(arr); arr[0] = -24; arr[1] = 42; printf("ptr2[0] = %d\n", arr[0]); printf("ptr2[1] = %d\n", arr[1]); Folge: Möglicherweise stehen dort noch immer die gleichen Werte, doch dafür gibt es keine Garantie. Vor einem solchen Zugriff wird nicht gewarnt. Basis-Pointer von freigegebenen Blöcken auf NULL setzen

144 Typische Fehler bei der Speicherverwaltung Ein umgebogener Base-Pointer wird in free() verwendet. int i, *arr = (int *) malloc(20 * sizeof(int)); for (i = 0; i < 20; i++) *(arr++) = 5 * i; free(arr); Folge: Speicherzugriffsfehler Es werden statische statt dynamischer Felder verwendet. int feld[5], *ptr; ptr = feld; ptr[4] = 42; // Alternativer Zugriff // Code free(ptr); Folge: Speicherzugriffsfehler

145 Dynamische Speicherverwaltung Speicherverwaltung während der Programmlaufzeit: Speicherbereich Code Daten-Segment Stack (dt. Stapel) Heap (dt. Halde) Verwendung Maschinencode des Programms Statische und globale Variablen Funktionsaufrufe und lokale Variablen Dynamisch reservierter Speicher

146 Variablentypen Lokale, globale und statische Variablen Name und Typ werden im Programmcode durch eine Deklaration festgelegt. Variablen werden direkt über ihren Namen angesprochen. Scope und Lifetime sind durch die statische Struktur des Programms festgelegt. Speicherverwaltung erfolgt implizit: Lokale Variable werden im Stack angelegt. Globale und statische Variablen werden im Daten-Segment angelegt. Dynamische Variablen Erscheinen in keiner Deklaration, tragen keinen Namen. Variablen werden über ihre Adressen mit einem Pointer angesprochen. Scope und Lifetime folgen nicht den Regeln statischer Variablen. Speicherverwaltung im Heap geschieht explizit im Programm.

147 Dynamische Implementierung von Vektoren Vorüberlegung: Vektoren x R n lassen sich offenbar als Felder double x[n] implementieren. Um flexibler zu sein, bietet sich eine Implementierung als dynamisches Feld double *x in Verbindung mit den Speicherverwaltungsfunktionen an. Erzeugung eines neuen Vektors: double *neuervektor(int laenge) { double *v; v = (double *) malloc(laenge * sizeof(double)); return v; // Bei Scheitern automatisch NULL } Erzeugung eines Nullvektors: double *neuernullvektor(int laenge) { double *v; v = (double *) calloc(laenge, sizeof(double)); return v; // Bei Scheitern automatisch NULL }

148 Dynamische Implementierung von Vektoren Ausgabe am Bildschirm: void zeigevektor(double *vektor, int laenge) { int i; for (i = 0; i < laenge; i++) printf("%lf\n", vektor[i]); return; } Löschung: void loeschevektor(double *vektor) { free(vektor); return; }

149 Dynamische Implementierung von Vektoren Kopie anlegen: void kopierevektor(double *ziel, double *quelle, int laenge) { int i; for (i = 0; i < laenge; i++) ziel[i] = quelle[i]; return; } Weitere Funktionen: Arithmetische Operationen mit Vektoren (elementweise) Skalierung, Addition einer Konstanten (Betragsmäßig) maximales / minimales Element finden Untervektoren: Block, jedes n-te Element Permutationen

150 Minimales Verwendungsbeispiel 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 double *neuervektor(int laenge); 5 void zeigevektor(double *vektor, int laenge); 6 void loeschevektor(double *vektor); 7 void kopierevektor(double *ziel, double *quelle, 8 int laenge); 9 10 int main(void) 11 { 12 int i, N = 10; 13 double *v = neuervektor(n), *w = neuervektor(n); if (v==null w==null) 16 { 17 printf("zu wenig freier Speicher!"); 18 free(v);free(w); 19 return 1; 20 } 21 for (i = 0; i < N; i++) 22 v[i] = 2.0 * i; kopierevektor(w, v, N); 25 printf("w =\n"); 26 zeigevektor(w, N); 27 loeschevektor(v); loeschevektor(w); 28 return 0; 29 } 30 // Hier kommen noch die Funktionsdefinitionen Ausgabe: w =

151 Dynamische Implementierung von Matrizen Vorüberlegungen: Felder in C sind eindimensional, Matrizen jedoch (mindestens) zweidimensional. Jede Zeile einer 2D-Matrix kann als 1D-Vektor aufgefasst werden. Idee 1: Eine Matrix A R m n wird als Feld von Vektoren A i R n interpretiert. A 1 A 2 A 2,3 A 3 A 4 Idee 2: Die Zeilen einer Matrix A R m n werden aneinandergereiht, so dass ein langer Vektor a R m n entsteht. Es gilt der Zusammenhang A i,j = a (i 1)n+(j 1). A 1 A 2 A 3 A 4 a 11 a

152 Dynamische Implementierung von Matrizen Umsetzung in C Variante 1: Beispiel A R 4 8 double ** double * A A[0] A[0][0] A[0][7] A[1] A[1][0] A[1][7] A[2] A[2][0] A[2][7] A[3] A[3][0] A[3][7] Ein Vektor v ist ein (dynamisches) Feld vom Typ double, implementiert als double *v. Folglich ist eine Matrix A ein (dynamisches) Feld von Vektoren A[i] vom Typ double *. Technisch gesehen ist A ein Zeiger auf double *, und jedes A[i] ist ein Zeiger auf double. Deklaration: double **A; Zugriff auf Feldelemente: A i,j ˆ= A[i-1][j-1]

153 Dynamische Implementierung von Matrizen: Funktionen Erzeugung einer Matrix: 1 double **neuematrix(int n_zeilen, int n_spalten) 2 { 3 int i, j; 4 double **matrix; 5 6 matrix = (double **) malloc(n_zeilen * sizeof(double *)); 7 if (matrix == NULL) // Speicheranforderung gescheitert 8 return NULL; 9 10 for (i = 0; i < n_zeilen; i++) 11 { 12 matrix[i] = (double *) malloc(n_spalten * sizeof(double)); 13 if (matrix[i] == NULL) 14 { 15 for (j = 0; j < i; j++) 16 free(matrix[j]); free(matrix); 19 return NULL; 20 } 21 } return matrix; 24 }

154 Dynamische Implementierung von Matrizen: Funktionen Bei der Speicheranforderung für die Matrix (double **) muss der Größenmultiplikator sizeof(double *) sein, denn jeder Eintrag in matrix ist schließlich ein double *! matrix = (double **) malloc(n_zeilen * sizeof(double *)); Scheitert die Anforderung, wird NULL zurückgegeben. if (matrix == NULL) return NULL; Ist die Speicheranforderung für eine Zeile A[i] erfolglos, so muss der bisher reservierte Speicher - also sämtliche Zeilenvektoren A[j] (j < i) sowie matrix selbst - wieder freigegeben werden, bevor die Funktion mit Rückgabewert NULL endet. if (matrix[i] == NULL) { for (j = 0; j < i; j++) free(matrix[j]); free(matrix); return NULL; }

155 Dynamische Implementierung von Matrizen: Funktionen Ausgabe einer Matrix auf dem Bildschirm: 1 void zeigematrix(double **matrix, int n_zeilen, int n_spalten) 2 { 3 int i, j; 4 5 for (i = 0; i < n_zeilen; i++) 6 { 7 for (j = 0; j < n_spalten; j++) 8 printf("%.4lf ", matrix[i][j]); 9 10 printf("\n"); 11 } return; 14 } Beachte: Die Ausgabe auf der Kommandozeile funktioniert nur zeilenweise. Nach dem Ende der Zeile den Newline-Character \n nicht vergessen!

156 Dynamische Implementierung von Matrizen: Funktionen Löschung einer Matrix: 1 void loeschematrix(double **matrix, int n_zeilen) 2 { 3 int i; 4 5 for (i = 0; i < n_zeilen; i++) 6 free(matrix[i]); 7 8 free(matrix); 9 } Beachte: Es genügt nicht, das übergeordnete Feld matrix zu befreien. Jedes Feld matrix[i] muss einzeln an free() übergeben werden. Sicherheitshalber sollte nach dem Aufruf loeschematrix(a, n); noch die Zuweisung A = NULL; vorgenommen werden, um zukünftige Zugriffe auf den freigegebenen Bereich zu verhindern.

157 Anwendungsbeispiel 1 #include<stdio.h> 2 #include<stdlib.h> 3 4 double **neuematrix(int n_zeilen, int n_spalten); 5 void zeigematrix(double** matrix, int n_zeilen, int n_spalten); 6 void loeschematrix(double** matrix, int n_zeilen); 7 8 int main(void) 9 { 10 int i, j, n = 10; 11 double **A; A = neuematrix(n, n); for (i = 0; i < n; i++) 16 { 17 for (j = 0; j < n; j++) 18 A[i][j] = i+0.1*j; 19 } zeigematrix(a, n, n); 22 loeschematrix(a, n); return 0; 25 } // Ab hier noch die Funktionsdefinitionen

158 Dynamische Implementierung von Matrizen Umsetzung in C Variante 2: A 1 A 2 A 3 A 4 a 11 Deklaration: double *a; Als Funktionen zum Erzeugen und Löschen einer Matrix können die Vektor-Funktionen neuervektor() und loeschevektor() verwendet bzw. umbenannt werden. Zum Zugriff auf das Element A i,j verwendet man den Ausdruck a[i * n_spalten + j]. a Umgekehrt lassen sich Zeilen- und Spaltenindex in a[index] berechnen mittels i = index / n_spalten; j = index % n_spalten; Vorteil gegenüber Doppelpointer-Variante: Vermeidung von Doppelschleifen schneller bei Operationen auf der ganzen Matrix und bei Multiplikation. Viele numerische Programmbibliotheken (GSL, BLAS, NumPy,... ) verwenden intern diese Darstellung. Nachteil: Operationen basierend auf Zeilen- und Spaltenindex (z. B. transponieren) sind wegen des zusätzlichen Berechnungsschritts aufwändiger.

159 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

160 Strukturen: Motivation Situation: Mit Funktionen verfügen wir über ein wirksames Mittel, um Programmcode sinnvoll zu gliedern und Komplexität zu verbergen. Für Daten haben wir bisher kein solches Konstrukt! Beispielproblem: Adressdatenbank Eine Adresse (in Deutschland) besteht aus Straße Hausnummer Stockwerk Postleitzahl Ort Zeichenkette positive Ganzzahl (Vorsicht: 21a nicht darstellbar!) positive Ganzzahl (optional) positive Ganzzahl Zeichenkette Umsetzung: jede Informationseinheit wird in einem entsprechenden Feld gespeichert, und die Einträge mit gleichen Feldindices gehören zueinander. char **strassen, unsigned *hausnummern, unsigned *stockwerke, unsigned *postleitzahlen, char **orte Nachteile: Unlogische Gruppierung von Daten, die miteinander nichts zu tun haben Zuordnung über den Feldindex ist fehleranfällig

161 Strukturen Bessere Lösung: jede Adresse ist wie eine Variable ansprechbar, deren Elemente die einzelnen Informationseinheiten sind. Dazu gibt es in C das Konzept der Strukturen. Definition Eine Struktur ist die Zusammenfassung einer bestimmten Anzahl von Daten (möglicherweise) verschiedenen Typs zu einer Einheit, die mit einem festgelegten Namen angesprochen werden kann. Deklaration eines Strukturtyps: struct Etikett; Teilt dem Compiler mit, dass es einen Strukturtyp mit einem bestimmten Etikett (engl. structure tag) gibt. Muss außerhalb von main stehen. einer Strukturvariablen: struct Etikett Variablenname; Teilt dem Compiler mit, dass eine Variable eines bestimmten Namens gibt, die vom Typ struct Etikett ist. Definition eines Strukturtyps: struct Etikett {Codeblock}; Legt fest, welche Komponenten (engl. members) zu einer Struktur dieses Typs gehören. Muss außerhalb von main stehen (meistens direkt nach oder sogar statt der Deklaration).

162 Strukturen Anwendung auf das Beispielproblem: 1 struct Adresse; // Deklaration des Strukturtyps "Adresse" 2 3 struct Adresse // Definition der Struktur 4 { 5 char *strasse; 6 unsigned hausnr; 7 int stock; // 0 = "keine Angabe" 8 unsigned plz; 9 char *ort; 10 }; int main(void) 13 { 14 // Deklaration einer Variablen vom Typ "struct Adresse" 15 struct Adresse meine_adr; 16 printf("sizeof(struct Adresse) = %lu\n", sizeof(struct Adresse)); 17 printf("alternativ: sizeof(meine_adr) = %lu\n", sizeof(meine_adr)); 18 return 0; 19 } Die Definition macht in diesem Beispiel die Deklaration überflüssig. Anzahl und Namen der Komponenten der Struktur sind mit der Definition festgelegt und im Nachhinein nicht mehr veränderbar. Hinter der schließenden geschweiften Klammer muss ein Semikolon stehen! struct Adresse kann wie ein gewöhnlicher Datentyp behandelt werden.

163 Strukturen: Zugriff auf Komponenten Die Komponenten einer Struktur werden mit ihrem Namen angesprochen, im Gegensatz zum Zugriff über Indices bei Feldern. Das Pendant zum Index-Operator [] ist der Strukturkomponenten-Operator.. Verwendung: Strukturvariable.Komponentenname Greift auf die Komponente des entsprechenden Namens einer zuvor deklarierten Strukturvariable zu. Der.-Operator hat wie [] höchste Priorität, insbesondere höher als *, &, ++, Cast und arithmetische Operatoren. Beispiel: meine_adr.strasse = "Stuhlsatzenhausweg"; meine_adr.hausnr = 103; meine_adr.stock = 0; meine_adr.plz = 66123; meine_adr.ort = "Saarbruecken"; Ausgabe: Stuhlsatzenhausweg Saarbruecken printf("%s %u\n", meine_adr.strasse, meine_adr.hausnr); printf("%u %s\n", meine_adr.plz, meine_adr.ort);

164 Strukturen: Weitere Eigenschaften Initialisierung bei Deklaration: wie bei statischen Feldern in geschweiften Klammern struct Adresse meine_adr = {"Stuhlsatzenhausweg", 103, 0, 66123, "Saarbruecken"}; Strukturen können andere Strukturen enthalten (Schachtelung) struct Anschrift { char *nachname; char *vorname; struct Adresse adresse; }; Initialisierung: struct Anschrift meine_anschrift = {"mit Biergarten", "Restaurant", {"Stuhlsatzenhausweg", 103, 0, 66123, "Saarbruecken"} }; Zugriff auf geschachtelte Strukturen: z. B. meine_anschrift.adresse.hausnr Achtung: Zu tiefe Schachtelung macht Code unlesbar!

165 Strukturen: Weitere Eigenschaften Zeiger auf Strukturen: struct Etikett *pointer Deklariert einen Zeiger auf den Datentyp struct Etikett Für den Zugriff (*pointer).komponente auf Komponenten mit Hilfe des Pointers gibt es die vereinfachte Schreibweise pointer->komponente Strukturen können einen Zeiger auf den eigenen Typ enthalten.dazu müssen jedoch Deklaration und Definition getrennt werden: struct Anschrift; struct Anschrift { char *nachname; char *vorname; struct Adresse adresse; struct Anschrift *li_nachbar; struct Anschrift *re_nachbar; } meine_anschrift_; Bemerkung: In diesem Programmcode wird in Verbindung mit der Definition der Struktur Anschrift eine globale Instanz meine_anschrift_ deklariert. Zugriff auf Nachbarelement: z. B. meine_anschrift_.li_nachbar->hausnr Anwendung: Verkettung von Daten, z. B. Listen oder Bäume

166 Strukturen: Weitere Eigenschaften Zeiger auf Strukturen können an Funktionen als Parameter übergeben und als Rückgabewert zurückgegeben werden. Strukturen, die andere Strukturen oder dynamische Speicherobjekte enthalten, sollten nicht mehr von Hand, sondern mit speziell dafür vorgesehenen Funktionen (Erstellung, Manipulation von Komponenten, Kopie, Löschung,... ) verwaltet werden Robuste Programme, Anfänge der Objektorientierten Programmierung Vorsicht Strukturen können nur komponentenweise verglichen werden. Der Vergleich struct Vektor u,v; if (u==v) printf("vektoren stimmen überein"); führt zu der Fehlermeldung: invalid operands to binary ==

167 Strukturen: Weitere Eigenschaften Vorsicht: Bei Zuweisung wird nur die oberste Ebene betrachtet. 1 struct Vektor 2 { 3 int dim; 4 char *label; 5 int *koord; 6 }; 7 8 int main(void) 9 { 10 struct Vektor u = {2, "Ursprung", NULL}, e1; 11 u.koord = (int *) malloc(u.dim * sizeof(int)); e1 = u; // Shallow copy 14 e1.label = "1. Einheitsvektor"; 15 //umbiegen des Zeigers e1.label 16 e1.koord[0] = 1; printf("%s u = (%d,%d), ", u.label, u.koord[0], u.koord[1]); 19 printf("%s e1 = (%d,%d)\n", e1.label, e1.koord[0], e1.koord[1]); free(u.koord); 22 return 0; 23 } Ausgabe: Ursprung u = (1,0), 1. Einheitsvektor e1 = (1,0) Es wurden sämtliche Variablenwerte (= Adressen bei Zeigern!) kopiert, nicht jedoch das dynamische Feld! (Woher soll der Compiler auch davon wissen?)

168 Typendefinition Mit Hilfe des Schlüsselworts typedef lassen sich in C eigene Datentypen definieren. Syntax: typedef AlterDatentyp NeuerDatentyp; Beispiel 1: primitive Datentypen typedef unsigned long int size_t; Anwendung: architekturunabhängige Programmierung Beispiel 2: in Verbindung mit Strukturen typedef struct Adresse Adresse; Im folgenden Code kann statt struct Adresse einfach Adresse geschrieben werden. Folge: erhöhte Lesbarkeit Die Typendefinition lässt sich mit der Strukturdefinition verbinden: typedef struct _Adresse_ Adresse; struct _Adresse_ { char *strasse; unsigned hausnr; int stock; unsigned plz; char *ort; }; oder äquivalent: typedef struct { char *strasse; unsigned hausnr; int stock; unsigned plz; char *ort; } Adresse;

169 Beispiel: Erstellen/Kopieren eines Vektors 1 Vektor *neuervektor(int dim, char *label, int *koord) 2 { Vektor *vec=(vektor *) malloc(sizeof(vektor));int i; 3 if(vec==null) 4 printf("zu wenig speicher"); 5 vec->dim = dim; 6 vec->koord = (int *) malloc(dim*sizeof(int)); 7 for (i=0;i<dim;i++) 8 vec->koord[i] = koord[i]; 9 vec->label = duplizierestring(label); 10 return vec;} char *duplizierestring(char *string) 13 {char *clone = NULL; 14 if (string) 15 { 16 if (!(clone = (char *) malloc(sizeof(char)*(strlen(string)+1)))) 17 printf("zu wenig Speicher!"); 18 strcpy(clone,string); 19 } 20 return clone;} Ausschnitt aus dem Hauptprogramm: int dimension=2; koord=(int*) malloc(dimension*sizeof(int)); char ulabel[]="ursprung"; struct Vektor *u=neuervektor(dimension,ulabel,koord); //neuen Vektor erzeugen struct Vektor *e1=neuervektor(u->dim,u->label,u->koord); //Vektor kopieren

170 Beispiel: komplexe Zahlen Deklarationen und Definitionen 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 5 typedef struct 6 { 7 double re; 8 double im; 9 } Complex; Complex *newcomplexpolar(double radius, double angle); 12 Complex *complexproduct(complex *z1, Complex *z2); 13 // Complex *newcomplexpolar(double radius, double winkel) 16 { 17 Complex *z = (Complex *) malloc(sizeof(complex)); 18 z->re = radius * cos(winkel); 19 z->im = radius * sin(winkel); 20 return z; 21 } 22 // Complex *complexproduct(complex *z1, Complex *z2) 24 { 25 Complex *z = (Complex *) malloc(sizeof(complex)); 26 z->re = z1->re*z2->re - z1->im*z2->im; 27 z->im = z1->re*z2->im + z1->im*z2->re; 28 return z; 29 }

171 Beispiel: komplexe Zahlen Hauptprogramm 1 int main(void) 2 { 3 Complex a = {1.0, 1.0}; 4 Complex *b = newcomplexpolar(2.0, 3*M_PI/4.0); 5 Complex *c = complexproduct(&a, b); 6 7 printf("a = %lf+%lfi\n", a.re, a.im); 8 printf("b = %lf+%lfi\n", b->re, b->im); 9 printf("a*b = %lf+%lfi\n", c->re, c->im); 10 c->re = 0; 11 c->im = 2.5; 12 b = complexproduct(&a, c); 13 printf("a*c = %lf+%lfi\n", b->re, b->im); return 0; 16 } Ausgabe: a = i b = i a*b = i a*c = i

172 Felder von Strukturen 1 int main(void) 2 { 3 int j, n = 10; 4 Complex *dyn_vek = (Complex *) calloc(n, sizeof(complex)); 5 Complex stat_vek[10]; 6 7 for (j=0; j<n; j++) 8 { 9 dyn_vek[j].re = 2.0 * j; 10 dyn_vek[j].im = 0.5 * j; 11 stat_vek[j].re = 2.0 * j; 12 stat_vek[j].im = 0.5 * j; 13 } 14 printf(" dyn_vek[7] = %lf+%lfi\n", dyn_vek[7].re, dyn_vek[7].im); 15 printf("stat_vek[7] = %lf+%lfi\n", stat_vek[7].re, stat_vek[7].im); free(dyn_vek); 18 return 0; 19 } Ausgabe: dyn_vek[7] = stat_vek[7] = i i Statische und dynamische Felder von Strukturen können wie gewohnt verwaltet werden.

173 Allgemeine Merkregeln für Strukturen Strukturen dienen dazu, Einzelinformationen zu einer sinnvollen Einheit zusammenzufassen. Häufig sind Strukturen Abbilder von Konzepten aus dem Alltag oder von mathematischen Objekten (siehe Beispiele zu Adressen und komplexen Zahlen). Geschachtelte Strukturen sind möglich und oft sinnvoll, bergen aber die Gefahr, zu große Komplexität zu erzeugen und das Ziel größerer Ordnung zu verfehlen. Zudem steigt das Risiko von Programmierfehlern (s. shallow-copy-problem). Strukturen, die andere Strukturen oder dynamische Speicherobjekte enthalten, sollten nicht mehr von Hand, sondern mit speziell dafür vorgesehenen Funktionen verwaltet werden (Erstellung, Manipulation von Komponenten, Kopie, Löschung,... ) Anfänge der Objektorientierten Programmierung Zur Erhöhung der Lesbarkeit sollten Strukturen immer mit einem typedef als neuer Datentyp definiert werden. Zur Namensgebung hat sich folgende Konvention durchgesetzt: ErsterBuchstabeGrossOhneUnterstriche für eigene typedef-datentypen _VorneUndHintenMitUnterstrichen_ für Strukturen, die nicht direkt, sondern nur in Verbindung mit typedef verwended werden. Beispiel: typedef struct _Adresse_ Adresse; Verwendung nur über Adresse, niemals über struct _Adresse_

174 Funktionszeiger: Vorüberlegungen Funktionsaufrufe sind bis jetzt im Code mit Name explizit angegeben ( hartcodiert ) Folge: Zur Compilierzeit muss bekannt sein, welche Funktion eine bestimmte Aufgabe erfüllen soll. Beispielszenario: Wir haben einen Sortieralgorithmus geschrieben, der ein Feld von Zahlen sortiert. Dabei wollen wir flexibel bestimmen können, nach welchem Vergleichskriterium sortiert werden soll (größer, kleiner, 5. Ziffer in Dezimaldarstellung größer, Quersumme kleiner,... ). Mit den bisherigen Mitteln müsste im Algorithmus nach Vergleichskriterien unterschieden werden, z. B. mittels if-else oder switch. Alternativ müsste für jedes Vergleichskriterium eine separate Funktion geschrieben werden. Einleuchtendere Herangehensweise: Es gibt einen allgemeinen Sortieralgorithmus, der das Feld nach einer bestimmten Methode durchsucht und beim Vergleich zweier Elemente irgendein (variables!) Vergleichskriterium heranzieht. Zum Vergleich gibt es eine Reihe von Vergleichsfunktionen, die getrennt vom Sortierverfahren deklariert und definiert sind. Beim Aufruf des Sortieralgorithmus wird eine der Vergleichsfunktionen als Parameter mit angegeben. Genau dieses Verhalten lässt sich mit Funktionszeigern erzeugen!

175 Funktionszeiger:Deklaration Datentyp (*FktZeigerName)(Parameter(typ)liste); Deklariert einen Zeiger auf eine Funktion, welche die Signatur Datentyp Funktion(Parameter(typ)liste) besitzt. Beispiele: void (*InitFkt)(double *, double); Zeiger auf eine Funktion, die einen double *- und einen double-parameter nimmt und nichts (void) zurückgibt. double (*ZufGen)(void) Zeiger auf eine Funktion, die keine Parameter nimmt und einen double-wert zurückgibt. double *(*NeuesFeld)(unsigned) Zeiger auf eine Funktion, die einen unsigned-parameter nimmt und einen Pointer auf double zurückgibt.

176 Funktionszeiger: Beispiel Deklaration und Definition von zwei Anzeigevarianten 1 #include <stdio.h> 2 3 void anzeigevariante1(char *text); 4 void anzeigevariante2(char *text); 5 6 // void anzeigevariante1(char* text) 9 { 10 printf("\n %s\n", text); 11 return; 12 } // void anzeigevariante2(char* text) 17 { 18 printf("\n **********************************"); 19 printf("\n * %-30s *\n", text); 20 printf(" **********************************\n"); 21 return; 22 }

177 Funktionszeiger: Beispiel Hauptprogramm 1 int main(void) 2 { 3 void (*AnzFkt)(char *); 4 5 AnzFkt = anzeigevariante1; 6 AnzFkt("Test Variante 1"); 7 8 AnzFkt = anzeigevariante2; 9 AnzFkt("Test Variante 2"); return 0; 12 } Ausgabe: Test Variante 1 ********************************** * Test Variante 2 * ********************************** Vor dem Funktionsnamen in der Zuweisung steht kein &-Operator. Wie bei statischen Feldern hätte er keine Auswirkung. Beim Aufruf der Funktion via Pointer wird kein *-Operator benötigt. Die Schreibweise (*AnzFkt)(...) wäre äquivalent, nicht jedoch *AnzFkt(...)!

178 Funktionszeiger: Bemerkungen Bei Zuweisungen mit Funktionspointern ist unbedingt darauf zu achten, dass die Signaturen von Pointer und Funktion übereinstimmen. Alles andere führt zu unkontrollierbarem Programmverhalten! Vor Fehlern dieser Art warnt der Compiler bestenfalls. Deklarationen von Zeigern auf Funktionen mit langer Parameterliste werden leicht unleserlich (vor allem bei Funktionen mit Funktionszeigern als Parameter): void funktion(int a, double b, double *(*f)(double, int, int, double *, double *)); int main(void) { double *(*fp)(double, int, int, double *, double *); fp = testfkt; // testfkt sei passend deklariert funktion(42, 3.14, fp); return 0; } Ein typedef schafft Abhilfe: typedef double *(*MeineFkt)(double, int, int, double *, double *); void funktion(int a, double b, MeineFkt f); Deklaration der Funktionspointer-Variable in main: MeineFkt fp;

179 Rangfolge von Operatoren (Überblick) Priorität Operator Bedeutung Assoziativität Priorität 1 () Funktionenaufruf linksassoziativ [] Array/Index-Operator., -> Member-Zugriff Priorität 2! Logische Negation rechtsassoziativ ++, Inkrement, Dekrement, + Unäres Plus, unäres Minus & Adress-Operator * Dereferenzierung (type) Cast Priorität 3 *, / Multiplikation, Division linksassoziativ % Modulo Priorität 4 +, Plus, Minus linksassoziativ Priorität 6 <, <=, >, >= kleiner,... linksassoziativ Priorität 7 ==,! = gleich, ungleich linksassoziativ Priorität 11 && logisches UND linksassoziativ Priorität 12 logisches ODER linksassoziativ

180 Merkregeln und lesen von Deklarationen Merke: Rechtsassoziativ sind lediglich: Zuweisungsoperatoren, Bedingungsoperator (? :) und unäre Operatoren. Sinnvolle Klammerungen können die Lesbarkeit von Code deutlich erhöhen! int * (*Funkzeiger)(); Resultat: Funkzeiger ist ein Zeiger (1) auf eine Funktion (2) mit leerer Parameterliste, die einen Zeiger (3) auf Integer (4) zurückgibt.

181 Publikumsfrage Was deklarieren die folgenden Statements? double *f(double *, int); Funktion, die als Parameter einen double * und einen int nimmt und einen double * zurückgibt. double (*f)(double *, int); Zeiger auf eine Funktion, die als Parameter einen double * und einen int nimmt und einen double zurückgibt. double *(*f)(double *, int); Zeiger auf eine Funktion, die als Parameter einen double * und einen int nimmt und einen double * zurückgibt. double *g[20]; g ist Feld mit 20 Einträgen vom Typ double *.

182 Funktionszeiger: Beispiel Sortierverfahren In stdlib.h ist die folgende Sortierfunktion deklariert ( quicksort ): void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); Parameter: base Zu sortierendes Feld eines (noch) nicht festgelegten Datentyps nmemb Anzahl der Feldelemente size Größe eines Feldelements in Byte compar Zeiger auf eine (Vergleichs-)Funktion, die zwei void *-Zeiger auf zu vergleichende Elemente als Parameter nimmt und einen int zurückgibt. Interpretation: compar repräsentiert eine mathematische Ordnungsrelation auf einer Menge M, d. h. für zwei beliebige Werte a, b M gilt entweder a b, b a oder beides. Die zu vergleichenden Elemente von base stammen aus M. Der Rückgabewert von compar ist -1 (a b), 0 (Gleichheit) oder +1 (b a), wobei a dem ersten und b dem zweiten Parameter von compar entspricht.

183 Funktionszeiger: Beispiel Sortierverfahren Anwendung: 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int unsign_qsumme_kleiner(const void *pa, const void *pb); 5 6 int main(void) 7 { 8 unsigned z[5] = {23, 511, 10100, 8, 333}; 9 qsort(z, 5, sizeof(unsigned), unsign_qsumme_kleiner); printf("sortiertes Feld:\n"); 12 printf("%d %d %d %d %d\n", z[0], z[1], z[2], z[3], z[4]); 13 return 0; 14 } int unsign_qsumme_kleiner(const void *pa, const void *pb) 17 { 18 unsigned a = *((unsigned *) pa), b = *((unsigned *) pb); 19 unsigned qs_a = a % 10, qs_b = b % 10; while (a /= 10) 22 qs_a += a % 10; while (b /= 10) 25 qs_b += b % 10; return (qs_a < qs_b)? -1 : (qs_b < qs_a)? +1 : 0; 28 }

184 Funktionszeiger: Beispiel Sortierverfahren Codeanalyse Deklaration und Definition der Sortierfunktion unsign_qsumme_kleiner soll die Quersumme von unsigned-werten vergleichen. Die Signatur muss int fkt(const void *, const void *) sein. Im Funktionsrumpf werden die Parameter pa und pb zunächst als unsigned * gecastet, anschließend dereferenziert und die Werte in unsigned-variablen a und b geschrieben. Exemplarisch für pa: unsigned a = *( (unsigned *) pa ); In den Schleifen werden die Quersummen von a und b berechnet und in qs_a bzw. qs_b gespeichert. Beachte: while (a /= 10) führt zuerst Ganzzahl-Division durch und prüft anschließend, ob das Ergebnis ungleich 0 ist (in diesem Fall besteht a noch nicht aus einer einzigen Dezimalziffer). Die return-zeile verwendet die verkürzte Fallunterscheidung (Bedingung)? Wert_Falls_Ja : Wert_Falls_Nein; Beispiel: absx = (x > 0)? x : -x; speichert genau wie if (x > 0) absx = x; else absx = -x; den Betrag von x in absx.

185 Funktionszeiger: Beispiel Sortierverfahren Codeanalyse Hauptprogramm Das Feld z mit 5 unsigned-einträgen soll aufsteigend bzgl. der Quersumme sortiert werden. Dazu wird qsort mit den Parametern z (base), 5 (nmemb), sizeof(unsigned) (size) und unsign_qsumme_kleiner (compar) aufgerufen: qsort(z, 5, sizeof(unsigned), unsign_qsumme_kleiner); Ausgabe: Sortiertes Feld: Fazit: Wie qsort genau funktioniert, ist hier völlig unerheblich. Entscheidend ist, dass die Funktion ein Feld anhand einer gegebenen Vergleichsroutine sortiert.

186 Kommandozeilenargumente Bisher: $ gcc -o ProgName C_Code.c $./ProgName. Neu: Komandozeilenargumente Die main-funktion lässt sich auch mit zwei Parametern aufrufen. Vollständige Deklaration von main: int main(int argc, char *argv[]) argc argument counter: Anzahl der beim Programmaufruf übergebenen Argumente, einschließlich des Programmnamens. Erst wenn argc > 1 ist, werden tatsächlich Parameter übergeben. argv argument vector: Feld von Strings Bemerkung: Die Namen argc und argv (auch üblich: args) sind Konvention und nicht zwingend festgelegt. Möglich (aber wenig sinnvoll) wäre auch int main(bla, blubb)

187 Kommandozeilenargumente 1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i; 6 Beispiel: argumente.c 7 printf("anzahl der Argumente: %d\n\n", argc); 8 9 for (i=0; i<=argc; i++) 10 printf("argument %d: %s\n", i, argv[i]); return 0; 13 } Ausgabe: Der Aufruf./argumente arg1 arg2 help +~ liefert Anzahl der Argumente: 7 Argument 0:./argumente Argument 4: +~ Argument 1: arg1 Argument 5: Argument 2: arg2 Argument 6: -99 Argument 3: --help Argument 7: (null)

188 Kommandozeilenargumente Beachte: Parameter werden durch Leerzeichen getrennt. Ausdrücke wie -o Ausgabe werden als zwei separate Argumente aufgefasst. Jedes Argument wird als Zeichenkette in argv gespeichert. Die Reihenfolge der Strings in argv entspricht dabei der Reihenfolge auf der Kommandozeile. Werden Zahlen als Argumente übergeben, müssen diese mit Hilfe der entsprechenden Umwandlungsfunktionen (z. B. atoi oder atof) in ein Zahlenformat konvertiert werden. Mit String-Vergleichsfunktionen (z. B. strncmp) lässt sich beispielsweise prüfen, ob ein Programm mit einem bestimmten Optionsparameter aufgerufen wurde. Da jedoch die Reihenfolge der Optionsargumente festgelegt ist, benötigen Programme mit vielen Optionen einen flexibleren Ansatz zur Auswertung der Kommandozeile ( Parser).

189 Kommandozeilenargumente: Beispiel 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(int argc, char *argv[]){ 5 double x, y, z; 6 if (argc < 4){ // Es fehlen Argumente 7 printf("\nkorrekter Aufruf: "); 8 printf("%s zahl1 op zahl2\n", argv[0]); 9 return 1; 10 } 11 x = atof(argv[1]); 12 y = atof(argv[3]); 13 switch (argv[2][0]) { 14 case '+': 15 z = x + y; break; 16 case '-': 17 z = x - y; break; 18 case 'x': 19 case '*': 20 z = x * y; break; 21 case '/': 22 z = x / y; break; 23 default: 24 printf("\nfalscher Operator! ABBRUCH!\n"); 25 return -1; 26 } printf("\n%s %s %s = %lf", argv[1], argv[2], argv[3], z); 29 return 0; 30 }

190 Kommandozeilenargumente $ gcc -o berechne taschenrechner.c $./berechne = $./berechne 2 x 5 2 x 5 = $./berechne 2 / 5 2 / 5 = $./berechne 2 / Korrekter Aufruf:./berechne zahl1 op zahl2

191 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

192 Fortgeschrittene Ein- und Ausgabe Bisher: Ein- und Ausgabe nur über die Kommandozeile Erweiterung: Konzept des Datenstroms (engl. data stream) Bezeichnet allgemein eine Folge von Datensätzen gleichen Typs, deren Ende nicht im Voraus bekannt ist. Lässt sich nicht als Ganzes, sondern nur sequentiell verarbeiten. Kann vor- und zurückgespult werden. Ein spezieller End-of-file -Indikator dient zur Markierung des Endes. In C wird auf Datenströme mit Variablen vom Typ FILE * zugegriffen. FILE ist eine Struktur und enthält u.a. einen Zeiger auf die aktuell zu verarbeitende Position im Datenstrom. Standard-Datenströme #include <stdio.h> stdin (standard input): Einlesen von Daten über die Tastatur stdout (standard output): (gepufferte) Ausgabe von Daten auf der Kommandozeile stderr (standard error): Ausgabe auf der Kommandozeile zur Fehlerbehandlung Dateiströme: Zeiger vom Typ FILE * können mit Hilfe spezieller Funktionen mit Dateien verbunden werden.

193 Fortgeschrittene Ein- und Ausgabe: Funktionen Öffnen einer Datei FILE *fopen(const char *path, const char *mode); Erzeugt einen Datenstrom aus der Datei, die unter dem Pfad path liegt. Rückgabewert ist ein FILE *, der entweder an den Anfang oder das Ende des neuen Dateistroms zeigt. Bei Scheitern - z. B. Öffnen einer nicht vorhandenen Datei oder fehlende Dateirechte - liefert die Funktion NULL zurück. Der String path gibt den Pfad zur Datei im Dateisystem an, z. B.../Daten/werte.dat (relativer Pfad) oder /usr/include/stdio.h (absoluter Pfad). Der Modus mode gibt an, in welche Richtung der Strom fließen kann. Mögliche Modi sind r, w, a, r+, w+ und a+ : Modus Bedeutung Zeigerposition Dateiinhalt Datei existiert nicht r read only Dateianfang unverändert Rückgabewert NULL w write only Dateianfang wird gelöscht Neue Datei a append only Dateiende unverändert Rückgabewert NULL r+ read/write Dateianfang unverändert Rückgabewert NULL w+ read/write Dateianfang wird gelöscht Neue Datei a+ append/write Dateiende unverändert Neue Datei Bezieht sich nur auf den Zeitpunkt des Öffnens Beispiel: FILE *strom = fopen( Dokument.txt, r );

194 Fortgeschrittene Ein- und Ausgabe: Funktionen Schließen eines Datenstroms int *fclose(file *stream); Schließt den Datenstrom stream und gibt 0 zurück bei Erfolg, andernfalls die Konstante EOF (End Of File). Wichtig: Zu jedem fopen() gehört ein fclose()! Position herausfinden long ftell(file *stream); Gibt die aktuelle Position des Zeigers in stream als Adressabstand vom Anfang des Stromes (offset) in Byte an. Vor- und Zurückspulen int fseek(file *stream, long offset, int whence); Versetzt die Position des Zeigers in stream um offset Byte. Je nach Wert von whence wird der Versatz relativ zum Anfang (whence = SEEK_SET), zum Ende (whence = SEEK_END) oder zur aktuellen Position (whence = SEEK_CUR) gerechnet. Der Rückgabewert ist 0 bei Erfolg, andernfalls ungleich 0. void rewind(file *stream); Spult den Strom stream an den Anfang zurück. Der Aufruf rewind(stream); ist äquivalent zu fseek(stream, 0, SEEK_SET); (bis auf interne Feinheiten).

195 Fortgeschrittene Ein- und Ausgabe: Funktionen Aus einem Datenstrom lesen (Text) int fscanf(file *stream, const char *format,...); Genau wie scanf, wobei statt aus stdin aus dem Datenstrom stream gelesen wird. (Deshalb ist scanf(...) äquivalent zu fscanf(stdin,...).) Rückgabewert ist die Anzahl der gelesenen Zeichen. int fgetc(file *stream); Liest ein Zeichen aus stream, das als int gecastet zurückgegeben wird. Im Fehlerfall oder bei Dateiende ist der Rückgabewert EOF. char *fgets(char *s, int n, FILE *stream); Liest aus stream und schreibt das Ergebnis in den Puffer, auf den s zeigt. Das Lesen wird abgebrochen, wenn entweder das Zeilenende \n oder das Dateiende erreicht ist oder n 1 Zeichen gelesen wurden. Im Fehlerfall oder bei Dateiende ist der Rückgabewert NULL. Beachte: Nach dem Aufruf einer dieser Funktionen zeigt stream auf die erste Position im Anschluss an den bearbeiteten Bereich.

196 Fortgeschrittene Ein- und Ausgabe: Funktionen In einen Datenstrom schreiben (Text) int fprintf(file *stream, const char *format,...); Arbeitet wie printf, wobei in den Datenstrom stream geschrieben wird. (Deshalb ist printf(...) äquivalent zu fprintf(stdout,...).) Rückgabewert ist die Anzahl der geschriebenen Zeichen. int fputc(int c, FILE *stream); Schreibt das als unsigned char gecastete Zeichen c in den Strom stream. Das Zeichen wird als int zurückgegeben, im Fehlerfall EOF. int *fputs(const char *s, FILE *stream); Schreibt den String, auf den s zeigt, ohne das Nullzeichen \0 in den Strom stream. Im Fehlerfall wird EOF zurückgegeben. Beachte: Nach dem Aufruf einer dieser Funktionen zeigt stream auf die erste Position im Anschluss an den bearbeiteten Bereich.

197 Fortgeschrittene Ein- und Ausgabe: Beispiel 1 #include <stdio.h> 2 3 int main(void) 4 { 5 FILE *datenstrom; 6 7 datenstrom = fopen("testdatei.txt", "r"); 8 if (datenstrom == NULL) 9 { 10 fprintf(stderr, "Fehler: Testdatei.txt konnte nicht geoeffnet werden!\n"); 11 return -1; 12 } fseek(datenstrom, 0, SEEK_END); // Ans Ende spulen 15 fprintf(stdout, "Offset am Ende: %lu\n", ftell(datenstrom)); fseek(datenstrom, -5, SEEK_CUR); // 5 Bytes zurueck 18 fprintf(stdout, " nach Zurueckspulen: %lu\n", ftell(datenstrom)); rewind(datenstrom); // Zurueckspulen 21 fprintf(stdout, " am Anfang: %lu\n", ftell(datenstrom)); fclose(datenstrom); return 0; 26 }

198 Fortgeschrittene Ein- und Ausgabe: Beispiel Datei Testdatei.txt: Hallo Welt! Die Datei enthält genau eine Zeile Hallo Welt! (11 Characters = 11 Byte). Ausgabe des Programms: Offset am Ende: 11 nach Zurueckspulen: 6 am Anfang: 0

199 Beispiel: Matrix aus Datei einlesen (Funktion) 1 double **neuematrixausdatei(file *strom, int *n_zeilen, int *n_spalten) 2 { 3 int i, j; 4 double **matrix; 5 6 while (fgetc(strom) == '#') // Kommentarzeichen->Zeile ueberspringen 7 while (fgetc(strom)!= '\n'); 8 fseek(strom, -1, SEEK_CUR); // Eine Position zurueck 9 10 fscanf(strom, " n = %d, m = %d\n", n_zeilen, n_spalten); 11 matrix = neuematrix(*n_zeilen, *n_spalten); for (i=0; i<*n_zeilen; i++) 14 { 15 for (j=0; j<*n_spalten; j++) 16 fscanf(strom, "%lf", &matrix[i][j]); 17 } 18 return matrix; 19 } Jeder Aufruf von fgetc erhöht die Position von strom um 1. Deshalb zeigt strom nach der (leeren) Schleife in Zeile 7 auf den Anfang der nächsten Zeile. Insgesamt überspringen die Zeilen 6 und 7 Dateizeilen, die mit # beginnen. Zeile 10 erwartet einen Text wie n = 6, m = 1. Die gelesenen Zahlen werden in n_zeilen und n_spalten (beides Zeiger auf int-variablen) gespeichert. Für fscanf gilt: Leerzeichen und Zeilenumbrüche innerhalb der Platzhalter werden automatisch übersprungen. Ein Leerzeichen im Formatstring steht für eine beliebige Anzahl (auch 0) von tatsächlich zu lesenden Leerzeichen.

200 Beispiel: Matrix aus Datei einlesen (Hauptprogramm) 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void zeigematrix(double **matrix, int n_zeilen, int n_spalten); 5 void loeschematrix(double **matrix, int n_zeilen); 6 double **neuematrixausdatei(file *strom, int *n_zeilen, int *n_spalten); 7 8 int main(void) 9 { int n, m; 10 FILE *fp; 11 if ( (fp = fopen("matrix.dat", "r")) == NULL ) 12 return 1; 13 double **H = neuematrixausdatei(fp, &n, &m); zeigematrix(h, n, m); 16 loeschematrix(h, n); 17 fclose(fp); 18 return 0; 19 } // Funktionsdefinitionen... Ausgabe:

201 Beispiel: Matrix aus Datei einlesen (Matrix.dat) Version 1 n=4, m= Version 2 # Das ist die Hilbertmatrix H_ij = 1/(i+j-1) # # Hoffentlich werden diese Zeilen uebersprungen... n = 4, m = Beide Versionen werden korrekt eingelesen!

202 Beispiel: Matrix in Datei schreiben (Funktion) 1 void schreibematrixindatei(file *strom, double **matrix, 2 int n_zeilen, int n_spalten) 3 { 4 int i, j; 5 fprintf(strom, "# Automatisch generiert mit schreibematrixindatei\n\n"); 6 fprintf(strom, "n = %d, m = %d\n\n", n_zeilen, n_spalten); 7 8 for (i=0; i<n_zeilen; i++) 9 { 10 for (j=0; j<n_spalten; j++) 11 fprintf(strom, "%.6lf ", matrix[i][j]); fprintf(strom, "\n"); 14 } return; 17 }

203 Beispiel: Matrix in Datei schreiben (Hauptprogramm) 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void loeschematrix(double **matrix, int n_zeilen); 5 double **neuematrixausdatei(file *strom, int *n_zeilen, int *n_spalten); 6 void schreibematrixindatei(file *strom, double **matrix, 7 int n_zeilen, int n_spalten); 8 9 int main(void) 10 { 11 int n, m; 12 FILE *fp; 13 double **H; fp = fopen("matrix.dat", "r"); 16 H = neuematrixausdatei(fp, &n, &m); 17 fclose(fp); fp = fopen("matrix_generiert.dat", "w"); 20 schreibematrixindatei(fp, H, n, m); 21 fclose(fp); loeschematrix(h, n); return 0; 26 }

204 Beispiel: Matrix in Datei schreiben Inhalt von Matrix_generiert.dat: # Automatisch generiert mit schreibematrixindatei n = 4, m =

205 Speicherung als Text oder binär: Vergleich Dateigröße Binär: Produkt aus Anzahl der Elemente und Größe eines Elements in Byte. Text: Variiert mit der textuellen Länge der gespeicherten Elemente. Meistens größer als im Binärformat. Präzision Binär: Entspricht der Genauigkeit des Datentyps. Text: Der Genauigkeit sind prinzipiell keine Grenzen gesetzt, jedoch kann der Speicherbedarf dadurch sehr groß werden. Beispiel: Um die größtmögliche float-zahl ( ) als Text zu speichern, benötigt man 38 Zeichen ˆ= 38 Byte, im Binärformat nur sizeof(float) = 4 Byte! Metadaten Binär: Üblicherweise ist die Datei unterteilt in einen Header und die Daten. Es muss genau beschrieben werden, welche Bytes wofür stehen. Beispiel Matrix: Bytes 1-4 unsigned Länge des Headers in Byte, 5-8 unsigned Anzahl der Zeilen, 9-12 unsigned Anzahl der Spalten, 13-Ende double Matrixelemente. Text: Informationen über die Daten können in die Datei geschrieben werden.

206 Vor- und Nachteile der binären Speicherung Vorteile: Allein der Datentyp bestimmt die Größe des benötigten Speicherplatzes. Es wird meistens deutlich weniger Speicherplatz als für die Textvariante benötigt. Maschinennahe Ein- und Ausgabe, daher deutlich schneller. Nachteile: Binäre Daten sind nicht direkt vom Menschen lesbar. Binärformate sind vom System abhängig, d.h. Daten müssen unter Umständen zuerst umgewandelt werden.

207 Fortgeschrittene Ein- und Ausgabe: Funktionen Aus einem Datenstrom lesen (binär) size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); Liest nmemb Elemente der Größe size aus dem Datenstrom stream und speichert sie (in der gleichen Reihenfolge) im Feld, auf das ptr zeigt. Rückgabewert ist die Anzahl der erfolgreich gelesenen Elemente. Beachte: Die Daten werden nicht als Text, sondern als Bitfolgen interpretiert. In einen Datenstrom schreiben (binär) size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); Schreibt nmemb Elemente der Größe size aus dem Feld, auf das ptr zeigt, in den Datenstrom stream. Rückgabewert ist die Anzahl der erfolgreich geschriebenen Elemente.

208 Beispiel: binäres Lesen und Schreiben (Funktionen) 1 void schreibematrixinbindatei(char *pfad, double **matrix, 2 int n_zeilen, int n_spalten) 3 { int i; 4 FILE *fp; 5 6 if ( (fp = fopen(pfad, "wb")) == NULL ) return; 7 8 for (i=0; i<n_zeilen; i++) 9 fwrite(matrix[i], sizeof(double), n_spalten, fp); fclose(fp); 12 return; 13 } double **neuematrixausbindatei(char *pfad, int n_zeilen, int n_spalten) 16 { int i; 17 double **matrix; 18 FILE *fp; if ( (fp = fopen(pfad, "rb")) == NULL ) return NULL; matrix = neuematrix(n_zeilen, n_spalten); 23 for (i=0; i<n_zeilen; i++) 24 fread(matrix[i], sizeof(double), n_spalten, fp); fclose(fp); 27 return matrix; 28 }

209 Beispiel: binäres Lesen und Schreiben (Hauptprogramm) 1 double **neuematrixausbindatei(char *pfad, int n_zeilen, int n_spalten); 2 void schreibematrixinbindatei(char *pfad, double **matrix, 3 int n_zeilen, int n_spalten); 4 5 int main(void) 6 { 7 int n = 4, m = 7; 8 double **H; 9 10 H = neuematrixausbindatei("matrix.bin", n, m); 11 zeigematrix(h, n, m); 12 schreibematrixinbindatei("matrix_kopie.bin", H, n, m); 13 loeschematrix(h, n); return 0; 16 } Beachte: Die Funktionen werden (zur Abwechslung) nicht mit dem bereits geöffneten Strom, sondern mit dem Pfad zum Dateinamen aufgerufen. In den if-statements wird zunächst der Datenstrom geöffnet und anschließend auf NULL überprüft. Da jede Matrixzeile als separates Feld gespeichert ist (zweistufige Speicherung mit double **), muss auch zeilenweise in die Datei geschrieben werden.

210 C-Präprozessor Wir haben bereits die Direktive #include <Headerdatei> verwendet, um fremde Funktionsdeklarationen zu importieren Generell ist der Präprozessor dafür zuständig, Text durch anderen Text zu ersetzen. Der Präprozessor wird vor dem Compiler aufgerufen, deshalb muss der verarbeitete Code syntaktisch korrekt sein. Präprozessordirektiven beginnen stets mit einer Raute # und stehen im Code bis auf wenige Ausnahmefälle ganz zu Beginn. Die wichtigsten Direktiven sind Einfügungen, Makros und bedingte Ersetzungen.

211 C-Präprozessor: Einfügungen Standard-Header Syntax: #include <systemheader.h> Fügt den Text der Headerdatei systemheader.h an der Stelle im Code ein, an welcher der #include-befehl steht. Die Datei wird im Standard-Header-Pfad des Systems gesucht. Bei Unix-Systemen ist dies üblicherweise /usr/include. Beispiel: #include <stdio.h> bindet die Datei /usr/include/stdio.h ein. Bei Headern in Unterordnern muss dieser mit angegeben werden, z. B. #include <sys/time.h> für den Header /usr/include/sys/time.h. Lokale (eigene) Header Syntax: #include lokaler_header.h Bindet die Datei lokaler_header.h ein, die im aktuellen Verzeichnis liegt. Will man einen Header aus einem anderen Pfad inkludieren, muss dem Compiler der Pfad mitgeteilt werden. Beispiel: Zum Einbinden des Headers matrix.h im Unterordner meine_header wird im Quellcode die Zeile #include matrix.h eingefügt. der Compiler mit der Option -Imeine_header aufgerufen. Die Option -IOrdner macht die Header in Ordner für den Compiler sichtbar.

212 C-Präprozessor: Einfügungen Beispiel matrix.h Headerdatei matrix.h im Unterordner meine_header (Ausschnitt): 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 // Legt Speicher fuer eine neue Matrix an und liefert einen Zeiger auf die 5 // erste Zeile; bei Misserfolg NULL 6 double **neuematrix(int n_zeilen, int n_spalten); 7 8 // Gibt die Matrix am Bildschirm aus 9 void zeigematrix(double **matrix, int n_zeilen, int n_spalten); Programmcode matrix_test.c: 1 #include "matrix.h" 2 3 int main(void) 4 { 5 int n = 4, m = 7; 6 double **H = neuematrixausbindatei("matrix.bin", n, m); 7 zeigematrix(h, n, m); 8 loeschematrix(h, n); 9 10 return 0; 11 } // Hiernach die Funktionsdefinitionen!! Compiler-Aufruf: gcc -o matrix_test matrix_test.c -Imeine_header

213 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

214 C-Präprozessor: Einfügungen Standard-Header Syntax: #include <systemheader.h> Fügt den Text der Headerdatei systemheader.h an der Stelle im Code ein, an welcher der #include-befehl steht. Die Datei wird im Standard-Header-Pfad des Systems gesucht. Bei Unix-Systemen ist dies üblicherweise /usr/include. Beispiel: #include <math.h> bindet die Datei /usr/include/math.h ein. Bei Headern in Unterordnern muss dieser mit angegeben werden, z. B. #include <sys/time.h> für den Header /usr/include/sys/time.h. Lokale (eigene) Header Syntax: #include lokaler_header.h Bindet die Datei lokaler_header.h ein, die im aktuellen Verzeichnis liegt. Will man einen Header aus einem anderen Pfad inkludieren, muss dem Compiler der Pfad mitgeteilt werden. Beispiel: Zum Einbinden des Headers matrix.h im Unterordner meine_header wird im Quellcode die Zeile #include matrix.h eingefügt. der Compiler mit der Option -Imeine_header aufgerufen. Die Option -IOrdner macht die Header in Ordner für den Compiler sichtbar.

215 C-Präprozessor: Einfügungen Beispiel matrix.h Headerdatei matrix.h im Unterordner meine_header (Ausschnitt): 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 // Legt Speicher fuer eine neue Matrix an und liefert einen Zeiger 5 // auf die erste Zeile; bei Misserfolg NULL 6 double **neuematrix(int n_zeilen, int n_spalten); 7 8 // Gibt die Matrix am Bildschirm aus 9 void zeigematrix(double **matrix, int n_zeilen, int n_spalten); Programmcode matrix_test.c: 1 #include "matrix.h" 2 3 int main(void) 4 { 5 int n = 4, m = 7; 6 double **H = neuematrixausbindatei("matrix.bin", n, m); 7 zeigematrix(h, n, m); 8 loeschematrix(h, n); 9 10 return 0; 11 } // Hiernach die Funktionsdefinitionen!! Compiler-Aufruf: gcc -o matrix_test matrix_test.c -Imeine_header

216 C-Präprozessor: Makros ohne Parameter I Syntax: #define MAKRO_OHNE_PARAMETER Definiert ein Makro mit dem Namen MAKRO_OHNE_PARAMETER Namenskonvention: Makros werden immer durchgehend mit Großbuchtstaben benannt. Mit #ifdef MAKRO_OHNE_PARAMETER bzw. #ifndef MAKRO_OHNE_PARAMETER kann abgefragt werden, ob das Makro definiert bzw. nicht definiert ist. Syntax: #ifdef MAKRO // Weitere Direktiven, z. B. #include #endif Verwendungsbeispiel: Verhindern, dass ein Header mehrfach eingebunden wird. #ifndef MEIN_HEADER_H #define MEIN_HEADER_H // Weitere Direktiven, Funktionsdeklarationen etc. #endif

217 C-Präprozessor: Makros ohne Parameter II Syntax: #define MAKRO Ersetzungstext Bewirkt, dass überall im Quellcode, wo MAKRO steht, der Ersetzungstext eingesetzt wird. Beispiele: #define ANTWORT 42 Alle Vorkommen von ANTWORT im Quellcode wird textuell durch 42 ersetzt. Auf diese Weise brauchen für Konstanten keine Variablen deklariert zu werden. #define ADRESSE long unsigned int In der Folge kann ADRESSE wie ein Datentyp verwendet werden. So lassen sich intuitive Namen für Datentypen vergeben. Generelle Verwendung: Vergabe von sprechenden Namen für Konstanten, Datentypen,.... Vermeidung von magische Zahlen (PUFFERGROESSE hat mehr Aussagekraft als 1024). Durch Änderung der Definition lassen sich alle Vorkommen der Konstanten schnell und unkompliziert anpassen. Folge: Besser lesbarer und leichter zu pflegender Code

218 C-Präprozessor: Makros mit Parametern Syntax: #define MAKRO(Parameterliste) Ersetzungstext Im Code lässt sich MAKRO wie eine Funktion aufrufen. Achtung: Es wird nicht überprüft, ob die Argumente passende Typen haben. Generell umfassen Makros nur eine einzige Zeile. Lange Zeilen lassen sich mit einem Backslash \ am Ende umbrechen. Beispiel 1: #define MAX(X,Y) X > Y? X : Y Berechnet das Maximum von zwei Argumenten Beispiel 2: #define TAUSCHE(X,Y) int z = X; X = Y; Y = z; Vertauscht die Werte von zwei int-argumenten.

219 C-Präprozessor: Makros mit variabler Anzahl von Parametern Syntax: #define MAKRO(Parameterliste,...) Ersetzungstext Werden in Verbindung mit Funktionen verwendet, die mit einer variablen Anzahl von Argumenten aufgerufen werden können, z. B. fprintf. Das oben definierte Makro muss mit mindestens so vielen Parametern aufgerufen werden wie die Parameterliste lang ist. Auf die über diese Anzahl hinausgehenden Parameter, die anstelle der Punkte... übergeben werden, kann mit dem Makro VA_ARGS zugegriffen werden. Beispiel: #define EPRINTF(_fmt_str,...) \ fprintf(stderr, _fmt_str, ## VA_ARGS ) Der Aufruf EPRINTF( i = %d\n, i); wird beispielsweise ersetzt durch fprintf(stderr, i = %d\n, i); Die doppelte Raute ## vor VA_ARGS bewirkt hier speziell, dass das Komma nach dem Formatstring wegfällt, falls VA_ARGS leer ist. Andernfalls ergäbe sich ein Syntaxfehler. Beispiel: EPRINTF( Hallo!\n ); wird hier ersetzt zum syntaktisch korrekten fprintf(stderr, Hallo!\n ); Ohne die Doppelraute würde die Ersetzung fprintf(stderr, Hallo!\n, ); lauten, was einen Syntaxfehler darstellt.

220 C-Präprozessor: Fallstricke Argumente sollten im Ersetzungstext immer geklammert werden, da sonst zusammengesetzte Ausdrücke als Argument eventuell falsch ausgewertet werden. Beispiel: #define ZWEIMAL(X) 2 * X Der Aufruf ZWEIMAL(4 + 2) wird aufgelöst zu 2 * 4 + 2, im Ergebnis 10. Das Makro #define ZWEIMAL(X) 2 * (X) liefert das korrekte Ergebnis: 2*(4 + 2)=12. Formeln sollten im Ersetzungstext immer geklammert werden, da sonst zusammengesetzte Ausdrücke als Argument eventuell falsch ausgewertet werden. Beispiel: #define MAX(X,Y) (X) > (Y)? (X) : (Y) Der Aufruf 2*MAX(3,5) wird aufgelöst zu 2 * (3) > (5)? (3) : (5), im Ergebnis 3. Das Makro #define MAX(X,Y) ((X) > (Y)? (X) : (Y)) liefert das korrekte Ergebnis: 2*((3) > (4)? (3) : (5))=10.

221 C-Präprozessor: Fallstricke Anweisungsfolgen sind in geschweifte Klammern zu setzen. Beispiel 1: #define ABBRUCH_VOID printf( Abbruch!\n ); return; Der Code if(fehler) ABBRUCH_VOID; resultiert im aufgelösten Code if(fehler) printf( Abbruch!\n ); return; Dadurch wird immer das return-statement ausgeführt! Mit #define ABBRUCH_VOID {printf( Abbruch!\n ); return;} tritt dieses Problem nicht auf. Beispiel 2: #define TAUSCHE(X,Y) int z = X; X = Y; Y = z; Falls eine Variable z bereits deklariert wurde, beschwert sich der Compiler! Die Definition #define TAUSCHE(X,Y) {int z = X; X = Y; Y = z;} behebt diesen Konflikt, da die Deklaration im neuen Anweisungsblock übergeordnete Variablen bis zum Ende des Blocks überdecken kann.

222 C-Präprozessor: Fallstricke Ausdrücke mit Nebeneffekten oder Aufrufe von rechenzeitintensiven Funktionen sollten in Makros vermieden werden, da sie eventuell mehrfach ausgewertet werden. Beispiel 1: char c = MAX(fgetc(stdin), a ); wird aufgelöst zu char c = ((fgetc(stdin)) > ( a ))? (fgetc(stdin)) : a ; Falls das erste eingelesene Zeichen (als Zahl) größer ist als a, so wird ein zweites Zeichen eingelesen. Beispiel 2: int i = 5, j = MAX(i++, 2); wird aufgelöst zu int i = 5, j = ((i++) > (2))? (i++) : (2); Danach gilt nicht wie erwartet i = und j =, sondern i = und j =. Beispiel 3: double x = 1.0; double y = MAX(viel_zu_rechnen(x), 0.0); Hier wird unter Umständen die Funktion viel_zu_rechnen zweimal ausgewertet, was zu einer doppelt so langen Laufzeit führt!

223 C-Präprozessor: Vor- und Nachteile von Makros gegenüber Funktionen Vorteile: Makros werden zur Compilezeit ausgewertet und sind in vielen Fällen schneller. Viele Makros können universell eingesetzt werden (z. B. MAX für alle vergleichbaren Datentypen). Nachteile: Zu viele oder zu lange Makros lassen den Codeumfang anwachsen und ergeben unter Umständen große und langsame Programme. Zudem ist der Speicherbedarf größer als bei Funktionen. Mehrfache Auswertung von Ausdrücken kann unerwünschte Konsequenzen nach sich ziehen. Es gibt keine Möglichkeit zu prüfen, ob sinnvolle Datentypen als Parameter verwendet werden. Die Fehlerquellen im Umgang mit Makros sind vielfältig. Der Versuch, Funktionspointer als Zeiger auf ein Makro zu verwenden, scheitert an einem Syntaxfehler. Dies ist vor allem dann problematisch, wenn nicht klar ist, ob ein Name für eine Funktion oder ein Makro steht. Fazit: Makros mit Parametern als Ersatz für Funktionen eignen sich für einfache Aufgaben in geschwindigkeitskritischen Bereichen.

224 C-Präprozessor: bedingte Ersetzung Konditionale Direktiven: #if Bedingung1 // Direktiven / Code #elif Bedingung // Weitere Direktiven / Code #else // Alternative Direktiven / Code #endif Prinzipiell funktioniert dieses Konstrukt wie C-Statements mit if - else if - else (#elif ist eine Kurzform für else if ). Als Bedingungen können konstante Zahlen, andere Makros sowie arithmetische und logische Ausdrücke verwendet werden. #ifdef MAKRO // bzw. #ifndef // Weitere Direktiven #endif Direktiven werden ausgeführt, falls MAKRO (nicht) definiert wurde. Wichtig: #if und #ifdef müssen immer durch ein #endif abgeschlossen werden. Definition rückgängig machen: #undef MAKRO

225 C-Präprozessor: bedingte Ersetzung Beispiel Header debug.h: 1 #ifndef DEBUG_H 2 #define DEBUG_H 3 4 #include <stdio.h> 5 6 #ifdef DEBUG_AN 7 #define DEBUG_AUSGABE(_fmt_string,...) \ 8 fprintf(stderr, "[Datei %s, Zeile %d] " _fmt_string, \ 9 FILE, LINE, ## VA_ARGS ) #else 12 #define DEBUG_AUSGABE(_fmt_string,...) // definiert als "nichts" #endif // DEBUG_AN 15 #endif // DEBUG_H Programmcode debug_test.c: 1 #include "debug.h" 2 3 int main(void) 4 { 5 int i = 42; 6 DEBUG_AUSGABE("Hallo Welt!\n"); 7 DEBUG_AUSGABE("i = %d\n", i); 8 9 return 0; 10 }

226 C-Präprozessor: bedingte Ersetzung Beispiel Codeanalyse Header: #ifdef DEBUG_AN #define DEBUG_AUSGABE(_fmt_string,...) \ fprintf(stderr, "[Datei %s, Zeile %d]: " _fmt_string, \ FILE, LINE, ## VA_ARGS ) #else #define DEBUG_AUSGABE(_fmt_string,...) // definiert als "nichts" #endif // DEBUG_AN Falls DEBUG_AN definiert wurde, nimmt der Präprozessor die obere Version des Makros DEBUG_AUSGABE, andernfalls die untere leere Version. Die hintereinander geschriebenen Strings [Datei %s, Zeile %d]: und _fmt_string werden automatisch zu einem zusammengefügt (konkateniert). Dies gilt generell für konstante Strings in C. FILE und LINE sind vordefinierte Makros, die vom Präprozessor durch den Dateinamen (als String) und die Codezeile (als Ganzzahlkonstante) ersetzt werden. Aus diesem Grund sind dafür die Platzhalter %s bzw. %d vorgesehen. Wurde DEBUG_AN nicht definiert, so ersetzt der Präprozessor alle Aufrufe von DEBUG_AUSGABE durch nichts. In diesem Fall wäre beispielsweise DEBUG_AUSGABE( Hallo!\n ); ein leeres Statement (;).

227 C-Präprozessor: bedingte Ersetzung Beispiel Codeanalyse Hauptprogramm: int i = 42; DEBUG_AUSGABE("Hallo Welt!\n"); DEBUG_AUSGABE("i = %d\n", i); Compileraufruf 1: gcc -o debug_test debug_test.c Das Makro DEBUG_AN ist nicht definiert, daher erfolgt keine Ausgabe. Compileraufruf 2: gcc -o debug_test debug_test.c -DDEBUG_AN Durch die Option -DDEBUG_AN wird das Makro DEBUG_AN definiert, und der Präprozessor verwendet die gehaltvolle Version von DEBUG_AUSGABE. Das zweite DEBUG_AUSGABE-Statement in obigem Code beispielsweise wird wie erwartet durch fprintf(stderr, [Datei %s, Zeile %d] i = %d\n, i); ersetzt. Generell wirkt die Compileroption -DMAKRO[=Wert] wie eine Zeile #define MAKRO [Wert] im Code. Achtung: Bei Stringkonstanten müssen Zeichen, die normalerweise von der Kommandozeile interpretiert werden, mit einem Backslash escaped werden. Dazu gehören u. a. Anführungszeichen, Hochkommata, Backslash, Dollar, Raute, Klammern,... Beispiel: Um ein Makro NACHRICHT mit dem Wert Im Westen nichts Neues. zu definieren, muss die Compileroption -DNACHRICHT=\ Im Westen nichts Neues.\ lauten.

228 C-Präprozessor: mehrstufige Ersetzungen und Zugriff auf Variablennamen Es ist möglich, Makros zu definieren, die von anderen Makros abhängen. Beispiel: Kurzform eines langen Konstantennamens #define ERDBESCHLEUNIGUNG 9.81 #define G ERDBESCHLEUNIGUNG Generell durchläuft der Präprozessor den Code so lange, bis alle Ersetzungen erfolgt sind. Beim ersten Durchlauf werden alle Vorkommen von G durch ERDBESCHLEUNIGUNG ersetzt. Im zweiten Durchgang erfolgt die endgültige Auflösung zur Konstanten Es ist möglich, mit Makros auf den Namen von Variablen zuzugreifen. Beispiel: #define PRINTDOUBLEWITHNAME(X) printf("%s = %.3lf ",#X,X) Im Programm führt der Aufruf double x=1.121,y=2.3; PRINTDOUBLEWITHNAME(x); PRINTDOUBLEWITHNAME(y); dann zu folgender Ausgabe: x = y =

229 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald

230 Programmierprojekte mit mehreren Dateien Die Präprozessordirektive #include eigener_header.h erlaubt es, Funktionsdeklarationen, typedefs, Definitionen von Makros usw. in einen externen Header auszulagern. Nach wie vor müssen jedoch alle Funktionsdefinitionen im Hauptprogrammcode enthalten sein. Diese Tatsache führt zu langen und unübersichtlichen Codedateien und verhindert eine einfache Wiederverwendung der programmierten Funktionen (einzige Möglichkeit: copy&paste in andere Projekte). Wünschenswert wäre eine Möglichkeit, Funktionsdefinitionen (gruppiert nach Aufgabengebiet) in externe Quellcode-Dateien auszulagern. Mit Hilfe von Objektdateien, einem Zwischenprodukt beim Compilieren von Code, lässt sich diese Möglichkeit realisieren.

231 Programmierprojekte mit mehreren Dateien: Prinzip main.c modul_1.c... modul_n.c gcc -c -o <ausgabe>.o <code>.c main.o modul_1.o... modul_n.o Bibliotheken gcc -o programm main.o modul_1.o [...] modul_n.o -lbibliotheksname programm

232 Programmierprojekte mit mehreren Dateien: Prinzip Der Quellcode ist verteilt auf mehrere Module - getrennt nach Funktionalität - und das Hauptprogramm. Jede Codedatei wird separat zu Objektcode compiliert mit dem Aufruf gcc -c -o Datei.o Datei.c Mit gcc -o programm Liste_von_Objektdateien werden die Objektdateien zu einem ausführbaren Programm zusammengebunden (gelinkt). Erinnerung: Der Compiler braucht alle nötigen Deklarationen und gibt als Resultat Objektcode aus. Erst der Linker muss die Definitionen zur Verfügung haben, sonst liefert er einen undefined reference-fehler. Das Ergebnis des Link-Vorgangs ist schließlich das ausführbare Programm.

233 Programmierprojekte mit mehreren Dateien: Beispiel matrix.h 1 #ifndef MATRIX_H 2 #define MATRIX_H 3 4 // Legt Speicher fuer eine neue Matrix an und liefert einen Zeiger auf die 5 // erste Zeile; bei Misserfolg NULL 6 double **neuematrix(int n_zeilen, int n_spalten); 7 8 // Gibt die Matrix am Bildschirm aus 9 void zeigematrix(double **matrix, int n_zeilen, int n_spalten); // Gibt den Speicherplatz der Matrix frei 12 void loeschematrix(double **matrix, int n_zeilen); // Reserviert Speicher fuer eine Matrix und liest die Elemente aus der 15 // TEXTdatei, die unter dem Pfad zu finden ist; bei Misserfolg NULL 16 double **neuematrixausdatei(char *pfad, int *n_zeilen, int *n_spalten); // Schreibt die Matrix im Textmodus in die Datei im angegebenen Pfad 19 void schreibematrixindatei(char *pfad, double **matrix, 20 int n_zeilen, int n_spalten); // Reserviert Speicher fuer eine Matrix und liest die Elemente aus der 23 // BINAERdatei, die unter dem Pfad zu finden ist; bei Misserfolg NULL 24 double **neuematrixausbindatei(char *pfad, int n_zeilen, int n_spalten); // Schreibt die Matrix im Binaermodus in die Datei im angegebenen Pfad 27 void schreibematrixinbindatei(char *pfad, double **matrix, 28 int n_zeilen, int n_spalten); 29 #endif

234 Programmierprojekte mit mehreren Dateien: Beispiel matrix_funktionen.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 double **neuematrix(int n_zeilen, int n_spalten) 5 { 6 int i, j; 7 double **matrix; 8 9 matrix = (double **)malloc(n_zeilen * sizeof(double *)); 10 if (matrix == NULL) 11 return NULL; for (i = 0; i < n_zeilen; i++) 14 { 15 matrix[i] = (double *)malloc(n_spalten * sizeof(double)); 16 if (matrix[i] == NULL) 17 { 18 for (j = 0; j < i; j++) 19 free(matrix[j]); 20 free(matrix); 21 return NULL; 22 } 23 } 24 return matrix; 25 } // Und alle weiteren Funktionsdefinitionen

235 Programmierprojekte mit mehreren Dateien: Beispiel matrix_test_main.c 1 #include "matrix.h" 2 3 int main(void) 4 { 5 int n, m; 6 double **G, **H; 7 8 G = neuematrixausdatei("matrix_kommentiert.dat", &n, &m); 9 H = neuematrixausbindatei("matrix.bin", n, m); zeigematrix(g, n, m); 12 zeigematrix(h, n, m); loeschematrix(g, n); 15 loeschematrix(h, n); return 0; 18 } Kommandos: gcc -c -o matrix_test_main.o matrix_test_main.c (Compiler) gcc -c -o matrix_funktionen.o matrix_funktionen.c (Compiler) gcc -o matrix_test matrix_test_main.o matrix_funktionen.o (Linker)

236 Programmierprojekte mit mehreren Dateien Anmerkungen: Das Modul matrix_funktionen.c benötigt die Systemheader stdio.h und stdlib.h, damit die dort deklarierten Funktionen (z. B. printf oder malloc) verwendet werden können. Sind sie nicht eingebunden, liefert der Compiler (gcc -c) einen Fehler. Der Header matrix.h braucht hingegen nicht inkludiert zu werden. Im Hauptprogramm genügt es, matrix.h einzufügen. Gäbe es Statements, die beispielsweise printf enthalten, so müsste zusätzlich stdio.h inkludiert werden. Merkregel: Jede Codedatei benötigt genau diejenigen Header, die nötig sind, damit alle Variablen, Konstanten, Funktionen,... innerhalb dieser Datei korrekt deklariert sind. Wird der Code eines Moduls geändert, so müssen alle Objekte und Programme neu compiliert und gelinkt werden, die von diesem Modul abhängen. In diesem Beispiel müssen nach einer Änderung in matrix_funktionen.c alle Schritte von neuem durchgeführt werden! Bei größeren Projekten mit vielen Dateien wird es zunehmend schwierig nachzuvollziehen, welche Aktionen nach welcher Änderung vollzogen werden müssen. Hierfür gibt es die elegante Lösung der Makefiles.

237 Mehrdateiprojekte mit make make ist das mit Abstand wichtigste Entwicklungs-Tool bei Softwareprojekten. Wir betrachten hier die am weitesten verbreitete Implementierung GNU Make ( Aufgabe: Neuübersetzung genau derjenigen Programmteile, die von einer Änderung am Code betroffen sind. Hintergrund: Die Brechstangen -Methode, bei jeder Änderung alles neu zu übersetzen, ist aus zwei Gründen nicht praktikabel: 1 Umfangreiche Projekte benötigen Minuten bis Stunden zur Komplettübersetzung 2 Für jede einzelne Datei gcc aufzurufen (mit individuellen Optionen) ist extrem mühsam. Funktionsweise: In einer Steuerungsdatei mit dem Namen Makefile gibt man alle Abhängigkeiten zwischen den Dateien in Form von Erstellungsregeln (engl. make rules) an.

238 Mehrdateiprojekte mit make: Erstellungsregeln Aufbau: Ziel: Abhaengigkeiten \ weitere Abhaengigkeiten Befehl1... BefehlN Ziel bezeichnet das, was getan werden soll, also entweder einen Dateinamen (z. B. matrix_funktionen.o) oder eine abstrakte Aktion (z. B. clean). Als Abhängigkeiten werden sämtliche Dateien angegeben, von deren Änderung das Ziel abhängt bzw. abhängen soll. Lange Zeilen können mit einem Backslash am Ende umgebrochen werden. Die Befehle definieren, was make unternehmen soll, um das Ziel zu erstellen. Wichtig: Befehlszeilen müssen immer mit einem Tabulator (TAB-Taste) beginnen, sonst meldet make einen Fehler.

239 Mehrdateiprojekte mit make: Beispiel Matrix-Projekt Bisheriger Inhalt: matrix.h, matrix_funktionen.c und matrix_test_main.c Jede Quellcodedatei.c wird zu einer Objektdatei.o kompiliert. Diese Objekte werden schließlich zu einem ausführbaren Programm zusammengebunden. Abhängigkeiten: matrix_funktionen.o: Entsteht aus matrix_funktionen.c matrix_test_main.o: Entsteht aus matrix_test_main.c und inkludiert matrix.h matrix_test: Entsteht aus matrix_test_main.o und matrix_funktionen.o Daraus ergeben sich folgende Erstellungsregeln: 1 matrix_test: matrix_test_main.o matrix_funktionen.o 2 gcc -o matrix_test matrix_test_main.o matrix_funktionen.o 3 4 matrix_test_main.o: matrix_test_main.c matrix.h 5 gcc -c -o matrix_test_main.o matrix_test_main.c 6 7 matrix_funktionen.o: matrix_funktionen.c 8 gcc -c -o matrix_funktionen.o matrix_funktionen.c Von nun an genügt es, nach jeder Änderung auf der Kommandozeile make aufzurufen: $ make gcc -c -o matrix_test_main.o matrix_test_main.c gcc -c -o matrix_funktionen.o matrix_funktionen.c gcc -o matrix_test matrix_test_main.o matrix_funktionen.o

240 Auflösung der Abhängigkeiten: Erster Aufruf von make mit Ziel Z Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

241 Auflösung der Abhängigkeiten: Erster Aufruf von make mit Ziel Z Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

242 Auflösung der Abhängigkeiten: Erster Aufruf von make mit Ziel Z Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

243 Auflösung der Abhängigkeiten: Erster Aufruf von make mit Ziel Z Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

244 Auflösung der Abhängigkeiten: Nach Aktualisierung der Datei a Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

245 Auflösung der Abhängigkeiten: Nach Aktualisierung der Datei a Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

246 Auflösung der Abhängigkeiten: Nach Aktualisierung der Datei a Z = Ziel im Makefile = Abhängigkeit neuer als Ziel = nicht vorhanden A B C = erstellt und aktuell = kein Ziel a D A E b c c d

247 Mehrdateiprojekte mit make Bemerkungen: Das Kommando make Ziel erstellt Ziel anhand der Abhängigkeiten in der Datei Makefile. Der Befehl make ohne Argument erstellt das erste Ziel im Makefile. Eine Erstellungsregel muss nicht unbedingt Abhängigkeiten besitzen. Beispiel: Regel zum Aufräumen clean: rm *.o matrix_test -f Mit make clean können nun alle Sicherungs- und Objektdateien sowie das Programm gelöscht werden. Auch Befehle müssen nicht zwingend in einer Regel enthalten sein. Beispiel: Alles erstellen all: matrix_test matrix_test_main.o matrix_funktionen.o Durch die Auflistung sämtlicher Objekt- und Programmdateien als Abhängigkeiten von all lässt sich mit dem Aufruf make all das gesamte Projekt aktualisieren.

248 Mehrdateiprojekte mit make: implizite Regeln und Variablen GNU Make verfügt über eine große Menge von impliziten Regeln, die im Makefile nicht neu definiert werden müssen. Wichtigstes Beispiel: %.o: %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o In Worten: Jedes Objekt Datei.o hängt vom entsprechenden Code Datei.c ab. Zur Erstellung eines solchen Objekts ist folgender Befehl aufzurufen: Compiler -c Compileroptionen Präprozessoroptionen Datei.c -o Datei.o Zusätzliche Abhängigkeiten können als Regel ohne Befehl angegeben werden. Beispiel: matrix_test_main.o: matrix.h Mit Hilfe der Variablen CC, CFLAGS und CPPFLAGS lassen sich Compilername und Optionen anpassen. Beispiel: CC = gcc CFLAGS = -Wall CPPFLAGS = -DDEBUG_AN resultiert im generellen Compilerbefehl gcc -c -Wall -DDEBUG_AN Datei.c -o Datei.o

249 Mehrdateiprojekte mit make: Vereinfachtes Matrix-Makefile Ursprüngliches explizites Makefile: 1 matrix_test: matrix_test_main.o matrix_funktionen.o 2 gcc -Wall -o matrix_test matrix_test_main.o matrix_funktionen.o 3 4 matrix_test_main.o: matrix_test_main.c matrix.h 5 gcc -c -Wall -o matrix_test_main.o matrix_test_main.c 6 7 matrix_funktionen.o: matrix_funktionen.c 8 gcc -c -Wall -o matrix_funktionen.o matrix_funktionen.c Angepasstes Makefile: 1 CC = gcc 2 CFLAGS = -Wall 3 4 matrix_test: matrix_test_main.o matrix_funktionen.o 5 $(CC) $(CFLAGS) $^ -o $@ 6 7 matrix_test_main.o: matrix.h

250 Mehrdateiprojekte mit make: Vereinfachtes Matrix-Makefile Bemerkung: Wichtige vordefinierte Variablen sind beispielsweise: Name des Ziels $ˆ Liste aller Abhängigkeiten ohne Wiederholungen $+ Liste aller Abhängigkeiten $< erste Abhängigkeit Resultat des angepassten Makefiles: $ make gcc -Wall -c -o matrix_test_main.o matrix_test_main.c gcc -Wall -c -o matrix_funktionen.o matrix_funktionen.c gcc -Wall matrix_test_main.o matrix_funktionen.o -o matrix_test

251 Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald und

252 Die kommerzielle Software MATLAB von MathWorks MathWorks TAH Campuslizenz Die Software darf von allen Studierenden der Universität des Saarlandes genutzt werden. Das umfasst einerseits beliebig viele Installationen auf dem Campus und andererseits auch die Nutzung auf privaten Computern. Notwendig: Registrierung bei der Firma ASKnet AG. Weitere Informationen: Weitere Informationen: Herstellerseite:

253 Einleitung Matlab Matlab (Matrix Laboratory) ist ein interaktives Matrix-orientiertes Softwaresystem zur Berechnung und Lösung numerischer Probleme. Warum Matlab? benutzerfreundliche Syntax umfangreiche Sammlung mathematischer Algorithmen vielfältige und einfach realisierbare Datenausgabe / Visualisierung kurze Entwicklungszeiten, in der eingebauten Programmiersprache lassen sich Algorithmen schnell und leicht realisieren (Interpretersprache) einfaches Debugging hohe Absturzsicherheit Nachteile: etwas höhere Anforderungen an den Rechner (v.a. Speicherbedarf) langsamer als kompilierter Code, insbesondere bei for-schleifen Aber: Programme anderer Sprachen, z.b. C, lassen sich leicht einbinden.

254 Literatur Anne Angermann et al.: Matlab-Simulink-Stateflow: Grundlagen, Toolboxen, Beispiele, Oldenbourg Verlag, 2011 Wolfgang Schweizer: Matlab kompakt, Oldenbourg Verlag, 2009 Cleve Moler: Numerical Computing with Matlab, SIAM, 2010 Stormy Attaway: MATLAB: A Practical Introduction to Programming and Problem Solving, Butterworth Heinemann, 2011 Günter Gramlich: Eine Einführung in Matlab aus der Sicht eines Mathematikers Susanne Teschl: MATLAB - Eine Einführung staff.technikum-wien.at/~teschl/matlabskriptum.pdf weitere Informationen: Homepage von Matlab: bzw. Informationen zur Campuslizenz: unisb.asknet.de/cgi-bin/program/s1552

255 Matlab-Oberfläche aktuelles Release: R2012b

256 Matlab-Oberfläche Starten mit $ matlab & Elemente der Matlab-Oberfläche: Command Window: Command History: Workspace Browser: Current Folder: Editor: direkte Eingabe von Matlab-Befehlen Historie der im Command Window eingegebenen Befehle Anzeige der Variablen des Base-Speicherbereichs Auflistung der Dateien des aktuellen Verzeichnisses Bearbeitung von Matlab-Skripten und Funktionen (mit Syntax-Highlighting, Tab-Vervollständigung,...)

257 erste Schritte in Matlab Eingabe direkt im Command Window Ausgabe erscheint beim Weglassen des Semikolons am Ende der Befehlszeile. Es gilt Punktrechnung vor Strichrechnung. Es wird zwischen Klein- und Großbuchstaben unterschieden. Strings werden durch Hochkommata erzeugt. Beispiele: >> ans = 4 >> a=3; >> b=4; >> a/b ans = 0.75 >> c=a^2+b^2 c = 25 >> d=a+b*c d = 103 >> s = 'Hallo' Unterschied zu C: keine Deklaration der Variablen nötig! Variablen werden (intern) automatisch als double behandelt und belegen 8 Bytes Speicherplatz. kein cast notwendig! Ganzzahlige Ergebnisse werden als ganze Zahl ausgegeben.

258 Script-Files Skripte / Script-Files Ein Skript ist eine Folge von Anweisungen, die im Editor eingegeben und mit der Endung.m abgespeichert werden. Es wird durch Eingabe des Dateinamens im Command Window oder durch den Button Run im Editor (alternativ: F5) ausgeführt, d.h. alle Anweisungen werden der Reihe nach abgearbeitet. Beispiel: Das Skript testskript.m a=3; b=4; c=a^2+b^2 führt nach Ausführung zu der Ausgabe c=25 auf dem Bildschirm. Zudem sind die Variablen a=3, b=4 und c=25 im Workspace enthalten (und können bei Bedarf für weitere Berechnungen verwendet werden).

259 Operatoren Vergleichende und logische Operatoren wie in C Notation in Matlab Notation in C a < b a < b a >= b a >= b a == b a == b A && B A && B A B A B Ausnahme: a = b a!= b A!A Unterschied zu C: In Matlab dürfen a und b Matrizen gleicher Dimension sein. In diesem Fall wird elementweise verglichen und eine Matrix gleicher Dimension mit Nullen und Einsen zurückgegeben. keine Inkrement- und Dekrementoperatoren keine arithmetischen Zuweisungsoperatoren a++, a a+=2

260 Ausgewählte Variablen und Konstanten ans inf NaN pi wird dem Ergebnis kein Variablenname zugeordnet, so wird automatisch die Variable ans erzeugt liegt das Ergebnis nicht im Intervall [-realmax; realmax] erhält dieses automatisch den Wert inf Ergebnis nicht definierter arithmetischer Operationen, z.b. 0/0, inf/inf, 0*inf, sind vom Typ NaN (Not a Number) = π realmax größte positive Zahl (PC: ) realmin kleinste positive Zahl (PC: ) In Matlab ist es möglich mit komplexen Zahlen zu rechnen: i,j imaginäre Einheit (i 2 = 1) conj(z) konjugiert komplexe Zahl zu Z real(z),imag(z) Real- bzw. Imaginärteil von Z angle(z), abs(z) Polardarstellung von Z

261 Nützliche Befehle bzw. Funktionen clc clear [mod] ctrl + c help [Name] isinf(var) isnan(var) who whos löscht die Oberfläche des Command Window, aber nicht die Variablen selbst löscht alle Variablen aus dem Workspace mod = var: nur die Variable var wird gelöscht mod = all: alle Variablen werden gelöscht (Achtung: auch globale!) Notbremse, bricht die aktuelle Berechnung ab listet alle bzw. die zu Name gehörigen Hilfe-Themen auf, bequemer: GUI (Grafische Benutzeroberfläche) benutzen (z. B. mit F1) liefert 1, falls var vom Typ inf liefert 1, falls var vom Typ NaN listet die im Workspace vorhandenen Variablen auf liefert eine detailierte Liste der Variablen (Typ, Wert)

262 Erzeugen von Matrizen Eingabe in eckigen Klammern: einzelne Komponenten in einer Zeile werden durch Leerzeichen oder Komma getrennt neue Zeilen werden durch ein Semikolon gekennzeichnet x = [1 2 3] A = [1 2 3; 4 5 6] erzeugt den Zeilenvektor x = (1, 2, 3) sowie die Matrix A = ( ) Merke: Zeilenvektoren werden intern als 1 N-Matrizen behandelt. Eingabe durch Steuerung der Schrittweite: y= a:h:b erzeugt einen Vektor von a bis b mit Schrittweite h y= 0:2:6 erzeugt den Zeilenvektor y = (0, 2, 4, 6) Eingabe mit der Funktion linspace(a,b,n) erzeugt einen Vektor von a bis b mit N Komponenten y=linspace(0,6,4) erzeugt ebenfalls den Zeilenvektor y = (0, 2, 4, 6). Bemerkung: Bei großen Dimensionen ist die Eingabe mittels a:h:b etwas schneller als bei der Verwendung von linspace.

263 Arbeiten mit Matrizen Die Indizierung von Matrizen beginnt in Matlab bei 1! Reservierung von Speicherplatz erfolgt automatisch. Initialisierung trotzdem sinnvoll, führt in der Regel zu schnelleren Programmen. ( ) ( ) a11 a Wir betrachten die Matrix A = = 12 a a 21 a 22 a 23 einzelnes Matrizenelement : A(i,j) liefert das Element a i,j >> A(2, 3) ans = 6 i-te Zeile einer Matrix: A(i,:) j-te Spalte einer Matrix: A(:,j) Teilmatrix: A(p:q,[r,s]) liefert die Zeilen p bis q und die Spalten r und s der Matrix >> A(1,:) ans = >> A(:,2) ans = 2 5 >> A(1:2,[1,3]) ans =

264 Arbeiten mit Matrizen Wir betrachten zusätzlich die Matrix B = ( ) 1 2. B=[1,2;0,3] 0 3 Addition, Subtraktion und Multiplikation definiert wie in Linearer Algebra >> B*A ans = >> B+3 % oder 3+B ans = elementweiser Zugriff auf Matrizen mit Punktoperator: >> B.^2 % oder B.*B ans = >> B^2 % oder B*B ans = Transponierte Matrix: >> B' ans =

265 Matrix-Funktionen det(b) eig(b) rank(b) norm(b,1) Determinante von B Eigenwerte und (normierte) Eigenvektoren von B ( 1 lambda=eig(b) liefert λ = 3) ( ) [v,lambda]=eig(b) liefert v = 1 und λ = 0 2 Rang der Matrix B 1-Norm (Spaltensummenorm) der Matrix B, entspricht dem Ausdruck max 1 j n n i=1 b ij, hier B 1 = 5. ( ) norm(b, fro ) Frobeniusnorm der Matrix B, entspricht dem Ausdruck m n B F = i=1 j=1 b ij 2, hier B F = 14. ( ) inv(b) Inverse der Matrix B, Ergebnis: B 1 = Hinweis: Die explizite Berechnung einer inversen Matrix, um ein lineares Gleichungssystem zu lösen, ist in der Praxis (fast immer) zu aufwendig! Ausweg: Vorlesung Praktische Mathematik (QR-Zerlegung,... ) Matlab: linsolve

266 Matrix-Funktionen end length size zeros(m,n) ones(m,n) eyes(m) rand(m,n) A(:) A(A>2) max(v) min(v) sum(v) maximaler Indexwert von Matrizen, z. B. liefert y(end) den letzten Eintrag des Vektors y. höchste Dimension einer Matrix beide Dimensionen einer Matrix M N-Matrix, deren Einträge 0 sind M N-Matrix, deren Einträge 1 sind M M-Einheitsmatrix M N-Zufallsmatrix, wobei jeder Matrixeintrag eine Realisierung einer auf [0,1)-gleichverteilten Zufallsvariablen ist. Vektor, der die Spalten der Matrix A hintereinandergereit enthält. Mit der Matrix A der vorigen Folie entspricht A(:) dem Vektor [1; 4; 2; 5; 3; 6]. Vektor, der die Spalten der Matrix A hintereinandergereit enthält, wobei nur Matrixelemente größer zwei berücksichtigt werden. Mit der Matrix A der vorigen Folie entspricht A(A>2) dem Vektor [4; 5; 3; 6]. kleinster Eintrag des Vektors v größter Eintrag des Vektors v Summe der Vektorelemente

267 Mathematische Funktionen Trigonometrische Funktionen: cos, sin, tan, asin, acos, atan, atan2 Soll die Berechnung in Grad durchgeführt werden, so müssen die Funktionen mit d ergänzt werden. Beispiel: >>sin(pi/4) ans = >> sind(45) ans = Exponential- und logarithmische Funktionen: exp, log, log10 Wurzeln: sqrt, nthroot(x,n) Rundungsfunktionen: ceil, floor, round (runden zum nächsten integer-wert) Betragsfunktion: abs Hinweis: Diese Funktionen akzeptieren als Argumente Matrizen, die dann elementweise ausgewertet werden. Dies führt in der Regel zu schnellerem Code.

268 if-anweisung Syntax in Matlab if Bedingung_1 Anweisungsblock_1 elseif Bedingung_2 Anweisungsblock_2... elseif Bedingung_N Anweisungsblock_N else Anweisungsblock end Syntax in C if (Bedingung_1) Anweisungsblock_1 else if (Bedingung_2) Anweisungsblock_2... else if (Bedingung_N) Anweisungsblock_N else Anweisungsblock

269 switch-anweisung switch Variable Syntax in Matlab case Wert_1 Anweisungsblock_1 case Wert_2 Anweisungsblock_2... case Wert_N Anweisungsblock_N otherwise Anweisungsblock end Syntax in C switch (Variable) { case Wert_1: Anweisungsblock_1 break; // optional case Wert_2: Anweisungsblock_2... case Wert_N: Anweisungsblock_N default: // optional Anweisungsblock // optional } Unterschied zu C: In Matlab ist kein break notwendig, es wird immer nur der zum case gehörige Anweisungsblock ausgeführt.

270 for- und while-schleifen Syntax in Matlab for Variable = Vektor Anweisungsblock end while Bedingung Anweisungsblock; end Syntax in C for (Initialisierung; Bedingung; Update) Anweisungsblock while (Bedingung) Anweisungsblock Do-while-Schleifen existieren in Matlab nicht! (Ausweg: while 1 und break) Beispiel: x=1:1:10; summe=0; for k=1:10 summe=summe+x(k); end % Ergebnis entspricht sum(x) m=0; Erg=0; while m<=10 Erg=Erg+2; m=m+1; end

271 Ein- und Ausgabe am Bildschirm Eingabe: variable=input(string) variable=input(string, s ) Ausgabe von string auf dem Bildschirm, die Eingabe (Zahl, Variablenname) wird ausgewertet und variable zugewiesen. Ausgabe von string auf dem Bildschirm, die Eingabe wird nicht ausgewertet sondern als String variable zugewiesen. Ausgabe: disp(string) disp(variable) fprintf(string, Parliste) Ausgabe von string auf dem Bildschirm. Ausgabe der Werte von variable auf dem Bildschirm. formatierte Ausgabe auf dem Bildschirm. Die Syntax entspricht im Wesentlichen der von printf in C.

272 Matlab als Programmiersprache Neben den Script-Files gibt es noch Function-Files, welche auch in Dateien mit der Endung.m gespeichert werden. Sie beginnen mit dem Schlüsselwort function. function [ Rueckgabewerte ] = Funktionsname( Parameter ) % Beschreibung als Kommentar Anweisungsblock end Unterschiede zu C: beliebige Anzahl von Rückgabewerten Matrizen können übergeben werden Übergabemethode: shared-data-copy, d.h. intern werden Variablen, die nicht verändert werden, als Zeiger übergeben. Wird eine Variable in der Funktion verändert, so wird eine Kopie im Speicherbereich der Funktion erstellt. Funktionenvariablen sind lokale Variablen Function-Files unter Funktionsname.m abspeichern Aufruf anderer m-files in m-files möglich, sofern die Verzeichnisse in denen die Dateien liegen unter Set Path eingetragen sind oder mit dem Current Folder übereinstimmen.

273 Nützliches tic-toc pause pause(n) nargin nargout (primitive) Zeitmessung: Mit tic wird die Stoppuhr auf null gesetzt, toc gibt die vergangene Zeit aus. wartet bis zu einem Tastendruck auf der Tastatur Pause für n Sekunden Anzahl der Funktionsparameter Anzahl der Rückgabewerte Beispiel: function [ erg ] = testfunktion(a, b) if nargin==1 erg=a; else erg=a+b; end %if end testfunktion(2) liefert das Ergebnis 2. testfunktion(2,3) liefert das Ergebnis 5. testfunktion(2,3,2) führt zu der Fehlermeldung: Too many input arguments

274 Beispiel: Fakultät function [ erg ] = fakul(n) % berechnet n! = n*(n-1)*...*1 rekursiv if(n==0 n==1) erg = 1; elseif n<0 disp('bitte positive Zahl eingeben!') else erg = n*fak(n-1); end end function [ erg ] = fakul_iter(n) % berechnet n! = n*(n-1)*...*1 iterativ erg=1; for k=1:n erg=erg*k; end % Alternative zur for-schleife: erg=prod(1:n); end

275 Beispiel: Fakultät Startscript: % Vergleich Rekursion und Iteration k=20; % rekursiv tic n1=fakul(k); zeit1=toc; fprintf('\n Zeitverbrauch rekursiv: %f',zeit1); % iterativ tic n2=fakul_iter(k); zeit2=toc; fprintf('\n Zeitverbrauch iterativ: %f',zeit2); fprintf('\n Ergebnis rekursiv: %d, iterativ: %d \n',n1,n2); Ergebnis: Zeitverbrauch rekursiv: Zeitverbrauch iterativ: Ergebnis rekursiv: , iterativ:

276 Arbeiten mit Dateien spezielles Matlab-Binärformat (Endung:.mat) save(dateiname, [Var1,...,VarN]) load(dateiname, [Var1,...,VarN]) speichert die Variablen Var1 bis VarN (bzw. alle im Workspace enthaltenen) in die Datei Dateiname.mat lädt die Variablen Var1 bis VarN (bzw. alle enthaltenen) aus der Datei Dateiname.mat in den Workspace Textdateien fopen, fprintf, fscanf, fclose (nahezu) analog zu C, siehe Matlabhilfe für Details. Binärdateien fopen, fread, fwrite, fclose (nahezu) analog zu C, siehe Matlabhilfe für Details. verschiedene Dateiformate, z.b. xls, bmp, png, jpg,... und (einfache) Textdateien importdata(dateiname,[delimiter],[kopfzeilen]) importiert zahlreiche Formate, wobei Delimiter das Spaltentrennzeichen festlegt und die Kopfzeilen beim Einlesen übersprungen werden

277 Fallstricke Achtung: In Matlab ist fast alles erlaubt! Führt man das folgende Skript in Matlab aus, so erhält man keine Fehlermeldung und auch der Code Analyzer gibt keine Warnungen aus! sum = 0; for i = 1:10 sum = sum + i; end pi = 1; Folgende Probleme treten anschließend auf: 1»sum([1 2]) Index exceeds matrix dimensions. Merke: Funktionen werden durch Variablen mit gleichem Namen überdeckt! 2»z=1+2*i z=21. Merke: Möchte man mit komplexen Zahlen arbeiten, so muss man auf i oder j als Laufindex für Schleifen verzichten! 3»sin(pi) ans= Merke: Selbst Konstanten wie π können durch beliebige Werte überschrieben werden!

278 Matlab-Profiler und Code Analyzer Code Analyzer Während der Eingabe wird eine Syntax-Prüfung des Codes durchgeführt, auffällige Stellen werden unterschlängelt. Zudem werden Syntax-Fehler mit roten und Warnungen durch orangefarbene Balken in der Scrollleiste markiert. mlintrpt mlintrpt( Dateiname ) Anzeige des Code Analyzer Reports für alle Dateien im current folder. (alternativ Klick auf Analyze Code ) Anzeige des Code Analyzer Reports für die Datei Dateiname.m. (alternativ Show Code Analyzer Report im Editor) Matlab-Profiler Suche nach Optimierungspotential, meist ist nur ein kleiner Teil des Programms für lange Rechenzeiten verantwortlich. Debugging des Matlab-Codes profile on profile viewer Profiling starten. Profile Summary anzeigen. (alternativ: Run and Time im Editor)

279 geschicktes Programmieren in Matlab: Binomialkoeffizient langsame Variante: function [ binom ] = binom_l( n,k ) if (n < 0 k < 0) disp(['bitte zwei positive,'... 'Zahlen eingeben']); binom = NaN; elseif n >= k if k > n/2, k = n-k; end zaehler = 1; nenner = 1; for l = n-k+1:n zaehler = zaehler * l; end for l = 1:k nenner = nenner * l; end binom = zaehler / nenner; else binom = 0; end schnellere Variante: function [ binom ] = binom_s( n,k ) if (n < 0 k < 0) disp(['bitte zwei positive,'... 'Zahlen eingeben']); binom = NaN; elseif n >= k if k > n/2, k = n-k; end binom = prod(((n-k+1):n)./(1:k)); else binom = 0; end end Bemerkung: Die schnellere Variante ist zudem stabiler, da für große k die Variablen zaehler und nenner nicht über alle Schranken wachsen. end

280 geschicktes Programmieren in Matlab: Funktionsauswertungen langsame Variante: function [x,y] = funkauswert_l( t ) x = zeros(1,length(t)); y = zeros(1,length(t)); for i = 1:length(t) x(i) = t(i)^2; y(i) = log2(t(i)); end schnelle Variante: function [x,y] = funkauswert_s( t ) x = t.^2; y = log2(t); end end

281 geschicktes Programmieren in Matlab: Vandermonde-Matrix 1 x 1 x x n x 2 x x n 1 2 V := 1 x 3 x x n x m xm 2... xm n 1 häufige Anwendung: Beschreibung einer Polynom-Interpolation. langsame Variante: function [ V ] = vander_l( x ) for l = 1:length(x) for m = 1:length(x) V(l,m) = x(l)^(m-1); end end end schnelle Variante: function [ V ] = vander_s( x ) n = length(x); x = x(:); %x ist nun Spaltenvektor V = ones(n); for l = 1:n-1 V(:,l+1) = x.*v(:,l); end end Bemerkung: Die Matlab-Funktion vander berechnet eine modifizierte Version der Vandermonde-Matrix.

282 geschicktes Programmieren in Matlab Um die Geschwindigkeit der Funktionen zu testen, wird mit dem Matlab-Profiler das folgende Skript analysiert: B1 = ones(100); B2 = ones(100); for n = 1:100 for k = 1:n B1(n,k) = binom_l(n, k); B2(n,k) = binom_s(n, k); end end t = 0: :1; [f1,f2] = funkauswert_s(t); [g1,g2] = funkauswert_l(t); x = 0.1:0.001:1; v1 = vander_l(x); v2 = vander_s(x);

283 geschicktes Programmieren in Matlab: Ergebnis des Profilers

284 allgemeine Tipps: (Große) Matrizen sollten vor ihrer Verwendung mit der maximal benötigten Größe vorbelegt werden (z.b. mit zeros(m,n)). For-Schleifen sollten (falls möglich) durch Vektorisierung des Codes vermieden werden. Dies führt zu effizienterem und übersichtlicherem Code. Ausgaben im Command Window sowie grafische Ausgaben benötigen viel Zeit und sollten daher nur mit Bedacht eingesetzt werden. Löschen nicht mehr benötigter Variablen (mit clear Name) gibt dadurch belegten Speicher wieder frei. Jeder Aufruf von Skripten oder Funktionen, die in separaten Dateien gespeichert sind, benötigt zusätzlich Zeit. Wird eine Funktion in einer for-schleife häufig aufgerufen, so erhält man schnelleren Code, indem man den Inhalt der Funktion (entsprechend angepasst) in die Schleife kopiert. Achtung: Dadurch verschlechtert sich die Lesbarkeit sowie die Wartung des Codes, daher nur in geschwindigkeitskritischen Stellen verwenden! Function Handles welche im Wesentlichen mit dem Konzept von Funktionszeigern in C übereinstimmen, können oft gewinnbringend eingesetzt werden.

285 Graphiken mit Matlab Graphiken können direkt in Matlab erstellt werden! figure erzeugt ein neues Figure hold on schützt ein Fenster vor Überschreiben hold all schützt ein Fenster vor Überschreiben, die Linienfarbe wird automatisch gewechselt legend(string1,string2,...) fügt der Grafik eine Legende hinzu axis equal Achseneinheit in alle Richtungen gleich lang set(...) / get(...) Eigenschaften setzen/anzeigen lassen gcf / gca aktuelles Figure- bw. Achsen-Handle 2D-Plots: plot(x,y) plot(y) subplot(m, n, zaehler) plottet den Vektor y gegen den Vektor x plottet den Vektor y gegen dessen Indizes erstellt ein Figure für m n - Subplots, 1 zaehler m n bezeichnet das aktuelle Fenster, wobei zeilenweise gezählt wird. Allgemeine Syntax: plot(x,y, FMS,..., Eigenschaft, Wert ) FMS = Farbe Marker Linientyp z.b. r*: zeichnet rote gepunktete Linie mit *-Marker Eigenschaft = LineWidth, MarkerSize,

286 2D-Plot: Beispiel 1 theta = linspace(-pi,pi,25); 2 sinus = sin(theta); 3 kosinus = cos(theta); 4 5 %figure 6 hold all; 7 plot(theta,sinus); 8 plot(theta,kosinus,'-o','markersize',8); 9 10 set(gca,'xtick',-pi:pi/2:pi); 11 set(gca,'xticklabel',{'-pi','-pi/2','0','pi/2','pi'}); 12 xlabel('-\pi \leq \Theta \leq \pi'); 13 set(gca,'ytick',-1:0.5:1); 14 title('plot von sin(\theta) und cos(\theta)'); 15 text(-pi/4,sin(-pi/4),'\leftarrow sin(-\pi/4)', 'HorizontalAlignment','left') ; 17 legend('sinus','kosinus'); 18 axis([-pi pi -1 1]); 19 axis equal; 20 box;

287 2D-Plot: Beispiel Plot von sin(θ) und cos(θ) Sinus Kosinus sin( π/4) pi pi/2 0 pi/2 pi π Θ π Der Plot Editor bietet zahlreiche Möglichkeiten der Nachbearbeitung. Bei Speicherung als.fig ist eine Nachbearbeitung auch später noch möglich. Export in alle Standardformate möglich: PNG, JPG, EPS, PDF,...

288 3D Grafiken: Beispiele

Programmierung für Mathematiker

Programmierung für Mathematiker Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald 03.05.2017 Funktionen Funktion Zusammenfassung eines Anweisungsblocks zu einer aufrufbaren Einheit Gehört zu den wichtigsten

Mehr

Programmierung für Mathematiker

Programmierung für Mathematiker Programmierung für Mathematiker Prof. Dr. Thomas Schuster M.Sc. Dipl.-Phys. Anne Wald 19.04.2017 Mathematik und ihre Anwendung am Lehrstuhl Prof. Thomas Schuster www.num.uni-sb.de/schuster Forschungsschwerpunkte

Mehr

Modellierung und Programmierung

Modellierung und Programmierung Modellierung und Programmierung Dr. Martin Riplinger 24.10.2012 IAM Institut für Angewandte Mathematik Literatur: Internet Skripte Erik Wallacher: Vorlesungsskript Modellierung/Programmierung Gerald Kempfer:

Mehr

Kapitel 4. Programmierkurs. Datentypen. Arten von Datentypen. Wiederholung Kapitel 4. Birgit Engels, Anna Schulze WS 07/08

Kapitel 4. Programmierkurs. Datentypen. Arten von Datentypen. Wiederholung Kapitel 4. Birgit Engels, Anna Schulze WS 07/08 Kapitel 4 Programmierkurs Birgit Engels, Anna Schulze Wiederholung Kapitel 4 ZAIK Universität zu Köln WS 07/08 1 / 23 2 Datentypen Arten von Datentypen Bei der Deklaration einer Variablen(=Behälter für

Mehr

Grundlagen der Programmierung

Grundlagen der Programmierung Grundlagen der Programmierung 7. Vorlesung 18.05.2016 1 Konstanten Ganzzahlkonstante Dezimal: 42, 23, -2 Oktal (0 vorangestellt): 052 Hexadezimal (0x vorangestellt): 0x2A Gleitkommazahlen: 3.1415, 2.71,

Mehr

Ralf Kirsch Uwe Schmitt. Programmieren inc. Eine mathematikorientierte Einführung. Mit 24 Abbildungen und 13 Tabellen. Springer

Ralf Kirsch Uwe Schmitt. Programmieren inc. Eine mathematikorientierte Einführung. Mit 24 Abbildungen und 13 Tabellen. Springer Ralf Kirsch Uwe Schmitt Programmieren inc Eine mathematikorientierte Einführung Mit 24 Abbildungen und 13 Tabellen Springer Inhaltsverzeichnis Eine Einleitung in Frage und Antwort V 1 Vorbereitungen 1

Mehr

Grundlagen der Programmierung

Grundlagen der Programmierung Grundlagen der Programmierung 8. Vorlesung 25.05.2016 1 Ausdrücke "Befehle", die ein Ergebnis liefern 3 + 4 sin(x) x < 10 getchar() Ausdrücke können Teil eines anderen Ausdrucks sein x = sin( x + y ) Auswertung:

Mehr

Dr. Monika Meiler. Inhalt

Dr. Monika Meiler. Inhalt Inhalt 3 C-Ausdrücke...3-2 3.1 Arithmetische Ausdrücke...3-3 3.2 Wertzuweisungen...3-5 3.3 Inkrementieren und Dekrementieren...3-6 3.4 Logische Ausdrücke (Bedingungen)...3-7 3.5 Bedingte Ausdrücke...3-8

Mehr

C++ Teil 2. Sven Groß. 16. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 22

C++ Teil 2. Sven Groß. 16. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 22 C++ Teil 2 Sven Groß IGPM, RWTH Aachen 16. Apr 2015 Sven Groß (IGPM, RWTH Aachen) C++ Teil 2 16. Apr 2015 1 / 22 Themen der letzten Vorlesung Hallo Welt Elementare Datentypen Ein-/Ausgabe Operatoren Sven

Mehr

Institut für Programmierung und Reaktive Systeme. Java 2. Markus Reschke

Institut für Programmierung und Reaktive Systeme. Java 2. Markus Reschke Java 2 Markus Reschke 07.10.2014 Datentypen Was wird gespeichert? Wie wird es gespeichert? Was kann man mit Werten eines Datentyps machen (Operationen, Methoden)? Welche Werte gehören zum Datentyp? Wie

Mehr

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 1. Teil

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 1. Teil MÜNSTER Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++ 1. Teil 11. April 2012 Organisatorisches MÜNSTER Übung zur Vorlesung Wissenschaftliches

Mehr

Java I Vorlesung Imperatives Programmieren

Java I Vorlesung Imperatives Programmieren Java I Vorlesung 2 Imperatives Programmieren 3.5.2004 Variablen -- Datentypen -- Werte Operatoren und Ausdrücke Kontrollstrukturen: if Imperatives Programmieren Im Kern ist Java eine imperative Programmiersprache.

Mehr

Programmierkurs C++ Variablen und Datentypen

Programmierkurs C++ Variablen und Datentypen Programmierkurs C++ Variablen und Datentypen Prof. Dr. Stefan Fischer Institut für Telematik, Universität zu Lübeck http://www.itm.uni-luebeck.de/people/fischer #2 Überblick Welche Datentypen gibt es in

Mehr

FACHHOCHSCHULE AUGSBURG Hochschule für Technik, Wirtschaft und Gestaltung

FACHHOCHSCHULE AUGSBURG Hochschule für Technik, Wirtschaft und Gestaltung C Sprachelemente für Übung 2 Typumwandlungen (type casts) Bei Ausdrücken, in denen Operanden mit unterschiedlichem Typ vorkommen, werden diese vom Compiler vor der Ausführung automatisch in einen gemeinsamen

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 47 Einstieg in die Informatik mit Java Anweisungen Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 47 1 Ausdrucksanweisung 2 Einfache Ausgabeanweisung 3 Einfache Eingabeanweisung,

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java Vorlesung vom 25.4.07, Anweisungen Übersicht 1 Ausdrucksanweisung 2 Einfache Ausgabeanweisung 3 Einfache Eingabeanweisung, Vorbereitungen 4 Verbundanweisung 5 Bedingte Anweisung 6 Auswahlanweisung 7 for

Mehr

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom Einstieg in die Informatik mit Java, Vorlesung vom 2.5.07 Übersicht 1 2 definition 3 Parameterübergabe, aufruf 4 Referenztypen bei 5 Überladen von 6 Hauptprogrammparameter 7 Rekursion bilden das Analogon

Mehr

Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion. Programmieren in C

Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion. Programmieren in C Übersicht Funktionen Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion Sinn von Funktionen Wiederverwendung häufig verwendeter nicht banaler Programmteile Wiederverwendung

Mehr

4.2 Gleitkommazahlen. Der Speicherbedarf (in Bits) ist üblicherweise. In vielen Anwendungen benötigt man gebrochene Werte. Physikalische Größen

4.2 Gleitkommazahlen. Der Speicherbedarf (in Bits) ist üblicherweise. In vielen Anwendungen benötigt man gebrochene Werte. Physikalische Größen . Gleitkommazahlen In vielen Anwendungen benötigt man gebrochene Werte. Physikalische Größen Umrechnen von Einheiten und Währungen Jede Zahl x Q mit x 0 lässt sich folgendermaßen schreiben: x = s m e mit

Mehr

JavaScript. Dies ist normales HTML. Hallo Welt! Dies ist JavaScript. Wieder normales HTML.

JavaScript. Dies ist normales HTML. Hallo Welt! Dies ist JavaScript. Wieder normales HTML. JavaScript JavaScript wird direkt in HTML-Dokumente eingebunden. Gib folgende Zeilen mit einem Texteditor (Notepad) ein: (Falls der Editor nicht gefunden wird, öffne im Browser eine Datei mit der Endung

Mehr

Javakurs FSS Lehrstuhl Stuckenschmidt. Tag 1 - Variablen und Kontrollstrukturen

Javakurs FSS Lehrstuhl Stuckenschmidt. Tag 1 - Variablen und Kontrollstrukturen Javakurs FSS 2012 Lehrstuhl Stuckenschmidt Tag 1 - Variablen und Kontrollstrukturen main Methode Startpunkt jeder Java Anwendung String[] args ist ein Array aus Parametern, die beim Aufruf über die Kommandozeile

Mehr

Unterlagen. CPP-Uebungen-08/

Unterlagen.  CPP-Uebungen-08/ Unterlagen http://projects.eml.org/bcb/people/ralph/ CPP-Uebungen-08/ http://www.katjawegner.de/lectures.html Kommentare in C++ #include /* Dies ist ein langer Kommentar, der über zwei Zeilen

Mehr

GI Vektoren

GI Vektoren Vektoren Problem: Beispiel: viele Variablen vom gleichen Typ abspeichern Text ( = viele char-variablen), Ergebnisse einer Meßreihe ( = viele int-variablen) hierfür: Vektoren ( = Arrays = Feld ) = Ansammlung

Mehr

Einheit Datentypen in der Programmiersprache C Schwerpunkt: Elementare (arithmetische) Datentypen

Einheit Datentypen in der Programmiersprache C Schwerpunkt: Elementare (arithmetische) Datentypen Einheit Datentypen in der Programmiersprache C Schwerpunkt: Elementare (arithmetische) Datentypen Kurs C/C++ Programmierung, WS 2008/2009 Dipl.Inform. R. Spurk Arbeitsgruppe Programmierung FR 6.2 Informatik

Mehr

Primitive Datentypen, Eingaben, Kontrollstrukturen und Methodendeklaration

Primitive Datentypen, Eingaben, Kontrollstrukturen und Methodendeklaration Primitive Datentypen, Eingaben, Kontrollstrukturen und Methodendeklaration CoMa-Übung III TU Berlin 30.10.2013 Primitive Datentypen, Eingaben, Kontrollstrukturen und Methodendeklaration 30.10.2013 1 /

Mehr

4.4 Imperative Algorithmen Prozeduren

4.4 Imperative Algorithmen Prozeduren 4.4.2 Prozeduren Der Wert eines Ausdrucks u in Zustand z Z lässt sich damit auch leicht definieren (jetzt W Z statt W σ ) Dazu erweitern wir die rekursive Definition von Folie 57 (Wert eines Ausdrucks):

Mehr

Prof. Dr. Oliver Haase Karl Martin Kern Achim Bitzer. Programmiertechnik Operatoren, Kommentare, Ein-/Ausgabe

Prof. Dr. Oliver Haase Karl Martin Kern Achim Bitzer. Programmiertechnik Operatoren, Kommentare, Ein-/Ausgabe Prof. Dr. Oliver Haase Karl Martin Kern Achim Bitzer Programmiertechnik Operatoren, Kommentare, Ein-/Ausgabe Was sind Operatoren Ein Operator ist eine in die Programmiersprache eingebaute Funktion, die

Mehr

Algorithmen zur Datenanalyse in C++

Algorithmen zur Datenanalyse in C++ Algorithmen zur Datenanalyse in C++ Hartmut Stadie 16.04.2012 Algorithmen zur Datenanalyse in C++ Hartmut Stadie 1/ 39 Einführung Datentypen Operatoren Anweisungssyntax Algorithmen zur Datenanalyse in

Mehr

Einführung in die C-Programmierung

Einführung in die C-Programmierung Einführung in die C-Programmierung Warum C? Sehr stark verbreitet (Praxisnähe) Höhere Programmiersprache Objektorientierte Erweiterung: C++ Aber auch hardwarenahe Programmierung möglich (z.b. Mikrokontroller).

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java Vorlesung vom 6.11.07, Weitere Anweisungen Übersicht 1 Verbundanweisung 2 Bedingte Anweisung 3 Auswahlanweisung 4 for Schleife 5 while Schleife 6 do Schleife 7 break Anweisung 8 continue Anweisung 9 Leere

Mehr

Tag 3 Repetitorium Informatik (Java)

Tag 3 Repetitorium Informatik (Java) Tag 3 Repetitorium Informatik (Java) Dozent: Marius Kamp Lehrstuhl für Informatik 2 (Programmiersysteme) Friedrich-Alexander-Universität Erlangen-Nürnberg Wintersemester 2017/2018 Übersicht Typkonvertierung

Mehr

einlesen n > 0? Ausgabe Negative Zahl

einlesen n > 0? Ausgabe Negative Zahl 1 Lösungen Kapitel 1 Aufgabe 1.1: Nassi-Shneiderman-Diagramm quadratzahlen Vervollständigen Sie das unten angegebene Nassi-Shneiderman-Diagramm für ein Programm, welches in einer (äußeren) Schleife Integer-Zahlen

Mehr

Modul Entscheidungsunterstützung in der Logistik. Einführung in die Programmierung mit C++ Übung 2

Modul Entscheidungsunterstützung in der Logistik. Einführung in die Programmierung mit C++ Übung 2 Fakultät Verkehrswissenschaften Friedrich List, Professur für Verkehrsbetriebslehre und Logistik Modul Entscheidungsunterstützung in der Logistik Einführung in die Programmierung mit C++ Übung 2 SS 2016

Mehr

ModProg 15-16, Vorl. 5

ModProg 15-16, Vorl. 5 ModProg 15-16, Vorl. 5 Richard Grzibovski Nov. 18, 2015 1 / 29 Übersicht Übersicht 1 Logische Operationen 2 Priorität 3 Verzweigungen 4 Schleifen 2 / 29 Logische Werte Hauptkonzept: Besitzt ein C-Ausdruck

Mehr

Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen

Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen 16OH21005 gefördert. Die Verantwortung für den Inhalt dieser

Mehr

C++ Teil 4. Sven Groß. 30. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 16

C++ Teil 4. Sven Groß. 30. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 16 C++ Teil 4 Sven Groß IGPM, RWTH Aachen 30. Apr 2015 Sven Groß (IGPM, RWTH Aachen) C++ Teil 4 30. Apr 2015 1 / 16 Themen der letzten Vorlesung Funktionen: Definition und Aufruf Wert- und Referenzparameter,

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 26 Einstieg in die Informatik mit Java Methoden Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 26 1 Methoden 2 Methodendefinition 3 Parameterübergabe, Methodenaufruf

Mehr

Einführung in C. EDV1-04C-Einführung 1

Einführung in C. EDV1-04C-Einführung 1 Einführung in C 1 Helmut Erlenkötter C Programmieren von Anfang an Rowohlt Taschenbuch Verlag ISBN 3-4993 499-60074-9 19,90 DM http://www.erlenkoetter.de Walter Herglotz Das Einsteigerseminar C++ bhv Verlags

Mehr

C-Programmierung: Ausdrücke und Operatoren#Division.2F

C-Programmierung: Ausdrücke und Operatoren#Division.2F C-Programmierung: Ausdrücke und Operatoren#Division.2F http://de.wikibooks.org/wiki/c-programmierung:_ausdrücke_und_operatoren#division_.2f This Book Is Generated By Wb2PDF using RenderX XEP, XML to PDF

Mehr

Schleifenanweisungen

Schleifenanweisungen Schleifenanweisungen Bisher: sequentielle Abarbeitung von Befehlen (von oben nach unten) Nun: Befehle mehrfach ausführen (= Programmschleife): for-anweisung - wenn feststeht, wie oft z.b.: eine Berechnung

Mehr

Java - Schleifen. Bedingung. wiederhole. Anweisung Anweisung Anweisung. Leibniz Universität IT Services Anja Aue

Java - Schleifen. Bedingung. wiederhole. Anweisung Anweisung Anweisung. Leibniz Universität IT Services Anja Aue Java - Schleifen Bedingung wiederhole ja Anweisung Anweisung Anweisung Leibniz Universität IT Services Anja Aue Anweisung int zahl; zahl = 2; zahl = zahl * 10; int zahl; ; Jede Anweisung endet mit einem

Mehr

Programmieren in C. Eine Einführung in die Programmiersprache C. Prof. Dr. Nikolaus Wulff

Programmieren in C. Eine Einführung in die Programmiersprache C. Prof. Dr. Nikolaus Wulff Programmieren in C Eine Einführung in die Programmiersprache C Prof. Dr. Nikolaus Wulff Agenda Elementare Einführung C Programm Syntax Datentypen, Variablen und Konstanten Operatoren und Ausdrücke Kontrollstrukturen

Mehr

RO-Tutorien 15 und 16

RO-Tutorien 15 und 16 Tutorien zur Vorlesung Rechnerorganisation Tutorienwoche 2 am 04.05.2011 1 Christian A. Mandery: KIT Universität des Landes Baden-Württemberg und nationales Grossforschungszentrum in der Helmholtz-Gemeinschaft

Mehr

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 12/13. Kapitel 3. Grunddatentypen, Ausdrücke und Variable

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 12/13. Kapitel 3. Grunddatentypen, Ausdrücke und Variable 1 Kapitel 3 Grunddatentypen, Ausdrücke und Variable 2 Eine Datenstruktur besteht aus Grunddatentypen in Java einer Menge von Daten (Werten) charakteristischen Operationen Datenstrukturen werden mit einem

Mehr

Projekt 3 Variablen und Operatoren

Projekt 3 Variablen und Operatoren Projekt 3 Variablen und Operatoren Praktisch jedes Programm verarbeitet Daten. Um mit Daten programmieren zu können, muss es Möglichkeiten geben, die Daten in einem Programm zu verwalten und zu manipulieren.

Mehr

Operatoren für elementare Datentypen Bedingte Anweisungen Schleifen. Operatoren für elementare Datentypen Bedingte Anweisungen Schleifen

Operatoren für elementare Datentypen Bedingte Anweisungen Schleifen. Operatoren für elementare Datentypen Bedingte Anweisungen Schleifen Programmieren I Martin Schultheiß Hochschule Darmstadt Wintersemester 2011/2012 1 / 25 Operatoren für elementare Datentypen Bedingte Schleifen 2 / 25 Zuweisungsoperator Die Zuweisung von Werten an Variablen

Mehr

3.2 Datentypen und Methoden

3.2 Datentypen und Methoden Kap03.fm Seite 217 Dienstag, 7. September 2010 1:48 13 3.2 Datentypen und Methoden 217 3.2 Datentypen und Methoden Wie bei vielen höheren Programmiersprachen gibt es auch in Java einfache und strukturierte

Mehr

Vorlesung 6: Operatoren, Logische Ausdrücke

Vorlesung 6: Operatoren, Logische Ausdrücke Vorlesung 6: Operatoren, Logische Ausdrücke Inhalt Organisatorisches Offene Übungen Operatoren Logische Ausdrücke Dr. J. Raimann 1 Offene Übungen (durch Tutoren betreut) montags 1. und 2. Block (8.15 Uhr

Mehr

Funktionales C++ zum Ersten

Funktionales C++ zum Ersten Funktionales C++ zum Ersten WiMa-Praktikum 1, Teil C++, Tag 1 Christoph Ott, Büro: Helmholtzstr.18, E22 Tel.: 50-23575, Mail: christoph.ott@uni-ulm.de Institut für Angewandte Informationsverarbeitung 26.08.08

Mehr

Schachtelung der 2. Variante (Bedingungs-Kaskade): if (B1) A1 else if (B2) A2 else if (B3) A3 else if (B4) A4 else A

Schachtelung der 2. Variante (Bedingungs-Kaskade): if (B1) A1 else if (B2) A2 else if (B3) A3 else if (B4) A4 else A 2.4.6. Kontrollstrukturen if-anweisung: Bedingte Ausführung (Verzweigung) 2 Varianten: if (Bedingung) Anweisung (Anweisung = einzelne Anweisung oder Block) Bedeutung: die Anweisung wird nur ausgeführt,

Mehr

Operatoren und Ausdrücke

Operatoren und Ausdrücke Operatoren und Ausdrücke Zuweisungsoperator Arithmetische Operatoren Vergleichsoperatoren Logische Operatoren und Ausdrücke Implizite Typ-Umwandlung Rangordnung der Operatoren / Reihenfolge der Auswertung

Mehr

4. Einfache Programmstrukturen in C Einfache Programmstrukturen in C

4. Einfache Programmstrukturen in C Einfache Programmstrukturen in C Einfache Programmstrukturen in C 4-1 Welche einfache Programmstrukturen sind zu unterscheiden? Arithmetische und logische Ausdrücke und Zuweisungen Verzweigungen Unvollständige bedingte Anweisungen Vollständige

Mehr

Algorithmen & Programmierung. Ausdrücke & Operatoren (1)

Algorithmen & Programmierung. Ausdrücke & Operatoren (1) Algorithmen & Programmierung Ausdrücke & Operatoren (1) Ausdrücke Was ist ein Ausdruck? Literal Variable Funktionsaufruf Ausdruck, der durch Anwendung eines einstelligen (unären) Operators auf einen Ausdruck

Mehr

Grundlagen der Programmierung

Grundlagen der Programmierung Grundlagen der Programmierung 11. Vorlesung 14.06.2017 1 Schleifen 2 do...while do block while ( bedingung ); block: eine Anweisung oder Anweisungen in { block bed JA NEIN 3 while while ( bedingung ) block

Mehr

Die Programmiersprache C Eine Einführung

Die Programmiersprache C Eine Einführung Die Programmiersprache C Eine Einführung Christian Gentsch Fakutltät IV Technische Universität Berlin Projektlabor 2. Mai 2014 Inhaltsverzeichnis 1 Einführung Entstehungsgeschichte Verwendung 2 Objektorientiert

Mehr

Übungen zur Vorlesung Wissenschaftliches Rechnen I. Grundelemente von Java. Eine Anweisung. wird mit dem Wertzuweisungsoperator = geschrieben.

Übungen zur Vorlesung Wissenschaftliches Rechnen I. Grundelemente von Java. Eine Anweisung. wird mit dem Wertzuweisungsoperator = geschrieben. Eine Anweisung wird mit dem Wertzuweisungsoperator = geschrieben. Eine Anweisung wird mit dem Wertzuweisungsoperator = geschrieben. Daher ist y = x + 5.6; keine Gleichung, sondern die Anweisung den Wert

Mehr

4 Formelsammlung C/C++

4 Formelsammlung C/C++ 4 Formelsammlung C/C++ 4.1 Datentypen Datentyp stdint.h type Bits Sign Wertebereich (unsigned) char uint8_t 8 Unsigned 0.. 255 signed char int8_t 8 Signed -128.. 127 unsigned short uint16_t 16 Unsigned

Mehr

Grundlagen. Die Komponenten eines C Programms. Das erste Programm

Grundlagen. Die Komponenten eines C Programms. Das erste Programm Grundlagen 1. Die Komponenten eines C Programms 2. Ein Programm erzeugen und übersetzen 3. Variablen Deklarieren und Werte zuweisen 4. Zahlen eingeben mit der Tastatur 5. Arithmetische Ausdrücke und Berechnungen

Mehr

Wertebereich und Genauigkeit der Zahlendarstellung

Wertebereich und Genauigkeit der Zahlendarstellung Wertebereich und Genauigkeit der Zahlendarstellung Sowohl F als auch C kennen bei ganzen und Floating Point-Zahlen Datentypen verschiedener Genauigkeit. Bei ganzen Zahlen, die stets exakt dargestellt werden

Mehr

PIC16 Programmierung in HITECH-C

PIC16 Programmierung in HITECH-C PIC16 Programmierung in HITECH-C Operatoren: Arithmetische Operatoren - binäre Operatoren + Addition - Subtraktion * Multiplikation / Division % Modulo + - * / sind auf ganzzahlige und reelle Operanden

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 34 Einstieg in die Informatik mit Java Klassen mit Instanzmethoden Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 34 1 Definition von Klassen 2 Methoden 3 Methoden

Mehr

RO-Tutorien 3 / 6 / 12

RO-Tutorien 3 / 6 / 12 RO-Tutorien 3 / 6 / 12 Tutorien zur Vorlesung Rechnerorganisation Christian A. Mandery WOCHE 2 AM 06./07.05.2013 KIT Universität des Landes Baden-Württemberg und nationales Forschungszentrum in der Helmholtz-Gemeinschaft

Mehr

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C Einführung in die Programmiersprache C Marcel Arndt arndt@ins.uni-bonn.de Institut für Numerische Simulation Universität Bonn Der Anfang Ein einfaches Programm, das Hello World! ausgibt: #include

Mehr

Einführung in die Programmierung Wintersemester 2011/12

Einführung in die Programmierung Wintersemester 2011/12 Einführung in die Programmierung Wintersemester 2011/12 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund : Kontrollstrukturen Inhalt Wiederholungen - while

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java Vorlesung vom 25.4.07, Ausdrücke Übersicht 1 Die wichtigsten arithmetischen Ausdrücke Arithmetische Operatoren Inkrement und Dekrementoperatoren Zuweisungsoperator Mathematische Standardfunktionen Vergleichsoperatoren

Mehr

Kapitel 5. Datentypen und Operatoren

Kapitel 5. Datentypen und Operatoren Kapitel 5 Datentypen und Operatoren 1 Gliederung Kapitel 5 Datentypen und Operatoren 5.1 Elementare Datentypen 5.2 Symbolische Konstanten 5.3 Typumwandlungen 5.4 Operatoren 2 5.1. Elementare Datentypen

Mehr

Programmieren in C / C++ Grundlagen C 2

Programmieren in C / C++ Grundlagen C 2 Programmieren in C / C++ Grundlagen C 2 Hochschule Fulda FB AI Wintersemester 2016/17 http://c.rz.hs-fulda.de Peter Klingebiel, HS Fulda, FB AI Anweisung / Ausdruck 1 Programm setzt sich aus vielen Anweisungen

Mehr

Operatoren (1) Operatoren (2)

Operatoren (1) Operatoren (2) Operatoren (1) Binäre Operatoren + - * / % < = > & ^ > && Addition Subtraktion Multiplikation Division Divisionsrest Vergl. auf kleiner Vergl. auf kleiner oder gleich Vergl. auf gleich Vergl.

Mehr

Programmieren in C. Eine Einführung in die Programmiersprache C. Prof. Dr. Nikolaus Wulff

Programmieren in C. Eine Einführung in die Programmiersprache C. Prof. Dr. Nikolaus Wulff Programmieren in C Eine Einführung in die Programmiersprache C Prof. Dr. Nikolaus Wulff Textausgabe per printf Die Funktion printf ist kein Bestandteil der C Sprache sondern gehört zur C Bibliothek. printf

Mehr

Informatik I (D-MAVT)

Informatik I (D-MAVT) Informatik I (D-MAVT) Übungsstunde 2 simon.mayer@inf.ethz.ch Distributed Systems Group, ETH Zürich Ablauf Besprechung der Vorlesung Vorbesprechung Übung 2 Variablen + Scopes Zahlensysteme Bits&Bytes Datentypen

Mehr

F Zeiger, Felder und Strukturen in C

F Zeiger, Felder und Strukturen in C F Zeiger, Felder und Strukturen in C F Zeiger, Felder und Strukturen in C F.1 Zeiger(-Variablen) 1 Einordnung Konstante: Bezeichnung für einen Wert a 0110 0001 Variable: Bezeichnung eines Datenobjekts

Mehr

Teil 5: Zeiger, Felder, Zeichenketten Gliederung

Teil 5: Zeiger, Felder, Zeichenketten Gliederung Teil 5: Zeiger, Felder, Zeichenketten Gliederung Zeiger und Adressen Felder (Arrays) Zeichenketten (Strings) Zeigerarithmetik Mehrdimensionale Felder Zeiger und Adressen Felder Zeichenketten Zeigerarithmetik

Mehr

Kapitel 3. Grunddatentypen, Ausdrücke und Variable

Kapitel 3. Grunddatentypen, Ausdrücke und Variable Kapitel 3 Grunddatentypen, Ausdrücke und Variable Grunddatentypen, Ausdrücke und Variable 1 Eine Datenstruktur besteht aus Grunddatentypen in Java einer Menge von Daten (Werten) charakteristischen Operationen

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 29 Einstieg in die Informatik mit Java Weitere Ausdrücke Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 29 1 Überblick 2 Kombinierte Zuweisungsoperatoren 3 Vergleichsoperatoren

Mehr

JAVA-Datentypen und deren Wertebereich

JAVA-Datentypen und deren Wertebereich Folge 8 Variablen & Operatoren JAVA 8.1 Variablen JAVA nutzt zum Ablegen (Zwischenspeichern) von Daten Variablen. (Dies funktioniert wie beim Taschenrechner. Dort können Sie mit der Taste eine Zahl zwischenspeichern).

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java Vorlesung vom 18.4.07, Grundlagen Übersicht 1 Kommentare 2 Bezeichner für Klassen, Methoden, Variablen 3 White Space Zeichen 4 Wortsymbole 5 Interpunktionszeichen 6 Operatoren 7 import Anweisungen 8 Form

Mehr

Die Programmiersprache C

Die Programmiersprache C Die Programmiersprache C höhere Programmiersprache (mit einigen Assembler-ähnlichen Konstrukten) gut verständliche Kommandos muss von Compiler in maschinenlesbaren Code (Binärdatei) übersetzt werden universell,

Mehr

S. d. I.: Programieren in C Folie 4-1. im Gegensatz zu Pascal gibt es in C kein Schlüsselwort "then"

S. d. I.: Programieren in C Folie 4-1. im Gegensatz zu Pascal gibt es in C kein Schlüsselwort then S. d. I.: Programieren in C Folie 4-1 4 Anweisungen 4.1 if-anweisung 1) if (Ausdruck) 2) if (Ausdruck) } else im Gegensatz zu Pascal gibt es in C kein Schlüsselwort "then" es wird nur der numerische Wert

Mehr

C- Kurs 04 Anweisungen

C- Kurs 04 Anweisungen C- Kurs 04 Anweisungen Dipl.- Inf. Jörn Hoffmann jhoffmann@informa@k.uni- leipzig.de Universität Leipzig Ins@tut für Informa@k Technische Informa@k Ausdrücke Institut für Informatik Anweisungen C-Programm

Mehr

Variablen. CoMa-Übung VIII TU Berlin. CoMa-Übung VIII (TU Berlin) Variablen / 15

Variablen. CoMa-Übung VIII TU Berlin. CoMa-Übung VIII (TU Berlin) Variablen / 15 Variablen CoMa-Übung VIII TU Berlin 4.12.2013 CoMa-Übung VIII (TU Berlin) Variablen 4.12.2013 1 / 15 Themen der Übung 1 Typanpassungen 2 Operatoren 3 Variablen-Gültigkeit CoMa-Übung VIII (TU Berlin) Variablen

Mehr

Elementare Datentypen in C++

Elementare Datentypen in C++ Elementare Datentypen in C++ bool signed/unsigned char signed/unsigned short int signed/unsigned int signed/unsigned long int (signed/unsigned long long int) float double long double void enum char Der

Mehr

2. Programmierung in C

2. Programmierung in C 2. Programmierung in C Inhalt: Überblick über Programmiersprachen, Allgemeines zur Sprache C C: Basisdatentypen, Variablen, Konstanten Operatoren, Ausdrücke und Anweisungen Kontrollstrukturen (Steuerfluss)

Mehr

Kapitel 05. Datentypen. Fachgebiet Knowledge Engineering Prof. Dr. Johannes Fürnkranz

Kapitel 05. Datentypen. Fachgebiet Knowledge Engineering Prof. Dr. Johannes Fürnkranz Kapitel 05 Datentypen Inhalt des 5. Kapitels Datentypen 5.1 Einleitung 5.2 Eingebaute Datentypen Übersicht Die Datentypen char, float und double Standardwerte Operatoren Konversion / Type-Cast Datentyp

Mehr

Programmieren I. Kapitel 5. Kontrollfluss

Programmieren I. Kapitel 5. Kontrollfluss Programmieren I Kapitel 5. Kontrollfluss Kapitel 5: Kontrollfluss Ziel: Komplexere Berechnungen im Methodenrumpf Ausdrücke und Anweisungen Fallunterscheidungen (if, switch) Wiederholte Ausführung (for,

Mehr

Java: Eine kurze Einführung an Beispielen

Java: Eine kurze Einführung an Beispielen Java: Eine kurze Einführung an Beispielen Quellcode, javac und die JVM Der Quellcode eines einfachen Java-Programms besteht aus einer Datei mit dem Suffix.java. In einer solchen Datei wird eine Klasse

Mehr

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften Programmiertechnik Teil 4 C++ Funktionen: Prototypen Overloading Parameter C++ Funktionen: Eigenschaften Funktionen (Unterprogramme, Prozeduren) fassen Folgen von Anweisungen zusammen, die immer wieder

Mehr

Java Einführung VARIABLEN und DATENTYPEN Kapitel 2

Java Einführung VARIABLEN und DATENTYPEN Kapitel 2 Java Einführung VARIABLEN und DATENTYPEN Kapitel 2 Inhalt dieser Einheit Variablen (Sinn und Aufgabe) Bezeichner Datentypen, Deklaration und Operationen Typenumwandlung (implizit/explizit) 2 Variablen

Mehr

Outline. 1 Einleitung. 2 Einführung in C. 3 Fortgeschrittenes in C. 4 Einführung in Emacs Lisp. 5 Einführung in Prolog. 6 Formale Semantik

Outline. 1 Einleitung. 2 Einführung in C. 3 Fortgeschrittenes in C. 4 Einführung in Emacs Lisp. 5 Einführung in Prolog. 6 Formale Semantik Outline 1 Einleitung 2 Einführung in C 3 Fortgeschrittenes in C 4 Einführung in Emacs Lisp 5 Einführung in Prolog 6 Formale Semantik Imperative Programmierung Imperatives Paradigma Ziel: Ablaufbeschreibung

Mehr

Ursprünge. Die Syntax von Java. Das Wichtigste in Kürze. Konsequenzen. Weiteres Vorgehen. Rund um Java

Ursprünge. Die Syntax von Java. Das Wichtigste in Kürze. Konsequenzen. Weiteres Vorgehen. Rund um Java Ursprünge Die Syntax von Java Borland Software Corp 1995 Syntax: Pascal Objektorientierte Prorammierung optional Plattformen: Windows (Linux, Mac OS X) Sun Microsystems 1995 Syntax: C/C++ Objektorientiert

Mehr

Programmiersprachen Einführung in C

Programmiersprachen Einführung in C Programmiersprachen Einführung in C Teil 4: Prof. Dr. Jörg Schwenk Lehrstuhl für Netz- und Datensicherheit Gliederung Programmiersprachen 1. Von der Maschinensprache zu C 2. Die Struktur von C-Programmen

Mehr

Vorkurs C++ Programmierung

Vorkurs C++ Programmierung Vorkurs C++ Programmierung Funktionen Rückblick Operatoren logische Verknüpfungen Zusammengesetzte Operatoren ( Zuweisungsoperatoren ) Kontrollstrukturen Bedingte Anweisungen (if-abfrage, switch-konstrukt)

Mehr

Effektiv Programmieren in С und C++

Effektiv Programmieren in С und C++ Dietmar Herrmann Effektiv Programmieren in С und C++ Eine Einführung mit Beispielen aus Mathematik, Naturwissenschaft und Technik 3., vollständig überarbeitete und erweiterte Auflage 3 vieweg flffm \;i

Mehr

Kapitel 3: Variablen

Kapitel 3: Variablen Kapitel 3: Variablen Thema: Programmieren Seite: 1 Kapitel 3: Variablen Im letzten Kapitel haben wir gelernt, bestimmte Ereignisse zu wiederholen solange eine Bedingung erfüllt ist. Nun möchten wir aber

Mehr

Microcontroller Praktikum SS2010 Dipl. Ing. R. Reisch

Microcontroller Praktikum SS2010 Dipl. Ing. R. Reisch Microcontroller Praktikum SS2010 Dipl. Ing. R. Reisch Die wichtigsten Unterlagen/Tools Für das Praktikum Unterlagen/Kenntnisse/Tools wichtig: Datenblatt des AT80USB1287 µc Schaltplan des im Praktikum verwendeten

Mehr

L6. Operatoren und Ausdrücke

L6. Operatoren und Ausdrücke L6. Operatoren und Ausdrücke 1. Arithmetische Operatoren: +, -, *, /, %, --, ++ 2. Zuweisung-Operatoren: =, +=, -=, *=, /= 3. Vergleichsoperatoren: =, ==,!= 4. Logische Operatoren:!, &&, 5.

Mehr

Informationsverarbeitung im Bauwesen

Informationsverarbeitung im Bauwesen 1/24 Informationsverarbeitung im Bauwesen Einführung in das Programmieren mit C++ Markus Uhlmann Institut für Hydromechanik Karlsruher Institut für Technologie www.ifh.kit.edu WS 2010/2011 Vorlesung 7

Mehr

Einführung in den Einsatz von Objekt-Orientierung mit C++ I

Einführung in den Einsatz von Objekt-Orientierung mit C++ I Einführung in den Einsatz von Objekt-Orientierung mit C++ I ADV-Seminar Leiter: Mag. Michael Hahsler Syntax von C++ Grundlagen Übersetzung Formale Syntaxüberprüfung Ausgabe/Eingabe Funktion main() Variablen

Mehr

1. Referenzdatentypen: Felder und Strings. Referenz- vs. einfache Datentypen. Rückblick: Einfache Datentypen (1) 4711 r

1. Referenzdatentypen: Felder und Strings. Referenz- vs. einfache Datentypen. Rückblick: Einfache Datentypen (1) 4711 r 1. Felder und Strings Eigenschaften von Referenzdatentypen 1. Referenzdatentypen: Felder und Strings Referenzdatentypen sind Konstrukte, mit deren Hilfe wir aus einfachen Datentypen neue eigene Typen erzeugen

Mehr

1. Referenzdatentypen: Felder und Strings

1. Referenzdatentypen: Felder und Strings 1. Felder und Strings Eigenschaften von Referenzdatentypen 1. Referenzdatentypen: Felder und Strings Referenzdatentypen sind Konstrukte, mit deren Hilfe wir aus einfachen Datentypen neue eigene Typen erzeugen

Mehr