KURZUS: Programozás alapjai

MODUL: IV. modul

12. lecke: Magas szintű bemenet, kimenet

Ebben a fejezetben a magas szintű ki- és bemenet kezelésével foglalkozunk, ami egységes szemléletben teszi lehetővé a billentyűzet, a képernyő és hasonló hardver elemek, illetve fájlok kezelését. Megismerkedünk a folyam fogalmával, azok használatával, a különféle típusú folyamok eltérő pufferezési módjával, a legkülönfélébb fájlkezelési műveletekkel, és időnként megtapasztaljuk majd a magas szintű megközelítés hátulütőit is.

A lecke végére a hallgatónak tisztában kell lennie

  • A folyam fogalmával, a reprezentálására használt struktúra használatának módjával, a folyamkezelés fő lépéseivel.
  • Ismernie kell a folyamok megnyitásának különféle módjait (olvasás/írás/hozzáfűzés, szöveges/bináris mód).
  • A különféle folyamtípusok alapértelmezett pufferezési módjaival.
  • Folyamon belüli pozícionálás és jelenlegi pozíció lekérdezési lehetőségeivel.
  • Folyamból történő olvasás és írás elvégzésének módjaival (karakterenként/soronként/mezőnként, blokkokban).
  • Ki- és bemeneti hibák kezelésének lehetséges módjaival.
  • Előre definiált folyamok részleteivel.
  • Formázott ki- és bemenet képességeinek részleteivel.
  • Gyakran használt fájlműveletek elvégzésének módjával, mint pl. törlés, átnevezés.

A lecke kulcsfogalmai: folyam, fájlazonosító, FILE struktúra, szöveges és bináris fájl, transzláció, írás, olvasás, hozzáfűzés, felújítás, pufferezetlenség, sorpufferezettség, teljes pufferezettség, fájlmutató, cső, átirányítás.

Áttekintés

A magas szintű bemeneten és kimeneten olyan folyam, áram (stream) jellegű fájl, ill. eszköz (nyomtató, billentyűzet, képernyő stb.) kezelést értünk, ami a felhasználó szempontjából nézve szinte nincs tekintettel a mögöttes hardverre, s így a lehető legflexibilisebb kimenetet, bemenetet biztosítja.

A valóságban a folyamot egy FILE típusú struktúrára mutató mutatóval manipuláljuk. Ezt a struktúrát, a folyamkezelő függvények prototípusait stb. az stdio.h fejfájlban definiálták. A struktúra például legyen a következő!

typedef struct{
  short level; /* Puffer telítettségi szint. */
  unsigned short flags; /* Fájl állapotjelzők. */
  char fd; /* Fájl leíró. */
  unsigned char hold; /* ungetc kar., ha nincs puffer. */
  int bsize; /* A puffer mérete. */
  unsigned char *buffer;/* A puffer címe. */
  unsigned char *curp; /* Aktuális pozíció a pufferben. */
  /* . . . */
} FILE;

A programunkban

FILE *fp;

deklarációs utasítással FILE típusú struktúrára mutató mutatót kell deklarálni, mely értéket a folyamot megnyitó fopen, freopen függvényektől kap. Tehát használat előtt a folyamot meg kell nyitni. Megnyitása a folyamot egy fájlhoz, vagy egy eszközhöz kapcsolja. Jelezni kell azt is ilyenkor, hogy a folyamot csak olvasásra, vagy írásra, vagy mind kettőre kívánjuk használni stb. Ezután elvégezhetjük a kívánt bemenetet, kimenetet a folyamon, majd legvégül le kell zárni.

Folyamok megnyitása

FILE *fopen(const char *fajlazonosito, const char *mod);

A függvény megnyitja a fajlazonositoval megnevezett fájlt, és folyamot kapcsol hozzá. Visszaadja a fájlinformációt tartalmazó FILE struktúrára mutató mutatót, mely a rákövetkező műveletekben azonosítani fogja a folyamot, ill. NULL mutatót kapunk tőle, ha a megnyitási kísérlet sikertelen volt.

A fajlazonosito természetesen tartalmazhat (esetleg meghajtó nevet) utat is, de a maximális összhossza FILENAME_MAX karakter lehet.

A második paraméter mod karakterlánc meghatározza a későbbi adatátvitel irányát, helyét és a folyam típusát. Nézzük a lehetőségeket!

rMegnyitás csak olvasásra.
wLétrehozás írásra. A már létező, ilyen azonosítójú fájl tartalma megsemmisül.
aHozzáfűzés: megnyitás írásra a fájl végén, vagy létrehozás írásra, ha a fájl eddig nem létezett.
r+Egy létező fájl megnyitása felújításra (írásra és olvasásra).
w+Új fájl létrehozása felújításra. A létező fájl tartalma elvész.
a+Megnyitás hozzáfűzésre: a fájl végén felújításra, vagy új fájl létrehozása felújításra, ha a fájl eddig nem létezett.

A folyam típusa szöveges (text), vagy bináris lehet. A szöveges folyam a bemenetet és a kimenetet sorokból állóknak képzeli el. A sorok végét egy '\n' (LF) karakter jelzi. Lemezre történő kimenet esetén bizonyos operációs rendszereken (pl. Windows) a folyam a sorlezáró '\n' karaktert "\r\n" karakter párral (CR-LF) helyettesíti. Megfordítva: lemezes bemenetnél a CR-LF karakter párból ismét LF karakter lesz. Ezt a manipulációt transzlációnak nevezzük. Bemenet esetén a folyam a 0x1A értékű karaktert fájlvégnek tekinti. Összegezve: a szöveges folyam bizonyos, kitüntetett karaktereket speciálisan kezel, míg a bináris folyam ilyent egyetlen karakterrel sem tesz.

A mod karakterláncban expliciten megadhatjuk a folyam típusát. A szövegest a 't', a binárist a 'b' jelöli. A folyamtípus karakter a karakterláncban az első betű után bárhol elhelyezhető, azaz megengedettek az rt+, r+t stb. Nem kötelező azonban a folyamtípust a mod karakterláncban expliciten megadni. Ha elhagyjuk, alapértelmezés a szöveges.

Ha a folyamot felújításra (update) nyitották meg, akkor megengedett mind a bemenet, mind a kimenet. A kimenetet azonban fflush, vagy pozícionáló (fseek, rewind stb.) függvény hívása nélkül nem követheti közvetlenül bemenet. A fordított adatirányváltás is csak fájlvégen, vagy e függvények hívásának közbeiktatásával valósítható meg.

Folyamok pufferezése

A fájlokhoz kapcsolt folyamok szokásosan pufferezettek, s a puffer lefoglalása megnyitáskor automatikusan megtörténik malloc hívással. Ez is megengedi azonban az "egy karakteres szintű" bemenetet, kimenetet (getc, putc), ami nagyon gyors. A pufferrel kapcsolatos információkat a FILE struktúra tagjai írják le:

Puffer állapotát leíró tagok a FILE struktúrában
12.1 ábra

ahol buffer a puffer kezdőcíme és bsize a mérete. A curp a pufferbeli aktuális pozícióra mutat, s level pedig számlálja, hogy még hány karakter van hátra a pufferben. A teljes pufferezettség azt jelenti, hogy kiírás automatikusan csak akkor történik, ha a puffer teljesen feltelt, ill. olvasás csak akkor következik be, ha a puffer teljesen kiürült. Egy karakter írása, vagy olvasása a curp pozícióról, ill. pozícióra történik, s a művelet mellékhatásaként a curp eggyel nő, s a level eggyel csökken.

A pufferezetlenség azt jelenti, hogy a bájtok átvitele azonnal megtörténik a fájlba (fájlból), vagy az eszközre (eszközről).

A mai operációs rendszerek legtöbbje a kisebb fájlokat megnyitásuk után valamilyen rendszer területen (cache) tartja, s a pufferek is csak a memóriabeli fájllal vannak kapcsolatban. Célszerű tehát, a programfejlesztő rendszer segítségében utánanézni, hogy az azonnali fájlba írás, vagy olvasás pontosan hogyan valósítható meg, ha igazán szükség van rá.

