Tomasz Marks - Wydział MiNI PW
-1-
Programowanie Obiektowe
(j
ę
zyk C++)
Wykład 7.
Tomasz Marks - Wydział MiNI PW
-2-
Problem projektowy
DANE WEJ
Ś
CIOWE:
Dysponujemy dobrze zdefiniowan
ą
i przetestowan
ą
klas
ą
Vector,
która faktycznie pozwala na operowanie dynamicznie alokowanymi
tablicami danych liczbowych.
ZADANIE:
Chcemy operowa
ć
wektorami, które zwi
ą
zane b
ę
d
ą
z pewnymi
zadawanymi dowolnie jednostkami fizycznymi ( n.p. [m], [m/s],
[m/s2], [A], [V] ).
Dla uproszczenia zało
ż
ymy,
ż
e wszystkie współrz
ę
dne wektora
zwi
ą
zane s
ą
z t
ą
sam
ą
jednostk
ą
.
Klas
ę
nazwiemy PhVectorX
Prze
ś
ledzimy dwa sposoby podej
ś
cia do tego problemu definiuj
ą
c
dwie klasy: PhVectorA i
PhVectorB.
Tomasz Marks - Wydział MiNI PW
-3-
KLASY
POCHODNE
DZIEDZICZENIE
Tomasz Marks - Wydział MiNI PW
-4-
Klasa pochodna klasy Vector [1]
// phvectorb.h
#include "vector.h"
#define U_LEN 10
class PhVectorB
: public Vector
{
char Unit [ U_LEN ];
public:
PhVectorB ( int = 0, double* = 0, char* = "" );
PhVectorB ( const PhVectorB& );
~PhVectorB ( );
PhVectorB& operator = ( const PhVectorB& );
void setUnit ( const char* );
const char* getUnit ( ) const;
....................................
friend istream& operator >> ( istream&, PhVectorB& );
friend ostream& operator << ( ostream&, const PhVectorB& );
};
Tomasz Marks - Wydział MiNI PW
-5-
Klasa pochodna klasy Vector [2]
Powiemy,
ż
e ….
Klasa PhVectorB
dziedziczy
(dokładniej: dziedziczy publicznie)
klas
ę
Vector.
… dziedziczy klas
ę
…
… dziedziczy z klasy …
… dziedziczy po klasie …
Klasa Vector jest klas
ą
bazow
ą
(dokładniej: publiczn
ą
klas
ą
bazow
ą
).
Klasa PhVectorB jest klas
ą
pochodn
ą
dziedzicz
ą
c
ą
z klasy Vector.
UWAGA: W j
ę
zyku C++ dopuszczalne jest równie
ż
dziedziczenie
wielobazowe (tzn. dziedziczenie jednocze
ś
nie z wielu
klas bazowych).
Tomasz Marks - Wydział MiNI PW
-6-
Klasa pochodna klasy Vector [3]
1. Klasa PhVectorB (pochodna) zawiera nienazwan
ą
składow
ą
klasy Vector.
2. W tre
ś
ci metod klasy pochodnej wolno odwoływa
ć
si
ę
jawnie
do publicznych (public) i chronionych (protected) składowych
klasy bazowej.
3. Lista inicjacyjna konstruktora pochodnej nie mo
ż
e zawiera
ć
bezpo
ś
rednich inicjatorów składowych bazowej.
Trzeba tu u
ż
y
ć
inicjatora bazowej w postaci jawnego wywołania
jej konstruktora.
4. Ka
ż
dy obiekt klasy pochodnej jest obiektem klasy bazowej.
Ale nie odwrotnie !
5. Ka
ż
da metoda klasy bazowej mo
ż
e by
ć
wywołana na rzecz obiektu
klasy pochodnej (dziedziczenie metod). Nie dotyczy to konstruktorów,
destruktora i operatora przypisania.
Tomasz Marks - Wydział MiNI PW
-7-
Klasa pochodna klasy Vector [4]
Ka
ż
dy PhVectorB jest Vector'em
(uzupełnionym dodatkowymi informacjami).
W konsekwencji:
Vector V, *pV;
PhVectorB PV, *pPV;
V = PV;
// o.k. - przepisz na V cz
ęść
PV opisuj
ą
c
ą
Vector
// (slicing – wycinanie)
Vector W = PV;
// o.k. - inicjuj W cz
ęś
ci
ą
PV opisan
ę
Vector'em
PV = V;
//
BŁ
Ą
D
- co zrobi
ć
z cz
ęś
ci
ą
PV nieopisan
ą
Vector'em?
pV = &PV;
// o.k. - PV jest Vector'em.
pPV = &V;
//
BŁ
Ą
D
- V nie jest typu PhVectorB,
// jaki jest sens pPV->Unit po takim przypisaniu
Vector & rV = PV;
// o.k. - referencja do składowej typu Vector w obiekcie PV
PhVectorB & rPV = V;
//
BŁ
Ą
D
- co oznacza rPV.Unit po takim skojarzeniu?
Tomasz Marks - Wydział MiNI PW
-8-
Klasa pochodna klasy Vector [5]
Ka
ż
dy PhVectorB jest Vector'em
(uzupełnionym dodatkowymi informacjami).
Kompilator niejawnie stosuje konwersje:
Vector V, *pV;
PhVectorB PV, *pPV;
V = PV;
// o.k. V = (Vector) PV
Vector W = PV;
// o.k. Vector W = (Vector) PV;
PV = V;
//
BŁ
Ą
D
pV = &PV;
// o.k. pV = (Vector*) &PV;
pPV = &V;
//
BŁ
Ą
D
Vector & rV = PV;
// o.k. Vector& rV = (Vector&) PV;
PhVectorB & rPV = V;
//
BŁ
Ą
D
Tomasz Marks - Wydział MiNI PW
-9-
PhVectorB – implementacje [1]
// phvectorb.cpp
#include <cstring>
// ew. <string.h> jak w j
ę
z. C
// niektóre kompilatory pozwalaj
ą
pomija
ć
// pliki nagłówkowe 'odziedziczone' z C
#include "phvectorb.h"
const char* PhVectorB::getUnit ( ) const
{ return Unit; }
void PhVectorB::setUnit ( const char* str )
{ strncpy ( Unit, str, U_LEN ); }
………………………………………………………...
…………………………………………………………
Tomasz Marks - Wydział MiNI PW
-10-
PhVectorB – implementacje [2]
PhVectorB::PhVectorB ( int s, double tab[ ], char* u )
{
// odziedziczona składowa klasy Vector ju
ż
istnieje
// zainicjowana wywołaniem konstruktora Vector ( )
strncpy ( Unit, u, U_LEN );
// jak "przewymiarowa
ć
" składow
ą
odziedziczon
ą
, by miała
// rozmiar s i była zapełniona warto
ś
ciami z tablicy tab ???
………………………………………………………...
…………………………………………………………
Tomasz Marks - Wydział MiNI PW
-11-
PhVectorB – implementacje [3]
PhVectorB::PhVectorB ( int s, double tab[ ], char* u )
: Vector ( s, tab )
{
// odziedziczona składowa klasy Vector ju
ż
istnieje
// zainicjowana wywołaniem konstruktora
Vector ( s, tab )
strncpy ( Unit, u, U_LEN );
// i nic wi
ę
cej nie trzeba robi
ć
!!!
}
Tomasz Marks - Wydział MiNI PW
-12-
PhVectorB – implementacje [4]
PhVectorB::PhVectorB ( const PhVectorB& arg )
: Vector ( arg )
{
// odziedziczona składowa klasy Vector ju
ż
istnieje
// zainicjowana wywołaniem konstruktora
Vector ( arg )
strncpy ( Unit, arg.Unit, U_LEN );
// i nic wi
ę
cej nie trzeba robi
ć
!!!
}
Ale
UWAGA:
Niemal dokładnie to samo zrobi predefiniowany
konstruktor kopiuj
ą
cy, realizuj
ą
cy kopiowanie płytkie: dla pól
obiektowych zostan
ą
u
ż
yte odpowiednie konstruktory kopiuj
ą
ce,
a dla pozostałych (nieobiektowych i tablicowych) wykonane b
ę
dzie
proste kopiowanie zawarto
ś
ci pami
ę
ci.
WNIOSEK:
Definicj
ę
(i deklaracj
ę
) tego konstruktora mo
ż
na (nale
ż
y)
w klasie PhVectorB pomin
ąć
.
Tomasz Marks - Wydział MiNI PW
-13-
PhVectorB – implementacje [5]
W wersji
PhVectorB::PhVectorB ( const PhVectorB& arg )
: Vector ( arg )
{ .... }
kompilator zastosuje konwersj
ę
PhVectorB::PhVectorB ( const PhVectorB& arg )
: Vector ( (Vector&)arg )
{ .... }
Zauwa
ż
my te
ż
,
ż
e gdyby
ś
my si
ę
zdecydowali pisa
ć
własn
ą
wersj
ę
konstruktora kopiuj
ą
cego, to mogliby
ś
my alternatywnie u
ż
y
ć
innego
konstruktora dla inicjalizacji składowej dziedziczonej (zakładaj
ą
c
dost
ę
pno
ść
pól Size i V ):
PhVectorB::PhVectorB ( const PhVectorB& arg )
: Vector ( arg.Size, arg.V )
{ .... }
Tomasz Marks - Wydział MiNI PW
-14-
Tworzenie / niszczenie obiektów [2]
Etapy tworzenia obiektu:
1. Przydział pami
ę
ci dla niestatycznych pól obiektu,
w tym składowej bazowej
.
2. Wykonanie inicjalizacji w oparciu o pełn
ą
list
ę
inicjacyjn
ą
z uwzgl
ę
dnieniem inicjatora składowej bazowej
.
3. Wykonanie tre
ś
ci
konstruktor
a.
Etapy niszczenia obiektu:
1. Wykonanie tre
ś
ci
destruktor
a.
2. Wykonanie tre
ś
ci destruktorów pól składowych,
z uwzgl
ę
dnieniem destruktora składowej bazowej
.
3. Zwolnienie pami
ę
ci przydzielonej dla niestatycznych pól obiektu,
w tym składowej bazowej
.
Tomasz Marks - Wydział MiNI PW
-15-
PhVectorB – implementacje [6]
PhVectorB::~PhVectorB ( )
{
// nic nie trzeba robi
ć
!!!
}
UWAGA 1.:
Je
ż
eli destruktor został zadeklarowany, to musimy
zdefiniowa
ć
jego implementacj
ę
.
UWAGA 2.:
Dokładnie to samo nic zrobiłby destruktor predefiniowany.
WNIOSEK:
Definicj
ę
(i deklaracj
ę
) destruktora mo
ż
na (nale
ż
y)
w klasie PhVectorB pomin
ąć
.
Tomasz Marks - Wydział MiNI PW
-16-
PhVectorB – implementacje [7]
PhVectorB& PhVectorB::operator = ( const PhVectorB& rhs )
{
(Vector&)*this = (Vector&) rhs;
// operacja w klasie Vector
strcpy ( Unit, rhs.Unit );
return *this;
}
Ale
UWAGA:
Niemal dokładnie to samo zrobi predefiniowany
operator przypisania, realizuj
ą
cy kopiowanie płytkie: dla pól
obiektowych zostan
ą
u
ż
yte odpowiednie operatory przypisania,
a dla pozostałych (nieobiektowych i tablicowych) wykonane b
ę
dzie
proste kopiowanie zawarto
ś
ci pami
ę
ci.
WNIOSEK:
Definicj
ę
(i deklaracj
ę
) tego operatora mo
ż
na (nale
ż
y)
w klasie PhVectorB pomin
ąć
.
Tomasz Marks - Wydział MiNI PW
-17-
PhVectorB – implementacje [8]
Implementacje strumieniowych operatorów WE/WY mog
ą
wygl
ą
da
ć
jak ni
ż
ej:
istream& operator >> ( istream& inp, PhVectorB& vec )
{
inp >> (Vector&) vec;
// konieczna jawna konwersja !!!
inp >> vec.Unit;
return inp;
}
ostream& operator << ( ostream& out, const PhVectorB& vec )
{
out << (Vector&) vec;
// konieczna jawna konwersja !!!
out << "[" << vec.Unit << "]";
return out;
}
Tomasz Marks - Wydział MiNI PW
-18-
PhVectorB – u
ż
ycie [1]
// phvb_prog.cpp
#include "phvectorb.h"
double forA [ ] = { 1., 2., 3., };
double forB [ ] = { 4., 5., 6., };
int main ( )
{
PhVectorB A ( 3, forA, "m/s" ), B ( 3, forB, "m/s" ), C;
Vector D;
double a, b; int i, j;
char txt [ 20 ];
strcpy ( txt, A.getUnit ( ) );
// o.k.
j = B.W.getSize ( );
//
BŁ
Ą
D
(brak pola W w obiekcie B)
i = B.getSize ( );
// O.K. ! ( getSize – odziedziczona metoda )
b = A.W [ 2 ];
//
BŁ
Ą
D
(brak pola W w obiekcie B)
a = A [ 2 ];
// O.K. ! ( [ ] – odziedziczona metoda)
C.setUnit ( "m/s2" );
// o.k.
C = A + B;
//
BŁ
Ą
D
A + B;
// O.K. ! (wynik typu Vector)
D = A + B;
// o.k.
Tomasz Marks - Wydział MiNI PW
-19-
PhVectorB – u
ż
ycie [2]
WNIOSEK:
Metody takie jak
getSize()
(pobranie rozmiaru),
[ ]
(indeksowanie),
s
ą
dost
ę
pne dla obiektów klasy PhVectorB, bez jawnej kwalifikacji
nazw
ą
pola klasy Vector (tej nazwy faktycznie nie ma).
Te składowe s
ą
odziedziczone z klasy Vector.
Operator
+ (dodawanie),
mo
ż
e by
ć
zastosowany do argumentów typu PhVectorB, ale jego
wynikiem jest obiekt typu Vector. Aby dysponowa
ć
dodawaniem
zwracaj
ą
cym typ PhVectorB, trzeba przeci
ąż
y
ć
go dla obiektów
tej klasy.
Tomasz Marks - Wydział MiNI PW
-20-
Klasa pochodna klasy Vector [6]
// phvectorb.h
#include "vector.h"
#define U_LEN 10
class PhVectorB : public Vector
{
char Unit [ U_LEN ];
public:
PhVectorB ( int = 0, double* = 0, char* = "" );
void setUnit ( const char* );
const char* getUnit ( ) const;
....................................
friend PhVectorB operator + (const PhVectorB&, const PhVectorB& );
friend istream& operator >> ( istream&, PhVectorB& );
friend ostream& operator << ( ostream&, const PhVectorB& );
};
Tomasz Marks - Wydział MiNI PW
-21-
PhVectorB – implementacje [9]
// funkcja zaprzyja
ź
niona
PhVectorB operator+ ( const PhVectorB& a, const PhVectorB& b )
{
PhVectorB c;
(Vector&) c = (Vector&) a + (Vector&) b;
// operacja w klasie Vector
c.setUnit( a.Unit );
return c;
}
Tomasz Marks - Wydział MiNI PW
-22-
PhVectorB – implementacje [9a]
// funkcja zaprzyja
ź
niona (inny wariant)
PhVectorB operator+ ( const PhVectorB& a, const PhVectorB& b )
{
PhVectorB sum ( ( a.getSize() > b.getSize() )
? a.getSize() : b.getSize(), 0, (char*) a.Unit );
for ( int i = 0; i < sum. getSize(); ++i )
{
if ( i < a.getSize() ) sum[ i ] = a.getAt( i );
if ( i < b.getSize() ) sum[ i ] += b.getAt( i );
}
return sum;
}
Tomasz Marks - Wydział MiNI PW
-23-
PhVectorB – implementacje [...]
W tym wariancie nie ma bezpo
ś
rednich odwoła
ń
do składowych
odziedziczonych z klasy Vector, poniewa
ż
s
ą
one w niej prywatne.
ś
eby mo
ż
na było napisa
ć
pro
ś
ciej ten wariant dodawania,
trzeba zmodyfikowa
ć
definicj
ę
klasy bazowej:
class Vector
{
public:
int Size;
double *V;
private:
static int DefSize;
void Init ( int size=0, double val=0, double *arr=0 );
public:
Vector ( );
……………..
……………..
};
Tomasz Marks - Wydział MiNI PW
-24-
PhVectorB – implementacje [9b]
Teraz poprawna b
ę
dzie implementacja w postaci:
// funkcja zaprzyja
ź
niona (jeszcze inny wariant)
PhVectorB operator+ ( const PhVectorB& a, const PhVectorB& b )
{
Vector sum ( ( a.Size > b.Size ) ? a.Size : b.Size );
for ( int i = 0; i < sum. Size; ++i )
{
if ( i < a.Size ) sum.V[ i ] = a.V[ i ];
if ( i < b.Size ) sum.V[ i ] += b.V[ i ];
}
return PhVectorB ( sum.Size, sum.V, (char*)a.Unit );
}
Tomasz Marks - Wydział MiNI PW
-25-
PhVectorB – implementacje [...]
Faktycznie najsensowniejsze byłoby mie
ć
klas
ę
Vector zdefiniowan
ą
z u
ż
yciem składowych chronionych:
class Vector
{
protected:
int Size;
double *V;
private:
static int DefSize;
void Init ( int size=0, double val=0, double *arr=0 );
public:
Vector ( );
……………..
……………..
};
Tomasz Marks - Wydział MiNI PW
-26-
PhVectorB – implementacje [9d]
Mo
ż
na temu zaradzi
ć
deklaruj
ą
c sum, jako zmienn
ą
typu PhVectorB:
// funkcja zaprzyja
ź
niona (jeszcze inny wariant)
PhVectorB operator+ ( const PhVectorB& a, const PhVectorB& b )
{
PhVectorB
sum ( (a.Size>b.Size)?a.Size:b.Size, 0, (char*)a.Unit );
for ( int i = 0; i < sum. Size; ++i )
{
if ( i < a.Size ) sum.V[ i ] = a.V[ i ];
if ( i < b.Size ) sum.V[ i ] += b.V[ i ];
}
return sum;
}
Tomasz Marks - Wydział MiNI PW
-27-
METODY
WIRTUALNE
Tomasz Marks - Wydział MiNI PW
-28-
Metody wirtualne – wprowadzenie [1]
W klasie Vector zadeklarowali
ś
my metod
ę
Norm()
class Vector
{
……………..
public:
double Norm ( ) const;
……………..
};
o implementacji
// norma euklidesowa
double Vector :: Norm ( ) const
{
double s = 0;
for ( int i = 0; i < Size; ++i ) s += V[ i ]*V[ i ];
return sqrt( s );
}
Tomasz Marks - Wydział MiNI PW
-29-
Metody wirtualne – wprowadzenie [2]
Je
ż
eli w klasie pochodnej PhVectorB chcemy przedefiniowa
ć
metod
ę
Norm(),
to jest to dozwolone:
class PhVectorB : public Vector
{
……………..
public:
double Norm ( ) const;
……………..
};
o implementacji
// norma taksówkowa
double PhVectorB :: Norm ( ) const
{
double s = 0;
for ( int i = 0; i < Size; ++i ) s += fabs( V[ i ] );
return s;
}
Tomasz Marks - Wydział MiNI PW
-30-
Metody wirtualne – wprowadzenie [3]
Rozpatrzmy teraz fragment programu:
Vector V( 4, 1.0 );
PhVectorB P ( 4 ); P[0] = P[1] = P[2] = P[3] = 1;
cout << V.Norm();
// 2
cout << P.Norm();
// 4 //norma taksówkowa 'zasłoniła' norm
ę
euklidesow
ą
Vector* pV = &V;
PhVectorB* pP = &P;
cout << pV->Norm();
// 2
cout << pP->Norm();
// 4
cout << pP->Vector::Norm();
// 2
pV = &P;
cout << pV->Norm();
// 2
I to jest zgodne z oczekiwaniem, poniewa
ż
pV jest typu Vector*,
wi
ę
c "z natury" uruchamia metody nale
żą
ce do klasy Vector.
cout << pV->getUnit();
//
BŁ
Ą
D !
Tomasz Marks - Wydział MiNI PW
-31-
Metody wirtualne [1]
W klasie Vector zadeklarujemy metod
ę
Norm() jako metod
ę
wirtualn
ą
.
class Vector
{
……………..
public:
virtual
double Norm ( ) const;
……………..
};
W implementacji nic si
ę
nie zmienia (virtual zakazane):
// norma euklidesowa
double Vector :: Norm ( ) const
{
double s = 0;
for ( int i = 0; i < Size; ++i ) s += V[ i ]*V[ i ];
return sqrt( s );
}
Tomasz Marks - Wydział MiNI PW
-32-
Metody wirtualne [2]
W klasie pochodnej PhVectorB mo
ż
emy, ale nie musimy, powieli
ć
specyfikator virtual:
class PhVectorB : public Vector
{
……………..
public:
virtual
double Norm ( ) const;
……………..
};
W implementacji nic si
ę
nie zmienia (virtual zakazane):
// norma taksówkowa
double PhVectorB :: Norm ( ) const
{
double s = 0;
for ( int i = 0; i < Size; ++i ) s += fabs( V[ i ] );
return s;
}
Tomasz Marks - Wydział MiNI PW
-33-
Metody wirtualne [3]
Rozpatrzmy jeszcze raz ten sam fragment programu co poprzednio:
Vector V( 4, 1.0 );
PhVectorB P ( 4 ); P[0] = P[1] = P[2] = P[3] = 1;
cout << V.Norm();
// 2
cout << P.Norm();
// 4 //norma taksówkowa 'zasłoniła' norm
ę
euklidesow
ą
Vector* pV = &V;
PhVectorB* pP = &P;
cout << pV->Norm();
// 2
cout << pP->Norm();
// 4
cout << pP->Vector::Norm();
// 2
pV = &P;
cout << pV->Norm();
// 4
//norma taksówkowa z klasy PhVectorB
cout << pV->Vector::Norm();
// 2 //norma euklidesowa z klasy Vector
Tym razem wska
ź
nik pV, który jest typu Vector*, po zainicjowaniu wskazaniem
na obiekt klasy pochodnej uruchamia wersj
ę
metody zdefiniowan
ą
w klasie,
do której nale
ż
y obiekt.
Tomasz Marks - Wydział MiNI PW
-34-
Metody wirtualne [4]
Podobnie b
ę
dzie dla referencji:
Vector V( 4, 1.0 );
PhVectorB P ( 4 ); P[0] = P[1] = P[2] = P[3] = 1;
cout << V.Norm();
// 2
cout << P.Norm();
// 4 //norma taksówkowa 'zasłoniła' norm
ę
euklidesow
ą
Vector& rV = V;
PhVectorB& rP = P;
cout << rV.Norm();
// 2
cout << rP.Norm();
// 4
cout << rP.Vector::Norm();
// 2
Vector& rV2 = P;
cout << rV2.Norm();
// 4
//norma taksówkowa z klasy PhVectorB
cout << rV2.Vector::Norm();
// 2 //norma euklidesowa z klasy Vector
Referencja rV2, która jest typu Vector&, po zainicjowaniu obiektem
klasy pochodnej uruchamia wersj
ę
metody zdefiniowan
ą
w klasie,
do której nale
ż
y obiekt.
Tomasz Marks - Wydział MiNI PW
-35-
Koniec wykładu 7.