首先让我们弄清楚我们正在做什么。从signal
的手册页中窃取:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
在这里,我们可以更清楚地看到signal是一个接受两个参数(一个int和一个函数指针)并返回一个函数指针的函数。
功能指针
函数指针的工作方式非常简单:将一个函数指针指向一个函数,然后可以通过该指针调用该函数。例如:
#include <stdio.h>
void hello(int x) {
printf("Hello, world! x is %d.\n", x);
}
int main(void) {
void (*ptr)(int) = hello;
ptr(2); // calls hello(2)
}
阅读宣言
信号的签名看起来很吓人,主要是因为变量声明语法的一半规则仅是函数和数组的指针所需要的,即使那样,也很少被过度使用。我将尝试揭开神秘面纱:
C语言中的变量声明是从和
内到的从右至左读取的。当您的变量是
int
或
struct foo
时,这没有什么区别,但对于指针,数组和函数很重要。从右到左的
部分非常简单:
// a is an array ----+
// of pointers --+ |
// to int ----v v v
int *aa[10];
// f is a function ----+
// returning ptr -+ |
// to int -----v v v
int *foo(void);
我认为
由内而外的位是一种,仅在函数和数组指针中可以看到。这是因为这有点麻烦,所以将其排除在更常见的用例之外是有意义的。指针数组比指向数组的指针更常见,返回指针的函数比指向函数的指针更常见,因此,幸运的是您不需要它们。但是,如果我们希望能够使用它们,则需要某种方法来声明这些数据类型。
那么语法如何适应函数的指针,以及指针数组的指针之类的组合呢?
让我们从较少使用的数组指针开始,因为语法不太拥挤:
int arr[10]; // is an array
int *ptrarr[10]; // is an array of pointers
int (*arrptr)[10]; // is a pointer to an array.
int *(*arrptr)[10]; // is a pointer to an array of pointers
括号提供了一个嵌套结构,我们从
里面的中读取,从ozt_strong从右到左读取每个级别:
// arrptr is a pointer -+
// to an array ---------|-------+
// of pointers -------+ | |
// to int ---------v v v v
int *(*arrptr)[10]; // is a pointer to an array.
这可以任意扩展:
// foo is an array --------+
// of pointers -------+ |
// to array ----------|----|----+
// of pointers -----+ | | |
// of pointers ---+ | | | |
// to array ------|-|-|----|----|---+
// of pointers -+ | | | | | |
// to int ---v v v v v v v v
int *(* *(*foo[10])[20])[30];
虽然:请不要那样做。
然后,函数指针的作用与数组指针几乎相同,不同之处在于数组边界被参数列表替换。我们有
void f(void); // a function taking no arguments and returning nothing
void *ptrf(void) // a function taking no arguments and returning a pointer
void (*fptr)(void); // a pointer to a function taking no arguments and returning nothing
void *(*ptrfptr)(void) // a pointer to a function taking no arguments and returning a pointer
将此与上面的数组指针语法进行比较。这也可以任意扩展,并且函数指针可以出现在参数列表中(可以独立读取)。因此,处理
signal
:一个函数
void foo(void (*fptr)(void));
是一个接受函数指针作为参数的函数。除了参数列表中看上去恐怖的函数指针外,这与您惯常的函数声明非常相似。可怕的部分是返回函数指针的函数,它看起来像这样:
void (*foo(void))(void);
让我们拆开它:
// foo is a function -----------------------
// returning a pointer ----------------+ |
// to a function taking no arguments --|---|-------+
// returning nothing -------------v v v v
void (*foo(void))(void);
您可以看到,它的工作原理也非常类似于数组指针。现在,我们快到家了:
signal
以这种方式非常有效,只有参数列表不同。再看一遍:
// signal is a function ------------+
// returning a pointer ------+ |
// to a function taking int -|------|-------------------------------------+
// returning nothing --v v v v
void ( *signal(int signum, void (*handler)(int)) ) (int);
signal
本身的参数列表
int signum, void (*handler)(int))
可以单独读取,并且希望您现在再也不会感到恐惧,现在您可以看到哪个参数列表属于哪个参数列表。
但是由于我不能无所畏惧,所以请注意:可以将函数指针和数组指针结合使用。例如,
void (*foo[10])(void);
是一个函数指针数组(实际上可能很有用),并且
int (*foo(void))[10];
是一个返回指向数组的指针的函数。这样可以构建真正可怕的东西,例如
int (*(*(*foo)[10])(void))[20];
...,让我们看一下,它是一个指向指针数组的指针,该指针指向函数返回的数组指针。如果您确实需要这种类型的高阶类型,
一定要使用typedefs 。这可以重写为
typedef int (*arrptr)[20];
typedef arrptr (*funcptr)(void);
funcptr (*foo)[10];
将其与原始声明进行比较,并告诉我哪个更有意义。