Lekce 5 - Ukazatel this v C++
V předešlém cvičení, Řešené úlohy k 3. a 4. lekci OOP v C++, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Ukazatel
this
je klíčové slovo jazyka C++ a nemůžeme vytvořit
proměnnou, třídu nebo typ, který by se jmenoval stejně. Jak bylo řečeno,
jedná se o ukazatel, který je přístupný ve všech metodách třídy
a odkazuje se na samotnou instanci. S touto konstrukcí jazyka bývá
často problém, proto začneme zlehka. this je ukazatel na instanci
samotnou, musí být tedy stejného typu jako je třída. To lze demonstrovat
například u třídy Hrac, kde změníme konstruktor následovně:
Hrac.cpp
#include "Hrac.h" Hrac::Hrac(string _jmeno) { Hrac const * aktualni = this; jmeno = _jmeno; }
Pokud se pokusíme uložit ukazatel do libovolného jiného typu (například
int
), potom nám kompilátor zahlásí následující chybu (pro
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 nelze převést ukazatel typu Hrac * const
na
typ int *
. Zároveň nám tím kompilátor prozrazuje typ ukazatele
this. Jedná se o konstantní ukazatel (viz lekce o Konstantních
hodnotách). Můžeme měnit instanci, na kterou ukazuje, ale nemůžeme
měnit hodnotu ukazatele (const za hvězdičkou). Následující kód tedy
nebude validní:
this = new Hrac("Karel");
Kompilace zahlásí:
error C2106: '=': left operand must be l-value
Tím máme vyřešeno, co to vlastně this je. Nyní ještě musíme vyřešit, na co ukazuje.
Příklad s metodou hod()
Jak bylo řečeno, ukazuje na instanci samotnou. Pro příklad si upravíme
metodu hod()
na třídě Kostka tak, aby přijímala jako
parametr ukazatel 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; }
Nyní, když zavoláme v main.cpp metodu hod()
,
předáme jí ukazatel na samotnou instanci:
// main.cpp Kostka kostka; for (int i = 0; i < 10; i++) kostka.hod(&kostka); cout << endl;
A jak si dokážeme, že this
odkazuje skutečně na tuto
instanci? Porovnáme adresy odkazů - metodu hod()
upravíme
následovně 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 se zřejmě budou lišit, ale dvojice by měla být stejná.
Konzolová aplikace
Adresa this: 0x7ffc781864e0
Adresa parametru: 0x7ffc781864e0
Pokud vytvoříme kostky dvě, budou adresy rozdílné:
#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á aplikace
Adresa this: 0x7ffe01f06b40
Adresa parametru: 0x7ffe01f06b40
Adresa this: 0x7ffe01f06b30
Adresa parametru: 0x7ffe01f06b30
Zjednodušení názvů parametrů pomocí this
Co z toho plyne? O this
můžeme uvažovat jako o
ukazateli, který se odkazuje na instanci, pro kterou jsme metodu
volali. Tento ukazatel je přístupný ve všech metodách (včetně
konstruktorů a destruktorů) a toho my využijeme. Všechny úpravy kódu,
které jsme zatím provedli, přepíšeme zpět do původního stavu (nebo
postačí stáhnout projekt z minulé lekce).
Teď již můžeme odstranit ty škaredé názvy parametrů. V čem byl problém? Pokud jsme použili parametr se stejným názvem jako je atribut, tento parametr překryl atribut a pracovali jsme pouze s parametrem. Například pro kostku, pokud změníme konstruktor do následující 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 nějak říci, že chceme použít proměnnou z instance. Ale samotnou instanci přece máme v ukazateli 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)); }
Stejným způsobem upravíme i třídu Arena a Hrac. Tím jsme vlastně hotovi s praktickou částí v této lekci.
Používat nebo nepoužívat this
Do této lekce jsme o ukazateli this nevěděli a přesto jsme mohli měnit atributy tříd. Pokud neexistuje proměnná (nemusí se nutně jednat o parametr), který má stejný název jako atribut, this používat nemusíme (ale můžeme). Některé jazyky (jako Java nebo C#) pracují stejně jako C++ a nevyžadují použití this, pokud to není nutné. Naopak jiné jazyky (například PHP nebo Python) vyžadují, aby byl ukazatel pro přístup k atributu vždy použit. V C++ můžeme například destruktor arény napsat dvěma způsoby a oba budou fungovat.
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; }
Kterou variantu používat není přesně dáno a je na každém programátorovi, aby si zvolil. Osobně upřednostňuji druhou variantu (i když je delší), protože zřetelně vyjadřuje použití atributu na třídě. Proto tento zápis budu používat i dále v tutoriálu (ale není nutný).
Stejně jako můžeme přistupovat k atributům, můžeme i volat metody
instance. Například pokud bychom chtěli z konstruktoru (z jakéhokoliv
důvodu), zavolat metodu hod()
, můžeme to udělat pouze názvem
metody, nebo pomocí this. Oba přístupy jsou demonstrovány:
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í lekce kompletní.
V následujícím cvičení, Řešené úlohy k 5. lekci OOP v C++, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 53x (7.22 kB)
Aplikace je včetně zdrojových kódů v jazyce C++