Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Proud je prostředek, kterým lze s pomocí standardní knihovny pracovat se soubory a dalšími vstupně-výstupními zařízeními (např. klávesnice, terminál). Existují i další prostředky, například systémová volání nebo jiné knihovny. Těm se ovšem věnovat nebudeme.

Funkce, o kterých budeme mluvit, mohou skončit chybou. Uvedeme si jenom, jakým způsobem poznáme, že k chybě došlo a proč k ní mohlo dojít. Zpracování chyby a to, jak se má program chovat, pokud k chybě dojde, je mimo rozsah tohoto kurzu. V příkladech budeme možnost, že k chybě dojde buď ignorovat, nebo použijeme assert.

Proudy ze standardní knihovny

Proud je objekt, do kterého lze zapisovat nebo z něj číst. Otevřením souboru jej na proud napojíme a poté zapisování do či čtení z proudu odpovídá zapisování do souboru či čtení ze souboru. Jsou definovány i další, tzv. standardní proudy. Pomocí nich můžeme zapisovat na standardní výstup (tam zapisuje např.printf), na standardní chybový výstup a číst ze standardního vstupu.

Proud je reprezentován strukturou FILE, v programu pracujeme s ukazateli na tuto strukturu. Získáme jej pomocí funkce fopen.

FILE* fopen(char* path, char* mode);

První argument je cesta k otevíranému souboru (ta je buď absolutní, nebo relativní vzhledem k umístění binárky spoušteného programu). Textový řetězec mode určuje způsob otevření souboru. Jeden ze znaků mode obvykle udává režim otevření: ten je buď textový nebo binární. Další znak pak to, co se souborem můžeme dělat: číst jej, zapisovat do něj, připojit něco na jeho konec, vytvořit jej, pokud neexistuje, nebo nějaká kombinace předchozího. Nejpoužívanější možnosti ukážeme níže.

/*
POPIS ZNAKU
'r' otevreno pro cteni
'w' otevreno pro zapis, existujici soubor je prepsan
'a' otevreno pro zapis, pokud soubor existuje, zapisujeme na jeho konec
't' otevreno v textovem rezimu 'b' otevreno v binarnim rezimu

PRIKLADY KOMBINACI
"rt" cteni v textovem rezimu
"wt" zapis v textovem rezimu 
"rb" cteni v binarnim rezimu 
"wb" zapis v binarnim rezimu
*/

Pokud se v řetězci nenachází ani jeden ze znaků tb, otevře se proud v textovém režimu.

Pokud otevření souboru selže, vrátí fopen 0. Tuto situaci je potřeba ošetřit, pro účely kurzu se spokojíme s assercí. (Pro zjištění důvodu lze někdy použít kombinaci errno a strerror.)

FILE *st = fopen("path/to/file", "t");
assert(st);

Nepoužívaný proud se zavírá funkcí fclose.

V textovém režimu čteme a zapisujeme znaky, v binárním režimu bajty. Ačkoliv se zdá, že je to totéž, rozdíl je například u konce řádku ve Windows, který končí dvojící bajtů '\r' '\n'.

Ze streamu se čte a zapisuje se do něj pomocí funkcí z stdio.h. Stream si udržuje místo, ze kterého se bude číst při příštím zavolání funkce pro čtení. Po přečtení nějakého úseku (např. znaku, řádku apod.) posune toto místo na první bajt nepřečteného obsahu.

Pokud se knihovní funkci pro čtení ze streamu čtení nepovede, programátor to pozná z návratové hodnoty, případně může zkontrolovat příznaky streamu pro konec souboru a pro chybu. To se dělá funkcemi

int feof(FILE* stream); // vraci true, pokud jsme na konci souboru
int ferror(FILE* stream); // vraci true, pokud došlo k chybě

Analogické úvahy platí i pro zápis.

Textový režim

Základní funkce pro čtení je

int getc(FILE *stream);

Přečte ze streamu jeden znak, který získáme přetypováním návratové hodnoty na char. Při neúspěšném čtení vrací konstantu EOF.

Větší část textu lze načíst pomocí funkce fgets (tu si čtenář dostuduje z referenční příručky).

Základními funkcemi pro zápis jsou

int fputc(int ch, FILE* stream);
int fputs(char *s, FILE* stream);
int fprintf(FILE* stream, char* format, ...), 

Funkce fputc zapíše do streamu znak, který dostane přetypováním ch na unsigned int. Funkce fputs zapíše do streamu řetězec. V případě neúspěchu obě funkce vracejí EOF a nastaví streamu flag ferror.

Funkce fprintf funguje jako printf pouze tiskne do streamu. Při chybě vrací zápornou hodnotu, jinak počet vytistěných znaků.

Zápis do streamu je bufferovaný, zapsané změny se v napojeném souboru (či zařízení) mohou projevit později. Chceme-li si jejich okamžité projevení, můžeme k tomu použít funkci

int fflush(FILE* stream);

V případě neúspěchu vrací EOF. Tato funkce je volána (či je prováděn kód jí velmi podobný) i při zavření streamu, proto fclose také může neuspět a vrátit EOF.

Existují tři standardní proudy, přistupné jsou pomocí proměnných stdin, stdout, stderr.

Jako příklad si uvedeme funkci, která načte obsah souboru na disku a vytiskne jej na standardní výstup.

void print_file(char *filename) 
{
	FILE *in = fopen(filename, "rt");
	assert(in);

	int z = 0;
	while (1) 
	{
		z = getc(in);

	    if (z == EOF)    // konec souboru nebo chyba cteni
		{ 
			if (ferror(in)) 
			{ 
				fprintf(stderr, "chyba cteni\n");
				assert(0);
			}
			break;
		}
		fputc(z, stdout);
	}
	fclose(in);
}

Úkoly

  1. Napište program, který spočítá počet znaků, řádků a slov v textovém souboru. (Analogie textové utility wc).

  2. Napište funkci, která z proudu načte jeden řádek, když předem neznáme délky řádků. Zajistěte, aby šla funkce používat opakovaně. (Musíte vymyslet vhodné argumenty, jejich předání a vypořádat se s alokacemi).

  3. Nastudujte v referenční příručce a vyzkoušejte funkci fgets.

  4. Napište program, který načte textový soubor a do jiného souboru uloží kopii jeho obsahu tak, že jeden řádek bude mít maximálně 80 znaků. Přitom nerozdělí žádná slova kratší než 80 znaků a minimalizuje počet řádků.