Указатель на функцию - это переменная, которая содержит адрес функции. Поскольку это переменная-указатель, хотя и с некоторыми ограниченными свойствами, вы можете использовать ее почти так же, как и любую другую переменную-указатель в структурах данных.
Единственное исключение, о котором я могу думать, - это обращение к указателю на функцию, указывающее на что-то, отличное от одного значения. Делать арифметику указателя, увеличивая или уменьшая указатель на функцию или добавляя / вычитая смещение к указателю на функцию, на самом деле не имеет никакого смысла, так как указатель на функцию указывает только на одну вещь - точку входа в функцию.
Размер переменной указателя функции, количество байтов, занимаемых этой переменной, может варьироваться в зависимости от базовой архитектуры, например, x32 или x64 или чего-либо еще.
Объявление для переменной указателя функции должно указывать ту же информацию, что и объявление функции, чтобы компилятор 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 может делать что-то похожее на namespace
подход в стиле C, в котором конкретный интерфейс библиотеки запрашивается из фабричного метода в интерфейсе библиотеки, который поддерживает создание 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, которые доставляются в окно или поток.
Асинхронные функции, требующие обратного вызова, аналогичны обработчику событий. Пользователь асинхронной функции вызывает асинхронную функцию для запуска некоторого действия и предоставляет указатель на функцию, которую асинхронная функция будет вызывать после завершения действия. В этом случае событие является асинхронной функцией, выполняющей свою задачу.