180 likes | 354 Views
Programowanie obiektowe III rok EiT. dr inż. Jerzy Kotowski Wykład XII. Program wykładu. Język C++ Klasy, c.d. przeciążanie (przeładowanie) operatorów Przykład problemu Podstawy języka JAVA Klasówka. Literatura.
E N D
Programowanie obiektoweIII rok EiT dr inż. Jerzy Kotowski Wykład XII
Program wykładu • Język C++ • Klasy, c.d. • przeciążanie (przeładowanie) operatorów • Przykład problemu • Podstawy języka JAVA • Klasówka
Literatura • C++ for C programmers, Ira Pohl, The Benjamin/Cummings Publishing Company, Inc. • Symfonia C++, Jerzy Grębosz, Oficyna Kallimach, Kraków 1999
Reguły przeciążania operatorów • Przeciążenia operatora dokonuje się definiując swoją własną funkcję, która: • Nazywa się operator@ gdzie @ jest operatorem, o którego przeładowanie nam chodzi. • Co najmniej jeden z argumentów jest obiektem danej klasy. • Składnia: typ operator@(argumenty) • Lista operatorów, które mogą być przeładowane:+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> new delete () [] • Operatory & * - +mogą być przeciążane w zarówno w wersji jednoargumentowej jak i dwuargumentowej. • Lista operatorów, które nie mogą być przeciążane:. .* :: ?:
Reguły przeciążania operatorów c.d. • Przeciążać można wymienione operatory i tylko te. • Za wyjątkiem operatorów new i delete nie ma ograniczeń na typ zwracanej wartości. • Nie można zmieniać priorytetu operatora. • Nie można zmieniać liczby argumentów operatora. • Nie można zmieniać łączności operatora. • Ten sam operator można przeciążyć wielokrotnie. • Przynajmniej jeden z argumentów musi być typu zdefiniowanego przez użytkownika. Nie ma znaczenia który. • Jeżeli funkcja operatorowa jest zdefiniowana jako funkcja zewnętrzna (tzn. nie jako funkcja składowa w klasie) to liczba argumentów na liście musi się zgadzać z liczbą, na której pracuje operator. • W przypadku operatora definiowanego jako funkcja składowa lista argumentów nie zawiera lewego operandu. Jest nim obiekt typu klasa, w której dokonuje się takiego przeciążenia. • Operatory predefiniowane: = & , new delete
Przeciążamy co popadnie .. \test0.sln • Program vect1.cpp class vect { int *p; int size; public: int ub;// upper bound = size-1 vect(int n=10);// konstruktor z domniemanym argumentem vect(const vect &v); // konstruktor kopiujący ~vect() { delete p; } // destruktor vect& operator =(const vect &v);// operator podstawiania intoperator ==(const vect &v) const;// porównanie wektorów int& operator[](int i);// operator indeksowy vect operator+(const vect &v) const;// dodawanie wektorów vect operator-(const vect &v) const;// odejmowanie wektorów intoperator*(const vect &v) const; // iloczyn skalarny vect operator*(int a) const;// mnożenie wektora przez skalar voidoperator !() const;// print };
Opis interfejsu klasy • Operator podstawiania i operator indeksowy udostępniają odpowiednie obiekty. Przeciążenie operatora podstawiania jest niezbędne do poprawnej pracy programu. • Wszystkie pozostałe przeciążone operatory obiecują nie zmieniać swoich operandów. Podobnie deklaruje konstruktor kopiujący. • Wielokrotnie korzystano ze słowa kluczowego this np. w składni vect temp(*this); • Mnożenie wektora przez liczbę zdefiniowano jako funkcję składową w klasie. Było to możliwe bo lewy argument jest w tym przypadku obiektem klasy. • Mnożenie liczby przez wektor zdefiniowano jako funkcję zewnętrzną ze względu na to, że lewy argument jest obiektem typu build-in. • Definicja ta korzysta z funkcji składowej i jest bardzo prosta ale formalnie jest niezbędna: vect operator*(int a, const vect &v) { return v*a; }
Kod klienta - opis • Zakładamy trzy wektory trójelementowe a, b, c. • Do dwóch pierwszych coś podstawiamy za pomocą przeciążonego operatora indeksowego. • Do wektora c podstawiamy wartość wyrażenia 2*a-b. • Wyprowadzamy wartości wektorów a, b, c przy pomocy przeciążonego operatora logicznej negacji. • Na koniec wyprowadzamy wartość iloczynu skalarnego a*b. • Prawidłowy jest napis vect d[5];Jest to 5-elementowa tablica obiektów typu vect zakładanych przy pomocy konstruktora domniemanego, to znaczy 10-elementowych. • Użycie: d[2][3] = 7; • W powyższej instrukcji użyto dwóch różnych operatorów indeksowych: wbudowanego i przeciążonego.
Klasa histogram .. \test0.sln • Cel: przeciążenie operatora << dopisującego nową wartość do histogramu. • Interfejs klasy zawiera tylko niezbędne elementy. • Na potrzeby kodu klienta potrzebne są poniższe własności klasy: • Dynamiczna rezerwacja pamięci • Klasa ma mieć zabezpieczenie przed wyjściem poza obszar histogramu. • Łączność operatora << w celu umożliwienia napisów typu: h << a << b; • histogram.cpp
Definicja klasy histogram class histogram { double min; // dolna granica double max; // górna granica int przedz; // liczba przedziałów double * adlk; // wskaźnik do tablicy liczników double szer; // szerokość przedziału public: histogram(double=0.0,double=1.0,int=10);// konstruktor ~histogram (){ delete [] adlk; }// destruktor histogram & operator << (double);// dorzuca wartość intoperator [] (int);// licznik - zwraca wartość pola };
Najważniejsze elementy klasy • Przeciążony operator udostępnia „siebie” co rozwiązuje problem łączności operatora: • histogram& • return (*this); histogram& histogram::operator<<(double w) { int np=(w-min)/szer; if((np >= 0)&&(np<=przedz-1)) adlk[np]++; //tylko "dobre" wartości return (*this); } int histogram::operator [] (int n) { if((n<0) || (n>=przedz))n=0; return adlk[n]; }
Kod klienta void main(void) { constint lprzedz=4; double min=0, max=5, szer=(max-min)/lprzedz; histogram h(min,max,lprzedz);// cztery przedziały od 0 do 5 h << 1.5 << 2.4 << 3.8 << 3.0 << 2.0 << 3.5 << 2.8 << 4.6; h << 12.0 << -3.5; for (int i=0;i<10;i++) h << i/2.0; cout << "Przedzial liczba\n"; for(i=0;i<lprzedz;i++) cout << "[" << i*szer << ", " << (i+1)*szer << "]\t" << h[i] << "\n"; getch(); }
Przeciążanie operatorów inkrementacji i dekrementacji • Nienazwany argument pozwala przeciążać operatory inkrementacji i dekrementacji jako składowe klasy tak, aby można było odróżnić znaczenia post i prefiksowe. • Przypomnienie: void Ala(int) • Nienazwany argument występuje w definicji i w prototypie. • Operator z jednym argumentem udaje operator dwuargumentowy i ten drugi argument może być argumentem nienazwanym. • Odpowiada to składni x operator y, gdzie pierwszy argument to “ja sam” a drugiego może nie być. • Czyli: x++ to przeciążenie operatora z dwoma a ++x przeciążenie operatora z jednym argumentem. • Przykład: class String { public: String& operator ++(); String operator ++(int); };
Klasa String .. \test0.sln class String{ // String dynamiczny IV.cpp char *s; int len; static Stan; public: String(void); String(int n); String(constchar *p); String(const String& str); ~String() { delete s; } int lenght() const { return(len); } String& operator =(const String &str);// operator podstawiania void print() const {cout << s << "\nLenght: " << len << "\n";} voidoperator !() const {print();} // print operatorchar *() const { return(s); }// operator konwersji staticvoid switch_stan(void); String& operator ++(void); // małe na duże i zwraca nowy String operator ++(int); // małe na duże i zwraca stary String& operator --(void); // duże na małe i zwraca nowy String operator --(int); // duże na małe i zwraca stary friend String operator +(const String str1, const String str2);};
Interesujące elementy klasy String • Klasa String podobnie jak poprzednio ma cztery konstruktory • Konstruktor kopiujący korzysta z przeciążonego operatora podstawiania • Pojawił się przeciążony operator dodawania zdefiniowany jako funkcja globalna zaprzyjaźniona z klasą:friend String operator +(const String str1, const String str2); • Wszędzie gdzie jest to możliwe pojawiło się słowo kluczowe const • Dokonano przeciążenia jednoargumentowego operatora logicznego ! • String& operator ++(void); zamienia małe litery na duże i zwraca nowy string jako referencję • String operator ++(int); zamienia małe litery na duże i zwraca stary string przez kopiowanie • String& operator --(void); zamienia duże litery na małe i zwraca nowy string jako referencję • String operator --(int); zamienia duże litery na małe i zwraca stary string przez kopiowanie
Wybrane definicje funkcji String::String(const String& str) { s=0; (*this)=str; } String& String::operator ++(void) { char mm = 'A' - 'a'; for(int i=0; i<len; i++) if(s[i] >='a' && s[i] <='z') s[i] += mm; return (*this); } String String::operator ++(int) { String temp(*this); char mm = 'A' - 'a'; for(int i=0; i<len; i++) if(s[i] >='a' && s[i] <='z') s[i] += mm; return temp; } String operator +(const String str1, const String str2) { String temp(str1.len+str2.len); strcpy(temp.s,str1.s); strcat(temp.s,str2.s); return temp;}
Coś za coś • Operator dodawania obiektów klasy String:String operator +(const String str1, const String str2)można zdefiniować jako funkcję składową w klasie:String String::operator +(const String& str) const • Korzyści: Niepotrzebna jest już przyjaźń • Straty: Nie ma możliwości dokonywania niejawnej konwersji argumentów. Związane jest to z faktem, że napis a+b jest równoważny napisowi a.operator+(b). Nie można zatem w kodzie klienta napisać na przykład c=a+b jeżeli a nie jest typu String. • Ewentualne pytanka na klasówkę: • W jaki sposób wykonywana jest linia: cout << a+b; • W jaki sposób wykonywana jest linia: cout << str1+b; • Czy kompilator przeżyje coś takiego: cout << str1+str2;
Analogie – klasa Complex class Complex { double Re,Im; public: Complex(float x=0, float y=0): Re(x), Im(y){} void print() { cout << Re << "+" << Im << "i "; } // operator double() { return(sqrt(Re*Re+Im*Im)); } trzeba usunąć!!! friend Complex operator+(Complex z1, Complex z2); }; Complex operator+(Complex z1, Complex z2) { // Operator dodawania przeciążony jako funkcja globalna Complex temp; temp.Re=z1.Re+z2.Re; temp.Im=z1.Im+z2.Im; return temp; } void main(void) { Complex a(1,5),b(0,-2),c; cout << "\na= "; a.print(); cout << "\nb= "; b.print(); c=1+a+b; cout << "\n1+a+b= "; c.print(); // Niejawna konwersja argumentu getch(); } • Należy usunąć konwersję na typ double bo prowadzi ona do niejednoznaczności w wyrażeniu 1+a: error C2666: 'operator`+'' : 2 overloads have similar conversions