16.Prednaska
16. Prednáška |
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.