180 likes | 355 Views
Programowanie obiektowe III rok EiT. dr inż. Jerzy Kotowski Wykład XI. Program wykładu. Język C++ Klasy, c.d. konwersja typów przeciążanie i selekcja funkcji funkcje zaprzyjaźnione przeciążanie operatorów – operator konwersji Przykład problemu Podstawy języka JAVA Klasówka. Literatura.
E N D
Programowanie obiektoweIII rok EiT dr inż. Jerzy Kotowski Wykład XI
Program wykładu • Język C++ • Klasy, c.d. • konwersja typów • przeciążanie i selekcja funkcji • funkcje zaprzyjaźnione • przeciążanie operatorów – operator konwersji • 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
Konwersja typów - tradycyjna • Wyrażenie arytmetyczne typu x+y charakteryzuje się wartością i typem wartości. • W trakcie obliczania wartości wyrażeń ma miejsce automatyczna konwersja typów. • Prawdziwe są następujące reguły konwersji typów: • Wpierw: • Typy char oraz short są promowane do typu int. • unsigned char i unsignedshort są promowane do typu unsigned. • Następnie: • Jeżeli wyrażenie jest dalej typu mixedexpression, wtedy zgodnie z hierarchią typów:int < unsigned < long < unsigned long < float < doubleoperand niższego typu jest promowany do typu drugiego operandu i wartość całego wyrażenia jest też tego typu. • Automatyczna konwersja typu ma również miejsce w przypadku operatora podstawiania. • Istnieje możliwość dokonywania jawnej konwersji przy pomocy operatora rzutowania. Priorytet 15, jednoargumentowy, prawostronnie łączny.
Konwersja abstrakcyjnych typów danych • Explicit conversion jest niezbędna jeżeli konwersja niejawna jest niepożądana oraz kiedy brak konwersji jest błędem składni. • C++ w celu integracji ADT oraz typów built-in dysponuje możliwościami jawnej konwersji z użyciem składni: type_name (expression) • Przykład:było x=(float)y; jest x=float(y);było p=(char *)q; jest p=char *(q);typedef char *ptr_c; p=ptr_c(q); • Konstruktor z jednym argumentem jest operatorem konwersji typu z typu jaki ma argument do typu jaki określa klasa konstruktora. • String dynamiczny II.cpp • W przykładzie inny jest tylko kod klienta.
Kod klienta .. \test0.sln • Z wydruku widać, że wszystkie cztery obiekty zostały utworzone przy pomocy operatora konwersji tzn. konstruktora z jednym argumentem: String::String(char *p) char *str1="Typem, z ktorego dokonuje sie przeksztalcenia "; char *str2="nie musi byc typ wbudowany."; String a(str1),b(str2); String c="Symfonia C++"; String d=String("Jerzy Grebosz"); cout << endl << " ** Wyniki ** " << endl << endl; a.print(); b.print(); c.print(); d.print(); getch();
Konwersja z typu ADT .. \test0.sln • Przedstawiony program String dynamiczny II.cpp prezentuje konwersję typu built-in na typ zdefiniowany przez użytkownika czyli w tym przypadku konwersję z typu char* na typ String. • Nie jest możliwe dodanie konwersji do istniejącego typu built-in z naszego typu ADT. • Musimy taką konwersję zdefiniować sami wewnątrz naszej klasy. • Dokonuje się w tym celu przeciążenia operatora rzutowania. • Składnia ogólnie: operator type() { ... }. • Ten operator jest funkcją składową, która nic nie zwraca i ma pustą listę argumentów. Nie można jej zatem przeciążać. • Przykład: chcemy dokonać konwersji odwrotnej z typu String na char*. • Dodajemy jedną składową w definicji klasy String. • Postać operatora: operator char*() { return(s); }tzn. udostępniamy pole robocze obiektu klasy String. • String dynamiczny III.cpp
Kod klienta • Zamiast • jest • Usunięcie operatora konwersji skutkuje błędem kompilacji a.print(); b.print(); c.print(); d.print(); cout << a; cout << b; cout << endl << c; cout << endl << d;
Przeciążanie i selekcja funkcji • Przy wywołaniu funkcji przeciążonej kompilator musi uruchomić algorytm selekcji w celu wyboru odpowiedniej funkcji. • Postać algorytmu selekcji zależy od tego jakiego typu konwersje są dopuszczalne. • Należy upewnić się jakie reguły dopuszcza kompilator, którym się posługujemy. • Overloaded Function Selection Algorithm • Kompilator próbuje znaleźć funkcję z idealnie pasującymi typami argumentów. Przyjmuje się przy tym, że: • short, char oraz stała 0 odpowiadają typowi int, • float odpowiada typowi double. • Jeżeli to nie wystarcza kompilator próbuje dokonać klasycznej niejawnej konwersji typów dla obiektów typu built-in. Taka konwersja nie ma prawa prowadzić do utraty informacji. Dotyczy ona w związku z tym konwersji wskaźników i następujących rozszerzeń (projekcji na wyższe typy): int do longoraz int do double. • Następnie kompilator próbuje wykorzystać typy zdefiniowane przez użytkownika o ile prowadzą one do jednoznacznych reguł konwersji.
Przykład – klasa Complex.cpp .. \test0.sln • W programie jest przeciążona trzykrotnie funkcja greater. • Kompilator nie zgodził się na wywołanie jej z argumentami typu int oraz double: ‘greater' : 3 overloads have similar conversions Formal parameter lists are too similar to resolve ambiguity. class Complex { double Re,Im; public: Complex(float x=0, float y=0): Re(x), Im(y){} void assign(double r,double i) { Re=r; Im=i; } void print() { cout << Re << "+" << Im << "i "; } operatordouble() { return(sqrt(Re*Re+Im*Im)); } // konwersja }; inlineint greater(int i,int j) { return(i>j?i:j); } inlinedouble greater(double x,double y) { return(x>y?x:y); } inline Complex greater(Complex w,Complex z) { return(w>z?w:z); }
Kod klienta • Kompilator wymyślił sobie własną algebrę liczb zespolonych. • Complex greater(Complex w,Complex z) {return(w>z?w:z);} • Porównanie w>z dokonywane jest poprzez wstępną konwersję w i v do obiektów typu double bo te można porównać. • Zwracaną wartością jest liczba zespolona o większym module. • Nigdy nie zachodzi konwersja przy zwracaniu wartości ponieważ jeżeli była potrzebna to już została dokonana przy przekazywaniu argumentów. • Usunięcie operatora konwersji Complex::operator double() skutkuje błędem kompilacji. Complex u(1,3), v(2,2),z; z=greater(u,v); cout << "Compare "; u.print(); cout << " and "; v.print(); cout << " : greater is "; z.print();
Funkcje zaprzyjaźnione – friend functions • Słowo kluczowe friend daje dostęp funkcji, która nie jest memberfunction do ukrytych składowych klasy. • Filozofia języka C++, a w ogólności OOP, prowadzi do koncepcji separacji obiektów. Friendfunctions łamią tę filozofię. Musimy mieć zatem istotny powód, aby z takiego mechanizmu korzystać. • Powody: • Chcemy zdefiniować funkcję, która będzie miała dostęp do ukrytych składowych więcej niż jednej klasy równocześnie. • Wszystkie argumenty friendfunction ma przekazywane przez listę argumentów. Pozwala to na dokonywanie ukrytej konwersji typów i może być czasami bardzo wygodne i efektywne przy tworzeniu clientcode. Ma to w szczególności miejsce przy przeciążaniu operatorów. • Friendfunction musi się pojawić w deklaracji klasy, do której jest zaprzyjaźniona. • Nazwa funkcji musi być poprzedzona słowem friend. • Nie ma znaczenia, czy będzie to w bloku private czy też w bloku public.
Przykłady składni przyjaźni .. \test0.sln • Przyjaźń z funkcją zewnętrzną: • Przyjaźń z funkcjąskładową: • Przyjaźń z wszystkimifunkcjami składowymi: • Przykład z friend function - mnożenie wektor * macierz. Powód wykorzystania takiego mechanizmu - potrzeba dostępu do dwóch klas równocześnie. • Vect_matrix.cpp class star { … friend void Ala(); int Ola(); … }; class galaxy { … friend int star::Ola(); … }; class cosmos { … friend class galaxy; … };
Opis programu • Cel: Stworzenie narzędzia, które pomnoży wektor przez macierz. • Mnożenie będzie zdefiniowane jako funkcja zewnętrzna. Wymaga to zadeklarowania przyjaźni w obu klasach • Interesujące elementy programu: • Forward reference – deklaracja zapowiadająca. • Konstruktor kopiujący i przeciążony operator podstawiania w klasie vect. • Oba elementy są niezbędne. Widać to po debuggingu. • Przeciążony operator podstawiania pojawił się po raz pierwszy. • Funkcja vect mpy(vect& v,matrix& m) korzysta z przyjaźni. • Klasa matrix została potraktowana po macoszemu. Akurat w tym przykładzie nie było potrzeby utworzenia pełnego interfejsu klasy.
Przeciążony operator podstawiania • Zdefiniowany jako funkcja składowa • Na liście argumentów jest tylko prawy argument. Lewym argumentem jestem ‘ja sam’. • Prawy argument jest udostępniany przez referencję i nie można go zmieniać. • Operator udostępnia wynik (referencję), czyli lewy argument. Technika udostępnienia wymaga użycia wskaźnika this. • Użycie: a=b=c; Referencja!!! • Można napisać równieć: a.operator=(b); ale po co? vect& vect::operator =(const vect &v) { size=v.size; if(p) delete p; p=new int[size]; ub=size-1; for(int i=0;i<size;i++) p[i]=v.p[i]; return *this; }
Konstruktor kopiujący • Definicja konstruktora kopiującego jest podejrzanie podobna: • Można to wykorzystać, aby nie powielać kodu: vect& vect::operator =(const vect &v) { size=v.size; if(p) delete p; p=new int[size]; ub=size-1; for(int i=0;i<size;i++) p[i]=v.p[i]; return *this; } vect::vect(const vect &v) { size=v.size; p=new int[size]; ub=size-1; for(int i=0;i<size;i++) p[i]=v.p[i]; } vect::vect(const vect &v) { p=0; *this=v; } Ta linia musi być Dlaczego?
Słowo kluczowe operator • Słowo kluczowe operatorsłuży do: • tworzenia funkcji członkowskiej, która dokonuje konwersji typu • przeciążania operatorów. • W programie Vect_matrix.cpp nagłówek funkcji vect mpy(vect& v,matrix& m)może zostać zastąpiony przez vect operator* (vect& v, matrix& m) • Wywołanie lepiej wygląda. Zamiast instrukcji y=mpy(x,A);pisze się: y=x*A; • Lista WSZYSTKICH niezbędnych zmian w programie: • W definicjach klas prototyp funkcji mpypowinna zastąpić linia: friend vect operator* (vect& v,matrix& m); • nagłówek funkcji mpy powinien zostać zastąpiony przezvect operator* (vect& v, matrix& m) • W segmencie main zamiast y=mpy(x,A); ma się pojawić: y=x*A;
Operator konwersji • Do przekształcenia jednego typu na drugi mamy dwa narzędzia: • Konstruktor jednoargumentowy:T::T(K& k); • Funkcję konwertującą (operator konwersji):K::operator T(); • Zdaniem J. Grębosza konwersja jest rozwiązaniem zawsze lepszym. Wynika to między innymi z następujących faktów: • Nie można zdefiniować konstruktora dla typu wbudowanego. • Nie można napisać konstruktora dla klasy, która nie jest naszą własnością. • Konstruktor wymaga idealnego dopasowania argumentów. • Konstruktor nie jest dziedziczony przez klasy pochodne a operator konwersji jest – tak samo jak wszystkie inne funkcje składowe K T konwersja konstruktor