Jump to content
Hostul a fost schimbat. Daca vedeti serverul offline readaugati rpg.b-zone.ro sau 141.95.124.78:7777 in clientul de sa-mp ×

[PASCAL] Lectia 5 - Subprograme (proceduri si functii)


Leventhe
 Share

Recommended Posts

Tutorial Pascal

 

 

 

 

 

 

 

Subprograme (proceduri si functii)

 

 

 

 

 

Vi s-a intamplat sa aveti nevoie sa faceti un program si sa fie nevoie sa repetati o secventa de cod in mai multe locuri din program ? Probabil ca da, posibil ca nu.

 

 

Vi s-a intamplat sa aveti nevoie sa va ordonati codul in alt fel decat insiruirea liniilor una dupa alta, sa zicem ... dupa functionalitate ?

 

 

 

 

 

Pentru a usura aceste lucruri, Pascal ofera subprogramele: proceduri si functii. Pe scurt, acestea sunt parti de program care pot fi folosite apeland numele lor.

 

 

 

 

 

Proceduri

 

 

 

 

 

Sa vedem cum arata o structura generala a unei proceduri:

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

procedure nume_procedura [(lista_parametri)];

 

 

 

 

 

CONST

 

 

(* declaratii de constante *)

 

 

 

 

 

VAR

 

 

(* declaratii de variabile *)

 

 

 

 

 

BEGIN

 

 

(* instructiuni *)

 

 

END;

 

 

 

 

 

 

 

 

De remarcat ca, spre deosebire de structura unui program, end-ul de la sfarsit este finalizat cu ; (punct si virgula), nu cu . (punct). Lista de parametri este optionala, motiv pentru care este incadrata intre paranteze drepte. Ce inseamna parametri va fi explicat putin mai tarziu.

 

 

 

 

 

Sa vedem un program obisnuit folosind proceduri. Programul original (fara proceduri) ar fi urmatorul:

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var vector : array[1..200] of integer;

 

 

i, n: integer;

 

 

begin

 

 

Write('Dati N: '); ReadLn(n); {N = numarul de elemente}

 

 

 

 

 

i := 1; {*}

 

 

while (i <= N) do {citim elementele}

 

 

begin

 

 

Write('Dati elementul ', i, ': ');

 

 

ReadLn(vector);

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

i := 1; {*}

 

 

while (i <= N) do {impartim fiecare element la 2 si salvam catul}

 

 

begin

 

 

vector := vector div 2;

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

Write('Vectorul modificat este : ');

 

 

 

 

 

i := 1; {*}

 

 

while (i <= N) do {afisam noile valori din vector}

 

 

begin

 

 

Write(vector : 5);

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

Vedeti secventele marcate cu {*} ? Aceeasi instructiune, repetata de 3 ori... Bineinteles, intr-un program mai mare si mai stufos, secventele care se repeta contin mai multe instructiuni ... si probabil se repeta de mai multe ori.

 

 

 

 

 

Sa vedem cum rescriem programul folosind o procedura care sa faca acelasi lucru (sa initializeze variabila i cu 1).

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var vector : array[1..200] of integer;

 

 

i, n: integer;

 

 

 

 

 

procedure Init;

 

 

begin

 

 

i := 1;

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n); {N = numarul de elemente}

 

 

 

 

 

Init; {*}

 

 

while (i <= N) do {citim elementele}

 

 

begin

 

 

Write('Dati elementul ', i, ': ');

 

 

ReadLn(vector);

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

Init; {*}

 

 

while (i <= N) do {impartim fiecare element la 2 si salvam catul}

 

 

begin

 

 

vector := vector div 2;

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

Write('Vectorul modificat este : ');

 

 

 

 

 

Init; {*}

 

 

while (i <= N) do {afisam noile valori din vector}

 

 

begin

 

 

Write(vector : 5);

 

 

i := i + 1;

 

 

end;

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

Probabil ca acum va ganditi "Ce mare branza a rezolvat ? Numarul de linii este acum mai mare !". Da, asa e, dar asa cum am mentionat, acest program este doar un exemplu simplist.

 

 

 

 

 

In acest caz, variabila i este o variabila globala. Nu e declarata in niciun subprogram, ci direct in programul principal. Procedura Init o poate folosi fiindca variabila a fost declarata inaintea procedurii (daca erau declarate invers, compilatorul ar fi dat eroare spunand ca variabila folosita in procedura nu poate fi gasita).

 

 

 

 

 

Variabilele declarate in interiorul unui subprogram se numesc variabile locale.

 

 

 

 

 

Atentie: Variabilele globale si locale pot avea acelasi nume, lucru care poate duce la probleme in program.

 

 

 

 

 

Sa vedem un alt program care foloseste o procedura pentru a calcula factorialul unui numar (pentru cei care nu stiu, factorialul unui numar N este produsul lui 1 x 2 x ... x N).

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var i, n, f: integer;

 

 

 

 

 

procedure Fact;

 

 

begin

 

 

f := 1;

 

 

 

 

 

for i := 1 to n do

 

 

f := f * i;

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n);

 

 

 

 

 

Fact;

 

 

Write('Factorial de N este egal cu : ', f);

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

Destul de clar, cred. Sa trecem la ...

 

 

 

 

 

Functii

 

 

 

 

 

Functiile sunt foarte asemanatoare procedurilor, singura diferenta fiind faptul ca functiile pot intoarce un rezultat.

 

 

 

 

 

Sa vedem cum arata o structura generala a unei functii:

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

function nume_functie [(lista_parametri)] : tip_rezultat;

 

 

 

 

 

CONST

 

 

(* declaratii de constante *)

 

 

 

 

 

VAR

 

 

(* declaratii de variabile *)

 

 

 

 

 

BEGIN

 

 

(* instructiuni *)

 

 

END;

 

 

 

 

 

 

 

 

Haideti sa vedem cum ar putea arata un program care calculeaza factorialul unui numar, folosind o functie de data asta:

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var i, n, f: integer;

 

 

 

 

 

function Fact: Longint;

 

 

var lFact: Longint; {variabila locala}

 

 

begin

 

 

lFact := 1;

 

 

 

 

 

for i := 1 to n do

 

 

lFact := lFact * i;

 

 

 

 

 

Fact := lFact; {am asignat rezultatul in numele functiei !}

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n);

 

 

 

 

 

F := Fact;

 

 

Write('Factorial de N este egal cu : ', f);

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

De data asta am asignat variabila F din afara subprogramului, si i-am dat valoarea rezultata din functie. Rezultatul functiei a fost setat asignand valoarea respectiva numelui functiei, in interiorul functiei.

 

 

 

 

 

Alta modificare este faptul ca folosim o variabila locala pentru a calcula factorialul. Acest lucru nu este strict necesar, l-am facut doar pentru a exemplifica rolul variabilelor locale.

 

 

 

 

 

Parametri

 

 

 

 

 

Sa vedem ce sunt parametrii si cum ii folosim in subprograme.

 

 

 

 

 

Lista de parametri este scrisa, practic, ca o lista de variabile. Parametrii pot fi scrisi in orice ordine sau pot fi grupati dupa tipul variabilelor.

 

 

 

 

 

Parametrii pot fi de doua tipuri, prin modul in care sunt folositi:

 

 

parametri prin valoare - acesti parametri pot fi modificati in interiorul subprogramului, dar modificarile nu sunt vizibile in exteriorul subprogramului.

 

 

parametri prin adresa - acesti parametri sunt, de fapt, adresele de memorie ale unor variabile deja declarate. Modificarile aduse unor astfel de parametri in interiorul unui subprogram vor fi vizibile in exterior.

 

 

 

 

 

Teoria ca teoria, dar hai sa vedem niste exemple

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var i, n, f: integer;

 

 

 

 

 

function Fact(X: Integer): Longint; {X este un parametru transmis prin valoare}

 

 

var lFact: Longint; {variabila locala}

 

 

begin

 

 

lFact := 1;

 

 

 

 

 

for i := 1 to X do

 

 

lFact := lFact * i;

 

 

 

 

 

X := -1;

 

 

Fact := lFact; {am asignat rezultatul in numele functiei !}

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n);

 

 

 

 

 

F := Fact(N);

 

 

WriteLn('Factorial de N este egal cu : ', f);

 

 

WriteLn('Noua valoare a lui N este : ', n);

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

Bun. Am calculat din nou factorialul, dar, de data aceasta, functia noastra Fact a primit un parametru prin valoare, pe care-l foloseste sa calculeze factorialul. Pe scurt, o data intrati in corpul functiei (care este apelata ca Fact(N), X este egal cu N. Ce vreau sa remarcati este faptul ca, desi am dat o noua valoare lui X in interiorul functiei, aceasta nu este transmisa inapoi lui N, dovada fiind faptul ca noua valoare a lui N (care este afisata) este aceeasi cu cea introdusa de la tastatura.

 

 

 

 

 

Din punctul de vedere al functiei si al metodei de lucru, admitand ca valoarea lui N era egala cu 5, puteam apela Fact(5). Numele generic al parametrului ("prin valoare") este dat tocmai din acest motiv: indiferent ca functia este apelata cu o constanta sau o variabila, doar valoarea ei este transmisa.

 

 

 

 

 

Sa vedem acelasi cod, singura diferenta fiind ca acum folosim un parametru prin adresa.

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var i, n, f: integer;

 

 

 

 

 

function Fact(var X: Integer): Longint; {X este un parametru transmis prin adresa}

 

 

var lFact: Longint; {variabila locala}

 

 

begin

 

 

lFact := 1;

 

 

 

 

 

for i := 1 to X do

 

 

lFact := lFact * i;

 

 

 

 

 

X := -1;

 

 

Fact := lFact; {am asignat rezultatul in numele functiei !}

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n);

 

 

 

 

 

F := Fact(N);

 

 

WriteLn('Factorial de N este egal cu : ', f);

 

 

WriteLn('Noua valoare a lui N este : ', n);

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

Faptul ca parametrul este transmis prin adresa este simbolizat de cuvantul var care-l precede. Programul afiseaza noua valoare a lui N, asa cum era de asteptat, egala cu -1, dovada ca variabila N a fost modificata in functie.

 

 

 

 

 

Diferenta principala intre parametrii transmisi prin valoare si cei transmisi prin adresa este faptul ca, in cazul celor transmisi prin valoare, variabila trimisa ca parametru este copiata intr-o zona de memorie temporara si trimisa functiei, ca si copie, in timp ce in cazul celor transmisi prin adresa variabila trimisa ajunge in functie neschimbata, ca original. Din acest motiv, transmiterea parametrilor prin adresa poate rapidiza executia programului (totusi, sa nu va asteptati la minuni - vorbim de milisecunde si chiar mai putin).

 

 

 

 

 

Atentie: Un subprogram poate avea o lista de parametri micsti: o parte dintre ei sa fie trimisi prin valoare, iar altii prin adresa. Tot ce conteaza este modul de declarare al listei.

 

 

 

 

 

De remarcat ca parametrii listati in headerul subprogramului se numesc parametri formali (X este un parametru formal in programul de mai sus), care sunt inlocuiti de parametri efectivi, in momentul apelului subprogramului (N este un parametru efectiv in programul de mai sus).

 

 

 

 

 

Atentie: Numarul, tipul si ordinea parametrilor efectivi trebuie sa corespunda cu numarul, tipul si ordinea parametrilor formali !

 

 

 

 

 

Vizibilitatea variabilelor

 

 

 

 

 

Dupa cum am explicat mai sus, se pot declara variabile in interiorul subprogramelor, chiar si cu nume de variabile care exista deja definite in programul principal. Problema care se ridica este urmatoarea : de unde stie compilatorul (si implicit, programatorul) care variabila va fi folosita in anumite zone ale programului ?

 

 

 

 

 

Nu trebuie sa va speriati, este foarte simplu si usor de inteles.

 

 

 

 

 

Variabilele globale ale programului sunt vizibile oriunde in program. Daca un subprogram isi defineste o variabila cu acelasi nume, atunci variabila locala e prioritara in acel subprogram (si in posibilele subprograme ale subprogramului !).

 

 

 

 

 

Sa vedem un exemplu.

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

var i, n, f, lFact: integer;

 

 

 

 

 

function Fact(X: Integer): Longint; {X este un parametru, transmis prin valoare}

 

 

var lFact: Longint; {variabila locala}

 

 

begin

 

 

lFact := 1;

 

 

WriteLn('lFact local = ', lFact);

 

 

 

 

 

for i := 1 to X do

 

 

lFact := lFact * i;

 

 

 

 

 

X := -1;

 

 

Fact := lFact; {am asignat rezultatul in numele functiei !}

 

 

end;

 

 

 

 

 

begin

 

 

Write('Dati N: '); ReadLn(n);

 

 

 

 

 

lFact := 157;

 

 

WriteLn('lFact global = ', lFact);

 

 

 

 

 

F := Fact(N);

 

 

WriteLn('Factorial de N este egal cu : ', f);

 

 

 

 

 

ReadLn; {asteptam sa fie apasat Enter}

 

 

end.

 

 

 

 

 

 

 

 

Acest program va afisa urmatoarele linii (pentru N egal cu 5):

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

Dati N: 5

 

 

lFact global = 157

 

 

lFact local = 1

 

 

Factorial de N este egal cu : 120

 

 

 

 

 

 

 

 

Recursivitate

 

 

 

 

 

Un lucru important de stiut, legat de subprograme, este ca ele sunt necesare daca vrem sa folosim recursivitate.

 

 

 

 

 

Un subprogram este recursiv daca in implementarea lui se apeleaza pe el insusi.

 

 

 

 

 

Recursivitatea poate fi un concept destul de greu de inteles, asa ca vom incepe cu un exemplu. Sa vedem cum rescriem functia care calculeaza factorialul, folosind recursivitatea.

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

function Fact(X: Integer): Longint;

 

 

begin

 

 

if X > 1

 

 

then Fact := Fact(X - 1) * X

 

 

else Fact := 1

 

 

end;

 

 

 

 

 

 

 

 

Dupa se poate vedea, recursivitatea permite si formularea mai eleganta a solutiei.

 

 

 

 

 

Sa incercam sa intelegem ce se intampla. Factorial de N (notat si N!) este definit ca o inmultire succesiva a numerelor de la 1 la N (1 x 2 x ... x N). Din asta putem vedea ca N! este egal cu (N-1)! x N. La randul lui, (N-1)! = (N-2)! x (N-1) si asta poate continua, pana cand N este egal cu 1. Daca incercam sa calculam 1! folosind aceeasi relatie (adica 1! = 0! x 1) o sa cam dam gres, fiindca 0, inmultit cu orice numar, da 0.

 

 

 

 

 

Pe scurt, daca avem un caz initial caruia ii stim rezultatul, iar restul cazurilor se pot rezolva in functie de cazul initial, problema poate fi rezolvata recursiv. Cazul initial, pe functia de mai sus, este cazul in care X nu este mai mare decat 1 (adica este mai mic sau egal !), acest caz avand rezultatul 1. Pentru orice alta valoare pozitiva a lui X, rezolvarea se face in functie de X-1, pana cand X ajunge in cazul initial.

 

 

 

 

 

Ca urmare, aceste doua relatii de mai sus:

 

 

N! = (N-1)! x N

 

 

1! = 1

 

 

sunt tot ce ne trebuie pentru a ne defini functia recursiva de calculare a factorialului.

 

 

 

 

 

Sa vedem ce se intampla, pas cu pas, pentru N egal cu 3.

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

Fact(3)

 

 

X = 3 -> apelam Fact(2) si inmultim cu 3

 

 

X = 2 -> apelam Fact(1) si inmultim cu 2

 

 

X = 1 -> 1, se termina functia, revenim in apelul precedent

 

 

Fact = 1, care inmultit cu 2 este egal cu 2, revenim in apelul precedent

 

 

Fact = 2, care inmultit cu 3 este egal cu 6, se termina functia si revenim in locul unde a fost initial apelata functia.

 

 

 

 

 

 

 

 

