Třídy v C++

Přehled informací

Vymezení pojmu třída

Třídou se v C++ rozumí programová datová struktura. Uvnitř třídy můžete seskupit:

Přístup k seskupeným prvkům třídy mohou upravovat specifikátory přístupu. Třídy mezi sebou mohou vytvářet hierarchické struktury. Vhodným příkladem jsou například třídy v objektovám modelu AutoCADu. Například třída Application obsahuje data - objekty aplikace (Document, Collection ..), metody pro práci s těmito daty (ActiveDocument ..).

Vytvoření objektu třídy

Při psaní aplikací v C++ pro ovládání AutoCADu se často setkáte s vytvářením objektů tříd. Objekt se vytvoří podle schématu.

Syntaxe deklarace třídy

  klíč identifikátor_třídy [:seznam_předků]
  {
    deklarace_prvků
  } [instance1, instance2, ...];

Argumenty deklarace třídy

klíč
Povinná. Jedno z klíčových slov class, struct, union (strukt a union jsou považovány za speciální typy tříd).
identifikátor_třídy
Povinná. Identifikátor (jméno) deklarované třídy.
:seznam_předků
Seznam identifikátorů tříd, jejichž vlastnosti má nová třída zdědit. Přístup odvozené třídy k zděděným prvkům je možné upravit pomocí specifikátorů přístupu: public, protected a private.

Položky v :seznamu_předků mají následující syntaxi:

[virtual][specifikátor_přístupových_práv] jméno_bázové_třídy

Překladač musí znát definice všech bázových tříd, aby dokázal vytvořit strukturu instance odvozené třídy.

deklarace_prvků
Deklarace jednotlivých prvků třídy.
instance
Nepovinné, definice proměnných, ukazatelů na ně atd. tohoto typu (vytvářeného typu třída). Lze je definovat i později.

Jednotlivé prvky třídy

Datové složky

Každá instance třídy dostane k dispozici paměť pro své datové složky. Instance se po vytvoření zároveň inicializují - datovým položkám se přiřadí hodnoty. Přiřazení hodnoty má za úkol speciální metoda tzv. KONSTRUKTOR. Třídy tvoří zároveň oblast platnosti datových prvků. Datové prvky zanikají spolu se svou instancí. O správný zánik se stará speciální metoda DESTRUKTOR.

Charakteristické vlastnosti datových složek:

Členské funkce

Členské funkce by se daly přirovnat k "způsobu chování" objektů reálného světa, tzn. členské funkce dotváří chování objektu třídy.

Charakteristické vlastnosti členských funkcí:

virtual void jméno_funkce();

Výhodou virtuálních funkcí je, že objekty sdílející společnou základní třídu se mohou používat stejným způsobem. Například je možné definovat třídu s názvem Krivka s virtuální členskou funkcí Kresli a poté z ní odvodit třídy Kruznice a Obdelnik, každý s vlastní členskou funkcí Kresli. Každá instance objektu z těchto tříd volá členskou funkci Kresli. Kompilátor zajistí volání správné funkce Kresli.

Příklad použití virtuální funkce

class Zakladni
{
  public:
    virtual void Zobraz() {cout<<100<<"\n";}
};

class Odvozena
{
  public:
    virtual void Zobraz() {cout<<200<<"\n";}     
     // přetížení funkce definované v třídě Zakladni
};

void Tiskni(Zakladni* zt)      // vytisknutí jednotlivých hodnot
{
  zt->Zobraz();
}

void main()
{
  Zakladni* pZakladni = new Zakladni;
      // vytvoření objektu Zakladni třída
  Odvozena* pOdvozena = new Odvozena;
      // vytvoření objektu Odvozena třída
  Tisk(pZakladni);
      // vytisknutí hodnoty z funkce Zobraz Zakladni třídy
  Tisk(pOdvozena);
      // vytisknutí hodnoty z funkce Zobraz Odvozené  třídy
}

Poznámka - definice metody třídy mimo třídu

Programovací jazyk C++ umožňuje uvést definici metody třídy mimo vlastní třídu. Třída musí obsahovat deklaraci metody. Hlavička definice funkce má tvar:

  datový_typ_metody jméno_třídy::jméno_metody([parametry])
  {
    tělo_metody
  }

Datový typ metody musí být stejný jako typ uvedený v deklaraci metody ve třídě.

Příklad definice metody třídy mimo třídu

  class auto
  {
    public:
      auto(char* vyr, char* t, int rych, int c) 
      { 
        vyrobce = vyr; 
        typ = t; 
        rychlost = rych; 
        cena = c;
      }
      auto() 
      { 
        vyrobce = "neznamy"; 
        typ = "neznamy";
        rychlost = 0; 
        cena = 0;
      }
      ~auto(){};
      char* vyrobce;
      char* typ;
      int rychlost;
      int cena;
      void tisk_informaci();
  };

  void auto::tisk_informaci()
  {
    cout << "Vyrobce: " << vyrobce << 
    ", typ: " << typ <<  
    ", rychlost: " << rychlost << 
    ", cena: " << cena << "\n";
  }

Speciální metoda konstruktor

Konstruktor je metoda, která inicializuje datové prvky instance, volá se automaticky. Konstruktorů může být ve třídě definováno libovolné množství (musí mít smysl!).

Syntaxe deklarace metody konstruktor

  jméno_třídy ([seznam_parametrů])[:inicializační_část]
    tělo_konstruktoru

Základní pravidla při vytváření konstruktorů:

Kopírovací konstruktor

Kopírovací konstruktor slouží ke kopírování objektů (inicializuje instanci třídy pomocí jiné instance této třídy). Kopírovací konstruktor je volán s jedním parametrem typu reference na danou třídu.

Syntaxe deklarace kopírovacího konstruktoru

  identifikátor_třídy(identifikátor_třídy&)

Pokud není ve třídě deklarován vlastní kopírovací konstruktor, překladač vytvoří standardní kopírovací konstruktor, který překopíruje jednotlivé složky a použije k tomu jejich kopírovací konstruktory (prostě přenese složky základních typů). Při tomto postupu však může dojít k chybám, proto je lepší vytvořit kopírovací konstruktor vlastní.

Příklad vytvoření vlastního konstruktoru

  #include <iostream.h>
  class pole
  {
    int delka;
    int* p;
   public:
    pole(int _delka): delka(_delka) {p = new int[delka];}
    pole(pole&);        // deklarace kopírovacího konstruktoru
    ~pole() {delete p;}
    // další metody
  }

  pole::pole(pole& P):delka(P.delka)  
  {                         // definice kopírovacího konstruktoru
    p = new int[delka];
    for(int i = 0; i < delka; i++)
    p[i] = P.p[i];
  }

  void main()
  {
    pole a(7);
    pole b(a); // použije se kopírovací konstruktor
  }

Kopírovací konstruktor zjistí,že ukazatel p bude v různých instancích ukazovat na různá pole.

Speciální metoda destruktor

Destruktor je metoda, volaná automaticky při zániku instance. Destruktor se stará o správný zánik instance.

Syntaxe deklarace metody destruktor

  ~jméno_třídy ()  tělo_destruktoru

Základní pravidla při vytváření destruktorů


Přístupová práva k prvkům třídy

Způsob přístupu k jednotlivým prvkům třídy určují následující specifikátory:

Zřizování instancí třídy

Aby bylo možné s třídou v programu pracovat je nutné vytvořit tzv. proměnnou nebo konstantu instance třídy. Při vytváření instance vznikne konkrétní objekt dané třídy, vyhradí se mu paměť pro jeho datové složky a pak se jim přiřadí hodnoty. Při zřizování instance třídy se vždy volá konstruktor.

Příklad vytvoření instance třídy

  class auto
  {
    public:
      auto(char* vyr, char* t, int rych, int c)
      {
        vyrobce = vyr; 
        typ = t; 
        rychlost = rych; 
        cena = c;
      }
      auto() 
      { 
        vyrobce = "neznamy"; 
        typ = "neznamy"; 
        rychlost = 0; 
        cena = 0;
      }
      ~auto(){};
      char* vyrobce;
      char* typ;
      int rychlost;
      int cena;
  };

  auto Octavie;
    // vytvoření proměnné typu třída auto konstruktorem bez parametrů
  auto Felicie("Felicie", "Škoda", 156, 240000);                     
    // vytvoření proměnné typu třída auto konstruktorem s parametry

Poznámka ukazatel this

Každá instance třídy má vlastní kopii nestatických datových složek třídy. Naproti tomu metody třídy zůstávají uloženy v paměti počítače pouze jednou a používají je všechny instance společně. Aby mohla metoda pracovat s daty aktuální instance, předá ji překladač jako dodatečný parametr ukazatel na tuto (aktuální) instanci - ukazatel this. Ukazatel this se předává jako skrytý parametr, tj. nedeklaruje se v programu.

V jazyce C++ je ukazatel this využíván členskými funkcemi tříd při volání jiných funkcí, kde jim tento parametr předávají jako jeden z argumentů. Volaná funkce potom použije takto předaný parametr při zpětném volání funkcí objektu.

Příklad použití ukazatele this

  class trida
  {
      ....
    public:
      int hodnota;
      int funkce();
      ....
  }

  int trida::funkce()
  {
    return hodnota;
  }

Vrácení návratové hodnoty z metody funkce příkazem return je v podstatě volání následujícího příkazu:

return this->hodnota;

Přístup k metodám a proměnným třídy

Přístup k metodám a proměnným třídy v těle metod

V těle členských funkcí manipulujeme s ostatními složkami třídy bez jakýchkoliv omezení. Používáme při tom přímo jména složek:

  class ukazka
  {
    private:
      int operand1, operand2;
      int funkce1()
        return operand1+operand2;}
         // ukázka přístupu uvnitř třídy, všimněte si, že
         // není uvedena instance pouze jméno proměnných
      ....
  }

Přístup k metodám a proměnným třídy z vnějšku třídy

Při přístupu ke složkám třídy z vnějšku je nutné uvést jméno instance jejíž složky budeme používat. Způsob přístupu ke složkám třídy:

  jméno_instance.jméno_složky;
  ukazatel_na_instanci->jméno_složky;

Složka třídy, s kterou budeme manipulovat může být jednak datová složka nebo členská proměnná.

Příklad přístupu přes instanci

Přístup z vnějšku je regulován přístupovými právy (public, protected a private). Prvky, které jsou deklarovány jako chráněné nebo soukromé jsou pomocí přístupu přes instanci přístupné pouze ve spřátelených funkcích.

  class ukazka
  {
    private:
      int operand1;
      int funkce1() {...} 
    public:
      int operand2;
      int funkce2() {...} 
  };

  void main()
  {
    ukazka u, *ptr_u;
    int x;
    x = u.operand1;       // chyba operand1 je private - soukromá
    x = u.funkce1();      // chyba funkce1 je private - soukromá
    x = ptr_u->operand1;  // chyba operand1 je private - soukromá
    x = ptr_u->funkce1(); // chyba funkce1 je private - soukromá
    x = u.operand2;             
    x = u.funkce2();              
    x = ptr_u->operand2;   
    x = ptr_u->funkce2();
  }

Dědičnost

Představuje jeden ze základních rysů objektově orientovaného programování. Dědění tříd umožňuje definovat nové třídy na základě tříd, které již existují.

Třídy, od kterých odvozujeme nové třídy jsou nazývány rodičovské třídy. Odvozené třídy jsou označovány jako potomci nebo dceřinné třídy. Skupiny provázaných tříd (vztahem předek-potomek) jsou nazývány dědické hierarchie.

Odvozená třída "zdědí" vlastnosti svých předků - bude obsahovat všechny nestatické datové složky svých předků a bude moci používat jejich metody, typy a statické složky (pokud jí to dovolí přístupová práva).

Dědění se využívá v OOP pro vyjádření specifikace: Předek popisuje abstraktnější pojem, zatím co potomek popisuje konkrétnější, přesněji vymezený pojem. Z toho plyne důležité pravidlo:

!Potomek může zastoupit předka, nikoli naopak!

Syntaxe deklarace dědičné třídy

  klíč identifikátor_třídy : SEZNAM_RODIČOVSKÝCH_TŘÍD
  {
    Složky_třídy
  };

Argumenty deklarace dědičné třídy

klíč
Povinná. Jedno z klíčových slov class, struct (union nemohou být předkem ani potomkem žádné třídy).
identifikátor_třídy
Povinná. Identifikátor (jméno) deklarované třídy.
:SEZNAM_RODIČOVSKÝCH_TŘÍD
Seznam identifikátorů tříd, jejichž vlastnosti má nová třída zdědit. Přístup odvozené třídy k zděděným prvkům je možné upravit pomocí specifikátorů přístupu: public, protected a private.

Položky v :seznamu_předků mají následující syntaxi:

  [virtual][specifikátor_přístupových_práv] jméno_bázové_třídy
  [specifikátor_přístupových_práv][virtual] jméno_bázové_třídy

Překladač musí znát definice všech bázových tříd, aby dokázal vytvořit strukturu instance odvozené třídy.

Složky_třídy
Deklarace jednotlivých prvků třídy.

Poznámka - deklarace rodičovské třídy

Rodičovská třída musí být deklarována před tím než ji použijeme k dědění.