Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz  

Autor artykułu: mgr Jerzy Wałaszek

©2022 mgr Jerzy Wałaszek
I LO w Tarnowie

obrazek

Materiały dla klasy III

Funkcje

SPIS TREŚCI

Co to jest funkcja

Funkcja (ang. function) jest elementem programu komputerowego, który można wielokrotnie wykonywać w różnych miejscach przez wywołanie. Każda funkcja przed użyciem musi zostać zdefiniowana. Definicja wygląda następująco:

typ_wyniku nazwa(lista_parametrów)
{
   treść;
   return wynik;
}

Typ wyniku określa typ zwracanych przez funkcję danych. Może to być dowolny z poznanych typów (int, double) lub void. Ten ostatni oznacza funkcję, która nie zwraca żadnego wyniku. W funkcji typu void nie musi wystąpić rozkaz return, a jeśli występuje, to jest bez parametru wynik. W takim przypadku rozkaz return oznacza po prostu zakończenie działania funkcji i powrót do miejsca wywołania.

Nazwa funkcji tworzona jest tak samo jak nazwa zmiennej. Musi się rozpoczynać od litery lub znaku podkreślenia. Kolejnymi znakami mogą być litery, cyfry i znaki podkreślenia. Litery duże i małe są rozróżniane.

Lista parametrów określa dane, które zostaną przekazane do funkcji. Każdy parametr jest zmienną, którą definiujemy na tej liście jako ciąg: typ nazwa, typ nazwa, ...

Klamerki określają zakres funkcji. Wszystko, co się w nich znajduje, należy do zakresu funkcji.

Treść to polecenia wykonywane przez funkcję.

Jeśli funkcja ma zwrócić wynik, to w zakresie funkcji należy umieścić rozkaz return wynik, gdzie wynik jest dowolnym wyrażeniem, które komputer wylicza i którego wynik staje się wartością zwracaną przez funkcję. Wykonanie rozkazu return przerywa funkcję i następuje powrót do miejsca wywołania. Jeśli za tym rozkazem umieścimy w funkcji jakieś instrukcje, to nie zostaną one już wykonane.

Funkcję wywołujemy poprzez jej nazwę, umieszczając w nawiasach wartości dla kolejnych parametrów.

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

#include <iostream>

using namespace std;

// Funkcja zwraca NWD dwóch liczb a i b
//-------------------------------------

int nwd(unsigned a, unsigned b)
{
  while(b != a)
  {
      if(a > b) a = a - b;
      else      b = b - a;
  }
  return a;
}

int main()
{
  cout << nwd(36,24) << endl
       << nwd(36,64) << endl;

  return 0;
}
Na początek:  podrozdziału   strony 

Przekazywanie parametrów do funkcji

Funkcje mogą otrzymywać informację za pomocą parametrów, które wewnątrz funkcji wyglądają jak zmienne. Parametry są przekazywane na dwa sposoby:

Przekazywanie przez wartość oznacza, że funkcja otrzymuje w swoim parametrze wartość wyrażenia, które zostanie umieszczone w wywołaniu funkcji przez wywołujący ją program. Przykładem jest poprzednio napisana funkcja nwd. W parametrach a i b otrzymuje ona wartości dwóch liczb, dla których wyznacza największy wspólny dzielnik. Przeanalizujmy poniższy program:

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

#include <iostream>

using namespace std;

void cosik(int a)
{
  cout << "Funkcja: " << a << endl;
}

int main()
{
  int x = 15;

  cout << "Program: " << x << endl;
  cosik(x);
  cosik(x+15);
  cosik(x+255);
  cout << "Program: " << x << endl;
  return 0;
}

W funkcji głównej tworzymy zmienną x i nadajemy jej wartość 15. Program wyświetla zawartość tej zmiennej x, po czym wywołuje funkcję cosik, przekazując jej w parametrze a wartość x. Funkcja wyświetla to, co otrzymała w swoim parametrze i kończy działanie. Następuje drugie wywołanie cosik, ale teraz w parametrze a otrzyma ona zawartość x powiększoną o 15, czyli 30. Zauważ, iż wyrażenie x+15 jest obliczane zanim zostanie wywołana funkcja. W kolejnym wywołaniu funkcja cosik otrzyma w swoim parametrze wartość x powiększoną o 255, czyli 270. Na koniec program wyświetla jeszcze raz zawartość zmiennej x. Na wyjściu otrzymamy zatem:

Program: 15
Funkcja: 15
Funkcja: 30
Funkcja: 270
Program: 15

Zmieńmy nieco program:

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

#include <iostream>

using namespace std;

void cosik(int a)
{
  a = a * a;
  cout << "Funkcja: " << a << endl;
}

int main()
{
  int x = 15;

  cout << "Program: " << x << endl;
  cosik(x);
  cout << "Program: " << x << endl;
  return 0;
}

