10. cvičení
Práce se soubory
Třídy pro práci se soubory jsou dostupné v hlavičkovém souboru fstream
, který
je potřeba includovat na začátku zdrojového souboru:
#include <fstream>
Čtení ze souboru
Pro práci se soubory, ze kterých chceme číst data, slouží třída std::ifstream (input file stream).
Nejprve potřebujeme otevřít nějaký soubor, což můžeme provést buď pomocí metody
open
:
ifstream soubor;
soubor.open("data.txt");
Nebo přímo předáním názvu souboru do konstruktoru třídy ifstream
:
ifstream soubor("data.txt");
Otevírání souboru je ale operace, která může selhat (např. když vstupní soubor
neexistuje), takže bychom měli ověřit, jestli se operace povedla. To můžeme
provést např. pomocí metody
good, která vrací hodnotu
true
, pokud je daný objekt v pořádku, a false
, pokud nastala nějaká chyba:
if (soubor.good()) {
cout << "soubor otevren" << endl;
}
else {
cout << "soubor neslo otevrit" << endl;
return 1;
}
Dále můžeme přistoupit k samotnému čtení dat. Jelikož třída ifstream
představuje „stream“, můžeme pro čtení dat použít operátor >>
, např.:
string s;
soubor >> s;
cout << "1. slovo je " << s << endl;
Také můžeme použít funkci getline
:
getline(soubor, s);
cout << "zbytek radku je " << s << endl;
Operátor >>
funguje nejen pro textové řetězce, ale také např. pro čtení čísel:
int a = 0;
soubor >> a;
int b = 0;
soubor >> b;
cout << "cisla jsou " << a << " a " << b << endl;
Zde je ale potřeba dát pozor na to, že čtení se nemusí vždy povést, a proto
bychom po každém pokusu o čtení dat měli zkontrolovat, jestli se to povedlo.
Pokud bychom kontroly neprováděli, objekt soubor
by zůstal ve stavu s chybou a
žádné další čtení by se neprovedlo, takže program by bez kontrol nemusel
pracovat správně. Pro kontrolu můžeme opět použít metodu good
:
int c;
soubor >> c;
if (soubor.good()) {
cout << "c = " << c << endl;
}
else {
cout << "chyba" << endl;
}
Zápis do souboru
Pro práci se soubory, do kterých chceme zapisovat data, slouží třída std::ofstream (output file stream). Otevření výstupního souboru se provede podobně jako otevření vstupního souboru:
ofstream vystup("vystup.txt");
if (!vystup.good()) {
cout << "vystupni soubor neslo otevrit" << endl;
}
Pro zápis dat do souboru slouží operátor <<
:
vystup << "Hello, world!" << endl;
Výstupní soubor lze vlastně používat stejně jako objekt cout
pro výpis do
terminálu. Dokonce můžeme definovat funkce s parametrem typu ostream
, za který
můžeme dosadit jak objekty typu ofstream
, tak i objekt cout
:
void vypocet(ostream& out)
{
out << "vystupni text" << endl;
}
Potom uvnitř nějaké jiné funkce můžeme použít buď vypocet(vystup);
nebo
vypocet(cout);
, podle toho, kam chceme data vypsat.
Pozor na to, že parametr out
musí být reference, tj. ostream&
místo
ostream
. Důvod spočívá v tom, že pro objekty typu std::ifstream
a
std::ofstream
nelze vytvářet kopie, takže je lze předávat pouze pomocí
reference.
Příklad: funkce pro počítání slov v souboru
Pro počítání slov v souboru můžeme využít funkci z předchozího cvičení, která
počítá slova v textovém řetězci. Soubor pak můžeme zpracovat po řádcích s
využitím funkce getline
a pro každý řádek použít již hotovou funkci
pocet_slov
.
int pocet_slov(string text)
{
// funkce naprogramovana stejne jako na predchozim cviceni...
}
int pocet_slov(ifstream& soubor)
{
int pocet = 0;
while (soubor.good()) {
string s;
getline(soubor, s);
pocet += pocet_slov(s);
}
return pocet;
}
Čtení souboru provádíme, dokud je objekt soubor
v pořádku (jeho metoda good
vrací true
), čili dokud nenastane nějaká chyba nebo dokud nedojdeme na konec
souboru.
Všimněte si také, že máme dvě funkce se stejným názvem. To ale ničemu nevadí, protože překladač jazyka C++ se (v některých případech) dokáže rozhodnout podle typu parametrů, kterou z definovaných funkcí má v daném případě použít.
Alternativně můžeme počítání slov v souboru naprogramovat pomocí operátoru >>
,
který se postará o rozdělení na jednotlivá slova:
int pocet_slov2(ifstream& soubor)
{
int pocet = 0;
while (soubor.good()) {
string s;
soubor >> s;
pocet++;
}
return pocet;
}
Tyto funkce můžeme použít a porovnat např. takto:
int main()
{
ifstream vstup;
vstup.open("data.txt");
if (!vstup.good()) {
cout << "soubor neslo otevrit" << endl;
return 1;
}
cout << "pocet slov v souboru je " << pocet_slov(vstup) << endl;
// ekvivalentni podminka: if (!vstup.good() && !vstup.eof()) ...
if (vstup.fail() || vstup.bad()) {
cout << "ale pri pocitani doslo k chybe" << endl;
return 1;
}
// prejdeme zpet na zacatek souboru
vstup.seekg(0);
cout << "pocet slov v souboru je " << pocet_slov2(vstup) << endl;
if (vstup.fail() || vstup.bad()) {
cout << "ale pri pocitani doslo k chybe" << endl;
return 1;
}
return 0;
}
Zde po každém použití funkce pocet_slov
nebo pocet_slov2
navíc kontrolujeme,
jestli při provádění funkce nedošlo k chybě čtení souboru (potom by výsledek
nemusel odpovídat realitě). Mezi oběma funkcemi navíc používáme metodu
seekg, která nás
přesune zpět na začátek souboru, aby druhá funkce měla také co číst.