| ← datové proudy | C/C++ | binární 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ěříme na funkce, které čtou a zapisují řetězce. Při otevření souboru funkcí fopen() budeme vždy soubory 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 funkce prohlédnete, pochopíte, že by to nemělo moc velký smysl.
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 argument stream, kterým určujeme, do kterého souborového proudu chceme 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().
int fprintf(FILE *stream, const char *format, ...);
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í:
int fputs(const char *s, FILE *stream); int fputc(int c, FILE *stream); int putc(int c, FILE *stream);
Rozdíl mezi fputc() a putc() je v tom, že fputc() je funkce, zatímco putc() je implementován jako makro (proto je lepší používat fputc() u kterého se vám nestane, že by byl stream vyhodnocován vícekrát).
Funkce fputc() a putc() vracejí zapsaný znak, nebo EOF v případě chyby. Funkce fputs() nezáporné číslo v případě úspěchu, při chybě EOF.
V následujícím jednoduchém příkladě si vyzkoušíme použití těchto funkcí. Nebudu v něm kontrolovat návratové hodnoty funkcí (pro co největší jednoduchost), vy by jste však ve svých příkladech na kontrolu nikdy neměli zapomenout. Program bude zapisovat na konec souboru text zadaný od uživatele.
1: /*------------------------------------------------*/ 2: /* fprintf1.c */ 4: #include <stdio.h> 5: #include <string.h> 7: #define NAZEV "textak.txt" 9: int main(void) 10: { 11: FILE *soubor; 12: char text[255]; 14: soubor = fopen(NAZEV, "a+"); /* soubor se otevre pro aktualizaci, 15: neexistujici soubor se vytvori */ 16: do { 17: fputs("Zadejte slovo, ktere chcete zapsat do souboru\n" 18: "a stisknete ENTER, nebo \"q\" pro ukonceni: ", stdout); 19: scanf("%254s", text); 20: if (!strcmp(text, "q")) 21: break; 22: printf("Zapisuji >> %s <<\n", text); 23: fprintf(soubor, ">> %s <<\n", text); 24: } while (1); 26: fclose(soubor); 28: return 0; 29: } 31: /*------------------------------------------------*/
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 <<
Když už umíme 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čujeme, ze kterého souborového proudu chceme číst. Pokud tímto souborovým proudem bude stdin, pak se chování funkce nebude lišit od funkce scanf() opravdu v ničem.
int fscanf(FILE *stream, const char *format, ...);
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í:
char *fgets(char *s, int size, FILE *stream); int fgetc(FILE *stream); int getc(FILE *stream);
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řeme textový soubor pro zápis (zkrátíme jej na nulovou délku), zapíšeme text a čísla do souboru, pak soubor uzavřeme, otevřeme jej pro čtení, a jeho obsah načteme a vypíšeme.
1: /*------------------------------------------------*/ 2: /* fscanf1.c */ 4: #include <errno.h> 5: #include <stdio.h> 6: #include <string.h> 8: #define NAZEV "textak.txt" 10: int main(void) 11: { 12: FILE *soubor; 13: int i; 14: float x, y, z; 15: char text[255]; 16: /* nejdrive do souboru neco zapiseme */ 17: if ((soubor = fopen(NAZEV, "w")) == NULL) { 18: fprintf(stderr, "Chyba zapisu do souboru %s\n", NAZEV); 19: return errno; 20: } 22: fputs("Seznam zlomku:\n", soubor); 23: for (i = 1; i <= 5; i++) 24: fprintf(soubor, "%f %f %f\n", 100.0, (float) i, 100.0 / i); 25: fclose(soubor); 26: /* a ted to zpatky nacteme */ 27: if ((soubor = fopen(NAZEV, "r")) == NULL) { 28: fprintf(stderr, "Chyba cteni souboru %s\n", NAZEV); 29: return errno; 30: } 32: /* prvni radek nacteme znak po znaku */ 33: do { 34: i = fgetc(soubor); 35: fputc(i, stdout); 36: } while (i != '\n'); 38: /* druhy radek (prvni radek s cisly) nacteme naraz */ 39: fgets(text, sizeof(text) - 1, soubor); 40: fputs(text, stdout); 42: /* zbytek nacteme po cislech */ 43: for (i = 1; i <= 4; i++) { 44: fscanf(soubor, "%f %f %f", &x, &y, &z); 45: printf("%f/%f = %f\n", x, y, z); 46: } 48: fclose(soubor); 49: return 0; 50: } 52: /*------------------------------------------------*/
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
Na dalším příkladu si ukážeme, 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é ná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.
int feof(FILE *stream); void clearerr(FILE *stream); int ferror(FILE *stream);
Nejdůležitější je funkce feof(), která nás informuje o tom, zda jsme dosáhli konce souboru. Pokud jsme nejsme 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 dostaneme na konec souboru, ale pak se přesuneme na jeho začátek (jak to udělat se dozvíte později), nebo obnovení zápisu do proudu v případě uvolnění místa na zaplněném disku.
V příkladu máme za úkol otevřít soubor cisla.txt a sečíst všechna čísla, která jsou v něm zapsána.
1: /*------------------------------------------------*/ 2: /* feof1.c */ 4: #include <errno.h> 5: #include <stdio.h> 6: #include <string.h> 8: #define NAZEV "cisla.txt" 10: int zavri_soubor(FILE * soubor) 11: { 12: if (fclose(soubor) == EOF) { 13: fprintf(stderr, "%s %i", __FILE__, __LINE__); 14: perror(NULL); 15: return errno; 16: } 17: return 0; 18: } 20: int main(void) 21: { 22: FILE *soubor; 23: float f, suma; 25: if ((soubor = fopen(NAZEV, "rt")) == NULL) { 26: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 27: perror(NULL); 28: return errno; 29: } 30: suma = 0.0; 31: do { 32: if (fscanf(soubor, "%f", &f) == 1) { /* ma se nacist 1 cislo */ 33: printf("%.1f ", f); 34: suma += f; 35: } else if (feof(soubor)) /* dosahli jsme konce souboru */ 36: break; 37: else if (fscanf(soubor, "%*s") != 0) { /* preskoci retezec, ktery neni cislo */ 38: fprintf(stderr, "%s %i:", __FILE__, __LINE__); 39: perror(NULL); 40: return zavri_soubor(soubor); 41: } 42: } while (1); 44: (void) zavri_soubor(soubor); 45: printf("\nSuma = %.3f\n", suma); 46: return 0; 47: } 49: /*------------------------------------------------*/
Výhoda použití perror() je v tom, že lze pomocí knihovny <locale.h> nastavit jazyk, jakým na nás bude program mluvit. Knihovnou <locale.h> se budu zabývat až v části věnované programování pod Linuxem.
Podívejme se, jak bude program reagovat, pokud soubor cisla.txt nebude existovat.
kod.c 26:No such file or directory
Nebo když nebudeme mít přístupová práva k čtení souboru:
kod.c 26:Permission denied
Podívejme 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 část kódu, kde otvíráme soubor cisla.txt, zakomentovat a místo něj přiřadit do proměnné soubor stdin:
soubor = stdin;
| ← datové proudy | C/C++ | binární soubory → |
