C++: Fortgeschrittene Syntax
Inhaltsverzeichnis und Lernziele des Kapitels C++: Fortgeschrittene Syntax.
- Einordnung des Artikels
- Inhaltsverzeichnis
- Lernziele
- C++: Strukturierte Programmierung mit Funktionen
- Weitere Konzepte der strukturierten Programmierung in C++: Einsatz von Vektoren, Gültigkeitsbereiche
- Spezielle Konzepte der strukturierten Programmierung in C++: call by reference, Rekursion, Function Templates
- C++: Aufgaben zur strukturierten Programmierung
- C++: Grundlagen der objekt-orientierten Programmierung (OOP)
- C++: Konstruktoren und Destruktoren
- Objekt-orientiertes Design in C++: Datenkapselung und UML-Diagramme
- Objekt-orientiertes Design in C++: Komposition und Vererbung
Einordnung des Artikels
- Einführung in die Informatik
- C++: Fortgeschrittene Syntax
Inhaltsverzeichnis
- C++: Strukturierte Programmierung mit Funktionen
- Weitere Konzepte der strukturierten Programmierung in C++: Einsatz von Vektoren, Gültigkeitsbereiche
- Spezielle Konzepte der strukturierten Programmierung in C++: call by reference, Rekursion, Function Templates
- C++: Aufgaben zur strukturierten Programmierung
- C++: Grundlagen der objektorientierten Programmierung (OOP)
- C++: Konstruktoren und Destruktoren
- Objekt-orientiertes Design in C++: Datenkapselung und UML-Diagramme
- Objekt-orientiertes Design in C++: Komposition und Vererbung
Lernziele
In dieser Einführung in die Informatik sind zwei Teile zu C++ enthalten: die Einführung in C++ und C++: Fortgeschrittene Syntax. Im Teil Einführung in C++ wurde die elementare Syntax von C++ vorgestellt, insbesondere wie die allgegenwärtigen Strukturelemente Bedingung, Alternative und Schleife in C++ formuliert werden. Damit lassen sich bereits beliebige Algorithmen implementieren, man arbeitet dabei allerdings auf der Ebene der prozeduralen Programmierung. Das heißt, man schreibt einen Algorithmus als Abfolge von Befehlen und kann noch keine Unterprogramme bilden. Diese Art der Programmierung ist für kleine Programme, deren Quelltexte etwa ein bis zwei Bildschirme füllen, völlig ausreichend. Größere Projekte sollte man mit dieser Technik aber nicht in Angriff nehmen.
Deshalb werden in diesem Teil C++: Fortgeschrittene Syntax diejenigen Konzepte vorgestellt, die man für beliebig große Projekte benötigt. Damit sind insbesondere Funktionen (oder Unterprogramme) gemeint, um im Sinne der strukturierten Programmierung zu arbeiten, sowie Klassen, um objekt-orientierte Programmierung (OOP) zu verwirklichen.
Es wird sich zeigen, dass in diesem Umkreis noch weitere Konzepte vorgestellt werden müssen, die man für einen sicheren Umgang mit strukturierter beziehungsweise objekt-orientierter Programmierung benötigt; um die wichtigsten Konzepte zu nennen:
- der Unterschied zwischen call by value und call by reference
- Vertiefung des Begriffes des Gültigkeitsbereiches (scope)
- Function Templates
- Container-Klassen aus der Standard-Bibliothek, insbesondere Vektoren (vector) und Felder (array)
- Konstruktoren und Destruktoren
- Datenkapselung
- UML-Diagramme
- Vererbung, Komposition
- polymorphe Funktionen.
Nach dem Durcharbeiten dieses Teils sollten Sie in der Lage sein:
- die Eigenschaften der strukturierten und objekt-orientierten Programmierung kennen (so wie sie auf abstrakter Ebene im Teil Einführung in die Programmierung beschrieben wurden),
- die entsprechenden Konzepte konkret mit der Sprache C++ anwenden können,
- und damit Aufgaben lösen, wie sie im Teil Programmieraufgaben vorgestellt werden.
C++: Strukturierte Programmierung mit Funktionen
In der strukturierten Programmierung versucht man Aufgaben, die immer wieder erledigt werden müssen, in Funktionen (oder Unterprogramme) auszulagern. Dadurch wird das Hauptprogramm schlanker und — bei geeigneter Wahl der Namen der Funktionen — liest es sich so leicht wie Pseudocode.
In diesem ersten Kapitel lernen Sie die Syntax kennen, wie in C++ Funktionen realisiert werden und mit Hilfe der ersten Beispiele werden schon die ersten Design-Prinzipien vorgestellt. Nach dem Durcharbeiten dieses Kapitels sollten Sie:
- Die Unterscheidung zwischen Deklaration und Implementierung einer Funktion kennen und für beide die Syntax beherrschen.
- Sie sollten verstanden haben, warum man diese Trennung vornimmt und welche Informationen in der Deklaration beziehungsweise Implementierung enthalten ist.
- Verstehen, warum man früher zwischen Prozedur und Funktion unterschieden hat und warum diese Trennung heute nicht mehr vorgenommen wird und man nur noch von Funktionen spricht (die Aktionen ausführen oder einen Rückgabewert berechnen — hier wird oder wie ein logisches ODER verwendet und es sind auch Speizialfälle denkbar für Funktionen, die entweder Aktionen ausführen oder nur einen Rückgabewert berechnen).
- Sie sollen wissen,
- was man unter dem Überladen einer Funktion versteht,
- welche Versionen des Überladens möglich sind und welche nicht,
- wann das Überladen einer Funktion sinnvoll eingesetzt werden kann und
- wie man das Überladen von Funktionen in C++ realisiert.
- Verbunden damit sind die Begriffe Prototyp einer Funktion und Signatur einer Funktion; Sie sollten diese Begriffe kennen, da sie in der Literatur sehr oft verwendet werden und genauer beschreiben, welche Information in der Deklaration enthalten ist.
- Sie sollen globale Funktionen definieren können und verstehen, wie man die Deklaration und die Implementierung von Funktionen in verschiedenen Dateien vornimmt (Header-Datei mit der Endung .h und Implementierung mit der Endung .cpp).
Weitere Konzepte der strukturierten Programmierung in C++: Einsatz von Vektoren, Gültigkeitsbereiche
In diesem Kapitel werden einige Beispiele für Funktionen vorgestellt, die von Feldern (array) und Vektoren (vector) Gebrauch machen. Diese werden eingesetzt, wenn man Listen von gleichartigen Objekten benötigt (zum Beispiel Listen von Gleitkommazahlen oder ganzen Zahlen). In einem Feld kann dabei die Anzahl der Objekte nicht mehr verändert werden, wenn das Feld einmal erzeugt wurde, dagegen kann bei einem Vektor die Anzahl der Objekte auch später noch verändert werden. Es sollte klar sein, dass diese Container-Klassen nach den fundamentalen Datentypen die wichtigsten Datentypen in C++ sind.
Und anhand der Beispiele für Funktionen werden die Konzepte Gültigkeitsbereich einer Variable, lokale Variable, globale Variable und lokale static-Variable vorgestellt. Gerade bei großen Projekten (wo es schnell vorkommen kann, dass Variablen gleichen Namen besitzen, aber gänzlich verschiedene Bedeutung haben), muss man darauf achten, dass der Gültigkeitsbereich einer Variable so klein wie möglich gemacht wird, um Namenskonflikte zu vermeiden. Dazu ist es nötig, die genannten Arten von Variablen und ihre Eigenschaften zu kennen und zu verstehen, wann man welche einsetzen muss.
Spezielle Konzepte der strukturierten Programmierung in C++: call by reference, Rekursion, Function Templates
Die hier vorgestellten Konzepte sind für Anfänger schwer zu verstehen und Sie werden sie in Ihren ersten Programmen kaum einsetzen. Da sie aber in der Standard-Bibliothek häufig vorkommen und Sie im Lauf der Zeit mehr und mehr dazu übergehen, auf Funktionen und Klassen aus der Standard-Bibliothek zuzugreifen, ist es wichtig diese Konzepte kennenzulernen. Spätestens wenn Sie von Sandkastenspielen zu ernsthafter Programmierung übergehen, werden Sie diese Konzepte selbst einsetzen müssen.
C++: Aufgaben zur strukturierten Programmierung
Sie werden schnell sehen, dass ein Programm, in dem Funktionen definiert werden, nicht automatisch besser ist als das entsprechende Programm, das den kompletten Algorithmus als eine Abfolge von Befehlen ohne Funktionen implementiert. Man muss hier schwierige Design-Entscheidungen treffen, damit die strukturierte Programmierung tatsächlich besser lesbare und schlankere Programme liefert:
- Welche Aufgaben werden in Funktionen ausgelagert?
- Wann soll eine Funktion in mehrere Funktionen zerlegt werden?
- Welche Deklaration (also welche Datentypen für die Eingabe- und Rückgabewerte) ist für eine Funktion geeignet? (Bei der objekt-orientierten Programmierung wird man diese Entscheidung nach anderen Kriterien vornehmen.)
Es ist daher unbedingt nötig, dass Sie einige Übungen absolvieren, bei denen Sie:
- einerseits die Syntax für die Deklaration, Implementierung und den Aufruf von Funktionen einüben und
- andererseits erste Design-Entscheidungen selber treffen müssen.
C++: Grundlagen der objekt-orientierten Programmierung (OOP)
In der Übersicht über Programmiersprachen wurden — sehr allgemein — die Eigenschaften und Vorteile einer objekt-orientierten Programmiersprache beschrieben. Grundlegend dafür ist die Unterscheidung zwischen Klasse und Objekt. Eine Klasse wird oft als der Bauplan für ein Objekt bezeichnet: sie definiert die Datenelemente (Eigenschaften) und Methoden (Funktionalitäten), wobei die Datenelemente in einem konkreten Objekt spezielle Werte annehmen.
Hier wird die C++-Syntax eingeführt, mit der die Konzepte der objekt-orientierten Programmierung umgesetzt werden. Lernziele sind dabei:
- Sie sollen den Unterschied zwischen Klasse und Objekt beschreiben können.
- Sie sollen Klassen in C++ anlegen können und dazu verstanden haben, wie man dabei (ähnlich wie bei Funktionen) zwischen Deklaration und Implementierung trennt.
- Sie sollen sich mit Ihrer Entwicklungsumgebung vertraut machen, um zu wissen, wie Sie von ihr beim Anlegen einer neuen Klasse unterstützt werden.
- Die vorgestellten Beispiele sollen verdeutlichen, dass die objekt-orientierte Programmierung erst bei größeren Projekten eingesetzt wird. Deren Umfang ist jetzt deutlich größer als bei den Beispielen zur strukturierten Programmierung und daher ist es wichtig, sich vor dem Anlegen der Klasse einen objekt-orientierten Entwurf zu überlegen. Später werden Sie noch weitere Elemente kennenlernen, die in einen Entwurf eingehen können.
Damit ist klar, dass auch hier wichtige Design-Entscheidungen zu treffen sind:
- Wie werden die Verantwortlichkeiten in einem umfangreichen Programm auf die einzelnen Klassen verteilt?
- Welche Datenelemente und Methoden erhält eine Klasse?
- Welche Datentypen sind für die Datenelemente geeignet?
- Welche Deklarationen sind für die Methoden geeignet?
Um diese Entscheidungen zu treffen, werden in den folgenden Abschnitten weitere Konzepte der objekt-orientierten Programmierung vorgestellt.
C++: Konstruktoren und Destruktoren
Die Aktionen, die beim Erzeugen eines Objektes einer Klasse ausgeführt werden müssen (Initialisierung von Variablen, Erzeugen anderer Objekte), werden im Konstruktor der Klasse zusammengefasst. Entsprechend enthält der Destruktor die Aktionen, die ausgeführt werden bevor das Objekt zerstört wird (also aus dem Hauptspeicher entfernt wird).
Wie andere Funktionen kann ein Konstruktor überladen werden. Daher ist es wichtig in einem objekt-orientierten Entwurf festzulegen, welche Konstruktoren mit welchen Eingabewerten angeboten werden. Dazu wird das Design-Muster der Delegation vorgestellt, das man sehr geschickt einsetzen kann, um Quelltexte schlanker und leichter wartbar zu gestalten.
Wird kein Konstruktor explizit definiert, besitzt jede Klasse einen sogenannten Standard-Konstruktor (default-Konstruktor); mit ihm sind einige Spitzfindigkeiten verbunden, die man als Programmierer kennen muss.
In vielen Anwendungen wird ein sogenannter Kopier-Konstruktor benötigt: mit ihm kann festgelegt werden, welche Aktionen beim Kopieren eines Objektes ausgeführt werden.
Nach dem Durcharbeiten dieses Kapitels sollen Sie die hier vorgestellten Konstruktoren (und den Destruktor) und ihre Eigenschaften kennen, um selber Design-Entscheidungen zu treffen, wann und wie sie eingesetzt werden.
Objekt-orientiertes Design in C++: Datenkapselung und UML-Diagramme
Weitere Design-Entscheidungen betreffen die sogenannte Zugriffs-Spezifikation (access modifier). Damit ist gemeint, dass man Methoden einer Klasse als öffentlich (public) oder privat (private) kennzeichnen kann, wodurch festgelegt wird, ob die Methoden von anderen Klassen aufgerufen werden können oder nicht.
Diese Unterscheidung hört sich zunächst eher belanglos an, ist aber bei großen Projekten wieder eine wichtige Design-Entscheidung: die öffentlichen Methoden legen fest, welche Aktionen das Programm nach außen sichtbar ausführen kann, dagegen sind die privaten Methoden eher Hilfs-Methoden, die die Implementierung komplexer Algorithmen erleichtern. Etwas abstrakter formuliert, kann damit eine Datenkapselung erreicht werden, da gewisse Teile des Programmes nach außen nicht sichtbar sind.
In diesem Abschnitt wird am Beispiel eines kleinen Statistik-Projektes (insbesondere beim Einbau eines Zufallsgenerators) diskutiert, welche Methoden als öffentlich beziehungsweise als privat spezifiziert werden.
Bei der Besprechung von Design-Entscheidungen ist Ihnen inzwischen sicherlich aufgefallen, dass die Deklaration einer Methode viel wichtiger ist als ihre Implementierung. Die sogenannten UML-Klassendiagramme bieten eine Möglichkeit, die Deklarationen zusammen mit den Zugriffs-Spezifikationen übersichtlich und schnell verständlich darzustellen. Sie sind daher ein unverzichtbares Hilfsmittel beim Entwurf von Klassen geworden.
Objekt-orientiertes Design in C++: Komposition und Vererbung
In diesem Abschnitt wird die objekt-orientierte Programmierung mit C++ zum Abschluss gebracht: Sie lernen, wie man die Vererbung in C++ realisiert. In den Beispielen wird dann gezeigt:
- wie man die Vererbung einsetzen kann (insbesondere um bereits vorhandene Klassen — sei es aus Bibliotheken oder selbst implementierte — einzusetzen und zu erweitern),
- wie man sie gleichwertig durch eine Komposition von Klassen ersetzen kann und
- wie man durch die Kombination von Vererbung und Komposition eine Baumstruktur implementieren kann.
Letzteres soll insbesondere als Beispiel für eine selbstdefinierte Datenstruktur dienen — und Ihnen aufzeigen, welche ungeahnten Möglichkeiten die objekt-orientierte Programmierung bietet.
Das Beispiel der Baumstruktur wird zudem herangezogen, um das Konzept des Polymorphismus zu erläutern. Damit ist gemeint, dass eine Methode in den Unterklassen anders implementiert sein kann als in der Oberklasse und je nachdem welches Objekt gerade vorliegt, wird beim Aufruf der Methode die entsprechende Implementierung verwendet. Das heißt der Programmierer muss beim Erstellen des Quelltextes noch nicht wissen, mit welchem Objekt der Methodenaufruf stattfinden wird, aber er kann durch den Einsatz des Polymorphismus dafür sorgen, dass für jedes Objekt die richtige Methode aufgerufen wird.