Referenzen und Zeiger



  • Referenzen sind einfacher zu handhaben weil man sie nicht dereferenzieren muss um auf das Objekt zugreifen zu können.
    Dein beispiel mit dem const ist ein Spezialfall: eine konstante Referenz kann an ein temporäres Objekt gebunden werden (in den meisten Fällen passiert das halbwegs ungesehn bei Funktionsparametern). Die Lebenszeit des temporären Objektes verlängert sich dann auf den Gültigkeitsbereich der Referenz.
    Siehe auch hier für mehr: http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/


  • Mod

    Eine initialisierte Referenz ist das Objekt:

    foo a = ... ; foo& b = a;
    //
    ... irgendwas ...
    

    es ist hier völlig unmöglich festzustellen, ob a oder b die originale variable und das andere die Referenz ist:

    foo b = ...; foo& a = b;
    //
    ... irgendwas ...
    

    ist das gleiche Programm. Man kann sich auch einfach vorstellen, dass normale Variablen einfach Referenzen sind, mit dem Zusatz, dass deren Definition die Erzeugung des jeweiligen Objektes impliziert.

    Im Gegensatz dazu besteht ein klarer und offensichtlicher Unterschied zwischen Objekten und Zeigern auf diese Objekte.



  • Hallo,
    vielen Dank für die Antworten. Ich habe mir mal den Inhalt der Seite, die pumuckl mir vorgeschlagen hat, durchgelesen. Dazu habe ich eine Frage. Ich habe diesen Code:

    string f() { return "abc"; } //Funktion gibt ein temporäres Objekt zurück 
    
    void g() {
    string& s = f(); //nicht korrekt      
      cout << s << endl;
    }
    

    Folgende Fragen habe ich dazu:

    1.) Warum gibt die Funktion ein temporäres Objekt zurück. Eigentlich ist nur der return temporör, weil alles was mit der Funktion zu tun hat nach dem Aufruf wieder gelöscht. Da aber der zurückgegebene Wert irgendwo gespeichert wird, ist er doch gar nict richtig temporär?!

    2.) Meine Referenz soll nun den return-Wert von f() speichern. Wo liegt das Problem? Ich konnte der Erklärung der Seite nicht ganz folgen, da ich nicht verstehe, warum das const so essentiel ist.

    3.) Es ist aber richtig zu sagen, dass eine Referenz auch die Adresse einer Variablen hat? Wie soll das sonst funzen?

    4.) Diesen Code verstehe ich leider überhaupt nicht.

    string& message()
    {
     static string str = "Hallo";
     return str;
    }
    
    int main()
    {
     message() = "Tschüss";
     string s=message();
     cout << s << endl;
    }
    

    Seit wann kann ich einer Funktion Werte zuweisen. Kann mir da mal jemand auf die Sprünge helfen, da dass ja an dem Rückgabewert als Referenz liegen muss.

    Vielen Dank
    lg, freakC++



  • 1. Der string, den du da zurück gibst, ist temporär. Er hat ja keinen Namen und ist nur in dem aktuellen Kontext gültig. Wenn du da jetzt eine Referenz drauf speicherst, ist er nach dem nächsten Sequenzpunkt ( z.B ; ) nicht mehr gültig.

    2. const ist da nötig, damit der Compiler weiss, dass du den Wert nicht änderst und somit das reine lesen erlaubt ist. (steht im Standard irgendwo; schaue vlt. später nach, wo genau).

    3. Die Adresse einer Referenz ist die Adresse des referenzierten Objektes. Stell dir die Referenz eher als Alternativ Namen vor, als ein Zeiger. Sprich zur Laufzeit hast du kein "Referenz"-Objekt, wie du z.B einen Zeiger speichern kannst.

    4. Der Punkt liegt hier bei dem static. Ein statisches Objekt wird beim erreichen des Codes erzeugt und erst am Ende des gesamten Programmes wieder zerstört. Daher kannst du auch Problemlos darauf verweisen.
    Das du dem Funktionsaufruf einen Wert zuweisen kannst hat den Grund, dass die Funktion ja den Rückgabewert einer Referenz auf den string hat. Und einer Referenz kannst du ja einen Wert zu weisen. Du weisst also nicht der Funktion, sondern dem Rückgabewert etwas zu. (dass das geht, wir oft als Grund genannt, einen Rückgabewert const zu machen - um z.B den ==/= Fehler zu umgehen).



  • eine noch schönere Variante ist folgendes:

    NEVER DO THIS! NIEMALS MACHEN!

    std::string& genMessage(uint32 i)
    {
      static std::string abc;
      /* mach was mit dem Input und beschreibe den String */
    
      return abc;
    }
    

    NEVER DO THIS! NIEMALS MACHEN!

    Das hat ganz böse nebenwirkungen bei multithreading.

    Sowas sieht man hin und wieder bei Anfängern, die gerade erfahren haben, dass bei Rückgabewerten temporäre Objekte erstellt werden, die sie so eingespart werden sollen.



  • freakC++ schrieb:

    wo liegt dann der Bezug auf einer anderen Variable?

    Muss nicht, dass nennt man call-by-value.



  • eine referenz kann man sich am besten als selbstreferenzierenden zeiger
    vorstellen.

    gehen wir von deinem beispielscode aus:

    string& message()
    {
     static string str = "Hallo";
     return str;
    }
    
    int main()
    {
     message() = "Tschüss";
     string s=message();
     cout << s << endl;
    }
    

    jetzt stell dir vor, die referenzen wären zeiger (zeig mir einen realen
    compiler, der das so nicht handhabt), und bei jedem zugriff bekämen wir
    unser objekt (per * dereferenziert):

    string *message() // die referenz in form einer addresse
    {
     static string str = "Hallo";
     return &str; // referenz, also addresse zurückgeben
    }
    
    int main()
    {
     *message() = "Tschüss"; // addresse holen, dereferenzieren und reinschreiben
     string s = *message();
     cout << s << endl;
    }
    

    die addresse einer referenz bekommst du nicht, da sie für dein objekt steht.
    kann man sich auch herleiten:

    int i;
    int &r = i;
    
    int *ptr = &r;
    
    // wird zu
    
    int i;
    int *r = &i;
    
    int *ptr = &*r; // das kürzt sich!!
    

    ich wette mit dir, dass beide versionen auf assemblerebene gleich aussehen.
    die größen unterschiede liegen in der syntax. so brauchst du eine referenz
    nicht zu dereferenzieren (auch das kann man aus dem zeigerbeispiel erkennen).

    auf null setzen geht auch nicht.
    (mit einem trick schon: int &r = *(int *)0; jeder zugriff würde aber
    abschmieren).

    so.

    das ganze war eine veranschaulichung zur besseren verständniss. nichts davon
    ist wirklich durch den c++ standard vorgeschrieben.

    (offtopic: ist die zahl der bits in einem byte per standard festgelegt??)

    in den meisten fällen kannst du einen zeiger durch eine referenz ersetzen,
    andersrum (referenz durch zeiger) geht das immer (außer bei diesem const-teil...)

    wenn du z.b. deine referenzen/zeiger oft umsetzt oder als ungültig markierst (0),
    ist ein zeiger die bessere wahl. willst du ein objekt wie ein anderes
    behandeln (proxy-klassen) oder die schöne streaming syntax nutzen (cout <<),
    sind referenzen besser.
    wenn du einen konstanten parameter hast, der nicht kopiert werden soll, überzeugt
    eine referenz schon aus syntaktischen gründen.

    dynamische speicherverwaltung (new/delete) ist aber wieder ein klarer fall für
    zeiger. kein mensch schreibt sowas:

    obj &o = *new obj;
    delete &o;
    


  • Hey,
    vielen Dank für die ausführlichen Erklärungen. Ich denke, dass ich es verstanden habe. Falls dennoch Fragen wieder aufkommen sollte, dann werde ich mich melden!

    Bis dann
    lg, freakC++



  • Hallo zusammen,
    da bin ich nochmals. Ich habe nochmal eine Frage zu diesem Aufruf:

    int& max(int x, int y)
    {
     return x > y ? x : y;
    }
    

    Warum wird hier der Compiler eine Fehlermeldung ausgeben. Kann man überhaupt noch einen Rückgabetyp einer Funktion vom Typ "Referenz auf Typ" machen? Könnt ihr mir das nochmals an einem guten Beispiel erläutern?

    Vielen Dank
    lg, freakC++



  • freakC++ schrieb:

    int& max(int x, int y)
    {
     return x > y ? x : y;
    }
    

    Das klappt nicht, weil x eine lokale Variable innerhalb der Funktion ist und totgeht, wenn die Funktion verlassen wird. Eine referenz auf was totes ist nicht gut.

    int& max(int& x, int& y)
    {
     return x > y ? x : y;
    }
    

    So wäre erzwungen, daß der Aufrufer Referenzen auf außen lebende Variablen reinsteckt und eine Referenz auf eine dieser außen lebenden Variablen zurückzugeben, istr ok.



  • Hallo,
    das leuchtet mir zwar ein, doch dann verstehe ich dieses Beispiel nicht 😃 :

    string& message() 
    { 
     static string str = "Hallo"; 
     return str; 
    }
    
    int main()
    {
    	string str = message();
    	cout << str << endl;
    	return 0;
    }
    

    Dieses Beispiel hatten wir zwar schonmal, aber egal. Wenn ich nun das static wegmache, wird str auch zu einer lokalen Variable. Wenn man mal vom Logischen absieht: Das Programm wird auch ohne static kompiliert. Warum kompiliert dann das andere Beispiel nicht. Da sind doch auch "nur" lokale Variablen?!

    Vielen Dank
    lg, freakC++



  • Grundsätzlich kompiliert auch das obige Beispiel. Benenn es einfach mal um. 😉

    Das liegt daran, dass max ein Makro ist und dann bekommt der Compiler da ein sehr merkwürdiges Konstrukt vorgelegt. 😉


  • Mod

    freakC++ schrieb:

    Hallo zusammen,
    da bin ich nochmals. Ich habe nochmal eine Frage zu diesem Aufruf:

    int& max(int x, int y)
    {
     return x > y ? x : y;
    }
    

    Warum wird hier der Compiler eine Fehlermeldung ausgeben.

    Warum sollte er?



  • camper schrieb:

    freakC++ schrieb:

    Hallo zusammen,
    da bin ich nochmals. Ich habe nochmal eine Frage zu diesem Aufruf:

    int& max(int x, int y)
    {
     return x > y ? x : y;
    }
    

    Warum wird hier der Compiler eine Fehlermeldung ausgeben.

    Warum sollte er?

    neben dem von drakon vermuteten fakt, dass es ein max-makro gibt, gibts auch noch die möglichkeit, dass er algorithm included hat und using namespace std geschrieben hat - dann wird vermutlich kommen, dass man fkt. nicht mittels rückgabe-typ überladen kann...

    bb


  • Mod

    unskilled schrieb:

    neben dem von drakon vermuteten fakt, dass es ein max-makro gibt

    achso, dafür gibts NOMINMAX.

    unskilled schrieb:

    gibts auch noch die möglichkeit, dass er algorithm included hat und using namespace std geschrieben hat - dann wird vermutlich kommen, dass man fkt. nicht mittels rückgabe-typ überladen kann...

    Stimmt, allerdings stehen in std nur zwei Funktionstemplates für max, kein Problem also.



  • camper schrieb:

    unskilled schrieb:

    neben dem von drakon vermuteten fakt, dass es ein max-makro gibt

    achso, dafür gibts NOMINMAX.

    im nachhinen gibts dafür nur noch #undef min bzw #undef max, da jeder, der die windows-header included, NOMINMAX davor definieren müsste - machen aber die wenigsten (libs)... würd ich mich auch nie drauf verlassen...

    camper schrieb:

    unskilled schrieb:

    gibts auch noch die möglichkeit, dass er algorithm included hat und using namespace std geschrieben hat - dann wird vermutlich kommen, dass man fkt. nicht mittels rückgabe-typ überladen kann...

    Stimmt, allerdings stehen in std nur zwei Funktionstemplates für max, kein Problem also.

    stimmt ^^

    bb



  • Hallo zusammen,
    jetzt hab' ich es begriffen. Der Code kompiliert nun auch.

    Bis bald
    lg, freakC++


Anmelden zum Antworten