Zaawansowane programowanie w C++ (PCP) Wykład 2 - agregacja i dziedziczenie, polimorfizm. dr inż. Robert Nowak - p. 1/27
Powtórzenie strona przedmiotu: http://info.wsisiz.edu.pl/ rno Powtórzenie: programowanie strukturalne w C++ przestrzenie nazw obiekty automatyczne, statyczne, dynamiczne i tymczasowe klasy autonomiczne, konstruktor, destruktor, konstruktor kopiujacy - p. 2/27
Składowe statyczne klas» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Składowa statyczna: zmienna globalna (jedna kopia w programie), nie musi być publicznie dostępna (może być prywatna), obowiazkowo musi być zdefiniowana (i zainicjowana). class Foo { static int fooid;//składowa statyczna public: static int getnewid() { return ++fooid; } //metoda statycz... int Foo::fooId = 0; //Definicja i inicjacja składowej statyczn int newid = Foo::getNewId(); //Wołanie metody statycznej Foo f; f.getnewid(); //Wołanie metody statycznej Stosuj stałe statyczne! static const int SIZE = 40; - p. 3/27
Funkcje zaprzyjaźnione» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Dostęp do prywatnych składowych dla funkcji, które nie sa metodami. class Foo {... friend int get(const Foo& f);... private: int i; int get(const Foo& f) { return f.i; } funkcje zaprzyjaźnione sa częścia interfejsu klasy. - p. 4/27
Funkcje zaprzyjaźnione (2)» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector można zaprzyjaźnić metodę klasy (a nawet wszystkie metody innej klasy), deklaracja friend może wystapić w dowolnej sekcji, nie tylko metody moga modyfikować prywatne dane - złamanie obiektowego podejścia. Podział kodu: metoda metoda statyczna funkcja zaprzyjaźniona ma dostęp do składowych prywatnych jest w zasięgu klasy, jest wołana dla obiektu (ma wskaźnik this) ma dostęp do składowych prywatnych jest w zasięgu klasy ma dostęp do składowych prywatnych - p. 5/27
Operatory - powtórzenie» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector priorytet, łaczność. Oznaczenie Opis :: operator zasięgu. -> dostęp do składowej [] indeksowanie () operator funkcyjny ++ -- inkr. dekr. pre- i postfiksowa! negacja bitowa, negacja logiczna - + jednoargumentowy + i - & * adres argumentu, wyłuskanie new new[] delete tworzenie i zwalnianie delete[].* ->* dostęp do składowej (przez wskaźnik - p. 6/27
Operatory - powtórzenie (2)» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector * / % mnożenie, dzielenie, modulo + - dodawanie, odejmowanie << >> przesuwanie bitowe < <= > >= porównywanie ==!= badanie równości, różności & ˆ iloczyn bitowy suma bitowa && różnica symetryczna (bitowa) iloczyn logiczny suma logiczna? : wyrażenie warunkowe = *= /= %= += -= <<= >>= &= = ˆ= operatory przypisania, operator przecinka - p. 7/27
Przeciażanie operatorów» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Następujace wołania sa identyczne dla kompilatora (przykład dla operatora dwuargumentowego) aa bb aa.operator (bb) //Metoda operator (aa,bb) //Funkcja aa + bb aa.operator+(bb) //Metoda operator+(aa,bb) //Funkcja Możliwość definiowania operatorów: dostarczaja wygodna notację dla nowych typów; nie można zdefiniować nowych symboli operatora (np. ** ); nie można użyć innej liczby argumentów. nie można zdefiniować: ::..* tylko wyrażenia zawierajace typy zdefiniowane przez użytkownika używaja przeciażonych operatorów. - p. 8/27
Operator =» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Bardzo ważna metoda (tworzy kopię obiektu). 1. usuwa stara zawartość obiektu 2. tworzy nowa zawartość (to samo robi konstruktor kopiujacy) f = f;//przypisanie obiektu samego do siebie a[i] = a[j];//możliwe przypisanie j.w. px = *py;//możliwe przypisanie j.w. Uwaga! Należy się zabezpieczyć przed przypisaniem a=a class Foo { public: Foo& operator=(const Foo& r) { if(&r!= this)//zabezp. przed samo-przypisaniem... return *this;//zawsze powinien zwracać *this } - p. 9/27
Metody stałe» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Metody które można wołać dla obiektu const typedef int Element;//Typ elementu przechowywanego na liście class Node { public: const Element& get() const;//mozna wolac dla stalego obiek Element& get(); private: Element element_; const Element& Node::get() const { /* Tutaj implementacja */ return element_; } //Implementacja używa poprzedniej wersji Element& Node::get() { return const_cast<element&>( static_cast<const Node&>(*this).get() ); } - p. 10/27
Klasa std::string» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector nagłówek #include <string> inicjacja: string s1; //napis pusty string s2( Ala ); string s3 = Ala ma kota ; porównywanie napisów, operatory ==,!= if(s1 == s2)... if(s2!= string( ABCD ) )... uzyskanie napisu w stylu C : metoda c_str() const char* nap = s2.c_str(); konkatenacja (łaczenie napisów) - operator + string s = s2 + string( Ola ) + s2; badanie długości, podnapisy, wyszukiwanie i wiele innych. - p. 11/27
Klasa std::vector» Składowe statyczne klas» Funkcje zaprzyjaźnione» Funkcje zaprzyjaźnione (2)» Operatory - powtórzenie» Operatory - powtórzenie (2)» Przeciażanie operatorów» Operator =» Metody stałe» Klasa std::string» Klasa std::vector Zastępuje (w większości przypadków) tablicę. nagłówek #include <vector> inicjacja vector<int> v; //pusty wektor obiektów int vector<double> v2(3); vector<punkt> v3[2]; //Uwaga! Dostêp do elementu double d = v2[1]; //bez kontroli zakresu int i = v.at(5); //kontrola zakresu Metoda push_back dodaje element na koniec, np. v.push_back(4); Metoda size() zwraca liczbę elementów. Metoda empty() bada, czy kontener nie jest pusty. - p. 12/27
Tworzenie nowych klas - interfejs czy agregacja? Wielokrotne wykorzystanie kodu: Wykorzystanie istniejacych klas bez naruszania ich implementacji. Możliwość szybkiej zmiany struktury programu. Umożliwia programowanie przyrostowe. Budowa klas na podstawie już istniejacych: agregacja, dziedziczenie. Język UML (Unified Modeling Language) - diagram klas. - p. 13/27
Agregacja Agregacja - relacja typu składa się z lub posiada. - interfejs czy agregacja? Nauczyciel +getname() - imie - nazwisko Przedmiot +getname() - nazwa class Przedmiot { //Definicja klasy class Nauczyciel { //Interfejs private: std::vector<przedmiot*> przedmioty_; //Pozostałe składowe - p. 14/27
Kompozycja - interfejs czy agregacja? Szczególny przypadek agregacji: obiekt składowy nie może istnieć bez obiektu głównego. Samochód +getname() - marka - model Silnik +gettype() - rodzaj class Silnik { //Definicja klasy class Samochod { //Interfejs private: Silnik silnik_; //Pozostałe składowe - p. 15/27
Po co dziedziczenie? - interfejs czy agregacja? Relacja jest lub może być traktowany jako class Pracownik { //Interfejs klasy private: string imie; string nazwisko; class Kierownik { //Interfejs klasy private: Pracownik prac_; vector<pracownik*> podlegli_; Wady: kompilator nie wie, że Kierownik to także Pracownik - p. 16/27
Dziedziczenie Relacja może być traktowany jako, lub jest podtypem. - interfejs czy agregacja? Pracownik +getname() - imie - nazwisko Kierownik +getdepartment() - department klasa bazowa klasa pochodna class Pracownik { //Definicja klasy class Kierownik : public Pracownik { //Definicja klasy Bazowa Pochodna Dziedziczenie wprowadza powiazanie pomiędzy typami. - p. 17/27
Agregacja - interfejs - interfejs czy agregacja? Agregacja - należy dostarczyć nowy interfejs. class Silnik { public: void uruchom(); //Definicja klasy cd. class Samochod { public: void uruchomsilnik() { silnik.uruchom(); } //Interfejs cd. private: Silnik silnik_; //Pozostałe składowe - p. 18/27
Dziedziczenie - interfejs - interfejs czy agregacja? Dziedziczenie - klasa potomna zachowuje interfejs. class Pracownik { public: std::string imie() const; //Definicja klasy cd. class Kierownik : public Pracownik { //Definicja klasy Kierownik k; k.imie(); Dziedziczenie publiczne (najczęściej stosowane): Metody publiczne klasy bazowej sa publiczne w klasie pochodnej; Metody prywatne klasy bazowej sa prywatne w klasie pochodnej; Istnieje możliwość zmiany działania metod. - p. 19/27
Konwersje typów - interfejs czy agregacja? Dziedziczenie: klasa pochodna zawiera obiekt klasy bazowej (wszystkie składowe i metody). Możliwość rzutowania w górę : Pracownik p; Kierownik k; Pracownik* p2 = &k; //OK, można kierownik też jest pracownikie Kierownik* k2 = &p; //BŁĄD!! nie każdy kierownik jest pracowni istnieje możliwość rzutowania wskaźnika(referencji) do klasy pochodnej na wskaźnik (referencję) do klasy bazowej; istnieje możliwość tworzenia obiektu klasy bazowej na podstawie klasy pochodnej; Ale nie odwrotnie! - p. 20/27
Konstrukcja i destrukcja - interfejs czy agregacja? Konstrukcja obiektów złożonych: Konstruktor dla klasy bazowej (domyślnie bezparametowy) Konstruktory dla składowych (domyślnie bezparametrowe) Można wołać inne konstruktory - lista inicjatorów konstruktora: dla klas bazowych: nazwa typu, dla klas, które sa składowymi: nazwa składowej. Konstruktory te sa wołane przed rozpoczęciem kodu konstruktora. Destrukcja obiektów złożonych: Destruktor woła (po zakończeniu działania) destruktory dla obiektów składowych oraz dla klas bazowych. Prawie nigdy nie ma potrzeby wołania jawnie destruktorów. - p. 21/27
Dziedziczenie czy agregacja? - interfejs czy agregacja? dziedziczenie - relacja typu typ - podtyp; pochodna posiada wszystkie składowe bazowej; nie narusza się mechanizmów ochrony; pochodna może być używana tam, gdzie bazowa (rzutowanie wskaźnika) agregacja - gdy budowa z mniejszych kawałków większej całości; Pracownik +getid() - id - nazwisko Kierownik +liczbapodl() - sekcja - pracownicy agregacja: prostsza kontrola - należy ja faworyzować - p. 22/27
Hierarchie klas - interfejs czy agregacja? Figura Trójkąt Prostokąt Koło Złożona Osoba Klient Pracownik class Figura {... class Kolo : public Figura {... class Zlozona : public Figura { private: vector<figura*> sklad_; class Osoba {... class Pracownik : public Osoba {... Specjalista Zarząd Administracja class Specjalista : public Pracownik {... - p. 23/27
Wycinanie - interfejs czy agregacja? Działanie konstruktora kopiujacego lub operatora przypisania: class Pracownik {... class Kierownik : public Pracownik {... Kierownik k(...); Pracownik p = k; kopiuje tylko część klasy, źródło niespodzianek i błędów, rozwi azanie: przekazywanie wskaźników lub referencji do obiektów. - p. 24/27
Składowe chronione - interfejs czy agregacja? Sekcja protected - dostęp do składowych lub metod dla: metod klasy pochodnej; funkcji zaprzyjaźnionych z klasa pochodna. własne metody metody klasy pochodnej inny kod private + - - protected + + - public + + + class Foo { public: //Interfejs protected: //Składowe i metody dostępne dla klas pochodnych private: //Implementacja - p. 25/27
Przedefiniowywanie metod - interfejs czy agregacja? (redefining) w odróżnieniu do nadpisywania (overriding, funkcje wirtualne) class Pracownik { public: void drukuj(ostream& os) const { os << nazwisko: << nazwisko_; } class Kierownik : public Pracownik { public: void drukuj(ostream& os) const { Pracownik::drukuj(os); os << kierownik działu: << dzial_; } przedefiniowywanie: zmiana zachowania metody; jeżeli sygnatury sa różne - zmiana interfejsu, wskazuje na bład projektowy. - p. 26/27
Zakrywanie - interfejs czy agregacja? class Base { public: void f(); void f(double); class Derived : public Base { public: //Przedefiniowanie funkcji void f(); Funkcja przedefiniowana zakrywa inne implementacje: Derived d; d.f(); //Woła metodę D::f() d.f(1.0); //Błąd! D::f() zakrywa B::f(double) Rozwiazanie: polecenie using class Derived : public Base { public: using Base::f;//Wszystkie wersje funkcji f beda widoczne void f();//nadpisanie - p. 27/27