Wykład 4
Transkrypt
Wykład 4
PROE – wykład 4 pozostałe operatory, forward declaration, dziedziczenie dr inż. Jacek Naruniec Dyrektywy preprocesora Preprocesor przetwarza plik przed kompilacją. Podmienia/interpretuje dyrektywy/bloki poprzedzone znakiem #. #define ZMIENNA #ifdef ZMIENNA Cośtam cośtam … ten kod preprocesor włączy w kod #else Coś innego coś innego …tego kodu preprocesor nie włączy w kod #endif wynik działania preprocesora Cośtam cośtam … ten kod preprocesor włączy w kod Dyrektywy preprocesora Stała _DEBUG jest automatycznie definiowana przy wyborze trybu kompilacji „DEBUG” w Visual Studio. W większości środowisk definicje preprocesora globalne dla całego projektu wpisujemy w opcjach projektu pod pojęciem „preprocesor definitions” lub podobnym. Terminy kolokwium 7 kwietnia – I kolokwium ( w przyszłym tygodniu dokończenie materiału i powtórka przed kolokwium) 2 czerwca – II kolokwium 9 czerwca – kolokwium poprawkowe Operatory post i preinkrementacji. Jak rozróżnić operatory obiekt++ i ++obiekt? Operatory w klasie i poza klasą Operatory jako metody w klasie: Realizuje operacje (Pulsometr p1, p2): p1 == p2 p1 == 2 ale nie 2==p1 Pulsometr.h Pulsometr.cpp Operatory w klasie i poza klasą Operatory zadeklarowane poza klasą (nie ma dostępuj do this): Realizuje operacje (Pulsometr p1, p2): p1 == p2 p1 == 2 2==p1 Forward declaration Co będzie jeśli klasa Punkt będzie korzystała z klasy Wektor a klasa Wektor z klasy Punkt? Forward declaration Jeśli w pliku .cpp zostanie dołączony Wektor.h, to w drugiej linijce (po #pragma once) mamy: include „Punkt.h” -> Punkt.h będzie miał w drugiej linijce #include „Wektor.h” Kompilator nie wejdzie w Wektor.h, bo na początku jest #pragma once które wymusza fakt, że nie dołączymy tego pliku 2 razy. W rezultacie nagłówek ten będzie pominięty, powrócimy do dalszej kompilacji Punkt.h W przesunOWektor korzysta z klasy Wektor która jeszcze nigdzie nie została zadeklarowana i zgłasza błąd. Forward declaration W pliku Wektor.cpp mamy: #include „Wektor.h” Kompilujemy plik Wektor.cpp W bloczkach kolejność, w której będzie zaglądał do plików kompilator: 1 3 4 6 7 8 – błąd, bo nigdy nie doszliśmy do definicji Wektor 5 – dalej nie pójdzie, bo pragma 2 once! Forward declaration Zamiast dołączać Wektor.h i Punkt.h powiemy tylko kompilatorowi, że będzie coś takiego jak Punkt i jak Wektor (bez żadnych szczegółów): Forward declaration Dziedziczenie Jedna z najważniejszych cech programowania obiektowego! Jest to uszczegółowienie klasy. Uszczegóławiając klasę dodajemy jedynie te elementy, które pojawiają się w nowym obiekcie. Dziedziczenie Klasa OknoProgramu (różnice także w uruchomieniu – albo nieblokujące albo blokujące) Okno programu Okno dialogowe Okno edycyjne Rozszerzone okno dialogowe Okno hasła Dziedziczenie Okno dialogowe nie jest częścią okna programu – jest jego uszczegółowieniem Podobnie okno hasła nie jest częścią okna edycyjnego Itd… Dziedziczenie Okno programu stanowi „bazę” dla pozostałych okien (w rzeczywistości będzie zawierać więcej elementów) Te metody będą dotyczyły każdego okna To są zmienne, które definiują okno każdego typu (także dialogowe, hasła, edycyjne) Dziedziczenie Okno dialogowe rozszerza działanie okna o przyciski (powinna być jeszcze zawartość itp.): To znaczy, że dziedziczy po klasie OknoProgramu Identycznie zdefiniowana metoda jak w OknoProgramu Dziedziczenie Rozszerzone okno dialogowe rozszerza okno dialogowe o możliwość ustawienia koloru tła. Nie ma metody pokazOkno() ! – ale będzie ona dostępna dla tej klasy (z klasy bazowej) Dziedziczenie Okno edycyjne rozszerza działanie OknaProgramu o pole edycyjne (ale nie zawiera żadnych przycisków) Dziedziczenie OknoHasla jest uszczegółowieniem okna edycyjnego – kropki zamiast liter i dodatkowy przycisk OK. Kod aby hasło nie występowało jawnie w pamięci. Dziedziczenie Klasy asortymentu sklepu internetowego: Towar Komputer LCD Telewizor Plazma Komórkowy Telefon Stacjonarny Dziedziczenie Klasa Detektor Detektor Detektor ruchu Detektor twarzy Detektor twarzy Haar Detektor twarzy LBP Dziedziczenie Obiekt Detektor (bardzo ogólny) ? Dziedziczenie Detektor ruchu dziedziczy po detektorze i daje wyniki w postaci maski pikseli które się poruszyły obraz maska ruchu Dziedziczenie Detektor twarzy dziedziczy po detektorze i daje wyniki w postaci tablicy obiektów typu Twarz. Nie implementuje żadnej konkretnej metody, jest ciągle ogólny, ale wiemy jaki wynik chcemy otrzymać. wykryte twarze obraz Dziedziczenie DetektorTwarzyHaar rozszerza działanie detektora twarzy, implementując konkretną metodę detekcji (opartą na cechach Haara) Dziedziczenie DetektorTwarzyLBP rozszerza działanie detektora twarzy, implementując konkretną metodę detekcji (cechy Local Binary Patterns - LBP) Dziedziczenie Implementacja klasy Detektor: Dziedziczenie Najprostsze wykorzystanie obiektu bazowego: Po co nam taki obiekt?? – przecież on w zasadzie nic nie robi i nie ma żadnej sensownej funkcjonalności. My przecież chcemy mieć narzędzie do detekcji Dziedziczenie Implementacja klasy DetektorTwarzy, zaczniemy od konstruktora: Te zmienne dotyczą wszystkich detektorów więc chcemy też z nich korzystać! Rozwiązaniem jest trzeci tryb dostępu (przy private i public) – tryb chroniony, czyli protected Dziedziczenie Sekcja protected oznacza, że z metod i zmiennych mogą korzystać klasy pochodne, ale nie można korzystać z nich z zewnątrz: Dziedziczenie Przy dostępie do zmiennych protected: tutaj nie ma problemu Tu zgodnie z założeniem nie można tego zrobić Dziedziczenie Od tego momentu wszystkie składowe klasy (czyli zwykle większość, jeśli nie wszystkie) które chcemy aby były dziedziczone umiejscawiamy w sekcji protected. Tu w sumie zbędne (bo „dalej” nie ma dziedziczenia), ale nie zaszkodzi Dziedziczenie Proste wykorzystanie obiektów: Dziedziczenie Najważniejsza cecha dziedziczenia – polimorfizm. Dziedziczenie Polimorfizm: Słowo virtual określa, że funkcja zachowuje się „inteligentnie”, tzn. wywołuje się funkcja odpowiadająca klasie stworzonego obiektu a nie klasy wskaźnika. Dziedziczenie Polimorfizm Przy virtual (funkcje wirtualne) uruchamiana jest funkcja najbardziej odpowiadająca danej klasie. Jest to jeden z najistotniejszych elementów dziedziczenia. Dziedziczenie A co jeśli w DetektorTwarzyHaar nie ma zdefiniowanej funkcji uruchom? Uruchomi się najbliższa możliwa, czyli: Detektor virtual bool uruchom DetektorTwarzy bool uruchom DetektorTwarzyHaar uruchom() Funkcja wirtualna, szukamy dalej Funkcja wirtualna, szukamy dalej Nie znaleziono, więc wywoła się funkcja z klasy DetektorTwarzy Dziedziczenie Metody i klasy abstrakcyjne. Metoda abstrakcyjna to funkcja, której nazwa i parametry są zadeklarowane a jej definicja (ciało) już nie. Oznacza to, że funkcja jest wirtualna ale nie jest zdefiniowana w klasie Detektor (ale jest zdefiniowana w klasach pochodnych) Dziedziczenie Efekt posiadania funkcji wirtualnej: Klasa która ma metody(choćby jedną) abstrakcyjne staje się klasą abstrakcyjną, której obiektu nie da się utworzyć. Dziedziczenie Mimo, że nie można utworzyć klasy Detektor, to można utworzyć wskaźnik na obiekt klasy Detektor: Dziedziczenie Tak samo nie ma sensu definicja funkcji w klasie DetektorTwarzy: To też będzie metoda i klasa abstrakcyjna. Nie ma słowa virtual, bo wirtualność jest dziedziczona – jeśli funkcja była wirtualna w klasie Detektor to będzie i tutaj. Dziedziczenie Nasz main obecnie: Dziedziczenie Analogicznie można tworzyć wskaźniki na DetektorTwarzy: Dziedziczenie Konstruktory/desktruktory Konstruktory uruchamiają się od klasy bazowej do klas pochodnych, destruktory odwrotnie W rezultacie, w tym przypadku, z trzech konstruktorów otrzymujemy jeden obiekt. Dziedziczenie Każda klasa powinna dbać o własną „czystość pamięci”. Jeśli klasa DetektorTwarzy tworzy jakąś tablicę przez new to i w tej klasie powinno być jej usunięcie (a nie w bazowej ani innej!).: Dziedziczenie Usunięcie obiektu z wykorzystaniem polimorfizmu: Brakuje dwóch destruktorów! Dziedziczenie Aby umożliwić usunięcie obiektu z wykorzystaniem polimorfizmu, destruktor musi być wirtualny: Dziedziczenie Można także bezpośrednio odwoływać się do funkcji klasy bazowej danego obiektu, np.: Dziedziczenie Co jeśli funkcja jest zdefiniowana w klasie pochodnej, nie ma jej w klasie bazowej a my mamy wskaźnik na klasę bazową (uffff. ;-))? Detektor nie ma funkcji zmienParametryHaar! Dziedziczenie Możemy rzutować wskaźnik Detektor na wskaźnik klasy pochodnej, wtedy mamy dostęp do jej funkcji: Dziedziczenie Wirtualne funkcje: Okno programu Okno dialogowe Okno edycyjne Rozszerzone okno dialogowe Okno hasła pokazOkno wirtualny destruktor? Dziedziczenie Okna Windows (np. w MFC): Akcje na oknach wywołują pewne funkcje. Jeśli mamy swoją klasę okna możemy przedefiniować te funkcje (bo są wirtualne). Przykłady funkcji witualnych: OnOk() OnCancel() OnClick(Button &b) OnMove() Dziedziczenie Ćwiczenie: Obiekt bazowy (OB) void funkcja1() virtual void funkcja2() Obiekt pochodny 1 (OP1) virtual void funkcja1() Obiekt pochodny 3 (OP3) void funkcja1() Obiekt pochodny 2 (OP2) void funkcja2() Które funkcje się uruchomią jeśli mamy: - Wskaźnik na klasę OB., - Wskaźnik na klasę OP1, OP2, OP3 - Obiekt klasy OB - Obiekt klasy OP1 - Obiekt klasy OP3 Za każdym razem wywołujemy funkcja1 i funkcja 2 Dziedziczenie Które konstruktory/destruktory i w jakiej kolejności się uruchomią? Obiekt bazowy 1(OB1) Obiekt pochodny 1 (OP1) Obiekt bazowy 2(OB2) Obiekt pochodny 2 (OP2) Dziedziczenie Możliwe jest także dziedziczenie wielokrotne (np. Laptop dziedziczy po komputerze i urządzeniu). Najważniejsze w dziedziczeniu jest: Polimorfizm Możliwość rozszerzania klas bazowych – oszczędność kodu Paradygmaty programowania obiektowego Paradygmat (za Wikipedią) - zbiór pojęć i teorii tworzących podstawy danej nauki. Abstrakcja – dysponujemy abstrakcyjnymi obiektami, które mają określone działanie – nie musimy wnikać w sposób tego działania (tylko w część publiczną) Hermetyzacja (inaczej enkapsulacja) – chronimy obiekt aby nie został zmieniony w sposób nieprzewidziany (sekcje prywatne, chronione) Polimorfizm – zachowanie odpowiednie dla typu stworzonego obiektu, wielopostaciowość – przeciążanie funkcji Dziedziczenie – możliwość rozszerzania istniejących obiektów Paradygmaty programowania obiektowego Większość języków obiektowych je spełnia, ale nie wszystkie, np. Python: nie ma sekcji prywatnych, publicznych zakłada, że użytkownik wie co robi i nie będzie psuł nieprzewidzianych do modyfikacji elementów