Kirjautuminen

Haku

Tehtävät

Oppaat: Pascal-ohjelmointi: Osa 7 - Objektit ja luokat

  1. Osa 6 - Osoittimet
  2. Osa 7 - Objektit ja luokat

Kirjoittaja: Metabolix (2004).

Yleiskatsaus luokkien sisältöön

Luokat ovat Object Pascalin tuoma lisäys Pascaliin. Ne eivät siis toimi kaikilla kääntäjillä, ja kääntäjissä on muitakin eroja näiden suhteen.

Tietueiden tapaan myös luokat (class tai object) sisältävät muita muuttujia. Tietueista poiketen luokat sisältävät lisäksi myös omia aliohjelmia ja funktioita. Uuden luokan voi luoda joko kokonaan alusta asti tai vanhaa apuna käyttäen eli niin sanotusti perimällä aiemmasta luokasta, jolloin uudelle luokalle tulee kaikki aiemmankin ominaisuudet. Jokainen luokan jäsen määritellään kuuluvaksi johonkin viidestä vaihtoehtoisesta ryhmästä riippuen siitä, missä ohjelman osissa halutaan käyttää mitäkin ominaisuutta. Kolme yleisintä vaihtoehtoa ovat julkiset, kaikille näkyvät ominaisuudet (public), yksityiset, vain samassa moduulissa näkyvät ominaisuudet (private), ja suojatut, samassa moduulissa ja muiden moduulien perityillä luokilla näkyvät ominaisuudet (protected). Kaksi erikoisempaa ovat automated ja published, joita ei käsitellä tässä. Jokaisen luokan välttämättömät ominaisuudet ovat luojafunktio eli constructor ja tuhoajafunktio eli destructor, jolla se tuhotaan. Nämä on syytä sijoittaa kaikkien näkyville.

Yleensä luokan omat muuttujat ja avustavat funktiot ja aliohjelmat määritellään niin suojatuiksi kuin mahdollista. Tämä siksi, ettei niitä vahingossa käytettäisi tuhoisin seurauksin. Muuttujat on tapana asettaa näkyville luomalla suojattu muuttuja toisella nimellä, vaikkapa "FMuuttuja", ja lisäämällä public-määrittelyihin vastaava muuttuja oikealla nimellään määritelmällä property. Tälle muuttujalle voidaan määrätä erikseen paikka, johon siihen kirjoitettava tieto kirjoitetaan ja paikka, josta se luetaan. Muuttuja voidaan määritellä myös vain luettavaksi tai kirjoitettavaksi.

Kattava esimerkki luokkien luomisesta

unit Unit1;

interface

type
  (* Tässä esitellään TIhminen eli ilmoitetaan,
   * että myöhemmin määritellään luokka TIhminen. *)
  TIhminen = class;

  (* Määritellään luokka TAivot *)
  TAivot = class
  (* Aloitetaan suojatut määrittelyt *)
  protected
    (* Määritellään suojattu ominaisuus Parent, joka viittaa olioon TIhminen.
       Tätä varten TIhminen piti esitellä jo aikaisemmin. *)
    Parent: TIhminen;

    (* Määritellään myös FToimintaaTapahtunut ja FTehokerroin suojattuina *)
    FToimintaaTapahtunut, FTehokerroin: Single;

  (* Aloitetaan julkiset määrittelyt *)
  public
    (* Määritellään aliohjelma Aivotoiminta *)
    procedure Aivotoiminta(Tehokkuus: Integer; Sekuntia: Single);

    (* Määritellään julkinen ominaisuus Tehokerroin
     * Koska tälle on määritelty vain write, tiedon lukeminen on mahdotonta.
     * Kirjoittaminen tapahtuu muuttujaan FTehokerroin *)
    property Tehokerroin: Single write FTehokerroin;

    (* Määritellään julkinen ToimintaaTapahtunut. Tätä voi vain lukea *)
    property ToimintaaTapahtunut: Single read FToimintaaTapahtunut;

    (* Määritellään luojafunktio, joka saa parametrinään Parent-ominaisuuden *)
    constructor Create(AParent: TIhminen);

    (* Määritellään tuhoajafunktio. Tuhoajan perään laitetaan yleensä 'override'
     * jolloin se ohittaa kaikki aiemmat tuhoajafunktiot. *)
    destructor Destroy; override;
  end;

  (* Määritellään luokka TIhminen *)
  TIhminen = class
  (* Aloitetaan yksityiset määrittelyt *)
  private
    (* Määritellään yksityinen muuttuja Henkilotunnus *)
    Henkilotunnus: String;

    (* Määritellään suojattu muuttuja Aivot *)
    Aivot: TAivot;

  (* Aloitetaan suojatut määrittelyt *)
  protected

    (* Määritellään ominaisuudet Energia ja MaxEnergia *)
    Energia, MaxEnergia: Single;

  (* Aloitetaan suojatut määrittelyt *)
  public
    (* Määritellään julkinen ominaisuus Parstakerroin *)
    Parstakerroin: Single;

    (* Määritellään julkinen aliohjelma Ajattele *)
    procedure Ajattele(Tehokkuus, Sekuntia: Integer);

    (* Määritellään julkinen funktio AjattelunMaara *)
    function AjattelunMaara: Single;

    (* Määritellään luojafunktio, jolle kerrotaan, luodaanko Aivot *)
    constructor Create(OnkoAivot: Boolean);

    (* Määritellään tuhoajafunktio *)
    destructor Destroy; override;
  end;

implementation

procedure TAivot.Aivotoiminta(Tehokkuus: Integer; Sekuntia: Single);
begin
  (* Aivotoiminta vähentää Parent-olion Energia-ominaisuutta *)
  if (Parent <> nil) then
    Parent.Energia := Parent.Energia - Tehokkuus * Sekuntia;

  (* Moduulin sisäisesti voidaan käyttää muuttujaa FTehokerroin *)
  FToimintaaTapahtunut := FToimintaaTapahtunut
    + Tehokkuus * Sekuntia * FTehokerroin;
end;

constructor TAivot.Create(AParent: TIhminen);
begin
  (* Asetetaan Parent-muuttujan arvoksi annettu TIhminen *)
  Parent := AParent;

  (* Ja muille muuttujille lähtöarvot *)
  FToimintaaTapahtunut := 0;
  FTehokerroin := 1;
end;

destructor TAivot.Destroy;
begin
  (* Jos isäntäolio on vielä olemassa, kerrotaan, että Aivot on tuhottu.
   * Vaikka Aivot on määritelty yksityisiksi, niitä voidaan tämän moduulin
   * sisällä käyttää. Tarkistus olemassaolosta on välttämätön, koska muuten
   * ohjelma voi kaatua yrittäessään muokata olematonta ominaisuutta. *)
  if (Parent <> nil) then Parent.Aivot := nil;
end;

constructor TIhminen.Create(OnkoAivot: Boolean);
begin
  (* Luodaan aivot, jos niin on käsketty. Luojafunktion syötteenä on TIhminen.
   * Näin jokaiselle saadaan omat Aivot luontivaiheessa, mikäli niin halutaan *)
  if (OnkoAivot = True) then
    Aivot := TAivot.Create(Self);

  (* Muita lähtöarvoja *)
  Parstakerroin := Random(100) / 100;
  Energia := 10;
  MaxEnergia := 100;
end;

destructor TIhminen.Destroy;
begin
  (* Jos Aivot on olemassa, tuhotaan ne *)
  if (Aivot <> nil) then Aivot.Destroy;
end;

procedure TIhminen.Ajattele(Tehokkuus, Sekuntia: Integer);
begin
  (* Kutsutaan Sisäisen muuttujan Aivot Aivotoiminta-ominaisuutta *)
  if Aivot <> nil then
    Aivot.Aivotoiminta(Tehokkuus, Sekuntia);
end;

