r06-06.doc

(203 KB) Pobierz
Szablon dla tlumaczy

Rozdział 6.
Programowanie zorientowane obiektowo

Klasy rozszerzają wbudowane w C++ możliwości, ułatwiające rozwiązywanie złożonych, „rzeczywistych” problemów.

Z tego rozdziału dowiesz się:

·         czym są klasy i obiekty,

·         jak definiować nową klasę oraz tworzyć obiekty tej klasy,

·         czym są funkcje i dane składowe,

·         czym są konstruktory i jak z nich korzystać.

Czy C++ jest zorientowane obiektowo?

Język C++ stanowi pomost pomiędzy programowaniem zorientowanym obiektowo a językiem C, najpopularniejszym językiem programowania aplikacji komercyjnych. Celem jego autorów było stworzenie obiektowo zorientowanego języka dla tej szybkiej i efektywnej platformy.

Język C jest etapem pośrednim pomiędzy wysokopoziomowymi językami aplikacji „firmowych”, takimi jak COBOL, a niskopoziomowym, wysokowydajnym, lecz trudnym do użycia asemblerem. C wymusza programowanie „strukturalne”, w którym poszczególne zagadnienia są dzielone na mniejsze jednostki powtarzalnych działań, zwanych funkcjami.

Programyu, które piszemy na początku dwudziestego pierwszego wieku, są dużo bardziej złożone niż te, które były pisane pod koniec wieku dwudziestego. Programy stworzone w językach proceduralnych są trudne w zarządzaniu i konserwacji, a ich rozbudowa jest niemożliwa. Graficzne interfejsy użytkownika, Internet, telefonia cyfrowa i bezprzewodowa oraz wiele innych technologii, znacznie zwiększyły poziom skomplikowania nowych projektów, a wymagania konsumentów dotyczące jakości interfejsu użytkownika wzrosły.

W obliczu rosnących wymagań, programiści bacznie przyjrzeli się przemysłowi informatycznemu. Wnioski, do jakich doszli, były co najmniej przygnębiające. Oprogramowanie powstawało z opóźnieniem, posiadało błędy, działało niestabilnie i było drogie. Projekty regularnie przekraczały budżet i trafiały na rynek z opóźnieniem. Koszt obsługi tych projektów był znaczny, zmarnowano ogromne ilości pieniędzy.

Jedynym wyjściem z tej sytuacji okazało się tworzenie oprogramowania zorientowanego obiektowo. Języki programowania obiektowego stworzyły silne więzy pomiędzy strukturami danych a metodami manipulowania tymi danymi. A co najważniejsze, w programowaniu zorientowanym obiektowo nie już musisz myśleć o strukturach danych i manipulujących nimi funkcjami; myślisz o obiektach. Rzeczach.

Świat jest wypełniony przedmiotami: samochodami, psami, drzewami, chmurami, kwiatami. Rzeczy. Każda rzecz ma charakterystykę (szybki, przyjazny, brązowy, puszysty, ładny). Większość rzeczy cechuje jakieś zachowanie (ruch, szczekanie, wzrost, deszcz, uwiąd). Nie myślimy o danych psa i o tym, jak moglibyśmy nimi manipulować — myślimy o psie jako o rzeczy: do czego jest podobny i co robi.

Tworzenie nowych typów

Poznałeś już kilka typów zmiennych, m.in. liczby całkowite i znaki. Typ zmiennej dostarcza nam kilka informacji o niej. Na przykład, jeśli zadeklarujesz zmienne Height (wysokość) i Width (szerokość) jako liczby całkowite typu unsigned short int, wiesz, że w każdej z nich możesz przechować wartość z przedziału od 0 do 65 5356 (przy założeniu że typ unsigned short int zajmuje dwa bajty pamięci). Są to liczby całkowite bez znaku; próba przechowania w nich czegokolwiek innego powoduje błąd. W zmiennej typu unsigned short nie możesz umieścić swojego imienia, nie powinieneś nawet próbować.

Deklarując te zmienne jako unsigned short int, wiesz, że możesz dodać do siebie wysokość i szerokość oraz przypisać tę wartość innej zmiennej.

Typ zmiennych informuje:

·         o ich rozmiarze w pamięci,

·         jaki rodzaj informacji mogą zawierać,

·         jakie działania można na nich wykonywać.

W tradycyjnych językach, takich jak C, typy były wbudowane w język. W C++ programista może rozszerzyć język, tworząc potrzebne mu typy, zaś każdy z tych nowych typów może być w pełni funkcjonalny i dysponować tą samą siłą, co typy wbudowane.

Po co tworzyć nowy typ?

Programy są zwykle pisane w celu rozwiązania jakiegoś realnego problemu, takiego jak prowadzenie rejestru pracowników czy symulacja działania systemu grzewczego. Choć istnieje możliwość rozwiązywania tych problemów za pomocą programów napisanych wyłącznie przy użyciu liczb całkowitych i znaków, jednak w przypadku większych, bardziej rozbudowanych problemów, dużo łatwiej jest stworzyć reprezentacje obiektów, o których się mówi. Innymi słowy, symulowanie działania systemu grzewczego będzie łatwiejsze, gdy stworzymy zmienne reprezentujące pomieszczenia, czujniki ciepła, termostaty i bojlery. Im bardziej te zmienne odpowiadają rzeczywistości, tym łatwiejsze jest napisanie programu.

Klasy i składowe

Nowy typ zmiennych tworzy się, deklarując klasę. Klasa jest właściwie grupą zmiennych — często o różnych typach — połączonych skojarzonych z zestawem powiązanych odnoszących się do nich funkcji.

Jedną z możliwości myślenia o samochodzie jest potraktowanie go jako zbioru kół, drzwi, foteli, okien, itd. Inna możliwość to wyobrażenie sobie, co samochód może zrobić: jeździć, przyspieszać, zwalniać, zatrzymywać się, parkować, itd. Klasa umożliwia kapsułkowanie, czyli upakowanie, tych różnych części oraz różnych działań w jeden zbiór, który jest nazywana obiektem.

Upakowanie wszystkiego, co wiesz o samochodzie, w jedną klasę przynosi programiście liczne korzyści. Wszystko jest na miejscu, ułatwia to odwoływanie się, kopiowanie i manipulowanie danymi. Klienty twojej klasy — tj. te części programu, które z niej korzystają — mogą używać twojego obiektu bez zastanawiania się, co znajduje się w środku i jak on działa.

Klasa może składać się z dowolnej kombinacji zmiennych prostych oraz zmiennych innych klas. Zmienna wewnątrz klasy jest nazywana zmienną składową lub daną składową. Klasa Car (samochód) może posiadać składowe reprezentujące siedzenia, typ radia, opony, itd.

Zmienne składowe są zmiennymi w danej klasie. Stanowią one część klasy, tak jak koła i silnik stanowią część samochodu.

Funkcje w danej klasie zwykle manipulują zmiennymi składowymi. Funkcje klasy nazywa się funkcjami składowymi lub metodami klasy. Metodami klasy Car mogą być Start() (uruchom) oraz Brake() (hamuj). Klasa Cat (kot) może posiadać zmienne składowe, reprezentujące wiek i wagę; jej metodami mogą być Sleep() (śpij), Meow() (miaucz) czy ChaseMice() (łap myszy).

Funkcje składowe (metody) są funkcjami w klasie. Podobnie jak zmienne składowe, stanowią część klasy i określają, co dana klasa może zrobić.

Deklarowanie klasy

Aby zadeklarować klasę, użyj słowa kluczowego class, po którym następuje otwierający nawias klamrowy, a następnie lista danych składowych i metod tej klasy. Deklaracja kończy się zamykającym nawiasem klamrowym i średnikiem. Oto deklaracja klasy o nazwie Cat (kot):

 

class Cat

{

    unsigned int  itsAge;

    unsigned int  itsWeight;

    void Meow();

};

 

Zadeklarowanie takiej klasy nie powoduje zaalokowania pamięci dla obiektu Cat. Informuje jedynie kompilator, czym jest typ Cat, jakie dane zawiera (itsAge — jego wiek oraz itsWeight — jego waga) oraz co może robić (Meow() — miaucz). Informuje także kompilator, jak duża jest zmienna typu Cat — to jest, jak dużo miejsca w pamięci ma przygotować w przypadku tworzenia zmiennej typu Cat. W tym przykładzie, o ile typ int ma cztery bajty, zmienna typu Cat zajmuje osiem bajtów: cztery bajty dla zmiennej itsAge i cztery dla zmiennej itsWeight. Funkcja Meow() nie zajmuje miejsca, gdyż dla funkcjia składowych (metod) miejsce nie jest rezerwowane.

Kilka słów o konwencji nazw

