На этом шаге мы рассмотрим создание и использование массивов указателей на функции.
При определении указатель на функцию может быть инициализирован. В качестве инициализирующего значения должен использоваться адрес функции, тип и сигнатура которой соответствуют определяемому указателю.
При присваивании указателей на функции также необходимо соблюдать соответствие типов возвращаемых значений функций и сигнатур для указателей правой и левой частей оператора присваивания. То же справедливо и при последующем вызове функций с помощью указателей, т.е. типы и количество фактических параметров, используемых при обращении к функции по адресу, должны соответствовать формальным параметрам вызываемой функции. Например, только некоторые из следующих операторов будут допустимы:
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 - выход из программы.
На следующем шаге мы закончим рассматривать указатели на функции.