Prog cz 3

. FUNKCJE

Jeśli tworzenie programu w C++ porównamy do budowania domku z klocków, to funkcje są właśnie takimi klockami. Są to podprogramy działające pod kontrolą głównej funkcji programu - main(). Rozwiązanie takie sprzyja nie tylko pracy grup programistów (pracę nad programem można podzielić na części), ale też znajdowaniu potencjalnych błędów - łatwiej przecież znaleźć pomyłkę w mniejszych fragmentach kodu.

Deklaracja funkcji wygląda następująco: typ_zwracany nazwa_funkcji(argumenty).

Gdy użyjemy funkcji w programie, może ona w swoje miejsce zwrócić wartość określonego typu. Jeśli będzie to np. typ int (przypomnij sobie wiadomości z 1 części kursu na temat typów danych), to wartość zwracana tejże funkcji będzie traktowana w programie jako najzwyklejsza w świecie liczba całkowita (wartość zwracaną podajemy po słowie return). Argumentami funkcji będą zaś wartości, które wprowadzamy do funkcji i na których (najczęściej) funkcja ta pracuje.


Przykładowo, jeśli stworzymy funkcję sumującą dwie liczby a i b, to argumentami będą a i b, a wartością zwracaną ich suma. W praktyce definicja tej funkcji wygląda:


int dodaj(int a, int b) /* funkcja zwraca liczbe calkowita, a pobiera dwie takie liczby: a oraz b */

{

int suma;

suma = a + b;

return suma; // funkcja zwraca wartosc zmiennej suma

}


Do funkcji w programie odnosimy się bezpośrednio przez podanie jej nazwy i argumentów, czyli np. dodaj(3, 4). Jednak zanim z niej skorzystamy, powinniśmy "zgłosić" ją kompilatorowi, gdyż taka instrukcja: dodaj(liczba1, liczba2) jest dla niego (bez deklaracji) kompletnie niezrozumiała.

Istnieją dwa sposoby przekazania kompilatorowi informacji o funkcji: deklaracja przed, a definicja (tzn. cała treść funkcji) po funkcji main() lub też cała definicja przed main(). Przeanalizujmy rzecz na konkretnym przykładzie:


#include <iostream.h>

int odejmowanie(int a, int b); // deklaracja (nie definicja!) funkcji odejmowanie

int dodawanie(int a, int b) // poczatek definicji funkcji dodawanie

{

return a + b; // funkcja zwraca sume a i b

} // koniec definicji funkcji dodawanie

main ()

{ // poczatek programu

int suma, roznica;

suma = dodawanie(25, 28); /* do zmiennej suma wpisujemy wartosc zwracana przez funkcje */

roznica = odejmowanie(25, 28);

} // koniec programu

int odejmowanie(int a, int b) // poczatek definicji

{

return a - b; // funkcja zwraca roznice a i b

}


Zapewne domyślasz się już, czym różni się deklaracja od definicji, ale dla porządku możemy zaznaczyć: deklaracja jedynie sygnalizuje kompilatorowi, że coś się pojawi, podczas gdy definicja konkretnie określa co (tak to wygląda na "chłopski rozum"). Należy pamiętać, że każda definicja jest jednocześnie deklaracją. Tylko i wyłącznie od nas zależy, który sposób określania funkcji w programie będziemy stosować.


Czasami jednak nie chcemy, by funkcja zwracała jakąkolwiek wartość - po prostu mają się wykonać zawarte w niej instrukcje i tyle. Aby funkcja nie zwracała żadnych wartości określamy jej typ jako void. Przykladowo:


void suma (int a, int b) // typ zwracany jako void

{

cout << "Suma: " << a + b;

} // nie uzywamy return, bo nie ma co zwracac!

main()

{

suma(4, 5); // funkcja wyswietla sume 2 liczb

}


Jeśli nie chcemy natomiast pobierać żadnych argumentów, to teoretycznie też powinniśmy wpisać void w ich miejsce. Jednak w praktyce wystarczy, że pozostawimy pusty nawias, np. int funkcja().

Przykład funkcji bezargumentowych:


#include <iostream.h>

void pisz1();

void pisz2();

main()

{

int a;

cin >> a;

if (a == 1) pisz1();

else pisz2();

}

void pisz1()

{

cout << "Wybrales liczbe 1";

}

void pisz2()

{

cout << "Nie wybrales 1";

}


Funkcje mogą pracować nie tylko na argumentach - możemy również zdefiniować w ich wnętrzu zmienne (w taki sam sposób, jak zwykle). Przykład:


int funkcja(int a, char b)

{

int c = 28; // definicja zmiennej wewnatrz funkcji

return a + b + c;

}


Pomyślmy: zmienne możemy definiować poza funkcją oraz w jej wnętrzu. Co się więc stanie, jeśli zdefiniujemy zmienną o nazwie liczba poza funkcją, a potem drugą taką samą, ale już w środku funkcji? Jak się okazuje, że nie będzie to błąd - jako liczba traktowana będzie zmienna zdefiniowana "bardziej prywatnie", a więc wewnątrz funkcji. Wyróżniamy bowiem zmienne o różnym zakresie ważności:

- lokalne (dostępne tylko wewnątrz funkcji)

- globalne (dostępne w całym programie)

Podział ten w rzeczywistości jest nieco bardziej rozgałęziony, ale zatrzymajmy się na razie na przedstawionym.

Zobaczmy przykład:


#include <iostream.h>

int liczba = 28; // definicja zmiennej globalnej

void funkcja();

main()

{

cout << "Liczba = " << liczba << endl;

funkcja();

cout << "Liczba = " << liczba;

}

void funkcja()

{

int liczba = 25; // definicja zmiennej lokalnej

cout << "Liczba = " << liczba << endl;

}


Po wykonaniu programu na ekranie zobaczymy coś takiego:

Liczba = 28

Liczba = 25

Liczba = 28


Jak więc widzimy, zmienna lokalna zasłoniła zmienną globalną i to do niej została wpisana wartość 25.


Dygresja na temat zmiennych: zmienne możemy definiować naprawdę w dowolnym miejscu w programie (byle tylko przed pierwszym zastosowaniem w programie - nie możemy korzystać z czegoś, co nie zostało jeszcze stworzone). Jednak aby zmienna była widziana we wszystkich funkcjach, należy ją zdefiniować jeszcze przed rozpoczęciem funkcji main().


Wróćmy jeszcze na chwilę do argumentów funkcji. Przeanalizujmy przykład:


#include <iostream.h>

void funkcja(int liczba)

{

liczba = 28;

cout << liczba;

}

main()

{

int liczba = 25;

funkcja(liczba);

cout << liczba;

}


Mimo że funkcja zadziałała, na ekranie nie pojawiła się 2 razy liczba 28, ale 28 i 25. Jaki stąd wniosek? Otóż każda funkcja tworzy kopie argumentów - jeżeli odwołujemy się do jakiegoś argumentu, to w rzeczywistości odnosimy się do jego kopii - "pierwotna" zmienna pozostaje bez zmian.


Możemy zmieniać jednak pobierane argumenty. Istnieje na to kilka różnych metod, my skorzystamy z referencji. Do funkcji będzie przekazywany jedynie adres zmiennej w pamięci, a nie jej wartość, dzięki czemu wszystkie zmiany będą dokonywane bezpośrednio na zmiennej "macierzystej". Aby skorzystać z referencji, należy użyć znaku &, np. char &zmienna.

Przykładowo:


#include <iostream.h>

void funkcja(int &zmienna1, int zmienna2) // pobranie zmiennej przez referencje

{

zmienna2 = 25;

zmienna1 = 25; // przypisanie 25 do zmiennej GLOBALNEJ

}

main()

{

int liczba1 = 28, liczba2 = 28;

cout << "Liczba1 przed zmiana: " << liczba1 << endl;

cout << "Liczba2 przed zmiana: " << liczba2 << endl;

funkcja(liczba1, liczba2);

cout << "Liczba1 po zmianie: " << liczba1 << endl;

cout << "Liczba2 po zmianie: " << liczba2;

}


