Podręcznik
Wersja podręcznika: 1.0
Data publikacji: 01.01.2022 r.
8. Funkcje
8.2. Przekazywanie argumentów do funkcji
Domyślnie argumenty przekazujemy do funkcji przez wartość - czyli robiona jest kopia przekazywanej zmiennej - dostępna w funkcji. Taki sposób jest dobry, pod warunkiem że wartość argumentu nie będzie zmieniana wewnątrz funkcji (jest to p-wartość), oraz że przekazywany argument ma niewielki rozmiar. Dlaczego akurat te warunki muszą zostać spełnione?
Argumenty przekazywane jako wartość są przekazywane poprzez stos, przy czym - jak wspomniałem - przy każdym wywołaniu funkcji jest tworzona na stosie ich kopia. Stąd ograniczenie na wielkość argumentu przekazywanego przez wartość - wykonywanie po kilkanaście tymczasowych kopii dużego obiektu zakrawa na śmieszność – ze względu na zużycie pamięci, oraz czas potrzebny na kopiowanie.
Inna właściwość, która się co prawda z poprzednią nie wiąże w sposób logiczny, ale której brak jest również mankamentem przekazywania przez wartość, to fakt, że wewnątrz funkcji nie mamy żadnego wpływu na zmienne, jakie jej zostały przekazane. Klasycznym przykładem, który to obrazuje, jest funkcja o nazwie 'swap', której zadaniem jest zamienić miejscami wartości w dwóch zmiennych.
Pierwsze podejście do funkcji mogłoby wyglądać następująco:
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
...
int main() {
int x=10, y=20;
swap(x, y);
cout << x << " " << y;
...
}
Lecz po jej wywołaniu okaże się, że zmienne w funkcji nadrzędnej nie zostały zmienione. Funkcja operuje na kopiach wartości argumentów, które jednocześnie są jej zmiennymi lokalnymi.
Poznaliście wskaźniki, więc może ich wykorzystanie będzie jakimś rozwiązaniem? Przekazywana jest co prawda nadal wartość, ale tą wartością jest wskaźnik do zmiennej, wystarczy więc posłużyć się wyłuskiwaniem:
void swap(int *a, int *b ) {
int temp = *a;
*a = *b;
*b = temp;
}
...
int main() {
int x=10, y=20;
swap(&x, &y);
cout << x << " " << y;
...
}
Niestety, jak zauważyliście, konieczna jest zmiana sposobu wywołania funkcji, co nie wygląda najlepiej.
W C++ jest jeszcze jedna możliwość (w starym C niedostępna) – przekazywanie parametrów przez referencję. Wygląda to podobnie do definicji zmiennych typu referencyjnego- wystarczy zmienić nagłówek:
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
...
int main() {
int x=10, y=20;
swap(x, y);
cout << x << " " << y;
...
}
I voila – otrzymujemy to, o co nam chodziło.
Tak na boku - w C++ ta funkcja jest już dostarczona w bibliotece standardowej, i to w formie szablonowej, gdzie argumenty mogą być dowolnego typu.
W przypadku kiedy zakładamy, że przekazywany argument nie powinien być zmieniany w funkcji, ale ciągle chcemy uniknąć jego kopiowania przy wywołaniu funkcji - możemy stosować stałe referencje:
void funkcja(const MojTyp& duzy) {
// tu nie można zmienić wartości przechowywanej w duzy
}
Podsumowując:
- W C++ możemy przekazywać argumenty do funkcji przez wartość i przez referencję (pamiętajcie – przekazanie wskaźnika jest przekazaniem przez wartość),
- Jeśli argumenty są małe (w sensie zajętości pamięci) i nie są zmieniane wewnątrz funkcji (a przynajmniej ta zmiana nie musi być widoczna poza ciałem funkcji) – korzystamy z przekazywania przez wartość.
- W pozostałych przypadkach powinien być przekazany wskaźnik, lub wykorzystana referencja.
- Dodatkowo, warto pamiętać, że tablice zawsze są przekazywane do funkcji jako wskaźnik.
- Praktyczna rada - jeśli zależy Wam na wydajności, obiekty przekazujcie przez (potencjalnie stałe) referencje, typy proste - przez wartość.