5. Użycie funkcji
Przyjrzyj się poniższemu programowi – liczy on pole powierzchni prostokąta o podanych długościach boków.
void main(void)
{
float a, b; // deklarujemy zmienne przechowujące boki prostokąta
float dlugosc; // deklarujemy zmienną zawierającą wynik obliczeń
a = 5; b =10; // przypisujemy im wartości
dlugosc = a * b; // obliczamy pole prostokąta (tu równe 50)
}
A co jeśli chciałbyś obliczyć także pole prostokąta o innych długościach boków ? Odpowiesz pewnie: „Nic prostszego ! Wystarczy skopiować fragment tego programu i zmienić długości boków”. Owszem, ale co jeśli chciałbyś obliczyć pola stu prostokątów ? Albo gdybyś liczył coś bardziej skomplikowanego, co nie zajęłoby tylko jednej linijki, jak w naszym przypadku, a na przykład trzydzieści ? Przy użyciu tego sposobu program zająłby trzysta linijek ! Właśnie dlatego w języku C istnieją funkcje, które rozwiązują ten problem. Jak zbudowane są funkcje to już wiesz z poprzednich punktów, ale nie znasz praktycznego ich zastosowania. Poniższy program wykonuje tą samą operację jak ostatni, jednak obliczenia pola prostokąta wykonywane jest w funkcji:
float PoleProstokata(float bok1, float bok2)
{
// w tym miejscu bok1 jest równy 5,
// natomiast b jest równe 10
float wynik;
wynik = bok1 * bok2;
return wynik;
}
void main(void)
{
float a, b, dlugosc;
a = 5; b = 10;
dlugosc = PoleProstokata(a, b);
}
Jak widzisz, program zawiera dwie funkcje – main , która jest „obowiązkowa” w każdym programie oraz PoleProstokąta . Analizując nagłówek funkcji PoleProstokąta możemy zauważyć, że zwraca ona wynik obliczeń w postaci liczby rzeczywistej ( float ) oraz przyjmuje dwa parametry – bok1 i bok2 . W tym przypadku oba parametry są także typu float , jednak funkcja może przyjmować dowolną ilość argumentów dowolnego typu, wystarczy je wpisać w formie „typ_argumentu nazwa” i oddzielać przecinkami jeden od drugiego.
Pojawiło się nam tu także nowe słowo kluczowe – return . Wykonując to polecenie program powraca z funkcji do miejsca jej wywołania zwracając wartość podanego argumentu (który jest takiego typu, jak to określono w nagłówku funkcji). W naszym przypadku po tym poleceniu występuje nazwa zmiennej wynik , tak więc wartość zwracana przez tą funkcję jest równa wartości tej zmiennej, która z kolei jest obliczana linijkę wyżej (iloczyn dwóch podanych argumentów). Zacznijmy jednak analizę programu od miejsca, w którym się on rozpoczyna, czyli od funkcji main (pamiętaj, że jest to zawsze pierwsza funkcja wywoływana po uruchomieniu programu).
Na początku mamy znane już rzeczy – deklarację trzech zmiennych i przypisanie wartości. Ostatnia linijka jest jednak nowością, do zmiennej długość jest coś przypisywane. No właśnie, co ? Otóż jest to wartość zwrócona przez wywołaną funkcję. Jako parametry dla tej funkcji przekazujemy nasze zmienne a i b . Jest to tzw. przekazanie przez wartość, co znaczy, że nie przekazujemy samych zmiennych, a tylko wartości, które one zawierają. Wartości te są po prostu kopiowane do parametrów bok1 i bok2 i nawet jeśli w ciele funkcji zmienimy ich wartości to zmianie ulegną tylko te lokalne kopie, natomiast po powrocie do funkcji main zmienne a i b będą miały starą wartość.
Po wywołaniu funkcji PoleProstokata przenosimy się do ciała tej funkcji. W tym momencie pierwszy parametr funkcji o nazwie bok1 jest równy wartości zmiennej a (czyli pięć), natomiast drugi o nazwie bok2 jest równy wartości zmiennej b (czyli dziesięć). Pierwszą rzeczą jest deklaracja zmiennej wynik – to już znamy. Następnie, w wyniku pomnożenia zmiennej bok1 przez bok2 , do zmiennej wynik wpisana jest wartość piętnaście. Właśnie tą wartość zwracamy przy pomocy return do miejsca, w którym funkcja PoleProstokata została wywołana i właśnie tą wartość będzie zawierała zmienna długosc po wykonaniu ostatniej linijki programu.
Mam nadzieję, że zrozumiałeś rzeczy poruszone w tym punkcie, gdyż funkcje to podstawa języka C. Na zakończenie jednak wspomnę o jeszcze jednej ważnej rzeczy dotyczącej funkcji, żebyś mógł bezboleśnie zrozumieć następny punkt. Załóżmy, że nasz ostatni program zapiszemy w trochę inny sposób, tzn. najpierw zapiszemy funkcję main , a dopiero pod nią funkcję PoleProstokata . Czyli wyglądałoby to następująco:
void main(void)
{
float a, b, dlugosc;
a = 5; b = 10;
dlugosc = PoleProstokata(a, b);
}
float PoleProstokata(float bok1, float bok2)
{
// w tym miejscu bok1 jest równy 5,
// natomiast b jest równe 10
float wynik;
wynik = bok1 * bok2;
return wynik;
}
Jak myślisz, czy taki program skompiluje się bez żadnego problemu ? Odpowiesz zapewne: „Oczywiście, dlaczego kolejność zapisu funkcji miałaby wpływać na jego poprawność ?”. Masz rację, jednak tylko częściowo. W zależności od typu używanego przez Ciebie kompilatora, próba kompilacji tego programu albo zakończy się zupełnym niepowodzeniem, albo zostaną wyświetlone ostrzeżenia. Dlaczego ? Podejdźmy do zagadnienia od strony tego, w jaki sposób działa kompilator. Otóż analizuje on program, linijka po linijce, sprawdzając czy jest on poprawny. Tak więc sprawdza on pierwsze pięć linijek programu, aż dochodzi do linii, w której mamy wywołanie funkcji PoleProstokata . I tutaj wyświetla błąd ponieważ nie wie co ta nazwa oznacza – ani nie jest to żadne ze słów kluczowych języka C, ani nie jest to też wcześniej zadeklarowana zmienna. Sposób zapisu co prawda sugeruje, że jest to jakaś funkcja, ale skąd ma on wiedzieć jakie parametry powinna ona przyjmować ? Zapytasz zapewne: „Jak to nie wie ? Przecież parę linijek niżej jest ta funkcja zdefiniowana !”. Właśnie – parę linijek niżej. A ponieważ kompilator analizuje poprawność programu zaczynając od jego początku to nie wie, że definicja tej funkcji znajduje się gdzieś niżej (albo zupełnie w innym pliku). Aby zaradzić tej sytuacji stosuje się w języku C tzw. prototypy. Jest to po prostu informacja dla kompilatora, że gdzieś niżej znajdzie funkcję o podanej nazwie oraz określonych parametrach. W naszym przypadku chcemy powiadomić kompilator o funkcji PoleProstokata . Wystarczy, że dodany na samym początku taką oto linijkę:
float PoleProstokata(float bok1, float bok2);
Zauważ, że jest to dokładna kopia nagłówka naszej funkcji zakończona średnikiem. Teraz kompilator sprawdzając nasz program w pierwszej linijce znajdzie informację o tym, że w przypadku napotkania nazwy PoleProstokata jest to funkcja, która przyjmuje dwa parametry typu float i zwraca także float .