Kontynuując temat z poprzedniego wpisu, Wskaźniki w C++, referencje. Ściągawka, dziś ciąg dalszy. Jak wygląda przekazywanie parametrów do funkcji w C++? Jak przekazać wartości do funkcji, aby można było zmodyfikować wartość parametru?
Pierwsze, co przychodzi do głowy to coś takiego – zwykłe przekazanie parametru jako wartość:
Co tu się jednak stało?
Wartość zmiennej liczba1
nie zmieniła się. W funkcji Zwieksz(int liczba)
, liczba
jest zmienną lokalną. Po wyjściu z zakresu funkcji, wszystko zostaje zapomniane. Wartość nie zostaje nijak przekazana do liczba1
, przekazanej jako parametr.
Oczywiście można było zrobić return liczba
, jednak nie o to chodzi: wyobraźmy sobie, że funkcja ma zmodyfikować cały szereg wartości. Dodatkowo takie działanie jest nieoptymalne – w momencie rozpoczęcia działania funkcji zostaje utworzona kopia obiektu.
Wniosek? Nawet proste typy muszą być przekazywane przez wskaźnik albo referencję.
Przekazywanie parametrów przez referencję
Jedyna zmiana w stosunku do poprzedniego przykładu, to dodanie znaczka ampersand w nagłówku funkcji: ZwiekszPrzezReferencje(int &liczba)
. W środku jednak dzieje się magia i przykład zaczyna działać. Nawet po wyjściu poza zakres funkcji, zmienna liczba1
została zmodyfikowana, dlatego że referencja jest tożsama z obiektem, do którego się odnosi. Zatem &liczba
jest dokładnie tym samym, co liczba1
. A skoro jest tym samym, to zmieniona w jednym miejscu, zmienia wartość liczba1
globalnie. Dość proste, prawda?
Przekazywanie parametrów przez wskaźnik = przekazywanie przez wartość
Poza referencją, jest jeszcze druga opcja – przekazywanie przez wskaźnik. Tak naprawdę jest to przekazywanie przez wartość, czyli takie samo, jak w pierwszym („niedziałającym”) przykładzie. Tutaj jednak będzie inaczej, bo używamy wskaźnika. Podczas przekazywania do funkcji argumentów przez wartość, na początku funkcji tworzona jest lokalna kopia wszystkich parametrów przekazanych do funkcji. Ponieważ jednak jako argument funkcji podajemy wskaźnik, owszem, zostanie on skopiowany do lokalnej zmiennej wskaźnikowej, ale nic nam to nie szkodzi! Jego wartość to nadal poprawny adres pamięci do naszej zmiennej liczba1
, co za tym idzie, liczba1
zostanie zmodyfikowana wewnątrz funkcji.:
Przekazywanie typów złożonych, własnych klas
Żeby unaocznić sobie fakt kopiowania obiektów, można do tego użyć bardziej skomplikowanego przykładu – napisać swoją klasę, a w niej stworzyć konstruktor kopiujący, który uruchamiając się, wypisze tekst na ekran.
I tak oto, zwykłe przekazanie klasy przez wartość, bez przekazywania jej ze wskaźnikiem. Jak można się domyślić, wewnątrz funkcji powstanie kopia, która zostanie usunięta wraz z wyjściem z funkcji:
Jeszcze raz ten sam przykład z klasami, tylko tym razem przekazywanie ich przez referencję oraz przez wartość (wskaźnik):
Widać, że konstruktor kopiujący klasy uruchomił się tylko raz – w „niepoprawnym”, pierwszym przykładzie podanym wyżej. W przypadku przekazywania klasy przez referencję czy wskaźnik, nie uruchomił się konstruktor kopiujący. Operacje odbywają się na obiekcie przekazanym do funkcji z zewnątrz.
Przykład z życia – typ QString
W Qt jest taki typ jak QString
, będący reprezentacją ciągu znaków. Taki typ można przekazać na różne sposoby:
foo(QString zmienna)
– Przekazanie przez wartość. Wywoła się konstruktor kopiujący obiektu QString, tworząc lokalną kopię wewnątrz funkcji. Zmienna zmodyfikowana w funkcji nie zostanie zmodyfikowana na zewnątrz.foo(QString &zmienna)
– Przekazanie przez referencję. Pozwala na modyfikowanie zmiennej wewnątrz funkcji. W Qt w ten sposób najwygodniej przekazywać okna – brak nieoptymalnych kopiowań obiektu (bo jest referencja), zachowana możliwość modyfikowania.foo(QString *zmienna)
– Przekazanie przez wskaźnik. Pozwala na modyfikowanie. Skomplikowany temat, szczególnie jeśli dołożymy do tegoconst
. Raczej niepolecany sposób 😉foo(const QString &zmienna)
– Przekazanie przez referencję. Przekazana zmienna nie będzie modyfikowana wewnątrz funkcji (jestconst
). Najlepszy (najbardziej optymalny) sposób przekazywaniaQString
: brak niepotrzebnych kopiowań itd.
Kod źródłowy do przykładów
Cały kod dostępny jest tutaj: https://bitbucket.org/snippets/d9k/9exyp8. Moja wtyczka do prezentacji kodu na WordPressie nie chce współpracować z niektórymi znakami specjalnymi 😉 Tutaj kod jest bardziej bezpieczny i czytelny. W razie czego, mam nadzieję że zrzuty ekranu są wystarczające.