5. diel - Ukazovateľ this v C ++
V predchádzajúcom cvičení, Riešené úlohy k 3. a 4. lekciu OOP v C ++, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V predchádzajúcej lekcii, Riešené úlohy k 3. a 4. lekciu OOP v C ++ , sme dokončili konštruktory a do
dnešnej lekcie sme si sľúbili, že odstránime škaredé názvy parametrov,
ktoré začínajú podčiarknikom (_). Pred parametre sme museli pridať
podčiarkovník, pretože by C ++ nevedelo, ku ktorej premenné sa príkaz
vzťahuje (či k atribútu alebo k parametru). Ukazovateľ this
nám s týmto problémom pomôže.
Ukazovateľ
this
je kľúčové slovo jazyka C ++ a nemôžeme vytvoriť
premennú, triedu alebo typ, ktorý by sa volal rovnako. Ako bolo povedané,
jedná sa o ukazovateľ, ktorý je prístupný vo všetkých metódach
triedy a odkazuje sa na samotnú inštanciu. S touto konštrukciou
jazyka býva často problém, preto začneme zľahka. this je
ukazovateľ na inštanciu samotnú, musí byť teda rovnakého typu ako je
trieda. To možno demonštrovať napríklad pri triede Hrac, kde
zmeníme konštruktor nasledovne:
Hrac.cpp
#include "Hrac.h" Hrac::Hrac(string _jmeno) { Hrac const * aktualni = this; jmeno = _jmeno; }
Ak sa pokúsime uložiť ukazovateľ do ľubovoľného iného typu
(napríklad int
), potom nám kompilátor zahlási nasledujúce
chybu (pre Visual Studio):
error C2440: 'initializing': cannot convert from 'Hrac *const ' to 'int *' Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
To znamená, že nemožno previesť ukazovateľ typu
Hrac * const
na typ int *
. Zároveň nám tým
kompilátor prezrádza typ ukazovateľa this. Jedná sa o konštantný
ukazovateľ (pozri lekcie o Konštantných
hodnotách). Môžeme meniť inštanciu, na ktorú ukazuje, ale nemôžeme
meniť hodnotu ukazovateľa (const za hviezdičkou). Nasledujúci kód teda
nebude validný:
this = new Hrac("Karel");
Kompilácia zahlási:
error C2106: '=': left operand must be l-value
Tým máme vyriešené, čo to vlastne this je. Teraz ešte musíme vyriešiť, na čo ukazuje.
Príklad s metódou hod ()
Ako bolo povedané, ukazuje na inštanciu samotnú. Pre príklad si upravíme
metódu hod()
na triede Kocka tak, aby prijímala ako
parameter ukazovateľ na typ Kostka:
Kostka.h
class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; int hod(Kostka* k); };
Kostka.cpp
// ...předchozí implementace int Kostka::hod(Kostka* k) { return rand() % pocet_sten + 1; }
Teraz, keď zavoláme v main.cpp metódu hod()
,
odovzdáme jej ukazovateľ na samotnú inštanciu:
// main.cpp Kostka kostka; for (int i = 0; i < 10; i++) kostka.hod(&kostka); cout << endl;
A ako si dokážeme, že this
odkazuje skutočne na túto
inštanciu? Porovnáme adresy odkazov - metódu hod()
upravíme
nasledovne a program spustíme.
#include <iostream> #include <cstdlib> #include <ctime> #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; srand(time(NULL)); } Kostka::~Kostka() { cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl; } int Kostka::hod(Kostka* k) { cout << "Adresa this: " << this << endl; cout << "Adresa parametru: " << k << endl; return rand() % pocet_sten + 1; }
#include <iostream> #include "Kostka.h" using namespace std; int main() { Kostka kostka; for (int i = 0; i < 10; i++) kostka.hod(&kostka); cout << endl; }
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; int hod(Kostka* k); };
Pozn .: Adresy sa zrejme budú líšiť, ale dvojica by mala byť rovnaká.
Konzolová aplikácia
Adresa this: 0x7ffc781864e0
Adresa parametru: 0x7ffc781864e0
Ak vytvoríme kocky dve, budú adresy rozdielne:
#include <iostream> #include "Kostka.h" using namespace std; int main() { Kostka prvni; Kostka druha; prvni.hod(&prvni); druha.hod(&druha); }
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; class Kostka { public: Kostka(); Kostka(int _pocet_sten); ~Kostka(); int pocet_sten; int hod(Kostka* k); };
#include <iostream> #include <cstdlib> #include <ctime> #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; srand(time(NULL)); } Kostka::~Kostka() { cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl; } int Kostka::hod(Kostka* k) { cout << "Adresa this: " << this << endl; cout << "Adresa parametru: " << k << endl; return rand() % pocet_sten + 1; }
Konzolová aplikácia
Adresa this: 0x7ffe01f06b40
Adresa parametru: 0x7ffe01f06b40
Adresa this: 0x7ffe01f06b30
Adresa parametru: 0x7ffe01f06b30
Zjednodušenie názvov parametrov pomocou this
Čo z toho vyplýva? O this
môžeme uvažovať ako o
ukazovateli, ktorý sa odvoláva na inštanciu, pre ktorú sme metódu
volali. Tento ukazovateľ je prístupný vo všetkých metódach
(vrátane konstruktoru a deštruktory) a toho my využijeme. Všetky úpravy
kódu, ktoré sme zatiaľ vykonali, prepíšeme späť do pôvodného stavu
(alebo postačí stiahnuť projekt z minulej lekcie).
Teraz už môžeme odstrániť tie škaredé názvy parametrov. V čom bol problém? Ak sme použili parameter s rovnakým názvom ako je atribút, tento parameter prekryl atribút a pracovali sme iba s parametrom. Napríklad pre kocku, ak zmeníme konštruktor do nasledujúcej podoby:
Kostka.h
class Kostka { public: Kostka(); Kostka(int pocet_sten); ~Kostka(); int pocet_sten; int hod(); };
Kostka.cpp
//...zbývající implementace Kostka::Kostka(int pocet_sten) { cout << "Volani konstruktoru s parametrem" << endl; pocet_sten = pocet_sten; //do proměnné, kterou jsme přijali jako parametr, uložíme hodnotu z parametru srand(time(NULL)); }
Musíme nejako povedať, že chceme použiť premennú z inštancie. Ale samotnú inštanciu predsa máme v ukazovateli this!
Kostka.cpp
//...zbývající implementace Kostka::Kostka(int pocet_sten) { cout << "Volani konstruktoru s parametrem" << endl; this->pocet_sten = pocet_sten; //do proměnné instance uložíme hodnotu z parametru srand(time(NULL)); }
Rovnakým spôsobom upravíme aj triedu Arena a Hrac. Tým sme vlastne hotoví s praktickou časťou v tejto lekcii.
Používať alebo nepoužívať this
Do tejto lekcie sme o ukazovateli this nevedeli a napriek tomu sme mohli meniť atribúty tried. Ak neexistuje premenná (nemusí sa nutne jednať o parameter), ktorý má rovnaký názov ako atribút, this používať nemusíme (ale môžeme). Niektoré jazyky (ako Java alebo C #) pracujú rovnako ako C ++ a nevyžadujú použitie this, pokiaľ to nie je nutné. Naopak iné jazyky (napríklad PHP alebo Python) vyžadujú, aby bol ukazovateľ pre prístup k atribútu vždy použitý. V C ++ môžeme napríklad destruktor arény napísať dvoma spôsobmi a oba budú fungovať.
Arena::~Arena() { for (int i = 0; i < pocet_hracu; i++) delete hraci[i]; delete[] hraci; hraci = NULL; } Arena::~Arena() { for (int i = 0; i < this->pocet_hracu; i++) delete hraci[i]; delete[] hraci; hraci = NULL; }
Ktorú variantu používať nie je presne dané a je na každom programátorovi, aby si zvolil. Osobne uprednostňujem druhú variantu (aj keď je dlhšia), pretože zreteľne vyjadruje použitie atribútu na triede. Preto tento zápis budem používať aj ďalej v tutoriálu (ale nie je nutný).
Rovnako ako môžeme pristupovať k atribútom, môžeme aj volať metódy
inštancie. Napríklad, ak by sme chceli z konstruktoru (z akéhokoľvek
dôvodu), zavolať metódu hod()
, môžeme to urobiť len názvom
metódy, alebo pomocou this. Oba prístupy sú demonštrované:
Kostka::Kostka(int pocet_sten) { cout << "Volani konstruktoru s parametrem" << endl; this->pocet_sten = pocet_sten; // zde this být musí, protože máme parametr se stejným jménem srand(time(NULL)); hod(); // zde již ne, protože "hod" není nikde překryto } Kostka::Kostka(int pocet_sten) { cout << "Volani konstruktoru s parametrem" << endl; this->pocet_sten = pocet_sten; srand(time(NULL)); this->hod(); }
Tým je dnešná lekcia kompletný. V lekcii budúci, Riešené úlohy k 5. lekcii OOP v C ++ , si do arény vytvoríme bojovníkmi.
V nasledujúcom cvičení, Riešené úlohy k 5. lekcii 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é 54x (7.22 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C++