Document, comentariu, eseu, bacalaureat, liceu si facultate
Top documenteAdmitereTesteUtileContact
      
    


 


Ultimele referate adaugate

Adauga referat - poti sa ne ajuti cu un referat?

Politica de confidentialitate



Ultimele referate descarcare de pe site
  CREDITUL IPOTECAR PENTRU INVESTITII IMOBILIARE (economie)
  Comertul cu amanuntul (economie)
  IDENTIFICAREA CRIMINALISTICA (drept)
  Mecanismul motor, Biela, organe mobile proiect (diverse)
  O scrisoare pierduta (romana)
  O scrisoare pierduta (romana)
  Ion DRUTA (romana)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  COMPORTAMENT PROSOCIAL-COMPORTAMENT ANTISOCIAL (psihologie)
  Starea civila (geografie)
 

Ultimele referate cautate in site
   domnisoara hus
   legume
    istoria unui galban
   metanol
   recapitulare
   profitul
   caract
   comentariu liric
   radiolocatia
   praslea cel voinic si merele da aur
 
despre:
 
Pointeri si masive ale limbajului de programare C
Colt dreapta
Vizite: ? Nota: ? Ce reprezinta? Intrebari si raspunsuri
 

Un pointer este o variabila care contine adresa unei alte variabile. Pointerii sint foarte mult utilizati in programe scrise in C, pe de o parte pentru ca uneori sint unicul mijloc de a exprima un calcul, iar pe de alta parte pentru ca ofera posibilitatea scrierii unui program mai compact si mai eficient decit ar putea fi obtinut prin alte cai.

9.1. Pointeri si adrese

Deoarece un pointer contine adresa unui obiect, cu ajutorul lui putem avea acces, in mod indirect, la acea variabila (obiect). h4q12qk
Sa presupunem ca x este o variabila de tip intreg si px un pointer la aceasta variabila. Atunci aplicind operatorul unar & lui x, instructiunea: px = &x; atribuie variabilei px adresa variabilei x; in acest fel spunem ca px indica (pointeaza) spre x.
Invers, daca px contine adresa variabilei x, atunci instructiunea: y = *px; atribuie variabilei y continutul locatiei pe care o indica px.
Evident toate variabilele care sint implicate in aceste instructiuni trebuie declarate. Aceste declaratii sint: int x,y; int *px;
Declaratiile variabilelor x si y sint deja cunoscute. Declaratia pointerului px este o noutate. A doua declaratie indica faptul ca o combinatie de forma *px este un intreg, iar variabila px care apare in contextul *px este echivalenta cu un pointer la o variabila de tip intreg. In locul tipului intreg poate aparea oricare dintre tipurile admise in limbaj si se refera la obiectele pe care le indica px.
Pointerii pot aparea si in expresii, ca de exemplu in expresia urmatoare: y = *px + 1; unde variabilei y i se atribuie continutul variabilei x plus 1.
Instructiunea: d = sqrt((double)*px); are ca efect convertirea continutului variabilei x pe care o indica px in tip double si apoi depunerea radacinii patrate a valorii astfel convertite in variabila d.
Referiri la pointeri pot aparea de asemenea si in partea stinga a atribuirilor. Daca, de exemplu, px indica spre x, atunci:
*px = 0; atribuie variabilei x valoarea zero, iar:
*px += 1; incrementeaza continutul variabilei x cu 1, ca si in expresia:
(*px)++;
In acest ultim exemplu parantezele sint obligatorii deoarece, in lipsa lor, expresia ar incrementa pe px in loc de continutul variabilei pe care o indica (operatorii unari *, ++ au aceeasi precedenta si sint evaluati de la dreapta spre stinga).




9.2 Pointeri si argumente de functii

Deoarece in limbajul C transmiterea argumentelor la functii se face „prin valoare” (si nu prin referinta), functia apelata nu are posibilitatea de a altera o variabila din functia apelanta. Problema care se pune este cum procedam daca totusi dorim sa schimbam un argument?
De exemplu, o rutina de sortare poate schimba intre ele doua elemente care nu respecta ordinea dorita, cu ajutorul unei functii swap. Fie functia swap definita astfel:

swap(int x, int y) A /* gresit */ int temp; temp = x; x = y; y = temp;
S

Functia swap apelata prin swap(a,b) nu va realiza actiunea dorita deoarece ea nu poate afecta argumentele a si b din rutina apelanta.
Exista insa o posibilitate de a obtine efectul dorit, daca functia apelanta transmite ca argumente pointeri la valorile ce se doresc interschimbate. Atunci in functia apelanta apelul va fi: swap(&a,&b); iar forma corecta a lui swap este:

swap(int *px, int *py) A/* interschimba *px si *py */ int temp; temp = *px;
*px = *py;
*py = temp;
S

9.3. Pointeri si masive

