Differences

This shows you the differences between two versions of the page.

Link to this comparison view

programare:laboratoare:lab04 [2025/10/10 12:53]
darius.neatu
programare:laboratoare:lab04 [2025/10/15 18:30] (current)
darius.neatu [Referinţe]
Line 1: Line 1:
-===== PCLP Suport Teoretic pentru LaboratorFuncțiiRecursivitateClase de stocare ​=====+===== PCLP Laborator04TablouriParticularizare - vectori și matrici ​ ===== 
 + 
 +===== TablouriParticularizare - vectori. Aplicaţii ​===== 
 + 
 +**Responsabili:​** 
 +  * [[neatudarius@gmail.com|Darius Neațu (2019-2020)]] 
 +  * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2019-2020)]] 
 +  * [[andrei.parvu@cti.pub.ro|Andrei Pârvu]] ​
  
 ==== Obiective ==== ==== Obiective ====
Line 5: Line 12:
 În urma parcurgerii acestui laborator, studentul va fi capabil: În urma parcurgerii acestui laborator, studentul va fi capabil:
  
-  * să declare şi să definească o funcţie în limbajul C +  * să declare şi să iniţializeze vectori/​matrici (din declaraţie şi prin structuri iterative) 
-  * să apeleze funcţii definite în acelaşi fişier sursă, cât şi funcţii din alte fişiere ​sursă ​sau biblioteci +  * să implementeze algoritmi simpli de sortare ​şi căutare pe vectori 
-  * să distingă între parametrii formali ​şi cei efectivi, între cei transmişi prin valoare şi cei transmişi prin adresa ​de memorie +  * să folosească practici recunoscute ​şi recomandate pentru scrierea de cod sursă ​care implică lucrul cu tablouri 
-  * să explice rolul funcţiei ''​main()''​ într-un program +  * să recunoască şi să evite erorile comune ​de programare legate de aceste structuri 
-  * să folosească clasele ​de stocare în declaraţiile unor funcţii+  * sa cunoască regulile de reprezentare ale tablourilor în memorie și să înteleagă modul în care compilatorul interpretează operatorii ​de indexare;  
  
 ==== Noţiuni teoretice ==== ==== Noţiuni teoretice ====
  
-Funcţiile împart taskuri complexe în bucăţi mici mai uşor de înţeles şi de programat. Acestea pot fi refolosite cu alte ocazii, în loc să fie rescrise de la zero. De asemenea, funcţiile sunt utile pentru a ascunde detalii de funcţionare ale anumitor părţi ale programului,​ ajutând la modul de lucru al acestuia. Utilizând funcţii, care reprezintă unitatea fundamentală de execuţie a programelor C, se obţine o divizare logică a programelor mari şi complexe.+=== Vectori ===
  
-Împărţirea programelor ​în funcţii este arbitrară şi depinde ​de modul de gândire a celui care le creeazăDe obicei, funcţiile cuprind o serie de instrucţiuni care efectuează un calcul, realizează o acţiune, implementează un algoritm, etc. Crearea funcţiilor trebuie să se bazeze pe următoarele principii: ​''​claritate'',​ ''​lizibilitate'', ​''​uşurinţă în întreţinere'',​ ''​reutilizabilitate''​.+Printr-un vector se înţelege ''​o colecţie liniară şi omogenă'' ​de dateUn vector este liniar pentru că datele(elementele) pot fi accesate în mod unic printr-un ''​index''​. Un vector este, de asemenea, omogenpentru că toate elementele sunt de acelaşi ​''​tip''​. În limbajul Cindexul este un număr întreg pozitiv ​şi indexarea se face începând cu 0
  
-==== Definirea şi apelul unei funcţii în C ====+Declaraţia unei variabile de tip vector se face în felul următor:
  
-Caracteristicile definitorii ale unei funcţii în C sunt: numele, parametrii de apel şi valorea returnată. ​ +<code bash> 
-Sintaxa standard de **declarare** a unei funcţii este:+<​tip_elemente>​ <​nume_vector>​[<​dimensiune>​];​ 
 +</​code>​
  
-<code bash> tip_returnat nume_functie (tip_param1 nume_param1 ​tip_param2 nume_param2,​ ...); </​code>​+De exempluavem următoarele declaraţii de vectori:
  
-Această declarare poartă numele de **antetul funcţiei** (**function signature** sau simplu** signature**). Lista de parametri **poate** lipsi.+<code c> 
 +int a[100]; 
 +float vect[50];
  
-Odată declarată, o funcţie trebuie **definită**,​ în sensul că trebuie expandat corpul acesteia cu instrucţiunile pe care trebuie să le execute.  +#define MAX 100 
- +..
-Definirea unei funcţii are forma: ​+unsigned long numbers[MAX];​ 
 +</​code>​
  
 +Este de remarcat că vectorul este o ''​structură statică'':​ dimensiunea acestuia trebuie să fie o constantă la compilare şi nu poate fi modificată în cursul execuţiei programului. Astfel, programatorul trebuie să estimeze o dimensiune maximă pentru vector, şi aceasta va fi o limitare a programului. De obicei, se folosesc constante simbolice (ca în ultimul exemplu) pentru aceste dimensiuni maxime, pentru ca ele să poată fi ajustate uşor la nevoie. ​
 +De asemenea, în cadrul unei declaraţii,​ se pot iniţializa cu valori constante componente ale vectorului, iar în acest caz, dimensiunea vectorului poate rămâne neprecizată (compilatorul o va determina din numărul elementelor din listă). De exemplu:
  
 <code c> <code c>
