11. File-kezelés A Pascal programunkban használt adatok (változók, típusos konstansok) a számítógép memóriájában helyezkednek el. A memória mérete azonban erősen korlátozott, nem beszélve arról, hogy a számítógép kikapcsolása után az adatok egyszerűen elvesznek. Hogyan lehet a memória méretét jóval meghaladó mennyiségű adatot kezelni, úgy hogy azok a számítógép kikapcsolása után is megmaradjanak? A választ a file (adatállomány) bevezetése adja. A file kifejezés rendszerint valamely háttértárolón (merevlemez, mágnesszalag) tárolt adatok összességét jelöli. A Turbo Pascal-ban a file fogalma valamelyest eltér a szabványos Pascal-ban használttól: Eszköz (device) file-ok: a számítógépben található I/O eszközök (mint például a billentyűzet, a képernyő, a nyomtatók és a kommunikációs csatornák közvetlen elérését teszik lehetővé. Lemez (disk) file-ok: amelyek a számítógép lemezegységein tárolt egymással összefüggő adatok együttesét jelölik. A lemez file-kat, tartalmuk alapján szöveges (text), típusos és típus nélküli file-ok csoportjára osztjuk. 11.1. Turbo Pascal szöveges file-ok A szöveges file olyan sorokból épül fel, amelyek karaktereket tartalmaznak, és a sort a kocsivissza/soremelés (CR/LF) vezérlőkarakterek zárják. A CR/LF karakterek (ASCII kódjuk 13 és 10), a sorok végét jelzik a file-ban. A szöveges file-okat tetszőleges szövegszerkesztővel létrehozhatjuk illetve módosíthatjuk. A 11.1. ábra a szöveges file-ok szerkezetét szemlélteti. 1. sor: Ez egy példa a text file sorának felépítésére CR LF 2. sor: Mindegyik sort a CR/LF zárja CR LF 3. sor: A következő sor egy üres sor CR LF 4. sor: CR LF 5. sor: A sorok számokat is tartalmazhatnak: CR LF 6. sor: 50 3.1415926 1.45E-2 CR LF 11.1. ábra Példa a szöveges file-ok szerkezetére A TEXT file-ok az elérésük és használatuk szempontjából a szekvenciális file-okhoz tartoznak. Az egyes sorokat mindig a legelső sortól kezdve egyesével olvashatjuk illetve írhatjuk. Kivételt képez a hozzáírás művelete, mivel ekkor az írás a már meglévő file utolsó sora után kezdődik. A szöveges file-t vagy csak írhatjuk, vagy csak olvashatjuk, tehát a file használata előtt el kell döntenünk, hogy mit szeretnénk csinálni. A file-kezelés lépései programozási nyelvtől függetlenek. A file-t a felhasználása előtt meg kell "nyitni", mivel csak így férhetünk hozzá a benne tárolt adatokhoz. A megnyitás után következik a file elérése - adatok írása a file-ba vagy adatok olvasása a file-ból. Munkánk végeztével a file-t le kell "zárni", hogy annak adatai háttértáron megőrzésre kerüljenek. A 11.2 ábrán egy olyan Turbo Pascal programot látható, amely a file-kezeléshez szükséges lépéseket tartalmazza szöveges file esetén. PROGRAM text_file_kezeles; TYPE ptype = array[1..4096] of char; VAR F : text; {a file tipusa TEXT, a file-változó F} puffer: ptype; {4 Kbyte-os file-puffer} v1, v2: integer; BEGIN ASSIGN(F,'szov.txt');{a Pascal file-változó és a DOS file} {összerendelése} SETTEXTBUF(F,puffer);{I/O puffer hozzárendelése a file-hoz} RESET(F); { olvasás } REWRITE(F); { a file megnyitása -> létrehozás és írás} APPEND(F); { hozzáírás } { A file-kezelés lépései } { Ha reset-tel nyitottuk meg a file-t: } READ(F, v1,v2); { olvasás a file-ból } READLN(F,v1,v2); { teljes sor olvasása a file-ból } IF EOF(F) then { a file végének tesztelése }; IF EOLN(F) then { a sor végének tesztelése }; IF SEEKEOF(F) then { a file végének tesztelése az elválasztó karakterek átlépésével }; IF SEEKEOLN(F) then { a sor végének tesztelése az elválasztó karakterek átlépésével }; { Ha rewrite-tal vagy append-del nyitottuk meg a file-t: } WRITE(F,v1,v2+26); { írás a file-ba } WRITELN(F,v1,v2+26);{ teljes sor írása a file-ba } FLUSH(F); { az output puffer kiírása a file-ba} { Bármely megnyitási mód esetén a file lezárása:} CLOSE(F); END. 11.2. ábra Példa a szöveges file-t feldolgozó Pascal program szerkezetére 11.1.1. A szöveg file azonosítása Ahhoz, hogy a programunkból szöveg file-t használjunk, deklarálnunk kell egy ún. file-változót, amely egyértelműen azonosítja a file-t a programon belül. A deklarációban a változót TEXT típusúnak kell megadni. var file_valtozo:text; A következő lépésben meg kell mondanunk, hogy az adott file-változóhoz melyik külső (operációs rendszer szintjén található) file-t rendeljük hozzá. Ezt az összerendelést mindig a file megnyitása előtt kell elvégeznünk: Assing(file_valtozo, file_nev); A file_nev paraméter sztring formájában tartalmazza a felhasználni kívánt file névét (maximálisan 79 karakter hosszon). Nézzünk néhány lehetséges példát a file nevének megadására: 'C:\PASCAL\PELDA.TXT' - megadás a teljes elérési útvonal kijelölésével. 'SZOVEG.SZG' - a file az aktuális könyvtárban található. Az Assign rutin paraméterezésének további lehetőségeivel a fejezet végén ismerkedünk meg. 11.1.2. A szöveg file megnyitása Mint ahogy az a 11.2. ábrán is látható, a TEXT típusú file-okat háromféleképpen lehet megnyitni. A reset(file_valtozo); eljárás létező szöveg file-t nyit meg, csak olvasásra. A megnyitás után az eof függvény true értékkel jelzi, ha a file üres. A reset a file-ban az aktuális pozíciót a file elejére állítja (ez a művelet már megnyitott file-on is elvégezhető.). A rewrite(file_valtozo); eljárás új szöveg file-t hoz létre, vagy már meglévő szöveg file-t újraír (így annak teljes tartalma elvész…). Az így megnyitott file- okon csak az írás művelete használható (csak írható file). A rewrite az aktuális pozíciót a file elejére állítja. Az append(file_valtozo); eljárás létező szöveg file-t nyit meg, csak hozzáírásra. Az append a file-ban az aktuális pozíciót a file végére állítja. Mind a három megnyitási mód a gyorsabb file-kezelés érdekében egy-egy 128 byte-os pufferterületet rendel a file-hoz. Ha a file-műveleteket tovább szeretnénk gyorsítani, akkor ezt a területet átdefiniálhatjuk saját nagyobb pufferre. Az átdefiniálást a settextbuf eljárás hívásával a file megnyitása előtt kell elvégezni. (Ajánlott a pufferméretet 2 hatványának választani: 256, 512, 1024 ,....) A settextbuf eljárás kétféleképpen paraméterezhető: settextbuf(file_valtozo,puffer); vagy settextbuf(file_valtozo,puffer, puffermeret); ahol a puffer általában valamilyen tömbváltozó, és a pufferméret a puffer mérete (sizeof(puffer)), vagy annál kisebb szám. Az első esetben a rendszer állítja be a puffer méretét, a második esetben pedig a híváskor adjuk meg. Ha a file nyitásakor valamilyen hiba lép fel (például nem létező file- t nyitunk meg olvasásra), akkor a programunk 'Runtime error..' üzenettel leáll. A Turbo Pascal rendszer lehetővé teszi hogy az input/output (I/O) műveleteknél fellépő hibákat programon belül dolgozzuk fel. A kritikus programrészletet {$I-} és {$I+} direktívák közé helyezve, az ioresult függvény visszaadott értékéből következtethetünk a hiba megjelenésére. Az alábbi kis program felhasználva az elmondottakat, bekér egy file- nevet, és ellenőrzi, hogy az adott file létezik, vagy sem. program io_check; var f : text; fn : string[80]; begin write('Kerem a file nevét: '); readln(fn); assign(f,fn); {$I-} reset(f); if ioresult<>0 then begin writeln('A file nem elérhető…'); halt(1); end; {$I+} writeln('A file létezik.'); close(f); end. Felhívjuk a figyelmet arra, hogy az ioresult függvény értékéből mindig csak a legutolsó I/O művelet lefolyására következhetünk. A függvény hívása törli a belső állapotjelző értékét, így ha többször szeretnénk ezt lekérdezni, saját egész típusú változóban kell eltárolnunk az első hívásakor visszaadott értéket. Az ioresult 0 értékkel tér vissza, ha a legutolsó I/O művelet sikeres volt, és valamilyen run-time hiba kódjával, ha nem. 11.1.3. A szöveg file I/O műveletei Ha a szöveg file-t írásra nyitottuk meg (rewrite/append), akkor az adatok file-ba írását a már jól ismert write és writeln eljárások módosított változataival oldhatjuk meg. Minkét eljárásnál az első paraméterként a file-változót kell megadnunk: write(file_valtozo,kifejezéslista); illetve writeln(file_valtozo,kifejezéslista); A különbség a két eljárás között, hogy a writeln eljárás az írási művelet végeztével sorvége (CR/LF) jelet is ír a file-ba. Ha a Text típusú file-t olvasásra nyitottuk meg (reset), akkor szintén ismert eljárásokat használhatunk az olvasási művelet elvégzésére: read(file_valtozo, változólista); illetve readln(file_valtozo, változólista); A readln eljárás ellentétben a read eljárással, a szöveg file-ból mindig teljes sort dolgoz fel, így az aktuális pozició a file-ban az olvasás után a következő sor eleje lesz. A write, writeln, read és readln eljárások használatára vonatkozó szabályok (például formátum használata, milyen típusú adat írható olvasható segítségükkel,...) megegyeznek a 7. fejezetben ismertetettel. Ha a file-t olvasásra nyitottuk meg lehetőségünk van bizonyos ellenőrzések beépítésére, amelyek szükségesek ahhoz, hogy a programunk helyesen dolgozza fel a szöveg file-t. a. A file vége elérésének ellenőrzése: Az eof(file_valtozo) függvény boolean típusú értéket ad vissza, amely jelzi, hogy az aktuális file-pozíció a file utólsó eleme után helyezkedik el (true), vagy sem (false). Szöveg file-ok esetén a file- vége esemény két esetben is bekövetkezhet: - a file-ban ún. end-of-file jelzőt találtunk (ASCII 26), vagy - elértük a file fizikai végét. A seekeof(file_valtozo) függvény hasonlóan az eof függvényhez a file végét jelzi true értékkel, abban az esetben is, ha az aktuális file-pozíció és a file-vége között csak szóköz, tabulátor vagy sorvége karakterek helyezkednek el. b. A sorok végének érzékelése A sorvége elérésének ellenőrzésére szintén két függvény használható. Az eoln(file_valtozo) függvény true értékkel tér vissza, ha az aktuális file-pozícióban ún. end-of-line (sorvége) jelző (CR/LF) áll. A seekeoln(file_valtozo) függvény szintén jelzi a sor végét, méghozzá akkor is, ha az aktuális file-pozíció és a sor vége között csak szóköz és tabulátor karakterek helyezkednek el. A függvények ismeretében a file-olvasó programunk fő ciklusa az alábbiak szerint alakítható ki: while not eof(f) do begin readln(f,sor); ..... end; Ha a file biztosan tartalmaz sorokat (nem üres), akkor használhatjuk a repeat until ciklust is: repeat readln(f,sor); ... until eof(f); 11.1.4. Szöveg file lezárása A text típusú file lezárása a close(file_valtozo); eljárás hívásával történik, függetlenül attól, hogy milyen módon nyitottuk meg a file-t. Bizonyos esetekben, mint például ha a write / writeln eljárások helyett saját rutinokkal írunk a file-ba, külön kell gondoskodnunk a output puffer file-ba írásáról. Erre a célra használható a flush(file_változo); eljárás. Meg kell jegyeznünk, hogy a write, writeln és a close eljárások automatikusan ürítik az output puffert. Az fenti lépések bemutatására tekintsük a szöveg file (TYPE0.TXT) tartalmának képernyőn való megjelenítését végző programot. program type0; var tf : text; fn,sor : string; puffer : array[1..5120] of char; { 5K puffer } begin write('Kérem a file nevét: '); readln(fn); assign(tf,fn); settextbuf(tf,puffer); {$I-} reset(tf); if ioresult<>0 then begin writeln('A(z)',fn, 'nevű file nem olvasható…'); exit; end; {$I+} while not eof(tf) do begin readln(tf,sor); writeln(sor); end; close(tf); end. 11.1.5. A szabványos szöveg file-ok: input és output A szabványos input és output szöveg file-ok használatát a 7. fejezetben részletesen ismertettük. Az alábbi megjegyzések figyelembevételével ezen file-okat még sokoldalúbban tudjuk felhasználni. 1. A szabványos I/O műveletek esetén az alábbi rövidítések használatosak a Pascal nyelvben: write(output, ...); write(...); writeln(output, ...); writeln(...); read(input, ...); read(...); readln(input, ...); readln(...); eof(input) eof eoln(input) eoln 2. A szabványos bevitelre szintén használható az I/O hibaellenőrzés. Az alábbi példában csak numerikus input-ot fogadunk el: program num_inp; var x:real; begin repeat writeln('Kérek egy számot: '); {$i-} readln(x) {$i+} until ioresult=0; writeln('A beírt szám négyzete: ',sqr(x):10:2); end. 3. A szabványos input és output átirányítható a program neve után megadott < és > jelekkel. program stdio_1; var ch:char; begin repeat read(ch); write(ch); until eof; end. Ha a fenti programot .EXE file-ba fordítjuk, akkor DOS készenléti jelnél (prompt) az alábbi műveleteket végezhetjük el: c:\pascal>stdio_1 A program a begépelt karaktereket lenyomása után visszaírja a képernyőre. A programból a CTRL-Z és az billentyűk lenyomása után lehet kilépni. c:\pascal>stdio_1 stdio_1 >proba.txt A program a begépelt karaktereket lenyomása után beírja a PROBA.TXT file-ba. A programból a CTRL-Z és az billentyűk lenyomása után lehet kilépni. c:\pascal>stdio_1 proba2.txt A program a PROBA.TXT file-t átmásolja a PROBA2.TXT file-ba. 4. Ha a CRT unit-ot használjuk, akkor a szabványos I/O műveletek nem a DOS felhasználásával mennek végbe, ily módon az átírányítás sem működik. Ebben az esetben az input és output file-ok alábbi formában történő újranyitásával az átírányítás biztosítható: assign(output,''); assign(input,''); rewrite(output); reset(input); A 3. pontban közölt program CRT-s változata: program stdio_2; uses crt; var ch:char; begin assign(input,''); assign(output,''); reset(input); rewrite(output); repeat read(ch); write(ch); until eof; repeat until keypressed; end. A következő példa azt az esetet mutatja be, amikor a szabványos input és output műveletek a CRT unit felhasználásával mennek végbe (nem irányítható át). Lehetőség van azonban arra, hagy saját text típusú file-okhoz rendeljük a DOS szabványos be- és kimenetét, az assign eljárásban megadott '' file-névvel. A program a megadott szöveg file-t 23 soronként írja ki. program stdio_3; uses crt; const max=23; var sor : string[80]; cnt : integer; ch : char; inp,out: text; begin assign(inp,''); assign(out,''); reset(inp); rewrite(out); cnt:=0; repeat readln(inp,sor); inc(cnt); writeln(out,sor); if cnt mod max=0 then begin writeln('Tovább bármely billentyűvel...'); repeat until keypressed; ch:=readkey; end; until eof(inp); end. 5. Érdekes lehetőséget biztosít a CRT unit-ban definiált AssignCrt eljárás használata. Az eljárás paraméterként megadott szöveg file-t a konzolhoz rendeli. Reset-tel megnyitva közvetlenül a billentyűzetről tudunk olvasni adatokat, rewrite-val megnyitva pedig közvetlenül a képernyőre írhatunk. Az stdio_3 programot alakítsuk át az AssignCrt rutin felhasználásával: program stdio_4; uses crt; const max=23; var sor : string[80]; cnt : integer; ch : char; crtout: text; begin assign(input,''); assign(output,''); assigncrt(crtout); reset(input); rewrite(output); rewrite(crtout); cnt:=0; repeat readln(sor); inc(cnt); writeln(sor); if cnt mod max=0 then begin writeln(crtout,'Tovább bármely billentyűvel...'); repeat until keypressed; ch:=readkey; end; until eof; end. 11.1.6. Példaprogramok text típusú file-ok használatára a. A APP_TXT.PAS program a FILE1.TXT file tartalmát hozzámásolja a FILE2.TXT file-hoz. Külön felhívnánk a figyelmet, hogy a programban minden I/O művelet eredményét ellenőriztük. program app_txt; type Tpuffer = array[1..4*1024] of char; var inf,outf : text; ok : boolean; sor : string[80]; inp_puffer, out_puffer : tpuffer; begin {$I-} assign(outf,'file2.txt'); settextbuf(outf,out_puffer); append(outf); writeln(outf); ok:=ioresult=0; if ok then { ha sikeres volt az outf file megnyitása } begin assign(inf,'file1.txt'); settextbuf(inf,inp_puffer); reset(inf); ok:=ioresult=0; end; if ok then { ha sikeres volt az inf file megnyitása } begin { amíg nincs vége az inf file-nak, és sikeres a kiírás az outf file-ba } while (not eof(inf)) and ok do begin readln(inf,sor); ok:=ioresult=0; if ok then { ha sikeres volt a sor beolvasása } begin writeln(outf,sor); ok:=ioresult=0; end; end; end; close(inf); close(outf); {$I+} end. b. Az alábbi program MATRIX.TXT file-ban megadott maximálisan 10x10- es mátrix elemeit olvassa fel. A sorok és az oszlopok számát a program állapítja meg a file felépítéséből. A matrix.txt file lehetséges felépítése: 4 6 17 5 7 8 19 8 8 9 10 9 A beolvasást elvégző program: program matrix; const maxn =10; type mtype=array[1..maxn,1..maxn] of integer; var t : mtype; m,n : integer; fn : string; {Az mt tömb feltöltése az fnév nevű file-ból.} procedure read_matrix(var mt:mtype; fnev:string); var mf : text; i,j : integer; begin assign(mf,fnev); {$I-} reset(mf); if ioresult<>0 then begin writeln('File-hiba.'); halt; end; {$i+} i:=0; while not seekeof(mf) do begin j:=0; inc(i); while not seekeoln(mf) do begin inc(j); read(mf,mt[i,j]); end; readln(mf); end; m:=i; { a sorok száma } n:=j; { az oszlopok száma } close(mf); end; { A tömb tartalmának kiírása mátrixos formában a képernyőre} procedure list_matrix(mt:mtype); var i,j : integer; begin for i:=1 to m do begin for j:=1 to n do write(mt[i,j]:5); writeln; end; end; begin fn:='matrix.txt'; read_matrix(t,fn); list_matrix(t); end. c. A Turbo Pascal nyelven megírt programokat lehet a DOS parancssorból is paraméterezni. A ParamCount word típusú függvény megadja, hogy hány paraméter található a program indítási sorában. Az egyes paramétereket a ParamStr string típusú függvény szolgáltatja, a paraméter sorszámának megadása után. Az alábbi program kiírja a saját paramétereit: program param; var i:integer; begin for i:=0 to paramcount do writeln( 'Paraméter ',i,' ',paramstr( i ) ); end. MS-DOS 3.0 illetve újabb verziók esetén a 0-ás indexű paraméter a program nevét tartalmazza teljes elérési útvonallal. Az alábbi program parancssorban megadott szöveg file-ban adott sztring előfordulásait keresi meg. program stkereso; var tf : text; sor : string; ssz : integer; begin if paramcount < 2 then begin writeln; writeln('A program használata:'); writeln('STKERESO file sztring'); halt; end; assign(tf,paramstr(1)); {$I-} reset(tf); if ioresult<>0 then begin writeln('Hibás file.'); halt; end; {$I+} ssz:=0; while not eof(tf) do begin inc(ssz); readln(tf,sor); if pos(paramstr(2),sor)<>0 then writeln(paramstr(2),ssz,'. sorban'); end; close(tf); end. Futtassuk a programot az stkereso stkereso.txt alma parancssorral. 11.2. Típusos file-ok A típusos file-ok olyan adathalmazok, amelyek jól definiált, azonos típusú összetevőkre, elemekre bonthatók. Az elemek típusa a file és objektum típus kivételével tetszőleges lehet. A file-ban tárolt elemek a felírás sorrendjében 0-tól kezdve egyesével sorszámozódnak. A file feldolgozásánál ezen sorszám felhasználásával pozícionálhatunk az állomány valamely elemére. A típusos file-ok szerkezetét a 11.3. ábra szemlélteti 0. elem 1. elem 2. elem . . . n. elem 11.3. ábra Példa a típusos file szerkezetére A file-kezeléses szóhasználatban általában a rekord szót használják a file elemeire. A Pascal nyelvben a rekord szó azonban a felhasználói adattípust (record) jelöli. PROGRAM tipusos_file; TYPE tipus=RECORD { A file típusa } a:integer; s:string[30]; END; VAR f : FILE OF tipus; { A file-változó } a,b : tipus; p : longint; BEGIN { A Pascal - DOS kapcsolat megadása} ASSIGN(f,'file-név'); { A file megnyitása } { Létező file megnyitása írásra / olvasásra } RESET(f); {vagy} { Uj file megnyitása írásra / olvasásra (létrehozás) } REWRITE(f); { File-műveletek } WRITE(f,a,b); { Irás a file-ba } READ(f,a); { Olvasás a file-ból } SEEK(f,2); { Pocionálás a file-ban } TRUNCATE(f); { A file csonkítása } { A file-lal végzett műveleteket segítő függvények } p:=FILEPOS(f); {Az aktuális file-pozíció } p:=FILESIZE(f); {A file-ban található elemek száma } IF EOF(f) THEN; {A file-végének érzékelése } { A file lezárása } CLOSE(f); END. 11.4. ábra Példa a típusus file-t feldolgozó Pascal program szerkezetére A típusos file-on végzett műveletek esetén, a művelet egysége az elem. A file mérete szintén a benne található elemek számát jelöli. Hogy hány byte-ot foglal el a file a lemezen, azt egyszerűen kiszámíthatjuk: a file mérete * sizeof(elem). A típusos file-ok feldolgozása szekvenciálisan illetve - a pozícionálás műveletét használva - direkt módon egyaránt lehetséges. A típusos file-ok kezelése a szöveges file-okhoz hasonlóan végezhető el. Vannak azonban lényeges eltérések, amelyeket a továbbiakban ismertetünk. A típusos file-ok esetén felhasználható Turbo Pascal eljárásokat és függvényeket a 11.4. ábrán látható programban foglaltuk össze. 11.2.1. Típusos file deklarálása és megnyitása A típusos file változójának deklarálása az alábbi formában történik: var file_valtozo : file of típus; ahol a típus tetszőleges Pascal típus lehet, kivéve a file és az object típusokat. Nézzünk néhány helyes deklarációt különböző típusok felhasználásával: type str15 = string[15]; arr100 = array[1..100] of word; telrec = record nev : string[30]; cim : string[40]; tel : string[12]; end; var { egész számokat tartalmazó file } numfile : file of integer; { karaktereket tartalmazó file } chrfile : file of char; {15 karakter hosszú sztringeket tartalmazó file } strfile : file of str15; { 100 elemű szavas tömböket tartalmazó file } arrfile : file of arr100; { telrec típusú rekordokat tartalmazó file } telfile : file of telrec; A programban a file-kezelés első lépéseként a Pascal file_valtozo-t, hozzá kell rendelnünk valamely külső (DOS) file-hoz: assign(file_valtozo,DOS_File_nev); Ezek után következhet a file megnyitása. Ha létező file-t szeretnénk használni, akkor a megnyitást a reset eljárás hívásával végezhetjük el: reset(file_valtozo); Ha azonban új file-t szeretnénk létrehozni, vagy létező file-t felülírni, akkor a rewrite(file_valtozo); eljárást kell meghívnunk. Mindkét megnyitási mód után, az aktuális file-pozíció a file elejét jelöli. Lényeges eltérés a szöveg file-okhoz képest, hogy megnyitása után a típusos file egyaránt írható és olvasható. A file-kezelés során fellépő I/O hibák kezelése típusos file esetén is egyszerűen elvégezhető a {$I-} és {$I+} direktívák felhasználásával: {$I-} reset(f) if ioresult<>0 then begin writeln('A file nem létezik…'); exit; end. {$I+} { Létező file sikeres megnyitása: } 11.2.2. File-műveletek Megnyitott típusos file esetén az adatok kiírására a write, míg az olvasására a read eljárásokat használhatjuk. A read(file_valtozo, változólista); eljárás az aktuális pozíciótól kezdve olvassa a file elemeit a megadott változókba. A művelet végrehajtása után az aktuális pozíció a utoljára beolvasott elem után helyezkedik el. A file utolsó elemének beolvasása után az eof(file_valtozo) függvény true értékkel jelzi a file-végének elérését. A write(file_valtozo, változólista); eljárás az aktuális file-pozíciótól kezdve a változólistában megadott, a file típusával megegyező típusú változók tartalmát a file-ba másolja. Az aktuális file-pozíció az utoljára kiírt elem után helyezkedik el. Ha az írási művelet megkezdésekor a az aktuális pozíció a file végén volt, akkor a write eljárás a file-t kibővíti a megadott elemekkel. Ha file-kezelés során nem használjuk a seek eljárás által biztosított pozícionálási lehetőséget, akkor a read és write műveletekkel a file-t szekvenciális módon érjük el. A seek eljárás segítségével azonban a közvetlenül (direct , random access) elérhetjük a file tetszőleges elemét. A seek(file_valtozo, longint_index); hívás után a file aktuális pozíciója a longint_index paraméterben megadott pozíció lesz. Ha a file vége után pozícionálunk, akkor az eof függvény true értéket ad vissza. Ilyen pozíció esetén az olvasás művelete 'Disk read error' hibát eredményez, míg az írás hatására a rendszer a file-t felépíti (kiterjeszti) a megadott pozícióig. Mielőtt tovább mennénk, ismerkedjünk meg a pozícionálásnál is jól felhasználható filesize és filepos függvényekkel. A filesize(file_valtozo) függvény longint típusú értékben adja meg a file-ban található elemek számát. Ha a file üres, a visszaadott érték 0. A filepos(file_valtozo) függvény szintén longint típusú értékben az aktuális file-pozíció indexét adja meg. A file-ban az elemek sorszámozása 0-tól kezdődik. A file végének elérésekor a filesize és a filepos függvények ugyanazt az értéket szolgáltatják. Nézzünk néhány különleges pozícionálást a fenti függvények felhasználásával: Pozícionálás a file utolsó eleme utáni pozícióra: seek(file_valtozo, filesize(file_valtozo)); Visszalépés az előző pozícióra: seek(file_valtozo, filepos(file_valtozo)-1); A truncate(file_valtozo); hívás hatására az aktuális file-pozíción lesz a file vége. Igy az aktuális pozíciótól kezdve a file végéig "lecsonkítjuk" a file-t. A levágott rész tartalma elvész. 11.2.3 A file lezárása A típusos file-okat szintén a close eljárás hívásával kell lezárni: close(file_valtozo) 11.2.4. Példák típusos file-ok használatára a. Az első példában a tomb.dat nevű file-t feltöltjük n darab longint értékkel, majd a file tartalmát egyetlen read utasítással felolvassuk egy n elemű longint tömbbe (t). program tomb; uses crt; const n = 10; type ttomb = array[1..n] of longint; var f1 : file of longint; f2 : file of ttomb; l : longint; t : ttomb; i : integer; begin clrscr; randomize; { A f1 file feltöltése n db számmal } assign(f1,'tomb.dat'); rewrite(f1); for i:=1 to n do begin l:=random(maxint)*1000; write(f1,l); writeln(i:2,'. ',l); end; close(f1); { A file felolvasása a t tömbbe } assign(f2,'tomb.dat'); reset(f2); read(f2,t); close(f2); {A felolvasott tömb kiírása a képernyőre } writeln; for i:=1 to n do writeln('t[',i:2,']=',t[i]); end. b. Ha a programunkból nagy tömböt szeretnénk használni, akkor elképzelhető, hogy a dinamikus tárterület sem elegendő annak tárolására. Ekkor a tömb tárolását és elérését típusos file segítségével is megoldhatjuk, ún. virtuális tömb létrehozásával. A példánkban egy 100x200-as real tömböt használunk (120000 byte). program filetomb; uses crt; const m=100; n=200; var f : file of real; i,j,k : integer; { A virtuális tömb megnyitása } procedure file_tomb_open; begin assign(f,'virtomb.dat'); rewrite(f); end; { A tömb [s,o] elemének írása } procedure fwrite(s,o:integer; elem:real); var poz : longint; begin poz:=(s-1)*longint(n)+(o-1); seek(f,poz); write(f,elem); end; { A tömb [s,o] elemének olvasása } function fread(s,o:integer): real; var elem : real; poz : longint; begin poz:=(s-1)*longint(n)+(o-1); seek(f,poz); read(f,elem); fread:=elem; end; begin writeln('A file megnyitása'); file_tomb_open; writeln('Pozícionálás az utolsó elemre.'); fwrite(m,n,pi); writeln('A file tömb feltöltése.'); for i:=1 to m do for j:=1 to n do fwrite(i,j,i*1000.0+j); writeln('Elemek véletlenszerű kiolvasása.'); for k:=1 to 20 do begin i:=succ(random(m-1)); j:=succ(random(n-1)); writeln(i:5,' ',j:5,' ',fread(i,j):10:0); end; writeln; writeln(1:5,' ',1:5,' ',fread(1,1):10:0); writeln(m:5,' ',n:5,' ',fread(m,n):10:0); close(f); erase(f); end. c. Az utolsó példában a file-kezelő programok általános szerkezetét láthatjuk. Egyszerű menüvel vezérelve valósítottuk meg a file-ok kezelésének alapműveleteit (létrehozás, írás, keresés). A program telefonkönyvet kezel. program telefonkonyv; uses crt; const telbook:string='telefon.dat'; type telefon=record nev : string[25]; cim : string[30]; tel : string[12]; end; var telfile: file of telefon; ch : char; {----------------------------------------------} { A régi telefonkönyvet megnyító vagy az újat } { létrehozó eljárás. } {----------------------------------------------} procedure opentel; var tknev : string; begin gotoxy(20,13); write('Kérem a telefonkönyv file nevét: '); readln(tknev); if tknev<>'' then telbook:=tknev; clrscr; assign(telfile,telbook); {$I-} reset(telfile); if ioresult<>0 then begin writeln('Uj telefonkönyv: ', telbook); rewrite(telfile); end else writeln('Régi telefonkönyv: ', telbook); end; {----------------------------------------------} { Az s1 keresése az s2-ben, a kis- és nagybetűk} { megkülönböztetése nélkül } {----------------------------------------------} function hasonlit(s1,s2:string):boolean; var i:byte; begin for i:=1 to length(s1) do s1[i]:=Upcase(s1[i]); for i:=1 to length(s2) do s2[i]:=Upcase(s2[i]); hasonlit:=pos(s1,s2)<>0; end; {----------------------------------------------} { Adatok bevitele a meglévő elemek átlépésével } {----------------------------------------------} procedure felvitel; var telrec : telefon; begin opentel; { Pozicionálás a file végére } seek(telfile,filesize(telfile)); with telrec do begin gotoxy(10,6); write('Név : '); readln(nev); gotoxy(10,8); write('Lakcím : '); readln(cim); gotoxy(10,10); write('Telefonszám : '); readln(tel); end; write(telfile,telrec); close(telfile); end; {----------------------------------------------} { Soros keresés a definiált maszkok alapján } {----------------------------------------------} procedure kereses; var keres,telrec : telefon; van : array [1..3] of boolean; begin opentel; if eof(telfile) then { üres file } begin close(telfile); write(#7); exit; end; gotoxy(10,4); write('Kérem a keresendő szövegrészleteket:'); gotoxy(10,14); write('Tovább bármely billentyűvel'); window(1,2,80,12); with keres do begin gotoxy(10,6); write('Név : '); readln(nev); gotoxy(10,8); write('Lakcím : '); readln(cim); gotoxy(10,10); write('Telefonszám : '); readln(tel); end; seek(telfile,0); while not eof(telfile) do begin read(telfile,telrec); van[1]:=hasonlit(keres.nev,telrec.nev); van[2]:=hasonlit(keres.cim,telrec.cim); van[3]:=hasonlit(keres.tel,telrec.tel); if van[1] and van[2] and van[3] then begin clrscr; with telrec do begin gotoxy(10,6); write('Név : '); write(nev); gotoxy(10,8); write('Lakcím : '); write(cim); gotoxy(10,10); write('Telefonszám : '); write(tel); end; repeat until keypressed; ch:=readkey; end; end; window(1,1,80,25); close(telfile); end; {----------------------------------------------} { Véletlenszerű pozícionálás a file-ban } {----------------------------------------------} procedure lapozgatas; var telrec : telefon; poz : longint; rsz : longint; begin opentel; if eof(telfile) then { üres file } begin close(telfile); write(#7); exit; end; gotoxy(10,14); write('Tovább bármely billentyűvel - Kilépés: ESC'); window(1,2,80,12); rsz:=filesize(telfile); randomize; while true do begin poz:=random(rsz); seek(telfile,poz); read(telfile,telrec); clrscr; with telrec do begin gotoxy(10,6); write('Név : '); write(nev); gotoxy(10,8); write('Lakcím : '); write(cim); gotoxy(10,10); write('Telefonszám : '); write(tel); end; repeat until keypressed; ch:=readkey; { Kilépés az eljárásból az ESC billentyűvel} if ch=#27 then begin window(1,1,80,25); close(telfile); exit; end; end; end; begin {----------------------------------------------} { A program vezérlését végző menü } {----------------------------------------------} while true do begin clrscr; gotoxy(30,2); writeln('Válasszon:'); gotoxy(20,4); writeln('1: Uj telefonszámok felvitele'); gotoxy(20,6); writeln('2: Keresés a telefonkönyvben'); gotoxy(20,8); writeln('3: Lapozgatás a telefonkönyvben'); gotoxy(20,10); writeln('4: Kilépés a programból'); repeat until keypressed; ch:=readkey; case ch of '1': felvitel; '2': kereses; '3': lapozgatas; '4': exit; else begin gotoxy(25,13); writeln('Hibás választás…'); delay(500); end; end; end; end. 11.3. Típus nélküli file-ok A típus nélküli file-ok használata a Turbo Pascal-ban nagy segítséget jelent ismeretlen szerkezetű file-ok feldolgozásában. PROGRAM tipus_nelkuli_file; CONST bs = 512; { Blokkméret } pm = 4; { A puffer mérete blokkokban } VAR f : FILE; { A file-változó } p : longint; { File-pozíció } puf : array[1..pm*bs] of byte; { Puffer } nok : word; { Sikeres blokkok száma } BEGIN { A Pascal - DOS kapcsolat megadása} ASSIGN(f,'file-név'); { A file megnyitása } { Létező file megnyitása írásra / olvasásra } RESET(f); { vagy } RESET(f,bs); {vagy} { Uj file megnyitása írásra / olvasásra (létrehozás) } REWRITE(f); { vagy } REWRITE(f,bs); { File-műveletek } BLOCKWRITE(f,puf,pm); { Irás a file-ba } {vagy} BLOCKWRITE(f,puf,pm,nok); BLOCKREAD(f,puf,pm); { Olvasás a file-ból } {vagy} BLOCKREAD(f,puf,pm,nok); SEEK(f,p); { Pozícionálás a file-ban } TRUNCATE(f); { A file csonkítása } { A file-lal végzett műveleteket segítő függvények } p:=FILEPOS(f); { Az aktuális file-pozíció } p:=FILESIZE(f); { A file-ban található elemek száma } IF EOF(f) THEN ; {A file-végének érzékelése } { A file lezárása } CLOSE(f); END. 11.5. ábra Példa a típus nélküli file-t feldolgozó Pascal program szerkezetére Amíg a szöveg file-ok sorelválasztó karaktereket tartalmaznak, a típusos file-ok adott típusú adatelemekből állnak, addig a típus nélküli file-ból tetszőleges adatok olvashatók illetve írhatók a file- ba. Mivel az adatforgalom a file és program között mindenféle ellenőrzés nélkül megy végbe, gyors file-kezelés valósítható meg. A típus nélküli file-ok kezelésének lépéseit a 11.5. ábrán látható programban foglaltuk össze. Mint ahogy az ábrán is látható, a típusos file-ok esetén használt eljárások és függvények többsége a típus nélküli file-okhoz is használhatók. Az alábbiakban a két file-kezelés közötti különbségekre hívjuk fel a figyelmet. A típus nélküli file-okkal végzett műveletekben egyetlen lépésben elérhető egység a blokk. A blokk mérete alapértelmezés szerint 128 byte, de ez a file nyitásakor a reset és a rewrite eljárások második paraméterével módosítható. (A legtöbb számítógépen a leghatékonyabb blokkméret 512 vagy ennek többszöröse.) Ennek megfelelően a seek eljárás blokkonkénti pozícionálást hajt végre, illetve a filepos függvény az aktuális blokk sorszámával tér vissza. A filesize függvény pedig a file fizikai mérete (byte) div blokkméret kifejezés értékével tér vissza. Ha a file-hossza nem osztható maradék nélkül a blokkmérettel, akkor a fennmaradó byte-ok nem érhetők el. Ezért, ha olyan programot kívánunk írni, amely tetszőleges file-t fel tud dolgozni, akkor a blokkhosszat 1-nek kell választanunk. A típus nélküli file-ok írása és olvasása szintén blokkokban történik. A blockread eljárást használjuk a file-ból való olvasásra, míg blockwrite eljárás a file írására szolgál: procedure BlockRead( var file_valtozo : file; var puffer : tetszőleges típus; cnt : Word [; var res : Word ] ); procedure BlockWrite( var file_valtozo : file; var buffer : tetszőleges típus; cnt : Word [ ; var res : Word ] ); A két eljárás paraméterezése teljesen megegyezik. Meg kell adnunk a file-t azonosító file-változót, az adatátvitelhez használt puffert és az egy lépésben olvasni illetve írni kívánt blokkok számát (cnt). A negyedik opcionális változóparaméterben (res) adja vissza a rendszer a ténylegesen beolvasott illetve kiírt blokkok számát. A blokkos I/O műveletek során a rendszer semmilyen ellenőrzést sem végez arra vonatkozólag, hogy puffer mérete és a cnt*blokkhossz adat összhangban van-e egymással. Erről a program írása során kell gondoskodnunk. Egyetlen korlátja azonban mégis van a fenti eljárások használatának, nevezetesen az, hogy a cnt*blokkhossz érték nem lehet nagyobb 65535-nál (64K). A fejezetben ismertetett lehetőségek összefoglalásaként tekintsük a mycopy programot, amelyet parancssorból paraméterezve használhatunk: MYCOPY forrás_file cél_file A program a DOS COPY parancsához hasonlóan a forrás_file állomány tartalmát átmásolja a cél_file állományba. A művelethez egy 16 Kbyte- os puffert használtunk, amely mérete tovább növelhető ha azt dinamikusan, a heap területen hozzuk létre. program mycopy; type puf=array[1..32*512] of byte; { 16 Kbyte puffer } var fromf, tof : file; n_olvasott, n_kiirt : word; puffer : puf; procedure halterr(msg:string); begin writeln(#7,'Futási hiba : ',msg); writeln(#7,'a program futása véget ért.'); halt; end; begin { A paraméterezés tesztelése } if paramcount<>2 then halterr(#13+#10+#13+#10+ 'Használat: MYCOPY forrás_file'+ ' cél_file'+#13+#10); { A forrás file megnyitása} assign(fromf, paramstr(1)); {$I-} reset(fromf, 1); {$I+} if ioresult<>0 then halterr('nem elérhető file: '+paramstr(1)); { A cél file megnyitása } assign(tof, paramstr(2)); {$I-} rewrite(tof, 1); {$I+} if ioresult<>0 then halterr('nem elérhető file: '+paramstr(2)); { Másolás } writeln(filesize(fromf), ' byte másolása folyamatban ...'); repeat blockread(fromf,puffer,sizeof(puf),n_olvasott); blockwrite(tof,puffer,n_olvasott,n_kiirt); until (n_olvasott = 0) or (n_kiirt <>n_olvasott); close(fromf); close(tof); end. 11.4. Eszközök (device) használta A Turbo Pascal és a DOS operációs rendszer a külső hardvert (billentyűzet, képernyő, nyomtató) eszközökként kezeli. Programozói szempontból az eszköz file, amelyet ugyanazon eljárásokkal és függvényekkel érhetünk el, mint a lemez file-okat. A Turbo Pascal két csoportba osztja az eszközöket: - DOS eszközök, - szöveg file eszközök. Jó példa a szöveg file eszköz megvalósítására a szabványos CRT unit, amely használata gyorsabb és kényelmesebb lehetőségeket biztosít a képernyő és a billentyűzet elérésére, mint a DOS CON eszköze. A fejezetben a DOS eszközök használatát foglaljuk össze. Mint ahogy azt a fejezet bevezetőjében említettük a file-változót vagy lemez file-hoz, vagy pedig valamilyen eszközhöz rendelhetjük hozzá. Az összerendelés az assign rutin hívásával történik, ahol a file-név helyén a 11.6. ábrán összefoglalt előredefiniált eszközneveket kell megadni. A DOS eszközök kezelésére általában a szöveg file-okat használunk, de bizonyos esetekben a típus nélküli file hatékonyabb megoldást biztosít. A NUL eszköz egyszerűen elnyeli a rá írt adatokat, és file-véget jelez, ha olvassuk. Általában akkor használjuk, amikor a program mindenképpen vár valamilyen file-nevet, de nem akarunk semmilyen file- t létrehozni. Input File-név Szabványos eszköz vagy Output ---------------------------------------------------------------- --------------------------- 'AUX' Auxiliary (a COM1 szinonim neve) I/O 'COM1', 'COM2' Soros kommunikációs portok I/O 'CON' Konzol I/O (I - billenyűzet, O - képernyő) 'LPT1', 'LPT2', 'LPT3' Nyomtató portok O 'PRN' Nyomtatók O (LPT1 szinonim neve) 'NUL' Nulla eszköz I/O 11.6. ábra A DOS eszközök neve Az eszközök felhasználását a CON_PRN.PAS program szemlélteti. Ha a programot file-név paraméter nélkül indítjuk, akkor az megkérdezi a kiírandó file nevét is, különben pedig csak azt, hogy a nyomtatót (p), vagy a konzolt (c) kívánjuk használni. program con_prn; var file_nev : string; hova : char; infile : text; outfile : text; egy_sor : string; begin if (paramcount > 1) then begin writeln( 'Használat: con_prn ' ); exit; end else if (paramcount = 1) then file_nev := paramstr( 1 ) else begin write( 'A kiírandó file neve: '); readln( file_nev ); end; if file_nev='' then exit; repeat write( 'Kiírás nyomtatóra (''p'')'+ ' vagy képernyőre (''c''): ' ); readln( hova ); hova := upcase( hova ); until (hova = 'P') or (hova = 'C'); if (hova = 'P') then assign( outfile, 'prn' ) else assign( outfile, 'con' ); assign( infile, file_nev ); reset( infile ); rewrite( outfile ); while not eof( infile ) do begin readln( infile, egy_sor ); writeln( outfile, egy_sor ); end; close( infile ); close( outfile ); end. 11.5. File-ok törlése, átnevezése A file törlésére az erase eljárást, az átnevezésére pedig a rename eljárást használhatjuk. Mindkét esetben a műveletben résztvevő file-t file-változóval azonosítjuk, amelyet az assign segítségével rendelünk a file-hoz: assign(file_valtozo,file_nev); erase(file_valtozo); { törlés } rename(file_valtozo,uj_nev); { átnevezés } Az átnevezés során az uj_nev sztring tartalmazza az új file-nevet. A műveletek sikerességét a {$I-} kapcsoló megadása után, az ioresult függvény értékéből tudhatjuk meg (0 - sikeres). Nyitott állományra sem az erase, sem a rename eljárást nem szabad használni. 11.6. Könyvtárak kezelése A könyvtárak kezelésére szolgáló DOS parancsoknak léteznek Turbo Pascal megfelelőjük: Művelet DOS Turbo Pascal ____________________ _______________ ____________ könyvtár váltása CD/CHDIR temp chdir('temp'); könyvtár létrehozása MD/MKDIR c:\pas mkdir('c:\pas'); könyvtár törlése RD/RMDIR c:\dos rmdir('c:\dos'); Az eljárások hívásakor a szokásos I/O hibakezelést használhatjuk. A getdir(drv,dirnev) eljárás segítségével megtudhatjuk a drv meghajtón az akuális könyvtár nevét a dirnev változóparaméterben. A drv byte típusú paraméter definiálja a meghajtót: 0 - aktuális meghajtó 1 - A: 2 - B: 3 - C: .... Ha a megadott meghajtó nem létezik akkor az eljárás '\' könyvtárnevet ad vissza. (Nincs I/O hibafigyelés…) Az eljárások felhasználását az alábbi program szemlélteti: program dir_muv; procedure write_act_dir; var dn:string; begin getdir(0,dn); writeln(dn); end; procedure errchk(es:string); begin if ioresult<>0 then begin writeln('Hibás directory művelet - ',es); halt; end; end; begin {$I-} write_act_dir; mkdir('tempy'); errchk('A directory már létezik.'); chdir('tempy'); errchk('Nincs ilyen directory'); write_act_dir; chdir('..'); errchk('Nincs ilyen directory'); write_act_dir; rmdir('tempy'); errchk('A directory nem létezik vagy nem üres.'); {$I+} end. Ha a program indításakor az aktuális könyvtár a C:\PASCAL, akkor a program kimenete: C:\PASCAL C:\PASCAL\TEMPY C:\PASCAL Ellenőrző kérdések: 1. Hasonlítsa össze a szöveges és a típusos file-t az alábbi szempontok alapján: - elérési lehetőségek, - az elemek jellemzői, - megnyitási módok, - pozícionálás a file-ban. 2. Ismertesse a file-kezelés általános lépéseit… 3. Ismertesse a reset eljárás működését mind a három file-típus esetében? 4. Hogyan lehet a I/O hibákat a programon belül lekezelni? 5. Definiáljon rekordot, amely a következő adatokat tartalmazza: név, születési év, hónap, nap, hely, fizetés, adósság, lakcím. Deklaráljon ehhez a típushoz file-változót közvetlen elérésű állományhoz… 6. Csopostosítsa a következő eljárásokat és függvényeket: seek, append, assign, close, write, readln, rewrite, filepos, filesize, blockread, eoln, eof, reset csak text file csak típusos file mindkettő egyik sem Feladatok: 1. Śrjon programot, amely az type rt = record tipus : string[12]; ear : real; db : integer; memkb : integer; end; rekordokat tartalmazó file-t létrehozza, illetve amely kiírja annak tartalmát, miközben az ear mezőket összegzi. (RTCREAL.PAS, RTREAD.PAS) 2. Śrjon boolean típusú függvényt, amely egy adott nevű file-ról megmondja, hogy létezik-e (true=igen). (FEXIST.PAS) 3. Egy szöveges file (TEXTREAL.TXT) tetszőleges számú real adatot tartalmaz soraiban. Śrjon programot, amely a szöveges file tartalmát real típusú file-ba másolja… (TEXTREAL.PAS) 4. Śrjon programot, amely adott nevű szöveges file-t lapokra tördelve másik szöveg file-ba másol… A program kérje be a file-ok neveit, sorok számát a lapon, oldalsorszám kezdetét és a fejléc szövegét. (TORDEL.PAS)