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