start     Articole     Despre mine     Contact     Cursul ABCprog    

Cum citesti si cum scrii fisiere folosind limbajul C

Toate informatiile stocate in calculatorul tau se gasesc acolo (pe hard-disk, mai exact) sub forma de fisiere. Poze, texte, documente, filme, tabele — toate sunt salvate (sub forma de biti (grupati in octeti)) in fisiere.

(Iar fisierele sunt organizate intr-o structura arborescenta (ierarhica) de dosare. (Adica in orice dosar se pot gasi alte dosare (la fel cum aici am pus paranteze in interiorul altor paranteze).))

Orice fisier contine in el octeti (sau “bytes”, formati fiecare din cate 8 biti) cu semnificatii ce depind de tipul datelor stocate in acel fisier.

In realitate, insa, fisierul contine in el doar numere. E treaba aplicatiei care il deschide sa interpreteze corect acele numere (si sa produca, dupa caz: o poza, un video, un text, etc.).

Mod binar vs. mod text

Limbajul C ne permite atat sa deschidem fisiere existente si sa citim din ele fiecare octet, cat si sa scriem in fisiere octetii doriti.

Acest mod de lucru cu fisierele poarta numele de “mod binar”.

Putem, insa, sa lucram in C cu fisierele si in mod text.

In modul text, fisierul e vazut ca fiind format din octeti a caror semnificatie e de caractere (simboluri) din codul ASCII.

Pe langa caracterele afisabile obisnuite (gen litere, cifre si semne de punctuatie), fisierele text mai pot contine simboluri speciale, cum ar fi ‘\t’ (pe care editoarele de text il intepreteaza ca spatiu lung (de obicei, 4 spatii)) sau ‘\n’ (pe care editoarele de text il interpreteaza ca sfarsit de linie).

(Pe Windows sfarsitul de linie e format din doua caractere — ‘\r’ (carriage return; C — codul ASCII 13) si ‘\n’ (line feed; LF — codul ASCII 10). Dar la citirea sfarsitului de linie dintr-un astfel de fisier se va returna doar ‘\n’.)

Deschiderea fisierelor

In fisierul antet “stdio.h” avem declarate (pe langa functiile printf si scanf, cu care ai tot lucrat) si structura de date FILE si functiile necesare deschiderii, inchiderii, scrierii si citirii din fisiere.

Pentru a deschide un fisier, folosim functia fopen:

FILE *pf = fopen("calea catre fisier", "mod de deschidere");
// => Daca pf e diferit de NULL, inseamna ca fisierul a putut fi deschis.

Calea catre fisier poate fi o cale completa (de genul “/home/user/test/fisier.txt” (sau “C:\\temp\\fisier.txt”, pe Windows)), sau doar numele fisierului — caz in care se considera ca el e in dosarul curent (dosar ce depinde de contextul din care este rulat programul rezultat).

Iar modul de deschidere e un sir de caractere ce ne spune daca fisierul va fi deschis pentru citire sau pentru scriere si daca va fi descchis in mod binar sau in mod text.

Pentru modul text avem asa:

  • “r” sau “rt” — citire
  • “w” sau “wt” — scriere (daca nu exista => creare)
  • “a” sau “at” — adaugare la final (daca nu exista => creare)
  • “r+” sau “r+t” — citire si scriere
  • “w+” sau “w+t” — rescriere si citire (daca nu exista => creare)
  • “a+” sau “a+t” — adaugare, la final, si citire, de la inceput (daca nu exista => creare)

Iar pentru lucrul cu fisierul in mod binar avem variante similare: “rb”, “wb”, “ab”, “r+b”, “w+b”, “a+b”.

Citirea fisierelor

Pentru a citi un octet dintr-un fisier se poate folosi functia fgetc:

// fisierul
FILE *pf = fopen(...);
...
int c = fgetc(pf);
// Daca eroare => EOF (end of file)

De asemenea, citiri din fisier se pot face cu:

// fisierul
FILE *pf = fopen(...);
// vectorul in care se va stoca sirul de caractere (inclusiv '\0'-ul final)
char str[100];
...
// Citeste maxim 99 de caractere din fisierul pf
fgets(str, 100, pf);
// Sau: Citeste maxim 99 de caractere, 
//  sarind peste eventualele spatii de la inceput
fscanf(pf, "%99s", str);

Se poate verifica daca s-a ajuns la finalul fisierului cu ajutorul functiei feof:

if (feof(pf)) {
	printf("Am ajuns la finalul fisierului.");
}

Scrierea fisierelor

Pentru scrierea fisierelor exista functii similare celor pentru citire:

// fisierul
FILE *pf = fopen(...);
// string-ul
char str[...];
...
fputs(str, pf);
fprintf(pf, "%s", str);

Dar se poate scrie si cate un singur caracter, folosind functia fputc:

// fisierul
FILE *pf = fopen(...);
// caracterul
char caracter;
...
fputc(caracter, pf);

Pentru a efectua citiri si scrieri de date binare reprezentate pe mai multi octeti (cum ar fi vectori sau structuri) se pot folosi urmatoarele doua functii:

  • fread(ptr_data, dim_elem, nr_elem, pf);
  • fwrite(ptr_data, dim_elem, nr_elem, pf);

Cred ca se intelege din modul cum le-am denumit ca ai nevoie de o zona de memorie de nr_elem*dim_elem octeti, zona a carei adresa o memorezi in pointerul ptr_data.

Inchiderea fisierelor

Dupa deschidere si operarea cu el, fisierul trebuie inchis:

fclose(pf);

In felul asta se previne pierderea de date, caci unele operatii efectuate asupra fisierului nu sunt neaparat stocate imediat pe hard-disk, ci intr-o zona de memorie alocata automat de sistem pentru operarea mai rapida asupra fisierului respectiv.

Se poate forta scrierea datelor in fisier fara inchiderea acestuia cu fflush(pf).

Pe langa cele discutate, o alta functie utila in lucrul cu fisierele este fseek — care poate pozitiona “cursorul” (virtual) de citire/scriere oriunde in interiorul fisierului.
Iar functia ftell ne returneaza pozitia curenta a acestui cursor in cadrul fisierului.

Exemple

Da, informatiile sunt foarte multe si aparent imposibil de retinut (ceea ce nici nu iti recomand sa faci, caci poti oricand apela la documentatie) — asa ca e cazul sa fixam lucrurile cu ajutorul a 4 exemple.

  • In primul rand, hai sa vedem cum ai putea accesa continutul unui fisier octet cu octet:
#include <stdio.h>

void main() {
    FILE *pf = fopen("poza.jpg", "rb");
    if (pf == NULL) {
        printf("Eroare: Fisierul de intrare nu a putut fi deschis!\n");
        return; // Ies din program.
    }
    FILE *pfout = fopen("pozaout.jpg", "wb");
    if (!pfout) {
        printf("Eroare: Fisierul de iesire nu a putut fi deschis!\n");
        return; // Ies din program.
    }
    char c = fgetc(pf);
    while (!feof(pf)) {
        fputc(c, pfout);
        // il si afisez (in hexa), de fun :-)
        printf("%02X ", (unsigned char)c);
        c = fgetc(pf);
    }
    fclose(pf);
    fclose(pfout);
}
  • Iata si cum ai putea sa imparti un fisier cu continut oarecare in mai multe fisiere de maxim DIM (de exemplu, DIM=512KB) octeti:
#include <stdio.h>
#include <string.h>

// definesc o "constanta"
#define DIM 512*1024

void main() {
    FILE *pf = fopen("poza.jpg", "rb");
    if (pf == NULL) {
        printf("Eroare: Fisierul de intrare nu a putut fi deschis!\n");
        return; // Ies din program.
    }
    unsigned char bloc[DIM];
    FILE *pfout;
    int nr_cititi;
    char numefis[13];
    strcpy(numefis, "pozaout_.jpg");
    char nr = '0';
    while (1) {
        numefis[7] = nr;
        pfout = fopen(numefis, "wb");
        if (pfout == NULL) {
            printf("Eroare: Nu pot crea '%s'!\n", numefis);
            fclose(pf);
            return; // Ies din program.
        }
        nr_cititi = fread(bloc, sizeof(unsigned char), DIM, pf);
        if (nr_cititi != fwrite(bloc, sizeof(unsigned char), nr_cititi, pfout)) {
            printf("Eroare la scrierea lui '%s'!\n", numefis);
            fclose(pf);
            fclose(pfout);
            return; // Ies din program.
        }
        fclose(pfout);
        if (nr_cititi != DIM) {
            break; // Ies din bucla infinita.
        }
        nr++;
        if (nr-'0' > 9) {
            printf("Eroare: Fisierul are mai mult de 10 bucati!!\n");
            fclose(pf);
            return; // Ies din program.
        }
    }
    fclose(pf);
    printf("Fisierul a fost impartit in %d bucati.\n", nr-'0');
}
  • Si iata cum ai putea apoi sa il refaci la loc din acele “bucati”:
#include <stdio.h>
#include <string.h>

#define DIM 512*1024

