1. Hauptnavigation
  2. Navigation des Hauptbereiches
  3. Inhalt der Seite

Programmierung II

Der Modul "Programmierung II" findet im 2. Semester des Studiengangs Medieninformatik im Umfang von 2/0/2 SWS statt. Es werden Programmierkenntnisse der Programmiersprache C vorausgesetzt.
Die Lehrveranstaltung wird mit Hilfe von zwei Teilleistungen bewertet, wobei beide bestanden werden müssen. Im Laufe des Semesters ist ein komplexes Softwareprojekt zu entwickeln (APL, Beleg, Wichtung 30%). Die Aufgabenstellung und der Bewertungsmaßstab werden im Rahmen der Lehrveranstaltung bekanntgegeben. Im Prüfungszeitraum findet eine schriftliche Prüfung statt (120 Minuten, Wichtung 70%).


Inhalt der Lehrveranstaltung

  • grundlegende Erweiterungen zu C
    wir beginnen mit den "nicht objektorientierten" Erweiterungen
  • Referenzen
    in C++ gibt es mehr als nur Zeiger auf Werte
  • Klassen und Objekte
    nun wird es objektorientiert
  • dynamische Speicherverwaltung
    neue Möglichkeiten im Zusammenhang mit Konstruktoren und Destruktoren
  • ein Minimalbeispiel für ein MFC-Programm
    hier arbeiten wir mit Applikations- und Fensterobjekten, wir behandeln Ereignisse
  • Animationen mit Sprites
    grafische Benutzeroberflächen sollte man objektorientiert programmieren
  • Vererbung und Polymorphie
    eines der wichtigsten Standbeine der objektorientierten Programmierung
  • weitere Objekteigenschaften
    statische Elemente, "Freundschaften" zwischen Klassen, Klassen-Arrays, ...
  • Überladen von Operatoren
    die Semantik von Operatoren hängt von den Operanden ab
  • Templates
    wir implementieren Klassen und legen den Typ einiger Werte erst bei der Klassenbenutzung fest ...
  • Ausnahmebehandlung (exception handling)
    die Fehlerbehandlung wird dem Nutzer der Klasse überlassen
  • Namensräume
    Namenskonflikte gehören der Vergangenheit an
  • Komplexbeispiel
    wir fassen unser Wissen noch einmal bei der Programmierung eines kleinen "Grafikeditors" zusammen


Um im MS Visual Studio entsprechende Sicherheitswarnungen bei ANSI-C++ Bibliotheksfunktionen zu unterdrücken, fügen Sie das folgende Compiler-Pragma ein:

#pragma warning (disable: 4996)

Um in der Konsolenausgabe bei Nutzung MS Visual Studio die Umlaute korrekt dargestellt zu bekommen, schalten Sie bitte am Anfang der main()-Funktion die CodePage um:

system("chcp 1252 > NUL");

Sollten auch in den Windowsprogrammen die Umlaute nicht korrekt dargestellt werden, kodieren Sie bitte die Quellcode-Datei mit "UTF-8 BOM" (ggf. in einem externen Editor wie NotePad++). Damit sind die Zeichenketten im Editor und der Windowsoberfläche gleich kodiert und Sie tippen die Umlaute, wie Sie sie später dann auch sehen ;-)

Praktikumsaufgaben:





Grundlegende Erweiterungen zu C


Aufgabe 1: Ein- und Ausgabe über cin und cout
Erstellen Sie ein C++-Programm, das den Nutzer auffordert, entweder die Zahl 1, 2 oder die Zahl 3 einzugeben. Bei Eingabe der Zahl 1 gibt das Programm den Namen des Programmierers aus, bei der Zahl 2 ist es die Adresse des Programmierers und bei Eingabe einer 3 dessen Geburtsdatum. Die auszugebenden Werte sind in Variablen zu speichern, wobei für das Geburtsdatum eine nummerische Variable zu verwenden sind. Die Adresse ist mehrzeilig auszugeben.

Speichern Sie das Datum auf einen int-Wert (32 Bit), wobei jeweils ein Byte für Tag und Monat, sowie 2 Byte für das Jahr zu verwenden sind. Nutzen Sie den <<- und >>-Operator hierbei zum Setzen und Auslesen der Werte. Machen Sie sich deutlich, dass diesen Operatoren jetzt in C++ mit dem Streaming eine weitere Semantik zugeordnet wurde.


Aufgabe 2: Formatierte Ausgabe
Schreiben Sie ein C++-Programm, das eine Dezimalzahl von der Tastatur einliest und dieselbe Zahl in oktaler und hexadezimaler Darstellung wieder auf dem Bildschirm ausgibt.

