120 likes | 247 Views
Licznik. template<class Count_Type> class Count { public: // Konstruktory/Destruktory Count() { } virtual ~Count() { } // Funkcje implementujące akcje obiektów virtual void increment() = 0; virtual void decrement() = 0; void reset() {value = reset_value;}
E N D
Licznik • template<class Count_Type> • class Count { • public: • // Konstruktory/Destruktory • Count() { } • virtual ~Count() { } • // Funkcje implementujące akcje obiektów • virtual void increment() = 0; • virtual void decrement() = 0; • void reset() {value = reset_value;} • // Funkcje dające dostęp do prywatnych atrybutów obiektów • Count_Type get_value() { return value; } • void set_value(Count_Type new_value) { value = new_value; } • Count_Type get_reset_value() { return reset_value; } • void set_reset_value(Count_Type new_value) • { reset_value = new_value; } • protected: • Count_Type value; • Count_Type reset_value; • }; • class Integer_Count : public Count<int> { • public: • Integer_Count() { reset_value = 0; reset(); } • Integer_Count (int new_value) • { reset_value = 0; value = new_value; } • ~Integer_Count() { } • void increment(); • void decrement(); • char* asBase(int number_base); • };
Szablony (wzorce) • Przykład 1: Szablon klasy - • klasa implementująca wektory dowolnego typu. • template <class T > • class vector { • protected: • T * v; • int size; • public: • vector (int); // konstruktor • T& operator [ ] (int); • T& elem (int i) { return v [ i ]; } • .... • }; • vector <int> v1 (200); • vector <complex > v2 (10); • vector <String> vs(100); • vector <Ksztalt*> figury(20); • Przykład 2: Szablon funkcji - uniwersalna funkcja sortująca. • template <class T > void sort (vector <T> & v); • To jest nieskończenie wiele funkcji. • Przy wywołaniu funkcji wyznacza się funkcję stosując przeciążanie. • vector <complex> cv (100); • vector <String > cs (300); • sort (cv); // wywołuje funkcję sort (vector <complex>) • sort (cs); // wywołuje funkcję sort (vector <String>)
Przykład - wektory • template <class T > • class vector { • protected: • T *v; // właściwy wektor - tablica • int sz; // rozmiar • public: • vector (int); // konstruktor z rozmiarem • vector (vector<T>&); // konstruktor kopiujący • virtual ~vector() { delete v; }; // destructor • int size ( ) { return sz; } • void set_size ( int ); // ustawia rozmiar • T& operator [ ] (int); // dostęp do elementów z kontrolą • T& elem (int i ) // dostęp do elementów bez kontroli • { return v [ i ]; } • friend vector<T> operator+ (vector<T> & a, vector<T> & b); • // tworzy wektor będący sumą wektorów • vector<T> & operator= (vector<T> & ); // przypisanie • } • template <class T> • vector <T>:: vector ( int s) • { • if (s < 0 ) error (“zly rozmiar”); • sz = s; • v = new T [ s ]; • } • template <class T> • vector <T>:: vector (vector <T>& w) • { • sz = w .size(); • v = new T [sz ]; • for (i = 0; i < sz; i++) v [ i ] = w. elem (i); • }
template <class T> • vector <T> :: vector <T>& operator= (vector <T>& w) • { • if (sz != w.size ()) error (“ niezgodne rozmiary wektorów “); • for (int i = 0; i < s; i++) this -> elem ( i ) = w . elem ( i ); • } • template <class T> • vector <T> :: T& operator [] (int i) • { • if (i < 0 || i >= sz) error (“przekroczenie zakresu tablicy”); • return v [ i ]; • } • template <class T> • vector <T> operator+ (vector<T>& a, vector<T>& b) • { • int s = a.size (); • if (s != b.size()) error ( “ zły rozmiar welktora”); • vector<T> suma(s); // nowy wektor • for (int i = 0; i < s; i++) • suma.elem(i) = a.elem(i) + b.elem(i); • return suma; • }
ios istream ostream iostream Wielodziedziczenie(dziedziczenie wielobazowe) Chcemy mieć graf (acykliczny) dziedziczenia, a nie drzewo. • class A { ...}; • class B : { ... }; • class C : public A, public B { ... }; // klasa C dziedziczy po A i po B • Najważniejsze Problemy: • W jaki sposób wyszukiwać funkcje wirtualne? • Rozwiązanie w C++: • Jakakolwiek niejednoznacznośc jest błędem. • Może być tylko jedna możliwa funkcja do wyboru.
a z B b a z C c d a b c d Powtarzanie się klas. class A { int a; }; class B : public A { int b; }; class C: public A { int c; }; class D : public B, public C { int d; }; warstwa B wartstwa C Użycie zmiennej a jest niejednoznaczne. Trzeba kwalifikować zmienną a nazwą klasy (np. B :: a ). Pytanie: czy chcemy, żeby były dwa “egzemplarze” warstwy klasy A, czy jeden? Na ogół chcemy jeden. class A { int a; }; class B : virtual public A { int b; }; class C: virtual public A { int c; }; class D : public B, public C { int d; }; warstwa A warstwa B wartstwa C warstwa D class ios { .... }; class isrtream : virtual public ios { ... } class ostream : virtual public ios { ... } class iostream : public iostream, public ostream { ... }
Programowanie Obiektowe • Program = zbiór obiektów współpracujących poprzez komunikaty (funkcje) • Używamy abstrakcyjnych typów danych • Projektując program projektujemy klasy i akcje obiektów • jakie mają być klasy • jakie mają być hierarchie klas • co obiekt ma robić (jakie funkcje mają mieć klasy) • Nie interesuje nas podział na funkcje (jak w programowaniu strukturalnym). Funkcje są wyznaczone w naturalny sposób przez akcje obiektów • Atrybuty obiektów są chronione (niedostępne z zewnątrz) • Projektujemy klasy tak, aby mogły być wielokrotnie użyte (czyli dosyć ogólne). Specjalizujemy klasy poprzez dziedziczenie • Używamy funkcji wirtualnych - wtedy każdy obiekt wykonuje akcje sobie właściwe
Obsługa wyjątków • Co to jest wyjatek? • To jest wyjątkowa sytuacja w czasie wykonywania operacji • ( funkcji), której nie potrafimy obsłużyć na danym poziomie • abstrakcji, czyli w tej funkcji. • Operacja, która wykryła wyjątkową sytuację, zwykle nie rozumie • dobrze jej znaczenia. Sensowne jest więc przekazanie informacji • do “wyższej instancji”, czyli do funkcji, która tę operacją (funkcję) • wywołała. Nazywa się to zgłoszeniem wyjątku. • Funkcja, która wykonuje operację zgłaszającą wyjątek może go • obsłużyć (wyłapać), zignorować (czyli zostawić do obsłużenia przez • “jeszcze wyższą instancję”) lub propagować (przekazać jeszcze wyżej). • Wyjątki nie obsłużone są obsługiwane przez system (jako błędy) • i na ogół powodują zakończenie programu. • Po co obsługuje się wyjątki? • Zasada programu (systemu) bezpiecznego i odpornego na błędy: • Program nigdy nie ma prawa “paść” w sposób • niekontrolowany. • Program zawsze musi wypisać komunikat zrozumiały dla • użytkownika. • Przykłady wyjątków: • 1. próba pobrania czegoś z pustego stosu • funkcja używająca stosu sama powinna zdecydować, co to znaczy • 2. przepełnienie stosu (np. implementowanego w tablicy) • może funkcja działająca na stosie potrafi temu zaradzić? • 3. brak pliku otwieranego do czytania • może zwykle taki plik jest, ale jak nie ma, to trzeba go założyć
Moduł implementujący abstrakcyjną strukturę danych (np. stos) nie • powinien zawierać obsługi błędów, bo dla użytkownika tej struktury • to mogą być błędy innego rodzaju. • Przykład: • Funkcja zamieniająca wyrażenie na ONP. Przy poprawnym • wyrażeniu stos nigdy nie będzie pusty, więc nie warto • obciążać funkcji sprawdzaniem tego. • Próbę zdjęcia czegoś z pustego stosu warto obsłużyć jako • wyjątek - niepoprawne wyrażenie. • Co dają mechanizmy obsługi wyjątków? • szansę zareagowania na każdą sytuację we właściwy sposób • wydzielenie części obsługującej wyjątki z właściwego programu • C++ • Klasa wyjątków - opisuje wyjątki jakiegoś rodzaju. • throw wyjatek - zgłasza wyjątek • try { • ..... • f ( ... ) • } • catch (< klasa wyjątku1 > ) • {... • // kod obsługi wyjątku1 zgłoszonego w trakcie • // wykonywania funkcji f • } • catch ( < klasa wyjątku2 > ) • { • // kod obsługi wyjatku 2 • } • ....... // dalszy ciąg programu - wyjątki nas nie interesują
template <class T> • class Stos { • private: • T * top; • int max_size; • T * s; • public: • Stos (int n = 10 ) { s = top = new T [size = n]; } • class Empty { } ; // wyjatek - pusty stos • class Overflow { }; // wyjątek - przepełnienie stosu • int empty { return top == s ;} • void push (T & elem ) • { if (top > s + max_size - 1) throw Overflow ( ); • s [ top++ ] = elem; } • T & pop ( ) • { if (top == s) throw Empty ( ); • return s [ -- top ]; } • }; • class Koniec { }; // wyjątek powodujący zakończenie programu • void ONP ( ... ) • { • Stos < char > S (100); • try { .... • S.push (x); ... x = S.pop ( ); • } • catch (Stos <char>:: Overflow) • { cout << “Za długie wyrażenie \n“; • throw Koniec ();} • catch (Stos <char>:: Empty) • { // pominięcie wyrażenia do końca • cout << “Niepoprawne wyrażenie \n”; } • } • void main () • { • .... try { ... ONP ( ... ) ...} catch (Koniec) { } • // akcje końcowe - zamykanie plików itp • }
void G () • { try { // .... • F (... ); • // .. • } • catch (Wyjatek ) { • // ... obsługa możliwa do wykonania na poziomie funkcji G • throw ; // propagacja tego samego wyjątku wyżej • } • // ... • } • void H ( ) • { try { // .... • G ( ); • // .... • } • catch (Wyjątek ) { // ... obsługa na poziomie funkcji H } • } • Wyjątek to jest obiekt - może mieć atrybuty • class Wektor { • // .... • public: • class Zakres { // wyjatek - przekroczenie zakresu • public: • int indeks; // indeks przekraczający zakres • Zakres ( int i) { indeks = i; } • }; • int & Wektor :: operator [ ] (int i) • { if (0 <= i && i < rozm ) return p[i]; • throw Zakres (i); • }
void f (Wektor & w) • { • // ... • try { • zrób_coś (w); ... • } • catch (Wektor :: Zakres z ) • { cerr << “zły indeks “ << z.indeks << ‘\n’; } • // ... • } • Wektor :: Zakres z deklaracja wyjątku • Określa, jaki rodzaj (typ) wyjatku jest to wyłapywany. • Właściwym wyjątkiem jest z. • Wyjątek zgłoszony, ale nie obsłużony powoduje wywołanie funkcji • terminate () • kończącej program. • Można określić, jakie wyjątki może zgłaszać dana funkcja: • void f ( ...) throw (x2, x3, x4) • { ... } • Funkcja f może zgłaszać tylko wyjatki x2, x3, x4. Próba zgłoszenia • czegoś innego powoduje wywołanie funkcji unexpected () kończącej • program (przez wywołanie funkcji terminate() ). • Jeśli nie ma tego w deklaracji funkcji, to może ona zgłosić każdy wyjątek. • void f ( ) throw (); // nie zgłasza żadnych wyjątków • Tego typu deklaracje są ważne dla funkcji zewnętrznych (bibliotecznych)