background image

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& );

};

background image

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 cz

ęść

PV opisuj

ą

c

ą

Vector

// (slicing – wycinanie)

Vector W = PV;

// o.k.  - inicjuj cz

ęś

ci

ą

PV opisan

ę

Vector'em

PV = V;

//

Ą

D

- co zrobi

ć

z cz

ęś

ci

ą

PV nieopisan

ą

Vector'em?

pV = &PV;

// o.k.  - PV jest Vector'em.

pPV = &V;

//

Ą

D

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;

//

Ą

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;

//

Ą

D

pV = &PV;

// o.k.               pV = (Vector*) &PV;

pPV = &V;

//

Ą

D

Vector &  rV = PV;

// o.k.     Vector& rV = (Vector&) PV;

PhVectorB &  rPV = V;

//

Ą

D

background image

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

ąć

.

background image

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  ):

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

ąć

.

background image

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 ( );                    

// 

Ą

D   

(brak pola W w obiekcie B)

i = B.getSize ( );                        

// O.K. ! ( getSize – odziedziczona metoda )

b = A.W [ 2 ];                             

// 

Ą

D   

(brak pola W w obiekcie B)

a = A [ 2 ];                                 

// O.K. ! ( [ ] – odziedziczona metoda)

C.setUnit ( "m/s2" );                 

// o.k.

C = A + 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& );

};

background image

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 );

}

background image

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 );

}

background image

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();

//

Ą

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;

}

background image

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.