Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski Metody programowania 1 Literatura • • • • • • • S. B. Lippman, J.

Download Report

Transcript Metody Programowania Wykład Prowadzący: dr inż. Jacek Piątkowski Metody programowania 1 Literatura • • • • • • • S. B. Lippman, J.

Metody Programowania
Wykład
Prowadzący: dr inż. Jacek Piątkowski
Metody programowania 1
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, http://helion.pl/online/thinking/index.html
J. Walter, D. Kalev, M. J. Tobler, P. Sainth, A. Kossoroukov, S. Roberts , C++ w
zadanich, Robomatic Wrocław,
C++ bez cholesterolu, http://www.kis.p.lodz.pl/~sektor/cbx/
Metody programowania 2
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*);
Metody programowania 3
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ę
Metody programowania 4
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.
Metody programowania 5
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.
Metody programowania 6
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.
Metody programowania 7
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
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
Metody programowania 8
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.
Metody programowania 9
Funkcje przeciążone
typedef unsigned int naturalne;
enum dni_tygodnia {po=1, wt, sr, cz, pi, so, ni};
void
void
void
void
void
void
void
fun(char);
fun(double);
fun(const char*);
fun(naturalne);
fun(int&);
fun(int*);
fun(dni_tygodnia);
// l-wartości:
naturalne a;
int
b;
dni_tygodnia dzien = sr;
int t[5];
Metody programowania 10
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);
naturalne a;
int b;
dni_tygodnia dzien = sr;
int t[5];
// ś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
Metody programowania 11
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);
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);
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);
wsk_fun = max;
oblicz(2, 4, 6, 8, wsk_fun);
}
//dla min
//dla max
Metody programowania 13
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);
Metody programowania 14
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.
Metody programowania 15
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.
Metody programowania 16
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.
Metody programowania 17
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.
Metody programowania 18
Klasa
Definicja klasy
składa się z nagłówka klasy oraz treści klasy, która wyznacza oddzielny zasięg.
class xy
{
public :
// nagłówek klasy
//
//
//
//
xy():x_(0),y_(0){}
void fun1(int, int);
int& x(){return _x;}
const int& x()const {return _x;}
private :
//
//
void zerowanie();
//
int x_, y_ ;
//
};
specyfikator dostępu
publiczny interfejs klasy
konstruktor
metody
specyfikator dostępu
prywatna implementacja
metoda
pola ( przechowujące dane )
treść klasy
•
Metody programowania 19
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.
Metody programowania 20
Klasa
Deklaracja klasy składa się z nagłówka klasy i średnika
class C_pkt;
//...
class C_a {
int x; double y;
};
class C_b {
int x; double y;
};
class C_c;
//...
C_a ob1, ob2;
C_b ob3;
ob1 = ob2;
ob1 = ob3;
C_c ob4;
C_c* ob5;
// deklaracja
// definicja
// definicja
// deklaracja
// błąd – dwa różne obiekty
// błąd – brak definicji klasy;
// OK.
Metody programowania 21
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){}
void f3(C_c*){}
void f4(C_c&){}
// błąd
Metody programowania 22
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
Metody programowania 23
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.
Metody programowania 24
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;}
};
Metody programowania 25
Klasa
//...
xy ob1;
ob1.x()=10;
int& x(){return x_;}
const xy ob2(20,30,210,297);
const int& x()const {return x_;}
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){
/* obliczenia */
return r1;
}
Metody programowania 26
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.
Metody programowania 27
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 ).
Metody programowania 28
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
Metody programowania 29
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;
}
Metody programowania 30
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 ;
//...
}
Metody programowania 31
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;
Metody programowania 32
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;
Metody programowania 33
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.
Metody programowania 34
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&);
Metody programowania 35
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;
}
Metody programowania 36
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]);
cout << p1 ;
int i = p1.x_;
int j = tp[0].x();
fun(tp[1], p1);
fun(tp[1], &p1);
fun(tp, &p1);
fun(*tp, &p1);
// równoważne: fun(p1, tp+1);
// błąd
// błąd
// błąd
}
Metody programowania 37
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());
}
Metody programowania 38
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 definiowanych konstruktorów musi mieć jednoznaczną listę argumentów formalnych.
Metody programowania 39
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.
Metody programowania 40
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) {}
};
Metody programowania 41
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
Metody programowania 42
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
}
Metody programowania 43
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"; }
};
Metody programowania 44
Klasa, konstruktor
int main(){
Jeżeli zdefiniowano :
Jeżeli zdefiniowano :
k1, k2
k1, k2, k3
//...
c4 ob1;
c4 ob2("osa");
c4 ob3("kot", 4);
c4 ob2_1 = c4("osa");
c4 ob3_1 = c4("kot",4);
k1
k1
k1
k1
k1
k1
k3
k1
k3
k1
c4 ob4(7); // sposób zalecany
c4 ob4_1 = c4(7);
c4 ob4_2 = 7;
k2
k2
k2
k2
k2
k2
c4 ob5();
//...
// to nie jest definicja obiektu klasy c4
// jest to deklaracja funkcji zwracającej wartość typu c4
}
Metody programowania 45
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);
Metody programowania 46
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< 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,
Metody programowania 47
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.
Metody programowania 48
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;}
};
Metody programowania 49
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) {}
/...
};
Metody programowania 50
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(){}
};
Metody programowania 51
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--;}
};
Metody programowania 52
Klasa, destruktor klasy
int c7::lc=0;
int main(){
if(c7::lc) {
c7 c1; c7::view();
}
else c7::view();
0
{
c7 c2; c7::view();
if(c7::lc)
{ c7 c3[3]; c7::view(); }
else c7::view();
1
4
}
c7 c4;
c7::view();
//...
1
}
Metody programowania 53
Klasa, destruktor klasy
void fun()
{
c5 ob1("kot"), *wsk =0;
wsk = new c5(ob1);
//...
wsk->~c5(); // destruktor wywoływany jawnie
//...
}
Metody programowania 54
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_;}
Metody programowania 55
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);
Metody programowania 56
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;
}
Metody programowania 57
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.
Metody programowania 58
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;
}
Metody programowania 59
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 :
// ...
};
Metody programowania 60
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_; }
Metody programowania 61
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_ ;
//..
};
Metody programowania 62
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.
Metody programowania 63
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];
}
Metody programowania 64
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
i = p1.policz(2);
// wywołana
policz(int)const
policz(int)
Metody programowania 65
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;
// ...
};
Metody programowania 66
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);
Metody programowania 67
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 {/*...*/}
Metody programowania 68
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:
// ...
};
Metody programowania 69
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(){
/* ... */
}
Metody programowania 70
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.
Metody programowania 71
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
// ...
};
Metody programowania 72
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);
int fun2(int i=zm_stat);
int fun3(int i=::zm);
//
//
//
//
//
//
błąd nie określony
obiekt zawierający pole zm
OK. nie jest wymagane istnienie
obiektu zawierające pole zm_stat
OK.
zm z zasięgu globalnego
};
Metody programowania 73
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;
// ...
}
Metody programowania 74
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.
Metody programowania 75
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";
}
Metody programowania 76
Przeciążanie operatorów
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
operator@ ( lista_argumentów_formalnych )
gdzie @ reprezentuje przeciążany operator
Metody programowania 77
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 ); }
Metody programowania 78
Przeciążanie operatorów
Operatory przeciążalne
+
-
*
/
%
^
&
|
~
!
,
=
<
>
<=
>=
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
new[]
delete
delete[]
Operatory nieprzeciążalne
::
.
.*
?:
Metody programowania 79
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.
Metody programowania 80
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
+
-
*
&
Metody programowania 81
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);
Metody programowania 82
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;
//
const Lint1 operator-()const;
//
const Lint1 operator~()const;
//
int operator!() const;
//
Lint1* operator&();
//
const Lint1& operator++();
//
const Lint1 operator++(int);
//
//...
};
plus jednoargumentowy
minus jednoargumentowy
negacja bitowa
negacja logiczna
pobranie adresu
inkrementacja przedrostkowa
inkrementacja przyrostkowa
Metody programowania 83
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);
//...
};
Metody programowania 84
Przeciążanie operatorów,
// *** Lint1.cpp ***
(metody)
#include "Lint1.h"
const Lint1& Lint1::operator+()const
const Lint1 Lint1::operator-()const
const Lint1 Lint1::operator~()const
int
Lint1::operator!()const
Lint1*
Lint1::operator&()
//przedrostkowy
const Lint1& Lint1::operator++()
//przyrostkowy
const Lint1 Lint1::operator++(int){
Lint1 poprzednia(ii);
ii++;
return poprzednia;
}
operatory jednoargumentowe
{return
{return
{return
{return
{return
*this;}
Lint1(-ii); }
Lint1(~ii); }
int(!ii);}
this;}
{ii++; return *this;}
Metody programowania 85
Przeciążanie operatorów,
// *** Lint2.cpp ***
#include "Lint2.h"
operatory jednoargumentowe
( funkcje zaprzyjaźnione )
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,
Lint2 poprzednia(a.ii);
a.ii++;
return poprzednia;
}
int) {
Metody programowania 86
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
Metody programowania 87
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){...}
Metody programowania 88
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&
Lint1&
bool
bool
bool
bool
};
operator+=
operator/=
operator==
operator!=
operator<
operator&&
(const
(const
(const
(const
(const
(const
Lint1&
Lint1&
Lint1&
Lint1&
Lint1&
Lint1&
pa);
pa) throw(string);
pa) const;
pa) const;
pa) const;
pa) const;
Metody programowania 89
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
friend
friend
friend
friend
friend
};
Lint2& operator+=(Lint2& la, const Lint2& pa);
Lint2& operator/=(Lint2& la, const Lint2& pa)
throw(string);
bool
bool
bool
bool
operator==(const Lint2& la, const Lint2& pa);
operator!=(const Lint2& la, const Lint2& pa);
operator<(const Lint2& la, const Lint2& pa);
operator&&(const Lint2& la, const Lint2r& pa);
Metody programowania 90
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;
}
Metody programowania 91
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;}
Metody programowania 92
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);
}
Metody programowania 93
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 (!=), (<), (&&), ..
Metody programowania 94
Przeciążanie operatorów
int main() {
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;}
try {
cout << ob/bb;
}
catch(string er)
{ cerr << er << endl; }
Wyniki
... 7
... 3
... 10
... 2
ERROR ! ...
Metody programowania 95
Przeciążanie operatorów
cout << "operator+= \t" << (ob+=aa);
try {
cout << "operator/= \t" << (ob/=aa);
}
catch(string er)
{ cerr << er << endl;}
... 7
cout
cout
cout
cout
...
...
...
...
<<"operator==\t"<<
<<"operator!=\t"<<
<<"operator<\t" <<
<<"operator&&\t"<<
(ob==aa)
(ob!=aa)
(ob<aa)
(ob&&aa)
<<endl;
<<endl;
<<endl;
<<endl;
... 3
0
1
0
1
return 0;
}
Metody programowania 96
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;
// ..
};
Metody programowania 97
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 .
Metody programowania 98
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){
if(i<0 || i>= roz) throw string ("indeks poza zakresem\n");
return wti[i];
}
// ..
};
Metody programowania 99
Przeciążanie operatorów
Operatory przeciążone
class Napis {
public:
//...
Napis& operator += (const Napis&);
Napis& operator += (const char*);
//...
private:
int rozm;
char* txt;
};
Metody programowania 100
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;
}
Metody programowania 101
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"
Metody programowania 102
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łą.
Metody programowania 103
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ć.
Metody programowania 104
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
Operator
Wszystkie operatory jednoargumentowe
= ( ) [ ] –> –>*
+= –= /= *= ^=
&= |= %= >>= <<=
Wszystkie pozostałe operatory dwuargumentowe
Zalecany sposób użycia
metody
wyłącznie jako metody
metody
funkcje nie będące składowymi
klasy
Metody programowania 105
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.
Metody programowania 106
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;
}
Metody programowania 107
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().
Metody programowania 108
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)
Metody programowania 109
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);
}
Metody programowania 110
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;}
Metody programowania 111
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 ;}
};
Metody programowania 112
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);
}
}
Metody programowania 113
Obsługa sytuacji wyjątkowych
int main(){
i=-1 wynik : niedozwolony wynik ujemny
Bledna wartosc : -5
Lint1 aa(5), bb, cc;
//...
i=0 wynik : ERROR ! : Dzielenie przez zero ! (x/0)
try{
0 ;
for(int i=-1; i < 7; ++i){
i=1" wynik
5 ;
cout <<"i=" << i <<
" << :"wynik
: " ;
try
i=2 wynik : powstala reszta z dzielenia
{ cout<<aa/i<< "Bledna
; "<<wartosc
endl; :
} 1
catch(const blad err)
{cout << err;} i=3 wynik : powstala reszta z dzielenia
Bledna wartosc : 2
cout << endl;
}
i=4 wynik : powstala reszta z dzielenia
Bledna wartosc : 1
}
catch (...){
i=5 wynik : 1 ;
cout << "Blad nieznany " << endl;
i=6 wynik : dzielna mniejsza od dzielnika
}
Bledna wartosc : 0.833333
//...
}
Metody programowania 114
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 :-)";}
Metody programowania 115
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.
Metody programowania 116
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.
Metody programowania 117
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
Poziom dostępu:
• public
• protected
• private
klasa_bazowa {};
( dziedziczenie typu )
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.
Metody programowania 118
Dziedziczenie
class
class
class
class
Bazowa;
Pochodna1;
Pochodna2;
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;
};
Metody programowania 119
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”);
5, 3.14
ob.pokaz_xy() ; cout<< endl;
kot,
ob.pokaz_z() ; cout<< endl;
5, 3.14, kot,
ob.pokaz_xyz(); cout<< endl;
return 0;
}
Metody programowania 120
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.
Metody programowania 121
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;}
};
Metody programowania 122
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;
cout << ob.czytaj()
<< endl;
}
// i1
// i2
Metody programowania 123
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);
pokaz(ob);
// i2
ob.ustaw_xi(24);
pokaz(ob);
// i1
ob.zmiana();
pokaz(ob);
// i1, i2
return 0;
}
Wyniki:
4
8
Cx
Cy
6
3
6
12
24
12
26
26
~Cy
~Cx
Metody programowania 124
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.
Metody programowania 125
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;
};
Metody programowania 126
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.
Metody programowania 127
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,
Metody programowania 128
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; }
Metody programowania 129
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
Metody programowania 130
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);
Metody programowania 131
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();
•
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;
•
// wynik:
// 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.
Metody programowania 132
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();
static_cast<Poch*>(wsk)->f2();
// Ok. Poch::f1();
// 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.
Metody programowania 133
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;}
};
Metody programowania 134
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);
a2.g();
a2.B::f(s);
P2::f()
B::g()
B::f(string&) ABC
// błąd
// Ok.
// wersja niewidoczna ze względu na przedefiniowanie funkcji int f()
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;}
Metody programowania 135
Dziedziczenie, dostęp do składowych klasy
bazowej
//...
P3 a3 ;
// x =a3.f(); // błąd
// a3.f(s);
// błąd
a3.g();
B::g()
a3.f();
P3::f()
B::f()
x =a3.B::f();
B::f(string&) ABC
a3.B::f(s);
// zmiana typu zwracanego przez jedną z funkcji klasy pochodnej
// powoduje brak bezpośredniego dostępu do obydwu funkcji klasy bazowej.
//...
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";}
Metody programowania 136
Dziedziczenie, dostęp do składowych klasy
bazowej
//...
P4 a4 ;
// x =a4.f();
// a4.f(s);
x =a4.f(2);
a4.B::f(s);
a4.g();
// błąd
// błąd
// Ok.
P4::f(int)
B::f(string&) AbC
B::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;}
Metody programowania 137
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(): a('@') {info();}
// ...
};
Metody programowania 138
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();}
// ...
};
Metody programowania 139
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) {}
//...
C_abc(char z1, char z2, char z3):
a(z1), b(z2), c(z3) {}
// błąd
// błąd
Metody programowania 140
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.
Metody programowania 141
DZIEDZICZENIE, Konstruktory i destruktor klasy
pochodnej
int main() {
C_abc b3('x', 'y', 'z');
}
KC_a
KC_ab
KC_abc
Destruktory wywoływane są w kolejności odwrotnej niż wywołania konstruktorów
Metody programowania 142
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& );
};
Metody programowania 143
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;
}
Metody programowania 144
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);
}
};
Metody programowania 145
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 );
//...
}
Metody programowania 146
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";
( 1 ), 0x3e24a0 : pies
( 2 ), 0x3e2528 : szarik
( 0 ), 0x3e2558 :
( 0 ), 0 : brak
**********
( 1 ), 0x3e24a0
( 2 ), 0x3e2528
( 1 ), 0x3e2558
( 2 ), 0x3e2568
**********
( 0 ), 0 :
( 0 ), 0 :
( 0 ), 0 :
( 0 ), 0 :
**********
:
:
:
:
pies
szarik
pies
szarik
brak
brak
brak
brak
return 0;
}
Metody programowania 147
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
Metody programowania 148
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.
Metody programowania 149
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 : " ;
txt ? cout<<*txt : cout<<"brak"; cout<<endl;
}
};
class Poch : public Baz {
// ...
public:
void pokaz()const{ cout << "Ppv : " ;
txt ? cout << *txt : cout << "brak" ;
cout << " ( " << p << " )" << endl;}
void pokaz_nv(){cout << "Pnv : " << " ( " << p << " )" <<endl;}
};
Metody programowania 150
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 )
Metody programowania 151
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.
Metody programowania 152
Dziedziczenie, metody wirtualne
//...
Poch a2("pies" ,6);
Baz* wb = &a2;
// aktywny mechanizm wirtualny
wb->pokaz();
Ppv : pies ( 6 )
// unieważniony mechanizm wirtualny
wb->Baz::pokaz();
Bpv : pies
Metody programowania 153
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
Metody programowania 154
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"; }
};
Metody programowania 155
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
Metody programowania 156
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;
}
};
Metody programowania 157
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
Metody programowania 158
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"; }
};
Metody programowania 159
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.
Metody programowania 160
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>).
Metody programowania 161
Run-time Type Identification ( RTTI )
int ii=0, *wi =&ii;
cout << typeid(ii).name() << '\n‘
<< typeid(wi).name() << '\n';
cout << typeid(512).name() << '\n';
cout << typeid(3.14).name() << '\n';
int
int *
int
double
Baz2 *wsB2 = new Poch2;
cout << typeid(*wsB2).name() << '\n‘
<< typeid(wsB2).name() << '\n';
Poch2
Baz2 *
cout<< (typeid(ii)==typeid(512))<<'\n' ;
cout<< (typeid(*wsB2)==typeid(wsB2))<<'\n';
cout<< typeid(wsB2)==before(typeid(*wsB2)) << '\n';
1
0
1
Metody programowania 162
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ę ).
Metody programowania 163
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(){}
};
Metody programowania 164
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;}
};
Metody programowania 165
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 = 1000.0;
const int ilosc=3;
Metody programowania 166
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]);
Dyzma : 1000
Dolas : 1200
Talar : 1000
Pracownik::nowa_zasadnicza(1500);
for(int i=0; i<ilosc; ++i)
if(typeid(*team[i])==typeid(Akord))
static_cast<Akord*>(team[i])->wykonal(300);
for(int i=0; i<ilosc; ++i)
wyplata(team[i]);
return 0;
Dyzma : 1500
Dolas : 1800
Talar : 4500
}
Metody programowania 167
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)
Dyzma : 1500
wyplata_ref(*team[i]);
Dolas : 1800
Talar : 4500
Metody programowania 168
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;
Metody programowania 169
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;}
};
Metody programowania
170
Wzorce klas
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;
}
-10
3.14
Dabrowskiego
69
Metody programowania 171
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;
}
Metody programowania 172
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 .
//...
}
Metody programowania 173
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();
3
12.7
}
2
2.71
//...
}
-1
3.14
Metody programowania 174
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();
}
Dane 1 : 5, 10, 15,
cout << endl;
//...
}
Metody programowania 175
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,
//...
}
Metody programowania 176
Wzorce klas,
 Pamiętać o const !
