Proud je prostředek, kterým lze s pomocí standardní knihovny pracovat se soubory a dalšími vstupně-výstupními zařízeními (např. klávesnice, terminál). Existují i další prostředky, například systémová volání nebo jiné, nestandardní, knihovny. Těm se ovšem věnovat nebudeme.
Funkce, o kterých budeme mluvit, mohou skončit chybou. Uvedeme si
jenom, jakým způsobem poznáme, že k chybě došlo a proč k ní mohlo dojít.
Zpracování chyby a to, jak se má program chovat, pokud k chybě dojde, je
mimo rozsah tohoto kurzu. V příkladech budeme možnost, že k chybě dojde
buď ignorovat, nebo použijeme assert.
Proud je objekt, do kterého lze zapisovat nebo z něj číst. Otevřením
souboru jej na proud napojíme a poté zapisování do či čtení z proudu
odpovídá zapisování do souboru či čtení ze souboru. Jsou definovány i
další, tzv. standardní proudy. Pomocí nich můžeme zapisovat na
standardní výstup (tam zapisuje např.printf), na standardní
chybový výstup a číst ze standardního vstupu (odtud čte například
scanf ).
Proud je reprezentován strukturou FILE, v programu
pracujeme s ukazateli na tuto strukturu. Získámi jej pomocí funkce
fopen.
FILE* fopen(char* path, char* mode);První argument je cesta k otevíranému souboru (ta je buď absolutní,
nebo relativní vzhledem k umístění binárky spoušteného programu).
Textový řetězec mode určuje způsob otevření souboru. Jeden
ze znaků mode obvykle udává režim otevření: ten je buď
textový nebo binární. Další znak pak to, co se souborem můžeme dělat:
číst jej, zapisovat do něj, připojit něco na jeho konec, vytvořit jej,
pokud neexistuje, nebo nějaká kombinace předchozího. Nejpoužívanější
možnosti ukážeme níže.
/*
POPIS ZNAKU
'r' otevreno pro cteni
'w' otevreno pro zapis, existujici soubor je prepsan
'a' otevreno pro zapis, pokud soubor existuje, zapisujeme na jeho konec
't' otevreno v textovem rezimu 'b' otevreno v binarnim rezimu
PRIKLADY KOMBINACI
"rt" cteni v textovem rezimu
"wt" zapis v textovem rezimu
"rb" cteni v binarnim rezimu
"wb" zapis v binarnim rezimu
*/Pokud se v řetězci nenachází ani jeden ze znaků tb,
otevře se proud v textovém režimu.
Pokud otevření souboru selže, vrátí fopen 0. Tuto
situaci je potřeba ošetřit, pro účely kurzu se spokojíme s assercí. (Pro
zjištění důvodu lze někdy použít kombinaci errno a
strerror.)
FILE *st = fopen("path/to/file", "t");
assert(st);Nepoužívaný proud se zavírá funkcí fclose.
V textovém režimu čteme a zapisujeme znaky, v binárním režimu bajty.
Ačkoliv se zdá, že je to totéž, rozdíl je například u konce řádku ve
Windows, který končí dvojící bajtů '\r' '\n'.
Ze streamu se čte a zapisuje se do něj pomocí funkcí z
stdio.h. Stream si udržuje místo, ze kterého se bude číst
při příštím zavolání funkce pro čtení. Po přečtení nějakého úseku (např.
znaku, řádku apod.) posune toto místo na první bajt nepřečteného
obsahu.
Pokud se knihovní funkci pro čtení ze streamu čtení nepovede, programátor to pozná z návratové hodnoty, případně může zkontrolovat příznaky streamu pro konec souboru a pro chybu. To se dělá funkcemi
int feof(FILE* stream); // vraci true, pokud jsme na konci souboru
int ferror(FILE* stream); // vraci true, pokud došlo k chyběAnalogické úvahy platí i pro zápis.
Základní funkce pro čtení je
int getc(FILE *stream);Přečte ze streamu jeden znak, který získáme přetypováním návratové
hodnoty na char. Při neúspěšném čtení vrací konstantu
EOF.
Větší část textu lze načíst pomocí funkce fgets (tu si
čtenář dostuduje z referenční příručky).
Základními funkcemi pro zápis jsou
int fputc(int ch, FILE* stream);
int fputs(char *s, FILE* stream);
int fprintf(FILE* stream, char* format, ...), Funkce fputc zapíše do streamu znak, který dostane
přetypováním ch na unsigned int. Funkce
fputs zapíše do streamu řetězec. V případě neúspěchu obě
funkce vracejí EOF a nastaví streamu flag
ferror.
Funkce fprintf funguje jako printf pouze
tiskne do streamu. Při chybě vrací zápornou hodnotu, jinak počet
vytistěných znaků.
Zápis do streamu je bufferovaný, zapsané změny se v napojeném souboru (či zařízení) mohou projevit později. Chceme-li si jejich okamžité projevení, můžeme k tomu použít funkci
int fflush(FILE* stream);V případě neúspěchu vrací EOF. Tato funkce je volána (či
je prováděn kód jí velmi podobný) i při zavření streamu, proto
fclose také může neuspět a vrátit EOF.
Existují tři standardní proudy, přistupné jsou pomocí proměnných
stdin, stdout, stderr.
Jako příklad si uvedeme funkci, která načte obsah souboru na disku a vytiskne jej na standardní výstup.
void print_file(char *filename)
{
FILE *in = fopen(filename, "rt");
assert(in);
int z = 0;
while (1)
{
z = getc(in);
if (z == EOF) // konec souboru nebo chyba cteni
{
if (ferror(in))
{
fprintf(stderr, "chyba cteni\n");
assert(0);
}
break;
}
fputc(z, stdout);
}
fclose(in);
}Čteme a zapisujeme bajty, které při čtení nejsou nijak interpretovány.
Funkce pro čtení
size_t fread(void *dest, size_t element_size, size_t count, FILE *in)Čteme count prvků, každý o velikost
element_size bajtů, ze streamu in, ukládáme je
do pole na adrese dest. Vrací počet přečtených prvků, pokud
bylo čtení bez chyby, pak se count a návratová hodnota
rovnají. Je nutné myslet na to, že na adrese dest musí být
naalokováno dostatek paměti, jinak dojde k nedefinovanému chování.
Funkce pro zápis
size_t fwrite(void *src, size_t element_size, size_t count, FILE *out)do streamu out zapise count položek, každy
o velikosti element_size uložených za sebou na adrese
src. Funkce vrací počet zapsaných položek.
Můžeme také pracovat s akturální pozicí, ze které v souboru čteme.
Pozice je hodnota typu long a je to index bajtu od začátku.
Aktuální pozici můžeme získat pomocí funkce
long ftell(FILE* stream)Aktuální pozici také můžeme nastavit pomocí funkce
int fseek(FILE *stream, long offset, int start)kde offset je počet bajtů, o které se chceme posunout a
start určuje místo, ze kterého se chceme posunout. Pro
tento účel jsou definovány konstanty
SEEK_SET zacatek souboru
SEEK_CUR aktualni pozice
SEEK_END konec souboruV následujícím příkladu zapíšeme obsah pole do souboru a zase je načteme.
char *filename = "tmp.xx";
// zapis do souboru
float out_array[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
FILE *out = fopen(filename, "wb");
assert(out);
size_t written = fwrite(out_array, sizeof(float), 4, out);
assert(written == 4);
fclose(out);
// cteni ze souboru, od druheho prvku
FILE *in = fopen(filename, "rb");
// posuneme se o jeden float dopredu
int fail = fseek(in, sizeof(float), SEEK_SET);
assert(!fail);
float in_array[3]= { 0.0f, 0.0f, 0.0f };
size_t read = fread(in_array, sizeof(float), 3, in);
assert(read == 3);
fclose(in);
for(int i = 0; i < 3; i += 1) {
printf("%f ", in_array[i]);
}
printf("\n");Práce s binárními soubory a čtení binárních formátů má svá specifika, musíme si dávat pozor na velikosti typů, endianitu apod.
Čteme a zapisujeme bajty, které při čtení nejsou nijak interpretovány.
Funkce pro čtení
size_t fread(void *dest, size_t element_size, size_t count, FILE *in)Čteme count prvků, každý o velikost
element_size bajtů, ze streamu in, ukládáme je
do pole na adrese dest. Vrací počet přečtených prvků, pokud
bylo čtení bez chyby, pak se count a návratová hodnota
rovnají. Je nutné myslet na to, že na adrese dest musí být
naalokováno dostatek paměti, jinak dojde k nedefinovanému chování.
Funkce pro zápis
size_t fwrite(void *src, size_t element_size, size_t count, FILE *out)do streamu out zapise count položek, každy
o velikosti element_size uložených za sebou na adrese
src. Funkce vrací počet zapsaných položek.
Můžeme také pracovat s akturální pozicí, ze které v souboru čteme.
Pozice je hodnota typu long a je to index bajtu od začátku.
Aktuální pozici můžeme získat pomocí funkce
long ftell(FILE* stream)Aktuální pozici také můžeme nastavit pomocí funkce
int fseek(FILE *stream, long offset, int start)kde offset je počet bajtů, o které se chceme posunout a
start určuje místo, ze kterého se chceme posunout. Pro
tento účel jsou definovány konstanty
SEEK_SET zacatek souboru
SEEK_CUR aktualni pozice
SEEK_END konec souboruV následujícím příkladu zapíšeme do souboru a zase načteme pole.
char *filename = "tmp.xx";
// zapis do souboru
float out_array[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
FILE *out = fopen(filename, "wb");
assert(out);
size_t written = fwrite(out_array, sizeof(float), 4, out);
assert(written == 4);
fclose(out);
// cteni ze souboru, od druheho prvku
FILE *in = fopen(filename, "rb");
// posuneme se o jeden float dopredu
int fail = fseek(in, sizeof(float), SEEK_SET);
assert(!fail);
float in_array[3]= { 0.0f, 0.0f, 0.0f };
size_t read = fread(in_array, sizeof(float), 3, in);
assert(read == 3);
fclose(in);
for(int i = 0; i < 3; i += 1) {
printf("%f ", in_array[i]);
}
printf("\n");Práce s binárními soubory a čtení binárních formátů má svá specifika, musíme si dávat pozor na velikosti typů, endianitu apod.