SDA (lab 1-2)

41
Ministerul Educației al Republicii Moldova Universitatea Tehnică a Moldovei Facultatea Calculatoare, Informatică și Microelectronică Raport La Programarea Calculatoarelor LUCRAREA DE LABORATOR Nr. 1/2 Tema: “Analiza eficienţei prelucrării structurilor de date cu pointeri ” A efectuat studentul grupei C-141: Tutunaru Dan A verificat: Ștefan Marin

description

aaa

Transcript of SDA (lab 1-2)

Page 1: SDA (lab 1-2)

Ministerul Educației al Republicii Moldova

Universitatea Tehnică a Moldovei

Facultatea Calculatoare, Informatică și Microelectronică

RaportLa Programarea Calculatoarelor

LUCRAREA DE LABORATOR Nr. 1/2

Tema: “Analiza eficienţei prelucrării structurilor de date cu pointeri ”

A efectuat studentul grupei C-141:

Tutunaru Dan

A verificat:

Ștefan Marin

Chișinău, 2015

Page 2: SDA (lab 1-2)

Sarcina şi obiectivele:

De studiat şi însuşit materialul teoretic din lucrarea dată prin lansarea exerciţiilor la execuţie pentru analiza şi evidenţierea esenţialului prelucrării structurilor de date cu pointeri în elaborarea modelelor soluţiei prin organigrame.

Să se recapituleze materialul teoretic din lucrările de lab. 5-7 (PC) din semestrul I şi să se analizeze algoritmii şi programele cu şi fără pointeri (declarări şi parcurgeri). Pentru aprofundarea înţelegerii să se dezvolte algoritmii şi programele cu pointeri pentru condiţiile problemelor din Anexe şi să se elaboreze scenariile succinte de soluţionare prin pointeri cu calculele de verificare şi explicaţii. Rularea programelor în limbajul C cu afişarea tuturor variabilor de intrare, intermediare şi finale.

În raport să fie expuse toate programele şi calculele efectuate. Să se analizeze tehnica programării eficiente cu pointeri în baza exerciţiilor şi variantelor problemelor efectuate pentru diverse situaţii cu argumentări.

Consideraţii teoretice:

-Noţiuni, exemple şi importanţa implementării pointelor

Un pointer este o variabilă care are ca valoare adresa unei zone de memorie.

Zona de memorie este o succesiune de 1 ,2 ,4 ,8 sau mai multe locaţii ( octeţi ) consecutive de memorie. Adresa unei zone de memorie este numarul de ordine a primei locatii de memorie ( cea mai din stinga ). Sunt două mari categorii de pointeri :

pointeri catre variabile si pointeri catre functii .

Deci pointerii sunt variabile care contin adresa de memorie a unei alte variabile. Din aceste considerente, pointerii se numesc si variabile de adresa.

Presupunem ca avem o variabila de tip întreg numita entitate localizata la adresa de memorie 0x1000 (adresele sunt automat asigante variabilelor de catre compilator). Daca dorim sa referim aceasta variabila prin intermediul unui pointer entitate_ptr, atunci valoarea pointerului va fi 0x1000 (entitate_ptr = 0x1000), astfel spunem ca entitate_ptr "arata" spre variabila entitate (Pentru a se evita confuziile, se recomanda ca numele pointerilor sa aiba sufixul _ptr).

Pentru a întelege mai bine mecanismul de functionare a pointerilor, introducem o analogie pointeri - adrese postale, în care adresa de memorie este adresa postala, numele variabilei pointer este numele cladirii, iar entitatea referita este cladirea propriu-zisa. Aceasta analogie este prezentata în tabelul 7.1. Se observa ca doi pointeri diferiti (Fac_AC si Fac_ETC) au aceeasi valoare, indicând spre aceeasi locatie, astfel referind aceeasi entitate.

Page 3: SDA (lab 1-2)

Tabela 7.1:

Analogie pointeri adrese postaleVariabila (nume pointer) Fac_CIMValoare adresa str. Studenţilor 3Entitate Cladire FCIM

Pointerii se utilizeaza pentru a face referinţe ( a avea acces ) la valoarea unei variabile atunci când se cunoaste adresa ei . Dar o variabila se memoreaza intr - o zona de memorie de o anumita lungime, functie de tipul ei . De exemplu, o variabila de tip int se memoreaza pe doi octeti, pe când una de tip float pe 4 octeti . De aici urmeaza ca un pointer nu reprezinta numai adresa unei variabile ci mai mult decit atât, anume:

adresa unei zone de memorie; tipul variabilei ( int , char , double etc . ) care este memorata in acea zona

de memorie. Notiunea de pointer face ca limbajul C sa fie un puternic instrument de programare , mai ales la indemâna programatorilor avansaţi . Recomandam ca utilizarea pointerilor sa se faca numai dupa intelegerea clara a mecanismului de lucru cu adrese, intrucât, folositi fara discernamint, ( pointerii neinitializati, distrugerea unor zone de memorie etc.).

Totusi folosiţi cu economie si disciplina, ei dau nastere la programe clare si simple, si de cele mai multe ori mai rapide decit in varianta fara pointeri.

-DECLARAREA POINTERILOR

