Przekazywanie argumentów do funkcji C++

Ostatania modyfikacja: 28 września 2017, kategoria: C++

Funkcje nie byłyby tak użyteczne, gdyby nie dało się do nich przekazywać argumentów. Osoby zaczynające przygodę z programowaniem często mylą sposoby ich przekazywania. Warto poznać różnice jakie istnieją w przekazywaniu argumentów do funkcji z użyciem różnych metod.

Przekazywanie argumentów przez wartość

Najpopularniejszą metodą budowania funkcji jest przekazywanie argumentów przez wartość. Funkcja musi zawierać argumenty wraz z ich typami, oddzielone przecinkami. Kiedy przekazujemy do funkcji jakąś zmienną, zostaje utworzona w pamięci jej kopia. Wszystko co dzieje się wewnątrz funkcji odbywa się tak naprawdę na kopii zmiennej przekazanej w argumencie poprzez wartość.

W takim wypadku konieczne staje się zwrócenie odpowiedniej wartości za pomocą return i przypisanie zwróconej wartości do odpowiednich zmiennych. Jeżeli funkcja nie zwróci żadnej wartości, wtedy jej wywołanie może stać się bezcelowe. Utworzona kopia zmiennej, nawet jeżeli zostanie zmodyfikowana, zostanie usunięta z pamięci po zakończeniu funkcji.

Podsumowując przekazywanie przez wartość:

  • zmienna przekazana do funkcji jest jej kopią
  • wszelkie operacje odbywają się na kopii, więc nie są widoczne poza blokiem funkcji
  • aby jakiekolwiek zmiany były widoczne poza ciałem funkcji, należy przypisać do odpowiednich zmiennych poza funkcją wartość zwracaną przez funkcję (return)
  • ponieważ funkcja może zwrócić tylko jedną wartość (return) – możliwe jest zmodyfikowanie tylko jednej zmiennej z poza funkcji
#include <iostream>
#include <cstdlib>

using namespace std;

int zwieksz(int liczba)
{
    // zmienna 'liczba' jest kopią zmiennej 'dlugosc'

    liczba = liczba * 2;

    return liczba;
}

int main()
{
    int dlugosc = 125;

    // przypisujemy nową wartość dla zmiennej
    dlugosc = zwieksz(dlugosc);

    cout << dlugosc << endl;

    system("pause >nul");
    return 0;
}

Przekazywanie argumentów przez wskaźnik

Przekazywanie argumentów przez wskaźnik jest możliwe zarówno w języku C++ jak i C. Użycie wskaźników razem z funkcjami jest bardzo wygodne i ekonomiczne. Zdarzają się sytuacje, kiedy skorzystanie ze wskaźników jest niezbędne do osiągnięcia odpowiedniego efektu.

Przekazując argumenty przez wartość, funkcja może zwrócić tylko jeden parametr za pomocą return. Oznacza to, że możliwe jest zmodyfikowanie wartości tylko jednej zmiennej występującej poza wywoływaną funkcją (tak jak w akapicie wyżej). Co w przypadku gdy podczas wywołania jednej funkcji chcemy zmienić kilka zmiennych? – tutaj niezbędne są wskaźniki. Jest to najlepszy przykład na zobrazowanie tego, dlaczego wskaźniki są przydatne.

Podsumowując przekazywanie przez wskaźnik:

  • argumenty wskaźnikowe wskazują na zmienne z poza funkcji, więc wewnątrz funkcji nie są tworzone kopie
  • funkcja może zmodyfikować wiele zmiennych z poza funkcji (bez użycia return)
  • wskaźniki także przekazujemy przez wartość, wynika to z faktu że wskaźnik też jest typem zmiennej (zmienna wskaźnikowa), jednak mimo że wskaźnik (adres miejsca w pamięci) zostanie skopiowany  to wartość wyłuskana ze wskaźnika nie jest już kopią
#include <iostream>
#include <cstdlib>

using namespace std;

void zwieksz_kilka(int *dl, int *wys, int *waga)
{
    // zmienna '*dl', '*wys' i '*waga' nie są kopiami
    // operowanie na nich zmienia ich wartość w "całym" programie
    // funkcja nie zwraca nic - bo nie ma sensu

    *dl = *dl * 2;
    *wys = *wys * 2;
    *waga = *waga * 2;
}

int main()
{
    // zmienne
    int dlugosc = 125;
    int wysokosc = 300;
    int waga = 20;

    // wskaźniki do zmiennych
    int *wsk_dlugosc = &dlugosc;
    int *wsk_wysokosc = &wysokosc;
    int *wsk_waga = &waga;

    // wywołanie funkcji
    zwieksz_kilka(wsk_dlugosc, wsk_wysokosc, wsk_waga);

    // wyświetlenie nowych wartości
    cout << dlugosc << endl;
    cout << wysokosc << endl;
    cout << waga << endl;

    system("pause >nul");
    return 0;
}

Zwróć uwagę, argumenty funkcji to zmienne wskaźnikowe czyli „adresy do zmiennych”. Gwiazdka w argumentach nie jest operatorem wyłuskania, informuje ona kompilator że zmienna jest wskaźnikiem (czyli „adresem”). Jak można wykorzystać ten fakt? Nie trzeba deklarować zmiennych wskaźnikowych aby przekazywać argument funkcji przez wskaźnik. Można przekazać bezpośrednio adres za pomocą operatora pobrania adresu (&).

#include <iostream>
#include <cstdlib>

using namespace std;

void zwieksz_kilka(int *dl, int *wys, int *waga)
{
    // zmienna '*dl', '*wys' i '*waga' nie są kopiami
    // operowanie na nich zmienia ich wartość w "całym" programie
    // funkcja nie zwraca nic - bo nie ma sensu

    *dl = *dl * 2;
    *wys = *wys * 2;
    *waga = *waga * 2;
}

int main()
{
    // zmienne
    int dlugosc = 125;
    int wysokosc = 300;
    int waga = 20;

    // wywołanie funkcji z (&) (tak jak byśmy przekazywali wskaźniki)
    zwieksz_kilka(&dlugosc, &wysokosc, &waga);

    // wyświetlenie nowych wartości
    cout << dlugosc << endl;
    cout << wysokosc << endl;
    cout << waga << endl;

    system("pause >nul");
    return 0;
}

Przekazywanie argumentów przez referencję

