Informatik 1 (251-0832-00) D-MAVT F2010 Klassen, Konstruktoren, Operatoren, Templates
Nachbesprechung Blatt 8 Aufgabe 1 - FIFO Keine Probleme Aufgabe 2 - Stack einige Fallstudien... double pop (double* &stack, int cur); Keine Struktur, fragwürdiger Übergabeparameter. double pop (tstack& stack) { if (stack.cur < 1) { std::cout << Error ; return -1; else { return stack.data[--stack.cur]; So ist richtig! Elemente nicht überschreiben, geschweige denn den Speicher freigeben und neu allozieren.
Nachbesprechung Blatt 8 Beim neu Allozieren nicht zwei Mal kopieren! void push (double val, tstack& stack) { if (stack.cur == stack.max){ double *tmp_data = new double[stack.max]; for (int i=0; i<stack.max; i++){!!! *(tmp_data+i) = *(stack.data+i); delete[] stack.data; stack.data=new double[2*stack.max]!! for (int j=0; j<stack.max; j++){!!! *(stack.data+j) = *(tmp_data+j); Besser: double *tmp_data = new double [2*stack.max]; // Ein Mal kopieren delete[] stack.data; stack.data = tmp_data;! stack.max *= 2; delete [] tmp_data; *(stack.data+stack.cur) = val; ++stack.cur;
Nachbesprechung Blatt 8 Operationen nur ein Mal ausführen, wenn nicht anders nötig. double push(tstack &stack, double val){ if (stack.size < stack.max){ stack.data[stack.size] = val; stack.size++; else { double *temp = stack.data; stack.data = new double[2*stack.max]; for (int i = 0; i < stack.max; ++i){ stack.data[i] = temp[i]; stack.max *= 2; stack.data[stack.size] = val; stack.size++; delete[] temp; Besser ans Ende setzen! Wartbarkeit, Übersicht. Und: Man kann auch stack.data[stack.size++] = val; schreiben.
Nachbesprechung Blatt 8 Andere Lösung für einen Stapel. struct Item{ double val; Item* next; ; typedef Item* Stack; double pop(stack& stack) { if (stack!= null) { double ret = stack->val; Item* del = stack; stack = stack->next; delete del; return ret; else { std::cout << Error!\n ; return -1; void push (double val, Stack& stack) { Item* item = new Item; item->val = val; item->next = stack; stack = item; int size (Stack stack) { int i = 0; while (stack!= null) { ++i; stack = stack->next; return i; int clear (Stack& stack) { while (stack!= 0) pop(stack); Die Funktionen size und clear sind aufwendiger geworden, und unschöne Übergabeparameter. Noch bessere Lösung: Als Klasse implementieren, dann entfällt der Übergabeparameter stack.
Klassen Objektorientierte Programmierung Reale Dinge oder logische Untereinheiten werden als Klassen modelliert. Beispiele: Klassen enthalten nicht nur Daten sondern auch Funktionalität (Methoden). Rechteck Höhe Breite Skalieren Rotieren Spielbrett Felder Zug Bewege Figur Gewinnstellung? Kinosaal #Reihen Sitze Klimaanlage Reservieren Abfragen Matrix Dimension Einträge Zerlegen Multiplizieren Eintrag setzen Eintrag abfragen Auto Position Ziel Richtung Geschwindigkeit Bewege Kollision Berechne Weg
Freunde Berechtigungen in Klassen Es gibt private, protected und public. Zusätzlich können auch Freunde deklariert werden, die privaten Zugriff haben sollen. class Foo { friend void fkt(foo&); friend class Bar; private: void geheim(); ; class Bar { public: void oeffentlich(foo& f) { f.geheim(); ; void Foo::geheim() { std::cout << Geheim!\n ; void fkt(foo& f) { f.geheim(); int main() { Foo f; Bar b; f.geheim(); // NOK (private) b.oeffentlich(f); // OK fkt(f); // OK Erster Aufruf schlägt fehl, da die Funktion geheim nicht öffentlich ist. Die anderen Aufrufe sind in Ordnung, weil sie indirekt über befreundete Funktionen/Klassen getätigt werden.
Vererbung Auto Position Ziel Richtung Geschwindigkeit Bewege Kollision Berechne Weg Limousine Pool Helipad Heize Pool Öffne Trennscheibe class Auto { double posx, posy; double zielx, ziely; public: bool kollision();... ; class Limousine : public Auto { private: Pool pool; Helipad helipad; void heizepool(int celsius); void oeffnescheibe(): ; int main() { Auto auto; Limousine limo; auto = limo; // OK (Daten gehen verloren) limo = auto; // NOK (keine Zuweisung moegl.)
Templates Template Klasse Template Funktion template <typename T, typename E> class Klasse { T t; E e; ; template <typename T> T& fkt(); Template Parameter Templates sind Schablonen, um gewisse Typen unspezifiziert zu lassen. Der Kompiler generiert die konkreten Klassen/ Funktionen bei Bedarf. Gebrauch: Klasse<int,double> k; fkt<klasse<int,double> >();
Templates sind mächtig! template <int i> struct D { D(void*); ; template <int p, int i> struct is_prime { enum { prim = (p==2) (p%i) && is_prime<(i>2?p:0), i-1>::prim ; ; template<> struct is_prime<0,0> { enum {prim=1; ; template<> struct is_prime<0,1> { enum {prim=1; ; template <int i> struct Prime_print { Prime_print<i-1> a; enum { prim = is_prime<i, i-1>::prim ; void f() { D<i> d = prim? 1 : 0; a.f(); ; template<> struct Prime_print<1> { enum { prim = 0 ; void f() { D<1> d = prim? 1 : 0; ; ; int main() { Prime_print<18> a; a.f(); return 0; Sie müssen nebenstehendes Programm nicht verstehen! Das Programm berechnet zur Kompilierzeit die Primzahlen. Sie sollen nachvollziehen können, dass es qualitativ etwas ganz anderes ist, ob eine Berechnung zur Kompilierzeit oder zur Laufzeit geschieht. Jedes Computerprogramm kann mit Templates zur Kompilierzeit simuliert werden.
C++ als Konglomerat von Sprachen Es gibt verschiedene rel. eigenständige Teilbereiche, die oft unter dem Begriff C++ zusammengefasst werden: C Grundelemente der Sprache schon in C vorhanden. Blöcke, Anweisungen, primitive Datentypen, Felder, Zeiger. Objektorientiertes C++ Aus der Sprache C wurde dann irgendwann eine objektorientierte Sprache mit Klassen, Datenkapselung, Vererbung, virtuellen Funktionen, Polymorphismus. Template C++ Generisches Programmieren. Templates wurden eingeführt und erst später wurde entdeckt, dass Templates sog. Turing-vollständig sind. Standard Template Library Umfangreiche Bibliothek von generischen Containern und Algorithmen.
Blatt 10, Polynom Ein Polynom des Grades n ist durch n+1 Koeffizienten bestimmt. p n (x) = n c i x i i=0 Bearbeiten Sie die Datei polynom.cpp. class cpolynom { private: double *coef; int deg; public: ; // Ausgabefunktion fuer das Polynom void print() const;
Blatt 10, Polynom Funktionalität: // erstellt 2 Polynome 3. und 2. Grades double w[] = {5,4,2,1; cpolynom a(w,3); double v[] = {3,1,9; cpolynom b(v,2); // Erstellt ein neues Polynom und initialisiert // es mit der Addition von a und b. // Dies braucht den Kopierkonstruktor. cpolynom c = a+b; // gibt es aus c.print(); // testet den Zuweisungsoperator b = c + a; b.print(); // wertet ein Polynom aus cout << a.eval(2) << endl;
Blatt 10, Polynom Die Ausgabe ist schon implementiert... void cpolynom::print() const { // gehe vom hoechsten zum tiefsten Grad for (int i = deg; i>= 0; i--) { // Nur die Konstante ausgeben und kein + if (i == 0 ) cout << coef[i]; // Bei Grad eins, kein "Hoch" ausgeben else if (i == 1) cout << coef[i] << "*x" << " + "; else cout << coef[i] << "*x^" << i << " + "; cout << endl;
Blatt 10, Teilaufgabe a) Konstruktoren implementieren... cpolynom() { // mit der Nullfunktion initialisieren cpolynom(const double* const coef, int deg) { // Funktion aus coef übernehmen Teilaufgabe f): Copy-Konstruktor cpolynom (const cpolynom& rhs) { // Funktion aus rhs in ret übernehmen Ist notwendig wenn es Zeiger-Datenmitglieder gibt. Wenn man es nicht manuell macht, werden einfach die Zeiger kopiert und keine neuen Daten angelegt.
Blatt 10, Teilaufgabe b) Destruktor implementieren... class cpolynom { public: ~cpolynom();... Nicht vergessen: Wie für alle anderen Funktionen auch braucht es immer eine Deklaration im Klassenrumpf und eine Implementation später in der Datei. cpolynom::~cpolynom() { // Speicher freigeben
Blatt 10, Teilaufgabe c) Auswertung implementieren... double eval(double val) const; Effiziente Auswertung mit Horner-Schema p n (x) = n c i x i =(...(c n x + c n 1 )x +...)x + c 0 i=0 Braucht n-1 Multiplikationen und n Additionen (Vergleiche mit 2n und n naiv oder sogar O(n^2) und n sehr naiv).
Blatt 10, Teilaufgabe d) & e) Addition und Subtraktion implementieren... spolynom operator+ (const spolynom& rhs); spolynom operator- (spolynom rhs); Variante 2 (operator-) nur verwenden wenn es einen Copy-Konstruktor gibt. Bei der Parameterübergabe wird sonst auch eine bitweise Kopie erstellt. Dann tritt möglicherweise wieder das Zeiger Problem ein. Sollte auch für unterschiedliche Grade funktionieren O.B.d.A. n m, p n (x)+q m (x) = m i=0 (cp i + cq i )xi + n i=m+1 cp i xi
Blatt 10, Teilaufgabe f) Copy-Konstruktor schon gesehen. Hier einige Anwendungsbeispiele von Konstruktoren allgemein: int main() { double coeff[] = {1,2,3,4; cpolynom a(coeff, 3); // Feld Konstruktor cpolynom b(a); // Copy-Konstuktor cpolynom c; // Leerer Konstruktor cpolynom& d = a; // Referenz, kein Konstruktor cpolynom* e = &b; // Zeiger ohne Konstruktor cpolynom* f = new cpolynom(a); // Zeiger mit Copy-Konstr. cpolynom* g = new cpolynom(); // Zeiger mit leerem Konstruktor. cpolynom* h = new cpolynom(coeff, 3); // guess what...
Blatt 10, Teilaufgabe g) Zuweisungsoperator implementieren. cpolynom& cpolynom::operator= (const cpolynom& rhs) { if (this!= &rhs) { // Konstruiere Objekt return *this; Hier ist auch wichtig, dass die Daten und nicht die Zeiger kopiert werden. Es muss natürlich auch der entsprechende Speicherplatz reserviert werden. Code wiederverwenden? cpolynom& operator= (const cpolynom& rhs); cpolynom(const cpolynom& rhs) { *this = rhs; Gibt Probleme, wenn die Klasse als Mitglieder andere Klassen oder Strukturen hat. Diese werden 2 Mal initialisiert. Besser: Speicher allozieren und dann private Funktion copy o.ä. aufrufen.
Mehr Informationen Operatorüberladung www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html Buch Willemer: Einstieg in C++, Kapitel 5.4 und 5.5 Vorlesungsunterlagen Allgemein (inkl. Klassen) www.cplusplus.com/doc/tutorial