-tip_returnat nume_functie(tip_param1 nume_param1tip_param2 nume_param2, ...) { +int a[3] = {156};                     // Toate cele 3 elemente sunt initializate  
-  declaratii de variabile si instructiuni;​ +float num[] = {1.5, 2.3, 0.2, -1.3}     // Compilatorul determina dimensiunea - 4 - a vectorului 
-   +unsigned short vect[1000] = {0, 2, 4, 6}; // Sunt initializate doar primele 4 elemente, iar toate celelalte sunt initializate cu valoarea 0
-  return expresie+
-+
 </​code>​ </​code>​
  
-Limbajul C permite separarea declaraţiei unei funcţii de definiţia acesteia (codul ​care o implementează). Pentru ca funcţia să poată fi folosită, este obligatorie doar declararea acesteia înainte de codul care o apelează. Definiţia poate apărea mai departe în fişierul sursăsau chiar într-un alt fişier sursă sau bibliotecă.+În cazul special în care specificăm dimensiunea şi doar un singur element la initializareprimul element va fi cel specificatiar toate celelalte elemente ale vectorului vor fi iniţializate la ''​0'':​
  
-Diferite părţi din definirea unei funcţii pot lipsiAstfel, o funcţie minimală este:+<code c> 
 +char string[100] = {97}; // Sirul va fi initializat cu: 97 (caracterul '​a'​) pe prima poziţie ş99 de 0 
 +int v[100] = {0};        // Vectorul va fi umplut cu 0. 
 +</​code>​
  
-<​code ​c> dummy() {} </code>+Este important de remarcat faptul ​că elementele **neiniţializate** pot avea valori **oarecare**. La alocarea unui vector, compilatorul nu efectuează nici un fel de iniţializare şi nu furnizează nici un mesaj de eroare dacă un element este folosit înainte de a fi iniţializat. **Un program corect va iniţializa**,​ în orice caz, fiecare element înainte de a-l folosi. 
 +Elementele se accesează prin expresii de forma ''<​nume_vector>[<indice>]''​. De exemplu, putem avea:
  
-Funcţia de mai sus nu face absolut nimic, nu întoarce nici o valoare şi nu primeşte nici un argument, însă din punct de vedere al limbajului C este perfect validă.+<code c> 
 +char vect[100];​ 
 +vect[0] = 1; 
 +vect[5] = 10;
  
-Tipul returnat de o funcţie poate fi orice tip **standard** sau **definit** de utilizator (**struct**-uri - acoperite într-un laborator următor), inclusiv tipul **void** (care înseamnă că funcția nu returnează nimic).+int i = 90; 
 +vect[i] = 15; 
 +vect[i + 1] = 20; 
 +</​code>​
  
-Orice funcţie care întoare un rezultat trebuie să conţină instrucţiunea:​+==== Stil de programare ====
  
-<code c> return expression; </​code>​+=== Exemple de programe ===
  
-Expresia este evaluată şi convertită la tipul de date care trebuie returnat ​de funcţie. Această instrucţiune termină şi execuţia funcţiei, **indiferent** dacă după aceasta mai urmează sau nu alte instrucţiuni. Dacă este cazul, se pot folosi mai multe instrucţiuni **return** pentru a determina mai multe puncte de ieşire din funcţie, în raport cu evoluţia funcţiei.+Citirea unui vector ​de intregi ​de la tastatura:
  
-Exemplu:+<code c> 
 +int main()  
 +
 +  int a[100], n, i; /* vectorul a are maxim 100 de intregi */
  
-<code c declarare.c>​ +  scanf("​%d"​&n); /* citeste nr de elemente vector */ 
-int min(int xint y); + 
-</code>+  for (i = 0; i n; i++) { 
 +    scanf("​%d",​ &​a[i]); ​/* citire elemente vector */ 
 +  }
  
-<code c definire.c>​ +  for (i = 0; i n; i++) { 
-int min(int x, int y) { +    ​printf("%d ", a[i]); /* scrie elemente vector */
-  ​if ​(x < y+
-    return x;+
   }   }
   ​   ​
-  return ​y;+  return ​0;
 } }
 </​code>​ </​code>​
  
-Apelul unei funcţii se face specificând **parametrii efectivi** (parametrii care apar în declararea funcţiei se numesc **parametri formali**).+Generarea unui vector cu primele n numere Fibonacci:
  
 <code c> <code c>
-int main() { +#include <​stdio.h>​ 
-  ​int a, bminimum+int main() ​ 
-  ​//​........... +
-  x = 2+  ​long fib[100] = {11}
-  y = 5; +  ​int n, i
-  ​minimum = min(x, 4); + 
-  ​printf("Minimul dintre %d si 4 este: %d", ​x, minimum); +  ​printf("n = ");  
-  printf("​Minimul dintre %d si %d este: %d", ​x, y, min(x, y));+  ​scanf("​%d", ​&n); 
 +  ​ 
 +  for (i = 2; i < n; i++) { 
 +    fib[i] = fib[i - 1] + fib[i - 2]; 
 +  } 
 +  for (i = 0; i < n; i++) { 
 +    ​printf("​%ld ", ​fib[i])
 +  } 
 + 
 +  return 0;
 } }
 </​code>​ </​code>​
  
-==== Transmiterea parametrilor ====+<note warning>​ 
 +Deşi modul în care se manifestă erorile din program poate fi surprinzător şi imprevizibil,​ cauzele care produc aceste erori sunt destul de comune şi pot fi grupate în mai multe categorii. Câteva dintre acestea sunt prezentate mai jos 
 +</​note>​
  
 +  * Depăşirea limitelor indicilor (index out of bounds) este o eroare frecventă, ce poate duce la blocarea programului sau a sistemului şi poate fi evitată prin verificarea încadrării în intervalul valid. ​
 +  * Indici folosiţi greşit în bucle imbricate (index cross-talk). Sunt multe cazuri în care pe un nivel al buclei se foloseşte, de exemplu vect[i], şi pe nivelul imbricat vect[j], când de fapt se dorea folosirea lui i. Mare atenţie şi în astfel de cazuri!
  
