Frage zu Konstruktor einer abgleiteten Klasse



  • Hallo zusammen,
    ich habe ein Frage bezüglich Vererbung. Wenn ich eine Ursprungsklasse erstellt habe und dann eine davon abgeleitete Klasse erstelle, wird dann automatisch der Konstruktor der Ursprungsklasse aufgerufen. Dazu habe ich mal folgendes Beispielprogramm
    geschrieben.

    class CKlasse       //Ich deklariere eine Klasse namens "CKlasse"
    {
     public:
            CKlasse(char* Wort1, char* Wort2);    
            virtual void Speichern (char*,char*); 
            virtual void Anzeigen ();  
     protected:
            char array1[255];                      
            char array2[255];
    };
    //---------------------------------------------------------------------------
    class CUnterklasse : public CKlasse 
    {
     public:
            CUnterklasse(char* NeuWort, char* Wort1, char* Wort2); 
            void Speichern (char*);  
            void Anzeigen  ();
     protected:
            char array3[255];  
    };
    
    CKlasse::CKlasse(char* word1, char* word2)
    {
     Speichern(word1, word2); 
    }
    //---------------------------------------------------------------------------
    void CKlasse::Speichern (char* erstes_wort, char* zweites_wort)
    {
     strcpy(this->array1, erstes_wort);
     strcpy(this->array2, zweites_wort); 
    }
    //---------------------------------------------------------------------------
    void CKlasse::Anzeigen ()
    {
     cout << "Oberklasse: " << array1 << endl;
     cout << "Oberklasse: " << array2 << endl;
    }
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    CKlasse::CKlasse(char* word1, char* word2)
    {
     Speichern(word1, word2); 
    }
    //---------------------------------------------------------------------------
    void CKlasse::Speichern (char* erstes_wort, char* zweites_wort)
    {
     strcpy(this->array1, erstes_wort);
     strcpy(this->array2, zweites_wort); 
    }
    //---------------------------------------------------------------------------
    void CKlasse::Anzeigen ()
    {
     cout << "Oberklasse: " << array1 << endl;
     cout << "Oberklasse: " << array2 << endl;
    }
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    CUnterklasse::CUnterklasse(char* NeuWort, char* Wort1, char* Wort2) : CKlasse(Wort1, Wort2)
    {
     Speichern(NeuWort);                             //werden die Variablen Wort1 und Wort2 hier an CKlasse übergeben
    }
    //----------------------------------------------------------------------------
    void CUnterklasse::Speichern (char* NeuWort)
    {
     strcpy(this->array3,NeuWort);  
    }
    //----------------------------------------------------------------------------
    void CUnterklasse::Anzeigen()
    {
     cout << "Unterklasse: " << array3 <<endl; 
     CKlasse::Anzeigen();
    }
    //----------------------------------------------------------------------------
    int main()
    {
     CKlasse Instanz1("Max", "Mustermann");
     Instanz1.Anzeigen();
    
     CUnterklasse UnterInstanz1("Familie", "Mustermann", "6 Personen"); 
     UnterInstanz1.Anzeigen(); //"Mustermann" und "6 Personen" sollen nicht an   CKlasse übergeben werden
    return 0;
    }
    

    So, wenn ich nun eine Instanz der Unterklasse erstelle, dann wird nur das Wort "Familie" von der Unterklasse bearbeitet. Die Wörter "Mustermann" und "6 Personen" werden anscheinend an den Konstruktor der Ursprungsklasse übergeben, was ich daran sehen kann, dass die Ausgabe mit "Oberklasse: " beginnt. Nun meine Fragen:

    1.) Werden beim Aufruf des Konstruktors der Unterklasse die Variablen Wort1 und Wort2 an den Konstruktor der Oberklasse CKlasse übergeben? Eigentlich weiß ich die Antwort ja schon (sie lautet JA), da die Ausgabe der Wörter "Mustermann" und "6 Personen" mit "Oberklasse: " beginnen. Ist das denn immer so, dass die übergebenen Parameter an die Unterklasse automatische an die Oberklasse weitergegeben werden?

    2.) Wenn ich nicht möchte, dass die Variablen an CKlasse übergeben werden, sondern nur von CUNterklasse bearbeitet werden, dachte ich, dass ich die Variablen einfach umbenenne. Dann kommt jedoch ein Fehler:

    CUnterklasse::CUnterklasse(char* NeuWort, char* NeuWort1, char* NeuWort2) : CKlasse(Wort1, Wort2)
    {
     Speichern(NeuWort, NeurWort1, NeuWort2); //So wollte ich bezweken, dass die VAriablen nicht an CKLasse übergeben werden, doch kommt ein Fehler!
    } //Die Methode "Speichern" wird natürlich angepasst.
    

    Wie kann ich dies also erreichen, da es ja schon irgendwie blöd wäre, wenn Variablen für die Unterklasse automatisch an die Oberklasse weitergegebn werden.

    Vielen Dank für eure Hilfe,
    lg, freakC++



  • Da CKlasse keinen Default-Cosntructor hat und Du keinen anderen Constructor über die Initialisierungsliste in CUnterKlasse::CUnterKlasse aufrufst, sollte Dein Programm eigentlich gar nicht erst kompilieren.

    class A {
    public:
       A(int,int) {}
    };
    
    class B : A {
       B();
    }
    
    B::B()
    : A(23,42)  // <-- Konstruktor von A muss aufgerufen werden
    {
    }
    

    Wenn Du keinen Konstruktor in der Initialisierungsliste (das mit dem Doppelpunkt) aufrufst, versucht der Compiler automatisch den Default-Constructor aufzurufen, den es aber in diesem Fall nicht gibt.

    Übrigens: Der Speichern-Aufruf innerhalb CKlasse::CKlasse ist immer ein Aufruf von CKlasse::Speichern -- auch wenn das Objekt vom Typ CUnterKlasse ist. Das gleiche gilt für virtuelle Funktionen, die Du innerhalb des Destruktors aufrufst.

    Wenn Du also ein CUnterKlasse-Objekt erzeugst und wieder zerstörst läuft das so ab:
    1. Konstruktor von CKlasse wird aufgerufen. Das Objekt verhält sich währenddessen wie ein CKlasse und nicht wie ein CUnterKlasse. Nach Beendigung dieses Konstruktors ist das Objekt vom Typ CKlasse.
    2. Der Konstruktor von CUnterKlasse wird aufgerufen. Das Objekt verhält sich dann wie ein CUnterKlasse bzgl virtuellen Funktionen. Nach Beendigung des Konstruktors ist das Objekt vom Typ CUnterKlasse.
    3. Der Destruktor von CUnterKlasse wird aufgerufen. Ein Aufruf von Speichern würde zu CUnterKlasse::Speichern führen
    4. Der Destruktor von CKlasse wird aufgerufen. Ein Aufruf von Speichern innerhalb dieses Destruktors würde zu CKlasse::Speichern führen.

    Mit der Initialisierungsliste kannst Du steuern, welcher Konstruktor der Basisklasse mit welchen Parametern ausgeführt werden soll. Die Initialisierungsliste erlaubt Dir aber auch Elemente des Objektes zu initialisieren.



  • Hallo,
    vielen Dank für die Antwort. Das Programm ist fehlerfrei, das heißt es lässt sich kompilieren.

    Dennoch verstehe ich die Angelegenheit noch nicht ganz.

    Vielen Dank für eure Hilfe
    lg, freakC++



  • freakC++ schrieb:

    [...] das heißt es lässt sich kompilieren.

    Nachdem man

    #include <iostream>
    #include <string.h>
    using namespace std;
    

    hinzufügt und die Mehrfach-Definitionen entfernt, kompiliert das Programm mit 5 Warnungen der Art

    Warnung: veraltete Konvertierung von Zeichenkettenkonstante in »char*«
    

    Ich hatte Dein : CKlasse(Wort1,Wort2) am Ende von CUnterKlasse::CUnterKlasse(...) übersehen.

    freakC++ schrieb:

    Ist das denn immer so, dass die übergebenen Parameter an die Unterklasse automatische an die Oberklasse weitergegeben werden?

    Nein. Nie automatisch. Dafür sind ja die Initialisierungslisten da.

    freakC++ schrieb:

    Wenn ich nicht möchte, dass die Variablen an CKlasse übergeben werden, sondern nur von CUNterklasse bearbeitet werden, dachte ich, dass ich die Variablen einfach umbenenne. Dann kommt jedoch ein Fehler:

    CUnterklasse::CUnterklasse(char* NeuWort, char* NeuWort1, char* NeuWort2) : CKlasse(Wort1, Wort2)
    {
     Speichern(NeuWort, NeurWort1, NeuWort2); //So wollte ich bezweken, dass die VAriablen nicht an CKLasse übergeben werden, doch kommt ein Fehler!
    } //Die Methode "Speichern" wird natürlich angepasst.
    

    Was glaubst Du, was " : CKlasse(Wort1, Wort2) " macht? Es ist Teil der Konstruktordefinition. Du sagst hier, dass das CKlasse-Subobjekt mit dem Parametern Wort1 und Wort2 initialisiert werden soll, bevor der Rest ausgeführt wird. Ich habe das Gefühl, dass Dir das Konzept der Initialisierungslisten bei Konstruktoren fremd ist.

    Dazu kommst, dass Du Zeiger vom Typ char* benutzt, um Stringkonstanten zu referenzieren statt mit const char* und std::string zu arbeiten. Du wirst wesentlich weniger Ärger mit std::string haben, als mit rohen char-Zeigern und Arrays. Beispielsweise gibt es bei Deinem Programm ein Problem, falls eine der Zeichenkettenkonstanten in main() mehr als 254 Zeichen besitzt.

    Außerdem, ergibt es nicht viel Sinn Speichern als virtuell zu deklarieren. CUnterklasse::Speichern überschreibt jedenfalls nicht CKlasse::Speichern , da CUnterklasse::Speichern nur einen Parameter erwartet. Stattdessen "versteckt" diese Funktion die andere virtuelle aus der Basisklasse.


Anmelden zum Antworten