R05-06.DOC

(404 KB) Pobierz
Szablon dla tlumaczy

Rozdział 5.
Funkcje

Choć w programowaniu zorientowanym obiektowo zainteresowanie użytkowników zaczęło koncentrować się na obiektach, jednak mimo to funkcje w dalszym ciągu pozostają głównym komponentem każdego programu. Funkcje globalne występują poza obiektami, zaś funkcje składowe (zwane także metodami składowymi) występują wewnątrz obiektów, wykonując ich pracę.

Z tego rozdziału dowiesz się:

·         czym jest funkcja i z jakich części się składa,

·         jak deklarować i definiować funkcje,

·         jak przekazywać argumenty do funkcji,

·         jak zwracać wartość z funkcji.

Zaczniemy od funkcji globalnych; w następnym rozdziale dowiesz się, w jaki sposób funkcje działają wewnątrz obiektów.

Czym jest funkcja?

Ogólnie, funkcja jest podprogramem, operującym na danych i zwracającym wartość. Każdy program C++ posiada przynajmniej jedną funkcję, main(). Gdy program rozpoczyna działanie, funkcja main() jest wywoływana automatycznie. Może ona wywoływać inne funkcje, które z kolei mogą wywoływać kolejne funkcje.

Ponieważ funkcje te nie stanowią części jakiegoś obiektu, są nazywane „globalnymi” — mogą być dostępne z dowolnego miejsca programu. W tym rozdziale, gdy będziemy mówić o funkcjach, będziemy mieli na myśli właśnie funkcje globalne (chyba, że postanowimy inaczej).

Każda funkcja posiada nazwę; gdy ta nazwa zostanie napotkana przez program, przechodzi on do wykonywania kodu zawartego wewnątrz ciała tej funkcji. Nazywa się to wywołaniem funkcji. Gdy funkcja wraca, wykonanie programu jest wznawiane od instrukcji następującej po wywołaniu tej funkcji. Ten przepływ sterowania został pokazany na rysunku 5.1.

Rysunek 5.1. Gdy program wywołuje funkcję, sterowanie przechodzi do jej ciała, po czym jest wznawiane od instrukcji występującej po wywołaniu tej funkcji

 

Dobrze zaprojektowane funkcje wykonują określone, łatwo zrozumiałe zadania. Złożone zadania powinny być dzielone na kilka, odpowiednio wywoływanych funkcji.

Funkcje występują w dwóch odmianach: zdefiniowane przez użytkownika (programistę) oraz wbudowane. Funkcje wbudowane stanowią część pakietu dostarczanego wraz z kompilatorem — zostały one stworzone przez producenta kompilatora, z którego korzystasz. Funkcje zdefiniowane przez użytkownika są funkcjami, które piszesz samodzielnie.

Zwracane wartości, parametry i argumenty

Funkcja może zwracać wartość. Gdy wywołujesz funkcję, może ona wykonać swoją pracę, po czym zwrócić wartość stanowiącą rezultat tej pracy. Ta wartość jest nazywana wartością zwracaną, zaś jej typ musi być zadeklarowany. Zatem, gdy piszesz:

 

int myFunction();

 

deklarujesz, że funkcja myFunction zwraca wartość całkowitą.

Możesz także przekazywać wartości do funkcji. Te wartości pełnią rolę zmiennych, którymi możesz manipulowa䄇 wewnątrz funkcji.

Opis przekazywanych wartości jest nazywany listą parametrów.

 

int myFunction(int someValue, float someFloat);

 

Ta deklaracja wskazuje, że funkcja myFunction nie tylko zwraca liczbę całkowitą, ale także, że jej parametrami są: wartość całkowita oraz wartość typu float.

Parametr opisuje typ wartości, jaka jest przekazywana funkcji podczas jej wywołania. Wartości przekazywane funkcji są nazywane argumentami.

 

int theValueReturned = myFunction(5, 6.7);

 

W tej deklaracji Wwidzimy, tutaj że zmienna całkowita theValueReturned (zwracana wartość) jest inicjalizowana wartością zwracaną przez funkcję myFunction, której zostały przekazane wartości 5 oraz 6.7 (jako argumenty). Typy argumentów muszą odpowiadać zadeklarowanym typom parametrów.

Deklarowanie i definiowanie funkcji

Aby użyć funkcji w programie, należy najpierw zadeklarować funkcję, a następnie ją zdefiniować. Deklaracja informuje kompilator o nazwie funkcji, typie zwracanej przez nią wartości, oraz o jej parametrach. Z kolei definicja informuje, w jaki sposób dana funkcja działa. Żadna funkcja nie może zostać wywołana z jakiejkolwiek innej funkcji, jeśli nie zostanie wcześniej zadeklarowana. Deklaracja funkcji jest nazywana prototypem.

