Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Zmodyfikowano 26.04.2023

©2023 mgr Jerzy Wałaszek
I LO w Tarnowie

Materiały do matury z informatyki

Funkcje

SPIS TREŚCI

Definicja funkcji

Funkcja (ang. Function) jest blokiem kodu programu, który można wywoływać (uruchamiać) w wielu różnych miejscach programu. Do funkcji mogą być przekazywane różne dane, które funkcja przetwarza, po czym może ona zwracać wynik tego przetwarzania, który zostanie następnie wykorzystany w miejscu wywołania. Funkcje są bardzo istotnym elementem programowania i występują praktycznie w każdym większym programie, dlatego musisz dokładnie poznać sposób ich tworzenia i wywoływania.

Z jedną funkcją spotkaliśmy się już na samym początku nauki C++: main( ). Jest to funkcja główna, od której program rozpoczyna działanie, zatem musi wystąpić w każdym programie w C++.

Ogólna definicja funkcja funkcji jest następująca:

typ nazwa(parametry)
{
    kod
}
typ określa typ wyniku zwracanego przez funkcję.
nazwa umożliwia wywoływanie funkcji.
parametry określają dane przekazywane do funkcji.
kod instrukcje wykonywane wewnątrz funkcji.

Typ może być dowolnym typem danych języka C++, np. int, unsigned, float, double... Jeśli funkcja nie zwraca żadnej wartości, to ma specjalny typ void (pustka). O takiej funkcji mówimy, że jest procedurą.

Jeśli funkcja nie potrzebuje danych z programu, to nawiasy za nazwą zostawiamy puste (parametrami zajmujemy się dalej w tym rozdziale).

Funkcje definiujemy zwykle na początku programu.

Funkcję wywołujemy podając jej nazwę, nawiasy i ewentualne wartości parametrów w nawiasach za nazwą funkcji.


Poniższy przykład programu pokazuje sposób wywołania funkcji w programie.

// Funkcje
//--------

#include <iostream>

using namespace std;

// Definicja funkcji
//------------------
void piszABC()
{
    cout << "ABC";
    return; // Koniec działania funkcji
}

// Funkcja główna
//---------------
int main()
{
    setlocale(LC_ALL,"");

    int i;

    for(i = 0; i < 20; i++) piszABC();
    
    cout << endl;
    
    for(i = 0; i < 10; i++)
    {
        piszABC();
        cout << endl;
    }
    
    cout << endl;

    return 0;
}
ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC

Przyjrzyjmy się dokładnie temu programowi. W programie zdefiniowana jest funkcja piszABC( ) typu void, czyli funkcja nie zwraca żadnej wartości. Funkcja zdefiniowana jest przed funkcją główną main( ). To ważne, ponieważ kompilator C++ jest jednoprzebiegowy i w trakcie kompilacji czyta wiersze programu kolejno od pierwszego do ostatniego. Wszystkie używane w programie elementy muszą być wcześniej zdefiniowane przed ich pierwszym użyciem (wyjątki omówimy w dalszej części kursu). W przeciwnym razie powstanie błąd kompilacji i program nie zostanie utworzony, bo kompilator napotka nieznany mu element i zatrzyma proces kompilacji. Dlatego przy funkcjach zwracaj uwagę na to, aby miejsce ich definicji poprzedzało w programie miejsce wywołania funkcji.

Funkcja piszABC( ) wypisuje w konsoli literki ABC, po czym kończy swoje działanie. Instrukcja return (powrót) zawsze kończy działanie funkcji. Jeśli w funkcji umieścimy za return inne instrukcje, to komputer już ich nie wykona, ponieważ funkcja zakończy się i nastąpi powrót do miejsca za wywołaniem funkcji. Jeśli funkcja ma typ void, czyli nic nie zwraca, to instrukcja return nie jest obowiązkowa. W takim wypadku funkcja zakończy działanie po wykonaniu wszystkich instrukcji objętych klamerkami. W funkcji typu void za return nie umieszczamy żadnej wartości.

