Hlavní strana -> Programování v C -> 15. Práce se soubory

15. Práce se soubory

Těžko najdeme praktický příklad programu, ve kterém se nepoužívají soubory. Zatím vždy se v našem seriálu zadávaly všechny potřebné hodnoty buď přímo do programu nebo byl o jejich zadání požádán uživatel. Často je ale takový přístup obtěžující nebo snad dokonce nemožný. Budeme v našem programu chtít přehrávat nějaký zvuk a ten nám asi uživatel těžko zadá. Použijeme proto soubory ke vstupu dat do programu a taky pro výstup dat z programu. Data z a do souboru putují přes tzv. proudy (stream), které se ještě dají různě přesměrovávat, ale to teď není podstatné.

K věci. Soubory, tak jak je nejčastěji známe jsou uloženy na pevném disku (nebo jiném paměťovém zařízení). Přistupovat k nim je možno dvěma způsoby, ve dvou režimech. Jeden režim je textový a druhý binární. Rozdíl mezi nimi je v tom, jak se data čtou, případně zapisují. V textovém režimu se provádějí různé převody a proto data tak jak vypadají v paměti se neshodují s tím, jak vypadají v souboru. Soubory zapsané v textovém režimu jsou normálně pro člověka čitelné. Můžete si je klidně otevřít v nějakém textovém editoru. Dejme si jako příklad jednobajtovou proměnnou char, do které uložíme číslo 5. Zapsané jako text bude v souboru vidět znak "5", ale zapsáno v binárním režimu 8 bitů "00000101". V textovém režimu také bývá zvykem nahradit znak nového řádku dvojicí znaků návrat na začátek řádku a přechod na nový řádek. Práce s textovým souborem je tedy příjemnější pro člověka, s binárním zase pro počítač :-) Rozdíl je také v přístupu. Textový soubor se prochází sekvenčně, ale u binárního můžeme pozici následující operace (čtení / zápis) nastavit.

Pro použití souborů stačí mít přiinkludovaný hlavičkový soubor "stdio.h". K souborům se pak přistupuje přes ukazatel na strukturu FILE. Otevírání se provádí pomocí funkce fopen(jmeno_souboru, zpusob_otevreni), která vrátí ukazatel na strukturu FILE, která se k tomuto souboru vytvoří. Zavřít soubor je možné funkcí fclose(ukazatel_na_FILE). Funkce fopen vrací při neúspěchu NULL, který by se měl vždy otestovat, jestli se soubor podařilo správně otevřít. Důvody neúspěchu můžou být například pokus otevřít neexistující soubor, pokus otevřít soubor pro zápis, který je read-only... atd. Správně tedy vždy otestovat, jako při přidělování paměti pomocí malloc(). Způsoby otevření souboru popíšu po krátkém příkladě:

#include <stdio.h>

int main(int argc, char *argv[])
{
	FILE *fout; 
	char c = 123;

	fout = fopen("out.txt", "w");
	if(!fout){
		printf("Nelze otevrit soubor pro zapis");
		return 1;
	}

	fprintf(fout, "%d", c);

	fclose(fout);

	fout = fopen("out.bin", "wb");
	if(!fout){
		printf("Nelze otevrit soubor pro zapis");
		return 1;
	}

	fwrite(&c, sizeof(char), 1, fout);

	fclose(fout);
	return 0;
}

Nejprve si vytvoříme ukazatel na FILE a znakovou proměnnou, do které přiřadíme hodnotu 123. Otevřeme soubor "out.txt" s parametrem "w", což znamená zápis v textovém režimu. Dále je test na úspěšnost otevření, jinak se program ukončí. Zápis hodnoty 123 jako číslo do souboru ukazovaného přes fout pomocí funkce fprintf(), která je identická s již známou printf(), akorát její první parametr je ukazatel na FILE. Pak je soubor uzavřen. Druhá část dělá to samé, akorát v binárním režimu, jak je určeno parametrem "wb" ve funkci fopen(). Pro zápis binárních dat už se nepoužívá formátovaného výstupu, jaký dělá fprintf(). Musíme použít funkci pro zápis binárních dat fwrite(). Její první parametr je ukazatel na data, která chceme zapisovat. Druhý parametr určuje velikost jednoho takového prvku. Třetí parametr určuje, kolik takových prvků chceme zapsat a posledním parametrem je ukazatel na FILE, ke kterému se daný zápis vzahuje.

Zkuste si schválně oba vzniklé soubory otevřít např. v notepadu. V prvním případě naleznete "123", ve druhém "{". Důvod proč ve druhém případě se objeví složená závorka je, že fwrite zapíše právě jeden byte s hodnotou 123, který když je textovým editorem interpretován jako znak, vyjde složená závorka.

