NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

Lekce 3 - Proudy v C++

V minulé lekci, Typy souborů a správné umístění souborů v C++, jsme si řekli základní informace pro práci se soubory různých formátů na různých operačních systémech.

Dnes si konečně vysvětlíme, co to jsou proudy a k čemu jsou nám dobré.

Co je to proud

Proud je sekvence dat, která lineárně zpracováváme. Synonymem proudu by mohl být například tok dat. Vezměme si výpis do konzole. O tom jsme si řekli, že je proud. A kde je to "lineární zpracování"? Data se vypisují tak, jak je zapisujeme. Znak po znaku přidáváme znaky do proudu, který "odtéká" do konzole. Stejně je tomu u objektu cin. Tam pro změnu data "přitékají" z konzole a my je čteme. Například chceme-li přečíst číslo, vybereme z proudu znaky reprezentující číslici. Tyto znaky následně zpracujeme (tedy naparsujeme) a výsledkem je číslo.

Jaké by byly další příklady proudů? Například pokud pracujeme s mikrofonem. Z okolí budeme nahrávat zvuk, který je opět pouze proud dat, který naše aplikace zpracovává. Dále například video. Zde nám dokonce figurují dva proudy. Jedním proudem čteme data ze souboru, která aplikace následně zpracovává. Druhým proudem zpracovaná data "vykreslujeme" na obrazovku jako obrázky, a tím získáme video.

Sjednocení práce s daty

Myšlenka proudů (v anglické literatuře streams) je velmi silná. V aplikaci nás nemusí zajímat, odkud data pochází. Například v případě videa můžeme data číst ze souboru, ze sítě, z kamery nebo dokonce můžeme snímat obrazovku a posílat data zpět. Jádro aplikace se v takovém případě vůbec nemění, protože díky proudům je přístup k datům jednotný.

Typy proudů

Když již víme, co to proudy jsou, můžeme si je nyní lépe rozdělit. Na začátku jsme si řekli, že data mohou "přitékat" a také "odtékat". Tím jsou proudy rozděleny na dvě základní kategorie - vstupní a výstupní.

Vstupní proudy dědí ze třídy istream, kterou najdeme v hlavičkovém souboru istream. Třída istream poskytuje rozhraní (API), které jsme viděli a používali v první lekci.

Výstupní proudy dědí ze třídy ostream, která je definovaná v hlavičkovém souboru ostream. Její použití jsme viděli nejen v první lekci, ale ve všech tutoriálech, protože právě objekt cout dědí ze třídy ostream.

Některé proudy mohou být samozřejmě obojí (tedy jak vstupní tak výstupní). Takovým příkladem je například třída fstream. Můžeme soubor otevřít pro čtení i zápis, nějaké části přečíst a nějaké části přepsat. Musíte ovšem počítat s tím, že při zápisu se budou data přepisovat.

Seekable a non-seekable proudy

Proudy dále dělíme na tzv. seekable streams (volně přeloženo asi jako prohledatelné proudy) a non-seekable streams (neprohledatelné či čistě lineární proudy). Protože čeština nemá skutečný překlad těchto termínů, budu i dále používat jejich anglické názvy.

Seekable proudy

Seekable proudy jsou proudy, ve kterých se můžeme přesouvat. Například když máme data v paměti a víme, že jsme na offsetu 64, je pro nás jednoduché se přesunout na index 32 (pouze přesuneme ukazatel). Stejně tak, pokud čteme 100. bajt souboru a najednou chceme přečíst 50. bajt, můžeme se v souboru přesunout pouze tím, že řekneme operačnímu systému, aby nám vrátil 50. bajt souboru. Seekable proudy jsou zpravidla takové, kde jsou data již někde zaznamenána. Pokud se v takových datech někam přesuneme, neztrácíme žádné informace, protože ta jsou uložena někde jinde.

Non-seekable proudy

Na druhé straně jsou non-seekable proudy, které můžeme číst (resp. zapisovat) pouze od začátku do konce. Pokud některá data přeskočíme (například použitím metody ignore()), jsou poté tato data nenávratně ztracena a již se k nim nedostaneme. Stejně tak se nedostaneme k již přečteným datům, protože ta jsou již zpracována. Typickým příkladem je právě konzole. Jakmile na výstup něco vypíšeme, nelze (standardními prostředky) vypsaný text smazat. Operační systém samozřejmě tyto prostředky má, ale musíte použít systémová volání a v této chvíli pouze program využívá faktu, že si operační systém výstup pamatuje. Starší zařízení, která tyto vlastnosti neměla, již text upravit nedokázala. Dalším příkladem non-seekable proudu je na příklad poslech mikrofonu nebo čtení dat ze sítě. Jakmile jsou data zpracována a vyrovnávací paměť proudu je vymazána, není je již možné získat.

Nyní se vrátíme k seekable proudům. Vzhledem k tomu, že proudy mohou být i vstupní i výstupní, nevystačíme si pouze s metodou seek(), ale musíme rozlišit pozici pro čtení a pozici pro zápis. Metody pracující s pozicí pro zápis jsou ukončeny písmenem p (od slova put) a metody pracující s pozicí pro čtení jsou ukončeny písmenem g (od slova get). Metody máme k dispozici následující:

  • tellg() - vrátí aktuální pozici pro čtení od začátku proudu (tedy absolutní pozici)
  • tellp() - vrátí aktuální pozici pro zápis od začátku proudu (tedy absolutní pozici)
  • seekg( pos_type pos ) - nastaví pozici pro čtení absolutně (tj. od začátku proudu); voláním proud.seekg(proud.tellg()) zůstane pozice nezměněna
  • seekg( off_type off, std::ios_base::seekdir dir) - nastaví pozici pro čtení relativně, a to vzhledem k použitému flagu, který se předává jako druhý parametr
  • seekp( pos_type pos ) - nastaví pozici pro zápis absolutně (tj. od začátku proudu)
  • seekp( off_type off, std::ios_base::seekdir dir) - nastaví pozici pro zápis relativně, a to vzhledem k použitému flagu, který se předává jako druhý parametr