Ca si in cazul oricaror tipuri de variabile si pointerii trebuie declarati. Ei se declara la fel, cu deosebirea ca numele pointerului este precedat de caracterul *.Declaraţia de pointer este: tip *nume; si prin aceasta se precizeaza ca nume este un pointer catre o zona de memorie care contine valoarea unei variabile de tipul tip.In declaratia de mai sus tip poate fi : int, unsigned, char, float, double etc. sau un sablon de structura. Deci constructia tip * introduce un nou tip de date anume pointer la tip.

Exemple

long *pl ; /* pointer la long; */

char *pc; /* pointer la char; */

double *x; /* pointer la double; */

void *v ; /* pointer fara tip ; */

int *pi[ 8 ]; /* sir de 8 pointeri la int; */

Page 4: SDA (lab 1-2)

unsigned *pm [ 5 ][ 2 ]; /*masiv bidimensional de pointer la unsigned. */

int *p; /* pointeri la int; */

*p++ operatorul ++ incrementeaza pointerul

(*p)++ operatorul ++ incrementeaza continutul pointerului

*++p operatorul ++ incrementeaza pointerul

++(*p) operatorul ++ incrementeaza continutul pointerului

OPERATORII & si * Inainte de toate, pointerul este o variabila, ca orice variabila, el trebuie initializat. O valoare valida pentru un pointer este fie o adresa, fie constanta 0 ( zero ), definita in fisierul antet “stdio.h” prin NULL, si de aici urmeaza ca un pointer este initializat prin atribuirea unei adrese ( un alt pointer ) sau a constantei simbolice NULL. Initializarea se poate face la declararea pointerului sau in decursul programului.

Adresa zonei de memorie, unde se păstrează valoarea variabilei x se poate obţine cu operatorul obţinerii adresei “&”. Rezultatul operaţiei obţinerii adresei este adresa locaţiei de memorie ce a fost alocată pentru variabila respectivă. De exemplu: presupunând că x e înscrisă în memorie pe adresa 21650, atunci &x va fi egală cu 21650. Este important, că &x este constantă de tip indicator şi valoarea sa nu se schimbă în timpul execuţiei programului. Exemplu:

# include <stdio.h>

#include <conio.h>

void main (void){

int x=5;float r=1.7;

int *q;

float *w;

q=&x;

w=&r;

printf ("%f se afla pe adresa % d \n",r,w);

printf ("% d se afla pe adresa %d \n",x,q);

getch();}

Page 5: SDA (lab 1-2)

Exista totusi cateva restrictii , astfel daca ptr1 si ptr2 sunt pointeri de tip1 respectiv de tip2, atunci o atribuire:

ptr 1 = ptr 2 ; este permisa numai daca:

- tip 1 si tip 2 reprezinta acelasi tip, sau

- tip 1 este void , sau

se face o conversie explicita ( cast ) a lui tip2 catre tip1.

Analizînd toate aceste noţiuni, apare întrebarea: “Cu ce scop se folosesc indicatori, dacă valoarea variabilei şi valoarea adresei sale se poate păstra în variabile simple?”.

Prioritatea folosirii indicatorului constă în faptul, că la el se poate adresa în 2 moduri: q şi *q. Astericsul * în acest caz indică că se apelează la conţinutul celulei de memorie, adresa căreia este valoarea indicatorului. Adică valoarea variabilei x de tip întreg este 5; valoarea indicatorului q este 21650; iar valoarea lui *q este egală cu cifra de tip întreg 5 înscrisă pe adresa de memorie 21650.

În aşa mod:

1) Variabila q poate primi valori numai în formă de adresă q=&x şi atribuirea de forma q=21650; este incorectă din cauza că aici se încearcă atribuirea unei valori întregi unui indicator şi nu a adresei.

2) Variabila *q poate primi valori de tip întreg. De exemplu: *q=6; Această atribuire se descifrează astfel: de amplasat valoarea întreagă 6 în celula dememorie ce are adresa indicată

Page 6: SDA (lab 1-2)

în variabila q. Din cauza, că variabila q indică la celula cu adresa 21650, valoarea variabilei ce-şi păstrează valoarea în celula de memorie cu această adresă va fi egală cu 6. Exemplu:

# include <stdio.h>

#include <conio.h>

void main (void){

int x=5;

int *q;

q=&x; printf ("x=%d\n",x); // x=5;

*q=6; printf ("x=%d\n",x); // x= 6;

getch();

}

În rezultatul îndeplinirii exemplului vor fi afişate 2 expresii: x=5 şi x=6. Prima valoare primită de variabila x este valoarea 5 atribuită la momentul iniţializării acesteia. A doua valoare primită de variabila x este valoarea 6 atribuită celulei de memorie la care indică indicatorul q.

Desigur variabila x ar putea primi valoare nouă prin simpla atribuire, x=6; dar efectul deplin de la folosirea indicatorilor se poate observa la transmiterea lor în calitate de parametri unei funcţii, la crearea unui fişier, etc.

Page 7: SDA (lab 1-2)

Adresare si indirectare

Operatorii specifici pointerilor sunt: operatorul “ de dereferentiere ” *, sau “de indirectare” (pentru un pointer, returneaza entitatea referita de pointer) si operatorul de adresa & (pentru o entitate, returneaza adresa entitatii). Exemplul urmator explica folosirea acestor operatori. Variabila pointer obiect_ptr primeste adresa variabilei obiect, astfel obiect_ptr va indica spre zona de memorie unde este memorata variabila obiect. În ultima linie, se stocheaza valoarea 5 la zona de memorie spre care arata obiect_ptr, adica în variabila obiect.

