Obsah
Soubor je posloupnost znaků (bajtů) ukončená nějakou speciální kombinací, která již k obsahu souboru nepatří - konec souboru, symbolicky EOF.
Textový soubor obsahuje řádky textu. Binární soubor obsahuje hodnoty v témže tvaru, v jakém jsou uloženy v paměti počítače. |
Soubor, s nímž můžeme v jazyce C pracovat, má své jméno. Tím rozumíme jméno na úrovni operačního systému. Někdy se v této souvislosti zavádí pojmy vnější jméno souboru a vnitřní jméno souboru. Tím prvním rozumíme jméno souboru na úrovni OS, druhý pojem chápeme jako jednoznačnou identifikaci souboru v rámci programu v jazyce C - nejčastěji jde o jméno proměnné, jejímž prostřednictvím se souborem pracujeme.
Operační systém neví nic o obsahu souboru, nezná jeho typ souboru. Přípony jmen souborů jsou většinou pouze doporučeními. Binární soubor obvykle nemá smysl vypisovat na terminál. Textový soubor můžeme snadno zobrazit a číst.
ISO norma jazyka C nám umožňuje pracovat se soubory technikou datových proudů. Pro manipulaci s datovým proudem je k dispozici řada funkcí, které poskytují vysoký komfort. Navíc, díky normám, můžeme v dobré víře očekávat plnou přenositelnost našich zdrojových textů na všechny platformy.
Potřebné znalosti | |
K zvládnutí obsahu této kapitoly je nutné mít znalosti z kapitoly 2 až 9 - chápat a umět používat dříve probíranou teorii, rozumět problematice ukazatelů. Umět vytvářet pole (jedno i více rozměrná). Znát operace prováděné s řetězci a použití argumentů příkazového řádku. |
Než začneme probírat souborový I/O, musíme znát dva velice důležité pojmy: datový proud a soubor. O souboru jsme se již zmínili výše. Nyní se zaměříme na datový proud.
I/O systém jazyka C poskytuje programátorovi stále stejné rozhraní, bez ohledu na skutečně použité I/O zařízení. Aby se toho dalo dosáhnout, zavádí jazyk C mezi programátorem a zařízením (hardwarem) určitou úroveň abstrakce, která se nazývá datový proud (datový tok, stream). Skutečné zařízení provádějící I/O operace se nazývá soubor. Datový proud je tedy logickým rozhraním souboru. Za abstraktní soubor můžeme vzhledem k uvedenému považovat diskový soubor, obrazovku, klávesnici, port, tiskárnu a různé jiné. Nejčastějším typem je samozřejmě diskový soubor. Výhodou tohoto přístupu je, že pro programátora vypadá jedno hardwarové zařízení stejně jako druhé. Datový proud automaticky ošetřuje rozdíly.
Datový proud je připojen k souboru pomocí operace otevření (open) a odpojen od souboru pomocí operace uzavření (close).
ISO norma definuje dva režimy proudů - textový (rozlišuje řádky) a binární. Režim stanovíme při otevírání souboru. |
Dalším důležitým pojmem je tzv. aktuální pozice. Je to místo v souboru, kde se bude provádět další operace se souborem. Například, je-li soubor dlouhý 100 byte a byla přečtena jeho polovina, pak bude další operace čtení probíhat od 50 bytu, což je aktuální pozice v souboru.
Základem pro přístup k proudu je datový typ FILE. Pro práci s datovými proudy musíme používat funkční prototypy umístěné v soubor stdio.h. Při každém spuštění programu máme otevřeny následující proudy:
Teprve po otevření můžeme s proudem pracovat. Při otevření proudu provádíme spojení mezi vnitřním a vnějším jménem souboru. Při otevření určujeme režim našeho přístupu k datům v proudu. Uzavřením proudu umožňujeme OS aktualizovat adresářové informace podle aktuálního stavu souboru, který byl s proudem spojen.
FILE *fopen(const char *filename, const char *mode);
Je funkce, vracející ukazatel na strukturu FILE v případě úspěšného otevření proudu. Při neúspěchu vrací hodnotu NULL. Konstantní řetězec filename, označuje jméno souboru podle konvencí příslušného operačního systému. Řetězec mode určuje režim práce se souborem i jeho typ.
int fclose(FILE *stream);
Je funkce uzavírající určený proud. V případě úspěchu vrátí hodnotu nula, jinak EOF. Uvolní paměť vyhrazenou pro strukturu FILE * a vyprázdní případnou vyrovnávací paměť.
ferror()
Informuje o chybách při práci s proudem.
perror()
Pošle řetězec chybového hlášení do standardního chybového proudu, tj. do stderr.
Pozn: | |
Počet souborů, které můžeme z programu současně otevřít, je omezen operačním systémem
(nejčastěji jeho konfigurací). Pro zjištění, jaký limit máme k dispozici, slouží makro
FOPEN_MAX. Operační systém rovněž omezuje délku jména souboru.
Rovněž tuto hodnotu můžeme zjistit pomocí makra, tentokráte však
FILENAME_MAX. Konec souboru představuje makro
EOF.
|
Režimy práce s datovým proudem:
Ikdyž většina souborových režimů nevyžaduje bližší vysvětlení, je vhodné uvést několik poznámek. Pokud soubor otevíráme jen pro čtení a on neexistuje, funkce fopen() selže. Když se otevírá neexistující soubor s režimem přidávání, pak bude vytvořen. Otevírá-li se již existující soubor v režimu pro přidávání, budou nová data zapisovány automaticky na konec souboru. Ke změně existujících dat nedojde. Když se otevírá neexistující soubor pro zápis, pak se vytvoří. Pokud soubor existuje, bude jeho původní obsah přepsán novým obsahem. |
Jednoduchý příklad otevření souboru.
Příklad: | |
FILE *soubor; if(soubor = fopen("mujsoubor", "r")) == NULL) { printf("Chyba pri otevirani souboru"); exit(1); } |
Výše popsaný, v tuto chvíli otevřený soubor, zavřeme jednoduše takto:
Příklad: | |
fclose(soubor); |
|
Pozn: | |
Znak je po načtení z proudu konvertován bez znaménka na typ int.
Obdobně je při zápisu do proudu konvertován opačným postupem.
Tak máme ponechánu možnost rozlišit konec souboru od dalšího načteného znaku.
|
Na následujícím příkladu si vyzkoušíme základní funkce souborového systému. Nejprve otevřeme soubor MUJ pro zápis. Poté do něj zapíšeme: "Toto je cvicny soubor" a soubor zavřeme. Následně soubor otevřeme v režimu pouze pro čtení a vypíšeme jeho obsah na obrazovku a soubor opět zavřeme.
Příklad 10.1. | |
/****************************** * stream_1.c * 19.03.2002 ******************************/ #include <stdio.h> #include <stdlib.h> int main(void) { char str[80] = "Toto je cvicny soubor"; FILE *soubor; char ch, *p; /* otevreni souboru pro zapis */ if((soubor = fopen("muj", "w")) == NULL) { printf("Soubor nelze otevrit\n"); exit(1); } /* zapis do souboru a jeho ulozeni */ p = str; while(*p) if(fputc(*p++, soubor) == EOF) { printf("Pri zapisu do souboru doslo k chybe\n"); exit(1); } fclose(soubor); /* otevreni souboru pro cteni */ if((soubor = fopen("muj", "r")) == NULL) { printf("Soubor nelze otevrit\n"); exit(1); } /* cteni souboru */ while((ch = fgetc(soubor)) != EOF) putchar(ch); fclose(soubor); return 0; } |
Z proudu nemusíme číst pouze jednotlivé znaky. Můžeme načítat i celé řádky. Jednotlivé řádky jsou ukončeny přechodem na nový řádek. Pro čtení musíme mít k dispozici dostatečně velkou vyrovnávací paměť. Nejčastěji ji získáme pomocí znakového pole. Pro vyšší bezpečnost musíme při použití funkce pro čtení uvést velikost této vyrovnávací paměti. Při zápisu to pochopitelně nutné není. Do proudu se zapíše celý řetězec až po koncovou zarážku, ovšem bez ní.
|
A opět příklad. Naším úkolem bude číst řádky zadávané uživatelem a ukládat je do souboru jehož jméno bylo zadáno jako argument příkazového řádku. Když uživatel zadá prázdný řádek je vstup uzavřen a soubor uložen. Poté je soubor znovu otevřen a zadané řádky ze souboru jsou přečteny a vypsány na obrazovku.
Příklad 10.2. | |
/****************************** * stream_2.c * 19.03.2002 ******************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char str[80]; FILE *soubor; /* kontrola argumentu prikazoveho radku */ if(argc != 2) { printf("Chyba je nutne zadat jmeno souboru"); exit(1); } /* otevreni souboru pro zapis */ if((soubor = fopen(argv[1], "w")) == NULL) { printf("Soubor nelze otevrit\n"); exit(1); } printf("Pro ukonceni zadejte prazdny radek\n"); do { printf(": "); gets(str); strcat(str, "\n"); /* pridani noveho radku */ if(*str != '\n') fputs(str, soubor); } while(*str != '\n'); fclose(soubor); /* otevreni souboru pro cteni */ if((soubor = fopen(argv[1], "r")) == NULL) { printf("Soubor nelze otevrit\n"); exit(1); } /* cteni souboru */ do { fgets(str, 79, soubor); if(!feof(soubor)) printf(str); } while(!feof(soubor)); fclose(soubor); return 0; } |
Nová funkce fprintf(), známá printf() a podobně nová funkce fscanf() a známá scanf(). Je vidět, že nové funkce přidávají k odpovídajícím známým protějškům jen úvodní argument stream.
Pro srovnání:
int fprintf(FILE *stream, const char *format [,argument,...]); int printf ( const char *format [,argument,...]); int fscanf (FILE *stream, const char *format [,address, ...]); int scanf ( const char *format [,address, ...]); |
Pomocí řetězcového a formátovaného výstupu vytvořme několikařádkový textový soubor. Ukážeme si při tom, jak do datového proudu zapíšeme řetězce a naformátované číselné hodnoty.
Příklad 10.3. | |
/****************************** * stream_3.c * 19.03.2002 ******************************/ #include <stdio.h> #include <string.h> int main(void) { FILE *soubor; int i; char *s, *jmeno = "soubor1.txt"; if ((soubor = fopen(jmeno, "wt")) == NULL) return 1; s = "Toto je textovy soubor vytvoreny v jazyce C.\n"; fputs(s, soubor); for (i = 0; i<10; i++) { fprintf(soubor, "%5d", i); } fputs("\n", soubor); s = "Jeste pridat posledni radek na konec.\n"; fputs(s, soubor); if (fclose(soubor) == EOF) return 1; return 0; } |
Výstup:
Toto je textovy soubor vytvoreny v jazyce C. 0 1 2 3 4 5 6 7 8 9 Jeste pridat posledni radek na konec.
|
Při práci s binárním proudem je nezbytný blokový přenos dat. Do binárního proudu totiž zapisujeme hodnoty nikoliv pěkně naformátované v textové podobě, ale v binárním tvaru. Tedy, tak jak jsou uloženy v paměti počítače. Výhodou je, že dopředu víme, kolik bajtů je pro který datový typ potřeba.
Skutečnost, že položky binárního souboru jsou stejně veliké, nám umožňuje vypočíst jejich polohu a přečíst, nebo zapsat třeba jedinou hodnotu na určenou pozici. Tomu říkáme náhodný přístup.
Funkce pro práci s binárními soubory:
|
Ukažme si nyní program, který naplní pole o deseti prvcích čísly s pohyblivou řádovou čárkou, zapíše je do souboru a znovu přečte. Program zapisuje každý prvek pole zvlášť. Jelikož se binární data zapisují ve svém interním formátu, musí být soubor otevřen pro binární I/O operace.
Příklad 10.4. | |
/****************************** * strm_bin.c * 19.03.2002 ******************************/ #include <stdio.h> #include <stdlib.h> double d[10] = {10.23, 19.87, 100.2, 0.258, 11.15, 95.23, 21.21, 458.03, 73.321, 3.14}; int main(void) { int i; FILE *soubor; if((soubor = fopen("soubor2", "wb")) == NULL) { printf("Soubor nelze otevrit.\n"); exit(1); } for(i=0; i<10; i++) if(fwrite(&d[i], sizeof(double), 1, soubor) != 1) { printf("Chyba pri zapisu.\n"); exit(1); } fclose(soubor); if((soubor = fopen("soubor2", "rb")) == NULL) { printf("Soubor nelze otevrit.\n"); exit(1); } /* vymazani pole */ for(i=0; i<10; i++) d[i] = 0.0; for(i=0; i<10; i++) if(fread(&d[i], sizeof(double), 1, fp) != 1) { printf("Chyba pri zapisu.\n"); exit(1); } fclose(soubor); /* vypis pole */ for(i=0; i<10; i++) printf("%f ", d[i]); return 0; } |
Následující příklad používá fseek() pro výpis hodnoty libovolného byte v souboru zadaném na příkazovém řádku.
Příklad 10.5. | |
/****************************** * fseek.c * 19.03.2002 ******************************/ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { long pozice; FILE *soubor; /* kontrola argumentu prikazoveho radku */ if(argc != 2) { printf("Chyba je nutne zadat jmeno souboru"); exit(1); } /* otevreni souboru */ if((soubor = fopen(argv[1], "rb")) == NULL) { printf("Soubor nelze otevrit\n"); exit(1); } /* vyhledavani */ printf("Zadejte cislo vyhledavaneho byte: \n"); scanf("%ld",&pozice); if(fseek(soubor, pozice, SEEK_SET)) { printf("Chyba vyhledavani\n"); exit(1); } printf("Hodnota na pozici %ld je %d", pozice, getc(soubor)); fclose(soubor); return 0; } |