C++中的函数指针和函数对象

两方面用途:

  • 函数调用
  • 作为函数参数进行传递
1
2
3
const double * (*pf[3])(const double *, int) = {f1, f2, f3}
// pf 是一个函数指针数组, 数组中的每一个元素都是一个函数指针,
// 指向了形式为 const double * (const double *, int) 的函数

函数指针

函数指针:指向函数地址的指针变量。 在C++编译时,每一个函数都有一个入口地址,该地址是存储函数机器语言代码的内存的开始地址。函数指针主要有两方面的用途:调用函数和用作函数参数。函数指针的声明方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
double pam(int);

double (* pf)(int); //将星号与函数名括起来,表示当前星号修饰的是函数名,而不是类型
//可以理解为将pam替换成了(*pf)
//通常,要声明指向特定类型的指针,可以首先编写这种函数的原型,然后用(*pf)替换函数名即可
pf = pam; // 函数名pam本身就是函数地址,所以可直接赋值,无需用取址符,但是要求pam的特征标必须与pf相同
pf = &pam; //但实际上,也可以使用取址符,效果与上面 等价(why?)

//以下三种方式都是合法且等价的
double x = pam(4);
double y = (* pf)(5);
double z = pf(6);

从上面的语句可以看出,C++同时允许使用带星号和不带星号的函数指针,最神奇的是效果居然是等价的(对于给函数指针赋值时,函数名带取址符和不带取址符的效果也是等价的!)!

导致以上“神奇事情”发生的原因是,在C++指定函数指针标准时,出现了两种不同的声音:一种学派认为,由于pf是函数指针,而*pf是函数,因此应将(*pf)()用作函数调用。另一种学派认为,由于函数名是指向该函数的指针,指向函数的指针的行为应该与函数名相似,因此应将pf()用作函数调用使用。C++进行了折衷——这两种方式都是正确的,虽然看上去它们在逻辑上是相冲突的。

返回指向函数的指针

1
2
int (* func(int))(double, double);
// func 具有形参表, 因此它本身是一个参数为 (int) 的函数, 而该函数的返回值则是一个指向 int(double, double) 的函数指针.

下面的声明语句指出了一个函数指针数组,其中每个指针都指向这样的函数:将const double*int作为参数,返回一个const double *

1
const double* (* pa[3]) (const double * , int ) = {f1,f2,f3};

这里不能使用auto,因为auto只能用于单值初始化,而不能用于初始化列表,但声明数组pa后,可以使用auto

1
auto pb = pa; //

pa和pb都是指向函数指针的指针(数组首地址),使用时可以用下标法,也可以当做指针使用:

1
2
3
4
5
6
const double *px = pa[0](av,3);
const double *py = (* pa[0])(av,3); //如果带星号,则不能少括号

//要获得值,可使用运算符
double x = *pa[0](av,3); //少了括号以后,会取得返回值地址指向的值
double y = *(* pb[1])(av,3);

如果继续声明了指向函数指针整个数组的指针,则使用方法又有些不同,见下面:

1
2
3
4
5

auto pc = &pa; //pc指向pa的地址

*pd[3]; // 相当于 pa[3],代表一个包含三个指针的指针数组
(* pd)[3]; // 代表pc是一个函数指针,指向含3个元素的指针数组

函数对象

函数对象的实质是对运算符()的重载, 函数对象也叫做仿函数。

1
2
3
4
5
6
7
8
class A{

public:
int operator()(int x){return x;}
};

A a;
std::cout << a(5) std::endl;

函数对象与函数指针的比较

函数对象的优势在于,可以把附加对象保存在函数对象中,也可以存储一些其他的额外信息,甚至可以用来封装类成员函数指针。

但是,函数对象本身并不是指针,因此在使用函数指针的场合中,往往无能为力。例如,不能将对象传给qsort函数!因为它只接受函数指针。