1.) Tippe die C++ Variante des "Hello World"-Programms ab und bringe es mit dem Compiler zum laufen.
2.) Schreibe ein Programm, das die Größen der fundamentalen
Datentypen, ein paar Zeigertypen und ein paar Aufzählungen deiner
Wahl ausgeben. Benutze dabei den sizeof-Operator (das ist eine Funktion,
die in C/C++ fest integriert ist (formal ist sie als Operator definiert);
sie gibt die Größe eines Objektes zurück).
3.) Schreibe die Deklaration für einen Zeiger auf einen char,
ein Feld von 10 int, einen Zeiger auf ein Feld von Zeichenketten, einen
Zeiger auf einen Zeiger auf ein char, einen konstanten int, einen Zeiger
auf einen konstanten int und einen konstanten Zeiger auf einen int. Initialisiere
jeden.
4.) Füge alle sinnerhaltenden Klammern in die folgenden Ausdrücke
ein (Tipp: Schreibe daneben, was jeweils bei gesetzten Klammern passiert.):
*p++
*–-p
++a--
(int*)p->m
*p.m
*a[i]
5.) Schreibe eine Funktion, die die Fakultät berechnet und rekursiv
arbeitet. Gib mit Hilfe dieser Funktion die Fakultäten der Zahlen
1 bis 10 aus.
6.) Schreibe eine Funktion, die die Werte zweier ints austauscht. Benutze
int* (also Zeiger auf int) als Argumenttyp. Schreibe eine weitere Funktion,
die int& (Referenz) als Argumenttyp verwendet.
7.) Was ist die Größe des Feldes str im folgenden Beispiel:
char str[] = "a short string";
Was ist die Länge der Zeichenkette "a short string"? (Tipp: Größe
ist nicht gleich Länge.)
8.) Definiere eine Tabelle mit den Monatsnamen des Jahres und der Anzahl
der Tage in jedem Monat. Gebe diese Tabelle aus. Tue dies genau zweimal:
Einmal mit einem Feld von char für die Namen und einem Feld für
die Anzahl der Tage und einmal mit einem Feld von Strukturen, wobei jede
Struktur den Namen des Monats und die Anzahl seiner Tage enthält.
9.) Definiere ein Feld von Zeichenketten, wobei die Zeichenketten die
Monatsnamen enthalten. Gebe diese Zeichenketten aus. Übergebe das
Feld an eine Funktion, die die Zeichenketten ausgibt.
10.) Lese eine Folge von Wörtern aus der Eingabe (analog zu cout
ist die Eingabe über cin>>variablenname möglich). Benutze Quit
als das Wort, das die Eingabe beendet. Gebe die Wörter in der Reihenfolge
aus, in der sie eingegeben wurden. Gib kein Wort mehrmals aus. Modifiziere
das Programm so, dass es die Wörter sortiert (Tipp: Verwende die Klasse
string - das ist eine gute Gelegenheit mit dieser Klasse etwas vertrauter
zu werden. :-))
11.) Schreibe eine Funktion cat(), die zwei C-Strings (also Arrays
von chars) als Argumente bekommt und einen C-String zurückgibt, der
die Konkatenation der beiden Argumente ist. Benutze new um Speicher für
das Ergebnis zu bekommen (Tipp: Überlege dir genau, wie du die Strings
übergibst bzw. wie du sie zurücklieferst.) Führe dieselbe
Aufgabe mit der Klasse string durch.
12.) Etwas größeres Projekt: Schreibe einen kleinen Taschenrechner.
Das Programm wird mit dem zu berechnenden Ausdruck aufgerufen, wobei es
sich um einen "einfachen" Ausdruck handeln soll (z. B. 1+3; Eingabe an der
Kommandozeile lautet dann z. B. rechner 1+3), von dem das Programm dann
das Ergebnis berechnet. Der Taschenrechner soll das Ergebnis für +,
- und * berechnen. Verwende in deinem Programm die Parameter von main()
und zur Syntaxanalyse switch-case.
13.) Betrachte:
struct Tnode{
string wort;
int anzahl;
Tnode* links;
Tnode* rechts;
}
a) Schreibe eine Funktion, um neue Wörter in einen Baum von Tnodes
einzufügen, Schreibe eine Funktion, die einen Baum von Tnodes ausgibt.
Schreibe eine Funktion, die einen Baum von Tnodes mit alphabetisch sortierten
Wörtern ausgibt.
b) Arbeite so mit Dateien, dass ein Programm, das ein Tnode verwenden
möchte, nur noch eine Header-Datei einzubinden braucht und eine entsprechende
Objektdatei hinzu linken muss. Schreibe zur Überprüfung ein (kurzes)
Beispielprogramm, das dies leistet.
c) Ändere Tnode so ab, dass nur (ein) Zeiger auf ein beliebig
langes Wort, das als Feld von char im Speicher mit new angelegt wird, verwendet
wird.
14.) Konstruiere einen (kleinen) Fall, indem eine Header-Datei möglicherweise
mehrmals eingefügt würde. Schreibe diesen Fall auf und behebe
ihn mit den entsprechenden #if defined(...) Anweisungen. Schreibe deine
Lösung als Programm auf und versuche es zu compilieren (einmal mit
Zyklus, einmal mit #if defined...).
Klassen:
15.) Schreibe selbst eine Klasse Datum, die die Datumseingabe von der
Eingabe (cin) liest und auch wieder ausgibt. Ergänze weiterhin eine
Elementfunktion, mit der man einen Tag, einen Monat und ein Jahr auf das
Datum aufaddieren kann.
16.) Reimplementiere die Klasse Datum mit einer "Tage seit dem 01.01.1970"-Repräsentation.
17.) Definiere einige Klassen, die Zufallszahlen mit einer bestimmten
Verteilung (z. B. gleichmäßig oder exponentiell), erzeugen. Jede
Klasse soll die Parameter für die Verteilung über den Konstruktor
erhalten. Eine Funktion ziehe() soll den nächsten Wert liefern.
18.) Schreibe die Struktur Tnode (siehe oben) neu als Klasse mit Konstruktoren,
Destruktor etc. Definiere einen Baum aus Tnodes als Klasse mit Konstruktoren,
Destruktoren etc.
Überladene Operatoren:
19.) Erweitere die Klasse Datum (siehe oben) um Operatoren + und -,
die jeweils ein anderes Datum addieren bzw. subtrahieren. Überprüfe
die Korrektheit der Klasse mit entsprechenden Beispielwerten.
20.) Definiere eine Klasse INT, die sich genau wie ein int verhält.
Tipp: Definiere einen Operator INT::operator int().
21.) Definiere eine Klasse vector, die einen Operator [] enthält,
so dass man auf ein vector-Element wie auf ein Array zugreifen kann. Verwende
als Elemente, die vector enthalten kann, int.
Abgeleitete Klassen:
22.) Für die Klasse
class Basis{
public:
virtual void ichBin() {cout << "Basis\n";}
}
leite zwei Klassen ab und definiere für jede Klasse ichBin() so,
dass der Name der Klasse ausgegeben wird. Erzeuge Objekte dieser Klasse
und rufe ichBin() für sie auf. Weise Zeiger auf Objekte der abgeleiteten
Klassen an Basis* -Zeiger zu und rufe ichBin() über diese Zeiger auf.
23.) Für die Klassen Kreis, Quadrat und Dreieck, jeweils abgeleitet
von der abstrakten Klasse Form, soll eine Funktion ueberlappt() definiert
werden, die für zwei Form*-Argumente die passende Funktionen aufruft,
um festzustellen, ob sich die beiden überlappen. Es ist dazu notwendig,
passende (virtuelle) Funktionen zu den Klassen hinzuzufügen. Halte
dich nicht damit auf, den Code zu schreiben, der auf Überlappung prüft;
achte nur darauf, dass sie die richtigen Funktionen aufrufen.
Ausnahmebehandlung:
24.) Schreibe ein Programm, das eine Ausnahme in einer Funktion wirft
und sie in einer anderen auffängt.
25.) Schreibe ein Programm, das aus Funktionen besteht, die einander
bis zu einer Aufruftiefe von 10 aufrufen. Gebe jeder Funktion ein Argument,
das bestimmt, auf welcher Ebene eine Ausnahme geworfen werden soll. Lasse
mit main() diese Ausnahmen fangen und ausgeben, welche Ausnahme gefangen
wurde. Vergesse nicht den Fall, in dem eine Ausnahme in der Funktion gefangen
wird, die sie geworfen hat.
26.) Schreibe eine Funktion, die einen Wert basierend auf ihrem Argument
entweder per return liefert oder diesen Wert als Ausnahme wirft.
27.) Ausnahmen sind Objekte einer Klasse. Diese kann auch aus einer
ganzen Fehlerklassen-Hierarchie stammen, d. h. von einer anderen Klasse
abgeleitet sein. Denke dir eine Beispiel-Fehlerklassen-Hierarchie (z. B.
Mathematische Fehler wie Overflow, Underflow, Division durch Null)
aus. Schreibe ein Beispielprogamm, das einen Fehler aus der untersten Hierarchieebene
wirft und prüfe, was passiert, wenn du anstelle dieses Fehlers die
Basisklasse der Hierarchie fängst.
Templates:
28.) Schreibe ein Template, das einen Stack repräsentiert, der
beliebige Elemente verwalten kann. Zur Überprüfung schreibe eine
Klasse Element, mit Hilfe dessen Objekten du das Template testest.
29.) Schreibe ein vector-Template (siehe auch weiter oben bei den Operatoren),
das beliebige Elemente verwalten kann und Range- und Size- Ausnahmen besitzt
(meint: Wenn man über [] auf nicht vorhandene Elemente zugreifen will
etc.).
30.) Schreibe ein leseZeile()-Template für verschiedene Zeilenarten,
z. B. Artikel-, Anzahl-, Preis-Zeilen.
STL:
31.) Definiere eine Klasse telnummer, die Telefonnummern speichern
kann. Instanziiere die Klasse list (dazu die Header-Datei <list.h> einbinden),
die Daten vom Typ telnummer aufnehmen kann. Füge als Beispiel mehrere
Telefonnummern in die Liste ein. Durchlaufe sie dann einmal mit Hilfe von
Iteratoren in einer While-Schleife, und danach ein zweites Mal mit Hilfe
der Funktion for_each (dazu die Header-Datei <algorithm> einbinden).
Leite nun von der Klasse list<telnummer> eine Klasse tel_liste ab,
die eine zusätzliche Funktion für die Ausgabe der Telefonnummern
besitzt.
32.) Definiere eine Queue, die nur zwei Objekte der Klasse stack verwendet.
Anmerkung: Weitere Übungen zur Standardbibliothek (insbesondere was die Dateioperationen/Streams betrifft) wurden nicht mit aufgenommen, sondern durch Übungen zu LEDA ersetzt. Die STL funktioniert auf dieselbe Weise wie LEDA, nur mit entsprechend anderen Klassen. Man sollte sich die Klassen und die Hierarchie einmal in einem entsprechenden Buch angeschaut haben.
LEDA:
33.) Implementiere den Typen stack mit Hilfe des Ledatyps array
(für allgemeine Typen als Elemente).
34.) Implementiere den Typen queue mit Hilfe des LedaTyps array (für
allgemeine Typen als Elemente).
35.) Implementiere den Typen stack mit Hilfe (Vererbung) des LedaTyps
sList.
36.) Implementiere den Typen queue mit Hilfe (Vererbung) des LedaTyps
sList.
37.) Implementiere eine Funktion reverse, die als Parameter eine list
bekommt und als Rückgabe eine list erzeugt, die die Elemente der Parameter-list
umdreht.
38.) Implementiere eine Funktion conc, die zwei Parameter vom Typ list
bekommt und als Rückgabe eine list erzeugt, die die Elemente der
beiden list konkateniert.
39.) Implementiere eine Funktion merge, die zwei Parameter vom Typ
list und eine Compare-Funktion bekommt (die eine lineare Ordnung auf den
Listenelementen erzeugt) und die Listenelemente geordnet in eine neue Liste
einfügt.
40.) Implementiere eine Funktion quicksort bezüglich des Typs
array.
41.) Implementiere eine Copy-Funktion, die eine Kopie eines Graphen
(als Typ gegeben) erzeugt und dabei alle Kanten umdreht.
42.) Gegeben sei graph als Typ. Implementiere eine Funktion strongComponents,
die einen graph erhält und starke Zusammenhangskomponenten berechnet.
Die Rückgabe soll ein node_array sein, wobei die Nummer der Zusammenhangskomponente
vermerkt ist.
43.) Gegeben sei graph als Typ. Implementiere eine Funktion, die überprüft,
ob der übergebene graph symmetrisch ist und falls dem so ist, ein
edge_array erzeugt, das für jede Kante sein Pendant enthält.
44.) Erstelle einen parametrisierten Graphen. An den Knoten soll eine ID,
an den Kanten soll eine Fahrzeit und eine Liniennummer vermerkt werden.
Erzeuge einen Multigraphen (mit ein paar paar beliebigen Werten). Implementiere
eine Funktion, die zwei Argumente (HaltestellenIDs) bekommt und nachschaut,
ob diese adjazent sind und ggf. die einzelnen Linien und deren Fahrzeiten
(sortiert nach Fahrzeit) ausgibt.