|
I-PROGR2 SS03 - PROGRAMMIEREN2 - Vorlesung mit Übung
|
Oberbegriffe, Typüberprüfung in C++, mit Vererbung oder mit Templates, Oberbegriffe als gemeinsame Basisklassen, virtuelle Funktionen, abstrakte Klassen mit garantierter 'Nicht-Erzeugung' durch Privatiierung des Konstruktors, aber virtueller Destruktor, konkrete Klassen
Reine virtuelle funktionen ('pure virtual functions') sind nicht nur virtuell, sondern besitzen nur eine Deklarierung, keine Implementierung; abstrakte Operation. Eine abgeleitete Klasse muss nicht implementieren, da sie selbst wiederum eine abstrakte Klasse sein kann.
Identität eines Objektes über Zeiger!
Verwaltung aller abgeleiteten Objekte durch eine Gruppenklasse
Runtime type Information (RTTI) mit static_cast<>() oder typeid(); insgesamt nicht gut
Abstrakte Klassen als Schnittstellenvereinbarung; darauf basierend abgeleitete Klassen bzw. Funktionen
[JOSUTTIS 1999]
Beispiel mit Basis-Klasse Geo
Man beachte den Unterschied zwischen Objekt:Klasse in UML zu Paket::Klasse. In C++ lässt sich ein Paket z.B. realisieren als namespace Geo{}, der dann die Basisklassen, die abgeleitete Klassen und mögliche Hilfsklassen enthält. In unserem Beispiel findet sich explizit folgende Struktur:
namespace Geo { /* Klasse Koord * - Hilfsklasse für geometrische Objekte * - nicht zur Vererbung geeignet */ class Koord {...}; /* abstrakte Basisklasse GeoObj * - gemeinsame Basisklasse für geometrische Objekte * - zur Vererbung vorgesehen */ class GeoObj { ...}; /* Klasse Kreis * - abgeleitet von GeoObj * - ein Kreis besteht aus: * - Mittelpunkt (Referenzpunkt, geerbt) * - Radius (neu) */ class Kreis : public GeoObj { ...}; /* Klasse Linie * - abgeleitet von GeoObj * - ein Linie besteht aus: * - einem Anfangspunkt (Referenzpunkt, geerbt) * - einem Endpunkt (neu) */ class Linie : public GeoObj { ...}; /* Klasse GeoGruppe * - abgeleitet von GeoObj * - eine GeoGruppe besteht aus: * - einem Referenzpunkt (geerbt) * - einer Menge von geometrischen Elementen (neu) */ class GeoGruppe : public GeoObj { ..}; }//End of namespace Geo
Makefile für Beispiele
objects1 = geotest1.o objects2 = gruppe.o geotest2.o geo1: $(objects1) g++ -o geo1 $(objects1) geotest1.o: geotest1.cpp koord.hpp geoobj.hpp kreis.hpp linie.hpp g++ -c geotest1.cpp geo2: $(objects2) g++ -o geo2 $(objects1) geotest2.o: geotest2.cpp gruppe.cpp koord.hpp geoobj.hpp kreis.hpp linie.hpp gruppe.hpp g++ -c geotest2.cpp gruppe.o: gruppe.cpp gruppe.hpp g++ -c gruppe.cpp clean: rm geo1 $(objects1) geo2 $(objects2)
Klasse Koordinate
/* Die folgenden Code-Beispiele stammen aus dem Buch: * Objektorientiertes Programmieren in C++ * Ein Tutorial für Ein- und Umsteiger * von Nicolai Josuttis, Addison-Wesley München, 2001 * * (C) Copyright Nicolai Josuttis 2001. * Diese Software darf kopiert, verwendet, modifiziert und verteilt * werden, sofern diese Copyright-Angabe in allen Kopien vorhanden ist. * Diese Software wird "so wie sie ist" zur Verfügung gestellt. * Es gibt keine explizite oder implizite Garantie über ihren Nutzen. */ #ifndef KOORD_HPP #define KOORD_HPP // Headerdatei für I/O #include <iostream> namespace Geo { /* Klasse Koord * - Hilfsklasse für geometrische Objekte * - nicht zur Vererbung geeignet */ class Koord { private: int x; // X-Koordinate int y; // Y-Koordinate public: // Default-Konstruktor und Konstruktor aus zwei ints Koord () : x(0), y(0) { // Default-Werte: 0 } Koord (int newx, int newy) : x(newx), y(newy) { } Koord operator + (const Koord&) const; // Addition Koord operator - () const; // Negation void operator += (const Koord&); // += void printOn (ostream& strm) const; // Ausgabe }; /* Operator + * - X- und Y-Koordinaten addieren */ inline Koord Koord::operator + (const Koord& p) const { return Koord(x+p.x,y+p.y); } /* einstelliger Operator - * - X- und Y-Koordinaten negieren */ inline Koord Koord::operator - () const { return Koord(-x,-y); } /* Operator += * - Offset auf X- und Y-Koordinaten aufaddieren */ inline void Koord::operator += (const Koord& p) { x += p.x; y += p.y; } /* printOn() * - Koordinaten als Wertepaar ausgeben */ inline void Koord::printOn (ostream& strm) const { strm << '(' << x << ',' << y << ')'; } /* Operator << * - Umsetzung für Standard-Ausgabeoperator */ inline ostream& operator<< (ostream& strm, const Koord& p) { p.printOn (strm); return strm; } } // namespace Geo #endif // KOORD_HPP
Abstrake Klasse geoobj
#ifndef GEOOBJ_HPP #define GEOOBJ_HPP // Headerdatei für Koord #include "koord.hpp" namespace Geo { /* abstrakte Basisklasse GeoObj * - gemeinsame Basisklasse für geometrische Objekte * - zur Vererbung vorgesehen */ class GeoObj { protected: // Jedes GeoObj hat einen Referenzpunkt Koord refpunkt; /* Konstruktor für Startwert vom Referenzpunkt * - nichtöffentlich * - damit ist kein Default-Konstruktor vorhanden */ GeoObj (const Koord& p) : refpunkt(p) { } public: // GeoObj um relativen Offset verschieben virtual void move (const Koord& offset) { refpunkt += offset; } /* GeoObj ausgeben * - rein virtuelle Funktion */ virtual void draw () const = 0; // virtueller Destruktor virtual ~GeoObj () { } }; } // namespace Geo #endif // GEOOBJ_HPP
Abgeleitete Klasse Kreis
#ifndef KREIS_HPP #define KREIS_HPP // Headerdatei für I/O #include <iostream> // Headerdatei der Basisklasse #include "geoobj.hpp" namespace Geo { /* Klasse Kreis * - abgeleitet von GeoObj * - ein Kreis besteht aus: * - Mittelpunkt (Referenzpunkt, geerbt) * - Radius (neu) */ class Kreis : public GeoObj { protected: unsigned radius; // Radius public: // Konstruktor für Mittelpunkt und Radius Kreis (const Koord& m, unsigned r) : GeoObj(m), radius(r) { } // Ausgabe (jetzt auch implementiert) virtual void draw () const; // virtueller Destruktor virtual ~Kreis () { } }; /* Ausgabe * - inline definiert */ inline void Kreis::draw () const { std::cout << "Kreis um Mittelpunkt " << refpunkt << " mit Radius " << radius << std::endl; } } // namespace Geo #endif // KREIS_HPP
Abgeleitete Klasse Linie
#ifndef LINIE_HPP #define LINIE_HPP // Headerdatei für I/O #include <iostream> // Headerdatei der Basisklasse #include "geoobj.hpp" namespace Geo { /* Klasse Linie * - abgeleitet von GeoObj * - ein Linie besteht aus: * - einem Anfangspunkt (Referenzpunkt, geerbt) * - einem Endpunkt (neu) */ class Linie : public GeoObj { protected: Koord p2; // zweiter Punkt, Endpunkt public: // Konstruktor für Anfangs- und Endpunkt Linie (const Koord& a, const Koord& b) : GeoObj(a), p2(b) { } // Ausgabe (jetzt auch implementiert) virtual void draw () const; // Verschieben (neu implementiert) virtual void move (const Koord&); // virtueller Destruktor virtual ~Linie () { } }; /* Ausgabe * - inline definiert */ inline void Linie::draw () const { std::cout << "Linie von " << refpunkt << " bis " << p2 << std::endl; } /* Verschieben * - inline neu implementiert */ inline void Linie::move (const Koord& offset) { refpunkt += offset; // entspricht GeoObj::move(offset); p2 += offset; } } // namespace Geo #endif // LINIE_HPP
Usage1-Datei
// Headerdateien für verwendete Klassen #include "linie.hpp" #include "kreis.hpp" #include "geoobj.hpp" // Vorwärtsdeklaration void geoObjAusgeben (const Geo::GeoObj&); int main() { Geo::Linie l1 (Geo::Koord(1,2), Geo::Koord(3,4)); Geo::Linie l2 (Geo::Koord(7,7), Geo::Koord(0,0)); Geo::Kreis k (Geo::Koord(3,3), 11); // inhomogene Menge von geometrischen Objekten: Geo::GeoObj* menge[10]; menge[0] = &l1; // Menge enthält: - Linie l1 menge[1] = &k; // - Kreis k menge[2] = &l2; // - Linie l2 /* Elemente in der Menge ausgeben und verschieben * - es wird automatisch die richtige Funktion aufgerufen */ for (int i=0; i<3; i++) { menge[i]->draw(); menge[i]->move(Geo::Koord(3,-3)); } // Linien einzeln über Hilfsfunktion ausgeben geoObjAusgeben(l1); geoObjAusgeben(k); geoObjAusgeben(l2); } void geoObjAusgeben (const Geo::GeoObj& obj) { /* es wird automatisch die richtige Funktion aufgerufen */ obj.draw(); }
Abgeleitete Klasse Gruppe als Factory Klasse
#ifndef GEOGRUPPE_HPP #define GEOGRUPPE_HPP // Headerdatei der Basisklasse einbinden #include "geoobj.hpp" // Headerdatei für die interne Verwaltung der Elemente #include <vector> namespace Geo { /* Klasse GeoGruppe * - abgeleitet von GeoObj * - eine GeoGruppe besteht aus: * - einem Referenzpunkt (geerbt) * - einer Menge von geometrischen Elementen (neu) */ class GeoGruppe : public GeoObj { protected: std::vector<GeoObj*> elems; // Menge von Zeigern auf GeoObjs public: // Konstruktor mit optionalem Referenzpunkt GeoGruppe (const Koord& p = Koord(0,0)) : GeoObj(p) { } // Ausgabe (jetzt auch implementiert) virtual void draw () const; // Element einfügen virtual void add (GeoObj&); // Element entfernen virtual bool remove (GeoObj&); // virtueller Destruktor virtual ~GeoGruppe () { } }; } // namespace Geo #endif // GEOGRUPPE_HPP
Implementierung der Factory-Klasse
#include "gruppe.hpp" #include <algorithm> namespace Geo { /* add * - Element einfügen */ void GeoGruppe::add (GeoObj& obj) { // Adresse vom geometrischen Objekt eintragen elems.push_back(&obj); } /* remove * - Element löschen */ bool GeoGruppe::remove (GeoObj& obj) { // erstes Element mit dieser Adresse finden std::vector<GeoObj*>::iterator pos; pos = std::find(elems.begin(),elems.end(), &obj); if (pos != elems.end()) { elems.erase(pos); return true; } else { return false; } } /* draw * - alle Elemente unter Berücksichtigung des Referenzpunktes ausgeben */ void GeoGruppe::draw () const { for (unsigned i=0; i<elems.size(); ++i) { elems[i]->move(refpunkt); // Offset für den Referenzpunkt addieren elems[i]->draw(); // Element ausgeben elems[i]->move(-refpunkt); // Offset wieder entfernen } } } // namespace Geo
Usage2-Datei mit Factory-Klasse
// Headerdatei für I/O #include <iostream> // Headerdateien für verwendete Klassen #include "linie.hpp" #include "kreis.hpp" #include "gruppe.hpp" int main() { Geo::Linie l1 (Geo::Koord(1,2), Geo::Koord(3,4)); Geo::Linie l2 (Geo::Koord(7,7), Geo::Koord(0,0)); Geo::Kreis k (Geo::Koord(3,3), 11); Geo::GeoGruppe g; g.add(l1); // GeoGruppe enthält: - Linie l1 g.add(k); // - Kreis k g.add(l2); // - Linie l2 g.draw(); // GeoGruppe ausgeben std::cout << std::endl; g.move(Geo::Koord(3,-3)); // GeoGruppen-Offset verschieben g.draw(); // GeoGruppe nochmal ausgeben std::cout << std::endl; g.remove(l1); // GeoGruppe enthält nur noch k und l2 g.draw(); // GeoGruppe nochmal ausgeben }