Program wykorzystuje tę funkcję w dwóch pętlach for. Pierwsza wykonuje się 20 razy i w każdym obiegu wywołuje funkcję piszABC( ). Funkcja wypisuje trzy literki ABC. Ponieważ wiersz nie jest kończony manipulatorem endl, to następne wywołanie funkcji spowoduje dopisanie kolejnych literek ABC w tym samym wierszu. W efekcie powstaje długi wiersz zbudowany z 20 powtórzeń frazy ABC.

Druga pętla for wykonuje się 10 razy i w każdym obiegu wywołuje funkcję piszABC( ), po czym kończy wiersz manipulatorem endl. W wyniku powstanie 10 wierszy, a w każdym są literki ABC.

Zwróć uwagę, iż funkcje pozwalają skrócić program i uczynić go bardziej przejrzystym. Jedną z metod programowania jest rozbicie problemu na prostsze podproblemy i każdy z tych podproblemów jest następnie rozwiązywany w osobnej funkcji (dalej w kursie omówimy tę metodę dokładnie).


Następny przykład programu zawiera funkcję zwracającą pewną wartość.

// Funkcje
//--------

#include <iostream>
#include <iomanip>

using namespace std;

// Definicja funkcji
//------------------
double pi2()
{
    const double PI = 3.14159265;
    return PI * PI;
}

int main()
{
    setlocale(LC_ALL,"");

    cout << fixed << setprecision(5);

    int i;
    double x;

    x = 1;

    for(i = 2; i <= 20; i += 2)
    {
        x *= pi2();
        cout << "PI^"
             << setw( 2) << i
             << " = "
             << setw(16) << x
             << endl;
    }

    cout << endl;

    return 0;
}
PI^ 2 =          9.86960
PI^ 4 =         97.40909
PI^ 6 =        961.38919
PI^ 8 =       9488.53093
PI^10 =      93648.04641
PI^12 =     924269.16885
PI^14 =    9122171.03582
PI^16 =   90032219.19690
PI^18 =  888582384.79490
PI^20 = 8769956595.65997

Analiza:

W programie zdefiniowana jest funkcja pi2( ). Funkcja zwraca wynik typu double. Wynikiem funkcji jest przybliżony kwadrat liczby π. Przeanalizuj samodzielnie działanie tej funkcji. Zwróć uwagę, iż jeśli funkcja zwraca wartość, to musi posiadać na końcu instrukcję return z wartością (może to być dowolne wyrażenie). Instrukcja return zakończy wykonywanie funkcji i zwróci do miejsca wywołania podaną wartość. Dzięki temu funkcję można wykorzystywać w wyrażeniach tak jak liczby i zmienne.

Pętla for wyświetla parzyste potęgi liczby π. Jak to robi? Wykorzystuje zmienną x do wyliczania potęg. Na początku do zmiennej x wprowadzamy 1, po czym w każdym obiegu przemnażamy x przez wartość zwracaną przez funkcję pi2( ). Prześledźmy kilka kolejnych wartości tej zmiennej:

Przed pierwszym obiegiem: x ← 1 ; x = 1
Obieg pierwszy, i = 2 x ← 1 · π2 ; x = π2
Obieg drugi, i = 4 x ← π2 · π2 ; x = π4
Obieg trzeci, i = 6 x ← π4 · π2 ; x = π6
Obieg czwarty, i = 8 x ← π6 · π2 ; x = π8
... ... ...

Numery obiegów w zmiennej i tworzą ciąg kolejnych liczb parzystych 2 4 6 8 ... Numery te odpowiadają wykładnikowi potęgi liczby π w zmiennej x.

Ciekawsze rzeczy można uzyskać, przekazując do funkcji dane w postaci parametrów. Tym zajmuje się następny podrozdział.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Parametry funkcji

Parametry są danymi przekazywanymi do funkcji w czasie jej wywołania. Parametry określamy w trakcie definiowania funkcji, podając dla każdego z nich typ oraz nazwę. Nazwa parametru jest lokalna, tzn. nie jest widoczna poza funkcją, powiemy o tym za chwilę. Definicja parametru  jest podobna do definicji zmiennej:

typ_funkcji nazwa_funkcji(lista_parametrów)
{
    kod_funkcji
}
 
lista_parametrów: typ nazwa_1, typ nazwa_2, ... typ nazwa_n
typ określa rodzaj danych przekazywanych w parametrze. Jest to normalny typ C++: int, unsigned, float, double...
nazwa_i umożliwia w funkcji odwoływanie się do określonego parametru

Jeśli na liście jest więcej niż jeden parametr, to ich definicje rozdzielamy przecinkami. Każdy parametr musi posiadać swoją własną definicję. Tu jest różnica w stosunku do zmiennych:

Definicja trzech zmiennych typu int:

int a,b,c;

Definicja trzech parametrów typu int:

(int a, int b, int c)

Listy parametrów również nie kończymy średnikiem, tylko nawiasem zamykającym parametry funkcji. Każdy parametr może posiadać oddzielny typ w zależności od potrzeb danej funkcji:

(int a, double x, bool c)

Wartości parametrów określone zostają w momencie wywołania funkcji.

Poniższy przykład pokazuje tworzenie funkcji z parametrami oraz wykorzystanie jej w programie:

// Funkcje
//--------

#include <iostream>
#include <iomanip>

using namespace std;

// Średnia dwóch liczb
//--------------------
double srednia(double a, double b)
{
    return (a + b) / 2;
}

int main()
{
    setlocale(LC_ALL,"");

    cout << fixed << setprecision(4);

    int i, x, y;


    cout << " x  y | Średnia z x i y" << endl
         << "------+----------------" << endl;

    for(i = 0; i < 10; i++)
    {
        x = 20 - (i * 2);
        y = 3 + (i * 6);
        cout << setw(2) << x
             << setw(3) << y
             << " | "
             << setw(10) << srednia(x,y)
             << endl;
    }

    cout << endl;

    return 0;
}
 x  y | Średnia z x i y
------+----------------
20  3 |    11.5000
18  9 |    13.5000
16 15 |    15.5000
14 21 |    17.5000
12 27 |    19.5000
10 33 |    21.5000
 8 39 |    23.5000
 6 45 |    25.5000
 4 51 |    27.5000
 2 57 |    29.5000

Przeanalizujmy ten program:

W programie mamy zdefiniowaną funkcję o nazwie srednia( ) (w nazwie funkcji nie mogą występować polskie litery, jeśli cię to denerwuje, używaj nazw angielskich: średnia = average). Funkcja zwraca wynik zmiennoprzecinkowy typu double. Funkcja zwracająca wynik musi posiadać w swoim kodzie instrukcję return, która kończy działanie funkcji i zwraca jako jej wynik wartość wyrażenia, które umieścimy za return. Funkcja posiada dwa parametry typu double: a i b. W parametrach tych kod wywołujący funkcję przekazuje do niej dane, które funkcja ma przetworzyć. Parametry te widzimy w wyrażeniu, którego wartość zwraca instrukcja return jako wartość funkcji.

// Średnia dwóch liczb
//--------------------
double srednia(double a, double b)
{
    return (a + b) / 2;
}

Teraz przejdźmy do miejsca w programie, w którym funkcja jest wywoływana:

for(i = 0; i < 10; i++)
{
    x = 20 - (i * 2);
    y = 3 + (i * 6);
    cout << setw(2) << x
         << setw(3) << y
         << " | "
         << setw(10) << srednia(x,y)
         << endl;
}

Funkcja srednia( ) jest wywoływana wewnątrz pętli for, która wykonuje 10 obiegów. W pętli wyliczamy dwie liczby x i y, następnie wyświetlamy wyliczone liczby x i y oraz wynik funkcji srednia( ), do której przekazano wartości liczb x i y. Wewnątrz funkcji liczby x i y trafiają odpowiednio do parametru a (x) i b (y). Następnie funkcja wykorzystuje te wartości do wyliczenia średniej i zwraca wynik. Wynik funkcji zostaje przekazany do strumienia cout.

Wewnątrz funkcji parametry można traktować jak zmienne. Parametry można dowolnie zmieniać i modyfikować w kodzie funkcji. Opisany sposób przekazywania danych do funkcji nosi nazwę przekazywania przez wartość. W miejsce parametru przy wywołaniu funkcji możemy wstawić dowolne wyrażenie. Komputer oblicza wartość tego wyrażenia i wynik umieszcza w odpowiednim parametrze. Następnie uruchomiony zostaje kod funkcji, która wykorzystuje parametr do swoich obliczeń.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Zmienne lokalne i globalne

Jeśli w funkcji zostanie utworzona zmienna, to będzie ona widoczna tylko w bloku tej funkcji. Wpisz do edytora poniższy program i spróbuj go skompilować:
// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

// Funkcja
//--------
void f(void) // (void) = brak parametrów
{
    int a = 15;
    cout << "W funkcji f() zmienna a = "
         << a << endl << endl;
}

int main()
{
    cout << "W funkcji main() zmienna a = "
         << a << endl << endl;

    return 0;
}
C:\lekcje\005\main.cpp In function 'int main()':
C:\lekcje\005\main.cpp 25 error: 'a' was not declared in this scope

Otrzymasz komunikat o błędzie, który mówi, iż zmienna a w funkcji main( ) w wierszu 25 jest niezdefiniowana. O co tutaj chodzi? Zmienna a jest zdefiniowana wewnątrz bloku funkcji f( ) i jest dla niej lokalna, tzn. że nie jest widoczna poza blokiem tej funkcji. Z kolei w funkcji main( ) program odwołuje się do zmiennej a, której jednak nie zdefiniowano w main( ). Zmienna a w main( ) jest zupełnie inną zmienną, to tak jak uczniowie w dwóch różnych klasach mogą posiadać te same nazwiska i imiona, jednak są różnymi osobami. Uruchom następny program:

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

// Funkcja
//--------
void f(void) // (void) = brak parametrów
{
    int a = 15; // zmienna lokalna w f()
    cout << "W funkcji f() zmienna a = "
         << a << endl << endl;
}

int main()
{
    double a = 3.1415; // zmienna lokalna w main()
    f();
    cout << "W funkcji main() zmienna a = "
         << a << endl << endl;

    return 0;
}
W funkcji f() zmienna a = 15

W funkcji main() zmienna a = 3.1415

W obu funkcjach są zdefiniowane zmienne o nazwie a. W rzeczywistych programach należy raczej unikać takich rozwiązań, bo mogą być mylące, tu jednak utworzyliśmy te zmienne specjalnie, aby zademonstrować ich lokalność. Obie zmienne są różnymi zmiennymi. W funkcji f( ) zmienna lokalna a jest typu int i ma nadaną wartość 15. W funkcji main( ) zmienna lokalna a jest typu double i ma wartość 3.1415. Jak widać, obie zmienne są różne, mają tylko taką samą nazwę.

Wynika z tego pewien problem: co zrobić, jeśli chcemy, aby zmienna była widoczna i dostępna w całym programie? Rozwiązaniem są zmienne globalne. Takie zmienne definiujemy na początku programu przed funkcjami, w których mają być dostępne. Uruchom poniższy program:

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

// Zmienna globalna
int a = 21;

// Funkcja
//--------
void f(void)
{
    cout << "W funkcji f() zmienna a = "
         << a << endl << endl;
}

int main()
{
    f();
    cout << "W funkcji main() zmienna a = "
         << a << endl << endl;

    return 0;
}
W funkcji f() zmienna a = 21

W funkcji main() zmienna a = 21

Teraz nasza zmienna a jest dostępna w obu funkcjach i nie musimy jej w nich definiować. Zmienne globalne należy stosować ostrożnie, ponieważ mogą być zmieniane w dowolnym miejscu programu, czasem w sposób niezamierzony przez programistę. Stara maksyma programistów mówi, iż najlepszą liczbą zmiennych globalnych jest 0. Uruchom poniższy program:

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

// Zmienna globalna
int a = 1;

// Funkcje
//--------
void f1(void)
{
    cout << (++a) << " ";
}

void f2(void)
{
    cout << (a *= 3) << " ";
}

void f3(void)
{
    cout << (a -= 5) << " ";
}

