Pisząc program w C++ można korzystać zarówno z typów wbudowanych jak i z typów zdefiniowanych przez siebie (jako nowe klasy).
Wszystkie typy wbudowane mają zdefiniowane operatory porównania i przypisania, inkrementacji czy dekrementacji; tablice, wskaźniki czy referencje również posiadają zestaw operatorów ułatwiających (czy nieraz wręcz umożliwiających korzystanie z ich własności).
Dla typów zdefiniowanych przez programistę można zdefiniować (przeciążyć) operatory istniejące w języku C++ dla danego typu, dzięki czemu stosowanie ich w kontekście tych typów ma sens. Jednym z typowych przykładów przeciążania operatorów jest przeładowanie operatora wstawiania i pobierania danych ze strumienia:
I definicja w pliku Point.cpp
Powyższy przykład umożliwia wczytanie punktu podanego w formacie (12, 22). Należy zwrócić uwagę, że operator „»” nie może być metodą klasy a funkcją zaprzyjaźnioną jeśli chcemy wywołać go jako
cin >> punkt;
Jeśli operator zadeklarowany byłby jako metoda klasy trzeba by go wywoływać na przykład tak:
punkt >> cin
Operatory to „zwyczajne” funkcje w języku C++. Kompilator zamienia każde pojawienie się operatora na wywołanie odpowiedniej funkcji.
Zakładając że operatory + oraz * są składowymi klas, tak będzie wyglądało użycie tych operatorów dla kompilatora:
Można także zdefiniować niektóre operatory jako funkcje nieskładowe (jak w przykładzie z poprzedniej sekcji). Zakłądjąc że operatory + i * są zadeklarowane jako funkcje zaprzyjaźnione, tak będzie wyglądało użycie tych operatorów dla kompilatora:
Operatory jednoargumentowe można przeciążyć za pomocą niestatycznej funkcji składowej nie pobierającej żadnych argumentów (jeden niejawny argument jest przekazywany jako this) lub za pomocą funkcji nie będącej składową klasy, pobierającą jeden argument, który jest obiektem (przekazywanie przez wartość) lub referencją do obiektu tej klasy. Wyjątkiem jest operator rzutowania, który NIE może być zadeklarowany jako funkcja nieskładowa.
Funkcje nieskładowe mogą być dodatkowo deklarowane jako zaprzyjaźnione z klasą dla której są zdefiniowane wtedy mają dostęp do prywatnych składników klasy.
Załóżmy, że klasa Text jest naszą wersją klasy string, przechowującej napisy i umożliwiającą manipulowanie nimi. Chcąc zdefiniować na przykład operator ! (logiczne zaprzeczenie) dla klasy Napis, który będzie zwracał true jeśli obiekt będzie zawierał pusty napis, a w każdym innym przypadku true, możemy to zrobić na da sposoby:
Operatory dwuargumentowe można przeciążyć za pomocą niestatycznej funkcji składowej klasy pobierającej jeden argument (pierwszy argument jest przekazywany niejawnie jako this) lub za pomocą funkcji nie będącej składową klasy, pobierającą dwa argumenty z których jeden jest obiektem lub referencją do obiektu tej klasy. Wyjątkami są operatory przypisania, [] oraz →, które NIE mogą być zadeklarowane jako funkcje zaprzyjaźnione.
Specyficznym rodzajem operatorów są tzw. operatory rzutowania. Wykorzystywane one są do konwersji typów, czy inaczej mówiąc do rzutowania. W prosty sposób można rzutować pomiędzy typami wbudowanymi:
Aby możliwe było rzutowanie pomiędzy typami zdefiniowanymi przez programistę, konieczna jest definicja operatorów realizujących taką funkcjonalność. Operatory rzutowania NIE mogą być zadeklarowane jako funkcje zaprzyjaźnione - muszą być składowymi klasy. Dodatkowo niczego nie zwracają:
Dzięki powyższej deklaracji i odpowiedniej definicji możliwe będzie następujące wywołanie:
Complex c("4i5"); //Powinien utworzyć macierz 1x1 z wartością 4i5 w jedynej komórce Matrix m = (Matrix)c;
Operatory ++ i - - mogą również być przeciążane. Mogą one być jednak stosowane zarówno w wersji prefiksowej i postfiksowej.
Chcąc przeładować operator inkrementacji dla klasy Complex tak aby można było wykonać następującą operację:
Complex c(1); ++c; //kompilator tak anprawdę wywołuje c.operator++()
należy zadeklarować operator ++ w następujcy sposób:
class Complex{ public: Complex &operator++(); };
Aby możliwe było wywołanie inkrementacji w notacji postfiksowej:
Complex c(1); c++; //kompilator tak naprawdę wywołuje c.operator++(0)
konieczne jest zadeklarowanie operatora ++ w następujący sposób:
class Complex{ public: // parametr "int" jest "dowolnym argumentem" który jest wykorzystywany // przez kompilator do odróżnienia operatora postfiksowego od // operatora prefiksowego. Podczas wywołania c++ // kompilator wywoła tak naprawdę c.operator++(0) Complex operator(int); };
Operatory, które można przeciążać | ||||||
---|---|---|---|---|---|---|
+ | - | * | / | % | ^ | & |
| | ~ | ! | = | < | > | += |
-= | *= | /= | %= | ^= | &= | |= |
« | » | »= | «= | == | != | <= |
>= | && | || | ++ | -- | →* | , |
→ | [] | () | new | delete | new[] | delete[] |
Operatory, których nie można przeciążać | ||||
---|---|---|---|---|
. | .* | :: | ?: | sizeof |
Zasadniczo jeśli zastosowanie operatora w kodzie używającym danego obiektu (wywołującym metody) jest czytelne, jednoznaczne i intuicyjne wtedy można rozważyć przeciążenie operatora dla danego typu. W przeciwnym wypadku nie należy na siłę deklarować kodu operatorów, gdyż pogorszą one tylko jakość i czytelność kodu.
Zapoznać się z opisem na stronie Operatory. Na tej stronie są dodatkowe przykłady.
i została zmienione imię studenta już w repozytorium.
Podpowiedź: udostępniona też TU