Tomasz Marks - Wydział MiNI PW
-1-
Programowanie Obiektowe
(j
ę
zyk C++)
Wykład 9.
Tomasz Marks - Wydział MiNI PW
-2-
OBSŁUGA
WYJ
Ą
TKÓW
Tomasz Marks - Wydział MiNI PW
-3-
Przeciwdziałanie bł
ę
dom wykonania programu (1)
double& Vector :: operator [ ] ( int i )
{
return V[ i ];
// operacja niebezpieczna !!!
}
double& Vector :: operator [ ] ( int i )
{
if ( i >= 0 && i < Size ) return V[ i ];
DisplayMessage( "komunikat o bł
ę
dzie" );
// fukcja systemowa lub własna
exit(1);
// funkcja standardowa
}
double& Vector :: operator [ ] ( int i )
{
assert ( i >= 0 && i < Size )
// makro assert
return V[ i ];
}
double& Vector :: operator [ ] ( int i )
{
if ( i < 0 || i >= Size ) throw xcp_scope( i ); // odrzucenie wyj
ą
tku
return V[ i ];
}
Tomasz Marks - Wydział MiNI PW
-4-
Przeciwdziałanie bł
ę
dom wykonania programu (2)
// vector.h
struct xcp_memory { };
// zewn
ę
trzna (wzgl. Vector) klasa pomocnicza
class Vector
{
int Size, Nr;
double *V;
static int DefSize, VCounter;
void Init ( int = 0, double = 0, double * = 0 );
public:
Vector ( );
Vector ( int size, double val = 0 );
Vector ( int size, double arr[ ] );
……………………………….
struct xcp_scope
// wewn
ę
trzna (w Vector) klasa pomocnicza
{
int index;
xcp_scope ( int );
};
……………………………….
};
Tomasz Marks - Wydział MiNI PW
-5-
Przeciwdziałanie bł
ę
dom wykonania programu (3)
// vector.cpp
// implementacja konstruktora klasy xcp_scope
Vector :: xcp_scope :: xcp_scope ( int i ) : index ( i ) { }
// metoda prywatna (wersja oryginalna)
void Vector :: Init ( int s, double val, double tab[ ] )
{
Nr = ++VCounter;
if ( s <= 0 ) s = DefSize;
V = new double [ s ];
if ( ! V ) { Size = 0; return; }
Size = s;
for ( s = 0; s < Size; ++s )
V [ s ] = tab ? tab [ s ] : val;
}
Tomasz Marks - Wydział MiNI PW
-6-
Przeciwdziałanie bł
ę
dom wykonania programu (4)
// metoda prywatna (wersja zmodyfikowana)
void Vector :: Init ( int s, double val, double tab[ ] )
{
if ( VCounter > 7 ) throw "Za duzo wektorow!";
Nr = ++VCounter;
if ( s < 0 ) throw "Blad konstrukcji!";
if ( s <= 0 ) s = DefSize;
try
{
V = new double [ s ];
}
catch ( … ) { throw xcp_memory ( ); }
if ( ! V ) throw xcp_memory ( );
Size = s;
for ( s = 0; s < Size; ++s )
V [ s ] = tab ? tab [ s ] : val;
}
Tomasz Marks - Wydział MiNI PW
-7-
Przeciwdziałanie bł
ę
dom wykonania programu (5)
#include "vector.h"
void main ( )
{
Vector w( 4 );
for ( int i = -1; i < 7; i++ )
try {
cout << endl << "i = " << i << " ";
Vector z( i );
w[ i ] = i;
cout << "w[" << i << "] = " << w[ i ];
}
catch ( const char * str )
{ cout << str; }
catch ( xcp_memory )
{ cout << "Brak pamieci"; }
catch ( Vector :: xcp_scope ex )
{ cout << "Odrzucono indeks " << ex.index; }
}
Tomasz Marks - Wydział MiNI PW
-8-
Przeciwdziałanie bł
ę
dom wykonania programu (6)
Program wy
ś
wietli:
i = -1
Blad konstrukcji!
i = 0
w[ 0 ] = 0
i = 1
w[ 1 ] = 1
i = 2
w[ 2 ] = 2
i = 3
w[ 3 ] = 3
i = 4
Odrzucono indeks 4
i = 5
Odrzucono indeks 5
i = 6
Za duzo wektorow!
Tomasz Marks - Wydział MiNI PW
-9-
Obsługa wyj
ą
tków [1]
1.
Składnia instrukcji try-catch:
try
// pocz
ą
tek bloku try
{
…...............
……………
……………
}
// koniec bloku try
catch ( ….. ) { ……… }
// 1. blok catch
catch ( ….. ) { ……… }
// 2. blok catch
.…………………………..
catch ( ….. ) { ……… }
// ostatni blok catch
blok try wraz ze wszystkimi nast
ę
puj
ą
cymi po nim blokami
catch stanowi składniowo jedn
ą
cało
ść
, któr
ą
nazywamy
instukcj
ą
try-catch
Tomasz Marks - Wydział MiNI PW
-10-
Obsługa wyj
ą
tków [2]
2. Bloki catch s
ą
nazywane procedurami obsługi wyj
ą
tków.
3. Ka
ż
da procedura obsługi wyj
ą
tków ma okre
ś
lony typ sytuacji
wyj
ą
tkowej, która j
ą
aktywizuje.
4. Zgłoszenie sytuacji wyj
ą
tkowej (throw) podczas wykonywania
bloku try skutkuje przekazaniem pewnego obiektu do odpowiedniej
procedury obsługi.
5. Dopasowanie wyra
ż
enia throw do procedury obsługi polega na
porównaniu typu warto
ś
ci wyra
ż
enia throw z typami ( parametrów )
okre
ś
lonymi dla kolejnych procedur obsługi.
Tomasz Marks - Wydział MiNI PW
-11-
Obsługa wyj
ą
tków [3]
6. Dopasowanie uznane b
ę
dzie za pomy
ś
lne, je
ż
eli spełniony zostanie
jeden z trzech warunków:
(i) oba typy s
ą ś
ci
ś
le zgodne;
(ii) typ okre
ś
lony dla parametru procedury obsługi jest
publiczn
ą
klas
ą
bazow
ą
klasy obiektu b
ę
d
ą
cego warto
ś
ci
ą
wyra
ż
enia throw;
(iii) mo
ż
liwe jest standardowe przekształcenie typu warto
ś
ci
wyra
ż
enia throw, b
ę
d
ą
cej wska
ź
nikiem, na typ wska
ź
nikowy
parametru procedury.
Po pozytywnym rozpoznaniu jednego z warunków (i), (ii), (iii),
dalsze frazy catch nie b
ę
d
ą
analizowane.
Tomasz Marks - Wydział MiNI PW
-12-
Obsługa wyj
ą
tków [4]
7. Je
ż
eli dopasowanie nie da pozytywnego rezultatu, t.zn.
ż
adna
procedura catch nie zostanie uruchomiona, to dana instrukcja
try-catch b
ę
dzie uznana za zako
ń
czon
ą
i nast
ą
pi sprawdzenie,
czy w aktualnie wykonywanej funkcji jest blok try zawieraj
ą
cy
dan
ą
instukcj
ę
try-catch.
Je
ż
eli taki blok istnieje, to wykonana zostanie próba dopasowania
rozpatrywanygo wyj
ą
tku do wyst
ę
puj
ą
cych po nim procedur obsługi.
Je
ż
eli taki blok nie istnieje, to wykonanie funkcji zostanie zako
ń
czone
i odpowiedni blok b
ę
dzie poszukiwany w funkcji wywołuj
ą
cej t
ę
,
w której został odrzucony wyj
ą
tek itd. itd.
W skrajnym przypadku, je
ż
eli dopasowanie nie zostanie zrealizowane
w obr
ę
bie funkcji main(), obsług
ę
wyj
ą
tku przejmie systemowa procedura
obsługi wyj
ą
tków, która wy
ś
wietli odpowiedni komunikat i zako
ń
czy
działanie programu.
Tomasz Marks - Wydział MiNI PW
-13-
Obsługa wyj
ą
tków [5]
8. Zapis procedury obsługi nie musi zawiera
ć
nazwy przyjmowanego
obiektu (parametru). Nazwa jest konieczna jedynie w sytuacji, gdy
zachodzi rzeczywista potrzeba u
ż
ycia warto
ś
ci tego parametru.
9. Trzykropek … pozwala okre
ś
li
ć
procedur
ę
wychwytuj
ą
c
ą
wyj
ą
tki
wszystkich typów.
10. Wewn
ą
trz procedury obsługi (catch) mo
ż
na u
ż
y
ć
frazy throw
z pustym wyra
ż
eniem
throw;
Skutkuje to przekazaniem wyj
ą
tku do zewn
ę
trznego bloku try.
W innych kontekstach u
ż
ycie pustego wyra
ż
enia throw jest bł
ę
dem.
Tomasz Marks - Wydział MiNI PW
-14-
Obsługa wyj
ą
tków [6]
11. Deklaracja funkcji mo
ż
e zawiera
ć
klauzul
ę
, która stanowi
ograniczenie zbioru sytuacji wyj
ą
tkowych zgłaszanych bezpo
ś
rednio
lub po
ś
rednio przez t
ę
funkcj
ę
:
void fun1 ( …….. );
// mo
ż
e zgłasza
ć
dowolne wyj
ą
tki
long fun2 ( …….. ) throw (const char*, xcp_scope&);
// mo
ż
e zgłasza
ć
wyj
ą
tki wymienionych typów
double fun3 ( ….. ) throw ( );
// nie mo
ż
e zgłasza
ć ż
adnych wyj
ą
tków
Naruszenie zadeklarowanej specyfikacji spowoduje bł
ą
d wykonania
programu.
12. Klauzula ograniczenia zbioru sytuacji wyj
ą
tkowych nie jest uwa
ż
ana
za element okre
ś
lenia typu funkcji ( tzn. nie wchodzi w skład
deskryptora funkcji ).
Tomasz Marks - Wydział MiNI PW
-15-
FUNKTORY
Tomasz Marks - Wydział MiNI PW
-16-
Funktory (1)
Definicja:
Funktorem (inaczej obiektem funkcyjnym) nazywamy obiekt
klasy, w której zdefiniowany został operator wywołania funkcji
t.zn. operator( ).
Uwaga 1:
Korzystanie z obiektu funkcyjnego jest składniowo
identyczne z wywołaniem funkcji, dlatego wsz
ę
dzie tam,
gdzie parametrem funkcji ma by
ć
funkcja dostarczana jako
argument, mo
ż
na u
ż
y
ć
funktora.
Tomasz Marks - Wydział MiNI PW
-17-
Funktory (2)
Zwykł
ą
funkcj
ę
typ funkcja ( argumenty )
{
Tre
ść
Funkcji
}
mo
ż
na "przekształci
ć
" na funktor wg schematu:
class Funktor
{
public:
typ operator ( ) ( argumenty ) const
{
Tre
ść
Funkcji
}
}
funkcja;
Tomasz Marks - Wydział MiNI PW
-18-
Funktory (3)
Uwaga 2:
Funktory s
ą
obiektami klas, w których oprócz operator()
mog
ą
by
ć
zdefiniowane inne metody i pola składowe, w tym
konstruktory. Zatem:
- funktory mog
ą
posiada
ć
stan wewn
ę
trzny, który mo
ż
e by
ć
inicjowany, odczytywany i modyfikowany;
- mo
ż
e jednocze
ś
nie istnie
ć
wiele funktorów (obiektów)
tej samej klasy (realizuj
ą
cych takie same obliczenia),
ale ró
ż
ni
ą
cych si
ę
stanem wewn
ę
trznym;
- w przypadku zwykłych funkcji namiastk
ą
stanu mo
ż
e by
ć
zmienna globalna lub lokalna dla funkcji zm. statyczna.
Tomasz Marks - Wydział MiNI PW
-19-
Funktory (4)
Uwaga 3:
Ka
ż
dy funktor posiada własny typ, co mo
ż
e by
ć
pomocne przy korzystaniu z szablonów:
- funktory b
ę
d
ą
c obiektami ró
ż
nych klas maj
ą
ró
ż
ne typy,
nawet je
ś
li ich operatory wywołania funkcji maj
ą
identyczne
sygnatury;
- zwykłe funkcje o identycznych sygnaturach s
ą
z punktu
widzenia szablonów nierozró
ż
nialne.
Tomasz Marks - Wydział MiNI PW
-20-
Funktory – przykład (1)
#include <iostream>
using namespace std;
class Sum
{
int s;
public:
Sum ( ) : s(0) { }
void operator ( ) ( int a ) { s += a; }
int operator ( ) ( ) { return s; }
};
int arr[ ] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, };
void main ( )
{
Sum parzyste, nieparzyste;
for ( int i = 0; i < 10; ++i )
if ( i & 1 ) nieparzyste ( arr[ i ] ); else parzyste ( arr[ i ] );
cout << parzyste() << " " << nieparzyste();
// 25 30
}
Tomasz Marks - Wydział MiNI PW
-21-
Funktory – przykład (2)
#include <iostream>
using namespace std;
class Arr
{
int n, m;
double *A;
public:
Arr ( int i, int j ) : n( i ), m( j ) { A = new double [ n * m ]; }
~Arr ( ) { delete [ ] A; }
double & operator ( ) ( int i, int j ) { return A[ i * m + j ]; }
};
void main ( )
{
Arr A (3, 4);
for ( int i = 0; i < 3; ++i )
for ( int j = 0; j < 4; ++j ) A( i, j ) = 10 * ( i + 1) + ( j + 1 );
for ( int i = 0; i < 3; ++i )
{
cout << endl;
for ( int j = 0; j < 4; ++j ) cout << A( i, j ) << " ";
// 11 12 13 14
}
// 21 22 23 24
}
// 31 32 33 34
Tomasz Marks - Wydział MiNI PW
-22-
KONWERSJE
i KONWERTERY
Tomasz Marks - Wydział MiNI PW
-23-
Klasyfikacja konwersji (klasycznych) (1)
1. Standardowe – mog
ą
by
ć
wykonywane jawnie i niejawnie, np.
- dana typu arytmetycznego lub znakowego -> inna dana typu
arytmetycznego lub znakowego,
- dana reprezentowana literałem 0 lub 0L -> dana wskazuj
ą
ca
dowolnego typu,
- dana dowolnego typu wskazuj
ą
cego -> dana typu void*,
- ………………………………..
2. Definiowalne – mog
ą
by
ć
wykonywane jawnie i niejawnie:
- wszystkie konwersje okre
ś
lone przez konstruktory jednoparametrowe
i konwertery.
3. Dopuszczalne (predefiniowane konwersje, których skutek mo
ż
e zale
ż
e
ć
od implementacji) – musz
ą
by
ć
wykonywane jawnie, np.
- dana całkowita -> dana wyliczeniowa,
- dana typu wskazuj
ą
cego -> dana innego typu wskazuj
ą
cego
(z wył
ą
czeniem przypadku void*),
- dana typu całkowitego <-> dana typu wskazuj
ą
cego,
- wskazanie zmiennej ustalonej (albo ulotnej) -> wskazanie analogicznej
zmiennej nieustalonej (albo nieulotnej),
- ………………………………..
Tomasz Marks - Wydział MiNI PW
-24-
Operatory konwersji (klasyczne)
Dla typu docelowego TYPE operator konwersji jest opisywany
wyra
ż
eniem postaci:
TYPE ( wyr ) , gdzie TYPE - identyfikator typu,
wyr
- konwertowane wyra
ż
enie,
albo
( TYPE ) wyr
, gdzie TYPE - dowolna nazwa typu,
wyr
- konwertowane wyra
ż
enie,
Np.
k = (int) 12.5;
// zapis poprawny
k = int ( 12.5 );
// zapis poprawny
p = (char *) z;
// zapis poprawny
p = char* ( z );
//
BŁ
Ą
D!
- char* jest nazw
ą
typu
// ale nie jest identyfikatorem
Tomasz Marks - Wydział MiNI PW
-25-
Klasyfikacja konwersji (klasycznych) (2)
1. Konwersje warto
ś
ciowe (nieodno
ś
nikowe, niereferencyjne)
Je
ż
eli typ docelowy nie jest odno
ś
nikowy, to rezultatem konwersji
b
ę
dzie zmienna typu TYPE zainicjowana dan
ą
powstał
ą
z przekształcenia
danej o warto
ś
ci wyr na dan
ą
typu TYPE.
Zmienna taka nie jest L-nazw
ą
.
Np.
double X = 1.5;
//(int) X = 12;
//
BŁ
Ą
D!
- (int) X nie jest L-nazw
ą
cout << (int) X;
// O.K. - wyprowadzi 1
// rezultatem konwersji jest zmienna tymczasowa,
// któr
ą
mo
ż
na zdefiniowa
ć
instrukcj
ą
: int tmp = 1;
.
Tomasz Marks - Wydział MiNI PW
-26-
Klasyfikacja konwersji (klasycznych) (3)
2. Konwersje odno
ś
nikowe (referencyjne)
Je
ż
eli typ docelowy jest odno
ś
nikowy, to rezultatem konwersji b
ę
dzie
odno
ś
nik typu TYPE zainicjowany argumentem konwersji.
Taki odno
ś
nik
jest L-nazw
ą
tylko wówczas, gdy spełnione s
ą
2 warunki:
(i) wyr jest L-nazw
ą
zmiennej,
(ii) istnieje konwersja &wyr na TYPE*
(konwersja wskazania argumentu na wskazanie typu docelowego).
Np.
const int X = 10;
X = 12
//
BŁ
Ą
D!
- X ma atrybut const
(int &) X = 20;
// O.K.
int & tmp = *(int *) & X;
// (zmienna tmp skojarzona jest z obszarem
// pami
ę
ci przydzielonym na zmienn
ą
X)
cout << X;
// wyprowadzi 20 albo 10 (
!!!
)
Tomasz Marks - Wydział MiNI PW
-27-
Konwertery (1) - Definicja
Konwerterem
definiowanego typu Class do typu docelowego TYPE
jest bezparametrowa metoda o deklaracji
Class :: operator TYPE ( );
w której TYPE jest nazw
ą
(!) typu nie zawieraj
ą
c
ą
nawiasów.
Wykonanie w ciele konwertera instrukcji
return wyr ;
skutkuje udost
ę
pnienien zmiennej typu TYPE o warto
ś
ci
(TYPE) wyr
UWAGA.
Wymaga si
ę
, by konwersja typu wyra
ż
enia wyr do TYPE istniała.
Tomasz Marks - Wydział MiNI PW
-28-
Konwertery (2) – przykład definiowania
class CMPLX
{
double Re, Im;
public:
CMPLX ( double );
// konwersja double
CMPLX
……………………..
operator double ( );
// konwersja CMPLX
double
operator const char * ( );
// konwersja CMPLX
const char *
……………………..
};
CMPLX :: CMPLX ( double re ) : Re(re), Im(0) { }
CMPLX :: operator double ( ) { return Re; }
CMPLX :: operator const char * ( )
{
if ( Re == 0 && Im == 0 ) return "Zero";
if ( Im == 0 ) return "Real";
if ( Re == 0 ) return "Imag";
return "Cmplx";
}
Tomasz Marks - Wydział MiNI PW
-29-
Konwertery (3) – przykład u
ż
ycia
#include "cmplx.h"
typedef const char * ConstCharPtr;
void main ( )
{
CMPLX X(1.5,2.3), Y(5), Z;
double a, b, c;
const char *A, *B, *C;
a = (double) X;
// jawne u
ż
ycie konwertera na double
b = double (X);
// jawne u
ż
ycie konwertera na double
c = X;
// niejawne u
ż
ycie konwertera na double
cout << a << b << c;
// 1.5 1.5 1.5
A = (const char *) Y;
// jawne u
ż
ycie konwertera na const char *
//A = const char * (Y); // BŁ
Ą
D! - const char * nie jest identyfikatorem
B = ConstCharPtr (Y);
// jawne u
ż
ycie konwertera na const char *
C = Y;
// niejawne u
ż
ycie konwertera na const char *
cout << A << B << C;
// Real Real Real
X = (CMPLX) a;
// jawne u
ż
ycie konwersji konstruktorowej
Y = CMPLX (a);
// jawne u
ż
ycie konwersji konstruktorowej
Z = a;
// niejawne u
ż
ycie konwersji konstruktorowej
cout << X << Y << Z;
// [1.5,0] [1.5,0] [1.5,0]
}
Tomasz Marks - Wydział MiNI PW
-30-
"Nowe" operatory konwersji (1)
Obecnie zaleca si
ę
stosowanie "nowych" operatorów konwersji,
zapisywanych z wykorzystaniem słów kluczowych:
static_cast
reinterpret_cast
const_cast
dynamic_cast
U
ż
ycie "nowych" operatorów konwersji ma posta
ć
:
xxxxx_cast < typ_docelowy > ( konwertowane_wyra
ż
enie )
Tomasz Marks - Wydział MiNI PW
-31-
"Nowe" operatory konwersji (2)
static_cast – wykorzystywany do konwersji typów spokrewnionych
- typu zmiennopozycyjny -> typu całkowity,
- typ wyliczeniowy -> typ całkowity,
- typ wska
ź
nikowy -> inny spokrewniony typ wska
ź
nikowy, ……
Np.
int x = static_cast<int>(22.6);
PhVectorB *p = static_cast<PhVectorB*>(Vec);
int *p = static_cast<int*>(malloc(20));
reinterpret_cast
– wykorzystywany do konwersji typów niespokrewnionych
- typ całkowity -> typ wska
ź
nikowy,
- typ wska
ź
nikowy -> inny niespokrewniony typ wska
ź
nikowy, ……
Np.
io_Unit* we = reinterpret_cast<io_Unit*>(0x123e);
Tomasz Marks - Wydział MiNI PW
-32-
"Nowe" operatory konwersji (3)
const_cast – wykorzystywany do usuwania kwalifikatora const
Np.
const int x = 10;
const_cast <int&> ( x ) = 20;
dynamic_cast
– wykorzystywany do konwersji kontrolowanej w czasie
wykonywania programu.
Np.
Zwierz* pZ;
Pies P("Burek");
Ssak D("Umo");
pZ = dynamic_cast <Zwierz*> ( &P );
// pZ = wskazanie na P
pZ = dynamic_cast <Zwierz*> ( &D );
// pZ = 0, je
ś
li niepoprawne
Zwierz& rZ = dynamic_cast <Zwierz&> ( *pZ );
// odrzuci wyj
ą
tek bad_cast, je
ś
li niepoprawne
Tomasz Marks - Wydział MiNI PW
-33-
Koniec wykładu 9.