Deklarowanie funkcji

Istnieją trzy sposoby deklarowania funkcji:

·         zapisanie prototypu funkcji w pliku, a następnie użycie dyrektywy #include w celu dołączenia go do swojego programu,

·         zapisanie prototypu w pliku, w którym dana funkcja jest używana,

·         zdefiniowanie funkcji zanim zostanie wywołana przez inne funkcje. Jeśli tego nie dokonasz, definicja będzie pełnić jednocześnie rolę deklaracji funkcji.

Choć możesz zdefiniować funkcję przed jej użyciem i uniknąć w ten sposób konieczności tworzenia jej prototypu, nie należy to do dobrych obyczajów programistycznych z trzech powodów.

Po pierwsze, niedobrze jest, gdy funkcje muszą występować w pliku źródłowym w określonej kolejności. Powoduje to, że w razie wprowadzenia zmian trudno jest zmodyfikować taki program.

Po drugie, istnieje możliwość, że w pewnych warunkach funkcja A() musi być w stanie wywołać funkcję B(), a funkcja B() także musi być w stanie wywołać funkcję A(). Nie jest możliwe zdefiniowanie funkcji A() przed zdefiniowaniem funkcji B() i jednoczesne zdefiniowanie funkcji B() przed zdefiniowaniem funkcji A(), dlatego przynajmniej jedna z nich zawsze musi zostać zadeklarowana.

Po trzecie, prototypy funkcji stanowią wydajną technikę debuggowania (usuwania błędów w programach). Jeśli z prototypu wynika, że funkcja otrzymuje określony zestaw parametrów lub że zwraca określony typ wartości, to w przypadku gdy funkcja nie jest zgodna z tym prototypem, kompilator, zamiast czekać na wystąpienie błędu podczas działania programu, może wskazać tę niezgodność. Przypomina to dwustronną księgowość. Prototyp i definicja sprawdzają się wzajemnie, redukując prawdopodobieństwo, że zwykła literówka spowoduje błąd w programie.

Prototypy funkcji

Wiele z wbudowanych funkcji posiada już gotowe prototypy. Występują one w plikach, które są dołączane do programu za pomocą dyrektywy #include. W przypadku funkcji pisanych samodzielnie, musisz stworzyć samodzielnie także ich prototypy.

Prototyp funkcji jest instrukcją, co oznacza, że kończy się on średnikiem. Składa się ze zwracanego przez funkcję typu oraz tzw. sygnatury funkcji. Sygnatura funkcji to jej nazwa oraz lista parametrów.

Lista parametrów jest listą wszystkich parametrów oraz ich typów, oddzielonych od siebie przecinkami. Elementy prototypu funkcji przedstawia rysunek 5.2.

 

Rysunek 5.2. Elementy prototypu funkcji

 

Zwracany typ oraz sygnatura prototypu i definicji funkcji muszą zgadzać się dokładnie. Jeśli nie są one zgodne, wystąpi błąd kompilacji. Zauważ jednak, że prototyp funkcji nie musi zawierać nazw parametrów, a jedynie ich typy. Poniższy prototyp jest poprawny:

 

long Area(int, int);

 

Ten prototyp deklaruje funkcję o nazwie Area (obszar), która zwraca wartość typu long i posiada dwa parametry, będące wartościami całkowitymi. Choć ten zapis jest poprawny, jednak jego stosowanie nie jest dobrym pomysłem. Dodanie nazw parametrów powoduje, że prototyp staje się bardziej czytelny. Ta sama funkcja z nazwanymi parametrami mogłaby być zadeklarowana następująco:

 

long Area(int length, int width );

 

W tym przypadku jest oczywiste, do czego służy ta funkcja oraz jakie są jej parametry.

Zwróć uwagę, że wszystkie funkcje zwracają wartość pewnego typu. Jeśli ten typ nie zostanie podany jawnie, zakłada się, że jest wartością całkowitą, a konkretnie typem int. Twoje programy będą jednak łatwiejsze do zrozumienia, jeśli we wszystkich funkcjach, włącznie z funkcją main(), będziesz deklarował zwracany typ.

Definiowanie funkcji

Definicja funkcji składa się z nagłówka funkcji oraz z jej ciała. Nagłówek przypomina prototyp funkcji, w którym wszystkie parametry muszą być nazwane a na końcu nagłówka nie występuje średnik.

