Klassen / Abgeleitete Klassen / Basisinitialisierer



  • Hallo!

    Ich glaube so langsam, dass ich nun den Großteil der Klassen verstanden habe.
    Hier jedoch noch ein paar Fragen zum Verständnis anhand dieses Beispiels:

    Beispiel:

    class Produkt
    {
      private:
        int id, preis;
        string name, beschreibung;
      public:
        void Produkt(int id2);
        void ~Produkt();
    };
    
    Produkt::Produkt(int id2)
    {
      id = id2;
      if(id==111)
        {
          preis = 30;
          name = "Buch";
          beschreibung = "Dickes Ding mit vielen Seiten";
        }
       else
        {
          preis = 50;
          name = "Spiel";
          beschreibung = "Freizeitbeschäftigung - mit Spaß verbunden";
        }
    }
    
    Produkt::~Produkt()
    {
    }
    
    class Buch : public Produkt
    {
      private:
        string verlag, autor;
      public:
        void Buch(int id2);
        void ~Buch();
    }
    
    Buch::Buch(int id2) : Produkt(int id2)
    {
      if(id2==111)
        {
          verlag = "Galileo-Computing";
          autor = "Max";
        }
       else
        {
          verlag = "Hanser Verlag";
          autor = "Moritz";
        }
    }
    
    Buch::~Buch()
    {
    }
    

    Frage1:
    Wenn ich nun den Konstruktor Buch::Buch aufrufe, dann wird doch automatisch auch der Konstruktor von Produkt aufgerufen, da ich ja hier diesen Basisinitialisierer ( : Produkt(int id2)) an den Buch-Konstruktor angehängt habe, oder?

    Frage2:
    Brauche ich dann nun überhaupt noch den Destruktor Buch::~Buch? Reicht da nicht allein der Basisklassendestruktor Produkt::~Produkt??

    Frage3:
    Muss ich, falls ich den Basisinitialisierer verwende, exakt die selben Parameter übergeben wie wenn ich ihn direkt aufrufe?

    Frage4:
    Kann man bei der Deklaration der Konstruktoren bzw. Destruktoren das "void" nicht weglassen, da diese sowieso keine Rückgabewerte haben dürfen?

    Frage5:
    Könnte ich nun in der Klasse Buch dem Objekt auch eine Eigenschaft mit dem Namen id zuweisen, obwohl die Basisklasse schon diese Eigenschaft hat? Dann hätte mein Buch ja zwei ids?

    Ich hoffe auf eine Antwort auf alle 5 Fragen! 😋

    Viele Grüße
    Morx



  • 1. Ja.

    2. Nein. Eben nicht. Schau dir mal virtual an.

    3. Nein. Müssen nicht, aber macht meist Sinn.

    4. Unbedingt weglassen.

    5. Ja. Kannst du. Den "inneren" kannst du dann mittels Produkt::id ansprechen (innerhalb von Buch).

    War jetzt leider nicht EINE Antwort, aber 5 sind auch nicht schlecht. 😉



  • @drakon:
    WOW! Das ist ja LIVE-SUPPORT pur!!
    Prima! Vielen Dank für die Antworten!!! 👍

    War jetzt leider nicht EINE Antwort, aber 5 sind auch nicht schlecht.

    Oh 😃

    Schönen Abend noch!

    Morx



  • Dieser Support ist echt mies...
    So schnell, wie der antwortet kann ich gar nicht antworten, geschweige denn Postings sinnvoll füllen...


  • Administrator

    Quellcode schrieb:

    Dieser Support ist echt mies...
    So schnell, wie der antwortet kann ich gar nicht antworten, geschweige denn Postings sinnvoll füllen...

    Obwohl das zwar jetzt Off-Topic ist, so muss ich dir zustimmen. Es kam schon sehr oft vor, dass ich ein Post verfasst habe und in der Vorschau gesehen habe, dass jemand anderes bereits geantwortet hat. Die ganze Arbeit für nix.

    Manchmal kommt es mir fast wie ein Wettrennen vor. Wer schickt die erste Hilfe ab 😃

    Aber zu Frage 2 noch was:
    Jede Klasse hat immer einen "Default-Destruktor". Falls du wirklich etwas freigegen möchtest, was von der Klasse Buch angefordert wurde, dann brauchst du natürlich einen Buch Destruktor.
    Das virtual vor dem Produkt Destruktor ist eigentlich nur dazu da, dass wenn ein Produkt zerstört wird, auch die Destruktoren (bzw. zuerst), von den abgeleiteten Klassen aufgerufen werden.

    Grüssli



  • Werden die Default-Destruktoren (der Unterklssen) eigentlich immer aufgerufen beim Zerstören der Oberklassen?



  • Hallo nochmal! 🙄

    Ok ein paar Fragen sind mir noch eingefallen (falls du noch nicht die Lust verloren hast^^):

    Frage1:
    Wenn von Dynamischen Klassenobjekten/Elementen die Rede ist, an welches Stichwort muss ich da denken:
    Zeiger/Referenzen/Heap ??

    Frage2:
    Wie muss ich mir denn einen Zeiger im Speicher vorstellen?

    Bei Variablen sieht ein Speichereintrag ja ca. so aus:

    adresse|wert|name

    Aber wie sieht er bei Zeigern aus?
    Zeiger können ja schließlich auch auf mehrere Dinge zeigen wie zum Beispiel auf ganze Objekte, und jede Eigenschaft/Methode muss ja dann ihren eigenen "Unterzeiger" im Zeiger haben, oder?

    Wie sieht das dann aus im Speicher wenn ich einen Zeiger auf mein Objekt Buch (id,preis,name,beschreibung,autor,verlag) ??

    Und wenn wir schon dabei sind: Wie sieht ein Speichereintrag von einer Referenz aus? Denn ich habe den Unterschied zwischen Referenzen und Zeigern noch nicht ganz verstanden? Haben Referenzen und deren Variablen den selben Speicher, oder die selbe Adresse oder den selben Wert oder beides oder was 😮 😮 😕 ??

    Frage3:
    Und warum höre ich ständig von dem Begriff Heap, sobald man das Thema "Dynamische..." anspricht? Was genau bedeutet denn dynamisch??

    Wenn ich nun auf diese 3 Fragen auch noch eine Antwort bekommen würde, wäre ich wunschlos glücklich!! 😃

    Viele Grüße
    Morx



  • 1. Was du vlt. suchst ist new /delete. Mit diesen Operatoren kannst du Speicher dynamisch anfordern, was soviel beduetet, dass DU dich um das aufräumen kümmern musst. Sprich, wenn du den Speicher nicht mehr brauchst auch wieder mit delete/delete[] freigeben. (Hier wäre noch das Stichwort smartpointer erwähnt, welches du später einmal noch kennenlernen solltest). Die meisten Implementierungen benutzen für dynamische Objekte den sogenannten Heap, aber um das musst du dich nicht kümmern. Du bekommst einfach einen Zeiger auf den angeforderten Speicher.

    2. Ein Zeiger ist lediglich eine Adresse, wo dein wirkliches Objekt im Speicher ist.

    3. Wie schon gesagt ist der Heap der Bereich im RAM, der meist von dynamisch angefordertem Speicher benutzt wird und nicht der sog. Stack. In C++ gibt es diese Begriffe aber nicht. Dort gibt es nur Speicher, um den du dich selber kümmern musst und Speicher, um den du dich nicht selber kümmern musst.


  • Administrator

    Frage 1:

    CKlasse Objekt; // <- zur Compilezeit bekannt und auf dem Stack.
    CKlasse* pObjekct = new CKlasse(); // Dynamisches Objekt, da es zur Laufzeit erzeugt werden kann.
    

    Frage 2:
    Also so sieht die Variable im Speicher definitiv nicht aus!
    Gespeichert wird nur der Wert und zwar an der Stelle der Adresse. Du hast einen Integer irgendwo, dann hast du 4 Bytes irgendwo mit dem Wert drin. Die Adresse definiert diesen Ort im Speicher.

    Ein Zeiger ist ein 4 Byte Wert (32-Bit System) oder 8 Byte Wert (64-Bit System), welcher die Adresse zu einem Objekt speichern kann.

    Frage 3:
    Wenn du ein Objekt per new allokierst, dann kommt es auf den Heap. Der Stack ist im allgemeinen äusserst klein, ca. 1 - 2 MB, dafür sehr schnell. Der Heap umfasst dagegen eigentlich dein ganzer verfügbarer Hauptspeicher. Dafür ist er allerdings auch etwas langsamer. Wenn man zur Laufzeit Objekte allokieren will, müssen diese auf den Heap.

    Grüssli



  • Ah! OK schonmal vielen DANK!!

    2. Ein Zeiger ist lediglich eine Adresse, wo dein wirkliches Objekt im Speicher ist.

    Aber wie kann man dann mit nur einer Adresse viele weitere Adressen (die Eigenschaften des Objekts) ansprechen?? Hat das dann nicht den selben Effekt wie wenn ich das Objekt "persönlich" anspreche?

    Und wenn ein Zeiger nur eine Adresse zu einem Objekt etc. hat, könnte man dann nicht auch einfach einer normalen Variable die Adresse des Objekts zuweisen? Wenn nicht, was macht dann der Zeiger anders als eine Variable mit der Adresse und wie erkennt der Computer ob das im Speicher jetzt ein Zeiger oder eine Variable ist???

    Morx



  • Frage1:
    Wenn ich nun den Konstruktor Buch::Buch aufrufe, dann wird doch automatisch auch der Konstruktor von Produkt aufgerufen, da ich ja hier diesen Basisinitialisierer ( : Produkt(int id2)) an den Buch-Konstruktor angehängt habe, oder?

    Es wird auch automatisch ein Konstruktor aufgerufen wenn du den Basisklassen Konstruktor NICHT in der initializer-list angibst. In dem Fall wird halt der "default constructor" aufgerufen, also der der ohne Parameter auskommt. Wenn es den nicht gibt bekommst du entsprechend einen Fehler beim compilieren.

    Frage2:
    Brauche ich dann nun überhaupt noch den Destruktor Buch::~Buch? Reicht da nicht allein der Basisklassendestruktor Produkt::~Produkt??

    In deinem konkreten Beispiel brauchst du garkeine selbst definierten Destruktoren. Einen Destruktor kannst du grundsätzlich immer weglassen wenn er leer ist, AUSSER du brauchst in einer Klasse einen virtuellen Destruktor, und diese Klasse hat keine Basisklasse die schon einen virtuellen Destruktor hätte.

    Frage3:
    Muss ich, falls ich den Basisinitialisierer verwende, exakt die selben Parameter übergeben wie wenn ich ihn direkt aufrufe?

    Wie willst du "ihn" denn überhaupt "direkt" aufrufen? Und wer ist "er"? Einen Konstruktor, falls du den meinst, kannst du nicht "direkt" aufrufen. Von daher erübrigt sich die Frage, nicht?

    Frage4:
    Kann man bei der Deklaration der Konstruktoren bzw. Destruktoren das "void" nicht weglassen, da diese sowieso keine Rückgabewerte haben dürfen?

    Das void ist sogar falsch an der Stelle. Daher wurde auch "unbegint weglassen" geschrieben.

    Frage5:
    Könnte ich nun in der Klasse Buch dem Objekt auch eine Eigenschaft mit dem Namen id zuweisen, obwohl die Basisklasse schon diese Eigenschaft hat? Dann hätte mein Buch ja zwei ids?

    Wie schon geschrieben wurde kann man das. Sollte man aber nicht, ist viel zu verwirrend und unübersichtlich. Also nur wenns unbedingt sein muss, und mir fällt gerade kein Grund ein warum es irgendwo unbedingt sein müsste.



  • @hustbaer:
    Jepp DANKE nochmal für die Erläuterung.
    Wenn du mir jetzt noch Antworten auf meine letzte Frage hättest, wäre alles perfekt! 🙂

    Grüße
    Morx



  • Wie willst du "ihn" denn überhaupt "direkt" aufrufen? Und wer ist "er"? Einen Konstruktor, falls du den meinst, kannst du nicht "direkt" aufrufen. Von daher erübrigt sich die Frage, nicht?

    na klar kann man direkt aufrufen, und zwar bei der basisinitialisierung, oder ist das indirekt?

    class A
    {
        private: int wert;  
        public: A(int w):wert(w);
    }
    
    class B : public A
    {
        private: int wert2;
        public: B(int w1, int w2):wert2(w2),A(w1);
    }
    

Log in to reply