Am vazut ca operatorul de adresa & se aplica unei variabile si intoarce valoarea adresei sale din memorie. Operatorul de indirectare (sau de dereferentiere) se aplica unui pointer si returneaza valoarea scrisa in memorie la adresa data de pointer. Intr-un anumit sens, acesti doi operatori sunt inversi unul altuia. Pentru a intelege mai bine aceste notiuni, sa vedem pe un exemplu ce se intampla in memorie: Exemplu: Presupunem ca avem declaratiile:

int obiect; /* variabila de tip intreg */

int * obiect_ptr; /* pointer la un intreg */

obiect=4; obiect_ptr=&obiect; /* obiect_ptr va indica spre obiect */

printf("%d\n", *obiect_ptr); /* afiseaza valoarea spre care indica obiect_ptr */

*obiect_ptr=5; /* atribuie lui obiect valoarea 5 */

Înainte de folosire, un pointer trebuie iniţializat, de ex. cu adresa unei variabile de tipul potrivit: int x, *p, **pp; p = &x; pp = &p;

O referinţă *p poate fi folosită la stânga sau la dreapta unei atribuiri (în cazul de mai sus, *p se foloseşte absolut la fel (sinonim) cu x): int x, y, z, *p; p = &x; z = *p; /* z = x */

*p = y; /* x = y */

Un pointer special definit în limbajul C este pointerul NULL, care nu indica spre nimic (adresa spre care arata acest pointer este 0). Acest pointer este definit în stdio.h si în stdlib.h ca fiind (void *) 0, adica un pointer spre o locatie de tip void si care arata spre locatia de memorie 0.

Pentru a atribui unui pointer o adresa exista operatorul & , numit si “de adresa” , care aplicat unei variabile , unui element al unui masiv sau unei structuri furnizeaza adresa zonei

Page 8: SDA (lab 1-2)

de memorie unde este memorat respectivul obiect . Operatorul & este unar , are aceeasi prioritate cu a altor operatori unari ++, -- , ! , - (dupa paranteze) si se asociaza de la dreapta la stanga. Nu se aplica expresiilor intre paranteze , constantelor sau variabilelor de tip register si de aceea utilizarile:

& (a + 2 * b ) sau & 13 sunt eronate.

Deci constructia “ * pointer “ este o valoare- stanga.

#include <stdio.h>

#include <conio.h>

main()

{int ivar, *iptr;

iptr = &ivar;

ivar = 421;

printf(" ivar: %p\n",&ivar);

printf(" Continutul ivar: %d\n", ivar);

printf("Continutul iptr: %p\n", iptr);

printf(" Valoarea adresata: %d\n",*iptr);

getch();

}

Page 9: SDA (lab 1-2)

//Operatii indirecte asupra variabilelor x y x, y se adresează direct la variabila

#include<stdio.h>

void main()respectic 97.

{int x = 7, y = 97, s = 0;

int *px, *py;

//Luam adresa variabilelor x, y: adresa 5000. px se

px = &x;

py = &y;

//Suma x + y

s = (*px) + (*py);

//Afisarea Sumei

printf("\nSuma=%d\ ",s); }

977

6000 7

Page 10: SDA (lab 1-2)

POINTER SI TABLOURI UNIDIMENSIONALE

In urmatorul exemplu de program se defineste un sir de caractere de dimensiune maxima 8 elemente initializate cu : abc).

Exemplu 1.4.1. Rulaţi, afişaţi şi analizaţi

main ( )

{ char s[ 8];

s[0] = ‘a’;

s[1] = ‘b’;

s[2] = ‘c’;

s[3] = ‘ ) ‘;

s[4] = ‘\ o’;

printf (“sirul S este % s \ n”, S);

}

La executie se va afisa : abc ) adica elementele sirului pana la caracterul NULL (’ \0 ‘).

In exemplul de mai sus s s-a declarat ca masiv unidimensional (sir) de maxim 8 elemente , dar in program s-au utilizat 5 elemente. Mai mult decat atat s se putea declara ca parametru la o functie ca tablou deschis :

Page 11: SDA (lab 1-2)

char s[ ] ; pentru ca in aceasta situatie alocarea spatiului de memorie necesar se face dinamic . Acest lucru este permis deoarece in limbajul C numele s al sirului este automat convertit la un pointer catre primul element al sirului. Acest pointer este constant in sensul ca este ilegal a se modifica ( prin atribuiri) in program, deci nu este o parte stanga.

Inseamna ca declaratiile: char s[ ] ; si char *s ; sunt echivalente, cu observatia ca in primul caz s este un pointer constant, initializat cu adresa lui s[0], iar in al doilea caz este un pointer obisnuit si neinitializat.

Aceasta echivalenta ne permite sa transmitem ca parametri unei functii siruri, matrici etc. prin pointeri catre primul lor element.

Deci o functie care prelucreaza elementele unui sir poate arata:

void f (char s[ ])

{

. . .

}

sau In ambele exemple, sfarsitul sirului s va fi determinat de detectarea

void f (char * s) caracterului ‘ \ o ‘ .

{ . . . }

Al doilea tip de functie foloseste ca argument o adresa si de aceea in acest caz se mai spune ca apelul este prin referinta ( in engleza call by adress).

Totusi, in cazul masivelor multidimensionale care sunt parametrii unei functii numai prin dimensiune poate lipsi.

