Prawo Murphy'ego: Głupcy są tak pomysłowi, że niemożliwe jest stworzenie czegoś, z czym każdy głupi sobie poradzi.
Kurs C++ - Część VIII. Własne funkcje

Program często powtarza jakieś czynności wielokrotnie. Na przykład wczytuje jakieś dane ze skomplikowaną kontrola poprawności lub przeprowadza identyczne obliczenia. Jednak umieszczenie kodu pojedynczego wczytania w pętli nie jest możliwe, gdyż musimy wczytywać dane na różnych drogach programu i w dodatku różnie je rozmieszczać na ekranie. Do takich zadań służą w C++ funkcje. Używaliśmy już funkcji, nawet się nad tym nie zastanawiając. Przecież cały program to jedną funkcją, która nazywała się main.

Funkcja to jakby zastąpienie jedną instrukcją wielu instrukcji wykonujących jakieś konkretne zadanie. W naszych przykładach zajmiemy się sprawdzaniem typu znaku. Wpiszemy przed funkcją main coś takiego:

   void znak()
   {   //Tu jest ciało funkcji, czyli instrukcje, z których funkcja się składa
   }

To jest definicja funkcji, czyli określenie, co funkcja ma robić. Przyjrzyjmy się dokładniej tej konstrukcji. Nasza funkcja będzie się nazywać znak. W nawiasach klamrowych, czyli w ciele funkcji, będą się znajdowały wszystkie instrukcje, które chcemy wykonywać przy pomocy tej funkcji. Na początek wyświetlimy wynik prostego działania. Zmienimy nasz kod:

   void znak ()
   {   cout << "\'A\' jest literą"<< endl;
   }

Apostrof jako znak specjalny - podobnie jak znak nowego wiersza - musi być poprzedzony znakiem \. Teraz w funkcji main wywołamy naszą funkcję, czyli wydamy polecenie:

   znak ();

a program wyświetli na konsoli komunikat: 'A' jest literą. Spróbuj teraz przenieść kod naszej funkcji za funkcję main. Kompilator wyświetli komunikat o braku naszej funkcji.

Zapowiedź funkcji

Nasz funkcja jest dla kompilatora nowym poleceniem, musi ją więc znać, żeby mógł dobrze wykonać pracę. Początkowo zdefiniowaliśmy całą funkcję przed funkcją main. Gdy kompilator analizuje funkcję main i napotka na wywołanie naszej funkcji kwadrat(), to już ją zna. W drugim przypadku jest inaczej. Kompilator dochodząc do wywołania nie zna jeszcze naszej funkcji kwadrat(). Stąd błąd. Często wygodniej i czytelniej jest definiować swoje funkcje za funkcją main. Jeśli tak robimy to należy jakoś powiadomić kompilator, że mamy zamiar użyć funkcji i będzie się ona nazywać tak a tak. Robimy to umieszczając przed funkcją main zapowiedź naszej funkcji (bez określania jej ciała), a zapisujemy ją tam, gdzie nam odpowiada. Jedynym miejscem, gdzie nie wolno definiować funkcji, jest ciało innej funkcji. Zapowiedź funkcji wygląda następująco:

   void znak();

Parametry funkcji

Napisaliśmy funkcję, która wyświetla komunikat. Jaki to ma jedna sens, jeżeli zawsze wyświetli nam tekst 'A' jest literą? My byśmy chcieli, sprawdzał dowolny znak.

Zauważyłeś już, że przy deklaracji funkcji zawsze podajemy parę nawiasów. Służą one do umieszczenia między nimi jakiejś informacji dla funkcji, którą właśnie wywołujemy. Ta informacja nazywa się parametrami funkcji. My chcemy podać funkcji argument do sprawdzenia klasy znaku. Na przykład tak:

   znak ('1');

I teraz funkcja powinna wyświetlić komunikat '2' jest cyfrą. Znak powinien być typu char. Musimy powiedzieć kompilatorowi, że nasza funkcja będzie pobierać parametr typu char:

   void znak (char zn)
   {   if (zn >='A' && zn <='Z') cout <<  zn << " jest wielką literą" <<endl;
       if (zn >='a' && zn <='z') cout <<  zn << " jest małą literą" <<endl;
       if (zn >='0' && zn <='0') cout <<  zn << " jest cyfrą" <<endl;
   }

Jak widać, podaliśmy w nawiasach parametr wraz z typem (możesz podać w razie potrzeby wiele parametrów oddzielonych przecinkami). Przypomina to deklarowanie zmiennych. Parametry nie są niczym innym, jak zmiennymi jakiegoś typu, które przekazujemy do funkcji. Kompilator widząc taki kod zarezerwuje wewnątrz funkcji zmienną typu char i nazwie ją zn. Zmienna taka będzie wyłącznie własnością funkcji. Podobnie jak inne zmienne zadeklarowanymi wewnątrz funkcji. Na takie zmienne mówimy, że są lokalne dla funkcji. Gdy funkcja się zakończy, program automatycznie usunie te zmienne z pamięci. Zmienna lokalna ma jeszcze jedną zaletę: na jej nazwę nie ma wpływu to, czy gdzieś poza funkcją jest już zmienna o takiej samej nazwie. Dla funkcji ważniejsze są zmienne, które są jej własnością. Jeśli poza funkcją istnieje zmienna o nazwie identycznej jak zmienna lokalna, to zostanie przasłonięta przez zmienną lokalną.

Zapowiedź naszej funkcji można zapisać na 2 sposoby. Podając nagłówek funkcji, wraz z listą parametrów lub podając jedynie typy parametrów.

   void znak(char zn);
   void znak(char);

Wartość zwracana z funkcji

Funkcja testuje parametr i wyświetla komunikat, ale my chcielibyśmy jeszcze ją mieć w programie, aby użyć do dalszych działań. Do zakończenia działania funkcji służy instrukcja return. Gdy wykonamy testy i wyświetlimy komunikat, użyjemy return by wyjść z funkcji. return pozwala również przy wychodzeniu z funkcji zwrócić do programu jakieś dane.

   int znak(char zn)
   {   int retwar = 4;
       if (zn >='A' && zn <='Z')
       {   cout <<  zn << " jest wielką literą" <<endl;
           retwar = 0;
       }
       if (zn >='a' && zn <='z')
       {   cout <<  zn << " jest małą literą" <<endl;
           retwar = 1;
       }
       if (zn >='0' && zn <='0')
       {   cout <<  zn << " jest cyfrą" <<endl;
           retwar = 2;
       }
       zn+=10;			//wykonaliśmy zwiększenie parametru o 10
       return (retwar)
   }

Tera zmieniliśmy w nagłówku funkcji typ void na typ int. Co to znaczy? Poprzednio funkcja nie zwracała żadnej wartości do kodu, który ją wywołał. Czyli typ wartości zwracanej przez funkcję był void (żaden). Teraz funkcja zwraca wartość całkowitą, czyli typ wartości zwracanej jest int. Do zwracania wartości służy instrukcja return. W naszym przykładzie zwracamy kod typu podanego parametru. Co nam to daje? Możemy teraz wywołać funkcję w taki sposób:

   if (znak (x)<2) cout << "To jest litera\n";
   else cout << "To nie jest litera\n";

Jeżeli funkcja zwróci wartość < 2, to wykona się pierwszy wiersz. W przeciwnym razie - drugi. Zapowiedź funkcji będzie teraz wyglądała tak:

   int znak(char);

W tym zapisie widać, co wchodzi do funkcji, a co z niej wychodzi. Wartością zwracana przez funkcję może być zmienną dowolnego typu, oprócz tablicy. Można to obejść, zwracając wskaźnik do tablicy. Podobnie z przekazywaniem parametrów do funkcji.

Przekazywanie parametrów przez wartość

Sprawdźmy poniższe użycie naszej funkcji:

   void main()
   {   char x = 'X';

       cout << znak (x) << endl;
       cout << "x=" << x<< endl;
   }

