Основными характеристиками функции является тип возвращаемого значения и список типов формальных параметров. Подобно тому, как имена переменных никаким образом не влияют на их тип, имена функций не является частью их типа. Тип функции определяется типом возвращаемого значения и списком типов её формальных параметров. Например, пара функций
char MyF1 (int, int, int*, float); char MyNew (int MyP1, int MyP2, int* MyP3, float MyP3);
имеют один и тот же тип:
char (int, int, int*, float)
Подобную конструкцию мы назовём описанием типа функции.
А вот как выглядит описание типа функции, которая возвращает указатель на объект типа char:
char * (int, int, int*, float)
Описанию этого типа соответствует, например, функция
char *MyFp (int MyP1, int MyP2, int* MyP3, float MyP3);
Комбинируя знак ptr-операции * с именем функции мы получаем новую языковую конструкцию:
char (*MyPt1) (int MyP1, int MyP2, int* MyP3, float MyP3);
Это уже не объявление функции. Это определение указателя на функцию! Это объект со следующими характеристиками:
Так что это должны быть функции со строго определёнными характеристиками. В нашем случае - это функции типа
char (int, int, int*, float)
Описание типа указателя на функцию, возвращающую указатель на объект типа char с параметрами (int, int, int*, float)
char * (int, int, int*, float)
отличается от описания типа этой функции дополнительным элементом (*):
char * (*) (int, int, int*, float).
Пример определения подобного указателя:
char* (*MyPt2) (int MyP1, int MyP2, int* MyP3, float MyP3);
И опять новый объект:
Также можно определить функцию, которая будет возвращать указатель на объект типа void (то есть просто указатель). Это совсем просто:
void * (int)
Описанию этого типа соответствует, например, функция
void *malloc (int size);
Эта функция пытается выделить блок памяти размера size и в случае, если это удалось сделать, возвращает указатель на выделенную область памяти. В противном случае возвращается специальное значение NULL. Как распорядиться выделенной памятью - личное дело программиста. Единственное ограничение заключается в том, что при этом необходимо использовать явное преобразование типа:
#include <stdlib.h> char *p = NULL; void NewMemory () { p = malloc(sizeof(char)*1024);// Этот оператор не пройдёт! p = (char*) malloc(sizeof(char)*1024); // Требуется явное преобразование типа. }
Имя массива, если к нему не применяется операция индексации, оказывается указателем на первый элемент массива. Аналогично, имя функции, если к нему не применяется операция вызова, является указателем на функцию. В нашем случае ранее объявленная функция под именем MyFp приводится к безымянному указателю типа
char * (*) (int, int, int*, float)
К имени функции может быть применена операция взятия адреса. Её применение также порождает указатель на эту функцию. Таким образом, MyFp и &MyFp имеют один и тот же тип. А вот как инициируется указатель на функцию:
char* (*MyPt2) (int, int, int*, float) = MyFp;
Очевидно, что функция MyFp() должна быть к этому моменту не только объявлена, но и определена.
Новому указателю на функцию
char* (*MyPt3) (int, int, int*, float);
можно также присвоить новое значение.
Для этого достаточно использовать ранее определённый и проинициализированный указатель:
MyPt3 = MyPt2;
Или адрес ранее определённой функции:
MyPt3 = MyFp;
При этом инициализация и присваивание оказываются корректными лишь при условии, что имеет место точное сопоставление списков формальных параметров и списков формальных значений в объявлениях указателей и функций.
Для вызова функции с помощью указателя использование операции разыменования не обязательно. Полная форма вызова
char* MyPointChar = (*MyPT3)(7,7,NULL,7.7);
имеет краткую эквивалентную форму
char* MyPointChar = MyPT3(7,7,NULL,7.7);
Значением выражения MyPT3 является адрес функции.
А вот каким образом описывается массив указателей на функцию:
char* (*MyPtArray[3]) (int, int, int*, float); Здесь описан массив указателей из 3 элементов. Инициализация массива указателей возможна лишь после объявления трёх однотипных функций: extern char* MyFF1 (int, int, int*, float); extern char* MyFF2 (int, int, int*, float); extern char* MyFF3 (int, int, int*, float); char* (*MyPtArray[3]) (int, int, int*, float) = { MyFF1, MyFF2, MyFF3 }; // Инициализация массива указателей.
Вызов функции (например, MyFF3()) с помощью элемента массива указателей можно осуществить следующим образом:
char* MyPointChar = MyPtArray[2](7,7,NULL,7.7);
Указатель на функцию может быть описан как параметр функции:
void MonitorF(int,int,int*,float,char*(*)(int,int,int*,float)); // Торжество абстрактного описателя!
И этому параметру можно присвоить значение (значение по умолчанию):
void MonitorF(int,int,int*,float,char*(*)(int,int,int*,float)=MyFF1);
Функция, что используемая для инициализации последнего параметра функция должна быть к моменту инициализации, по крайней мере, объявлена.
А вот как может выглядеть определение функции MonitorF:
#include <assert.h> /* Заголовочный файл, содержащий макроопределение assert. Это макроопределение преобразуется в условный оператор if. Если в ходе проверки значение условного выражения оказывается равным нулю, то происходит прерывание выполнения программы. */ void MonitorF ( int val1, int val2, int* pVal, float fVal, char*(*pParF)(int,int,int*,float) ) { char* pChar; assert(pVal != NULL); assert(pParF != NULL); //Это всего лишь проверка того, не являются ли указатели пустыми... pChar = pParF(val1, val2, pVal, fVal); }
Возможные варианты вызова этой функции:
int MMM; int* pIval = &MMM; /* Указатель pIval используется для инициализации третьего параметра. */ MMM = 100; /* А значение объекту, на который настроен указатель pIval, может быть изменено в любой момент. */ MonitorF(9,9,pIval,9.9); /* При вызове используем значение указателя на функцию, присвоенное последнему параметру по умолчанию. */ MonitorF(11,11,pIval,11.11,MyFF3); /* А теперь передаём адрес новой функции.*/
Указатель на функцию может также быть типом возвращаемого значения. Объявление подобной функции требует определённого навыка. Начнём с той части объявления, которая содержит имя функции и список её формальных параметров.
ReturnerF(int, int)
Определим теперь тип указателя на функцию, который будет возвращаться функцией ReturnerF(int, int).
char* (*)(int,int,int*,float)
Теперь остаётся правильно соединить обе части объявления.
char* (*ReturnerF(int, int))(int,int,int*,float);
Получилась такая вот матрёшка. Функция о двух целочисленных параметрах, возвращающая указатель на функцию, которая возвращает указатель на объект типа char и имеет собственный список формальных параметров вида: (int,int,int*,float). Нет предела совершенству!
Самое сложное - это объявить прототип подобной функции. Всё остальное очень просто. При определении функции нужно помнить, что она (всего лишь) возвращает указатель на функцию, то есть просто имя функции. Разумеется, эта функция должна быть предварительно объявлена и определена, а её описание должно соответствовать характеристикам функции ReturnerF.
Есть такие функции! Здесь их целых три: MyFF1, MyFF2, MyFF3.
Приступаем к реализации и параллельно обыгрываем параметры.
char* (*ReturnerF(int param1, int param2))(int,int,int*,float) { char* (*PointF) (int,int,int*,float); /* Это всего лишь указатель на функцию. Мы можем себе позволить этот пустяк. */ if (!param1) return NULL; switch param2 { case 1: PointF = MyFF1; break; case 2: PointF = MyFF2; break; case 3: PointF = MyFF3; break; default: PointF = NULL; break; } return PointF; }
Теперь только вызов! Наша функция возвращает адрес функции. И поэтому самое простое - это вызов функции непосредственно из точки возврата функции ReturnerF:
int val1, val2; ::::: MyPointChar = (ReturnerF(val1,val2))(7,7,NULL,7.7); Всё было бы хорошо, если бы только не существовала вероятность возвращения пустого указателя. Так что придётся воспользоваться ранее объявленным указателем на функцию, проверять возвращаемое значение и только потом вызывать функцию по означенному указателю. Это не намного сложнее: MyPtArray[3] = ReturnerF(val1,val2); if (MyPtArray[3]) {MyPointChar = (MyPtArray[3])(7,7,NULL,7.7);} /* Вот и элемент массива указателей пригодился.*/
Настало время вспомнить о typedef-спецификаторе. С его помощью запись указателя на функцию можно сделать компактнее:
typedef char* (*PPFF) (int,int,int*,float);
Здесь надо представлять всё ту же матрёшку. Замещающий идентификатор PPFF располагается внутри определяемого выражения. И вот новое объявление старой функции.
PPFF ReturnerF(int, int);
В процессе трансляции будет восстановлен исходный вид объявления.
Назад | Содержание | Вперед