instrukcja

Transkrypt

instrukcja
06
Metodyka i Techniki Programowania II
C++: Wprowadzenie do projektowania obiektowego cz. 2
dr inż. Jarosław Bułat, modyfikacje: dr inż. Jacek Dańda
2013.03.27
Do realizacji poniższych zadań staraj się używać IDE Code::Blocks.
Najistotniejsze mechanizmy programowania obiektowego to: dziedziczenie, abstrakcja, enkapsulacja
i polimorfizm.
Dziedziczenie
Często nową klasę można stworzyć w oparciu o istniejącą już definicję klasy. Jeśli te dwie klasy są ze
sobą powiązane pojęciowo i logicznie, na przykład: jeśli projektujemy nowy rodzaj przycisków do
aplikacji, który różni się od wcześniej używanych przycisków na przykład tym, że na nowym
przycisku może być narysowana ikona, to możliwe jest wykorzystanie mechanizmu dziedziczenia.
Mechanizm ten nie tylko upraszcza pisanie nowego kodu, ale również otwiera drogę dla dwóch
kolejnych technik: abstrakcji oraz polimorfizmu.
W najprostszym ujęciu, dziedziczenie używane jest po to, aby na bazie starej klasy zbudować nową,
rozszerzoną klasę. Oryginalna klasa wyjściowa dla mechanizmu dziedziczenia nazywana jest klasą
bazową, a klasa utworzona z klasy bazowej i uzupełniona o nowe składowe to klasa pochodna.
Nowa klasa może korzystać z metod starej klasy. Funkcjonalność klasy bazowej jest podzbiorem
funkcjonalności klasy pochodnej.
W przykładzie poniżej jako klasę bazową zaimplementowano klasę CDROM. Umożliwia ona otwieranie i
zamykanie napędu metodą eject(...) oraz odczytywanie treści płytki metodą read(...).
Następnie zaimplementowano klasę pochodną CDRW rozszerzającą CDROM o możliwość zapisu płytki.
Klasa pochodna wykorzystuje wszystkie elementy klasy bazowej.
#include <iostream>
#include <string.h>
using namespace std;
class CDROM{
protected:
bool cdInDevice;
char content[10];
// klasa bazowa
public:
CDROM( void ){ cdInDevice = false; };
void eject( void )
{ cout << "CD in Device = " << cdInDevice << endl; };
void read( void ){ cout << "content: " << content << endl; };
};
class CDRW : public CDROM {
// klasa pochodna
public:
CDRW( void ){ cdInDevice = false; };
void write( char *data ){ strcpy( content, data ); };
};
int main() {
CDRW cdrw;
cdrw.eject();
cdrw.write( "nagranie" );
cdrw.read();
return 0;
}
Zmodyfikuj powyższy przykład tak, aby metoda eject(...) zmieniała wartość zmiennej
cdInDevice, a metody read(...) i write(...) wykonywały polecenia wyłącznie wtedy, gdy płytka
jest w napędzie. Następnie w konstruktorze klasy bazowej wypełnij ,,na stałe'' tablicę content, tak,
aby metoda read(...) wypisywała jakiś tekst.
Wykorzystując mechanizm dziedziczenia zaprojektuj klasę BD (Blu-ray). Napęd BD charakteryzuje się
tym, że content może zawierać 20 znaków. Można to zrobić na dwa sposoby:
– prosty (brzydki): wykorzystując z klasy bazowej wyłącznie eject(...) i cdInDevice.
– trudny (porządny): dziedzicząc po CDRW i modyfikując CDROM tak aby konstruktorem zmieniać
wielkość bufora content (10 dla CDROM i CDRW oraz 20 dla BD), pomysł wymaga dynamicznej
alokacji pamięci.
Przeciążenie jako przykład polimorfizmu statycznego
Polimorfizm to mechanizm pozwalający programiście używać elementów programu na kilka różnych
sposobów. Technika programowania zwana przeciążeniem (ang. overloading, przeładowanie) jest
przykładem polimorfizmu statycznego, czyli takiego, który jest wykonywany na etapie kompilacji.
Polimorfizm dynamiczny odbywa się w czasie wykonania kodu.
W poniższym kodzie przeciążono konstruktor klasy napis. Przeciążono względem typu argumentu.
Zadanie klasy napis to przechowywanie (i w przyszłości obróbka) tekstu. Podczas tworzenia obiektu
klasy można wprowadzić albo tekst albo liczbę. W zależności czy zostanie wprowadzony teksty czy
liczba wykona się odpowiedni konstruktor. W pierwszym przypadku wystarczy zarezerwować miejsce
na tekst i go przekopiować w drugim przypadku należy dodatkowo przekonwertować liczbę na tekst.
class napis{
public:
napis( char* text );
// przeciążony konstruktor
napis( int number );
// przeciążony konstruktor
~napis();
void wpisz( char* inText ){ strcpy( text, inText ); };
void print( void ){ cout << "tresc:" << text << endl; };
private:
char* text;
};
napis::napis( char *inText ){
text = new char[ strlen(inText) ];
strcpy( text, inText );
}
napis::napis( int number ){
char buffer[20];
sprintf( buffer, "%d", number );
text = new char[ strlen(buffer) ];
strcpy( text, buffer );
}
// konwersja liczby na tekst
napis::~napis(){
delete [] text;
}
int main() {
napis n1( "to jest pierwszy napis" );
n1.print();
napis n2( 10 );
n2.print();
return 0;
}
Przy okazji zadania wykorzystano dynamiczną alokację pamięci w konstruktorze za pomocą
operatora new dostępnego w C++. W destruktorze zwolniono zarezerwowaną pamięć.
Wykorzystując mechanizm przeciążania, dodaj możliwość inicjalizowania klasy napis liczbą
zmiennoprzecinkową i dwoma liczbami zmiennoprzecinkowymi (wektorem).
Przeciążenie operatorów
Jeszcze lepszym przykładem polimorfizmu statycznego jest przeciążanie operatorów. Jako przykład
wykorzystamy klasę vector2d z poprzedniego laboratorium. Zamiast metody dodaj(...)
pozwalającej dodać dwa wektory wykorzystany zostanie znak dodawania: ,,+''.
#include <iostream>
using namespace std;
class vector2d{
public:
int x1, x2;
vector2d( int xx1, int xx2 ){
x1 = xx1;
x2 = xx2;
};
};
void wyswietl( void ){ cout << x1 << " " << x2 << endl; };
vector2d operator+( vector2d a ){ return vector2d( x1+a.x1, x2+a.x2 ); };
int main() {
vector2d x( 3, 1 );
vector2d y( 1, 2 );
x.wyswietl();
y.wyswietl();
vector2d z = x + y;
z.wyswietl();
}
Wykonaj przeciążenie operatora <<, tak aby zamiast metody wyswietl(...) był możliwe było użycie
strumienia wyjściowego w następujący sposób:
cout << "wektor z:" << z << endl;