Przeciążenie operatorów W C++ można przeciążyć większość operatory tak, żeby wykonywali zadania, charakterystyczne dla danej klasy Po przeciążeniu odpowiednich operatorów można posługiwać się obiektami tych klas tak samo jak typami wbudowanymi C++. Operator można przeciążyć, tworząc funkcje operatora. Funkcje operatora wyznacza, jakie operacje na obiektach wskazanej klasy ma wykonywać operator. Do tworzenia funkcji operatora służy słowo kluczowe operator. Funkcja operatora może być składową klasy (a może i nie być). Najczęściej funkcja operatora, która nie jest składową klasy, jest funkcją zaprzyjaźnioną do tej klasy. Funkcja operatora składowej klasy zwrac_typ nazwa_klasy :: operator # (lista_argumentów) //operacje 1
Często typem wartości zwracanej funkcji operatora jest klas, dla którego ta funkcja jest wyznaczona. Znak # oznacza działanie operatora, który przeciążamy. Ograniczenia na przeciążenie operatorów: o Nie wolno zmieniać priorytet operatorów o Nie wolno zmieniać ilość operandów operatora o Nie wolno przeciążać operatory:. ::.*? Operator funkcje, za wyjątkiem operatora =, dziedziczy się klasą pochodną. Dla klasy pochodnej można przeciążyć dowolny operator, nawet te operatory, który już byli przeciążone w klasie bazowej. Funkcje operatory należy używać odpowiednio ich przeznaczeniu. Operatory funkcje nie mogą mieć parametrów domyślnych 2
Przeciążenie operatorów binarnych To jest taki operator, który ma dwa operandy Operator funkcje ma tylko jeden parametr dostanie ten obiekt, który jest umieszczony z prawej strony od operatora. Obiekt z lewej strony od operatora generuje wywołanie operator funkcji i będzie przekazany przez wskaźnik this. Dla napisania operatorów funkcji istnieje wielu sposobów Przykład W8_0_0. Przeciążenie operatorów + - * \ = dla klasy coord coord coord::operator + (const coord&praw) const coord ret; ret.x = x+praw.x; ret.y = y+praw.y; return ret; o Funkcja operator + zwraca obiekt typu coord. Wewnątrz tej funkcji 3
tworzymy pomocniczy obiekt res. Nadaję to możliwość używać ten operator w wyrażeniach złożonych jako a+b+c, dla tego że a+b+c = (a+b)+c i pierwszą sumę trzeba umieścić w pamięci tak, żeby nie zmienić żaden z składników. Dla tego i używamy tą funkcje, która zwraca obiekt. Prawy operand przekazujemy przez referencje, żeby przyspieszyć wykonanie i uniknąć tworzenia kopii obiektu, charakternego przy przekazani samego obiektu, a później zniszczenia tej kopii przez wywołanie destruktora. coord coord::operator -(const coord&praw) const coord ret; ret.x = x-praw.x; ret.y = y-praw.y; return ret; o Tu sytuacje jest podobna do przeciążenia operatora +, tylko trzeba dokładnie pamiętać o kolejności operandu z lewej strony (jest przekazany przez this) i z prawej strony (jest przekazany jako argument). 4
double coord::operator * ( const coord&praw) const /*================================================================== dot_prod ====================================================================*/ doubleret= 0.0; ret = x*praw.x+y*praw.y; return ret; o Wynikiem iloczynu skalarnego jest wartość rzeczywista operator funkcja zwraca wartość typu double. Pierwszy wektor (lewostronni operand) będzie pobrany przez wskaźnik this, a prawostronni operand przekazany jako parametr referencyjny. coord coord::operator * (const double alpa) const /*================================================================== mnozenie wektora przez skalar alpa ====================================================================*/ coord ret; ret.x = x*alpa; ret.y = y*alpa; return ret; o Wynikiem jest wektor (obiekt klasy coord, a skalar jest przekazywany jako parameter. 5
coord coord::operator / (const double alpa) const /*================================================ dzielenie wektora 2D przez skalar alpa =================================================*/ coord res; res.x = x/alpa; res.y = y/alpa; return res; o Wynikiem jest wektor (obiekt klasy coord, a skalar jest przekazywany jako parameter Jeśli prawy operand jest skalarem, to funkcji operatorowi trzeba przekazać skalar. Odwrotnie (lewy operand skalar) jest nie możliwe lewostronni operator generuje wywołanie operatora funkcji i przekazanie wskaźnika this powinien być obiektem klasy, odnośnie do której przeciążamy operator. 6
Przeciążenie operatora przypisania coord & coord::operator = (const coord &praw) /*================================================= Przyciazenie operatora przypisania: zwraca referencje do obiektu ==================================================*/ x = praw.x; y = praw.y; return *this; o Zwraca referencje do obiektu lewostronni operand jest L-value, w dodatek do tego po przypisani zwykłe się zmienia. Funkcja zwraca *this pobiera wartość po wskaźniku this i zwraca referencje. To nadaje możliwość używać kolejki a = b = c; To oznacza, że najpierw obiektowi b będzie przypisane wartości odpowiednich składowych obiektu c (b = c), a później wartości składowych obiektu b zastąpią odpowiedni wartości składowych obiektu a ( a = b). o Parametrem operator-funkcji = jest referencja do obiektu - operandu prawostronnego. Przekazywanie funkcji parametru referencyjnego nie powoduje tworzenia kopii obiektu jest istotnie szybsze od przekazywania obiektu i nie powoduje efektów ubocznych o Przykład W7 7
Przeciążenie operatorów relacyjnych i logicznych Operatory relacyjne i logiczne zwracają 0 (false) lub!0 (true), więc typem wartości zwracanej może być int lub bool. Operatory relacyjne i logiczne mogą się spotkać w wyrażeniach skomplikowanych, które zawierają dane innych typów Przykład W8_1_0 class coord double x; double y; public: coord(double xx, double yy) x = xx; y = yy; coord() x = 0.0; y = 0.0; void set(double xx, double yy) x = xx; y = yy; coord get() return *this; int operator == (coord &praw); //relacje == int operator > (coord &praw); //relacje > int operator && (coord &praw); //logichny koniunkcji int operator (coord &praw); //logichny alternatywy void disp(char *tit) cout << tit << ": " << x << " " << y << endl; ; 8
Przeciążenie operatora == odnośnie klasy coord. Lewostronni operand jest obiektem klasy coord, generuje wywołanie operator-funkcji i jest przekazywany przez this. Prawostronni operand jest również obiektem klasy coord i przekazywany przez referencje. Dane klasy są składowymi wektora, dwa wektora jest równe, jeśli są równe każda z odpowiednich składowych. int coord::operator == (coord &praw) return (x == praw.x && y == praw.y); Przeciążenie operatora > odnośnie klasy coord. Definiujemy dla obiektu danej klasy: relacje >, <, >=, <= są wyznaczone na podstawie porównywania module tych wektorów (norma 2 wektora). (Przecież to nie oznacza, że taka definicja jest jednoznaczna, można by było porównywać na podstawie normy lub inaczej to zależy od tego, którą normę dla podanego zagadnienia uważamy bardziej reprezentatywnej). int coord::operator > (coord &praw) double ro_lew, ro_praw; ro_lew = x*x+y*y; ro_praw = praw.x*praw.x+praw.y*praw.y; return (ro_lew > ro_praw); 9
Przeciążenie operatorów unarnych Funkcja-operator przy przeciążeni operatora unarnego nie potrzebuje przekazywania obiektu unarny operator ma tylko jeden operand, który generuje wywołanie tej funkcji i jest przekazywany przez this. Przykład W8_1_1 class scr_coord int x; int y; public: scr_coord(int xx, int yy) x = xx; y = yy; scr_coord() x = 0; y = 0; void set(int xx, int yy) x = xx; y = yy; scr_coord get() return *this; void disp(char *tit) cout << tit << ": " << x << " " << y << endl; scr_coord operator ++ (); //prefiksowa forma: ++ob; scr_coord operator ++ (int notused); //postfiksowa forma: ob++ ; 10
scr_coord scr_coord::operator ++ () //prefiksowa forma ++ob - najpierw inkrementujemy // - poznej - przepisyjemy ++x; ++y; return *this; scr_coord scr_coord::operator ++ (int notused) //postfiksowa forma ob()++ - najpierw przypisujemy, //pozniej - inkrementujemy //uwaga! dane klasy scr_coord nie //zawieraja wskaznikow, plikow,... //dla tego mozna nie przeciazac konstruktor kopiujacy //(inicjowanie obiektem *this, funkcje zwraca obiekt klasy) //i nie przyciazac operator = (ob = ob1++;) scr_coord ret = *this; x++; y++; return ret; Operator ++ zwiększa wartości składowych obiektu zmienia obiekt. Oprócz tego, może być używany w postaci ob = ++ob1. Dla tego zwraca obiekt klasy, żeby można było wykorzystywać w konstrukcjach złożonych. 11
Przy prefiksowej formie operatora ++ lista argumentów formalnych jest pusta. Dla odróżnienia od formy prefiksowej forma postfiksowa ma argument int notused. Dla zachowania analogii z typami danych wbudowanych C++ przy użyci operatora ++ w kontekście ob = ob1++ dane obiektu ob nie powinni być zmienione. Dla tego postfiksowa forma operatora ++ zwraca obiekt, który zawiera dane przed inkrementowaniem. Operator-funkcje zaprzyjaźnione Istnieje możliwość przeciążenia operator-funkcji odnośnie klasy podanej, używając nie funkcje - metodą klasy, a funkcje zaprzyjaźnioną. Dla funkcji zaprzyjaźnionych wskaźnik this nie jest przekazywany musimy przekazywać jawnie i operator lewostronni, i operator prawostronni (dla operatorów binarnych). Inne cechę przeciążenia operatorów dla funkcji zaprzyjaźnionych pozostają takie same, jak i dla operator funkcji składowych klasy. 12
Dla przeciążenia operatora przypisania (=), a również operatora [ ], można używać tylko operator funkcje składową klasy (nie wolno używać operator funkcje zaprzyjaźnione do klasy). Deklaracja operator-funkcji zaprzyjaźnionej do klasy: friend wart_zwrac operator # (typ_lew lewy_operand, typ_praw prawy operand); Definicja operator-funkcji nie składowej klasy (operator binarny): wart_zwrac operator # (typ_lew lewy_operand, typ_praw prawy operand) W liście argumentów na pierwszym miejscu stoi lewostronni operand, a na prawym prawostronni. Dla operator-funkcji składowych klasy lewostronni operand jest przekazywany niejawnie przez wskaźnik this to oznacza, że lewostronni operand mysi mieć typ obiektu klasy. Na przykład: 13
class coord double x, y; public: coord operator + (double aa); ; coord coord::operator + (double aa) coord tmp; tmp.x = x+aa; tmp.y = y+aa; return tmp; int main() coord ob( ), res; res = ob + 10.0; res = 10.0 + ob; return 0; //OK, lewostronni operand ma typ coord //!OK, lewostronni operand nie jest typu coord 14
Ten problem może być rozwiązany przy użycji operator-funkcji zaprzyjaźnionych do klasy: class coord double x, y; public:. coord operator + (double aa); friend coord operator + (double lew, coord praw); ; coord operator + (double lew, coord praw) coord tmp; tmp.x = lew+praw.x; tmp.y = lew+praw.y; return tmp; int main() coord ob( ), res; res = ob + 10.0; res = 10.0 + ob; return 0; //OK, lewostronni operand ma typ coord //!OK, lewostronni operand nie jest typu coord Przykład W8_1_2. 15
Przeciążenie operatorów unarnych ++, -- na podstawie operator-funkcji zaprzyjaźnionych. Operatory ++, -- zmieniają wartości składowych obiektu. Dla przyspieszenia działania warto przekazywać referencje do obiektów: class scr_coord int x; int y; public: friend scr_coord operator ++ (scr_coord &ob); //prefiksowa forma friend scr_coord operator ++ (scr_coord &ob, int notused); //postfiksowa forma: ob++ ; Przeciążenie operatora indeksu tablicy [ ]. W języku C++ operator [ ] jest operatorem binarnym, przy czym jego można przeciążyć tylko jako funkcjeskładową klasy. zwrac_typ & nazwa_klasy :: operator [ ] (int indeks) //.. Operator [ ] zwraca element tablicy o podanym indeksie typ wartości zwracanej referencje do zwracanego obiektu. To nadaje możliwość używać ob[i] = z lewej strony operatora przypisania. 16
Lewostronni operand obiekt klasy, prawostronni indeks elementu tablicy Przykład: class s_array double *arr; public: s_array(int ndim); //konstruktor: alokuje pamięć dla tablicy arr o ndim elementów ~s_array(); //destruktor: zwalnia tą pamięć double &operator[](int i); void put(double a, int i) arr[i] = a; double get(int i) return arr[i]; ; double & s_array::operator [] (int i) return arr[i]; int _tmain(int argc, _TCHAR* argv[]) int ii; s_array aa(10); //teraz można odwołowywac do obiektu klasy s_array tak, jako do zwyklej tablicy aa[0] = 100.0; //tu [] jest użyty jako L-value aa[1] = aa[0]+100.0;. 17
Bezpieczna tablica - przykład W8_1 Tablica bezpieczna wymaga dość istotnego nakładu pracy, powoduje to znaczny spadek wydajności obliczeń. Dla tego w języku C++ granicy tablic nie są kontrolowane. Przecież w wersji debagowej można zastosować tablicy bezpieczne, a w wersji release usunąć ich. Przeciążenie operatorów new, delete. Przeciążenie lokalne funkcje-operatory new, delete są składowymi danej klasy. Obiekty danej klasy będą alokowane przez przeciążone operatory new, delete, a obiekty innych typów - przez standardowe operatory new, delete. Przeciążenie globalne - funkcje-operatory new, delete są zdefiniowane w pliku globalnie poza deklaracją jakieś klasy (prototypy trzeba usunąć). Obiekty wszystkich typów będą alokowane przez przeciążone operatory new, delete. Przykład W8_3. 18