我在想它们是如何工作的。要解释我所说的“函数调用器”,一个很好的例子就是glutTimerFunc
,它能够将函数作为参数传入并调用它,即使它不知道该函数是否已声明。它是如何做到这一点的?
typedef void (* FGCBIdle)( void );
在这里,FGCBIdle(即FreeGlut CallBack Idle的缩写)被定义为一个不带参数且返回类型为void的函数指针。这只是一种类型定义,使表达式的编写更加容易,它不会分配任何存储空间。
Freeglut有一个叫做SFG_State的结构体,保存着各种设置。该结构体的定义部分如下:
struct tagSFG_State
{
/* stuff */
FGCBIdle IdleCallback; /* The global idle callback */
/* stuff */
};
void glutIdleFunc( void (* callback)( void ) )
{
fgState.IdleCallback = callback;
}
在这里,fgState是一个SFG_State变量。你可以看到,glutIdleFunc接受一个参数,这个参数是指向一个不带参数且无返回值的函数的函数指针,这个参数的名称是callback。在函数内部,全局的fgState变量中的IdleCallback被设置为用户提供的回调函数。当你调用glutIdleFunc函数时,你传递了自己函数的名称(例如glutIdleFunc(myIdle)),但实际上你传递的是函数的地址。
稍后,在由glutMainLoop启动的大型glut事件处理循环中,你会找到以下代码:
if( fgState.IdleCallback )
{
/* stuff */
fgState.IdleCallback( );
}
该参数是指向函数的指针。调用者有责任确保函数声明与要求相匹配(例如,参数的数量和类型,调用约定)。
作为参数传递的函数被传递为函数指针。
在编译后的代码中,函数只是一个地址,CPU可以通过该地址进行向量执行。当您传递函数指针时,编译器(以及后来的链接器)会将正确的地址插入调用中。
函数参数必须完全匹配,因为执行代码只会将值推送到堆栈上(或寄存器,取决于架构),并期望弹出(读取)返回值。