Gdyby zabrakło const :
template <typename T1,int roz>
class stos {
//...
void dodaj(T1&);
const T1& element const();
bool pusty();
//...
};
Konkretyzacja wzorca klasy
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;
string ob1("kota");
para <int, double> p1(-1, 3.14);
s1.dodaj(ob1);
s2.dodaj( p1 );
s1.dodaj(string("Ala"));
s2.dodaj( para<int, double>(2, 2.71) );
p3.w2().dodaj(15);
W tym przypadku :
// błąd
// błąd
// błąd
argumenty aktualne
są obiektami
stałymi !
Metody programowania 177
Wzorce klas,
 Pamiętać o const !
Gdyby zabrakło const :
template <typename T1,int roz>
class stos {
//...
void dodaj( const T1&);
const T1& element()const;
bool pusty();
//...
};
cout << s1.element() << ' ';
cout << s2.element().w1() ;
Konkretyzacja wzorca klasy
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;
// błąd
// 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.
Metody programowania 178
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;
};
Metody programowania 179
Pola statyczne wzorców klas
template<typename T>
T A<T>::s1 =1;
template<typename T>
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;
0:
10:
120:
2, 51
4, 52
8, 53
**************
0:
2.71:
3.14:
2, 51
4, 52
8, 53
// błąd
//...
}
Metody programowania 180
Pola statyczne wzorców klas
template<typename T>
T A<T>::s1 =0.5;
template<typename T>
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();
// błąd
// błąd
// błąd
0:
2.71:
3.14:
0.6, 51
1.2, 52
2.4, 53
//...
}
Metody programowania 181