16.Prednaska

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

úlohy | cvičenie


Programová jednotka RobotUnit

Podrobne sa teraz pozrieme na programovú jednotku RobotUnit. Najprv, ako vyzerá samotná deklarácia triedy TRobot:

type
  TRobot = class
  private
    FX, FY, FH: Real;
    FDown: Boolean;
    FPC: TColor;
    FPW: Integer;
  public
    Value: Integer;
    constructor Create;
    constructor Create(NewX, NewY: Real; Angle: Real = 0);
 
    procedure Fd(D: Real); virtual;
    procedure Rt(Angle: Real); virtual;
    procedure Lt(Angle: Real); virtual;
    procedure SetH(Angle: Real); virtual;
    procedure SetXY(NewX, NewY: Real); virtual;
    procedure SetX(NewX: Real); virtual;
    procedure SetY(NewY: Real); virtual;
    procedure MoveXY(NewX, NewY: Real); virtual;
    procedure PU; virtual;
    procedure PD; virtual;
    procedure SetPen(Down: Boolean); virtual;
    procedure SetPC(NewColor: TColor); virtual;
    procedure SetPW(NewWidth: Integer); virtual;
 
    procedure Point(NewWidth: Real = 0); virtual;
    procedure Text(NewText: string); virtual;
    procedure Towards(AnyX, AnyY: Real); virtual;
    procedure Fill(NewColor: TColor); virtual;
    function  Dist(AnyX, AnyY: Real): Real; virtual;
    function  IsNear(AnyX, AnyY: Real): Boolean; virtual;
    procedure Draw; virtual;
 
    property  X: Real read FX write SetX;
    property  Y: Real read FY write SetY;
    property  H: Real read FH write SetH;
    property  IsPD: Boolean read FDown write SetPen;
    property  PC: TColor read FPC write SetPC;
    property  PW: Integer read FPW write SetPW;
  end;

Väčšinu vecí buď už poznáme, alebo sme s nimi už pracovali predtým. Ale môžeme tu vidieť aj niekoľko noviniek:

  • všetky stavové premenné a metódy sú rozdelené do dvoch veľkých skupín: private a public
  • skoro za všetkými definíciami metód je kľúčové slovo virtual, ktorého význam sa budeme učiť neskôr
  • konštruktor Create a aj procedúra Point má predvolený parameter (default hodnota)
  • niektoré stavové premenné (napr. X, Y, H a pod.) sú uvedené kľúčovým slovom property - tzv. vlastnosť

V tejto prednáške sa postupne zoznámime s týmito a aj ďalšími novinkami.


Privátne definície


Už vieme, že v programovej jednotke môžeme deklarácie uviesť nielen v interface časti, ale aj za implementation. Vďaka tomu môžeme vytvoriť buď verejné definície alebo súkromné (len pre potreby unitu). Podobne je to pri definovaní triedy: definície stavových premenných a metód môžeme rozdeliť do dvoch skupín (neskôr uvidíme aj ďalšie) private a public:

  • na súkromné, ktoré sú určené len pre potreby samotného objektu a zvonku unitu sú neprístupné,
  • na verejné, ktoré zverejňujeme pre používateľov (programátorov) tejto triedy.

To, že privátne stavové premenné a metódy sú zvonku neprístupné znamená, že ak nejakú triedu zadeklarujeme v inteface časti nejakého unitu, tak ostatné unity, ktoré túto deklaráciu vidia, môžu pracovať len s public deklaráciami. V implementation časti samotného unitu sú oba typy deklarácií rovnocenné. Pri práci s formulárom (napr. v triede TForm1) môžeme vidieť, že niektoré prvky (stavové premenné a metódy) nie sú v ani jednej z týchto skupín - okrem týchto dvoch skupín je aj ďalšia: published, ktorá je skoro rovnaká ako public, ale zatiaľ pre jednoduchosť predpokladajme, že ju prekladač potrebuje pre komponenty a metódy, ktoré sú zviazané s nejakým formulárom.



Predvolené parametre a viac variantov procedúry



Pri volaní procedúry (aj metódy) môžem uviesť menší počet skutočných parametrov ako je zadeklarovaný počet formálnych parametrov. Ak kompilátor pozná predvolené (náhradné) hodnoty týchto parametrov, tak nehlási chybu, ale doplní ich týmito hodnotami za nás - predvolenými hodnotami môžu byť len konštanty alebo konštantné výrazy (niečo, čo vie kompilátor vypočítať už počas kompilácie). Predvolené (default) parametre môžu byť len hodnotové alebo konštantné parametre - toto neplatí pre var-parametre. Za predvoleným parametrom (v zozname formálnych parametrov) môžu nasledovať už len predvolené. Robota môžeme skonštruovať napr. takto:

Robot := TRobot.Create(0, 0, 0);
Robot := TRobot.Create(x, y);

Predvolený parameter je aj v definícii procedúry CS:

procedure CS(NewColor: TColor = clWhite);

t.j. keď je volaná bez parametrov, farbou pozadia bude biela, inak sa použije zadaný skutočný parameter.


Viac variantov jednej procedúry (overload) je spôsob, pomocou ktorého môžeme nazvať rôzne procedúry jedným menom, resp. keď chceme, aby mala jedna procedúra viac rôznych variantov. Aby pascal pri volaní takejto procedúry správne rozhodol, ktorý variant má použiť, musia sa tieto varianty líšiť buď počtom parametrom alebo typmi parametrov. Funguje to aj pre obyčajné podprogramy, ktoré nie sú metódami nejakej triedy. Môžeme vidieť dva varianty konštruktora Create - varianty sa líšia počtom parametrov.

Pozrime sa na vlastnú verziu štandardnej procedúry Inc, ktorá zvyšuje hodnotu celočíslenej premennej:

procedure Inc(var P: Integer; H: Integer = 1);
begin
  P := P + H;
end;
 
procedure Inc(var P: Real; H: Real = 1);
begin
  P := P + H;
end;

Zároveň s tým sme vytvorili aj verziu pre reálne čísla, ktorá pre štandardnú procedúru Inc nefunguje. Treba si ale teraz uvedomiť, že od teraz už nebude fungovať štandardná verzia Inc lebo je prekrytá touto našou - napr. pre znakový typ, vymenovaný typ, ale napr. aj pre Byte. Hoci aj tu existuje spôsob, ako napriek prekrytiu štandardnej procedúry našou definíciou, sa vieme dostať k originálu:

procedure Inc(var P: Char; H: Integer = 1);
begin
  System.Inc(P, H);
end;

Môžeme vidieť, že predponou "System." pred Inc dosiahneme štandardnú verziu namiesto našej prekrytej. Môžeme vytvoriť aj ďalšie verzie Inc, napr. pre znakový reťazec alebo otvorené pole:

procedure Inc(var P: string; H: Integer = 1);
var
  I: Integer;
begin
  for I := 1 to Length(P) do
    Inc(P[I], H);
end;
 
procedure Inc(var P: array of Integer; H: Integer = 1);
var
  I: Integer;
begin
  for I := 0 to High(P) do
    Inc(P[I], H);
end;

Všimnite si, že obe tieto procedúry Inc znovu volajú procedúry Inc - ale zakaždým je to iná verzia (prvá pre znakovú premennú, druhá pre celočíselnú).

Ďalší príklad ukazuje viac verzií jednej metódy v jednoduchej definícii triedy:

type
  TSubor = class
  private
    Subor: TextFile;
  public
    constructor Create(M: string);
    destructor Destroy; override;
    procedure Zapis(S: string);
    procedure Zapis(S: array of Integer);
  end;
 
constructor TSubor.Create(M: string);
begin
  AssignFile(Subor, M);
  Rewrite(Subor);
end;
 
destructor TSubor.Destroy;
begin
  CloseFile(Subor);
end;
 
procedure TSubor.Zapis(S: string);
begin
  WriteLn(Subor, S);
end;
 
procedure TSubor.Zapis(S: array of Integer);
var
  I: Integer;
begin
  for I := 0 to High(S) do
    Write(Subor, S[I], ' ');
  WriteLn(Subor);
end;

Všimnite si

  • trieda umožňuje pracovať s textovým súborom - obsahuje jednu privátnu stavovú premennú Subor (mimo metód triedy k nej nemáme prístup) a okrem konštruktora a deštruktora obsahuje jedinú metódu Zapis
  • táto metóda Zapis má dve verzie: jedna pre reťazec a druhá pre otvorené pole celých čísel
  • definovali sme aj deštruktor Destroy, ktorý musí byť označený slovom override - deštruktor je zavolaný vtedy, keď sa končí práca s objektom a inštanciu treba uvoľniť (volanie Free). Preto sme sem zaradili zatvorenie súboru. O deštruktoroch sa hovorí o kúsok neskôr.

Túto triedu môžeme použiť napr. takto:

var
  T: TSubor;
  I: Integer;
begin
  T := TSubor.Create('test.txt');
  T.Zapis('prvy riadok');
  T.Zapis('druhy riadok');
  T.Zapis([2, 3, 5, 7,11, 13, 17, 19]);
  for I := 1 to 5 do
    T.Zapis([I, I * I, I * I * I, I * I * I * I]);
  T.Free;
 
  ReadLn;
end.


Vlastnosti (property)

Stavové premenné s kľúčovým slovom property sú špeciálne atribúty triedy (nie sú to pamäťové položky triedy ako v zázname). "Skutočným" stavovým premenným je vyhradené nejaké pamäťové miesto (môžeme zistiť jej hodnotu, resp. ju meniť) - to je analógia položkám záznamov. "Virtuálna" stavová premenná, t.j. vlastnosť musí mať priradené nejaké akcie, ktoré umožnia čítanie, resp. modifikovanie takejto virtuálnej stavovej premennej. Pomocou tohto mechanizmu môžeme kontrolovať prístup ku "skutočným" stavovým premenným alebo môžeme pre takéto stavové premenné vypočítať ich hodnotu, až keď budú požadované (čítané). Syntax je

property meno: typ read atribút|metóda write atribút|metóda;

atribút|metóda je buď obyčajná stavová premenná objektu (hoci aj privátna) alebo nejaká (hoci aj privátna) metóda. Typ atribútu sa musí zhodovať s typom vlastnosti. Pre read - metóda musí byť bezparametrová funkcia (tzv. getter) rovnakého typu ako typ vlastnosti. Pre write - metóda musí byť jednoparametrová procedúra (tzv. setter) s hodnotovým alebo konštantným parametrom rovnakého typu ako typ vlastnosti. Jedna z častí read alebo write môže chýbať => potom tento prístup nie je povolený (napr. nedá sa meniť hodnota stavovej premennej, teda dá sa iba čítať). Vždy, keď sa v programe bude čítať, resp. meniť hodnota takejto "virtuálnej" stavovej premennej - vlastnosti, tak sa buď prečíta hodnota, resp. priradí do položky, alebo sa zavolá príslušná metóda. Napr. robot má tieto vlastnosti:

   property  X: Real read FX write SetX;
   property  Y: Real read FY write SetY;
   property  H: Real read FH write SetH;
   property  PC: TColor read FPC write SetPC;
   ...

Všetky tieto "virtuálne" stavové premenné môžeme čítať (zistiť ich hodnotu) lebo reprezentujú "skutočné" (hoci privátne) stavové premenné. Môžeme im dokonca meniť ich hodnoty, ale treba si uvedomiť, že vtedy sa automaticky zavolá príslušná metóda na zmenu hodnoty. Napr.

Robot.X := Robot.X + 5;

v skutočnosti znamená:

Robot.SetX(Robot.FX + 5);

keď sa pozriete do kódu metódy SetX, tak zistíte, že je to vlastne volanie:

Robot.SetXY(Robot.FX + 5, Robot.FY);

Na rovnakom princípe fungujú aj ostatné virtuálne stavové premenné, napr.

Robot.H := 2 * Robot.H;    // znamená Robot.SetH(2 * Robot.FH);
Robot.PC := clRed          // znamená Robot.SetPC(clRed);

Virtuálna stavová premenná (property) nemusí reprezentovať nejakú skutočnú stavovú premennú, ale jej hodnota môže byť vypočítaná na základe nejakej funkcie. Zapíšme takúto deklaráciu triedy:

type
  TUkazka = class
  private
    FR: Real;
    FPocet: Integer;
    FHeslo: string;
  public
    function TestHesla(Heslo2: string): Boolean;
    property X: Integer read GetX write SetX;
    property R: Real read GetR write FR;
    property Pocet: Integer read FPocet;
    property Heslo: string write FHeslo;
  end;

Všimnite si, pre nové vlastnosti sme zapísali aj nejaké metódy (setter a getter), pričom sme ich zatiaľ nikde nedeklarovali: GetX, SetX, GetR by mali byť asi najlepšie ako privátne metódy. Stlačme Shift-Ctrl-C a vytvorí sa nielen prázdna definícia metódy TestHesla, ale aj sa dopíšu deklarácie predpokladaných metód pre setter a getter:

type
  TUkazka = class
  private
    FR: Real;
    FPocet: Integer;
    FHeslo: string;
    function GetR: Real;
    function GetX: Integer;
    procedure SetX(AValue: Integer);
  public
    function TestHesla(Heslo2: string): Boolean;
    property X: Integer read GetX write SetX;
    property R: Real read GetR write FR;
    property Pocet: Integer read FPocet;
    property Heslo: string write FHeslo;
  end;

Ako definície všetkých metód môžeme zapísať napr.

function TUkazka.GetR: Real;
begin
  Result := FR;
  Inc(FPocet);
end;
 
function TUkazka.GetX: Integer;
begin
  Result := Random(100);
end;
 
procedure TUkazka.SetX(AValue: Integer);
begin
 
end;
 
function TUkazka.TestHesla(Heslo2: string): Boolean;
begin
  Result := FHeslo = Heslo2;
end;

V tejto ukážke môžeme vidieť niekoľko prístupov k takýmto virtuálnym premenným:

  • stavová premenná X v skutočnosti nereprezentuje žiadne pamäťové miesto, jej použitie v programe asi nemá veľký význam: do tejto premennej môžeme priraďovať ľubovoľnú hodnotu (tá sa aj tak zahodí), a keď budeme zisťovať hodnotu tejto premennej, vráti nám náhodné číslo
  • stavová premenná R slúži na sprístupnenie práce s privátnou premennou FR: do premennej môžeme priraďovať ľubovoľné desatinné číslo, môžeme aj zisťovať hodnotu tejto premennej, ale vtedy sa v privátnej premennej Pocet ukladá počet prístupov do FR
  • premenná Pocet sa dá len čítať, jej hodnotu meniť nemôžeme - obsahuje počet čítaní premennej R
  • premenná Heslo má prístup len na zápis, t.j. môžeme do nej len nejakú hodnotu zapísať, vtedy sa uloží do privátnej premennej FHeslo, metóda TestHesla umožňuje preveriť, či je zadaný reťazec zhodný s privátnou premennou FHeslo

Tieto vlastnosti môžeme preveriť napr. takýmto programom:

var
  Ukazka: TUkazka;
  I: Integer;
begin
  Ukazka := TUkazka.Create;
  with Ukazka do
  begin
    X := 10;
    WriteLn('X = ', X);
    R := 0;
    for I := 1 to 10 do
      R := R + 0.1;
    WriteLn('R = ', R:0:2);
    WriteLn('pocet citani premennej R = ', Pocet);
    Heslo := 'nbu1234';
    WriteLn('test hesla nbu2345 = ', TestHesla('nbu2345'));
    WriteLn('test hesla nbu1234 = ', TestHesla('nbu1234'));
    Free;
  end;
 
  ReadLn;
end.

Dostávame približne takýto výstup:

X = 54
R = 1.00
pocet citani premennej R = 11
test hesla nbu2345 = FALSE
test hesla nbu1234 = TRUE


Konštruktory a deštruktory

Konštruktor Create



Trieda TRobot má zadefinované dva rôzne konštruktory a oba majú rovnaké meno Create. Už vieme, že toto je možné vďaka tomu, že tieto podprogramy sa líšia počtom alebo typmi parametrov. Vďaka tomu kompilátor vždy vie jednoznačne rozhodnúť, ktorú verziu má použiť. Pozrite sa, ako konštruktor Create inicializuje stavové premenné:

constructor TRobot.Create(NewX, NewY, Angle: Real);
begin
  SetImage;
  FX := NewX;
  FY := NewY;
  FH := Angle;
  FPC := clBlack;
  FPW := 1;
  FDown := True;
end;

Procedúra SetImage slúži na vyhľadanie grafickej plochy, v ktorej bude pracovať robot. Táto grafická plocha sa uloží v globálnej premennej MyImage unitu RobotUnit. Všimnite si, ako druhý variant konštruktora bez parametrov volá svoj prvý variant s parametrami:

constructor TRobot.Create;
begin
  SetImage;
  Create(MyImage.Width / 2, MyImage.Height / 2);
end;



Deštruktor Destroy



Pri definovaní triedy môžeme zadefinovať aj vlastný deštruktor. Ten má opačnú úlohu ako konštruktor: automaticky sa zavolá pri metóde Free, t.j. keď objekt rušíme. Vtedy na záver môžeme vykonať ešte nejaké "upratovacie" akcie (napr. uvoľniť bitmapu, zmazať plochu, zatvoriť otvorený súbor a pod.). Volanie metódy Obj.Free nejakého objektu Obj si môžeme zjednodušene predstaviť ako

if Obj <> nil then
  Obj.Destroy;

Ak by sme triede TRobot definovali deštruktor, vyzeral by takto:

destructor TRobot.Destroy;
begin
  ...
  inherited;
end;

Pričom inherited na záver metódy je ukážkou toho, že netreba zabudnúť spustiť "upratovacie" akcie aj pre triedu predka - v našom prípade je to zbytočné, lebo predok triedy TRobot má prázdny deštruktor (trieda TRobot je potomkom triedy TObject).

Zapamätajte si, že deštruktor sa vždy musí volať Destroy a za jeho deklaráciu píšeme slovo override. Prečo to tak musí byť, budeme rozumieť, keď sa naučíme polymorfizmus. Zápis deštruktora pri deklarovaní triedy vyzerá takto:

type
  TTrieda = class
    ...
    destructor Destroy; override;
  end;



Ďalšie pomocné podprogramy



V jednotke RobotUnit sú okrem triedy TRobot v interface časti definované aj nejaké konštanty, premenné a procedúry a preto sú viditeľné aj pre programy, ktoré použijú uses RobotUnit:

  • CS - má jeden predvolený parameter - farbu pozadia grafickej plochy - bez parametra sa zmaže grafickú plochu na bielo,
  • Wait - pozdrží vykonávanie programu o zadaný počet milisekúnd - počas tohto čakania je celá aplikácia "zamrznutá" a nereaguje na udalosti zvonku (napr. klikanie do plochy alebo na tlačidlá),
  • konštanty Rad a Deg slúžia na prepočty radiánov a stupňov,
  • procedúra SetImage slúži na zadefinovanie grafickej plochy, v ktorej sa budú vytvárať nové roboty.

Pozrime si inicializáciu grafickej plochy, ktorá sa spustí pri prvom volaní TRobot.Create - jej najdôležitejšou úlohou je nájsť v našej aplikácii nejakú grafickú plochu (TImage) a zapamätať si ju. Nemá zmysel sa snažiť do detailov pochopiť, ako to naozaj funguje:

procedure SetImage(Image: TImage);
var
  I, N: Integer;
  Form: TForm;
begin
  if MyImage <> nil then
    Exit;
  if Image = nil then
  begin
    Form := Application.MainForm;
    if Form = nil then
    begin
      N := Application.ComponentCount;
      I := 0;
      while (I < N) and not (Application.Components[I] is TForm) do
        Inc(I);
      if I = N then
      begin
        ShowMessage('vadná aplikácia - Robot nenašiel formulár');
        Halt;
      end;
      Form := TForm(Application.Components[I]);
    end;
    N := Form.ControlCount;
    I := 0;
    while (I < N) and not (Form.Controls[I] is TImage) do
      Inc(I);
    if I >= N then
    begin
      ShowMessage('vadná aplikácia - Robot nenašiel grafickú plochu');
      Halt;
    end;
    Image := TImage(Form.Controls[I]);
  end;
  with Image, Canvas do
  begin
    Brush.Color := clWhite;
    Brush.Style := bsSolid;
    FillRect(ClientRect);
  end;
  if Image.Owner is TForm then
    TForm(Image.Owner).DoubleBuffered := True;
  MyImage := Image;
end;

Táto pomocná procedúra hľadá grafickú plochu v prvom formulári aplikácie. Ak ju nájde, zapamätá si ju v globálnej premennej MyImage, inak aplikácia skončí s chybovou správou, napr. "vadná aplikácia - Robot nenašiel formulár"


Odvodené triedy od TRobot

Zadefinujeme vlastnú triedu robota TMojRobot (odvodenú od TRobot), v ktorej dodefinujeme novú metódu Poly:

type
  TMojRobot = class(TRobot)
    procedure Poly(N: Integer; S, U: Real);
  end;
 
procedure TMojRobot.Poly(N: Integer; S, U: Real);
begin
  while N > 0 do
  begin
    Fd(S);
    Rt(U);
    Dec(N);
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2: TMojRobot;
begin
  Robot1 := TMojRobot.Create(50, 150);
  Robot2 := TMojRobot.Create(100, 100, 30);
  Robot1.Poly(3, 100, 120);
  Robot2.Poly(5, 100, 144);
  Robot1.Free;
  Robot2.Free;
end;

Každý robot teraz nakreslí iný mnohouholník. Teraz pridáme aj vlastný konštruktor a prenesieme celú túto deklaráciu triedy do samostatnej programovej jednotky 'MojRobotUnit.pas':

unit MojRobotUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, RobotUnit;
 
type
  TMojRobot = class(TRobot)
    constructor Create(NewX, NewY: Real; Angle: Real = 0);
    procedure Poly(N: Integer; S, U: Real);
  end;
 
implementation
 
{ TMojRobot }
 
constructor TMojRobot.Create(NewX, NewY, Angle: Real);
begin
  inherited;
  PW := 5;
  PC := clGreen;
end;
 
procedure TMojRobot.Poly(N: Integer; S, U: Real);
begin
  while N > 0 do
  begin
    Fd(S);
    Rt(U);
    Dec(N);
  end;
end;
 
end.

a v programovej jednotke s formulárom 'Unit1.pas' zostane iba:

...
 
implementation
 
uses
  MojRobotUnit;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2: TMojRobot;
begin
  Robot1 := TMojRobot.Create(50, 150);
  Robot2 := TMojRobot.Create(100, 100, 30);
  Robot1.Poly(3, 100, 120);
  Robot2.PC := clRed;
  Robot2.Poly(5, 100, 144);
  Robot1.Free;
  Robot2.Free;
end;

Všimnite si, že trojuholník prvého robota je zelený a hviezda druhého je červená. Oba útvary majú veľkosť strany 100.

Do našej novej triedy TMojRobot pridáme aj stavovú premennú Koef, ktorá bude označovať koeficient zmenšenia/zväčšenia pri kreslení pomocou Poly:

unit MojRobotUnit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, RobotUnit;
 
type
  TMojRobot = class(TRobot)
    Koef: Real;
    constructor Create(NewX, NewY: Real; Angle: Real = 0);
    procedure Poly(N: Integer; S, U: Real);
  end;
 
implementation
 
{ TMojRobot }
 
constructor TMojRobot.Create(NewX, NewY, Angle: Real);
begin
  inherited;
  PW := 5;
  PC := clGreen;
  Koef := 1;
end;
 
procedure TMojRobot.Poly(N: Integer; S, U: Real);
begin
  while N > 0 do
  begin
    Fd(Koef * S);
    Rt(U);
    Dec(N);
  end;
end;
 
end.

Ak nebudeme stavovú premennú meniť, budú sa kresliť rovnako veľké útvary ako doteraz. Ak, ale pre nejaký robot zmeníme túto hodnotu, bude kresliť rôzne veľké útvary:

uses
  MojRobotUnit;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2: TMojRobot;
  I: Integer;
begin
  Robot1 := TMojRobot.Create(50, 150);
  Robot2 := TMojRobot.Create(100, 100, 30);
  for I := 10 downto 1 do
  begin
    Robot1.Poly(3, 100, 120);
    Robot1.Rt(20);
    Robot1.Koef := I / 10;
  end;
  Robot2.PC := clRed;
  Robot2.Poly(5, 100, 144);
  Robot1.Free;
  Robot2.Free;
end;

Prvý robot nakreslí 10 zmenšujúcich sa trojuholníkov. Do triedy môžeme pridať aj rekurzívnu metódu na kreslenie binárneho stromu:

...
 
type
  TMojRobot = class(TRobot)
    Koef: Real;
    constructor Create(NewX, NewY: Real; Angle: Real = 0);
    procedure Poly(N: Integer; S, U: Real);
    procedure Strom(N: Integer; S: Real);
  end;
 
implementation
 
...
 
procedure TMojRobot.Strom(N: Integer; S: Real);
begin
  Fd(S);
  if N > 0 then
  begin
    Lt(45);
    Strom(N - 1, S * 0.6);     // rekurzia
    Rt(90);
    Strom(N - 1, S * 0.7);     // rekurzia
    Lt(45);
  end;
  Fd(-S);
end;
 
end.

a strom nakreslíme napr. takto:

procedure TForm1.Button2Click(Sender: TObject);
var
  Robot: TMojRobot;
begin
  Robot := TMojRobot.Create(130, 250, 0);
  with Robot do
  begin
    Strom(5, 80);
    Free;
  end;
end;

pričom to isté sa dá zapísať aj bez premennej Robot:

procedure TForm1.Button2Click(Sender: TObject);
begin
  with TMojRobot.Create(130, 250, 0) do
  begin
    Strom(5, 80);
    Free;
  end;
end;



Paramater otvorené pole



V tomto príklade vytvoríme metódu, ktorá dostane "postupnosť" čísel a na jej základe robot nakreslí krivku. Postupne z nej bude z vyberať čísla, ktoré sú dĺžkami pre príkaz fd a zakaždým sa otočí o 90 stupňov vpravo. Nakoľko nepoznáme dĺžku vstupnej postupnosti (t.j. poľa) opäť použijeme parameter otvorené pole, t.j. skutočným parametrom bude môcť byť ľubovoľné jednorozmerné pole reálnych čísel:

type
  TMojRobot = class(TRobot)
    procedure Kresli(Dlzky: array of Real);
  end;
 
procedure TMojRobot.Kresli(Dlzky: array of Real);
var
  I: Integer;
begin
  for I := 0 to High(Dlzky) do
  begin
    Fd(Dlzky[I]);
    Rt(90);
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
const
  Pole: array [5..10] of Real = (50, 50, 100, 100, 50, 50);
var
  Robot: TMojRobot;
begin
  Robot := TMojRobot.Create;
  Robot.Kresli(Pole);
  Robot.Free;
end;

Metódu Kresli môžeme zavolať aj s konštantou typu otvorené pole:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TMojRobot;
begin
  Robot := TMojRobot.Create;
  Robot.Kresli([50, 50, 100, 100, 50, 50]);
  Robot.Free;
end;



Zovšeobecnený príkaz Poly



Do poľa čísel môžeme uložiť aj zložitejšiu kresbu. Už viackrát sme programovali procedúru Poly, ktorá mala 3 parametre: počet opakovaní, dĺžku strany (pre Fd), uhol otočenia (pre Rt). Takéto kreslenie by sme mohli trochu zovšeobecniť: parametrom pre Poly bude pole čísel, v ktorých prvé označuje počet opakovaní postupnosti príkazov Fd a Rt. Parametre pre túto postupnosť sú čísla vo vstupnosm poli hneď za počtom opakovaní. Napr.

  • Poly([1, 30, 90, 30, -90]) - nakreslí jeden schod
  • Poly([10, 30, 90, 30, -90]) - nakeslí 10 schodov
  • Poly([5, 30, 144, 30, -72]) - nakreslí päťcípu hviezdu
  • Poly([N, S, U]) - nakreslí to isté ako pôvodná Poly(N, S, U)

Zadefinujeme novú triedu (pridali sme sem aj pôvodnú definíciu Poly):

type
  TPolyRobot = class(TRobot)
    procedure Poly(Pole: array of Real);
    procedure Poly(N: Integer; S, U: Real);
  end;
 
procedure TPolyRobot.Poly(Pole: array of Real);
var
  I, N: Integer;
begin
  if Length(Pole) < 2 then
    Exit;
  N := Trunc(Pole[0]);
  while N > 0 do
  begin
    I := 1;
    while I <= High(Pole) do
    begin
      Fd(Pole[I]);
      if I < High(Pole) then
        Rt(Pole[I + 1]);
      Inc(I, 2);
    end;
    Dec(N);
  end;
end;
 
procedure TPolyRobot.Poly(N: Integer; S, U: Real);
begin
  Poly([N, S, U]);
end;

Preveriť funkčnosť tejto metódy môžeme napr. takto:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TPolyRobot;
begin
  Robot := TPolyRobot.Create;
  Robot.Poly([5, 30, 144, 30, -72]);
  Robot.Free;
end;

Dorobíme do aplikácie vstupný riadok (Edit1), do ktorého bude možné zadať parameter pre takúto vylepšenú metódu Poly. Môžeme takémuto robotu pridať stavovú premennú, v ktorej si bude pamätať kresbu, ktorú sme mu zadali. Pri kliknutí do grafickej plochy (Image1MouseDown) na tejto pozícii vznikne nový robot a nakreslí obrázok zadaný vo vstupnom riadku (Edit1). Tlačidlo Button1 prekreslí všetky roboty, pričom sa im zmení farba pera. Tlačidlo Button2 zruší všetky roboty a zmaže grafickú plochu:

type
  TPolyRobot = class(TRobot)
    Kresba: array of Real;
    X0, Y0, H0: Real;
    constructor Create(NewX, NewY, Angle: Real);
    procedure Poly(Pole: array of Real);
    procedure Poly(N: Integer; S, U: Real);
  end;
 
constructor TPolyRobot.Create(NewX, NewY, Angle: Real);
begin
  inherited;
  X0 := NewX;
  Y0 := NewY;
  H0 := H;
end;
 
procedure TPolyRobot.Poly(Pole: array of Real);
  ...
 
var
  Robot: array of TPolyRobot;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  S: string;
  Param: array of Real;
  I: Integer;
begin
  S := Trim(Edit1.Text);
  Param := nil;
  repeat
    I := Pos(' ', S + ' ');
    SetLength(Param, Length(Param) + 1);
    Param[High(Param)] := StrToFloatDef(Copy(S, 1, I - 1), 0);
    Delete(S, 1, I);
    S := Trim(S);
  until S = '';
  SetLength(Robot, Length(Robot) + 1);
  Robot[High(Robot)] := TPolyRobot.Create(X, Y, Random(360));
  with Robot[High(Robot)] do
  begin
    Kresba := Param;
    Poly(Param);
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);  // prekresli
var
  I: Integer;
begin
  for I := 0 to High(Robot) do
    with Robot[I] do
    begin
      PC := Random($1000000);
      MoveXY(X0, Y0);
      H := H0;
      Poly(Kresba);
    end;
end;
 
procedure TForm1.Button2Click(Sender: TObject);  // zmaž
var
  I: Integer;
begin
  for I := 0 to High(Robot) do
    Robot[I].Free;
  Robot := nil;
  CS;
end;

V tomto príklade si všimnite, ako pracujeme s dynamickým poľom robotov.

Ďalšie námety:

  • tlačidlo Button3 uloží do nejakého textového súboru informácie o všetkých robotoch:
  • každý robot do jedného riadka ako postupnosť reálnych čísel oddelených medzerami
  • v riadku budú postupnosti čísel: X0, Y0, H0 (pozícia a otočenie robota, kde vznikol) a za tým obsah poľa Kresba
  • ďalší konštruktor Create s parametrom textový súbor (TextFile), ktorý z jedného riadka súboru (súbor je otvorený a nastavený na začiatok jedného riadka) prečíta postupnosť čísel: X, Y, H a pole čísel pre Poly.


späť | ďalej