函数指针,其实就是把函数用指针的形式来表达出来。换句话说,就是指向函数的一个指针变量。其实学过编译器知识的和有过操作系统知识的人基本都明白,c/c++中,函数其实就是一个地址,而指针一般来说指向的也是一个地址,所以函数才可以用指针来表示。虽然c/c++中不同的类型可以用一个指针来强制操作,但这风险是非常大的。而函数和指针的类型一样就表示这种风险降低到了一个可以接受的地步。为什么这样说?因为函数指针应用的条件限制还是比较多的。不同的编译器,早期的和晚期的同样的编译器,可能都会在编译时处理一些细节导致编译错误或者警告的出现。这里提一句,不要忽视编译器的警告,否则,它很可能给你带来你无法理解的伤害。
下面就一个基本的函数指针定义:
int (*pf)(int);
*pf表示这是一个函数指针,后面的int表示有一个参数,开头int表示这个函数指针代表的函数的返回值是整形。通过它的定义和标准函数定义比较可以清楚的发现只把函数名称换成(*pf)这种形式即可以定义函数指针。在早期的编译器中,还可以直接有&取地址符对普通函数名进行取地址来给函数指针变量赋值,就是同样的意思。这里需要注意的是,在高版本的编译器中,这样做可能会出现编译错误。
c/c++中,c++以C中定义函数指针的方法是完全兼容的,除此之外,c++还提供了一些自己的定义函数指针的方式和库的应用,灵活的使用这些定义和方法,就可以在编程中恰到好处的使用函数指针。下面看一下相关的定义和使用:
1、标准的定义
int (*pf)(int);
2、使用typedef定义
typedef int (*PF)(int);
PF p1;//这就是一个函数指针变量,这里提出一个问题,如果*PF不带星号如何处理?有兴趣可以试试,前面的文章有说明和例程
3、使用decltype(c++)和auto
int inc(int);
typedef decltype(inc) inc_pf;
inc_ pf * pf;
typedef decltype(inc)* inc_pf1;
inc_pf1 *pf1;
auto *pf2 = inc;
auto pf3 = inc;
4、std:function
把这个说成是函数指针有点不太准确,但基本上它可以包含函数指针的所有用法,这里就把它归到这里。std:function是一函数包装的模板,常见的函数,函数指针,以及闭包和仿函数都可以用这个模板来进行处理使用。类似下面这种:
std::function fn[] = {
std::plus(),
std::minus(),
std::multiplies()
};
for (auto& x: fn) std::cout << x(10,5) << '\n';
这里需要说明的是,在c++中为保证安全提供了一个std::function::target来安全的转向另外一个类型相同的不同函数。有兴趣可以看看,还是有些小坑儿的,比如对成员函数指针的使用上。这里不再多说,自己搞搞就都明白了。
函数指针的例程其实到处都有,下面把它们总结一下:
1、当成函数使用
2、做为参数(回调函数)使用
3、做为返回值应用
在做为返回值使用时,C和c++还是有一些不同的用法,这里还涉及到一个拖尾类型的问题,在前面分析过《decltype和拖尾返回类型》,可以回头去翻一下。
4、成员函数指针(c++)
5、函数指针的重载
下面看一个综合的例程:
#include
#include
#include
int (*pf)(int a);
typedef int(*PF)(int a);
int inc(int a)
{
return a = a + 1;
}
//回调函数
int TestCallBack(PF cb,int d)
{
return cb(d);
}
//返回函数指针
PF TestReturn()
{
return (*pf);
}
//C++混用
typedef decltype(inc) inc_pf;
inc_pf *ipf = inc;
typedef decltype(inc)* PF1;
PF1 pf1 = inc;
auto fa = inc;
auto* pfa = inc;
//拖尾类型
auto TestEnd(int a,int b)->int(*)(int c)
{
int tmp = a + b;
return ipf;
}
decltype(auto) TestEnd1()
{
return pf1;
}
//test std::function
using FUNC = std::function ;
//overload
int (*pfunc1)(int a);
int (*pfunc2)();
int add()
{
return 10;
}
int add(int a)
{
return a + a;
}
FUNC TestEnd2(FUNC f)
{
return f;
}
//类应用
class Base
{
public:
void A()
{
std::cout<<"this Base A()"< func = inc;
r = func(1);
std::cout << "fucn return is:" << r << std::endl;
r = TestEnd2(func)(2);
std::cout << "std::function call value is:" << r << std::endl;
//pf overload
pfunc1 = add;
pfunc2 = add;
int r0 = pfunc1(10);
int r1 = pfunc2();
std::cout << "overload vlaue is:" << r0 << "-------" << r1 << std::endl;
//class
Base b;
Sub s;
//see cout
(b.*pfbase)();
(b.*pfbase)();
pfbase = &Base::B;
(b.*pfbase)();
(s.*pfbase)();
PF_Sub psub = &Base::B;
(s.*psub)();
s.Do();
std::cout << "run is end!" << std::endl;
}
运行结果:
func point call return value is:3
PF call return is:11
fp C++ return is :5
fp param return is:4
return func get value is:5
fucn return is:2
std::function call value is:3
overload vlaue is:20-------10
this Base A()
this Base A()
this Base B()
this Base B()
this Base B()
bind func!
std::function class example call:2
run is end!
代码很简单,但是能说明问题。这里面涉及到std::function回头会在专门组织分析一下,现在基本上函数指针在c++上都是使用这个,其它的用起来都不如这个方便简单,而且它也支持Lambda表达式。
指针本来就相当灵活,灵活就意味着不好掌握。函数指针更是如此,它带来上开发上的便利并能实现一些令人惊喜的技巧性的应用。但要清楚的看到,函数指针也有它的劣势之处,特别在源码阅读上,会使代码变得更加的晦涩,如果开发者再任意的改变函数指针的用途,那么更会让阅读代码者产生困惑。
世上没有完美的事物,更重要的是应用者要学会它的应用场景,在合适的场景下应用好它,就会起到事半功倍的效果。