5.Prednaska

Z Pascal
Prejsť na: navigácia, hľadanie
5. Prednáška

úlohy | cvičenie


Znakové reťazce

Znakový reťazec je údajový typ, ktorý obsahuje nejakú postupnosť znakov (Char). Je to vlastne postupnosť znakových premenných, s ktorými môžeme pracovať ako s celkom a môžeme ich aj jednoducho modifikovať. Naproti tomu v súbore sú údaje najčastejšie uložené na disku a ich zmena je algoritmicky trochu náročnejšia.

Znaky v znakovom reťazci (t.j. v postupnosti znakov) sú očíslované od 1 až po momentálnu dĺžku reťazca. Túto dĺžku zistíme pomocou štandardnej funkcie Length. FreePascal si uchováva dĺžku v 4 bajtoch, t.j. teoreticky by maximálna dĺžka mohla byť 4 gigabajty - v skutočnosti je obmedzená možnosťami Windows: možno len 1 gigabajt - otestujte to na vašom počítači.

Premennú typu znakový reťazec deklarujeme takto:

var Retazec: string;

Premenná Retazec môže mať zatiaľ nedefinovanú hodnotu a preto jej musíme niečo priradiť, napr. reťazcovú konštantu:

Retazec := 'reťazec';

Premenná Retazec teraz obsahuje postupnosť znakov dĺžky 7: prvý znak je 'r', druhý 'e', atď. T.j. hodnota funkcie Length(Retazec) je teraz 7. Reťazcové konštanty sú uzavreté rovnako ako znakové konštanty v apostrofoch. Premenná typu string môže obsahovať aj prázdny reťazec:

Retazec := '';

Vtedy je jej dĺžka 0.

Môžeme pracovať aj s jednotlivými prvkami postupnosti, t.j. s jednotlivými znakmi v reťazci. Napr.

Retazec := 'abcdef';
Retazec[3] := '*';

Najprv sme do Retazec priradili 5-znakový reťazec a potom sme v ňom zamenili 3-tí znak (znak 'c') za hviezdičku '*'. Nesmieme sa pritom odvolávať na znaky mimo rozsahu <1, momentálna dĺžka>, napr.

Retazec[10] := '+'; // reťazec má zatiaľ dĺžku len 6

spôsobí chybovú správu.

Okrem priradení môžeme reťazce zapisovať do textového súboru a tiež ich môžeme zo súboru čítať. Napr.

Write(Subor, premenná_typu_string ); // výpis hodnoty premennej
Read(Subor, premenná_typu_string ); // prečítanie riadka zo vstupu do premennej

Znakové reťazce môžeme porovnávať pomocou relačných operácií (=, <>, <, >, <=, >=). Reťazce sú usporiadané pomocou tzv. lexikografického usporiadania. Napr. platí

'abc' > 'ABC'
'Adam' < 'Eva'
'jana' < 'jano'
'Jana' < 'jana'

Postupne sa pri tom porovnáva znak za znakom: kým sú v oboch reťazcoch rovnaké, pokračuje sa v porovnávaní; keď sa narazí na rozdielne znaky, tak sa tieto dva porovnajú navzájom a podľa toho sa nastaví celkový výsledok porovnania. Porovnávanie dvoch znakov sa robí podľa pravidiel Char: t.j. menší je ten znak, ktorý má menší ASCII-kód.

Operácia '+' slúži na zreťazenie reťazcov (hovoríme, že + je polymorfný operátor, lebo jeho funkčnosť závisí od typu operandov: iný význam má pre čísla a iný pre reťazce). Napr.

Retazec := 'abc'+'def';      // Retazec='abcdef'
Retazec := '';
for I := 1 to 10 do         // Retazec='**********'
  Retazec := Retazec + '*';



Krátke znakové reťazce


V staršej verzii Pascalu sa môžeme stretnúť s typom string, pri ktorom sa do hranatých zátvoriek zapisuje maximálna možná dĺžka reťazca (číslo od 1 do 255). Napr.

var
  Retazec: string[20];

Označuje, že premenná Retazec môže mať maximálnu dĺžku 20 znakov. Priradenie dlhšieho reťazca spôsobí automatické odhodenie všetkých znakov za 20. znakom. Aj vo FreePascale fungujú takéto reťazce, ale my ich budeme používať veľmi zriedka. Napr. môžeme zapísať

var
  Retazec: string[10];
begin
  Retazec := 'abcd'+'efgh'+'ijkl'+'mnop';

Do premennej Retazec sa ale dostalo len prvých 10 znakov, t.j. Retazec = 'abcdefghij'.



Formátovací parameter vo Write


Formátovací parameter môžeme uviesť za ľubovoľnú vypisovanú hodnotu. Zapisuje sa zo znak ":" a označuje šírku poľa, do ktorej sa daná hodnota vypíše. Ak je vypisovaná hodnota kratšia ako šírka poľa, tak sa zľava doplní medzerami, inak sa tento parameter ignoruje. Napr.

  • za znakom alebo znakovým reťazcom
Write('*':10);
označuje, že znak sa vypíše na šírku 10, t.j. najprv 9 medzier a potom '*'
Write('Lazarus':3);
nakoľko reťazec je dlhší ako formátovací parameter, formát sa ignoruje a vypíše sa kompletný reťazec
  • formátovací parameter za celým číslom označuje šírku, do ktorej sa má zapísať číslo, ak by nevošlo do danej šírky, formát sa ignoruje
Write(25 * 25:5);
zapíše dve medzery, za ktoré dá číslo 625
  • formátovací parameter za reálnym číslom tiež označuje šírku, číslo sa vypíše v semilogaritmickom tvare; druhý formátovací parameter označuje počet desatinných miest
Write(Sin(2):15);
zapíše 9.092974E-0001
Write(Cos(2):7:4);
zapíše -0.4161
  • logické hodnoty True a False sa vypisujú rovnako ako znakové reťazce 'TRUE' a 'FALSE', preto tiež môžeme uviesť formátovací parameter, pre ktorý zrejme nemá zmysel, aby bol menší ako 5
Write(3*7>22:6, (L1 = L2) or (L1 <> L2):6)
zapíše FALSE TRUE



Podprogramy s reťazcami


FreePascal ponúka sadu preddefinovaných (štandardných) podprogramov, ktoré pracujú s reťazcami. Medzi základné patria

  • Length(reťazec)
    • táto funkcia vráti momentálnu dĺžku reťazca
  • SetLength(reťazcová_premenná, nová_dĺžka)
    • táto procedúra nastaví novú dĺžku reťazca
    • pre krátke reťazce nesmie presiahnuť deklarované maximum
      • ak je menšia ako momentálna dĺžka, znaky na konci sa strácajú
      • ak je väčšia ako momentálna, tak pridané znaky (od konca) majú nedefinovanú hodnotu
  • Copy(reťazec, od, koľko)
    • táto funkcia vráti nový reťazec, ktorý je podreťazcom pôvodného
    • z pôvodného reťazca sa zoberú znaky počínajúc od hodnoty od a zoberie sa maximálne koľko znakov
    • ak je od mimo rozsahu indexov reťazca, funkcia vráti prázdny reťazec, t.j. '', napr.
Retazec := Copy('abcde', 2, 3); // Retazec='bcd'
Retazec := Copy('abcde', 7, 3); // Retazec=''
Retazec := Copy('abcde', 3, 5); // Retazec='cde'
  • ak potrebujeme podreťazec od nejakého indexu až do konca, nemusíme vypočítať presný počet, ale môžeme použiť konštantu MaxInt, napr.
Retazec := Copy('abcde', 3, MaxInt);
  • Pos(podreťazec, reťazec)
    • táto funkcia vráti začiatočný index prvého výskytu podreťazca v danom reťazci alebo 0, ak sa v reťazci podreťazec nenachádza, napr.
I := Pos('d', 'abcde'); // I=4
I := Pos('C', 'abcde'); // I=0
I := Pos('ba', 'abababab'); // I=2


Konzolová aplikácia

Programátor veľmi často potrebuje vypisovať výsledky výpočtov buď do nejakej textovej plochy, alebo do textového súboru. V Lazarus na to máme niekoľko možností (doteraz sme to vedeli len veľmi pracne pomocou výpisu textov do grafickej plochy príkazom TextOut):

  • využijeme jednoduchosť "konzolovej aplikácie" a pomocou príkazov Write a WriteLn píšeme priamo do textového okna,
  • vo formulári aplikácie zadefinujeme textovú plochu (podobne ako grafickú plochu pomocou TImage) komponentom TMemo - do takejto plochy píšeme napr. príkazom Append,
  • naučíme sa zapisovať výsledky (texty a čísla) do textového súboru, ktorý sa potom dá prezerať v nejakej inej aplikácii (napr. Notepad, resp. Poznámkový blok) alebo vypísať pomocou LoadFromFile do textovej plochy.

Postupne sa naučíme všetky tri možnosti a tiež aj čítanie nielen z konzolovej aplikácie ale aj z textového súboru priamo z programu pomocou príkazov Read a ReadLn.

Vývojové prostredie umožňuje vytvárať aj aplikácie, ktoré nevyužívajú Windows formuláre a komponenty, ale pracujú v textovom okienku. Takáto aplikácia nemá žiadne tlačidlá ani grafickú plochu a nereaguje ani na žiadne udalosti, napr. od klikania myši. Možností, ako vytvoriť konzolovú aplikáciu je viac. My odporúčame zvoliť v menu Novy projekt ... voľba Program: 200px link=http://pascal.input.sk/images/0/0a/P5.x.png

Celý program sa redukuje na spustenie príkazov medzi begin a end "hlavného programu":

program Project1;
 
{$mode objfpc}{$H+}
 
uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes
  { you can add units after this };
 
{$R *.res}
 
begin
end.

Premenné a pomocné procedúry definujeme tesne pred begin. Všimnite si bodku za posledným end.

Niektoré riadky asi nikdy nevyužijeme, preto ich môžeme vyhodiť a ponechať len tieto (mohli by sme vyhodiť aj riadok {$R *.res} a naše jednoduché konzolové programy by fungovali naďalej):

program Project1;
 
{$mode objfpc}{$H+}
 
uses
  Classes;
 
{$R *.res}
 
begin
end.

Skôr, ako sa pustíme do programovania konzolovej aplikácie, musíme vo voľbách prekladača nastaviť, že naša aplikácia nebude pracovať s Windows prostredím a teda musíme vypnúť voľbu Aplikácia Win32 GUI. Po uložení projektu sa toto nastavenie už pamätá a nastavovať ho bude treba až pri ďalšej konzolovej aplikácii.

P5.x.png

Z historického hľadiska je tento spôsob najstarší a takto sa programovalo už v polovici minulého storočia. Ale v niektorých situáciách toto využívajú programátori aj v súčasnosti.



Vypisovanie v konzolovej aplikácii


Na výpis ľubovoľných hodnôt (textov a čísel) do textového okna konzolovej aplikácie slúžia príkazy Write a WriteLn. Oba tieto príkazy môžu mať ľubovoľný počet parametrov oddelených čiarkami - to sú hodnoty a znakové reťazce, ktoré budú postupne vypisované do textového okna. Príkaz WriteLn sa od Write líši len tým, že po vypísaní všetkých parametrov kurzor prejde na začiatok nasledovného riadka a teda všetky ďalšie výpisy sú už v ďalšom riadku. Syntax oboch príkazov je

Write(hodnota1, hodnota2, ..., hodnotaN);
WriteLn(hodnota1, hodnota2, ..., hodnotaN);

pričom príkaz WriteLn sa môže použiť aj bez parametrov - v tomto prípade sa nevypíše nič, len sa kurzor nastaví na začiatok ďalšieho riadka. Samotné hodnoty (texty aj čísla) sa najprv vypočítajú (vyhodnotia) a potom sa vypíšu v textovom tvare - čísla sa nevypisujú so žiadnymi medzerami, preto ich často pri výpise oddeľujeme medzerami, napr.

  WriteLn('*** prvý riadok obsahuje len nejaký nadpis ***');
  WriteLn;            // ďalší riadok je prázdny
  Write('desiata mocnina 2 je ');
  WriteLn(2*2*2*2*2*2*2*2*2*2);
  A := 37;
  B := 14;
  Wtiteln('výpočet ', A, ' * ', B, ' = ', A*B);
  WriteLn(A, B, A*B);     // tieto čísla nebudú oddelené medzerou, preto sa vypíše: 3714518

Podobne treba myslieť na medzery medzi číslami, aj pri výpise for-cyklom:

program Project1;
 
{$mode objfpc}{$H+}
 
uses
  Classes;
 
{$R *.res}
 
var
  I: Integer;
begin
  for I := 1 to 100 do
  begin
    Write(I * I * I);
    if I mod 8 = 0 then
      WriteLn
    else
      Write(' ');
  end;
 
  ReadLn;
end.

Program vypisuje tretie mocniny čísel, pričom po každom ôsmom čísle prejde výpis do nového riadka. Ak toto spustíme ako konzolovú aplikáciu, tak program hneď po výpise všetkých čísel končí a teda automaticky zatvorí textové okno aplikácie. Z tohto dôvodu dáme na koniec programu príkaz ReadLn, ktorý pozdrží ukončenie programu - program teraz čaká na stlačenie klávesu <Enter> a až potom skončí:

P5.x.png

Príkazy Write a WriteLn umôžňujú výpisy aj jednoducho naformátovať - ku každej vypisovanej hodnote môžeme zapísať aj formátovací parameter: je to celé číslo za znakom ':' a označuje "šírku vypisovaného poľa":

  • ak má vypisovaná hodnota (text alebo číslo) menej znakov ako "šírka poľa", výpis sa zľava doplní medzerami,
  • ak má vypisovaná hodnota viac znakov ako "šírka poľa", tento formátovací parameter sa ignoruje a vypíše sa kompletná hodnota.

Napr. tento program bez formátovacích parametrov:

begin
  WriteLn('Bratislava', 452000);
  WriteLn('Kosice', 241000);
  WriteLn('Presov', 93000);
  WriteLn('Nitra', 88000);
  WriteLn('Zilina', 87000);
  WriteLn('Banska Bystrica', 85000);
  ReadLn;
end.

vypíše:

Bratislava452000
Kosice241000
Presov93000
Nitra88000
Zilina87000
Banska Bystrica85000

Ale s formátovacími parametrami:

begin
  WriteLn('Bratislava':20, 452000:8);
  WriteLn('Kosice':20, 241000:8);
  WriteLn('Presov':20, 93000:8);
  WriteLn('Nitra':20, 88000:8);
  WriteLn('Zilina':20, 87000:8);
  WriteLn('Banska Bystrica':20, 85000:8);
  ReadLn;
end.

vypíše:

         Bratislava  452000
             Kosice  241000
             Presov   93000
              Nitra   88000
             Zilina   87000
    Banska Bystrica   85000



Čítanie v konzolovej aplikácii


Príkaz ReadLn môžeme použiť nielen na pozdržanie výpisu, resp. programu, ale aj na načítanie údajov od používateľa - používateľ napíše nejaké číslo, resp. čísla oddelené medzerami a Read, resp. ReadLn ich prečíta. Syntax príkazov je:

ReadLn;
ReadLn(premenná);
ReadLn(premenná1, premenná2, ...);
Read(premenná);
Read(premenná1, premenná2, ...);

ReadLn sa od Read líši len tým, že po prečítaní údajov do premenných, príkaz ReadLn ignoruje zvyšok vstupného riadka a ďalší Read, resp. ReadLn bude opäť očakávať od používateľa zadanie nejakých ďalších hodnôt. Príkaz Read, na rozdiel od ReadLn sa nesmie použiť bez aspoň jedného parametra. Tiež si uvedomte, že

ReadLn(A, B, C);

je to isté ako postupné volania:

Read(A); Read(B); Read(C); ReadLn;

Nasledujúci program ilustruje načítanie a spracovanie troch čísel:

var
  A, B, C: Integer;
begin
  Write('Zadaj velkost kvadra: ');
  ReadLn(A, B, C);
  WriteLn('Kvader ma hrany: ', A, ' ', B, ' ', C);
  WriteLn('Povrch = ', 2 * (A * B + A * C + B * C));
  WriteLn('Objem = ', A * B * C);
  ReadLn;
end.

a výstup je napr.

Zadaj velkost kvadra: 4 5 6
Kvader ma hrany: 4 5 6
Povrch = 148
Objem = 120

Ak sme sa pri zadávaní čísel pomýlili a na vstupe sú aj iné znaky ako čísla a medzery, program spadne s chybovou hláškou "Invalid numeric input".



Textové súbory TextFile

Vo všeobecnosti je súbor postupnosť (sekvencia) prvkov (väčšinou) rovnakého typu. Najčastejšie sa nachádza na nejakom vonkajšom zariadení, napr. na disku.

Potom textový súbor je postupnosť riadkov (aj prázdna), pričom riadok textového súboru je postupnosť znakov (aj prázdna) ukončená špeciálnou značkou <Eoln> (z anglického end of line). Všetky údaje sú v textovom súbore zapísané ako ASCII znaky.

Vo všeobecnosti sa dá so súbormi pracovať "sekvenčne" alebo "priamo":

  • priamy prístup do súboru: určíme pozíciu (index) niektorého prvku v súbore a s týmto ďalej pracujeme, napr. ho prečítame do pamäte alebo prepíšeme novým obsahom,
  • sekvenčný prístup do súboru: s prvkami súboru môžeme pracovať len postupne od prvého až po posledný: postupne vieme údaje čítať, alebo postupne ich vieme do súboru zapisovať.

Textové súbory v pascale majú iba sekvenčný prístup a preto

  • sa musí rozlišovať, či zo súboru údaje čítame alebo ich do neho zapisujeme:
    • vstupný súbor - môžeme len čítať pripravené údaje
    • výstupný súbor - môžeme len zapisovať nové údaje na koniec súboru
  • do textového súboru sa nedá aj zapisovať aj súčasne z neho čítať

Ako pracujeme so súborom:

  1. najprv zadeklarujeme premennú typu TextFile, pomocou ktorej budeme k tomuto súboru ďalej v programe pristupovať (podobne ako premenná typu TRobot slúži na prístup k robotovi)
    • var Subor: TextFile;
  2. ďalej treba tejto "súborovej" premennej priradiť nejaký konkrétny súbor (najčastejšie na disku):
    • AssignFile(Subor, meno_súboru);
  3. potom treba súbor "otvoriť" - pri otváraní určíme, či z neho budeme len čítať (Reset) alebo budeme do neho len zapisovať (Rewrite):
    • Reset(Subor); // otvorenie súboru na čítanie - súbor už musí existovať
    • Rewrite(Subor); // otvorenie súboru na zápis - ak už existuje, tak sa najprv vyprázdni
  4. potom môžeme pracovať s jeho obsahom:
    • Read(Subor, premenná); // čítanie zo súboru - z pozície ukazovateľa
      • ReadLn(Subor, ...);
    • Write(Subor, hodnota); // zápis do súboru - na jeho koniec
      • WriteLn(Subor, ...);
  5. na záver ho treba "zatvoriť", t.j. ukončíme s ním ďalšiu prácu:
    • CloseFile(Subor);

