14. díl - Mág do objektové arény v C++

C a C++ C++ Objektově orientované programování Mág do objektové arény v C++

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Dědičnost v C++, jsme si vysvětlili, co je to dědičnost a jak ji využít. V dnešním C++ tutoriál si vytvoříme třídu Mag, kterého si později přidáme do arény.

Vytvoření mága

Než začneme něco psát, shodněme se na tom, co by měl mág umět. Mág bude fungovat stejně jako bojovník. Kromě života bude mít však i manu. Zpočátku bude mana plná. V případě plné many může mág vykonat magický útok, který bude mít pravděpodobně vyšší damage, než útok normální (ale samozřejmě záleží na tom, jak si jej nastavíme). Tento útok manu vybije na 0. Každé kolo se bude mana zvyšovat o 25 a mág bude podnikat jen běžný útok. Jakmile se mana zcela doplní, opět bude moci magický útok použít. Mana bude zobrazena grafickým ukazatelem, stejně jako život.

Vytvoříme si novou třídu Mag, podědíme ji ze třídy Bojovnik a přidáme jí atributy, které bude mít oproti bojovníkovi navíc. K atributům samozřejmě přidáme gettery.

Mag.h

#ifndef __MAG_H_
#define __MAG_H_
#include "Bojovnik.h"

class Mag : public Bojovnik
{
private:
    int mana;
    int max_mana;
public:
    float magicky_utok;
    int getMana();
    float getMagickyUtok();
};

#endif

V mágovi nemáme zatím přístup ke všem proměnným, protože jsou v bojovníkovi nastavené jako privátní. Musíme třídu Bojovnik lehce upravit. Změníme modifikátory private u atributů na protected. Prozatím nám stačí nastavit na protected pouze atribut kostky. Zbytek atributů zatím nepotřebujeme a proto je necháme privátní. Obecně je pravidlem co nejvíce omezit přístup k proměnným, proto viditelnost změníme, až když to potřebujeme. Třída Bojovnik tedy bude vypadat nějak takto:

Bojovnik.h

class Bojovnik
{
private:
    float zivot;
    float max_zivot;
    float utok;
    float obrana;
protected:
    Kostka &kostka;
public:
    Bojovnik() = delete;
    Bojovnik(float zivot, float utok, float obrana, Kostka &kostka);
    Bojovnik(const Bojovnik& bojovnik) = default;
    ~Bojovnik() = default;
    Bojovnik& operator = (const Bojovnik& bojovnik);
    bool nazivu() const;
    float utoc(Bojovnik &druhy) const;
    float getZivot() const;
    float getMaxZivot() const;
    float getUtok() const;
    float getObrana() const;
    float operator > (Bojovnik& druhy) const;
    float operator < (const Bojovnik& druhy);
};

Konstruktory

C++ nedědí konstruktory! Je to pravděpodobně z toho důvodu, že předpokládá, že potomek bude mít navíc nějaké atributy a původní konstruktor by u něj byl na škodu. To je i náš případ, protože konstruktor mága bude brát oproti tomu z bojovníka navíc 2 parametry (mana a magický útok).

Definujeme si tedy konstruktor v potomkovi, který bere parametry potřebné pro vytvoření bojovníka a několik parametrů navíc pro mága.

V konstruktorech potomků je nutné vždy volat konstruktor předka. Je to z toho důvodu, že bez volání konstruktoru nemusí být instance správně inicializovaná. Konstruktor předka nevoláme pouze v případě, že má pouze bezparametrický (ten se zavolá automaticky). Náš konstruktor musí mít samozřejmě všechny parametry potřebné pro předka plus ty nové, co má potomek navíc. Některé potom předáme předkovi a některé si zpracujeme sami. Konstruktor předka se vykoná před naším konstruktorem. Jak volat konstruktor předka už víme z minula.

Mag.cpp

Mag::Mag(float zivot, float utok, float obrana, int mana, int magicky_utok, Kostka & kostka)
    : Bojovnik(zivot, utok, obrana, kostka), mana(mana), max_mana(mana), magicky_utok(magicky_utok)
{
}

Dále potřebujeme metodu, která mágovi obnoví část many. Ačkoliv by šlo přidat setter pro manu a nový počet many počítat někde jinde, ztratili bychom tím zapouzdření, o které se snažíme. Vytvoříme si proto metodu obnov(), která se o výpočet postará.

void Mag::obnov()
{
    this->mana += 25;
    if (this->mana > this->max_mana)
        this->mana = this->max_mana;
}

Překrývání metod

Poslední věc, kterou potřebujeme, je dovolit mágovi útočit. Mág nyní disponuje metodou utoc(), kterou podědil ze třídy Bojovnik. Její implementace nám ale nevyhovuje, protože nepoužívá manu. Tuto metodu musíme znovu naimplementovat pro mága - tomu se říká překrytí metody. Nadefinujeme si metodu se stejnou signaturou (se stejným názvem a parametry) a změníme její chování. Pokud se zavolá metoda utoc() na mágovi, potom se nezavolá ta ze třídy Bojovnik, ale ta ze třídy Mag.

float Mag::utoc(Bojovnik& druhy) const
{
    if (this->mana == this->max_mana)
    {
        float utok = this->magicky_utok + this->kostka.hod();
        druhy.zivot -= utok;
        this->mana = 0;
        return utok;
    }
}

Pokud tento kód napíšete do Visual Studia, podtrhne vám část druhy.zivot. Pokud je parametrem bázová třída, metoda nemá přístup k jejím privátním a chráněným (protected) atributům. To spravíme přidáním veřejného setteru do bojovníka, který bude nastavovat životy. Dále se Visual Studiu nebude líbit, že měníte počet many aktuálního objektu. To je tím, že je funkce deklarovaná jako konstantní. Řešením je odstranit const za metodou, ale musíme to provést i ve třídě Bojovnik a na operátorech, které jsme si zadefinovali pro útok.

