理解C中函数指针的typedef
当我阅读其他人的代码,这些代码有指向具有参数的函数的指针时,我总是有点难过。 我记得,我花了一段时间才弄清了这个定义,同时试图理解一个用C编写的数值algorithm。 那么,你能分享一下你的技巧和想法:如何编写好的指向函数指针的types定义(Do's and Do not's),为什么它们是有用的,以及如何理解别人的工作? 谢谢!
考虑C标准中的signal()
函数:
extern void (*signal(int, void(*)(int)))(int);
完全模糊的明显 – 这是一个函数,它接受两个参数,一个整数和一个指向一个函数的指针,它将一个整数作为参数,并且不返回任何内容,并且它( signal()
)返回一个指向一个函数的指针,论点,并没有返回。
如果你写:
typedef void (*SignalHandler)(int signum);
那么你可以改为将signal()
声明为:
extern SignalHandler signal(int signum, SignalHandler handler);
这意味着同样的事情,但通常被认为比较容易阅读。 更清楚的是函数接受一个int
和一个SignalHandler
并返回一个SignalHandler
。
但是,这需要一些习惯。 你不能做的一件事,就是在函数定义中使用SignalHandler
typedef
来编写一个信号处理函数。
我仍然是喜欢调用函数指针的老派:
(*functionpointer)(arg1, arg2, ...);
现代语法只使用:
functionpointer(arg1, arg2, ...);
我可以看到为什么这是有效的 – 我只是想知道我需要查找variables初始化的位置,而不是一个名为functionpointer
。
山姆评论道:
我以前看过这个解释。 然后,就像现在的情况一样,我认为我没有得到的是这两个陈述之间的联系:
extern void (*signal(int, void()(int)))(int); /*and*/ typedef void (*SignalHandler)(int signum); extern SignalHandler signal(int signum, SignalHandler handler);
或者,我想问的是,你可以使用什么基本概念来提出你的第二个版本? 连接“SignalHandler”和第一个typedef的基础是什么? 我认为这里需要解释的是typedef实际上在这里做了什么。
让我们再试一次。 其中的第一个是从C标准中直接提出来的 – 我重新input了它,并检查了我是否有括号(直到我改正了它 – 这是一个难以记住的曲奇)。
首先,请记住typedef
引入了一个types的别名。 所以,别名是SignalHandler
,它的types是:
一个指向一个函数的指针,它将一个整数作为参数并不返回任何内容。
“无返回”部分拼写void
; 这是一个整数的论点是(我相信)不言自明。 下面的符号是简单的(或不)C如何拼写指针来指定参数并返回给定的types:
type (*function)(argtypes);
在创build信号处理程序types之后,我可以使用它来声明variables等等。 例如:
static void alarm_catcher(int signum) { fprintf(stderr, "%s() called (%d)\n", __func__, signum); } static void signal_catcher(int signum) { fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum); exit(1); } static struct Handlers { int signum; SignalHandler handler; } handler[] = { { SIGALRM, alarm_catcher }, { SIGINT, signal_catcher }, { SIGQUIT, signal_catcher }, }; int main(void) { size_t num_handlers = sizeof(handler) / sizeof(handler[0]); size_t i; for (i = 0; i < num_handlers; i++) { SignalHandler old_handler = signal(handler[i].signum, SIG_IGN); if (old_handler != SIG_IGN) old_handler = signal(handler[i].signum, handler[i].handler); assert(old_handler == SIG_IGN); } ...continue with ordinary processing... return(EXIT_SUCCESS); }
请注意如何避免在信号处理程序中使用printf()
?
那么,我们在这里做了什么 – 除了省略了4个标准头文件,这些头文件需要让代码干净地编译?
前两个函数是采用一个整数并且不返回的函数。 其中一个实际上由于exit(1);
完全没有返回exit(1);
但是打印完一条消息后,另一个会返回。 请注意,C标准不允许你在信号处理程序中做很多事情; POSIX在允许的范围内有点慷慨,但是官方没有批准fprintf()
。 我也打印出收到的信号编号。 在alarm_handler()
函数中,值始终是SIGALRM
,因为它是处理程序的唯一信号,但是signal_handler()
可能会将SIGINT
或SIGQUIT
作为信号编号,因为两者都使用相同的函数。
然后我创build一个结构数组,其中每个元素标识一个信号编号和为该信号安装的处理程序。 我select了担心3个信号; 我经常担心SIGHUP
, SIGPIPE
和SIGTERM
以及它们是否被定义( #ifdef
条件编译),但这只是使事情复杂化。 我也可能使用POSIX sigaction()
而不是signal()
,但这是另一个问题; 让我们坚持我们开始的。
main()
函数迭代要安装的处理程序列表。 对于每个处理程序,它首先调用signal()
来确定进程是否正在忽略信号,并在此过程中安装SIG_IGN
作为处理程序,以确保信号保持忽略状态。 如果信号之前没有被忽略,则它再次调用signal()
,这次安装首选的信号处理程序。 (其他值大概是SIG_DFL
,是信号的默认信号处理程序。)因为第一次调用signal()将处理程序设置为SIG_IGN
, signal()
返回前一个error handling程序,则if
必须是SIG_IGN
– 因此是断言。 (好吧,如果事情发生了严重的错误,可能是SIG_ERR
,但是我会从断言的解雇中了解到这一点。)
程序然后做它的东西,并正常退出。
请注意,函数的名称可以视为指向相应types的函数的指针。 当你不应用函数调用圆括号时(例如在初始化函数中),函数名变成函数指针。 这也是为什么通过pointertofunction(arg1, arg2)
表示调用函数是合理的; 当看到alarm_handler(1)
,可以认为alarm_handler
是一个指向该函数的指针,因此alarm_handler(1)
是通过函数指针调用函数的。
所以,到目前为止,我已经展示了一个SignalHandler
variables是相对直接使用的,只要你有一些正确的值赋给它 – 这是两个信号处理函数提供的。
现在我们回到问题 – signal()
的两个声明如何相互关联。
我们来看看第二个声明:
extern SignalHandler signal(int signum, SignalHandler handler);
如果我们改变了这个函数的名字和types:
extern double function(int num1, double num2);
你将没有任何问题解释这是一个函数,它接受一个int
和一个double
作为参数,并返回一个double
值(你会吗?也许你最好不要担心,如果这是有问题的 – 但也许你应该谨慎的问如果这是一个问题,就像这个问题一样困难)。
现在, signal()
函数不再是一个double
,而是将SignalHandler
作为第二个参数,并返回一个作为结果。
这种机制也可以被看作:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
很难解释 – 所以我可能会搞砸了。 这一次我给了参数名称 – 虽然名称并不重要。
一般来说,在C中,声明机制是这样的,如果你写:
type var;
那么当你写var
它代表了给定type
的值。 例如:
int i; // i is an int int *ip; // *ip is an int, so ip is a pointer to an integer int abs(int val); // abs(-1) is an int, so abs is a (pointer to a) // function returning an int and taking an int argument
在标准中, typedef
在语法中被当作存储类,而static
和extern
则是存储类。
typedef void (*SignalHandler)(int signum);
意味着当你看到一个types为SignalHandler
(比如alarm_handler)的variables被调用为:
(*alarm_handler)(-1);
结果有type void
– 没有结果。 和(*alarm_handler)(-1);
是参数为-1
的alarm_handler()
的调用。
所以,如果我们宣称:
extern SignalHandler alt_signal(void);
代表着:
(*alt_signal)();
代表一个空值。 因此:
extern void (*alt_signal(void))(int signum);
是等同的。 现在, signal()
更复杂,因为它不仅返回SignalHandler
,还接受int和SignalHandler
作为参数:
extern void (*signal(int signum, SignalHandler handler))(int signum); extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
如果这仍然让你感到困惑,我不知道如何提供帮助 – 对我来说,这在某种程度上还是很神秘的,但是我已经习惯了它是如何工作的,因此可以告诉你,如果你坚持25年或者如此,它将成为你的第二天性(如果你聪明,甚至可能会更快一些)。
函数指针就像其他指针一样,但指向函数的地址而不是数据的地址(在堆或栈上)。 像任何指针一样,它需要正确input。 函数是由它们的返回值和它们接受的参数的types来定义的。 所以为了完整地描述一个函数,你必须包含它的返回值,并且接受每个参数的types。 当你input这样一个定义时,你给它一个'友好的名字',这使得使用这个定义创build和引用指针变得更容易。
所以例如假设你有一个function:
float doMultiplication (float num1, float num2 ) { return num1 * num2; }
那么下面的typedef:
typedef float(*pt2Func)(float, float);
可以用来指向这个doMulitplication
函数。 它只是定义一个指向一个返回一个float的函数的指针,并且接受两个参数,每个参数都是float型的。 这个定义有一个友好的名字pt2Func
。 请注意, pt2Func
可以指向任何函数返回一个浮动,并采取2浮游物。
所以你可以创build一个指向doMultiplication函数的指针,如下所示:
pt2Func *myFnPtr = &doMultiplication;
你可以使用这个指针调用函数,如下所示:
float result = (*myFnPtr)(2.0, 5.1);
这使得很好的阅读: http : //www.newty.de/fpt/index.html
cdecl
是破译怪异语法(如函数指针声明)的绝佳工具。 您也可以使用它来生成它们。
至于为了将来维护(由你自己或其他人)更容易parsing复杂声明的技巧,我build议制作小块的typedef
,并用这些小块作为更大更复杂的expression式的构build块。 例如:
typedef int (*FUNC_TYPE_1)(void); typedef double (*FUNC_TYPE_2)(void); typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);
而不是:
typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);
cdecl
可以帮你解决这个问题:
cdecl> explain int (*FUNC_TYPE_1)(void) declare FUNC_TYPE_1 as pointer to function (void) returning int cdecl> explain double (*FUNC_TYPE_2)(void) declare FUNC_TYPE_2 as pointer to function (void) returning double cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int int (*(*FUNC_TYPE_3)(double (*)(void )))(void )
事实上,我是如何在上面生成这个疯狂的混乱的。
理解函数指针的typedef的一个非常简单的方法:
int add(int a, int b) { return (a+b); } typedef int (*add_integer)(int, int); //declaration of function pointer int main() { add_integer addition = add; //typedef assigns a new variable ie "addition" to original function "add" int c = addition(11, 11); //calling function via new variable printf("%d",c); return 0; }
int add(int a, int b) { return (a+b); } int minus(int a, int b) { return (ab); } typedef int (*math_func)(int, int); //declaration of function pointer int main() { math_func addition = add; //typedef assigns a new variable ie "addition" to original function "add" math_func substract = minus; //typedef assigns a new variable ie "substract" to original function "minus" int c = addition(11, 11); //calling function via new variable printf("%d\n",c); c = substract(11, 5); //calling function via new variable printf("%d",c); return 0; }
这个输出是:
22
6
请注意,相同的math_func定义者已被用于声明这两个函数。
对于extern结构体,可以使用相同的typedef方法(在其他文件中使用sturuct)。
这是我作为练习写的函数指针和函数指针数组的最简单的例子。
typedef double (*pf)(double x); /*this defines a type pf */ double f1(double x) { return(x+x);} double f2(double x) { return(x*x);} pf pa[] = {f1, f2}; main() { pf p; p = pa[0]; printf("%f\n", p(3.0)); p = pa[1]; printf("%f\n", p(3.0)); }