referenz & zeiger bei parametern/konstruktoren (beginnerfrage)



  • hallo allerseits!

    ich bin neu sowohl im forum als auch im bereich von c/c++. grundsätzliche
    programmiergrundlagen sind mir einigermassen geläufig und ich versuche, mein
    wissen auf c/c++ auszudehnen.
    nicht ganz sicher bin ich mir, ob das thema nun hier rein oder vielleicht doch in
    die c ecke gehört, denn im grunde ist ja objektorientierung mit drinnen, zeiger
    und referenzen (und darum gehts ja im kern meiner frage) gehen allerdings auf c
    zurück... also bitte mich nicht gleich anschreien.

    also, ich arbeite mich gerade durch das buch 'c++ programmierung' von andre
    willms und habe dort im kapitel zeiger folgendes beispielprogramm, das zeigen
    soll, wie man mit dem konstruktor übergebenen parametern klassenelemente
    initialisiert.

    #include <iostream>
    using namespace std;
    
    class Referenz
    {
    	private:
    	int &a;
    	int b;
    
    	public:
    	Referenz(int&);
    	void print(void);
    };
    
    Referenz::Referenz(int &r) : a(r),b(3)
    {
    }
    
    void Referenz::print(void)
    {
    	cout << "a=" << a << " b=" << b << endl;
    }
    
    int main()
    {
    int willi=8;
    
    Referenz t(willi);
    
    	t.print();
    	willi=5;
    	t.print();
    
      return(0);
    }
    

    soweit so gut, mir leuchtet das einigermassen ein. allerdings steige ich bei der
    verwendung des adressoperators (&) ein wenig aus. ich versuche mal das in worte
    zu fassen, was ich verstanden gehabt zu glauben meine:

    bei erzeugung des objekts 't' in der main methode wird ja direkt die variable
    'willi' an den konstruktor übergeben (wird hier schon eine kopie davon erzeugt?),
    allerdings wird durch die parameterangabe (int &r) im konstruktor doch
    eigentlich die adresse von 'r' (durch das '&') geholt. meiner logik zufolge
    müsste es dann doch aber eigentlich (int* &r) heissen, oder?

    die frage geht insofern weiter, da dann ja die variable 'a' mit 'r'
    initialisiert wird (durch '... :a(r) ...). ist das r denn nun ein verkapptes
    '&r' oder das wirkliche 'r' (also eine kopie von 'willi')?

    darauf frage ich mich, ob der ausdruck in zeile 8 denn wirklich korrekt ist
    (offensichtlich ist er es, denn das ganze lässt sich kompilieren und
    funktioniert), denn es wird ja wiederrum durch das '&' vor dem 'a' auf die
    adresse verwiesen und diese dann zu 'int'??? meiner logik nach müsste das doch
    eher 'int* (&a)' sein, oder? wird das '&' in zeile 8 durch die initialisierung
    etwa quasi unbedeutend?
    die printfunktion dereferenziert 'a' ja nicht, also scheint 'a' einfach eine
    gültige int-variable zu sein.

    um die verwirrung komplett zu machen, habe ich den code zu folgendem umgebaut,
    was mir zur zeit (teilweise) einfacher nachvollziehbar scheint und die selben
    resultate in der ausgabe liefert:

    #include <iostream>
    using namespace std;
    
    class Referenz
    {
    	private:
    	int* a;
    	int b;
    
    	public:
    	Referenz(int&);
    	void print(void);
    };
    
    Referenz::Referenz(int &r) : a(&r),b(3)
    {
    }
    
    void Referenz::print(void)
    {
    	cout << "a=" << *a << " b=" << b << endl;
    }
    
    int main()
    {
    int willi=8;
    
    Referenz t(willi);
    
    	t.print();
    	willi=5;
    	t.print();
    
      return(0);
    }
    

    hier ist 'a' also wirklich ein zeiger wie ich es verstehe. eigentlich sollte
    hier dann keinerlei kopie von 'willi' erzeugt werden (im ersten fall bin ich mir
    da nicht so sicher).
    unklar bei dieser version ist mir jedoch z.b. , warum in der deklaration des
    konstruktors (zeile 12) als paraeter '(int&)' steht, und nicht (int*).

    hat irgendwer die nerven mir die punkte zu erklären?

    ich danke euch schonmal für die mühe und verbleibe derweil mit grüssen an alle
    fleissigen coder - redchili

    EDIT: je länger ich so drüber nachdenke... vielleicht sollte ich mir das '&'
    weniger als operator, sondern vielmehr '&a' als eine einheit vorstellen, die
    eben die adresse von 'a' darstellt... geht das in die richtige richtung?
    dann noch was - an den stellen, wo ich sowas schreibe wie "...sollte das nicht
    xxx heissen..." habe ich das schon ausprobiert und gemerkt dass es nicht geht;
    in diesen fällen gehts mir ums verständnis _warum_ es nicht geht.



  • Das &-Zeichen wird in C++ quasi doppelt verwendet, einmal als Adress-Operator und einmal als Referenz-Zeichen.

    Wenn man bei einer Deklaration das &-Zeichen angibt, dann bedeutet das, daß bei der Parameterübergabe keine Kopie sondern die Adresse der Variablen übergeben wird.
    Eine Referenz ist intern (fast) dasselbe wie ein Zeiger, aber man braucht nicht immer beim Zugriff auf die Variable den Wert dereferenzieren (z.B. *a).

    Deine 2. Version entspricht von der Logik her genau der 1. Version, aber trotzdem sollte man in C++ besser mit Referenzen arbeiten anstatt mit Zeigern.

    Gerade bei Klassen als Parameter werden (konstante) Referenzen eingesetzt, z.B.

    void f(std::string &s)
    {
      s += "x";  // hier wird direkt der übergebene String verändert
    };
    
    // oder konstant
    
    void f(const std::string &s)
    {
      std::cout << s << std::endl; // nur lesender Zugriff auf den String (daher als konstant deklariert)
    };
    

    Ich glaube du verwirrst dich ein wenig selber, wenn du zu sehr mit Zeigern denkst...

    P.S. Referenzen gibt es in "C" nicht, sondern sind erst in "C++" dazugekommen, daher bist du schon im richtigen Forum -)


  • Mod

    eine referenz ist ein alias, also ein alternativer name, für ein objekt. damit besteht eine gewisse verwandtschaft mit zeigern:

    int foo;
    int& bar = foo;
    int* baz = &foo;
    

    hier übernehmen bar und der ausdruck *baz die identität von foo. es ist auch eine plausible annahme, dass der compiler in fällen, in denen der initialisierer der referenz nicht im scope ist (also bei funktionsargumenten oder kalssenmembern), referenzen intern mit hilfe von zeigern implementiert. das ist allerdings nicht der wesentliche punkt. weil eine referenz das objekt IST, mit dem sie initialisiert wurde, ist es z.b. nicht möglich, die referenz auf ein anderes objekt verweisen zu lassen. darin ähnelt es konstanten pointern, allerdings sind pointer objekte aus eigenem recht. da referenzen wie pointer auf objekte verweisen, gelten für ihre initialisierer dem grunde nach dieselben anforderungen. man kann sie also z.b. nicht mit einem wert initialisieren:

    int& a = 5; // fehler: kein lvalue
    int* b = &5; // dito, 5 hat keine adresse
    

    eine ausnahme gilt für referenzen auf konstante objekte. hier wird gegebenenfalls ein temporäres objekt erstellt und die referenz daran gebunden. das temporäre objekt lebt dann in der regel solange, wie die referenz existiert. ein referenz auf konstantes T kann auch mit einer variable vom typ T, die nicht konstant ist, initialisiert werden (so wie zeiger auf konstantes T mit zeigern auf nicht konstantes T initilisiert werden können).



  • vielen dank euch beiden für die antworten!
    langsam klärt sich der nebel ein wenig; in dem buch ist irgendwie nicht explizit
    die sache mit der doppelverwendung des '&' rübergekommen.

    ein detail würde mich aber dennoch interessieren:
    wenn ich also einer funktion (oder meinetwegen auch einem kostruktor) als paramter
    eine referenz deklariere & definiere, dann kann ich innerhalb dieser auf die
    variable direkt zugreifen (also ohne &)? also z.b.

    void f(int &i){ 
       i += 2;  // zugriff aufs "referenzierte" i
       int j = i;  // zuweisung des wertes von i an lokale variable (kopie!!!)
       int& k = i;  // was aber ist das? hier wird wohl die referenz kopiert (?)
    }
    

    aufbauend auf dieser logik verstehe ich glaube ich, was mich in meinem allerersten
    beispiel (das aus dem buch) so irritiert hat. zur erinnerung:

    class Referenz
    {
        private:
        int &a;   // das hat mich verwirrt
        int b;
    
        public:
        Referenz(int&);
        void print(void);
    };
    
    Referenz::Referenz(int &r) : a(r),b(3) { // nix }
    

    und zwar verstehe ich es jetzt so, dass bei der initialisierung von 'a' mit 'r'
    im konstruktor ( durch a(r) ) 'r' bereits die referenz ist, die ja aufgrund der
    eigenschaft einer referenz nicht wie ein zeiger dereferenziert werden muss.
    wenn jetzt in zeile 4 statt 'int &a;' lediglich 'int a;' stehen würde, dann
    würde ich damit a zu einer eigenen int-instanz machen und hätte mir das ganze
    übergeben der referenz auch sparen können, da 'a' jetzt unabhängig von der als
    parameter übergebenen variable ist (auch wenn sie im moment deren wert kopiert
    hat).
    dadurch dass da aber 'int &a;' steht, wird neben 'r' also noch eine referenz auf
    die ursprüngliche variable erzeugt, sprich ich benötige nur einmal speicherplatz
    für den ursprünglichen wert, aber habe ihn zugreifbar durch 'r' und 'a'
    innerhalb des konstruktors, durch 'a' innerhalb des objektes und dann noch durch
    das, wo der aufruf der funktion (bzw konstruktor) mit dem parameter stattfindet.

    *puh* , ich hoffe das stimmt so... bitte bitte sagt mir wer dass ichs jetzt
    richtig vertanden hab!!!



  • Ja, stimmt genau, was du geschrieben hast (bin beeindruckt - meine es ehrlich, das sich doch noch Leute intensiv mit der Programmierung auseinandersetzen - wenn ich dazu manchmal andere Fragen hier sehe...)

    Um das mit den Referenzen auch direkt im Programmcode zu sehen, sollte man für den Kostruktor-Parameter und die Member-Variable denselben Namen verwenden (oder aber ein Präfix, z.B. "m_")

    Nicht wundern, aber dies geht wirklich:

    class X
    {
    private:
        int &a; // oder eben m_a;
    
    public:
        X(int& a);
    };
    
    X::X(int &a)
      : a(a) // <- dies funktioniert, oder auch "m_a(a)"
    {
    }
    

Anmelden zum Antworten