Hodnotou druhého parametru metod seekx() může být:

  • ios:beg - od začátku proudu (tedy stejný případ jako předchozí přetížení)
  • ios:end - od konce proudu (pozice musí být záporná nebo 0)
  • ios:cur - od aktuální pozice

Pojďme si to nyní vyzkoušet na souboru:

fstream soubor("zapis.txt");

//precteni slova
string slovo;
char radek[128];
soubor >> slovo;
cout << "Po precteni slova \"" << slovo << "\" je pozice " << soubor.tellg() << " pro cteni a " << soubor.tellp() << " pro zapis" << endl;

//prepsani souboru
soubor.seekp(0);
soubor << "Ya Ya";
soubor.seekg(0);
soubor.getline(radek, 128);
cout << "Prvni radek souboru: " << radek << endl;

//zapis na konec
soubor.seekp(-1, ios::end);
soubor << " You are so awesome";
soubor.seekg(0, ios::beg);
soubor.getline(radek, 128);
cout << "Prvni radek po zmene souboru: " << radek << endl;

Standardní proudy

Již jsme se dozvěděli o objektech cout a cin, které slouží jako proudy pro vstup a výstup z konzole.

istream, ostream a iostream

Dále víme o třídách istream a ostream, které slouží jako bázové třídy pro ostatní proudy. istream poskytuje rozhraní pro vstupní proudy, zatímco ostream poskytuje rozhraní pro proudy výstupní. Dále existuje třída iostream, která (jak je z názvu patrné) poskytuje rozhraní pro vstupní i výstupní proudy. Reálně třída iostream dědí ze tříd istream a ostream.

ifstream, ofstream a fstream

Pro práci se soubory jsme použili třídu fstream, která dědí z iostream a poskytuje tak obě rozhraní. Toto rozhraní můžeme nicméně rozložit a tak existují ve standardu dále třídy ifstream (vstupní proud pro soubory) a ofstream (výstupní proud pro soubory). V tomto případě již neplatí, že třída fstream dědí ze tříd ifstream a ofstream, ale pouze ze třídy iostream.

istringstream, ostringstream, stringstream

Standard dále definuje třídu stringstream v knihovně sstream, která ukládá data v operační paměti RAM. Tento proud můžeme použít pro předávání dat uvnitř aplikace. V souvislosti s použitím čistě RAM paměti je potřeba dát pozor na jeho velikost. Pokud čteme soubor pomocí třídy fstream, soubor je čten po částech a nenačítá se do operační paměti. Pokud načteme velký soubor (například 4GB) a uložíme jeho obsah do objektu třídy stringstream, program zabere minimálně 4GB operační paměti.

Stejně jako třída fstream má i třída stringstream varianty pouze pro čtení (istringstream) nebo pouze pro zápis (ostringstream). Data pro istringstream naplníme při jeho vytváření. Třídu ostringstream přirozeně plnit nemusíme, protože do ní teprve data budeme předávat. Data poté získáme metodou str(), která vrátí obsah jako string. Pokud zavoláme metodu str() s parametrem typu string, je vnitřní obsah proudu nahrazen obsahem parametru. Toho lze využít i pro istringstream - pokud je proud prázdný, dodáme další data a zbytek aplikace může s proudem dále pracovat.

Hlavní použití stringstream třídy je pro převod objektů na řetězec. C++ nemá nic jako metodu toString(). Pro výstup se používá operátor <<, jako jsme to dělali u našich tříd. Pomocí stringstream můžeme převést objekt na řetězec stejně, jako bychom objekt vypisovali:

class MojeTrida{};
ostream& operator<<(ostream& str, const MojeTrida& trida)
{
    return str << "Reprezentace tridy";
}


MojeTrida a;
ostringstream str;
str << a;
string prevedenoNaRetezec = str.str();
cout << "Vypis tridy jako retezec: " << prevedenoNaRetezec << endl;

Shrnutí

Výjimečně si dovolím krátké shrnutí dnešní lekce.

  • Proud či stream je sekvence bajtů, ze které čteme nebo do které zapisujeme.
  • Proudy dělíme na vstupní (dědící ze třídy istream) a výstupní (dědící ze třídy ostream).
  • Proudy mohou být seekable a non-seekable.
  • stringstream slouží jako náhrada toString() metody.

Nakonec přikládám kompletní hierarchii zmíněných tříd v C++.

Hiearchie proudů ve standardní knihovně C++ - Soubory a práce s nimi v C++

To je pro dnešní lekci vše.

V té příští, Souborové proudy v C++ a UTF kódování, se ještě jednou podíváme na souborové proudy a naučíme se pracovat s rozšířenou znakovou sadou.


 

Předchozí článek
Typy souborů a správné umístění souborů v C++
Všechny články v sekci
Soubory a práce s nimi v C++
Přeskočit článek
(nedoporučujeme)
Souborové proudy v C++ a UTF kódování
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
13 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity