C中的函数调用者(回调)?
所以我想知道它们是如何工作的。 为了解释我的意思是“函数调用者”,我的意思是glutTimerFunc
一个很好的例子,它能够接受一个函数作为参数并调用它,即使它不知道它被声明。 它是怎么做到的?
您所谈论的内容称为回调,并使用C和C ++中的函数指针实现。
既然你提到了过剩,那么让我们直接从freeglut源代码中获取一个真实的例子。 我将使用glutIdleFunc而不是glutTimerFunc,因为代码更简单。
在glut中,idle函数回调(你提供给glutIdleFunc的是)一个指向函数的指针,该函数不带任何参数并且什么都不返回。 typedef用于为这样的函数类型命名:
typedef void (* FGCBIdle)( void );
这里,FGCBIdle(FreeGlut CallBack Idle的缩写)被定义为指向不带参数且返回类型为void的函数的指针。 这只是一种类型定义,使编写表达式更容易,它不分配任何存储。
Freeglut有一个名为SFG_State的结构,它包含各种设置。 该结构的部分定义是:
struct tagSFG_State { /* stuff */ FGCBIdle IdleCallback; /* The global idle callback */ /* stuff */ };
该结构包含一个FGCBIdle类型的变量,我们建立的是特定函数指针的另一个名称。 您可以将IdleCallback字段设置为指向用户使用glutIdleFunc函数提供的函数的地址。 该函数的(简化)定义是:
void glutIdleFunc( void (* callback)( void ) ) { fgState.IdleCallback = callback; }
这里,fgState是一个SFG_State变量。 如您所见,glutIdleFunc接受一个参数,该参数是一个函数指针,该函数指向不带参数且不返回任何内容的函数,此参数的名称为回调函数。 在函数内部,全局fgState变量中的IdleCallback设置为用户提供的回调。 当你调用glutIdleFunc函数时,你传递了你自己的函数的名字(例如glutIdleFunc(myIdle)),但你真正传递的是函数的地址。
之后,在由glutMainLoop发起的大过量事件处理循环中,您将找到以下代码:
if( fgState.IdleCallback ) { /* stuff */ fgState.IdleCallback( ); }
如果用户提供了空闲回调,则在循环中调用它。 如果您在我的post开头检查函数指针教程,您将更好地理解语法,但我希望现在的一般概念更有意义。
该参数是指向函数的指针。 调用者有责任确保函数声明符合要求(例如,参数的数量和类型,调用约定)。
作为参数传递的函数作为函数指针传递。
在编译的代码中,函数只不过是CPU可以向量执行的地址。 传递函数指针时,编译器(以及稍后的链接器)会在调用中插入正确的地址。
函数参数必须完全匹配,因为执行代码将简单地推送堆栈上的值(或寄存器,具体取决于体系结构),并期望弹出(读出)返回值。