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

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.

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:

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++

 

Predchádzajúci článok
Riešené úlohy k 3. a 4. lekciu OOP v C ++
Všetky články v sekcii
Objektovo orientované programovanie v C ++
Preskočiť článok
(neodporúčame)
Riešené úlohy k 5. lekcii OOP 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