C++-Programmierung verständlich erklärt

Werbung
1
Einstieg in die Welt von C++
Im ersten Kapitel des Buches können Sie sich noch entspannt zurücklehnen
und einen Kaffee oder eine Tüte Gummibärchen bereitstellen. Hier geht es
einfach nur um die Geschichte von C++, den vorhandenen Standard, was Sie
für C++ benötigen und welche Schreibkonventionen im Buch verwendet
werden. Wenn diese Formalitäten geklärt sind, können Sie in Kapitel 2
schon mit den ersten Trockenübungen anfangen.
1.1
Die (Kurz-)Geschichte von C++
Ursprünglich wurde C++ von Dr. Bjarne Stroustrup 1979 entwickelt, um Simulationsprojekte mit geringem Speicher- und Zeitbedarf zu programmieren. Auch hier lieferte (wie schon bei C) kein geringeres Betriebssystem als
Unix die Ursprungsplattform dieser Sprache. Stroustrup musste (damals
noch bei den Bell Labs beschäftigt) den Unix-Betriebssystemkern auf verteilte Programmierung analysieren. Für größere Projekte hatte Stroustrup bis
dahin die Sprachen Simula – die allerdings in der Praxis recht langsam bei
der Ausführung ist – und BCPL verwendet, die zwar sehr schnell ist, aber
sich für große Projekte nicht eignet.
Simula und BCPL
Simula gilt als Vorgänger von Smalltalk. Viele der mit Simula eingeführten
Konzepte finden sich in modernen objektorientierten Programmiersprachen wieder. BCPL (Kurzform für: Basic Combined Programming Language)
ist eine um 1967 von Martin Richards entwickelte, kompilierte, systemnahe Programmiersprache, abgeleitet von der Combined/Cambridge Programming Language CPL. Es ist eine Sprache aus der ALGOL-Familie.
Stroustrup erweiterte die Sprache C um ein Klassenkonzept, wofür er die
Sprache Simula-67 (mit der Bildung von Klassen, Vererbung und dem Entwurf virtueller Funktionen) und später Algol68 (Überladen von Operatoren;
Deklarationen im Quelltext frei platzierbar) sowie Ada (Entwicklung von
Templates; Ausnahmebehandlung) als Vorlage nahm. Heraus kam ein »C mit
Klassen« (woraus etwas später auch C++ wurde). C wurde als Ursprung verwendet, weil diese Sprache eben schnellen Code erzeugte, einfach auf andere Plattformen zu portieren ist, fester Bestandteil von Unix ist und vor allem
15
1
Einstieg in die Welt von C++
auch, weil C eine stark verbreitete Sprache ist, wenn nicht sogar die am
stärksten verbreitete Sprache überhaupt. Natürlich wurde weiterhin auf die
Kompatibilität zu C geachtet, damit auch in C entwickelte Programme in
C++-Programmen liefen. Zusammengefasst wurde »C mit Klassen« zunächst
um folgende Sprachelemente, auf die später eingegangen wird, erweitert:
왘
Klassen
왘
Vererbung (ohne Polymorphismus)
왘
Konstruktoren, Destruktoren
왘
Funktionen
왘
Friend-Deklaration
왘
Typüberprüfung
Einige Zeit später, 1983, wurde aus »C mit Klassen« die Programmiersprache
C++. Der Inkrementoperator ++ am Ende des C sollte darauf hinweisen, dass
die Programmiersprache C++ aus der Programmiersprache C entstand und
erweitert wurde. Folgende neue Features kamen dann gegenüber »C mit
Klassen« hinzu:
왘
virtuelle Funktionen
왘
Überladen von Funktionen
왘
Überladen von Operatoren
왘
Referenzen
왘
Konstanten
왘
veränderbare Freispeicherverwaltung
왘
verbesserte Typüberprüfung
왘
Kommentare mit // an das Zeilenende anfügen (von BCPL)
1985 erschien dann die Version 2.0 von C++, die wiederum folgende Neuerungen enthielt:
왘
Mehrfachvererbung
왘
abstrakte Klassen
왘
statische und konstante Elementfunktionen
왘
Erweiterung des Schutzmodells um das Schlüsselwort protected
16
1.2 Der ANSI-C++-Standard
1.2
Der ANSI-C++-Standard
Es dauerte relativ lange, bis 1998 C++ von der ISO genormt wurde (ISO/IEC
14882:1998). Erweitert wurde C++ in dieser Zeit noch um folgende Features:
왘
Templates
왘
Ausnahmebehandlung
왘
Namensräume
왘
neuartige Typumwandlung
왘
boolesche Typen
Natürlich entstanden während der Zeit der Weiterentwicklung von C++
auch eine Menge Standardbibliotheken wie beispielsweise die Stream-I/OBibliothek, die die bis dahin traditionellen C-Funktionen printf() und
scanf() ablösten. Ebenfalls eine gewaltige Standardbibliothek fügte HP mit
STL (Standard Template Library) hinzu.
Im Jahre 2003 wurde dann die erste überarbeitete Version von ISO/IEC
14882:1998 verabschiedet und ISO/IEC 14882:2003 eingeführt. Allerdings
stellte diese Revision lediglich eine Verbesserung von ISO/IEC 14882:1998
dar und keine »neue« Version.
Eine neue Version von C++ (auch inoffiziell bekannt unter C++0x oder
C++200x) sollte noch in jenem Jahrzehnt erscheinen – zumindest deutete
der Name dies so an. 2009 wurde der Termin dann auf 2010 geändert. Auch
wenn einige Compiler jetzt schon Teile des neuen Standards implementiert
haben, scheint die offizielle Einführung des neuen Standards wohl erst für
2011 angesetzt zu sein.
Standard in diesem Buch
Für dieses Buch brauchen Sie sich übrigens noch keine Gedanken um die
Frage »Welcher C++-Standard und mit welchem Compiler?« zu machen. Da
dieses Taschenbuch kein Kompendium ist, werden hier nur die grundlegenden Paradigmen von C++ behandelt, und diese können Sie mittlerweile mit
jedem handelsüblichen und kostenlosen C++-Compiler verwenden und
erlernen, der dem Standard ISO/IEC 14882:2003 entspricht.
17
1
Einstieg in die Welt von C++
1.3
Was benötige ich für C++?
Um aus einem einfachen C++-Quellcode eine ausführbare Datei zu erstellen,
gibt es im Grunde zwei Wege – einen ungemütlichen und einen gemütlichen Weg. Der unbequeme Weg (wobei dies wohl eher Ansichtssache ist)
lautet:
1. Den Quellcode in einen beliebigen ASCII-Texteditor eintippen und
abspeichern.
2. Den Quellcode mit einem Compiler übersetzen, wodurch eine Objektdatei (*.obj oder *.o) erzeugt wird.
3. Die Objektdatei mit einem Linker binden, woraus eine ausführbare
Datei erzeugt wird. Der Linker sucht außerdem alle benötigten Funktionen aus den Standardbibliotheken heraus und fügt sie anschließend dem
fertigen Programm hinzu.
Für den unbequemen Weg bräuchten Sie also nur einen einfachen ASCIITexteditor, einen Compiler und einen Linker. Den Compiler und Linker
müssten Sie hierbei in der Kommandozeile aufrufen. Abbildung 1.1 zeigt
diesen Übersetzungsvorgang.
Quellcode(s)
#include <iostream>
using namespace std;
int main() {
cout << "Hallo Welt!" << endl;
return 0;
}
Compiler
Objektdatei(en)
1110001010101000010101010101001010
Linker
Ausführbare Datei
1100100101010101010010010101010101010
1010101010101010101010111111111111000
1011111111111111111110000101000100101
1110000001111111111000000111111100001
1000000000000000000000000000000000001
1110100010000000001100001111000101000
1100000000000000001100010010010010011
Abbildung 1.1 Die Grundlagen, um aus einem Quellcode mit einfachsten
Mitteln eine ausführbare Datei zu erstellen
18
1.3 Was benötige ich für C++?
Der bequemere Weg führt über eine All-in-one-Lösung, eine Entwicklungsumgebung. Wie der Name schon vermuten lässt, finden Sie hier alles, was
Sie zum Programmieren benötigen, in einem Fenster. Sie müssen sich hierbei nicht mühsam Programm für Programm besorgen und sich durch die
Fenster klicken. Eine Entwicklungsumgebung bietet natürlich meistens
weitaus mehr als nur den Texteditor, Compiler und Linker. Häufig finden
Sie hier auch einen Debugger zum schrittweisen Durchlaufen einer Anwendung, eine Projektverwaltung, um die Übersicht zu behalten, oder einen
Profiler, um die Laufzeit des Programms zu analysieren.
Quellcode(s)
#include <iostream>
using namespace std;
int main() {
cout << "Hallo Welt!" << endl;
return 0;
}
Objektdatei(en)
Compiler
Linker
1110001010101000010101010101001010
Debugger
Ausführbare Datei
1100100101010101010010010101010101010
1010101010101010101010111111111111000
1011111111111111111110000101000100101
1110000001111111111000000111111100001
1000000000000000000000000000000000001
1110100010000000001100001111000101000
1100000000000000001100010010010010011
Versionskontrolle
...
Entwicklungsumgebung
Abbildung 1.2 Der komfortablere Weg, ein ausführbares Programm zu
erstellen, führt über eine Entwicklungsumgebung.
Anleitungen zu Compilern und Entwicklungsumgebungen
Damit Sie sich nicht mit der Installation und Ausführung eines Compilers
oder einer Entwicklungsumgebung herumärgern müssen, habe ich für Sie,
die Leser dieses Buchs, einige Dokumentationen geschrieben. Darin finden
Sie Informationen zu vielen gängigen Compilern und Entwicklungsumgebungen auf den verschiedensten Systemen, die Sie benötigen, um mit dem
Programmieren anzufangen. Sie finden diese Dokumentationen auf der
Webseite des Verlags unter http://www.galileo-press.de/bonus-seite oder
auf http://www.pronix.de/.
19
1
Einstieg in die Welt von C++
1.4
Welches Betriebssystem ...?
Das Buch ist völlig unabhängig vom Betriebssystem und Compiler, weil ich
hier den klassischen C++-Standard (ISO/IEC 14882:2003) beschreibe. Egal,
ob Sie also auf dem Mac, unter Windows oder Linux zu Hause sind, jeder
wichtige Compilerhersteller unterstützt mindestens den C++-Standard.
1.5
Schreibkonventionen
In diesem Buch werden nicht viele Schreibkonventionen verwendet. Listings werden im Buch wie folgt gedruckt:
00
01
02
// listings/kapitelnummer/name_des_lisings.cpp
#include <iostream>
using namespace std;
03
04
05
06
int main() {
cout << "Ich bin ein Blindtext!" << endl;
return 0;
}
In der ersten Zeile (00) finden Sie immer einen Kommentar mit dem Namen
des Listings und der Angabe, in welchem Verzeichnis Sie das Beispiel finden, wenn Sie die Listings heruntergeladen haben.
Bei fett hervorgehobene Zeilen wie hier mit Zeile (03) handelt es sich in der
Regel um die Zeilen, um die es sich hauptsächlich im Listing dreht und die
in der Regel auch nach dem Listing genauer beschrieben werden.
Zeilennummerierungen
Damit Sie in der anschließenden Erläuterung eines Listings auch schneller
die entsprechende Codestelle finden, um die es geht, habe ich bei den entsprechenden Listings diese Zeilen nummeriert. Wenn Sie den Quellcode
abtippen, sollten Sie diese Nummern natürlich nicht mit eingeben.
Wenn nicht völlig eindeutig ist, was ausgegeben wird, finden Sie nach dem
Listing auch eine Art Ausgabe des Programms, die wie folgt aussehen kann:
Ich bin ein Blindtext!
20
1.8 Aufgaben im Buch
1.6
Was kann ich von dem Buch erwarten?
Das Buch richtet sich an Einsteiger, Wiedereinsteiger, Umsteiger, HobbyProgrammierer oder Studenten, die einfach nur die fundamentalen Grundlagen und Standards des puren C++ kennenlernen wollen oder müssen. Dinge wie System-, Hardware- oder GUI-Programmierung werden Sie hier
vergeblich suchen. Auch ein umfassendes Handbuch zu C++ dürfen Sie nicht
erwarten, dazu ist die Materie einfach zu umfangreich. Für solche Zwecke
kann ich Ihnen das umfassendere Buch »C++ von A bis Z«, ebenfalls bei
Galileo Computing erschienen, empfehlen.
1.7
Listings zum Buch
Zwar wäre es empfehlenswert, wenn Sie die Beispiele im Buch selbst eintippen, aber aus eigener Erfahrung weiß ich, dass bei dem einen oder anderen
Beispiel einfach die Lust dazu fehlt. Außerdem habe ich an der einen oder
anderen Stelle auf seitenlangen Code verzichtet und nur die zum Thema passenden Codezeilen ins Buch gedruckt. Daher können Sie die (kompletten)
Listings aus dem Buch und auch die Musterlösungen zu den Aufgaben von
der Webseite http://www.galileo-press.de/bonus-seite herunterladen. Die
Listings zum Buch wurden auf vielen gängigen Systemen (Windows, Linux,
Mac OS X) und Compilern getestet.
1.8
Aufgaben im Buch
Sie finden am Ende jedes Kapitels einige Aufgaben, die in drei Levels aufgeteilt sind. Ziel und Zweck dieser Tests ist es, zunächst einmal zu überprüfen,
ob Sie das Gelesene grundsätzlich verstanden haben, und anschließend zu
üben, wie Sie das erworbene Wissen anwenden.
1.8.1
Level 1
Bei Level 1 handelt es sich um einfache Fragen zum grundlegenden Verständnis des behandelten Themas. Wenn Ihnen diese Fragen Schwierigkeiten bereiten, empfehle ich Ihnen, dass Sie das Kapitel nochmals
durchlesen, weil Ihnen sonst die Grundlagen für weitere Kapitel (und C++
im Allgemeinen) fehlen. Die Fragen von Level 1 zu beantworten, sollte immer das Minimalziel sein.
21
1
Einstieg in die Welt von C++
1.8.2
Level 2
Bei Level 2 werden Code-Ausschnitte gezeigt, und Sie müssen herausfinden,
was daran falsch oder schlecht ist. Hier werden Sie manchen Aha-Effekt erleben. Diesen Level werden Sie übrigens auch in Ihrer künftigen Laufbahn,
öfter als Ihnen lieb ist, durchlaufen. Allerdings wird es sich meistens um Ihren eignen Code handeln, den Sie selbst analysieren und verbessern müssen.
1.8.3
Level 3
Der dritte Level ist der Praxis-Level, wo Sie anhand einer Aufgabenstellung
und mithilfe des im Kapitel Gelernten ein eigenes Listing entwerfen sollen.
Natürlich führen bei der Lösung einer Programmieraufgabe viele Wege ans
Ziel. Daher finden Sie in der Auflösung der Aufgaben am Ende des Buches
auch immer eine mögliche Musterlösung dazu.
22
6
Klassen
In den bisherigen Kapiteln haben Sie die Basistechniken kennengelernt,
jetzt erfahren Sie mehr zu den komplexeren Abstraktionsmechanismen von
C++. Los geht’s mit der objektorientierten Programmierung (kurz: OOP).
Quellcodes herunterladen
Um im Buch nicht seitenweise Code abzudrucken, führe ich meistens nur
die bedeutenden Stellen an. Zwar sollte es für Sie anhand der Erklärungen
kein Problem sein, diese Codestellen selbst in den Beispielen einzubringen,
aber für den Fall der Fälle finden Sie sämtliche Quellcodes auf der Webseite
http://www.galileo-press.de/bonus-seite zum Download.
6.1
Abstraktionsmechanismus
Bei der klassischen prozeduralen Programmierung, wie Sie sie bis zu diesem
Kapitel noch verwendet haben, ist es immer so, dass Daten und Funktionen
keine Einheit bildeten. Probleme traten hierbei häufiger bei falschem Zugriff auf (beispielsweise nicht initialisierte) Daten auf. Musste dann noch ein
Programm geändert oder erweitert werden, war der (Zeit-)Aufwand häufig
enorm, und weitere Fehler waren so gut wie sicher.
Bei der objektorientierten Programmierung hingegen bilden die Objekte
eine echte und logische Einheit aus Daten und Funktionen. Dabei werden
die Daten gekapselt, um Fehler und unzulässige Zugriffe darauf zu vermeiden. Ein so entstandener Typ aus Daten und Funktionen wird als abstrakter
Datentyp bezeichnet, wobei in der OOP die Funktionen als Elementfunktionen (engl. Member Functions) oder auch Methoden bezeichnet werden.
Sinn und Zweck einer solchen Datenkapsel ist es, einen Zugriff auf diese Daten nur noch über eine Elementfunktion zu ermöglichen. Eine direkte Änderung der Daten ohne eine Elementfunktion darf nicht mehr möglich sein.
OOP-Paradigmen kennenlernen
OOP-Sprachen sind für Anfänger häufig nicht unbedingt die leichtesten
Sprachen. Aber Sie sollten sich dennoch Zeit nehmen, die Grundlagen der
OOP-Paradigmen zu verstehen. Umso einfacher fällt dann das Erlernen
einer weiteren OOP-Sprache wie beispielsweise Java oder C#.
203
6
Klassen
6.2
Klassen
Sie werden überrascht sein, wenn ich Ihnen sage, dass Klassen zunächst
nichts anderes als einfache Strukturen (struct) sind – die abgesehen davon
auch Elementfunktionen enthalten dürfen und für die üblicherweise das
Schlüsselwort class statt struct verwendet wird. Wenn Sie das Kapitel zu
den Strukturen gut durchgelesen haben, wird Ihnen der Einstieg in die Welt
der Klassen leichter fallen.
»struct« versus »class«
Der Unterschied zwischen struct und class ist folgender: Wenn Sie in
Ihrer Klasse nicht das Schlüsselwort public oder private verwenden, sind
bei einer mit struct definierten Klasse alle Daten public (also öffentlich
zugänglich) und beim Schlüsselwort class eben alle Daten private (nur
zur Klasse gehörend) sind. Auf public und private gehe ich noch ein. In
C++ stellen die Strukturen allerdings nur einen Sonderfall einer Klasse dar,
und in der Praxis werden Klassen natürlich hauptsächlich mit class programmiert.
6.2.1
Klassendefinition
Um das Klassenkonzept etwas deutlicher zu erklären, soll hier eine Klasse
Automat erstellt werden. Bei dieser Klasse soll es sich um einen Geldautomaten handeln, wo der Kunde Bargeld von seinem Giro- oder Kreditkartenkonto
abheben kann. Natürlich möchte ich das Thema hier nicht verkomplizieren,
weshalb wir uns bei dieser Klasse auf das Wesentliche beschränken.
Eine Klassendefinition sieht wie folgt aus:
class Automat {
// Daten und Elementfunktionen
};
Wie schon bei der Struktur dürfen Sie bei der Klassendefinition nicht das Semikolon am Ende vergessen. Für den Klassennamen gelten dieselben Regeln
wie bei den Bezeichnern (Abschnitt 2.3.1); allerdings sollte ein Klassenname
immer eindeutig sein. Gewöhnlich verwendet man einen großen Anfangsbuchstaben (dies ist zwar kein Standard, aber eine gängige Praxis).
Als Nächstes fügen Sie die Elemente (auch Members genannt) zur Klasse hinzu. Mitglieder einer Klasse sind die Daten (auch als Eigenschaften oder
204
6.2 Klassen
Attribute bezeichnet) und die Elementfunktion(en). Zunächst die Attribute
(Daten) der Klasse:
class Automat {
unsigned long geld;
string standort;
};
Hier haben Sie die Klasse Automat mit den Daten geld und standort versehen. Bis jetzt entspricht unsere Klasse nur einer einfachen Struktur mit Daten. Daher bekommt sie jetzt noch die Elementfunktionen:
class Automat {
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
// Elementfunktionen der Klasse "Automat"
unsigned long get_geld();
void set_geld(unsigned long g);
string& get_standort();
void set_standort(string& s);
void init(unsigned long g, string s);
};
Hiermit hätten Sie praktisch eine (fast) fertige Klassendefinition mit zwei Attributen (Daten) und fünf Elementfunktionen.
Klassendeklaration oder Klassendefinition?
Auch wenn die Elementfunktionen in der Klasse noch nicht definiert, sondern nur deklariert wurden, spricht man bei class Y{ ... }; von einer Klassendefinition und nicht von einer Klassendeklaration. Es ist eine Definition,
weil sie einen neuen Datentyp definiert.
6.2.2
Elementfunktionen definieren
Die Elementfunktionen, die Sie in der Klasse deklariert haben, müssen Sie
nun auch definieren, um eine Klassendefinition vollständig zu machen. Theoretisch könnten Sie eine Elementfunktion auch gleich in der Klasse selbst
definieren, aber in der Praxis ist dies doch eher unüblich. Ein Beispiel:
class Automat {
// Daten der Klasse "Automat"
unsigned long geld;
205
6
Klassen
string standort;
// Definition der Elementfunktion in der Klasse
unsigned long get_geld() {
// Anweisungen für Elementfunktion
}
...
};
In der Praxis wird eine Elementfunktion gewöhnlich der Übersichtlichkeit
halber außerhalb der Klassendefinition definiert. Allerdings genügt es dann
nicht mehr, bei der Definition nur den Funktionsnamen anzugeben. Hierfür
müssen Sie den Klassennamen, gefolgt vom Scope-Operator (Bereichsoperator) :: und dann dem eigentlichen Funktionsnamen verwenden. Die Syntax
hierzu:
Typ Klassenname::funktionsname( parameter ) {
// Anweisungen der Funktion
}
Hiermit teilen Sie dem Compiler mit, dass die Funktion funktionsname eine
Elementfunktion der Klasse Klassenname ist. Würden Sie den Klassennamen
hierbei nicht verwenden, so hätten Sie nur eine einfache globale Funktion
definiert.
Bezogen auf die Klasse Automat und die Elementfunktion set_geld() sieht
eine Definition außerhalb der Klasse wie folgt aus:
void Automat::set_geld(unsigned long g){
geld = g;
}
Hierbei erkennen Sie auch gleich, dass Sie auf die Daten einer Klasse innerhalb der Elementfunktion direkt zugreifen können – also ohne irgendwelche Scope-Operatoren. Dies funktioniert natürlich auch, wenn
Elementfunktionen einer Klasse andere Elementfunktionen derselben Klasse aufrufen. Auch dann können Sie ohne weiteren Bezug auf diese Klasse zugreifen. Somit kann man sagen, dass alle Daten und Elementfunktionen
innerhalb einer Klasse ohne besonderen Bezug miteinander harmonieren –
womit Elementfunktionen ohne Umwege auf die Daten und andere Elementfunktionen zugreifen können.
Hierzu schon einmal die komplette Implementierung des ersten Quellcodes
automat.cpp:
206
6.2 Klassen
00
01
02
03
// listings/006/automat1/automat.cpp
#include <string>
#include "automat.h"
using namespace std;
04
05
06
unsigned long Automat::get_geld() {
return geld;
}
07
08
09
void Automat::set_geld(unsigned long g){
geld = g;
}
10
11
12
string Automat::get_standort() {
return standort;
}
13
14
15
void Automat::set_standort(string s){
standort = s;
}
16
17
18
19
void Automat::init(unsigned long g=0, string s="") {
set_geld(g);
set_standort(s);
}
Auf die Einzelheiten zum Zugriff auf die Elementfunktionen gehe ich in Kürze noch gesondert ein.
6.2.3
Zugriffskontrolle mit »public« und »private«
Mit unserer bisherigen Klassendefinition Automat könnten Sie zwar jetzt ein
Objekt anlegen, aber Sie haben noch keinerlei Zugriff von außen auf die
Klasse. Wie bereits einmal kurz erwähnt, sind alle Daten und Elementfunktionen innerhalb einer Klasse – im Gegensatz zu einer Struktur – zunächst
immer private, wenn Sie nichts anders angeben. private-Daten und -Elementfunktionen können Sie somit nur innerhalb einer Klasse verwenden.
Natürlich können Sie private auch explizit als Schlüsselwort in der Klassendefinition benutzen.
Würden Sie jetzt ein Objekt erstellen und versuchen, eine Elementfunktion
zu verwenden, würde der Compiler die Übersetzung verweigern, mit der
207
6
Klassen
Meldung, dass diese Elementfunktion private ist. Standardmäßig ist also
ein Zugriff ohne weitere Angaben auf Klassen von außen verboten. Der
Grund solcher Rechte ist, dass der Zugriff auf die Daten einer Klasse nur
noch kontrolliert erfolgen soll. So gehören Dinge wie die falsche Übergabe
von Argumenten oder nicht initialisierte Variablen der Vergangenheit an
(oder werden zumindest bei richtiger Anwendung eingedämmt).
Datenkapselung nicht aufweichen
An dieser Stelle muss natürlich noch erwähnt werden, dass der Schutz der
Daten jederzeit umgangen werden kann. Der Schutz der Datenkapselung
dient hier vor versehentlichen Änderungen der Daten und bewahrt nicht
vor mutwilliger Aufweichung des Konzeptes der Datenkapselung. C++ liefert Ihnen nur die Mittel; für eine saubere Implementierung von abstrakten
Typen sind also nach wie vor Sie als Programmierer verantwortlich.
Natürlich ergibt eine solche komplette Sperrung einer Klasse von außen wenig Sinn. Um jetzt auf einzelne Elementfunktionen einer Klasse von außen
zuzugreifen, müssen Sie das Schlüsselwort public (das Gegenstück zu
private) verwenden. Aller Elemente einer Struktur, die mit public gekennzeichnet sind, können über das Objekt außerhalb der Klasse verwendet werden. Eine Syntax dazu:
class Klassenname {
// Auf Elemente kann nur innerhalb einer Klasse
// zugegriffen werden
private:
typ daten1;
typ daten2;
...
// Zugriff von außen auf die Elemente möglich
public:
typ funktionsname1( parameter );
typ funktionsname2( parameter );
...
};
Alle Elemente, die hinter private (Doppelpunkt muss angegeben werden)
stehen, sind nur innerhalb der Klasse sichtbar. Von außen kann darauf nicht
zugegriffen werden. Diese Rechteerteilung gilt bis zum Ende der Klasse –
208
6.2 Klassen
oder bis zum Schlüsselwort public (auch hier ist der Doppelpunkt dahinter
wichtig). Ab dem Schlüsselwort public ist alles dahinter Geschriebene öffentlich außerhalb der Klasse zugänglich. Es ist im Grunde egal, ob Sie zuerst
die Klassenelemente public oder private oder gar durcheinander gemischt
verwenden. Es ist auch egal, wie oft Sie public oder privat in einer Klasse
benutzen und ob Sie dabei Daten und/oder Elementfunktionen öffentlich
oder privat zugänglich machen.
In der Praxis werden aber gewöhnlich als Entwurfsmuster die Daten einer
Klasse als private vorbehalten und die Elementfunktionen als public (öffentlich) deklariert. Damit lässt sich der Zugriff auf die Daten über die
Schnittstellen (Elementfunktionen) kontrollieren.
Mit diesem Wissen können Sie die Klassendefinition abschließen. Unsere
Headerdatei automat.h sieht somit wie folgt aus:
00
01
02
03
04
// listings/006/automat1/automat.h
#ifndef _AUTOMAT_H_
#define _AUTOMAT_H_
#include <string>
using namespace std;
05
06
07
08
09
10
11
12
13
14
15
16
17
18
class Automat {
private:
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
public:
// Elementfunktionen der Klasse "Automat"
unsigned long get_geld();
void set_geld(unsigned long g);
string get_standort();
void set_standort(string s);
void init(unsigned long g, string s);
};
#endif
Bereiche hinter der geschweiften Klammer, die mit keinem der Schlüsselwörter versehen wurden, sind natürlich nach wie vor private. In der Headerdatei automat.h könnten Sie somit auf Zeile (06) verzichten. Der Bereich
der Klassedefinition ab Zeile (10) ist öffentlich von außen zugänglich.
209
6
Klassen
6.2.4
Zugriff auf die Daten innerhalb einer Klasse
Bevor Sie jetzt ein Objekt der Klasse Automat in einer main-Funktion erzeugen und benutzen, möchte ich noch einen Abschnitt dem wichtigen Thema
des Zugriffs auf die Daten einer Klasse widmen.
Wie Sie bereits erfahren haben, können Sie außerhalb der Klasse mit den
Objekten einer Klasse nur auf die public-Elemente zugreifen. Die Daten
sind in der Regel private und nicht von außen zu erreichen. Es ist daher
gängige Praxis, die Daten einer Klasse nur über Elementfunktionen zu ermitteln und zu setzen.
Bezogen auf unsere Klasse Automat können Sie die Daten geld oder
standort nur über Elementfunktionen (in dem Fall auch als Zugriffsfunktionen bezeichnet) setzen oder abfragen. Im Beispiel der Daten geld:
00 // listings/006/automat1/automat.cpp
...
01 unsigned long Automat::get_geld() {
02
return geld;
03 }
04
05
06
void Automat::set_geld(unsigned long g){
geld = g;
}
Da die Daten bei der Klassendefinition im selben Gültigkeitsbereich definiert wurden, brauchen Sie hier natürlich keinen Punkt-, Pfeil- oder ScopeOperator zu verwenden.
In der Praxis sind solche Elementfunktionen meistens nicht auf einfache Zuweisungen oder Wertrückgaben beschränkt. In der Regel sind hier noch
Überprüfungen implementiert, ob beispielsweise ein gültiger Wert zugewiesen wurde.
6.2.5
Objekte erzeugen und benutzen
Jetzt haben Sie zwar eine Klasse Automat geschaffen, aber eine solche Klasse
zu haben ist zunächst nur so viel wie die Vorstellung eines Bankautomaten
im Kopf. Und allein Gedanken an einen Bankautomaten machen diesen
noch nicht real.
Sie müssen also ein Objekt (auch als Instanz einer Klasse bezeichnet) definieren. Klassen und Objekte? – Bevor Sie etwas durcheinanderbringen: Eine
210
6.2 Klassen
Klasse ist zunächst nichts als der bloße Gedanke an einen Gegenstand (eine
Beschreibung des Gegenstandes, wenn Sie wollen). Wenn wir von einem
Objekt selbst reden, dann handelt es sich um einen realen Gegenstand, der
vor Ihren Augen ist (natürlich nicht wirklich, aber von der IT-Welt auf unser
Gehirn projiziert ist das einfacher verständlich). Die Definition eines solchen Objekts verläuft so, wie Sie dies schon von den Basistypen und den anderen fortgeschrittenen Typen wie den Strukturen her kennen:
Klassenname Objekt;
Auf unsere Klasse Automat bezogen, sieht dies wie folgt aus:
Automat device01;
Natürlich können Sie auch mehrere Objekte deklarieren:
Automat device01, device02;
Mit einer solchen Deklaration reservieren Sie auch gleich Speicher für die
Daten des Objekts device01 und device02. Im Beispiel sind dies die Attribute geld und standort. Natürlich gilt dies für jedes Objekt. Es spricht auch
nichts dagegen, ein Array von Objekten der Klasse Automat anzulegen:
#include <vector>
...
// 10 Objekte der Klasse »Automat«
vector <Automat> device_germany(10);
// Ein leeres Array der Klasse »Automat«
vector <Automat> device_austria;
// ... auch die C-Arrays lassen sich hiermit verwenden
Automat Cdevice[10];
Jedes Objekt hat somit seinen eigenen Speicher für die Daten. Allerdings arbeiten alle Objekte mit denselben Elementfunktionen. Daher wird der Code
für die Elementfunktionen nur einmalig im Speicher abgelegt.
Zugriff mit dem Punktoperator
Wenn Sie ein Objekt definiert haben, können Sie den Punktoperator verwenden, um auf die public-Elemente direkt zu zugreifen (eben wie schon
bei den gewöhnlichen Strukturen mit struct). Im Grunde können Sie auf
die Klassenelemente immer nur mit einem Objekt der Klasse direkt zugreifen. Dieser direkte Zugriff auf ein Element erfolgt durch die Bezeichnung
des Objekts, gefolgt vom Punktoperator und vom Bezeichner des Klassenelements. Die Syntax:
211
6
Klassen
// Objekt erzeugen
Klassenname Objekt;
// Zugriff auf public-Daten der Klasse (nicht zu empfehlen)
Objekt.Daten = 1234;
// Zugriff auf public-Elementfunktionen der Klasse
Objekt.Funktion( parameter );
In unserem Beispiel Automat können Sie daher folgendermaßen einen Bankautomat erzeugen und mit entsprechenden Daten initialisieren:
// Objekt der Klasse "Automat" erzeugen
Automat device;
// Objekt mit Werten initialisieren
device.init(10000, "Bonn, Hauptstrasse 123");
Im Beispiel wurde die Elementfunktion init() zum Initialisieren der Daten
der Klasse Automat verwendet.
00 // listings/006/automat.cpp
...
16 void Automat::init(unsigned long g=0, string s="") {
17
set_geld(g);
18
set_standort(s);
19 }
Die Elementfunktion init() der Klasse Automat wiederum ruft letztendlich
auch wieder nur zwei Elementfunktionen auf (Zeile (17) und (18)), in denen
die Parameter an die entsprechenden Daten übergeben werden.
Würden Sie jetzt versuchen, die Werte direkt an die Daten der Klasse
Automat zu übergeben (oder darauf zuzugreifen), würde der Compiler dies
mit einer Fehlermeldung quittieren. Folgender Codeausschnitt ist (und sollte) nicht möglich sein:
Automat device;
// !!! Fehler, geld ist ein privat-Element !!!
device.geld = 10000;
// !!! Fehler, standort ist ein privat-Element !!!
device.standort = "Bonn, Hauptstrasse 123";
Ein Zugriff auf ein mit private gekennzeichnetes Element ist weder lesend
noch schreibend möglich. Die einzige theoretische Möglichkeit wäre es daher, das mit private gekennzeichnete Element mit public zu kennzeichnen.
Allerdings würden Sie hiermit das OOP-Prinzip aufweichen. Wenn Sie daher irgendwann einmal in die Verlegenheit kommen, Daten notgedrungen
212
6.2 Klassen
als public zu kennzeichnen, haben Sie einen Designfehler in der Planung
gemacht und sollten Ihren Code nochmals überdenken und überarbeiten.
// listings/006/automat1/automat.h
...
class Automat {
// So etwas ist möglich, sollte aber unbedingt vermieden
// werden. Damit wird das Prinzip des Klassendesigns
// aufgeweicht !!!
public:
// !!! Daten der Klasse "Automat" sind jetzt public !!!
unsigned long geld;
string standort;
...
};
Indirekter Zugriff mit dem Pfeiloperator
Wie auch schon bei den Zeigern auf Strukturen können Sie auch mit Zeigern
auf Objekte von Klassen arbeiten – was bei dynamischen Datenstrukturen
durchaus gängig ist. Der indirekte Zugriff wird mit dem Objektzeiger realisiert, gefolgt vom Pfeiloperator und vom Bezeichner eines Elements. Natürlich müssen Sie auch vor der Verwendung des Objektzeigers zunächst auf
eine gültige Adresse verweisen, bevor Sie ihn verwenden. Der Vorteil von
Objektzeigern ist ebenfalls, dass Sie ihnen jederzeit wieder eine andere
Adresse zuweisen können. In der Praxis wird dies gerne zum dynamischen
Erzeugen von Objekten zur Laufzeit mit dem new-Operator verwendet. Hier
einige Syntax-Beispiele:
// Objekt erzeugen
Klassenname Objekt;
// Objektzeiger
Klassenname *ObjektPtr1, *ObjektPtr2;
// Gültige Adresse an ObjektPtr zuweisen
ObjektPtr1 = &Objekt;
// Speicher zur Laufzeit anfordern
ObjektPtr2 = new Klassenname;
// Indirekter Zugriff auf die Elementfunktionen
ObjektPtr1->Elementfunktion_X( parameter );
ObjektPtr2->Elementfunktion_Y( parameter );
213
6
Klassen
Bezogen auf den Bankautomaten könnten Sie folgendermaßen dynamisch
Speicher zur Laufzeit für ein neues Objekt anfordern und indirekt darauf mit
dem Pfeiloperator zugreifen:
// Speicher für ein neues Objekt anfordern
Automat *device = new Automat;
...
// Indirekt mit Werten initialisieren
device->init(10000, "Bonn, Hauptstrasse 123");
// Indirekte Ausgabe des Standortes
cout << device->get_standort() << endl;
Hier nun, wie versprochen, die main-Funktion zur Headerdatei automat.h
und zur Quelldatei automat.cpp, um die einfache Verwendung von Objekten mit der Klasse Automat in der Praxis zu demonstrieren:
00
01
02
03
04
05
// listings/006/automat1/main.cpp
#include <iostream>
#include <vector>
#include <string>
#include "automat.h"
using namespace std;
06
07
08
09
10
11
int main(void) {
Automat *device_ptr = new Automat;
vector <Automat> Vdevice(2);
Automat device;
// Initialisieren
device_ptr->init(10000, "Bonn, Hauptstrasse 123");
12
13
14
Vdevice[0].init(20000, "Augsburg, Fuggerweg 345");
Vdevice[1].set_geld(25000);
Vdevice[1].set_standort("Berlin, Berliner Allee 567");
15
device.init(123456, "Frankfurt, Goethestrasse 123");
16
17
18
// Ausgabe
cout << device_ptr->get_geld() << endl;
cout << device_ptr->get_standort() << endl;
19
20
21
for(size_t i=0; i<Vdevice.size(); i++) {
cout << Vdevice[i].get_geld() << endl;
cout << Vdevice[i].get_standort() << endl;
214
6.3 Konstruktoren
22
23
24
25
26
}
cout << device.get_geld() << endl;
cout << device.get_standort() << endl;
return 0;
}
Das Beispiel zeigt in der Praxis in mehreren Variationen, die in den Abschnitten zuvor beschrieben wurden, wie Sie ein Objekt der Klasse Automat
verwenden können.
Code organisieren und übersetzen
In Abschnitt 5.5 bin ich kurz auf das Thema Modularisierung eingegangen.
Wenn Sie nicht wissen sollten, wie Sie die Quelldateien main.cpp, automat.cpp und automat.h organisieren und übersetzen, finden Sie auf der
Webseite http://www.galileo-press.de/bonus-seite Anleitungen zu diversen Compilern, die erläutern, wie Sie Beispiele, die in mehrere Quell- und
Headerdateien aufgeteilt sind, übersetzen.
6.3
Konstruktoren
Bisher haben Sie noch nichts von der »sichereren« Initialisierung von Variablen gesehen. Folgendes ist leider immer noch möglich:
Automat device01;
// !!! Zugriff auf einen undefinierten Wert !!!
cout << device01.get_geld() << endl;
Obwohl die Daten nach der Objekterzeugung noch nicht initialisiert wurden, kann immer noch auf einen undefinierten Wert zugegriffen werden.
Folgender Versuch ein Objekt zu erzeugen, würde der Compiler komplett
mit einer Fehlermeldung quittieren:
// !!! Der Compiler kann hiermit noch nichts anfangen !!!
Automat device02("Freiburg, Bahnhofweg 2");
Ein Objekt der Klasse Automat besitzt in diesem Beispiel so lange keinen gültigen Wert, bis die Elementfunktion init() aufgerufen wird. Zugegeben,
die Elementfunktion init() trifft den Sachverhalt schon recht gut – aber
hierfür bieten Klassen einen speziellen Mechanismus an, der dafür verantwortlich ist, dass ein Objekt bei der Definition automatisch mit einer Initialisierungsfunktion mit Werten für die Daten belegt wird. Damit ist
215
6
Klassen
garantiert, dass Sie immer mit gültigen Werten arbeiten. Die Rede ist von
den Konstruktoren (im Englischen auch kurz ctor genannt), die im Grunde
den Methoden einer Klasse recht ähnlich sind.
Doch in zwei Dingen unterscheiden sich Konstruktoren von Klassenmethoden:
왘
Der Name des Konstruktors ist derselbe wie der Name der Klasse.
왘
Der Konstruktor besitzt keinen Rückgabewert.
6.3.1
Konstruktoren deklarieren
Damit der Konstruktor auch zur Erzeugung von Objekten von außen zur
Verfügung steht, erfolgt die Deklaration gewöhnlich im public-Bereich der
Klasse. Hierzu müssen Sie zunächst die Konstruktoren in der Klassendefinition Automat deklarieren. Die einzelnen Konstruktoren (im Beispiel vier) unterscheiden sich durch ihre Parameter:
// listings/006/automat2/automat.h
...
class Automat {
private:
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
public:
// Deklaration der Konstruktoren
Automat(unsigned long, string);
Automat(unsigned long);
Automat(string);
Automat();
// Elementfunktionen der Klasse "Automat"
unsigned long get_geld();
...
};
Ja, Sie haben es richtig erkannt, die Konstruktoren arbeiten nach demselben
Prinzip der Funktionsüberladung, wie Sie dies in Abschnitt 3.2.7 näher
kennengelernt haben. Daher ist es immer wichtig, wenn Sie mehrere Konstruktoren verwenden wollen, dass sich diese durch ihre Parameter (genauer: Signatur) unterscheiden. Dadurch lassen sich die Daten der Objekte auf
unterschiedliche Arten initialisieren.
216
6.3 Konstruktoren
6.3.2
Konstruktoren definieren
Nachdem Sie die Deklaration der Konstruktoren in die Headerdatei geschrieben haben, müssen Sie diese Konstruktoren, wie eben die Klassenmethoden
auch, definieren. Die Definition ähnelt ebenfalls den Klassenmethoden, nur
dass hierbei zweimal der Klassenname, gefolgt von den Parametern, verwendet
wird:
Klassenname::Klassenname( Parameter ) {
// Daten von Klassenname initialisieren
}
Bezogen auf unsere Definition der Klasse Automat sieht die Definition der
Klasse im Quellcode automat.cpp wie folgt aus:
// listings/006/automat2/automat.cpp
#include <string>
#include "automat.h"
using namespace std;
// Definition der Konstruktoren
Automat::Automat(unsigned long g, string s) {
set_geld(g);
set_standort(s);
}
Automat::Automat(unsigned long g) {
set_geld(g);
set_standort("");
}
Automat::Automat(string s) {
set_geld(0);
set_standort(s);
}
Automat::Automat() {
set_geld(0);
set_standort("");
}
// Definition der Elementfunktionen
...
217
6
Klassen
Je nachdem, welches Objekt jetzt instantiiert wird, wird der entsprechende
Konstruktor aufgerufen und/oder das Objekt mit Daten oder Nullwerten initialisiert. Im Beispiel werden diese Werte wiederum mit den Zugriffselementfunktionen set_geld() und set_standort() übergeben. Sie hätten
hier aber genauso gut eine direkte Zuweisung an die Daten geld und
standort innerhalb des Konstruktor machen können.
Dank dieser Konstruktoren können Sie folgendermaßen Objekte erzeugen
und auch gleich mit Zugriffsfunktionen verwenden, ohne befürchten zu
müssen, mit nicht initialisierten Werten zu arbeiten:
// listings/006/automat2/automat.cpp
...
// geld=0; standort=""
Automat device01;
// geld=0; standort="Freiburg, Bahnhofweg 2"
Automat device02("Freiburg, Bahnhofweg 2");
// geld=20000; standort="Fulda, Hauptstrasse 1"
Automat device03(20000, "Fulda, Hauptstrasse 1");
// geld=10000; standort=""
Automat device04(10000);
Gerne ist man als Programmierer versucht, seinen Klassen möglichst viel
Flexibilität zu verleihen und daher möglichst viele Konstruktoren zu implementieren, um alle Möglichkeiten abzudecken. An manchen Stellen sollten
Sie aber überlegen, ob Sie nicht lieber statt einer Serie von Konstruktoren
den Einsatz von Standardparametern (Default-Argumenten) in Erwägung
ziehen sollten.
Neben den Initialisierungen von Variablen werden Konstruktoren auch für
andere Dinge verwendet. Gängige und häufig verwendete Beispiele sind das
Reservieren von Speicher, das Öffnen von Dateien und das Vorbereiten von
Daten.
Folgendes lässt sich übrigens ebenfalls realisieren, wenn ein entsprechender
Konstruktor vorhanden ist:
// geld=25000; standort=""
Automat device05 = 25000;
Hiermit wird der Konstruktor Automat(unsigned long) aufgerufen.
218
6.3 Konstruktoren
Die Konstruktor-Schreibweise der Initialisierung zwischen den Klammerungen ist übrigens nicht nur auf Klassen beschränkt, sondern lässt sich auch
auf Basisdatentypen anwenden, beispielsweise: int iwert(1234) statt int
iwert=1234.
6.3.3
Standardkonstruktor (Default-Konstruktor)
Konstruktoren ohne Parameter heißen Standardkonstruktoren (DefaultKonstruktoren). Sie werden immer dann aufgerufen, wenn bei der Definition eines Objekts keine weiteren Initialisierungswerte angegeben wurden,
beispielsweise:
// Verwendet Standardkonstruktor
Klassenname Objekt;
In der Praxis schreibt man diesen Standardkonstruktor gewöhnlich so, dass
alle Eigenschaften einer Klasse mit Standardwerten versehen werden. Im
Beispiel zuvor sah die Deklaration und Definition des Standardkonstruktors
wie folgt aus:
// automat.h
...
// Deklaration des Standardkonstruktors
Automat();
// autmat.cpp
...
// Definition des Standardkonstruktor
Automat::Automat() {
set_geld(0);
set_standort("");
}
Dieser Konstruktor wurde immer dann verwendet, wenn ein Objekt folgendermaßen erzeugt wurde:
// Beide Male wird der Standardkonstruktor aufgerufen
Automat device01;
Automat* device02 = new Automat;
Sofern Sie keinen Standardkonstruktor verwenden (was man in der Praxis
allerdings immer machen sollte), stellt Ihnen der Compiler automatisch einen solchen zur Verfügung. Dieser Standardkonstruktor (ebenfalls von außen erreichbar – als public) initialisiert allerdings nicht die Daten der Klasse
mit gültigen Werten.
219
6
Klassen
Beachten Sie auch, dass, sobald Sie mindestens einen Konstruktor deklariert
und definiert haben, der Compiler Ihnen keinen Standardkonstruktor mehr
zur Verfügung stellt und Sie sich selbst darum kümmern müssen. Würden
Sie den Standardkonstruktor im Beispiel Automat entfernen, würde folgende Objektdefinition zu einem Compilerfehler führen, weil der Compiler keinen passenden Konstruktor dafür finden kann:
Automat device03;
Dies hätte allerdings wieder den Vorteil, dass Sie gezwungen sind bei der Erzeugung eines neuen Objektes eine Initialisierungsliste mit anzugeben.
6.3.4
Kopierkonstruktor
Einen Konstruktor, den der Compiler ebenfalls von Haus aus zur Verfügung
stellt, ist der Kopierkonstruktor (Copy-Konstruktor). Wenn zwei Objekte
von derselben Klasse Automat sind, so würde eine Zuweisung von a1=a2
oder a1(a2) ein elementweises Kopieren der Daten von a2 nach a1 bedeuten. Zum Beispiel:
Automat device01(12000, "Bonn, Rheinwerkallee");
// Kopie von device01 nach device02
Automat device02 = device01;
// Kopie von device 02 nach device03
Automat device03(device02);
Falls das Kopieren nicht zum gewünschten Verhalten führt – was beispielsweise sein kann, wenn einer der Daten ein Zeiger ist, der dynamisch zur
Laufzeit Speicherplatz anfordert –, können Sie einen eigenen Kopierkonstruktor definieren. Ein solcher Kopierkonstruktor hat immer folgende Syntax:
Klassenname ( const Klassenname & );
Die Deklaration des Kopierkonstruktors in der Headerdatei automat.h als
public-Elementfunktion der Klasse Automat sähe wie folgt aus:
// automat.h
...
public:
...
// Kopierkonstruktor
Automat( const Automat & );
...
220
6.4 Destruktoren
Wollen Sie erfahren, wann dieser Kopierkonstruktor verwendet wird, können Sie in der Quelldatei automat.cpp folgende Codezeile einbauen:
// automat.cpp
...
Automat::Automat( const Automat & ) {
cout << "Kopierkonstruktor aufgerufen\n";
}
Da unsere Klasse Automat allerdings bisher noch keine dynamischen Klassenelemente besitzt, gehe ich auf das Thema erst im entsprechenden Abschnitt 7.6.1, »Dynamische Klassenelemente«, ein.
6.4
Destruktoren
Das Gegenstück zum Konstruktor, der für das Initialisieren von Objekten zuständig ist, wird Destruktor (im englischen kurz als dtor bezeichnet) genannt. Der Destruktor wird genauso wie der Konstruktor automatisch
aufgerufen – allerdings mit dem Unterschied, dass der Destruktor nicht bei
der Definition aufgerufen wird, sondern beim »Zerstören« des Objektes.
Der Destruktor wird gewöhnlich für Aufräumarbeiten wie beispielsweise
das Freigeben von dynamisch reserviertem Speicherplatz, Aufheben von
Sperren, Dateifreigaben und anderen Rückgaben von bestimmten Ressourcen verwendet. Man spricht auch vom Abbauen eines Objektes.
6.4.1
Destruktor deklarieren
Der Destruktor besteht, wie schon der Konstruktor, auch nur aus dem Klassenamen – allerdings mit einem vorangestellten ~ (Komplement-Zeichen),
beispielsweise:
// Deklaration eines Destruktors
~Klassenname( );
Also gilt auch für den Destruktor, dass er keinen Rückgabewert enthält, und
zusätzlich (im Gegensatz zum Konstruktor) besitzt er niemals einen Parameter. Außerdem muss der Destruktor immer im public-Bereich einer Klasse
stehen.
In der Klasse Automat deklarieren wir den Destruktor im public-Bereich.
Hierzu die Headerdatei automat.h, ein wenig gekürzt:
221
6
Klassen
// listings/006/automat3/automat.h
...
class Automat {
private:
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
public:
// Deklaration der Konstruktoren
Automat(unsigned long, string);
Automat(unsigned long);
Automat(string s);
Automat();
// Deklaration des Konstruktors
~Automat();
// Elementfunktionen der Klasse "Automat"
unsigned long get_geld();
...
};
6.4.2
Destruktor definieren
Sofern Sie keinen Destruktor deklarieren und definieren, erzeugt auch hierbei der Compiler eine Standardversion davon (im public-Bereich). Dieser
Destruktor zerstört die Eigenschaften eines Objektes in umgekehrter Reihenfolge (da Stack) der Erzeugung. Dies gilt auch, wenn das Attribut selbst
ein Objekt ist – nur wird dann dessen Destruktor (implizit oder falls vorhanden explizit) verwendet.
Wenn Sie hingegen einen Destruktor selbst explizit definieren, gehen Sie
ähnlich wie schon beim Konstruktor vor. Die Syntax lautet:
// Definition eines Destruktors
Klassenname::~Klassenname( ) {
// Anweisungen
}
Bezogen auf unsere Klasse Automat kann dieser Destruktor daher wie folgt
aussehen (gekürzte Fassung):
// listings/006/automat3/automat.cpp
#include <string>
#include <iostream>
#include "automat.h"
222
6.4 Destruktoren
using namespace std;
...
// Definition des Destruktors
Automat::~Automat() {
cout << get_standort() << " entfernt" << endl;
}
Natürlich dient der Destruktor nicht dazu, um – wie im Beispiel – einen
sinnlosen Text auf dem Bildschirm auszugeben, sondern er kümmert sich
um diverse Aufräumarbeiten. Die Textausgabe in diesem Beispiel diente nur
zur Demonstration, damit Sie im anschließenden Beispiel besser verstehen,
wann der Destruktor aufgerufen wird.
Hierzu daher noch eine main-Funktion, die Ihnen den Destruktor in der Praxis zeigt:
00
01
02
03
04
05
// listings/006/automat3/main.cpp
#include <iostream>
#include <vector>
#include <string>
#include "automat.h"
using namespace std;
06
Automat device01("Global: Bonn");
07
08
09
10
11
12
13
14
15
16
int main(void) {
Automat* device02 = new Automat("main(new): Mering");
static Automat device03("main(static): Erfurt");
delete device02;
Automat device04("main(): Berlin");
{
Automat device05("main(lokal): Kiel");
}
return 0;
}
Das Programm bei der Ausführung:
main(new): Mering entfernt
main(lokal): Kiel entfernt
main(): Berlin entfernt
main(static): Erfurt entfernt
Global: Bonn entfernt
223
6
Klassen
Anhand der Ausgabe des Beispiels erkennen Sie, dass der Destruktor immer
explizit aufgerufen wird, wenn sich der Gültigkeitsbereich eines Objekts beendet. Somit gilt auch für den Gültigkeitsbereich von Objekten dasselbe wie
schon bei den Basisdatentypen:
왘
Ist ein Objekt lokal deklariert und gehört es nicht zur Speicherklasse
static, wird es am Ende des entsprechenden Anweisungsblocks zerstört (im Beispiel die Objekte der Zeilen (11) und (13)).
왘
Ist ein Objekt global oder static deklariert, wird es am Ende des Programms zerstört (im Beispiel die Objekte der Zeilen (06) und (09)).
왘
Dynamische Objekte werden an Ort und Stelle gelöscht, wenn der Speicher mit delete explizit freigegeben wird (wie im Beispiel in den Zeilen
(08) und (10) geschehen).
6.5
Elementfunktionen
In diesem Kapitel soll auf typische Themen zu den Elementfunktionen (oft
auch als Methoden bezeichnet) eingegangen werden – was natürlich voraussetzt, dass Sie die Abschnitte zu den Klassen gründlich durchgelesen und
auch verstanden haben.
6.5.1
Inline-Elementfunktionen
Wie Sie bereits bei den Funktionen erfahren haben, stellt der Aufruf einer
solchen nicht einen unerheblichen Aufwand dar (Stichwort: Stack[-Frame]).
Dasselbe gilt hierbei auch für die Elementfunktionen – die ja im Grunde
auch nur Funktionen (für Klassen) sind. Auch bei den Elementfunktionen
steht Ihnen, wie schon bei den Funktionen, die Möglichkeit zur Verfügung,
sie als inline zu definieren (siehe Abschnitt 3.2.9). Damit genießen Sie wieder die Vorteile der Klassen, ohne dass diese zu einem schlechten Laufzeitverhalten beitragen.
Kluger Compiler
An dieser Stelle muss natürlich hinzugefügt werden, dass moderne Compiler, auch ohne explizite oder implizite Verwendung von inline, die Elementfunktionen automatisch als inline deklarieren können. Natürlich entscheidet auch hier, wie Sie bereits in Abschnitt 3.2.9, »Inline-Funktionen«,
erfahren haben, dass letztendlich der Compiler selbst über eine tatsächliche
Inline-Verwendung entscheidet.
224
6.5 Elementfunktionen
Zur Verwendung von inline stehen Ihnen bei den Klassen zwei Möglichkeiten zur Verfügung: explizit und implizit.
Implizit »inline«
Definieren Sie kleinere Elementfunktionen gleich innerhalb der Klassedefinitionen definiert, werden diese implizit zu inline-Elementfunktionen –
auch, wenn Sie das Schlüsselwort inline nicht mit angeben. Natürlich können Sie diese Methoden trotzdem, zur besseren Lesbarkeit, extra mit inline
definieren. Nehmen wir als Beispiel wieder die Klasse Automat:
00
01
02
03
04
// listings/006/automat4/automat.h
#ifndef _AUTOMAT_H_
#define _AUTOMAT_H_
#include <string>
using namespace std;
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Automat {
private:
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
public:
// Deklaration der Konstruktoren
Automat(unsigned long, string);
Automat(unsigned long);
Automat(string s);
Automat();
// Deklaration des Konstruktors
~Automat();
// Elementfunktionen implizit als inline definieren
unsigned long get_geld() {
return geld;
}
void set_geld(unsigned long g){
geld = g;
}
string get_standort() {
return standort;
}
void set_standort(string s){
standort = s;
}
225
6
Klassen
31
32
33
34
35
36
void init(unsigned long g=0, string s="") {
set_geld(g);
set_standort(s);
}
};
#endif
Da die Elementfunktionen in der Klassendefinition definiert wurden, gelten
get_geld(), set_geld(), get_standort(), set_standort() und init() aus
den Zeilen (19) bis (34) implizit als inline.
Natürlich bedeutet das, dass Sie die Definitionen der Quelldatei automat.cpp entfernen müssen.
Explizit inline
Elementfunktionen können Sie natürlich auch explizit als inline definieren,
was durchaus die gängigere Methode darstellt. Hierbei gehen Sie genauso
wie schon bei den inline-Funktionen vor und setzen bei der Definition vor
die Klassenmethode das Schlüsselwort inline.
Auf unser Beispiel der Klasse Automat bezogen, müssten Sie hierzu in der
Datei automat.cpp vor die entsprechenden Elementfunktionen das Schlüsselwort inline schreiben (natürlich setzt dies voraus, dass Sie in der Headerdatei automat.h in der Klassendefinition noch keine Elementfunktionen,
wie eben im Abschnitt »Inline implizit« gesehen, definiert haben). Hier das
Listing dazu (gekürzte Fassung):
// listings/006/automat5/automat.cpp
#include <string>
#include <iostream>
#include "automat.h"
using namespace std;
...
...
inline unsigned long Automat::get_geld() {
return geld;
}
inline void Automat::set_geld(unsigned long g){
geld = g;
}
inline string Automat::get_standort() {
226
6.5 Elementfunktionen
return standort;
}
inline void Automat::set_standort(string s){
standort = s;
}
inline void Automat::init(unsigned long g=0, string s="") {
set_geld(g);
set_standort(s);
}
Konstruktoren und Destruktoren
Natürlich können Sie die Konstruktoren und Destruktoren, wie auch die
Elementfunktionen, als inline definieren. Und das sowohl explizit als auch
implizit – nur müssen Sie eben auf die »Merkmale« von Konstruktoren (siehe Abschnitt 6.3) und Destruktoren (siehe Abschnitt 6.4) achten.
6.5.2
Konstante Elementfunktionen (read-only)
Elementfunktionen, die nur lesend auf die Daten zugreifen, werden in der
Praxis speziell gekennzeichnet. Damit ist es auch möglich, sie mit const-Objekten aufzurufen. Eine Elementfunktion deklarieren und definieren Sie als
read-only, indem Sie an den Funktionskopf das Schlüsselwort const anhängen:
// Deklaration in der Klasse
typ methode( parameter ) const;
...
// Definition der Methode
typ methode( parameter ) const {
// Anweisungen
}
In unserer Klasse Automat sind beispielsweise die Elementfunktionen get_
geeignet, eine Methode als read-only zu deklarieren und definieren. Hierzu
müssen Sie im Beispiel nur bei den entsprechenden Stellen das Schlüsselwort const anhängen. In der Headerdatei automat.h wären dies folgende
zwei Methoden (gekürzte Fassung):
227
6
Klassen
// listings/006/automat6/automat.h
...
class Automat {
private:
// Daten der Klasse "Automat"
unsigned long geld;
string standort;
public:
...
// Elementfunktionen der Klasse "Automat"
unsigned long get_geld() const;
void set_geld(unsigned long g);
string get_standort() const;
void set_standort(string s);
void init(unsigned long g, string s);
};
...
Neben der Deklaration in der Klassendefinition müssen Sie selbstverständlich
auch die Definitionen mit dem Schlüsselwort const signieren. Hierzu passen
Sie im Quellcode automat.cpp ebenfalls nur die entsprechenden zwei Elementfunktionen Automat::get_geld() und Automat::get_standort() an (ebenfalls gekürzt):
// listings/006/automat6/automat.cpp
...
...
unsigned long Automat::get_geld() const {
return geld;
}
...
string Automat::get_standort() const {
return standort;
}
...
Der Vorteil solcher konstanten Elementfunktionen besteht darin, dass aus
diesen Elementfunktionen keine anderen Funktionen aufgerufen werden
können, die die Daten eines Objekts womöglich versehentlich überschreiben. Beispielsweise würde der Compiler folgenden Codeabschnitt bemängeln und das Programm nicht übersetzen:
unsigned long Automat::get_geld() const {
// Nicht erlaubt, da als read-only mit const signiert
228
6.5 Elementfunktionen
set_geld(0);
return geld;
// !!! Fehler !!!
}
6.5.3
this-Zeiger
Sicherlich haben Sie sich schon gefragt, wie es möglich sein kann, mit den
Elementfunktionen auf die Daten eines bestimmten Objekts zuzugreifen,
obwohl niemals eine explizite Angabe zum entsprechenden Objekt gemacht
wurde. Als Beispiel nehmen wir wieder die Klasse Automat. Nehmen wir an,
Sie wollen folgendes Objekt ausgeben:
Automat device(123456, "Mering, Bahnhofweg 1");
...
cout << device.get_standort() << endl;
Hierbei wird ja die Elementfunktion get_standort() aufgerufen:
string Automat::get_standort() const {
return standort;
}
Bei dieser Elementfunktion ist weit und breit keine Angabe in Sicht, mit
welchem Objekt diese Methode eigentlich arbeitet. Die Antwort lautet, dass
beim Aufruf als nicht sichtbares Argument die Adresse des aktuellen Objekts mit übergeben wird. Diese Adresse steht in der Elementfunktion mit
dem konstanten Zeiger this zur Verfügung. Die Syntax der Deklaration dieses this-Zeigers sieht intern folgendermaßen aus:
Klassenname* const this = &Objekt;
Auf das Beispiel Automat device bezogen, stellt sich dies folgendermaßen
dar:
Automat* const this = &device;
Somit ist also das Objekt device das Objekt, mit dem die Elementfunktion
aufgerufen wird.
Verwenden Sie this innerhalb von Klassenmethoden, erfolgt der Zugriff auf
die einzelnen Elemente einer Klasse mit folgendem Ausdruck:
this->Element
In der Tat ist das genau das, was der Compiler implizit daraus machen würde, wenn Sie nur den folgenden Ausdruck verwenden würden:
Element
229
6
Klassen
Somit entspricht – bezogen auf die Klasse Automat und die Elementfunktion
get_standort() – die Definition
string Automat::get_standort() const {
return standort;
}
der folgenden Definition der Elementfunktion:
string Automat::get_standort() const {
return this->standort;
}
In der Praxis lässt sich ein expliziter this-Zeiger verwenden, um bei Bedarf
die Bezeichner von lokalen Variablen einer Elementfunktion von den Daten
(Klassenvariablen) mit gleichem Namen zu unterscheiden (wenn ein gleicher Name verwendet wurde). Allerdings wird in der Praxis der this-Zeiger
eher als Ganzes benutzt, wenn zum Beispiel aus einer Elementfunktion ein
Objekt als Kopie oder Referenz zurückgegeben werden soll (return *this).
Darauf gehe ich in Abschnitt 7.2.6, »*this-Zeiger«, noch ein.
6.6
Aufgaben
Das Grundlagenkapitel zur OOP haben Sie hiermit geschafft. Mit den Klassen haben Sie die ersten wichtigen Eckpfeiler näher kennengelernt. Natürlich wird das Thema noch weitaus komplexer, aber seien Sie ehrlich – so
schlimm war es nun auch wieder nicht. Um allerdings mit dem Buch fortzufahren, sollten Sie schon die folgenden Aufgaben aller drei Level selbständig
lösen können.
6.6.1
Level 1
1. Was ist ein abstrakter Datentyp?
2. Womit wird in C++ ein abstrakter Datentyp formuliert?
3. Mit welchen Schlüsselwörtern erstellen Sie in C++ einen abstrakten
Datentyp, und wo liegt der Unterschied zwischen ihnen?
4. Wie erfolgt der Zugriff auf die Daten in einer Klasse?
5. Was ist ein Konstruktor?
6. Was ist ein Destruktor?
7. Erklären Sie kurz, was der this-Zeiger ist.
230
6.6 Aufgaben
6.6.2
Level 2
1. Die folgende Headerdatei enthält einen Designfehler. Welchen?
// listings/006/aufgabe001.h
#ifndef _TEST_H_
#define _TEST_H_
using namespace std;
class XYZ {
public:
int ival;
XYZ(int i) { ival = i; }
XYZ() { ival = 0; }
int get_ival() const { return ival; };
void set_ival(int i) { ival = i; }
};
#endif
2. Die Klassendefinition der Headerdatei aufgabe002.h und die Definition
der Elementfunktion des Quellcodes aufgabe002.cpp lassen sich nicht
übersetzen. Wo sind die Fehler? Zunächst die Headerdatei aufgabe002.h:
00
01
02
03
// listings/006/aufgabe002.h
#ifndef _TEST_H_
#define _TEST_H_
using namespace std;
04
05
06
07
08
09
10
11
12
13
14
class XYZ {
private:
int ival;
public:
XYZ(int i) { ival = i; }
XYZ() { ival = 0; }
int get_ival() const { return ival; };
void set_ival(int i) { ival = i; }
void initVal(int i=0) const;
};
#endif
Der Quellcode aufgabe002.cpp:
00
01
// listings/006/aufgabe002.cpp
#include "aufgabe002.h"
231
6
Klassen
02
03
04
6.6.3
void initVal(int i) const {
set_ival(i);
}
Level 3
In der Klasse Automat fehlt noch ein Konstruktor, der ein Objekt vom Typ
Automat(string, unsigned long) abdeckt. Implementieren Sie nachträglich
diesen Konstruktor. Erweitern Sie außerdem die Klasse um eine Elementfunktion print(), die sämtliche Daten eines Objektes ordentlich auf dem
Bildschirm ausgibt.
232
Index
- -Operator (Minus) 48
-- 52
! (logisches NICHT) 77
!=-Operator (Vergleich) 71
#define 111
#elif 116
#else 116
#endif 116
#error 118
#if 116
#if defined 116
#ifdef 116
#ifndef 116
#include 109
#line 118
#pragma 119
#undef 115
%-Operator (Modulo) 48
& (Adressoperator) 124
&& (logisches UND) 77
* (Indirektionsoperator) 126
*-Operator (multiplizieren) 48
*this 242
++ 52
+-Operator 48
/-Operator (dividieren) 48
<< überladen 289
<=-Operator (Vergleich) 70
<-Operator (Vergleich) 70
==-Operator (Vergleich) 71
>=-Operator (Vergleich) 71
>> überladen 288
>-Operator (Vergleich) 71
?:-Operator (Bedingung) 76
|| (logisches ODER) 77
64 Bit 41
A
Abgeleitete Klasse 299
implizite Typumwandlung 313
Konstruktor 307
Mehrfachvererbung 314
protected 309
Redefinition 306
virtual (Schlüsselwort) 321
Zugriffsrechte vererben 311
Abgeleitete Klasse 씮 Vererbung
abort() 108
Abstrakte Klasse 325
Polymorphie 327
Abstrakter Datentyp 203
Ada 15
Adressoperator 124
Algol68 15
ANSI-C++ 17
Anweisung 67
Anweisungsblock 67
Arithmetik
Zeiger 129
Arithmetischer Operator 48
Array
C-Array 136
vector 133
Zeiger 145
Array 씮 Feld
Array 씮 Vektor
atexit() 108
Aufzählungstyp
enum 173
Ausgabe
cout 27
Ausnahmebehandlung 씮 ExceptionHandling
407
Index
Ausnahme-Klasse 366
auto (Schlüsselwort) 190
B
bad_alloc 162
Basisdatentyp 28, 29, 33
initialisieren 35
Basisdatentyp 씮 Datentyp
BCPL 15
Bedingte Kompilierung 116
Bedingungsoperator 76
Bezeichner 30
bool 36
Bool-Typumwandlung 57
break 80
break-Anweisung 90
Breite Zeichen 40
C
C mit Klassen 16
C++ 16
Entstehung 15
Geschichte 15
C++0x 17
Call-by-Reference 150
Call-by-Value 96
C-Array 136, 246
Initialisierungsliste 138
case-Marke 79
catch 361
alternatives 365
C-Cast 58
cerr 28
cfloat 45
char 36
Zeiger 147
408
char-Array
Zeiger 147
cin 29
class 204
clog 28
Compiler 18
const 63, 113, 233
const (Schlüsselwort) 191
const_cast 60
continue-Anweisung 92
cout 27
C-String 142
Zeiger 147
C-Zeichenkette 씮 C-String
D
Datenkapsel 203
Datentyp 33
abstrakter 203
Default-Konstruktor 219
default-Marke 80
define 111
Definition 33
Deklaration 33
Dekrementoperator 52
delete 162
Dereferenzierung 126
Destruktor 221
abgeleitete Klasse 308
virtueller 329
double 44
do-while-Anweisung 84
dynamic_cast 63
Dynamische Datenstruktur 165,
168
Dynamisches Objekt 247
Dynamisches Speicherobjekt 159
Index
E
F
Eingabe
cin 29
Elementfunktion 205, 224
friend 267
Inline- 224
konstante 227
virtuelle 325
Wertübergabe 239
Elementfunktion 씮 Methode
Elementinitialisierer 266
abgeleitete Klasse 308
elif 116
else 116
else-if-Verzweigung 73
else-Verzweigung 71
endif 116
Endlosschleife 84
Entwicklungsumgebung 19
enum 173
error 118
Escape-Zeichen 38
Exception
auslösen 360
behandeln 360
Fehlerklasse 366
Stack-Abwicklung 365
Standard- 368
System- 373
Exception-Handling 359
exit() 107
explicit (Funktionsattribut) 193
Explizite Typumwandlung 58
extern (Schlüsselwort) 186
Feld 133
Fließkomma-Arithmetik 50
Fließkommazahl 31, 44
float 44
for-Anweisung 87
friend 267
Funktion
aufrufen 94
Call-by-Reference 150
definieren 93
friend 267
inline 105
main 106
Parameter 96
Referenzen 153
Rückgabewert 99
Scope-Operator 104
Standardparameter 97
überladen 101
Vorwärtsdeklaration 95
Zeiger als Parameter 150
Zeiger als Rückgabe 152
Funktionsattribut 193
Funktions-Template 335
G
Ganzzahl 31
Ganzzahltypen 35
getline() 141
Gleitkomma-Promotion 56
Gleitkomma-Typumwandlung 57
Gültigkeitsbereich 103
Namensraum 181
409
Index
H
Hilfsfunktion 234
I
if 116
if defined 116
ifdef 116
ifndef 116
if-Verzweigung 68
Implizite Typumwandlung 54
abgeleitete Klasse 313
Indirektionsoperator 126
Inkrementoperator 52
inline 105, 225
inline (Funktionsattribut) 193
Instanz 210
int 36, 40
Integer 40
Integral-Gleitkomma-Typumwandlung 57
Integral-Promotion 55
Integral-Typumwandlung 56
Iterator 355
K
Klasse 203
abgeleitete 씮 Abgeleitete Klasse
Call-by-Reference 237
Call-by-Value 235
Elementfunktion 205
friend 269
Hilfsfunktion 234
indirekter Zugriff 213
Mehrfachvererbung 314
Polymorphie 327
Punktoperator 211
410
Klasse (Forts.)
Referenz 239
Vererbung 299
virtual (Schlüsselwort) 321
virtuelle Vererbung 318
Zugriff 210
Klasse (Forts.)
Zugriffskontrolle 207
Klassendefinition 204
Klassenelement
dynamisches 250
statisches 257
Klassen-Template 343
Kommentar 32
Kompilierung
bedingte 116
Konstante 63
Konstruktor 215
abgeleitete Klasse 307
Konvertierungs- 292
Kopier- 220
Standard- 219
Kontrollstrukturen 67
Konvertierungsfunktion 294
Konvertierungskonstruktor 292
Konvertierungsoperator 292
Kopierkonstruktor 220, 255
Zuweisungsoperator überladen 285
L
limits 46
line 118
Linker 18
Literal 31
Logische Operatoren 76
long 36, 41
long double 44
Index
M
P
main-Funktion 24
Makro 113
Mehrfachvererbung 314
Memory Leak 160
Methode 205
Elementfunktion 224
Modularisierung 179
Pointer 씮 Zeiger
Polymorphie 327
pragma 119
Präprozessor-Direktive 108
private 207, 310
Programmende 107
protected 309
ptrdiff_t 129
public 207, 310
Vererbung 303
N
Namensraum 179
deklarieren 179
Gültigkeitsbereich 181
importieren 184
std 27
Namespace 씮 Namensraum
new 160, 248
nullptr 128
Null-Zeiger 127
numeric_limits 46
O
Objekt 210
dynamisches 247
konstantes 233
Rückgabewert 244
Operator
arithmetischer 48
logischer 76
überladen 273
überladen als Elementfunktion 277
überladen als globale Funktion 280
operator (Schlüsselwort) 274
R
Rechnen 47
Redefinition 306
Referenz 131
Funktionsparameter 153
Klasse 239
Rückgabe aus Funktion 154
Struktur 167
register (Schlüsselwort) 189
reinterpret_cast 62
return 99
S
Schablone 씮 Template
Schleife 82
Semikolon 25
set_new_handler 248
short 36, 41
signed 43
Simula 15
size_t 136
sizeof
Zeiger 128
Speicherklassenattribut 185
Speicherloch 160
411
Index
Speicherobjekt
dynamisches 159
Sprunganweisung
kontrollierte 90
Stack-Abwicklung
Exception 365
Standard-Exceptions 368
Standardkonstruktor 219
static 186, 257
static_cast 61
std 27
Steuerzeichen 38
STL 343, 351
Algorithmus 355
Container 351
Elementfunktion 354
Iterator 355
Stream 26
Ausgabe- 27
Eingabe- 29
String 140
string (Klasse) 140
String-Ende-Zeichen 142
Stroustrup, Bjarne 15
struct 164
Struktur 164
deklarieren 165
dynamische 168
Referenz 167
typedef 174
Union 172
Zugriff 165
Strukturzeiger 166
switch-Fallunterscheidung 79
Symbol 30
System-Exception 373
412
T
Template 335
Funktions- 335
Klassen- 343
STL 351
terminate() 108, 364
this-Zeiger 229
throw 360
try 361
typedef 174
Struktur 174
typeid 331
typeinfo 331
Typenqualifikator 191
Typumwandlung 54
explizite 58
implizite 54
U
Überladen
Operator 273
Zuweisungsoperator 285
Union 172
Union 씮 Variante
unsigned 43
using 184
V
Variable 34
Gültigkeitsbereich 103
Variante 172
vector 133
vector (Klasse) 246
Vektor 133
Vererbung 299
public 303
Redefinition 306
Index
Vererbung (Forts.)
virtuelle 318
Vergleichsoperator 70
Verzweigung 68
virtual 325
virtual (Funktionsattribut) 193
virtual (Schlüsselwort) 321
void 42
void * 130
volatile 192
Vorzeichenbehaftet 42
Vorzeichenlos 42
W
wchar_t 40
what() 369
while-Anweisung 83
Z
Zeichen 32
Zeichenkette 32
Zeichenkette 씮 String
Zeiger 123
*this 242
Arithmetik 129
Array 145
char-Array 147
Zeiger (Forts.)
C-String 147
deklarieren 124
Dereferenzierung 126
Funktionsparameter 150
Klassenelement 250
Null- 127
Rückgabe aus Funktion 152
sizeof 128
Speichergröße 128
Struktur 166
this 229
void* 130
Zeigerarray 148
Zeilenende 39
Zuweisungsoperator
überladen 285
413
Herunterladen