This shows you the differences between two versions of the page.
|
programare:laboratoare:lab09 [2019/11/25 05:47] dragos.corlatescu [Exercitii laborator CB/CD] |
programare:laboratoare:lab09 [2025/10/15 18:32] (current) darius.neatu [Referinţe] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Alocarea dinamică a memoriei. Aplicaţii folosind tablouri şi matrice. ===== | + | ===== PCLP Laborator09: Operaţii cu fişiere. Aplicaţii folosind fişiere ===== |
| - | **Responsabil:** | + | **Resposabili:** |
| - | *[[laura.vasilescu@cti.pub.ro|Laura Vasilescu]] | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2016-2020)]] |
| - | *[[murarugeorgec@gmail.com|George Muraru]] | + | * [[neatudarius@gmail.com|Darius Neațu (2019-2020)]] |
| + | * [[mihaela.vasile@gmail.com|Mihaela Vasile (2015)]] | ||
| + | ==== Obiective ==== | ||
| - | ===== Obiective ===== | + | În urma parcurgerii acestui laborator studentul va fi capabil să: |
| + | * lucreze cu fişiere text (deschidere, închidere, citire, scriere) | ||
| + | * înteleaga un fişier binar şi să lucreze cu el; | ||
| + | * să se poziţioneze in interiorul unui fişier; | ||
| + | * poată determina poziţia în cadrul unui fişier; | ||
| + | * înteleagă diferenţa între organizarea internă a fişierelor pe sistemele de operare Linux şi Windows. | ||
| - | În urma parcurgerii acestui laborator studentul va fi capabil: | + | ==== Noţiuni teoretice ==== |
| - | * să aloce dinamic o zona de memorie; | + | |
| - | * să elibereze o zona de memorie; | + | |
| - | * să lucreze cu vectori şi matrice alocate dinamic. | + | |
| - | ===== Funcţii de alocare şi eliberare a memoriei ===== | + | === Introducere === |
| - | Funcțiile standard de alocare și de eliberare a memoriei sunt declarate în fişierul antet ''stdlib.h''. | + | Un fişier este o structură dinamică, situată în memoria secundară (pe disk-uri). Limbajul C permite operarea cu fişiere: |
| - | * ''void *malloc(size_t size);'' | + | * de **tip text** - un astfel de fişier conţine o succesiune de **linii**, separate prin new line ('\n') |
| - | * ''void *calloc(size_t nmemb, size_t size);'' | + | * de **tip binar** - un astfel de fişier conţine o succesiune de octeti, **fără nici o structură**. |
| - | * ''void *realloc(void *ptr, size_t size);'' | + | |
| - | * ''void free(void *ptr);'' | + | |
| - | ==== Alocarea memoriei ===== | + | Prelucrarea unui fişier presupune asocierea acestuia cu un canal de I/E (numit flux sau stream). Există trei canale predefinite, care se deschid automat la lansarea unui program: |
| + | * **stdin** - fişier de intrare, text, este intrarea standard - tastatura | ||
| + | * **stdout** - fişier de iesire, text, este ieşirea standard - ecranul monitorului. | ||
| + | * **stderr** – fişier de iesire, text, este ieşirea standard unde sunt scris mesajele de eroare - ecran. | ||
| - | Cele trei funcţii de alocare (''malloc'', ''calloc'' și ''realloc'') au ca rezultat adresa zonei de memorie alocate (de tip ''void*'') şi ca argument comun dimensiunea, în octeţi, a zonei de memorie alocate (de tip ''size_t'' ). Dacă cererea de alocare nu poate fi satisfăcută pentru că nu mai există un bloc continuu de dimensiunea solicitată, atunci funcţiile de alocare au rezultat ''NULL'' (ce reprezintă un pointer de tip ''void *'' la adresa de memorie 0, care prin convenţie este o adresă nevalidă - nu există date stocate în acea zonă). | + | Pentru a prelucra un fişier, trebuie parcurse următoarele etape: |
| + | * **se defineşte** o variabilă de tip **FILE*** pentru accesarea fişierului; **FILE** este un tip structură definit în <stdio.h>, care conţine informaţii referitoare la fişier şi la tamponul de transfer de date între memoria centrală şi fişier (adresa, lungimea tamponului, modul de utilizare a fişierului, indicator de sfârsit, de poziţie în fişier). Puteți citi mai multe [[http://stackoverflow.com/questions/5672746/what-exactly-is-the-file-keyword-in-c | aici ]]. | ||
| + | * **se deschide fişieru**l pentru un anumit **mod de acces**, folosind funcţia de bibliotecă **fopen**, care realizează şi asocierea între variabila fişier şi numele extern al fişierului | ||
| + | * **se prelucrează fişierul** în citire/scriere cu **funcţiile specifice** | ||
| + | * **se închide fişierul** folosind funcţia de bibliotecă **fclose** | ||
| - | == Exemplu == | + | === Funcții === |
| - | <code c> | + | Mai jos se prezintă restul funcţiilor de prelucrare a fişierelor. Pentru documentația oficială puteți citi [[http://www.cplusplus.com/reference/cstdio/ | aici]]. |
| - | char *str = malloc(30); // Aloca memorie pentru 30 de caractere | + | |
| - | int *a = malloc(n * sizeof(int)); // Aloca memorie pt. n numere intregi | + | == fopen == |
| + | <code c> | ||
| + | FILE *fopen(const char *filename, const char *mod); | ||
| </code> | </code> | ||
| - | <note>Dimensiunea memoriei luată ca parametru de ''malloc()'' este specificată în ''octeţi'', indiferent de tipul de date care va fi stocat în acea regiune de memorie! Din acest motiv, pentru a aloca suficientă memorie, numărul dorit de elemente trebuie înmulţit cu dimensiunea unui element, atunci când are loc un apel ''malloc()''.</note> | + | deschide fişierul cu numele **filename** pentru acces de tip **mod**. |
| + | Returnează **pointer la fişier** sau **NULL** dacă fişierul nu poate fi deschis; valoarea returnată este memorată în variabila fişier, care a fost declarată pentru accesarea lui. | ||
| - | Alocarea de memorie pentru un vector şi iniţializarea zonei alocate cu zerouri se poate face cu funcţia ''calloc''. | + | Modul de deschidere poate fi: |
| - | == Exemplu == | + | * "**r**" - **readonly** , este permisă doar citirea dintr-un fişier existent |
| - | <code c> | + | * "**w**" - **write**, crează un nou fişier, sau dacă există deja, distruge vechiul continut |
| - | int *a = calloc(n, sizeof(int)); // Aloca memorie pentru n numere intregi și inițializează zona cu zero | + | * "**a**" - **append**, deschide pentru scriere un fişier existent ( scrierea se va face în continuarea |
| - | </code> | + | informaţiei deja existente în fişier, deci pointerul de acces se plasează la sfârşitul fişierului ) |
| + | * "**+**" - permite scrierea şi citirea - **actualizare** (ex: "r+", "w+", "a+"). Între read şi write trebuie repoziţionat cursorul de acces printr-un apel la **fseek**. | ||
| - | Codul de mai sus este perfect echivalent (dar mai rapid) cu următoarea secvenţă de instrucţiuni: | + | * "**b**" - specifică fişier de tip **binar** |
| + | * "**t**" - specifică fişier de tip **text** (implicit), la care se face automat conversia CR-LF("\n\f") în sau din CR ('\n'). | ||
| + | |||
| + | == fclose == | ||
| <code c> | <code c> | ||
| - | int i; | + | int fclose(FILE *pFile); |
| - | int *a = malloc(n * sizeof(int); | + | |
| - | for (i = 0; i < n; i++) { | + | |
| - | a[i] = 0; | + | |
| - | } | + | |
| </code> | </code> | ||
| - | <note>În timp ce funcţia ''malloc()'' ia un singur parametru (o dimensiune în octeţi), funcţia ''calloc()'' primeşte două argumente, o lungime de vector şi o dimensiune a fiecărui element. Astfel, această funcţie este specializată pentru memorie organizată ca un vector, în timp ce ''malloc()'' nu ţine cont de structura memoriei.</note> | + | **închide fişierul** asociat cu variabila **pFile** şi eliberează zona tampon; returnează 0 la succes, EOF (end of file) la eroare |
| - | <note warning>Acest lucru aduce și o măsură de siguranță în plus deoarece înmulțirea dintre numărul de elemente și dimensiunea tipului de date ar putea face overflow, iar dimensiunea memoriei alocate să nu fie în realitatea cea așteptată.</note> | + | |
| - | Realocarea unui vector care creşte (sau scade) faţă de dimensiunea estimată anterior se poate face cu funcţia ''realloc'', care primeşte adresa veche şi noua dimensiune şi întoarce **noua adresă**: | + | == fseek == |
| <code c> | <code c> | ||
| - | int *aux; | + | int fseek(FILE *pFile, long offset, int whence); |
| - | aux = realloc(a, 2 * n * sizeof(int)); // Dublare dimensiune anterioara (n) | + | |
| - | if (aux) //daca aux este diferit de NULL | + | |
| - | a = aux; | + | |
| - | else | + | |
| - | //prelucrare in caz de eroare | + | |
| </code> | </code> | ||
| - | În exemplul anterior, noua adresă este memorată tot în variabila pointer ''a'', înlocuind vechea adresă (care nu mai este necesară şi nici nu mai trebuie folosită). Funcţia ''realloc()'' realizează următoarele operaţii: | + | **repoziţionează pointerul** asociat fişierului **pFile**; offset - numărul de octeţi între poziţia dată de whence şi noua poziţie. |
| - | * alocă o zonă de dimensiunea specificată ca al doilea argument | + | |
| - | * copiază la noua adresă datele de la adresa veche (primul argument al funcţiei) | + | |
| - | * eliberează memoria de la adresa veche. | + | |
| - | In cazul in care nu reuseste sa aloce memorie functia ''realloc()'' intoarce NULL lasand pointerul initial nemodificat. Din acest motiv verificam daca realocarea a reusit si apoi asignam rezultatul pointerului ''a''. | + | |
| - | ==== Eliberarea memoriei ===== | + | whence - are una din cele trei valori posibile: |
| + | * SEEK_SET = 0 - Căutarea se face de la începutul fişierului | ||
| + | * SEEK_CUR = 1 - Căutare din poziţia curentă | ||
| + | * SEEK_END = 2 - Căutare de la sfârşitul fişierului | ||
| - | Funcţia ''free()'' are ca argument o adresă (un pointer) şi eliberează zona de la adresa respectivă (alocată prin apelul unei funcţii de tipul ''[m|c|re]alloc''). Dimensiunea zonei nu mai trebuie specificată deoarece este ţinută minte de sistemul de alocare de memorie în nişte structuri interne. | + | == ftell == |
| + | <code c> | ||
| + | long ftell(FILE *pFile); | ||
| + | </code> | ||
| - | ===== Vectori alocaţi dinamic ===== | + | **întoarce poziţia curentă** în cadrul fișierului asociat cu pFile. |
| - | Structura de vector are avantajul simplităţii şi economiei de memorie faţă de alte structuri de date folosite pentru memorarea unei colectii de informaţii între care există anumite relaţii. Între cerinţa de dimensionare constantă a unui vector şi generalitatea programelor care folosesc astfel de vectori există o contradicţie. De cele mai multe ori programele pot afla (din datele citite) dimensiunile vectorilor cu care lucrează şi deci pot face o alocare dinamică a memoriei pentru aceşti vectori. Aceasta este o solutie mai flexibilă, care foloseşte mai bine memoria disponibilă şi nu impune limitări arbitrare asupra utilizării unor programe. În limbajul C nu există practic nici o diferenţă între utilizarea unui vector cu dimensiune fixă şi utilizarea unui vector alocat dinamic, ceea ce încurajează şi mai mult utilizarea unor vectori cu dimensiune variabilă. | + | == fgetpos == |
| - | + | ||
| - | == Exemplu == | + | |
| <code c> | <code c> | ||
| - | int main(void) | + | int fgetpos(FILE *pFile, fpos_t *ptr); |
| - | { | + | </code> |
| - | int n,i; | + | |
| - | int *a; // Adresa vector alocat dinamic | + | |
| - | printf("n = "); | + | această funcţie **memorează poziţia curentă** în variabila ptr în cadrul fişierului asociat cu pFile (ptr va putea fi folosit ulterior cu funcţia fsetpos). |
| - | scanf("%d", &n); // Dimensiune vector | + | |
| - | a = calloc(n, sizeof(int)); // Alternativ: a = malloc(n * sizeof(int)); | + | == fsetpos == |
| - | printf("Componente vector: \n"); | + | <code c> |
| - | + | int fsetpos(FILE *pFile, const fpos_t *ptr); | |
| - | for (i = 0; i < n; i++) { | + | </code> |
| - | scanf("%d", &a[i]); // Sau scanf (“%d”, a+i); | + | această funcţie **setează poziţia curentă** în fişierul asociat cu pFile la valoarea ptr, obţinută anterior prin funcţia fgetpos. |
| - | } | + | |
| - | for (i = 0; i < n; i++) { // Afisare vector | + | |
| - | printf("%d ",a[i]); | + | |
| - | } | + | |
| - | free(a); // Nu uitam sa eliberam memoria | + | == feof == |
| + | <code c> | ||
| + | int feof(FILE *fis); | ||
| + | </code> | ||
| + | returnează 0 dacă nu s-a detectat **sfârşit de fişier** la ultima operaţie de citire, respectiv o valoare nenulă (adevărată) pentru sfârşit de fişier. | ||
| - | return 0; | + | == freopen == |
| - | } | + | <code c> |
| + | FILE* freopen(const char *filename, const char *mode, FILE *fp); | ||
| </code> | </code> | ||
| - | <note tip>Puteti testa codul [[http://tpcg.io/BZeAR5|aici]]. Trebuie introdus in tabul de STDIN inputul.</note> | + | se închide fişierul fp, se deschide fişierul cu numele filename în modul mode şi acesta se asociază la fp; se întoarce fp sau NULL în caz de eroare. |
| - | Există şi cazuri în care datele memorate într-un vector rezultă din anumite prelucrări, iar numărul lor nu poate fi cunoscut de la începutul execuţiei. Un exemplu poate fi un vector cu toate numerele prime mai mici ca o valoare dată. În acest caz se poate recurge la o realocare dinamică a memoriei. În exemplul următor se citeşte un număr necunoscut de valori întregi într-un vector extensibil: | + | == fflush == |
| <code c> | <code c> | ||
| - | #define INCR 100 // cu cat creste vectorul la fiecare realocare | + | int fflush(FILE *fp); |
| + | </code> | ||
| + | Această funcţie se utilizează pentru fişierele deschise pentru scriere şi are ca efect scrierea în fişier a datelor din bufferul asociat acestuia, care înca nu au fost puse în fişier. | ||
| - | int main(void) | + | === Citirea şi scrierea în/din fişiere === |
| - | { | + | |
| - | int n, i, m; | + | |
| - | float x, *v, *tmp; // v = adresa vector | + | |
| - | + | ||
| - | n = INCR; | + | |
| - | i = 0; | + | |
| - | v = malloc(n * sizeof(float)); // Dimensiune initiala vector | + | Citirea/scrierea în fişiere se poate face în două moduri (în funcție de tipul fişierului): în mod text sau în mod binar. Principalele diferenţe dintre cele două moduri sunt: |
| - | if (v == NULL) { | + | * în modul text, la sfarsitul fişierului se pune un caracter suplimentar, care indică sfârşitul de fişier. În DOS şi Windows se utilizează caracterul cu codul ASCII 26 (Ctrl-Z), iar în Unix se utilizează caracterul cu codul ASCII 4. Dacă citim un fişier în mod text, citirea se va opri la intâlnirea acestui caracter, chiar dacă mai există şi alte caractere după el. În modul binar nu există caracter de sfârşit de fişier (mai precis, caracterul cu codul 26, respectiv 4, este tratat la fel ca şi celelalte caractere). |
| - | /* Nu s-a reusit alocarea */ | + | * în DOS şi Windows, în modul text, sfârşitul de linie este reprezentat prin două caractere, CR (Carriage Return, cod ASCII 13) şi LF (Line Feed, cod ASCII 10). Atunci când în modul text scriem un caracter '\n' (LF) în fişier, acesta va fi convertit într-o secventă de 2 caractere CR şi LF. Când citim în mod text dintr-un fişier, secvenţa CR, LF este convertită într-un '\n' (LF). În Unix, sfârşitul de linie este reprezentat doar prin caracterul LF. În mod binar, atât în DOS-Windows cât şi în Unix, sfârşitul de linie este reprezentat doar prin caracterul LF. |
| - | printf("Could not allocate v\n"); | + | |
| - | return 1; | + | |
| - | } | + | |
| - | + | ||
| - | while (scanf("%f", &x) != EOF) { | + | |
| - | if (i == n) { // Daca este necesar... | + | |
| - | n = n + INCR; // ... creste dimensiune vector | + | |
| - | tmp = realloc(v, n * sizeof(float)); | + | |
| - | if (tmp != NULL) { | + | |
| - | /* Daca s-a reusit alocarea pentru noua zona de memorie */ | + | |
| - | v = tmp; | + | |
| - | } else { | + | |
| - | /* Daca nu s-a reusit alocarea */ | + | |
| - | break; | + | |
| - | } | + | |
| - | } | + | |
| - | v[i++] = x; // Memorare in vector numar citit | + | Modul binar se utilizează de obicei pentru a scrie în fişier datele exact aşa cum sunt reprezentate în memorie (cu functiile **fread**, **fwrite**) - de exemplu pentru un număr intreg se va scrie reprezentarea internă a acestuia, pe 2 sau pe 4 octeti. |
| - | } | + | |
| - | m = i; | + | Modul text este utilizat mai ales pentru scrierea cu format (cu funcţiile fprintf, fscanf) - în cazul acesta pentru un număr întreg se vor scrie caracterele ASCII utilizate pentru a reprezenta cifrele acestuia (adică un şir de caractere cum ar fi "1" sau "542"). |
| - | for (i = 0; i < m; i++) { // Afisare vector | + | === Citire/scriere cu format === |
| - | printf("%f ", v[i]); | + | |
| - | } | + | |
| - | printf("\n"); | + | |
| - | free(v); | + | <code c> |
| - | + | int fprintf(FILE *fp, const char *format, ...); | |
| - | return 0; | + | int fscanf(FILE *fp, const char *format, ...); |
| - | } | + | |
| </code> | </code> | ||
| - | <note tip>Puteti testa codul [[http://tpcg.io/KSDiLd|aici]]. Trebuie introdus in tabul de STDIN inputul.</note> | + | Funcţiile sunt utilizate pentru citire/scriere în mod text şi sunt asemănătoare cu printf/scanf (diferenţa fiind că trebuie dat pointerul la fişier ca prim parametru). |
| - | ===== Matrice alocate dinamic ===== | + | == Exemplu 1 == |
| + | Să presupunem că avem următorul fișier care pe prima linie conține un număr natural nenul n, iar pe a doua linie se află n numere întregi reprezentând elementele unui vector. Se cere citirea vectorului, dublarea fiecarui element, apoi salvarea rezultatelor în fișierul "gigel.out". | ||
| - | Alocarea dinamică pentru o matrice este importantă deoarece: | + | <code c> |
| - | * foloseşte economic memoria şi evită alocări acoperitoare, estimative. | + | gigel.in |
| - | * permite matrice cu linii de lungimi diferite (denumite uneori ''ragged arrays'', datorită formelor "zimţate" din reprezentările grafice) | + | 5 |
| - | * reprezintă o soluţie bună la problema argumentelor de funcţii de tip matrice. | + | 1 3 -1 6 7 |
| - | + | </code> | |
| - | Daca programul poate afla numărul efectiv de linii şi de coloane al unei matrice (cu dimensiuni diferite de la o execuţie la alta), atunci se va aloca memorie pentru un vector de pointeri (funcţie de numărul liniilor) şi apoi se va aloca memorie pentru fiecare linie (funcţie de numărul coloanelor) cu memorarea adreselor liniilor în vectorul de pointeri. O astfel de matrice se poate folosi la fel ca o matrice declarată cu dimensiuni constante. | + | |
| - | == Exemplu == | + | Rulați și înțelegeți următorul cod C. |
| <code c> | <code c> | ||
| - | int main (void) | + | #include <stdio.h> |
| - | { | + | #define NMAX 100 |
| - | int **a; | + | |
| - | int i, j, nl, nc; | + | |
| - | printf("nr. linii = "); | + | int main() { |
| - | scanf(“%d”, &nl); | + | // numele fisierului de intrare |
| + | char input_filename[] = "gigel.in"; | ||
| - | printf("nr. coloane = "); | + | // deschidere fisier de intrare pentru |
| - | scanf(“%d”, &nc); | + | // citire (r) in modul text (t) |
| + | FILE *in = fopen(input_filename, "rt"); | ||
| - | /* | + | // verific daca fisierul a fost deschis cu succes |
| - | * In cele ce urmeaza presupunem ca toate apelurile de alocare de memorie | + | // altfel opresc executia (in cazul acestei probleme) |
| - | * nu vor esua. | + | if (in == NULL) { |
| - | */ | + | fprintf(stderr, "ERROR: Can't open file %s", input_filename); |
| - | a = malloc(nl * sizeof(int *)); // Alocare pentru vector de pointeri | + | return -1; |
| + | } | ||
| - | for (i = 0; i < n; i++) { | + | int n, v[NMAX], i; // numarul de elemente && vectorul |
| - | a[i] = calloc(nc, sizeof(int)); // Alocare pentru o linie si initializare la zero | + | |
| - | } | + | |
| - | // Completare diagonala matrice unitate | + | // citesc n din fisier |
| - | for (i = 0; i < nl; i++) { | + | fscanf(in, "%d", &n); |
| - | a[i][i] = 1; // a[i][j]=0 pentru i != j | + | //citesc tabloul |
| - | + | for (i = 0; i < n; ++i) { | |
| + | fscanf(in, "%d", &v[i]); | ||
| + | } | ||
| - | // Afisare matrice | + | // deoarece stiu sigur ca nu mai am nimic de citit |
| - | printmat(a, nl, nc); | + | // pot inchide fisierul de intrare |
| + | fclose(in); | ||
| - | for (i = 0; i < nl; i++) | + | // dublez elementele din vector |
| - | free(a[i]); | + | for (i = 0; i < n; ++i) { |
| - | free(a); // Nu uitam sa eliberam! | + | v[i] <<= 1; |
| + | } | ||
| - | return 0; | + | // deschid fisierul pentru a scrie rezultatele |
| - | } | + | char output_filename[] = "gigel.out"; |
| - | </code> | + | // deschid pentru scriere (w) in modul text (t) |
| + | FILE *out = fopen(output_filename, "wt"); | ||
| - | Funcţia de afişare a matricei se poate defini astfel: | + | // verific daca fisierul a fost deschis cu succes |
| - | <code c> | + | // altfel opresc executia (in cazul acestei probleme) |
| - | void printmat(int **a, int nl, int nc) { | + | if (out == NULL) { |
| - | for (i = 0; i < nl; i++) { | + | fprintf(stderr, "ERROR: Can't open file %s", output_filename); |
| - | for (j = 0; j < nc; j++) { | + | return -1; |
| - | printf("%2d", a[i][j]); | + | |
| } | } | ||
| - | printf("\n"); | + | // scriu n si vectorul in fisier |
| - | } | + | fprintf(out, "%d\n", n); |
| - | } | + | for (i = 0; i < n; ++i) { |
| - | </code> | + | fprintf(out, "%d ", v[i]); |
| + | } | ||
| + | fprintf(out, "\n"); | ||
| - | Notaţia ''a[i][j]'' este interpretată astfel pentru o matrice alocată dinamic: | + | // inchid fisierul de iesire |
| - | * ''a[i]'' conţine un pointer (o adresă ''b'') | + | fclose(out); |
| - | * ''b[j]'' sau ''b+j'' conţine întregul din poziţia ''j'' a vectorului cu adresa ''b''. | + | |
| - | Astfel, ''a[i][j]'' este echivalent semantic cu expresia cu pointeri ''*(*(a + i) + j)''. | + | return 0; |
| - | + | ||
| - | Totuşi, funcţia ''printmat()'' dată anterior nu poate fi apelată dintr-un program care declară argumentul efectiv ca o matrice cu dimensiuni constante. Exemplul următor este corect sintactic dar nu se execută corect: | + | |
| - | <code c> | + | |
| - | int main() { | + | |
| - | int x[2][2] = { {1, 2}, {3, 4} }; // O matrice patratica cu 2 linii si 2 coloane | + | |
| - | printmat((int**)x, 2, 2); | + | |
| - | return 0; | + | |
| } | } | ||
| </code> | </code> | ||
| - | Explicaţia este interpretarea diferită a conţinutului zonei de la adresa aflată în primul argument: funcţia ''printmat()'' consideră că este adresa unui vector de pointeri (''int *a[]''), iar programul principal consideră că este adresa unui vector de vectori (''int x[][2]''), care este reprezentat liniar in memorie. | + | Pentru exemplul de fișier de intrare de mai sus, rezultatul este următorul. |
| - | + | ||
| - | Se poate defini şi o funcţie pentru alocarea de memorie la execuţie pentru o matrice. | + | |
| - | + | ||
| - | == Exemplu == | + | |
| <code c> | <code c> | ||
| - | int **newmat(int nl, int nc) { // Rezultat adresa matrice | + | gigel.out |
| - | int i; | + | 5 |
| - | int **p = malloc(nl * sizeof(int *)); | + | 2 6 -2 12 14 |
| - | + | ||
| - | for (i = 0; i < n; i++) { | + | |
| - | p[i] = calloc(nc, sizeof(int)); | + | |
| - | } | + | |
| - | + | ||
| - | return p; | + | |
| - | } | + | |
| </code> | </code> | ||
| - | ===== Stil de programare ===== | + | == Exemplu 2 == |
| - | ==== Exemple de programe ==== | + | Fie un fișier text cu un nume dat. Scrieți o funcție care calculează numărul de bytes (dimensiune fișierului). |
| - | + | ||
| - | **''Exemplul 1''**: Funcţie echivalentă cu funcţia de bibliotecă ''strdup()'': | + | |
| - | <code c> | + | |
| - | // Alocare memorie si copiere sir | + | |
| - | char *mystrdup(char *adr) | + | |
| - | { | + | |
| - | int len = strlen(adr); | + | |
| - | char *rez = malloc(len + 1); // len+1, deoarece avem si un '\0' la final | + | |
| - | + | ||
| - | /* Daca alocarea nu a reusit, intorcem NULL */ | + | |
| - | if(rez == NULL) | + | |
| - | return NULL; | + | |
| - | + | ||
| - | strcpy(rez, adr); | + | |
| - | + | ||
| - | return rez; | + | |
| - | } | + | |
| - | </code> | + | |
| <code c> | <code c> | ||
| #include <stdio.h> | #include <stdio.h> | ||
| - | #include <string.h> | ||
| - | #include <stdlib.h> | ||
| - | // Utilizare "mystrdup" | + | int sizeof_file(char *filename) { |
| - | int main(void) | + | // deschidere fisier de intrare pentru |
| - | { | + | // citire (r) in modul text (t) |
| - | char s[80], *d; | + | FILE *file = fopen(filename, "rt"); |
| - | do { | + | // verific daca fisierul a fost deschis cu succes |
| - | if (fgets(s, 80, stdin) == NULL) { | + | // altfel opresc executia |
| - | break; | + | if (file == NULL) { |
| + | return -1; // nu am putut calcula dimensiunea | ||
| } | } | ||
| - | | ||
| - | d = mystrdup(s); | ||
| - | if (d != NULL) { | ||
| - | /* Nu s-a reusit alocarea de memorie */ | ||
| - | fputs(d, stdout); | ||
| - | free(d); | ||
| - | } else { | ||
| - | printf("Nu s-a reusit alocarea\n"); | ||
| - | return 1; | ||
| - | } | ||
| - | } while (1); | ||
| - | return 0; | + | // ma pozitionez la sfarsit |
| - | } | + | fseek(file, 0, SEEK_END); |
| - | </code> | + | // acum cursorul este dupa ultimul caracter |
| - | Puteti testa codul [[http://tpcg.io/eaSDUd|aici]]. | + | // deci ftell imi va spune pozitia, care este echivalenta cu numarul de bytes |
| + | int bytes_count = ftell(file); | ||
| - | In exemplele urmatoare consideram ca toate alocarile de memorie nu vor esua. | + | // inchid fisierul |
| + | fclose(file); | ||
| - | **''Exemplul 3''**: Vector realocat dinamic (cu dimensiune necunoscută) | + | return bytes_count; |
| + | } | ||
| - | <code c> | + | int main() { |
| - | #include <stdio.h> | + | // numele fisierului de intrare |
| - | #include <stdlib.h> | + | char filename[] = "gigel.in"; |
| - | #define INCR 4 | + | int sz = sizeof_file(filename); |
| + | if (sz < 0) { | ||
| + | // afisez la stderr | ||
| + | fprintf(stderr, "ERROR: Can't open file %s", filename); | ||
| + | return -1; | ||
| + | } | ||
| - | int main(void) | + | // afisez la stdout rezultatul |
| - | { | + | printf("fisierul %s are %d bytes\n", filename, sz); |
| - | int n, i, m; | + | |
| - | float x, *v; | + | |
| - | n = INCR; | + | // pot folosi tot fprintf pentru a afisa la stdout |
| - | i = 0; | + | fprintf(stdout, "fisierul %s are %d bytes\n", filename, sz); |
| - | v = malloc(n * sizeof(float); | + | return 0; |
| + | } | ||
| - | while (scanf("%f", &x) != EOF) { | + | </code> |
| - | if (i == n) { | + | |
| - | n = n + INCR; | + | |
| - | v = realloc(v, n * sizeof(float); | + | |
| - | } | + | |
| - | + | ||
| - | v[i++] = x; | + | |
| - | } | + | |
| - | m = i; | + | === Citire/scriere fără conversie === |
| - | for (i = 0; i < m; i++) { | + | <code c> |
| - | printf("%.2f ", v[i]); | + | size_t fread(void *ptr, size_t size, size_t nrec, FILE *fp); |
| - | } | + | size_t fwrite(const void *ptr, size_t size, size_t nrec, FILE *fp); |
| - | printf("\n"); | + | |
| - | + | ||
| - | free(v); | + | |
| - | + | ||
| - | return 0; | + | |
| - | } | + | |
| </code> | </code> | ||
| - | Puteti testa codul [[http://tpcg.io/q0djWl|aici]]. | ||
| + | Cu aceste funcţii lucrăm cand deschidem fişierul în mod binar; citirea/scrierea se face fără nici un fel de conversie sau interpretare. Se lucrează cu "înregistrări", adică zone compacte de memorie: funcţia fread citeşte nrec înregistrări începănd de la poziţia curentă din fişierul fp, o înregistrare având dimensiunea size. Acestea sunt depuse în tabloul ptr. "Înregistrările" pot fi asociate cu structurile din C - adică în mod uzual, tabloul ptr este un tablou de structuri (dar în loc de structuri putem avea şi tipuri simple de date). | ||
| - | **''Exemplul 4''**: Matrice alocată dinamic (cu dimensiuni necunoscute la execuţie) | ||
| + | == Exemplu 3 == | ||
| + | Să reluăm problema de la Exemplul 1. Să presupunem că rezolvăm aceeași problemă, doar ca fișierul de intrare este binar: primii 4 bytes din fișier reprezintă numarul n (numărul de elemente); următorii 4*n bytes reprezintă vectorul. | ||
| + | |||
| + | Luăm ca exemplul fișierul text care are conținutul. | ||
| + | <code c> | ||
| + | gigel.in | ||
| + | 3 | ||
| + | 1 2 3 | ||
| + | </code> | ||
| + | Fișierul binar echivalent este "gigel_in.bin". | ||
| + | <code c> | ||
| + | gigel_in.bin | ||
| + | 0300 0000 0100 0000 0200 0000 0300 0000 | ||
| + | </code> | ||
| + | <note warning> | ||
| + | * Fișierul poate fi descărcat de [[http://ocw.cs.pub.ro/courses/_media/programare/teme_2016/lab11_gigel_in.zip | aici]]. | ||
| + | * Pentru a putea vizualiza continutul mai usor, acesta poate fi deschis cu [[https://www.sublimetext.com/ | Sublime]]. Observați reprezentarea în baza 16 a numerelor. Fiecare grup de câte 2 cifre reprezintă un octet. 4 astfel de grupuri formează un int: "0300 0000" semnifică numărul 3. | ||
| + | * Atenție la [[https://en.wikipedia.org/wiki/Endianness || Endianness]]! | ||
| + | </note> | ||
| + | Următoarea sursă C citește vectorul din fișierul "gigel_in.bin", dublează elementele și apoi scrie rezultatul în "gigel_out.bin". | ||
| <code c> | <code c> | ||
| #include <stdio.h> | #include <stdio.h> | ||
| - | #include <stdlib.h> | + | #define NMAX 100 |
| - | int main(void) | + | int main() { |
| - | { | + | // numele fisierului de intrare |
| - | int n, i, j; | + | char input_filename[] = "gigel_in.bin"; |
| - | int **mat; // Adresa matrice | + | FILE *in; |
| - | + | ||
| - | // Citire dimensiuni matrice | + | |
| - | printf("n = "); | + | |
| - | scanf("%d", &n); | + | |
| - | // Alocare memorie ptr matrice | + | // incerc sa dechid pentru citire (r) |
| - | mat = malloc(n * sizeof(int *)); | + | // fisierul in mod binar (b) |
| - | + | // observati alt mod de a scrie deschiderea && verificarea | |
| - | for (i = 0; i < n; i++) { | + | if ((in = fopen(input_filename, "rb")) == NULL) { |
| - | mat[i] = calloc(n, sizeof(int)); | + | fprintf(stderr, "Can't open %s", input_filename); |
| - | } | + | return -1; |
| - | + | ||
| - | // Completare matrice | + | |
| - | for (i = 0; i < n; i++) { | + | |
| - | for (j = 0; j < n; j++) { | + | |
| - | mat[i][j] = n * i + j + 1; | + | |
| } | } | ||
| - | } | ||
| - | // Afisare matrice | + | // numarul de elemente, vectorul, variabila de iterare |
| - | for (i = 0; i < n; i++) { | + | int n, v[NMAX], i; |
| - | for (j = 0;j < n; j++) { | + | |
| - | printf("%6d", mat[i][j]); | + | |
| - | } | + | |
| - | printf("\n"); | + | // doresc sa citesc tinand cont de: |
| - | } | + | // voi stoca in n (deci specific adresa lui n ca la scanf => &n) |
| + | // valoarea citita are dimensiunea unui int => sizeof(int) | ||
| + | // citesc o singura variabila => 1 | ||
| + | // voi citi din fisierul asociat cu variabila in | ||
| + | fread(&n, sizeof(int), 1, in); | ||
| - | return 0; | + | // fread permite citirea unui vector printr-un singur apel |
| - | } | + | // doresc sa citesc tinand cont de: |
| - | </code> | + | // voi stoca in v (care este adresa primului element) |
| - | Puteti testa codul [[http://tpcg.io/Z5DeQC|aici]]. | + | // o valoare citita are dimensiunea unui int => sizeof(int) |
| + | // citesc n valori | ||
| + | // voi citi din fisierul asociat cu variabila in | ||
| + | fread(v, sizeof(int), n, in); | ||
| - | **''Exemplul 5''**: Vector de pointeri la şiruri alocate dinamic | + | // pot inchide fisierul |
| - | <code c> | + | fclose(in); |
| - | /* Creare/afisare vector de pointeri la siruri */ | + | |
| - | #include <stdio.h> | + | // dublez elementele din vector |
| - | #include <stdlib.h> | + | for (i = 0; i < n; ++i) { |
| - | #include <string.h> | + | v[i] <<= 1; |
| + | } | ||
| - | // Afisare siruri reunite in vector de pointeri | + | // salvez rezultatul in fisierul out |
| - | void printstr(char *vp[], int n) | + | // numele fisierului de intrare |
| - | { | + | char output_filename[] = "gigel_out.bin"; |
| - | int i; | + | FILE *out; |
| - | for (i = 0; i < n; i++) { | + | // incerc sa dechid pentru scriere (w) |
| - | printf("%s\n", vp[i]); | + | // fisierul in mod binar (b) |
| - | } | + | // observati alt mod de a scrie deschiderea && verificarea |
| - | } | + | if ((out = fopen(output_filename, "wb")) == NULL) { |
| - | + | fprintf(stderr, "Can't open %s", output_filename); | |
| - | // Ordonare vector de pointeri la siruri | + | return -1; |
| - | void sort(char *vp[], int n) | + | |
| - | { | + | |
| - | int i, j; | + | |
| - | char *tmp; | + | |
| - | + | ||
| - | for (j = 1; j < n; j++) { | + | |
| - | for (i = 0; i < n - 1; i++) { | + | |
| - | if (strcmp(vp[i], vp[i+1]) > 0) { | + | |
| - | tmp = vp[i]; | + | |
| - | vp[i] = vp[i+1]; | + | |
| - | vp[i+1] = tmp; | + | |
| - | } | + | |
| } | } | ||
| - | } | ||
| - | } | ||
| - | // Citire siruri si creare vector de pointeri | + | // doresc sa scriu tinand cont de: |
| - | int readstr (char * vp[]) | + | // voi scrie valoare de la adresa lui n (&n) |
| - | { | + | // valoarea scrisa are dimensiunea unui int => sizeof(int) |
| - | int n = 0; | + | // scriu o singura variabila => 1 |
| - | char *p, sir[80]; | + | // voi scrie in fisierul asociat cu variabila out |
| + | fwrite(&n, sizeof(int), 1, out); | ||
| - | while (scanf("%s", sir) == 1) { | ||
| - | p = malloc(strlen(sir) + 1); | ||
| - | strcpy(p, sir); | ||
| - | vp[n] = p; | ||
| - | ++n; | ||
| - | } | ||
| - | return n; | + | // fwrite permite scrierea unui vector printr-un singur apel |
| - | } | + | // doresc sa scriu tinand cont de: |
| + | // voi scrie elemente incepand de la adresa indicata de v | ||
| + | // o valoare scrisa are dimensiunea unui int => sizeof(int) | ||
| + | // scriu n valori | ||
| + | // voi scrie in fisierul asociat cu variabila out | ||
| + | fwrite(v, sizeof(int), n, out); | ||
| - | int main(void) | + | // inchid fisierul |
| - | { | + | fclose(out); |
| - | int n; | + | |
| - | char *vp[1000]; // vector de pointeri, cu dimensiune fixa | + | |
| - | n = readstr(vp); // citire siruri si creare vector | + | return 0; |
| - | sort(vp, n); // ordonare vector | + | |
| - | printstr(vp, n); // afisare siruri | + | |
| - | + | ||
| - | return 0; | + | |
| } | } | ||
| </code> | </code> | ||
| - | Puteti testa codul [[http://tpcg.io/2nn7vJ|aici]]. | + | Rezultatul îl putem vizualiza tot cu Sublime. |
| + | <code c> | ||
| + | gigel_out.bin | ||
| + | 0300 0000 0200 0000 0400 0000 0600 0000 | ||
| + | </code> | ||
| + | <note warning> | ||
| + | * În acest exemplu am pus extensia "bin" pentru a sugera faptul că fișierele sunt binare. Acest lucru este irelevant pentru funcțiile fread și fwrite. | ||
| + | </note> | ||
| - | ==== Practici recomandate ==== | + | === Citire/scriere la nivel de caracter === |
| - | Deşi au fost enunţate în momentul în care au fost introduse noţiunile corespunzătoare în cursul acestui material, se pot rezuma câteva reguli importante de folosire a variabilelor de tip pointer: | + | <code c> |
| - | * Aveţi grijă ca variabilele de tip pointer să indice către adrese de memorie valide înainte de a fi folosite; consecinţele adresării unei zone de memorie aleatoare sau nevalide (''NULL'') pot fi dintre cele mai imprevizibile. | + | int fgetc(FILE *fp); // întoarce următorul caracter din fişier, EOF la sfârşit de fişier |
| - | * Utilizaţi o formatare a codului care să sugereze asocierea operatorului ''*'' cu variabila asupra căreia operează; acest lucru este în special valabil pentru declaraţiile de pointeri. | + | </code> |
| - | * Nu returnaţi pointeri la variabile sau tablouri definite în cadrul funcţiilor, întrucât valabilitatea acestora încetează odată cu ieşirea din corpul funcţiei. | + | |
| - | * Verificaţi rezultatul funcţiilor de alocare a memoriei, chiar dacă dimensiunea pe care doriţi s-o rezervaţi este mică. Atunci când memoria nu poate fi alocată rezultatul este ''NULL'' iar programul vostru ar trebui să trateze explicit acest caz (finalizat, de obicei, prin închiderea "curată" a aplicaţiei). | + | |
| - | * Nu uitaţi să eliberaţi memoria alocată dinamic, folosind funcţia ''free()''. Memoria rămasă neeliberată încetineşte performanţele sistemului şi poate conduce la erori (bug-uri) greu de depistat. | + | |
| - | ===== Studiu de caz ===== | + | <code c> |
| + | char* fgets(char *s, int n, FILE *fp); // întoarce următoarele n caractere de la pointer sau pâna la sfârşitul de linie | ||
| + | </code> | ||
| - | <spoiler Clase de stocare> | + | <code c> |
| - | <note>Această secţiune este opţională şi nu este necesară pentru rezolvarea exerciţiilor de laborator, însă ajută la înţelegerea aprofundată a modului în care limbajul C lucrează cu variabilele.</note> | + | int fputc(int c, FILE *fp); //pune caracterul c in fişier |
| - | + | </code> | |
| - | Clasa de stocare (memorare) arată când, cum şi unde se alocă memorie pentru o variabilă (vector). Orice variabilă C are o clasă de memorare care rezultă fie dintr-o declaraţie explicită, fie implicit din locul unde este definită variabila. Există trei moduri de alocare a memoriei, dar numai două corespund unor clase de memorare: | + | |
| - | * ''Static'': memoria este alocată la compilare în segmentul de date din cadrul programului şi nu se mai poate modifica în cursul execuţiei. Variabilele externe, definite în afara funcţiilor, sunt implicit statice, dar pot fi declarate static şi variabile locale, definite în cadrul funcţiilor. | + | |
| - | * ''Automat'': memoria este alocată automat, la activarea unei funcţii, în zona stivă alocată unui program şi este eliberată automat la terminarea funcţiei. Variabilele locale unui bloc (unei funcţii) şi argumentele formale sunt implicit din clasa ''auto''. | + | |
| - | * ''Dinamic'': memoria se alocă la execuţie în zona ''heap'' alocată programului, dar numai la cererea explicită a programatorului, prin apelarea unor funcţii de bibliotecă (''malloc'', ''calloc'', ''realloc''). Memoria este eliberată numai la cerere, prin apelarea funcţiei ''free''. Variabilele dinamice nu au nume şi deci nu se pune problema clasei de memorare (atribut al variabilelor cu nume). | + | |
| - | + | ||
| - | Variabilele statice pot fi iniţializate numai cu valori constante (pentru că se face la compilare), dar variabilele auto pot fi iniţializate cu rezultatul unor expresii (pentru că se face la execuţie). Toate variabilele externe (şi statice) sunt automat iniţializate cu valori zero (inclusiv vectorii). Cantitatea de memorie alocată pentru variabilele cu nume rezultă automat din tipul variabilei şi din dimensiunea declarată pentru vectori. Memoria alocată dinamic este specificată explicit ca parametru al funcţiilor de alocare. | + | |
| - | O a treia clasă de memorare este clasa ''register'' pentru variabile cărora, teoretic, li se alocă registre ale procesorului şi nu locaţii de memorie, pentru un timp de acces mai bun. În practică nici un compilator modern nu mai ţine cont de acest cuvânt cheie, folosind automat registre atunci când codul poate fi optimizat în acest fel (de exemplu când observă că nu se accesează niciodata adresa variabilei în program). | + | <code c> |
| - | + | int ungetc(int c, FILE *fp); // pune c în bufferul asociat lui fp (c va fi următorul caracter citit din fp) | |
| - | Memoria neocupată de datele statice şi de instrucţiunile unui program este împărţită între stivă şi heap. Consumul de memorie pe stivă este mai mare în programele cu funcţii recursive şi număr mare de apeluri recursive, iar consumul de memorie heap este mare în programele cu vectori şi matrice alocate (şi realocate) dinamic. | + | </code> |
| - | </spoiler> | + | |
| - | ===== Exercitii laborator CB/CD ===== | + | |
| - | Codul sursa se gaseste [[http://swarm.cs.pub.ro/~gmuraru/PC/ex.c|aici]] | + | |
| - | + | ||
| - | Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul citeste o matrice si afiseaza suma elementelor de pe fiecare linie. | + | |
| - | * Nu uitati ca trebuie sa utilizam un coding style adecvat atunci cand scriem sursele. | + | |
| - | + | ||
| - | Cerinte: | + | |
| - | *Sa se mute elementele de pe o anumita linie, intr-un vector, alocat dinamic: | + | |
| - | *Sa se mareasca dimensiunea matricei astfel incat sa aiba o linie in plus, iar pointerul specific ultimei linii sa indice spre vectorul generat anterior. | + | |
| - | + | ||
| - | + | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | + | |
| - | [[https://drive.google.com/drive/folders/1qB6EZLGVubKbuTXMtMue06egH_8fo25M|Checker laborator 9]] | ||
| - | <hidden> | + | ==== Exerciții ==== |
| - | Link direct către lista completă de probleme: [[https://docs.google.com/document/d/15IBKbSiFYEJ_9zeuMksWYnFt2WkKgC1EaigCpyFAi6E/edit|aici]] | + | Exercițiile pentru laborator se găsesc pe [[https://acs-pclp.github.io/laboratoare/09 | PCLP Laborator09: Operaţii cu fişiere. Aplicaţii folosind fişiere]]. |
| - | </hidden> | + | |
| - | ===== Exerciţii de Laborator ===== | + | |
| - | - **[2p]** Să se scrie un program care citeşte de la tastatură un număr pozitiv n împreună cu alt număr pozitiv max. Programul va aloca apoi dinamic un vector de întregi de n elemente, pe care îl va iniţializa cu numere aleatoare în intervalul [0..max-1]. Sortaţi vectorul, folosind metoda preferată, afişându-i conţinutul atât înainte, cât şi după ce sortarea a avut loc. | ||
| - | - **[3p]** Să se scrie un program care citeşte de la tastatură două matrice: una inferior triunghiulară (toate elementele de deasupra diagonalei principale sunt nule), şi cealaltă superior triunghiulară. Ele vor fi reprezentate în memorie cât mai compact cu putinţă (fară a stoca şi zerourile de deasupra, respectiv dedesubtul diagonalei). Se va calcula apoi produsul celor matrice, şi se va afişa. | ||
| - | - Un număr lung (cu o valoare mult mai mare decât maximul reprezentabil pe un tip de date întreg standard din C), poate fi reprezentat ca un vector char *v de cifre (considerate valori de tip char), în felul următor: | ||
| - | * v[0] reprezintă numărul de cifre ale numărului lung. Lungimea vectorului în memorie va fi v[0]+1. | ||
| - | * v[i], unde i este de la 1 la v[0], reprezintă a i-a cifră a numărului, în ordinea crescătoare a semnificativităţii. Astfel v[1] reprezintă cifra unităţilor, v[2] cifra zecilor, etc. O reprezentare eficientă va avea întotdeauna ultima cifră v[v[0]] nenulă (altfel numărul de cifre ar fi putut fi mai mic şi reprezentarea mai compactă). | ||
| - | * **[1p]** a) Scrieţi o funcţie care construieşte vectorul de cifre asociat unui număr întreg simplu (de tipul int):<code>char *build_number(int value);</code> | ||
| - | * **[2p]** b) Scrieţi o funcţie care adună două numere lungi şi întoarce ca rezultat un alt număr lung:<code>char *add_numbers(char *a, char *b);</code> | ||
| - | * **[2p]** c) Scrieţi un program care calculează şirul Fibonacci folosind numere lungi. Se cer primii 100 de termeni ai şirului, afişaţi pe câte o linie în parte. | ||
| - | <note>Toate funcţiile cerute vor aloca dinamic memoria necesară reprezentării vectorului întors. Numerele nefolosite vor trebui eliberate, pentru a evita consumarea memoriei. Trataţi tipul de date char ca pe un tip numeric (deci lucraţi cu vectori de numere, nu cu şiruri de caractere ASCII).</note> | ||
| - | ==== Bonus ==== | + | ==== Referinţe ==== |
| - | - Considerând structura unui număr lung prezentată la punctul precedent, să se rezolve următoarele: | + | * [[http://crasseux.com/books/ctutorial/Input-and-output.html#Input%20and%20output|The GNU Programming Tools - Input and Output]] |
| - | * **[1p]** a) Scrieţi o funcţie care înmulţeşte două numere lungi şi întoarce ca rezultat un alt număr lung:<code>char *multiply_numbers(char *a, char *b);</code> | + | |
| - | * **[1p]** b) Scrieţi un program care calculează factorialul numerelor de la 1 la 50, afişând câte un număr pe fiecare linie. | + | |
| - | - **[2p]** Se consideră un paralelipiped tridimensional cu dimensiunile citite de la tastatură, pentru care va trebui să alocaţi memorie. De asemenea, se citeşte apoi un număr pozitiv N, ce reprezintă un număr de bombe care vor fi plasate în paralelipiped. Apoi se citesc N triplete ce reprezintă coordonatele bombelor. Valorile citite vor trebui validate astfel încât să nu depăşească dimensiunile paralelipipedului. Pentru fiecare cub liber se va calcula numărul de bombe din cei maxim 26 de vecini ai săi, şi aceste numere vor fi afişate pe ecran, alături de coordonatele corespunzătoare. La sfârşitul execuţiei programului, memoria alocată va trebui eliberată. | + | |
| - | <hidden> | ||
| - | [[https://drive.google.com/file/d/1D5BNlnEtLwYfxenbMjaNwEJEEtnmUGKg/view?usp=sharing|Probleme laborator 14 - 16]] | ||
| - | </hidden> | ||