0. 什么是函数指针
指针是C++中的一种复合类型,是“指向(pointer to)”另外一种类型的复合类型,实现了对其他对象的间接访问。
函数指针指向的不是对象,而是函数。它基于函数类型的定义。
回想函数的三要素:返回类型、函数名、形参类型,函数类型指的就是由返回类型和形参类型共同决定的。
bool shorter(const string &, const string &);
// 函数的类型是
// bool(const string &, const string &)
// 是这样的一类函数:
// 返回值是bool类型,需要两个const string&类型的函数
此时,可以将函数名位置用指针替换,就声明了指向上述函数类型的指针:
bool (*pf)(const string &, const string &);
// 未初始化
解析上面的声明,依旧是按照从内向外、先右后左的口诀。
很多读者看到函数指针的声明和定义方法就望而却步了,但其实记住从内向外、先右后左以及学会类型处理,理解函数指针很简单。
关于函数指针,我们需要了解:
- 函数指针的声明&定义
- 函数指针的使用
- 类型处理(typedef、using、auto、decltype)
1. 函数指针的声明&定义
// 声明了一个函数
bool shorter(const string &, const string &);
// 声明并初始化了一个函数指针
bool (*pf)(const string &, const string &) = shorter;
// 可以省略取地址运算符&,也可以使用
// bool (*pf)(const string &, const string &) = &shorter;
注意:(*pf)
的括号必不可少。
上述代码在初始化pf
时,并没有在等号=
右侧使用&
,原因是①把函数名作为一个值使用时,函数自动转换成指针(除了decltype)。
2. 函数指针的使用
2.1 函数指针的调用
定义了一个有明确指向的函数指针后,可直接通过调用运算符()
调用该函数:
pf("hello", "goodbye");
可以不解引用指针,也可以解引用,但是必须加括号:
(*pf)("hello", "goodbye");
其实,带有明确指向的函数指针属于可调用对象(callable object)。
2.2 函数指针作为形参
我们可以声明一个具有函数指针形参的函数:
void callCompare(bool pf(const string &, const string &), int count);
// 第一个参数是函数类型为
// bool(const string &, const string &)
// 的函数指针
注意:此处虽然看起来是函数类型的声明,但实际上②函数类型作为形参会自动的转换为相应类型的函数指针。当然写成指针类型也是可以的。
void callCompare(bool (*pf)(const string &, const string &), int count);
// 和上面等价,显式添加了*
在使用callCompare
时:
callCompare(shorter, 1);
这里也没有用取地址运算符&
,和1.1叙述的原因一样。
2.3 函数指针作为返回值
假如我想定义一个返回与shorter
同类型函数指针的函数,该如何声明呢?
从里向外,先右后左。
首先确定我想定义的函数的三要素:
- 函数名,假设retFunc
- 参数列表,假设接受一个int
- 返回值,一个与
shorter
同类型函数指针bool (*)(const string &, const string &)
自然而然想到可以这么写:
// ERROR,这是错误的
bool (*)(const string &, const string &) retFunc(int);
这么写一定是不行的,因为左边一大坨不符合编译器编译的原理,从retFunc向左看到了参数列表,完全不知道是什么。
因此必须把指针*
放在retFunc的左边。
bool (*retFunc(int))(const string &, const string &);
①从标识符开始,向右看到了参数列表,因此retFunc是一个函数。
②右括号,不能向右了,向左,看到了指针*
,因此返回的是一个指针。
③看到左括号,向外,继续向右,看到参数列表,因此返回的是函数指针。
④再向左,看到了bool,可以确定了返回的函数指针的类型。
3. 类型处理
若每次都像上面一样使用这么复杂的类型定义,程序一定很乱。
这时,typedef、using、auto、decltype就派上了用场。
我们可以用typedef
、using
给函数类型声明一个简单的名字;在明确知道使用的是哪个函数时,可以使用auto
和decltype
。
例如:
// 声明了一个函数类型
using FuncType = int(int &, int);
// typedef int FuncType(int &, int);
// using PFuncType = int (*)(int &, int);
typedef int (*PFuncType)(int &, int);
// 下面的函数就是上面的类型
int add_to(int &des, int ori) {
return des += ori;
}
int minus_to(int &des, int ori) {
return des -= ori;
}
int multiply_to(int &des, int ori) {
return des *= ori;
}
int divide_to(int &res, int ori) {
return res /= ori;
}
int main() {
FuncType *pf0 = add_to;
auto *pf1 = minus_to;
decltype(multiply_to) *pf2 = multiply_to;
PFuncType pf3 = divide_to;
int a = 4;
// 通过函数指针调用
pf0(a, 2); // 6
pf1(a, 3); // 3
pf2(a, 4); // 12
pf3(a, 3); // 4
}
这样的声明就简单多了。
注意:decltype和auto不会自动的获得函数指针类型,都需要添加*
。
4. 总结
函数类型是根据函数定义的返回值和形参列表抽象出来的一类函数。
可以使用函数类型定义指向同种类型的函数指针。
使用函数指针可以间接调用函数,且可以实现函数指针作为形参、函数指针作为返回值等操作。
在使用函数指针时,使用类型别名(typedef、using)和类型推断(auto、decltype),可以使代码更清晰。