r15-06.doc

(156 KB) Pobierz
Szablon dla tlumaczy

Rozdział 15.
Specjalne klasy i funkcje

C++ oferuje kilka sposobów na ograniczenie zakresu i oddziaływania zmiennych i wskaźników. Do tej pory, dowiedzieliśmy się, jak tworzyć zmienne globalne, lokalne zmienne funkcji, wskaźniki do zmiennych oraz zmienne składowe klas.

Z tego rozdziału dowiesz się:

·         czym są zmienne statyczne i funkcje składowe,

·         jak używać zmiennych statycznych i statycznych funkcji składowych,

·         jak tworzyć i operować wskaźnikami do funkcji i wskaźnikami do funkcji składowych,

·         jak pracować z tablicami wskaźników do funkcji.

Statyczne dane składowe

Prawdopodobnie do tej pory uważałeś dane każdego obiektu za unikalne dla tego obiektu (i nie współużytkowane pomiędzy obiektami klasy). Gdybyś miał na przykład pięć obiektów klasy Cat, każdy z nich miałby swój wiek, wagę, itp. Wiek jednego kota nie wpływa na wiek innego.

Czasem zdarza się jednak, że chcemy śledzić pulę danych. Na przykład, możemy chcieć wiedzieć, ile obiektów danej klasy zostało stworzonych w programie, a także ile z nich istnieje nadal. Statyczne zmienne składowe są współużytkowane przez wszystkie egzemplarze obiektów klasy. Stanowią one kompromis pomiędzy danymi globalnymi, które są dostępne dla wszystkich części programu, a danymi składowymi, które zwykle są dostępne tylko dla konkretnego obiektu.

Statyczne składowe można traktować jako należące do całej klasy, a nie tylko do pojedynczego obiektu. Zwykłae danae składowae odnosi się do pojedynczego przechowywane są po jednej dla każdego obiektu, a dana składowae statycznae odnosi się przechowywane po jednej dla do całej klasy. Listing 15.1 deklaruje obiekt Cat, zawierający statyczną składową HowManyCats (ile kotów). Ta zmienna śledzi, ile obiektów klasy Cat zostało utworzonych. Śledzenie odbywa się poprzez inkrementację statycznej zmiennej HowManyCats w konstruktorze klasy i dekrementowanie jej w destruktorze.

Listing 15.1. Statyczne dane składowe

  0:  //Listing 15.1 Statyczne dane składowe

  1: 

  2:  #include <iostream>

  3:  using namespace std;

  4: 

  5:  class Cat

  6:  {

  7:  public:

  8:     Cat(int age):itsAge(age){HowManyCats++; }

  9:     virtual ~Cat() { HowManyCats--; }

10:     virtual int GetAge() { return itsAge; }

11:     virtual void SetAge(int age) { itsAge = age; }

12:     static int HowManyCats;

13: 

14:  private:

15:     int itsAge;

16: 

17:  };

18: 

19:  int Cat::HowManyCats = 0;

20: 

21:  int main()

22:  {

23:     const int MaxCats = 5; int i;

24:     Cat *CatHouse[MaxCats];

25:     for (i = 0; i<MaxCats; i++)

26:        CatHouse[i] = new Cat(i);

27: 

28:     for (i = 0; i<MaxCats; i++)

29:     {

30:        cout << "Zostalo kotow: ";

31:        cout << Cat::HowManyCats;

32:        cout << "\n";

33:        cout << "Usuwamy kota, ktory ma ";

34:        cout << CatHouse[i]->GetAge();

35:        cout << " lat\n";

36:        delete CatHouse[i];

37:        CatHouse[i] = 0;

38:     }

39:     return 0;

40:  }

 

Wynik

Zostalo kotow: 5

Usuwamy kota, ktory ma 0 lat

Zostalo kotow: 4

Usuwamy kota, ktory ma 1 lat

Zostalo kotow: 3

Usuwamy kota, ktory ma 2 lat

Zostalo kotow: 2

Usuwamy kota, ktory ma 3 lat

Zostalo kotow: 1

Usuwamy kota, ktory ma 4 lat

Analiza

W liniach od 5. do 17. została zadeklarowana uproszczona klasa Cat. W linii 12. zmienna HowManyCats została zadeklarowana jako statyczna zmienna składowa typu int.

Sama deklaracja zmiennej HowManyCats nie definiuje wartości całkowitej i nie jest dla niej rezerwowane miejsce w pamięci. W odróżnieniu od zwykłych zmiennych składowych, w momencie tworzenia egzemplarzy obiektów klasy Cat, nie jest tworzone miejsce dla tej zmiennej statycznej, gdyż nie znajduje się ona w obiekcie. W związku z tym musieliśmy zdefiniować i zainicjalizować tę zmienną w linii 19..

Programistom bardzo często zdarza się zapomnieć o zdefiniowaniu statycznych zmiennych składowych klasy. Nie pozwól, by przydarzało się to tobie! Oczywiście, gdy się przydarzy, linker zgłosi komunikat błędu, informujący o niezdefiniowanym symbolu, na przykład taki jak poniższy:

 

undefined symbol Cat::HowManyCats

(niezdefiniowany symbol Cat::HowManyCats)

 

Nie musimy definiować zmiennej itsAge, gdyż nie jest statyczną zmienną składową i w związku z tym jest definiowana za każdym razem, gdy tworzymy obiekt klasy Cat (w tym przypadku w linii 26. programu).

Konstruktor klasy Cat w linii 8. inkrementuje statyczną zmienną składową. Destruktor (zawarty w linii 9.) dekrementuje ją. Zatem zmienna HowManyCats przez cały czas zawiera właściwą ilość obiektów Cat, które zostały utworzone i jeszcze nie zniszczone.

Program sterujący. zawarty w liniach od 21. do 40., tworzy pięć egzemplarzy obiektów klasy Cat i umieszcza je w tablicypamięci. Powoduje to pięciokrotne wywołanie deskonstruktora, w związku z czym następuje pięciokrotne inkrementowanie zmiennej HowManyCats od jej początkowej wartości 0.

Następnie program w pętli przechodzi przez wszystkie pięć elementów tablicy i przed usunięciem kolejnego bieżącego wskaźnika do obiektu Cat wypisuje wartość zmiennej HowManyCats. Wydruk pokazuje to, że wartością początkową jest 5 (gdyż zostało skonstruowanych pięć obiektów) i że przy każdym wykonaniu pętli pozostaje o jeden obiekt Cat mniej.

Zwróć uwagę, że zmienna HowManyCats jest publiczna i jest używana bezpośrednio w funkcji main(). Nie ma powodu do udostępniania zmiennej składowej w ten sposób. Najlepszą metodą jest uczynienie z niej prywatnej składowej i udostępnienie publicznego akcesora (o ile ma być ona dostępna wyłącznie poprzez egzemplarze klasy Cat). Z drugiej strony, gdybyśmy chcieli korzystać z tej danej bezpośrednio, niekoniecznie posiadając obiekt klasy Cat, mamy do wyboru dwie opcje: możemy zadeklarować tę zmienną jako publiczną, tak jak pokazano na listingu 15.2, albo dostarczyć akcesor w postaci statycznej funkcji składowej, co zostanie omówione w dalszej części rozdziału.

Listing 15.2. Dostęp do statycznych danych składowych bez obiektu

  0:  //Listing 15.2 Statyczne dane składowe

  1: 

  2:  #include <iostream>

  3:  using namespace std;

  4: 

  5:  class Cat

  6:  {

  7:  public:

  8:     Cat(int age):itsAge(age){HowManyCats++; }

  9:     virtual ~Cat() { HowManyCats--; }

10:     virtual int GetAge() { return itsAge; }

11:     virtual void SetAge(int age) { itsAge = age; }

12:     static int HowManyCats;

13: 

14:  private:

15:     int itsAge;

16: 

17:  };

18: 

19:  int Cat::HowManyCats = 0;

20: 

21:  void TelepathicFunction();

22: 

23:  int main()

24:  {

25:     const int MaxCats = 5; int i;

26:     Cat *CatHouse[MaxCats];

27:     for (i = 0; i<MaxCats; i++)

28:     {

29:        CatHouse[i] = new Cat(i);

30:        TelepathicFunction();

31:     }

32: 

33:     for ( i = 0; i<MaxCats; i++)

34:     {

35:        delete CatHouse[i];

36:        TelepathicFunction();

37:     }

38:     return 0;

39:  }

40: 

41:  void TelepathicFunction()

42:  {

43:     cout << "Zostalo jeszcze zywych kotow: ";

44:     cout << Cat::HowManyCats << "\n";

45:  }

 

Wynik

Zostalo jeszcze zywych kotow: 1

Zostalo jeszcze zywych kotow: 2

Zostalo jeszcze zywych kotow: 3

Zostalo jeszcze zywych kotow: 4

Zostalo jeszcze zywych kotow: 5

Zostalo jeszcze zywych kotow: 4

Zostalo jeszcze zywych kotow: 3

Zostalo jeszcze zywych kotow: 2

Zostalo jeszcze zywych kotow: 1

Zostalo jeszcze zywych kotow: 0

Analiza

Listing 15.2 przypomina listing 15.1, z wyjątkiem nowej funkcji, TelepathicFunction() (funkcja telepatyczna). Ta funkcja nie tworzy obiektu Cat ani nie otrzymuje obiektu Cat jako parametru, a mimo to może odwoływać się do zmiennej składowej HowManyCats. Należy pamiętać, że ta zmienna składowa nie należy do żadnego konkretnego obiektu; znajduje się w klasie i, o ile jest publiczna, może być wykorzystywana przez każdą funkcję w programie.

Alternatywą dla tej zmiennej publicznej może być zmienna prywatna. Gdy skorzystamy z niej, możemy to uczynić poprzez funkcję składową, ale wtedy musimy posiadać obiekt tej klasy. Takie rozwiązanie przedstawia listing 15.3. Alternatywne rozwiązanie, z wykorzystaniem statycznej funkcji składowej, zostanie omówione bezpośrednio po analizie listingu 15.3.

Listing 15.3. Dostęp do statycznych składowych za pomocą zwykłych funkcji składowych

  0:  //Listing 15.3 prywatne statyczne dane składowe

  1: 

  2:  #include <iostream>

  3:  using std::cout;

  4: 

  5:  class Cat

  6:  {

  7:  public:

  8:     Cat(int age):itsAge(age){HowManyCats++; }

  9:     virtual ~Cat() { HowManyCats--; }

10:     virtual int GetAge() { return itsAge; }

11:     virtual void SetAge(int age) { itsAge = age; }

12:     virtual int GetHowMany() { return HowManyCats; }

13: 

14: 

15:  private:

16:     int itsAge;

17:     static int HowManyCats;

18:  };

19: 

20:  int Cat::HowManyCats = 0;

21: 

22:  int main()

23:  {

24:     const int MaxCats = 5; int i;

25:     Cat *CatHouse[MaxCats];

26:     for (i = 0; i<MaxCats; i++)

27:        CatHouse[i] = new Cat(i);

28: 

29:     for (i = 0; i<MaxCats; i++)

30:     {

31:        cout << "Zostalo jeszcze ";

32:        cout << CatHouse[i]->GetHowMany();

33:        cout << " kotow!\n";

34:        cout << "Usuwamy kota, ktory ma ";

35:        cout << CatHouse[i]->GetAge()+2;

36:        cout << " lat\n";

37:        delete CatHouse[i];

38:        CatHouse[i] = 0;

39:     }

40:     return 0;

41:  }

 

Wynik

Zostalo jeszcze 5 kotow!

Usuwamy kota, ktory ma 2 lat

Zostalo jeszcze 4 kotow!

Usuwamy kota, ktory ma 3 lat

Zostalo jeszcze 3 kotow!

Usuwamy kota, ktory ma 4 lat

Zostalo jeszcze 2 kotow!

Usuwamy kota, ktory ma 5 lat

Zostalo jeszcze 1 kotow!

Usuwamy kota, ktory ma 6 lat

Analiza

W linii 17. statyczna zmienna składowa HowManyCats została zadeklarowana jako składowa prywatna. Nie możemy więc odwoływać się do niej z funkcji innych niż składowe, na przykład takich, jak TelepathicFunction() z poprzedniego listingu.

Jednak mimo, iż zmienna HowManyCats jest statyczna, nadal znajduje się w zakresie klasy. Może się do niej odwoływać dowolna funkcja składowa klasy, na przykład GetHowMany(), podobnie jak do wszystkich innych danych składowych. Jednak, aby zewnętrzna funkcja mogła wywołać metodę GetHowMany(), musi posiadać obiekt klasy Cat.

 

TAK

NIE

W celu współużytkowania danych pomiędzy wszystkimi egzemplarzami klasy używaj statycznych zmiennych składowych

Jeśli chcesz ograniczyć dostęp do statycznych zmiennych składowych, uczyń je składowymi prywatnymi lub chronionymi.

Nie używaj statycznych zmiennych składowych do przechowywania danych należących do pojedynczego obiektu. Statyczne dane składowe są współużytkowane przez wszystkie obiekty klasy.

 

Statyczne funkcje składowe

Statyczne funkcje składowe działają podobnie do statycznych zmiennych składowych: istnieją nie w obiekcie, ale w zakresie klasy. W związku z tym mogą być wywoływane bez posiadania obiektu swojej klasy, co ilustruje listing 15.4.

Listing 15.4. Statyczne funkcje składowe

  0:  //Listing 15.4 statyczne funkcje składowe

  1: 

  2:  #include <iostream>

  3: 

  4:  class Cat

  5:  {

  6:  public:

  7:     Cat(int age):itsAge(age){HowManyCats++; }

  8:     virtual ~Cat() { HowManyCats--; }

  9:     virtual int GetAge() { return itsAge; }

10:     virtual void SetAge(int age) { itsAge = age; }

11:     static int GetHowMany() { return HowManyCats; }

12:  private:

13:     int itsAge;

14:     static int HowManyCats;

15:  };

16: 

17:  int Cat::HowManyCats = 0;

18: 

19:  void TelepathicFunction();

20: 

21:  int main()

22:  {

23:     const int MaxCats = 5;

...

Zgłoś jeśli naruszono regulamin