Benutzerdefinierte Typen als Parametertypen
Wie schon im Grundkurs an Hand von »::std::string« gezeigt, kann der Typ eines Parameters auch ein benutzerdefinierter Typ sein.
Die Initialisierung von Parametern durch Argumentwerte gilt als Kopier-Initialisierung.
Der Parameter wird initialisiert wie bei einer Kopier-Initialisierung »::std::string const s = "alpha"«.
instance_parameter.cpp
#include <iostream>
#include <string>
void print( ::std::string const s ){ ::std::cout << s << '\n'; }
int main(){ print( "alpha" ); }::std::cout
alpha
Das folgende Programm zeigt, daß als Argument auch eine geschweifte-Klammer-Initialisierungsliste erlaubt ist. Der Parameter wird dann damit initialisiert wie bei einer Kopier-Initialisierung »::std::string const s = { "alpha" }«.
instance_parameter.cpp
#include <iostream>
#include <string>
void print( ::std::string const s ){ ::std::cout << s << '\n'; }
int main(){ print( { "alpha" } ); }::std::cout
alpha
Die Aufrufe mit Typwandlung wären also nicht gestattet, wenn diese explizit wäre.
instance_parameter.cpp
#include <iostream>
#include <string>
void print( ::std::string const s ){ ::std::cout << s << '\n'; }
int main(){ print( { 65, 66 } ); }::std::cout
AB
Dies verhält sich wie eine Kopierinitialisierung mit Initialisierungsliste »::std::string const s = { 65, 66 }«
- Guidelines
- GUIDELINE: non-fundamental types are passed by const reference (Hier kommt heute auch by value in Frage, wenn ohnehin eine Kopie benötigt wird)
- GUIDELINE: types passed by value: iterators, function objects (including predicates), built-in types
- GUIDELINE: where there are output parameters the parameters are passed by reference, not by pointer
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>
#include <initializer_list>
void ausgabe( int const & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ int o = 7; std::cout << &o << '\n';
ausgabe( o ); }std::cout
006BFDF4
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>
#include <initializer_list>
void ausgabe( int const & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ ausgabe( 7 ); }std::cout
006BFDF4: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 "int const & 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>
#include <initializer_list>
void ausgabe( int const & parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n'; }
int main()
{ int o = 7; std::cout << &o << '\n';
ausgabe( o );
ausgabe( o + 1 ); }std::cout
006BFDF4
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>
#include <initializer_list>
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::cout
006BFDF4
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( int const & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int o{ 7 }; show( o ); eight( o ); show( o ); }std::cout
006BFDF4: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>
#include <initializer_list>
void ausgabe( int parameter )
{ std::cout <<
¶meter << ':' <<
parameter << '\n';
parameter = 8;
std::cout << parameter << '\n'; }
void show( int const & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int argument{ 7 }; show( argument );
ausgabe( argument ); show( argument ); }std::cout
006BFDF4: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>
#include <initializer_list>
void ausgabe( int const parameter )
{ ::std::cout <<
¶meter << ':' <<
parameter << '\n'; }
void show( int const & i )
{ std::cout << &i << ':' << i << '\n'; }
int main()
{ int o{ 7 }; show( o );
ausgabe( o ); show( o ); }std::cout
006BFDF4:7
006BFDA4:7
006BFDF4:7
Wahl der Parameter-Art
Fundamentale 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.