PHP týden Předvánoční slevová akce
Pouze tento týden sleva až 80 % na PHP e-learning!
Využij předvánočních slev a získej od nás 20 % bodů zdarma! Více zde

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

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 prázdnou 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
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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;
using namespace std;
class Kostka
{
public:
    Kostka(int _pocet_sten);
    ~Kostka();
    int pocet_sten;
};
#include <iostream>
#include "Kostka.h"

using namespace std;
Kostka::Kostka(int _pocet_sten)
{
    pocet_sten = _pocet_sten;
}

Kostka::~Kostka()
{
}

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. Protože jsme konstruktor nadefinovali, ten bezparametrický není k dispozici. Ukázka výše tedy funguje až s následujícím kódem.

V konstruktoru bychom měli požadovat všechny informace, které třída během 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 = 6);
    ~Kostka();
    int pocet_sten;
};

//Kostka.cpp
Kostka::Kostka(int _pocet_sten)
{
    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;
};
#include <iostream>
#include "Kostka.h"

using namespace std;
Kostka::Kostka()
{
    pocet_sten = 6;
}

Kostka::Kostka(int _pocet_sten)
{
    pocet_sten = _pocet_sten;
}
Kostka::~Kostka()
{
}
#include "Kostka.h"
#include <iostream>
using namespace std;
int main()
{
//...main
Kostka sestisten;
Kostka desetisten(10);
cout << sestisten.pocet_sten << endl;
cout << desetisten.pocet_sten << endl;
    cin.get();
    return 0;
}

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 hlavičku definice 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;
#ifndef __KOSTKA_H__
#define __KOSTKA_H__
#include <iostream>
using namespace std;

class Kostka
{
public:
    Kostka();
    Kostka(int _pocet_sten);
    ~Kostka();
    int pocet_sten;
};
#endif
#include <iostream>
#include "Kostka.h"
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()
{
}

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é.

#include "Arena.h"

// 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)
{
}
class Arena
{
public:
    Hrac prvni;
    Hrac druhy;
    Arena(string jmeno_prvni, string jmeno_druhy);
};
#ifndef __HRAC__H_
#define __HRAC__H_
#include <string>
using namespace std;
class Hrac
{
public:
    string jmeno;
    Hrac(string _jmeno);
};
#endif
#include "Hrac.h"
Hrac::Hrac(string _jmeno)
{
    jmeno = _jmeno;
}
#include <iostream>
#include <string>
#include "Arena.h"
#include "Hrac.h"
using namespace std;
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 74x (6.6 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

 

Článek pro vás napsal Patrik Valkovič
Avatar
Jak se ti líbí článek?
9 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Předchozí článek
První objektová aplikace v C++
Všechny články v sekci
Objektově orientované programování v C++
Miniatura
Následující článek
Destruktory a aplikace konstruktorů v C++
Aktivity (14)

 

 

Komentáře

Avatar
Tomáš Rumíšek:24. března 20:26

Ahoj,
zkoušel jsem ten kód ve kterém je main.cpp, Kostka.h a Kostka.cpp v jednom souboru (sekce před přetěžováním konstruktoru) rozhodit do tří souborů, ale při kompilaci to vyhodí chybu "error C2512: 'Kostka' : no appropriate default constructor available". Zkoušel jsem to spustit i tady na stránkách online a dopadlo to stejně. V čem je problém? Proč toto řešení funguje jen v rámci jednoho filu?

Díky, Tom

 
Odpovědět
24. března 20:26
Avatar
Odpovídá na Tomáš Rumíšek
Patrik Valkovič:24. března 21:52

Ahoj,
který konkrétně příklad ti nefunguje? Já teď spustil všechny a žádný z nich nehlásí žádný error.

Odpovědět
24. března 21:52
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
Imrich Vízi
Člen
Avatar
Imrich Vízi:21. července 19:05

Zdravím,

tá varianta s

Kostka::Kostka(int _pocet_sten=6)

mi nefunguje.

main.cpp

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

using namespace std;

int main()
{

        Kostka kostka;
        cout << kostka.pocet_sten << endl;


        cin.get();
        return 0;
}

Kostka.cpp

#include "Kostka.h"


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


Kostka::~Kostka()
{

}

Kostka.h

#ifndef _KOSTKA_H_
#define _KOSTKA_H_

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


#endif

"error C2512: 'Kostka' : no appropriate default constructor available".

 
Odpovědět
21. července 19:05
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Imrich Vízi
Člen
Avatar
Imrich Vízi:21. července 19:08

Tiež mi nefunguje za predokladu použitia parametrického konštruktora

Kostka* dynamicky_sestisten = new Kostka;
Kostka* dynamicky_sestisten2 = new Kostka(); // bude fungovat

Zmenilo sa niečo od napísania článku ?

 
Odpovědět
21. července 19:08
Avatar
Imrich Vízi
Člen
Avatar
Imrich Vízi:22. července 18:15

Syntaxe je taková, že za deklaraci konstruktoru přidáme dvojtečku a následně název konstruktoru s jeho parametry.

Nepovažovali ste deklaráciu v predchádzajúcich článkoch za to, čo je obsiahnuté v Kostka.h a definíciu za to, čo je obsiahnuté v Kostka.cpp ?

 
Odpovědět
22. července 18:15
Avatar
Odpovídá na Imrich Vízi
Patrik Valkovič:23. července 11:02

Ahoj,
díky za upozornění, článek jsem opravil, snad už by vše mělo být jasné. Divím se, že na to ještě nikdo nepřišel.

Odpovědět
23. července 11:02
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 6 zpráv z 6.