In limbajul C exista o strinsa legatura intre pointeri si masive. Orice operatie care poate fi realizata prin indicarea masivului poate fi de asemenea facuta prin pointeri, care, in plus, conduce si la o accelerare a operatiei. Declaratia: int aa10i; defineste un masiv de dimensiune 10, care reprezinta un bloc de 10 obiecte consecutive numite aa0i, ... aa9i. Notatia aaii reprezinta al i-lea element al masivului sau elementul din pozitia i?1, incepind cu primul element. Daca pa este un pointer la un intreg declarat sub forma: int *pa; atunci atribuirea: pa = &aa0i;
incarca variabila pa cu adresa primului element al masivului a.
Atribuirea: x = *pa; copiaza continutul lui aa0i in x.
Daca pa indica un element particular al unui masiv a, atunci prin definitie pa+i indica un element cu i pozitii dupa elementul pe care il indica pa, dupa cum pa-i indica un element cu i pozitii inainte de cel pe care indica pa. Astfel, daca variabila pa indica pe aa0i atunci *(pa+i) se refera la continutul lui aaii.
Aceste observatii sint adevarate indiferent de tipul variabilelor din masivul a.
Intreaga aritmetica cu pointeri are in vedere faptul ca expresia pa+i inseamna de fapt inmultirea lui i cu lungimea elementului pe care il indica pa si adunarea apoi la pa, obtinindu-se astfel adresa elementului de indice i al masivului.
Corespondenta dintre indexarea intr-un masiv si aritmetica de pointeri este foarte strinsa. De fapt, o referire la un masiv este convertita de compilator intr-un pointer la inceputul masivului. Efectul este ca un nume de masiv este o expresie pointer, deoarece numele unui masiv este identic cu numele elementului de indice zero din masiv.
Atribuirea: pa = &aa0i; este identica cu: pa = a;
De asemenea, expresiile aaii si *(a+i) sint identice. Aplicind operatorul & la ambele parti obtinem &aaii identic cu a+i. Pe de alta parte, daca pa este un pointer, expresiile pot folosi acest pointer ca un indice: paaii este identic cu *(pa+i). Pe scurt orice expresie de masiv si indice poate fi scrisa ca un pointer si un deplasament si invers, chiar in aceeasi instructiune.
Exista insa o singura diferenta intre un nume de masiv si un pointer la inceputul masivului. Un pointer este o variabila, deci pa = a si pa++ sint instructiuni corecte. Dar un nume de masiv este o constanta si deci constructii de forma a = pa, a++ sau p = &a sint ilegale.
Cind se transmite unei functii un nume de masiv, ceea ce se transmite de fapt este adresa primului element al masivului. Asadar, un nume de masiv, argument al unei functii, este in realitate un pointer, adica o variabila care contine o adresa. Fie de exemplu functia strlen care calculeaza lungimea sirului s:

strlen(char *s) A /* returneaza lungimea sirului */ int n; for (n=0; *s!='\0'; s++) n++; return n;
S

Incrementarea lui s este legala deoarece s este o variabila pointer. s++ nu afecteaza sirul de caractere din functia care apeleaza pe strlen, ci numai copia adresei sirului din functia strlen.
Este posibil sa se transmita unei functii, ca argument, numai o parte a unui masiv, printr-un pointer la inceputul sub-masivului respectiv. De exemplu, daca a este un masiv, atunci:

f(&aa2i) f(a+2)

transmit functiei f adresa elementului aa2i, deoarece &aa2i si a+2 sint expresii pointer care, ambele, se refera la al treilea element al masivului a. In cadrul functiei f argumentul se poate declara astfel: f(int arrai) A S sau f(int *arr) A S
Declaratiile int arrai si int *arr sint echivalente, optiunea pentru una din aceste forme depinzind de modul in care vor fi scrise expresiile in interiorul functiei.

9.4. Aritmetica de adrese

Daca p este un pointer, atunci p += i incrementeaza pe p pentru a indica cu i elemente dupa elementul pe care il indica in prealabil p. Aceasta constructie si altele similare sint cele mai simple si comune formule ale aritmeticii de adrese, care constituie o caracteristica puternica a limbajului C. Sa ilustram citeva din proprietatile aritmeticii de adrese scriind un alocator rudimentar de memorie. Fie rutina alloc(n) care returneaza un pointer p la o zona de n caractere consecutive care vor fi folosite de rutina apelanta pentru memorarea unui sir de caractere. Fie rutina free(p) care elibereaza o zona incepind cu adresa indicata de pointerul p pentru a putea fi refolosita mai tirziu. Zona de memorie folosita de rutinele alloc si free este o stiva functionind pe principiul ultimul intrat ? primul iesit, iar apelul la free trebuie facut in ordine inversa cu apelul la alloc. Sa consideram ca functia alloc va gestiona stiva ca pe elementele unui masiv pe care il vom numi allocbuf. Vom mai folosi un pointer la urmatorul element liber din masiv, pe care-l vom numi allocp. Cind se apeleaza rutina alloc pentru n caractere, se verifica daca exista suficient spatiu liber in masivul allocbuf. Daca da, alloc va returna valoarea curenta a lui allocp, adica adresa de inceput a blocului cerut, dupa care va incrementa pe allocp cu n pentru a indica urmatoarea zona libera. free(p) actualizeaza allocp cu valoarea p, daca p indica in interiorul lui allocbuf.

#define NULL 0
/* valoarea pointerului pentru semnalizarea erorii */
#define ALLOCSIZE 1000
/* dimensiunea spatiului disponibil */ static char allocbuf aALLOCSIZEi;
/* memoria pentru alloc */ static char *allocp = allocbuf;
/* urmatoarea pozitie libera */

char *alloc(int n) A
/* returneaza pointer la n caractere */ if (allocp+n<=allocbuf+ALLOCSIZE) A allocp += n; /* dimensiunea satisfacuta */ return allocp-n; /* vechea valoare */
S else return NULL; /* nu este spatiu suficient */
S

free(char *p) A /* elibereaza memoria indicata de p */ if (p>=allocbuf && p<allocbuf+ALLOCSIZE) allocp = p;
S

Testul if (allocp+n<=allocbuf+ALLOCSIZE) verifica daca exista spatiu suficient pentru satisfacerea cererii de alocare a n caractere. Daca cererea poate fi satisfacuta, alloc revine cu un pointer la zona de n caractere consecutive. Daca nu, alloc trebuie sa semnaleze lipsa de spatiu pe care o face returnind valoarea constantei simbolice NULL. Limbajul C garanteaza ca nici un pointer care indica corect o data nu va contine zero, prin urmare o revenire cu valoarea zero poate fi folosita pentru semnalarea unui eveniment anormal (in cazul nostru, lipsa de spatiu). Atribuirea valorii zero unui pointer este deci un caz special.
Observam de asemenea ca variabilele allocbuf si allocp sint declarate static cu scopul ca ele sa fie locale numai fisierului sursa care contine functiile alloc si free.
Exemplul de mai sus demonstreaza citeva din facilitatile aritmeticii de adrese (pointeri). In primul rind, pointerii pot fi comparati in anumite situatii. Daca p si q sint pointeri la membri unui acelasi masiv, atunci relatiile <, <=, >, >=, ==, != sint valide. Relatia p<q, de exemplu, este adevarata daca p indica un element mai apropiat de inceputul masivului decit elementul indicat de pointerul q. Compararile intre pointeri pot duce insa la rezultate imprevizibile, daca ei se refera la elemente apartinind la masive diferite.
Se observa ca pointerii si intregii pot fi adunati sau scazuti. Constructia de forma: p+n
inseamna adresa celui de-al n-lea element dupa cel indicat de p, indiferent de tipul elementului pe care il indica p. Compilatorul C aliniaza valoarea lui n conform dimensiunii elementelor pe care le indica p, dimensiunea fiind determinata din declaratia lui p (scara de aliniere este 1 pentru char, 2 pentru int etc).
Daca p si q indica elemente ale aceluiasi masiv, p-q este numarul elementelor dintre cele pe care le indica p si q. Sa scriem o alta versiune a functiei strlen folosind aceasta ultima observatie:

strlen(char *s) A /* returneaza lungimea unui sir */ char *p; p = s;
while (*p != '\0') p++; return p-s;
S

In acest exemplu s ramine constant cu adresa de inceput a sirului, in timp ce p avanseaza la urmatorul caracter de fiecare data. Diferenta p-s dintre adresa ultimului element al sirului si adresa primului element al sirului indica numarul de elemente.
In afara de operatiile binare mentionate (adunarea sau scaderea pointerilor cu intregi si scaderea sau compararea a doi pointeri), celelalte operatii cu pointeri sint ilegale. Nu este permisa adunarea, inmultirea, impartirea sau deplasarea pointerilor, dupa cum nici adunarea lor cu constante de tip double sau float.
Sint admise de asemenea incrementarile si decrementarile precum si alte combinatii ca de exemplu *++p si *--p.

9.5. Pointeri la caracter si functii

O constanta sir, de exemplu:
"Buna dimineata" este un masiv de caractere, care in reprezentarea interna este terminat cu caracterul '\0', astfel incit programul poate depista sfirsitul lui. Lungimea acestui sir in memorie este astfel cu 1 mai mare decit numarul de caractere ce apar efectiv intre ghilimelele de inceput si sfirsit de sir.
Cea mai frecventa aparitie a unei constante sir este ca argument la functii, caz in care accesul la ea se realizeaza prin intermediul unui pointer.
In exemplul: printf("Buna dimineata\n"); functia printf primeste de fapt un pointer la masivul de caractere.
In prelucrarea unui sir de caractere sint implicati numai pointeri, limbajul C neoferind nici un operator care sa trateze sirul de caractere ca o unitate de informatie.

Vom prezenta citeva aspecte legate de pointeri si masive analizind doua exemple. Sa consideram pentru inceput functia strcpy(s,t) care copiaza sirul t peste sirul s. O prima versiune a programului ar fi urmatoarea:

strcpy(char sai, char tai) A/* copiaza t peste s */ int t; i = 0;
while ((saii=taii) != '\0') i++;
S

O a doua versiune cu ajutorul pointerilor este urmatoarea:

strcpy(char *s, char *t) A
/* versiune cu pointeri */
while ((*s++=*t++) != '\0') ;
S

Aceasta versiune cu pointeri modifica prin incrementare pe s si t in partea de test. Valoarea lui *t++ este caracterul indicat de pointerul t, inainte de incrementare. Notatia postfix ++ asigura ca t va fi modificat dupa depunerea continutului indicat de el, la vechea pozitie a lui s, dupa care si s se incrementeaza. Efectul este ca se copiaza caracterele sirului t in sirul s pina la caracterul terminal '\0' inclusiv.
Am mai putea face o observatie legata de redundanta compararii cu caracterul '\0', redundanta care rezulta din structura instructiunii while.
Si atunci forma cea mai prescurtata a functiei strcpy(s,t) este:

strcpy(char *s, char *t) A
while (*s++ = *t++) ;
S

Sa consideram, ca al doilea exemplu, functia strcmp(s,t) care compara caracterele sirurilor s si t si returneaza o valoare negativa, zero sau pozitiva, dupa cum sirul s este lexicografic mai mic, egal sau mai mare ca sirul t. Valoarea returnata se obtine prin scaderea caracterelor primei pozitii in care s si t difera.
O prima versiune a functiei strcmp(s,t) este urmatoarea:

strcmp(char s, char t) A/* compara sirurile s si t */ int i; i = 0;
while (saii==taii) if (sai++i=='\0') return 0; return saii-taii;
S

Versiunea cu pointeri a aceleiasi functii este:

strcmp(char *s, char *t) A for (; *s==*t; s++,t++) if (*s=='\0') return 0; return *s-*t;
S

In final prezentam functia strsav care copiaza un sir dat prin argumentul ei intr-o zona obtinuta printr-un apel la functia alloc. Ea returneaza un pointer la sirul copiat sau NULL, daca nu mai exista suficient spatiu pentru memorarea sirului.

char *strsav(char *s) A /* copiaza sirul s */ char *p; p = alloc(strlen(s)+1); if (p!=NULL) strcpy(p,s); return p;
S

9.6. Masive multidimensionale

Limbajul C ofera facilitatea utilizarii masivelor multidimensionale, desi in practica ele sint folosite mai putin decit masivele de pointeri.
Sa consideram problema conversiei datei, de la zi din luna, la zi din an si invers, tinind cont de faptul ca anul poate sa fie bisect sau nu. Definim doua functii care sa realizeze cele doua conversii.
Functia day_of_year converteste ziua si luna in ziua anului si functia month_day converteste ziua anului in luna si zi.
Ambele functii au nevoie de aceeasi informatie si anume un tabel cu numarul zilelor din fiecare luna. Deoarece numarul zilelor din luna difera pentru anii bisecti de cele pentru anii nebisecti este mai usor sa consideram un tabel bidimensional in care prima linie sa corespunda numarului de zile ale lunilor pentru anii nebisecti, iar a doua linie sa corespunda numarului de zile pentru anii bisecti. In felul acesta nu trebuie sa tinem o evidenta in timpul calculului a ceea ce se intimpla cu luna februarie. Atunci masivul bidimensional care contine informatiile pentru cele doua functii este urmatorul:

static int day_taba2ia13i = A
A0,31,28,31,30,31,30,31,31,30,31,30,31S,
A0,31,29,31,30,31,30,31,31,30,31,30,31S
S;

Masivul day_tab trebuie sa fie declarat extern pentru a putea fi folosit de ambele functii.
In limbajul C, prin definitie, un masiv cu doua dimensiuni este in realitate un masiv cu o dimensiune ale carui elemente sint masive. De aceea indicii se scriu sub forma aiiaji in loc de ai,ji, cum se procedeaza in cele mai multe limbaje. Un masiv bidimensional poate fi tratat in acelasi fel ca si in celelalte limbaje, in sensul ca elementele sint memorate pe linie, adica indicele cel mai din dreapta variaza cel mai rapid.
Un masiv se initializeaza cu ajutorul unei liste de initializatori inchisi intre acolade; fiecare linie a unui masiv bidimensional se initializeaza cu ajutorul unei subliste de initializatori. In cazul exemplului nostru, masivul day_tab incepe cu o coloana zero, pentru ca numerele lunilor sa fie intre 1 si 12 si nu intre 0 si 11, aceasta pentru a nu face modificari in calculul indicilor.
Si atunci functiile care realizeaza conversiile cerute de exemplul nostru sint:

day_of_year (int year, int month, int day)
A /* ziua anului din luna si zi */ int i, leap; leap = (year%4==0) && (year%100!=0) ||
(year%400==0); for (i=1; i<month; i++) day += day_tabaleapiaii; return day;
S

Deoarece variabila leap poate lua ca valori numai zero sau unu dupa cum expresia:

(year%4==0) && (year%100!=0) ||
(year%400==0)

este falsa sau adevarata, ea poate fi folosita ca indice de linie in tabelul day_tab care are doar doua linii in exemplul nostru.

month_day(int year, int yearday, int *pmonth, int *pday) A int i,leap; leap = (year%4==0) && (year%100!=0) ||
(year%400==0); for (i=1; yearday>day_tabaleapiaii; i++) yearday -= day_tabaleapiaii;
*pmonth = i;
*pday = yearday;
S

Deoarece aceasta ultima functie returneaza doua valori, argumentele luna si zi vor fi pointeri.
Exemplu: month_day(1984,61,&m,&d) va incarca pe m cu 3, iar pe d cu 1 (adica 1 martie).
Daca un masiv bidimensional trebuie transmis unei functii, declaratia argumentelor functiei trebuie sa includa dimensiunea coloanei. Dimensiunea liniei nu este necesar sa apara in mod obligatoriu, deoarece ceea ce se transmite de fapt este un pointer la masive de cite 13 intregi, in cazul exemplului nostru. Astfel, daca masivul day_tab trebuie transmis unei functii f, atunci declaratia lui f poate fi: f(int (*day_tab)a13i) unde declaratia (*day_tab)a13i) indica faptul ca argumentul lui f este un pointer la un masiv de 13 intregi.
In general deci, un masiv d-dimensional aaiiaji...api de rangul i*j*...*p este un masiv d?1 - dimensional de rangul j*k*...*p ale carui elemente, fiecare, sint masive d?2 - dimensionale de rang k*...*p ale carui elemente, fiecare, sint masive d?3 - dimensionale s.a.m.d. Oricare dintre expresiile aaii, aaiiaji..., aaiiaji... api pot aparea in expresii. Prima are tipul masiv, ultima are tipul int, de exemplu, daca masivul este de tipul int. Vom mai reveni asupra acestei probleme cu detalii.

9.7. Masive de pointeri si pointeri la pointeri

Deoarece pointerii sint variabile, are sens notiunea de masiv de pointeri. Vom ilustra modul de lucru cu masive de pointeri pe un exemplu.
Sa scriem un program care sa sorteze lexicografic liniile de lungimi diferite ale unui text, linii care spre deosebire de intregi nu pot fi comparate sau schimbate printr-o singura operatie.
Daca memoram liniile textului una dupa alta intr-un masiv lung de caractere (gestionat de functia alloc), atunci fiecare linie poate fi accesibila cu ajutorul unui pointer la primul ei caracter. Pointerii tuturor liniilor, la rindul lor, pot fi memorati sub forma unui masiv. Atunci doua linii de text pot fi comparate transmitind pointerii lor functiei strcmp. Daca doua linii care nu respecta ordinea trebuie sa fie schimbate, se schimba doar pointerii lor din masivul de pointeri si nu textul efectiv al liniilor.

Procesul de sortare il vom realiza in trei pasi:
1) se citesc toate liniile textului de la intrare;
2) se sorteaza liniile in ordine lexicografica;
3) se tiparesc liniile sortate in noua ordine.

Vom scrie programul prin functiile sale, fiecare functie realizind unul din cei trei pasi de mai sus. O rutina principala va controla cele trei functii. Ea are urmatorul cod:

#define LINES 100 /* nr maxim de linii de sortat */ main() A /* sorteaza liniile de la intrare */ char *lineptraLINESi; /* pointeri la linii */ int nlines; /* nr linii intrare citite */ if ((nlines=readlines(lineptr,LINES))>=0)
A sort(lineptr,nlines);
writelines(lineptr,nlines);
S else printf
("Intrarea prea mare pentru sort\n");
S

Cele 3 functii care realizeaza intregul proces sint: readlines, sort si writelines.

Rutina de intrare readlines trebuie sa memoreze caracterele fiecarei linii si sa construiasca un masiv de pointeri la liniile citite. Trebuie, de asemenea, sa numere liniile din textul de la intrare, deoarece aceasta informatie este necesara in procesul de sortare si de imprimare. Intrucit functia de intrare poate prelucra numai un numar finit de linii de intrare, ea poate returna un numar ilegal, cum ar fi ?1, spre a semnala ca numarul liniilor de intrare este prea mare pentru capacitatea de care dispune.
Atunci functia readlines care citeste liniile textului de la intrare este urmatoarea:

#define MAXLEN 1000
#define NULL 0
#define EOF -1 readlines(char *lineptrai, int maxlines) A
/* citeste liniile */ int len,nlines; char *p,*alloc(),lineaMAXLENi; nlines = 0;
while ((len=getline(line,MAXLEN))>0) if (nlines>=maxlines) return -1; else if ((p=alloc(len))==NULL) return -1; else A linealen-1i = '\0'; strcpy(p,line); lineptranlines++i = p;
S return nlines;
S

Instructiunea linealen-1i = '\0'; sterge caracterul ?LF? de la sfirsitul fiecarei linii ca sa nu afecteze ordinea in care sint sortate liniile si depune in locul lui caracterul '\0' ca marca de sfirsit de sir.

Rutina care tipareste liniile in noua lor ordine este writelines si are urmatorul cod:

writelines(char *lineptrai, int nlines) A
/* scrie liniile sortate */ int i; for (i=0; i<nlines; i++) printf("%s\n",lineptraii);
S

Declaratia noua care apare in aceste programe este: char *lineptraLINESi; care indica faptul ca lineptr este un masiv de LINES elemente, fiecare element al masivului fiind un pointer la un caracter. Astfel lineptraii este un pointer la un caracter, iar *lineptraii permite accesul la caracterul respectiv.
Deoarece lineptr este el insusi un masiv, care se transmite ca argument functiei writelines, el va fi tratat ca un pointer (vezi sectiunea 9.3) si atunci functia writelines mai poate fi scrisa si astfel:

writelines(char *lineptrai, int nlines) A
while (--nlines>=0) printf("%s\n",*lineptr++);

S

In functia printf, lineptr indica initial prima linie de imprimat; fiecare incrementare avanseaza pe *lineptr la urmatoarea linie de imprimat, in timp ce nlines se micsoreaza dupa fiecare tiparire a unei linii cu 1.

Functia care realizeaza sortarea efectiva a liniilor se bazeaza pe algoritmul de injumatatire si are urmatorul cod:

#define NULL 0
#define LINES 100 /* nr maxim de linii de sortat */ sort(char *vai, int n) A
/* sorteaza sirurile v0, v1, ... vn-1 in ordine crescatoare */ int gap,i,j; char *temp; for (gap=n/2; gap>0; gap/=2) for (i=gap; i<n; i++) for (j=i-gap; j>=0; j-=gap) A if (strcmp(vaji,vaj+gapi)<=0) break; temp = vaji; vaji = vaj+gapi; vaj+gapi = temp;
S
S

Deoarece fiecare element al masivului v (care este de fapt masivul lineptr) este un pointer la primul caracter al unei linii, variabila temp va fi si ea un pointer la un caracter, deci operatiile de atribuire din ciclu dupa variabila j sint admise si ele realizeaza reinversarea pointerilor la linii daca ele nu sint in ordinea ceruta.

Sa retinem deci urmatoarele lucruri legate de masive si pointeri. De cite ori apare intr-o expresie un identificator de tip masiv el este convertit intr-un pointer la primul element al masivului. Prin definitie, operatorul de indexare ai este interpretat astfel incit E1aE2i este identic cu *((E1)+(E2)). Daca E1 este un masiv, iar E2 un intreg, atunci E1aE2i se refera la elementul de indice E2 al masivului E1.
O regula corespunzatoare se aplica si masivelor multi-dimensionale. Daca E1 este un masiv d-dimensional, de rangul i*j*...*k, atunci ori de cite ori e1 apare intr-o expresie, e1 va fi convertit intr-un pointer la un masiv d?1 - dimensional de rangul j*...*k, ale carui elemente sint masive. Daca operatorul * se aplica acestui pointer, rezultatul este masivul d?1 - dimensional, care se va converti imediat intr-un pointer la un masiv d?2 - dimensional s.a.m.d. Rationamentul se poate aplica in mod inductiv pina cind, in final, ca urmare a aplicarii operatorului * se obtine ca rezultat un intreg, de exemplu, daca masivul a fost declarat de tipul int.

Sa consideram, de exemplu, masivul: int xa3ia5i; x este un masiv de intregi, de rangul 3*5. Cind x apare intr-o expresie, el este convertit intr-un pointer la (primul din cele trei) masive de 5 intregi.

In expresia xaii, care este echivalenta cu expresia *(x+i), x este convertit intr-un pointer la un masiv, ale carui elemente sint la rindul lor masive de 5 elemente; apoi i se converteste la tipul x, adica indicele i se inmulteste cu lungimea elementului pe care il indica x (adica 5 intregi) si apoi rezultatele se aduna. Se aplica operatorul * pentru obtinerea masivului i (de 5 intregi) care la rindul lui este convertit intr-un pointer la primul intreg din cei cinci.
Se observa deci ca primul indice din declaratia unui masiv nu joaca rol in calculul adresei.

9.8. Initializarea masivelor si masivelor de pointeri

Initializatorul unei variabile declarate masiv consta dintr-o lista de initializatori separati prin virgula si inchisi intre acolade, corespunzatori tuturor elementelor masivului. Ei sint scrisi in ordinea crescatoare a indicilor masivului. Daca masivul contine sub-masive atunci regula se aplica recursiv membrilor masivului. Daca in lista de initializare exista mai putini initializatori decit elementele masivului, restul elementelor neinitializate se initializeaza cu zero. Nu se admite initializarea unui masiv de clasa cu automatic.
Acoladele A si S se pot omite in urmatoarele situatii:
-; daca initializatorul incepe cu o acolada stinga (A), atunci lista de initializatori, separati prin virgula, va initializa elementele masivului; nu se accepta sa existe mai multi initializatori decit numarul elementelor masivului;
-; daca insa initializatorul nu incepe cu acolada stinga (A), atunci se iau din lista de initializatori atitia initializatori citi corespund numarului de elemente ale masivului, restul initializatorilor vor initializa urmatorul membru al masivului, care are ca parte (sub-masiv) masivul deja initializat.
Un masiv de caractere poate fi initializat cu un sir, caz in care caracterele succesive ale sirului initializeaza elementele masivului.