Megjegyezzük, hogy a setbuf és a setvbuf függvényhívásokkal kijelölhetünk saját puffert, módosíthatjuk a használatos puffer méretét, vagy pufferezetlenné tehetjük a bemenetet és a kimenetet, használatuk részleteit azonban itt nem tárgyaljuk.

A szabvány bemenet (stdin) sorpufferezett és a szabvány kimenet (stdout) pufferezetlen, ha nincsenek az operációs rendszerben átirányítva, mert ekkor mindkettő teljesen pufferezett. A sorpufferezettség azt jelenti, hogy ha a puffer üres, a következő bemeneti művelet megkísérli a teljes puffer feltöltését. Kimenet esetén mindig kiürül a puffer, ha teljesen feltelik, ill. amikor '\n' karaktert írunk bele.

Eddig csak a pufferek automatikus ürítéséről beszéltünk. Lehetséges azonban a pufferek kézi ürítése is. Sőt, adatátviteli irányváltás előtt a kimeneti puffert ki is kell üríteni. Lássuk a függvényt!

int fflush(FILE *stream);

Kimenetre nyitott folyam esetén a rutin kiírja a puffer tartalmát a kapcsolt fájlba.

Bemeneti folyamnál a függvény eredménye nem definiálható, de többnyire törli a puffer tartalmát.

Mindkét esetben nyitva marad a folyam.

Pufferezetlen folyamnál e függvény hívásának nincs hatása.

Sikeres esetben zérust kapunk vissza. Hibás esetben a szolgáltatott érték EOF.

Az fflush(NULL) üríti az összes kimeneti folyamot.

Pozícionálás a folyamokban

A folyamokat rendszerint szekvenciális fájlok olvasására, írására használják. A magas szintű bemenet, kimenet a fájlt bájtfolyamnak tekinti, mely a fájl elejétől (0 pozíció) indul és a fájl végéig tart. A fájl utolsó pozíciója a fájlméret - 1. Az adatátvitel mindig az aktuális fájlpozíciótól kezdődik, megtörténte után a fájlpozíció a fájlban következő, át nem vitt bájtra mozdul. A fájlpozíciót fájlmutatónak is szokás nevezni.

Eszközhöz kapcsolt folyam mindig csak szekvenciálisan (zérustól induló, monoton növekvő fájlpozícióval) érhető el. Lemezes fájlhoz kapcsolt folyam bájtjai azonban direkt (random) módon is olvashatók és írhatók.

Lemezes fájlok esetén a fájlmutató adatátvitel előtti beállítását az

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

függvénnyel végezhetjük el, mely a stream folyam fájlmutatóját offset bájttal az ahonnet paraméterrel adott fájlpozíción túlra állítja be. Szöveges folyamokra az offset zérus lehet, vagy egy az ftell függvény által visszaadott érték.

Az ahonnet paraméter a következő értékeket veheti fel:

  • SEEK_SET: A fájl kezdetétől.
  • SEEK_CUR: Az aktuális fájlpozíciótól.
  • SEEK_END: A fájl végétől.

A függvény elvet minden a bemenetre ungetc-vel visszarakott karaktert.

Felújításra megnyitott fájl esetén az fseek után mind bemenet, mind kimenet következhet.

A függvény törli a fájlvég jelzőt.

A függvény zérust ad vissza, ha a fájlpozícionálás sikeres volt, ill. nem zérust kapunk hiba esetén.

Írjunk fájlméretet megállapító függvényt!

#include <stdio.h>
long fajlmeret(FILE *stream) {
  long aktpoz, hossz;
  aktpoz=ftell(stream);
  fseek(stream, 0L, SEEK_END);
  hossz=ftell(stream);
  fseek(stream, aktpoz, SEEK_SET);
  return(hossz); }

A fajlmeret elteszi a pillanatnyi pozíciót az aktpoz változóba, hogy a fájl végére állítás után helyre tudja hozni a fájlmutatót. A lekérdezett fájlvég pozíció éppen a fájlméret.

Nézzük a további fájlpozícióval foglalkozó függvényeket!

long int ftell(FILE *stream);

A rutin visszaadja a stream folyam aktuális fájlpozícióját sikeres esetben, máskülönben -1L-t kapunk tőle. A

void rewind(FILE *stream);

a stream folyam fájlmutatóját a fájl elejére állítja.

Felújításra megnyitott fájl esetén a rewind után mind bemenet, mind kimenet következhet.

A függvény törli a fájlvég és a hibajelző biteket.

A FILE struktúra flags szava bitjei (állapotjelzői) a következő jelentésűek lehetnek!

#define _F_RDWR 0x0003 /* olvasás és írásjelző */
#define _F_READ 0x0001 /* csak olvasható fájl */
#define _F_WRIT 0x0002 /* csak írható fájl */
#define _F_BUF 0x0004 /* malloc pufferelt */
#define _F_LBUF 0x0008 /* sorpufferelt fájl */
#define _F_ERR 0x0010 /* hibajelző */
#define _F_EOF 0x0020 /* fájlvég jelző */
#define _F_BIN 0x0040 /* bináris fájl jelző */
/* . . . */

A megadott stream folyam aktuális fájlpozícióját helyezi el az

int fgetpos(FILE *stream, fpos_t *pos);

a pos paraméterrel adott címen. Ez az érték felhasználható az fsetpos-ban. A visszaadott érték zérus hibátlan, és nem zérus sikertelen esetben. Az

int fsetpos(FILE *stream, const fpos_t *pos);

a stream folyam fájlmutatóját állítja be a pos paraméterrel mutatott értékre.

Felújításra megnyitott fájl esetén az fsetpos után mind bemenet, mind kimenet következhet.

A függvény törli a fájlvég jelző bitet, és elvet minden, e fájlra vonatkozó ungetc karaktert.

A visszakapott érték egyezik az fgetpos-nál írottakkal.

Vegyük észre, hogy az fseek és az ftell long értékekkel dolgozik. A maximális fájlméret így 2GB lehet. Az fpos_t adattípus e korlát áttörését biztosítja, hisz mögötte akár 64 bites egész is lehet.

Bemeneti műveletek

int fgetc(FILE *stream);

A folyam következő unsigned char karakterét adja vissza előjel kiterjesztés nélkül int-té konvertáltan, s eggyel előbbre állítja a fájlpozíciót. Sikertelen esetben, ill. fájl végén EOF-ot kapunk. A

int getc(FILE *stream);

makró, mint ahogyan ez a lehetséges definíciójából is látszik, ugyanezt teszi:

#define getc(f) \
((--((f)->level)>=0) ? (unsigned char)(*(f)->curp++) :\
                      _fgetc(f))

int ungetc(int c, FILE *stream);

A függvény visszateszi a stream bemeneti folyamba a c paraméter unsigned char típusúvá konvertált értékét úgy, hogy a következő olvasással ez legyen az első elérhető karakter. A szabályos működés csak egyetlen karakter visszahelyezése esetén garantált, de a visszatett karakter nem lehet az EOF.

Két egymást követő ungetc hívás hatására már csak a másodiknak visszatett karakter érhető el, mondjuk, a következő getc-vel, azaz az első elveszik. Gondoljuk csak meg, hogyha nincs puffer, akkor a visszatételhez a FILE struktúra egyetlen hold tagja áll rendelkezésre!

Az fflush, az fseek, az fsetpos, vagy a rewind törli a bemenetre visszatett karaktert.

Sikeres híváskor az ungetc a visszatett karaktert adja vissza. Hiba esetén viszont EOF-ot kapunk tőle. Az

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

karakterláncot hoz be a stream folyamból, melyet az s címtől kezdve helyez el a memóriában. Az átvitel leáll, ha a függvény n - 1 karaktert, vagy '\n'-t olvasott. A rutin a '\n' karaktert is kiteszi a láncba, és a végéhez még záró '\0'-t is illeszt.

Sikeres esetben az fgets az s karakterláncra mutató mutatóval tér vissza. Fájlvégen, vagy hiba esetén viszont NULL-t szolgáltat.