Deci, daca avem o problema care poate fi exprimata prin subprobleme ale ei si putem gasi o conditie de terminare a apelurilor recursive (in acest caz, X = 1), problema poate fi rezolvata printr-un subprogram recursiv.

 

 

 

 

 

Atentie: Apelurile recursive consuma relativ multa memorie. Daca o problema poate fi rezolvata iterativ (fara apeluri recursive), e de preferat (in general) ca problema sa fie rezolvata iterativ. O lista prea lunga de apeluri recursive poate genera "stack overflow" si bloca programul / calculatorul.

 

 

 

 

 

Pentru edificare, atasez acelasi program care calculeaza factorialul, modificat pentru a afisa ce se intampla. Sper sa va fie de ajutor, impreuna cu explicatiile de mai sus.

 

 

 

 

 

Codul sursa al programului:

 

 

LINE NUMBER ON/OFF | EXPAND/CONTRACT | SELECT ALL

 

 

uses crt;

 

 

 

 

 

const lin = 9;

 

 

line = '-----------------------------------------------------------------';

 

 

Wait = 1000;

 

 

 

 

 

var i, n, f, lFact, test: longint;

 

 

 

 

 

function Fact(X: Integer): Longint; {X este un parametru, transmis prin valoare}

 

 

begin

 

 

if X > 1

 

 

then

 

 

begin

 

 

WriteLn('Fact(', X, ') = Fact(', X - 1 ,') * ', X);

 

 

WriteLn(line);

 

 

Delay(Wait);

 

 

 

 

 

Fact := Fact(X - 1) * X;

 

 

Delay(Wait);

 

 

 

 

 

TextColor(LightGreen);

 

 

if Test > 0 then

 

 

Test := Test * X;

 

 

GotoXY(40, (N - X) * 2 + lin);

 

 

Write('Fact(', X,') = ', Test);

 

 

end

 

 

else

 

 

begin

 

 

TextColor(LightRed);

 

 

WriteLn('X = 1 ' +

 

 

'Fact(1) = 1, caz initial !');

 

 

TextColor(LightGray);

 

 

WriteLn(line);

 

 

Delay(Wait);

 

 

WriteLn('Functia s-a terminat, revenim din apel ^^^');

 

 

Delay(Wait);

 

 

Fact := 1;

 

 

Test := 1;

 

 

end;

 

 

end;

 

 

 

 

 

begin

 

 

test := 0;

 

 

repeat {verificam daca 1 <= N <= 6}

 

 

ClrScr;

 

 

Write('Dati N (maxim 6): '); ReadLn(n);

 

 

until (N > 0) and (N < 7);

 

 

ClrScr;

 

 

 

 

 

TextColor(LightRed);

 

 

GotoXY(Length(line) div 2 - 16 ,2);

 

 

WriteLn('Calculul factorialului - recursiv');

 

 

WriteLn(line);

 

 

WriteLn;

 

 

TextColor(LightGray);

 

 

WriteLn('Se apeleaza Fact(', N,') ...');

 

 

Delay(Wait);

 

 

 

 

 

TextColor(lightGray);

 

 

GotoXY(1, lin);

 

 

F := Fact(N);

 

 

 

 

 

Delay(Wait);

 

 

GotoXY(1, N * 2 + lin + 3);

 

 

TextColor(LightGray);

 

 

 

 

 

Write('Fact(', N, ') este egal cu : ');

 

 

TextColor(LightGreen);

 

 

WriteLn(f);

 

 

TextColor(LightGray);

 

 

Write('... apasa orice tasta pentru a termina programul ...');

 

 

 

 

 

While Keypressed Do

 

 

Readkey; {daca s-a apasat o tasta in timp ce programul rula,

 

 

va astepta totusi apasarea urmatoarei taste}

 

 

ReadKey;

 

 

end.

 

 

 

 

 

Surse si executabil - Download

 

 

 

 

 

Screenshot din timpul rularii programului:

 

 

fact_rec.png

 

 

 

 

 

 

 

 

 

Edited by Cdorsu
Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.