Table of Contents

PCLP Suport Teoretic pentru Laborator: Operaţii cu fişiere. Aplicaţii folosind fişiere

Obiective

În urma parcurgerii acestui material studentul va fi capabil să:

Noţiuni teoretice

Introducere

Un fişier este o structură dinamică, situată în memoria secundară (pe disk-uri). Limbajul C permite operarea cu fişiere:

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:

Pentru a prelucra un fişier, trebuie parcurse următoarele etape:

Funcții

Mai jos se prezintă restul funcţiilor de prelucrare a fişierelor. Pentru documentația oficială puteți citi aici.

fopen
FILE *fopen(const char *filename, const char *mod);

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.

Modul de deschidere poate fi:

informaţiei deja existente în fişier, deci pointerul de acces se plasează la sfârşitul fişierului )

fclose
int fclose(FILE *pFile);

închide fişierul asociat cu variabila pFile şi eliberează zona tampon; returnează 0 la succes, EOF (end of file) la eroare

fseek
int fseek(FILE *pFile, long offset, int whence);

repoziţionează pointerul asociat fişierului pFile; offset - numărul de octeţi între poziţia dată de whence şi noua poziţie.

whence - are una din cele trei valori posibile:

ftell
long ftell(FILE *pFile);

întoarce poziţia curentă în cadrul fișierului asociat cu pFile.

fgetpos
int fgetpos(FILE *pFile, fpos_t *ptr);

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).

fsetpos
int fsetpos(FILE *pFile, const fpos_t *ptr);

această funcţie setează poziţia curentă în fişierul asociat cu pFile la valoarea ptr, obţinută anterior prin funcţia fgetpos.

feof
int feof(FILE *fis);

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.

freopen
FILE* freopen(const char *filename, const char *mode, FILE *fp);

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.

fflush
int fflush(FILE *fp);

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.

Citirea şi scrierea în/din fişiere

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:

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.

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”).

Citire/scriere cu format

int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);

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).

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”.

gigel.in
5
1 3 -1 6 7

Rulați și înțelegeți următorul cod C.

#include <stdio.h>
#define NMAX 100
 
int main() {
    // numele fisierului de intrare
    char input_filename[] = "gigel.in";
 
    // deschidere fisier de intrare pentru
    // citire (r) in modul text (t)
    FILE *in = fopen(input_filename, "rt");
 
    // verific daca fisierul a fost deschis cu succes
    // altfel opresc executia (in cazul acestei probleme)
    if (in == NULL) {
        fprintf(stderr, "ERROR: Can't open file %s", input_filename);
        return -1;
    }
 
    int n, v[NMAX], i; // numarul de elemente && vectorul
 
    // citesc n din fisier
    fscanf(in, "%d", &n);
    //citesc tabloul
    for (i = 0; i < n; ++i) {
        fscanf(in, "%d", &v[i]);
    }
 
    // deoarece stiu sigur ca nu mai am nimic de citit
    // pot inchide fisierul de intrare
    fclose(in);
 
    // dublez elementele din vector
    for (i = 0; i < n; ++i) {
        v[i] <<= 1;
    }
 
    // deschid fisierul pentru a scrie rezultatele
    char output_filename[] = "gigel.out";
    // deschid pentru scriere (w) in modul text (t)
    FILE *out = fopen(output_filename, "wt");
 
    // verific daca fisierul a fost deschis cu succes
    // altfel opresc executia (in cazul acestei probleme)
    if (out == NULL) {
        fprintf(stderr, "ERROR: Can't open file %s", output_filename);
        return -1;
    }
 
    // scriu n si vectorul in fisier
    fprintf(out, "%d\n", n);
    for (i = 0; i < n; ++i) {
        fprintf(out, "%d ", v[i]);
    }
    fprintf(out, "\n");
 
    // inchid fisierul de iesire
    fclose(out);
 
    return 0;
}

Pentru exemplul de fișier de intrare de mai sus, rezultatul este următorul.

gigel.out
5
2 6 -2 12 14 
Exemplu 2

