This shows you the differences between two versions of the page.
programare:laboratoare:lab13 [2013/09/29 23:41] 127.0.0.1 external edit |
programare:laboratoare:lab13 [2020/10/05 00:39] (current) darius.neatu [Parametrii liniei de comandă. Preprocesorul. Funcţii cu număr variabil de parametri.] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 13. Recapitulare ===== | + | ===== Parametrii liniei de comandă. Preprocesorul. Funcţii cu număr variabil de parametri. ===== |
- | **Responsabili:** | + | **Resposabili:****Responsabili:** |
- | * [[florin.pop@cs.pub.ro|Florin Pop]], [[george.popescu@cs.pub.ro|George Popescu]] | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (CA 2016-2020)]] |
+ | * [[neatudarius@gmail.com|Darius Neațu (CA 2016-2020)]] | ||
+ | * [[mihaela.vasile@gmail.com|Mihaela Vasile (2015)]] | ||
- | ==== Obiective ==== | + | **Ultima modificare:** **09.12.2018** |
+ | |||
+ | ==== Obiective ==== | ||
În urma parcurgerii acestui laborator, studentul va fi capabil: | În urma parcurgerii acestui laborator, studentul va fi capabil: | ||
- | * să-şi estimeze gradul de acoperire al cunoştinţelor la Programare; | + | * să interpreteze şi să manipuleze parametrii liniei de comandă |
- | * să-şi facă o privire de ansamblu mai bună asupra noţiunilor învăţate la laborator şi a modului în care acestea sunt legate între ele; | + | * să scrie programe care comunică cu exteriorul (utilizatorul, sistemul de operare, alte programe) prin intermediul parametrilor liniei de comandă |
+ | * să scrie şi să folosească macrodefiniţii | ||
+ | * 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 | ||
- | ==== Exerciţii de Laborator ==== | + | <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> | ||
+ | ==== Parametrii liniei de comandă ==== | ||
+ | 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.) | ||
- | * ''[Easy]'' Scrieţi un program care citeşte un număr ''n'' de la tastatură şi apoi alte ''n*n'' numere reale, pe care le plasează într-o matrice pătratică, alocată static, de dimensiune ''n''. Să se sorteze apoi aceste numere crescător, de la stânga la dreapta şi de sus în jos, fără a folosi alţi vectori sau matrice, şi apoi să se afişeze matricea sortată. | + | 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. |
- | * ''[Medium]'' Scrieţi un program care defineşte un vector de 10 numere întregi, pe care îl iniţializează apoi cu numere de la 0 la 9 şi apoi afişează conţinutul memoriei ocupate de vector, octet cu octet, fiecare octet fiind afişat cu două cifre hexazecimale. | + | 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> | ||
- | * ''[Medium]'' a) Care este diferenţa dintre următoarele două expresii? Explicaţi concluzia. | + | Î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> | ||
- | int expr = (a + b)*(c - d); | + | int main(int argc, char *argv[]) |
</code> | </code> | ||
+ | |||
+ | 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> | ||
- | int expr = ((a + b))*((c - d)); | + | #include <stdio.h> |
+ | #include <string.h> // strrchr | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | // afiseaza numarul de argumente + primul argument | ||
+ | printf("Am primit %d argument(e)\n", argc); | ||
+ | printf("argv[0] = %s\n", argv[0]); | ||
+ | |||
+ | // extrage numele executabilului | ||
+ | char *program_name = strrchr(argv[0], '/') + 1; | ||
+ | printf("Eu sunt programul %s\n", program_name); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
</code> | </code> | ||
- | b) Care este diferenţa dintre următoarele două expresii? Explicaţi concluzia. | + | Pentru compilare vom rula comanda: |
+ | <code bash> | ||
+ | gcc -Wall test.c -o gigel | ||
+ | </code> | ||
+ | |||
+ | 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> | ||
- | printf("Hello there, %s!\n", "student"); | + | #define EA Ana |
+ | #define si C | ||
+ | #ifdef CIFRE | ||
+ | #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> | ||
+ | |||
+ | La rularea comenzii | ||
+ | <code bash> | ||
+ | gcc -E -DCIFRE rubbish.c | ||
+ | </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> | ||
- | printf(("Hello there, %s!\n", "student")); | + | # 1 "rubbish.c" |
+ | # 1 "<built-in>" | ||
+ | # 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> | ||
- | * ''[Hard]'' Fie următoarea structură: | + | 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 test_struct { | + | #include <nume_fişier> |
- | char a; | + | |
- | short b; | + | |
- | char c; | + | |
- | int d; | + | |
- | }; | + | |
</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. | ||
- | Scrieţi un program care defineşte această structură şi o variabilă de acest tip şi care afişează diferenţa dintre dimensiunea totală a structurii (calculată cu operatorul ''sizeof'') şi suma dimensiunilor fiecărui câmp în parte al structurii. Explicaţi rezultatul obţinut. | + | === 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> | ||
+ | #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. | ||
- | '''SOLUŢIE:''' | ||
<code c> | <code c> | ||
#include <stdio.h> | #include <stdio.h> | ||
- | struct test_struct { | + | // am definit GIGEL ca fiind 1 |
- | char a; | + | #define GIGEL 1 |
- | short b; | + | |
- | char c; | + | // Verific daca este definit simbolul CHANGE |
- | int d; | + | #ifdef CHANGE |
- | };// __attribute__((packed)); | + | #undef GIGEL // sterg GIGEL |
- | //decomentati linia anterioara pentru a instrui compilatorul sa nu mai puna | + | #define GIGEL 0 // redefinesc simbolul cu alta valoare |
- | // "padding" in structuri. | + | #endif |
int main() { | int main() { | ||
- | struct test_struct t; | + | printf("GIGEL = %d\n", GIGEL); |
- | void *start, *offset; | + | return 0; |
- | + | ||
- | printf("sizeof(struct): %d\n", sizeof(t)); | + | |
- | printf("sum(sizeof(campuri)): %d\n", | + | |
- | sizeof(t.a) + sizeof(t.b) + sizeof(t.c) + sizeof(t.d)); | + | |
- | + | ||
- | //Bonus, afisarea pozitiilor fiecarui camp in structura. Ce se afiseaza | + | |
- | //daca se mentine si atributul __attribute__((packed)) in declaratia | + | |
- | //structurii? | + | |
- | start = &t; | + | |
- | offset = &t.a; | + | |
- | printf("Offset a: %d\n", offset - start); | + | |
- | + | ||
- | offset = &t.b; | + | |
- | printf("Offset b: %d\n", offset - start); | + | |
- | + | ||
- | offset = &t.c; | + | |
- | printf("Offset c: %d\n", offset - start); | + | |
- | + | ||
- | offset = &t.d; | + | |
- | printf("Offset d: %d\n", offset - start); | + | |
- | + | ||
- | return 0; | + | |
} | } | ||
</code> | </code> | ||
- | * ''[Medium]'' Definiţi macro-ul ''for_each(vect, n, val)'', care să reprezinte antetul unei bucle de iteraţie prin elementele de tip ''int'' ale vectorului ''vect''. ''n'' reprezintă numărul de elemente ale vectorului şi ''val'' numele unei variabile care să conţină valoarea curentă din iteraţie. Un exemplu de funcţie care foloseşte acest macro arată astfel: | + | 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> | <code c> | ||
- | void afiseaza(int *v, int n) { | + | #define IDENTIFICATOR valoare |
- | int crt_val; | + | </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. | ||
- | for_each(v, n, crt_val) { | + | Un exemplu foarte bun este definirea unor macro-uri pentru dimensiunile tablourilor alocate static. |
- | printf("%d ", crt_val); | + | <code c> |
- | } | + | #define NMAX 100 |
- | printf("\n"); | + | #define MMAX 10 |
+ | #define LMAX 10000 | ||
+ | |||
+ | ... | ||
+ | int a[NMAX][MMAX]; | ||
+ | ... | ||
+ | int v[LMAX]; | ||
+ | ... | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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> | ||
+ | DUBLU(a + 3) | ||
+ | </code> | ||
+ | în pasul de preprocesare se va genera expresia | ||
+ | <code c> | ||
+ | 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() { | ||
+ | // ceea ce se obtine | ||
+ | printf("%d\n", ~SQUARE(2)); | ||
+ | |||
+ | // ceea ce se doreste | ||
+ | printf("%d\n", ~4); | ||
+ | |||
+ | return 0; | ||
} | } | ||
+ | |||
</code> | </code> | ||
- | * ''[Medium]'' Implementaţi funcţiile: | + | <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. | ||
+ | |||
+ | Pentru a fi convinsi ca se intampla acest lucru, putem compila cu -E si sa inspectam codul urmator. | ||
<code c> | <code c> | ||
- | int max(int a, int b); | + | ... // continut din stdio.h, alte simboluri etc. |
- | int min(int a, int b); | + | |
+ | int main() { | ||
+ | printf("%d\n", ~(2) * (2)); | ||
+ | |||
+ | printf("%d\n", ~4); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
</code> | </code> | ||
- | care calculează maximul, respectiv minimul dintre două numere fără a folosi nici o comparaţie (instrucţiune ''if'' sau operatorul ternar). Scrieţi apoi un program care primeşte două numere întregi ca parametri '''în linia de comandă''' şi afişează maximul şi minimul folosind cele două funcţii definite mai sus. | ||
- | * ''[Hard]'' Fie programul C simplu de mai jos: | + | Pentru a corecta aceasta greseala, putem adauga inca o paranteza in definitia lui SQUARE. |
<code c> | <code c> | ||
- | int func() { | + | #define SQUARE(a) ( (a) * (a) ) |
- | return 1 + 2 + 3 + 4; | + | </code> |
+ | Pentru noua sursa C obtinem urmatorul rezultat. | ||
+ | |||
+ | <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> | ||
+ | int a, b; | ||
+ | ... | ||
+ | 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> | ||
+ | |||
+ | Solutia intuitiva de definire a simbolului SWAP este urmatoarea. | ||
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | |||
+ | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; | ||
int main() { | int main() { | ||
- | return func(10, 20, 30, 40); | + | // 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> | </code> | ||
- | Încercaţi să-l compilaţi. Ce observaţi? Ce explicaţie aveţi pentru acest comportament? | + | In urma precompilarii, functia main arata astfel. |
+ | <code c> | ||
+ | ... | ||
+ | int main() { | ||
- | * ''[Medium]'' Implementaţi funcţiile: | + | int x = 1, y = 2; |
+ | printf("before: x = %d y = %d\n", x, y); | ||
+ | int tmp = x; x = y; y = tmp; | ||
+ | printf("after : x = %d y = %d\n", x, y); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | Sursa compileaza si ruleaza corect. | ||
<code c> | <code c> | ||
- | int sort(void * , int (*f) (void *, void*)); | + | gcc -Wall -Wextra test.c -o test |
- | //funcţie care sortează un vector si foloseste drept comparator functia f. | + | ./test |
- | int comparator (void * a, void * b); | + | before: x = 1 y = 2 |
- | //funcţie care întoarce un număr negativ dacă a<b, 0 pentru a==b, un număr pozitiv pentru a>b, particularizată pentru numere întregi. | + | after : x = 2 y = 1 |
</code> | </code> | ||
- | Scrieţi apoi un program care primeşte ca parametru al liniei de comandă un nume de fişier care este de forma: | + | Urmatoarea sursa nu va compila, intrucat la folosirea repetata a lui SWAP in acelasi scop, variabila tmp va fi declarate de mai multe ori. |
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
- | N | + | #define SWAP(type,x,y) type tmp = x; x = y; y = tmp; |
- | A1 A2 A3 ... An | + | 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); | ||
- | unde: | ||
- | N-> numărul de elemente ale vectorului | + | // 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); | ||
- | Ai -> numere întregi. | + | // elibereaza memoria alocata dinamic |
+ | free(p); | ||
+ | free(q); | ||
- | '''SOLUŢIE:''' | + | 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> | <code c> | ||
#include <stdio.h> | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
- | //functia de sortare generica | + | #define SWAP(type,x,y) {\ |
- | int sort(void * v, int n, int size, int (*f) (void *, void*)) { | + | type tmp = x; \ |
- | int i,j; | + | x = y; \ |
- | void *aux = malloc(size); | + | y = tmp; \ |
- | for (i = 0; i < n; i++) { | + | |
- | for (j = 0; j < n; j++) { | + | |
- | //incrementarea pointerilor void* se face cu 1 | + | |
- | if (f(v + i*size, v + j*size) > 0) { | + | |
- | memcpy(aux, v + i*size, size); | + | |
- | memcpy(v + i*size, v + j*size, size); | + | |
- | memcpy(v + j*size, aux, size); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | | + | |
} | } | ||
- | //functia care compara doua elemente date prin pointeri void* | + | int main() { |
- | int comparator (void * a, void * b) { | + | // interschimbare int |
- | return *((int*)a) - *((int*)b); | + | 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> | ||
+ | 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> | ||
- | int main(int argc, char *argv[]) { | + | ==== Instrucţiuni de compilare condiţionată ==== |
- | if (argc < 2) | + | |
- | return 1; | + | 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> | |
- | FILE * f = fopen(argv[1], "r"); | + | #if conditie |
- | + | .... | |
- | //citire din fisier si alocare de memorie | + | #else |
- | int i, *v, n; | + | .... |
- | fscanf(f, "%d", &n); | + | #endif |
- | v = (int*)malloc(n * sizeof(int)); | + | </code> |
- | + | unde conditie este este o expresie constantă întreagă. Pentru realizarea de expresii cu mai multe | |
- | for (i = 0; i < n; i++) { | + | opţiuni se poate folosi şi forma **#elif**: |
- | fscanf(f, "%d", &v[i]); | + | <code c> |
- | } | + | #if conditie |
- | + | ... | |
- | fclose(f); | + | #elif conditie2 |
- | + | ... | |
- | //afisare date initiale | + | #elif conditie3 |
- | for (i = 0; i < n; i++) { | + | ... |
- | printf("%d ", v[i]); | + | #else |
- | } | + | ... |
- | printf("\n"); | + | #endif |
- | + | </code> | |
- | //sortare si afisare dupa sortare | + | |
- | sort(v, n, sizeof(int), comparator); | + | 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 | |
- | for (i = 0; i < n; i++) { | + | * compilare condiţionată în funcţie de platforma de rulare |
- | printf("%d ", v[i]); | + | * prevenirea includerii multiple a fişierelor antet |
- | } | + | |
+ | Î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. | ||
+ | |||
+ | <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> | ||
+ | #include <stdio.h> | ||
+ | |||
+ | #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; | ||
} | } | ||
</code> | </code> | ||
- | * ''[Hardcore]'' Într-un fişier MP3 datele legate de titlul melodiei, artist, album, etc sunt stocate conform cu standardul ID3, într-o structură numită ''tag ID3''. Această structură ocupă ultimii 128 octeţi din fişier (vezi descrierea detaliată în continuare). Prezenţa sa este determinată de primii 3 octeţi din tag. Astfel, dacă primii 3 octeţi din cei 128 conţin caracterele ''TAG'' atunci tagul ID3 este prezent. Altfel, se consideră că este absent. | + | Sa ne uitam la fisierul C obtinut in urma etapei de preprocesare pe un **sistem UNIX**. |
- | Câmpurile unui tag ID3 sunt reprezentate în fişier pe ultimii 128 de octeţi, după cum urmează: | + | <code bash> |
+ | gcc -Wall -Wextra -E test.c | ||
+ | </code> | ||
- | ^ Field ^ Length ^ Description | + | <code c> |
- | | header | 3 | "TAG" | + | ... |
- | | title | 30| 30 characters of the title | + | void f() { |
- | | artist | 30| 30 characters of the artist name | + | printf("__unix__\n"); |
- | | album | 30| 30 characters of the album name | + | } |
- | | year | 4 | A four-digit year | + | int main() { |
- | | comment| 28 or 30 | The comment. | + | f(); |
- | | zero-byte | 1 | If a track number is stored, this byte contains a binary 0. | + | return 0; |
- | | track | 1 | The number of the track on the album, or 0. Invalid, if previous byte is not a binary 0. | + | } |
- | | genre | 1 | Index in a list of genres, or 255 | + | </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> | ||
- | Se cere să realizaţi un program care, primind în linia de comandă o serie de nume de fişiere mp3 să afişeze (în cazul în care există): | + | La rularea executabilului, se va afisa pe ecran stringul corespunzator sistemului de operare pe care a fost compilat executabilul. |
- | # numele artistului | + | </spoiler> |
- | # titlul melodiei | + | |
- | # titlul albumului | + | |
- | # anul înregistrării | + | |
- | # genul melodiei | + | |
- | Numele genurilor vor fi citite din fisierul ''genres.dat''. Fişierul ''genres.dat'' conţine un număr de linii, fiecare linie conţinând indexul genului şi numele lui. | + | |
- | * ''[Medium]'' Scrieţi un program care primeşte ca parametru un nume de fişier şi îi afişează pe ecran conţinutul în hexazecimal, pe linii de câte 64 de caractere (asemănător cu utilitarul ''hexdump'' din Linux). | + | === Includere multipla === |
+ | Prevenirea includerii multiple a fişierelor antet se realizează astfel: | ||
- | * ''[Medium]'' Scrieţi un program care permite căutarea unui cuvânt sau a unei măşti într-un text. Masca poate conţine caractere (care vor fi verificate ca atare) sau caracterul ''?'' cu semnificaţia că poate fi înlocuit cu orice caracter. Textul se va citi dintr-un fişier al cărui nume va fi trimis ca parametru în linia de comandă. Se vor afişa cuvintele găsite şi linia pe care au fost găsite. | + | <code c> |
+ | #ifndef _NUME_FISIER_ANTET_ | ||
+ | #define _NUME_FISIER_ANTET_ | ||
+ | /* corpul fisierului antet */ | ||
+ | /* prototipuri de functii, declaratii de tipuri si de constante */ | ||
+ | #endif | ||
+ | </code> | ||
+ | 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) > | ||
- | ==== Teste recapitulative ==== | + | Vom face o ierarhie de fisiere pentru a vedea utilizarea ** garzilor ** ([[ https://en.wikipedia.org/wiki/Include_guard | include guard]]). |
- | === Testul 1 === | + | Fie sursa test.c in care dorim sa apelam functia ** pc ** din fisierul antet **pc.h** si functia ** run ** din fisierul antet ** uso.h **. |
+ | <code c> | ||
+ | #include <stdio.h> | ||
+ | #include "uso.h" | ||
+ | #include "pc.h" | ||
- | 1. (task1.c) Fie funcţiile definite astfel: | + | int main() { |
+ | pc(); | ||
+ | run(); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | Fisierul **pc.h** are urmatorul continut. | ||
<code c> | <code c> | ||
- | int functie(void); | + | #include "uso.h" |
- | int functie(); | + | |
+ | void write_in_c() { | ||
+ | printf("write code in C\n"); | ||
+ | } | ||
+ | |||
+ | void pc() { | ||
+ | write_in_c(); | ||
+ | compile(); | ||
+ | run(); | ||
+ | } | ||
</code> | </code> | ||
+ | Fisierul **uso.h** are urmatorul continut. | ||
+ | <code c> | ||
+ | void compile() { | ||
+ | printf("compile with gcc\n"); | ||
+ | } | ||
- | Sunt cele două definiţii echivalente? Scrieţi un program C care să justifice răspunsul dat. | + | void run() { |
+ | printf("run\n"); | ||
+ | } | ||
+ | </code> | ||
- | 2. (task2.c) Scrieţi un program care să afişeze propriul cod sursă. | + | 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 | ||
- | 3. (task3.c) Rezolvaţi următoarele cerinţe: | + | # 1 "uso.h" // aici se include prima oara "uso.h" (linia ''#include "uso.h"'' din "test.c") |
+ | void compile() { | ||
+ | printf("compile with gcc\n"); | ||
+ | } | ||
- | * 3.1 Definiţi structura ''Numar_Complex'', cu câmpurile parte reală, parte imaginară. | + | 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"); | ||
+ | } | ||
- | * 3.2. Definiţi funcţia ''Numar_Complex* citire (char *filename,int *n)'', care să citească din fişierul dat ca parametru numărul natural n, după care să aloce un vector de n numere complexe, care se vor citi apoi de pe următoarele n linii ale fişierului. | + | void run() { |
+ | printf("run\n"); | ||
+ | } | ||
+ | # 2 "pc.h" 2 | ||
- | Formatul fisierului: | + | void write_in_c() { |
+ | printf("write code in C\n"); | ||
+ | } | ||
- | n | + | void pc() { |
- | x1 y1 | + | write_in_c(); |
- | x2 y2 | + | compile(); |
- | ... | + | run(); |
- | xn yn | + | } |
+ | # 4 "test.c" 2 | ||
- | * 3.3 Scrieţi un program care citeşte un vector de n numere complexe folosind funcţia de la punctul precedent, şi apoi afişează numărul de modul maxim şi poziţia acestuia în vector. | + | int main() { |
+ | pc(); | ||
+ | run(); | ||
+ | return 0; | ||
+ | } | ||
- | 4. (task4.c) Scrieţi un program care poate primi (ca argumente în linia de comandă) opţiunile “-a” şi “-p”. Programul primeşte ca prim parametru în linia de comandă un număr întreg n. | + | </code> |
- | *Opţiunea “-a” va avea ca efect afişarea adresei variabilei din program în care se reţine numărul. | + | Aplicam solutia de mai sus. Definim in fiecare fisier header cate un simbol. |
- | *Opţiunea “-p” va avea ca efect afişarea pătratului numarului. Pentru a calcula pătratul numărului se va folosi o macroinscructiune. | + | Definim simbolul ** PC_H ** in **pc.h**. |
- | *Se permite rularea executabilului cu ambele opţiuni, caz în care se va afişa atât adresa variabilei cât şi pătratul valorii. La rularea executabilului fără opţiuni, nu se va afiţa nimic. | + | <code c> |
- | *Se va afişa un mesaj de eroare în oricare dintre cazurile: lipsa parametrului n, parametrii nu sunt cei specificaţi mai sus, număr prea mare de parametri. | + | #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 | ||
- | 5. (task5.c) Scrieţi un program care interschimbă două variabile x şi y, fără a folosi o variabilă auxiliară. | + | #include "uso.h" |
- | 6. Modificaţi următorul fişier makefile, astfel încât executabilul de la problema 4 să se numească task6. | + | 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> | <code c> | ||
- | #makefile | + | #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> | ||
+ | |||
+ | Fisierele antet vor fi incluse o singura data, codul va compila. | ||
+ | <code c> | ||
+ | gcc -Wall -Wextra test.c -o test | ||
+ | ./test | ||
+ | |||
+ | write code in C | ||
+ | compile with gcc | ||
+ | run | ||
+ | run | ||
+ | </code> | ||
+ | </spoiler> | ||
+ | |||
+ | === Alte instrucţiuni === | ||
+ | |||
+ | <code c> | ||
+ | #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. | ||
+ | |||
+ | <code c> | ||
+ | #error MESSAGE | ||
+ | </code> | ||
+ | La întâlnirea acestei instrucţiuni de preprocesare compilatorul va raporta o eroare, având ca text explicativ mesajul **MESSAGE**. | ||
+ | |||
+ | <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. | ||
+ | |||
+ | ==== Funcţii cu număr variabil de parametri ==== | ||
+ | |||
+ | === Introducere === | ||
+ | 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. | ||
+ | |||
+ | Limbajul C permite totuşi şi declararea şi folosirea funcţiilor cu un număr (şi eventual tip) variabil de | ||
+ | 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 === | ||
+ | |||
+ | 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> | ||
+ | |||
+ | Notaţia **„ ,... ”** comunică compilatorului faptul că funcţia poate primi un număr arbitrar de parametrii | ||
+ | începând cu poziţia în care apare. De exemplu, prototipul funcţiei **fprintf()** arată în felul următor: | ||
+ | <code c> | ||
+ | void fprintf(FILE*, const char*, ...); | ||
+ | </code> | ||
+ | |||
+ | 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 | ||
+ | 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** ). | ||
+ | |||
+ | <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> | ||
+ | |||
+ | === Implementarea funcţiilor cu număr variabil de parametri === | ||
+ | |||
+ | Pentru prelucrarea listei de parametrii variabili este necesară includerea fişierului antet **stdarg.h**. | ||
+ | Acesta conţine declaraţii pentru tipul de date variable argument list (**va_list**) şi o serie de macrodefiniţii | ||
+ | pentru manipularea unei astfel de liste. În continuare este detaliat modul de lucru cu liste variabile de | ||
+ | parametri. | ||
+ | |||
+ | * 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 > | ||
- | CC=gcc | + | Fie o functie care afiseaza un număr variabil de numere naturale primite ca parametru. Lista este terminată cu un număr negativ. |
- | CFLAGS=-Wall | + | <code c> |
- | + | #include <stdio.h> // printf | |
- | all: task1 task2 task3 task4 task5 | + | #include <stdarg.h> // va_list, va_arg, |
- | + | // va_start,va_end | |
- | task1: task1.c | + | |
- | task2: task2.c | + | // semnatuna functie |
- | task3: task3.c | + | // - functia nu intoarce nimic(void) |
- | task4: task4.c | + | // - are un numar variabil de parametri (...) |
- | task5: task5.c | + | // - 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; | ||
+ | } | ||
+ | |||
+ | void list_ints(int first, ...) { | ||
+ | // tratam cazul special cand nu avem | ||
+ | // 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> | </code> | ||
- | === Testul 2 === | + | Observati comportamentul acestei functii pentru diversi parametri. |
+ | <code bash> | ||
+ | gcc -Wall -Wextra test.c -o test | ||
+ | ./test | ||
- | 1. (task1.c) De ce urmatorul program nu afişează rezultatul corect/aşteptat? | + | 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> | <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> | ||
+ | |||
+ | ==== Exerciții de laborator CB/CD ==== | ||
+ | |||
+ | Vă invităm să evaluați activitatea echipei de **programare CB/CD** și să precizați punctele tari și punctele slabe și sugestiile voastre de îmbunătățire a materiei. Feedback-ul vostru este foarte important pentru noi să creștem calitatea materiei în anii următori și să îmbunătățim materiile pe care le veți face în continuare. | ||
+ | |||
+ | <note> | ||
+ | Găsiți formularul de feedback în partea dreaptă a paginii principale de programare de pe cs.curs.pub.ro într-un frame numit “FEEDBACK” ([[http://cs.curs.pub.ro/2016/course/view.php?id=17|moodle]]). Trebuie să fiți inrolați la cursul de programare, altfel veți primi o eroare de acces. | ||
+ | </note> | ||
+ | |||
+ | Pentru a putea înțelege și valorifica feedback-ul mai ușor, apreciem orice formă de feedback negativ constructivă. Nu este suficient să ne spuneți, spre exemplu, //tema 5 a fost grea//, ne dorim să știm care au fost dificultățiile și, eventual, o propunere despre cum considerați că ar fi trebuit procedat. | ||
+ | |||
+ | Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul populeaza vectorul cu valorile parametrilor primiti de functie (atat timp cat valoarea parametrului este > 0). Daca nu s-a populat tot vectorul, se adauga 0 pe pozitiile ramase goale. | ||
+ | |||
+ | <spoiler ex1.c> | ||
+ | <code c ex1.c> | ||
+ | |||
#include <stdio.h> | #include <stdio.h> | ||
+ | #include <stdarg.h> | ||
- | int main() | + | #define N 10 |
+ | |||
+ | void insert_elements(int *v, int n, ...) | ||
{ | { | ||
- | float f=0.0f; | + | int count = 0; |
- | int i; | + | |
- | for(i=0;i<10;i++) | + | |
- | f = f + 0.1f; | + | |
- | + | ||
- | if(f == 1.0f) | + | |
- | printf("f is 1.0 \n"); | + | |
- | else | + | |
- | printf("f is NOT 1.0\n"); | + | |
- | return 0; | + | va_list args; |
+ | va_start(args, n); | ||
+ | |||
+ | int elem = va_arg(args, int); | ||
+ | while (elem > 0 && count < n) { | ||
+ | v[count++] = elem; | ||
+ | elem = va_arg(args, int); | ||
+ | } | ||
+ | |||
+ | while (count < n) { | ||
+ | v[count++] = 0; | ||
+ | } | ||
} | } | ||
- | </code> | ||
- | 2. (task2.c) Scrieţi un program care să afişeze câţi biţi dintr-un număr reprezentat pe 4 octeţi sunt 1. | + | int main(void) |
+ | { | ||
- | 3. (task3.c) Rezolvaţi următoarele cerinţe: | + | int v[N]; |
+ | int i; | ||
- | * 3.1 Definiţi structura Numar_Real, cu câmpurile parte întreagă, parte fracţionară. | + | insert_elements(v, N, 1, 2, 3, 4, -1); |
- | * 3.2 Definiţi funcţia ''Numar_Real* citire(char *filename,int *n)'', care să citească din fişierul dat ca parametru dimensiunea vectorului v, să îl aloce dinamic, apoi să citeasca n numere reale, şi să le reţină într-un vector, sub formă de structuri definite ca la punctul precedent. | + | for (i = 0; i < N; i ++) { |
+ | printf("%d ", v[i]); | ||
+ | } | ||
- | Formatul fişierului: | + | printf("\n"); |
- | n | + | return 0; |
- | nr_1 | + | } |
- | nr_2 | + | </code> |
- | ... | + | </spoiler> |
- | nr_n | + | Cerinte: |
+ | * Sa se modifice functia astfel incat sa se poata popula si un vector cu elemente de tip char. Functia va fi declarata astfel: | ||
+ | * **void insert_elemenets(void *v, int n, char type, …)** - unde type va specifica tipul (‘d’ pentru int si ‘c’ pentru char). | ||
+ | * Daca nu se umple tot vectorul cu valori, vor trebui puse niste valori default (cum este si in codul deja existent): pentru int va ramane valoarea 0, iar pentru char va fi ‘a’. | ||
+ | * In functie de simbolul NUMBERS, programul va popula fie un vector de numere, fie un vector de caractere si il va afisa (se pot lasa valori specifice trimise la functia de populare) | ||
+ | * Simbolul nu trebuie definit in cadrul fisierului sursa | ||
- | * 3.3 Scrieţi un program care să testeze funcţia de citire de la punctul precedent, afişând numerele din vector în ordinea crescătoare a părţii fracţionare. Afişarea se va face în forma: | + | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** |
- | i1 f1 | + | <hidden> |
- | i2 f2 | + | Link direct către lista completă de probleme: [[https://docs.google.com/document/d/1cOh9mCbPhAT6DqRUGUbRPBmaV01yK4-ESJdYkqZuDws/edit|aici]] |
- | ... | + | </hidden> |
- | in fn, | + | ==== Exerciţii de Laborator ==== |
- | unde i reprezintă partea întreagă, iar f partea fracţionară. | + | Exercițiile următoare fac referire la anumite exemple (ex. **Exemplu1**). Aceste exemple se găsesc în laborator (CTRL + F pentru **Exemplu1**). |
- | 4. (task4.c) Scrieţi un program care primeşte (ca argumente în linia de comandă) două numere ''a'' şi ''b'', şi poate primi una dintre opţiunile: “-m” sau “-f”. | + | - [1p] Completati formularul de **FEEDBACK** pentru [[https://acs.curs.pub.ro/2018/course/view.php?id=127 | Programarea calculatoarelor (CA)]]. Vă rugăm să îl completați cu atenție și răbdare. Apreciem orice feedback **NEGATIV CONSTRUCTIV **. Vă rugăm nu spuneți ** DOAR ** lucruri precum "tema X a fost grea", "nu am reușit să fac tot la parțial", "Gigel apare de prea multe ori prin laboratoare". Încercați să argumentați de ce credeți că s-a ajuns în acea situație și (eventual) cum credeți voi că am putea remedia asta pentru anul viitor. Mulțumim! |
- | * Veţi defini un macro care calculează minimul dintre a şi b, precum şi o funcţie cu acelaşi scop: <tt>int minim(int a, int b)</tt>. | + | - [1p] Să se realizeze un program care să afișeze toți parametrii liniei de comandă primiți. Hint: ** Exemplu1**. |
- | * Dacă este apelat cu optiunea “-m”, programul va afisa minimul dintre a si b, folosind macrodefinitia. | + | - [2p] Scrieti un program C care primeste argumente in linia de comanda. Acesta trebuie sa respecte urmatoarele reguli. |
- | * Dacă este apelat cu optiunea “-f”, programul va afişa adresa funcţiei <tt>minim</tt>. Dacă este apelat cu ambele optiuni, vor fi afişate ambele informaţii. | + | * Numele executabilului va fi ''gigel''. |
- | * Programul va afişa un mesaj de eroare în oricare dintre următoarele cazuri: nu sunt daţi parametrii a şi b (sau unul dintre ei), parametrii nu sunt cei specificaţi mai sus, număr prea mare de parametri. | + | * Primeste cel putin ''doua argumente'' in linia de comanda. In caz contrar, se va afisa la stderr mesajul ''Gigele, cel putin doua!'', iar programul se va termina cu codul de iesire ''-2''. |
+ | * Lista de argumente reprezinta un sir de numere naturale (''unsigned int''). Se cere sortarea listei folosind functia ''[[http://www.cplusplus.com/reference/cstdlib/qsort/?kw=qsort | qsort ]]''. | ||
+ | * Daca lista de argumente contine stringuri care nu reprezinta numere intregi fara semn, se va afisa la stderr mesajul ''Gigele, da-mi numere naturale!''. In acest caz, programul va iesi cu valoarea ''[[http://www.cplusplus.com/reference/climits/ | INT_MIN]]''. | ||
+ | - [2p] Realizați un macro SWAP(x, y). Hint: Puteti folosi ''sizeof'' pentru a determina cata memorie este necesara pentru variabila de interschimbare. De asemenea, puteti consulta si **Exemplu 5**. | ||
+ | - [4p] Realizați implementarea funcției cu ''număr variabil de parametri'' numită ''gcd'', care să permită aflarea celui mai mare divizor comun (''[[https://en.wikipedia.org/wiki/Greatest_common_divisor | gcd]]'') al parametrilor dați (''cel puțin'' două elemente care sunt numere naturale). Puteti presupune ca lista se termina cu un numar negativ. Hint: ** Exemplu8**. | ||
- | 5. (task5.c) Scrieţi un program care afişează caracterul ';' (cod ASCII: 59), fără a folosi '''deloc''' ';' în sursa C. (task5.c) | ||
- | 6. Modificaţi următorul fişier makefile, adăugând o regulă ''clean'', care să şteargă fişierele executabile, precum şi fisierele temporare, ale căror nume se termină cu '~'. | + | <hidden> |
- | <code c> | + | - [2.5p] Să se realizeze un program care, în funcție de starea simbolului ASCENDING să realizeze sortarea unui șir de numere. Astfel, dacă simbolul este definit, sortarea va fi crescătoare, dacă este nedefinit, sortarea va fi descrescătoare. Șirul de numere (întregi) este dat ca parametru în linia de comandă, iar definirea simbolului se va face din linia de comandă a compilatorului. |
- | #makefile | + | - [1.5p] Realizați un macro SWAP(x, y, type) care ia ca parametrii un tip de date, și două variabile și le interschimba conținutul. |
+ | mare divizor comun al parametrilor de tip int dați (cel puțin două elemente). | ||
+ | </hidden> | ||
- | CC=gcc | + | <hidden> |
- | CFLAGS=-Wall | + | Să se realizeze un program care să afișeze, în ''ordine inversă'', toți parametrii primiți în linia de comandă. Fiecare parametru se va afișa pe o linie separată. Să se rezolve problema DOAR cu instructiuni repetive, apoi doar cu RECURSIVITATE. |
- | + | ||
- | all: task1 task2 task3 task4 task5 | + | - [2p] Realizați o funcție cu număr variabil de parametri, numită de exemplu myscanf() care să permită citirea cu format a unui număr de variabile dintr-un fișier binar. Prototipul va arăta în modul următor: int myprintf(FILE*, char *format, ...); |
- | + | * Funcția va fi scrisă într-un fișier separat. Asigurați-vă că fișierul nu poate fi inclus de mai multe ori în același program! | |
- | task1: task1.c | + | * Funcțiile myscanf() va folosi în implementare funcția fread() și va funcționa în felul următor: |
- | task2: task2.c | + | * pentru fiecare caracter care nu e specificator de format, se va avansa o poziţie (un octet) în fişier. |
- | task3: task3.c | + | * pentru fiecare specificator de format, se va citi din fișier în variabila curentă un număr de octeți corespunzător tipului dat de specificator (vezi mai jos). |
- | task4: task4.c | + | * Specificatorii de format suportați sunt următorii: |
- | task5: task5.c | + | * %c - dată de tip char |
- | </code> | + | * %d - dată de tip integer |
+ | * %f - dată de tip float | ||
+ | * Pentru simplitate se va considera că după '%' urmează mereu unul din cei 3 specificatori. | ||
+ | * Pentru testare folosiți fișierul de ''lab14.bin''; afișați la stdout datele citite. | ||
+ | mare divizor comun al parametrilor de tip int dați (cel puțin două elemente). | ||
+ | </hidden> | ||
+ | |||
+ | ==== Bonus ==== | ||
+ | |||
+ | [4p] ''mycat'' (PC CA - 2015). Implementaţi comanda ''mycat [+-][n][LCB] nume_fisier'', având ca efect scrierea a n unităţi din fişierul text specificat la unitatea standard de ieşire. | ||
+ | O unitate poate reprezenta: ''o linie (L)'', ''un caracter (C)'' sau ''un bloc'' (B = 512 caractere). | ||
+ | * Dacă primul argument este ''+'' se vor afişa ''primele n unităţi'' din fişier, iar dacă este ''–'' atunci se afişează ''ultimele n unităţi''. Cu excepţia numelui fişierului, oricare din elementele comenzii poate lipsi, caz in care se consideră valorile implicite: + pentru primul argument, 10 pentru argumentul 2 şi L pentru argumentul 3. | ||
+ | * Exemple: | ||
+ | |||
+ | > ''mycat date.txt'' – afişează primele 10 linii din fişier | ||
+ | |||
+ | > ''mycat - date.txt'' – afişează ultimele 10 linii din fişier | ||
+ | |||
+ | > ''mycat -25 C date.txt'' – afişează ultimele 25 de caractere din fişier | ||
+ | |||
+ | > ''mycat +3 B date.txt'' – afişează primele 3 blocuri din fişier | ||
+ | |||
+ | * Indicaţie: În cazul în care se cere afişarea ultimelor ''n'' linii din fişier, se va determina numărul de linii din fişier ''nl'' şi se vor ignora primele ''nl-n linii''. Dacă se cere afişarea ultimelor ''n'' caractere, va trebui să contorizăm numărul de caractere din fişier. | ||
+ | * ''NU'' se vor folosi variabile globale. | ||
+ | |||
+ | * Punctaje: | ||
+ | * Pentru implementarea corecta si completa a tuturor functionalitatilor corespunzatoare unui argument se acorda cate ''1p''. | ||
+ | * Se acorda inca ''0.5p'' daca rularea respecta formatul de mai sus. Hint: ''[[http://alvinalexander.com/blog/post/linux-unix/create-aliases | Linux alias command]]''. | ||
+ | * Se acorda inca ''0.5p'' daca implementarea propusa este performanta. Justificati alegerea facuta! | ||
+ | |||
+ | |||
+ | <hidden> | ||
+ | - [2p] Implementați myprintf, care afișează după formatul de mai sus în fișier binar. | ||
+ | * pentru fiecare caracter care nu e specificator de format, se va scrie în fișier chiar caracterul corespunzător | ||
+ | * pentru fiecare specificator de format, se va scrie în fișier din variabila curentă un număr de octeți corespunzător tipului dat de specificator (vezi mai jos). | ||
+ | </hidden> | ||
+ | |||
+ | [[https://docs.google.com/document/d/1lwQBmJ4YuaBmpUOb5uUIs27TwLvWu-6jTL3aQ9TAwIA/edit?usp=sharing|Probleme laborator 14:00 - 16:00]] |