-Apelul unei funcţii se face specificând parametrii care se transmit acesteia. În limbajul C, dar şi în alte limbaje de programare există ** 2 moduri de transmitere a parametrilor**. Deoarece nu avem încă cunoștințele necesare pentru ​a înțelege ambele moduri, astăzi vom studia doar unul, urmând ca în laboratorul 8 să revenim și să îl explicăm șpe al doilea.+<note tip> 
 +Definiţi dimensiunile prin constante ​şi folosiţi-le pe acestea ​în locul tastării explicite ​valorilor ​în codul sursă. Astfel veţi evita neconcordanţe ​în cod dacă doriţi ulterior ​să modificaţi dimensiunile şi uitaţi să modificaţpeste tot prin cod. 
 +</​note>​
  
-=== Transmiterea parametrilor prin valoare ===+<code c> 
 +#define MAX   100
  
-Funcţia va lucra cu **o copie** a variabilei pe care a primit-o şi **orice** modificare din cadrul funcţiei va opera asupra aceste copii. La sfârşitul execuţiei funcţiei, copia va fi **distrusă** şi astfel se va pierde orice modificare efectuată.  +int vect[MAX];​ 
- +</​code>​ 
-Pentru a nu pierde modificările făcute se foloseşte instrucţiunea **return**, care poate întoarce, la terminarea funcţiei, noua valoare a variabilei. Problema apare în cazul în care funcţia modifică mai multe variabile şi se doreşte ca rezultatul lor să fie disponibil şi la terminarea execuţiei funcţiei.  +va fi de preferat ​în locul lui
- +
-Exemplu de transmitere a parametrilor prin valoare:+
 <code c> <code c>
-min(x, 4) // se face o copie lui x+int vect[100];
 </​code>​ </​code>​
  
 <note tip> <note tip>
-Până acum aţi folosit în programele voastre funcţii care trimit valorile atât prin valoare (de exemplu ''​printf()''​) cât şi prin intermediul adresei ​de memorie (de exemplu ''​scanf()''​)Mecanismul de transfer al valorilor prin intermediul adresei de memorie unde sunt stocate va fi complet „elucidat” ​în laboratorul ​de pointeri.+Verificaţi că indicii se încadrează între marginile superioară ​şi inferioară a intervalului ​de valori valideAcest lucru trebuie ​în general făcut în cazul în care datele provin dintr-o sursă externă: citite ​de la tastatură sau pasate ca parametri efectivi unei funcţii, de exemplu.
 </​note>​ </​note>​
  
-==== Funcţii recursive ====+Exemplu:
  
-O funcţie poate să apeleze la rândul ei alte funcţii. Dacă o funcţie se apelează pe sine **însăşi**, atunci funcţia este **recursivă**. Pentru a evita un număr infinit de apeluri recursive, trebuie ca funcţia să includă în corpul ei **condiţie de oprire**astfel ca, la un moment dat, recurenţa să se oprească ​şi să se revină succesiv din apeluri.+<code c> 
 +// program care citeşte un index şi o valoare, şi atribuie valoarea elementului din vector care se găseşte la poziţia respectivă 
 +#include <stdio.h> 
 +#define N 10
  
-Condiţia trebuie să fie una generică, şsă oprească recurenţa în orice situaţie. Această condiţie se referă în general a parametrii de intrare, pentru care la un anumit moment, răspunsul poate fi returnat directfără a mai fi necesar un apel recursiv suplimentar. ​+int main()  
 +
 +  int i, val; 
 +  int v[N];
  
-''​Exemplu'':​ Calculul recursiv al factorialului+  scanf("​%d%d",​ &i, &val);
  
-<code c> +  /* !!! Verific daca indexul este valid */ 
-int fact(int n) { +  if (i >= 0 && i < N) { 
-  if (n == 0) { +   v[i] = val;
-    ​return 1;+
   } else {   } else {
-    ​return n * fact(n - 1);+    ​printf("​Introduceti un index >= 0 si < %d\n", N);
   }   }
 +
 +  return 0;
 } }
 </​code>​ </​code>​
  
-sau, într-o formă mai compactă:+<note tip> 
 +Folosiţi comentarii pentru a explica ce reprezintă diverse variabile. Acest lucru vă va ajuta atât pe voi să nu încurcaţi indici, de exemplu, cât şi pe ceilalţi care folosesc sau extind codul vostru. 
 +</​note>​ 
 + 
 +Exemplu:
  
 <code c> <code c>
-int fact(int n) { +#include <​stdio.h>​ 
-  ​return (n >= 1) ? n fact(n - 1) : 1;+#define N 100 
 + 
 +int main()  
 +
 +  ​int v[N]; 
 +  int i, j; /indecsii elementelor ce vor fi interschimbate */ 
 +  int aux;  /* variabila ajutatoare pentru interschimbare */ 
 + 
 +  /*... initializari */ 
 +   
 +  /* Interschimb */ 
 +  aux = v[i]; 
 +  v[i] = v[j]; 
 +  v[j] = aux; 
 + 
 +  return 0;
 } }
 </​code>​ </​code>​
  
-Întotdeauna trebuie avut grijă în lucrul cu funcţii recursive deoarece, la fiecare apel recursiv, contextul este salvat pe stivă pentru a putea fi refăcut la revenirea din recursivitate. În acest fel, în funcţie de numărul apelurilor recursive şi de dimensiunea contextului (variabile, descriptori de fişier, etc.) stiva se poate umple foarte rapid, generând o eroare de tip [[http://​en.wikipedia.org/​wiki/​Stack_overflow | stack overflow]] (vezi şi [[http://​en.wikipedia.org/​wiki/​Infinite_recursion | Infinite recursion pe Wikipedia]]).+==== Aplicaţii cu vectori ====
  
-==== Funcţia main ====+=== Căutări ​===
  
-Orice program ​conţine cel puţin o funcţie, ​şi anume cea principală, numită ''​main()''​. Aceasta are un format special de definire:+== Căutare secvenţială == 
 + 
 +Când avem de a face cu un vector nesortat (şi nu numai în acest caz), cea mai simplă abordare pentru a găsi o valoareeste căutarea secvenţială. Cu alte cuvinte, se compară, la rând, fiecare valoare din vector cu valoarea căutată. Dacă valoarea a fost găsită, căutarea se poate opri (nu mai are sens să parcugem vectorul până la capăt, dacă nu se cere acest lucru explicit).  
 + 
 +''​Exemplu'':​
  
 <code c> <code c>
-int main(int argc, char *argv[]) 
-{ 
-    // some code 
-    return 0; 
-} 
-</​code>​ 
  
-Primul parametru, ''​argc'',​ reprezintă numărul de argumente primite de către program la linia de comandă, incluzând numele cu care a fost apelat programul. Al doilea parametru, ''​argv'',​ este un pointer către conţinutul listei de parametri al căror număr este dat de ''​argc''​. Lucrul cu parametrii liniei de comandă va fi reluat într-un laborator viitor. ​+#define MAX 100
  
-Atunci când nu este necesară procesarea parametrilor de la linia de comandă, se poate folosi forma prescurtată a definiţiei funcţiei ''​main'',​ şi anume:+...
  
-<code c> +int v[MAX], x, i; 
-int main(void+ 
-+/* initializari */ 
-    ​// some code +... 
-    ​return 0;+ 
 +int found = 0; 
 +for (i = 0; i < MAX; i++{ 
 +  if (x == v[i]) 
 +    ​found = 1; 
 +    ​break; 
 +  } 
 +
 + 
 +if (found) { 
 +   ​printf("​Valoarea %d a fost gasita in vector\n",​ x); 
 +} else {  
 +   ​printf("​Valoarea %d nu a fost gasita in vector\n",​ x);
 } }
 +...
 </​code>​ </​code>​
  
-În ambele cazuri, standardul impune ca ''​main''​ să întoarcă o valoare de tip întreg, care să reprezinte codul execuţiei programului şi care va fi pasată înapoi sistemului de operare, la încheierea execuţiei programului. Astfel, instrucţiunea ''​return''​ în funcţia ''​main''​ va însemna şi terminarea execuţiei programului. ​+== Căutare binară iterativă ==
  
-În mod normal, orice program ​care se execută corect va întoarce 0şi o valoare diferită ​de în cazul în care apar eroriAceste coduri ar trebui documentate pentru ca apelantul programului să ştie cum să adreseze eroarea respectivă.+Dacă vectorul pe care se face căutarea este sortatalgoritmul mai eficient ​de folosit ​în acest caz este căutarea binarăPresupunem că vectorul este sortat crescător (pentru vectori sortaţi descrescător, raţionamentul este similar).
  
-==== Tipul de date void ====+Valoarea căutată, ''​x'',​ se compară cu valoarea cu indexul N/2 din vector, unde N este numărul ​de elemente. Dacă ''​x''​ este mai mic decât valoarea din vector, se caută în prima jumătate a vectorului, iar dacă este mai mare, în cea de-a doua jumătate. Căutarea în una dintre cele două jumătăţi se face după acelaşi algoritm. ​
  
-Tipul de date ''​void''​ are mai multe întrebuinţări.+Conceptual, căutarea binară este un algoritm recursiv, dar poate fi implementat la fel de bine într-un mod iterativ, folosind indecşii corespunzători bucăţii din vector în care se face căutarea. Aceşti indecşi se modifică pe parcursul algoritmului,​ într-o buclă, în funcţie de comparaţiile făcute. Evoluţia algoritmului este ilustrată în imaginea de mai jos
  
-Atunci când este folosit ca tip returnat de o funcţie, specifică faptul ​că funcţia nu întoarce nici o valoare. Exemplu:+Pseudocodul pentru ​utarea binară:
  
 <code c> <code c>
-void print_nr(int number) { +// cauta elementul x in vectorul sortat v, intre pozitiile 0 si n-1  si returneaza pozitia gasita sau -1 
-  ​printf("​Numarul este %d"number);+int binary_search(int n, int v[NMAX], int x) { 
 +  ​int low = 0, high = n - 1; 
 + 
 +  while (low <= high) { 
 +    // De ce preferăm această formă față de (low + high) / 2 ? 
 +    int middle = low + (high - low) / 2; 
 + 
 +    if (v[middle] == x) { 
 +      // Am gasit elementulreturnam pozitia sa 
 +      return middle; 
 +    } 
 + 
 +    if (v[middle] < x
 +      // Elementul cautat este mai mare decat cel curent, ne mutam in jumatatea 
 +      // cu elemente mai mari 
 +      low = middle + 1; 
 +    } else { 
 +      // Elementul cautat este mai mic decat cel curent, ne mutam in jumatatea 
 +      // cu elemente mai mici 
 +      high = middle - 1; 
 +    } 
 +  } 
 + 
 +  // Elementul nu a fost gasit 
 +  return -1;
 } }
 </​code>​ </​code>​
  
-Atunci când este folosit în declaraţia unei funcţii, ''​void'' ​semnifică faptul că funcţia nu primeşte nici un parametruExemplu:+<note tip> 
 +Preferăm calcularea mijlocului intervalului [lowhigh] folosind formula ​''​x = low + (high - low) / 2'' ​deoarece formula perfect analogă ''​x = (low + high) / 2''​ poate da overflow pentru valori mari ale low si highDe altfel, acest bug a existat în biblioteca Java timp de 9 de ani. Puteți citi mai mult despre asta în [[http://​googleresearch.blogspot.ro/​2006/​06/​extra-extra-read-all-about-it-nearly.html | acest articol]]. 
 +</​note>​
  
 +=== Sortări ===
 +
 +== Bubble Sort ==
 +
 +Metoda bulelor este cea mai simplă modalitate de sortare a unui vector, dar şi cea mai ineficientă. Ea funcţionează pe principiul parcurgerii vectorului şi comparării elementului curent cu elementul următor. Dacă cele două nu respectă ordinea, sunt interschimbate. Această parcurgere este repetată de suficiente ori până când nu mai există nici o interschimbare în vector. ​
 +
 +== Sortarea prin selecţie ==
 +
 +Sortarea prin selecţie oferă unele îmbunătăţiri în ceea ce priveşte complexitatea,​ însă este departe de a fi considerat un algoritm eficient. Presupunând că se doreşte sortarea crescătoare a vectorului, se caută minimul din vector, şi se interschimbă cu primul element - cel cu indexul 0. Apoi se reia acelaşi procedeu pentru restul vectorului. Motivul pentru care algoritmul de sortare prin selecţie este mai eficient este acela că vectorul în care se caută minimul devine din ce în ce mai mic, şi, evident, căutarea se face mai repede la fiecare pas.
 +
 +Studiul unor algoritmi mai avansaţi de sortare, precum şi studiul complexităţii lor nu constituie obiectul acestui laborator. Acestea se vor relua mai detaliat în cadrul altor cursuri (AA/​PA). ​
 +
 +===== Matrice. Operaţii cu matrice: adunare, înmulţire. Reprezentarea în memorie. =====
 +
 +**Responsabili:​**
 +  * [[neatudarius@gmail.com|Darius Neațu (CA 2019-2020)]]
 +  * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (CA 2019-2020)]]
 +  * [[andrei.parvu@cti.pub.ro|Andrei Pârvu]] ​
 +
 +[[https://​docs.google.com/​document/​d/​16GNacxqmnRjaBx7w8tt9rRprbpiOjx8njzIrv4Q0mI0/​edit?​usp=sharing|Probleme]]
 +
 +[[https://​we.tl/​t-HTa9YV1Wvg|Teste]]
 +
 +=== Matrice ===
 +
 +Matricea este o colecţie omogenă şi bidimensională de elemente. Acestea pot fi accesate prin intermediul a doi indici, numerotaţi,​ ca şi în cazul vectorilor, începand de la 0. Declaraţia unei matrice este de forma:
 +
 +<code bash>
 +<​tip_elemente>​ <​nume_matrice>​[<​dim_1>​][<​dim_2>​];​
 +</​code>​
 +
 +De exemplu, avem:
 <code c> <code c>
-int init(void) { +int mat[5][10]
-  return 1+</​code>​ 
-}+<code c> 
 +#define MAX_ELEM 100 
 +float a[MAX_ELEM][MAX_ELEM];​
 </​code>​ </​code>​
  
-<note important>​ +Numărul de elemente ale unei matrice va fi ''​dim_1*dim_2'',​ şi semnificaţia fiecărei dimensiuni este o chestiune ce ţine de logica programului. În matematică,​ prima dimensiune poate să însemne linia şi a doua coloana pentru fiecare element, însa acest lucru nu este obligatoriu. Este necesar totuşi, pentru funcţionarea corectă a programului,​ să se respecte semnificaţiile alese pe întreg parcursul codului sursă.
-Această declaraţie nu este similară cu următorul caz:+
  
 +=== Tablouri multidimensionale ===
 +
 +Vectorii şi matricele se pot extrapola la noţiunea generală de tablou cu mai multe dimensiuni, care se declară în modul următor:
 +
 +<code bash>
 +<​tip_elemente>​ <​nume_tablou>​[<​dim_1>​][<​dim_2>​]...[<​dim_n>​];​
 +</​code>​
 +
 +De exemplu:
 <code c> <code c>
-int init() { +int cube[3][3][3];
-  return 1; +
-}+
 </​code>​ </​code>​
  
-În cel de-al doilea cazcompilatorul ​nu verifică dacă funcţia este într-adevăr apelată fără nici un parametru. Apelul celei de-a doua funcţii cu un număr arbitrar de parametri nu va produce nici o eroare, ​în schimb apelul primei funcţii cu un număr de parametri diferit de zero va produce o eroare de tipul:+Deşi, în cazul a mai mult de 3 dimensiunitablourile pot să nu mai aibă sens concret sau fizic, acestea pot fi deosebit ​de utile în multe situaţii. În acest laborator ne vom rezuma totuşi la tablouri bidimensionale.
  
-''​too many arguments to function''​. +=== Adunarea si înmulţirea matricelor ===
-</​note>​+
  
 +== Suma matricelor ==
  
-==== Clase de stocare. Fişiere antet vs. biblioteci ==== +Fie $A \in {\mathbb R}^{m \times n}$, $B \in {\mathbb R}^{m \times n}$, atunci $(A+B) \in {\mathbb R}^{m \times n}$, unde: $(A+B)_{i,​j} ​ A_{i,j} + B_{i,j}$
-<note tip> +
-Această secţiune este importantă pentru înţelegerea modului de lucru cu mai multe fişiere sursă şcu bibliotecile oferite de GCC. Deşi în continuare sunt discutate în contextul funcţiilorlucrurile se comportă aproximativ la fel şîn cazul variabilelor globale (a căror utilizare esteoricum, descurajată). +
-</​note>​+
  
-După cum se ştie, într-un fişier sursă (.c) pot fi definite un număr oarecare de funcţii. În momentul în care programul este compilat, din fiecare fişier sursă se generează un fişier obiect (.o), care conţine codul compilat al funcţiilor respective. Aceste funcţii pot apela la rândul lor alte funcţii, care pot fi definite în acelaşi fişier sursă, sau în alt fişier sursă. În orice caz, compilatorul nu are nevoie să ştie care este definiţia funcţiilor apelate, ci numai semnătura acestora (cu alte cuvinte, ''​declaraţia''​ lor), pentru a şti cum să realizeze instrucţiunile de apel din fişierul obiect. Acest lucru explică de ce, pentru a putea folosi o funcţie, trebuie ''​declarată''​ înaintea codului în care este folosită.+{{:​programare:​laboratoare:​sum.png|}}
  
-''​Fişierele antet''​ conţin o colecţie de declaraţii de funcţii, grupate după funcţionalitatea pe care acestea o oferă. Atunci când includem un fişier antet (.h) într-un fişier sursă (.c), compilatorul va cunoaşte toate semnăturile funcţiilor de care are nevoie, şi va fi în stare să genereze codul obiect pentru fiecare fişier sursă în parte. (NOTĂAstfel nu are sens includerea unui fişier .c în alt fişier .c; se vor genera două fişiere obiect care vor conţine definiţii comune, şi astfel va apărea un conflict de nume la editarea legăturilor).+Exemplu:
  
-Cu toate acestea, pentru a realiza un fişier executabil, trebuie ca fiecare funcţie să fie ''​definită''​. Acest lucru este realizat de către editorul de legături; cu alte cuvinte, fiecare funcţie folosită în program trebuie să fie conţinută în fişierul executabil. Acesta caută în fişierele obiect ale programului definiţiile funcţiilor de care are nevoie fiecare funcţie care le apelează, şi construieşte un singur fişier executabil care conţine toate aceste informaţii. ''​Bibliotecile''​ sunt fişiere obiect speciale, al căror unic scop este să conţină definiţiile funcţiilor oferite de către compilator, pentru a fi integrate în executabil de către editorul de legături.+$$ 
 +  \Large 
 +  \begin{bmatrix} 
 +     1 & 3 \\  
 +     0 & 4 \\ 
 +     5 & 8 
 +  \end{bmatrix} 
 +
 +  \begin{bmatrix}  
 +    2 & 5 \\  
 +    1 & 2 \\  
 +    6 & 1 
 +  \end{bmatrix} 
 +
 +  \begin{bmatrix} 
 +    3 & 8 \\  
 +    1 & 6 \\  
 +    11 & 9 
 +  \end{bmatrix} 
 +$$
  
-''​Clasele de stocare''​ intervin în acest pas al editării de legături. O clasă de stocare aplicată unei funcţii indică dacă funcţia respectivă poate fi folosită şi de către alte fişiere obiect (adică este ''​externă''​),​ sau numai în cadrul fişierului obiect generat din fişierul sursă în care este definită (în acest caz funcţia este ''​statică''​). Dacă nu este specificată nici o clasă de stocare, o funcţie este implicit externă.+== Înmulţirea matricelor ==
  
-Cuvintele cheie ''​extern''​ şi ''​static''​puse în faţa definiţiei funcţieiîi specifică clasa de stocareDe exemplupentru a defini o funcţie internăse poate scrie:+Fie $A \in {\mathbb R}^{m \times n}$$B \in {\mathbb R}^{n \times p}$atunci $(AB) \in {\mathbb R}^{m \times p}$, unde elementele $A.B$ sunt date de formula: 
 +$(AB)_{i,j} = \sum_{r=1}^n A_{i,r}B_{r,j}$
  
-<code c> 
-static int compute_internally(int,​ int); 
-</​code>​ 
  
-Funcţia ​''​compute_internally'' ​nu va putea fi folosită decât de către funcţiile definite în acelaşi fişier sursă şi nu va fi vizibilă de către alte fişiere sursă, în momentul editării  legăturilor.+{{:​programare:​laboratoare:​mul.png|}} Exemplul din stânga prezintă cum se calculează valorile (1,2) si (3,3) ale '''​AB'''​ daca '''​A'''​ este o matrice 3×2, si '''​B'''​ o matrice 2×3. Pentru calculul unui element din matrice se consideră o linie respectiv o coloană din fiecare matrice conform săgeţilor. Elementele din acestea sunt înmulţite câte 2 conform înmulţirii pe vectoriapoi suma produselor constituie elementul din matricea finală
  
 +$$
 +  (\mathbf{AB})_{1,​2} = \sum_{r=1}^2 a_{1,​r}b_{r,​2} = a_{1,​1}b_{1,​2}+a_{1,​2}b_{2,​2} \\ 
 +  (\mathbf{AB})_{3,​3} = \sum_{r=1}^2 a_{3,​r}b_{r,​3} = a_{3,​1}b_{1,​3}+a_{3,​2}b_{2,​3}
 +$$
  
-==== Exerciții Laborator CB/CD ====+Exemplu:
  
-  ​Primul exercitiu presupune modificarea/​adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul afiseaza suma cifrelor unui numar. +$$ 
-    ​*Nu uitati ca trebuie sa utilizam un [[http://​ocw.cs.pub.ro/​courses/​programare/​coding-style| coding style]] adecvat atunci cand scriem sursele. +  \Large 
-<code c ex1.c>+   
 +  \begin{bmatrix} 
 +     1 & 0 & 2 \\  
 +     -1 & 3 & 1 
 +  \end{bmatrix} 
 +\cdot 
 +  \begin{bmatrix} ​ 
 +    ​3 & 1 \\  
 +    2 & 1 \\  
 +    1 & 0 
 +  \end{bmatrix} 
 +
 +\begin{bmatrix} 
 +   1 \times 3 + 0 \times 2 + 2 \times 1 & 1 \times 1 + 0 \times 1 + 2 \times 0 \\ 
 +  ​-1 \times 3 + 3 \times 2 + 1 \times 1 & -1 \times 1 + 3 \times 1 + 1 \times 0  
 +\end{bmatrix} 
 +
 +\begin{bmatrix} 
 +    5 & 1 \\ 
 +    4 & 2 
 +\end{bmatrix} 
 +$$
  
-#include <​stdio.h> ​                                                              +=== Reprezentarea în memorie ===
-                                                                                 +
-int sum_recursive(int n)                                                         +
-{                                                                                +
-    if (n == 0) {                                                            +
-        return 0;                                                        +
-    }                                                                        +
-                                                                                 +
-    return n % 10 + sum_recursive(n / 10);                                   +
-}                                                                                +
-                                                                                 +
-                                                                                 +
-int main(void) ​                                                                  +
-{                                                                                +
-    int nr;                                                                  +
-                                                                                 +
-    scanf("​%d",​ &​nr); ​                                                       +
-                                                                                 +
-    printf("​%d\n",​ sum_recursive(nr)); ​                                      +
-                                                                                 +
-    return 0;                                                                +
-}+
  
-</​code>​+Cunoaşterea reprezentării în memorie a tablourilor vă ajută să înţelegeţi mai bine cum se lucrează cu aceste tipuri de date şi să evitaţi atât erorile comune, cât şi pe cele mai subtile. Aşa cum se ştie, fiecare variabilă are asociata o anumită adresă în memorie şi ocupă o anumită lungime, măsurată în octeţi. Standardul C impune ca un tablou să fie memorat într-o zonă continuă de memorie, astfel ca pentru un tabloul de forma: ''​T tab[dim1][dim2]...[dimn];''​ 
 +dimensiunea ocupată în memorie va fi ''​sizeof(T)*dim1*dim2*...*dimn''​. Vom considera în continuare cazul particular al unui vector vect de lungime n, şi al unui element oarecare al acestuia, de pe pozitia i. 
 +Atunci când întalneşte numele vect, compilatorul va intelege ''"​adresa în memorie de la care începe vectorul vect"''​. Operatorul de indexare [] aplicat numelui vect instruieşte compilatorul să ''"​evalueze acel element de tipul T, care se află pe pozitia i în vectorul care începe de la adresa vect"''​. Acest lucru se poate exprima direct: ''"​evaluarea variabilei de tip T de la adresa vect + i * sizeof(T)"''​.
  
 +În ultima formulare observaţi ca nu mai intervine sub nici o formă dimensiunea vectorului dată la declarare. Aceea a fost necesară doar compilatorului,​ ca sa ştie câtă memorie să aloce pentru reprezentarea acestuia. De asemenea, observaţi că sunt permise indexari în afara spaţiului de memorie alocat, şi astfel programul va putea, din greşeala, accesa alte zone de memorie, lucru care poate avea repercursiuni grave. În cel mai bun caz programul nostru se va comporta foarte ciudat (erori în locuri total imprevizibile),​ şi în cel mai rău caz întreg sistemul va fi blocat (în cazul sistemelor care nu au implementate spaţii virtuale de memorie proprii fiecărei aplicaţii - platformele Windows NT si Linux).
  
 +Faptul că graniţa dintre vectori şi adrese de memorie este atât de fină în limbajul C, sintaxa acestuia permite expresii ciudate, de forma:
  
-Cerinte: +<code c> 
-  ​*Scrieti o functie care realizeaza tot suma cifrelor, insa intr-un mod nerecursiv. +char a[100]; 
-  *Modificati functia recursiva astfel incat sa realizeze doar suma cifrelor impare. +a[0= 1; 
-  ​*Modificati functia recursiva astfel incat la prima cifra impara sa nu mai mearga in recursivitate si sa intoarca suma realizata pana in acel moment. +3[a] = 5; 
-  *Luati urmatoarea [[http://​swarm.cs.pub.ro/​~gmuraru/​lab4-example.tar|arhiva]] si observati cum sunt structurate fisierele. Rulati **make** pentru ​crea executabilul si **make clean** pentru stergerea fisierelor generate.+</​code>​
  
-**Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.**+Instrucţiunea din urmă înseamna pur şi simplu "​asignează 5 variabilei ​de tip char de la adresa 3 + a * sizeof(char) = 3 + a"Observaţi că aceasta este echivalentă cu a[3] = 5;
  
-[[https://​drive.google.com/​file/​d/​1BH7Z__2eg5g5sIAMm5AETW4ityNexqk6/​view?​usp=sharing|Checker si teste laborator 4]]+De asemenea, un alt avantaj apare la definirea unui parametru al unei funcţii, de tip vector, caz în care nu este necesară precizarea dimensiunii acestuia: 
 +void sort(int[] vect, n);
  
-<spoiler Cum se foloseste checkerul+<note
-Pentru utilizarea checkerului:​ +Este de remarcat faptul că pentru **tablouri ​de dimensiuni m > 1**, este necesară precizarea **lungimilor ultimelor m - 1 dimensiuni**pentru ca compilatorul să poată calcula adresa fiecărui element atunci când acesta este referit în program. 
-  *Se va scrie cate un fisier sursa pentru ​fiecare problema; +Mai multe detalii legate de reprezentarea tablourilor şi, în general, a datelor în memorie, vor fi date în laboratorul 8. 
-  ​*La finalul fiecarui printf utilizat pentru afisarea rezultatului trebuie sa existe un newline; +</note>
-  ​*Sursa nu trebuie sa contina alte printf-uri in afara de cele care scriu rezultatul asteptat la stdout. +
-  ​*Se va dezarhiva arhiva specifica exercitiului;​ +
-  ​*In directorul curent se afla checkerulexecutabilul generatfolderele de input si output specifice problemei; +
-  *Se va rula “bash checker.sh <executabil>” unde <​executabil>​ este numele executabilului generat;+
  
-</​spoiler>​+=== Exemple de programe ===
  
-<hidden+  * Declararea unei matrici unitate: 
-Link direct către lista completă ​de probleme: [[https://​github.com/​cs-pub-ro/​ComputerProgramming-internal/​tree/​master/​Laboratories/​Lab4|aici]] +<code c
-</​hidden>​+#define M 20 /* nr maxim de linii si de coloane */
  
 +int main() {
 +  float unit[M][M];
 +  int i,j,n;
 +    ​
 +  printf("​nr.linii/​coloane:​ ");
 +  scanf("​%d",​ &n);
 +  if (n > M) {
 +    return;
 +  }
 +    ​
 +  for (i = 0; i < n; i++) {
 +    for (j = 0; j < n; j++) {
 +      if (i != j) {
 +        unit[i][j]=0;​
 +      } else {
 +        unit[i][j]=1;​
 +      }
 +    }
 +  }
 +   
 +  return 0;
 +}
 +</​code>​
  
-===== Exerciţii ​de Laborator =====+  * Citire/​scriere ​de matrice de reali:
  
-  - [2 pct]: Implementați o funcție **int factorial_iterativ(int n)** care returnează n! = 1 * 2 * 3 * … * (n – 1) * n calculat iterativ. Implementați funcția **int factorial_recursiv(int n)** care are acelașscopdar implementarea este recursivă. +<code c> 
-  ​[2 pct]: Scrieți o funcție recursivă și una iterativă care returnează suma cifrelor unui număr natural ​**int suma_cifre_recursiv(int n);** respectiv **int suma_cifre_iterativ(int n);**. +int main() 
-  ​- [1 pct]Scrieți o funcție care determină dacă un număr este prim sau nu (întoarce 1 dacă numărul este prim sau 0 în caz contrar): **int este_prim(int n);**. +  ​int nl, nc, i, j; 
-  ​- [1.5 pct]: Scrieți o funcție care determină dacă un număr natural este palindrom sau nu (returnează 1 în cazul în care este palindrom sau 0 în caz contrar): **int este_palindrom(int ​n);** +  ​float a[20][20]; 
-  ​- [1.5 pct]: Scriețo funcție care citește de la tastatură un număr natural șcalculează câțdivizori sunt numere palindrom. Se va afișa la consolă divizorii care sunt palindromprecum și numărul acestora. Alegeți un nume funcției implementate și apelați această funcție din main pentru ​o testa. +   
-  - [2 pct]: Scrieți o funcție recursivă care citește de la tastatură câte un număr natural (citirea unui număr negativ duce la ignorarea sași incrementează un contor de fiecare data când este tastat un număr prim. Citirea se încheie când se întâlnește numărul 0. Afișați contorul după finalizarea citirii de numere de la tastatură. +  /Citire de matrice ​*
- +  printf("​nr.linii:​ "); 
-===Bonus=== +  scanf("​%d",​ &nl); 
-  ​- [2 pct]: Folosind declarații si definiri de variabile șfuncții, creațdouă fișiere main.c șautentificare.c,​ și apleați din funcția main din fișierul main.c o funcție void login(int username, int passworddefinită în autentificare.c. Funcția login va afișa mesajul “Autentificare cu success!”respectiv “Autentificare esuata!” în urma verificării celor două argumente primite care trebuie să fie numere prime între ele, pentru ca autentificarea să aibă loc cu succes. De asemenea, sursa autentificare.c va cuprinde o variabilă numită status care va fi setată de funcția login cu 1 în cazul autentificării cu success, respective 0 în caz contrar. Afișațîn main, valoarea acestei variabile. Compilați fișierele împreună și executați programul rezultat. Scriți fișierul autentificare.h cu antetul funcției implementată în autentificare.c și NU uitați să îl includeți în main.c. +  ​printf("​nr.coloane")
- <code bash> +  scanf("​%d",​ &nc); 
-gcc main.c autentificare.c./a.out+    ​ 
 +  ​if (nl > 20 || nc > 20
 +    printf("​Eroaredimensiuni > 20 \n"); 
 +    return ; 
 +  ​
 +     
 +  for (= 0; < nl; i++) { 
 +    for (j = 0; j < nc; j++) { 
 +      scanf("​%f"​&a[i][j]); 
 +    } 
 +  } 
 +    ​ 
 +  ​/* Afisare matrice */ 
 +  for (= 0; < nl; i++) { 
 +    for (j = 0; j < nc; j++
 +      printf("​%f ",a[i][j]); 
 +    } 
 +    ​printf ("​\n"​); 
 +  } 
 +   
 +  return 0; 
 +}
 </​code>​ </​code>​
-===== Referinţe ===== 
  
-  * [[http://​www.tutorialspoint.com/​ansi_c/​c_using_functions.htm | C - Using Functions]] +==== Erori comune ====
-  * [[https://​github.com/​cs-pub-ro/​ComputerProgramming/​blob/​master/​Laboratories/​Lab4/​Lab4.pdf | Cheatsheat Functii]]+
  
-<​hidden>​ +  * Inversarea indicilor pentru elementele unei matrice sau tablou. E usor sa-l inversezi pe i cu j in expresia A[i][j] astfel ca trebuie sa fiti atenti cand scrieti astfel de cod. Luati in considerare si folosirea de nume mai sugestive pentru variabile.  
-===== Soluții =====+ 
 +==== Exerciții ==== 
 +Exercițiile pentru laborator se găsesc pe [[https://​acs-pclp.github.io/​laboratoare/​04 | PCLP Laborator04:​ Tablouri. Particularizare:​ vectori și matrici]]. 
 + 
 + 
 +===== Referinţe ===== 
 +  * [[https://​github.com/​cs-pub-ro/​ComputerProgramming/​blob/​master/​Laboratories/​Lab5/​Lab5.pdf| Cheatsheet vectori]] 
 +  * [[http://​en.wikipedia.org/​wiki/​Binary_search | Wikipedia - Căutare binară]] 
 +  * [[http://​en.wikipedia.org/​wiki/​Sorting_algorithm |Wikipedia - Algoritmi de sortare]] 
 +  * [[http://​en.wikipedia.org/​wiki/​Matrix_multiplication | Wikipedia - Matrix Multiplication]] 
 +  * [[http://​ocw.cs.pub.ro/​courses/​so/​laboratoare/​resurse/​gdb | GDB - Tutorial]]
  
  
-</​hidden>​ 
programare/laboratoare/lab04.1760090028.txt.gz · Last modified: 2025/10/10 12:53 by darius.neatu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0