Informatyka dla klas II

Funkcje

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, unsigned, 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
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

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

unsigned nwd(unsigned a, unsigned b)
{
  unsigned r;
  while(b)
  {
      r = a % b;
      a = b;
      b = r;
  }
  return a;
}

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

  return 0;
}

 

Dane przekazywane przez wartość i przez referencję

Przekazywanie przez wartość oznacza, że funkcja otrzymuje w swoim parametrze wartość wyrażenia, które zostanie umieszczone w wywołaniu funkcji. 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
// (C)2014 I LO w Tarnowie
//------------------------

#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ść 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. W kolejnym wywołaniu 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
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

void cosik(int 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 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ć ze swoim 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
// (C)2014 I LO w Tarnowie
//------------------------

#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

 

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
// (C)2014 I LO w Tarnowie
//------------------------

#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
// (C)2014 I LO w Tarnowie
//------------------------

#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, do 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
// (C)2014 I LO w Tarnowie
//------------------------

#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

 

Podsumowanie:

Definicja funkcji:

typ_wyniku nazwa(lista_parametrów)
{
   treść;
   return wynik;
}
typ_wyniku określa zwracaną wartość
nazwa budowana wg zasad tworzenia nazw w C++. Nazwa funkcji pozwala się odwoływać do niej w programie.
lista_parametrów dane dostarczane do funkcji z wywołującego ją programu. Każdy parametr ma postać:

typ nazwa

Parametry są oddzielone od siebie przecinkami. Parametry można traktować jak wewnętrzne zmienne funkcji, tzn. funkcja może nadawać im nowe wartości.

treść zawartość funkcji
return wynik; polecenie kończące wykonywanie kodu funkcji. Powoduje powrót do miejsca wywołania oraz zwraca wartość wyliczoną przez funkcję

 

Parametry funkcji:

Funkcja może otrzymywać dane z programu na dwa sposoby:
  1. Przez wartość – w tym przypadku parametr funkcji jest kopią danych przekazanych jej z programu. Tego typu parametr w wywołaniu może przyjmować postać dowolnego wyrażenia. Wartość takiego wyrażenia jest obliczana i przekazywana funkcji w parametrze, np:
    cosik(a + 6 * b - c);
  2. Przez referencję – w definicji funkcji nazwa parametru musi być poprzedzona znakiem & (operatorem adresu). Funkcja otrzymuje adres zmiennej przechowującej dane. Poprzez ten adres funkcja ma pełny dostęp do zmiennej i może ją dowolnie zmieniać. W wywołaniu parametr musi być zmienną.

Zmienne lokalne i globalne:

Jeśli zmienna jest tworzona wewnątrz funkcji (a ogólniej wewnątrz bloku objętego klamerkami), to jej zakres widoczności ogranicza się do tej funkcji (do danego bloku). Jest to zmienna lokalna. Poza funkcją (blokiem) zmienna lokalna nie jest dostępna.

Jeśli zmienna jest tworzona na zewnątrz funkcji, to jej widoczność sięga od miejsca utworzenia do końca programu. Jest to zmienna globalna, czyli zmienna widoczna w każdej funkcji występującej w programie po jej definicji. Zmienna globalna jest ogólnie dostępna, może być zmieniona w dowolnym miejscu w programie. Dlatego zmienne globalne należy tworzyć ze szczególną ostrożnością.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe