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:

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:

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:

  1. 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);

  1. 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:

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

60