Szybki wstęp do OOP na przykładzie C++

46
Szybki wstęp do OOP na przykladzie C++ (wersja 0.1) Marcin Kośmider Instytut Fizyki Uniwersytet Zielonogórski [email protected] 8 grudzień 2010 1

Transcript of Szybki wstęp do OOP na przykładzie C++

Page 1: Szybki wstęp do OOP na przykładzie C++

Szybki wstęp do OOPna przykładzie C++

(wersja 0.1)

Marcin KośmiderInstytut Fizyki

Uniwersytet Zielonogó[email protected]

8 grudzień 2010

1

Page 2: Szybki wstęp do OOP na przykładzie C++

SPIS TREŚCI SPIS TREŚCI

Spis treści

1 Wstęp 3

2 Klasy 4

2.1 Wykorzystanie gotowych klas - klasa string . . . . . . . . . . . . . . . . . 5

2.2 Wykorzystanie gotowych klas - operacje wejścia/wyjścia . . . . . . . . . . 10

2.3 Tworzenie własnych klas i hermetyzacja danych . . . . . . . . . . . . . . . 12

2.4 Pliki nagłówkowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor . . . . . . . . . . 23

3 Dziedziczenie 29

3.1 Konstruktor, destruktor i wywołanie metod klasy bazowej . . . . . . . . . 32

3.2 Dziedziczenie i enkapsulacja . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4 Polimorfizm 38

5 Abstrakcja 42

2 Materiał dystrybuowany bezpłatnie

Page 3: Szybki wstęp do OOP na przykładzie C++

1 WSTĘP

1 Wstęp

Współczesny świat, w tym także współczesna fizyka, nie może się obejść bez metod itechnik komputerowych. Dobra znajomość tych technik, jak i szerokiej gamy narzędzi,pozwalają w sposób wygodny i sprawny rozwiązywać i optymalizować problemy orazzadania napotykane w toku studiów, życiu codziennym i przyszłej pracy zawodowej.Program kierunku fizyka w Instytucie Fizyki Uniwersytetu Zielonogórskiego jest dosto-sowany do tego wymogu, oczywiście w różnym stopniu w zależności od wybranej spe-cjalności. Mimo sporych różnic programowych w zakresie przedmiotów komputerowych(np. między specjalnością fizyka komputerowa a ekofizyka czy fizyka teoretyczna) częśćprzedmiotów jest wspólna. Jednym z pierwszych wspólnych przedmiotów jest przedmiot“Podstawy programowania” prowadzony w II semestrze pierwszego roku studiów.

Program tego przedmiotu zakłada naukę programowania od zupełnych podstaw - odpojęcia kodu źródłowego, kompilacji, aż po elementy związane z pojęciem klasy. Takidobór treści jest spowodowany słabym przygotowaniem uczniów z zakresu programowa-nia na poziomie szkoły średniej. W większości przypadków programowanie realizowanebyło w PASCALU, a jeżeli nawet był to język C++, to nie zawierał on żadnych elemen-tów programowania obiektowego. Dlatego też, mimo iż kurs podstaw programowaniatrwa cały semestr, to typowa tematyka zawiązana z programowaniem obiektowym naj-częściej jest pomijana w trakcie jego trwania (choć zależy to od aktualnego poziomugrup laboratoryjnych i tempa zdobywania umiejętności). Z drugiej strony programowa-nie obiektowe stanowi współczesny standard programowania i praktycznie niemożliwejest omawianie współczesnych bibliotek, języków programowania czy frameworków woderwaniu od pojęć związanych z programowaniem obiektowym i bez zrozumienia jegopodstawowych mechanizmów (jak np. omawiać analizę i prezentację danych w SciPyczy mówić o tworzeniu GUI bez zrozumienia programowania obiektowego). Studencispecjalności fizyka komputerowa są w tym wypadku w znacznie lepszej sytuacji ponie-waż w ich programie nauczania programowanie obiektowe jest osobnym, kolejnym popodstawach programowania przedmiotem.

Celem niniejszego opracowania jest szybkie i “łagodne” wprowadzenie do pojęć zwią-zanych z programowaniem obiektowym na przykładzie języka C++ tak, aby po jegolekturze móc zacząć programować obiektowo na poziomie podstawowym. Opracowanieprzeznaczone jest dla każdego kto zna podstawowe elementy języka C/C++ takie jakdeklaracje zmiennych, pętle, warunki, funkcje, tablice czy dynamiczną rezerwację pa-mięci. Nie jest to materiał z zakresu informatyki, bo unika abstrakcyjnego i formalnegojęzyka oraz teoretycznego podejścia całościowego do problematyki OOP, nie ma tutajzagadnień związanych z UML, z wzorcami projektowymi, z szablonami czy wreszcie niejest to opis języka C++ i jego bibliotek.

Osoby zainteresowane poszerzeniem swojej wiedzy w tym zakresie bez problemu znajdąstosowną literaturę i materiały w internecie.

3 Materiał dystrybuowany bezpłatnie

Page 4: Szybki wstęp do OOP na przykładzie C++

2 KLASY

2 Klasy

Klasa to jedno z fundamentalnych pojęć w programowaniu obiektowym. Zanim jed-nak wyjaśnimy czym jest klasa, zastanówmy się jak postrzegamy rzeczywistość i jakijest związek naszego postrzegania z programowaniem obiektowym. Obserwując swojeotoczenie, niejako podświadome widzimy i charakteryzujemy występujące w nim przed-mioty. Każdy z otaczających nas przedmiotów jest w jakiś sposób charakterystyczny -ma swój kształt, kolor, rozmiar, ma swoje funkcje czy zachowania. Co więcej, bardzoczęsto w danym przedmiocie jesteśmy w stanie wyróżnić inne przedmioty składowe. Teprzedmioty składowe także mają swoje własne cechy i zachowania. Jako przykład roz-ważmy żarówkę. Żarówka, np. ta, która właśnie świeci się w lampce na biurku, maswój kolor, ma swój kształt, ma konkretną, określoną średnicę gwintu, ma określonąmoc i określone napięcie z jakim może pracować. Co więcej, ma ona również dwa stany- włączona, wyłączona oraz charakterystyczne zachowania - świecenie i grzanie się. Innyprzykład z naszego otoczenia - pilot od telewizora. Ma on również swój kształt, swój nu-mer seryjny, swój kolor, oraz szereg funkcji - włącz/wyłącz, głośniej/ciszej, przełączaniekanałów itp.

Wszystkie przedmioty które nas otaczają nazywamy obiektami. Generalizując możemypowiedzieć, że obiekt to coś konkretnego, coś na co patrzymy, coś co ma swoje unikatowecechy, coś co może się jakoś zachowywać (np. włącz/wyłącz). Obiektem będzie żarówka,pilot od telewizora, samochód zaparkowany na parkingu przed oknem. Jednak analizującdalej obiekty z naszego otoczenia, możemy wyciągnąć kolejne wnioski. Pewne obiektysą bardzo podobne (prawie identyczne), inne zaś są zupełnie od siebie różne. Każdażarówka ma zespół takich samych wielkości i zachowań, które ją opisują. Każda żarówkama gwint, każda ma jakiś kolor, każda świeci (poza zepsutą). Każdy pilot od telewizorama możliwości włączania i wyłączania odbiornika, zmiany kanałów, regulacji głośności,każdy ma jakiś kolor i jest “przypisany” do jakiegoś modelu lub marki telewizora. Każdyobiekt więc należy do jakiegoś typu, który jednoznacznie go odróżnia od obiektów innegotypu. Każda żarówka jest do innych żarówek podobna (np. ma określoną moc), alezupełnie nie jest podobna do samochodu czy pilota od telewizora. Obiekty różnychtypów są czymś zupełnie innym. Co więcej, taki typ w przeciwieństwie do obiektówjest czymś zupełnie abstrakcyjnym, jest pojęciem, a nie konkretną rzeczą. Wykonującpolecenie “podaj mi żarówkę” podajemy konkretną żarówkę, konkretny obiekt (ma kolor,moc etc.) Nie możemy komuś podać żarówki jako abstrakcyjnego typu. Tak samo niemożemy powiedzieć w salonie samochodowym “kupuję samochód“. Możemy co najwyżejpowiedzieć, że chcemy kupić ten konkretny samochód, albo chcemy samochód o pewnych,konkretnych parametrach. Otaczające nas przedmioty są obiektami określonych typów.

Właśnie taki typ w programowaniu nazywamy klasą. Klasa jest pewnym szablonemopisującym cechy obiektów i ich zachowania. Jednak klasa mówi tylko jakie są to cechy izachowania, nie precyzując przy tym wartości tych cech. Klasa żarówka będzie zawieraćcechę - moc, ale nie będzie miała wartości tej mocy. Wartość będą miały już konkretneobiekty tej klasy - żarówki. Samochód “mały fiat” jako klasa (czyli typ) będzie miał

4 Materiał dystrybuowany bezpłatnie

Page 5: Szybki wstęp do OOP na przykładzie C++

2.1 Wykorzystanie gotowych klas - klasa string 2 KLASY

pole kolor, ale to konkretny “mały fiat” jest czerwony. Oczywiście pewne cechy mogąmieć wartość taką samą dla każdego obiektu klasy (np. pojemność silnika “malucha”)i wtedy są cechami samej klasy (tzw. pola statyczne klasy). Zespół zachowań jestnatomiast wspólny dla każdego obiektu klasy. Każda żarówka świeci, każdy samochódskręca w lewo, każdy pies szczeka.

Przekładając obserwacje z realnego świata na programowanie powiemy, że klasa jestpewnym typem, szablonem określającym jakie pola obiekty tej klasy mają oraz określa-jącym zachowania obiektów tej klasy. Pola to po prostu zmienne klasy, a zachowaniato funkcje, które w odróżnieniu od zwykłych funkcji (nie zawierających się w klasie)nazywamy metodami tej klasy. A więc klasa to nie tylko nowy typ danych, ale to typdanych wraz z możliwymi zachowaniami tych danych.

Podkreślmy po raz kolejny - klasa to szablon, klasa o danej nazwie jest jedna, klasajest pewnym przepisem, natomiast obiekt to konkretna realizacja tej klasy ( mówimyże obiekt to instancja klasy), obiektów danej klasy może być wiele, ale klasa będziezawsze jedna. Klasa to “mały fiat”, a obiekty tej klasy to “mały fiat” sąsiada, czerwony”mały fiat“ za oknem, klasa to student, ale student Jan Kowalski to już obiekt tejklasy bo ma konkretne cechy (czyli przykładowe pola tej klasy takie jak imię, nazwisko,kierunek studiów, grupa mają konkretne wartości, które odróżniają obiekty klasy studentod siebie).

2.1 Wykorzystanie gotowych klas - klasa string

Zanim stworzymy swoją pierwszą klasę zobaczmy jak korzystać z gotowych klas do-stępnych w bibliotekach języka. Jako przykład wykorzystamy klasę string. Aby móckorzystać z klasy string należy dołączyć plik nagłówkowy.

Listing 1:1 #i nc l ude <s t r i n g >2 us ing namespace s t d ;34 i n t main ( )5 {6 s t r i n g name ;7 }

Obiekty klasy deklarujemy tak samo samo jak zmienne zwykłych typów. Podajemynazwę klasy (jako typ), a później nazwę zmiennej reprezentującej obiekt tej klasy. Wpowyższym przykładzie utworzyliśmy obiekt klasy string, obiekt o nazwie name. Jednakw tym wypadku stworzenie obiektu jest czymś znacznie więcej niż zwykłym zdeklaro-waniem zmiennej. W momencie, kiedy tworzymy obiekt jakiejś klasy to zawsze odbywasię to poprzez wywołanie specjalnej metody zawartej w klasie (metody czyli funkcji).Metoda nazywa się konstruktorem i wywoływana jest ZAWSZE jako pierwsza w mo-mencie tworzenia klasy. Po co jest konstruktor ? Otóż, czasami w momencie tworzenia

5 Materiał dystrybuowany bezpłatnie

Page 6: Szybki wstęp do OOP na przykładzie C++

2.1 Wykorzystanie gotowych klas - klasa string 2 KLASY

obiektu danej klasy, chcemy dokładnie określić jak dany obiekt ma być tworzony - np.jaką mają mieć wartość początkową jego atrybuty (pola klasy), czy ma być stworzonyjako kopia innego obiekty tej klasy, czy może ma odbyć się dynamiczna rezerwacja pa-mięci, otwarcie pliku i wiele innych czynności. Jednym słowem, konstruktor odpowiadaza skonstruowanie obiektu. Bardzo często klasa dostarcza kilku różnych konstruktorówodpowiadających możliwościom tworzenia obiektów na podstawie różnych danych po-czątkowych. W powyższym programie w momencie tworzenia obiektu wywoływany jestkonstruktor bezparametrowy, który tworzy pusty obiekt klasy string.

Konstruktory klasy string zostały zebrane w tabeli 1.

Konstruktor Opis

s t r i n g ( )Konstruktor bezparametrowytworzący pusty obiekt klasystring

s t r i n g ( const s t r i n g & s )Nowy obiekt klasy string jesttworzony na podstawie już ist-niejącego obiektu s

s t r i n g ( const s t r i n g & s , s i z e t pos , s i z e t n )

Nowy obiekt klasy stringjest tworzony na podstawiepodstringu już istniejącegoobiektu s. Podstring zaczynasię na pozycji pos i zawiera nelementów

s t r i n g ( const char ∗ t , s i z e t n )

Nowy obiekt klasy string jesttworzony na podstawie npierwszych elementów tablicyznaków t

s t r i n g ( const char ∗ t )Nowy obiekt klasy string jesttworzony na podstawie ta-blicy znaków t

s t r i n g ( s i z e t n , char c )Nowy obiekt klasy string za-wiera n powtórzeń znaku c

Tablica 1: Konstruktory klasy string

Przykłady tworzenia obiektów klasy string:

Listing 2:1 #i nc l ude <i o s t r eam >2 #i nc l ude <s t r i n g >34 us ing namespace s t d ;56 i n t main ( )7 {

