PARADYGMATY PROGRAMOWANIA Wykład 2
Transkrypt
PARADYGMATY PROGRAMOWANIA Wykład 2
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 { public: 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 { public: 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 { public: Nazwisko( char *); private: char nazw[MAX_LEN]; }; class Data { public: Data (int d, int m, int r); private: int dd, mm, rr; }; class NazwiskoData { public: 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 { public: 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 { public: 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> : { <deklaracja elementów klasy> }; public <nazwa_klasy bazowej> Każdy obiekt klasy pochodnej ma dostęp do elementów sekcji public i protected klasy bazowej. Wywoływanie funkcji klasy bazowej class Pracownik { public: void print(); . . . }; class Kasjer : public Pracownik { public: 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 { public: Odcinek(int,int,int,int); void dane(); protected: int x1,y1,x2,y2; private: double odleglosc(); }; class Prosta : protected Odcinek { public: 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 { public: doublepole(); Prostokat(int a=100, int b=100); ~Prostokat(); protected: int a, b; }; // definicja klasy Prostokąt class Kolo { public: doublepole(); Kolo(int r=10); ~Kolo(); protected: int r; }; // definicja klasy Kolo class Walec : public Kolo, public Prostokat { public: // 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 return } double return } Walec::objetosc() { Kolo::pole()*Prostokat::pole(); Walec::pole_pow_bocznej() { 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]; public: 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; public: 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; public: 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; }