Práce s textovými soubory
Když už umíte soubor otevřít a zavřít, je na čase do něj také
něco zapsat a něco z něj přečíst. V této kapitole se zaměřím na
funkce, které čtou a zapisují řetězce. Při otevření souboru funkcí
fopen()
budu soubor otvírat
v textovém režimu. To však
neznamená, že by zde popisované funkce nemohli pracovat v binárním
režimu a s binárními (netextovými) soubory. Ale až si
prohlédnete příklady, pochopíte, že by to nemělo moc velký smysl.
Zápis do textového souboru
Jako programátoři jistě víte, že textový soubor není dán tím, že má příponu „.txt“ ale svým obsahem – obsahuje jen text. Tedy i čísla která chcete zapsat budete zapisovat jako text. Na rozdíl od binárních souborů, kde se čísla zapisují tak, jak jsou uloženy v paměti, tedy v bajtech.
První funkcí pro zápis do textového souboru, se kterou se nyní
seznámíme, je funkce fprintf()
.
Tato funkce je na chlup stejná, jako funkce printf().
Jediný rozdíl je v tom, že funkce fprintf()
má navíc
parametr stream
,
kterým určujete, do kterého souborového proudu chcete zapisovat.
Pokud tímto souborovým proudem bude stdout
, pak se chování
funkce nebude lišit od funkce printf()
opravdu v ničem.
Další možností je zápis do standardního chybového proudu stderr
,
nebo do našeho otevřeného souborového proudu otevřeného funkcí
fopen().
Kromě této funkce je možné také použít funkce
fputs()
pro tisk řetězce,
fputc()
a
putc()
pro tisk znaku. Jejich
deklarace jsou následující:
Rozdíl mezi fputc()
a putc()
je v tom, že
fputc()
je funkce, zatímco putc()
je implementována
jako makro (proto je lepší používat fputc()
).
Funkce fputc()
a putc()
vracejí zapsaný znak,
nebo EOF
v případě chyby. Funkce fputs()
nezáporné číslo v případě úspěchu (tedy 0 a více), při chybě EOF
.
V následujícím jednoduchém příkladě si vyzkoušíte použití těchto funkcí. Nebudu v něm kontrolovat návratové hodnoty funkcí (pro co největší jednoduchost), vy byste však ve svých příkladech na kontrolu nikdy neměli zapomenout. Program bude zapisovat na konec souboru text zadaný od uživatele.
/* c32/fprintf1.c */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define NAZEV "textak.txt"
int main(void)
{
FILE *soubor;
char text[255];
soubor = fopen(NAZEV, "a+"); /* soubor se otevre pro aktualizaci,
neexistujici soubor se vytvori */
do {
fputs("Zadejte slovo, ktere chcete zapsat do souboru\n"
"a stisknete ENTER, nebo \"q\" pro ukonceni: ", stdout);
scanf("%254s", text);
if (!strcmp(text, "q"))
break;
printf("Zapisuji >> %s <<\n", text);
fprintf(soubor, ">> %s <<\n", text);
} while (1);
fclose(soubor);
return 0;
}
/*------------------------------------------------*/
Makro _CRT_SECURE_NO_WARNINGS
je tu kvůli funkci fopen()
,
viz scanf().
Možný výstup z programu:
Zadejte slovo, ktere chcete zapsat do souboru a stisknete ENTER, nebo "q" pro ukonceni: jedna Zapisuji >> jedna << Zadejte slovo, ktere chcete zapsat do souboru a stisknete ENTER, nebo "q" pro ukonceni: dve Zapisuji >> dve << Zadejte slovo, ktere chcete zapsat do souboru a stisknete ENTER, nebo "q" pro ukonceni: honza jde Zapisuji >> honza << Zadejte slovo, ktere chcete zapsat do souboru a stisknete ENTER, nebo "q" pro ukonceni: Zapisuji >> jde << Zadejte slovo, ktere chcete zapsat do souboru a stisknete ENTER, nebo "q" pro ukonceni: q
V aktuálním adresáři se vytvoří soubor textak.txt, který bude mít takovýto obsah:
>> jedna << >> dve << >> honza << >> jde <<
Čtení z textového souboru
Když už umíte do textového souboru zapisovat, proč se nenaučit z
něj číst. Je to stejně snadné. K čtení z textového souboru bude
nejlepší použít funkci fscanf()
.
Tato funkce je na chlup stejná, jako funkce scanf().
Jediný rozdíl je v tom, že funkce scanf()
má navíc
argument stream,
kterým určujete, ze kterého souborového proudu chcete číst.
Pokud tímto souborovým proudem bude stdin
, pak se chování funkce
nebude lišit od funkce scanf()
opravdu v ničem.
Kromě této funkce je možné také použít funkce
fgets()
pro načtení řetězce,
fgetc()
a
getc()
pro načtení znaku.
Jejich deklarace jsou následující:
Rozdíl mezi fgetc()
a getc()
je pouze v implementaci těchto funkcí
překladačem. Doporučuji používat vždy fgetc()
.
V příkladě otevřu textový soubor pro zápis (zkrátím jej na nulovou délku), zapíši text a čísla do souboru, pak soubor uzavřu, otevřu jej pro čtení, a jeho obsah načtu a vypíšu.
- /*------------------------------------------------*/
- /* c32/fscanf1.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <errno.h>
- #include <stdio.h>
- #include <string.h>
- #define NAZEV "textak.txt"
- {
- FILE *soubor;
- size_t i;
- int c;
- NAZEV);
- return errno;
- }
- /* nejdrive do souboru neco zapisi */
- }
- /* a ted to zpatky nactu */
- NAZEV);
- return errno;
- }
- /* prvni radek nactu znak po znaku */
- do {
- /* druhy radek (prvni radek s cisly) nactu naraz */
- /* zbytek nactu po cislech */
- }
- }
- /*------------------------------------------------*/
Výstup z programu:
Seznam zlomku: 100.000000 1.000000 100.000000 100.000000/2.000000 = 50.000000 100.000000/3.000000 = 33.333332 100.000000/4.000000 = 25.000000 100.000000/5.000000 = 20.000000
Kontrola chyb – feof(), ferror(), clearerr()
Na dalším příkladu ukáži, co všechno je třeba kontrolovat při práci se soubory. Pro začátek si zopakujte vše, co jste se dozvěděli o knihovně <errno.h>. U každé funkce pracující se soubory máte definovanou návratovou hodnotu, pomocí níž můžete (nebo spíš musíte) kontrolovat, zda nedošlo k chybě. Při práci s datovými proudy máte k dispozici ještě další funkce, které vám pomohou se vyvarovat chyb. Tyto funkce pracují s datovým proudem stream a je zcela jedno, jestli jde o textový, nebo binární přístup.
Nejdůležitější je funkce feof()
,
která vás informuje o tom,
zda jste dosáhli konce souboru. Pokud nejste na konci souboru,
vrací 0, jinak kladnou hodnotu (1). Často se používá v cyklech, kde
se čte, dokud není dosaženo konce „souboru“. Její použití je
velice jednoduché a velice časté.
Funkce ferror()
testuje
indikátory chyb (obdobně jako feof()
indikátor konce souboru). Pokud nedošlo k chybě, vrací 0, jinak
kladnou hodnotu.
Funkce clearerr()
dokáže
jako jediná odstranit indikátor konce souboru a indikátory chyb v datovém
proudu. To může být užitečné například ve chvíli, kdy se dostanete na konec
souboru, ale pak se přesunete na jeho začátek (jak to udělat se dozvíte
později), nebo pro obnovení zápisu do proudu v případě uvolnění místa
na zaplněném disku.
V příkladu máte za úkol otevřít soubor cisla.txt a sečíst všechna čísla, která jsou v něm zapsána.
- /*------------------------------------------------*/
- /* c32/feof1.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <errno.h>
- #include <stdio.h>
- #include <string.h>
- #define NAZEV "cisla.txt"
- {
- return errno;
- }
- }
- {
- FILE *soubor;
- return errno;
- }
- suma = 0.0;
- do {
- /* ma se nacist jedno cislo */
- suma += f;
- /* dosahli jsme konce souboru */
- /* preskoci retezec, ktery neni cislo */
- }
- }
- /*------------------------------------------------*/
Výhoda použití perror()
je v tom, že lze pomocí
knihovny <locale.h> nastavit jazyk,
jakým na vás bude program mluvit.
Knihovnou <locale.h> se budu zabývat až v části věnované
programování pod Linuxem.
Podívejte se, jak bude program reagovat, pokud soubor cisla.txt nebude existovat.
kod.c 26:No such file or directory
Nebo když nebudete mít přístupová práva k čtení souboru:
kod.c 26:Permission denied
Podívejte se na výstup programu, kdy soubor cisla.txt existuje a mát následující obsah:
text 11.257 te123xt 20.447 18.3 text 30 text ? 20
Výstup:
11.3 20.4 18.3 30.0 20.0 Suma = 100.004
Zkuste za domácí úkol část kódu kde se otvírá soubor cisla.txt zakomentovat
a místo něj přiřadit do proměnné soubor
stdin: