1
一、函数指针的定义二、函数指针的使用三、函数指针作为形参
2
一、函数指针的定义函数是由执行语句组成的指令序列或代码,这些代码的有序集合根据其大小被分配到一定的内存空间中,这一片内存空间的起始地址就称为该函数的地址,不同的函数有不同的函数地址,编译器通过函数名来索引函数的入口地址 。
为了方便操作类型属性相同的函数,C/C++引进了函数指针 (function pointer)。
函数指针是指向函数代码入口地址的指针 。
3
以两个入口参数为例,设 type,T1,T2,Tj,Tk是系统内置的基本类型或用户已经声明的类型。考虑一系列函数的定义或说明:
type f(T1 a,T2 b) {语句序列; }
type f1(T1 a1,T2 a2) ;,..
type fn(T1 n1,T2 n2) {语句序列; }
为方便操作这些入口参数类型相同个数相同、返回类性也相同、函数名称不同的函数,可以引进函数指针。重载函数仅是函数名相同的函数。
4
函数指针的定义形式为:
type (*pf)(T1,T2 );
类型 (*函数指针名 )(类型 1,类型 2);
该定义语句引进函数指针 pf,具有类型属性抽象为
type (*)(T1,T2),表示这个指针操作入口参数分别为 T1,T2类型的数据、返回 type类型数据的函数。
如此定义的函数指针 pf称为一级函数指针,由于函数类型属性相同的代码其内存空间的长度是不一致的,因此不对一级函数指针进行加减运算。
函数指针指向程序的代码区,数据指针指向数据存储区。
5
函数指针用于共性地处理类型属性相同名称不同的函数族,因此定义时不需要具体的形参名;多余地摆设形参名不算错误,如,
type (*pf)(T1 x,T2 y),
但形参名 x 和 y形同虚设。
在函数指针定义语句中的圆括号绝对不能省略,如果省略圆括号就成为下面形式:
type *pf(T1,T2 );
这是一个返回 type*型的指针值的函数原型。函数指针的定义语句涉及到内存的空间分配,定义的是一个函数指针;函数原型说明仅只是告知编译器全局函数名是备案可查的。定义语句是唯一的,说明语句则无妨有所重复。
6
对于变量定义语句,
long (*p)(int,long);
类型抽象为 long (*)(int,long),根据运算符的优先级和结合性分解为:
1,( *),一个指针
2,(int,long),函数调用
3,long,long型的数据编译系统根据运算的优先级别建立一个指针,这个指针可以是局部作用域的,该指针用于函数调用,函数调用返回
long型的数据。
7
而对于函数原型说明语句:
long *f(int,long);
类型抽象为 long * (int,long)根据运算级别分解为:
1,(int,long),函数调用
2,*,指针
3,long,long型的数据系统理解 f为一个函数名,函数名具有全局作用范围,
该函数返回一个指针,这个指针访问 long型数据。
注意 { long (*p) [4][5];}是指向三维数组的指针定义。
8
二、函数指针的使用函数指针是函数代码入口地址的变量,本身不提供独立的函数代码。访问函数指针之前需要初始化,函数名代表函数代码的入口地址。
函数原型 [type f(T1,T2);]中的 f与函数指针 pf具有类型属性 type (*)(T1,T2 ),f可以赋值给相同类型的指针 pf。函数指针初始化的形式有两种:一是直接赋值,二是加取地址运算符赋值。格式如下:
函数指针名 =函数名 ;
函数指针名 =&函数名 ;
也可以在定义时进行初始化:
type (*pf)(T1,T2 )= &f;
type (*pf)(T1,T2 )= f;
9
函数的地址在函数名中,取函数的地址不写成:
pf= f(T1,T2);
上面这种写法遗漏了函数的返回类型信息。而是写成:
pf= f;
设 v1,v2分别是与 T1,T2匹配的实参。函数指针赋值之后就可以利用函数指针调用相关的函数,使用函数指针满足函数虚实结合的规则,其使用的格式有两种如下:
,函数指针名 (实参列表 ); pf(v1,v2);
此种函数指针作为函数名直接调用相关的函数,键入的字符少,程序风格短小精悍。
(*函数指针名 )(实参列表 ); (*pf)( v1,v2);
这种访问函数指针的形式调用相关的函数,类型属性清晰可鉴一目了然。
10
如果 pf是返回指针的函数,则 pf( v1,v2)是指针表达式,*pf( v1,v2)就是访问指针指向的内存。因此,
(*pf)( v1,v2)
不写成 *pf( v1,v2),这导致其它的语义或错误。
函数指针调用入口类型属性相同的函数称为函数的间接调用。
函数指针调用的函数是函数指针最新指向的函数。
f(v1,v2)是直接调用,如果 fp指向 f,则 pf(v1,v2)是 f函数的间接调用。
如果 fp指向 fn,则此时 pf (v1,v2)相当于 fn(v1,v2)即 pf
间接调用 fn函数。
11
[例 ]函数指针间接调用函数
#include<stdio.h>
int max ( int x,int y){ return x>y?x:y;}
int min ( int x,int y){ return x<y?x:y;}
void main ()
{ int ( *f ) ( int,int )=max;
printf ( "%d,%d\t",max (2,3),f (5,4));
f=&min;
printf (" %d,%d\t",min (2,3),f (5,4));
}
//输出,3,5 2,4
12
在 C++中函数指针只能指向类型属性相同的函数地址,
除非强制类型转换。因此定义语句 {int (*f)(int,int)=max;}不能写为 {int (*f)()=max;},两边类型不一致。
早期 C语言中这样写是可以的,但容易引起错误的调用匹配。
不同类型的函数指针之间的赋值是危险的,可以通过强制类型转换完成指针的映射,否则编译器或提出警告或弹出错误显示。
函数指针与数据指针绝不相互赋值。不同类型的函数指针强制类型转换之后发生的函数调用,如果不适当细心地予以复原其结果是不确定的或未必是所需要的,因为两种函数的实参个数、类型并不一致。
13
三、函数指针作为形参存在大量的运算如求方程的根、求函数的积分或微分,
这些运算的流程是相同的,变动的是求值的基本单位函数本身,因此将这些全局函数名匹配函数指针形参,就使得代码具有更多的通用性。
[例 ] 根据公式 =[f(a)+f(a+h)+...+f(b)]h
求定积分
#include<stdio.h>
#include <math.h>
double f (double x) { return exp(x)/(2.5+x*x); }
double g (double x) { return (1+x); }
f x dx f x h
a
b
i
x a
x b
( ) ( )
14
double IntegralEny ( double (*f) (double),
double a,double b,int n=300)
{ const double h= ( b – a )/n;
double sum=0;
for ( double x=a; x<=b; x+=h) sum + =f (x);
return sum*h;
}
double IntegralOne (double a,double b,int n=300)
{ const double h= (b-a)/n;
double sum=0;
for( double x=a; x<=b; x+=h) sum+=f (x);
return sum*h;
}
15
void main (void)
{ printf ( "%f\t",IntegralEny ( &f,1,2 ));
printf ( "%f\n",IntegralEny ( f,1,2,500 ));
printf ( "%f\n",IntegralOne (1,2 ));
printf ( "%f\t",IntegralEny ( g,1,2 ));
printf ( "%f\n",IntegralEny (&g,1,2,600));
}
//输出结果,
0.947337 0.947577
0.947337
2.498333 2.499167
16
IntegralEny 和 IntegralOne 的函数体中的代码是一样的,但 sum + =f (x) 的 f在两个函数中具有不同的含义,
IntegralEny函数中 f是类型为 double (*) ( double )的局部变量,而 IntegralOne中的 f是具有全局作用域的函数名,编译器优先采用局部名。
IntegralOne只能局限于一个名为 f的函数运算。
IntegralEny则通过虚实结合可以对函数类型属性为
double (*) ( double )的函数族进行操作,因此函数指针提供了更大的代码重用性和灵活性。
17
区间二分法求单变量实函数的根,
单变量实函数 y=f(x)在区间 [a,b]上单调连续,如果在区间端点函数的值 f(a),f(b)异号,则方程在区间内存在根 x满足:
f(x)=0
区间二分法的基本思路是:将方程所在的区间平分为两个子区间,接着判断根属于哪个子区间;将有根的区间继续一分为二,再判断根所隶属的更小的子区间,如此循环,
最后求出满足给定精度的近似根,二分法的收敛于根的速度是相当快的,在数学上已经证明速度以 1/2为公比的等比级数收敛。
18
f(a)*f(x)<0时,b=x
f (a)
y
x
x
y f (b) f (b)
a a b b
x
x
f (x)
f (x)f (a)
f(a)*f(x)>=0时,a=x
二分法编程关键之点是确定寻根的无穷迭代中根所在的子区间,并且给出合适的跳出循环的条件,循环迭代次数制约于该收敛的条件或近似根与理论值的接近程度。
19
下面是具体的编程步骤:
1.计算区间端点 [a,b]的函数值 f(a),f(b) 如果 f(a)与 f(b) 同号区间无根,程序通过 return语句带标志 0退出。
2.取中点 x=(a+b)/2
3.确定根所在的子区间如果 f(a)与 f(x)异号有 f(a)*f(x)<0;
则根在子区间 [a,x] 此时令 b=x,如果 f(a)*f(x)>=0,此时根必在子区间 [x,b] 此时令 a=x。
4.近似根的目标条件:判断动态子区间 [a,b]<eps,如果子区间小于给定的精度,则认为近似根已经找到,近似根就是中点的值,此时通过 return语句带成功标志 1退出二分法求根函数。不然从步骤 2继续迭代。
20
根据这些思路就容易实现具体的程序如下:
1.single函数用于求区间 [a,b]之间的单根,引用形参
real& x中的 x表示求得的根,这是一个返回 int整型变量的函数,返回 0代表求根为空,返回 1表示求根成功。
2.muliply函数用于求区间 [a,b]之间的多个根,该函数返回在区间实际求得的根的个数 m,入口参数 n用于区间的细分数,n越大捕获区间的所有根的能力越强,入口的指针 xr
期望指向 n个元素的实在数组匹配,调用返回时相应实在数组的前 m个元素存放着区间的根,其顺序从小到大排列。
21
[例 ]二分法求方程的根
#include<stdio.h>
#include<math.h>
typedef double real;
int single (real(*f) (real),real a,real b,real& x)
{ const real eps=1.0e-12;
if (f(a)*f(b)>0.0 ) return 0;
for(;;)
{ x = (a+b) / 2.0;
if (f(a)*f(x)<0.0) b = x;
else a = x
if(b-a <= eps) { x =(a+b)/2.0; return 1; }
}
}
22
int muliply(real(*f) (real),real a,real b,int n,real xr[ ])
{ const real dx= (b-a)/n;
int m=0; real x;
for (int k=0;k<n;k++)
{ b=a+dx;
if (single (f,a,b,x)==1)
{ xr [m]=x; m++; }
a=b;
}
return m;
}
real f1(real x) { return ((x-2.0)*x-4.0)*x-7.0;}
real f2(real x) { return exp(x)+x-2; }
real fm(real x)
{ return (((x-29.1)*x+196.2)*x+114.2)*x-782.6; }
23
void main (void)
{ real root;
single(f1,3,4,root);
printf ("f1(%f)=%16.13f\t",root,f1(root));
single (f2,0,2,root);
printf ("f2(%f)=%16.13f\n",root,f2(root));
real* const p=new real[4];
int m=muliply (fm,0,100,100,p);
for (int k=0; k<m; k++)
printf ("fm(%9.6f)=%016.13f\n",p[k],fm(p[k]));
delete []p;
}
24
程序运行输出结果:
f1(3.631981)= 0.0000000000071
f2(0.442854)=-0.0000000000009
fm( 1.976236)=00.0000000001032
fm(11.150718)=-0.0000000001276
fm(17.951383)=00.0000000004731
25
牛顿迭代法下面是单根牛顿计算法方法中一些数学上的结论。
设方程 f(x)=0在区间 [a,b]上满足条件:
1,f(a)*f(b)<0;
2,f(x)的一阶导数二阶导数存在,不变号 ;
3,选取,使得 f(x0)f’’(x)>0则方程方程 f(x)=0在区间 [a,b]
上有唯一根,取初值 x0,牛顿迭代公式:
Xn+1=xn- f(xn)f’(xn) n=0,1,…
计算得到的序列 在初值 X0选取合适下具有二阶收敛速度,其近似根 Xm取决于收敛条件或其中 是预定的精度,两个条件实质上等价的。?)x(f m
n1n xx
...x,..,x,x,n10
26
[例 ] 牛顿迭代法求方程的根
#include<stdio.h>
#include<math.h>
typedef double real;
int newtonroot (real(*f)(real),real(*df)(real),
real x,real & root)
{ root=88888.8888;
const real eps = 1e-15,
maxiterations=616,nearzero = 1e-2
real lastx; int i=0; int erinfo = 0;
do
{ if (fabs(df(x)) > nearzero)
{ lastx = x; x - = f (x) / df (x);
27
if (fabs(x-lastx)<=eps||fabs(f(x))<eps)
{ erinfo =1; root=x; }
i++;
}
else erinfo =111;
}
while ( i < maxiterations && erinfo == 0);
if ( i == maxiterations) erinfo = 333;
return erinfo;
}
28
real funct (real x) { return ((x-2.0)*x-4.0)*x-7.0; }
real dfunct (real x) { return (3.0*x-4.0)*x-4.0; }
void main (void)
{ real root;
switch(newtonroot (funct,dfunct,4,root))
{ case 111,printf ("df/dx is near zero\n"); break;
case 333,printf ("iteration number passed max\n");
break;
case 1,printf("funct(%f)=%19.17e\n",root,funct(root));
break;
}
}
//输出,funct(3.631981)=2.66453525910037570e-015
29