9. PODPROGRAMY - FUNKCJE I PROCEDURY
9.1. Konieczność stosowania podprogramów
Podprogram to niewielki program, który zostaje uruchomiony (wywołany) w jakimś miejscu programu głównego po to, aby wykonać określone zadanie cząstkowe.
Po jego wywołaniu podprogram pobiera z programu głównego dane wejściowe, a po wykonaniu swojego zadania przekazuje do programu głównego dane wyjściowe (wyniki).
W Turbo Pascalu mamy dwa rodzaje podprogramów: funkcje i procedury
Są co najmniej cztery powody, dla których stosowanie podprogramów jest niezbędne:
Potrzeba dekompozycji zadania na stosunkowo proste, kolejno wykonywane, zadania cząstkowe wykonywane przez podprogramy.
Lepsze wykorzystanie pamięci operacyjnej. większość zmiennych jest wykorzystywana przez podprogramy jako t. zw. zmienne lokalne, które istnieją tylko w czasie działania podprogramu.
Jeżeli w programie powtarza się wielokrotnie ta sama sekwencja instrukcji, to jednokrotne zapisanie jej w postaci podprogramu znacznie skraca zapis programu źródłowego.
Raz opracowany podprogram może być wykorzystany jako podstawowy element funkcjonalny, do budowy innych programów.
9.2. Procedury i funkcje standardowe
Turbo Pascal oferuje programistom obszerny zbiór gotowych procedur i funkcji standardowych, które mogą być wywoływane za pomocą odpowiednich instrukcji, zwanych instrukcjami wywołania, które dominują w tekście programu. Jedynie nieliczne instrukcje podstawowe: while, repeat, for, break, continue, if, case, nie są instrukcjami wywołania podprogramów.
Procedury i funkcje znajdują się w tak zwanych modułach o nazwach System, Crt, Dos, Windos, Graph, Overlay, Strings. Chcąc użyć procedury lub funkcji, zawartej w danym module, trzeba na początku programu zadeklarować użycie tego modułu, na przykład:
uses Crt,Dos;
Nie deklaruje się jednak podstawowego modułu System, którego zasoby są dołączane do programu domyślnie.
9.3. Wywołania funkcji
Funkcję wywołujemy, podając jej nazwę i po niej, w nawiasach zwykłych, argumenty, oddzielone przecinkami. Wywołania funkcji mogą być umieszczane:
w wyrażeniach - na ogół po prawej stronie instrukcji przypisania, lub w wyrażeniu relacyjnym
jako argument innej funkcji lub procedury.
Przykład
Obliczenie odległości między dwoma punktami na płaszczyźnie XY
program Ex9_1; uses Crt; var D,X1,Y1,X2,Y2:Real; begin Clrscr; Write('Podaj X1,Y1: '); Readln(X1,Y1); Write('Podaj X2,Y2: '); Readln(X2,Y2); D:=Sqrt(Sqr(X1-X2)+Sqr(Y1-Y2)); Write('Wynik:',D:0:6); {Można także jak poniżej: } {Write('Wynik:',Sqrt(Sqr(X1-X2)+Sqr(Y1-Y2)));} Readln; end. |
Często popełniany błąd to wywołaniu funkcji w następujący sposób:
Sin(X);
Przy takim wywołaniu funkcja, po dokonaniu obliczeń i przyjęciu odpowiedniej wartości, nie dałaby żadnych rezultatów, bo jej wartość nie zostaje nigdzie przekazana.
9.4. Wywołania procedur
Dotychczas poznaliśmy już kilka instrukcji wywołania procedur standardowych: Readln, Write, Writeln, Inc z modułu System oraz procedury Clrscr, Gotoxy z modułu Crt.
Ogólną postać instrukcji wywołania procedury:
nazwa_procedury(arg1,arg2, . . . argN);
Argumenty arg mogą mieć postać stałych, zmiennych lub wyrażeń. Przykłady:
Write('X1=',X1,'X2=',X2);
Readln(X,Y); {Tutaj wyłącznie nazwy zmiennych!}
Gotoxy(Wherex+1,Wherey+2);
Writeln(Sin(X));
Inc(J,2); {J musi być nazwą zmiennej!}
Mamy dwa rodzaje argumentów:
Przekazywane przez wartość. Procedura traktuje takie argumenty jako dane wejściowe, kopiuje je do swojego obszaru pamięci i z tej kopii korzysta, wykonując obliczenia. Procedura w toku obliczeń może zmienić wartość kopii, ale sam argument użyty w wywołaniu nie ulega zmianie. Parametry tej grupy mogą w instrukcji wywołania mieć postać stałej jawnej, stałej definiowanej, zmiennej, lub wyrażenia, przy zachowaniu wymaganej kolejności oraz typów kolejnych argumentów.
Przykład: Gotoxy(5,Wherey);
Przekazywane przez zmienną. Procedura operuje bezpośrednio na zmiennej, użytej w instrukcji wywołania, więc w toku obliczeń może zmieniać jej wartość. Dlatego ten rodzaj argumentów służy do przekazywania danych wyjściowych, czyli rezultatów działania procedury. Parametry tej grupy w instrukcji wywołania mogą mieć wyłącznie postać nazw zmiennych.
Przykład:
Readln(X,Y,Z);
Inc(X,3);
{Argument wyjściowy X musi być nazwą zmiennej!}
9.5. Definiowanie funkcji własnych
Oczywiście, funkcje standardowe oferowane w Turbo Pascalu nie spełniają wszystkich możliwych potrzeb programisty. Dlatego przewidziano możliwość definiowania w programie własnych funkcji. Definicje funkcji własnych umieszcza się przed programem głównym. Każda z nich rozpoczyna się odrębnym słowem kluczowym function. Ogólna postać definicji funkcji jest następująca:
function
nazwa_funkcji(arg1:typ1;arg2:typ2; . . .):typ_wyniku;
var
{deklaracje zmiennych lokalnych}
begin
instrukcja_1;
instrukcja_2;
{ - - - }
instrukcja_N;
{przekazanie wyniku:}
nazwa_funkcji:= wynik_obliczeń;
end;
Uwaga: Argumenty funkcji, podobnie jak zmienne lokalne, są tworzone w momencie wywołania funkcji w obszarze pamięci zwanym stosem.
Zmienne lokalne i argumenty istnieją tylko w czasie działania funkcji i są usuwane po zakończeniu jej pracy.
Wewnątrz bloku funkcji należy wprowadzić specjalną instrukcję przypisania, która do nazwy funkcji przypisuje obliczony wynik. Jeżeli pominiemy wspomnianą instrukcję, to obliczony wynik zostanie utracony, a funkcja po jej wywołaniu przekaże jakąś przypadkową wartość.
Przykład
Program z funkcją własną, która oblicza wartość silni swojego argumentu.
program Ex9_2; uses Crt; var Arg:Byte; S:Longint; {definicja funkcji) function Silnia(Arg:Byte):Longint; var I:Byte; S:Longint; begin if Arg>12 then S:=0 else begin S:=1; for I:=1 to Arg do S:=S*I; end; Silnia:=S; {instrukcja przekazania wyniku} end;
{program główny} begin Clrscr; Write('Podaj argument silni <13: '); Readln(Arg); S:=Silnia(Arg); {wywołanie funkcji} if S=0 then Write('Argument za duzy!') else Write('Silnia liczby ',Arg,' wynosi ',S); Readln; end. |
Przykład
Program z funkcją własną, która oblicza średnią arytmetyczną swoich trzech argumentów.
Przed przystąpieniem do zadania dobrze jest wyobrazić sobie funkcję jako „czarną skrzynkę”, z trzema wejściami (argumentami wejściowymi), oraz wyjściem danych; w tym przypadku rolę wyjścia pełni instrukcja, która nazwie funkcji przypisuje uzyskany wynik.
X:Real ——► Y:Real ——► Z:Real ——► |
function Srednia
|
|
│ Srednia:=wynik │ ▼ |
program Ex9_3; uses Crt; var A,B,C,S:Real; {zmienne programu głównego}
{definicja funkcji} function Srednia(X,Y,Z:Integer):Real; var Wynik:Real; begin Wynik:=(X+Y+Z)/3; Srednia:=Wynik; {przekazanie wyniku} end;
{program główny} begin Clrscr; Write('Podaj trzy liczby: '); Readln(A,B,C); S:=Srednia(A,B,C);{wywołanie funkcji} Write('Wynik: ',S); Readln; end.
|
9.6. Definiowanie procedur własnych
Poniżej pokazano ogólną postać definicji procedury.
procedure nazwa(a1:typ1...; var wy1:typ1; . . .);
var {deklaracje zmiennych lokalnych}
begin
instrukcja1;
instrukcja2;
{ - - - }
instrukcjaN;
end;
Procedura może zawsze zastąpić funkcję, a funkcja - procedurę.
Przykład
Program z procedurą równoważną funkcji Srednia.
Wyobrazimy sobie tę procedurę jako „czarną skrzynkę”, pokazując wejścia i wyjścia danych. Są trzy wejścia (argumenty wejściowe) A, B, C, i jedno wyjście (argument wyjściowy var S dla przekazania wyniku.
A: Real ——► B: Real ——► C: Real ——► |
procedure Srednia
|
——► var S: Real |
program Ex9_4; uses Crt; {zmienne programu głównego} var A,B,C,S:Real;
{definicja procedury} procedure Srednia(X,Y,Z:Real; var Sr:Real); begin Sr:=(X+Y+Z)/3; end;
{program główny} begin Clrscr; Write('Podaj trzy liczby: '); Readln(A,B,C); Srednia(A,B,C,S);{wywołanie procedury} Write('Wynik: ',S); Readln; end.
|
Porównując postać funkcji Srednia z postacią procedury o tej samej nazwie, i sposoby wywołania obu tych podprogramów, zauważamy, że:
W nagłówku funkcji brak argumentu wyjściowego; zamiast tego po nawiasie i dwukropku jest typ wyniku, a w ciele funkcji znajduje się instrukcja, przypisująca obliczony wynik do nazwy funkcji.
W nagłówku procedury w nawiasie pojawia się argument wyjściowy S, poprzedzony słowem var. Do niego po obliczeniu średniej jest przypisany wynik.
W ciele definicji procedury nie ma instrukcji przypisującej nazwie wynik obliczeń.
Funkcję wywołuje się w programie głównym po prawej stronie instrukcji przypisania, natomiast wywołanie procedury ma postać samodzielnej instrukcji o takiej nazwie, jak nazwa procedury.
9.7. Przekazywanie danych pomiędzy programem głównym
a podprogramami
a/ Organizacja zasobów pamięci operacyjnej Turbo Pascala
STERTA ZMIENNE DYNAMICZNE rozmiar <= 655 kilobajtów |
STOS ZMIENNE LOKALNE, ARGUMENTY PODPROGRAMÓW Rozmiar domyślny: 16 kilobajtów |
OBSZAR ZMIENNYCH GLOBALNYCH ZMIENNE GLOBALNE PROGRAMU Rozmiar: 64 kilobajty |
OBSZAR KODU WYNIKOWEGO DEKLAROWANE MODUŁY+MODUŁ SYSTEM + SKOMPILOWANY PROGRAM .EXE Rozmiar: co najwyżej 64 K dla każdego z modułów i dla programu |
b/ Mechanizm przekazywania danych wejściowych z programu głównego do podprogramu
Rozważmy prostą procedurę, która znajduje sumę swoich argumentów oraz instrukcję wywołania tej procedury w programie głównym:
procedure Suma(X,Y:Real; var S:Real); begin S:=X+Y; end;
{program główny} var A,B,Wynik:Real;
begin {------} Suma(A,B,Wynik); {------} end.
|
Argumenty wejściowe instrukcji wywołania w programie głównym są samoczynnie kopiowane do argumentów wejściowych procedury lub funkcji:
X:=A; Y:=B;
Dlatego procedura doda te wartości, jakie mają aktualnie zmienne A, B w instrukcji wywołania. |
c/ Mechanizm przekazywania danej wyjściowej z procedury do programu głównego
Słowo var użyte w definicji procedury przed argumentem S powoduje, że
wszelkie wartości nadawane argumentowi S będą jednocześnie nadawane zmiennej Wynik, użytej w instrukcji wywołania.
Mamy tutaj do czynienia z jedną zmienną (jednym obszarem pamięci) o dwóch równoważnych nazwach!
Przykład
Program znajdujący rozwiązania równania kwadratowego, który zawiera trzy procedury własne.
Ap:Real ——► Bp:Real ——► Cp:Real ——► |
procedure Rowkwad
|
——► var X1p: Real ——► var X2p: Real ——► var Kp: Byte |
program Ex9_5; {Znajduje rozwiązania równania kwadratowego.} uses Crt;
procedure Dane(var A,B,C:Real); begin Write('Podaj A,B,C: '); Readln(A,B,C); end;
procedure Rowkwad(Ap,Bp,Cp:Real; var X1p,X2p:Real; var Kp:Byte); var Delta:Real; begin Delta:=Bp*Bp-4*Ap*Cp; if Delta<0 then Kp:=1 else begin Kp:=0; X1p:=(-Bp+Sqrt(Delta))/(2*Ap); X2p:=(-Bp-Sqrt(Delta))/(2*Ap); end; end;
procedure Wyniki(X1,X2:Real; K:Byte); begin if K=0 then begin Writeln('X1=',X1); Writeln('X2=',X2); end else Writeln('Brak pierwiastkow.'); end;
{program główny} var A,B,C,X1,X2:Real; K:Byte; begin Clrscr; Dane(A,B,C); Rowkwad(A,B,C,X1,X2,K); Wyniki(X1,X2,K); Readln; end.
|
9.9. Funkcja jako podprogram uniwersalny
Funkcja, podobnie jak procedura, może zwracać wyniki obliczeń, posługując się argumentami wyjściowymi (poprzedzonymi słowem var) Na przykład funkcja własna o nagłówku:
function Rowkwad(A,B,C:Real;var X1,X2:Real):Byte;
wywołana, jak poniżej:
K:=Rowkwad(A,B,C,X1,X2);
zwróci trzy wartości - dwie wartości typu Real przez argumenty X1, X2 oraz jedną wartość typu Byte, przypisaną do zmiennej K.
Przykład
Program z poprzedniego przykładu, w którym procedury zastąpiono funkcjami
program Ex9_6; {Znajduje rozwiązania równania kwadratowego.} uses Crt;
function Dane(var A,B,C:Real):Byte; begin Write('Podaj A,B,C: '); Readln(A,B,C); Dane:=0; end; function Rowkwad(Ap,Bp,Cp:Real; var X1p,X2p:Real):Byte; var Delta:Real; begin Delta:=Bp*Bp-4*Ap*Cp; if Delta<0 then Rowkwad:=1 else begin Rowkwad:=0; X1p:=(-Bp+Sqrt(Delta))/(2*Ap); X2p:=(-Bp-Sqrt(Delta))/(2*Ap); end; end; |
function Wyniki(X1,X2:Real;K:Byte):Byte; begin if K=0 then begin Writeln('X1=',X1); Writeln('X2=',X2); end else Writeln('Brak pierwiastkow.'); Wyniki:=0; end; var A,B,C,X1,X2:Real; K:Byte; begin Clrscr; Dane(A,B,C); K:=Rowkwad(A,B,C,X1,X2); Wyniki(X1,X2,K); Readln; end. |
9.10. Unikanie błędów przy definiowaniu i wywoływaniu
podprogramów
W ciele funkcji lub procedury nie wolno używać zmiennych globalnych. Wszystkie użyte tam zmienne muszą być deklarowane jako zmienne lokalne lub argumenty.
Nie wolno wpisywać zmiennych lokalnych do listy argumentów procedury lub funkcji. Argumenty reprezentują wyłącznie dane we/wy. Wszelkie inne zmienne należy definiować jako lokalne.
W podprogramach służących do obliczeń nie należy umieszczać instrukcji czytania danych z klawiatury. Dane trzeba przekazywać przez argumenty wejściowe.
W podprogramie wykonującym obliczenia nie należy wyprowadzać na ekran ich wyników. Rezultaty obliczeń należy przekazać przez argumenty wyjściowe, a druk wyników powierzyć odrębnej procedurze.
60