Ex. int f ( float a [ ] [10])

In acest exemplu parametrul functiei f este un pointer catre primul element a unei matrici cu 10 coloane.

OPERATII ARITMETICE CU POINTERI

Deoarece pointerii contin ca valoare o adresa de memorie, operatiile aritmetice permise sunt: atribuirea, adunarea cu un intreg, scaderea cu un intreg, scaderea a doi pointeri, comparatia unui pointer cu constanta simbolica NULL si compararea a doi pointeri. Atribuirea a fost tratata in paragraful 5.3.

Adunarea unui pointer cu un intreg Rezultatul adunarii este un pointer de acelasi tip marit cu intregul de adunat inmultit cu numarul de octeti necesari pentru a memora o valoare de tipul pointerului. Astfel daca :

Page 12: SDA (lab 1-2)

tip * ptr; atunci: ptr + i modifica ptr cu i * sizeof (tip).

Deci noua valoare a lui ptr este: ptr + i* sizeof (tip).

Daca intregul i este l atunci se poate folosi operatorul ++ prefixat sau postfixat: ++ ptr sau ptr ++. Noul ptr va fi : ptr + sizeof (tip).

Ex. int * pi, i;

. . .

pi++; /* Aduna 2 la pi (sizeof (int) = 2) */

i = * (pi + 3); /* Aduna 2 * 3 la pi si intregul memorat la pi + 6 este atribuit lui i */

. . .

Daca avem de-a face cu un masiv unidimensional (sir) tip a [DIM]; atunci numele sirului neurmat de indici este considerat pointer catre primul sau element, anume pointer constant ce contine intotdeauna adresa lui a[ 0]. .Altfel spus, valoarea lui a este & a[0]. Deci inseamna ca adunarea unui intreg la acest tip de pointer este o constructie corecta si :

a + i este identic cu &a[ i ] iar

*(a+i) este identic cu a [ i ] .

Scaderea unui intreg dintr-un pointer

Efectul scaderii unui intreg dintr-un pointer este acela ca valoarea pointerului se micsoreaza cu intregul de scazut inmultit cu numarul de octeti necesari pentru a memora o valoare de tipul pointerului.

Daca: tip *ptr; atunci noua valoare a pointerului dupa: ptr – i este : ptr – i * sizeof (tip).

Utilizand operatorul de decrementare -- ptr sau ptr – noua valoare pentru ptr va fi ptr – sizeof (tip).

Atat operatorul de incrementare ++, cat si cel de decrementare – se pot utiliza in expresii cu pointeri impreuna cu operatorul de dereferentiere *. Pentru corectitudine trebuie tinut cont ca operatorii ++ , -- si * sunt de aceeasi prioritate si se asociaza de la dreapata la stanga, deci se vor folosi parantezele pentru a forta ca un operator sa se execute primul.

Astfel daca: tip *ptr; atunci:

*ptr ++ respectiv *ptr – mai intai obtine valoarea de la adresa ptr si apoi incrementeaza , respectiv

Page 13: SDA (lab 1-2)

decrementeaza pointerul;

(*ptr)++ respectiv (*ptr) – obtine valoarea de la adresa ptr pe care o incrementeaza , respectiv o

decrementeaza;

*++ptr respectiv * -- ptr mai intai incrementeaza, respectiv decrementeaza pointerul si apoi obtine

valoarea de la noua adresa ptr;

++*ptr respectiv --*ptr obtine valoarea de la adresa ptr pe care o incrementeaza , respectiv o decrementeaza .

Expresiile cu pointeri de mai sus permit o scriere mai compacta a programului si in comparatie cu utilizarea indicilor la un tablou duc la o executie a programului intr-un timp mai scurt. Un motiv in plus pentru utilizarea acestor expresii este ca: *ptr++, *ptr --, *++ptr si *-- ptr, intrucat se refera la obiectul catre care indica adresa din ptr, sunt valori-stanga (L value) si deci pot fi utilizate la stanga operatorului de atribuire =. In acest caz operatorul = va modifica obiectul referit.

Scaderea a doi pointeri

In general scaderea a doi pointeri este o operatie ilegala, cu cateva exceptii.Astfel, daca pointerii sunt de acelasi tip , in sensul ca se refera la obiecte de acelasi tip, ei se pot scade si rezultatul scaderii este un intreg int sau long ( functie de modelul de memorie) egal cu numarul de locatii de memorie capabile sa memoreze o valoare de tipul comun al pointerilor existente intre adresa primului si adresa celui de-al doilea. Rezultatul este pozitiv daca primul pointer este o adresa aflata la dreapta adresei din cel de-al doilea pointer si negativ in caz contrar.

Fie pointerii ptrl si ptr2:

tip *ptrl , *ptr2; atunci: ptr l – ptr 2 va fi egal cu (ptr l – ptr 2)/ sizeof (tip).

Alta scadere corecta este aceea cand cei doi pointeri indica spre elementele aceluiasi tablou si in aceasta situatie rezultatul este egal cu numarul de elemente dintre cei doi indici: pozitiv daca indicele l > indicele 2 si negativ in caz contrar.

Exemplu:

#include <stdio.h>

Page 14: SDA (lab 1-2)

#include <conio.h>

main ( )

