Öffentlichkeit und Privatsphäre
Willemers Informatik-Ecke
So wie in einer Funktion lokale Variable nach außen unsichtbar sind, können in einer Klasse Datenelemente und Elementfunktionen verborgen werden, die nur für den internen Bedarf gedacht sind. Diese Elemente sind privat. Auf private Elemente können alle Elementfunktionen frei zugreifen. Dadurch ist jeder Zugriff auf sie kontrollierbar. Die öffentlichen Elementfunktionen bestimmen, wie die privaten Datenelemente geändert werden können und welche Abläufe dazu notwendig sind.

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

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.