| ← textové soubory | C/C++ | adresářová struktura → |
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 rozumí speciálnímu znaku pro konec řádky '\n'. To pro binární soubory neplatí. Funkce probírané v kapitole o textových souborech ná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 budeme ukládat data tak, jak je ukládáme v proměnných. Nic nám však nebrání mít v binárních souborech uloženo vše v ASCII znacích. Záleží jen na úhlu pohledu, zda budeme považovat soubor za textový, nebo binární. Říkám to proto, aby jste si uvědomily, ž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ř. fgets()) očekávají textový formát souboru (tedy ASCII znaky), a proto není dvakrát vhodné je používat na binární soubory (přesto že je to možné). Druhá část této kapitoly je věnována posunu ukazatele do souboru.
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ě.
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í. Přesto, kvůli přenositelnosti, je dobré příznak b nebo t použít.
Funkce fwrite() slouží k zápisu dat do souborového proudu. Její deklarace je následující:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
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, kterou budeme zapisovat v bajtech (může to být např. sizeof(struktura)). Třetím argumentem je nmemb, což je počet položek, které zapíšeme. 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.
Funkce fread() slouží ke čtení dat ze souborového proudu. Její deklarace je následující:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
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().
V příkladu zapíšeme do souboru strukturu data a pak ji znovu načteme a vypíšeme.
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.
1: /*------------------------------------------------*/ 2: /* binar.c */ 4: #include <errno.h> 5: #include <stdio.h> 7: typedef struct { 8: float cisla[5]; 9: char text[100]; 10: } data; 12: #define NAZEV "bsoub.dat" 14: int main(void) 15: { 16: FILE *soubor; 17: /* pole obsahuje 5 struktur typu data */ 18: data pole[] = { {{1.1, 1.2, 1.3, 1.4, 1.5}, "prvni struktura"}, 19: {{2.1, 2.2, 2.3, 2.4, 2.5}, "druha struktura"}, 20: {{3.1, 3.2, 3.3, 3.4, 3.5}, "treti struktura"}, 21: {{4.1, 4.2, 4.3, 4.4, 4.5}, "ctvrta struktura"}, 22: {{5.1, 5.2, 5.3, 5.4, 5.5}, "pata struktura"} 23: }; 24: data xdata; 25: size_t navrat; 26: unsigned int i; 28: if ((soubor = fopen(NAZEV, "wb")) == NULL) { 29: /* vite co znamena "w" ? */ 30: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 31: perror(NULL); 32: return errno; 33: } 35: /* zapis dat do souboru - nejdriv prvnich tri a pak zbylych dvou */ 37: navrat = fwrite(pole, sizeof(data), 3, soubor); 38: if (navrat != 3) { 39: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 40: perror(NULL); 41: if (EOF != fclose(soubor)) 42: fprintf(stderr, "S jistotou bylo zapsano %i polozek\n", 43: navrat); 44: else 45: perror(NULL); 46: return errno; 47: } 49: navrat = fwrite(pole + 3, sizeof(data) * 2, 1, soubor); 50: if (navrat != 1) { 51: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 52: perror(NULL); 53: fclose(soubor); 54: fprintf(stderr, "Neni znam pocet zapsanych polozek\n"); 55: return errno; 56: } 58: /* soubor pred ctenim uzavreme a pak zase otevreme */ 59: if (fclose(soubor) == EOF) 60: perror(NULL); 62: if ((soubor = fopen(NAZEV, "rb")) == NULL) { 63: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 64: perror(NULL); 65: return errno; 66: } 68: /* nyni data nacteme a vypiseme */ 69: do { 70: navrat = fread(&xdata, 1, sizeof(xdata), soubor); 71: if (navrat != sizeof(xdata)) { 72: if (feof(soubor)) { 73: printf("Konec souboru!\n"); 74: break; 75: } else { 76: perror(NULL); 77: fprintf(stderr, "Bylo nacteno pouze %i bajtu " 78: "z jedne polozky spravne\n", navrat); 79: break; 80: } 81: } 83: /* vypasni jedne struktury */ 84: for (i = 0; i < sizeof(xdata.cisla) / sizeof(xdata.cisla[0]); i++) 85: printf("%3.1f ", xdata.cisla[i]); 86: printf("%s\n", xdata.text); 88: } while (!feof(soubor)); 90: if (fclose(soubor) == EOF) 91: perror(NULL); 93: return 0; 94: } 96: /*------------------------------------------------*/
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šíme načíst strukturu šestou, která již v souboru neexistuje. Příznak konce souboru je tedy nastaven až poté, co se pokoušíme načíst více dat, než je v souboru a ne ve chvíli, kdy přečteme poslední existující data ze souboru. Je to možná ošklivé, ale je to tak.
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 1GB, máme 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 problémů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().
int fseek(FILE *stream, long offset, int whence);
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().
long ftell(FILE *stream); void rewind(FILE *stream);
Funkce rewind() posune
kurzor na začátek souboru stream. Je to totéž,
jako (void) fseek(stream, 0L, SEEK_SET).
Funkce ftell() vrací 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é.
int fgetpos(FILE *stream, fpos_t *pos); int fsetpos(FILE *stream, fpos_t *pos);
Všechny tyto funkce jsou vhodné pro používání jak v textovém, tak binárním přístupu k 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šíme práci s kurzorem a zjištění velikosti souboru. Pro jednoduchost 1) nebudu v programu ošetřovat žádné chyby.
1: /*------------------------------------------------*/ 2: /* ftell1.c */ 4: #include <stdio.h> 7: int main(void) 8: { 9: FILE *soubor; 10: unsigned int i; 11: char pole[100]; 12: soubor = fopen("pokus.txt", "w+t"); 14: /* nejdrive zapiseme do souboru nekolik znaku 'a' */ 15: for (i = 0; i < 50; i++) 16: fputc('a', soubor); 18: /* posuneme kurzor za prvnich 5 bajtu */ 19: fseek(soubor, 5L, SEEK_SET); 20: /* zapiseme 5 znaku '-' */ 21: for (i = 0; i < 5; i++) 22: fputc('-', soubor); 24: /* posuneme se na konec souboru a zjistime jeho velikost */ 25: fseek(soubor, 0L, SEEK_END); 26: printf("Velikost souboru je %lu bajtu.\n", ftell(soubor)); 28: /* presuneme se opet na zacatek souboru a precteme jeho obsah */ 29: fseek(soubor, 0L, SEEK_SET); 30: while (!feof(soubor)) { 31: fgets(pole, sizeof(pole) / sizeof(char) - 1, soubor); 32: printf("%s", pole); 33: } 34: printf("\n"); 35: fclose(soubor); 36: return 0; 37: } 39: /*------------------------------------------------*/
Výstup z programu:
Velikost souboru je 50 bajtu. aaaaa-----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
| ← textové soubory | C/C++ | adresářová struktura → |