Teraz funkcja cosik wyznacza kwadrat otrzymanego parametru i wyświetla wartość tego kwadratu. W funkcji głównej tworzymy zmienną x i nadajemy jej wartość 15. Następnie wyświetlamy wartość x, wywołujemy funkcję cosik z wartością x jako parametrem i na koniec ponownie wyświetlamy zawartość x. Jako wynik działania otrzymamy:

Program: 15
Funkcja: 225
Program: 15

Parametr a zmienił się w funkcji cosik na kwadrat, lecz nie wpłynęło to na zmianę zawartości zmiennej x, która dalej pozostała równa 15 po powrocie z funkcji cosik. Wynika z tego, że przy przekazywaniu parametru przez wartość funkcja może zrobić z otrzymanym parametrem cokolwiek, ale będzie to miało znaczenie tylko wewnątrz tej funkcji.


Dane do funkcji możemy również przekazywać przez referencję. W tym przypadku funkcja nie otrzymuje wartości danej, lecz samą daną, np. zmienną. Jeśli ją zmieni, to zmiana ta pozostanie w zmiennej również po zakończeniu działania funkcji.

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

#include <iostream>

using namespace std;

void cosik(int & a)  // Parametr przekazywany przez referencję
{
  a *= a;
  cout << "Funkcja: " << a << endl;
}

int main()
{
  int x = 15;

  cout << "Program: " << x << endl;
  cosik(x);
  cout << "Program: " << x << endl;
  return 0;
}

W programie dodaliśmy tylko jeden znak '&' przed nazwę parametru funkcji cosik. Znak ten jest w C++ operatorem referencji. Referencja oznacza, że parametr a nie jest wartością jakiegoś wyrażenia, lecz obiektem, który został udostępniony funkcji jako parametr. Funkcja cosik wyznacza kwadrat tego, co zawiera przekazany obiekt, czyli x. Obliczony kwadrat jest wstawiany z powrotem do otrzymanego obiektu, który wewnątrz funkcji cosik nosi nazwę a. Dlatego po powrocie z wywołania zmienna x zawiera kwadrat swojej początkowej wartości.

Program: 15
Funkcja: 225
Program: 255

Referencja jest przydatna przy wielu okazjach. Na przykład, jest ona jednym ze sposobów zwracania przez funkcję więcej niż jednej wartości.

// Funkcje
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

void cosik(int a, int b, int & wd, int & rd)
{
  wd = a / b;
  rd = a % b;
}

int main()
{
  int i,w,r;

  for(i = 2; i <= 9; i++)
  {
     cosik(20 - i, i, w, r);
     cout << 20 - i << " : " << i << " = " << w << " i reszta " << r << endl;
  }
  return 0;
}

W powyższym przykładzie funkcja cosik przyjmuje cztery parametry. Dwa pierwsze są przekazywane przez wartość, a dwa kolejne przez referencję. Wynik jest następujący:

18 : 2 = 9 i reszta 0
17 : 3 = 5 i reszta 2
16 : 4 = 4 i reszta 0
15 : 5 = 3 i reszta 0
14 : 6 = 2 i reszta 2
13 : 7 = 1 i reszta 6
12 : 8 = 1 i reszta 4
11 : 9 = 1 i reszta 2

Podsumowując:

Parametr przekazywany przez wartość jest liczbą, którą otrzymuje funkcja od programu głównego. Z otrzymaną w ten sposób liczbą funkcja może zrobić cokolwiek i jest to działanie lokalne wewnątrz funkcji. Parametr przekazywany przez wartość może być wynikiem wyrażenia, który komputer oblicza i przekazuje do funkcji w postaci liczby.

Parametr przekazywany przez referencję jest wypożyczonym obiektem. Jeśli funkcja zmieni ten obiekt, to zmiana ta będzie również widoczna w programie, który dany obiekt wypożyczył. Parametr przekazywany przez referencję musi być zmienną.

Na początek:  podrozdziału   strony 

Zmienne lokalne i globalne

Zmienne tworzone wewnątrz zakresu funkcji są zmiennymi lokalnymi. Ich przestrzeń życiowa ogranicza się do wnętrza funkcji, w której zostały utworzone. Przeanalizujmy poniższy program:

// Funkcje
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

void cosik()
{
  int x = 555; // Zmienna lokalna dla cosik
  cout << "Funkcja: " << x << endl;
}

int main()
{
  int x = 111; // Zmienna lokalna dla main
  cout << "Program: " << x << endl;
  cosik();
  cout << "Program: " << x << endl;
  return 0;
}

Funkcja cosik (tym razem bez parametrów) tworzy zmienną x i nadaje jej wartość 555, co potwierdza wypisaniem odpowiedniego tekstu w oknie konsoli. Funkcja main również tworzy sobie zmienną o nazwie x i nadaje jej wartość 111, po czym wypisuje ją w oknie konsoli, wywołuje funkcję cosik i jeszcze raz wypisuje zawartość swojej zmiennej x. Otrzymujemy:

Program: 111
Funkcja: 555
Program: 111