Exemple:

1) int xai = A1,3,5S;
Aceasta declaratie defineste si initializeaza pe x ca un masiv unidimensional cu trei elemente, in ciuda faptului ca nu s-a specificat dimensiunea masivului. Prezenta initializatorilor inchisi intre acolade determina dimensiunea masivului.

2) Declaratia int ya4ia3i=A
A1,3,5S,
A2,4,6S,
A3,5,7S,
S; este o initializare complet inchisa intre acolade. Valorile 1,3,5 initializeaza prima linie a masivului ya0i si anume pe ya0ia0i, ya0ia1i, ya0ia2i. In mod analog urmatoarele doua linii initializeaza pe ya1i si ya2i. Deoarece initializatorii sint mai putini decit numarul elementelor masivului, linia ya3i se va initializa cu zero, respectiv elementele ya3ia0i, ya3ia1i, ya3ia2i vor avea valorile zero.

3) Acelasi efect se poate obtine din declaratia: int ya4ia3i = A1,3,5,2,4,6,3,5,7S; unde initializatorul masivului y incepe cu acolada stinga in timp ce initializatorul pentru masivul ya0i nu, fapt pentru care primii trei initializatori sint folositi pentru initializarea lui ya0i, restul initializatorilor fiind folositi pentru initializarea masivelor ya1i si respectiv ya2i.

4) Declaratia: int ya4ia3i = A
A1S,A2,S,A3,S,A4S
S; initializeaza masivul ya0i cu (1,0,0), masivul ya1i cu (2,0,0), masivul ya2i cu (3,0,0) si masivul ya4i cu (4,0,0).

5) Declaratia: static char msgai = "Eroare de sintaxa"; initializeaza elementele masivului de caractere msg cu caracterele succesive ale sirului dat.

In ceea ce priveste initializarea unui masiv de pointeri sa consideram urmatorul exemplu.
Fie functia month_name care returneaza un pointer la un sir de caractere care indica numele unei luni a anului. Functia data contine un masiv de siruri de caractere si returneaza un pointer la un astfel de sir, cind ea este apelata.
Codul functiei este urmatorul:

char *month_name(int n) A
/* returneaza numele lunii a n-a */ static char *nameai = A
"luna ilegala", "ianuarie",
"februarie", "martie", "aprilie",
"mai", "iunie", "iulie", "august",
"septembrie", "octombrie", "noiembrie",
"decembrie"
S return ((n<1) || (n>12)) ? namea0i : nameani ;
S

In acest exemplu, name este un masiv de pointeri la caracter, al carui initializator este o lista de siruri de caractere. Compilatorul aloca o zona de memorie pentru memorarea acestor siruri si genereaza cite un pointer la fiecare din ele pe care apoi ii introduce in masivul name. Deci nameaii va contine un pointer la sirul de caractere avind indice i al initializatorului. Dimensiunea masivului name nu este necesar a fi specificata deoarece compilatorul o calculeaza numarind initializatorii furnizati si o completeaza in declaratia masivului.

9.9. Masive de pointeri si masive multidimensionale

Adesea se creeaza confuzii in ceea ce priveste diferenta dintre un masiv bidimensional si un masiv de pointeri. Fie date declaratiile:

int aa10ia10i; int *ba10i;

In aceasta declaratie a este un masiv de intregi caruia i se aloca spatiu pentru toate cele 100 de elemente, iar calculul indicilor se face in mod obisnuit pentru a avea acces la oricare element al masivului.
Pentru masivul b, declaratia aloca spatiu numai pentru zece pointeri, fiecare trebuind sa fie incarcat cu adresa unui masiv de intregi.
Presupunind ca fiecare pointer indica un masiv de zece elemente inseamna ca ar trebui alocate inca o suta de locatii de memorie pentru elementele masivelor.
In aceasta acceptiune, folosirea masivelor a si b poate fi similara in sensul ca aa5ia5i si ba5ia5i, de exemplu, se refera ambele la unul si acelasi intreg (daca fiecare element baii este initializat cu adresa masivului aaii).
Astfel, masivul de pointeri utilizeaza mai mult spatiu de memorie decit masivele bidimensionale si pot cere un pas de initializare explicit. Dar masivele de pointeri prezinta doua avantaje, si anume: accesul la un element se face cu adresare indirecta, prin intermediul unui pointer, in loc de procedura obisnuita folosind inmultirea si apoi adunarea, iar al doilea avantaj consta in aceea ca dimensiunea masivelor pointate poate fi variabila. Acest lucru inseamna ca un element al masivului de pointeri b poate indica un masiv de zece elemente, altul un masiv de doua elemente si altul de exemplu poate sa nu indice nici un masiv.
Cu toate ca problema prezentata in acest paragraf am descris-o in termenii intregilor, ea este cel mai frecvent utilizata in memorarea sirurilor de caractere de lungimi diferite (ca in functia month_name prezentata mai sus).

9.10. Argumentele unei linii de comanda

In sistemul de calcul care admite limbajul C trebuie sa existe posibilitatea ca in momentul executiei unui program scris in acest limbaj sa i se transmita acestuia argumente sau parametri prin linia de comanda. Cind un program este lansat in executie si functia main este apelata, apelul va contine doua argumente. Primul argument (numit conventional argc) reprezinta numarul de argumente din linia de comanda care a lansat programul. Al doilea argument (argv) este un pointer la un masiv de pointeri la siruri de caractere care contin argumentele, cite unul pe sir.
Sa ilustram acest mod dinamic de comunicare intre utilizator si programul sau printr-un exemplu.
Fie programul numit pri care dorim sa imprime la terminal argumentele lui luate din linia de comanda, imprimarea facindu-se pe o linie, iar argumentele imprimate sa fie separate prin spatii.
Comanda: pri succes colegi va avea ca rezultat imprimarea la terminal a textului succes colegi
Prin conventie, argva0i este un pointer la numele pri al programului apelat, astfel ca argc, care specifica numarul de argumente din linia de comanda este cel putin 1.
In exemplul nostru, argc este 3, iar argva0i, argva1i si argva2i sint pointeri la "pri", "succes" si respectiv "colegi". Primul argument real este argva1i iar ultimul este argvaargc-1i. Daca argc este 1, inseamna ca linia de comanda nu are nici un argument dupa numele programului.

