NOVINKA: Získej 40 hodin praktických dovedností s AI – ZDARMA ke každému akreditovanému kurzu!

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.

Klikni pro editaci
  • App
    • Kostka.cpp
    • main.cpp
    • Kostka.h
  • 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);
    };
    
    • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

    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é:

    Klikni pro editaci
    • App
      • main.cpp
      • Kostka.h
      • Kostka.cpp
    • 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;
      }
      
      • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.


      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 54x (7.22 kB)
      Aplikace je včetně zdrojových kódů v jazyce C++

       

      Jak se ti líbí článek?
      Před uložením hodnocení, popiš prosím autorovi, co je špatněZnaků 0 z 50-500
      Předchozí článek
      Řešené úlohy k 3. a 4. lekci OOP v C++
      Všechny články v sekci
      Objektově orientované programování v C++
      Přeskočit článek
      (nedoporučujeme)
      Řešené úlohy k 5. lekci OOP v C++
      Článek pro vás napsal Patrik Valkovič
      Avatar
      Uživatelské hodnocení:
      38 hlasů
      Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
      Aktivity