private und public
Syntax
Standardmäßig sind alle Elemente einer Klasse privat. Sobald Sie in der Klassendefinition das Label public: setzen, sind alle folgenden Elemente öffentlich. Sollen wieder private Elemente folgen, setzen Sie das Label private:. Sie können beliebig oft wechseln. Es sieht allerdings ordentlicher aus, wenn Sie Ihre Elemente nach öffentlich und privat ordnen. Es hat sich als praktisch erwiesen, die öffentlichen Elemente zuerst zu nennen.Private Daten
Es ist eine gute Idee, Datenelemente grundsätzlich privat zu halten. Auf diese Weise kann der Zugriff auf sie durch entsprechende Elementfunktionen kontrolliert werden. So können Inkonsistenzen vermieden und Abhängigkeiten kontrolliert werden. Das sollten Sie auch dann tun, wenn Sie bei dem Erstellen der Klasse noch keinen Grund sehen, warum Sie den Zugriff beschränken sollten. Dann werden alle anderen Module die Elementfunktionen für den Zugriff nutzen. Wenn sich dann eine Abhängigkeit herausstellt, brauchen Sie nur die Implementation der Elementfunktion anzupassen. Die Klasse kann ebenso unverändert bleiben wie auch alle Programmteile, die diese Klasse nutzen.Beispiel
Das folgende Beispiel ist eine Klasse für Brüche. Die Datenbestandteile zaehler und nenner sind als privat vor der Außenwelt abgeschirmt. Zu den öffentlichen Funktionen zählen Funktionen, die das Setzen und Auslesen dieser Werte ermöglichen.[Bruchrechnung mit privaten Daten]
class tBruch { public: tBruch() {zaehler=0; nenner=1;} long GetNenner() {return nenner;} long GetZaehler() {return zaehler;} void SetNenner(long p) {if (p!=0) nenner=p;} void SetZaehler(long p) {zaehler=p;} void Show(); private: long zaehler; long nenner; }; void tBruch::Show() { cout << zaehler << "/" << nenner << endl; } int main() { tBruch bruch; long inNenner, inZaehler; cout << "Zähler Leerzeichen Nenner eingeben!" << endl; cin >> inZaehler >> inNenner; bruch.SetNenner(inNenner); bruch.SetZaehler(inZaehler); bruch.Show(); }
Im Beispiel der Klasse tBruch sind die Datenelemente zaehler und nenner privat. Der Zugriff erfolgt durch die Funktionen, die mit »Get« oder »Set« beginnen. Auf diese Weise lässt sich verhindern, dass jemand den Nenner auf 0 setzt. Auch können Sie bei Änderungen oder vor Ausgaben den Bruch automatisch kürzen. Auch bei der Datumsklasse kann es durchaus sinnvoll sein, einen direkten Zugriff der Datenelemente zu verhindern. So können Sie leicht verhindern, dass als Monat 13 oder 0 angegeben wird. Aber die Möglichkeiten gehen über die einfachen Konsistenzprüfungen hinaus. Vielleicht soll auch der Wochentag Bestandteil des Datums werden. Damit der Wochentag nicht bei jeder Anfrage neu berechnet werden muss, kann man ihn in einer privaten Elementvariablen vorhalten. Dann muss aber sichergestellt sein, dass das Datum nicht direkt geändert wird. Werden Datumsänderungen aber durch entsprechende Funktionen durchgeführt, dann kann bei jeder Änderung der Wochentag für ungültig erklärt werden. Wenn er angefordert wird, schaut die Funktion nach, ob er neu berechnet werden muss.
[Klasse mit privaten Elementen]
class tDatum { private: int tag; int monat; int jahr; int wochentag; public: tDatum(); virtual ~tDatum(); void setTag(int Wert); void setMonat(int Wert); void setJahr(int Wert); int getTag() {return tag;} int getMonat() {return monat;} int getJahr() {return jahr;} int getWochentag(); }; tDatum::tDatum() { wochentag = -1; } void tDatum::setMonat(int Wert) { if (Wert>0 && Wert<13 && monat!=Wert) { monat = Wert; wochentag = -1; } }
Damit jeder, der die Klasse benutzt, daran denkt, bei Änderungen die Elementvariable wochentag auf -1 zu setzen, werden die Elementvariablen tag, monat und jahr als privat deklariert. Damit sind sie dem Zugriff von außen entzogen. Sie können nur über die Elementfunktionen geändert werden. So sorgt die Elementfunktion setMonat() dafür, dass einerseits kein ungültiger Monat in das Objekt eingetragen werden kann. Gleichzeitig sorgt sie auch dafür, dass der Wochentag ungültig wird, sobald sich der Monat ändert. Auf diese Weise wird erreicht, dass die Elementfunktion getWochentag() den Wochentag ohne Neuberechnung zurückgeben kann, sofern sie nicht von einer der Änderungsfunktionen auf -1 gesetzt wurde. Nur dann muss der Wochentag neu berechnet werden.
Schnittstelle
Die öffentlich zugänglichen Elemente einer Klasse werden als Schnittstelle bezeichnet. Sie sind für den Anwender der Klasse der Zugang zu den Elementen. Die Schnittstelle sollte entsprechend auf eine möglichst einfache Benutzung zugeschnitten sein. Andererseits sollte sie möglichst klein und übersichtlich gehalten werden. Insbesondere sollte die Schnittstelle nicht abhängig von der Implementation der Klasseninterna gestaltet werden.Beispiel: Stack
Die verkettete Liste vorgestellt. Dort wurde eine erste Implementation durch eine Struktur realisiert. Indem Sie die Möglichkeiten einer Klasse nutzen, können Sie die linearen Listen sicherer und übersichtlicher umsetzen. Die Funktionen push() und pop() gehören nun zur Klasse und sind nicht mehr unabhängige Funktionen. Da die Kombination aus Datenverbund und Funktionalität einen Stack wesentlich besser beschreibt, kann ein Stack durch eine Klasse wesentlich umfassender modelliert werden.[Implementation eines Stacks (stack.cpp)]
// Programm zur Demonstration einer Stack-Klasse #include <iostream> using namespace std; // Knoten einer verketteten Liste class tNode { public: int d; // Daten, hier ganze Zahlen tNode *next; // Verkettung }; // Der Stack als Verbindung aus Anker und Operationen class tStack { public: tStack(); ~tStack(); void push(int); // fügt Informationen hinzu int pop(); // holt Informationen heraus private: tNode * Anker; // jeder Stack hat seinen eigenen Anker }; tStack::tStack() { Anker = 0; // leere Liste wird vorbereitet } // Der Destruktor löscht alle übriggebliebenen Elemente tStack::~tStack() { tNode *last = Anker; // Hilfszeiger zum Sichern des Ankers while (Anker) // solange noch Elemente in der Liste { last = Anker; // erstes Element sichern Anker = Anker->next; // Anker auf zweites Element setzen delete last; // erstes Element freigeben } } // Neues Element vorn in die Liste einhängen void tStack::push(int d) { tNode *node = new tNode; // erzeugt Listenelement node->d = d; // besetzt das Datenfeld node->next = Anker; // hänge bisherige Liste an Anker = node; // Mache dieses Element zum Anker } // Oberstes Element auslesen und aus der Liste löschen int tStack::pop() { int inhalt=0; // Hilfsvariable vom Typ der Elementdaten if (Anker) // sofern die Liste nicht schon leer ist { tNode *old = Anker; // sichere erstes Element Anker = Anker->next; // setze Anker auf zweites Element inhalt = old->d; // kopiere den Knoteninhalt delete old; // Ausgelesenen Knoten freigeben } return inhalt; // liefere den Knoteninhalt } // Hauptprogramm zum Testen des Stacks int main() { tStack stack; // lege einen Stack an stack.push(2); // schiebe Testdaten darauf stack.push(5); stack.push(18); cout << stack.pop() << endl; // lese den Stack aus cout << stack.pop() << endl; cout << stack.pop() << endl; }
Übungsaufgaben
- Erstellen Sie eine Klasse tFIFO, die ebenfalls eine dynamische Datenstruktur darstellt. Im Unterschied zum Stack arbeitet ein FIFO (First In First Out) eher wie ein Rohr oder eine Warteschlange. Das Paket, das zuerst hineingesteckt wird, wird auch als Erstes wieder herauskommen. Lösungshinweis: Sie werden nicht mit einem Anker auskommen, sondern werden zwei Zeiger brauchen. Der eine zeigt auf den Kopf der Schlange und der andere auf ihr Ende.
- Ergänzen Sie die Klasse tBruch um eine private ggT-Funktion, die bei der Ausgabe durch Show() aufgerufen wird, um die Brüche zu kürzen.
Freunde
Freunde sind Menschen, denen wir vertrauen und mit denen wir unsere Geheimnisse teilen. Genauso kann sich eine Klasse Freunde suchen, die auch auf private Elemente zugreifen dürfen. Als potenzielle Freunde kommen Funktionen oder Klassen in Frage.friend
Wer der Freund einer Klasse ist, wird innerhalb der Klassendefinition deklariert. Dazu folgt auf das Schlüsselwort friend die Deklaration der Funktion oder der Klasse, die als Freund akzeptiert wird.[Freundliche Klasse (friend.cpp)]
class Kumpel; class Polizei { public: void hilf(Kumpel&); }; // Damit die Klasse Kumpel die Klasse in der Schnittstelle // benutzen kann muss die Klasse Kollege hier deklariert werden class Kollege; class Kumpel { private: int geheim; void spionieren(); friend void hilf(Kumpel); friend class Kollege; friend void Polizei::hilf(Kumpel&); }; class Kollege { public: Kollege() { JB.geheim=007; } Kumpel JB; }; void hilf(Kumpel Egon) { Egon.spionieren(); }
Die Klasse Kumpel hat drei friend-Deklarationen: Die erste erlaubt der Funktion hilf(), auf alle Elemente zuzugreifen. In der zweiten Deklaration wird die Klasse Kollege zum Freund. Damit darf jede Elementfunktion der Klasse Kollege beliebig auf Elemente von Kumpel zugreifen. Die dritte Deklaration erlaubt es, dass von der Klasse Polizei nur die Elementfunktion hilf() auf alle Elemente der Klasse Kumpel zugreifen darf. Alle anderen Elementfunktionen der Klasse Polizei dürfen dies nicht.