home | ### 15. Teilobjekte und statische Elemente
Druckversion dieser Seite | Ziele Wir können ab heute Abend Objekte erstellen, die aus anderen Objekten bestehen und diese als Teilobjekte enthalten. Wir sind auch in der Lage Datenelemente zu einer Klasse zu erstellen, die nur einmal im Speicher sind.
### Teilobjekte und deren Initialisierung
| Gewisse Objekte, die wir in C++ abstrahieren wollen bestehen aus gewissen anderen und enthalten diese. Nehmen wir an wir schreiben eine Renn-Simulation in der Autos vorkommen. Nehmen wir weiter an, dass ein Programmierer sich nur um die Räder eines Autos kümmert um grössmögliche Realität zu erreichen. Ein anderer Programmierer beschäftigt sich dann mit dem Auto als Ganzes. Ein Auto hat vier Räder, man spricht von einer Hat-Beziehung. Das könnte ungefähr so aussehen :
    class Rad
    {
        public:
            // Konstruktor
            Rad();
            // Destruktor
            ~Rad();

            // weitere Methoden

    };

    class Auto
    {
        public:
            // Konstruktor
            Auto();
            // Destruktor
            ~Auto();

            // weitere Methoden

        private:
            // Teilobjekte
            Rad     m_linkesVorderrad;
            Rad     m_rechtesVorderrad;
            Rad     m_linkesHinterrad;
            Rad     m_rechtesHinterrad;
    };
| Ein Auto besteht also aus vier Rädern.
| Angenommen die Klasse Rad hat nur einen speziellen Konstruktor, der als Parameter den Radius des Rades hat :
    class Rad
    {
        public:
            // Konstruktor mit Parametern
            Rad(long radius);

        private:
            long    m_Radius;
    };
| Ein Konstruktor mit Parametern führt dazu, dass der Compiler keinen Konstruktor ohne Parameter mehr zur Verfügung stellt (ausser er wird explizit definiert). Der Programmierer der Klasse Rad wollte aber wohl nicht, dass jemand den Defaultkonstruktor verwendet.
| Da beim Erzeugen eines Objektes der Klasse Auto auch alle Teilobjekte - also auch alle Räder - mit dem default-Konstruktor erzeugt werden wollen, wird die Klasse Auto nicht mehr compilieren. Um Teilobjekte mit Parametern zu initialisieren gibt es die Möglichkeit eine Initialisierungsliste zu verwenden. Hier der Konstruktor der Klasse Auto mit Initialisierungsliste :
    Auto::Auto()
         :m_linkesVorderrad(4),
          m_rechtesVorderrad(4),
          m_linkesHinterrad(4),
          m_rechtesHinterrad(4)
    {
    }
| Vor der {-Klammer des Konstruktors kommt ein : (Doppelpunkt) und danach die Datenelement mit dem speziellen Kosntruktorparameter. Auch andere Datenelemente können mittels Initialisierungsliste erzeugt werden. Hier als Beispiel der Konstruktor der Klasse Rad :
    Rad::Rad(long radius)
        :m_radius(radius)
    {
    }
| Der Konstruktor könnte aber auch so aussehen :
    Rad::Rad(long radius)
    {
        m_radius = radius;
    }
| Der Konstruktor von Auto muss aber die Initialisierungsliste verwenden, denn die Rad hat nur einen Konstrukto mit Parametern.
| Konstanten müssen auf die gleiche Art initialisiert werden !
    class Rad
    {
        public:
            // Konstruktor mit Parametern
            Rad(long radius);

        private:
            long         m_radius;
            const double m_pi;
    };

    Rad::Rad(long radius)
        :m_pi(3.1415),
         m_radius(radius)
    {
    }
### Statische Datenelemente
| Die Eigenschaften eines jeden Objektes sind in seinen Datenelementen gespeichert. Ein Objekt unterscheidet sich von einem anderen durch seine Datenelemente. Es gibt manchmal aber auch Daten, die nicht nur zu einem Objekt gehören, sondern nur einmal für die ganze Klasse existieren müssen.
| Ein Beispiel, das man häufig antrifft, ist wenn man die Anzahl der Objekte einer bestimmten Klasse zählen will. Man verwendet hierfür statische Variablen, die mit dem Schlüsselwort static definiert werden.
    class Rad
    {
        public:
            // Konstruktor mit Parametern
            Rad(long radius);

        private:
            long         m_radius;
            const double m_pi;
            // diese Zahl gibt es
            // nur einmal, unabhängig
            // der Anzahl Räder die
            // erzeugt werden
            static long  s_anzahlRaeder;
    };

    // Initialisierung der statischen Variablen
    long Rad::s_anzahlRaeder = 0;

    Rad::Rad(long radius)
        :m_pi(3.1415),
         m_radius(radius)
    {
        // Gesamtanzahl der Räder
        // erhöhen
        s_anzahlRaeder++;
    }
| Erzeugen wir ein Objekt der Klasse Rad, werden die Datenelemente die zu diesem Objekt gehören automatisch im Speicher erzeugt, das heisst wenn wir ein Rad erzeugen wird im Speicher auch gleich ein long für m_radius und ein double für m_pi erzeugt. Da s_anzahlRaeder aber nicht direkt zu einem einzelnen Objekt gehört, muss diese statische Variable speziell initialisiert werden (siehe oben). Übrigens wäre es für m_pi auch nicht nötig, dass jedes Objekt ein eigenes Datenelement m_pi besitzt. Man könnte also pi auch als statische Variable von Rad definieren !
### Statische Elementfunktionen
| Da ein statisches Datenelement existieren kann ohne, dass es ein Objekt gibt kann man Elementfunktionen (Methoden) definieren, die den Zugriff auf solche Datenelemente ermöglichen. Die Methoden werden auch statisch definiert und können so unabhängig von einem Objekt aufgerufen werden:
    class Rad
    {
        public:
            // Konstruktor mit Parametern
            Rad(long radius);

            static long holeAnzahlRaeder();

        private:
            long         m_radius;
            const double m_pi;
            // diese Zahl gibt es
            // nur einmal, unabhängig
            // der Anzahl Räder die
            // erzeugt werden
            static long  s_anzahlRaeder;
    };

    long Rad::s_anzahlRaeder = 0;

    long Rad::holeAnzahlRaeder()
    {
        return s_anzahlRaeder;
    }

    int main()
    {
        long radius = 6;

        Rad einRad(radius);
        Rad einZweitesRad(radius);

        // statische Methode aufrufen
        long anzahl = Rad::holeAnzahlRaeder();
        // oder wenn es ein Objekt gibt
        anzahl = einRad.holeAnzahlRaeder();

        return 0;
    }
| Hier das main.cpp zum herunterladen, mit dem kompletten Beispiel.
### Klassenspezifische Konstanten, enum
| Mit enum kann ganz einfach eine Aufzählung definiert werden.
    enum AmpelFarbe
    {
        rot,
        gelb,
        gruen
    };

    int main()
    {
        AmpelFarbe farbe;
        farbe = gelb;
        farbe = rot;
        farbe = gruen;

        int test = -1;

        // solche enums können einer
        // Ganzzahl zugewiesen werden
        test = rot;
        test = gelb;
        test = gruen;

        return 0;
    }
| Der erste Wert in einer Aufzählung bekommt den numerischen Wert 0, die folgenden immer den um eins erhöhten des Vorgängers, hier also gelb = 1 und gruen = 2.
| Diese Werte können aber auch beeinflusst werden :
    enum AmpelFarbe
    {
        rot = 10,
        gelb,
        gruen = 15
    };
| "rot" hat jetzt den Wert 10, gelb den Wert 11 und gruen den Wert 15.
| Ein solcher enum kann auch in einer Klassen definiert werden und gehört dadurch zur Klasse. Hier ein komplettes Beispiel dazu (mit download):
    class Ampel
    {
        public:
            enum AmpelFarbe
            {
                rot,
                gelb,
                gruen
            };

            AmpelFarbe getFarbe() const
            {
                return m_farbe;
            }

        private:
            AmpelFarbe m_farbe;
    };

    int main()
    {
        Ampel eineAmpel;

        Ampel::AmpelFarbe farbe = eineAmpel.getFarbe();

        switch(farbe)
        {
            case Ampel::rot:
                // nicht fahren
                break;
            case Ampel::gelb:
                // na ja
                break;
            case Ampel::gruen:
                // fahren
                break;
            default:
                // gibts doch gar nicht
                break;
        }

        return 0;
    }