Letní akce! Lákají tě IT školení C#, Javy a PHP v Brně? Přihlas se a napiš nám do zpráv kód "BRNO 500" pro slevu 500 Kč na libovolný brněnský kurz. Lze kombinovat se slevami uvedenými u školení i použít pro více kurzů. Akce končí 28.7.

Lekce 3 - Hrací kostka v C++ a konstruktory

C a C++ C++ Objektově orientované programování Hrací kostka v C++ a konstruktory

ONEbit hosting 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 lekci První objektová aplikace v C++ jsme si naprogramovali první objektovou aplikaci. Již umíme tvořit nové třídy a vkládat do nich atributy a metody. Dnes začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat 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ření projektu

Vytvořme si novou konzolovou aplikaci a pojmenujme ji Arena. Jako obvykle vytvoříme soubor main.cpp se základním kódem:

#include <iostream>

using namespace std;

int main()
{
        cin.get();
        return 0;
}

K projektu si přidejme novou třídu 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: pocet_sten typu int. Naše třída nyní vypadá asi takto:

Pozn.: Vygenerované #pragma once jsme odstranili, ale předgenerované metody jsme ponechali.

Kostka.h

#ifndef __KOSTKA_H__
#define __KOSTKA_H__

class Kostka
{
public:
        Kostka();
        ~Kostka();
        int pocet_sten;
};

#endif

Konstruktory

Až doposud jsme při vytváření nové instance neuměli nastavit atributy třídy. Museli jsme psát něco v tomto smyslu:

Kostka k;
k.pocet_sten = 6;

My bychom ale chtěli nastavit počet stěn již během vytváření instance. Minule jsme si letmo zmínili, že existuje konstruktor objektu. Je to metoda, která se zavolá ve chvíli vytvoření instance. Slouží samozřejmě k nastavení vnitřního stavu objektu a k provedení případné inicializace. Jednoduchý konstruktor nám již vygenerovalo Visual Studio. Je zapsaný takto: Kostka(). 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.cpp

