Krzysztof Wesołowski Programowanie, elektronika, automatyka – doświadczenia i projekty

14lip/08

Przeładowanie operatorów cześć 1.

Słowem wstępu

Postaram się opisać na podstawie już wcześniej stworzonego programu (operacja na wektorach) jak przeładowywać w C++ podstawowe operatory.

Postaram się skupić na samym przeładowaniu, bez wchodzenia w szczegóły samego działania.

Operatory powinno się przeładowywać właśnie tam, gdzie ich działanie jest jednoznaczne na pierwszy rzut oka: działania arytmetyczne, operatory wejścia, wyjścia i inne, rzadziej używane o których wspomnę innym razem.

Ogólną ideą która powinna przyświecać przeładowywaniu to tworzenie przewidywalnych, zachowujących się analogicznie jak te wbudowane operatorów.

Przeładowany operator to po prostu funkcja o nazwie operator z dopisaną końcówką charakteryzującą operator, np operator+, operator*= etc. Samą funkcje można zdefiniować na dwa sposoby:

  1. Jako funkcje składową klasy
    wtedy napisanie obiekt1+obiekt2 oznacza wywołanie: obiekt1.operator+(obiekt2)
  2. Jako funkcje globalną
    wtedy napisanie obiekt1+obiekt2 oznacza wywołanie: operator+(obiekt1,obiekt2)

Operatory dodawania

Występuje w kilku wariacjach:

Zwykły operator dodawania:

Założenia:

  1. Dodaje elementy tego samego/podobnego typu do siebie
  2. Nie zmienia żadnego z elementów (żeby działało na obiektach stałych tak jak 2+5)
  3. Wynik powinien być zwrócony przez wartość (gdyż obiekty wewnątrz funkcji utworzone mogą zostać zniszczone już po jej zakończeniu)1

Przykład:

Listing:
  1. class Vector {
  2. ...
  3. public:
  4. Vector         operator+(const Vector& rhs)    const;
  5. ...
  6. }
Listing:
  1. Vector         operator+(const Vector& lhs,const Vector& rhs);

Obie metody działają, natomiast zdecydowanie elegantsza wydaje się dla mnie być ta pierwsza, gdyż w gruncie rzeczy operator ten jest tak silnie związany z klasa że powinien być jej funkcją składową.

Operator dodawania do lewej strony:

Założenia:

  1. Dodaje elementy tego samego/podobnego typu do siebie
  2. Zmienia element po lewej stronie (zwiększając go o wartość tego po prawej)
  3. Wynik powinien być zwrócony przez referencje, tak aby wskazywał na właśnie przypisany obiekt. Zwracanie wyniku przez wartość byłoby nieefektywne z względu na pamięć (zbędne kopiowanie), natomiast ważne jest zwracanie w ogóle, aby poprawna była konstrukcja while( (i+=6)<20 )

Przykład, jako funkcja składowa (do takich będę się teraz ograniczał, ew zmiany są analogiczne jak powyżej)

Listing:
  1. class Vector {
  2. ...
  3. public:
  4. Vector&       operator+=(const Vector& rhs);
  5. ...
  6. }

Operator odejmowania czyli operator- i operator -= są analogiczne, przejde więc do operatorów mnożenia. Wektory można mnożyć na 3 sposoby: wektor*liczba, liczba*wektor, wektor*liczba2, oraz wektor*wektor (skalarne).

Mnożenie wektor*liczba

Założenia:

  1. Mnoży wektor po lewej stronie razy liczbę
  2. Zwraca nowy,przemnożony wektor
  3. Zwraca oczywiście przez wartość

Przykład:

Listing:
  1. class Vector {
  2. ...
  3. public:
  4.  Vector         operator*(const int& rhs)       const;
  5. ...
  6. }
Mnożenie wektor*=liczba

Założenia:

  1. Mnoży wektor po lewej stronie razy liczbę
  2. Zmienia lewą stronę wpisując wynik
  3. Zwraca referencje do przemnożonego wektora (z tych samych przyczyn co operator+=)

Przykład:

Listing:
  1. class Vector {
  2. ...
  3. public:
  4.  Vector&        operator*=(const int& rhs);
  5. ...
  6. }

Nie miałoby natomiast sensu napisanie: liczba*=wektor, gdyż takie działanie, teoretycnzie powinno liczbe zmienic na wektor, co jest niemożliwe. Pozostaje nam operator liczba*wektor. Ponieważ operator jako skladowa swoja klase musi mieć po lewej stronie, jedynym wyjściem jest utworzenie funkcji globalnej, spełniającej praktycznie te same założenia co wektor*liczba.

Listing:
  1. Vector operator* (const int& lhs,const Vector& rhs);

Często aby moc korzystać z zmiennych prywatnych klasy Vector, takie funkcje nie deklaruje się poza ich ciałem, tylko wewnątrz definicji klasy jako funkcje zaprzyjaźnione:

Listing:
  1. class Vector {
  2. ...
  3. friend Vector operator* (const int& lhs,const Vector& rhs);
  4. ...
  5. public:
  6. ...
  7. }

Ostatni operator wart wymienia to proste mnożenie skalarne wektorów, którego przykład poniżej:

Listing:
  1. class Vector {
  2. ...
  3. public:
  4.  int            operator*(const Vector& rhs)    const;
  5. ...
  6. }

Często dużo więcej można odczytać i zrozumieć z przykładowego kodu, dlatego poniżej zamieszczam w miarę dopieszczona klasę Vector :)

Udokumentowane źródła klasy Vector

  1. Szczerze mówiąc nie miałem okazji tego testować, ale z rozmów z znajomymi wynika że nawet przy zastosowaniu pewnej dowolności kompilator ogarnia za nas sytuacje, np nie kopiuje niepotrzebnie gdy zwracamy przez wartość obiekt lokalny, czy też nie wywala błędów gdy zwracamy obiekt lokalny przez referencje (tak jakby automatycznie zostawiał ten obiekt referencje zmieniając na jego nazwę??) []
  2. pomimo, że my wiemy o przemienności działania, musimy poinformować o tej właściwości kompilator[]
Komentarze (0) Trackbacks (0)

Przepraszam, dodawanie komentarzy zablokowane.

Trackbacks are disabled.