int main()
{
    int i;

    for(i = 0; i < 10; i++)
    {
        f1();
        f2();
        f3();
    }
    cout << endl << endl;

    return 0;
}
2 6 1 2 6 1 2 6 1 2 6 1 2 6 1 2 6 1 2 6 1 2 6 1 2 6 1 2 6 1

Postaraj się przeanalizować działanie tego programu. Zmień wartość początkową zmiennej globalnej a na 2. Jak zmienią się wyniki?

Jeśli zmienna zostanie utworzona wewnątrz bloku, to będzie zmienną lokalną dla tego bloku. Odnosi się to w szczególności do instrukcji for. Wpisz do edytora poniższy program i spróbuj go skompilować:

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

int main()
{

    for(int i = 0; i < 10; i++)
        cout << "Zmienna i w for = "
             << i << endl;

    cout << endl
         << "Zmienna i poza for = "
         << i << endl << endl;

    return 0;
}

Znów trzymasz informację o błędzie: zmienna i niezdefiniowana. O co tutaj chodzi? Zwróć uwagę, iż zmienna i została zdefiniowana wewnątrz instrukcji pętli for:

for(int i = 0; i < 10; i++)

Jest zatem zmienną lokalną tylko dla tej instrukcji. W zaznaczonej instrukcji, która jest poza pętlą for, program odwołuje się do zmiennej i, lecz nie ma do nie dostępu, ponieważ zmienna ta jest lokalna dla instrukcji for i tylko w niej jest dostępna.

Zmodyfikujmy nieco ten program:

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

int main()
{
    float i = 2.73;

    cout << endl
         << "Zmienna i poza for = "
         << i << endl << endl;

    for(int i = 0; i < 10; i++)
        cout << "Zmienna i w for = "
             << i << endl;

    cout << endl
         << "Zmienna i poza for = "
         << i << endl << endl;

    return 0;
}
Zmienna i poza for = 2.73

Zmienna i w for = 0
Zmienna i w for = 1
Zmienna i w for = 2
Zmienna i w for = 3
Zmienna i w for = 4
Zmienna i w for = 5
Zmienna i w for = 6
Zmienna i w for = 7
Zmienna i w for = 8
Zmienna i w for = 9

Zmienna i poza for = 2.73

Na początku funkcji main została dodana definicja zmiennej i. Specjalnie zmieniony został typ tej zmiennej na float, aby pokazać, że jest to zupełnie inna zmienna od tej, którą zdefiniowano w instrukcji for. Zmienne te posiadają tylko tę samą nazwę. W pętli for zmienna lokalna i przebiega wartości od 0 do 9 i są to wartości całkowite. Zmienna i poza pętlą for przechowuje wartość zmiennoprzecinkową 2.73, której pętla for zupełnie nie zmienia.

Zapamiętaj, iż zmienna lokalna o tej samej nazwie, co zmienna zdefiniowana na wyższym poziomie zawsze przesłania tą drugą. W rzeczywistych programach należy raczej unikać takich sytuacji, ponieważ są mylące (dla programisty):

// Zmienne lokalne
//----------------

#include <iostream>

using namespace std;

int main()
{
    float i = 2.73; // Lokalna dla bloku main()

    { // Blok #1
        int i = 15; // Lokalna dla bloku #1
        { // Blok #2
            double i = 3.1415; // Lokalna dla bloku #2
            cout << i << endl; // Z bloku #2
        }
        cout << i << endl; // Z bloku #1
    }
    cout << i << endl; // Z bloku main()

    return 0;
}
3.1415
15
2.73

Ze zmiennymi lokalnymi i globalnymi wiąże się w języku C++ wiele różnych zagadnień, część z nich omówimy później, z resztą zapoznasz się na studiach informatycznych.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Wskaźniki

Każdy komputer wyposażony jest w pamięć, w której przechowywane są instrukcje programu, zmienne oraz inne obiekty potrzebne do działania komputera. Pamięć zbudowana jest z ciągu komórek, w których znajduje się informacja. Każda komórka posiada przydzielony numer, który nazywamy adresem komórki. Pamięć współczesnych komputerów jest zwykle bardzo duża, liczona w gigabajtach, czyli miliardach komórek:

1GB = 230 bajtów = 1.073.741.824 bajtów

Programując w języku C++ nie musisz zwykle znać liczbowych adresów komórek pamięci, jednak powinieneś coś wiedzieć na ich temat. Kompilator dba o to, aby twój program umieszczał dane we właściwym miejscu pamięci, a następnie je z niego pobierał. Do tego celu wymyślono zmienne. Nazwa zmiennej informuje komputer o który obszar pamięci chodzi. Pod nazwą ukryty jest adres zmiennej w pamięci. Do wydobycia adresu z nazwy zmiennej mamy w C++ operator adresu &. Operator & w wyrażeniu umieszczamy przed nazwą zmiennej, której adres chcemy otrzymać. Uruchom poniższy program:

// Adres zmiennej
//----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    double x = 12.56;
    cout << "Wartość x : " <<  x << endl
         << "Adres   x : " << &x << endl << endl;
    return 0;
}
Wartość x : 12.56
Adres   x : 0x61fe18

Strumień prezentuje adresy obiektów w pamięci w postaci liczby szesnastkowej. U ciebie adres 0x61fe18 może być inny - zależy to od zajętości pamięci, zwykle nie ma to znaczenia, ważne jest tylko to, iż mamy dostęp do adresu. Po co nam adres? W języku C++ możemy utworzyć zmienną do przechowania adresu zmiennej określonego typu. Taką zmienną nazywamy wskaźnikiem (ang. pointer). Wskaźnik definiujemy następująco:

typ * nazwa;

typ : określa typ danych, których adres przechowuje wskaźnik
* : operator wskazania, informuje, że zmienna jest wskaźnikiem do danych, a nie danymi
nazwa : nazwa wskaźnika

Uruchom poniższy program:

// Adres zmiennej
//----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    double x = 12.56;
    double *p = &x; // Tworzymy wskaźnik i wpisujemy
                    // do niego adres zmiennej x
    cout << "Wartość  x : " << x << endl
         << "Adres    x : " << &x << endl
         << "Wskaźnik p : " << p
         << endl << endl;
    return 0;
}
Wartość  x : 12.56
Adres    x : 0x61fe10
Wskaźnik p : 0x61fe10

Zwróć uwagę na to, jak utworzono wskaźnik. Sama definicja wskaźnika tworzy jedynie zmienną do przechowywania adresu. Zmiennej należy przypisać odpowiedni adres, aby była użyteczna. Mając adres zmiennej, możemy uzyskać do niej dostęp przy pomocy operatora wskazania (*):

Jeśli p jest adresem zmiennej x, to *p jest wskazywaną przez p zmienną x. Uruchom poniższy program:

// Adres zmiennej
//----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    double x;
    double *p = &x; // Tworzymy wskaźnik p i wpisujemy
                    // do niego adres zmiennej x

    // Korzystając ze wskaźnika p nadajemy zmiennej x wartość
    *p = 16.3333;

    cout << "Wartość x = " << x << endl << endl;

    return 0;
}
Wartość x = 16.3333

W programie wpisaliśmy do zmiennej x wartość 16.3333 korzystając z adresu we wskaźniku p. Ponieważ wskaźnik jest zmienną, to zmiana adresu spowoduje, iż zacznie on wskazywać inny obiekt. Jednak musisz pamiętać, iż w języku C++ adresy mają typy i nie można normalnie do wskaźnika wpisać adresu innego typu, np. jeśli stworzyliśmy w programie wskaźnik do zmiennych typu double, to nie możemy przypisywać mu adresu zmiennej typu int. Dlaczego? Zmienne są umieszczane w pamięci obok siebie. Zmienna typu double zajmuje 8 bajtów pamięci, czyli tyle co 2 zmienne typu int. Wprowadzenie danych o długości 8 bajtów do danej o długości 4 bajtów mogłoby spowodować nadpisanie innej zmiennej, która w pamięci leży obok, co z kolei najprawdopodobniej doprowadziłoby do błędnego działania całego programu. Na szczęście kompilator C++ wykrywa takie błędy.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Wskaźniki jako parametry funkcji

Do funkcji można przekazać wskaźniki jako parametry. Wtedy funkcja otrzymuje adres danych, z których ma korzystać. Mając adres, funkcja może zmienić obiekt w pamięci, który jest zdefiniowany poza jej blokiem. W ten sposób funkcja może zwracać więcej niż jeden wynik, ale wymaga to ostrożności ze strony programisty. Uruchom poniższy program:
// Wskaźniki
//----------------

#include <iostream>
#include <iomanip>

using namespace std;

// Funkcja oblicza wynik dzielenia
// całkowitoliczbowego oraz resztę
// z tego dzielenia
//--------------------------------
int divmod(int a, int b, int * p)
{
    * p = a % b;
    return a / b;
}

int main()
{
    setlocale(LC_ALL,"");

    for(int i = 0; i < 10; i++)
    {
        int x,y,r;

        x = (i + 4) * 20;
        y = (i + 2) * 3;

        cout << setw(3) << x << " /"
             << setw(3) << y << " ="
             << setw(3) << divmod(x,y,&r)
             << " i reszta"
             << setw(3) << r << endl;
    }

    cout << endl;

    return 0;
}
 80 /  6 = 13 i reszta  2
100 /  9 = 11 i reszta  1
120 / 12 = 10 i reszta  0
140 / 15 =  9 i reszta  5
160 / 18 =  8 i reszta 16
180 / 21 =  8 i reszta 12
200 / 24 =  8 i reszta  8
220 / 27 =  8 i reszta  4
240 / 30 =  8 i reszta  0
260 / 33 =  7 i reszta 29

Przeanalizujmy ten program. Zaczynamy od funkcji:

int divmod(int a, int b, int * p)
{
    * p = a % b;
    return a / b;
}

Funkcja divmod( ) przyjmuje trzy parametry. Pierwsze dwa a i b są typu int. To normalne parametry, w których komputer umieści dane przy wywołaniu funkcji. Trzeci parametr p jest wskaźnikiem do danej typu int. W tym parametrze komputer umieści adres zmiennej typu int. Mając ten adres, funkcja uzyskuje dostęp do tej zmiennej poprzez operator wskazania * tak, jakby była ona zdefiniowana wewnątrz bloku funkcji.

W bloku funkcji wykonywane są dwie operacje. Pierwsza wylicza resztę z dzielenia a przez b i umieszcza ją w pamięci pod adresem wskazywanym przez wskaźnik p. Dzięki wskaźnikowi wynik ten zostanie zapamiętany poza funkcją i będzie go można wykorzystać. Druga operacja kończy działanie funkcji, zwracając jako jej wynik iloraz całkowity a przez b.

Teraz przejdźmy do funkcji main( ). Znajduje się tutaj pętla for. Przyjrzyjmy się jej bliżej:

for(int i = 0; i < 10; i++)
{
    int x,y,r;
    x = (i + 4) * 20;
    y = (i + 2) * 3;
    cout << setw(3) << x << " /"
         << setw(3) << y << " ="
         << setw(3) << divmod(x,y,&r)
         << " i reszta"
         << setw(3) << r << endl;
}

W pętli zdefiniowane są cztery zmienne lokalne typu int: i, x, y, r. Zmienna i służy do zliczania obiegów pętli oraz do tworzenia wartości x i y. Wartości te są przekazywane do funkcji divmod( ), która je dzieli i zwraca wynik, a resztę z dzielenia umieszcza w zmiennej r przekazanej do funkcji poprzez adres w trzecim argumencie. Instrukcja cout wyświetla w konsoli wartości x, y, x / y (wynik funkcji) oraz x % y (zwrócone przez funkcję w zmiennej r).

Do zapamiętania:

Na początek:  podrozdziału   strony 

Referencja

