);
Hasonlóan működik, mint a scanf, csakhogy az fp állományból (általában szöveges) történik az olvasás. A sikeresen beolvasott változók számát téríti vissza. Az állományvégjel olvasásakor -1(EOF)et térít vissza. Az előbbi függvényeket általában szöveges állományok esetében használjuk, az alábbi kettőt viszont kifejezetten binárisoknál. A size_t típus az stdio.h header-állományban van definiálva, és rendszerint előjel nélküli egész. – Adott számú adatblokk kiírása állományba : size_t fwrite(const void*, size_t<m>, size_t, FILE * fp); A p címről nr darab m byte méretű blokkot kiír az állományba.
Visszatéríti a sikeresen kiírt blokkok számát. – Adott számú adatblokk beolvasása állományból: size_t fread(void*, size_t <m>, size_t , FILE * fp); Beolvas az állományból a p címre nr darab m byte méretű blokkot.
Visszatéríti a sikeresen beolvasott blokkok számát. Hibalekérdezés : int ferror(FILE * fp);
„Igaz”-at térít vissza, ha egy állományművelet kapcsán hiba adódott. Ellenkező esetben értéke „hamis”. Minden olyan állományhoz, amelyhez lehetséges a véletlen hozzáférés (floppy, merevlemez stb.), kapcsolódik egy pozíciómutató, amely az állományban az aktuális pozíciót (karaktert vagy byte-ot) jelöli. Az írás- és olvasásműveletek mindig ettől a pozíciótól kezdődően történnek. A pozíciómutató léptetése automatikusan történik a beolvasott vagy kiírt adatokat követő pozícióra. Ha létrehozásra és írásra nyitunk meg egy állományt (wmód), akkor kezdetben csak az állományvégjelt tartalmazza, és a pozíciómutató ide mutat. Minden kiírást követően a pozíciómutató automatikusan továbblép az állományvégjelre. Hasonlóan működik az állomány végére történő írás is (a-mód), csakhogy ez esetben az állomány megnyitáskor
“C_Programozas” — 2008/6/2 — 17:13 — page 204 — #200
204
11. ÁLLOMÁNYKEZELÉS (INPUT/OUTPUT MŰVELETEK)
már létezik, a pozíciómutató viszont ugyancsak az állományvégjelre mutat. Olvasásra való megnyitáskor (r-mód) a pozíciómutató kezdetben az állomány elejére mutat. Például egy szöveges állományt – amely alapvetően egy karaktersor – az alábbi módon képzelhetünk el (feltételeztük, hogy az állomány első sorában egy személy vezeték- és keresztneve található szóközzel elválasztva, a második sorában pedig az illető személy születési éve). A nyilak azt szemléltetik, miként mozdul el az állománymutató az egymást követő olvasási műveletek által (a fscanf függvény működését részletesen tárgyaljuk a 11.2. alfejezetben):
N a g y
I s t v á n 1 9 6 7 <EOF>
FILE *f; char knev[30], vnev[30]; int ev; f = fopen("szemely.txt", "r"); fscanf(f, "%s", vnev); fscanf(f, "%s", knev); fscanf(f, "%d", &ev);
Pozíciómutató-lekérdezés és -mozgatás Állományvégjel érzékelése : int feof(FILE * fp);
Nullától különböző értéket (logikai igaz) térít vissza, ha az állományvégjelt próbáltuk kiolvasni. A pozíciómutatónak az állomány elejére állítása : void rewind(FILE * fp);
A pozíciómutató beállítása :
int fseek(FILE * fp, long , int ); Elmozdítja nr byte-tal a pozíciómutatót a viszonyítási_pont-
hoz képest. A viszonyítási_pont paraméter lehetséges értékei: SEEK_SET (állomány eleje) SEEK_CUR (pozíciómutató aktuális pozíciója)
“C_Programozas” — 2008/6/2 — 17:13 — page 205 — #201
11.1. ÁLLOMÁNYKEZELŐ FÜGGVÉNYEK
205
SEEK_END (állomány vége) Nullát térít vissza sikeres művelet esetén. A pozíciómutató lekérdezése : long ftell(FILE * fp);
Visszatéríti a pozíciómutató aktuális pozícióját. Hiba esetén -1-et térít vissza. Háttér-információ az állománykezeléssel kapcsolatban : A belső memória és a háttértárak közötti adatcsere köztudottan lassú. Az adatsín hatékonyabb kihasználása érdekében kidolgoztak egy pufferelt rendszert az I/O műveletek lebonyolítására. Például állományba való íráskor rendkívül lefoglalná az adatsínt, ha minden byte-tal egyenként szaladnánk valamely perifériára. Ennek elkerülése érdekébe az állomány megnyitásakor általában létrejön a memóriában az állomány részére egy úgynevezett memóriapuffer. Ez úgy működik, mint egy raktár. Az állományba író függvények az adatokat egyelőre csak ezen pufferbe írják be, és ha feltelt a puffer, akkor egyszerre, egyetlen háttértár-hozzáféréssel, a puffer teljes tartalma kiíródik az állományba. Valami hasonló történik olvasáskor is. A háttértárról a pufferbe egyszerre egy egész puffernyi adat kerül beolvasásra, és innen olvasnak majd az állományolvasó függvények. Az imént felvázolt mechanizmusnak van egy mellékhatása : amit kiírunk az állományba, az csak később – a puffer ürítése után – jut el effektíve a háttértárra (automatikus pufferürítésre kerül sor, valahányszor feltelik a puffer, a program befejeztével, illetve az fclose függvényhívás hatására). Létezik azonban egy függvény, amelynek segítségével programból ellenőrizhetjük, hogy mikor történjen a pufferürítés. Memóriapuffer ürítése : int fflush(FILE * fp);
Ha fp bemeneti állományt (csak olvasni lehet belőle) képvisel (például a billentyűzetet), akkor egyszerűen törlődik a puffer tartalma. Amennyiben fp-ét írásra nyitottuk meg, a puffer tartalma beíródik az állományba. A függvény nullát térít vissza sikeres művelet esetén, és EOF-et, azaz -1-et, ha valamilyen hiba történt. Nem pufferelt I/O műveletek esetén (például képernyőre való írás vagy billentyűzetről való nem pufferelt olvasás (getch, getche)) az fflush hatástalan. Példa: Tegyük fel, hogy egy egysoros szöveges állomány az angol ábécé nagybetűit tartalmazza ábécésorrendben. Minden magánhangzót írjunk át kisbetűre.
“C_Programozas” — 2008/6/2 — 17:13 — page 206 — #202
206
11. ÁLLOMÁNYKEZELÉS (INPUT/OUTPUT MŰVELETEK)
FILE * f; char c; f = fopen("abc.txt", "r+t"); while (!feof(f)) { c = fgetc(f); if (!feof(f) && (c==‘A’ || c==‘E’ || c==‘I’ || c==‘O’ || c==‘U’)) { fseek(f, -1, SET_CUR); fputc(c + (‘a’ - ‘A’), f); fflush(f); /* minden karakterátírás után beírjuk (visszaírjuk) a puffer tartalmát az állományba */ } } fclose(f); Ha nem szerepelne az fflush a kódban, akkor megállítva a programot közvetlen az fclose előtt, még semmilyen változás nem lenne észlelhető az abc.txt állományban. A fenti példa azt is megmutatja, hogy az fseek
használatát is a pufferelt állománykezelés teszi lehetővé. Megjegyzés. Bináris állományok esetén a -1 érték (az EOF értéke) lehet az állomány valamely elemének értéke is. De egyes függvények az EOF visszatérítésével jelzik a hibát vagy azt, hogy állományvégjelnél vagyunk. Azért, hogy biztosak legyünk a visszatérített -1-es jelentésében, használjuk a hibaellenőrzésre az ferror, az állományvégjel érzékelésére pedig az feof függvényeket. Bezárt állományok letörölhetők, illetve átnevezhetők egy C programból. Állomány törlése : int remove(<állomány_név>);
Nullát térít vissza sikeres művelet esetén. Állomány átnevezése : int rename(, <új_állomány_név>);
Nullát térít vissza sikeres művelet esetén. Még egy kérdés tisztázásra vár: ha stream-eken keresztül minden állomány (a billentyűzet és a képernyő is) egyformának látszik, és ebből adódóan ugyanazon függvénykészletet használjuk bármilyen állomány kezelésére, akkor mi a helyzet a printf és scanf, illetve egyéb olyan
“C_Programozas” — 2008/6/2 — 17:13 — page 207 — #203
11.1. ÁLLOMÁNYKEZELŐ FÜGGVÉNYEK
207
függvényekkel, amelyek kifejezetten a billentyűzetről való olvasásra és a képernyőre való írásra szolgálnak? Létezik az ANSI C-ben három elődefiniált állománypointer (stream), stdin, stdout és stderr. stdin a billentyűzethez (standard bemenet) rendelt stream-re mutat, stdout és stderr pedig a képernyőhöz (standard kimenet) rendeltre. A billentyűzet-állomány, illetve a képernyőállomány megnyitása, illetve bezárása (minden ehhez kapcsolódó művelettel) automatikusan történik a programfutás kezdetekor, illetve befejeztével. Így bármely állománykezelő függvényben, ha állománypointerként stdin-t, illetve stdout-ot használunk, akkor az a billentyűzetre, illetve a képernyőre fog vonatkozni. Következésképpen az alábbi vázlatos függvényhívások teljesen egyenértékűek: fscanf(stdin, ...); fprintf(stdout, ...); fputs(..., stdout); fgets(..., stdin); fputc(..., stdout); fgetc(stdin);
scanf(...); printf(...); puts(...); gets(...); putchar(...); getchar();
Miért vezették akkor be a második oszlopban lévő függvényeket? A programozó kényelméért. Egy másik érdekes tulajdonság, amely kihasználható a standard stream-ek esetében, az, hogy (stdin, stdout, stderr) átirányíthatók az freopen függvény segítségével. Stream- (fluxus-) átirányítás : FILE * freopen(<állomány_név>, <mód>, FILE * <stream>);
Egy létező stream-hez (állománypointerhez) új állományt rendel. Példa: char s[80]; freopen("ki.txt", "w", stdout); printf("Írd be a karakterláncot:"); /* ki.txt-be ír */ gets(s); /* billenty˝ uzetr˝ ol olvas */ printf("%s",s); /* ki.txt-be ír */ A fenti példában átirányítottuk az stdout stream-et a képernyőről a ki.txt állományra, aminek következtében a printf az illető állományba ír.
“C_Programozas” — 2008/6/2 — 17:13 — page 208 — #204
208
11. ÁLLOMÁNYKEZELÉS (INPUT/OUTPUT MŰVELETEK)
11.2. A scanf/fscanf, illetve printf/fprintf függvények közelebbről Csak a scanf és a printf függvényeket fogjuk bemutatni, hiszen az fscanf és az fprintf hasonlóan működnek. Az alábbi információk kiegészítik azt, amit a 2. fejezet tartalamaz a szóban forgó függvényekkel kapcsolatban. 11.2.1. A scanf függvény Szintaxisa : int scanf(