Práce se soubory pomocí knihovny STL
C++ 11

image_printTisk

V tomto článku si popíšeme základy práce se soubory pomocí knihovny STL. Práce se soubory pomocí této knihovny je mnohem snadnější, než pomocí staršího přístupu pomocí standardní knihovny C.

Otevření a zavření souboru

Pro práci se soubory pomocí STL je nutné vložit hlavičkový soubor <fstream> do příslušného CPP souboru. Po té si musíme vybrat, jak se souborem chceme pracovat. Soubor můžeme otevřít pouze pro čtení, nebo pouze pro zápis, nebo i pro čtení a zápis. Pro každý z těchto přístupů se deklaruje jiná třída. Viz následující příklad:

#include <fstream>
using namespace std;

int main() {
    ifstream ifs("cteni.txt");
    ofstream ofs("zapis.txt");
    fstream iofs("cteni_a_zapis.txt");
}

Na příkladu je vložen hlavičkový soubor fstream a jsou definovány tři objekty. Proměnná ifs otevírá soubor pouze pro čtení pomocí třídy ifstream, proměnná ofs typu ofstream otevírá soubor pro zápis a nakonec proměnná iofs typu fstream otevírá soubor jak pro zápis, tak pro čtení.

Možná se ptáte, jak soubor zavřít. O tuto problematiku se nemusíme vůbec starat. Jakmile program opustí blok, kde končí platnost proměnné reprezentující příslušný soubor, dojde automaticky k vyvolání speciální metody s názvem destruktor, který soubor řádně zavře.

Textový a binární způsob otevření souboru

Soubor může být otevřen ve dvou režimech, a to binárním nebo textovém. Pokud je soubor otevřen v režimu binárním, metody pro práci se vstupem a výstupem dostávají data, laicky řečeno “byte po bytu”, přesně tak, jak jsou v souboru uložena. Standard C++ v tomto případě zakazuje implementacím jakékoliv modifikace při práci s daty. Na rozdíl od toho, když otevřeme soubor v režimu textovém, konkrétní implementace knihovny STL pro daný OS může upravovat textový vstup a výstup dle zvyklostí příslušného operačního systému. Týká se to hlavně rozdílů znaků konce řádku mezi Windows, Linux a MacOS. Například ve Windows jsou konce znaků 0xD a 0xA automaticky na znak reprezentovaný escape sekvencí “\n”.

Výchozím režimem pro práci se soubory je režim textový. Pokud si chceme vyžádat otevření souboru v režimu binárním, musíme to provést způsobem, jak ukazuje následující příklad:

#include <fstream>
using namespace std;

int main() {
    ifstream ifs("binarni_soubor.bin", std::ios_base::binary);
    return 0;
}

Na příkladu je vidět, že pro binární otevření souboru musíme uvést flag std::ios_base::binary.

Detekce chybových stavů

Každá operace se soubory by měla být zkontrolována, zda proběhla v pořádku. Knihovna STL pro práci se soubory nabízí pro tuto záležitost čtyři stavové metody a jeden přetížený operátor. Tyto metody by se měli používat při provedení každé operace se souborem. Jsou to:

  • good() – vrací logickou hodnotu true, pokud při poslední operaci nenastala chyba
  • eof() – vrací logickou hodnotu true, pokud bylo dosaženo konce souboru
  • fail() – vrací logickou hodnotu truepokud nastala libovolná chyba
  • bad() – vrací logickou hodnotu true, pokud nastala chyba, která nelze nijak napravit
  • operátor ! – je v podstatě synonymum pro volání ifs.fail(), kde ifs je proměnná reprezentující příslušný soubor

Na následujícím příkladu je vidět ukázka, která testuje, jak je možné otestovat otevření souboru:

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream ifs("soubor.txt");
    if(!ifs) {
         cerr << "Soubor se nepodařilo otevřít" << endl;
    }
}

Neformátovaný vstup a výstup

Nyní co víme, jak soubor otevřít a zavřít a případně obsloužit chybové stavy, přejdeme k popisu tématu, jak do souboru zapisovat nebo z něj číst. K tomu máme dvě možnosti. Prvním je neformátovaný přístup a druhým je přístup formátovaný. Začneme popisem přístupu neformátovaného.

Neformátovaný přístup by se dal popsat jako přístup čistě po znacích, které se v souboru vyskytují. Pro čtení znaků můžeme použít následující metody příslušného objektu reprezentující soubor:

  • get() – přečte jeden znak ze souboru
  • peek() – získá znak ze souboru, který je na řadě, ale neposune pozici čtení, takže znak stále zůstává v souboru jako nepřečten
  • unget() – vrátí zpět pozici posledního přečteného znaku o jeden znak zpět

Vše si nyní objasníme na příkladu:

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream ifs("soubor.txt");  // Otevřeme soubor

    char c = ifs.get(); // přečteme jeden znak
    if(c == 'a') {      // Pokud je přečteným znakem písmeno 'a'
        ifs.unget();    // pak vrátíme stav souboru tak, jako by byl přečtený znak nepřečten, tedy posuneme kurzor čtení o jeden znak zpět
    }

    c = ifs.peek();     // Nyní čteme znak, který je na řadě, ale neposouváme kurzor pro čtení, tedy znak je stále nepřečten

    return 0;
}

Pokud potřebujeme přečíst více znaků najednou, z důvodu lepšího výkonu je vhodné nepoužívat metodu get() a číst znak po znaku, ale využít metod, které umí načíst posloupnost znaků do pole nebo bufferu. Jsou to následující metody:

  • getline() – tato metoda čte znaky do doby, až je dosažen znak konce řádku nebo je přečten zadaný maximální počet znaků
  • read() – tato metoda čte pouze zadaný maximální počet znaků, pokud není dosaženo konce souboru

Zadávání maximálního počtu znaků při hromadném čtení je důležité a mělo by odpovídat velikosti pole nebo bufferu, do kterého znaky čteme. Jinak může dojít k přepsání jiných dat v paměti počítače, což může vést ke zbytečnému pádu aplikace a případné ztrátě dat uživatele. Jedná se o jednu z nejčastějších chyb začátečníků. Viz následující příklad:

#include <fstream>
#include <array>
#include <iostream>
using namespace std;