{int * pl, *p2; /* se declara doi pointer de tip int */

int x [ ] = { 0,1,2,3,4,5,6,7}; /* se declara un sir de 8 intregi care se initializeaza */

pl = & x [ 2 ]; /* Pointerul pl se initializeaza cu adresa lui x [2] . Echivalent cu : pl = x + 2 ; */

p2 = x + 7; /*Pointerul p2 se initializeaza cu adresa de inceput a lui x plus inca 7 locatii a cate 2 octeti adica cu adresa lui x [7]. Echivalent cu : p2 = & x [ 7]; */

printf ("x = %u pl = %u p2= %u \n", x,pl,p2);

printf ("Diferente: pl - x = %u p2 - x = %u p2- p1 = %u \n", pl - x, p2 - x, p2 - pl);

getch();

}

Compararea a doi pointeri .

Pentru ca operatia de comparare sa fie valida, cei doi pointeri trebuie sa indice catre doua elemente ale aceluiasi tablou si rezultatul comparatiei este egal cu rezultatul comparatiei celor doi indici.

Page 15: SDA (lab 1-2)

Daca pointerul pl indica pe a [i], iar pointerul p2 indica pe a [j] atunci:

pl < p2 = 1 (adevarata) daca i < j;

= 0 (falsa) daca i > j;

pl > = p2 =l (adevarata) daca i > j;

= O (falsa) daca i < j;

pl = = p2 = l (adevarata) daca i = j; şi = 0 (falsa) daca i j

Legatura dintre tablouri si pointeri

Numele unui tablou este un pointer constant spre primul sau element. Expresiile de mai jos sunt deci echivalente:

     

   nume_tablou        &nume_tablou        &nume_tablou[0]

    *nume_tablou       nume_tablou[0]

La declaraţiile: int a[3][3]={ { 11,12,13 }, { 21,22,23 }, { 31,32,33 } }; int *pa[3]={ a,a[1],a[2] }; int *p=a[0]; se obţine schema următoare:

Conform schemei de mai sus se obţin următoarele echivalenţe: a[0][0], *a, **a[0], *p, **pa, *p[0].

В[i][j][k] poate avea următoarele construcţii: *(*(*(B + i ) + j ) + k ) sau *(B[i][j] + k ).

Exemplu: Programul de mai jos citeste si afiseaza elementele a doua tablouri, la primul accesul se face indexat, la al doilea prin pointeri.   Rulaţi, afişaţi şi analizaţi

#include <stdio.h>

#include <conio.h>

#define N 5

int tab1[N],tab2[N];

Page 16: SDA (lab 1-2)

void citire1(void){ /* citeste elementele lui tab1 prin accesarea indexata   a elementelor */

  int i;   puts("Introduceti elementele lui tab1:");   for(i=0;i<N;i++){    putchar(':');scanf("%d",&tab1[i]);   } /*for i*/ } /* citire1 */

void tiparire1(void){ /* tipareste elementele lui tab1 prin accesarea indexata   a elementelor */

  int i;   puts("Elementele lui tab1:");

  for(i=0;i<N;i++)     printf("%d ",tab1[i]);   putchar('\n'); } /* tiparire1 */

void citire2(void){ /* citeste elementele lui tab2 - accesarea fiecarui   element se face printr-un pointer la el */

  int *pi;   puts("Introduceti elementele lui tab2:");

for(pi=tab2;pi<tab2+N;pi++){ /* initializari echivalente sunt pi=&tab2 

  sau pi=&tab[0]; conditie echivalenta pi<=&tab2[N-1] */

  putchar(':');scanf("%d",pi);   } /*for pi*/ } /* citire2 */

void tiparire2(void){ /* tipareste elementele lui tab2 prin accesare la pointeri */

  int *pi;   puts("Elementele lui tab2:");

  for(pi=tab2;pi<tab2+N;pi++)   printf("%d ", *pi);   putchar('\n'); } /* tiparire2 */

void main(void){   clrscr();   citire1();   tiparire1();   citire2();   tiparire2();   getch(); }

Page 17: SDA (lab 1-2)

Programul acesta demonstrează două posibilităţi de prelucrare a elementelelor tabloului, unde accesul prin pointeri este mai eficient.  

POINTERI CA ARGUMENTE LA FUNCTII. APEL PRIN REFERINTA Am precizat in capitolul precedent ca la apelul prin valoare functia apelata prelucreaza o copie a valorilor parametrilor, valori care au fost puse pe stiva. Inseamna ca functia apelata are efect asupra functiei apelante doar printr-o singura valoare returnata. Dar ce posibilitati are programatorul cand doreste ca functia apelata sa modifice mai multe variabile din functie apelanta? In aceste situatii programatorul va transmite functiei apelate adresele variabilelor ce urmeaza sa fie modificate. Avand la dispozitie aceste adrese, functia apelata va putea lucra direct asupra valorilor aflate la aceste adrese. Deci programul apelant va trebui sa transmita functiei apelate pointeri catre variabilele ce vor fi modificate.

În limbajul C se pot transmite parametri si returna prin adresă, intr-un mod indirect, folosind pointeri care ne permit să utilizăm funcţiile ca “proceduri”, adică să returnăm mai multe rezultate şi, chiar, structuri complexe (tablourilor uni- şi multi-dimensionale etc.).

În cazul pointerilor, funcţia apelantă trebuie să furnizeze adresa variabilei de modificat (tehnic printr-un pointer la această variabilă), iar funcţia apelată trebuie să declare argumentul corespunzător ca fiind un pointer. Referirea la variabila de modificat se face prin adresare indirectă.