6 Materiał dystrybuowany bezpłatnie

Page 7: Szybki wstęp do OOP na przykładzie C++

2.1 Wykorzystanie gotowych klas - klasa string 2 KLASY

89 s t r i n g s1 ; // pus ty s t r i n g

10 s t r i n g s2 ( ” j a k i s n a p i s ” ) ; // to samo co s2=” j a k i s n a p i s ”11 char t [ ]= ”c−s t r i n g ” ; // t a b l i c a znakow z konczacym NULL12 // tzw c−s t r i n g13 s t r i n g s3 ( t ) ; // o b i e k t utworzony na pods taw i e t a b l i c y znakow14 s t r i n g s4 ( s2 , 6 , 5 ) ;15 s t r i n g s5 (10 , ’ a ’ ) ;1617 cout <<” s2 = ”<<s2<<e nd l ;18 cout <<” s3 = ”<<s3<<e nd l ;19 cout <<” s4 = ”<<s4<<e nd l ;20 cout <<” s5 = ”<<s5<<e nd l ;212223 re tu rn 0 ;24 }

wynik programu to:s2 = j a k i s n a p i ss3 = c−s t r i n gs4 = n a p i ss5 = aaaaaaaaaa

Jak już zostało to podkreślone, klasa to nie tylko same dane, ale to dane wraz z me-todami, które na tych danych pracują. Metody są wspólne dla wszystkich obiektówdanej klasy, jednak wywołuje się je zawsze (z wyjątkiem tzw. metod statycznych) narzecz konkretnych obiektów. Jest to dość oczywiste - metody to funkcje pracujące nakonkretnych danych, a to właśnie konkretne dane są tym, co rozróżnia obiekty tej samejklasy. Jeden człowiek ma na imię Jan a inny Karol, a więc metoda ”podajImie“ musiwyraźnie wskazywać kto ma to imię podać, a więc musi być wywołana dla konkretnegoobiektu. Jak to zrealizować technicznie ( a właściwie jak to zapisać) zależy od tego czypracujemy na obiekcie czy na wskaźniku do obiektu. W pierwszym przypadku odwołaniedo metody lub atrybutu obiektu odbywa się za pomocą znaku ”.“:s t r i n g s ( ” n a p i s ” ) ;cout << s . s i z e ()<< e nd l ; // odwo lan i e do metody s i z e na r z e c z o b i e k t u s

w drugim przypadku poprzez dwa znaki ”->“:s t r i n g s ( ” n a p i s ” ) ;s t r i n g ∗ s1=&s ;cout << s1−>s i z e ()<< e nd l ; // odwo lan i e do metody s i z e

//na r z e c z o b i e k t u s1 ( pop rzez wskazn ik )

Programowanie z wykorzystaniem gotowych klas to przede wszystkim paca z dokumen-tacją dotyczącą konkretnych bibliotek i klas. Celem tego opracowania nie jest przeglądwszystkich standardowych klas wraz z ich metodami, dlatego ograniczymy się do zastoso-wani kilku metod z klasy string na konkretnym przykładzie. Z całością standardowej do-kumentacji zapoznać się można np. pod adresem http://www.cplusplus.com/reference.

7 Materiał dystrybuowany bezpłatnie

Page 8: Szybki wstęp do OOP na przykładzie C++

2.1 Wykorzystanie gotowych klas - klasa string 2 KLASY

Napiszmy dla przykładu program, który pobierze z klawiatury napis, wypisze informacjęo jego długości, następnie wypisze każdą jego literę w nowej linii, a na zakończenieutworzy nowy obiekt klasy string zawierający podany napis, ale zapisany od końca.Dodatkowo program sprawdzi czy napis jest palindromem (w tym wypadku porównaniebędzie uwzględniać zarówno wielkość liter jaki i tzw. białe znaki!!!!).

Listing 3:1 #i nc l ude <i o s t r eam >2 #i nc l ude <a l go r i t hm >3 #i nc l ude <s t r i n g >45 us ing namespace s t d ;67 i n t main ( )8 {9

10 s t r i n g s1 , s2 ;1112 cout <<” Podaj j a k i s n a p i s ”<<e nd l ;13 // czytamy l i n i e14 g e t l i n e ( c in , s1 ) ;15 cout <<” Poda l e s n a p i s o d l u g o s c i ”<<s1 . s i z e ()<< e nd l ;16 cout <<” Zawiera on z n a k i : ”<<e nd l ;17 f o r ( i n t i =0; i <s1 . s i z e ( ) ; i ++)18 cout <<s1 . a t ( i )<<e nd l ;1920 // kop i a o b i e k t u21 s2=s1 ;22 // odwrocen i e23 r e v e r s e ( s2 . beg in ( ) , s2 . end ( ) ) ;24 cout <<” Napis od konca to : ” ;25 cout <<s2<<e nd l ;26 // sprawdzamy pa l i nd rom :27 i f ( s2==s1 )28 cout <<” Napis j e s t pal indromem ”<<e nd l ;29 e l s e30 cout <<” Napis n i e j e s t pal indromem ”<<e nd l ;31 re tu rn 0 ;32 }

W linii 2 dołączamy do naszego programu bibliotekę z algorytmami (część bibliotekiSTL), a to po to, aby wykorzystać zawartą tam funkcję reverse (uwaga funkcję a niemetodę !!!!!) odwracającą zawartość stringa (w ogólności każdego kontenera). W naszymprogramie w linii 10 tworzymy dwa puste obiekty klasy string - obiekt o nazwie s1 i obiekto nazwie s2. Następnie w linii 14 pobieramy dane z klawiatury i zapisujemy je w obiekcies1. Dane odczytujemy za pomocą funkcji getline po to, aby odczytać wszystkie danewprowadzone przez użytkownika w danej linii. Zakładamy, że użytkownik poproszony owprowadzenie napisu może chcieć wpisać jakieś zdanie zawierające białe znaki (spacja,tabulator itp). Gdybyśmy skorzystali z formatowanych operacji wejścia/wyjściac i n >>s1 ;

8 Materiał dystrybuowany bezpłatnie

Page 9: Szybki wstęp do OOP na przykładzie C++

2.1 Wykorzystanie gotowych klas - klasa string 2 KLASY

to wtedy wszystko co zostałoby wprowadzone po znaku spacji zostałoby zignorowane.Funkcja getline nie interpretuje znaków za wyjątkiem znaku końca linii, a więc wczytawszystkie znaki wprowadzone aż do naciśnięcia klawisza Enter. W linii 17 i 18 wyko-rzystujemy dwie metody z klasy string - size() i at() wywołując je na rzecz obiektu s1.Pierwsza z nich zwraca długość stringa (a więc mamy pewność, że pętla for przebiegniepo każdej literze napisu), druga zwraca znak znajdując się na pozycji wskazanej przezparametr wywołania metody at(). Od linii 21 zaczyna się druga część naszego programu.Najpierw kopiujemy obiekt s1 jako obiekt s2 wykorzystując przy tym operator przypisa-nia odpowiednio przedefiniowany w klasie string. Następnie korzystając z funkcji reversodwracamy napis zawarty w obiekcie s2. Ważne jest to, że reverse jest funkcją a niemetodą - nie jest ona wywoływana na rzecz żadnego obiektu. Przyjmuje ona dwa pa-rametry - tzw. iteratory początku i końca zakresu kontenera który chcemy odwrócić.Metoda begin() wywołana na rzecz obiektu s2 zwraca iterator (analog do wskaźnika) dopoczątku stringu s2, a metoda end() do jego końca. Na zakończenie wystarczy spraw-dzić czy podany napis jest palindromem. Twórcy klasy string zadbali w tym wypadku ostosowne przedefiniowanie operatora porównania (==), a więc możemy go użyć zgodniez jego przeznaczeniem. Wynik programu jest następujący:Podaj j a k i s n a p i sj a k i s n a p i sPoda l e s n a p i s o d l u g o s c i 11Zawiera on z n a k i :jakis

napisNapis od konca to : s i p a n s i k a jNapis n i e j e s t pal indromem

a w przypadku palindromu ”kobyla ma maly bok“ (uwaga na spacje !!!!)Podaj j a k i s n a p i skobylamamalybokPoda l e s n a p i s o d l u g o s c i 15Zawiera on z n a k i :kobylama

9 Materiał dystrybuowany bezpłatnie

Page 10: Szybki wstęp do OOP na przykładzie C++

2.2 Wykorzystanie gotowych klas - operacje wejścia/wyjścia 2 KLASY

malybokNapis od konca to : kobylamamalybokNapis j e s t pal indromem

2.2 Wykorzystanie gotowych klas - operacje wejścia/wyjścia

Jako kolejny przykład wykorzystania gotowych klas i ich obiektów omówmy zagadnie-nia związane z operacjami wejścia/wyjścia na przykładzie operacji plikowych. Aby móckorzystać z operacji plikowych dołączamy plik nagłówkowy <fstream >. Jak zwyklepracę z klasą zaczynamy od zapoznania się z możliwymi konstruktorami. W tym wy-padku nie mamy wielkiego wyboru. Obiekty klasy fstream możemy tworzyć albo tylkopoprzez ich deklarację (czyli za pośrednictwem pustego konstruktora), albo podając wmomencie tworzenia obiektu nazwę pliku (w postaci c-stringu !!!!) i tryb dostępu dopliku (dokładny opis w dokumentacji). Załóżmy, że chcemy odczytać plik o podanejnazwie (wprowadzonej przez użytkownika) i wyświetlić kilka informacji o tym pliku.

Przykładowy plik tekstowy - dane.txtTo j e s t l i n i a p i e r w s z aTo j e s t l i n i a drugat r z e c i ai k o n i e c p l i k u .

i nasz program

Listing 4:1 #i nc l ude <i o s t r eam >2 #i nc l ude <f s t r eam >3 #i nc l ude <s t r i n g >45 us ing namespace s t d ;67 i n t main ( )8 {9 s t r i n g nazwa ;

1011 cout <<” Podaj nazwe p l i k u ”<<e nd l ;12 c i n >>nazwa ;1314 f s t r e am p l i k ( nazwa . c s t r ( ) , f s t r e am : : i n ) ;1516 // j e s l i o b i e k t n i e z o s t a l utworzony

10 Materiał dystrybuowany bezpłatnie

Page 11: Szybki wstęp do OOP na przykładzie C++

2.2 Wykorzystanie gotowych klas - operacje wejścia/wyjścia 2 KLASY

17 i f ( p l i k . f a i l ( ) )18 {19 cout <<” Blad zwiazany z otwarc iem p l i k u ! ! ! ! ”<<en d l ;20 re tu rn 1 ;21 }2223 // p l i k o twar ty a wiec czytamy dane l i n i a po l i n i i24 char b u f f [ 1 0 2 4 ] ;2526 whi le ( ! p l i k . e o f ( ) && p l i k . good ( ) )27 {28 p l i k . g e t l i n e ( bu f f , 1 0 2 4 ) ;29 cout <<”Wczytano l i n i e z a w i e r a j a c a ”<<p l i k . gcount ( )30 <<” znakow”<<e nd l ;31 cout <<” Tresc wczy tane j l i n i i to : ”<<bu f f <<end l<<en d l ;32 }3334 p l i k . c l o s e ( ) ;35 re tu rn 0 ;36 }

wynik:Podaj nazwe p l i k udane . t x tWczytano l i n i e z a w i e r a j a c a 23 znakowTo j e s t l i n i a p i e r w s z a

Wczytano l i n i e z a w i e r a j a c a 20 znakowTo j e s t l i n i a druga

Wczytano l i n i e z a w i e r a j a c a 8 znakowt r z e c i a

Wczytano l i n i e z a w i e r a j a c a 15 znakowi k o n i e c p l i k u .

Do linii 14 właściwie nie pojawia się nic nowego. W linii 14 definiujemy obiekt o na-zwie plik, który jest obiektem klasy fstream. W tym wypadku używamy konstruktora,który wymaga podania nazwy pliku w postaci wskaźnika do tablicy znaków zakończonejznakiem NULL (czyli po prostu c-stringa). W naszym programie nazwa pliku zostałapobrana z klawiatury i zapisana w obiekcie klasy string. Jednak klasa ta definiuje metodęc str(), która wywołana na rzecz obiektu klasy string zwraca jego elementy w postacic-stringa. Drugi parametr konstruktora określa typ dostępu do pliku - w tym wypadkufstream::in co oznacza, że plik zostanie otwarty do odczytu. Kolejnym niezbędnym ele-mentem jest sprawdzenie czy udało się otworzyć plik zgodnie z tym co zostało przesłanew konstruktorze (linia 17). Metoda fail() (wywołana na rzecz obiektu plik w tym wy-padku) zwraca czy bit błędu jest ustawiony czy nie. Jeżeli wszystko poszło pomyślniemożna rozpocząć pracę z plikiem. Aby właściwie przeczytać dane z pliku tekstowego(bez pomijania np. białych znaków) użyjemy nieformatowanych operacji wejścia/wyj-

11 Materiał dystrybuowany bezpłatnie

Page 12: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

ścia. Metoda getline() czyta z pliku jedną linię (jako linię znaków) i przeczytane daneumieszcza w podanej tablicy znaków. Drugi parametr tej metody to maksymalny roz-miar bufora (tablicy w której dane będą zapisywane). Czytanie z pliku linia po liniitrwać będzie tak długo, dopóki nie dotrzemy do końca pliku (metoda eof() zwraca truejeśli napotkano koniec pliku) lub nie wystąpił jakiś błąd (metoda good() zwraca wartośćtrue jeśli nie wystąpił błąd w operacjach wejścia/wyjścia). Jako dodatkową informacjęwyświetlamy na ekranie ilość przeczytanych elementów (linia 29).Metoda gcount zwracailość przeczytanych bajtów w trakcie ostatniej wykonanej operacji wejścia/wyjścia. Wnaszym wypadku operacje to czytanie całej linii z pliku, a więc gcount zwraca ilośćznaków w linii, a dokładniej o jeden więcej ze względu na znak nowej linii.

2.3 Tworzenie własnych klas i hermetyzacja danych

Programowanie polega na rozwiązywaniu realnych problemów (postrzeganych obiek-towo), a więc wcześniej czy później każdy spotka się z koniecznością stworzenia własnychklas stosownych do rozwiązania zadanego problemu. Deklaracja klasy zawiera deklaracjęjej pól oraz metod i jest informacją dla kompilatora jak postępować z obiektami danejklasy. Aby stworzyć własną klasę używamy słówka kluczowego class.c l a s s ComplexNumber{

// t u t a j j e s t c i a l o k l a s y} ;

Bardzo ważne jest to, że zamykający nawias } zakończony jest znakiem ”;“ !!!! Przyokazji omawiania tworzenia własnych klas należy wspomnieć o standardach związanychz nazewnictwem. Wprawdzie standardy te nie stanowią formalnego wymogu językaprogramowania, ale niewątpliwie ułatwiają programowanie i wpływają na jakość koduźródłowego. Oczywiście standardów jest kilka, jednak na potrzeby tego opracowaniaprzyjmiemy następujące reguły:

• Nazwy klas piszemy z dużej litery. Jeżeli nazwa składa się z kilku wyrazów, tokażdy z nich piszemy dużą literą np. Numbers, ComplexNumbers, Person, Dowo-dOsobisty.

• Nazwy zmiennych piszemy od małej litery. Jeżeli nazwa składa się z kliku wyrazów,to każdy z nich piszemy od dużej litery (za wyjątkiem pierwszego) np: suma, kolor,mainWindow.

• Nazwy metod podlegają tym samym regułom co nazwy zmiennych.

• Stałe piszemy dużymi literami

12 Materiał dystrybuowany bezpłatnie

Page 13: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

Powyższy standard nosi nazwę ”camel“ i jest jednym z najpopularniejszych (np. językJava, framework QT). Jak łatwo zauważyć standardu tego nie stosuje np. bibliotekaSTL (klasa string, vector etc.)

W przypadku tworzenia własnych klas należy bardzo wyraźnie odróżnić deklarację klasyod jej definicji. Deklaracja mówi czym klasa jest - jakie ma pola i jakie metody jednaknie zawiera konkretnej implementacji tych metod. W języku C++ wszystkie deklaracjeklas powinny się znajdować w plikach nagłówkowych ( o rozszerzeniu .h), a ich definicjew plikach o rozszerzeniu cpp (znowu wspomniana zasada jest pewną konwencją, choćczasami jest wymogiem formalnym - np. QT). Na razie jednak dla prostoty nie będziemydokonywać takiego rozbicia.

Kontynuujmy tworzenie klasy ComplexNumber reprezentującej liczby zespolone. Popierwsze musimy się zastanowić jakie pola klasa będzie miała. W tym przypadku sytu-acja jest oczywista - dwa pola typu double reprezentujące część rzeczywistą i urojonąliczby zespolonej. Drugi etap projektowania klasy to wyróżnienie konstruktorów, a więcsposobów w jaki obiekty klasy mogą być tworzone. Konstruktor to po prostu metodao nazwie takiej samej jak nazwa klasy, metoda która nic nie zwraca. Jeżeli sami nienapiszemy żadnego konstruktora, to domyślnie tworzone są dwa - konstruktor bezpara-metrowy i konstruktor kopiujący. Pierwszy nie robi nic, drugi ma za zdanie utworzyćobiekt danej klasy na podstawie przesłanego obiektu tej samej klasy,a więc po prostuma wykonać kopię obiektu. Zagadnienie konstruktora kopiującego zostanie omówione wjednym z kolejnych podrozdziałów.

Listing 5:1 c l a s s ComplexNumber2 {3 double re , im ;45 // k o n s t r u k t o r y6 // bezparametrowy u s t a w i a j a c y w a r t o s c i r e i im na 07 ComplexNumber ( )8 {9 r e =0.0 ;

10 im =0.0;11 }1213 // k o n s t r u k t o r z dwoma parametrami − im i r e14 ComplexNumber ( double re , double im )15 {16 th i s−>r e=r e ;17 th i s−>im=im ;18 }19 } ;

W drugi konstruktorze tworzone są dwie zmienne lokalne re i im. Nazwy tych zmien-nych są dokładnie takie same jak nazwy pól klasy, czyli je przekrywają. Dlatego chcącustawić wartość pola klasy musimy dokładnie określić, że chodzi nam o zmienne klasy

13 Materiał dystrybuowany bezpłatnie

Page 14: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

a nie lokalne zmienne tej metody, a więc musimy użyć wskaźnika this. Wskaźnik tenwskazuje nam na ten konkretny obiekt na którym właśnie pracujemy. Oczywiście możnaby unikać przekrywania nazw i zmienne deklarowane w metodzie nazwać np. a i b, jed-nak wtedy czytelność kodu dramatycznie się zmniejsza - sama deklaracja metody niedaje nam żadnej informacji co ta metoda będzie robić. Wykorzystajmy naszą klasę (wtym wypadku listing przedstawia cały plik o nazwie zespolone.cpp).

Listing 6:1 #i nc l ude <i o s t r eam >2 us ing namespace s t d ;345 c l a s s ComplexNumber6 {7 double re , im ;89 // k o n s t r u k t o r y

10 // bezparametrowy u s t a w i a j a c y w a r t o s c i r e i im na 011 ComplexNumber ( )12 {13 r e =0.0 ;14 im =0.0;15 }1617 // k o n s t r u k t o r z dwoma parametrami − im i r e18 ComplexNumber ( double re , double im )19 {20 th i s−>r e=r e ;21 th i s−>im=im ;22 }23 } ;2425 i n t main ( )26 {27 ComplexNumber z1 ( 1 0 , 5 ) ;28 re tu rn 0 ;29 }

Próba kompilacji przebiegnie niepomyślnie, kompilator zwróci następujące błędy:g++ z e s p o l o n e . cppz e s p o l o n e . cpp : I n f u n c t i o n i n t main ( ) :z e s p o l o n e . cpp : 1 8 : e r r o r : ComplexNumber : : ComplexNumber ( double , doub l e )i s p r i v a t ez e s p o l o n e . cpp : 2 7 : e r r o r : w i t h i n t h i s c o n t e x t

W linii 27 próbujemy tworzyć obiekt za pomocą konstruktora przyjmującego dwa pa-rametry. Oczywiście taki konstruktor jest w naszej klasie (linia 18), jednak kompilatorzgłosił błąd polegający na próbie dostępu do prywatnych składowych klasy. W językuC++ wszystkie składniki domyślnie są prywatne.

14 Materiał dystrybuowany bezpłatnie

Page 15: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

W tym momencie spotykamy drugie fundamentalne pojęcie związane z programowa-niem obiektowym - hermetyzacja danych lub inaczej enkapsulacja. Znowu i tymrazem najprościej będzie odwołać się do obserwacji przedmiotów (obiektów) w naszymotoczeniu. Weźmy dla przykładu telewizor. Telewizor jest jakimś obiektem posiadają-cym mnóstwo atrybutów (oporniki, procesory, tranzystory, kineskop, ....) oraz szeregzachowań (włącz, wyłącz, głośniej, ciszej, .....). Jednak my jako użytkownicy nie mamydostępu bezpośredniego do wszystkich elementów telewizora. Mimo, iż praca telewi-zora polega na zmianie stanu jego atrybutów (np. przyłożenie odpowiedniego napięciado poszczególnych elementów) to my jako użytkownicy tej zmiany dokonujemy poprzezprzygotowany dla nas interfejs (np. pilot), nie mamy dostępu do elementów składowychi nie manipulujemy nimi (nie przełączamy ”kabelków”). Możemy więc powiedzieć, żeelementy wewnętrzne telewizora są jego składowymi prywatnymi. Wszystkie elementyprywatne są niedostępna spoza danej klasy - a więc elementy wewnątrz telewizora wpły-wają na swoje działanie bezpośrednio, ale my znajdując się na zewnątrz zmiany te wpro-wadzamy tylko za pośrednictwem publicznych (czyli ogólnodostępnych) metod. Ogólnareguła dobrego programowania mówi, że wszystkie pola powinny być prywatne, a dostępdo nich powinien być realizowany za pośrednictwem publicznych metod.

Idea tego podejścia jest znacznie głębsza niż zwykła dbałość o to, aby użytkownik cze-goś nie zepsuł. Enkaspulacja zapewnia separację konkretnej realizacji kodu od interfejsu(czyli mówiąc ogólnie metody pracy z klasą). Programista projektujący klasę może do-starczyć metodę string getFirstName() i dla użytkownika tej klasy nie ma znaczenia jakzmienna firstName jest reprezentowana w klasie. Może się zdarzyć tak, że na początkuto będzie zwykła zmienna typu string, ale z biegiem czasu metoda ta zwróci wynik wy-szukiwania w rozproszonej bazie danych. Dzięki enkaspsulacji, program bazujący natakiej klasie nie ulegnie zmianie bo zmieni się tylko prywatna część klasy, ale interfejspozostał ten sam. Można to porównać z samochodem. Nie ma znaczenia jaki silnik jest”pod maską“ sposób zmiany biegów, hamowania, przyspieszana i skręcania jest taki sam- działanie silnika jest ”prywatne“, a obsługa samochodu to publiczny interfejs.

W języku C++ mamy trzy modyfikatory dostępu do danych - public, private i pro-tected. Pierwszy oznacza, że wszystkie pola i metody znajdujące się za jego deklaracjąbędą publiczne (a więc dostępne spoza klasy), drugi oznacza, że będą prywatne (a więcdostępne tylko z poziomu tej klasy) i to jest domyślny tryb dostępu, trzeci tryb dostępuma znaczenie w dziedziczeniu i zostanie omówiony później. Zmieńmy w takim razienaszą klasę, tak aby konstruktory były metodami publicznymi.

Listing 7:1 c l a s s ComplexNumber2 {3 double re , im ;45 p u b l i c :6 // k o n s t r u k t o r y7 // bezparametrowy u s t a w i a j a c y w a r t o s c i r e i im na 08 ComplexNumber ( )

15 Materiał dystrybuowany bezpłatnie

Page 16: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

9 {10 r e =0.0 ;11 im =0.0;12 }1314 // k o n s t r u k t o r z dwoma parametrami − im i r e15 ComplexNumber ( double re , double im )16 {17 th i s−>r e=r e ;18 th i s−>im=im ;19 }20 } ;

Wszystkie metody i ewentualne pola począwszy od linii 5 traktowane będą jako pu-bliczne, chyba, że zmienimy dostęp na private. Aby móc przetestować naszą klasępowinniśmy mieć dostęp do informacji o części rzeczywistej i urojonej liczby zespolonej.W obecnej postaci pola te są prywatne i nie możemy się do nich odwołać. Dlatego do-damy do klasy metody dostępowe set (ustawiające pola) i get (pobierające wartości tychpól). Metody takie nazywa się muttator i accesor.

Listing 8:12 c l a s s ComplexNumber3 {4 double re , im ;56 p u b l i c :7 // k o n s t r u k t o r y8 // bezparametrowy u s t a w i a j a c y w a r t o s c i r e i im na 09 ComplexNumber ( )

10 {11 r e =0.0 ;12 im =0.0;13 }1415 // k o n s t r u k t o r z dwoma parametrami − im i r e16 ComplexNumber ( double re , double im )17 {18 th i s−>r e=r e ;19 th i s−>im=im ;20 }2122 // metody dostepowe do po l2324 vo id se tRe ( double r e )25 {26 th i s−>r e=r e ;27 }2829 vo id se t Im ( double im )30 {

16 Materiał dystrybuowany bezpłatnie

Page 17: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

31 th i s−>r e=im ;32 }3334 double getRe ( )35 { re tu rn r e ;}3637 double getIm ( )38 { re tu rn im ;}3940 } ;

Następnym elementem tworzenia klasy jest dodawanie do niej kolejnych funkcjonalności- obliczenie modułu liczby zespolonej, operacje matematyczne na liczbach czy operacjeporównujące dwie liczby. W języku C++ mamy możliwość przedefiniowania operatorówtak, aby praca z obiektami tworzonych klas była jak najbardziej wygodna i intuicyjna.Problem ten jednak wymaga szerszego omówienia i w tym opracowaniu nie zostanieomówiony. Naszą klasę uzupełnimy o dwie metody - metodę obliczającą moduł liczbyzespolonej i metodę zwracającą sumę dwóch liczb zespolonych (ale bez przedefiniowaniaoperatora dodawania).

Moduł liczby zespolonej to po prostu

|z| =√re2 + im2.

Jednak, aby móc skorzystać z pierwiastkowania musimy do naszego programu dołączyćplik nagłówkowy <cmath >. W przypadku dodawania sprawa jest oczywista:

(a+ ib) + (c+ id) = (a+ c) + i(b+ d)

jednak do rozstrzygnięcia pozostaje inna kwestia - jak zapisać wynik dodawania. Możli-wości są co najmniej dwie - albo modyfikujemy liczbę zespoloną (w sensie obiektu klasyComplexNumber) do której dodajemy inną liczbę, albo w wyniku operacji otrzymujemynową liczbę zespoloną (nowy obiekt klasy ComplexNumber). Oczywiste jest, że jedynymrozsądnym rozwiązaniem jest opcja druga.

Listing 9:1 #i nc l ude <i o s t r eam >2 #i nc l ude <cmath>34 us ing namespace s t d ;567 c l a s s ComplexNumber8 {9 double re , im ;

1011 p u b l i c :12 // k o n s t r u k t o r y13 // bezparametrowy u s t a w i a j a c y w a r t o s c i r e i im na 0

17 Materiał dystrybuowany bezpłatnie

Page 18: Szybki wstęp do OOP na przykładzie C++

2.3 Tworzenie własnych klas i hermetyzacja danych 2 KLASY

14 ComplexNumber ( )15 {16 r e =0.0 ;17 im =0.0;18 }1920 // k o n s t r u k t o r z dwoma parametrami − im i r e21 ComplexNumber ( double re , double im )22 {23 th i s−>r e=r e ;24 th i s−>im=im ;25 }2627 // metody dostepowe do po l2829 vo id se tRe ( double r e )30 {31 th i s−>r e=r e ;32 }3334 vo id se t Im ( double im )35 {36 th i s−>r e=im ;37 }3839 double getRe ( )40 { re tu rn r e ;}4142 double getIm ( )43 { re tu rn im ;}444546 // metdoy abs i add47 double abs ( )48 { re tu rn s q r t ( r e ∗ r e + im∗ im ) ; }4950 ComplexNumber add ( ComplexNumber &w)51 {52 re tu rn ComplexNumber (w. getRe ()+ re , w. get Im ()+im ) ;53 }5455 } ;5657 i n t main ( )58 {59 ComplexNumber z1 ( 1 , 1 ) , z2 ( 4 , 2 ) , z3 ;6061 z3=z1 . add ( z2 ) ;62 cout <<”Nowa l i c z b a z e s p o l o n a : ”<<e nd l ;63 cout <<” r e = ”<<z3 . getRe()<< e nd l ;64 cout <<” im = ”<<z3 . get Im()<< e nd l ;65 cout <<”Modul l i c z b y z e s p o l o n e j z3 = ”<<z3 . abs()<< e nd l ;66

18 Materiał dystrybuowany bezpłatnie

Page 19: Szybki wstęp do OOP na przykładzie C++

2.4 Pliki nagłówkowe 2 KLASY

67 re tu rn 0 ;68 }

Prezentowany powyżej sposób zapisu klasy polegający na pomieszaniu deklaracji z de-finicjami jest bardzo złym nawykiem. Po pierwsze, kod takiej klasy jest nieczytelny.Użytkownik klasy chciałbym mieć szybki dostęp do informacji na temat dostępnych me-tod i pól bez konieczności ”przedzierania“ się przez kod źródłowy. Po drugie, dobrzenapisana klasa będzie wykorzystywana wielokrotnie, a więc umieszczenie deklaracji, de-finicji i przykładowego wykorzystania w jednym pliku powoduje brak takiej możliwości.Po trzecie klasy często wykorzystują inne pliki nagłówkowe (przykładowo w naszymprzypadku cmath). To definicja klasy powinna określać co ma być dołączone aby klasadziałała. Użytkownik nie musi, co więcej najczęściej nie chce, wiedzieć co dołącza klasa.Użytkownik chce dołączyć bibliotekę z daną klasą i móc z niej korzystać. I po czwarteprzy powyższym zapisie niemożliwe jest używanie zmiennych i metod statycznych po-nieważ muszą one być definiowane poza obszarem deklaracji klasy.

2.4 Pliki nagłówkowe

Dla przykładu rozpatrzmy klasę Employee reprezentującą pracownika. Załóżmy dlaprostoty, że każdy pracownik ma tylko trzy atrybuty go opisujące - imię, nazwisko istanowisko pracy. Każde z tych pól jest po prostu ciągiem znaków (stringiem). Zgodniez tym co powiedzieliśmy wcześniej, pola te będą polami prywatnymi, a dostęp do nichrealizować będą publiczne metody.

Zacznijmy od deklaracji klasy z wyróżnieniem interfejsu. Deklarację zapisujemy w plikunagłówkowym (z rozszerzeniem .h), najczęściej o nazwie takiej, jak nazwa klasy, którajest w nim deklarowana. W naszym przypadku plik employee.h wyglądałby następująco:

Listing 10:1 // employee . h23 #i f n d e f EMPLOYEE H4 #def ine EMPLOYEE H56 #i nc l ude <s t r i n g >789 c l a s s Employee

10 {11 // p u b l i c i n t e r f a c e12 p u b l i c :13 Employee ( ) {} ;14 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,15 s t d : : s t r i n g p o s i t i o n ) ;16 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ) ;17

19 Materiał dystrybuowany bezpłatnie

Page 20: Szybki wstęp do OOP na przykładzie C++

2.4 Pliki nagłówkowe 2 KLASY

18 s t d : : s t r i n g getF i r s tName ( ) ;19 s t d : : s t r i n g getSecondName ( ) ;20 s t d : : s t r i n g g e t P o s i t i o n ( ) ;2122 vo id s e t P o s i t i o n ( s t d : : s t r i n g p o s i t i o n ) ;2324 // p r i v a t e data25 p r i v a t e :26 s t d : : s t r i n g f i r s tName , secondName , p o s i t i o n ;2728 } ;29 #e n d i f

Plik nagłówkowy wymaga pewnego omówienia. Po pierwsze, musimy zabezpieczyć sięprzed sytuacją, że nasz plik będzie dołączany wielokrotnie do programu. Taka sytu-acja spowodowałaby ponowną deklarację klasy o tej samej nazwie, a więc kompilatorzgłosiłby błąd. Standardowym rozwiązaniem jest ”obłożenie“ deklaracji klasy przez dy-rektywy pre-procesora (linia 3,4 i 28). Rozwiązanie sprowadza się do sprawdzenia czyzostała zdefiniowana zmienna o nazwie EMPLOYEE H. Jeżeli nie, to jest ona definio-wana i dołączana jest deklaracja klasy, jeżeli natomiast zmienna była zdeklarowana, tocały kod pomiędzy #ifndef i #endif zostanie pominięty. Drugi problem to używanieusing namespace w plikach nagłówkowych. Sprawa jest prosta - nie używać nigdy,ponieważ może to prowadzić do wielu, bardzo trudnych do wyłapania błędów (patrzgoogle: namespace pollution). Plik nagłówkowy nie zawiera konkretnego kodu metod(za wyjątkiem pustego konstruktora) a jedynie deklaruje jakie metody w klasie są, jakijest do nich dostęp oraz jakie pola klasa posiada. Dość często przyjętym standardemjest wypisywanie najpierw publicznych metod i pól (choć dyskusyjne jest czy takie byćpowinny), a później dopiero prywatnych składowych klasy. Powód jest bardzo prosty -informacja jak używać obiektów klasy jest dostępna od razu bez konieczności przewijaniakolejnych ekranów z kodem.

Następnym krokiem jest zaimplementowanie wypisanych metod w pliku z kodem (np. orozszerzeniu cpp). W naszym przypadku plik nazywa się employee.cpp.

Listing 11:1 // employee . cpp2 #i nc l ude ” employee . h”34 us ing s t d : : s t r i n g ;56 Employee : : Employee ( s t r i n g f i r s tName , s t r i n g secondName , s t r i n g p o s i t i o n )7 {8 th i s−>f i r s tName=f i r s tName ;9 th i s−>secondName=secondName ;

10 th i s−>p o s i t i o n=p o s i t i o n ;11 }1213 Employee : : Employee ( s t r i n g f i r s tName , s t r i n g secondName )14 {

20 Materiał dystrybuowany bezpłatnie

Page 21: Szybki wstęp do OOP na przykładzie C++

2.4 Pliki nagłówkowe 2 KLASY

15 th i s−>f i r s tName=f i r s tName ;16 th i s−>secondName=secondName ;17 p o s i t i o n=” brak p r z y d z i a l u ” ;18 }1920 s t r i n g Employee : : ge tF i r s tName ( )21 {22 re tu rn f i r s tName ;23 }2425 s t r i n g Employee : : getSecondName ( )26 {27 re tu rn secondName ;28 }2930 s t r i n g Employee : : g e t P o s i t i o n ( )31 {32 re tu rn p o s i t i o n ;33 }3435 vo id Employee : : s e t P o s i t i o n ( s t r i n g p o s i t i o n )36 {37 th i s−>p o s i t i o n=p o s i t i o n ;38 }

Po pierwsze musimy dołączyć plik nagłówkowy z deklaracją naszej klasy. W odróżnieniuod plików dołączanych z standardowych bibliotek, nasze pliki nagłówkowe dołączane sąpoprzez ujęcie nazwy pliku w podwójny cudzysłów. Jeżeli plik znajduje się w jakimśpodkatalogu to musimy podać w jego nazwie ścieżkę (względną lub bezwzględną) dotego pliku np. #include ”src/employee.h“.

Drugi problem znowu wiąże się z przestrzeniami nazw - nie używamy całej przestrzeninazw, wystarczy używać tych nazw które są wykorzystywane w naszym kodzie w danympliku (tutaj std::string - linia 4).

Trzecia sprawa, na którą należy zwrócić uwagę, to definicja metod. Nazwa metody MUSIbyć poprzedzona nazwą klasy (i dwoma znakami ::). Jest to wyraźne określenie do jakiejklasy metoda należy. W jednym pliku źródłowym może znajdować się wiele deklaracjimetod należących do różnych klas, co więcej, część tych metod może mieć takie samenazwy i przyjmować takie same parametry. Kompilator musi wiedzieć dokładnie dojakiej klasy dana metoda należy. W przykładach prezentowanych w poprzednich pod-rozdziałach nie było problemu z jednoznacznością przynależności metod, bo były onedefiniowane wewnątrz klasy. Znowu łatwa do zapamiętania jest analogia z naszego oto-czenia. Nie możemy zdefiniować czynności ”włącz“, możemy co najwyżej powiedzieć jakwłączyć światło, jak włączyć telewizor czy pralkę, a więc definiujemy czynność przyna-leżną do konkretnego typu urządzenia (ale nie obiektu, bo każdy obiekt tego typu włączasię tak samo).

Kolejną ważną sprawą jest przetestowanie napisanej klasy. Problem testowania i pisa-

21 Materiał dystrybuowany bezpłatnie

Page 22: Szybki wstęp do OOP na przykładzie C++

2.4 Pliki nagłówkowe 2 KLASY

nia aplikacji jest bardzo obszernym zagadnieniem, które zdecydowanie wykracza pozato opracowanie. Dla naszych celów warto stosować prostą zasadę - kompilować kodprogramu po wprowadzeniu każdego nowego elementu kodu (nowej metody, nowej funk-cjonalności w metodzie, nowych danych etc.) Nikt nie jest w stanie napisać programu odpoczątku do końca bez żadnego błędu (oczywiście istnieje silna zależność długość kodu -profesjonalizm programisty), a szukanie błędu w 100 czy 10000 linii kodu jest po prostukoszmarem. Dlatego też, zanim wykorzystamy naszą klasę przetestujmy czy nie zawieraona błędów.

g++ −c employee . cpp

Ważne jest aby dodać do g++ opcję -c. Opcja ta powoduje, że plik źródłowy jestkompilowany, ale nie następuje proces linkowania. Bez opcji -c kompilator zasygnalizujenam błąd braku funkcji main, a więc braku punktu startowego aplikacji.

Po kompilacji, w katalogu powinien znajdować się plik z rozszerzeniem .o. Na zakończe-nie wystarczy zastosować naszą klasę w jakimś konkretnym przypadku:

Listing 12:1 // p r z y k l a d . cpp2 #i nc l ude ” employee . h”3 #i nc l ude <i o s t r eam >45 us ing namespace s t d ;67 i n t main ( )8 {9

10 Employee p1 ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ;1112 cout <<p1 . getF i r s tName()<< e nd l ;13 cout <<p1 . getSecondName()<< e nd l ;14 cout <<p1 . g e t P o s i t i o n ()<< e nd l ;1516 cout <<end l<<e nd l ;17 Employee p2 ( ” Karo l ” , ” Jankowsk i ” ) ;18 cout <<p2 . getF i r s tName()<< e nd l ;19 cout <<p2 . getSecondName()<< e nd l ;20 cout <<p2 . g e t P o s i t i o n ()<< e nd l ;2122 re tu rn 0 ;23 }

Jeżeli wcześniej skompilowaliśmy klasę z opcją -c to teraz wystarczy dołączyć skompilo-wany plik (ten z rozszerzeniem .o):

g++ p r z y k l a d . cpp employee . o

lub możemy też ponownie skompilować wszystkie pliki źródłowe:g++ p r z y k l a d . cpp employee . cpp

22 Materiał dystrybuowany bezpłatnie

Page 23: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor

Bardzo często zachodzi potrzeba dynamicznej rezerwacji pamięci w trakcie tworzeniaobiektu. Mimo, iż wskaźniki nie stanowią żadnego specjalnie skomplikowanego narzę-dzia, to w praktyce okazuje się, że podczas nauki, praca z nimi nastręcza najwięcej pro-blemów. Jako przykład rozważmy klasę Tablica, która służy do przechowywania liczbtypu double. Oczywiście w praktycznych zastosowaniach pisanie takiej klasy mija się zcelem (jest STL i np. klasa vector), ale problem ten idealnie nadaje się do omówieniapracy na wskaźnikach w przypadku klas.

Zacznijmy od deklaracji klasy:

Listing 13:1 // a r r a y . h2 #i f n d e f ARRAY H3 #def ine ARRAY H45 c l a s s Array6 {7 p u b l i c :8 Array ( i n t s i z e ) ;9 ˜ Array ( ) ;

10 vo id s e tE l ement ( i n t i , double v a l ) ;11 vo id c l e a r ( ) ;12 double getE lement ( i n t i ) ;13 double getMaximum ( ) ;14 double getMinimum ( ) ;15 double g e t S i z e ( ) ;1617 p r i v a t e :18 double ∗ tab ;19 i n t s i z e ;20 } ;21 #e n d i f

Pojawiła się po raz pierwszy nowa metoda specjalna zwana destruktorem (linia 9). De-struktor to metoda, która jest wywoływana zawsze gdy obiekt danej klasy przestajeistnieć (np. po zakończeniu funkcji). Destruktor to metoda o nazwie takiej jak nazwaklasy, ale poprzedzony znakiem tyldy. Destruktor nie przyjmuje i nie zwraca żadnychargumentów. Rolą destruktora jest posprzątanie po obiekcie, co najczęściej ma formęzwolnienia dynamicznie zarezerwowanej pamięci lub zwolnienia innych zasobów (np. za-mknięcie plików). Reszta metod w powyższym kodzie jest oczywista i nie wymagatłumaczenia.

Kolejnym krokiem jest zdefiniowanie metod:

Listing 14:1 // a r r a y . cpp

23 Materiał dystrybuowany bezpłatnie

Page 24: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

2 #i nc l ude ” a r r a y . h”34 Array : : Ar ray ( i n t s i z e )5 {6 th i s−>s i z e=s i z e ;7 tab=new double [ s i z e ] ;8 c l e a r ( ) ;9 }

1011 Array : : ˜ Ar ray ( )12 {13 de le te [ ] tab ;14 }1516 vo id Array : : c l e a r ( )17 {18 f o r ( i n t i =0; i <s i z e ; i ++)19 tab [ i ] = 0 . 0 ;20 }2122 vo id Array : : s e tE l ement ( i n t i , double v a l )23 {24 i f ( i <0 | | i>=s i z e )25 throw ”Wrong Array Index ” ;26 tab [ i ]= v a l ;27 }2829 double Array : : getE lement ( i n t i )30 {31 i f ( i <0 | | i>=s i z e )32 throw ”Wrong Array Index ” ;33 re tu rn tab [ i ] ;34 }35 double Array : : getMaximum ( )36 {37 double max=tab [ 0 ] ;38 f o r ( i n t i =1; i <s i z e ; i ++)39 i f ( tab [ i ]>max)40 max=tab [ i ] ;41 re tu rn max ;42 }4344 double Array : : getMinimum ( )45 {46 double min=tab [ 0 ] ;47 f o r ( i n t i =1; i <s i z e ; i ++)48 i f ( tab [ i ]<min )49 min=tab [ i ] ;50 re tu rn min ;51 }5253 double Array : : g e t S i z e ( )54 {

24 Materiał dystrybuowany bezpłatnie

Page 25: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

55 re tu rn s i z e ;56 }

Konstruktor pobiera informacje o rozmiarze tablicy i na jej podstawie dynamicznie rezer-wuje stosowny obszar pamięci, a następnie zeruje elementy tablicy poprzez wywołaniemetody clear(). Destruktor (linia 11) zwalnia zarezerwowany w konstruktorze obszarpamięci. Jest to niezwykle ważne ponieważ w momencie zniszczenia obiektu klasy Ar-ray, informacja o tym gdzie znajduje się przydzielona dynamicznie pamięć jest tracona,zniszczenie obiektu nie powoduje jej zwolnienia, a więc mamy zarezerwowaną pamięć,do której nie mamy dostępu. Kolejną nowością pojawiającą się w kodzie programu jestwyrzucenie wyjątku. Metody naszej klasy dbają o to, aby nie odnosić się do elemen-tów spoza tablicy. Jeżeli użytkownik spróbuje odnieść się do komórki tablicy spoza jejzakresu, program wyrzuci wyjątek i przestanie działać (linia 25 i 32).

Przykład :

Listing 15:1 // t a b l i c a . cpp2 #i nc l ude <i o s t r eam >3 #i nc l ude ” a r r a y . h”45 us ing namespace s t d ;67 main ( )8 {9 Array t a b l i c a ( 1 0 ) ;

10 f o r ( i n t i =0; i <10; i ++)11 t a b l i c a . s e tE l ement ( i , i ) ;1213 f o r ( i n t i =0; i <12; i ++)14 cout << t a b l i c a . getE lement ( i )<<e nd l ;1516 re tu rn 0 ;17 }

kompilacja:g++ a r r a y . cpp t a b l i c a . cpp

wynik programu:012345678

25 Materiał dystrybuowany bezpłatnie

Page 26: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

9t e r m i n a t e c a l l e d a f t e r th row ing an i n s t a n c e o f ’ cha r con s t ∗ ’Aborted

Pętla wyświetlająca elementy tablicy przekracza jej zakres i program przestaje działaćw wyniku wyrzucenia i nieobsłużenia wyjątku.

Przy omawianiu konstruktorów wspomnieliśmy, że jeżeli sami nie zdefiniujemy żadnegokonstruktora to automatycznie są tworzone dwa konstruktory - bezparametrowy i ko-piujący. Jeżeli deklarujemy jakikolwiek konstruktor (za wyjątkiem kopiującego), to do-myślnie tworzony jest tylko konstruktor kopiujący. Rolą konstruktora kopiującego jestutworzenie obiektu klasy, na podstawie innego obiektu tej samej klasy. Domyślnie jestto realizowane poprzez kopiowanie wartości pól. Skoro kopiowana jest wartość pól, tow przypadku gdy pole jest wskaźnikiem, kopiowany jest adres przechowywany w zmien-nej a nie wartość na jaką on wskazuje. W praktyce oznacza to, że dwa obiekty będąwskazywać na ten sam obszar pamięci.

Listing 16:1 #i nc l ude <i o s t r eam >2 #i nc l ude ” a r r a y . h”34 us ing namespace s t d ;56 main ( )7 {8 Array t a b l i c a ( 1 0 ) ;9 f o r ( i n t i =0; i <10; i ++)

10 t a b l i c a . s e tE l ement ( i , i ) ;1112 // t2 b e d z i e kop i a t a b l i c a13 Array t2 ( t a b l i c a ) ;1415 t a b l i c a . s e tE l ement ( 0 , 1 0 0 ) ;16 cout <<t2 . getE lement (0)<< e nd l ;171819 re tu rn 0 ;20 }

Zmiany w obiekcie tablica będą widoczne również w obiekcie t2. Jednak to nie koniecproblemów. Wynik działania tego programu będzie wyglądał mniej więcej tak:100∗∗∗ g l i b c d e t e c t e d ∗∗∗ . / a . out : doub l e f r e e or c o r r u p t i o n ( top ) : 0 x09b6b008 ∗∗∗======= Backt race : =========/ l i b / t l s / i 686 /cmov/ l i b c . so . 6 [ 0 x17b0d1 ]/ l i b / t l s / i 686 /cmov/ l i b c . so . 6 [ 0 x17c7d2 ]/ l i b / t l s / i 686 /cmov/ l i b c . so . 6 ( c f r e e +0x6d ) [ 0 x17f8ad ]/ u s r / l i b / l i b s t d c ++.so . 6 ( ZdlPv+0x21 ) [ 0 xab86f1 ]/ u s r / l i b / l i b s t d c ++.so . 6 ( ZdaPv+0x1d ) [ 0 xab874d ]

26 Materiał dystrybuowany bezpłatnie

Page 27: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

. / a . out [ 0 x8048a72 ]

. / a . out [ 0 x8048951 ]/ l i b / t l s / i 686 /cmov/ l i b c . so . 6 ( l i b c s t a r t m a i n +0xe6 ) [ 0 x126b56 ]. / a . out [ 0 x80487d1 ]

======= Memory map : ========00110000−0024 e000 r−xp 00000000 08 :03 553359 / l i b / t l s / i 686 /cmov/ l i b c −2 .10 . 1 . so0024 e000−0024 f000 −−−p 0013 e000 08 :03 553359 / l i b / t l s / i 686 /cmov/ l i b c −2 .10 . 1 . so0024 f000 −00251000 r−−p 0013 e000 08 :03 553359 / l i b / t l s / i 686 /cmov/ l i b c −2 .10 . 1 . so00251000−00252000 rw−p 00140000 08 :03 553359 / l i b / t l s / i 686 /cmov/ l i b c −2 .10 . 1 . so00252000−00255000 rw−p 00000000 00 :00 000309000−0032 d000 r−xp 00000000 08 :03 551417 / l i b / t l s / i 686 /cmov/ l ibm −2 .10 . 1 . so0032 d000−0032e000 r−−p 00023000 08 :03 551417 / l i b / t l s / i 686 /cmov/ l ibm −2 .10 . 1 . so0032 e000−0032 f000 rw−p 00024000 08 :03 551417 / l i b / t l s / i 686 /cmov/ l ibm −2 .10 . 1 . so00539000−0053 a000 r−xp 00000000 00 :00 0 [ vdso ]00 a00000−00ae6000 r−xp 00000000 08 :03 852161 / u s r / l i b / l i b s t d c ++.so . 6 . 0 . 1 300 ae6000−00aea000 r−−p 000 e6000 08 :03 852161 / u s r / l i b / l i b s t d c ++.so . 6 . 0 . 1 300 aea000−00aeb000 rw−p 000 ea000 08 :03 852161 / u s r / l i b / l i b s t d c ++.so . 6 . 0 . 1 300 aeb000−00af2000 rw−p 00000000 00 :00 000 c58000−00c73000 r−xp 00000000 08 :03 524539 / l i b / ld −2 .10 . 1 . so00 c73000−00c74000 r−−p 0001 a000 08 :03 524539 / l i b / ld −2 .10 . 1 . so00 c74000−00c75000 rw−p 0001 b000 08 :03 524539 / l i b / ld −2 .10 . 1 . so00 de4000−00e00000 r−xp 00000000 08 :03 524345 / l i b / l i b g c c s . so . 100 e00000−00e01000 r−−p 0001 b000 08 :03 524345 / l i b / l i b g c c s . so . 100 e01000−00e02000 rw−p 0001 c000 08 :03 524345 / l i b / l i b g c c s . so . 108048000−08049000 r−xp 00000000 08 :06 7446599 /home/ u s e r 1 /a . out08049000−0804 a000 r−−p 00001000 08 :06 7446599 /home/ us e r 1 /a . out0804 a000−0804b000 rw−p 00002000 08 :06 7446599 /home/ us e r 1 /a . out09 b6b000−09b8c000 rw−p 00000000 00 :00 0 [ heap ]b7600000−b7621000 rw−p 00000000 00 :00 0b7621000−b7700000 −−−p 00000000 00 :00 0b7767000−b7769000 rw−p 00000000 00 :00 0b7784000−b7787000 rw−p 00000000 00 :00 0bfe90000−bfea5000 rw−p 00000000 00 :00 0 [ s t a c k ]Aborted

Program wykonał się poprawnie (wypisało na ekranie liczbę 100), jednak na samymkońcu programu wystąpił jakiś błąd związany z pamięcią. Koniec programu oznaczazniszczenie zmiennych występujących w funkcji main. W przypadku naszego programupowinno nastąpić zniszczenie dwóch obiektów klasy Array - obiektu tablica i t2. Zanimjednak obiekty przestaną istnieć, nastąpi wywołanie destruktora na rzecz każdego z tychobiektów. Destruktor zwalania przydzieloną pamięć na podstawie adresu, który jestprzechowywany w zmiennej tab. I tutaj właśnie tkwi problem. Domyślny konstruktorkopiujący w obiekcie t2 ustawił pole tab na ten sam obszar pamięci co pole tab w obiekcietablica. Znajduje to swoje potwierdzenie w wypisaniu elementów t2 po ich zmianiew obiekcie tablica. A więc w momencie, kiedy wywołany został destruktor obiektutablica, zarezerwowana pamięć została zwolniona. Następnie, likwidowany jest obiektt2 i uruchamiany jego destruktor. Tym razem destruktor działający w obiekcie t2 chcezwolnić pamięć wskazywaną przez wskaźnik tab, ale jest to ta sama pamięć, która zostałazwolniona w destruktorze obiektu tablica. Nie można zwolnić niezarezerwowanej pamięcii właśnie to jest powodem pojawiającego się błędu.

27 Materiał dystrybuowany bezpłatnie

Page 28: Szybki wstęp do OOP na przykładzie C++

2.5 Pamięć dynamiczna - konstruktor kopiujący i destruktor 2 KLASY

Rozwiązanie opisanego problemu jest jedno - jeżeli w klasie korzystamy z wskaźników,ZAWSZE sami piszemy konstruktor kopiujący. Zmodyfikujmy naszą klasę dodając kon-struktor kopiujący (pamiętajmy, że zmiany dokonać trzeba zarówno w pliku array.h jaki array.cpp):

Listing 17:1 Array : : Ar ray ( Ar ray &a r r a y )2 {3 s i z e=a r r a y . g e t S i z e ( ) ;4 tab=new double [ s i z e ] ;5 f o r ( i n t i =0; i <s i z e ; i ++)6 tab [ i ]= a r r a y . getE lement ( i ) ;7 }

Ponowne uruchomienie naszego programu da na ekranie wartość 0, czyli faktycznie mamykopię obiektu, a nie kopię wskaźnika.

Z konstruktorem kopiującym, a właściwie z każdą metodą, która pobiera referencję dojakiegoś obiektu, wiąże się kwestia zapewnienia braku zmian w obiekcie. Kiedy two-rzymy kopię obiektu chcemy mieć pewność, że obiekt ”wzorcowy“ nie ulegnie podczastej operacji zmianie. Pierwszym rozwiązaniem jest przesyłanie do metod obiektów po-przez wartość (kopie),jednak sposób ten jest zarówno czasochłonny jaki i niepotrzebniezużywa pamięć. Drugi sposób to przesłanie obiektu przez referencję i użycie słowa klu-czowego const zapewniającego, że referencja dotyczy stałego obiektu, a więc obiektu wktórym nic zmienić nie możemy(pamiętajmy, że zmianę trzeba wprowadzić i w deklaracji,i w definicji).Array : : Ar ray ( const Array &a r r a y )

Jednak tym razem kompilacja się nie powiedzie:a r r a y . cpp : I n copy c o n s t r u c t o r Ar ray : : Ar ray ( con s t Ar ray &):a r r a y . cpp : 1 3 : e r r o r : p a s s i n g con s t Ar ray as t h i s argumento f doub l e Ar ray : : g e t S i z e ( ) d i s c a r d s q u a l i f i e r sa r r a y . cpp : 1 6 : e r r o r : p a s s i n g con s t Ar ray as t h i s argumento f doub l e Ar ray : : getE lement ( i n t ) d i s c a r d s q u a l i f i e r s

Problem tkwi w metodach getSize() i getElement(int) wywoływanych na rzecz obiektuprzekazanego do konstruktora. W deklaracji metody wyraźnie wskazaliśmy, że obiektprzekazany do konstruktora ma pozostać niezmienny, jednak w samym konstruktorzewywołujemy na rzecz tego obiektu metody, a te mogą zmienić stan tego obiektu (choćbytylko hipotetycznie), a więc niejako próbujemy oszukiwać. W tym wypadku musimywyraźne wskazać, że wymienione metody nie zmieniają stanu obiektu na rzecz któregosą wywoływane. Wskazanie takie odbywa się poprzez użycie słowa kluczowego constzaraz za nazwą Metody (i znowu pamiętajmy o pliku .h i .cpp)double getE lement ( i n t i ) const ;double g e t S i z e ( ) const ;

28 Materiał dystrybuowany bezpłatnie

Page 29: Szybki wstęp do OOP na przykładzie C++

3 DZIEDZICZENIE

3 Dziedziczenie

Dziedziczenie to kolejne fundamentalne pojęcie w programowaniu obiektowym. Tech-nika ta, tak jak i pozostałe koncepcje programowania obiektowego wynika bezpośrednioz analizy tego jak postrzegamy otaczający nas świat. Aby zrozumieć ideę dziedziczenia,dla przykładu rozważmy obiekty jakimi są samochody. Od razu widzimy, że samochodysą różne i to nie tylko pod względem wyglądu i cech, ale również pod względem funkcjo-nalności - jedne mają abs inne nie, jedne mają klimatyzację inne nie. Niejako intuicyjniewiemy, że wszystkie auta należą do jednego typu bazowego, który określa zespół takichsamych zachowań i cech. Każdy samochód ma kierownicę, każdy ma silnik, każdy maskrzynie biegów. Podobną analizę można wykonać praktycznie dla każdych otaczającychnas przedmiotów.

Przekładając obserwacje na programowanie powiemy, że możemy wyróżnić klasę bazowąz której innej klasy dziedziczą. A więc klasy dziedziczące (potomne) mają wszystkie ce-chy klasy bazowej, ale dodatkowo mogą definiować swoje pola, swoje metody jak równieżmodyfikować zachowania odziedziczone z klasy bazowej. Taki podział i rozplanowanieklas jest właśnie jednym z fundamentalnych zagadnień przy projektowaniu aplikacji. Zzespołu klas należy wyróżnić fundamentalne klasy bazowe oraz klasy pochodne dziedzi-czące z tak wyróżnionych klas.

Przypomnijmy sobie klasę Employee:

Listing 18:1 // employee . h23 #i f n d e f EMPLOYEE H4 #def ine EMPLOYEE H56 #i nc l ude <s t r i n g >789 c l a s s Employee

10 {11 // p u b l i c i n t e r f a c e12 p u b l i c :13 Employee ( ) {} ;14 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,15 s t d : : s t r i n g p o s i t i o n ) ;16 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ) ;1718 s t d : : s t r i n g getF i r s tName ( ) ;19 s t d : : s t r i n g getSecondName ( ) ;20 s t d : : s t r i n g g e t P o s i t i o n ( ) ;2122 vo id s e t P o s i t i o n ( s t d : : s t r i n g p o s i t i o n ) ;2324 // p r i v a t e data

29 Materiał dystrybuowany bezpłatnie

Page 30: Szybki wstęp do OOP na przykładzie C++

3 DZIEDZICZENIE

25 p r i v a t e :26 s t d : : s t r i n g f i r s tName , secondName , p o s i t i o n ;2728 } ;29 #e n d i f

Klasa ta reprezentuje zwykłego pracownika. Jednak od razu widzimy, że pracownicyw firmie są różni. Są zwykli pracownicy, są szefowie działów, są kierownicy. Każdyz nich jest pracownikiem, każdy z nich ma imię, nazwisko oraz stanowisko w pracy,jednak są między nimi również duże różnice np. w wynagrodzeniach, w nagrodach czyilości podwładnych. Pierwszym etapem rozwoju programu będzie stworzenie nowej klasyManager dziedziczącej z klasy Employee i rozszerzającej jej funkcjonalność. Dodatkowododamy do klasy Employee metodę info() zwracającą string z opisem danego obiektu.

Najpierw w pliku nagłówkowym employee.h dodajemy w części publicznej deklaracjęmetody info:s t d : : s t r i n g i n f o ( ) ;

Kod metody dodajemy do pliku employee.cpp

Listing 19:1 // employee . cpp2 #i nc l ude ” employee . h”34 us ing s t d : : s t r i n g ;56 Employee : : Employee ( s t r i n g f i r s tName , s t r i n g secondName , s t r i n g p o s i t i o n )7 {8 th i s−>f i r s tName=f i r s tName ;9 th i s−>secondName=secondName ;

10 th i s−>p o s i t i o n=p o s i t i o n ;11 }1213 Employee : : Employee ( s t r i n g f i r s tName , s t r i n g secondName )14 {15 th i s−>f i r s tName=f i r s tName ;16 th i s−>secondName=secondName ;17 p o s i t i o n=” brak p r z y d z i a l u ” ;18 }1920 s t r i n g Employee : : ge tF i r s tName ( )21 {22 re tu rn f i r s tName ;23 }2425 s t r i n g Employee : : getSecondName ( )26 {27 re tu rn secondName ;28 }29

30 Materiał dystrybuowany bezpłatnie

Page 31: Szybki wstęp do OOP na przykładzie C++

3 DZIEDZICZENIE

30 s t r i n g Employee : : g e t P o s i t i o n ( )31 {32 re tu rn p o s i t i o n ;33 }3435 vo id Employee : : s e t P o s i t i o n ( s t r i n g p o s i t i o n )36 {37 th i s−>p o s i t i o n=p o s i t i o n ;38 }3940 s t r i n g Employee : : i n f o ( )41 {42 s t r i n g tmp ;43 tmp=” Im ie : ”+f i r s tName+”\n”+” Nazwisko : ”+secondName+”\n”44 +” Stanowisko : ”+p o s i t i o n ;45 re tu rn tmp ;46 }

Następny krok to stworzenie klasy Manager jako klasy dziedziczącej z klasy Employee irozszerzającej tą klasę o pole employees. Pole to reprezentować będzie liczbę pracowni-ków podległych danemu kierownikowi..

Listing 20:1 // manager . h2 #i f n d e f MANAGER H3 #def ine MANAGER H45 #i nc l ude ” employee . h”67 c l a s s Manager : p u b l i c Employee8 {9 p u b l i c :

10 Manager ( s t d : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,11 s t d : : s t r i n g p o s i t i o n , i n t employees ) ;1213 p r i v a t e :14 i n t employees ;15 } ;16 #e n d i f

W linii 7 deklarujemy klasę Manager jako klasę dziedziczącą z klasy Employee. Dziedzi-czenie oznaczone jest znakiem ”:“. Przy dziedziczeniu klasa potomna (w tym wypadkuManager) określa jak dziedziczy publiczne składniki klasy bazowej. W naszym wy-padku słowo kluczowe public poprzedzające nazwę klasy Employee mówi, że wszystkiepubliczne składniki klasy Employee mają być publiczne w klasie Manager, a więc bę-dzie do nich dostęp poza obszarem klasy Manager. Oznacza to, że można wykonać naobiekcie klasy Manager publiczne metody z klasy Employee takie jak np. info() czygetFirstName().

31 Materiał dystrybuowany bezpłatnie

Page 32: Szybki wstęp do OOP na przykładzie C++

3.1 Konstruktor, destruktor i wywołanie metod klasy bazowej 3 DZIEDZICZENIE

3.1 Konstruktor, destruktor i wywołanie metod klasy bazowej

W momencie tworzenia obiektu danej klasy, najpierw wykonywany jest jeden z jej kon-struktorów. W przypadku klas dziedziczących wykonane muszą zostać dwa konstruktory- ten z klasy potomnej i ten z klasy bazowej. Zawsze jako pierwszy jest wykonywany kon-struktor z klasy bazowej, a dopiero potem konstruktor z klasy potomnej. Jeżeli chcemywywołać w klasie potomnej konstruktor klasy bazowej inny niż bezparametrowy (bo tenjest uruchamiany domyślnie) to po znaku ”:“ wywołujemy konstruktor klasy bazowej zstosownymi parametrami (tzw. lista inicjalizacyjna).

Listing 21:1 // manager . cpp2 #i nc l ude ” manager . h”34 us ing s t d : : s t r i n g ;56 Manager : : Manager ( s t r i n g f i r s tName , s t r i n g secondName ,7 s t r i n g p o s i t i o n , i n t employees )8 : Employee ( f i r s tName , secondName , p o s i t i o n )9 // l i s t a i n i c j a l i z a c y j n a

10 {11 th i s−>employees=employees ;12 }

W naszym przykładzie lista inicjalizacyjna zawiera wywołanie konstruktora klasy Em-ployee z trzema obiektami klasy string - firstName, secondName i position. Obiekty tesą parametrami pobieranymi przez konstruktor klasy Manager. Sam konstruktor klasyManager ustawia tylko pole employees, pole które zostało dodane w klasie Manager.

Przetestujmy obie klasy

Listing 22:1 // t e s t . cpp2 #i nc l ude ” employee . h”3 #i nc l ude ” manager . h”4 #i nc l ude <i o s t r eam >56 us ing namespace s t d ;78 main ( )9 {

1011 Employee p1 ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ;1213 cout <<p1 . i n f o ()<< e nd l ;1415 Manager m1( ” Karo l ” , ” Jankowsk i ” , ” s t a r s z y p r o g r a m i s t a ” , 1 0 ) ;16 cout <<m1. i n f o ()<< e nd l ;17 }

32 Materiał dystrybuowany bezpłatnie

Page 33: Szybki wstęp do OOP na przykładzie C++

3.1 Konstruktor, destruktor i wywołanie metod klasy bazowej 3 DZIEDZICZENIE

Po uruchomieniu na ekranie zostaną wyświetlone informacje o pracownikach:Im i e : JanNazwisko : Kowa l sk iStanowisko : p r o g r a m i s t aIm i e : Karo lNazwisko : Jankowsk iStanowisko : s t a r s z y p r o g r a m i s t a

Metoda info() jest zdeklarowana w klasie Employee, a więc nie wyświetla dodatkowychdanych dostępnych w klasie Manager. Kolejnym krokiem będzie przedefiniowanie me-tody info() w klasie Manager. W tym celu po pierwsze w sekcji metod publicznych wpliku nagłówkowym manager.h dodajemy deklarację metody info():s t d : : s t r i n g i n f o ( ) ;

Po drugie modyfikujemy plik źródłowy manager.cpp:

Listing 23:1 #i nc l ude ” manager . h”2 #i nc l ude <ss t ream>34 us ing s t d : : s t r i n g ;5 us ing s t d : : o s t r i n g s t r e a m ;67 Manager : : Manager ( s t r i n g f i r s tName , s t r i n g secondName , s t r i n g p o s i t i o n ,8 i n t employees )9 : Employee ( f i r s tName , secondName , p o s i t i o n )

10 {11 th i s−>employees=employees ;12 }1314 s t r i n g Manager : : i n f o ( )15 {16 o s t r i n g s t r e a m b u f f o r ;17 b u f f o r <<Employee : : i n f o ()<<”\nPodwladni : ”<<employees ;18 re tu rn b u f f o r . s t r ( ) ;19 }

W linii 2 dołączyliśmy plik nagłówkowy z biblioteką umożliwiającą wykonywanie ope-racji strumieniowych na stringach. Metoda info w klasie Employee pracowała tylko naobiektach klasy string. Klasa string przedefiniowuje operator dodawania, a więc łącze-nie stringów nie nastręczało żadnych problemów. W przypadku metody info w klasieManager do stringów musimy dołączyć liczbę typu int, a więc nie możemy po prostuużyć operatora dodawania. Konwersji liczby na reprezentujący ją ciąg znaków możnadokonać na dwa sposoby - albo wykorzystując funkcję sprintf z języka C, albo właśniepoprzez operacje strumieniowe na stringach. Skoro niniejsze opracowanie dotyczy pro-gramowania obiektowego to oczywisty jest wybór drugiej opcji. Klasa ostringstream(linia 14) reprezentuje formatowany, wyjściowy strumień związany z stringiem. Praca z

33 Materiał dystrybuowany bezpłatnie

Page 34: Szybki wstęp do OOP na przykładzie C++

3.1 Konstruktor, destruktor i wywołanie metod klasy bazowej 3 DZIEDZICZENIE

obiektem tej klasy właściwie nie różni się niczym od pracy z obiektem cout czy obiek-tami plikowym (linia 15). Aby otrzymać zapisany string wystarczy wywołać metodęstr() na rzecz obiektu klasy ostringstream (linia 16). Ostatnim elementem wymagają-cym omówienia jest odwołanie się do metod z klasy bazowej. Dopóki klasa bazowa iklasa potomna nie mają metod o tej samej nazwie i tych samych parametrach nie maproblemu. Odwołanie do metody jest jednoznaczne i wiadomo o którą metodę chodzi.Klasa Manager definiuje swoją metodę info(), która przesłania metodę info() z klasybazowej. Mino, iż klasa Manager definiuje swoją metodę info(), to częścią jej działaniajest pobranie wyniku wywołania metody info() z klasy Employee, a więc musimy jakośokreślić, o którą metodę info() nam chodzi.. Sposób dostępu do przesłoniętych metodklasy bazowej przedstawiony jest w linii 15.

Kompilacja i uruchomienie programu tym razem da nam poprawny wynik:Im i e : JanNazwisko : Kowa l sk iStanowisko : p r o g r a m i s t aIm i e : Karo lNazwisko : Jankowsk iStanowisko : s t a r s z y p r o g r a m i s t aPodwladni : 10

Nasza klasa nie pracuje na wskaźnikach, ani nie wykorzystuje żadnych dodatkowychzasobów (pliki, sockety itp), a więc nie musieliśmy tworzyć do tej pory własnego de-struktora. Jednak czasami konieczne jest stworzenie konstruktora ze względu na spe-cyfikę klasy i wtedy powstaje to samo pytanie co w przypadku konstruktora - co jestwywoływane najpierw - destruktor z klasy bazowej czy z klasy potomnej. Po chwilizastanowienia odpowiedź jest oczywista - destruktory będą wywoływane w odwrotnejkolejności, a więc najpierw ten z kasy potomnej, a potem z bazowej. Najlepiej zoba-czyć kolejność wywoływania na konkretnym przykładzie. Załóżmy że mam dwie klasy -klasę Basic jako klasę podstawową i klasę Advance jako klasę dziedziczącą. Klasy pozawypisywaniem informacji na ekranie nie robią nic więcej.

Listing 24:1 #i nc l ude <i o s t r eam >23 us ing namespace s t d ;45 c l a s s Bas i c6 {7 p u b l i c :8 Bas i c ( )9 {

10 cout <<” Kons t ruk to r k l a s y Bas i c ”<<e nd l ;11 }1213 ˜ Bas i c ( )14 {15 cout <<” D e s t r u k t o r k l a s y Bas i c ”<<e nd l ;

34 Materiał dystrybuowany bezpłatnie

Page 35: Szybki wstęp do OOP na przykładzie C++

3.2 Dziedziczenie i enkapsulacja 3 DZIEDZICZENIE

16 }17 } ;1819 c l a s s Advance : p u b l i c Bas i c20 {21 p u b l i c :22 Advance ( )23 {24 cout <<” Kons t ruk to r k l a s y Advance ”<<e nd l ;25 }2627 ˜Advance ( )28 {29 cout <<” D e s t r u k t o r k l a s y Advance ”<<en d l ;30 }3132 } ;3334 main ( )35 {36 Advance a ;3738 re tu rn 0 ;39 }

Wynik:Kons t ruk to r k l a s y Bas i cKons t ruk to r k l a s y AdvanceD e s t r u k t o r k l a s y AdvanceD e s t r u k t o r k l a s y Bas i c

3.2 Dziedziczenie i enkapsulacja

Do tej pory korzystaliśmy z dwóch modyfikatorów dostępu do składowych klasy - publici private. Zmienne i metody prywatne są dostępne tylko z wnętrza ich klasy. Nawetklasa pochodna nie ma do nich dostępu. Zmienne publiczne są dostępne poza obszaremklasy. W przypadku dziedziczenia klasa pochodna decyduje jak zmienne publiczne klasypodstawowej będą widoczne w klasie pochodnej, natomiast klasa pochodna nie może wżaden sposób decydować o zmiennych prywatnych klasy podstawowej (bo są przecież oneprywatne). Rozszerzmy przykład klas Basic i Advance o dodanie prywatnej i publicznejzmiennej w klasie Basic (linia 16 i 19). Dodatkowo niech klasa Advance odziedziczypubliczne składniki klasy Basic jako prywatne (linia 22):

Listing 25:1 #i nc l ude <i o s t r eam >23 us ing namespace s t d ;

35 Materiał dystrybuowany bezpłatnie

Page 36: Szybki wstęp do OOP na przykładzie C++

3.2 Dziedziczenie i enkapsulacja 3 DZIEDZICZENIE

45 c l a s s Bas i c6 {7 p u b l i c :8 Bas i c ( )9 {

10 cout <<” Kons t ruk to r k l a s y Bas i c ”<<e nd l ;11 p u b l i c a =10;12 p r i v a t e b =100;13 }1415 ˜ Bas i c ( )16 {17 cout <<” D e s t r u k t o r k l a s y Bas i c ”<<e nd l ;18 }1920 i n t p u b l i c a ;2122 p r i v a t e :23 i n t p r i v a t e b ;24 } ;2526 c l a s s Advance : p r i v a t e Bas i c27 {28 p u b l i c :29 Advance ( )30 {31 cout <<” Kons t ruk to r k l a s y Advance ”<<e nd l ;32 }3334 ˜Advance ( )35 {36 cout <<” D e s t r u k t o r k l a s y Advance ”<<en d l ;37 }3839 vo id i n f o ( )40 {41 cout <<p u b l i c a <<” ”<<p r i v a t e b <<en d l ;42 }4344 } ;4546 main ( )47 {48 Advance a ;4950 cout <<a . p u b l i c a <<e nd l ;51 re tu rn 0 ;52 }

Próba kompilacji zakończy się błędem:d z i e d z i c z e n i e . cpp : I n member f u n c t i o n vo i d Advance : : i n f o ( ) :d z i e d z i c z e n i e . cpp : 2 3 : e r r o r : i n t Bas i c : : p r i v a t e b i s p r i v a t e

36 Materiał dystrybuowany bezpłatnie

Page 37: Szybki wstęp do OOP na przykładzie C++

3.2 Dziedziczenie i enkapsulacja 3 DZIEDZICZENIE

d z i e d z i c z e n i e . cpp : 4 1 : e r r o r : w i t h i n t h i s c o n t e x td z i e d z i c z e n i e . cpp : I n f u n c t i o n i n t main ( ) :d z i e d z i c z e n i e . cpp : 2 0 : e r r o r : i n t Bas i c : : p u b l i c a i s i n a c c e s s i b l ed z i e d z i c z e n i e . cpp : 5 0 : e r r o r : w i t h i n t h i s c o n t e x t

Błędy są dwa. Po pierwsze, w linii 41 odwołujemy się w klasie potomnej do prywatnegoskładnika klasy bazowej co oczywiście jest niedozwolone. Po drugie, klasa Advancedzdeklarowała, że publiczne składowe klasy Basic zostaną odziedziczone jako składoweprywatne, a więc nie będzie do nich dostępu z zewnątrz za pośrednictwem obiektówklasy Advance (linia 26). W głównej części programu (linia 50) próbujemy odwołać siędo pola public a, ale pole to w klasie Advance jest prywatne mimo, iż w klasie bazowejpole było polem publicznym.

Brak dostępu do prywatnych składowych klasy bazowej z poziomu klasy dziedziczącejczasami jest kłopotliwy. Oczywiście zawsze można w klasie bazowej zdefiniować pu-bliczne metody dostępowe i z nich korzystać w klasie potomnej. Jednak co w sytuacjikiedy chcemy ukryć dane przed ”światem zewnętrznym“, ale z drugiej strony dać do nichbezpośredni dostęp w klasach dziedziczących ? Właśnie w takich sytuacjach używa się wklasie bazowej modyfikatora dostępu protected:. Modyfikator ten powoduje, że skła-dowe są na zewnątrz traktowane jak składowe prywatne, ale w klasach dziedziczącychsą one dostępne bezpośrednio (tak jakby były publiczne). Poprawmy nasz program wdwóch miejscach - po pierwsze niech klasa potomna dziedziczy składowe publiczne jakopubliczne (linia 26), po drugie zmienną private b poprzedźmy modyfikatorem protected(linia 22).

Listing 26:1 #i nc l ude <i o s t r eam >23 us ing namespace s t d ;45 c l a s s Bas i c6 {7 p u b l i c :8 Bas i c ( )9 {

10 cout <<” Kons t ruk to r k l a s y Bas i c ”<<e nd l ;11 p u b l i c a =10;12 p r i v a t e b =100;13 }1415 ˜ Bas i c ( )16 {17 cout <<” D e s t r u k t o r k l a s y Bas i c ”<<e nd l ;18 }1920 i n t p u b l i c a ;2122 protected :23 i n t p r i v a t e b ;

37 Materiał dystrybuowany bezpłatnie

Page 38: Szybki wstęp do OOP na przykładzie C++

4 POLIMORFIZM

24 } ;2526 c l a s s Advance : p u b l i c Bas i c27 {28 p u b l i c :29 Advance ( )30 {31 cout <<” Kons t ruk to r k l a s y Advance ”<<e nd l ;32 }3334 ˜Advance ( )35 {36 cout <<” D e s t r u k t o r k l a s y Advance ”<<en d l ;37 }3839 vo id i n f o ( )40 {41 cout <<p u b l i c a <<” ”<<p r i v a t e b <<en d l ;42 }4344 } ;4546 main ( )47 {48 Advance a ;4950 cout <<a . p u b l i c a <<e nd l ;51 re tu rn 0 ;52 }

Teraz program skompiluje się bez problemu.

4 Polimorfizm

Z mechanizmem dziedziczenia nierozerwalnie związane jest pojęcie polimorfizmu. Po-limorfizm to kolejny fundament programowania obiektowego, mechanizm pozwalającyna wyabstrahowanie zespołu zachowań (metod) dla klas posiadających wspólne klasybazowe. Mimo, iż brzmi to bardzo formalnie to sprawa jest dość prosta. Wróćmy doprzykładu z klasami Employee i Manager. Język C++ jest językiem, w którym musimyokreślać typ danych. Jeżeli będziemy próbować przypisać dane jednego typu do zmien-nej innego typu, to albo nastąpi konwersja (o ile jest możliwa), albo zgłoszony zostaniebłąd w trakcie kompilacji. Jednak w przypadku dziedziczenia sprawa wgląda nieco bar-dziej skomplikowanie (ale tylko pozornie) Wykorzystajmy klasy Employee i Manager wnastępującym programie:

Listing 27:1 #i nc l ude ” employee . h”

38 Materiał dystrybuowany bezpłatnie

Page 39: Szybki wstęp do OOP na przykładzie C++

4 POLIMORFIZM

2 #i nc l ude ” manager . h”3 #i nc l ude <i o s t r eam >45 us ing namespace s t d ;67 main ( )8 {9

10 Employee tab [ ]={ Employee ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ,11 Manager ( ” Karo l ” , ” Jankowsk i ” , ” s t a r s z y p r o g r a m i s t a ” , 10 ) ,12 Employee ( ” K a r o l i n a ” , ” Wisn iewska ” , ” g r a f i k ” ) } ;1314 f o r ( i n t i =0; i <3; i ++)15 cout <<tab [ i ] . i n f o ()<<”\n\n\n” ;1617 re tu rn 0 ;18 }

W linii 10 mamy deklarację, że tablica tab jest tablicą do przechowywania obiektówklasy Employee, ale zapisujemy do niej obiekty zarówno klasy Employee jak i klasydziedziczącej - Manager. Uruchomienie programu da nam następujący wynik:Im i e : JanNazwisko : Kowa l sk iStanowisko : p r o g r a m i s t a

Im i e : Karo lNazwisko : Jankowsk iStanowisko : s t a r s z y p r o g r a m i s t a

Im i e : K a r o l i n aNazwisko : Wisn iewskaStanowisko : g r a f i k

Wynik działania programu jest dość oczywisty. Obiekt klasy Manager został zrzutowany(zawężony) na typ reprezentowany przez klasę Employee, a więc metoda info() jest me-todą z klasy Employee a nie Manager. Takie rzutowanie jest w tym wypadku zupełniedomyślnym rzutowaniem - klasa Manager zawiera wszystko to co klasa Employee plusdodatkowe elementy, które w rzutowaniu zostają ”pominięte“. Z tego powodu nie możnatablicy zdeklarować jako tablicy obiektów klasy Manager, bo nie można zrzutować obiek-tów z klasy Employee na klasę Manager - nie ma przepisu jak np. dodać i ustawić poleemployees. Taka modyfikacja:

Manager tab [ ]={ Employee ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ,Manager ( ” Karo l ” , ” Jankowsk i ” , ” s t a r s z y p r o g r a m i s t a ” , 10 ) ,Employee ( ” K a r o l i n a ” , ” Wisn iewska ” , ” g r a f i k ” ) } ;

spowoduje błąd w trakcie kompilacji:

39 Materiał dystrybuowany bezpłatnie

Page 40: Szybki wstęp do OOP na przykładzie C++

4 POLIMORFIZM

p r z y k l a d . cpp : I n f u n c t i o n i n t main ( ) :p r z y k l a d . cpp : 1 2 : e r r o r : c o n v e r s i o n from Employee to non−s c a l a rtype Manager r e q u e s t e dp r z y k l a d . cpp : 1 2 : e r r o r : c o n v e r s i o n from Employee to non−s c a l a rtype Manager r e q u e s t e d

Jak do tej pory o żadnym polimorfiźmie nie ma mowy i nic specjalnego czy rewolucyjnegosię nie stało. Wprowadźmy do programu pierwszą małą zmianę - zdeklarujmy, że tablicanie będzie tablicą obiektów, a tablicą wskaźników do obiektów i sprawdźmy działanieprogramu.

Listing 28:1 #i nc l ude ” employee . h”2 #i nc l ude ” manager . h”3 #i nc l ude <i o s t r eam >45 us ing namespace s t d ;67 main ( )8 {9

10 Employee ∗ tab [ ]={new Employee ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ,11 new Manager ( ” Karo l ” , ” Jankowsk i ” , ” s t a r s z y p r o g r a m i s t a ” , 10 ) ,12 new Employee ( ” K a r o l i n a ” , ” Wisn iewska ” , ” g r a f i k ” ) } ;1314 f o r ( i n t i =0; i <3; i ++)15 cout <<tab [ i ]−> i n f o ()<<”\n\n\n” ;1617 // z w o l n i e n i e pamiec i18 f o r ( i n t i =0; i <3; i ++)19 de le te tab [ i ] ;2021 re tu rn 0 ;22 }

Oprócz zmiany w deklaracji tablicy, drugą zmianą konieczną do wprowadzenia było zwol-nienie dynamicznie zarezerwowanej pamięci dla obiektów(linie 18-19). Zmiany wprowa-dzone do programu na razie nie wpłynęły na uzyskiwany wynik:Im i e : JanNazwisko : Kowa l sk iStanowisko : p r o g r a m i s t a

Im i e : Karo lNazwisko : Jankowsk iStanowisko : s t a r s z y p r o g r a m i s t a

Im i e : K a r o l i n aNazwisko : Wisn iewskaStanowisko : g r a f i k

40 Materiał dystrybuowany bezpłatnie

Page 41: Szybki wstęp do OOP na przykładzie C++

4 POLIMORFIZM

