Jazyk C práce se soubory 1
Soubory Použití souborů pro vstup většího množství dat do programu (uživatel nezadává z klávesnice ručně tisíce údajů...) pro uložení většího množství výsledků, např. k pozdějšímu zpracování Soubor = řada (posloupnost) bajtů uložených na disku způsob a detaily uložení jsou řízeny operačním systémem práce se souborem: o naším úkolem je požádat operační systém o přístup k souboru o úkolem operačního systému je poskytnou nám bajty souboru ve správném pořadí o se soubory se pracuje téměř stejně jako s klávesnicí a monitorem 2
Soubory textové a binární Textový soubor veškerá data jsou uložena v textové podobě, jako posloupnost jednotlivých znaků (resp. jejich ASCII kódů, tzn. v souboru jsou uložena čísla velikosti 1 bajt) při zápisu čísel do souboru probíhá automatická konverze z vnitřní reprezentace čísel v proměnných do textové podoby, při čtení probíhá opačná konverze (probíhá tedy to, co dělají funkce scanf a printf) vytvořené textové soubory je možné číst v textových editorech (např. Notepad, Kate, PSPad) a také je samozřejmě možné je v těchto editorech vytvářet a načítat do programu Binární soubor data jsou na disku uložena přesně tak, jak jsou uložena v proměnných při zápisu a čtení neprobíhá žádná konverze, jednotlivé bajty proměnných jsou přímo zapisovány na disk nebo jsou z disku čteny a ukládány do proměnných binární soubory nelze smysluplně přečíst v žádném editoru, při jejich čtení do programu je nutné přesně vědět jaké typy proměnných jsou uloženy a v jakém pořadí 3
Soubory textové a binární Příklad: uložení proměnné typu short předpokládejme, že typ short int zabírá 2 bajty paměti proměnná short int a = 5; uložení hodnoty 5 v proměnné: v binárním souboru bude uloženo totéž co v proměnné - velikost souboru bude 2 bajty (stejně jako proměnná) v textovém souboru bude uložen znak '5' (resp. jeho ASCII kód) - velikost souboru bude 1 bajt proměnná short int b = 1507; uložení hodnoty 1507 v proměnné: 0000 0000 0000 0101 0000 0101 1110 0011 v binárním souboru bude uloženo totéž co v proměnné - velikost souboru bude opět 2 bajty v textovém souboru budou uloženy znaky '1' '5' '0' '7' - velikost souboru bude 4 bajty pořadí bitů se může na různých počítačích lišit Častěji se pracuje se soubory textovými, budeme se tedy věnovat jim, nicméně práce s oběma typy souborů je téměř shodná. 4
Sekvenční a náhodný přístup do souboru sekvenční: data jsou načítána bajt po bajtu od začátku souboru do konce, což je obvyklé při práci s textovými soubory přímý (náhodný): lze číst a zapisovat na libovolnou pozici v souboru, tento způsob se používá spíše při práci s binárními soubory Bufferování čtení a zápis neprobíhá po jednotlivých znacích, ale po větších dávkách, které jsou dočasně skladovány v tzv. bufferu (vyrovnávací paměti) čtení z disku: místo jediného znaku je do bufferu je načten celý blok dat, další znaky jsou místo z disku čteny z bufferu (pozn. načtení jednoho znaku z disku trvá skoro stejně dlouho jako načtení celého bloku) zápis na disk: data jsou zapisována do bufferu, k zápisu na disk dojde se zpožděním, když je buffer plný, zapíše se naráz na disk důvod bufferování: omezení počtu přístupů na disk, který je řádově pomalejší, než práce s pamětí způsob bufferování lze nastavit, viz funkce setvbuf ze souboru (stdio.h), funkce umí bufferování i úplně potlačit, pak jsou veškerá data načítána a zapisována okamžitě 5
Přístup k souboru pomocí datového typu FILE (nutno psát velkými písmeny) se souborem pracujeme pomocí pointeru na FILE: FILE * f; Otevření souboru pomocí funkce fopen: FILE * fopen("soubor", "mód") // funkce ze stdio.h parametry funkce fopen: 1) soubor = řetězec s názvem souboru nebo cestou k souboru (relativní či absolutní) "pokus.txt"... práce se souborem pokus.txt, který je ve stejném adresáři jako program "D:\\pracovni\\pokus.txt"... lomítka v cestě zdvojujeme 2) mód = řetězec "r"... read, otevření souboru pro čtení, soubor musí existovat FILE obsahuje informace potřebné k práci se souborem (umístění bufferu souboru, pozice kurzoru v souboru...) "w"... write, otevření pro zápis pokud soubor existuje, jeho obsah je vymazán a nahrazen novým zápisem pokud soubor neexistuje, je vytvořen aby nedošlo k nechtěnému napsání některého speciálního znaku (\a, \n...) "a"... append, otevření pro zápis, zapisovaná data jsou připsána na konec souboru pokud soubor neexistuje, je vytvořen návratová hodnota funkce: pointer na FILE, pomocí kterého pak pracujeme s daným souborem 6
soubor lze pomocí jednoho FILE* v jednu chvíli otevřít: 1. buďto pro čtení: PROGRAM BUFFER SOUBOR 2. nebo pro zápis: PROGRAM BUFFER SOUBOR vznikají tzv. vstupní / výstupní proudy (stream) soubor může být pomocí jedné proměnné FILE* otevřen např. nejdříve pro zápis a po uzavření znovu otevřen pro čtení, ale je zvykem používat pro zápis a pro čtení jiné proměnné FILE* FILE *fr, *fw; // proměnná fr pro čtení a fw pro zápis pokud otevření souboru selže, vrací funkce fopen hodnotu NULL o o obvyklý důvod selhání otevření: špatně zadané jméno nebo cesta k souboru selhání není nijak výjimečná situace, návratovou hodnotu bychom tedy měli testovat FILE * fr = fopen("soubor.txt, "r"); if (fr == NULL) printf("otevreni souboru selhalo!"); 7
Základní práce se souborem Přehled základních funkcí a jejich porovnání s funkcemi pro práci s klávesnicí a monitorem (pro demonstraci souborových funkcí používáme proměnnou FILE * f) soubory monitor, klávesnice čtení znaku int znak = getc(f) 1 char znak = getchar() zápis znaku putc(znak, f) putchar(znak) čtení proměnné fscanf(f, "formát", adresa) scanf("formát", adresa) zápis proměnné fprintf(f, "formát", proměnná) printf("formát", proměnná) čtení řetězce fgets(řetězec, max, f) 2 gets(řetězec) zápis řetězce fputs(řetězec, f) puts(řetězec) 1 návratová hodnota funkce getc je typu int, kvůli detekci konce souboru (viz dále) 2 max je celočíselná hodnota, který udává kapacitu řetězce (tedy počet znaků, které lze do pole zapsat) 8
Detaily k funkcím pro tisk a čtení řetězců funkce gets čte z klávesnice znaky včetně mezer až po '\n', znaky uloží do řetězce, znak '\n' zahodí, na konec řetězce doplní znak '\0' funkce fgets čte ze souboru znaky včetně mezer až po '\n' nebo do dosažení kapacity, vše (včetně '\n') uloží do řetězce, který zakončí znakem '\0' funkce puts vytiskne na monitor řetězec (musí být zakončený '\0') a odřádkuje (vytiskne '\n') funkce fputs zapíše do souboru řetězec, ukončující znak '\0' nezapisuje, pokud operace selže (např. při plném disku), vrací konstantu EOF (viz dále) Soubory versus klávesnice/monitor jak je vidět, souborové funkce jsou velmi podobné funkcím pro standardní vstup/výstup jazyk C ve skutečnosti pracuje s klávesnicí/monitorem stejně jako se soubory, pomocí FILE * stdin // vstup z klávesnice FILE * stdout // výstup na monitor funkce pro práci se soubory lze použít i pro práci s monitorem/klávesnicí, pokud místo "souborového " FILE * zadáte jako argument stdin nebo stdout např. výpis znaku 'a' na monitor: putc('a', stdout); obě proměnné jsou zajištěny operačním systémem při spuštění programu 9
Test konce souboru 1. pomocí funkce (makra) feof int feof(file * f) makro foef vrací nenulovou hodnotu, pokud poslední čtení uskutečněné pomocí příslušného FILE * bylo ZA koncem souboru načtení všech čísel ze souboru data.txt a jejich tisk na monitor: FILE * f = fopen("data.txt", "r"); int cislo; while (1) { fscanf(f, "%i", &cislo); if (feof(f)!= 0) break; printf("nactene cislo: %i\n", cislo); } 10
Test konce souboru 2. pomocí konstanty EOF konstanta EOF (End Of File) je definována v stdio.h a má obvykle hodnotu - 1 (není to zaručeno) hodnota EOF je vracena funkcí getc při čtení posledního znaku na konci souboru načtení a tisk celého obsahu souboru na monitor: FILE * f = fopen("data.txt", "r"); int znak = getc(f); while (znak!= EOF) { putchar(znak); znak = getc(f); } proměnná, do které je ukládána načtená hodnota musí být typu int, protože EOF má obvykle hodnotu -1 (pokud by byla proměnná typu char, hodnota -1 by mohla způsobit aritmetické přetečení rozsahu char a do proměnné by se uložila zcela jiná hodnota než -1) 11
Zavření souboru pomocí funkce fclose(file * f) soubory je nutné zavírat ihned po ukončení práce s nimi důvody: 1) při zavření se výstupní buffer zapíše do souboru (kdyby program později spadl, data budou zapsaná) 2) počet současně otevřených souborů je v každém operačním systému omezen, pokud tedy necháváte otevřené soubory, se kterými už nepracujete, můžete tento počet časem překročit pokud zavření selže, funkce fclose vrací konstantu EOF if (fclose(f) == EOF) printf("zavreni souboru selhalo"); 12
Příklady načtení čísel ze souboru in.txt a výpočet jejich průměru double cislo, suma = 0.0; int pocet = 0; FILE * fr = fopen("in.txt", "r"); if (fr == NULL) { printf("otevreni souboru selhalo"); return 0; } while (1) { fscanf(f, "%lf", &cislo); if (feof(f)!= 0) break; suma += cislo; pocet++; } fclose(f); double prumer = suma / (double)pocet; printf("prumer nactenych cisel: %f", prumer); 13
Příklady kopírování souboru FILE * fr = fopen("soubor.txt", "r"); FILE * fw = fopen("kopie.txt", "w"); if (fr == NULL fw == NULL) { printf("otevreni nektereho souboru selhalo"); return 0; } int znak = getc(f); while (znak!= EOF) { putc(znak, fw); znak = getc(f); } fclose(fr); fclose(fw); 14
Další užitečné funkce int fflush(file * f) o o o pokud je f otevřen pro zápis, funkce si vynutí okamžité zapsání výstupního bufferu na disk pokud je f otevřen pro čtení, chování není předepsané, ale většinou způsobí vyprázdnění vstupního bufferu (fflush(stdin) vyčištění vstupu od nezpracovaných znaků, např. '\n') návratová hodnota: 0 = úspěch, EOF = selhání operace int rename(char starejmeno[], char novejmeno[]) o přejmenování souboru, parametry jsou řetězce udávající jména souboru (případně relativní či absolutní cesty, soubor lze takto i přesunout) o funkce není spojena s žádným proudem FILE * o návratová hodnota: 0 = úspěch, jiná hodnota = selhání operace int remove(char soubor[]) o smazání souboru, parametry a návratová hodnota jsou stejné jako u rename další užitečné funkce najdete v dokumentaci k stdio.h pozn. pro čtení a zápis dat do binárních souborů se používají funkce fread a fwrite (podrobnosti opět najdete v dokumentaci k stdio.h) 15
Přesměrování vstup a výstup do souboru lze využít i při práci s programem, který je napsán tak, aby pracoval pouze s klávesnicí a monitorem, stačí program zavolat z příkazové řádky operačního systému a použít přesměrování program soucet má načíst z klávesnice dvě čísla a vypsat jejich součet na monitor volání programu z příkazové řádky: D:\> soucet program je spuštěn tak jak byl napsán, vstupy čte z klávesnice, výsledek vypisuje na monitor D:\> soucet < in.txt program čte vstupní údaje ze souboru in.txt, výsledek vypisuje na monitor D:\> soucet > out.txt program čte vstupy z klávesnice, výstup zapisuje do souboru out.txt (který vytvoří, pokud neexistuje) D:\> soucet < in.txt > out.txt program čte vstupy z in.txt a zapisuje výstupy do out.txt 16