Przeciążanie operatorów inkrementacji (++) i dekrementacji (--) jest całkiem proste, z jednym małym wyjątkiem. W rzeczywistości istnieją dwie wersje operatorów inkrementacji i dekrementacji: inkrementacja i dekrementacja przedrostkowa (np. ++x; --y;) oraz inkrementacja i dekrementacja postfiksowa (np. x++; y--;).
Ponieważ operatory inkrementacji i dekrementacji są operatorami jednoargumentowymi i modyfikują swoje operandy, najlepiej są przeciążane jako funkcje składowe. Najpierw zajmiemy się wersjami przedrostków, ponieważ są one najbardziej proste.
Przeciążanie inkrementacji i dekrementacji prefiksu
Inkrementacja i dekrementacja prefiksu są przeciążane dokładnie tak samo jak każdy normalny operator jednoargumentowy. Zrobimy to na przykładzie:
#include <iostream>
class Digit
{
private:
int m_digit{};
public:
Digit(int digit=0)
: m_digit{digit}
{
}
Digit& operator++();
Digit& operator--();
friend std::ostream& operator<< (std::ostream& out, const Digit& d);
};
Digit& Digit::operator++()
{
// If our number is already at 9, wrap around to 0
if (m_digit == 9)
m_digit = 0;
// otherwise just increment to next number
else
++m_digit;
return *this;
}
Digit& Digit::operator--()
{
// If our number is already at 0, wrap around to 9
if (m_digit == 0)
m_digit = 9;
// otherwise just decrement to next number
else
--m_digit;
return *this;
}
std::ostream& operator<< (std::ostream& out, const Digit& d)
{
out << d.m_digit;
return out;
}
int main()
{
Digit digit { 8 };
std::cout << digit;
std::cout << ++digit;
std::cout << ++digit;
std::cout << --digit;
std::cout << --digit;
return 0;
}Nasza klasa Digit przechowuje liczbę od 0 do 9. Przeciążyliśmy inkrementację i dekrementację, więc zwiększają/zmniejszają cyfrę, zawijając się, jeśli cyfra jest zwiększana/zmniejszana. zakres.
Ten przykład wyświetla:
89098
Zauważ, że zwracamy *this. Przeciążone operatory inkrementacji i dekrementacji zwracają bieżący ukryty obiekt, dzięki czemu można „połączyć” wiele operatorów razem.
Przeciążanie inkrementacji i dekrementacji postfiksu
Zwykle funkcje mogą zostać przeciążone, gdy mają tę samą nazwę, ale inną liczbę i/lub inny typ parametrów. Należy jednak rozważyć przypadek inkrementacji przedrostka i postfiksu oba operatory mają tę samą nazwę (np. operator++), są jednoargumentowe i przyjmują jeden parametr tego samego typu. Jak zatem można rozróżnić te dwa parametry podczas przeciążania?
Specyfikacja języka C++ zawiera specjalny przypadek, który zapewnia odpowiedź: kompilator sprawdza, czy przeciążony operator ma parametr int. Jeśli przeciążony operator ma parametr int, operator jest przeciążeniem typu postfix. Jeśli przeciążony operator nie ma parametru, jest to przedrostek przeciążenie.
Oto powyższa klasa Digit z przeciążeniem zarówno prefiksu, jak i postfiksu:
class Digit
{
private:
int m_digit{};
public:
Digit(int digit=0)
: m_digit{digit}
{
}
Digit& operator++(); // prefix has no parameter
Digit& operator--(); // prefix has no parameter
Digit operator++(int); // postfix has an int parameter
Digit operator--(int); // postfix has an int parameter
friend std::ostream& operator<< (std::ostream& out, const Digit& d);
};
// No parameter means this is prefix operator++
Digit& Digit::operator++()
{
// If our number is already at 9, wrap around to 0
if (m_digit == 9)
m_digit = 0;
// otherwise just increment to next number
else
++m_digit;
return *this;
}
// No parameter means this is prefix operator--
Digit& Digit::operator--()
{
// If our number is already at 0, wrap around to 9
if (m_digit == 0)
m_digit = 9;
// otherwise just decrement to next number
else
--m_digit;
return *this;
}
// int parameter means this is postfix operator++
Digit Digit::operator++(int)
{
// Create a temporary variable with our current digit
Digit temp{*this};
// Use prefix operator to increment this digit
++(*this); // apply operator
// return temporary result
return temp; // return saved state
}
// int parameter means this is postfix operator--
Digit Digit::operator--(int)
{
// Create a temporary variable with our current digit
Digit temp{*this};
// Use prefix operator to decrement this digit
--(*this); // apply operator
// return temporary result
return temp; // return saved state
}
std::ostream& operator<< (std::ostream& out, const Digit& d)
{
out << d.m_digit;
return out;
}
int main()
{
Digit digit { 5 };
std::cout << digit;
std::cout << ++digit; // calls Digit::operator++();
std::cout << digit++; // calls Digit::operator++(int);
std::cout << digit;
std::cout << --digit; // calls Digit::operator--();
std::cout << digit--; // calls Digit::operator--(int);
std::cout << digit;
return 0;
}To wypisuje
5667665
Dzieje się tu kilka interesujących rzeczy. Po pierwsze, zauważ, że odróżniliśmy przedrostek od operatorów postfiksowych, dostarczając fikcyjny parametr całkowity w wersji postfiksowej. Po drugie, ponieważ parametr fikcyjny nie jest używany w implementacji funkcji, nie nadaliśmy mu nawet nazwy This mówi kompilatorowi, aby traktował tę zmienną jako symbol zastępczy, co oznacza, że nie ostrzeże nas, że zadeklarowaliśmy zmienną, ale nigdy jej nie użyliśmy.
Po trzecie, zauważ, że operatory przedrostka i postfiksu wykonują tę samą pracę — oba zwiększają lub zmniejszają obiekt. Różnica między nimi polega na zwracanej wartości. Przeciążone operatory przedrostkowe zwracają obiekt po jego zwiększeniu lub zmniejszeniu. W związku z tym ich przeciążanie jest dość proste nasze zmienne składowe, a następnie zwróć *to.
Z drugiej strony operatory przyrostkowe muszą zwrócić stan obiektu przed jest on zwiększany lub zmniejszany. Prowadzi to do pewnej zagadki — jeśli zwiększymy lub zmniejszymy obiekt, nie będziemy w stanie zwrócić stanu obiektu przed jego zwiększeniem lub zmniejszeniem. Z drugiej strony, jeśli zwrócimy stan obiektu przed zwiększaj lub zmniejszaj, zwiększenie lub zmniejszenie nigdy nie zostanie wywołane.
Typowym sposobem rozwiązania tego problemu jest użycie zmiennej tymczasowej, która przechowuje wartość obiektu przed jej zwiększeniem lub zmniejszeniem. Następnie sam obiekt można zwiększać lub zmniejszać. Na koniec zmienna tymczasowa jest zwracana do osoby wywołującej. W ten sposób wywołujący otrzymuje kopię obiektu przed jego zwiększeniem lub zmniejszeniem, ale sam obiekt jest zwiększany lub zmniejszany. Należy pamiętać, że oznacza to, że wartość zwracana przez przeciążony operator musi być wartością niebędącą referencją, ponieważ nie możemy zwrócić referencji do zmiennej lokalnej, która zostanie zniszczona po wyjściu funkcji. Należy również pamiętać, że oznacza to, że operatory przyrostkowe są zazwyczaj mniej wydajne niż operatory przedrostkowe ze względu na dodatkowy narzut związany z tworzeniem instancji zmiennej tymczasowej i zwracaniem wartości zamiast referencji.
Na koniec zauważ, że napisaliśmy post-inkrementację i post-dekrementację w taki sposób, że wywołują one inkrementację wstępną i dekrementację wstępną, które wykonują większość pracy. Zmniejsza to liczbę duplikatów kodu i ułatwia modyfikowanie naszej klasy w przyszłości.