Aufgabe 3: Standardwerte für Funktionsparameter
Ihr C++-Programm soll eine Funktion enthalten, die Personendaten auf dem Bildschirm ausgibt. Die Funktion muss mit mindestens einem Parameter (dem Namen der Person) aufgerufen werden. Optional können weitere Parameter angegeben werden: das Alter, das Geschlecht, der Wohnort und die Telefonnummer. Werden beim Aufruf die letzten Parameter nicht angegeben, dann entfällt in der Ausgabemaske der Funktion die entsprechende Zeile.
  schreibe("Albin",'m',10); // Name, Geschlecht und Alter ausgeben
  schreibe("Sabine");       // es wird nur der Name ausgegeben
  schreibe(); // FEHLER

Aufgabe 4: Überladen von Funktionen
Schreiben Sie ein C++-Programm mit einer mehrfach überladenen Funktion add. Mit Hilfe dieser Funktion soll die Addition von ganzzahligen Werten, von Gleitkommazahlen und Zeichenketten möglich sein. Testen Sie Ihre Funktion mit folgenden Programmzeilen:
  cout << "3 + 4 = " << add(3,4) << endl;
  cout << "3.1 + 4.1 = " << add(3.1,4.1) << endl;
  char a[20]={"alles"}, b[20]={" einfach"};
  cout << "alles + einfach = " << add(a,b) << endl;
Achten Sie bei der Addition von Zeichenketten auf das Problem des Bereitstellens von Speicherplatz für die verketteten Werte.




Referenzen


Aufgabe 1a: Löschen von Vektorelementen
Programmieren Sie eine Funktion "loesche" mit 3 Parametern: einem Vektor mit int-Werten, der Anzahl der Vektorelemente und einen weiteren int-Wert, der die Position des Vektorelementes angibt, das gelöscht werden soll. Der zweite Parameter ist als Referenz zu übergeben.

Testen Sie Ihre Funktion: Beachten Sie dabei, dass sich die Anzahl der gültigen Vektorelemente nach jedem Aufruf verändert. Zur Ausgabe des Vektors auf dem Bildschirm nutzen Sie ebenfalls eine selbst programmierte Funktion, die den Vektor und die Anzahlder Vektorelemente als Parameter erhält. Der Vektor ist aber hierbei als "konstanter Zeiger" (const) zu realisieren.

Aufgabe 1b: Löschen von Vektorelementen in einer Strukturvariable
Ändern Sie das Programm aus der vorhergehenden Aufgabe in folgendem Sinn:
  • Eine Struktur verwalte den Vektor und dessen Länge.
  • Die Löschefunktion bekommt als Parameter eine Referenz auf die Strukturvariable und die Position des zu löschenden Elementes.
  • Die Ausgabefunktion arbeitet mit einer konstanten Referenz auf die Strukturvariable.
  • Zusatzaufgabe: Versuchen Sie nun den Speicher des Vektors dynamisch zu verwalten :-)
Machen Sie sich deutlich, welchen Effizienzgewinn die Arbeit mit Referenzen an dieser Stelle bedeutet. Zur Vorfreude: Die von Ihnen angelegte Strukturvariable ist bereits ein C++-Objekt ;-)

Aufgabe 2: Vertauschen von Werten
Schreiben Sie zwei Funktionen mit dem Namen swap (siehe auch Überladen von Funktionen). Beide Funktionen haben die Aufgabe die Werte zweier Variablen vom Typ int zu tauschen. Implementieren Sie diese Funktionalität in der einen swap-Funktion mit Hilfe von Zeigern und in der anderen Funktion mit Hilfe von Referenzen. Testen Sie beide Funktionen und machen Sie sich den Unterschied bewusst.



Klassen und Objekte



Aufgabe 1: Konstruktoren und Destruktoren
Schreiben Sie ein C++-Programm mit einer Klasse INT, die neben einem Datenelement vom Typ int noch 3 Methoden enthält: einen Konstruktor, einen Destruktor und eine Display-Funktion zur Anzeige des int-Wertes. Der Konstruktor erhält als einzigen Parameter den im Objekt zu speichernden int-Wert, den er an das Datenelement übergibt und zusätzlich auf dem Bildschirm zusammen mit der Zeichenkette "Konstruktoraufruf" ausgibt. Die Destruktormethode gibt den int-Wert zusammen mit der Zeichenkette "Destruktoraufruf" auf dem Bildschirm aus.