Wszystko działa poprawnie, ale zwróć uwagę, że zmienna x ma nadal taką samą wartość. Przy przekazywaniu parametrów do funkcji, program tylko kopiuje ich wartości i przypisuje je do zmiennych liczba lokalnych wewnątrz funkcji. Takie przekazywanie parametru nazywa się przekazywaniem przez wartość. Wewnątrz funkcji pracujemy więc na kopii zmiennej podanej jako parametr, nie modyfikując oryginału. W podobny sposób zwracany jest wynik. Funkcja kopiuje wartość zwracaną do tymczasowego obiektu, którego nie widać. Po wyświetleniu jego wartości obiekt zostaje zlikwidowany. Ale my byśmy woleli pozostawić ten wynik przy życiu. Co więc zrobić, żeby funkcja znak zapisała wynik bezpośrednio w parametrze?

Przekazywanie parametrów przez referencję

I właśnie tutaj przychodzi nam z pomocą referencja. Jak pamiętasz, referencja to jakby inna nazwa zmiennej. Jeśli parametr funkcji będzie przyjmowany jako referencja, to tak jakbyśmy posłali do funkcji zmienną, a nie jej kopię. Wewnątrz funkcji będziemy używać już nie kopii, ale samej zmiennej, tylko będzie ona mieć inną nazwę dzięki referencji. Po takiej zmianie:

   int znak(char &zn)
   {   int retwar = 4;
       if (zn >='A' && zn <='Z')
       {   cout <<  zn << " jest wielką literą" <<endl;
           retwar = 0;
       }
       if (zn >='a' && zn <='z')
       {  cout <<  zn << " jest małą literą" <<endl;
          retwar = 1;
       }
       if (zn >='0' && zn <='0')
       {   cout <<  zn << " jest cyfrą" <<endl;
           retwar = 2;
       }
       zn+=10;			//wykonaliśmy zwiększenie parametru o 10
       return (retwar)
   }

funkcja będzie modyfikować wartość oryginału, a nie kopii jak poprzednio. Dzięki temu zmienna x będzie teraz powiększona o 10. I to dzięki jednemu dodatkowemu znaczkowi. Również funkcja może wynik poprzez referencję. Wtedy nagłówek funkcji wygląda tak:

   int &znak(char &zn)

W naszym przypadku nic by to nie zmieniło. Czasami jednak taki sposób zwracania wartości jest przydatny. Trzeba uważać, żeby nie zwracać referencji czegoś, co już nie będzie istniało poza funkcją: na przykład jej zmiennych lokalnych.

Przekazywanie parametrów przez wskaźnik

Trzeci sposób przekazywania parametrów do funkcji lub zwracania wartości polega na użyciu wskaźników. Pamiętasz, że parametrem funkcji nie może być tablica? Teraz poznasz sposób, w jaki można tablicę wrzucić do funkcji. Dajmy na to, że masz taką tablicę znaków:

   char napis[] = "Tablica przekazana jako parametr";

Jak można by ją przekazać do funkcji, która służy do wyświetlania tekstu? Przy okazji wskaźników mówiliśmy, że nazwa tablicy jest wskaźnikiem do jej pierwszego elementu. Jeśli dałoby się przekazać do funkcji taki wskaźnik, to problem będzie rozwiązany. Oto jak należy to zrobić:

   void wyswietl(char * text)   { cout << text << endl; }

Teraz już możemy posłać do funkcji tablicę, wiedząc, że jej nazwa jest adresem jej pierwszego elementu:

   wyswietl(napis);

W podobny sposób możemy wyświetlić tekst zaczynając od innej litery. Wystarczy, że poślemy wskaźnik na inny element tablicy, jak w poniższej instrukcji:

   wyswietl(&napis[8]);

Spowoduje to wyświetlenie tylko przekazana jako parametr. Zauważ, że odwoływanie się do parametru poprzez jego wskaźnik również pozwala na modyfikowanie oryginału. Podobnie jak referencję, funkcja może także zwracać wskaźnik. I tutaj też trzeba uważać, żeby nie zwracać wskaźnika do obiektów, które przestaną istnieć po opuszczeniu funkcji.

Na tym kończymy wstęp do C++. Teraz zostaje ci już tylko zacząć samodzielne pisanie kolejnych programów: im więcej napisanych programów, tym lepsza znajomość programowania i tym łatwiejszy następny program...
« wstecz   dalej »