21.11.2002
|
čo sa budeme dnes učiť
- ako funguje polymorfizmus pre korytnačky,
ako ho môžeme využiť, ako pracujeme s
polymorfným poľom korytnačiek (trieda
TVelaKor)
- indexované vlastnosti (property)
- pretypovanie, resp. zisťovanie, či je daná
inštancia nejakého typu
Virtuálne metódy triedy TKor
Definícia triedy TKor má skoro všetky
metódy virtuálne. To znamená, že
budeme môcť v našich programoch využívať
polymorfizmus. Okrem toho niektoré metódy
triedy TKor využívajú iné korytnačie
metódy a teda ich predefinovanie má za
následok zmenu správania aj týchto
metód. Napr. ak opravíme ZmenXY, potom
to bude mať vplyv aj na metódu Dopredu.
malý príklad:
|
type
TKor1=class(TKor)
procedure ZmenXY(nx,ny:real); override;
end;
TKor2=class(TKor)
procedure ZmenXY(nx,ny:real); override;
end;
procedure TKor1.ZmenXY(nx,ny:real);
begin
inherited ZmenXY(X,ny);
end;
procedure TKor2.ZmenXY(nx,ny:real);
begin
inherited ZmenXY(nx,Y);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
k,k1,k2:TKor;
u:integer;
begin
k:= TKor.Create; k.HP:=5; k.FP:=clRed;
k1:=TKor1.Create; k1.HP:=5; k1.FP:=clBlue;
k2:=TKor2.Create; k2.HP:=5; k2.FP:=clGreen;
repeat
// zmaz;
k.dopredu(5); k1.dopredu(5); k2.dopredu(5);
cakaj(10);
u:=random(10);
k.vpravo(u); k1.vpravo(u); k2.vpravo(u);
until false;
end;
|
- v tomto prvom príklade sa 3 korytnačky
pohybujú úplne rovnakým spôsobom
- ak by boli všetky tri rovnakej tiedy TKor,
tak by sme videli kresbu len jednej z nich (poslednej
- zelenej)
- k1 a k2 majú zmenené
správanie tak, že k1 mení iba
Y-ovú súradnicu a k2
mení len X-ovú
- ak odkomentujete príkaz zmaz na
priebežné zmazávanie plochy, tak korytnačky
nebudú kresliť čiary, ale budú sa
zobrazovať krátkymi "paličkami"
- v nasledujúcom príklade zatiaľ
nevyužívame žiadne predefinovanie metód
- pomocou dvoch korytnačiek kreslíme myšou
do plochy
"obyčajné" kreslenie myšou:
|
var
k1,k2:TKor;
procedure TForm1.FormCreate(Sender: TObject);
begin
k1:=TKor.Create; k1.PH; k1.FP:=clLtGray; k1.HP:=7;
k2:=TKor.Create; k2.PH;
end;
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
k1.PH; k1.ZmenXY(X,Y); k1.PD;
k2.PH; k2.ZmenXY(X,Y); k2.PD;
end;
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
u,v:real;
begin
if Shift=[ssLeft] then begin
u:=k1.Smerom(X,Y); v:=k1.Vzd(X,Y);
k1.Smer:=u; k1.Dopredu(v);
k2.Smer:=u; k2.Dopredu(v);
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
k1.Free; k2.Free;
end;
|
- dodefinujeme novú triedu, ktorej poopravíme
metódu ZmenSmer, aby nastavovala uhol
na opačný:
trieda TMojaKor má zmenený ZmenSmer:
|
type
TMojaKor = class(TKor)
procedure ZmenSmer(uhol:real); override;
end;
procedure TMojaKor.ZmenSmer(uhol:real);
begin
inherited ZmenSmer(-uhol);
end;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
k1:=TKor.Create; k1.PH; k1.FP:=clLtGray; k1.HP:=7;
k2:=TMojaKor.Create; k2.PH;
end;
|
- teraz sa bude 2. korytnačka správať
veľmi čudne
- zaujímavý efekt vznikne aj vtedy,
keď v Image1MouseDown vyhodíme príkaz
k1.PD;
- v ďalšej sérii príkladov demonštrujeme
rôzne pozmenené dopredu
- najprv základná verzia bez pozmenenej
korytnačky - na kliknutie do plochy sa na tom mieste
nakreslí domček:
domčeky:
|
procedure domcek(k:TKor; d:real);
var
i:integer;
begin
for i:=1 to 4 do begin
k.Dopredu(d);
k.Vpravo(90);
end;
k.Vlavo(60);
for i:=1 to 2 do begin
k.Dopredu(d);
k.Vpravo(120);
end;
end;
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
k:TKor;
begin
randomize;
k:=TKor.Create(X,Y,90); k.FP:=random(256*256*256);
domcek(k,70);
k.Free;
end;
|
- ak korytnačka nedokáže robiť úsečky
presnej dĺžky, ale s nejakou pravdepodobnosťou sa
"mýli" o -10%..10%, môžu
vznikať zaujímavé kresby
nepresná dĺžka:
|
type
TKor1 = class(TKor)
procedure Dopredu(dlzka:real); override;
end;
procedure TKor1.Dopredu(dlzka:real);
begin
inherited Dopredu(dlzka*(0.9+random(20)/100));
end;
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
k:TKor;
begin
randomize;
k:=TKor1.Create(X,Y,90); k.FP:=random(256*256*256);
domcek(k,70);
k.Free;
end;
|
- trieda TKor2 má metódu Dopredu
v poriadku (je odvodená z Tkor), ale
otáčanie Vpravo nechodí presne,
ale korytnačka sa mýli o -10%..10% - otestujte
to na príklade s domčekom
nepresné otáčanie vpravo:
|
type
TKor2 = class(TKor)
procedure Vpravo(u:real); override;
end;
procedure TKor2.Vpravo(u:real);
begin
inherited Vpravo(u*(0.9+random(20)/100));
end;
...
k:=TKor2.Create(X,Y,90);
|
- ak by sme odvodili triedu TKor2 z TKor1,
dostali by sme korytnačku, ktorá nevie robiť
ani presné Dopredu ani presné
Vpravo
kombinovaná trieda - preberá vlastnosti
z TKor1:
|
type
TKor2 = class(TKor1)
...
|
- ďalšie dve triedy ilustrujú iné
varianty zmenenej metódy Dopredu:
iné verzie Dopredu:
|
type
TKor3 = class(TKor)
procedure Dopredu(d:real); override;
end;
TKor4 = class(TKor)
procedure Dopredu(d:real); override;
end;
procedure TKor3.Dopredu(d:real);
begin
Vlavo(random(41)/10-2);
inherited; // to je to isté, ako inherited Dopredu(d);
Vlavo(180+random(41)/10-2);
inherited;
Vlavo(180+random(41)/10-2);
inherited;
end;
procedure TKor4.Dopredu(d:real);
begin
Vlavo(60);
while d>=5 do begin
inherited Dopredu(5);
Vpravo(120);
inherited Dopredu(5);
Vlavo(120);
d:=d-5;
end;
Vpravo(60);
inherited Dopredu(d);
end;
|
- otestujte kreslenie domčeka korytnačkou, ktorá
je inštanciou týchto tried
- poexperimentujte s tým, ak TKor3,
resp. TKor4 bude odvodená nie z TKor
ale z TKor1 alebo TKor2
- nasledujúci jednoduchý príklad
ilustruje pole 10 korytnačiek, ktoré sa pohybujú
po rôzne veľkých kružniciach
10 korytnačiek:
|
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
k:array [1..10] of TKor;
begin
for i:=1 to 10 do begin
k[i]:=TKor.Create; k[i].HP:=7;
end;
repeat
zmaz;
for i:=1 to 10 do begin
k[i].Dopredu(i); k[i].Vpravo(3);
end;
cakaj(100);
until false;
end;
|
- ak týmto korytnačkám opravíme
metódu ZmenXY, tak budeme vidieť tento pohyb
po kružniciach "zboku"
predefinujeme ZmenXY:
|
type
TKor0 = class(TKor)
procedure ZmenXY(xx,yy:real); override;
end;
procedure TKor0.ZmenXY(xx,yy:real);
begin
Inherited ZmenXY(X,yy);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
k:array [1..10] of TKor;
begin
for i:=1 to 10 do begin
k[i]:=TKor0.Create; k[i].HP:=7;
end;
...
|
- iných 10 korytnačiek bude kresliť 10
rovnako veľkých kružníc, ktoré
ale budú rôzne veľké:
10 rovnako vľkých kružníc:
|
type
TMojaKor = class(TKor)
procedure Dopredu(d:real); override;
end;
procedure TMojaKor.Dopredu(d:real);
begin
inherited Dopredu(d*1.5);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
k:array [1..10] of TKor;
i,j:integer;
begin
for i:=1 to 10 do
if random(5)=0 then
k[i]:=TMojaKor.Create(50+i*10,200)
else
k[i]:=TKor.Create(50+i*10,200);
for i:=1 to 10 do k[i].HP:=3;
for i:=1 to 10 do
if k[i] is TMojaKor then k[i].FP:=clRed;
for j:=1 to 120 do begin // tu sa kreslia kružnice
for i:=1 to 10 do k[i].Dopredu(3);
for i:=1 to 10 do k[i].Vpravo(3);
cakaj(10);
end;
for i:=1 to 10 do
k[i].Free;
end;
|
- všimnite si riadok, v ktorom sme zafarbili
tie korytnačky, ktoré sú odvodené
z TMojaKor - použili sme na to operátor
as, ktorý otestuje, či je inštancia
danej triedy (alebo triedy, ktorá je potomkom
tejto triedy)
- častou začiatočníckou chybou je test
if k[i] is TKor then - totiž všetky
prvky k[i] sú odvodené
z TKor - aj keď sú odvodené
z nejakých tried, ktoré sú
potomkami TKor - teda tento test je splnený
vždy
- v tomto príklade sa niektoré korytnačie
metódy často volajú pre všetky
existujúce korytnačky - asi by sa nám
tu hodila nejaká trieda, ktorá "zapúzdri"
pole korytnačiek a hromadné metódy
pre toto pole
Trieda na obsluhu viac korytnačiek - TVelaKor
Do programovej jednotky (unitu) VelaKorUnit zadefinujeme
novú triedu TVelaKor. Táto trieda sa bude
starať o dynamické (polymorfné) pole korytnačiek:
umožní pridávať a vyhadzovať korytnačky
a tiež paralelne vykonávať všetky korytnačie
príkazy (metódy). Definícia triedy
obsahuje:
- súkromné dynamické pole Fk korytnačiek
a ich počet Fpk (pomocou vlastností - property
umožníme čítať tieto stavové premenné
- ale nie modifikovať);
- metódy na pridávanie do tohoto poľa:
- metóda UrobKor je definovaná v troch
rôznych verziách: verzia bez parametrov,
verzia s troma číselnými
parametrami x, y, a u; a verzia s polymorfnou korytnačkou
- podľa typu volaných parametrov sa Delphi rozhodnú,
ktorú z týchto verzií naozaj zavolajú
(použijeme špecifikátor overload)
- stavové premenné - vlastnosti, pomocou
ktorých povolíme čítanie súkromných
(a teda chránených) stavových premenných
- vlastnosť k, ktorá sprístupní
pole Fk je "indexovaná" stavová
premenná, to znamená, že metóda
dajKor musí mať jeden parameter typu index a
musí to byť funkcia, ktorá vracia príslušnú
hodnotu stavovej premennej
deklarácie triedy:
|
type
TVelaKor = class
private
Fk:array of TKor;
Fpk:integer;
function dajKor(i:integer):TKor;
public
constructor Create;
destructor Destroy; override;
procedure UrobKor; overload;
procedure UrobKor(x,y:real; u:real = 0); overload;
procedure UrobKor(nk:TKor); overload;
procedure ZrusKor(i:integer); overload;
procedure ZrusKor(nk:TKor); overload;
procedure Uprac;
procedure Dopredu(d:real);
...
procedure Vypln(fv:TColor);
function Blizko(x,y:real):TKor;
property k[i:integer]:TKor read dajKor;
property pk:integer read Fpk;
end;
|
realizácia kľúčových metód:
|
constructor TVelaKor.Create;
begin
Fpk:=0;
end;
destructor TVelaKor.Destroy;
var
i:integer;
begin
for i:=0 to Fpk-1 do Fk[i].Free;
end;
function TVelaKor.dajKor(i:integer):TKor;
begin
if (i<0) or (i>=Fpk) then Result:=nil
else Result:=Fk[i];
end;
procedure TVelaKor.UrobKor;
begin
UrobKor(TKor.Create); // toto nie je rekurzia
end;
procedure TVelaKor.UrobKor(x,y,u:real);
begin
UrobKor(TKor.Create(x,y,u)); // toto nie je rekurzia
end;
procedure TVelaKor.UrobKor(nk:TKor);
begin
if Fpk>high(Fk) then SetLength(Fk,Length(Fk)+10);
Fk[Fpk]:=nk;
inc(Fpk);
end;
procedure TVelaKor.ZrusKor(i:integer);
begin
if (i>=0) and (i<Fpk) then begin
Fk[i].Free; Fk[i]:=nil;
end;
end;
procedure TVelaKor.ZrusKor(nk:TKor);
var
i:integer;
begin
i:=0; while (i<Fpk) and (Fk[i]<>nk) do inc(i);
ZrusKor(i); // ani toto nie je rekurzia
end;
procedure TVelaKor.Uprac; // vyhodí nil korytnačky z poľa
var
i,j:integer;
begin
j:=0;
for i:=0 to Fpk-1 do
if Fk[i]<>nil then begin
Fk[j]:=Fk[i]; inc(j);
end;
Fpk:=j;
end;
|
metóda Dopredu posunie všetky korytnačky
|
procedure TVelaKor.Dopredu(d:real);
var
i:integer;
begin
for i:=0 to Fpk-1 do
if Fk[i]<>nil then
Fk[i].Dopredu(d);
end;
|
- na rovnakom princípe ako metóda
Dopredu pracujú skoro všetky ostatné
metódy
metóda Blízko - vráti
korytnačku, ktorá je dostatočne blízko
od bodu (x,y):
|
function TVelaKor.Blizko(x,y:real):TKor;
var
i:integer;
begin
i:=Fpk-1;
while (i>=0) and ((Fk[i]=nil) or not Fk[i].Blizko(x,y)) do dec(i);
if i<0 then Result:=nil else Result:=Fk[i];
end;
|
Príklad
- teraz môžeme prepísať príklad
s 10 kružnicami s použitím triedy TVelaKor
použitie triedy TVelaKor:
|
uses
KorUnit, VelaKorUnit;
...
procedure TForm1.Button1Click(Sender: TObject);
var
v:TVelaKor;
i,j:integer;
begin
v:=TVelaKor.Create;
for i:=1 to 10 do
if random(5)=0 then
v.UrobKor(TMojaKor.Create(50+i*10,200))
else
v.UrobKor(50+i*10,200);
v.HP:=3;
for i:=0 to v.pk-1 do
if v.k[i] is TMojaKor then v.k[i].FP:=clRed;
for j:=1 to 120 do begin
v.Dopredu(3);
v.Vpravo(3);
cakaj(10);
end;
v.Free;
end;
|
zápis pomocou príkazu with:
|
procedure TForm1.Button1Click(Sender: TObject);
var
i,j:integer;
begin
with TVelaKor.Create do begin
for i:=1 to 100 do
if random(5)=0 then
UrobKor(TMojaKor.Create(50+i*10,200))
else
UrobKor(50+i*10,200);
HP:=3;
for i:=0 to pk-1 do
if not (k[i] is TMojaKor) then k[i].FP:=clRed;
for j:=1 to 120 do begin
//zmaz; Dopredu(23); Dopredu(-20);
Dopredu(3);
Vpravo(3);
cakaj(10);
end;
Free;
end;
end;
|
- v tomto riešení sme nepotrebovali premennú
v typu TVelaKor - tento objekt existoval
len počas platnosti with
- príkaz if not (k[i] is TMojaKor) then
... testuje tie korytnačky, ktoré nie
sú triedy TMojaKor a teda sú
to všetky ostatné
Príklad
- nasledujúci príklad demonštruje nutnosť
pretypovať inštanciu, ak potrebujeme vyvolať metódu
(alebo aj použiť stavovú premennú), ktorú
nepozná predok (základná trieda)
ale iba potomok
korytnačky nakreslia sínus:
|
type
TMojaKor = class(TKor)
private
k:real;
public
constructor Create(x,y:real; u:real = 0);
procedure Koef(kk:real);
procedure Dopredu(d:real); override;
end;
constructor TMojaKor.Create(x,y,u:real);
begin
inherited; k:=1;
end;
procedure TMojaKor.Koef(kk:real);
begin
k:=kk;
end;
procedure TMojaKor.Dopredu(d:real);
begin
inherited Dopredu(d*k);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
with TVelaKor.Create do begin
for i:=1 to Image1.Width div 2 do begin
UrobKor(TMojaKor.Create(2*i,250));
TMojaKor(k[pk-1]).koef(sin(2*rad*i)); // posledne urobená korytnačka
end;
Dopredu(200);
Free; // uvoľní inštanciu TVelaKor - pole korytnačiek
end;
end;
|
"sinusové" korytnačky kmitajú:
|
...
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
with TVelaKor.Create do begin
for i:=1 to Image1.Width div 2 do begin
UrobKor(TMojaKor.Create(2*i,250));
(k[pk-1] as TMojaKor).koef(sin(2*rad*i));
end;
HP:=3;
Dopredu(200);
repeat
vpravo(180);
for i:=1 to 100 do begin
zmaz;
Dopredu(4);
cakaj(1);
end;
until false;
end;
end;
|
- v druhom príklade môžeme vidieť
iný variant pretypovania - použili sme operátor
as, ktorým sa "pozrieme" na nejakú
inštanciu (korytnačku) akoby bola inej triedy (svoj
potomok)
Projekt, ktorý
bol na skúške v 1996:
zadanie:
|
Napíšte program, ktorý bude riešiť nasledovnú úlohu: textový súbor subor.txt obsahuje postupnosť príkazov pre korytnačku,
ktorá popisuje nejaký obrázok. Najprv zadefinujete triedu TKor0 potomka triedy TKor, ktorá bude obsahovať jedinú metódu
Táto metóda sa nastaví na začiatok súboru f, korytnačke zdvihne pero a zinterpretuje príkazy v súbore. Nakoľko takto kreslený
obrázok môže byť buď príliš veľký alebo malý alebo môže presahovať okraje grafickej obrazovky, bude ho treba zmenšiť alebo
zväčšiť, resp. pri inicializovaní korytnačky nezačať kresliť v strede
plochy, ale zvoliť si domovskú pozíciu vhodnejšie a to tak, aby na
šírku alebo výšku bol maximálne možne veľký.
Na riešenie tejto úlohy zadefinujete ešte dve podtriedy triedy TKor0: trieda TKor1 bude slúžiť na zistenie rozmerov obrázka
pomocou zdedenej metódy kresli pričom na obrazovke sa bude pohybovať so zdvihnutým perom. Trieda TKor2 (tiež podtrieda
triedy TKor0) bude vedieť nakresliť daný obrázok (zdedenou metódou kresli) pričom prekryje virtuálnu metódu Dopredu tak, že
obrázok bude požadovanej veľkosti.
Textový súbor subor.txt sa skladá z takýchto "viet": prvé číslo každej vety je typu ordinálna hodnota z
type TPrik = (pdo,pvl,pvp,pfp,pph,ppd);
a reprezentuje postupne príkazy (Dopredu, Vlavo, Vpravo, ZmenFP, PH, PD). Pre hodnoty pdo, pvl, pvp a pfp za týmto číslom
nasleduje parameter príslušného príkazu - jedno celé číslo (integer). Pre hodnoty pph a ppd veta už neobsahuje ďalšie čísla.
Predpokladajte, že vstupný súbor je zadaný korektne (obrázok nemá nulovú ani výšku ani šírku).
Váš program teda najprv popíše tri triedy TKor0, TKor1 a TKor2. Potom pomocou inštancie triedy TKor1 (s domovskou pozíciou
v (0,0)) zistí rozmery obrázka (použijeme na to stavové premenné minX, minY, maxX a maxY) - zrejme predefinujete virtuálne
metódy Dopredu, PH a PD. Nakoniec, pomocou inštancie triedy TKor2, ktorú zinicializuje v nejakej novej domovskej pozícii,
vykreslí daný obrázok správnej veľkosti. Obe korytnačky majú rovnaký počiatočný smer zadaný konštantou programu, napr.
Na testovanie programu môžete použiť
súbory subor.txt
alebo subor1.txt.
|
Riešenie:
najprv len vykreslíme súbor
pomocou inštancie Kor0, aby sme mohli vidieť, momentálny
obsah súboru:
|
type
TKor0 = class(TKor)
procedure kresli(var t:TextFile);
end;
procedure TKor0.kresli(var t:TextFile);
type
TPrik = (pdo,pvl,pvp,pfp,pph,ppd);
var
p:TPrik;
i:integer;
begin
PH; Reset(t);
while not seekEof(t) do begin
read(t,i); p:=TPrik(i);
if p<=pfp then read(t,i);
case p of
pdo: Dopredu(i);
pvl: Vlavo(i);
pvp: Vpravo(i);
pfp: ZmenFP(i);
pph: PH;
ppd: PD;
end;
end;
end;
const
u = -45;
subor = 'subor.txt';
// toto slúži len na otestovanie metódy kresli
procedure TForm1.FormCreate(Sender: TObject);
var
t:TextFile;
begin
AssignFile(t,subor);
with TKor0.Create do begin // nepotrebujeme premennú typu TKor0
Smer:=u;
kresli(t);
Free;
end;
CloseFile(t);
end;
|
teraz dodefinujeme Kor1 a Kor2:
|
type
TKor1 = class(TKor0)
b,p:boolean;
minx,miny,maxx,maxy:real;
procedure Dopredu(d:real); override;
procedure PH; override;
procedure PD; override;
end;
procedure TKor1.PH;
begin
inherited; p:=false;
end;
procedure TKor1.PD;
begin
p:=true;
end;
procedure TKor1.Dopredu(d:real);
procedure xy;
begin
if not p then exit; // ak je pero hore
if b or (X<minx) then minx:=X;
if b or (X>maxx) then maxx:=X;
if b or (Y<miny) then miny:=Y;
if b or (Y>maxy) then maxy:=Y;
b:=false;
end;
begin
xy; inherited; xy;
end;
|
trieda TKor2:
|
type
TKor2 = class(TKor0)
r:real;
procedure Dopredu(d:real); override;
end;
procedure TKor2.Dopredu(d:real);
begin
inherited Dopredu(d*r);
end
|
tlačidlo Button1 naštartuje celý algoritmus:
|
procedure TForm1.Button1Click(Sender: TObject);
var
t:TextFile;
rr,r1,r2:real;
begin
AssignFile(t,subor);
with TKor1.Create(0,0,u) do begin
b:=true; // bude treba inicializovať minx, maxx, miny, maxy
kresli(t); // TKor1 naozaj nekreslí - len zisťuje oblasť
r1:=sirka/(maxx-minx+1); // sirka a vyska sú premenne z KorUnit
r2:=vyska/(maxy-miny+1);
if r1<r2 then rr:=r1 else rr:=r2;
with TKor2.Create(-rr*minx,-rr*miny,u) do begin
r:=rr; // nastaví mierku pre Dopredu
kresli(t);
Free;
end;
Free;
end;
CloseFile(t);
end;
|
|