Neben Deklaration und Definition der Klasse INT enthalte Ihr C++-Programm noch die folgenden Programmzeilen:
    void myfunc() {
        cout << "** erste Anweisung in myfunc\n" ;
        INT i(10);        // lokal
        static INT j(11); // lokal statisch
        cout << "** letzte Anweisung in myfunc\n" ;
    }

    INT a(0); // global

    int main() {
        INT x(1); // lokal
        cout << "* in main: zum ersten Mal\n";
        myfunc();
        cout << "* in main: zum zweiten Mal\n";
        myfunc();
        cout << "* letzte Anweisung in main \n";
        return 0;
    }

Erklären Sie sich die Ausschriften des von Ihnen erstellten Programms!

Erweiteren Sie Ihre Klasse INT um eine Methode mit dem Namen "addiere", die den als Parameter übergebenen Wert zum aktuellen Wert des Objektes addieren soll:
    INT o(5);
    o.addiere(10);
    o.display();

Überladen Sie nun die Methode addiere dahingehend, dass folgender Aufruf möglich wird:
    INT o(5),n(10);
    o.addiere(n);
    o.display();
Ergänzen Sie Ihre Vorlesungsmitschriften um die obigen Beispiele.

Aufgabe 2: die Vektorenklasse
Schreiben Sie eine Klasse CVektor50. Von dieser Klasse können Vektorobjekte als Instanzen erzeugt werden, die jeweils bis zu 50 Vektorelemente des Typs int enthalten können. Jedes Objekt "weiß" wieviel gültige Elemente ihr Vektor zur Zeit umfasst. Folgende Methoden sollen zur Klasse CVektor50 gehören:
  • ein Standardkonstruktor, der alle Vektorelemente mit 0 initialisiert und die Vektorgröße auf 50 setzt.
  • ein Konstruktor mit einem Parameter anz (Typ int): Es werden die ersten anz Werte des Vektors (anz<=50) mit Zufallszahlen initialisiert.
  • eine Methode zur Sortierung des Vektors,
  • eine Methode zum Einfügen und eine Methode zum Löschen eines Vektorelementes an einer bestimmten Stelle. (Beachten Sie, dass sich dabei die Anzahl der gültigen Vektorelemente ändert.)
  • und eine Methode zur Ausgabe des Vektorinhaltes auf dem Bildschirm.
Verwenden Sie nach Möglichkeit die im vergangenen Semester angefertigten C-Funktionen indem Sie diese für Ihre neue Klasse anpassen. Auf Instanzen der Klassen soll nur über die oben angegebenen Methoden zugegriffen werden können. Testen Sie Ihre neue Klasse.

Aufgabe 3: einfache Grafikklassen
Schreiben Sie je eine Klasse zur Beschreibung der grafischen Primitive Punkt, Kreis, Linie, Dreieck und Viereck (CPunkt, CKreis, CLinie, CDreieck, CViereck). Die Klasse CPunkt ist dabei zur Implementierung der Klassen CLine, CKreis, CDreieck und CViereck im Sinne der "benutzt-Beziehung" zu verwenden. Eine display()-Methode soll der Ausgabe der Eigenschaften eines Objektes (diesmal noch in Textform) dienen. Alle gekapselten Eigenschaften sollen über Methoden der Klasse änderbar und abfragbar sein.

Schreiben Sie eine Container-Klasse CContainer, die jeweils 50 Objekte der oben genannten Klassen in einem Array "sammeln" kann. Es sind Methoden zum Einfügen und Löschen solcher Objekte bereitzustellen. Eine display()-Methode dient der Ausgabe aller gesammelten Objekte.



Dynamische Speicherverwaltung


Aufgabe 1: eine Vektorklasse mit dynamischer Vektorlänge
ändern Sie Ihr Programm aus der Aufgabe "die Vektorenklasse" des Abschnitts "Klassen und Objekte" dahingehend, dass die Vektorgröße nicht mehr auf max. 50 Elemente eingeschränkt bleibt, sondern dynamisch ist. Beachten Sie also, dass sich die Größe des Vektors beim Einfügen und Löschen von Elementen ändert. Folgende Methoden sollen zusätzlich implementiert werden:
  • Ein Destruktor (incl. Freigabe des Speichers, der durch das Objekt belegt wurde),
  • Der Konstruktor erzeugt einen "leeren" Vektor mit 0 Vektorelementen.
  • überschreiben Sie den Zuweisungsoperator und den Kopierkonstruktor.
Testen Sie Ihr Programm mit einer kleinen main()-Funktion oder in einem Windowprogramm (hierzu müssten Sie aber die Ausgabefunktion ändern).