Referencja występuje jedynie w języku C++ (oraz innych językach wysokiego poziomy jak C# i Java). Wszystko odbywa się na takiej samej zasadzie jak w przypadku wskaźników. Sama referencja także bardzo przypomina wskaźniki. Ciężko jednoznacznie podać definicję referencji, która jednoznacznie odróżni ją od wskaźnika.

Różnica pomiędzy referencją a wskaźnikiem

Wskaźnik jest typem zmiennej, czyli zmienną wskaźnikową. Do wskaźnika możemy przypisać dowolny adres pamięci, na którą ten wskaźnik będzie wskazywał. Dlatego przekazując wskaźnik do funkcji, zostaje on przekazany przez wartość (a więc skopiowany), jednak prawdziwą wartością wskaźnika jest właśnie adres pamięci. Próbując wyświetlić wskaźnik ukaże się naszym oczom adres pamięci. Aby natomiast dostać się do prawdziwej wartości wskaźnika, trzeba posłużyć się operatorem wyłuskania (gwiazdka), czyli operatorem pobrania wartości wskaźnika.

Skoro wskaźnik jest typem zmiennej, to w dowolnym momencie możemy zmienić adres na jaki wskazuje. Referencja natomiast jest bezpośrednim adresem pamięci danej zmiennej. Oznacza to, że nie można jej zmienić, skasować ani uszkodzić. Usunięcie wskaźnika, nie spowoduje utraty zmiennej na jaką wskazywał. Jakiekolwiek nadpisanie referencji, spowoduje natychmiastową utratę danych.

Przekazywanie argumentów przez referencję jest łatwiejsze, ponieważ nie trzeba operować na gwiazdkach i ampersandach. To jedyna różnica między referencją a wskaźnikami w argumentach funkcji. Przez to początkujący programiści często mylą lub wręcz nie wiedzą, z którego sposobu chcą skorzystać.

W funkcji tworzymy dowolną ilość argumentów wraz z typami. Nazwy argumentów poprzedzone są ampersandem (&). Zmienne wewnątrz funkcji nie są kopią, oznacza to że operując na zmiennych referencyjnych operujemy także na zmiennej oryginalnej z pod której wywołana została funkcja.

#include <iostream>
#include <cstdlib>

using namespace std;

void zmien(int &liczba)
{
    // modyfikując referencję modyfikujemy też zmienną oryginalną

    liczba = 123456;
}

int main()
{
    int test = 0;

    // wywołanie funkcji (referencja zmiennej 'test')
    zmien(test);

    // wyświetlenie nowej wartości
    cout << test << endl;

    system("pause >nul");
    return 0;
}

Przekazywanie tablic jako argument funkcji

Tablice wygodnie przekazywać do funkcji za pomocą wskaźników. Wynika to z faktu, że nazwa tablicy (bez podania indeksu) jest wskaźnikiem na jej pierwszy element.

#include <iostream>
#include <cstdlib>

using namespace std;

// przekazujemy przez wskaźnik
void wyswietl(int tab[])
{
    for (int i = 0; i<6; i++)
        cout << tab[i] << endl;
}

int main()
{
    int tablica[6] = {1,2,3,4,5,6};

    // nazwa tablicy to wskaźnik na tablica[0]
    wyswietl(tablica);

    system("pause >nul");
    return 0;
}

Nie tak pięknie wygląda sprawa z przekazywaniem do funkcji tablicy dwuwymiarowej (lub więcej). Ponieważ są to tablice wskaźników do wskaźników, w argumencie funkcji musimy podać wymiary tablicy. Inaczej program nie skompiluje się, ponieważ kompilator nie będzie umiał przewidzieć wielkości poszczególnych wymiarów.

Użytkownik LordzFc napisał:

16 marca 2013


Bardzo dobry :) Przeczytałem go w ramach ogólnej powtórki z C++, bo w toku swojego nauczania pewne rzeczy pominąłem. W zasadzie mam pytanie właśnie odnośnie wskaźników i referencji: co lepiej używać? Wydaje mi się że wskaźniki są nieco bardziej uniwersalne

Użytkownik Adrian napisał:

09 maja 2013


No po prostu super. Tyle rzeczy wytłumaczone w tak niedużym tekście i to nie gorzej niz w niejednej książce. Gratulacje i podziękowania dla autora, mam nadzieje, że będziesz tworzył Drogi Autorze więcej tematów na temat programowania. Dodaje stronke do ulubionych.

Użytkownik Arek napisał:

16 maja 2013


Przekazywanie przez wskaźnik i przekazywanie przez wartość to to samo. Wskaźnik do zmiennej sam jest zmienną, przekazując do funkcji wskaźnik na zmienną przekazujemy zmienną typu wskaźnikowego przez wartość.
Jak to udowodnić: Spróbuj wewnątrz funkcji utworzyć nową zmienną i przypisać ją do przekazanego wskaźnika. Cz po opuszczeniu funkcji wskaźnik będzie wskazywał na nową zmienną? Nie.
Wniosek: wskaźnik jest przekazany przez wartość.
IMO są dwa sposoby przekazywania parametrów: przez wartość i przez referencję. To, jakiego typu jest przekazywana zmienna jest sprawą nieistotną.

Użytkownik Karol napisał:

02 sierpnia 2013


Masz rację to to samo, jednak przekazywanie przez wartość i wskaźnik wolałem rozbić na dwa osobne akapity.

Użytkownik Joanna napisał:

19 września 2013


Prosto i zrozumiale. Bardzo mi się podobało. Dziękuję :)

Użytkownik Kaczus napisał:

24 czerwca 2014


„Referencja występuje jedynie w języku C++.”

Nie jest to prawda….

„Przekazywanie argumentów przez referencję jest łatwiejsze, ponieważ nie trzeba operować na gwiazdkach i ampersandach. To jedyna różnica między referencją a wskaźnikami w argumentach funkcji.”

Nie jest to jedyna różnica.

przykład jeśli masz funkcję
int foo(int *x);

To ktoś może ją wywołać np w sposób:
foo(NULL);

przy foo(int &x);
konstrukcja jak powyżej nie będzie prawidłowa.

Użytkownik buckya napisał:

06 listopada 2014


Świetny artykuł, w sposób prosty i przejrzysty udało Ci się wytłumaczyć, to co inni niepotrzebnie komplikują. Dzięki :)

Użytkownik Sara napisał:

05 lutego 2015


Dzięki bardzo. Krótko i na temat, w jednym miejscu. :)

Użytkownik Michał napisał:

31 lipca 2015


Przekazywanie funkcji dwuwymiarowej jeszcze jest w zasięgu moich zwojów mózgowych.
Problem zaczyna się, gdy chcemy przekazać z podprogramu np. tablicę int-ów zdeklarowaną dynamicznie do maina.
W jaki sposób to zrobić?

Użytkownik ala maj napisał:

24 marca 2016


Taki mały szczegół ci umknął:
nie pisze się: „z poza”.
Chyba chodziło Ci o: „spoza”.

pozdr.

Użytkownik Shivy napisał:

18 października 2016


Hej,
Dobry artykuł!
Definicję referencji uzupełniłbym dodając: „Referencja to po prostu inna nazwa istniejącej zmiennej/obiektu”.
Pozdrawiam.

Użytkownik marianekA napisał:

31 grudnia 2016


Poćwiczyłem i zauważyłem że używając referencji w funkcji nie można zmieniać kolejności argumentów przy jej wywołaniu. Przy innych sposobach nie sprawdzałem bo nie przypadły mi do gustu.

Użytkownik Milena napisał:

16 czerwca 2017


Świetny artykuł do usystematyzowania wiedzy!
Dzięki, że Ci się chciało!

Użytkownik Wiktor napisał:

20 lutego 2018


Jeśli w funkcji działa się na kopiach argumentu to czemu w moim przypadku argument jest zmieniany na stałe?
Nie wiem gdzie robię błąd. Będę wdzięczny za podpowiedź.

#include
#include
void piszTo(int we[])
{
printf(” we=[%d %d %d]\n”, we[0], we[1], we[2]);
we[0] = 55;
we[1] = 55;
we[2] = 55;
printf(” we=[%d %d %d]\n”, we[0], we[1], we[2]);
//nawet nie dotykam zmiennej Tablica więc nic się z nią nie powinno stac.
}

int main()
{
int Tablica[10] = { 11,22,33,44,55,66,77,88,99,100 };
printf(„Tablica=[%d %d %d %d]\n”, Tablica[0], Tablica[1], Tablica[2], Tablica[3]);
piszTo(Tablica);
printf(„Tablica=[%d %d %d %d]\n\n”, Tablica[0], Tablica[1], Tablica[2], Tablica[3]);
printf(„Dlaczego wartosc zmiennej Tablica ulegla zmianie?\n”);
_getch();
return 0;
}

Użytkownik Karol napisał:

20 lutego 2018


@Wiktor
W C/C++ przekazując do funkcji tablicę, tak naprawdę przekazujemy wskaźnik na jej pierwszy element. Przez to funkcja docelowa jest w stanie zmodyfikować całą zawartość tablicy.

void fun (int tab[]); // to też wskaźnik
void fun (int tab[10]); // to też wskaźnik
void fun (int* tab); // i to też wskaźnik

Przekazanie tablicy do funkcji to to samo co przekazanie przez wskaźnik opisane w tym artykule. Co z tego, że wskaźnik zostanie skopiowany, skoro jego kopia nadal wskazuje to samo miejsce w pamięci? :)

Użytkownik Adam napisał:

21 marca 2018


B. dobry artykuł. Pozdrawiam

Użytkownik Zegar. napisał:

06 kwietnia 2019


Bardzo ładnie. Zrozumiałem, mimo że jestem już stary. :-)
Proszę o używanie „spoza”, bo „z poza” to błąd ortograficzny i aż bolą zęby w czasie czytania.
Pozdrawiam.

Sławek.

Zachęcam Cię do zostawienia komentarza!

Ilość znaków: 0