How do function pointers in C work?
我最近在C语言的函数指针方面有一些经验。
因此,按照回答你自己问题的传统,我决定为那些需要快速深入主题的人做一个非常基础的小总结。
C中的函数指针
让我们从一个基本函数开始,我们将指向:
1 2 3 | int addInt(int n, int m) { return n+m; } |
首先,让我们定义一个指向函数的指针,该函数接收2个
1 | int (*functionPtr)(int,int); |
现在我们可以安全地指出我们的功能:
1 | functionPtr = &addInt; |
现在我们有了一个指向函数的指针,让我们使用它:
1 | int sum = (*functionPtr)(2, 3); // sum == 5 |
将指针传递给另一个函数基本上是相同的:
1 2 3 | int add2to3(int (*functionPtr)(int, int)) { return (*functionPtr)(2, 3); } |
我们也可以在返回值中使用函数指针(尝试跟上,它会变得混乱):
1 2 3 4 5 6 7 8 | // 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; } |
但使用
1 2 3 4 5 6 7 8 | 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编写的:
1 2 | String s1 = newString(); s1->set(s1,"hello"); |
是的,
通过使用函数指针,可以在C中模拟方法。
这是如何完成的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); |
可以看出,
1 2 3 4 5 6 7 8 9 10 11 12 | String newString() { String self = (String)malloc(sizeof(struct String_Struct)); self->get = &getString; self->set = &setString; self->length = &lengthString; self->set(self,""); return self; } |
例如,通过调用
1 2 3 4 | char* getString(const void* self_obj) { return ((String)self_obj)->internal->value; } |
可以注意到的一件事是,没有对象实例的概念,并且方法实际上是对象的一部分,因此在每次调用时都必须传递一个"自对象"。(而
因此,我们不能做
有了这一次要的解释,我们将转到下一个部分,即C语言中的继承。
假设我们要做一个
1 2 3 4 5 6 7 8 9 10 11 | 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); |
基本上,对于所有子类,可用的方法再次是函数指针。此时,
关于
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } |
在实例化
使用函数指针可以从超类继承方法。
我们可以继续研究C的多态性。
例如,如果我们想改变
在
1 2 3 4 | int lengthOverrideMethod(const void* self) { return 0; } |
然后,将构造函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } |
现在,与
我必须添加一个免责声明,我仍然在学习如何用C语言编写面向对象的编程风格,因此可能有一些点我解释得不好,或者可能只是在如何最好地在C语言中实现OOP方面做了偏离标记。但是我的目的是试图说明函数指针的多种用途之一。
有关如何在C中执行面向对象编程的详细信息,请参阅以下问题:
- C中的对象方向?
- 你能用C语言写面向对象的代码吗?
被解雇指南:如何通过手工编译代码来滥用x86机器上gcc中的函数指针:
这些字符串文本是32位x86计算机代码的字节。
通常不需要手工编写,而是使用汇编语言编写,然后使用像
返回EAX寄存器上的当前值
1 | int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); |
编写交换函数
1 2 | 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,每次调用一些函数
1 | ((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的递归函数
1 2 | 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); |
请注意,编译器将字符串文本放在
文本段具有read+exec权限,因此将字符串文本强制转换为函数指针可以在不需要像动态分配内存那样调用
要分解这些,可以编译它以在字节上放置标签,并使用反汇编程序。
1 2 | // 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"; |
用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 00000000 <swap>: 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位代码工作:所有这些操作系统上的默认调用约定都在堆栈上传递参数,而不是在寄存器中更有效地传递参数。但是ebx在所有正常的调用约定中都保留了调用,因此将其用作临时寄存器而不保存/恢复它很容易使调用方崩溃。
我最喜欢的函数指针的用法之一是使用廉价且简单的迭代器-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <stdio.h> #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<MAX_COLORS; i++) (*fp)(&Colors[i]); } void printColor(Color* c) { if (c->name) printf("%s = %i,%i,%i ", 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是另一个使用相同规则构建的声明器。最后,在某个地方,它以
1 2 3 | typedef int ReturnFunction(char); typedef int ParameterFunction(void); ReturnFunction *f(ParameterFunction *p); |
如您所见,使用typedef构建它非常容易。如果没有typedef,使用上面一致应用的声明器规则也不难。如您所见,我遗漏了指针指向的部分,以及函数返回的内容。这就是在声明的最左边出现的,并不重要:如果已经构建了声明者,则在末尾添加它。让我们这样做。始终如一地建立起来,第一个字-显示使用
1 2 3 4 | function taking [pointer to [function taking [void] returning [int]]] returning [pointer to [function taking [char] returning [int]]] |
如您所见,可以通过逐个附加声明符来完全描述一个类型。施工有两种方式。一个是自下而上的,从非常正确的东西(leaves)开始,一直到标识符。另一种方法是自顶向下,从标识符开始,一直向下到树叶。我将展示两种方式。好的。自下而上
构造从右边的东西开始:返回的东西,它是接受char的函数。为了保持声明者的不同,我要给他们编号:好的。
1 | D1(char); |
直接插入char参数,因为它很简单。通过将
1 | (*D2)(char); |
返回类型已完成!现在,让我们用函数声明器函数来代替
1 | (*D3(<parameters>))(char) |
注意,不需要括号,因为我们希望
1 | (*D3( (*ID1)(void)))(char) |
我已经用
1 | int (*ID0(int (*ID1)(void)))(char) |
在那个例子中,我调用了函数
这从类型描述中最左边的标识符开始,在我们向右走的时候包装这个声明符。从函数开始取
1 | ID0(<parameters>) |
描述中的下一件事(在"返回"之后)是指向的指针。让我们把它合并起来:好的。
1 | *ID0(<parameters>) |
然后,下一个功能是获取
1 | (*ID0(<parameters>))(char) |
注意我们添加的括号,因为我们再次希望
现在我们只需要把
1 2 | pointer to: *ID1 ... function taking void returning: (*ID1)(void) |
就像我们自下而上那样,把
1 | int (*ID0(int (*ID1)(void)))(char) |
好东西
自下而上还是自上而下更好?我习惯自下而上,但有些人可能更喜欢自上而下。我认为这是一个品味问题。顺便说一句,如果应用该声明中的所有运算符,最终将得到一个int:好的。
1 | int v = (*ID0(some_function_pointer))(some_char); |
这是C中声明的一个很好的属性:声明声明断言,如果在使用标识符的表达式中使用这些运算符,那么它将在最左边生成类型。数组也是这样。好的。
希望你喜欢这个小教程!现在,当人们对函数的奇怪声明语法感到疑惑时,我们可以链接到这一点。我试着尽可能少地放入C内部。请随意编辑/修复其中的内容。好的。好啊。
函数指针的另一个好用法:在版本之间轻松切换
当您希望在不同的时间或不同的开发阶段使用不同的函数时,它们非常方便。例如,我正在一台有控制台的主机上开发一个应用程序,但是软件的最终版本将被放在一个avnet zedboard上(它有用于显示和控制台的端口,但最终版本不需要/不需要)。所以在开发过程中,我将使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // 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 <stdio.h> #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 |
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #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; } |
注意函数指针是如何在
当它在应用程序中被引用时,它将开始执行它所指向的任何地方,而这个地方还没有被定义。
在
或
运行代码如下所示:
主丙1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include"version.h" #include <stdlib.h> int 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 "); return 1; } // Other things to do... return 0; } |
如果处于调试模式,上述代码将使用
函数指针通常由typedef定义,并用作参数和返回值,
以上答案已经解释了很多,我只举了一个完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #include <stdio.h> #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 ", 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 ", 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 ", 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运行时库有两个例程,分别是
一个非常基本的例子,如果有一个函数名为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <stdio.h> int add() { return (100+10); } int sub() { return (100-10); } void print(int x, int y, int (*func)()) { printf("value is: %d ", (x+y+(*func)())); } int main() { int x=100, y=200; print(x,y,add); print(x,y,sub); return 0; } |
输出是:
value is: 410
value is: 390
函数指针是包含函数地址的变量。由于它是一个指针变量,尽管有一些受限制的属性,所以您可以像在数据结构中使用任何其他指针变量一样使用它。好的。
我能想到的唯一例外是将函数指针视为指向单个值以外的其他值。通过增加或减少一个函数指针或增加/减少一个函数指针的偏移量来进行指针算术实际上不是什么实用程序,因为函数指针只指向一个单一的东西,即一个函数的入口点。好的。
函数指针变量的大小,即变量所占的字节数,可能因基础架构(如x32或x64或其他)的不同而有所不同。好的。
函数指针变量的声明需要指定与函数声明相同的信息类型,以便C编译器执行其通常执行的检查类型。如果在函数指针的声明/定义中没有指定参数列表,C编译器将无法检查参数的使用。有些情况下,这种缺乏检查是有用的,但请记住,安全网已被移除。好的。
一些例子:好的。
1 2 3 4 5 6 7 | 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 。
因此,从上面我们可以得到一个源行,其中函数
注意与函数指针声明/定义一起使用的语法,其中括号用于克服自然运算符优先规则。好的。
1 2 | 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 |
几个不同的用法示例好的。
函数指针的一些用法示例:好的。
1 2 3 4 5 6 7 8 9 | 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 |
可以在函数指针的定义中使用可变长度参数列表。好的。
1 2 | int sum (int a, int b, ...); int (*psum)(int a, int b, ...); |
或者根本不能指定参数列表。这可能很有用,但它消除了C编译器对所提供的参数列表执行检查的机会。好的。
1 2 3 4 | 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编译器可能在检查方面比较松懈,或者提供警告而不是错误。好的。
1 2 3 4 5 | 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. |
将函数指针与相等项进行比较好的。
您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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; } |
函数指针数组好的。
如果你想要一个函数指针数组,每个元素的参数列表都有不同,那么你可以定义一个函数指针,参数列表未指定(不是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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. } |
使用带有函数指针的全局
您可以使用EDCOX1的3个关键字来指定一个函数,该函数的名称是文件范围,然后将其赋值给全局变量,以提供类似于C++的EDCOX1和1的功能。好的。
在头文件中定义一个结构,该结构将是我们的命名空间以及使用它的全局变量。好的。
1 2 3 4 5 6 | 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源文件中:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #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}; |
然后,通过指定全局结构变量的完整名称和成员名称来访问函数。在全球范围内使用
1 | int abcd = FuncThingsGlobal.func1 (a, b); |
功能指针的应用领域好的。
dll库组件可以执行类似于c样式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
这可用于:好的。
1 2 3 4 5 6 | LibraryFuncStruct myLib = {0}; LoadLibraryFunc (L"library.dll", &myLib); // .... myLib.Func1(); // .... FreeLibraryFunc (&myLib); |
同样的方法可以用于为使用底层硬件的特定模型的代码定义抽象硬件层。函数指针由工厂用特定于硬件的函数填充,以提供实现抽象硬件模型中指定的函数的特定于硬件的功能。这可用于提供软件使用的抽象硬件层,该软件调用工厂函数以获取特定的硬件功能接口,然后使用提供的函数指针为底层硬件执行操作,而无需了解有关特定目标的实现细节。好的。
用于创建委托、处理程序和回调的函数指针好的。
可以使用函数指针作为委托某些任务或功能的方法。C中的经典示例是比较委托函数指针,与标准C库函数
另一种用法类似于将算法应用到C++标准模板库容器中。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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消息。好的。
需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数来启动一些操作,并提供一个函数指针,异步函数将在操作完成后调用该指针。在这种情况下,事件是完成其任务的异步函数。好的。好啊。
从零开始的函数有一些从中开始执行的内存地址。在汇编语言中,它们被称为(调用"函数的内存地址")。现在回到c,如果函数有内存地址,那么它们可以由c中的指针操作。因此,根据c的规则
1.首先需要声明一个指向函数的指针2.传递所需函数的地址
****注->功能类型应相同****
这个简单的程序将说明一切。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include<stdio.h> void (*print)() ;//Declare a Function Pointers void sayhello();//Declare The Function Whose Address is to be passed //The Functions should Be of Same Type int main() { print=sayhello;//Addressof sayhello is assigned to print print();//print Does A call To The Function return 0; } void sayhello() { printf(" Hello World"); } |
之后,让我们来看看机器是如何理解它们的。
红色标记区显示地址是如何交换和存储在EAX中的,然后它们是EAX上的调用指令。EAX包含所需的函数地址
由于函数指针通常是类型化回调,因此您可能需要查看类型安全回调。这同样适用于非回调函数的入口点等。
C是相当善变和宽容的同时:)