Ciało funkcji jest ujętym w nawiasy klamrowe zestawem instrukcji. Rysunek 5.3 przedstawia nagłówek i ciało funkcji.

 

Rysunek 5.3. Nagłówek i ciało funkcji

 

Listing 5.1 demonstruje program zawierający prototyp oraz deklarację funkcji Area().

Listing 5.1. Deklaracja i definicja funkcji oraz ich wykorzystanie w programie

  0:  // Listing 5.1 - demonstruje użycie prototypów funkcji

  1: 

  2:  #include <iostream>

  3:  int Area(int length, int width); //prototyp funkcji

  4: 

  5:  int main()

  6:  {

  7:      using std::cout;

  8:      using std::cin;

  9: 

10:      int lengthOfYard;

11:      int widthOfYard;

12:      int areaOfYard;

13: 

14:      cout << "\nJak szerokie jest twoje podworko? ";

15:      cin >> widthOfYard;

16:      cout << "\nJak dlugie jest twoje podworko? ";

17:      cin >> lengthOfYard;

18: 

19:      areaOfYard= Area(lengthOfYard,widthOfYard);

20: 

21:      cout << "\nTwoje podworko ma ";

22:      cout << areaOfYard;

23:      cout << " metrow kwadratowych\n\n";

24:      return 0;

25:  }

26: 

27:  int Area(int l, int w)

28:  {

29:      return l * w;

30:  }

 

Wynik

Jak szerokie jest twoje podworko? 100

 

Jak dlugie jest twoje podworko? 200

 

Twoje podworko ma 20000 metrow kwadratowych

Analiza

Prototyp funkcji Area() znajduje się w linii 3. Porównaj ten prototyp z definicją funkcji, zaczynającą się od linii 27. Zwróć uwagę, że nazwa, zwracany typ oraz typy parametrów są takie same. Gdyby były różne, wystąpiłby błąd kompilacji. W rzeczywistości jedyną różnicę stanowi fakt, że prototyp funkcji kończy się średnikiem i nie posiada ciała.

Zwróć także uwagę, że nazwy parametrów w prototypie to length (długość) oraz width (szerokość), ale nazwy parametrów w definicji to l oraz w. Jak wspomniano wcześniej, nazwy w prototypie nie są używane i służą wyłącznie jako informacja dla programisty. Do dobrych obyczajów programistycznych należy dopasowanie nazw parametrów prototypu do nazw parametrów definicji (nie jest to wymaganie języka).

Argumenty są przekazywane funkcji w takiej kolejności, w jakiej zostały zadeklarowane i zdefiniowane parametry, lecz ich nazwy nie muszą do siebie pasować. Gdy przekażesz zmienną widthOfYard (szerokość podwórka), a po niej lengthOfYard (długość podwórka), wtedy funkcja FindArea (znajdź obszar) użyje wartości widthOfYard jako długości oraz wartości lengthOfYard jako szerokości. Ciało funkcji jest zawsze ujęte w nawiasy klamrowe, nawet jeśli (tak jak w tym przypadku) składa się z jednej tylko instrukcji.

Wykonywanie funkcji

