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:
#include "Kostka.h" #include <iostream> using namespace std; int main() { Kostka kostka(10); cout << kostka.pocet_sten << endl; cin.get(); return 0; }
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:
#include <iostream>
//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;
}
Kostka::~Kostka()
{
}
using namespace std;
int main()
{
//main.cpp
Kostka sestisten;
Kostka desetisten(10);
cout << sestisten.pocet_sten << endl;
cout << desetisten.pocet_sten << endl;
cin.get();
return 0;
}
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.
#ifndef __KOSTKA_H__ #define __KOSTKA_H__ class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; }; #endif
#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.
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:
#include "Kostka.h" #include <iostream> using namespace std; int main() { Kostka sestisten; cin.get(); return 0; }
#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) { }
#ifndef __ARENA_H_ #define __ARENA_H_ #include "Hrac.h" class Arena { public: Hrac prvni; Hrac druhy; Arena(string jmeno_prvni, string jmeno_druhy); }; #endif
#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++