|
MODEL 1 - każde dana jest klasą
( klasy są jednopolowe )
MODEL 2 - model opcjonalny
MODEL 3 - model z jedną tylko klasą
= model strukturalny
Konstruktory
class DATA
{ int dzień, miesiąc, rok;
public:
DATA( int, int, int);
. . . . . . . . . . . . . . . . . . .
};
DATA dzisiaj = DATA(2, 3, 2001);
DATA moje_urodziny(12, 10, 1973);
class DATA
{ int dzień, miesiąc, rok;
public:
DATA( int, int, int);
DATA( int, int); // wystarczy podać dzień i miesiąc
DATA( int); // wystarczy podać dzień
DATA( ); // konstruktor domyślny
DATA( const char * ); // data w postaci napisu do
// rozpakowania
};
DATA dzisiaj, d; DATA data(4); DATA dzien(5, 2);
DATA boże_narodzenie( ”25 grudnia 2000”);
Zasady tworzenia i używania konstruktorów:
W deklaracji konstruktora nie wolno określać typu zwracanej wartości, nawet void,
Wybór właściwego konstruktora odbywa się tak jak funkcji przeciążonej,
Jeśli zadeklarowano jakikolwiek konstruktor jawny, to należy zadeklarować i zdefiniować konstruktor domyślny,
Konstruktory nie są dziedziczone,
Identyfikator konstruktora jest identyczny z nazwa klasy,
Nie można pobrać adresu konstruktora,
Konstruktor nie może być funkcją wirtualną.
class PUNKT
{
int x, y;
unsigned kolor;
public:
PUNKT(void): x(0), y(0), kolor(0) { }
PUNKT ( int _x, int _y, unsigned _kolor = 0);
PUNKT ( unsigned _kolor);
. . . . . . . . . . . . . . . . . . . . . . .
void UstawWsp( int _x, int _y);
void UstawKolor( unsigned _kolor);
void Rysuj( void);
};
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
PUNKT:: PUNKT( int _x, int _y, unsigned _kolor = 0):
x( _x ), y( _y ), kolor( _kolor ) { }
PUNKT:: PUNKT( unsigned _kolor ):
x( 0 ), y( 0 ), kolor( _kolor ) { }
void PUNKT:: UstawWsp( int _x, int _y)
{ x = _x; y = _y; }
Przykłady poprawnych definicji obiektów:
PUNKT p1, p2( 1, 1 ), p3( 10, 10, 5 ), p4( 1 );
PUNKT tab[ 10 ], * ptr;
Niejednoznaczności
class X
{
public:
X( void );
X( int j = 1);
};
Deklaracja X obj; jest dla kompilatora niejednoznaczna.
Zasady używania list inicjacyjnych:
Obiekty będące składowymi innych klas oraz klasy bazowe mogą inicjowane tylko w liście inicjacyjnej
Składowe stałe i referencyjne mogą być inicjowane tylko w liście inicjacyjnej.
Uwaga: Jeżeli obiekt nie ma jawnego konstruktora, może być inicjowany wartościami innego obiektu tej samej klasy, np. DATA d = dzisiaj;
Konstrukcja „podobiektów” (obiektów składowych klasy)
class PUNKT_2D
{
int x, y;
public:
PUNKT_2D(void): x(0), y(0) { }
PUNKT_2D( int _x, int _y ): x( _x ), y( _y ) { }
int GetX( void ) {return x;}
int GetY( void ) {return y;}
};
class ODCINEK
{
PUNKT_2D A, B;
public:
ODCINEK(void) { }; // konstruktor 1
ODCINEK( int ax, int ay, int bx, int by):
A( ax, bx),
B( bx, by)
{ } // konstruktor 2
ODCINEK( PUNKT_2D &A, PUNKT_2D &B):
A( A ),
B( B )
{ } // konstruktor 3
};
Przykłady poprawnych definicji obiektów:
PUNKT_2D A, B( 10, 10 );
ODCINEK O1( 10, 20, 110, 120 ), O2, O3( A, B);
Komentarz:
Obiekt O2 zostanie skonstruowany przy pomocy konstruktora bezparametrowego konstruktor 1. Ponieważ lista inicjacyjna tego konstruktora jest pusta kompilator przyjmie przez domniemanie, że ma on postać
ODCINEK(void): A( ), B( ) { } // konstruktor 1
a co za tym idzie - do inicjacji obiektów składowych A i B zostanie użyty konstruktor domyślny klasy PUNKT_2D.
Konstruktory kopiujące
Ich zadaniem jest tworzenie obiektów z innych obiektów tej samej klasy poprzez kopiowanie tych obiektów łącznie ze strukturami związanymi z obiektem.
class X
{ . . . . . . . .
public:
X( const X& ); // konstruktor kopiujący
};
Przykład:
#include <string.h>
class STRING
{
char *str;
public:
STRING( void ): str( new char[10] ) { }
// konstruktor domyślny
STRING(char *s ): str( s ) { }
STRING( const STRING &s ): str( strdup( s.str) ) { }
~ STRING( void ) { delete [ ] str; } // destruktor
};
Przykłady:
STRING Imie( “Jan” ); // konstrukcja obiektu stałego
STRING Imie1( Imie ); // 1
// jawne użycie konstruktora kopiującego
STRING Imie1 = Imie; // 2
// niejawnie użycie konstruktora kopiującego
Gdyby nie zdefiniowano konstruktora kopiującego:
w przypadku // 1 kompilator użyłby konstruktora kopiującego domyślnego,
w przypadku // 2 kompilator użyłby konstruktora domyślnego bez kopiowania zawartości łańcucha, w rezultacie czego oba obiekty wskazywałyby na ten sam łańcuch.
Uwaga: Znak = nie jest operatorem przypisania (sic !)
Destruktory
Destruktor jest funkcją bezparametrową.
Destruktor, oprócz usunięcia obiektu, powinien „posprzątać po sobie”, np. usunąć z ekranu okienko obsługujące obiekt, itp.
Przykład niejawnego użycia destruktora delete Imie;
Jeśli nie zdefiniowano destruktora jawnego kompilator wygeneruje destruktor domyślny (zwykle nie sprząta po sobie).
Jawne wywołanie destruktora: STRING:: ~STRING( );
Usuwanie obiektów automatycznych:
Przykład:
void f( char *s)
{ STRING obS( s );
. . . . . . . . . . . . . . .
}
Zakończenie wykonywania się funkcji spowoduje wywołanie destruktora (gdyby nie był zdefiniowany - domyślnego) i usunięcie obiektu automatycznego obS.
Tworzenie i usuwanie obiektów dynamicznych:
Przykład:
STRING *ptrS = new STRING( „Andrzej” );
delete ptrS; // wywołany zostanie destruktor
Przykład - obsługa stosu statycznego
(bez obsługi błędów)
rozmiar
class STOS
{
int rozmiar;
char * stos; // wskaźnik stosu
char * top; // wskaźnik wierzchołka pusty stos s1
public:
STOS( int r ) { top = stos = new char[ rozmiar = r ]; }
~ STOS( ) { delete [ ]stos; }
void push( char c ) { *top++ = c; }
char pop( ) { return *− −top; }
};
Przykłady użycia tak zdefiniowanej klasy:
STOS s1( 100 ); STOS s2( 200 ); s1.push( `a' );
s2.push( s1.pop( ) ); char c = s2.pop( );
cout<< ch << `\n';
Dziedziczenie proste
Umożliwia uszczegóławianie już zdefiniowanych klas bez potrzeby ponownej kompilacji klas bazowych
rozbudowywalność
Przykład:
class PRACOWNIK
{
char * Nazwisko, * Dział;
int Uposażenie;
. . . . . . . . . . . . . . . . . . . . .
};
class KIEROWNIK: public PRACOWNIK
{
int Uprawnienia[ 8 ];
. . . . . . . . . . . . . . . . . . . . .
};
Przykłady poprawnych definicji obiektów i wskazań do obiektów w warunkach dziedziczenia prostego:
KIEROWNIK kk;
PRACOWNIK *p = & kk;
// każdy kierownik jest jednocześnie pracownikiem,
PRACOWNIK pp;
KIEROWNIK *k = &pp;
// . . . ale nie każdy pracownik kierownikiem
|
Dziedziczenie i polimorfizm
W poniższym przykładzie zaprezentowane zostanie użycie: klas abstrakcyjnych, funkcji wirtualnych i czysto wirtualnych (abstrakcyjnych) oraz omówiony mechanizm polimorfizmu.
Problem:
Zdefiniować klasę, mogącą przechowywać wskaźniki do obiektów różnych typów i zapewniającą ich wizualizacje za pomocą jednej funkcji Out( ).
class ELEMENT
{
public:
void Out( );
};
rozmiar = 5
typedef BOOL;
class KOSZ
{
ELEMENT **tab; // tablica wskaźników do obiektów
unsigned rozmiar, ostatni;
public:
KOSZ( unsigned rozm = 10 );
~ KOSZ( void );
BOOL DoKosza( ELEMENT * );
// weźmie do kosza wskazanie obiektu
dowolnego typu
void Out( void );
// wysyła na wyjście wszystkie obiekty znajdu-
jące się w koszu niezależnie od ich typu
};
KOSZ:: KOSZ( unsigned rozm ):
tab( new ELEMENT *[ rozm ],
rozmiar( rozm), ostatni( 0 ) { }
KOSZ:: ~KOSZ( void )
{
for(int i = 0; i < ostatni; ++i) delete tab[ i ];
delete tab;
};
BOOL KOSZ:: Do Kosza( ELEMENT *pE1)
{ if( ostatni < rozmiar )
{ tab[ ostatni ] = pE1; ++ostatni;
return 1; }
else return 0;
}
void KOSZ:: Out( void )
{
for( int i=0; i < ostatni; i++) tab[ i ] → Out( );
}
|
Teraz możemy definiować klasy zupełnie dowolnych obiektów dziedziczących z klasy ELEMENT
class STUDENT: public ELEMENT
{
char *nazwisko;
int wiek;
public:
STUDENT( char *_nazwisko, int _wiek);
~STUDENT( );
void Out( );
};
STUDENT:: STUDENT( char *_nazwisko, int _wiek):
nazwisko( strdup( _nazwisko ), wiek( _wiek) { }
STUDENT:: Out( void )
{ cout<< “\nNazwisko “<<nazwisko
<<”\nWiek “<<wiek; }
|
Przedefiniujmy więc klasę ELEMENT
class ELEMENT
{
public:
virtual ~ELEMENT( );
virtual void Out( ) = 0;
// deklaracja funkcji abstrakcyjnej (czysto wirtualnej)
};
Klasę KOSZ tez można wyprowadzić z klasy ELEMENT. Wtedy będzie można UMIESZCZAĆ W Koszu inne Kosze wraz z zawartością (sic ! ).
Oto poprawiona definicja klasy KOSZ:
class KOSZ: public ELEMENT
{
ELEMENT **tab;
unsigned rozmiar, ostatni;
public:
KOSZ( unsigned rozm = 10 );
virtual ~ KOSZ( void ); // wirtualny destruktor
BOOL DoKosza( ELEMENT * );
virtual void Out( void ); // wirtualna metoda
};
Również klasy STUDENT i LITERA, dziedziczące z klasy ELEMENT będą miały wirtualne destruktory i metody Out( ).
Funkcja testująca zdefiniowaną wyżej strukturę klas:
void main( )
{
KOSZ *k1= new KOSZ(3), *k2=new KOSZ(2);
// oba obiekty są dynamiczne
k1→DoKosza( new STUDENT(„Jan Nowak”, 20);
k1→DoKosza( new LITERA(`B');
k1→DoKosza( new STUDENT(„Anna Jopek”, 25);
k2→DoKosza( new LITERA(`Q');
k2→DoKosza(k1);
k2→Out( ); // polimorfizm
delete k2; // polimorfizm
return 0;
};
|
Zasady używania funkcji wirtualnych:
Można tworzyć obiekty klas zawierających funkcje wirtualne, ale jeśli chociaż jedną z tych funkcji uczynimy abstrakcyjną, klasa staje się abstrakcyjną.
Typ funkcji wirtualnej jest deklarowany w klasie bazowej i nie może ulec zmianie w klasach pochodnych.
Funkcja wirtualna musi być zdefiniowana w klasie, w której po raz pierwszy została zadeklarowana.
Klasy pochodne nie muszą korzystać z funkcji wirtualnych klas bazowych.
Jeśli wywołuje się funkcję wirtualną z kwalifikatorem zakresu, np. KOSZ:: Out( ), mechanizm wirtualny nie działa.
Dziedziczenie wielobazowe
Postać ogólna nagłówka definicji klasy pochodnej
class oznacznik : lista_klas_bazowych
Element listy klas bazowych ma postać:
< public | protected | private > ozn_klasy_bazowej
Dostępność klas bazowych w klasach pochodnych:
Rodzaj dziedziczenia |
Składowe klasy bazowej ... |
w klasach pochodnych są ... |
public |
private |
niedostępne |
|
protected |
protected |
|
public |
public |
protected |
private |
niedostępne |
|
protected |
protected |
|
public |
protected |
private |
private |
niedostępne |
|
protected |
prywatnymi klasy pochodnej |
|
public |
prywatnymi klasy pochodnej |
29
MODEL 1
MODEL 2
MODEL 3
top
stos
Ta klasa mogłaby być klasą abstrak-cyjną dla takiej klasy
Zdefiniujmy wobec tego klasę podstawową
KOSZ zawierającą wszystko co niezbędne
tab
student
litera
ostatni
ELEMENT
void Out( void );
LITERA
char ch;
void Out( void );
STUDENT
char * nazwisko;
int wiek;
void Out( void );
Klasa abstrakcyjna - nie można tworzyć obiektów