Problem mit Voklards c++ Kurs



  • Hi,

    ich versuche gerade Volkards c++ Kurs zu verstehen. Ich bin in Lektion 60.
    http://www.iks.hs-merseburg.de/~kilian/ak_Dateien/ak_lehre_Dateien/2009_WS/Bioinformatik_VL/Lehrmaterial/C++/Kurs Volkhard Henkel/html/die_klasse_vector.html
    Ich habe Probleme das mit dem L-Wert zu verstehen.

    Soweit ich verstanden habe ist ein L-Wert ein Wert dem etwas zugewiesen werden darf.

    Nun hat die Klasse "Vector" den Opertaor [] überladen mit

    double operator [] (long array_index)
    {
    	return array_data[array_index];
    }
    

    .

    Im Hauptprogramm funktioniert nun die zweite Zeile von

    Vector v(10);
    v[5]=4711;
    std::cout<<v[5];
    

    nicht. Das ist mir klar. Die Funktion v_[5] liefert den Wert des Arrays an der Stelle 5 zurück.

    Ich verstehe aber nicht warum diese zweite Zeile auf einmal funktioniert wenn der Rückgabetyp des überladenden Operators in eine Referenz geändert wird also:

    double& operator [] (long array_index)
    {
    	return array_data[array_index];
    }
    

    Das verwirrt mich total. 😕
    Wieso kann nun einem Rückgabetyp was zugewiesen werden?

    Ich hätte spontan versucht den Operator nochmal zu überladen, oder sowas ähnliches, damit er den Wert 4711 z.b als double annimmt.

    Warum kann ich nun mit einer Operatorüberladung, die nur was zurückgibt einen Wert annehmen. Das ist für mich so, als ob ich eine "get" Methode geschrieben hätte und sie auf einmal die Funktionsweise einer "set" Methode kann.



  • Einmal kriegst du eine einfache Kopie eines Wertes (wie z.B. 5). Dem kannst du natürlich nichts zuweisen.

    Bei der Referenz kriegst du aber die Referenz auf die Variable, der du was zuweisen kannst (int x; x=5). Du greifst also auf die Variable an sich zu, wie du das mit arr[0] auch machen würdest.



  • Anfänger001 schrieb:

    Die Funktion v_[5] liefert den Wert des Arrays an der Stelle 5 zurück.

    Nein, es liefert den Wert an der sechsten Stelle zurück, da bei 0 angefangen wird zu zählen. (Beliebter Anfängerfehler)

    Anfänger001 schrieb:

    Wieso kann nun einem Rückgabetyp was zugewiesen werden?

    Der Unterschied ist der Rückgabetyp. In der ersten Funtion wird eine Kopie des wertes zurückgegeben, in der zweiten Funktion allerdings nur eine Referenz auf den Wert. Du greifst also bei dem Funktionsaufruf direkt auf den Wert in deiner Klasse zu und veränderst den Wert in der Klasse. Am besten guckst du dir nochmal allgemein Referenzen (erkennbar an dem &) an, insbesondere Call-by-Value vs. Call-by-Reference, das lässt sich auf die Rückgabe von Funktionen übertragen.



  • Cyres schrieb:

    erkennbar an dem &

    Das editierst du mal besser schnell- sonst wird er Referenzierung und Referenzen verwechseln oder gar Pointer und Referenzen.



  • OK, aber warum schreibt man dann z.B. getrennte get und Set methoden und nicht einfach

    double& getsetValue(long array_index)
    {
    	return array_data[array_index];
    }
    

    und verwendet es als get so:

    int x = v.getsetValue(5);
    

    und als set so

    v.getsetValue(5) = 700;
    

    Wäre doch praktisch nur eine Methode zu brauchen.
    (Obwohls komisch aussieht)



  • Anfänger001 schrieb:

    (Obwohls komisch aussieht)

    Ja, und überladene Index-Operatoren sind doch noch viel geiler.



  • Weil man für Arrays o.ä. normalerweise den operator[] verwendet und dies auch beibehalten will (bzw. das verhalten imitieren will).
    Außerdem ist das viel weniger zu schreiben..



  • Anfänger001 schrieb:

    Wäre doch praktisch nur eine Methode zu brauchen.
    (Obwohls komisch aussieht)

    Das kommt auf den Anwendungsfall an. Bei std::vector gibt es zwei von den Methoden. Einmal at(size_t index) und einmal operator[](size_t index), jeweils mit einer const-Überladung. Das geht aber nicht, wenn man beim setzen der Variable eine Plausibilitätsprüfung oder andere Dinge Prüfen bzw. aufrufen muss. Deswegen sieht man eigentlich 2 Dinge sehr häufig:

    class Foo {
        int bar_;
    
    public:
        int bar() const {return bar_;}
        void bar(int bar) {bar_ = bar;} // ggf. sogar return bar_ oder return *this
                                        // damit sind Ausdrücke wie foo.bar(20).fbar(40); möglich
    };
    

    bzw.

    class Foo {
        int bar_;
    
    public:
        int getBar() const {return bar_;}
        void setBar(int bar) {bar_ = bar;}
    };
    


  • Cyres schrieb:

    / damit sind Ausdrücke wie foo.bar(20).fbar(40); möglich

    Meiner Ansicht nach wäre eine Elementinitialisierungsliste besser, da kann
    das Objekt gleich bei der Erstellung mit Werten versehen werden.

    Zum Stichwort Elementinitialisierungsliste:
    http://de.wikipedia.org/wiki/Initialisierungsliste
    http://www.c-plusplus.net/forum/304526-full

    Beispiel:

    #include <iostream>
    
    using namespace std;
    
    class Foo {
    private:
        int bar_;
        int alter_;
    
    public:
        int Getbar() const {return bar_;}
        void Setbar(int bar) {bar_ = bar;}
    
        int Getalter()const{return alter_;}
        void Setalter(int alter){alter_=alter;}
    
       //Elementinitialisierungsliste
        Foo(int bar=0,int alter=0):bar_(bar),alter_(alter) {}
    };
    
    int main()
    {
        Foo foo(100,20);
        cout<<foo.Getbar()<<" "<<foo.Getalter()<<endl;
    }
    


  • Anfänger001 schrieb:

    OK, aber warum schreibt man dann z.B. getrennte get und Set methoden und nicht einfach

    Soweit ich das weiß, sollten Funktionen möglichst einfach sein, also nur das unbedingt Notwendige enthalten.
    Das ermöglicht z.B. eine bessere Wartbarkeit.

    Wenn eine Funktion mehrere Befehle enthält, man will aber nur einen davon ansprechen, muss die gesamte Funktion in den Speicher übertragen werden, man verbraucht also mehr Ressourcen.



  • redrew99 schrieb:

    Soweit ich das weiß, sollten Funktionen möglichst einfach sein, also nur das unbedingt Notwendige enthalten.

    Stichwort starke Kohäsion, was aber etwas anders ist, als du es beschreibst.



  • Anfänger001 schrieb:

    Wäre doch praktisch nur eine Methode zu brauchen.
    (Obwohls komisch aussieht)

    Es gibt auch einige Bibliotheken, die das so machen. In Sprachen wie C# ist das meines Wissens über Properties gelöst, die sich genau so verhalten. Aber sie sind letztendlich nur Syntaxzucker über separate get() und set() Methoden.
    Es ist hauptsächlich Konvention, dass man direkt am Namen der Methode erkennen kann, ob ein Objekt durch den Aufruf verändert werden kann oder nicht(stell dir vor, du suchst in einem Projekt mit 100.000 Zeilen Code einen Fehler und müsstest jedes mal überprüfen, ob Objekte irgendwie verändert werden...gruselig!). Wenn ich a.getValue(5) sehe, dann weiß ich, dass ich mir um den Zustand keine Sorgen machen muss, a kann nicht verändert werden. Dementsprechend sehe ich bei a.setValue(5,x) das a verändert wird und kann sofort vergleichen, ob das notwendig ist.
    In C++ haben wir noch einen kleinen Helfer der sich const-correctness nennt. Kurz zusammengefasst: auf konstanten Objekten können nur Methoden angewandt werden, die es nicht verändern (also const-deklariert sind). Das hilft schon in 99% der Fälle und ich könnte sowas machen:

    class A{
    private:
        double values[5];
    public:
        double value(unsigned int index )const{
            return value[i];//const methode, value kann nur gelesen werden
        }
        double& value(unsigned int index ){
            return value[i];
        }
    
    };
    
    A a;
    a.value(4)=100;//funktioniert nur, weil a nicht konstant ist.
    std::cout<<a.value(4); 
    
    A const& b=a;//konstante Referenz auf a
    b.value(4)=200;//kompiliert nicht! b ist konstant
    std::cout<<b.value(4);//alles super!
    

    Es sieht von außen genauso aus, wie du beschrieben hast, funktioniert aber besser. Es ist aber nicht perfekt, wie du bei a siehst. Wir können gerade beide Methoden aufrufen, obwohl wir das vielleicht nicht wollen. Es könnte also sein, dass wir a aus versehen verändern, ohne dass wir es sofort merken.

    Es gibt noch andere Gründe, aber das führt hier zu weit 🙂


Anmelden zum Antworten