PDF-Datei
Transcription
PDF-Datei
FH Wiesbaden FB DCSM Aufgaben zum Praktikum zur Vorlesung Programmieren in C++, WS 2007/2008 Prof. Dr. Dreher 4. AUFGABENBLOCK Aufgabe 5. Eine Template-Klasse für dynamische n x m Matrizen mit überladenen Operatoren In der Vorlesung wurde die Definition einer Vektor-Klasse Vector für ints besprochen. Orientieren Sie sich daran (und am weiteren Verlauf der Vorlesung), um eine entsprechende Template-Klasse für einen Vektor aus beliebigen Datentypen T zu implementieren. Hier (nur) die (vorläufige) Deklaration der Klasse: template <class T> class Vektor { public: explicit Vektor(int n); ~Vektor(); T& element(int i); int num_elem() const; private: T* p; int size; }; // // // // Konstruktor für n Elemente Destruktor Zugriff auf i-tes Element Anzahl Elemente Verwenden Sie diese (später entsprechend zu modifizierende) Klasse, um eine TemplateKlasse Matrix für dynamische n x m Matrizen zu erstellen, die Elemente vom Typ T enthalten, wobei diese so implementiert ist, daß eine n x m Matrix aus n Vektoren zu je m Elementen besteht, d.h. jede der n Zeilen wird durch einen Vektor aus m Elementen dargestellt. Dies kann so implementiert werden, daß Matrix als dynamische Datenstruktur ein Array aus Zeigern auf (ebenfalls dynamisch erzeugte) Vektoren enthält. Die vorläufige(!) Deklaration von Matrix hat die folgende Form: template <class T> class Matrix { public: Matrix(int n, int m); ~Matrix(); T& element(int i, int j); int num_rows() const; int num_cols() const; private: Vektor<T>** p; int rows; int columns; }; // // // // // Konstruktor für n x m Elemente Destruktor Zugriff auf i-tes Element Anzahl Zeilen Anzahl Spalten // Array von Zeigern auf Objekte Vektor Implementieren Sie die Klasse Matrix unter Beachtung der folgenden zusätzlichen Punkte: • Implementieren Sie den Zugriff auf die Matrixelemente nicht über die oben angegebene Methode element(int, int), sondern duch Überladen des Index-Operators, so dass auf die Matrixelemente in der in C++ üblichen Indexschreibweise, z.B. m[5][7], zugegriffen werden kann (bzw. (*m)[5][7], wenn m ein Zeiger auf eine Matrix ist). - 12 - 19.11.2007 FH Wiesbaden FB DCSM Aufgaben zum Praktikum zur Vorlesung Programmieren in C++, WS 2007/2008 Prof. Dr. Dreher – Um dies realiseren zu können, nutzen Sie aus, daß eine Matrix aus Vektoren besteht. Sie enthält so viele Vektoren, wie sie Zeilen hat, jeder Vektor hat soviele Elemente wie die Matrix Spalten besitzt. – Der Indexoperator der Matrix (erste Indexposition) liefert eine Referenz auf den entsprechenden Vektor zurück, der zweite Indexoperator hat als Operanden diese Vektorreferenz und liefert selbst eine Referenz auf das entsprechende Element des Vektors zurück (vom Typ T). – Aus diesem Grund müssen Sie auch für die Klasse Vektor den Indexoperator überladen! • Der Konstruktor für Matrix hat zwei Parameter, wobei der erste die Anzahl der Zeilen, der zweite die Anzahl der Spalten der Matrix angibt. • Implementieren Sie zusätzlich eine Methode init(), die die Matrix mit irgendwelchen Werten initialisiert, z.B. mit Zeilen- und Spaltenindex als zusammengesetzte Zahl interpretiert. • Überladen Sie den Ausgabeoperator, so dass eine Matrix wie ein Standarddatentyp in geeigneter Weise ausgegeben werden kann: cout << *m; // m ist Zeiger auf eine Matrix – Um dies zu realisieren überladen sie zunächst den << Operator der Klasse Vektor. Bei der Implementierung des Überladens desselben Operators für Matrix können Sie dann bereits von dem überladenen Operator der Klasse Vektor Gebrauch machen, indem sie für jede Zeile der Matrix einen Vektor ausgeben. • Überladen Sie den Multiplikationsoperator, so dass zwei Matrizen a und b ebenfalls in der üblichen Schreibweise miteinander multipliziert werden können und das Ergebnis einer (existierenden und geeignet dimensionierten) Matrix c zugewiesen werden kann: c = a * b; bzw. *c = *a * *b; // a, b und c Zeiger auf Matrizen – Um dies zu realisieren, müssen Sie neben dem Überladen des Operators * für ein Produkt aus zwei Matrizen auch für beide Klasse Matrix und Vektor geeignete Kopierkonstruktoren definieren und ihre Zuweisungsoperatoren überladen. Nur dadurch können Sie sicherstellen, daß nicht nur die Attributwerte der Klassen sondern auch die mit den Klassen verbundene dynamische Datenstrukturen kopiert und zugewiesen werden. – Stellen Sie auch hier durch Zusicherungen sicher, daß die Zeilen- und Spaltenzahlen der beteiligten Matrizen so geschaffen sind, daß die gewünschte Operation durchgeführt werden kann. – Auch hier müssen alle Matrix-Objekte vor Aufruf in der korrekte Größe existieren. – Die Funktion wird sinnvollerweise in der Header-Datei von Matrix deklariert. Prüfen Sie alle möglichen Fehlerzustände durch Zusicherungen (Makro assert(); deklariert in der Include-Datei <cassert>) und fangen Sie mögliche Exceptions. Schreiben Sie ein Hauptprogramm mit den folgenden Funktionsschritten: • Lesen sie die vom Benutzer gewünschte Zeilen- und Spaltenzahl ein. • Erzeugen sie dynamisch ein entsprechendes Matrix-Objekt m und initialisieren es mit der Methode init() und geben es mit print() aus. - 13 - 19.11.2007 FH Wiesbaden FB DCSM Aufgaben zum Praktikum zur Vorlesung Programmieren in C++, WS 2007/2008 Prof. Dr. Dreher • Erzeugen Sie ein zweites Matrix-Objekt n von geeigneter Größe, so daß das Produkt m * n gebildet werden kann. • Erzeugen Sie ein drittes Matrix-Objekt mn, das das Produkt aus m mal n aufnehmen kann. • Berechnen Sie das Produkt mn als m mal n mittels mmpy() und geben es aus. • Führen Sie die Ausgabe sowohl über den überschriebenen Ausgabeoperator als auch durch Ausgabe jedes Elementes der Matrix einzeln (über Index-Zugriffe) durch. • Vernichten Sie alle dynamisch allokierten Instanzen. Beobachten Sie mit dem Debugger, daß beim Vernichten der Objekte auch die internen Strukturen mit Hilfe des Destruktors freigegeben werden. Teilen Sie die einzelnen Teile des Programms in der folgenden Form projektmäßig auf. Von der C++ Sprachdefinition sollte das möglich sein: • MatMain.cpp Hauptprogramm • Vektor.h Deklaration der Klasse Vektor • Vektor.cpp Implementierung der Klasse Vektor • Matrix.h Deklaration der Klasse Matrix • Matrix.cpp Implementierung der Klasse Matrix Leider erlauben die meisten existierenden Compiler dies nicht, wenn in den cpp-Dateien Template-Klassen implementiert sind. Die entsprechenden Instanzen der Methoden, die ja nur im Hauptprogramm angefordert werden, werden dann nicht gefunden. Nicht sehr eleganter Ausweg ist, alle Methoden in den Header-Dateien zu implementieren. Auszug aus http://www.comeaucomputing.com/techtalk/templates/#whylinkerror (xyz ist die Template-Klasse): One way around this is to explicitly request the instantiation of xyz, in this example of xyz<int>. In a brute force effort, this could be added to xyz.cpp by adding this line at the end of it: template xyz<int>; which requests that (all of) xyz<int> be instantiated. That's kind of in the wrong place though, since it means that everytime a new xyz type is brought about that the implementation file xyz.cpp must be modified. A less intrusive way to avoid that file is to create another: // xyztir.cpp #include "xyz.cpp" // .cpp file!!!, not .h file!! template xyz<int>; This is still somewhat painful because it still requires a manual intervention everytime a new xyz is brought forth. In a non-trivial program this could be an unreasonable maintenance demand. - 14 - 19.11.2007 FH Wiesbaden FB DCSM Aufgaben zum Praktikum zur Vorlesung Programmieren in C++, WS 2007/2008 Prof. Dr. Dreher So instead, another way to approach this is to #include "xyz.cpp" into the end of xyz.h: // xyz.h // ... previous content of xyz.h ... #include "xyz.cpp" You could of course literally bring (cut and paste it) the contents of xyz.cpp to the end of xyz.h, hence getting rid of xyz.cpp; it's a question of file organization and in the end the results of preprocessing will be the same, in that the method bodies will be in the header, and hence brought into any compilation request, since that would be using the respective header. Either way, this has the side-effect that now every template is in your header file. It could slow compilation, and it could result in code bloat. One way to approach the latter is to declare the functions in question as inline, so this would require you to modify xyz.cpp in the running example. - 15 - 19.11.2007