IX.  

TYPY  SOUBOR


Soubor je posloupnost dat, existující nebo vytvářená mimo operační paměť počítače na některém z připojených zařízení (disk, tiskárna, klávesnice…). V operačním systému DOS je soubor identifikován speciálním řetězcem znaků, zvaným systémová specifikace. Úplná systémová specifikace diskového souboru obsahuje označení zařízení (logického disku), cestu k adresáři a jméno souboru. Specifikace ostatních souborů obsahují pouze označení zařízení. Označení logického disku se skládá z písmene a dvojtečky (například a:). Toto jsou označení některých dalších běžných zařízení:

con konzola (klávesnice a obrazovka)
prn tiskárna
nul fiktivní zařízení

Fiktivní zařízení se používá především pro ladění programů. Výstup na toto zařízení se „ztrácí“, vstup z něj je prázdný.

Cesta obsahuje postupný výčet jmen adresářů na trase k souboru. Položky výčtu jsou vzájemně odděleny znakem obrácené lomítko (\), který se vkládá i mezi cestu a označení zařízení a mezi cestu a jméno souboru. Všechna jména jsou maximálně osmiznaková, mohou však být doplněna až tříznakovou příponou, která je pak od (hlavní části) jména oddělena tečkou.


POZNÁMKA: Jména adresářů a souborů (včetně přípon) ve specifikaci nesmí obsahovat „zakázané“ znaky (< > , ; \ / . : [ ] + = * ? | a mezera), které využívá DOS k jiným účelům, všechny ostatní znaky jsou povoleny. Verzálky a minusky se ve specifikacích souborů nerozlišují.

Podrobnější informace o struktuře systémové specifikace souboru lze nalézt v dokumentaci operačního systému DOS.

Kromě dalších atributů souboru uchovává operační systém především informaci o jeho velikosti, čímž se rozumí počet bytů dat, kterými je soubor tvořen. Velikost je omezena kapacitou média, na němž se soubor nachází.

Z pohledu programovacího jazyka Turbo Pascal je soubor datovým objektem — abstrakcí bez konkrétních fyzických vlastností souborů operačního systému. Jazyk definuje soubor jako posloupnost komponent stejného typu, předem neznámé a neomezené délky. Umístění komponenty v souboru charakterizuje její pozice (index), udávající pořadí komponenty od začátku souboru. Číslování začíná od nuly (počáteční komponenta se nachází na pozici nula).

Základními operacemi nad souborem jsou čtení aktuální komponenty a zápis (do) aktuální komponenty — komponenty na aktuální pozici v souboru. Hodnota aktuální pozice je udržována automaticky — po zpracování komponenty operací čtení nebo zápisu je automaticky inkrementována, takže „ukazuje“ na komponentu následující.

IX.1. 

Proměnné typu soubor

V programu je soubor identifikován pomocí proměnné některého ze souborových typů, v jejíž vnitřní struktuře jsou zakódovány údaje o aktuálním stavu souboru. Tato proměnná může být v průběhu výpočtu programu zástupcem neomezeného počtu fyzických souborů, v každém okamžiku je však spojena nejvýše s jedním. Typem obslužné proměnné je stanovena struktura souboru, z níž vyplývá způsob jeho zpracování. V tomto smyslu jsou rozlišovány dvě základní třídy souborů: soubory textovébinární (datové).

soubor   textový
 binární

Textové soubory se používají pro komunikaci programu s uživatelem. Data jsou v nich uložena ve vnější reprezentaci (jako při výpisu na displej), takže jsou uživatelem přímo čitelná, zato při jejich načítání resp. zápisu programem musí být prováděny vstupně-výstupní konverze z vnější na vnitřní reprezentaci resp. z vnitřní na vnější reprezentaci. Jako textové jsou zpracovány pouze soubory, jejichž obslužné proměnné jsou standardního (jazykem předdeklarovaného) typu Text (nebo jeho identického ekvivalentu).

Binární soubory jsou určeny pro komunikaci mezi programy či ukládání dat programem před jejich pozdějším zpracováním. Obsahují data v příslušném vnitřním tvaru (žádné vstupně-výstupní konverze se neprovádějí), takže nejsou uživatelem přímo čitelné. Podle způsobu zpracování jsou klasifikovány na soubory typovénetypové.

binární soubor   typový
 netypový

Jako typové jsou zpracovány ty soubory, jejichž obslužná proměnná má v popisu svého typu (obrázek 44) uveden typ komponent souboru, uvozený rezervovanými slovy file of. Takové soubory jsou pak chápány jako posloupnosti hodnot specifikovaného typu. Pokud specifikace typu komponent chybí, tj. je-li v popisu typu soubor uvedeno pouze file, je proměnná zástupcem souboru netypového. Komponenty netypových souborů jsou chápány jako bloky bytů, jejichž vnitřní struktura není při zpracování souboru důležitá.

Obrázek 44: Popis typu soubor

Popis typu soubor může být (stejně jako popis jiného typu) uveden buď přímo v deklaraci proměnné nebo v deklaraci identifikátoru typu. Typ komponent nesmí být soubor ani strukturovaný typ s komponentou typu soubor na jakékoliv úrovni struktury, jinak smí být libovolný.

type RealFile = file of Real;
     Kniha    = record
                 Autor, Nazev : string [30];
                 Editor       : string [15];
                 RokVydani    : Word
                end;
var  F1       : RealFile;
     F2       : file of Kniha;
     F3       : file of array [1..10] of Integer;
     F4       : file;
Těmito deklaracemi jsou zavedeny čtyři proměnné typu soubor. Každý fyzický soubor, který bude později spojen s proměnnou F1, bude chápán jako posloupnost reálných čísel, soubor spojený s proměnnou F2 jako posloupnost komponent typu Kniha, soubor spojený s proměnnou F3 jako posloupnost polí uvedeného typu a soubor spojený s proměnnou F4 jako posloupnost bloků bytů, bez definované vnitřní struktury bloků.

Deklarace proměnné F3 je sice z hlediska syntaxe bezchybná, avšak pro zpracování komponent fyzického souboru nepoužitelná. Jak procedura čtení komponenty ze souboru, tak procedura zápisu komponenty do souboru totiž vyžadují identitu typu komponent souboru a typu proměnné, do níž je komponenta načítána resp. jejíž hodnota má být jako komponenta do souboru zapsána. Pro splnění podmínky identity je nutné, aby typ komponent byl pojmenovaný (aby byl v popisu typu soubor uveden identifikátor typu komponent, jako například v deklaraci proměnné F2).

IX.2. 

Zpracování souborů v programu

Pro proměnné typu soubor nejsou definovány vůbec žádné operace, dokonce ani přiřazovací příkaz ne. Samotná proměnná obsahuje jisté informace o fyzickém souboru s nímž je spojena, přičemž veškeré manipulace s těmito údaji i se samotnými komponentami fyzického souboru je nutno realizovat prostřednictvím standardních procedur a funkcí. Z těchto důvodů není vhodné na proměnnou typu soubor pohlížet jako na datový objekt, mající nějakou hodnotu. Nelze ji použít v přiřazovacím příkaze a pokud je předávána podprogramu, musí být odpovídající formální parametr volán výlučně odkazem.

Jak bylo již v předchozím textu řečeno, deklarace proměnné typu soubor v sobě nese informaci o tom, zda soubor s ní spojený je textový, typový nebo netypový. Obecné standardní podprogramy, používané pro zpracování souborů, modifikují svou činnost podle toho, do které třídy aktuální soubor patří. Kromě obecných pak existují i podprogramy speciální, které lze použít pouze pro soubory určité třídy.

Zpracování souboru programem vyžaduje tuto posloupnost akcí (realizovaných voláním zmíněných podprogramů), jejichž pořadí nelze zaměnit:

Po zavření souboru jej lze znovu otevřít a pokračovat v jeho zpracování nebo opakovat uvedenou posloupnost akcí pro tutéž proměnnou a jiný (další) fyzický soubor.


POZNÁMKA: Z uvedeného rozhodně nevyplývá, že by v každém okamžiku mohl být v otevřeném stavu nejvýše jeden soubor. Pro mnoho programů je naopak typické, že pracují s několika otevřenými soubory současně.

Spojení proměnné se souborem   Spojením proměnné s fyzickým souborem je míněna akce, kterou je specifikován konkrétní fyzický soubor, jejž bude dále uvažovaná proměnná v programu zastupovat.

procedure Assign (var F: soubor; Spec: string);

Spojí proměnnou F (libovolného typu soubor) se souborem, jehož systémová specifikace je hodnotou parametru Spec. Pokud je hodnotou parametru Spec prázdný řetězec, je proměnná spojena se standardním systémovým zařízením con.

var Vstup, Vystup : Text;
    Specifikace   : string;
...
Write('Soubor vstupních dat: ');
Readln(Specifikace);
Assign(Vstup, Jmeno);
Assign(Vystup, 'abcd.txt');
...
Program, jehož fragment je uveden výše, pracuje se soubory VstupVystup. Specifikace souboru Vstup se zadává až za běhu programu z klávesnice, kdežto specifikace souboru Vystup je pevně daná.

Otevření souboru   Otevření souboru zahrnuje různé akce, spojené s inicializací přístupu k souboru — vyhledání fyzického souboru na disku, případně jeho založení, přidělení komunikačního kanálu, počáteční nastavení aktuální pozice v souboru a pod. Realizuje se voláním některé z procedur Reset, Rewrite nebo Append. Procedury ResetRewrite jsou univerzální, modifikují svou činnost podle typu otvíraného souboru, kdežto procedura Append je určena výhradně pro otevírání souborů textových.

Volba vhodné procedury závisí na tom, zda otevíraný soubor již existuje nebo má být vytvořen, a na tom, zda má být použit pro čtení nebo zápis dat.

Zpracování komponent souboru   Pro každou třídu souborů existují podprogramy pro zpracování jejich komponent. Jedná se především o procedury pro zápis a čtení komponent a různé funkce, indikující stav souboru.

Zavření souboru   Při zavírání jsou ve fyzickém souboru realizovány dosud nevykonané změny (vyprázdnění vyrovnávací paměti, aktualizace údajů v hlavičce souboru) a je uvolněn použitý komunikační kanál. Opomenutí zavření vytvořeného souboru vede zpravidla ke ztrátě dat.

procedure Close (var F: soubor);

Zavře soubor, spojený s parametrem F (proměnnou libovolného typu soubor).

IX.3. 

Textové soubory

Jako textové jsou chápány (pouze) soubory, spojené s proměnnou předdeklarovaného typu Text. Jejich komponentami jsou znaky. Vůči binárním mají textové soubory zvláštní postavení — jejich vstupní a výstupní procedury obsahují vestavěné konverze mezi vnitřními (binárními) a vnějšími (textovými) reprezentacemi běžných typů dat. Například do binárního souboru typu file of Char, jehož komponentami jsou rovněž znaky, lze zapsat pouze hodnotu proměnné typu Char, kdežto do textového souboru může být zapsána hodnota libovolného výrazu některého z povolených typů — příslušná procedura automaticky provede konverzi této hodnoty na posloupnost znaků (např. číslic dekadického zápisu čísla) a tu pak do textového souboru zapíše.

Výskytem dvojic znaků CR (#13) a LF (#10) je obsah textového souboru členěn na řádky, kdežto soubory ostatních typů tuto vnitřní organizaci komponent postrádají. Pouze u textových souborů lze proto například testovat stav „konec řádky“.

Za svou „přívětivost k uživateli“ textové soubory platí dvěmi omezujícími vlastnostmi: lze je zpracovávat pouze sekvenčně (postupně připisovat na konec souboru resp. postupně číst od začátku souboru) a nelze je otevřít pro současné čtení i zápis.

Otevření textového souboru   Textový soubor je možno otevřít některým ze tří způsobů — pro čtení, pro zápis nebo pro přípis. Způsob otevření je dán volbou jedné z procedur Reset, Rewrite nebo Append. Proměnná, která je uvedena jako parametr procedury, již musí být spojena s konkrétním fyzickým souborem, jinak dojde k chybě. Otevírání již otevřeného souboru naopak chybou není, před znovuotevřením je totiž soubor zavřen automaticky.

procedure Reset (var F: Text);

Otevře pro čtení textový soubor spojený s proměnnou F. Fyzický soubor musí v okamžiku otvírání již existovat, jinak dojde k chybě. Ukazatel aktuální pozice je nastaven na začátek souboru, ukazuje tedy na první znak. Ze souboru lze pak pouze číst, nelze do něj zapisovat.

procedure Rewrite (var F: Text);

Textový soubor, spojený s proměnnou F, založí (vytvoří nový) a otevře pro zápis. Pokud specifikovaný soubor již existuje, je nejprve smazán (!), nelze takto otevřít existující soubor. Po otevření není v souboru žádný znak, ukazatel aktuální pozice je tedy nastaven současně na začátek i konec souboru. Do souboru lze pak pouze zapisovat, nelze z něj číst.

procedure Append (var F: Text);

Otevře pro přípis textový soubor spojený s proměnnou F. Fyzický soubor musí v okamžiku otvírání již existovat (jinak dojde k chybě), nelze takto založit nový soubor. Ukazatel aktuální pozice je nastaven na konec souboru (za poslední platný znak). Dále se soubor chová jako po otevření procedurou Rewrite — lze do něj pouze zapisovat, nelze z něj číst.

Proměnné Input a Output   Jazykem jsou předdeklarovány dvě proměnné typu Text — proměnná Input a proměnná Output.

var Input, Output: Text;

Obě proměnné jsou součástí každého programu. Na začátku jeho běhu jsou automaticky spojeny se standardním vstupním resp. výstupním zařízením operačního systému, ty jsou pak automaticky otevřeny a při ukončení běhu programu automaticky uzavřeny:

Assign (Input,  ''); Reset (Input);
Assign (Output, ''); Rewrite (Output);
... { uživatelská část programu }
Close (Input);
Close (Output)
Jinak jsou InputOutput obyčejnými proměnnými typu Text, které lze volně používat stejným způsobem, jako proměnné deklarované uživatelsky.

Zpracování textového souboru   Do textového souboru lze vždy buď pouze sekvenčně zapisovat nebo z něj pouze sekvenčně číst. Sekvenční přístup spočívá v automatickém přesunu ukazatele aktuální pozice na následníka právě přečteného resp. zapsaného znaku v souboru. Ze souboru, který je otevřen pro čtení, lze tedy znaky číst pouze postupně, k již přečteným znakům se nelze vracet (leda znovuotevřením souboru), ani nelze znaky přeskakovat. Do souboru otevřeného pro zápis lze podobně zapisovat nové znaky pouze na jeho konec, nelze přepisovat znaky již zapsané.

Pro zpracování obsahu textového souboru jsou k dispozici procedury zápisu WriteWriteln, procedury čtení ReadReadln a testovací funkce EOF, EOLn, SeekEOFSeekEOLn.

Činnost všech těchto podprogramů a předepsaný tvar jejich volání již byly zevrubně popsány v kapitole III. Nebylo však řečeno, že na prvním místě v seznamu jejich parametrů může být uvedena proměnná typu Text. Pokud není uvedena, aplikuje se činnost podprogramu na soubor proměnné Input (Read, Readln, EOF, SeekEOF, EOLn, SeekEOLn) resp. Output (Write, Writeln). Pokud však uvedena je, aplikuje se činnost podprogramu na soubor spojený s ní. Implicitně zpracovávaným souborem je tedy soubor proměnné Input pro vstupní operace resp. soubor proměnné Output pro výstupní operace.


PŘÍKLAD: Sestavíme program pro zpracování a tisk klasifikační tabulky třídy. Vstupní data jsou k dispozici v textovém souboru v tomto tvaru:

9
Plášek Zdeněk
1 2 3 2 2 3 4 1 2
Marek Miloslav
1 1 2 1 1 2 3 1 1
Holubářová Lenka
2 2 3 3 2 3 4 1 2
Holub Ladislav
1 2 2 1 1 3 3 1 1
Koutský Dušan
1 1 2 1 1 2 2 1 1
Vaňousová Iva
1 1 2 2 2 3 3 1 1

Číslo v první řádce má význam počtu klasifikovaných předmětů. V dalších řádkách následují jména jednotlivých studentů a jejich závěrečné známky (v nějakém pevně daném pořadí předmětů). Počet studentů není předem znám, avšak není příliš velký. Klasifikační tabulku vytiskne program do výstupního textového souboru podle tohoto vzoru:

Koutský Dušan         1    1    2    1    1    2    2    1    1    1.33
Marek Miloslav        1    1    2    1    1    2    3    1    1    1.44
Holub Ladislav        1    2    2    1    1    3    3    1    1    1.67
Vaňousová Iva         1    1    2    2    2    3    3    1    1    1.78
Plášek Zdeněk         1    2    3    2    2    3    4    1    2    2.22
Holubářová Lenka      2    2    3    3    2    3    4    1    2    2.44
                      1.17 1.50 2.33 1.67 1.50 2.67 3.17 1.00 1.33 1.81

Jednotlivým studentům odpovídají jednotlivé řádky tabulky, poslední údaj na řádce je studijní průměr studenta. Řádky jsou podle tohoto údaje v tabulce seřazeny. Poslední řádka tabulky obsahuje klasifikační průměry třídy v jednotlivých předmětech a na posledním místě celkový průměr známek všech studentů ze všech předmětů.

{======================================================================}
program Klasifikace;
{======================================================================}

 const MaxZnamek   = 10;                    { maximální počet známek   }
       MaxStudentu = 30;                    { maximální počet studentů }

 type  Student     = record                 { struktura studenta       }
                      Jmeno  : string [20];
                      Znamky : array [1..MaxZnamek] of 1..5;
                      Prumer : Real
                     end;
       Trida       = array [1..MaxStudentu] { struktura třídy          }
                      of Student;

 var   Soubor      : Text;            { vstupní a později              }
                                      { výstupní textový soubor        }
       Spec        : string;          { systémová specifikace souboru  }
       T           : Trida;           { tabulka údajů                  }
       Studentu,                      { skutečný počet studentů        }
       Znamek,                        { skutečný počet známek          }
       S,                             { index studenta                 }
       Z           : Byte;            { index známky                   }
       Soucet      : Word;            { součet známek studenta a       }
                                      { později součet všech známek    }
       Sum         : array [1..MaxZnamek]  { součty známek všech stud. }
                      of Byte;        { v jednotlivých předmětech      }
       Pom         : Student;         { pomocné proměnné               }
       I, J        : Byte;

begin { program }
 Writeln;                             { tisk titulku                   }
 Writeln('KLASIFIKACE');
 Writeln;
 Write('vstupní soubor  : ');         { dotaz na specifikaci souboru,  }
 Readln(Spec);                        { obsahujícího vstupní data      }
 Assign(Soubor, Spec);                { inicializace souboru pro čtení }
 Reset(Soubor);
 S := 0;                              { inicializace indexu studenta   }
 Readln(Soubor, Znamek);              { načtení skutečného počtu známek}
 for Z := 1 to Znamek do Sum[Z] := 0; { inicializace součtů známek     }
 while not SeekEOF(Soubor) do         { dokud není vstupní soubor celý }
  begin                               { zpracován                      }
   Inc(S);                            { index dalšího studenta         }
   Soucet := 0;                       { inicializace součtu jeho známek}
   with T[S] do
    begin
     Readln(Soubor, Jmeno);           { načtení jména                  }
     for Z := 1 to Znamek do          { pro všechny známky             }
      begin
       Read(Soubor, Znamky[Z]);       { načtení známky                 }
       Inc(Soucet, Znamky[Z]);        { přičtení do součtu studenta    }
       Inc(Sum[Z], Znamky[Z])         { přičtení do součtu předmětu    }
      end;                            { další známka                   }
     Prumer := Soucet/Znamek          { studijní průměr studenta       }
    end;
   Readln(Soubor)                     { nová řádka                     }
  end;                                { další student                  }
 Studentu := S;                       { skutečný počet studentů        }
 Close(Soubor);                       { zavření zpracovaného souboru   }
 for S := 1 to Studentu-1 do          { seřazení tabulky studentů      }
  begin                               { podle studijních průměrů       }
   I := S;
   for J := S+1 to Studentu do
    if T[I].Prumer > T[J].Prumer then I := J;
   if I <> S then
    begin
     Pom  := T[S];
     T[S] := T[I];
     T[I] := Pom
    end
  end;
 Write('výstupní soubor : ');         { dotaz na specifikaci souboru   }
 Readln(Spec);                        { výstupních dat                 }
 Assign(Soubor, Spec);                { jeho inicializace pro zápis    }
 Rewrite(Soubor);
 for S := 1 to Studentu do            { pro všechny studenty           }
  with T[S] do
   begin
    Write(Soubor, Jmeno, ' ':21-Length(Jmeno)); { výpis jména studenta }
    for Z := 1 to Znamek do           { pro všechny jeho známky        }
     Write(Soubor, Znamky[Z]:5);      { výpis známky                   }
    Writeln(Soubor, Prumer:8:2)       { výpis studijního průměru       }
   end;
 Writeln(Soubor);                     { prázdná řádka                  }
 Write(Soubor, ' ' : 24);             { mezery do začátku sloupce      }
                                      { známek prvního předmětu        }
 Soucet := 0;                         { inicializace součtu všech      }
                                      { známek ze všech předmětů       }
 for Z := 1 to Znamek do              { pro všechny předměty           }
  begin
   Write(Soubor, Sum[Z]/Studentu:5:2);{ tisk průměru                   }
   Inc(Soucet, Sum[Z])                { aktualizace součtu             }
  end;
 Writeln(Soubor, Soucet/Studentu/Znamek:5:2); { celkový průměr         }
 Close(Soubor)                        { zavření výstupního souboru     }
end. { program }

IX.4. 

Typové soubory

Typové soubory jsou určeny pro uchování dat a komunikaci mezi programy, nikoliv pro komunikaci programu s uživatelem. Data jsou v nich uložena ve vnitřní reprezentaci, proto nejsou obecně čitelná například pomocí textového editoru nebo po běžném výpisu na tiskárnu či displej, které soubory zpracovávají jako soubory textové — každý byte souboru je jimi interpretován jako znak.

Pro ilustraci poslouží jednoduchý příklad: Chceme vytvořit soubor, obsahující jediný údaj — číslo 65. Vytvoříme-li jej jako soubor typu file of Byte, bude obsahovat jediný byte s hodnotou 65. Pokud bychom ovšem později četli tento soubor jako textový, byl by jeho jediný byte interpretován jako znak #65 neboli 'A'. Bude-li soubor naopak vytvořen jako textový, bude obsahovat dva byty — znak '6' a znak '5'. Při čtení tohoto souboru jako file of Byte by pak první z jeho bytů byl interpretován jako číslo 54 ('6' = #54) a druhý jako číslo 53 ('5' = #53).

Na rozdíl od textových lze binární (typové i netypové) soubory otevřít pro současné čtení i zápis a je umožněno jejich index-sekvenční zpracování (výběr aktuální komponenty) — po přečtení či zápisu komponenty je sice ukazatel aktuální pozice v souboru automaticky inkrementován, v kterémkoliv okamžiku po otevření souboru jej však lze nastavit i explicitně.

Otevření typového souboru   Pro otevření existujícího souboru je třeba použít proceduru Reset, pro založení nového souboru proceduru Rewrite. Obě pracují obdobně jako při otvírání souboru textového.

procedure Reset (var F: typový soubor);
procedure Rewrite (var F: typový soubor);

Obě procedury otevírají fyzický soubor spojený s parametrem F a ukazatel aktuální pozice v souboru nastavují na jeho začátek. Fyzický soubor, otevíraný procedurou Reset, musí v okamžiku jejího volání již existovat, kdežto Rewrite případně existující fyzický soubor smaže a založí nový. Procedura Append nemůže být pro otevření typového souboru použita — otevření existujícího typového souboru pro přípis lze dosáhnout procedurou Reset a následným nastavením ukazatele aktuální pozice na konec souboru (procedura Seek).

Při použití procedury Rewrite je soubor vždy otevřen současně pro čtení i pro zápis (jeho komponenty lze číst i zapisovat), kdežto při použití procedury Reset závisí režim přístupu k souboru na hodnotě standardní proměnné FileMode.

const FileMode: Byte = 2;

Definuje režim přístupu k binárnímu souboru, otvíranému procedurou Reset. Všeobecně platné hodnoty proměnné a jim příslušející režimy přístupu k souboru jsou:

  kód     režim  
0 pouze pro čtení
1 pouze pro zápis
2 pro čtení i zápis


POZNÁMKA: V závislosti na verzi operačního systému mohou existovat i další platné hodnoty, umožňující sdílení souborů v síťovém prostředí.

Přiřazení nové hodnoty proměnné FileMode způsobí, že všechna další volání procedury Reset vezmou tuto hodnotu v úvahu. Implicitní hodnotou (při startu programu) je 2.

Zpracování typového souboru   Pro čtení aktuální komponenty a zápis do aktuální komponenty jsou stejně jako u textových souborů k dispozici procedury WriteRead. Neprovádějí však žádné vstupně-výstupní konverze a jejich modifikace WritelnReadln nejsou povoleny (obsah binárních souborů není členěn na řádky).

procedure Read (var F: typový soubor; var X1[, X2...]: typ komponent);

Přečte do proměnných X1, X2... příslušný počet komponent ze souboru, spojeného s proměnnou F. Všechny parametry X1, X2... musí být proměnné toho typu, který je uveden jako typ komponent souboru v deklaraci typu proměnné F. Soubor musí být otevřen pro čtení (nebo pro čtení i zápis) a požadovaný počet komponent musí být k dispozici — čtení za koncem souboru je chybou.

procedure Write (var F: typový soubor; var X1[, X2...]: typ komponent);

Zapíše hodnoty proměnných X1, X2... do souboru F. Všechny parametry X1, X2... musí být opět striktně proměnné toho typu, který je uveden jako typ komponent souboru v deklaraci typu proměnné F (nemohou to být výrazy ani proměnné typů pouze kompatibilních s typem komponent souboru). Soubor musí být otevřen pro zápis (nebo pro čtení i zápis). Zapisovat lze na konec i dovnitř souboru (ve druhém případě jsou zápisem nových hodnot přepisovány existující komponenty souboru).

var F: file of Char;
    C: 'a'..'z';
    Z: Char;
...
Write (F, Z, C);     { chyba - C není typu Char }
Zatímco zpracování komponent textových souborů je pouze sekvenční, typové (a netypové) soubory jsou zpracovávány index-sekvenčně. Procedury ReadWrite sice čtou a zapisují komponenty sekvenčně od aktuální pozice v souboru, tu však lze kdykoliv od okamžiku otevření binárního souboru až do jeho zavření nastavit explicitně. Pro podporu index-sekvenčního zpracování komponent binárních souborů jsou implementovány následující podprogramy. Všechny předpokládají, že soubor F je již otevřen (jinak jejich volání způsobí chybu).

function FileSize (var F: binární soubor): LongInt;

Vrací velikost souboru v komponentách (aktuální celkový počet komponent v souboru).

function FilePos (var F: binární soubor): LongInt;

Vrací hodnotu ukazatele aktuální pozice v souboru — index aktuální komponenty (počáteční komponenta má index 0).

procedure Seek (var F: binární soubor; Pozice: LongInt);

Nastavuje ukazatel aktuální pozice v souboru na hodnotu parametru Pozice.

procedure Truncate (var F: binární soubor);

Odstraní (vymaže) ze souboru aktuální komponentu a všechny následující.

function EOF (var F: binární soubor): Boolean;

K dispozici je rovněž funkce EOF, testující stav konec souboru. Vrací True v případě, kdy se ukazatel aktuální pozice v souboru nachází za jeho poslední komponentou (má hodnotu velikosti souboru v komponentách).


POZNÁMKA: Dříve popsané testovací funkce EOLn, SeekEOFSeekEOLn jsou použitelné pouze pro textové sobory, neboť souvisí s jejich strukturou.


PŘÍKLAD: Následující program je realizací jednoduché databáze — knihovny. Knihovnou je zde typový soubor, nad nímž jsou prováděny programem implementované operace: založení nové knihovny, otevření existující knihovny, přípis titulu do knihovny, výmaz titulu a tisk seznamu titulů.

{======================================================================}
program Knihovnik;
{======================================================================}

 type Titul         = record                  { struktura titulu       }
                       Autor  : string [20];
                       Nazev  : string [20];
                       Editor : string [10];
                       Rok    : Word;
                       Medium : (Kniha, MGF, LP, CD)
                      end;

 var  Knihovna      : file of Titul;          { datový soubor          }
      JmenoKnihovny : string;                 { systémová specifikace  }
      Akce          : Char;                   { symbol zvolené operace }

 procedure ZalozeniKnihovny;
 {-------------------------}
 begin { ZalozeniKnihovny }
  Close(Knihovna);                    { zavření aktuální knihovny      }
  Write('založit knihovnu : ');       { zadání systémové specifikace   }
  Readln(JmenoKnihovny);              { zakládané knihovny             }
  Assign(Knihovna, JmenoKnihovny);    { a její inicializace            }
  Rewrite(Knihovna)
 end; { ZalozeniKnihovny }

 procedure OtevreniKnihovny;
 {-------------------------}
 begin { OtevreniKnihovny }
  Close(Knihovna);                    { zavření aktuální knihovny      }
  Write('otevřít knihovnu : ');       { zadání systémové specifikace   }
  Readln(JmenoKnihovny);              { otvírané (existující) knihovny }
  Assign(Knihovna, JmenoKnihovny);    { a její inicializace            }
  Reset(Knihovna)
 end; { OtevreniKnihovny }

 procedure PripisTitulu;
 {---------------------}

  var T          : Titul;                     { připisovaný titul      }
      CisloMedia : Char;                      { pomocné označení média }

 begin { PripisTitulu }
  with T do                                   { zadání nového titulu   }
   begin
    Write('autor  : ');                       { položka Autor          }
    Readln(Autor);
    Write('název  : ');                       {         Nazev          }
    Readln(Nazev);
    Write('editor : ');                       {         Editor         }
    Readln(Editor);
    Write('rok    : ');                       {         Rok            }
    Readln(Rok);
    repeat                                    { pomocné označení média }
     Write('médium (0-KNIHA 1-MGF 2-LP 3-CD) : ');
     Readln(CisloMedia)
    until (CisloMedia >= '0') and (CisloMedia <= '3');
    case CisloMedia of                        { určení média           }
     '0': Medium := Kniha;                    { z pomocného označení   }
     '1': Medium := MGF;
     '2': Medium := LP;
     '3': Medium := CD
    end
   end;
  Seek(Knihovna, FileSize(Knihovna));         { nastavení pozice zápisu}
                                              { na konec knihovny      }
  Write(Knihovna, T)                          { zápis titulu           }
 end; { PripisTitulu }

 procedure VymazTitulu;
 {--------------------}

  var T           : Titul;            { mazaný titul                   }
      CisloTitulu : LongInt;          { jeho číslo (pořadí v seznamu)  }
      Volba       : Char;             { potvrzení požadavku smazání    }
      I           : LongInt;          { pomocný index pozice v souboru }

 begin { VymazTitulu }
  Write('číslo titulu : ');           { zadání čísla titulu            }
  Readln(CisloTitulu);
  Writeln;
  if (CisloTitulu < 1) or (CisloTitulu > FileSize(Knihovna)) then
   Writeln('chybné číslo titulu')     { zadané číslo mimo rozsah       }
  else                                { přípustné číslo                }
   begin
    Seek(Knihovna, CisloTitulu-1);    { nastavení pozice v knihovně    }
    Read(Knihovna, T);                { načtení titulu uvedeného čísla }
    with T do                         { vytištění na displej (kontrola)}
     begin
      Write(Autor, ' : ', Nazev, ', ', Editor, ' ', Rok, ', ');
      case Medium of
       Kniha : Writeln('KNIHA');
       MGF   : Writeln('MGF');
       LP    : Writeln('LP');
       CD    : Writeln('CD')
      end
     end;
    Writeln;
    Write('vymazat ? (A/N) : ');      { potvrzení požadavku výmazu     }
    Readln(Volba);
    if UpCase(Volba) = 'A' then
     begin                            { výmaz :                        }
      for I := CisloTitulu to FileSize(Knihovna)-1 do   { pro všechny  }
                                                        { další tituly }
       begin
        Read(Knihovna, T);            { načtení titulu                 }
        Seek(Knihovna, I-1);          { a jeho zápis na předchozí      }
        Write(Knihovna, T);           { pozici                         }
        Seek(Knihovna, I+1)           { další titul                    }
       end;
      Seek(Knihovna, FileSize(Knihovna)-1);      { "odříznutí"         }
      Truncate(Knihovna)                         { poslední komponenty }
     end
   end
 end; { VymazTitulu }

 procedure SeznamTitulu;
 {---------------------}

  var Seznam      : Text;             { soubor výpisu seznamu          }
      Spec        : string;           { specifikace souboru            }
      T           : Titul;            { vypisovaný titul               }
      CisloTitulu : LongInt;          { pořadí titulu v knihovně       }

 begin { SeznamTitulu }
  Write('seznam vytisknout do souboru : ');       { otevření seznamu   }
  Readln(Spec);
  Assign(Seznam, Spec);
  Rewrite(Seznam);
  Writeln(Seznam);
  Writeln(Seznam, 'knihovna : ', JmenoKnihovny);  { zápis jména        }
  Writeln(Seznam);                                { knihovny           }
  Writeln(Seznam, ' ':8, 'autor', ' ':18,         { a hlavičky seznamu }
          'název', ' ':18, 'editor', ' ':7,
          'rok', ' ':4, 'médium');
  Writeln(Seznam);
  Seek(Knihovna, 0);                              { od začátku knihovny}
  for CisloTitulu := 1 to FileSize(Knihovna) do   { pro všechny tituly }
   begin
    Read(Knihovna, T);                            { načtení titulu     }
    with T do                                     { z knihovny         }
     begin
      Write(Seznam, CisloTitulu:5, ' ':3,         { výpis položek      }
            Autor,  ' ':23-Length(Autor),         { titulu do seznamu  }
            Nazev,  ' ':23-Length(Nazev),
            Editor, ' ':13-Length(Editor),
            Rok,    ' ':3);
      case Medium of
       Kniha : Writeln(Seznam, 'KNIHA');
       MGF   : Writeln(Seznam, 'MGF');
       LP    : Writeln(Seznam, 'LP');
       CD    : Writeln(Seznam, 'CD')
      end
     end
   end;
  Close(Seznam)                                   { zavření seznamu    }
 end; { SeznamTitulu }

begin { program }
 Writeln;                                         { tisk titulku       }
 Writeln('KNIHOVNÍK');
 Writeln;
 JmenoKnihovny := 'NUL';                          { inicializace       }
 Assign(Knihovna, JmenoKnihovny);                 { knihovny           }
 Reset(Knihovna);
 repeat
  Writeln('aktuální knihovna: ', JmenoKnihovny);  { tisk specifikace   }
  Writeln;                                        { aktuální knihovny  }
  Writeln('Z .......... založení knihovny');      { a menu operací nad }
  Writeln('O .......... otevření knihovny');      { knihovnou          }
  Writeln('P .......... přípis titulu');
  Writeln('V .......... výmaz titulu');
  Writeln('S .......... seznam titulů');
  Writeln('K .......... konec');
  Writeln;
  Write('akce: ');
  Readln(Akce);                                   { volba              }
  Writeln;
  Akce := UpCase(Akce);
  case Akce of                                    { výběr odpovídající }
   'Z': ZalozeniKnihovny;                         { procedury          }
   'O': OtevreniKnihovny;
   'P': PripisTitulu;
   'V': VymazTitulu;
   'S': SeznamTitulu;
   'K':
   else Writeln(#7'neznámá akce')
  end;
  Writeln
 until Akce = 'K';                                { dokud není zvolenou}
                                                  { akcí konec         }
 Close(Knihovna)                                  { zavření knihovny   }
end. { program }

IX.5. 

Netypové soubory

Pro netypové soubory není v deklaraci typu obslužné proměnné definována struktura ani velikost komponent souboru. Na komponenty netypových souborů je proto pohlíženo jako na kompaktní bloky bytů, jejichž velikost (počet bytů v bloku) je specifikována při otevření souboru. Takový přístup k souboru dovoluje podstatně zjednodušit a urychlit vstupně-výstupní operace. Zvláště výhodný je v aplikacích přenosu souborů nebo jejich částí, kdy konkrétní obsah souboru není z hlediska přenosu podstatný — archivaci vybraných částí operační paměti (například datového segmentu programu nebo videopaměti), kopírovaní souborů a pod.

Otevření netypového souboru   K otevření netypového souboru lze opět (jako k otevření souboru typového) použít pouze proceduru Reset nebo Rewrite.

procedure Reset (var F: file [; Velikost: Word]);
procedure Rewrite (var F: file [; Velikost: Word]);

Činnost obou procedur je při otvírání netypových souborů obdobná jako při otvírání souborů typových, je však rozšířena o definici velikosti komponent souboru — počtu bytů základního přenosového bloku. Pokud nepovinný parametr Velikost není uveden, je velikost přenosového bloku nastavena na implicitní hodnotu 128 bytů.

Zpracování netypového souboru   Pro index-sekvenční zpracování netypových souborů lze použít jednak podprodprogramy FileSize, FilePos, EOF, SeekTruncate, jejichž funkce byla popsána již u souborů typových, jednak speciální procedury BlockWrite (zápis bloku do souboru) a BlockRead (čtení bloku ze souboru).

procedure BlockWrite (var F: file; var Prom; Pocet: Word
                      [; var SkutPocet : Word]);
procedure BlockRead  (var F: file; var Prom; Pocet: Word
                      [; var SkutPocet: Word]);

Skutečným parametrem, odpovídajícím formálnímu parametru Prom, smí být proměnná libovolného typu. Procedura BlockWrite zapíše souvislý blok bytů z proměnné Prom do souboru F, procedura BlockRead jej do proměnné ze souboru načte. Počátkem bloku je počáteční byte proměnné resp. počáteční byte komponenty na aktuální pozici v souboru. Požadovaná velikost bloku je dána součinem počtu přenášených komponent (parametr Pocet) a jejich velikosti (definované při otevření souboru).

Po skončení přenosu je v parametru SkutPocet vrácen skutečný počet přenesených komponent. Při hodnotě menší než Pocet byl přenos předčasně ukončen, například z důvodu zaplnění média (BlockWrite) nebo vyčerpání obsahu souboru (BlockRead). Celkový počet skutečně přenesených bytů je pak dán součinem skutečného počtu přenesených komponent a jejich velikosti. Pokud nepovinný parametr SkutPocet není uveden, nastává při předčasném ukončení přenosu chyba.

Požadovaná velikost přenosového bloku by neměla překročit hranici 65 535 bytů. Při jejím překročení totiž sice není generována chyba, ale velikost přeneseného bloku neodpovídá očekávání:

var F         : file;
    Buffer    : Real;
    SkutPocet : Word;

begin
 Assign (F, 'zkusebni.sou');
 Rewrite (F, 6000);
 BlockWrite (F, Buffer, 15, SkutPocet);
 Close (F);
 Writeln ('přeneseno bloků: ', SkutPocet);
 Writeln ('přeneseno bytů : ', 6000 * SkutPocet)
end.
Tento program založí v aktuálním adresáři soubor zkusebni.sou, při jeho otevření definuje velikost komponent na 6 000 bytů a pokusí se do něj zapsat blok patnácti komponent, alokovaný v operační paměti na adrese proměnné Buffer. Při výpočtu velikosti přenosového bloku dojde k přetečení, takže výsledkem nebude 15 × 6 000 = 90 000, nýbrž 15 × 6 000 mod 65 536 = 24 464 (nejnižších šestnáct bitů matematické hodnoty součinu). Do souboru tedy bude zapsáno právě 24 464 bytů (jak se lze přesvědčit otestováním jeho velikosti) — 6 bytů proměnné Buffer, další dva byty proměnné SkutPocet a dalších 24 456 bytů. Programem hlášený počet přenesených bloků je však 4 a jemu odpovídající hlášená velikost přeneseného bloku 24 000 bytů.


PŘÍKLAD: Porovnejte rychlost zkopírování nějakého většího souboru pomocí programů Kopie1Kopie2:

{======================================================================}
program Kopie1;
{======================================================================}

 const VelBuf = 64000;                           { velikost bufferu    }
 var   Zdroj,                                    { kopírovaný soubor   }
       Cil    : file;                            { kopie               }
       Spec   : string;                          { specifikace souboru }
       Buf    : array [1..VelBuf] of Byte;       { přenosový buffer    }
       Pocet,                                    { počet úseků o       }
                                                 { velikosti VelBuf    }
       Zbytek,                                   { velikost zbytku     }
       I      : Word;                            { pomocná proměnná    }

begin {program}
 Writeln;                            { tisk titulku                    }
 Writeln('KOPIE 1');
 Writeln;
 Write('zdroj : ');                  { inicializace souboru Zdroj      }
 Readln(Spec);
 Assign(Zdroj, Spec);
 Reset(Zdroj, 1);
 Write('cíl   : ');                  { inicializace souboru Cil        }
 Readln(Spec);
 Assign(Cil, Spec);
 Rewrite(Cil, 1);
 Pocet := FileSize(Zdroj) div VelBuf;{ počet jednorázově kopírovaných  }
                                     { úseků                           }
 for I := 1 to Pocet do              { kopírování po úsecích o         }
  begin                              { velikosti bufferu               }
   BlockRead (Zdroj, Buf, VelBuf);   { načtení bufferu ze Zdroj        }
   BlockWrite(Cil,   Buf, VelBuf)    { výpis bufferu do Cil            }
  end;
 Zbytek := FileSize(Zdroj)           { velikost nezkopírovaného zbytku }
            mod VelBuf;
 BlockRead (Zdroj, Buf, Zbytek);     { načtení zbytku do bufferu       }
 BlockWrite(Cil,   Buf, Zbytek);     { výpis zbytku do Cil             }
 Close(Zdroj);                       { zavření souborů                 }
 Close(Cil)
end. { program }

{======================================================================}
program Kopie2;
{======================================================================}

 var   Zdroj,                                    { kopírovaný soubor   }
       Cil    : file of Byte;                    { kopie               }
       Spec   : string;                          { specifikace souboru }
       Buf    : Byte;                            { přenosový buffer    }
       I      : LongInt;                         { pomocná proměnná    }

begin { program }
 Writeln;                                 { tisk záhlaví               }
 Writeln('KOPIE 2');
 Writeln;
 Write('zdroj : ');                       { inicializace souboru Zdroj }
 Readln(Spec);
 Assign(Zdroj, Spec);
 Reset(Zdroj);
 Write('cil   : ');                       { inicializace souboru Cil   }
 Readln(Spec);
 Assign(Cil, Spec);
 Rewrite(Cil);
 for I := 1 to FileSize(Zdroj) do         { kopírování souboru         }
  begin
   Read (Zdroj, Buf);                     { načtení bytu               }
   Write(Cil,   Buf)                      { výpis bytu                 }
  end;
 Close(Zdroj);                            { zavření souborů            }
 Close(Cil)
end. { program }

IX.6. 

Ošetření chybových stavů

V důsledku nekorektního zpracování souborů nebo zásahem vyšší moci (vadná disketa apod.) dochází ve vstupně-výstupních operacích ke vzniku různých chyb. Vzhledem k tomu, že při předčasném ukončení programu běhovou chybou mohou být ztracena důležitá data, poskytuje Turbo Pascal možnost uživatelského ošetření vstupně-výstupních chyb.

Volba mezi automatickým a uživatelským ošetřením vstupně-výstupních chyb v uvažované části programu se provádí již při překladu příslušného úseku zdrojového textu, uvedením přepínačové direktivy {$I} na jeho začátku. Ve stavu {$I+} je povoleno automatické ošetření vstupně-výstupních chyb — důsledkem výskytu vstupně-výstupní chyby je běhová chyba programu. Ve stavu {$I–} je automatické ošetření vstupně-výstupních chyb zakázáno a předpokládá se, že budou ošetřeny uživatelsky — při výskytu chyby je pouze uloženo její číslo do standardní proměnné InOutRes. Program pak běží dál, avšak veškeré vstupně-výstupní operace jsou ignorovány, dokud není hodnota proměnné InOutRes vynulována.


POZNÁMKA: Implicitní způsob ošetření vstupně-výstupních chyb lze stanovit v zaškrtávacím poli I/O checking dialogu Compiler Options, který je přístupný příkazem Compiler nabídky Options vývojového prostředí. Při zaškrtnutém poli je automatické ošetření implicitně povoleno.

const InOutRes: Integer = 0;

Obsahuje chybový kód naposled provedené vstupně-výstupní operace — nulu, pokud byla úspěšná, resp. kladné číslo vzniklé chyby.

Seznam chybových kódů je poměrně rozsáhlý a je součástí jak manuálu, tak interaktivní nápovědy ve vývojovém prostředí. Nejčastěji se vyskytují chyby:

  kód     popis chyby  
0   žádná chyba, úspěch
2   soubor nenalezen
3   vadná či neexistující cesta
5   přístup zakázán (například při pokusu otevřít pro zápis soubor, který je přístupný pouze pro čtení)
106   chybný číselný formát (při čtení čísla z textového souboru)

function IOResult: Integer;

Vrací aktuální hodnotu proměnné InOutRes (chybový kód poslední vstupně-výstupní operace) a současně ji vynuluje (přiřadí proměnné hodnotu nula), čímž umožní normální funkci vstupně-výstupních operací.


PŘÍKLAD: V následujícím fragmentu programu je ošetřen vznik chyb při otevírání textového souboru pro čtení (v úvahu připadají především chyby 2 a 3 — soubor uvedeného jména neexistuje nebo je specifikována vadná či neexistující cesta k souboru).

var F     : Text;                      { ovládací proměnná souboru     }
    Jmeno : string;                    { specifikace fyzického souboru }
    Chyba : Word;                      { chybový kód                   }
...
 {$I-}                                 { manuální ošetření IO chyb     }
 repeat                                { opakuj                        }
  Write ('jméno souboru : ');          { načtení systémové specifikace }
  Readln (Jmeno);                      { fyzického souboru             }
  Assign (F, Jmeno);                   { spojení F s fyzickým souborem }
  Reset (F);                           { pokus o otevření pro čtení    }
  Chyba := IOResult;                   { výsledek pokusu               }
  if Chyba <> 0 then                   { nebyl-li pokus úspěšný        }
   Writeln ('chyba ', Chyba,           { zpráva o chybě                }
            ' při otvírání souboru ',
            Jmeno)
 until Chyba = 0;                      { dokud není soubor otevřen     }
 {$I+}                                 { dále již automatické ošetření }
...                                    { IO chyb                       }