IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

3. diel - Hracia kocka v C ++ a konštruktory

V predchádzajúcom cvičení, Riešené úlohy k 1. a 2. lekciu OOP v C ++, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V lekcii Riešené úlohy k 1. a 2. lekciu OOP v C ++ sme si naprogramovali prvú objektovú aplikáciu. Už vieme tvoriť nové triedy a vkladať do nich atribúty a metódy. Dnes začneme pracovať na sľúbené aréne, v ktorej budú proti sebe bojovať bojovníci. Boj bude ťahový (napřeskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolný hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme ako definovať vlastné konštruktor.

Vytvorenie projektu

Vytvorme si novú prázdnu aplikáciu a pomenujte ju Arena. Ako obvykle vytvoríme súbor main.cpp so základným kódom:

#include <iostream>

using namespace std;

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

K projektu si pridajme novú triedu s názvom Kocka. Zamyslime sa nad atribúty, ktoré kocke dáme. Iste by sa hodilo, keby sme si mohli zvoliť počet stien kocky (klasicky 6 alebo 10 stien, ako je zvykom u tohto typu hier). Naša trieda bude mať teraz 1 atribút: pocet_sten typu int. Naša trieda teraz vyzerá asi takto:

Pozn .: Vygenerované #pragma once sme odstránili, ale předgenerované metódy sme ponechali.

Kostka.h

#ifndef __KOSTKA_H__
#define __KOSTKA_H__

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

#endif

Konštruktory

Až doteraz sme pri vytváraní novej inštancie nevedeli nastaviť atribúty triedy. Museli sme písať niečo v tomto zmysle:

Kostka k;
k.pocet_sten = 6;

My by sme ale chceli nastaviť počet stien už počas vytvárania inštancie. Minule sme si letmo zmienili, že existuje konštruktor objektu. Je to metóda, ktorá sa zavolá vo chvíli vytvorení inštancie. Slúži samozrejme k nastavenie vnútorného stavu objektu a na vykonanie prípadnej inicializácia. Jednoduchý konštruktor nám už vygenerovalo Visual Studio. Je zapísaný takto: Kostka(). V súbore Kostka.cpp má prázdne telo ({}). My si však teraz do jeho tela niečo doplníme. V konstruktoru nastavíme počet stien na pevnú hodnotu. Konštruktor bude vyzerať nasledovne:

Kostka.cpp

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

Konštruktor sa deklaruje ako metóda, ale nemá návratový typ a musia mať rovnaké meno ako je meno triedy, v našom prípade teda Kostka.

Ak kocku teraz vytvoríme, bude mať v atribúte pocet_sten hodnotu 6.

Main.cpp

Vypíšme si počet stien do konzoly, nech vidíme, že tam hodnota naozaj je. V main.cpp si kocku vytvorí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ýsledok:

Konzolová aplikácia
6

Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocky špecifikovať pri vytvorení, koľko stien budeme potrebovať.

Kostka.cpp

Dáme teda konstruktoru parameter (musíme upraviť aj deklaráciu v Kostka.h, aby prijímala parameter typu int):

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

Všimnite si, že sme pred názov parametra metódy pridali znak "_", pretože inak by mal rovnaký názov ako atribút a C ++ by to zmiatlo. Vráťme sa k main.cpp a zadajte tento parameter 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ýsledok:

Konzolová aplikácia
10

Všimnite si použitie zátvoriek. Ak zátvorky vynecháme (ako sme to robili doteraz), potom sa volá bezparametrický konštruktor (ten, ktorý bol pôvodne vygenerovaný). Pokiaľ má trieda iba konštruktory, ktoré prijímajú parametre, potom inštanciu musíme vytvoriť týmto spôsobom. Ak by trieda mala bezparametrický konštruktor a nejaké ďalšie, potom sa pri vytváraní inštancie bez zátvoriek zavolá práve ten bez parametrov. Jediný rozdiel je pri dynamickom vytváraní inštancie, kde zátvorky byť môžu, ale nemusia.

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 .: Pokiaľ nie je nadefinovaný žiadny konštruktor (ako tomu bolo v minulej lekcii), potom kompilátor sám vygeneruje bezparametrický prázdny konštruktor. Pretože sme konštruktor nadefinovali, ten bezparametrický nie je k dispozícii. Ukážka vyššie teda funguje až s nasledujúcim kódom.

V konstruktoru by sme mali požadovať všetky informácie, ktoré trieda počas svojho pôsobenia nutne potrebuje. Donútime tým programátorov, ktorí našu triedu používajú, aby parametre zadali (inak inštanciu nevytvorí).

My ale predpokladáme 6-tich stenná kocku ako predvolený, preto by sme chceli automaticky vytvoriť šesťstenná kocku, ak nezadáme parameter. To možno vykonať dvoma spôsobmi - prvým z nich je použiť predvolené hodnoty parametra:

//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á aplikácia
6
10

Druhým variantom by bolo preťažiť konštruktor a nadefinovať ďalší, ktorý neprijíma žiaden parameter.

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;
}

Takáto implementácia nie je úplne šťastná, ale o tom si povieme za chvíľu.

C ++ nevadí, že máme 2 metódy s rovnakým názvom, pretože ich parametre sú rôzne. Hovoríme o tom, že metóda Kostka() (teda tu konštruktor) má preťaženia (overload). Toho môžeme využívať aj u všetkých ďalších metód, nielen u konstruktoru. Mnoho metód v C ++ má hneď niekoľko preťaženie, skúste sa pozrieť napr. Na metódu getline() na objekte cin (Visual Studio parametre napovedá). Je dobré si u metód prejsť ich preťaženie, aby ste neprogramoval niečo, čo už niekto urobil pred vami.

cin.getline preťaženia v C ++ - Objektovo orientované programovanie v C ++

Provolávání konštruktor

V predchádzajúcom prípade sme priamo v bezparametrickém konstruktoru priradili atribútu pocet_sten hodnotu 6. Predstavme si, že máme napríklad 5 atribútov ak tomu ešte 3 konštruktory. Nastavovať vo všetkých konštruktor všetky atribúty nie je moc šťastné, pretože opakovane robíme to isté a môžeme urobiť chybu. Býva pravidlom, aby špecifickejšie konštruktor (s menej parametrami) volal ten všeobecnejší (ten s viac parametrov). Ako ale zavoláme konštruktor, keď sa volá automaticky pri vytvorení inštancie? Pre tento prípad zavádza C ++ tzv. Poverovateľ konštruktor (delegating constructor). Syntax je taká, že za hlavičku definícia konstruktoru pridáme dvojbodku a následne názov konstruktoru s jeho parametrami. Najvyššie poslúži ukážka:

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žitia:

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ážke vidíme, že sa prevolali obaja konštruktory.

Konzolová aplikácia
Volani konstruktoru s parametrem
Volani bezparametrickeho konstruktoru

Pozn .: Delegating constructor funguje až od štandardu C ++ 11, preto nemusí fungovať pre všetkých kompilery.

Toto delegovanie konstruktoru má ešte jedno uplatnenie. Predstavme si, že chceme vytvoriť inštanciu užívateľa, od ktorého potrebujeme meno. To ešte nie je problém. My ho ale budeme potrebovať deklarovať v inom objekte, v aréne, a to vtedy, keď meno ešte nebudeme poznať. Nasleduje ukážka triedy pre hráčov 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 nemal ísť skompilovať a bude hlásiť nasledujúce hlášku (pre Visual Studio):

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

Dôvod je jednoduchý. Konštruktor sa stará o vytvorenie inštancie. To znamená, že vo chvíli, keď je konštruktor ukončený, musí byť trieda pripravená na použitie. Toho ale nemáme ako docieliť, pretože hráč potrebuje meno. Mohli by sme skúsiť nasledujúci kód:

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

Dostaneme ale stále rovnakú chybu. Než sa na inštanciu zavolá ľubovoľná metóda (vrátane konstruktoru), musí byť už inštancia správne vytvorená. Musíme teda C ++ nejako povedať, ako má hráča vytvoriť ešte pred tým, než je vytvorená samotná Arena. Syntax je rovnaká ako u delegujícího konstruktoru, len namiesto názvu triedy použijeme názov premennej.

#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á aplikácia
Souboj mezi Karel a Pavel

To by bolo pre dnešné lekciu všetko. V budúcom lekcii, Deštruktory a aplikácie konštruktor v C ++ , si povieme o hlavnom dôvodu existencie konstruktoru, popíšeme si deštruktory a dokončíme našu hraciu kocku.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 125x (6.6 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C++

 

Predchádzajúci článok
Riešené úlohy k 1. a 2. lekciu OOP v C ++
Všetky články v sekcii
Objektovo orientované programovanie v C ++
Preskočiť článok
(neodporúčame)
Deštruktory a aplikácie konštruktor v C ++
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity