Kompaktkurs C++ Themen D 1 Bereits behandelt: Einfache Datentypen, Felder, Zeiger, Referenzen Kontrollstrukturen Funktionen Heute: Datentypen: Structs, Unions Datentypen: typedef, sizeof, Typkonvertierung Ein-/Ausgabe: Formatierung, Dateien Verschiedenes: Präprozessor, Vergleich zu C
Kompaktkurs C++ Strukturen (structs) D 2 Ein struct fasst mehrere Elemente unterschiedlichen Typs zu einem neuen Typ zusammen: struct MyStruct int i, j; char c; double feld[3]; }; // Definition des Typs "MyStruct" int main() MyStruct m1; // Variablendefinition MyStruct m2 = 1, 2, X, 0, 1.2, -9.2 } }; // + Initialisierung m1 = m2; // Zuweisung m1.i = 3; // Elementzugriff, schreibend bool b = (m1.c == Z ); // bzw. lesend } return 0;
Kompaktkurs C++ Strukturen (structs) D 3 struct SName... }; definiert eine Struktur SName. Der neue Typ SName kann wie ein eingebauter Typ verwendet werden, z. B. deklariert SName s; eine Variable s vom Typ SName. Man kann Strukturen gleichen Typs einander zuweisen: SName s1, s2; s1 = s2; Dabei wird die Struktur elementweise kopiert. Durch struct S1 int i; }; und struct S2 int i; }; werden zwei unterschiedliche Typen S1 und S2 definiert, die einander nicht zugewiesen werden können: S1 s1; S2 s2 = s1; // Fehler! Auf die Elemente einer Struktur greift man über den Operator. zu: struct S int i; }; S s; s.i = 1; Eine Struktur kann leer sein: struct S }; Man kann eine Struktur auch in einer anderen Struktur verwenden: struct S1 int i; }; struct S2 S1 s1; };
Kompaktkurs C++ Strukturen kopieren D 4 #include <iostream> struct Feld int Laenge; double* p; char Name[20]; }; int main() Feld feld1 = 5, new double[5], "Feld1" }, feld2 = feld1; } feld2.laenge = 7; feld2.p[0] = 2.3; feld2.name[0] = X ; std::cout << feld1.laenge << " " << feld1.p[0] << " " << feld1.name << \n << feld2.laenge << " " << feld2.p[0] << " " << feld2.name << std::endl; return 0; 5 2.3 Feld1 7 2.3 Xeld1
Kompaktkurs C++ Strukturen und Zeiger D 5 Sind Felder in einer Struktur vorhanden, werden die Felder bei einer Zuweisung komplett kopiert. Bei Zeigern werden dagegen nur die Zeiger selbst kopiert und nicht der Speicher, auf den sie zeigen. struct S; deklariert eine Struktur nur (um sie später zu definieren). Von diesem noch unvollständigen Typ können keine Variablen angelegt werden. Aber es kann bereits ein Zeiger auf S definiert werden: S* p; Er darf allerdings noch nicht dereferenziert werden. Ist die Struktur vollständig definiert struct S int i; }; dann kann man über den Zeiger auf die Elemente der Struktur zugreifen: (*p).i oder p->i
Kompaktkurs C++ Einfach verkettete Liste D 6 #include <iostream> struct ListElem double x; ListElem* next; }; int main() ListElem *List = 0, *tmp; tmp = new ListElem; tmp->x = 1.3; tmp->next = 0; List = tmp; tmp = new ListElem; tmp->x = 2.4; tmp->next = 0; List->next = tmp; // leere Liste, Hilfszeiger // 1. Listenelement erzeugen, // initialisieren // und in die Liste einhaengen // 2. Listenelement erzeugen, // initialisieren // und in die Liste einhaengen for (ListElem* p = List; p; p = p->next) // durch die Liste laufen std::cout << p->x << std::endl; // und Eintraege ausgeben } return 0;
Kompaktkurs C++ Einfach verkettete Liste D 7 List tmp List x = 1.3 next tmp List x = 1.3 next tmp List x = 1.3 next x = 2.4 next tmp List x = 1.3 next x = 2.4 next tmp
Kompaktkurs C++ Unions D 8 Eine union fasst ähnlich wie ein struct mehrere Elemente unterschiedlichen Typs zu einem neuen Typ zusammen: union MyUnion // Definition des Typs "MyUnion" int i; char c; }; Bei einer Union belegen aber alle Elemente denselben Speicherplatz! Unions kann man zum Platzsparen bei Datenstrukturen benutzen, in denen zwei Elemente nie gleichzeitig sinnvolle Werte haben können: struct Zahl bool IsInteger; union int i; double x; }; }; struct ListElem // Listenelement fuer Liste, die zwei Typen aufnehmen kann! Zahl z; ListElem* next; };
Kompaktkurs C++ Unions D 9 Eine andere Anwendung ist, die interne Darstellung eines Datentyps auszulesen: #include <iostream> const int SizeDbl = sizeof(double); // Speichplatzbedarf eines double (meist 8) union ShowDouble double x; char c[sizedbl]; }; int main() ShowDouble s; s.x = -6.8; for (int i=0; i<sizedbl; ++i) std::cout << int(s.c[i]) << std::endl; } return 0;
Kompaktkurs C++ typedef D 10 Mittels typedef kann man Typen einen neuen Namen geben. Dies ist insbesondere bei zusammengesetzten Typen sinnvoll. Zur Abstrahierung wird es aber auch bei einfachen Datentypen benutzt: typedef unsigned int size_t; size_t i; typedef const int* const_ptr; const_ptr p; typedef void (*fptr)(double, int); fptr f; Durch typedef wird kein neuer Typ erzeugt, sondern nur ein zusätzlicher Name vergeben. Daher ist folgendes erlaubt: struct A int i; }; A a; typedef A B; B b = a; Vorsicht bei der Verwendung von const: typedef int* ptr_1; typedef const ptr_1 ptr_2; typedef const int* ptr_3; // ptr_3!= ptr_2 typedef int* const ptr_4; // ptr_4 == ptr_2
Kompaktkurs C++ sizeof D 11 Mit sizeof kann man feststellen, wie viele Bytes Speicherplatz ein Typ bzw. eine Variable belegt (hier für einen Pentium 4 Prozessor): #include<iostream> struct S int i; char c; }; union U int i; char c; }; int main() int i, array[7]; } std::cout << sizeof(char) << \n // 1 << sizeof(int) << \n // 4 << sizeof(double) << \n // 8 << sizeof(double*) << \n // 4 << sizeof(bool) << \n // 1 << sizeof(i) << \n // 4 << sizeof(array)/sizeof(int) << \n // 7 << sizeof(3*7) << \n // 4 << sizeof( X ) << \n // 1 << sizeof(s) << \n // 8 wegen Alignment << sizeof(u) << std::endl; // 4 return 0;
Kompaktkurs C++ sizeof, Zeiger und Alignment D 12 Wenn man einen Zeiger int *p inkrementiert (++p), dann wird zu der Adresse sizeof(int) hinzuaddiert. Inkrementiert man einen Zeiger auf double, so wird sizeof(double) hinzuaddiert. Dafür (und für das Dereferenzieren) ist es wichtig, dass der Zeiger den Typ kennt, auf den er zeigt. Viele Datentypen dürfen nicht an irgendwelchen Stellen im Speicher liegen. Ein Integer sollte (bei einem Pentium 4) beispielsweise immer an einer Adresse liegen, die durch 4 teilbar ist. Dies nennt man Alignment. Daher findet man in einem struct of Füllbytes (Padding), die nur dazu dienen, das Alignment für alle Elemente der Struktur (oder der nächsten Struktur in einem Feld) zu garantieren. So kann ein struct größer sein als alle seine Elemente zusammen. Eine union hat die Größe des größten Elements. Eine bool-variable belegt ein ganzes Byte, damit jede bool-variable eine eigene Speicheradresse hat, über die man mit einem Zeiger auf die Variable zugreifen kann.
Kompaktkurs C++ Speicher-Layout D 13 c1 c2 c3 c4 c5 i c c i c1 i c2 i c1 c2 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 i c
Kompaktkurs C++ Datentypen D 14 Einfache Datentypen: Integers int, short,... Fließkommazahlen double, float, long double Wahrheitswerte bool Zeichen char, wchar_t,... Aufzählungen enum Zusammengesetzte Datentypen: Felder char c[5]; Zeiger int* p; Referenzen double& r; Funktionszeiger void (*fptr)(double); Funktionsreferenzen int (&fptr)(); Strukturen struct S int i; double x; }; Unions union U int i; char c; };
Kompaktkurs C++ Typkonvertierung (Casting) D 15 Die einfachen Datentypen lassen sich alle ineinander umwandeln: Einen Datentyp mit kleinem Wertebereich (z. B. short) kann man meist problemlos in einen Typ mit größerem Wertebereich (z. B. int) umwandeln. Umgekehrt kann es vorkommen, dass das der Wert des größeren Typs nicht mit dem kleineren Typ darstellbar ist. Das Ergebnis der Konvertierung ist dann unbestimmt. Für die Größe der Wertebereiche kann man als Faustregel nehmen: bool < enum, Zeichen Integers < Fließkommazahlen. Innerhalb der einzelnen Kategorien ist die Relation meist klar: short < int bzw. float < double etc. Bei der Umwandlung eines Integers mit vielen Stellen in eine Fließkommazahl mit kurzer Mantisse werden die letzten Ziffern unter Umständen gerundet. Dasselbe gilt für die Konvertierung einer Fließkommazahl mit langer Mantisse in eine mit kurzer Mantisse.
Kompaktkurs C++ Typkonvertierung (Casting) D 16 Bei der Konvertierung einer Fließkommazahl in einen Integer werden die Nachkommastellen abgeschnitten. Zeichen werden nach der Zeichentabelle des Rechners in Zahlen umgewandelt und umgekehrt. Beim ASCII-Code gilt beispielsweise A == 65, 0 == 48. Die Wahrheitswerte false und true werden in 0 bzw. 1 umgewandelt. Umgekehrt wird nur die 0 bzw. das Null-Zeichen \0 in false umgewandelt; alle anderen Werte werden nach true konvertiert. Für den Aufzählungstyp enum Farben Rot, Gelb, Blau }; gilt: Rot == 0, Gelb == 1, Blau == 2. Man kann die Werte aber auch explizit vorgeben: enum E A = -1, B = 1, C, D = 1, E = 17 }; Damit ist dann C == 2.
Kompaktkurs C++ Explizite und implizite Typkonvertierung D 17 Bei der expliziten Typkonvertierung wird der Typ angegeben, in den man einen Ausdruck umwandeln will: int i; double x = (double)i; // C-Notation int i; double x = double(i); // funktionale Notation Bei der impliziten Typkonvertierung nimmt der Compiler die Umwandlung selbst vor: int i; double x = i; Da implizite Typkonvertierung nicht trivial ist, sollte man in Zweifelsfällen eine explizite Typumwandlung vornehmen. Außerdem sind weniger implizite als explizite Typumwandlungen möglich. Bei Initialisierung und Zuweisung (also auch bei der Übergabe von Argumenten an Funktionen) wird die rechte Seite in den Typ der linken Seite konvertiert. Bei arithmetischen und Vergleichsoperationen mit einfachen Datentypen gilt die Faustregel: Das Argument mit dem kleineren Typ wird in den größeren Typ umgewandelt, wenn dieser größer als int ist. Ansonsten werden beide in int umgewandelt.
Kompaktkurs C++ Typkonvertierung mit Zeigern D 18 Zeiger lassen sich durch explizite Typkonvertierung in Zeiger eines anderen Typs umwandeln. int *p; double *q = (double*)p; Wegen falschen Alignments kann das Dereferenzieren eines umgewandelten Zeigers aber zu unerwünschten Resultaten führen. Jeder Zeiger kann durch implizite Konvertierung in einen Zeiger vom Typ void* umgewandelt werden. Für die Umkehrung ist wieder eine explizite Konvertierung erforderlich. int *p; void *q; q = p; p = (int*)q; Ein Zeiger auf void kann nicht dereferenziert werden. Die Umwandlung von Zeigern auf Daten/void in Zeiger auf Funktionen und umgekehrt ist möglich. Sie kann aber zu unerwünschten Resultaten führen, da die beiden Zeiger auf einigen Maschinen eine unterschiedliche interne Darstellung haben. Zeiger lassen sich durch explizite Typkonvertierung in Integer konvertieren und umgekehrt.
Kompaktkurs C++ Ausgabeformatierung D 19 Im Header iomanip sind sogenannte Manipulatoren definiert, mit denen sich das Verhalten der Ausgabe-Streams verändern lässt: std::cout << std::setbase(n); Basis für die Darstellung von Integerzahlen std::cout << std::setprecision(n); Anzahl der Dezimalstellen std::cout << std::setw(n); Breite des Ausgabefeldes für die nächste Zahl std::cout << std::setfill( X ); Füllzeichen, mit dem das Ausgabefeld aufgefüllt wird std::cout << std::setiosflags(std::ios::flag); Eigenschaft eines Streams einschalten std::cout << std::resetiosflags(std::ios::flag); Eigenschaft wieder ausschalten
Kompaktkurs C++ Ausgabeformatierung D 20 FLAG steht beispielsweise für: left right scientific fixed stdio linksbündige Ausgabe rechtsbündige Ausgabe Fließkommaformat mit Mantisse und Exponent Festkommaformat Puffer nach jedem Zeichen leeren #include <iostream> #include <iomanip> int main() std::cout << std::setiosflags(std::ios::scientific) << std::setprecision(4) << std::setiosflags(std::ios::right) << std::setfill( # ) << std::setw(15) << 13.52734 << std::endl; return 0; } Ausgabe: #####1.3527e+01
Kompaktkurs C++ Einlesen von Zeichenketten D 21 Achtung: Beim Einlesen von Zeichenketten sollte man dem Eingabestrom mittels std::setw immer mitteilen, wie lang das Feld ist, in das die Daten geschrieben werden sollen. Sonst können andere Speicherbereiche durch zu lange Eingaben überschrieben werden! #include <iostream> #include <iomanip> int main() char Feld1[10], Feld2[10]; std::cout << "Bitte geben Sie zwei Zeichenketten ein!" << std::endl; } std::cin >> Feld1; // Pufferueberlauf moeglich! // Sicherheitsluecke! std::cin >> std::setw(10) >> Feld2; // OK, maximal 10 Zeichen einlesen return 0; Werden 10 oder mehr Zeichen eingegeben, wird kein abschließendes \0 in das Feld geschrieben! Überschüssige Zeichen bleiben im Eingabestrom liegen und werden erst bei der nächsten Leseoperation verwendet.
Kompaktkurs C++ Ein-/Ausgabe mit Dateien D 22 Um Ein-/Ausgaben mit Dateien vornehmen zu können, muss der Header fstream eingebunden werden. Die Datentypen std::ifstream und std::ofstream dienen zum Lesen aus einer Datei bzw. zum Schreiben in eine Datei: #include <fstream> int main() std::ifstream InFile("Datei1.txt"); // Datei zum Lesen oeffnen std::ofstream OutFile("Datei2.txt"); // Datei zum Schreiben oeffnen char c; while (InFile >> c) OutFile << c; InFile.close(); OutFile.close(); // Zeichen einlesen, solange es geht // Zeichen ausgeben // Dateien schliessen } return 0;
Kompaktkurs C++ Präprozessor D 23 Zeilen, die mit # beginnen, leiten Präprozessor-Direktiven ein. Der Präprozessor führt im wesentlichen eine Textersetzung vor der eigentlichen Compilierung durch. #include <xyz> bindet die Header-Datei xyz ein, die in den System- Verzeichnissen gesucht wird. Bei der Form #include "xyz" wird in den Anwender-Verzeichnissen gesucht. #define xyz abc definiert das Macro xyz. Überall im nachfolgenden Programmtext wird xyz durch abc ersetzt. abc kann auch leer sein. #define xyz(a,b) ((a)/(b)) definiert eine Macro-Funktion. Überall wird der Funktionsaufruf xyz durch den Ausdruck auf der rechten Seite ersetzt. Die Klammern auf der rechten Seite sind wichtig, denn #define xyz(a,b) a/b macht aus 2-xyz(4,2-3) den Ausdruck 2-4/2-3 == -3 und nicht 2-((4)/(2-3)) == 6. #undef xyz löscht die Macrodefinition von xyz wieder.
Kompaktkurs C++ Präprozessor D 24 #if expr testet, ob der Ausdruck expr erfüllt ist. Wenn nicht, wird der Text bis zum zugehörigen #endif gelöscht. Es gibt auch die Variante mit #else-zweig, bei der jeweils einer der beiden Zweige gelöscht wird. #if-direktiven können geschachtelt werden. Mit #if 0... #endif wird Code oft auskommentiert. #ifdef xyz ist eine Abkürzung für #if defined (xyz). Folgender Trick wird oft in Header-Dateien verwendet, um zu verhindern, dass sie mehrfach eingebunden werden: #ifndef _MY_HEADER_ #define _MY_HEADER_... hier steht der eigentliche Code... #endif #error xyz gibt die Fehlermeldung xyz aus. #pragma xyz reicht die systemspezifische Information xyz an den Compiler weiter.
Kompaktkurs C++ Vergleich mit C D 25 Die meisten bislang dargestellten Elemente aus C++ finden sich auch in C wieder, einige davon aber in abgewandelter Form. Die wichtigsten Unterschiede sind: Ein-/Ausgabe geschieht über die Funktionen scanf und printf statt über die Streams std::cin und std::cout in Verbindung mit den Operatoren >> und <<. Zur Ein-/Ausgabe in Dateien wird anstelle von Streams der Typ FILE verwendet. Speicherallozierung und -freigabe geschieht mit malloc und free statt new und delete. Viele Header-Dateien haben andere Namen, z. B. math.h statt cmath. Es gibt keine Referenzen, sondern nur Zeiger in C. In C kann man keine Funktionen überladen.