PARADYGMATY PROGRAMOWANIA Wykład 3 Definiowanie operatorów i ich przeciążanie Przykłady zastosowania operatorów: a) operator podstawienia ( = ) obiektów o złożonej strukturze, b) operatory działania na łańcuchach: + konkatenacja - wycięcie z łańcucha podanego innego łańcucha, ^ powtórzenie łańcucha zadaną ilość razy, ==, <=, >= <>... - relacje porządku leksykograficznego dat, c) operatory do operacji na macierzach + dodawanie macierzy * mnożenie macierzy ^ mnożenie macierzy określoną ilość razy przez siebie d) operacje na datach: + dodawanie liczby do daty - odejmowanie liczby od daty - wynik: nowa data - odejmowanie dat - wynik: liczba dni dzieląca daty ==, <=, >= <>... - relacje pomiędzy datami e) operacje na zbiorach: + dodawanie zbiorów, - odejmowanie zbiorów (operator dwuragumentowy) - dopełnianie zbioru (operator jednoargumentowy) * wyliczanie części wspólnej ==, <=, >= <>... - relacje pomiędzy zbiorami Ponieważ do zdefiniowania nowych operatorów używamy tych samych symboli już zdefiniowanych na poziomie języka (predefiniowanych) to mówimy o przeciążaniu operatorów. Dlaczego istnieje potrzeba przedefiniowywania operatora podstawienia: class String
String( char *_stg ); ~String() private: char *stg; int length; String::String( char *_stg ) len = strlen( _stg ); stg = new char[ len + 1 ]; strcpy( stg, _stg ); ~String() delete [] stg;... String s1("ala ma kota"); String s2("to kot Ali");... s2 = s1; //??? - co z zaalokowaną pamięcią w s2... //??? - jak wykonuje się destruktor Operatory a metody wykonywane na obiektach obiekt_1 operator obiekt_2 => obiekt_3 obiekty mogą być dowolnych typów - niekoniecznie takich samych obiekt_2 lub obiekt_3 może być typu prostego (np. int, BOOL, float, double char) każdemu operatorowi odpowiada funkcja wykonująca odpowiednią operację, zapis w postaci infiksowej lub prefiksowej jest równoważny - zwykliśmy jednak stosować zapis infiksowy, np: (a * (b + 10) + 15) / (b + 6) jest równoważny div( add( mul( a, add( b, 10 )), 15 ), add( b, 6 )) Operatory podlegające przeciążaniu + - * / % ^ & ~! = < > += -= *= /= %= ^= &=
!= << >> <<= >>= == = <= >= && ++ -- ->*, -> [] new delete Zasady przeciążania: nie można zmieniać liczby operandów operatora, priorytety operatorów oraz zasady ich łączności nie podlegają zmianie, nie można modyfikować znaczenia operatorów wobec typów predefiniowanych języka( int, float, char...) Składnia deklaracji i definicji operatorów przeciążanych <typ wyniku operacji> operator <symbol_operatora> ( <specyfikacja_parametru> ); Przykład: class String String( char *_stg ); String& operator=( const String& _stg ) private: char *str; int length; String& String::operator= (const String &_stg ) if (this == &_stg) if ( _stg.length > length ) delete [] str; length = _stg.length; str = new char [length + 1]; strcpy( str, _stg.str); String& String::operator= ( char *_stg ) if (this->str == &_stg) if ( strlen( _stg) > length )
delete [] str; length = strlen( _stg); str = new char [length + 1]; strcpy( str, _stg ); Przeciążony operator przypisania a konstruktor kopiujący: konstruktor kopiujący - automatycznie tworzony konstruktor o prototypie X (const X&) gdzie: X - identyfikator klasy Wołany przy: Inicjalizacji innym obiektem tej samej klasy, przekazaniu obiektu do funkcji przez wartość, zwracaniu przez funkcję obiektu. Różnice pomiędzy operatorem przypisania a konstruktorem kopiującym: nie ma potrzeby zwalniania składowych obiektu do którego następuje przypisanie, nie ma potrzeby zabezpieczania się przed kopiowaniem na samego siebie, nie ma potrzeby zwracania referencji na obiekty docelowy. String::String( const String& _stg ) length = _stg.length; str = new char[ length + 1]; strcpy( str, _stg.str ); String::String( char *_stg ) length = strlen( _stg); str = new char[ length + 1]; strcpy( str, _stg ); Przykłady implementacji operatorów łańcuchowych: bool String::operator==( const String& _stg ) return ( strcmp( str, _stg.str ) == 0 );
bool String::operator!=( const String& _stg ) return (strcmp( str, _stg.str )!= 0); char& String::operator[]( int index ) if ((index >= 0) && (index < lengtgh)) return str[ index ]; else return str[0]; #if 0 String& String::operator+=( const String& _stg )... //??? #endif String String::operator+( const String& _stg )... //??? String& operator+=( String& left, const String &right ) String tymcz; tymcz = left; tymcz = tymcz + right; left = tymcz; return left; String operator+( const String& left, const String &right ) String tymcz; tymcz = left; tymcz += right; return tymcz;
Przykłady użycia: String s1, s2, s3; s1 = "Jan"; // s1 = String( Jan ); s2 = "Anna"; s2 += "Kowalska"; s3 = s1 + s2; if (s2!= s1) s3 = (s2 + " ") + "Nowak"; s3 = "Nowak" + s2; // BŁĄD; s3 = String("Nowak") + s2; // OK Przykład - realizacja operacji na wektorach: wektor.h: class Wektor protected: int liczba; int *pocz; Wektor(int); Wektor(const Wektor &x); ~Wektor(); int Podaj(int); void Wpisz(int, int); void Mnoz(int); void Dodaj(Wektor); ; class WektorR : public Wektor private: int przekroczenie; WektorR(int); int & operator [] (int i); void Sprawdz(); WektorR & operator += (WektorR); WektorR & operator *= (int); WektorR & operator = (WektorR &); WektorR & operator -(); WektorR & operator -= (WektorR); int operator == (WektorR &); WektorR & operator ++ ();
; WektorR & operator -- (); wektor.cpp: #include "wektor.h" Wektor::Wektor(int n) pocz = new int[liczba = n]; pocz[i] = 0; Wektor::Wektor(const Wektor &x) pocz = new int[liczba = x.liczba]; pocz[i] = x.pocz[i]; Wektor::~Wektor() delete [liczba] pocz; int Wektor::Podaj(int k) if ((k < liczba) && (k >= 0)) return pocz[k]; else return pocz[0]; void Wektor::Wpisz(int x, int k) if (k < liczba && k >= 0) pocz[k] = x; void Wektor::Dodaj(Wektor b) int i; for (i=0; i<liczba; i++) pocz[i] += b.pocz[i]; void Wektor::Mnoz(int k)
for (int i=0; i< liczba; i++) pocz[i] = pocz[i]*k; WektorR::WektorR(int n):wektor(n) przekroczenie = 0; int WektorR::Sprawdz() return (przekroczenie); int & WektorR::operator [] (int i) if ( i<0 ) i = 0; przekroczenie = 1; if ( i>=liczba) i = liczba-1; przekroczenie = 1; return pocz[i]; WektorR & WektorR::operator += (WektorR b) int i; if (liczba <= b.liczba) for (i=0; i<liczba; i++) pocz[i] += b.pocz[i]; void WektorR::operator *= (int k) for (int i=0; i< liczba; i++) pocz[i] = pocz[i]*k; WektorR & WektorR::operator = (WektorR &b) if (this!= &b) if (liczba!= b.liczba) delete [liczba] pocz; pocz = new int[liczba = b.liczba];
pocz[i] = b.pocz[i]; WektorR & WektorR::operator -() pocz[i] = -pocz[i]; WektorR & WektorR::operator -= (WektorR b) pocz[i] -= b.pocz[i]; int WektorR:: operator == (WektorR &b) int i; if (liczba!= b.liczba) return 0; else for (i=0; i<liczba; i++) if (pocz[i]!= b.pocz[i]) return 0; return 1; WektorR & WektorR:: operator ++ () pocz[i]++; WektorR & WektorR:: operator -- () pocz[i]--; mian()
WektorR a, b, c; a[5] = 10; x = a[5]; a = ++b; a += b; b = -a; a = (b = c); a = b = c; Przeciążanie operatorów poza klasami: void operator += (Wektor &a, Wektor &b) for (int i=0; i < a.liczba; i++) a.pocz[i] += b.pocz[i]; Stosujemy gdy lewy operand nie jest obiektem klasy. W pozostałych przypadkach zaleca się stosowanie operatora jako metody Operator () Przykład: Jako jedyny pozwala przekazać większą niż dwa liczbę argumentów (NIEMOŻLIWE np. Z WYKORZYSTANIEM OPERATORA [] ) class Tablica protected: int m; // liczba wierszy int n; // liczba kolumn int *pocz; Tablica(int, int); ~Tablica(); int & operator() (int ind_w, int ind_k); ; Tablica::Tablica(int liczbaw, int liczbak) m = liczbaw; n = liczbak;
pocz = new int[m*n]; for (int i = 0; i < m*n; i++) pocz[i] = 0; Tablica::~Tablica() delete [m*n] pocz; int & Tablica::operator() (int i, int j) if ( i< 0 i >= m) i = 0; if (j < 0 j >= n) j = 0; return pocz[i * n + j]; void main(void) Tablica t(10, 10); t(0,0) = 10; t(5,7) = 100; Funkcje i klasy zaprzyjaźnione Stosujemy gdy funkcja nie będąca metodą danej klasy powinna mieć dostęp do jej składowych prywatnych lub chronionych. Informację o zaprzyjaźnieniu umieszczamy na początku deklaracji klasy, do której dostęp otwieramy: Udostępnienie danych pojedynczej funkcji: friend <typ funkcji> <ident.funkcji> ( <parametry> ) Udostępnienie danych funkcji przeciążającej operator: friend <typ wyniku> operator <symbol operatora> ( <parametry> ) Udostępnienie danych pojedynczej metodzie innej klasy friend <typ metody> <ident.klasy>::<ident.metody> ( <parametry> ) Udostępnienie danych całej innej klasie X: friend class X;
Przykład - porównywanie zawartości wektorów i tablic: class Tabl3; class Wekt3 friend void Sprawdz(const Wekt3 &, const Tabl3 &); private: int w[3]; Wekt3(); void Wczytaj(); void Drukuj(); ; class Tabl3 friend void Sprawdz(const Wekt3 &, const Tabl3 &); private: int t[3][3]; Tabl3(); void Wczytaj(); void Drukuj(); ; void Sprawdz(const Wekt3 & x, const Tabl3 & y) int i, j, ident; for (i=0; i<3; i++) ident = 1; for (j=0; j<3; j++) if (x.w[j]!= y.t[i][j]) ident = 0; if (ident == 1) cout << "Wiersz " << i << " jest identyczny" << endl;