Vegyük észre, hogy a jegyzet eleje óta használt getline függvény csak annyiban tér el az fgets-től, hogy:

  • A beolvasott karakterlánc méretét adja vissza.
  • A szabvány bemenetről (stdin) olvas, s nem más folyamból, így eggyel kevesebb a paramétere.
  • n karaktert hoz be legfeljebb, vagy '\n'-ig, de magát a soremelés karaktert nem teszi be az eredmény karakterláncba.

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

A függvény n * size bájtot olvas a stream folyamból, melyet a ptr paraméterrel mutatott címen helyez el. Visszaadott értéke nem a beolvasott bájtok száma, hanem a

beolvasott bájtok száma / size

sikeres esetben. Hiba, vagy fájlvég esetén ez persze nem egyezik n-nel.

Az eddig ismertetett bemeneti függvények nem konvertálták a beolvasott karakter(lánco)t. Az

int fscanf(FILE *stream, const char *format<, cim, ...>);

viszont a stream folyamból karakterenként olvasva egy sor bemeneti mezőt vizsgál. Aztán minden mezőt a format karakterláncnak megfelelően konvertál, és letárol rendre a paraméter cimeken. A format karakterláncban ugyanannyi konverziót okozó formátumspecifikációnak kell lennie, mint ahány bemeneti mező van.

A jelölésben a <> az elhagyhatóságot, a ... a megelőző paraméter tetszőleges számú ismételhetőségét jelenti. A bemeneti mező definíciója, a formázás és a konvertálás részletei a scanf függvény leírásában találhatók meg!

Az fscanf a sikeresen vizsgált, konvertált és letárolt bemeneti mezők számával tér vissza. Ha a függvény az olvasást a fájl végén kísérelné meg, vagy valamilyen hiba történne, akkor EOF-ot kapunk tőle vissza. A rutin zérussal is visszatérhet, ami azt jelenti, hogy egyetlen vizsgált mezőt sem tárolt le.

Kimeneti műveletek

int fputc(int c, FILE *stream);

A függvény a c unsigned char típusúvá konvertált értékét írja ki a stream folyamba. Sikeres esetben a c karaktert kapjuk vissza tőle, hiba bekövetkeztekor viszont EOF-ot. A

int putc(int c, FILE *stream);

makró, mint ahogyan ez a lehetséges definíciójából is látszik, ugyanezt teszi:

#define putc(c,f) \
((++((f)->level)<0) ? (unsigned char)(*(f)->curp++)=(c)) :\
                      _fputc((c),f))

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

A függvény az s karakterláncot kiírja a stream folyamba. Nem fűz hozzá '\n' karaktert, és a lezáró '\0' karakter sem kerül át.

Sikeres esetben nem negatív értékkel tér vissza. Hiba esetén viszont EOF-ot kapunk tőle. Az

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

a ptr címmel mutatott memória területről n * size bájtot ír ki a stream folyamba. Visszaadott értéke nem a kiírt bájtok száma, hanem a

kiírt bájtok száma / size

sikeres esetben. Hiba bekövetkeztekor ez nem egyezik n-nel.

Az eddigi kimeneti függvények nem végeztek konverziót. Az

int fprintf(FILE *stream, const char *format<, parameter, ...>);

fogad egy sor parametert, melyeket a format karakterláncnak megfelelően formáz (konvertál), és kivisz a stream folyamba. A format karakterláncban ugyanannyi konverziót okozó formátumspecifikációnak kell lennie, mint ahány parameter van.

A jelölésben a <> az elhagyhatóságot, a ... a megelőző paraméter tetszőleges számú ismételhetőségét jelenti. A formázás és a konvertálás részletei a printf függvény leírásában találhatók meg!

Az fprintf a folyamba kivitt karakterek számával tér vissza sikeres esetben, ill. EOF-ot kapunk tőle hiba bekövetkeztekor.

Folyamok lezárása

int fclose(FILE *stream);

A rutin lezárja a stream folyamot. Ez előtt azonban üríti a folyamhoz tartozó puffert, s a pufferhez automatikusan allokált memóriát fel is szabadítja. Ez utóbbi nem vonatkozik a setbuf, vagy a setvbuf függvényekkel hozzárendelt pufferekre. Ezek "ügyei" csak a felhasználóra tartoznak.

Sikeres esetben az fclose zérussal tér vissza. Hiba esetén viszont EOF-ot kapunk tőle.

Hibakezelés

Tudjuk, hogy a szabvány könyvtári függvények - így a magas szintű bemenetet, kimenetet kezelők is - a hibát, a kivételes esetet úgy jelzik, hogy valamilyen speciális értéket (EOF, NULL mutató,  HUGE_VAL stb.) adnak vissza, és az errno globális hibaváltozóba beállítják a hiba kódját. A hibakódok az errno.h fejfájlban definiált, egész, nem zérusértékű szimbolikus állandók.

Programunk indulásakor a szabvány bemeneten (stdin) és kimeneten (stdout) túl a hibakimenet (stderr) is rendelkezésre áll, s a hibaüzeneteket ez utóbbin célszerű megjelentetni. Az stderr a képernyő (karakteres ablak) alapértelmezés szerint.

Mit jelentessünk meg hibaüzenetként az stderr-en?

Természetesen bármilyen szöveget kiírathatunk, de a hibakódokhoz programfejlesztő rendszertől függően hibaüzenet karakterláncok is tartoznak, s ezek is megjelentethetők. A hibakodhoz tartozó hibaüzenet karakterlánc kezdőcímét szolgáltatja a szabványos

#include <string.h >

char *strerror(int hibakod);

függvény, s az üzenet meg is jelentethető

fprintf(stderr, "Hiba: %s\n", strerror(hibakod));

módon. A

voidperror(const char *s);

kiírja az stderr-re azt a hibaüzenetet, melyet a legutóbbi hibát okozó, könyvtári függvény hívása idézett elő. Először megjelenteti az s karakterláncot a rutin, aztán kettőspontot (:) tesz, majd az errno aktuális értékének megfelelő üzenet karakterláncot írja ki lezáró '\n'-nel.

Tehát például a perror("Hiba: ") megfelel a

fprintf(stderr, "Hiba: %s\n", strerror(errno));

függvényhívásnak.

Vigyázat! Az errno értékét csak közvetlenül a hibát okozó rutin hívása után szabad felhasználni, mert a következő könyvtári függvény megidézése felülírhatja e globális változó értékét. Ha a hibakóddal mégis később kívánnánk foglalkozni, akkor tegyük el az errno értékét egy segédváltozóba!

Folyamokkal kapcsolatban a perror s paramétere a fájlazonosító szokott lenni.

Meg kell tárgyalnunk még három, csak a folyamok hibakezelésével foglalkozó függvényt! A

void clearerr(FILE *stream);

nullázza a stream folyam fájlvég és hibajelzőjét. Ha a folyam hibajelző bitje egyszer bebillent, akkor minden a folyamon végzett művelet hibával tér vissza mindaddig, míg a hibajelzőt e függvénnyel, vagy a rewind-dal nem törlik.

A fájlvég jelző bitet egyébként minden bemeneti művelet nullázza. Az

int feof(FILE *stream);

többnyire makró, mely a következő lehetséges

#define feof(f) ((f)->flags & _F_EOF)

definíciója miatt, visszaadja a fájlvég jelző bit állapotát, azaz választ ad a fájlvég van-e kérdésre.

Az egyszer bebillent fájlvég jelző bit a következő, e folyamra vonatkozó bemeneti, pozícionáló műveletig, vagy clearerr-ig 1 marad. Az

int ferror(FILE *stream);

makró ebben a szellemben

#define ferror(f) ((f)->flags & _F_ERR)

a hiba jelző bit állapotát adja vissza.

Az egyszer bebillent hiba jelző bitet csak a clearerr és a rewind függvények törlik. Ha a kérdéses folyammal kapcsolatban a bebillent hiba jelző bit törléséről nem gondoskodunk, akkor minden e folyamra meghívott további függvény hibát jelezve fog visszatérni.

Írjuk meg az fputc segítségével az fputs függvényt!

int fputs(const char *s, FILE *stream){
  int c;
  while(c=*s++)
    if(c!=fputc(c, stream)) break;
  return ferror(stream) ? EOF : 1; }

