LABORATORUL 12

Operații de intrare/ieșire. Fișiere.

Durată: 2 ore Bibliotecă: stdio.h Precedent: Laboratorul 11 PDF îndrumar EN English version

Datele din memorie dispar la terminarea programului. Fișierele sunt structuri de date organizate pe suporturi externe, care permit păstrarea informației între execuții și schimbul de date între aplicații.

1Obiectivele lucrării

inchisdeschisgood() == trueeof()fail()opensfarsit de fisiereroare de formatclose - abia acum datele ajung sigur pe disc
Fig. - Starile unui flux de fisier. Orice operatie ulterioara unei erori este ignorata pana la clear(), iar datele scrise sunt garantate pe disc abia dupa inchidere.
  • Clasificarea fișierelor după conținut, acces și tipul operațiilor permise
  • Deschiderea și închiderea corectă a unui fișier, cu verificarea reușitei
  • Alegerea modului potrivit de deschidere
  • Citirea și scrierea în fișiere text și binare
  • Poziționarea în fișier pentru acces direct

2Clasificarea fișierelor

CriteriuCategoriiDescriere
După conținutfișiere textcaractere organizate pe linii; sfârșitul liniei marcat prin <CR><LF>
fișiere cu tipînregistrări de un anumit tip, elementar sau structurat
fișiere fără tipfișiere binare, secvențe brute de octeți
După accesacces secvențialinformația se prelucrează doar în ordinea înregistrării
acces directcomponentele pot fi prelucrate în orice ordine, după poziționare
După operațiide intraredoar citire
de ieșiredoar scriere
de intrare/ieșireambele - posibilă actualizarea informației
Marcajul de sfârșitSfârșitul unui fișier este semnalat printr-un marcaj special numit EOF (end of file). Funcțiile de citire îl returnează pentru a indica faptul că nu mai există date.

3Deschidere și închidere

Orice transfer este precedat de operația de deschidere, care creează un stream și îl asociază fișierului. Se folosește o variabilă de tip FILE * și se alocă automat o zonă tampon prin care se fac transferurile.

tiparul corect
FILE *f = fopen("date.txt", "r");      // 1. deschidere

if (f == NULL) {                        // 2. VERIFICARE obligatorie
    printf("Nu pot deschide fisierul!\n");
    return 1;
}

/* ... citire sau scriere ... */        // 3. prelucrare

fclose(f);                              // 4. inchidere
De ce este obligatorie închidereaDatele scrise stau într-o zonă tampon și ajung pe disc abia când aceasta se umple sau la fclose. Dacă programul se termină fără închidere, ultimele date scrise se pot pierde. În plus, numărul de fișiere deschise simultan este limitat.
FuncțieRol
fopen(nume, mod)deschide fișierul; returnează NULL la eșec
fclose(f)golește tamponul și închide fișierul
feof(f)testează dacă s-a atins sfârșitul fișierului
ferror(f)testează dacă a apărut o eroare
fflush(f)forțează scrierea tamponului pe disc
remove(nume)șterge fișierul
rename(vechi, nou)redenumește fișierul

4Modurile de deschidere

ModSemnificațieDacă fișierul existăDacă nu există
"r"citirese deschide de la începuteroare (NULL)
"w"scrierese șterge tot conținutul!se creează
"a"adăugarese scrie la sfârșitse creează
"r+"citire și scrierese deschide de la începuteroare
"w+"citire și scrierese șterge tot conținutulse creează
"a+"citire și adăugarescrierea se face la sfârșitse creează

Se adaugă litera b pentru fișiere binare ("rb", "wb", "ab") și t pentru text, acesta fiind modul implicit.

Cel mai periculos mod este "w"Deschiderea unui fișier existent cu "w" îi șterge instantaneu tot conținutul, chiar dacă nu se scrie nimic după aceea. Pentru adăugare la un fișier existent folosiți "a".

5Operații cu fișiere text

