-1-
Programowanie Obiektowe
(j
ę
zyk C++)
Wykład 10.
-2-
FUNKCJE
WZORCOWE
-3-
Funkcje wzorcowe – wprowadzenie (1)
int max ( int a, int b )
{ return a>b ? a : b; }
Aby mie
ć
analogiczn
ą
funkcj
ę
działaj
ą
c
ą
na danych typu double
w j
ę
z. C musimy wprowadzi
ć
dla niej odr
ę
bny identyfikator:
double d_max ( double a, double b )
{ return a>b ? a : b; }
W j
ę
z. C++ mamy mo
ż
liwo
ść
przeci
ąż
ania identyfikatorów funkcji,
wi
ę
c mo
ż
emy u
ż
y
ć
tej samej nazwy:
double max ( double a, double b )
{ return a>b ? a : b; }
-4-
Funkcje wzorcowe – wprowadzenie (2)
W obu j
ę
zykach C i C++ mo
ż
emy si
ę
posłu
ż
y
ć
w tej sytuacji, jako pewnego
rodzaju alternatyw
ą
, tzw. makrodefinicj
ą
:
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
co daje nam mo
ż
liwo
ść
posługiwania si
ę
wyra
ż
eniami przypominaj
ą
cymi
wywołania 2-argumentowej funkcji o tej samej nazwie dla argumentów
ró
ż
nych typów:
int i, j, k;
double A, B, C;
……………………….
k = MAX(2*i+5, j+i);
C = MAX(3.4+A, 7*B);
Ale wi
ąż
e si
ę
to z szeregiem niedogodno
ś
ci i pułapek.
-5-
Funkcje wzorcowe (1)
W j
ę
zyku C++ mo
ż
emy si
ę
posłu
ż
y
ć
konstrukcj
ą
wzorca (szablonu) funkcji:
template < class TYPE >
TYPE max (TYPE a, TYPE b )
{ return a>b ? a : b; }
gdzie TYPE mo
ż
e by
ć
typem wbudowanym lub definiowanym w programie.
<class TYPE> nazywa si
ę
tu opisem parametru wzorca.
Maj
ą
c tak zdefiniowany szablon, mo
ż
e go wykorzysta
ć
do konkretyzacji funkcji
przyjmuj
ą
cych argumenty potrzebnych typów, n.p.:
int main ( )
{
int i, j, k; double A, B, C;
k = max( i, j + 5 );
// skonkretyzuje
int max ( int, int );
C = max( A, B + 0.5 );
// skonkretyzuje
double max ( double, double );
A = max ( B, 10 );
//
BŁ
Ą
D!
– konieczna
ś
cisła zgodno
ść
typów
-6-
Funkcje wzorcowe (2)
1. Wzorzec funkcji mo
ż
e mie
ć
wi
ę
cej parametrów (ale nie mniej ni
ż
jeden!).
2. Ka
ż
dy opis parametru wzorca składa si
ę
ze słowa kluczowego class
( lub typename ) oraz wybranego identyfikatora ( nazwy parametru ).
3. Na li
ś
cie parametrów wzorca ka
ż
dy identyfikator mo
ż
e wyst
ą
pi
ć
tylko raz.
4. Parametr wzorca staje si
ę
specyfikatorem typu, którego mo
ż
na u
ż
ywa
ć
w pozostałej cz
ęś
ci definicji funkcji wzorcowej ( n.p. w deklaracjach zmiennych
lokalnych, operacjach rzutowania e.t.c. )
5. Ka
ż
dy parametr wzorca musi wyst
ą
pi
ć
co najmniej jeden raz w sygnaturze
funkcji wzorcowej.
-7-
Funkcje wzorcowe (3)
Wychodz
ą
c od definicji funkcji:
double
Summa (
double
tab[ ], int size )
{
double
sum = 0;
for ( int i = 0; i < size; i++ ) sum += tab[ i ];
return sum;
}
Mo
ż
emy łatwo utworzy
ć
jednoparametrowy wzorzec:
template < class
T
>
T
Summa (
T
tab[ ], int size )
{
T
sum = 0;
for ( int i = 0; i < size; i++ ) sum += tab[ i ];
return sum;
}
-8-
Funkcje wzorcowe (4)
Wychodz
ą
c od definicji tej samej funkcji:
double
Summa (
double
tab[ ],
int
size )
{
double
sum = 0;
for (
int
i = 0; i < size; i++ ) sum += tab[ i ];
return sum;
}
Mo
ż
emy równie łatwo utworzy
ć
wzorzec dwuparametrowy:
template < class
T
, class
S
>
T
Summa (
T
tab[ ],
S
size )
{
T
sum = 0;
for (
S
i = 0; i < size; i++ ) sum += tab[ i ];
return sum;
}
-9-
Funkcje wzorcowe (5)
Rozró
ż
nianie przeci
ąż
onych funkcji wzorca i innych funkcji
o tej samej nazwie jest realizowane wg schematu:
1. Je
ż
eli istnieje funkcja o deskryptorze dokładnie pasuj
ą
cym do wywołania,
to j
ą
wywołaj.
2. Je
ż
eli istnieje wzorzec pozwalaj
ą
cy zrealizowa
ć
(skonkretyzowa
ć
)
funkcj
ę
o dokładnie pasuj
ą
cym deskryptorze, to u
ż
yj tego wzorca.
3. Je
ż
eli istnieje funkcja przeci
ąż
ona, któr
ą
mo
ż
na dopasowa
ć
wg zwykłych
reguł (tzn. z zastosowaniem konwersji), to jej u
ż
yj.
4. Je
ż
eli
ż
adnego z punktów 1., 2., 3. nie dało si
ę
zastosowa
ć
, to wywołanie
zostanie uznane za bł
ę
dne.
-10-
Funkcje wzorcowe (6)
Rozpatrzmy przykład:
template < class T >
T max ( T a, T b )
{ return a>b ? a : b; }
void test ( )
{
int a, b;
char c, d;
//int A = max( a, c ); // BŁ
Ą
D! – nie mo
ż
na wygenerowa
ć
int max(int,char);
int B = max( a, b );
// int max(int, int); - niejawne utworzenie egzemplarza
char C = max( c, d );
// char max(char,char); niejawne utworzenie egz.
int D = max( c, d );
// char max(char, char); typ zwracanej warto
ś
ci nie
// nie nale
ż
y do deskryptora
-11-
Funkcje wzorcowe (7)
Ale:
template < class T >
T max ( T a, T b )
{ return a>b ? a : b; }
int max ( int, int );
// deklaracja funkcji (by
ć
mo
ż
e zewn
ę
trznej)
void test ( )
{
int a, b;
char c, d;
int A = max( a, c );
//
O.K.!
– u
ż
yte b
ę
dzie int max(int,int);
// zgodnie z reguł
ą
3.
-12-
Funkcje wzorcowe (8)
Inne mo
ż
liwo
ś
ci:
template < class T >
T max ( T a, T b )
{ return a>b ? a : b; }
template int max ( int, int );
// wymuszone (jawne) utworzenie egzemplarza
// (tylko w zasi
ę
gu definicji szablonu)
template< >
char max ( char a, char b )
{ return a>b ? a : 0; }
// dostarczenie szczególnej definicji egzemplarza!
// (tylko w zasi
ę
gu definicji szablonu)
-13-
WZORCE KLAS
( SZABLONY KLAS )
-14-
Klasy wzorcowe - wprowadzenie (1)
W j
ę
zyk C++ mo
ż
emy si
ę
posłu
ż
y
ć
równie
ż
konstrukcj
ą
wzorca (szablonu)
klasy. Podobnie jak w przypadku wzorców funkcji najpro
ś
ciej jest przyj
ąć
za
punkt wyj
ś
cia jak
ąś
konkretn
ą
klas
ę
(dobrze wcze
ś
niej sprawdzon
ą
w praktyce).
#define CSTACKSIZE 100
class CharStack
{
char tab [ CSTACKSIZE ];
int size, top;
public:
CharStack ( ) { size = CSTACKSIZE; top = 0; }
void Push ( char e ) { tab [ top++ ] = e; }
char Pop ( ) { return tab [ --top ]; }
char Top ( ) { return tab [ top - 1 ]; }
int Size ( ) const { return size; }
int Used ( ) const { return top; }
int Place ( ) const { return size - top; }
void Display ( ) const
{ cout << endl; for ( int i = 0; i < top; ++i ) cout << tab [ i ] << " "; }
};
-15-
Przy okazji …
(1)
Zauwa
ż
my,
ż
e dotychczas w definicji klasy podawali
ś
my zwykle jedynie
deklaracje metod. Ich definicje umieszczali
ś
my na zewn
ą
trz, najcz
ęś
ciej
w tzw. pliku implementacyjnym.
Tym razem definicje metod zostały podane od razu w definicji klasy.
Jaka ró
ż
nica?
1. Metoda definiowana w ciele definicji klasy otrzymuje domy
ś
lnie
atrybut inline. Metody (i zwykłe funcje) z takim atrybutem nie maj
ą
jednokrotnie wygenerowanego kodu o okre
ś
lonym adresie, który
jest uruchamiany przy ka
ż
dym odwołaniu do metody (funkcji).
Zamiast tego kompilator mo
ż
e (ale nie musi!) generowa
ć
kod metody
(funkcji) w ka
ż
dym miejscu jej wywołania. Mo
ż
e to da
ć
zysk na czasie
wykonania programu, chocia
ż
zwykle zwi
ę
ksza jego obj
ę
to
ść
.
UWAGA: Funkcja z atrybutem inline nazywa si
ę
te
ż
… funkcj
ą
rozwijaln
ą
…
albo … funkcj
ą
otwart
ą
…
-16-
Przy okazji …
(2)
2.
Atrybut inline mo
ż
e by
ć
podany jawnie a tre
ść
metody na zewn
ą
trz
definicji klasy, ale wtedy nale
ż
y j
ą
poda
ć
w pliku header'owym (.h)
klasy, a nie w pliku implementacyjnym.
Wynika to z faktu,
ż
e tre
ść
takiej metody potrzebna jest kompilatorowi
w ka
ż
dym miejscu jej wywołania.
3. U
ż
ycie odr
ę
bnego (niezale
ż
nie kompilowanego) pliku zawieraj
ą
cego
definicje metod ( posiadaj
ą
cych atrybut extern ) pozwala ukry
ć
szczegóły implementacyjne. U
ż
ytkownik naszej klasy b
ę
dzie korzystał
tylko z pliku nagłówkowego w postaci
ź
ródłowej (.h) i skompilowanego
pliku zawieraj
ą
cego kod metod (.obj). A wi
ę
c np. wcale nie musi
wiedzie
ć
, jaki algorytm zastosowali
ś
my do realizacji konkretnych
oblicze
ń
numerycznych
.
-17-
Przy okazji …
(3)
// charstack.h
……………………………
class CharStack
{
……………………………
void Push ( char e ) { tab [ top++ ] = e; }
inline char Pop ( );
char Top ( ) const;
……………………………
};
……………………………
inline char CharStack :: Pop ( ) { return tab [ --top ]; }
……………………………..…………………………………..….… end of charstack.h
// charstack.cpp
……………………………
char CharStack :: Top ( ) const { return tab [ top - 1 ]; }
……………………………
W powy
ż
szym przykładzie metody:
Push i Pop maj
ą
atybut inline ( Push – domy
ś
lnie, Pop – jawnie )
metoda
Top ma atrybut extern.
-18-
Klasy wzorcowe - wprowadzenie (2)
Poszukajmy 'kandydatów' na parametry.
Zaznaczyłem je na kolorowo.
Oczywi
ś
cie nie wszystkie mo
ż
liwo
ś
ci musimy wykorzysta
ć
.
#define
CSTACKSIZE
100
class CharStack
{
char
tab [
CSTACKSIZE
];
int
size, top;
public:
CharStack ( ) { size =
CSTACKSIZE
; top = 0; }
void Push (
char
e ) { tab [ top++ ] = e; }
char
Pop ( ) { return tab [ --top ]; }
char
Top ( ) { return tab [ top - 1 ]; }
int
Size ( ) const { return size; }
int
Used ( ) const { return top; }
int
Place ( ) const { return size – top; }
void Display ( ) const
{ cout << endl; for (
int
i = 0; i < top; ++i ) cout << tab [ i ] << " "; }
};
-19-
Klasy wzorcowe (1)
A tak mo
ż
e wygl
ą
da
ć
nasz szablon:
#define STACKSIZE 100
template < class
T
= char, int
S
= STACKSIZE >
class Stack
{
T
tab [
S
];
int size, top;
public:
Stack ( ) { size =
S
; top = 0; }
void Push (
T
e ) { tab [ top++ ] = e; }
T
Pop ( ) { return tab [ --top ]; }
T
Top ( ) { return tab [ top - 1 ]; }
int Size ( ) const { return size; }
int Used ( ) const { return top; }
int Place ( ) const { return size - top; }
void Display ( ) const
{ cout << endl; for ( int i = 0; i < top; ++i ) cout << tab [ i ] << " "; }
};
#undef STACKSIZE
-20-
Klasy wzorcowe (2)
I funkcja main:
int main ( )
{
const int k = 10;
Stack<> S0; Stack<int> S1, S2; Stack<int,10> S3;
Stack<double> S4; Stack<double,55> S5;
Stack<double,k+5> S6; Stack<double,k+45> S7;
cout << endl << S0.Size() << endl << S1.Size() << endl << S3.Size();
cout << endl << S4.Size() << endl << S5.Size() << endl << S6.Size();
S1.Push(10); S1.Push(11); S1.Push(12); S1.Push(13); S1.Push(14);
S1.Display();
S2 = S1;
S1.Pop(); S1.Pop();
S1.Display(); S2.Display();
//S3 = S1; // BŁ
Ą
D!
//S4 = S1; // BŁ
Ą
D! ale S7 = S5; O.K.
while ( S1.Place() && S2.Used() ) S1.Push( S2.Pop() );
S1.Display();
}
-21-
Klasy wzorcowe (3)
Program wy
ś
wietli (po zakomentowaniu wierszy z bł
ę
dami):
100
100
10
100
55
15
10 11 12 13 14
10 11 12
10 11 12 13 14
10 11 12 14 13 12 11 10
-22-
Klasy wzorcowe (4)
CharStack St;
// obiekt St jest typu CharStack
Stack<> S0;
// obiekt S0 jest typu Stack<char, 100>
Stack<int> S1, S2;
// obiekty S1 i S2 s
ą
typu Stack<int, 100>
Stack<int,10> S3;
// obiekt S3 jest typu Stack<int, 10>
Stack<double> S4;
// obiekt S4 jest typu Stack<double, 100>
Stack<double,k+5> S6;
// obiekt S6 jest typu Stack<double, 15>
Stack<CMPLX> SC;
// obiekt SC jest typu Stack<CMPLX, 100>
-23-
Klasy wzorcowe (1a)
Kusz
ą
ca (niebezpieczna) alternatywa:
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ];
T *p;
int size;
public:
Stack ( ) { p = tab; size = S; }
void Push ( T e ) { *p++ = e; }
T Pop ( ) { return *--p; }
T Top ( ) { return *(p – 1); }
int Size ( ) const { return size; }
int Used ( ) const { return p – tab; }
int Place ( ) const { return size – (p – tab); }
void Display ( ) const { T *r = tab; while ( r < p ) cout << *r++ << " "; }
};
Na czym polega niebezpiecze
ń
stwo
i jak mu zaradzi
ć
?
-24-
Klasy wzorcowe (5)
1. Wzorzec klasy mo
ż
e mie
ć
wi
ę
cej parametrów (ale nie mniej ni
ż
jeden!).
2. Opis parametru wzorca składa si
ę
ze słowa kluczowego class
(ew. typename) lub nazwy typu wbudowanego oraz wybranego
identyfikatora (nazwy parametru). Dla parametrów mo
ż
na okre
ś
la
ć
warto
ś
ci domy
ś
lne.
3. Na li
ś
cie parametrów wzorca ka
ż
dy parametr mo
ż
e wyst
ą
pi
ć
tylko raz.
4. Parametr wzorca poprzedzony słowem kluczowym class (ew. typename)
staje si
ę
specyfikatorem typu, którego mo
ż
na u
ż
ywa
ć
w pozostałej cz
ęś
ci
definicji klasy wzorcowej (n.p. w deklaracjach pól, specyfikacjach parametrów
metod et c.).
5. Parametr wzorca poprzedzony nazw
ą
typu wbudowanego staje si
ę
stał
ą
,
której mo
ż
na u
ż
ywa
ć
np. do zapisu rozmiarów tablic, inicjowania warto
ś
ci
zmiennych et c.
-25-
Klasy wzorcowe (6)
Zobaczmy inny wariant zapisu szablonu.
// stack.h
#include <iostream>
using namespace std;
#define STACKSIZE 1000
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
void Display ( ) const;
};
...................................................
-26-
Klasy wzorcowe (7)
W dalszym ci
ą
gu pliku stack.h podajemy szablony metod:
...................................................
template < class T, int S >
Stack< T, S > :: Stack ( ) { p = q = tab; size = S; }
template < class T, int S >
Stack< T, S >& Stack< T, S > :: operator= ( const Stack& rhs )
{ p = q; for ( T* r = rhs.q; r < rhs.p; *p++ = *r++ ); return *this; }
template < class T, int S >
void Stack< T, S > :: Push ( T e ) { *p++ = e; }
template < class T, int S >
T Stack< T ,S > :: Pop ( ) { return *--p; }
template < class T, int S >
T Stack< T, S > :: Top ( ) const { return *(p - 1); }
template < class T, int S >
int Stack< T, S > :: Size ( ) const { return size; }
...................................................
-27-
Klasy wzorcowe (8)
i dalej w pliku stack.h:
...................................................
template < class T, int S >
int Stack< T, S > :: Used ( ) const { return p - q; }
template < class T, int S >
int Stack< T, S > :: Place ( ) const { return size - (p - q); }
template < class T, int S >
void Stack< T, S > :: Display ( ) const
{ T* r = q; while ( r < p ) cout << *r++ << " "; }
#undef STACKSIZE
UWAGA! UWAGA!
To wszystko powinno by
ć
podane w pliku stack.h.
Dla klas wzorcowych nie tworzymy odr
ę
bnych plików implementacyjnych *.cpp.
-28-
funkcje zaprzyja
ź
nione
we wzorcach klas
-29-
Klasy wzorcowe (6)
W rozpatrywanym wcze
ś
niej wzorcu klasy
...................................................
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
void Display ( ) const;
};
...................................................
chcemy zast
ą
pi
ć
metod
ę
Display zaprzyja
ź
nionym operatorem << .
-30-
Wzorce i friend (1)
Próba zrealizowania tego zadania w nast
ę
puj
ą
cy sposób:
...................................................
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
friend ostream& operator << (ostream&, const Stack<T,S>&);
};
...................................................
template < class T, int S>
ostream& operator << (ostream& out, const Stack<T,S>& st)
{ T* r = st.q; while ( r < st.p ) cout << *r++ << " ";
return out;
}
nie da oczekiwanego efektu. Operator << nie zostanie wygenerowany.
-31-
Wzorce i friend (2)
Poprawny efekt otrzymamy pisz
ą
c:
...................................................
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
template < class Q, int R>
friend ostream& operator << (ostream&, const Stack<Q,R>&);
};
...................................................
template < class T, int S>
ostream& operator << (ostream& out, const Stack<T,S>& st)
{ T* r = st.q; while ( r < st.p ) out << *r++ << " ";
return out;
}
-32-
Wzorce i friend (3)
a nawet:
...................................................
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
template < class Q, int R>
friend ostream& operator << (ostream&,
const Stack&
);
};
...................................................
template < class T, int S>
ostream& operator << (ostream& out, const Stack<T,S>& st)
{ T* r = st.q; while ( r < st.p ) out << *r++ << " ";
return out;
}
-33-
Wzorce i friend (4)
Jeszcze innym rozwi
ą
zaniem jest:
...................................................
template < class T = char, int S = STACKSIZE >
class Stack
{
T tab [ S ]; T *p, *q; int size;
public:
Stack ( );
Stack& operator= ( const Stack& );
void Push ( T ); T Pop ( ); T Top ( ) const;
int Size ( ) const; int Used ( ) const; int Place ( ) const;
friend ostream& operator << (ostream& out, const Stack& st)
{ T* r = st.q; while ( r < st.p ) out << *r++ << " ";
return out;
}
};
...................................................
-34-
Koniec wykładu 10.