function TIhminen.AjattelunMaara: Single;
begin
  (* Asetetaan palautusarvoksi tapahtuneen toiminnan määrä tai 0 *)
  if Aivot <> nil then
    Result := Aivot.FToimintaaTapahtunut
  else
    Result := 0;
end;

end.
unit Unit2;

interface

(* Ilmoitetaan, että käytetään myös toista moduulia *)
uses
  Unit1;

type
  (* Määritellään myöhemmin käytettävä numeroitu tyyppi TKummatAivot *)
  TKummatAivot = (TIhmisenAivot, TOpettajanAivot, TMolemmatAivot);

  (* Määritellään luokka TOpettaja, joka on peritty perusluokasta TIhminen *)
  TOpettaja = class (TIhminen)
  private
    (* Määritellään Aivot. Aiempi määrittely luokassa TIhminen ei haittaa,
     * koska se määriteltiin yksityiseksi eikä sitä siis voi käyttää. *)
    Aivot: TAivot;
  public
    (* Sana 'overload' mahdollistaa usean samannimisen aliohjelman
     * määrittelemisen siten, että niillä on erilaiset parametrit.
     * Näin saamme aikaan uuden aliohjelman Aivotoiminta, jonka para-
     * metreihin kuuluu myös se, kumpia aivoja käytetään *)
    procedure Ajattele(Tehokkuus, Sekuntia: Integer;
                       Kummat: TKummatAivot); overload;

    (* Määritellään uudestaan funktio AjattelunMaara,
     * jotta saadaan molempien aivojen ajattelut *)
    function AjattelunMaara: Single;

    constructor Create;
    destructor Destroy; override;
  end;

  TNarkkari = class (TIhminen)
  private
    SisainenMina: TIhminen;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

constructor TOpettaja.Create;
begin
  (* Kutsutaan perittyä Create-luojafunktiota, mutta annetaan
   * OnkoAivot-parametriksi True kaikesta riippumatta. *)
  inherited Create(True);

  (* TOpettaja sisältää nyt kahdet aivot, mutta vain toisia voi käyttää.
   * Ne pitää luoda erikseen, koska peritty Create luo vain vanhat. *)
  Aivot := TAivot.Create(Self);

  (* Asetetaan aivojen tehokertoimeksi 2 *)
  Aivot.Tehokerroin := 2;
end;

destructor TOpettaja.Destroy;
begin
  (* Yleensä perittyä tuhoajafunktiota kutsutaan vasta tuhoamisfunktion lopussa.
   * Tässä tapahtuu kuitenkin nyt poikkeus, koska mikäli tuhoaisimme nyt
   * TOpettaja-olion Aivot-olion, se asettaisi TIhminen-luokalta perittyyn
   * Aivot-muuttujaan arvoksi 'nil' eli tyhjä, jolloin peritty Aivot-olio
   * jäisi tuhoamatta. *)
  inherited Destroy;

  (* Tuhotaan uudet aivot *)
  Aivot.Destroy;
end;

procedure TOpettaja.Ajattele(Tehokkuus, Sekuntia: Integer;
                             Kummat: TKummatAivot);
begin
  (* Katsotaan, kumpia aivoja pitää käyttää, ja käytetään niitä. *)
  case Kummat of
    (* Vanha Ajattele-aliohjelma ottaa vain kaksi parametriä *)
    TIhmisenAivot:
      Ajattele(Tehokkuus, Sekuntia);

    TOpettajanAivot:
    begin
      (* Tässä tapauksessä käytetään uutta Aivot-oliota *)
      if Aivot <> nil then
        Aivot.Aivotoiminta(Tehokkuus, Sekuntia);
    end;

    TMolemmatAivot:
    begin
      (* Tässä tehdään kummatkin *)
      Ajattele(Tehokkuus, Sekuntia);
      if Aivot <> nil then
        Aivot.Aivotoiminta(Tehokkuus, Sekuntia);
    end;
  end;
end;

function TOpettaja.AjattelunMaara: Single;
begin
  (* Haetaan perityn Aivot-olion ajattelun määrä *)
  Result := TIhminen(Self).AjattelunMaara;

  (* Lisätään siihen uusi määrä *)
  if Aivot <> nil then
    Result := Result + TOpettaja(Self).Aivot.ToimintaaTapahtunut;
end;

constructor TNarkkari.Create;
begin
  (* Kutsutaan perittyä Create-luojafunktiota, mutta annetaan
   * OnkoAivot-parametriksi False kaikesta riippumatta. *)
  inherited Create(False);

  (* Luodaan TNarkkarille SisainenMina.
   * Sen luomisfunktiolle annamme parametrin True *)
  SisainenMina := TIhminen.Create(True);

  (* Muutetaan Maksimaalista energiamäärää pienemmäksi *)
  MaxEnergia := MaxEnergia / 10;
end;

destructor TNarkkari.Destroy;
begin
  (* Tuhotaan SisainenMina *)
  SisainenMina.Destroy;

  (* Kutsutaan perittyä tuhoajafunktiota *)
  inherited Destroy;
end;

end.

Luokkien käyttäminen

Luokat toimivat hieman eri tavalla kuin tavalliset muuttujat. Vaikka ne määritelläänkin samalla tavalla kuin muutkin muuttujat, ne täytyy erikseen luoda käyttäen niiden luojafunktiota, joka yleensä on nimeltään Create. Luokan käyttäminen ennen tätä aiheuttaa virheen. Muuttuja, jonka avulla luokkaa käytetään, onkin tyypiltään itse asiassa osoitin. Osoitinjärjestelmästä johtuen luokan tietoja ei voi kopioida toiseen luokkaan suoraan := -merkillä. Tällöin ensimmäinen osoitin muutettaisiin osoittamaan samaan paikkaan kuin jälkimmäinen, ja vaikka tulos ensinäkemältä olisikin sama, niin itse asiassa A:n ominaisuuksien muuttaminen muuttaisikin B:n ominaisuuksia ja päinvastoin. Lisäksi muistiin jäisi ajelehtimaan irrallinen kopio luokasta. Tämä kopio varaisi muistia, mutta olisi mahdoton tuhota.

program Luokat;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Unit1 in 'Unit1.pas',
  Unit2 in 'Unit2.pas';

var
  Matti: TIhminen;
  Pekka: TOpettaja;
  Lassi: TIhminen;
  Narkkari: TNarkkari;

begin
  (* Luodaan henkilöt *)
  Matti := TIhminen.Create(True);
  Pekka := TOpettaja.Create;
  Narkkari := TNarkkari.Create;

  (* Koska TOpettaja on peritty luokasta TIhminen,
   * TIhminen voi olla TOpettaja, mutta ei päin vastoin *)
  Lassi := TOpettaja.Create;

  Matti.Ajattele(10, 10);

  (* Koska Pekka on TOpettaja, voidaan käyttää molempia TAivoja *)
  Pekka.Ajattele(10, 10, TMolemmatAivot);

  (* Vaikka Lassi onkin oikeasti TOpettaja, häntä voi käyttää TIhmisenä *)
  Lassi.Ajattele(10, 10);

  Narkkari.Ajattele(100, 100);

  Writeln('Matti on ajatellut näin paljon: ', FloatToStr(Matti.AjattelunMaara));
  Writeln('Lassikin on ajatellut samoin: ', FloatToStr(Lassi.AjattelunMaara));
  Writeln('Pekka onkin aivan eri tasoinen: ', FloatToStr(Pekka.AjattelunMaara));
  Writeln('Narkkari jäi yrityksistään huolimatta nollaan: ',
          FloatToStr(Narkkari.AjattelunMaara));

  (* Tuhotaan oliot *)
  Matti.Destroy;
  Pekka.Destroy;
  Narkkari.Destroy;

  (* Koska Lassi on oikeasti TOpettaja, pitää hänet tuhota
   * kuin TOpettaja jotta molemmat TAivot tuhotaan *)
  TOpettaja(Lassi).Destroy;
end.

Lauri Kenttä, 19.9.2004


Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta