15.Prednaska

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

úlohy | cvičenie


Programové jednotky - unity

Programy v Pascale sa väčšinou rozdeľujú do viacerých súborov. Každý z týchto súborov potom môže obsahovať nejaké podprogramy, definície typov a konštánt a aj deklarácie premenných. Takémuto balíku podprogramov, premenných, typov a konštánt hovoríme programová jednotka (unit). Ak sa to dobre navrhne, tak rôzne programové jednotky môžu programovať nezávislí programátori na rôznych miestach a v rôznom čase.

Každá programová jednotka zrejme vie riešiť nejaké úlohy (podprogramy) a pritom využíva deklarácie typov, konštánt a premenných buď zo svojej jednotky, alebo z nejakých iných (možno cudzích) jednotiek. A naopak, iné jednotky (možno úplne cudzie) môžu využívať niektoré deklarácie alebo podprogramy z našich jednotiek. Hovoríme, že každá jednotka môže využívať niektoré časti iných jednotiek (import) a tiež môže niektoré svoje časti poskytnúť pre iné jednotky (označí ich na export). Keď je nejaká jednotka naprogramovaná, môže sa skompilovať a ďalším programátorom poskytnúť už len v takomto skompilovanom stave - ďalší programátori takúto jednotku môžu používať, ale nevidia jej zdrojový tvar a nemôžu ju ani študovať ani modifikovať.

Najčastejšie použitie programovej jednotky je

  • ako knižnice podprogramov, ktoré sa dajú pripojiť k rôznym programom (často bez sprístupnenia zdrojového kódu) - napr. štandardná jednotka s menom System obsahuje štandardné pascalovské procedúry a funkcie, napr. AssignFile, Reset, Write, Copy, Insert, Cos, Ord, Odd, Pi, ... ale aj definície niektorých typov, napr. triedu TObject,
  • ako programový kód, ktorý popisuje správanie formulára (najčastejšie má každý formulár v aplikácii svoju jednotku - napr. 'Unit1.pas') - vytvorený je automaticky prostredím kompilátora pri vytváraní nového formulára (popis komponentov vo formulári je v súbore s príponou .LFM - môžete si ho pozrieť, je to textový súbor),
  • na rozčlenenie rozsiahleho programu do logicky súvisiacich menších modulov, resp. na popis nejakej časti hierarchie objektov.

Programové jednotky sú v súboroch s príponou .PAS, po prekompilovaní je ich preložený kód v súbore s príponou .PPU.

Programová jednotka má svoju presnú štruktúru. Skladá sa z niekoľkých častí, ktoré sú označené pascalovskými rezervovanými slovami:

  • unit - označenie mena jednotky,
  • interface - začiatok popisu tých identifikátorov, ktoré plánujeme poskytnúť ďalším jednotkám - tzv. export,
  • implemetation - začiatok časti, v ktorej definujeme podprogramy (aj metódy) z časti interface a tiež popisujeme ďalšie súkromné identifikátory (premenné, typy, procedúry) - tieto už iné jednotky nevidia,
  • initialization - nepovinná časť - tu sa môžu nachádzať akcie, ktoré by sa mali vykonať počas inicializácie aplikácie,
  • finalization - tiež nepovinná časť - tu sa môžu nachádzať akcie, ktoré by sa mali vykonať počas ukončovania práce celej aplikácie,
  • end. - ukončenie programovej jednotky.

V zdrojovom tvare programová jednotka vyzerá takto:

unit menoUnitu;     // meno jednotky sa musí zhodovať s menom súboru
 
{$mode objfpc}{$H+}
 
interface
 
uses ...
 
{ verejné deklarácie a definície konštánt, typov (aj tried), premenných, procedúr a funkcií (len ich hlavičky),
  každý, kto jednotku používa, k nim môže pristupovať ako k svojim vlastným }
 
const ...
 
type ...
 
procedure ...
 
function ...
 
var ...
 
implementation
 
uses ...
 
{ definície verejných procedúr a funkcií (ich definície je možné písať bez ohľadu na poradie a vzájomné odkazy,
  t.j. napísaním len názvu podprogramu), súkromné deklarácie a definície konštánt, typov, premenných, procedúr
  a funkcií }
 
const ...
 
type ...
 
var ...
 
procedure ...
 
function ...
 
 
initialization    // tieto ďalšie časti môžu chýbať
 
// inicializácia jednotky – spustí sa tesne pred spustením
// hlavného programu
 
finalization
 
// finalizácia jednotky – spustí sa po skončení hlavného programu
 
end.

Jednotky môžeme použiť v iných programoch, resp. jednotkách, tak, že do časti deklarácií uses napíšeme mená týchto jednotiek - ak sa jednotka nenachádza v tom istom priečinku disku ako samotný projekt, musíme zapísať meno súboru aj s cestou (meno súboru sa aj tak musí zhodovať s názvom jednotky), napr.

uses
  Messages, Vektory in '..\vektory.pas', PomocnyUnit;

Poradie mien jednotiek v popise uses určuje poradie spúšťania ich inicializácií.



Príklad - zápis do súboru



Nasledujúci príklad demonštruje jednoduchú programovú jednotku s inicializáciou aj finalizáciou. Novú programovú jednotku najlepšie vytvárame takto:

  • klikneme na prvé tlačidlo Nová jednotka alebo v menu Súbor zvolíme položku Nová jednotka
  • v editovacom okne sa vytvorí schéma pre novú programovú jednotku:
unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils;
 
implementation
 
end.

Nie je vhodné premenovávať túto jednotku zmenou identifikátora za slovom unit, ale najlepšie bude tento súbor uložiť na disk a pritom mu zmeniť meno: klikneme na tlačidlo Uložiť alebo stlačíme klávesy Ctrl-S alebo v menu Súbor zvolíme položku Uložiť a teraz určíme meno, napr. MojUnit. Automaticky sa toto meno zapíše aj v hlavičke súboru za slovo unit.

Teraz môžeme zapísať samotný obsah programovej jednotky:

unit MojUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils;
 
procedure Zapis(Retazec: string);
 
implementation
 
var
  Subor: TextFile;
 
procedure Zapis(Retazec: string);
begin
  WriteLn(Subor, Retazec);
end;
 
initialization
 
  WriteLn('spustam jednotku MojUnit');
  AssignFile(Subor, 'test.txt');
  Rewrite(Subor);
 
finalization
 
  WriteLn('koncim jednotku MojUnit');
  CloseFile(Subor);
 
end.

Všimnite si

  • v časti interface definuje jediný podprogram: procedúru Zapis,
  • v časti implementation okrem realizácie procedúry Zápis vidíme deklaráciu súkromnej premennej Subor - typu textový súbor, túto premennú nebude vidieť žiadna iná jednotka,
  • časť initialization sa automaticky spustí pri štarte programu: otvorí sa textový súbor na zápis,
  • časť finalization sa vykoná pri ukončovaní programu: otvorený súbor sa zatvorí.


Typ zásobník

Zásobník je údajová štruktúra, ktorá sa podobá postupnosti prvkov. S touto štruktúrou pracujeme pomocou dvoch operácií: pridaj prvok a vyber prvok. Narozdiel od klasického radu, napr. pred obchodom, tejto štruktúre hovoríme nespravodlivý rad: noví zákazníci sa zaraďujú na koniec radu a obslúžený bude ten, ktorý je v rade na konci - prišiel ako posledný. Tomuto v informatike hovoríme aj LIFO: last in - first out. Takže

  • prvky do štruktúry môžeme pridávať len na koniec - operáciou Push,
  • prvky zo štruktúry môžeme odoberať len z konca - operácia Pop,
  • pomocná operácia Empty slúži na otestovanie, či je štruktúra prázdna,
  • pomocná funkcia Full zistí, či už nie je dátová štruktúra plná a teda sa do nej už žiadne nové prvky nezmestia.

Túto údajovú štruktúru teraz naprogramujeme ako triedu s metódami Push, Pop a Empty. Zásobník sa po anglicky povie stack a preto triedu nazveme TStack a celú ju zadefinujeme v samostatnej programovej jednotke StackUnit.



Programová jednotka StackUnit



Novú programovú jednotku vytvoríme opäť tak, že v menu File zvolíme položku New, v nej vyberieme Unit, potom takýto zatiaľ prázdny unit uložíme (Save As...) s menom StackUnit. Toto meno sa objaví v hlavičke súboru za slovom unit. Kompletná jednotka StackUnit môže vyzerať takto:

unit StackUnit;
 
{$mode objfpc}{$H+}
 
interface
 
type
  TPrvok = Integer;
 
  TStack = class
    Pole: array [0..99] of TPrvok;
    Vrch: Integer;
    constructor Create;
    procedure Push(Prvok: TPrvok);
    procedure Pop(var Prvok: TPrvok);
    function Pop: TPrvok;
    function Top: TPrvok;
    function Full: Boolean;
    function Empty: Boolean;
  end;
 
implementation
 
procedure Chyba(Retazec: string);
begin
  WriteLn(Retazec);
  ReadLn;
  Halt;
end;
 
constructor TStack.Create;
begin
  Vrch := -1;
end;
 
procedure TStack.Push(Prvok: TPrvok);
begin
  if Full then
    Chyba('Plny zasobnik pri prikaze Push');
  Inc(Vrch);
  Pole[Vrch] := Prvok;
end;
 
procedure TStack.Pop(var Prvok: TPrvok);
begin
  if Empty then
    Chyba('Prazdny zasobnik pri prikaze Pop');
  Prvok := Pole[Vrch];
  Dec(Vrch);
end;
 
function TStack.Pop: TPrvok;
begin
  Pop(Result);
end;
 
function TStack.Top: TPrvok;
begin
  if Empty then
    Chyba('Prazdny zasobnik pri prikaze Top');
  Result := Pole[Vrch];
end;
 
function TStack.Full: Boolean;
begin
  Result := Vrch = High(Pole);
end;
 
function TStack.Empty: Boolean;
begin
  Result := Vrch < 0;
end;
 
end.

V tomto programe si všimnite:

  • zásobník realizujeme ako 100-prvkové pole celých čísel,
  • prvky tejto štruktúry sú definované typom TPrvok - ak zmeníme deklaráciu tohto typu, zmení sa aj štruktúra prvkov zásobníka,
  • typ TPrvok je tiež deklarovaný v časti interface a preto ho budú môcť používať aj tie časti programu, ktoré zapíšu uses StackUnit,
  • stavová premenná Vrch slúži na zapamätanie pozície posledného prvku v zásobníku (posledne pridávaného) - ďalší pridávaný prvok sa bude dávať za tento vrchný, resp. odoberať zo zásobníka sa bude práve tento vrchný,
  • okrem Push, Pop a Empty sme dodefinovali aj tieto ďalšie metódy:
    • Full - logická funkcia vráti True, ak je zásobník už plný a teda ďalšie Push by spôsobilo spadnutie programu,
    • funkcia Pop - funkcia typu TPrvok vráti prvok z vrchu zásobníka, pričom, rovnako ako Pop, tento prvok zo zásobníka odstráni,
    • Top - funkcia typu TPrvok vráti prvok z vrchu zásobníka, pričom, na rozdiel od Pop, tento prvok na zásobníku ponechá - toto môže byť v niektorých aplikáciách dosť užitočná metóda.
  • zadefinovali sme pomocnú procedúru Chyba, ktorá má za úlohu oznámiť správu o chybe a násilne ukončiť celú aplikáciu
    • použili sme pascalovský príkaz Halt (štandardnú procedúru), ktorá okamžite ukončí bežiaci program - tento príkaz používajte veľmi rozvážne, lebo pri komplexnejších projektoch môžete pri takomto prerušení programu prísť o dôležité ukončovacie akcie rozbehnutých procesov - neskôr sa naučíme pracovať s výnimkami,
    • ak by sme definovali tento unit pre grafickú aplikáciu, museli by sme výpis nahradiť volaním napr. ako ShowMessage z knižničnej programovej jednotky Dialogs - na začiatok implementačnej časti pridáme uses Dialogs

Ďalším programom otestujeme fungovanie zásobníka. Najprv do neho postupne vložíme (príkaz Push) čísla od 1 do 10 a potom ich vyberieme (príkazom Pop) a vypíšeme:

uses
  StackUnit;
 
var
  Zasobnik: TStack;
  I: Integer;
begin
  Zasobnik := TStack.Create;
  for I := 1 to 10 do
    Zasobnik.Push(I);
  while not Zasobnik.Empty do
  begin
    Zasobnik.Pop(I);
    Write(I, ' ');
  end;
  WriteLn;
  Zasobnik.Free;
 
  ReadLn;
end.

Všimnite si, že na záver programu sme použili Zasobnik.Free - podobne ako pri bitmapách je dobre na záver nezabudnúť každý nami vytvorený objekt aj zrušiť, t.j. uvoľniť ho z pamäti počítača.

Teraz zmeňme celočíselný zásobník na zásobník znakových reťazcov: v unite StackUnit zapíšeme

type
  TPrvok = string;

A program demonštruje jednoduché použitie zásobníka: najprv do prvkov zásobníka postupne naukladáme riadky textového súboru a potom tieto riadky vyberáme a vypisujeme - zrejme v opačnom poradí:

uses
  StackUnit;     // zásobník znakových reťazcov
 
var
  Subor: TextFile;
  Zasobnik: TStack;
  Retazec: string;
