14. Unity, dynamické polia


Posledná zmena: 14.11.2002

Banner Text 7.11.2002

    čo sme sa doteraz naučili

    • keď definujeme nejaký formulár, Delphi na to pripraví súbor Unit1.pas, v ktorom je samotný program (v súbore Unit1.dfm je informácia o komponentoch vo formulári)
    • keď potrebujeme pracovať s korytnačkami, musíme za implementation napísať uses KorUnit, aby Delphi vedelo, kde sú korytnačky definované

    čo sa budeme dnes učiť

    • vytvárať vlastné programové jednotky, štruktúru a vlastnosti unitov
    • čo sú to dynamické polia a ako sa s nimi pracuje
    • čo sú to parametre typu otvorené pole a ako ich môžeme používať

Programové jednotky - unity

  • balík "preddefinovaných" podprogramov, premenných, typov a konštánt
  • používajú sa
    • ako knižnice podprogramov, ktoré sa dajú pripojiť k rôznym programom (bez sprístupnenia zdrojového kódu) - štandardná jednotka System obsahuje štandardné pascalovské procedúry a funkcie, napr. AssignFile, Reset, write, copy, insert, cos, ord, odd, pi, ... ale aj 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 Delphi pri vytváraní nového formulára (popis komponentov vo formulári je v súbore s príponou .DFM)
    • často ho môžeme použiť na rozčlenenie rozsiahleho programu do logicky súvisiacich menších modulov, resp. na popis nejakej časti hierarchie objektov
    • sú v súboroch s príponou .PAS => po prekompilovaní je ich kód v súbore s príponou .DCU (aby sme mohli nejaký unit použiť v našom programe nepotrebujeme jeho zdrojový tvar .PAS, ale stačí nám súbor .DCU)

Definícia unitu:

unit menoUnitu;     // meno unitu sa musí zhodovať s menom súboru

interface

{ 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 }

uses ...

const ...

type ...

var ...

procedure ...

function ...

implementation

{ 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, aj bez formálnych
  parametrov, 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í }

uses ...

const ...

type ...

var ...

procedure ...

function ...

initialization    // tieto časti môžu chýbať

// inicializácia unitu – spustí sa tesne pred spustením
// hlavného programu

finalization

// finalizácia unitu – 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 meno jednotky - ak sa jednotka nenachádza v tom istom adresári 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 ich inicializácií
  • príklad demonštruje jednoduchú programovu jednotku s inicializáciou a finalizáciou

ukážkový unit:

unit MojUnit;

interface

procedure zapis(s:string);

implementation

var
  t:TextFile;

procedure zapis(s:string);
begin
  writeln(t,s);
end;

initialization

  AssignFile(t,'d:\test.txt'); Rewrite(t);

finalization

  CloseFile(t);

end.

Programová jednotka StackUnit - zásobník

  • údajová štruktúra zásobník - stack je pri programovaní veľmi často používaná a pritom je dosť jednoduchá na to, aby sme na nej vysvetlili použitie programových jednotiek
  • je to údajová štruktúra, ktorá sa podobá postupnosti prvkov, len spôsob pridávania a uberania prvkov je špecifický:
    • 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
  • zásobníku niekedy hovoríme aj LIFO (last in - first out) alebo aj nespravodlivý rad

zásobník zrealizujeme ako triedu:

unit StackUnit;

interface

const
  maxStack = 100;

type
  TPrvok = string;
  TStack = class
    st: array[1..maxStack] of TPrvok;
    vrch: 0..maxstack;
    constructor Create;
    procedure push(p:TPrvok);
    procedure pop(var p:TPrvok);
    function top:TPrvok;
    function full:boolean;
    function empty:boolean;
  end;

implementation

uses
  Dialogs;    // obsahuje ShowMessage

procedure chyba(s:string);
begin
  ShowMessage(s); halt;       // okno so správou
end;

constructor TStack.Create;
begin
  vrch:=0;
end;

procedure TStack.push(p:TPrvok);
begin
  if full then chyba('Plný zásobník');
  inc(vrch); st[vrch]:=p;
end;

procedure TStack.pop(var p:TPrvok);
begin
  if empty then chyba('Prázdny zásobník');
  p:=st[vrch]; dec(vrch);
end;

function TStack.top:TPrvok;
begin
  if empty then chyba('Prázdny zásobník');
  Result:=st[vrch];
end;

function TStack.full:boolean;
begin
  Result:=vrch=maxStack;
end;

function TStack.empty:boolean;
begin
  Result:=vrch=0;
end;

end.
  • najlepšie je vytvárať nové unity pomocou New -> Unit v menu File - vytvorí sa skoro prázdny unit, napr.

nový unit:

unit Unit2;

interface

implementation

end.
  • tento pomocou Save As... v menu File môžeme premenovať a tým sa zmení aj hlavička unitu, napr. na StackUnit
  • takto definovaný unit (teda triedu zásobník), môžeme použiť vo svojom programe

použitie zásobníka:

uses StackUnit;
...
procedure TForm1.Button1Click(...);
var
  t1,t2:TextFile;
  z:TStack;
  s:string;
begin
  AssignFile(t1,'unit1.pas'); Reset(t1);
  AssignFile(t2,'x.txt'); Rewrite(t2);
  z:=TStack.Create;
  while not eof(t1) do begin
    readln(t1,s); z.push(s);
  end;
  while not z.empty do begin
    z.pop(s); writeln(t2,s);
  end;
  z.Free;
  CloseFile(t1); CloseFile(t2);
end;
  • ešte si všimnite, že na záver programu sme použili z.Free - podobne ako pri bitmapách je dobre na záver nezabudnúť objekt zrušiť t.j. uvolniť z pamäti počítača
  • pozrime sa, ako vyzerá "hlavný program", t.j. Delphi projekt aplikácie Project1.dpr:

hlavný program:

program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  StackUnit in 'StackUnit.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
  • tento súbor je tiež pascalovský zdrojový kód, ale na rozdiel od programovej jednotky obsahuje len deklarácie a "hlavný program", t.j. to, čo sa spustí po štarte programu (po inicializácii unitov) -- zrejme Application je nejaká špeciálna inštancia (zrejme je deklarovaná v unite Forms), o ktorú sa stará Delphi a tento hlavný program len zavolá nejaké 3 metódy tejto inštancie.

Dynamické polia

  • deklarujeme ich
       var
         a:array of
    typ_prvku;
  • znamená to, že premenná a tohoto typu zatiaľ nemá určenú veľkosť, t.j. počet prvkov
  • veľkosť poľa musíme zadefinovať pomocou
    • procedúry SetLength(a,počet_prvkov); vyhradí pamäť
    • a:=nil; znamená, že pole je prázdne => nulový počet prvkov (niečo podobné ako pri objektoch) - toto je to isté ako SetLength(a,0);
    • a:=copy(iné_pole,od_prvku,počet); rovnako ako pri znakových reťazcoch
  • štandardná funkcia Length vracia momentálnu veľkosť poľa (pre nil je to 0)
  • takto definované pole má indexy vždy od 0 po Length(pole)-1, t.j. Low(a) je 0 a High(a) je Length(a)-1
  • ak už pole obsahuje nejaké prvky a meníme jeho veľkosť pomocou SetLength, buď nejaké prvky stratíme, alebo pri zväčšovaní dĺžky pribudnú na koniec nové prvky - zatiaľ ešte s nedefinovanou hodnotou

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

unit StackUnit;

interface

type
  TPrvok = string;
  TStack = class
    st:array of TPrvok;
    constructor Create;
    procedure push(const p:TPrvok);
    procedure pop(var p:TPrvok);
    function top:TPrvok;
    function empty:boolean;
  end;

implementation

uses
  Dialogs;

procedure chyba(const s:string);
begin
  ShowMessage(s); halt;
end;

constructor TStack.Create;
begin
  st:=nil;
end;

procedure TStack.push(const p:TPrvok);
begin
  SetLength(st,Length(st)+1); st[High(st)]:=p;
end;

procedure TStack.pop(var p:TPrvok);
begin
  if empty then chyba('Prázdny zásobník');
  p:=st[High(st)]; SetLength(st,Length(st)-1);
end;

function TStack.top:TPrvok;
begin
  if empty then chyba('Prázdny zásobník');
  Result:=st[High(st)];
end;

function TStack.empty:boolean;
begin
  Result:=Length(st)=0;   // alebo Result:=st=nil;
end;

end.
  • Nakoľko môže byť operácia "nafukovania" dynamického poľa pomerne "drahá", t.j. časovo náročná, môžeme pole zväčšovať po väčších úsekoch, napr. 10:

dynamické pole sa "nafukuje" po väčších krokoch:

unit StackUnit;

interface

type
  TPrvok = string;
  TStack = class
    st:array of TPrvok;
    vrch:integer;
    constructor Create;
    procedure push(const p:TPrvok);
    procedure pop(var p:TPrvok);
    function top:TPrvok;
    function empty:boolean;
  end;

implementation

uses
  Dialogs;

procedure chyba(const s:string);
begin
  ShowMessage(s); halt;
end;

constructor TStack.Create;
begin
  st:=nil; vrch:=-1;
end;

procedure TStack.push(const p:TPrvok);
begin
  inc(vrch);
  if vrch>High(st) then SetLength(st,Length(st)+10);
  st[vrch]:=p;
end;

procedure TStack.pop(var p:TPrvok);
begin
  if empty then chyba('Prázdny zásobník');
  p:=st[vrch]; dec(vrch);
  if vrch<High(st)-20 then SetLength(st,Length(st)-10);
end;

function TStack.top:TPrvok;
begin
  if empty then chyba('Prázdny zásobník');
  Result:=st[vrch];
end;

function TStack.empty:boolean;
begin
  Result:=vrch<0;
end;

end.
  • príklad, v ktorom použijeme zásobník spolu s korytnačkou (predpokladáme, že prvky zásobníka sú čísla, napr.
      type TPrvok=real;):

:

procedure TForm1.Button1Click(Sender: TObject);
var
  z:TStack;
  k:TKor;
  i:integer;
  r:real;    // TPrvok
begin
  k:=TKor.Create;
  z:=TStack.Create;
  for i:=1 to 20 do begin
    k.Dopredu(i*10); k.Vpravo(90); z.push(i*10);
  end;
  cakaj(1000);
  k.fp:=clRed;
  while not z.empty do begin
    z.pop(r); k.vlavo(90); k.dopredu(-r);
  end;
  z.Free;
end;

Parameter otvorené pole

  • Potrebujeme procedúru, ktorá dostane "postupnosť" čísel a na jej základe nakreslí krivku tak, že postupne z nej bude brať dĺžky pre príkaz dopredu a zakaždým sa otočí o 90 stupňov vpravo. Nakoľko nepoznáme dĺžku vstupnej postupnosti (t.j. poľa) použijeme tzv. parameter otvorené pole, t.j. skutočným parametrom môže byť ľubovoľné jednorozmerné pole, ktoré sa zhoduje s typom prvkov. Zapisujeme, napr.
       procedure kresli(const d:array of real);
  • takýto formálny parameter nie je dynamické pole!!!
  • Vo vnútri procedúry pristupujeme k prvkom poľa s indexmi od Low(d) až po High(d), pričom Low(d) je vždy 0 a High(d) je vždy Length(d)-1. Nesmieme použiť SetLength ani copy.

príklad:

var
  k:TKor;

procedure kresli(d:array of real);
var
  i:integer;
begin
  for i:=low(d) to high(d) do begin     // low(d) je vždy 0
    k.Dopredu(d[i]);
    k.Vpravo(90);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  dd:array[5..10] of real = (50,50,100,100,50,50);
begin
  k:=TKor.Create;
  kresli(dd);
end;
  • Delphi umožňujú poslať ako skutočný parameter typu otvorené pole aj konštantu otvorené pole, t.j. "postupnosť" hodnôt rovnakého typu ako prvky otvoreného poľa uzavretú v hranatých zátvorkách, napr.

použitie konštanty otvorené pole:

procedure TForm1.Button1Click(Sender: TObject);
begin
  k:=TKor.Create;
  kresli([50,50,100,100,50,50]);
end;
  • už poznáme procedúru Polygon, ktorá funguje v Canvase grafickej plochy, má parameter otvorené pole typu TPoint a štandardná funkcia Point vyrába záznam typu TPoint, t.j. niečo ako

štandardná procedúra Polygon:

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;
  • môžeme jednoducho volať takúto procedúru konštantou otvoreného poľa, napr.

volanie procedúry Polygon:

  g.Polygon([Point(100,100),Point(200,150),Point(50,200)]);


© 2002 AB, KVI
blaho@fmph.uniba.sk