int main() {
    array<char, 100> buffer;  // Deklarujeme buffer
    ifstream ifs("soubor.txt")
    if(!ifs) {
        cerr << "Selhalo otevření souboru" << endl;
        return 1;
    }

    if(! ifs.getline(&buffer[0], 100) {
        cerr << "Selhalo čtení pomocí getline" << endl;
        return 1;
    }

    if(! ifs.read(&buffer[0], 100) {
        cerr << "Selhalo čtení pomocí read" << endl;
        return 1;
    }
 
    return 0;
}

Na příkladu napřed deklarujeme pole o velikosti 100 znaků. Po té otevřeme soubor a pokusíme se přečíst jeden řádek pomocí metody getline. Nakonec se pokusíme načíst do pole maximálně 100 znaků pomocí metody read.

Pro zápis do souboru si představíme pouze dvě metody. První zapisuje jeden znak a druhá slouží k zápisu celého bufferu nebo pole znaků. Jsou to následující:

  • put() – slouží k zápisu jednoho znaku
  • write() – zapisuje celé pole nebo buffer znaků

Vše si lze prohlédnout na následujícím příkladu:

#include <iostream>
#include <fstream>
#include <array>
using namespace std;

int main() {
    ofstream ofs("soubor.txt"); // Otevřeme soubor pro zápis
    if(!ofs) {
        cerr << "Soubor nelze otevřít pro zápis" << endl;
        return 1;
    }

    if(! ofs.put('A')) {
        cerr << "Selhalo volání metody put" << endl;
        return 1;
    }

    array<char, 100> buffer;
    for(int i = 0; i < 100; i++)
        buffer[i] = i;

    if(! ofs.write(&buffer[0], 100)) {
        cerr << "Selhalo volání metody write" << endl;
        return 1;
    }

    return 0;
}

Na příkladu se napřed pokusíme otevřít soubor pro zápis. Po té se pokusíme pomocí metody put zapsat písmeno A a nakonec připravíme buffer znaků s kódem od 0 do 99 a pokusíme se tento buffer zapsat pomocí metody write.

Formátovaný vstup a výstup

V textovém režimu otevření souboru můžeme provádět i formátovaný textový vstup a výstup. Data jsou pak automaticky konvertována pomocí operátorů << a >> na příslušný datový typ. Formátovaný vstup a výstup funguje nejen pro základní vestavěné datové typy, ale můžeme definovat způsob formátování i pro uživatelsky definované typy jako jsou struktury nebo třídy. K tomu ale potřebujeme znalost přetěžování operátorů. Tato problematika je probírána v navazující kapitole o objektově orientovaném programování.

Mimo jiné lze ovlivňovat formátování, například čísel, pomocí tzv. manipulátorů. Blíže tématiku o formátovaném vstup a výstupu, včetně seznámení se se základními manipulátory probíráme ve článku o formátovém výstupu na textovou konzoli, kterou lze aplikovat i na soubory.

Přesun pozice v souboru

Už jsme si zmínili v předchozím textu, že příslušný objekt udržuje pozici pro čtení a pro zápis. Pozice pro čtení slouží jako jakýsi ukazatel k tomu, které znaky byly přečteny a které číst dál a pozice pro zápis určuje, kam zapisovat další znaky. Pokud chceme získat hodnoty těchto pozic v číselné podobě, jinak nazývané také offset, lze použít následující metody:

  • tellg() – vrací pozici pro čtení v číselné podobě od začátku souboru
  • tellp()  – vrací pozici pro zápis od začátku souboru

Pokud chceme pozici pro čtení nebo pro zápis přesunout, nabízí se nám dvě metody:

  • seekg() – přesouvá pozici pro čtení
  • seekp() – přesouvá pozici pro zápis

Metody seekgseekp berou dva argumenty. Prvním z nich se číselný výraz offsetu v souboru a druhým je směr, jak s tímto číselným výrazem naložit. Směry mohou nabývat tří následujících hodnot:

  • std::ios_base::beg – číselný výraz stanovuje absolutní pozici od začátku souboru
  • std::ios_base::cur – číselný výraz stanovuje relativní hodnotu přesunu pozice vzhledem k pozici současné
  • std::ios_base::end – číselný výraz stanovuje absolutní pozici v souboru od jeho konce

Pokud není druhý parametr uveden, považuje se číselný výraz, tedy offset, jako absolutní pozice od začátku souboru.

 

image_printTisk
Práce se soubory pomocí knihovny STL
C++ 11
Ohodnoťte tento článek

Související články

  • Paměťový buffer – třída “stringstream” V této kapitole uzavřeme téma kolem vstupu a výstupu. Zatím jsme si ukázali, jak je možné provádět vstup a výstup na textovou konzoli a do souboru. Tato kapitola popisuje poslední datový […]
  • Datový typ “string” podrobněji Datový typ string nás provází od počátku studia jazyka C++ na těchto stránkách. V tomto článku se hlouběji seznámíme, jak funguje a co nám nabízí za funkce, které pro práci s textem můžeme […]
  • Formátování textů na textové konzoli V tomto článku si popíšeme, jak provádět formátovaný výstup na textovou konzoli. Výše uvedené způsoby formátování výstupu lze ale použít, jak se dále dovíme, i při zápisu do souboru nebo […]
  • Datová struktura “queue” Fronta funguje na principu zvaném FIFO (First-In First-Out). To znamená, že prvek, který byl do fronty vložen dříve, bude také dříve zpracováván. V podstatě můžeme říci, že prvky vyjímáme […]
  • Aritmetické operátory Tento článek by měl čtenáři nastínit používání základních aritmetických operátorů v jazyce C++. Je dobré upozornit, že C++ disponuje širší sadou operátorů, než se třeba pojednává ve […]
  • Překlad do strojového kódu V tomto článku se seznámíme s tím, jak překladač převádí zdrojový kód programu do binární podoby spustitelné operačním systémem. Článek se zmiňuje nejen o procesu překladu zdrojových kódů, […]