C a C++ týden ITnetwork Flashka zdarma
Akce! Pouze tento týden sleva až 80 % na kurzy C++. Lze kombinovat s akcí 50 % bodů navíc na prémiový obsah!
Brno? Vypsali jsme pro vás nové termíny školení Základů programování a OOP v Brně!

Lekce 8 - Konstantní metody v C++

Unicorn College 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, Aréna s bojovníky v C++, jsme dokončili naši objektovou arénu. V dnešním tutoriálu zjistíme, co jsou to konstantní metody a proč se jimi zabývat.

Konstantní metody

Jak již název napovídá, konstantní metoda je taková metoda, která nemění data instance. Jsou to všechny gettery - pouze získávají data, nic nemění. Z toho důvodu by správně měly být všechny gettery označeny jako konstantní. Metoda Bojovnik.nazivu() je také svým způsobem getter, protože pouze zjišťuje zda je dostatečný počet životů. Všechny metody, které nemění data, bychom měli označit jako konstantní - to uděláme jednoduše přidáním klíčového slova const za název metody (do .h i .cpp souboru). Například pro třídu Bojovnik:

Bojovnik.h

#ifndef __BOJOVNIK_H_
#define __BOJOVNIK_H_
#include <string>
#include "Kostka.h"

using namespace std;

class Bojovnik
{
private:
    float zivot;
    float max_zivot;
    float utok;
    float obrana;
    Kostka &kostka;
public:
    Bojovnik(float zivot, float utok, float obrana, Kostka &kostka);
    bool nazivu() const;
    float utoc(Bojovnik &druhy) const;
    float getZivot() const;
    float getMaxZivot() const;
    float getUtok() const;
    float getObrana() const;
};
#endif

Bojovnik.cpp

#include "Bojovnik.h"

Bojovnik::Bojovnik(float zivot, float utok, float obrana, Kostka &kostka) :
    kostka(kostka), zivot(zivot), max_zivot(zivot), utok(utok), obrana(obrana)
{}

bool Bojovnik::nazivu() const
{
    return this->zivot > 0;
}

float Bojovnik::utoc(Bojovnik & druhy) const
{
    float obrana_druhy = druhy.obrana + druhy.kostka.hod();
    float utok_prvni = this->utok + this->kostka.hod();
    float zraneni = utok_prvni - obrana_druhy;
    if (zraneni < 0)
        zraneni = 0;
    druhy.zivot -= zraneni;
    return zraneni;
}

float Bojovnik::getZivot() const
{
    return this->zivot;
}

float Bojovnik::getMaxZivot() const
{
    return this->max_zivot;
}

float Bojovnik::getUtok() const
{
    return this->utok;
}

float Bojovnik::getObrana() const
{
    return this->obrana;
}

Všimněte si, že i metoda utoc() je konstantní - nemění data instance, ale mění data parametru.

Pravidla

Proč se tím zabýváme? Klíčové slovo const nám pohlídá, že je metoda správně implementovaná. Pokud bychom pro třídu Kostka nadefinovali metodu setPocetSten() a nastavili ji jako konstantní, kompilátor by nám zahlásil následující hlášku (pro Visual Studio):

error C2228: left of '.pocet_sten' must have class/struct/union
note: type is 'const Kostka *const '
note: did you intend to use '->' instead?

Kompilátor si sám pohlídá, že funkce nemůže nic změnit a ostatní programátory tím informujeme, že data jsou v bezpečí (nemohou se změnit).

Jaká další pravidla platí? Z konstantní metody můžeme volat pouze konstantní metody. To znamená, že ve chvíli, kdy nastavíme getter jako konstantní, nemůžeme z něj zavolat setter (jinak by klíčové slovo const nedávalo smysl - modifikovala by se data).

Existuje ještě třetí a nejdůležitější pravidlo: na konstantní objekty lze volat pouze konstantní metody. Například pro našeho bojovníka. Předpokládáme, že následující kód by měl fungovat:

const Bojovnik bojovnik(100,8,5,kostka);
float utok = bojovnik.getUtok();
float obrana = bojovnik.getObrana();
float uroven_agresivity = utok - obrana;

Proč bychom to očekávali? Ačkoliv je bojovník konstantní, chceme získat útok a obranu pouze pro čtení - to by neměl být problém, protože metody nemění data a konstanta je dodržena. To ale víte vy, ale zatím to neví kompiler. Aby tento kód fungovat, musíte nastavit gettery jako konstantní - kompiler má potom jistotu, že metoda nic nezmění a může být tedy volána nad konstantním objektem.

Ukazatele

Pro ukazatele a reference se situace ještě trochu zkomplikuje. Přidáním klíčového slova const za název metody se stane, že jsou (pro představu) všechny atributy označeny jako konstantní. Pro ukázku si představme následující třídu:

class Uzivatel
{
    int vek;
    char* jmeno;
    void vypisJmeno() const;
};

Uvnitř metody vypisJmeno() bude díky const ukazatel this typu Uzivatel const * const (na rozdíl od klasického Uzivatel * const). Jen pro upřesnění, ukazatele vždy čteme odzadu, tedy Uzivatel const * const je "konstantní ukazatel na konstantního uživatele", zatímco Uzivatel * const je "konstantní ukazatel na uživatele". Také máme dvě možnosti zápisu, tedy následující dvě ukázky jsou ekvivalentní: Uzivatel const * a const Uzivatel *.

Konstantní metodu si lze tedy představit tak, že všechny atributy budou konstantní:

class Uzivatel
{
    const int vek;
    char * const jmeno;
};

Všimněte si typu atributu jmeno. Jedná se o konstantní ukazatel (nemůžeme změnit adresu, kam ukazuje), ale není to ukazatel na konstantní hodnotu - jméno můžeme stále změnit. Konstantní metody nám nezaručí, že se nezmění data instance - zaručí nám pouze to, že se nezmění atributy.

Na problémy dojde i při práci s referencemi. Představme si, že chceme vrátit věk uživatele referencí. Není to obvyklé, ale z nějakého důvodu to tak chceme. Referenci na int použít nemůžeme, protože typ atributu je const int a typy se musí shodovat. Z toho plyne, že musíme použít konstantní referenci:

class Uzivatel
{
    int vek;
    char* jmeno;
    void vypisJmeno() const;
    const int& getVek() const;
};

Nyní by naše část kódu fungovala, a když se nad tím zamyslíme, pak teprve teď funguje správné - respektuje konstantní hodnoty tam, kde to dává smysl.

To by bylo pro dnešní kratší lekci vše. Ve zdrojovém byly přidány konstanty ke getterům a k pár dalším metodám (u kterých to dávalo smysl). Příště budeme s kódem pokračovat a proto bych doporučil stáhnout si zdrojové kódy dole pod článkem. Budete mít jistotu, že budeme pracovat se stejným zdrojovým kódem. Příště, v lekci Statika v C++, nás čeká statika.


 

Stáhnout

Staženo 34x (9.78 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?
2 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Předchozí článek
Aréna s bojovníky v C++
Všechny články v sekci
Objektově orientované programování v C++
Miniatura
Následující článek
Cvičení k 6. až 8. lekci OOP v C++
Aktivity (8)

 

 

Komentáře

Avatar
Petr Tureček:20.11.2018 18:11

V poslednim kousku kodu tady v clanku by asi melo byt:
const int& getVek() const;

 
Odpovědět 20.11.2018 18:11
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Petr Tureček
David Čápka:20.11.2018 18:26

Jasně, díky, opraveno :)

Odpovědět 20.11.2018 18:26
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Otakar Pok
Člen
Avatar
Otakar Pok:13. února 14:04

Ahoj, jsem teď docela zmatený z popisu tohoto kódu:

class Uzivatel
{
    const int vek;
    const char* jmeno;
};

kde se píše, že pokud bude použita konstatní metoda, tak si před atributy můžeme představit klíčové slovo const a z toho nám tedy vznikne const char* jmeno;
Toto ale chápu jako pointer na konstantní char, tzn. mohu změnit kam bude pointer ukazovat, ale nemohu změnit hodnotu na adrese kam ukazuje.
Konstatní pointer na char by měl být char * const jmeno, ne?

Zkoušel jsem to kompilovat:

void nastavJmeno() const
    {
        *jmeno = 'C';
    }

jen abych zjistil co lze tedy měnit a opravdu se to chová jako konstatní pointer na char. Mohu změnit obsah na adrese kam pointer jmeno ukazuje, ale změnit pointeru adresu kam ukazuje již nejde:

char znak = "C";
void nastavJmeno() const
    {
        jmeno = &znak;  // nelze
    }

Neměl by tedy být správně zápis v článku char * const jmeno? Nebo mi něco uniklo? :-)
Díky moc!

 
Odpovědět 13. února 14:04
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na Otakar Pok
patrik.valkovic:13. února 14:20

Jo máš pravdu, opraveno v článku.

Odpovědět 13. února 14:20
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 4 zpráv z 4.