Predchozi kapitola

Obsah

Konec

Nasledujici kapitola

8. Vstup a vystup

8.1. Standardni vstup a vystup

Standardni vstup a vystup znaku
Standardni vstup a vystup retezcu
Formatovany standardni vstup a vystup

8.2. Vstupni a vystupni operace v pameti.
8.3.
Prace se soubory.
8.4.
Soubory s primym volanim
8.5.
Datove proudy

Otevreni a zavreni proudu
Proudy a vstup/vystup znaku
Proudy a vstup/vystup retezcu
Formatovany vstup/vystup z/do proudu
Proudy a blokovy prenos dat
Dalsi uzitecne funkce
Priklady prace s proudy


Kazdy program zpracovava nejaka vstupni data a sdeluje nam vysledky touto cinnosti ziskane. Pokud by tomu tak nebylo, nemeli bychom zrejme duvod takovy program vubec aktivovat.

Vstup a vystup probiha z ruznych vstupne vystupnich zarizeni. Jejich nejjednodussi rozdeleni je znakova a blokova (blokove orientovana). Znakove vstupni zarizeni typicky predstavuje klavesnice, vystupni pak monitor ci tiskarna. Blokove vstupne/vystupni zarizeni je velmi casto pruzny ci pevny disk. Rozdil mezi nimi spociva zvlaste v moznostech pristupu k datum. Z klavesnice cteme sekvencne znak po znaku (sekvencni pristup), zatimco u diskoveho souboru muzeme libovolne pristupovat ke zvolene casti dat (pouzivany termin popisujici tuto cinnost je nahodny pristup).

Vstup a vystup budeme casto zkracene zapisovat I/O, nebo, nebude-li moznost omylu, jen IO.

Jeste nez se pustime do podrobneho vykladu, definujme si nektere zakladni pojmy.

Radek textu je posloupnost znaku ukoncena symbolem (symboly) prechodu na novy radek. Zdurazneme, ze v uvedene definici neni zadna zminka o delce radku. Tuto skutecnost je treba mit na pameti.1

Soubor je posloupnost znaku (bajtu) ukoncena nejakou specialni kombinaci, ktera do obsahu souboru nepatri - konec souboru. Tuto hodnotu oznacujeme symbolicky EOF. Textovy soubor obsahuje radky textu. Binarni soubor obsahuje hodnoty v temze tvaru, v jakem jsou ulozeny v pameti pocitace2. Binarni soubor obvykle nema smysl vypisovat na terminal.

8.1. Standardni vstup a vystupzpet

Kazdy program v jazyce C ma standardne otevren standardni vstup stdin, standardni vystup stdout a standardni chybovy vystup stderr. Ty jsou obvykle napojeny na klavesnici a terminal. Na urovni operacniho systemu mame ovsem moznost vstup a vystup presmerovat. Programy, ktere ctou ze standardniho vstupu a zapisuji do standardniho vystupu se casto nazyvaji filtry.

Pripomenme si uzitecne filtry grep, more, find. Na filtrech, kratkych a relativne jednoduchych programech, je zalozena i jedna ze zakladnich myslenek Unixu. Snadno zvladnutelne filtry spojene do kolony maji casto stejny efekt, jako slozite a rozsahle programy, ktere byvaji navic jednostranne orientovane3.

Moznost presmerovani vstupu a vystupu je rovnez velmi oblibena pro tvorbu funkcniho jadra programu, ktery zatim nema obsazen styk s uzivatelem. Takovemu programu presmerovavame data ulozena v pripravenem testovacim vstupnim souboru a vysledek presmerovavame do jineho vystupniho souboru.

Nekolik poznamek ke standardnimu I/O:

Pro ukonceni vstupu z klavesnice pouzijeme v MS-DOSu kombinaci ctrl-z, v Unixu ctrl-d.

Standardni vstup a vystup pouziva vyrovnavaci pamet obsahujici jeden textovy radek.

Pri volani funkci standardniho vstupu/vystupu musime pouzit hlavickovy soubor stdio.h.

Standardni vstup a vystup znakuzpet

predstavuje zcela zakladni moznost I/O. Funkce4

int getchar(void);

precte ze standardniho vstupu jeden znak, ktery vrati jako svou navratovou hodnotu. V pripade chyby vrati hodnotu EOF. Funkce

int putchar(int c);

ma zcela opacnou ulohu, znak, ktery je jejim argumentem zapise na standardni vystup. Zapsana hodnota je soucasne navratovou hodnotou, nenastane-li chyba. Pak vraci EOF.

Zdurazneme podstatny fakt, typ hodnoty, kterou cteme/zapisujeme je int, nikoliv char, jak bychom v prvni chvili ocekavali. Tuto zalezitost nam objasni opetne precteni definice souboru z uvodu teto kapitoly. Soubor obsahuje znaky. To odpovida nasi predstave. Ale konec souboru je predstavovan hodnotou, ktera do souboru nepatri. Tato hodnota ovsem musi byt odlisna od ostatnich znaku. A proto je typu int.

Cteni znaku ze standardniho vstupu a jejich zapis na standardni vystup ukazuje program, predstavujici jednoduchou variantu prikazu kopirovani souboru (nesmime ovsem zapomenout presmerovat vstup a vystup).

/****************************************/
/* CPY.C                                */
/* CoPY character                       */
/****************************************/

#include <stdio.h>

int main(void)
{
 int c;

 while ((c = getchar()) != EOF)
   putchar(c);
 return 0;
}

Standardni vstup a vystup retezcuzpet

je jednoduchou nadstavbou nad ctenim znaku. Obe funkce,

char *gets(char *s);
int puts(const char *s);

pracuji s retezci. gets nacte do znakoveho pole vstupni retezec az do konce radku, symbol '\n' neni do znakoveho pole zapsan. Ukazatel na pole (nacteny retezec) je rovnez navratovou hodnotou. Chybu signalizuje navrat NULL. puts zapise retezec na vystup a prida prechod na novy radek '\n'. Chybu predstavuje navratove EOF, jinak vraci kladne cele cislo.

Jednoduchost pouziti skryva velke nebezpeci. Funkce gets() nema informaci o delce oblasti vymezene pro cteny retezec. Je-li oblast kratsi, nez vstupni radek, dojde jeho nactenim velmi pravdepodobne k prepsani pametove oblasti souvisejici s vyhrazenou pameti. A to se vsemi dusledky z toho vyplyvajicimi.

Nasledujici program je modifikaci predchoziho kopirovani souboru. Je ovsem mozno jej pouzit pouze pro textove soubory. Navic mohou vzniknout odlisnosti mezi originalem a kopii - napr. konci-li posledni radek originalu primo EOF a ne '\n', je konec radku '\n' pripojen diky vlastnosti funkce puts().

/****************************************/
/* GETS.C                               */
/* GET String function                  */
/****************************************/

#include <stdio.h>
#define MAX_STR 512

int main(void)
{
 char s[MAX_STR];

 while (gets(s) != NULL)
   puts(s);
 return 0;
}

Formatovany standardni vstup a vystupzpet

V Uvodu jsme se povrchne seznamili s funkcemi pro formatovany vstup a vystup printf() a scanf(). Tyto funkce jsou zakladnimi predstaviteli funkci pro formatovany I/O, ktere se sice navzajem lisi zdrojem ci spotrebitelem, nicmene format argumentu a formatovaci retezce pouzivaji stejnym zpusobem. Proto na tomto miste podrobneji popiseme vlastnosti (zpusob volani), na nez se dale budeme odvolavat, nebo ktere jsou v textu pouzity.

Pro formatovany standardni vystup pouzivame funkci:

int printf (const char *format [, argument, ...]);

Prvni parametr format urcuje formatovaci retezec. Ten muze obsahovat popis formatu pro kazdy argument, nebo text, ktery bude zapsan do vystupu. Popis formatu vzdy zacina znakem %. Chceme-li znak % pouzit jako text, zdvojime jej: %%. Navratova hodnota reprezentuje pocet znaku zapsanych do vystupu, nebo EOF v pripade chyby.

Specifikace formatu ma pomerne velmi rozsahle moznosti:

% [flags] [width] [.prec] [F|N|h|l|L] type_char

Kazda specifikace formatu zacina symbolem %. Po nem mohou v uvedenem poradi nasledovat dalsi polozky:

polozka vyznam
flags zarovnani vystupu, zobrazeni znamenka a desetinnych mist u cisel, uvodni nuly, prefix pro osmickovy a sestnactkovy vystup
width minimalni pocet znaku na vystupu, mohou byt uvedeny mezerami nebo nulami
.prec maximalni pocet znaku na vystupu, pro cela cisla minimum zobrazenych znaku, pro racionalni pocet mist za desetinnou teckou
F|N|h|l|L l indikuje dlouhe cele cislo, L long double, ostatni maji vyznam v MS-DOSu
type_char povinny znak, urcuje typ konverze

Specifikujme si nyni typ konverze (type_char):

symbol vyznam
d, i desitkove cele cislo se znamenkem
u desitkove cele cislo se bez znamenka
o osmickove cele cislo
x, X sestnactkove cele cislo, cislice ABCDEF male (x) nebo velke (X)
f racionalni cislo (float, double) bez exponentu, implicitne 6 desetinnych mist
e, E racionalni cislo (float, double) vdesetinnem zapisu s exponentem, implicitne jedna pozice pred des. Teckou, 6 za. Exponent uvozuje e, resp. E (dle pouziteho symbolu).
g, G racionalni cislo (float, double) vdesetinnem zapisu s exponentem nebo bez nej (podle absolutni hodnoty cisla). Nemusi obsahovat desetinnou tecku (nema-li desetinnou cast). Pokud je exponent mensi, nez -4, nebo vetsi, nez pocet platnych cislic, je pouzit.
c znak
s retezec

Priznak (flag) muze byt:

priznak vyznam
- vysledek je zarovnan zleva
+ u cisla bude vzdy zobrazeno znamenko (i u kladneho)
mezera pro kladna cisla vynecha prostor pro znamenko
# pro formaty o, x, X vystup jako konstanty jazyka C, pro formaty e, E, f, g, G vzdy zobrazi desetinnou tecku, pro g, G ponecha nevyznamne nuly, pro c, d, i, s, u nema vyznam.

Sirka (width) muze byt:

sirka vyznam
n je vytisteno nejmene n znaku zarovnanych zleva ci zprava (viz priznak), doplneno mezerami
0n jako predchozi, doplneno zleva nulami
* jako sirka pole bude pouzit nasledujici parametr funkce printf()

Presnost (.prec) muze byt:

presnost vyznam
.0 pro e, E, f nezobrazi desetinnou tecku, pro d, i, o, u, x nastavi standardni hodnoty
.n pro d, i, o, u, x minimalni pocet cislic, pro e, E, f pocet desetinnych cislic. Pro g, G pocet platnych mist, pro s maximalni pocet znaku
* jako presnost bude pouzit nasledujici parametr funkce printf()

Pro formatovany standardni vstup pouzijeme funkci:

int scanf (const char *format [, address, ...]);

Prvni argument je opet formatovaci retezec. Ten muze obsahovat tri typy zpracovani objektu. jsou to:

Druhy (pripadne treti, ctvrty, ...) argument je umyslne nazvan address. Jde o to, ze musi urcovat pametovou oblast, do niz bude odpovidajici vstupni hodnota ulozena. V praxi jde nejcasteji o adresu promenne, nebo o ukazatel na pole znaku.

Cteni ze vstupu probiha tak, ze prvni formatovaci popis je pouzit pro vstup prvni hodnoty, ktera je ulozena na prvni adresu, druhy formatovaci popis je pouzit pro vstup druhe hodnoty ulozene na druhou adresu, ... .

Navratova hodnota nas informuje kladnym celym cislem o poctu bezchybne nactenych a do pameti ulozenych polozek (poli), nulou o nulovem poctu ulozenych polozek a hodnotou EOF o pokusu cist dalsi polozky po vycerpani vstupu.

Format vstupni hodnoty popiseme nasledovne:

% [*] [width] [F|N] [h|l|L] type_char

Po znaku % mohou nasledovat jednotlive polozky v uvedenem poradi:

polozka vyznam
* preskoc popsany vstup
width maximalni pocet vstupnich znaku
F|N blizky nebo vzdaleny ukazatel (jen MS-DOS)
h|l|L modifikace typu
type_char (povinny) typ konverze

Hvezdicka (*) ve specifikaci formatu umoznuje prislusnou vstupni hodnotu sice ze vstupu nacist, ale do pameti nezapisovat. Sirka (width) urcuje maximalni pocet znaku, ktere budou pouzity pri vstupu. Modifikatory typu (h|l) tykajici se celociselne konverze (d) urcuji typ short resp. long, (l|L) modifikuje racionalni typ float na double na long double.

Specifikujme si nyni typ konverze (type_char):

symbol vyznam
d cele cislo
u cele cislo bez znamenka
o osmickove cele cislo
x sestnactkove cele cislo
i cele cislo, zapis odpovida zapisu konstanty jazyka C (napr. 0x uvozuje sestnackove c.)
n pocet dosud prectenych znaku probihajicim volanim funkce scanf()
e, f, g racionalni cislo typu float, lze modifikovat pomoci l|L
s retezec (i zde jsou uvodni oddelovace preskoceny!), v cilovem poli je ukoncen '\0',
c vstup znaku, je-li urcena sirka, je cten retezec bez preskoceni oddelovacu!
[search_set] jako s, ale se specifikaci vstupni mnoziny znaku (je mozny i interval, napr. %[0-9], i negace, napr. %[^a-c].

Popis formatovaneho standardniho vstupu a vystupu je znacne rozsahly. Nektere varianty kombinaci paramertu formatu se vyskytuji pomerne exoticky. Casteji se setkame s vyuzitim navratove hodnoty funkce scanf().

Prikladem muze byt cyklus, v nemz nacteme ciselnou hodnotu (zadat muzeme cele i racionalni cislo) a vypocteme a zobrazime jeji druhou mocninu. Cyklus konci teprve ukoncenim vstupu - symbolem EOF. Tento symbol je v MS-DOSu ^Z, v Unixu ^D. Program muzeme chapat i jako male ohlednuti k cyklum:

/************************************************/
/* SCANWHIL.C                                   */
/* program je ukazkou nacitani pomoci scanf     */
/* v kombinaci s cyklem while.                  */
/* Ideova motivace K&R.	                        */
/************************************************/

#include <stdio.h>

int main(void)
{
 float f;

 printf("zadej x:");
 while (scanf("%f", &f) != EOF)
   {
    printf("x=%10.4f x^2=%15.4f\nzadej x:", f, f*f);
   }
 return 0;
} /* int main(void) */

zadej x:2
x=    2.0000 x^2=         4.0000
zadej x:3
x=    3.0000 x^2=         9.0000
zadej x:4
x=    4.0000 x^2=        16.0000
zadej x:^Z

8.2. Vstupni a vystupni operace v pameti.zpet

V pameti pocitace muzeme provadet prakticky stejne operace, jako pri standardnim vstupu a vystupu. Jen musime specifikovat vstupni respektive vystupni retezec. Tyto operace se provadeji pomoci funkci sprintf() a sscanf(). Uvodni s v nich znamena string a fakticky definuje rozdil mezi nimi a rodinou funkci scanf a printf. Podivejme se na deklarace:

int sprintf (char *buffer, const char *format [, argument, ...]);
int sscanf (const char *buffer, const char *format [, address, ...]);

Vstupni a vystupni retezec je buffer, ostatni argumenty maji stejny vyznam jako u funkci pro formatovany standardni vstup a vystup.

8.3. Prace se soubory.zpet

V odstavci venovanem standardnimu vstupu a vystupu jsme si ukazali, ze na teto urovni muzeme vlastne pracovat i se soubory. Staci jen na urovni operacniho systemu presmerovat vstup a/respektive vystup. Takova prace se soubory ma velikou vyhodu. Vzdy probiha na textove urovni, ktera je implementacne nezavisla5. Zminena nezavislost a tim i prenositelnost sebou prinasi i neustalou nutnost konverze mezi vnitrnim (binarnim) a vnejsim tvarem hodnoty. Zpravidla se jedna o cisla. Cislo ve vnitrnim tvaru ma pevny format a bez ohledu na svou hodnotu zaujima tedy vzdy6 stejnou cast pameti. Tuto vlastnost retezec, predstavujici textovy zapis cisla obvykle nesplnuje. Duvody popsane v tomto odstavci ovsem nejsou ty, pro nez je odlisen standardni IO od souboroveho IO.

Soubor, s nimz muzeme v jazyce C pracovat ma sve jmeno. Tim rozumime jmeno na urovni operacniho systemu7. Navic, program muze zjistit jmeno souboru napriklad az za chodu pri interakci s uzivatelem. Programator muze urcit, zda obsah souboru bude interpretovan textove, nebo binarne. Uvedene vlastnosti odlisuji pojmenovany soubor od standardniho vstupu a vystupu.

Jazyk C umoznuje se soubory pracovat na dvou odlisnych urovnich.

Prvni z nich je tak zvany prime volani . Tim se rozumi prime volani sluzeb jadra systemu. Poskytuje sice maximalni moznou rychlost diky vazbe na jadro systemu, soucasne vsak muze byt systemove zavisly. Tento zpusob prace se soubory je definovan v K&R. Je dobre znat jeho zakladni principy, nebot se v praxi muzeme setkat s mnoha starsimi zdrojovymi texty pouzivajicimi prime volani. Soubory s primym volanim nejsou soucasti ANSI normy. Jsou vsak definovany normou POSIX, kde se pouzivaji pro volani sluzeb jadra systemu.

Druhym pristupem, je datovy proud. Zpusob prace s nim definuje jak ANSI norma jazyka C, tak norma definujici rozhrani prenositelneho operacniho systemu POSIX (Portable Operating Systems Interface). Pro manipulaci s datovym proudem mame k dispozici radu funkci, ktere nam poskytuji vysoky komfort. Navic, diky citovanym normam, mame zajistenu nejen existenci techto funkci, modifikatoru a definic, ale i jejich definovane vlastnosti a rozhrani. To nas vede k pouziti tohoto pristupu v nasich programech.

Poznamejme, ze pocet souboru, ktere muzeme v programu soucasne otevrit, je omezen OS (nejcasteji jeho konfiguraci). Pro zjisteni, jaky limit mame k dispozici, slouzi makro FOPEN_MAX. Operacni system rovnez omezuje delku jmena souboru. Nekdy dokonce na neuveritelnych 8 + 3 znaky. Rovnez tuto hodnotu muzeme zjistit pomoci makra, tentokrat FILENAME_MAX. Odstavec venovany makrum, pouzitelnych jak ve spojeni se soubory s primym pristupem, tak s proudy, ukonceme znamou konstantou EOF, predstavujici konec souboru.

Popisme si obe moznosti prace se soubory v poradi jejich historickeho vzniku. V uvodu obou pasazi nejprve shrneme nejdulezitejsi funkce, pak uvedeme priklady, ukazujici zakladni principy jejich pouziti. Jeste jednou ovsem poznamenejme, ze prace s datovymi proudy zarucuje prenositelnost.

8.4. Soubory s primym volanimzpet

Pro praci se soubory s primym volanim musime pouzivat funkcni prototypy umistene v io.h., sys\stat.h a fcntl.h. Jednoznacnym popisem souboru v ramci beziciho programu (procesu) je kladne cele cislo, predstavujici ciselny popis souboru - manipulacni cislo (deskriptor, handle).

int open(const char *path, int access [ , unsigned mode ] );

Slouzi k otevreni a pripadnemu vytvoreni souboru. Vraci kladny deskriptor souboru, v pripade neuspechu -1 a nastavi kod chyby v globalni promenne errno.

Dalsi argumenty open() jsou:

path je retezec urcujici jmeno souboru na urovni OS. Muze obsahovat i cestu. V pripade MS-DOSu je nutne zdvojovat opacna lomitka oddelujici navzajem jednotlive podadresare cesty - jedna se o normalni ceckovy retezec, vcetne interpretace znaku \.

access popisuje rezim pristupu k otevrenemu souboru. S vyjimkou prvnich tri moznosti je mozne je navzajem kombinovat pomoci bitoveho souctu | .

mode je nepovinny priznak, definujici atributy souboru na urovni OS. Pri jeho urceni vyuzivame preddefinovane konstanty S_I... .

priznak vyznam
O_RDONLY otevreni pouze pro cteni
O_WRONLY otevreni pouze pro zapis
O_RDWR otevreni pro cteni i zapis
O_NDELAY pro kompatibilitu s UNIXem
O_APPEND pripojeni na konec souboru. Je-li nastaven, probiha kazdy zapis na konec souboru
O_CREAT vytvor a otevri soubor. Pokud soubor jiz existuje, nema vyznam. Jinak je soubor vytvoren.
O_EXCL vylucne otevreni. Pouziti jen soucasne s O_CREAT. Pokud soubor existuje, vrati chybovy kod.
O_TRUNC otevreni s orezanim. Pokud soubor existuje, je jeho delka nastavena na 0. Atributy souboru zustavaji nezmeneny.

Priznaky pro textovy/binarni rezim. Mohou byt pouzity funkcemi fdopen, fopen, freopen, _fsopen, open a sopen:

priznak vyznam
O_BINARY explicitni otevreni souboru v binarnim rezimu. Nejsou provadeny zadne konverze.
O_TEXT explicitni otevreni souboru v textovem rezimu. Jsou provadeny konverze CR-LF.

Dalsi symbolicke konstanty umoznuji nastaveni atributu souboru. Jsou definovany v souboru SYS\STAT.H:

priznak vyznam
S_IWRITE povoleni zapisu
S_IREAD povoleni cteni
S_IREAD|S_IWRITE povoleni cteni i zapisu

Popis k jedine funkci open() je pomerne zdlouhavy. Mame vsak alespon za sebou vycet preddefinovanych identifikatoru, se kterymi se muzeme pri praci s funkcemi pro primy pristup k souborum setkat. Protoze soubor chceme jiste nejen otevrit, ale s nim i dale pracovat, nezbyva, nez si popsat i dalsi potrebne funkce.

int creat(const char *path, int amode);

Vytvari novy soubor, nebo umoznuje prepsat soubor existujici. Hodnoty amode umoznuji nastaveni atributu souboru podobne, jako u open(). Retezec path predstavuje (uplne) jmeno souboru. Vraci kladny deskriptor souboru, v pripade neuspechu -1 a nastavi kod chyby v globalni promenne errno.

int close(int handle);

Zavira soubor, urceny hodnotou deskriptoru handle. Soucasne vyprazdni pripadne vyrovnavaci pameti, uzavre a uvolni souvisejici datove oblasti8. Vraci 0, probehlo-li uzavreni souboru spravne. V pripade neuspechu vraci hodnotu -1.

int read(int handle, void *buf, unsigned len);

Cte ze souboru urceneho manipulacnim cislem handle tolik bytu, kolik je urceno hodnotou len do pametove oblasti urcene ukazatelem buf. Navratova hodnota je -1 v pripade neuspechu. Jinak je vracen pocet prectenych bytu9. Funkce neprovadi (a ostatne ani nemuze provadet) zadnou kontrolu velikosti pametove oblasti buf a pozadovaneho pocetu bajtu pro cteni len. Pokud chceme jednim prikazem nacist vice, nez mame vyhrazeneho prostoru, skonci takovy prikaz fiaskem.

int write(int handle, void *buf, unsigned len);

Z pametove oblasti urcene ukazatelem buf zapise do souboru urceneho manipulacnim cislem handle tolik bytu, kolik je urceno hodnotou len. Navratova hodnota je v pripade neuspechu -1. Jinak je vracen pocet uspesne zapsanych bytu10. Ten by se mel shodovat s hodnotou len. Jinak muze byt napriklad plny disk.

long lseek(int handle, long offset, int fromwhere);

Je funkce, ktera nam umozni premistit aktualni pozici v souboru na pozadovane misto. A to jak pro cteni, tak pro zapis11. Hodnota handle je zrejma. Hodnota offset udava pozadovany pocet bajtu posunu. Zdurazneme, ze se jedna o hodnotu typu long int. Pro stanoveni pozice fromwhere, vzhledem k niz ma byt posun proveden, slouzi preddefinovane hodnoty:

SEEK_SET posun vuci aktualni pozici
SEEK_CUR posun vzhledem pocatku
SEEK_END posun vzhledem ke konci

Nedoporucuje se pouzivat jejich skutecne ciselne hodnoty (po rade 0, 1 a 2). Navratova hodnota je opet typu long int. Predstavuje skutecne nastavenou pozici v souboru, ovsem vzdy vzhledem k pocatku! V pripade chyby vraci -1L.

Pro zkraceni zavedeme pro aktualni pozici v souboru zkratku CP (od Current Pointer). CP si muzeme predstavit jeho cteci/zaznamovou hlavu, ktera se nachazi nad obsahem souboru. Dale zdurazneme, ze kazda operace cteni/zapisu presune CP o prislusny pocet bajtu. Pro jistotu doplnme, ze smerem od zacatku ke konci souboru. Rovnez musime pripomenout, ze pri nastavenem O_APPEND probiha kazdy zapis na konec souboru. Tedy bez ohledu na puvodni polohu CP.

Podivejme se postupne na nekolik prikladu, v nichz si ukazeme zakladni praci se soubory s primym pristupem.

Nejprve vytvorime binarni soubor, obsahujici hodnoty druhych odmocnin cisel od 1 do 99. Tyto hodnoty budou typu float.

/************************************************/
/* soubor IO-PV01.C                             */
/* vytvori binarni soubor ODMOC.DTA, ktery      */
/* obsahuje hodnoty druhych odmocnin od 1 do 99 */
/* hodnoty jsou typu float                      */
/************************************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <math.h>
#include <sys\stat.h>

int main(void)
{
 const int mez = 100;
 int handle, i;
 char *jmeno = "odmoc.dta";
 float f;

 handle = open(jmeno, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);
 if (handle == -1)
   {
    printf("Chyba!\nSoubor %s nejde vytvorit.\n", jmeno);
    return(handle);
   }

 for (i = 1; i < mez; i++)
   {
    f = sqrt(i);
    if (write(handle, &f, sizeof(float)) != sizeof(float))
      {
       printf("Chyba pri zapisu %2d. hodnoty\n", i);
       return(1);
      }
   } /* for (i = 1; i < mez; i++) */
 close(handle);
 return(0);
} /* int main(void) */

