我最近在C中使用了函数指针.
继续回答你自己的问题的传统,我决定对那些需要快速深入研究这个主题的人进行一些基本的总结.
让我们从一个基本功能开始,我们将指向:
int addInt(int n, int m) { return n+m; }
首先,让我们定义一个指向函数的指针,该函数接收2 int
秒并返回int
:
int (*functionPtr)(int,int);
现在我们可以安全地指出我们的功能:
functionPtr = &addInt;
现在我们有了一个指向函数的指针,让我们使用它:
int sum = (*functionPtr)(2, 3); // sum == 5
将指针传递给另一个函数基本相同:
int add2to3(int (*functionPtr)(int, int)) { return (*functionPtr)(2, 3); }
我们也可以在返回值中使用函数指针(尝试跟上,它变得混乱):
// this is a function called functionFactory which receives parameter n // and returns a pointer to another function which receives two ints // and it returns another int int (*functionFactory(int n))(int, int) { printf("Got parameter %d", n); int (*functionPtr)(int,int) = &addInt; return functionPtr; }
但是使用它更好typedef
:
typedef int (*myFuncDef)(int, int); // note that the typedef name is indeed myFuncDef myFuncDef functionFactory(int n) { printf("Got parameter %d", n); myFuncDef functionPtr = &addInt; return functionPtr; }
C中的函数指针可用于在C中执行面向对象的编程.
例如,以下行用C编写:
String s1 = newString(); s1->set(s1, "hello");
是的,->
缺乏一个new
操作员是一个死亡的赠品,但它肯定暗示我们正在设置一些String
类的文本"hello"
.
通过使用函数指针,可以在C中模拟方法.
这是如何完成的?
该String
级实际上是一个是struct
与一群函数指针充当了一种模拟方法.以下是String
该类的部分声明:
typedef struct String_Struct* String; struct String_Struct { char* (*get)(const void* self); void (*set)(const void* self, char* value); int (*length)(const void* self); }; char* getString(const void* self); void setString(const void* self, char* value); int lengthString(const void* self); String newString();
可以看出,String
类的方法实际上是声明函数的函数指针.在准备实例时String
,newString
调用该函数以设置指向其各自函数的函数指针:
String newString() { String self = (String)malloc(sizeof(struct String_Struct)); self->get = &getString; self->set = &setString; self->length = &lengthString; self->set(self, ""); return self; }
例如,getString
通过调用get
方法调用的函数定义如下:
char* getString(const void* self_obj) { return ((String)self_obj)->internal->value; }
可以注意到的一件事是,没有对象实例的概念,并且具有实际上是对象的一部分的方法,因此必须在每次调用时传入"自身对象".(这internal
只是一个隐藏的struct
,它在前面的代码清单中被省略了 - 它是一种执行信息隐藏的方法,但这与函数指针无关.)
因此,s1->set("hello");
必须传入对象来执行操作,而不是能够做到s1->set(s1, "hello")
.
由于这个小的解释必须传递给你自己的引用,我们将转到下一部分,即C中的继承.
假设我们想要创建一个子类String
,比如说ImmutableString
.为了使字符串不可变,该set
方法将无法访问,同时保持对get
和的访问length
,并强制"构造函数"接受char*
:
typedef struct ImmutableString_Struct* ImmutableString; struct ImmutableString_Struct { String base; char* (*get)(const void* self); int (*length)(const void* self); }; ImmutableString newImmutableString(const char* value);
基本上,对于所有子类,可用的方法再次是函数指针.这次,set
方法的声明不存在,因此,不能在a中调用ImmutableString
.
至于执行ImmutableString
,唯一相关的代码是"构造函数"函数,newImmutableString
:
ImmutableString newImmutableString(const char* value) { ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct)); self->base = newString(); self->get = self->base->get; self->length = self->base->length; self->base->set(self->base, (char*)value); return self; }
在实例化时ImmutableString
,指向get
和length
方法的函数指针实际上是通过遍历内部存储对象的变量来引用String.get
和String.length
方法.base
String
使用函数指针可以实现从超类继承方法.
我们可以进一步继续C中的多态性.
例如,如果我们想要改变length
方法的行为以便出于某种原因0
在ImmutableString
类中返回所有时间,那么所有必须做的就是:
添加一个将用作覆盖length
方法的函数.
转到"构造函数"并将函数指针设置为重写length
方法.
添加覆盖length
方法ImmutableString
可以通过添加lengthOverrideMethod
:
int lengthOverrideMethod(const void* self) { return 0; }
然后,length
构造函数中方法的函数指针连接到lengthOverrideMethod
:
ImmutableString newImmutableString(const char* value) { ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct)); self->base = newString(); self->get = self->base->get; self->length = &lengthOverrideMethod; self->base->set(self->base, (char*)value); return self; }
现在,不是将类中的length
方法ImmutableString
作为String
类具有相同的行为,现在该length
方法将引用lengthOverrideMethod
函数中定义的行为.
我必须添加一个免责声明,我仍然在学习如何使用C语言中的面向对象编程风格进行编写,因此可能有一点我没有解释得很好,或者可能只是在如何最好地实现OOP在C.但我的目的是试图说明函数指针的许多用法之一.
有关如何在C中执行面向对象编程的更多信息,请参阅以下问题:
C中的面向对象?
你能用C编写面向对象的代码吗?
被触发的指南:如何通过手动编译代码来滥用x86机器上GCC中的函数指针:
这些字符串文字是32位x86机器代码的字节. 0xC3
是一个x86 ret
指令.
你通常不会手工编写这些,你用汇编语言编写,然后使用汇编nasm
程序将它组装成一个平面二进制文件,然后将其转换为C字符串文字.
返回EAX寄存器上的当前值
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
写一个交换函数
int a = 10, b = 20; ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
将for循环计数器写入1000,每次调用一些函数
((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
您甚至可以编写一个计数为100的递归函数
const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol."; i = ((int(*)())(lol))(lol);
请注意,编译器将字符串文字放在.rodata
部分(或.rdata
Windows)中,该部分作为文本段的一部分链接(以及函数代码).
文本段已经阅读+ Exec的许可,所以铸造字符串文字到函数指针,而无需工作mprotect()
或VirtualProtect()
系统调用,就像你需要动态分配的内存.(或者gcc -z execstack
将程序与堆栈+数据段+堆可执行文件链接起来,作为快速入侵.)
要反汇编这些,您可以编译它以在字节上放置标签,并使用反汇编程序.
// at global scope const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";
使用gcc -c -m32 foo.c
和反汇编objdump -D -rwC -Mintel
,我们可以获得程序集,并通过破坏EBX(一个调用保留的寄存器)发现此代码违反了ABI,并且通常效率低下.
00000000: 0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] # load int *a arg from the stack 4: 8b 5c 24 08 mov ebx,DWORD PTR [esp+0x8] # ebx = b 8: 8b 00 mov eax,DWORD PTR [eax] # dereference: eax = *a a: 8b 1b mov ebx,DWORD PTR [ebx] c: 31 c3 xor ebx,eax # pointless xor-swap e: 31 d8 xor eax,ebx # instead of just storing with opposite registers 10: 31 c3 xor ebx,eax 12: 8b 4c 24 04 mov ecx,DWORD PTR [esp+0x4] # reload a from the stack 16: 89 01 mov DWORD PTR [ecx],eax # store to *a 18: 8b 4c 24 08 mov ecx,DWORD PTR [esp+0x8] 1c: 89 19 mov DWORD PTR [ecx],ebx 1e: c3 ret not shown: the later bytes are ASCII text documentation they're not executed by the CPU because the ret instruction sends execution back to the caller
这个机器代码(可能)在Windows,Linux,OS X等上以32位代码工作:所有这些操作系统上的默认调用约定都在堆栈中传递args而不是在寄存器中更有效.但EBX在所有正常的调用约定中都被调用保留,因此将其用作临时寄存器而不保存/恢复它可以轻松地使调用者崩溃.
我最喜欢的函数指针之一是使用廉价且简单的迭代器 -
#include#define MAX_COLORS 256 typedef struct { char* name; int red; int green; int blue; } Color; Color Colors[MAX_COLORS]; void eachColor (void (*fp)(Color *c)) { int i; for (i=0; i name) printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue); } int main() { Colors[0].name="red"; Colors[0].red=255; Colors[1].name="blue"; Colors[1].blue=255; Colors[2].name="black"; eachColor(printColor); }
拥有基本声明符后,函数指针变得易于声明:
id ID
:: ID是一个
指针*D
:: D指针指向
功能:D(
:d功能拍摄<
参数,>
返回
D是使用相同规则构建的另一个声明符.最后,在某个地方,它结束于ID
(参见下面的示例),这是声明的实体的名称.让我们尝试构建一个函数,它接受一个函数的指针,不带任何东西并返回int,并返回一个指向函数的指针,该函数接受一个char并返回int.使用type-defs就像这样
typedef int ReturnFunction(char); typedef int ParameterFunction(void); ReturnFunction *f(ParameterFunction *p);
如您所见,使用typedef构建它非常容易.如果没有typedef,使用上述声明符规则并不难,一致地应用.如你所见,我错过了指针指向的部分,以及函数返回的东西.这就是声明最左边出现的内容,并不感兴趣:如果已经建立了声明者,最后会添加它.我们这样做.建立起来,首先罗嗦 - 使用[
和显示结构]
:
function taking [pointer to [function taking [void] returning [int]]] returning [pointer to [function taking [char] returning [int]]]
如您所见,可以通过一个接一个地附加声明符来完全描述类型.施工可以通过两种方式完成.一个是自下而上的,从正确的事物(叶子)开始,一直到标识符.另一种方式是自上而下,从标识符开始,一直向下到叶子.我将展示两种方式.
构造从右边的东西开始:返回的东西,这是采取char的函数.为了使声明符保持不同,我将对它们进行编号:
D1(char);
直接插入char参数,因为它很简单.通过替换D1
为添加指向声明符的指针*D2
.请注意,我们必须括起括号*D2
.通过查找*-operator
和函数调用运算符的优先级可以知道这一点()
.没有我们的括号,编译器会将其读作*(D2(char p))
.但当然,这不再是D1的简单替代品*D2
了.声明者总是允许使用括号.实际上,如果你添加了太多的东西,你就不会犯错.
(*D2)(char);
退货类型齐全!现在,让我们用D2
函数声明器函数
替换return,这是D3(
我们现在所处的.
(*D3())(char)
请注意,不需要括号,因为我们希望 D3
这次是函数声明符而不是指针声明符.很棒,唯一剩下的就是它的参数.该参数与我们完成的返回类型完全相同,只需char
替换为void
.所以我会复制它:
(*D3( (*ID1)(void)))(char)
我已经替换D2
为ID1
,因为我们已经完成了该参数(它已经是一个指向函数的指针 - 不需要另一个声明符).ID1
将是参数的名称.现在,我最后在上面说了一个添加所有那些声明者修改的类型 - 出现在每个声明的最左边的那个.对于函数,它将成为返回类型.对于指向类型等的指针...当写下类型时,它会很有趣,它会以相反的顺序出现,在最右边:)无论如何,替换它会产生完整的声明.int
当然两次都是.
int (*ID0(int (*ID1)(void)))(char)
我ID0
在该示例中调用了函数的标识符.
这从类型描述最左边的标识符开始,在我们向右走过时包装该声明符.从函数获取<
参数>
返回开始
ID0()
描述中的下一个内容(在"返回"之后)是指针.让我们加入它:
*ID0()
那么接下来的事情是functon服用<
参数>
返回.参数是一个简单的char,所以我们再次把它放进去,因为它真的是微不足道的.
(*ID0())(char)
请注意,我们添加了括号,因为我们想再次的*
结合第一,而随后的(char)
.否则,它会读取功能拍摄<
参数,>
返回函数....Noes,甚至不允许函数返回函数.
现在我们只需要输入<
参数>
.我将展示一个简短的衍生版本,因为我认为你现在已经知道如何做到这一点.
pointer to: *ID1 ... function taking void returning: (*ID1)(void)
int
就像我们用自下而上的方式放在声明器之前,我们就完成了
int (*ID0(int (*ID1)(void)))(char)
自下而上还是自上而下更好?我习惯了自下而上,但有些人可能会更自在地自上而下.我认为这是一个品味问题.顺便说一下,如果你在该声明中应用所有运算符,最终会得到一个int:
int v = (*ID0(some_function_pointer))(some_char);
这是C中声明的一个很好的属性:声明声明如果使用标识符在表达式中使用这些运算符,那么它会在最左边产生类型.就像阵列一样.
希望你喜欢这个小教程!现在,当人们想知道函数的奇怪声明语法时,我们可以链接到这个.我试着把尽可能少的C内部装置.随意编辑/修复其中的内容.
当您在不同时间或不同开发阶段需要不同功能时,它们非常便于使用.例如,我正在一台带有控制台的主机上开发应用程序,但该软件的最终版本将放在Avnet ZedBoard上(它有显示器和控制台的端口,但它们不需要/想要用于最终发布).因此在开发过程中,我将使用printf
查看状态和错误消息,但是当我完成后,我不想要任何打印.这就是我所做的:
// First, undefine all macros associated with version.h #undef DEBUG_VERSION #undef RELEASE_VERSION #undef INVALID_VERSION // Define which version we want to use #define DEBUG_VERSION // The current version // #define RELEASE_VERSION // To be uncommented when finished debugging #ifndef __VERSION_H_ /* prevent circular inclusions */ #define __VERSION_H_ /* by using protection macros */ void board_init(); void noprintf(const char *c, ...); // mimic the printf prototype #endif // Mimics the printf function prototype. This is what I'll actually // use to print stuff to the screen void (* zprintf)(const char*, ...); // If debug version, use printf #ifdef DEBUG_VERSION #include#endif // If both debug and release version, error #ifdef DEBUG_VERSION #ifdef RELEASE_VERSION #define INVALID_VERSION #endif #endif // If neither debug or release version, error #ifndef DEBUG_VERSION #ifndef RELEASE_VERSION #define INVALID_VERSION #endif #endif #ifdef INVALID_VERSION // Won't allow compilation without a valid version define #error "Invalid version definition" #endif
在version.c
我将定义存在的2个函数原型version.h
#include "version.h" /*****************************************************************************/ /** * @name board_init * * Sets up the application based on the version type defined in version.h. * Includes allowing or prohibiting printing to STDOUT. * * MUST BE CALLED FIRST THING IN MAIN * * @return None * *****************************************************************************/ void board_init() { // Assign the print function to the correct function pointer #ifdef DEBUG_VERSION zprintf = &printf; #else // Defined below this function zprintf = &noprintf; #endif } /*****************************************************************************/ /** * @name noprintf * * simply returns with no actions performed * * @return None * *****************************************************************************/ void noprintf(const char* c, ...) { return; }
注意函数指针是如何在原型version.h
作为
void (* zprintf)(const char *, ...);
当它在应用程序中被引用时,它将在它指向的任何地方开始执行,这尚未定义.
在version.c
,请注意board_init()
函数在哪里zprintf
被赋予一个唯一函数(其函数签名匹配),具体取决于在其中定义的版本version.h
zprintf = &printf;
zprintf调用printf进行调试
要么
zprintf = &noprint;
zprintf只返回并且不会运行不必要的代码
运行代码将如下所示:
mainProg.c#include "version.h" #includeint main() { // Must run board_init(), which assigns the function // pointer to an actual function board_init(); void *ptr = malloc(100); // Allocate 100 bytes of memory // malloc returns NULL if unable to allocate the memory. if (ptr == NULL) { zprintf("Unable to allocate memory\n"); return 1; } // Other things to do... return 0; }
printf
如果处于调试模式,上面的代码将使用,如果处于释放模式,则不执行任何操作.这比通过整个项目并注释掉或删除代码要容易得多.我需要做的就是更改版本,version.h
代码将完成其余的工作!
函数指针通常由typedef定义,并用作参数和返回值,
上面的答案已经解释了很多,我只是举一个完整的例子:
#include#define NUM_A 1 #define NUM_B 2 // define a function pointer type typedef int (*two_num_operation)(int, int); // an actual standalone function static int sum(int a, int b) { return a + b; } // use function pointer as param, static int sum_via_pointer(int a, int b, two_num_operation funp) { return (*funp)(a, b); } // use function pointer as return value, static two_num_operation get_sum_fun() { return ∑ } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p = ∑ // call function via pointer printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B)); } // test - use function pointer as param, void test_pointer_as_param() { printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum)); } // test - use function pointer as return value, void test_pointer_as_return_value() { printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B)); } int main() { test_pointer_as_variable(); test_pointer_as_param(); test_pointer_as_return_value(); return 0; }
C中函数指针的一个重要用途是调用在运行时选择的函数.例如,C运行时库有两个例程,qsort和bsearch,它们指向一个函数,该函数被调用以比较两个被排序的项目; 这允许您根据您希望使用的任何条件分别对任何内容进行排序或搜索.
一个非常基本的例子,如果有一个名为print(int x,int y)的函数,它可能需要调用类似类型的add()函数或sub(),那么我们将要做的是,我们将添加一个函数print()函数的指针参数如下所示: -
#includeint add() { return (100+10); } int sub() { return (100-10); } void print(int x, int y, int (*func)()) { printf("value is: %d\n", (x+y+(*func)())); } int main() { int x=100, y=200; print(x,y,add); print(x,y,sub); return 0; }
函数指针是包含函数地址的变量。由于它是一个指针变量,但是具有某些受限制的属性,因此您可以像使用数据结构中的任何其他指针变量一样使用它。
我能想到的唯一例外是将函数指针视为指向单个值以外的其他东西。通过递增或递减函数指针或对函数指针增加/减去偏移量来进行指针算术并不是真正的实用程序,因为函数指针仅指向单个对象,即函数的入口点。
函数指针变量的大小,该变量占用的字节数可能会根据基础架构(例如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
是一个接受a int
和a char *
并返回an 的函数int
pFunc
是一个函数指针,分配给该函数的函数的地址是a int
和a char *
并返回aint
因此,从上面我们可以在源代码行中将函数的地址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
使用Globalstruct
您可以使用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};
然后,通过指定全局struct变量的完整名称和成员名称来使用该名称,以访问该函数。该const
改性剂的全球化,所以它不能被意外的改变。
int abcd = FuncThingsGlobal.func1 (a, b);
功能指针的应用领域
DLL库组件可以执行与C样式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中的典型的例子是与标准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消息。
需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数以启动某些动作,并提供一个函数指针,一旦动作完成,异步函数将调用该指针。在这种情况下,事件是异步功能完成其任务。