14.Prednaska

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

úlohy | cvičenie


Úvod do OOP

Pripomeňme si, aké pojmy sme prezradili v 11. prednáške pri definovaní robota:

V doterajších príkladoch sme zadefinovali a používali buď celočíselné premenné (Integer) alebo premenné typu TRobot. Tieto robotové premenné sa líšia od "obyčajných" premenných tým, že

  • si pamätajú svoj momentálny stav v svojich tzv. stavových premenných (napr. pozícia, farba, ...)
  • majú svoje súkromné príkazy, pomocou ktorých ich nejako riadime, resp. meníme ich stavové premenné - takýmto príkazom - sú to procedúry - hovoríme metódy a "rozumejú" im len roboty (premenné typu TRobot)
  • musia byť vytvorené (nie deklarované) špeciálnym spôsobom: TRobot.Create(...); a kým sa takto nevytvoria, nesmú sa vôbec používať
  • takýmto premenným hovoríme OBJEKT a typom, z ktorých vytvárame objekty (napr. TRobot) hovoríme TRIEDA (po anglicky object a class) - niekedy sa objektu hovorí aj inštancia triedy
  • okrem robotov sme sa už stretli aj s inými objektmi, napr. Form1, Image1, Button1, TBitmap ale aj Image1.Canvas, ktorá je inštanciou triedy TCanvas,
  • zatiaľ si o objektoch treba zapamätať, že sú to premenné, ktoré môžu v sebe obsahovať veľa stavových premenných a tiež "v sebe" obsahujú nejaké svoje procedúry (metódy) => tomuto hovoríme zapuzdrenie (enkapsulácia), lebo v jednom "puzdre" sú aj údaje (stavové premenné) aj algoritmy (metódy), ktoré vedia s týmito údajmi pracovať
  • uvidíme, že z programátorského hľadiska bude snaha, aby sa s údajmi v objekte pracovalo len pomocou jeho metód
  • neskôr sa stretneme s tým, že niektoré stavové premenné vôbec neuvidíme a nebudeme s nimi môcť pracovať inak ako pomocou niektorých metód

Z doterajších skúseností vieme, že s objektom robot sa pracuje veľmi podobne ako s obyčajným záznamom (record). Pozrime ako sme pracovali s nejakou jednoduchou štruktúrou, kým sme nepoznali objekty:

type
  TBod = record
    X, Y: Integer;
  end;
 
procedure Kresli(B: TBod);
begin
  Form1.Image1.Canvas.Line(B.X, B.Y, B.X + 1, B.Y);
end;
 
var
  Bod: TBod;
begin
  Bod.X := 100;
  Bod.Y := 150;
  Kresli(Bod);
end;

Zadefinovali sme štruktúru záznam s dvomi položkami X a Y a pomocnú procedúru, ktorá vie nakresliť takto zadefinovanú bodku v grafickej ploche. Potom sme zadefinovali premennú typu táto štruktúra, priradili sme jej položkám nejaké hodnoty a zavolali sme kresliacu procedúru.

Teraz sa pozrieme na toto isté "objektovými očami": zadefinujeme špeciálny typ, ktorý okrem údajov (premenných X a Y) bude obsahovať aj svoju súkromnú procedúru na kreslenie bodky v ploche na základe hodnoty údajov:

type
  TBod = class
    X, Y: Integer;
    procedure Kresli;
  end;
 
procedure TBod.Kresli;
begin
  Form1.Image1.Canvas.Pen.Color := clBlack;
  Form1.Image1.Canvas.Line(X, Y, X + 1, Y);
end;
 
var
  Bod: TBod;
begin
  Bod := TBod.Create;
  Bod.X := 100;
  Bod.Y := 150;
  Bod.Kresli;
  Bod.Free;
end;

Typu TBod budeme odteraz hovoriť trieda.

Premenným, ktoré sú definované vo vnútri triedy nebudeme hovoriť položky (ako v zázname), ale stavové premenné alebo atribúty.

Súkromnej procedúre Kresli, ktorá je zadeklarovaná vo vnútri definície typu a potom jej telo je definované neskôr s prefixom TBod budeme hovoriť metóda.

Ďalej sme definovali premennú Bod typu TBod. Budeme tomu hovoriť, buď objekt z triedy TBod, alebo inštancia triedy TBod.

Keď je takáto premenná zadeklarovaná, ešte to neznamená, že ako objekt už existuje. S objektom budeme môcť plnohodnotne pracovať až keď sa skonštruuje špeciálnym priraďovacím príkazom (s pôvodným záznamom sme mohli pracovať okamžite):

  premenná := trieda.Create;

Vtedy sa do premennej (do objektu, do inštancie) dostáva informácia, vďaka ktorej môžeme ďalej s objektom pracovať (metaforicky môžeme povedať, že objekt ožije, narodí sa) - hovoríme tomu konštruktor objektu.

Ďalšie dva priraďovacie príkazy priraďujú nejaké hodnoty do stavových premenných úplne rovnakým spôsobom, ako sme to robili so záznamami.

Ďalej sme zavolali súkromnú procedúru Kresli, povieme, že sme zavolali metódu (objektu sme povedali aby vykonal nejaký príkaz, napr. aby sa vykreslil). Všimnite si, že vo vnútri metódy sa pracuje so stavovými premennými objektu bez uvádzania Bod. Metóda sa vykonáva ako keby vo vnútri objektu, pričom vidí jeho stavové premenné ako obyčajné premenné.

Na konci programu je špeciálne volanie Free - je to povinné zrušenie objektu, hovoríme tomu deštruktor. Všimnite si, že obyčajné premenné ako napr. záznamy typu TBod vznikajú automaticky pri štarte podprogramu, a automaticky zanikajú pri konci podprogramu - netreba ich nijako rušiť. Objekty sa ale automaticky nerušia - keď ich nezrušíme manuálne, budú existovať aj po skončení podprogramu. (Metaforicky môžeme povedať, že pri rušení objekt zomrie.

Vytvorme ešte jednu metódu do triedy TBod: do deklarácie triedy dopíšme tento riadok:

type
  TBod = class
    X, Y: Integer;
    procedure Kresli;
    procedure Zmen(NoveX, NoveY: Integer);
  end;

Skôr, ako budeme definovať túto procedúru, stlačme klávesy <Shift-Ctrl-C> (kurzor musí byť niekde vo vnútri deklarácie triedy). Automaticky sa vytvorí prázdna metóda, ktorá je syntakticky správna:

procedure TBod.Zmen(NoveX, NoveY: Integer);
begin
 
end;

Sem môžeme dopísať ľubovoľný obsah, napr.

procedure TBod.Zmen(NoveX, NoveY: Integer);
begin
  X := NoveX;
  Y := NoveY;
end;

Táto nová metóda má dva celočíselné parametre, ktoré označujú nové hodnoty pre stavové premenné X a Y. Môžeme prepísať aj samotný program:

var
  Bod: TBod;                  // deklarácia objektu
begin
  Bod := TBod.Create;         // vytvorenie objektu
  Bod.Zmen(100, 150);         // nastavenie hodnôt atribútov
  Bod.Kresli;                 // zavolanie metódy
  Bod.Free;                   // zrušenie objektu
end;

Teraz náš program nepracuje priamo so stavovými premennými ale len prostredníctvom metód triedy. Takže, v našej štruktúre máme teraz dve stavové premenné a metódy. Takej vlastnosti, že v jednej štruktúre sú údaje spolu s algoritmami, hovoríme enkapsulácia alebo zapuzdrenie. To je jedna z troch základných vlastností objektového programovania. Ďalšou veľmi dôležitou vlastnosťou je dedičnosť.


Dedičnosť

Dedičnosť označuje, že z už vytvorenej triedy môžeme vytvoriť novú tak, že tento nový typ preberá (zdedí) všetky stavové premenné a tiež všetky metódy od pôvodného typu, pričom môže ďalšie stavové premenné pridať, prípadne niektoré metódy predefinovať, resp. nové pridať. Ukážme to na príklade triedy TBod. Vytvoríme novú triedu TBodka, ktorá bude odvodená z už existujúcej triedy TBod (vznikla dedením):

type
  TBod = class
    X, Y: Integer;
    procedure Kresli;
    procedure Zmen(NoveX, NoveY: Integer);
  end;
 
  TBodka = class(TBod)
    Farba: TColor;
    procedure Kresli;
  end;

Takáto deklarácia hovorí, že nová trieda TBodka automaticky preberá zo svojho nadradeného typu (od ktorého sme dedili) všetky stavové premenné (t.j. X a Y) a tiež metódu Zmen. Metódu Kresli nepreberá, lebo sme sa rozhodli ju predefinovať - musíme zadefinovať jej novú verziu. Okrem toho, táto nová trieda má zadefinovanú ďalšiu stavovú premennú Farba. Hovoríme, že TBodka je potomkom (descendent) triedy TBod, resp. že TBod je predkom (ancestor) triedy TBodka. Každá trieda môže mať ľubovoľný počet potomkov, ale len jediného predka.

To, že jedna trieda vznikla ako potomok inej triedy (vznikla odvodením od inej tiredy), zvykneme kresliť takto:

TBod
X, Y
Kresli
Zmen

TBodka
X, Y
Farba
Kresli
Zmen

Ešte musíme zadefinovať metódu Kresli, ktorú sme sa rozhodli pri dedení prepísať novšou verziou. Môže vyzerať napr. takto

procedure TBodka.Kresli;
begin
  with Form1.Image1.Canvas do
  begin
    Pen.Width := 10;
    Pen.Color := Farba;
    Line(X, Y, X + 1, Y);
  end;
end;

V tejto definícii metódy Kresli pracujeme so stavovými premennými X, Y a Farba. Otestovať túto novú triedu môžeme napr. takto:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  B: TBodka;
begin
  B := TBodka.Create;
  B.Zmen(X, Y);
  B.Farba := clRed;
  B.Kresli;
  B.Free;
end;

Zdedená metóda Zmen nastavuje len stavové premenné X a Y (podľa toho, kde sme klikli) a preto musíme novú stavovú premennú Farba nastavovať priraďovacím príkazom v programe. Ak by sme chceli v metóde Zmen nastaviť aj premennú Farba, museli by sem vyrobiť jej novú verziu. Napr. takto:

type
  TBod = class
    X, Y: Integer;
    procedure Kresli;
    procedure Zmen(NoveX, NoveY: Integer);
  end;
 
  TBodka = class(TBod)
    Farba: TColor;
    procedure Kresli;
    procedure Zmen(NoveX, NoveY: Integer; NovaFarba: TColor);
  end;
 
procedure TBodka.Kresli;
begin
  with Form1.Image1.Canvas do
  begin
    Pen.Width := 10;
    Pen.Color := Farba;
    Line(X, Y, X + 1, Y);
  end;
end;
 
procedure TBodka.Zmen(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  X := NoveX;
  Y := NoveY;
  Farba := NovaFarba;
end;

a potom aj samotný program

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  B: TBodka;
begin
  B := TBodka.Create;
  B.Zmen(X, Y, clRed);
  B.Kresli;
  B.Free;
end;

Všimnite si, že v novej metóde Zmen najprv robíme pôvodné dve priradenia do X a Y (to čo predtým robila metóda Zmen) a potom sa priradí do Farba. To znamená, že nová metóda Zmen najprv vykoná príkazy pôvodnej metódy Zmen (metódy predka) a potom niečo navyše. Toto v Pascale môžeme zapísať takto:

procedure TBodka.Zmen(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  inherited Zmen(NoveX, NoveY);
  Farba := NovaFarba;
end;

Keby tu nebolo rezervované slovo inherited, Pascal by pochopil, že na definovanie Zmen chceme použiť rekurziu, t.j. opäť zavolať tú istú procedúru - zrejme by tu hlásil chybu pre zlý počet parametrov.

Tu vidíme, že v programe môžeme súčasne používať objekty základnej aj odvodenej triedy: každý objekt volá svoje vlastné metódy a má svoje vlastné súkromné premenné:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  A: TBod;
  B: TBodka;
begin
  A := TBod.Create;
  A.Zmen(X, Y + 10);
  B := TBodka.Create;
  B.Zmen(X, Y, clRed);
  A.Kresli;
  B.Kresli;
  A.Free;
  B.Free;
end;


Konštruktor objektu

V doterajších príkladoch sme vytvárali objekty pomocou konštruktora Create, ktorý bol bez parametrov. Veľmi často sme po takomto skonštruovaní objektu volali metódu Zmen, ktorá inicializovala obsahy stavových premenných. Spomínali sme, že hlavnou úlohou konštruktora je pripraviť informáciu, ktorá sa priradí do objektovej premennej, vďaka čomu s ňou môžeme ďalej pracovať. Konštruovanie objektu sa môže spojiť s inicializáciou stavových premenných, t.j. konštruktor okrem prípravy pamäti pre objekt môže vykonávať ľubovoľné ďalšie činnosti. Vtedy zadefinujeme vlastný konštruktor, ktorý môže mať aj nejaké parametre. Prepíšeme triedy TBod a TBodka:

type
  TBod = class
    X, Y: Integer;
    constructor Create(NoveX, NoveY: Integer);
    procedure Kresli;
  end;
 
  TBodka = class(TBod)
    Farba: TColor;
    constructor Create(NoveX, NoveY: Integer; NovaFarba: TColor);
    procedure Kresli;
  end;
 
constructor TBod.Create(NoveX, NoveY: Integer);
begin
  X := NoveX;
  Y := NoveY;
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  X := NoveX;
  Y := NoveY;
  Farba := NovaFarba;
end;

Obe metódy Kresli ostávajú bezo zmeny, ale metódy Zmen sme vymenili za konštruktory Create. Všimnite si syntax konštruktorov Create: vyzerajú rovnako ako obyčajná metóda, ale namiesto slova procedure píšeme constructor. Teraz musíme opraviť aj program, ktorý používa objekty s takýmito konštruktormi:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  A: TBod;
  B: TBodka;
begin
  A := TBod.Create(X, Y + 10);
  B := TBodka.Create(X, Y, clRed);
  A.Kresli;
  B.Kresli;
  A.Free;
  B.Free;
end;

Trieda môže mať aj viac konštruktorov - keďže sú to pascalovské procedúry, musia sa líšiť menom alebo parametrami (počtom alebo typmi). Niekedy sa môžeme stretnúť napr. aj s takýmito konštruktormi:

type
  TBodka = class(TBod)
    Farba: TColor;
    constructor Create;
    constructor Create(NoveX, NoveY: Integer);
    constructor Create(NoveX, NoveY: Integer; NovaFarba: TColor);
    constructor ZoSuboru(var T: TextFile);
    procedure Kresli;
  end;
 
constructor TBodka.Create;
begin
  X := Form1.Image1.Width div 2;
  Y := Form1.Image1.Height div 2;
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer);
begin
  X := NoveX;
  Y := NoveY;
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  X := NoveX;
  Y := NoveY;
  Farba := NovaFarba;
end;
 
constructor TBodka.ZoSuboru(var T: TextFile);
begin
  ReadLn(T, X, Y, Farba);
end;

Aj štvrtý konštruktor ZoSuboru môže mať meno Create, len názov ZoSuboru lepšie vyjadruje jeho funkciu. Použiť by sme ho mohli napr. takto:

var
  Bod: array [1..100] of TBodka;
  T: TextFile;
...
begin
  AssignFile{T, 'bodky.txt');
  Reset(T);
  N := 0;
  while not EOF(T) do
  begin
    Inc(N);
    Bod[N] := TBodka.ZoSuboru(T);
  end;

Pri definovaní konštruktorov môžeme využiť iné konštruktory (aj metódy). Predchádzajúce konštruktory môžeme zapísať napr. aj takto

constructor TBodka.Create;
begin
  Create(Form1.Image1.Width div 2, Form1.Image1.Height div 2);
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer);
begin
  Create(NoveX, NoveY, clBlack);
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  X := NoveX;
  Y := NoveY;
  Farba := NovaFarba;
end;

Aj grafický robot TRobot má niekoľko variantov konštruktorov, napr.

var
  R: TRobot;
begin
  R := TRobot.Create;
...
  R := TRobot.Create(200, 100);
...
  R := TRobot.Create(200, 100, 90);

Všimnite si, že konštruktor Create sme použili aj v prvom príklade s triedou TBod:

var
  Bod: TBod;
begin
  Bod := TBod.Create;
  ...

a pritom sme tento konštruktor nikde nedeklarovali ani nedefinovali. Totiž platí jedna dôležitá vec: každá trieda, ktorej neuvedieme predka, je odvodená (je potomkom) základnej triedy TObject. To znamená, že keď pri definícii triedy zapíšeme

type
  TBod = class
    X, Y: Integer;
    procedure Kresli;
  end;

je to vlastne to istí, ako keby sme zapísali

type
  TBod = class(TObject)
    X, Y: Integer;
    procedure Kresli;
  end;

Táto trieda je niečim ako prarodičom všetkých tried. Okrem iného má definovaný konštruktor Create bez parametrov:

constructor TObject.Create;
begin
 
end;

ktorý môže využiť každá nová trieda, ktorá si nebude definovať svoj vlastný konštruktor Create - napr. trieda TBod v prvej verzii zdedila tento konštruktor.

Pripomeňme si konštruktory tried TBod a TBodka:

constructor TBod.Create(NoveX, NoveY: Integer);
begin
  X := NoveX;
  Y := NoveY;
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  X := NoveX;
  Y := NoveY;
  Farba := NovaFarba;
end;

Keďže trieda TBodka je odvodená z triedy TBod, pri definovaní jej metód aj konštruktorov, môžeme využiť zápisu inherited. Tieto dve definície môžeme zapísať aj takto:

constructor TBod.Create(NoveX, NoveY: Integer);
begin
  inherited Create;
  X := NoveX;
  Y := NoveY;
end;
 
constructor TBodka.Create(NoveX, NoveY: Integer; NovaFarba: TColor);
begin
  inherited Create(NoveX, NoveY);
  Farba := NovaFarba;
end;

Zapísali sme inherited Create aj do konštruktora TBod.Create. V tomto prípade to znamená zavolanie konštruktora TObject.Create. My už vieme, že je to prázdny konštruktor, ktorý nevykoná žiadne ďalšie príkazy. Z toho dôvodu ho sem písať nemusíme. V konštruktore TBodka.Create volanie zdedeného (inherited) konštruktora zavolá TBod.Create, ktorý vykoná dve priradenia a až potom sa priradí do stavovej premennej Farba.


Príklady tried

Uvádzame niekoľko príkladov, v ktorých využijeme na to definované triedy.



Snehové vločky



V tomto príklade zadefinujeme triedu, pomocou ktorej sa budú kresliť snehové vločky (malé biele kruhy). Takýchto vločiek vytvoríme veľké množstvo - pri hýbaní myšou (v udalosti onMouseMove) sa budú takéto vločky vytvárať a v časovači (Timer s intervalom 100 ms) budú tieto vločky padať nadol. Najprv definícia triedy:

type
  TVlocka = class
    X, Y: Integer;
    constructor Create(XX, YY: Integer);
    procedure Kresli(C: TCanvas);
    procedure Posun(DX, DY: Integer);
  end;
 
constructor TVlocka.Create(XX, YY: Integer);
begin
  X := XX;
  Y := YY;
end;
 
procedure TVlocka.Kresli(C: TCanvas);
begin
  with C do
  begin
    Pen.Color := clWhite;
    Pen.Width := 10;
    Line(X, Y, X + 1, Y);
  end;
end;
 
procedure TVlocka.Posun(DX, DY: Integer);
begin
  Inc(X, DX);
  Inc(Y, DY);
end;

Využijeme veľké pole objektov Vlocka pricom v premennej N bude aktuálny počet vytvorených vločiek:

var
  Vlocka: array [1..100000] of TVlocka;
  N: Integer;
 
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  Inc(N);
  Vlocka[N] := TVlocka.Create(X, Y);
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  I: Integer;
begin
  Image1.Canvas.Brush.Color := clNavy;
  Image1.Canvas.FillRect(Image1.ClientRect);
  for I := 1 to N do
    with Vlocka[I] do
    begin
      Posun(Random(3) - 1, Random(3) + 1);
      Kresli(Image1.Canvas);
    end;
end;



Objekt kružnica



V tomto príklade ukážeme použitie vlastných zadefinovaných objektov. Do formulára umiestnime grafickú plochu Image1 a 5 tlačidiel Button1Button5, ktorým budeme postupne priraďovať nejaké akcie. Zadefinujeme triedu TKruh odvodenú od skôr definovanej triedy TBodka, ktorá popisuje kružnicu v grafickej ploche (so stredom X, Y, s polomerom R. Táto sa bude kresliť farbou Farba, a môžeme určiť, či sa bude zobrazovať, teda, či ju bude Vidiet). Trieda bude mať okrem konštruktora Create metódy na vykreslenie Ukaz, ukrytie Ukry a posunutie stredu kružnice Posun.

type
  TKruh = class(TBodka)
    R: Integer;
    Vidiet: Boolean;
    constructor Create(NoveX, NoveY, NoveR: Integer; NovaFarba: TColor);
    procedure Ukaz;
    procedure Skry;
    procedure Posun(PosunX, PosunY: Integer);
  end;
 
constructor TKruh.Create(NoveX, NoveY, NoveR: Integer; NovaFarba: TColor);
begin
  inherited Create(NoveX, NoveY, NovaFarba);
  R := NoveR;
end;
 
procedure TKruh.Ukaz;
begin
  with Form1.Image1.Canvas do
  begin
    Pen.Color := Farba;
    Brush.Style := bsClear;                 // kružnica bez výplne
    Ellipse(X - R, Y - R, X + R, Y + R);
  end;
  Vidiet := True;
end;
 
procedure TKruh.Skry;
begin
  with Form1.Image1.Canvas do
  begin
    Pen.Color := clWhite;
    Brush.Style := bsClear;
    Ellipse(X - R, Y - R, X + R, Y + R);
  end;
  Vidiet := False;
end;
 
procedure TKruh.Posun(PosunX, PosunY: Integer);
var
  Bolo: Boolean;
begin
  Bolo := Vidiet;
  if Vidiet then
    Skry;
  Inc(X, PosunX);
  Inc(Y, PosunY);
  if Bolo then
    Ukaz;
end;

V aplikácii zadefinujeme 3 globálne premenné typu TKruh, t.j. tri inštancie triedy. Zatlačenie prvého tlačidlo vytvorí niekde v ploche prvú kružnica. Podobne aj na 2. a 3. tlačidlo vytvorí ďalšie kružnice:

var
  A, B, C: TKruh;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  A := TKruh.Create(100, 100, 50, clGreen);
  A.Ukaz;
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  B := TKruh.Create(300, 200, 100, clRed);
  B.Ukaz;
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  C := TKruh.Create(200, 250, 70, clBlue);
  C.Ukaz;
end;


Štvrté tlačidlo rozhýbe kruhy tak, že každý sa paralelne pohne nejakým smerom 20 krokov. Použijeme na to časovač (Timer1 - v inšpektore mu nastavíme Interval na 100 a Enabled na False). Zatlačenie tlačidla naštartuje časovač, časovač "zatiká" iba 20-krát a potom sa sám vypne:

var
  Pocitadlo: Integer;
 
procedure TForm1.Button4Click(Sender: TObject);
begin
  Pocitadlo := 20;
  Timer1.Enabled := True;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  A.Posun(3, 2);
  B.Posun(-5, -1);
  C.Posun(2, -3);
  Dec(Pocitadlo);
  if Pocitadlo <= 0 then
    Timer1.Enabled := False;
end;

Ak teraz naštartujeme projekt a 4. tlačidlo stlačíme skôr, ako sa vytvoria všetky kružnice (zatlačenie troch tlačidiel Button1Buton3), tak program spadne na chybe Acces violation at address.... Preto by sme mali zabezpečiť, že posúvať budeme len tie kružnice, ktoré už boli naozaj vyrobené. Ako ale zistíme, či bola inštancia už vytvorená (t.j. priradili sme jej nejaký Create)?

V skutočnosti v premennej A nie je priamo obsah objektu (t.j. všetky stavové premenné, tak ako by to bolo v zázname), ale len referencia na nejaké miesto v pamäti počítača, kde sa naozaj tento objekt nachádza. Kým nie je objekt vytvorený (skonštruovaný), tak v premennej A nie je žiadna referencia, čo sa v pascale označuje slovom nil a teda môžeme ju testovať na hodnotu nil:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if A <> nil then
    A.Posun(3, 2);
  if B <> nil then
    B.Posun(-5, -1);
  if C <> nil then
    C.Posun(2, -3);
  Dec(Pocitadlo);
  if Pocitadlo <= 0 then
    Timer1.Enabled := False;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  A := nil;
  B := nil;
  C := nil;
end;

Je dobré si zvyknúť hneď na začiatok programu (napr. do FormCreate) dať práve takéto priradenia. Tým inicializujeme všetky objektové premenné. Dohodli sme sa, že každý objekt musíme po skončení uvoľniť pomocou Free - v našom príklade to urobíme v udalosti OnFormDestroy:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  A.Free;
  B.Free;
  C.Free;
end;

A.Free je skrátený zápis konštrukcie if A <> nil then A.Destroy;, ktorá označuje: ak je A normálny objekt, tak ho korektne zruš (uvolni pamäť, ktorú zaberá).

Piate tlačidlo korektne uvoľní všetky inštancie (A, B, C):

procedure TForm1.Button5Click(Sender: TObject);
begin
  if A <> nil then
    with A do
    begin
      if Vidiet then
        Skry;
      Free;
      A := nil;      // aby A nereferencovalo na už nedefinovaný objekt
    end;
  if B <> nil then
    with B do
    begin
      if Vidiet then
        Skry;
      Free;
      B := nil;
    end;
  if C <> nil then
    with C do
    begin
      if Vidiet then
        Skry;
      Free;
      C := nil;
    end;
end;

Dvojicu príkazov A.Free; A := nil; môžeme nahradiť volaním štandardnej procedúry FreeAndNil(A);



Zhrnutie



Objekty sa skladajú z

  • stavových premenných (atribútov)
  • metód

Z pohľadu objektového programovania majú objekty takéto vlastnosti:

  • enkapsulácia (encapsulation - zapuzdrenie)
  • nový dátový typ trieda (class)
  • spojenie typu record a procedúry/funkcie pre manipuláciu so stavovými premennými
  • vďaka tomu môžeme zakryť premenné tak, aby sa s nimi pracovalo pomocou metód
  • neskôr uvidíme, že stavové premenné úplne skryjeme a necháme prístup len metódami
  • vďaka zapuzdreniu môžeme skryť realizáciu objektu - niekedy uvidíme len názvy metód a nie ako sú naprogramované
  • dedičnosť (inheritance)
  • od definovaného objektu môžeme odvodiť celú hierarchiu objektov
  • t.j. potomkov, ktorí dedia prístup k dátovým aj programovým zložkám
  • polymorfizmus
  • zdieľanie akcií v hierarchii objektov
  • budeme sa učiť až neskôr



Príklad - trieda veľké čísla



V predchádzajúcich prednáškach sme už viackrát riešili úlohy, v ktorých sme pracovali s poľom cifier nejakého veľkého čísla. Teraz zadefinujeme triedu, ktorá zapuzdrí deklarácie a metódy pre jednoduchšiu prácu s takouto reprezentáciou veľkých čísel. Zadefinujeme triedu TVelkeCislo:

type
  TVelkeCislo = class
    Cifry: array [1..10000] of Byte;
    constructor Create;
    procedure Prirad(X: Integer);
    procedure Pricitaj(X: Integer);
    procedure Nasob(X: Integer);
    procedure Kopiruj(Velke: TVelkeCislo);
    procedure Scitaj(Velke: TVelkeCislo);
    function Vypis: string;
  end;
 
{ TVelkeCislo }
 
constructor TVelkeCislo.Create;
var
  I: Integer;
begin                  // táto iniacializácia poľa je tu zbytočná
  for I := 1 to High(Cifry) do
    Cifry[I] := 0;
end;
 
procedure TVelkeCislo.Prirad(X: Integer);
var
  I: Integer;
begin
  for I := 1 to High(Cifry) do
  begin
    Cifry[I] := X mod 10;
    X := X div 10;
  end;
end;
 
procedure TVelkeCislo.Pricitaj(X: Integer);
var
  I, Prenos: Integer;
begin
  Prenos := X;
  for I := 1 to High(Cifry) do
  begin
    Prenos := Cifry[I] + Prenos;
    Cifry[I] := Prenos mod 10;
    Prenos := Prenos div 10;
  end;
end;
 
procedure TVelkeCislo.Nasob(X: Integer);
var
  I, Prenos: Integer;
begin
  Prenos := 0;
  for I := 1 to High(Cifry) do
  begin
    Prenos := Cifry[I] * X + Prenos;
    Cifry[I] := Prenos mod 10;
    Prenos := Prenos div 10;
  end;
end;
 
procedure TVelkeCislo.Kopiruj(Velke: TVelkeCislo);
begin
  Cifry := Velke.Cifry;
end;
 
procedure TVelkeCislo.Scitaj(Velke: TVelkeCislo);
var
  I, Prenos: Integer;
begin
  Prenos := 0;
  for I := 1 to High(Cifry) do
  begin
    Prenos := Cifry[I] + Velke.Cifry[I] + Prenos;
    Cifry[I] := Prenos mod 10;
    Prenos := Prenos div 10;
  end;
end;
 
function TVelkeCislo.Vypis: string;
var
  I: Integer;
begin
  I := High(Cifry);
  while (I > 1) and (Cifry[I] = 0) do
    Dec(I);
  Result := '';
  for I := I downto 1 do
    Result := Result + Char(Cifry[I] + Ord('0'));
end;

Teraz môžeme vypočítať veľký faktoriál, napr. takto (ako konzolová aplikácia):

const
  N = 1000;
 
var
  F: TVelkeCislo;
  I: Integer;
begin
  F := TVelkeCislo.Create;
  F.Prirad(1);
  for I := 2 to N do
  begin
    F.Nasob(I);
    if (I = 10) or (I = 100) or (I = N) then
      WriteLn(I, '! = ', F.Vypis);
  end;
  F.Free;
  ReadLn;
end.

Alebo N-ty člen Fibonacciho postupnosti:

const
  N = 1000;
 
var
  F1, F2, F3: TVelkeCislo;
  I: Integer;
begin
  F1 := TVelkeCislo.Create;
  F2 := TVelkeCislo.Create;
  F3 := TVelkeCislo.Create;
  F1.Prirad(0);
  F2.Prirad(1);
  for I := 1 to N - 1 do
  begin
    F3.Kopiruj(F1);
    F1.Kopiruj(F2);
    F2.Scitaj(F3);
  end;
  WriteLn('Fib(', N, ') = ', F2.Vypis);
  F1.Free;
  F2.Free;
  F3.Free;
  ReadLn;
end.

Ďalšie námety:

  • vypíšte mocniny čísla 2 do 100
  • vypíšte mocniny čísla 3 do 100


späť | ďalej