Mimo, iż w zapisie zmiany są niewielkie, to znaczeniowo bardzo istotne. Tablica zawierawskaźniki a nie obiekty, co więcej nie nastąpiło rzutowanie na obiekt klasy bazowej,a jedynie zrzutowanie wskaźnika. Ta ogromna różnica uwidoczni się po wprowadzeniekolejnej zmiany. Aby wykorzystać mechanizm polimorfizmu w klasie bazowej przy de-klaracji metod, które mają być polimorficzne musimy dodać słowo kluczowe virtual.Obecnie deklaracja klasy Employee (employee.h) wyglądać będzie następująco:

Listing 29:1 // employee . h23 #i f n d e f EMPLOYEE H4 #def ine EMPLOYEE H56 #i nc l ude <s t r i n g >789 c l a s s Employee

10 {11 // p u b l i c i n t e r f a c e12 p u b l i c :13 Employee ( ) {} ;14 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,15 s t d : : s t r i n g p o s i t i o n ) ;16 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ) ;1718 s t d : : s t r i n g getF i r s tName ( ) ;19 s t d : : s t r i n g getSecondName ( ) ;20 s t d : : s t r i n g g e t P o s i t i o n ( ) ;21 v i r t u a l s t d : : s t r i n g i n f o ( ) ;22 vo id s e t P o s i t i o n ( s t d : : s t r i n g p o s i t i o n ) ;232425 // p r i v a t e data26 p r i v a t e :27 s t d : : s t r i n g f i r s tName , secondName , p o s i t i o n ;2829 } ;30 #e n d i f

Skompilujmy nasz programg++ employee . cpp manager . cpp p r z y k l a d . cpp

i zobaczmy wynik:Im i e : JanNazwisko : Kowa l sk iStanowisko : p r o g r a m i s t a

Im i e : Karo lNazwisko : Jankowsk i

41 Materiał dystrybuowany bezpłatnie

Page 42: Szybki wstęp do OOP na przykładzie C++

5 ABSTRAKCJA

Stanowisko : s t a r s z y p r o g r a m i s t aPodwladni : 10

Im i e : K a r o l i n aNazwisko : Wisn iewskaStanowisko : g r a f i k

Wynik jest zupełnie inny niż do tej pory. Mimo, iż deklaracja tablicy pozostała taka jakbyła (a więc jako tablica wskaźników do obiektów typu Employee) to dzięki temu, żeklasa bazowa deklaruje metodę info() jako metodę wirtualną, to program niejako samorientuje się na obiekt jakiej klasy wskazuje wskaźnik, mimo iż został zdeklarowany jakowskaźnik do klasy bazowej. Dzięki temu zawsze wywołana zostanie właściwa metoda(raz info() dla Employee a raz dla Manager). Właśnie ten mechanizm nazywa się po-limorfizmem. Podkreślić należy wyraźnie jeszcze raz - mechanizm ten działa TYLKOw przypadku wskaźników do obiektów, zmiana deklaracji tablicy na tablicę obiektówspowoduje rzutowanie obiektów na typ reprezentujący klasę bazową i o żadnym poli-morfizmie nie ma mowy.

5 Abstrakcja

Abstrakcja to ostatni (ale równie ważny jak te poprzednio omówione) z elementów skła-dowych programowania obiektowego i jednocześnie chyba najtrudniejszy do wyjaśnieniaw kategorii opisowej. Zacznijmy od pojęcia metody czysto wirtualnej. W naszym przy-kładzie z klasami Employee i Manager zdeklarowaliśmy, że metoda info() jest metodąwirtualną co w efekcie pozwoliło na wykorzystanie polimorfizmu. Sama zmiana w sto-sunku do wcześniejszej wersji tej klasy polegała tylko na dopisaniu słowa virtual przednazwą metody. Jednak zagadnienie tworzenia metod wirtualnych jest znacznie szerszeniż tylko dodanie jednego słowa. Zastanówmy się ponownie nad klasami Employee iManager i nad problemem ”mini bazy danych“ pracowników firmy. Pojęcie pracownikw takim ujęciu jest pojęciem dość abstrakcyjnym. Oczywiście każdy pracownik firmyma wspólne cechy (dla nas to imię, nazwisko i stanowisko), ale reszta ”parametrów“jaki i możliwych zachować w głównej mierze zależeć będzie od statusu pracownika. Wnajprostszym przybliżeniu możemy wyróżnić - pracownika fizycznego, pracownika biu-rowego, majstra, kierownika działu, dyrektora działu i wielu innych pracowników. Nibynic nowego w tej analizie nie ma, ale czy ma sens tworzenie obiektu klasy Employee ?Czy w firmie może być tak prosty obiekt jak obiekt klasy Pracownik ? Otóż nie, bo samoimię, nazwisko i stanowisko oraz kilka prostych metod nie określa w żaden sposób kon-kretnego pracownika. Konkretny pracownik ma te cechy, ale ma jeszcze mnóstwo innychcech, odróżniających go od innych pracowników (innych typów), a zarazem upodabnia-jących do jeszcze innych (obiektów tej samej klasy). Dodajmy do klasy Employee nowąmetodę - getSalary() zwracającą wypłatę. Ale czy możemy obliczyć wypłatę pracow-nika ? Nie. Obliczyć możemy wypłatę ze względu na staż pracy, stawkę zaszeregowania,

42 Materiał dystrybuowany bezpłatnie

Page 43: Szybki wstęp do OOP na przykładzie C++

5 ABSTRAKCJA

ewentualne dodatki, potrącenia o pożyczki, ubezpieczenia i wiele innych czynników. Nie-wątpliwie więc taka funkcjonalność powinna być zaimplementowana w klasach innych niżEmployee, ale jak wtedy uzyskać polimorfizm dla wszystkich klas bazujących na klasieEmployee i jak np. stworzyć tablicę zawierającą różnych (w sensie klas) pracowników ?Co więcej - jak wymusić na klasach dziedziczących, aby dodały swoją własną implemen-tację metod wspólnych w sensie nazwy i parametrów dla wszystkich klas dziedziczącychi klasy bazowej (np. właśnie getSalary()).

Zmodyfikujmy nasz program - po pierwsze, dodajmy klasę OrdinaryEmployee reprezen-tującą szeregowego pracownika firmy, po drugie, dodajmy do wszystkich klas metodęgetSallary(), po trzecie załóżmy, że klasa Employee będzie klasą bazową dla pozostałychdwóch klas.

Zacznijmy od modyfikacji klasy Employee (employee.h) dodając do niej wirtualną me-todę getSallary(), jednak ze względu na to, że w klasie Employee nie możemy w żadensposób zdefiniować kodu tej metody, oczywiście z przyczyn sposobu naliczania pensji anie ograniczeń formalnych języka programowania, wyraźnie zaznaczymy, że metoda tanie ma implementacji w tej klasie. Metodę taką nazywamy metodą czysto wirtualną, cozaznaczamy zapisem = 0; w deklaracji metody:

Listing 30:1 // employee . h23 #i f n d e f EMPLOYEE H4 #def ine EMPLOYEE H56 #i nc l ude <s t r i n g >789 c l a s s Employee

10 {11 // p u b l i c i n t e r f a c e12 p u b l i c :13 Employee ( ) {} ;14 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,15 s t d : : s t r i n g p o s i t i o n ) ;16 Employee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ) ;1718 s t d : : s t r i n g getF i r s tName ( ) ;19 s t d : : s t r i n g getSecondName ( ) ;20 s t d : : s t r i n g g e t P o s i t i o n ( ) ;21 v i r t u a l s t d : : s t r i n g i n f o ( ) ;2223 //nowa metoda g e t S a l l a r y ( )24 v i r t u a l double g e t S a l l a r y ( ) = 0 ;2526 vo id s e t P o s i t i o n ( s t d : : s t r i n g p o s i t i o n ) ;272829 // p r i v a t e data

43 Materiał dystrybuowany bezpłatnie

Page 44: Szybki wstęp do OOP na przykładzie C++

5 ABSTRAKCJA

30 p r i v a t e :31 s t d : : s t r i n g f i r s tName , secondName , p o s i t i o n ;3233 } ;3435 #e n d i f

Skoro metoda jest tylko zdeklarowana i co więcej jest wyraźnie zaznaczone, że klasa nieimplementuje jej kodu to nie możemy w takim razie utworzyć obiektów tej klasy. Próbawykonania polecenia:

Employee pracown ik ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ;

spowoduje w trakcie kompilacji następujący błąd:p r z y k l a d . cpp : I n f u n c t i o n i n t main ( ) :p r z y k l a d . cpp : 1 0 : e r r o r : cannot d e c l a r e v a r i a b l e pracown ik to be o fa b s t r a c t type Employeeemployee . h : 1 0 : note : because the f o l l o w i n g v i r t u a l f u n c t i o n s a r epure w i t h i n Employee :employee . h : 2 3 : note : v i r t u a l doub l e Employee : : g e t S a l l a r y ( )

Nie możemy utworzyć obiektu klasy Employee ponieważ zawiera ona metodę czystowirtualną getSalary(),a więc sama klasa jest klasą abstrakcyjną. Klasy abstrakcyjnesą klasami najczęściej służącymi do określenia wspólnego interfejsu dla całej rodzinyklas dziedziczących z klasy abstrakcyjnej. Takie rozwiązanie zapewnia, że każda klasadziedzicząca z takiej klasy musi zdeklarować swój kod metod określonych jako czystowirtualne lub jeżeli tego nie zrobi, to sama będzie klasą abstrakcyjną czyli nie będziemymogli utworzyć obiektów tej klasy. Podkreślmy wyraźnie obiektów tej klasy a niewskaźników do obiektów tej klasy, bo te będziemy mieli i to bardzo często (ze względuna wykorzystanie polimorfizmu).

Kod klasy Employee poza deklaracją metody czysto wirtualnej nie ulegnie dalszym mo-dyfikacjom. Reszta plików może wyglądać np. tak:

Listing 31:1 // o r d i n a r y . h2 #i f n d e f ORIDINARY EMPLOYEE H3 #def ine ORIDINARY EMPLOYEE H45 #i nc l ude ” employee . h”67 c l a s s OrdinaryEmployee : p u b l i c Employee8 {9 p u b l i c :

10 OrdinaryEmployee ( s td : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,11 s t d : : s t r i n g p o s i t i o n ) ;1213 double g e t S a l a r y ( ) ;14

44 Materiał dystrybuowany bezpłatnie

Page 45: Szybki wstęp do OOP na przykładzie C++

5 ABSTRAKCJA

15 } ;16 #e n d i f

Listing 32:1 // o r d i n a r y . cpp2 #i nc l ude ” o r d i n a r y . h”3 #i nc l ude <ss t ream>45 us ing s t d : : s t r i n g ;67 OrdinaryEmployee : : Ord inaryEmployee ( s t r i n g f i r s tName , s t r i n g secondName ,8 s t r i n g p o s i t i o n )9 : Employee ( f i r s tName , secondName , p o s i t i o n )

10 {}1112 double OrdinaryEmployee : : g e t S a l a r y ( )13 {14 double podstawa =40∗4∗20;15 re tu rn podstawa ;16 }

Listing 33:1 // manager . h2 #i f n d e f MANAGER H3 #def ine MANAGER H45 #i nc l ude ” employee . h”67 c l a s s Manager : p u b l i c Employee8 {9 p u b l i c :

10 Manager ( s t d : : s t r i n g f i r s tName , s t d : : s t r i n g secondName ,11 s t d : : s t r i n g p o s i t i o n , i n t employees ) ;12 s t d : : s t r i n g i n f o ( ) ;13 double g e t S a l a r y ( ) ;1415 p r i v a t e :16 i n t employees ;1718 } ;19 #e n d i f

Listing 34:1 // manager . cpp2 #i nc l ude ” manager . h”3 #i nc l ude <ss t ream>45 us ing s t d : : s t r i n g ;6 us ing s t d : : o s t r i n g s t r e a m ;78 Manager : : Manager ( s t r i n g f i r s tName , s t r i n g secondName , s t r i n g p o s i t i o n ,

45 Materiał dystrybuowany bezpłatnie

Page 46: Szybki wstęp do OOP na przykładzie C++

5 ABSTRAKCJA

9 i n t employees )10 : Employee ( f i r s tName , secondName , p o s i t i o n )11 {12 th i s−>employees=employees ;13 }1415 s t r i n g Manager : : i n f o ( )16 {17 o s t r i n g s t r e a m b u f f o r ;18 b u f f o r <<Employee : : i n f o ()<<”\nPodwladni : ”<<employees ;19 re tu rn b u f f o r . s t r ( ) ;20 }2122 double Manager : : g e t S a l a r y ( )23 {24 double podstawa =40∗4∗20;25 //20% w i e c e j j a ko c o m i e s i e c z n a premia z r a c j i s t anow i s ka26 re tu rn 1 .2∗ podstawa ;27 }

no i ”główna“ część programu:

Listing 35:1 // p r z y k l a d . cpp2 #i nc l ude ” employee . h”3 #i nc l ude ” o r d i n a r y . h”4 #i nc l ude ” manager . h”5 #i nc l ude <i o s t r eam >67 us ing namespace s t d ;89 main ( )

10 {1112 Employee ∗ tab [ ]={new OrdinaryEmployee ( ” Jan ” , ” Kowa l sk i ” , ” p r o g r a m i s t a ” ) ,13 new Manager ( ” Karo l ” , ” Jankowsk i ” , ” s t a r s z y p r o g r a m i s t a ” , 10 ) ,14 new OrdinaryEmployee ( ” K a r o l i n a ” , ” Wisn iewska ” , ” g r a f i k ” ) } ;1516 f o r ( i n t i =0; i <3; i ++)17 cout <<tab [ i ]−> i n f o ()<<”\ nPens ja = ”<<tab [ i ]−>g e t S a l a r y ()<<”\n\n\n” ;1819 // z w o l n i e n i e pamiec i20 f o r ( i n t i =0; i <3; i ++)21 de le te tab [ i ] ;2223 re tu rn 0 ;24 }

46 Materiał dystrybuowany bezpłatnie