This shows you the differences between two versions of the page.
|
programare:laboratoare:lab08 [2017/11/14 14:21] ovidiu.nitu [Exerciții Laborator CB/CD] |
programare:laboratoare:lab08 [2025/11/19 20:25] (current) teodor.birleanu [PCLP Laborator08: Tipuri de date definite de utilizator: structuri, uniuni, enumuri] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Pointeri. Abordarea lucrului cu tablouri folosind pointeri. ===== | + | ===== PCLP Laborator08: Tipuri de date definite de utilizator: structuri, uniuni, enumuri ===== |
| - | **Responsabil:** [[laura.vasilescu@cti.pub.ro|Laura Vasilescu]] | ||
| + | **Resposabili:** | ||
| + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2016-2020)]] | ||
| + | * [[neatudarius@gmail.com|Darius Neațu (2019-2020)]] | ||
| + | * [[teodor.matei.birleanu@gmail.com|Bîrleanu Teodor Matei (2025 - prezent)]] | ||
| + | * [[mihaela.vasile@gmail.com|Mihaela Vasile]] | ||
| - | ===== Obiective ===== | + | ==== Obiective ==== |
| - | În urma parcurgerii acestui laborator, studentul va fi capabil: | + | În urma parcurgerii acestui laborator studentul va fi capabil să: |
| - | * să înţeleagă noţiunea de pointer şi modurile în care se poate opera cu memoria în limbajul C | + | * organizeze datele din rezolvarea unei probleme complexe în structuri şi uniuni; |
| - | * să cunoască modul în care pointerii sunt folosiţi în a returna sau modifica parametri în cadrul unei funcţii | + | * optimizeze scrierea funcţiilor prin minimizarea numărului de parametri şi prin utilizarea structurilor ca parametri returnaţi de funcţie; |
| - | * să înţeleagă noţiunea de pointer la o funcţie şi să-l folosească în situaţiile în care acesta este necesar; | + | * distingă diferenţa dintre o structură şi o uniune; |
| - | * să folosească funcţiile de alocare şi de eliberare a memoriei | + | * evite utilizarea greşită a structurilor. |
| - | {{ http://imgs.xkcd.com/comics/pointers.png}} | + | ==== Noţiuni teoretice ==== |
| + | === Structuri === | ||
| + | Structurile sunt tipuri de date în care putem grupa mai multe variabile eventual de tipuri diferite (spre deosebire de vectori, care conţin numai date de acelasi tip). O structură se poate defini astfel: | ||
| - | ===== Noţiunea de pointer ===== | ||
| - | |||
| - | Un **pointer** este o variabilă care reţine o adresă de memorie. | ||
| - | |||
| - | În C, un pointer poate reprezenta: | ||
| - | - adresa unor date de un anumit tip | ||
| - | * tip elementar, structură, şir de caractere etc. | ||
| - | * operațiile cu pointeri sunt determinate de dimensiunea tipului de date | ||
| - | - adresa unei funcții | ||
| - | * adresa la care punctul curent de execuţie va sări, în cazul în care acea funcţie este apelată | ||
| - | - adresa unei adrese de memorie | ||
| - | * acest tip de pointer poate fi redus la prima situaţie | ||
| - | - adresa unei zone cu conţinut necunoscut (pointer către void) | ||
| - | |||
| - | <note tip> | ||
| - | Dimensiunea unui pointer depinde de arhitectura și sistemul de operare pe care a fost compilat programul. Dimensiunea unui pointer se determină cu **''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. | ||
| - | </note> | ||
| - | |||
| - | ===== Operatori ===== | ||
| - | |||
| - | ==== Operatorul de referențiere ==== | ||
| - | |||
| - | **''&''** - 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. | ||
| - | |||
| - | ==== Operatorul de dereferențiere ==== | ||
| - | |||
| - | **''*''** - 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). | ||
| - | |||
| - | <note warning> | ||
| - | Declararea unui pointer nu înseamnă alocarea unei zone de memorie în care pot fi stocate date. Un pointer este tot un tip de date, a cărui valoare este un număr ce reprezintă o adresă de memorie. | ||
| - | </note> | ||
| - | |||
| - | 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 [[http://www.cplusplus.com/reference/cstddef/NULL/ | NULL]], compatibilă cu orice tip de pointer, care indica, prin convenție, un pointer neinițializat. | ||
| - | |||
| - | |||
| - | ==== Particularități ==== | ||
| <code c> | <code c> | ||
| - | int *a; /* Pointer */ | + | struct nume_structura { |
| - | int b = 5; /* Variabila */ | + | declaratii_de_variabile |
| - | 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; | + | |
| </code> | </code> | ||
| - | Î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: | + | Definiția unei structuri poate fi urmată imediat de declararea de variabile de acel tip, forma cea mai generală fiind: |
| <code c> | <code c> | ||
| - | /* sir1 e pointer, sir2 si sir3 sunt caractere */ | + | struct [structure tag] { |
| - | char *sir1, sir2, sir3; | + | |
| - | /* a, b si c sunt pointeri */ | + | member definition; |
| - | int *a, *b, *c; | + | member definition; |
| - | + | ... | |
| - | /* Doar a este pointer; formatarea codului este nerecomandata */ | + | member definition; |
| - | char* a, b; | + | } [one or more structure variables]; |
| </code> | </code> | ||
| - | 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: | + | Unde între ''[]'' se află câmpurile opționale. |
| - | <code c> | + | |
| - | void *var = NULL; | + | |
| - | int *num = (int *)var; // Operatie valida, dar riscanta | + | |
| - | </code> | + | |
| - | <note warning> | + | Exemple: |
| - | Un pointer către ''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. | + | |
| - | </note> | + | |
| - | De exemplu: | + | |
| <code c> | <code c> | ||
| - | void *mem; | + | struct student { |
| - | //[...] | + | char nume[40]; |
| - | *mem = 10; // Operatie ILEGALA | + | int an; |
| - | ((int *)mem) = 10; // Operatie legala, dar riscanta | + | float medie; |
| + | }; | ||
| </code> | </code> | ||
| - | ===== Tipuri de pointeri ===== | ||
| - | |||
| - | ==== Pointeri la date ==== | ||
| - | == Stocarea datelor la o anumită adresă; Citirea datelor de la o anumită adresă == | + | Variabilele declarate in interiorul structurii se numesc "campurile" structurii: |
| + | *nume; | ||
| + | *an; | ||
| + | *medie. | ||
| <code c> | <code c> | ||
| - | *p = y; // Ia valoarea y si pune-o la adresa indicata de p | + | struct complex { /* pentru memorarea unui număr complex cu dublă precizie */ |
| - | x = *p; // Ia valoarea de la adresa indicata de p si pune-o in variabila x | + | double re; |
| - | *s1++ = *s2++; | + | double im; |
| + | }; | ||
| </code> | </code> | ||
| - | == Atribuirea unei adrese unui pointer == | + | Declararea şi iniţializarea unor variabile de tip structură se poate face astfel: |
| <code c> | <code c> | ||
| - | p1 = p2; | + | struct student s1 = {"Popescu Ionel", 3, 9.25}; |
| - | p = NULL; | + | struct complex c1, c2; |
| - | p = malloc(n); // Veti studia malloc() intr-un laborator ulterior | + | struct complex v[10]; |
| </code> | </code> | ||
| - | == Interpretarea diferită a datelor din memorie == | + | Pentru simplificarea declaraţiilor, putem asocia unei structuri un nume de tip de date: |
| <code c> | <code c> | ||
| - | int n; | + | typedef struct student Student; |
| - | short s1, s2; | + | ... |
| - | s1 = *((short*)&n); // Extrage primul cuvant din intregul n | + | Student s1, s2, s3; |
| - | s2 = *((short*)&n + 1); // Extrage cel de-al doilea cuvant din intregul n | + | |
| </code> | </code> | ||
| - | == Aritmetică cu pointeri == | + | Redenumirea de tip de mai sus poate fi inclusă și în definirea structurii, caz în care putem folosi - din prima - atât ''struct Student'' cât și tipul de date rezultat din typedef: |
| - | 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 [[:programare:laboratoare:lab05|tablouri]]). De exemplu: | + | |
| <code c> | <code c> | ||
| - | int *num; | + | typedef struct student { |
| + | char nume[40]; | ||
| + | int an; | ||
| + | float medie; | ||
| + | } Student; | ||
| - | /* Aduna la adresa initiala dimensiunea tipului de date referit | + | int main() { |
| - | de pointer (pe sizeof(int)), dand acces la urmatorul intreg | + | /* Ambele declaratii de mai jos sunt valide */ |
| - | care ar fi stocat daca zona aceea de memorie ar fi organizata | + | struct student s1; |
| - | sub forma unui vector | + | Student s2; |
| - | */ | + | } |
| - | num++; | + | |
| - | + | ||
| - | /* Incrementeaza adresa cu 5 * sizeof(int) */ | + | |
| - | num = num + 5; | + | |
| </code> | </code> | ||
| - | ==== Pointeri la tablouri ==== | ||
| - | După cum a fost prezentat în [[programare:laboratoare:lab05|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: | + | |
| - | <code c> | + | Accesul la membrii unei structuri se face prin operatorul ".": |
| - | void printVec(int a[], int n); | + | |
| - | </code> | + | |
| - | sau | + | |
| <code c> | <code c> | ||
| - | void printVec(int *a, int n); | + | s1.an = 3; |
| </code> | </code> | ||
| - | Î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'': | + | În cazul pointerilor la structuri, accesul la membri se poate face astfel: |
| <code c> | <code c> | ||
| - | void printVec (int a[], int n) | + | Student *stud = (Student *)malloc(sizeof(Student)); |
| - | { | + | (*stud).medie = 9.31; |
| - | int i; | + | /* altă modalitate mai simplă şi mai des folosită: */ |
| - | for (i = 0; i < n; i++) | + | stud->medie = 9.31; |
| - | printf("%6d", a[i]); // Indexare | + | |
| - | } | + | |
| - | </code> | + | |
| - | sau | + | |
| - | <code c> | + | |
| - | void printVec (int *a, int n) | + | |
| - | { | + | |
| - | int i; | + | |
| - | for (i = 0; i < n; i++) | + | |
| - | printf("%6d", *a++); // Indirectare | + | |
| - | } | + | |
| </code> | </code> | ||
| - | Astfel, există următoarele echivalenţe de notaţii pentru un vector ''a'': | + | Atribuirile de structuri se pot face astfel: |
| <code c> | <code c> | ||
| - | a[0] <==> *a | + | struct complex n1, n2; |
| - | a[1] <==> *(a + 1) | + | ... |
| - | a[k] <==> *(a + k) | + | n2 = n1; |
| - | &a[0] <==> a | + | |
| - | &a[1] <==> a + 1 | + | |
| - | &a[k] <==> a + k | + | |
| </code> | </code> | ||
| - | 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: | + | Prin această atribuire se realizează o copiere bit cu bit a elementelor lui n1 în n2. |
| + | |||
| + | Alt exemplu de utilizare: După cum se vede mai jos trebuie facută diferenta când definim un tip şi cand declaram o variabila de tip struct sau typedef struct. | ||
| <code c> | <code c> | ||
| - | int a[100], *p; | + | typedef struct { |
| - | p = a; ++p; //corect | + | int data; |
| - | a = p; ++a; //EROARE | + | int text; |
| - | </code> | + | } S1; // este un typedef pentru S1, functional in C si C++ |
| - | 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. | + | struct S2 { |
| + | int data; | ||
| + | int text; | ||
| + | }; // este un typedef pentru struct S2 | ||
| - | ==== Pointeri în funcţii ==== | ||
| - | În cadrul funcţiilor, pointerii pot fi folosiţi, printre altele, pentru: | + | struct { |
| - | * Transmiterea de rezultate prin argumente | + | int data; |
| - | * Transmiterea unei adrese prin rezultatul funcţiei | + | int text; |
| - | * Utilizarea unor funcţii cu nume diferite (date prin adresele acestora) | + | } S3; |
| - | + | // este o declaratie a lui S3, variabila de tip struct nu defineste un tip | |
| - | 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. | + | // spune compilatorului sa aloce memorie pentru variablia S3 |
| + | |||
| + | typedef struct S4 { | ||
| + | int data; | ||
| + | int text; | ||
| + | } S4; // este un typedef atât pentru struct S4, cât și pentru S4 | ||
| - | De exemplu, o funcţie care primeşte ca parametru un număr, pe care il modifica: | ||
| - | <code c> | ||
| - | // Functie care incrementeaza un intreg n modulo m | ||
| - | int incmod (int *n, int m) { | ||
| - | return ++(*n) % m; | ||
| - | } | ||
| - | // Utilizarea functiei | ||
| int main() { | int main() { | ||
| - | int n = 10; | + | // ce se intampla la declarare variabile de tip S1,S2,S3 |
| - | int m = 15; | + | S1 mine1; // este un typedef si va merge |
| - | + | struct S2 mine2; // este un typedef si va merge | |
| - | incmod(&n, m); | + | S2 mine22; // S2 NU este un typedef si NU va merge |
| - | // Afisam noua valoare a lui n | + | S3 mine3; // nu va merge pt ca S3 nu este un typedef. |
| - | printf("n: %d", n); | + | struct S4 mine4; // este un typedef și va merge |
| + | S4 mine4; // este un typedef și va merge | ||
| | | ||
| + | // ce se intampla la utilizarea ca variabile a S1,S2,S3 | ||
| + | S1.data = 5; // da eroare deoarece S1 este numai un typedef. | ||
| + | struct S2.data = 5; // da eroare deoarece S2 este numai un typedef. | ||
| + | S3.data = 5; // merge doarece S3 e o variabila | ||
| return 0; | return 0; | ||
| + | } | ||
| </code> | </code> | ||
| - | 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: | + | <note>Dacă declaraţi pointeri la structuri, nu uitaţi să alocaţi memorie pentru aceştia înainte de a accesa câmpurile structurii. Nu uitaţi să alocaţi şi câmpurile structurii, care sunt pointeri, înainte de utilizare, dacă este cazul. De asemenea fiţi atenţi şi la modul de accesare al câmpurilor. |
| - | <code c> | + | </note> |
| - | void inctime (int *h, int *m, int *s); | + | |
| - | // sau | + | |
| - | void inctime (int t[3]); // t[0]=h, t[1]=m, t[2]=s | + | |
| - | </code> | + | |
| - | 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: | + | === Diferenţa dintre copierea structurilor şi copierea pointerilor === |
| - | <code c> | + | |
| - | // Incrementare pointer p | + | |
| - | char *incptr(char *p) { | + | |
| - | return ++p; | + | |
| - | } | + | |
| - | </code> | + | |
| - | 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ă'': | + | Pentru exemplificarea diferenţei dintre copierea structurilor şi copierea pointerilor să considerăm urmatorul exemplu: |
| <code c> | <code c> | ||
| - | // Vector cu cifrele unui nr intreg | + | struct exemplu { |
| - | int * cifre (int n) { | + | int n; |
| - | int k, c[5]; // Vector local | + | char *s; |
| - | + | ||
| - | for (k = 4; k >= 0; k--) { | + | |
| - | c[k] = n % 10; | + | |
| - | n = n / 10; | + | |
| - | } | + | |
| - | + | ||
| - | return c; // Aici este eroarea ! | + | |
| } | } | ||
| + | struct exemplu s1, s2; | ||
| + | char *litere = "abcdef"; | ||
| + | s1.n = 5; | ||
| + | s1.s = strdup(litere); | ||
| + | s2 = s1; | ||
| + | s2.s[1]='x'; | ||
| </code> | </code> | ||
| - | Astfel, o funcţie care trebuie să transmită ca rezultat un vector poate fi scrisă corect în două feluri: | + | După atribuirea s2 = s1;, s2.s va avea o valoare identică cu s1.s. Deoarece s este un pointer (o adresă de memorie), s2.s va indica aceeaşi adresa de memorie ca şi s1.s. Deci, după modificarea celui de-al doilea caracter din s2.s, atat s2.s cât si s1.s vor fi axcdef. De obicei acest efect nu este dorit şi nu se recomandă atribuirea de structuri atunci cand acestea contin pointeri. |
| - | * Primeşte ca argument adresa vectorului (definit şi alocat în altă funcţie) şi depune rezultatele la adresa primită (soluţia recomandată!); | + | |
| - | * Alocă dinamic memoria pentru vector (folosind ''malloc''), iar această alocare se menţine şi la ieşirea din funcţie. | + | |
| - | ==== Pointeri la funcţii ==== | + | Totuşi, putem atribui ulterior lui s2.s o altă valoare (o altă adresă), iar ca urmare a acestei operaţii, stringurile vor fi distincte din nou. |
| - | Anumite aplicaţii numerice necesită scrierea unei funcţii care să poată apela o funcţie cu nume necunoscut, dar cu prototip şi efect cunoscut. | + | Un alt caz (diferit de cel expus anterior) este cel al atribuirii aceleiaşi structuri către două variabile pointer diferite: |
| - | + | ||
| - | De exemplu, o funcţie care să calculeze integrala definită a oricărei funcţii cu un singur argument sau care să determine o radăcină reala a oricărei ecuaţii (neliniare). | + | |
| - | + | ||
| - | Aici vom lua ca exemplu o funcţie ''listf'' care poate afişa (lista) valorile unei alte funcţii cu un singur argument, într-un interval dat şi cu un pas dat. Exemple de utilizare a funcţiei ''listf'' pentru afişarea valorilor unor funcţii de bibliotecă: | + | |
| <code c> | <code c> | ||
| - | int main(void) { | + | struct exemplu { |
| - | listf(sin, 0.0, 2.0 * M_PI, M_PI / 10.0); | + | int n; |
| - | listf(exp, 1.0, 20.0, 1.0); | + | char * s; |
| - | + | ||
| - | return 0; | + | |
| } | } | ||
| + | struct exemplu s1; | ||
| + | struct exemplu *p1; | ||
| + | struct exemplu *p2; | ||
| + | p1 = &s1; | ||
| + | p2 = &s1; | ||
| </code> | </code> | ||
| - | Problemele apar la definirea unei astfel de funcţii, care primeşte ca argument numele (adresa) unei funcţii. Prin convenţie, în limbajul C, numele unei funcţii neînsoţit de o listă de argumente şi de parantezele ''()'' specifice unui apel este interpretat ca un pointer către funcţia respectivă (fără a se folosi operatorul de adresare ''&''). Deci ''sin'' este adresa funcţiei sin(x) în apelul funcţiei ''listf''. Declararea unui argument formal (sau a unei variabile) de tip pointer la o funcţie are forma următoare: | + | În acest caz observăm că din nou p1->s şi p2->s indică către acelaşi şir de caractere, dar aici adresa către şirul de caractere apare memorată o singura dată; spre deosebire de cazul anterior, dacă modificăm adresa din p2->s, ea se va modifica automat şi în p1->s. |
| - | <code c> | + | |
| - | tip (*pf) (lista_arg_formale) | + | |
| - | </code> | + | |
| - | unde: | + | |
| - | * ''pf'' este numele argumentului (variabilei) pointer la funcţie | + | |
| - | * ''tip'' este tipul rezultatului funcţiei | + | |
| - | + | ||
| - | Parantezele sunt importante, deoarece absenţa lor modifică interpretarea declaraţiei. De exemplu, putem avea: | + | |
| - | <code c> | + | |
| - | tip * f(lista_arg_formale) // functie cu rezultat pointer, si NU pointer | + | |
| - | </code> | + | |
| - | În concluzie, definirea funcţiei ''listf'' este: | + | === Uniuni === |
| - | <code c> | + | Uniunile sunt asemănătoare structurilor, dar lor li se rezervă o zonă de memorie ce poate conţine, la momente de timp diferite, variabile de tipuri diferite. Sunt utilizate pentru a economisi memoria (se refoloseşte aceeaşi zonă de memorie pentru a stoca mai multe variabile). |
| - | void listf (double (*fp)(double), double min, double max, double pas) { | + | |
| - | double x,y; | + | |
| - | + | ||
| - | for (x = min; x <= max; x = x + pas) { | + | |
| - | y=(*fp)(x); // apel functie de la adresa din "fp" | + | |
| - | printf("\n%20.10lf %20.10lf", x, y); | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | O eroare de programare care trece de compilare şi se manifestă la execuţie este apelarea unei funcţii fără paranteze; compilatorul nu apelează funcţia şi consideră că programatorul vrea să folosească adresa funcţiei. De exemplu: | + | Uniunile se pot declara astfel: |
| <code c> | <code c> | ||
| - | if (kbhit) | + | union numere { |
| - | break; // echivalent cu if(1) break; | + | int i; |
| + | float f; | ||
| + | double v; | ||
| + | }; /* se poate utiliza si typedef... */ | ||
| - | if (kbhit()) | + | union numere u1, u2; |
| - | break; // iesire din ciclu la apasarea unei taste | + | |
| </code> | </code> | ||
| - | ===== Expresii complexe cu pointeri ===== | + | Când scriem ceva într-o uniune (de exemplu când facem o atribuire de genul u1.f = 7.4), ceea ce citim apoi trebuie să fie de acelaşi tip, altfel vom obţine rezultate eronate (adică trebuie să utilizam u1.f, nu u1.v sau u1.i). Programatorul trebuie să ţină evidenţa tipului variabilei care este memorată în uniune în momentul curent pentru a evita astfel de greşeli. Operaţiile care se pot face cu structuri se pot face şi cu uniuni; o structura poate conţine uniuni şi o uniune poate conţine structuri. |
| - | + | ||
| - | Deşi sunt întâlnite mai rar în practică, limbajul C permite declararea unor tipuri de date complexe, precum: | + | |
| + | Exemplu: | ||
| <code c> | <code c> | ||
| - | char *(*(*x)())[10]; | ||
| - | </code> | ||
| - | |||
| - | În interpretarea acestor expresii, operatorii ''()'' şi ''[]'' au precedenţa în faţa ''*'' şi modul de interpretare al acestor expresii este pornind ''din interior spre exterior''. Astfel expresia dată ca exemplu mai sus este (numerele de sub expresie reprezintă ordinea de interpretare): | ||
| - | - o variabila ''x'' | ||
| - | - care este un pointer la o funcţie | ||
| - | - fără nici un parametru | ||
| - | - şi care întoarce un pointer | ||
| - | - la un vector de 10 elemente | ||
| - | - de tip pointer | ||
| - | - către tipul ''char'' | ||
| - | |||
| - | Folosind acest procedeu, se pot rezolva şi alte situaţii aparent extrem de complexe: | ||
| - | <code c> | ||
| - | unsigned int *(* const *name[5][10] ) ( void ); | ||
| - | </code> | ||
| - | care semnifică o matrice de 5x10 de pointeri către pointeri constanţi la o funcţie, care nu ia nici un parametru, şi care întoarce un pointer către tipul ''unsigned int''. | ||
| - | |||
| - | ==== Exerciții Laborator CB/CD ==== | ||
| - | 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 | ||
| - | |||
| - | |||
| - | <code c ex.c> | ||
| #include <stdio.h> | #include <stdio.h> | ||
| + | #include <stdlib.h> | ||
| - | void next(void) | + | typedef union { |
| - | { | + | int Wind_Chill; |
| - | fflush(stdout); | + | char Heat_Index; |
| - | getchar(); | + | } Condition; |
| - | } | + | |
| - | int main(void) | + | typedef struct { |
| - | { | + | float temp; |
| - | int a[10]; | + | Condition feels_like; |
| + | } Temperature; | ||
| - | printf("a[0] = ?"); next(); | + | int main() { |
| - | printf("a[0] = %d\n", a[0]); next(); | + | |
| - | printf("*a = ?"); next(); | + | Temperature *tmp; |
| - | printf("*a = %d\n", *a); next(); | + | tmp = (Temperature *)malloc(sizeof(Temperature)); |
| + | |||
| + | printf("\nAddress of Temperature = %u", tmp); | ||
| + | printf("\nAddress of temp = %u, feels_like = %u", | ||
| + | &(*tmp).temp,&(*tmp).feels_like); | ||
| + | printf("\nWind_Chill = %u, Heat_Index= %u\n", | ||
| + | &((*tmp).feels_like).Wind_Chill, | ||
| + | &((*tmp).feels_like).Heat_Index); | ||
| - | printf("a = ?"); next(); | + | return 0; |
| - | 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; | + | |
| } | } | ||
| </code> | </code> | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | ||
| - | <hidden> | ||
| - | [[https://drive.google.com/drive/folders/0BworQDqcRK0yd205cE1qamZ5bEU|Checker laborator 8]] | + | La rulare va afişa: |
| - | [[ https://ocw.cs.pub.ro/courses/programare/checker | Tutorial folosire checker laborator ]] | + | <code c> |
| - | Link direct către lista completă de probleme: [[https://docs.google.com/document/d/1nERoSfIgBICsjXPB7SE5fH7Spq3WwlOzWNes6Eivp48/edit|aici]] | + | Address of Temperature = 165496 |
| - | </hidden> | + | Address of temp = 165496, feels_like = 165500 |
| - | ===== Exerciţii de Laborator ===== | + | Wind_Chill = 165500, Heat_Index= 165500 |
| - | + | </code> | |
| - | - **[3p]** În reprezentarea unui număr întreg pe mai mulţi octeţi (de exemplu un short sau un int), se pune problema ordinii în care apar octeţii în memorie. Astfel, există două moduri de reprezentare: | + | |
| - | * Big-endian (în care primul octet din memorie este cel mai semnificativ) | + | |
| - | * Little-endian (în care primul octet din memorie este cel mai puţin semnificativ)\\ **Se cere** să se scrie un program care să determine endianess-ul calculatorului pe care este compilat şi rulat, şi să afişeze un mesaj corespunzător pe ecran. | + | |
| - | - În limbajul C, şirurile de caractere sunt reprezentate în memorie ca o succesiune de valori de tip char terminate printr-un caracter special NULL ('\0'). Șirurile de caractere sunt pasate ca parametri printr-un pointer la primul caracter din şir, şi sunt prelucrate până când se întâlneşte caracterul nul, indiferent care este lungimea reală a zonei alocate. Astfel, un anumit vector de caractere de lungime N poate stoca şiruri de caractere de lungime între 0 şi N-1 (pentru că nu considerăm şi caracterul nul ca făcând parte din conţinutul şirului). | + | |
| - | * **[1p]** Să se scrie o funcţie care calculează lungimea unui şir de caractere dat ca parametru<code>int str_length(char *s);</code> | + | |
| - | * **[1p]** Să se scrie o funcţie pentru ştergerea (eliminarea) a n caractere dintr-o poziţie dată a unui şir ce returnează adresa șirului de caractere modificat<code>char * strdel(char *s, int pos, int n);</code> | + | |
| - | * **[1p]** Să se scrie o funcţie pentru inserarea unui şir ''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''.<code>char * strins(char *s1, int pos, char *s2)</code> | + | |
| - | * **[1p]** Scrieţi o funcţie care stabileşte dacă un şir dat (format din caractere alfanumerice) este egal cu o mască, ce poate conţine caractere alfanumerice şi caracterul special '?'. Se consideră că acest caracter înlocuieşte orice alt caracter alfanumeric. De exemplu, "abcde" este echivalent cu "?bc?e".<code>int eq_mask(char *sir, char *masca);</code> | + | |
| - | * **[2p]** Scrieţi o funcţie care stabileşte dacă un cuvânt dat se găseşte sau nu într-un tablou de cuvinte.<code>int eqcuv(char *cuv, char **tablou);</code> Pentru testare folosiți următoarea funcție ''main'':<code> | + | |
| - | 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"); | + | |
| - | }</code> | + | |
| - | - **[3p]** Scrieţi un program care afişează valorile functiilor ''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. | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | <hidden> | + | |
| - | ==== Soluții ==== | + | |
| - | {{:programare:laboratoare:7-solutions.zip|}} | + | ==== Exerciții ==== |
| + | Exercițiile pentru laborator se găsesc pe [[https://acs-pclp.github.io/laboratoare/08 | PCLP Laborator08: Tipuri de date definite de utilizator: structuri, uniuni, enumuri]]. | ||
| - | </hidden> | + | ==== Referinţe ==== |
| + | * [[http://crasseux.com/books/ctutorial//Data-structures.html#Data%20structures|The GNU Programming Tools - Data structure]] | ||
| + | * [[http://publications.gbdirect.co.uk/c_book/chapter6/unions.html|The C Book - Unions]] | ||