PARADYGMATY PROGRAMOWANIA Wykład 3
Transkrypt
PARADYGMATY PROGRAMOWANIA Wykład 3
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 { public: 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 { public: String( char *_stg ); String& operator=( const String& _stg ) private: char *str; int length; } String& String::operator= (const String &_stg ) { if (this == &_stg) return *this; if ( _stg.length > length ) { delete [] str; length = _stg.length; str = new char [length + 1]; } strcpy( str, _stg.str); return *this; } String& String::operator= ( char *_stg ) { if (this->str == &_stg) return *this; if ( strlen( _stg) > length ) { delete [] str; length = strlen( _stg); str = new char [length + 1]; } strcpy( str, _stg ); return *this; } 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 s2 s2 s3 if = "Jan"; // s1 = String(“Jan”); = "Anna"; += "Kowalska"; = s1 + s2; (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; public: 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; public: 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]; for (int i=0; i<liczba; i++) pocz[i] = 0; } Wektor::Wektor(const Wektor &x) { pocz = new int[liczba = x.liczba]; for (int i=0; i<liczba; i++) 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]; return *this; } 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]; } for (int i=0; i<liczba; i++) pocz[i] = b.pocz[i]; } return *this; } WektorR & WektorR::operator -() { for (int i=0; i<liczba; i++) pocz[i] = -pocz[i]; return *this; } WektorR & WektorR::operator -= (WektorR b) { for (int i=0; i<liczba; i++) pocz[i] -= b.pocz[i]; return *this; } 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 ++ () { for (int i=0; i<liczba; i++) pocz[i]++; return *this; } WektorR & WektorR:: operator -- () { for (int i=0; i<liczba; i++) pocz[i]--; return *this; } 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 () Jako jedyny pozwala przekazać większą niż dwa liczbę argumentów (NIEMOŻLIWE np. Z WYKORZYSTANIEM OPERATORA [] ) Przykład: class Tablica { protected: int m; // liczba wierszy int n; // liczba kolumn int *pocz; public: 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]; public: Wekt3(); void Wczytaj(); void Drukuj(); }; class Tabl3 { friend void Sprawdz(const Wekt3 &, const Tabl3 &); private: int t[3][3]; public: 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; } }