begin
  AssignFile(Subor, 'Project1.lpr');
  Reset(Subor);
  Zasobnik := TStack.Create;
  while not Eof(Subor) do
  begin
    ReadLn(Subor, Retazec);
    Zasobnik.Push(Retazec);
  end;
  while not Zasobnik.Empty do
  begin
    Zasobnik.Pop(Retazec);
    WriteLn(Retazec);
  end;
  Zasobnik.Free;
  CloseFile(Subor);
 
  ReadLn;
end.


Dynamické polia

Doteraz sme sa stretli iba so statickými poľami: poľu sme v deklaráciách určili hranice jeho indexu (jeho veľkosť) a počas behu programu sme už nemali možnosť túto veľkosť zmeniť. Príklad zásobníka nám ukázal, že z daného poľa sme potrebovali vždy len nejaký počet prvkov a počas behu sa tento počet prvkov rôzne menil - použili sme na to premennú Vrch, v ktorej sme si pamätali pozíciu posledne pridávaného prvku, a teda veľkosť potrebnej časti poľa. Je jasné, že tak, ako sme to naprogramovali, tento zásobník zvládne maximálne 100 pridávaných prvkov (operácia Push) a potom program zahlási chybu. Môžeme zadeklarovať (a teda vyhradiť) hoci aj milión-prvkové pole, ale toto nie je pekné riešenie. Preto sa naučíme nový spôsob definovania štruktúry pole, pri ktorom nebudeme pri deklarácii definovať hranice indexu, ale pole najprv vytvoríme úplne prázdne (0-prvkové) a až počas behu ho budeme pomocou špeciálnych príkazov zväčšovať alebo zmenšovať.

Takéto polia nazývame dynamické (dynamicky menia svoju veľkosť) a deklarujeme ich takto:

var
  Pole: array of typ_prvku;

Premenná Pole je teraz dynamické pole, ktorá po zadeklarovaní zatiaľ nemá určenú veľkosť. Preto zatiaľ nemôžeme pracovať ani s prvkami tohto poľa. Najprv musíme nastaviť počet prvkov poľa pomocou príkazu (štandardnej procedúry)

  SetLength(Pole, počet_prvkov);

Táto procedúra vyhradí pamäť pre požadovaný počet prvkov poľa. Štandardná funkcia Length(Pole) vráti tento nastavený počet prvkov. Prvky sú vždy indexované od 0 do počet-1. Funkcia Low(Pole) má teda vždy hodnotu 0 a High(Pole) sa vždy rovná Length(Pole)-1. Niekedy sa môžete stretnúť aj so zápisom

  Pole := nil;

ktorý označuje priradenie nulového počtu prvkov, čo je to isté ako SetLength(Pole, 0). Uvedomte si, že keď dynamickému poľu zmenšíme počet jeho prvkov, tak sa hodnoty prvkov, o ktoré sa pole skrátilo, strácajú - pole je skracované od posledných prvkov. Keď dynamickému poľu pridáme nové prvky, tak tieto majú na začiatku nedefinovanú hodnotu. Často sa do poľa pridáva (na koniec) jeden prvok a hneď sa do neho priradí nejaká hodnota, napr. takto

  SetLength(Pole, Length(Pole) + 1);
  Pole[High(Pole)] := 123;

alebo

  SetLength(Pole, Length(Pole) + 5);
  for I := High(Pole) - 4 to High(Pole)
    Pole[I] := -1;

Skrátenie poľa o posledný prvok (zrejme pole musí byť neprázdne) urobíme napr. takto

  SetLength(Pole, Length(Pole) - 1);

Pre prácu s dynamickými poľami môžeme ešte použiť rovnakú štandardnú funkciu Copy ako pre prácu so znakovými reťazcami. Z ľubovoľného poľa môžeme vybrať súvislý úsek a priradiť ho do iného (ale aj toho istého) dynamického poľa:

Copy(dynamické_pole, od_prvku, počet);

Pracuje rovnako ako pri znakových reťazcoch. Len nezabudnite, že index prvku, od ktorého vyberáme časť poľa, je teraz už od 0. Vysledok funkcie Copy ale nesmie byť prázdne pole, preto treba niekedy pred jeho volaním otestovať parametre.

Napr.

var
  PoleA, PoleB: array of Integer;
 ...
  PoleB := Copy(PoleA, 2, 3);         // PoleB[0]:=PoleA[2]; PoleB[1]:=PoleA[3]; PoleB[2]:=PoleA[4];
  PoleA := Copy(PoleA, 1, MaxInt);    // vyhodí prvý prvok poľa