Fie un fișier text cu un nume dat. Scrieți o funcție care calculează numărul de bytes (dimensiune fișierului).

#include <stdio.h>
 
int sizeof_file(char *filename) {
    // deschidere fisier de intrare pentru
    // citire (r) in modul text (t)
    FILE *file = fopen(filename, "rt");
 
    // verific daca fisierul a fost deschis cu succes
    // altfel opresc executia
    if (file == NULL) {
        return -1; // nu am putut calcula dimensiunea
    }
 
    // ma pozitionez la sfarsit
    fseek(file, 0, SEEK_END);
    // acum cursorul este dupa ultimul caracter
    // deci ftell imi va spune pozitia, care este echivalenta cu numarul de bytes
    int bytes_count = ftell(file);
 
    // inchid fisierul
    fclose(file);
 
    return bytes_count;
}
 
int main() {
    // numele fisierului de intrare
    char filename[] = "gigel.in";
 
    int sz = sizeof_file(filename);
    if (sz < 0) {
        // afisez la stderr
        fprintf(stderr, "ERROR: Can't open file %s", filename);
        return -1;
    }
 
    // afisez la stdout rezultatul
    printf("fisierul %s are %d bytes\n", filename, sz);
 
    // pot folosi tot fprintf pentru a afisa la stdout
    fprintf(stdout, "fisierul %s are %d bytes\n", filename, sz);
 
    return 0;
}

Citire/scriere fără conversie

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);

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).

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.

gigel.in
3
1 2 3

Fișierul binar echivalent este “gigel_in.bin”.

gigel_in.bin
0300 0000 0100 0000 0200 0000 0300 0000

  • Fișierul poate fi descărcat de aici.
  • Pentru a putea vizualiza continutul mai usor, acesta poate fi deschis cu 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 | Endianness!

Următoarea sursă C citește vectorul din fișierul “gigel_in.bin”, dublează elementele și apoi scrie rezultatul în “gigel_out.bin”.

#include <stdio.h>
#define NMAX 100
 
int main() {
    // numele fisierului de intrare
    char input_filename[] = "gigel_in.bin";
    FILE *in;
 
    // incerc sa dechid pentru citire (r)
    // fisierul in mod binar (b)
    // observati alt mod de a scrie deschiderea && verificarea
    if ((in = fopen(input_filename, "rb")) == NULL) {
        fprintf(stderr, "Can't open %s", input_filename);
        return -1;
    }
 
    // numarul de elemente, vectorul, variabila de iterare
    int n, v[NMAX], i;
 
    // 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);
 
    // fread permite citirea unui vector printr-un singur apel
    // doresc sa citesc tinand cont de:
    // voi stoca in v (care este adresa primului element)
    // 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);
 
    // pot inchide fisierul
    fclose(in);
 
     // dublez elementele din vector
    for (i = 0; i < n; ++i) {
        v[i] <<= 1;
    }
 
    // salvez rezultatul in fisierul out
     // numele fisierului de intrare
    char output_filename[] = "gigel_out.bin";
    FILE *out;
 
    // incerc sa dechid pentru scriere (w)
    // 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);
        return -1;
    }
 
    // doresc sa scriu tinand cont de:
    // voi scrie valoare de la adresa lui n (&n)
    // valoarea scrisa are dimensiunea unui int => sizeof(int)
    // scriu o singura variabila => 1
    // voi scrie in fisierul asociat cu variabila out
    fwrite(&n, sizeof(int), 1, out);
 
 
    // 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);
 
    // inchid fisierul
    fclose(out);
 
    return 0;
}

Rezultatul îl putem vizualiza tot cu Sublime.

gigel_out.bin
0300 0000 0200 0000 0400 0000 0600 0000

  • Î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.

Citire/scriere la nivel de caracter

int fgetc(FILE *fp); // întoarce următorul caracter din fişier, EOF la sfârşit de fişier
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
int fputc(int c, FILE *fp); //pune caracterul c in fişier
int ungetc(int c, FILE *fp); // pune c în bufferul asociat lui fp (c va fi următorul caracter citit din fp)

Referinţe