Práce s binárními soubory a pohyb v souborech
Textové soubory mají tu výhodu, že je jsou zapisovány (jak čísla, tak text) v ASCII znacích, tedy čitelně pro člověka. Navíc funkce pro práci se soubory pracující v textovém režimu „rozumí“ speciálnímu znaku pro konec řádky '\n'. To pro binární soubory neplatí. Funkce probírané v kapitole o textových souborech vám umožňovali například zapsat nebo načíst číslo, řádku, slovo atp. uložené v ASCII znacích. Naproti tomu v binárních souborech se ukládají data tak, jak je ukládáte v proměnných.
Nic vám nebrání mít v
binárních souborech uloženy jen ASCII znaky. Záleží jen na úhlu
pohledu, zda budete považovat soubor za textový, nebo binární.
Říkám to proto, abyste si uvědomili, že zde probírané funkce
(např. fread()
a fwrite()
) je možné beze všeho použít i na textové
soubory, ale funkce vytvořené pro čtení z textových souborů (tedy
např. fscanf()
) očekávají textový formát
souboru (tedy ASCII znaky), a proto není dvakrát
vhodné je používat na binární soubory.
Vše popisované v této části je použitelné jak pro textové, tak binární soubory. Někde je to jen vhodnější a někde méně.
Čtení a zápis binárního souboru
Otevření a uzavření binárního souboru je
stejné jako textového, jen s tím rozdílem, že se v druhém argumentu
funkce fopen() místo příznaku
t
napíše b
.
Vše ostatní (včetně např. ošetřování chyb) je stejné.
V Linuxu dokonce žádný rozdíl mezi textovým a binárním přístupem k souboru
není (znak nové řádky je jeden znak). Přesto, kvůli přenositelnosti, je dobré příznak
b
nebo t
používat.
Zápis pomocí fwrite()
Funkce fwrite()
slouží k
zápisu dat do souborového proudu. Její deklarace je následující:
Použití této funkce je snazší, než se na první pohled zdá.
Prvním argumentem je ukazatel ptr,
který ukazuje na začátek
místa v paměti, odkud se budou brát data pro zápis do souboru. Může
to být ukazatel na strukturu, řetězec, nebo číslo atp. Druhý
argument, size, je velikost položky v bajtech,
kterou budete zapisovat (může to být např. sizeof(struktura)
).
Třetím argumentem je nmemb, což je počet
položek, které zapíšete. Celkem se tedy
zapíše nmemb * size
bajtů.
A čtvrtým argumentem není
nic jiného než datový proud stream, do kterého se data zapisují.
Návratovou hodnotou je počet zapsaných položek, v ideálním
případě je to rovno nmemb.
V případě chyby je nastavena globální proměnná errno
.
Čtení pomocí fread()
Funkce fread()
slouží ke čtení
dat ze souborového proudu. Její deklarace je následující:
Funkce fread()
načte
nmemb dat o velikosti size ze souboru
stream do paměti kam ukazuje
ptr.
Návratovou hodnotou je počet správně načtených položek, v ideálním případě nmemb. Pokud se nenačtou všechny požadované položky, pak došlo k chybě, nebo se funkce dostala na konec souboru. To lze zjistit pomocí funkcí feof() a ferror().
Příklad čtení a zápisu
V příkladu zapíši do souboru strukturu data
a pak ji znovu
načtu a vypíšu.
Všimněte si velikosti vytvořeného souboru.
Uvidíte, že velikost odpovídá velikosti pěti zapsaných struktur.
Pole text, které je položkou
struktury data
se zapíše celé, jako pole sta znaků,
a ne jenom textový řetězec v položce text
.
- /*------------------------------------------------*/
- /* c33/binar.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <errno.h>
- #include <stdio.h>
- } data;
- #define NAZEV "bsoub.dat"
- {
- FILE *soubor;
- /* pole obsahuje 5 struktur typu data */
- data pole[] = {
- { {1.1, 1.2, 1.3, 1.4, 1.5}, "prvni struktura"},
- { {2.1, 2.2, 2.3, 2.4, 2.5}, "druha struktura"},
- { {3.1, 3.2, 3.3, 3.4, 3.5}, "treti struktura"},
- { {4.1, 4.2, 4.3, 4.4, 4.5}, "ctvrta struktura"},
- { {5.1, 5.2, 5.3, 5.4, 5.5}, "pata struktura"}
- };
- data xdata;
- size_t navrat;
- size_t i;
- /* vite co znamena "w" ? */
- return errno;
- }
- /* zapis dat do souboru - nejdriv prvnich tri a pak zbylych dvou */
- navrat);
- return errno;
- }
- return errno;
- }
- /* soubor pred ctenim uzavru a pak zase otevru */
- return errno;
- }
- /* nyni data nactu a vypisu */
- do {
- "z jedne polozky spravne\n", navrat);
- }
- }
- /* vypasni struktury data */
- }
- /*------------------------------------------------*/
Makro _CRT_SECURE_NO_WARNINGS
je tu kvůli funkci fopen()
,
viz scanf().
Výstup z programu:
1.1 1.2 1.3 1.4 1.5 prvni struktura 2.1 2.2 2.3 2.4 2.5 druha struktura 3.1 3.2 3.3 3.4 3.5 treti struktura 4.1 4.2 4.3 4.4 4.5 ctvrta struktura 5.1 5.2 5.3 5.4 5.5 pata struktura Konec souboru!
Všimněte si, že po přečtení páté struktury ještě funkce
feof()
nevratcí hodnotu „true“, ale vrací ji až poté, co se pokouším
načíst strukturu šestou, která již v souboru neexistuje. Příznak
konce souboru je tedy nastaven až poté, co se pokusíte načíst více
dat, než je v souboru a ne ve chvíli, kdy přečtete poslední
existující data ze souboru. Je to možná ošklivé, ale je to tak.
Pozice v souboru
Představte si soubor o velikosti 10 bajtů. Když takový soubor
otevřete pro čtení, ukazuje souborový indikátor pozice
(dále jen kurzor) na začátek souboru (tedy na „nultý“ bajt). Pokud
použijete libovolnou funkci na přečtení prvního bajtu, kurzor bude
ukazovat na druhý bajt (bajt číslo 1). Přečtete-li dalších 5 bajtů,
bude ukazovat na šestý bajt atd. Takto můžete přečíst celý soubor
hezky bajt za bajtem. Ale co když chcete přečíst zase začátek
souboru? Zavřít a zase otevřít soubor je dost nepraktická, pomalá věc. A co
když chcete přečíst jenom posledních 5 bajtů souboru? Pokud má
nějaký soubor třeba 1 GiB, máte jej snad celý přečíst
jen kvůli pár bajtům na konci?
Pokud otevřete soubor pro zápis, ukazuje kurzor na konec
souboru. Co když ale chcete zapsat něco doprostřed souboru?
Kvůli výše popsaným situacím vzniklo několik funkcí, které vám
umožní snadno a rychle nastavit kurzor na požadovanou pozici. První
z těchto funkcí je fseek()
.
Funkce fseek()
pracuje se souborovým proudem stream.
Posouvá kurzor do vzdálenosti offset
(tato hodnota může být i záporná!) od místa whence.
Místo whence může být SEEK_SET
(relativní vzdálenost od začátku souboru),
SEEK_CUR
(relativní vzdálenost od aktuální pozice) nebo
SEEK_END
(relativní vzdálenost od konce souboru).
Funkce
vrací v případě úspěchu nulu.
Další používané funkce jsou ftell()
a rewind()
.
Funkce rewind()
posune
kurzor na začátek souboru stream. Je to totéž,
jako (void) fseek(stream, 0L, SEEK_SET)
.
Funkce ftell()
vám zjistí hodnotu
aktuální pozice kurzoru v souboru stream.
Alternativou k předešlým funkcím mohou být funkce
fgetpos()
a
fsetpos()
. Tyto
funkce pracují s objektem typu
fpos_t
, do kterého funkce
fgetpos()
ukládá hodnotu aktuální pozice
kurzoru a fsetpos()
nastavuje
pozici kurzoru podle této proměnné.
Všechny tyto funkce jsou vhodné pro používání jak v textovém, tak binárním přístupu k souboru.
Zjištění velikosti souboru
Nejlepším způsobem jak zjistit velikost souboru je přesunout
kurzor na konec souboru a pak (funkcí ftell()
)
zjistit aktuální pozici kurzoru. V příkladu si vyzkoušíte práci s kurzorem
a zjištění velikosti souboru. Pro jednoduchost
1)
nebudu v programu ošetřovat žádné chyby.
- /*------------------------------------------------*/
- /* c33/ftell1.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <stdio.h>
- #include <stddef.h>
- {
- FILE *soubor;
- size_t i;
- /* nejdrive zapisi do souboru nekolik znaku 'a' */
- /* posunu kurzor za prvnich 5 bajtu */
- /* zapisi 5 znaku '-' */
- /* posunu se na konec souboru a zjistim jeho velikost */
- /* presunu se opet na zacatek souboru a prectu jeho obsah */
- }
- }
- /*------------------------------------------------*/
Výstup z programu:
Velikost souboru je 50 bajtu. aaaaa-----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
1) Pro jednoduchost programu, ne moji :-).