[Přednáška 5] [Obsah] [Přednáška 7]

Přednáška 6


Soubory


Témata


Soubory v jazyce C

Soubor je v jazyce C reprezentován ukazatelem na datovou strukturu FILE, nesoucí informace o otevřeném souboru (tzv. file descriptor, popisovač souboru); struktura je uložena v datových strukturách operačního systému. Všechny datové typy a funkce vztahující se k souborům jsou deklarovány v hlavičkovém souboru stdio.h.

Soubor deklarujeme jako proměnnou typu ukazatel na FILE, např.:

FILE *vstupni;

Otevření souboru

Soubor se otevírá voláním funkce
FILE *fopen(const char *filename, const char *mode);
Funkce vrací ukazatel na nově vytvořenou datovou strukturu FILE. Nelze-li soubor otevřít, vrátí funkce hodnotu NULL. Parametr filename je jméno souboru, které může být uvedeno včetně cesty (syntaxe je pak závislá na operačním systému, pod kterým je program překládán; zvláště u systémů MS Windows je nutné dávat pozor na zápis znaku lomítko jako \\ v řetězcových konstantách). Parametr mode specifikuje mód otevření souboru. Módy uvádí tabulka 1.

MódVýznam
rRead - otevře soubor pouze pro čtení
wWrite - vytvoří soubor pouze pro zápis. Pokud soubor neexistuje, je vytvořen prázdný, pokud existuje, je smazán a přepsán.
aAppend - otevře soubor pro připojení. Pokud soubor existuje, je otevřen pro zápis, ale obsah není smazán. Aktuální pozice je nastavena na konec souboru, tj. zapisovaná data jsou přidána za konec souboru. Neexistuje-li soubor, je vytvořen prázdný.
r+Otevře existující soubor pro čtení i zápis (změnu).
w+Vytvoří nový soubor pro čtení i zápis. Jestliže soubor existuje, je přepsán.
a+Otevře existující soubor pro čtení i zápis; aktuální pozici nastaví na konec souboru.

Tabulka 1: Módy otevření souboru

Ke znakům určujícím otevření souboru pro čtení/zápis je možné připojit znak b nebo t, specifikující otevření v textovém nebo binárním módu. Chybí-li písmeno b nebo t, je soubor otevřen v textovém módu (viz binární a textové soubory).

Příklady:

"rt" otevře soubor pro čtení v textovém módu, "wb" vytvoří soubor pro zápis v binárním módu, "wt+" nebo "w+t" vytvoří soubor pro čtení i zápis v textovém módu.
Kód pro otevření souboru seznam.txt (pro čtení v textovém módu) v jazyce C i s ošetřením chyby zapíšeme např. takto:
FILE *vstupni;

vstupni=fopen("seznam.txt","rt");
if (vstupni==NULL)
{
  printf("Chyba: soubor seznam.txt nelze otevrit");
  return -1;
}
Efektivnější kód přiřadí ukazatel rovnou v podmínce:
FILE *vstupni;

if ((vstupni=fopen("seznam.txt","rt"))==NULL)
{
  printf("Chyba: soubor seznam.txt nelze otevrit");
  return -1;
}

Uzavření souboru

Soubor se zavírá voláním funkce

int fclose(FILE *stream);
Parametrem funkce je ukazatel na otevřený soubor. Obsah všech vyrovnávacích pamětí je zapsán před uzavřením do souboru. Funkce vrací hodnotu 0, jestliže se podařilo soubor zavřít, v případě chyby vrací hodnotu EOF (End of File), která je definována jako symbolická konstanta v knihovně stdio.h (zpravidla -1). Tato konstanta se používá jako návratová hodnota pro signalizaci chyb u většiny funkcí pracujících se soubory.

Soubor seznam.txt otevřený v předchozím odstavci uzavřeme: fclose(vstupni);


Funkce pro zápis/čtení dat do/z textového souboru

Zápis/čtení znaku do/ze souboru

K zápisu jednoho znaku do souboru slouží funkce
int fputc(int c, FILE *stream);
V případě úspěšného zápisu vrací funkce zapisovaný znak c, pokud dojde k chybě, pak hodnotu EOF.

Ke čtení jednoho znaku z textového souboru slouží funkce

int fgetc(FILE *stream);
V případě úspěšného načtení znaku vrací funkce přečtený znak konvertovaný na typ int (bez znaménkového rozšíření). Na konci souboru nebo při výskytu chyby vrací EOF.

Pro vrácení znaku do souboru existuje funkce int ungetc(int c, FILE *stream);

Zjištění konce řádku a konce souboru

V jazyce C neexistuje žádná funkce pro zjištění, zda je aktuální pozice v souboru na konci řádku. Test konce řádku spočívá v porovnání, zda načtený znak je roven znaku konce řádku '\n', tj. if (c == '\n') ....

U textových souborů je možné testovat jejich konec dvěma způsoby. Jeden z nich je test, zda se návratová hodnoty funkce pro čtení dat (fgetc, fscanf) rovná hodnotě EOF. Druhý způsob představuje volání funkce

int feof(FILE *stream);
Funkce vrací nenulovou hodnotu (true), pokud bylo dosaženo konce souboru, jinak 0. Pro binární soubory (viz dále) je vhodnější používat tuto funkci feof, u textových je efektivnější test návratové hodnoty funkcí na hodnotu EOF.
Poznámka: Zmíněné funkce pro čtení znaku lze použít i pro binární soubory (čtou soubor po slabikách), ale nastává problém při čtení a zápisu se znakem (znaky) konce řádku. Je možné, že data v binárním souboru budou taková, že funkce fgetc vrátí hodnotu -1 uprostřed souboru).


Kostra programu pro čtení textového souboru po znacích

Pro ilustraci uvedeme kostru programu pro čtení textového souboru po znacích.
FILE *vstupni;

if ((vstupni=fopen("seznam.txt","rt"))==NULL)
{
  printf("Chyba: soubor seznam.txt nelze otevrit");
  return -1;
}

while((c=fgetc(vstupni))!=EOF)
{
  /* zpracování znaku c */
  /* test na konec řádku */
  if (c == '\n') ... 
}
fclose(vstupni);

Program, který přečte textový soubor po znacích a vypíše obsah na obrazovku, je napsán podle této kostry.

Příklad:
Vstupní soubor:soubor1.txt
CodeBlocks:soubor1.cbp, soubor1.c


Formátovaný výstup/ vstup do/ze souboru

Pro formátovaný výstup do textového souboru slouží funkce
int fprintf(FILE *stream, const char *format[, argument, ...]);
Parametr stream je ukazatel na otevřený soubor, ostatní argumenty a používání funkce se neliší od funkce printf. Funkce fprintf vrací počet znaků zapsaných do souboru. Pokud dojde k chybě, vrací funkce EOF.

Pro formátovaný vstup z textového souboru používáme funkci

int fscanf(FILE *stream, const char *format[, address, ...]);
Parametr stream je ukazatel na otevřený soubor, ostatní argumenty a používání funkce se neliší od známé funkce scanf. Funkce provádí čtení (např. celého čísla), dokud nenarazí na bílý znak (mezera, tabulátor, nový řádek) nebo na znak nepatřící do zápisu konvertované hodnoty (např. nečíselný znak). Pokud načítáme řetězce, načte se text do prvního bílého znaku. Funkce vrací počet úspěšně načtených položek, pokud se nepodaří položku načíst (např. ve formátovacím řetězci je %d a v souboru je text) vrací 0, při pokusu číst na konci souboru vrací EOF.

Představme si textový soubor data.txt, který obsahuje posloupnost celých čísel:

1 2 3 5 -4 10
20 5
Fragment kódu, který přečte všechna čísla a spočítá součet, vypadá následovně:
FILE *fi;
int s,x;
fi = fopen("data.txt","rt");
if (fi != NULL)
{
  s = 0;
  while(fscanf(fi,"%d",&x) != EOF) s += x;
  printf("Soucet je %d\n",s);
  fclose(fi);
}
else printf("Soubor nelze otevrit!\n");

V kódu není ošetřeno, zda se funkce fscanf opravdu načetla ze souboru celé číslo, zde se testuje pouze dosažení konce souboru. Kompletní kód je k dispozici:

Kód ke stažení:
Vstupní soubor:data.txt
Dev C++:soucet.dev, soucet.c
CodeBlocks:soucet.cbp, soucet.c


Čtení textového souboru po řádcích

Pro přečtení celého řádku ze souboru je možné využít funkci
char *fgets(char *s, int n, FILE *stream);
Funkce přečte do řetězce s jeden řádek z textového souboru. Parametr n je velikost řetězce a slouží jako omezovač, aby nedošlo k přeplnění pole. Jinak řečeno, funkce ukončí čtení dat, načte-li n-1 znaků ze souboru nebo narazí dříve na znak konce řádky. Funkce ponechá na konci řetězce znak konce řádky '\n' a připojí za něj znak konce řetězce '\0'. Při bezchybém čtení vrací ukazatel na řetězec s, při chybě vrací NULL.

Poznámka: Čteme-li soubor po řádcích a máme omezenou délku pole s, zjistíme snadno, zda jsme přečetli skutečně celý řádek - poslední znak řetězce musí být '\n' (výjimku tvoří poslední řádek souboru, který nemusí být ukončen znakem \n'.)


Standardní soubory stdin, stdout, stderr, funkce perror()

V každém programu (resp. v operačním systému) jsou v jazyce C k dispozici tři soubory (pseudosoubory) - stdin (standard input, standardní vstup), stdout (standard output, standardní výstup) a stderr (standard error, standardní chybový výstup). Soubory se neotvírají pomocí fopen, jsou již otevřené!

Původ souborů je třeba opět hledat v systému UNIX a u sálových počítačů. Standardní výstupem je konzole (terminál), standardní vstup je také konzolový (klávesnice). Standardní chybový výstup byl u sálových počítačů směrován na tiskárnu, dnes je také posílán operačním systémem na terminál (obrazovku). Funkce printf vlastně posílá svůj výstup do souboru stdout a funkce scanf čte tedy ze souboru stdin. V programu bychom mohli tyto dvě funkce nahradit ekvivalentně voláním funkcí fprintf a fscanf, např.:

fprintf(stdout, "%d",a); fscanf(stdin,"%d",&a);
Soubor stdin využijeme při čtení celého řádku z klávesnice. Místo funkce gets(char *s) je lépe volat funkci s parametrem standardního vstupu: fgets(s,n,stdin). Oproti funkci gets(char *s) lze předejít přetečení maximálního indexu při ukládání vstupního řetězce do pole.

Pro výstup chybových hlášení na standardní chybový výstup bychom měli používat funkci

void perror(const char *s);
Funkce vytiskne chybové hlášení s a automaticky číslo chyby, uložené v globální proměnné errno, resp. navíc systémové chybové hlášení. Proměnnou errno nastavují některé systémové funkce (viz dokumentace).

Tisk chybového hlášení bez vedlejších efektů lze provést také pomocí fprintf(stderr,"Chyba");

Příklad:
Dev C++:chyba.dev, chyba.c
CodeBlocks:chyba.cbp, chyba.c


Binární soubory

Funkce pro čtení a zápis dat z/do binárních souborů, funkce pro posuv v binárních souborech

Ke čtení a zápisu z/do binárních souborů slouží dvě funkce:
size_t  fwrite(void *ptr, size_t size, size_t n, FILE *stream);

size_t fread(const void *ptr, size_t size, size_t n, FILE *stream);
Ukazatel ptr je ukazatel na blok paměti (pole), z/do kterého se data čtou/zapisují. Hodnota size je velikost jedné položky (v bytech), n je počet položek, stream je ukazatel na otevřený soubor.

Funkce zapíší/přečtou n*size slabik do/ze souboru. Funce fwrite vrací počet skutečně zapsaných slabik (bytů) do souboru, funkce fread vrací počet skutečně přečtených slabik ze souboru.

Posouvat aktuální pozici v souboru („ukazovátko“) lze pomocí funkce

int fseek(FILE *stream, long offset, int whence);
Parametr offset určuje, o kolik slabik (bytů) se má aktuální pozice v souboru posunout. Pozice je relativní nebo absolutní, podle parametru whence. Může být i záporná. Možné hodnoty parametru whence ukazuje následující tabulka:

Symbolická
konstanta
HodnotaVýznam
SEEK_SET0Pozice od počátku souboru, hodnota offset je kladná
SEEK_CUR1Relativní pozice vzhledem k aktuální, hodnota offset může být kladná i záporná
SEEK_END2Pozice od konce souboru, hodnota offset je záporná

S binárním souborem pracují i funkce pro zápis dat do textového souboru a naopak.

Úloha

Napište program, který zapíše do binárního souboru celočíselné pole a poté obah opět přečte do jiného pole. Pro posuv na počátek použijte funkci fseek. Prohlédněte si obsah zapsaného souboru.
Návod: Zápis pole int c[10]; do binárního souboru fo se provádí fwrite((void*)c,sizeof(int),10,fo);
Řešení:
Dev C++:zapispole.dev, zapispole.c
CodeBlocks:zapispole.cbp, zapispole.c


Rozdíly při čtení a zápisu mezi textovými a binárními soubory v systémech firmy Microsoft

V různých operačních systémech jsou v textových souborech také různě ukončeny řádky. V systémech firmy Microsoft je označen konec řádku dvěma znaky CR LF (Carriage Return, návrat vozíku, kód ASCII 13 dekadicky a Line Feed, nový řádek, kód ASCII 10 dekadicky), v operačních systémech UNIXového typu je na konci řádku pouze znak LF, v systému Mackintosh pouze znak CR. Při otevření souboru v textovém módu je znak konce řádku správně intepretován, tj. např. program s voláním funkce putchar('\n'); (kde '\n' je znak LF) je přeložen v překladači pod systémy firmy Microsoft tak, aby zapisoval na konec řádku správně oba znaky, tedy i CR. Program přeložený pod OS typu UNIX zapisuje pouze znak LF atd. Rovněž při čtení konce řádku je v systémech firmy Microsoft „přeskočen“ znak CR.

Otevřeme-li textový soubor pod systémem firmy Microsoft jako binární, nedojde při čtení k této konverzi a načtou se oba znaky CR a LF po sobě, tj. fgetc vrátí nejprve znak '\r' (CR) a při dalším volání '\n' (LF). Při zápisu do textového souboru, který by byl otevřen jako binární, bychom museli zapsat oba znaky, tj. fputc('\r'); fputc('\n');, aby byl textový soubor vytvořen správně. V systému UNIXového typu není rozdíl mezi prací s binárním a textovým souborem.


[Přednáška 5] [Obsah] [Přednáška 7]