Konstruktoren & Destruktoren - Blicke nicht durch !



  • Hi,

    wie ihr im Titel ja schon lesen könnt habe ich so meine Probleme mit Konstruktoren und Destruktoren.

    Mit Destruktoren eigentlich weniger, da C++ ja einen Standard-Destruktor zur Verfügung stellt und das erstellen eines Destruktors ist eignetlich auch garnicht so schwer !

    Mein Problem liegt hierbei beim Konstruktor.

    Ich weiss , dass der Konstruktor automatisch beim erzeugen eines Objektes aufgerufen wird, außerdem weiss ich das der Konstruktor den gleichen Namen besitzt wie die Klasse und der Konstruktor darf nicht in dem private Bereich deklariert sein !

    So das Beispielprogramm, zum erklären gebe ich mal vor :

    /*Programm: Bruch*/
    
    #include <iostream.h>
    #include <iomanip.h>
    #include <conio.h>
    
    //Deklaration der Klasse
    class Bruch{
     private:  long zaehler;
               long nenner;
    
     public:   long Zaehler(void);
               long Nenner(void);
               double Wert(void);
               Bruch(long Zaehler, long Nenner); //Konstruktor mit 2 Parametern
               ~Bruch(); //Destruktor (Angabe immer mit Tilde (~))
    };
    
    //Konstruktor mit 2 Parametern
    Bruch::Bruch(long Zaehler, long Nenner){
     zaehler=Zaehler;
     nenner=Nenner;
    }
    
    long Bruch::Zaehler(){
     return(zaehler);
    }
    
    long Bruch::Nenner(){
     return(nenner);
    }
    
    double Bruch::Wert(){
     return(float(zaehler)/float(nenner));
    }
    
    Bruch::~Bruch(){
    }
    
    //Hauptprogramm
    void main() {
    clrscr();
    
    Bruch w(22,7); //Erstellen eines neuen Objektes mit Parameter "22,7" ?
    
    cout<<"Der Zaehler ist: "<<w.Zaehler()<<endl;
    getch();
    
    cout<<"Der Nenner ist: "<<w.Nenner()<<endl;
    getch();
    
    cout<<"fixed<<setprecision(2)<<"Der Wert des Bruch betraegt: "<<w.Wert()<<endl;
    getch();
    }
    

    Kann mir bitte jemand erklären was da vorsich geht ?

    Ich weiß zwar was da deklariert ist , aber sobald die Funktionen wie "long Zaehler(void)" 'gesagt' bekommmen was sie zu tun haben , dann ist es bei mir vorbei 😕

    Bei...

    Bruch::Bruch(long Zahler, long Nenner){
     zaehler=Zaehler;
     nenner=Nenner;
    }
    

    ... bekommt die Variable nenner und zaehler doch den Wert von Nenner und Zaehler zugewiesen , oder ?
    Wieso brauch ich da einen Konstruktor ?
    Hätte ich nicht über eine weitere Funktion die Parameter übergeben können ?

    In...

    cout<<"Der Zaehler ist: "<<w.Zaehler()<<endl;
    getch();
    
    cout<<"Der Nenner ist: "<<w.Nenner()<<endl;
    getch();
    

    ... wird der Wert von den Variablen Zaehler und Nenner ausgegeben ?

    Und in ...

    cout<<"fixed<<setprecision(2)<<"Der Wert des Bruch betraegt: "<<w.Wert()<<endl;
    getch();
    

    ... wird der Wert über das Objekt 'w' der Wert von 'Wert' ausgegeben über ...

    double Bruch::Wert(){
     return(float(zaehler)/float(nenner));
    }
    

    ... ausgegeben ?

    Für mich ist das ziemich undurchsichtlich und unübersichtlich.
    Bei komplexeren Aufgaben werde ich da wohl meine Probleme haben!

    Gruß,
    Tuoni



  • Tuoni schrieb:

    der Konstruktor darf nicht in dem private Bereich deklariert sein !

    du hast die falsche Information 😃



  • Der konstruktor wird größtenteils dazu verwendet membervariablen der Klasse zu initialisieren. Der Destruktor dient zum Beispiel zum freigeben von allokiertem Speicher



  • der Konstruktor darf nicht in dem private Bereich deklariert sein !

    Ohne Prinzipienreiter sein zu wollen: Das ist soo nicht richtig!
    Bei "gewöhnlichen" Objekten macht es nur einfach keinen Sinn.
    Bei speziellen Software-Entwurfsmustern (z. B. das Singleton) kann es jedoch
    sehr wohl erforderlich sein, einen Konstruktor als private zu deklarieren.

    Bei...

    Bruch::Bruch(long Zahler, long Nenner){
     zaehler=Zaehler;
     nenner=Nenner;
    }
    

    Weiter empfiehlt sich die Werte für die Klassenvariablen "zaehler" und "nenner" wie folgt zuzuweisen:

    Bruch::Bruch(long Zahler, long Nenner) : zaehler(Zahler), nenner(Nenner) {}
    

    Das solltest Du so immer machen. Insbesondere dann, wenn Du Objekte (z. B. std::string) bzw. Objektreferenzen als Konstruktor-Parameter verwendest.

    ... bekommt die Variable nenner und zaehler doch den Wert von Nenner und Zaehler zugewiesen , oder ?
    Wieso brauch ich da einen Konstruktor ?
    Hätte ich nicht über eine weitere Funktion die Parameter übergeben können ?

    Richtig, die Klassenvariablen "zaehler" und "nenner" bekommen durch den Konstruktor ihren Wert zugewiesen. Und das ist der Punkt.
    Wenn Du ein Objekt vom Typ "Bruch" erzeugst, ist ohne die Zuweisung konkreter Werte zu den Klassenvariablen, der Wert der Klassenvariablen unbestimmt.
    Wenn Du keine unmittelbare Wertzuweisung benötigst kannst zu ja z. B. zusätzlich noch folgenden Konstruktor in Deine Klasse implementieren:

    Bruch::Bruch() : zaehler(0), nenner(0) {}
    

    "zaehler" und "nenner" bekommen bei dieser Konstruktorvariante jeweils die Werte 0 (=Zero) verpasst.

    Und - klar hättest Du auch die Werte auch über eine Funktion übergeben können. Ob das sinnvoll ist, hängt von Deinem Programmdesign oder dem Verwendungszweck Deiner Klasse ab.

    Und jetzt - noch alles klar bei Dir oder hast Du mehr Fragen als zuvor? 🤡

    H.-Gerd

    http://www.brainsandbytes.de



  • Hi,

    O.K. ich hoffe das ich das jetzt einigermaßen begriffen habe 🙂
    Das wird sich ja dann sicherlich spätestens morgen zeigen !
    Habe ja noch ein paar Bsp.-Programme die ich durchgehen kann , damit sich das endlich in mein Gehrin 'einbrennt' 🙂

    Danke für eure Hilfe !

    Bis denne,
    Tuoni



  • So würde ich Deine Klasse aufbauen:

    //Deklaration der Klasse
    // --- Snip (Bruch.h) --------
    class Bruch{
      private:  
        long zaehler;
        long nenner;
    
      public:   
        Bruch();
        Bruch(long Zaehler, long Nenner);
        ~Bruch(){}
    
        // Mit einer inline-Deklaration kannst Du die Implementierung im Header machen. 
        // Aber nur bei sehr, sehr kurzen Anweisung - wie hier - verwenden.
        // Das const zeigt nur, dass sich durch den Methodenaufruf am Zustand
        // Deiner Klasse nichts verändert. Also dass sich dadurch z. B. die Werte
        // von zaehler und nenner nicht verändern.
        inline long getZaehler() const { return zaehler; }
        inline long getNenner() const { return nenner; }
    
        double getWert() const;
    };
    
    // Implementierung
    // --- Snip (Bruch.cpp) --------
    // Konstruktor ohne Parameter
    Bruch::Bruch() : zaehler(0), nenner(0) {}
    
    //Konstruktor mit 2 Parametern
    Bruch::Bruch(long Zaehler, long Nenner) : zaehler(Zaehler), nenner(Nenner) {}
    
    double Bruch::getWert() const {
      return (double)zaehler / (double)nenner;
    }
    

    Gruß

    H.-Gerd

    http://www.brainsandbytes.de



  • die deklaration des standardkonstruktors

    Bruch::Bruch() : zaehler(0), nenner(0) {}
    

    ist gefährlich, da es hier zu einer division durch null kommen kann.

    das sollte man in der methode getWert() sowieso abfangen

    gruß horst


  • Mod

    irgendwie macht der default constructor so keinen sinn. der zweck des konstruktors ist schliesslich, das objekt in einen konsistenten und sinnvollen zustand zu versetzen. mit einem default-konstruierten objekt wie oben kann man nicht vernünftig rechnen. 0.0/0.0 ist nun mal NaN. da es sich offenbar um einen konkreten datentyp handelt, sollte er sich doch möglichst wie ein built-in typ verhalten.
    mir erscheint es hier sinnvoller, dem constructor default parameter mitzugeben, so dass man zum beispiel auch normale integer zur initialisierung benutzen kann.
    ausserdem ist inline in der klassendefinition redundant. insofern macht es die angelegenheit unübersichtlich.

    //Definition der Klasse
    // --- Snip (Bruch.h) --------
    class Bruch
    {
    // Interface
    public:
        // Constructor
        Bruch(long Zaehler = 0, long Nenner = 1);
        // Destructor
        ~Bruch() {}
    
        // Operationen
        long getZaehler() const { return zaehler; }
        long getNenner() const { return nenner; }
    
        double getWert() const;
    
    // Implementation
    private:  
        long zaehler;
        long nenner;
    };
    
    // Implementierung
    // --- Snip (Bruch.cpp) --------
    // Konstruktor
    Bruch::Bruch(long Zaehler = 0, long Nenner = 1) : zaehler(Zaehler), nenner(Nenner)
    {
    }
    
    double Bruch::getWert() const
    {
      return (double)zaehler / (double)nenner;
    }
    


  • trotzdem sollte in der methode getWert() eine division durch 0 vermieden werden

    double Bruch::getWert() const
    {
      if (nenner == 0)
      {
         return (double)0;
      }
    
      return (double)zaehler / (double)nenner;
    }
    

  • Mod

    Horst2 schrieb:

    trotzdem sollte in der methode getWert() eine division durch 0 vermieden werden

    double Bruch::getWert() const
    {
      if (nenner == 0)
      {
         return (double)0;
      }
    
      return (double)zaehler / (double)nenner;
    }
    

    warum? wenn es sich nicht um integrable typen handelt, ist es eigentlich semantisch falsch, eine division durch 0 durch rückgabe von 0 verhindern zu wollen. im übrigen ist der zustand nenner==0 durchaus grundverschieden von dem zustand zähler==0 - wenn überhaupt, dann wäre dort eine exception angebracht. andererseits handelt es sich um eine const methode, diese sollte sich darauf verlassen können, dass das objekt, für das sie aufgerufen wird, sich in einem gültigen zustand befindet (sofern nenner==0 überhaupt als ungültig ansehen will), denn es ist aufgabe jeder methode, die das objekt verändert, dies sicherzustellen. dann müsste folgerichtig eine exception dort stattfinden, wo der ungültige zustand entsteht bzw. entstehen würde.



  • camper schrieb:

    Horst2 schrieb:

    trotzdem sollte in der methode getWert() eine division durch 0 vermieden werden

    double Bruch::getWert() const
    {
      if (nenner == 0)
      {
         return (double)0;
      }
    
      return (double)zaehler / (double)nenner;
    }
    

    warum? wenn es sich nicht um integrable typen handelt, ist es eigentlich semantisch falsch, eine division durch 0 durch rückgabe von 0 verhindern zu wollen. im übrigen ist der zustand nenner==0 durchaus grundverschieden von dem zustand zähler==0 - wenn überhaupt, dann wäre dort eine exception angebracht. andererseits handelt es sich um eine const methode, diese sollte sich darauf verlassen können, dass das objekt, für das sie aufgerufen wird, sich in einem gültigen zustand befindet (sofern nenner==0 überhaupt als ungültig ansehen will), denn es ist aufgabe jeder methode, die das objekt verändert, dies sicherzustellen. dann müsste folgerichtig eine exception dort stattfinden, wo der ungültige zustand entsteht bzw. entstehen würde.

    ok



  • Hey,

    also , mhmm ... wenn ich mir das so durchlese , dann verstehe ich nur Bahnhof !
    Bin mittlerweile total irretiert durch die ganzen Fachwörtern wie Exception oder integrable Typen !

    Brauche nur die Deklaration von Konstruktoren, da ich das bei der Ausbildung noch nicht wirklich verstanden habe !

    Bis denne,
    Tuoni


  • Mod

    jede klasse, von der man objeklte erstellen kann, besitzt (wenigstens) einen konstruktor. der sinn des konstruktors ist es, das objekt in einen definitierten, und daher nutzbaren zustand zu bringen. wenn man keinen konstruktor definiert, erstellt der compiler einen, der dafür sorgt, dass alle (nichtstatischen) member der klasse defaultinitialisiert werden (das heisst, deren defaultkonstruktor wird aufgerufen). etc. etc. ich empfehle ein gutes buch oder tutorial zu lesen, ist einfach zuviel, um es hier zu schreiben.


Anmelden zum Antworten