Atunci programul pri are urmatorul cod:

main(int argc, char *argvai) A
/* tipareste argumentele */ int i; for (i=1; i<argc; i++) printf("%s%%c",argvaii,(i<argc-1)?
' ':'\n');
S

Deoarece argv este un pointer la un masiv de pointeri, exista mai multe posibilitati de a scrie acest program. Sa mai scriem doua versiuni ale acestui program.

main(int argc, char *argvai) A
/* versiunea a doua */
while (--argc>0) printf("%s%c",*++argv,(argv>1)?
' ':'\n');
S

Deoarece argv este un pointer la un masiv de pointeri, incrementindu-l, (++argv), el va pointa la argva1i in loc de argva0i. Fiecare incrementare succesiva pozitioneaza pe argv la urmatorul argument, iar *argv este pointerul la argumentul sirului respectiv. In acelasi timp argc este decrementat pina devine zero, moment in care nu mai sint argumente de imprimat.
Alternativ:

main(int argc, char *argva i) A
/* versiunea a treia */
while (--argc>0) printf((argc>1)? "%s ":"%s\n",*++argv);
S

Aceasta versiune arata ca argumentul functiei printf poate fi o expresie ca oricare alta, cu toate ca acest mod de utilizare nu este foarte frecvent.
Ca un al doilea exemplu, sa reconsideram programul din sectiunea 7.5, care imprima fiecare linie a unui text care contine un sir specificat de caractere (schema).
Dorim acum ca aceasta schema sa poata fi modificata dinamic, de la executie la executie. Pentru aceasta o specificam printr-un argument in linia de comanda.
Si atunci programul care cauta schema data de primul argument al liniei de comanda este:

#define MAXLINE 1000 main(int argc, char *argva i) A
/* gaseste schema din primul argument */ char lineaMAXLINEi; if (argc!=2) printf("Linia de comanda eronata\n"); else
while (getline(line,MAXLINE)>0) if (index(line,argva1i)>=0) printf("%s",line);
S

unde linia de comanda este de exemplu: "find limbaj" in care "find" este numele programului, iar "limbaj" este schema cautata. Rezultatul va fi imprimarea tuturor liniilor textului de intrare care contin cuvintul "limbaj".
Sa elaboram acum modelul de baza, legat de linia de comanda si argumentele ei.
Sa presupunem ca dorim sa introducem in linia de comanda doua argumente optionale: unul care sa tipareasca toate liniile cu exceptia acelora care contin schema, si al doilea care sa preceada fiecare linie tiparita cu numarul ei de linie.
O conventie pentru programele scrise in limbajul C este ca argumentele dintr-o linie de comanda care incep cu un semn '-' sa introduca un parametru optional. Daca alegem, de exemplu, -x pentru a indica „cu exceptia” si -n pentru a cere „numararea liniilor”, atunci comanda: find -x -n la avind intrarea:

la miezul stinselor lumini s-ajung victorios, la temelii, la radacini, la maduva, la os.

va produce tiparirea liniei a doua, precedata de numarul ei, deoarece aceasta linie nu contine schema "la".
Argumentele optionale sint permise in orice ordine in linia de comanda. Analizarea si prelucrarea argumentelor unei linii de comanda trebuie efectuata in functia principala main, initializind in mod corespunzator anumite variabile. Celelalte functii ale programului nu vor mai tine evidenta acestor argumente.
Este mai comod pentru utilizator daca argumentele optionale sint concatenate, ca in comanda: find -xn la
Caracterele 'x' respectiv 'n' indica doar absenta sau prezenta acestor optiuni (switch) si nu sint tratate din punct de vedere al valorii lor.
Fie programul care cauta schema "la" in liniile de la intrare si le tipareste pe acelea, care nu contin schema, precedate de numarul lor de linie. Programul trateaza corect, atit prima forma a liniei de comanda cit si a doua.

#define MAXLINE 1000

main(int argc, char *argvai) A
/* cauta schema */ char lineaMAXLINEi, *s; long line0; int except, number; line0 = 0; number = 0;
while (--argc>0 && (*++argv)a0i=='-') for (s=argva0i+1; *s!='\0'; s++) switch(*s) A case 'x': except = 1; break; case 'n': number = 1; break; default: printf
("find: optiune ilegala %c\n",
*s); argc = 0; break;
S if (argc!=1) printf
("Nu exista argumente sau schema\n"); else
while (getline(line,MAXLINE)>0) A line0++; if ((index(line,*argv)>=0)!=except)
A if (number) printf("%d:",line0); printf("%s",line);
S
S
S

Daca nu exista erori in linia de comanda, atunci la sfirsitul primului ciclu while argc trebuie sa fie 1, iar *argv contine adresa schemei. *++argv este un pointer la un sir argument, iar (*++argv)a0i este primul caracter al sirului. In aceasta ultima expresie parantezele sint necesare deoarece fara ele expresia inseamna *++(argva0i) ceea ce este cu totul altceva (si gresit): al doilea caracter din numele programului. O alternativa corecta pentru (*++argva0i) este **++argv.
9.11. Pointeri la functii

