IT-Academy Logo
Sign Up Login Help
Home - Programmieren - C++ - c++



c++

Alles was es zu dieser Programmiersprache zu sagen gibt


Autor: Arnold Willerner (willemer)
Datum: 23-01-2002, 19:36:25
Referenzen: http://www.willemer.de/informatik/index.htm
Schwierigkeit: Profis
Ansichten: 9717x
Rating: 6.4 (10x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Klassen in C++

Klasse und Struktur

Jeder C-Programmierer kennt die Struktur (ein PASCAL-Programmierer würde es RECORD nennen). Es ist die Zusammenfassung mehrerer zusammengehöriger Datenelemente. Ein einfaches typisches Beispiel ist das Datum. Es besteht aus drei ganzen Zahlen. Ein C++-Programmierer wird eine Struktur als eine Klasse, die nur Datenelemente enthält und deren Elemente alle öffentlich zugänglich sind.

Struktur:


typedef struct {
  int Tag;
  int Monat;
  int Jahr;
} tDatum;

Klasse:

class tDatum {
public:
  int Tag;
  int Monat;
  int Jahr;
};

Nach beiden Deklarationen kann man eine Variable vom Typ tDatum anlegen. In beiden Fällen wird jedes Teilelement über einen Punkt angesprochen. Auch die Möglichkeit Zeiger zu definieren und der Zugriff per -> ist identisch.

Funktionen als Bestandteil

Eine Klasse kann neben Datenelementen auch Funktionen enthalten.

class tDatum {
public:
  int Tag;
  int Monat;
  int Jahr;
  void NeuJahr(int Jahr);
};

Die Funktionen gehören zur Struktur. Im Normalfall wird man auch zu Strukturen bereits Funktionen geschrieben haben, die darauf basieren. Neu ist, dass die Funktion mit der Klasse deklariert ist. Die Funktion braucht die Struktur nicht mehr als Parameter, da sie auf "ihr" Objekt wirkt.


tDatum Letzlich;
Letzlich.NeuJahr(2000);

Hier wird das Objekt Letztlich angelegt. Dieses Objekt wird durch die Neujahrsfunktion auf den 1.1.2000 gesetzt.

Innerhalb der Funktion kann auf die Klassenelemente direkt zugegriffen werden. Man kann aber auch den vordefinierten Selbstreferenzzeiger this verwenden.

void tDatum::NeuJahr(int pJahr)
{
  Tag = 1;
  Monat = 1;
  Jahr = pJahr;
}

void tDatum::NeuJahr(int pJahr)
{
  this->Tag = 1;
  this->Monat = 1;
  this->Jahr = pJahr;
}

Konstruktor und Destruktor

Man kann für eine Klasse eine Funktion schreiben, die immer ausgeführt wird, wenn das Objekt erzeugt wird. Dies nennt man den Konstruktor. Er sorgt dafür das die Elemente korrekt initialisiert sind und vermeidet dadurch Flüchtigkeitsfehler. Der Name der Funktion lautet wie die Klasse. Damit man merkt, dass das Datum noch nicht festgelegt wurde, kann man alle Elemente auf 0 setzen. Man könnte auch standardmäßig das heutige Datum eintragen, je nach Anwendung.

class tDatum {
public:
   tDatum();
   ~tDatum();
...
};

tDatum::tDatum()
{
  Tag=0; Monat=0; Jahr=0;
}

tDatum::~tDatum()
{
}

Der Konstruktor kann beispielsweise auch Speicher alloziieren, der für das Objekt gebraucht wird. Damit dieser Speicher wieder zurückgegeben werden kann, gibt es einen Destruktor, der bei Zerstörung der Variablen gerufen wird.

Übrigens schadet es nichts, im Destruktor auf alle Zeiger der Klasse ein delete anzuwenden, sofern dieser 0 ist oder auf einen gültigen Bereich zeigt.

Der Name der Destruktorfunktion wird gebildet, indem eine Tilde (~) dem Klassennamen vorangestellt wird.

Globale Variablen werden zum Programmstart angelegt und zum Programmende zerstört. Lokale Variablen rufen ihren Konstruktor bei Definition und werden bei Verlassen ihres Geltungsbereichs zerstört.

new und delete

Wird ein Zeiger auf eine Klasse definiert, wird mit dem new-Operator der Speicher für die Instanz alloziiert und der Konstruktor aufgerufen. Die angeforderte Variable wird mit delete wieder weggeräumt.

Wird new auf ein Array angewandt, muss auch der Array-Aufruf delete[] erfolgen. Ansonsten würde nur der Speicher des Feldes freigegeben, aber die Elemente des Arrays nicht.

Privatsphäre

Man kann den Zugriff auf die Elemente von aussen verhindern, indem man sie privat deklariert. Aber wozu soll das gut sein? Warum sollte man sich selbst (oder die Kollegen) behindern?

Gehen wir bei der Datumsklasse davon aus, dass der Wochentag für das Datum berechnet werden soll. Da die Berechnung sehr oft gebraucht wird, soll der Wochentag in der Klasse zwischengespeichert werden, wenn er einmal berechnet wurde. Solange das Datum nicht geändert wird, braucht der Wochentag auch nicht geändert werden. Man muss nur daran denken, bei jeder Änderung des Datums den Wochentag für ungültig zu erklären, damit er neu berechnet wird, wenn er angefordert wird.


class tDatum  
{
private:
	int tag;
	int monat;
	int jahr;
	int wochentag;
public:
	tDatum();
	virtual ~tDatum();
	int Tag(int Wert=0);
	int Monat(int Wert=0);
	int Jahr(int Wert=0);
	int Wochentag();
};

Zu diesem Zweck werden die Variablen Tag, Monat und Jahr als privat deklariert. Das Setzen wird durch Funktionen erreicht. Neben dem Setzen des Datums erklärt sie automatisch den Wochentag für ungültig. Die Wochentagsfunktion prüft, ob ein gültiger Wochentag vorliegt und berechnet ihn ggfs. neu.

Die Funktion Tag(int) wird so benutzt, dass sie als Auslesen des Datums interpretiert wird, wenn 0 übergeben wird. Ansonsten wird der Wert gesetzt. In C++ kann man bei der Deklaration angeben, wie der Parameter interpretiert werden soll, wenn keiner verwendet wird. Wird die Funktion Tag() aufgerufen, wird also der Parameter Wert auf 0 gesetzt.

tDatum::tDatum()
{
	wochentag = -1;
}

int tDatum::Tag(int Wert)
{
	if (Wert>0) {
		wochentag = -1;
		tag = Wert;
	}
	return tag;
}

// fuer Monat und Jahr analog!

int tDatum::Wochentag()
{
	if (wochentag<0) {
		// berechne ....
	} 
	return wochentag;
}

Vererbung in C++

Wenn eine neue Klasse benötigt wird, die alle Eigenschaften einer anderen Klasse hat und nur spezielle Eigenschaften hinzufügt, kann die neue Klasse abgeleitet werden. Man spricht davon, dass sie die Eigenschaften der alten Klasse erbt.

Im Beispiel setzen wir die Existenz der tDatum-Klasse voraus. Wir wollen nun Feiertage definieren. Ein Feiertag ist einerseits ein Tag wie jeder andere auch. Er hat einen Tag, Monat, Jahr und einen Wochentag. Es kann sein, dass dieser Tag berechnet werden muss. Dann kann die Datumsaddition verwendet werden. Hinzu kommt eine Bezeichnung. Vielleicht soll später noch hinzugefügt werden, ob es ein gesetzlicher Feiertag ist.

Deklariert eine abgeleitete Klasse so:

class tFeiertag : public tDatum {
public:
  char Name[40];
};
tFeiertag Ostern;

Konstanten in C++

In C gibt es keine symbolischen Konstanten, sondern man verwendet den Präprozessor mit der Anweisung #define.

#define MAX 1245

Unter C++ wird eine Konstante definiert.

const int MAX = 1245;

Der Vorteil des Mehraufwands beim Schreiben ist die Tatsache, dass nun der Name MAX dem Compiler und dem Debugger bekannt sind. Durch den #define wird einfach die Zahl 1245 in den Code eingefügt.

Virtuelle Funktionen in C++

Virtuelle Funktionen sind Funktionen einer Basisklasse. Eine abgeleitete Klasse kann sie überschreiben und übernimmt damit die Ausführung der Funktion für ihre Klassenmitglieder. Das ist nichts Neues, dass funktioniert auch mit nicht virtuellen Klassen. Das Besondere an der virtuellen Klasse ist, dass das Objekt selbst weiss, zu welcher abgeleiteten Klasse es gehört und seine zugehörige Klassenfunktion ruft.

Im Beispiel haben wir eine Klasse Bass und eine abgeleitete Klasse Tuba. Eine Tuba ist eine Spezifizierung eines Basses. Alle Bässe können dröhnen. Aber eine Tuba dröhnt in einer ganz besonderen Art. Wurde ein Objekt einmal als Tuba erstellt, wird es immer wie eine Tuba dröhnen, auch wenn es einfach nur als irgendein Bass eingesetzt wird.

#include <stdio.h>

class Bass {
public:
    virtual void droehn();
};

void Bass::droehn()
{
    puts("Bass");
}

class Tuba : public Bass {
public:
    void droehn();
};

void Tuba::droehn()
{
    puts("Tuba");
}


void main()
{
    Tuba Objekt;

    Tuba *tuba = &Objekt;
    Bass *bass = &Objekt;

    tuba->droehn();
    bass->droehn();
}

In beiden Fällen wird Tuba ausgegeben. Das zeigt, dass das Objekt "weiß", dass es eine Tuba ist, auch dann, wenn es über einen Basszeiger zugegriffen wird. Das ist anders, wenn das Schlüsselwort virtual in der Klasse Bass entfernt wird.

VTable

Realisiert wird dies durch die VTable, die jede Klasse mit virtuellen Funktionen und die abgeleiteten Klassen erstellen. In der VTable wird jede virtuelle Funktion aufgeführt. Wird die Klasse abgeleitet, erhält sie eine Kopie der VTable. Für alle selbstvorhandenen Funktionen wird der Zeiger umgeleitet.

Bei jedem erzeugten Objekt ist ein Zeiger auf die VTable seiner Klasse enthalten. Wird mit einem beliebigen Zeiger auf diese Klasse zugegriffen, wird die VTable durchlaufen und so die richtige Funktion gefunden. Das bedeutet, dass jedes Objekt selbst weiss, was es ist und welche Funktion zu ihm gehört, egal über was für einen Zeiger es aufgerufen wird.

Da das Objekt den Zeiger auf die VTable hat, sind virtuelle Funktionen nur bei nicht-statischen Elementen sinnvoll und erlaubt.

Virtueller Destruktor

Es gibt eine Faustregel, die besagt, dass jede Klasse mit virtuellen Funktionen auch einen virtuellen Destruktor haben soll. Wie oben gesehen, wird die abgeleitete Klasse Tuba auch durch einen Basszeiger angesprochen. Wird über diesen ein delete ausgeführt, würden bei einem nicht virtuellem Destruktor nur die Bestandteile des Bass, aber nicht der Tuba angesprochen. In diesem einfachen Beispiel ist das nicht relevant. Würde die Tuba aber gesonderte Speicherbereiche brauchen, dann würde diese über einen nichtvirtuellen Destruktor nicht aufgelouml;st.

Da ein nicht virtueller Destruktor nicht gewährleistet, dass abgeleitete Klassen ordnungsgemäß abgebaut werden, kann ein nicht virtueller Destruktor sogar so interpretiert werden, dass der Autor ein Ableiten seiner Klasse nicht vorgesehen hat und wohl auch nicht empfielt.

Abstrakte Basisklasse

Im Beispiel kann man ein Objekt der Basisklasse erzeugen, einen Bass. Es kann aber sein, dass der Bass nur die Eigenschaften bestimmter Instrumente beschreiben soll. Einen realen Bass als solchen gibt es ja schliesslich nicht. Man kann die ableitende Klasse dazu zwingen, ihre eigene Realisierung von droehn zu liefern. Dazu setzt man die virtuelle Funktion in der Basisklasse gleich Null.

class Bass {
public:
    virtual void droehn() = 0;
};

Damit wird die Basisklasse zur abstrakten Basisklasse. Es kann nur noch Ableitungen geben, keine Instanzen. Damit beschreibt die Klasse Bass eine Kategorie von Instrumenten, die gemeinsame Eigenschaften haben. Ein Bass als solcher existiert nicht wirklich. Es gibt aber eine Tuba, eine Bassgitarre, eine Bassstimme oder Basstrommel. Und jeder von diesen Basstypen kann eines: dröhnen.

Die Klasse Bass kann als Schnittstelle zu allen abgeleiteten Klassen dienen und als ein solches Interface wird sie auch in der COM-Technologie verwendet. Das Interface legt den Vertrag fest, den die Ableitungen zu erfüllen haben.

Überladene Funktionen in C++

Überladen von Funktionen

C++ unterscheidet Funktionen nicht nur am Funktionsnamen, sondern auch an den Parametern. Damit ist es möglich, mehrere Funktionen gleichen Namens zu haben, sofern sich die Parameter unterscheiden.

Beispielsweise kann es die Funktion print für verschiedene Datentypen geben.

int print(int ganzzahl);
int print(double fliesskomma);
int print(char *string);
print(variable);

Der Compiler wird im obigen Fall die print-Funktion verwenden, die zum Typ von variable passt.

Überladen von Operatoren

Auch Operatoren können überladen werden. Damit können Operatoren verwendet werden. Beispielsweise ist es sinnvoll, bei einem Datum eine Addition ganzer Zahlen zuzulassen. Die Deklaration und Definition sieht etwa so aus:

class tDatum  
{
public:
	// ....
	tDatum operator+(int Tage);
};

tDatum tDatum::operator+(int Tage)
{
	// Berechnung des Datums
	return *this;
}

Die Addition liefert ein neues Datum zurück und als rechter Operand ist eine ganze Zahl zulässig. So kann das Datum durch einfache Addition um 14 Tage weitergeschoben werden.


tDatum heute;
   heute = heute + 14;

Das Überladen von Operatoren hat seine Tücken, wenn die Semantik nicht mehr so eindeutig ist, wie im obigen Fall.

Templates in C++

Templates werden verwendet, wenn gleiche Aktionen auf unterschiedlichen Datentypen anfallen.

Template-Funktionen

Zunächst sollen Template-Funktionen betrachtet werden. Es sind Funktionen mit einer Funkionalität, die auf verschiedenen Datentypen gleich sind. Damit sind sie prädistiniert als Ersatz für die C-Makros. Diese konnten vor allem keine Typsicherheit gewährleisten und waren mehr oder weniger auf eine Zeile beschränkt.

Als Beispiel dient die Funktionen min:

template <class T> T min( T a, T b ) {
    return ( a < b ) ? a : b;
}

Die Templatefunktion instanziiert erst bei Benutzung. Es wird nicht explizit angegeben, daß z. B. eine char-Instanz erzeugt werden soll. Der Aufruf sähe beispielsweise so aus:

int i=15;
int j=7;
char c    
printf("%d = min(%d, %d)\n", min(i,j), i, j);

Templatefunktionen gewährleisten im Gegensatz zu den C-Makros, daß alle gleichen Typen auch gleich bleiben. Das min aus einem long und einem char zu bilden, ist also nicht möglich.

Als Abschlußbeispiel die Funktion swap. Die Kopie der ersten Variablen wird durch den Konstruktor erzeugt. Die Parameterübergabe erfolgt per call by reference.


template <class T> void swap( T& a, T& b )
{
    T c( a );
    a = b; b = c;
}

Template-Klassen

Wenn Gleichartigkeit über eine Funktion hinaus geht, wird die template-Klasse interessant.

Als sehr einfaches Beispiel soll hier eine Billigimplementation eines Stacks dienen. Realisisiert wird der Stack mittels einem Array.


template<class T> class Stack {
public:
	Stack() : maxStack(256) { s = new T[maxStack]; index=0;}
	~Stack() {delete[] s;}
	int pop(T *);
	int push(T );
private:
	const int maxStack;
	T *s;
	int index;
};

template<class T> int Stack<T>::pop(T *get)
{
	if (index==0 ) return 0;
	*get = s[--index];
	return 1;
}

template<class T> int Stack<T>::push(T set)
{
	if (index>=maxStack) return 0;
	s[index++] = set;
	return 1;
}

Die Template-Klasse realisiert das Verhalten unabhängig vom verwendeten Datentyp. Die Definition von T dient als Platzhalter für den Datentyp.

Eine Instanziierung erfolgt durch

    Stack<int> iStack;

Hier wird erst angegeben, welchen Datentyps der Inhalt ist. Statt int könnte jeder andere Datentyp verwendet werden.

Template-Klasse mit Parameter

Im Beispiel oben haben wir die Stackgröße mit 256 festgelegt. Zwischen den eckigen Klammern kann ein solcher Wert als Parameter übergeben werden, im folgenden Beispiel als size. Dieser Wert muß bei der Instanziierung als Konstante angegeben werden.

template<class T, int size> class Stack {
public:
	Stack() : maxStack(size) { s = new T[maxStack]; index=0;}
	~Stack() {delete[] s;}
	int pop(T *);
	int push(T );
private:
	const int maxStack;
	T *s;
	int index;
};

template<class T, int size> int Stack<T, size>::pop(T *get)
{
	if (index==0 ) return 0;
	*get = s[--index];
	return 1;
}

template<class T, int size> int Stack<T, size>::push(T set)
{
	if (index>=maxStack) return 0;
	s[index++] = set;
	return 1;
}

Eine Instanziierung erfolgt durch

    Stack<int,256> iStack;



simsn
Rookie
Beitrag vom:
21-07-2004, 17:19:25

zu Zeiger

Das stimmt was der Kollege sagt.
Zeiger sind elemetar bei c++,vorallem wenn man z.b ein Kontoverwaltungsprogramm schreibt.
Damit ich ein schönes heap konstruieren kann.
Aber sonst gut erklärt.

-----------------------------------------------------


Rolfhub
Senior Member
Beitrag vom:
29-08-2002, 16:05:35

Drei kleine Anmerkungen

Ich habe drei Anmerkungen zu diesem (ansonsten wirklich guten) Artikel: 1.: Ein Code-Beispiel zu "new und delete" wäre sinnvoll. 2.: Kleiner Schreibfehler: "nicht aufgelouml;st". 3.: Es wäre sicherlich sinnvoll, mehr zu Zeigern zu sagen, da diese ja nicht gerade simpel und schnell zu erlernen sind, vor allem, wenn man mit Zeigern auf Funktionen/Klassen arbeitet.

-----------------------------------------------------


[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04511
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16552
Queueeinträge:06248
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1158
Comments: 0