1. diel - Úvod do práce so súbormi v jazyku C ++
Vitajte u prvej lekcie kurzu tutoriálov o prácu so súbormi a prúdmi v jazyku C ++.
K čomu potrebujeme súbory?
Doteraz sme vždy pracovali len so štandardným vstupom a výstupom (teda s
objektmi cout
a cin
). Všetky dáta sme mali uložené
v pamäti
RAM. To má nespornú výhodu v rýchlosti ich čítaní, avšak po
ukončení aplikácie sú všetky dáta z pamäte vymazané a tým stratená. Ak
našu aplikáciu spustíme znova, k dátam sa už nedostaneme (pretože už ani
neexistujú). Tu prichádza na rad súbory. Do súborov si môžeme vložiť
ľubovoľnú informáciu a pri ďalšom spustením aplikácie ju opäť
načítať.
V C ++ je práca so súbormi súčasťou väčšieho celku, ktorý sa nazýva prúdy. Čo to sú prúdy a ako s nimi pracovať si povieme neskôr. V tejto lekcii sa pozrieme na základnú prácu so súbormi.
Základné operácie
Pre prácu so súbormi má C ++ dve API. Jedno API je v hlavičke cstdio
a
jedná sa o API zdedené z jazyka C. Toto API je
popísané v kurze Práca so súbormi v jazyku
C a nebudeme sa ním ďalej zaoberať. Čisto C ++ API pre prácu so
súbormi ponúka knižnica fstream
. V nej je (okrem iných tried)
trieda fstream
, s ktorou budeme dnes pracovať.
API je skratka pre A pplication
P rogramming I nterface. To sa dá preložiť
ako "rozhranie pre programovanie aplikácií" a jedná sa o súhrn funkcií a
tried, ktoré môže programátor využívať. Napríklad napíšete Ak
knižnicu pre sčítaní dvoch čísel, jej rozhranie bude napr. Funkcie
double secti(double a, double b)
. Každá knižnica poskytuje
nejaké rozhranie, v opačnom prípade by nebol spôsob ako s ňou pracovať.
To, že môžeme vypisovať do konzoly ako cout << neco
, je
opäť len API, ktoré poskytuje objekt cout
(alebo všeobecnejšie
štandardná knižnica).
Otvorenie súboru pre zápis
Súbor najskôr musíme otvoriť (či vytvoriť, ak neexistuje). To
vykonávame konstruktoru objektu, ktorý prijíma názov súboru ako parameter.
Ak chceme súbor aj vytvoriť, odovzdáme ako druhý parameter konstruktoru
hodnotu ios::out
. Potom zo súboru môžeme čítať alebo do neho
zapisovať. Nasleduje uvoľnenie prostriedkov pomocou metódy
close()
.
Ukážme si prvý príklad, ktorý otvorí súbor pre zápis:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream soubor("zapis.txt", ios::out);
// operace se souborem
//nemusi byt volano, bude zavolano po destruovani objektu
soubor.close();
cin.get();
return 0;
}
Ak by sme objekt neuzavreli, súbor zostane v držaní
aplikácie. V takom prípade vám operačný systém neumožní so súborom
manipulovať (napríklad ho zmazať), dokiaľ nie je súbor uvoľnený. Jedná
sa o klasickú hlášku "Súbor sa nedá zmazať, pretože je používaný iným
procesom". Treba podotknúť, že trieda fstream
vykonáva
uzavretie automaticky v destruktor, metódu close()
je teda nutné
volať len pre objekty, ktorých platnosť je dlhšia ako je ich použitie
(napríklad na začiatku metódy main()
).
Zápis do súboru
Vieme otvoriť už existujúci súbor pre zápis. A ako že do neho vlastne
zapisovať? Prekvapivo rovnako, ako sme to robili s objektom cout
.
Objekt cout
je totiž podobne ako trieda fstream
iba
prúd. Poďme teda vypísať do súboru text "Hello World":
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream soubor("zapis.txt", ios::out);
soubor << "Hello World" << endl;
//nemusi byt volano, bude zavolano po destruovani objektu
soubor.close();
cin.get();
return 0;
}
A ak chceme sa súboru niečo prečítať? Opäť použijeme to, čo už
poznáme. Čítanie prebieha rovnako ako pri objekte cin
, ktorý je
(prekvapivo) opäť iba prúd. Načítate text, ktorý sme do súboru zapísali
výšky:
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { fstream soubor("zapis.txt"); string slovo; soubor >> slovo; cout << slovo << endl; //nemusi byt volano, bude zavolano po destruovani objektu soubor.close(); cin.get(); return 0; }
Hello World
Teraz už vieme základné operácie. To ale nie je všetko. Prúdy toho vie oveľa viac a teraz si predstavíme ich základné metódy.
Neformátovaný vstup a výstup
Všetko, čo sme zatiaľ vykonávali, sme robili na tzv. Formátovanom
vstupu, resp. výstupu. Prečo hovoríme o formátovanom? Ak sme chceli
vypísať napríklad číslo, len sme do cout
poslali
int
a cout
sa už postaral o správny prevod čísla
na text. Inými slovami nami odovzdaná dáta naformátovali do
čitateľnej podoby. Niekedy kvôli výkonu chceme formátovanie preskočiť a
vypísať iba text. O to sa stará práve neformátovaný vstup, resp.
výstup.
Neformátovaný výstup
Pre výstup sú to dve metódy, put()
a write()
.
Tá prvá odovzdá na výstup len jeden znak, zatiaľ čo metóda
write()
vypíše pole. Prečo by sme chceli používať
neformátovaný výstup? Pretože je rýchlejší ako výstup formátovaný. Pre
neformátovaný výstup neprebieha žiadna konverzia, žiadna kontrola, obsah
poľa je iba skopírovaný.
Neformátovaný vstup
Pre neformátovaný vstup je metód viac. Základom je metóda
get()
, ktorá zo vstupu odoberie jeden znak. Existuje aj
preťaženie, ktoré zo vstupu odoberie znakov niekoľko.
int get()
vráti znak zo vstupu.istream& get (char& c)
vráti znak zo vstupu argumentom.istream& get (char* s, streamsize n)
vráti až n-1 znakov zo vstupu a uloží ich do poľas
. Funkcia skončí po prečítaní n-1 znakov alebo po výskyte znaku\n
na vstupe (ten vo výstupe zostáva). Na koniec poľa sa automaticky pridá znak\0
a jedná sa teda o plnohodnotný C-like reťazec (preto číta max n-1 znakov).istream& get (char* s, streamsize n, char delim)
rovnaký ako predchádzajúci prípad, iba možno namiesto ukončovacieho symbolu\n
zvoliť čokoľvek iného.istream& get (streambuf& sb)
prečíta vstup až po znak\n
a vloží ho do streambufferu (viď. ďalší lekcie).istream& get (streambuf& sb, char delim)
prečíta vstup až po znakdelim
a vloží ho do streambuferu (viď. ďalší lekcie).
Vstup má ďalej metódy read()
a getline()
, ktoré
fungujú takmer rovnako ako tretí prípad metódy get()
. Metóda
read()
len prečíta n znakov alebo zlyhá (nepridáva
\0
znak). Metóda getline()
prečíta maximálne
n-1 znakov, pridá \0
symbol a ukončovacie symbol
delim
odstráni zo vstupu, ak na neho narazila. K tomu, ako vstup
môže zlyhať, sa dostaneme niekedy inokedy.
Nahliadanie
Okrem samotného čítania môžete na znaky iba nazerať, a to metódou
peek()
. Tá vráti ďalší znak zo vstupu, ale neodstráni ho
(takže zostane pre ďalšie čítanie k dispozícii).
Pridávanie
Na výstup môžete dokonca aj znaky pridávať, a to metódou
putback()
. Tá vloží jeden znak do fronty na čítanie. To sa
samozrejme neprejaví v konzole (text, ktorý užívateľ napísal, tam
zostane), ale pri ďalšom čítaní bude znak prečítaný.
Ignorovanie
Nakoniec tu máme metódy pre ignorovanie vstupu, tj. Metódu
ignore()
. Tá je deklarovaná ako
istream& ignore (streamsize n = 1, int delim = EOF)
. Parameter
n
udáva počet znakov na odstránenie (-1 znamená bez limitu) a
delim
definuje znak, kde sa má metóda zastaviť. Konštanta
EOF
označuje koniec súboru (taktiež bude prebrané ďalej).
Nasledujúci program demonštruje základné neformátované operácie:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
//{
fstream zapis("soubor.txt", ios::out);
zapis.write("Hello World", 11);
zapis.put('!');
zapis.put('?');
zapis.close();
//}
fstream cteni("soubor.txt", ios::in);
cout << "Dalsi znak: " << (char)cteni.peek() << endl;
char prvni = (char)cteni.get();
char druhy = (char)cteni.get();
cout << "Po znaku " << prvni << " je znak " << druhy << endl;
char zbytekHello[4];
cteni.get(zbytekHello, 4, ' ');
cout << "Zbytek slova je " << zbytekHello << endl;
cteni.ignore(999, 'l');
cout << "Dalsi znak po prvnim ignorovani: " << (char)cteni.peek() << endl;
cteni.ignore(1);
char zbytekRadku[3];
cteni.getline(zbytekRadku, 3);
cout << "Po ignorovani je zbytek radku " << zbytekRadku << endl;
cteni.close();
cin.get();
return 0;
}
Všimnite si explicitného uzatvorenie prvého súboru. Pokiaľ by prvý
objekt nebol uzavretý, druhý by z neho nemohol čítať, pretože súbor
môže byť otvorený vždy iba raz. Avšak ak by bol prvý blok kódu uzavretý
do zložených zátvoriek, potom sa na ich konci zavolá deštruktor objektu
zapis
a tak môžeme volanie metódy close()
vynechať.
Kompletné výklad, čo druhý parameter konstruktoru ios::out
robí, si necháme na niekedy inokedy.
Tým máme základy za sebou. Nabudúce, v lekcii Typy súborov a správne umiestnenie súborov v C ++ , sa pozrieme na rôzne typy súborov a ako s nimi nakladať.