3. díl - Hrací kostka v C++ - Konstruktory a náhodná čísla

C++ Objektově orientované programování Hrací kostka v C++ - Konstruktory a náhodná čísla

V minulém tutoriálu z našeho seriálu o jazyce C++ jsme si naprogramovali první objektovou aplikaci. Již umíme tvořit nové třídy a vkládat do nich atributy a metody s parametry a návratovou hodnotou. Dnes začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (napřeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.

Vytvořme si novou konzolovou aplikaci a pojmenujme ji Arena. Jako obvykle vytvoříme soubor Source.cpp s tímto kódem:

#include <conio.h>
#include <windows.h>
#include <iostream>
using namespace std;

int main(void)
{
        //nastavíme české kódování(podpora diakritiky v konzoli)
        std::locale loc("Czech_Czech Republic.1250");
        std::locale::global(loc);
        //nastavíme titulek okna
        SetConsoleTitleA("Arena");
        //zprovozníme generátor náhodných čísel
        srand(time(NULL));

        _getch();
        return 0;
}

K projektu si přidejme novou class s názvem Kostka. Zamysleme se nad atributy, které kostce dáme. Jistě by se hodilo, kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10 stěn, jak je zvykem u tohoto typu her). Naše třída bude mít nyní 1 atribut: pocetSten typu int. Minule jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy jako public, tedy jako veřejně přístupné. Většinou se však spíše nechce, aby se daly zvenčí modifikovat a používá se modifikátor private. Atribut je poté viditelný jen uvnitř třídy a zvenčí se C++ tváří, že vůbec neexistuje. Při návrhu třídy tedy nastavíme vše na private a v případě, že něco bude opravdu potřeba vystavit, použijeme public. Naše třída nyní vypadá asi takto:

#pragma once
/// Třída reprezentuje hrací kostku
class Kostka
{
private:
        /// Počet stěn kostky
        int pocetSten;
public:
        Kostka(void);
        ~Kostka(void);
}

Pozn.: Když vynecháme private:, nic se nestane. Na začátek třídy si to totiž doplní překladač.

Konstruktory

Až doposud jsme neuměli zvenčí nastavit jiné atributy než public, protože např. private nejsou zvenčí viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to metoda, která se zavolá ve chvíli vytvoření instance objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a k provedení případné inicializace. Kostku bychom nyní v Source.cpp vytvořili takto:

Kostka kostka();

Právě závorkami za názvem kostka voláme konstruktor. Jednoduchý konstruktor nám už vygenerovalo VS. Je zapsaný takto: Kostka(void);. V souboru Kostka.cpp má prázdné tělo({}). My si však nyní do jeho těla něco doplníme. V konstruktoru nastavíme počet stěn na pevnou hodnotu. Konstruktor bude vypadat následovně:

Kostka::Kostka()
{
        pocetSten = 6;
}

Konstruktor se deklaruje jako metoda, ale nemá návratový typ a musí mít stejné jméno jako je jméno třídy, v našem případě tedy Kostka.

Pokud kostku nyní vytvoříme, bude mít v atributu pocetSten 6. Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. Není dobré atribut nastavit na public, protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit počet stěn. Přidáme do třídy tedy metodu VratPocetSten(), která nám vrátí hodnotu atributu pocetSten. Docílili jsme tím v podstatě toho, že je atribut read-only (atribut není viditelný a lze ho pouze číst metodou, změnit ho nelze). C++ má k tomuto účelu ještě další konstrukce, ale tím se zatím nebudeme zabývat. Nová metoda bude vypadat asi takto:

//Kostka.h
/// Vrátí počet stěn hrací kostky
int VratPocetSten();
//Kostka.cpp
int Kostka::VratPocetSten()
{
        return pocetSten;
}

Přesuňme se do Source.cpp a vyzkoušejme si vytvořit kostku a vypsat počet stěn:

Kostka kostka; // v tuto chvíli se zavolá konstruktor
cout << kostka.VratPocetSten();

Výstup:

6

Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy kostruktoru parametr:

Kostka::Kostka(int _pocetSten)
{
        pocetSten = _pocetSten;
}

Všimněte si, že jsme před název parametru metody přidali znak "_", protože jinak by měl stejný název jako atribut a C++ by to zmátlo. Vraťme se k Source.cpp a zadejme tento parametr do konstruktoru:

Kostka kostka(10); // v tuto chvíli se zavolá konstruktor
cout << kostka.VratPocetSten() << endl;

Výstup:

10

Vše funguje, jak jsme očekávali. Ve třídě v tuto chvíli již není prázdný (tzv. bezparametrický konstruktor), takže kostku bez parametru vytvořit nelze. My to však můžeme umožnit, vytvořme si další konstruktor a tentokrát bez parametru. V něm nastavíme počet stěn na 6, protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako výchozí:

Kostka::Kostka()
{
        pocetSten = 6;
}

Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v Source.cpp):

Kostka sestistenna;
Kostka desetistenna(10);
cout << sestistenna.VratPocetSten() << endl;
cout << desetistenna.VratPocetSten() << endl;

Výstup:

6
10

C++ nevadí, že máme 2 metody se stejným názvem, protože jejich parametry jsou různé. Hovoříme o tom, že metoda Kostka (tedy zde konstruktor) má přetížení (overload). Toho můžeme využívat i u všech dalších metod, nejen u konstruktorů. Visual Studio nám přetížení u metod přehledně nabízí (ve chvíli, kdy za název metody napíšeme levou závorku), variantami metody si můžeme listovat pomocí šipek. Tohoto pomocníka nazval MS IntelliSense. V nabídce vidíme naše 2 konstruktory:

Intellisense pro C++ ve Visual Studio

Mnoho funkcí v C++ má hned několik přetížení, zkuste se podívat např. na metodu getline na objektu cin. Je dobré si u metod projít jejich přetížení, abyste neprogramovali něco, co již někdo udělal před vámi.

Ukážeme si ještě, jak jde obejít nepraktický název atributu u parametrického konstruktoru (v našem případě _pocetSten) a potom konstruktory opustíme. Problém je samozřejmě v tom, že když napíšeme:

Kostka::Kostka(int pocetSten)
{
        pocetSten = pocetSten;
}

C++ neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V tomto případě přiřazujeme do parametru znovu ten samý parametr. VS nás na tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost odkazovat na její instanci, je uložena v proměnné *this (hvězdička znamená ukazatel). Využití si můžeme představit např. kdyby kostka měla metodu DejHraci(Hrac hrac) a tam by volala hrac.SeberKos­tku(this). Zde bychom hráči pomocí this předali sebe sama, tedy tu konkrétní kostku, se kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme odkazu na instanci při nastavování atributu:

Kostka::Kostka(int pocetSten)
{
        this->pocetSten = pocetSten;
}

Pomocí this jsme specifikovali, že levá proměnná pocetSten náleží instanci, pravou C++ chápe jako z parametru. Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací kostky. Přejděme dál.

Náhodná čísla

Definujme na kostce metodu hod(), která nám vrátí náhodné číslo od 1 do počtu stěn. Je to velmi jednoduché, metoda bude public (půjde volat zvenčí) a nebude mít žádný parametr. Návratová hodnota bude typu int. Náhodné číslo získáme tak, že zavoláme funkci rand().

/// Vykoná hod kostkou
int Kostka::hod()
{
        return 1+rand()%pocetSten;
}

rand() vrací náhodné číslo. Aby bylo v požadovaném rozsahu, musíme na něj použít %pocetSten. Jednička se přičítá proto, aby náhodná čísla byla od jedničky a ne od nuly.

Přetížení výstupu

Kostka je téměř hotová, ukažme si ještě jednu užitečnou funkci, kterou ji přidáme a kterou budeme hojně používat i ve většině našich dalších objektů. Řeč je o funkci operator<<(). Funkce je určena k tomu, aby vypsala "hodnotu" instance. Hodí se ve všech případech, kdy si instanci potřebujeme vypsat nebo s ní pracovat jako s textem. Již víme, že jakmile budeme chtít do konzole vypsat číslo nebo kterýkoli jiný objekt, C++ vybere správné přetížení funkce operator<< a vypíše její výstup. Pokud si děláme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako Vypis(), když máme v C++ připravenou cestu, jak toto řešit. U kostky nemá operator<< vyšší smysl, ale u bojovníka bude jistě vracet jeho jméno. My si ji ke kostce stejně přidáme, bude vypisovat, že se jedná o kostku a vrátí i počet stěn.

//Kostka.h
std::ostream& operator<<(std::ostream &s);
//Kostka.cpp
std::ostream& operator<<(std::ostream &s)
{
        return s << "Kostka s " << VratPocetSten() << " stěnami";
}

Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našimi dvěmi kostkami v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:

// vytvoření
Kostka sestistenna;
Kostka desetistenna(10);

// hod šestistěnnou
cout << sestistenna << endl;
for (int i = 0; i < 10; i++)
        cout << sestistenna.hod() << ' ';

// hod desetistěnnou
cout << "\n\n" << desetistenna << endl;
for (int i = 0; i < 10; i++)
        cout << desetistenna.hod() << ' ';

Výstup může vypadat nějak takto:

Objektová hrací kostka v C++

Máme hotovou docela pěknou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty. Příště si naprogramujeme objekt bojovníka do naší arény. To je zatím vše. :)


 