Co ale dělat, když mág nemá dostatek many? Chtěli bychom, aby se zavolala metoda utoc() ze třídy bojovník, protože její implementace je dostatečná. Provedeme to pomocí syntaxe NazevTridy::nazevMetody. Ve finále naše implementace vypadá následovně:

Mag.cpp

float Mag::utoc(Bojovnik & druhy)
{
    if (this->mana == this->max_mana)
    {
        float utok = this->magicky_utok + this->kostka.hod();
        druhy.setZivot(druhy.getZivot() - utok);
        this->mana = 0;
        return utok;
    }
    return Bojovnik::utoc(druhy); //volání metody ze třídy Bojovnik
}

Bojovnik.h

class Bojovnik
{
private:
    float max_zivot;
    float utok;
    float obrana;
    float zivot;
protected:
    Kostka &kostka;
public:
    Bojovnik() = delete;
    Bojovnik(float zivot, float utok, float obrana, Kostka &kostka);
    Bojovnik(const Bojovnik& bojovnik) = default;
    ~Bojovnik() = default;
    Bojovnik& operator = (const Bojovnik& bojovnik);
    bool nazivu() const;
    float utoc(Bojovnik &druhy);
    float getZivot() const;
    float getMaxZivot() const;
    float getUtok() const;
    float getObrana() const;
    void setZivot(float zivot);
    float operator > (Bojovnik& druhy);
    float operator < (Bojovnik& druhy);
};
ostream& operator << (ostream &str, const Bojovnik &bojovnik);

To, co jsme právě provedli, se nazývá překrytí metody. Nahradili jsme metodu v bázové třídě naší vlastní implementací, která se nám hodí pro danou třídu.

Skrývání metod

V C++ funguje ještě tzv. skrývání metod. To nastane v situaci, kdy máme v bázové třídě přetížené (ne překryté) metody. Jak víme, přetížená metoda je taková metoda, která má stejný název ale rozdílné parametry. Pokud z takové třídy podědíme, máme k dispozici všechny tyto metody. Pokud ale nadefinujeme překrytí jedné z těchto metod, potom nebudeme mít přístup ke zbývajícím přetížením. Říkáme, že metoda v odvozené třídě skryla metody z bázové.

class A
{
public:
     int metoda(int x);
     int metoda(int x, int y);
};

class B : public A
{
public:
     int metoda(int x); // přetížení skrylo metodu z bázové třídy
};

int main()
{
    B b;
    b.metoda(5); // validní
    b.metoda(5,4); // nevalidní, metoda je skrytá
}

V našem projektu nemáme situaci, kdy by to mohlo nastat, proto je tu jen obecný zjednodušený příklad. Je důležité s tím počítat, až budete překrývat metody.

Boj mágů

Před tím, než si přidáme mágy do arény, zkusíme jejich boj mezi sebou. Do main.cpp includujeme soubor Mag.h a v metodě main() si zkusíme simulovat jejich souboj:

int main()
{
    Kostka kostka;
    Mag prvni(100,3,1,100,30,kostka);
    Mag druhy(100, 3, 1, 100, 30, kostka);

    while (prvni.nazivu() && druhy.nazivu())
    {
        cout << "Prvni utoci: " << prvni.utoc(druhy) << endl;
        if (druhy.nazivu())
            cout << "Druhy utoci: " << druhy.utoc(prvni) << endl;
        prvni.obnov();
        druhy.obnov();
    }

    if (prvni.nazivu())
        cout << "Prvni mag vyhral souboj";
    else
        cout << "Druhy mag vyhral souboj";
    return 0;
}
Konzolová aplikace
Prvni utoci: 36
Druhy utoci: 33
Prvni utoci: 1
Druhy utoci: 6
Prvni utoci: 0
Druhy utoci: 5
Prvni utoci: 0
Druhy utoci: 5
Prvni utoci: 31
Druhy utoci: 31
Prvni utoci: 1
Druhy utoci: 2
Prvni utoci: 3
Druhy utoci: 0
Prvni utoci: 5
Druhy utoci: 3
Prvni utoci: 33
Prvni mag vyhral souboj

Tím máme mága připraveného, ale ještě nám něco schází. Dědičností jsme řekli, že mág je nějaký bojovník, ale ještě se tak k němu nemůžeme chovat. Také si všimněte, že i když metoda utoc() přijímá jako parametr referenci na Bojovnik, stejně jsme mohli předat mága jako parametr. To je již základ polymorfismu, o kterém si povíme v příští lekci Polymorfismus v C++.


 

Stáhnout

Staženo 2x (12.87 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

 

Článek pro vás napsal patrik.valkovic
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Miniatura
Předchozí článek
Dědičnost v C++
Miniatura
Následující článek
Polymorfismus v C++
Aktivity (5)

 

 

Komentáře

Avatar
dan.azo
Člen
Avatar
dan.azo:4. září 14:16

Děkuji za tutoriál :).
Nemělo by ale být místo ,,Pokud ale nadefinujeme přetížení..." napsáno ,,Pokud ale nadefinujeme PŘEKRYTÍ..." ??
Díky :)

Editováno 4. září 14:17
 
Odpovědět 4. září 14:16
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na dan.azo
patrik.valkovic:4. září 14:58

Těžko říct, myslím že v tomto případě může být oboje, ale opravil jsem to na překrytí.

Odpovědět 4. září 14:58
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.