Software Praktikum: C++ und LEDA / STL Sommersemester 2003 Oliver Zlotowski FB IV – Informatik Universität Trier 20. Mai 2003 Basistechniken – Überblick • Typen und Deklarationen • Zeiger, Felder und Strukturen • Ausdrücke und Anweisungen • Funktionen • Namensbereiche und Ausnahmen • Quelldateien und Programme Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 1 Basistechniken – Typen • jeder Bezeichner (engl. identifier) in C++ hat einen Typ • der Typ bestimmt, welche Operationen auf der Variable angewendet werden dürfen float x; // x ist eine Gleitkommavariable int y = 7; // y ist eine int-Variable mit 7 als Initalwert double d(3.14); // d ist eine Gleitkommavariable mit Initialwert 3.14 float f(int); // f ist eine Funktion, die ein Argument vom Typ int // erhält und eine Gleitkommazahl zurücklierfert Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 2 Basistechniken – Fundamentale Typen C++ hat eine Anzahl von fundamentalen Typen • ein Boolescher Typ (bool) • Zeichentypen (z. B. char) • ganzzahlige Typen (z. B. int, long) • Gleitkommatypen (z. B. float, double) zusätzliche Typen • Aufzählungstypen zur Darstellung von bestimmten Mengen von Werten (enum) • einen Typ void, der für die Abwesenheit von Information steht Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 3 Basistechniken – Fundamentale Typen Aus diesen Typen können andere Typen konstruiert werden • Zeigertypen (z. B. int*) • Feldtypen (z. B. char[]) • Referenztypen (z. B. double&) • benutzerdefinierte Datentypen Boolesche Werte, Zeichen und ganze Zahlen sind integrale Typen. Integrale und Gleitkommatypen sind arithmetische Typen. Aufzählungen und Klassen sind benutzerdefinierte Typen. In der Regel benutzt man bool für logische Werte, char für Zeichen, int für ganzzahlige Werte und double für Gleitkommawerte. Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 4 Basistechniken – Boolesche Werte • Boolescher Wert bool hat nur zwei Werte true oder false • bool wird zur Darstellung des Ergebnisses eine logischen Operation benutzt bool f(int a, int b) { bool b = a == b; // = ist Zuweisung, == ist Gleichheit return b; } • true hat den Wert 1, wenn es in eine ganze Zahl konvertiert wird; entsprechend hat false den Wert 0 • ganze Zahlen können auch implizit in bool-Werte konvertiert werden • jeder Zeiger kann implizit nach bool konvertiert werden • Nullzeiger konvertiert nach false, jeder andere zu true Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 5 Basistechniken – Boolesche Werte Beispiel bool a = true; bool b = 7; // bool(7) ist true, daher wird b true int i = true; // int(true) ist 1, daher wird i 1 bool x = a + b; bool y = a | b; // a+b ist 2, daher wird x true // a|b ist 1, daher wird y true int* p = 0; bool z = p; // z ist false, da p der Nullzeiger ist Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 6 Basistechniken – Zeichentypen • eine Variable vom Typ char kann Zeichen speichern • eine char-Variable hat immer 8 Bit und kann daher 256 Werte speichern • singned char kann Werte zwischen -128 bis 127 speichern • unsigned char kann Werte zwischen 0 bis 255 speichern • ob char vorzeichenbehaftet oder vorzeichenlos ist, ist implementierungsabhängig • der Typ von Zeichenkonstanten ist char Beispiel: char c = ’a’; Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 7 Basistechniken – Ganzzahlige- und Gleitkommatypen • es gibt vier Darstellungsformen für ganzzahlige Konstanten 1. 2. 3. 4. dezimal (zur Basis 10) int i = 63 oktal (zur Basis 8) int j = 077 hexadezimal (zur Basis 16) int k = 0x3f Zeichenkonstante • Gleitkommatypen gibt es in drei Größen 1. single-precision float i = 2.9e-3f 2. double-precision double j = 0.23 3. extended-precision long double k = 3.14159265l #include <iostream> #include <limits> void f() { std::cout << "max float " << std::numeric_limits<float>::max(); std::cout << " char is signed " << std::numeric_limits<char>::is_signed << ’\n’; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 8 Basistechniken – void • void ist ein fundamentaler Typ, von dem keine Objekte erzeugt werden können • void wird benutzt ? wenn eine Funktion keinen Rückgabewert hat ? als Basistyp für Zeiger auf Objekte unbekannten Typs auch als generischer Zeiger bezeichnet • Beispiel void x; void& r; void f(); void* pv; // // // // Fehler: es gibt keine Fehler: es gibt keine Funktion liefert kein Zeiger auf ein Objekt Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ void-Objekte Referenzen auf void Ergebnis unbekannten Typs 9 Basistechniken – Aufzählungstypen • Aufzählung (engl. enumeration) ist ein Typ, der eine Menge durch den Anwender spezifizierte Werte speichert • Aufzählungstypen werden ähnlich benutzt wie int • ganzzahlige Konstanten (Enumeratoren) bekommen Werte von 0 an aufsteigend zugewiesen; Beispiel: enum { ASM, AUTO, BREAK }; • jede Aufzählung ist ein unterschiedlicher Typ enum e1 { dunkel, hell }; // Bereich 0:1 enum e2 { a = 3, b = 9 }; // Bereich 0:15 enum e3 { min = -10, max = 1000000 }; // Bereich -1048576:1048575 enum flag { x = 1, y = 2, z = 4, e = 8 }; flag f(flag f) { return f == e ? x : y; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 10 Basistechniken – Deklarationen • jeder Bezeichner muß in einem Programm deklariert werden, d. h. sein Typ muß spezifiziert werden char ch; string s; int count = 1; const double pi = 3.141592; const char* name = "Praktikum"; const char* jahreszeit[] = { "Frühlung", "Sommer", "Herbst", "Winter" }; struct Datum { int t, m, j }; int tag (Datum* p) { return p->d; } template <class T> T abs(T a) { return a < 0 ? -a : a; } • obige Deklarationen sind auch Definitionen • Deklarationen aber keine Definitionen sind double sqrt(double); extern int fehler; struct User; Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 11 Basistechniken – Deklarationen • der Rumpf der Funktion sqrt(), der Speicher für die int-Variable und die Definition des Typs User müssen durch eine andere Deklaration spezifiziert werden double sqrt(double d) { /* ... */ } int fehler = 1; struct User { /* ... */ }; • es muß genau eine Definition für einen Namen in einem C++-Programm geben • es kann aber viele Deklarationen geben int count; int count; // Fehler: Redefinition extern int fehler; extern short fehler; // Fehler: Typfehler extern int fehler; extern int fehler; Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 12 Basistechniken – Struktur einer Deklaration • Deklaration besteht aus vier Teilen 1. 2. 3. 4. einem optionalen »Spezifizierer«, z. B. virtual, extern einem Basistyp, z. B. char einem Deklarator, z. B. *king[] einem optionalen Initialisierer, z. B. { ... } • Beispiel char* kings[] = { "Antigonus", "Seleucus", "Ptolemy" }; • ein Deklarator ist aus einem Namen und optionalen Dekalaratoroperatoren zusammengesetzt * * const & [] () Zeiger konstanter Zeiger Referenz Feld Funktion Oliver Zlotowski, FB IV – Informatik, Universität Trier Präfix Präfix Präfix Postfix Postfix ❂ ⇐ ⇐▲▼ ⇒ ⇒ * ptr * const ptr & ref A[] f() 13 Basistechniken – Deklaration • es ist möglich, mehrere Namen in einer einzelnen Deklaration zu deklarieren int x, y; // int x; int y; • die Deklarationsoperatoren beziehen sich aber nur auf einzelne Namen int *p, y; // int *p; int y; NICHT: int* y int x, *q; // int x; int *q; int v[10], *pv; // int v[10]; int* pv; • ein Name besteht aus einer Folge von Buchstaben und Ziffern, wobei das erste Zeichen ein Buchstabe sein muß (Unterstrich ist ein Buchstabe) • die Länge der Namen ist nicht beschränkt; Groß-/Kleinschreibung wird unterschieden • Schlüsselwörter (new, delete, . . . ) dürfen keine Bezeichner sein Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 14 Basistechniken – Gültigkeitsbereich • Deklaration führt einen Namen in einen Gültigkeitsbereich ein • der Gültigkeitsbereich eines Namens beginnt ab seiner Deklaration bis zum Ende eines Blocks • ein Block ist ein durch {...} Klammern begrenzter Codebereich int x; void f() { int x; x = 1; { int x; x = 2; } x = 3; } // globale Variable x // // // // lokales x verdeckt globales x Zuweisung an lokales x verdeckt erstes lokales x Zuweisung an zweites lokales x // Zuweisung an erstes lokales x int* p = &x; // holt die Adresse des globalen x Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 15 Basistechniken – Gültigkeitsbereich • ein verdeckter globaler Name kann durch Bereichsauf lösungsoperator :: sichtbar gemacht werden int x; // globale Variable x void f() { int x; ::x = 2; x = 2; } // lokales x verdeckt globales x // Zuweisung an globales x // Zuweisung an lokales x • es gibt keine Möglichkeit, einen verdeckten lokalen Namen zu benutzen • Namen von Funktionsargumenten werden im äußersten Block einer Funktion deklariert void f(int x) { int x; // Fehler: x schon deklariert //... } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 16 Basistechniken – Initialisierung • ist für ein Objekt ein Initialisierer spezifiziert, bestimmt dieser den Initialwert des Objekts • ist kein Initialisierer angegeben, dann wird ein globales Objekt, ein in einem Namesbereich stehendes Objekt oder ein lokales statisches Objekt mit einer 0 des passenden Typs initialisiert int a; double d; // Bedeutet int a = 0; // Bedeutet double d = 0.0; • lokale Variablen und auf dem Heap erzeugte Objekte, werden standardmäßig nicht initialisiert void f() { int x; //... } // x hat keinen wohldefinierten Wert • benutzerdefinierte Typen können eine Default-Initialisierung definiert haben, oder haben Konstruktoren mit funktionstypischen Argumentlisten Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 17 Basistechniken – typedef • eine Deklaration mit dem Präfix typedef deklariert einen neuen Namen für den Typ (statt einer neuen Variable des Typs) typedef char* pointer; pointer p1, p2; // p1 und p2 sind jeweils char* char* p3 = p1; typedef unsigned char uchar; typedef int int32; typedef short int 16; • so definierte Namen sind in der Regel bequeme Abkürzungen für unhandliche Namen • typedefs sind Synonyme für andere Typen und definieren keine neuen Typen Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 18 Basistechniken – Überblick • Typen und Deklarationen • Zeiger, Felder und Strukturen • Ausdrücke und Anweisungen • Funktionen • Namensbereiche und Ausnahmen • Quelldateien und Programme Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 19 Basistechniken – Zeiger • für einen Typ T ist T* der Typ »Zeiger auf T« • eine Variable vom Typ T* kann die Adresse eines Objekts von Typ T speichern char c = ’a’; char* p = &c; // p enthält die Adresse von c • Zeiger auf Felder und Zeiger auf Funktionen int* pi; char** ppc; int* ap[15]; int (*fp)(char*); int* f(char*); // // // // // // // Zeiger auf ein int Zeiger auf Zeiger auf char Feld von 15 Zeigern auf int Zeiger auf Funktionen mit einem char*-Argument; liefert einen int Funktion mit einem char*-Argument; liefert einen Zeiger auf int Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 20 Basistechniken – Zeiger • fundamentale Operation auf einem Zeiger ist das Dereferenzieren, d. h. »sich auf ein Objekt beziehen, auf das ein Zeiger zeigt« • die Operation wird auch als Indirektion bezeichnet • der Dereferenzierungsoperator ist das einstellig * char c = ’a’; char*p = &c; // p enthält die Adresse von c char x = *p; // x = ’a’ • es ist möglich einige arithmetische Operationen auf Zeigern auf Feldelemente durchzuführen (Pointerarithmetik) • Implementierung von Zeigern soll direkt den Adressierungsmechanismus des Rechners abbilden; die meisten Rechner können als kleinste Einheit ein Byte adressieren Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 21 Basistechniken – Zeiger Null • Null (0) ist ein int • Null wird als Zeigerkonstante benutzt, um anzuzeigen, daß ein Zeiger nicht auf ein Objekt zeigt, da kein Objekt die Adresse Null hat • in wird C häufig ein Makro NULL definiert #define NULL 0 int* p = NULL; if (p) { //... } // Zeiger mit dem Nullzeiger initialisiert // implizite Zeiger zu bool-Konvertierung void* q = p; // jeder Zeiger kann einem generischen Zeiger // zugewiesen werden Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 22 Basistechniken – Felder • für eine Typ T ist T[size] der Typ »Feld mit size Elementen vom Typ T« • die Elemente sind von 0 bis size -1 indiziert float v[3]; // Feld von drei floats v[0], v[1], v[2] char* a[32]; // Feld von 32 Zeigern auf char: a[0]...a[31] • die Feldgröße muß ein konstanter Ausdruck sein void f(int i) { int v1[i]; // Fehler: Feldgröße kein konstanter Ausdruck vector<int> v2(i); // OK } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 23 Basistechniken – Felder • Mehrdimensionale Felder werden als Felder von Feldern dargestellt int d2[10][20]; // d2 ist ein Feld von 10 Feldern von 20 int d2[0][0] = 3; // weist der 0. Komponente des 1. Feldes 3 zu Feldinitialisierer • ein Feld kann durch eine Liste von Werten initialisiert werden int v1[] = { 1, 2, 3, 4, 5 }; char v2[] = { ’a’, ’b’, ’c’, 0 }; • Größe der Felder v1 und v2 wird automatisch ermittelt int v3[8] = { 1, 2, 3, 4, 5 }; // explizite Größe; // v3[5]...v3[7] mit 0 initialisiert int v4[8] = { 1, 2, 3, 4, 5, 0, 0, 0 }; // äquivalent zu v3 Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 24 Basistechniken – Zeiger in Felder • Zeiger und Felder sind eng verwandt • der Name eines Feldes kann als Zeiger auf sein erstes Element benutzt werden int v[] int* p1 int* p2 int* p3 = = = = { 1, 2, 3, 4 }; v; // Zeiger auf erstes Element; implizite Konvertierung &v[0]; // Zeiger auf erstes Element v + 4; // Zeiger hinter das letzte Element • der Zeiger hinter das letzte Element p3 darf nicht zum Lesen oder Schreiben benutzt werden • Zuweisung und implizite Konvertierung char v[] = "Software"; char* p = v; // implizite Konvertierung von char[] nach char* // Größe des Feldes geht verlohren v = p; // Fehler: Zuweisung an Feld nicht möglich Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 25 Basistechniken – Zeiger in Felder Navigieren in Feldern • Zugriff auf ein Element kann über einen Zeiger auf ein Feld plus einem Index erfolgen int v[] = { 1, 2, 3, 4 }; for (int i = 0; i < 5; ++i) f(v[i]); • oder über einen Zeiger auf ein Element for (int *p = v, *p_stop = v + 5; p != p_stop; ++p) f(*p); • der Präfixoperator * dereferenziert den Zeiger, so daß *p die Zahl ist, auf die p zeigt • ++ inkrementiert den Zeiger, so daß er auf das nächste Element des Feldes zeigt Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 26 Basistechniken – Zeiger in Felder Pointerartihmetik • Subtraktion von Zeigern ist nur dann definiert, wenn beide Zeiger auf Elemente desselben Feldes zeigen; das Ergebnis der Subtraktion ist eine ganze Zahl • man kann eine ganze Zahl zu einem Zeiger addieren oder subtrahieren, in beiden Fällen ist das Ergebnis ein Zeiger void f() { int v1[10], v2[10]; int i1 = v1 + 5 - v1 + 3; // i1 = 2 int i2 = &v1[5] - &v2[3]; // Resultat undefiniert int* p1 = v2 + 2; // p1 = &v2[2] int* p2 = v2 - 2; // *p2 undefiniert } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 27 Basistechniken – Konstanten • mit const kann man benutzerdefinierte Konstanten deklarieren • an eine Konstante kann keine Zuweisung erfolgen, daher muß sie initialisiert werden const int model = 90; // model ist ein Konstante const int v[] = { 1, 2, 3, 4 }; // v[i] ist eine Konstante const int x; // Fehler: keine Initialisierung void f() { model = 200; // Fehler v[2]++; // Fehler } • const verändert einen Typ, es beschränkt die Möglichkeiten, wie das Objekt benutzt werden kann Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 28 Basistechniken – Konstanten Zeiger und Konstanten • benutzt man Zeiger, sind zwei Objekte beteiligt: der Zeiger und das Objekt, auf das er zeigt • einer Deklaration eines Zeigers ein const voranzustellen, macht aus dem Objekt, aber nicht aus dem Zeiger, eine Konstante • um den Zeiger selbst (anstatt des Objekts, auf das er zeigt) als konstant zu deklarieren, benutzt man den Deklaratoroperator *const Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 29 Basistechniken – Konstanten void f(char* p) { char s[] = "Gnom"; const char* pc = s; pc[3] = ’g’; pc = p; char *const cp = s; cp[3] = ’g’; cp = p; // Zeiger auf Konstante // Fehler: pc zeigt auf Konstante // OK // Konstanter Zeiger // OK // Fehler: cp ist konstant const char *const cpc = s; // Konstanter Zeiger auf Konstante cpc[3] = ’g’; // Fehler: cpc zeigt auf Konstante cpc = p; // Fehler: cpc ist konstant int a = 1; const int c = 2; const int* p1 = &c; const int* p2 = &a; int* p3 = &c; *p3 = 7; // // // // OK OK Fehler: Initialisierung von int* mit const int* Versuch, den Wert von c zu ändern } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 30 Basistechniken – Referenzen • eine Referenz ist ein alternativer Name für ein Objekt • Hauptanwendung ist die Angabe von Argumenten und Rückgabewerten für Funktionen void f() { int i = 1; int& r = i; // r und i beziehen sich nun auf das gleiche int-Objekt int x = r; // x = 1; r = 2; // i = 2; } • Referenzen müssen bei der Erzeugung immer initialisiert werden int i = 1; int& r1 = i; // OK: r1 initialisiert int& r2; // Fehler: Initialisierer fehlt extern int& r3; // OK: r3 wird anderswo initialisiert Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 31 Basistechniken – Referenzen • eine Referenz wird einmal initialisiert und verweist dann immer auf das gleiche Objekt • eine Referenz ist eine Art konstanter Zeiger, der bei jeder Benutzung derefernziert wird void f() { int i = 0; int& r = i; r++; // i wird auf 1 initialisiert int* p = &r; // p zeigt auf i } • Initialisierer einer einfachen Referenz muß ein lvalue sein, d. h. ein Objekt des Adresse sich ermitteln läßt double& dr = 1; // Fehler: lvalue benötigt const double& cdr = 1; // OK: erzeugt eine temp. Variable // die als Initialisierer benutzt wird Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 32 Basistechniken – Referenzen • eine Referenzen und Zeiger als Funktionsargumente template <class T> void swap(T& x, T& y) { T tmp = x; x = y; y = tmp; } template <class T> void swap(T* x, T* y) { T tmp = *x; *x = *y; *y = tmp; } template<class T> T swap(const T& x, T& y) { T tmp = y; y = x; return tmp; } void f() { int x = 1, y = 2; swap(x,y); // x = 2; y = 1; } void g() { int x = 1, y = 2; swap(&x,&y); // x = 2; y = 1; } void h() { int x = 1, y = 2; x = swap(x,y); // x = 2; y = 1; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 33 Basistechniken – Referenzen struct Pair { string name; unsigned value; // positive ganze Zahl }; vector<Pair> Pairs; unsigned& values(const string& s) { for (int i = 0; i < Pairs.size(); ++i) if (s == Pairs[i].name) return Pairs[i].value; Pair p = { s, 0 }; Pairs.push_back(p); return Pairs.back().value; } int main() { string buf; while (cin >> buf) values(buf)++; for (vector<Pair>::const_iterator it = Pairs.begin(); it != Pairs.end(); ++it) cout << it->name << " : " << (*it).value << ’\n’; return 0; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 34 Basistechniken – Zeiger auf void • ein Zeiger auf ein beliebiges Objekt kann einer Variablen vom Typ void* zugewiesen werden • ein void* kann einem anderen void* zugewiesen werden • void*s können auf Gleichheit und Ungleichheit getestet werden • void* kann explizit in einen Zeiger auf einen anderen Typ konvertiert werden void f(int* pi) { void* pv = pi; // OK: implizite Konvertierung von int* auf void* *pv; // Fehler: void* nicht derefernzierbar pv++; // Fehler: keine Pointerarithmetik auf void* int* pi2 = static_cast<int*>(pv); // explizite Konvertierung zurück zu int* int* pi3 = (int*) pv; double& dpi1 = *static_cast<double*>(pv); // Unsicher double& dpi2 = *(double*) pv; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 35 Basistechniken – Strukturen • eine Struktur struct ist die Zusammenfassung von beliebigen Typen struct address { struct { string first; string last; } name; // Struktur ohne Namen unsigned postal; string town; }; int main() { address x; cin >> x; cout << x; return 0; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 36 Basistechniken – Strukturen • Definition eines Ausgabestreams für die Struktur address ostream& operator<<(ostream& os, const address& x) { os << "first name " << x.name.first << ’\n’; os << "last name " << x.name.last << ’\n’; os << "postal code " << x.postal << ’\n’; os << "town " << x.town << ’\n’; return os; } • Definition eines Eingabestreams für die Struktur address istream& operator>>(istream& is, address& x) { is >> x.name.first >> x.name.last; is >> x.postal >> x.town; return is; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 37 Basistechniken – Überblick • Typen und Deklarationen • Zeiger, Felder und Strukturen • Ausdrücke und Anweisungen • Funktionen • Namensbereiche und Ausnahmen • Quelldateien und Programme Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 38 Basistechniken – Kommandozeilenargumente • Programm wird mit dem Aufruf von main() gestartet • main() kann zwei Argumente enthalten: argc und argv[] • argc enthält die Anzahl der Argumente plus das Programm selbst • argv enthält die Argumente als Zeichenketten istream* input; int main(int argc, char* argv[]) { switch (argc) { case 1: // keine Parameter übergeben input = &cin; // lies von Standardeingabe break; case 2: // verarbeite das erste Argument input = new istringstream(argv[1]); break; } //... Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 39 Basistechniken – Kommandozeilenargumente //... while (*input) { char ch; (*input).get(ch); cout << ch; } if (input != &cin) delete input; } • istringstream ist ein istream der aus einem Zeichenkettenargument liest • ist das Ende der Zeichenkette erreicht, schlägt istringstream fehl • um istringstream zu benutzen muß man <sstream> per #include einbinden Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 40 Basistechniken – Zusammenfassung von Ausdrücken Bereichsauflösung Elementselektion Indizierung Funktionsaufruf Postinkrement Postdekrement Präincrement Prädekrement Typgröße Adresse Derefernzierung Cast (Typkonvertierung) Oliver Zlotowski, FB IV – Informatik, Universität Trier klassenname::element namensbereichsname::element objekt.element zeiger->element zeiger[ausdruck] ausdruck(ausdrucksliste) lvalue++ lvalue−− ++lvalue −−lvalue sizeof(typ) &lvalue *ausdruck (typ) ausdruck ❂ ⇐ ⇐▲▼ ⇒ ⇒ 41 Basistechniken – Zusammenfassung von Ausdrücken Erzeugung Erzeugung und Initialisierung Erzeugung (Plazierung) Erzeugung (Plazierung) und Initialisierung Zerstörung Feldzerstörung new typ Multiplikation ... Addition Substraktion ausdruck * ausdruck new typ (ausdrucksliste) new (ausdrucksliste) typ new (ausdrucksliste) typ (ausdrucksliste) delete zeiger delete [] zeiger ausdruck + ausdruck ausdruck - ausdruck Einfache Zuweisung lvalue Multiplikation und Zuweisung lvalue Division und Zuweisung lvalue Addition und Zuweisung lvalue Oliver Zlotowski, FB IV – Informatik, Universität Trier = ausdruck *= ausdruck /= ausdurck += ausdruck ❂ ⇐ ⇐▲▼ ⇒ ⇒ 42 Basistechniken – Inkrement und Dekrement • ++lvalue ist dasselbe wie lvalue += 1, was dasselbe wie lvalue = lvalue + 1 • Operatoren ++ und −− können als Präfix- und als Postfixoperatoren verwendet werden • y = ++x; ist äquivalent zu y = (x + 1); • y = x++; ist äquivalent zu y = x; x = x + 1; Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 43 Basistechniken – Dynamischer Speicher • new-Operator erzeugt Objekte, die unabhängig von einem Gültigkeitsbereich exisitieren • delete-Operator zerstört diese dynamisch erzeugten Objekte wieder • ein mit new erzeugtes Objekt exisitert solange bis es mit delete zerstört wird • der delete-Operator darf nur auf von new gelieferte Zeiger oder Null angewendet werden int* iptr = 0; void f() { iptr = new int(1); } void g() { *iptr++; delete iptr; } // Erzeuge und Initialsiere ein int // Zerstöre das int Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 44 Basistechniken – Dynamischer Speicher Dynamische Felder • Felder von Objekten können auch mit new angelegt char* string_copy(const char* p) { char* s = new char[strlen(p) + 1]; strcpy(s,p); return s; } • und mit delete [] wieder freigegeben werden int main(int argc, char* argv[]) { if (argc < 2) return 1; char* p = string_copy(argv[1]); //... delete [] p; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 45 Basistechniken – Zusammenfassung der Anweisungen anweisung: deklaration ausdruck; if (bedinung) anweisung if (bedinung) anweisung else anweisung switch (bedingung) anweisung while (bedingung) anweisung do anweisung while (bedingung); for (for-init-anweisung; bedingung; ausdruck) anweisung case konstanter-ausdruck: anweisung default: anweisung break; contiunue; return ausdruck; goto label; label: anweisung Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 46 Basistechniken – Auswahlanweisung • mit if- oder switch-Anweisung kann ein Wert überprüft werden • Vergleichsoperatoren: == != < <= > >= • der Vergleich liefert true oder false • logische Operatoren: && || ! if (p && p->count > 1) //... if (x) //... if (x != 0) //... if (a <= b) max = b; else max = a; switch (wert) // Vorsicht! { case 1: cout << "Fall 1\n"; // wert == 1 fällt man case 2: // auch in nächste case cout << "Fall 2\n"; break; default: cout << "Fall nicht gefunden!\n"; } max = (a <= b) ? b : a; Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 47 Basistechniken – Schleifenanweisung • jede Schleifenanweisung führt solange einen Schleifenrumpf aus, wie die Bedinung true ist • eine Schleife kann explitzit mittels break, return, throw oder goto verlassen werden • Endlosschleifen: for (;;) oder while (1) Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 48 Basistechniken – Goto • goto hat wenige Anwendung in guten Programmen • in seltenen Fällen wichtig für die Effizienz void f() { for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) if (M[i][j] == a) goto gefunden; gefunden: //... } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 49 Basistechniken – Überblick • Typen und Deklarationen • Zeiger, Felder und Strukturen • Ausdrücke und Anweisungen • Funktionen • Namensbereiche und Ausnahmen • Quelldateien und Programme Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 50 Basistechniken – Funktionsdeklarationen • eine Funktion muß vor ihrer ersten Verwendung deklariert werden • eine Funktionsdeklaration bestimmt den Namen der Funktion, den Typ des zurückgelieferten Wertes und die Anzahl und Typen der Argumente char* strcpy(char* to, const char* from); double sqrt(double); void exit(int); • die Argumententypen werden überprüft und ggf. wird eine implizite Typkonvertierung der Argumente durchgeführt double sr2 = sqrt(2); // implizite Konvertierung von int nach double double sr3 = sqrt("drei"); // Fehler: sqrt() benötigt ein double-Argument Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 51 Basistechniken – Funktionsdeklarationen Funktionsdefinition • eine Funktionsdefinition ist eine Funktionsdeklaration, bei der der Rumpf der Funktion angegeben wird template <class T> inline void swap(T&, T&); // ... // eine Deklaration template <class T> // eine Definition inline void swap(T& x, T& y) { T tmp = x; x = y; y = tmp; } • Argumentnamen sind nicht Teil des Typs und müssen nicht übereinstimmen • Funktionen können als inline definiert werden • der Compiler versucht den Code der Funktion anstelle eines Funktionsaufrufs an der Aufrufstelle der Funktion einzusetzen Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 52 Basistechniken – Funktionsdeklarationen Statische Variablen • statische lokale Variablen in einer Funktion, werden nur einmal erzeugt und initialisiert void f(int a) { while (a--) { static int n = 0; // einmal initialisiert int x = 0; cout << "n = " << n++ << ", x = " << x++ << ’\n’; } } Aufruf f(3) erzeugt folgende Ausgabe n = 0, x = 0 n = 1, x = 0 n = 2, x = 0 • statische Variablen in Funktionen sind eine Art »Gedächtnis« für die Funktion, so daß keine globalen Variablen notwendig sind Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 53 Basistechniken – Argumentenübergabe bei Funktionen • wird eine Funktion aufgerufen, dann wird Speicher für die formalen Argumente bereitgestellt • jedes formale Argument wird durch das korrespondierende aktuelle Argument initialisiert • dabei werden die Argumenttypen überprüft und ggf. eine Typkonvertierung durchgeführt void f(int wert, int& ref) { wert++; ref++; } void g() { int i = 1, j = 1; f(i,j); // i = 1, j = 2; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 54 Basistechniken – Argumentenübergabe bei Funktionen • beim Aufruf von f() erhöht wert++ eine lokale Kopie des ersten aktuellen Arguments und eine Referenz ref++ des zweiten Arguments • i wird als Wert (engl. by value) übergeben; nach dem Funktionsaufruf hat i in g() den Wert 1 • j wird als Referenz (engl. by reference) übergeben; nach dem Funktionsaufruf hat j in g() den Wert 2 • für viele Typen (beispielsweise Container) ist es effizienter das Objekt per Referenz und nicht per Wert zu übergeben • soll das Objekt nicht geändert werden, kann es als const deklariert werden void f(const vector<int>& V) { // ohne explizite Typkonvertierung darf auf // V keine modifzieren Operationen ausgeführt werden } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 55 Basistechniken – Argumentenübergabe bei Funktionen • ist ein Feld ein Funktionsargument, wird ein Zeiger auf das erste Element übergeben int strlen(const char*); // übergebene String darf nicht modifiziert werden void f() { char v[] = "ein Feld"; int i = strlen(v); int j = strlen("Software"); } • ein Argument vom Typ T[] wird bei der Übergabe in ein T* konvertiert • Felder können damit nicht per Wert übergeben werden • die Größe des Feldes ist der aufrufenden Funkton nicht bekannt, daher terminieren C-Strings mit einem Null-Zeichen Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 56 Basistechniken – Wertrückgabe • eine nicht als void deklarierte Funktion muß eine Wert zurückliefern; entsprechend kann eine als void deklarierte Funktion keinen Wert zurückliefern int f1() { } void f2() { } // Fehler: es wird kein Wert zurückgeliefert // OK int f3() { return 1; } // OK void f4() { return 1; } // Fehler: return in void Funktion int f5() { return; } void f6() { return; } // Fehler: kein Rückgabewert // OK • Rückgabewert durch return-Anweisung int fac(int n) { return n > 1 ? n * fac(n - 1) : 1; } • es kann mehrere return-Anweisungen in einer Funktion geben int fac(int n) { if (n > 1) return n * fac(n - 1); return 1; } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 57 Basistechniken – Überladene Funktionsnamen • Benutzung desselben Names für Operationen für verschiedene Typen wird Überladen (engl. overloading) genannt void ausgeben(int); void ausgeben(const char*); // gib einen int aus // gib eine C-String aus • das Finden der richtigen Version aus einer Menge von überladenen Funktionen wird durch Suchen nach einer besten Übereinstimmung zwischen den Typen des Argumentenausdrucks und den formalen Argumenten der Funktion durchgeführt void ausgeben(long); void ausgeben(double); void f() { ausgeben(1L); ausgeben(1.0); ausgeben(1); } // // // // ausgeben(long) ausgeben(double) Fehler, mehrdeutig: ausgeben(long(1)) oder ausgeben(double(1)) rufen? Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 58 Basistechniken – Überladene Funktionsnamen Manuelle Auflösung von Mehrdeutigkeiten • häufig kann das Problem der Mehrdeutigkeit durch Hinzufügen einer weiteren Version behoben werden void ausgeben(int); void f() { ausgeben(1L); ausgeben(1); } // ausgeben(long) // ausgeben(int) • man kann auch eine explizite Typkonvertierung zur Auflösung benutzen int f(char*); void f(int*); void g() { f(0); // Fehler, mehrdeutig f(char*) oder f(int*) f(static_cast<int*>(0)); // OK: f(int*) f((char*)0); // OK: f(char*) } • Rückgabetypen werden bei der Auflösung von Überladungen nicht berücksichtigt Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 59 Basistechniken – Default-Argumente • ein Default-Argument wird bei der Funktionsdeklaration typgeprüft und zum Aufrufszeitpunkt ausgewertet • Default-Argumente können nur für hinten stehende Argumente benutzt werden int f(int, int = 0, char* = 0); // OK int g(int = 0, int = 0, char*); // Fehler • und dürfen nicht im gleichen Gültigkeitsbereich wiederholt werden int h(int = 0, int = 0, char* = 0); // OK int h(int = 0, int = 0, char* = 0); // Fehler, Wiederholung Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 60 Basistechniken – Funktionszeiger • neben dem Aufruf einer Funktion, ist es möglich auch ihre Adresse zu ermitteln • ein Zeiger (Funktionszeiger) der auf eine Funktion verweist, kann benutzt werden, um die Funktion aufzurufen void error(string s) { /* ... */ } void (*fptr)(string s); // Funktionszeiger void f() { fptr = error; // fptr zeigt auf error fptr("Fehler"); // rufe error über fptr } • bei der Zuweisung an einen Funktionszeiger muß der komplette Funktionstyp exakt übereinstimmen void f1(string s); int f2(string s); void (*fptr)(string s); // Funktionszeiger void g() { fptr = f1; // OK fptr = f2; // Fehler: falscher Rückgabetyp } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 61 Basistechniken – Funktionszeiger • Anwendung eines Funktionszeigers in der Sortierfunktion ssort() typedef int (*CFT)(const void*, const void*); void ssort(void* base, int n, int sz, CFT cmp) { for (int gap = n / 2; gap > 0; gap /= 2) { for (int i = gap; i < n; ++i) { for (int j = i - gap; j >= 0; j -= gap) { char* b = static_cast<char*>(base); // notwendiger cast char* pj = b + j * sz; // &base[j] char* pjg = b + (j + gap) * sz; // &base[j + gap] if (cmp(pjg,pj) < 0) { for (int k = 0; k < sz; ++k) { char tmp = pj[k]; pj[k] = pjg[k]; pjg[k] = tmp; } } } } } } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 62 Basistechniken – Funktionszeiger struct user { char* name; char* id; int dept; } user heads[] = { "Ritchie D.", "dr", 11271, "Sethi R.", "rs", 11272, "Kernighan B.", "bk", 11273 }; int cmp1(const void* p, const void* q) // Vergleicht die Namen { retrun strcmp(static_cast<const user*>(p)->name, static_cast<const user*>(q)->name); } int cmp2(const void* p, const void* q) // Vergleicht die Namen { retrun static_cast<const user*>(p)->dept static_cast<const user*>(q)->dept); } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 63 Basistechniken – Funktionszeiger • Aufruf der Funktion mit verschiedenen Vergleichsfunktionen void ausgeben(user* v, int n) { for (int i = 0; i < n; ++i) cout << v[i].name << ’\t’ << v[i].id << ’\t’ << v[i].dept << ’\n’; } int main() { cout << "Leiter in alphabetischer Reihenfolge\n" ssort(heads, 3, sizeof(user), cmp1); ausgeben(heads, 3); cout << "Leiter sortiert nach Abteilungsnummern\n" ssort(heads, 3, sizeof(user), cmp2); ausgeben(heads, 3); } Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 64 Basistechniken – Überblick • Typen und Deklarationen • Zeiger, Felder und Strukturen • Ausdrücke und Anweisungen • Funktionen • Namensbereiche und Ausnahmen • Quelldateien und Programme Vielleicht später noch. . . Oliver Zlotowski, FB IV – Informatik, Universität Trier ❂ ⇐ ⇐▲▼ ⇒ ⇒ 65