Widzimy więc jasno, że wewnątrz funkcji x ma inną wartość niż poza nią. Staje się to oczywiste, gdy przyjmiemy do wiadomości, że są to dwie różne zmienne, które mają tę samą nazwę. Jednak nie ma tutaj niejednoznaczności, ponieważ ich przestrzenie życiowe (czyli zakresy) są różne. Jedna zmienna x żyje wewnątrz funkcji cosik, a ta druga wewnątrz funkcji main. Pierwszej nadano wartość 555, a drugiej 111. Gdy wywołamy funkcję cosik, to będzie ona korzystała ze swojej zmiennej x, czyli tej o wartości 555. Natomiast funkcja main posiada swoją zmienną x o wartości 111. W obu przypadkach są to zmienne lokalne dla cosik i main.

Inna sytuacja wystąpi w poniższym programie:

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

#include <iostream>

using namespace std;

int x; // Zmienna globalna

void cosik()
{
  x = 555;
  cout << "Funkcja: " << x << endl;
}

int main()
{
  x = 111;
  cout << "Program: " << x << endl;
  cosik();
  cout << "Program: " << x << endl;
  return 0;
}

Teraz nie tworzymy zmiennych wewnątrz funkcji, lecz na zewnątrz. Każda z funkcji zmienia zawartość zmiennej x. W funkcji main zmienna x otrzymuje wartość 111, po czym w funkcji cosik zmienna ta otrzymuje nową wartość 555 i po powrocie z cosik x wciąż tę wartość przechowuje. Dlatego otrzymujemy wynik:

Program: 111
Funkcja: 555
Program: 555

W ten sposób zadeklarowana zmienna nazywa się zmienną globalną. Jest ona widoczna we wszystkich funkcjach poniżej swojej definicji – w naszym przypadku będą to obie funkcje cosik i main. Wspólna zmienna może być zarówno wygodna (np. pozwala zmniejszyć liczbę parametrów przekazywanych do funkcji) jak i niebezpieczna (przy dużej liczbie funkcji problemem może okazać się ich współpraca przy zmianach współdzielonej zmiennej globalnej). Fachowi programiści zalecają, aby nie używać zmiennych globalnych bez wyraźnej potrzeby. Na pewno mają rację.

Co się jednak stanie, jeśli w funkcji utworzymy zmienną lokalną o takiej samej nazwie jak zmienna globalna? Sprawdźmy:

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

#include <iostream>

using namespace std;

int x; // Zmienna globalna

void cosik()
{
  int x = 555; // Zmienna lokalna
  cout << "Funkcja: " << x << endl;
}

int main()
{
  x = 111;
  cout << "Program: " << x << endl;
  cosik();
  cout << "Program: " << x << endl;
  return 0;
}

Jako wynik otrzymamy:

Program: 111
Funkcja: 555
Program: 111

Wygląda na to, że funkcja cosik straciła dostęp do zmiennej globalnej x na rzecz zmiennej lokalnej x. Jest to zamierzone. Otóż zmienne lokalne zawsze przysłaniają zmienne globalne o tych samych nazwach. Jeśli w cosik stworzyliśmy zmienną lokalną x, to funkcja ta będzie się odwoływać do tej lokalnej zmiennej, a nie do x globalnego, które zostało przysłonięte przez x lokalne. Na pierwszy rzut oka wygląda to zagmatwanie, lecz jest zupełnie proste w momencie, gdy zrozumiemy i zapamiętamy tę regułę. Powstaje jedynie pytanie, jak odwołać się w takim przypadku do zmiennej globalnej? Musisz skorzystać z operatora zakresu ::

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

#include <iostream>

using namespace std;

int x; // Zmienna globalna

void cosik()
{
  int x = 555; // Zmienna lokalna
  cout << "Funkcja: " << x << endl;
  ::x = 333;   // Zmiana zmiennej globalnej
}

int main()
{
  x = 111;
  cout << "Program: " << x << endl;
  cosik();
  cout << "Program: " << x << endl;
  return 0;
}

Teraz funkcja cosik zmienia również zawartość zmiennej globalnej x na 333, co uwidacznia się po powrocie do main.

Program: 111
Funkcja: 555
Program: 333

Podsumowując:

Jeśli zmienna jest tworzona wewnątrz funkcji (a ogólnie wewnątrz bloku objętego klamerkami), to jej zakres widoczności ogranicza się do tej funkcji. Jest to zmienna lokalna.

Jeśli zmienna jest tworzona nazewnątrz funkcji, to jej widoczność sięga od miejsca utworzenia do końca programu. Jest to zmienna globalna.

Jeśli zmienna lokalna ma tę samą nazwę co zmienna globalna, to przysłania zmienną globalną. Aby uzyskać dostęp do zmiennej globalnej, należy użyć operatora zakresu ::.

Na początek:  podrozdziału   strony 

xxx

xxx
Na początek:  podrozdziału   strony 

xxx

xxx
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
©2022 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.