Gdy wywołujesz funkcję, jej wykonanie rozpoczyna się od pierwszej instrukcji następującej po otwierającym nawiasie klamrowym ({). Rozgałęzienie działania można uzyskać za pomocą instrukcji if. (Instrukcja if oraz instrukcje z nią związane zostaną omówione w rozdziale 7.). Funkcje mogą także wywoływać inne funkcje, a nawet wywoływać siebie same (patrz podrozdział „Rekurencja” w dalszej części tego rozdziału).

Zmienne lokalne

Zmienne można przekazywać funkcjom; można również deklarować zmienne wewnątrz ciała funkcji. Zmienne deklarowane wewnątrz ciała funkcji są nazywane „lokalnymi”, gdyż istnieją tylko lokalnie wewnątrz danej funkcji. Gdy funkcja wraca (kończy działanie), zmienne lokalne przestają być dostępne i zostają zniszczone przez kompilator.

Zmienne lokalne są definiowane tak samo, jak wszystkie inne zmienne. Parametry przekazywane do funkcji także są uważane za zmienne lokalne i mogą być używane identycznie, jak zmienne zadeklarowane wewnątrz ciała funkcji. Przykład użycia parametrów oraz zmiennych zadeklarowanych lokalnie wewnątrz funkcji przedstawia listing 5.2.

 

Listing 5.2. Użycie zmiennych lokalnych oraz parametrów

  0:  #include <iostream>

  1: 

  2:  float Convert(float);

  3:  int main()

  4:  {

  5:      using namespace std;

  6: 

  7:      float TempFer;

  8:      float TempCel;

  9: 

10:      cout << "Podaj prosze temperature w stopniach Fahrenheita: ";

11:      cin >> TempFer;

12:      TempCel = Convert(TempFer);

13:      cout << "\nOdpowiadajaca jej temperatura w stopniach Celsjusza: ";

14:      cout << TempCel << endl;

15:      return 0;

16:  }

17: 

18:  float Convert(float TempFer)

19:  {

20:      float TempCel;

21:      TempCel = ((TempFer - 32) * 5) / 9;

22:      return TempCel;

23:  }

 

Wynik

Podaj prosze temperature w stopniach Fahrenheita: 212

 

Odpowiadajaca jej temperatura w stopniach Celsjusza: 100

 

Podaj prosze temperature w stopniach Fahrenheita: 32

 

Odpowiadajaca jej temperatura w stopniach Celsjusza: 0

 

Podaj prosze temperature w stopniach Fahrenheita: 85

 

Odpowiadajaca jej temperatura w stopniach Celsjusza: 29.4444

Analiza

W liniach 7. i 8. są deklarowane dwie zmienne typu float, z których jednak przechowuje temperaturę w stopniach Fahrenheita, zaś druga w stopniach Celsjusza. W linii 10. użytkownik jest proszony o podanie temperatury w stopniach Fahrenheita, zaś uzyskana wartość jest przekazywana funkcji Convert() (konwertuj).

Wykonanie programu przechodzi do pierwszej linii funkcji Convert() w linii 20., w której jest deklarowana zmienna lokalna, także o nazwie TempCel (temperatura w stopniach Celsjusza). Zwróć uwagę, że ta zmienna lokalna nie jest równoważna zmiennej TempCel w linii 8. Ta zmienna istnieje tylko wewnątrz funkcji Convert(). Wartość przekazywana jako parametr, TempFer (temperatura w stopniach Fahrenheita), także jest tylko lokalną kopią zmiennej przekazywanej przez funkcję main().

Ta funkcja mogłaby posiadać parametr o nazwie FerTemp i zmienną lokalną CelTemp, a program działałby równie dobrze. Aby przekonać się że program działa, możesz wpisać te nazwy i ponownie go skompilować.

Lokalnej zmienneja funkcji, TempCel, jest przypisywana wartość, będąca wynikiem odjęcia 32 od parametru TempFer, pomnożenia przez 5, a następnie podzielenia przez 9. Ta wartość jest następnie zwracana jako wartość funkcji, która w linii 12. jest przypisywana zmiennej TempCel wewnątrz funkcji main(). Ta wartość jest wypisywana w linii 14.

Program został uruchomiony trzykrotnie. Za pierwszym razem została podana wartość 212, w celu upewnienia się, czy punkt wrzenia wody w stopniach Fahrenheita (212) daje właściwy wynik w stopniach Celsjusza (100). Drugi test sprawdza temperaturę zamarzania wody. Trzeci test to przypadkowa wartość, wybrana w celu wygenerowania wyniku ułamkowego.

Zakres

Zmienna posiada zakres, który określa, jak długo i w których miejscach programu jest ona dostępna. Zmienne zadeklarowane wewnątrz bloku mają zakres obejmujący ten blok; mogą być dostępne tylko wewnątrz tego bloku i „przestają istnieć” po wyjściu programu z tego bloku. Zmienne globalne mają zakres globalny i są dostępne w każdym miejscu programu.

Zmienne globalne

Zmienne zdefiniowane poza funkcją mają zakres globalny i są dostępne z każdej funkcji w programie, włącznie z funkcją main().

Zmienne lokalne o takich samych nazwach, jak zmienne globalne nie zmieniają zmiennych globalnych. Jednak zmienna lokalna o takiej samej nazwie, jak zmienna globalna przesłania „ukrywa” zmienną globalną. Jeśli funkcja posiada zmienną o takiej samej nazwie jak zmienna globalna, to ta nazwa użyta wewnątrz funkcji odnosi się do zmiennej lokalnej, a nie do globalnej. Ilustruje to listing 5.3.

Listing 5.3. Przykład zmiennych lokalnych i globalnych

  0:  #include <iostream>

  1:  void myFunction();           // prototyp

  2: 

  3:  int x = 5, y = 7;            // zmienne globalne

  4:  int main()

  5:  {

  6:      using std::cout;

  7: 

...

Zgłoś jeśli naruszono regulamin