Zusatzaufgabe:
  • Eine Schreibmethode schreibt die Elemente des Vektors in eine Binärdatei (d.h. alle int-Werte mit jeweils sizeof(int) Bytes). Beachten Sie, dass Sie zu Beginn der Datei zunächst die aktuelle Elementanzahl in die Datei schreiben müssen. Als Parameter ist der Dateiname zu verwenden.
  • Eine Lesemethode dient dem Lesen von Binärdateien, die mit der Schreibmethode geschrieben wurden. Die gelesenen Vektorelemente werden an den aktuellen Vektor angehängt. Als Parameter ist der Dateiname zu verwenden.
Testen Sie Ihre Klasse, indem Sie gleichzeitig mehrere Objektinstanzen erzeugen und über ein kleines Menü die entsprechenden Methoden für diese Objekte dem Nutzer (DAU) zur Auswahl stellen.

Aufgabe 2: eine dynamische Containerklasse für grafische Objekte
Ändern Sie Ihr Programm aus der Aufgabe "einfache Grafikklassen" des Abschnitts "Klassen und Objekte" dahingehend, dass die Größe des "Containers" nicht mehr auf jeweils 50 grafische Objekte eingeschränkt bleibt, sondern dynamisch ist. Beachten Sie also, dass sich die Größe des Vektors beim Einfügen und Löschen von Elementen ändert. Folgende Methoden sollen zusätzlich implementiert werden (wobei Sie natürlich die Algorithmen aus der vorangegangenen Aufgabe wieder verwenden können):
  • Ein Destruktor (incl. Freigabe des Speichers, der durch das Objekt belegt wurde),
  • Der Konstruktor erzeugt einen "leeren" Container mit 0 Objekten.
  • überschreiben Sie den Zuweisungsoperator und den Kopierkonstruktor.
Testen Sie Ihr Programm mit einer kleinen main()-Funktion oder in einem Windowprogramm. Auch hier können Sie sich geeignete Methoden implementieren, die den Containerinhalt in eine Datei schreiben bzw. aus einer Datei lesen können. Somit haben Sie das Fundament für einen ersten eigenen Grafikeditor :-)

Aufgabe 3: CLineList - für doppelt verkettete Listen
Schreiben Sie eine Klasse CLineList, deren Objektinstanz eine doppelt verkettete Liste mit folgenden Datenelementen verwaltet.
	int element_nr;
	enum color {rot, gruen, blau};
	int koord_x1, koord_y1;
	int koord_x2, koord_y2;
Implementieren Sie zunächst eine Klasse CLinie zur Kapselung dieser Daten. Die Klasse CLinie hat lediglich einen Konstruktor, einen Destruktor und eine display-Methode zur Anzeige der Werte (in Textform oder grafisch). Überlegen Sie sich, wo die Aufzählung zu definieren ist. Schreiben Sie nun die Klasse CLineList, indem Sie die aus der Vorlesung bekannten C-Funktionen für doppelt verkettete Listen zu Methoden Ihrer Klasse wandeln. Arbeiten Sie innerhalb der Klasse CLineList mit einer lokalen Klasse CElement, die jeweils einen Zeiger auf den Vorgänger, den Nachfolger und ein Element der Klasse CLinie kapselt. Die Initialisierung einer solchen Liste wird im Standard-Konstruktor vorgenommen. Im Destruktor ist die Liste komplett zu leeren und der Speicher aller Elemente wieder frei zu geben.

Testen Sie Ihre Klasse, indem Sie mehrere Objektinstanzen erzeugen und über ein kleines Menü die entsprechenden Methoden für diese Objekte dem Nutzer (DAU) zur Auswahl stellen.



Windows-Programmierung und Sprite-Animationen



Aufgabe 1: eine erste Dialoganwendung
Machen Sie sich mit dem "Microsoft Developer Studio" und dem Projekttyp "MFC Anwendung" vertraut. Generieren Sie Ihre erste Dialoganwendung. Das Dialogfenster habe die Überschrift "Erste Dialoganwendung". Geben Sie auf den Koordinaten 10,10 die Zeichenkette "Hello world" (oder etwas ähnliches) aus. Vergleichen Sie die generierten Klassen und Objekte mit dem ersten Beispiel in der Vorlesung. Wo wird der Dialog gestartet?

Aufgabe 2: Reaktion auf Ereignisse
Erweitern Sie Ihr Projekt aus der ersten Aufgabe dahingehend, dass Sie auf Mausereignisse reagieren. Nutzen Sie die Entwicklungsumgebung um für die Mausereignisse WM_LBUTTONDBLCLK-, WM_LBUTTONDOWN-, WM_LBUTTONUP-, WM_RBUTTONDBLCLK-, WM_RBUTTONDOWN- und WM_RBUTTONUP-Ereignishandler einzufügen. Diese geben an der Mausposition, an der das Ereignis ausgelöst wurde, den Namen des Ereignisses auf dem Bildschirm aus. Erweitern Sie Ihr Programm um einen Ereignishandler für das Ereignis WM_MOUSEMOVE, der die aktuellen Mauskoordinaten an der Position 10,10 ausgibt, wenn die Maus bei gedrückter Maustaste bewegt wurde.

