Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Metody programowania 1 Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski.

Podobne prezentacje


Prezentacja na temat: "Metody programowania 1 Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski."— Zapis prezentacji:

1 Metody programowania 1 Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski

2 Metody programowania 2 Literatura S. B. Lippman, J. Lajoie. Podstawy języka C++, WNT Warszawa, B. Stroustrup, C++, WNT Warszawa, B. Eckel, Thinking in C++. Tom 1 edycja polska, HELION Gliwice, T. Hansen, C++ zadania i odpowiedzi, WNT Warszawa, B. Eckel, Thinking in C++. Vol 2, J. Walter, D. Kalev, M. J. Tobler, P. Sainth, A. Kossoroukov, S. Roberts, C++ w zadanich, Robomatic Wrocław, C++ bez cholesterolu,

3 Metody programowania 3 Funkcje przeciążone mają taką samą nazwę, są zadeklarowane w tym samym zasięgu, różnią się między sobą listą argumentów formalnych. int min(int, int); // funkcje przeciążone int min(int, int, int); int min(char, char); int min(char*, char*);

4 Metody programowania 4 Funkcje przeciążone int min(int, int); // deklaracje tej samej funkcji int min(int A, int B); // błąd – nazwy argumentów nie są brane // pod uwagę. short min(int, int); // błąd – do rozróżnienia funkcji nie wystarczą // różne typy wartości przekazywanych int min(int, int = 5); // błąd – nie wystarcza, by funkcje różniły się // ich argumentami domyślnymi typedef int liczba; int min(int, liczba); // błąd – nazwa zdefiniowana instrukcją typdef // nie oznacza nowego typu int min(const int, const int); // błąd – w tym przypadku // modyfikatory const jak również volatile // nie są brane pod uwagę

5 Metody programowania 5 Funkcje przeciążone Rozstrzyganie przeciążenia funkcji proces, w którym dokonywane jest powiązanie wywołania funkcji z jedną funkcją ze zbioru funkcji przeciążonych, przebiega w trzech etapach. 1.Identyfikacja zbioru funkcji przeciążonych – prowadzi do wyłonienia funkcji kandydujących, które to funkcje mają taką samą nazwę jak funkcja wywołana. Następnie dla każdej wybranej funkcji analizowane są właściwości jej listy argumentów formalnych.

6 Metody programowania 6 Funkcje przeciążone 2.Wybór funkcji żywotnych – czyli tych, spośród zbioru funkcji kandydujących, które mogą być wywołane z argumentami funkcji wywoływanej. Funkcja żywotna może mieć: dokładnie tyle samo argumentów formalnych co funkcja wywołana, więcej argumentów formalnych, przy czym każdy dodatkowy argument formalny ma określony argument domyślny. Dana funkcja może być żywotna jeżeli istnieją przekształcenia typu, pozwalające na przekształcenie każdego z argumentów wywołania funkcji w wartość typu odpowiadającego argumentu z listy argumentów formalnych funkcji.

7 Metody programowania 7 Funkcje przeciążone 3.Wybór funkcji najżywotniejszej (funkcji najlepiej dopasowanej ). Dokonywany jest ranking przekształceń typów i funkcją najlepiej dopasowaną zostaje funkcja, dla której przekształcenia typów spełniają warunki: nie są gorsze od przekształceń niezbędnych do tego, by inna z funkcji żywotnych mogła zostać najżywotniejszą, przekształcenia typów zastosowane do pewnych argumentów są lepsze od przekształceń, które byłyby zastosowane do tych samych argumentów przy wywołaniu innej funkcji.

8 Metody programowania 8 Funkcje przeciążone // funkcje kandydujące void fun(); void fun(int); void fun(char*, char*); void fun(double, double=3.14); // funkcje żywotne void fun(int); void fun(double, double=3.14); // funkcja najżywotniejsza void fun(double, double=3.14); // argument typu double jest ściśle zgodny z argumentem formalnym // deklaracje funkcji void fun(); void fun(int); void fan(double = 2.71); void fun(char*, char*); void fun(double, double=3.14); int main() { fun(2.71); return 0; }

9 Metody programowania 9 Funkcje przeciążone Klasyfikacja przekształceń typów argumentów może być zakończona jednym z trzech wyników: ścisłą zgodnością, zgodnością po przekształceniu, niezgodnością. Ścisła zgodność - występuje wówczas, gdy: typ argumentu wywołania funkcji jest taki sam jak typ argumentu formalnego deklarowanej funkcji, istnieje jedno z następujących przekształceń – tzw. przekształceń mniejszych: przekształcenie l-wartości w p-wartość, przekształcenie tablicy we wskaźnik, przekształcenie funkcji we wskaźnik, przekształcenie kwalifikujące.

10 Metody programowania 10 Funkcje przeciążone typedef unsigned int naturalne; enum dni_tygodnia {po=1, wt, sr, cz, pi, so, ni}; void fun(char); void fun(double); void fun(const char*); void fun(naturalne); void fun(int&); void fun(int*); void fun(dni_tygodnia); // l-wartości: naturalne a; int b; dni_tygodnia dzien = sr; int t[5];

11 Metody programowania 11 Funkcje przeciążone // ścisła zgodność: fun('a'); // zgodne z fun(char); fun("a"); // zgodne z fun(const char*); fun(3.14); // zgodne z fun(double); // ścisła zgodność, przekształcenie l-wartości w p-wartość fun(a); // zgodne z fun(naturalne); fun(dzien); // zgodne z fun(dni_tygodnia); // ścisła zgodność, bez przekształcenia l-wartości w p-wartość fun(b);// zgodne z fun(int&); fun(t[1]);// zgodne z fun(int&); // ścisła zgodność, przekształcenie tablicy we wskaźnik fun(t); // zgodne z fun(int*); // uwaga na brak drugiego argumentu naturalne a; int b; dni_tygodnia dzien = sr; int t[5];

12 Metody programowania 12 Funkcje przeciążone //... int min(const int& a, const int& b) {return a<=b ? a : b;} int max(const int& a, const int& b) {return a>=b ? a : b;} typedef int (*p_fun)(const int& a, const int& b);

13 Metody programowania 13 Funkcje przeciążone //... int min(const int& a, const int& b) {return a<=b ? a : b;} int max(const int& a, const int& b) {return a>=b ? a : b;} typedef int (*p_fun)(const int& a, const int& b); int oblicz(int a, int b, int c, int d, p_fun w_f) { return w_f(a,b) * w_f(c,d);} // można też używać jawnej notacji wskaźnikowej : return (*w_f)(a,b) * (*w_f)(c,d); int main() { // ścisła zgodność przekszt. funkcji we wskaźnik do funkcji cout << oblicz(2, 4, 6, 8, min) << endl; cout << oblicz(2, 4, 6, 8, max) << endl; // ścisła zgodność, bez przekształcenia p_fun wsk_fun = min; oblicz(2, 4, 6, 8, wsk_fun); //dla min wsk_fun = max; oblicz(2, 4, 6, 8, wsk_fun); //dla max }

14 Metody programowania 14 Funkcje przeciążone //... int i = 5, *wi = &i; void fun(const int* a); void fun(const int a); int main() { // ścisła zgodność, // przekształcenie kwalifikujące // (z typu int* do const int*) fun(wi); } Przekształcenie kwalifikujące stosuje się tylko do typu wskazywanego przez wskaźnik. //... int i = 5, *wi = &i; const int* wic = &i; void fun(const int* a); void fun(int* const a); void fun(const int a);

15 Metody programowania 15 Funkcje przeciążone int main() { // ścisła zgodność, // bez przekształceń kwalifikujących fun(wic); // zgodne z fun(const int* a); fun(wi) ; // zgodne z fun(int* const a); fun(i) ; // zgodne z fun(const int a); } Modyfikator const argumentu formalnego funkcji fun(int* const a) nie odnosi się do typu wskazywanego przez wskaźnik lecz do samego wskaźnika.

16 Metody programowania 16 Funkcje przeciążone. Zgodność do przekształcenia typu Przekształcenia typów dzielą się na trzy kategorie: awansowanie, przekształcenia standardowe, przekształcenia zdefiniowane przez użytkownika. Awansowanie: wykonywane jest wówczas, gdy typ argumentu aktualnego jest jednym z wymienionych poniżej typów, a typ argumentu formalnego jest odpowiednim typem, do którego dany argument aktualny może awansować Argument typu : char, unsigned char, lub short awansuje do typu int. Jeżeli na danej maszynie sizeof(int) > sizeof(short) to argument typu unsigned short awansuje do tpu int, w przeciwnym razie awansuje do typu unsigned int. Argument typu : float awansuje do typu double. Argument typu wyliczeniowego awansuje do pierwszego z poniższych typów, które mogą w całości reprezentować wartości stałych należących do danego wyliczenia: int, unsigned int, long, unsigned long. Argument typu : bool awansuje do typu int.

17 Metody programowania 17 Funkcje przeciążone. Zgodność do przekształcenia typu 1.Przekształcenia typów całkowitych dowolne przekształcenia dowolnego typu całkowitego lub wyliczeniowego w inny typ całkowity, z wyjątkiem przekształceń będących awansowaniem. 2.Przekształcenia typów zmiennopozycyjnych dowolne przekształcenia typu zmiennopozycyjnego w inny typ zmiennopozycyjny, z wyjątkiem przekształceń będących awansowaniem. 3.Przekształcenia zmiennopozycyjno-całkowite przekształcenia dowolnego typu zmiennopozycyjnego w dowolny typ całkowity lub dowolnego typu całkowitego w dowolny typ zmiennopozycyjny. 4.Przekształcenia typów wskaźnikowych przekształcenia wartości całkowitej zero w typ wskaźnikowy i przekształcenia wskaźnika do dowolnego typu w typ void*. 5.Przekształcenia typów logicznych przekształcenia dowolnego typu całkowitego, wyliczeniowego lub wskaźnikowego w typ bool.

18 Metody programowania 18 Klasa jest abstrakcyjnym typem danych definiowanym przez użytkownika, pozwala na definiowanie obiektów, których własności nie da się opisać bezpośrednio przy wykorzystaniu typów wbudowanych, stwarza możliwość posługiwania się tymi obiektami w taki sam sposób jak w przypadku typów wbudowanych.

19 Metody programowania 19 Klasa Definicja klasy składa się z nagłówka klasy oraz treści klasy, która wyznacza oddzielny zasięg. class xy // nagłówek klasy { public : // specyfikator dostępu // publiczny interfejs klasy xy():x_(0),y_(0){}// konstruktor void fun1(int, int); // metody int& x(){return _x;} const int& x()const {return _x;} private : // specyfikator dostępu // prywatna implementacja void zerowanie(); // metoda int x_, y_ ; // pola ( przechowujące dane ) }; treść klasy

20 Metody programowania 20 Klasa W definicji klasy wyróżnia się takie elementy jak : nazwa klasy, będąca specyfikatorem typu zdefiniowanego przez użytkownika, deklaracje pól, będących wewnętrzną reprezentacją klasy, przy czym w szczególnym wypadku zbiór pól może być zbiorem pustym, deklaracje metod ( funkcji składowych ), określających sposób dostępu do danych, jak również dozwolone operacje wykonywane na obiektach danej klasy ; zbiór metod może być także zbiorem pustym, specyfikatory dostępu ( private, protected, public ) określających, które składowe klasy są prywatne, chronione, a które publiczne. Sama definicja klasy nie powoduje przydziału nowego obszaru pamięci, z którego można byłoby korzystać w programie.

21 Metody programowania 21 Klasa Deklaracja klasy składa się z nagłówka klasy i średnika class C_pkt; // deklaracja //... class C_a { int x; double y;// definicja }; class C_b { int x; double y;// definicja }; class C_c;// deklaracja //... C_a ob1, ob2; C_b ob3; ob1 = ob2; ob1 = ob3; // błąd – dwa różne obiekty C_c ob4; // błąd – brak definicji klasy ; C_c* ob5; // OK.

22 Metody programowania 22 Klasa Definicję obiektu danej klasy musi poprzedzać definicja tej klasy. Zamieszczenie deklaracji klasy daje możliwość deklarowania obiektów typu wskaźnikowego lub/i referencyjnego, wskazujących na obiekty tej klasy. Jest to możliwe ponieważ obiekty tego typu mają zawsze stały rozmiar niezależny od klasy, do której się odnoszą. //... void f1(C_a){} void f2(C_c){} // błąd void f3(C_c*){} void f4(C_c&){}

23 Metody programowania 23 Klasa Deklaracje pól tworzy się identycznie jak w przypadku deklaracji innych obiektów, polami mogą być obiekty typów prostych, złożonych oraz typów abstrakcyjnych, mogą to być wskaźniki lub/i referencje, w tym także do obiektów definiowanej klasy, jedyna różnica pomiędzy deklaracjami pól klasy a deklaracjami innych obiektów polega na tym, że pola nie mogą być jawnie inicjowane; czynność ta może być zrealizowana jedynie za pośrednictwem konstruktora. class C_d { C_c *x; C_d *z; int i = 0; // błąd };

24 Metody programowania 24 Klasa Deklaracje metod Tworzy się umieszczając prototypy funkcji ( wszystkich metod ) w treści klasy, class xy { public : //... xy(int, int); const int& x()const; private : void zerowanie(); int x_, y_ ; int max_x, max_y; }; Definicje metod Tworzy się z reguły poza treścią klasy. Definicje mogą jednak znajdować się także i w treści klasy, przy czym nie powinny one zajmować więcej niż jedną lub dwie linie kodu. Wszystkie metody zdefiniowane wewnątrz treści klasy stają się automatycznie funkcjami zadeklarowanymi jako inline.

25 Metody programowania 25 Klasa class xy { public : xy(int a1=0, int a2=0, int a3=800, int a4=600): x_(a1), y_(a2), max_x(a3), max_y(a4) {} int& x(){return x_;}// metoda dostępowa const int& x()const {return x_;}// metoda dostępowa (metoda stała) //... xy przeksztalcenie(const xy&, const xy&)const ; // (metoda stała) xy& przeksztalcenie(xy&, const xy&); private : //... void zerowanie(){x_=0; y_=0;} };

26 Metody programowania 26 Klasa //... xy ob1; ob1.x()=10; const xy ob2(20,30,210,297); cout << ob2.x(); int& x(){return x_;} const int& x()const {return x_;} xy xy::przeksztalcenie(const xy& r1, const xy& r2) const { xy tmp; /* obliczenia */ return tmp; } xy& xy::przeksztalcenie(xy& r1, const xy& r2){ /* obliczenia */ return r1; }

27 Metody programowania 27 Klasa W definicji metod zapewniony jest nieograniczony dostęp zarówno do składowych publicznych jak i prywatnych klasy. Dostęp do składowych Formalnym mechanizmem zabraniającym dostępu do wewnętrznej reprezentacji klasy jest ukrywanie informacji. Wyodrębnianie i ukrywanie prywatnej wewnętrznej reprezentacji implementacji jest określane jako hermetyzacja lub kapsułkowanie. Wykorzystując specyfikatory dostępu (umieszczane w treści klasy w postaci etykiet ) można określać sekcje, w obrębie których poszczególne składowe stają się publiczne, prywatne lub chronione.

28 Metody programowania 28 Klasa Składowe publiczne są dostępne w każdym miejscu bieżącego zasięgu, oraz w zasięgach zagnieżdżonych. Składowe prywatne są dostępne tylko w treści metod danej klasy, oraz funkcji zaprzyjaźnionych. Domyślnie wszystkie składowe są prywatne. Składowe chronione są dostępne tak jak składowe publiczne tylko w zasięgu klas potomnych względem danej klasy. W każdym innym miejscu programu dostęp jest taki sam, jak do składowych prywatnych. Zaprzyjaźnianie Mechanizm dający uprawnienie dostępu do składowych niepublicznych wybranym funkcjom ( spoza zasięgu danej klasy ), metodom innych klas jak również całym klasom ( zaprzyjaźniając w ten sposób wszystkie metody innej klasy ).

29 Metody programowania 29 Klasa, metody klas Metody klasy reprezentują operacje, które można wykonywać na obiektach, mogą być deklarowane zarówno w sekcjach publicznych, prywatnych jak i chronionych, niezależnie czy ich definicja znajduje się wewnątrz czy na zewnątrz treści klasy zawsze należą do zasięgu klasy. Ogólnie metody klasy można podzielić na następujące kategorie: 1.konstruktory i destruktor 2.przeciążone funkcje operatorów 3.operatory przekształcenia typów 4.metody dostępowe 5.metody statyczne 6.metody pozostałe

30 Metody programowania 30 Klasa class xy { //... friend void przesun(xy&, int, int); friend ostream& operator<< (ostream&, const xy&); public : xy(int a1=0, int a2=0, int a3=800, int a4=600): x_(a1), y_(a2), max_x(a3), max_y(a4) {} //... private : //... }; void przesun(xy& r1, int dx, int dy){ r1.x_ +=dx; r1.y_ +=dy; }

31 Metody programowania 31 Klasa ostream& operator<< (ostream& out, const xy& r) { return out <<"x = " << r.x_ <<"\tmax_x = " << r.max_x <<"\ny = " << r.y_ <<"\tmay_y = " << r.max_y << endl ; } int main(){ xy p1; cout << p1 ; przesun(p1, 2,2); cout << p1 ; //... }

32 Metody programowania 32 Klasa, obiekt klasy Obiekty klas Obiekty posiadają zasięg zależny od lokalizacji deklaracji obiektu ( globalny, lokalny, przestrzeni nazw ). Posiadają czas trwania zależny od zasięgu, ( mogą być obiektami globalnymi, automatycznymi, statycznymi, dynamicznymi ). //... class C_a {// deklaracja klasy w zasięgu globalnym int x, double y; }; int main(){ C_a ob1;// definicja obiektu w zasięgu lokalnym }; Można deklarować/definiować wskaźniki i referencje do obiektów klasy. //... C_a ob1, ob2, *wob=&ob1, &rob=ob2;

33 Metody programowania 33 Klasa, obiekt klasy Można tworzyć tablice obiektów, tablice wskaźników do obiektów, w tym również tablice, którym pamięć zostaje przydzielana dynamicznie. //... C_a tob[4], *twob[4]; C_a *wtc = new C_a[5]; //... delete [] wtc; Obiekty tej samej klasy można wzajemnie inicjować i przypisywać; domyślnie kopiowanie obiektu klasy wykonywane jest poprzez kopiowanie wszystkich jego pól. C_a ob1, ob2, ob3 = ob2; ob1 = ob2;

34 Metody programowania 34 Klasa, obiekt klasy Obiekt klasy będący argumentem lub wartością przekazywaną przez funkcję jest domyślnie przekazywany przez wartość. Można też zadeklarować, by argumenty jak i wartość zwracana przez funkcję były wskaźnikami lub referencjami do obiektu danej klasy. C_a fun1(C_a); C_a& fun2(C_a&, const C_a*); Dostęp zarówno do pól jak i metod klasy uzyskiwany jest poprzez operatory dostępu do składowych, przy czym operator: (. ) stosuje się do obiektów lub referencji, ( -> ) stosuje się do wskaźników do obiektów.

35 Metody programowania 35 Klasa, obiekt klasy class xy { friend void przesun(xy&, int, int); friend ostream& operator<< (ostream&, const xy&); public : xy(int a1=0, int a2=0, int a3=800, int a4=600): x_(a1), y_(a2), max_x(a3), max_y(a4) {} //... const int& x()const { return x_; } const int& y()const { return y_; } private : int x_, y_ ; //.. }; Funkcje posiadające dostęp do składowych prywatnych: void przesun(xy&, int, int); ostream& operator<< (ostream&, const xy&);

36 Metody programowania 36 Klasa, obiekt klasy void fun(xy& A, const xy* B) { if ( A.x() y() ) przesun(A, B->x(), B->y()); else przesun(A, A.x(), B->y()); } void przesun(xy& r1, int dx, int dy){ r1.x_ +=dx; r1.y_ +=dy; }

37 Metody programowania 37 Klasa, obiekt klasy int main() { xy p1; xy tp[2]={xy(20,40), xy(12, 14)}; cout << p1 << tp[0] << tp[1]; fun(p1, &tp[1]); // równoważne: fun(p1, tp+1); cout << p1 ; int i = p1.x_; // błąd int j = tp[0].x(); fun(tp[1], p1); // błąd fun(tp[1], &p1); fun(tp, &p1); // błąd fun(*tp, &p1); }

38 Metody programowania 38 Klasa, obiekt klasy Mając wskaźnik do obiektu klasy można użyć operator wyłuskania ( * ) w celu otrzymania obiektu, do którego odnosi się wskaźnik. Następnie można wykorzystać operator kropkowy (.) w celu uzyskania dostępu do składowych. void fun(xy &A, const xy *B) { if ( A.x() < (*B).y() ) przesun(A, (*B).x(), (*B).y()); else przesun(A, A.x(), (*B).y()); } void fun(xy& A, const xy* B) { if ( A.x() y() ) przesun(A, B->x(), B->y()); else przesun(A, A.x(), B->y()); }

39 Metody programowania 39 Klasa, metody klas Konstruktor klasy jest definiowaną przez użytkownika metodą specjalną klasy służącą inicjowania obiektów danej klasy. Konstruktor definiuje się nadając mu nazwę danej klasy, jednakże nie można dla niego określić typu wartości przekazywanej. class ccc { int i, j; public: ccc() {i=0, j=0;} // lepiej jednak używając listy inicjalizacyjnej // ccc():i(0), j(0) {} }; Konstruktory mogą tworzyć zbiór funkcji przeciążonych co oznacza, że niema ograniczenia liczby deklarowanych konstruktorów, przy czym każdy z definio- wanych konstruktorów musi mieć jednoznaczną listę argumentów formalnych.

40 Metody programowania 40 Klasa, konstruktor Konstruktor jest automatycznie wywoływany dla każdej definicji obiektu danej klasy oraz dla każdego wyrażenia new przydzielającego pamięć nowemu obiektowi. ccc* o1 new ccc; ccc* wsk new ccc[10]; Konstruktor, którego wywołanie nie wymaga określenia argumentów aktualnych jest nazywany konstruktorem domyślnym. //... ccc(){} lub ccc(int a=0, int b=0) :i(a), j(b){} // przy czym obie te definicje // nie mogą istnieć jednocześnie Konstruktory podlegają hermetyzacji.

41 Metody programowania 41 Klasa, konstruktor Lista inicjowania składowych jest umieszczana po dwukropku pomiędzy listą argumentów formalnych a treścią konstruktora, może zatem występować tylko w definicji konstruktora, składa się z nazw pól, rozdzielonych przecinkami, oraz ich wartości początkowych, kolejność inicjowania pól nie zależy od uporządkowania nazw pól na liście inicjalizacyjnej, lecz od kolejności deklaracji pól w definicji klasy. class ccc { int i, j; public: // w tym przypadku nie będzie prawidłowego inicjowania pól !!! ccc(int wrt): j(wrt), i(j) {} };

42 Metody programowania 42 Klasa, konstruktor Obiekt klasy można definiować bez określenia zbioru argumentów tylko wówczas, gdy albo klasa nie zawiera definicji żadnego konstruktora, albo zawiera m.in. definicję konstruktora domyślnego. class c1 { public: string s; int a; }; void fc1() { c1 ob1, ob2={"Ala", 2}; } class c2{ string s; int a; }; void fc2(){ c2 ob1; c2 ob2={"ala", 2}; // błąd nie dozwolone gdy wszystkie } // składowe nie są publiczne

43 Metody programowania 43 Klasa, konstruktor Jeżeli klasa zawiera definicję choćby jednego konstruktora, to każda definicja obiektu tej klasy musi spowodować wywołanie jednego z zadeklarowanych konstruktorów. class c3 { string s; int a; public: c3(string _s): s(_s) {} c3(int _a): a(_a) {} }; void fc3() { c3 ob1("ala"); c3 ob2(2); c3 ob3; // błąd - brak konstruktora domyślnego c3 tab[2]; // błąd - brak konstruktora domyślnego }

44 Metody programowania 44 Klasa, konstruktor class c4{ string s; int a; public: c4(string _s="nic",int _a=0): s(_s), a(_a){ cout << "k1\n"; } c4(int _a): s("nic"), a(_a) { cout << "k2\n"; } //c4(string _s): s(_s), a(0){} // mogłoby być – ale niejednoznaczne // dla definicji c4 ob("ALA"); //c4(): s("nic"), a(0){} // mogłoby być - ale niejednoznaczne // dla definicji c4 ob ; // nie będzie kłopotów z konstruktorem : c4(const char * _s): s(_s), a(0){ cout << "k3\n"; } };

45 Metody programowania 45 int main(){ //... c4 ob1; c4 ob2("osa"); c4 ob3("kot", 4); c4 ob2_1 = c4("osa"); c4 ob3_1 = c4("kot",4); c4 ob4(7); // sposób zalecany c4 ob4_1 = c4(7); c4 ob4_2 = 7; //... } Klasa, konstruktor k1 k2 Jeżeli zdefiniowano : k1, k2 k1 k3 k1 k3 k1 k2 Jeżeli zdefiniowano : k1, k2, k3 c4 ob5(); // to nie jest definicja obiektu klasy c4 // jest to deklaracja funkcji zwracającej wartość typu c4

46 Metody programowania 46 Klasa, inicjowanie składowa po składowej Inicjowanie składowa po składowej jest domyślnym mechanizmem inicjowania jednego obiektu klasy wartością innego obiektu tej samej klasy jednostkami inicjowania są poszczególne pola niestatyczne, a nie cały obiekt klasy kopiowany bit po bicie, Przypadek inicjowania jednego obiektu wartością obiektu tej samej klasy występuje w następujących sytuacjach: 1.jawne inicjowanie obiektu klasy wartością innego obiektu, //... c4 ob5(ob2), ob6=ob2; 2.przekazanie obiektu klasy jako argumentu funkcji : //... void fun(c4 arg) {/*... */} //... fun(ob3);

47 Metody programowania 47 Klasa, inicjowanie składowa po składowej 3.przekazanie obiektu klasy jako wyniku wykonania funkcji: //... c4 fun2(const c4 &arg1, const c4 &arg2) { c4 wynik; /*... */ return wynik; } 4.definicja niepustej kolekcji uporządkowanej : //... vector wekt(5); najpierw wywoływany jest konstruktor domyślny klasy c4 w celu utworzenia obiektu tymczasowego, którym następnie inicjowane są kolejne elementy wektora,

48 Metody programowania 48 Klasa, inicjowanie składowa po składowej 5.wstawienie obiektu klasy do kolekcji //... wekt.push_back(c4("oko")); W wielu jednak praktycznych przypadkach domyśle inicjowanie składowa po składowej nie zapewnia bezpiecznego i poprawnego użycia obiektów klasy. W szczególności dotyczy to klas, które zawierają pola wskazujące na dynamicznie przydzielane obszary pamięci zwalniane następnie przez destruktor klasy. Projektant klasy może zastąpić domyślny mechanizm kopiowania niestatycznych pól klasy definiując dla danej klasy specjalny konstruktor kopiujący.

49 Metody programowania 49 Klasa, konstrukotr kopiujący Konstruktor kopiujący X (const X&) class c5 { char* s; double d; public: c5(): s(0), d(0.0){} c5(char* a, double b): s( new char[strlen(a)+1]), d(b){ strcpy(s,a); } c5(const c5& ob): s(new char[strlen(ob.s)+1]),d(ob.d){ strcpy(s,ob.s); } ~c5(){delete [] s;} };

50 Metody programowania 50 Klasa, konstruktor kopiujący void fc5(){ c5 ob1, ob2("KOT"), ob3("OKO",3); c5 ob4(ob3); //... } Jeżeli w klasie, której składowe są obiektami innych klas, nie ma zdefiniowanego konstruktora kopiującego to każda klasa składowa jest inicjowana składowa po składowej. Jeżeli w takiej klasie jest zdefiniowany konstruktor kopiujący to należy używać jawnego inicjowania składowych przy pomocy listy inicjującej składowe class c4 { /... c4(const c4 &obj): s(obj.s), a(obj.a) {} /... };

51 Metody programowania 51 Klasa, destruktor klasy Destruktor klasy metoda specjalna klasy definiowana przez użytkownika, wywoływana zawsze (automatycznie) gdy obiekt ma być usunięty z powodu opuszczenia zasięgu lub zastosowania operatora delete do wskaźnika do dynamicznego obiektu danej klasy, posiada nazwę klasy poprzedzoną znakiem ( ~ ), nie może przekazywać wartości ani pobierać żadnych argumentów, podlega hermetyzacji, może być wywoływany w sposób jawny. // przypadek, w którym destruktor nie jest konieczny class c6 { int a, b, c; public: ~c6(){} };

52 Metody programowania 52 Klasa, destruktor klasy // przypadek, kiedy destruktor jest niezbędny class c5 { char* s; public: //... ~c5(){delete [] s;} }; // przypadek, kiedy destruktor może być przydatny class c7 { static int lc; static void view(){cout << lc << "\t";} c7(){lc++;} ~c7(){lc--;} };

53 Metody programowania 53 Klasa, destruktor klasy int c7::lc=0; int main(){ if(c7::lc) { c7 c1; c7::view(); } else c7::view(); { c7 c2; c7::view(); if(c7::lc) { c7 c3[3]; c7::view(); } else c7::view(); } c7 c4; c7::view(); //... }

54 Metody programowania 54 Klasa, destruktor klasy void fun() { c5 ob1("kot"), *wsk =0; wsk = new c5(ob1); //... wsk->~c5(); // destruktor wywoływany jawnie //... }

55 Metody programowania 55 Klasa, Wskaźnik this Wskaźnik this niejawnie zdefiniowane pole klasy przechowujące adres obiektu, pozwala metodom klasy uzyskiwać bezpośredni dostęp do pól obiektu, dla którego dana metoda została wywołana, jego typ zależy od atrybutów metody; jeśli metoda nie posiada atrybutu const to jest to wskaźnik do obiektu, int& x(){return x_;} jeśli metoda posiada atrybut const to jest to wskaźnik do obiektu z atrybutem const const int& x()const {return x_;}

56 Metody programowania 56 Klasa, Wskaźnik this Dwuetapowy przebieg implementacji wskaźnika this przez kompilator. 1. Redefinicja metod klasy – metody otrzymują dodatkowy argument : void xy::ustaw(int a, int b) { x_ = a; y_ = b; } // pseudokod void xy::ustaw(C_pkt* this, int a, int b){ this->x = a; this->y = b; } 2. Przetłumaczenie wywołań metody klasy //... xy ob1; ob1.ustaw(5, 10); // pseudokod ustaw(&ob1,5, 10);

57 Metody programowania 57 Klasa, Wskaźnik this Wskaźnik this może być wykorzystywany w sposób jawny. void C_pkt::ustaw(int a, int b) { this->x = a; // wywołanie poprawne (*this).y = b; // chociaż niepotrzebne } tab& tab::operator= (const tab& ob) { if(this!=&ob) { /*... */ } return *this; }

58 Metody programowania 58 Klasa, przypisywanie składowa po składowej Przypisywanie składowa po składowej jest metody domyślnym mechanizmem przypisania jednemu obiektowi wartości drugiego obiektu tej klasy jednostkami inicjowania są poszczególne pola niestatyczne. //... c4 ob2("osa"), ob3; ob3 = ob2; Analogicznie jak w przypadku domyślnego inicjowania składowa po składowej również przypisywanie składowa po składowej nie zapewnia zawsze bezpiecznego i poprawnego użycia obiektów klasy. W sytuacji, gdy implementacja klasy wymaga stosowania konstruktora kopiującego należy także zdefiniować jawną wersję operatora przypisania kopiującego.

59 Metody programowania 59 Klasa, przypisywanie składowa po składowej Operator przypisania kopiującego X& operator = (const X&); class tab { public: //... tab& operator=(const tab& ob); private: int roz; int *wti; //.. }; tab& tab::operator=(const tab& ob) { if(this!=&ob) { delete [] wti; //... } return *this; }

60 Metody programowania 60 Klasa, metody klasy Metody z atrybutem inline metody zdefiniowane wewnątrz treści klasy, automatycznie otrzymują atrybut inline, są zatem kwalifikowane jako funkcje rozwijane w miejscu wywołania, metody definiowane poza treścią klasy, by mogły być rozwijanymi w miejscu wywołania muszą być jawnie poprzedzone specyfikatorem inline; jawne użycie słowa inline jest dozwolone zarówno w deklaracji metody wewnątrz definicji klasy jak i jej definicji znajdującej się poza definicją klasy class xy { public : inline void ustaw(int a=0, int b=0); // deklaracja xy():x(0), y(0), max_x(80), max_y(60){} //... int f1(); inline int f2(); private : //... };

61 Metody programowania 61 Klasa, metody klasy // definicja inline void xy::ustaw(int a=0, int b=0) {x_ = a; y_ = b;} // poniższe metody też są metodami inline klasy xy inline int xy::f1() { return max_x – x_; } int xy::f2() { return max_y – y_; }

62 Metody programowania 62 Klasa, metody klasy Metody z atrybutem const metody, które mogą być wywoływane dla obiektów zadeklarowanych jako const, Słowo kluczowe const umieszcza się między listą argumentów a treścią metody. Jeżeli definicja metody znajduje się poza treścią klasy słowo kluczowe const należy zapisać zarówno w deklaracji jak i definicji metody. class xy{ public : //... void ustaw(int a=0, int b=0){x_ = a; y_ = b;} void fun1(){cout << x_ << \t << y_ << endl;} const int& x() const { return x_; } const int& y() const ; private : int x_, y_ ; //.. };

63 Metody programowania 63 Klasa, metody klasy const int& xy::y()const { return y_; } //... const xy ob1; cout << ob1.x() ; ob1.f1(); // błąd ob1.ustaw(); // błąd Jeżeli metoda zmienia wartość jakiegoś pola klasy, to nie wolno jej deklarować jako const. Deklaracja metody z atrybutem const gwarantuje niezmienność wartości pól klasy, nie gwarantuje jednak niezmienności wartości obiektów, do których odnoszą się wskaźniki będące polami klasy.

64 Metody programowania 64 Klasa, metody klasy class tab { public: //... void zla_metoda(const tab & obj) const; private: int roz; int *wti; }; void tab::zla_metoda (const tab & ob) const { wti=new int [ob.roz]; // to się nie uda // ale to jest możliwe for (int i=0; i

65 Metody programowania 65 Klasa, metody klasy Metodę z atrybutem const można przeciążyć, definiując metodę bez tego atrybutu, ale z taką samą listą argumentów. class xy { public : //... int policz(int a) const ; int policz(int a) ; //... }; //... const xy p1; xy p2; //... int i = p1.policz(2); // wywołana policz(int)const i = p1.policz(2); // wywołana policz(int)

66 Metody programowania 66 Klasa, metody klasy Konstruktorów oraz destruktora nigdy nie deklaruje się jako metod z atrybutem const. Mogą być one jednak wywoływane dla obiektów zadeklarowanych jako const. Obiekt otrzymuje atrybut const z chwilą zakończenia wykonania konstruktora, a atrybut ten traci w chwili wywołania destruktora. Pola ze specyfikatorem mutable pola, które zyskały zezwolenia na modyfikowanie ich wartości nawet dla obiektów zadeklarowanych jako const. class tab { public: //... int pobiez(int i) const private: mutable int pobrano; //... };

67 Metody programowania 67 Klasa, metody klasy int tab::pobiez(int i) const { if(i>=0 && i

68 Metody programowania 68 Klasa, metody klasy Metody ze specyfikatorem volatile metody deklarowane jako ulotne (analogicznie jak dla modyfikatora const ) mogą być wywoływane dla obiektów z atrybutem volatile tj. obiektów, mogących zmieniać swoją wartość poza kontrolą kompilatora. class semafor { public: bool stan() volatile; //... }; bool semafor::stan() volatile {/*...*/}

69 Metody programowania 69 Klasa, pola statyczne Pola statyczne pola będące własnością klasy, z których każde istnieje w jednym egzemplarzu, niezależnie od liczby obiektów danej klasy, są deklarowane z wykorzystaniem słowa kluczowego static, są traktowane jako obiekty globalne należące jednak do przestrzeni nazw klasy, są dostępne dla wszystkich obiektów klasy, podlegają mechanizmowi ukrywania informacji. class rachunek { static double stopa; //... public: //... };

70 Metody programowania 70 Klasa, pola statyczne Pola statyczne inicjuje się na zewnątrz definicji klasy podobnie jak w przypadku metody definiowanej poza treścią klasy należy posługiwać się nazwą kwalifikowaną. W całym programie można użyć tylko jednej definicji pola statycznego. double rachunek:: stopa = 15.5; int main(){ /*... */ }

71 Metody programowania 71 Klasa, pola statyczne Pola statyczne mogą być dowolnego typu, mogą też być obiektami z atrybutem const. //... class rachunek { //... private : static const string nazwa ; }; //... const string rachunek::nazwa("ROR") ; Przypadek szczególny pole statyczne z atrybutem const, będące liczbą całkowitą może być inicjowane wewnątrz treści klasy.

72 Metody programowania 72 Klasa, pola statyczne // plik nagłówkowy class ABC { static const int rozm_bufora1 = 16; static const int rozm_bufora2 ; //... }; // plik źródłowy const int ABC::rozm_bufora1; // wymagana definicja składowej const int ABC::rozm_bufora2 = 32; Typem pola statycznego może być jego klasa: class BCD { static BCD pole1;// OK. BCD *pole2 ; // OK. BCD pole3 ; // błąd //... };

73 Metody programowania 73 Klasa, pola statyczne Pole statyczne można używać jako wartości domyślnej argumentu metody klasy: extern int zm; class CDE { int zm; static zm_stat; public: int fun1(int i=zm); // błąd nie określony // obiekt zawierający pole zm int fun2(int i=zm_stat); // OK. nie jest wymagane istnienie // obiektu zawierające pole zm_stat int fun3(int i=::zm); // OK. // zm z zasięgu globalnego };

74 Metody programowania 74 Klasa, pola statyczne Dostęp do pól statycznych klasy 1.W treści metod nie wymaga użycia operatora dostępu do składowych //... double rachunek::odsetkiDzienne() { return stopa/365 * kwota; } 2.W zasięgu zaprzyjaźnionych funkcji lub klas oraz dla pól statycznych publicznych możliwe są dwa sposoby dostępu z wykorzystaniem operatorów dostępu (.) lub (->) class rachunek { //... friend int porownanie(const rachunek&, constrachunek*); }; int porownanie(const rachunek& ra1, const rachunek* ra2) { double ods1, ods2; ods1 = ra1.stopa /365 * ra1.kwota; ods2 = ra2->stopa /365 * ra2->kwota; //... }

75 Metody programowania 75 Klasa, pola statyczne z wykorzystaniem operatora zasięgu (::) int porownanie(rachunek& ra1, rachunek* ra2) { double ods1, ods2; ods1 = ra1::stopa /365 * ra1.kwota; ods2 = ra2::stopa /365 * ra2->kwota; //...} Metody statyczne metody umożliwiające bezpośredni dostęp do pól statycznych klasy, bez korzystania mechanizmu odwoływania się do składowej z pomocą nazwy obiektu, nie mogą być deklarowane ze specyfikatorem const, ani volatile, nie mogą zawierać w treści odwołań do wskaźnika this, ani do niestatycznych pól klasy.

76 Metody programowania 76 Klasa, pola statyczne class rachunek { static double stopa; //... public: static void zmiana_stopy(double); static double pokaz_stopy(); }; Definicja metody zapisana poza treścią klasy nie powinna zawierać słowa kluczowego static. void rachunek::zmiana_stopy(double sto) { stopa = sto; } double rachunek::pokaz_stopy() { return stopa; } double rachunek::stopa = 0; int main(){ rachunek::zmiana_stopy(2.2); cout << "stopa = " << rachunek::pokaz_stopy()<< " %\n"; }

77 Metody programowania 77 Przeciążanie operatorów mechanizm pozwalający na definiowanie wersji operatorów ( zdefiniowanych pierwotnie w języku C++ ) przeznaczonych dla argumentów będących obiektami klas lub typu wyliczeniowego, pozwala na intuicyjne posługiwanie się obiektami klas użytymi w wyrażeniach, tak jak obiektami typów wbudowanych, inny sposób wywołania metody lub funkcji realizującej określoną operację na obiektach klasy lub/i wyliczenia; Różnica: - operator jest wywoływany przez umieszczenie go między argumentami, lub czasami też przed lub za argumentami. Deklaracja przeciążonego operatora przypomina deklarację funkcji lub metody, wymaga jednak zastosowania słowa kluczowego operator typ_wart_zwracanej ( lista_argumentów_formalnych ) reprezentuje przeciążany operator

78 Metody programowania 78 Przeciążanie operatorów class Complex { double re, im; public: Complex(double a = 0, double b = 0 ): re( a ), im( b ) {} //... friend Complex operator+ ( const Complex& a, const Complex& b ) }; Complex operator+( const Complex& a, const Complex& b ) { return Complex( a.re + b.re, a.im + b.im ); }

79 Metody programowania 79 Przeciążanie operatorów Operatory przeciążalne Operatory nieprzeciążalne +-*/%^& |~!,=<> <=>=++--<<>>== !=&&||+=-=/=%= ^=&=|=*=<<=>>=[] ()->->*newnew[]deletedelete[] ::..*?:

80 Metody programowania 80 Przeciążanie operatorów Liczba argumentów formalnych przeciążanego operatora zależy od tego czy : jest to operator jednoargumentowy czy dwuargumentowy, dany operator jest zdefiniowany jako funkcja przestrzeni nazw : jeden argument operatora jednoargumentowego dwa argumenty operatora dwuargumentowego czy jako metoda klasy: brak argumentów operatora jednoargumentowego jeden argument operatora dwuargumentowego. Jeżeli operator przeciążony jest zdefiniowany jako metoda klasy to ta metoda jest wywoływana tylko wtedy, gdy argument po lewej stronie operatora jest obiektem danej klasy. Jeśli lewy argument jest innego typu wówczas przeciążony operator musi być składową przestrzeni nazw.

81 Metody programowania 81 Przeciążanie operatorów Operatory, które muszą być definiowane jako metody klasy Przeciążalne operatory ( zdefiniowane pierwotnie dla typów wbudowanych ), które mogą występować zarówno w wersji jednoargumentowej jak i dwuargumentowej =[]()->->* +-*&

82 Metody programowania 82 Przeciążanie operatorów Nie wolno: zmieniać znaczenia operatora, które zostało określone dla wbudowanych typów danych, np.: int operator+(int, int); definiować dodatkowych operatorów dla wbudowanych typów danych, np.: operator+(), którego argumentami byłyby tablice zmieniać zdefiniowanych pierwotnie reguł pierwszeństwa operatorów, zmieniać liczby argumentów właściwej dla danego operatora np.: bool operator!(const T1, const T1);

83 Metody programowania 83 Przeciążanie operatorów Operatory jednoargumentowe (przykłady ) // *** Lint1.h *** (metody) //... class Lint1 { long ii; public: Lint1(long a = 0):ii(a){} const Lint1& operator+()const; // plus jednoargumentowy const Lint1 operator-()const; // minus jednoargumentowy const Lint1 operator~()const; // negacja bitowa int operator!() const; // negacja logiczna Lint1* operator&(); // pobranie adresu const Lint1& operator++(); // inkrementacja przedrostkowa const Lint1 operator++(int); // inkrementacja przyrostkowa //... };

84 Metody programowania 84 Przeciążanie operatorów, operatory jednoargumentowe // *** Lint2.h *** (funkcje zaprzyjaźnione) //... class Lint2 { long ii; public: Lint2(long a = 0):ii(a){} Lint2* This(){ return this; } friend const Lint2& operator+ (const Lint2& a); friend const Lint2 operator- (const Lint2& a); friend const Lint2 operator~ (const Lint2& a); friend int operator! (const Lint2& a); friend Lint2* operator& (Lint2& a); friend const Lint2& operator++(Lint2& a); friend const Lint2 operator++(Lint2& a, int); //... };

85 Metody programowania 85 Przeciążanie operatorów, operatory jednoargumentowe // *** Lint1.cpp *** ( metody ) #include "Lint1.h" const Lint1& Lint1::operator+()const {return *this;} const Lint1 Lint1::operator-()const {return Lint1(-ii); } const Lint1 Lint1::operator~()const {return Lint1(~ii); } int Lint1::operator!()const {return int(!ii);} Lint1* Lint1::operator&() {return this;} // przedrostkowy const Lint1&Lint1::operator++() {ii++; return *this;} // przyrostkowy const Lint1 Lint1::operator++(int){ Lint1 poprzednia(ii); ii++; return poprzednia; }

86 Metody programowania 86 Przeciążanie operatorów, operatory jednoargumentowe // *** Lint2.cpp *** ( funkcje zaprzyjaźnione ) #include "Lint2.h" const Lint2& operator+(const Lint2& a){return a;} const Lint2 operator-(const Lint2& a){return Lint2(-a.ii);} const Lint2 operator~(const Lint2& a){return Lint2(~a.ii);} int operator!(const Lint2& a){return int(!a.ii);} Lint2* operator&(Lint2& a){return a.This();} const Lint2& operator++(Lint2& a){i++; return *this;} const Lint2 operator++(Lint2& a, int) { Lint2 poprzednia(a.ii); a.ii++; return poprzednia; }

87 Metody programowania 87 Przeciążenie operatorów #include "Lint1.h" int main() {Wyniki: Lint1 ob(1); Lint1* wsi ; cout << +ob; 1 cout << -ob; -1 cout << ~ob; -2 cout << !ob << endl; 0 cout << (++ob); 2 cout << (--ob); 1 cout << (ob++); 1 cout << (ob--); 2 wsi = &ob; cout << wsi; 0x22ff70 } // Analogicznie dla obiektów typu Lint2

88 Metody programowania 88 Przeciążenie operatorów Inkrementacja i dekrementacja może być realizowana w sposób przedrostkowy ( ++a ) lub przyrostkowy ( a++ ), istnieje konieczność zdefiniowania dwóch postaci przeciążonych operatorów ++ i --, przedrostkowych: const abc& abc::operator++() {...} const abc operator++(abc& a) {...} i przyrostkowych posiadających dodatkowy argument formalny typu int, którego nazwy nie trzeba podawać ponieważ nie jest on wykorzystywany w treści klasy : const abc& abc::operator++(int) {...} const abc operator++(abc& a, int){...}

89 Metody programowania 89 Przeciążenie operatorów Operatory dwuargumentowe – wybrane przykłady // metody klasy class Lint1 { //... const Lint1 operator+ (const Lint1& pa) const; const Lint1 operator* (const Lint1& pa) const; const Lint1 operator/ (const Lint1& pa) const throw(string); Lint1& operator+= (const Lint1& pa); Lint1& operator/= (const Lint1& pa) throw(string); bool operator== (const Lint1& pa) const; bool operator!= (const Lint1& pa) const; bool operator< (const Lint1& pa) const; bool operator&& (const Lint1& pa) const; };

90 Metody programowania 90 Przeciążanie operatorów, Operatory dwuargumentowe // funkcje zaprzyjaźnione class Lint2 { //... friend const Lint2 operator+(const Lint2& la, const Lint2& a); friend const Lint2 operator*(const Lint2& la, const Lint2& pa); friend const Lint2 operator/(const Lint2& la, const Lint2& pa) throw(string); friend Lint2& operator+=(Lint2& la, const Lint2& pa); friend Lint2& operator/=(Lint2& la, const Lint2& pa) throw(string); friend bool operator==(const Lint2& la, const Lint2& pa); friend bool operator!=(const Lint2& la, const Lint2& pa); friend bool operator<(const Lint2& la, const Lint2& pa); friend bool operator&&(const Lint2& la, const Lint2r& pa); };

91 Metody programowania 91 Przeciążanie operatorów, Operatory dwuargumentowe // definicje (metody) Lint1 const Lint1 Lint1::operator+(const Lint1& pa)const { return Lint1(ii + pa.ii ); } const Lint1 Lint1::operator*(const Lint1& pa)const { return Lint1(ii * pa.ii); } const Lint1 Lint1::operator/(const Lint1& pa)const throw (string){ if(pa.ii == 0) throw string("ERROR ! : Dzielenie przez zero ! (x/0)" ); return Lint1(ii / pa.ii); } Lint1& L_int_1::operator+=(const Lint1& pa){ ii += pa.ii; return *this; }

92 Metody programowania 92 Przeciążanie operatorów, Operatory dwuargumentowe // definicje c.d. (metody) Lint1&Lint1::operator/=(const Lint1& pa)throw(string) { if(pa.ii == 0) throw string("ERROR ! : Dzielenie przez zero ! (x/=0)" ); ii /= pa.ii; return *this; } bool Lint1::operator==(const Lint1& pa) const { return ii == pa.ii; } bool Lint1::operator!=(const Lint1& pa) const { return ii != pa.ii; } bool Lint1::operator<(const Lint1& pa)const { return ii < pa.ii;} bool Lint1::operator&&(const Lint1& pa)const { return ii && pa.ii;}

93 Metody programowania 93 Przeciążanie operatorów, Operatory dwuargumentowe // definicje (funkcje zaprzyjaźnione) const Lint2 operator+(const Lint2& la, const Lint2& pa) { return Lint2(la.ii + pa.ii); } // analogicznie dla operatorów (-), (*), (^),... const Lint2 operator/(const Lint2& la, const Lint2&pa) throw(string) { if(pa.ii == 0) throw string("ERROR ! : Dzielenie przez zero ! (x/0)" ); return Lint2(la.ii / pa.ii); }

94 Metody programowania 94 Przeciążanie operatorów, Operatory dwuargumentowe // definicje (funkcje zaprzyjaźnione) c.d. Lint2& operator+=(Lint2& la, const Lint2& pa) { la.ii += pa.ii; return la; } Lint2& operator/=(Lint2& la, const Lint2& pa)throw(string) { if(pa.ii == 0) throw string("ERROR ! : Dzielenie przez zero ! (x/=0)" ); la.ii /= pa.ii; return la; } bool operator==(const Lint2& la, const Lint2& pa) { return la.ii == pa.ii;} // analogicznie dla operatorów (!=), (<), (&&),..

95 Metody programowania 95 Przeciążanie operatorów int main() {Wyniki Lint1 ob.(5), aa(2), bb; cout << "operator+\t" << ob+aa;... 7 cout << "operator-\t" << ob-aa;... 3 cout << "operator*\t" << ob*aa; try { cout << "operator/\t" << ob/aa;... 2 } catch(string er) { cerr << er << endl;} try { cout << ob/bb; } catch(string er) { cerr << er << endl; }ERROR !...

96 Metody programowania 96 Przeciążanie operatorów cout << "operator+= \t" << (ob+=aa);... 7 try { cout << "operator/= \t" << (ob/=aa);... 3 } catch(string er) { cerr << er << endl;} cout <<"operator==\t"<< (ob==aa) <

97 Metody programowania 97 Przeciążanie operatorów Operator przypisania kopiującego X& operator = (const X&); może występować wyłącznie jako metoda klasy musi być zdefiniowany zawsze w sytuacji, gdy implementacja klasy wymaga stosowania konstruktora kopiującego. class tab { public: //... tab& operator= (const tab& ob); private: int roz; int *wti; //.. };

98 Metody programowania 98 Przeciążanie operatorów tab& tab::operator= (const tab& ob) { if(this!=&ob) { delete [] wti; roz = ob.roz; wti = new int [roz]; for(int i=0; i

99 Metody programowania 99 Przeciążanie operatorów Operator indeksowania może występować wyłącznie jako metoda klasy class tab { public: //... int& operator[](int i)throw (string){ if(i = roz) throw string ("indeks poza zakresem\n"); return wti[i]; } const int& operator[](int i)const throw (string){ if(i = roz) throw string ("indeks poza zakresem\n"); return wti[i]; } //.. };

100 Metody programowania 100 Przeciążanie operatorów Operatory przeciążone class Napis { public: //... Napis& operator += (const Napis&); Napis& operator += (const char*); //... private: int rozm; char* txt; };

101 Metody programowania 101 Przeciążanie operatorów Napis& Napis::operator+= (const Napis& ob) { if (ob.txt) { Napis tmp(*this); rozm += ob.rozm; delete[] txt; txt = new char[rozm + 1]; strcpy(txt, tmp.txt); strcat(txt, ob.txt); } return *this; } Napis& Napis::operator+= (const char* nps) { if (nps) { Napis tmp(*this); rozm+= strlen(nps); delete[] txt; txt = new char[rozm + 1]; strcpy(txt, tmp.txt); strcat(txt, nps); } return *this; }

102 Metody programowania 102 Przeciążanie operatorów, operatory przeciążone //... Napis ja("Moje"), mam("zaliczenie"); ja += " marzenie to mieć "; // "Moje marzenie to mieć " ja += mam; // "Moje marzenie to mieć zaliczenie"

103 Metody programowania 103 Przeciążanie operatorów Argumenty i zwracane wartości – zasady ogólne 1.Zwyczajne operacje arytmetyczne (jak +, - itd ) oraz logiczne nie zmieniają swoich wartości swoich argumentów, więc powinny być one przekazywane w postaci referencji do stałych. Gdy funkcja jest składową klasy powinna być funkcją stałą. 2. Jeżeli lewy argument nie jest modyfikowany, a w skutek działania operatora powstaje nowa wartość to, aby ją zwrócić, trzeba utworzyć nowy obiekt. 3. Zaleca się by zwracany obiekt był wartością stałą.

104 Metody programowania 104 Przeciążanie operatorów Przykład – dwuargumentowy operator + ; const abc abc::operator+(const abc& pa) const { //... return abc ( /* działania */); } Jeśli operator ten zostanie zastosowany w takich wyrażeniach jak : fun(a+b); to wynik działania a+b stanie się obiektem tymczasowym użytym do wywołania funkcji fun(). Obiekty tymczasowe są automatycznie stałymi więc w tym przypadku nie ma znaczenia czy wartość zwracana przez operator była stała. Jeśli jednak operator byłby zastosowany w wyrażeniu (a+b).metoda() ; to gdyby operator nie zwracał obiektu stałego wynik działania a+b mógłby być potencjalnie modyfikowany (działaniem metody ), co mogłoby doprowadzić do utraty jakiejś informacji. Należy więc jawnie tego zabronić.

105 Metody programowania 105 Przeciążenie operatorów, argumenty i zwracane wartości 4.Wszystkie operatory przypisania modyfikują l-wartość. Wartości zwracane przez te operatory powinny być referencjami do l-wartości. Ogólne wskazówki dotyczące wyboru pomiędzy metodami a funkcjami nie będącymi składowymi klasy OperatorZalecany sposób użycia Wszystkie operatory jednoargumentowe metody = ( ) [ ] –> –>* wyłącznie jako metody += –= /= *= ^= &= |= %= >>= <<= metody Wszystkie pozostałe operatory dwuargumentowe funkcje nie będące składowymi klasy

106 Metody programowania 106 Obsługa sytuacji wyjątkowych Obsługa sytuacji wyjątkowych - mechanizm pozwalający na komunikację pomiędzy dwoma fragmentami programu w celu obsłużenia zdarzenia nienormalnego - sytuacji wyjątkowej. W języku C++ mechanizm niewznawialny. Po zgłoszeniu wyjątku i jego ewentualnym obsłużeniu, sterowanie nie może być przekazane do miejsca zgłoszenia wyjątku. Zgłaszanie wyjątku. - z wykorzystaniem słowa kluczowego throw, które poprzedza wyrażenie, którego typem jest zgłaszany wyjątek. obsługa wyjątków. Obsługa wyjątków blok try - zawiera instrukcje zgłaszające wyjątki oraz jednej lub kilku klauzul catch z argumentem typu wyjątku i blokiem instrukcji jego obsługi.

107 Metody programowania 107 Obsługa sytuacji wyjątkowych const Lint1 Lint1::operator/(const Lint1& pa)const throw (string) { if(pa.ii == 0) throw string("ERROR ! : Dzielenie przez zero ! (x/0)" ); return Lint1(ii / pa.ii); } int main() { Lint1 aa(5), bb, cc; //... try { cc = aa/bb; } catch(const string& er){ cerr << er << endl; }

108 Metody programowania 108 Obsługa sytuacji wyjątkowych Zwijanie stosu - proces, w którym wychodzi się z instrukcji złożonych i definicji funkcji w poszukiwaniu klauzuli catch przeznaczonej do obsługi zgłoszonego wyjątku. - Jest gwarancją wywołania destruktorów obiektów lokalnych podczas opuszczania zasięgu, w którym zgłaszana jest sytuacja wyjątkowa. - Jeżeli nie zostanie znaleziona klauzula obsługująca zgłaszany wyjątek wywołana zostanie funkcja terminate(). Specyfikacja wyjątków - lista wyjątków, które może zgłosić dana funkcja. - Jest umieszczana za listą argumentów formalnych funkcji po słowie kluczowym throw pomiędzy parą nawiasów okrągłych - int fun(... )throw (int, string);, - Jest gwarancją nie zgłaszania przez funkcję wyjątków z poza listy. - Pusta lista wyjątków jest gwarancją, że funkcja nie zgłosi żadnego wyjątku. - Jeśli funkcja zgłosi wyjątek z poza listy nastąpi wywołanie funkcji unexpected() (ze standardowej biblioteki C++), której domyślnym zachowaniem jest wywołanie funkcji terminate(), która w takcie wykonywania programu informuje o braku procedury obsługi zgłaszanego wyjątku.

109 Metody programowania 109 Obsługa sytuacji wyjątkowych class abc{ public : int a; ~ijk(){cout << "~abc(){}" << endl ;} }; int main() { Lint1 aa(5), bb, cc; //... try { abc x; x = 5; cc = aa/bb; } catch(const string& er){ cerr << er << endl; } //... ~abc(){} ERROR !: Dzielenie przez zero !(x/0)

110 Metody programowania 110 Obsługa sytuacji wyjątkowych Wychwytywanie wszystkich wyjątków - catch (... ){}. Klauzule catch przeglądane są w kolejności występowania w programie. Po znalezieniu właściwej pozostałe nie są sprawdzane. Klauzula wychwytująca wszystkie wyjątki powinna być zapisywana jako ostatnia. enum kategoria {duzy_dzielnik, jest_reszta}; //... const Lint1 ::operator/(const int& pa) throw (string, kategoria, int) { if (pa==0) throw string ("ERROR !: Dzielenie przez zero !(x/0)"); if (pa<0) throw int (-1); if (pa>ii) throw kategoria (duzy_dzielnik); if (ii%pa) throw kategoria (jest_reszta); return abc(ii/pa); }

111 Metody programowania 111 Obsługa sytuacji wyjątkowych //... try{ // cout << aa/2<< endl; // cout << aa/6<< endl; cout << aa/-1<< endl; } catch (const string& err){ cerr << err << endl;} catch (kategoria err){ switch (err){ case duzy_dzielnik : cout << "dzielna mniejsza od dzielnika" << endl; break; case jest_reszta : cout << "powstala reszta z dzielenia" << endl; break; } catch (... ){ cerr << "Blad nieznany " << endl;}

112 Metody programowania 112 Obsługa sytuacji wyjątkowych Ponowne zgłoszenie wyjątku. Klauzula catch może przekazać wyjątek do innej klauzuli znajdującej się wyżej na liście wywołań funkcji. class blad{ double x; public: blad(double a=0):x(a){} friend ostream& operator << (ostream& out, const blad& r) {return out << "Bledna wartosc : " << r.x << endl;} }; enum kategoria {duzy_dzielnik, jest_reszta}; class Lint1 { //... const Lint1 operator/(const Lint1 & pa) throw (string); const Lint1 operator/(const int& pa) throw (blad); friend ostream& operator << (ostream& out, const Lint1& r) {return out << r.ii ;} };

113 Metody programowania 113 Obsługa sytuacji wyjątkowych const abc abc::operator/(const int& pa) throw (blad) { try{ if (pa==0) throw string ("ERROR! : Dzielenie przez zero !(x/0)"); if (pa<0) throw int (-1); if (pa>ii) throw kategoria (duzy_dzielnik); if (ii%pa) throw kategoria (jest_reszta); return abc(ii/pa); } catch (const string& err){ cerr << err << endl; } catch (kategoria err){ switch (err){ case duzy_dzielnik : cout << "dzielna mniejsza od dzielnika" << endl; throw blad(double(ii)/pa); case jest_reszta : cout << "powstala reszta z dzielenia" << endl; throw blad(ii%pa); } catch (int err){ cerr << "niedozwolony wynik ujemny " << endl; throw blad(double(ii)/pa); }

114 Metody programowania 114 Obsługa sytuacji wyjątkowych int main(){ Lint1 aa(5), bb, cc; //... try{ for(int i=-1; i < 7; ++i){ cout <<"i=" << i << " " << "wynik : " ; try { cout<

115 Metody programowania 115 Obsługa sytuacji wyjątkowych Obsługa sytuacji wyjątkowej braku wystarczających zasobów pamięci. operator new zgłasza wyjątek bad_alloc. int main() try { int* wsk = new int[1024*1024*116]; delete [] wsk; return 0; } catch (bad_alloc) {cerr << "Kup nowy komputer :-)";}

116 Metody programowania 116 Dziedziczenie mechanizm pozwalający na definiowanie nowej klasy na podstawie klasy istniejącej. Klasa podstawowa - bazowa klasa, po której własności dziedziczą inne klasy ( klasy pochodne ). Klasa pochodna każda klasa zdefiniowana przez rozszerzenie własności istniejącej klasy, bez ponownego programowania i kompilowania klasy bazowej; dziedziczy pola i metody klasy bazowej. Hierarchia zbiór zawierający klasę bazową i jej klasy pochodne. pojazd łódźsamochód osobowy Jeżeli klasa bazowa ma wspólny interfejs publiczny z klasami pochodnymi, to klasy pochodne są podtypami klasy bazowej.

117 Metody programowania 117 Dziedziczenie Polimorfizm * w języku C++ specjalna relacja pomiędzy typem a podtypem, dzięki której wskaźnik lub referencja do obiektu klasy bazowej może odnosić się do dowolnego obiektu będącego podtypem tej klasy; istnieje tylko wewnątrz poszczególnych hierarchii klas. Korzystanie z polimorfizmu jest możliwe za pomocą takich mechanizmów jak : niejawne przekształcenia typu wskaźników do klas pochodnych, referencji do wskaźników lub referencji do pub-licznego typu podstawowego, funkcje wirtualne, operacje rzutowania dynamicznego ( dynamic_cast ) Klasa abstrakcyjna niepełna klasa bazowa, która staje się mniej lub bardziej ukończoną w wyniku każdego kolejnego dziedziczenia po niej przez inną klasę, stanowi wyłącznie interfejs dla swoich klas pochodnych, nie daje możliwości tworzenia obiektów tej klasy. * Dosłownie wiele form. Obiekty polimorficzne mają ten sam interfejs, ale zachowują się w sposób odmienny – zależnie od ich pozycji w hierarchii dziedziczenia.

118 Metody programowania 118 Dziedziczenie Lista dziedziczenia jest umieszczana po dwukropku pomiędzy nazwą klasy pochodnej a nawiasem klamrowym rozpoczynającym, ciało klasy, może zatem występować tylko w definicji klasy, składa się ze specyfikatora poziomu dostępu oraz nazwy klasy bazowej ( lub specyfikatorów i nazw oddzielonych przecinkami – w przypadku dziedziczenia wielokrotnego ) class Poch : poziom_dostępu klasa_bazowa {}; Poziom dostępu: public ( dziedziczenie typu ) protected private Klasa określona w liście dziedziczenia musi być zdefiniowana zanim zostanie określona jako klasa bazowa. Deklaracja zapowiadająca klasy pochodnej nie zawiera listy dziedziczenia, tak jak gdyby to nie była klasa pochodna.

119 Metody programowania 119 Dziedziczenie class Bazowa; class Pochodna1; class Pochodna2; class Pochodna3 : public Bazowa; // błąd Składnia dziedziczenia class Baz { public: Baz(int a=0, double b=0.0): x(a), y(b) {} void pokaz_xy(){ cout << x << ", " << y << ", "; } protected : int x; double y; };

120 Metody programowania 120 class Poch : public Baz { public: Poch(int a=0, double b=0.0, const string& c = "nic"): Baz(a,b), z(c){} void pokaz_z() { cout << z << ", "; } void pokaz_xyz() { cout << x << ", " << y << ", " << z << ", "; } protected: string z; }; //... int main() { Poch ob(5, 3.14, "kot); ob.pokaz_xy() ; cout<< endl; ob.pokaz_z() ; cout<< endl; ob.pokaz_xyz(); cout<< endl; return 0; } Dziedziczenie 5, 3.14 kot, 5, 3.14, kot,

121 Metody programowania 121 Dziedziczenie Dostęp do składowych klasy bazowej Obiekt klasy pochodnej składa się z: podobiektu utworzonego z niestatycznych pól klasy bazowej ( pola odziedziczone ), części pochodnej składającej się z niestatycznych pól klasy pochodnej ( pól deklarowanych w klasie pochodnej ) W treści klasy pochodnej można bezpośrednio odnosić się do składowych podobiektów odziedziczonych po klasie bazowej tak jak gdyby to były składowe danej klasy pochodnej. Głębokość łańcucha dziedziczenia nie ogranicza dostępu do tych danych ani też nie zwiększa kosztów tego dostępu. Metody klasy bazowej wywoływane są tak samo jak gdyby to były metody klasy pochodnej. Wyjątek dotyczy bezpośredniego dostępu w treści klasy pochodnej do składowej klasy bazowej, jeśli nazwy składowej klasy bazowej używa się powtórnie w klasie pochodnej.

122 Metody programowania 122 Dziedziczenie, dostęp do składowych klasy bazowej // --Cx.h-- #include using namespace std; class Cx { protected : int i; public: Cx(int a=0):i(a) {cout << "Cx\n";} ~Cx(){cout << "~Cx\n";} void ustaw(int a) { i = a; } const int& czytaj() const { return i; } const int& zmiana(int a){return i+=a;} void inc(){++i;} };

123 Metody programowania 123 Dziedziczenie, dostęp do składowych klasy bazowej // --progam.cpp-- #include "Cx.h" class Cy : public Cx { int i; // inne niż i klasy Cx public: Cy(int a=0): Cx(2*a), i(a) {cout << "Cy\n";} ~Cy(){cout << "~Cy\n";} void ustaw(int a) { i = a;} void ustaw_xi(int a) { Cx::i = a; } const int& czytaj() const { return i; } int zmiana() { i = Cx::zmiana(2); } }; // umownie: i1 składowa i klasy Cy - podobiekt odziedziczony // i2 składowa i klasy Cy - składowa dołożona void pokaz(const Cy& ob){ cout << ob.Cx::czytaj() << endl; // i1 cout << ob.czytaj() << endl; // i2 }

124 Metody programowania 124 Dziedziczenie, dostęp do składowych klasy bazowej int main() { cout<< sizeof(Cx)<< endl; cout<< sizeof(Cy)<< endl; Cy ob(3); pokaz(ob); ob.ustaw(12); // i2 pokaz(ob); ob.ustaw_xi(24); // i1 pokaz(ob); ob.zmiana(); // i1, i2 pokaz(ob); return 0; } Wyniki: 4 8 Cx Cy ~Cy ~Cx

125 Metody programowania 125 Dziedziczenie, dostęp do składowych klasy bazowej Mimo, że do składowych klasy podstawowej można odnosić się bezpośrednio składowe te zachowują zasięg klasy bazowej. class Baz { //... int x; float y; }; class Poch : public Baz { //... // bezpośredni dostęp do x, y void pokaz_xyz() { cout << x << ", " << y << ", " << z << ", "; } protected: string z; // inny zasięg niż x, y }; Brak jest jednak niejawnego przeciążenia nazw z klasy bazowej w klasie pochodnej. Funkcje int zmiana(int); oraz int zmiana(); nie tworzą zbioru funkcji przeciążonych – należą one bowiem do osobnych zasięgów. Gdyby tak nie było to z powodu funkcji void ustaw(int a); oraz const int& czytaj() const; sygnalizowany byłby błąd bowiem zarówno w klasie Cx jak i Cy, mają takie same sygnatury.

126 Metody programowania 126 Dziedziczenie, dostęp do składowych klasy bazowej Jawne przeciążenia metod klasy pochodnej metodami klasy bazowej class Baz { public: //... void ustaw(int a) {x=a;} void ustaw(double a) {y=a;} protected : int x; double y; }; class Poch : public Baz { public: //... void ustaw(const string& a) {z=a;} void ustaw(int a) {Baz::ustaw(a);} void ustaw(double a) {Baz::ustaw(a);} protected: string z; };

127 Metody programowania 127 Dziedziczenie, jawne przeciążenia metod klasy pochodnej metodami klasy bazowej Przy pomocy deklaracji używania: class Poch : public Baz { public: //... void ustaw(const string& a) {z=a;} using Baz::ustaw; protected: string z; }; Deklaracja używania wprowadza każdą nazwaną składową klasy bazowej do zasięgu klasy pochodnej.

128 Metody programowania 128 Dziedziczenie, jawne przeciążenia metod klasy pochodnej metodami klasy bazowej //... int main() { Poch ob(5, 3.14, "kot"); //... ob.ustaw(99); ob.ustaw(2.74); ob.ustaw("pies"); ob.pokaz_xyz(); cout<< endl; return 0; } 99, 2.74, pies,

129 Metody programowania 129 Dziedziczenie, dostęp do składowych klasy bazowej Klasa pochodna ma dostęp do składowych prywatnych chronionych innych obiektów własnej klasy: string Poch::fun1(const Poch& arg) { return z + arg.z; } Klasa pochodna ma dostęp do składowych chronionych swojego podobiektu klasy bazowej int Poch::fun2(const Poch& arg) { return x + arg.z.size(); } Klasa pochodna ma dostęp do składowych chronionych klasy bazowej w innych obiektach własnej klasy double Poch::fun3(const Poch& arg) { return b + arg.b; }

130 Metody programowania 130 Dziedziczenie, dostęp do składowych klasy bazowej Klasa pochodna nie ma dostępu do składowych chronionych niezależnego obiektu klasy bazowej double Poch::fun4(const Baz& arg) { return b + arg.b; } //błąd Klasa pochodna ma dostęp do składowych chronionych i prywatnych niezależnego obiektu klasy bazowej jeśli została z nią zaprzyjaźniona class Baz { friend class Poch ; //... }; double Poch::fun4(const Baz& arg) { return b + arg.b; } // teraz OK

131 Metody programowania 131 Dziedziczenie, dostęp do składowych klasy bazowej Wskaźnik do obiektu klasy bazowej pozwala na dostęp do pól i metod jego klasy (zarówno deklarowanych jak i odziedziczonych) niezależnie od faktycznego typu obiektu na jaki wskazuje class Baz { //... public: void f1(){cout << "Baz::f1();\n";} int p1; }; class Poch : public Baz { //... public: void f1(){cout << "Poch::f1();\n";} void f2(){cout << "Poch::f2();\n";} int p1, p2; }; Baz* wsk = new Poch(2, 3.14);

132 Metody programowania 132 Dziedziczenie, dostęp do składowych klasy bazowej jeżeli w klasie bazowej i w klasie pochodnej jest deklarowana metoda niewirtualna mająca taką samą nazwę, to za pomocą wskaźnika będzie wywołana metoda klasy bazowej, wsk->f1(); // wynik: Baz::f1(); jeżeli w klasie bazowej i pochodnej jest deklarowane pole mające ta samą nazwę to wskaźnik będzie odnosił się do pola z klasy bazowej, wsk->p1=5; // dotyczy pola Baz::p1; jeżeli w klasie pochodnej jest deklarowana metoda niewirtulalna (lub pole) o nazwie, która nie występuje w klasie bazowej to za pomocą wskaźnika (do klasy bazowej) nie można wywołać takiej metody lub odnieść się do pola. Baz* wsk = new Poch(2, 3.14); wsk->f2(); // błąd wsk->p2=5; // błąd wsk->Poch::f2(); // błąd wsk->Poch::p2=5; // błąd Poch* wsk_p = new Poch(2, 6.28); wsk_p->f2(); // Ok.

133 Metody programowania 133 Dziedziczenie, dostęp do składowych klasy bazowej Można dokonać jawnego przekształcenia typu z zastosowaniem operatora static_cast static_cast (wsk)->f1(); // Ok. Poch::f1(); static_cast (wsk)->f2(); // Ok. Poch::f2(); static_cast (wsk)->p2=5; // Ok. cout (wsk)->p2 << endl; // Ok. UWAGA – należy jednak kontrolować czy takie przekształcenie się udało.

134 Metody programowania 134 Dziedziczenie, dostęp do składowych klasy bazowej class B { public: int f() const { cout<<"B::f()\n"; return 1; } void f(string& s) { cout <<"B::f(string&) " << s << endl; } void g(){cout<<"B::g() \n";} }; struct P1 : public B { void g() const { cout << "P1::g() \n";} }; struct P2 : public B{ // przedefiniowanie int f() const { cout << "P2::f() \n"; return 2;} }; struct P3 : public B{ // zmiana zwracanego typu void f() const { cout << "P3::f() \n";} }; struct P4 : public B{ // zmiana listy argumentów int f(int i) const {cout << "P4::f(int) \n"; return i;} };

135 Metody programowania 135 Dziedziczenie, dostęp do składowych klasy bazowej int main() { string s("ABC"); P1 a1 ; int x =a1.f(); a1.f(s); a1.g(); B::f() B::f(string&) ABC P1::g() P2 a2 ; x =a2.f(); // a2.f(s); // błąd a2.g(); a2.B::f(s); // Ok. // wersja niewidoczna ze względu na przedefiniowanie funkcji int f() P2::f() B::g() B::f(string&) ABC int B::f() const { cout<<"B::f()\n"; return 1; } void B::f(string& s) { cout <<"B::f(string&) " << s << endl; } void B::g(){cout<<"B::g() \n";} int P2::f() const { cout << "P2::f() \n"; return 2;}

136 Metody programowania 136 Dziedziczenie, dostęp do składowych klasy bazowej //... P3 a3 ; // x =a3.f(); // błąd // a3.f(s); // błąd a3.g(); a3.f(); x =a3.B::f(); a3.B::f(s); // zmiana typu zwracanego przez jedną z funkcji klasy pochodnej // powoduje brak bezpośredniego dostępu do obydwu funkcji klasy bazowej. //... B::g() P3::f() B::f() B::f(string&) ABC int B::f() const { cout<<"B::f()\n"; return 1; } void B::f(string& s) { cout <<"B::f(string&) " << s << endl; } void B::g(){cout<<"B::g() \n";} void P3::f() const { cout << "P3::f() \n";}

137 Metody programowania 137 Dziedziczenie, dostęp do składowych klasy bazowej //... P4 a4 ; // x =a4.f(); // błąd // a4.f(s); // błąd x =a4.f(2); a4.B::f(s); // Ok. a4.g(); // brak dostępu do funkcji spowodowany zmianą listy argumentów formalnych return 0; } int B::f() const { cout<<"B::f()\n"; return 1; } void B::f(string& s) { cout <<"B::f(string&) " << s << endl; } void B::g(){cout<<"B::g() \n";} int P4::f(int i) const {cout << "P4::f(int) \n"; return i;} P4::f(int) B::f(string&) AbC B::g()

138 Metody programowania 138 DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej Klasa pochodna nie dziedziczy po klasie bazowej konstruktorów, destruktora, operatorów przypisania. Każda klasa pochodna musi mieć zdefiniowany własny zbiór tych funkcji. Konstruktor klasy pochodnej może wywoływać tylko konstruktor bezpośredniej klasy bazowej. class C_a { void info() {cout<<"KC_a"<

139 Metody programowania 139 DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej class C_ab : public C_a { void info() { cout<<"KC_ab"<

140 Metody programowania 140 DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej class C_abc : public C_ab { void info() { cout<<"KC_abc"<

141 Metody programowania 141 DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej Konstruktor klasy pochodnej nie powinien bezpośrednio przypisywać wartości polu klasy bazowej, lecz przekazywać wartość do odpowiedniego konstruktora klasy bazowej. //... C_abc(char z1, char z2, char z3) { a=z1; b=z2; c=z3; } // niepoprawnie składowe a i b przed przypisaniem im wartości przekazywanych przez argumenty z1 i z2 będą inicjowane poprzez konstruktory domyślne klas C_a i C_ab. Kolejność wywoływania konstruktorów konstruktor klasy bazowej; jeżeli jest kilka klas bazowych, to konstruktory są wywoływane w kolejności występowania tych klas w liście dziedziczenia, konstruktor obiektu klasy składowej; jeżeli jest kilka klas składowych, to konstruktory są wywoływane w kolejności deklarowania obiektów w definicji klasy, konstruktor klasy pochodnej.

142 Metody programowania 142 DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej int main() { C_abc b3('x', 'y', 'z'); } Destruktory wywoływane są w kolejności odwrotnej niż wywołania konstruktorów KC_a KC_ab KC_abc

143 Metody programowania 143 DZIEDZICZENIE, konstruktor kopiujący i operator przypisania klasy pochodnej class Baz { public: Baz(): txt(0) {} Baz(const char* nps): txt(new string(nps)){} Baz(string* wsk): txt(wsk) {} Baz( const Baz& ob) { txt = ob.txt ? new string(*ob.txt): 0; } Baz& operator= (const Baz& ob){ if (this !=&ob) { delete txt; txt = ob.txt ? new string(*ob.txt) : 0 ; } return * this; } ~Baz(){ delete txt; } protected: string * txt; friend ostream& operator<<(ostream&, const Baz& ); };

144 Metody programowania 144 DZIEDZICZENIE, konstruktor kopiujący i operator przypisania klasy pochodnej ostream& operator<<(ostream& out, const Baz& ob) { out << ob.txt << " : ";// adres ob.txt ? out << *ob.txt : out << "brak" ;// wartość cout << endl ; return out; }

145 Metody programowania 145 DZIEDZICZENIE, konstruktor kopiujący i operator przypisania klasy pochodnej class Poch : public Baz { public: Poch(): p(0){} Poch(const char* nps, int x=0):Baz(nps), p(x){} Poch(string* wsk, int x): Baz(wsk), p(x){} Poch(const Poch& ob):Baz(ob), p(ob.p) {} Poch& operator= (const Poch& ob) { if(this != &ob) { Baz::operator= (ob); p = ob.p; } return *this; } protected: int p; friend ostream& operator<<(ostream& out, const Poch& ob){ return out << "( " << ob.p << " ), " (ob); } };

146 Metody programowania 146 DZIEDZICZENIE, konstruktor kopiujący i operator przypisania klasy pochodnej Rzutowanie w górę pobranie adresu obiektu ( zarówno w postaci wskaźnika, jak i referencji ) i traktowanie go jako adresu typu bazowego. Baz::Baz( const Baz& ob) {/*...*/} Poch::Poch(const Poch& ob):Baz( ob ), p(ob.p) {} Baz::Baz& operator= (const Baz& ob) {/*...*/} Baz::Poch& operator= (const Poch& ob) { //... Baz::operator= ( ob ); //... }

147 Metody programowania 147 DZIEDZICZENIE, konstruktor kopiujący i operator przypisania klasy pochodnej int main() { Poch p1(new string("pies"), 1); Poch p2("szarik", 2), p3(""), p4, p5; cout << p1 << p2 << p3 << p4 << "**********\n"; p3 = p1; p4 = p2; cout << p1 << p2 << p3 << p4 << "**********\n"; p1=p2=p3=p4=p5; cout << p1 << p2 << p3 << p4 << "**********\n"; return 0; } ( 1 ), 0x3e24a0 : pies ( 2 ), 0x3e2528 : szarik ( 0 ), 0x3e2558 : ( 0 ), 0 : brak ********** ( 1 ), 0x3e24a0 : pies ( 2 ), 0x3e2528 : szarik ( 1 ), 0x3e2558 : pies ( 2 ), 0x3e2568 : szarik ********** ( 0 ), 0 : brak **********

148 Metody programowania 148 Dziedziczenie, metody wirtualne Metody wirtualne - pozwalają uzyskiwać indywidualne traktowanie podobnych typów danych, pod warunkiem, że typy te są wyprowadzone z tego samego typu bazowego. Metody klasy są domyślnie niewirtualne. Deklaracja metody wirtualnej wymaga zastosowania słowa kluczowego virtual, jeżeli definicja jest umieszczona na zewnątrz klasy, to słowa virtual nie wolno powtarzać; Klasa wprowadzająca funkcję wirualną musi ją albo definiować albo deklarować jako funkcję czysto wirtualną. class AAA { //... // deklaracja funkcji czysto wirtualnej virtual void wykonaj()=0; }; // to jest abstrakcyjna klasa bazowa

149 Metody programowania 149 Dziedziczenie, metody wirtualne Klasa abstrakcyjna – klasa zawierająca (lub dziedzicząca) co najmniej jedną metodę czysto wirtualną brak możliwości definiowania obiektów tej klasy, obiekt klasy abstrakcyjnej może występować jedynie jako podobiekt klas pochodnych. Jeżeli dla klasy pochodnej jest zdefiniowana wersja metody wirtualnej to zastępuje ona wersję dziedziczoną po klasie bazowej Aby wersja metody wirtualnej dla klasy pochodnej mogła zastępować wersję klasy bazowej musi być spełniony warunek zgodności prototypów tych metod.

150 Metody programowania 150 Dziedziczenie, metody wirtualne class Baz { //... public: virtual void pokaz()const { cout << "Bpv : " ; txt ? cout<<*txt : cout<<"brak"; cout<

151 Metody programowania 151 Dziedziczenie, metody wirtualne //... Baz a1("kot"); Poch a2("pies",6); Baz* wb = &a2; // statyczne wywołanie metody wirtualnej a1.pokaz(); a2.pokaz(); a2.pokaz_nv(); wb->pokaz_nv(); Bpv : kot Ppv : pies ( 6 ) Pnv : ( 6 ) Bnv : pies // dynamiczne wywołanie metody wirtualnej wb->pokaz(); Ppv : pies ( 6 )

152 Metody programowania 152 Dziedziczenie, metody wirtualne Polimorfizm funkcji jest dostępny wówczas, gdy do podtypu klasy pochodnej odnosimy się pośrednio za pomocą wskaźnika albo referencji do obiektu klasy bazowej. Wiązanie wywołania funkcji – połączenie wywołania funkcji z jej ciałem: statyczne ( wczesne ) – dokonywane przed uruchomieniem programu ( przez kompilator lub/i konsolidator ), dynamiczne ( późne ) – dokonywane w trakcie wykonywania programu na podstawie informacji o typie obiektu. Statyczne wywołanie funkcji wirtualnej – jawny wybór wersji metody wirtualnej przy pomocy operatora zasięgu unieważniający mechanizm wirtualny. Rozstrzyganie o wyborze funkcji odbywa się na etapie kompilacji programu.

153 Metody programowania 153 Dziedziczenie, metody wirtualne //... Poch a2("pies",6); Baz* wb = &a2; // aktywny mechanizm wirtualny wb->pokaz(); // unieważniony mechanizm wirtualny wb->Baz::pokaz(); Ppv : pies ( 6 ) Bpv : pies

154 Metody programowania 154 Dziedziczenie, metody wirtualne Wycinanie podobiektu – występuje gdy dokonywane jest rzutowanie w górę na obiekt a nie na wskaźnik czy referencję. void info1(const Baz& arg)const { cout << "info1: "; arg.pokaz(); } void info2(Baz arg)const { cout << "info2: "; arg.pokaz(); } info1(a2); info2(a2); info1: Ppv : pies ( 6 ) info2: Bpv : pies // a2 obcięte

155 Metody programowania 155 Dziedziczenie, wirtualne funkcje wejścia - wyjścia Nie jest możliwe bezpośrednie zdefiniowanie wirtualnych operatorów wejścia czy też wyjścia, gdyż operatory te są już składowymi klasy ostream, ale... class B{ protected: //.... virtual ostream& print(ostream& out) const = 0; friend ostream& operator<<(ostream& out, const B& ob) { return ob.print(out); } }; class P1 : public B { //... ostream& print(ostream& out) const { return out << "P1::print\n"; } }; class P2 : public B { //... ostream& print(ostream& out) const { return out << "P2::print\n"; } };

156 Metody programowania 156 Dziedziczenie, wirtualne funkcje wejścia - wyjścia class P3 : public B { //... ostream& print(ostream& out) const { return out << "P3::print\n"; } }; int main() { P1 o1; P2 o2; P3 o3; cout << o1 << o2 << o3 ; //... }; P1::print P2::print P3::print

157 Metody programowania 157 Dziedziczenie, argumenty domyślne funkcji wirtualnych Argument domyślny funkcji wirtualnej nie jest określany w trakcie wykonywania programu, lecz podczas kompilacji, na podstawie typu obiektu, dla którego ma być wywołana funkcja. class Baz{ //.... public: virtual int funkcja(int arg = 100) { cout << " Baz::funkcja() : " ; return arg; }; class Poch : public Baz { //... public: int funkcja(int arg = 500) { cout << "Poch::funkcja() : " ; return arg; } };

158 Metody programowania 158 Dziedziczenie, argumenty domyślne funkcji wirtualnych int main() { Poch obP; Baz* wsB= &obP; Poch* wsP= &obP; cout << "Za pomoca wskaznika wsB"; cout funkcja() << endl; cout << "Za pomoca wskaznika wsP"; cout funkcja()<< endl; } // wynik: Za pomoca wskaznika wsB Poch::funkcja(): 100 Za pomoca wskaznika wsP Poch::funkcja(): 500

159 Metody programowania 159 Dziedziczenie, destruktory wirtualne Destruktor ( w przeciwieństwie do konstruktorów ) może a niejednokrotnie musi być funkcją wirtualną. class B1 { public: ~B1(){ cout << "~B1()\n"; } }; class B2 { public: virtual ~B2() { cout << "~B2()\n"; } }; class P1 : public B1 { public: ~P1() { cout << "~P1()\n"; } }; class P2 : public B2 { public: ~P2() { cout << "~P2()\n"; } };

160 Metody programowania 160 Dziedziczenie, destruktory wirtualne int main() { B1* wsB1 = new P1; // rzutowanie w górę delete wsB1; cout << "**************\n"; B2* wsB2 = new P2; // rzutowanie w górę delete wsB2; } // wynik:~B1() ************** ~P2() ~B2() Jeżeli w głównej klasie bazowej ( dla danej hierarchii klas ) jest zadeklarowana co najmniej jedna funkcja wirtualna to zaleca się by destruktor był deklarowany również jako wirtualny.

161 Metody programowania 161 Run-time Type Identification ( RTTI ) RTTI – identyfikacja typu podczas wykonywania programu. mechanizm wykorzystywany w sytuacjach gdy typ obiektów może być określony tylko w takcie wykonywania programu, przy czym jest on aktywny dla obiektów klas posiadających co najmniej jedną funkcję wirtualną. Dla argumentów innych typów identyfikacja typu wykonywana jest podczas kompilacji. jest mniej wydajny i mniej bezpieczny niż statyczny system określania typów ( tzn. system sprawdzania zgodności typów podczas kompilacji ). Operator typeid – wskazuje na aktualny typ obiektu, do którego występuje odniesienie za pomocą wskaźnika lub referencji (konieczna jest deklaracja pliku nagłówkowego ).

162 Metody programowania 162 Run-time Type Identification ( RTTI ) int ii=0, *wi =ⅈ cout << typeid(ii).name() << '\nint << typeid(wi).name() << '\n';int * cout << typeid(512).name() << '\n';int cout << typeid(3.14).name() << '\n';double Baz2 *wsB2 = new Poch2; cout << typeid(*wsB2).name() << '\nPoch2 << typeid(wsB2).name() << '\n';Baz2 * cout<< (typeid(ii)==typeid(512))<<'\n' ;1 cout<< (typeid(*wsB2)==typeid(wsB2))<<'\n';0 cout<< typeid(wsB2)==before(typeid(*wsB2)) << '\n';1

163 Metody programowania 163 Run-time Type Identification ( RTTI ) Operator dynamic_cast – umożliwia przekształcanie typów podczas wykonywania programu przekształcenie wskaźnika wskazującego na obiekt klasy we wskaźnik do obiektu innej klasy w ramach tej samej hierarchii klas, przekształcenie l-wartości obiektu klasy w referencję do klasy należącej do tej samej hierarchii klas. Przy niepowodzeniu przekształcenia: do typu wskaźnikowego zwracana jest wartość 0 do typu referencyjnego zwracana jest sytuacja wyjątkowa bad_cast. Rzutowanie w dół – zastosowanie operatora dynamic_cast w celu dokonywania bezpiecznego rzutowania wskaźnika do klasy bazowej na wskaźnik klasy pochodnej. Zaleca się by zawsze sprawdzać wartość uzyskaną w wyniku rzutowania w dół i nie dopuścić do użycia wskaźnika, którego wartością jest 0 ( rzutowanie nie powiodło się ).

164 Metody programowania 164 Run-time Type Identification ( RTTI ) #include using namespace std; class Pracownik { protected: static double zasadn; string name; //... public: Pracownik (const string& a="--"):name(a){} static void nowa_zasadnicza(double a) { zasadn = a; } const string& nazwisko()const {return name;} virtual double pensja()const {return 1.0 * zasadn;} virtual ~Pracownik(){} };

165 Metody programowania 165 Run-time Type Identification ( RTTI ) class Grupa1 : public Pracownik { //... public: Grupa1 (const string& a="--" ):Pracownik(a){} }; class Grupa2 : public Pracownik { //... public: Grupa2 (const string& a="--" ):Pracownik(a){} double pensja()const {return 1.2*zasadn;} }; class Akord : public Pracownik { protected: mutable double _wykonal; //... public: Akord(const string& a="--" ): Pracownik(a),_wykonal(100.0){} void wykonal(double n) {_wykonal=n;} double premia() const {return wykonal/100.0;} };

166 Metody programowania 166 Run-time Type Identification ( RTTI ) // -- glowny.cpp – #include #include "pracownicy.h" void wyplata(const Pracownik* osoba) { cout nazwisko() << " : " ; if( const Akord* wA = dynamic_cast (osoba) ) cout pensja() * wA->premia(); else cout pensja(); cout << endl; } double Pracownik::zasadn = ; const int ilosc=3;

167 Metody programowania 167 Run-time Type Identification ( RTTI ) int main() { Pracownik* team[ilosc]; team[0]=new Grupa1 ("Dyzma"); team[1]=new Grupa2 ("Dolas"); team[2]=new Akord ("Talar"); for(int i=0; iwykonal(300); for(int i=0; i

168 Metody programowania 168 Run-time Type Identification ( RTTI ) //... void wyplata_ref(const Pracownik& osoba) { cout << osoba.nazwisko() << " : " ; try { const Akord& rAk = dynamic_cast (osoba); cout << rAk.pensja() * rAk.premia(); } catch(bad_cast) { cout << osoba.pensja(); } cout << endl; } //... for(int i=0; i

169 Metody programowania 169 Wzorce klas Wzorzec klasy - bazujący na koncepcji sparametryzowanego typu mechanizm automatycznego generowania różnych wersji klas związanych z konkretnym typem danych. Deklaracja wzorca klasy template class ElementZbioru; template class Para; template class Trojka; //template // błąd ! // class Trojka; Parametr typu wzorca - składa się ze słowa kluczowego class lub typename oraz identyfikatora reprezentującego późniejszy typ.

170 Metody programowania 170 Wzorce klas Parametr nietypu wzorca - nie reprezentuje żadnego typu. Zawiera zwykłą deklarację argumentu formalnego. template class Tablica; Definicja wzorca klasy template class para{ T1 _w1; T2 _w2; public: para(){} para( const T1& a1, const T2& a2): _w1(a1), _w2(a2){} T1& w1(){return _w1;} T1& w2(){return _w2;} const T1& w1()const {return _w1;} const T2& w2()const {return _w2;} };

171 Metody programowania 171 Wzorce klas Konkretyzacja wzorca klasy - mechanizm tworzenia przez kompilator właściwego typu danych. int main(){ para p1(-10, 3.14); para p2("Dabrowskiego", 69); cout << p1.w1() << '\t' << p1.w2() << endl; cout << p2.w1() << '\t' << p2.w2() << endl; return 0; } Dabrowskiego 69

172 Metody programowania 172 Wzorce klas, Konkretyzacja wzorca klasy template class stos { public: stos(): pozycja(-1) {} void dodaj(const T1&); const T1& element()const { return pusty()? tab[0] : tab[pozycja]; } void usun() { if (!pusty()) --pozycja; } bool pelny()const{ return pozycja == (roz - 1); } bool pusty()const{ return pozycja < 0; } protected: T1 tab[roz]; int pozycja; }; template void stos ::dodaj(const T1& a1) { if (!pelny()) tab[++pozycja] = a1; }

173 Metody programowania 173 Wzorce klas, Konkretyzacja wzorca klasy int main(){ //... stos s1; s1.dodaj("kota"); s1.dodaj("ma"); s1.dodaj("Ala"); while(!s1.pusty()){ cout << s1.element() << ' '; s1.usun(); } cout << ".\n"; //... } Ala ma kota.

174 Metody programowania 174 Wzorce klas, Konkretyzacja wzorca klasy int main(){ para p1(-1, 3.14); //... stos, 3> s2; s2.dodaj( p1 ); s2.dodaj( para (2, 2.71) ); s2.dodaj( para (3, 12.7) ); while(!s2.pusty()){ cout<< s2.element().w1()<< '\t' << s2.element().w2()<< endl; s2.usun(); } //... }

175 Metody programowania 175 Wzorce klas, Konkretyzacja wzorca klasy int main(){ //... para > p3; p3.w1()="Dane 1"; p3.w2().dodaj(15); p3.w2().dodaj(10); p3.w2().dodaj(5); cout << p3.w1() << " : "; while(!p3.w2().pusty()){ cout << p3.w2().element()<< ", "; p3.w2().usun(); } cout << endl; //... } Dane 1 : 5, 10, 15,

176 Metody programowania 176 Wzorce klas, Konkretyzacja wzorca klasy int main(){ //... para, 3> > p4; p4.w1()="Dane 2"; p4.w2().dodaj( para ('J',23) ); p4.w2().dodaj( para ('B',5) ); p4.w2().dodaj( para ('A',4) ); cout << p4.w1() << " : "; while(!p4.w2().pusty()){ cout << p4.w2().element().w1()<< p4.w2().element().w2()<< ", "; p4.w2().usun(); } cout << endl; //... } Dane 1 : A4, B5, J23,

177 Metody programowania 177 Wzorce klas, Konkretyzacja wzorca klasy Pamiętać o const ! Gdyby zabrakło const : template class stos { //... void dodaj(T1&); const T1& element const(); bool pusty(); //... }; template class stos { public: //... void dodaj(const T1&); const T1& element()const; bool pusty()const; //... }; //... stos s1; stos, 3> s2; s1.dodaj(string("Ala")); // błąd s2.dodaj( para (2, 2.71) ); // błąd p3.w2().dodaj(15); // błąd string ob1("kota"); para p1(-1, 3.14); s1.dodaj(ob1); s2.dodaj( p1 ); W tym przypadku : argumenty aktualne są obiektami stałymi !

178 Metody programowania 178 Wzorce klas, Konkretyzacja wzorca klasy Pamiętać o const ! template class stos { public: //... void dodaj(const T1&); const T1& element()const; bool pusty()const; //... }; stos s1; stos, 3> s2; Gdyby zabrakło const : template class stos { //... void dodaj( const T1&); const T1& element()const; bool pusty(); //... }; cout << s1.element() << ' '; // błąd cout << s2.element().w1() ; // błąd W przypadku stałej metody const T1& element()const { return pusty()? tab[0] : tab[pozycja];} także i metoda pusty() musi być stała Zdefiniowanie metody T1& element() {return pusty()? tab[0] : tab[pozycja];} w tym przypadku nie miałoby sensu.

179 Metody programowania 179 Pola statyczne wzorców klas Pole statyczne wzorca klasy jest również wzorcem. Z każdą skonkretyzowaną wersją klasy jest związany jej własny zbiór pól statycznych ( tych które zadeklarowano ). Definicja pól statycznych musi występować na zewnątrz definicji wzorca klasy. template class A { public: A(): p1( T() ){ s1+=s1; ++s2; } A(const T& a1) : p1(a1) { s1+=s1; ++s2; } void pokaz( )const{ cout<< p1<< ": \t"<< s1<< ", "<< s2<< '\n';} private: T p1; static T s1; static int s2; };

180 Metody programowania 180 Pola statyczne wzorców klas template T A ::s1 =1; template int A ::s2=50; int main() { const A o1; o1.pokaz(); A o2(10); o2.pokaz(); A o3(120); o3.pokaz(); cout("**************\n") ; const A o4; o4.pokaz(); A o5(2.71); o5.pokaz(); A o6(3.14); o6.pokaz(); // A o7; // błąd //... } 0: 2, 51 10: 4, : 8, 53 ************** 0: 2, : 4, : 8, 53

181 Metody programowania 181 Pola statyczne wzorców klas template T A ::s1 =0.5; template int A ::s2=50; int main() { // const A o1; o1.pokaz(); // błąd // A o2(10); o2.pokaz(); // błąd // A o3(120); o3.pokaz(); // błąd cout("**************\n") ; const A o4; o4.pokaz(); A o5(2.71); o5.pokaz(); A o6(3.14); o6.pokaz(); //... } 0: 0.6, : 1.2, : 2.4, 53


Pobierz ppt "Metody programowania 1 Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski."

Podobne prezentacje


Reklamy Google