Funkcje, procedury i moduły
Ogólne przeznaczenie podprogramów
Tworząc programy często spotykamy się z sytuacją, kiedy niezbędne jest powtórzenie tego samego algorytmu w wielu miejscach programu. Możliwe jest powielenie kodu, ale niesie to za sobą wiele niekorzyści. Po pierwsze, tak napisany program zawiera wiele linii kodu i staje się mało czytelny, a tym samym trudny do interpretacji i korekty. Po drugie, jeżeli w kopiowanym fragmencie pojawi się błąd, konieczne będzie jego poprawienie w kilku miejscach programu. Aby uniknąć tego typu problemów korzystne jest wydzielenie powielanego fragmentu w postaci podprogramu (w postaci funkcji lub procedury).
Stosowanie podprogramów ma wiele zalet, min.:
zwiększa czytelność kodu programu
umożliwia dzielenie programu na części (strukturalizacja programu)
zmniejsza objętość programu dzięki wielokrotnemu wywołaniu podprogramu
zmniejsza zajmowany przez program obszar pamięci, dzięki wykorzystaniu zmiennych lokalnych, dla których pamięć przydzielana jest tylko na czas wykonania podprogramu
umożliwia wykorzystanie raz napisanych podprogramów także w innych programach
Definicje funkcji i procedury
|
Funkcja jest podprogramem służącym do obliczania pewnej wartości. Dla zadanych parametrów, funkcja przetwarza je i zwraca wartość, czyli wynik określonego typu |
Definicja funkcji składa się z dwóch części: nagłówka i bloku funkcji. W nagłówku funkcji po słowie kluczowym function podaje się jej nazwę (identyfikator) i opcjonalnie parametry formalne, tj. pomocnicze nazwy parametrów przekazywanych do funkcji.
W nagłówku funkcji określa się także typ wyniku, czyli typ wartości zwracanej przez funkcję. Blok funkcji składa się z części opisowej (deklaracyjnej), w której definiuje i deklaruje się elementy lokalne wewnątrz podprogramu oraz części wykonawczej, czyli ciągu instrukcji, stanowiącego instrukcję złożoną.
function nazwa_funkcji(lista_parametrów_formalnych): typ_wyniku;
część_opisowa
begin
instrukcje
end;
Definicja funkcji
Pisząc funkcję należy pamiętać aby umieścić w niej instrukcję zwracającą wynik.
nazwa_funkcji := wartość;
Definicję funkcji należy umieścić w bloku deklaracji programu. Dobrym zwyczajem programisty jest również umieszczenie opisu, stanowiącego informację o tym co dana funkcja robi. Podobnie jak w programie, opis umieszczamy tuż przed częścią opisową funkcji.
Poniżej przedstawiono przykład definicji funkcji, obliczającej wartość n! metodą iteracyjną.
function silnia(n: Byte): Longint;
{funkcja oblicza silnię liczby naturalnej n}
var i : Byte;
wynik : Longint;
begin
wynik := 1;
for i:=1 to n do
wynik := wynik*i;
silnia := wynik; {instrukcja zwracająca wynik}
end;
Zwróćmy uwagę na zastosowanie dodatkowej zmiennej wynik. Nasuwa się pytanie, czy nie można wykonać obliczeń zapisując:
for i:=1 to n do
silnia := silnia*i;
Otóż silnia nie jest zwykłą zmienną, lecz nazwą funkcji, która z definicji wymaga podania jednego parametru. Powyższy zapis nie jest prawidłowy, bowiem odwołanie do funkcji silnia nie zawiera wymaganego argumentu. W tym miejscu nie mamy możliwości przekazania do funkcji stosownego argumentu, dlatego konieczne jest wykorzystanie dodatkowej zmiennej przechowującej obliczenia. Po zakończeniu obliczeń należy pamiętać o nadaniu wartości funkcji.
|
Procedura jest podprogramem którego zadaniem jest wykonanie sekwencji instrukcji, polegających na obliczeniu kilku wartości lub realizacji jakiejś czynności na podstawie ustalonego algorytmu. |
Definicja procedury, podobnie jak definicja funkcji składa się z dwóch części: nagłówka i bloku procedury. W nagłówku funkcji po słowie kluczowym procedure podaje się jej nazwę (identyfikator) i parametry formalne (opcjonalnie). Blok procedury składa się z części opisowej oraz części wykonawczej.
procedure nazwa_procedury(lista_parametrów_formalnych);
część_opisowa
begin
instrukcje
end;
Definicja procedury
Definicję procedury, tak jak w przypadku funkcji, należy umieścić w bloku deklaracji programu.
Poniższy przykład pokazuje definicję procedury wypisującej na ekranie n gwiazdek w jednym wierszu.
procedure gwiazdki(n: Byte);
{procedura wypisuje na ekranie n gwiazdek}
var i : Byte;
begin
for i:=1 to n do
write(`*');
writeln;
end;
Zauważmy, że w nagłówku procedury nie podajemy typu zwracanej wartości a w części wykonawczej nie ma instrukcji zwracającej wynik, co jest istotnym elementem w przypadku funkcji.
|
Różnica pomiędzy funkcją a procedurą polega na tym, że funkcja zwraca wartość określonego typu, można więc używać jej w wyrażeniach. Procedura nie zwraca wyniku, może służyć do obliczenia kilku wartości lub tylko do wykonania jakiejś czynności, np. czyszczenie ekranu. |
Wywołanie funkcji i procedury
Wywołanie funkcji jest realizowane za pomocą podania nazwy funkcji jako argumentu wyrażenia. W przypadku, gdy funkcja zawiera parametry, to przy wywołaniu po nazwie funkcji podaje się w nawiasach listę argumentów. Liczba i typ argumentów muszą być zgodne z liczbą i typem definiowanych parametrów formalnych.
Zdefiniowaną wcześniej funkcję silnia możemy wywołać w programie za pomocą instrukcji:
wartosc_silnia := silnia(6);
wartość_silnia := silnia(k);
writeln(k,`!= ', silnia(k));
gdzie wartosc_silnia musi być zmienną tego samego typu, co wartość zwracana przez funkcję silnia, zaś zmienna k (tego samego typu co parametr n w definicji funkcji silnia) musi mieć nadaną wartość przed wywołaniem funkcji.
Sposoby przekazywania parametrów do funkcji i procedury zostaną omówione w dalszej części rozdziału.
Na listingu 3.1. przedstawiono program, w którym zdefiniowano funkcję obliczającą największy wspólny dzielnik dwóch liczb całkowitych.
Listing 3.1.
program przykład_3.1;
var a, b : Integer;
function NWD(n, m: Integer): Integer;
{funkcja oblicza największy wspólny dzielnik liczb n i m}
var pom : Integer;
begin
while (m<>0) do
begin
pom := n mod m;
n := m;
m := pom;
end;
NWD := n;
end;
BEGIN
Write(`a='); Readln(a);
Write(`b='); Readln(b);
Writeln(`NWD(' ,a, ',' ,b, `= `, NWD(a,b));
END.
Wywołując procedurę w programie wystarczy podać jej nazwę i listę argumentów. Przykładowo, chcemy na ekranie narysować trójkąt prostokątny złożony z gwiazdek. Wykorzystamy wcześniej zdefiniowaną procedurę gwiazdki.
Listing 3.2.
program przykład_3.2;
var m, i : Integer;
procedure gwiazdki(n: Byte);
{procedura wypisuje na ekranie n gwiazdek}
var i : Byte;
begin
for i:=1 to n do
write(`*');
writeln;
end;
BEGIN
Write(`Podaj bok trójkąta: ');
Readln(m);
If (m<1) then
Writeln(`Trójkąt będzie niewidoczny')
else for i:=1 to m do
gwiazdki(i); {wywołanie procedury}
END.
Zauważmy, że w programie przed wywołaniem procedury gwiazdki wykorzystaliśmy instrukcję warunkową w celu sprawdzenia poprawności danych. Procedura gwiazdki zostanie wywołana m razy, wypisując na ekranie za każdym razem tyle gwiazdek ile wynosi wartość argumentu i. Po wypisaniu odpowiedniej ilości symboli `*' procedura przenosi kursor do następnej linii.
Zmienne lokalne i globalne
Kod podprogramu ma postać instrukcji złożonej, wewnątrz której można wykonywać operacje na parametrach i zmiennych lokalnych. Funkcja powinna działać wyłącznie na parametrach, zaś procedura może także wykonywać operacje na zmiennych globalnych, co jednak nie jest zalecane. Wykorzystywanie w procedurze tylko parametrów i zmiennych lokalnych pozwala na wykorzystanie tej procedury w innych programach, gdyż nie jest ona zależna od programu głównego. Ponadto procedury nie korzystające ze zmiennych globalnych są szybciej uruchamiane i bardziej odporne na błędy.
|
Zmienną globalną nazywamy zmienną zadeklarowaną w części deklaracyjnej głównego programu. Zmiennej globalnej przydzielana jest pamięć w momencie uruchomienia programu i zwalniana po zakończeniu jego działania. Zmienne globalne dostępne są w całym programie, z wyjątkiem bloków, w których zadeklarowano zmienną lokalną o takiej samej nazwie. |
|
Zmienną lokalną nazywamy zmienną zadeklarowaną w części opisowej podprogramu (funkcji lub procedury). Zmienna lokalna ma zasięg tylko w obrębie podprogramu, w którym została zadeklarowana i nie jest widoczna na zewnątrz. Obszar pamięci na zmienne lokalne jest rezerwowany na stosie przydzielonym dla programu na czas wykonania podprogramu i zwalniany przy wyjściu z niego. Wartość zadeklarowanych zmiennych lokalnych jest nieokreślona. |
Z pojęciami zmiennej globalnej i zmiennej lokalnej ściśle związane jest pojęcie przesłaniania zmiennych. Mechanizm ten zilustrowano na poniższym przykładzie.
Listing 3.3.
program przykład_3.3;
var n : Integer;
function Suma(k : Integer): Integer;
{funkcja oblicza sumę liczb całkowitych od 1 do k }
var i, n : Integer;
begin
n := 0;
for i:=1 to k do
n := n + i;
Suma := n;
end;
BEGIN
Write(`Podaj liczbę całkowitą dodatnią, n = ');
Readln(n);
Writeln(`Suma liczb od 1 do ',n,` = `,Suma(n));
Writeln(`Wartość n = ',n);
END.
Zwróćmy uwagę, że w programie zadeklarowano dwie zmienne całkowite o nazwie n: zmienną globalną i zmienną lokalną. Przykładowo, po uruchomieniu programu na ekranie pojawiają się następujące informacje:
Podaj liczbę całkowitą dodatnią, n = 5
Suma liczb od 1 do 5 = 15
Wartość n = ?
Nasuwa się pytanie jaka wartość zostanie wyświetlona na ekranie w miejscu znaku zapytania? Przy nieuważnej analizie można stwierdzić, że n = 15, gdyż w funkcji Suma, n zmienia swoją wartość i jest równe sumie liczb od 1 do 5. Biorąc pod uwagę fakt, że n jest zmienną lokalną w funkcji, jest to nowa zmienna dla której przydzielono oddzielną pamięć na stosie na czas wykonywania funkcji. Oznacza to, że w funkcji zmienna n przyjmuje wartość 15, ale bezpośrednio po wyjściu z funkcji zmienna ta jest usuwana z pamięci i jej wartość nie jest już dla nas dostępna. Na ekranie wyświetlona zostanie wartość zmiennej globalnej:
Wartość n = 5
Podsumowując, jeżeli w podprogramie zadeklarowano zmienną o takiej samej nazwie co zmienna globalna, wtedy wszelkie operacje w podprogramie wykonywane są na zmiennej lokalnej. Wartość zmiennej globalnej pozostaje niezmieniona. Mechanizm taki nazywamy przesłanianiem zmiennych.
|
Zmienne lokalne przesłaniają zmienne globalne o takich samych nazwach, przy czym zmienną globalną jest zmienna zadeklarowana w programie głównym lub w podprogramie nadrzędnym. |
Parametry formalne i aktualne
Przyjrzyjmy się deklaracji zmiennych w poniższym programie:
Listing 3.4.
program przykład_3.4;
var a, b, c : Integer;
wyn : Integer;
function Max(x, y, z: Integer): Integer;
{funkcja jako wynik zwraca największą z trzech liczb}
{całkowitych przekazanych do niej jako parametry}
begin
Max := a;
If (Max<b) then Max := b;
If (Max<c) then Max := c;
end;
BEGIN
Write(`Podaj trzy liczby: ');
Readln(a,b,c);
wyn := Max(a,b,c);
Write(`Największą liczbą spośród liczb: `);
Writeln(a, ',' ,b, ',' ,c, ' jest: ', wyn);
END.
W programie na listingu 3.4. w definicji funkcji Max parametry a, b i c symbolizują dane przekazywane do podprogramu mówiące o tym, że do funkcji należy przekazać trzy argumenty. Poza symboliczną nazwą, mają one określony typ. W wywołaniu funkcji przekazywane są już konkretne wartości reprezentowane przez zmienne globalne.
|
Parametrem formalnym nazywamy identyfikator (nawę) symbolizujący dane przekazywane do podprogramu. |
|
Parametrem aktualnym nazywamy wartość konkretnej zmiennej (stałej lub wyrażenia), na której wykonywane są operacje w podprogramie. |
Zauważmy ponadto w jaki sposób została wywołana funkcja w omawianym programie. Zadeklarowano zmienną wyn do której przypisano wartość zwróconą przez funkcję. Ten sposób jest w wielu przypadkach pożyteczny, ale w programie przykład_3.3 nie było konieczności deklarowania dodatkowej zmiennej, a tym samym przydzielania jej pamięci. Wystarczyło napisać:
Write(`Największą liczbą spośród liczb: `);
Writeln(a, ',' ,b, ',' ,c, ' jest: ', Max(a,b,c));
Pisząc programy należy pamiętać aby nie deklarować niepotrzebnych zmiennych globalnych. W ten sposób zaoszczędzimy pamięć, dzięki czemu nasz program będzie działał szybciej.
Przekazywanie parametrów do podprogramu
W języku Pascal zdefiniowano kilka sposobów przekazywania parametrów do podprogramu. W zależności od sposobu zastępowania parametrów formalnych argumentami, wyróżniamy następujące rodzaje parametrów:
parametry przekazywane przez wartość
parametry przekazywane przez zmienną
parametry przekazywane przez stałą
parametry przekazywane przez funkcję
parametry otwarte
parametry amorficzne
Szczegółowo omówione zostaną najważniejsze sposoby przekazywania parametrów do podprogramu: przez wartość i przez zmienną. Inne metody wykraczają poza poziom tego podręcznika i zostaną tylko ogólnie opisane.
Przekazywanie parametrów przez wartość
Jeżeli parametr jest przekazywany przez wartość, np. parametr k dla funkcji Suma w przykładzie przedstawionym na listingu 3.3. :
function Suma(k : Integer): Integer;
to w momencie wywołania podprogramu odpowiadający mu argument musi być wyrażeniem, którego typ jest zgodny z typem deklarowanego parametru. Przed wywołaniem podprogramu tworzone są kopie parametrów, obliczana jest ich wartość i umieszczane zostają na stosie, tzn. że parametry formalne przekazywane przez wartość są traktowane jak zmienne lokalne wewnątrz podprogramu. Jeżeli wewnątrz podprogramu wykonywane są operacje na tego rodzaju parametrach nie powoduje to zmiany wartości odpowiadających im parametrów aktualnych.
Listing 3.5.
program przykład_3.5;
var n : Integer;
procedure Losowe(n : Integer);
{procedura wypisuje na liczby losowe z zakresu 0..100}
var i : integer;
begin
Randomize; {uruchomienie generatora liczb losowych}
for i:=1 to n do
Writeln(Random(101));
Writeln;
n:=5;
for i:=1 to n do
Writeln(Random(101));
end;
BEGIN
Write(`Podaj ilość liczb losowych, n = ');
Readln(n);
Losowe(n);
Writeln(`Zmienna n = ');
END.
W programie przykład_3.5 zdefiniowano procedurę wyświetlającą n+5 liczb losowych. W nagłówku procedury Losowe zadeklarowano parametr formalny n.
Przykładowo, wynik działania programu może mieć postać:
Podaj ilość liczb losowych, n = 3
0
55
1
0
16
0
23
71
Zmienna n = 3
W momencie wywołania procedury tworzona jest kopia wartości parametru n, dlatego instrukcja:
n:=5;
wewnątrz procedury nie powoduje zmiany wartości zmiennej globalnej n.
|
Liczba parametrów przekazywanych przez wartość nie może przekraczać ośmiu. Jest to związane z ograniczeniami koprocesora arytmetycznego dla parametrów typu Single, Double, Extended, Comp. Przekroczenie dozwolonej liczby parametrów przekazywanych przez wartość nie jest sygnalizowane przez kompilator, dopiero w momencie wykonywania programu wystąpi błąd powodujący przerwanie działania programu. |
Przekazywanie parametrów przez zmienną
Przekazywanie parametrów przez zmienną nazywane jest również przekazywaniem parametrów przez nazwę, referencję lub wskaźnik.
Parametry przekazywane do podprogramu przez zmienną poprzedzone są słowem kluczowym var. Argumenty przekazywane do podprogramu, odpowiadające tym parametrom muszą być zmiennymi. Program nie przekazuje do funkcji lub procedury kopii parametru, lecz umieszcza na stosie wskaźnik do parametru, czyli adres tej zmiennej w pamięci komputera. Informacja o położeniu oryginału skutkuje tym, że modyfikacja parametru wewnątrz podprogramu wpływa na zmianę wartości oryginału.
W przypadku procedur daje nam to dodatkowo możliwość zwracania wyników.
Jako przykład rozważmy procedurę, której zadaniem jest zamiana miejscami wartości dwóch zmiennych. Aby ta zamiana widoczna była na zewnątrz procedury, konieczne jest przekazanie parametrów przez zmienną.
Listing 3.6.
program przykład_3.6;
var a,b : Integer;
procedure Zamien(var x,y : Integer);
{procedura zamienia miejscami wartości parametrów}
var pom : integer;
begin
pom := x;
x := y;
y := pom;
end;
BEGIN
Writeln(`Podaj dwie zmienne:');
Write(`a = '); Readln(a);
Write(`b = '); Readln(b);
Writeln(`Wartość parametrów przed zamianą: ');
Writeln(`a=', a ,' b = `, b);
Zamien(a,b);
Writeln(`Wartość parametrów po zamianie: ');
Writeln(`a=', a ,' b = `, b);
END.
W momencie uruchomienia programu i po podaniu dwóch zmiennych, na ekranie wyświetlone zostaną dwie informacje: wartość parametrów przed i po zamianie. Jak można zauważyć wartości te zostały zamienione miejscami. Gdyby parametry x i y przekazane zostały do procedury przez wartość, wtedy zamiana zostałaby wykonana na zmiennych lokalnych, czyli widoczna byłaby tylko wewnątrz procedury. W momencie zakończenia wykonywania procedury zmienne lokalne byłyby zniszczone i wartości parametrów aktualnych pozostałyby bez zmian.
Inne sposoby przekazywania parametrów
do podprogramu
Jak zostało wspomniane wcześniej, poza przekazywaniem parametrów do podprogramu przez wartość lub przez zmienną istnieją jeszcze inne sposoby komunikowania się funkcji czy procedury z programem głównym.
W przypadku przekazywania parametru przez stałą parametry w nagłówku podprogramu poprzedzone są słowem const. Nie mogą one zmieniać swojej wartości w żadnym miejscu podprogramu.
Przekazując parametr przez funkcję, w nagłówku podprogramu deklarujemy nazwę funkcji jako parametr formalny. W momencie wywołania funkcji lub procedury nazwa parametru funkcyjnego zastępowana jest wewnątrz podprogramu przez wywołanie funkcji, podanej jako parametr aktualny.
Parametry otwarte dotyczą zmiennych tablicowych o nieokreślonym wymiarze. Dopiero przekazując tablicę jako parametr aktualny, możemy odczytać jej wymiar.
Parametry amorficzne nie mają określonego typu. Stosując tego typu parametry musimy pamiętać, aby w treści podprogramu dokonać konwersji parametru na odpowiedni typ.
Ćwiczenia
Ćwiczenie 3.1. Napisz procedurę obliczającą pierwiastki równania kwadratowego i przetestuj jej działanie w programie.
Ćwiczenie 3.2. Napisz funkcję obliczającą wartość
, gdzie podstawa potęgi może być liczbą rzeczywistą, zaś wykładnik potęgi liczbą naturalną.
Ćwiczenie 3.3. Wykorzystując funkcję z ćwiczenia 3.2. napisz program obliczający wartość wyrażenia:
Ćwiczenie 3.4. Napisz program, który pobierze od użytkownika liczbę, a następnie wyświetli kwadrat którego bok złożony będzie z takiej liczby gwiazdek, jaką podał użytkownik, ale z pustymi znakami na przekątnej. Narysuj dwa kwadraty z przekątnymi poprowadzonymi od lewego górnego i od prawego górnego rogu.
Ćwiczenie 3.5. Napisz program, który zamieni 3 podane przez użytkownika liczby na postać binarną.
8
9