4. diel - Deštruktory a aplikácie konštruktor v C ++
V minulej lekcii, Hracia kocka v C ++ a konštruktory , sme si popísali syntax konštruktory a to vrátane pokročilých konštrukcií ako bol poverovateľ konštruktor a volanie Konstruktor pre atribúty. Dnes si popíšeme deštruktory a ukážeme si hlavný účel, pre ktorý sú konštruktory a deštruktory použité.
Deštruktory
Podobne ako konštruktor, ktorý sa volá ihneď po vytvorení inštancie, sa
destruktor volá automaticky pred zmazaním inštancie. Mazanie všeobecne
prebieha na konci bloku (teda koniec funkcie alebo pri uzatvárajúca zložené
zátvorke }
). Destruktor sa zapisuje ako metóda, ktorá začína
vlnovkou (~
), po ktorej nasleduje názov triedy. Destruktor nikdy
nemá parametre a nevracia hodnotu. Takýto základný destruktor nám už
Visual Studio vygenerovalo a je prázdny (ak destruktor nedodáme, kompilátor
automaticky vytvorí destruktor s prázdnym telom):
Kostka.h
class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); // deklarace destruktoru int pocet_sten; };
Kostka.cpp
Kostka::~Kostka() // prázdný destruktor
{
}
Aby sme videli, kedy sa destruktor volá, pridáme si do jeho implementácie výpis do konzoly:
#include <iostream> // pokud v souboru chybí using namespace std; // pokud v souboru chybí Kostka::~Kostka() { cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl; }
Do main.cpp si vložíme nasledujúci kód, ktorý ukazuje prípady, kedy sa destruktor volá.
#include <iostream> #include "Kostka.h" using namespace std; void funkce(Kostka k) { cout << "Funkce" << endl; } int main() { Kostka prvni(1); if (true) { Kostka druha(2); funkce(druha); cout << "Funkce skoncena" << endl; } // cin.get(); return 0; }
#include <iostream> #ifndef __KOSTKA_H__ #define __KOSTKA_H__ using namespace std; class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; }; #endif
#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() { cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl; }
Výpisy vidíme na výstupe aplikácie:
Konzolová aplikácia
Volani konstruktoru s parametrem
Volani konstruktoru s parametrem
Funkce
Volani destruktoru pro kostku s 2 stenami
Funkce skoncena
Volani destruktoru pro kostku s 2 stenami
Volani destruktoru pro kostku s 1 stenami
Keď si príklad rozoberieme, zistíme, že destruktor sa volá pred ukončovacími zloženými zátvorkami a na konci funkcie. Vtedy už nie je premenná potreba a C ++ vykoná jej odstránenie z pamäte. Pre lepšie pochopenie prikladám kód s komentármi.
void funkce(Kostka k) { cout << "Funkce" << endl; } // destruktor pro "k" int main() { Kostka prvni(1); // první konstruktor if (true) { Kostka druha(2); // druhý konstruktor funkce(druha); cout << "Funkce skoncena" << endl; } // destruktor pro "druha" // cin.get(); pokud volání necháme, neuvidíme mazání "prvni" return 0; } // destruktor pro "prvni"
Konštruktory sú vypísané tiež, pretože sme ponechali kód z minulej lekcie. Mohlo by vás zaraziť, že sú volány tri deštruktory, ale iba dva konštruktory. V jednom prípade sa volá kopírujúci konštruktor, ale tým sa budeme zaoberať v inej lekciu. Zatiaľ nám stačí vedieť, kedy je destruktor volaný.
Konštruktor pre inicializáciu
Teraz sa pozrieme na jeden prípad, kedy sa nám konstruktory hodí - inicializácia triedy.
Definujme na kocke metódu hod()
, ktorá nám vráti náhodné
číslo od 1 do počtu stien. Je to veľmi jednoduché, metóda nebude mať
žiadny parameter a návratová hodnota bude typu int
. Náhodné
číslo získame tak, že zavoláme funkciu rand()
z knižnice
cstdlib.
Kostka.h
#ifndef __KOSTKA_H__ #define __KOSTKA_H__ class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int hod(); int pocet_sten; }; #endif
Kostka.cpp
#include <iostream> #include <cstdlib> #include "Kostka.h" using namespace std; // ... již definované metody int Kostka::hod() { return rand() % pocet_sten + 1; }
rand()
vracia pseudo náhodné číslo. Aby
bolo v požadovanom rozsahu, musíme naň použiť %pocet_sten
.
Jednička sa pripisuje preto, aby náhodné čísla bola od jednotky a nie od
nuly. Pseudonáhodnej číslo znamená, že sa začne na nejakom čísle a
nejakú operácií sa od neho dopočítavajú zostávajúce čísla. To má
jednu nevýhodu - do main.cpp napíšte nasledujúci kód (ten
pôvodný môžete zmazať):
#include <iostream> #include "Kostka.h" #include "Arena.h" using namespace std; int main() { Kostka kostka; for (int i = 0; i < 10; i++) cout << kostka.hod() << " "; cin.get(); return 0; }
Všimnite si, že ak program spustíme opakovane, vždy generuje rovnaké
čísla (aj keď by ich mal generovať náhodne). To je z dôvodu, že
východisková číslo pre generovanie je zakaždým rovnaké. My potrebujeme,
aby sa pri každom spustení začínalo od iného čísla, toho docielime
pomocou metódy srand()
, do ktorej odovzdáme aktuálny čas. A
pretože to vlastne nastavuje inštanciu, tento kód vložíme do
konstruktoru.
Pozn .: Vedľa knižnice cstdlib
musíme includovat aj
knižnicu ctime
.
#include <iostream> #include "Kostka.h" using namespace std; //main.cpp int main() { Kostka kostka; for (int i = 0; i < 10; i++) cout << kostka.hod() << " "; cin.get(); return 0; }
#ifndef __KOSTKA_H__ #define __KOSTKA_H__ class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; int hod(); }; #endif
#include "Kostka.h" #include <iostream> #include <cstdlib> #include <ctime> 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; srand(time(NULL)); } // zbývající metody Kostka::~Kostka() { cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl; } int Kostka::hod() { return rand() % pocet_sten + 1; }
Teraz kocka vždy vygeneruje iné čísla a tým sme hotoví.
Konštruktor pre správu pamäte
Druhým prípadom, kedy môžeme použiť konštruktor (a destruktor), je pre správu pamäte. Vďaka tomu, že sa konštruktory a deštruktory volajú automaticky, máme istotu, že sa kód vždy vykoná. Môžeme si teda v konstruktoru alokovať pamäť a v destruktor ju zase zmazať. Pre príklad si zoberieme našu arénu, v ktorej sú aktuálne dva bojovníci. Povedzme, že chceme zadať počet bojovníkov v parametri - musíme si dynamicky vytvoriť pole bojovníkov. Súbor Arena.h upravíme nasledovne:
#ifndef __ARENA_H_ #define __ARENA_H_ #include "Hrac.h" class Arena { public: Hrac** hraci; int pocet_hracu; Arena(int _pocet_hracu); // byl změněn název parametru ~Arena(); }; #endif
Dvoch hviezdičiek sa nebojte - je to pole ukazovateľov na Hrac (nemôžeme vytvoriť len pole hráčov, pretože nemáme predvolená = bezparametrický konštruktor, ktorý je potreba). V konstruktoru toto pole alokuje, podľa počtu hráčov sa opýtame na mená a hráča vytvoríme. V destruktor potom vykonáme opačnú operáciu a všetko zmažeme. Na kód sa môžete pozrieť:
Arena.cpp
#include <iostream> #include "Arena.h" using namespace std; Arena::Arena(int _pocet_hracu) { pocet_hracu = _pocet_hracu; // uložení počtu hráčů hraci = new Hrac*[pocet_hracu]; // vytvoření pole pro hráče for (int i = 0; i < pocet_hracu; i++) { string jmeno; cout << "Zadejte jmeno hrace: "; cin >> jmeno; hraci[i] = new Hrac(jmeno); // vytvoření hráče } } Arena::~Arena() { for (int i = 0; i < pocet_hracu; i++) delete hraci[i]; // mazání hráčů delete[] hraci; // mazání pole hraci = NULL; }
Ak by sme pamäť nemazali, zostávala by alokovaná a nemali by sme sa k nej ako dostať (neexistoval by na ňu ukazovateľ) a nebolo by ju možné zmazať teda ani neskôr. Ak by prebiehalo vytváranie inštancií napríklad v cykle, potom by program začal konzumovať stále viac a viac RAM, kým by ju nezabral celú (a mať rádovo gigabajty RAM pre takú malú aplikáciu je už trochu zvláštne). Ak nie je voľná RAM pamäť a program požiada o ďalšie, operačný systém už nemá čo prideliť a aplikáciu zhodí. Preto, ak sa vám stáva, že aplikácia po nejakej dobe spadne, skúste si skontrolovať, koľko zaberá miesta v pamäti a ak sa toto miesto neustále zväčšuje, zrejme niekde neuvoľňuje pamäť - tzv. Memory leak.
Main.cpp
Tým máme hotovú arénu a môžeme ju použiť v main.cpp:
#include <iostream> #include "Kostka.h" #include "Arena.h" using namespace std; int main() { Kostka kostka; for (int i = 0; i < 10; i++) cout << kostka.hod() << " "; cout << endl; Arena arena(4); cin.get(); return 0; }
výsledok:
Konzolová aplikácia
Volani konstruktoru s parametrem
Volani bezparametrickeho konstruktoru
2 6 1 2 1 6 2 3 1 4
Zadejte jmeno hrace: Pavel
Zadejte jmeno hrace: Karel
Zadejte jmeno hrace: Zdenek
Zadejte jmeno hrace: Lukas
Volani destruktoru pro kostku s 6 stenami
Všetko funguje podľa našich predstáv. Alokovanie a uvoľňovanie pamäte je najčastejšia vec, ktorá sa v konstruktoru a deštruktory vykonáva, preto odporúčam si posledný ukážku poriadne prejsť a pochopiť, ako to vlastne funguje.
To je pre túto lekciu všetko a nabudúce, Riešené úlohy k 3. a 4. lekciu OOP v C ++ , si odstránime tie škaredé názvy parametrov začínajúcich podčiarknikom. Zdrojové kódy z dnešnej lekcie sú priložené pod článkom na stiahnutie ako vždy.
V nasledujúcom cvičení, Riešené úlohy k 3. a 4. lekciu OOP v C ++, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
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é 108x (7.21 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C++