Jelikoz nam deklarace a definice promennych a konstant necini potize, podivejme se na klicove prikazy. Vcelku prirozene ocekavame, ze nejprve musime soubor vytvorit12 a otevrit jej pro zapis. Muzeme tak ucinit jak funkci creat(), tak nasledovne:

handle = open(jmeno, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);

Vytvarime (O_CREAT) novy soubor s nazvem urcenym retezcem jmeno (pokud existuje, je zkracen na delku 0), ktery bude obsahovat binarni (O_BINARY) data (tedy zadne textove konverze CR-LF) a do tohoto souboru budeme zapisovat, pripadne z nej mame moznost i cist (O_RDWR). Na urovni OS pozadujeme, aby soubor mel atributy umoznujici jeho vlastnikovi cteni i zapis (S_IREAD | S_IWRITE). Tento treti argument je sice nepovinny, ovsem jeho urcenim se vyvarujeme nedefinovaneho chovani nekterych prekladacu. Pokud je vse v poradku, mame nyni manipulacni cislo souboru handle, jinak ohlasime chybu a ukoncime chod programu.

Dale v cyklu do naseho otevreneho souboru - prvni argument funkce write() obsahuje manipulacni cislo souboru handle,

if (write(handle, &f, sizeof(float)) != sizeof(float))

zapisujeme obsah promenne f. Tento moment je velmi vyznamny. Nestaci totiz jen urcit, ze obsah promenne se nachazi na adrese &f. Musime jeste urcit, kolik bajtu ma byti zapsano. Cinime tak tretim argumentem sizeof(float) funkce write(). Tim sice mame zapis popsan, my vsak radeji jeste provedeme kontrolu, bylo-li preneseno prave tolik bytu, kolik jsme pozadovali. Vyhodnoceni pripadne nerovnosti je v programu soucasne chybovou zpravou a naslednym ukoncenim programu.

Po patricnem poctu opakovani, ktere zajisti cyklus, musime pred ukoncenim programu jeste soubor uzavrit:

close(handle);

Teprve pote si muzeme byt jisti, ze jsme pro zdarny prubeh naseho prvniho setkani se soubory ucinili vse potrebne.

V pracovnim adresari mame vytvoren soubor odmoc.dta. Mel by byt dlouhy 396 bajtu ( 99 hodnot typu float - tedy kazda 4 bajty). Je-li tomu tak, muzeme s timto souborem dale pracovat. Potrebujeme si ukazat cteni a premisteni CP v souboru.

V binarnim souboru odmoc.dta mame zapsany hodnoty druhych odmocnin cisel od 1 do 99. Podivejme se nyni na program, ktery podle zadane hodnoty bud zobrazuje hodnoty druhe odmocniny pro zadane cele cislo ve zminenem intervalu, nebo ukonci svou cinnost. Poznamejme, ze se hodnoty druhych odmocnin nepocitaji, nybrz ctou ze souboru13. Uloha tedy spociva v umisteni CP na vhodnou pozici v souboru a naslednem nacteni a zobrazeni zapsane racionalni hodnoty. Cely program muze vypadat takto:

/************************************************/
/* soubor IO_PV02.C                             */
/* cte binarni soubor ODMOC.DTA, ktery obsahuje */
/* hodnoty druhych odmocnin od 1 do 99          */
/* hodnoty jsou typu float                      */
/* hledanou hodnotu odmocniny najde v souboru   */
/* pomoci lseek                                 */
/************************************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>

int main(void)
{
 const int mez = 100;
 int handle, i;
 char *jmeno = "odmoc.dta";
 float f;

 handle = open(jmeno, O_BINARY | O_RDONLY);
 if (handle == -1)
   {
    printf("Chyba!\nSoubor %s nejde cist.\n", jmeno);
    return(handle);
   }
 printf("zadej hodnotu <1..%3d> :", mez - 1);
 scanf("%d", &i);
 while ((i >= 1) && (i < mez))
   {
    if (lseek(handle, ((long) i - 1l) * sizeof(float), SEEK_SET) == -1l)
      {
       printf("Chyba pri nastaveni na %2d. polozku\n", i);
       return(1);
      }

    if (read(handle, &f, sizeof(float)) != sizeof(float))
      {
       printf("Chyba pri cteni %2d. hodnoty\n", i);
       return(1);
      }
    printf("2. odmocnina z %3d je %10.4f\n\n", i, f);
    printf("zadej hodnotu <1..%3d> :", mez - 1);
    scanf("%d", &i);
   } /* while ((i >= 1) && (i < mez)) */
 close(handle);
 return(0);
} /* int main(void) */

Opet se zamerime na nove funkce pripadne na jejich nove pouziti. Nejprve musime otevrit existujici soubor. Vime, ze z nej budeme pouze cist, tedy:

handle = open(jmeno, O_BINARY | O_RDONLY);

Tentokrat testujeme navratovou hodnotu (a tedy uspesnost otevreni souboru) az na nasledujicim radku. Dale potrebujeme premistit CP na spravnou pozici v souboru,

if (lseek(handle, ((long) i - 1l) * sizeof(float), SEEK_SET) == -1l).

Prvni argument lseek(), tedy manipulacni cislo souboru, ziskame pri otevreni. Nasleduje zdanlive slozity vypocet pozice, na niz chceme CP premistit. Treti SEEK_SET argument rika, ze pozice bude vzhledem k pocatku souboru. Odbocme na chvili. Prvni polozka souboru ma posun nula. Druha polozka ma posun jedna krat delka polozky. Treti ... . Posledni polozka, rekneme n-ta, ma posun (n - 1) krat delka polozky.

Nyni jiz snadno nahledneme, ze vypocet ((long) i - 1l) * sizeof(float) skutecne urcuje spravnou pozici CP pro nacteni hodnoty druhe odmocniny. Ty jsou v souboru prece pro cela cisla 1 az 99. Pretypovani promenne i na long je spise proto, abychom si uvedomili typ tohoto argumentu.

CP je spravne nastaven, nezbyva nam, nez nacist potrebny pocet bajtu na spravne misto,

if (read(handle, &f, sizeof(float)) != sizeof(float))

pro jistotu i s kontrolou na skutecne precteny pocet bajtu. A to je v podstate vse. Po zadani cisla mimo rozsah je program ukoncen. Predtim pochopitelne nezapomeneme uzavrit soubor.

