Programowanie zorientowane obiektowo część 2


[Komentarz LeMUra]
[Komentarz Kaczusia]


Cóż jak na razie brak jest pytań do mnie, lecz nic dziwnego, ponieważ gdy piszę te słowa dotarła do mnie jedynie wersja testowa magazynu. No... kolega LeMUr wystosował do mnie prośbę, aby odcinki były dłuższe, lecz ze względu na sposób w jaki to piszę nie jest to dla mnie najwygodniejsze, dlatego postaram się dosłać w najbliższym czasie kolejną część i gdy redakcja stwierdzi, że któraś z nich jest zbyt krótka to połączy je.

Ale przejdźmy do rzeczy. W tym rozdziale będziemy mówili o pewnych słowach kluczowych (spotkaliśmy się z jednym z nich w przykładach poprzedniej części), oraz o składowych static i const (nie oznacza to bynajmniej, iż skończyliśmy temat konstruktorów i destruktorów - będziemy jeszcze do niego powracać).


W obiektach języka C++ możemy spotkać trzy słowa kluczowe: public, private i protected. Programujący w klasycznym C być może się spotkali z którymś z nich ponieważ słowa te już tam się pojawiły, lecz nie były tak często wykorzystywane. Pytanie jakie ciśnie się wam na usta to zapewne: "Po co nam te etykiety?" Odpowiedź jest prosta, otóż etykiety te określają reguły dostępu do odpowiednich pól składowych obiektu. W klasycznym C domyślnym słowem było słowo public, natomiast w klasach (obiektach) w C++ domyślnym słowem jest private. Dobrze, ktoś mi może zarzucić, że nie odpowiadam na pytanie, lecz zanim dojdę do sedna sprawy muszę parę rzeczy wyjaśnić. Najpierw może powiem co powoduje użycie poszczególnych słów:

public:
wszystkich składowych obiektu o tej etykiecie możemy bez obaw używać.
private:
składowe obiektu mogą być używane jedynie przez inne składowe tego obiektu, lub przez funkcje zaprzyjaźnione (o których powiem w następnych częściach).
protected:
jak wyżej. Różnice są zauważalne dopiero przy dziedziczeniu o którym będzie w dalszej części kursu.
Coś mi się wydaje, że zaczynacie się gubić w tym wszystkim, dlatego przedstawię to na krótkim przykładzie:
(tym razem przykłady trącić będą nieco matematyką)
 
class Trójkąt
{
   // składowe prywatne (private)
   Pole P;
   LiczPole();

   // składowe publiczne 
   public:
   boki a,b,c;
   Policz();   // public
   Trójkąt(bok,bok,bok);

   // składowe zastrzeżone
   protected:
   obwód O;
   LiczObwód();
};	

// tu powinny wystąpić definicje powyższych funkcji

void main()
{
   // ...
   Trójkąt Równoboczny(5,5,5);
   Równoboczny.Policz;
   Równoboczny.LiczObwód();   // w tym miejscu kompilator poinformuje nas o
                              // błędzie, ponieważ nie możemy użyć tej
                              // funkcji ze względu na jej status  
   Równoboczny.P=15;          // również tu pojawi się błąd
   // ...                        
}

Dobrze, Teraz mogę ze spokojem serca wyjaśnić po co nam te słowa kluczowe. Otóż weźmy pod uwagę taki obiekt jak trójkąt, w którym wartości odpowiednich pól są od siebie zależne. Aby obronić się przed przypadkową zmianą jednej z wartości bez zmiany innej zależnej od niej wartości możemy zabezpieczyć się po przez nadanie tym polom statusu private (lub protected), a do zmiany wartości tych pól stworzyć odpowiednie funkcje o statusie private. Pewną niedogodnością tego rozwiązania jest fakt, iż są kłopoty z bezpośrednim pobraniem wartości takiego pola na zewnątrz obiektu (np. gdy chcemy wydrukować na ekranie jego wartość). Należy do tego celu użyć znowu publicznych funkcji składowych tej klasy. Więcej na ten temat dowiecie się w dalszej części cyklu, gdy będziemy mówili o zaprzyjaźnianiu się i dziedziczeniu.


