Podstawy Programowania Obiektowego Pojęcie konstruktora i destruktora. Spotkanie 04 Dr inż. Dariusz JĘDRZEJCZYK
Tematyka wykładu Pojęcie konstruktora Konstruktor domyślny Konstruktor sparametryzowany Konstruktor kopiujący Przeładowanie konstruktora Destruktor Jawne wywołanie konstruktora i destruktora 2
Definicja konstruktora Konstruktor jest specjalną funkcją składową (metodą klasy), która: wywołuje się automatycznie i niejawnie podczas definicji obiekt danej klasy, pozwala nadać wartości początkowe dla danych składowych klasy (inicjalizuje wartości początkowe dla atrybutów klasy). Konstruktory charakteryzują się on tym, że: nazywają się tak jak samo jak klasa, nie zwracają żadnego typu nawet void. 3
Cechy konstruktora 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 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. 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. 4
Zadania konstruktora Wywołanie konstruktora powoduje wykonanie następujących zadań: obliczenie rozmiaru obiektu alokacja obiektu w pamięci wyczyszczenie (zerowanie) obszaru pamięci zarezerwowanej dla obiektu (tylko w niektórych językach) wpisanie do obiektu informacji łączącej go z odpowiadającą mu klasą (połączenie z metodami klasy) wykonanie kodu klasy bazowej (w niektórych językach nie wymagane) wykonanie kodu wywołanego konstruktora Z wyjątkiem ostatniego punktu powyższe zadania są wykonywane wewnętrznie i są wszyte w kompilator lub interpreter języka, lub w niektórych językach stanowią kod klasy bazowej. 5
Zadania konstruktora W językach programowania w różny sposób oznacza się konstruktor: w C++, Javie, C# - jest to metoda o nazwie zgodnej z nazwą klasy w Pascalu - metoda której nazwę poprzedzono słowem kluczowym constructor. W języku C++ wyróżnia się następujące szczególne rodzaje konstruktorów: konstruktor domyślny konstruktor sparametryzowany (zwykły) konstruktor kopiujący 6
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. 7
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. }; 8
Konstruktor zwykły Konstruktor, który można wywołać, podając co najmniej jeden parametr. Jest to zwykły konstruktor stworzony przez twórcę klasy. Jego zadeklarowanie w C++ nie powoduje niejawnego generowania konstruktora domyślnego. Z reguły parametry takiego zwykłego konstruktora spełniają funkcję inicjalizatorów, które przypisują odpowiednie wartości wewnętrznym zmiennym tworzonego obiektu. 9
Konstruktor zwykły 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. } 10
Konstruktor kopiujący Konstruktorem kopiującym w danej klasie klasa nazywamy konstruktor, który można wywołać z jednym argumentem poniższego typu: mojaklasa::mojaklasa(const mojaklasa & ); 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. 11
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 ); 12
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. 13
Konstruktor kopiujący 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.}; 14
Konstruktor kopiujący 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.} 15
Konstruktor kopiujący 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.} 16
Wymóg stosowania konstruktora kopiującego Jest ZAPAS PAMIĘCI fizyk nazw imie Metz Albert kolega nazw imie 17
Wymóg stosowania konstruktora kopiującego Powinno być ZAPAS PAMIĘCI fizyk nazw imie Einstein Albert kolega nazw imie Metz Albert 18
Konstruktor kopiujący 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. } 19
Jawne wywołanie konstruktora 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 ) 20
Jawne wywołanie konstruktora 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. } 21
Jawne wywołanie konstruktora 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. } 22
Lista inicjalizacyjna konstruktora 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 } 23
Lista inicjalizacyjna konstruktora 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łą. 24
Lista inicjalizacyjna konstruktora 01. class abc 02.{ 03. const int stal; 04. float x; 05. char c; 06. abc( float pp, int dd, char znak ); 07.}; 08. abc::abc( float pp, int dd, char znak ):stal(dd),c(znak) 09. { 10. x = pp; 11. } 25
Niepubliczny konstruktor 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. 26
Wady konstruktora 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 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). 27
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ć: 28
Cechy destruktora 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. 29
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. 30
Destruktor 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. } 31
Jawne wywołanie destruktora 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(); 32
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. 33
Koniec Dziękuję za uwagę i zapraszam na 15 minut przerwy. W dalszej części ćwiczenia do samodzielnego wykonania. 34
Ćwiczenia do wykonania Zadanie 01 Zbuduj klasę LiczbaZespolona zawierająca następujące składniki: konstruktory: domyślny, zwykły, kopiujący, destruktor, funkcję wypisz -> wypisującą informację o obiekcie klasy LiczbaZespolona, funkcję ustaw -> zapisującą informację o obiektach klasy LiczbaZespolona, funkcję obliczsume -> dodającą dwie liczby zespolone, funkcję obliczroznice -> odejmującą dwie liczby zespolone, funkcję obliczliczbesprzezona -> obliczającą liczbę sprzężoną, funkcję obliczliczbeprzeciwna -> obliczającą liczbę przeciwną, funkcję obliczmoduł -> liczącą moduł z liczby zespolonej, Ponadto należy wyposażyć projekt w funkcję globalną: funkcję oblicziloczyn -> pozwalająca na mnożenie dwóch liczb zespolonych, Następnie w funkcji main należy stworzyć dwa obiekty klasy LiczbaZespolona oraz jedną zmienną dynamiczną klasy LiczbaZespolona. Zaprezentować działanie metod klasy na stworzonych obiektach. 35
Ćwiczenia do wykonania Uwaga: Dla liczby zespolonej z1 = a + bi oraz z2 = c + di gdzie: a,c -> to części rzeczywiste, b,d -> części urojone, operacje arytmetyczne przedstawiają się następująco: dodawanie: z1 + z2 = (a+c) + (b+d)i, odejmowanie: z1 - z2 = (a-c) + (b-d)i, mnożenie: z1*z2 = (a*c-b*d)+ (a*d+ b*c)i, moduł z z1=a + bi, to: z1 = pierwiastek(a^2 + b^2), liczba przeciwna do liczby zespolonej z1=a + bi, to: -z1 = -a - bi, liczbą sprzężoną z liczbą z1=a + bi, to: z1 = a - bi. 36