580 likes | 720 Views
Programowanie obiektowe 2013/2014. Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego Matuszyka. Konwersje. Konwersja polega na przekształceniu obiekty jednej klasy (typu) na inny.
E N D
Programowanie obiektowe2013/2014 Łukasz SztangretKatedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiałyDanuty Szeligi i Pawła Jerzego Matuszyka
Konwersje • Konwersja polega na przekształceniu obiekty jednej klasy (typu) na inny. • Konwersje najczęściej zachodzą niejawnie.
Kiedy konwersja się przydaje? #include<iostream> usingnamespacestd; classcmpx { friend ostream &operator<<(ostream &, const cmpx &); friendcmpx operator+(constcmpx &,constcmpx &); double re; double im; public: cmpx():re(0),im(0){} cmpx(double a, double b) {re=a;im=b;} }; ostream &operator<<(ostream &S, const cmpx &A) { S<<A.re<<showpos<<A.im<<"i"; return S; } cmpxoperator+(const cmpx &A,constcmpx &B) { cmpx C; C.re=A.re+B.re; C.im=A.im+B.im; return C; } intmain() { cmpx A(1,2), B(3,4), C, D; C=A+B; cout<<C<<endl; D=A+1; cout<<D<<endl; return 0; } 4+6i BŁĄD
Co można zrobić??? • Przeładować operator+: cmpx=operator+(cmpx,double); Wówczas wywołanie D=A+1; nie wygeneruje błędu. Ale wywołanie D=1+A; tak -> kolejne przeładowania. • W klasie cmpx zdefiniować konstruktor konwertujący.
Konstruktor konwertujący • To taki konstruktor, który jako argument przyjmuje jeden obiekt takiego typu, z którego konwersje przeprowadzamy. • Konwersje mają zachodzić niejawnie dlatego nie może być z przydomkiem explicit. U nas: cmpx(double a):re(a),im(0){} lub jeszcze prościej zmieniamy konstruktor już istniejący tak aby mógł służyć do konewrsji: cmpx(double a, double b=0) {re=a;im=b;}
Kiedy konwersja się przydaje? #include<iostream> usingnamespacestd; classcmpx { friend ostream &operator<<(ostream &, const cmpx &); friendcmpx operator+(constcmpx &,constcmpx &); double re; double im; public: cmpx():re(0),im(0){} cmpx(double a, double b=0) {re=a;im=b;} }; ostream &operator<<(ostream &S, const cmpx &A) { S<<A.re<<showpos<<A.im<<"i"; return S; } cmpxoperator+(const cmpx &A,constcmpx &B) { cmpx C; C.re=A.re+B.re; C.im=A.im+B.im; return C; } intmain() { cmpx A(1,2), B(3,4), C, D; C=A+B; cout<<C<<endl; D=A+1; cout<<D<<endl; return 0; } 4+6i 2+2i
Konwersja z jednej klasy na drugą class kwadrat { friendclassprostokat; double a; public: kwadrat():a(0){} kwadrat(double bok):a(bok){} }; classprostokat { friend double pole(constprostokat &); double a, b; public: prostokat():a(0),b(0){} prostokat(double bok1, double bok2) {a=bok1;b=bok2;} prostokat(const kwadrat &A):a(A.a),b(A.a) {cout<<"Konwersja\n";} }; double pole(constprostokat &A) { return A.a*A.b; } intmain() { prostokat A(3,4); kwadrat B(2); cout << pole(A)<<endl; cout << pole(B)<<endl; return 0; } 12 Konwersja 4
Funkcja konwertująca • Konstruktor konwertujący nie nadaje się do dokonywania konwersji na typ wbudowany (nie możemy zdefiniować konstruktora konwertującego dla klasy np. double). • Funkcja konwertująca może wykonać przekształcenie z typu zdefiniowanego przez użytkownika na dowolny inny.
Funkcja konwertująca Funkcja konwertująca z typu K na typ T to funkcja składowa klasy K o nazwie: K::operator T() Jej cechy: • Jest funkcją składową klasy -> jest w niej znany wskaźnik this. • Nie ma określonego typu, który zwraca (musi to być typ T). • Ma pustą listę argumentów -> nie można jej przeładować. • Jest dziedziczona. • Może być funkcją wirtualną.
Funkcja konwertująca class kwadrat { friend double pole(const kwadrat &); double a; public: kwadrat():a(0){} kwadrat(double bok):a(bok){} }; classprostokat { double a, b; public: prostokat():a(0),b(0){} prostokat(double bok1, double bok2) {a=bok1;b=bok2;} operator kwadrat() {cout<<"Konwersja\n"; return a;} }; double pole(const kwadrat &A) { return A.a*A.a; } intmain() { prostokat A(3,4); kwadrat B(2); cout << pole(A)<<endl; cout << pole(B)<<endl; return 0; } Konwersja 9 4
Funkcja konwertująca class kwadrat { friend double pole(const kwadrat &); double a; public: kwadrat():a(0){} kwadrat(double bok):a(bok){} }; classprostokat { double a, b; public: prostokat():a(0),b(0){} prostokat(double bok1, double bok2) {a=bok1;b=bok2;} operator kwadrat() {cout<<"Konwersja\n"; return kwadrat(a);} }; double pole(const kwadrat &A) { return A.a*A.a; } intmain() { prostokat A(3,4); kwadrat B(2); cout << pole(A)<<endl; cout << pole(B)<<endl; return 0; }
Funkcja konwertująca K::operator T() – zmiana K->T Typ T nie może być: • Tym samym typem co K. • Typem &K. • Typem void. • Typem jakiejś funkcji. • Typem reprezentującym tablicę • Typem klasy podstawowej dla K.
Konstruktor konwertujący vs funkcja konwertująca • Nie można zdefiniować konstruktora dla typu wbudowanego. • Konstruktor musi mieć dostęp do zmiennych klasy z której konwertujemy. • Konstruktora się nie dziedziczy. Wniosek: Wybieramy funkcję konwertującą!
Kiedy zachodzi konwersja • Przy wywoływaniu funkcji. • Przy zwracaniu wartości przez funkcję • W obecności operatorów. • W instrukcjach sterujących (if, switch, while, for). • Przy inicjalizacji obiektów.
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, konwersję KL->intoraz dwie funkcję: • voidfun(KL); • voidfun(int); Wywołujemy: fun(OB); Wywołana zostanie funkcja nr 1. Nie ma potrzeby wykonywania konwersji więc kompilator jej nie wykona.
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, konwersję KL->intoraz jedną funkcję: • voidfun(double); Wywołujemy: fun(OB); Funkcja zostanie wywołana. Nie mamy wprawdzie zdefiniowanej konwersji KL->double, ale mamy KL->int. Konwersjęint->doublewykona kompilator (konwersja standardowa).
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, konwersję int->KLoraz dwie funkcję: • voidfun(KL); • voidfun(double); Wywołujemy: fun(1); Wywołana zostanie funkcja nr 2. Zawsze kompilator wcześniej próbuje dopasować typy przy pomocy konwersji standardowych. Dlatego wykona konwersję int->double.
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, dwie konwersję KL->intiKL->doubleoraz dwie funkcję: • voidfun(double); • voidfun(int); Wywołujemy: fun(OB); Wystąpi BŁĄD. Kompilator nie wie którą konwersję zastosować.
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, dwie konwersję KL->intiKL->doubleoraz dwie funkcję: • voidfun(double); • voidfun(long); Wywołujemy: fun(OB); Wywołana zostanie funkcja nr 1. Wywołanie funkcji nr 1 wymaga wykonanie konwersji KL->double. Wywołanie funkcji nr 2 wymagałoby wykonania konwersji KL->int->long – dłuższa droga.
Kilka ciekawych sytuacji • Mamy obiekt OB klasy KL, dwie konwersję KL->int(zdefiniowana w części prywatnej)iKL->double(zdefiniowana w części publicznej) oraz dwie funkcję: • voidfun(double); • voidfun(int); Wywołujemy: fun(OB); Wystąpi BŁĄD. Kompilator nie wie którą konwersję zastosować pomimo, że ma dostęp tylko do jednej.
Kilka ciekawych sytuacji • Mamy obiekt OB_Aklasy KL_A, obiekt OB_Bklasy KL_Bidwie konwersję • KL_A->KL_B(konstruktor konwertujący w klasie KL_B) • KL_A->KL_B(funkcja konwertująca w klasie KL_A. Błąd wystąpi w momencie, gdy kompilator będzie miał wykonać konwersję niejawną. Kompilator nie będzie wiedział którą konwersję zastosować.
Kilka ciekawych sytuacji • Mamy obiekt OB_Aklasy KL_A, obiekt OB_Bklasy KL_B, dwie konwersję KL_A->KL_B, KL_B->KL_A oraz dwie funkcję: • voidfun_A(KL_A); • voidfun_B(KL_B); Po wywołanach: fun_A(OB_A) - brak konwersji fun_A(OB_B) - konwersja KL_B->KL_A fun_B(OB_A) - konwersja KL_A->KL_B fun_B(OB_B) - brak konwersji
Dziedziczenie • Dziedziczenie – technika pozwalająca na definiowanie nowej klasy w oparciu o klasę zdefiniowaną wcześniej • Jeżeli w trakcie realizacji programu konieczne jest zbudowanie nowej klasy, która różni się od klasy zdefiniowanej wcześniej kilkoma składowymi lub wymaga określenia tylko kilku nowych składowych, to można do budowy tej klasy wykorzystać klasę zdefiniowaną wcześniej wykorzystując mechanizm dziedziczenia
Dziedziczenie classcmpx //klasa podstawowa { public: double re,im; double modul(); }; class cmpx2: public cmpx //klasa pochodna { public: string nazwa; double modul(); };
Dziedziczenie Klasa cmpx2 wywodzi się z klasy cmpx
Dziedziczenie vs zawieranie Klasa A składa się z klasy B – zawieranie class A { ... class B { ... }; }; Klasa A jest rodzajem klasy B – dziedziczenie class B { ... }; class A: public B { ... };
Dziedziczenie W klasie pochodnej można: • Zdefiniować nowe dane składowe(nadanie klasie nowych cech) • Zdefiniować nowe funkcje składowe(wyposażenie klasy w nowe zachowania) • Zdefiniować składnik, najczęściej funkcję, już istniejący w klasie podstawowej(możliwość korekty lub zmiany już zdefiniowanych zachowań w klasie podstawowej)
Lista pochodzenia class cmpx2: public cmpx • Lista pochodzenia jest informacją, od czego pochodzi klasa pochodna. • Definicja klasy na liście pochodzenia musi być znana (nie wystarczy deklaracja zapowiadająca). • Lista pochodzenia definiowana jest po nazwie klasy pochodnej i dwukropku : • Klasa pochodna dziedziczy wszystkie składniki klasy podstawowej. Do składników z klasy podstawowej można odnosić się tak, jakby zdefiniowane były w klasie pochodnej.
Zagnieżdżanie zakresów • Składniki odziedziczone mają zakres klasy podstawowej, na to nakłada się zakres klasy pochodnej • Składniki z klasy pochodnej zasłaniają składniki odziedziczone { //zakres klasy podstawowej double re,im; double modul(); { //zakres klasy pochodnej double modul(); } }
Zagnieżdżanie zakresów • Odniesienie się do zasłoniętych składników klasy podstawowej wymaga użycia operatora zakresu :: cmpx2 liczba; double Z; Z=liczba.modul(); //wywołanie //funkcji z //klasy cmpx2 Z=liczba.cmpx::modul(); //wywołanie //funkcji z //klasy cmpx
Kolejności wywoływania konstruktorów Najpierw pracują konstruktory klas podstawowy, potem konstruktory klas składowych. Dla hierarchii przedstawionej po prawej kolejność ta będzie następująca: • B • A • D • C • E
Czego się nie dziedziczy? „Nie dziedziczy się”: • konstruktorów, • destruktorów, • operatora przypisania „Nie dziedziczy się” – składniki te są dostępne w klasie pochodnej i można z nich korzystać (jeżeli są nie prywatne), ale konstruktor, … klasy podstawowej nie staje się konstruktorem, … klasy pochodnej.
Operator= w warunkach dziedziczenia Operator przypisania nie jest generowany automatycznie gdy: • klasa podstawowa ma zdefiniowany operator przypisania jako prywatny, • klasa składowa ma zdefiniowany operator przypisania jako prywatny, • klasa ma składnik const lub będący referencją.
Konstruktor kopiujący w warunkach dziedziczenia Konstruktor kopiujący nie jest generowany automatycznie gdy: • klasa podstawowa ma zdefiniowany konstruktor kopiujący jako prywatny, • klasa składowa ma zdefiniowany konstruktor kopiujący jako prywatny,
Inicjalizacja i przypisanie według stałego obiektu Automatycznie generowany konstruktor kopiujący (operator=) zapewni nietykalność obiektowi wzorcowemu jeżeli: • konstruktory kopiujące (operatory=) we wszystkich klasach podstawowych zapewniają nietykalność, • konstruktory kopiujące (operatory=) we wszystkich klasach składowych zapewniają nietykalność.
Definiowanie operatora przypisania i konstruktora kopiującego dla klasy pochodnej classcmpx { public: double re,im; cmpx(double a=0,double b=0); cmpx(constcmpx &A); cmpx &operator=(constcmpx &A); double modul(); }; class cmpx2: public cmpx { public: string nazwa; cmpx2(double a=0,double b=0,string n="NN"); cmpx2(const cmpx2 &A); cmpx2 &operator=(const cmpx2 &A); };
Definiowanie operatora przypisania dla klasy pochodnej cmpx2 &cmpx2::operator=(const cmpx2 &A) { (*this).cmpx::operator=(A); nazwa=A.nazwa; return *this; } wywołanie operatora przypisania dla części odziedziczonej przypisanie części nie odziedziczonej
Definiowanie konstruktora kopiującego dla klasy pochodnej cmpx2::cmpx2(const cmpx2 &A):cmpx(A) { nazwa=A.nazwa; } wywołanie konstruktora kopiującego dla części odziedziczonej przypisanie części nie odziedziczonej
Dziedziczenie wielokrotne Dziedziczenie od kilku klas podstawowych. class A {...}; class B {...}; class C: public A, public B {...}; Dana klasa podstawowa może pojawić się liście pochodzenia tylko raz.
Dziedziczenie wielokrotne – kolejność wywoływania konstruktorów class A {A(int i);}; class B {B(int i);}; class C: public A, public B {C(int i);}; C::C(int i):A(i),B(i){...} Kolejność jest zgodna z kolejnością na liście inicjalizacyjnej (czyli: 1.A, 2.B, 3.C).
Wieloznaczność Wieloznaczność to sytuacja, gdy wyrażenie odnoszące się do składnika klasy podstawowej, równie dobrze może odnosić się do innego składnika drugiej klasy podstawowej, np. C.x – BŁĄD C.A::x – OK C.B::x – OK
Wieloznaczność Bliższe pokrewieństwo nie usuwa wieloznaczności. D.x – BŁĄD D::A.x – OK D::C.x – OK D::B.x – OK – wystarczy wskazać gałąź
Wieloznaczność Najpierw sprawdzana jest jednoznaczność, a dopiero potem ewentualny dostęp. Dlatego, odniesienie się do składnika x wywoła błąd. C.x – BŁĄD
Konwersje standardowe przy dziedziczeniu Jeśli klasa podstawowa jest dziedziczona publicznie, to: • Wskaźnik do obiektu klasy pochodnej może być niejawnie przekształcony na wskaźnik dostępnej jednoznacznie klasy podstawowej • Referencja do obiektu klasy pochodnej może być niejawnie przekształcona na referencje dostępnej jednoznacznie klasy podstawowej
Konwersje standardowe przy dziedziczeniu class A { public: int liczba; }; class B: public A { ... }; voidfun(A &ob) { ob.liczba=100; } void fun1(A *wsk) { wsk->liczba=0; } intmain() { A a; B b; fun(a); fun(b); //niejawna konwersja fun1(&a); fun1(&b); //niejawana konwersja return 0; }
Konwersje standardowe przy dziedziczeniu Niejawne konwersje standardowe wskaźnika lub referencji zachodzą w przypadku: • przesyłania argumentów do funkcji • zwracania przez funkcję wyniku będącego wskaźnikiem lub referencją • wyrażeń inicjalizujących Niemożliwe są konwersje: • kl_pochodna** -> kl_podstawowa** • *kl_pochodna[] -> *kl_podstawowa[]
Dziedziczenie wirtualne Dana jest hierarchia: W obrębie klasy D występują dwukrotnie składniki odziedziczone od klasy A.
Dziedziczenie wirtualne Stosując dziedziczenie wirtualne otrzymujemy hierarchię: Co zyskujemy? • W obrębie klasy D składniki odziedziczone od klasy A występują jednokrotnie – klasa D zajmuje mniej miejsca. • Nie występuje wieloznaczność.
Dziedziczenie wirtualne class A {...}; class B: public virtual A {...}; class C: public virtualA {...}; class D: public B,public C {...}; Na liście inicjalizacyjnej klasy D słówko virtualjuż nie występuje