NivelCitireScriere
Caracterfgetc(f), getc(f)fputc(c, f), putc(c, f)
Linie / șirfgets(s, n, f)fputs(s, f)
Cu formatfscanf(f, "...", ...)fprintf(f, "...", ...)
Corespondența cu funcțiile de consolăFiecare funcție de fișier are un echivalent pentru consolă: fprintf ↔ printf, fscanf ↔ scanf, fgets ↔ gets. Diferența este primul parametru, care indică streamul. printf(...) este echivalent cu fprintf(stdout, ...).
Atenție la feofFuncția feof devine adevărată doar după o citire eșuată, nu înainte. Bucla while (!feof(f)) { fscanf(...); ... } prelucrează ultima înregistrare de două ori. Corect: while (fscanf(f, "%d", &x) == 1) { ... }

6Operații cu fișiere binare

prototipuri
size_t fread (void *p, size_t dim, size_t nr, FILE *f);
size_t fwrite(const void *p, size_t dim, size_t nr, FILE *f);

// p   - adresa zonei din memorie
// dim - dimensiunea unei inregistrari, in octeti
// nr  - cate inregistrari se transfera
// f   - streamul
// returneaza numarul de inregistrari transferate efectiv
AspectFișier textFișier binar
Conținutcaractere lizibilecopie exactă a reprezentării din memorie
Numărul 123455 caractere (5 octeți)4 octeți (un int)
Editare manualăposibilă în orice editorpractic imposibilă
Vitezămai lentă (conversii)rapidă, fără conversii
Portabilitatebunălimitată (endianness, padding)

7Acces direct - poziționarea în fișier

FuncțieRol
fseek(f, offset, origine)mută indicatorul de poziție
ftell(f)returnează poziția curentă, în octeți
rewind(f)revine la începutul fișierului
Constantă pentru originePunct de referință
SEEK_SETînceputul fișierului
SEEK_CURpoziția curentă
SEEK_ENDsfârșitul fișierului
Citirea înregistrării cu numărul n fseek(f, n * sizeof(Inregistrare), SEEK_SET); urmat de fread(...). Acesta este avantajul major al fișierelor binare cu înregistrări de dimensiune fixă: orice element se accesează direct, fără a parcurge tot fișierul.

8Cod sursă

text_scriere.c - creare și adăugare
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("date.txt", "w");     // ATENTIE: sterge continutul existent
    if (f == NULL) {
        printf("Nu pot crea fisierul!\n");
        return 1;
    }

    fprintf(f, "Popescu Ion;20;9.15\n");
    fprintf(f, "Ionescu Maria;21;8.70\n");
    fprintf(f, "Georgescu Radu;20;9.60\n");

    fclose(f);
    printf("Fisier creat.\n");

    // adaugare la sfarsit, fara stergerea continutului
    f = fopen("date.txt", "a");
    if (f) {
        fprintf(f, "Dumitrescu Ana;22;9.85\n");
        fclose(f);
        printf("Inregistrare adaugata.\n");
    }
    return 0;
}
text_citire.c - parcurgere corectă
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("date.txt", "r");
    if (f == NULL) {
        printf("Fisierul nu exista!\n");
        return 1;
    }

    char linie[200];
    int nrLinii = 0;

    // varianta corecta: conditia este chiar reusita citirii
    while (fgets(linie, sizeof(linie), f) != NULL) {
        nrLinii++;
        printf("%3d: %s", nrLinii, linie);
    }

    printf("\nTotal %d linii\n", nrLinii);
    fclose(f);

    // citire cu format, camp cu camp
    f = fopen("date.txt", "r");
    char nume[50];
    int varsta;
    float medie;

    printf("\nCitire structurata:\n");
    while (fscanf(f, "%49[^;];%d;%f\n", nume, &varsta, &medie) == 3) {
        printf("  %-20s %3d ani  media %.2f\n", nume, varsta, medie);
    }
    fclose(f);

    return 0;
}
binar.c - scrierea și citirea structurilor
#include <stdio.h>

typedef struct {
    char  nume[50];
    int   varsta;
    float medie;
} Student;

