[Pascal] Subprograme

User avatar
Addicted.
Posts: 6853
Contact:

[Pascal] Subprograme

Post#1 » July 10th, 2014, 4:30 pm

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:


Code: 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:


Code: 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 := i + 1;
    end;
 
  i := 1; {*}
  while (i <= N) do {impartim fiecare element la 2 si salvam catul}
    begin
      vector[i] := vector[i] div 2;
      i := i + 1;
    end;
 
  Write('Vectorul modificat este : ');
 
  i := 1; {*}
  while (i <= N) do {afisam noile valori din vector}
    begin
      Write(vector[i] : 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).


Code: 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 := i + 1;
    end;
 
  Init; {*}
  while (i <= N) do {impartim fiecare element la 2 si salvam catul}
    begin
      vector[i] := vector[i] div 2;
      i := i + 1;
    end;
 
  Write('Vectorul modificat este : ');
 
  Init; {*}
  while (i <= N) do {afisam noile valori din vector}
    begin
      Write(vector[i] : 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).


Code: 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:


Code: 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:


Code: 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 :)


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


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


Code: 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):


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


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


Code: 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:


Code: 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]http://www.bitcell.info/resources/programe/DarkByte/tut/fact_rec.zip[/download]

Screenshot din timpul rularii programului:


Image
Daca vrei o viata perfecta pe PRONION.RO, Staff-ul te roaga sa respecti ACEST REGULAMENT - Click !

Image

Return to “Pascal / Turbo Pascal”

Who is online

Users browsing this forum: No registered users and 4 guests

Pronion.Ro : Disclaimer