C++-Programmierung/ Weitere Grundelemente/ Referenzen

Aus Wikibooks

Wechseln zu: Navigation, Suche


Inhaltsverzeichnis

[Bearbeiten] Grundlagen zu Referenzen

Referenzen sind interne Zeiger auf Variablen. Sie werden also genau so verwendet wie gewöhnliche Variablen, verweisen jedoch auf das Objekt, mit dem sie initialisiert wurden. Die Zeigerverwendung wird vor dem Programmierer verborgen.

Crystal Clear app terminal.png
int  a = 1;  // eine Variable
int &r = a;  // Referenz auf die Variable a

std::cout << "a: " << a << " r: " << r << std::endl;
++a;
std::cout << "a: " << a << " r: " << r << std::endl;
++r;
std::cout << "a: " << a << " r: " << r << std::endl;
Crystal Clear app kscreensaver.png
Ausgabe:
a: 1 r: 1
a: 2 r: 2
a: 3 r: 3

Wie Sie im Beispiel sehen, sind a und r identisch. Gleiches können Sie natürlich auch mit einem Zeiger erreichen, auch wenn bei einem Zeiger die Syntax etwas anders ist als bei einer Referenz.

Im Beispiel wurde die Referenz auf int r, mit dem int a initialisiert. Beachten Sie, dass die Initialisierung einer Referenzvariablen nur beim Anlegen erfolgen kann, danach kann ihr Wert nur noch durch eine Zuweisung geändert werden. Daraus folgt, dass eine Referenz immer initialisiert werden muss und es nicht möglich ist, eine Referenzvariable auf ein neues Objekt verweisen zu lassen:

Crystal Clear app terminal.png
int  a = 10; // eine Variable
int  b = 20; // noch eine Variable
int &r = a;  // Referenz auf die Variable a

std::cout << "a: " << a << " b: " << b << " r: " << r << std::endl;
++a;
std::cout << "a: " << a << " b: " << b << " r: " << r << std::endl;
r = b;       // r zeigt weiterhin auf a, r (und somit a) wird 20 zugewiesen
std::cout << "a: " << a << " b: " << b << " r: " << r << std::endl;
Crystal Clear app kscreensaver.png
Ausgabe:
a: 10 b: 20 r: 10
a: 11 b: 20 r: 11
a: 20 b: 20 r: 20

Wie Sie sehen, ist es nicht möglich, r als Alias für b zu definieren, nachdem es einmal mit a initialisiert wurde. Die Zuweisung bewirkt genau das, was auch eine Zuweisung von b an a bewirkt hätte. Dass eine Referenz wirklich nichts weiter ist als ein Aliasname wird umso deutlicher, wenn man sich die Adressen der Variablen aus dem ersten Beispiel ansieht:

Crystal Clear app terminal.png
int  a = 1;  // eine Variable
int &r = a;  // Referenz auf die Variable a

std::cout << "a: " << &a << " r: " << &r << std::endl;
Crystal Clear app kscreensaver.png
Ausgabe:
a: 0x7fffbf623c54 r: 0x7fffbf623c54
Die Ausgabe sieht bei Ihnen vermutlich etwas anders aus.

Wie Sie sehen, sind die Adressen identisch.

[Bearbeiten] Anwendung von Referenzen

Vielleicht haben Sie sich bereits gefragt, wofür Referenzen nun eigentlich gut sind, schließlich könnte man ja auch einfach die Originalvariable benutzen.

[Bearbeiten] Selbstdefinierte Referenzen

Referenzen bieten in einigen Anwendungsfällen eine Beschleunigung und bessere Lesbarkeit der Quelltexte. Sie müssen sofort initialisiert werden.

Crystal Clear app terminal.png
int main (void)
{
        const size_t x = 9, y = 15, z = 45;
        unsigned int ar_Zahlen[x][y][z];
        for (size_t a=0;a<x;++a)
        {
                for (size_t b=0;b<y;++b)
                {
                        for (size_t c=1;c<z;++c)
                        {
                                unsigned int & s = ar_Zahlen[a][b][c];
                                // Mehrfache Verwendung der Referenz
                        }
                }
        }
        return 0;
};

Bei umfangreicher, mehrfacher Verwendung, kann die Referenz viel Tipparbeit ersparen.

Außerdem erhöht diese Vorgehensweise die Performanz, da der Zugriff auf Daten in verschachtelten Klassen, großen Feldern, durch eine Referenzdefinition vereinfacht wird. Bei obigem Beispiel wird zur Laufzeit, im Arbeitsspeicher, bei jeder Verwendung von ar_Zahlen[a][b][c] zuerst der Speicherort der einzelnen Zahl berechnet, dabei müssen die Inhalts-,Feldgrößen und Offsets innerhalb des Felds berücksichtigt werden. An anderen Stellen mag dies alles mit STL-Kontainerklassen und verschachtelten Methoden- und Operatoraufrufen erfolgen. Dies alles können Sie dem Prozessor nicht ersparen. Sie können aber dafür sorgen, dass es bei obiger Iteration, pro Schritt, nur einmal vorkommt. Die Verwendung einer Referenz macht daher Sinn, sobald Sie ar_Zahlen[a][b][c] mehr als einmal verwenden. Eine Referenz ist intern mit einem Zeiger implementiert.

[Bearbeiten] Call-By-Reference

Möglicherweise erinnern Sie sich aber auch noch, dass im Kapitel „Prozeduren und Funktionen“ die Wertübergabe als Referenz (call-by-reference) vorgestellt wurde. Darauf wird nun genauer eingegangen.

Referenzen bieten genau wie Zeiger die Möglichkeit, den Wert einer Variable außerhalb der Funktion zu ändern. Im Folgenden sehen Sie die oben vorgestellte Funktion swap() mit Referenzen:

Crystal Clear app terminal.png
#include <iostream>

void swap(int &wert1, int &wert2) {
    int tmp;
    tmp   = wert1;
    wert1 = wert2;
    wert2 = tmp;
}

int main() {
    int a = 7, b = 9;

    std::cout << "a: " << a << ", b: " << b << "\n";

    swap(a, b);

    std::cout << "a: " << a << ", b: " << b << "\n";
    return (0);
}
Crystal Clear app kscreensaver.png
Ausgabe:
a: 7, b: 9
a: 9, b: 7

Diese Funktion bietet gegenüber der Zeigervariante einige Vorteile. Die Syntax ist einfacher und es ist nicht möglich, so etwas wie einen Nullzeiger zu übergeben. Um diese Funktion zum Absturz zu bewegen, ist schon einige Mühe nötig.

[Bearbeiten] const-Referenzen

Referenzen auf konstante Variablen spielen in C++ eine besondere Rolle. Eine Funktion die eine Variable übernimmt, kann genauso gut auch eine Referenzen auf eine konstante Variablen übernehmen. Folgendes Beispiel soll dies demonstrieren:

Crystal Clear app terminal.png
#include <iostream>

void ausgabe1(int wert) {
    std::cout << "wert: " << wert << "\n";
}

void ausgabe2(int const &wert) {
    std::cout << "wert: " << wert << "\n";
}

int main() {
    ausgabe1(5);
    ausgabe2(5);
    return (0);
}
Crystal Clear app kscreensaver.png
Ausgabe:
ausgabe1 wert: 5
ausgabe2 wert: 5

Die beiden Ausgabefunktionen sind an sich identisch, lediglich die Art der Parameterübergabe unterscheidet sich. ausgabe1() übernimmt einen int, ausgabe2() eine Referenz auf einen konstanten int. Beide Funktionen lassen sich auch vollkommen identisch aufrufen. Würde ausgabe2() eine Referenz auf einen nicht-konstanten int übernehmen, wäre ein Aufruf mit einer Konstanten, wie dem int-Literal 5 nicht möglich.

In Verbindung mit Klassenobjekten ist die Übergabe als Referenz auf ein konstantes Objekt sehr viel schneller, dazu erfahren Sie aber zu gegebener Zeit mehr. Für die Ihnen bereits bekannten Basisdatentypen ist tatsächlich die Übergabe als Wert effizienter.

[Bearbeiten] Referenzen als Rückgabetyp

Referenzen haben als Rückgabewert die gleichen Vorteile wie bei der Wertübergabe. Allerdings sind Sie in diesem Zusammenhang wesentlich gefährlicher. Es kann schnell passieren, dass Sie versehentlich eine Referenz auf eine lokale Variable zurückgeben. Diese Variable ist außerhalb der Funktion allerdings nicht mehr gültig, daher ist das Resultat, wenn Sie außerhalb der Funktion darauf zugreifen, undefiniert. Aus diesem Grund sollten Sie Referenzen als Rückgabewert nur verwenden wenn Sie wirklich wissen, was Sie tun.

Crystal Clear app terminal.png
#include <iostream>

// gibt die Referenz des Parameters x zurück
int &zahl(int &x) {
    return x;
}

int main() {
    int y = 3;
    zahl(y) = 5;
    std::cout << y; // Ausgabe: 5
    return y;
}


Persönliche Werkzeuge