Készítsünk szoftvert komplett hibakezeléssel, mely az első parancssori paramétere fájlt átmásolja a második paramétere azonosítójú fájlba! Ha a programot nem elég parancssori paraméterrel indítják, akkor ismertesse használatát! A másolási folyamat előrehaladásáról tájékoztasson feltétlenül!

/* PELDA30.C: Elso parameter fajl masolasa a masodikba. */
#include <stdio.h>
#include <string.h> /* strerror miatt! */
#include <errno.h>  /* Az errno vegett! */
#define KENT 10  /* Hanyankent jelenjen meg a szamlalo.*/
#define SZELES 10 /* Mezoszelesseg a szamlo kozlesehez. */
int main(int argc, char *argv[]) {
  FILE *be, *ki;  /* A be es kimeneti fajlok. */
  long szlo=0l; /* A szamlalo. */
  int c;      /* A kov. karakter es segedvaltozo. */
  printf("Az elso parameter fajl masolasa a masodikba:\n");
  if(argc<3) {
    fprintf(stderr, "Programinditas:\n"
            "PELDA30 forrasfajl celfajl\n");
    return 1; }
  if(!(be=fopen(argv[1],"rb"))) {
    perror(argv[1]);
    return 1; }
  if(!(ki=fopen(argv[2],"wb"))) {
    perror(argv[2]);
    fclose(be);
    return 1; }
  printf("%s --> %s:\n%*ld", argv[1], argv[2], SZELES, szlo);

A formátumspecifikációbeli * SZELES mezőszélességet eredményez.

  while((c=fgetc(be))!=EOF) {
    /* Olvasas fajlvegig, vagy hibaig. */
    if(c==fputc(c,ki)) {  /* Kiiras rendben. */
      if(!(++szlo%KENT)) {
        for(c=0; c<SZELES; ++c) fputc('\b', stdout);
        printf("%*ld", SZELES, szlo); }
    } else {      /* Kiirasnal hiba van. */
      perror(argv[2]);
      clearerr(ki);
      if(!fclose(ki))  /* A felkesz fajl torlese. */
        remove(argv[2]);
      else perror(argv[2]);
      fclose(be);
      return 1; } }
  /* Az olvasas EOF ertekkel fejezodott be. */
  c=errno;          /* Hibakod mentese. */
  fclose(ki);
  /* A vegso meret kiirasa: */
  for(c=0; c<SZELES; ++c) fputc('\b', stdout);
  printf("%*ld\n", SZELES, szlo);
  if(ferror(be)) {      /* Hiba volt. */
    fprintf(stderr, "%s: %s\n", argv[1], strerror(c));
    clearerr(be);
    fclose(be);
    remove(argv[2]);
    return 1; }
  fclose(be);        /* Minden rendben ment. */
  return 0; }

Az stdout-ra irányuló műveletek hibakezelésével azért nem foglalkoztunk, mert ahol az sem működik, ott az operációs rendszer sem megy.

Előre definiált folyamok

Egy időben legfeljebb FOPEN_MAX, vagy OPEN_MAX folyam (fájl) lehet megnyitva. Ennek megfelelő a globális FILE struktúratömb

extern FILE _streams[];

mérete is, melyből ráadásul még az első három bizonyosan foglalt is:

#define stdin (&_streams[0])
#define stdout (&_streams[1])
#define stderr (&_streams[2])

A globális FILE struktúratömb neve persze lehet ettől eltérő is.

Ezek az előre definiált folyamok, melyek programunk futásának megkezdésekor már megnyitva rendelkezésre állnak.

NévB/KTípusFolyamAlapértelmezés
stdinbemenetszövegesszabványos bemenetCON:
stdoutkimenetszövegesszabványos kimenetCON:
stderrkimenetszövegesszabvány hibakimenetCON:

Az stdin és az stdout átirányítható a programot indító parancssorban szövegfájlba.

program < bemenet.txt > kimenet.txt

Ha nincsenek átirányítva, akkor az stdin sorpufferezett, s az stdout pedig pufferezetlen. Ilyen az stderr is, tehát pufferezetlen. A legtöbb operációs rendszerben cső (pipe) is használható. Például:

program1 | program2 | program3

program1 a rendszerben beállított szabvány bemenettel rendelkezik. Szabvány kimenete szabvány bemenete lesz program2-nek, aminek szabvány kimenete program3 szabvány bemenete. Végül program3 szabvány kimenete az, amit a rendszerben beállítottak.

Mindhárom előre definiált folyam átirányítható a programban is, azaz ha nem felelne meg az alapértelmezés szerint a folyamhoz kapcsolt eszköz, akkor ezt kicserélhetjük az

FILE *freopen(const char *fajlazonosito, const char *mod, FILE *stream);

függvénnyel a fajlazonositojú fájlra. A rutin első két paraméterének értelmezése és visszaadott értéke egyezik az fopen-ével. A harmadik viszont az előre definiált folyam: stdin, stdout vagy stderr.

Az freopen persze nem csak előre definiált folyamokra használható, hanem bármilyen mással is, de ez a legjellemzőbb alkalmazása.

Készítsünk programot, mely a szabvány bemenetről érkező karaktereket a parancssori paraméterként megadott szövegfájlba másolja! Ha indításkor nem adnak meg parancssori paramétert, akkor csak echózza a szoftver a bementet a kimeneten!

A feladatot az stdout átirányításával oldjuk meg.

/* PELDA31.C: Bemenet masolasa fajlba stdout-kent. */
#include <stdio.h>
#include <stdlib.h> /* A system rutin miatt! */
#define PUFF 257  /* A bemeneti puffer merete. */
int main(int argc, char *argv[]) {
  char puff[PUFF];/* Bemeneti puffer. */
  if(system(NULL)) system("CLS");
  printf("A szabvany bemenet fajlba masolasa "
        "Ctrl+Z-ig:\n");
  if(argc<2) printf("A program indithato igy is:\n"
                    "PELDA31 szovegfajl\n\n");
  else if(!freopen(argv[1],"wt", stdout)) {
    perror(argv[1]);
    return 1; }
  while(fgets(puff, PUFF, stdin)) {
    if(fputs(puff, stdout)<0) {
      perror(argv[1]);
      clearerr(stdout);
      if(!fclose(stdout)) remove(argv[1]);
      else perror(argv[1]);
      return 1; } }
  return 0; }

Az stdlib.h bekapcsolásával rendelkezésre álló

int system(const char *parancs);

rutin parancs paraméterét átadja végrehajtásra az operációs rendszernek (a parancsértelmezőnek), azaz végrehajtatja a rendszerrel a parancsot. A függvény visszatérési értéke a programfejlesztő rendszertől függ, de többnyire a parancsértelmező által szolgáltatott érték az.

Ha a parancs NULL, akkor a rutin a parancsértelmező létezéséről számol be, azaz ha van, nem zérussal tér vissza, és zérust szolgáltat, ha nincs.

Bemenet az stdin-ről

int getchar(void);

A függvény makró, azaz:

#define getchar() getc(stdin)

A

char *gets(char *s);

az fgets-hez hasonlóan karaktereket olvas az stdin-ről, melyeket rendre elhelyez a paraméter s karaktertömbben. A visszaadott értéke is egyezik az fgets-ével, azaz normál esetben s-t szolgáltatja, s fájlvég vagy hiba bekövetkeztekor NULL-t. Az stdin-ről való olvasás azonban az első '\n' karakterig tart. Magát az LF karaktert nem viszi át az s tömbbe, hanem helyette a karakterláncot záró '\0'-t ír oda.

A konverziót is végző

int scanf(const char *format<, cim, ...>);

függvény az fscanf-hez hasonlóan - de az stdin folyamból - olvasva egy sor bemeneti mezőt vizsgál. Aztán minden mezőt a format karakterláncnak megfelelően formáz (konvertál), és letárol rendre a paraméter címeken.

A jelölésben a <> az elhagyhatóságot, a ... a megelőző paraméter tetszőleges számú ismételhetőségét jelenti. A bemeneti mező definíciójára rögtön kitérünk!