In limbajul C o functie nu este o variabila, dar putem defini un pointer la o functie, care apoi poate fi prelucrat, transmis unor alte functii, introdus intr-un masiv si asa mai departe. Relativ la o functie se pot face doar doua operatii: apelul ei si considerarea adresei ei. Daca numele unei functii apare intr-o expresie, fara a fi urmat imediat de o paranteza stinga, deci nu pe pozitia unui apel la ea, atunci se genereaza un pointer la aceasta functie. Pentru a transmite o functie unei alte functii, ca argument, se poate proceda in felul urmator: int f(); g(f); unde functia f este un argument pentru functia g. Definitia functiei g va fi: g(int(*funcpt) ()) A
(*funcpt)();
S
Functia f trebuie declarata explicit in rutina apelanta (int f();), deoarece aparitia ei in g(f) nu a fost urmata de paranteza stinga ’(’. In expresia g(f) f nu apare pe pozitia de apel de functie. In acest caz, pentru argumentul functiei g se genereaza un pointer la functia f. Deci g apeleaza functia f printr-un pointer la ea.
Declaratiile din functia g trebuie studiate cu grija. int (*funcpt)(); spune ca funcpt este un pointer la o functie care returneaza un intreg. Primul set de paranteze este necesar, deoarece fara el int *funcpt();
inseamna ca funcpt este o functie care returneaza un pointer la un intreg, ceea ce este cu totul diferit fata de sensul primei expresii. Folosirea lui funcpt in expresia:
(*funcpt)(); indica faptul ca funcpt este un pointer la o functie, *funcpt este functia, iar (*funcpt)() este apelul functiei.
O forma echivalenta simplificata de apel este urmatoarea: funcpt();
Ca un exemplu, sa consideram procedura de sortare a liniilor de la intrare, descrisa in sectiunea 9.7, dar modificata in sensul ca daca argumentul optional -n apare in linia de comanda, atunci liniile se vor sorta nu lexicografic ci numeric, liniile continind grupe de numere.
O sortare consta adesea din trei parti: o comparare care determina ordinea oricarei perechi de elemente, un schimb care inverseaza ordinea elementelor implicate si un algoritm de sortare care face compararile si inversarile pina cind elementele sint aduse in ordinea ceruta. Algoritmul de sortare este independent de operatiile de comparare si inversare, astfel incit transmitind diferite functii de comparare si inversare functiei de sortare, elementele de intrare se pot aranja dupa diferite criterii.
Compararea lexicografica a doua linii se realizeaza prin functiile strcmp si swap. Mai avem nevoie de o rutina numcmp care sa compare doua linii pe baza valorilor numerice si care sa returneze aceiasi indicatori ca si rutina strcmp.
Declaram aceste trei functii in functia principala main, iar pointerii la aceste functii ii transmitem ca argumente functiei sort, care la rindul ei va apela aceste functii prin intermediul pointerilor respectivi.
Functia principala main va avea atunci urmatorul cod:

#define LINES 100 /* nr maxim de linii de sortat */

main (int argc, char *argvai) A char *lineptraLINESi; /* pointeri la linii text */ int nlines; /* numar de linii citite */ int strcmp(), numcmp(); /* functii de comparare */ int swap (); /* functia de inversare */ int numeric; numeric = 0; /* 1 daca sort numeric */ if (argc>1 && argva1ia0i=='-' && argva1ia1i=='n') numeric = 1; if ((nlines=readlines(lineptr,LINES))>=0)
A if (numeric) sort(lineptr,nlines,numcmp,swap); else sort(lineptr,nlines,strcmp,swap);
writelines (lineptr,nlines);
S else printf
("Nr de linii de intrare prea mare\n");
S

In apelul functiei sort, argumentele strcmp, numcmp si swap sint adresele functiilor respective. Deoarece ele au fost declarate functii care returneaza un intreg, operatorul ’&’ nu este necesar sa preceada numele functiilor, compilatorul fiind cel care gestioneaza transmiterea adreselor functiilor.
Functia sort care aranjeaza liniile in ordinea crescatoare se va modifica astfel:

sort(char *vai, int n, int (*comp)(), int (*exch)()) A /* sorteaza v0, v1, ... , vn?1 */ int gap,i,j; for (gap=n/2; gap>0; gap/=2) for (i=gap; i<n; i++) for (j=i-gap; j>=0; j-=gap) A if (comp(vaji,vaj+gapi)<=0) break; exch(v+j,v+j+gap);
S
S

Sa studiem declaratiile din aceasta functie. int(*comp)(), (*exch)(); indica faptul ca comp si exch sint pointeri la functii care returneaza un intreg (primul set de paranteze este necesar). if (comp(vaji,vaj+gapi)<=0)
inseamna apelul functiei comp (adica strcmp sau numcmp), deoarece comp este un pointer la functie, *comp este functia, iar comp(vaji,vaj+gapi) este apelul functiei. exch(v+j,v+j+gap) este apelul functiei swap, de inversare a doua linii, inversare care realizeaza interschimbarea adreselor liniilor implicate (vezi sectiunea 9.2). Functia numcmp este urmatoarea:

numcmp(char *s1, char *s2) A
/* compara s1 si s2 numeric */ double atof(),v1,v2; v1 = atof(s1); v2 = atof(s2); if (v1<v2) return -1; else if (v1>v2) return 1; else return 0;
S

Pentru ca programul nostru sa fie complet sa mai prezentam si codul functiei swap, care schimba intre ei pointerii a doua linii.

swap(char *pxai, char *pyai) A char *temp; temp = *px;
*px = *py;
*py = temp;
S


Colt dreapta
Creeaza cont
Comentarii:

Nu ai gasit ce cautai? Crezi ca ceva ne lipseste? Lasa-ti comentariul si incercam sa te ajutam.
Esti satisfacut de calitarea acestui document, eseu, cometariu? Apreciem aprecierile voastre.

Nume (obligatoriu):

Email (obligatoriu, nu va fi publicat):

Site URL (optional):


Comentariile tale: (NO HTML)


Noteaza documentul:
In prezent fisierul este notat cu: ? (media unui numar de ? de note primite).

2345678910

 
Copyright© 2005 - 2024 | Trimite document | Harta site | Adauga in favorite
Colt dreapta