Otevření souboru
Před tím než začneme pracovat se souborem, tak jej musíme otevřít.
K tomu máme v knihovně stdio.h
funkci fopen
.
Tato funkce bere jako svoje argumenty cestu k souboru a mód ve kterém chceme
soubor otevřít a vrací ukazatel na strukturu FILE
. Pokud otevření
souboru selže, tak funkce vrací NULL
.
FILE* fopen(const char *filename, const char *mode);
Mód otevření souboru
Pomocí módu specifikujeme co chceme se souborem dělat. Můžeme si vybrat z následujícíchc možností:
- "r" - Otevře soubor pro čtení
- "w" - Vytvoří nový soubor pro zápis (pokud soubor existuje, smaže jej)
- "a" - Otevře soubor pro zápis na konec souboru
Dále exitují varianty r+, w+, a+. Ty otevřou soubor současně pro zápis i čtení.
// otevre soubor pro cteni
fopen("users.txt", "r");
// pokud soubor existuje, smaze jeho obsah
// a otevre ho jak pro cteni, tak pro zapis
fopen("users.txt", "w+");
// otevre soubor pro zapis, nesmaze obsah
// a zapisovat se bude na konec souboru
fopen("users.txt", "a");
Textový vs. binární režim
Ke všem předchozím módům ještě můžeme přidat t nebo b. Mód t značí textový mód, mód b je binární. Na tomto semináři se budeme zabývat pouze textovými soubory.
// otevre soubor pro cteni v textovem modu
fopen("seznam.txt", "rt");
Co je to datový proud
Jedná se o posloupnost dat (vstupní/výstupní), kde každá hodnota obsažená v proudu může být.
přečtena pouze 1 a hodnoty nelze přeskakovat. Například u souborů se jedná
o vstupní datový proud (angl. stream), naopak printf
odesílá
řetězec do výstupního datového proudu (standartní výstup).
Čtení z textového souboru
Ke čtení jednoho znaku ze souboru používáme funkci fgetc
. Tato funkce bere
jako svůj jedniný argument ukazatel na stream (FILE
*)
int fgetc(FILE *stream)
Uvažujme soubor users.txt
který obrahuje následující data:
Darth Vader
Obi-Wan Kenobi
Yoda
Kód který přečte první 3 znaky vypadá následovně:
FILE* file = fopen("users.txt", "r");
printf("%c\n", fgetc(file)); // D
printf("%c\n", fgetc(file)); // a
printf("%c\n", fgetc(file)); // r
Čtení jednoho řádku
Pokud chceme přečíst jeden řádek včetně znaku '\n'
, tak k tomu
máme funkci fgets
. Tato funkce načte do parametru str
maximálně num
znaků (včetně ukončovací nuly) ze souboru stream
.
char* fgets(char *str, int num, FILE *stream);
Příklad čtení ze souboru
#define LINE_SIZE 50
FILE* file = fopen("users.txt", "r");
char* line = (char*) malloc(LINE_SIZE * sizeof(char));
fgets(radek, LINE_SIZE, file);
printf("%s\n", line); // Přečte: Darth Vader\n
fgets(line, 5, file);
printf("%s\n", line); // Obi-
Všimněte si, že druhý fgets
nezačal číst od začátku souboru,
ale pokračoval tam, kde první fgets
skončil. Druhý fgets
také přečetl pouze
4 znaky, 5. použil na uložení ukončovací nuly.
Formátované čtení
Pro formátované čtení používáme funkci fscanf
, která je
podobná funkci scanf
.
int fscanf(FILE *stream, const char *format, ... );
Návratovou hodnotou této funkce je počet úspěšně načtených hodnot.
Příklad
// Obsah souboru: 10 3.14! Neprecte se.
int number;
double double_number;
char character;
FILE* soubor = fopen("file.txt", "r");
fscanf(soubor, "%i %lf%c", &number, &double_number, &character);
printf("%i, %g, %c\n", number, double_number, character); // 10, 3.14, !
Jak poznat konec souboru
Pokud jsme již narazili na konec souboru, vrací funkce fgetc
a
fscanf
konstantu EOF („End of file) a funkce fgets
vrací NULL
.
char character;
FILE* file = fopen("file.txt", "r");
while((character = fgetc(file)) != EOF) {
printf("%c\n", character);
}
// program vypise kazdy znak souboru
// na samostatny radek a skonci
Toto řešení není dokonalé – fgetc vrací EOF i při chybě.
Funkce feof
Funkce feof zjišťuje, jestli už jsme přečetli celý soubor. Pokud jsme ho přečetli celý, vrací 1, jinak 0.
int feof(FILE *stream);
Pozor – funkce vrací 1 jen v případě, kdy už jsme se pokusili
přečíst znak mimo soubor. Pokud má soubor 10 znaků, my přečteme 10
znaků, funkce feof
vrací 0.
Špatné použití funkce feof
// Obsah souboru: "abcd"
FILE* file = fopen("file.txt", "r");
while(!feof(file)) {
printf("%c, ", fgetc(file));
}
// Výstup:
// a, b, c, d, EOF,
Správné použití funkce feof
// Obsah souboru: "abcd"
FILE* file = fopen("file.txt", "r");
while(1) {
char c = fgetc(file);
if (feof(file)) {
return 0;
}
printf("%c, ", c);
}
// Výstup:
// a, b, c, d,
Navrácení znaku do proudu
Častým požadavkem je něco jako: "čti znaky, dokud nenarazíš na číslici".
Což ale znamená, že se zarazíme až ve chvíli, kdy nějakou číslici přečteme.
Pokud čteme soubor s obsahem abcdef123456,
zůstane nám ve zbytku proudu obsah 23456. Funkce ungetc
vrací znak do proudu.
int ungetc(int character, FILE *stream);
// Použití
if (is_digit(character)) {
ungetc(character, file);
}
Zápis do textového souboru
Zápis do souboru probíhá analogicky ke čtení. Nejdříve je nutné soubor otevřít v módu pro zápis a pak již je možné do souboru zapisovat pomocí následujících funkcí.
Funkce fputc
Funkce zapíše jeden znak do souboru.
int fputc(int character, FILE* stream);
// Použití
FILE* file = fopen("file.txt", "w");
fputc('4', file);
fputc('2', file);
Zápis do textového souboru v ”a” módu
V módu "a" budeme zapisovat na konec souboru.
FILE* file = fopen("users.txt", "a");
fputc('4', file);
fputc('2', file);
// Obsah souboru users.txt:
Darth Vader
Obi-Wan Kenobi
Yoda42
Zápis textového řetězce
Pro zapsání textového řetězce používáme funkci fputs
.
Funkce zapíše do souboru celý řetězec vyjma ukončovací nuly.
int fputs(const char *str, FILE *stream);
// Použití
FILE* file = fopen("file.txt", "w");
fputs("Hello", file);
fputs(" World!", file);
// Obsah souboru:
Hello World!
Funkce fprintf
Funkce je stejná jako funkce printf
, akorát se zapisuje do souboru.
int fprintf(FILE *stream, const char *format, ...);
// Použití
FILE* file = fopen("file.txt", "w");
fprintf(file, "%i -- %g", 12, 3.0 / 7);
// Obsah souboru:
12 -- 0.428
Uzavření souboru
Po ukončení práce se souborem vždy musíme soubor uzavřít pomocí funkce fclose
:
int fclose(FILE * stream);
Proč uzavírat?
- Aby se skutečně zapsal obsah na disk. Data se po použití zapisovacích funkcí ukládají do mezipaměti, zápis na disk je drahá záležitost, proto se to OS snaží optimalizovat.
- Mohou existovat limity na počet otevřených souborů pro jeden program.
- Jiný program může chtít zapisovat do stejného souboru.
Příklad
FILE* file = fopen("file.txt", "w");
// zapis, cteni, cokoliv...
fclose(file);
Vynucení zápisu bez uzavření souboru
Pokud chceme vynutit uložení dat z mezipaměti
na disk, můžeme použít funkci fflush
:
FILE* file = fopen("file.txt", "w");
fprintf(file, "%i -- %g", 12, 3.0 / 7);
fflush(file);
fputs("dalsi zapis", file);
fclose(file);
Funkce fflush
ve skutečnosti nevynucuje zápis na disk, vynucuje jen
uvolnění mezipaměti. Data ale mohou skončit v další
mezipaměti.
Odchytávání chyb
Jakákoliv práce s diskem může selhat. Soubor nemusí existovat, program nemusí mít práva daný soubor otevřít, disk může být plný, ...
Po každém volání některé z funkcí bychom tak měli otestovat, zda vše proběhlo v pořádku.
Pokud dojde k chybě
Funkce fputc
, fputs
, fgetc
,
fscanf
, fclose
a ungetc
vrací při chybě
EOF
a funkce fgets
, fopen
vrací NULL
.
Kód s ošetřením chyb při zápisu
FILE* file = fopen("file.txt", "r");
if (file) {
if (fputs('!', file) == EOF) {
// Nepodarilo se zapsat znak
}
if (fclose(file) == EOF) {
// Nepodarilo se uzavrit soubor
}
} else {
// Soubor se nepodarilo otevrit
}
Kód s ošetřením chyby při čtení
int character;
FILE* file = fopen("file.txt", "r");
if (file) {
character = fgetc(file);
if (character == EOF) {
if (feof(file)) {
// OK, jsme na konci souboru
} else {
// Hmm, nepodarilo se precist znak
}
} else {
printf("Nacteny znak: %c\n", character);
} else {
// nepodarilo se otevrit soubor
}
Přečetli jsme celý soubor?
FILE* file = fopen("file.txt", "r");
char* buffer = (char*) malloc(30 * sizeof(char));
// nacitej radky, dokud fgets nevrati NULL.
// pak jsme bud precetli cely soubor, nebo nastala chyba
while (fgets(buffer, 30, file)) {
printf("%s", buffer);
}
if (feof(file)) {
printf("Konec souboru.\n");
} else {
printf("Nastala chyba.\n");
}
Typ chyby
V knihovně errno.h
existuje globální proměnná
errno
, ve které je uloženo číslo chyby. Funkce perror slouží
k vypsání chybové hlášky. Řetězec, který bere jako parametr, vypíše před chybovou hláškou.
void perror(const char * str);
Příklad
FILE* file = fopen("neexistujici_soubor", "r");
if (file) {
// udelej neco se souborem...
} else {
perror("Chyba");
printf("ID chyby: %i\n", errno);
switch (errno) {
case // Kód chyby
}
}
// Vystupem bude:
// Chyba: No such file or directory
// ID chyby: 2
Ošetření chyb pomocí errno
Proměnnou errno můžeme použít k ošetření chyb. Pokud chyba nenastala, má proměnná errno hodnotu 0. Jinak obsahuje číslo chyby. Musíme ale testovat hodnotu hned po provedení funkce! Aby hodnotu nepřepsala zase jiná funkce.
FILE* file = fopen("file.txt", "r");
if (errno) {
printf("Cislo chyby: %i\n", errno);
} else {
// zpracovani souboru...
}
Relativní cesta k souboru
Absolutní cesta: /cesta/ke/slozce na unixu, C:\cesta\ke\slozce na Windows
Relativní cesta: např. zmíněné fopen("file.txt", "r"). Cesta je vždy relativní vůči umístění, ze kterého se program spouští, ne vůči souboru, ve kterém je program uložený.
Tj. pokud otevřeme příkazový řádek ve složce /home/vyjidacek/zpc2/ a spustíme v něm program ./home/vyjidacek/programs/load_file, bude se hledat ve složce /home/vyjidacek/zpc2/.