函数指针(指向函数的指针)
与数据项类似,函数也有自己的地址。函数的地址是存储其机器代码的内存的开始地址。例如,可以编写将另一个函数的地址作为参数的函数,这样第一个函数将能够找到第二个函数,并运行它。与直接调用另一个函数相比,这种方法显得很笨重,但它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针的用法需要以下三个步骤:
- 获取函数的地址
- 声明一个函数指针
- 使用函数指针来调用函数
1.获取函数的地址
只要使用函数名(后面不跟参数)就可以获取到函数地址,即函数名就是该函数的地址。要想将函数作为参数进行传递,必须传递函数名,例如 Func()是一个函数,则 Func 就是该函数的地址。同时读者一定要着重区分传递的是函数地址还是函数的返回值:
one(Func); //传递的是 Func 函数的地址
two(Func()); //传递的是 Func() 函数的返回值
注:one() 调用使得 one() 函数能够在其内部调用 Func() 函数。two() 调用首先调用 Func() 函数,然后将Func() 函数的返回值传递给 two() 函数。
2.声明函数指针
声明某种数据类型的指针时,必须指定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的参数列表。也就是说,声明应像函数原型那样指出有关函数的信息。例如:
有一个函数:double pam(int);
则正确的指针类型声明为:double (*pf)(int); //注意 * 的优先级。
从上面可以看出,这与 pam() 声明类似,只是将 pam 替换为了 (*pf)。由于 pam 是函数,因此 (*pf) 也是函数,则 pf 就是函数指针。通常,要声明指向特定类型的函数指针,可以首先编写这样的函数原型,然后用(*pf)替换函数名。这样 pf 就是函数指针。
注:为提供正确的运算符优先级,必须在声明中使用括号将 *pf 括起。括号的优先级比 * 运算符高,因此 *pf(int) 意味着 pf() 是一个返回指针的函数,而 (*pf)(int) 意味着 pf 是一个指向函数的指针。
double (*pf)(int); // pf 是一个指向返回值为 double 有一个 int 参数函数的指针
double* pf(int); // pf 是有一个 int 参数返回值为 double 型指针的函数。
正确声明后,便可将相应的函数地址赋给它:
double pam(int);
double (*pf)(int);
pf = pam;
//其他声明方式
double (*pf)(int) = pam; //直接初始化
auto pf = pam; //自动获取类型并初始化(C++11)
//注意,pam()的形参和返回值类型必须与 pf 相同。如果不相同,编译器将拒绝这样赋值。
例如有如下函数原型:
void time(int line, double (*pf)(int));
//该声明指出,第二个参数是一个函数指针,它指向的函数接受一个 int 参数,并返回一个 double 值。
要想让 time() 使用 pam() 函数,需要将 pam() 的地址传递给它:
time(50, pam);
3.使用指针来调用函数
(*pf)扮演的角色和函数名相同,因此使用(*pf)时,只需要将它看做函数名即可:
double pam(int);
double (*pf)(int);
pf = pam;
double x = pam(4);
double y = (*pf)(5);
double y = pf(5); //C++允许这样调用函数和第 5 行语句等效
综合举例
//
// main.cpp
// fun_ptr
//
// Created by 刘一丁 on 2019/9/21.
// Copyright © 2019年 LYD. All rights reserved.
//
#include <iostream>
using namespace std;
double betsy(int);
double pam(int);
//second argument is pointer to a type double function that
//takes a type int argument
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.5 * 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";
}
//output:
//How many lines of code do you need? 100
//Here's Betsy's estimate:
//100 lines will take 50 hour(s)
//Here's Pam's estimate:
//100 lines will take 7 hour(s)
4.函数指针数组(数组指针和指针数组)
先解释下数组指针(指向数组的指针)和指针数组(存储指针的数组)
//定义一个指针数组,该数组中每个元素是一个指针,每个指针指向哪里就需要程序中后续再定义了。
int *p[4];
//定义一个数组指针,该指针指向含4个元素的一维数组(数组中每个元素是int型)。
int (*p)[4];
//注,[]优先级高于*
下面是一些函数的原型,他们的形参和返回值相同:
const double* f1(const double ar[], int n);
const double* f2(const double [], int n);
const double* f3(const double *, int n);
这些函数的形参看起来不同,但实际是相同的。在函数原型中,参数列表 const double ar[],与 const double *ar 的含义完全相同。其次在函数原型中,可以省略标识符。因此,const double ar[] 可简化为 const double[],而 const double *ar 可以简化为const double *。因此,上述所有函数形参的含义都相同,另一方面,函数定义必须提供标识符,因此需要使用 const double ar[] 和const double *ar。
现要声明一个指针,他可以指向这三个函数之一。假定该指针名为 pa,则只需将目标函数原型中的函数名替换为(*pa):
const double* (*p1)(const double *, int);
//可在声明的时候进行初始化
const double* (*p1)(const double *, int) = f1;
//使用 C++11 的自动类型推断功能是,代码要简单的多:
auto p2 = f2;
//调用语句
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
cout << p2(av, 3) << " " << *p2(av, 3) << endl;
根据前 3 点,可以知道,(*p1)(av, 3) 和 p2(av, 3) 都调用指向的函数(这里为 f1() 和 f2()),并将 av 和 3 作为参数传递。因此,显示的是这两个函数的返回值。返回值的类型为 const double*(即 double 值的地址),因此在每条 cout 语句中,前半部分显示的都是一个 double 值的地址。想要查看存储在这些地址的实际值,需要将运算符*应用于这些地址,如表达式 *(*p1)(av, 3) 和 *p2(av, 3)。
函数指针数组
const double* (*pa[3])(const double *, int) = {f1, f2, f3};
/*
上述声明中 pa[3],表示 pa 是一个包含三个元素的数组,运算符 [] 优先级高于 * ,因此 *pa[3] 表示
pa 是一个包含三个指针的数组。
声明的其他部分表示:形参为 const double *, int 且返回类型为 const double* 的函数。因此,
pa 是一个包含三个指针的数组,其中每个指针都指向这样的函数,即将 const double *, int 作为形参,
并返回一个 const double*。
*/
auto pb = pa; //数组名是指向第一个元素的指针,因此 pa 和 pb 都是指向函数指针的指针。
调用方法
const double* px = pa[0](av, 3);
const double* py = (*pb)[1](av, 3);
//要获得指向 double 值,可使用运算符*:
double x = *pa[0](av, 3);
double y = *(*pb[1])(av, 3);
指向整个数组的指针。
如果这个指针名为 pd,则需要指出它是一个指针,而不是数组。这意味着应该声明为 (*pd)[3] ,其中的括号让标识符 pd 与 * 现结合。换句话说 pd 是一个指针,它指向一个包含三个元素的数组。具体声明如下:
const double* (*(*pd)[3])(const double*, int) = &pa;
调用函数时,需要知道:既然 pd 指向数组,那么 *pd 就是数组,而 (*pd)[i] 是数组中的元素,即函数指针。因此,较简单的函数调用是 (*pd)[i](av, 3),而 *(*pd)[i](av, 3) 是返回的指针指向的值。也可以使用第二种使用指针调用函数的语法:使用 (*(*pd)[i])(av, 3) 来调用函数,而 *(*(*pd)[i])(av, 3) 是指向的 double 值。
注意 pa(数组名,表示地址)和&pa 之间的差别。在大多数情况下, pa 都是数组第一个元素的地址,即 &pa[0]。因此,他是单个指针的地址。但 &pa 是整个数组(即三个指针块)的地址。从数字上说, pa 和 &pa 的值相同,但他们的类型不同。一种差别是, pa + 1 为数组中下一个元素的地址,而 &pa + 1 为数组 pa 后面一个 12 字节内存块的地址(假定地址为 4 字节)。另一个差别是,要得到第一个元素的值,只需对 pa 解除一次引用,但需要对 &pa 解除两次引用:
**&pa == *pa == pa[0];
5.为什么会用到函数指针
(1)实现运行时的多态性
(2)实现函数回调
具体可以参考其他博主写的这篇博文https://blog.csdn.net/zhangsan_3/article/details/53535551