函数指针是一个包含函数地址的变量。由于它是一个指针变量,但具有一些受限制的属性,因此您可以像使用数据结构中的任何其他指针变量一样使用它。
我能想到的唯一例外是将函数指针视为指向单个值以外的东西。通过递增或递减函数指针或添加/减去函数指针的偏移量来进行指针运算实际上并没有任何实用性,因为函数指针仅指向单个事物,即函数的入口点。
函数指针变量的大小,变量占用的字节数,可能因底层架构而异,例如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 使用带函数指针的全局 struct
您可以使用static 关键字指定名称为文件范围的函数,然后将其分配给全局变量,以提供类似于C++ 的namespace 功能的方式。
在头文件中定义一个结构体作为我们的命名空间以及使用它的全局变量。
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 中的经典示例是与标准 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 消息。
需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数来启动某个动作,并提供一个函数指针,一旦动作完成,异步函数将调用该函数指针。在这种情况下,事件是完成其任务的异步函数。