详细理解C指针指针函数与函数指针和回调函数.docx
《详细理解C指针指针函数与函数指针和回调函数.docx》由会员分享,可在线阅读,更多相关《详细理解C指针指针函数与函数指针和回调函数.docx(11页珍藏版)》请在冰豆网上搜索。
![详细理解C指针指针函数与函数指针和回调函数.docx](https://file1.bdocx.com/fileroot1/2023-4/29/64bc5d28-0a48-47e9-94e8-f8f19c27b535/64bc5d28-0a48-47e9-94e8-f8f19c27b5351.gif)
详细理解C指针指针函数与函数指针和回调函数
1、函数指针:
指针函数是指带指针的函数,即本质是一个函数。
我们知道函数都又返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
其定义格式如下所示:
返回类型标识符*返回名称(形式参数表)
{函数体}
返回类型可以是任何基本类型和复合类型。
返回指针的函数的用途十分广泛。
事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。
比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。
例如下面一个返回指针函数的例子:
float*find(float(*pionter)[4],intn)/*定义指针函数*/
{
float*pt;
pt=*(pionter+n);
return(pt);
}
main()
{
staticfloatscore[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};
float*p;
inti,m;
printf("Enterthenumbertobefound:
");
scanf("%d",&m);
printf("thescoreofNO.%dare:
\n",m);
p=find(score,m);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
}
学生学号从0号算起,函数find()被定义为指针函数,起形参pointer是指针指向包含4个元素的一维数组的指针变量。
pointer+1指向score的第一行。
*(pointer+1)指向第一行的第0个元素。
pt是一个指针变量,它指向浮点型变量。
main()函数中调用find()函数,将score数组的首地址传给pointer.
将字符串1(str1)连接字符串2(str2),并输出字符串1.
#include"stdio.h"
char*mystrcpy(char*str1,char*str2)
{
char*p;
p=str1;
while(*str1)
str1++;
while(*str1++=*str2++);
returnp;
}
intmain(void)
{
charstr1[]="ILOVESHY";
charstr2[]=" chj!
";
char*p;
p=mystrcpy(str1,str2);
printf("%s\n",p);
}
例3:
int*GetDate(intwk,intdy)
{
staticintcalendar[5][7]=
{
{1,2,3,4,5,6,7},
{8,9,10,11,12,13,14},
{15,16,17,18,19,20,21},
{22,23,24,25,26,27,28},
{29,30,31,-1,0}
};
return(&calendar[wk-1][dy-1]);
}
int main(void)
{
intwk,dy;
do
{
printf("Enterweek(1-5)day(1-7)\n");
scanf("%d%d",&wk,&dy);
}
while(wk<1||wk>5||dy<1||dy>7);
printf("%d",*GetDate(wk,dy));
}
2,函数指针:
“函数指针”是指向函数的指针变量,因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上一致的。
函数指针有两个用途:
调用函数和做函数的参数。
函数指针的说明方法为:
函数类型(*指针变量名)(形参列表);
“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。
例如:
int(*f)(intx);
double(*ptr)(doublex);
在定义函数指针时请注意:
函数指针和它指向的函数的参数个数和类型都应该是—致的;
函数指针的类型和函数的返回值类型也必须是一致的。
函数指针的赋值
函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名就行了。
例如,
intfunc(intx); /*声明一个函数*/
int(*f)(intx); /*声明一个函数指针*/
f=func; /*将func函数的首地址赋给指针f*/
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
与其他指针变量相类似,如果指针变量pi是指向某整型变量i的指针,则*p等于它所指的变量i;如果pf是指向某浮点型变量f的指针,则*pf就等价于它所指的变量f。
同样地,*f是指向函数func(x)的指针,则*f就代表它所指向的函数func。
所以在执行了f=func;之后,(*f)和func代表同一函数。
由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。
现在我们就讨论如何用函数指针调用函数,它应执行下面三步:
首先,要说明函数指针变量。
例如:
int(*f)(intx);
其次,要对函数指针变量赋值。
例如:
f=func; (func(x)必须先要有定义)
最后,要用(*指针变量)(参数表);调用函数。
例如:
(*f)(x);(x必须先赋值)
例1:
intmax(intx,inty)
{return(x>y?
x:
y);
}
int main(void)
{
inta,b,c;
int(*ptr)(int,int);
scanf("%d,%d",&a,&b);
ptr=max;
c=(*ptr)(a,b);
printf("a=%d,b=%d,max=%d",a,b,c);
return0;
}
例2:
voidFileFunc()
{
printf("FileFunc\n");
}
voidEditFunc()
{
printf("EditFunc\n");
}
intmain(void)
{
void(*funcp)();
funcp=FileFunc;
(*funcp)();
funcp=EditFunc;
(*funcp)();
}
例3:
ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你像怎么做了。
在程序中把哪个函数的地址赋给它,它就指向哪个函数。
而后用指针变量调用它,因此可以先后指向不同的函数,不过注意,指向函数的指针变量没有++和--运算,用时要小心。
3、函数指针数组
关于函数指针数组的定义
关于函数指针数组的定义方法,有两种:
一种是标准的方法;一种是蒙骗法。
第一种,标准方法:
{
分析:
函数指针数组是一个其元素是函数指针的数组。
那么也就是说,此数据结构是是一个数组,且其元素是一个指向函数入口地址的指针。
根据分析:
首先说明是一个数组:
数组名[]
其次,要说明其元素的数据类型指针:
*数组名[].
再次,要明确这每一个数组元素是指向函数入口地址的指针:
函数返回值类型(*数组名[])().请注意,这里为什么要把“*数组名[]”用括号扩起来呢?
因为圆括号和数组说明符的优先级是等同的,如果不用圆括号把指针数组说明表达式扩起来,根据圆括号和方括号的结合方向,那么*数组名[]()说明的是什么呢?
是元素返回值类型为指针的函数数组。
有这样的函数数祖吗?
不知道。
所以必须括起来,以保证数组的每一个元素是指针。
}
第二种,蒙骗法:
尽管函数不是变量,但它在内存中仍有其物理地址,该地址能够赋给指针变量。
获取函数方法是:
用不带有括号和参数的函数名得到。
函数名相当于一个指向其函数入口指针常量。
那么既然函数名是一个指针常量,那么就可以对其进行一些相应的处理,如强制类型转换。
那么我们就可以把这个地址放在一个整形指针数组中,然后作为函数指针调用即可。
完整例子:
#include"stdio.h"
intadd1(inta1,intb1);
intadd2(inta2,intb2);
intmain(intargc,char*argv[])
{
intnuma1=1,numb1=2;
intnuma2=2,numb2=3;
int(*op[2])(inta,intb);
op[0]=add1;
op[1]=add2;
printf("%d%d\n",op[0](numa1,numb1),op[1](numa2,numb2));
getch();
}
intadd1(inta1,intb1)
{
returna1+b1;
}
intadd2(inta2,intb2)
{
returna2+b2;
}
再给出常用的C变量的定义方式:
a)一个整型数(Aninteger)
b)一个指向整型数的指针(Apointertoaninteger)
c)一个指向指针的的指针,它指向的指针是指向一个整型数(Apointertoapointertoaninteger)
d)一个有10个整型数的数组(Anarrayof10integers)
e)一个有10个指针的数组,该指针是指向一个整型数的(Anarrayof10pointerstointegers)
f)一个指向有10个整型数数组的指针(Apointertoanarrayof10integers)
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数(Apointertoafunctionthattakesanintegerasanargumentandreturnsaninteger)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数(Anarrayoftenpointerstofunctionsthattakeanintegerargumentandreturnan
integer)
答案是:
a)inta;//Aninteger
b)int*a;//Apointertoaninteger
c)int**a;//Apointertoapointertoaninteger
d)inta[10];//Anarrayof10integers
e)int*a[10];//Anarrayof10pointerstointegers
f)int(*a)[10];//Apointertoanarrayof10integers
g)int(*a)(int);//Apointertoafunctionathattakesanintegerargumentandreturnsaninteger
h)int(*a[10])(int);//Anarrayof10pointerstofunctionsthattakeanintegerargumentandreturnaninteger
####################################################################
#####################################################
程序员常常需要实现回调。
本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。
注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。
声明函数指针
回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。
要实现回调,必须首先定义函数指针。
尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。
请看下面的例子:
voidf();//函数原型
上面的语句声明了一个函数,没有输入参数并返回void。
那么函数指针的声明方法如下:
void(*)();
让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。
另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。
注意本例中还没有创建指针变量-只是声明了变量类型。
目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:
//获得函数指针的大小
unsignedpsize=sizeof(void(*)());
//为函数指针声明类型定义
typedefvoid(*pfv)();
pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。
使用这个类型定义名可以隐藏复杂的函数指针语法。
指针变量应该有一个变量名:
void(*p)();//p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。
左边圆括弧里星号后的就是指针变量名。
有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。
例如:
voidfunc()
{
/*dosomething*/
}
p=func;
p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。
传递回调函数的地址给调用者
现在可以将p传递给另一个函数(调用者)-caller(),它将调用p指向的函数,而此函数名是未知的:
voidcaller(void(*ptr)())
{
ptr();/*调用ptr指向的函数*/
}
voidfunc();
intmain()
{
p=func;
caller(p);/*传递函数地址到调用者*/
}
如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。
赋值可以发生在运行时,这样使你能实现动态绑定。
调用规范
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSIC/C++的编译器规范。
许多编译器有几种调用规范。
如在VisualC++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。
C++Builder也支持_fastcall调用规范。
调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。
例如:
//被调用函数是以int为参数,以int为返回值
__stdcallintcallee(int);
//调用函数以函数指针为参数
voidcaller(__cdeclint(*ptr)(int));
//在p中企图存储被调用函数地址的非法操作
__cdeclint(*p)(int)=callee;//出错
指针p和callee()的类型不兼容,因为它们有不同的调用规范。
因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。