Práce se soubory

Pro práci se soubory máme k dispozici následující třídy, které jsou definované v hlavičkovém souboru <fstream>:

  • ofstream - Třída pro zápis do souboru.
  • ifstream - Třída pro čtení ze souboru.
  • fstream - Třída pro čtení i zápis do souboru.

Tyto třídy jsou stejně jako třídy cin a cout potomky proudových tříd istream a ostream, proto je ve většině případů můžeme používat stejným způsobem. Zásadním rozdílem je pouze to, že je musíme navázat na konkrétní soubor na disku. Neboli požádat jádro OS o otevření/vytvoření souboru a udělení file descriptoru - popisovače.

Otevření a vytvoření souboru

Pokud chceme vytvořit, nebo otevřít již existující soubor na disku, použijeme pro to členskou metodu open:

open(jmeno_souboru, rezim)

Kde jmeno_souboru je řetězec odpovídající názvu souboru (včetně cesty) a rezim je nepovinný parametr, který může být pomocí bitového operátoru OR (|) složený z příznaků z následující tabulky.

ios::inOtevře soubor pro vstupní operace (čtení).
ios::outOtevře soubor pro výstupní operace (zápis).
ios::binaryOtevře soubor v binárním režimu.
ios::ateUmístí automaticky virtuální kurzor na konec souboru. Bez tohoto příznaku je kurzor ve výchozím stavu na začátku souboru.
ios::appVšechny výstupní operace jsou směřovány na konec souboru.
ios::truncPředchozí obsah souboru je odstraněn.

Každá z tříd pro práci se soubory má výchozí příznaky odpovídající jejich typu, takže se při volání metody open, nemusí předávat.

Jelikož je operace otevření souboru ta první kterou chceme s objektem souboru provést, můžeme předat oba parametry už v konstruktoru a metoda open bude zavolána automaticky.


  // Způsob 1 - vlastní volání metody
  ofstream mujSoubor;
  mujSoubor.open("ukazkovy.txt");

  // Způsob 2 - použití konstruktoru
  ofstream mujSoubor("ukazkovy.txt");

  // Způsob 3 - použití konstruktoru v binárním režimu se zápisem na konec
  ofstream mujSoubor("ukazkovy.txt", ios::binary | ios::app);

Kontrola otevření souboru

Operace otevření souboru nemusí být vždy úspěšná. Soubor nemusí existovat, aktuální uživatel nemusí mít práva pro zápis atd. Z těchto důvodů musíme před samotnou prací s daty zkontrolovat, že soubor je skutečně otevřený pomocí členské metody is_open().


  // Test na otevření souboru
  if (mujSoubor.is_open())
    cout << "Soubor otevren" << endl;
  else
    cout << "Chyba pri otevreni souboru" << endl;

Uzavření souboru

Protože se souborem pracujeme v paměti RAM a o samotný zápis na disk se stará jádro OS, musíme soubor vždy korektně uzavřít a uvolnit jemu odpovídající popisovač ostatním procesům. K tomu slouží členská metoda close().


  // Uzavření souboru
  mujSoubor.close();

Stavové příznaky proudu

Stejně jako u každé jiné vstupně-výstupní operace se můžou dispozice přiřazeného proudu změnit (chyba, konec souboru apod). Proto, stejně jako u proudů cin, cout a cerr máme k dispozici členské metody pro ověření příznaků aktuálního stavu.

bad()
True pokud došlo k chybě během čtení, nebo zápisu (Soubor není otevřen, došlo místo na disku, apod).
fail()
True stejně jako v předešlém případě, ale zahrnuje i chyby formátu. Například jsme ze streamu přečetly znak, ačkoli jsme chtěli číst číslo.
eof()
True pokud jsme při čtení souboru narazily na konec.
good()
True pokud je proud v pořádku = žádné z předchozích volání by nevrátilo True

Pokud se náš proud dostane do jednoho z chybových stavů, nelze s ním opět pracovat, dokud se jeho příznaky nevyresetují voláním clear().

  
    // Resetování stavových příznaků
    mujSoubor.clear();
  

Pohyb v souboru

Každý soubor si udržuje alespoň jeden virtuální kurzor, označující místo kde se provede následující operace čtení/zápisu. Pro manipulaci s tímto virtuálním kurzorem máme k dispozici následující metody:

tellg()
tellp()

Vrací pozici kurzoru pro čtení(Get), resp. zápis(Put) v datovém typu streampos

seekg(pozice)
seekp(pozice)

Nastaví kurzor na pozici předanou parametrem typu streampos

seekg(posun, smer)
seekp(posun, smer)

Nastaví kurzor na relativní pozici zadanou parametren posun vůči místu v souboru předanému jako parametr smer typu seekdir, který je výčtovým typem:

ios::begOd začátku souboru
ios::curOd aktuální pozice
ios::endOd konce souboru

Textový soubor

