1、函数指针linux
定义:函数指针是指向函数的指针变量,即本质是一个指针变量。程序员
int (*f) (int x); /* 声明一个函数指针 */算法
f=func; /* 将func函数的首地址赋给指针f */ shell
指向函数的指针包含了函数的地址,能够经过它来调用函数。声明格式以下: 类型说明符 (*函数名)(参数)
其实这里不能称为函数名,应该叫作指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明笔削和它指向函数的声明保持一致。
指针名和指针运算符外面的括号改变了默认的运算符优先级。若是没有圆括号,就变成了一个返回整型指针的函数的原型声明。
例如:
void(*fptr)();
应用:把函数的地址赋值给函数指针,能够采用下面两种形式:
fptr=&Function;
fptr=Function;
取地址运算符&不是必需的,由于单单一个函数标识符就标号表示了它的地址,若是是函数调用,还必须包含一个圆括号括起来的参数表。
能够采用以下两种方式来经过指针调用函数:
x=(*fptr)();
x=fptr();ide
下面举个函数指针的使用例子:函数
#include<stdio.h> int f(int x,int y) { int z; z=(x>y)?x:y; return z; } int main() { int f(); int i,a,b; int (*p)();//定义函数指针 scanf("%d",&a); p=f;//给函数指针p赋值,使它指向函数 for(i=1;i<9;i++) { scanf("%d",&b); a=(*p)(a ,b);//经过指针p调用函数f } printf("The max number is:%d\n",a); system("pause"); return 0; }
运行结果以下:测试
2、回调函数ui
一、什么是回调函数?
回调函数就是一个经过函数指针调用的函数。若是你把函数的指针(地址)做为参数传递给另外一个函数,当这个指针被用为调用它所指向的函数时,咱们就说这是回调函数。
二、为何要使用回调函数?
由于能够把调用者与被调用者分开。调用者不关心谁是被调用者,全部它需知道的,只是存在一个具备某种特定原型、某些限制条件(如返回值为int)的被调用函数。
若是想知道回调函数在实际中有什么做用,先假设有这样一种状况,咱们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可使用函数指针,并进行回调。
回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到必定时间,程序会获得相应的通知,但通知机制的实现者对咱们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知咱们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,并且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。
另外一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上全部的顶层窗口,为每一个窗口调用一个程序提供的函数,并传递窗口的处理程序。若是被调用者返回一个值,就继续进行迭代,不然,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序作了什么,它只关心返回值,由于基于返回值,它将继续执行或退出。
无论怎么说,回调函数是继续自C语言的,于是,在C++中,应只在与C代码创建接口,或与已有的回调接口打交道时,才使用回调函数。除了上述状况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。spa
三、C语言回调函数的实现(函数指针的使用)指针
程序员经常须要实现回调。下面将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括彻底依赖于不一样语法和语义规则的类成员函数。
声明函数指针
回调函数是一个程序员不能显式调用的函数;经过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点难以想象,但若是你熟悉函数声明的通常方法,便会发现函数指针的声明与函数声明很是相似。请看下面的例子:
void f();//函数原型
上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法以下:
void (*) ();
分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数。注意本例中尚未建立指针变量-只是声明了变量类型。目前能够用这个变量类型来建立类型定义名及用sizeof表达式得到函数指针的大小:
// 得到函数指针的大小
unsigned psize = sizeof (void (*) ());
// 为函数指针声明类型定义
typedef void (*pf) ();
pf是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名能够隐藏复杂的函数指针语法。
指针变量应该有一个变量名:
void (*p) (); //p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量即可以赋值,值的内容是署名匹配的函数名和返回类型。例如:
void func()
{
}
p = func;
p的赋值能够不一样,但必定要是函数的地址,而且署名和返回类型相同。
传递回调函数的地址给调用者
如今能够将p传递给另外一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:
voidcaller(void(*ptr)())
{
ptr();
}
void func();
int main()
{
p = func;
caller(p);
}
若是赋了不一样的值给p(不一样函数地址),那么调用者将调用不一样地址的函数。赋值能够发生在运行时,这样使你能实现动态绑定。
调用规范
到目前为止,咱们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,能够在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范当作是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int);
// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));
// 在p中企图存储被调用函数地址的非法操做
__cdecl int(*p)(int) = callee; // 出错
指针p和callee()的类型不兼容,由于它们有不一样的调用规范。所以不能将被调用者的地址赋值给指针p,尽管二者有相同的返回值和参数列。
四、函数指针&回调函数&linux中的signal函数
函数指针在linux中的应用signal函数
在Unix/Linux中signal函数是比较复杂的一个,其定义原型以下:
void (*signal(intsigno,void (*func)(int))) (int)
signal(int signo,void(*func)(int))是signal函数的主体.
须要两个参数:int型的signo,以及一个指向函数的指针.
void (*func)(int).
这个函数中,最外层的函数体
void (* XXX )(int)代表其返回值是一个指针(函数指针),指向一个函数XXX的指针,XXX所表明的函数须要一个int型的参数,返回void。
正是因为其复杂性,在[Plauger 1992]用typedef来对其进行简化
typedef voidSigfuc(int);//这里能够当作一个返回值 .
再对signal函数进行简化就是这样的了
Sigfunc*signal(int,Sigfuc *);
在signal.h头文件中还有如下几个定义
#define SIG_ERR(void (*)())-1
#define SIG_DFL(void (*)())0
#define SIG_IGN(void (*)())1
alarm(设置信号传送闹钟)
相关函数 signal,sleep
表头文件 #include
定义函数 unsigned int alarm(unsigned int seconds);
函数说明 alarm()用来设置信号SIGALRM在通过参数seconds指定的秒数后传送给目前的进程。若是参数seconds 为0,则以前设置的闹钟会被取消,并将剩下的时间返回。返回值返回以前闹钟的剩余秒数,若是以前未设闹钟则返回0。
kill(传送信号给指定的进程)
相关函数 raise,signal
表头文件 #include
#include
定义函数 int kill(pid_t pid,int sig);
函数说明 kill()能够用来送参数sig指定的信号给参数pid指定的进程。参数pid有几种状况:
pid>0 将信号传给进程识别码为pid 的进程。
pid=0 将信号传给和目前进程相同进程组的全部进程
pid=-1 将信号广播传送给系统内全部的进程
pid<0 将信号传给进程组识别码为pid绝对值的全部进程
参数sig表明的信号编号可参考附录D
返回值 执行成功则返回0,若是有错误则返回-1。
错误代码 EINVAL 参数sig 不合法
ESRCH 参数pid 所指定的进程或进程组不存在
EPERM 权限不够没法传送信号给指定进程
pause(让进程暂停直到信号出现)
相关函数 kill,signal,sleep
表头文件 #include
定义函数 int pause(void);
函数说明 pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值 只返回-1。
错误代码 EINTR 有信号到达中断了此函数。
sigaction(查询或设置信号处理方式)
相关函数 signal,sigprocmask,sigpending,sigsuspend
表头文件 #include
定义函数 int sigaction(int signum,const struct sigaction *act ,structsigaction *oldact);
函数说明 sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum能够指定SIGKILL和SIGSTOP之外的全部信号。
如参数结构sigaction定义以下
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
sa_handler此参数和signal()的参数handler相同,表明新的信号处理函数,其余意义请参考signal()。
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
sa_restorer 此参数没有使用。
sa_flags 用来设置信号处理的其余相关操做,下列的数值可用。
OR 运算(|)组合
A_NOCLDSTOP : 若是参数signum为SIGCHLD,则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前,将此信号处理方式改成系统预设的方式。
SA_RESTART:被信号中断的系统调用会自行重启
SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来。
若是参数oldact不是NULL指针,则原来的信号处理方式会由此结构sigaction 返回。
返回值 执行成功则返回0,若是有错误则返回-1。
错误代码 EINVAL 参数signum 不合法,或是企图拦截SIGKILL/SIGSTOPSIGKILL信号
EFAULT 参数act,oldact指针地址没法存取。
EINTR 此调用被中断
sigaddset(增长一个信号至信号集)
相关函数 sigemptyset,sigfillset,sigdelset,sigismember
表头文件 #include
定义函数 int sigaddset(sigset_t *set,int signum);
函数说明 sigaddset()用来将参数signum 表明的信号加入至参数set 信号集里。
返回值执行成功则返回0,若是有错误则返回-1。
错误代码 EFAULT 参数set指针地址没法存取
EINVAL 参数signum非合法的信号编号
sigdelset(从信号集里删除一个信号)
相关函数 sigemptyset,sigfillset,sigaddset,sigismember
表头文件 #include
定义函数 int sigdelset(sigset_t * set,int signum);
函数说明 sigdelset()用来将参数signum表明的信号从参数set信号集里删除。
返回值 执行成功则返回0,若是有错误则返回-1。
错误代码 EFAULT 参数set指针地址没法存取
EINVAL 参数signum非合法的信号编号
sigemptyset(初始化信号集)
相关函数 sigaddset,sigfillset,sigdelset,sigismember
表头文件 #include
定义函数 int sigemptyset(sigset_t *set);
函数说明 sigemptyset()用来将参数set信号集初始化并清空。
返回值 执行成功则返回0,若是有错误则返回-1。
错误代码 EFAULT 参数set指针地址没法存取
sigfillset(将全部信号加入至信号集)
相关函数 sigempty,sigaddset,sigdelset,sigismember
表头文件 #include
定义函数 int sigfillset(sigset_t * set);
函数说明 sigfillset()用来将参数set信号集初始化,而后把全部的信号加入到此信号集里。
返回值 执行成功则返回0,若是有错误则返回-1。
附加说明 EFAULT 参数set指针地址没法存取
sigismember(测试某个信号是否已加入至信号集里)
相关函数 sigemptyset,sigfillset,sigaddset,sigdelset
表头文件 #include
定义函数 int sigismember(const sigset_t *set,int signum);
函数说明 sigismember()用来测试参数signum 表明的信号是否已加入至参数set信号集里。若是信号集里已有该信号则返回1,不然返回0。
返回值信号集已有该信号则返回1,没有则返回0。若是有错误则返回-1。
错误代码 EFAULT 参数set指针地址没法存取
EINVAL 参数signum 非合法的信号编号
signal(设置信号处理方式)
相关函数 sigaction,kill,raise
表头文件 #include
定义函数 void (*signal(int signum,void(* handler)(int)))(int);
函数说明 signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。若是参数handler不是函数指针,则必须是下列两个常数之一:
SIG_IGN 忽略参数signum指定的信号。
SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。
关于信号的编号和说明,请参考附录D
返回值返回先前的信号处理函数指针,若是有错误则返回SIG_ERR(-1)。
附加说明在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,若是要改变此操做请改用sigaction()。
sigpending(查询被搁置的信号)
相关函数 signal,sigaction,sigprocmask,sigsuspend
表头文件 #include
定义函数 int sigpending(sigset_t *set);
函数说明 sigpending()会将被搁置的信号集合由参数set指针返回。
返回值执 行成功则返回0,若是有错误则返回-1。
错误代码 EFAULT 参数set指针地址没法存取
EINTR 此调用被中断。
sigprocmask(查询或设置信号遮罩)
相关函数 signal,sigaction,sigpending,sigsuspend
表头文件 #include
定义函数 int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);
函数说明 sigprocmask()能够用来改变目前的信号遮罩,其操做依参数how来决定
SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数set 指定的信号遮罩做联集
SIG_UNBLOCK 将目前的信号遮罩删除掉参数set指定的信号遮罩
SIG_SETMASK 将目前的信号遮罩设成参数set指定的信号遮罩。
若是参数oldset不是NULL指针,那么目前的信号遮罩会由此指针返回。
返回值 执行成功则返回0,若是有错误则返回-1。
错误代码 EFAULT 参数set,oldset指针地址没法存取。
EINTR 此调用被中断
sleep(让进程暂停执行一段时间)
相关函数 signal,alarm
表头文件 #include
定义函数 unsigned int sleep(unsigned int seconds);
函数说明 sleep()会令目前的进程暂停,直到达到参数seconds 所指定的时间,或是被信号所中断。
返回值 若进程暂停到参数seconds 所指定的时间则返回0,如有信号中断则返回剩余秒数。