A scanf a sikeresen vizsgált, konvertált és letárolt bemeneti mezők számával tér vissza. A vizsgált vagy akár konvertált, de le nem tárolt mezők ebbe a számba nem értendők bele. Ha a függvény az olvasást a fájl végén kísérelné meg, vagy valamilyen hiba következne be, akkor EOF-ot kapunk tőle vissza. A függvény zérussal is visszatérhet, ami azt jelenti, hogy egyetlen vizsgált mezőt sem tárolt le.

A format karakterláncban ugyanannyi formátumspecifikációnak kell lennie, mint ahány bemeneti mező van, és ahány cim paramétert megadtak a hívásban. Ha a formátumspecifikációk többen vannak, mint a cimek, akkor ez előre megjósolhatatlan hibához vezet. Ha a cim paraméterek száma több mint a formátumspecifikációké, akkor a felesleges cimeket egyszerűen elhagyja a scanf.

A format karakterlánc három féle objektumból áll:

  • fehér karakterekből,
  • nem fehér karakterekből és
  • formátumspecifikációkból.

Ha fehér karakter következik a format karakterláncban, akkor a scanf olvassa, de nem tárolja a bemenetről érkező fehér karaktereket egészen a következő nem fehér karakterig.

Nem fehér karakter minden más a '%' kivételével. Ha a format karakterláncban ilyen karakter következik, akkor a scanf olvas a bemenetről, de nem tárol, hanem elvárja, hogy a beolvasott karakter egyezzen a format karakterláncban levővel.

A formátumspecifikációk vezérlik a scanf függvényt az olvasásban, a bemeneti mezők konverziójában és a konverzió típusában. A konvertált értéket aztán a rutin elhelyezi a soron következő paraméterrel adott cím-en. A formátumspecifikáció általános alakja:

% <*> <szélesség> <h|l|L> típuskarakter

ahol a <> az elhagyhatóságot és a | a vagylagosságot jelöli. Nézzük a részleteket!

  • Minden formátumspecifikáció % karakterrel indul, és típuskarakterrel végződik. Az általános alakban elhagyhatónak jelölt részek csak az ott megadott sorrendben kerülhetnek a % és a típuskarakter közé.
  • A * elnyomja a következő bemeneti mező hozzárendelését. A scanf a "%*típuskarakter" hatására olvassa, ellenőrzi és konvertálja a vonatkozó bemeneti mezőt, de nem helyezi el a kapott értéket az idetartozó cim paraméteren. Tehát a bemeneti mező tartalmának ilyenkor is meg kell felelnie a konverziós típuskarakternek.
  • A szélesség maximális mezőszélességet határoz meg, azaz a scanf legfeljebb ennyi karaktert olvashat, de olvashat ennél kevesebbet is, ha fehér, vagy konvertálhatatlan karakter következik a bemeneten.
  • A h, az l és az L a cim paraméter alapértelmezés szerinti típusát módosítja. A h short int. Az l long int, ha a típuskarakter egész konverziót specifikál, ill. double, ha a típuskarakter lebegőpontos átalakítást ír elő. Az L pedig a long double módosítója.

A következő táblázatban felsoroljuk az aritmetikai konverziót okozó típuskaraktereket:

TípuskarakterAz elvárt bemenet A paraméter típusa
d decimális egészint *
i decimális, oktális vagy hexadecimális egészint *
o oktális egész (vezető 0 nélkül is annak minősül a szám)int *
u előjel nélküli decimális egészunsigned int *
xhexadecimális egész (vezető 0x vagy 0X nélkül is az a szám)int *
e, E lebegőpontos valósfloat *
f lebegőpontos valósfloat *
g, Glebegőpontos valósfloat *
  • A %d, a %i, a %o, a %x, a %D, a %I, a%O, a %X, a %c és a %n konverziók esetén unsigned char-ra, unsigned int-re, vagy unsigned long-ra mutató mutatók is használhatók azoknál az átalakításoknál, ahol a char-ra, az int-re, vagy a long-ra mutató mutató megengedett.
  • A %e, a %E, a %f, a %g és a %G lebegőpontos konverziók esetén a bemeneti mezőben levő valós számnak ki kell elégítenie a kővetkező formát:

    <+|-> ddddddddd <.> dddd <E|e> <+|-> ddd

    ahol d decimális, oktális, vagy hexadecimális számjegyet, a <> elhagyhatóságot és a | vagylagosságot jelöl.

A mutató konverzió típuskarakterei:

Típuskarakter Az elvárt bemenetA paraméter típusa
nNincs.int *. A %n-ig sikeresen olvasott karakterek számát tárolja ebben az int-ben a scanf.
pMegvalósítástól függő formában, de általában hexadecimálisan.void *

A karakteres konverzió típuskarakterei:

TípuskarakterAz elvárt bemenetA paraméter típusa
ckarakterMutató char-ra, ill. mutató char tömbre, ha mezőszélességet is megadtak. Pl.: %7c.
%% karakterNincs konverzió. Magát a % karaktert tárolja.
skarakterláncMutató char tömbre.
[keresőkészlet]karakterláncMutató char tömbre.
[^keresőkészlet]karakterláncMutató char tömbre.
  • A %c hatására a scanf a következő karaktert (akár fehér, akár nem) olvassa a bemenetről. Ha a fehér karaktereket át kívánjuk lépni, használjuk a %1s formátumspecifikációt!
  • A %szélességc specifikációhoz tartozó cim paraméternek legalább szélesség elemű karaktertömbre kell mutatnia.
  • A %s specifikációhoz tartozó cim paraméternek legalább akkora karaktertömbre kell mutatnia, melyben a vonatkozó bemeneti mező minden karaktere, és a karakterláncot lezáró '\0' is elfér.
  • A %[keresőkészlet] és a %[^keresőkészlet] alakú specifikáció teljes mértékben helyettesíti az s típuskaraktert. A vonatkozó cim paraméternek karaktertömbre kell ekkor is mutatnia. A szögletes zárójelben levő karaktereket keresőkészletnek nevezzük.
  • %[keresőkészlet] esetében a scanf addig olvassa a bemenetet, míg a bejövő karakterek egyeznek a keresőkészlet valamelyik karakterével. A karaktereket kiteszi rendre a rutin '\0'-val lezártan a paraméter karaktertömbbe. Például a %[abc]-vel az 'a', a 'b' és a 'c' karakterek valamelyikét kerestetjük a bemeneti mezőben. A %[]xyz] viszont a ']', az 'x', a 'y' és a 'z' után kutat.
  • %[^keresőkészlet] a scanf bármilyen olyan karaktert keres, ami nincs benn a keresőkészletben. Például a %[^]abc] hatására addig tart a bemenet olvasása, míg róla ']', 'a', 'b' vagy 'c' nem érkezik.

Néhány programfejlesztő rendszer esetén a keresőkészletben tartomány is megadható, azaz például a %[0123456789]-et a %[0-9] teljes mértékben helyettesíti. A tartomány kezdő karaktere kódjának azonban kisebbnek kell lenni a tartomány vég karaktere kódjánál. Nézzünk néhány példát!

  • %[-+*/]: A négy aritmetikai operátort keresi.
  • %[0-9A-Za-z]: Alfanumerikus karaktert keres.
  • %[+0-9-A-Z]: A '+', a '-', a szám és a nagybetű karaktereket keresi.
  • %[z-a]: A 'z', a '-' és az 'a' karaktereket keresi.

Tisztázzuk végre a bemeneti mező fogalmát!

  • Minden karakter a következő fehér karakterig, de a fehér karakter maga már nem tartozik bele.
  • Minden karakter az első olyan karakterig, mely az aktuális típuskarakter szerint nem konvertálható.
  • Minden karakter, míg a megadott mezőszélesség ki nem merül.
  • Keresőkészlet esetén addig tart a bemeneti mező, míg a keresőkészlet feltételeinek meg nem felelő karakter nem érkezik a bemenetről.

A bemeneti mező második alternatívája miatt, nem javasoljuk a scanf függvény széleskörű használatát programokban. Helyette olvassuk be a bemeneti karakterláncot, végezzük el rajta az összes formai ellenőrzést! Ha aztán minden rendben volt, a konverzió megvalósítható egy menetben az