void main() {
    int catefis;
    printf("Din cate fisiere pozaout_.jpg sa recompun pozarecompusa.jpg? ");
    scanf("%d", &catefis);
    if ( !(catefis>0 && catefis <10) ) {
        printf("Eroare: N-are cum sa fie %d fisiere!\n", catefis);
        return; // Ies din program.
    }
    FILE *pfout = fopen("pozarecompusa.jpg", "wb");
    if (pfout == NULL) {
        printf("Eroare: Fisierul de iesire nu a putut fi creat!\n");
        return; // Ies din program.
    }
    unsigned char bloc[DIM];
    FILE *pf;
    int nr_cititi;
    char numefis[13];
    strcpy(numefis, "pozaout_.jpg");
    char nr = '0';
    while (1) {
        numefis[7] = nr;
        pf = fopen(numefis, "rb");
        if (pf == NULL) {
            printf("Eroare: Nu pot citi fisierul '%s'!\n", numefis);
            fclose(pfout);
            return; // Ies din program.
        }
        nr_cititi = fread(bloc, sizeof(unsigned char), DIM, pf);
        if (nr_cititi != fwrite(bloc, sizeof(unsigned char), nr_cititi, pfout)) {
            printf("Eroare la scrierea din '%s'!\n", numefis);
            fclose(pf);
            fclose(pfout);
            return; // Ies din program.
        }
        fclose(pf);
        nr++;
        if (nr-'0' == catefis) {
            break;
        }
    }
    fclose(pfout);
    printf("Fisierul a fost recompus din cele %d bucati.\n", nr-'0');
}
  • Daca vrei sa afli dimensiunea in octeti a unui fisier, o poti face astfel:
#include <stdio.h>

void main() {
    FILE *pf = fopen("poza.jpg", "rb");
    if (pf == NULL) {
        printf("Nu gasesc fisierul in dosarul curent!\n");
        return; // Ies din program.
    }
    fseek(pf, 0, SEEK_END);
    long int dim = ftell(pf); // imi spune pozitia curenta in fisier
    printf("Fisierul are %ld octeti.\n", dim);
    // fseek(pf, 0, SEEK_SET); // <-- Daca vreau sa revin la inceput.
    fclose(pf);
}

In sfarsit, cu fscanf si fprintf poti opera cu fisierele cam la fel cum ai facut-o cu citirile si afisarile din/in terminalul de comenzi. Diferenta e ca mai apare un ‘f’ inaintea numelui functiei si ca primul parametru din ea e un pointer catre o structura FILE.

Fisierele stdin, stdout, stderr

Gata! Asta a fost articolul despre lucrul cu fisierele folosind limbajul C.

(Poti sa sari peste ce urmeaza (mai ales daca citesti asta de pe un calculator cu Windows). Sau poti citi de curiozitate.)

Sistemul de operare ne pune la dispozitie trei fisiere virtuale pe care le putem utiliza prin pointerii stdin, stdout si stderr.

“Fisierul” stdin inseamna “standard input” si in mod normal prin el avem acces sa citim ceea ce utilizatorul scrie din tastatura.

“Fisierul” stdout inseamna “standard output” si in mod normal prin el avem acces sa scriem text in terminal.

“Fisierul” stderr inseamna “standard error” si e similar cu stdout, doar ca e destinat scrierii mesajelor de eroare. In mod normal si el e “legat”, ca si stdout, de terminalul de comenzi.

De ce am zis “in mod normal”?

Pentru ca avem posibilitatea sa schimbam acest “normal” prin comenzi in terminal.

De exemplu, pot trimite stdout (adica orice afisare care era destinata consolei) catre un fisier prin simbolul >, asa:

  • echo “mesaj” >fisier.txt

Sau pot lua stdin (adica orice citire care era asteptata de la tastatura) dintr-un fisier prin simbolul <, asa:

  • (read x; echo $x) <fisier.txt

Daca incerci sa rulezi comanda asta fara partea “<fisier.txt“, vei vedea ca asteapta sa introduci de la tastatura valoarea pentru variabila x.

De asemenea, pot trimite catre un fisier orice afisare de eroare (adica orice ajunge la stderr) folosind comanda 2>, asa:

  • echo “mesaj” 2>fisier.txt

Doar ca aceasta comanda (echo “mesaj”, adica) nu genereaza nicio afisare de eroare, deci nu se va duce nimic in fisier. Poti verifica asta afisand continutul fisierului:

  • cat fisier.txt

Ca sa testezi comanda de redirectionare a mesajelor de eroare, ai putea redirectiona iesirea comenzii echo catre stderr, asa:

  • echo “mesaj” >&2

Apoi redirectionezi stderr catre fisierul fisier.txt:

  • (echo “mesaj” >&2) 2>fisier.txt

E foarte smecher terminalul de comenzi din Linux, nu-i asa?
Din fericire, il poti folosi si din Windows — vezi aici cum.

 

Alatura-te celor peste 8000 de oameni din armata noastra de creiere cu muschi si vei primi testul care iti va spune daca ai sau nu minte de programator.

In plus, vei fi mereu la curent cu tot ce pun la cale.

(Vei primi automat un email in care ti se va solicita acordul de prelucrare a datelor cu caracter personal.)

 

Cu drag,

Florin Bîrleanu





Loading Facebook Comments ...