Napiszmy program podający drogę w ruchu jednostajnie prostoliniowym (S=vt) lub jednostajnie zmiennym (S=1/2 at2 + v0t):


#include <iostream.h>

float ruch_zmienny()

{

float czas, przyspieszenie, predkosc_p;

cout << "Podaj czas (s): ";

cin >> czas;

cout << "Podaj przyspieszenie (m/s*s): ";

cin >> przyspieszenie;

cout << "Podaj predkosc poczatkowa (m/s): ";

cin >> predkosc_p;

return (przyspieszenie*czas*czas)/2 + predkosc_p*czas;

}

float ruch_prostoliniowy()

{

float predkosc, czas;

cout << "Podaj predkosc (m/s): ";

cin >> predkosc;

cout << "Podaj czas (s): ";

cin >> czas;

return predkosc*czas;

}

main()

{

int decyzja;

char ponownie;

do

{

cout << endl << "Droga w ruchu prostoliniowym - 0, w ruchu zmiennym - 1." << endl << "Wybierz: ";

cin >> decyzja;

if (decyzja)

cout << << "Droga wynosi: " << ruch_zmienny() << endl << endl;

else cout << endl << "Droga wynosi: " << ruch_prostoliniowy() << endl << endl;

cout << "Jeszcze raz (t/n)? ";

cin >> ponownie;

} while (ponownie != 'n');

}


W C++ istnieje coś takiego, jak argumenty domniemane. Zdarza się czasem, że wywołujemy daną funkcję i jeden (albo i więcej) argument prawie zawsze jest taki sam. Można mu więc przypisać domyślną wartość, aby przy odwoływaniu się do funkcji nie trzeba było podawać wszystkich wartości. Możemy na przykład napisać:


int suma(int a = 2, int b)

{

return a + b;

}


Funkcję taką możemy wywołać w dwojaki sposób - podając 1 lub 2 argumenty. Jeśli podamy tylko jeden - drugi będzie domyślny. Przykłady wywołania:

suma(25); // a jest domniemane i wynosi 2

suma(28, 25); // a wynosi 28


Wyobraźmy sobie teraz, że piszemy program wspólnie z kolegą. Dzielicie się pracą, każdy pisze własne funkcje. Jak je jednak połączyć? Można oczywiście skopiować i wkleić kod, ale po co? Istnieje przecież możliwość dołączania funkcji w osobnych plikach. Służy do tego nic innego, jak dobrze już nam znana dyrektywa #include. Piszemy: #include "nazwa_pliku", wskazując na plik, w którym zawarta jest definicja funkcji.

Przykład:


Plik definicja.h:

int suma(int a, b)

{

return a + b;

}


Plik programu - suma.cpp:

#include <iostream.h>

#include "definicja.h"

main()

{

cout << suma(25, 28);

}


Zwróćmy na koniec uwagę na estetykę dołączania plików. Ogólnie przyjętym standardem jest wykorzystywanie cudzysłowia " " przy dołączaniu własnych plików i nawiasów < > przy dołączaniu standardowych plików dostarczonych wraz z kompilatorem. Starajmy się nie odbiegać zbyt często od tych norm.


Do zapamiętania z rozdziału:

- funkcje pozwalają podzielić program na mniejsze części

- funkcje mogą zwracać wartość i pobierać argumenty

- funkcję w programie wywołujemy przez podanie jej nazwy i argumentów

- typ void oznacza brak zwracanej wartości

- aby odnieść się w funkcji bezpośrednio do zmiennej (a nie do jej wartości) możemy zastosować referencję (np. &zmienna = 25)

- argumenty domniemane pozwalają na uruchamianie funkcji bez podawania wszystkich parametrów

- definicje funkcji możemy dołączać w plikach nagłówkowych przy pomocy dyrektywy #include


Zadanie:

Zmodyfikuj program do obliczania drogi w ruchach jednostajnych, który napisaliśmy na tej lekcji. Program ma mieć możliwość pobierania danych w różnych jednostkach (np. km/h, min).


W następnej części kursu powiemy sobie o przeładowaniach nazw funkcji, nowych typach danych oraz


Wyszukiwarka