Printre argumentele funcţiei pot apărea şi nume de tablouri. În acest caz valoarea transmisă funcţiei este în realitate adresa de început a masivului (elementele masivului nu sînt copiate). Prin indexarea acestei valori funcţia poate avea acces şi poate modifica orice element din tablou

FUNCTII CARE RETURNEAZA POINTERI

Sunt situatii cand nu este suficient ca functia apelanta sa intoarca o singura valoare catre apelanta. De multe ori apelanta ar trebui sa returneze un sir de valori, o structura sau o uniune, un sir de caractere, o matrice etc.

Toate aceste alternative sunt rezolvate in limbajul C prin intermediul functiilor care returneaza pointeri. Anume pointeri la siruri, la structuri, la uniuni, la matrice etc.

O functie care returneaza pointer se declara astfel :

[tip] nume-functie (lista-declaratori-parametri) ;

In aceasta constructie caracterul asterisc “ * “ este cel care indica faptul ca va returna un pointer. Tip-ul din fata este de fapt tipul pointerului returnat de functie.

Page 18: SDA (lab 1-2)

Exemplu 1 .7.1 Urmatoarea functie concateneaza sirul S la sfarsitul lui D si returneaza pointer catre noul sir D. Se presupune ca in D este suficient loc pentru ambele siruri.

char *strcat (char *D, char *S)

