Přetížení binárních operátorů c. Operátory inkrementace a dekrementace. Interpretace binárních a unárních operátorů

Dnes se seznámíme s úžasnou vlastností našeho libovolného jazyka C++ – přetěžováním operátorů. Nejprve si ale ujasněme, proč je to potřeba.

S základní typy můžete použít libovolné operátory: +, -, *, ++, = a mnoho dalších. Například:

int a=2,b=3,c;
c = a + b;

Zde nad proměnnými zadejte int Nejprve se provede operace + a poté se výsledek přiřadí proměnné c pomocí operace =. Nemůžete to udělat s třídami. Vytvoříme jednoduchou třídu:

kód v jazyce c++ class Counter ( public: int counter; Counter() : c(0) () ); Čítač a,b,c; a.počítadlo = 2; b.počítadlo = 3; c = a + b;

Zde překladač vyhodí chybu na posledním řádku: neví přesně, jak se chovat při použití operací = a + na objektech třídy Counter. Dá se vyřešit tento problém takhle:

kód v jazyce c++ class Counter ( public: int counter; Counter() : count(0) () void AddCounters (Counter& a, Counter& b) ( counter = a.counter + b.counter; ) ); Čítač a,b,c; a.počítadlo = 2; b.počítadlo = 3; c.AddCounters(a,b);

Souhlasíte s použitím operací + a = v v tomto případě program by byl srozumitelnější. Takže tady je k použití standardní operace C++ s třídami je potřeba tyto operace přetížit.

Přetížení unárních operátorů

Začněme s jednoduché operace- unární. V první řadě nás zajímá přírůstek. Při použití této operace na základních typech lze jednu přidat nebo odečíst z proměnné:

kód v jazyce c++ int a=1; ++a; // a = 2; --A; // a = 1;

Nyní naučme třídu Counter používat předběžné zvýšení:

kód v jazyce c++ class Counter ( private: counter; public: Counter() : counter(0) () void operator++ () ( counter += 1; // Můžete také counter++ nebo ++counter - // v tomto případě je to jedno )); Počítadlo a; ++a; ++a; ++a;

Tento kód funguje. Při použití operace ++ (je důležité, aby tento znak byl před identifikátorem!) na objektu třídy Counter se zvýší proměnná čítač objektu a.

V v tomto příkladu přetížili jsme provoz ++. To se provádí vytvořením metody uvnitř třídy. Jediná důležitá vlastnost tato metoda- název identifikátoru. Název identifikátoru přetížených operací se skládá z klíčového slova operátor a názvu operace. Ve všech ostatních ohledech je tato metoda definována jako každá jiná.

Použijte přetížené operátory s vlastní typy velmi jednoduché - stejně jako s běžné typy data.

Přetížení operátoru Postfixu

Příklady operací postfixu:

int a = 3;
a++;
A--;

To znamená, že zde je znak operace umístěn za názvem identifikátoru. Chcete-li používat postfixové operace s vlastními datovými typy, potřebujete velmi málo:

kód v jazyce c++ public: void operator++ () ( counter += 1; ) void operator++ (int) ( counter += 1; )

Jediný rozdíl mezi operací prefixu a operací postfixu je klíčové slovo int v seznamu argumentů. Ale int není argument! Toto slovo říká, že operátor postfixu je přetížený. Nyní lze operátor ++ použít před i za identifikátorem objektu:

Počítadlo a;
++a;
++a;
a++;
a++;

Přetížení binárního operátoru

Přetížení dvouargumentových operátorů je velmi podobné přetížení binárních operátorů:

kód v jazyce c++ Operátor čítače+ (Počítadlo t) ( Součet čítače; součet.počítadlo = čítač + t.počítadlo; návratový součet; ) Čítač c1,c2,c3; c1.počítadlo = 3; c2.počítadlo = 2; c3 = cl + c2;

Která proměnná bude volat funkci operator+? V přetížených binárních operátorech se vždy volá metoda levého operandu. V tomto případě metoda operator+ volá objekt c1.

Metodě předáme argument. Argument je vždy správný operand.

Navíc v tomto případě musí operace + vrátit nějaký výsledek, aby jej bylo možné přiřadit k objektu c3. Vrátíme objekt Counter. Návratová hodnota je přiřazena proměnné c3.

Všimněte si, že jsme přetížili operátor +, ale nepřetížili operátor =! Samozřejmě musíte tuto metodu přidat do třídy Counter:

kód v jazyce c++ Operátor počítadla= (Počítadlo t) (Přiřazení počítadla; počítadlo = t.počítadlo; přiřadit.počítadlo = t.počítadlo; návrat přiřazení; )

Uvnitř metody jsme vytvořili další přiřazovací proměnnou. Tato proměnná se používá, abyste mohli pracovat s následujícím kódem:

Čítač cl(5),c2,c3;
c3 = c2 = cl;

Pro návratovou hodnotu však můžete použít elegantnější metody, se kterými se brzy seznámíme.

Dobrý den!

Chuť psát tento článek se objevil po přečtení příspěvku Přetížení operátorů C++, protože mnoho důležitých témat v něm nebylo pokryto.

Nejdůležitější je zapamatovat si, že přetížení operátora je jen více pohodlný způsob volání funkcí, takže se nenechte unést přetížením operátora. Mělo by se používat pouze tehdy, když to usnadní psaní kódu. Ale ne tolik, aby to znesnadňovalo čtení. Koneckonců, jak víte, kód se čte mnohem častěji, než je zapsán. A nezapomeňte, že nikdy nebudete moci přetěžovat operátory v tandemu s vestavěnými typy, přetížení je možné pouze pro uživatelem definované typy/třídy.

Syntaxe přetížení

Syntaxe pro přetěžování operátorů je velmi podobná definici funkce nazvané operátor@, kde @ je identifikátor operátora (například +, -,<<, >>). Uvažujme nejjednodušší příklad:
class Integer ( private: int value; public: Integer(int i): value(i) () const Integer operator+(const Integer& rv) const ( return (value + rv.value); ) );
V tomto případě je operátor orámován jako člen třídy, argument určuje hodnotu umístěnou na pravé straně operátoru. Obecně existují dva hlavní způsoby přetížení operátorů: globální funkce, které jsou přátelské ke třídě, nebo inline funkce třídy samotné. Která metoda je pro kterého operátora lepší, zvážíme na konci tématu.

Ve většině případů operátory (kromě podmíněných) vracejí objekt nebo odkaz na typ, ke kterému patří jeho argumenty (pokud se typy liší, pak se rozhodnete, jak interpretovat výsledek vyhodnocení operátorů).

Přetížení unárních operátorů

Podívejme se na příklady přetížení unárních operátorů pro třídu Integer definovanou výše. Zároveň je definujme ve formě přátelských funkcí a zvažte operátory dekrementace a inkrementace:
class Integer ( private: int value; public: Integer(int i): value(i) () //unary + friend const Integer& operator+(const Integer& i); //unary - friend const Integer operator-(const Integer& i) ; //přírůstek předpony friend const Integer& operator++(Integer& i); postfixový přírůstek friend const Integer operator++(Integer& i, int);
//předpona dekrementovat friend const Integer& operator--(Integer& i);

//postfix dekrementuje friend const Integer operator--(Integer& i, int); ); //unární plus nedělá nic. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //verze prefixu vrátí hodnotu po přírůstku const Integer& operator++(Integer& i) ( i.value++; return i; ) //verze postfixu vrátí hodnotu před přírůstkem const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //verze prefixu vrátí hodnota po dekrementaci const Integer& operátor--(Celé číslo& i) ( i.hodnota--; return i; ) //verze postfixu vrátí hodnotu před snížením const Integer operator--(Integer& i, int) ( Integer oldValue(i. hodnota i .hodnota--;

Nyní víte, jak kompilátor rozlišuje mezi prefixovou a postfixovou verzí dekrementace a inkrementace. V případě, že vidí výraz ++i, zavolá se operátor funkce ++(a). Pokud vidí i++, zavolá se operátor ++(a, int). To znamená, že se volá funkce přetíženého operátora++ a k tomu slouží parametr dummy int ve verzi postfixu. Binární operátory Podívejme se na syntaxi přetěžování binárních operátorů. Pojďme přetížit jeden operátor, který vrací l-hodnotu, one
podmíněný operátor
a jeden operátor vytvářející novou hodnotu (definujme je globálně): class Integer ( private: int value; public: Integer(int i): value(i) () friend const Integer operator+(const Integer& left, const Integer& right); friend Integer& operator+=(Integer& left, const Integer& right); friend bool operator==(const Integer& left, const Integer& right); const Integer operator+(const Integer& left, const Integer& right) ( return Integer(left.value + right.value); ) Integer& operator+=(Integer& left, const Integer& right) ( left.value += right.value; return left; ) bool operator==(const Integer& left, const Integer& right) ( return left.value == right.value; ) Ve všech těchto příkladech jsou operátoři přetíženi pro stejný typ, to však není nutné. Můžete například přetížit přidání našeho

zadejte Integer

a definován ve své podobě Float. Argumenty a návratové hodnoty Jak vidíte, příklady používají
  • Pokud není argument modifikován operátorem, například v případě unárního plus, musí být předán jako odkaz na konstantu. Obecně to platí téměř pro všechny aritmetické operátory(sčítání, odčítání, násobení...)
  • Typ návratové hodnoty závisí na povaze operátora. Pokud musí operátor vrátit novou hodnotu, musí být vytvořen nový objekt (jako v případě binárního plus). Pokud chcete zabránit tomu, aby byl objekt upraven jako l-hodnota, musíte jej vrátit jako konstantu.
  • Operátoři přiřazení musí vrátit odkaz na změněný prvek. Také, pokud chcete použít operátor přiřazení v konstrukcích jako (x=y).f(), kde je funkce f() volána pro proměnnou x, po jejím přiřazení k y, pak nevracejte odkaz na konstantní, stačí vrátit odkaz.
  • Logické operátory by měly v nejhorším případě vrátit int a v lepším případě bool.

Optimalizace návratové hodnoty

Při vytváření nových objektů a jejich vracení z funkce byste měli použít zápis podobný výše popsanému příkladu binárního operátoru plus.
return Integer(left.value + right.value);
Abych byl upřímný, nevím, jaká situace je relevantní pro C++11, všechny další argumenty jsou platné pro C++98.
Na první pohled to vypadá podobně jako syntaxe pro vytvoření dočasného objektu, to znamená, že mezi výše uvedeným kódem a tímto není žádný rozdíl:
Integer temp(left.value + right.value); návratová teplota;
Ale ve skutečnosti se v tomto případě na prvním řádku zavolá konstruktor, pak se zavolá kopírovací konstruktor, který objekt zkopíruje, a pak při rozbalování zásobníku se zavolá destruktor. Při použití první položky kompilátor nejprve vytvoří objekt v paměti, do které je třeba jej zkopírovat, čímž ušetří volání konstruktoru a destruktoru kopírování.

Speciální operátoři

C++ má operátory, které mají specifickou syntaxi a metody přetěžování. Například operátor indexování. Je vždy definován jako člen třídy, a protože indexovaný objekt se má chovat jako pole, měl by vrátit odkaz.
Čárkový operátor
Mezi "speciální" operátory patří také operátor čárka. Je volána u objektů, které mají vedle sebe čárku (ale není volána na seznamech argumentů funkcí). Vymyslet smysluplný případ použití pro tohoto operátora není snadné. Habrowser AxisPod v komentářích k předchozímu článku o přetížení hovořil o jedné věci.
Operátor dereference ukazatele
Přetížení těchto operátorů může být ospravedlnitelné pro třídy inteligentních ukazatelů. Tento operátor je nutně definován jako funkce třídy a jsou na něj kladena určitá omezení: musí vracet buď objekt (nebo odkaz) nebo ukazatel, který umožňuje přístup k objektu.
Operátor přiřazení
Operátor přiřazení je nutně definován jako funkce třídy, protože je vnitřně propojen s objektem nalevo od "=". Definování operátoru přiřazení v globálně by umožnilo přepsat výchozí chování operátoru "=". Příklad:
class Integer ( private: int value; public: Integer(int i): value(i) () Integer& operator=(const Integer& right) ( //kontrola samopřiřazení if (this == &right) ( return *this; ) hodnota = vpravo.hodnota return *toto;

Jak vidíte, na začátku funkce je provedena kontrola samopřiřazení. Obecně je v tomto případě sebepřivlastňování neškodné, ale ne vždy je situace tak jednoduchá. Pokud je objekt například velký, můžete ztrácet spoustu času zbytečným kopírováním nebo při práci s ukazateli.

Nepřetížitelné operátory
Některé operátory v C++ nejsou vůbec přetížené. Zřejmě se tak stalo z bezpečnostních důvodů.
  • Operátor výběru členů třídy ".".
  • Operátor pro dereferencování ukazatele na člena třídy ".*"
  • C++ nemá operátor umocňování (jako ve Fortranu) "**".
  • Je zakázáno definovat vlastní operátory (může dojít k problémům s určením priorit).
  • Priority operátora nelze změnit
Jak jsme již zjistili, existují dva způsoby operátorů - jako třídní funkce a jako přátelská globální funkce.
Rob Murray ve své knize C++ Strategies and Tactics definoval následující doporučení podle volby operátora:

proč tomu tak je? Za prvé, někteří operátoři jsou zpočátku omezeni. Obecně platí, že pokud neexistuje žádný sémantický rozdíl v tom, jak je operátor definován, je lepší jej navrhnout jako třídní funkci pro zdůraznění spojení a navíc funkce bude inline. Navíc někdy může být potřeba reprezentovat levý operand jako objekt jiné třídy. Asi nejvýraznějším příkladem je redefinice<< и >> pro I/O streamy.

Literatura

Bruce Eckel - C++ filozofie. Úvod do standardu C++.

Štítky: Přidat štítky

Poslední aktualizace: 20.10.2017

Přetížení operátora vám umožňuje definovat akce, které operátor provede. Přetížení zahrnuje vytvoření funkce, jejíž název obsahuje slovo operátor a symbol přetěžovaného operátoru. Operátorovou funkci lze definovat jako člen třídy nebo mimo třídu.

Přetížit můžete pouze operátory, které jsou již definovány v C++. Nemůžete vytvářet nové operátory.

Pokud je funkce operátora definována jako samostatná funkce a není členem třídy, pak se počet parametrů takové funkce shoduje s počtem operandů operátoru. Například funkce, která představuje unární operátor, bude mít jeden parametr a funkce, která představuje binární operátor, bude mít dva parametry. Pokud operátor vezme dva operandy, pak je první operand předán prvnímu parametru funkce a druhý operand je předán druhému parametru. V tomto případě musí alespoň jeden z parametrů reprezentovat typ třídy

Podívejme se na příklad s třídou Counter, která představuje stopky a ukládá počet sekund:

#zahrnout << seconds << " seconds" << std::endl; } int seconds; }; Counter operator + (Counter c1, Counter c2) { return Counter(c1.seconds + c2.seconds); } int main() { Counter c1(20); Counter c2(10); Counter c3 = c1 + c2; c3.display(); // 30 seconds return 0; }

Zde funkce operátora není součástí třídy Counter a je definována mimo ni. Tato funkce přetěžuje operátor sčítání pro typ čítače. Je binární, takže vyžaduje dva parametry. V tomto případě přidáváme dva objekty Counter. Funkce také vrátí objekt Counter, který ukládá celkový počet sekund. To znamená, že operace sčítání zde spočívá v přidání sekund obou objektů:

Operátor čítače + (počítadlo c1, čítač c2) ( return Counter(c1.seconds + c2.seconds); )

Není nutné vracet objekt třídy. Může to být i objekt vestavěného primitivního typu. A můžeme také definovat další přetížení operátorů:

Int operátor + (Counter c1, int s) ( return c1.seconds + s; )

Tato verze přidá k číslu objekt Counter a také vrátí číslo. Proto levý operand operace musí být typu Counter a pravý operand musí být typu int. A tuto verzi operátoru můžeme použít například takto:

Čítač cl(20); int sekund = c1 + 25; // 45 std::cout<< seconds << std::endl;

Operátorové funkce lze také definovat jako členy tříd. Pokud je funkce operátoru definována jako člen třídy, pak je levý operand přístupný přes tento ukazatel a představuje aktuální objekt a pravý operand je předán podobné funkci jako jediný parametr:

#zahrnout class Counter ( public: Counter(int sec) (seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter operator + (Counter c2) { return Counter(this->sekundy + c2.sekundy);

) operátor int + (int s) ( return this->seconds + s; ) int seconds; ); int main() ( Counter c1(20); Counter c2(10); Counter c3 = c1 + c2; c3.display(); // 30 sekund int seconds = c1 + 25; // 45 return 0; )

V tomto případě přistupujeme k levému operandu v operátorských funkcích přes tento ukazatel.

Kteří operátoři by se měli kde předefinovat? Operátory přiřazení, indexování (), volání (()), přístup ke členu třídy pomocí ukazatele (->) by měly být definovány jako funkce členů třídy. Operátory, které mění stav objektu nebo jsou s objektem přímo spojeny (inkrementovat, dekrementovat,) jsou obvykle také definovány jako členské funkce třídy. Všechny ostatní operátory jsou často definovány jako jednotlivé funkce spíše než členové třídy.

Porovnávací operátory< надо также определять функцию для оператора >Řada operátorů je přetížena ve dvojicích. Pokud například definujeme operátor ==, musíme definovat také operátor !=. A při definování operátora

. Přetěžme například tyto operátory:< (Counter c1, Counter c2) { return c1.seconds < c2.seconds; } int main() { Counter c1(20); Counter c2(10); bool b1 = c1 == c2; // false bool b2 = c1 >Boolský operátor == (Čítadlo c1, Počítadlo c2) ( návrat c1.sekundy == c2.sekundy; ) boolovský operátor != (Čítadlo c1, čítač c2) ( návrat c1.sekundy != c2.sekundy; ) boolovský operátor > ( Čítač c1, Čítač c2) ( návrat c1.seconds > c2.seconds; ) boolův operátor<< b1 << std::endl; std::cout << b2 << std::endl; return 0; }

c2; // true std::cout

#zahrnout class Counter ( public: Counter(int sec) (seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } Counter& operator += (Counter c2) { seconds += c2.seconds; return *this; } int seconds; }; int main() { Counter c1(20); Counter c2(10); c1 += c2; c1.display(); // 30 seconds return 0; }

Operátoři přiřazení

Operace zvýšení a snížení

#zahrnout class Counter ( public: Counter(int sec) (seconds = sec; ) void display() ( std::cout<< seconds << " seconds" << std::endl; } // префиксные операторы Counter& operator++ () { seconds += 5; return *this; } Counter& operator-- () { seconds -= 5; return *this; } // постфиксные операторы Counter operator++ (int) { Counter prev = *this; ++*this; return prev; } Counter operator-- (int) { Counter prev = *this; --*this; return prev; } int seconds; }; int main() { Counter c1(20); Counter c2 = c1++; c2.display(); // 20 seconds c1.display(); // 25 seconds --c1; c1.display(); // 20 seconds return 0; }

Předefinování operátorů inkrementace a dekrementace může být obzvláště náročné, protože pro tyto operátory potřebujeme definovat jak předponu, tak i postfix. Pojďme definovat podobné operátory pro typ Counter:

Counter& operator++ () ( sekund += 5; return *this; )

V samotné funkci můžete definovat nějakou logiku pro inkrementaci hodnoty. V tomto případě se počet sekund zvýší o 5.

Postfixové operátory musí vrátit hodnotu objektu před inkrementací, tedy předchozí stav objektu. Aby se formulář s příponou lišil od předponové formy, dostávají verze přípony další parametr typu int, který se nepoužívá. I když v zásadě to můžeme použít.

Dobrý den!

Operátor počítadla++ (int) (Předchozí počítadlo = *toto; ++*toto; vrátit předchozí; )

Nejdůležitější je zapamatovat si, že přetížení operátora je jen pohodlnější způsob volání funkcí, takže se nenechte unést přetížením operátora. Mělo by se používat pouze tehdy, když to usnadní psaní kódu. Ale ne tolik, aby to ztěžovalo čtení. Koneckonců, jak víte, kód se čte mnohem častěji, než je zapsán. A nezapomeňte, že nikdy nebudete moci přetěžovat operátory v tandemu s vestavěnými typy, přetížení je možné pouze pro uživatelem definované typy/třídy.

Syntaxe přetížení

Syntaxe pro přetěžování operátorů je velmi podobná definici funkce nazvané operátor@, kde @ je identifikátor operátora (například +, -,<<, >>). Podívejme se na jednoduchý příklad:
class Integer ( private: int value; public: Integer(int i): value(i) () const Integer operator+(const Integer& rv) const ( return (value + rv.value); ) );
V tomto případě je operátor orámován jako člen třídy, argument určuje hodnotu umístěnou na pravé straně operátoru. Obecně existují dva hlavní způsoby přetížení operátorů: globální funkce, které jsou přátelské ke třídě, nebo inline funkce třídy samotné. Která metoda je pro kterého operátora lepší, zvážíme na konci tématu.

Ve většině případů operátory (kromě podmíněných) vracejí objekt nebo odkaz na typ, ke kterému patří jeho argumenty (pokud se typy liší, pak se rozhodnete, jak interpretovat výsledek vyhodnocení operátorů).

Přetížení unárních operátorů

Podívejme se na příklady přetížení unárních operátorů pro třídu Integer definovanou výše. Zároveň je definujme ve formě přátelských funkcí a zvažte operátory dekrementace a inkrementace:
class Integer ( private: int value; public: Integer(int i): value(i) () //unary + friend const Integer& operator+(const Integer& i); //unary - friend const Integer operator-(const Integer& i) ; //přírůstek předpona friend const Integer& operator++(Integer& i) //přírůstek za předponu přítel const Integer operator++(Integer& i, int); i, int); //unární plus nedělá nic. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //verze prefixu vrátí hodnotu po přírůstku const Integer& operator++(Integer& i) ( i.value++; return i; ) //verze postfixu vrátí hodnotu před přírůstkem const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //verze prefixu vrátí hodnota po dekrementaci const Integer& operátor--(Celé číslo& i) ( i.hodnota--; return i; ) //verze postfixu vrátí hodnotu před snížením const Integer operator--(Integer& i, int) ( Integer oldValue(i. hodnota i .hodnota--;
//předpona dekrementovat friend const Integer& operator--(Integer& i);

//postfix dekrementuje friend const Integer operator--(Integer& i, int); ); //unární plus nedělá nic. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //verze prefixu vrátí hodnotu po přírůstku const Integer& operator++(Integer& i) ( i.value++; return i; ) //verze postfixu vrátí hodnotu před přírůstkem const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //verze prefixu vrátí hodnota po dekrementaci const Integer& operátor--(Celé číslo& i) ( i.hodnota--; return i; ) //verze postfixu vrátí hodnotu před snížením const Integer operator--(Integer& i, int) ( Integer oldValue(i. hodnota i .hodnota--;

Podívejme se na syntaxi přetěžování binárních operátorů. Pojďme přetížit jeden operátor, který vrací l-hodnotu, jeden podmíněný operátor a jeden operátor, který vytváří novou hodnotu (definujme je globálně):
podmíněný operátor
Ve všech těchto příkladech jsou operátoři přetíženi pro stejný typ, to však není nutné. Můžete například přetížit sčítání našeho typu Integer a Float definovaného jeho podobností.

zadejte Integer

Jak vidíte, příklady používají různé způsoby předávání argumentů funkcím a vracení hodnot operátorů.
  • Pokud není argument modifikován operátorem, například v případě unárního plus, musí být předán jako odkaz na konstantu. Obecně to platí pro téměř všechny aritmetické operátory (sčítání, odčítání, násobení...)
  • Typ návratové hodnoty závisí na povaze operátora. Pokud musí operátor vrátit novou hodnotu, musí být vytvořen nový objekt (jako v případě binárního plus). Pokud chcete zabránit tomu, aby byl objekt upraven jako l-hodnota, musíte jej vrátit jako konstantu.
  • Operátoři přiřazení musí vrátit odkaz na změněný prvek. Také, pokud chcete použít operátor přiřazení v konstrukcích jako (x=y).f(), kde je funkce f() volána pro proměnnou x, po jejím přiřazení k y, pak nevracejte odkaz na konstantní, stačí vrátit odkaz.
  • Logické operátory by měly v nejhorším případě vrátit int a v lepším případě bool.

Optimalizace návratové hodnoty

Při vytváření nových objektů a jejich vracení z funkce byste měli použít zápis podobný výše popsanému příkladu binárního operátoru plus.
return Integer(left.value + right.value);
Abych byl upřímný, nevím, jaká situace je relevantní pro C++11, všechny další argumenty jsou platné pro C++98.
Na první pohled to vypadá podobně jako syntaxe pro vytvoření dočasného objektu, to znamená, že mezi výše uvedeným kódem a tímto není žádný rozdíl:
Integer temp(left.value + right.value); návratová teplota;
Ale ve skutečnosti se v tomto případě na prvním řádku zavolá konstruktor, pak se zavolá kopírovací konstruktor, který objekt zkopíruje, a pak při rozbalování zásobníku se zavolá destruktor. Při použití první položky kompilátor nejprve vytvoří objekt v paměti, do které je třeba jej zkopírovat, čímž ušetří volání konstruktoru a destruktoru kopírování.

Speciální operátoři

C++ má operátory, které mají specifickou syntaxi a metody přetěžování. Například operátor indexování. Je vždy definován jako člen třídy, a protože indexovaný objekt se má chovat jako pole, měl by vrátit odkaz.
Čárkový operátor
Mezi "speciální" operátory patří také operátor čárka. Je volána u objektů, které mají vedle sebe čárku (ale není volána na seznamech argumentů funkcí). Vymyslet smysluplný případ použití pro tohoto operátora není snadné. Habrauser v komentářích k předchozímu článku o přetížení.
Operátor dereference ukazatele
Přetížení těchto operátorů může být ospravedlnitelné pro třídy inteligentních ukazatelů. Tento operátor je nutně definován jako funkce třídy a jsou na něj kladena určitá omezení: musí vracet buď objekt (nebo odkaz) nebo ukazatel, který umožňuje přístup k objektu.
Operátor přiřazení
Operátor přiřazení je nutně definován jako funkce třídy, protože je vnitřně propojen s objektem nalevo od "=". Globální definování operátoru přiřazení by umožnilo přepsat výchozí chování operátoru "=". Příklad:
class Integer ( private: int value; public: Integer(int i): value(i) () Integer& operator=(const Integer& right) ( //kontrola samopřiřazení if (this == &right) ( return *this; ) hodnota = vpravo.hodnota return *toto;

Jak vidíte, na začátku funkce je provedena kontrola samopřiřazení. Obecně je v tomto případě sebepřivlastňování neškodné, ale ne vždy je situace tak jednoduchá. Pokud je objekt například velký, můžete ztrácet spoustu času zbytečným kopírováním nebo při práci s ukazateli.

Nepřetížitelné operátory
Některé operátory v C++ nejsou vůbec přetížené. Zřejmě se tak stalo z bezpečnostních důvodů.
  • Operátor výběru členů třídy ".".
  • Operátor pro dereferencování ukazatele na člena třídy ".*"
  • C++ nemá operátor umocňování (jako ve Fortranu) "**".
  • Je zakázáno definovat vlastní operátory (může dojít k problémům s určením priorit).
  • Priority operátora nelze změnit
Jak jsme již zjistili, existují dva způsoby operátorů - jako třídní funkce a jako přátelská globální funkce.
Rob Murray ve své knize C++ Strategies and Tactics definuje následující pokyny pro výběr formy operátora:

proč tomu tak je? Za prvé, někteří operátoři jsou zpočátku omezeni. Obecně platí, že pokud neexistuje žádný sémantický rozdíl v tom, jak je operátor definován, je lepší jej navrhnout jako třídní funkci pro zdůraznění spojení a navíc funkce bude inline. Navíc někdy může být potřeba reprezentovat levý operand jako objekt jiné třídy. Asi nejvýraznějším příkladem je redefinice<< и >> pro I/O streamy.

Literatura

Bruce Eckel - C++ filozofie. Úvod do standardu C++.

Štítky:

  • C++
  • přetěžování operátorů
  • přetížení operátora
Přidejte značky

Mnoho programovacích jazyků používá operátory: minimálně přiřazení (=, := nebo podobné) a aritmetické operátory (+, -, * a /). Ve většině staticky typovaných jazyků jsou tyto operátory vázány na typy. Například v Javě je sčítání pomocí operátoru + možné pouze pro celá čísla, čísla s plovoucí desetinnou čárkou a řetězce. Pokud definujeme vlastní třídy pro matematické objekty, jako jsou matice, můžeme implementovat metodu pro jejich sčítání, ale můžeme ji volat pouze takto: a = b.add(c) .

V C++ takové omezení neexistuje – přetížit můžeme téměř jakýkoli známý operátor. Možnosti jsou nekonečné: můžete si vybrat libovolnou kombinaci typů operandů, jediným omezením je, že musí být přítomen alespoň jeden uživatelsky definovaný typ operandu. To znamená definovat nový operátor nad vestavěnými typy nebo přepsat existující je to zakázáno.

Kdy přetížit operátory?

Pamatujte na hlavní věc: přetěžujte operátory tehdy a jen tehdy, když to dává smysl. Tedy pokud je smysl přetížení zřejmý a neobsahuje skrytá překvapení. Přetížení operátoři se musí chovat stejně jako jejich základní verze. Výjimky jsou samozřejmě povoleny, ale pouze v případech, kdy jsou doprovázeny jasným vysvětlením. Dobrým příkladem jsou operátoři<< и >> standardní knihovna iostream, které se zjevně nechovají jako běžné operátory.

Zde jsou dobré a špatné příklady přetěžování operátorů. Výše uvedené přidání matice je ilustrativní případ. Zde je přetížení operátoru sčítání intuitivní a pokud je implementováno správně, nevyžaduje vysvětlení:

Matice a, b; Matice c = a + b;

Příkladem špatného přetížení operátora přidávání by bylo přidání dvou objektů hráče ve hře. Co tím tvůrce třídy myslel? Jaký bude výsledek? Nevíme, co operace dělá, a proto je použití tohoto operátoru nebezpečné.

Jak přetížit operátory?

Přetěžování operátorů je podobné přetěžování funkcí se speciálními názvy. Ve skutečnosti, když kompilátor vidí výraz, který obsahuje operátor a uživatelsky definovaný typ, nahradí výraz voláním odpovídající funkce přetíženého operátoru. Většina jejich názvů začíná operátorem klíčového slova, za kterým následuje označení odpovídajícího operátoru. Pokud se zápis neskládá ze speciálních znaků, například v případě převodu typu nebo operátoru správy paměti (nový, smazat atd.), musí být slovo operátor a zápis operátoru odděleny mezerou (operátor nový) , jinak může být mezera ignorována (operátor+ ).

Většina operátorů může být přetížena buď metodami tříd nebo jednoduchými funkcemi, ale existuje několik výjimek. Když je přetížený operátor metodou třídy, typ prvního operandu musí být tato třída (vždy *this) a druhý musí být deklarován v seznamu parametrů. Příkazy metody navíc nejsou statické, s výjimkou příkazů správy paměti.

Když přetížíte operátor v metodě třídy, získá přístup k soukromým polím třídy, ale skrytý převod prvního argumentu není dostupný. Binární funkce jsou proto obvykle přetěžovány jako volné funkce. Příklad:

Class Rational ( public: //Konstruktor lze použít pro implicitní převod z int: Rational(int čitatel, int jmenovatel = 1); Racionální operátor+(Rational const& rhs) const;); int main() ( Racionální a, b, c; int i; a = b + c; //ok, není nutná konverze a = b + i; //ok, implicitní konverze druhého argumentu a = i + c; //CHYBA: první argument nelze implicitně převést)

Když jsou unární operátory přetíženy jako volné funkce, je jim k dispozici konverze skrytých argumentů, která se však obvykle nepoužívá. Na druhou stranu je tato vlastnost nezbytná pro binární operátory. Proto by hlavní rada byla následující:

Implementujte unární operátory a binární operátory jako „ X=” ve formě metod tříd a dalších binárních operátorů – ve formě volných funkcí.

Kteří operátoři mohou být přetíženi?

Můžeme přetížit téměř jakýkoli operátor C++, s výhradou následujících výjimek a omezení:

  • Nemůžete definovat nový operátor, například operátor** .
  • Následující operátory nelze přetížit:
    1. ?: (ternární operátor);
    2. :: (přístup k vnořeným názvům);
    3. . (přístup k polím);
    4. .* (přístup k polím pomocí ukazatele);
    5. Operátory sizeof , typeid a cast.
  • Následující operátory lze přetížit pouze jako metody:
    1. = (zadání);
    2. -> (přístup k polím pomocí ukazatele);
    3. () (volání funkce);
    4. (přístup pomocí indexu);
    5. ->* (přístup k ukazateli na pole ukazatelem);
    6. operátory konverze a správy paměti.
  • Počet operandů, pořadí provádění a asociativita operátorů je určena standardní verzí.
  • Alespoň jeden operand musí být typu uživatele. Typedef se nepočítá.

Další část představí přetížení operátorů C++, ve skupinách i jednotlivě. Každý úsek je charakterizován sémantikou, tzn. očekávané chování. Kromě toho budou ukázány typické způsoby deklarování a implementace operátorů.




Nahoru