1931


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ę:

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ła[Author ID1: at Wed Oct 31 14:41:00 2001 ]e[Author ID1: at Wed Oct 31 14:41:00 2001 ] dana[Author ID1: at Wed Oct 31 14:41:00 2001 ]e[Author ID1: at Wed Oct 31 14:41:00 2001 ] składowa[Author ID1: at Wed Oct 31 14:41:00 2001 ]e[Author ID1: at Wed Oct 31 14:41:00 2001 ] odnosi się do pojedynczego [Author ID1: at Wed Oct 31 14:41:00 2001 ]przechowywane są po jednej dla każdego[Author ID1: at Wed Oct 31 14:41:00 2001 ] obiektu, a dana [Author ID1: at Wed Oct 31 14:52:00 2001 ]składowa[Author ID1: at Wed Oct 31 14:52:00 2001 ]e[Author ID1: at Wed Oct 31 14:52:00 2001 ] statyczna[Author ID1: at Wed Oct 31 14:52:00 2001 ]e[Author ID1: at Wed Oct 31 14:52:00 2001 ] odnosi się [Author ID1: at Wed Oct 31 14:52:00 2001 ][Author ID1: at Wed Oct 31 14:53:00 2001 ] przechowywane po jednej dla[Author ID1: at Wed Oct 31 14:53:00 2001 ] do [Author ID1: at Wed Oct 31 14:53:00 2001 ]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[Author ID1: at Wed Oct 31 14:54:00 2001 ], 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 tablicy[Author ID1: at Wed Oct 31 14:54:00 2001 ]pamięci[Author ID1: at Wed Oct 31 14:54:00 2001 ]. Powoduje to pięciokrotne wywołanie des[Author ID1: at Wed Oct 31 14:54:00 2001 ]kons[Author ID1: at Wed Oct 31 14:54:00 2001 ]truktora, 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 [Author ID1: at Wed Oct 31 14:55:00 2001 ]bieżącego[Author ID1: at Wed Oct 31 14:55:00 2001 ] 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[Author ID1: at Wed Oct 31 14:56:00 2001 ] 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;

24: Cat *CatHouse[MaxCats]; int i;

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

26: {

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

28: TelepathicFunction();

29: }

30:

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

32: {

33: delete CatHouse[i];

34: TelepathicFunction();

35: }

36: return 0;

37: }

38:

39: void TelepathicFunction()

40: {

41: std::cout<<"Zostalo jeszcze "<< Cat::GetHowMany()<<" zywych kotow!\n";

42: }

Wynik

Zostalo jeszcze 1 zywych kotow!

Zostalo jeszcze 2 zywych kotow!

Zostalo jeszcze 3 zywych kotow!

Zostalo jeszcze 4 zywych kotow!

Zostalo jeszcze 5 zywych kotow!

Zostalo jeszcze 4 zywych kotow!

Zostalo jeszcze 3 zywych kotow!

Zostalo jeszcze 2 zywych kotow!

Zostalo jeszcze 1 zywych kotow!

Zostalo jeszcze 0 zywych kotow!

Analiza

W linii 14., w klasie Cat została zadeklarowana statyczna prywatna zmienna składowa HowManyCats. Publiczny akcesor, GetHowMany(), został w linii 11. zadeklarowany zarówno jako metoda publiczna, jak i statyczna.

Ponieważ metoda GetHowMany() jest publiczna, może być używana w każdej funkcji zewnętrznej, a ponieważ jest też statyczna, może być wywołana bez obiektu klasy Cat. Zatem, w linii 41., funkcja TelepathicFunction() jest w stanie użyć publicznego statycznego akcesora, nie posiadając dostępu do obiektu klasy Cat. Oczywiście, moglibyśmy wywołać funkcję GetHowMany() dla [Author ID1: at Wed Oct 31 14:56:00 2001 ]obiektów klasy [Author ID1: at Wed Oct 31 14:56:00 2001 ]Cat dostępnych w funkcji main(), tak samo jak w przypadku innych akcesorów.

UWAGA Statyczne funkcje składowe nie posiadają wskaźnika this. W związku z tym nie mogą być deklarowane jako const. Ponadto, ponieważ zmienne składowe są dostępne w funkcjach składowych poprzez wskaźnik this, statyczne funkcje składowe nie mogą korzystać z żadnych zmiennych składowych, nie będących składowymi statycznymi!

Statyczne funkcje składowe

Możesz korzystać ze statycznych funkcji składowych, wywołując je dla obiektu klasy, tak samo jak w przypadku innych funkcji składowych, możesz też wywoływać je bez obiektu klasy, stosując pełną kwalifikowaną nazwę klasy i funkcji.

Przykład

class Cat

{

public:

static int GetHowMany() { return HowManyCats; }

private:

static int HowManyCats;

};

int Cat::HowManyCats = 0;

int main()

{

int howMany;

Cat theCat; // definiuje obiekt klasy Cat

howMany = theCat.GetHowMany(); // dostęp poprzez obiekt

howMany = Cat::GetHowMany(); // dostęp bez obiektu

}

Wskaźniki do funkcji

Nazwa tablicy jest wskaźnikiem const do pierwszego elementu tej tablicy, a nazwa funkcji jest wskaźnikiem const do funkcji. Istnieje możliwość zadeklarowania zmiennej wskaźnikowej wskazującej na funkcję i wywoływania tej funkcji za pomocą tej zmiennej. Może to być bardzo przydatne; umożliwia tworzenie programów, które decydują (na podstawie wprowadzonych danych) o tym, która funkcja ma zostać wywołana.

Jedyny problem ze wskaźnikami do funkcji polega na zrozumieniu typu wskazywanego obiektu. Wskaźnik do int wskazuje zmienną całkowitą, zaś wskaźnik do funkcji musi wskazywać funkcję o określonym zwracanym typie i sygnaturze.

W deklaracji:

long (* funcPtr) (int);

funcPtr jest deklarowane jako wskaźnik (zwróć uwagę na gwiazdkę przed nazwą), wskazujący funkcję otrzymującą parametr typu int i zwracającą wartość typu long. Nawiasy wokół * funcPtr są konieczne, gdyż nawiasy wokół int wiążą ściślej[Author ID1: at Wed Oct 31 14:56:00 2001 ]bardziej[Author ID1: at Wed Oct 31 14:57:00 2001 ]; tj. mają priorytet nad operatorem dostępu pośredniego (*). Bez zastosowania pierwszych nawiasów zostałaby zadeklarowana funkcja otrzymująca parametr typu int i zwracająca wskaźnik do typu long. (Pamiętaj, że spacje nie mają tu znaczenia.)

Przyjrzyjmy się dwóm poniższym deklaracjom:

long * Function (int);

long (* funcPtr) (int);

Pierwsza, Function(), jest funkcją otrzymującą parametr typu int i zwracającą wskaźnik do zmiennej typu long. Druga, funcPtr, jest wskaźnikiem do funkcji otrzymującej wartość typu int i zwracającej zmienną typu long.

Deklaracja wskaźnika do funkcji zawsze zawiera zwracany typ oraz, w nawiasach, typ parametrów (o ile występują). Listing 15.5 ilustruje deklarowanie i używanie wskaźników do funkcji.

Listing 15.5. Wskaźniki do funkcji

0: // Listing 15.5 Użycie wskaźników do funkcji.

1:

2: #include <iostream>

3: using namespace std;

4:

5: void Square (int&,int&); // do kwadratu

6: void Cube (int&, int&); // do trzeciej potegi

7: void Swap (int&, int &); // zamiana

8: void GetVals(int&, int&); // zmiana

9: void PrintVals(int, int);

10:

11: int main()

12: {

13: void (* pFunc) (int &, int &);

14: bool fQuit = false;

15:

16: int valOne=1, valTwo=2;

17: int choice;

18: while (fQuit == false)

19: {

20: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: ";

21: cin >> choice;

22: switch (choice)

23: {

24: case 1: pFunc = GetVals; break;

25: case 2: pFunc = Square; break;

26: case 3: pFunc = Cube; break;

27: case 4: pFunc = Swap; break;

28: default : fQuit = true; break;

29: }

30:

31: if (fQuit)

32: break;

33:

34: PrintVals(valOne, valTwo);

35: pFunc(valOne, valTwo);

36: PrintVals(valOne, valTwo);

37: }

38: return 0;

39: }

40:

41: void PrintVals(int x, int y)

42: {

43: cout << "x: " << x << " y: " << y << endl;

44: }

45:

46: void Square (int & rX, int & rY)

47: {

48: rX *= rX;

49: rY *= rY;

50: }

51:

52: void Cube (int & rX, int & rY)

53: {

54: int tmp;

55:

56: tmp = rX;

57: rX *= rX;

58: rX = rX * tmp;

59:

60: tmp = rY;

61: rY *= rY;

62: rY = rY * tmp;

63: }

64:

65: void Swap(int & rX, int & rY)

66: {

67: int temp;

68: temp = rX;

69: rX = rY;

70: rY = temp;

71: }

72:

73: void GetVals (int & rValOne, int & rValTwo)

74: {

75: cout << "Nowa wartosc dla ValOne: ";

76: cin >> rValOne;

77: cout << "Nowa wartosc dla ValTwo: ";

78: cin >> rValTwo;

79: }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1

x: 1 y: 2

Nowa wartosc dla ValOne: 2

Nowa wartosc dla ValTwo: 3

x: 2 y: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

x: 2 y: 3

x: 8 y: 27

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

x: 8 y: 27

x: 64 y: 729

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4

x: 64 y: 729

x: 729 y: 64

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza

W liniach od 5. do 8. zostały zadeklarowane cztery funkcje, wszystkie o tych samych zwracanych typach i sygnaturach, zwracające void i przyjmujące referencje do dwóch zmiennych całkowitych.

W linii 13. zmienna pFunc jest deklarowana jako wskaźnik do funkcji zwracającej void i przyjmującej referencje do dwóch zmiennych całkowitych. Wskaźnik pFunc może więc wskazywać na dowolną z zadeklarowanych wcześniej czterech funkcji. Użytkownik ma możliwość wyboru funkcji, która powinna zostać wybrana, a wskaźnik pFunc jest ustawiany zgodnie z tym wyborem. W liniach od 34. do 36. są wypisywane bieżące wartości obu zmiennych całkowitych, wywoływana jest aktualnie przypisana funkcja, po czym ponownie wypisywane są wartości obu zmiennych.

Wskaźnik do funkcji

Wskaźnik do funkcji jest wywoływany tak samo jak funkcja, którą wskazuje, z wyjątkiem tego, że zamiast nazwy funkcji, używana jest nazwa wskaźnika do tej funkcji.

Przypisanie wskaźnikowi konkretnej funkcji odbywa się przez przypisanie mu nazwy funkcji bez nawiasów. Nazwa funkcji jest wskaźnikiem const do samej funkcji. Wskaźnika do funkcji można więc używać tak samo, jak nazwy funkcji. Zwracany typ oraz sygnatura wskaźnika do funkcji muszą być zgodne z przypisywaną mu funkcją.

Przykład

long (*pFuncOne) (int, int);

long SomeFunction (int, int);

pFuncOne = SomeFunction;

pFuncOne(5,7);

Dlaczego warto używać wskaźników do funkcji?

Listing 15.5 z pewnością mógłby zostać napisany bez użycia wskaźników do funkcji, ale użycie takich wskaźników jawnie określa przeznaczenie programu: wybór funkcji z listy, a następnie jej wywołanie.

Listing 15.6 wykorzystuje prototypy i definicje funkcji z listingu 15.5, ale ciało programu nie korzysta ze wskaźników do funkcji. Przyjrzyj się różnicom dzielącym te dwa listingi.

Listing 15.6. Listing 15.5 przepisany bez używania wskaźników do funkcji

0: // Listing 15.6 bez wskaźników do funkcji

1:

2: #include <iostream>

3: using namespace std;

4:

5: void Square (int&,int&); // do kwadratu

6: void Cube (int&, int&); // do trzeciej potegi

7: void Swap (int&, int &); // zamiana

8: void GetVals(int&, int&); // zmiana

9: void PrintVals(int, int);

10:

11: int main()

12: {

13: bool fQuit = false;

14: int valOne=1, valTwo=2;

15: int choice;

16: while (fQuit == false)

17: {

18: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: ";

19: cin >> choice;

20: switch (choice)

21: {

22: case 1:

23: PrintVals(valOne, valTwo);

24: GetVals(valOne, valTwo);

25: PrintVals(valOne, valTwo);

26: break;

27:

28: case 2:

29: PrintVals(valOne, valTwo);

30: Square(valOne,valTwo);

31: PrintVals(valOne, valTwo);

32: break;

33:

34: case 3:

35: PrintVals(valOne, valTwo);

36: Cube(valOne, valTwo);

37: PrintVals(valOne, valTwo);

38: break;

39:

40: case 4:

41: PrintVals(valOne, valTwo);

42: Swap(valOne, valTwo);

43: PrintVals(valOne, valTwo);

44: break;

45:

46: default :

47: fQuit = true;

48: break;

49: }

50:

51: if (fQuit)

52: break;

53: }

54: return 0;

55: }

56:

57: void PrintVals(int x, int y)

58: {

59: cout << "x: " << x << " y: " << y << endl;

60: }

61:

62: void Square (int & rX, int & rY)

63: {

64: rX *= rX;

65: rY *= rY;

66: }

67:

68: void Cube (int & rX, int & rY)

69: {

70: int tmp;

71:

72: tmp = rX;

73: rX *= rX;

74: rX = rX * tmp;

75:

76: tmp = rY;

77: rY *= rY;

78: rY = rY * tmp;

79: }

80:

81: void Swap(int & rX, int & rY)

82: {

83: int temp;

84: temp = rX;

85: rX = rY;

86: rY = temp;

87: }

88:

89: void GetVals (int & rValOne, int & rValTwo)

90: {

91: cout << "Nowa wartosc dla ValOne: ";

92: cin >> rValOne;

93: cout << "Nowa wartosc dla ValTwo: ";

94: cin >> rValTwo;

95: }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1

x: 1 y: 2

Nowa wartosc dla ValOne: 2

Nowa wartosc dla ValTwo: 3

x: 2 y: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

x: 2 y: 3

x: 8 y: 27

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

x: 8 y: 27

x: 64 y: 729

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4

x: 64 y: 729

x: 729 y: 64

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza

Kusiło mnie, by umieścić wywołanie funkcji PrintVals() na początku i na końcu pętli while, a nie w każdej z instrukcji case. Spowodowałoby to jednak wywołanie tej funkcji także dla opcji wyjścia z programu, co nie zgadzałoby się z jego specyfikacją.

Poza wzrostem objętości kodu i powtórzonych wywołań tej samej funkcji, znacznie zmniejszyła się także ogólna przejrzystość programu. Ten przypadek został jednak stworzony w celu zilustrowania działania wskaźników do funkcji. W rzeczywistości zalety wskaźników do funkcji są jeszcze większe: wskaźniki do funkcji mogą eliminować powtórzenia kodu, sprawiają, że program staje się bardziej przejrzysty i umożliwiają stosowanie tablic funkcji wywoływanych w zależności od sytuacji powstałych podczas działania programu.

Skrócone wywołanie

Wskaźnik do funkcji nie musi być wyłuskiwany, choć oczywiście można przeprowadzić tę operację. Zatem, jeśli pFunc jest wskaźnikiem do funkcji przyjmującej wartość całkowitą i zwracającej zmienną typu long, i gdy do pFunc przypiszemy odpowiednią dla niego funkcję, możemy wywołać tę[Author ID1: at Wed Oct 31 14:57:00 2001 ]e[Author ID1: at Wed Oct 31 14:57:00 2001 ] funkcję, pisząc albo:

pFunc(x);

albo:

(*pFunc)(x);

Obie te formy działają identycznie. Pierwsza jest jedynie skróconą wersją drugiej.

Tablice wskaźników do funkcji

Można deklarować nie tylko tablice wskaźników do wartości całkowitych, ale również tablice wskaźników do funkcji zwracających określony typ i posiadających określoną sygnaturę. Listing 15.7 stanowi zmodyfikowaną wersję listingu 15.5, tym razem wykorzystującą tablicę do wywoływania wszystkich opcji naraz.

Listing 15.7. Demonstruje użycie tablicy wskaźników do funkcji

0: // Listing 15.7

1: //Demonstruje użycie tablicy wskaźników do funkcji

2:

3: #include <iostream>

4: using namespace std;

5:

6: void Square (int&,int&); // do kwadratu

7: void Cube (int&, int&); // do trzeciej potegi

8: void Swap (int&, int &); // zamiana

9: void GetVals(int&, int&); // zmiana

10: void PrintVals(int, int);

11:

12: int main()

13: {

14: int valOne=1, valTwo=2;

15: int choice, i;

16: const MaxArray = 5;

17: void (*pFuncArray[MaxArray])(int&, int&);

18:

19: for (i=0;i<MaxArray;i++)

20: {

21: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: ";

22: cin >> choice;

23: switch (choice)

24: {

25: case 1: pFuncArray[i] = GetVals; break;

26: case 2: pFuncArray[i] = Square; break;

27: case 3: pFuncArray[i] = Cube; break;

28: case 4: pFuncArray[i] = Swap; break;

29: default:pFuncArray[i] = 0;

30: }

31: }

32:

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

34: {

35: if ( pFuncArray[i] == 0 )

36: continue;

37: pFuncArray[i](valOne,valTwo);

38: PrintVals(valOne,valTwo);

39: }

40: return 0;

41: }

42:

43: void PrintVals(int x, int y)

44: {

45: cout << "x: " << x << " y: " << y << endl;

46: }

47:

48: void Square (int & rX, int & rY)

49: {

50: rX *= rX;

51: rY *= rY;

52: }

53:

54: void Cube (int & rX, int & rY)

55: {

56: int tmp;

57:

58: tmp = rX;

59: rX *= rX;

60: rX = rX * tmp;

61:

62: tmp = rY;

63: rY *= rY;

64: rY = rY * tmp;

65: }

66:

67: void Swap(int & rX, int & rY)

68: {

69: int temp;

70: temp = rX;

71: rX = rY;

72: rY = temp;

73: }

74:

75: void GetVals (int & rValOne, int & rValTwo)

76: {

77: cout << "Nowa wartosc dla ValOne: ";

78: cin >> rValOne;

79: cout << "Nowa wartosc dla ValTwo: ";

80: cin >> rValTwo;

81: }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

Nowa wartosc dla ValOne: 2

Nowa wartosc dla ValTwo: 3

x: 2 y: 3

x: 4 y: 9

x: 64 y: 729

x: 729 y: 64

x: 531441 y: 4096

Analiza

W linii 17. tablica pFuncArray jest deklarowana jako tablica pięciu wskaźników do funkcji zwracających void i przyjmujących po dwie referencje do wartości całkowitych.

W liniach od 19. do 31. użytkownik jest proszony o wybranie funkcji przeznaczonej do wywołania, po czym każdemu elementowi tablicy jest przypisywany adres odpowiedniej funkcji. W liniach od 33. do 39. wywoływane są funkcje wskazywane przez kolejne elementy tablicy. Po każdym wywołaniu wypisywane są wyniki.

Przekazywanie wskaźników do funkcji do innych funkcji[Author ID1: at Wed Oct 31 14:58:00 2001 ] innym funkcj[Author ID1: at Wed Oct 31 14:58:00 2001 ]om[Author ID1: at Wed Oct 31 14:58:00 2001 ]

Wskaźniki do funkcji (a także tablice wskaźników do funkcji) mogą być przekazywane do innych funkcji, które mogą wykonywać obliczenia i na podstawie ich wyniku, dzięki otrzymanym wskaźnikom, wywoływać odpowiednie funkcje.

Możemy na przykład poprawić listing 15.5, przekazując wskaźnik do wybranej funkcji do innej funkcji (poza main()), która wypisuje wartości, wywołuje funkcję i ponownie wypisuje wartości. Ten wariant działania przedstawia listing 15.8.

Listing 15.8. Przekazywanie wskaźników do funkcji jako argumentów funkcji

0: // Listing 15.8 Przekazywanie wskaźników do funkcji

1:

2: #include <iostream>

3: using namespace std;

4:

5: void Square (int&,int&); // do kwadratu

6: void Cube (int&, int&); // do trzeciej potegi

7: void Swap (int&, int &); // zamiana

8: void GetVals(int&, int&); // zmiana

9: void PrintVals(void (*)(int&, int&),int&, int&);

10:

11: int main()

12: {

13: int valOne=1, valTwo=2;

14: int choice;

15: bool fQuit = false;

16:

17: void (*pFunc)(int&, int&);

18:

19: while (fQuit == false)

20: {

21: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: ";

22: cin >> choice;

23: switch (choice)

24: {

25: case 1: pFunc = GetVals; break;

26: case 2: pFunc = Square; break;

27: case 3: pFunc = Cube; break;

28: case 4: pFunc = Swap; break;

29: default:fQuit = true; break;

30: }

31: if (fQuit == true)

32: break;

33: PrintVals ( pFunc, valOne, valTwo);

34: }

35:

36: return 0;

37: }

38:

39: void PrintVals( void (*pFunc)(int&, int&),int& x, int& y)

40: {

41: cout << "x: " << x << " y: " << y << endl;

42: pFunc(x,y);

43: cout << "x: " << x << " y: " << y << endl;

44: }

45:

46: void Square (int & rX, int & rY)

47: {

48: rX *= rX;

49: rY *= rY;

50: }

51:

52: void Cube (int & rX, int & rY)

53: {

54: int tmp;

55:

56: tmp = rX;

57: rX *= rX;

58: rX = rX * tmp;

59:

60: tmp = rY;

61: rY *= rY;

62: rY = rY * tmp;

63: }

64:

65: void Swap(int & rX, int & rY)

66: {

67: int temp;

68: temp = rX;

69: rX = rY;

70: rY = temp;

71: }

72:

73: void GetVals (int & rValOne, int & rValTwo)

74: {

75: cout << "Nowa wartosc dla ValOne: ";

76: cin >> rValOne;

77: cout << "Nowa wartosc dla ValTwo: ";

78: cin >> rValTwo;

79: }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1

x: 1 y: 2

Nowa wartosc dla ValOne: 2

Nowa wartosc dla ValTwo: 3

x: 2 y: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

x: 2 y: 3

x: 8 y: 27

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

x: 8 y: 27

x: 64 y: 729

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4

x: 64 y: 729

x: 729 y: 64

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza

W linii 17. zmienna pFunc zostaje zadeklarowana jako wskaźnik do funkcji zwracającej void i przyjmującej dwa parametry, [Author ID1: at Wed Oct 31 14:59:00 2001 ], [Author ID1: at Wed Oct 31 14:59:00 2001 ]będące [Author ID1: at Wed Oct 31 14:59:00 2001 ]referencjami[Author ID1: at Wed Oct 31 14:59:00 2001 ]e[Author ID1: at Wed Oct 31 14:59:00 2001 ] do wartości typu int. W linii 9. została zadeklarowana funkcja PrintVals przyjmująca trzy parametry. Pierwszym z nich jest wskaźnik do funkcji zwracającej void i przyjmującej dwie referencje do typu int, a drugim i trzecim są referencje do zmiennych [Author ID1: at Wed Oct 31 15:00:00 2001 ]wartości[Author ID1: at Wed Oct 31 15:00:00 2001 ] typu int. Także w tym programie użytkownik jest proszony o wybranie funkcji do wywołania, po czym w linii 33. wywoływana jest funkcja PrintVals.

Znajdź jakiegoś programistę C++ i zapytaj go, co oznacza poniższa deklaracja:

void PrintVals(void (*)(int&, int&), int&, int&);

Jest to niezbyt często używany rodzaj deklaracji; prawdopodobnie zajrzysz do książki za każdym razem, gdy spróbujesz z niej skorzystać. Może ona jednak ocalić twój [Author ID1: at Wed Oct 31 15:00:00 2001 ]program w tych rzadkich przypadkach, gdy niezbędna[Author ID1: at Wed Oct 31 15:00:00 2001 ] będzie[Author ID1: at Wed Oct 31 15:00:00 2001 ] taka właśnie [Author ID1: at Wed Oct 31 15:00:00 2001 ]będzie dokładnie tą[Author ID1: at Wed Oct 31 15:00:00 2001 ]konstrukcja[Author ID1: at Wed Oct 31 15:00:00 2001 ]ą[Author ID1: at Wed Oct 31 15:00:00 2001 ].[Author ID1: at Wed Oct 31 15:00:00 2001 ], jaka jest wymagana.[Author ID1: at Wed Oct 31 15:01:00 2001 ]

Użycie instrukcji typedef ze wskaźnikami do funkcji

Konstrukcja void (*)(int&, int&) jest co najmniej niezrozumiała. Aby ją uprościć, możemy użyć instrukcji typedef, deklarując typ (w tym przypadku nazwiemy go VPF) jako wskaźnik do funkcji zwracającej void i przyjmującej dwie referencje do typu int. Listing 15.9 stanowi nieco zmodyfikowaną wersję listingu 15.8. w którym zastosowano instrukcję typedef.

Listing 15.9. Użycie instrukcji typedef w celu uproszczenia deklaracji wskaźnika do funkcji

0: // Listing 15.9.

1: // Użycie typedef w celu uproszczenia deklaracji wskaźnika do funkcji

2:

3: #include <iostream>

4: using namespace std;

5:

6: void Square (int&,int&); // do kwadratu

7: void Cube (int&, int&); // do trzeciej potegi

8: void Swap (int&, int &); // zamiana

9: void GetVals(int&, int&); // zmiana

10: typedef void (*VPF) (int&, int&) ;

11: void PrintVals(VPF,int&, int&);

12:

13: int main()

14: {

15: int valOne=1, valTwo=2;

16: int choice;

17: bool fQuit = false;

18:

19: VPF pFunc;

20:

21: while (fQuit == false)

22: {

23: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: ";

24: cin >> choice;

25: switch (choice)

26: {

27: case 1: pFunc = GetVals; break;

28: case 2: pFunc = Square; break;

29: case 3: pFunc = Cube; break;

30: case 4: pFunc = Swap; break;

31: default:fQuit = true; break;

32: }

33: if (fQuit == true)

34: break;

35: PrintVals ( pFunc, valOne, valTwo);

36: }

37: return 0;

38: }

39:

40: void PrintVals( VPF pFunc,int& x, int& y)

41: {

42: cout << "x: " << x << " y: " << y << endl;

43: pFunc(x,y);

44: cout << "x: " << x << " y: " << y << endl;

45: }

46:

47: void Square (int & rX, int & rY)

48: {

49: rX *= rX;

50: rY *= rY;

51: }

52:

53: void Cube (int & rX, int & rY)

54: {

55: int tmp;

56:

57: tmp = rX;

58: rX *= rX;

59: rX = rX * tmp;

60:

61: tmp = rY;

62: rY *= rY;

63: rY = rY * tmp;

64: }

65:

66: void Swap(int & rX, int & rY)

67: {

68: int temp;

69: temp = rX;

70: rX = rY;

71: rY = temp;

72: }

73:

74: void GetVals (int & rValOne, int & rValTwo)

75: {

76: cout << "Nowa wartosc dla ValOne: ";

77: cin >> rValOne;

78: cout << "Nowa wartosc dla ValTwo: ";

79: cin >> rValTwo;

80: }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1

x: 1 y: 2

Nowa wartosc dla ValOne: 2

Nowa wartosc dla ValTwo: 3

x: 2 y: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

x: 2 y: 3

x: 8 y: 27

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2

x: 8 y: 27

x: 64 y: 729

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4

x: 64 y: 729

x: 729 y: 64

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza

W linii 10. instrukcja typedef została użyta do zadeklarowania VPF jako typu „wskaźnik do funkcji zwracającej void i przyjmującej dwa parametry w postaci referencji do wartości typu int”.

W linii 11. została zadeklarowana funkcja PrintVals() przyjmująca trzy parametry: VPF i dwie referencje do wartości typu int. W tym programie wskaźnik pFunc został zadeklarowany jako zmienna typu VPF (w linii 19.).

Po zdefiniowaniu typu VPF wszystkie następne konstrukcje z użyciem pFunc i PrintVals() stają się dużo bardziej przejrzyste. Jak widać, działanie programu nie ulega zmianie.

Wskaźniki do funkcji składowych

Do tej pory wszystkie wskaźniki do funkcji odnosiły się do funkcji ogólnych, nie będących funkcjami składowymi. Istnieje jednak możliwość tworzenia wskaźników do funkcji, będących składowymi klas.

Aby stworzyć wskaźnik do funkcji składowej, musimy użyć tej samej składni, jak w przypadku zwykłego wskaźnika do funkcji, ale z zastosowaniem nazwy klasy i operatora zakresu (::). Jeśli pFunc ma wskazywać na funkcję składową klasy Shape zwracającą void i przyjmującą dwie wartości całkowite, wtedy deklaracja tej zmiennej powinna wyglądać następująco:

void (Shape::*pFunc) (int, int);

Wskaźniki do funkcji składowych są używane tak samo, jak wskaźniki do zwykłych funkcji, z tym że w celu wywołania ich potrzebny jest obiekt właściwej klasy. Listing 15.10 ilustruje użycie wskaźników do funkcji składowych.

Listing 15.10. Wskaźniki do funkcji składowych

0: //Listing 15.10 Wskaźniki do funkcji składowych

1:

2: #include <iostream>

3: using namespace std;

4:

5: class Mammal

6: {

7: public:

8: Mammal():itsAge(1) { }

9: virtual ~Mammal() { }

10: virtual void Speak() const = 0;

11: virtual void Move() const = 0;

12: protected:

13: int itsAge;

14: };

15:

16: class Dog : public Mammal

17: {

18: public:

19: void Speak()const { cout << "Hau!\n"; }

20: void Move() const { cout << "Gonie w pietke...\n"; }

21: };

22:

23:

24: class Cat : public Mammal

25: {

26: public:

27: void Speak()const { cout << "Miau!\n"; }

28: void Move() const { cout << "Skradam sie...\n"; }

29: };

30:

31:

32: class Horse : public Mammal

33: {

34: public:

35: void Speak()const { cout << "Ihaaa!\n"; }

36: void Move() const { cout << "Galopuje...\n"; }

37: };

38:

39:

40: int main()

41: {

42: void (Mammal::*pFunc)() const =0;

43: Mammal* ptr =0;

44: int Animal;

45: int Method;

46: bool fQuit = false;

47:

48: while (fQuit == false)

49: {

50: cout << "(0)Wyjscie (1)pies (2)kot (3)kon: ";

51: cin >> Animal;

52: switch (Animal)

53: {

54: case 1: ptr = new Dog; break;

55: case 2: ptr = new Cat; break;

56: case 3: ptr = new Horse; break;

57: default: fQuit = true; break;

58: }

59: if (fQuit)

60: break;

61:

62: cout << "(1)Speak! (2)Move: ";

63: cin >> Method;

64: switch (Method)

65: {

66: case 1: pFunc = Mammal::Speak; break;

67: default: pFunc = Mammal::Move; break;

68: }

69:

70: (ptr->*pFunc)();

71: delete ptr;

72: }

73: return 0;

74: }

Wynik

(0)Wyjscie (1)pies (2)kot (3)kon: 1

(1)Speak (2)Move: 1

Hau!

(0)Wyjscie (1)pies (2)kot (3)kon: 2

(1)Speak (2)Move: 1

Miau!

(0)Wyjscie (1)pies (2)kot (3)kon: 3

(1)Speak (2)Move: 2

Galopuje...

(0)Wyjscie (1)pies (2)kot (3)kon: 0

Analiza

W liniach od 5. do 14. została zadeklarowana abstrakcyjna klasa Mammal, zawierająca dwie czyste metody wirtualne: Speak() (daj głos) oraz Move() (ruszaj się). Z klasy Mammal zostały wyprowadzone klasy Dog, Cat i Horse, z których każda przesłania metody Speak() i Move().

Program sterujący (funkcja main()) prosi użytkownika o wybranie zwierzęcia, które ma zostać stworzone, po czym w liniach od 54. do 56. na stercie tworzony jest nowy obiekt klasy pochodnej; jego adres zostaje przypisany wskaźnikowi ptr.

Następnie użytkownik jest proszony o wybranie metody, która ma zostać wywołana; wybrana metoda jest przypisywana do wskaźnika pFunc. W linii 70. wywoływana jest metoda wybrana dla stworzonego wcześniej obiektu. Wywoływana jest ona poprzez użycie wskaźnika ptr (w celu uzyskania dostępu do obiektu) i wskaźnika pFunc (w celu wywołania jego metody).

Na zakończenie, w linii 71., za pomocą operatora delete zostaje usunięty obiekt wskazywany przez ptr (w celu zwolnienia pamięci na stercie). Zauważ, że nie ma powodu wywoływania delete dla wskaźnika pFunc, gdyż jest to wskaźnik do kodu, a nie do obiektu na stercie. W rzeczywistości taka próba zakończyłaby się wypisaniem błędu kompilacji.

Tablice wskaźników do funkcji składowych

Podobnie jak w przypadku wskaźników do zwykłych funkcji, w tablicach można przechowywać także wskaźniki do funkcji składowych. Tablica może zostać zainicjalizowana adresami różnych funkcji składowych, które potem mogą być wywoływane dla poszczególnych elementów tablicy. Technikę tę ilustruje listing 15.11.

Listing 15.11. Tablica wskaźników do funkcji składowych

0: //Listing 15.11 Tablica wskaźników do funkcji składowych

1:

2: #include <iostream>

3: using std::cout;

4:

5: class Dog

6: {

7: public:

8: void Speak()const { cout << "Hau!\n"; }

9: void Move() const { cout << "Gonie w pietke...\n"; }

10: void Eat() const { cout << "Jem...\n"; }

11: void Growl() const { cout << "Warcze\n"; }

12: void Whimper() const { cout << "Wyje...\n"; }

13: void RollOver() const { cout << "Tarzam sie...\n"; }

14: void PlayDead() const { cout << "Koniec Malego Cezara?\n"; }

15: };

16:

17: typedef void (Dog::*PDF)()const ;

18: int main()

19: {

20: const int MaxFuncs = 7;

21: PDF DogFunctions[MaxFuncs] =

22: {Dog::Speak,

23: Dog::Move,

24: Dog::Eat,

25: Dog::Growl,

26: Dog::Whimper,

27: Dog::RollOver,

28: Dog::PlayDead };

29:

30: Dog* pDog =0;

31: int Method;

32: bool fQuit = false;

33:

34: while (!fQuit)

35: {

36: cout << "(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz";

37: cout << " (5)Wyj (6)Tarzaj sie (7)Zdechl pies: ";

38: std::cin >> Method;

39: if (Method == 0)

40: {

41: fQuit = true;

42: }

43: else

44: {

45: pDog = new Dog;

46: (pDog->*DogFunctions[Method-1])();

47: delete pDog;

48: }

49: }

50: return 0;

51: }

Wynik

(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 1

Hau!

(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 4

Warcze

(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 7

Koniec Malego Cezara?

(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 0

Analiza

W liniach od 5. do 15. została stworzona klasa Dog, zawierająca siedem funkcji składowych, każdą o tym samym zwracanym typie i sygnaturze. W linii 17. instrukcja typedef deklaruje PDF jako wskaźnik do funkcji składowej klasy Dog, która nie przyjmuje żadnych parametrów i nie zwraca żadnej wartości, a ponadto jest funkcją const — typ ten jest zgodny z sygnaturą wszystkich siedmiu funkcji składowych klasy Dog.

W liniach od 21. do 28. została zadeklarowana tablica DogFunctions, przechowująca wskaźniki do siedmiu funkcji składowych; jest ona inicjalizowana adresami tych funkcji.

W liniach 36[Author ID1: at Wed Oct 31 15:02:00 2001 ].7[Author ID1: at Wed Oct 31 15:02:00 2001 ] i 37[Author ID1: at Wed Oct 31 15:02:00 2001 ].8[Author ID1: at Wed Oct 31 15:02:00 2001 ] użytkownik jest proszony o wybranie metody. Do momentu wybrania opcji Wyjście [Author ID1: at Wed Oct 31 15:02:00 2001 ], [Author ID1: at Wed Oct 31 15:02:00 2001 ]na stercie za każdym razem jest tworzony obiekt klasy Dog, następnie w linii 46. jest dla niego wywoływana odpowiednia funkcja z tablicy. Oto kolejna linia, którą warto pokazać zarozumiałym programistom z twojej firmy; zapytaj ich, do czego służy:

(pDog->*DogFunctions[Method-1])();

Także ta konstrukcja może wydawać się niezrozumiała, ale zbudowana ze wskaźników do funkcji składowych tablica może znacznie ułatwić konstruowanie i analizę programu.

TAK

NIE

Wywołuj wskaźniki do funkcji składowych dla konkretnych obiektów klasy.

W celu uproszczenia deklaracji wskaźników do funkcji składowych używaj instrukcji typedef.

Nie używaj wskaźników do funkcji składowych, gdy można zastosować prostsze rozwiązanie.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 F:\korekta\r15-06.doc[Author ID2: at Mon Nov 26 09:30:00 2001 ]C:\Moje dokumenty\jr\doc\Korekt_rzeczo\2\Kopia r15-05.doc[Author ID2: at Mon Nov 26 09:30:00 2001 ]



Wyszukiwarka

Podobne podstrony:
43 A 1926 1931 I pol XX wie Nieznany
Pius XI 1931 05 15 Encyklika Quadragesimo Anno 1
1931 12 25 Lux Veritatis temporumque testis
1931
1931-1935, Pomoce naukowe itp, Nogroda Nobla
1931
1931
współczesne teorie podświadomości(1931) - A.Dryjski - referat
43 B 1926 1931 I pol XX wiek Nieznany
ENCYKLIKA 1931 Pius XI Quadragesimo Anno
NSU cennik 1931
1999 09 09 1931
1931
książka z 1931 r., hist - drobne, różne
1931 OtteCelPedroJoseCostaBarros
1931 ParaaHistoriadeSobral
1931 MonsenhorBrunoFigueiredo

więcej podobnych podstron