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.

Andrei Badea