众所周知,C 语言的一大难点就在于变量声明。 比如说如下的几个例子:
-
char* const *(*next)();
-
char *(*c[10])(int **p);
-
void (*signal(int, void(*)(int)))(int);
基本上每一个都是晦涩难懂,看了简直像杀人。但其实也是相对来会所比较简单的一个。如果熟悉编译原理,其实可以通过人脑编译器的手段来解决难懂问题。毕竟,代码也需要编译器进行翻译。
首先我们得知道运算符号的优先级顺序。这里只说在声明期间会出现的符号。主要分为变量名
,括号内内容
,后缀操作符如:[], ()
,前缀操作符如: *
,类型描述符如:const, volatile, 其中, 如果 const 或 volatile 后面跟着类型如 int, long 等,那么它作用于类型。其他情况下,const 和 volatile 作用于左边的指针
。
理解了这个顺序,就可以来看看如何解读第一个声明了。
-
首先,next 先拿出来,说明,next 是一个 … 变量。声明变成了
char* const *(*)();
-
然后看到
*
,说明 next 是一个指向 … 的指针。声明变成了char* const *();
-
然后就是
()
符号,说明 next 是一个指向函数的指针。然后再是一个*
表明读取指针的内容即该函数地址。所以声明等价于char* const func();
-
那么接着看,
char* const
就表示返回值是一个 const 字符指针。 - 所以该声明就能读出来了。即 next 是一个函数指针,该函数返回值是一个 const 字符指针。
再来看看第二个声明:
-
首先 c 先拿出来,一看有后缀操作符,那就直接拿出来
c[10]
,说明 c 是一个大小为 10 的数组。声明变成了char *(*)(int**p);
-
然后可以看到有一个指针操作符,那就可以看成,c 是一个大小为 10 的数组,内容是指针。声明变成了
char *()(int **p);
- 因为可以看到紧跟的是一个后缀操作符,可以知道,这是一个函数,它接受的参数是一个指向 int 的指针的指针(可以理解为二维数组)。
-
往前就是一个
*
号,表示读取该指针的内容,最前面的 char 表示该函数的返回值是字符。 - 所以,最后这个声明也能看出来了:c 是一个大小为 10 的数组,数组的类型是一个指向函数的指针,该函数接受一个指向 int 的指针的指针,返回值是 char。
最后就可以看最后的一个声明了。
- 首先拿出来 signal ,紧跟着是一个后缀操作符,所以可以读作 signal 是一个函数,他接受的参数是一个 int 和一个返回值为 void 接受参数为 int 的函数指针。
-
声明变成了
void (*)(int);
,是不是突然就简单了。这个就可以读作一个指向函数的指针,该函数返回值为 void 参数为 int。 - 所以最后的声明也就出来了。signal 是一个函数指针,它指向的函数读入的参数为 int 和一个返回值为 void 接受参数为 int 的函数指针,指向的函数的返回值也是一个返回值为 void 参数为 int 的函数指针。
- 虽然有点拗口,但是实际使用的时候,其实也很简单。
#include "signal.h"
#include "stdio.h"
#include "sys/signal.h"
void f(int a)
{
printf("%d", a);
}
int main(int argc, char *argv[])
{
void (*sfp)(int);
sfp = f;
signal(SIGINT, sfp);
int a;
scanf("%d", &a);
return 0;
}
这样的话其实就完成了一个绑定。在这个例子里,你可以通过 ctrl + c
来触发一个 SIGINT 信号。然后系统会自动的执行那个回调函数。当然你也可以手动触发这个函数:(*signal(SIGINT, sfp))(2);
, 当然,通常情况是,异常退出。
以上就是刚看完的一个理解声明的方法以及几个简单的实例。不得不说,和其他严格限制程序员想象力的高级语言不同。C 只定义了简单的规则,然后留有大片的空间可供发挥。非常喜欢这个。