int main(void)
{
    Student grupa[3] = {
        {"Popescu Ion",    20, 9.15f},
        {"Ionescu Maria",  21, 8.70f},
        {"Georgescu Radu", 20, 9.60f}
    };

    // scriere binara: tot tabloul, dintr-un singur apel
    FILE *f = fopen("studenti.dat", "wb");
    if (!f) return 1;
    fwrite(grupa, sizeof(Student), 3, f);
    fclose(f);

    // citirea inregistrarii cu numarul 1 (a doua), FARA a citi prima
    f = fopen("studenti.dat", "rb");
    if (!f) return 1;

    Student unul;
    fseek(f, 1 * sizeof(Student), SEEK_SET);   // pozitionare directa
    fread(&unul, sizeof(Student), 1, f);

    printf("Inregistrarea 1: %s, %d ani, media %.2f\n",
           unul.nume, unul.varsta, unul.medie);

    // dimensiunea fisierului
    fseek(f, 0, SEEK_END);
    long dim = ftell(f);
    printf("Dimensiune fisier: %ld octeti = %ld inregistrari\n",
           dim, dim / (long)sizeof(Student));

    fclose(f);
    return 0;
}
copiere.c - copierea unui fișier, octet cu octet
#include <stdio.h>

int main(void)
{
    FILE *sursa = fopen("date.txt", "rb");
    if (!sursa) { printf("Sursa lipseste\n"); return 1; }

    FILE *dest = fopen("copie.txt", "wb");
    if (!dest) { fclose(sursa); return 1; }

    char tampon[1024];
    size_t citite;
    long total = 0;

    // se copiaza in blocuri, mult mai rapid decat octet cu octet
    while ((citite = fread(tampon, 1, sizeof(tampon), sursa)) > 0) {
        fwrite(tampon, 1, citite, dest);
        total += citite;
    }

    printf("Copiati %ld octeti\n", total);

    fclose(sursa);
    fclose(dest);
    return 0;
}

9Atelier de cod

Casetele de mai jos au un sistem de fișiere propriu, care există doar în pagină. Fișierele scrise de un program pot fi citite imediat după aceea de același program.

Scriere și citire dintr-un fișier text
#include <stdio.h>

int main(void)
{
    FILE *f;
    int   nota;
    char  nume[20];

    /* --- scriere --- */
    f = fopen("note.txt", "w");
    if (f == NULL) { printf("Nu pot deschide fisierul\n"); return 1; }

    fprintf(f, "Popescu 9\n");
    fprintf(f, "Ionescu 7\n");
    fprintf(f, "Vasilescu 10\n");
    fclose(f);

    /* --- citire --- */
    f = fopen("note.txt", "r");
    if (f == NULL) { printf("Fisierul nu exista\n"); return 1; }

    printf("Continutul fisierului:\n");
    while (fscanf(f, "%s %d", nume, &nota) == 2)
        printf("  %-12s %2d\n", nume, nota);
    fclose(f);
    return 0;
}
De încercatSchimbați modul din "w" în "a" (adăugare) și rulați de două ori: veți vedea că liniile se adaugă la sfârșit în loc să înlocuiască fișierul.
Exercițiu - citire linie cu linie
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("poezie.txt", "r");
    char linie[100];
    int  nr = 0;

    if (f == NULL) { printf("Fisierul nu exista\n"); return 1; }

    /* Cititi fisierul linie cu linie cu fgets si afisati fiecare
       linie precedata de numarul ei, in formatul din dreapta.
       La final afisati cate linii are fisierul.                  */

    fclose(f);
    return 0;
}
De încercatFișierul poezie.txt există deja în sistemul de fișiere al acestei casete.

10Sarcini de lucru

  • Creați un fișier text cu date despre studenți și citiți-l apoi linie cu linie.
  • Deschideți un fișier existent cu modul "w" și observați ce se întâmplă cu conținutul.
  • Adăugați înregistrări noi folosind modul "a", fără a pierde datele vechi.
  • Numărați liniile, cuvintele și caracterele dintr-un fișier text.
  • Scrieți un tablou de structuri într-un fișier binar și citiți doar a treia înregistrare.
  • Comparați dimensiunea aceleiași informații salvate în format text și în format binar.
  • Implementați copierea unui fișier folosind un tampon de 1024 de octeți.
  • Testați diferența dintre while (!feof(f)) și while (fscanf(...) == n).

11Aplicație de aprofundare

ExtindereImplementați o agendă telefonică persistentă, cu fișier binar de înregistrări de dimensiune fixă: adăugare, căutare după nume, modificarea unei înregistrări existente (prin fseek și rescriere pe loc, fără a recrea fișierul) și ștergere logică printr-un câmp indicator. Adăugați o funcție de „compactare" care elimină fizic înregistrările marcate ca șterse.

12Întrebări de verificare

13Resurse