200 likes | 336 Views
Programowanie obiektowe III rok EiT. dr inż. Jerzy Kotowski Wykład XIII. Program wykładu. Język C++ Dziedziczenie Przykład problemu Podstawy języka JAVA Klasówka. Literatura. C++ for C programmers , Ira Pohl , The Benjamin/Cummings Publishing Company, Inc.
E N D
Programowanie obiektoweIII rok EiT dr inż. Jerzy Kotowski Wykład XIII
Program wykładu • Język C++ • Dziedziczenie • Przykład problemu • Podstawy języka JAVA • Klasówka
Literatura • C++ for C programmers, Ira Pohl, The Benjamin/Cummings Publishing Company, Inc. • Ćwiczenia z języka C++,Claude Delannoy • Symfonia C++, Jerzy Grębosz, Oficyna Kallimach, Kraków 1999
Dziedziczenie – terminologia i podstawy • Inherit -dziedziczyć • Inheritance –dziedziczenie • Derive – pochodzić • Deriving– pochodzący • Derived– pochodny • A derivedclass– klasa pochodna • Virtual- czynny, prawdziwy, faktyczny • Pojęcie dziedziczenia należy do podstaw programowania obiektowego. • Dziedziczenie jest mechanizmem wyprowadzania nowej klasy ze starej. • Dziedziczenie umożliwia zdefiniowanie nowej klasy B, nazywanej pochodną, na podstawie już istniejącej klasy A, nazywanej bazową. • Wiele struktur to warianty jednej podstawowej struktury i często jest nudnym produkowanie tego samego kodu dla każdej klasy pochodnej z osobna.
Dziedziczenie – podstawowe idee • Klasa pochodna dziedziczy opis podstawowej klasy. Może on być następnie zmieniony przez dodawanie nowych składników, polimorfizm istniejących już member functions i modyfikowanie przywilejów związanych z regułami dostępu. • Idea: Klasyfikacja bazowa z reguły obejmuje rozległe obszary wiedzy. Przykład: znając definicję ssaka i wiedząc, że zarówno mysz jak i słoń to ssaki można stworzyć ich opis w sposób dużo bardziej zwięzły niż przy wykorzystywaniu innych technik. • Virtual member functions – funkcje wirtualne. • Są to funkcje deklarowane w klasie bazowej i overloadedw klasach pochodnych. • Hierarchia ADT, definiowana przez proces dziedziczenia tworzy relację w zbiorze typów zdefiniowanych przez użytkownika. • Obiekty typów pochodnych mogą być wskazywane przez wskaźnik do klasy bazowej. Pozwala to na dynamiczne rozróżnianie typów. Jest to typowa cecha OOP. • Czyste funkcje wirtualne klasy abstrakcyjne.
Klasa pochodna • Klasa może pochodzić (can be derived) od klasy istniejącej przy pomocy konstrukcji:class class_name: (public|private) base_class {…} • public|privatesą opcjonalne. • Jak zwykle słowo kluczowe class może być zastąpione przez słowo kluczowe struct. Wtedy wszystkie składniki klasy są przez domniemaniepublic. • Najbardziej skomplikowanym aspektem klas pochodnych jest widzialność ich dziedziczonych składników. • Jedno z trzech słów kluczowych public, private, protected może być opcjonalnie wykorzystane do wyspecyfikowania jak składniki klasy bazowej mają być dostępne w klasie pochodnej. • Słowo kluczowe publicpo dwukropku w nagłówku klasy pochodnej oznacza, że wszystkie publicmembersklasy bazowej są publicmembersklasy pochodnej. • Klasa pochodna może być restrykcyjna. Może zmniejszać widzialność swoich składników i/lub zmieniać ich znaczenie.
Modyfikatory widzialności • Słowa kluczowe public, private i protectedmogą być wykorzystane jako modyfikatory widzialności składników klasy. • A public memberjest widzialny w obszarze dostępności całego obiektu. • A private memberjest widzialny przez wszystkie funkcje składowewewnątrz swojej klasy. • A protected memberjest widzialny przez wszystkie funkcje składowe wewnątrz swojej klasy oraz przez funkcje składoweklasy bezpośrednio dziedziczącej. • Modyfikatory widzialności mogą być używane wewnątrz klasy w dowolnej kolejności i dowolną liczbę razy. • Modyfikatory widzialności mogą być wykorzystywane w nagłówku klasy pochodnej do ograniczenia widzialności elementów klasy bazowej. • W razie potrzeby, dla wybranych elementów klasy bazowej rodzaj widzialności można zmieniać indywidualnie wewnątrz klasy pochodnej.
Dostęp do składowych klasy bazowej • W przypadku dziedziczenia publicznego wszystkie składowe publiczne klasy bazowej stają się składowymi publicznymi klasy pochodnej. • W przypadku dziedziczenia prywatnego, składowe publiczne klasy bazowej stają się składowymi prywatnymi klasy pochodnej. Użytkownik klasy pochodnej nie ma dostępu do składowych prywatnych klasy bazowej. • W obu typach dziedziczenia składowe prywatne klasy bazowej nie są udostępniane funkcjom składowym klasy pochodnej. • Jest to OCZYWISTE: Mechanizm dziedziczenia nie może łamać reguł dostępu wprowadzonych przez twórcę klasy bazowej.
Dostęp do składowych klasy bazowej, c.d. • Dziedziczenie prywatne jest stosunkowo rzadkie i używane w szczególnych przypadkach (całkowita zmiana interfejsu klasy). • Składowe klasy bazowej mogą być chronione (protected). W tym przypadku są one uważane za: • publiczne dla funkcji składowych klasy pochodnej, • prywatne dla użytkownika klasy pochodnej. • Nawet jeżeli jakaś składowa z klasy bazowej (dana lub funkcja) została przedefiniowana w klasie pochodnej, to funkcje składowe i użytkownik klasy pochodnej mogą w dalszym ciągu z niej korzystać jeżeli nie jest to zabronione. • Dostęp do przedefiniowanej składowej umożliwia operator zakresu ::
Wywołanie konstruktorów i destruktorów • Niech B będzie klasą pochodną do klasy bazowej A. • Jeżeli B ma jakiś konstruktor, to utworzenie obiektu typu B powoduje zawsze niejawne wywołanie konstruktora. • W konstruktorze B powinno się przewidzieć argumenty dla konstruktora A, chyba że A nie ma konstruktora lub ma konstruktor bezargumentowy. • Argumenty dla konstruktora A są podawane jak w przykładzie:B(int x, int y, char kol): A(x,y); • Argumenty dla A mogą mieć postać wyrażeń. • Konstruktory i destruktory nie są dziedziczone. • Nie jest również dziedziczony operator przypisania (podstawiania) nawet jeżeli został przeciążony w klasie bazowej. • Uzasadnienie: Jeżeli dokonano takiego przeciążenia to znaczy, że czynność ta jest nietypowa i należy traktować ją ze szczególną uwagą również w klasie pochodnej.
Przypadek konstruktora kopiującego • Jeżeli klasa B nie ma konstruktora kopiującego to jest wywoływany jej automatyczny konstruktor kopiujący. Działa on następująco: • wywołuje konstruktor kopiujący z A (automatyczny lub kopiujący) • inicjalizuje te dane składowe B, które nie pochodzą z A. • Jeżeli w klasie pochodnej został jawnie zdefiniowany konstruktor kopiujący to jego wywołanie spowoduje: • wywołanie konstruktora klasy bazowej wymienionej w nagłówku jak w przykładzie: B(B& b): A(b);wywołanie konstruktora kopiującego z A, do którego zostanie przekazana część B odziedziczona a A. • wywołanie konstruktora bezargumentowego (domniemanego), jeżeli w nagłówku nie został wymieniony żaden konstruktor. Jeżeli klasa bazowa nie ma konstruktora bezargumentowego to wystąpią błędy kompilacji.
Zgodność obiektu klasy bazowej i obiektu klasy pochodnej • Niech B będzie klasą pochodną do klasy bazowej A. A a;obiekt typu bazowego B b;obiekt typu pochodnego A *wska;wskaźnik obiektu typu bazowego B *wskb;wskaźnik obiektu typu pochodnego • W języku C++ mają miejsce dwie poniższe niejawne konwersje: • Przypisanie a=b; jest legalne i polega na przekształceniu b do typu A, czyli rozważeniu w A tylko składowych należących także do A i przypisaniu wyniku do a. Jest przy tym wywoływany operator przypisania z A, przeciążony lub automatyczny. Przypisanie odwrotne, czyli b=a; jest zabronione. • Przypisanie wskaźnika do klasy pochodnej we wskaźnik do klasy bazowej. Można napisać przykładowo: wska=wskb;natomiast napisanie bezpośrednio wskb=wska; jest zabronione. Ewentualnie można użyć rzutu: wskb=(B *)wska;
Dziedziczenie wielobazowe (wielokrotne) • Dziedziczenie wielobazowe umożliwia dziedziczenie po kilku klasach jak w przykładzie:class B: public A1, public A2 • Każde dziedziczenie może być publiczne lub prywatne. Wykorzystanie składowych każdej z klas bazowych jest podobne jak w przypadku zwykłego dziedziczenia. • W przypadku napisu:class B: public A1, A2dziedziczenie po klasie A2 będzie prywatne przez domniemanie. • Operator zakresu :: może być używany: • jeżeli chce się użyć składowej klasy bazowej, przedefiniowanej w klasie pochodnej, albo • gdy dwie klasy bazowe mają składową o tej samej nazwie i trzeba sprecyzować o którą chodzi.
Wywołania konstruktorów i destruktorów • Utworzenie obiektu pociąga za sobą wywołania konstruktorów klas bazowych w takiej kolejności w jakiej są wymienione w deklaracji klasy pochodnej (od lewej do prawej) • W nagłówku konstruktora klasy pochodnej można wymienić informacje do przekazania każdemu z konstruktorów klas bazowych. Przykład:B(typ1 a1, typ2 a2, typ3 a3): A1(a1,a2), A2(a3) {} • Nie jest to obowiązkowe, gdy klasa bazowa ma konstruktor bezargumentowy (domniemany) lub gdy nie ma konstruktora. • Przypadek ogólny: klasa pochodna ma składowe, które same w sobie są obiektami innych klas, które też mogą być klasami pochodnymi. • Problem kolejności wywołania konstruktorów (J. Grębosz): Klasa uszanuje najpierw starszych, potem swoich gości, a dopiero na samym końcu zajmie się sobą. • Jeżeli dziadek miał swojego gościa, to ta sprawa zostanie załatwiona już przy konstrukcji dziadka (J. Grębosz).
Przykład – klasa punkt_kolorowy1 .. \test0.sln • punkt_kolorowy1.cpp • Idea: tworzymy klasę pochodną ze znanej od dawna klasy punkt dodając nową składową o nazwie kolor. Klasa punkt jest znana. • Interesujące elementy programu: • punkt_kolorowy(int=0, int=0, int=0); • Definicja konstruktora klasy pochodnejpunkt_kolorowy::punkt_kolorowy(int xx, int yy, int kolor): punkt(xx,yy) { kol=kolor; } • Alternatywa:punkt_kolorowy::punkt_kolorowy(int xx, int yy, int kolor): punkt(xx,yy), kol(kolor){} • Wywołanie funkcji z klasy bazowej • Niebezpieczeństwo nieskończonej rekursji • Wywołania funkcji operator !() • Niejawne wywołanie destruktora klasy bazowej
Klasy wirtualne • Przy kilkustopniowym dziedziczeniu może zajść sytuacja, w której dana klasa będzie dziedziczyć dwukrotnie po innej klasie. Przykład: class B: public Aclass C: public Aclass D: public B, public C • Składowe klasy A pojawiają się dwukrotnie w klasie D. Aby tego uniknąć trzeba w deklaracjach pochodnych klas B i C zadeklarować z atrybutem virtual klasę, której powielania chce się uniknąć. • Słowo virtual może być umieszczone zarówno po jak i przed modyfikatorem widzialności:class B: virtual public Aclass C: public virtual A • Konstruktory klas pochodnych muszą przekazywać argumenty konstruktorowi klasy wirtualnej w sposób jawny:D(arg dla D):B(arg dla B),C(arg dla C),A(arg dla A) • W konstruktorach B i C, w których zadeklarowano klasę wirtualną nie trzeba umieszczać wywołania konstruktora klasy wirtualnej A. • Konstruktor klasy wirtualnej jest zawsze wywoływany na początku.
Przykład – amfibia.cpp.. \test0.sln • Idea: Klasa samochod i klasa boat dziedziczą po klasie pojazd. Klasa amfibia dziedziczy po obu klasach samochod i boat. • Klasa pojazd przechowuje nazwę typu pojazdu. Dlatego ma konstruktor z dynamiczną rezerwacją pamięci. Destruktor, nie powinien być dwa razy wywoływany. • Wymaga to odpowiedniej precyzji: • Klasy samochod i boat mają po dwa konstruktory – jeden dla tworzenia obiektu swojego typu a drugi na potrzeby dziedziczenia wielokrotnego. • Klasa pojazd ma konstruktor z domniemanymi argumentami – aby oszukać kompilator gdy analizuje konstruktor z mniejszą liczbą argumentów w klasie samochod i boat. • Funkcja nazwa w klasie pojazd pozwala na ominięcie ograniczeń na dostęp do pola s w klasach pochodnych. • Wywołania destruktorów w kodzie klienta.
Funkcje wirtualne – sformułowanie problemu • Klasyka: Niezależnie od tego na co wskazuje wskaźnik to jest to zawsze wskaźnik do typu wynikającego z deklaracji wskaźnika. • Dlatego wska->fun(); jest zawsze wywołaniem składowej z klasy bazowej. • Statyczne rozpoznawanie typu – kompilator • Dynamiczne rozpoznawanie typu – program • Język C++ pozwala na dynamiczne rozpoznawanie typu z powodu omówionej wcześniej zgodności klasy bazowej z klasą pochodną. • Jeżeli w klasie bazowej zadeklarujemy funkcję wirtualną przy pomocy słowa kluczowego virtual, to wywołania tej funkcji i funkcji w klasach pochodnych będą wybierane wyłącznie w zależności od rzeczywistego typu obiektu. • Nazywa się to dynamicznym nadawaniem typu lub dynamicznym wiązaniem funkcji. class A { .... public: void fun(...); ..... } a; class B: public A { ..... public: void fun(....); ...... } b; ..... A *wska = &a; B *wskb = &b; wska = wskb;
Funkcje wirtualne – reguły stosowania • Słowo kluczowe virtual jest używane tylko raz dla danej funkcji i nie powinno być używane dla funkcji przedefiniowywanych w klasach pochodnych. • Metoda zadeklarowana w klasie bazowej jako wirtualna nie musi być przedefiniowywana w klasach pochodnych. • Funkcja wirtualna może być przeciążona. • Każda funkcja przeciążona może być wirtualna ale nie musi. • Konstruktor nie może być wirtualny a destruktor może. • Wiązanie dynamiczne jest z oczywistych względów wykorzystywane tylko przy hierarchii klas (dziedziczeniu).
Przykład – klasa punkt_kolorowy2.cpp .. \test0.sln • Tworzymy klasę pochodną z dwóch klas bazowych: klasy punkt oraz klasy o nazwie kolor. • Klasa bazowa punkt posiada funkcję wirtualną kto. Funkcja ta jest przeciążana w klasie pochodnej punkt_kol. • W klasie bazowej funkcja ktowyprowadza tekst: Jestem punktem. • W klasie pochodnej funkcja ktowyprowadza wpierw tekst: Jestem kolorowym punktem w kolorze:. • W następnej linii jest wyprowadzany kolor punktu. Operator zakresu nie jest obligatoryjny! • Funkcja kto jest wykorzystywana przez funkcję składową printklasy bazowej punkt. • Zgodnie z metodyką funkcji wirtualnych rodzaj użytej metody zależy od typu argumentu. Oznacza to, że efekt działania linii p.print(); zależy od typu p, to znaczy od tego, czy p jest obiektem typu bazowego punkt czy też typu pochodnego punkt_kol. • Dynamiczne rozpoznawanie typu widać najlepiej w trzech ostatnich liniach segmentu main.