Praktische Einführung in C++ - EAH-Jena
Transcription
Praktische Einführung in C++ - EAH-Jena
Alfred H. Gitter Praktische Einführung in C++ unter Verwendung des GNU C++ Compilers für Linux einschliesslich einfacher Beispiele aus der Bioinformatik ( in der Fassung vom 23. August 2011 ) Prof. Dr. Alfred H. Gitter (2006 - 2011) 1 Inhalt Linux – Kurzeinführung 3 C++ – Kurzeinführung 6 C++ - Praktikum 21 Prof. Dr. Alfred H. Gitter (2006 - 2011) 2 Linux - Kurzeinführung 1 Dateien Eine Datenmenge auf einem Speichermedium, die als logische Einheit betrachtet wird, heißt Datei (file) und erhält einen eindeutigen Namen (filename), bei dem Groß- und Kleinschreibung unterschieden werden. Ein Dateiname kann aus bis zu 255 Zeichen bestehen, wobei Sonderzeichen und Leerzeichen vermieden werden sollen, da es damit Schwierigkeiten geben kann. Es gibt (im Gegensatz zu Microsoft Windows) kein System für bestimmte Datei-Endungen und der Punkt (.) kann beliebig oft im Namen vorkommen. Dateien, deren Name mit einem Punkt beginnt, sind versteckte Dateien, die beim einfachen Auflisten der Dateien eines Verzeichnisses (mit dem Befehl ls als Eingabe in einer Shell, siehe unten) nicht angezeigt werden. Dateien sind, gemäß einer internationalen Vereinbarung (Filesystem Hierarchy Standard = FHS) in einem hierarchischen System (in einer Baumstruktur) von Verzeichnissen (directories) geordnet. Das erste Verzeichnis (directory), die Wurzel des Verzeichnisbaums, nennt man root ( / ). In Linux-Systemen findet man als Unterverzeichnisse unter anderem boot (mit Dateien, die für den Start des Betriebssystems gebraucht werden), bin (mit Dateien des Betriebssystemkerns, die ausführbare Programme, also mögliche Prozesse, enthalten), etc (mit Konfigurationsdateien; das sind Dateien, die Einstellungen und Werte für wichtige Programme speichern), root (mit den Dateien, die dem Systemadministrator zugeordnet sind), und usr (mit Dateien, die wichtige ausführbare Programme enthalten, die aber nicht dem Betriebssystemkern zugeordnet sind). Leider ist "root" mehrdeutig. Es bezeichnet a) die Wurzel des Verzeichnisbaums, b) den Systemadministrator und c) das Verzeichnis, das dem Systemadministrator zugeordnet ist. 2 Programme Programme sind eine Sammlung von Befehlen an den Rechner, die in einer oder mehreren Dateien gespeichert sind. Ein Programm das in den Arbeitsspeicher geladen wurde und vom Rechner ausgeführt wird, heisst Prozess (Task). In einem Mehrbenutzersystem (multiuser system) wie Linux werden mehrere Prozesse gleichzeitig ausgeführt. Die Prozesssteuerung durch das Betriebssystem startet und beendet Prozesse und verteilt den Arbeitsspeicherplatz sowie die Rechenzeit des Prozessors. Am Anfang, wenn das Betriebssystem geladen wird, startet es den init-Prozess. Ein Prozess kann neue Prozesse starten. Nach dem Start des Betriebssystems wird jedem neuen Prozess eine eindeutige Identifikationsnummer, die Process ID (PID = process identifier), zugeteilt. Einen Überblick über die laufenden Prozesse erhält man, wenn man in die bash (siehe unten) den Befehl pstree eingibt. Alle Dateien und Prozesse sind einem Nutzer zugeordnet. Für die Ein- und Ausgabe vom und an den Benutzer gibt es Standardkanäle, die Standardeingabe (0, Tastatur), die Standardausgabe (1, meistens der Bildschirm) und die Standardfehlerausgabe (2, Bildschirm). Prof. Dr. Alfred H. Gitter (2006 - 2011) 3 Daemonen sind Programme, die im Hintergrund ohne direkte Kommunikation mit dem Nutzer ablaufen und warten, bis ein anderes Programm einen Auftrag an sie erteilt. Ein Beispiel ist der Druckerdaemon (printer daemon) lpd, der (zum Beispiel von einem Textverarbeitungsprogramm) Druckaufträge entgegennimmt (auch wenn der Drucker noch ausgechaltet ist) und speichert, bis er sie drucken kann. Daemonen (die etwa den Diensten oder services bei Microsoft-Windows entsprechen) werden meistens vom Betriebssystem beim Systemstart geladen und verbrauchen auch beim Warten etwas von der Rechnerleistung. Die Shell (Kommandozeileninterpreter) ist ein Programm, das Befehle des Benutzers (User) in Textform (ohne "Herumklicken" in einem zweidimensionalen Fenster) über die Tastatur annimmt, aufbereitet und an den Kern des Betriebssystems (Kernel) weitergibt. Es gibt verschiedene Shell-Programme für Linux, zum Beispiel die bash (Bourne-again-shell, benannt nach Erfinder der Vorversion bsh, Stephen Bourne). In grafischen Benutzeroberflächen wird für die Shell ein Fenster auf dem Bildschirm geöffnet, Terminal genannt. (Im Betriebssystem Windows gibt es eine Entsprechung, die "Eingabeaufforderung") 3 Arbeiten mit der bash Die bash speichert die eingebenen Befehlszeilen und man kann eine alte Befehlszeile aus diesem Speicher abrufen (indem man auf den "Pfeil nach oben" der Tastatur drückt), statt sie nochmals einzutippen. Eine Befehlszeile wird erst dann abgearbeitet wenn die "Return"-Taste ("Enter") auf der Tastatur gedrückt wurde. Vorher kann man sich in der Befehlszeile mit den Tasten "Pfeil nach links" und "Pfeil nach rechts" bewegen und die Befehlszeile ändern. Mit der "Backspace"-Taste ("großer Pfeil nach links") kann man das Zeichen links vom Zeiger (Cursor) wieder löschen. Es gibt die Möglichkeit, einfache Zeichenkettenmuster (Reguläre Ausdrücke) zu bilden, die für verschiedene Zeichenketten stehen können. Manche Zeichenkettenmuster können durch ein einzelnes Zeichen (Metazeichen) dargestellt werden; zum Beispiel ? für genau ein beliebiges Zeichen, * für beliebig viele (auch 0) beliebige Zeichen. Ein weiteres Zeichenkettenmuster ist [abc] (oder entsprechend andere Zeichen in eckigen Klammern), was für genau eines der Zeichen steht, die in den eckigen Klammern aufgeführt sind (a oder b oder c). $USER steht für den Namen des Benutzers (user name), der die bash benutzt. Normalerweise erfolgen Ein- und Ausgaben der bash über die Standardkanäle, aber mit < dateiname beziehungsweise > dateiname wird stattdessen aus einer Datei gelesen beziehungsweise in eine Datei geschrieben. Bei manchen Befehlen, die man (in diesem Zusammenhang) Filter nennt, kann man die Ausgabedaten als Eingabedaten eines zweiten Befehls verwenden. Eine solche Verknüpfung heisst pipe und wird durch das Sonderzeichen | dargestellt. Einige wichtige Befehle der bash Shell (gleich bei anderen Shells) sind: date zeigt Datum und Uhrzeit (gemäß der internen Uhr des Rechners) df -m zeigt den freien und belegten Speicherplatz auf den Festplatten free zeigt freien und belegten Speicherplatz im Arbeitsspeicher (Mem) pwd gibt den Namen des aktuellen Verzeichnisses am Bildschirm aus cd uv wechselt in das Unterverzeichnis uv des aktuellen Verzeichnisses cd .. wechselt in das Verzeichnis, das über dem aktuellen Vezeichnis liegt ls zeigt eine einfache Liste der Dateien im aktuellen Verzeichnis Prof. Dr. Alfred H. Gitter (2006 - 2011) 4 ls -al zeigt eine ausführliche Liste einschließlich der versteckten Dateien more dat zeigt den Inhalt der Datei dat seitenweise an (ähnlich: cat, less) cp alt neu kopiert, wenn neu ein Verzeichnis ist, Datei alt in Verzeichnis neu cp alt neu kopiert, wenn neu kein Verzeichnis ist, Datei alt in die neue Datei neu rm -i dat löscht, wenn man dazu berechtigt ist, Datei oder Verzeichnis dat gzip -v dat komprimiert die einzelne Datei dat in Datei dat.gz (neuer ist bzip2) gzip -d dat.gz dekomprimiert eine einzelne Datei dat.gz in Datei dat (= gunzip) ./prog startet das im aktuellen Verzeichnis gespeicherte Programm prog grep -w qms d.txt sucht das Wort qms (-w: vor und nachher eine Lücke) in Datei d.txt grep -i qms ord/* sucht Zeichenfolge qms (-i: groß oder kleingeschrieben) im Ordner ord make führt, in der besonderen Datei makefile gespeicherte, Anweisungen aus 4 Compiler (Übersetzungsprogramme) für C oder C++ Die Programmiersprache C ist die wichtigste Grundlage für die Erschaffung des Linux-Betriebssystems. Linux enthält als C-Compiler, zur Übersetzung von C-Quellcode in Maschinensprache, das Programm gcc, das im Terminal (Konsole) gestartet wird. Die Erweiterung von C zu einer objektorientierten Programmiersprache ergab C++. Der entsprechende Linux-Compiler ist g++. Im folgenden soll gezeigt werden, wie g++ aufgerufen wird. (Entsprechendes gilt für gcc.) Der Befehl g++ p_01.cpp –o p_01 weist den Compiler an, das C++-Programm p_01.cpp zu übersetzen und (sofern das ohne Fehler möglich war) in der ausführbaren Maschinensprache-Datei p_01 zu spechern. Mit dem Befehl ./p_01 wird p_01 , die sich im aktuellen Verzeichnis befindet (was durch " ./ " mitgeteilt wird), ausgeführt. Wenn man beim Aufruf von g++ (oder von gcc für C statt C++) die Option -ggdb verwendet, werden dem übersetzten Maschinensprache-Programmcode Informationen für den gdb-Debugger hinzugefügt. Der gdb-Debugger ist ein zusätzliches Programm, das beim Testen und bei der Suche nach logischen Fehlern im Programmcode helfen kann. Die Option -Wall veranlasst den Compiler, alle ihm möglichen Warnhinweise auszugeben, wenn das Programm zwar formal fehlerfrei übersetzt werden kann, es aber wahrscheinlich logische Fehler enthält. Damit lautet der Übersetzungsbefehl beispielsweise g++ -Wall -ggdb p_01.cpp -o p_01 Der Compiler besteht aus drei Teilen: Präprozessor, Compiler (im engeren Sinne) und Linker. Wenn man das Ergebnis des ersten Teils der Übersetzung von der Datei p_01.cpp in eine Datei p_01.pre speichern will, gibt man den Befehl g++ -E p_01.cpp > p_01.pre Die erzeugte Datei p_01.pre kann danach mit einem Editor-Programm angesehen werden. Wenn mehrere Quelldateien (und mehrere Header-Dateien) zu einer ausführbaren MaschinenspracheDatei übersetzt werden sollen, wird der Aufruf des Compilers und die Berücksichtigung von Abhängigkeiten der verschiedenen Quellprogramme durch Benutzung des Programms make erleichtert. Die Anweisungen für das Programm make werden vom Programmierer in der Datei Makefile gespeichert. Prof. Dr. Alfred H. Gitter (2006 - 2011) 5 C++ - Kurzeinführung 1 Programmiersprache (C++) - Symbolvorrat = Menge der lexikalischen Elemente - Syntax = Regeln für Symbolfolgen - Semantik = Bedeutung (was tut das Programm ?) 2 Lexikalische Elemente = kleinste Einheiten, aus denen sich ein C++-Programm zusammensetzt - 2.1 Bezeichner Schlüsselwörter Literalkonstanten Operatoren Trenner Bezeichner sind Namen, die der Programmierer für Variablen, Funktionen u.s.w. wählt - beliebig lang, alle Zeichen signifikant erstes Zeichen: Buchstabe ( oder Unterstrich _ ) Buchstaben, Ziffern, Unterstrich _ Groß- und Kleinschreibung wird unterschieden Beispiel: 2.2 flaeche als Variablenname Schlüsselwörter sind von C++ vorgegebene Namen zum Bilden eines Programmbefehls Beispiele: class if int u.s.w. dazu gehören auch die Interpunktionszeichen von C++ Beispiele: 2.3 ; { } u.s.w. Literalkonstanten beschreiben einen bestimmten, festen Wert und werden nach Datentypen geordnet Beispiele: 43 eine ganzzahlige Konstante "Sinn des Lebens" eine Zeichenkette (string) Prof. Dr. Alfred H. Gitter (2006 - 2011) 6 2.4 Operatoren sind von C++ vorgegebene Symbole, die eine Funktion auf ihren Argumenten, den Operanden, ausführen Beispiele: + [ ] <= u.s.w. Ein Ausdruck ist eine Kombination von Operatoren und Operanden, die ausgewertet werden kann. Beispiel: 2.5 4*3 ergibt 12 Trenner trennen die übrigen lexikalischen Einheiten Leerzeichen (Lücke, blank) Tabulatoren Zeilenendezeichen, Seitenvorschübe white spaces Kommentare - Zeilenkommentar // ... bis Zeilenende - Kommentarblock /* Anfang des Kommentars . . . Ende des Kommentars */ 3 Variablen Eine Variable muss deklariert (dem Compiler bekann gemacht) werden, bevor sie benutzt wird: Datentyp Variablenname ; Eine ganzzahlige Variable (englisch: integer) könnte wie folgt mit dem Namen x deklariert werden: int x ; Man kann gleich mit Hilfe des Zuweisungsoperators = einen Wert zuweisen (Initialisierung): int x = 17 ; 3.1 Elementare Datentypen ( Ganzzahl- und Gleitpunktdatentypen ) Datentyp Bits min. Wert max. Wert char 8 -128 127 unsigned char 8 short int 16 -32768 32767 unsigned short int 16 int 16 / 32 unsigned int 16 long int 32 / 64 unsigned long int 32 Genauigkeit float 32 6 Ziffern double 64 10 Ziffern long double 80 double Prof. Dr. Alfred H. Gitter (2006 - 2011) (Zeichen) 7 Variablen eines bestimmen Datentyps können nur einen bestimmeten Wertebereich speichern. Der Progammierer muss darauf achten, dass der Wertebereich nicht überschritten wird, da es sonst – meistens unbeabsichtigt – zu merkwürdigen Effekten kommt. 3.2 Übungsaufgabe Angenommen, eine ganzzahlige Variable ( int ) wird durch 32 bits dargestellt. Welche Ausgabe erzeugt das folgende Programm ? #include <iostream> #include <limits.h> using namespace std; int main() { int i = INT_MAX; cout << sizeof(int) << " bytes benoetigt eine int - Variable" << endl ; cout << i << " ist der hoechste Wert einer int - Variablen" << endl ; if ( (i+1) == INT_MIN ) cout << i+1 << " ist der niedrigste Wert einer int - Variablen" << endl ; else cout << "Die kuerzesten Irrtuemer sind immer die besten. (Moliere)" << endl ; return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 8 4 Operatoren (Auswahl) In folgender Liste weiter oben stehende Operatoren werden in einem Ausdruck zuerst ausgewertet. Operator Beschreibung Beispiel Abarbeitung () Gruppierung (x + y) / 2; von links nach rechts [] Zugriff auf Array-Element Werteliste[3] = 7.45; von links nach rechts . Zugriff auf Element eines Objekts obj.alter = 66; von links nach rechts ++ nachträgliches Inkrement for ( i = 1; i <= 10; i++ ) ... von links nach rechts -- nachträgliches Dekrement for ( i = 10; i >= 1; i-- ) ... von links nach rechts ! logische Negation if( !wahr ) ... von rechts nach links ++ vorheriges Inkrement n = ++n; von rechts nach links -- vorheriges Dekrement n = --n; von rechts nach links - negatives Vorzeichen int n = -10; von rechts nach links < Vergleich: kleiner als if( i < 13 ) ... von links nach rechts <= Vergleich: kleiner oder gleich if( i <= 13 ) ... von links nach rechts > Vergleich: größer als if( i > 13 ) ... von links nach rechts >= Vergleich: größer oder gleich if( i >= 13 ) ... von links nach rechts == Vergleich: gleich if( i == 13 ) ... von links nach rechts != Vergleich: ungleich if( i != 13 ) ... von links nach rechts && logische UND-Verknüpfung if( n > 0 && n != 13 ) ... von links nach rechts || logische ODER-Verknüpfung if( n < 0 || n > 10 ) ... von links nach rechts = Zuweisung int n = 13; von rechts nach links += Addition und Zuweisung i += 2; von rechts nach links -= Subtraktion und Zuweisung i -= 2; von rechts nach links , aufeinander folgende Abarbeitung for( i=0, j=0; i < 10; i++, j++ ) ... von links nach rechts Prof. Dr. Alfred H. Gitter (2006 - 2011) 9 5 Ein C++ - Programm 5.1 Quell-Code Folgendes Programm gibt das Quadrat einer eingegebenen ganzen Zahl auf dem Bildschirm aus: // Programmname // einzeiliger Kommentar (siehe Abschnitt 2.5) # include <iostream> // Direktive (Anweisung an Präprozessor, siehe unten) using namespace std; // Standard-Namensbereich (wird erst später erklärt) // Jedes C- oder C++-Programm enthält eine Funktion main() mit zugehörigem Anweisungsblock. int main() // Die Funktion main() hat hier den Rückgabetyp int . { // Beginn des Anweisungsblocks der Funktion main() . int rein; // Die Variable rein vom Typ int wird definiert. int raus; // Anweisungen enden mit einem Semikolon ; . cout << "Geben Sie eine Zahl ein: "; // << und >> sind Umlenkungsoperatoren. Die Tasta- cin >> rein; // tureingabe wird zur Variablen rein gelenkt. raus = rein * rein; // Der Variablen raus wird ein Wert zugewiesen. cout << "Das Quadrat von " << rein << "ist " << raus << endl; // cout ist das Ausgabeobjekt. // endl ist der Zeilenumbruch-Manipulator (end of line) return 0; // Die Funktion main() liefert hier den Wert 0 zurück. } // Ende des Anweisungsblocks der Funktion main() . 5.2 Programm erstellen und ausführen Editor öffnen (OHNE Formatierungsinformationen) Quelltext eintippen und C++-Datei speichern mit Endung .cpp (oder C-Datei mit der Endung .c) Quellprogramm in Maschinensprache übersetzen Die Übersetzung erfolgt mit folgenden Programmen (oder Programmteilen des erweiterten Compilers): - Präprozessor (Aufbereitung des Quelltextes) (Direktiven = Befehle an PP beginnen mit #) - Compiler (Übersetzung in Maschinensprache) (speichert in eine sogenannte Objektdatei) - Linker (Zusammenbindung mehrerer Objektdateien) (speichert in ausführbare Datei, z.B. mit Endung .exe) Programm ausführen Größere Programme bestehen aus mehreren Quelltextdateien, die nach einzelner Compilierung vom Linker zusammengebunden werden. Außerdem werden Standardaufgaben von "Klassen" erledigt, die mit dem Compiler geliefert werden und bereits compiliert als Objektdateien vorliegen. Die mitgelieferten Objektdateien werden gruppenweise zusammengefasst und eine solche Gruppe von Objektdateien heißt Bibliothek (library). Mit Hilfe der #include-Direktive wird der Inhalt einer Datei in den Programm-Quelltext kopiert, die Compiler und Linker Information darüber geben, welche Objektdateien aus den mitgelieferten Bibliotheken von unserem Programm gebraucht werden. In obigem Beispiel werden Objektdateien mit "Klassen" für Tatstatureingabe ( über cin ) und Bildschirmausgabe ( über cout ) gebraucht. Prof. Dr. Alfred H. Gitter (2006 - 2011) 10 6 Ein- und Ausgabe 6.1 Spaltenweise Formatierung einer Textausgabe Folgendes Programm gibt ein Beispiel für die Verwendung von Manipulatoren zur Ausgabeformatierung: // Formatierung der Textausgabe #include <iostream> #include <string> #include <iomanip> using namespace std; void aus(int b) { string vorname = "Angela", nachname = "Merkel", beruf = "Kanzlerin"; cout << setw(b) << vorname // für jedes Ausgabefeld die Breite setzen << setw(b) << nachname; // für jedes Ausgabefeld die Breite setzen cout.width(b); cout<< beruf << endl; // Alternative zum Setzen der Ausgabebreite // neue Zeile, alles ausgeben und Ausgabepuffer leeren } int main() { int b = 20; // die Variable b enthält die Textbreite (zunächst 20) cout << right; // im Folgenden wird stets rechtsbündig geschrieben aus(b); cout << left // im Folgenden wird stets linksbündig geschrieben << setfill('.'); // im Folgenden werden Lücken durch . ersetzt aus(b); cout << endl; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 11 7 Kontrollstrukturen 7.1 Abfrage und Boolsche Ausdrücke Folgendes Programm führt eine Division nur aus, wenn der Nenner ungleich Null ist: // if und else # include <iostream> using namespace std; int main() { float zaehler = 3.0, nenner =2.0; // Variablen (Typ float ) werden definiert und initialisiert. float ergebnis; // Eine Variable vom Typ float wird definiert. if (nenner == 0) // Schlüsselwort if , gefolgt von Bedingung in Klammern { // Beginn des Anweisungsblocks für Bedingung = true cout << "Das geht nicht ! "; } // Ausgabe eines Strings auf dem Bildschirm. // Ende des Anweisungsblocks für Bedingung = true else { // Beginn des Anweisungsblocks für Bedingung = false ergebnis = zaehler / nenner; } // Zuweisung eines berechneten Werts an ergebnis . // Ende des Anweisungsblocks für Bedingung = false return 0; } Achtung: == ist der Vergleichsoperator , aber = ist der Zuweisungsoperator UND – Verknüpfung von logischen Ausdrücken: if ( i >= -1 && i != 0 ) // Wenn i größer oder gleich –1, aber ungleich Null ist, // dann ist die Bedingung wahr ( true ) ODER – Verknüpfung von logischen Ausdrücken: if ( i == 1 || i == 2 ) // Wenn i gleich 1 oder 2 ist, dann ist die Bedingung wahr ( true ) NEGATION eines logischen Ausdrucks: if ( ! ( i == 1 || i == 2) ) // Wenn i weder 1 noch 2 ist, dann ist die Bedingung wahr ( true ) Prof. Dr. Alfred H. Gitter (2006 - 2011) 12 7.2 while-Schleife Folgendes Programm führt eine Division nur aus, wenn der Nenner ungleich Null ist: // while-Schleife # include <iostream> using namespace std; int main() { int eingabe = 0; // Die Schleifenvariable eingabe wird initialisiert. while ( eingabe == 0 ) // Der folgende Block (Schleifenkörper) wird ausge// führt, solange die Schleifenbedingung = true bleibt. { // Beginn des Anweisungsblocks für die Schleife cout << "Eingabe (ungleich 0): "; // Ausgabe der Aufforderung zur Eingabe einer Zahl cin >> eingabe; // Einlesen einer Zahl von der Tastatur } // Ende des Anweisungsblocks für die Schleife cout << eingabe << endl; // Ausgabe der eingegebenen Zahl auf dem Bildschirm. return 0; } 7.3 do...while-Schleife Folgendes Programm gibt mindestens einmal "Hallo Welt !" aus: // do...while-Schleife # include <iostream> using namespace std; int main() { int eingabe = 0; // Die Schleifenvariable eingabe wird initialisiert. cout << "Wiederholungen: "; // Ausgabe der Aufforderung zur Eingabe einer Zahl cin >> eingabe; // Einlesen einer Zahl von der Tastatur cout << endl << endl; do // Folgender Block wird mindestens einmal ausgeführt. { // Beginn des Anweisungsblocks für die Schleife cout << "Hallo Welt ! " << endl; // Ausgabe von "Hallo Welt !" eingabe = eingabe –1; // Dekrementierung der Schleifenvariablen } // Ende des Anweisungsblocks für die Schleife while (eingabe >= 0) ; // Schleifenende, wenn die Schleifenvariable eine Schwelle unterschritt. cout << endl; return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 13 7.4 for-Schleife Folgendes Programm führt eine Division nur aus, wenn der Nenner ungleich Null ist: // for-Schleife # include <iostream> using namespace std; Initialisierung der Schleifenvariablen int main() Inkrementierung der Schleifenvariablen { int i; // Die Schleifenvariable i definiert. for ( i=1 ; i <=10 ; ++i ) // Der folgende Block (Schleifenkörper) wird ausge// führt, solange die Schleifenbedingung = true bleibt. { // Beginn des Anweisungsblocks für die Schleife cout << "i ist " << i << endl ; } // Bildschirmausgabe der Zahlen von 1 bis 10 // Ende des Anweisungsblocks für die Schleife return 0; } Hinweis: ++i ist eine Kurzform von i = i + 1 . Eine mit for, while oder do while gebildete Schleife nennt man auch Iteration (zum Beispiel zur Abgrenzung von der Rekursion, siehe unten). Prof. Dr. Alfred H. Gitter (2006 - 2011) 14 8 Übungsaufgaben 8.1 Welche Ausgabe erzeugt das folgende Programm ? # include <iostream> using namespace std; int main() { int a, b; a=3; b=9; if (a >= 3 && b <= 0) { cout << "Fall 1" << endl ; } else { if ( a == 3 || b != 9 ) cout << "Fall 2" << endl ; else cout << "Fall 3" << endl ; } return 0; } 8.2 Welche Ausgabe erzeugt das folgende Programm ? # include <iostream> using namespace std; int main() { int a = 0, b; double x = 18.8, y = 3.0; if ( a = 1 ) { b=x/y; cout << "b ist " << b << endl ; } else { cout << "Hallo" << endl ; } return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 15 8.3 Welche Ausgabe erzeugt das folgende Programm ? // while-Schleife # include <iostream> using namespace std; int main() { int a = 1; while ( a != 4 ) ; { cout << "a ist " << a << endl; a = a + 1; } return 0; } 8.4 Welche Ausgabe erzeugt das folgende Programm ? // for-Schleife # include <iostream> using namespace std; int main() { int i; double n = 0.0 ; bool b ; b = false; if ( !b ) b = true ; for ( i=1 ; i <=10 ; ++i ) { if ( b ) { n = n + i; } } n = int(n) % 10; cout << "n ist " << n << endl ; cout << "i ist " << i << endl ; cout << "b ist " << b << endl ; return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 16 9 Arrays Ein Array ist eine Zusammenfassung von Daten des gleichen Typs unter einem Namen. Folgendes Programm erzeugt ein Array, weist den Elementen Werte zu und gibt sie dann am Bildschirm aus: // Array # include <iostream> using namespace std; int main() { int i; int liste[ 3 ] ; // Definition eines Arrays für 3 Zahlen vom Typ int . liste[ 0 ] = 3 ; // Zuweisung eines Werts an ein Element des Arrays. liste[ 1 ] = 7 ; // Zuweisung eines Werts an ein Element des Arrays. liste[ 2 ] = 5 ; // Zuweisung eines Werts an ein Element des Arrays. for ( i=0 ; i <= 2 ; ++i ) // Der folgende Block wird für i = 0, 1 und 2 ausgeführt. { // Beginn des Anweisungsblocks für die Schleife cout << liste[ i ] << endl ; } // Bildschirmausgabe der 3 Elemente des Arrays // Ende des Anweisungsblocks für die Schleife return 0; } 3 7 5 Achtung: Ein Array liste mit drei Elementen hat die Elemente liste[ 0 ] , liste[ 1 ] und liste[ 2 ] . 10 Funktionen Eine Funktion ist eine Zusammenfassung von Anweisungen unter einem Namen. Funktionen werden außerhalb der main-Funktion definiert und innerhalb der main-Funktion aufgerufen: // Einfache Funktion # include <iostream> using namespace std; void gruss() // Definition der Funktion gruss() ohne Rückgabewert. Vor dem Namen steht { // der Typ des Rückgabewerts bzw. void, wenn es keinen Rückgabewert gibt. cout << "Hallo" << endl ; // Bildschirmausgabe eines Grußes } int main() { gruss() ; // erster Aufruf der Funktion gruss() gruss() ; // zweiter Aufruf der Funktion gruss() return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 17 10.1 Funktion mit Parameter // Funktion mit Parameter # include <iostream> using namespace std; void gruss(int n) // Definition der Funktion gruss() mit Parameter n aber ohne Rückgabewert. // Der Parameter n ist eine lokale (nur in dieser Funktion gültigen) Variable. { int i; // Definition der lokalen (nur in dieser Funktion gültigen) Variablen i . for ( i=1 ; i <= n ; ++i ) { cout << "Hallo" << endl ; } // Bildschirmausgabe des Grußes } int main() { gruss(3) ; // Aufruf der Funktion gruss() return 0; } 10.2 Funktion mit Parameter und Rückgabewert // Funktion mit Parameter und Rückgabewert # include <iostream> using namespace std; int quadrat(int n) // Definition einer Funktion mit Parameter n und Rückgabewert vom Typ int. // Der Parameter n ist eine lokale (nur in dieser Funktion gültige) Variable. { return n*n ; // Rückgabe eines Werts vom Typ int } int main() { int nn; // Definition einer nur in main() gültigen lokalen Variablen nn . nn = quadrat (3) ; // Aufruf der Funktion quadrat() cout << "3 Quadrat ist " <<nn << endl ; return 0; } Zur Funktionsdefinition gehört - Rückgabetyp (ein beliebiger Typ außer einem Array, ohne Rückgabe Typ void ) - Funktionsname - (durch Kommata getrennte) Parameterliste in runden Klammern - Funktionsrumpf in geschweiften Klammern (Anweisungsblock) Prof. Dr. Alfred H. Gitter (2006 - 2011) 18 10.3 Überladung Funktion werden anhand ihres Namens und ihrer Parameterliste unterschieden. Daher kann es verschiedene Funktionen mit gleichem Namen geben. Im folgenden Beispiel wird eine Funktion definiert, die zwei ganze Zahlen (Typ int) als Argument enthält, und das Minimum (den kleinsten Wert der übergebenen Argumente) zurückgibt. Außerdem wird eine Funktion gleichen Namens definiert, die drei ganze Zahlen als Argument enthält, und das Minimum zurückgibt. Da die Funktionen im wesentliche die gleiche Aufgabe haben, ist es schön, wenn sie den gleichen Namen haben. Es sind aber zwei verschiedene, getrennt definierte Funktionen. // Überladung einer Funktion # include <iostream> using namespace std; int min(int a, int b) // Definition einer Funktion mit zwei Argumenten in der Parameterliste { if( a<b) return a; // Rückgabe von a, wenn es kleiner als b ist, return b; // sonst Rückgabe von b } int min(int a, int b, int c) // Definition einer Funktion mit drei Argumenten in der Parameterliste { int d = min(a,b); // Zwischenspeicherung des Minimums von a und b in d return min(c,d); // Rückgabe des Minimums von c und d } int main() { cout << min(2,5) << endl; // Aufruf der Funktion min() mit 2 Parametern cout << min(3,6,8) << endl; // Aufruf der Funktion min() mit 3 Parametern return 0; } Obiges Programm erzeugt folgende Bildschirmausgabe: 2 3 Press any key to continue Prof. Dr. Alfred H. Gitter (2006 - 2011) 19 11 Rekursion Eine Funktion darf sich selbst aufrufen. Dies nennt man Rekursion. (Ausnahme: Die Funktion main() darf sich nicht selbst aufrufen.) Betrachten Sie die Zahlenfolge 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ... Sie heißt Fibonacci-Folge. Mit Ausnahme der zwei ersten Glieder der Folge ( Anfangswerte f(0) = 0 und f(1) = 1 ) wird jedes Glied der Folge als Summe der beiden Vorgänger gebildet: f(n) = f(n-2) + f(n-1), mit n 0 und n 2. Mit Hilfe einer rekursiven Funktion kann man ein elegantes ( kurzes und leicht lesbares ) Programm schreiben, das ein beliebiges, vorgegebenes Glied der Fibonacci-Folge berechnet. Das folgende C++Programm gibt das 13. Glied der Fibonacci-Folge auf dem Bildschirm aus. // Berechnung des 13. Glieds der Fibonacci-Folge per Rekursion # include <iostream> using namespace std; int f ( int i ) // Definition der Fibonacci-Folge als rekursive Funktion f { if( i == 0 ) return 0; // Anfangswert f(0) = 0 der Fibonacci-Folge if( i == 1 ) return 1; // Anfangswert f(1) = 1 der Fibonacci-Folge return f(i-2) + f(i-1); // wenn i > 1: Wert f (i) = f (i-1) + f (i-1) } int main() { int i = 13; cout << "Das " << i << ". Glied der Fibonacci-Folge ist " << f(i) << endl ; return 0; } Obiges Programm erzeugt folgende Bildschirmausgabe: Das 13. Glied der Fibonacci-Folge ist 233 Press any key to continue Man kann die Aufgabe auch iterativ, also mit Hilfe einer Schleife, lösen. Das Programm ist dann nicht so elegant, aber schneller. Prof. Dr. Alfred H. Gitter (2006 - 2011) 20 C++ - Praktikum Teil 1 : Grundlagen 1 2 3 4 5 6 7 8 9 Das kürzeste Programm Hallo Welt Elementare Datentypen Schleifchen Drei Bier Für Steven und Todd Größter gemeinsamer Teiler ( ggT ) Mathematische Funktionen Mittelwert berechnen (Arrays) 10 11 12 13 14 15 16 17 18 19 20 Mittelwert, x , Standardabweichung, SD, und Standardfehler des Mittelwerts, SEM Lotto ( 1 ) Lotto ( 2 ) Call by value und call by reference Lotto ( 3 ) Lotto ( 4 ) ( freiwillig, nur für Fortgeschrittene ) Überladung Zahlenprüfung Strings Struktur ( freiwillig, nur für Fortgeschrittene ) Klasse ( 1 ) 21 22 23 24 25 26 27 28 29 30 Klasse ( 2 ) Klasse ( 3 ) Klasse ( 4 ) Klasse ( 5 ) Überladen von Operatoren ( freiwillig, nur für Fortgeschrittene ) Komplexe Zahlen ( 1 ) Komplexe Zahlen ( 2 ) : mehrere Quellcode-Dateien Komplexe Zahlen ( 3 ) : Header-Datei Komplexe Zahlen ( 4 ) : complex ( freiwillig, nur für Fortgeschrittene ) In einer Datei schreiben und lesen 31 Debugging Teil 2 : 32 33 34 ( freiwillig, nur für Fortgeschrittene ) Bioinformatik Translation Sequenz aus überlappenden Fragmenten Globales Alignment mittels Dynamic Programming Prof. Dr. Alfred H. Gitter (2006 - 2011) 21 1 Das kürzeste Programm Aufgabe: Öffnen Sie mit einem Editor (zum Beispiel "Kate" oder "KWrite") eine neue Textdatei und schreiben Sie folgendes ( in blau geschriebenes ) Programm. ( kursiv: Erläuterungen für Sie ) int main() Jedes C++-Programm muss genau eine Funktion main() enthalten. { Ein Anweisungsblock wird von geschweiften Klammern { } eingeschlossen. } Nach der letzten Zeile RETURN eingeben, sonst gibt es vielleicht eine Warnung ! - Speichern Sie die Datei mit Namen p_01.cpp in ein geeignetes Verzeichnis (Ordner), zum Beispiel "Documents" (oder ein anderes vom Editor angebotenes): /home/student/Documents/ - Rufen Sie nun eine Konsole (zum Beispiel "Terminal-Programm") auf und wechseln Sie dann mit cd Documents in das Unterverzeichnis Documents . ( Achten Sie auf Groß- / Kleinschreibung ! ) - Schreiben Sie die Kommandozeile g++ p_01.cpp –o p_01 mehrere statt einer Lücke sind zwischen den Worten der Kommandozeile erlaubt Nachdem Sie die RETURN-Taste gedrückt haben, Der GNU-Compiler sollte das Programm ohne Fehlermeldung übersetzen und eine ausführbare Ausgabedatei mit dem Namen p_01 erzeugen. (Möglicherweise gibt er eine Warnung aus, die wir aber ignorieren.) - Nun schreiben Sie die Kommandozeile ./p_01 Auf die Zeichenfolge ./ folgt ohne Lücke der Name der Datei, hier also p_01 Das übersetzte Programm sollte nun ausgeführt werden. Da es aber keine Ein- und Ausgaben gibt, tut das Programm (scheinbar) nichts. Daher werden wir gleich ein etwas lustigeres Programm schreiben. Hinweise 1. Wenn Sie den Editor Kwrite verwenden, sollten Sie in den Optionen der Menüleiste die Anzeige der Zeilennummern wählen. Beim Editor Kate werden die Zeilennummern in der Statusleiste angezeigt. 2. Der GNU-Compiler ist ein zuverlässiger, weit verbreiteter Kommandozeilen-Compiler. Man kann ihn für alle gängigen Betriebssysteme kostenlos aus dem Internet herunterladen. Allerdings ist der GNUCompiler relativ langsam. 3. Der GNU-Compiler unter dem Betriebssystem Linux kann Programme, die in C oder in C++ geschreiben sind, übersetzen. Beachten Sie, dass mit dem Kommando cc oder gcc nur der CCompiler aufgerufen wird. Für C++-Programme muss g++ oder c++ verrwendet werden. 4. Mit dem Kommando dir erhalten Sie in der Konsole eine Liste der Dateien im aktuellen Verzeichnis. 5. In der Konsole kann man mit Strg p ( gleichzeitiges Drücken der Taste Strg und der Taste p ) und mit Strg n in den zuvor verwendeten Kommandozeilen blättern ( und so einiges "Eintippen" sparen ). 6. Die Kommandozeile g++ p_01.cpp erzeugt eine ausführbare Ausgabedatei mit dem Namen a.out . Mit der Option -o Dateiname erhält die Ausgabedatei den Namen Dateiname . 7. Eine sehr lange Liste mit den Kommandos und Optionen des GNU-Compilers erhalten Sie, wenn Sie in der Konsole die Kommandozeile man g++ eingeben. 8. Wenn der Quellcode des Programms in mehreren Dateien, zum Beispiel p_01a.cpp und p_01b.cpp verteilt steht, übersetzt man beide Quelldateien: g++ p_01a.cpp p_01b.cpp –o p_01.exe . In diesem Beispiel wird eine ausführbare Ausgabedatei mit dem Namen p_01.exe erzeugt. 9. Wenn man eine einzelne Datei mit C++-Quellcode in eine Objektdatei übersetzen und das Linken (Zusammenbinden) später durchführen will, gibt man die Option -c an, zum Beispiel erzeugt die Kommandozeile g++ -c p_01.cpp eine Objekt-Datei namens p_01.o . 10. Wenn man die Option -ggdb verwendet, werden dem Code Informationen für den gdb-Debugger hinzugefügt. Prof. Dr. Alfred H. Gitter (2006 - 2011) 22 2 Hallo Welt Aufgabe: Überschreiben Sie das alte Programm in Ihrer Textdatei (p_01.cpp) nun mit folgendem neuen: // Hallo Welt mit // beginnt ein Kommentar, mit ihm wird der Titel des Programms angegeben #include <iostream> mit der #include-Direktive wird die Headerdatei iostream eingefügt using namespace std; Angabe des Namensbereiches (namespace), wir verwenden stets std int main() { cout << "Hallo Welt\n"; cout ist das Ausgabeobjekt, welches für eine Bildschirmdarstellung sorgt. Die Zeichenfolge \n steht für "Zeilenvorschub", also einen Zeilenumbruch. cin.get(); cin ist das Eingabeobkjekt, mit cin.get() holen wir ein Zeichen von der Tastatur: Der User muss RETURN drücken, um das Programm zu beenden. return 0; Diese Zeile ist nach C++-Standard eigentlich überflüssig; einige Compiler erwarten aber, dass int main() einen Wert zurückgibt. } Mit Operator senden. << kann man Zeichenketten (Text), englisch: strings, an das Ausgabeobjekt cout Stringkonstanten, wie im obigen Beispiel Hallo Welt , stehen in doppelten hochgestellten Anführungszeichen. Einen Zeilenumbruch erreicht man entweder durch die Zeichenfolge \n Zeilenumbruch-Manipulators endl (für: end of line) an das Ausgabeobjekt: cout << "Hallo Welt" << endl ; oder durch Senden des dies ergibt die gleiche Bildschirmausgabe wie Zeile 5 in obigem Programm Falls das Programm beim Befehl cin.get(); nicht anhält, muss man den Eingabespeicher erst freimachen. Ohne uns um die Details zukümmern schreiben wir dann statt cin.get(); die folgende Zeile: cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); Obiges Programm kann für das nächste Programm als Gerüst wieder verwendet werden: Im nächsten Programm schreiben Sie statt der Zeile cout << "Hallo Welt\n"; den neuen Programmcode und fügen, soweit erforderlich weitere Headerdateien nach der Zeile #include <iostream> ein. Hinweise 1. Direkiven werden nicht Teil des vom Compiler erzeugten Maschinencodes, sondern sind Befehle an den Compiler. Direktiven beginnen mit # . 2. Mit der #include-Direktive wird der Inhalt einer Datei anstelle der Direktive in den Quelltext kopiert. So werden mit #include <iostream> Teile (Klassen) der Laufzeitbibliothek von C++ dem Programm hinzugefügt, in denen Objekte wie zum Beispiel cout definiert sind. Die Klammerung des Dateinamens mit < > sagt dem Compiler, dass er die Datei in einem bestimmten Include-Verzeichnis suchen soll. 3. Die standardisierte Header-Datei iostream enthält Klassen der Laufzeitbibliothek von C++ für die Einund Ausgabe. Es gibt eine ältere Header-Datei iostream.h , die nicht standardisiert ist. Das heisst, dass verschiedene Compiler unterschiedliche Header-Dateien mit dem Namen iostream.h haben können. Daher empfiehlt sich die Verwendung der (vom ANSI-Komitee) standardisieten Header-Datei iostream . 4. Bindet man die neuen, standardisierten C++-Header-Dateien ein, muss man dem Compiler mit using namespace std; sagen, dass die Bibliotehkselemente aus dem std-Namensbereich verwendet werden. 5. Einfache Anweisungen, nicht aber Anweisungsblöcke, werden mit Semikolon abgeschlossen ! Prof. Dr. Alfred H. Gitter (2006 - 2011) 23 3 Elementare Datentypen In C++ gibt es elementare Datentypen und definierte Datentypen. Zu ersteren gehören unter anderen bool char short int int long int float double long double logischer Datentyp (1 Byte), Werte: true, false (1Byte), Werte: einzelnes Zeichen (in einfachen Anführungszeichen), zum Beispiel 'A' kurze Ganzzahl (üblicherweise: 2 Bytes) Ganzzahl (üblicherweise 2 oder 4 Bytes, der Wertebereich hängt vom Betriebssystem ab) lange Ganzzahl (üblicherweise: 4 Bytes) Fliesspunktzahl (üblicherweise: 4 Bytes) lange Fliesspunktzahl (üblicherweise: 8 Bytes) sehr lange Fliesspunktzahl (üblicherweise: 12 Bytes) Mit der Pseudofunktion sizeof() Beispiel: sizeof(bool) ergibt 1. können Sie die Größe (in Bytes) der Datentypen erfragen, zum Um die Pseudofunktion sizeof() verwenden zu können, muss erst die Header-Datei <limits.h> eingefügt werden. Dies geschieht, indem nach der Zeile #include <iostream> eine neue Zeile #include <limits.h> geschrieben wird. Die niedrigsten und höchsten Werte, die für die Datentypen char, short int, int und long int möglich sind, sind in den Konstanten CHAR_MIN, CHAR_MAX, SHRT_MIN, SHRT_MAX, INT_MIN, INT_MAX, LONG_MIN, LONG_MAX der Header-Datei <limits.h> verfügbar. Aufgabe: Schreiben Sie ein neues Programm in der Datei (p_01.cpp) und geben Sie die Größe (in Bytes), sowie die niedrigsten und höchsten Werte der Datentypen char, short int, int und long int auf dem Bildschirm aus. (Der Datentyp char kann ein Zeichen oder eine kurze Ganzzahl enthalten. Daher gibt es auch einen ganzzahligen Wertebereich für char.) Ergänzen Sie nun mit den vom Programm ermittelten Werten die folgende Tabelle: Datentyp Bytes niedrigster Wert höchster Wert char short int int long int 4 Schleifchen Mit einer Schleife lässt sich ein Anweisungsblock mehrfach hintereinander ausführen. Ein Beispiel zeigt die Syntax einer for-Schleife: for ( i = 1 ; i <=10 ; ++i ) Initialisierung: i = 1 (wird nur beim ersten Eintritt in die Schleife 1 mal ausgeführt) { Bedingung: i <= 10 (die Schleife läuft, wenn der Ausdruck true oder ungleich 0 ist) cout << "i ist " << i << endl; Veränderung: ++i (wird am Ende jedes Schleifendurchlaufs ausgeführt) } Achtung: nach dem 10., dem letzten Schleifendurchlauf ist i = 11 Diese Schleife zählt von 1 bis 10 und gibt die aktuelle Zahl auf dem Bildschirm aus. Die Anweisung ++i steht für i = i + 1 . Beachten Sie folgendes: - Die Initialisierung i = 1 ist eine Zuweisung; daher nicht den Vergleichsoperator == verwenden ! Prof. Dr. Alfred H. Gitter (2006 - 2011) 24 - Die Bedingung muss einen logischen Wert ergeben; eine Zuweisung > 0, wie i = 10 , wäre true ! - Nach der Veränderungsanweisung ++i steht kein Semikolon ! - Nach dem Schleifenkopf for ( i = 1 ; i <=10 ; ++i ) steht kein Semikolon ! - Nach der geschweiften Klammer am Ende des Anweisungsblocks } steht kein Semikolon ! Die folgende fehlerhafte Schleife soll die Summe der Zahlen von 1 bis 10 bilden: int i; int summe = 0; for ( i == 1 , i >= 10 ; ++i ) ; { summe = summe + i; } Aufgabe: Markieren Sie rot die vier Fehler in obigem Code und schreiben Sie ein neues Programm in die Datei (p_01.cpp) mit der korrigierten Schleife und einer Bildschirmausgabe des Ergebnisses. Hinweis Ein weiteres Beispiel zeigt die Syntax einer while-Schleife: i=1; Initialisierung: i = 1 (vor der Schleife durch eine normale Zuweisung durchgeführt) while ( i <= 10 ) Bedingung: i <= 10 (die Schleife läuft, wenn der Ausdruck true oder ungleich 0 ist) { Der Anweisungsblock der Schleife steht in geschweiften Klammern und enthält cout << "i ist " << i << endl; die in der Schleife auszuführenden Anweisungen und ++i ; } die Veränderung: ++i (muss in der Schleife, zum Beispiel am Ende, eingebaut sein) 5 Drei Bier Aufgabe: Öffnen Sie eine neue Textdatei (p_02.cpp) und schreiben Sie ein Programm mit einer forSchleife oder mit einer while-Schleife, das die folgende Ausgabe erzeugt: 3 bottles of beer on the wall, 3 bottles of beer. Take one down, pass it around! 2 bottles of beer on the wall. 2 bottles of beer on the wall, 2 bottles of beer. Take one down, pass it around! 1 bottles of beer on the wall. 1 bottles of beer on the wall, 1 bottles of beer. Take one down, pass it around! 0 bottles of beer on the wall. Hinweise 1. Schreiben Sie am Anfang einen einzeiligen Kommentar mit dem Titel des Programms. 2. Definieren Sie eine ganzzahlige Variable bottles und initialisieren Sie diese mit dem Wert 3 . 3. Die Schleife soll so lange laufen, wie bottles größer als Null ist. 4. Die in diesem (5) und dem folgenden Programm (6) dargestellten Ereignisse basieren auf tatsächlichen Begebenheiten. Vor einer Nachahmung muss entschieden gewarnt werden. Prof. Dr. Alfred H. Gitter (2006 - 2011) 25 6 Für Steven und Todd Aufgabe: Öffnen Sie die Datei (p_02.cpp) ändern Sie den Quellcode (= das von Ihnen geschriebene Programm vor der Übersetzung in Maschinensprache) folgendermaßen: a) Die Anfangszahl der Flaschen soll eingelesen werden. Dazu kann das Eingabeobjekt cin verwendet werden: cin >> bottles; b) In jeder Strophe soll die zweite Zeile n bottles of beer. , wobei n die aktuelle Flaschenzahl ist, ersetzt werden durch even is for Steven ! , falls n eine gerade Zahl ist, beziehungsweise durch odd is for Todd ! andernfalls (wenn n eine ungerade Zahl ist). - Zur Prüfung, ob eine ganze Zahl gerade ist, teilt man sie durch 2 und schaut ob kein Rest bleibt. Dafür verwendet man die Modulo-Division bottles % 2 . - Ein Beispiel zeigt die Syntax einer if-Bedingung mit anschließendem else-Anweisungsblock: if (zahl == 0) { cout << "Die Zahl ist Null. "; } else { cout << "Die Zahl ist nicht Null."; } 7 Größter gemeinsamer Teiler ( ggT ) ( freiwillig, nur für Fortgeschrittene ) Der gößte gemeinsame Teiler ggT zweier Zahlen ist die größte Zahl, durch die beide Zahlen teilbar sind. Das nach dem griechischen Mathematiker Euklid benannte Verfahren zur Bestimmung des ggT zweier Zahlen a und b ist einer der ältesten Algorithmen der Welt. Dabei wird von der jeweils größeren Zahl immer so oft die kleinere Zahl abgezogen, bis die kleinere Null ist. Dann ist die verbliebene größere Zahl der ggT. Der Algorithmus ist in dem nebenstehenden Struktogramm dargestellt. Hinweis Um den den Wert zweier Variablen a und b zu vertauschen, benutzt man eine Hilfsvariable c : c = a; // Wert von a in c zwischenspeichern a = b; // Wert von b in a speichern b = c; // Wert von c ( = alter Wert von a ) in b speichern Aufgabe: Öffnen Sie eine neue Textdatei (p_03.cpp) und schreiben Sie ein Programm, das zwei Zahlen a und b einliest und den ggT dieser Zahlen nach dem Verfahren von Euklid berechnet. Prüfen Sie das Programms anhand von Beispielen ! Prof. Dr. Alfred H. Gitter (2006 - 2011) 26 8 Mathematische Funktionen Mathematische Funktionen gehören nicht zum Sprachumfang von C++, sondern sind in Bibliotheken ausgelagert, die über eine Header-Datei eingebunden werden. Die Header-Datei math.h hat C++ von der älteren Programmiersprache C übernommen. Es gibt aber auch eine entsprechende, standardisierte Datei für C++ namens cmath , bei der alle Deklarationen im Namensbereich std stehen. Da wir stets den Namensbereich std verwenden, binden wir mit #include <cmath> die Header-Datei cmath ein. Hier ist eine Tabelle mit einigen trigonometrischen Funktionen und ihren Datentypen: Funktion Beispiel Funktion Beispiel Sinus x = sin(3.1415926) ; Arcus Sinus cout << asin(x) ; Cosinus cout << cos(a) << endl ; Arcus Cosinus z = acos(x)/2.0 ; Tangens y = tan(3.1415926/2.0) ; Arcus Tangens cout << atan(0.5) ; Argumente und Rückgabewerte der trigononmetrischen Funktionen sind vom Datentyp double ! Die Parameter der trigonometrischen Funktionen in der linken Tabellenhäfte müssen in Bogenmass übergeben werden. Wenn die Werte in Gradmass vorliegen, kann man sie wie folgt umrechnen: bogenmass = ( gradmass / 360 ) * 2 * 3.1415926535 ; Die Ergebnisse der trigonometrischen Funktionen in der rechten Tabellenhäfte werden in Bogenmass ausgegeben. Wenn die Ergbnisse in Gradmass vorliegen sollen, kann man sie wie folgt umrechnen: gradmass = bogenmass * 360 / ( 2 * 3.1415926535 ) ; Bei manchen C++-Compilern ist als Konstante ( z.B. M_PI ) verfügbar, aber das ist nicht allgemein so ! Hier ist eine Tabelle mit weiteren mathematischen Funktionen: Funktion e–Funktion, z.B. e b a , z.B. 3 5 2 Logarithmus, Basis e Beispiel Funktion Beispiel x = exp(5.0) ; Logarithmus, Basis 10 y = log10(1000) ; cout << pow(3.0, 2.0) ; y = log(x) ; cout << sqrt(a) ; a (nichtneg.) Betrag z = 1.0 + fabs(-2.345) ; Argumente und Rückgabewerte dieser Funktionen sind vom Datentyp double ! Um die mathematischen Funktionen verwenden zu können, muss erst die Header-Datei <cmath > eingefügt werden. Dies geschieht, indem nach der Zeile #include <iostream> eine neue Zeile #include <cmath> geschrieben wird. Aufgabe: Öffnen Sie eine neue Textdatei (p_04.cpp) und schreiben Sie ein Programm, das folgende Aufgabe löst: Die Spitze eines Baumes erscheint einem Wurm, der 5 m vom Baum entfernt ist, unter einem Winkel von 60 º (gegen die Horizontale gemessen). Wie hoch ist der Baum ? Prof. Dr. Alfred H. Gitter (2006 - 2011) 27 9 Mittelwert berechnen (Arrays) Wenn man viele Werte des gleichen Datentyps bearbeiten will und nicht für jeden Wert eine Variable mit eigenem Namen anlegen will, verwendet man Datenfelder: Arrays. Beispiel für eine Array-Definition: double messwerte[10] ; Das Array mit dem Namen messwerte enthält 10 Datenwerte vom Typ double. Die Anzahl der Datenwerte, die ein Array speichert, heisst Länge des Arrays (im Beispiel: 10). Bei der Deklaration muss man dem Compiler die genaue Länge des Arrays als Zahl oder Konstante mitteilen. Die Elemente des Arrays, das heisst die in ihm gespeicherten Werte, werden mit einem Index angesprochen, der von 0 bis n - 1 läuft, wobei n die Länge des Arrays ist. Somit sind in obigem Array folgende Werte definiert: messwerte[0] , messwerte[1] , messwerte[2] , messwerte[3] , messwerte[4] , messwerte[5] , messwerte[6] , messwerte[7] , messwerte[8] , messwerte[9] . Die eckigen Klammern [ ] heißen Indexoperator, wenn man mit Ihnen auf ein bestimmtes Array-Element zugreift. Wie bei einfachen Variablen werden den Elementen eines Arrays Werte zugewiesen, zum Beispiel messwerte[3] = 12.345 ; Achtung: messwerte[3] ist das vierte Element des Arrays. Entsprechend kann auch umgekehrt der Wert eines Arrayelements einer Variablen zugewiesen werden: int mw = 3 ; Der Index kann auch durch eine ganzzahlige Variable angegeben werden. dat_1 = messwerte[mw] ; Hier wird der Variablen dat_1 der Wert des vierten Arrayelements zugewiesen. Aufgabe: Öffnen Sie eine neue Textdatei (p_05.cpp) und schreiben Sie ein Programm, das abfragt, wie viele Messwerte eingegeben werden sollen, dann diese Messwerte einliest und in ein (genügend großes) Array speichert, und schliesslich den arithmetischen Mittelwert ausrechent und ausgibt. 10 Mittelwert, x , Standardabweichung, SD, und Standardfehler des Mittelwerts, SEM - Folgende Formel dient zur Berechnung der Standardabweichung SD (englisch: standard deviation) mit Hilfe einer Stichprobe (n Messwerten x i ) : SD - n 1 x i x 2 n 1 i 1 Ausgehend von der Standardabweichung SD wird der Standardfehler des Mittelwerts SEM (englisch: standard error of the mean) mit folgender Formel berechnet : SEM SD n Aufgabe: Öffnen Sie die Textdatei (p_05.cpp) und ergänzen Sie das Programm, so es dass nicht nur den Mittelwert, sondern auch die Standardabweichung und den Standardfehler des Mittelwerts ausrechnet ! Prof. Dr. Alfred H. Gitter (2006 - 2011) 28 11 Lotto ( 1 ) Aufgabe: Öffnen Sie eine neue Textdatei (p_06.cpp) und tippen Sie folgendes Programm ein. Ergänzen Sie das Programm, so dass es nicht nur 6 Zufallszahlen erzeugt, sondern diese auch mit Hilfe einer Funktion anzeige() , die aus main() aufgerufen wird, auf dem Bildschirm ausgibt. // Lotto #include <cstdlib> // Header-Datei mit Funktionen wie rand() und srand() #include <ctime> // Header-Datei mit Zeit-Funktionen wie time() #include <iostream> // Header-Datei für Ein- und Ausgaben über die Konsole using namespace std; int lotto[6]; // Definition des Arrays lotto außerhalb aller Blöcke: global void ziehung() // Definition der Funktion zur Erzeugung von Lottozahlen { int i, k=6, n=49; // Definition im Anweisungsblock einer Funktion: lokal srand( (unsigned)time(NULL) ); // Aufmischen des Zufallszahlen-Generators for(i=0;i<k;++i) // Ziehung der k = 6 Zahlen { lotto[i]= rand() % n + 1; // Zuweisung einer Zufallszahl zwischn 1 und n } } // Der obere Programmteil soll in Ihr Programm unverändert übernommen werden. int main() { ziehung(); // Aufruf der Funktion zur Erzeugung von Lottozahlen cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 12 Lotto ( 2 ) Aufgabe: Ergänzen Sie Funktion ziehung () im Programm in Datei p_06.cpp, so dass keine Lottozahl doppelt vorkommen kann. Hinweise 1. Definieren Sie eine boolesche Variable alt . Sie soll die beiden Möglichkeiten unterscheiden, ob eine Zufallszahl bereits als Lottozahl gezogen wurde ( alt == true ) oder noch nicht ( alt == false ). 2. Wiederholen Sie für jede Lottozahl in einer while-Schleife die Erzeugung einer Zufallszahl solange, bis diese Zufallszahl nicht gleich einer bereits gezogenen Lottozahl ist . (Übrigens: Wer sich mit do-whileSchleifen auskennt, kann es damit etwas eleganter programmieren.) 3. Zur Prüfung, ob eine Zufallszahl nicht gleich einer bereits gezogenen Lottozahl ist, weisen Sie, als Arbeitshypothese, der booleschen Variable alt zunächst den Wert false zu. Dann sollen Sie in einer for-Schleife die Zufallszahl mit allen bereits gezogenen Lottozahlen vergleichen. Wenn (mindestens) eine Übereinstimmung gefunden wird, erhält alt den Wert true . Prof. Dr. Alfred H. Gitter (2006 - 2011) 29 13 Call by value und call by reference Funktionen können Parameter als Argument übernehmen. Danach folgt eine Anweisungsblock, in dem mit return ein Wert bestimmten Typs zurückgeben werden kann. Die Syntax ist daher zum Beispiel float f1(float a, int b) { return a/b; } c = f1(2.5,3); In diesem Beispiel sind a und b die Parameter und der im Anweisungsblock errechnete Wert a/b wird als Rückgabewert der Variablen c zugewiesen, die vom Typ float sein sollte, wie der Rückgabetyp. Funktionsparamter elementarer Datentypen erhalten normalerweise den Wert des Arguments als Kopie: call by value. Aber durch Voranstelllung eines & wird eine Referenz auf den den Parameter übergeben: call by reference. Damit kann die im Aufruf genannte Variable in der Funktion verändert werden. Aufgabe: Öffnen Sie eine neue Datei (p_07.cpp) und tippen Sie folgendes Programm ein: // call by value <-> call by reference #include <iostream> using namespace std; float q1(int x) { // Definition der Funktion q1: call by value float xx; xx = x * x; x = 3; // in der Funktion q1 ist x eine lokale Variable von q1 return(xx); } float q2(int &z) { // Definition der Funktion q2: call by reference float xx; xx = z * z; z = 5; // In der Funktion q2 ist z nur ein anderer Name für die in // main() definierte Variable x, die in main und q2 gültig ist return(xx); } int main() { // Definition der Funktion main: Aufruf von q1 und q2 int x; float y; x = 2; y = q1(x); cout << endl << "q1: x ist " << x << endl; cout << "q1: y ist " << y << endl; x = 2; y = q2(x); cout << endl << "q2: x ist " << x << endl; cout << "q2: y ist " << y << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } - Welche Ausgabe erzeugt obiges Programm? Ergänzen Sie die folgende Tabelle: q1: x ist q2: x ist q1: y ist q2: y ist Prof. Dr. Alfred H. Gitter (2006 - 2011) 30 14 Lotto ( 3 ) Aufgabe: Überschreiben Sie das Programm in Datei p_06.cpp mit dem folgenden und markieren Sie alle wesentlichen (nicht nur die Formatierung betreffenden) Änderungen (am besten in ROT): // Lotto #include <cstdlib> // Header-Datei mit Funktionen wie rand() und srand() #include <ctime> // Header-Datei mit Zeit-Funktionen wie time() #include <iostream> // Header-Datei für Ein- und Ausgaben über die Konsole using namespace std; #define ANZ 6 // Die Direktive #define ersetzt vor Übersetzung in Maschinen- #define AUS 49 // Code eine symbolische Konstante durch einen Zahlenwert void ziehung(int lotto_1[ANZ]) // Definition einer Funktion mit Integer-Array als Parameter { // ( Die Übergabe von Arrays erfolgt durch call by reference ! ) int i, j, k=ANZ, n=AUS; // lokale Variablen; k = Anzahl, n = Größte der Lottozahlen bool alt; // lokale boolesche Variable srand( (unsigned)time(NULL) ); // Aufmischen des Zufallszahlen-Generators for(i=0;i<k;++i) // Ziehung der k = 6 Lottozahlen { alt = true; // Die Zahl gilt als "alt", bis gezeigt ist, dass sie "neu" ist. while(alt) // Solange die Zahl "alt" ist, muss eine Zufallszahl erzeugt werden. { lotto_1[i]= rand() % n + 1; // Erzeugung einer Zufallszahl zwischen 1 und n alt = false; // Hypothese: Die Zufallszahl ist "neu" for(j = 0; j<i; ++j) // Schleife über alle gezogenen Lottozahlen { } } } } if (lotto_1[i]==lotto_1[j]) // vergleiche Zufallszahl mit Lottozahlen, { alt = true; // und wenn gleich, dann ist die Zahl "alt" } void anzeige(const int lotto_2[ANZ]) // Funktionsdefinition mit Integer-Array als Parameter { // ( Die Funktion kann das Array nicht verändern. ) int i, k=ANZ; // Definition von Variablen im Anweisungsblock einer Funktion: lokal for(i=0;i<k;++i) { cout << lotto_2[i] << endl ; } } int main() { int lotto[ANZ]; // Definition des Arrays lotto innerhalb der Funktion main(): lokal ziehung(lotto); // Aufruf der Funktion zur Lottozahlen-Erzeugung, Übergabe von lotto anzeige(lotto); // Aufruf der Funktion zur Anzeige der Lottozahlen, Übergabe von lotto cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 31 15 Lotto ( 4 ) ( freiwillig, nur für Fortgeschrittene ) Aufgabe: Ergänzen Sie die Funktion main() des Lotto-Programms in Datei p_06.cpp durch einen Aufruf der neuen Funkion ordnung(lotto); mit Übergabe des Arrays lotto : int main() { int lotto[ANZ]; // Definition des Arrays lotto innerhalb der Funktion main(): lokal ziehung(lotto); // Aufruf der Funktion zur Lottozahlen-Erzeugung, Übergabe von lotto ordnung(lotto); // Aufruf der Funktion zur Lottozahlen-Sortierung, Übergabe von lotto anzeige(lotto); // Aufruf der Funktion zur Anzeige der Lottozahlen, Übergabe von lotto cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } Mit dem Algorithmus "Bubblesort" sollen die Lottozahlen der Größe nach geordnet werden. Im ersten Durchlauf geht der Algorithmus vom ersten ( lotto[0] ) bis zum vorletzten Element des Arrays lotto und vergleicht dabei jedes Element j mit seinem rechten Nachbarn, j+1 . Wenn lotto lotto[j] > lotto[j+1] ist, werden die beiden Elemente vertauscht. Dann folgen weitere Durchläufe mit gleichem Verfahren, bis keine Vertauschungen mehr nötig sind. Hierfür sind (maximal) ANZ-1 Durchläufe notwendig (wobei ANZ die Zahl der Elemente im Array lotto ist). Im ersten Durchlauf (der bis zum vorletzten Element des Arrays geht) wird die höchste Zahl an die richtige Stelle verschoben. Im zweiten Durchlauf wird die zweithöchste Zahl an die richtige Stelle verschoben, und so weiter. Da im zweiten Durchlauf das letzte Element des Arrays schon richtig besetzt ist, braucht der zweite Durchlauf nur bis zum vorvorletzten Element des Arrays gehen. Und so weiter. Wenn nach ANZ-1 Durchläufen das zweite bis zum letzten Array-Element richtig sortiert sind, muss auch das erste Array-Element schon richtig gesetzt sein. Daher kann der Algorithmus dann aufhören. - Ergänzen Sie das Programm nun durch Definition einer Funktion mit dem Namen ordnung , welche die Lottozalen im Array lotto in aufsteigender Reihenfolge sortiert. 16 Überladung Die Definition von Funktionen gleichen Namens, die sich nur in der Parameterliste unterscheiden, nennt man Überladung. Der Compiler unterscheidet sie durch den Typ und die Anzahl der Aufrufparameter. Aufgabe: Öffnen Sie eine neue Textdatei (p_08.cpp) und tippen Sie folgendes Programm ein (die Kommentare können Sie weglassen) : // Überladung #include <iostream> #include <cmath> using namespace std; void wurzel(double x) { double y; // Definition einer Funktion mit double-Variabler als Parameter // Definition der in wurzel(double x) lokalen Variablen y vom Datentyp double y = sqrt(x); cout << "die Wurzel von " << x << " ist " << y << endl; } Prof. Dr. Alfred H. Gitter (2006 - 2011) 32 void wurzel(int x) // Definition einer Funktion mit int-Variabler als Parameter { int i; // Definition der in wurzel(int x) lokalen Variablen i vom Datentyp int double y; // Definition der in wurzel(int x) lokalen Variablen y vom Datentyp double y = sqrt( double(x) ); i = int(y); // beachte: Typ-Umwandlung des Argument der Funktion sqrt() // explizite Umwandlung der double-Variablen y in eine Variable i vom Typ int // die Nachkommastellenwerden abgeschnitten (es wird nicht gerundet) if ( i * i == x ) { cout << "die Wurzel von " << x << " ist " << i << endl; } else { cout << "die ganze Zahl " << x << " hat keine ganzzahlige Wurzel" << endl; } } int main() { int a = 9, b = 11; double x = 11.0; // Definition der in main() lokalen Variablen a, b und x wurzel( a ); // Aufruf der Funktion wurzel(int x) mit int-Variabler als Parameter wurzel( b ); // Aufruf der Funktion wurzel(int x) mit int-Variabler als Parameter wurzel( x ); // Funktionsaufruf wurzel(double x) mit double-Variabler als Parameter cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } - Schreiben Sie die Bildschirmausgabe des Programms hier auf : 17 Zahlenprüfung Aufgabe: Öffnen Sie eine neue Textdatei (p_09.cpp) und tippen Sie folgendes Programm ein. Lassen Sie das Programm ein paar Mal laufen und beschreiben Sie dann kurz, was das Programm macht : Überdenken Sie den mathematischen Ansatz und ändern Sie die Funktion test(), so dass das Programm schneller wird. ( Hinweis: Mit dem Befehl break; können Sie die Schleife sofort beenden und verlassen. ) Verwenden Sie als Testzahlen: a) 1234567890 und b) 1234567891 // Zahlenpruefung #include <iostream> #include <climits> // wird für Limit_konstante LONG_MAX gebraucht #include <ctime> // wird für die Funktion time() gebraucht using namespace std; bool test(long n) // Definition der Funktion test() { long int t; bool p = true; // lokale Variablen der Funktion test for ( t=2; t<n ; ++t ) Prof. Dr. Alfred H. Gitter (2006 - 2011) 33 { if ( n%t == 0 ) { p = false; } } return p; } int main() { long int n, sek_1, sek_2, sek_delta; double x; bool p = true, zu_gross = true; while ( zu_gross ) { cout << endl << "Gib eine positive ganze Zahl ein: "; cin >> x; if ( x < LONG_MAX) { zu_gross = false; } else { cout << "Diese Zahl ist zu gross !" << endl; } } sek_1 = time(0); // time(0) gibt einen Wert vom Datentyp long int .. n = long(x); // .. zurück, der die Sekunden seit 1. 1. 1970 angibt p = test(n); // Aufruf der Funktion test() if (p) { cout << "Ja !" <<endl; } else { cout << "Nein !" << endl; } sek_2 = time(0); sek_delta = sek_2 - sek_1; cout << "Das hat " << sek_delta << " Sekunden gedauert."; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 18 Strings In C++ können Zeichenketten mit Hilfe der Klasse string bearbeitet werden, wenn die Header-Datei <string> eingebunden wird. Achtung: Die Zählung der Zeichen in der Zeichenkette beginnt mit 0 ! Für string-Objekte gibt es, unter anderem, die Methoden length() , find(s,i) , und replace(i,n,s) : string satz; satz = "Guten Tag, Ute, wie geht es Dir?"; // Deklaration eines Strings und Wertzuweisung int L ; L = satz.length() ; // satz.length() gibt die Anzahl der Zeichen im String satz zurück int pos ; int start = 0 ; string wort1 = "Ute"; pos = satz.find(wort1,start) ; // satz.find(wort1,start) sucht // ab Position start und liefert die Position von String wort1 , oder –1 , wenn dieser nicht gefunden wird string wort2 = "Peter"; start = 11 ; int n = 3 ; satz.replace(start,n,wort2) ; // satz.replace(start,n,wort2) ersetzt im String satz n Zeichen ab Position start durch den String wort2 Aufgabe: Öffnen Sie eine neue Textdatei (p_10.cpp) und schreiben ein Programm, welches folgendes tut: a) Einer String-Variablen A wird der Text "Hat Gott sich selbst, also Gott erschaffen ? " , und einer String-Variablen B der Text "Oh Gott ich hoffe, das ist keine Klausurfrage !" zugewiesen. b) Einer String-Variablen C wird die Konkatenation von A und B zugewiesen. c) Der Wert der StringVariablen C wird auf dem Bildschirm ausgegeben. d) Einer String-Variablen D wird der Wert "Gott" , und einer String-Variablen E der Wert "jenes hoehere Wesen, das wir verehren," zugewiesen. e) Einer String-Variablen F wird der Wert der String-Variablen C zugewiesen. f) In der String-Variablen F wird die Zeichenkette, die in der String-Variablen D gespeichert ist, bei jedem Auftreten durch den Wert der String-Variablen E ersetzt. g) Der neue Wert der String-Variablen F wird auf dem Bildschirm ausgegeben. Prof. Dr. Alfred H. Gitter (2006 - 2011) 34 19 Struktur ( freiwillig, nur für Fortgeschrittene ) Mit Arrays kann man Variablen gleichen Typs unter einem Namen zusammenfassen. Außerdem gibt es bereits in der Programmiersprache C, und damit auch in C++, die "Struktur", mit der ein Datentyp erzeugt werden kann, der verschiedene Variablen zusammenfasst. Ein Beispiel zeigt die Deklaration: struct Studi Die Deklaration beginnt mit dem Schlüsselwort struct, gefolgt vom Namen der Struktur. { Die Deklaration der Strukturelemente erfolgt in einem von { und } eingeschlossenen Block. string vorname; Deklaration einer Variablen vom Datentyp string als Strukturelement bool maennlich; Deklaration einer Variablen vom Datentyp bool als Strukturelement long int matrikel; Deklaration einer Variablen vom Datentyp long int als Strukturelement double kontostand; Deklaration einer Variablen vom Datentyp double als Strukturelement }; Achtung: nach der geschweiften Klammer am Blockende folgt noch ein Semikolon ! Um eine neue Variable vom Datentyp Studi anzulegen, wird diese auf die gleiche Art wie etwa eine Integer-Variable deklariert. Man kann die neue Variable auch gleich initialisieren (siehe zweites Beispiel): Studi Muemmelmann; Deklaration einer Variablen vom Datentyp Studi Studi Pfeiffer = { "Peter" , true , 123456 , -23.61 }; Deklaration und Initialisierung einer Studi-Variablen Der Zugriff auf die Strukturelemente erfolgt mit Hilfe des Punktoperators: Muemmelmann.vorname = "Bernhardine"; Zuweisung eines Werts an ein Strukturelement if (Pfeiffer.maennlich) cout << "lieber" << Pfeiffer.vorname; Auswertung von Strukturelementen von Pfeiffer Deklarationen von Datentypen, wie im Beispiel oben Studi , stehen üblicherweise am Anfang der Quelldatei oder in einer eigenen Header-Datei. Statt Strukturen verwendet man in C++ hauptsächlich die sogenannten Klassen, die alles können, was Strukturen können, und noch mehr (siehe unten). Aufgabe: a) Öffnen Sie eine neue Textdatei (p_11.cpp) und schreiben Sie ein Programm, das einen Datentyp vektor als Struktur erzeugt. Der neue Datentyp soll einen zweidimensionalen Vektor repräsentieren. b) Definieren Sie Variablen für drei Vektoren A , B , C . A soll mit den Koordinaten 1 und 2,5, B mit 2,0 und 3,5 initialisiert werden. Berechnen Sie C als Summenvektor von A und B und geben Sie die Koordinaten von A und B , sowie des des Summenvektors C als Ergebnis auf dem Bildschirm aus. 20 Klasse ( 1 ) Mit dem Datentyp "Struktur" kann man verschiedene Variablen zusammenfassen. Durch Deklaration einer "Klasse" wird ein neuer Datentyp erzeugt, der nicht nur Variablen zusammenfasst, sondern auch Funktionen bereitstellen kann, die mit den Variablen der Klasse arbeiten. Eine Variable vom Datentyp einer Klasse heißt Objekt, oder Instanz der Klasse; und die Deklaration heißt daher auch Instanzbildung. Variablen, die in einer Klasse definiert sind, werden als Eigenschaften, Elementvariablen oder Instanzvariablen bezeichnet. Funktionen, die in einer Klasse definiert sind, heißen Methoden oder Instanzmethoden. Oft findet sich die Bezeichnung "Klassenmethode", was aber irreführend ist, da die Methoden (ebenso wie die Eigenschaften) über den Namen der Instanz, und nicht über den Namen der Klasse aufgerufen werden. (Allerdings werden Instanzmethoden nur einmal für jede Klasse gespeichert, während jeweils ein Satz von Instanzvariablen für jede Instanz gebraucht wird.) In einer Klassendefinition wird mt dem Zugriffsspezifizierer public: für die danach deklarierten Eigenchaften und Methoden der Zugriff (über eine Instanz) von außerhalb der Klassendefinition erlaubt. Aufgabe: Öffnen Sie eine neue Textdatei (p_12.cpp) und tippen Sie folgendes Programm ein. Danach ergänzen Sie das Programm durch die Ausgabe der Koordinaten des Vektors A und seiner Länge. Prof. Dr. Alfred H. Gitter (2006 - 2011) 35 // Vektor #include <cmath> #include <iostream> using namespace std; class vektor // Definition der Klasse vektor vor der Funktion main() { public: // Zugriffsspezifizierer, die folgenden Elemente sind öffentlich double x, y; // Definition von Eigenschaften (der Klasse zugeordnete Variablen) double laenge() // Definition einer Methode (der Klasse zugeordnete Funktion) { double z; z = sqrt(x*x + y*y); return z; } }; // Das Semikolon nicht vergessen ! int main() { vektor A = {1.0, 2.5}; // Instanzbildung - Alternative: vektor A; A.x = 1.0; A.y = 2.5; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 21 Klasse ( 2 ) Bei jeder Instanzbildung, also der Deklaration einer Variablen vom Datentyp einer Klasse, wird automatisch eine besondere Funktion aufgerufen, die dafür sogen soll, dass das erzeugte Objekt initialisiert wird. Diese Funktion heisst allgemein "Konstruktor" und trägt den gleichen Namen wie die Klasse. Eine Konstruktorfunktion sollte vom Programmierer zusammen mit der Klasse definiert werden. Wenn ein Konstruktor fehlt, erzeugt der Compiler einen einfachen Standard-Konstruktor. Der Konstruktor hat, im Gegensatz zu anderen Funktionen und Methoden, keinen Rückgabewert (nicht einmal void) und kann nicht über den Namen einer Instanz aufgerufen werden (ist also, im oben genannten Sinne, keine "Instanzmethode", sondern eine "Klassenmethode"). Die Konstruktorfunktion kann, wie andere Funktionen (siehe oben), überladen werden. Aufgabe: Öffnen Sie die erneut die Textdatei (p_12.cpp) und ändern Sie das Programm wie folgt. Compilieren Sie das Programm und führen es aus ! // Vektor #include <cmath> #include <iostream> using namespace std; class vektor { public: double x, y; vektor() { x = 0; // Definition der Klasse vektor vor der Funktion main() // Zugriffsspezifizierer, die folgenden Elemente sind öffentlich // Definition von Eigenschaften (der Klasse zugeordnete Variablen) // Konstruktor ohne Parameter // Initialisierung der Eigenschaft x Prof. Dr. Alfred H. Gitter (2006 - 2011) 36 y = 0; // Initialisierung der Eigenschaft y } vektor(double p_1, double p_2) // Konstruktor mit Parametern { x = p_1; // Initialisierung der Eigenschaft x y = p_2; // Initialisierung der Eigenschaft y } double laenge() // Definition einer Methode (der Klasse zugeordnete Funktion) { double z; z = sqrt(x*x + y*y); return z; } }; // Das Semikolon nicht vergessen ! int main() { vektor A; // Instanzbildung mit Hilfe des Konstruktors ohne Parameter vektor B(1.0,2.5); // Instanzbildung mit Hilfe des Konstruktors mit Parametern cout << "Der Vektor A hat die Kooordinaten " << A.x << " und " << A.y << " ." << endl; cout << "Er hat eine Laenge von " << A.laenge() << " ." << endl; cout << "Der Vektor B hat die Kooordinaten " << B.x << " und " << B.y << " ." << endl; cout << "Er hat eine Laenge von " << B.laenge() << " ." << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 22 Klasse ( 3 ) Es wird übersichtlicher, wenn die Definition der Methoden nach der Klassendefinition erfolgt. Dann müssen die Methoden in der Klassendefinition nur deklariert werden. Ausserhalb der Klassendefinition setzt man vor den Methodennamen einen doppelten Doppelpunkt gefolgt vom Klassennamen. So kann der Compiler die Methode der Klasse zuordnen und von einer globalen Funktion unterscheiden. Aufgabe: Öffnen Sie die erneut die Textdatei (p_12.cpp) und ändern Sie das Programm wie folgt. Compilieren Sie das Programm und führen es aus. // Vektor #include <cmath> #include <iostream> using namespace std; class vektor // Definition der Klasse vektor vor der Funktion main() { public: // Zugriffsspezifizierer, die folgenden Elemente sind öffentlich double x, y; // Definition von Eigenschaften (der Klasse zugeordnete Variablen) vektor(); // Deklaration des Konstruktors ohne Parameter vektor(double p_1, double p_2); // Deklaration des Konstruktors mit Parametern double laenge(); // Deklaration einer Methode }; // Das Semikolon nicht vergessen ! // vektor::vektor() // Konstruktor ohne Parameter Prof. Dr. Alfred H. Gitter (2006 - 2011) 37 { x = 0; y = 0; // Initialisierung der Eigenschaft x // Initialisierung der Eigenschaft y } vektor::vektor(double p_1, double p_2) // Konstruktor mit Parametern { x = p_1; // Initialisierung der Eigenschaft x y = p_2; // Initialisierung der Eigenschaft y } double vektor::laenge() // Definition einer Methode (der Klasse zugeordnete Funktion) { double z = sqrt( x*x + y*y ); return z; } // int main() { vektor A; // Instanzbildung mit Hilfe des Konstruktors ohne Parameter vektor B(1.0,2.5); // Instanzbildung mit Hilfe des Konstruktors mit Parametern cout << "Der Vektor A hat die Kooordinaten " << A.x << " und " << A.y << " ." << endl; cout << "Er hat eine Laenge von " << A.laenge() << " ." << endl; cout << "Der Vektor B hat die Kooordinaten " << B.x << " und " << B.y << " ." << endl; cout << "Er hat eine Laenge von " << B.laenge() << " ." << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 23 Klasse ( 4 ) Man unterscheidet Zugriffe von innerhalb und ausserhalb der Klasse. Es ist üblich, den Zugriff auf Eigenschaften durch den Zugriffsspezifizierer private: nur für Methoden der Klasse zu erlauben. Methoden dagegen sind öffentlich (Zugriffsspezifizierer public: ). Die Änderung einer Eigenschaft von ausserhalb erfolgt über eine SET-Methode und die Abfrage des Wertes erfolgt über eine GET-Methode. Aufgabe: Öffnen Sie die Textdatei (p_12.cpp) und ändern Sie die Definitionen für Klasse und Methoden: class vektor // Definition der Klasse vektor vor der Funktion main() { private: // Zugriffsspezifizierer, folgende Elemente sind nicht öffentlich double x, y; // Definition von Eigenschaften (der Klasse zugeordnete Variablen) public: // Zugriffsspezifizierer, die folgenden Elemente sind öffentlich vektor(); // Deklaration des Konstruktors ohne Parameter vektor(double p_1, double p_2); // Deklaration des Konstruktors mit Parametern void set_x(double x_neu); // Deklaration einer öffentlichen SET-Methode void set_y(double y_neu); // Deklaration einer öffentlichen SET-Methode double get_x(); // Deklaration einer öffentlichen GET-Methode double get_y(); // Deklaration einer öffentlichen GET-Methode double laenge(); // Deklaration einer Methode }; // Das Semikolon nicht vergessen ! // Prof. Dr. Alfred H. Gitter (2006 - 2011) 38 vektor::vektor() { x = 0; y = 0; } // Konstruktor ohne Parameter vektor::vektor( double p_1, double p_2 ) { x = p_1; y = p_2; } // Konstruktor mit Parametern double vektor::get_x() { return x; } // Definition der GET - Methode für die x-Koordinate double vektor::get_y() { return y; } // Definition der GET - Methode für die y-Koordinate void vektor::set_x(double x_neu) { x = x_neu; } // Definition der SET - Methode für die x-Koordinate void vektor::set_y(double y_neu) { y = y_neu; } // Definition der SET - Methode für die y-Koordinate double vektor::laenge() { double z = sqrt( x*x + y*y ); return z; } // Definition einer Methode - Ändern Sie die Funktion main() so, dass dem Vektor A (nach der unveränderten Deklaration) mit Hilfe der SET-Methode die Koordinaten 8,5 und 4,0 zugewiesen werden. Stellen Sie mittels der GET-Methode sicher, dass die Ausgabe des Programms wie zuvor aussieht ! 24 Klasse ( 5 ) Aufgabe: Öffnen Sie die Textdatei (p_12.cpp) und ändern Sie den Code der Funktion main() wie folgt: int main() { vektor A; // Instanzbildung mit Hilfe des Konstruktors ohne Parameter vektor B(1.0,2.5); // Instanzbildung mit Hilfe des Konstruktors mit Parametern A.set_x(8.5); A.set_y(4.0); cout << "Der Vektor A hat die Kooordinaten " << A.get_x() << " und " << A.get_y() << " ." << endl; cout << "Er hat eine Laenge von " << A.laenge() << " ." << endl; cout << "Der Vektor B hat die Kooordinaten " << B.get_x() << " und " << B.get_y() << " ." << endl; cout << "Er hat eine Laenge von " << B.laenge() << " ." << endl; vektor C = A.plus(B); cout << "Der Summenvektor von A und B hat eine Laenge von " << C.laenge() << " ." << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; cout << "Er hat eine Laenge von " << A.laenge() << " ." << endl; } - Ändern Sie die Definitionen für Klasse und Methoden, so dass ein funktionierendes Programm entsteht. Prof. Dr. Alfred H. Gitter (2006 - 2011) 39 25 Überladen von Operatoren ( freiwillig, nur für Fortgeschrittene ) Durch Überladung des Operators + kann man zwei Vektoren elegant mit einem + - Zeichen addieren. Aufgabe: Kopiereren Sie Datei p_12.cpp in p_13.cpp . Ändern Sie die Klassen- und Methodendefinitionen wie folgt : class vektor // Definition der Klasse vektor { private: // Zugriffsspezifizierer, folgende Elemente sind nicht öffentlich double x, y; // Definition von Eigenschaften public: // Zugriffsspezifizierer, die folgenden Elemente sind öffentlich vektor(); // Deklaration des Konstruktors ohne Parameter vektor(double p_1, double p_2); // Deklaration des Konstruktors mit Parametern void set_x(double x_neu); // Deklaration einer öffentlichen SET-Methode void set_y(double y_neu); // Deklaration einer öffentlichen SET-Methode double get_x(); // Deklaration einer öffentlichen GET-Methode double get_y(); // Deklaration einer öffentlichen GET-Methode double laenge(); // Deklaration einer Methode vektor operator+(vektor v); // Deklaration einer Methode }; // vektor::vektor() { x = 0; y = 0; } // Konstruktor ohne Parameter vektor::vektor( double p_1, double p_2 ) { x = p_1; y = p_2; } // Konstruktor mit Parametern double vektor::get_x() { return x; } // Definition der GET - Methode für die x-Koordinate double vektor::get_y() { return y; } // Definition der GET - Methode für die y-Koordinate void vektor::set_x(double x_neu) { x = x_neu; } // Definition der SET - Methode für die x-Koordinate void vektor::set_y(double y_neu) { y = y_neu; } // Definition der SET - Methode für die y-Koordinate double vektor::laenge() { double z = sqrt( x*x + y*y ); return z; } // Definition einer Methode vektor vektor::operator+(vektor a) // Definition einer Methode { vektor V; V.x = x + a.x; V.y = y + a.y; return V; } Ändern Sie nun noch den Code der Funktion main() so, dass die Summe der Vektoren A und B mit Hilfe des + Operators gebildet und dem Vektor C zugewiesen wird. Die Länge des Vektors C soll genauso wie in der vorigen Aufgabe ausgegeben werden . Prof. Dr. Alfred H. Gitter (2006 - 2011) 40 26 Komplexe Zahlen ( 1 ) Zum Rechnen mit komplexen Zahlen gibt es in der Laufzeitbibliothek von C++ die Klasse complex mit der Header-Datei <complex> . Man kann sich aber auch selbst eine solche Klasse machen. Aufgabe: Öffnen Sie eine neue Textdatei (p_14.cpp) und tippen Sie folgendes Programmgerüst ein: // komplex #include <iostream> using namespace std; class komplex // Definition der Klasse komplex { private: // Zugriffsspezifizierer: nicht öffentlich double Re, Im; // Definition von Eigenschaften public: // Zugriffsspezifizierer: öffentlich komplex(); // Deklaration des Konstruktors ohne Parameter komplex(double p_1, double p_2); // Deklaration des Konstruktors mit Parametern double get_Re(); double get_Im(); // Deklaration öffentlicher GET-Methoden und void set_Re(double Re_neu); void set_Im(double Im_neu); // öffentlicher SET-Methoden komplex plus(komplex k); // Deklaration einer Methode zur Addition komplex mal(komplex k); // Deklaration einer Methode zur Multiplikation }; // hier fehlt die Definition der Methoden der Klasse komplex int main() { komplex a, b(3.0,7.0); // Instanzbildung mit Hilfe der Konstruktoren a.set_Re(2.0); a.set_Im(5.0); // Wertzuweisungen mit Hilfe der öffentlichen SET-Methoden komplex c = a.plus(b); cout << "a + b ist " << c.get_Re() << " + " << c.get_Im() << " i" << endl; c = a.mal(b); cout << "a mal b ist " << c.get_Re() << " + " << c.get_Im() << " i" << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } - Ergänzen Sie in obigem Programm die fehlenden Definitionen für die Methoden der Klasse komplex , so dass ein funktionierendes Programm entsteht. 27 Komplexe Zahlen ( 2 ) : mehrere Quellcode-Dateien Bei großen Programmen ist es sinnvoll, den Quelltext auf mehrere Dateien zu verteilen. Eine Datei enthält das Hauptprogramm mit der Funktion main() . Für jede Klasse wird eine Header-Datei mit der Klassendefinition und eine zweite Datei mit den Methodendefinitionen angelegt. Zunächst soll das Hauptprogramm mit der Funktion main() in der Datei p_14.cpp gespeichert werden und danach die Methodendefinitionen in der Datei komplex.cpp gespeichert werden. Aufgabe: a) Kopieren Sie den Inhalt der Datei p_14.cpp in zwei neue Dateien komplex.cpp und komplex.h . b) Öffnen Sie die Datei p_14.cpp und löschen Sie die Klassendefinition und die Methodendefinitionen; die Funktion main() bleibt aber. Da das Ausgabeobjekt cout verwendet wird, braucht man die Einbindung der Headerdatei <iostream> mit dem Namensraum std . Ausserdem wird der Datentyp komplex verwendet und man muss die Headerdatei komplex.h einbinden. Da diese Header-Datei im gleichen Verzeichnis steht wie die Datei p_14.cpp , wird sie mit ihrem Namen in doppelten Anführungszeichen ( nicht mit < und > ) eingebunden. Die Datei p_14.cpp soll dann folgenden Code enthalten : Prof. Dr. Alfred H. Gitter (2006 - 2011) 41 // komplex Hauptprogramm #include <iostream> #include "komplex.h" using namespace std; int main() { komplex a, b(3.0,7.0); // Instanzbildung mit Hilfe der Konstruktoren a.set_Re(2.0); a.set_Im(5.0); // Wertzuweisungen mit Hilfe der öffentlichen SET-Methoden komplex c = a.plus(b); cout << "a + b ist " << c.get_Re() << " + " << c.get_Im() << " i" << endl; c = a.mal(b); cout << "a mal b ist " << c.get_Re() << " + " << c.get_Im() << " i" << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } c) Öffnen Sie die Datei komplex.cpp und löschen Sie die Funktion main() und die Klassendefinition; die Methodendefinitionen bleiben aber. Die Datei soll dann folgenden Code enthalten : // komplex Methodendefinitionen #include "komplex.h" komplex::komplex() { Re = 0; Im = 0; } // Konstruktor ohne Parameter komplex::komplex( double p_1, double p_2 ) { Re = p_1; Im = p_2; } // Konstruktor mit Parametern double komplex::get_Re() { return Re; } // Definition der GET - Methode für den Realteil double komplex::get_Im() { return Im; } // Definition der GET - Methode für den Imaginärteil void komplex::set_Re(double Re_neu) { Re = Re_neu; } // Definition der SET - Methode für Re void komplex::set_Im(double Im_neu) { Im = Im_neu; } // Definition der SET - Methode für Im komplex komplex::plus(komplex k) // Definition einer Methode zur Addition { komplex z; z.Re = Re + k.Re; z.Im = Im + k.Im; return z; } komplex komplex::mal(komplex k) // Definition einer Methode zur Multiplikation { komplex z; z.Re = Re * k.Re - Im * k.Im; z.Im = Re * k.Im + Im * k.Re; return z; } d) Compilieren Sie das Programm und führen es aus ! 28 Komplexe Zahlen ( 3 ) : Header-Datei Bei großen Programmen ist es sinnvoll, den Quelltext auf mehrere Dateien zu verteilen. Eine Datei enthält das Hauptprogramm mit der Funktion main() . Für jede Klasse wird eine Header-Datei mit der Klassendefinition und eine zweite Datei mit den Methodendefinitionen angelegt. Aufgabe: Öffnen Sie die Datei komplex.h und löschen Sie die Funktion main() , die Methodendefinitionen, die nicht benötigte #include-Direktive #include <iostream> und die nicht benötigte Anweisung zur Benutzung des Standard-Namensraums. Die Klassendefinition bleibt. Nun werden drei Direktiven eingefügt, welche die Mehrfacheinbindung der Header-Datei komplex.h verhindern (nachfolgend durch Fettdruck markiert). Die Datei soll schließlich folgenden Code enthalten : // komplex Header-Datei #ifndef komplex_header #define komplex_header Prof. Dr. Alfred H. Gitter (2006 - 2011) 42 class komplex // Definition der Klasse komplex { private: // Zugriffsspezifizierer: nicht öffentlich double Re, Im; // Definition von Eigenschaften public: // Zugriffsspezifizierer: öffentlich komplex(); // Deklaration des Konstruktors ohne Parameter komplex(double p_1, double p_2); // Deklaration des Konstruktors mit Parametern double get_Re(); double get_Im(); // Deklaration öffentlicher GET-Methoden und void set_Re(double Re_neu); void set_Im(double Im_neu); // öffentlicher SET-Methoden komplex plus(komplex k); // Deklaration einer Methode zur Addition komplex mal(komplex k); // Deklaration einer Methode zur Multiplikation }; #endif Die Compilierung mehrerer Quellcode-Dateien wurde auf Seite 1, Hinweis 6 bereits erläutert. Schreiben Sie die Kommandozeile g++ p_14.cpp komplex.cpp –o p_14.exe - Zur Ausführung des Programms schreiben Sie nun die Kommandozeile ./p_14.exe Das aus zwei Quellcode-Dateien und einer Headerdatei übersetzte Programm sollte, wie zuvor die einfache Quellcode-Datei, ausgeführt werden. 29 Komplexe Zahlen ( 4 ) : complex ( freiwillig, nur für Fortgeschrittene ) Man braucht zum Rechnen mit komplexen Zahlen keine eigenen Klasse zu definieren, denn es gibt eine fertige Lösung in der C++ - Bibibliothek, die mit #include <complex> eingebunden wird. Komplexe Zahlen bestehen aus einem Zahlenpaar (a,b) mit Realteil a und Imaginärteil b. Es macht in der Regel keinen Sinn, a und b als ganze Zahlen zu definieren. In C++ können die Zahlen a und b als Gleitpunktzahlen vom Typ float , double oder long double definiert werden. Um nicht für jeden Datentyp eine eigene Klasse für komplexe Zahlen zu definieren, gibt es eine sogenannte Template-Klasse complex . Bei der Deklaration wird der Typ in spitzen Klammern hinter den Template-Namen gesetzt: complex<double> a ; Deklaration der komplexen Variablen a mit Real- und Imaginärteil vom Typ double complex<double> b(1.5, -3.3) ; Deklaration und Initialisierung der Variablen b mit Realteil 1.5 und Imaginärteil –3.3 Für diese Klasse gibt es öffentliche Methoden zum Setzen und Abfragen von Real- und Imaginärteil: double x = b.real() ; Deklaration der double-Variablen x und und Initialisierung mit dem Realteil von b b = complex<double>( b.imag() , 4.4 ); Setzt den Imaginärteil der Variablen b auf den Wert 4.4. Für das Rechnen mit den komplexen Zahlen sind die üblichen Operatoren für Adddition ( + ), Subtraktion ( - ), Multiplikation ( ) und Division ( / ) definiert. Ausserdem gibt es spezielle komplexe Funktionen. a=a–b; Der komplexen Variablen a wird eine Differenz komplexer Zahlen zugewiesen. x = abs(a) ; Der double-Variablen x wird der Betrag der komplexen Variablen a zugewiesen. Aufgabe: Öffnen Sie eine neue Textdatei (p_15.cpp) und schreiben Sie ein Programm, welches das gleiche macht wie das in Datei p_14.cpp gespeicherte Programm. Es soll aber nicht die selbst definierte Klasse komplex sondern die Template-Klasse complex aus der C++ - Bibliothek verwendet werden. Prof. Dr. Alfred H. Gitter (2006 - 2011) 43 30 In einer Datei schreiben und lesen Die Ein- und Ausgabe von Daten in Dateien beruht auf dem Konzept der Streams. Streams sind Datenströme, die von einer Quelle zu einer Senke (Ziel) fließen. Sie werden durch Objekte repräsentiert. Aufgabe: Öffnen Sie eine neue Textdatei (p_16.cpp) und tippen Sie folgendes Programm ein : // Datei I/O #include <fstream> #include <iostream> #include <string> using namespace std; int main() { string name = "datei"; double x = 12.34; double y = 56.78; // ofstream schreib; schreib.open("datei", ios_base::out); schreib << x << endl; schreib << y << endl; schreib.close(); // x = 99.99; y = 99.99; // // Diese Zeile soll ersetzt werden. // ifstream lies; lies.open(name.c_str(), ios_base::in); // Header-Datei für das Schreiben und Lesen in Dateien // Variable name enthält den Namen der Eingabedatei // ( input file ), aus der das Programm Daten einliest. // Öffnen des Dateiausgabe-Streams "schreib" // bei älteren Compilern: "ios" statt "ios_base" // Falls eine alte Datei "datei" existierte, // wird sie hiermit ersetzt (überschrieben). // Schließen des Streams "schreib" // Öffnen des Dateieingabe-Streams "lies" // Die Methode c_str() wandelt ein String-Objekt in ein // Zeichenketten-Format, das von open() verstanden wird. // Wenn die Datei "datei" nicht geöffnet werden kann, // behalten die Variablen x und y ihren Wert (99.99). // Schließen des Streams "lies" lies >> x ; lies >> y ; lies.close(); // cout << "x ist " << x << " ." << endl; cout << "y ist " << y << " ." << endl; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } Ersetzen Sie in obigem Programm die Kommentarzeile " // Diese Zeile soll ersetzt werden. " durch eine Abfrage des Namens der Eingabedatei (input file) und die Zuweisung an die Variable name . - Testen Sie das Programm mit verschiedenen Namen der Eingabedatei (" datei " oder " xxx "). Prof. Dr. Alfred H. Gitter (2006 - 2011) 44 31 Debugging Wenn aus einem Quellcode von Compiler und Linker ohne Fehlermeldung und ohne Warnung ein ausführbares Programm erzeugt wurde, ist der Quellcode syntaktisch fehlerfrei. Oft macht das Programm aber nicht das, was es soll, weil der der Programmierer semantische Fehler eingebaut hat. Um die Suche dieser Fehler zu erleichtern, kann man einen Debugger benutzen. Damit kann man das zu untersuchende Programm schrittweise ausführen und an bestimmten Stellen den aktuelle Wert von Variablen anschauen. Das folgende, syntaktisch richtige Programm soll im natürlichen Zahlbereich zwischen 25 und 30 Kubikzahlen finden. Da 3 mal 3 mal 3 gleich 27 ist, erwartet man (vergeblich) als Ausgabe des Programms "27 ist eine Kubikzahl". Mit Hilfe des Debuggers soll der Fehler gefunden werden. Befehle für den gdb-Debugger in der Konsole (Beispiele) : break 11 Haltepunkt (break point) an das Ende von Zeile 11 setzen run die Ausführung es Programms starten, aber am Haltepunkt stoppen print n den aktuellen Wert der Variablen n ausgeben next nächste Zeile ausführen lassen step in die aufgerufene Funktion springen quit gdb-Debuger beenden Aufgabe: Öffnen Sie eine neue Textdatei (p_17.cpp) und tippen Sie folgendes Programm ein : // Kubik #include <iostream> #include <cmath> using namespace std; bool kubik(int j) { double x ; int n ; bool w ; x = double(j) ; n = int( pow(x,(1/3)) ) ; w = (x == n*n*n) ; return w; } int main() { int i = 25 ; double x ; for ( x = 1.5 ; i<=30 ; i=i+1 ) { if ( kubik(i) ) cout << i << " ist eine Kubikzahl" << endl ; } return 0 ; } - Übersetzen Sie das Programm zunächst wie gewohnt. ( Der Compiler sollte keine Fehlermeldung und keine Warnung zeigen. ) Lassen Sie das Programm ausführen. Es erfolgt keine Ausgabe. Übersetzen Sie dann das Programm nochmal, aber mit Informationen für den Debugger : g++ -ggdb p_17.cpp –o p_17 - Lassen Sie das Programm im gdb-Debugger ausführen: gdb p_17 - Compilierung mit Informationen für das gdb-Debugger-Programm Programm Kubik im gdb-Debugger starten Suchen Sie mit dem Debugger den semantischen Fehler im Programm. Danach sollen Sie den Fehler korrigieren und das fehlerfreie Programm ohne Debugger ausführen lassen. Prof. Dr. Alfred H. Gitter (2006 - 2011) 45 32 Translation Oben (18) wurde beschrieben wie Zeichenketten mit Hilfe der Klasse string bearbeitet werden. Für string-Objekte gibt es, unter anderem, die Methoden length() und substr(i,n) : string satz; satz = "Guten Tag, Ute, wie geht es Dir?"; // Deklaration eines Strings und Wertzuweisung int L ; L = satz.length() ; // satz.length() gibt die Anzahl der Zeichen im String satz zurück int i = 0, n = 3 ; string trp; trp = satz.substr ( i , n ) ; // Gibt den aus n Zeichen bestehenden Teilstring // von String satz ab Position i ( im Beispiel mit i = 0 und n = 3 die drei Zeichen am Anfang von satz ) Aufgabe: Öffnen Sie eine neue Textdatei (p_18.cpp) und tippen Sie folgendes Programmgerüst ein. Ergänzen Sie es zu einem funktionierenden Programm, welches die vorgegebene DNA-Zeichenkette dna="gcgttctttgag" in die zugehörige Aminosäresequenz übersetzt ! // Translation #include <iostream> #include <string> using namespace std; int main() { int i,j,n; string dna="gcgttctttgag", ass="", trp; string cod[100]; // Array, das alle möglichen Codons ( = DNA-Tripletts ) aufnimmt string as1[100]; // Array für die zugehörigen einbuchstabigen Aminosäure-Codes cod[1] = "gct"; as1[1] = "A"; cod[2] = "gcc"; as1[2] = "A"; cod[3] = "gca"; as1[3] = "A"; cod[4] = "gcg"; as1[4] = "A"; cod[5] = "tgt"; as1[5] = "C"; cod[6] = "tgc"; as1[6] = "C"; cod[7] = "gat"; as1[7] = "D"; cod[8] = "gac"; as1[8] = "D"; cod[9] = "gaa"; as1[9] = "E"; cod[10] = "gag"; as1[10] = "E"; cod[11] = "ttt"; as1[11] = "F"; cod[12] = "ttc"; as1[12] = "F"; cod[13] = "tta"; as1[13] = "F"; cod[14] = "ttg"; as1[14] = "F"; cod[15] = "ggt"; as1[15] = "G"; cod[16] = "ggc"; as1[16] = "G"; cod[17] = "gga"; as1[17] = "G"; cod[18] = "ggg"; as1[18] = "G"; // hier soll das Programm ergänzt werden cout << endl << "Die DNA-sequenz ist: " << dna << endl; cout << "Die DNA-Sequenz besteht aus " << dna.length() << " Zeichen."; cout << endl << "Die AS-sequenz ist: " << ass << endl; cout << "Die AS-Sequenz besteht aus " << ass.length() << " Zeichen."; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); return 0; } 33 Sequenz aus überlappenden Fragmenten Für Zeichenketten, die Objekte der Klasse string sind wurden die Methoden length() und substr(i,n) eingeführt (siehe 32). Außerdem kann man Vergleichsoperatoren ( z.B. == ) auch auf Strings anwenden. Aufgabe: Schreiben Sie ein Programm, das einen Satz ( String seq_n; ) aus zwei überlappenden Fragmenten ( String string seq_1="Jena ist eine wunderschoene"; string seq_2="schoene kleine Stadt."; ) zusammensetzt. Der überlappende Abschnitt sei seq_i. Die Reihenfolge der Fragmente wird als bekannt vorausgesetzt, aber der überlappende Abschnitt muss vom Programm ermittelt werden. Die Ausgabe soll so aussehen: - Speichern Sie das Programm in einer Textdatei (p_19.cpp). Prof. Dr. Alfred H. Gitter (2006 - 2011) 46 34 Globales Alignment mittels Dynamic Programming Bei einem paarweisen Alignment werden zwei Zeichenketten so untereinander angeordnet, dass jedem Zeichen der einen (oberen) Sequenz ein Zeichen der anderen (unteren) Sequenz oder eine Lücke (gap) gegenübersteht. Durch diese Schreibweise kann man leicht erkennen, wie viele Editieroperationen (Substitution, Insertion oder Deletion) nötig sind, um die eine (obere) Sequenz in die andere (untere) umzuwandeln. Bei einem optimalen Alignment ist die (gewichtete) Summe der Editieroperationen minimal. Ein globales Alignment ist ein optimales Alignment, welches alle Zeichen der beiden Sequenzen enthält. Ein geeignetes mathematisches Verfahren hierfür ist Dynamic Programming. Aufgabe: Schreiben Sie ein Programm, das durch Dynamic Programming ein globales Alignment von den beiden Sequenzen cgtagc und ctaga erzeugt. Als Ergebnis sollen auf dem Bildschirm die beiden Sequenzen, die beim Dynamic Programming erzeugte Scoring-Matrix, ein globales Alignment (weitere, gleich gute Lösungen sollen nicht betrachtet werden) und der Score für das Alignment (Summe aller Bewertungen der Editieroperationen) ausgegeben werden. Die Ausgabe soll ungefähr so aussehen: - Speichern Sie das Programm in einer Textdatei (p_20.cpp). Prof. Dr. Alfred H. Gitter (2006 - 2011) 47 Prof. Dr. Alfred H. Gitter (2006 - 2011) 48