În urma parcurgerii acestui laborator, studentul va fi capabil:
Un pointer este o variabilă care reţine o adresă de memorie.
În C, un pointer poate reprezenta:
sizeof(void *)
și nu este în mod necesar egală cu dimensiunea unui tip de date întreg.
În cadrul laboratorului și al temelor de casă se vor utiliza mașini și pointeri pe 32 de biți.
&
- apare în fața variabilei asupra căreia acționează
Este aplicat unei variabile, avand ORICE tip de date, și obține ADRESA de (din) memorie a variabilei respective.
*
- apare în fața variabilei asupra căreia acționează
Este aplicat unei variabile de tip pointer și obține valoarea stocată la adresa respectivă (indicata de pointer).
Pentru ca dereferențierea să aibă loc cu succes, pointer-ul trebuie să indice o adresă de memorie validă, la care programul are acces. Această adresă poate fi adresa unei variabile declarate în prealabil sau adresa unui bloc de memorie alocat dinamic (după cum vom vedea mai departe).
Este indicată inițializarea pointerilor cu constanta NULL, compatibilă cu orice tip de pointer, care indica, prin convenție, un pointer neinițializat.
int *a; /* Pointer */ int b = 5; /* Variabila */ char *c; /* Pointer catre un caracter (sau sir de caractere) */ void *buff = NULL; /* Pointer catre void, initializat la NULL */ /* Asignare NEVALIDA; a este un pointer neinitializat */ *a = 1; /* a ia adresa variabilei b */ a = &b; /* Continutul memoriei de la adresa a (care a fost initializata mai sus) ia valoarea 5. Acest lucru este echivalent cu "b = 5;" */ *a = 5;
În cazul declaraţiilor de pointeri, operatorul *
este asociat numelui variabilei, şi nu numelui tipului, astfel că, pentru o declaraţie de mai multe variabile, operatorul *
trebuie să apară pentru fiecare variabilă în parte şi este recomandat ca şi formatarea codului să indice această asociere. De exemplu:
/* sir1 e pointer, sir2 si sir3 sunt caractere */ char *sir1, sir2, sir3; /* a, b si c sunt pointeri */ int *a, *b, *c; /* Doar a este pointer; formatarea codului este nerecomandata */ char* a, b;
Operatorul *
poate fi folosit şi în specificarea numelui unui tip (de exemplu în cazul unui cast
), şi în acest caz el apare după numele tipului. De exemplu:
void *var = NULL; int *num = (int *)var; // Operatie valida, dar riscanta
void
nu poate fi folosit direct în operaţii cu pointeri, ci trebuie convertit mai întâi la un pointer către un tip de date.
void *mem; //[...] *mem = 10; // Operatie ILEGALA ((int *)mem) = 10; // Operatie legala, dar riscanta
*p = y; // Ia valoarea y si pune-o la adresa indicata de p x = *p; // Ia valoarea de la adresa indicata de p si pune-o in variabila x *s1++ = *s2++;
p1 = p2; p = NULL; p = malloc(n); // Veti studia malloc() intr-un laborator ulterior
int n; short s1, s2; s1 = *((short*)&n); // Extrage primul cuvant din intregul n s2 = *((short*)&n + 1); // Extrage cel de-al doilea cuvant din intregul n
Adunarea sau scăderea unui întreg la un pointer, incrementarea sau decrementarea unui pointer. Aceste operaţii lucrează în multipli de dimensiunea tipului de date la care pointerii se referă, pentru a permite accesul la memorie ca într-un vector (a se vedea laboratorul de tablouri). De exemplu:
int *num; /* Aduna la adresa initiala dimensiunea tipului de date referit de pointer (pe sizeof(int)), dand acces la urmatorul intreg care ar fi stocat daca zona aceea de memorie ar fi organizata sub forma unui vector */ num++; /* Incrementeaza adresa cu 5 * sizeof(int) */ num = num + 5;
După cum a fost prezentat în laboratorul de vectori, o variabilă vector conţine adresa de început a vectorului (adresa primei componente a vectorului), şi de aceea este echivalentă cu un pointer la tipul elementelor din vector. Această echivalenţă este exploatată, de obicei, în argumentele de tip vector şi în lucrul cu vectori alocaţi dinamic. De exemplu, pentru declararea unei funcţii care primeşte un vector de întregi şi dimensiunea lui, avem două posibilităţi:
void printVec(int a[], int n);
sau
void printVec(int *a, int n);
În interiorul funcţiei ne putem referi la elementele vectorului a
fie prin indici, fie prin indirectare, indiferent de felul cum a fost declarat parametrul vector a
:
void printVec (int a[], int n) { int i; for (i = 0; i < n; i++) printf("%6d", a[i]); // Indexare }
sau
void printVec (int *a, int n) { int i; for (i = 0; i < n; i++) printf("%6d", *a++); // Indirectare }
Astfel, există următoarele echivalenţe de notaţii pentru un vector a
:
a[0] <==> *a a[1] <==> *(a + 1) a[k] <==> *(a + k) &a[0] <==> a &a[1] <==> a + 1 &a[k] <==> a + k
Diferenţa dintre o variabilă pointer şi un nume de vector este aceea că un nume de vector este un pointer constant (adresa sa este alocată de către compilatorul C şi nu mai poate fi modificată la execuţie), deci nu poate apărea în stânga unei atribuiri, în timp ce o variabilă pointer are un conţinut modificabil prin atribuire sau prin operaţii aritmetice. De exemplu:
int a[100], *p; p = a; ++p; //corect a = p; ++a; //EROARE
De asemenea, o variabilă de tip vector conţine şi informaţii legate de lungimea vectorului şi dimensiunea totală ocupată în memorie, în timp ce un pointer doar descrie o poziţie în memorie (e o valoarea punctuală). Operatorul sizeof(v)
pentru un vector v[N]
de tipul T
va fi N * sizeof(T)
, în timp ce sizeof(v)
pentru o variabila v
de tipul T *
va fi sizeof(T *)
, adică dimensiunea unui pointer.
Ca o ultimă notă, este importat de remarcat că o funcţie poate avea ca rezultat un pointer, dar nu poate avea ca rezultat un vector.
În cadrul funcţiilor, pointerii pot fi folosiţi, printre altele, pentru:
O funcţie care trebuie să modifice mai multe valori primite prin argumente sau care trebuie să transmită mai multe rezultate calculate în cadrul funcţiei trebuie să folosească argumente de tip pointer.
De exemplu, o funcţie care primeşte ca parametru un număr, pe care il modifica:
// Functie care incrementeaza un intreg n modulo m int incmod (int *n, int m) { return ++(*n) % m; } // Utilizarea functiei int main() { int n = 10; int m = 15; incmod(&n, m); // Afisam noua valoare a lui n printf("n: %d", n); return 0;
O funcţie care trebuie să modifice două sau mai multe argumente, le va specifica pe acestea individual, prin câte un pointer, sau într-un mod unificat, printr-un vector, ca în exemplul următor:
void inctime (int *h, int *m, int *s); // sau void inctime (int t[3]); // t[0]=h, t[1]=m, t[2]=s
O funcţie poate avea ca rezultat un pointer, dar acest pointer nu trebuie să conţină adresa unei variabile locale. De obicei, rezultatul pointer este egal cu unul din argumente, eventual modificat în funcţie. De exemplu:
// Incrementare pointer p char *incptr(char *p) { return ++p; }
O variabila locală are o existenţă temporară, garantată numai pe durata execuţiei funcţiei în care este definită (cu excepţia variabilelor locale statice), şi de aceea adresa unei astfel de variabile nu trebuie transmisă în afara funcţiei, pentru a fi folosită ulterior. De exemplu, următoarea secvenţă de cod este greşită
:
// Vector cu cifrele unui nr intreg int * cifre (int n) { int k, c[5]; // Vector local for (k = 4; k >= 0; k--) { c[k] = n % 10; n = n / 10; } return c; // Aici este eroarea ! }
Astfel, o funcţie care trebuie să transmită ca rezultat un vector poate fi scrisă corect în două feluri:
malloc
), iar această alocare se menţine şi la ieşirea din funcţie.Primul exercițiu presupune rularea unei secvente de cod cu scopul de a clarifica diverse aspecte legate de pointeri. Analizați fiecare intrebare si incercati sa intuiti ce ar trebui sa se afiseze in continuare. După aceea verificați
#include <stdio.h> void next(void) { fflush(stdout); getchar(); } int main(void) { int a[10]; printf("a[0] = ?"); next(); printf("a[0] = %d\n", a[0]); next(); printf("*a = ?"); next(); printf("*a = %d\n", *a); next(); printf("a = ?"); next(); printf("a = %p\n", a); next(); printf("&a = ?"); next(); printf("&a = %p\n", &a); next(); printf("sizeof(a[0]) = ?"); next(); printf("sizeof(a[0]) = %ld\n", sizeof(a[0])); next(); printf("sizeof(*a) = ?"); next(); printf("sizeof(*a) = %ld\n", sizeof(*a)); next(); printf("sizeof(a) = ?"); next(); printf("sizeof(a) = %ld\n", sizeof(a)); next(); printf("sizeof(&a) = ?"); next(); printf("sizeof(&a) = %ld\n", sizeof(&a)); next(); printf("*a + 1 = ?"); next(); printf("*a + 1 = %d\n", *a + 1); next(); printf("*a + 2 = ?"); next(); printf("*a + 2 = %d\n", *a + 2); next(); printf("a + 1 = ?"); next(); printf("a + 1 = %p\n", a + 1); next(); printf("a + 2 = ?"); next(); printf("a + 2 = %p\n", a + 2); next(); printf("&a + 1 = ?"); next(); printf("&a + 1 = %p\n", &a + 1); next(); printf("&a + 2 = ?"); next(); printf("&a + 2 = %p\n", &a + 2); next(); return 0; }
Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.
int str_length(char *s);
char * strdel(char *s, int pos, int n);
s2
într-o poziţie dată pos
dintr-un şir s1
. Se va presupune că există suficient loc în vectorul lui s1
pentru a face loc şirului s2
. Funcţia returnează adresa şirului s1
.char * strins(char *s1, int pos, char *s2)
int eq_mask(char *sir, char *masca);
int eqcuv(char *cuv, char **tablou);
Pentru testare folosiți următoarea funcție main
:
int main(void) { char *tablou[100] = {"curs1", "curs2", "curs3"}; char *cuv1 = "curs2", *cuv2 = "curs5"; printf("curs2 %s in tablou\n",(eqcuv(cuv1, tablou)) ? "este" : "nu este"); printf("curs5 %s in tablou\n",(eqcuv(cuv2, tablou)) ? "este" : "nu este"); }
sqrt
, sin
, cos
, tan
, exp
şi log
, în intervalul [1..10], cu pasul 0.1. În acest scop, se creează un tablou de pointeri la aceste funcţii şi se apelează funcţiile în mod indirect prin aceşti pointeri.