int sscanf(const char *puffer, const char *format<, cim, ...>);

függvénnyel, mely ugyanazt teszi, mint a scanf, de bemeneti mezőit nem az stdin-ről, hanem az első paraméterként kapott karakterláncból veszi.

Kimenet az stdout-ra

int putchar(int c);

A függvény makró, azaz:

#define putchar(c) putc((c), stdout)

A

int puts(const char *s);

függvény a '\0' lezárású s karakterláncot az stdout folyamba írja a '\0' nélkül, mely helyett viszont kitesz még egy '\n' karaktert.

Sikeres esetben nem negatív értékkel tér vissza. Hiba bekövetkeztekor viszont EOF-ot kapunk tőle.

A konverziót is végző

int printf(const char *format<, parameter, ...>);

rutin fogad egy sor parametert, melyek mindegyikéhez hozzárendel egy, a format karakterláncban lévő formátumspecifikációt, és az ezek szerint formázott (konvertált) adatokat kiviszi az stdout folyamba.

A jelölésben a <> az elhagyhatóságot, a ... a megelőző paraméter tetszőleges számú ismételhetőségét jelenti.

A format karakterláncban ugyanannyi formátumspecifikációnak kell lennie, mint ahány parameter van. Ha kevesebb a paraméter, mint a formátumspecifikáció, akkor ez előre megjósolhatatlan hibához vezet. Ha több a paraméter, mint a formátumspecifikáció, akkor a felesleges paramétereket egyszerűen elhagyja a printf.

A rutin a folyamba kivitt bájtok számával tér vissza sikeres esetben, ill. EOF-ot kapunk tőle hiba bekövetkeztekor.

A format karakterlánc kétféle objektumot tartalmaz:

  • sima karaktereket és
  • formátumspecifikációkat.

A sima karaktereket változatlanul kiviszi az stdout-ra a printf. A formátumspecifikációhoz veszi a következő parameter értékét, konvertálja, és csak ezután teszi ki az stdout-ra.

A formátumspecifikáció általános alakja a következő:

% <jelzők> <szélesség> <.pontosság> <h|l|L> típuskarakter

  • Minden formátumspecifikáció % karakterrel kezdődik, és típuskarakterrel végződik.
  • Ha a '%' karaktert szeretnénk az stdout-ra vinni, akkor meg kell duplázni (%%).
  • Az általános alakban elhagyhatónak jelölt részek csak az ott megadott sorrendben kerülhetnek a % és a típuskarakter közé.

A következőkben leírjuk a típuskarakterek értelmezését arra az esetre, ha a formátumspecifikációban a % jelet csak a típuskarakter követi. Nézzük előbb az aritmetikai konverziót okozó típuskaraktereket:

TípuskarakterElvárt paraméterA kimenet formája
dintElőjeles decimális egész.
iintElőjeles decimális egész.
ointElőjel nélküli oktális egész vezető 0 nélkül.
uintElőjel nélküli decimális egész.
xintElőjel nélküli hexadecimális egész (a, b, c, d, e, f-fel), de vezető 0x nélkül.
XintElőjel nélküli hexadecimális egész (A, B, C, D, E, F-fel), de vezető 0X nélkül.
fdouble<->dddd.dddd alakú előjeles érték.
edouble<->d.ddd...e<+|->ddd alakú előjeles érték.
Edouble<->d.ddd...E<+|->ddd alakú előjeles érték.
gdoubleAz adott értéktől és a pontosságtól függően e, vagy f alakban előjeles érték.
G doubleUgyanaz, mint a g forma, de az e alak használata esetén az exponens részben E van.

e vagy E típuskarakter esetén a vonatkozó paraméter értékét a printf

<->d.ddd...e<+|->ddd

alakúra konvertálja, ahol:

  • Egy decimális számjegy (d) mindig megelőzi a tizedes pontot.
  • A tizedes pont utáni számjegyek számát a pontosság határozza meg.
  • A kitevő rész mindig legalább két számjegyet tartalmaz.

f típuskarakternél a vonatkozó paraméter értékét a printf

<->ddd.ddd...

alakúra konvertálja, s a tizedes pont után kiírt számjegyek számát itt is a pontosság határozza meg.

g vagy G típuskarakter esetén a printf a vonatkozó paraméter értékét e, E, vagy f alakra konvertálja

  • Olyan pontossággal, melyet a szignifikáns számjegyek száma meghatároz.
  • A követő zérusokat levágja az eredményről, és a tizedes pont is csak akkor jelenik meg, ha szükséges, azaz van még utána értékes tört számjegy.
  • A g e, vagy f formájú, a G pedig E, vagy f alakú konverziót okoz.
  • Az e, ill. az E formát akkor használja a printf, ha a konverzió eredményében a kitevő nagyobb a pontosságnál, vagy kisebb -4-nél.

A karakteres konverzió típuskarakterei:

Típuskarakter
Elvárt paraméterA kimenet formája
%nincsNincs konverzió. Maga a % karakter jelenik meg.
cintEgyetlen karakter.
schar*A karakterlánc karakterei megjelennek a záró '\0'-t kivéve. Ha megadtak pontosságot, akkor legfeljebb annyi karaktert ír ki a printf.

A mutató konverzió típuskarakterei:

TípuskarakterElvárt paraméterA kimenet formája
nint*A paraméter által mutatott int-ben letárolja az eddig kiírt karakterek számát. Nincs különben semmilyen konverzió.
pvoid*A paramétert mutatóként jelenteti meg. A kijelzés formátuma programfejlesztő rendszer függő, de általában hexadecimális.

Lássuk a jelzőket!

-Az eredmény balra igazított, és jobbról szóközzel párnázott. Ha a - jelzőt nem adják meg, akkor az eredmény jobbra igazított, és balról szóközökkel, vagy zérusokkal párnázott.
+Előjeles konverzió eredménye mindig plusz, vagy mínusz előjellel kezdődik. Ha a + jelzővel együtt szóköz jelzőt is megadnak, akkor a + jelző van érvényben.
szóközHa az érték nem negatív, a kimenet egy szóközzel kezdődik a plusz előjel helyett. A negatív érték ilyenkor is mínusz előjelet kap.
# Azt határozza meg, hogy a paramétert alternatív formát használva
kell konvertálni.

Az alternatív formák a típuskaraktertől függnek:

Típ. kar.A # hatása a paraméterre
c,s,d,i,uNincs hatás.
e,E,fAz eredményben mindenképpen lesz tizedes pont még akkor is, ha azt egyetlen számjegy sem követi. Normálisan ilyenkor nem jelenik meg a tizedes pont.
g,GUgyanaz, mint e és E, de az eredményből a követő zérusokat nem vágja le a printf.
o0-t ír a konvertált, nem zérus paraméter érték elé. Ez az oktális szám megjelentetése.
x, X0x, 0X előzi meg a konvertált, nem zérus paraméter értéket.

A szélesség a kimeneti érték minimális mezőszélességét határozza meg, azaz a megjelenő eredmény legalább ilyen szélességű. A szélességet két módon adhatjuk meg:

  • vagy expliciten beírjuk a formátumspecifikációba,
  • vagy a szélesség helyére * karaktert teszünk. Ilyenkor a printf hívásban a következő parameter csak int típusú lehet, s ennek az értéke definiálja a kimeneti érték mezőszélességét.

Bármilyen szélességet is írunk elő, a printf a konverzió eredményét nem csonkítja! A lehetséges szélesség specifikációk:

Szélesség Hatása a kimenetre
nA printf legalább n karaktert jelentet meg. Ha a kimeneti érték n karakternél kevesebb, akkor szóközzel n karakteresre párnázza (jobbról, ha a - jelzőt megadták, máskülönben balról).
0nLegalább n karakter jelenik meg ekkor is. Ha a kimeneti érték n-nél kevesebb karakterből áll, akkor balról zérus feltöltés következik.
*A paraméter lista szolgáltatja a szélesség specifikációt, de ennek a paraméter listában meg kell előznie azt a paramétert, amire az egész formátumspecifikáció vonatkozik.

