Signal函数用起来其实很简单,但是回头看看他的声明,相信会有很多人表示费解。自己也在这个问题中纠结了好几年了,今天终于弄明白,很是兴奋,一起分享一下。

先看函数原型:void (*signal(int signo, void (*func)(int)))(int);对于看惯了类似unsigned int sleep(unsigned int seconds);这种声明的人们来说,signal的声明到底是个啥啊?signal是个函数,后面应该是形参啊,但为什么形参后面又来个形参,我们使用的时候可没有后面的(int)啊?

问题就出在这,难以理解的也是这里。我们又掉进了一个误区,我们往往以为signal函数的返回值是void类似的,这样后面的声明部分就无法理解了。在网上找一些signal的使用方法:

  if(signal(SIGINT,sig_int)==SIG_ERR)
            err_sys("can't catch the SIGINT");

SIG_ERR长得跟那些信号值好像,如果之前没看过signal的声明,那么急躁的人们就会想:signal函数返回的跟信号类型一样,是一个int型的值。因为往往判错的时候都只会用到SIG_ERR,记住这样用倒也不会产生太大的问题。

但这样也就太业余了,根本不利于成长啊。再看看SIG_ERR的定义,它可不是int型的:

#define SIG_ERR (void(*)())-1
     #define SIG_DFL (void(*)())0
     #define SIG_IGN (void(*)())1

然后就必须再弄明白signal函数了。其实声明最后的那个(int)是用来修饰返回值的,那什么类型的数据需要用形参来修饰呢?自然只有函数指针了。我们把这个声明修改一下:void (*p)(int);这是什么?它就是一个函数指针啊,该函数指针指向的函数有一个int型的参数。说到这,我们就很容易理解了,signal函数声明说明了signal函数的返回值是一个函数指针,该函数指向的函数有一个int型的参数。而signal(int signo, void (*func)(int));才是我们在函数里调用signal函数时的使用方法。

这里应当注意,signal函数返回的是一个函数指针,而不是一个指针函数。怎么理解呢?函数指针它是个指针,但该指针指向的是一个函数的入口,因此它需要指定参数类型。指针函数是指一个函数它的返回值是指针,普通类型的指针是不存在形参的概念的。将signal函数与char *ctime(const time_t *timep);进行对比。前者返回值是函数指针,因此最后有int来修饰形参,后者返回的是一个char型指针,是一个完整的数据类型,不需要任何修饰。

现在应该弄明白了,一个函数声明函数返回类型、函数名和形参列表组成,signal函数就是函数返回类型复杂了一点。那能不能让它表现的更简单一些呢,最好就是像ctime那样,一眼就能让人看到返回值是char*?答案是肯定的。

Signal函数不是返回的是有一个int型参数、返回值是void型的函数指针吗,这样的函数指针的原型(ctime返回值的原型是char*)是什么呢?不是void,而是void(*)(int),这样的数据类型不好理解,我们可以给它换个简单的名字,取名字自然就是用typedef了:typedef void (*pSigfunc)(int);(注意这里有分号,typedef是编译时候处理的,突然发现似乎所有预处理命令后面都没有分号)当然,也可以给函数取名typedef void (Sigfunc)(int);对应修改后的signal函数声明可以简化为pSigfunc signal(int signo, pSigfunc func)或者Sigfunc* signal(int signo, Sigfunc* func);这样就和直观的ctime函数一样了吧!(此处需要对typedef有一定的了解,可以参考我转的一篇文章《typedef常见用法》)

最后看一下signal函数的那几个返回值。现在应该很容易理解,它们其实就是个强制类型转换,将一些默认错误码强制转换为一个函数指针。这个函数指针的原型就是void(*)()了。有人会问,(*)怎么没有函数名啊?答案也是很简单地,这是原型不是定义,只有定义时才会有变量。就像int a;int是原型,只有在定义变量时才会用到a(这个问题其实挺弱智的,但我自己一开始也没弄明白)。然后又有人会问,signal返回的那种函数指针不是有一个int型的形参吗?这个问题我也纠结了一段时间。之后写了一段测试代码(最后给出),才总算弄明白。其实这样写更通用一些。

C语言在声明一个函数时,如果不指定形参,那么在定义时可以使用任意形参。而C++却不是这样,如果定义时的形参个数和声明时的对不上,那么就会报错。测试代码很好地说明了这样的一个差异,用gcc编译或者使用g++编译,两者的区别是显而易见的。

综上,C语言其实可以写的很精练,但过于精练往往带来的就是不好理解。作为一个热爱C语言的人来说,她的精练真是让人又爱又恨,如果有一天,我们能够完全理解,就能真正欣赏她的美了。

最后给出测试代码:

 #include <stdio.h>

 typedef void (*func)(/*int*/);

 void print(int a)
{
printf("%d\n\n", a);
}
int main()
{
func f1 = print;
f1(); return ;
}

test

最新文章

  1. DDD心得
  2. php 画图片3
  3. [php入门] 3、WAMP中的集成MySQL相关基础操作
  4. java获取对象属性类型、属性名称、属性值
  5. fenshijin
  6. access检测表没有的字段,添加之
  7. 入门1:PHP的优点
  8. 【Android界面实现】可旋转的汽车3D模型效果的实现
  9. 2.1 Word 插入 smartart、图表
  10. (转)CentOS下一键安装GitLab
  11. 一起学Linux02之Linux系统启动过程
  12. Entity Framework 乐观并发处理
  13. PHP算法之二分查找
  14. setData方法修改data中对象或数组的属性值(小程序开发)
  15. Swarm stack
  16. Oracle课程档案,第三天
  17. terraform 几个方便的工具
  18. clientX, clientY,offsetX, offsetY,screenX, screenY, x, y
  19. 九度OJ小结
  20. Sql server函数的学习2(游标函数、日期函数、字符串操纵函数)

热门文章

  1. 软件project(十)——软件维护
  2. C#二维码生成与解码
  3. Metrics监控应用
  4. 归并排序 &amp; 快速排序
  5. CSS3 制作向左、向右及关闭图标的效果
  6. Coreseek/sphinx全文检索的了解
  7. MySQL引擎的相关知识
  8. Hadoop-2.4.1完全分布式环境搭建
  9. Robotium源码分析之Instrumentation进阶
  10. LibVLC video controls