10. díl - Přetěžování operátorů v C++

C a C++ C++ Objektově orientované programování Přetěžování operátorů v C++

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Konstantní metody v C++, jsme si popsali konstantní metody v C++. Tentokrát si v tutoriálu řekneme o přetěžování operátorů v C++ a nějaké přetížení si nadefinujeme.

Operátory

Co je to vlastně operátor? Operátory jsou operace, které v kódu provádíme pomocí speciálního zápisu. Téměř všechny operátory již znáte - aritmetické operace nad čísly, binární operace, inkrementace a spousta dalších. Jedná se o speciální zápis (+, && atd.) a jejich funkci si můžeme pro vlastní typy předefinovat. Seznam všech operátorů naleznete v tabulce.

Přiřazení inkrementace aritmetické logické porovnávací přístupové ostatní
a = b ++a +a !a a == b a[b] a(...)
a += b --a -a a && b a != b *a a, b
a -= b a++ a + b a || b a < b &a a ? b : c
a *= b a-- a - b   a > b a->b  
a /= b   a * b   a <= b a.b  
a %= b   a / b   a >= b a->*b  
a &= b   a % b     a.*b  
a |= b   ~a     a::b  
a ^= b   a & b        
a <<= b   a | b        
a >>= b   a ^ b        
    a << b        
    a >> b        

Ne všechny operátory lze předefinovat. C++ nepovolí předefinovat rozlišení scopu (::) a přístup do třídy (operátor tečky). Dále nejde nadefinovat nové operátory (například ** nebo <>). Dalším omezením je, že nemůžeme předefinovat prioritu operátorů (tj. krát se vždy vyhodnotí před plus). I přes tato omezení mají programátoři spoustu volnosti a jedná se o silnou konstrukci jazyka.

K čemu nám to vlastně všechno je? Můžeme například psát knihovnu, která dokáže reprezentovat nekonečně velká čísla (pro připomínku - pro typ int je maximální hodnota 2 147 483 647). Pro takovou knihovnu má smysl nadefinovat operace, které od čísel očekáváme (sčítání, násobení, porovnání a další). Operátory nicméně můžeme přetížit i v přeneseném významu, například jak to dělají objekty cin a cout. Ty mají přetížený operátor binárního posunu (<< a >>), pomocí kterých vypisují (nebo čtou) data do (z) konzole.

