Programowanie obiektowe
w C++
Wykład 04 Temat wiodący:
Dziedziczenie cz. 1
Dziedziczenie
Po co nam dziedziczenie?
class osoba {
char * imie, * nazwisko;
public:
void wypisz_imie();
void wypisz_nazwisko();
void wypisz_wszystko();
}
class dorosly {
char * imie, * nazwisko, * nr_dowodu;
public:
void wypisz_imie();
void wypisz_nazwisko();
void wypisz_numer();
void wypisz_wszystko();
}
Po co nam dziedziczenie?
class osoba {
char * imie, * nazwisko;
public:
void wypisz_imie();
void wypisz_nazwisko();
void wypisz_wszystko();
}
class dorosly: public osoba {
char * nr_dowodu;
public:
void wypisz_numer();
void wypisz wszystko();
}
Dziedziczenie
n
Wyraża związki hierarchiczne między klasami
n
Rozbudowywanie istniejących klas, bez ich modyfikowania lub rekompilacji
n
Wykorzystanie już gotowego kodu (definiujemy tylko to , co dodane lub zmienione)
Dziedziczenie
n
Klasa bazowa osoba
n
Klasa pochodna dorosly każdy dorosły jest osobą
dorosły jest przypadkiem szczególnym osoby
Klasa pochodna
n
dorosły zawiera zmienne i metody:
char * imie, * nazwisko
— odziedziczone, niedostępne dla kl. dorosly char *nr_dowodu
— prywatne dla dorosly
wypisz_imie(), wypisz_nazwisko()
— publiczne, odziedziczone z osoba osoba::wypisz_wszystko()
— publiczne, odziedziczone i przysłonięte, dostępne po kwalifikacji op. zakresu
Klasa pochodna
n
zawiera wszystkie pola bazowej
n
dziedziczy wszystkie metody, może je przedefiniowywać
n
do dostępnych lecz przysłoniętych nazw z
zakresu klasy bazowej odwołujemy się za
pomocą operatora zakresu
Klasa pochodna
osoba o;
dorosly s;
o.wypisz_imie();
o.wypisz_nazwisko();
o.wypisz_wszystko();
s.wypisz_imie(); // z osoba s.wypisz_nazwisko(); // z osoba s.wypisz_numer(); // z dorosly s.wypisz_wszystko(); // z dorosly s.osoba::wypisz_wszystko(); // z osoba
Dostęp do pól kl. bazowej
class pochodna : public bazowa;
class pochodna : protected bazowa;
class pochodna : private bazowa;
class pochodna : bazowa; // : private
Dostęp do pól kl. bazowej
sekcja w kl. bazowej
public protected
niedostępne : public
protected protected
niedostępne : protected
private private
niedostępne : private
public protected
private
Dostęp do pól kl. bazowej
class a { public:
int f(int);
};
class b:private a //można „osłabić” ograniczenie dostępu {
public:
a::f; // teraz a::f jest dziedziczone publicznie };
Dostęp do pól kl. bazowej
class baza {
public:
int ba_pub, i;
protected:
int ba_prot;
private:
int ba_priv;
};
class prot: protected baza {
public:
int i;
void metoda();
protected:
int prot_prot;
friend void friend_prot();
};
Dostęp do pól kl. bazowej
void prot::metoda() {
int i;
i=baza::i;
i=i;
i=prot::i;
i=ba_pub;
i=baza::ba_pub;
i=prot::ba_pub;
i=ba_prot;
i=baza::ba_prot;
i=prot::ba_prot;
// i=ba_priv;
// i=baza::ba_priv;
// i=prot::ba_priv;
i=prot_prot;
i=prot::prot_prot;
}
Dostęp do pól kl. bazowej
void not_a_friend() {
baza ba;
prot pr;
int i;
i=ba.i;
i=ba.ba_pub;
// i=ba.ba_prot;
// i=ba.ba_priv;
// i=pr.baza::i; !!!!!
i=pr.i;
i=pr.prot::i;
// i=pr.ba_pub;
// i=pr.baza::ba_pub;
// i=pr.prot::ba_pub;
// i=pr.ba_prot;
// i=pr.baza::ba_prot;
// i=pr.prot::ba_prot;
// i=pr.ba_priv;
// i=pr.baza::ba_priv;
// i=pr.prot::ba_priv;
// i=pr.prot_prot;
// i=pr.prot::prot_prot;
}
Dostęp do pól kl. bazowej
void friend_prot(void) {
baza ba;
prot pr;
int i;
i=ba.i;
i=ba.ba_pub;
// i=ba.ba_prot;
// i=ba.ba_priv;
i=pr.baza::i; // !!!!!
i=pr.i;
i=pr.prot::i;
i=pr.ba_pub;
i=pr.baza::ba_pub;
i=pr.prot::ba_pub;
i=pr.ba_prot;
i=pr.baza::ba_prot;
i=pr.prot::ba_prot;
// i=pr.ba_priv;
// i=pr.baza::ba_priv;
// i=pr.prot::ba_priv;
i=pr.prot_prot;
i=pr.prot::prot_prot;
}
Konstruktor i destruktor dla klasy potomnej
n
Konstruktorów się nie dziedziczy, ale
n
Kolejność konstrukcji obiektu klasy:
n
wirtualne klasy bazowe
n
bezpośrednie klasy bazowe
n
obiektowe zmienne klasowe
n
dana klasa
n
Destruktory - odwrotnie
Konstruktor i destruktor dla klasy potomnej – lista inicjalizacyjna
n
Opisuje sposób inicjalizacji zmiennych klasowych zadeklarowanych w klasie (a nie odziedziczonych).
n
Opisuje sposób wywołania konstruktorów
przodków wirtualnych oraz bezpośrednich.
Przykład
class osoba {
protected:
char * imie, * nazwisko;
char * aloc_string(const char * s);
public:
osoba(const char * im, const char * naz) :imie(aloc_string(im)),
nazwisko(aloc_string(naz)) {
}
~osoba() {
delete imie;
delete nazwisko;
}
void wypisz_imie();
void wypisz_nazwisko();
void wypisz_wszystko();
}
Przykład
char * osoba::aloc_string(const char * s) {
char * cp=new char[strlen(s) + 1];
strcpy(cp, s);
return cp;
}
void osoba::wypisz_imie() {
cout << imie << " ";
}
void osoba::wypisz_nazwisko() {
cout << nazwisko << " ";
}
void osoba::wypisz_wszystko() {
wypisz_imie();
wypisz_nazwisko();
}
Przykład - konstruktor klasy pochodnej
class dorosly:public osoba {
protected:
char * nr_dowodu;
public:
dorosly(const char * imie, const char * nazwisko, const char * dowod) :osoba(imie, nazwisko),
nr_dowodu(aloc_string(dowod)) {
};
~dorosly() {
delete nr_dowodu;
};
void wypisz_numer();
void wypisz_wszystko();
};
Przykład
void dorosly::wypisz_numer() {
cout << "dowod: " << nr_dowodu << " ";
}
void dorosly::wypisz_wszystko() {
osoba::wypisz_wszystko();
wypisz_numer();
}
Przykład – wiele kl. pochodnych
osoba
dorosly dziecko
class dziecko:public osoba {
protected:
osoba * mama, * tata;
public:
dziecko(const char * imie, const char * nazwisko, osoba * mama, osoba * tata)
:osoba(imie, nazwisko), mama(mama), tata(tata) {
};
// ~dziecko() //wystarczy wygenerowany automatycznie – dlaczego?
void wypisz_wszystko();
};
Przykład
void dziecko::wypisz_wszystko() {
osoba::wypisz_wszystko();
cout << "mama: " ;
mama->wypisz_wszystko();
cout << "tata: " ;
tata->wypisz_wszystko();
}
Przykład – kl. bazowe i pochodne
osoba
dorosly dziecko
posel
class posel:public dorosly {
protected:
char * nr_immunitetu;
public:
posel(const char * imie, const char * nazwisko, const char * dowod, const char * immunitet)
:dorosly(imie, nazwisko, dowod), nr_immunitetu(aloc_string(immunitet)) { // klase osoba możemy skonstruować tylko pośrednio
};
~posel() {
delete nr_immunitetu;
}
void wypisz_wszystko();
};
void posel::wypisz_wszystko() {
dorosly::wypisz_wszystko();
cout << "immunitet : " << nr_immunitetu;
}
Przykład
Struktury
n
Struktury w C++ to też klasy struct A { coś tam }
odpowiada
class A {public: coś tam }
Struktury i klasy
n Inicjatorem klamrowym można inicjalizować wyłącznie pola klasy w której wszystkie dane są publiczne i nie zdefiniowano konstruktora.
struct s_xy {
int x,y;
};
s_xy sxy={10,20}; // ok.
//s_xy sxy1(10,10) - źle
struct xy {
int x,y;
xy(int x, int y) : x(x), y(y) {}
};
//xy xy1={10,20}; — źle xy xy1(10,10); //ok
Unie
n
W C++ unia jest klasą, ale
n
nie może zawierać obiektów z zdefiniowanymi konstruktorami bądź destruktorami
n
nie wolno ograniczać dostępu do pól - wszystkie pola muszą być publiczne
Przykład – kl. bazowe i pochodne
punkt
okrąg odcinek
prostokąt
n
Zadeklarować klasy, konstruktory, przesuń, +=
W klasach zadeklarować zmienne:
n punkt:
n int kolor; xy polozenie
n okrag :public punkt
n int promien
n odcinek :public punkt
n xy rozmiar;
n prostokąt :public odcinek
Przykład – kl. bazowe i pochodne
struct xy {
int x,y;
xy(int x, int y): x(x), y(y) {
} };
class punkt {
int kolor;
xy polozenie;
public:
punkt(int kolor, xy poz) :kolor(kolor), polozenie(poz) {
}
void przesun(int dx, int dy) {
polozenie.x+=dx;
polozenie.y+=dy;
}
punkt & operator+=(int d) {
return *this;
} };
class okrag:public punkt {
int promien;
public:
okrag(int kolor, xy poz, int promien) :punkt(kolor, poz), promien(promien) {
}
okrag & operator+=(int d) {
(promien*=100+d)/=100;
return *this;
}
};
class odcinek:public punkt {
xy rozmiar;
public:
odcinek(int kolor, xy poz, int p2_x, int p2_y) :punkt(kolor, poz), rozmiar(p2_x, p2_y) {
}
odcinek & operator+=(int d) {
(rozmiar.x*=100+d)/=100;
(rozmiar.y*=100+d)/=100;
return *this;
}
};
class prostokat:public odcinek {
public:
prostokat(int kolor, xy poz, int p2_x, int p2_y) :odcinek(kolor, poz, p2_x, p2_y)
{ }
prostokat & operator+=(int d) {
odcinek::operator+=(d);
return *this;
}
};
Przykład – kl. bazowe i pochodne
xy
punkt
okrąg odcinek
prostokąt
Jak się zmieni hierarchia klas?
n xy polozenie
n int x,y
n punkt :public xy
n int kolor;
n okrag :public punkt
n int promien
n odcinek :public punkt
n xy rozmiar;
n prostokąt :public odcinek
class punkt :public xy {
int kolor;
// xy polozenie public:
punkt(int kolor, xy poz) :kolor(kolor), xy(poz) {
}
void przesun(int dx, int dy) {
x+=dx; // publiczne w przodku y+=dy;
}
punkt & operator+=(int d) {
return *this;
} };
Konwersja potomna ? bazowa
n
Automatyczna (niejawna) konwersja wskaźnik do klasy bazowej na wskaźnik do potomnej jest dozwolona przy dziedziczeniu publicznym
n rationale
n Przykład
osoba o(…);
dorosly d(…);
dziecko x(„Jaś”, „Kowalski”, &o, &d);
x.wypisz_wszystko() // nie wypisze dowodu d
Konwersja potomna ? bazowa
n
Automatyczna (niejawna) konwersja wskaźnik do klasy bazowej na wskaźnik do potomnej jest dozwolona przy dziedziczeniu publicznym
n uwaga: destrukcja przez wskaźnik po konwersji
osoba *o=new dorosly("aa", "bb", "cc");
delete o; // nie zwolni nr_dowodu
n rozwiązanie: destruktor wirtualny.
Konwersja potomna ? bazowa
n
Skojarzenie referencji do klasy bazowej z obiektem klasy potomnej jest dozwolone przy dziedziczeniu publicznym
n
uwagi (konwersje wskaźników oraz referencji):
n te konwersje są przechodnie
n dozwolone wyłącznie dla dziedziczenia publicznego (aby uniemożliwić obejście ograniczenia dostępu do pól odziedziczonych jako private/protected)
Konwersja potomna ? bazowa
class A {};
class B: public A {};
class C: private A {};
int fa(A a) {};
int fb(B b) {};
--- A a;
B b;
C c;
fa(a);
// konstruktor kopiujący A fa(b); // jak wyżej // fa(c); // błąd
// fb(a);
fb(b);
// fb(c);
A *pa0=&a;
A *pa1=&b;
// A *pa2=&c;
// B *pb=&a;
// C *pc=&a;
A &ra0=a;
A &ra1=b;
// A &ra2=c;
// B &rb=a;
// C &rc=a;
Konwersja potomna ? bazowa
class A {};
class B: public A {};
class C: private A {};
class D: public B{};
int fa(A a) {};
---
D d;
fa(d); // przechodnie
Konstruktor kopiujący klasy pochodnej
n
to jest konstruktor, a zatem
n obowiązuje koleność konstrukcji obiektów: zawsze wywolaja sie konstruktory klas bazowych.
n mozna uzyc listy inicjalizacyjnej by określić sposób wywołania konstruktora klasy bazowej
n Przypomnienie: konstruowane będą również obiekty zawarte w danym.
Konstruktor kopiujący klasy pochodnej
class potomek:public baza { ... };
potomek::potomek(const potomek & p)
:baza(p) // niejawna konwersja do kl. bazowej
{ ... };
Konstruktor kopiujący klasy pochodnej
n
podobnie jak konstruktor domyślny i
destruktor, konstruktor kopiujący może być generowany automatycznie
n
jezeli w klasie potomnej nie zdefiniujemy go to zostanie wygenerowany
n
przed nim wywola sie konstruktor kopiujący klasy bazowej (a nie bezparametrowy, czyli odwrotnie niz w przypadku konstr. kop. nie-generowanego)
n
musi byc dostepny k. kopiujacy. klasy bazowej (byc i byc publiczny)
Operator przypisania klasy pochodnej
n
Wygenerowany automatycznie operator przypisania kopiuje skladowe danej klasy pole po polu
n najpierw klasa bazowa kopiowana jest operatorem przypisania, potem obecna.
n nie zostanie wygenerowany jeżeli klasa zawiera pola stałe albo referencje (w klasie albo przodkach), ani jeżeli w klasie bazowej lub obiekcie zawartym op.= byl prywatny.
n prawdopodobnie nie sprawdzi się dla pól wskaźnikowych
n
jeżeli napiszemy wlasny operator to odziedziczony
operator nie wywola sie sam, trzeba to zrobic jawnie.
Operator przypisania klasy pochodnej
class potomek:public baza { ... };
potomek & potomek::operator=(const potomek & p) {
if (this == &p) return *this;
this->baza::operator=(p);
// alternatywy: baza *pb=this; (*pb)=p;
// baza *pb=this; pb->operator=(p);
// baza &rb=*this; rb=p;
// przypisanie dla własnej klasy
return *this; // zwrócenie wartości };
Przykład
n
Konstruktory kopiujące i operatory przypisania dla klas osoba i dorosły
class osoba {
char * imie, * nazwisko;
… }
class dorosly: public osoba {
char * nr_dowodu;
…
}
Przykład
osoba::osoba(const osoba &o)
:imie(aloc_string(o.imie)),nazwisko(aloc_string(o.nazwisko)) {}
osoba & osoba::operator=(const osoba &o) {
if (this == &o) return *this;
delete imie;
imie=aloc_string(o.imie);
delete nazwisko;
nazwisko=aloc_string(o.nazwisko);
return *this;
}
Przykład
dorosly::dorosly(const dorosly & d)
:osoba(d), nr_dowodu(aloc_string(d.nr_dowodu)) {}
dorosly & dorosly::operator=(const dorosly & d) {
if (this == &d) return *this;
this->osoba::operator=(d);
delete nr_dowodu;
nr_dowodu=aloc_string(d.nr_dowodu);
return *this;
}