Podstawy Programowania Obiektowego Pojęcie struktury i klasy. Konstruktor i destruktor. Spotkanie 08 Dr inż. Dariusz JĘDRZEJCZYK
Tematyka wykładu Spotkanie 08 Klasy: definicja a deklaracja klasy dane składowe funkcje składowe (definiowanie) sposoby ukrywania informacji w klasie posługiwanie się składnikami klasy Pojęcie konstruktora i destruktora: Definicja konstruktora i destruktora Konstruktor domniemany Konstruktor sparametryzowany Konstruktor kopiujący Przeładowanie konstruktora Cechy charakterystyczne konstruktora i destruktora, Jawne wywołanie konstruktora i destruktora Zadania do samodzielnego wykonania 2
Klasa - definicja Klasa to zdefiniowanie własnego typu danych - wymyślony na potrzeby danego programu. Ten typ, to nie tylko jedna lub kilka zebranych liczb, ale to również sposób ich zachowania jako całości. 01. class nazwa_klasy 02. { 03. // ciało klasy 04. //... 05. }; 3
Klasa - tworzenie obiektów tworzenie obiektu klasy NazwaKlasy NazwaObiektu; tworzenie wskaźnika do obiektu klasy NazwaKlasy *NazwaWskaznika; tworzenie referencji (przezwiska) do obiektu klasy NazwaKlasy &NazwaObiektu = NazwaWskaznika; 4
Klasa - składniki klasy dane składowe 01. class nazwa_klasy 02. { 03. int a; // definicja zmiennej typu całkowitego 04. float b[10]; // definicja tablicy typu rzeczywistego 05. char *c; // definicja wskaźnika na zmienną znakową 06. }; funkcje składowe 01. class nazwa_klasy 02. { 03. int funkcja1(); // deklaracja funkcji zwracającej typ int 04. float funkcja2(); // deklaracja funkcji zwracającej typ float 05. char funkcja3(); // deklaracja funkcji zwracającej typ char 06. }; 5
Klasa - składniki klasy - przykład dane składowe 01. class radio 02. { 03. int glosnosc; // definicja zmiennej typu całkowitego 04. float czestotliwosc[10]; // definicja tablicy typu rzeczywistego 05. char *nazwa; // definicja wskaźnika na zmienną znakową 06. }; funkcje składowe 01. class radio 02. { 03. void UstawGlosnosc( ); 04. float PobierzCzestotliwosc( ); 05. void wypisz(); 06. }; 6
Klasa - definicja funkcji składowych wewnątrz klasy: 01. class nazwa_klasy 02. { 03. int a; 04. void funkcja( void ) 05. { 06. cout<< "zmienna a = "<< a; 07. } 08. }; 7
Klasa - definicja funkcji składowych na zewnątrz klasy: 01. class nazwa_klasy 02. { 03. int a; 04. void funkcja( void ); 05. }; 06. void nazwa_klasy::funkcja( void ) 07. { 08. cout<< "zmienna a = "<< a; 09. } 8
Klasa - definicja funkcji składowych 01. class radio 02. { 03. int glosnosc; 04. float czestotliwosc[10]; 05. char *nazwa; 06. void UstawGlosnosc(int & ); 07. float PobierzCzestotliwosc( ); 08. void wypisz() 09. { 10. cout<< "Radio "<< nazwa<< endl; 11. } 12. }; 13. void radio::ustawglosnosc( int &_a ) 14. { 15. glosnosc = _a; 16. } 9
Klasa - sposoby ukrywania informacji prywatny - składniki klasy dostępne są tylko i wyłącznie w klasie i poprzez funkcje składowe klasy. private: publiczny - składniki klasy dostępne zarówno dla funkcji składowych klasy jak również poza klasą. public: chroniony - składniki klasy nie dostępne poza klasą, jednak dostępne dla klas pochodnych. protected: 10
Klasa dostęp do danych składowych klasy obiekt - do danej składowej klasy można odwołać się poprzez operator. obiekt.skladnik wskaźnik - odniesienie się do danej składowej obiektu pokazywanego wskaźnikiem można odwołać się poprzez operator -> wskaznik->skladnik referencja - do danej składowej obiektu znanego z referencji można odwołać się poprzez operator. referencja.skladnik 11
Klasa dostęp do funkcji składowych klasy obiekt - do funkcji składowej klasy można odwołać się poprzez operator. obiekt.funkcja( ); wskaźnik - odniesienie się do funkcji składowej obiektu pokazywanej wskaźnikiem można odwołać się poprzez operator -> wskaznik->funkcja( ); referencja - do funkcji składowej obiektu znanego z referencji można odwołać się poprzez operator. referencja.funkcja( ); 12
Klasa - posługiwanie się składnikami klasy Napisanie prostego przykładu na wykorzystanie danych i funkcji składowych klasy. 13
Struktura 01. struct nazwa_struktury 02. { 03. // ciało struktury 04. //... 05. }; 14
Konstruktor Konstruktor jest specjalną funkcją składową, w której definiuje się obiekt i nadaje mu wartość. Charakteryzuje się on tym, że nazywa się tak jak samo jak klasa. 01. class NowyTyp 02.{ 03. private: 04. int a; 05. float b; 06. public: 07. NowyTyP( int &_a, float &_b ); 08. }; 09. NowyTyP::NowyTyP( int &_a, float &_b ) 10. { 11. a = _a; 12. b = _b; 13. } 15
Konstruktor Konstruktor może być przeładowany. Jest to bardzo częsta praktyka, w definicjach klas widzi się zwykle kilka wersji konstruktora (różnią się listą argumentów). Konstruktor nie ma wyspecyfikowanego żadnego typu wartości zwracanej. Nie zwraca nic nawet typu void! Konstruktor może być wywoływany dla tworzenia obiektów z przydomkami const i volatile, ale sam nie może być funkcją typu const i volatile. Konstruktor nie może być typu static między innymi dlatego, że ma pracować na niestatycznych składnikach klasy. Osobliwością konstruktora jest to, że jest on wywoływany automatycznie ilekroć powołujemy do życia nowy obiekt danej klasy. Konstruktor nie może być także typu virtual. Nie można posłużyć się adresem konstruktora Jeśli obiekt ma być składnikiem unii, to jego klasa nie może mieć żadnego konstruktora. 16
Konstruktor - jawne wywołanie Obiekt może być też stworzony przez jawne wywołanie konstruktora. W efekcie otrzymujemy obiekt, który nie ma nazwy, a czas jego życia ogranicza się do wyrażenia, w którym go użyto. nazwa_klasy( argumenty ) Zauważ, że wywołujemy konstruktor czyli funkcję składową, a nie stosujemy notacji obiekt.funkcja_skladowa( argumenty ) 17
Konstruktor - jawne wywołanie - przykład 01. class NowyTyp 02.{ 03. public: 04. int a; 05. float b; 07. NowyTyp( int _a, float _b ); 08. }; 09. NowyTyp::NowyTyp( int _a, float _b ) 10. { 11. a = _a; 12. b = _b; 13. } 09. void wypisz( NowyTyp _param ) 10. { 11. cout<< " a= "<< _param.a<< " b= "<< _param.b<< endl; 13. } 18
Konstruktor - jawne wywołanie - przykład 01. #include <iostream> 02. using namespace std; 03. void main( void ) 04. { 05. NowyTyp obiekta(1, 3.14), obiektb(2, 1.41); 06. cout<<"\n\n"; 07. wypisz( obiekta ); 08. wypisz( obiektb ); 09. wypisz( NowyTyp(3, 7.77) ); 10. } 19
Konstruktor domniemany Konstruktor domniemany to taki konstruktor, który można wywołać bez żadnego argumentu. Zauważmy, że nie mówimy konstruktor bez argumentów, tylko konstruktor, który można wywołać bez żadnych argumentów. W świetle tej definicji konstruktorem, który można wywołać bez żadnych argumentów jest konstruktor ze wszystkimi argumentami domniemanymi. Klasa może mieć tylko jeden konstruktor domniemany. Jeśli klasa nie ma w ogóle żadnego konstruktora, wówczas sam kompilator wygeneruje dla tej klasy konstruktor domniemany. 20
Konstruktor domniemany - przykład 01. class boss{ 02. // 03. public: 04. // konstruktory 05. boss(int); 06. boss(void); 07. boss(float *); 08. boss(char *s=null, int a=4, float pp=6.66); 09. // 10. }; 21
Konstruktor - lista inicjalizacyjna Do tego, aby zainicjować składniki składnik stały, służy konstruktor. Konkretnie: lista inicjalizacyjna konstruktora. NowyTyp::NowyTyp( argumenty ) : lista_inicjalizacyjna { // ciało klasy } 22
Konstruktor - lista inicjalizacyjna Lista inicjalizacyjna to nie cecha konstruktora, ale lista konkretnych prac, które ma on wykonywać. Specyfikuje ona jak należy zainicjować niestatyczne składniki klasy. Składnikowi nie const możemy w konstruktorze nadać wartość na dwa sposoby: Przez listę inicjalizacyjną konstruktora Przez zwykłe podstawienie w ciele konstruktora Składnikowi const można nadać wartość początkową tylko za pomocą listy inicjalizacyjnej. Lista inicjalizacyjna nie może inicjalizować składnika static. Inicjalizować możemy nie tylko argumentem konstruktora, ale nawet jakimś wyrażeniem, w którym możemy wywołać jakąś funkcję składową lub zwykłą. 23
Konstruktor - lista inicjalizacyjna - przykład 01. class abc 02.{ 03. const int stal; 04. float x; 05. abc( float pp, int dd, char znak ); 06.}; 07. abc::abc( float pp, int dd, char znak ) : stal(dd), c(znak) 08. { 09. x = pp; 10. } 24
Konstruktor - czy może być niepubliczny? Konstruktor może być nie publiczny. Jest on składnikiem klasy i jako takiego obowiązują go również zwykłe reguły dostępu ustalane za pomocą słów: public/protected/private. Klasa, która nie ma publicznych konstruktorów nazywana jest klasą prywatną. Pomimo tego, iż konstruktor jest niedostępny dla tzw. szerokiej publiczności, jest dostępny dla obiektów tej klasy. Funkcja zaprzyjaźniona czy też klasa zaprzyjaźniona ma również dostęp do prywatnych składników klasy więc mogłaby uruchomić prywatny konstruktor. 25
Konstruktor kopiujący Konstruktorem kopiującym w danej klasie klasa nazywamy konstruktor, który można wywołać z jednym argumentem poniższego typu: klasa::klasa(klasa&); Argumentem jest, jak widać, referencja (przezwisko) obiektu danej klasy. Konstruktor ten służy do konstruowania obiektu, który jest kopią innego, już istniejącego obiektu tej klasy. Konstruktor kopiujący nie jest obowiązkowy. Jeśli go nie zdefiniujemy wówczas kompilator wygeneruje go sobie sam. 26
Konstruktor kopiujący Konstruktor kopiujący inaczej można nazwać inicjalizatorem kopiującym. Konstruktor kopiujący jest wywoływany w kilku sytuacjach, które można najogólniej podzielić na: Gdy tego jawnie zażądamy, Bez naszej wiedzy. Wywołanie konstruktora kopiującego na nasze życzenie następuje wtedy, gdy tego jawnie zażądamy i definiujemy nowy obiekt w następujący sposób: NowyTyp ObiektWzorcowy; NowyTyp NowyObiekt = NowyTyp( ObiektWzorcowy ); 27
Konstruktor kopiujący Niejawne wywołanie konstruktora kopiującego klasy NowyTyp następuje w kilku sytuacjach: Podczas przesyłania argumentów do funkcji jeśli argumentem funkcji jest obiekt klasy NowyTyp, a przesyłanie odbywa się przez wartość. Podczas, gdy funkcja jako swój rezultat zwraca przez wartość obiekt klasy NowyTyp. 28
Konstruktor kopiujący - przykład 01. class wizytowka 02.{ 03. public: 04. char *nazw; 05. char *imie; 06. wizytowka( char *na, char *im ); 07. void personalia( ) 08. { 09. cout<< imie<< " "<< nazw<< endl; 10. } 11. void ZmianaNazwiska( char *nowe ) 12. { 13. strcpy( nazw, nowe ); 14. } 15.}; 29
Konstruktor kopiujący - przykład 01. wizytowka::wizytowka( char *im, char *na ) 02.{ 03. imie = new char[strlen( im )+1]; 04. strcpy( imie, im ); 05. nazw = new char[strlen( na )+1]; 06. strcpy( nazw, na ); 07.} 30
Konstruktor kopiujący - przykład 01. int main(int argc, char *argv[]) 02.{ 03. wizytowka fizyk( "Albert", "Einstein"); 04. wizytowka kolega = fizyk; 05. cout<< "Dane fizyka:\t"; 06. fizyk.personalia( ); 07. cout<< "\npo utworzeniu blizniaczego obiektu\n"; 08. cout<< "\ndane fizyka:\t"; 09. fizyk.personalia( ); 10. cout<< "\ndane kolegi:\t"; 11. kolega.personalia( ); 12. kolega.zmiananazwiska( "Metz" ); 13. cout<< "\ndane kolegi po zmianie: "; 14. kolega.personalia( ); 15. cout<< "Niemodyfikowany fizyk nazywa sie : "; 16. fizyk.personalia( ); 17. system("pause"); 18. return EXIT_SUCCESS; 19.} 31
Konstruktor - kiedy jest wymagany Jest ZAPAS PAMIĘCI fizyk nazw imie Metz Albert kolega nazw imie 32
Konstruktor - kiedy jest wymagany Powinno być ZAPAS PAMIĘCI fizyk nazw imie Einstein Albert kolega nazw imie Metz Albert 33
Konstruktor kopiujący - przykład 01. wizytowka::wizytowka(wizytowka &wzor) 02. { 03. imie = new char[strlen( wzor.imie )+1]; 04. strcpy( imie, wzor.imie ); 05. nazw = new char[strlen( wzor.nazw )+1]; 06. strcpy( nazw, wzor.nazw ); 07. } 34
Konstruktor - wady Nie można zdefiniować konstruktora dla typu wbudowanego. Nie można napisać konstruktora dla klasy, która nie jest naszą własnością np.: będącą składnikiem biblioteki. Modyfikacje takiej klasy są zwykle niepożądane. Przy konstruktorze konwertującym argument musi pasować dokładnie do typu argumentu deklarowanego w konstruktorze. Nie możemy polegać na żadnych tak zwanych konwersjach standardowych. Nawet jeśli klasa jest naszą własnością, to konstruktor, który chcemy napisać, musi oprzeć się na informacjach z tej obcej klasy. Tamta obca klasa musi zapewnić sposoby dotarcia do tych informacji. (Robi się to: albo przez publiczne dane składowe, albo przez deklarację przyjaźni). Jeśli ta obca klasa nie zapewnia nam tych informacji, to musimy ją zmodyfikować. Konstruktora służącego do konwersji nie dziedziczy się (bo nie dziedziczy się żadnych konstruktorów). 35
Destruktor Destruktor jest przeciwieństwem konstruktora, czyli funkcja składowa wywoływana wtedy, gdy obiekt danej klasy ma być likwidowany. Destruktor to funkcja składowa klasy. Nazywa się tak samo, jak klasa z tym, że przed nazwą ma znak ~ (tylda). Podobnie jak konstruktor - nie ma on określenia typu zwracanego. Destruktorem klasy K jest funkcja składowa o nazwie ~NowyTyp (wężyk i nazwa klasy). Funkcja ta jest wywoływana automatycznie zawsze, gdy obiekt jest likwidowany. Klasa nie musi mieć obowiązkowo destruktora. Destruktor nie likwiduje obiektu, ani nie zwalnia obszaru pamięci, który obiekt zajmował. Destruktor przydaje się wtedy, gdy przed zniszczeniem obiektu trzeba jeszcze dokonać jakichś działań. Po prostu trzeba posprzątać: 36
Destruktor - funkcje Jeśli na przykład obiekt reprezentował okienko na ekranie, to możemy chcieć, by w momencie likwidacji tego obiektu okienko zostało zamknięte, a ekran wyglądał jak dawniej. Destruktor jest potrzebny, gdy konstruktor danej klasy dokonał na swój użytek rezerwacji dodatkowej pamięci (operatorem new). Wtedy w destruktorze umieszcza się instrukcję delete zwalniającą ten już nie potrzebny obszar pamięci. Destruktor może się też przydać, gdy liczymy obiekty danej klasy. W konstruktorze podwyższamy taki licznik o jeden, a w destruktorze zmniejszamy o jeden. Destruktor może wywołać jakąś funkcję składową swojej klasy. Niemożliwe jest pobranie adresu destruktora. Obiekt klasy mającej destruktor nie może być składnikiem unii. 37
Destruktor Destruktor nie jest wywoływany z żadnymi argumentami. W związku z tym nie może być przeładowany. Destruktor jest automatycznie wywoływany, gdy obiekt automatyczny lub chwilowy wychodzi ze swojego zakresu ważności. Jeśli obiekt lokalny jest statyczny, to mimo, że kończy się jego zakres ważności nie jest likwidowany więc także nie uruchamia się jego destruktora. Likwidacja następuje dopiero przy zakończeniu programu i wtedy też rusza do pracy destruktor. Jeśli kończy się zakres ważności referencji (przezwiska) obiektu destruktor nie jest wywoływany. Analogicznie nie jest automatycznie wywoływany, gdy wskaźnik do jakiegoś obiektu wychodzi ze swojego zakresu. Destruktor nie może być ani const ani volatile, ale może pracować na obiektach swojej klasy z takimi przydomkami. 38
Destruktor - przykład definicja konstruktora 01. wizytowka::wizytowka( char *im, char *na ) 02. { 03. imie = new char[strlen( im )+1]; 04. strcpy( imie, im ); 05. nazw = new char[strlen( na )+1]; 06. strcpy( nazw, na ); 07. } definicja destruktora 01. wizytowka::~wizytowka( ) 02. { 03. delete nazw; 04. delete imie; 05. } 39
Destruktor - jawne wywołanie Destruktor można wywołać jawnie. Należy wówczas podać całą jego nazwę. Jawne wywołanie destruktora nie może się zacząć od ~ (wężyka). Wcześniej musi być albo obiekt, na rzecz którego jest wywoływany i kropka., albo wskaźnik do obiektu i ->. obiekt.~klasa( ); wskaznik->~klasa( ); Jeśli destruktor jest uruchamiany z wnętrza klasy, to wtedy nic nie stoi przed nazwą uruchamianej funkcji składowej (destruktora). ~klasa(); 40
Wywołanie nieistniejącego destruktora Może się zdarzyć, że klasa, którą się posługujemy, nie ma destruktora. Jeśli mimo to jawnie go wywołamy, to wywołanie takie zostanie zignorowane. Można również wywołać destruktor dla typu wbudowanego. Także i takie wywołanie jest dopuszczalne, ale ignorowane. 41
Przykład Napisanie prostego przykładu na wykorzystanie konstruktorów i destruktora. 42
Koniec Dziękuję za uwagę i zapraszam na 15 minut przerwy. W dalszej części ćwiczenia do samodzielnego wykonania.
Programowanie proceduralne Zadania do samodzielnego wykonania Zad. 01 Napisz klasę wektor, oraz konstruktory klasy: a. domyślny, b. kopiujący, c. przyjmujący współrzędne (x,y) końca wektora, d. przyjmujące dwa obiekty typu pkt, określające punkty początku i końca wektora. A także: e. destruktor, f. funkcję składową, która służą do dodawania wektorów, g. funkcję globalną, która służy do odejmowania wektorów, h. funkcję składową, służącą do zwracania normy wektora, i. mnożenie skalarne dwóch wektorów. 44