{ char *M = D ;

while ( *D) /* cat timp caracterul curent din D 0 */

++D ; /* avanseaza la urmatorul caracter */

while ( *D++ = *S++) /* Copie caracterul curent */

; /* din S in caracterul curent de la sf. */

/* lui D si trece la caracterul urmator */

return M ; /* Returneaza adresa de inceput a sirului rezultat */

.

TABLOURI DE POINTERI. INITIALIZAREA TABLOURILOR DE POINTERI

Sa presupunem ca avem de scris o functie care sa returneze un sir de caractere cu numele zilei a n-a din saptamana. O functie nu poate intoarce un sir de caractere asa ca este clar ca trebuie sa revina cu un pointer la numele zilei a n-a. Deci, in cadrul functiei trebuie sa se creeze un sir de nume de zile si un sir de pointeri spre aceste nume. Clasa de memorie pentru acest sir de nume trebuie sa fie static interna, asa fel incat sa nu se distruga la fiecare revenire din functie.

Exemplu 1 .9.1

char *nume-zi (int n)

{

static char *zi [ ] = { /* Declaratie de sir de */

“zi ilegala”, /* pointeri si */

“Luni”, /* initializarea */

“Marti”, /* acestuia */

“Miercuri”

“Joi”

Page 19: SDA (lab 1-2)

“Vineri”

“Sambata”

“Duminica” } ;

if (n > = 1 && n < = 7) return zi [ n ] ;

else return zi [ 0 ] ;

}

Condiţia problemei:

Exemplul 1 .10.12: Da-ţi la execuţie şi analizaţi modul de organizare a parcurgerii şi obţinere a rezultatelor prin pointeri şi indecşi, afişând toate valorile. Face-ţi îmbunătăţiri şi rulaţi din nou. Comparaţi.

#include …. #include ….. double cos(double); double sin(double); double tan(double); int main() { double (*(*masfun))(double); double x=0.5, y; int i; masfun=(double(*(*))(double)) calloc(3,sizeof(double(*(*))(double))); masfun[0]=cos; masfun[1]=sin; masfun[2]=tan; for (i=0; i<3; i++);{ y="masfun[i](x);" printf("\n x="%g" y="%g",x,y);" } return 0; }

Program nou:

#include<stdlib.h>

#include <conio.h>

#include <malloc.h>

#include <stdio.h>

double cos(double); /* pointer la double; */

double sin(double); /* pointer la double; */

Page 20: SDA (lab 1-2)

double tan(double); /* pointer la double; */

int main() /* citeste elementele */

{ double (*(*masfun))(double);

double x=0.5, y; /* pointer la double; */

int i; /*n va fi numarul de elemente */

masfun=(double(*(*))(double))

calloc(3,sizeof(double(*(*))(double)));

masfun[0]=cos;

masfun[1]=sin;

masfun[2]=tan;

for (i=0; i<3; i++) /*variabila i va fi folosita pt parcurgerea in ordine a sirului, crescand cu 1 dupa fiecare afisare*/

{ y=masfun[i](x);

printf("\n x=%g y=%g",x,y); }

getch();

return 0; }

Observație: Am obținut rezultatele necesare, deci programul lucrează.

Page 21: SDA (lab 1-2)

Exemplul 1 .10.13: Da-ţi la execuţie şi analizaţi modul de organizare a parcurgerii şi obţinere a rezultatelor prin pointeri şi indecşi, afişând toate valorile. Face-ţi îmbunătăţiri şi rulaţi din nou. Comparaţi.

#define n 3

int a[n][n],b[n][n];

void linii(int **a,int i,int j) // cerinţa a)

{ int *p,*q,aux; for(p=*(a+i),q=*(a+j);p<=*(a+i)+n-1;p++,q++)

{ aux=*p; *p=*q; *q=aux; } }

void coloane(int **a,int i,int j) // cerinţa b)

{ int *p,*q,aux; for(p=*a+i,q=*a+j;p<=*(a+n-1)+i;p+=n,q+=n)

{ aux=*p; *p=*q; *q=aux; } }

int suma(int **a) // cerinţa c)

{ int *p,s=0; for(p=*a;p<=*(a+n-1)+n-1;p+=n+1)

s+=*p; return s; }

int produs(int **a) // cerinţa d)

{ int *p,pr=1; for(p=*a+n-1;p<=*(a+n-1);p+=n-1)

pr*=*p; return pr; }

void transpusa(int **a,int **b) // cerinţa e)

{ int *p,*q,*r; for(int i=0;i<n;i++)

for(p=*(a+i),q=p+n-1,r=*b+i;p<=q;p++,r+=n)

*r=*p;

}

Program nou:

Page 22: SDA (lab 1-2)

#include<stdlib.h>

#include <conio.h>

#include <malloc.h>

#include <stdio.h>

/*citeste de la tastatura elementele unei linii*/

void linii(int **a,int i, int j, int n) {

int *p,*q,aux;

for(i=0,j=0,p=*(a+i),q=*(a+j); p<=*(a+i)+n-1; p++, q++,i++,j++)/*for i*/

{ aux=*p; *p=*q; *q=aux; }

}

/*citeste de la tastatura elementele unei coloane*/

void coloane(int **a,int i,int j, int n) // cerința b)

{ int *p,*q,aux;

for(p=*a+i,q=*a+j;p<=*(a+n-1)+i;p+=n,q+=n) /*for p*/

{ aux=*p; *p=*q; *q=aux; } }

/*

Descriere: Functia afiseaza suma elementelor , punand spatii intre fiecare elemet

Rezultate:Afisarea sumei tabloului bidimensional.

*/

int suma(int **a, int n)

{ int *p,s=0; for(p=*a;p<=*(a+n-1)+n-1;p+=n+1)

s+=*p; return s; }

/*

Page 23: SDA (lab 1-2)

Descriere: Functia afiseaza produsul elementelor , punand spatii intre fiecare elemet

Rezultate:Afisarea sumei tabloului bidimensional.

*/

int produs(int **a, int n)

{int *p,pr=1;

for(p=*a+n-1;p<=*(a+n-1);p+=n-1)

pr*=*p; return pr; }

/*

Descriere: Functia afiseaza transpusa matricei, punand spatii intre fiecare elemet

Rezultate:Afisarea transpusei matricii.

*/

void transpusa(int **a,int **b, int n)

{ int *p,*q,*r; int i;

for( i=0;i<n;i++)

for(p=*(a+i),q=p+n-1,r=*b+i;p<=q;p++,r+=n){

*r=*p;

printf(" %d ",*r); }

}

int main()

{ int i, j; /* se declara două variabile de tip int */

int **a, **b; /* se declara doi pointeri de tip int */

int n=3;

a=b =(int**)malloc(n*sizeof(int*)); /*alocare dinamica a memoriei;

sirul a va contine numere naturale ce urmeaza sa fie verificate*/

for (i=0; i<n; i++)

Page 24: SDA (lab 1-2)

*(a+i)=*(b+i)= (int*) malloc(n*sizeof(int));

puts("\nMatricea initiala");

for(i=0;i<n;i++) /*variabila i va fi folosita pt parcurgerea in ordine a sirului, crescand cu 1 dupa fiecare afisare*/

for( j=0;j<n;j++)/*variabila j va fi folosita pt parcurgerea in ordine a sirului, crescand cu 1 dupa fiecare afisare*/

{

*(*(a+i)+j)=random(100);

printf("\n a[%d][%d]=", i,j);

printf(" %d ", a[i][j]);

}

puts("\nMatricea la indeplinirea functiei linii"); /* se e-a din memorie matricea la indeplinirea functiei linei*/

for(i=0;i<n;i++)

for( j=0;j<n;j++)

{

linii(a,i,j,n);

printf("\n a[%d][%d]=", i,j);

printf(" %d ", a[i][j]);

}

puts("\nMatricea la indeplinirea functiei coloane"); /* se e-a din memorie matricea la indeplinirea functiei coloanei*/

for(i=0;i<n;i++)

for( j=0;j<n;j++)

Page 25: SDA (lab 1-2)

{

coloane(a,i,j,n);

printf("\n a[%d][%d]=", i,j);

printf(" %d ", a[i][j]);

}

puts("\nSuma: ");

printf(" %d ", suma(a,n));

puts("\nProdusul: ");

printf(" %d ", produs(a,n));

puts("\nTranspusa: ");

transpusa(a, b, n) ;

getch();

return 0;

}

Observație: Am obținut rezultatele necesare, deci programul lucrează.

Page 26: SDA (lab 1-2)

22. Fiind date mai multe numere naturale prin reprezentările lor în baza p, să se determine cel mai mic şi cel mai mare dintre ele şi suma lor. Numerele se introduc de la tastatură prin cifrele lor, dupǎ fiecare număr fiind scrisǎ "cifra" -1, iar şirul de numere se termină la întâlnirea "numărului 0" (citirea unui numǎr dat în baza p, compararea a douǎ astfel de numere şi suma a douǎ numere).

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <string.h>

int sum[30], lungime;

/*

Descriere: citeste de la tastatura un sir de numere naturale

Rezultate: compararea acestor numere

*/

int compare(int x, int y)

{

int i=0, j=0; /* i,j vor fi folositi pt compararea acestor numere*/

while (*(x+i)>=0)i++; // daca i este mai mare ca 0, crescand cu 1 dupa fiecare afisare /

while (*(y+j)>=0)j++; // daca j este mai mare ca 0, crescand cu 1 dupa fiecare afisare /

if (i==j)

{

i=0;

while (*(x+i)==*(y+i)) /*se verifica atat timp cat a x[i] este egal cu y[i]*/

{

if (i==0) return 0; /* se returneaza 0 daca nu exista cifre asemenea*/

Page 27: SDA (lab 1-2)

i++; };

if (*(x+i)>*(y+i)) return 1;

else return -1;

}

else

{

if (i>j) return 1;

else return -1;

}}

/*

Descriere: citeste de la tastatura un sir de numere naturale

Rezultate: sumarea acestor numere*/

void sumation(int x, int y, int baza)

{

int i=0,j=0,k=0; /* i,j,k vor fi folositi pt sumarea acestor numere*/

while (*(x+i)>-1)i++;i--; /*se citeste pana cind elementul este mai mare ca -1*/

while (*(y+j)>-1)j++;j--; /*se citeste pana cind elementul este mai mare ca -1*/

lungime=0;

for (k=0; k<30; k++) *(sum+k)=0;

k=0;

while (i>=0 && j>=0)

{

*(sum+k)+=*(x+i)+*(y+j);

*(sum+k+1)+=*(sum+k)/baza;

*(sum+k)%=baza;

Page 28: SDA (lab 1-2)

k++;

i--;

j--;

}

while (i>=0)

{

*(sum+k)+=*(x+i);

*(sum+k+1)+=*(sum+k)/baza;

*(sum+k)%=baza;

k++;

i--; }

while (j>=0)

{

*(sum+k)+=*(y+j);

*(sum+k+1)+=*(sum+k)/baza;

*(sum+k)%=baza;

k++;

j--;

}

do { *(sum+k+1)+=*(sum+k)/baza;

*(sum+k)%=baza;

k++;

} while (*(sum+k)>=baza);

lungime=k;}

int main()

{

int b,i=0, k=0, comp;

Page 29: SDA (lab 1-2)

int *x,*max,*min;

x=max=min=(int*) malloc(30*sizeof(int));

printf("\nDati baza numerelor: ");

scanf("%d",&b);

printf("\nDati sirul de elemente, finisat de -1\n");

i=0;

scanf("%d", &*(x+i));

while (*(x+i)!=-10)

{

while (*(x+i++)>-1) scanf("%d",&*(x+i));

k++;

if (k==1)

{

i=0;

while (*(x+i)>-1)

{

*(max+i)=*(x+i);

i++;

}

*(max+i)=-1;

i=0;

while (*(x+i)>-1)

{

*(min+i)=*(x+i);

i++;

}

*(min+i)=-1;

Page 30: SDA (lab 1-2)

}

else

{

if (compare(x,max)==1)

{

i=0;

while (*(x+i)>-1)

{

*(max+i)=*(x+i);

i++;

}

*(max+i)=-1;

}

if (compare(x,min)==-1)

{

i=0;

while (*(x+i)>-1)

{

*(min+i)=*(x+i);

i++;

}

*(min+i)=-1;

}

}

i=0;

scanf("%d",&*(x+i));

}

Page 31: SDA (lab 1-2)

i=0;

printf("\nAu fost citite %d numere; \nMaxim: ",k);

while (*(max+i)>-1)

{

printf("%d",*(max+i));

i++; }

printf("\nMinim: ");

i=0;

while (*(min+i)>-1)

{

printf("%d",*(min+i));

i++;

}

printf("\nSuma min si max: ");

sumation(min,max,b);

i=0;

while(lungime>0)

{

lungime--;

if (sum[lungime]>0 || i==1) printf("%d",sum[lungime]);

if (sum[lungime]>0) i=1; }

return 0;

}

Page 32: SDA (lab 1-2)

Concluzie:

1. În urma efectuarii lucrării de laborator, am căpătat noi abilităţi în tehnicile de prelucrare a

pointerilor şi funcţiilor în limbajul C,am insusit metode de folosire a pointerilor,ne-am reamintit declararea pointerilor,si ne-am facut cunoscuti cu tehnica de lucru pe baza lor.

Bibliografie

1. O. Catrina, I. Cojocaru, Turbo C++, ed. Teora 19932. V. Petrovici, Florin Goicea, Programarea in limbajul C. Eed. Teora 19993. Liviu Negrescu, ,,Limbajul C” ,volumul I_partea I-a si partea II-a32 32ditura

MicroInformatica, Cluj-napoca 20014. Б.Керниган, Д.Ритчи. Язык программирования Си. Санкт-Петербург, 2001,,, Brian

Kernighan, Dennis Ritchie ” The C Programming Language” este în l. română format electronic

5. Vlad Caprariu ”Ghid de utilizare Turbo C” Cluj - Napoca 1993.6. Cristea Valentin. Tehnici de programare. Ed.: Bucur., Teora, 1993. /681.3; T29/7. Odagescu Ioan, Copos Cristina s.a. Metode si Tehnici de programare./enunturi, solutii,

probleme propuse/ Ed.:Bucur.: INTACT, 1994 /681.3; O23/8. Tudor Bălănescu. Corectudinea agoritmilor.Bucur.:Ed. Tehn.19959. Indicatii metodice „Stefan Marin”