Stáhnout

Staženo 482x (18.92 MB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

  Aktivity (1)

Článek pro vás napsal Zdeněk Pavlátka
Avatar
Autor se věnuje spoustě zajímavých věcí :)

Jak se ti líbí článek?
Celkem (5 hlasů) :
3.23.23.2 3.23.2


 


Miniatura
Předchozí článek
První objektová aplikace v C++
Miniatura
Následující článek
OOP v C++ - Bojovník do arény

 

 

Komentáře
Zobrazit starší komentáře (8)

Avatar
Tlapka
Člen
Avatar
Tlapka:

No ony se ve článku vůbec neřeší includy. Takže ve zdrojácích jsou includnuty soubory <time.h> a <stdlib.h>, ale to se ve článku bohužel vůbec nedozvíme. :(

 
Odpovědět  +1 8.11.2014 2:11
Avatar
Tlapka
Člen
Avatar
Tlapka:

Ve článku je "tak trochu" rozpor mezi textem a přiloženým zdrojovým kódem. V textu je uvedena deklarace "std::ostream& operator<<(std::os­tream &s);", kdežto ve zdrojácích je to zapsáno takto: "std::ostream &operator<<(std::os­tream &s, Kostka &k);"
A samozřejmě i tělo metody je jiné. Funkční je ta verze ze zdrojáku.

 
Odpovědět  +1 8.11.2014 2:20
Avatar
Libor Šimo (libcosenior):

Riadok
std::ostream &operator<<(std::os­tream &s, Kocka &k);
je prečo mimo telo triedy?

Odpovědět 28.12.2014 11:23
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Ondřej Langr (andysekcze):

nechtěl by někdo pořešit ty includy? Nebaví mě pořád po něčem pátrat a zjišťovat co kde chybí ;-) Samozřejmě si to nikdo neberte jako něco ofenziivního ;-)

Odpovědět  +1 7.11.2015 23:25
I have a charger. I have Note 7. Umh I haven't Note7.
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Ondřej Langr (andysekcze)
Zdeněk Pavlátka:

celý C++ tutoriál teď přepracovávám a aktualizuji, takže počkej tak týden, dva a celý článek bude předělaný

Odpovědět 8.11.2015 8:25
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Odpovídá na Zdeněk Pavlátka
Ondřej Langr (andysekcze):

V tom případě je vše v pořádku ;-) pokud tě to nějak urazilo nebo něco podobného, omlouvám se ;-)

Odpovědět 8.11.2015 13:14
I have a charger. I have Note 7. Umh I haven't Note7.
Avatar
Roman
Člen
Avatar
Roman:

Tie includy by sa fakt zišli a neber to osobne, ale tiež sa mi nepáči že sem v tutoriálu máš napísané napr:

//Kostka.h
std::ostream& operator<<(std::ostream &s);

a keď si stiahnem tvoj kód pre ukážku (veľká vďaka lebo bez toho by som sa nepomohol :)) tak tam máš zasa napísané:

//Kostka.h
std::ostream &operator<<(std::ostream &s, Kocka &k);

človek začiatočník ako ja .... by darmo potom hľadal kde má chybu + ešte to že tá funkcia má byť definovaná mimo class Kostka to tiež nebolo spomenuté :P ale inak good článok :)

 
Odpovědět 28. února 15:00
Avatar
Peter T
Člen
Avatar
Peter T:

Ahojte, prečo musí byť srand v konštruktore? Keď som ho dal do metódy hod, tak kocka vráti vždy rovnaké číslo, nerozumiem prečo.

int Kostka::hod() {
    srand(time(NULL));
    return 1+rand()%pocetSten;
}

Ďakujem za odpoveď :)

 
Odpovědět 11. června 15:38
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Peter T
Zdeněk Pavlátka:

Protože srand stačí volat jednou, takhle ho zbytečně voláš znovu a znovu a znovu.

Odpovědět 11. června 17:20
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Drahomír Hanák
Tým ITnetwork
Avatar
Odpovídá na Peter T
Drahomír Hanák:

Pseudonáhodná čísla obecně fungují tak, že začneš s počátečním stavem (to je to, co nastavuje funkce srand), a každé volání rand ten stav změní nějakými matematickými operacemi (jednoduchý algoritmus je třeba tady https://en.wikipedia.org/…al_generator, ale existují i mnohem sofistikovanější metody). Pokud nastavíš stejný počáteční stav, dostaneš stejnou posloupnost čísel. Voláním Kostka::hod víckrát za sebou je dost pravděpodobné, že pro několik hodů bude stav stejný (time(NULL) se mění jen 1 za sekundu a to je spousta času).

 
Odpovědět  +2 11. června 17:37
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 10 zpráv z 18. Zobrazit vše