A pontosság specifikáció mindig ponttal (.) kezdődik. A szélességhez hasonlóan ez is megadható közvetlenül, vagy közvetve (*) a paraméter listában. Utóbbi esetben egy int típusú paraméternek meg kell előznie azt a paramétert a printf hívásban, amire az egész formátumspecifikáció vonatkozik.

Megemlítjük, hogy a szélességet és a pontosságot is megadhatjuk egyszerre közvetetten. Ilyenkor a formátumspecifikációban *.* van. A printf hívás paraméter listájában két int típusú paraméter előzi meg (az első a szélesség, a második a pontosság) azt a paramétert, amire az egész formátumspecifikáció vonatkozik. Lássunk egy példát!

printf("%*.*f", 6, 2, 6.2);

A 6.2-et f típuskarakterrel kívánjuk konvertáltatni úgy, hogy a mezőszélesség 6 és a pontosság 2 legyen.

A pontosság specifikációk a következők:

PontosságHatása a kimenetre
.*Lásd előbbre!
nincs megadvaÉrvénybe lépnek a típuskaraktertől függő alapértelmezés szerinti értékek. Ezek:
- 1 : d, i, o, u, x, X esetén,
- 6 : e, E, f típuskaraktereknél, minden szignifikáns számjegy g és G-nél,
- s típuskarakternél a teljes karakterlánc megy a kimenetre és a
- c típuskarakterre nincs hatással.
.0- Az e, E, f típuskaraktereknél nem jelenik meg a tizedes pont.
- A d, i, o, u, x, X esetén pedig az alapértelmezés szerinti pontosság lép érvénybe (1). Ha ilyenkor a kiírandó paraméter értéke ráadásul zérus is, akkor csak egyetlen szóköz jelenik meg.
.nA printf legfeljebb n karaktert, vagy decimális helyi értéket jelentet meg. Ha a kimenet n-nél több karakterből áll, akkor csonkul, vagy kerekíti a rutin a vonatkozó típuskaraktertől függően:
- d, i, o, u, x és X esetén legalább n számjegy jelenik meg. Ha a kimenet n-nél kevesebb jegyből áll, akkor balról zérus feltöltés történik. Ha a kimeneti n-nél többjegyű, akkor sem csonkul.
- e, E, f-nél a printf n számjegyet jelentet meg a tizedes ponttól jobbra. Ha szükséges, a legalacsonyabb helyi értéken kerekítés lesz.
- g, G esetén legfeljebb n szignifikáns jegy jelenik meg.
- A c típuskarakterre nincs hatása.
Az s típuskarakternél legfeljebb n karakter jelenik meg, azaz a hosszabb karakterlánc csonkul.

Legvégül nézzük még a h, l és L méretmódosító karaktereket! A méretmódosítók annak a paraméternek a hosszát módosítják, melyre az egész formátumspecifikáció vonatkozik.

  • A d, i, o, u, x és X típuskarakterekkel kapcsolatban csak a h és az l méretmódosítók megengedettek. Jelentésük: h esetén a vonatkozó paramétert a printf tekintse short int-nek, l esetén pedig long int-nek.
  • Az e, E, f, g, és G típuskarakterekkel kapcsolatban csak az l és az L méretmódosítók megengedettek. Jelentésük: l esetén a vonatkozó paramétert a printf tekintse double-nek, L-nél pedig long double-nek.

Jelentessük meg a 2003. március 2. dátumot ÉÉÉÉ-HH-NN alakban!

printf("%04d-%02d-%02d", 2003, 3, 2);
printf("%.4d-%.2d-%.2d", 2003, 3, 2);

Mindkét hívás 2003-03-02-t szolgáltat.

Szemléltessük a 0, a #, a + és a - jelzők hatását d, o, x, e és f típuskarakterek esetén!

/* PELDA32.C: A printf jelzoinek szemleltetese nehany
  tipuskarakterre. */
#include <stdio.h>
#include <string.h>
#define E 555
#define V 5.5
int main(void) {
  int i,j,k,m;
  char prefix[7], format[100], jelzok[]=" 0# +  -",
      *tk[]= {"6d", "6o", "8x", "10.2e", "10.2f"};
#define NJ (sizeof(jelzok)-2)*2
#define NTK sizeof(tk)/sizeof(tk[0])
  printf("prefix    6d      6o      8x"
        "        10.2e      10.2f\n"
        "------+-------+-------+-----"
        "----+-----------+-----------+\n");
  for(i=NJ-1; i>=0; --i) {
    strcpy(prefix, "%");
    for(j=k=1; k<NJ; k<<=1)
      if(i&k) prefix[j++]=jelzok[k];
    prefix[j]=0;

Az i 15-ről indul, s zérusig csökken egyesével, azaz eközben leírja az összes lehetséges négybites bitkombinációt. A k felvett értékei 1, 2, 4 és 8, s a jelzok tömb épp ezen indexű elemeiben találhatók meg a jelző karakterek.

    strcpy(format, "%5s |");
    for(m=0; m<NTK; ++m) {
      strcat(format, prefix);
      strcat(format, tk[m]);
      strcat(format, " |"); }
    strcat(format, "\n");
    printf(format, prefix, E, E, E, V, V); }
  return(0); }

A program futtatásakor megjelenő kimenet:

prefix    6d      6o      8x        10.2e       10.2f
------+-------+-------+---------+-----------+-----------+
%0#+- |+555  |01053  |0x22b    |+5.50e+00  |+5.50      |
%#+- |+555  |01053  |0x22b    |+5.50e+00  |+5.50      |
%0+- |+555  |1053  |22b      |+5.50e+00  |+5.50      |
  %+- |+555  |1053  |22b      |+5.50e+00  |+5.50      |
%0#- |555    |01053  |0x22b    |5.50e+00  |5.50      |
  %#- |555    |01053  |0x22b    |5.50e+00  |5.50      |
  %0- |555    |1053  |22b      |5.50e+00  |5.50      |
  %- |555    |1053  |22b      |5.50e+00  |5.50      |
%0#+ |+00555 |001053 |0x00022b |+05.50e+00 |+000005.50 |
  %#+ |  +555 | 01053 |  0x22b | +5.50e+00 |    +5.50 |
  %0+ |+00555 |001053 |0000022b |+05.50e+00 |+000005.50 |
  %+ |  +555 |  1053 |    22b | +5.50e+00 |    +5.50 |
  %0# |000555 |001053 |0x00022b |005.50e+00 |0000005.50 |
  %# |  555 | 01053 |  0x22b |  5.50e+00 |      5.50 |
  %0 |000555 |001053 |0000022b |005.50e+00 |0000005.50 |
    % |  555 |  1053 |    22b |  5.50e+00 |      5.50 |

int sprintf(char *puffer, const char *format<, parameter, ...>);

A függvény ugyanazt teszi, mint a printf, de a kimenetét nem az stdout-ra készíti, hanem az első paraméterként megkapott karaktertömbbe '\0'-lal lezárva.

Egyéb függvények

Csak lezárt fájlokkal foglalkozik a következő két függvény. A

int remove(const char *fajlnev);

törli az akár komplett úttal megadott azonosítójú fájlt. Sikeres esetben zérust, máskülönben -1-et szolgáltat a rutin. A

int rename(const char *reginev, const char *ujnev);

a reginev azonosítójú fájlt átnevezi ujnev-re. Ha az ujnev meghajtónevet is tartalmaz, akkor az nem térhet el attól, ahol a reginev azonosítójú fájl elhelyezkedik. Ha viszont az ujnev a fájl eredeti helyétől eltérő utat tartalmaz, akkor az átnevezésen túl megtörténik a fájl átmozgatása is.

A függvény egyik paramétere sem lehet globális fájlazonosító!

Sikeres esetben zérust szolgáltat a rutin. A problémát a -1 visszaadott érték jelzi.

FILE *tmpfile(void);

A rutin "wb+" móddal ideiglenes fájlt hoz létre, melyet lezárásakor, vagy normális programbefejeződéskor automatikusan töröl a rendszer. A visszaadott érték az ideiglenes fájl FILE struktúrájára mutat, ill. NULL jön létrehozási probléma esetén.