Geben Sie in der Mitte des Fensters den Buchstaben 'x' aus.

Mit den Cursortasten soll dieser Buchstabe im Fenster bewegt werden können. Über den  WM_CHAR-Ereignishandler kann der darzustellende Buchstabe geändert werden.

Was müssten Sie ändern, damit die Ausgaben erhalten bleiben, wenn das Fenster zum Beispiel nach einer Überdeckung seinen Inhalt behalten soll?

prakt_cpp_prog_bild5.jpg


Aufgabe 3:  die CDIB-Klasse
Generieren Sie eine dialogbasierte Anwendung mit der Entwicklungsumgebung von Visual C++. Fügen Sie 3 Buttons in Ihre Anwendung ein und verbinden Sie diese mit entsprechenden Ereignishändlern. Die Buttons tragen die Aufschrift "zeichne Hintergrund", "zeichne Sprite", "Sprite-Animation". Die Ereignishändler geben zunächst lediglich diese Zeichenketten auf dem Bildschirm aus. Testen Sie Ihre Anwendung. Binden Sie die Klasse CDIB in Ihr Projekt ein und kopieren Sie die Dateien "snow.bmp" und "dogs.bmp" in das aktuelle Projektverzeichnis. Legen Sie in der Dialogklasse zwei Membervariablen der Klasse CDIB an. Laden Sie die Datei "snow.bmp" im Ereignishändler des Buttons "zeichne Hintergrund" auf eine dieser Membervariablen und zeichnen daraufhin das Bild. Testen Sie Ihre Anwendung.

ändern Sie Ihren Ereignishändler des Buttons "zeichne Sprite": Laden Sie die Datei "dogs.bmp" auf die zweite Membervariable der Klasse CDIB. Nutzen Sie die Methode SpriteAdd um einen Spritezustand der Datei in das Hintergrundbild zu kopieren und stellen Sie das veränderte CDIB-Objekt mit dem Hintergrundbild im Dialogfenster dar. Testen Sie Ihre Anwendung.

Ändern Sie Ihren Ereignishändler des Buttons "Sprite-Animation": Zeichnen Sie eine Folge von Spritezuständen aus der Datei "dogs.bmp" (als Animation eines laufenden Hundes) in das Dialogfenster. Nutzen Sie hierzu ebenfalls die Methode SpriteAdd. Ein weiteres Objekt der Klasse CDIB mit dem Hintergrundbild hilft Ihnen, das durch SpriteAdd veränderte CDIB-Objekt wieder in den ursprünglichen Zustand zurückzusetzen.

Aufgabe 4a: Steuerung von Sprites mit Timern, Grafische Menuführung
Programmieren Sie mit Visual C++ und der Ihnen zur Verfügung gestellten Sprite-Bibliothek ein Windows-Programm, das 3 Sprites wie in dem nachfolgenden Screenshot dargestellt über einen Timer gesteuert bewegt. Die "Spriteanimation" soll 15 Bilder pro Sekunde erzeugen. Das sich waagerecht bewegende Sprite rückt pro Bild um 4 Pixel nach links, das andere Sprite -2.5 Pixel in x-Richtung und 1.5 Pixel in y-Richtung. Machen Sie sich Gedanken über eine geeignete Verteilung der Sprites auf z-Ebenen. Die Spritedateien werden Ihnen im Praktikum zur Verfügung gestellt.



Fügen Sie drei weitere Sprites (aus der Datei "buttons.bmp") zur Steuerung der Animation hinzu. Wird die Maus über einen dieser Sprites platziert, so ändert dieses Sprite sein Erscheinungsbild von grau nach rot und zeigt somit an, dass es sich um einen sensitiven Bildschirmbereich handelt. Wird das Start-Sprite mit der Maus angeklickt, so startet die Animation. Das Stop-Sprite stoppt die Animation. Mit Exit kann die Applikation verlassen werden.

Aufgabe 4b: Drag & Drop Sprites
Das Programm aus der vorangegangenen Aufgabe ist dahingehend zu erweitern, dass sich die Sprites für die einzelnen Menüpunkte per Drag & Drop beliebig im Dialogfenster platzieren lassen. Hierzu ist die rechte Maustaste zu verwenden.

