Frage zur Schreibweise innerhalb einer Klasse



  • Hallo Liebe Leute,

    ich bin neu im Forum und lerne gerade C++.

    Ich habe eine Frage zur Schreibweise von Klassen.

    Ich habe folgenden Code abschnitt, dessen Schreibweise ich nicht so recht verstehe.

    public:
    mitarbeiter(string v, string n, int nr, float g):
    vorname(v),
    nachname(n),
    nummer(nr),
    gehalt(g)
    {}
    

    Wofür ist der Doppelpunkt gedacht?

    Warum kann man es nicht so schreiben:

    public:
    mitarbeiter (string vorname, string nachname, int nummer, float gehalt);
    

    Der Rest vom Code ist mir klar. Bloß wo ist der Unterschied mit dem : und den nachgestellten Variablen?

    Vielen Dank für Eure Hilfe !

    string getVorname ()
    {
    return vorname;
    }
    string getNachname ()
    {
    return nachname;
    }
    int getNummer ()
    {
    return nummer;
    }
    float getGehalt ()
    {
    return gehalt;
    }
    void setGehalt(float g)
    {
    gehalt = g;
    }
    };
    
    int main ()
    {
    mitarbeiter meinMitarbeiter = mitarbeiter
    ("Sebastian", "Becker", 10011, 2400);
    cout << meinMitarbeiter.getVorname() << endl;
    cout << meinMitarbeiter.getNachname() << endl;
    cout << meinMitarbeiter.getNummer() << endl;
    cout << meinMitarbeiter.getGehalt() << endl;
    


  • Das ist die sogennante Initialisierungsliste.
    Damit werden die Membervariablen der Klasse im Konstruktor initialisiert.
    Dadurch wird deren repsektiver Konstruktor verwendet der die Parameter nimmt.

    Dies ist sinnvoller als sie erst leer zu erstellen und dann zuzuweisen.

    EDIT: Vielleicht hast du schonmal den rat erhalten jede Variable bei Deklaration auch direkt zu initialisieren.

    int a = 0;
    
    // statt
    int a;
    
    // vergessen dass man a kein wert verpasst hat und a benutzen = autsch, weil a nicht garantiert 0 ist, außer sie ist global.
    a = a + 2;
    

    Dies kann auch auf die Member der Klasse übertragen werden:
    Jeder Member eine Klasse sollte idealerweise in der Initialisierungsliste auftauchen, dann hat man sich wenigstens einmal den Gedanken gemacht was Initialer Wert und Zustand der Variable ist.
    (EDIT 2: caveat: es sei den man initialisert inline

    struct A
    {
        int mem = 0; // valid C++
    }
    

    )
    Die Reihenfolge der Variable in der Initialisierungliste sollte der Reihenfolge der Deklaration in der Klasse entsprechen, da es sich nach letzterem richtet in welcher Reihenfolge sie erstellt werden.



  • @5cript Wow danke für die Schnelle Antwort.

    Das habe ich sogar auch schon probiert.

    So:

    mitarbeiter (string vorname="Manfred", string nachname="Mueller", int nummer=123, float gehalt=10000);



  • @bthight
    Ich hoffe darauf folgt noch die Initialisierungsliste wie oben.
    Weil das sind erstmal nur Default Parameter. dadurch kannst du mitarbeiter ohne Angabe von Vornamen etc erstellen.

    Allerdings ist das hier gar nicht mal so klug.

    Einen Mitarbeiter mit Default Namen erstellen können, könnte dazu führen dass du versehentlich ganz viele Manfreds erstellst, aber das gar nicht beabsichtigst.



  • @5cript Das ist korrekt. Ich wollte aber zu erst mir den Default einfach nur per cout ausgeben lassen. Aber so wie ich die Zeile geschrieben habe kommt mein Inhalt nicht raus.

    mit meinMitarbeiter.setvorname("blabla") wollte ich das nachher einfach ändern.



  • @bthight

    Dann lieber ein leeren Namen. Das fällt eher auf. (Oder ein anderer invalider Platzhalter.)

    EDIT: Auch wenn man noch über alternativen reden kann dazu, aber ich glaube zu fortgeschrittene Sachen jetzt anzufangen ist nicht Sinnvoll bei dem Stand den ich bei dir vermute.



  • @5cript Danke für deine Hilfe. Nein mehr macht keinen Sinn.

    Die Syntax von C++ ist mir einfach noch nicht geläufig genug.
    Es gibt gefühlt 100 Schreibweisen, für das selbe Ziel.



  • @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    Vergleiche:

    struct Mitarbeiter1 {
       string name;
       Mitarbeiter1(string name) : name(name) {}
    };
    
    struct Mitarbeiter2 {
       string name;
       Mitarbeiter2(string name) { this->name = name; }
    };
    
    struct Mitarbeiter3 {
       string name;
       Mitarbeiter3(string name="Manfred") : name(name) {}
    };
    

    1 und 2 haben das gleiche Ergebnis. Von der Logik her wird bei 1 direkt der Copy-Constructor von string aufgerufen, um das "name"-Objekt zu erzeugen. Bei 2 wird dagegen erstmal der Default-Constructor von string aufgerufen und erzeugt einen leeren String. Danach wird operator= aufgerufen und erledigt die Zuweisung.
    Bei 3 bekommt dein Mitarbeiter automatisch den Namen "Manfred", wenn du ein Mitarbeiter-Objekt ohne Parameter erzeugst.
    Beachte, dass bei 1 name(name) zwei verschiedene Namen sind, nämlich membername(parametername) - auch wenn hier 2x name verwendet werden darf. Also ist Mitarbeiter1(string xxx) : name(xxx) das gleiche wie das Beispiel 1.

    PS: std::string darf man auch als const& übergeben - oder hier könnte man mit ...name(std::move(name)) auch den String moven, wenn man ihn im Parameter kopiert.



  • @wob sagte in Frage zur Schreibweise innerhalb einer Klasse:

    der Logik her wird bei 1 direkt der Copy-Constructor von string aufgerufen, um das "name"-Objekt zu erzeugen. Bei 2 wird dagegen erstmal der Default-Constructor von string aufge

    Danke dafür - ich merke ich habe noch Schwierigkeiten den Aufbau des Konstruktors zu verstehen und den genauen Ablauf dessen.



  • @bthight

    Ich habe nun folgenden Code:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Box
    {
    public:
        int width { 0 };
        int length { 0 };
        int height { 0 };
        Box(int width, int length, int height):
        width(w),length(l),height(h)
        {}
        void setWidth (int w){width =w;}
        int getWidth (){return width;}
        void setLength (int l){length =l;}
        int getLength (){return length;}
        void setHeight (int h){height = h;}
        int getHeight(){return height;}
        int Volume() {return width * height * length;}
    
    };
    
    int main()
    {   Box box1=Box(1,1,1);
        cout << "box1.Volume: " << box1.Volume() << endl;
        cout << Box.getHeight()<<endl;
    }
    
    

    Die fett markierte Stelle wird nicht compiliert. "Not declared in this scope".
    ** width(w),length(l),height(h)**

    Aber in meinem Codebeispiel aus dem Buch compiliert er das problemlos.
    Habe ich hier etwas falsch initialisiert?

    Beste Grüße und vielen Dank an Euch alle.



  • w, l und h sind nicht deklariert.

    Ich tippe auf ein flüchtigkeitsfehler, deine parameter heißen anders.



  • @5cript

    aahh ok. Ich habe angenommen, dass ich das nicht exra deklarieren muss.

    In meinem Code Beispiel aus dem Buch waren die in meinen "Anfänger" Augen nicht deklariert:

    #include <iostream>
    #include <string>
    using namespace std;
    class teller
    {
    float groesse;
    string farbe;
    string material;
    float preis;
    public:
    teller(float g, string f, string m, float p) :
    groesse(g),
    farbe(f),
    material(m),
    preis(p)
    {}
    void setGroesse (float g)
    {
    groesse = g;
    } float getGroesse ()
    {
    return groesse;
    } void setFarbe (
    string f)
    {
    farbe = f;
    } string getFarbe ()
    {
    return farbe;
    } void setMaterial (
    string m)
    {
    material = m;
    } string getMaterial ()
    {
    return material;
    } void setPreis (
    float p)
    {
    preis = p;
    } float getPreis ()
    {
    return preis;
    }
    void rabatt ()
    {
    preis *= 80/100;
    }
    };
    int main ()
    {
    teller Teller1 = teller(21, "braun", "Porzellan", 4.99);
    cout << Teller1.getGroesse() << endl;
    cout << Teller1.getFarbe() << endl;
    cout << Teller1.getMaterial() << endl;
    cout << Teller1.getPreis() << endl;
    }
    

    Mein Code:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Box
    {
    public:
        int width { 0 };
        int length { 0 };
        int height { 0 };
        int w;
        int l;
        int h;
        Box(int width, int length, int height):
        width(w),length(l),height(h)
        {}
        void setWidth (int w){width =w;}
        int getWidth (){return width;}
        void setLength (int l){length =l;}
        int getLength (){return length;}
        void setHeight (int h){height = h;}
        int getHeight(){return height;}
        int Volume() {return width * height * length;}
    };
    int main()
    {   Box box1= Box(1,1,1);
        cout << "box1.Volume: " << box1.Volume() << endl;
        cout << box1.getHeight()<<endl;
    }
    

    Gibt leider nicht mein Gewünschten Output aus. Ich möchte eine 1 erhalten in beiden Fällen.
    Es wird 0 angezeigt. (Das sind soweit ich verstehe meine Default-Werte int width { 0 }; int length { 0 };int height { 0 };)



  • @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    Box(int width, int length, int height):
    width(w),length(l),height(h)
    {}

    Box(int width, int length, int height): // <- ne hier :P
        width(w),length(l),height(h) // <-- w, l, h müssen die parameter sein ofc
        {}
    
    // --> also
    Box(int w, int l, int h)
      : width{w}, length{l}, height{h}
    {
    }
    

    hätte mehr dazu schreiben sollen.

    EDIT: die können auch genauso heißen:

    // --> also
    Box(int width, int length, int height)
      : width{width}, length{length}, height{height} // initialisiere member width, mit parameter width.
    {
    }
    


  • @5cript sagte in Frage zur Schreibweise innerhalb einer Klasse:

    @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    Box(int width, int length, int height):
    width(w),length(l),height(h)
    {}

    Box(int width, int length, int height): // <- ne hier :P
        width(w),length(l),height(h) // <-- w, l, h müssen die parameter sein ofc
        {}
    
    // --> also
    Box(int w, int l, int h)
      : width{w}, length{l}, height{h}
    {
    }
    

    hätte mehr dazu schreiben sollen.

    EDIT: die können auch genauso heißen:

    // --> also
    Box(int width, int length, int height)
      : width{width}, length{length}, height{height} // initialisiere member width, mit parameter width.
    {
    }
    

    Vielen lieben Dank dafür.

    Eine Frage habe ich noch.
    Warum funktioniert die Initialisierungsliste auch mit {} statt ()

    Box(int w, int l, int h):
    width{w},length{l},height{h}
    {}

    oder

    Box(int w, int l, int h):
    width(w),length(l),height(h)
    {}



  • @bthight ```cpp
    #include <iostream>
    #include <string>
    using namespace std;

    class Box
    {
    private:
    int width { 0 };
    int length { 0 };
    int height { 0 };

    public:

    Box(int w, int l, int h):
    width{w},length{l},height{h}
    {}
    void setWidth (int w){width =w;}
    int getWidth (){return width;}
    void setLength (int l){length =l;}
    int getLength (){return length;}
    void setHeight (int h){height = h;}
    int getHeight(){return height;}
    int Volume() {return width * height * length;}
    

    };

    int main()
    { Box box1= Box();
    cout << "box1.Volume: " << box1.Volume() << endl;
    cout << box1.getHeight()<<endl;
    }

    
    Box box1= Box();
    
    wieso gibt der Compiler mir nicht als Box 1 die Defaultwerte aus der private aus?


  • @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    Warum funktioniert die Initialisierungsliste auch mit {} statt ()

    Das ist uniform initialization, eine alternative schreibweise für das Initialisieren von Variablen und Konstanten.
    Was man grundsätzlich benutzt, es sei denn man muss das andere Nutzen hängt am persönlichen Geschmack. Es gibt einen Unterschied in manchen Fällen, also ist es nicht immer equivalent.
    Für built-in types (int, float, ...) ist es allerdings egal.
    Ich könnte Beispiele nennen, aber das holt grad zu weit aus, weil dann muss ich die auch erklären.

    @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    wieso gibt der Compiler mir nicht als Box 1 die Defaultwerte aus der private aus?

    1. Der compiler gibt sowieso nichts aus nach übersetzung
    2. Soweit ich das sehen kann sollte der das Programm nicht übersetzen, es gibt kein Konstruktor für Box, der keine Argumente nimmt.

    füge Box() = default; in deine Klasse ein. Dadurch wird ein Standardkonstruktor wieder automatisch generiert, was er in deinem Beispiel nicht macht, weil du einen eigenen definiert hast.



  • Danke für die Tipps. Ich bin gerade am Einstieg und versucher mich hier weiterzubilden!



  • füge Box() = default; in deine Klasse ein. Dadurch wird ein Standardkonstruktor wieder automatisch generiert, was er in deinem Beispiel nicht macht, weil du einen eigenen definiert hast.

    Das habe ich unter Public eingefügt. Es funktioniert.

    int main()
    { Box box1; // Default Konstruktor
    Box box2= Box(1,2,3); // Box 2
    cout << "box1.Volume: " << box1.Volume() << endl;
    cout << "box2.Volume: " << box2.Volume() << endl;
    cout << box1.getHeight()<<endl;
    }



  • @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    füge Box() = default; in deine Klasse ein. Dadurch wird ein Standardkonstruktor wieder automatisch generiert, was er in deinem Beispiel nicht macht, weil du einen eigenen definiert hast.

    Das habe ich unter Public eingefügt. Es funktioniert.

    int main()
    { Box box1; // Default Konstruktor
    Box box2= Box(1,2,3); // Box 2
    cout << "box1.Volume: " << box1.Volume() << endl;
    cout << "box2.Volume: " << box2.Volume() << endl;
    cout << box1.getHeight()<<endl;
    }

    Wenn ich die Box mit folgendem Code schreibe:

    Box(int w=2, int l=4, int h=6):
       width{w},length{l},height{h}
       {}
    

    Habe ich die Box auch per Default Konstruktor belegt?
    Bzw. ist Box()=default;

    das selbe?



  • @bthight sagte in Frage zur Schreibweise innerhalb einer Klasse:

    Wenn ich die Box mit folgendem Code schreibe:

    Box(int w=2, int l=4, int h=6):
       width{w},length{l},height{h}
       {}
    

    Habe ich die Box auch per Default Konstruktor belegt?

    Nein, der Default Konstruktor wird immer mit leerer Argumentsliste definiert und auch aufgerufen. Wenn dieser nicht vorhanden ist, und wie hier ein Konstuktor mit drei Argumenten, die jeweils ein Default haben, definiert wird, dann wird nicht der Default Konstruktor sondern der Konstruktor mit den drei Defaults aufgerufen. Defaults sind nur bedingt sinnvoll, und man muss bei Konstruktoren noch auf einen besonderen Aspekt hinweisen. Konstruktoren mit einem Argument (oder Konstruktoren mit mehreren Default Werten, die nur mit einem expliziten Argument aufgerufen werden können) funktionieren als Konversionkonstruktor.

    Beispiel

    const short s = -1;
    Box bb(1,2,3);
    bb = s;
    

    Was passiert in Zeile drei? Die short Variable s wird in ein int konvertiert, und das lässt sich dank Deines Konstuktors mit Defaults in eine Box konvertieren.

    Damit man das ausschließen kann, gibt es dann Schlüsselwort explicit, damit wird die ungewollte Konversion verhindert.