Fișiere
Fișere
Toate fișierele sunt o înșiruire de bytes, indiferent de extensie. În realitate, extensia nu contează la nimic! Dacă numim un fișier text a.exe și îl deschidem cu un editor de text, va funcționa exact ca un a.txt. La fel, dacă numim executabilul unui joc video main.cpp și îl rulăm din consolă, va porni jocul ca și cum nu ar fi nimic greșit. Diferența între fișiere se face prin format. Fiecare tip de fișier (.pdf, .xlsx, .bmp, etc.) începe cu câțiva bytes (file header) care îl identifică în mod unic astfel încât un program să își poată da seama cum să îl gestioneze.
Fișiere text
Fișierele cu care suntem obișnuiți de la probleme de algoritmică sunt fișiere text. Ele nu au file header specific, și în realitate funcționează ca orice alt fișier, doar că cin interpretează fiecare byte ca un caracter ASCII și ne mai ajută sărind peste spații atunci când citim numere, etc.
Fișiere binare
C Style
O secvență completă de instrucțiuni pentru citirea unui fișier ca binar în C este următoarea:
FILE *fileIn = fopen(fileName, "rb");
fseek(fileIn, 0, SEEK_END);
uint32_t fileLen = ftell(fileIn);
uint8_t *buffer = (uint8_t *) malloc(fileLen);
fseek(fileIn, 0, SEEK_SET);
fread(buffer, sizeof(uint8_t), fileLen, fileIn);
fclose(fileIn);
Pentru scriere:
FILE *fileOut = fopen(fileName, "wb");
fwrite(buffer, sizeof(uint8_t), fileLen, fileOut);
fclose(fileOut);
FILE *fileIn = fopen(fileName, "rb") deschide fișierul fileName ca binar pentru citire. Toate modurile de deschidere a unui fișier în C se pot găsi în documentație;
fseek(fileIn, 0, SEEK_END) mută indicatorul de citire la finalul fișierului (la 0 bytes începând cu finalul fișierului).
ftell(fileIn) ne spune câți bytes au fost deja parcurși în fișier. Din moment ce indicatorul este la finalul fișierului deja, ne va da lungimea datelor din fișier. Creăm un buffer de asceastă dimensiune pentru a stoca conținutul.
fseek(fileIn, 0, SEEK_SET) mută indicatorul de citire la începutul fișierului (la 0 bytes de la începutul fișierului).
fread(buffer, sizeof(uint8_t), fileLen, fileIn) stochează la adresa buffer următorii sizeof(uint8_t) * fileLen bytes din fișier, începând cu poziția actuală a indicatorului de citire.
Exemplul de citire funcționează analog.
C++ Style
O secvență completă de instrucțiuni pentru citirea unui fișier ca binar este următoarea:
std::ifstream fileIn(fileName, std::ios::ate | std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file");
}
size_t fileLen = (size_t) fileIn.tellg();
std::vector<char> buffer(fileLen, 0);
fileIn.seekg(0);
fileIn.read(buffer.data(), fileLen);
fileIn.close();
Pentru scriere:
std::ofstream fileOut(fileName, std::ios::trunc | std::ios::out);
fileOut.write(buffer.data(), fileLen * sizeof(uint8_t));
fileOut.close();
std::ifstream fileIn(fileName, std::ios::ate | std::ios::binary) deschide fișierul ca binar și mută indicatorul de citire la finalul fișierului. Al doilea parametru este o mască de biți care ne permite să specificăm cum vrem să fie deschis fișierul. Toate opțiunile pentru această mască se pot găsi în documentație
fileIn.tellg() ne spune câți bytes au fost deja parcurși în fișier. Din moment ce indicatorul este la finalul fișierului deja, ne va da lungimea datelor din fișier. Creăm un buffer de asceastă dimensiune pentru a stoca conținutul.
fileIn.seekg(0) mută indicatorul de citire la începutul fișierului (byte-ul de indice 0). De aici vom începe citirea efectivă.
fileIn.read(buffer.data(), fileLen) citește fileLen bytes și îi stochează la adresa de memorie care începe cu adresa dată ca primul parametru. buffer.data() întoarce pointerul către inceputul datelor efective din vectorul STL numit buffer.
fileOut.write(buffer.data(), fileLen) scrie fileLen bytes începând cu adresa datelor efective cu vectorul STL buffer.
Formatul de Fișier .bmp
bmp este un format de fișier simplu pentru imagini. Nu are compresie, ca png sau jpg, deci poate fi manipulat mult mai ușor. Întreaga specificație a formatului de fișier este disponibilă pe Wikipedia și nu va fi reexplicat aici.
Datele efective din imagine sunt ținute în format RGB, culorile fiind compuse din roșu, verde și albastru. Cele 3 canale de culori pot lua valori între 0 și 255. O observație importantă este că o culoare este o nuanță de gri dacă și numai dacă r = g = b.
Exerciții
- Creați o interfață numită
`FileHandlercare va avea următoarele metode publice:uint8_t *readBinaryFile(std::string fileName): întoarce adresa la care stochează datele citite binar din fișierulfileNamevoid writeBinaryFile(std::string fileName, uint8_t *buffer): scrie datele dinbufferca binar în fișierulfileNameImage *readImage(std::string fileName): creează un obiect de tip imagine și întoarce un pointer către acesta. Trebuie creată și clasaImageîn care se află toate datele utile din fișierulfileNamede tipbmp. O puteți lăsa neimplementată momentan.void writeImage(std::string fileName, Image *image): scrie în fișierulfileNametoate datele unei imagini de tipbmp, unde structura fișierului este corectă și dedusă din starea obiectului aflat la adresaimage.
- Completați clasa
Image, adăugând un buffer pentru pixelii din imagine și variabile membru pentru toate datele din specificația formatului de care am putea avea nevoie pentru afișare sau logică. - Extindeți interfața
FileHandlercu clasaCFileHandlerși implementați, adăugând orice metode auxiliare aveți nevoie. - Extindeți interfața
FileHandlercu clasaCPPFileHandlerși adăugați orice metode auxiliare aveți nevoie. - În
main, lucrați doar cuFileHandler. NiciodatăCFileHandlersauCPPFileHandler, mai puțin la declarare. De exemplu, aveți voieFileHandler *fileHandler = new CFileHandler(). Schimbând acest unic rând, programul trebuie să funcționeze cu sintaxa de C sau sintaxa de C++. - Completați clasa
Imagecu metodaImage *createGreyscaleImage()care să creeze o imagine nouă, doar că în alb-negru. Puteți folosi formula de transformare a unei culori în nuanță de gri:gray = 0.3 * r + 0.59 * g + 0.11 * b.