Input/output with files

C++ furnizează următoarele clase pentru citirea și afisarea dintr-un / într-un fișier de caractere (fișier text):

  • ofstream: Clasă stream pentru scrierea în fișiere
  • ifstream: Clasă stream pentru citirea din fișiere
  • fstream: Clasă stream petru operții de citire/scriere din/în fișiere.

Aceste clase sunt derivate direct sau indirect din clasele istream și ostream. Deja am folosit obiecte din aceste clase: cin este un obiect al clasei istream și cout este un obiect al clasei ostream. De aceea, noi deja am folosit clasele care conțin stream-uri (fluxuri) la fișiere. De fapt, putem folosi fluxurile pentru fișiere în aceeași manieră în care am folosit cin și cout, cu singura diferență că va trebui să asociem aceste fluxuri unor fișiere fizice. Să vedem un exemplu:

// operații de bază cu fișiere
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream fisierul_meu;
  fisierul_meu.open ("exemplu.txt");
  fisierul_meu << "Scriem acest text intr-un fisier.\n";
  fisierul_meu.close();
  return 0;
}
[fișierul exemplu.txt]
Scriem acest text intr-un fisier.

Acest program creează un fișier numit exemplu.txt și inserează un test în el, în același fel cum eram obișnuiți cu cout, dar de data aceasta folosim fluxul fisierul_meu.

Haideți să o luam pas cu pas:

Deschiderea unui fișier

În general, prima operație executată cu un obiect aparținând uneia dintre aceste clase este asocierea cu un fișier fizic. Aceasta acțiune este cunoscută ca deschiderea unui fișier. Un fișier deschis este reprezentat într-un program printr-un stream = flux (adică, un obiect aparținând acestor clase; în exemplul anterior, am avut fisierul_meu) și orice operație de intrare sau ieșire cu acest flux va fi aplicată fișierului fizic asociat.

Pentru a deschide un fișier cu un obiect de tip flux de date, folosim funcția membru open:

open (nume_fișier, mod);

unde nume_fișier este un string reprezentănd numele fișierului ce trebuie deschis, iar mod este un parametru opțional conțnând una dintre combinațiile de mai jos:

ios::inDeschide pentru operații de citire.
ios::outDeschide pentru operații de scriere.
ios::binaryDeschide în mod binar.
ios::ateSetează poziția inițială la sfârșitul fișierului.
Dacă nu se precizează, poziția inițială este la începutul fișierului.
ios::appToate operațiile de scriere se efectuează la sfârșitul fișierului, adăugându-se la cnținutul curent al fisierului.
ios::truncDacă fișierul este deschis pentru operații de ieșire și fișierul exista anterior, întregul său conținut este șters și va fi înlocuit cu noile informații.

Toți acești indicatori pot fi combinați folosind operatorul pe biți OR (|). De exemplu, dacă vrem să deschidem fișierul exemplu.bin în mod binar pentru a adăuga informații, am putea-o face apelând următoarea funcție membru open:

1
2
ofstream fisierul_meu;
fisierul_meu.open ("exemplu.bin", ios::out | ios::app | ios::binary);

Fiecare dintre funcțiile membru open ale claselor ofstream, ifstream și fstream are un mod implicit de lucru pe care îl folosește atunci când fișierul ete deschis fără al doilea parametru:

clasamod implicit
ofstreamios::out
ifstreamios::in
fstreamios::in | ios::out

Pentru clasele ifstream și ofstream, indicatorii folosiți automat sunt ios::in respectiv ios::out, chiar dacă se transmite funcției membru open un al parametru care nu îi include (indicatorii vor fi combinați).

Pentru fstream, valoarea implicită se aplică numai dacă funcția se apelează fără parametrul mod. Dacă funcția este apelată cu o valoare pentru al doilea parametru, atunci modul implicit este suprascris, fără combinare.

Fluxurile pentru fișiere deschise în mod binar execută operații de intrare și ieșire independent de orice format. Fișierele nebinare sunt cunosute ca fișiere text și pot să apară unele modificări datorită unor caractere speciale (precum caracterele newline și carriage return).

Deoarece prima acțiune cu un flus pentru fișiere constă în deschiderea fișierului, aceste trei clase inclu un constructor care apelează automat funcția membru open , constructor care are exact aceeași parametri ca funcția membru. De aceea, am fi putut declara obiectul fisierul_meu din exemplul anterior scriind:

1
ofstream fisierul_meu ("exemplu.bin", ios::out | ios::app | ios::binary);

combinând construcția obiectului și deschiderea flusului într-o singură instrucțiune. Cele două forme de deschidere a fișierului sunt echivalente.

Pentru a verifica dacă un stream a reușit deschiderea unui fișier, putem apela funcția membru is_open. Această funcție returnează valoarea true de tip bool în cazul în care obiectul stream este asociat unui fișier deschis, respectiv false în caz contrar:

1
if (fisierul_meu.is_open()) { /* ok, continuam cu scrierea */ }

Închiderea unui fișier

Când terminăm operațiile de intrare și ieșire cu un fișier, ar trebui să închidem acel fișier, astfel încât sistemuld e operare să fie notificat și să elibereze resursele. Pentru aceasta, apelăm funcția membru close a fluxului. Această funcție curăță buffer-ele asociate și închide fișierul:

1
fișierul_meu.close();

După apelarea acestei funcții, obiectul stream poate fi reutilizat pentru deschiderea altul fișier, iar fișierul anterior devine disponibil și poate fi deschis de alte procese.

În cazul în care un obiect este distrus în timp ce mai are încă asociat un fișier deschis, destructorul apelează automat funcția membru close.

Fișiere text

Fluxurile pentru fișiere text sunt cele pentru care nu este inclus indicatorul ios::binary în modul de deschidere. Aceste fișiere sunt proiectate pentru a stoca text și, de aceea, toate valorile care se citesc/scriu din/în ele pot suferi transformări care nu corespund neapărat valorilor lor binare exacte.

Operațiile de scriere într-un fișier text se execută exact în același mod în care lucram cu cout:

// scrierea într-un fișier text
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  ofstream fisierul_meu ("exemplu.txt");
  if (fisierul_meu.is_open())
  {
    fisierul_meu << "Aceasta este o linie.\n";
    fisierul_meu << "Aceasta este alta linie.\n";
    fisierul_meu.close();
  }
  else cout << "Nu s-a putut deschide fisierul!";
  return 0;
}
[fișierul exemplu.txt]
Aceasta este o linie.
Aceasta este alta linie.

Citirea dintr-un fișier se execută la fel cum citeam cu cin:

// citirea unui fișier text
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
  string linie;
  ifstream fisierul_meu ("exemplu.txt");
  if (fisierul_meu.is_open())
  {
    while ( getline (fisierul_meu,linie) )
    {
      cout << linie << '\n';
    }
    fisierul_meu.close();
  }

  else cout << "Nu se poate deschide fișierul"; 

  return 0;
}
Aceasta este o linie.
Aceasta este alta linie.  

Acest ultim exemplu citește un fișier text și afișează conținutul lui pe ecran. Am creat o repetiție while pentru a citi fișierul linie cu linie, folosind getline. Valoarea returnată de getline este o referință chiar la obiectul stream (la fluxul de date), iar evaluarea expresiei de tip boolean (pe parcursul repetiției while) are valoarea true dacă fluxul este pregătit pentru mai multe operații, respectiv are valaorea false fie când se ajunge la sfârșitul fișierului, fie când apare altă eroare.

Verificarea indicatorilor de stare

În vederea verificării stării unui anumit flux, există următoarele funcții membru (toate returnează o valoare de tip bool):

bad()
Returnează true dacă o operație de citire sau scriere eșuează. De exemplu, dacă încercăm să scriem într-un fișier care nu este deschis pentru scriere sau dacă nu mai este spațiu disponibil pentru scriere.
fail()
Returnează true în aceleași situații ca bad(), dar și atunci când apare o eroare de format, ca de exemplu la întâlnirea unui caracter alfabetic într-o operație care necesită citirea unui număr întreg.
eof()
Returnează true dacă s-a ajuns la sfârșitul unui fișier deschis pentru citire.
good()
Este cel mai generic indicator de stare: returnează false în aceleași situații în care apelul oricăreia dintre funcțiile anterioare ar returna true. Să observăm că funcțiile good și bad nu sunt chiar opuse (good verifică mai mulți indicatori de stare deodată).

Funcția membru clear() poate fi folosită pentru a reseta indicatorii de stare.

get and put stream positioning

All i/o streams objects keep internally -at least- one internal position:

ifstream, like istream, keeps an internal get position with the location of the element to be read in the next input operation.

ofstream, like ostream, keeps an internal put position with the location where the next element has to be written.

Finally, fstream, keeps both, the get and the put position, like iostream.

These internal stream positions point to the locations within the stream where the next reading or writing operation is performed. These positions can be observed and modified using the following member functions:

tellg() and tellp()

These two member functions with no parameters return a value of the member type streampos, which is a type representing the current get position (in the case of tellg) or the put position (in the case of tellp).

seekg() and seekp()

These functions allow to change the location of the get and put positions. Both functions are overloaded with two different prototypes. The first form is:

seekg ( position );
seekp ( position );

Using this prototype, the stream pointer is changed to the absolute position position (counting from the beginning of the file). The type for this parameter is streampos, which is the same type as returned by functions tellg and tellp.

The other form for these functions is:

seekg ( offset, direction );
seekp ( offset, direction );

Using this prototype, the get or put position is set to an offset value relative to some specific point determined by the parameter direction. offset is of type streamoff. And direction is of type seekdir, which is an enumerated type that determines the point from where offset is counted from, and that can take any of the following values:

ios::begoffset counted from the beginning of the stream
ios::curoffset counted from the current position
ios::endoffset counted from the end of the stream

The following example uses the member functions we have just seen to obtain the size of a file:

// obtaining file size
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos begin,end;
  ifstream myfile ("example.bin", ios::binary);
  begin = myfile.tellg();
  myfile.seekg (0, ios::end);
  end = myfile.tellg();
  myfile.close();
  cout << "size is: " << (end-begin) << " bytes.\n";
  return 0;
}
size is: 40 bytes.

Notice the type we have used for variables begin and end:

1
streampos size;

streampos is a specific type used for buffer and file positioning and is the type returned by file.tellg(). Values of this type can safely be subtracted from other values of the same type, and can also be converted to an integer type large enough to contain the size of the file.

These stream positioning functions use two particular types: streampos and streamoff. These types are also defined as member types of the stream class:

TypeMember typeDescription
streamposios::pos_typeDefined as fpos<mbstate_t>.
It can be converted to/from streamoff and can be added or subtracted values of these types.
streamoffios::off_typeIt is an alias of one of the fundamental integral types (such as int or long long).

Each of the member types above is an alias of its non-member equivalent (they are the exact same type). It does not matter which one is used. The member types are more generic, because they are the same on all stream objects (even on streams using exotic types of characters), but the non-member types are widely used in existing code for historical reasons.

Binary files

For binary files, reading and writing data with the extraction and insertion operators (<< and >>) and functions like getline is not efficient, since we do not need to format any data and data is likely not formatted in lines.

File streams include two member functions specifically designed to read and write binary data sequentially: write and read. The first one (write) is a member function of ostream (inherited by ofstream). And read is a member function of istream (inherited by ifstream). Objects of class fstream have both. Their prototypes are:

write ( memory_block, size );
read ( memory_block, size );

Where memory_block is of type char* (pointer to char), and represents the address of an array of bytes where the read data elements are stored or from where the data elements to be written are taken. The size parameter is an integer value that specifies the number of characters to be read or written from/to the memory block.

// reading an entire binary file
#include <iostream>
#include <fstream>
using namespace std;

int main () {
  streampos size;
  char * memblock;

  ifstream file ("example.bin", ios::in|ios::binary|ios::ate);
  if (file.is_open())
  {
    size = file.tellg();
    memblock = new char [size];
    file.seekg (0, ios::beg);
    file.read (memblock, size);
    file.close();

    cout << "the entire file content is in memory";

    delete[] memblock;
  }
  else cout << "Unable to open file";
  return 0;
}
the entire file content is in memory

In this example, the entire file is read and stored in a memory block. Let's examine how this is done:

First, the file is open with the ios::ate flag, which means that the get pointer will be positioned at the end of the file. This way, when we call to member tellg(), we will directly obtain the size of the file.

Once we have obtained the size of the file, we request the allocation of a memory block large enough to hold the entire file:

1
memblock = new char[size];

Right after that, we proceed to set the get position at the beginning of the file (remember that we opened the file with this pointer at the end), then we read the entire file, and finally close it:

1
2
3
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();

At this point we could operate with the data obtained from the file. But our program simply announces that the content of the file is in memory and then finishes.

Buffers and Synchronization

When we operate with file streams, these are associated to an internal buffer object of type streambuf. This buffer object may represent a memory block that acts as an intermediary between the stream and the physical file. For example, with an ofstream, each time the member function put (which writes a single character) is called, the character may be inserted in this intermediate buffer instead of being written directly to the physical file with which the stream is associated.

The operating system may also define other layers of buffering for reading and writing to files.

When the buffer is flushed, all the data contained in it is written to the physical medium (if it is an output stream). This process is called synchronization and takes place under any of the following circumstances:

  • When the file is closed: before closing a file, all buffers that have not yet been flushed are synchronized and all pending data is written or read to the physical medium.
  • When the buffer is full: Buffers have a certain size. When the buffer is full it is automatically synchronized.
  • Explicitly, with manipulators: When certain manipulators are used on streams, an explicit synchronization takes place. These manipulators are: flush and endl.
  • Explicitly, with member function sync(): Calling the stream's member function sync() causes an immediate synchronization. This function returns an int value equal to -1 if the stream has no associated buffer or in case of failure. Otherwise (if the stream buffer was successfully synchronized) it returns 0.
Index
Index