Tady jsou všechny potřebné režimy otevření:
rOtevře textový soubor pro čtení
wVytvoří textový soubor pro zápis
aOtevře textový soubor pro připisování
rbOtevře binární soubor pro čtení
wbOtevře binární soubor pro zápis
abOtevře binární soubor pro připisování
r+Otevře textový soubor pro čtení / zápis
w+Vytvoří textový soubor pro čtení / zápis
a+Otevře textový soubor pro čtení / zápis
rb+Otevře binární soubor pro čtení / zápis
wb+Vytvoří binární soubor pro čtení / zápis
ab+Otevře binární soubor pro čtení / zápis

V originále tuto tabulku najdete na Cpp reference

Jenom pár poznámek. Pokud je soubor otevírán jakýmkoliv způsobem s parametrem "a", pak pokud soubor neexistuje, vytovří se, pokud existuje, automaticky se bude zapisovat na jeho konec a původní data se nepřepíšou. Další rozdíl je u typů s "+". Pokud soubor neexistuje a je otevírán jako r+, tak se nový nevytvoří, pokud jako w+, pak se vytvoří (nehledě na to že jsou oba uvedeny jako pro čtení / zápis).

Kontrolu, jestli se podařilo soubor otevřít je možno stejně jako u čehokoliv jiného integrovat přímo do příkazu if. Způsob, jakým ji provedete je na vás, na vašem stylu a na tom, co je podle vás přehlednější.

    /* bezpecne otevrit soubor muzete takto: */
    
    if((fout = fopen("out.bin", "wb")) == NULL){
        printf("Nelze otevrit soubor pro zapis");
        return 1; /* nebo vlastni osetreni chyby */
    }

Jenom ještě upozorním, že u takového zápisu se dá udělat lehce chyba, pokud špatně nastavíte závorky. Porovnání pomocí operátoru == má vyšší prioritu, než operátor = pro přiřazení. Potom se může stát, že do fout se přiřadí buď 1 nebo 0 v závislosti na tom, jestli fopen() vrátila NULL, či nikoli. Takže na to pozor, hodnota 0 nebo 1 zcela jistě není adresa právě otevřeného souboru.

Takže už dokážeme spolehlivě otevřít soubor pro účely jaké potřebujeme. Teď přichází na řadu čtení a zapisování. Číst a zapisovat po jednom znaku lze pomocí funkcí:

int fgetc(FILE *fp);
int fputc(int ch, FILE *fp);

Obě sice přebírají a vracejí typ int a ne jeden znak, ale ve skutečnosti pracují pouze s nejnižším bytem. Při chybě čtení (například konec souboru) vrátí fgetc() hodnotu EOF, která je zpravidla -1. Na tuto hodnotu můžeme testovat, jestli bylo čtení úspěšné.

Obě funkce nyní použijeme pro zkopírování jednoho souboru do druhého po jednom znaku.

#include <stdio.h>

int main(int argc, char *argv[])
{
	FILE *fin, *fout;
	char c;

	if((fin = fopen("in.txt", "r")) == NULL){
		printf("Nelze otevrit soubor pro cteni");
		return 1;
	}

	if((fout = fopen("out.txt", "w")) == NULL){
		printf("Nelze otevrit soubor pro zapis");
		return 1;
	}

	while((c=fgetc(fin)) != EOF){  /* dokud nacteny znak neni EOF */
		fputc(c, fout);
	}
	printf("Zkopirovano");

	fclose(fin);
	fclose(fout);
	return 0;
}

Jiné funkce, které můžeme použít k práci s celými řetězci jsou:

int fputs(char *str, FILE *fp);
char *fgets(char *str, int num, FILE *fp);

Funkce fputs() zapíše řetězec odkazovaný přes ukazatel str do souboru fp. Při chybě vrátí EOF. Funkce fgets() slouží ke čtení řetězce ze souboru. Její první parametr je ukazatel na paměť, kam se bude zapisovat, druhý parametr je říká, kolik znaků se načte -1 a poslední parametr opět ukazatel na FILE. V souborech není potřeba ukončovací nuly, takže fputs() žádnou nezapíše a fgets() nepotřebuje, aby načítaný řetězec končil nulou, ale do str ji připojí. fgets() tedy končí se čtením, jestliže načetla num-1 znaků (a přidá nulu), narazila na nový řádek nebo na konec souboru. Vrací ukazatel na načtený řetězec a při chybě NULL.

Dalšími funkcemi jsou fprintf() a fscanf(), které jako jejich "nesouborové" protějšky umožňují formátovaný vstup / výstup. Fungují stejně, jenom mají jako první parametr ukazatel na FILE.

Tolik zatím pro práci se soubory. Tenhle díl se ještě brzy rozroste, ale už ne dnes.

Zpět

Programování v C | CZ 175/477 | Mapa stránek
Bc. Petr Klimánek, student Ostravské univerzity v Ostravě