Nez prejdeme k ukazce prikladu prace s textovymi soubory s primym pristupem, ukazme si, jak zjistit informaci, ktera nezavisi na typu dat v souboru ulozenych. Touto informaci je jiste delka souboru (pocet bajtu). Muzeme se pustit na tenky led a pouzit specialisovane funkce zaviske na konkretnim OS. Nebo si uvedomime, jakou navratovou hodnotu ma funkce lseek(). Je to prece pozice v souboru vzhledem k jeho pocatku. Nastavime-li tedy CP na konec souboru, ziskame pozadovanou informaci. Jak provedeme nastaveni na konec? Snadno,

if ((l = lseek(handle, 0l, SEEK_END)) == -1l)

proste provedeme v otevrenem souboru presun o nula bajtu, ovsem vzhledem k jeho konci. Vypis celeho programu, s mirnym osetrenim nekterych moznych zdroju chyb, nasleduje:

/************************************************/
/* soubor IO-PV03.C                             */
/* Zjisti a vytiskne delku souboru, jehoz jmeno */
/* je zadano z klavesnice.                      */
/************************************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>

int main(void)
{
 int handle;
 char jmeno[80];
 long l;

 printf("\nzadej jmeno souboru :");
 scanf("%s", jmeno);

 handle = open(jmeno, O_BINARY | O_RDONLY);
 if (handle == -1)
   {
    printf("Chyba!\nSoubor <%s> nejde cist.\nNeexistuje?\n", jmeno);
    return(handle);
   }

 if ((l = lseek(handle, 0l, SEEK_END)) == -1l)
   {
    printf("Chyba pri nastaveni na konec souboru <%s>\n", jmeno);
    return(1);
   }
 else
   {
    printf("Soubor <%s> je dlouhy %ld bajtu.\n", jmeno, l);
    close(handle);
    return(0);
   }
} /* int main(void) */

Je nacase ukazat si i praci s textovymi soubory pomoci funkci pro primy pristup. Nasim ukolem bude vytvorit nekolikaradkovy textovy soubor. Ukazeme si, jak do nej zapsat retezce a co musime udelat proto, chceme-li do textoveho souboru zapsat i ciselne hodnoty. V podstate se nejedna o nic prevratneho. Jen pri otevreni ci vytvoreni souboru musime urcit, ze jej budeme chapat jako textovy,

if ((handle = open(jmeno, O_TRUNC | O_CREAT | O_TEXT | O_WRONLY, S_IWRITE)) == -1)

Proto tedy hodnota O_TEXT. Navic zde mame kombinaci priznaku O_TRUNC | O_CREAT, ktera zajisti nejen vytvoreni souboru, ale v pripade jeho existence i zkraceni na nulovou delku (tedy zruseni stareho obsahu). Prikaz je doplnen i priznakem O_WRONLY, davajicim informaci, ze do souboru hodlame vyhradne zapisovat. Tretim argumentem funkce pro otevreni, priznakem S_IWRITE, sdelujeme prakticky totez OS.

Vyvstava problem. Pri zapisu do souboru musime znat pocet bajtu, ktere chceme prenest. Mame, jako jiz tradicne, vice moznosti, jak tuto informaci ziskat. Postup

write(handle, "Uz to umim!\n", strlen("Uz to umim!\n"));

jiste neni prilis vhodny. Retezec musime uvest dvakrat. Nejenze tedy je pravdepodobne umisten dvakrat v pameti (pokud nas prekladac pri optimalizaci nezjisti shodu literalu). Navic v pripade, kdy chceme retezec zmenit, musime tuto zmenu identicky provest na dvou mistech. Zvolene reseni, kdy do vytvoreneho prostoru char s[81] retezec proste nakopirujeme pomoci strcpy(), vede ve vsech pripadech zapisu ke shodnemu a pomerne citelnemu

write(handle, s, strlen(s));

Po kratkem zamysleni ovsem opet nahledneme, ze retezec je v pameti opet umisten dvakrat. Navic jej jeste kopirujeme! Proc jsme tedy volili tuto na prvni pohled straslivou variantu?14 Odpovi nam dvojice nasledujicich radku

sprintf(s, "%5d", i);
write(handle, s, strlen(s));,

ktere jsou umisteny v tele cyklu. Pro zobrazeni ciselnych hodnot musime pouzit u textovych souboru s primym pristupem umely krok. Cislo nejprve prevedeme z vnitrniho tvaru na retezec a teprve pote tento retezec (reprezentujici cislo ve zvolenem formatu) zapiseme do souboru. A jestlize pro prevod potrebujeme vyhrazeny prostor, tak jsme tento prostor vyuzili i vyse. Odmenou za prece jen nadbyvajici kopirovani nam muze byt identicky zapis prikazu vystupu v obou pripadech.

Nyni jiz nasleduje souvisly vypis programu:

/************************************************/
/* soubor IO-PV04.C                             */
/* vytvori  textovy soubor SOUBOR.TXT           */
/* verze s primym pristupem - open              */
/************************************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <string.h>
#include <sys\stat.h>

int chyba(int er)
{
 extern int errno;
 if (er != 0)
   {
    perror(strerror(errno));	/* zapise chybove hlaseni do stderr */
		exit(errno);		/* -> OS; ukonci chod programu	    */
   }
 return er;
} /* int chyba(void) */

int main(void)
{
 int handle, i;
 char s[81], *jmeno = "soubor.txt";

 if ((handle = open(jmeno, O_TRUNC | O_CREAT | O_TEXT | O_WRONLY, S_IWRITE)) == -1)
   chyba(handle);

 strcpy(s, "Toto je textovy soubor vytvoreny v C.\n"
	     "primy pristup - handle\n");
 write(handle, s, strlen(s));
 for (i = 1; i < 10; i++)
   {
    sprintf(s, "%5d", i);
    write(handle, s, strlen(s));
   } /* for (i = 1; i < 10; i++) */

 strcpy(s, "\n\n");
 write(handle, s, strlen(s));

 strcpy(s, "Uz to umim!\n");
 write(handle, s, strlen(s));

 strcpy(s, "Jeste pridat posledni radek na konec.");
 write(handle, s, strlen(s));

 if (close(handle) == -1)
   chyba(1);

 return 0;
} /* int main(void) */

Program obsahuje i funkci chyba(), o niz jsme se dosud nezminili. Ta pracuje s globalni promennou errno, a pomoci funkce perror() zobrazi pred ukoncenim programu textovy popis chyby. Vystup je smerovan na standardni vystupni chybove zarizeni, jimz je obvykle terminal. Co se popisu chyby tyce, je jemnost jejiho rozliseni dana nabidkou hodnot errno. Textove retezce standardni knihovny jsou zpravidla jednojazycne (anglicke). I to je jeden z duvodu. proc jsme funkci chyba() vubec vytvorili. Neni prece zadny problem dat do tela teto funkce (nejlepe) staticke pole retezcu s chybovymi hlasenimi podle nasich potreb. Z uspornych duvodu nebudeme uvadet kod funkce chyba() v dalsich vypisech programu, a to az do konce teto kapitoly.

Text musime byt schopni do textoveho souboru nejen zapsat, ale nasledovne jej pak i zpracovat. Obsah vytvoreneho textoveho soubor zobrazime pomoci nasledujiciho programu. Podivejme se opet na jeho klicove casti. Na rozdil od binarnich souboru, obsahujicich treba jen hodnoty typu float, obsahuje textovy soubor (zpravidla) nestejne dlouhe radky textu ukoncene oddelovacem - koncem radku. Pokud otevreme soubor jako textovy, nemusi nas zajimat, jaky znak, ci znaky, konec radku tvori. O jeho rozpoznani se postara standardni knihovna. Ovsem presto pred nami stoji problem. Je jim delka radku. Tu zpravidla dopredu nezname. Ale jiz vime, ze pouhe cteni znaku do vymezene oblasti pameti, dokud nenarazime na konec radku, muze byt velmi nebezpecne. Proto budeme radeji testovat skutecne precteny pocet znaku vzhledem ke stanovenemu limitu. Volba limitu je casto dana obvyklou delkou radku znakoveho terminalu (plus mala rezerva). Casto se pohybuje mezi osmdesati az sto znaky15.

Opustme uvahy a podivejme se na zahlavi funkce cti_radek():

int cti_radek(int hdl, char *radek, int max)

Obsahuje tri argumenty potrebne pro urceni souboru, vyrovnavaci pameti a jeji delky (limitu). Navratova hodnona vypovida o vysledku cteni. Je popsana ve vypisu programu. Dale si povsimneme nutnosti ukonceni nacteneho retezce zarazkou:

s[i] = '\x0';

Nyni se podivejme na vypis zdrojoveho textu:

/********************************/
/* soubor IO-PV05.C             */
/* precte a vytiskne obsah      */
/* textoveho souboru SOUBOR.TXT */
/* pouziva UNIXove funkce       */
/********************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <sys\stat.h>

#define MAX_DELKA_RADKU 81 /* plus jedna */

int cti_radek(int hdl, char *radek, int max)
/************************************************/
/* Do *radek nacte ze souboru hdl radek textu   */
/* dlouhy nejvice max znaku.                    */
/* vrati: -1 pri neuspesnem cteni               */
/*         0 pri konci souboru                  */
/*           +n pri uspechu (pocet znaku)       */
/*           vyjimka (prazdny radek)  n == 1    */
/************************************************/
{
 int i = 0, kod;
 char *s = radek, ch = 0;

 while ((i < max)		/* neprekrocena max. delka	*/
	&& ((kod = read(hdl, &ch, sizeof(char))) == sizeof(char))
		/* ^ korektne nacteno a dosud neni konec	*/
	&& (ch != '\n'))	/* neni konec radku		*/
   {
    s[i] = ch;
    i++;
   }

 s[i] = '\x0';
 return (kod > 0) ? ((i == 0) ? kod : i) : kod;
} /* int cti_radek(int hdl, char *radek, int max) */

int chyba(int er)
{
 extern int errno;
 if (er != 0)
   {
    perror(strerror(errno));
    exit(errno);
   }
 return(0);
} /* int chyba(void) */

int main(void)
{
 int handle, nacteno;
 char s[MAX_DELKA_RADKU], *jmeno = "soubor.txt";

 if ((handle = open(jmeno, O_TEXT, S_IREAD)) == -1)
   chyba(handle);


 printf("\nCteni textoveho souboru <%s> funkcemi s primym pristupem\n", jmeno);
 while ((nacteno = cti_radek(handle, s, sizeof(s) - 1)) > 0)
   {
    printf("%s\n", s);
   }

 if (nacteno == 0)
   printf("%s\n\n...KONEC...\n", s);
 else
   chyba(nacteno);

 if (close(handle) == -1)
   chyba(1);

 return(0);
} /* int main(void) */

Vystup tohoto programu na obrazovku je nasledujici16:

Cteni textoveho souboru <soubor.txt> funkcemi s primym pristupem
Toto je textovy soubor vytvoreny v C.
primy pristup - handle
    1    2    3    4    5    6    7    8    9

Uz to umim!
Jeste pridat posledni radek na konec.

...KONEC...

8.5. Datove proudyzpet

Pro praci s datovymi proudy musime pouzivat funkcni prototypy umistene v stdio.h. Zakladem pro pristup k proudu je datovy typ FILE17. Pri kazdem spusteni programu mame otevreny proudy, ktere bychom mohli deklarovat takto:

FILE *stdin;
FILE *stdout;
FILE *stderr;

Pri praci s proudy se doporucuje testovat hodnoru TMP_MAX. Toto makro je rozsirenim moznosti spolecnych pro obe varianty prace se soubory.

ANSI norma definuje dva rezimy proudu - textovy (rozlisuje radky) a binarni. Rezim stanovime pri otevirani souboru. Pro dalsi vyklad bude zrejme opet nejvhodnejsi, uvedeme-li si nejprve nejdulezitejsi funkce a konstanty pro praci s proudy, a teprve pote nekolik prikladu, ktere vycet osvetli. Pro prehlednost uvedeme skupiny funkci, rozdelene podle moznosti jejich pouziti.

Pro praci s proudy mame k dispozici znacne mnozstvi funkci, maker a globalnich promennych. Jejich plny vycet je nad ramec tohoto textu. Proto se omezime na ty z nich, ktere povazujeme za vyznamne. Ostatni muzeme vyhledat v referencnich priruckach C, nebo v elektronicke dokumentaci primo u pocitace.

Otevreni a zavreni prouduzpet

Kazdy proud mame moznost otevrit a uzavrit. Pri otevreni urcujeme rezim naseho pristupu k datum v proudu. Ve specialnich pripadech ocenime i moznost proud znovuotevrit ci spojit s novym souborem.

FILE *fopen(const char *filename, const char *mode);

Je funkce, vracejici ukazatel na strukturu FILE v pripade uspesneho otevreni proudu. Pri neuspechu vraci hodnotu NULL. Konstantni retezec filename, oznacuje jmeno souboru podle konvenci prislusneho OS. Retezec mode urcuje rezim prace se souborem i jeho typ. Zakladni moznosti jsou uvedeny v tabulce:

retezec vyznam (otevreni pro:)
r cteni
w zapis
a pripojeni
r+ aktualizace (update) - jako rw
w+ jako vyse (r+), ale existujici proud orizne na nulovou delku, jinak vytvori novy
a+ pro aktualizaci, pokud neexistuje, vytvori
t textovy tezim
b binarni rezim

Poznamenejme, ze otevreni proudu v binarnim rezimu vyzaduje vzdy pismeno b v retezci mode, napriklad: "a+b", "wb", ... . Obdobne pro textovy rezim je vhodne uvest jako soucast retezce mode znak t.18

int fclose(FILE *stream);

Je funkce uzavirajici urceny proud. V pripade uspechu vrati hodnotu 0, jinak EOF. Uvolni pamet vyhrazenou pro strukturu FILE * a vyprazdni pripadnou vyrovnavaci pamet.

FILE *freopen(const char *filename, const char *mode, FILE *stream);

Funkce uzavre soubor asociovany s proudem stream (jako pri volani fclose()). Pak otevre soubor jmenem filename (jako pri fopen(filename, mode)). Navratovou hodnotou je v pripade uspechu otevreny proud, jinak nulovy ukazatel NULL.

Proudy a vstup/vystup znakuzpet

Znak je po nacteni z proudu konvertovan bez znamenka na typ int. Obdobne je pri zapisu do proudu konvertovan opacnym postupem. Tak mame ponechanu moznost rozlisit konec souboru od dalsiho nacteneho znaku.

Pripomenme si cteni a zapis znaku z/do standardniho vstupu/vystupu. Pri pohledu na nasledujici funkce je vyznam maker getchar() a putchar() zcela zrejmy. Staci proste doplnit druhy argument odpovidajici funkce hodnotou stdin ci stdout.

int getc(FILE *stream);

V pripade uspesneho nacteni znaku z proudu jej bez znamenka prevede na typ int. Takto ziskana hodnota je hodnotou navratovou. V pripade chyby, nebo dosazeni konce proudu pro getc(), vraci EOF.

int ungetc(int c, FILE *stream);

Je-li c ruzne od EOF, ulozi jej funkce ungetc() do datoveho objektu s adresou stream a pripadne zrusi priznak konce souboru. Naslednym ctenim z tohoto proudu ziskame nami zapsanou hodnotu. Je-li c rovno EOF, nebo nemuze-li zapis probehnout, vraci funkce EOF. Jinak vraci c (presneji (unsigned char) c).

int putc(int c, FILE *stream);

Zapise znak c do proudu stream. Vrati stejnou hodnotu, jako zapsal. V pripade chyby, nebo dosazeni konce proudu pro getc(), vraci EOF.

Proudy a vstup/vystup retezcuzpet

Z proudu nemusime cist pouze jednotlive znaky. Muzeme nacitat i cele radky19. Ty jsou ukonceny prechodem na novy radek. Pro vyssi bezpecnost musime pri cteni uvest velikost vyrovnavaci pameti. Pri zapisu to pochopitelne nutne neni. Do proudu se zapise cely retezec az po koncovou zarazku (ovsem bez ni).

char *fgets(char *s, int n, FILE *stream);

Nacte retezec (radek az po jeho konec) z proudu stream do vyrovnavaci pameti s, nejvyse dlouhy n-1 znaku. Vrati ukazatel na tento retezec (vyrovnavaci pamet), nebo, pri chybe, NULL.

int fputs(const char *s, FILE *stream);

Zapise do proudu retezec ukonceny zarazkou. Ani zarazku, ani pripadny konec radku (obsazeny na konci retezce) do proudu nezapise. V pripade uspechu vrati pocet zapsanych znaku (delku retezce), jinak EOF.

Formatovany vstup/vystup z/do prouduzpet

Jestlize jsme zvladli standardni formatovany vstup a vystup, nemuze nam cinit formatovany vstup a vystup z a do proudu potize. Funkce se navzajem lisi pouze uvodnim f a identifikaci proudu jako prvniho argumentu. Prave srovnani je duvodem, proc uvadime odpovidajici funkce ve dvojici.

int fprintf (FILE *stream, const char *format [, argument, ...]);
int printf ( const char *format [, argument, ...]);

int fscanf (FILE *stream, const char *format [, address, ...]);
int scanf ( const char *format [, address, ...]);

Proudy a blokovy prenos datzpet

Blokovy prenos dat je nezbytny pri praci s binarnim proudem. Pokud srovname tyto funkce s odpovidajicimi funkcemi pro soubory s primym pristupem, pochopime, jak se navzajem lisi. Funkci pro primy pristup je pomerne malo. Predstavuji zakladni operace, ktere pri praci programator potrebuje. A to je vse. Funkce pro praci s proudy umi vselijake jemnustky. Je jich diky tomu pomerne mnoho a programator se musi umet rozhodnout, kterou z nich pro dany ucel pouzije. Nektere jejich argumenty muzeme dokonce povazovat za nadbytecne. Zminujeme se o tom prave zde, nebot to muzeme nazorne dokumentovat. Pri blokovem proudovem IO musim urcit, kolik polozek chci prenest a jaka je velikost jedne polozky. Prosty soucin techto hodnot by jiste kazdy zvladnul20. Pocet argumentu by se o jeden snizil.

Typ size_t, pouzity v nasledujicich funkcich, je zaveden pro urceni velikosti pametovych objektu a pripadne poctu. Dava nam rovnez najevo, ze je implementacne zavisly. Kdyby misto nej bylo pouze unsigned int, nemuseli bychom si hned uvedomit, ze v 16-ti bitovem systemu je velikost polozky omezena na 65535 bajtu.

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Precte z proudu stream polozky o velikosti size v poctu n jednotek do pametove oblasti urcene ukazatelem ptr. V pripade uspechu vrati pocet nactenych polozek. Jinak vrati pocet mensi (pravdepodobne to bude nula). Mensi navratovou hodnotu, nez je zadany pocet polozek, muzeme ziskat napriklad pri dosazeni konce proudu pred nactenim pozadovaneho poctu polozek. Nactene polozky jsou platne.

size_t fwrite(const void *ptr, size_t size, size_t n, FILE*stream);

Tato funkce ma argumenty obdobneho vyznamu, jako funkce predchozi. Pochopitelne s tim, ze provadi zapis polozek do proudu.

Dalsi uzitecne funkcezpet

Protoze je pocet dosud neuvedenych funkci pro praci s proudy velmi znacny, shrneme pod nazvem Dalsi uzitecne funkce alespon nektere z nich.

int feof(FILE *stream);

Je funkce, umoznujici zjistit dosazeni konce proudu. Jeji navratova hodnota je - true (t.j. ruzna od nuly), nachazime-li se na konci proudu, nebo - false (nula) - jinak.

int fflush(FILE *stream);

Ma-li proud prirazenu vyrovnavaci pamet, provede jeji vyprazdneni (zapis) do souboru. Jinak neprovede nic. Navratova nula svedci o uspechu, EOF signalizuje chybu.

int fseek(FILE *stream, long offset, int whence);

Prenese aktualni pozici CP v proudu stream na stanovene misto. To je urceno posunem offset vzhledem k pocatku, aktualni pozici, nebo konci souboru. Vztazny bod urcuje argument whence. Preddefinovane konstanty jsou stejne, jako v pripade primeho pristupu - tj. lseek() (jsou to SEEK_SET, SEEK_CUR, SEEK_END). Poznamejme, ze pouziti teto funkce zrusi ucinek bezprostredniho predesleho ungetc().

long ftell(FILE *stream);

Vrati aktualni pozici v proudu. To je pro binarni proud pocet bajtu vzhledem k zacatku. V pripade chyby vrati -1L a nastavi globalni promennou errno na kladne cele cislo.

Jen zkratkovite uvedeme jeste nektere dalsi uzitecne funkce:

ferror() informuje o pripadne chybe pri praci s proudem.

clearerr() rusi nastaveni priznaku chyby a konce proudu.

perror() zobrazi retezec chyboveho hlaseni na standardni chybove zarizeni (obvykle je to konsola).

tmpfile() otevre prechodny soubor v binarnim rezimu pro aktualizaci. S prechodnymi soubory jsou spjaty jeste funkce tmpnam() a tempnam().

Dvojice fgetpos() a fsetpos() umoznuje uchovat (ziskat) pozici v proudu a pak ji (opetne) nastavit.

setbuf() a setvbuf() umoznuji nastavit a pripadne modifikovat velikost vyrovnavaci pameti pro urceny proud.

Priklady prace s proudyzpet

Nasledujici priklad vychazi ze stejneho zadani, jako priklad obsazeny ve zdrojovem textu io-pv04.c. Nasim ukolem je vytvorit nekolikaradkovy textovy soubor. Ukazeme si, jak do nej zapiseme retezce i ciselne hodnoty.

Nejprve musime vytvorit datovy proud

if ((soubor = fopen(jmeno, "wt")) == NULL) chyba(1);

Retezec jmeno udava jeho jmeno pod OS. Dalsi argument funkce fopen(), retezec "wt", urcuje zpusob otevreni a rezim prace s proudem (proud vytvorime a otevreme pro zapis v textovem rezimu). Osetrujeme soucasne chybovy stav, indikovany navratovym NULL.

Pri zapisu retezce do proudu nemame zadne problemy. Diky existenci zarazky nemusime ani udavat jeho delku, staci napsat

fputs(s, soubor);

Pri zapisu cisla do proudu ovsem stejnou funkci pouzit nemuzeme. Vime totiz, ze cislo je v pameti v ulozeno v binarnim tvaru, zatimco proud ma textovy rezim. S vyhodou pouzijeme funkci pro formatovany vystup do proudu,

fprintf(soubor, "%5d", i);

jeji pouziti je obdobne, jako pri formatovanem standardnim vystupu. Pouze je navic uvedeno urceni proudu, jehoz se vystup tyka.

Po ukonceni prace nesmime zapomenout proud uzavrit,

if (fclose(soubor) == EOF) chyba(1);

Po popisu klicovych radku se podivejme na souvisly vypis zdrojoveho textu programu:

/************************************************/
/* soubor IO-DP01.C                             */
/* vytvori  textovy soubor SOUBOR.TXT           */
/* verze se streamem                            */
/************************************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <string.h>
#include <sys\stat.h>

int chyba(int er)
{
 extern int errno;
 if (er != 0)
   {
    perror(strerror(errno));  /* zapise chybove hlaseni do stderr */
    exit(errno);              /* -> DOS; ukonci chod programu     */
   }
 return er;
} /* int chyba(void) */

int main(void)
{
 FILE *soubor;
 int i;
 char *s, *jmeno = "soubor.txt";

 if ((soubor = fopen(jmeno, "wt")) == NULL)
        chyba(1);

 s = "Toto je textovy soubor vytvoreny v C.\n"
     "pristup pomoci proudu - FILE *\n";
 fputs(s, soubor);
 for (i = 1; i < 10; i++)
        {
         fprintf(soubor, "%5d", i);
        } /* for (i = 1; i < 10; i++) */

 fputs("\n\n", soubor);

 s = "Uz to umim!\n";
 fputs(s, soubor);

 s = "Jeste pridat posledni radek na konec.";
 fputs(s, soubor);

 if (fclose(soubor) == EOF)
        chyba(1);

 return 0;
} /* int main(void) */

Povsimneme si jeste zajimave skutecnosti. Poslednim prikazem fputs() zapiseme do proudu retezec, nikoli ovsem symbol konce radku. Protoze se jedna o posledni text, umisteny do souboru, bude uzitecne si rici, ze konec radku je bud prechod na radek novy, nebo konec souboru.

Opacnou ulohou, nez je predchozi tvorba a zapis do textoveho souboru, muze byt jeho nacteni a zobrazeni obsahu. Otevreni textoveho proudu pro cteni nam jiste vrasky na cele neudela. Pro urceni rezimu zadame "rt" - otevirame existujici textovy soubor pro cteni. Dale je uloha zdanlive rovnez jednoducha. Staci preci v cyklu cist cele radky a zobrazovat jejich obsah. Napriklad takto:

while (fgets( s, sizeof(s) - 1, soubor) != NULL)

Vse bude fungovat. Jen posledni informace, zapsana do souboru, nebude zobrazena. Naopak uzrime hlaseni o chybe. Problem tkvi v tom, ze fgets() pri poslednim cteni (spravne) vrati NULL a my neziskame zaverecny text. Nase reseni je nasledujici:

while ((nacteno = cti_radek(soubor, s, sizeof(s) - 1)) != EOF)

I kdyz je na prvni pohled zrejme, nemuzeme vynechat popis funkce cti_radek(). Jeji zahlavi je diky mnemonicky pojmenovanym argumentum samopopisne.

int cti_radek(FILE *f, char *radek, int max);

Prakticky (az na poradi) je shodne s fgets(). Vzhledem k nasemu problemu se musi lisit ve zpusobu reakce na konec souboru a v navratove hodnote. Nejprve navratova hodnota. Vnorene (dvojite) pouziti podmineneho operatoru by pro nas melo byt citelne. Mozne navratove hodnoty tedy jsou:

EOF pri chybe pri cteni

n - (kladne cele cislo) - pocet nactenych znaku retezce

vyjimka: prazdny radek (pouze ukonceni radku) n == 1.

Kod teto funkce nam ukaze, kolik toho za nas provadi standardni knihovni funkce fgets(). Cely zdrojovy text reseni naseho prikladu nasleduje.

/*******************************/
/* soubor IO-DP02.C            */
/* precte a vytiskne obsah     */
/* textoveho souboru SOUBOR.TXT*/
/* verze pouziva stream        */
/* pouziva UNIXove funkce      */
/*******************************/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <sys\stat.h>
#include <string.h>

#define MAX_DELKA_RADKU 81 /* plus jedna */

int cti_radek(FILE *f, char *radek, int max)
/************************************************/
/* Do *radek nacte ze streamu f radek textu     */
/* dlouhy nejvice max znaku.                    */
/* vrati: EOF pri neuspesnem cteni              */
/*       +n pri uspechu (pocet znaku)           */
/*       vyjimka - prazdny radek - n == 1       */
/************************************************/
{
 int i = 0, ch = 0;
 char *s = radek;
 while ((i < max)           /* neprekrocena max. delka    */
       && ((ch = getc(f)) != EOF)
              /* ^ korektne nacteno a dosud neni konec    */
       && (ch != '\n'))     /* neni konec radku           */
   {
    s[i] = ch;
    i++;
   }

 s[i] = '\x0';
 return (ch == EOF) ? ch : ((i == 0) ? 1 : i);
} /* int cti_radek(int hdl, char *radek, int max) */

int main(void)
{
 int nacteno;
 FILE *soubor;
 char s[MAX_DELKA_RADKU], *jmeno = "soubor.txt";

 if ((soubor = fopen(jmeno, "rt")) == NULL)
   chyba(1);

 printf("\nCteni textoveho souboru <%s> funkcemi pro datovy proud\n", jmeno);

 while ((nacteno = cti_radek(soubor, s, sizeof(s) - 1)) != EOF)
  {
   printf("%s\n", s);
   *s = 0x0;
  }

 if ((nacteno == EOF) && (strlen(s) != 0))
   printf("%s\n\n...KONEC...\n", s);
 else
   chyba(nacteno);

 if (fclose(soubor) == -1)
   chyba(1);

 return 0;
} /* int main(void) */

A takto vypada vystup programu IO-DP01.C zobrazeny pomoci programu IO-PV02.C:

Cteni textoveho souboru <soubor.txt> funkcemi pro datovy proud
Toto je textovy soubor vytvoreny v C.
pristup pomoci proudu - FILE *
    1    2    3    4    5    6    7    8    9

Uz to umim!
Jeste pridat posledni radek na konec.

...KONEC...

Namety pro dalsi mozne pokusy pri praci s proudy muzeme cerpat z prikladu napsanych pro ukazky prace se soubory s primym pristupem. I bez jejich prepsani a komentovani je tato kapitola velmi rozsahla.


Vysvetlivky:

1 Nase programy by se mely rozumne chovat pro vstupni radky libovolne delky. Nicmene za slusne chovani se obvykle poklada prijeti radku do nejvyse 512 znaku, jak cini mnohe Unixove programy.
2 OS nezna typ souboru. Pripony jmen souboru jsou vetsinou pouze doporucenimi. Proto zalezi jen na nas, v jakem rezimu chceme k souboru pristupovat.
3 Tim mame na mysli, ze vyber a vzajemne spojeni funkci je dano autorem takoveho programu. Unixovske filtry si propojime sami.
4 Ve skutecnosti jsou jak getchar tak putchar makra, predstavujici volani odpovidajicich funkci pracujicich se standardnim vstupnim a vystupnim proudem - getc(stdin) a putc(c, stdout).
5 Pokud nebereme v uvahu dve mozna kodovani ASCII a EBCDIC, nebot druhe se v MS-DOSu ani v Unixu nepouziva.
6 Mame pochopitelne na mysli libovolny pevne zvoleny datovy typ.
7 Nekdy se v teto souvislosti zavadi pojmy vnejsi a vnitrni jmeno souboru. Tim se rozumi jmeno souboru na urovni OS a jednoznacna identifikace souboru v ramci programu (procesu).
8 Navod na overeni uvolnovani hodnot manipulacnich cisel souboru je nasledujici. Oteviranim novych souboru se zvysuje jejich manipulacni cislo. Pokud uzavreme nektery z otevrenych souboru a posleze otevreme soubor novy, muze dostat stejne manipulacni cislo, jake mel soubor uzavreny.
9 Chceme-li napriklad precist poslednich 100 bajtu ze souboru, kde vsak zbyva jiz jen 50 bajtu, neni to chyba. Funkce read() proste vrati hodnotu 50. Skutecne precteny pocet bajtu. Z jejiho pohledu se o chybu nejedna.
10 Nezapominejme, ze nase data jsou zrejme ulozena ve vyrovnavaci pameti. Teprve po close() mame jistotu, ze vyrovnavaci pamet pro prislusny soubor je vyprazdnena.
11 Pokud napriklad potrebujeme vytvorit soubor dlouhy 500000 bajtu, mame dve moznosti. Bud budeme otrocky zapisovat napriklad 1000krat 500 bajtu. Nebo po otevreni souboru pouzijeme lseek(handle, 500000L, SEEK_SET) a je to. V prvnim pripade ovsem mame jistotu, co soubor obsahuje. Ve druhem pripade je to pravdepodobne nahodny pozustatek predchozich dat. Rychlost provedeni je ovsem velmi rozdilna.
12 V prave vytvorenem (ci otevrenem) souboru se nachazime na jeho pocatku.
13 Takovy pristup muzeme provest napriklad tehdy, trva-li vypocet hodnot prilis dlouho. Pak je vyhodnejsi hodnoty jedenkrat vypocist a ulozit do souboru.. Dale budeme data pouze cist.
14 Vzdyt jiste staci char *s a dale jen s = "Uz to umim!\n". Tedy bez nutnosti kopirovani. Pouze nastavime ukazatel s na pocatek retezce. Navic bychom usetrili i misto pro vyrovnavaci pamet.
15 Chceme-li byt velmi opatrni, zvolime jako maximalni delku radku 512 znaku. Mnoho unixovskych programu ma prave toto omezeni.
16 Nesmime zapomenout soubor.txt nejprve predchozim spustenim drive uvedeneho programu vytvorit.
17 Jeho definici muzeme nalezt i v kapitole Odvozene a strukturovane typy dat.
18 Pri startu programu je jako default hodnota aktivni rezim textovy. Tento stav je ovsem mozno zmenit nastavenim hodnot O_TEXT, pripadne O_BINARY v globalni promenne _fmode (je deklarovana v stdlib.h a v fcntl.h (zde se rovnez nachazeji O_... konstanty).
19 Mame-li dostatecne velikou vyrovnavaci pamet.
20 Tim spise, kdyz napiseme pouze operator nasobeni mezi operandy. Praci za nas vykona stroj.


Predchozi kapitola

Obsah

Zacatek

Nasledujici kapitola


Nazev: Programovani v jazyce C
Autor: Petr Saloun
Do HTML prevedl: Kristian Wiglasz
Posledni uprava: 29.11.1996