Programowanie obiektowe III rok EiT
description
Transcript of Programowanie obiektowe III rok EiT
Programowanie obiektoweIII rok EiT
dr inż. Jerzy KotowskiWykł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 int operator ==(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 int operator*(const vect &v) const; // iloczyn skalarny vect operator*(int a) const;// mnożenie wektora przez skalar void operator !() 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łupublic: histogram(double=0.0,double=1.0,int=10); // konstruktor ~histogram (){ delete [] adlk; } // destruktor histogram & operator << (double); // dorzuca wartość int operator [] (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){ const int 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(const char *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";} void operator !() const {print();} // print operator char *() const { return(s); }// operator konwersji static void 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
• 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
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();}