Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
1
Metody Programowania Wykład
Prowadzący: dr inż. Jacek Piątkowski
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
Funkcje przeciążone 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
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
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. 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
Funkcje przeciążone 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
Funkcje przeciążone 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
Funkcje przeciążone // 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; } // funkcje kandydujące void fun(); void fun(int); void fun(char*, char*); void fun(double, double=3.14); // funkcje żywotne // funkcja najżywotniejsza // argument typu double jest ściśle zgodny z argumentem formalnym
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
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
Funkcje przeciążone // ścisła zgodność:
naturalne a; int b; dni_tygodnia dzien = sr; int t[5]; // ś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
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
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
Funkcje przeciążone //... int i = 5, *wi = &i; 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. const int* wic = &i; void fun(int* const a);
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
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
Funkcje przeciążone. Zgodność do przekształcenia typu
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. Przekształcenia typów zmiennopozycyjnych dowolne przekształcenia typu zmiennopozycyjnego w inny typ zmiennopozycyjny, z wyjątkiem przekształceń będących awansowaniem. Przekształcenia zmiennopozycyjno-całkowite przekształcenia dowolnego typu zmiennopozycyjnego w dowolny typ całkowity lub dowolnego typu całkowitego w dowolny typ zmiennopozycyjny. 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*. Przekształcenia typów logicznych przekształcenia dowolnego typu całkowitego, wyliczeniowego lub wskaźnikowego w typ bool.
18
Klasa 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
Klasa treść klasy 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
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
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 { 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
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
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
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
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
Klasa //... xy ob1; int& x(){return x_;} ob1.x()=10;
const int& x()const {return x_;} //... xy ob1; ob1.x()=10; const xy ob2(20,30,210,297); cout << ob2.x(); xy xy::przeksztalcenie(const xy& r1, const xy& r2) const { xy tmp; /* obliczenia */ return tmp; } xy& xy::przeksztalcenie(xy& r1, const xy& r2){ return r1;
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
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
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: konstruktory i destruktor przeciążone funkcje operatorów operatory przekształcenia typów metody dostępowe metody statyczne metody pozostałe
30
Klasa class xy { //... friend void przesun(xy&, int, int);
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
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); //...
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
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
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
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
Klasa, obiekt klasy 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 przesun(xy& r1, int dx, int dy){ r1.x_ +=dx; r1.y_ +=dy; }
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
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() < B->y() ) przesun(A, B->x(), B->y()); else przesun(A, A.x(), B->y()); }
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
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
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
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
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
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
Klasa, konstruktor 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; } k1 k2 Jeżeli zdefiniowano : k1, k2 k1 k3 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
Klasa, 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: jawne inicjowanie obiektu klasy wartością innego obiektu, //... c4 ob5(ob2), ob6=ob2; przekazanie obiektu klasy jako argumentu funkcji : void fun(c4 arg) {/* ... */} fun(ob3);
47
Klasa, inicjowanie składowa po składowej
przekazanie obiektu klasy jako wyniku wykonania funkcji: //... c4 fun2(const c4 &arg1, const c4 &arg2) { c4 wynik; /* ... */ return wynik; } definicja niepustej kolekcji uporządkowanej : vector< c4 > 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
Klasa, inicjowanie składowa po składowej
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
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
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
Klasa, 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
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
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(); } c7 c4; c7::view(); //... 1 4
54
Klasa, destruktor klasy
void fun() { c5 ob1("kot"), *wsk =0; wsk = new c5(ob1); //... wsk->~c5(); // destruktor wywoływany jawnie }
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
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
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
Klasa, 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
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
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
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
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
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
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<roz && i<ob.roz; ++i) wti[i]=ob.wti[ob.roz-1-i]; }
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
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
Klasa, metody klasy int tab::pobiez(int i) const {
if(i>=0 && i <roz){ return wti[i]; pobrano=i; // modyfikowana wartość pola mimo atrybutu } // const metody pobiez else return -1; } // ... int ti1[] = {1,2,3,4,5}; const tab ob3(ti1, ti1+5); cout << ob3.pobiez(2);
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
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
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
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
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
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
Klasa, pola statyczne Dostęp do pól statycznych klasy
W treści metod nie wymaga użycia operatora dostępu do składowych // ... double rachunek::odsetkiDzienne() { return stopa/365 * kwota; } 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
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
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
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
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
Przeciążanie operatorów
Operatory przeciążalne Operatory nieprzeciążalne + - * / % ^ & | ~ ! , = < > <= >= ++ -- << >> == != && || += -= /= %= ^= &= |= *= <<= >>= [] () -> ->* new new[] delete delete[] :: . .* ?:
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
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
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
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
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
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
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
Przeciążenie operatorów
#include "Lint1.h" int main() { Wyniki: Lint1 ob(1); Lint1* wsi ; cout << +ob; cout << -ob; cout << ~ob; cout << !ob << endl; 0 cout << (++ob); cout << (--ob); cout << (ob++); cout << (ob--); wsi = &ob; cout << wsi; 0x22ff70 } // Analogicznie dla obiektów typu Lint2
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
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
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) 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
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
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
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
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; bool operator==(const Lint2& la, const Lint2& pa) { return la.ii == pa.ii;} // analogicznie dla operatorów (!=), (<), (&&), ..
95
Przeciążanie operatorów
int main() { Wyniki Lint1 ob.(5), aa(2), bb; cout << "operator+\t" << ob+aa; cout << "operator-\t" << ob-aa; cout << "operator*\t" << ob*aa; try { cout << "operator/\t" << ob/aa; } catch(string er) { cerr << er << endl;} cout << ob/bb; { cerr << er << endl; } ERROR ! ...
96
Przeciążanie operatorów
cout << "operator+= \t" << (ob+=aa); try { cout << "operator/= \t" << (ob/=aa); } catch(string er) { cerr << er << endl;} cout <<"operator==\t"<< (ob==aa) <<endl; ... 0 cout <<"operator!=\t"<< (ob!=aa) <<endl; ... 1 cout <<"operator<\t" << (ob<aa) <<endl; ... 0 cout <<"operator&&\t"<< (ob&&aa) <<endl; ... 1 return 0;
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
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<roz; ++i) wti[i] = ob.wti[i]; } return *this; Zaleca się, by w przypadku operatorów przypisania ( +=, -=, *=, ...) , a w szczególności operatora ( = ) kontrolować skutki przypisania obiektu samego do siebie np. A += A czy też A = A .
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<0 || i>= roz) throw string ("indeks poza zakresem\n"); return wti[i]; } const int& operator[](int i)const throw (string){ // .. };
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
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) { rozm+= strlen(nps); strcat(txt, nps);
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
Przeciążanie operatorów
Argumenty i zwracane wartości – zasady ogólne 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
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
Przeciążenie operatorów, argumenty i zwracane wartości
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 Operator Zalecany sposób użycia Wszystkie operatory jednoargumentowe metody = ( ) [ ] –> –>* wyłącznie jako metody += –= /= *= ^= &= |= %= >>= <<= Wszystkie pozostałe operatory dwuargumentowe funkcje nie będące składowymi klasy
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
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
Obsługa sytuacji wyjątkowych
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. 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().
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
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
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; catch ( ... ){ cerr << "Blad nieznany " << endl;}
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
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;
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<<aa/i<< " ; "<< endl; } catch(const blad err) {cout << err;} cout << endl; } catch (...){ cout << "Blad nieznany " << endl; i=-1 wynik : niedozwolony wynik ujemny Bledna wartosc : -5 i=0 wynik : ERROR ! : Dzielenie przez zero ! (x/0) 0 ; i=1 wynik : 5 ; i=2 wynik : powstala reszta z dzielenia Bledna wartosc : 1 i=3 wynik : powstala reszta z dzielenia Bledna wartosc : 2 i=4 wynik : powstala reszta z dzielenia i=5 wynik : 1 ; i=6 wynik : dzielna mniejsza od dzielnika Bledna wartosc :
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
Dziedziczenie 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
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
class Poch : poziom_dostępu klasa_bazowa {};
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
Dziedziczenie Składnia dziedziczenia class Bazowa; class Pochodna1;
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
Dziedziczenie 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; } 5, 3.14 kot, 5, 3.14, kot,
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
Dziedziczenie, dostęp do składowych klasy bazowej
// --Cx.h-- #include <iostream> 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
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
Dziedziczenie, dostęp do składowych klasy bazowej
Wyniki: 4 8 Cx Cy 6 3 12 24 26 ~Cy ~Cx int main() { cout<< sizeof(Cx)<< endl; cout<< sizeof(Cy)<< endl; Cy ob(3); pokaz(ob); ob.ustaw(12); // i2 ob.ustaw_xi(24); // i1 ob.zmiana(); // i1, i2 return 0; }
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
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 { 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
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
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
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
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 ; // ... }; { return b + arg.b; } // teraz OK
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 { void f1(){cout << "Poch::f1();\n";} void f2(){cout << "Poch::f2();\n";} int p1, p2; Baz* wsk = new Poch(2, 3.14);
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
Dziedziczenie, dostęp do składowych klasy bazowej
Można dokonać jawnego przekształcenia typu z zastosowaniem operatora static_cast static_cast<Poch*>(wsk)->f1(); // Ok. Poch::f1(); static_cast<Poch*>(wsk)->f2(); // Ok. Poch::f2(); static_cast<Poch*>(wsk)->p2=5; // Ok. cout << static_cast<Poch*>(wsk)->p2 << endl; // Ok. UWAGA – należy jednak kontrolować czy takie przekształcenie się udało.
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
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
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
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; } P4::f(int) B::f(string&) AbC B::g() 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;}
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"<<endl;} protected: char a; public: C_a(char z1): a(z1) {info();} C_a(): {info();} // ... };
139
DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej
class C_ab : public C_a { void info() { cout<<"KC_ab"<<endl; } protected: char b; public: C_ab(): b('#') {info();} C_ab(char z2): C_a('_'), b(z2){info();} C_ab(char z1, char z2):C_a(z1), b(z2) {info();} // ... };
140
DZIEDZICZENIE, Konstruktory i destruktor klasy pochodnej
class C_abc : public C_ab { void info() { cout<<"KC_abc"<<endl; } protected: char c; public: C_abc(): c('$'){info();} C_abc(char z1, char z2, char z3): C_ab(z1,z2), c(z3){info();} // ... }; //... C_abc(char z1, char z2, char z3): C_a(z1), C_ab(z2), c(z3) {} // błąd a(z1), b(z2), c(z3) {} // błąd
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
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
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
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
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 << " ), " << static_cast<const Baz&>(ob); };
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
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; p1=p2=p3=p4=p5; 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
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
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
Dziedziczenie, metody wirtualne
class Baz { //... public: virtual void pokaz()const { cout << "Bpv : " ; txt ? cout<<*txt : cout<<"brak"; cout<<endl; } void pokaz_nv() const { cout << "Bnv : " ; }; class Poch : public Baz { // ... void pokaz()const{ cout << "Ppv : " ; txt ? cout << *txt : cout << "brak" ; cout << " ( " << p << " )" << endl;} void pokaz_nv(){cout << "Pnv : " << " ( " << p << " )" <<endl;}
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
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
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
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
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 { { return out << "P2::print\n"; }
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
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 { //... int funkcja(int arg = 500) { cout << "Poch::funkcja() : " ; }
158
Dziedziczenie, argumenty domyślne funkcji wirtualnych
int main() { Poch obP; Baz* wsB= &obP; Poch* wsP= &obP; cout << "Za pomoca wskaznika wsB"; cout << wsB->funkcja() << endl; cout << "Za pomoca wskaznika wsP"; cout << wsP->funkcja()<< endl; } // wynik: Za pomoca wskaznika wsB Poch::funkcja(): 100 Za pomoca wskaznika wsP Poch::funkcja(): 500
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 { virtual ~B2() { cout << "~B2()\n"; } class P1 : public B1 { ~P1() { cout << "~P1()\n"; } class P2 : public B2 { ~P2() { cout << "~P2()\n"; }
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
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 <typeinfo>).
162
Run-time Type Identification ( RTTI )
int ii=0, *wi =ⅈ cout << typeid(ii).name() << '\n‘ int << 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() << '\n‘ Poch2 << 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
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
Run-time Type Identification ( RTTI )
#include <string> #include <typeinfo> 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
Run-time Type Identification ( RTTI )
class Grupa1 : public Pracownik { //... public: Grupa1 (const string& a="--"):Pracownik(a){} }; class Grupa2 : public Pracownik { Grupa2 (const string& a="--"):Pracownik(a){} double pensja()const {return 1.2*zasadn;} class Akord : public Pracownik { protected: mutable double _wykonal; Akord(const string& a="--"): Pracownik(a),_wykonal(100.0){} void wykonal(double n) {_wykonal=n;} double premia() const {return wykonal/100.0;}
166
Run-time Type Identification ( RTTI )
// -- glowny.cpp – #include<iostream> #include "pracownicy.h" void wyplata(const Pracownik* osoba) { cout << osoba->nazwisko() << " : " ; if( const Akord* wA = dynamic_cast< const Akord* >(osoba) ) cout << wA->pensja() * wA->premia(); else cout << osoba->pensja(); cout << endl; } double Pracownik::zasadn = ; const int ilosc=3;
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; i<ilosc; ++i) wyplata(team[i]); Pracownik::nowa_zasadnicza(1500); if(typeid(*team[i])==typeid(Akord)) static_cast<Akord*>(team[i])->wykonal(300); return 0; } Dyzma : 1000 Dolas : 1200 Talar : 1000 Dyzma : 1500 Dolas : 1800 Talar : 4500
168
Run-time Type Identification ( RTTI )
//... void wyplata_ref(const Pracownik& osoba) { cout << osoba.nazwisko() << " : " ; try { const Akord& rAk = dynamic_cast< const Akord& >(osoba); cout << rAk.pensja() * rAk.premia(); } catch(bad_cast) { cout << osoba.pensja(); cout << endl; for(int i=0; i<ilosc; ++i) wyplata_ref(*team[i]); Dyzma : 1500 Dolas : 1800 Talar : 4500
169
Wzorce klas Wzorzec klasy - bazujący na koncepcji sparametryzowanego typu mechanizm automatycznego generowania różnych wersji klas związanych z konkretnym typem danych. Parametr typu wzorca - składa się ze słowa kluczowego class lub typename oraz identyfikatora reprezentującego późniejszy typ. Deklaracja wzorca klasy template < typename T > class ElementZbioru; template < class T1, class T2 > class Para; template < typename T1, class T2, class T3 > class Trojka; //template < typename T1, T2, T3 > // błąd ! // class Trojka;
170
Wzorce klas Parametr nietypu wzorca - nie reprezentuje żadnego typu.
Zawiera zwykłą deklarację argumentu formalnego. template < typename Typ, unsigned rozmiar > class Tablica; Definicja wzorca klasy template <class T1, class T2> 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
Wzorce klas -10 3.14 Dabrowskiego 69
Konkretyzacja wzorca klasy - mechanizm tworzenia przez kompilator właściwego typu danych . int main(){ para <int, double> p1(-10, 3.14); para <string, unsigned> p2("Dabrowskiego", 69); cout << p1.w1() << '\t' << p1.w2() << endl; cout << p2.w1() << '\t' << p2.w2() << endl; return 0; } Dabrowskiego 69
172
Wzorce klas, Konkretyzacja wzorca klasy
template <typename T1, int roz > 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 <typename T1, int roz> void stos<T1, roz>::dodaj(const T1& a1) { if (!pelny()) tab[++pozycja] = a1; }
173
Wzorce klas, Konkretyzacja wzorca klasy
int main(){ //... stos < string, 3 > s1; s1.dodaj("kota"); s1.dodaj("ma"); s1.dodaj("Ala"); while(!s1.pusty()){ cout << s1.element() << ' '; s1.usun(); } cout << ".\n"; Ala ma kota .
174
Wzorce klas, Konkretyzacja wzorca klasy
int main(){ para <int, double> p1(-1, 3.14); //... stos < para<int, double>, 3> s2; s2.dodaj( p1 ); s2.dodaj( para<int, double>(2, 2.71) ); s2.dodaj( para<int, double>(3, 12.7) ); while(!s2.pusty()){ cout<< s2.element().w1()<< '\t' << s2.element().w2()<< endl; s2.usun(); }
175
Wzorce klas, Konkretyzacja wzorca klasy
int main(){ //... para< string, stos<int,3> > 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
Wzorce klas, Konkretyzacja wzorca klasy
int main(){ //... para< string, stos< para<char,int>, 3> > p4; p4.w1()="Dane 2"; p4.w2().dodaj( para<char,int>('J',23) ); p4.w2().dodaj( para<char,int>('B',5) ); p4.w2().dodaj( para<char,int>('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
Wzorce klas, Konkretyzacja wzorca klasy
Pamiętać o const ! template <typename T1, int roz > class stos { public: //... void dodaj(const T1&); const T1& element()const; bool pusty()const; }; stos <string, 3> s1; stos <para<int, double>, 3> s2; Gdyby zabrakło const : template <typename T1,int roz> class stos { //... void dodaj(T1&); const T1& element const(); bool pusty(); }; string ob1("kota"); para <int, double> p1(-1, 3.14); s1.dodaj(ob1); s2.dodaj( p1 ); W tym przypadku : argumenty aktualne są obiektami stałymi ! s1.dodaj(string("Ala")); // błąd s2.dodaj( para<int, double>(2, 2.71) ); // błąd p3.w2().dodaj(15); // błąd
178
Wzorce klas, Konkretyzacja wzorca klasy
Pamiętać o const ! template <typename T1, int roz > class stos { public: //... void dodaj(const T1&); const T1& element()const; bool pusty()const; }; stos < string, 3> s1; stos < para<int, double>, 3> s2; Gdyby zabrakło const : template <typename T1,int roz> 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
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 T> 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
Pola statyczne wzorców klas
template<typename T> T A<T>::s1 =1; int A<T>::s2=50; int main() { const A<int> o1; o1.pokaz(); A<int> o2(10); o2.pokaz(); A<int> o3(120); o3.pokaz(); cout("**************\n") ; const A<double> o4; o4.pokaz(); A<double> o5(2.71); o5.pokaz(); A<double> o6(3.14); o6.pokaz(); // A<string> o7; // błąd //... } 0: 2, 51 10: 4, 52 120: 8, 53 ************** 2.71: 4, 52 3.14: 8, 53
181
Pola statyczne wzorców klas
template<typename T> T A<T>::s1 =0.5; int A<T>::s2=50; int main() { // const A<int> o1; o1.pokaz(); // błąd // A<int> o2(10); o2.pokaz(); // błąd // A<int> o3(120); o3.pokaz(); // błąd cout("**************\n") ; const A<double> o4; o4.pokaz(); A<double> o5(2.71); o5.pokaz(); A<double> o6(3.14); o6.pokaz(); //... } 0: 0.6, 51 2.71: 1.2, 52 3.14: 2.4, 53
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.