Niekedy môžeme aj skracovanie poľa o niekoľko prvkov urobiť pomocou Copy namiesto SetLength:

  Pole := Copy(Pole, 0, Length(Pole) - 5);

namiesto

  SetLength(Pole, Length(Pole) - 5);

Samozrejme, že v oboch prípadoch treba testovať počet prvkov poľa Pole, aby program nespadol na chybe.

Pozrite si nasledovný kód, v ktorom použijeme dynamické pole:

var
  Pole: array of Integer;
  I: Integer;
begin
  SetLength(Pole, Random(11) + 10);
  for I := 0 to High(Pole) do
    Pole[I] := Random(30);
 
  for I := 0 to High(Pole) do
    Write(Pole[I], ' ');
  WriteLn;
  ReadLn;
end.
  • najprv sa vytvorí dynamické pole náhodnej dĺžky od 10 do 20,
  • potom sa všetkým prvkom priradia nejaké náhodné hodnoty od 0 do 29,
  • obsah poľa sa vypíše.



StackUnit s dynamickým poľom



Triedu zásobník zrealizujeme pomocou dynamického poľa:

unit StackUnit;
 
{$mode objfpc}{$H+}
 
interface
 
type
  TPrvok = string;
 
  TStack = class
    Pole: array of TPrvok;
    constructor Create;
    procedure Push(const Prvok: TPrvok);
    procedure Pop(var Prvok: TPrvok);
    function Pop: TPrvok;
    function Top: TPrvok;
    function Empty: Boolean;
  end;
 
implementation
 
procedure Chyba(const Retazec: string);
begin
  WriteLn(Retazec);
  ReadLn;
  Halt;
end;
 
constructor TStack.Create;
begin
  Pole := nil;
end;
 
procedure TStack.Push(const Prvok: TPrvok);
begin
  SetLength(Pole, Length(Pole) + 1);
  Pole[High(Pole)] := Prvok;
end;
 
procedure TStack.Pop(var Prvok: TPrvok);
begin
  if Empty then
    Chyba('Prazdny zasobnik pri prikaze Pop');
  Prvok := Pole[High(Pole)];
  SetLength(Pole, Length(Pole) - 1);
end;
 
function TStack.Pop: TPrvok;
begin
  Pop(Result);
end;
 
function TStack.Top: TPrvok;
begin
  if Empty then
    Chyba('Prazdny zasobnik pri prikaze Top');
  Result := Pole[High(Pole)];
end;
 
function TStack.Empty: Boolean;
begin
  Result := Length(Pole) = 0;   // alebo Result := Pole = nil;
end;
 
end.

Všimnite si, že už nepotrebujeme metódu Full, keďže teraz už nemáme horný index poľa.

Ukážeme ešte jednu verziu triedy zásobník pomocou dynamického poľa. Nakoľko operácia "nafukovania" dynamického poľa je pomerne "drahá", t.j. časovo náročná, môžeme pole zväčšovať po väčších úsekoch ako 1, napr. po 100. Ukážeme verziu unitu pre grafickú aplikáciu:

unit StackUnit;
 
{$mode objfpc}{$H+}
 
interface
 
type
  TPrvok = string;
 
  TStack = class
    Pole: array of TPrvok;
    Vrch: Integer;
    constructor Create;
    procedure Push(const Prvok: TPrvok);
    procedure Pop(var Prvok: TPrvok);
    function Pop: TPrvok;
    function Top: TPrvok;
    function Empty: Boolean;
  end;
 
implementation
 
uses
  Dialogs;
 
procedure Chyba(const Retazec: string);
begin
  ShowMessage(Retazec);
  Halt;
end;
 
constructor TStack.Create;
begin
  Pole := nil;
  Vrch := -1;
end;
 
procedure TStack.Push(const Prvok: TPrvok);
begin
  Inc(Vrch);
  if Vrch > High(Pole) then
    SetLength(Pole, Length(Pole) + 100);
  Pole[Vrch] := Prvok;
end;
 
procedure TStack.Pop(var Prvok: TPrvok);
begin
  if Empty then
    Chyba('Prázdny zásobník pri príkaze Pop');
  Prvok := Pole[Vrch];
  Dec(Vrch);
  if Vrch < High(Pole)-150 then
    SetLength(Pole, Length(Pole)-100);
end;
 
function TStack.Pop: Prvok;
begin
  Pop(Result);
end;
 
function TStack.Top: Prvok;
begin
  if Empty then
    Chyba('Prázdny zásobník pri príkaze Top');
  Result := Pole[Vrch];