Príkazy Read a Write sú veľmi podobné tým, ktoré sme používali v konzolových aplikáciách - aj pravidlá sú tu veľmi podobné. Najväčší rozdiel je ale v tom, že pri práci so súbormi musíme počítať s tým, že súbor sa skladá z viacerých riadkov a tieto sú oddelené špeciálnou značkou <Eoln>.

Zavedieme pojem ukazovateľ, t.j. pozícia v súbore:

  • na začiatku (pri otvorení súboru) je na 1. znaku
  • po každom Read aj Write sa automaticky posunie o 1 znak vpravo (resp. o toľko znakov, koľko spracoval)
  • pri práci so súborom sa môžeme hocikedy nastaviť na jeho začiatok: príkaz Reset umožní čítanie, príkaz Rewrite vymaže momentálny obsah a teda môžeme začať zapisovať od začiatku



Koniec súboru a koniec riadka


Na testovanie konca súboru slúži štandardná logická funkcia Eof(Subor) - skratka z end of file

  • vráti True, ak je ukazovateľ nastavený za posledným znakom súboru
  • najčastejšie sa bude používať v teste while not Eof(Subor) do ..., t.j. rob, kým nie je koniec súboru
  • čítanie za koncom súboru najčastejšie spôsobí chybovú správu
  • uvedomte si, že ak je súbor otvorený na zápis (Rewrite), tak ukazovaťeľ je stále na konci a teda Eof stále vracia True
  • znak #26 má v pascale (z historických dôvodov) niekedy špeciálny význam: čítanie textového súboru si na ňom "myslí", že je na konci súboru (Eof(Subor)=True) a nedovolí čítať ďalšie znaky za ním :-(

Na testovanie konca riadka slúži štandardná logická funkcia Eoln(Subor) - skratka z end of line

  • vráti True, ak je ukazovateľ v súbore na značke <Eoln>
  • True vráti aj vtedy, keď je za posledným znakom súboru, t.j. ak platí Eof(Subor)=True, tak platí aj Eoln(Subor)=True
  • značka <Eoln> sa vnútorne kóduje dvomi znakmi #13 a #10 (hovoríme im CR a LF)
  • príkazom ReadLn(Subor) preskočíme od momentálnej pozície ukazovateľa všetky znaky až za najbližšiu značku <Eoln> (na konci súboru nerobí nič)
  • POZOR, pomocou Read(Subor, Znak) je možné čítať aj značku <Eoln>, lenže táto sa potom chápe ako 2 znaky #13 a #10 a nie ako nejaký jeden špeciálny znak
  • príkaz ReadLn(Subor, Znak); je skrátený tvar pre dvojicu príkazov Read(Subor, Znak); ReadLn(Subor);
  • príkaz WriteLn(Subor); sa dá zapísať aj ako Write(Subor, #13#10);

V nasledujúcom príklade zistíme počet medzier v textovom súbore "text.txt":

var
  Subor: TextFile;
  Znak: Char;
  Pocet: Integer;
begin
  AssignFile(Subor, 'medzery.txt');
  Reset(Subor);
  Pocet := 0;
  while not Eof(Subor) do
  begin
    Read(Subor, Znak);
    if Znak = ' ' then
      Inc(Pocet);
  end;
  CloseFile(Subor);
 
  WriteLn('Pocet medzier v subore = ', Pocet);
  ReadLn;
end.

Poznámka

  • všimnite si, že sme si tu vôbec nevšímali konce riadkov, t.j. značky <Eoln> - tie sme čítali ako dva obyčajné znaky

Zistíme, počet riadkov textového súboru "text.txt":

var
  Subor: TextFile;
  Pocet: Integer;
begin
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  Pocet := 0;
  while not Eof(Subor) do
  begin
    ReadLn(Subor);
    Inc(Pocet);
  end;
  CloseFile(Subor);
 
  WriteLn('Pocet riadkov v subore = ', Pocet);
  ReadLn;
end.

Poznámka

  • tu sme si vôbec nevšímali obsah riadkov - príkazom ReadLn sme preskočili kompletný obsah riadka až za najbližšiu značku <Eoln> - zaujímali nás tu len počty riadkov

Zistíme, dĺžku najdlhšieho riadka súboru "text.txt":

var
  Subor: TextFile;
  Znak: Char;
  Max, Dlzka: Integer;
begin
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  Max := 0;
  while not Eof(Subor) do
  begin
    Dlzka := 0;
    while not Eoln(Subor) do    // zistí dĺžku riadka
    begin
      Read(Subor, Znak);
      Inc(Dlzka);
    end;
    ReadLn(Subor);             // tu nesmieme zabudnúť na ReadLn
    if Dlzka > Max then
      Max := Dlzka;
  end;
  CloseFile(Subor);
 
  WriteLn('Dlzka najdlhsieho riadka = ', Max);
  ReadLn;
end.

Poznámka:

  • tieto tri rôzne programy ilustrujú tri najčastejšie schémy spracovania textového súboru:
    • čítanie po znakoch - ignorujeme značky <Eoln>
    • čítanie celých riadkov
    • čítanie po riadkoch - v každom riadku čítame po znakoch až do konca riadka

Veľmi často sa spracovanie textového súboru zapisuje aj do takejto schémy:

var
  Subor: TextFile;
  Znak: Char;
begin
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  while not Eof(Subor) do
    if Eoln(Subor) then     // ak je koniec riadka
    begin
      ReadLn(Subor);
      // spracuj koniec riadka
    end
    else             // ak nie je koniec riadka, spracujem jeden znak
    begin
      Read(Subor, Znak);
      // spracuj Znak
    end;
  end;
  CloseFile(Subor);
end;



Zápis do súboru


Do súboru zapisujeme pomocou príkazov Write a WriteLn. Pred ich prvým použitím musí byť súbor otvorený na čítanie pomocou

Rewrite(Subor);

Príkaz Rewrite má ale tieto dôsledky:

  • ak súbor už existoval, tak hneď po Rewrite sa jeho obsah zruší
  • ak súbor ešte neexistoval, vytvorí sa s prázdnym obsahom
  • ukazovateľ v súbore je vždy nastavený na jeho koniec

Príkazy Write a WriteLn majú oproti konzolovej aplikácii tieto ďalšie vlastnosti:

  • príkaz WriteLn(Subor) zapíše do súboru značku <Eoln>, t.j. robí to isté ako Write(Subor, #13#10)
  • príkaz WriteLn(Subor, hodnota) je skrátený tvar pre Write(Subor, hodnota); WriteLn(Subor); alebo aj Write(Subor, hodnota, #13#10);

Vytvoríme súbor "text.txt" z písmen 'a' až 'z' a potom jeho obsah vypíšeme:

var
  Subor: TextFile;
  Znak, Znak1: Char;
begin
  AssignFile(Subor, 'text.txt');
  Rewrite(Subor);
  for Znak := 'a' to 'z' do
  begin
    for Znak1 := Znak to 'z' do
      Write(Subor, Znak1);
    WriteLn(Subor);
  end;
  CloseFile(Subor);
 
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  while not Eof(Subor) do
  begin
    Read(Subor, Znak1);
    Write(Znak1);
  end;
  CloseFile(Subor);
  ReadLn;
end.

Poznámka

  • po vytvorení súboru sme ho zatvorili (CloseFile) a potom opäť priradili (AssignFile) a otvorili (Reset); keby sme súbor nezatvárali, mohli sme ho priamo otvoriť pomocou Reset.

Vytvoríme kópiu súboru unit1.pas do súboru text.txt

var
  Subor1, Subor2: TextFile;
  Znak: Char;
begin
  AssignFile(Subor1, 'unit1.pas');
  Reset(Subor1);
  AssignFile(Subor2, 'text.txt');
  Rewrite(Subor2);
  while not Eof(Subor1) do
    if Eoln(Subor1) then
    begin
      ReadLn(Subor1);
      WriteLn(Subor2);
    end
    else
    begin
      Read(Subor1, Znak);
      // spracuj prečítaný znak
      Write(Subor2, Znak);
    end;
  CloseFile(Subor1);
  CloseFile(Subor2);
 
  WriteLn('vytvoreny subor text.txt');
  ReadLn;
end.

iný variant kopírovania súboru - nevšímame si konce riadkov - pritom prerábame malé písmená na veľké:

var
  Subor1, Subor2: TextFile;
  Znak: Char;
begin
  AssignFile(Subor1, 'unit1.pas');
  Reset(Subor1);
  AssignFile(Subor2, 'text.txt');
  Rewrite(Subor2);
  while not Eof(Subor1) do
  begin
    Read(Subor1, Znak);
    if (Znak >= 'a') and (Znak <= 'z') then
      Znak := Char(Ord(Znak) - Ord('a') + Ord('A'));          // alebo Dec(Znak, 32);
    Write(Subor2, Znak);
  end;
  CloseFile(Subor1);
  CloseFile(Subor2);
 
  WriteLn('vytvoreny subor text.txt');
  ReadLn;
end.

Poznámka:

  • na prerábanie malých písmen na veľké sme mohli použiť aj štandardnú znakovú funkciu UpCase(Znak), t.j. namiesto príkazu if sme mohli zmeniť priamo príkaz Write:
Write(Subor2, UpCase(Znak));

Ďalšie námety:

  • postupnosť za sebou idúcich medzier nahraď jednou medzerou
  • vyhoď riadky, ktoré sú prázdne alebo obsahujú len medzery
  • vyhoď medzery na konci (na začiatkoch) riadkov
  • postupnosť znakov 'end' nahraď '***'



Reset a Rewrite na ten istý súbor na disku


var
  Subor1, Subor2: TextFile;
begin
  AssignFile(Subor1, 'a.txt');
  Reset(Subor1);
  AssignFile(Subor2, 'a.txt');
  Rewrite(Subor2);
  ...

je to nepredvídateľné - rôzne verzie pascalu reagujú rôzne, napr. Lazarus hlási I/O chybu


Čítanie a zápis čísel

Čítanie čísel


Už vieme, že pomocou Read môžeme čítať aj čísla (celé aj reálne), ale si treba zapamätať, že v súbore musia byť tieto čísla ukončené medzerou, koncom riadka alebo tabulátorom (znak s kódom #9). Ak Cislo je číselná premenná (Integer alebo Real), potom

Read(Subor, Cislo);
  • najprv preskočí všetky medzerové znaky (medzera, <Eoln> alebo #9)
  • potom ďalšie znaky zo vstupu prekonvertuje na číslo
  • ak je toto číslo ukončené nemedzerovým znakom (iným ako medzera, <Eoln> alebo #9, napr. ',' alebo ';'), tak vyhlási chybu Invalid numeric format
  • POZOR! čítanie čísla na konci súboru (t.j. platí Eof, ale aj ak sú tam len medzery) vráti hodnotu 0 - treba sa tohoto vyvarovať!

Profesionálny softvér tento štandardný Read na čítanie čísel nepoužíva, lebo chyba v súbore spôsobí chybovú správu (výpočet je ďalej nekorektný). Vy v 1. ročníku môžete používať takéto čítanie, len ak je v zadaní výslovne povedané, že je súbor korektný a číslo je ukončené medzerovým znakom. Neskôr uvidíme aj iný (bezpečný) spôsob čítania čísel.

Nasledovný program nájde maximálne číslo v súbore celých čísel:

var
  Subor: TextFile;
  Cislo, Max: Integer;
begin
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  Max := -MaxInt;          // lepšie by bolo Low(Integer)
  while not Eof(Subor) do
  begin
    Read(Subor, Cislo);
    if Cislo > Max then
      Max := Cislo;
  end;
  CloseFile(Subor);
  if Max = -MaxInt then
    WriteLn('subor je prazdny')
  else
    WriteLn('maximalne cislo v subore je ', Max);
  ReadLn;
end.

Poznámky

  • ak súbor obsahoval napr. len prázdny riadok (alebo len medzery), tak program vypíše, že maximum bolo 0
  • toto isté sa stane, ak súbor obsahuje len záporné čísla a za posledným číslom sú ešte nejaké medzerové znaky - funkcia Eof(Subor) za posledným číslom vráti False - ešte nie je koniec súboru, ale už tam nie je žiadne číslo, teda prečíta sa hodnota 0



Koniec riadka a súboru s filtrovaním medzier


Niekedy sa stáva situácia (napr. pri čítaní čísel), že štandardné funkcie Eof a Eoln oznámia, že ešte nie je koniec súboru, resp. riadka, pritom tam ostali len medzerové znaky, ktoré ale už nepotrebujeme interpretovať ako vstup. Tu výhodne využijeme upravené funkcie SeekEof a SeekEoln, ktoré pracujú rovnako ako ich pôvodné verzie, ale každá najprv odfiltruje medzerové znaky a až potom vráti informáciu o konci súboru, resp. riadka.

  • štandardná funkcia SeekEof(Subor) najprv odfiltruje medzerové znaky (medzera, <Eoln> alebo #9) a až potom otestuje koniec súboru,
  • štandardná funkcia SeekEoln(Subor)najprv odfiltruje medzerové znaky (medzera a #9) a až potom otestuje koniec riadka,

Predchádzajúci program by už teraz nemal mať predtým uvedené problémy:

var
  Subor: TextFile;
  Cislo, Max: Integer;
begin
  AssignFile(Subor, 'text.txt');
  Reset(Subor);
  Max := Low(Integer);
  while not SeekEof(Subor) do
  begin
    Read(Subor, Cislo);
    if Cislo > Max then
      Max := Cislo;
  end;
  CloseFile(Subor);
  if Max = Low(Integer) then
    WriteLn('subor je prazdny')
  else
    WriteLn('maximalne císlo v subore je ', Max);
  ReadLn;
end.



Zápis čísel


Už vieme, že pomocou Write(Subor, ...) môžeme do textového súboru zapisovať aj čísla, t.j. hodnoty číselných výrazov (Integer alebo Real):

  • celé čísla sa zapíšu bez medzery pred číslom aj za číslom, t.j. Write(Subor, I, I+1) pre i=17 zapíše 1718
  • reálne čísla sa do súboru zapisujú v semilogaritmickom tvare s medzerou pred číslom

Príklad: Všetky čísla zo súboru "text1.txt" budeme kopírovať do súboru "text2.txt" pričom ich budeme zaraďovať po troch do riadka:

var
  Subor1, Subor2: TextFile;
  Cislo, Pocet: Integer;
begin
  AssignFile(Subor1, 'text1.txt');
  Reset(Subor1);
  AssignFile(Subor2, 'text2.txt');
  Rewrite(Subor2);
  Pocet := 0;
  while not SeekEof(Subor1) do
  begin
    Read(Subor1, Cislo);
    if Pocet = 3 then
    begin
      WriteLn(Subor2);
      Pocet := 1;
    end
    else
    begin
      if Pocet > 0 then
        Write(Subor2, ' ');
      Inc(Pocet);
    end;
    Write(Subor2, Cislo);
  end;
  CloseFile(Subor1);
  CloseFile(Subor2);
 
  WriteLn('hotovo');
  ReadLn;
end.

V súbore sú reálne čísla, máme zistiť počet čísel, ktoré majú hodnotu menšiu ako je priemer všetkých čísel v súbore - tieto "podpriemerné" čísla zapíšeme do druhého súboru.

var
  Subor, Vystup: TextFile;
  Cislo, Suma, Priemer: Real;
  Pocet: Integer;
begin
  AssignFile(Subor, 'text1.txt');
  Reset(Subor);                       // priradenie vstupného súboru a jeho otvorenie na čítanie
  Suma := 0;
  Pocet := 0;   // v premennej Pocet zistíme počet všetkých čísel
  while not SeekEof(Subor) do
  begin
    Read(Subor, Cislo);
    Suma := Suma + Cislo;        // Inc(Suma, Cislo);
    Inc(Pocet);
  end;
  Reset(Subor);                   // !!! nastavenie pozície na začiatok súboru !!!
  AssignFile(Vystup, 'text2.txt');
  Rewrite(Vystup);
  Priemer := Suma / Pocet;
  Pocet := 0;
  // premennú Pocet využijeme na zistenie počtu podpriemerných čísel
  while not SeekEof(Subor) do
  begin
    Read(Subor, Cislo);
    if Cislo < Priemer then
    begin
      Inc(Pocet);
      WriteLn(Vystup, Cislo:0:2);
    end;
  end;
  CloseFile(Subor);
  CloseFile(Vystup);
 
  WriteLn('pocet podpriemernych = ', Pocet);
  ReadLn;
end.

Poznámka

  • všimnite si, že program spadne na delení nulou, ak súbor neobsahoval ani jedno číslo


Textová plocha

V ďalšej časti prednášky sa vrátime k pôvodnému tvaru Lazarus-projektu ako windows aplikácie.

Textová plocha je veľmi podobná grafickej ploche: z palety komponentov do formulára vložíme "súčiastku" TMemo - vytvorí sa komponent s menom Memo1 (v prvom riadku sa objaví text Memo1). Podobne ako pri grafickej ploche (písali sme Image1.Canvas. a potom samotný príkaz), aj tu budeme najprv písať Memo1.Lines. a za to príslušný príkaz:

  • Memo1.Lines.Clear; - vyčistí všetky riadky
  • Memo1.Lines.Append('nejaký text'); - pridá ďalší riadok s daným textom na koniec
  • Memo1.Lines.LoadFromFile(súbor); - do textovej plochy zapíše obsah súboru z disku
  • Memo1.Lines.SaveToFile(súbor); - obsah textovej plochy zapíše do súboru

Nasledujúca ukážka vypisuje do textovej plochy kódy znakov písmen:

procedure TForm1.FormCreate(Sender: TObject);
var
  Znak: Char;
begin
  Memo1.Lines.Clear;
  for Znak := 'a' to 'z' do       // k písmenu prilepí medzeru a číselný kód znaku
    Memo1.Lines.Append(Znak + ' ' + IntToStr(Ord(Znak)));
end;

Komponent textovú plochu môžete využívať aj na pomocné výpisy, napr. počas kreslenia do grafickej plochy, pričom kompletný výpis môžete veľmi jednoducho zapísať do nejakého súboru, napr.

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.SaveToFile('zaznam.txt');
end;


späť | ďalej