Teraz przejdźmy do następnego dość istotnego problemu. Zdarza się, iż podczas programowania dla wszystkich obiektów danego typu chcemy mieć jeden wspólny obiekt. Jest na to rozwiązanie - deklaracja globalna takiego obiektu, lecz nie jest to rozwiązanie zbyt eleganckie. Oczywiście jest jeszcze możliwość zadeklarowania takiego pola w klasie nas interesującej, lecz to rozwiązanie nie jest zbyt wygodne, ponieważ przy każdorazowej zmianie wartości takiego pola musimy dokonać tych zmian we wszystkich obiektach tego typu. Cóż więc w takim wypadku zrobić? - otóż twórcy obiektowości przeciążyli operator static. Dzięki temu zabiegowi deklarując w jakimś obiekcie pole typu static otrzymujemy wspólne pole dla wszystkich obiektów (może to być na przykład licznik obiektów danego typu). Uwaga - bardzo istotna sprawa: jeżeli w obiekcie występuje pole typu static należy mu nadać wartość zanim powstanie obiekt. Sposób deklaracji takiego pola podałem na przykładzie:


 
class Przykładowa
{
   public:
     //...
   static int a;  // co akurat ta zmienna ma oznaczać - nieważne
     //...      
};
//...
int Przykładowa::a=0; // nadałem tu akurat wartość zerową, lecz nie jest to
                      // istotne

Do czego wykorzystać to dobrodziejstwo? Otóż zdarza nam się,iż pisząc jakiś program chcemy z obiektów danego typu odwoływać się często do adresu ekranu, czy okna głównego programu. Wtedy jako pole typu static możemy utworzyć wskaźnik na okno (czy ekran). Aby ułatwić sobie sprawę można wykorzystać składowe funkcje typu static (zapomniałem wspomnieć, lecz również składowe mogą być tego typu), ponieważ funkcje takie mogą zostać wywołane zanim jeszcze powstanie jakikolwiek obiekt tego typu. A oto wspomniany przeze mnie przykład:


 
class Okna
{
   //...
   public:
   static Window *okno;
   static Window *OtwórzOkno();
   static void ZmknijOkno();
   //...
};

//...

void main()
{
   //...
   Window *Okna::okno=Okna::OtwórzOkno();
   //... 
   Okna::ZamknijOkno();
}

Uwaga!!! W defincji składowych funkcji statycznych nie wolno nam się odwoływać do niestatycznych pól czy niestatycznych składowych danego obiektu.

Na zakończenie chciałbym wspomnieć jeszcze o polach i składowych typu const. Czym charakteryzują się pola typu const - otóż nie wolno nam ich zmieniać, powinny one powstać zanim powstanom pozostałe pola klasy dlatego sposób ich przypisania jest nieco inny niż pozostałych pól. Wartości ich muszą być nadane na tzw. liście inicjacyjnej. Zapytacie zapewne co to jest ta lista inicjacyjna - jest to część konstruktora występująca przed jego ciałem (czyli definicją).


 
class Obiekt
{
     //...
   public:
   const int stała;
   int inna;
   Obiekt(int a,int b,...);
     //...
};

//...

Obiekt::Obiekt(int a,int b,...):stała(a)
{
   inna=b;
     //...
}

Czy zauważyliście coś nowego? -oczywiście! Za nawiasem znajduje się dwukropek, a za nim coś jakby funkcja, z tym że nazwą funkcji jest stała (lub jakieś inne pole tego obiektu) natomiast w nawiasie znajduje się wartość, która ma być nadana tej stałej (lub temu polu funkcji - niekoniecznie musi być to stała). Wstępują oczywiście również składowe typu const. Mają one specyficzne usytuowanie operatora const. Znajduje się mniej więcej w tym samym miejscu co lista inicjacyjna, z tą różnicą, że nie występuje on przy konstruktorze, oraz nie jest poprzedzony dwukropkiem. Bardzo ważną rzeczą jest fakt, że w ciele tej funkcji nie możemy zmieniać żadnej z wartości pól danej klasy.


 
class  Obiekt  
{
   //...
   public:  
   void  funkcja_stała() const;
   //...
};

//...

void Obiekt::funkcja_stała() const 
{
     //...
}

Na tym kończę kolejną część z cyklu "Programowanie obiektowe". Do artykułu dołączony jest program przykładowy. Wszelkie zapytania proszę kierować na adres: kaczus@poczta.onet.pl

Kaczuś

.
Powrót do Menu