Przekazywanie adresu zmiennej w parametrze funkcji jest w języku C++ używane dosyć często, dlatego uproszczono ten proces przez wprowadzenie tzw. referencji. W parametrze funkcji umieszcza się przed nazwą parametru operator adresu &, który informuje kompilator, że dany parametr będzie zawierał adres zmiennej, a nie dane. W kodze funkcji odwołanie do tej zmiennej następuje poprzez nazwę parametru (bez operatora *).  Również w wywołaniu w parametrze umieszcza się samą nazwę zmiennej bez operatora adresu &. Uruchom poniższy program:

// Referencja
//-----------

#include <iostream>
#include <iomanip>

using namespace std;

// Funkcja oblicza wynik dzielenia
// całkowitoliczbowego oraz resztę
// z tego dzielenia
//--------------------------------
int divmod(int a, int b, int & p)
{
    p = a % b;
    return a / b;
}

int main()
{
    setlocale(LC_ALL,"");

    for(int i = 0; i < 10; i++)
    {
        int x,y,r;

        x = (i + 4) * 20;
        y = (i + 2) * 3;

        cout << setw(3) << x << " /"
             << setw(3) << y << " ="
             << setw(3) << divmod(x,y,r)
             << " i reszta"
             << setw(3) << r << endl;
    }

    cout << endl;

    return 0;
}

Jest to ten sam program, co poprzednio. Zwróć uwagę na różnicę definicji parametru funkcji:

Z referencją Ze wskaźnikiem
int divmod(int a, int b, int & p)
{
    p = a % b;
    return a / b;
}
int divmod(int a, int b, int * p)
{
    * p = a % b;
    return a / b;
}

Trzeci argument jest zdefiniowany jako adres zmiennej: int & p, a nie jako wskaźnik int * p. W kodzie funkcji odwołujemy się do zmiennej po prostu nazwą parametru p = a % b, a nie wskaźnikiem * p = a % b.

Również wywołanie funkcji jest teraz uproszczone: divmod(x,y,r), a nie divmod(x,y,&r). Adres zmiennej jest automatycznie przekazywany do parametru funkcji.

Taki sposób przekazywania danych do funkcji nosi nazwę przekazywania przez referencję. Poznaliśmy dwa sposoby przekazywania danych do funkcji:

  1. Przekazywanie przez wartość:
    f(typ x)
    {
      funkcja otrzymuje w parametrze x wartość
    }
  2. Przekazywanie przez referencję:
    f(typ &x)
    {
      funkcja otrzymuje w parametrze x adres innej
      zmiennej, do której ma pełen dostęp
    }

Jeśli parametr przekazywany jest poprzez wartość, to w wywołaniu funkcji można w nim umieścić dowolne wyrażenie. Komputer obliczy wartość tego wyrażenia i wynik przekaże do funkcji.

Jeśli parametr przekazywany jest przez referencję, to w wywołaniu funkcji należy w nim umieścić zmienną, którą chcemy przekazać funkcji. Komputer wyznaczy adres tej zmiennej i skojarzy go z parametrem. W ten sposób parametr referencyjny będzie odpowiadał przekazanej zmiennej - jeśli funkcja go zmieni, to zmiana wystąpi w przekazanej zmiennej. W ten sposób funkcje mogą udostępniać sobie zmienne lokalne. Przeanalizuj poniższy program:

// Referencja
//-----------

#include <iostream>
#include <iomanip>

using namespace std;

int set3(int & x, int & y, int & z, int n)
{
    x = n;
    y = x + n / 2;
    z = x * y;
}

int main()
{
    setlocale(LC_ALL,"");

    int a,b,c;

    cout << "    n    a    b    c" << endl
         << "--------------------" << endl;
    for(int i = 0; i < 10; i++)
    {
        set3(a,b,c,i * 3);
        
        cout << setw(5) << i * 3
             << setw(5) << a
             << setw(5) << b
             << setw(5) << c << endl;
    }

    cout << endl;

    return 0;
}
    n    a    b    c
--------------------
    0    0    0    0
    3    3    4   12
    6    6    9   54
    9    9   13  117
   12   12   18  216
   15   15   22  330
   18   18   27  486
   21   21   31  651
   24   24   36  864
   27   27   40 1080

Do zapamiętania:

 
Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2023 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.