Zdefiniowane konwersje typów

Jak już wiemy wyrażenie

double x=2;
jest poprawne, mimo, że 2 jest typu int a nie double. Powoduje ono wywołanie automatycznej konwersji liczby 2 na typ double, a następnie przypisanie wyniku na x.

Możliwe jest zdefiniowanie takich konwersji przez użytkownika. Zmienne jednego typu (nazwijmy go T1) są konwertowane na inny (T2) w dwóch przypadkach:

  1. Jeśli T2 jest klasą, w której jest zdefiniowany konstruktor z parametrem typu T1.
  2. Jeśli T1 jest klasą, w której zdefiniowana jest metoda o nazwie operator T2 (bez parametrów).

Przykład: Oto przykład klasy, której obiektami są liczby zespolone:

class Zespolona {
public:
   Zespolona(): re(0), im(0) {};
   Zespolona(double _re): re(_re), im(0) {};
   Zespolona(double _re, double _im): re(_re), im(_im) {};
   operator double() {return re;};
   void wypisz() { cout << re << "+" << im << "i"; };
private:
   double re, im;
};
Teraz kod
Zespolona z=5.5;
z.wypisz();
powoduje wypisanie 5.5+0i (wywołany zostaje konstruktor Zespolona(double), który zamienia wartość 5.5 typu double na typ Zespolona. Natomiast kod
Zespolona z(3,1);
double n=z;
cout << n;
powoduje wypisanie liczby 3 - z zostaje zamieniona na typ double przy pomocy metody operator double.

Przeciążanie operatorów

Jeśli chcemy obliczyć sumę dwóch wartości typu int, np. a i b piszemy po prostu a+b. W przypadku gdybyśmy potrzebowali dodać dwie liczby typu Zespolona z powyższego przykładu możemy napisać metodę, która dodaje dwie liczby, np.
class Zespolona {
public:
   ...
   Zespolona suma(Zespolona z) { return Zespolona(re+z.re, im+z.im); }
   ...
};
a następnie jej użyć np. w następujący sposób:
Zespolona z1(2,1);
Zespolona z2=5.5;
Zespolona z3=z1.dodaj(z2);
Nie jest to zbyt wygodny sposób, szczególnie gdy chcemy obliczać bardziej skomplikowane wyrażenia. Na szczęscie C++ umożliwia zdefiniowanie standardowych operatorów dla zdefiniowanych typów danych. Możemy to zrobić na jeden z dwóch sposobów:
  1. Przy pomocy metody operator+, np.
    class Zespolona {
    public:
       ...
       Zespolona operator+(Zespolona z) { return Zespolona(re+z.re, im+z.im); }
       ...
    };
  2. Przy pomocy funkcji
    Zespolona operator+(Zespolona z1, Zespolona z2);

Jeśli zdefiniujemy operator + na jeden z tych sposobów (nie należy używać obydwu, gdyż spowoduje to błąd), możemy dodawać liczby zespolone tak jak całkowite, np. można napisać

Zespolona z1(2,1);
Zespolona z2=5.5;
Zespolona z3=z1+z2;
lub nawet
Zespolona z3=Zespolona(2,1)+5.5;
(w tym przypadku zostaną zastosowane zarówno konwersje jak i operator +).

Uwagi:

Operatory zwykle wygodniej jest definiować jako metody, jednak czasem niezbędne jest zdefiniować go jako funkcję. Otóż w przeciwnym przypadku kod
Zespolona z3=5.5+Zespolona(2,1);
nie będzie poprawny - 5.5 jest typu double i nie może posiadać metody operator+ (zresztą żadnej metody). Jeśli zaś zdefiniowana będzie funkcja
Zespolona operator+(Zespolona z1, Zespolona z2);
wszystko obliczy się poprawnie.

Funkcje zaprzyjaźnione

W powyższym przykładzie nie została zdefiniowana funkcja operator+ do dodawania liczb zespolonych. Definicja
Zespolona operator+(Zespolona z1, Zespolona z2) {
   return Zespolona(z1.re+z2.re, z1.im+z2.im);
}
nie jest dobra, gdyż odwołuje się do pól prywatnych re oraz im. Staje się ona poprawna po zadeklarowaniu jej jako funkcji zaprzyjaźnionej dla klasy Zespolona poprzez umieszczenie linijki
friend Zespolona operator+(Zespolona z1, Zespolona z2);
w ciele klasy.