Wstęp do programowania obiektowego
WYKŁAD 3
Dziedziczenie
Pola i funkcje statyczne
Funkcje zaprzyjaźnione, this
Nazwa typu Rozmiar Zakres Uwagi bool 1 bit wartości true albo false stdbool.h
TYPY ZNAKOWE
char 1 bajt 0..255 (unsigned) -128.. 127 (signed)
reprezentuje znak w kodzie ASCII (7bit) + strona kodowa char16_t
char32_t
2 bajty 4 bajty
1 znak Unicode UTF-16
1 znak Unicode UTF-32 reprezentuje znak Unicode
TYPY CAŁKOWITE
int 2 lub 4 bajty zależnie od rozmiaru, taki
jak: short lub long int rozmiar zależy od kompilatora short int 2 bajty -32768.. 32767 (signed)
0..65535 (unsigned)
long int 4 bajty -2147483648..2147483647 (s) 0..4294967295 (unsigned)
long long int 8 bajtów
-9223372036854775808..
9223372036854775807 (s) 0..18446744073709551615 (us)
TYPY ZMIENNOPRZECINKOWE
float 4 bajty 3.4e +/- 38
(7 cyfr dziesiętnych) double 8 bajtów 1.7e +/- 308
(15 cyfr dziesiętnych) long double 8, 10 lub 12B 1.7e +/- 308
(15 cyfr dziesiętnych)
void reprezentuje brak typu
2
Napisowe typy danych w C/C++
char[] – (tzw. c-string) tablica znaków char,
(czyli znaki ASCII plus strona kodowa), obecny w C/C++.
string – typ obiektowy zawarty w klasie string, reprezentujący napis oraz użyteczne operacje na nim (rozmiar, łączenie, wycinanie,
porównanie, kopiowanie, początek, koniec, etc.) Wyłącznie C++.
Operowanie na C-stringach
#include <string.h> // strcpy()
#include <stdlib.h> // malloc()
#include <iostream>
using namespace std;
main()
{ char CC[7]; // C-string (6 znaków + NULL) char *CC2; // C-string (tylko wskaźnik) strcpy(CC,"Napis");
CC2 = (char *) malloc(15); // Alokacja pamięci strcpy(CC2, "To jest napis.");
cout << CC << endl;
cout << CC2 << endl;
}
4
Operowanie na obiektach klasy string
#include <string>
#include <iostream>
using namespace std;
main()
{ string SS; // C++ STL string string SS2; // C++ STL string SS = "This is a string";
SS2 = SS;
cout << SS << endl;
cout << SS2 << endl;
}
Definicja funkcji
<typ zwracany> <nazwa> (<lista parametrów>)
{
… }
6
Deklaracja funkcji – nagłówek zakończony średnikiem
Sygnatura – lista typów parametrów z
uwzględnieniem kolejności. Przykłady sygnatur: (int) (int, int) (int, float) (float, int) () (char, float, int, int)
DZIEDZICZENIE
Dziedziczenie - definicja
Dziedziczenie - rozszerzanie funkcjonalności klasy bazowej i
stworzenie nowej klasy (dziedziczącej) na bazie tej klasy.
8
Schemat dziedziczenia w UML
Telewizor
# głośność
# kontrast
# jasność
+ getGlosnosc()
+ setGlosność(double) ...
TelewizorKolorowy - nasycenie
+ getNasycenie()
+ setNasycenie(double)
Zapis dziedziczenia w kodzie
class TVKolorowy : public TV {
...
}
10
Zapis dziedziczenia w kodzie
class TVKolorowy : public TV {
...
}
W C++ w klasie pochodnej możemy m.in.:
zdefiniować dodatkowe zmienne składowe (w naszym przykładzie: nasycenie)
zdefiniować dodatkowe metody (w naszym
przykładzie getNasycenie() i setNasycenie() )
zmieniać (nadpisywać) treść metod (zdefiniowanych jako „wirtualne”)
Konstruktory i destruktory nie są dziedziczone – każda klasa ma własne! (ale podczas tworzenia każdego obiektu wywołuje się też konstruktor klas bazowej)
12
Korzyści z dziedziczenia:
oszczędność implementacji - nie musimy powtórnie definiować tej samej funkcjonalności
hierarchiczne uporządkowanie klas
tzw. polimorfizm – wywoływanie takich samych metod dla różnych klas (o
wspólnej klasie bazowej), daje różne efekty
◦ Przykład: wywołujemy metodę
poruszajSię() dla Orła i Słonia, Orzeł
„lata”, Słoń „biega”
Dziedziczenie może być „wielopoziomowe”:
Zwierzę
Ssak Ptak Płaz
Słoń Żyrafa Orzeł Wrona Żaba
14
Dziedziczenie możemy też zobrazować jako zbiory i podzbiory obiektów:
Zwierzę Ssak
Słoń
Żyrafa
Ptak Orzeł
Wrona
Płaz
Żaba
Typy dziedziczenia (w C++)
W C++ mamy dostępne trzy typy dziedziczenia:
publiczne
prywatne
chronione
Typ dziedziczenia jest specyfikowany w nagłówku klasy pochodnej i może
powodować zmiany widoczności odziedziczonych składowych
(wyłącznie w klasie pochodnej).
16
Typy dziedziczenia (w C++)
Dziedziczenie publiczne - TVKol :public TV
public -> public
protected -> protected
private -> nigdy się nie dziedziczą
Dziedziczenie prywatne - TVKol :private TV
public -> private
protected -> private
private -> nigdy się nie dziedziczą
Dziedziczenie chronione
TVKol :protected TV
public -> protected
protected -> protected
private -> nigdy się nie dziedziczą
Najczęściej używane jest dziedziczenie publiczne, we współczesnych językach zwykle dostępne jest tylko ono.
18
Zmienne składowe i metody oznaczone jako protected, są
dziedziczone i dostępne w klasach pochodnych. Nie są natomiast
dostępne „na zewnątrz” struktury dziedziczenia.
DZIEDZICZENIE WIELOKROTNE
20
Dziedziczenie wielokrotne
Dziedziczenie wielokrotne (ang. multiple inheritance) nazywane także
dziedziczeniem wielobazowym to operacja polegająca na dziedziczeniu po więcej niż jednej klasie bazowej.
Samochód
# liczbaKół + jedź(int) ...
Amfibia - kolor ...
Statek
+ wyporność + płyń() ...
Dziedziczenie wielokrotne w kodzie (C++)
class Samochód { protected:
int liczbaKół;
public:
void jedź(int bieg) { …}
};
class Statek { public:
float wyporność;
void płyń() { … } };
class Amfibia : public Samochód, public Statek { private:
int kolor;
};
22
Dziedziczenie wielokrotne stosowane jest m.in. w językach C++, Perl.
W innych językach programowania (np.
w Javie) zwykle dopuszczalne jest
wyłącznie dziedziczenie jednokrotne, zaś do uzyskania efektu, który w C++
osiąga się poprzez dziedziczenie wielokrotne używa się tzw.
interfejsów.
Zaleta dziedziczenia wielokrotnego:
(niekiedy) intuicyjność dziedziczenia
Wady dziedziczenia wielokrotnego:
niejednoznaczność działania dla bardziej skomplikowanych drzew,
problemy z łańcuchowym wywoływaniem konstruktorów,
przesłanianie zmiennych i metod,
trudności implementacyjne.
24
POLA I FUNKCJE
STATYCZNE
Pola statyczne
Niekiedy potrzebne są pola wspólne dla wszystkich obiektów klasy.
W C++ i wielu innych językach takie pola można zadeklarować przy pomocy słowa kluczowego static. Tak zadeklarowane
pole jest widoczne we wszystkich
obiektach klasy, ale w pamięci istnieje tylko jeden egzemplarz takiego pola.
Pola statyczne są jak gdyby zmiennymi globalnymi danej klasy i istnieją
niezależnie od tworzonych obiektów klasy.
26
Pola statyczne w C++
W C++ potrzebna jest definicja, deklaracja i zwykle inicjacja pola statycznego:
class Punkt { private:
int x;
int y;
public:
static int a; //definicja pola statycznego publicznego
}
int Punkt::a = 1; //deklaracja i inicjacja pola statycznego poza klasą
Odwołanie do publicznego pola statycznego jest możliwe przez nazwę kwalifikowaną (nazwa klasy z operatorem zakresu ::) lub przez operator . i ->.
Punkt::a = 2; //odwołanie do pola statycznego przez ::
Punkt p1;
p1.a =2; //odwołanie do pola statycznego przez . Punkt *wsk;
wsk = new Punkt;
wsk->a=10; //odwołanie do pola statycznego przez ->
delete wsk;
28
Pole statyczne prywatne może być poza klasą tylko zainicjowane
(jednokrotny dostęp przy deklaracji).
Poza tym wyjątkiem poza klasą nie ma do niego dostępu.
Inicjalizacja pól statycznych w C++:
Pola statyczne w C++ nie mogą być inicjowane przez konstruktor. Inicjuje się je poza klasą przy użyciu nazwy kwalifikowanej (nazwa klasy i
czterokropek).
30
Funkcje statyczne
Funkcje statyczne deklarowane z
atrybutem static, podobnie jak pola
statyczne związane są z klasą a nie z obiektami; czyli:
Można się do nich odwoływać nawet gdy nie istnieje żaden obiekt
Niestatyczne pola i metody klasy nie są widoczne wewnątrz funkcji statycznych (ponieważ są związane z konkretnymi obiektami)
Pola statyczne są widoczne wewnątrz funkcji statycznych
Funkcje statyczne służą:
zwykle do wykonywania operacji na polach statycznych
mogą służyć do zapisu operacji nie związanych z zawartością klasy (np.
operacje na plikach, zewnętrznych bazach danych itp.).
32
Przykład użycia funkcji statycznej
class Wektor3d { private:
double x, y, z;
public:
static int wymiar;
static void zmien_wymiar( int nowy_wymiar) { wymiar = nowy_wymiar;}
};
int Wektor3d :: wymiar; //pole statyczne musi być zadeklarowane int main(){
Wektor3d w1;
w1.zmien_wymiar( 2);
cout << "Wymiar wektora w1 = " << w1.wymiar << endl;
Wektor3d :: zmien_wymiar(3);
cout << "Nowy wymiar wektora w1 = " << w1.wymiar << endl;
}
FUNKCJE
ZAPRZYJAŹNIONE
34
Funkcje zaprzyjaźnione w C++
Funkcje zewnętrzne albo funkcje innej klasy mogą mieć dostęp do pól
prywatnych (lub chronionych) klasy jeżeli zostaną zadeklarowane z
atrybutem friend.
Są zdefiniowane poza klasą,
wewnątrz klasy deklarujemy ich istnienie i fakt przyjaźni.
Przykład funkcji zaprzyjaźnionej
class Wektor3d { private:
double x, y, z;
friend double dlugosc( Wektor3d w);
};
double dlugosc( Wektor3d w) {
return sqrt(w.x * w.x + w.y * w.y + w.z * w.z);
}
int main() { Wektor3d w;
cout << ", dlugosc = " << dlugosc(w);}
36
klasa
funkcja
Cechy funkcji
zaprzyjaźnionych
Funkcja zaprzyjaźniona nie jest składową klasy (nie można się do niej odwołać poprzez nazwę
kwalifikowaną).
Funkcja zaprzyjaźniona nie może być aktywowana na rzecz jakiegoś obiektu
Funkcja zaprzyjaźniona powinna mieć jako wynik albo jako argument obiekt klasy, z którą jest zaprzyjaźniona (inaczej przyjaźń nie ma sensu!)
Każda funkcja może być zaprzyjaźniona z wieloma klasami.
W praktyce, najczęściej jako funkcje zaprzyjaźnione
deklaruje się funkcje operatorowe (operatory to np. + - * / ).
Ponieważ funkcje zaprzyjaźnione są naruszaniem reguł enkapsulacji, powinno się eksponować jej
deklaracje, umieszczając je na samym początku deklaracji ustrojów klas.
Ponadto starajmy się nie nadużywać przyjaźni.
Słowo kluczowe this
Wskaźnik this, automatycznie dodawany przez kompilator do każdego obiektu, jest wskazaniem na ten właśnie obiekt. Funkcje składowe mogą w ten sposób zwrócić wskaźnik na obiekt:
return this;
lub też sam obiekt:
return *this;
Przykład - definicja operatora powiększania dla
liczb zespolonych:
Zespolone operator+= (const Zespolone& z) { re+=z.re; im+=z.im;
return *this;
}
38