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ă ```FileHandler`` care va avea următoarele metode publice:
uint8_t *readBinaryFile(std::string fileName)
: întoarce adresa la care stochează datele citite binar din fișierulfileName
void writeBinaryFile(std::string fileName, uint8_t *buffer)
: scrie datele dinbuffer
ca binar în fișierulfileName
Image *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șierulfileName
de tipbmp
. O puteți lăsa neimplementată momentan.void writeImage(std::string fileName, Image *image)
: scrie în fișierulfileName
toate 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
FileHandler
cu clasaCFileHandler
și implementați, adăugând orice metode auxiliare aveți nevoie. - Extindeți interfața
FileHandler
cu clasaCPPFileHandler
și adăugați orice metode auxiliare aveți nevoie. - În
main
, lucrați doar cuFileHandler
. NiciodatăCFileHandler
sauCPPFileHandler
, 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
Image
cu 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
.