end;
 
function TStack.Empty: Boolean;
begin
  Result := Vrch < 0;
end;
 
end.

V nasledujúcom príklade použijeme zásobník spolu s robotom. Budeme predpokladať, že prvky zásobníka nie sú reťazce ale reálne čísla, t.j. platí

type
  TPrvok = Real;

Asi v tomto príklade rozpoznáte nakreslenie špirály, pričom sa zapamätávajú dĺžky kreslených čiar. Potom sa kreslí táto špirála v opačnom poradí čiar.

uses
  StackUnit, RobotUnit;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Zasobnik: TStack;
  Robot: TRobot;
  I: Integer;
  Cislo: Real;          // TPrvok
begin
  Robot := TRobot.Create;
  Zasobnik := TStack.Create;
  for I := 1 to 20 do
  begin
    Robot.Fd(I * 10);
    Robot.Rt(90);
    Zasobnik.Push(I * 10);
    Wait(50);
  end;
  Wait(1000);
  Robot.PC := clRed;
  Robot.Rt(1);
  while not Zasobnik.Empty do
  begin
    Zasobnik.Pop(Cislo);
    Robot.Lt(90);
    Robot.Fd(-Cislo);
    Wait(50);
  end;
  Zasobnik.Free;
end;


Viacrozmerné dynamické polia

Podobne ako statické polia, aj dynamické môžu byť viac rozmerné. Pozrime najprv rôzne dvojrozmerné polia, pri ktorých si najlepšie uvedomíme rozdiely:

var
  Pole1: array [0..9] of array [0..9] of Integer;
  Pole2: array of array [0..9] of Integer;
  Pole3: array [0..9] of array of Integer;
  Pole4: array of array of Integer;
  • Pole1 je statické dvojrozmerné, t.j. má 10 riadkov - každý po 10 čísel
  • Pole2 môže mať ľubovoľný počet riadkov - ale každý po 10 čísel
  • t.j. je to dynamické pole statických polí
  • Pole3 má 10 riadkov - ale každý môže mať ľubovoľný počet čísel (v každom riadku hoci aj iny počet prvkov)
  • t.j. je to statické pole dynamických polí
  • Pole4 môže mať ľubovoľný počet riadkov - kde každý môže obsahovať ľubovoľný počet čísel (každý riadok môže mať inú dĺžku)
  • t.j. je to dynamické pole dynamických polí - dvojrozmerné dynamické pole

Všimnite si nasledujúci kód, v ktorom sa všetkým 4 verziám deklarovaných dvojrozmerných polí priraďujú nejaké hodnoty:

var
  Pole1: array [0..9] of array [0..9] of Integer;
  Pole2: array of array [0..9] of Integer;
  Pole3: array [0..9] of array of Integer;
  Pole4: array of array of Integer;
  I, J: Integer;
begin
  for I := 0 to High(Pole1) do
    for J := 0 to High(Pole1[I]) do
      Pole1[I, J] := (I + 1) * (J + 1);
 
  SetLength(Pole2, 10);
  for I := 0 to High(Pole2) do
    for J := 0 to High(Pole2[I]) do
      Pole2[I, J] := (I + 1) * (J + 1);
 
  for I := 0 to High(Pole3) do
  begin
    SetLength(Pole3[I], 10);
    for J := 0 to High(Pole3[I]) do
      Pole3[I, J] := (I + 1) * (J + 1);
  end;
 
  SetLength(Pole4, 10);
  for I := 0 to High(Pole4) do
  begin
    SetLength(Pole4[I], 10);
    for J := 0 to High(Pole4[I]) do
      Pole4[I, J] := (I + 1) * (J + 1);
  end;
end;

Všetky tieto 4 polia majú teraz rovnaké rozmery 10x10 a obsahujú "malú násobilku".

Všimnite si prípravu posledného dvojrozmerného poľa Pole4: najprv sme mu vytvorili 10 riadkov (zatiaľ sú to riadky s nedefinovaným obsahom, t.j. s nedefinovaným počtom prvkov v jednotlivých riadkoch). Potom sme postupne každému riadku nastavili 10 prvkov (zatiaľ s nedefinovanou hodnotou) a na záver sme do každého prvku priradili nejakú hodnotu. Príkaz SetLength umožňuje automatické vytvorenie naraz viacerých rozmerov - ale samozrejme, že len v prípade, ak dĺžky všetkých riadkov v dvojrozmernom dynamickom poli sú rovnaké. Premennú Pole4 by sme mohli pripraviť aj takto:

  SetLength(Pole4, 10, 10);
  for I := 0 to High(Pole4) do
    for J := 0 to High(Pole4[I]) do
      Pole4[I, J] := (I + 1) * (J + 1);

