执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行完函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处。这与阅读文章时停下来看脚注,并在阅读完脚注后返回到以前阅读的地方类似。
与数据项相似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。
double pam(int);
double (*pf)(int);
pf = pam;
double x = pam(4);
double x = (*pf)(5);
为何pam和(*pf)等价呢?由于pf是函数指针,那*pf是函数,因此可将(*pf)( )用作函数调用。
#include <iostream>
using namespace std;
double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));
int main() {
int code;
cout << "How many lines of code do you need? ";
cin >> code;
cout << "Here's Betsy's estimate:\n";
estimate(code, betsy);
cout << "Here's Pam's estimate:\n";
estimate(code, pam);
return 0;
}
double betsy(int lns) { return 0.05 * lns; }
double pam(int lns) { return 0.03 * lns + 0.0004 * lns * lns; }
void estimate(int lines, double (*pf)(int)) {
cout << lines << " lines will take ";
cout << (*pf)(lines) << " hour(s)\n";
}
How many lines of code do you need? |100<Enter>
Here's Betsy's estimate:
100 lines will take 5 hour(s)
Here's Pam's estimate:
100 lines will take 7 hour(s)
可以看到,调用相同的estimate()函数,有不同的效果,这都是函数指针的功劳,它可以按需实现不同的功能。
下面看一个稍复杂点的示例:
#include <iostream>
const double* f1(const double ar[], int n);
const double* f2(const double[], int);
const double* f3(const double*, int);
int main() {
using namespace std;
double av[3] = { 1112.3, 1542.6, 2227.9 };
typedef const double* (*p_fun)(const double*, int);
p_fun p1 = f1;
auto p2 = f2;
cout << "Using pointers to functions:\n";
cout << " Address Value\n";
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
cout << p2(av, 3) << ": " << *p2(av, 3) << endl;
p_fun pa[3] = { f1,f2,f3 };
auto pb = pa;
cout << "\nUsing an array of pointers to functions:\n";
cout << " Address Value\n";
for (int i = 0; i < 3; i++)
cout << pa[i](av, 3) << ": " << *pa[i](av, 3) << endl;
cout << "\nUsing a pointer to a pointer to a function:\n";
cout << " Address Value\n";
for (int i = 0; i < 3; i++)
cout << pb[i](av, 3) << ": " << *pb[i](av, 3) << endl;
cout << "\nUsing pointers to an array of pointers:\n";
cout << " Address Value\n";
auto pc = &pa;
cout << (*pc)[0](av, 3) << ": " << *(*pc)[0](av, 3) << endl;
p_fun (*pd)[3] = &pa;
const double* (*(*pd)[3])(const double *, int) = &pa;
const double* pdb = (*pd)[1](av, 3);
cout << pdb << ": " << *pdb << endl;
cout << (*(*pd)[2])(av, 3) << ": " << *(*(*pd)[2])(av, 3) << endl;
return 0;
}
const double* f1(const double* ar, int n) { return ar; }
const double* f2(const double ar[], int n) { return ar + 1; }
const double* f3(const double ar[], int n) { return ar + 2; }
Using pointers to functions:
Address Value
006FFC0C: 1112.3
006FFC14: 1542.6
Using an array of pointers to functions:
Address Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9
Using a pointer to a pointer to a function:
Address Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9
Using pointers to an array of pointers:
Address Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9
代码里面最难理解的点在于如下这句代码:
p_fun pa[3] = { f1,f2,f3 };
const double* (*(*pd)[3])(const double *, int) = &pa;
首先,运算符优先级:[ ] > () > * T (*pd)[3](K) ——pd是一个指针(函数指针),pd指向一个包含3个元素的数组,每个数组元素都是具有参数列表为K,返回类型为T特征的函数。由于函数名只可以作为指针(函数指针)使用,而不可能有其他函数名用法,所以此声明是无效的!在此只是作个比较说明而已。 T (*(*pd)[3])(K) ——pd是一个指针(函数指针),pd指向一个包含3个元素的数组,每个数组元素都是具有参数列表为K,返回类型为T特征的指针(函数指针)。第一个星号作用于pd变量,第二个星号作用于数组元素。 以上图片是去掉最外层的*(),其就不能作为函数指针数据了,在此无任何意义。
程序解读: 对于函数原型f1、f2、f3这些函数的特征标看似不同,但实际上相同。首先,在函数原型中,参数列表const double ar [ ]与const double * ar的含义完全相同。其次,在函数原型中,可以省略标识符。因此,const double ar [ ]可简化为const double [ ],而const double * ar可简化为const double *。因此,上述所有函数特征标的含义都相同。
从运行结果,可以看到,同一函数的地址是固定不变的。
可以看到auto 的强大,让程序员将主要精力放在设计而不是细节上。
注意到程序中: p_fun pa[3] = { f1,f2,f3 };// f1、f2、f3都是函数名,也是代表着地址,都是指针变量 auto pb = pa; cout << pa[i](av, 3); 对比: auto pc = &pa; cout << (*pc)[0](av, 3) ———————————— 通过IDEA的提示,如下图,更直观比较pb与pc的赋值区别: auto pb = pa; ——对于数组pa是数组首元素地址,这时pb[0]=f1, pb[1]=f2, pb[2]=f3 auto pc = &pa; ——对于数组&pa是整个数组的地址,两边加星号,则*pc=pa, (*pc)[0]=pa[0]=f1, (*pc)[1]=f2, (*pc)[2]=f3 下面这个示例,非常直观解释了以上疑惑:
int arr[3] = {3, 5, 7};
int (*pc)[3] = &arr;
cout << arr[0] << "," << arr[1] << "," << arr[2] << endl;
cout << pc[0] << "," << pc[1] << "," << pc[2] << endl;
cout << *pc[0] << "," << *pc[1] << "," << *pc[2] << endl;
cout << (*pc)[0] << "," << (*pc)[1] << "," << (*pc)[2];
3,5,7
0x3bc51ffafc,0x3bc51ffb08,0x3bc51ffb14
3,-987759876,59
3,5,7
arr是数组首元素地址,步长为1,所以可以直接下标访问。 pc被赋值为整个数组的地址,通过例子打印的地址也可佐证它步长为3,所以除了pc[0]外,其他下标访问的元素是无意义的。 pc指向整个数组的地址&arr,那(*pc)就恒等于arr了,这时(*pc)就是首元素地址了,可以直接用下标访问元素。
还可以看到:使用typedef ,使程序编写简单很多:
typedef const double* (*p_fun)(const double*, int);
|