Wstęp do programowania obiektowego WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this 1
Nazwa typu Rozmiar Zakres Uwagi bool 1 bit wartości true albo false stdbool.h TYPY ZNAKOWE char char16_t char32_t 1 bajt 2 bajty 4 bajty 0..255 (unsigned) -128.. 127 (signed) 1 znak Unicode UTF-16 1 znak Unicode UTF-32 reprezentuje znak w kodzie ASCII (7bit) + strona kodowa reprezentuje znak Unicode int 2 lub 4 bajty zależnie od rozmiaru, taki jak: short lub long int rozmiar zależy od kompilatora TYPY CAŁKOWITE short int long int long long int 2 bajty 4 bajty 8 bajtów -32768.. 32767 (signed) 0..65535 (unsigned) -2147483648..2147483647 (s) 0..4294967295 (unsigned) -9223372036854775808.. 9223372036854775807 (s) 0..18446744073709551615 (us) TYPY ZMIENNOPRZECINKOWE float 4 bajty double 8 bajtów long double 8, 10 lub 12B 3.4e +/- 38 (7 cyfr dziesiętnych) 1.7e +/- 308 (15 cyfr dziesiętnych) 1.7e +/- 308 (15 cyfr dziesiętnych) void reprezentuje brak typu 2
Napisowe typy danych w C/C++ char[] (tzw. c-string) tablica znaków char, (czyli znaki ASCII plus strona kodowa), obecny w C/C++. string typ obiektowy zawarty w klasie string, reprezentujący napis oraz użyteczne operacje na nim (rozmiar, łączenie, wycinanie, porównanie, kopiowanie, początek, koniec, etc.) Wyłącznie C++. 3
Operowanie na C-stringach #include <string.h> #include <stdlib.h> #include <iostream> // strcpy() // malloc() using namespace std; main() { char CC[7]; char *CC2; // C-string (6 znaków + NULL) // C-string (tylko wskaźnik) strcpy(cc,"napis"); CC2 = (char *) malloc(15); // Alokacja pamięci strcpy(cc2, "To jest napis."); } cout << CC << endl; cout << CC2 << endl; 4
Operowanie na obiektach klasy string #include <string> #include <iostream> using namespace std; main() { string SS; string SS2; // C++ STL string // C++ STL string SS = "This is a string"; SS2 = SS; } cout << SS << endl; cout << SS2 << endl; 5
Definicja funkcji <typ zwracany> <nazwa> (<lista parametrów>) { } Deklaracja funkcji nagłówek zakończony średnikiem Sygnatura lista typów parametrów z uwzględnieniem kolejności. Przykłady sygnatur: (int) (int, int) (int, float) (float, int) () (char, float, int, int) 6
DZIEDZICZENIE 7
Dziedziczenie - definicja Dziedziczenie - rozszerzanie funkcjonalności klasy bazowej i stworzenie nowej klasy (dziedziczącej) na bazie tej klasy. 8
Schemat dziedziczenia w UML Telewizor # głośność # kontrast # jasność + getglosnosc() + setglosność(double)... TelewizorKolorowy - nasycenie + getnasycenie() + setnasycenie(double) 9
Zapis dziedziczenia w kodzie class TVKolorowy : public TV {... } 10
Zapis dziedziczenia w kodzie class TVKolorowy : public TV {... } 11
W C++ w klasie pochodnej możemy m.in.: zdefiniować dodatkowe zmienne składowe (w naszym przykładzie: nasycenie) zdefiniować dodatkowe metody (w naszym przykładzie getnasycenie() i setnasycenie() ) zmieniać (nadpisywać) treść metod (zdefiniowanych jako wirtualne ) Konstruktory i destruktory nie są dziedziczone każda klasa ma własne! (ale podczas tworzenia każdego obiektu wywołuje się też konstruktor klas bazowej) 12
Korzyści z dziedziczenia: oszczędność implementacji - nie musimy powtórnie definiować tej samej funkcjonalności hierarchiczne uporządkowanie klas tzw. polimorfizm wywoływanie takich samych metod dla różnych klas (o wspólnej klasie bazowej), daje różne efekty Przykład: wywołujemy metodę poruszajsię() dla Orła i Słonia, Orzeł lata, Słoń biega 13
Dziedziczenie może być wielopoziomowe : Zwierzę Ssak Płaz Ptak Słoń Żyrafa Orzeł Wrona Żaba 14
Dziedziczenie możemy też zobrazować jako zbiory i podzbiory obiektów: Słoń Ssak Orzeł Ptak Zwierzę Płaz Żaba Żyrafa Wrona 15
Typy dziedziczenia (w C++) W C++ mamy dostępne trzy typy dziedziczenia: publiczne prywatne chronione Typ dziedziczenia jest specyfikowany w nagłówku klasy pochodnej i może powodować zmiany widoczności odziedziczonych składowych (wyłącznie w klasie pochodnej). 16
Typy dziedziczenia (w C++) Dziedziczenie publiczne - TVKol :public TV public -> public protected -> protected private -> nigdy się nie dziedziczą Dziedziczenie prywatne - TVKol :private TV public -> private protected -> private private -> nigdy się nie dziedziczą 17
Dziedziczenie chronione TVKol :protected TV public -> protected protected -> protected private -> nigdy się nie dziedziczą Najczęściej używane jest dziedziczenie publiczne, we współczesnych językach zwykle dostępne jest tylko ono. 18
Zmienne składowe i metody oznaczone jako protected, są dziedziczone i dostępne w klasach pochodnych. Nie są natomiast dostępne na zewnątrz struktury dziedziczenia. 19
DZIEDZICZENIE WIELOKROTNE 20
Dziedziczenie wielokrotne Dziedziczenie wielokrotne (ang. multiple inheritance) nazywane także dziedziczeniem wielobazowym to operacja polegająca na dziedziczeniu po więcej niż jednej klasie bazowej. Samochód # liczbakół + jedź(int)... Statek + wyporność + płyń()... Amfibia - kolor... 21
Dziedziczenie wielokrotne w kodzie (C++) class Samochód { protected: int liczbakół; public: void jedź(int bieg) { } }; class Statek { public: float wyporność; void płyń() { } }; class Amfibia : public Samochód, public Statek { private: int kolor; }; 22
Dziedziczenie wielokrotne stosowane jest m.in. w językach C++, Perl. W innych językach programowania (np. w Javie) zwykle dopuszczalne jest wyłącznie dziedziczenie jednokrotne, zaś do uzyskania efektu, który w C++ osiąga się poprzez dziedziczenie wielokrotne używa się tzw. interfejsów. 23
Zaleta dziedziczenia wielokrotnego: (niekiedy) intuicyjność dziedziczenia Wady dziedziczenia wielokrotnego: niejednoznaczność działania dla bardziej skomplikowanych drzew, problemy z łańcuchowym wywoływaniem konstruktorów, przesłanianie zmiennych i metod, trudności implementacyjne. 24
POLA I FUNKCJE STATYCZNE 25
Pola statyczne Niekiedy potrzebne są pola wspólne dla wszystkich obiektów klasy. W C++ i wielu innych językach takie pola można zadeklarować przy pomocy słowa kluczowego static. Tak zadeklarowane pole jest widoczne we wszystkich obiektach klasy, ale w pamięci istnieje tylko jeden egzemplarz takiego pola. Pola statyczne są jak gdyby zmiennymi globalnymi danej klasy i istnieją niezależnie od tworzonych obiektów klasy. 26
Pola statyczne w C++ W C++ potrzebna jest definicja, deklaracja i zwykle inicjacja pola statycznego: class Punkt { private: int x; int y; public: static int a; //definicja pola statycznego publicznego } int Punkt::a = 1; //deklaracja i inicjacja pola statycznego poza klasą 27
Odwołanie do publicznego pola statycznego jest możliwe przez nazwę kwalifikowaną (nazwa klasy z operatorem zakresu ::) lub przez operator. i ->. Punkt::a = 2; przez :: //odwołanie do pola statycznego Punkt p1; p1.a =2; //odwołanie do pola statycznego przez. Punkt *wsk; wsk = new Punkt; wsk->a=10; przez -> delete wsk; //odwołanie do pola statycznego 28
Pole statyczne prywatne może być poza klasą tylko zainicjowane (jednokrotny dostęp przy deklaracji). Poza tym wyjątkiem poza klasą nie ma do niego dostępu. 29
Inicjalizacja pól statycznych w C++: Pola statyczne w C++ nie mogą być inicjowane przez konstruktor. Inicjuje się je poza klasą przy użyciu nazwy kwalifikowanej (nazwa klasy i czterokropek). 30
Funkcje statyczne Funkcje statyczne deklarowane z atrybutem static, podobnie jak pola statyczne związane są z klasą a nie z obiektami; czyli: Można się do nich odwoływać nawet gdy nie istnieje żaden obiekt Niestatyczne pola i metody klasy nie są widoczne wewnątrz funkcji statycznych (ponieważ są związane z konkretnymi obiektami) Pola statyczne są widoczne wewnątrz funkcji statycznych 31
Funkcje statyczne służą: zwykle do wykonywania operacji na polach statycznych mogą służyć do zapisu operacji nie związanych z zawartością klasy (np. operacje na plikach, zewnętrznych bazach danych itp.). 32
Przykład użycia funkcji statycznej class Wektor3d { private: double x, y, z; public: static int wymiar; static void zmien_wymiar( int nowy_wymiar) { wymiar = nowy_wymiar;} }; int Wektor3d :: wymiar; //pole statyczne musi być zadeklarowane int main(){ Wektor3d w1; w1.zmien_wymiar( 2); cout << "Wymiar wektora w1 = " << w1.wymiar << endl; Wektor3d :: zmien_wymiar(3); cout << "Nowy wymiar wektora w1 = " << w1.wymiar << endl; } 33
FUNKCJE ZAPRZYJAŹNIONE 34
Funkcje zaprzyjaźnione w C++ Funkcje zewnętrzne albo funkcje innej klasy mogą mieć dostęp do pól prywatnych (lub chronionych) klasy jeżeli zostaną zadeklarowane z atrybutem friend. Są zdefiniowane poza klasą, wewnątrz klasy deklarujemy ich istnienie i fakt przyjaźni. 35
Przykład funkcji zaprzyjaźnionej class Wektor3d { private: double x, y, z; friend double dlugosc( Wektor3d w); }; klasa double dlugosc( Wektor3d w) { return sqrt(w.x * w.x + w.y * w.y + w.z * w.z); } funkcja int main() { Wektor3d w; cout << ", dlugosc = " << dlugosc(w);} 36
Cechy funkcji zaprzyjaźnionych Funkcja zaprzyjaźniona nie jest składową klasy (nie można się do niej odwołać poprzez nazwę kwalifikowaną). Funkcja zaprzyjaźniona nie może być aktywowana na rzecz jakiegoś obiektu Funkcja zaprzyjaźniona powinna mieć jako wynik albo jako argument obiekt klasy, z którą jest zaprzyjaźniona (inaczej przyjaźń nie ma sensu!) Każda funkcja może być zaprzyjaźniona z wieloma klasami. W praktyce, najczęściej jako funkcje zaprzyjaźnione deklaruje się funkcje operatorowe (operatory to np. + - * / ). Ponieważ funkcje zaprzyjaźnione są naruszaniem reguł enkapsulacji, powinno się eksponować jej deklaracje, umieszczając je na samym początku deklaracji ustrojów klas. Ponadto starajmy się nie nadużywać przyjaźni. 37
Słowo kluczowe this Wskaźnik this, automatycznie dodawany przez kompilator do każdego obiektu, jest wskazaniem na ten właśnie obiekt. Funkcje składowe mogą w ten sposób zwrócić wskaźnik na obiekt: return this; lub też sam obiekt: return *this; Przykład - definicja operatora powiększania dla liczb zespolonych: Zespolone operator+= (const Zespolone& z) { re+=z.re; im+=z.im; return *this; } 38