Přetěžování operátorů je velmi diskutované téma - existují jeho zastánci a odpůrci. Na jednu stranu ztrácíme informaci o akci. Takový operátor + na třídě Motyl nic moc neřekne, zatímco ekvivalentní metoda paritSe() již ano. Na druhou stranu je v jiných situacích (například u zmíněné numerické knihovny) použití operátorů naprosto přirozené. Některé jazyky (Java, JavaScript, Go) přetěžování operátorů vůbec nepřipouštějí, zatímco jiné jazyky (C#, Python, PHP) jej dovolují. Používejte tedy přetěžování operátorů s rozumem - mým doporučením je nadefinovat metodu, která vykoná požadovanou akci a k ní teprve dodefinovat operátor, který tuto metodu bude pouze volat. Dovolíte tím oba přístupy a uživatelé si budou moci vybrat, jakého přístupu využijí.

Parametry

Před tím, než si nějaký operátor napíšeme, si musíme říci něco o parametrech. Každý z operátorů vyžaduje trochu jiný typ parametru. Je to způsobeno použitím, například u porovnávacích operátorech (<, == aj.) předpokládáme, že nemění instanci - měly by mít tedy konstantní parametry. Na druhou stranu, operátor přiřazení (=) musí změnit aktuální instanci, ten tedy nemůže být konstantní. Správnou syntaxi můžete najít na wikipedii (bohužel pouze v angličtině). U každého operátoru je napsáno, zda jde nadefinovat a jak vypadá deklarace ve třídě a mimo třídu. Co to znamená, si řekneme hned.

Operátor mimo třídu

Pokud použijeme přetížení operátoru mimo třídu, píšeme vlastně funkci. Rozdíl oproti normální funkci je v názvu - pro operátory má speciální název. Vždy začíná klíčový slovem operator následovaným samotným operátorem. Například pro naši třídu Bojovnik si nadefinujeme operátor <<, abychom mohli vypsat bojovníka do konzole.

//Bojovnik.h
class Bojovnik
{
private:
    float zivot;
    float max_zivot;
    float utok;
    float obrana;
    Kostka &kostka;
public:
    Bojovnik(float zivot, float utok, float obrana, Kostka &kostka);
    bool nazivu() const;
    float utoc(Bojovnik &druhy) const;
    float getZivot() const;
    float getMaxZivot() const;
    float getUtok() const;
    float getObrana() const;
};

// deklarace
ostream& operator << (ostream &str, const Bojovnik &bojovnik);

// Bojovnik.cpp
ostream & operator<<(ostream & str, const Bojovnik & bojovnik)
{
    if (!bojovnik.nazivu())
    {
        str << "mrtev" << endl;
        return str;
    }
    str << "[";
    float pocet_zivotu_procent = bojovnik.getZivot() / bojovnik.getMaxZivot();
    for (double z = 0; z < 1.0; z += 0.1)
        str << (z < pocet_zivotu_procent ? '#' : ' ');
    str << "] (utok: " << bojovnik.getUtok() << ", obrana: " << bojovnik.getObrana() << ")" << endl;
    return str;
}

// main.cpp
Kostka kostka;
Bojovnik bojovnik(100, 8, 5, kostka);
cout << bojovnik;

Neděste se použití třídy ostream - cout je z ní odvozen. Vidíme typické rozhraní, které metoda musí splňovat - prvním parametrem musí být reference na ostream, druhým parametrem musí být konstantní reference na naši třídu. Návratovým typem je opět reference na ostream a vrací se hodnota z parametru. Výsledek:

Konzolová aplikace
[###########] (utok: 8, obrana: 5)

Je nutno podotknout, že se jedná se o jediný způsob, jak můžeme do konzole zapisovat pomocí cout (protože objekt cout už měnit nemůžeme). Ačkoliv je použita nová syntaxe, stále se jedná pouze o funkci - nemáme přístup k privátním členským proměnným!

Členské operátory

Druhý způsob, jak můžeme napsat vlastní operátor, je jako členskou metodu. Díky tomuto přístupu máme přístup i k privátním atributům, protože se nejedná o funkci, ale o metodu. Oproti funkci se zmenší počet parametrů, protože levý operand již máme - je v ukazateli this. Můžeme si například předefinovat operátor a > b tak, aby znamenal "a útočí na b". Potom by ale dávalo smysl mít i a < b jako "b útočí na a". Implementace je velmi jednoduchá - jen zavoláme metodu utoc() správným směrem.

class Bojovnik
{
//...zbytek implementace
    float operator > (Bojovnik& druhy) const;
    float operator < (const Bojovnik& druhy);
};

//Bojovnik.cpp
//...zbytek implementace
float Bojovnik::operator > (Bojovnik & druhy) const
{
    return this->utoc(druhy);
}

float Bojovnik::operator < (const Bojovnik & druhy)
{
    return druhy.utoc(*this);
}

Všimněte si použití konstant. V každém případě je konstantní něco jiného, protože v prvním případě se metoda volá na útočníkovi, zatímco ve druhém na obránci. Teď můžeme upravit implementaci v souboru Arena.cpp tak, aby naše nové operátory používala (pro výpis i pro souboj):

Arena.cpp

//...zbytek implementace
void Arena::vypis() const
{
    cout << "-------------- Arena --------------" << endl;
    cout << "Tah: " << this->tah << endl;
    cout << "Zdravi bojovniku:" << endl;
    for (int i = 0; i < this->pocet_hracu; i++)
    {
        cout << "\t" << this->hraci[i]->getJmeno() << ": "; //vypíšeme jméno bojovníka
        cout << this->hraci[i]->getBojovnik(); //použití << operátoru
    }
}

void Arena::zapas()
{
    while (!this->existujeVitaz())
    {
        this->vypis();
        for (int i = 0; i < this->pocet_hracu; i++)
        {
            if (!this->hraci[i]->getBojovnik().nazivu())
                continue;
            if (this->existujeVitaz())
                break;t
            int utok_na = (i + 1) % this->pocet_hracu;
            while (!this->hraci[utok_na]->getBojovnik().nazivu())
                utok_na = (utok_na + 1) % this->pocet_hracu;
            // POUŽITÍ > OPERÁTORU
            float zraneni = this->hraci[i]->getBojovnik() > this->hraci[utok_na]->getBojovnik();
            cout << this->hraci[i]->getJmeno() << " utoci na "
                << this->hraci[utok_na]->getJmeno() << " za " << zraneni << " zraneni" << endl;
        }
        this->tah++;
    }
}

Jak jsem varoval na začátku, změnili jsme význam operátoru >. To může být někdy problematické, protože pokud po nás bude program někdo číst, nemusí mu být hned jasné, co program dělá. Proto znovu zdůrazňuji, ať používáte operátory s rozumem.

Přetypování

Poslední věc, která s operátory úzce souvisí, je přetypování. Pokud má jít naše třída převést na jiný typ, musíme o tom dát kompilátoru vědět. Přetypování má trochu jinou syntaxi - nemá návratový typ (ten je na místě operátoru). Nejlépe opět poslouží ukázka - chceme převést hráče na řetězec:

Player.h

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    static const int MIN_DELKA_JMENA = 4;
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno() const;
    Bojovnik& getBojovnik();
    operator string() const; //operátor převodu na string
};

Hrac.cpp

//...zbytek implementace
Hrac::operator string() const
{
    return string("Hrac ") + this->jmeno;
}

//main.cpp
Hrac x("Karel", kostka);
string reprezentace = x;
cout << reprezentace << endl;

Hráč se sám převedl na typ string, aniž bychom ho do toho nějak nutili. Zajímavostí je, že pokud nadefinujeme přetypování na typ string nebo char*, potom máme v principu hotový operátor pro výstup, protože pouze zavoláme přetypování na string.

ostream & operator<<(ostream & str, const Hrac & hrac)
{
    str << (string)hrac;
    return str;
}

To je pro dnešní lekci vše. Vím, že se jedná o jeden z těch náročnějších dílů, ale pozitivní je, že si nemusíte vše pamatovat. Důležité věci jsou 3 - existuje nějaké přetěžování operátorů, používá se klíčové slovo operator a lze tím vyřešit i přetypování. V příští lekci, Kopírovací konstruktory v C++, se blíže podíváme na kopírovací konstruktor, který je velmi důležitý.


 

Stáhnout

Staženo 1x (10.68 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

 

Článek pro vás napsal patrik.valkovic
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Miniatura
Předchozí článek
Konstantní metody v C++
Miniatura
Následující článek
Kopírovací konstruktory v C++
Aktivity (6)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!