This shows you the differences between two versions of the page.
|
programare:laboratoare:lab11 [2019/12/01 09:04] george.pirtoaca [Exercitii laborator CB/CD] |
programare:laboratoare:lab11 [2025/12/14 16:35] (current) teodor.birleanu [Referinţe] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Structuri. Uniuni. Aplicaţie: Matrice rare ===== | + | ===== PCLP Laborator11: Parametrii liniei de comandă. Preprocesorul. Funcții cu număr variabil de parametri. Opțiuni de compilare ===== |
| - | **Responsabil:** | + | **Resposabili:** |
| - | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip]] (2016) | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2016 - Prezent)]] |
| - | * [[mihaela.vasile@gmail.com|Mihaela Vasile]] | + | * [[neatudarius@gmail.com|Darius Neațu (2016 - Prezent)]] |
| + | * [[mihaela.vasile@gmail.com|Mihaela Vasile (2015)]] | ||
| - | ==== Obiective ==== | + | ==== Obiective ==== |
| - | În urma parcurgerii acestui laborator studentul va fi capabil să: | + | În urma parcurgerii acestui laborator, studentul va fi capabil: |
| - | * organizeze datele din rezolvarea unei probleme complexe în structuri şi uniuni; | + | * să interpreteze şi să manipuleze parametrii liniei de comandă |
| - | * optimizeze scrierea funcţiilor prin minimizarea numărului de parametri şi prin utilizarea structurilor ca parametri returnaţi de funcţie; | + | * să scrie programe care comunică cu exteriorul (utilizatorul, sistemul de operare, alte programe) prin intermediul parametrilor liniei de comandă |
| - | * distingă diferenţa dintre o structură şi o uniune; | + | * să scrie şi să folosească macrodefiniţii |
| - | * evite utilizarea greşită a structurilor. | + | * să utilizeze macrodefiniţiile în lucrul cu mai multe fişiere |
| + | * să evite erorile care apar frecvent în lucrul cu macrodefiniţii | ||
| + | * să realizeze funcţii care primesc un număr variabil de parametri | ||
| + | * să folosească macroinstrucţiunile necesare manipulării stivei de parametri a unei funcţii cu număr variabil de parametri | ||
| + | |||
| + | <note warning> | ||
| + | * Laboratorul curent introduce mai multe notiuni. Pe multe dintre acestea le veti mai intalni si la alte cursuri precum USO si SO. | ||
| + | * Sectiunile marcate cu ** studiu de caz ** nu sunt obligatorii, dar recomandam parcurgerea intregului material pentru o viziune de ansamblu mai buna. | ||
| + | </note> | ||
| ==== Noţiuni teoretice ==== | ==== Noţiuni teoretice ==== | ||
| - | === Structuri === | + | === Parametrii liniei de comandă === |
| - | 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: | + | Pentru a controla execuţia unui program, de multe ori este de dorit furnizarea datelor de lucru înaintea lansării în execuţie a programului, acesta urmând să se execute apoi fără intervenţia utilizatorului (aşa-numitul „batch mode”). Acest lucru se realizează prin intermediul parametrilor liniei de comandă. (Un exemplu cunoscut este lansarea compilatorului gcc în linia de comandă cu diverse argumente, care îi spun ce şi cum sa compileze.) |
| + | Din punct de vedere al utilizatorului, parametrii liniei de comandă sunt simple argumente care se adaugă după numele unui program, în linia de comandă, la rularea sa. Elementele acestei liste de argumente sunt şiruri de caractere separate de spaţii. Argumentele care conţin spaţii pot fi combinate într-un singur argument prin inchiderea acestuia între ghilimele. Shell-ul este cel care se ocupă de parsarea liniei de comandă şi de crearea listei de argumente. | ||
| + | |||
| + | Exemplu de apelare a unui program cu argumente în linia de comandă: | ||
| + | <code bash> | ||
| + | gcc -Wall -I/usr/include/sys -DDEBUG -o "My Shell" myshell.c | ||
| + | </code> | ||
| + | |||
| + | În acest caz, argumentele liniei de comandă sunt în acest caz: | ||
| + | * gcc | ||
| + | * -Wall | ||
| + | * -I/usr/include/sys | ||
| + | * -DDEBUG | ||
| + | * -o | ||
| + | * My Shell | ||
| + | * myshell.c | ||
| + | |||
| + | Din punct de vedere al programatorului, parametrii liniei de comandă sunt accesibili prin utilizarea parametrilor funcţiei main(). Astfel, când se doreşte folosirea argumentelor liniei de comandă, funcţia main() se va defini astfel: | ||
| <code c> | <code c> | ||
| - | struct nume_structura { | + | int main(int argc, char *argv[]) |
| - | declaratii_de_variabile | + | |
| - | }; | + | |
| </code> | </code> | ||
| - | Definiția unei structuri poate fi urmată imediat de declararea de variabile de acel tip, forma cea mai generală fiind: | + | Astfel, funcţia **main()** primeşte, în mod formal, doi parametri, un întreg şi un vector de şiruri de caractere. Numele celor două variabile nu e obligatoriu să fie **argc** şi **argv**, dar tipul lor, da. Semnificaţia lor este următoarea: |
| + | * **int argc** (argument count) - reprezintă numărul de parametrii ai liniei de comandă. După cum se vede din exemplul anterior, există cel puţin un parametru, acesta fiind chiar numele programului (numele care a fost folosit pentru a lansa în execuţie programul - şi care poate fi diferit de numele executabilului - de exemplu prin crearea unui symlink, în Linux). | ||
| + | * **char *argv[]** (arguments value) - reprezintă un vector de şiruri de caractere, având **argc** elemente (indexate de la **0** la **argc - 1**). Întotdeauna **argv[0]** conţine **numele programului**. | ||
| + | |||
| + | Parametrii liniei de comandă se pot accesa prin intermediul vectorului **argv** şi pot fi prelucraţi cu funcţiile standard de prelucrare a şirurilor de caractere. | ||
| + | |||
| + | |||
| + | <spoiler Exemplu1 > | ||
| + | Fie urmatorul cod sursa care afiseaza numarul de argumente primite, apoi afiseaza numele extras al executabilului din calea primitita ca argument. | ||
| <code c> | <code c> | ||
| - | struct [structure tag] { | + | #include <stdio.h> |
| + | #include <string.h> // strrchr | ||
| - | member definition; | + | int main(int argc, char *argv[]) { |
| - | member definition; | + | // afiseaza numarul de argumente + primul argument |
| - | ... | + | printf("Am primit %d argument(e)\n", argc); |
| - | member definition; | + | printf("argv[0] = %s\n", argv[0]); |
| - | } [one or more structure variables]; | + | |
| + | // extrage numele executabilului | ||
| + | char *program_name = strrchr(argv[0], '/') + 1; | ||
| + | printf("Eu sunt programul %s\n", program_name); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| </code> | </code> | ||
| - | Unde între ''[]'' se află câmpurile opționale. | + | Pentru compilare vom rula comanda: |
| + | <code bash> | ||
| + | gcc -Wall test.c -o gigel | ||
| + | </code> | ||
| - | Exemple: | + | Exemple de rulare: |
| + | <code bash> | ||
| + | darius@pc ~ $ ./gigel | ||
| + | Am primit 1 argument(e) | ||
| + | argv[0] = ./gigel | ||
| + | Eu sunt programul gigel | ||
| + | |||
| + | darius@pc ~ $ /home/darius/gigel | ||
| + | Am primit 1 argument(e) | ||
| + | argv[0] = /home/darius/gigel | ||
| + | Eu sunt programul gigel | ||
| + | |||
| + | darius@pc ~ $ ./gigel Gigel e pe val! | ||
| + | Am primit 5 argument(e) | ||
| + | argv[0] = ./gigel | ||
| + | Eu sunt programul gigel | ||
| + | |||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | === Preprocesorul === | ||
| + | |||
| + | Preprocesorul este componenta din cadrul compilatorului C care realizează preprocesarea. În urma acestui pas, toate instrucţiunile de preprocesare sunt **înlocuite (substituite)**, pentru a genera cod C „pur”. Preprocesarea este o **prelucrare exclusiv textuală** a fişierului sursă. În acest pas nu se fac nici un fel de verificări sintactice sau semantice asupra codului sursă, ci doar sunt efectuate substituţiile din text. Astfel, preprocesorul va prelucra şi fişiere fără nici un sens în C. | ||
| + | |||
| + | Se consideră următorul exemplu: | ||
| <code c> | <code c> | ||
| - | struct student { | + | #define EA Ana |
| - | char nume[40]; | + | #define si C |
| - | int an; | + | #ifdef CIFRE |
| - | float medie; | + | #define CINCI 5 |
| - | }; | + | #define DOUA 2 |
| + | EA are mere. Mara are DOUA pere shi CINCI cirese. | ||
| + | #endif | ||
| + | Vasilica vrea sa cante o melodie in si bemol. | ||
| </code> | </code> | ||
| - | Variabilele declarate in interiorul structurii se numesc "campurile" structurii: | + | La rularea comenzii |
| - | *nume; | + | <code bash> |
| - | *an; | + | gcc -E -DCIFRE rubbish.c |
| - | *medie. | + | </code> |
| + | |||
| + | se va obţine următoare ieşire (se cere compilatorului să execute doar pasul de preprocesare (-E), definind în acelaşi timp şi simbolul CIFRE (-DCIFRE) : | ||
| <code c> | <code c> | ||
| - | struct complex { /* pentru memorarea unui număr complex cu dublă precizie */ | + | # 1 "rubbish.c" |
| - | double re; | + | # 1 "<built-in>" |
| - | double im; | + | # 1 "<command line>" |
| - | }; | + | # 1 "rubbish.c" |
| + | Ana are mere. Mara are 2 pere shi 5 cirese. | ||
| + | Vasilica vrea sa cante o melodie in C bemol. | ||
| </code> | </code> | ||
| - | Declararea şi iniţializarea unor variabile de tip structură se poate face astfel: | + | Cele mai importante instrucţiuni de preprocesare sunt prezentate în continuare. |
| + | |||
| + | == Incluziune == | ||
| + | |||
| + | Probabil cea mai des folosită instrucţiune de preprocesare este cea de incluziune, de forma | ||
| <code c> | <code c> | ||
| - | struct student s1 = {"Popescu Ionel", 3, 9.25}; | + | #include <nume_fişier> |
| - | struct complex c1, c2; | + | |
| - | struct complex v[10]; | + | |
| </code> | </code> | ||
| + | sau | ||
| + | <code c> | ||
| + | #include "nume_fisier" | ||
| + | </code> | ||
| + | care are ca rezultat înlocuirea sa cu conţinutul fişierului specificat de **nume_fişier**. Diferenţa dintre cele două versiuni este că cea cu paranteze unghiulare caută **nume_fişier** doar în directorul standard de fişiere antet (numit deobicei **include**), iar cea cu ghilimele caută atât în directorul **include** cât şi în directorul curent. | ||
| - | Pentru simplificarea declaraţiilor, putem asocia unei structuri un nume de tip de date: | + | === Definirea de simboluri === |
| + | |||
| + | Definirea de simboluri este cel mai des folosită în conjuncţie cu instrucţiunile de procesare condiţionată, fiind folosită pentru activarea şi dezactivarea unor segmente de cod în funcţie de prezenţa unor simboluri. Definirea unui simbol se face în cod cu instrucţiunea | ||
| <code c> | <code c> | ||
| - | typedef struct student Student; | + | #define SIMBOL |
| + | </code> | ||
| + | sau se poate realiza şi la compilare, prin folosirea flagului -D al compilatorului (după cum am văzut în exemplul precedent). | ||
| + | |||
| + | Un simbol poate fi de asemenea „şters” folosind instrucţiunea | ||
| + | <code c> | ||
| + | #undef SIMBOL | ||
| + | </code> | ||
| + | în cazul în care nu se mai doreşte prezenţa simbolului de preprocesor ulterior definirii sale. | ||
| + | |||
| + | <spoiler Exemplu2 (studiu de caz)> | ||
| + | |||
| + | Codul urmator defineste cateva simboluri. Simbolul CHANGE este definit la compilare. Definirea acestuia determina modificarea simbolului GIGEL. | ||
| + | |||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | |||
| + | // am definit GIGEL ca fiind 1 | ||
| + | #define GIGEL 1 | ||
| + | |||
| + | // Verific daca este definit simbolul CHANGE | ||
| + | #ifdef CHANGE | ||
| + | #undef GIGEL // sterg GIGEL | ||
| + | #define GIGEL 0 // redefinesc simbolul cu alta valoare | ||
| + | #endif | ||
| + | |||
| + | int main() { | ||
| + | printf("GIGEL = %d\n", GIGEL); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Observati diferentele intre cele doua rulari. | ||
| + | <code bash> | ||
| + | darius@pc ~ gcc -Wall -Wextra test.c -o test -DCHANGE=2017 | ||
| + | darius@pc ~ $ ./test | ||
| + | GIGEL = 0 | ||
| + | |||
| + | darius@pc ~ $ gcc -Wall -Wextra test.c -o test | ||
| + | darius@pc ~ $ ./test | ||
| + | GIGEL = 1 | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | == Definirea de macro-uri == | ||
| + | |||
| + | Instrucţiunile de preprocesare mai pot fi folosite şi pentru definirea de constante simbolice şi macroinstrucţiuni. De exemplu | ||
| + | <code c> | ||
| + | #define IDENTIFICATOR valoare | ||
| + | </code> | ||
| + | va duce la înlocuirea peste tot în cadrul codului sursă a şirului **IDENTIFICATOR** cu şirul **valoare**. Înlocuirea nu se face totuşi în interiorul şirurilor de caractere. | ||
| + | |||
| + | Un exemplu foarte bun este definirea unor macro-uri pentru dimensiunile tablourilor alocate static. | ||
| + | <code c> | ||
| + | #define NMAX 100 | ||
| + | #define MMAX 10 | ||
| + | #define LMAX 10000 | ||
| ... | ... | ||
| - | Student s1, s2, s3; | + | int a[NMAX][MMAX]; |
| + | ... | ||
| + | int v[LMAX]; | ||
| + | ... | ||
| </code> | </code> | ||
| - | 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: | + | O macroinstrucţiune este similară unei constante simbolice, ca definire, dar acceptă parametrii. Este folosită în program în mod asemănător unei funcţii, dar la compilare, ea este înlocuită în mod textual cu corpul ei. În plus, nu se face nici un fel de verificare a tipurilor. Spre exemplu: |
| + | <code c> | ||
| + | #define MAX(a, b) a > b ? a : b | ||
| + | </code> | ||
| + | va returna maximul dintre a şi b, iar | ||
| + | <code c> | ||
| + | #define DUBLU(a) 2*a | ||
| + | </code> | ||
| + | va returna dublul lui a. | ||
| + | <note important> | ||
| + | Deoarece preprocesarea este o prelucrare textuală a codului sursă, în cazul exemplului de mai sus, macroinstrucţiunea în forma prezentată nu va calcula întotdeauna dublul unui număr. | ||
| + | </note> | ||
| + | |||
| + | Astfel, la o folosire de forma: | ||
| <code c> | <code c> | ||
| - | typedef struct student { | + | DUBLU(a + 3) |
| - | char nume[40]; | + | </code> |
| - | int an; | + | în pasul de preprocesare se va genera expresia |
| - | float medie; | + | <code c> |
| - | } Student; | + | 2*a+3 |
| + | </code> | ||
| + | care bineînţeles că nu realizează funcţia dorită. | ||
| + | |||
| + | Pentru a evita astfel de probleme, este bine ca întotdeauna în corpul unui macro, numele „parametrilor” să fie închise între paranteze: | ||
| + | <code c> | ||
| + | #define SQUARE(a) (a)*(a) | ||
| + | </code> | ||
| + | |||
| + | <spoiler Exemplu3 (studiu de caz)> | ||
| + | |||
| + | Fie simbolul SQUARE, care inlocuieste o expresie cu expresia ridicata la patrat. Acesta poate fi definit astfel. | ||
| + | <code c> | ||
| + | #define SQUARE(a) (a)*(a) | ||
| + | </code> | ||
| + | Este aceasta expresie este corecta? (oricare ar fi a, va calcula patratul lui a?) Ce se intampla daca inaintea lui **SQUARE** aplicam un operand care are prioritate mai mare decat ** * **? | ||
| + | |||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | |||
| + | #define SQUARE(a) (a) * (a) | ||
| int main() { | int main() { | ||
| - | /* Ambele declaratii de mai jos sunt valide */ | + | // ceea ce se obtine |
| - | struct student s1; | + | printf("%d\n", ~SQUARE(2)); |
| - | Student s2; | + | |
| + | // ceea ce se doreste | ||
| + | printf("%d\n", ~4); | ||
| + | |||
| + | return 0; | ||
| } | } | ||
| + | |||
| </code> | </code> | ||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | -6 // ceea ce se obtine | ||
| + | -5 // ceea ce se doreste | ||
| + | </code> | ||
| + | Operatiile nu se vor executa in ordinea in care dorim. ** ~ ** are precedenta mai mare decat ** * **, deci mai intai se va executa operatia pe biti apoi inmultirea. | ||
| - | Accesul la membrii unei structuri se face prin operatorul ".": | + | Pentru a fi convinsi ca se intampla acest lucru, putem compila cu -E si sa inspectam codul urmator. |
| <code c> | <code c> | ||
| - | s1.an = 3; | + | ... // continut din stdio.h, alte simboluri etc. |
| + | |||
| + | int main() { | ||
| + | printf("%d\n", ~(2) * (2)); | ||
| + | |||
| + | printf("%d\n", ~4); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| </code> | </code> | ||
| - | În cazul pointerilor la structuri, accesul la membri se poate face astfel: | + | Pentru a corecta aceasta greseala, putem adauga inca o paranteza in definitia lui SQUARE. |
| <code c> | <code c> | ||
| - | Student *stud = (Student *)malloc(sizeof(Student)); | + | #define SQUARE(a) ( (a) * (a) ) |
| - | (*stud).medie = 9.31; | + | |
| - | /* altă modalitate mai simplă şi mai des folosită: */ | + | |
| - | stud->medie = 9.31; | + | |
| </code> | </code> | ||
| + | Pentru noua sursa C obtinem urmatorul rezultat. | ||
| - | Atribuirile de structuri se pot face astfel: | + | <code bash> |
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | -5 // ceea ce se obtine | ||
| + | -5 // ceea ce se doreste | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | <spoiler Exemplu4 (studiu de caz)> | ||
| + | |||
| + | Sa incercam sa definim un simbol corect pentru aflarea maximului intre 2 expresii numerice. | ||
| + | |||
| + | Mai sus a fost prezentata urmatoarea varianta. | ||
| + | <code c> | ||
| + | #define MAX(a, b) a > b ? a : b | ||
| + | </code> | ||
| + | |||
| + | Fie urmatorul cod C. | ||
| + | <code C> | ||
| + | #include <stdio.h> | ||
| + | |||
| + | #define MAX(a, b) a > b ? a : b | ||
| + | |||
| + | int main() { | ||
| + | printf("max = %d\n", 1 << MAX(19 + 1, 10 + 1)); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | Ceea ce ne-am astepta este sa se evalueze cele doua expresii (19 + 1 == 20, 10 + 1 == 11), sa sa calculeze maximul (20) si sa se afiseze ** 1048576 ** (1 << 20). | ||
| + | |||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | test.c: In function ‘main’: | ||
| + | test.c:6:28: warning: suggest parentheses around ‘+’ inside ‘<<’ [-Wparentheses] | ||
| + | printf("max = %d\n", 1 << MAX(19 + 1, 10 + 1)); | ||
| + | ^ | ||
| + | max = 20 | ||
| + | </code> | ||
| + | |||
| + | Din nou precedenta operatorilor ** + ** si ** << ** impiedica obtinerea rezulatului dorit. | ||
| + | |||
| + | Solutia este sa punem si la MAX paranteze. | ||
| + | <code c> | ||
| + | #define MAX(a, b) ((a) > (b) ? (a) : (b)) | ||
| + | </code> | ||
| + | |||
| + | Vom obtine urmatorul rezultat. | ||
| + | |||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | max = 1048576 | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | |||
| + | <spoiler Exemplu5 (studiu de caz)> | ||
| + | |||
| + | Sa se defineasca un simbol SWAP care sa interschimbe doua variabile de acelasi tip, tipul fiind precizat. | ||
| + | |||
| + | Pornim de la o posibila secventa de cod care realizeaza acest lucru pentru doua variabile int. | ||
| <code c> | <code c> | ||
| - | struct complex n1, n2; | + | int a, b; |
| ... | ... | ||
| - | n2 = n1; | + | int tmp = a; |
| + | a = b; | ||
| + | b = tmp; | ||
| + | </code> | ||
| + | Observati ca substituind textual cuvantul "int" cu "double", bucata de cod nu isi schimba logica. Generalizarea acestui cod, presupune generalizarea tipului acestor variabile. Daca am nota acest type cu "type" (presupunand ca exista acest tip), atunci codul obtinut este urmatorul. | ||
| + | <code c> | ||
| + | type a, b; | ||
| + | ... | ||
| + | type tmp = a; | ||
| + | a = b; | ||
| + | b = tmp; | ||
| </code> | </code> | ||
| - | Prin această atribuire se realizează o copiere bit cu bit a elementelor lui n1 în n2. | + | Solutia intuitiva de definire a simbolului SWAP este urmatoarea. |
| + | <code c> | ||
| + | #include <stdio.h> | ||
| - | 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. | + | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; |
| + | |||
| + | int main() { | ||
| + | // interschimbare int | ||
| + | int x = 1, y = 2; | ||
| + | printf("before: x = %d y = %d\n", x, y); | ||
| + | SWAP(int, x, y) | ||
| + | printf("after : x = %d y = %d\n", x, y); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | In urma precompilarii, functia main arata astfel. | ||
| <code c> | <code c> | ||
| - | typedef struct { | + | ... |
| - | int data; | + | int main() { |
| - | int text; | + | |
| - | } S1; // este un typedef pentru S1, functional in C si C++ | + | |
| - | struct S2 { | + | int x = 1, y = 2; |
| - | int data; | + | printf("before: x = %d y = %d\n", x, y); |
| - | int text; | + | int tmp = x; x = y; y = tmp; |
| - | }; // este un typedef pentru struct S2 | + | printf("after : x = %d y = %d\n", x, y); |
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | Sursa compileaza si ruleaza corect. | ||
| + | <code c> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| - | struct { | + | before: x = 1 y = 2 |
| - | int data; | + | after : x = 2 y = 1 |
| - | int text; | + | </code> |
| - | } S3; | + | |
| - | // este o declaratie a lui S3, variabila de tip struct nu defineste un tip | + | |
| - | // spune compilatorului sa aloce memorie pentru variablia S3 | + | |
| - | typedef struct S4 { | + | Urmatoarea sursa nu va compila, intrucat la folosirea repetata a lui SWAP in acelasi scop, variabila tmp va fi declarate de mai multe ori. |
| - | int data; | + | <code c> |
| - | int text; | + | #include <stdio.h> |
| - | } S4; // este un typedef atât pentru struct S4, cât și pentru S4 | + | #include <stdlib.h> |
| + | #include <string.h> | ||
| + | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; | ||
| int main() { | int main() { | ||
| - | // ce se intampla la declarare variabile de tip S1,S2,S3 | + | // interschimbare int |
| - | S1 mine1; // este un typedef si va merge | + | int x = 1, y = 2; |
| - | struct S2 mine2; // este un typedef si va merge | + | printf("before: x = %d y = %d\n", x, y); |
| - | S2 mine22; // S2 NU este un typedef si NU va merge | + | SWAP(int, x, y) |
| - | S3 mine3; // nu va merge pt ca S3 nu este un typedef. | + | printf("after : x = %d y = %d\n", x, y); |
| - | struct S4 mine4; // este un typedef și va merge | + | |
| - | S4 mine4; // este un typedef și va merge | + | |
| - | + | // interschimbare pointeri (char *) | |
| - | // ce se intampla la utilizarea ca variabile a S1,S2,S3 | + | char *p = strdup("A"); |
| - | S1.data = 5; // da eroare deoarece S1 este numai un typedef. | + | char *q = strdup("B"); |
| - | struct S2.data = 5; // da eroare deoarece S2 este numai un typedef. | + | printf("before: p = (%s) q = (%s)\n", p, q); |
| - | S3.data = 5; // merge doarece S3 e o variabila | + | SWAP(char *, p, q); |
| - | return 0; | + | printf("after : p = (%s) q = (%s)\n", p, q); |
| + | |||
| + | // elibereaza memoria alocata dinamic | ||
| + | free(p); | ||
| + | free(q); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test && ./test | ||
| + | test.c: In function ‘main’: | ||
| + | test.c:5:29: error: conflicting types for ‘tmp’ | ||
| + | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; | ||
| + | ^ | ||
| + | test.c:19:5: note: in expansion of macro ‘SWAP’ | ||
| + | SWAP(char *, p, q); | ||
| + | ^ | ||
| + | test.c:5:29: note: previous definition of ‘tmp’ was here | ||
| + | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; | ||
| + | ^ | ||
| + | test.c:11:5: note: in expansion of macro ‘SWAP’ | ||
| + | SWAP(int, x, y) | ||
| + | </code> | ||
| + | Solutia este sa folosim un scop local pentru tmp ([[https://en.wikipedia.org/wiki/Scope_(computer_science)#Levels_of_scope | scope]]). Pentru lizibilitate putem scrie instructiunile din SWAP pe mai multe randuri. In acest caz vom pune caracterul '\' pentru a marca faptul ca simbolul continua pe randul urmator. | ||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | #include <stdlib.h> | ||
| + | #include <string.h> | ||
| + | |||
| + | #define SWAP(type,x,y) {\ | ||
| + | type tmp = x; \ | ||
| + | x = y; \ | ||
| + | y = tmp; \ | ||
| + | } | ||
| + | |||
| + | int main() { | ||
| + | // interschimbare int | ||
| + | int x = 1, y = 2; | ||
| + | printf("before: x = %d y = %d\n", x, y); | ||
| + | SWAP(int, x, y) | ||
| + | printf("after : x = %d y = %d\n", x, y); | ||
| + | |||
| + | |||
| + | // interschimbare pointeri (char *) | ||
| + | char *p = strdup("A"); | ||
| + | char *q = strdup("B"); | ||
| + | printf("before: p = (%s) q = (%s)\n", p, q); | ||
| + | SWAP(char *, p, q); | ||
| + | printf("after : p = (%s) q = (%s)\n", p, q); | ||
| + | |||
| + | // elibereaza memoria alocata dinamic | ||
| + | free(p); | ||
| + | free(q); | ||
| + | |||
| + | return 0; | ||
| } | } | ||
| </code> | </code> | ||
| - | <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. | + | Inspectati codul dupa etapa de preprocesare. Compilati si rulati codul. |
| + | </spoiler> | ||
| + | <note warning> | ||
| + | * ATENTIE! O variabila este vizibila in scopul ([[https://en.wikipedia.org/wiki/Scope_(computer_science)#Levels_of_scope | scope]]) in care a fost declarata. La iesirea din scop, zona de memorie corespunzatoare este eliberata([[https://blog.feabhas.com/2010/09/scope-and-lifetime-of-variables-in-c/ | Scope and Lifetime of Variables in C]]). | ||
| </note> | </note> | ||
| - | === Diferenţa dintre copierea structurilor şi copierea pointerilor === | + | === Instrucţiuni de compilare condiţionată === |
| - | Pentru exemplificarea diferenţei dintre copierea structurilor şi copierea pointerilor să considerăm urmatorul exemplu: | + | Instrucţiunile de compilare condiţionată sunt folosite pentru a „ascunde” fragmente de cod în funcţie de anumite condiţii. Formatul este următorul: |
| <code c> | <code c> | ||
| - | struct exemplu { | + | #if conditie |
| - | int n; | + | .... |
| - | char *s; | + | #else |
| - | } | + | .... |
| - | struct exemplu s1, s2; | + | #endif |
| - | char *litere = "abcdef"; | + | </code> |
| - | s1.n = 5; | + | unde conditie este este o expresie constantă întreagă. Pentru realizarea de expresii cu mai multe |
| - | s1.s = strdup(litere); | + | opţiuni se poate folosi şi forma **#elif**: |
| - | s2 = s1; | + | <code c> |
| - | s2.s[1]='x'; | + | #if conditie |
| + | ... | ||
| + | #elif conditie2 | ||
| + | ... | ||
| + | #elif conditie3 | ||
| + | ... | ||
| + | #else | ||
| + | ... | ||
| + | #endif | ||
| </code> | </code> | ||
| - | 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. | + | De obicei condiţia testează existenţa unui simbol. Scenariile tipice de folosire sunt: |
| + | * dezactivarea codului de debug o dată ce problemele au fost remediate | ||
| + | * compilare condiţionată în funcţie de platforma de rulare | ||
| + | * prevenirea includerii multiple a fişierelor antet | ||
| - | 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. | + | În aceste cazuri se foloseşte forma |
| + | <code c> | ||
| + | #ifdef SIMBOL | ||
| + | </code> | ||
| + | sau | ||
| + | <code c> | ||
| + | #ifndef SIMBOL | ||
| + | </code> | ||
| + | care testează dacă simbolul SIMBOL este definit, respectiv nu este definit. | ||
| - | Un alt caz (diferit de cel expus anterior) este cel al atribuirii aceleiaşi structuri către două variabile pointer diferite: | + | <spoiler Exemplu 6 (studiu de caz)> |
| + | |||
| + | Directivele de preprocesor ar putea fi utile pentru a scrie cod portabil. De exemplu, putem identifica tipul sistemului de operare sau arhitectura procesorului. In exemplul urmator, se doreste folosirea unei functii ''f'' care sa aiba implementare de dependenta de sistemul de operare. | ||
| <code c> | <code c> | ||
| - | struct exemplu { | + | #include <stdio.h> |
| - | int n; | + | |
| - | char * s; | + | #ifdef _WIN32 // is _WIN32 defined? |
| + | //do something for windows like #include <windows.h> | ||
| + | void f() { | ||
| + | printf("_WIN32\n"); | ||
| + | } | ||
| + | #elif defined __unix__ // or is __unix__ defined? | ||
| + | //do something for unix like #include <unistd.h> | ||
| + | void f() { | ||
| + | printf("__unix__\n"); | ||
| + | } | ||
| + | #elif defined __APPLE__ // or is __APPLE__ defined? | ||
| + | //do something for mac | ||
| + | void f() { | ||
| + | printf("__APPLE__\n"); | ||
| + | } | ||
| + | #endif | ||
| + | |||
| + | int main() { | ||
| + | f(); | ||
| + | return 0; | ||
| } | } | ||
| - | struct exemplu s1; | ||
| - | struct exemplu *p1; | ||
| - | struct exemplu *p2; | ||
| - | p1 = &s1; | ||
| - | p2 = &s2; | ||
| </code> | </code> | ||
| - | Î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. | + | Sa ne uitam la fisierul C obtinut in urma etapei de preprocesare pe un **sistem UNIX**. |
| - | === Uniuni === | + | <code bash> |
| - | 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). | + | gcc -Wall -Wextra -E test.c |
| + | </code> | ||
| - | Uniunile se pot declara astfel: | ||
| <code c> | <code c> | ||
| - | union numere { | + | ... |
| - | int i; | + | void f() { |
| - | float f; | + | printf("__unix__\n"); |
| - | double v; | + | } |
| - | }; /* se poate utiliza si typedef... */ | + | int main() { |
| + | f(); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | Se observa disparitia acelor directive de preprocesare si faptul ca sursa obtinuta contine doar implementarea pentru Unix. | ||
| + | Daca rulam aceeasi comanda pe Windows (32bit sau 64 bit) sursa obtinut va fi urmatoarea. | ||
| + | <code c> | ||
| + | ... | ||
| + | void f() { | ||
| + | printf("_WIN32\n"); | ||
| + | } | ||
| + | int main() { | ||
| + | f(); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| - | union numere u1, u2; | + | La rularea executabilului, se va afisa pe ecran stringul corespunzator sistemului de operare pe care a fost compilat executabilul. |
| + | </spoiler> | ||
| + | |||
| + | == Includere multipla == | ||
| + | Prevenirea includerii multiple a fişierelor antet se realizează astfel: | ||
| + | |||
| + | <code c> | ||
| + | #ifndef _NUME_FISIER_ANTET_ | ||
| + | #define _NUME_FISIER_ANTET_ | ||
| + | /* corpul fisierului antet */ | ||
| + | /* prototipuri de functii, declaratii de tipuri si de constante */ | ||
| + | #endif | ||
| </code> | </code> | ||
| - | 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. | + | Astfel, la prima includere a fişierului antet, simbolul **_NUME_FISIER_ANTET_** nu este definit. Preprocesorul execută ramura **#ifndef** în care este definit simbolul **_NUME_FISIER_ANTET_** şi care conţine şi corpul - conţinutul util - al fişierului antet. La următoarele includeri ale fişierului antet simbolul **_NUME_FISIER_ANTET_** va fi definit iar preprocesorul va sări direct la sfârşitul fişierului antet, după **#endif**. |
| + | |||
| + | <spoiler Exemplu7(studiu de caz) > | ||
| + | |||
| + | Vom face o ierarhie de fisiere pentru a vedea utilizarea ** garzilor ** ([[ https://en.wikipedia.org/wiki/Include_guard | include guard]]). | ||
| + | |||
| + | Fie sursa test.c in care dorim sa apelam functia ** pc ** din fisierul antet **pc.h** si functia ** run ** din fisierul antet ** uso.h **. | ||
| - | Exemplu: | ||
| <code c> | <code c> | ||
| #include <stdio.h> | #include <stdio.h> | ||
| - | #include <stdlib.h> | + | #include "uso.h" |
| + | #include "pc.h" | ||
| - | typedef union { | + | int main() { |
| - | int Wind_Chill; | + | pc(); |
| - | char Heat_Index; | + | run(); |
| - | } Condition; | + | return 0; |
| + | } | ||
| - | typedef struct { | + | </code> |
| - | float temp; | + | Fisierul **pc.h** are urmatorul continut. |
| - | Condition feels_like; | + | <code c> |
| - | } Temperature; | + | #include "uso.h" |
| + | |||
| + | void write_in_c() { | ||
| + | printf("write code in C\n"); | ||
| + | } | ||
| + | |||
| + | void pc() { | ||
| + | write_in_c(); | ||
| + | compile(); | ||
| + | run(); | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | Fisierul **uso.h** are urmatorul continut. | ||
| + | <code c> | ||
| + | void compile() { | ||
| + | printf("compile with gcc\n"); | ||
| + | } | ||
| + | |||
| + | void run() { | ||
| + | printf("run\n"); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Daca compilam fisierul sursa C vom obtine urmatoarele erori. | ||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | In file included from pc.h:1:0, | ||
| + | from test.c:3: | ||
| + | uso.h:1:6: error: redefinition of ‘compile’ | ||
| + | void compile() { | ||
| + | ^ | ||
| + | In file included from test.c:2:0: | ||
| + | uso.h:1:6: note: previous definition of ‘compile’ was here | ||
| + | void compile() { | ||
| + | ^ | ||
| + | In file included from pc.h:1:0, | ||
| + | from test.c:3: | ||
| + | uso.h:5:6: error: redefinition of ‘run’ | ||
| + | void run() { | ||
| + | ^ | ||
| + | In file included from test.c:2:0: | ||
| + | uso.h:5:6: note: previous definition of ‘run’ was here | ||
| + | void run() { | ||
| + | </code> | ||
| + | Functiile compile si run au fost definite de doua ori. Pentru a inspecta aceasta problema oprim, din nou, compilarea in faza de preprocesare. | ||
| + | <code bash> | ||
| + | ... | ||
| + | # 2 "test.c" 2 | ||
| + | # 1 "uso.h" 1 | ||
| + | |||
| + | # 1 "uso.h" // aici se include prima oara "uso.h" (linia ''#include "uso.h"'' din "test.c") | ||
| + | void compile() { | ||
| + | printf("compile with gcc\n"); | ||
| + | } | ||
| + | |||
| + | void run() { | ||
| + | printf("run\n"); | ||
| + | } | ||
| + | # 3 "test.c" 2 | ||
| + | # 1 "pc.h" 1 | ||
| + | # 1 "uso.h" 1 // aici se include a doua oara "uso.h" (linia ''#include "pc.h"'' din "test.c" include "pc.h", | ||
| + | //care contine linia ''#include "uso.h"'', instructiune in urma careia se va include din nou "uso.h" | ||
| + | |||
| + | void compile() { | ||
| + | printf("compile with gcc\n"); | ||
| + | } | ||
| + | |||
| + | void run() { | ||
| + | printf("run\n"); | ||
| + | } | ||
| + | # 2 "pc.h" 2 | ||
| + | |||
| + | void write_in_c() { | ||
| + | printf("write code in C\n"); | ||
| + | } | ||
| + | |||
| + | void pc() { | ||
| + | write_in_c(); | ||
| + | compile(); | ||
| + | run(); | ||
| + | } | ||
| + | # 4 "test.c" 2 | ||
| int main() { | int main() { | ||
| + | pc(); | ||
| + | run(); | ||
| + | return 0; | ||
| + | } | ||
| - | Temperature *tmp; | + | </code> |
| - | 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); | + | |
| - | return 0; | + | Aplicam solutia de mai sus. Definim in fiecare fisier header cate un simbol. |
| + | Definim simbolul ** PC_H ** in **pc.h**. | ||
| + | <code c> | ||
| + | #ifndef PC_H // daca este prima oara | ||
| + | // cand acest fiser este inclus | ||
| + | #define PC_H // defineste simbolul pentru a nu | ||
| + | // nu permite o includere ulterioara | ||
| + | |||
| + | #include "uso.h" | ||
| + | |||
| + | void write_in_c() { | ||
| + | printf("write code in C\n"); | ||
| } | } | ||
| + | |||
| + | void pc() { | ||
| + | write_in_c(); | ||
| + | compile(); | ||
| + | run(); | ||
| + | } | ||
| + | |||
| + | #endif // sfarsitul codului care depinde de | ||
| + | // existenta simbolului PC_H | ||
| + | </code> | ||
| + | Definim simbolul ** USO_H ** in fisierul **uso.h**. | ||
| + | <code c> | ||
| + | #ifndef USO_H // daca este prima oara | ||
| + | // cand acest fiser este inclus | ||
| + | #define USO_H // defineste simbolul pentru a nu | ||
| + | // nu permite o includere ulterioara | ||
| + | |||
| + | void compile() { | ||
| + | printf("compile with gcc\n"); | ||
| + | } | ||
| + | |||
| + | void run() { | ||
| + | printf("run\n"); | ||
| + | } | ||
| + | |||
| + | #endif // sfarsitul codului care depinde de | ||
| + | // existenta simbolului USO_H | ||
| </code> | </code> | ||
| - | La rulare va afişa: | + | Fisierele antet vor fi incluse o singura data, codul va compila. |
| <code c> | <code c> | ||
| - | Address of Temperature = 165496 | + | gcc -Wall -Wextra test.c -o test |
| - | Address of temp = 165496, feels_like = 165500 | + | ./test |
| - | Wind_Chill = 165500, Heat_Index= 16550 | + | |
| + | write code in C | ||
| + | compile with gcc | ||
| + | run | ||
| + | run | ||
| </code> | </code> | ||
| + | </spoiler> | ||
| - | ===== Exercitii laborator CB/CD ===== | + | == Alte instrucţiuni == |
| - | Codul sursa se gaseste [[http://swarm.cs.pub.ro/~gmuraru/PC/lab10.c|aici]] | + | <code c> |
| - | Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul numara cate persoane au acelasi prenume. | + | #pragma expresie |
| + | </code> | ||
| + | Sunt folosite pentru a controla din codul sursă comportamentul compilatorului (modul în care generează cod, alinierea structurilor, etc.) iar formatul lor diferă de la compilator la compilator. Pentru a determina ce opţiuni **#pragma** aveţi la dispoziţie consultaţi manualul compilatorului. | ||
| - | Cerinte: | + | <code c> |
| - | *Sa se afiseze cate persoane s-au nascut in acelasi an | + | #error MESSAGE |
| - | *Sa se sorteze persoanele dupa nume | + | </code> |
| + | La întâlnirea acestei instrucţiuni de preprocesare compilatorul va raporta o eroare, având ca text explicativ mesajul **MESSAGE**. | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | + | <code c> |
| + | #line NUMBER FILENAME | ||
| + | </code> | ||
| + | Această instrucţiune de preprocesare modifică numărul liniei curente în valoarea specificată de **NUMBER**. În cazul în care este prezent şi parametru opţional **FILENAME** este modificat şi numele fişierului sursă curent. Astfel, mesajele de eroare şi avertismentele produse de compilator vor folosi numere de linie (şi eventual nume de fişiere) inexistente, „imaginare”, conform acestei instrucţiuni. | ||
| - | [[ https://ocw.cs.pub.ro/courses/programare/checker | Tutorial folosire checker laborator ]] | + | === Funcţii cu număr variabil de parametri === |
| - | <hidden> | + | == Introducere == |
| - | Link direct către lista completă de probleme: [[https://docs.google.com/document/d/1Q374YAA_JopvxJNv-WG51xA6_bSDJSS3nha63XH1N4I/edit?usp=sharing|aici]] | + | Marea majoritate a funcţiilor discutate până acum şi totalitatea funcţiilor realizate în cadrul laboratorului |
| + | până în acest moment primeau ca parametrii un număr prestabilit de parametrii, de tipuri bine | ||
| + | precizate. Acest lucru se datorează faptului că limbajul C este un limbaj [[http://en.wikipedia.org/wiki/Strongly-typed_programming_language|strong-typed]]. În cele mai multe cazuri acesta este un aspect pozitiv întrucât compilatorul va sesiza de la compilare situaţiile în care se încearcă pasarea unui număr eronat de parametrii sau a unor parametrii de tipuri necorespunzătoare. | ||
| - | Link catre teste: [[https://drive.google.com/drive/folders/12WvwMu7Do5QWlbjvnYrmbL_UWb73O-f3?usp=sharing|aici]] | + | Limbajul C permite totuşi şi declararea şi folosirea funcţiilor cu un număr (şi eventual tip) variabil de |
| - | </hidden> | + | parametri. Astfel, numărul şi tipul tuturor parametrilor va fi cunoscut doar la rulare, biblioteca standard |
| + | C punând la dispoziţie o serie de definiţii de tipuri şi macro definiţii care permit parcurgerea listei de | ||
| + | parametri a unei funcţii cu număr variabil de parametri. Exemplul cel mai comun de astfel de funcţii | ||
| + | sunt funcţiile din familia **printf()**, **scanf()**. | ||
| + | === Definirea funcţiilor cu număr variabil de parametri === | ||
| - | ==== Exerciţii de laborator ==== | + | Prototipul funcţiilor cu număr variabil de parametrii arată în felul următor: |
| + | <code c> | ||
| + | tip_rezultat nume_funcţie(listă_parametrii_fixaţi, ...); | ||
| + | </code> | ||
| - | - [2p] O matrice rară (cu circa 90% din elemente 0) este păstrată economic sub forma unei structuri, care conţine următoarele câmpuri: | + | Notaţia **„ ,... ”** comunică compilatorului faptul că funcţia poate primi un număr arbitrar de parametrii |
| - | * int L,C - numărul de linii/coloane al matricei rare | + | începând cu poziţia în care apare. De exemplu, prototipul funcţiei **fprintf()** arată în felul următor: |
| - | * int N - numărul de elemente nenule | + | <code c> |
| - | * int LIN[] - vectorul ce păstrează liniile în care se află elemente nenule | + | void fprintf(FILE*, const char*, ...); |
| - | * int COL[] - vectorul ce păstrează coloanele în care se află elemente nenule | + | </code> |
| - | * float X[] - vectorul ce păstrează elementele nenule | + | |
| - | - Să se definească funcţii pentru: | + | |
| - | * [1p] citirea unei matrice rare | + | |
| - | * [1p] afişarea unei matrice rare (ca o matrice, cu tot cu zerouri) | + | |
| - | * [2p] adunarea a două matrice rare. | + | |
| - | * **Observatii** : | + | |
| - | * Vectorii LIN, COL, X vor avea fiecare cate N elemente. | + | |
| - | * Elementul nenul cu valoare X[index] se afla pe linia LIN[index] si coloana COL[index]. | + | |
| - | * Elementele vor fi citite in ordinea crescatoare a liniilor, iar elementele de pe aceeasi linie in ordinea crescatoare a coloanelor. | + | |
| - | - Un polinom este reprezentat printr-o structură care conţine gradul polinomului şi tabloul coeficientilor. Să se definească funcţii pentru: | + | |
| - | * [1p] citirea unui polinom | + | |
| - | * [1p] afişarea unui polinom | + | |
| - | * [2p] adunarea/scăderea a două polinoame | + | |
| - | ==== Bonus ==== | + | Astfel, funcţia **fprintf()** trebuie să primească un pointer la o structură **FILE** şi un string de format şi eventual mai poate primi 0 sau mai multe argumente. Modul în care ele vor fi interpretate fiind |
| - | 1. [3p] În registrul ''Uni-Struct'', fiecărui elev îi corespunde o înregistrare care reține: | + | determinat în cazul de faţă de conţinutul variabilei de tip **const char*** (acesta este motivul pentru care pot apărea erori la rulare în condiţiile în care încercăm să tipărim un număr întreg cu **%s** ). |
| - | * numele acestuia (maxim 50 de caractere), | + | |
| - | * clasa în care acesta se află (o valoare de tipul ''unsigned int''), | + | |
| - | * rezultatul obținut la materia cea mai apropaiata (ca semnificație) de programare. | + | |
| - | + | ||
| - | Tipul rezultatului diferă în funcție de clasa în care elevul se află, astfel: | + | |
| - | * elevii din clasele primare (1-4), primesc un calificativ (FB, B, S, I) reprezentat ca un șir maxim 9 caractere; | + | |
| - | * elevii din toate celelalte clase primesc note, care vor fi reținute cu ajutorul unui câmp de tip ''float''. | + | |
| - | + | ||
| - | Scrieți un program care citește de la tastatură astfel de înregistrări, până la inserarea șirului de caractere ''"stop"'' în locul numelui unuia dintre elevi, apoi afișează toți studenții din registru, alături de toate informațiile despre ei, în ordinea lexicografică a numelor cu care sunt înregistrați. | + | |
| - | Folosiți un vector de structuri pentru a stoca toate informațiile despre elevi. Faceți eficient reținerea unei singure valori pentru rezultatul parcurgerii materiei în cadrul structurii. | + | <note important> |
| + | O funcţie cu număr variabil de parametrii trebuie să aibă cel puţin un parametru fixat, cu nume (motivul acestei limitări rezidă în modalitatea de implementare a funcţiilor cu număr variabil de parametri, după cum se va vedea în paragraful următor). | ||
| + | </note> | ||
| - | <file - bonus.in> | + | == Implementarea funcţiilor cu număr variabil de parametri == |
| - | Ionut Popescu | + | |
| - | 10 | + | |
| - | 7.4 | + | |
| - | Maria Almasan | + | |
| - | 3 | + | |
| - | FB | + | |
| - | Alex Grigorescu | + | |
| - | 12 | + | |
| - | 10 | + | |
| - | stop | + | |
| - | </file> | + | |
| - | <file - bonus.out> | + | Pentru prelucrarea listei de parametrii variabili este necesară includerea fişierului antet **stdarg.h**. |
| - | Alex Grigorescu | + | Acesta conţine declaraţii pentru tipul de date variable argument list (**va_list**) şi o serie de macrodefiniţii |
| - | 12 | + | pentru manipularea unei astfel de liste. În continuare este detaliat modul de lucru cu liste variabile de |
| - | 10 | + | parametri. |
| - | Ionut Popescu | + | |
| - | 10 | + | |
| - | 7.4 | + | |
| - | Maria Almasan | + | |
| - | 3 | + | |
| - | FB | + | |
| - | </file> | + | |
| + | * Declararea unei variabile de tip **va_list** (denumită de obicei **args**, **arguments**, **params**) | ||
| + | * Iniţializarea variabilei de tip **va_list** cu lista de parametri cu care a fost apelată funcţia se realizează cu macrodefiniţia **va_start(arg_list, last_argument)** unde: | ||
| + | * **arg_list** reprezintă variabila de tip **va_list** prin intermediul căreia vom accesa lista de parametri a funcţiei. | ||
| + | * **last_argument** reprezintă numele ultimei variabile fixate din lista de parametri a funcţiei (în exemplul următor, aceasta este şi prima variabilă, numită **first**). | ||
| + | * Accesarea variabilelor din lista de parametri se realizează cu macro-definiţia **va_arg(arg_list, type)**, unde | ||
| + | * **arg_list** are aceeaşi semnificaţie ca mai sus. | ||
| + | * **type** este un nume de tip şi reprezintă tipul variabilei care va fi citită. La fiecare apel se avansează în listă. În cazul în care se doreşte întoarcerea în listă, aceasta trebuie reiniţializată, iar elementul dorit este accesat prin apeluri succesive de **va_arg()** | ||
| + | * Eliberarea memoriei folosite de lista de parametri se realizează prin intermediul macro-definiţiei **va_end(arg_list)**, unde **arg_list** are aceeaşi semnificaţie ca mai sus. | ||
| + | <spoiler Exemplu8 > | ||
| + | |||
| + | Fie o functie care afiseaza un număr variabil de numere naturale primite ca parametru. Lista este terminată cu un număr negativ. | ||
| + | <code c> | ||
| + | #include <stdio.h> // printf | ||
| + | #include <stdarg.h> // va_list, va_arg, | ||
| + | // va_start,va_end | ||
| + | // semnatuna functie | ||
| + | // - functia nu intoarce nimic(void) | ||
| + | // - are un numar variabil de parametri (...) | ||
| + | // - primul parametru este fixat (int first) | ||
| + | void list_ints(int first, ...); | ||
| + | int main() { | ||
| + | list_ints(-1); // nimic de afisat | ||
| + | list_ints(128, 512, 768, 4096, -1); // afiseaza pana la -1 | ||
| + | list_ints('a', 'b', 0xa, 0xb, -2); // afiseaza pana la -2; apel corect deoarece castul la int este valid | ||
| + | list_ints(1, -1); // afiseaza pana la -1 | ||
| + | list_ints(1, 2, 3, -1, 1, 2, 3); // afiseaza pana la -1 | ||
| + | return 0; | ||
| + | } | ||
| - | ==== Referinţe ==== | + | void list_ints(int first, ...) { |
| - | * [[http://crasseux.com/books/ctutorial//Data-structures.html#Data%20structures|The GNU Programming Tools - Data structure]] | + | // tratam cazul special cand nu avem |
| - | * [[http://publications.gbdirect.co.uk/c_book/chapter6/unions.html|The C Book - Unions]] | + | // nici un numar (in afara de delimitator) |
| + | if (first < 0) { | ||
| + | printf("No numbers present (besides terminator)\n"); | ||
| + | return; | ||
| + | } | ||
| + | // lista de parametri | ||
| + | va_list args; | ||
| + | |||
| + | /* initializam lista de parametri */ | ||
| + | va_start(args, first); | ||
| + | |||
| + | // parcurgem lista de parametri pana ce | ||
| + | // intalnim un numar negativ | ||
| + | printf("These are the numbers (excluding terminator): "); | ||
| + | int current = first; | ||
| + | do { | ||
| + | // afiseaza numarul curent | ||
| + | printf("%d ", current); | ||
| + | |||
| + | // obtine urmatorul numar intreg din lista | ||
| + | current = va_arg(args, int); | ||
| + | } while (current >= 0); | ||
| + | printf("\n"); | ||
| + | |||
| + | /* curatam lista de parametrii */ | ||
| + | va_end(args); | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | |||
| + | Observati comportamentul acestei functii pentru diversi parametri. | ||
| + | <code bash> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | |||
| + | No numbers present (besides terminator) | ||
| + | These are the numbers (excluding terminator): 128 512 768 4096 | ||
| + | These are the numbers (excluding terminator): 97 98 10 11 | ||
| + | These are the numbers (excluding terminator): 1 | ||
| + | These are the numbers (excluding terminator): 1 2 3 | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | <spoiler Exemplu 9 (studiu de caz)> | ||
| + | |||
| + | In exemplul anterior am considerat toate argumentele primite erau intregi. In general, acestea pot avea tipuri diferite. | ||
| + | |||
| + | Realizați o **funcție cu număr variabil de parametri**, numită de exemplu **show()** care să permită afisarea cu format a unui număr de variabile date ca parametri, acestia putand avea **tipuri diferite**. | ||
| + | |||
| + | Prototipul va arăta în modul următor: | ||
| + | <code c> | ||
| + | int show(char *format, …); | ||
| + | </code> | ||
| + | * Pentru fiecare specificator de format, se va afisa variabila în variabila curentă un număr de octeți corespunzător tipului dat de specificator (vezi mai jos). | ||
| + | * Specificatorii de format suportați sunt următorii: | ||
| + | * %c - dată de tip char | ||
| + | * %d - dată de tip integer | ||
| + | * %f - dată de tip float | ||
| + | * %s - dată de tip char * (se va afisa un string) | ||
| + | * Pentru simplitate se va considera că după '%' urmează mereu unul din cei 3 specificatori. | ||
| + | * Functia va returna numarul de argumente procesate din lista. | ||
| + | * Afișați la stdout datele citite. In implementare aveti voie sa folositi functiile printf/fprintf. | ||
| + | |||
| + | <code c> | ||
| + | #include <stdio.h> // printf | ||
| + | #include <string.h> // strlen | ||
| + | #include <stdarg.h> // va_list, va_arg, | ||
| + | // va_start,va_end | ||
| + | |||
| + | int show(const char *format, ...); | ||
| + | |||
| + | int main() { | ||
| + | char name[] = "Gigel"; | ||
| + | int age = 15; | ||
| + | float weight = 91.5; | ||
| + | int argc; | ||
| + | |||
| + | // afisare @ Gigel | ||
| + | argc = show("%s are %d ani si %f de kg.\n", name, age, weight); | ||
| + | |||
| + | // afisare numar de argumente folosite | ||
| + | show("arguments used: %d/3\n", argc); | ||
| + | |||
| + | // afisare double | ||
| + | double x = 5.0; | ||
| + | show("%f wtf?\n", x); | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | |||
| + | int show(const char *format, ...) { | ||
| + | // n = lungime format, i iterator | ||
| + | // proccesed = argumente procesate | ||
| + | int n, i, proccesed; | ||
| + | |||
| + | // initializari | ||
| + | n = strlen(format); | ||
| + | proccesed = 0; | ||
| + | va_list args; | ||
| + | va_start(args, format); | ||
| + | |||
| + | // parcurge fiecare caracter din format | ||
| + | for (i = 0; i < n; ++i) { | ||
| + | // c e caracterul curent | ||
| + | char c = format[i]; | ||
| + | |||
| + | // verifica sa nu inceapa o interpolare | ||
| + | // de stringuri | ||
| + | if (c != '%') { | ||
| + | // afisez exact ce primesc | ||
| + | printf("%c", c); | ||
| + | continue; | ||
| + | } | ||
| + | |||
| + | // c == '%', deci am gasit o interpolare | ||
| + | // decid ce caz am gasit | ||
| + | ++proccesed; | ||
| + | c = format[ ++i ]; | ||
| + | switch(c) { | ||
| + | // am gasit "%d" | ||
| + | // urmatorul parametru este un int | ||
| + | case 'd': { | ||
| + | int d = va_arg(args, int); | ||
| + | printf("%d", d); | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | // am gasit "%f" | ||
| + | // urmatorul parametru este un double | ||
| + | case 'f': { | ||
| + | double f = va_arg(args, double); | ||
| + | printf("%f", f); | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | // am gasit "%s" | ||
| + | // urmatorul parametru este un char* | ||
| + | case 's': { | ||
| + | char *s = va_arg(args, char*); | ||
| + | printf("%s", s); | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | default: | ||
| + | fprintf(stderr, "Asta nu trebuia sa se intample!\n"); | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | va_end(args); | ||
| + | return proccesed; | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | |||
| + | Rulare: | ||
| + | <code c> | ||
| + | gcc -Wall -Wextra test.c -o test | ||
| + | ./test | ||
| + | |||
| + | Gigel are 15 ani si 91.500000 de kg. | ||
| + | arguments used: 3/3 | ||
| + | 5.000000 wtf? | ||
| + | |||
| + | </code> | ||
| + | |||
| + | Incercati acasa sa inlocuiti functiile printf/fprintf cu alte primitive (de exemplu puts/putc). | ||
| + | |||
| + | <note warning> | ||
| + | * Codul a compilat, desi nu am implementat separat float si double. La pasarea parametrilor, unele tipuri de date sunt promovotate: float la double, char/short la int ([[http://stackoverflow.com/questions/11270588/variadic-function-va-arg-doesnt-work-with-float | stackoverflow ]]). | ||
| + | </note> | ||
| + | |||
| + | </spoiler> | ||
| + | |||
| + | === Opțiuni de compilare === | ||
| + | |||
| + | == Compilatorul GCC == | ||
| + | |||
| + | În cadrul laboratorului și pentru testarea temelor de casă se va folosi compilatorul GCC. GCC este unul dintre primele pachete software dezvoltate în cadrul Proiectului GNU (''GNU's Not Unix'') de către Free Software Foundation. Deși GCC se traducea iniţial prin ''GNU C Compiler'', acesta a devenit între timp un compilator multifrontend, multi-backend, având suport pentru o serie largă de limbaje, ca C, C++, Objective-C, Ada, Java, etc, astfel că denumirea curentă a devenit ''GNU Compiler Collection''. În cadrul cursului de [[http://ocw.cs.pub.ro/programare|Programare]] ne vom referi totuşi numai la partea de C din suita de compilatoare. | ||
| + | |||
| + | Compilatorul GCC rulează pe o gamă largă de echipamente hardware (procesoare din familia: ''i386, alpha, vax, m68k, sparc, HPPA, arm, MIPS, PowerPC,'' etc.) și de sisteme de operare (''GNU/Linux, DOS, Windows 9x/NT/2000, Solaris, Tru64, VMS, Ultrix, Aix ''), fiind la ora actuală cel mai folosit compilator. | ||
| + | |||
| + | Compilatorul GCC se apelează din linia de comandă, folosind diferite opțiuni, în funcție de rezultatul care se dorește (specificarea de căi suplimentare de căutare a bibliotecilor/fișierelor antet, link-area unor biblioteci specifice, opțiuni de optimizare, controlul stagiilor de compilare, al avertisementelor, etc.). | ||
| + | |||
| + | == Utilizare GCC == | ||
| <hidden> | <hidden> | ||
| + | <note> | ||
| + | Instrucțiuni de instalare gcc - [[https://ocw.cs.pub.ro/courses/programare/vmchecker_conf#instalare_gccmake| Instalare gcc/make ]] | ||
| + | </note> | ||
| + | </hidden> | ||
| - | [[https://drive.google.com/open?id=1DdnXnY0VURSNevNpDVTJNmfTxza0JnBg|Probleme laborator 14 - 16]] | + | Vom folosi pentru exemplificare un program simplu care tipărește la ieșirea standard un șir de caractere. |
| + | <code c hello.c> | ||
| + | #include <stdio.h> | ||
| + | |||
| + | int main() { | ||
| + | printf("Hello from your first program!\n"); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Pentru compilarea programului se va lansa comanda (în linia de comandă): | ||
| + | <code bash> gcc hello.c</code> | ||
| + | presupunând că fișierul sursă se numește ''hello.c''. | ||
| + | |||
| + | Pe un sistem de operare ''Linux'', compilarea default va genera un executabil cu numele ''a.out''. Pentru rularea acestuia, trebuie executată comanda: | ||
| + | <code bash> ./a.out</code> | ||
| + | |||
| + | Pentru un control mai fin al comportării compilatorului, sunt prezentate în tabelul următor cele mai folosite opţiuni (pentru lista completă studiaţi pagina de manual pentru GCC - ''man gcc''): | ||
| + | |||
| + | ^ Opțiune ^ Efect ^ | ||
| + | |**-o** nume_fișier|Numele fișierului de ieşire va fi nume_fişier. În cazul în care această opțiune nu este setată, se va folosi numele implicit (pentru fișiere executabile: a.out - pentru Linux).| | ||
| + | |**-I** cale_către_fișiere_antet|Caută fișiere antet și în calea specificată.| | ||
| + | |**-L** cale_către_biblioteci|Caută fișiere bibliotecă și în calea specificată.| | ||
| + | |**-l** nume_bibliotecă|Link-editează biblioteca ''nume_bibliotecă''. **Atenție!!!** ''nume_bibliotecă'' nu este întotdeauna același cu numele fișierului antet prin care se include această bibliotecă. Spre exemplu, pentru includerea bibliotecii de funcții matematice, fișierul antet este math.h, iar biblioteca este ''m''.| | ||
| + | |**-W** tip_warning|Afișează tipurile de avertismente specificate (Pentru mai multe detalii ''man gcc'' sau ''gcc --help''). Cel mai folosit tip este ''all''. Este indicat ca la compilarea cu ''-Wall'' să nu apară nici un fel de avertismente.| | ||
| + | |**-c**|Compilează și asamblează, dar nu link-editează. Generează fișiere obiect, cu extensia ''.o''.| | ||
| + | |**-S**|Se oprește după faza de compilare, fară să asambleze. Rezultă cod assembler în fișiere cu extensia ''.s''.| | ||
| + | |||
| + | <hidden> | ||
| + | Despre etapele compilării puteți să citiți mai multe [[http://elf.cs.pub.ro/so/wiki/laboratoare/laborator-01#fazele-compilarii|aici]]. | ||
| </hidden> | </hidden> | ||
| + | |||
| + | = Exemplu = | ||
| + | |||
| + | <code bash>gcc -o tema1 tema1.c -lm -Wall</code> | ||
| + | |||
| + | Comanda de mai sus are ca efect compilarea și link-editarea fişierului ''tema1.c'', cu includerea bibliotecii matematice, afişând toate avertismentele. Fişierul de ieşire se va numi ''tema1''. | ||
| + | |||
| + | **Atenție!!!** Dacă folosiți opțiunea **-o**, nu adăugați imediat după fișierele sursă. Acest lucru ar avea ca efect suprascrierea acestora și pierderea întregului conținut. | ||
| + | |||
| + | == Utilitarul Make == | ||
| + | |||
| + | <note> | ||
| + | Instrucțiuni de instalare gcc - [[https://ocw.cs.pub.ro/courses/programare/vmchecker_conf#instalare_gccmake| Instalare gcc/make ]] | ||
| + | </note> | ||
| + | |||
| + | Utilitarul ''make'' determină automat care sunt părțile unui proiect care trebuie recompilate ca urmare a operării unor modificări și declanşează comenzile necesare pentru recompilarea lor. Pentru a putea utiliza ''make'', este necesar un fișier de tip ''makefile'' (numit de obicei ''Makefile'' sau ''makefile'') care descrie relațiile de dependenţă între diferitele fișiere din care se compune programul şi care specifică regulile de actualizare pentru fiecare fişier în parte. | ||
| + | |||
| + | În mod normal, într-un program, fişierul executabil este actualizat (recompilat) pe baza fișierelor-obiect, care la rândul lor sunt obținute prin compilarea fișierelor sursă. Totuși, acest utilitar poate fi folosit pentru orice proiect care conţine dependenţe şi cu orice compilator/utilitar care poate rula în linia de comandă. Odată creat fișierul ''makefile'', de fiecare dată când apare vreo modificare în fișierele sursă, este suficient să rulăm utilitarul ''make'' pentru ca toate recompilările necesare să fie efectuate. Programul ''make'' utilizează fișierul Makefile ca bază de date şi pe baza timpilor ultimei modificări a fișierelor din Makefile decide care sunt fișierele care trebuie actualizate. Pentru fiecare din aceste fișiere, sunt executate comenzile precizate in Makefile. În continuare prezentăm un exemplu simplu. | ||
| + | |||
| + | <code bash> | ||
| + | # Declaratiile de variabile | ||
| + | CC = gcc | ||
| + | CFLAGS = -Wall -lm | ||
| + | SRC = radical.c | ||
| + | EXE = radical | ||
| + | |||
| + | # Regula de compilare | ||
| + | all: | ||
| + | $(CC) -o $(EXE) $(SRC) $(CFLAGS) | ||
| + | |||
| + | # Regulile de "curatenie" (se folosesc pentru stergerea fisierelor intermediare si/sau rezultate) | ||
| + | .PHONY : clean | ||
| + | clean : | ||
| + | rm -f $(EXE) *~ | ||
| + | </code> | ||
| + | |||
| + | **Atenție!!!** Este obligatorie folosirea tab-urilor (nu spații!). | ||
| + | Mai multe informații puteți găsiti pe [[http://www.gnu.org/software/make|pagina proiectului]]. | ||
| + | |||
| + | |||
| + | ==== Exerciții ==== | ||
| + | Exercițiile pentru laborator se găsesc pe [[https://acs-pclp.github.io/laboratoare/11 | PCLP Laborator11: Parametrii liniei de comandă. Preprocesorul. Funcții cu număr variabil de parametri. Opțiuni de compilare]]. | ||
| + | |||
| + | |||
| + | ==== Referinţe ==== | ||
| + | * https://en.cppreference.com/w/c/variadic.html | ||
| + | * https://en.cppreference.com/w/c/preprocessor.html | ||