Na podobnom princípe fungujú aj viacrozmerné dynamické polia, napr.

var
  Pole: array of array of array of Integer;
  I, J, K: Integer;
begin
  SetLength(Pole, 4, 5, 6);
  for I := 0 to High(Pole) do
    for J := 0 to High(Pole[I]) do
      for K := 0 to High(Pole[I, J]) do
        Pole[I, J, K] := (I + 1) * (J + 1) * (K + 1);


Parameter otvorené pole

Už viackrát sme programovali procedúru, v ktorej sme z poľa čísel vytvorili znakový reťazec. Tentoraz je tu verzia pre dynamické pole:

type
  TPole = array of Integer;
 
function Vypis(Pole: TPole): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Pole) to High(Pole) do
    Result := Result + IntToStr(Pole[I]) + ' ';
end;

Ak by sme potrebovali rovnakú funkciu napr. aj pre tieto tri typy:

type
  TPole1 = array [1..20] of Integer;
  TPole2 = array [-10..10] of Integer;
  TPole3 = array [Char] of Integer;

pre každý z nich by sme museli napísať vlastnú verziu tejto funkcie. Všetky tieto funkcie by boli rovnaké a líšili by sa len typom formálneho parametra. Napr.

function Vypis(Pole: TPole1): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Pole) to High(Pole) do
    Result := Result + IntToStr(Pole[I]) + ' ';
end;
 
function Vypis(Pole: TPole2): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Pole) to High(Pole) do
    Result := Result + IntToStr(Pole[I]) + ' ';
end;
 
function Vypis(Pole: TPole3): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Pole) to High(Pole) do
    Result := Result + IntToStr(Pole[I]) + ' ';
end;

Tento problém pomáha vyriešiť špeciálny typ formálneho parametra: parameter otvorené pole, vďaka ktorému stačí napísať jedinú funkciu, ktorá pokryje úplne všetky typy jednorozmerných polí celých čísel:

function Vypis(Pole: array of Integer): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Pole) to High(Pole) do
    Result := Result + IntToStr(Pole[I]) + ' ';
end;

Hoci sa tento zápis podobá na dynamické pole, v žiadnom prípade to neznamená, že formálny parameter Pole je dynamické pole. Naopak s touto premennou vnútri vo funkcii ani nesmieme pracovať ako s dynamickým poľom: napr. s ním nefunguje ani SetLength ani Copy. Skutočným parametrom do tejto procedúry môže byť ľubovoľné jednorozmerné pole celých čísel (aj dynamické). Vo vnútri procedúry pristupujeme k prvkom poľa s indexmi od Low(Pole) až po High(Pole), pričom Low(Pole) je vždy 0 a High(D) je vždy Length(Pole)-1 (bez ohľadu na to, aké pole sa poslalo ako skutočný parameter).

Nasledujúci príklad ukazuje, ako môžeme prekonvertovať ľubovoľné celočíselné pole na dynamické pole:

type
  TPole = array of Integer;
 
function Urob(Pole: array of Integer): TPole;
var
  I: Integer;
begin
  SetLength(Result, Length(Pole));
  for I := 0 to High(Pole) do        // Low(Pole) je vždy 0
    Result[I] := Pole[I];
end;

Do tejto funkcie môžeme poslať ako skutočný parameter ľubovoľné celočíslené pole (aj dynamické, hoci to veľký význam nemá) - na type indexov totiž pri otvorenom poli nezáleží.

Existuje ale jedna špeciálna možnosť, vďaka ktorej môžeme poslať ako skutočný parameter otvorené pole aj konštantu otvorené pole. To znamená, že ak chceme ako parameter poslať len zopár hodnôt, nemusíme na to definovať nejakú premennú typu pole, do nej priradiť nejaké hodnoty a potom to posielať ako skutočný parameter. Konštanta otvorené pole je postupnosť hodnôt (hodnoty môžu byť aj výrazy, ale musia byť typu prvkov otvoreného poľa), ktorá je uzavretá v hranatých zátvorkách. Nasledujúci príklad ilustruje rôzne použitie otvoreného poľa pre funkciu Vypis:

const
  Pole1: array [-2..2] of Integer = (3, 5, 7, 11, 13);
var
  Pole2: array [1..3] of Integer;
begin
  Pole2[1] := 100;
  Pole2[2] := 144;
  Pole2[3] := 133;
  WriteLn(Vypis(Pole1));
  WriteLn(Vypis(Pole2));
  WriteLn(Vypis([1, 3, 3 * 3, 7 * 3, 13 * 3]));
  ReadLn;
end.

Ďalšia funkcia vypočíta priemer z prvkov celočíselného poľa:

function Priemer(Cisla: array of Integer): Real;
var
  I: Integer;
begin
  Result := 0;
  if Length(Cisla) = 0 then
    Exit;
  for I := 0 to High(Cisla) do
    Result := Result + Cisla[I];
  Result := Result / Length(Cisla);
end;

Otestovať ju môžeme napr. takto:

var
  Pole: array of Integer;
  Pole2: array [1..3, 1..3] of Integer;
  A: array [1..100] of Integer;
  I, J: Integer;
begin
  for I := 1 to 3 do
    for J := 1 to 3 do
      Pole2[I, J] := I * 3 + J;
  for I := 1 to 3 do
    WriteLn('Priemer pre ', I, '. riadok pola: ', Priemer(Pole2[I]):0:2);
  //Pole := nil;
  WriteLn('Priemer pre prazdne pole: ', Priemer(Pole):0:2);
  for I := 1 to 100 do
    A[I] := I;
  WriteLn('Priemer 100 prvkov: ', Priemer(A):0:2);
  WriteLn('Priemer prvych 50 prvkov: ', Priemer(A[1..50]):0:2);
  WriteLn('Priemer poslednych 50 prvkov: ', Priemer(A[51..100]):0:2);
  ReadLn;
end.

Všimnite si, že ako parameter môžeme poslať nielen statické alebo dynamické pole, ale aj nejakú časť poľa, napr. pomocou Priemer(A[1..50]) posielame do funkcie len prvých 50 prvkov 100-prvkového poľa.

Tento program vypíše:

Priemer pre 1. riadok pola: 5.00
Priemer pre 2. riadok pola: 8.00
Priemer pre 3. riadok pola: 11.00
Priemer pre prazdne pole: 0.00
Priemer 100 prvkov: 50.50
Priemer prvych 50 prvkov: 25.50
Priemer poslednych 50 prvkov: 75.50



Opäť grafická procedúra Polygon



Už sme sa stretli s grafickou procedúrou Polygon, ktorá funguje v plátne (Canvas) grafickej plochy (v 9. prednáške). Má jeden parameter otvorené pole typu TPoint. Okrem toho máme k dispozícii aj štandardnú funkciu Point, ktorá vracia záznam typu TPoint, t.j. v štandardných pascalovských jednotkách by sme mohli nájsť zadeklarované približne toto:

type
  TPoint = record X, Y: Integer; end;
 
procedure TCanvas.Polygon(const Points: array of TPoint);
...
 
function Point(AX, AY: Integer): TPoint;
begin
  Result.X := AX;
  Result.Y := AY;
end;

Do tejto procedúry môžeme ako paramater poslať ľubovoľné pole bodov (typu TPoint), napr. trojprvkové pole vytvorí trojuholník:

var
  Pole: array [1..3] of TPoint;
begin
  Pole[1] := Point(200, 50);
  Pole[2] := Point(100, 150);
  Pole[3] := Point(250, 200);
  Image1.Canvas.Brush.Color := clBlue;
  Image1.Canvas.Polygon(Pole);
end;

To isté môžeme vytvoriť pomocou konštanty otvorené pole, t.j. nepoužije pole, ale Polygon zavoláme so špeciálnym parametrom, napr. takto

var
  A, B, C: TPoint;
begin
  A := Point(200, 50);
  B := Point(100, 150);
  C := Point(250, 200);
  Image1.Canvas.Brush.Color := clBlue;
  Image1.Canvas.Polygon([A, B, C]);
end;

Alebo to isté aj bez pomocných premenných:

begin
  Image1.Canvas.Brush.Color := clBlue;
  Image1.Canvas.Polygon([Point(200, 50), Point(100, 150), Point(250, 200)]);
end;

Postupnosť bodov môžeme vytvárať v dynamickom poli, napr. takto

var
  Pole: array of TPoint;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Image1.Canvas.Brush.Color := clWhite;
  Image1.Canvas.FillRect(Image1.ClientRect);
  Image1.Canvas.Brush.Color := Random($1000000);
  SetLength(Pole, Length(Pole) + 1);
  Pole[High(Pole)] := Point(X, Y);
  Image1.Canvas.Polygon(Pole);
end;


späť | ďalej