Указатель на функцию - это переменная, которая содержит адрес функции. Поскольку это переменная-указатель, хотя и с некоторыми ограниченными свойствами, вы можете использовать ее почти так же, как и любую другую переменную-указатель в структурах данных.
Единственное исключение, о котором я могу думать, - это обращение к указателю на функцию как к указанию на что-то, отличное от одного значения. Делать арифметику указателя, увеличивая или уменьшая указатель на функцию или добавляя / вычитая смещение к указателю на функцию, на самом деле не имеет никакого смысла, поскольку указатель на функцию указывает только на одну вещь, точку входа в функцию.
Размер переменной указателя функции, количество байтов, занимаемых этой переменной, может варьироваться в зависимости от базовой архитектуры, например, х32 или х64 или что угодно.
Объявление для переменной указателя функции должно указывать ту же информацию, что и объявление функции, чтобы компилятор C мог выполнять те виды проверок, которые он обычно делает. Если вы не укажете список параметров в объявлении / определении указателя функции, компилятор C не сможет проверить использование параметров. Бывают случаи, когда отсутствие проверки может быть полезным, но помните, что защитная сетка была удалена.
Некоторые примеры:
int func (int a, char *pStr); // declares a function
int (*pFunc)(int a, char *pStr); // declares or defines a function pointer
int (*pFunc2) (); // declares or defines a function pointer, no parameter list specified.
int (*pFunc3) (void); // declares or defines a function pointer, no arguments.
Первые два объявления в чем-то похожи:
func
- это функция, которая принимает int
и char *
и возвращает int
pFunc
- указатель на функцию, которой присваивается адрес функции, которая принимает int
и char *
и возвращает int
Таким образом, из вышесказанного может быть строка источника, в которой адрес функции func()
назначен переменной указателя функции pFunc
, как в pFunc = func;
.
Обратите внимание на синтаксис, используемый с объявлением / определением указателя функции, в котором скобки используются для преодоления правил естественного приоритета оператора.
int *pfunc(int a, char *pStr); // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr); // declares a function pointer that returns an int
Несколько примеров использования
Некоторые примеры использования указателя на функцию:
int (*pFunc) (int a, char *pStr); // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr); // declare a pointer to a function pointer variable
struct { // declare a struct that contains a function pointer
int x22;
int (*pFunc)(int a, char *pStr);
} thing = {0, func}; // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr)); // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr)); // declare a function pointer that points to a function that has a function pointer as an argument
Вы можете использовать списки параметров переменной длины в определении указателя функции.
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
Или вы не можете указать список параметров вообще. Это может быть полезно, но исключает возможность для компилятора C выполнять проверки в представленном списке аргументов.
int sum (); // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int sum2(void); // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);
Слитки в стиле C
Вы можете использовать приведения в стиле C с указателями на функции. Однако следует помнить, что компилятор C может быть небрежным в отношении проверок или предоставлять предупреждения, а не ошибки.
int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum; // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum; // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum; // compiler error of bad cast generated, parenthesis are required.
Сравнить функциональный указатель с равенством
Вы можете проверить, что указатель функции равен определенному адресу функции, с помощью оператора if
, хотя я не уверен, насколько это было бы полезно. Другие операторы сравнения могут показаться еще менее полезными.
static int func1(int a, int b) {
return a + b;
}
static int func2(int a, int b, char *c) {
return c[0] + a + b;
}
static int func3(int a, int b, char *x) {
return a + b;
}
static char *func4(int a, int b, char *c, int (*p)())
{
if (p == func1) {
p(a, b);
}
else if (p == func2) {
p(a, b, c); // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
} else if (p == func3) {
p(a, b, c);
}
return c;
}
Массив указателей функций
И если вы хотите иметь массив указателей на функции, каждый из элементов которых содержит список аргументов, то вы можете определить указатель на функцию с неопределенным списком аргументов (не void
, что означает отсутствие аргументов, а просто неопределенное) что-то вроде следующего, хотя вы можете увидеть предупреждения от компилятора C. Это также работает для параметра указателя на функцию:
int(*p[])() = { // an array of function pointers
func1, func2, func3
};
int(**pp)(); // a pointer to a function pointer
p[0](a, b);
p[1](a, b, 0);
p[2](a, b); // oops, left off the last argument but it compiles anyway.
func4(a, b, 0, func1);
func4(a, b, 0, func2); // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);
// iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
func4(a, b, 0, p[i]);
}
// iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
(*pp)(a, b, 0); // pointer to a function pointer so must dereference it.
func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it.
}
стиль C namespace
Использование Global struct
с указателями функций
Вы можете использовать ключевое слово static
, чтобы указать функцию, имя которой является областью действия файла, а затем назначить ее глобальной переменной, чтобы обеспечить нечто похожее на функциональность namespace
в C ++.
В заголовочном файле определите структуру, которая будет нашим пространством имен, вместе с глобальной переменной, которая ее использует.
typedef struct {
int (*func1) (int a, int b); // pointer to function that returns an int
char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer
} FuncThings;
extern const FuncThings FuncThingsGlobal;
Затем в исходном файле C:
#include "header.h"
// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
return a + b;
}
static char *func2 (int a, int b, char *c)
{
c[0] = a % 100; c[1] = b % 50;
return c;
}
const FuncThings FuncThingsGlobal = {func1, func2};
Затем это можно использовать, указав полное имя глобальной переменной структуры и имя члена для доступа к функции. Модификатор const
используется в глобальном масштабе, поэтому его нельзя изменить случайно.
int abcd = FuncThingsGlobal.func1 (a, b);
Области применения указателей функций
Компонент библиотеки DLL может сделать что-то похожее на подход в стиле C namespace
, в котором определенный интерфейс библиотеки запрашивается из фабричного метода в интерфейсе библиотеки, который поддерживает создание struct
, содержащего указатели на функции. Интерфейс библиотеки загружает запрошенную версию DLL, создает структуру с необходимыми указателями на функции, а затем возвращает структуру запрашивающей вызывающей стороне для использования.
typedef struct {
HMODULE hModule;
int (*Func1)();
int (*Func2)();
int(*Func3)(int a, int b);
} LibraryFuncStruct;
int LoadLibraryFunc LPCTSTR dllFileName, LibraryFuncStruct *pStruct)
{
int retStatus = 0; // default is an error detected
pStruct->hModule = LoadLibrary (dllFileName);
if (pStruct->hModule) {
pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
retStatus = 1;
}
return retStatus;
}
void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
if (pStruct->hModule) FreeLibrary (pStruct->hModule);
pStruct->hModule = 0;
}
и это можно использовать как в:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
Тот же подход можно использовать для определения абстрактного аппаратного уровня для кода, который использует конкретную модель базового аппаратного обеспечения. Указатели функций заполняются фабрично-специфическими функциями для обеспечения аппаратно-специфической функциональности, которая реализует функции, указанные в абстрактной аппаратной модели. Это может использоваться для предоставления абстрактного аппаратного уровня, используемого программным обеспечением, которое вызывает заводскую функцию, чтобы получить интерфейс конкретной аппаратной функции, а затем использует предоставленные указатели функций для выполнения действий для базового аппаратного обеспечения без необходимости знать подробности реализации о конкретной цели. .
Функциональные указатели для создания делегатов, обработчиков и обратных вызовов
Вы можете использовать указатели функций как способ делегировать некоторые задачи или функции. Классическим примером в C является указатель функции делегата сравнения, используемый с функциями библиотеки Standard C qsort()
и bsearch()
для предоставления порядка сортировки для сортировки списка элементов или выполнения двоичного поиска по отсортированному списку элементов. Делегат функции сравнения указывает алгоритм сопоставления, используемый при сортировке или двоичном поиске.
Другое использование аналогично применению алгоритма к контейнеру стандартной библиотеки шаблонов C ++.
void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for ( ; pList < pListEnd; pList += sizeItem) {
p (pList);
}
return pArray;
}
int pIncrement(int *pI) {
(*pI)++;
return 1;
}
void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for (; pList < pListEnd; pList += sizeItem) {
p(pList, pResult);
}
return pArray;
}
int pSummation(int *pI, int *pSum) {
(*pSum) += *pI;
return 1;
}
// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;
ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);
Другой пример - с исходным кодом GUI, в котором обработчик для определенного события регистрируется путем предоставления указателя функции, который фактически вызывается, когда происходит событие. Среда Microsoft MFC с ее картами сообщений использует нечто подобное для обработки сообщений Windows, доставляемых в окно или поток.
Асинхронные функции, требующие обратного вызова, аналогичны обработчику событий. Пользователь асинхронной функции вызывает асинхронную функцию для запуска какого-либо действия и предоставляет указатель на функцию, которую асинхронная функция будет вызывать после завершения действия. В этом случае событие является асинхронной функцией, выполняющей свою задачу.