PROE wykład 4 pozostałe operatory, forward declaration, dziedziczenie dr inż. Jacek Naruniec
Kolokwium wykładowe Pierwsze kolokwium 6 kwietnia Drugie kolokwium 1 czerwca Kolokwium poprawkowe 8 czerwca
Operacje na plikach Pliki traktujemy tak samo jak strumienie cout, cin. Tworzymy obiekt strumienia plikowego wejściowego (ofstream), wyjściowego (ifstream) lub ogólnego (fstream).
Operacje na plikach
Przestrzenie nazw Używane zwykle aby: Uniknąć konfliktu nazw w różnych bibliotekach. Zwiększyć przejrzystość kodu.
Przestrzenie nazw Jeśli takie same przestrzenie istnieją w różnych miejscach kodu łączą się
Przestrzenie nazw
Przestrzenie nazw Jeśli wpiszemy: Oznacza to, że bez podania nazwy przestrzeni domyślnie korzystamy z PrzestrzenGienka, czyli:
Przestrzenie nazw Stąd nasze using namespace std; sprawia, że korzystamy z przestrzeni nazw std domyślnie. Nie musimy wtedy pisać std::cout tylko po prostu cout Tak samo string i std::string i inne Można mieć włączone wiele domyślnych przestrzeni nazw, ale wtedy nazwy elementów nie mogą się pokrywać, czyli:
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: 3 4 1 2 5 dalej nie pójdzie, bo pragma once! 6 7 8 błąd, bo nigdy nie doszliśmy do definicji Wektor
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
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.
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.
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
Okno dialogowe nie jest częścią okna programu jest jego uszczegółowieniem Podobnie okno hasła nie jest częścią okna edycyjnego Itd
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)
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
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)
Okno edycyjne rozszerza działanie OknaProgramu o pole edycyjne (ale nie zawiera żadnych przycisków)
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.
Klasy asortymentu sklepu internetowego: Towar Komputer Telewizor Telefon LCD Plazma Komórkowy Stacjonarny
Klasa Detektor Detektor Detektor ruchu Detektor twarzy Detektor twarzy Haar Detektor twarzy LBP
Obiekt Detektor (bardzo ogólny)?
Detektor ruchu dziedziczy po detektorze i daje wyniki w postaci maski pikseli które się poruszyły obraz maska ruchu
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
DetektorTwarzyHaar rozszerza działanie detektora twarzy, implementując konkretną metodę detekcji (opartą na cechach Haara)
DetektorTwarzyLBP rozszerza działanie detektora twarzy, implementując konkretną metodę detekcji (cechy Local Binary Patterns - LBP)
Implementacja klasy Detektor:
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
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
Sekcja protected oznacza, że z metod i zmiennych mogą korzystać klasy pochodne, ale nie można korzystać z nich z zewnątrz:
Przy dostępie do zmiennych protected: tutaj nie ma problemu Tu zgodnie z założeniem nie można tego zrobić
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
Proste wykorzystanie obiektów:
Najważniejsza cecha dziedziczenia polimorfizm.
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.
Polimorfizm Przy virtual (funkcje wirtualne) uruchamiana jest funkcja najbardziej odpowiadająca danej klasie. Jest to jeden z najistotniejszych elementów dziedziczenia.
A co jeśli w DetektorTwarzyHaar nie ma zdefiniowanej funkcji uruchom? Uruchomi się najbliższa możliwa, czyli: Detektor virtual bool uruchom uruchom() Funkcja wirtualna, szukamy dalej DetektorTwarzy bool uruchom Funkcja wirtualna, szukamy dalej DetektorTwarzyHaar Nie znaleziono, więc wywoła się funkcja z klasy DetektorTwarzy
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)
Efekt posiadania funkcji wirtualnej: Klasa która ma metody(choćby jedną) abstrakcyjne staje się klasą abstrakcyjną, której obiektu nie da się utworzyć.
Mimo, że nie można utworzyć klasy Detektor, to można utworzyć wskaźnik na obiekt klasy Detektor:
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.
Nasz main obecnie:
Analogicznie można tworzyć wskaźniki na DetektorTwarzy:
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.
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!).:
Usunięcie obiektu z wykorzystaniem polimorfizmu: Brakuje dwóch destruktorów!
Aby umożliwić usunięcie obiektu z wykorzystaniem polimorfizmu, destruktor musi być wirtualny:
Można także bezpośrednio odwoływać się do funkcji klasy bazowej danego obiektu, np.:
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!
Możemy rzutować wskaźnik Detektor na wskaźnik klasy pochodnej, wtedy mamy dostęp do jej funkcji:
Wirtualne funkcje: Okno programu pokazokno wirtualny destruktor? Okno dialogowe Okno edycyjne Rozszerzone okno dialogowe Okno hasła
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()
Ćwiczenie: Obiekt bazowy (OB) void funkcja1() virtual void funkcja2() Obiekt pochodny 1 (OP1) Obiekt pochodny 2 (OP2) virtual void funkcja1() void funkcja2() Obiekt pochodny 3 (OP3) void funkcja1() 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
Które konstruktory/destruktory i w jakiej kolejności się uruchomią? Obiekt bazowy 1(OB1) Obiekt bazowy 2(OB2) Obiekt pochodny 1 (OP1) Obiekt pochodny 2 (OP2)
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