Przeciążanie operatorów w C ++: podstawy, przykłady

W każdej nauce istnieją standardowe zapisy, które ułatwiają zrozumienie pomysłów. Na przykład w matematyce jest to mnożenie, dzielenie, dodawanie i inna symboliczna notacja. Wyrażenie (x + y * z) jest dużo łatwiejsze do zrozumienia niż "pomnóż y, z i dodaj do x". Wyobraźmy sobie, że aż do XVI wieku matematyka nie miała symbolicznej notacji, wszystkie wyrażenia były napisane werbalnie, jak gdyby był tekstem artystycznym z opisem. A zwykłe znaki operacji dla nas pojawiły się później. Trudno przecenić znaczenie krótkiego zapisu postaci. Na podstawie tych rozważań języki programowania zostały dodane do przeciążenia operatorów. Rozważ przykład.


Przykład przeciążenia operatora

Prawie jak każdy język, C ++ obsługuje wiele operatorów pracujących z typami danych wbudowanymi w standardowym języku. Jednak większość programów używa specjalnych typów do rozwiązywania niektórych zadań. Na przykład złożona matematyka lub algebra macierzowa jest zaimplementowana w programie, reprezentując liczby zespolone lub macierze w niestandardowych typach C ++. Wbudowani operatorzy nie są w stanie rozprowadzać swojej pracy i przeprowadzać niezbędnych procedur na niektórych zajęciach, jakkolwiek są one oczywiste. Dlatego na przykład do dodawania macierzy tworzona jest zwykle osobna funkcja. Oczywiście wywołanie sum_matrix (A, B) w kodzie będzie miało mniej czytelny charakter niż wyrażenie A + B.
Rozważ przybliżoną klasę liczb zespolonych:

//wyobraź sobie liczbę zespoloną w postaci pary liczb zpunkt przestawny.
klasa złożona {{12} double re, im;
public:
complex (double r, double i): re (r), im (i) {} //constructor
complex operator + (complex); //złożony operator zespołu przeciążającego
* (złożony); //przeciążenie mnożenia
};

void kompleks główny () {
a {1 2}, b {3}}, c {0}};
c = a + b;
c = a.operator + (b); ////funkcję operatora można nazwać dowolną funkcją, ten wpis jest równoważny z kompleksem a + b
c = a * b + (1 3); //przeprowadzono zwykłe zasady działania priorytetowych dodawania i mnożenia
}

W podobny sposób można zrobić, na przykład przeciążenia operatora /wy w C ++ i dostosować je do wyświetlania takich złożonych struktur jako szablony.

Operatorzy dostępni do przeciążenia

Pełny wykaz wszystkich operatorów, w przypadku których można zastosować mechanizm przeciążenia:

,

110

nowość

+

,

-

,

*

,

/

%

^

& amp;

~

!

=

& lt; /p & gt;

& gt;

+ =

- =

,

=

,

/=

,

% =

,

^ =

,

i wzmacniacz =

,

| =

=

,

=

,

==

! =

> =

i;

||

,

++

,

-

,

- & gt; *

,

- & gt;

nowy []

usunąć

usunąć []

Jak wynika z tabeli, dla większości operatorów językowych dopuszczalne jest przeciążenie. Nie ma potrzeby przeciążania operatora. Odbywa się to wyłącznie dla wygody. Dlatego nie ma na przykład przeciążenia operatorów w Javie. A teraz o tak ważnym momencie.

Operatorzy, których przeciążenie jest zabronione

  • Pozwolenie na widoczność - «::»;
  • Wybór członka to ".";
  • Wybór członka przez wskaźnik do członka - ". *";
  • Trójskładnikowy operator warunkowy - «?:»;
  • rozmiar operatora;
  • Operator typu.

Prawym operandem danych operatorów jest nazwa, a nie wartość. Dlatego zezwolenie na ich przeciążenie mogłoby doprowadzić do napisania wielu niejednoznacznych projektów i znacznie skomplikowałoby życie programistów. Chociaż istnieje wiele języków programowania, które pozwalają na przeciążenie wszystkich operatorów - na przykład przeciążenie operatorów Pythona.


& lt; script type = "text /javascript" & gt;
var blockSettings2 = {blockID "R-A-70350-2" renderTo "yandex_rtb_R-A-70350-2" asynchroniczny :! 0};

, jeżeli (document.cookie.indexOf ("abmatch =") i GT = 0) {
blockSettings2 = {blockID "RA 70350-2" renderTo „yandex_rtb_R A-70350- 2 ", statId: 70350async:! 0};
}

! Zastosowanie (a, b, c, d, e) {A [c] = a [c] || [] do [C] .Push (funkcja () {Ya .Context.AdvManager.render (blockSettings2)}), e = b.getElementsByTagName ("scenariusz")d = b.createElement ("scenariusz") d.type = "text /JavaScript" d.src = "//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
,

ograniczenia operator

ograniczenia przeciążenia

,
  • nie może zostać zmieniona na jednoargumentowego operator binarny i odwrotnie, jak nie można dodać trzeci argument.
  • Nie można tworzyć nowych operatorów innych niż te, które są. To ograniczeniepromuje eliminację wielu niejednoznaczności. Jeśli istnieje potrzeba nowego operatora, można użyć funkcji, która wykona pożądaną akcję dla tych celów.
  • Funkcja operatora może być członkiem klasy lub mieć przynajmniej jeden argument typu. Wyjątkiem są operatorzy new i delete. Ta reguła zabrania zmiany znaczenia wyrażeń, jeśli nie zawierają typów obiektów zdefiniowanych przez użytkownika. W szczególności nie można utworzyć funkcji operatora, która działałaby tylko ze wskaźnikami lub zmuszała operatora dodawania do pracy jako mnożenie. Wyjątkiem są operatory "=", "& amp;" i "," dla obiektów klasy.
  • Funkcja operatora z pierwszym terminem, należącym do jednego z wbudowanych typów danych w języku C ++, nie może być członkiem klasy.
  • Nazwa dowolnej funkcji operatora rozpoczyna się od słowa kluczowego operator, po którym następuje symboliczne oznaczenie samego operatora.
  • Wbudowane operatory są zdefiniowane w taki sposób, że istnieje między nimi połączenie. Na przykład następujące operatory są równoważne sobie: ++ x; x + = 1; x = x + 1. Po ponownym zdefiniowaniu, połączenie między nimi nie zostanie zachowane. Utrzymanie ich wspólnej pracy w podobny sposób z nowymi typami programistów będzie musiało dbać o siebie.
  • Kompilator nie może myśleć. Wyrażenia z + 5 i 5 + z (gdzie z - liczba zespolona) będą rozpatrywane przez kompilator na różne sposoby. Pierwsza to "liczba zespolona +", a druga to "liczba + kompleks". Dlatego dla każdego wyrażenia musisz zdefiniować własną instrukcję dodawania.
  • Podczas wyszukiwania definicji operatora kompilator nie nadaje preferencji żadnym członkom funkcji klasy ani funkcjom pomocniczym,które są zdefiniowane poza klasą. Dla kompilatora są równe.

Interpretacje operatorów binarnych i jednoarkowych.

Operator binarny jest zdefiniowany jako funkcja składowa z jedną zmienną lub jako funkcja z dwiema zmiennymi. Dla każdego operatora z binarnym ekspresji @ @ B @ tylko struktur:


Związek pośredni skryptu typu = "text /JavaScript" & gt;
var blockSettings3 = {blockID "R-A-70350-3" renderTo "yandex_rtb_R-A-70350-3" asynchroniczny :! 0};

, jeżeli (document.cookie.indexOf ("abmatch =") i GT = 0) {
blockSettings3 = {blockID "RA 70350-3" renderTo „yandex_rtb_R A-70350- 3 ", statId: 70350async:! 0};
}

! Zastosowanie (a, b, c, d, e) {A [c] = a [c] || [] do [C] .Push (funkcja () {Ya .Context.AdvManager.render (blockSettings3)}), e = b.getElementsByTagName ("scenariusz")d = b.createElement ("scenariusz") d.type = "text /JavaScript" d.src = "//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");

a.operator @ (b) lub operator @ (a, b).

Rozważmy przykład klasy liczb zespolonych dla definicji operacji jako członków klasy i pomocniczych.

klasa złożona {{174} double re, im;
public:
complex & amp; operator + = (complex z);
kompleks i amp; operator * = (złożony z);
};
//funkcji pomocnika
operatora + kompleks (kompleks złożony Z1 Z2);
operator złożony + (złożony z, podwójny a);
,

, który jest wybrany operatorów oraz wybiera się na ogół określane przez wewnętrzne mechanizmy języka, które zostaną omówione poniżej. Zazwyczaj dzieje się to zgodnie z typami.

​​

Wybór opisać funkcję członka klasy lub poza tym - jest na ogół, smaku. W powyższym przykładzie, zasada doboru się w sposób następujący: jeżeli operacja zmiany lewy argument (na przykład, a + = B), po czym rejestruje się w klasie i w użyciu zmiennej transmisji, na swoich bezpośrednich zmian; jeśli operacja niemodyfikuje i po prostu zwraca nową wartość (na przykład a + b) - poza definicją klasy.

Definicja przeciążenia operatorów jednoargumentowych w C ++ występuje w taki sam sposób, z tą różnicą, że dzieli się je na dwa typy:

  • operator prefiksu, zlokalizowany na operandzie, - @a, na przykład, i ++. o Zdefiniowany jako a.operator @ () lub operator @ (aa);
  • operator przyrostowy, umieszczony za operandem, - b @, na przykład, i ++. o Zdefiniowane jako b.operator @ (int) lub operator @ (b, int)

W ten sam sposób, co w przypadku operatorów binarnych, w przypadku, gdy instrukcja operatora znajduje się zarówno w klasie, jak i poza nią, wybór zostanie dokonany za pomocą mechanizmów C ++.

Zasady wyboru operatora

Niech operator binarny @ zostanie zastosowany do obiektów x klasy X i y klasy Y. Reguły dla rozdzielczości x @ y będą następujące:

  1. jeśli X jest klasą, przejrzyj w niej definicję operatora @ jako termin X lub podstawową klasę X;
  2. , aby zobaczyć kontekst, w którym znajduje się wyrażenie x @ y;
  3. jeśli X należy do przestrzeni nazw N, poszukaj instrukcji operatora N;
  4. Jeśli Y należy do przestrzeni nazw M, poszukaj instrukcji operatora M.

Jeśli znaleziono kilka stwierdzeń operatora operatora @ w 1-4, wybór zostanie dokonany zgodnie z regułami zezwolenia na przeciążone funkcje.

Poszukiwanie operatorów jednoargumentowych odbywa się dokładnie w ten sam sposób.

Uściślij definicję kompleksu klasowego

Teraz skonstruujemy klasę liczb zespolonych w bardziej szczegółowy sposóbwykazać liczbę wcześniej ogłoszonych zasad.

klasa złożona {
double re, im;
publiczny:
złożony & amp; operator + = (complex z) {//działa z wyrażeniami w postaci z1 + = z2
re + = z.re;
im + = z.im;
zwracają * to;
}
complex & amp; operator + = (double a) {//działa z wyrażeniami w postaci z1 + = 5;
re + = a;
zwraca * to;
}
complex (): re

, im

{} //konstruktor domyślnie inicjalizujący. W ten sposób, wszystkie liczbami zespolonymi ogłoszone będą miały wartości początkowej (0 0)
, kompleks (podwójne R): Re (R), IM

{} //konstruktor umożliwiają ekspresję formy złożonej z = 11; odpowiednik rekordu z = complex
;
kompleksu (R dwukrotnie dwukrotnie I) Re (R), IM (I) {} //konstruktor
};
operatora + kompleks (kompleks złożony z1 z2) {//współpracuje z wyrażenia Z1 + Z2
złożone res = z1;
return res + = z2; //operator określa się jako funkcję członu
}
operatora + kompleks (kompleks z, dwukrotnie a) {//wyrażenia uchwyt z + 2
złożone Res = Z;
return res + = a;
}
złożone operatora + (podwójne A, złożone z) {//wyrażenia uchwyt z + 7
złożone Res = Z;
return res + = a;
}
//


, jak pokazano na kodzie przeciążenia operatora jest dość skomplikowany mechanizm, który może rosnąć silnie. Jednak takie szczegółowe podejście pozwala na przeciążenie nawet w przypadku bardzo złożonych struktur danych. Na przykład przeciążanie operatorów C ++ w klasie szablonu. Takie tworzenie funkcji dla wszystkich i wszystkich może być uciążliwe i prowadzić do błędów. Na przykład, jeśli dodasz trzeci typ rozważanych funkcji, będziesz musiał rozważyć operacje z powodu kombinacji trzech typów. Będziemy musieli napisać 3 funkcje z jednym argumentem, 9 - z dwoma i 27 - z trzema. Dlatego w niektórych przypadkach wdrożenie wszystkich tych funkcji i znaczne ich zmniejszenieilości można osiągnąć, stosując konwersję typów.

Operatory specjalne

Operator indeksujący "[]" powinien zawsze być zdefiniowany jako element klasy, ponieważ redukuje zachowanie obiektu do tablicy. W tym przypadku argument indeksujący może być dowolnego typu, co pozwala na przykład na utworzenie tablic asocjacyjnych. Operator połączenia funkcji (() może być traktowany jako operacja binarna. Na przykład w konstrukcie "wyrażenie (lista wyrażeń)" lewy operand operacji binarnej () będzie "wyrażenie", a prawy jest listą wyrażeń. Funkcja operator () () musi należeć do klasy. Operator sekwencji "," (przecinek) jest wywoływany dla obiektów, jeśli obok nich znajduje się przecinek. Jednak operator nie bierze udziału w przekazywaniu argumentów funkcji. Operator nazewnictwa "->" musi również być zdefiniowany jako członek funkcji. W jego treści można go zdefiniować jako unarodowy operator postfiksów. Jednocześnie musi koniecznie zwrócić link lub wskaźnik, który umożliwia dostęp do obiektu. Operator przypisania jest również zdefiniowany tylko jako członek klasy ze względu na jego połączenie z lewym operandem. Operatory przypisania «=», adresy «i »;» oraz sekwencja «,» muszą być zdefiniowane w bloku publicznym.

Wynik

Przeciążenie operatora pomaga zrealizować jeden z kluczowych aspektów OWP w zakresie polimorfizmu. Ale ważne jest, aby zrozumieć, że przeciążenie to nic innego jak inny sposób wywoływania funkcji. Zadaniem przeciążania operatorów jest często poprawa zrozumienia kodu, niż zapewnienie wygranej w dowolnych kwestiach.
A to nie wszystko. Należy także pamiętać, że przeciążanie operatorów jest skomplikowany mechanizm z wielu pułapek. Dlatego bardzo łatwo popełnić błąd. Jest to główny powód, dla którego większość programistów zaleca powstrzymanie się od używania przeciążanie operatorów i ośrodek do niego tylko w ostateczności iz pełnym zaufaniem w swoich działaniach.

Zalecenia

  • Wykonuj przeciążenie operatora, aby symulować zwykły rekord. Aby utworzyć odczytany kod. Jeśli kod stanie się bardziej złożony pod względem struktury lub czytelności, należy porzucić przeciążenie operatorów i korzystać z funkcji.
  • W przypadku dużych argumentów celu zaoszczędzenia miejsca, należy przesłać typ argumentu konstantnыh linki.
  • Zoptymalizuj zwracane wartości.
  • Nie dotykaj operacji kopiowania, jeśli jest odpowiednia dla twojej klasy.
  • Jeśli domyślna kopia nie jest odpowiednia, zmodyfikuj lub jawnie zakazuj kopiowania.
  • powinny dać pierwszeństwo funkcji składowych funkcji nieczłonkowskimi gdy funkcje wymagają dostępu do formularza zgłoszeniowego.
  • Wskaż przestrzeń nazw i wskaż relację między funkcjami klasy.
  • Użyj funkcji niezrzeszonych dla operatorów symetrycznych.
  • Użyj operatora () do indeksów w wielowymiarowych tablicach.
  • Zachowaj ostrożność przy niejawnych przekształceniach.
  • Powiązane publikacje