函数指针是C和C++语言中非常重要的一个知识点。学习函数指针,将函数以指针的形式进行处理可以为工程开发提供很大的便捷。同时,将函数作为参数、作为类型来定义变量,传递参数的操作对于笔者这样的初学者来说也是很神奇。本文就来总结一下函数指针的一些用法。
既然我们要把函数当做一种指针来看待,那就需要知道,在C++语言中,函数在调用时,会访问其所在的地址。
那么要如何得到这个地址呢?
其实很简单,单独的函数名所代表的,就是函数的地址。这点和数组是一样的。
举个例子:
函数指针的声明,使用如下形式:
返回类型 (*函数指针名)(参数列表);
星号代表着它是一个指针类型。而返回类型和参数列表,是函数的特征,表明这个指针指向了何种函数。
有了这个指针,就可以用它来指向一个函数。由于函数名称就代表该函数的地址,所以给函数指针赋值的操作可以写成:
函数指针名 = 函数名;
例如,声明如下函数原型和函数指针:
int f(int,int);
int (*fp)(int,int);
则将fp指向f应写为:
fp = f;
是不是很简单呢?
typedef关键字可以为一种类型命名。
它的一般格式为:
typedef 类型名 新的类型名
执行过这条语句后,新的名称和旧的名称在其作用域内便可以通用。
但是在定义函数指针时,格式稍有不同:
typedef 返回类型(*函数指针类型名称)(参数列表);
也就是说上文中定义指针名称的地方变成了填写函数指针类型名称的地方了。
下套用这个写法,来为刚刚的函数指针类型明一个新的名称用来简化函数的声明;
int f(int,int);
typedef int (*ft)(int,int);
ft fp = f;
可以看到,在执行过第二条语句后,ft 就变成了一个指向返回值为int,参数为两个整型的函数的指针类型。接着,我们定义这个类型的变量 fp 并用它指向这个类型的函数f。
在C++11之后,auto 关键字更改了原先的含义,变成了一种可以使编译器自动推理需要的类型的关键字。
也就是说,使用 auto 关键字定义的变量,不需要考虑它是什么类型,编译器会根据赋值自动推理所需要的类型。
因此,上面的声明,使用 auto 还可以写成这个样子:
int f(int,int);
auto fp = f;
这样写可以大大简化定义函数指针的步骤,很简单,很nice
不过,需要注意的是,正是由于auto的自动判断带来的灵活性,要求在使用它的时候务必要确保右值类型的准确!
在了解过如何声明函数指针之后,接下来介绍如何使用这种指针。
通过指针访问这个指针指向的函数,我们有如下两种方式:
方式一:函数指针名称(实参列表);
方式二:(*函数指针名称)(实参列表);
这两种写法在语法上都是允许的,但是在严谨的指针使用上确实矛盾的。笔者所参考的《C++ Primer Plus(第六版)》的243页记录了这个问题。下面来简要的阐述一下。
例如:下面两种写法都是可以的:
int f(int a,int b)
{
return a + b;
}
.
.
.
auto fp = f;
int a1 = fp(1,1);
int a2 = (*fp)(1,1);
这两种方式使用哪一种都可以,可以在编写程序时选择任意一种,但是在阅读他人的程序的时候,就需要明确两种写法的结构了,有时候当 * 多时,想要理清楚调用的结构也是一件难事。
将函数作为参数进行传递,这很让人兴奋,因为这代表着调用一个函数的同时,不仅可以传递函数中进行运算的参数,还可以传递这个函数可以调用的一些函数。这使一个函数可处理的范围大大扩展了。
当然,传参同一般的指针传递并没有什么不同,在声明指针变量时,我们可以使用刚刚说到的 typedef 或是 auto 对程序进行简化。
这里,我写了一份程序,调用一个处理数据的函数,参数列表里加入处理数据的算法(加,减,乘)。可以用它来很好的理解函数指针参数传递的过程
#include
using namespace std;
//函数原型
int add(int,int);
int mns(int,int);
int mul(int,int);
int calc(auto,int,int);
int main()
{
int a,b;
cin >> a >> b;
typedef int(*calcF)(int,int);
calcF f;
f = add;
cout << "the ans of a + b is : " << calc(f,a,b) << endl;
f = mns;
cout << "the ans of a - b is : " << calc(f,a,b) << endl;
f = mul;
cout << "the ans of a * b is : " << calc(f,a,b) << endl;
return 0;
}
//三个算法函数
int add(int a,int b){return a + b;}
int mns(int a,int b){return a - b;}
int mul(int a,int b){return a * b;}
//计算函数
int calc(auto f,int a,int b)
{
return f(a,b);
}
现在来到了最后一个重要的知识点,创建一个函数指针数组。相比于函数指针的声明,它们的格式如下:
函数指针变量: 返回类型 (*函数指针变量名)(参数列表);
函数指针数组: 返回类型 (*函数指针数组名称[数组规模])(参数列表);
相比之下,从单个的函数指针变量,变成函数指针数组,在写法上的不同仅仅是在名称后面加上了表示数组的中括号,参考常规指针变量和指针数组的区别:
指针变量: 指向类型 *指针名称;
指针数组: 指向类型 *指针数组名称[数组规模];
我们可以得到一个启示,那就是:
在函数指针的定义中,返回类型以及参数列表是函数指针的指向类型,声明指针的地方在括号内。
你品,你细品。
函数指针数组在使用的时候同一般的指针数组相同,我们不仅可以通过方括号来指定访问下标,而且还可以使用方括号的指针版本,下面,我修改了刚刚的程序,来表示这一点:
#include
using namespace std;
//函数原型
int add(int,int);
int mns(int,int);
int mul(int,int);
int calc(auto,int,int);
int main()
{
int a,b;
cin >> a >> b;
typedef int(*calcF[])(int,int);
calcF f = {add,mns,mul};
cout << "the ans of a + b is : " << calc(f[0],a,b) << endl;
cout << "the ans of a - b is : " << calc(*(f + 1),a,b) << endl;
cout << "the ans of a * b is : " << calc(**(f + 2),a,b) << endl;
return 0;
}
//三个算法函数
int add(int a,int b){return a + b;}
int mns(int a,int b){return a - b;}
int mul(int a,int b){return a * b;}
//计算函数
int calc(auto f,int a,int b)
{
return f(a,b);
}
在这份程序中,我将主函数中的 f 定义为函数指针数组,分别记录三个算法函数的指针。在传参时,前两个传递我分别使用了数组的两种访问元素的方式。
值得注意的是第三个传参的写法,程序中使用了两个 * ,代表着我去了两次值,一次是取得指针数组第三个元素地址的值,拿到了函数指针,再用一个 * 实际上是使用了表示函数的两种方式之一来表示并传递这个函数的地址(在上文我们讨论过,还记得吗?)。
如有内容遗漏或是错误,敬请指正。