Kostka::Kostka()
{
        pocet_sten = 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 pocet_sten hodnotu 6.

main.cpp

Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. V main.cpp si kostku vytvoříme a hodnotu si vypíšeme.

#include <iostream>
#include "Kostka.h"

using namespace std;

int main()
{
        Kostka kostka;
        cout << kostka.pocet_sten << endl;
        cin.get();
        return 0;
}

Výsledek:

Konzolová aplikace
6

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

Kostka.cpp

Dáme tedy konstruktoru parametr (musíme upravit i deklaraci v Kostka.h, aby přijímala parametr typu int):

Kostka::Kostka(int _pocet_sten)
{
        pocet_sten = _pocet_sten;
}

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 main.cpp a zadejme tento parametr do konstruktoru:

Kostka kostka(10);
cout << kostka.pocet_sten << endl;

Výsledek:

Konzolová aplikace
10

Všimněte si použití závorek. Pokud závorky vynecháme (jako jsme to dělali doteď), potom se volá bezparametrický konstruktor (ten, který byl původně vygenerován). Pokud má třída pouze konstruktory, které přijímají parametry, potom instanci musíme vytvořit tímto způsobem. Pokud by třída měla bezparametrický konstruktor a nějaké další, poté se při vytváření instance bez závorek zavolá právě ten bez parametrů. Jediný rozdíl je při dynamickém vytváření instance, kde závorky být mohou, ale nemusí.

Kostka sestisten;
Kostka sestisten2();  // nebude fungovat
Kostka desetisten(10);
Kostka* dynamicky_sestisten = new Kostka;
Kostka* dynamicky_sestisten2 = new Kostka(); // bude fungovat
Kostka* dynamicky_desetisten = new Kostka(10);

Pozn.: Pokud není nadefinovaný žádný konstruktor (jak tomu bylo v minulé lekci), poté kompilátor sám vygeneruje bezparametrický prázdný konstruktor.

V konstruktoru bychom měli požadovat všechny informace, které třída běhe svého působení nutně potřebuje. Donutíme tím programátory, kteří naši třídu používají, aby parametry zadali (jinak instanci nevytvoří).

My ale předpokládáme 6-ti stěnnou kostku jako výchozí, proto bychom chtěli automaticky vytvořit šestistěnnou kostku, pokud nezadáme parametr. To lze provést dvěma způsoby - prvním z nich je použít výchozí hodnoty parametru:

//Kostka.h
class Kostka
{
public:
        Kostka(int _pocet_sten);
        ~Kostka();
        int pocet_sten;
};

//Kostka.cpp
Kostka::Kostka(int _pocet_sten = 6)
{
        pocet_sten = _pocet_sten;
}

//main.cpp
Kostka sestisten;
Kostka desetisten(10);
cout << sestisten.pocet_sten << endl;
cout << desetisten.pocet_sten << endl;

Výstup:

Konzolová aplikace
6
10

Druhou variantou by bylo přetížit konstruktor a nadefinovat další, který nepřijímá žádný parametr.

class Kostka
{
public:
        Kostka();
        Kostka(int _pocet_sten);
        ~Kostka();
        int pocet_sten;
};

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

Kostka::Kostka(int _pocet_sten)
{
        pocet_sten = _pocet_sten;
}

//...main
Kostka sestisten;
Kostka desetisten(10);
cout << sestisten.pocet_sten << endl;
cout << desetisten.pocet_sten << endl;

Taková implementace není úplně šťastná, ale o tom si povíme za chvíli.

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ů. Mnoho metod v C++ má hned několik přetížení, zkuste se podívat např. na metodu getline() na objektu cin (Visual Studio parametry napovídá). 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.

cin.getline přetížení v C++

Provolávání konstruktorů

V předchozím případě jsme přímo v bezparametrickém konstruktoru přiřadili atributu pocet_sten hodnotu 6. Představme si, že máme například 5 atributů a k tomu ještě 3 konstruktory. Nastavovat ve všech konstruktorech všechny atributy není moc šťastné, protože opakovaně děláme to stejné a můžeme udělat chybu. Bývá pravidlem, aby specifičtější konstruktor (s méně parametry) volal ten obecnější (ten s více parametrů). Jak ale zavoláme konstruktor, když se volá automaticky při vytvoření instance? Pro tento případ zavádí C++ tzv. delegující konstruktor (delegating constructor). Syntaxe je taková, že za deklaraci konstruktoru přidáme dvojtečku a následně název konstruktoru s jeho parametry. Nejlépe poslouží ukázka:

Kostka.cpp

#include <iostream>
#include "Kostka.h"

using namespace std;

Kostka::Kostka() : Kostka(6)
{
        cout << "Volani bezparametrickeho konstruktoru" << endl;
}

Kostka::Kostka(int _pocet_sten)
{
        cout << "Volani konstruktoru s parametrem" << endl;
        pocet_sten = _pocet_sten;
}

Kostka::~Kostka()
{
}

A použití:

Kostka sestisten;

V ukázce vidíme, že se provolaly oba konstruktory.

Konzolová aplikace
Volani konstruktoru s parametrem
Volani bezparametrickeho konstruktoru

Pozn.: Delegating constructor funguje až od standardu C++11, proto nemusí fungovat pro všechny kompilery.

Toto delegování konstruktoru má ještě jedno uplatnění. Představme si, že chceme vytvořit instanci uživatele, od kterého potřebujeme jméno. To ještě není problém. My ho ale budeme potřebovat deklarovat v jiném objektu, v aréně, a to tehdy, kdy jméno ještě nebudeme znát. Následuje ukázka třídy pro hráče a arénu:

Hrac.h

#ifndef __HRAC__H_
#define __HRAC__H_
#include <string>
using namespace std;

class Hrac
{
public:
        string jmeno;
        Hrac(string _jmeno);
};
#endif

Hrac.cpp

#include "Hrac.h"

Hrac::Hrac(string _jmeno)
{
        jmeno = _jmeno;
}

Arena.h

#ifndef __ARENA_H_
#define __ARENA_H_
#include "Hrac.h"

class Arena
{
public:
        Hrac prvni;
        Hrac druhy;
        Arena();
};
#endif

Arena.cpp

#include "Arena.h"

Arena::Arena()
{
}

Projekt by neměl jít zkompilovat a bude hlásit následující hlášku (pro Visual Studio):

error C2512: 'Hrac': no appropriate default constructor available

Důvod je jednoduchý. Konstruktor se stará o vytvoření instance. To znamená, že ve chvíli, kdy je konstruktor ukončen, musí být třída připravena k použití. Toho ale nemáme jak docílit, protože hráč potřebuje jméno. Mohli bychom zkusit následující kód:

Arena::Arena()
{
        prvni = Hrac("Karel");
        druhy = Hrac("Pavel");
}

Dostaneme ale stále stejnou chybu. Než se na instanci zavolá libovolná metoda (včetně konstruktoru), musí být již instance správně vytvořena. Musíme tedy C++ nějak říci, jak má hráče vytvořit ještě před tím, než je vytvořena samotná Arena. Syntaxe je stejná jako u delegujícího konstruktoru, pouze místo názvu třídy použijeme název proměnné.

class Arena
{
public:
        Hrac prvni;
        Hrac druhy;
        Arena(string jmeno_prvni, string jmeno_druhy);
};
// Zde se volají konstruktor hráče ještě před tím, než vstoupíme do samotné implementace konstruktoru
Arena::Arena(string jmeno_prvni, string jmeno_druhy): prvni(jmeno_prvni), druhy(jmeno_druhy)
{
}


int main()
{
        Arena arena("Karel", "Pavel");
        cout << "Souboj mezi " << arena.prvni.jmeno << " a " << arena.druhy.jmeno << endl;
        cin.get();
        return 0;
}
Konzolová aplikace
Souboj mezi Karel a Pavel

To by bylo pro dnešní lekci vše. V příštím lekci, Destruktory a aplikace konstruktorů v C++, si řekneme o hlavním důvodu existence konstruktorů, popíšeme si destruktory a dokončíme naši hrací kostku.


 

Stáhnout

Staženo 41x (6.6 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?
5 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity (8)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!