Referenzen in C++
Objektnamensdeklarationen in C++
In dem folgenden Beispielprogramm wird zunächst eine Variable in der klassischen Weise definiert. Bei einer solchen Variablendefinition wird automatisch immer auch ein neues Objekt angelegt.
Es folgt dann die Definition eines reinen Objektnamens. Hierbei wird dem neuen Namen das Zeichen "&" vorangestellt.
Bei einer reinen Objektnamensdeklaration wird kein neues Objekt angelegt, sondern nur ein weiterer Name für ein schon vorhandenes Objekt vereinbart.
Daher muß bei einer Objektnamensdeklaration immer ein schon vorhandenes Objekt angegeben werden, an das dann (auch) der neue Name gebunden wird. In dem folgenden Beispiel wird der neue Name "w" an das Objekt der schon bestehenden Variablen "v" gebunden.
Danach ist der Name "w" an dasselbe Objekt gebunden wie zuvor schon der Name "v". Nun sind die Variable "v" und die Variable "w" beide Variablen mit dem gleichen Objekt, die sich nur noch in ihrem Namen unterscheiden, aber sonst absolut gleichberechtigt sind.
Wurde durch eine Variablendefinition oder eine Objektnamensdeklaration ein Name einmal an ein Objekt gebunden, so gilt diese Bindung danach unabänderlich im ganzen Gültigkeitsbereich des Namens. Derselbe Name kann also in seinem Gültigkeitsbereich (während derselben Lebenszeit) nicht an ein anderes Objekt gebunden werden. Hierin unterscheiden sich die durch eine Objektnamensdeklaration eingeführten Namen nicht von den bisher schon bekannten Variablennamen.
Die folgende Zeilen zeigen dann, daß es daher im folgenden egal ist, wann man die Variable "v" und wann die Variable "w" verwendet. Sie unterscheiden sich jetzt nur in dem Namen, aber nicht in ihrem Objekt. Man kann auch sagen, der Bezeichner "w" sei nun ein Synonym für den Bezeichner "v". Die Bezeichner sind, abgesehen davon, daß der eine »w« und der andere »v« lautet, absolut gleichberechtigt.
referenzen.cpp#include <iostream>
#include <ostream>
int main()
{ std::cout << "Objektnamensdefinition\n";
int v( 12 ); /* Variablendefinition und Initialisierung */
int & w( v ); /* Namensdeklaration von w zum Objekt von v */
std::cout
<< & v << '\n' /* Ausgabe der Objektkennzahl (Adresse) */
<< & w << '\n' /* Ausgabe der Objektkennzahl (Adresse) */
<< w << '\n' /* Ausgabe des Objektwertes */
<< v << '\n'; /* Ausgabe des Objektwertes */
w = 7; /* Das Objekt wird auf 7 gesetzt. */
std::cout
<< w << '\n' /* Ausgabe des Objektwertes */
<< v << '\n'; /* Ausgabe des Objektwertes */
v = 4; /* Das Objekt wird auf 4 gesetzt. */
std::cout
<< w << '\n' /* Ausgabe des Objektwertes */
<< v << '\n'; /* Ausgabe des Objektwertes */
}std::coutReferenzen
006BFDF4
006BFDF4
12
12
7
7
4
4
Man beachte den Alias-Effekt: Ein Schreibzugriff über den Namen "w" verändert nicht nur den über den Namen "w" erreichbaren Wert, sondern auch den über den Namen "v" erreichbaren Wert, da beide Namen sich ein Objekt teilen.
Da die Adressen (Objekte) beider Variablen gleich sind, kann man den Adreßoperator in der Objektnamensdefinition "& w = v" auch lesen als „die Adresse (d.h.: das Objekt) der Variablen "w" soll die von (dem Objekt von) der Variablen "v" sein.“
Nach der Definition von v [UML static structure diagram].----------------------.
| 12 : int |
| -------- |
| |
'----------------------'
^
| hat Wert ^
|
.--------------. .----------------------.
| v : Name | gebunden an > | 006BFDF4: int-Objekt |
| -------- |------------------->| -------------------- |
| | | |
'--------------' '----------------------'Nach der Definition von w [UML static structure diagram].----------------------.
| 12 : int |
| -------- |
| |
'----------------------'
^
| hat Wert ^
|
.--------------. .----------------------.
| v : Name | gebunden an > | 006BFDF4: int-Objekt |
| -------- |------------------->| -------------------- |
| | | |
'--------------' '----------------------'
.--------------. ^
| w : Name | gebunden an > |
| -------- |--------------------------------'
| |
'--------------'Nach der Definition von v [Behaelterdiagramm]. .
|-------. _____ Wert | Objekt
| v |' '-. |
|-------'# ## '. |
| / ## # # \ |
| ; # # # ; |
| | # # | |
| ; # # ; |
| \ # # / |
' '. ### ######.' '
\ '-._____.-' /
'-------------------'Nach der Definition von w [Behaelterdiagramm]. .
|-------. _____ Wert | Objekt
| v |' '-. |
|-------'# ## '. |
|-------.# # # \ |
| w |# # # ; |
|-------'# # | |
| ; # # ; |
| \ # # / |
' '. ### ######.' '
\ '-._____.-' /
'-------------------'
Namen, die über eine Namensdefinition an ein schon vorhandenes Objekt gebunden werden, nennt man auch „Referenzen“, dementsprechend wird die Objektnamensdefinition auch als „Referenzdefinition“ bezeichnet. Der Name „Referenz“ ist hier allerdings irreführend, wenn man dadurch verleitet wird, in einer Referenz etwas anderes zu sehen als in einer Variablen. Das letzte Programmbeispiel zeigte ja, daß ein durch eine Referenzdeklaration eingeführter Name nicht von einem zuvor durch eine Variablendefinition eingeführten Namen zu unterscheiden ist. Daher ist es besser, beide Namen als Objektnamen anzusehen, die sich nur in der Art ihres Zustandekommens unterscheiden, aber absolut gleichberechtigt sind, nachdem sie einmal gebildet wurden.
Da sich ein über eine Referenzdefinition eingeführter Name danach wie eine zusammen mit einem Objekt definierte Variable verhält, werden in diesem Text Referenzen auch manchmal als „Variablen“ bezeichnet. Eine Variable kann umgekehrt als eine Referenz auf ihr Objekt angesehen werden. Eine Referenz unterscheidet sich von einer anderen Variablen nur durch ihre Entstehung, aber nicht durch die Eigenschaften, die sie danach im Gebrauch hat. Allerdings können einer Referenz durch ausdrückliches Verlangen noch spezielle Eigenschaften gegeben werden, wie dies im folgenden Abschnitt beschrieben wird.
Objektnamen mit Nur-Lese-Zugriff
Variablen können sich in ihren Eigenschaften unterscheiden. In dem folgenden Beispiel wird die Variable "w" mit dem Schlüsselwort "const" vor dem Und-Zeichen "&" definiert. Das bedeutet, daß durch die Variable "w" keine Schreibzugriffe auf das Objekt möglich sein sollen. Anstatt dieser genauen Formulierung wird auch die Bezeichnung „konstante Referenz“ verwendet. Die Bezeichnung „konstante Referenz“ ist also immer so zu verstehen, daß über die verwendete Referenz keine Schreibzugriffe auf das Objekt möglich sind. (Es ist nicht so gemeint, daß die Referenz in dem Sinne konstant sei, daß sie nicht auf ein anderes Objekt geändert werden könne. Das ist zwar auch richtig, ist aber bei Variablen immer so. Es ist auch nicht notwendigerweise eine Referenz auf ein konstantes Objekt, weil das Objekt durch andere [nichtkonstante] Referenzen, ja durchaus noch verändert werden könnte, also nicht konstant sein muß.)
Im folgenden Programmbeispiel kann die Variable "w" wie bisher verwendet werden, um den Wert des Objektes zu bezeichnen. Es ist aber nicht mehr möglich, den Wert des Objektes über die Variable "w" zu verändern. Da dies aber eine Eigenschaft der Variablen "w" und nicht des Objektes selber ist, ist es über die Variable "v" weiterhin möglich, den Wert des Objektes zu verändern.
referenzen2.cpp#include <iostream>
#include <ostream>
int main()
{ std::cout << "Referenzen\n";
int v( 12 ); /* Variablendefinition und Initialisierung */
const int & w( v ); /* Namensdefinition von w zum Objekt von v */
v = 7; /* Das Objekt wird auf 7 gesetzt. */
std::cout
<< w << '\n' /* Ausgabe des Objektwertes */
<< v << '\n'; /* Ausgabe des Objektwertes */
/* w = 4; */ /* Nicht erlaubt, da w nur fuer Lesezugriff */ }
Die wesentlichen Aspekte der Situation nach der Definition der Variablen "w" werden in dem folgenden Schaubild dargestellt.
Nach der Definition von w [UML static structure diagram].----------------------.
| 12 : int |
| -------- |
| |
'----------------------'
^
| hat Wert ^
|
.--------------. .----------------------.
| v : Name | Referenz > | 006BFDF4: int-Objekt |
| -------- |----------------------->| -------------------- |
| | Lesen und Schreiben | |
'--------------' '----------------------'
.--------------. ^
| w : Name | konstante Referenz > |
| -------- |------------------------------------'
| | ueber diese Bindung nur Lesen
'--------------'
Das folgende Diagramm stellt dieselbe Situation etwas ausführlicher dar.
Referenz [UML, static structure diagram].--------. .--------.
| v:Var- | .----------. .----------. | w:Var- |
| iable | * 1 | Eigen- | | const&: | 1 * | iable |
| ------ |<>------| schaft- | | Eigen- |------<>| ------ |
| | | ten | | schaft | | |
| | '----------' '----------' | |
| | .----------. .----------. | |
| | 1 1 | v : Name | | w : Name | 1 1 | |
| |<>------| -------- | | ------- |------<>| |
| | | | | | | |
| | '----------' '----------' | |
| | | | | |
| | | * | * | |
| | '----. .----' | |
| | V Referenz | | const Referenz V | |
| | | | | |
| | | 1 | 1 | |
| | V V | |
| | .-----------. | |
| | * 1 | O:Objekt | 1 * | |
| |--------------->| -------- |<--------------| |
| | | | | |
| | '-----------' | |
| | | | | | |
'--------' .--------------' | '--------------. '--------'
| 1 | 1 | 1
v v v
.---------. .---------. .---------.
| int:Typ | | Wert | | Eigen- |
| ------- | | | | schaf- |
| | | | | ten |
'---------' '---------' '---------'
C++ überwacht den Zugriffschutz, den eine konstante Referenz bietet: Es ist zwar möglich von einer Referenz eine konstante Referenz abzuleiten, aber nicht umgekehrt von einer konstanten Referenz eine (nicht-konstante) Referenz, da dieses es erlauben würde, den Zugriffsschutz zu umgehen.
referenzen2c.cppint main()
{ int v( 2 ); /* Variablendefinition und Initialisierung */
int & r( v ); /* Referenzdefinition */
const int & c( r ); /* konstante Referenzdefinition */
int & u( c ); /* nicht erlaubt */ }Fehlermeldungenline 5: error: conversion from `const int' to `int &'
discards qualifiers
qualifiers dropped in binding reference of type "int &"
to initializer of type "const int"
Es ist natürlich nicht möglich, durch eine Referenz einen Schreibzugriff auf eine Konstante zu erhalten, da dies keinen Sinn ergeben würde, denn nur Objekte (Wertspeicher) können überhaupt verändert werden, keine Werte. In dem folgenden Programm wird eine Konstante "k" definiert. Zu solch einer Konstanten muß ja gar kein Objekt existieren. Demzufolge kann sie auch nicht zur Definition einer „weiteren Referenz“ auf ein Objekt verwendet werden.
Dies folgt der allgemeinen Regel, daß man ein veränderliches Objekt immer als Wert auffassen darf (nämlich als den momentanen Wert des Objektes), aber es umgekehrt nicht erlaubt ist, einen Wert als zeitlich veränderbares Objekt (als einen Wertspeicher) zu interpretieren.
referenzen3.cpp#include <iostream>
#include <ostream>
int main()
{ std::cout << "Referenzen\n";
const int k( 2 ); /* Definition einer Konstanten */
int & w( k ); /* Nicht erlaubt */ }
Die Referenzdefinition in dem folgenden Beispiel ist offensichtlich nicht erlaubt, da der Wert "2" kein Objekt ist.
referenzen4.cpp#include <iostream>
#include <ostream>
int main()
{ int & v( 2 ); /* Nicht erlaubt */ }
Nachdem nun so plastisch geschildert wurde, daß zu einem Wert kein Objekt gebildet werden kann, muß nun angefügt werden, daß es von dieser Regel in C++ doch auch eine Ausnahme gibt.
C++ erlaubt es nämlich einen Rechtswert zur Initialisierung von konstanten Referenzen zu verwendet. Da ein Rechtswert aber im allgemeinen nicht verändert werden kann, muß dann auch die Referenz konstant sein, also eine Referenz auf ein konstantes Objekt sein, aber immerhin auf ein Objekt. Woher soll aber solch ein konstantes Objekt kommen? Es wird in diesem Fall von C++ automatisch ein neues Objekt angelegt und mit dem gegebenen Rechtswert initialisiert! Dieses Objekt wird dann an die neue Variable gebunden.
referenzen5.cpp#include <iostream>
#include <ostream>
int main()
{ const int & v( 2 ); /* erlaubt */
std::cout
<< &v << '\n'
<< v << '\n';
const int & w( 2 ); /* erlaubt */
std::cout
<< &w << '\n'
<< w << '\n'; }std::cout006BFDF0
2
006BFDE8
2
Hier wurden zwei Objekte erzeugt und mit dem Wert "2" initialisiert.
Gibt es auch Referenzen auf Referenzen?
Eine sogenannte Referenz ist nichts weiter als eine Variable. Sie unterscheidet sich von anderen Variablen nur durch die Art der Entstehung ihrer Bindung: Bei einer Variablendefinition wird ein neuer Name an ein neues Objekt gebunden, bei einer Referenzdefinition wird ein neuer Name an ein schon existierendes Objekt gebunden.
Wird nun eine weitere Referenz unter Verwendung einer Referenz erstellt, so ist dies nur eine weitere Referenz auf das Objekt der vorgegebenen Referenz. Dies kann man schon daran erkennen, daß eine Referenz zum Objekt einer Variablen danach der ursprünglichen Variablen gleichzusetzen ist (abgesehen von Einschränkungen wie dem Nur-Lese-Zugriff). Daher ist eine Referenz, die unter Verwendung einer Referenz gebildet wird, nichts anderes als eine Referenz, die unter Verwendung einer Variablen mit demselben Objekt gebildet wird. Insofern gibt es also immer nur Referenzen auf Objekte, aber keine Referenzen auf Referenzen (oder Variablen).
referenzen6.cpp#include <iostream>
#include <ostream>
int main()
{ int v( 4 ); /* Erzeugung eines Objekts, Bindung des Namens "v" */
int & w( v ); /* Bindung des Namens "w" an dasselbe Objekt */
int & u( w ); /* Bindung des Namens "u" an dasselbe Objekt */
std::cout
<< &v << ", " << v << '\n'
<< &w << ", " << w << '\n'
<< &u << ", " << u << '\n'; }std::cout0x254fdd4, 4
0x254fdd4, 4
0x254fdd4, 4
Rückgabe von Referenzen
Die Lebenszeit von Parametern und lokalen Variablen in Funktionsinkarnationen endet (mit einer Ausnahme) mit dem Ende der Lebenszeit der Funktionsinkarnation. Daher ist es nicht sinnvoll, Referenzen auf solche Objekte aus einer Funktion zurückzugeben. Denn wenn der Aufrufer die erhaltene Referenz verwenden kann, ist das Objekt schon erloschen.
Das folgende Programm deklariert die Rückgabe einer Referenz auf ein Objekt vom Typ "double". Dadurch bewirkt die Anweisung "return result;" nicht die Rückgabe des Wertes (Rechtswertes) des Objektes "result" sondern die Rückgabe einer Referenz auf das Objekt "result".
referenzreturn.cpp#include <iostream>
#include <ostream>
double & kelvin( double const celsius )
{ double result( celsius + 273.15 );
return result; }
int main(){ std::cout << kelvin( 22.0 )<< '\n'; }
Die Inkarnation einer Funktion ist ein Behälter für alle lokalen Definitionen der Funktion. Daher endet die Lebenszeit der lokalen Objekte mit der Inkarnation der Funktion. So endet auch die Lebenszeit der Objektes "result" mit dem Ende der Funktionsinkarnation der Funktion "kelvin". Die zurückgegebene Referenz ist für den Klienten der Funktion also bedeutungslos und darf nicht verwendet werden, weil sie eine Referenz auf ein nicht mehr existentes Objekt darstellt.
Rückgabe einer Referenz auf nicht mehr existentes Objekt|Zeit main
| .-------------------.
| | |
| | |
| | << kelvin( 22.0 ) | double & kelvin
| | | .--------------------.
| | | | ( double const |
| | '-------> celsius ) |
| | | { double | Anfang der
| | | result ( | Lebenszeit von
| | | celsius + 273.15 );| "result"
| | | |
| | Referenz auf das | return result;--. |
| | Objekt "result" | } | | Ende der
| | .-----------------------------' | Lebenszeit von
| | | '--------------------' "result"
| | | |
| | V |
| | << kelvin( 22.0 ) |
| | |
| | |
| | | Fehler: Bezug auf nicht mehr
| | | existentes Objekt.
V '-------------------'
In bestimmten Fällen kann die Rückgabe einer Objektreferenz sinnvoll sein (nämlich dann, wenn das Objekt noch weiterexistiert). Man muß aber grundsätzlich immer sicherstellen, daß nur Referenzen auf existierende Objekte verwendet werden.
Ein solcher Fall kann gegeben sein, wenn eine Funktion ein vorübergehendes Objekt zurückgibt, das an einen Referenzparameter gebunden ist. Dessen Existenz ist während der Auswertung des den Funktionsaufrufs enthaltenden Vollausdrucks garantiert. Von dieser Garantie sollte aber nur mit Vorsicht Gebrauch gemacht werden, weil anscheinend harmlose Änderungen an solch einem Vollausdruck dann zu Fehlern führen können.
Referenzparameter
Wie bisher beschrieben, kann eine Variable durch eine Referenzdefinition so initialisiert werden, daß sie ein schon vorhandenes Objekt verwendet. Auch ein Parameter kann so initialisiert werden, wenn er als Referenzparameter definiert wird.
referenzparameter.cpp#include <iostream>
#include <ostream>
void ausgabe( const int & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ int o = 7; std::cout << &o << '\n';
ausgabe( o ); }std::cout006BFDF4
006BFDF4:7
Der Wert eines Argumentes steht in der erzeugten Inkarnation der aufgerufenen Funktion als Wert des Parameters zur Verfügung. Daher wird der Wert "7" aus dem Objekt "o" nach dem Aufruf "ausgabe( o )" der Funktion "ausgabe" zugänglich sein.
Wenn ein Parameter einer Funktion als Referenzparameter deklariert wurde, dann wird für diesen Parameter bei Aufrufen der Funktion ein besonders Übergabeverfahren eingesetzt: Die Referenzübergabe.
Bei der Referenzübergabe wird Information über das Argumentobjekt (und nicht nur sein Wert) an die Inkarnation der Funktion übergeben. Der Aufruf »ausgabe( o )« übergibt also Information über das ganze Objekt "o" an die Funktion »ausgabe«. Das sieht man auch daran, daß Adressen und Wert des Objektes der Funktion im Programm »referenzparameter.cpp« bekannt sind.
referenzparameter1.cpp#include <iostream>
#include <ostream>
void ausgabe( const int & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ ausgabe( 7 ); }std::cout006BFDF4:7
Wenn die Referenz konstant ist, kann auch ein Wert („Rechtswert“) als Argument angegeben werden, zu dem dann automatisch ein vorübergehendes Objekt erzeugt wird. Die Veränderung eines vorübergehenden anonymen Objektes ergäbe keinen Sinn, weil auf solch ein Objekt nach der ersten Verwendung kein weiterer Zugriff mehr möglich ist. Wenn ein Parameter für eine konstante Referenz steht, dann besagt dies, daß über diesen Parameter auch gar keine Veränderungen des Objektes erfolgen sollen. Daher ist es dann möglich, ein vorübergehendes Objekt für diesen Parameter zu verwenden.
Der Aufruf "ausgabe( 7 )" wäre bei einem Parameter vom Typ einer nicht-konstanten Referenz nicht möglich, da der Ausdruck "7" kein Objekt notiert, sondern einen Wert (ein Wert kann nicht verändert werden). Ist der Referenzparameter jedoch konstant, so wird ein vorübergehendes Objekt erzeugt, mit dem Wert "7" initialisiert und vorübergehend (während der Inkarnation der Funktion "ausgabe") für den Parameter verwendet. Das geschieht genau so, wie es auch bei einer durch eine konstante Referenz erzeugten Variablen geschehen würde, die beispielsweise mit "const int & v( 2 );" definiert wird.
Bei größeren Objekten würde es viel Zeit kosten, wenn deren Wert an eine Funktion übergeben werden würde, weil ein Wert eines größeren Objektes viel Speicher umfassen kann, der dabei kopiert werden müßte. Daher ist es in C++ üblich, größere Objekte als Referenz zu übergeben. Damit dann aber auch weiterhin Rechtswerte als Argument angegeben werden können, wird dann oft eine konstante Referenz als Parameter verwendet.
referenzparameter2.cpp#include <iostream>
#include <ostream>
void ausgabe( const int & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ int o = 7; std::cout << &o << '\n';
ausgabe( o );
ausgabe( o + 1 ); }std::cout006BFDF4
006BFDF4:7
006BFDF0:8
Auch der Ausdruck "o + 1" im Programm »referenzparameter2.cpp« ist kein Objekt. Trotzdem kann auch er als Argument einer Funktion mit einem konstanten Referenzparameter verwendet werden, denn es wird wieder ein vorübergehendes Objekt erzeugt und mit dem Wert des Ausdrucks initialisiert.
Nichtkonstante Referenzparameter
Ein Referenzparameter kann auch nichtkonstant sein. In diesem Fall gilt alles vorher Gesagte, nur daß dann als Argument nur Objekte („Linkswerte“) in Frage kommen. Es werden dann keine vorübergehenden Objekte erzeugt.
referenzparameter3.cpp#include <iostream>
#include <ostream>
void ausgabe( int & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ int o = 7; std::cout << &o << '\n';
ausgabe( o ); /* Übergabe des Objektes o */
/* ausgabe( 7 ); */ /* nicht erlaubt */ }std::cout006BFDF4
006BFDF4:7
Bei einem nichtkonstanten Referenzparameter ist es aber auch möglich, daß eine Funktion den Wert in einem Argumentobjekt verändert. In solch einem Fall kann die Funktion aber nicht die Adresse oder den Datentyp des Objekts ändern, sondern nur den Wert (Inhalt) des Objekts. Da die Funktion dann aber keine Kopie des Wertes sondern eine Referenz auf das Argumentobjekt selber hat, kann sie dessen Wert auch dauerhaft verändern—die Veränderung bleibt nach dem Ende der Inkarnation der Funktion bestehen.
referenzparameter4.cpp#include <iostream>
#include <ostream>
void eight( int & parameter ){ parameter = 8; }
void show( const int & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int o = 7; show( o ); eight( o ); show( o ); }std::cout006BFDF4:7
006BFDF4:8
Ein Problem kann es werden, wenn der Benutzer einer Funktion sich nicht bewußt ist, daß eine Funktion den Wert eines bestimmten Argumentes verändern kann. Daß dies geschieht, ist nämlich eher selten. Manche sehen dies als ein Argument gegen Referenzparameter an. Andere weisen darauf hin, daß der Benutzer einer Funktion deren Spezifikation kennen sollte und dieser ja entnehmen kann, ob die Funktion ein bestimmtes Argumentobjekt verändern könnte.
Variablenparameter
Bei einem konstanten Referenzparameter wird ein Objekt übergeben. Wird ein Rechtswert als Argument angegeben, dann wird ein vorübergehendes Objekt erzeugt.
Es ist auch möglich, immer ein vorübergehendes Objekt für einen Parameter zu erzeugen. In diesem Fall wird der Parameter nicht wie eine Referenz, sondern wie eine Variable definiert. Genau wie bei einer Variablendefinition, wird dann immer ein neues Objekt für den Parameter erzeugt und der Wert des Argumentes wird in dieses neue Parameterobjekt kopiert. Da hierbei nur der Wert übergeben wird und nicht das gesamte Objekt wird diese Art der Parameterübergabe im Englischen auch als “by value ” (mit Wertübergabe) bezeichnet. Das Parameterobjekt kann dann in der Funktion verändert werden, aber dies hat keine Auswirkungen auf den Wert des Argumentobjekts.
parameter.cpp#include <iostream>
#include <ostream>
void ausgabe( int parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n';
parameter = 8;
std::cout << parameter << '\n'; }
void show( const int & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int argument( 7 ); show( argument );
ausgabe( argument ); show( argument ); }std::cout006BFDF4:7
006BFDA4:7
8
006BFDF4:7
Ein Referenzparameter bezieht sich direkt auf das beim Aufruf angegebene Objekt.
Referenzparameter.--------------------.
| Argumentobjekt | Referenz auf Argumentobjekt
| |<------------------------------.
| | |
'--------------------' |
| |
| |
| |
.--------------------. .--------------------.
| Aufruf | | Referenzparameter |
| | | |
| | | |
'--------------------' '--------------------'
Für einen Variablenparameter wird bei der Inkarnation einer Funktion ein vorübergehendes Parameterobjekt erzeugt, der Wert des Argumentobjektes wird daraufhin in das Parameterobjekt kopiert. Am Ende der Inkarnation wird das vorübergehende Parameterobjekt wieder aufgelöst.
Variablenparameter.--------------------. .--------------------.
| Argumentobjekt | | Parameterobjekt |
| |------------------->| - Erzeugung |
| | Kopieren des | - Vernichtung |
'--------------------' Wertes '--------------------'
| ^
| |
| |
.--------------------. .--------------------.
| Aufruf | | Variablenparameter |
| | | |
| | | |
'--------------------' '--------------------'
Konstante Variablenparameter
Oft ist es sinnvoll, den Werte eines Variablenparameters nicht zu verändern, da dieser eine von außen in die Inkarnation der Funktion gegebene Information darstellt. Dann empfiehlt es sich diesen Parameter mit »const« zu kennzeichnen, wodurch dessen Wert nicht mehr verändert werden kann.
constparameter.cpp#include <iostream>
#include <ostream>
void ausgabe( const int parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
void show( const int & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int o = 7; show( o );
ausgabe( o ); show( o ); }std::cout006BFDF4:7
006BFDA4:7
006BFDF4:7
Wahl der Parameter-Art
Primitive Zahlenobjekte und Zahlenwerte brauchen relativ wenig Speicherplatz, sind leicht zu erzeugen und aufzulösen. Die Verwaltung eines Referenzparameters erfolgt intern meistens durch eine Objektadresse, was aufwendiger ist als eine direkte Kopie eines Zahlenwertes. Daher ist es für Zahlen (Gleitkommazahlen und Ganzzahlen) effizienter, in konstanten Variablenparameter gehalten zu werden.
Soll ein beim Aufruf angegebenes Objekt verändert werden, dann muß jedoch ein nichtkonstanter Referenzparameter eingesetzt werden.
Die konstanten Referenzparameter haben auch ein wichtiges Anwendungsgebiet, das aber erst später behandelt werden wird.
Übungsaufgaben
- Übungsaufgabe Vertauschen
Schreiben Sie eine Funktion "swap", welche die Inhalte zweier Objekte vom Datentyp "int" vertauscht.
swap.c#include <iostream>
#include <ostream>
// hier "swap" definieren
int main()
{ int a( 1 ); int b( 7 );
swap( a, b ); std::cout << a << b << '\n'; }std::cout71
Hinweis Überlegen Sie für jeden Parameter, ob er ein Variablenparameter oder ein Referenzparameter sein sollte. Bedenken Sie weiterhin für jeden Parameter, ob er ein konstanter oder nichtkonstanter Referenzparameter bzw. ein konstanter oder nichtkonstanter Variablenparameter sein sollte.
- Übungsaufgabe Zuweisung
Schreiben Sie eine Funktion "assign", welche einem Objekt einen Wert zuweist.
swap.c#include <iostream>
#include <ostream>
// hier "assign" definieren
int main()
{ int a; assign( a, 7 ); std::cout << a << '\n'; }std::cout7
Hinweis Überlegen Sie für jeden Parameter, ob er ein Variablenparameter oder ein Referenzparameter sein sollte. Bedenken Sie weiterhin für jeden Parameter, ob er ein konstanter oder nichtkonstanter Referenzparameter bzw. ein konstanter oder nichtkonstanter Variablenparameter sein sollte.