Kurz C++ (8.)
Prßce se soubory
Zßklady
DneÜnφ dφl kurzu C++ je v∞novßn prßci se soubory. ╪φkßm-li C++, dopouÜtφm se dnes
jistΘ mystifikace, my se budeme zatφm uΦit zp∙sob, kter² existoval ji₧ v jazyce
C, p°esn∞ji °eΦeno v jeho run-time knihovn∞. Jazyk C++ p°inesl objekty a
objektov∞ orientovan² p°φstup k soubor∙m jist∞ nemohl chyb∞t, ale my zatφm
objekty neumφme. A₧ se je nauΦφme, vysv∞tlφme si i p°φstup "po C++".
VeÜkerΘ funkce a struktury pot°ebnΘ pro prßci se soubory se nachßzφ v
hlaviΦkovΘm souboru <stdio.h> , nezapomφnejte ho poka₧dΘ
vklßdat. Funkce v n∞m deklarovanΘ pou₧φvajφ tzv. proudy. Proud
si m∙₧ete p°edstavit jako n∞jakou datovou strukturu, kterß slou₧φ k p°φstup k
obsahu souboru a provßd∞nφ vÜech mo₧n²ch operacφ. TakΘ udr₧uje aktußlnφ polohu
v souboru (aktußlnφ poloha se m∞nφ Φtenφm ze souboru a zßpisem do n∞j), a takΘ
poskytuje vyrovnßvacφ pam∞ti (to mß za ·kol zrychlovat p°φstup k souboru,
trochu vφce si o tom vysv∞tlφme dßle). JeÜt∞ bych cht∞l podotknout, ₧e pro plnΘ
pochopenφ lßtky bude pot°eba pracovat s nßpov∞dou. Funkcφ pro prßci se soubory
je mnoho a jejich mo₧nosti skuteΦn∞ rozsßhlΘ, proto nenφ zde mφsto popsat vÜe
(to by vydalo na knihu).
Otev°enφ a zav°enφ
Na zaΦßtku prßce se souborem je t°eba ho otev°φt (tφm se vytvo°φ proud) a k tomu
slou₧φ funkce fopen() , kterß je deklarovanß takto:
FILE *fopen(const char *filename, const char *mode);
To znamenß, ₧e funkce fopen() dostßvß jako parametry dva °et∞zce:
prvnφ urΦuje cestu k souboru (m∙₧e b²t relativnφ i absolutnφ), a druh² urΦuje,
jak se mß soubor otev°φt (zda jen pro Φtenφ, nebo jen pro zßpis, pop°. pro
Φtenφ i zßpis najednou, a dßle zda se mß vytvo°it nov² soubor pokud cesta
zadanß prvnφ parametrem neexistuje - vÜe si vysv∞tlφme). Velice d∙le₧itß je
nßvratovß hodnota funkce fopen() , je to ukazatel na prom∞nnou typu
FILE (struktura FILE je takΘ deklarovanß v <stdio.h> ).
Tato prom∞nnß je alokovßna n∞kde v ·trobφ run-time knihovny a my dostßvßme
pouze ukazatel, kter² si musφme n∞kam uschovat, proto₧e pomocφ n∞ho budeme
provßd∞t veÜkerΘ dalÜφ operace se souborem. Dßle hned po zavolßnφ funkce fopen()
je t°eba testovat vracen² ukazatel, zda nenφ nulov² - to by znamenalo, ₧e doÜlo
k n∞jakΘ chyb∞ a soubor se nepoda°ilo otev°φt:
FILE *proud;
proud = fopen("soubor.txt", "r");
if (!proud)
cout << "Soubor se nepodarilo otevrit";
Pou₧ili jsme pro parametr mode hodnotu "r" , kterß
znamenß, ₧e soubor mß b²t otev°en pouze pro Φtenφ. DalÜφ mo₧nΘ hodnoty jsou:
r |
pouze Φtenφ |
w |
pouze zßpis (p°epφÜe existujφcφ soubor, vytvo°φ nov² pokud soubor neexistuje) |
a |
zßpis pouze na konec souboru (p°idßvßnφ) |
r+ |
Φtenφ i zßpis |
w+ |
jako w, ale i Φtenφ |
a+ |
jako a, ale i Φtenφ |
Na konec °et∞zce mode je mo₧nΘ p°idat jeden z t∞chto znak∙:
t |
textov² m≤d - p°i Φtenφ p°eklßdß znakovΘ kombinace CR/LF (znaky s k≤dy 13/10
znamenajφcφ konec °ßdku) na LF (znak s k≤dem 10 odpovφdajφcφ escape-sekvenci
\n),
p°i zßpisu p°ekladß LF na CR/LF - tφm je zjednoduÜena kontrola konc∙ °ßdk∙ v
textovΘm souboru (nenφ pot°eba testovat dva znaky za sebou, ale pouze jeden)
|
b |
binßrnφ m≤d - neprobφhß ₧ßdn² p°eklad |
Textov² m≤d pou₧ijeme pokud zpracovßvßme textovΘ soubory, ve vÜech jin²ch
p°φpadech pou₧ijeme binßrnφ m≤d. Pokud se neuvede ani b ani t platφ textov²
m≤d, ale toto lze takΘ m∞nit nastavenφ globßlnφ prom∞nnΘ _fmode ,
deklarovanΘ v hlaviΦkovΘm souboru <stdlib.h> .
Poznßmka: uvedenφ znaku b bude urΦit∞ fungovat ve Visual C++, ale nemusφ
fungovat v jin²ch p°ekladaΦφch nebo na jin²ch operaΦnφch systΘmech. Myslφm, ₧e
norma uvßdφ pouze znak t, ale nemohu to zjistit proto₧e ji nemßm k dispozici
(nedß se sehnat v elektronickΘ podob∞).
Na konci prßce se souborem je pot°eba ho uzav°φt. Pokud ho zapomeneme uzav°φt,
run-time knihovna ho uzav°e za nßs, ale s tφm nesmφme poΦφtat, uklφzet po sob∞
pat°φ k zßkladnφm programßtorsk²m zvyk∙m. Soubor se zavφrß funkcφ fclose() :
int fclose(FILE *stream);
Funkce vracφ 0 pokud uzav°enφ prob∞hlo v po°ßdku nebo konstantu
EOF pokud doÜlo
k n∞jakΘ chyb∞.
╚tenφ ze souboru
Jazyk C poskytuje mnoho mo₧nostφ Φtenφ ze souboru. Je mo₧nΘ Φφst jednotlivΘ
znaky, °ßdky, nebo i libovoln² poΦet znak∙ najednou. Nap°φklad:
int fgetc(FILE *stream);
P°ecte jedin² znak, vracφ EOF v p°φpad∞ chyby nebo konce souboru.
char *fgets(char *string, int n, FILE *stream);
P°eΦte cel² °ßdek (do nalezenφ znaku \n ) vΦetn∞ \n ,
ale ne vφce ne₧ n znak∙ (vΦetn∞ nulovΘho ukonΦovacφho, tak₧e se ze
souboru p°eΦte pouze nejv²Üe n - 1 znak∙).
Tyto dv∞ funkce jsou spφÜe vhodnΘ pro textovΘ soubory. Pro binßrnφ soubory (kde
chceme Φφst urΦit² poΦet znak∙ najednou) se hodφ funkce fread() :
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
Tato funkce p°eΦte z proudu stream count polo₧ek
velikosti size , kterΘ ulo₧φ do pam∞ti na mφsto, kam ukazuje
ukazatel buffer (vÜimn∞te si, ₧e je typu void , tak₧e
tφmto parametrem m∙₧ete p°edßvat ukazatel na jak²koli datov² typ). Funkce vracφ
poΦet skuteΦn∞ p°eΦten²ch polo₧ek (toto Φφslo m∙₧e b²t menÜφ nez count
v p°φpad∞ chyby).
Nynφ si ukß₧eme mal² p°φklad: chceme spoΦφtat poΦet °ßdk∙ v textovΘm souboru.
Nabφzφ se mo₧nost p°eΦφst cel² soubor znak po znaku a poΦφtat znaky \n :
#include <stdio.h>
#include <iostream.h>
void main(int argc, char *argv[]) {
FILE *proud;
char ch;
unsigned radky =
0; if (argc
< 2) { cout
<< "Zadejte soubor jako parametr.\n";
return;
}
// otevrenφ souboru v textovΘm m≤du pro Φtenφ
proud = fopen(argv[1], "rb");
if (!proud)
return;
// p°eΦtenφ prvnφho znaku
ch = fgetc(proud);
// dokud nenφ konec souboru
while (ch != EOF) {
if (ch == '\n')
radky++;
// Φteme dalÜφ znak
ch = fgetc(proud);
}
cout << "Pocet radku: " << radky + 1 << endl;
}
Zßpis do souboru
Funkce pro zßpis do souboru majφ podobnß jmΘna jako ty pro Φtenφ, ale mφsto
"get" se pou₧φvß "put" a mφsto "read" je "write".
int fputc(int c, FILE *stream);
ZapφÜe znak c a vracφ hodnotu c v p°φpad∞ ·sp∞chu, EOF
jinak.
int fputs(const char *string, FILE *stream);
ZapφÜe do souboru °et∞zec string
(bez ukonΦovacφho znaku), vracφ kladnou hodnotu v p°φpad∞ ·sp∞chu a
jinak EOF.
Ekvivalent funkce fread() je funkce fwrite() :
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
Tato funkce zapφÜe do proudu stream count polo₧ek
velikosti size , kterΘ p°eΦte z pam∞ti z adresy buffer .
Nßvratovß hodnota je poΦet zapsan²ch polo₧ek (pozor, ne byt∙).
Vyrovnavacφ pam∞ti
Velkou v²hodou proud∙ je ₧e veÜkerΘ Φtecφ a zapisovacφ operace probφhajφ p°es
vyrovnßvacφ pam∞ti. V p°φpad∞ Φtenφ to vypadß tak, ₧e v okam₧iku kdy je vy₧ßdßn
prvnφ znak souboru je toho p°eΦteno vφce (nap°. dokumentace VC++ 6.0 uvßdφ 4
KB), a dalÜφ Φtenφ prob∞hne pouze z vyrovnßvacφ pam∞tφ, bez p°φstupu na disk
(ten se samoz°ejm∞ provede, kdy₧ u₧ nejsou dalÜφ data ve vyrovnßvacφ pam∞ti).
Podobn∞ zßpis se provßdφ do vyrovnßvacφ pam∞ti, kterß se zapφÜe na disk a₧ v
okam₧iku napln∞nφ. P°φnos vyrovnßvacφch pam∞tφ je mnohonßsobn∞ rychlejÜφ Φtenφ
i zßpis. Pokud si to chcete vyzkouÜet, p°idejte v naÜem ΦφtaΦi °ßdk∙ hned
pod volanφ fopen () toto:
setvbuf(proud, 0, _IONBF, 0);
Tφm se vypφnajφ vyrovnßvacφ pam∞ti. Zkuste spustit program na n∞jak² v∞tÜφ
soubor (alespo≥ 1 MB) a uvidφte rozdφl.
DalÜφ funkce
Pro skuteΦn∞ efektivnφ prßci se soubory si nevystaΦφme pouze s funkcemi pro
zßpis a Φtenφ. Existujφ dalÜφ funkce jako nap°φklad:
int fflush(FILE *stream);
Slou₧φ k vyprßzdn∞nφ vyrovnßvacφch pam∞tφ. U proud∙ otev°en²ch pro Φtenφ
vyprßzdnφ vyrovnßvacφ pam∞¥, u proudu otev°en²ch pro zßpis provede totΘ₧, ale
p°edtφm zapφÜe obsah vyrovnßvacφ pam∞ti na disk. Tato funkce je obzvlßÜ¥
u₧iteΦnß u soubor∙ otev°en²ch pro Φtenφ i zßpis, proto₧e mezi p°epφnßnφm mezi
Φtenφm a zßpisem musφme p°idat volßnφ funkce fflush()
(z d∙vodu pou₧itφ vyrovnßvacφ pam∞ti).
long ftell(FILE *stream);
Vracφ aktußlnφ polohu v souboru (je to celoΦφselnß hodnota kterß udßvß na jakΘm
mφst∞ v souboru prob∞hne dalÜφ operace Φtenφ nebo zßpisu - hned po otev°enφ mß
hodnotu nula a zvyÜuje se s ka₧dou operacφ Φtenφ nebo zßpisu).
int fseek(FILE *stream, long offset, int origin);
Nastavuje aktußlnφ polohu v souboru na offset byt∙ od mφsta,
udßvanΘho parametrem origin . Parametr origin nab²vß hodnot: SEEK_CUR
(aktußlnφ poloha), SEEK_END (konec souboru), SEEK_SET
(konec souboru). Nap°φklad
// nastavujeme polohu 10 byt∙ od zaΦßtku souboru
fseek(proud, 10, SEEK_SET);
// nastavujeme polohu 10 bytu od konce souboru (pozor na zßpornou hodnotu!)
fseet(proud, -10, SEEK_END);
Funkce fseek() vracφ 0 v p°φpad∞ ·sp∞chu a
jinak jinou hodnotu.
Bohu₧el funkce fseek() mß omezenou pou₧itelnost u soubor∙
otev°en²ch v textovΘm m≤du û lze ji pou₧φt pouze volßnφ s origin = SEEK_SET
a navφc parametr offset musφ obsahovat hodnotu vracenou funkcφ ftell() .
void rewind(FILE *stream);
Nastavφ aktußlnφ polohu v souboru na jeho zaΦßtek.
int fscanf(FILE *stream, const char *format [, argument ]...);
int fprintf(FILE *stream, const char *format [, argument ]...);
Tyto funkce jsou obdobnΘ funkcφm scanf() a printf() .
Specißlnφ proudy
Run-time knihovna definuje takzvanΘ standardnφ proudy, kterΘ jsou p°i spuÜt∞nφ
programu automaticky otev°eny. Jsou definovßny t°i standardy proudy, kterΘ
m∙₧ete pou₧φt v programech podobn∞ jako jsme v²Üe pou₧ili nap°. prom∞nnou proud :
stdin |
je pevn∞ nastaven na tzv. standardnφ vstup, co₧ je v∞tÜinou klßvesnice |
stdout |
je pevn∞ nastaven na standardnφ v²stup, co₧ b²vß monitor |
stderr |
jednß se o tzv. standardnφ chybov² v²stup, kter² je urΦen pro vypisovßnφ chyb,
a v∞tÜinou je takΘ p°esm∞rovßn na monitor |
To znamenß, ₧e budete-li Φφst ze stdin , provßdφte vlastn∞ Φtenφ
vstupu z klßvesnice, a obdobn∞ zßpis na stdout znamenß zßpis na
obrazovku.
P°φklad
Jako v∞tÜφ p°φklad si dnes doplnφme adresß°, kter² jsme napsali minule, o prßci
se soubory. Jmenovit∞ budeme pot°ebovat dv∞ funkce, nazv∞me je uloz()
a otevri() .
Uklßdat budeme celΘ struktury Osoba , a to p°esn∞ tolik, kolik je
hodnota osoby.pocet . Ale abychom mohli pak ulo₧en² soubor nahrßt
zpßtky, pot°ebujeme v∞d∞t, kolik osob v souboru vlastn∞ je. Proto jako prvnφ
ulo₧φme hodnotu prom∞nnΘ osoby.pocet a to binßrn∞, tzn. ulo₧φme 4
byty, kterΘ tuto prom∞nnou tvo°φ. Dßle budeme muset poΦφtat s chybami p°i
zßpisu. Pokud se nepoda°φ otev°φt soubor pro zßpis, prost∞ vracφme false .
Ale v p°φpad∞, ₧e jsme soubor ·sp∞Ün∞ otev°eli, a dojde k chyb∞ p°i zßpisu,
bude t°eba ho uzav°φt a vymazat. Proto si vytvo°φme jeÜt∞
jednu funkci, chybaUlozeni() , kterß tyto Φinnosti provede:
const char *strModUloz = "wb";
bool uloz(Osoby &osoby, char *soubor) {
FILE *fp;
fp = fopen(soubor, strModUloz);
if (!fp)
return false;
// zapisujeme poΦet osob
if (fwrite(&osoby.pocet, sizeof(osoby.pocet), 1, fp) != 1)
return chybaUlozeni(fp, soubor);
// zapisujeme struktury Osoba
// p°etypovani je nutnΘ kv∙li tomu, ₧e osoby.pocet je unsigned,
// ale nßvratovß hodnota fwrite je int
if (fwrite(osoby.pole, sizeof(*osoby.pole), osoby.pocet, fp) != (size_t)osoby.pocet)
return chybaUlozeni(fp, soubor);
fclose(fp);
return true;
}
bool chybaUlozeni(FILE *fp, char *soubor) {
fclose(fp);
// funkce remove se take nachazi v <stdio.h>
remove(soubor);
return false;
}
Otev°enφ provedeme podobn∞, s tφm rozdφlem, ₧e pokud dojde k n∞jakΘ chyb∞,
soubor zav°eme a nastavφme osoby.pocet = 0 :
const char *strModOtevri = "rb";
bool otevri(Osoby &osoby, char *soubor) {
FILE *fp;
osoby.pocet = 0;
fp = fopen(soubor, strModOtevri);
if (!fp)
return false;
// Φteme poΦet osob
if (fread(&osoby.pocet, sizeof(osoby.pocet), 1, fp) != 1)
return chybaOtevreni(fp, osoby);
// pokud je poΦet osob vetÜφ ne₧ zvlßdneme, hlßsφme chybu
if
(osoby.pocet > MAX_POCET_OSOB)
return chybaOtevreni(fp, osoby);
// Φteme osoby
if (fread(osoby.pole, sizeof(*osoby.pole), osoby.pocet, fp) != (size_t)osoby.pocet)
return chybaOtevreni(fp, osoby);
fclose(fp);
return true;
}
bool chybaOtevreni(FILE *fp, Osoby &osoby) {
fclose(fp);
osoby.pocet = 0;
return false;
}
Ob∞ funkce vΦetn∞ jejich za°azenφ do menu najdete v sekci Download.
Zßv∞r
To je pro dneÜek vÜe. Pokud vßm tento v²klad nestaΦil, doporuΦuji, abyste si
p°eΦetli nßpov∞du k uveden²m funkcφm, je tam spousta zajφmav²ch skuteΦnostφ a
souvislostφ. D∞kuji za pozornost a t∞Üφm se na dalÜφ dφl.
|