Eine Einführung in die Programmiersprache C++ Thomas Kastl 5. April 2004 Die folgenden Ausführungen zu C++ stellen keine vollständige Beschreibung dieser Programmiersprache dar. Es sind hauptsächlich die Bestandteile der Sprache beschrieben, die auch in der Programmiersprache C zu finden sind. 1 Compiler Um von einer Problemstellung zu einem lauffähigen Programm zu gelangen, sind mehrere Schritte notwendig. Der Programmtext (Quellcode) muss zunächst mit einem Texteditor (nedit, emacs, ...) in Form einer ASCII-Datei (Quelldatei) gespeichert werden. Der Dateiname sollte dabei mit .cpp enden. Danach wird der Compiler (Übersetzer) unter Angabe der Quelldatei(en) aufgerufen $ CC kreis.cpp -o kreis Hierbei übersetzt der Compiler (CC) die Quelldatei kreis.cpp in ein ausführbares Programm (“Executable”) mit dem Namen kreis. Die Erzeugung des ausführbaren Programmes führt der Compiler in mehrerem Phasen durch. Die Dateien werden zuerst vom Präprozessor bearbeitet, d.h. es wird die sogenannte Makroverarbeitung ausgeführt. Dabei werden sämtliche Präprozessor-Kommandos (beginnend alle mit #) durch entsprechenden Programmcode ersetzt. Die Anweisung #include <iostream.h> bewirkt etwa, dass diese Zeile durch die gesamte Header-Datei iostream.h ersetzt wird. Das Ergebnis dieses “Preprocessing”, der interne Quelltext, wird schließlich von dem eigentlichen Compiler weiterverarbeitet. 1 2 GRUNDLAGEN Getrennte Übersetzung Für größere Projekte ist es sinnvoll den Programmcode auf mehrere Quelldateien aufzuteilen. Die physische Trennung eines Programms in getrennte Dateien sollte dabei von der logischen Struktur des Problemes geleitet werden. Der Compiler übersetzt nun jede einzelne Quelldatei für sich. Dabei ist zu beachten, dass der Compiler beim Übersetzen einer Datei nichts vom Inhalt der anderen Dateien weiß. Um eine getrennte Übersetzung zu ermöglichen, muss der Programmierer daher Deklarationen für Funktionen zur Verfügung stellen. Nach dem Übersetzen aller Quelldateien wird der sogenannte Linker (Binder) gestartet. Der Linker ist ein Programm, das die getrennt übersetzten Teile zusammenbindet. 2 Grundlagen Mit dem folgenden C++-Quellcode kann ein Programm generiert werden, dass den Umfang und die Fläche eines Rechtecks berechnet: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // rechteck.cpp 21.11.2002 /* berechnet den Umfang und die Flaeche eines Rechteckes nach der Eingabe von Laenge und Breite */ #include <iostream.h> // zusaetzliche Funktionen /* Hauptprogramm */ int main() { float laenge, breite; // Variablendeklaration cout << "Umfang- und Flaechenberechnung eines Rechteckes\n\n"; // Eingabe von Laenge und Breite cout << "Bitte die Laenge eingeben: "; cin >> laenge; cout << "Bitte die Breite eingeben: "; cin >> breite; 2 2 GRUNDLAGEN 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 if ((laenge > 0) && (breite > 0)) { float flaeche, umfang; // Variablendeklaration // Flaeche und Umfang berechnen flaeche = laenge * breite; umfang = 2.0 * (laenge + breite); // Resultate ausgeben cout << endl; cout << "Umfang = " << umfang << endl; cout << "Flaeche = " << flaeche << endl; } else { cout << endl; cout << "Falsche Eingabe !!!\n"; } } Die Ausführung des “Executable” erzeugt auf dem Bildschirm den folgendem Dialog zwischen Anwender und Computer: Umfang- und Flaechenberechnung eines Rechteckes Bitte die Laenge eingeben: 3 Bitte die Breite eingeben: 4 Umfang = 14 Flaeche = 12 Kommentare In C++ werden Kommentare mit zwei aufeinanderfolgenden Schrägstrichen (//) eingeleitet, d.h. der Compiler ignoriert alle folgenden Zeichen bis zum jeweiligen Zeilenende. Zusätzlich können Passagen von beliebiger Größe durch /* ... */ auskommentiert werden (Zeilen 3-6). Struktur eines C++-Programmes Ein einfaches C++-Programm hat die folgende Struktur: 3 3 DATENTYPEN, VARIABLEN, KONSTANTEN #include <Headerdatei> // Header-Datei(en) einbinden Datentyp Funktion(Parameterliste) { Anweisungen; } // Funktionsdefinition int main() { Anweisungen; } // Hauptprogramm Die Anweisung #include teilt dem Präprozessor mit, dass eine sogenannte HeaderDatei eigefügt werden soll. Diese enthält u.a. Funktionsdeklarationen (vgl. Abschnitt 9.3). Aufgrund deren Einbindung können in der aktuellen Quelldatei zusätzlichen Funktionen verwendet werden. Die Funktion main() muss in jedem C++-Programm genau einmal vorkommen, denn bei der Ausführung eines Programms wird genau diese Funktion abgearbeitet. Alle Anweisungen, die in einer Funktion ausgeführt werden sollen, müssen in einem Block zusammengefasst werden. Blöcke werden in C++ von geschweiften Klammern ({ ... }) umschlossen. C++ ist eine formatfreie Sprache, d.h. zusätzlich eingefügte Füllzeichen (Leerzeichen, Tabulatoren, Zeilenumbrüche) haben keine Auswirkungen auf den Quellcode. Damit der Compiler erkennt, an welcher Stelle eine Anweisung aufhört, müssen diese mit einem Semikolon (;) abgeschlossen werden. Außerdem ist C++ case-sensitive, d.h. es wird zwischen Groß- und Kleinschreibung unterschieden. Um einen wiederholt auszuführenden Programmteil auszulagern, können in einem C++Programm Funktionen definiert werden. Diese werden in Abschnitt 9 näher vorgestellt. 3 Datentypen, Variablen, Konstanten Eine Variable bezeichnet einen Hauptspeicherbereich, in dem man zu unterschiedlichen Zeitpunkten beim Programmablauf unterschiedliche Werte abspeichern kann. Variablenzuweisung Mit der Zuweisung laenge = 5.3; wird der Wert 5.3 in der Variablen laenge gespeichert. Zu einem späteren Zeitpunkt kann mit 4 3 DATENTYPEN, VARIABLEN, KONSTANTEN laenge = 12.6; der Wert der Variablen auf 12.6 abgeändert werden. Deklaration von Variablen C++ ist eine typbasierte Sprache, d.h. jede Variable besitzt einen Datentyp, der vor ihrer ersten Verwendung bestimmt werden muss. Mit der Deklaration einer Variablen wird dem Compiler der Datentyp dieser Variablen mitgeteilt. Der Compiler kann nun dafür sorgen, dass ein dem Datentyp entsprechender Speicherbereich für diese Variable reserviert wird. Mit der Anweisung float laenge; wird die Variable laenge vom Fließkommadatentyp float deklariert. Bei der esten Zuweisung (Initialisierung der Variablen) wird deshalb ein Speicherbereich von 32 Bit für das Datenobjekt laenge reserviert. Es stehen in C++ eine Vielzahl von elementaren Datentypen zur Verfügung: Datentyp char short int int unsigned int long int Bit 8 16 (16)32(64) 16 32(64) Min. -128 -32768 -2147483648 0 -2147483648 Max. 127 32767 2147483647 65535 2147483647 Tabelle 1: Ganzzahldatentypen Die Datenbitbreite für die Typen int und long int hängt vom verwendeten Compiler und Betriebssystem ab. Datentyp float double long double Bit 32 64 80 Min. ca. 3.4E-38 ca. 1.7E-308 ca. 1.2E-4932 Max. ca. 3.4E38 ca. 1.7E308 ca. 1.2E4932 Tabelle 2: Fließkommadatentypen Zusätzlich können auch eigene Datentypen entwickelt werden. Mehrere Variablen vom gleichen Typ können durch Kommas (,) getrennt in einer Zeile deklariert werden: float laenge, breite, hoehe; 5 4 EIN- UND AUSGABEFUNKTIONEN Initialisierung von Variablen Die Initialisierung einer Variablen kann direkt bei ihrer Deklaration erfolgen float durchmesser = 10.6; oder sich erst zu einem späteren Programmzeitpunkt ergeben: float durchmesser, radius; ... durchmesser = 2.0 * radius; ... // Deklaration // Initialisierung (erste Zuweisung) Der Gültigkeitsbereich einer Variablen reicht bis zum Ende des Blocks, in dem sie deklariert wurde. Sie gilt zusätzlich in allen Blöcken, die innerhalb dieses Blockes nach der Deklaration der Variablen auftreten. Außerhalb dieses Blockes ist die Variable unbekannt. Gültige Namen Zur Konstruktion von Namen für Variablen, Konstanten oder Funktionen sind in C++ nur alphanumerische Zeichen (A..Z, a..z, 0..9), sowie der Unterstrich (_) erlaubt. Zusätzlich darf ein gültiger Name nicht mit einer Ziffer beginnen. Groß- und Kleinschreibung wird unterschieden. Konstanten Werte, die sich während des Programmablaufes nicht mehr verändern sollen, können in C++ als Konstanten definiert werden. Konstanten werden mit dem vorangestellten Schlüsselwort const wie Variablen deklariert und müssen sofort initialisiert werden: const float PI = 3.14159; Der dem Namen zugewiesene wert kann nach der Deklaration nirgendwo mehr im Programm verändert werden. 4 Ein- und Ausgabefunktionen Unter C++ steht eine komfortabele Ein- und Ausgabe mittels Datenströmen (streams) zur Verfügung. 6 4 EIN- UND AUSGABEFUNKTIONEN Standardausgabe Mit der Anweisung cout << "Bitte eine Zahl eingeben: "; folgt die Ausgabe Bitte eine Zahl eingeben: auf der Standardausgabe (d.h. i.a. auf dem Bildschirm). Der Einschiebeoperator (<<) “schiebt” dabei den auszugebenden Text “Bitte eine Zahl eingeben: “ zum Standardausgabegerät. Mit cout kann auch der Inhalt beliebiger Variablen auf der Standardausgabe abgebildet werden. Die Anweisungen float zahl = 45.323; cout << zahl; liefern 45.323 auf der Standardausgabe. Eine Formatangabe wird dabei nicht benötigt. Ebenfalls können auch mehrere Datenströme hintereinander zur Standardausgabe “geschoben” werden. Die Anweisungen int ganze_zahl = 23; float fliesskommazahl = 423.3423; cout << "Zahlentypen in C++: \n" << endl << "Ganze Zahl: " << ganze_zahl << endl << "Fliesskommazahl: " << fliesskommazahl << endl; führen zur Ausgabe: Zahlentypen in C++ : Ganze Zahl: 23 Fliesskommazahl: 423.3423 Mit dem Befehl endl oder mit der Zeichenkette "\n" kann Zeilenumbruch erzwungen werden. 7 5 OPERATOREN UND AUSDRÜCKE Standardeingabe Bei dem Gegenstück zu cout, der Anweisung cin >> zahl; wird eine Eingabe vom Standardeingabegerät (i.a. der Tastatur) zur angegebenen Variablen zahl “geschoben”. Man beachte, dass der Einschiebeoperator (>>) hierbei in Richtung der Variablen zeigt. Ist die Tastatur als Standardeingabegerät definiert, so erwartet das Programm an dieser Stelle eine Eingabe vom Anwender. Alle weiteren Anweisungen werden vom Programm erst dann ausgeführt, wenn der Anwender seine Eingabe mit der Return-Taste abgeschlossen hat. Die Eingabe wird nicht automatisch auf ihre Gültigkeit hin untersucht, d.h. der Programmentwickler muss dafür sorgen, dass eventuell “unsinnige” Eingaben vom Programm abgefangen werden. Die Befehle für die Ein- und Ausgabe gehören nicht zum Grundwortschatz von C++ . Daher muss am Anfang mit der Präprozessor-Anweisung #include <iostream.h> die Header-Datei iostream.h in die Quelldatei eingebunden werden. Nach dem Einbinden der Header-Datei iomanip.h stehen zusätzlich noch einige Ausgabe-Manipulatoren zur Verfügung. Die Anweisung cout << setprecision(9); etwa legt eine Ausgabe von insgesamt 9 Stellen für Fließkommazahlen fest. Mit cout << setw(10) << zahl; kann das Eingabefeld für zahl auf eine Breite von 10 Zeichen beschränkt werden. 5 Operatoren und Ausdrücke In seiner einfachsten Form besteht ein Ausdruck aus einem Wert oder einer Variablen. In dieser Form wird er auch Primärausdruck genannt. Ausdrücke können durch Operatoren miteinander verknüpft werden. Ein in dieser Weise entstandenes Konstrukt bildet wiederum einen Ausdruck. Ausdrücke, auf die ein Operator angewendet wird, werden die Operanden des Operators genannt. 8 5 OPERATOREN UND AUSDRÜCKE 5.1 Arithmetische Operatoren In C++ existieren eine Vielzahl von Operatoren mit denen Ausdrücke verknüpft oder manipuliert werden können. Manche Operatoren können allerdings nicht auf beliebige Ausdrücke angewendet werden, so sind für Operanden solcher Operatoren z.B. nur Variablen von einem bestimmten Datentyp zugelassen. Operatoren können nach verschiedenen Gesichtspunkten klassifiziert werden. Zum einen besitzt jeder Operator eine Prioritätsstufe, die angibt, in welcher Reihenfolge Operatoren ausgewertet werden, wenn mehrere Operatoren in einem Ausdruck enthalten sind. Operatoren mit einer höheren Priorität werden immer vor Operatoren mit einer niedrigeren Priorität abgearbeitet. Jede Prioritätsstufe besitzt zusätzlich eine festgelegte Abarbeitungsrichtung. Diese gibt an, in welcher Reihenfolge Operatoren gleicher Prioritätsstufe innerhalb eines Ausdrucks ausgewertet werden. Ein weiteres Unterscheidungsmerkmal ist der Typ eines Operators. Der Typ eines Operator bezieht sich auf die Anzahl seiner Operanden. Unäre Operatoren wirken nur auf einen, binäre wirken auf zwei und ternäre Operatoren wirken auf drei Operanden. Zusätzlich gibt es noch primäre Operatoren, welche über die höchste Prioritätsstufe definiert sind. Im folgenden sind einige wichtige Operatoren nach Funktionsgruppen geordnet aufgeführt. 5.1 Arithmetische Operatoren Operator + * / % Bedeutung Addition Subtraktion Multiplikation Division Modulo Priorität 5 5 4 4 4 Typ binär binär binär binär binär Richtung > > > > > Tabelle 3: Arithmetische Operatoren Der Multiplikationsoperator (*) und der Divisionsoperator (\) haben eine höhere Priorität (je niedriger die Zahl, desto höher ist die Prioritätsstufe) als Additions- (+) und Subtraktionsoperator (-). Untereinander liegen sie jeweils auf der selben Prioritätsstufe mit einer Abarbeitungsrichtung von “links nach rechts”. Deshalb ist der Ausdruck a + 4 * 5 - b + 4 gleichbedeutend mit dem geklammerten Ausdruck (((a + (4 * 5)) - b) + 4) Der Modulo-Operator (%) darf nur auf ganzzahlige (Integer-) Datentypen angewendet werden, alle andern arithmetischen Operatoren können für Ganz- und Fließkommazahlen verwendet werden. 9 5 OPERATOREN UND AUSDRÜCKE 5.2 5.2 Relationale Operatoren Relationale Operatoren Operator < <= > >= == != Bedeutung kleiner als kleiner oder gleich größer größer oder gleich gleich ungleich Priorität 7 7 7 7 8 8 Typ binär binär binär binär binär binär Richtung > > > > > > Tabelle 4: Relationale Operatoren Das Ergebnis eines Vergleichs zweier Ausdrücke ist immer einer der beiden Integerwerte 0 (logisch unwahr) oder 1 (logisch wahr). Ein beliebiger Wert ungleich 0 wird ebenfalls als logisch wahr angesehen. Die Anweisungen int a = 2, b, c; b = 3 > a; c = 3 < a; ordnen der Variablen b den Wert 1 (wahr) zu und der Variablen c den Wert 0 (unwahr). Vergleichsausdrücke werden in der Regel in Verzweigungen oder in Schleifen verwendet (vgl. Abschnitt 6). Der Vergleichsoperator (==) darf nicht mit dem Zuweisungsoperator (=) verwechselt werden. Die Anweisung if (x = 3) { Anweisungen; } wird vom Compiler nicht als Fehler erkannt! Der Ausdruck x = 3 wird mit dem Wert von x, also mit 3, einem Wert ungleich 0 bewertet. Daher ist dieser Ausdruck immer wahr, wodurch die Anweisungen immer ausgeführt werden. 5.3 Logische Operatoren Operator && || ! Bedeutung logisches AND logisches OR logische Negation Priorität 12 13 2 Typ binär binär unär Tabelle 5: Logische Operatoren 10 Richtung > > < 5 OPERATOREN UND AUSDRÜCKE 5.4 Zuweisungsoperatoren Die logischen Operatoren verknüpfen logische Werte (wahr (1) oder unwahr (0)) miteinander, wobei das Ergebnis aus der folgenden Tabelle herauszulesen ist. a 0 0 1 1 b 0 1 0 1 a && b 0 0 0 1 a || b 0 1 1 1 !a 1 1 0 0 Tabelle 6: Wahrheitstabelle für logische Verknüpfungen Die Anweisungen der Verzweigung if ((x >= 3) && (x <= 5)) { Anweisungen; } werden nur ausgeführt, wenn die Variable x einen Wert zwischen 3 und 5 besitzt. 5.4 Zuweisungsoperatoren Operator = += -= *= /= Bedeutung Zuweisung Zuw. nach Addition Zuw. nach Subtraktion Zuw. nach Multiplikation Zuw. nach Division Priorität 15 15 15 15 15 Typ binär binär binär binär binär Richtung < < < < < Tabelle 7: Zuweisungsoperatoren Bei den letzten vier Zuweisungsoperatoren erfolgt die Zuweisung erst nach der Ausführung des vorangestellten Operators. Dies ermöglicht eine verkürzte Schreibweise. Der Additionsoperator (+) hat eine höhere Priorität als der Zuweisungsoperator (=). Daher ist der Ausdruck a = a + 5; gleichbedeutend mit a = (a + 5); 11 6 KONTROLLSTRUKTUREN 5.5 Post- und Prefix-Operatoren Zuerst verknüpft der Additionsoperator (+) die Varable a und den Wert 5 zu dem Ausdruck a + 5. Danach wird der Variablen a dieser Ausdruck zugewiesen, d.h. die Variable a enthält jetzt den alten Wert von a “und” 5. Somit ist der Wert von a wird um 5 erhöht worden. Den gleichen Effekt erzielt man mit dem Ausdruck a += 5; Der Zuweisungsoperator (+=) erhöht zuerst den Wert der Variablen a auf der linken Seite um 5 und weist diesen um 5 erhöhten Wert wiederum der Variablen a zu. Auf der linken Seite einer Zuweisung muss immer eine Variable stehen. 5.5 Post- und Prefix-Operatoren Operator ++ -() [] Bedeutung Inkrement Dekrement Funktionsaufruf Arrayelement Priorität 1 1 1 1 Typ unär unär primär primär Richtung > > > > Tabelle 8: Post- und Prefix-Operatoren Werterhöhungen um 1 können auch mit dem unären Inkrementoperator (++) durchgeführt werden. Nach Abarbeitung der Ausdrücke a += 1; oder a++; besitzt die Variable a jeweils einen um 1 erhöhten Wert. Für Subtraktionen um 1 steht der Dekrementoperator zur Verfügung. 6 Kontrollstrukturen Normalerweise werden die Anweisungen in einem C++-Programm der Reihe nach abgearbeitet. Über die Kontrollstrukturen Schleifen und Verzweigungen kann dieser Programmfluss gezielt gesteuert werden. Verzweigungen ermöglichen es, das Programm in Abhängigkeit von bestimmten Eingaben oder Variablenbelegungen an vorbestimmten Stellen fortzusetzen. Mit Schleifen können Anweisungen beliebig oft wiederholt ausgeführt werden. 12 6 KONTROLLSTRUKTUREN 6.1 6.1 Schleifenanweisungen Schleifenanweisungen Eine Schleife wiederholt einen Block von Anweisungen, solange ein Ausdruck als wahr (d.h. ungleich 0) bewertet wird. Dieser Ausdruck wird auch Laufbedingung genannt. Kopfgesteuerte Schleife Die kopfgesteuerte Schleife überprüft die Laufbedingung zu Beginn eines jeden Schleifendurchlaufs. while (ausdruck) { anweisung1; anweisung2; ... } Die Anweisungen werden nicht mehr ausgeführt, sobald zu Beginn eines Schleifendurchlaufs der Ausdruck unwahr (d.h. gleich 0) ist. Fussgesteuerte Schleife Bei einer fussgesteuerte Schleife wird die Laufbedingung am Ende eines jeden Schleifendurchlaufs überprüft, d.h. die Schleife muss mindestens einmal durchlaufen werden. do { anweisung1; anweisung2; ... } while (ausdruck); Die Anweisungen werden nicht mehr ausgeführt, sobald am Ende eines Schleifendurchlaufs der Ausdruck unwahr ist. Zählschleife Auch bei der Zählschleife (for-Schleife) wird die Laufbedingung zu Beginn eines jeden Schleifendurchlaufs überprüft. 13 6 KONTROLLSTRUKTUREN 6.2 Verzweigungen for (Initialisierungsausdruck; Laufbedingung; Inkrementausdruck) { anweisung1; anweisung2; ... } Bei dieser Schleife wird zuerst einmalig der Initialisierungsausdruck ausgeführt. Falls die Laufbedingung erfüllt ist, wird der Anweisungsblock abgearbeitet. Nach jedem Schleifendurchlauf wird der Inkrementausdruck ausgeführt und die Laufbedingung erneut überprüft. Solange die Laufbedingung erfüllt bleibt, wird die Schleife durchlaufen. Zählschleifen können verwendet werden, wenn Anweisungen mit einer festen Anzahl von Wiederholungen ausgeführt werden sollen. int i; for (i = 0; i < 5; i++) { cout << i << endl; } Diese Schleife gibt alle ganzen Zahlen von 0 bis 4 aus. Der Variablen i wird zu Beginn der Wert 0 zugewiesen (Initialisierungsausdruck). Nach jedem Schleifendurchlauf (Ausgabe des Wertes von i) wird der Wert von i um 1 erhöht (Inkrementausdruck). Sobald die Variable i den Wert 5 erreicht (Laufbedingung ist verletzt), wird die Schleife abgebrochen und der Anweisungsblock nicht mehr ausgeführt. Eine Zählschleife kann auch eine kopfgesteuerte Schleife simulieren: for (; ausdruck;) { anweisung1; anweisung2; ... } Solange der Ausdruck wahr bleibt, werden die Anweisungen ausgeführt. 6.2 Verzweigungen Mit selektiven Steurungsanweisungen kann man dafür sorgen, dass bestimmte Programmblöcke nur unter bestimmten Bedingungen abgearbeitet werden. 14 6 KONTROLLSTRUKTUREN 6.2 Verzweigungen if-else-Verzweigung if (ausdruck) { anweisungA_1; anweisungA_2; ... } else { anweisungB_1; anweisungB_2; ... } Falls der Ausdruck als wahr (d.h. ungleich 0) bewertet wird, so führt das Programm den Anweisungsblock A aus. Andernfalls wird Anweisungsblock B abgearbeitet. Der alternative Anweisungsblock B ist optional, d.h. eine if-Anweisung kann ohne den else-Zweig implementiert werden. if (x > 3) { cout << x; } switch-Verzweigung Im Gegensatz zur if-else-Verzweigung ist die switch-Verzweigung eine Auswahlanweisung. switch(ausdruck) { case markeA: { anweisungA_1; anweisungA_2; ... break; } case markeB: { anweisungB_1; anweisungB_2; ... 15 6 KONTROLLSTRUKTUREN 6.2 Verzweigungen break; } ... default: { anweisungd_1; anweisungd_2; ... } } Der Ausdruck wird nacheinander mit allen Marken auf Gleichheit verglichen. Es wird derjenige Anweisungsblock ausgeführt, bei dessen Marke zum ersten Mal die Gleichheit zutrifft. Stimmt keine Marke mit dem Ausdruck überein, so wird der optionale Default-Anweisungsblock abgearbeitet. Der break-Befehl sorgt dafür, dass die switchVerzweigung nach Ausführung des entsprechenden Blockes sofort verlassen wird. Fehlt der break-Befehl, so werden auch die Anweisungen der folgenden Marken abgearbeitet. Mit der switch-Verzweigung kann eine Fallunterscheidung implementiert werden. int zeichen; cin >> zeichen; switch(zeichen) { case ’A’: { functionA(); break; } case ’B’: { functionB(); break; } default: { cout << "Falsche Eingabe!\n"; } } Gibt ein Anwender das Zeichen A ein, so wird die Funktion functionA() ausgeführt. Im Fall der Eingabe des Zeichens B wird die entsprechende Funktion functionB() aufgerufen. Eine falsche Eingabe produziert eine Fehlermeldung als Ausgabe. 16 8 DATEIBEHANDLUNG 7 Felder Wird in einem Programm eine Vielzahl von Daten vom gleichen Typ benötigt, so können diese Daten in einem gemeinsamen Feld (Array) untergebracht werden. Die Deklaration eines Feldes besteht aus einem Datentyp, dem die Elemente des Feldes angehören sollen, dem Namen des Feldes und der Angabe der Größe des Feldes. Die Angabe de Größe darf nur in positiven, konstanten Ausdrücken erfolgen. Durch int vector[20]; wird etwa ein Integer-Feld der Größe 20 deklariert. Aus dem Datentyp und der Anzahl der Feldelemente ergibt sich der Speicherplatzbedarf für das Array, d.h. in diesem Fall ist Speicher für 20 Integer-Werte reserviert. Das Feld vector kann somit 20 Integer-Werte aufnehmen. Die einzelnen Elemente eines Feldes werden über ihren ganzzahligen Index angesprochen. Das erste Element hat immer den Index 0, das letzte immer den Index Größe−1. const int N = 20; int vector[N]; int i; for (i = 0; i < N; i++) { vector[i] = i+1; } for (i = 0; i < N; i++) { cout << vector[i] << endl; } // Deklaration // Feld initialisieren // Feld ausgeben Als erstes wird ein Feld mit 20 Elementen vom Datentyp int unter dem Namen vector deklariert. Die erste for-Schleife initialisiert das Feld mit den Werten 1 bis 20. In der zweiten for-Schleife werden danach die Einträge der einzelnen Feldelemente ausgegeben. Damit erscheinen in der Standardausgabe alle Ganzen Zahlen von 1 bis 20. 8 Dateibehandlung Bei der Behandlung von Dateien wird in C++ ebenfalls das “Stream-Konzept” verwendet. Hierfür muss die Header-Datei fstream.h eingebunden werden. 17 8 DATEIBEHANDLUNG 8.1 8.1 Lesen aus einer Datei Lesen aus einer Datei Das folgende Programm liest Fließkommazahlen aus einer Datei Daten.inp und gibt sie auf dem Standardausgabegerät aus. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <fstream.h> int main() { double x; ifstream leseDatei; leseDatei.open("Daten.inp", ios::in); if (leseDatei.good()) { leseDatei >> x; while (!leseDatei.eof()) { cout << x << endl; leseDatei >> x; } leseDatei.close(); } else { cout << "Fehler beim Oeffnen der Datei\n"; } } Zum Lesen aus einer Datei wird durch ifstream leseDatei; ein Objekt der Klasse ifstream kreiert. Damit sind mit dem Objekt leseDatei automatisch einige Methoden zur Dateibehandlung “verbunden”. Mit der Methode leseDatei.open("Daten.inp", ios::in); wird die Datei Daten.inp für einen Lesezugriff geöffnet. Tritt beim Öffnen der Datei kein Fehler auf (d.h. die angegebene Datei existiert und ist lesbar), so liefert die Methode leseDatei.good() einen positiven Wert. In diesem Fall führt das Programm einen Lesezugriff aus (Block 11-20), andernfalls wird eine Fehlermeldung ausgegeben (Block 22-24). 18 8 DATEIBEHANDLUNG 8.2 Schreiben in eine Datei Der Lesezugriff wird mit dem Einschiebeoperator (>>) durchgeführt. Solange das Dateiende noch nicht erreicht wurde while (!leseDatei.eof()) wird die eingelesene Fließkommazahl auf dem Standardausgabegerät ausgegeben und durch leseDatei >> x; die nächste Fließkommazahl aus der Datei auf die Variable x “geschoben”. Der Anwender hat darauf zu achten, dass die Lese-Datei Daten.inp ausschließlich Fließkommazahlen enthält. Nach erfolgtem Lesezugriff wird die Datei mittels der Methode leseDatei.close(); wieder geschlossen. 8.2 Schreiben in eine Datei Das folgende Programm erzeugt die Datei Daten.inp mit den 10 Fließkommazahlen 1.5, 2.5 bis 10.5. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <fstream.h> int main() { double x; ofstream schreibeDatei; schreibeDatei.open("Daten.inp", ios::out); if (schreibeDatei.good()) { for (x = 1.5; x < 11.0; x=x+1.0) { schreibeDatei << x << endl; } schreibeDatei.close(); } 19 9 FUNKTIONEN else { cout << "Fehler beim Erzeugen der Datei\n"; } 19 20 21 22 23 } Zum Schreiben in eine Datei wird durch ofstream schreibeDatei; ein Objekt der Klasse ofstream kreiert. Wie beim Lesen enthält damit das Objekt schreibeDatei automatisch einige Methoden zur Dateibehandlung. Die Methode schreibeDatei.open("Daten.inp", ios::out); erzeugt die Datei Daten.inp und hält sie für einen Schreibzugriff offen. Eine eventuell schon vorhandene, lesbare Datei desselben Namens wird dabei gelöscht. Mit der Methode schreibeDatei.good() kann wie beim Lesen der Öffnungsvorgang auf Fehlerfreiheit überprüft werden. In die geöffnete Datei kann nun genauso wie auf die Standardausgabe mit Hilfe des Einschiebeoperators (<<) geschrieben werden schreibeDatei << x << endl; Nach erfolgtem Schreibzugriff muss die Datei wieder geschlossen werden leseDatei.close(); 9 Funktionen Eine Funktion wird eingesetzt, wenn bestimmte Teilaufgaben öfters und an verschiedenen Stellen im Programm ausgeführt werden sollen. Dabei ist es unerheblich, ob diese Teilaufgaben einfach oder sehr komplex sind. 9.1 Definition einer Funktion Eine Funktion wird außerhalb des Hauptprogrammes definiert durch 20 9 FUNKTIONEN 9.1 Definition einer Funktion Datentyp Funktionsname(Parameterliste mit Typangabe) { Anweisungen; return Ausdruck; } Im Funktionskopf wird der Datentyp des Funktionswertes (Rückgabewert), sowie die Datentypen der einzelnen Parameter (Argumente), die beim Funktionsaufruf übergeben werden, spezifiziert. Zusätzlich wird dort der Funktionsname festgelegt, mit dem die Funktion später aufgerufen werden kann. Die Aufgabe die eine Funktion erfüllen soll, wird in ihrem Anweisungsblock formuliert. Nach Abarbeitung aller Anweisungen liefert die Anweisung return Ausdruck; den Wert eines Ausdrucks an die Stelle des “Aufrufs” der Funktion zurück. Der Datentyp dieses Rückgabewertes muss mit dem im Funktionskopf spezifizierten Rückgabedatentyp übereinstimmen. Die Ausführung der return-Anweisung führt zum sofortigen Verlassen der Funktion und Rücksprung an die Stelle im Programm, von der aus die Funktion aufgerufen wurde. Innerhalb des Anweisungsblocks muss mindestens eine return-Anweisung vorkommen. Eine Funktion quad, die als Rückgabewert das Quadrat (a + b)2 . liefert kann wie folgt definiert werden int quad(int a, int b) { int c; c = a + b; return c*c; } Die formalen Parameter a und b stehen innerhalb der Funktion als lokale Datenobjekte zur Verfügung. Natürlich können im Anweisungsblock zusätzliche lokale Variablen deklariert werden. Liefert eine Funktion kein Ergebnis zurück, so verwendet man den Rückgabedatentyp void. In diesem Fall wird auch keine return-Anweisung im Anweisungsblock benötigt. Eine Funktion kann auch ohne eine Parameterliste definiert werden 21 9 FUNKTIONEN 9.2 Aufruf einer Funktion void meldung(void) { cout << "Das Programm wird beendet." << endl; } Die Funktion meldung gibt einfach die Meldung Das Programm wird beendet. auf dem Standardausgabegerät aus. 9.2 Aufruf einer Funktion Eine Funktion kann beliebig oft und an beliebiger Stelle, sofern sie dort “bekannt” ist, innerhalb anderer Funktionen aufgerufen werden. Dazu wird die Funktion mit ihrem Namen und den entsprechend der Parameterliste gewählten Parametern aufgerufen Funktionsname(Parameter); Die Parameter müssen in Datentyp und Position mit der Paramterliste der Funktionsdefinition übereinstimmen, denn diese aktuellen Parameter ersetzen die formalen Parameter in der Funktion. Die Funktion quad etwa kann wie folgt aufgerufen werden: int q, r; q = quad(1,2); r = quad(q,1); cout << q << "und" << r << endl; Diese Anweisungen erzeugen als Ausgabe 9 und 100. Das Hauptprogramm main() selbst ist auch eine Funktion, so dass dort beliebige Funktionen aufgerufen werden können. Parameterübergabe Funktionsparameter können auf zwei verschiedene “Arten” übergeben werden. Bei Verwendung der oben beschriebenen “Art” wird beim Aufruf einer Funktion Speicher für 22 9 FUNKTIONEN 9.2 Aufruf einer Funktion die formalen Parameter bereitgestellt und jeder formale Parameter mit dem entsprechenden aktuellen Parameter initialisiert. Alternativ dazu kann man auch nur eine Referenz auf einen Parameter übergeben. Dabei arbeitet die Funktion direkt mit der übergebenen Variablen. Bei Aufruf der Funktion void add(int val, int &ref) { val++; ref++; } inkrementiert die Anweisung val++; eine lokale Kopie des ersten übergebenen Parameters. Der übergebene Parameter selbst bleibt dabei unverändert. Daher dürfen feste Zahlwerte beim Aufruf der Funktion übergeben werden. Die Anweisung ref++; inkrementiert den zweiten übergebenen Parameter direkt, d.h. es wird keine Kopie angelegt. Alle Änderungen werden an dem übergebenen Parameter selbst vorgenommen. In diesem Fall muss beim Aufruf der Funktion eine Variable übergeben werden. Die Anweisungen int i = 1; int j = 1; add(i,j); cout << i << "und" << j << endl; liefern 1 und 2 an das Standardausgabegerät. Der erste Parameter wird als Wert übergeben (“call by value”), dabei bleibt die Variable i unverändert. Das zweite Argument wird dagegen als Referenz (“call by reference”) übergeben und somit wird keine Kopie, sondern die Variable j selbst inkrementiert. Wird ein Objekt nur aus Effizienzgründen per Referenz übergeben, und nicht um es zu verändern, so sollte der entsprechende Parameter als const deklariert sein void f(const int &ref) { ... } 23 9 FUNKTIONEN 9.3 9.3 Deklaration einer Funktion Deklaration einer Funktion Damit der Compiler bei der Abarbeitung eines Programmes eine Funktion aufrufen kann, muss sie ihm an der Aufrufstelle “bekannt” sein. Entweder muss sich der Funktionsaufruf im Programmcode nach der Funktionsdefinition befinden, oder die Funktion muss vor ihrem Aufruf deklariert werden. In der Funktionsdeklaration wird eine Schnittstelle zu der Funktion festgelegt: Datentyp Funktionsname(Parameterliste mit Typangabe); Dabei müssen Rückgabedatentyp, Funktionsname und Parameterliste, inklusive der Parameterreihenfolge mit der Funktionsdefinition übereinstimmen. Mit der Anweisung int quad(int a, int b); wird etwa die Funktion quad deklariert. Eine Funktionsdeklaration muss sich vor dem ersten Aufruf der Funktion und außerhalb jeglicher anderer Funktionen befinden. 9.4 Mathematischen Funktionen Um die in Tabelle 9 aufgeführten mathematischen Funktionen verwenden zu können, muss die Header-Datei math.h eingebunden werden. Zusätzlich muss der Compiler mit der Option -lm gestartet werden, wodurch eine Mathe-Bibliothek “hinzugelinkt” wird. Deklaration int abs(int x); double fabs(double x); double sqrt(double x); double sin(double x); double cos(double x); double tan(double x); double exp(double x); double pow(double x, double y); double log(double x); double log10(double x); double ceil(double x); double floor(double x); Bedeutung Absolutbetrag von x Absolutbetrag von x Quadratwurzel von x Sinus von x Cosinus von x Tangens von x Exponentialfunktion Potenzfunktion xy Natürlicher Logarithmus von x Dekadischer Logarithmus von x Aufrunden auf Ganzzahl Abrunden auf Ganzzahl Tabelle 9: Mathematische Funktionen 24 LITERATUR LITERATUR Literatur [KR90] B.W. Kernighan, D.M. Ritchie. Programmieren in C. 2. Ausg., Hanser, München 1990. [S01] K. Schröder. Nitty Gritty C. Hanser, Addison-Wesley, München 2001. [S00] B. Stroustrup. Die C++ Programmiersprache. 4. Aufl., Addison-Wesley, München; Boston 2000. 25