Programowanie obiektowe w C++ Wykład 6
Transkrypt
Programowanie obiektowe w C++ Wykład 6
Programowanie obiektowe w C++
Wykªad 6
dr Lidia St¦pie«
Akademia im. Jana Dªugosza
w Cz¦stochowie
L. St¦pie« (AJD)
Programowanie obiektowe w C++
1 / 33
Dziedziczenie a dynamiczny przydziaª pami¦ci
Jak dziedziczenie wspóªpracuje z dynamicznym przydziaªem pami¦ci
(new i delete)?
Je±li klasa bazowa korzysta z dynamicznego przydziaªu pami¦ci
(zdeniowane konstruktor kopiuj¡cy, destruktor i operator
przypisania), to jak wpªywa to na implementacj¦ klasy pochodnej?
Odpowied¹ zale»y od charakteru klasy pochodnej, od tego czy klasa
pochodna wykorzystuje dynamiczny przydziaª pami¦ci czy te» nie.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
2 / 33
Klasa pochodna bez dynamicznego zarz¡dzania pami¦ci¡
Zaªó»my, »e klas¡ bazow¡ jest klasa A, która wykorzystuje dynamiczne
zarz¡dzanie pami¦ci¡. Ma wi¦c zdeniowane: konstruktor kopiuj¡cy,
destruktor i operator przypisania.
class A{
private:
char *l;
int r;
public:
A(const char* = "brak", int = 0);
A(const A&);
~A();
operator=(const A&);
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
3 / 33
Klasa pochodna bez dynamicznego zarz¡dzania pami¦ci¡
Zaªó»my teraz, »e tworzymy klas¦ pochodn¡ B, która nie u»ywa sªów
kluczowych new i delete, ani innych wªa±ciwo±ci wymagaj¡cych
specjalnych zabiegów.
class B : public A{
private:
float w;
public:
...
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
4 / 33
Klasa pochodna bez dynamicznego zarz¡dzania pami¦ci¡ -
Destruktor
Je±li nie zdeniujemy samodzielnie destruktora, kompilator utworzy go
automatycznie destruktor domy±lny, który nic nie robi.
Destruktor klasy pochodnej zawsze wywoªuje destruktor klasy bazowej.
Poniewa» skªadowe klasy pochodnej B nie wymagaj¡ »adnych
specjalnych dziaªa«, dziaªanie destruktora domy±lnego jest
wystarczaj¡ce.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
5 / 33
Klasa pochodna bez dynamicznego zarz¡dzania pami¦ci¡ -
Konstruktor Kopiuj¡cy
Domy±lny konstruktor kopiuj¡cy wykonuje kopiowanie pªytkie, które
nie jest dobre w przypadku dynamicznego zarz¡dzania pami¦ci¡, ale
jest poprawne dla nowej skªadowej klasy B.
Pªytkie kopiowanie stosuje kopiowania zdeniowane dla typu danych,
na których dziaªa, np. kopiowanie zmiennej long do innej zmiennej
tego typu realizowane jest za pomoc¡ zwykªego przypisania.
Kopiowanie skªadowych w postaci klasy lub odziedziczonych
fragmentów klasy odbywa si¦ za pomoc¡ kontruktora kopiuj¡cego w
niej zdeniowanego.
Domy±lny konstruktor kopiuj¡cy klasy B u»ywa jawnego konstruktora
kopiuj¡cego klasy A do skopiowania danych typu B nale»¡cych do
klasy A.
St¡d domy±lny konstruktor kopiuj¡cy klasy B dziaªa poprawnie
zarówno dla nowej skªadowej klasy B, jak i dla odziedziczonego
obiektu klasy A.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
6 / 33
Klasa pochodna bez dynamicznego zarz¡dzania pami¦ci¡ -
Operator Przypisania
Domy±lny operator przypisania wygenerowany przez kompilator u»ywa
operatora przypisania klasy bazowej - i w tym przypadku wszystko dziaªa
poprawnie.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
7 / 33
Klasa pochodna z dynamicznym zarz¡dzaniem pami¦ci¡
Zaªó»my, »e klasa pochodna u»ywa sªowa kluczowego
new.
class B : public A{
private:
char *w;
public:
...
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
8 / 33
Klasa pochodna z dynamicznym zarz¡dzaniem pami¦ci¡ -
Destruktor
Poniewa» destruktor klasy pochodnej w sposób automatyczny wywoªuje
destruktor klasy bazowej, pozostaje zadba¢ o zwrócenie zasobów zaj¦tych
w konstruktorze klasy B.
A::~A()
{
delete [] l;
l = nullptr;
}
B::~B()
{
delete [] w;
w = nullptr;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
9 / 33
Klasa pochodna z dynamicznym zarz¡dzaniem pami¦ci¡ -
Konstruktor Kopiuj¡cy
A::A(const A& ob){
l = new char[strlen(ob.l)+1];
strcpy(l, ob.l);
r = ob.r;
}
B::B(const B& ob) : A(ob){
w = new char[strlen(ob.w)+1];
strcpy(w, ob.w);
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
10 / 33
UWAGI
Na li±cie inicjalizacyjnej konstruktor klasy
obiekt klasy B.
W klasie
A otrzymuje jako argument
A nie ma konstruktora o takim argumencie.
Jest konstruktor, którego argumentem jest referencja do staªego
obiektu klasy A, a referencja typu klasy bazowej mo»e odnosi¢ si¦ do
dowolnego typu pochodnego.
Konstruktor kopiuj¡cy klasy A u»ywa do utworzenia fragmentu
nowym obiekcie danych tej klasy z argumentu typu B.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
Aw
11 / 33
Klasa pochodna z dynamicznym zarz¡dzaniem pami¦ci¡ -
Operator przypisania
A& A::operator=(const A& ob){
if(this == &ob) return *this;
delete []l;
l = new char[strlen(ob.l)+1]; strcpy(l, ob.l);
r = ob.r;
return *this;
}
B& B::operator=(const B& ob){
if(this == &ob) return *this;
A::operator=(ob); // kopiuje obiekt klasy bazowej
delete []w;
w = new char[strlen(ob.w)+1]; strcpy(w, ob.w);
return *this;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
12 / 33
UWAGI
Je±li zarówno klasa bazowa, jak i pochodna u»ywaj¡ dynamicznego
przydzielania pami¦ci, to:
destruktor, kostruktor kopiuj¡cy i operator przypisania w klasie
pochodnej musz¡ u»ywa¢ swoich odpowiedników z klasy bazowej do
obsªugi danych tej klasy;
destruktor wykonuje to automatycznie;
konstruktor kopiuj¡cy: nale»y wywoªa¢ na li±cie inicjalizacyjnej
konstruktor kopiuj¡cy klasy bazowej - w przeciwnym przypadku
wywoªany zostanie konstruktor domy±lny klasy bazowej;
w operatorze przypisania nale»y zastosowa¢ operator zasi¦gu i jawnie
wywoªa¢ operator przypisania z klasy bazowej.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
13 / 33
Funkcje zaprzyja¹nione z klas¡ pochodn¡
W funkcjach zaprzyja¹nionych z klas¡ pochodn¡ mo»na rzutowa¢ wska¹nik
lub referencj¦ typu tej klasy na wska¹nik lub referencj¦ typu klasy bazowej,
a nast¦pnie u»ywa¢ ich do wywoªywania funkcji zaprzyja¹nionych z klas¡
bazow¡.
ostream& operator<<(ostream &os, const A& ob){
os << ob.l << endl;
os << ob.r << endl;
return os;
}
ostream& operator<<(ostream& os, const B& ob){
os << (const A&) ob;
os << ob.w << endl;
return os;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
14 / 33
Dziedziczenie wielokrotne
Dziedziczenie wielokrotne opisuje klas¦, która ma wi¦cej ni» jedn¡
bezpo±redni¡ klas¦ bazow¡.
Zaªó»my, »e mamy klas¦ Kelner oraz klas¦ Spiewak, mo»emy
utworzy¢ z nich klas¦ pochodn¡ SpiewajacyKelner.
class SpiewajacyKelner : public Kelner, public Spiewak {
...
};
class SpiewajacyKelner : public Kelner, Spiewak {
...
};
Problemy zwi¡zane z dziedziczeniem publicznym to: dziedziczenie po
dwóch (lub wi¦cej) klasach bazowych ró»nych metod o tych samych
nazwach oraz dziedziczenie kilku egzemplarzy klasy poprzez dwie (lub
wi¦cej) powi¡zane klasy bazowe.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
15 / 33
Przykªad
L. St¦pie« (AJD)
Programowanie obiektowe w C++
16 / 33
Klasa abstrakcyjna
Pracownik
class Pracownik{
private:
string nazwisko;
int id;
public:
Pracownik(): nazwisko("brak"), id(0){}
Pracownik(const string& s, int id):
nazwisko(s), id(id){}
virtual ~Pracownik() = 0;
virtual void Set();
virtual void Show() const;
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
17 / 33
class Kelner : public Pracownik{
private:
int poziom_elegancji;
public:
Kelner(): Pracownik(), poziom_elegancji(0){}
Kelner(const string& s, int id, int pe) :
Pracownik(s,id), poziom_elegancji(pe){}
Kelner(const Pracownik & wk, int pe = 0) :
Pracownik(wk), poziom_elegancji(pe){}
virtual ~Kelner(){}
virtual void Set();
virtual void Show() const;
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
18 / 33
class Spiewak : public Pracownik{
protected:
enum {inny, alt, kontralt, sopran, bas,
baryton, tenor};
enum {typy = 7};
private:
static const char *pv[typy];
int glos;
public:
Spiewak(): Pracownik(), glos(inny){}
Spiewak(const string& s, int id, int gl = inny) :
Pracownik(s,id), glos(gl){}
Spiewak(const Pracownik & wk, int gl = inny) :
Pracownik(wk), glos(gl){}
virtual ~Spiewak(){}
virtual void Set();
virtual void Show() const;
};
L. St¦pie« (AJD)
Programowanie obiektowe w C++
19 / 33
Denicje skªadowych klasy
Pracownik
Pracownik::~Pracownik(){}
void Pracownik::Set(){
cout << "Nazwisko: ";
getline(cin, nazwisko);
cout << "Identyfikator: ";
cin >> id;
}
void Pracownik::Show() const {
cout << "Nazwisko: " << nazwisko << endl;
cout << "Identyfikator: " << id << endl;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
20 / 33
Denicje skªadowych klasy
Kelner
void Kelner::Set(){
Pracownik::Set();
cout << "Poziom elegancji: " ;
cin >> poziom_elegancji;
while(cin.get()!= '\n') continue;
}
void Kelner::Show() const {
Pracownik::Show();
cout << "Poziom elegancji: " << poziom_elegancji << endl;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
21 / 33
Skªadowe klasy
Spiewak
const char * Spiewak::pv[] = {"inny", "alt", "kontralt",
"sopran", "bas", "baryton", "tenor"};
void Spiewak::Set(){
Pracownik::Set();
cout << "Skala glosu: " ;
do{
cin >> glos;
}while(glos >= typy or glos < 0);
while(cin.get()!= '\n') continue;
}
void Spiewak::Show() const {
Pracownik::Show();
cout << "Skala glosu: " << pv[glos] << endl;
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
22 / 33
mainDziedziczenie()
#include<iostream>
#include"Klasa"
using namespace std;
int main(){
Kelner jan("Nowak", 12, 5);
Spiewak bet("Kiepura",13, 6);
Kelner pom;
Spiewak pom1;
Pracownik *tab[4]={&jan, &bet, &pom, &pom1};
for(int i = 2; i < 4; ++i)
tab->[i].Set();
for(int i = 0; i < 4; ++i){
tab->[i].Show();
cout << endl;
}
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
23 / 33
Podwójne egzemplarze klasy
Pracownik
Niech klasa SpiewajacyKelner b¦dzie klas¡ dziedzicz¡c¡ publicznie po
klasach Spiewak i Kelner.
class Spiewak : public Pracownik {...}
class Kelner : public Pracownik {...}
class SpiewajacyKelner : public Kelner, public Spiewak {
...
}
Poniewa» komponent Pracownik znajduje si¦ w obu klasach, w klasie
SpiewajacyKelner znajd¡ si¦ dwa podobiekty typu Pracownik.
Czy poni»sza deklaracja jest poprawna?
SpiewajacyKelner bob;
Pracownik *pp = &bob;
L. St¦pie« (AJD)
Programowanie obiektowe w C++
24 / 33
UWAGI
Przypisanie:
SpiewajacyKelner bob;
Pracownik *pp = &bob;
powoduje ustawienie wska¹nika klasy bazowej na adres danych klasy
bazowej w obiekcie klasy pochodnej, ale obiekt bob zawiera a» dwa takie
adresy (dwa obiekty klasy Pracownik) do wyboru.
U»ywaj¡c rzutowania typu mo»na wskaza¢ konkretny obiekt:
Pracownik *pp1 = (Kelner *) &bob;
Pracownik *pp2 = (Spiewak *) &bob;
Rozwi¡zanie to komplikuje wykorzystanie tablicy wska¹ników klasy bazowej
do wskazywania ró»nych obiektów, w tym obiektów klas pochodnych.
Posiadanie kilku kopii obiektu Pracownik nie jest dobre. Kopie mo»na
wyeliminowa¢ dzi¦ki wirtualnym klasom bazowym.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
25 / 33
Wirtualne klasy bazowe
Dzi¦ki wirtualnym klasom bazowym obiekty pochodne dziedzicz¡ce po
klasach, które maj¡ wspóln¡ klas¦ bazow¡, dziedzicz¡ tylko jeden obiekt tej
klasy bazowej.
class Spiewak : virtual public Pracownik {...}
class Kelner : public virtual Pracownik {...}
Obiekt klasy SpiewajacyKelner ma teraz tylko jedn¡ kopi¦ obiektu
Pracownik, dzi¦ki temu, »e klasy Spiewak i Kelner nie maj¡ odr¦bnych
kopii klasy Pracownik, ale wspóªdziel¡ jeden.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
26 / 33
Nowe zasady tworzenia konstruktorów
J¦zyk C++ uniemo»liwia automatyczne przekazywanie informacji przez
bezpo±redni¡ klas¦ do jej klasy bazowej, je±li klasa bazowa jest wirtualna.
SpiewajacyKelner::SpiewajacyKelner(const Pracownik &ob,
int p, int v)
: Kelner(ob, p), Spiewak(ob, v){}
W przypadku powy»szej denicji obiekt ob nie zostanie przekazany do
obiektu Kelner, poniewa» kompilator musi utworzy¢ komponent bazowy
zanim utworzy obiekt pochodny, zostanie wywoªany konstruktor domy±lny
klasy bazowej.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
27 / 33
Nowe zasady tworzenia konstruktorów
Je±li chcemy u»y¢ konstruktora wirtualnej klasy bazowej, który nie jest
domy±lny, musimy wywoªa¢ go jawnie na li±cie inicjalizacyjnej:
SpiewajacyKelner::SpiewajacyKelner(const Pracownik &ob,
int p, int v)
: Pracownik(ob), Kelner(ob, p), Spiewak(ob, v){}
UWAGA
Post¦powanie takie jest dozwolone i nieuniknione w przypadku wirtualnych
klas bazowych, jest jednak niepoprawne w przypadku niewirtualnych klas
bazowych.
L. St¦pie« (AJD)
Programowanie obiektowe w C++
28 / 33
Podwójne metody
Dziedziczenie wielokrotne mo»e prowadzi¢ do wieloznaczno±ci przy
wywoªywaniu funkcji skªadowych, np.
SpiewajacyKelner rob("Kowalski", 111, 5, 3);
rob.Show();
Mo»na rozwi¡za¢ to stosuj¡c operator zakresu:
SpiewajacyKelner rob("Kowalski", 111, 5, 3);
rob.Spiewak::Show();
lub deniuj¡c metod¦ Show() w klasie
SpiewajacyKelener:
void SpiewajacyKelner::Show(){
Spiewak::Show();
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
29 / 33
Podwójne metody
Ale wci¡» nie mamy informacji z klasy Kelner, musimy zatem
zmodykowa¢ denicj¦ metody Show() w klasie SpiewajacyKelner:
void SpiewajacyKelner::Show(){
Spiewak::Show();
Kelner::Show();
}
W tym przypadku mamy podwójnie wypisane dane z klasy
L. St¦pie« (AJD)
Programowanie obiektowe w C++
Pracownik.
30 / 33
Utwórz dodatkow¡ chronion¡ metod¦ Dane() w ka»dej z
klas - wy±wietl tylko komponenty danej klasy.
void Pracownik::Dane() const {
cout << "Nazwisko: " << nazwisko << endl;
cout << "Identyfikator: " << id << endl;
}
void Spiewak::Dane() const {
cout << "Skala glosu: " << pv[glos] << endl;
}
void Kelner::Dane() const {
cout << "Poziom elegancji: " << poziom_elegancji << endl;
}
void SpiewajacyKelner::Dane() const {
Pracownik::Dane();
Spiewak::Dane();
Kelner::Dane();
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
31 / 33
Show()
void Pracownik::Show() const {
Dane();
}
void Kelner::Show() const {
Pracownik::Dane();
Dane();
}
void Spiewak::Show() const {
Pracownik::Dane();
Dane();
}
void SpiewajacyKelner::Show() const {
Pracownik::Dane();
Spiewak::Dane();
Kelner::Dane();
}
L. St¦pie« (AJD)
Programowanie obiektowe w C++
32 / 33
Problem
Niech klasa B b¦dzie wirtualn¡ klas¡ bazow¡ dla
niewirtualn¡ klas¡ bazow¡ dla X i Y.
Klasa
C i D, a jednocze±nie
M dziedziczy po klasach C, D, X i Y.
Ile podobiektów klasy bazowej
L. St¦pie« (AJD)
B zawiera klasa M?
Programowanie obiektowe w C++
33 / 33