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.
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
.
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; }
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; }
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
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.
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.
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...
Pro praci s datovymi proudy musime pouzivat
funkcni prototypy umistene v stdio.h
. Zakladem
pro pristup k proudu je datovy typ FILE
17. 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.
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
.
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
.
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
.
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, ...]);
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.
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.
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.
Nazev: | Programovani v jazyce C |
Autor: | Petr Saloun |
Do HTML prevedl: | Kristian Wiglasz |
Posledni uprava: | 29.11.1996 |