Главная » Статьи » Програмування » C [ Добавить статью ]

RUS Уроки по программированию на языке С (Указатели на функции.)

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

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

А теперь путём последовательности утверждений придем к обсуждению темы данного раздела урока.

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

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

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

4. Указатель на функцию определяется следующим образом:

 тип_функции (*имя_указателя)(спецификация_параметров);

Например: int (*func1Ptr) (char); - определение указателя func1Ptr на функцию с параметром типа char, возвращающую значение типа int.

Примечание: Будьте внимательны!!! Если приведенную синтаксическую конструкцию записать без первых круглых скобок, т.е. в виде int *fun (char); то компилятор воспримет ее как прототип некой функции с именем fun и параметром типа char, возвращающей значение указателя типа int *.

Второй пример: char * (*func2Ptr) (char * ,int); - определение указателя func2Ptr на функцию с параметрами типа указатель на char и типа int, возвращающую значение типа указатель на char.

Иллюстрируем на практике.

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

#include <iostream>
using namespace std;
void f1(void) // Определение f1.
{ 
 cout << "Load f1()\n";
 }
void f2(void) // Определение f2.
{
 cout << "Load f1()\n";
}
void main()
{ 
 void (*ptr)(void); // ptr - указатель на функцию.
 ptr = f2; // Присваивается адрес f2().
 (*ptr)(); // Вызов f2() по ее адресу.
 ptr = f1; // Присваивается адрес f1().
 (*ptr)(); // Вызов f1() по ее адресу.
 ptr(); // Вызов эквивалентен (*ptr)();
}
Результат выполнения программы: 

 Load f2()
 Load f1()
 Load f1()
 Press any key to continue

Здесь значением имени_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr();. Дело в том, что операция () имеет более высокий приоритет, нежели операция обращения по адресу *. Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.

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

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

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'); // Ошибка - несоответствие сигнатур.
}

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

#include <iostream>
using namespace std;
// Функции одного типа с одинаковыми сигнатурами:
int add(int n, int m) { return n + m; }
int division(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 Arguments: a = " << a <<", b = " << b;
 cout << ". Result for c = \'" << c << "\':";

 switch (c)
 {
 case '+': 
 par = add;
 c = '/';
 break;
 case '-': 
 par = subt; 
 c = ' '; 
 break;
 case '*': 
 par = mult; 
 c = '-'; 
 break;
 case '/': 
 par = division;
 c = '*'; 
 break;
 }
 cout << (a = (*par)(a,b))<<"\n"; //Вызов по адресу.
 }
}
Результаты выполнения программы: 

 
 Arguments: a = 6, b = 2. Result for c = '+':8

 Arguments: a = 8, b = 2. Result for c = '/':4

 Arguments: a = 4, b = 2. Result for c = '*':8

 Arguments: a = 8, b = 2. Result for c = '-':6
 Press any key to continue

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

Массивы указателей на функции.

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

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

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

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

#include <iostream> 
using namespace std;

/* Определение функций для обработки меню
(функции фиктивны т. е. реальных действий не выполняют):*/

void act1 (char* name)
{
 cout <<"Create file..." << name; 
} 
void act2 (char* name)
{ 
 cout << "Delete file... " << name; 
}
void act3 (char* name)
{ 
 cout << "Read file... " << name; 
} 
void act4 (char* name)
{ 
 cout << "Mode file... " << name; 
} 
void act5 (char* name) 
{ 
 cout << "Close file... " << name; 
}
 
void main()
{ 
 // Создание и инициализация массива указателей 
 void (*MenuAct[5])(char*) = {act1, act2, act3, act4, act5};

 int number; // Номер выбранного пункта меню.
 char FileName[30]; // Строка для имени файла.

 // Реализация меню
 cout << "\n 1 - Create";
 cout << "\n 2 - Delete";
 cout << "\n 3 - Read";
 cout << "\n 4 - Mode";
 cout << "\n 5 - Close";

 while (1) // Бесконечный цикл.
 { 
 while (1)
 { // Цикл продолжается до ввода правильного номера. 
 cout << "\n\nSelect action: "; 
 cin >> number;
 if (number>>= 1 && number <= 5) break; 
 
 cout << "\nError number!"; 
 } 
 if (number != 5)
 { 
 cout << "Enter file name: ";
 cin >> FileName; // Читать имя файла. 
 }
 else break;
 // Вызов функции по указателю на нее:
 (*MenuAct[number-1])(FileName);
 } // Конец бесконечного цикла.
}

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


Категория: C | Добавил: DEN-SHP (05.11.2012)
Просмотров: 702 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]