PARADYGMATY PROGRAMOWANIA Wykład 2 Definiowanie klas w C++ - ciąg dalszy Lista inicjalizująca konstruktora Przeznaczenie - do inicjalizacji pól klasy z kwalifikatorem const i inicjalizacji obiektów składowych class Stack const int size; Stack( int max_size );... void Stack::Stack( int max_size ) size = max_size; // Blad - proba przypisania do zmiennej const Postać listy inicjalizującej <def_konstruktora_z_lista_init> :: = <nazwa_klasy>::<nazwa_konstruktora> (<parametry>):<lista_inicjalizująca> <lista_inicjalizująca> ::= <nazwa_pola>(<wartosc>), Lista inicjalizująca jest umieszczana w definicji (xxx.cpp) a nie w prototypie (xxx.h). Przykład: Stack::Stack( int max_size ): size( max_size )... Obiekt stały class String
; String( char *str); char *get(); private char contents[max_len]; const String s1("to jest lancuch"); cout << s1.get(); // BŁĄD Jeśli obiekt jest zadeklarowany jako const to metody użyte wobec niego też muszą być oznaczone jako nie zmieniające obiektu. const char *get() const; Funkcje udostępniające Przeznaczenie - pozwalają udostępnić dane klasy z sekcji public w trybie tylko do odczytu. Cele: ochrona danej przed nieuprawnioną modyfikacją, skupienie wszystkich operacji odstępu do zmiennej w jednym miejscu (np. możliwość sprawdzania poprawności przy podstawianiu, przebudowa struktury danych przy podstawianiu, konstruowanie wartości (wydobywanie ze złozonej struktury danych, konwersja) przy odczycie), hermetyzacja - możliwość zmiany typu danych lub implementacji zmiennej bez konieczności wykonywania zmian u użytkowników klasy. Składanie klas Umieszczanie jako pola jednej klasy obiektu innej klasy nazywamy składaniem (zagnieżdżaniem) klas. class Nazwisko Nazwisko( char *); private: char nazw[max_len]; ; class Data Data (int d, int m, int r);
; private: int dd, mm, rr; class NazwiskoData NazwiskoData( char *n, int d, int m, int r) private: Nazwisko nazw; Data data; Inicjalizacja obiektów składowych za pomocą konstruktora innego niż domyślny - tylko z listy inicjalizującej NazwiskoData::NazwiskoData( char *n, int d, int m, int r ): nazw( n ), data( d, m, r ) Jeśli brak inicjalizacji na liście - użyty będzie konstruktor domyślny. Kiedy automatycznie wywoływane są konstruktory i destruktory: dla zmiennych lokalnych - przy wejściu i wyjściu z bloku, w którym zadeklarowany jest obiekt, przy obiektach dynamicznych - w momencie użycia operatora new i delete, dla obiektów statycznych - w momencie rozpoczynania i kończenia programu, dla obiektów składowych innych klas - w momencie wywoływania konstruktora (ewentualnie z listy inicjalizującej) i destruktora; kolejność przy inicjalizacji - zgodna z kolejnością deklaracji, przy destrukcji - kolejność odwrotna. Wskaźnik this Dla pewnej klasy X typem wskaźnika this jest X const* this; this jest wskaźnikiem stałym (tzn. nie można zmieniać jego wartości) na obiekt, którego dotyczy wywołanie metody. Zastosowania: zwracanie wskaźnika do obiektu, na którym wykonywana jest operacja, zwracanie referencji do obiektu, na którym wykonywana jest operacja, przy operowaniu na strukturach wskaźnikowych zawierających wskaźniki do obiektów (np. samowstawianie się obiektu do wskazanej listy),
dla uniknięcia przesłaniania identyfikatorów pół klasy (np. parametrami metody) Operator zakresu :: Zastosowanie: A. typowo - do uniknięcia niejednoznaczności przy definiowaniu ciała metod klasy, B. do do dostępu z kodu poza klasą do obiektów zdefiniowanych w tej klasie, np. stałej zdefiniowanej w klasie, definicji klasy wewnętrznej, C. do wskazywania obiektów globalnych, których nazwy zostały przysłonięte nazwami lokalnymi zdefiniowanymi w klasie. ad B) class Stack enum StackState(OK, FULL, EMPTY); StrackState current_state; int get(); Stack s1;... if ( s1.current_state == Stack::OK ) s1.get; ad B) int get() class Stack // Funkcja globalna Stack.get() if (::get() == 10) // Odwołanie do funkcji globalnej Pola statyczne klasy są wspólne dla wszystkich obiektów danej klasy, muszą być zdefiniowane poza ciałem metod klasy. mogą być inicjalizowane poza ciałem metod klasy. class Osoba
String nazwisko, String imie; Osoba ( char *imie, char *nazwisko); private: static int liczba_osob; ; Osoba::Osoba(char *imie, char *nazwisko )... liczba_osob++;... int Osoba::liczba_osob = 0; //definicja i inicjalizacja Reguły dotyczące dobrego stylu tworzenia klas: umieszczać deklarację klasy i jej elementów (interfejs klasy) w pliku nagłówkowym xxx.h a implementację - w pliku z kodem xxx.cpp; możliwie dużo danych klasy ukrywać deklarować w sekcji private; jako public deklarować tylko funkcje udostępniające oraz funkcje reprezentujące abstrakcyjne zachowania klasy (wynikające z modelowania za jej pomocą rzeczywistości); do dostępu do danych stosować funkcje udostępniające definiowane wraz z deklaracją (co spowoduje ich klasyfikację jako inline); porządkować kolejność deklaracji elementów klasy - zachowywać kolejność sekcji: public, protected, private. Dziedziczenie (derywacja) Dziedziczenie - tworzenie klasy pochodnej w oparciu o już istniejącą klasę (klasę bazową) Klas bazowa - model pojęcia ogólnego Klasa pochodna - model przypadku szczególnego pojęcia bazowego modelowanego przez klasę bazową.
Przykłady: pojazd samochód, samochód osobowy/ciężarowy osoba pracownik/student pracownik umysłowy/fizyczny/wolny zawód sprzęt sprzęt elektroniczny/maszyny komputer/monitor <-- hierarchie obiektów! Składnia dziedziczenia: class <nazwa_klasy_pochodnej> : public <nazwa_klasy bazowej> <deklaracja elementów klasy> ; Każdy obiekt klasy pochodnej ma dostęp do elementów sekcji public i protected klasy bazowej. Wywoływanie funkcji klasy bazowej class Pracownik void print();... ; class Kasjer : public Pracownik void print(); int nr_kasy; ; void Kasjer::print() Pracownik::print(); cout << "Nr kasy " << nr_kasy; Kasjer prac;... prac.print(); // wywołuje metodę print klasy Kasjer Użycie konstruktora klasy bazowej
Kasjer::Kasjer( char *nazwisko, int stawka, int _nr_kasy ) : Pracownik( nazwisko, stawka ), nr_kasy(_nr_kasy ) Konwersja typów obiektów klasa pochodna -> klasa bazowa TAK klasa bazowa -> klasa pochodna NIE Pracownik p1; Kasjer k1;... p1 = k1; k1 = p1; // poprawne // niepoprawne Kolejność wywoływania konstruktorów (dla dziedziczenia): Najpierw wywoływany jest konstruktor Rodzica, Następnie wykonywany jest konstruktor Dziecka Kolejność wywoływania destruktorów (dla dziedziczenia): Najpierw wywoływany jest destruktor Dziecka, a potem destruktor Rodzica Rodzaje dziedziczenia (public, private, protected) dziedziczenie publiczne (public): klasa dziecka nie wprowadza ograniczeń na widzialność składowych rodzica dziedziczenie chronione (protected): klasa dziecka chroni (tryb protected) składowe publiczne i chronione odziedziczone od rodzica dziedziczenie prywatne (private): klasa dziecka ukrywa (tryb private) składowe publiczne i chronione odziedziczone od rodzica Kwalifikator dostępu protected przed składowymi klasy Składowe te są niedostępne na zewnątrz klasy (zachowują się jak prywatne) Składowe te są dostępne u potomków danej klasy (zachowują się jak publiczne) Kwalifikator dostępu private przed składowymi klasy Składowe te są niedostępne na zewnątrz klasy Składowe te są niedostępne u potomków danej klasy Kwalifikator dostępu public przed składowymi klasy sprawia, Składowe te są dostępne na zewnątrz klasy Składowe te są dostępne u potomków danej klasy class Odcinek Odcinek(int,int,int,int);
void dane(); protected: int x1,y1,x2,y2; private: double odleglosc(); ; class Prosta : protected Odcinek Prosta(int,int,int,int); // przechodząca przez odcinek void rownanie(); // wypisuje równanie prostej ; void main() Prosta p(0,0,10,10); // p.odleglosc(); // skladowa prywatna // p.dane(); //skladowa już chroniona p.rownanie(); //skladowa publiczna Dziedziczenie wielokrotne: Klasa potomna ma wiele klas bazowych: class Prostokat doublepole(); Prostokat(int a=100, int b=100); ~Prostokat(); protected: int a, b; ; // definicja klasy Prostokąt class Kolo doublepole(); Kolo(int r=10); ~Kolo(); protected: int r; ; // definicja klasy Kolo class Walec : public Kolo, public Prostokat // deklaracja publicznych składowych ;
Dostęp do składowych odziedziczonych z klas bazowych jest możliwy dzięki operatorowi zakresu nazwa_klasy::nazwa_składowej double Walec::objetosc() return Kolo::pole()*Prostokat::pole(); double Walec::pole_pow_bocznej() return 2*Kolo::pole()+Prostokat::pole(); Zalety dziedziczenia: Oszczędność pracy: o Definiujemy tylko różnice o Oddzielmy od siebie dwie sprawy: jak klasa jest zrealizowana oraz jak się nią posługiwać Możliwość wprowadzenia hierarchii klas klasy tworzymy w zależności logicznej Możliwość zdefiniowania klas ogólnych przeznaczone wyłącznie do dziedziczenia Przykład Sprzet.h #define DL 20 class Sprzet protected: char opis[dl], nazwa[dl], producent[dl], model[dl]; Sprzet(char *aopis, char *anazwa, char *aproducent, char *amodel); void Informuj_sprzet(); ; class Komputer: public Sprzet protected: char procesor[dl], plyta[dl], bios[dl]; int pamiec; Komputer(char *aopis, char *anazwa, char *aproducent, char *amodel, char *aprocesor, int apamiec, char *aplyta, char *abios); void Informuj_komputer(); ; class Monitor: public Sprzet
protected: int rozdzielczosc; Monitor(char *aopis, char *anazwa, char *aproducent, char *amodel, int arozdzielczosc); void Informuj_monitor(); ; Sprzet.cpp #include "k_sprzet.h" #include <iostream.h> #include <string.h> Sprzet::Sprzet(char *aopis, char *anazwa, char *aproducent, char *amodel) strcpy(opis,aopis); strcpy(nazwa,anazwa); strcpy(producent,aproducent); strcpy(model,amodel); void Sprzet::Informuj_sprzet() cout << endl<< "OPIS:" << endl << opis << endl; cout << "NAZWA:" << endl << nazwa << endl; cout << "PRODUCENT:" << endl << producent << endl; cout << "MODEL:" << endl << model << endl; Komputer::Komputer(char *aopis, char *anazwa, char *aproducent, char *amodel, char *aprocesor, int apamiec, char *aplyta, char *abios) : Sprzet(aOpis, anazwa, aproducent, amodel) strcpy(procesor, aprocesor); pamiec = apamiec; strcpy(plyta,aplyta); strcpy(bios,abios); void Komputer::Informuj_komputer() Informuj_sprzet(); cout << "PROCESOR:" << endl << procesor << endl; cout << "PAMIEC:" << endl << pamiec << endl; cout << "PLYTA:" << endl << plyta << endl; cout << "BIOS:" << endl << bios << endl; Monitor::Monitor(char *aopis, char *anazwa, char *aproducent, char *amodel, int arozdzielczosc):sprzet(aopis, anazwa, aproducent, amodel) rozdzielczosc = arozdzielczosc; void Monitor::Informuj_monitor()
Informuj_sprzet(); cout << "ROZDZIELCZOSC:" << endl << rozdzielczosc << endl;