Jako programista, musisz nazwać wszystkie swoje zmienne składowe, funkcje składowe oraz klasy. Jak przeczytałeś w rozdziale 3., „Stałe i zmienne,” nazwy te powinny być zrozumiałe i znaczące. Dobrymi nazwami klas mogą być wspomniana Cat, Rectangle (prostokąt) czy Employee (pracownik). Meow(), ChaseMice() czy StopEngine() (zatrzymaj silnik) również są dobrymi nazwami funkcji, gdyż informują, co robią te funkcje. Wielu programistów nadaje nazwom zmiennych składowych przedrostek „its” (jego), tak jak w zmiennych itsAge, itsWeight czy itsSpeed (jego szybkość). Pomaga to w odróżnieniu zmiennych składowych od innych zmiennych.

Niektórzy programiści wolą przedrostek „my” (mój), tak jak w nazwach myAge, myWeight czy mySpeed. Jeszcze inni używają po prostu litery m (od słowa member — składowa), czasem wraz ze znakiem podkreślenia (_): mAge i m_age, mWeight i m_weight czy mSpeed i m_speed.

Język C++ uwzględnia wielkość liter, dlatego wszystkie nazwy klas powinny przestrzegać tej samej konwencji. Dzięki temu nigdy nie będziesz musiał sprawdzać pisowni nazwy klasy (czy to było Rectangle, rectangle czy RECTANGLE?).

Niektórzy programiści lubią poprzedzić każdą nazwę klasy określoną literą — na przykład cCat czy cPerson — podczas, gdy inni używają wyłącznie dużych lub małych liter. Ja sam korzystam z konwencji, w której wszystkie nazwy klas rozpoczynają się od dużej litery, tak jak Cat czy Person (osoba).

Wielu programistów rozpoczyna wszystkie nazwy funkcji od dużej litery, zaś wszystkie nazwy zmiennych — od małej. Słowa zwykle rozdzielane są znakiem podkreślenia — tak jak w Chase_Mice — lub poprzez zastosowanie dużej litery dla każdego słowa — na przykład ChaseMice czy DrawCircle (rysuj okrąg).

Ważne jest, by wybrać określony styl i trzymać się go w każdym programie. Z czasem rozwiniesz swój styl nie tylko na konwencje nazw, ale także na wcięcia, wyrównanie nawiasów klamrowych oraz styl komentarzy.

UWAGA              W firmach programistycznych powszechne jest określenie standardu wielu elementów stylu zapisu kodu źródłowego. Sprawia on, że wszyscy programiści mogą łatwo odczytywać wzajemnie swój kod.

Definiowanie obiektu

Definiowanie obiektu nowego typu przypomina definiowanie zmiennej całkowitej:

 

unsigned int GrossWeight; // definicja zmiennej typu unsigned int

Cat Mruczek;              // definicja zmiennej typu Cat

 

Ten kod definiuje zmienną o nazwie GrossWeight (łączna waga), której typem jest unsigned int. Oprócz tego definiuje zmienną o nazwie Mruczek, która jest obiektem klasy (typu) Cat.

Klasy a obiekty

Nigdy nie karmi się definicji kota, lecz konkretnego kota. Należy dokonać rozróżnienia pomiędzy ideą kota a konkretnym kotem, który właśnie ociera się o twoje nogi. C++ również dokonuje rozróżnienia pomiędzy klasą Cat, będącą ideą kota, a poszczególnymi obiektami typu Cat. Tak więc Mruczek jest obiektem typu Cat, tak jak GrossWeight jest zmienną typu unsigned int.

Obiekt jest indywidualnym egzemplarzem klasy.

Dostęp do składowych klasy

Gdy zdefiniujesz już faktyczny obiekt Cat — na przykład Mruczek — w celu uzyskania dostępu do jego składowych możesz użyć operatora kropki (.). Aby zmiennej składowej itsWeight obiektu Mruczek przypisać wartość 50, powinieneś napisać:

 

Mruczek.itsWeight = 50;

 

Aby wywołać funkcję Meow(), możesz napisać:

 

Mruczek.Meow();

 

Gdy używasz metody klasy, oznacza to, że wywołujesz tę metodę. W tym przykładzie wywołałeś metodę Meow() obiektu Mruczek.

Przypisywać należy obiektom, nie klasom

W C++ nie przypisuje się wartości typom; przypisuje się je zmiennym. Na przykład, nie można napisać:

 

int = 5;               // źle

 

Kompilator uzna to za błąd, gdyż nie można przypisać wartości pięć typowi całkowitemu. Zamiast tego musisz zdefiniować zmienną typu całkowitego i przypisać jej wartość 5. Na przykład:

 

