Шаг 2.
Указатели на функции (продолжение)

    На этом шаге мы рассмотрим создание и использование массивов указателей на функции.

    При определении указатель на функцию может быть инициализирован. В качестве инициализирующего значения должен использоваться адрес функции, тип и сигнатура которой соответствуют определяемому указателю.

    При присваивании указателей на функции также необходимо соблюдать соответствие типов возвращаемых значений функций и сигнатур для указателей правой и левой частей оператора присваивания. То же справедливо и при последующем вызове функций с помощью указателей, т.е. типы и количество фактических параметров, используемых при обращении к функции по адресу, должны соответствовать формальным параметрам вызываемой функции. Например, только некоторые из следующих операторов будут допустимы:

char f1(char) {...}       // Определение функции.
char f2(int) {...}        // Определение функции.
void f3(float) {...}      // Определение функции.
int* f4(char *){...}     // Определение функции.
char (*pt1)(int);        // Указатель на функцию.
char (*pt2)(int);         // Указатель на функцию.
void (*ptr3)(float) = f3; // Инициализированный указатель.
void main()
{ 
    pt1 = f1;  // Ошибка - несоответствие сигнатур.
    pt2 = f3;  // Ошибка - несоответствие типов (значений и сигнатур).
    pt1 = f4;  // Ошибка - несоответствие типов.
    pt1 = f2;  // Правильно.
    pt2 = pt1; // Правильно.
    char с = (*pt1)(44); // Правильно.
    с = (*pt2)('\t');    // Ошибка - несоответствие сигнатур.
}

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

//RAZN2_1.СРР - вызов функций по адресам через указатель.
#include <iostream.h>
// Функции одного типа с одинаковыми сигнатурами:
int add(int n, int m) { return n + m; }
int div(int n, int m) { return n/m; }
int mult(int n, int m) { return n * m; }
int subt(int n, int m) { return n - m; }
void main()
{ 
  int (*par)(int, int); // Указатель на функцию.
  int a =  6, b = 2; 
  char c = '+';
  while   (c != ' ')
  {
	 cout << "\n Аргументы: a = " << a <<", b = " << b;
	 cout << ". Результат для с = \'" << c << "\'"  <<
			" равен ";
	 switch (c)
	 {
		case '+': par = add;  c = '/'; break;
		case '-': par = subt; c = ' '; break;
		case '*': par = mult; c = '-'; break;
		case '/': par = div;  c = '*'; break;
	 }
	 cout << (a = (*par)(a,b)); //Вызов  по  адресу.
  }
}
Текст этой программы можно взять здесь.

    Результаты выполнения программы:

    Аргументы: а  =  6,  b  =  2. Результат для  с = '+'  равен  8
    Аргументы: а  =  8,  b  =  2. Результат для  с = '/'   равен  4
    Аргументы: а  =  4,  b  =  2. Результат для  с = '*'  равен  8
    Аргументы: а  =  8,  b  =  2. Результат для  с = '-'   равен  б

    Цикл продолжается, пока значением переменной c не станет пробел. В каждой итерации указатель par получает адрес одной из функций, и изменяется значение c. По результатам программы легко проследить порядок выполнения ее операторов.

    Указатели на функции могут быть объединены в массивы. Например, float (*ptrArray) (char) [4]; - описание массива с именем ptrArray из четырех указателей на функции, каждая из которых имеет параметр типа char и возвращает значение типа float. Чтобы обратиться, например, к третьей из этих функций, потребуется такой оператор:

    float а = (*ptrArray[2])('f');

    Как обычно, индексация массива начинается с 0, и поэтому третий элемент массива имеет индекс 2.

    Приведенное описание массива ptrArray указателей на функции не особенно наглядное и ясное. Для функций с большим количеством параметров и сложным описанием возвращаемых значений оно может стать и вовсе непонятным. Ситуация особенно усложняется, когда такое описание типа должно входить в более сложные описания, например, когда указатели функций и их массивы используются в качестве параметров других функций. Для удобства последующих применений и сокращения производных описаний рекомендуется с помощью спецификатора typedef вводить имя типа указателя на функции:

    typedef float   (*PTF)(float);
    typedef char  *(*PTC)(char);
    typedef void   (*PTFUNC)(PTF,   int,   float);

    Здесь PTF - имя типа "указатель на функцию с параметром типа float, возвращающую значение типа float". PTC - имя типа "указатель на, функцию с параметром типа char, возвращающую указатель на тип char". PTFUNC - имя типа "указатель на функцию, возвращающую пустое значение (типа void)". Параметрами для этой функции служат: PTF - указатель на функцию float имя (float), выражение типа int и выражение типа float. (В определение имени типа PTFUNC вошел только что определенный тип с именем PTF.)

    Введя имена типов указателей на функции, проще описать соответствующие указатели, массивы и другие производные типы:

    PTF ptfloat1,  ptfloat2[5];  // Указатель и массив указателей 
                                 // на  функции float имя(float).
    РТС ptchar;    //  Указатель на функцию char  *(char).
    PTFUNC ptfunc[8];   // Массив указателей на функции.

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

//RAZN2_2.СРР - массив указателей на функции.
#include <stdlib.h>   // Для exit().
#include <iostream.h> // Для cout, cin.
// Определение функций для обработки меню:
void act1 (char* name)
{ cout <<"Действия по созданию файла " << name; } 
void act2 (char* name)
{ cout << "Действия по уничтожению файла " << name; }
void act3 (char* name)
{ cout << "Действия no чтению файла " << name; } 
void act4 (char* name)
{ cout << "Действия по модификации файла " << name; } 
void act5 (char* name) 
{ cout << "Действия no закрытию файла.";
  exit(0); // Завершить программу.
}
// Тип MENU указателей на функции типа void (char *): 
typedef void(*MENU)(char *);
// Инициализация таблицы адресов функций меню: 
MENU MenuAct[5] = { act1, act2, act3, act4, act5 }; 
void main()
{ 
  int number;  // Номер выбранного пункта меню.
  char FileName[30];  // Строка для имени файла.
  cout << "\n 1 - создание файла";
  cout << "\n 2 - уничтожение файла";
  cout << "\n 3 - чтение файла";
  cout << "\n 4 - модификация файла";
  cout << "\n 5 - выход из программы";
  while (1)  // Бесконечный цикл.
  { 
    while (1)
    { // Цикл продолжается до ввода правильного номера. 
      cout << "\n\nВведите номер пункта меню: "; 
      cin >> number;
      if (number >= 1 && number <= 5) break; 
          cout << "\nОшибка в номере пункта меню!"; 
    } 
    if (number != 5)
      { cout << "Введите имя файла: ";
        cin >> FileName; // Читать имя файла. 
      }
    // Вызов функции по указателю на нее:
   (*MenuAct[number-1])(FileName);
  } // Конец бесконечного цикла.
}
Текст этой программы можно взять здесь.

    При выполнении программы возможен, например, такой диалог:

    1 - создание файла
    2 - уничтожение файла
    3 - чтение файла
    4 - модификация файла
    5 - выход из программы
    Введите номер пункта меню: 3 <Enter>
    Введите имя файла:   PROBA.TXT <Enter> 
   Действия; по чтению файла PROBA.ТХТ
    .    .    .   .
   Введите номер пункта меню:   5 <Enter> 
  Действия  по закрытию файла.

    Пункты меню повторяются, пока не будет введен номер 5 - выход из программы.

    На следующем шаге мы закончим рассматривать указатели на функции.




Предыдущий шаг Содержание Следующий шаг