Wenn du einer Funktion Parameter übergibst, solltest du dir darüber Gedanken machen, ob dies als Wertparameter (call-by-value) oder als Referenzparameter (call-by-reference) geschieht. Gleichermaßen solltest du dir beim Schreiben einer Funktion überlegen, welche Art der Parameterübergabe für deinen Zweck am besten ist. Dieser Artikel erklärt dir den Unterschied zwischen call-by-value und call-by-reference und gibt dir praktische Tipps für deren Verwendung.
Zusammenfassung
- Call-by-Value (Wertparameter): Parameter werden in vielen Programmiersprachen als Kopie übergeben, Änderungen haben keine Auswirkungen auf den Originalwert.
- Call-by-Reference (Referenzparameter): Änderungen an den Parametern wirken sich auf die ursprünglichen Werte aus, z. B. bei Funktionen mit mehreren Rückgabewerten.
- Kopie einer Referenz: Bei Objektparametern werden in vielen Sprachen nur Referenzen oder Zeiger übergeben, nicht das gesamte Objekt.
- Verwendung von const: In C++ können Objekte, die nicht geändert werden sollen, als konstant markiert werden, um Fehler zu vermeiden.
- Java: Nur Call-by-Value, bei Werttypen werden Werte kopiert, bei Referenztypen Referenzen.
- C#: Standardmäßig Call-by-Value, aber Möglichkeit zur Erzwingung von Call-by-Reference mit den Schlüsselwörtern “ref” und “out”.
- Unterschiede beachten: Der Unterschied zwischen Call-by-Value und Call-by-Reference beeinflusst den Programmverlauf und sollte bei der Programmierung berücksichtigt werden.
Call-by-Value – Wertparameter
In vielen Programmiersprachen werden im Normalfall Parameter an Funktionen mithilfe einer Kopie übergeben. Das wird als call-by-value bezeichnet. Das bedeutet, dass innerhalb der aufgerufenen Funktion mit der Kopie gearbeitet wird und sich Änderungen nicht auf den ursprünglichen Wert auswirken.
Definition einer Funktion mit Wertparametern in C++
// Addiere a und b.
int CHelper::Sum(int a, int b) {
// Sowohl 'a' als auch 'b' werden "by value" übergeben
return a + b;
}
Call-by-Reference – Referenzparameter
Bei einer Übergabe als Referenz wirken sich Änderungen an den Parametern auf die ursprünglichen Werte aus. In der Praxis wird das zum Beispiel verwendet, wenn eine Funktion mehrere Rückgabewerte hätte. Nachfolgend ein Beispiel für eine Funktion, die call-by-reference nutzt:
Definition einer Funktion mit Referenzparametern in C++
void CHelper::Swap(int& a, int& b) {
// Sowohl 'a' als auch 'b' werden "by reference" übergeben,
// deshalb ist eine Zuweisung möglich.
int temp = a;
a = b;
b = temp;
}
Die Funktion Swap tauscht die Inhalte der Variablen. Damit sie das auch machen kann, müssen die Werte per Referenz übergeben werden, ansonsten hätte die Funktion gar keinen Einfluss auf die ursprünglichen Variablen.
Kopie einer Referenz
Wenn es sich bei Funktionsparametern um Objekte handelt, ist es in vielen Programmiersprachen üblich, dass für diese Objekte lediglich Referenzen oder Zeiger übergeben werden, also nur die Adresse an der das jeweilige Objekt gespeichert ist. Kommt in so einem Fall call-by-value zum Einsatz wird eben nicht das Objekt kopiert, sondern das Element, dass die Speicheradresse des Objekts enthält. Referenzen können in C++ nicht kopiert werden, Pointer hingegen werden standardmäßig kopiert, wie das nachfolgende Beispiel verdeutlicht:
Übergabe eines Pointers by-value
void PrintAndDeleteMyObject(CExampleClassBase* object) {
// Print() vom übergebenen Objekt wird ausgeführt
object->Print();
// Lösche das Objekt
delete object;
// Der Zeiger wird hier nur lokal zurückgesetzt,
// für den Aufrufer der Funktion ändert sich nichts.
// D.h. es steht im ursprünglichen Zeiger noch
// immer die Adresse des gelöschten Objektes drin,
// was zu Fehlern führen kann.
object = NULL;
}
Möchtest du den ursprünglichen Zeiger der Funktion manipulierbar übergeben, kannst du den Pointer mit dem &-Operator auch als Referenz übergeben:
void PrintAndDeleteMyObject(CExampleClassBase*& object) {
// Print() vom übergebenen Objekt wird ausgeführt
object->Print();
// Lösche das Objekt
delete object;
// Der Zeiger des Aufrufers wird jetzt auf NULL
// zurückgesetzt. Dies funktioniert nur, weil
// der Zeiger als Referenz übergeben wurde.
object = NULL;
}
Einen Zeiger per Referenz zu übergeben wird in der Praxis so gut wie nie benötigt. Du solltest vor der Verwendung von solchen Konstrukten gut überlegen, ob es nicht einen einfacheren, besseren Weg gibt.
Call-by-Value und Call-by-Reference in C++
In C++ ist so wie in vielen anderen Programmiersprachen call-by-value der Normalfall. Es werden also sowohl Pointer als auch normale Werte bei der Übergabe an Funktionen kopiert. Durch die Verwendung des &-Zeichens kannst du call-by-reference erzwingen und damit eine Kopie vermeiden. Objekte werden häufig mit einem Pointer oder per Referenz übergeben, da meistens keine Kopie des Objektes gewünscht ist.
Übergibst du ein Objekt das nicht geändert werden soll per Referenz, kannst du es mit dem Schlüsselwort const als konstant und damit “read-only” markieren. Eine möglichst umfangreiche Verwendung von const nennt man const-correctness, eine fortgeschrittene Programmiertechnik in C++ mit der Fehler vermieden werden können.
Es kann auch einmal vorkommen, dass es nötig ist, die Adresse eines Pointers von einer Funktion manipulieren zu lassen. In diesem Fall kannst du den Pointer per Referenz übergeben (siehe obiges Beispiel).
Call-by-Value und Call-by-Reference in Java
In Java gibt es ausschließlich call-by-value. Das bedeutet, dass bei Werttypen (primitive Datentypen wie beispielsweise Integer) die Werte und bei Referenztypen (Objekte) die Referenzen auf Objekte kopiert werden. Nochmal langsam: wenn du einen Integer, Float o.Ä. übergibst, wird der Wert kopiert und an den ursprünglichen Werten des Aufrufers kann nichts mehr geändert werden. Wenn du ein Objekt übergibst, wird die Referenz kopiert. Das bedeutet aber, dass noch immer das gleiche Objekt referenziert wird, Änderungen darauf wirken sich also sehr wohl auf das ursprüngliche Objekt aus.
Wertparameter und Referenzparameter in Java
public void printData(int wertParameter, Object referenzParameter) {
System.out.print(wertParameter);
System.out.print(referenzParameter);
return;
}
Beide Parameter von printData werden kopiert. Zuweisungen an wertParameter würden sich nur innerhalb der Funktion auswirken. Änderungen an dem Objekt auf das referenzParameter zeigt, würden sich auch nach außen hin auswirken, da ja das Objekt und nicht die kopierte Referenz geändert werden würde.
Call-by-Value und Call-by-Reference in C
Java und C# sind sich ja als Programmiersprachen sehr ähnlich. Auch bei der Parameterübergabe unterscheiden sie sich nicht großartig. Standardmäßig wird auch in C# by-value übergeben, bei Werttypen werden die Werte kopiert, bei Referenztypen die Referenzen. Allerdings bietet C# auch noch die Möglichkeit call-by-reference zu erzwingen. Dazu gibt es die Schlüsselwörter ref und out.
call-by-value in C#
public void swapContent(ref int a, ref int b) {
int temp = a;
a = b;
b = temp;
}
Durch das Schlüsselwort ref wird bei der Parameterübergabe call-by-reference erzwungen und Änderungen an den Parametern wirken sich auch auf die ursprünglichen Werte aus.
Fazit
Der Unterschied zwischen call-by-value und call-by-reference kann den Programmverlauf deutlich beeinflussen. Damit du gute Programme schreiben kannst, solltest du dir also immer im Klaren sein, was bei einem Funktionsaufruf mit deinen Parametern genau passiert. Am besten öffnest du gleich die Entwicklungsumgebung deiner Wahl und probierst die verschiedenen Möglichkeiten im Debugger aus.