int x ;                // definicja zmiennej typu int

x = 5;                 // ustawienie wartości zmiennej x na 5

 

Jest to skrócony zapis stwierdzenia: „Przypisz wartość 5 zmiennej x, która jest zmienną typu int.” Nie można również napisać:

 

Cat.itsAge = 5;        // źle

 

Kompilator uzna to za błąd, gdyż nie możesz przypisać wartości 5 do elementu itsAge klasy Cat. Zamiast tego musisz zdefiniować egzemplarz obiektu klasy Cat i dopiero wtedy przypisać wartość jego składowej. Na przykład:

 

Cat Mruczek;           // podobnie jak  int x;

Mruczek.itsAge = 5;    // podobnie jak  x = 5;

 

Czego nie zadeklarujesz, tego klasa nie będzie miała

Przeprowadź taki eksperyment: podejdź do trzylatka i pokaż mu kota. Następnie powiedz: To jest Mruczek. Mruczek zna sztuczkę. Mruczek, zaszczekaj! Dziecko roześmieje się i powie: „Nie, głuptasie, koty nie szczekają!”

Jeśli napisałeś:

 

Cat Mruczek;           // tworzy obiekt Cat o nazwie Mruczek

Mruczek.Bark();        // nakazuje Mruczkowi szczekać

 

Kompilator wypisze: „Nie, głuptasie, koty (cats) nie szczekają!” (Być może w twoim kompilatorze ten komunikat będzie brzmiał nieco inaczej.) Kompilator wie, że Mruczek nie może szczekać, gdyż klasa Cat nie posiada metody Bark() (szczekaj). Kompilator nie pozwoli Mruczkowi nawet zamiauczeć, jeśli nie zdefiniujesz dla niego funkcji Meow() (miaucz).

 

TAK

NIE

Do deklarowania klasy używaj słowa kluczowego class.

W celu uzyskania dostępu do zmiennych i funkcji składowych klasy używaj operatora kropki (.).

Nie myl deklaracji z definicją. Deklaracja mówi czym jest klasa, a definicja przygotowuje pamięć dla obiektu.

Nie myl klasy z obiektem.

Nie przypisuj klasie wartości. Wartości przypisuj danym składowym obiektu.

 

Prywatne i publiczne

W deklaracji klasy używanych jest także kilka innych słów kluczowych. Dwa najważniejsze z nich to: public (publiczny) i private (prywatny).

Wszystkie składowe klasy — dane i metody — są domyślnie prywatne. Prywatne składowe mogą być używane tylko przez metody należące do danej klasy. Składowe publiczne są dostępne dla innych funkcji i klas. To rozróżnienie jest ważne, choć na początku może sprawiać kłopot. Aby to lepiej wyjaśnić, spójrzmy na poprzedni przykład:

 

class Cat

{

    unsigned int  itsAge;

    unsigned int  itsWeight;

    viod Meow();

};

 

W tej deklaracji, składowe itsAge, itsWeight oraz Meow() są prywatne, gdyż wszystkie składowe klasy są prywatne domyślnie. Oznacza to, że dopóki nie postanowisz inaczej, pozostaną one prywatne.

Jeśli jednak w funkcji main() napiszesz na przykład:

 

Cat Bobas;

Bobas.itsAge = 5;      // błąd! nie można używać prywatnych danych!

 

kompilator uzna to za błąd. We wcześniejszej deklaracji powiedziałeś kompilatorowi, że składowych itsAge, itsWeight oraz Meow() będziesz używał tylko w funkcjach składowych klasy Cat. W powyższym fragmencie kodu próbujesz odwołać się do zmiennej składowej obiektu Bobas spoza metody klasy Cat. To, że Bobas jest obiektem klasy Cat, nie oznacza, że możesz korzystać z tych elementów obiektu Bobas, które są prywatne.

Właśnie to jest źródłem niekończących się kłopotów początkujących programistów C++. Jużę słyszę, jak narzekasz: „Hej! Właśnie napisałem, że Bobas jest kotem, tj. obiektem klasy Cat. Dlaczego Bobas nie ma dostępu do swojego własnego wieku?” Odpowiedź brzmi: Bobas ma dostęp, ale ty nie masz. Bobas, w swoich własnych metodach, ma dostęp do wszystkich swoich składowych, zarówno publicznych, jak i prywatnych. Nawet, jeśli to ty tworzysz obiekt klasy Cat, nie możesz przeglądać ani zmieniać tych jego składowych, które ...

Zgłoś jeśli naruszono regulamin