char *tmpnam(char s[L_tmpnam]);

tmpnam(NULL) módon hívva olyan fájlazonosítót kapunk, mely egyetlen létező fájl nevével sem egyezik. A szolgáltatott mutató belső, statikus karaktertömböt címez, ahol a fájlazonosító karakterlánc található.

A fájlazonosító karakterlánc nem marad ott örökké, mert a következő tmpnam hívás felülírja.

Nem NULL mutatóval hívva a rutin kimásolja a fájlazonosítót az s karaktertömbbe, s ezzel is tér vissza. Az s tömb legalább L_tmpnam méretű kell, legyen. Többszöri hívással legalább TMP_MAX darab, különböző fájlazonosító generálása garantált.

Feladatok

1. Készítsen szoftvert, mely eldönti az indító parancssorban megadott azonosítójú fájl típusát, azaz hogy szöveges, vagy bináris! Ha parancssori paraméter nélkül futtatják a programot, akkor ismertesse a használatát! A fájltípus megállapítása a benne szereplő karakterek kódjain alapulhat; az ASCII kódtábla első 32 vezérlőjele többségének semmi értelme sincs egy szöveges fájlban.

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
/* Fajl meretenek megallapitasa. */
long fajlmeret(FILE *stream) {
  long aktpoz, hossz;
  aktpoz=ftell(stream);
  fseek(stream, 0L, SEEK_END);
  hossz=ftell(stream);
  fseek(stream, aktpoz, SEEK_SET);
  return(hossz); }
int main(int argc, char *argv[]) {
  FILE *fp;
  int c;
  long meret, i=0l;
  if(argc!=2) {
    fprintf(stderr, "Hasznalat:\n\tASCIIE fajlazonosito\n\r");
    return 1; }
  if(!(fp=fopen(argv[1], "rb"))) {
    fprintf(stderr, "A(z) %s nem nyithato meg!\n\r", argv[1]);
    return 1; }
  meret=fajlmeret(fp);
  while((c=getc(fp))!=EOF) {
    ++i;    /* A fajlpozicio kovetese. */
    /* Elso probalkozas:
    if(!(c>=' '||c=='\n'||c=='\r'||c=='\t'||c=='\x1A'&&i==meret)*/
    if(c<' '&&c!='\n'&&c!='\r'&&c!='\t'&&(c!='\x1A'||i!=meret)) {
      printf("A(z) %s valoszinuleg binaris!\n", argv[1]);
      fclose(fp);
      return 0; } }
  fclose(fp);
  printf("A(z) %s valoszinuleg szovegfajl!\n", argv[1]);
  return 0; }

2. Írjon szoftvert, mely az indító parancssorban megadott szövegfájlokat egyesíti a megadás sorrendjében a parancssorban utolsóként előírt azonosítójú szövegfájlba! Ha parancssori paraméter nélkül indítják a programot, akkor ismertesse a képernyőn, hogyan kell használni! Ha csak egy fájlazonosító van a parancssorban, akkor a szabvány bemenet másolandó bele. A fájlok egyesítése során a folyamat előrehaladásáról tájékoztatni kell a képernyőn! A szabvány bemenet másolása esetén végül közlendő még az eredményfájl mérete!

/* EGYESIT.C: Szovegfajlok egyesitese. */
#include <stdio.h>
#define SZELES 30 /* Kijelzesi szelesseg. */
FILE *ofp; /* Az output fajl. */
void filecopy(FILE *p); /* A fajlmasolo fv. */
int main(int argc, char *argv[]) {
  FILE *fp; /* Az aktualis input fajl. */
  int i;
  printf("Szovegfajlok egyesitese:\n");
  if(argc < 2) { /* Nincs parameter: info a hasznalatrol. */
    fprintf(stderr,
            "Az input szovegfajlok egyesitese az utolso "
            "parameter szovegfajlba:\n\r"
            "Inditas: EGYESIT inputfajl inputfajl ... "
            "outputfajl\n\r");
    return 1; }
  /* Az output fajl megnyitasa: */
  if(!(ofp=fopen(argv[argc-1],"wt"))) {
    fprintf(stderr, "%s outputfajl nem nyithato meg!\n\r",
            argv[argc-1]);
    return 1; }
  /* Egy parameter eseten, a szabvany bemenet masolando: */
  if(argc==2) filecopy(stdin);
  /* Tobb parameternel a fajlok rendre masolandok: */
  else for(i=1; i < argc-1; ++i) {
      if(!(fp=fopen(argv[i],"rt"))) /* Inp.fajl nyitasa: */
        fprintf(stderr,"%s inputfajl nem nyithato meg!\n\r",
                argv[i]);
      else {
        printf("\n%*s -> %-*s: ", SZELES, argv[i], SZELES,
              argv[argc-1]);
        filecopy(fp); /* Inp.fajl masolasa az outputba.*/
        fclose(fp); } } /* Az input fajl bezarasa. */
  fclose(ofp); /* Az output fajl bezarasa. */
  printf("\n");
  return(0); }
void filecopy(FILE *fp) { /* 1 fajl masolasa az outputba. */
  int c;
  long szlo=0l; /* A kiirt bajtok szamlalasara. */
  while(!feof(fp)) /* Inputfajl olvasasa fajlvegig. */
    if((c=fgetc(fp))!=EOF) {
      if(c!=fputc(c,ofp)) { /* Van hiba a kiirasnal? */
        fprintf(stderr, "Output fajlnal irashiba!\n\r");
        fclose(ofp);
        exit(1); }
      ++szlo; /* Bajtok szamlalasa. */
      if(c=='\n')++szlo; /* A transzlacio miatt! */
      if(fp!=stdin) { /* A szamlalo ki ugyanott. */
        printf("%10ld", szlo);
        printf("\b\b\b\b\b\b\b\b\b\b"); }
    } else { /* Input fajl vege: */
      if(fp==stdin)
        printf("\nA letrejott fajl %ld bajtos.\n",szlo); } }

3. A kezdő programozó olyan függvényt próbált írni, ami helyben középre igazítja m szélességben a paraméter karakterláncot, majd visszaadja annak címét!

/*1*/char *kozepre(char *s,int m){
/*2*/  int hsz=strlen(s), elo=(m-hsz+1)/2;
/*3*/  if(elo){
/*4*/    s[m-1]='\0'; /* Karakterlanc vege: */
/*5*/    memset(s+hsz+elo, ' ', m-hsz-elo); /* Jobb oldal */
/*6*/    memcpy(s+elo, s, hsz); /* Szoveg kozepre: */
/*7*/    memset(s, ' ', hsz); } /* Bal oldal */
/*8*/  return s; }
Mivel a függvény nem tökéletes, jelölje meg azokat a sorokat, amelyekkel működőképessé tehető!
/*4*/    s[m]=0; /* Karakterlanc vege: */
/*5*/    memset(s+hsz+elo, ' ', elo); /* Jobb oldal */
/*6*/    memmove(s+elo, s, hsz); /* Szoveg kozepre: */
/*7*/    memset(s, ' ', elo); } /* Bal oldal */

4. Kezdő programozónk azt a feladatot kapta, hogy készítsen egy függvényt, ami a paramétereként adott egyik folyam tartalmát a másikba másolja, és ehhez meret méretű belső puffert használ. A megoldás sajnos nem tökéletes.

/*1*/void masol(FILE* be, FILE* ki, int meret) {
/*2*/  void* puffer;
/*3*/  int olvasva;
/*4*/  if((puffer = malloc(meret))) {
/*5*/    /* fread/fwrite par.: puffercim, blk. meret, blk. darab, folyam */
/*6*/    while((olvasva=fread(puffer, meret, 1, be))) {
/*7*/      fwrite(puffer, 1, olvasva, ki); }
/*8*/    free(puffer); } }
Jelölje meg azokat a sorokat, amelyekkel helyes lesz a működése!
/*6*/    while((olvasva=fread(puffer, 1, meret, be))) {
/*7*/      fwrite(puffer, olvasva, 1, ki); }
/*7*/      fwrite(puffer, 1, meret, ki); }
/*7*/      fwrite(puffer, meret, 1, ki); }