3. diel - Prúdy v C ++
V minulej lekcii, Typy súborov a správne umiestnenie súborov v C ++ , sme si povedali základné informácie pre prácu so súbormi rôznych formátov na rôznych operačných systémoch. Dnes si konečne vysvetlíme, čo to sú prúdy a na čo sú nám dobré.
Čo je to prúd
Prúd je sekvencia dát, ktoré lineárne spracovávame. Synonymom prúdu by
mohol byť napríklad tok dát. Zoberme si výpis do konzoly. O tom sme si
povedali, že je prúd. A kde je to "lineárna spracovania"? Dáta sa vypisujú
tak, ako ich zapisujeme. Znak po znaku pridávame znaky do prúdu, ktorý
"odteká" do konzoly. Rovnako je tomu pri objekte cin
. Tam pre
zmenu dátumu "pritekajú" z konzoly a my ich čítame. Napríklad ak chceme
prečítať číslo, vyberieme z prúdu znaky reprezentujúce číslicu. Tieto
znaky následne spracujeme (teda naparsujeme) a výsledkom je číslo.
Aké by boli ďalšie príklady prúdov? Napríklad, ak pracujeme s mikrofónom. Z okolia budeme nahrávať zvuk, ktorý je opäť iba prúd dát, ktorý naše aplikácie spracováva. Ďalej napríklad video. Tu nám dokonca figurujú dva prúdy. Jedným prúdom čítame dáta zo súboru, ktorá aplikácia následne spracováva. Druhým prúdom spracované dáta "vykresľuje" na obrazovku ako obrázky, a tým získame video.
Zjednotenie práce s dátami
Myšlienka prúdov (v anglickej literatúre streams) je veľmi silná. V aplikácii nás nemusí zaujímať, odkiaľ dáta pochádza. Napríklad v prípade videa môžeme dáta čítať zo súboru, zo siete, z kamery alebo dokonca môžeme snímať obrazovku a posielať dáta späť. Jadro aplikácie sa v takom prípade vôbec nemení, pretože vďaka prúdom je prístup k dátam jednotný.
Typy prúdov
Keď už vieme, čo to prúdy sú, môžeme si je teraz lepšie rozdeliť. Na začiatku sme si povedali, že dáta môžu "pritekať" a tiež "odtekať". Tým sú prúdy rozdelené na dve základné kategórie - vstupné a výstupné.
Vstupné prúdy dedí z triedy istream
, ktorú nájdeme v
hlavičkovom súbore istream
. Trieda istream
poskytuje
rozhranie (API), ktoré sme videli a používali v prvej lekcii.
Výstupné prúdy dedí z triedy ostream
, ktorá je definovaná
v hlavičkovom súbore ostream
. Jej použitie sme videli nielen v
prvej lekcii, ale vo všetkých tutoriáloch, pretože práve objekt
cout
dedí z triedy ostream
.
Niektoré prúdy môžu byť samozrejme oboje (teda ako vstupné tak
výstupné). Takým príkladom je napríklad trieda fstream
.
Môžeme súbor otvoriť pre čítanie aj zápis, nejaké časti prečítať a
nejaké časti prepísať. Musíte však počítať s tým, že pri zápise sa
budú dáta prepisovať.
Seekable a non-seekable prúdy
Prúdy ďalej delíme na tzv. Seekable streams (voľne preložené asi ako vyhľadateľné prúdy) a non-seekable streams (neprohledatelné či čisto lineárny prúdy). Pretože Čeština nemá skutočný preklad týchto termínov, budem aj ďalej používať ich anglické názvy.
Seekable prúdy
Seekable prúdy sú prúdy, v ktorých sa môžeme presúvať. Napríklad keď máme dáta v pamäti a vieme, že sme na offsetu 64, je pre nás jednoduché sa presunúť na index 32 (iba presunieme ukazovateľ). Rovnako tak, ak čítame 100. bajt súboru a zrazu chceme prečítať 50. bajt, môžeme sa v súbore presunúť iba tým, že povieme operačnému systému, aby nám vrátil 50. bajt súboru. Seekable prúdy sú spravidla také, kde sú dáta už niekde zaznamenaná. Ak sa v takýchto dátach niekam presunieme, nestrácame žiadne informácie, pretože tie sú uložené niekde inde.
Non-seekable prúdy
Na druhej strane sú non-seekable prúdy, ktoré môžeme
čítať (resp. Zapisovať) iba od začiatku do konca.
Ak niektoré dáta preskočíme (napríklad použitím metódy
ignore()
), sa potom tieto dáta nenávratne stratená a už sa k
nim nedostaneme. Rovnako tak sa nedostaneme k už prečítaným dátam, pretože
tá sú už spracované. Typickým príkladom je práve konzola. Akonáhle na
výstup niečo vypíšeme, nemožno (štandardnými prostriedkami) vypísaný
text zmazať. Operačný systém samozrejme tieto prostriedky má, ale musíte
použiť systémové volania a v tejto chvíli iba program využíva fakt, že
si operačný systém výstup pamätá. Staršie zariadenia, ktoré tieto
vlastnosti nemala, už text upraviť nedokázala. Ďalším príkladom
non-seekable prúdu je na príklad počúvanie mikrofónu alebo čítanie dát
zo siete. Akonáhle sú dáta spracované a vyrovnávacia pamäť prúdu je
vymazaná, nie je už možné získať.
Teraz sa vrátime k seekable prúdom. Vzhľadom k tomu, že prúdy môžu
byť aj vstupné aj výstupné, nevystačíme si iba s metódou
seek()
, ale musíme rozlíšiť pozíciu pre čítanie a pozíciu
pre zápis. Metódy pracujúce s pozíciou pre zápis sú ukončené písmenom
p (od slova put) a metódy pracujúce s pozíciou pre čítanie sú
ukončené písmenom g (od slova get). Metódy máme k dispozícii
nasledujúce:
tellg()
- vráti aktuálnu pozíciu pre čítanie od začiatku prúdu (teda absolútna pozíciu)tellp()
- vráti aktuálnu pozíciu pre zápis od začiatku prúdu (teda absolútna pozíciu)seekg( pos_type pos )
- nastaví pozíciu pre čítanie absolútne (tj. od začiatku prúdu); volanímproud.seekg(proud.tellg())
zostane pozície nezmenenáseekg( off_type off, std::ios_base::seekdir dir)
- nastaví pozíciu pre čítanie relatívne, a to vzhľadom k použitému Flag, ktorý sa odovzdáva ako druhý parameterseekp( pos_type pos )
- nastaví pozíciu pre zápis absolútne (tj. od začiatku prúdu)seekp( off_type off, std::ios_base::seekdir dir)
- nastaví pozíciu pre zápis relatívne, a to vzhľadom k použitému Flag, ktorý sa odovzdáva ako druhý parameter
Hodnotou druhého parametru metód seekx()
môže byť:
ios:beg
- od začiatku prúdu (teda rovnaký prípad ako predchádzajúci preťaženie)ios:end
- od konca prúdu (pozície musia byť záporná alebo0
)ios:cur
- od aktuálnej pozície
Poďme si to teraz vyskúšať na súboru:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream soubor1("zapis.txt", ios::out);
soubor1 << "Hello World!" << endl;
soubor1.close();
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;
return 0;
}
Štandardné prúdy
Už sme sa dozvedeli o objektoch cout
a cin
, ktoré
slúžia ako prúdy pre vstup a výstup z konzoly.
Istream, ostreae a iostream
Ďalej vieme o triedach istream
a ostream
, ktoré
slúžia ako bázovej triedy pre ostatné prúdy.
istream
poskytuje rozhranie pre vstupné prúdy, zatiaľ čo
ostream
poskytuje rozhranie pre prúdy výstupné. Ďalej existuje
trieda iostream
, ktorá (ako je z názvu zrejmé) poskytuje
rozhranie pre vstupné i výstupné prúdy. Reálne trieda iostream
dedí z tried istream
a ostream
.
Ifstream, ofstream a fstream
Pre prácu so súbormi sme použili triedu fstream
, ktorá dedí
z iostream
a poskytuje tak obe rozhrania. Toto rozhranie môžeme
však rozložiť a tak existujú v štandarde ďalej triedy
ifstream
(vstupný prúd pre súbory) a ofstream
(výstupný prúd pre súbory). V tomto prípade už neplatí, že trieda
fstream
dedia z tried ifstream
a
ofstream
, ale iba z triedy iostream
.
Istringstream, ostringstream, stringstream
Standard ďalej definuje triedu stringstream
v knižnici
sstream
, ktorá ukladá dáta v operačnej pamäti
RAM. Tento prúd môžeme použiť pre odovzdávanie dát vnútri
aplikácie. V súvislosti s použitím čisto RAM pamäte je potreba dať pozor
na jeho veľkosť. Ak čítame súbor pomocou triedy fstream
,
súbor je čítaný po častiach a nenačíta sa do operačnej pamäte. Ak
načítame veľký súbor (napríklad 4GB) a uložíme jeho obsah do objektu
triedy stringstream
, program zaberie minimálne 4GB operačnej
pamäte.
Rovnako ako trieda fstream
má aj trieda
stringstream
varianty iba pre čítanie
(istringstream
) alebo len pre zápis (ostringstream
).
Dáta pre istringstream
naplníme pri jeho vytváraní. Triedu
ostringstream
prirodzene plniť nemusíme, pretože do nej ešte
len dáta budeme odovzdávať. Dáta potom získame metódou str()
,
ktorá vráti obsah ako string
. Ak zavoláme metódu
str()
s parametrom typu string
, je vnútorný obsah
prúdu nahradený obsahom parametra. To možno využiť i pre
istringstream
- ak je prúd prázdny, dodáme ďalšie dáta a
zvyšok aplikácia môže s prúdom ďalej pracovať.
Hlavné použitie stringstream
triedy je pre prevod objektov na
reťazec. C ++ nemá nič ako metódu toString()
. Pre výstup sa
používa operátor <<
, ako sme to robili u našich tried.
Pomocou stringstream
môžeme previesť objekt na reťazec rovnako,
ako by sme objekt vypisovali:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;
class MojeTrida{};
ostream& operator<<(ostream& str, const MojeTrida& trida)
{
return str << "Reprezentace tridy";
}
int main()
{
MojeTrida a;
ostringstream str;
str << a;
string prevedenoNaRetezec = str.str();
cout << "Vypis tridy jako retezec: " << prevedenoNaRetezec << endl;
return 0;
}
Zhrnutie
Výnimočne si dovolím krátke zhrnutie dnešnej lekcie.
- Prúd či stream je sekvencia bajtov, z ktorej čítame alebo do ktorej zapisujeme.
- Prúdy delíme na vstupnú (Dedič z triedy
istream
) a výstupné (dedičov z triedyostream
). - Prúdy môžu byť seekable a non-seekable.
stringstream
slúži ako náhradatoString()
metódy.
Nakoniec prikladám kompletnú hierarchiu spomenutých tried v C ++.
To je pre dnešné lekciu všetko. V tej nasledujúcej, Súborové prúdy v C ++ a UTF kódovanie , sa ešte raz pozrieme na súborové prúdy a naučíme sa pracovať s rozšírenou znakovou sadou.