Textový soubor slouží k manipulaci s daty v jejich textové reprezentaci, která v důseldku formátování není identická s jejich binární formou. Obecně platí, že textové soubory jsou snadno editovatelné člověkem, jsou větší než binární a při programovém zpracování se v nich hůře pohybuje. Za textové soubory jsou považovány všechny, které nejsou otevřené s příznakem ios::binary.

Zápis textového souboru

  
    #include <fstream>

    using namespace std;

    int main(int argc, char *argv[]) {
      ofstream mujSoubor;
      mujSoubor.open("vystupniSoubor.txt");

      if (mujSoubor.is_open()) {
        mujSoubor << "Prvni radek textoveho souboru.\n";
        mujSoubor << "Druhy radek textoveho souboru." << endl;
        int a = 42;
        double d = 3.14159;
        mujSoubor << "Hodnota a: " << a << endl;
        mujSoubor << "Hodnota d: " << d << endl;

        mujSoubor.close();
      }

      return 0;
    }
  
Obsah souboru vystupniSoubor.txt:
  
    Prvni radek textoveho souboru.
    Druhy radek textoveho souboru.
    Hodnota a: 42
    Hodnota d: 3.14159
  

Čtení textového souboru

  
    #include <iostream>
    #include <fstream<
    #include <string<

    using namespace std;

    int main(int argc, char *argv[]) {
      ifstream mujSoubor;
      mujSoubor.open("vstupniSoubor.txt");

      if (mujSoubor.is_open()) {
        string slovo;
        mujSoubor >> slovo;
        cout << slovo; // Vypíše pouze "Prvni"

        // Čtení po celých řádkách až do konce souboru
        string radek;
        while (getline(mujSoubor, radek)) {
          cout << radek << '\n';
        }

        mujSoubor.close();
      }

      return 0;
    }
  
Obsah souboru vystupniSoubor.txt:
  
    Prvni radek textoveho souboru.
    Druhy radek textoveho souboru.
    Hodnota a: 42
    Hodnota d: 3.14159
    Dalsi text
    Šíleně žluťoučký kůň úpěl ďábelské ódy.
  
Výstup programu:
  
    Prvni radek textoveho souboru.
    Druhy radek textoveho souboru.
    Hodnota a: 42
    Hodnota d: 3.14159
    Dalsi text
    ┼á├şlen─Ť ┼żlu┼ąou─Źk├Ż k┼»┼ł ├║p─Ťl ─Ć├íbelsk├ę ├│dy.
  

Binární soubor

Do binárních souborů ukládáme data ve stejné formě, v jaké jsou uložená v paměti. Za předpokladu, že známe strukturu souboru, se můžeme po souboru snadno pohybovat.

Kdyby jsme použily proudové operátory <<, nebo >> došlo by k přeformátování dat a výstup by byl opět ve formátu řetězce. Proto používáme speciální metody:


  write(char * adresa, streamsize velikost)
  read(char * adresa, streamsize velikost)

Kde adresa je ukazatel na začátek bloku dat, který chceme zapsat/kam chceme uložit přečtená data a velikost je počet bajtů k zapsání/přečtení.

Zápis binárního souboru

  
    #include <fstream>

    #define ELEM_COUNT 5

    using namespace std;

    int main(int argc, char *argv[]) {
      ofstream binSoubor("datovySobor.bin", ios::binary);

      if (binSoubor.is_open()) {
        int count = ELEM_COUNT;
        int arr[ELEM_COUNT] = { 1, 2, 3, 4, 5 };
        double a = 3.14, b = 5.6;

        binSoubor.write((const char *)&count, sizeof(count));
        binSoubor.write((const char *)arr, count * sizeof(*arr));

        binSoubor.write((const char*)&a, sizeof(a));
        binSoubor.write((const char*)&b, sizeof(b));

        binSoubor.close();
      }

      return 0;
    }
  

Čtení binárního souboru

Při čtení souboru musíme dávat pozor, aby jsme data načítaly ve stejném pořadí, v jakém byla zapsána

  
    #include <fstream>
    #include <iostream>

    #define ELEM_COUNT 5

    using namespace std;

    int main(int argc, char *argv[]) {
      ifstream binSoubor("datovySobor.bin", ios::binary);

      if (binSoubor.is_open()) {
        int count;
        int arr[ELEM_COUNT] = { 1, 2, 3, 4, 5 };
        double a, b;

        binSoubor.read((char *)&count, sizeof(count));
        binSoubor.read((char *)arr, count * sizeof(*arr));

        binSoubor.read((char*)&a, sizeof(a));
        binSoubor.read((char*)&b, sizeof(b));

        binSoubor.close();

        cout << "Pocet prvku:" << count << endl;
        cout << "Prvky pole: {";

        int i;
        for (i = 0; i < count - 1; ++i)
          cout << arr[i] << ", ";
        cout << arr[i] << '}' << endl;

        cout << "a: " << a << "\nb: " << b << endl;
      }

      return 0;
    }
  
Výstup programu:
  
    Pocet prvku:5
    Prvky pole: {1, 2, 3, 4, 5}
    a: 3.14
    b: 5.6