Programowanie w języku C/C++

Przeładowywanie operatorów

  • 2011-01-07 01:27
  • 1 komentarz
  • 38787 odsłon
  • Oceń ten tekst jako pierwszy
Spis treści

     1 Wstęp
     2 Operacje na obiektach
     3 Operatory których nie można przeciążać
     4 Strumienie
          4.1 operator<
          4.2 operator>>
     5 Operatory binarne (Binary Operators)
          5.1 operator+
          5.2 operator=
     6 Operatory jednoskładnikowe (Unary Operators)
          6.1 operator! i operator bool
     7 Inne operatory
          7.1 operator[]
     8 Operatory wcześniej nieopisane
          8.1 operator+
          8.2 operator-
          8.3 operator*
          8.4 operator/
          8.5 operator%
          8.6 operator^
          8.7 operator&
          8.8 operator|
          8.9 operator~
          8.10 operator!
          8.11 operator
          8.12 operator>
          8.13 operator+=
          8.14 operator-=
          8.15 operator*=
          8.16 operator/=
          8.17 operator%=
          8.18 operator^=
          8.19 operator&=
          8.20 operator|=
          8.21 operator<
          8.22 operator>>
          8.23 operator<=
          8.24 operator>>=
          8.25 operator==
          8.26 operator!=
          8.27 operator<=
          8.28 operator>=
          8.29 operator&&
          8.30 operator||
          8.31 operator++
          8.32 operator--
          8.33 operator->


Wstęp


Przeładowywanie operatorów, zamiennie można używać słowa przeciążanie, wykorzystuje się dla struktur lub klas. Nie można ich przeciążyć gdzie inaczej niż tam.
Może przejdźmy od razu do przykładów.

Operacje na obiektach


A mianowicie mamy kod:
#include <iostream>
using namespace std;
 
class Foo
{
 public:
      int pierwsza;
      int druga;
};
int main()
{
    Foo ex1,ex2,ex3;
 
    ex1.pierwsza = 1;
    ex2.pierwsza = 2;
    ex1.druga = 3;
    ex2.druga = 4;
 
    ex3=ex1+ex2;
 
    system("pause>nul");
}

I niby wszystko było by pięknie ale niestety kompilatorowi nie pasuje.
no match for 'operator+' in 'ex1 + ex2' 

Dlaczego?
A no między innymi dla tego że nie wie co dodać i jak.
Musimy dać mu informację co ma dodać.
W powyższym przykładzie dla naszej klasy potrzebujemy przeciążenia dwóch operatorów (przypisania i dodawania).

Operatory których nie można przeciążać


Istnieje kilka operatorów, których nie można przeciążać, a mianowicie:
 .   (kropka) 
 .*
 ?:
 ::  (operator zakresu)
 sizeof


Strumienie


operator<


Aby zaoszczędzić czas i zyskać na szybkości programu możemy przeciążyć operator strumienia wyjścia dla naszej klasy.
Przykładowo żeby wyświetlić składniki naszej klasy Foo możemy napisać coś w tym stylu: (wykorzystujemy klasę i inicjację z przykładu wyżej oczywiście oprócz operacji przypisania i sumowania, tym zajmiemy się później)
cout << "Pierwsza liczba: " << ex1.pierwsza << " oraz druga: " << ex1.druga << endl;
cout << "Pierwsza liczba: " << ex2.pierwsza << " oraz druga: " << ex2.druga << endl;

Ale co jeśli będziemy mieli tablicę stu lub tysiąca takich obiektów ?
Kod stanie się dość nieczytelny i bardzo długi. Oczywiście można w pętli for i bawić się na różne sposoby, ale mamy na to prostą radę - przeciążenie operatora<.
Oczywiście trzeba nasz przeciążony operator zadeklarować jako metodę klasy Foo. Najczęściej pisze się ją zaprzyjaźnioną z klasą, ponieważ gdy mamy prywatne zmienne klasy, które chcemy wyświetlić, operator nie miał by do nich dostępu.

Oto przykład deklaracji:
friend ostream& operator<< (ostream&,Foo const&);


Natomiast kod naszej metody będzie wyglądał podobnie jakbyśmy pisali wszystko jw.
ostream& operator<< (ostream &wyjscie, Foo const& ex)
{
   wyjscie << "Pierwsza liczba: " << ex.pierwsza << " oraz druga: " << ex.druga << endl;
   return wyjscie;
}

Ale za to skraca nam się to w kodzie i wygląda przejrzyście.
cout << ex1 << ex2;

Efekt jest równoważny temu co z naszego naiwnego wyświetlania elementów funkcji, ale ma więcej zalet.

Zawsze używaj stałej referencji na obiekt klasy, którego elementy wyświetlasz.

operator>>


Przeciążenie operatora strumienia wejścia też może się czasem do czegoś przydać. Ewentualne instrukcje co użytkownik ma podać trzeba napisać przed wczytywaniem.
Operator>> także dobrze by było żeby był zaprzyjaźniony z klasą z tych samych powodów co operator<.

Deklaracja oraz kod metody wyglądają również podobnie jak z operatorem wyjścia:
//deklaracja
 
friend istream& operator>> (istream&,Foo&);
 
//kod
 
istream& operator>> (istream &wejscie, Foo& ex)         
{
   wejscie >> ex.pierwsza >> ex.druga;
   return wejscie;
}
 
//użycie zamiast przypisywania
 
cout << "Podaj 2 liczby dla:\n"
         << "Obiektu pierwszego: ";
    cin >> ex1;
    cout << "Oraz drugiego: ";
    cin >> ex2;


Tutaj nie możesz używać stałej referencji, ponieważ zmieniasz wartości elementów obiektu!

Operatory binarne (Binary Operators)


operator+


No i upragniony operator dodawania, co do którego krzyczał kompilator w naszym pierwszym programie.
Tutaj sprawa wygląda trochę inaczej, ponieważ metoda zwraca obiekt klasy Foo.
Musimy dodatkowo dopisać konstruktor klasy, a dokładniej dwa: jeden potrzebny nam do sumowania a drugi bezargumentowy.

Tak więc nasz program na daną chwilę będzie wyglądał tak:
#include <iostream>
using namespace std;
 
class Foo
{
 public:
      int pierwsza;
      int druga;
 Foo();
 Foo(int,int);   
 
 friend ostream& operator<< (ostream&,Foo const&);
 friend istream& operator>> (istream&,Foo&);
 Foo operator+ (Foo const&);
};
 
Foo::Foo()
{
 pierwsza = druga = 0;
}
 
Foo::Foo(int a,int b)
{
 pierwsza = a;
 druga = b;
}
 
ostream& operator<< (ostream &wyjscie, Foo const& ex)
{
   wyjscie << "Pierwsza liczba: " << ex.pierwsza << " oraz druga: " << ex.druga << endl;
   return wyjscie;
}
 
istream& operator>> (istream &wejscie, Foo& ex)         
{
   wejscie >> ex.pierwsza >> ex.druga;
   return wejscie;
}
 
Foo Foo::operator+ (Foo const& ex)
{    
   Foo tmp(pierwsza + ex.pierwsza, druga + ex.druga);
   return tmp;    
}
 
int main()
{
    Foo ex1,ex2,ex3;
 
    cout << "Podaj 2 liczby dla:\n"
         << "Obiektu pierwszego: ";
    cin >> ex1;
    cout << "Oraz drugiego: ";
    cin >> ex2;
 
    cout << ex1 << ex2 << ex1+ex2;
 
    system("pause>nul");
}


Kilka linijek wymaga chyba wyjaśnienia.
Co do konstruktorów to jeden musi być bezargumentowy (może nie robić nic), a drugi musi być (u nas akurat) dwuragumentowy żeby stworzyć nowy obiekt będący sumą dwóch.

W kodzie operatora można np. używać instrukcji warunkowych:
Foo Foo::operator+ (Foo const& ex)
{ 
   if(pierwsza && ex.pierwsza > 0)
   {   
     Foo tmp(pierwsza + ex.pierwsza, druga + ex.druga);
     return tmp;
   }
   else
   {
    Foo tmp(-1,-1); //wartość -1 często jest uznawana jako błąd funkcji
    cout << "Blad\n";
    return tmp;  //funkcja musi coś zwracać w innym wypadku wyjdą śmieci
   }
}


Można również jako argument pobierać np. zwykłą liczbę całkowitą i dodawać do któregoś elementu. Wtedy kod wyglądał by tak:
Foo Foo::operator+ (int const& liczba)
{    
   Foo tmp(pierwsza + liczba, druga);
   return tmp;    
}

Reszta zależy już od celu jaki ma dodawanie spełniać.

operator=


No i drugi operator potrzebny nam do podstawowych operacjach na obiektach, a mianowicie operator przypisania.
To już powinno być proste. Wszystkie operatory przeciąża się wg jakiegoś schematu.
Tutaj obiektowi dla którego wywołujemy operator przypisania po prostu kopiujemy wartości elementów z drugiego obiektu.
Foo& Foo::operator= (Foo const& ex)
{
     pierwsza = ex.pierwsza;
     druga = ex.druga;
}


Operatory jednoskładnikowe (Unary Operators)


operator! i operator bool


Te dwa operatory są po prostu swoją przeciwnością i pokarzę je w jednym przykładzie.

#include <iostream>
using namespace std;
 
class TablicaInt
    {
       public:
         TablicaInt(int el) : Tab(new int[el]), L_elementow(el) {}
 
         operator bool () const {return (L_elementow != 0);}
         bool operator! () const {return (L_elementow == 0);}
 
       private:
         int * Tab;
         int L_elementow;
    };
 
int main()
{
  int n = 5;
  TablicaInt tab(n);
 
  if(tab)
    cout << "Tablica nie jest pusta." << endl;
  if(!tab)
    cout << "Tablica jest pusta." << endl;
 
  TablicaInt tab2(0);
 
  if(tab2)
    cout << "Tablica nie jest pusta." << endl;
  if(!tab2)
    cout << "Tablica jest pusta." << endl;
 
  return 0;
}

Po prostu sprawdzają one czy dany element w strukturze/klasie ma wartość prawdziwą czy też nie.

Inne operatory


operator[]


Wykorzystując klasę TablicaInt wystarczy dodać:
int & operator[](int el) {return Tab[el];}
const int & operator[](int el) const {return Tab[el];}
 
int n = 5;
TablicaInt tab(n);
 
for(int i = 0; i < n; ++i)
{
  tab[i] = i;
  cout << tab[i] << endl;
}


Operatory wcześniej nieopisane


Poniżej spis wszystkich operatów, które da się przeciążyć i jakim schematem.

operator+


w klasie
_zwracany_typ_ operator+(const _typ_&);

poza klasą
_zwracany_typ_ operator+(const _typ1_&, const _typ2_&);


operator-


w klasie
_zwracany_typ_operator-(const _typ_&);

poza klasą
_zwracany_typ_ operator-(const _typ1_&, const _typ2_&);


operator*


w klasie
_zwracany_typ_ operator*(const _typ_&);

poza klasą
_zwracany_typ_ operator*(const _typ1_&, const _typ2_&);


operator/


w klasie
_zwracany_typ_ operator/(const _typ_&);

poza klasą
_zwracany_typ_ operator/(const _typ1_&, const _typ2_&);


operator%


w klasie
_zwracany_typ_ operator%(const _typ_&);

poza klasą
_zwracany_typ_ operator%(const _typ1_&, const _typ2_&);


operator^


w klasie
_zwracany_typ_ operator^(const _typ_&);

poza klasą
_zwracany_typ_ operator^(const _typ1_&, const _typ2_&);


operator&


w klasie
_zwracany_typ_ operator&(const _typ_&);

poza klasą
_zwracany_typ_ operator&(const _typ1_&, const _typ2_&);


operator|


w klasie
_zwracany_typ_ operator|(const _typ_&);

poza klasą
_zwracany_typ_ operator|(const _typ1_&, const _typ2_&);


operator~


w klasie
_zwracany_typ_ operator~();

poza klasą
_zwracany_typ_ operator~(const _typ_&);


operator!


w klasie
_zwracany_typ_ operator!();

poza klasą
_zwracany_typ_ operator!(const _typ_&);


operator


w klasie
_zwracany_typ_ operator<(const _typ_&);

poza klasą
_zwracany_typ_ operator<(const _typ1_&, const _typ2_&);


operator>


w klasie
_zwracany_typ_ operator>(const _typ_&);

poza klasą
_zwracany_typ_ operator>(const _typ1_&, const _typ2_&);


operator+=


w klasie
_zwracany_typ_ & operator+=(const _typ_&);

poza klasą
_zwracany_typ_ & operator+=(_zwracany_typ_&, const _typ_&);


operator-=


w klasie
_zwracany_typ_ & operator-=(const _typ_&);

poza klasą
_zwracany_typ_ & operator-=(_zwracany_typ_&, const _typ_&);


operator*=


w klasie
_zwracany_typ_ & operator*=(const _typ_&);

poza klasą
_zwracany_typ_ & operator*=(_zwracany_typ_&, const _typ_&);


operator/=


w klasie
_zwracany_typ_ & operator/=(const _typ_&);

poza klasą
_zwracany_typ_ & operator/=(_zwracany_typ_&, const _typ_&);


operator%=


w klasie
_zwracany_typ_ & operator%=(const _typ_&);

poza klasą
_zwracany_typ_ & operator%=(_zwracany_typ_&, const _typ_&);


operator^=


w klasie
_zwracany_typ_ & operator^=(const _typ_&);

poza klasą
_zwracany_typ_ & operator^=(_zwracany_typ_&, const _typ_&);


operator&=


w klasie
_zwracany_typ_ & operator&=(const _typ_&);

poza klasą
_zwracany_typ_ & operator&=(_zwracany_typ_&, const _typ_&);


operator|=


w klasie
_zwracany_typ_ & operator|=(const _typ_&);

poza klasą
_zwracany_typ_ & operator|=(_zwracany_typ_&, const _typ_&);


operator<


w klasie
_zwracany_typ_ & operator<<(const _typ_&);

poza klasą
_zwracany_typ_ & operator<<(const _zwracany_typ_&, const _typ_&);


operator>>


w klasie
_zwracany_typ_ & operator>>(const _typ_&);

poza klasą
_zwracany_typ_ & operator>>(const _zwracany_typ_&, const _typ_&);


operator<=


w klasie
_zwracany_typ_ & operator<<=(const _typ_&);

poza klasą
_zwracany_typ_ & operator<<=(_zwracany_typ_&, const _typ_&);


operator>>=


w klasie
 
_zwracany_typ_ & operator>>=(const _typ_&);

poza klasą
_zwracany_typ_ & operator>>=(_zwracany_typ_&, const _typ_&);


operator==


w klasie
 
_zwracany_typ_ operator==(const _typ_&);

poza klasą
_zwracany_typ_ operator==(const _typ1_&, const _typ2_&);


operator!=


w klasie
_zwracany_typ_ operator!=(const _typ_&);

poza klasą
_zwracany_typ_ operator!=(const _typ1_&, const _typ2_&);


operator<=


w klasie
_zwracany_typ_ operator<=(const _typ_&);

poza klasą
_zwracany_typ_ operator<=(const _typ1_&, const _typ2_&);


operator>=


w klasie
_zwracany_typ_ operator>=(const _typ_&);

poza klasą
_zwracany_typ_ operator>=(const _typ1_&, const _typ2_&);


operator&&


w klasie
_zwracany_typ_ operator&&(const _typ_&);

poza klasą
_zwracany_typ_ operator&&(const _typ1_&, const _typ2_&);


operator||


w klasie
_zwracany_typ_ operator||(const _typ_&);

poza klasą
_zwracany_typ_ operator||(const _typ1_&, const _typ2_&);


operator++


w klasie
_zwracany_typ_ & operator++();

poza klasą
_zwracany_typ_ & operator++(_typ_&);


operator--


w klasie
_zwracany_typ_ & operator--();

poza klasą
_zwracany_typ_ & operator--(_typ_&);


operator->


w klasie
_zwracany_typ_ operator->(const _typ_&);

poza klasą
_zwracany_typ_ operator->(const _typ1_&, const _typ2_&);

1 komentarz

kadoel 2011-08-20 10:59

http://programmuj.blogspot.com[...]nie-operatorow-w-jezyku-c.html Tutaj mamy prosty program z wykorzystaniem przeciążania ( przeładowywania) operatorów na przykładzie liczb zespolonych.