Č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 fgets1 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
freadnebofgets, 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:
fscanfvrátí číslo<= 0, pokud se jí nepodaří načíst žádný zástupný znak ze vstupu.fgetsvrá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.txtje 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 radek3Funkce
feofvrá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ů tedyfeofvrátífalse, protože poslední pokus o čtení uspěl. Až v momentě, kdy se pokusíme načíst čtvrtý řádek, tak funkcefgetsselže a potéfeofvrátítrue. Jelikož ale tento kód nekontroluje návratovou hodnotu funkcefgetsa vždy po pokusu o načtení řádku vypíše proměnnouradek, tak se poslední řádek souboru vypíše dvakrát.