Čtení ze souboru
Pro čtení ze souboru můžeme použít funkci fread
, která je
protikladem funkce fwrite
:
size_t fread(
void* buffer, // adresa, na kterou zapíšeme data ze souboru
size_t size, // velikost prvku, který načítáme
size_t count, // počet prvků, které načítáme
FILE* stream // soubor, ze kterého čteme
);
Tato funkce opět předpokládá, že budeme ze souboru načítat několik hodnot stejného datového typu. Například načtení pěti celých čísel, které jsme zapsali v kódu zde, by mohlo vypadat následovně:
#include <stdio.h>
#include <assert.h>
int main() {
int pole[5] = { 1, 2, 3, 4, 5 };
// otevření souboru
FILE* soubor = fopen("soubor", "rb");
assert(soubor);
// čtení ze souboru
int precteno = fread(pole, sizeof(int), 5, soubor);
assert(precteno == 5);
// zavření souboru
fclose(soubor);
return 0;
}
Funkce fread
vrací počet prvků, které úspěšně načetla ze souboru.
Textové čtení
Pokud bychom chtěli načítat ze souboru ASCII text, můžeme použít již známé funkce pro načítání textu,
například fgets
1 nebo fscanf
,
což je varianta funkce scanf
určená pro formátované čtení ze souborů.
1S funkcí fgets
jsme se setkali již dříve, kdy jsme jí
jako poslední parametr globální proměnnou stdin
. Datový typ proměnné stdin
je právě FILE*
–
při spuštění programu standardní knihovna C vytvoří proměnné stdin
, stdout
a stderr
a uloží
do nich standardní vstup, výstup a chybový výstup.
U načítání dat si vždy dejte pozor na to, abyste na adrese, kterou předáváte do
fread
nebofgets
, měli dostatek naalokované validní paměti. Jinak by se mohlo stát, že data ze souboru přepíšou adresy v paměti, kde leží nějaké nesouvisející hodnoty, což by vedlo k paměťové chybě 💣.
Rozpoznání konce souboru
Při čtení ze souboru je třeba vyřešit jednu dodatečnou věc – jak rozpoznáme, že už jsme soubor
přečetli celý a už v něm nic dalšího nezbývá? Pokud načítáme data ze souboru "binárně", tj.
interpretujeme je jako byty a ne jako (ASCII) text, obvykle stačí si velikost souboru
předpočítat po jeho otevření pomocí funkcí
ftell
a fseek
nebo si ji
přečíst přímo ze samotného souboru2.
2Spousta binárních formátů (např. JPEG
) jsou tzv. samo-popisné (self-describing), což
znamená, že typicky na začátku souboru je v pevně stanoveném formátu (tzv. hlavičce) uvedeno,
jak je daný soubor velký. Využijeme toho například při práci s obrázkovým formátem
TGA
.
Co ale dělat, když načítáme textové soubory, jejichž formát obvykle není ani zdaleka pevně daný? Předpočítat si velikost souboru a pak muset po každém načtení např. řádku počítat, kolik znaků jsme vlastně načetli, by bylo relativně komplikované. Při čtení textových souborů se tak obvykle využívá jiná strategie – čteme ze souboru tak dlouho, dokud nedojde k chybě. Způsob detekce chyby záleží na použité funkci:
fscanf
vrátí číslo<= 0
, pokud se jí nepodaří načíst žádný zástupný znak ze vstupu.fgets
vrátí ukazatel s hodnotou0
, pokud dojde k chybě při čtení.
Jakmile dojde k chybě, tak bychom ještě měli ověřit, jestli jsme opravdu na konci souboru, anebo
byla chyba způsobena něčím jiným3. To můžeme zjistit pomocí funkcí
feof
, která vrátí nenulovou hodnotu, pokud jsme se před jejím
zavoláním pokusili o čtení a pozice již byla na konci souboru,
a ferror
, která vrátí nenulovou hodnotu, pokud došlo k nějaké
jiné chybě při práci se souborem.
3Například pokud čteme soubor z USB flashky, který je během čtení odpojen od počítače.
Program, který by načítal a rovnou vypisoval řádky textu ze vstupního souboru, dokud nedojde na jeho konec, by tedy mohl vypadat například takto:
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main() {
FILE* soubor = fopen("soubor.txt", "rt");
assert(soubor);
char radek[80];
while (1) {
if (fgets(radek, sizeof(radek), soubor)) {
// radek byl uspesne nacten
printf("Nacteny radek: %s", radek);
}
else {
if (feof(soubor)) {
printf("Dosli jsme na konec souboru\n");
} else if (ferror(soubor)) {
printf("Pri cteni ze souboru doslo k chybe: %s\n", strerror(errno));
}
break;
}
}
fclose(soubor);
return 0;
}
Kvíz 🤔
-
Co vypíše následující program za předpokladu, že v souboru
soubor.txt
je tento obsah?radek1 radek2 radek3
#include <stdio.h> #include <assert.h> int main() { FILE* soubor = fopen("soubor.txt", "r"); assert(soubor); char radek[80]; while (feof(soubor) == false) { fgets(radek, sizeof(radek), soubor); printf("Nacteny radek: %s", radek); } fclose(soubor); return 0; }
Odpověď
Program vypíše:
radek1 radek2 radek3 radek3
Funkce
feof
vrátí pravdivou hodnotu pouze tehdy, kdy před jejím zavoláním na daném souborovém deskriptoru došlo k pokusu o čtení, který selhal z důvodu konce vstupního souboru. Po načtení prvních tří řádků tedyfeof
vrátífalse
, protože poslední pokus o čtení uspěl. Až v momentě, kdy se pokusíme načíst čtvrtý řádek, tak funkcefgets
selže a potéfeof
vrátítrue
. Jelikož ale tento kód nekontroluje návratovou hodnotu funkcefgets
a vždy po pokusu o načtení řádku vypíše proměnnouradek
, tak se poslední řádek souboru vypíše dvakrát.