Aufgabe 5: ein interaktives Ping-Pong-Spiel
Programmieren Sie ein einfaches Ping-Pong-Spiel. Das Spiel besteht aus 2 Schlägern, die sich am linken bzw. rechten Bildrand befinden und durch Mausbewegung auf- und abwärts bewegen lassen. Mit Hilfe der Schläger muss versucht werden, einen Ball innerhalb des Dialogfensters zu halten. Der Ball (natürlich ein Sprite) wird von der Ober- und Unterkante des Dialogfensters und den Schlägern reflektiert. Berührt der Ball eine Seitenkante des Dialogfensters erhält der Spieler einen Minuspunkt (Anzeige in der Statuszeile oder im Titel des Fensters). Nach jeweils einer Minute kommt ein neuer Ball (anderer Farbe) ins Spielfeld. Maximal 5 Bälle sind zu unterstützen. Mit der 'A'-Taste lässt sich Ihr Spiel in einen "Automatikmodus" bringen ;-)

Aufgabe 6: Ein interaktiver Katalog
Es ist die aus "Quicktime VR" bekannte Funktionalität eines virtuellen Kataloges mit der Spritebibliothek zu implementieren. Nutzen Sie hierzu die Vorgaben aus dem Vorlesungsbeispiel eines Modekataloges.

Das "Modelsprite" reagiert auf das Ereignis WM_LBUTTONDOWN und kann danach bis zum Auftreten WM_LBUTTONUP mit WM_MOUSEMOVE-Ereignisfolgen nach rechts bzw. links gedreht werden.





Vererbung und Polymorphie

Aufgabe 1: grafische Primitive
Schreiben Sie eine Klasse CGrafik, die als Membervariable ein Objekt m_punkt der Klasse CPoint und ein Objekt m_name der Klasse CString besitzt. Ein Konstruktor dient der Initialisierung dieser Variablen. Von dieser Basisklasse sind 2 Klassen abzuleiten:
  • die Klasse CKreis mit einer Membervariablen m_durchmesser und
  • die Klasse CLinie mit der Membervariablen m_zweiterpunkt (Typ CPoint).
Der Konstruktor dieser Klassen dient der Initialisierung der Membervariablen und bedient sich dabei auch des Konstruktors der Basisklasse. Zusätzlich besitzen diese beiden Klassen eine Methode display, die das jeweilige grafische Primitiv auf dem Bildschirm zeichnet (parametrisieren Sie diese Methode mit einem Gerätekontext) und auch den Namen m_name an das Objekt schreibt. Überlegen Sie sich, an welcher Stelle am sinnvollsten die Ausgabe des Namens zu implementieren ist.

prakt_cpp_prog_bild4.jpg 39218 bytes

Testen Sie Ihre Klassen in einer einfachen Dialoganwendung, die zusätzlich einen Button mit der Aufschrift "Zeichne" enthält, der jeweils ein Objekt der Klassen CLinie und CKreis anlegt, mit zufälligen Koordinaten (innerhalb der Fenstergröße) initialisiert und zeichnet.

Aufgabe 2: Basisklassenzeiger für grafische Primitive
Erweitern Sie die Anwendung aus der Aufgabe 1 um eine Klasse CEllipse, die ebenfalls von der Klasse CGrafik erbt und einen Konstruktor sowie eine Methode display() besitzt. Fügen Sie der Dialogklasse Ihrer Anwendung einen Vektor für 20 Zeiger auf Elemente der Klasse CGrafik ein. Initialisieren Sie diesen Vektor zunächst mit NULL-Zeigern. Im Ereignishandler eines neuen Buttons mit der Aufschrift "neue Objekte" werden nach dem Zufallsprinzip 20 neue Objekte der Klassen CLinie, CKreis und CEllipse erzeugt und deren Adressen in dem Vektor gespeichert.
Ändern Sie den Ereignishändler des Buttons "Zeichne" dahingehend, dass die Methode display über alle im Vektor gespeicherten Basisklassenzeiger ausgeführt wird. Vergessen Sie nicht, diese dynamisch erzeugten Objekte wieder zu zerstören. Ist nun klar, weshalb auch der Destruktor der Klasse CGrafik virtuell sein muss?

prakt_cpp_prog_bild3.jpg 28783 bytes


Aufgabe 3: eine Klasse CElemList und deren Elemente
Schreiben Sie sich eine Klasse CElemList, die Funktionalität für einfach verkettete Listen bereitstellen soll und zunächst nur nachfolgend genannte Methoden implementiert. Es ist die für einfach verkettete Listen aus der Vorlesung bekannte Grundfunktionalität in Methoden umzusetzen. In der Liste werden Zeiger auf Objekte der Klasse CElement gespeichert, die ebenfalls zu implementieren ist und die rein virtuelle Methode display bereitstellt.

  class CElemList {
	 private:
		CElement *anker;
		int anz;
	 public:
		CElemList(void);
		~CElemList(void);
		void insert(CElement *e);
		void deletefirst();
		CString display();
  };





Programmieren Sie weiterhin drei von CElement direkt abgeleitete Elementklassen CIntElement, CFloatElement und CXElement. Diese Klassen dienen zur Verwaltung von Elementwerten des Typs int, float und eines möglichst komplex strukturierten Datentyps Ihrer Wahl mit Konstruktor, Destruktor und display()-Methode.
Testen Sie Ihre Klassen nun mit verschiedenene Element-Typen. Wo haben Sie Polymorphie implementiert?

Bemerkung: Sie können (und sollten) auch hier weiter mit Windows-Applikationen arbeiten. Mit dem Visual-Studio ist es sehr einfach im Ressource-Editor einen Button (Schaltfläche) im Dialog zu platzieren und dann mit einem "Doppelklick" einen Ereignishandler für diesen Button in die Dialogklasse einzufügen. Dort können Sie nun Ihre Aufgabe lösen und mit der Methode dc.DrawText(...) die Textausgaben, mit Hilfe eines CString-Objektes auf das Fenster schreiben, z.B.
CElemList li; CClientDC dc(this);
CFont f; f.CreatePointFont(85,"Courier New");   // neuen Font mit 8.5 pt
dc.SelectObject(&f); dc.SetBkMode(TRANSPARENT);   // in den Device Context einwählen
CString s("Liste mit int und float initialisieren ...\n");
for (int i=10; i>0; i--)
     if (i%2)
         li.insert(new CIntElement(i));
     else
         li.insert(new CFloatElement((float)i));
s += li.display();
...
li.insert(new CStringElement("Hallo"));
s += li.display();   // CString-Objekt zusammensetzen
dc.DrawText(s,CRect(15,50,400,300),DT_LEFT);   // und ausgeben
    Beispiel



Aufgabe 4: Erweiterung der CDIB-Klasse (1)
Schreiben Sie eine Klasse CTileDIB, deren Basisklasse die Ihnen bereits bekannte Klasse CDIB ist. Überschreiben Sie die Draw-Methode der Basisklasse durch eine Draw-Methode, die den Inhalt der Bitmap in einem gegebenen Bereich kachelförmig zeichnet (programmieren Sie diese Methode unter Nutzung der ererbten Methode CDIB::Draw(...) aus der Basisklasse). Die Anzahl der "Kacheln" in X- und Y-Richtung ist als int-Parameter zu übergeben, d.h. die Deklaration der Methode CTileDIB::Draw(...) gleicht der Methode CDIB::Draw(...).

Testen Sie Ihre Klasse in einer einfachen Dialoganwendung.

Screenshot

Aufgabe 5: Erweiterung der CDIB-Klasse (2)
Schreiben Sie eine Klasse CCenterDIB, deren Basisklasse die Ihnen bereits bekannte Klasse CDIB ist. Überschreiben Sie die Draw-Methode der Basisklasse durch eine Draw-Methode, die das Bitmap in einem gegebenen Bereich zentriert zeichnet (programmieren Sie diese Methode unter Nutzung der ererbten Methode CDIB::Draw(...) aus der Basisklasse). Der Bereich, für den die Zentrierung gilt, beginnt an den Koordinaten 0,0. Breite und Höhe des Bereiches sind als int-Parameter zu übergeben, d.h. die Deklaration der Methode CCenterDIB::Draw(...) gleicht der Methode CDIB::Draw(...). Testen Sie Ihre Klasse in einer einfachen Dialoganwendung, wobei die Größe des Dialogfensters mit Hilfe der Methode CWnd::GetClientRect(CRect*) ermittelt werden kann.



Überladen von Operatoren


Aufgabe 1: Addition von Vektoren
Ändern Sie Ihr Programm der Aufgabe "eine Vektorklasse mit dynamischer Vektorlänge" des Abschnitts "Dynamische Speicherverwaltung" dahingehend, dass für Objekte dieser Klasse der +-Operator zur Addition (d.h. zum Aneinanderhängen von Vektoren und zum Anfügen eines neuen Vektorelementes) verwendet werden kann. Beachten Sie, dass Sie zum Test des überladenen Operators noch den Zuweisungsoperator und den Kopierkonstruktor überladen müssen. Damit wir diese Operatoren konsistent benutzen können ist nun der +=-Operator zu überladen.

Vektor v1(5), v2(10), v3;
v1.display(); v2.display();
v3 = v1 + v2 + 1000;
v3.display();
v3 += v1;
v3.display();
Vektor v4 = v1; // Kopierkonstruktor
v4.display();

Durch Überladen des []-Operators soll der "sichere" Zugriff auf einzelne Vektorelemente lesend und schreibend gewährleistet werden.

cout << "v1[0] = " << v1[0] << endl;
v1[0] = 1000;
cout << "v1[0] = " << v1[0] << endl;
Überladen Sie nun auch noch den <<-Operator, damit die Vektorobjekte mit Hilfe von Streams ausgegeben werden können.

cout << "Der Inhalt des Vektors v1: " << v1 << endl;


Aufgabe 2: Operatoren für grafische Primitive
Überladen Sie Operatoren für die in den vorangegangenen Praktika erstellte Klasse CKreis. Der "+"-Operator addiert zwei Kreise. Dabei ergibt sich des Durchmesser des Ergebniskreises aus der Summe der Durchmesser der beiden Operanden. Der Mittelpunkt des Ergebniskreises liegt in der Mitte zwischen den als Operanden gewählten Kreisen. Testen Sie den '+'-Operator mit einer kleinen Dialoganwendung. Beachten Sie, dass Sie zum Test des überladenen "+"-Operators noch den Zuweisungsoperator und (unter Umständen) den Kopierkonstruktor überladen müssen.

prakt_cpp_prog_bild1.jpg


Aufgabe 3: Addition von Listen
Ändern Sie Ihr Programm der Aufgabe "die Klasse CELemList" des Abschnitts "Vererbung und Polymorphie" dahingehend, dass für Objekte dieser Klasse der "+"-Operator zur Addition (d.h. zum Aneinanderhängen) von Listenelementen verwendet werden kann. Wo müssen Sie den Operator definieren, dass möglichst alle Klassen in Ihrer Vererbungshierarchie davon profitieren?

Vorsicht: Diese Aufgabe ist nicht so einfach, wie es zunächst scheint! Wir benötigen wegen der dynamischen Speicherverwaltung noch einen eigenen Zuweisungsoperator und einen Kopierkonstruktor. Aber wie können diese Operatoren Objektkopien mit Hilfe von Basisklassenzeigern erzeugen?



Templates


Aufgabe 1: Kellerspeicher und grafische Objekte
Implementieren Sie (unter Nutzung des Vorlesungsbeispiels) ein Template für die Funktionalität eines Kellerspeichers mit dynamischer Speicherverwaltung. Testen Sie diesen Kellerspeicher mit verschiedenen Datentypen und mit den oben eingeführten Grafikobjekten in einer Dialoganwendung. Generieren Sie hierzu 10 Kreise zum Beispiel folgenden Koordinaten ((100,100), (120,120), (140,140) ...) und einem Durchmesser von 100 Pixel, die sich also diagonal im Fenster überdecken. Diese Kreise sind jeweils zu zeichnen und danach im Kellerspeicher abzulegen. Wenn der Nutzer der Schaltfäche ("Auskellern") bedient, holen Sie nacheinander alle Kreise aus dem Keller und zeichnen diese jeweils nach dem pop()-Befehl. Ihre Applikation funktioniert richtig, wenn dabei z-Positionen der Kreise invertiert wurden.

prakt_cpp_prog_bild8.png





Ausnahmebehandlung ("exception handling")


Aufgabe 1: Kellerspeicher mit Ausnahmenbehandlung
Nutzen Sie das Template eines Kellerspeichers aus dem letzten Praktikum. Implementieren Sie zuerst eine Ausnahmebehandlung für den Fall, dass ein Element aus einem leeren Keller "ausgekellert" werden soll. Für diesen Fall ist eine Fehlermeldung mit Hilfe der Funktion AfxMessageBox() auszugeben.

Implementieren und testen Sie nun den []-Operator zum direkten Zugriff auf einzelne Objekte im Keller. Dieser Operator löst bei fehlerhaftem Index eine Ausnahme (exception) aus, die in der Dialogklasse ebenfalls mit einer MessageBox behandelt wird.

Überschreiben Sie zur Wiederholung nun noch den '='-Operator, den '+'-Operator, den '+='-Operator und den Kopierkonstruktor.

Aufgabe 2: Die Klasse CMyString
Sie kennen aus dem Vorlesungsabschnitt zur dynamischen Speicherverwaltung eine Klasse für Zeichenketten. Implementieren Sie diese Klasse hier unter dem Namen CMyString. Überschreiben Sie den Zuweisungs- und den '+'-Operator, den Standardkonstruktor, den Kopierkonstruktor und den Destruktor. Implementieren und testen Sie nun den []-Operator zum Zugriff auf einzelne Zeichen der Zeichenkette. Dieser Operator löst bei fehlerhaftem Index eine Ausnahme (exception) aus, die in der Dialogklasse "gefangen" und (z.B. mit einer MessageBox) behandelt wird.