Передача указателей на функции в другие функции

Указатели на функции (или массивы указателей) могут передаваться в другие функции для вызова в них с помощью указателя нужной функции.

Листинг 14.5 можно усовершенствовать, передав указатель на выбранную функцию другой функции (кроме main()), которая выведет исходные значения на печать, вызовет функцию и вновь напечатает измененные значения. Именно такой подход применен в листинге 14.8.

Листинг 14.8. Передана указателя на функцию другой функции

1: // Листинг 14.8. Передача указателя на функцию другой функции

2:

3: #include <iostream.h>

4:

5: void Square (int&,int&);

6: void Cube (int&, int&);

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

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

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

10:

11: int main()

12: {

13: int val0ne=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)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";

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& у)

40: {

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

42: pFunc(x,у);

43: cout << "x: " << x << " у: " << у << 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 << "New value for ValOne: ";

76: cin >> rValOne;

77: cout << "New value for ValTwo: ";

78: cin >> rValTwo;

79: }

Результат:

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1

x: 1 у: 2

New value for Val0ne: 2

New value for ValTwo: 3

x: 2 у: 3

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3

x: 2 у: 3

x: 8 у: 27

(O)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2

x: 8 у: 27

x: 64 у: 729

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4

x: 64 у: 729

x: 729 y:64

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Анализ: В строке 17 объявляется указатель на функцию pFunc, принимающую две ссылки на int и возвращающую void. Функция PrintVals, для которой задается три параметра, объявляется в строке 9. Первым в списке параметров стоит указатель на функцию, возвращающую void и принимающую две ссылки на int. Второй и третий параметры функции PrintVals представляют собой ссылки на значения типа int. После того как пользователь выберет нужную функцию, в строке 33 происходит вызов функции PrintVals.

Спросите у знакомого программиста, работающего с C++, что означает следующее выражение:

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

Это вид объявлений, который используется крайне редко и заставляет программистов обращаться к книгам каждый раз, когда нечто подобное встречается в тексте. Но временами данный подход позволяет значительно усовершенствовать код программы, как в нашем примере.

Использование typedef с указателями на функции

Конструкция void (*)(int&, int&) весьма громоздка. Для ее упрощения можно воспользоваться ключевым словом typedef, объявив новый тип (назовем его VPF) указателей на функции, возвращающие void и принимающие две ссылки на значения типа int. Листинг 14.9 представляет собой переписанную версию листинга 14.8 с использованием этого подхода.

Листинг 14.8. Использование оператора typedef для объявления типа указателей на функции

1: // Листинг 14.9. Использование typedef для

2: // объявления типа указателей на функции

3: #include <iostream.h>

4:

5: void Square (int&,int&);

6: void Cube (int&, int&);

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

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

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

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

11:

12: int main()

13: {

14: int val0ne=1, valTwo=2;

15: int choice;

16: bool fQuit = false;

17:

18: VPF pFunc;

19:

20: while (fQuit == false)

21: {

22: cout << "(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";

23: cin >> choice;

24: switch (choice)

25: {

26: case 1:pFunc = GetVals; break;

27: case 2:pFunc = Square; break;

28: case 3:pFunc = Cube; break;

29: case 4:pFunc = Swap; break;

30: default:fQuit = true; break;

31: }

32: if (fQuit == true)

33: break;

34: PrintVals ( pFunc, valOne, valTwo);

35: }

36: return 0;

37: }

38:

39: void PrintVals( VPF pFunc,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 << "New value for ValOne: ";

76: cin >> rValOne;

77: cout << "New value for ValTwo: ";

78: cin >> rValTwo;

79: }

Результат:

(0)Quit (1 )Change Values (2)Square (3)Cube (4)Swap: 1

x: 1 y: 2

New value for ValOne: 2

New value for ValTwo: 3

x: 2 y: 3

(0)Quit (1 )Change Values (2)Square (3)Cube (4)Swap: 3

x: 2 y: 3

x: 8 y: 27

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2

x: 8 y: 27

x: 64 y: 729

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4

x: 64 y: 729

x: 729 y: 64

(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Анализ: В строке 9 с помощью оператора typedef объявляется новый тип VPF как указатели на функции, возвращающие void и принимающие две ссылки на int. В строке 10 объявляется функция PrintVals(), которая принимает три параметра: VPF и две ссылки на integer. В строке 18 указатель Pfunc объявляется как принадлежащий TnnyVPF.

После объявления типа VPF дальнейшее использование указателя pFunc и функции PrintVals() становится проще и понятнее. Информация, выводимая программой на экран, не изменилась.

Указатели на функции члены

До настоящего времени все создаваемые указатели на функции использовались для общих функций, не принадлежащих к какому-нибудь одному классу. Однако разрешается создавать указатели и на функции, являющиеся членами классов (методы).

Для создания такого указателя используется тот же синтаксис, что и для указателя на обычную функцию, но с добавлением имени класса и оператора области видимости (::). Таким образом, объявление указателя pFunc на функции-члены класса Shape, принимающие два целочисленных параметра и возвращающие void, выглядит следующим образом:

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

Указатели на функции-члены используются так же, как и рассмотренные ранее указатели простых функции. Единственное отличие состоит в том, что для вызова функции необходимо наличие объекта соответствующего класса, для которого вызываются функции. В листинге 14.10 показано использование указателя на метод класса.

Листинг 14.10. Указатели на функции-члены

1: //Листинг 14.10. Указатели на виртуальные функции-члены

2:

3: #include <iostream.h>

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 << "Woof!\n"; }

20: void Move() const { cout << "Walking to heel...\n"; }

21: };

22:

23:

24: class Cat : public Mammal

25: {

26: public:

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

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

29: };

30:

31:

32: class Horse : public Mammal

33: {

34: public:

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

36: void Move() const 1 cout << "Galloping...\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)Quit (1)dog (2)cat (3)horse

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

Результат:

(0)Quit (1)dog (2)cat (3)horse: 1

(1)Speak (2)Move: 1

Woof!

(0)Quit (1)dog (2)cat (3)horse: 2

(1)Speak (2)Move: 1

Meow!

(0)Quit (1)dog (2)cat (3)horse: 3

(1)Speak (2)Move: 2

Galloping

(0)Quit (1)dog (2)cat (3)horse: 0

Анализ: В строках 4—14 объявляется тип абстрактных данных Mammal с двумя чистыми виртуальными методами Speak() и Move(). От класса Mammal производятся подклассы Dog, Cat и Horse, в каждом из которых замещаются соответствующим образом функции Speak() и Move().

В процессе выполнения тела функции main() пользователю предлагается выбрать животное, после чего в области динамического обмена создается новый подкласс выбранного животного, адрес которого присваивается в строках 54—56 указателю ptr.

Затем пользователь выбирает метод, который связывается с указателем pFunc. В строке 70 выбранный метод вызывается для созданного объекта посредством предоставления доступа к объекту с помощью указателя ptr и к функции с помощью указателя pFunc.

Наконец, строкой 71 для указателя ptr вызывается функция delete, которая очищает область памяти, занятую созданным ранее объектом. Заметьте, что нет смысла вызывать delete для pFunc, поскольку последний является указателем на код, а не на объект в области памяти. Хотя даже при попытке сделать это вы получите сообщение об ошибке компиляции.

Наши рекомендации