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::in | Otevře soubor pro vstupní operace (čtení). |
ios::out | Otevře soubor pro výstupní operace (zápis). |
ios::binary | Otevře soubor v binárním režimu. |
ios::ate | Umístí automaticky virtuální kurzor na konec souboru. Bez tohoto příznaku je kurzor ve výchozím stavu na začátku souboru. |
ios::app | Všechny výstupní operace jsou směřovány na konec souboru. |
ios::trunc | Př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::beg | Od začátku souboru |
ios::cur | Od aktuální pozice |
ios::end | Od 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