我很难看到函数指针的实用性。我想它在某些情况下可能有用(毕竟它们存在),但我想不出使用函数指针更好或不可避免的情况。
您能否举一些很好地使用函数指针(在 C 或 C++ 中)的例子?
大多数示例归结为回调:您调用一个f()
传递另一个函数地址的函数g()
,并f()
调用g()
某些特定任务。如果你传递f()
了的地址h()
,那么f()
将改为回调h()
。
基本上,这是一种对函数进行参数化的方法:它的某些部分行为不是硬编码到f()
中,而是硬编码到回调函数中。f()
调用者可以通过传递不同的回调函数来做出不同的行为。一个经典qsort()
来自 C 标准库,它将其排序标准作为指向比较函数的指针。
在 C++ 中,这通常使用函数对象(也称为函子)来完成。这些是重载函数调用运算符的对象,因此您可以像调用函数一样调用它们。例子:
class functor {
public:
void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};
functor f;
f(42);
这背后的想法是,与函数指针不同,函数对象不仅可以携带算法,还可以携带数据:
class functor {
public:
functor(const std::string& prompt) : prompt_(prompt) {}
void operator()(int i) {std::cout << prompt_ << i << '\n';}
private:
std::string prompt_;
};
functor f("the answer is: ");
f(42);
另一个优点是有时内联对函数对象的调用比通过函数指针调用更容易。这就是为什么在 C++ 中排序有时比在 C 中排序更快的原因。
好吧,我通常(专业地)在跳转表中使用它们(另见这个 StackOverflow 问题)。
跳转表通常(但不限于)在有限状态机中使用,以使其成为数据驱动的。而不是嵌套的开关/案例
switch (state)
case A:
switch (event):
case e1: ....
case e2: ....
case B:
switch (event):
case e3: ....
case e1: ....
你可以制作一个二维函数指针数组,然后调用handleEvent[state][event]
例子:
函数指针有用性的“经典”示例是 C 库qsort()
函数,它实现了快速排序。为了对用户可能提出的任何和所有数据结构都通用,它需要几个指向可排序数据的 void 指针和一个指向知道如何比较这些数据结构的两个元素的函数的指针。这允许我们为工作创建我们选择的函数,实际上甚至允许在运行时选择比较函数,例如升序或降序排序。
同意以上所有观点,另外......当您在运行时动态加载 dll 时,您将需要函数指针来调用函数。
我要在这里逆流而上。
在 C 中,函数指针是实现自定义的唯一方法,因为没有 OO。
在 C++ 中,您可以使用函数指针或仿函数(函数对象)来获得相同的结果。
由于它们的对象性质,仿函数比原始函数指针具有许多优点,特别是:
operator()
lambda
和bind
)我个人更喜欢函子而不是函数指针(尽管有样板代码),主要是因为函数指针的语法很容易变得很复杂(来自函数指针教程):
typedef float(*pt2Func)(float, float);
// defines a symbol pt2Func, pointer to a (float, float) -> float function
typedef int (TMyClass::*pt2Member)(float, char, char);
// defines a symbol pt2Member, pointer to a (float, char, char) -> int function
// belonging to the class TMyClass
我唯一一次在 Boost.Spirit 中看到在仿函数不能使用的地方使用函数指针。他们完全滥用语法将任意数量的参数作为单个模板参数传递。
typedef SpecialClass<float(float,float)> class_type;
但由于可变参数模板和 lambda 即将到来,我不确定我们是否会在纯 C++ 代码中长期使用函数指针。
在 C 中,经典用法是qsort 函数,其中第四个参数是指向用于在排序中执行排序的函数的指针。在 C++ 中,人们倾向于使用仿函数(看起来像函数的对象)来处理这类事情。
我最近使用函数指针来创建一个抽象层。
我有一个在嵌入式系统上运行的纯 C 语言程序。它支持多种硬件变体。根据我运行的硬件,它需要调用一些函数的不同版本。
在初始化时,程序确定它运行在什么硬件上并填充函数指针。程序中的所有高级例程都只是调用指针引用的函数。我可以添加对新硬件变体的支持,而无需触及更高级别的例程。
我曾经使用 switch/case 语句来选择正确的函数版本,但是随着程序发展到支持越来越多的硬件变体,这变得不切实际。我不得不到处添加案例陈述。
我还尝试了中间函数层来确定要使用哪个函数,但它们并没有太大帮助。每当我们添加新变体时,我仍然需要在多个地方更新 case 语句。使用函数指针,我只需要更改初始化函数。
我对它们的主要用途是 CALLBACKS:当您需要保存有关稍后调用的函数的信息时。
假设你正在写炸弹人。人投下炸弹 5 秒后,它应该会爆炸(调用explode()
函数)。
现在有两种方法可以做到这一点。一种方法是“探测”屏幕上的所有炸弹,看看它们是否准备好在主循环中爆炸。
foreach bomb in game
if bomb.boomtime()
bomb.explode()
另一种方法是将回调附加到您的时钟系统。当一个炸弹被植入时,你添加一个回调让它在合适的时候调用 bomb.explode()。
// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;
// IN the main loop:
foreach callback in callbacks
if callback.timeToRun
callback.function()
这里callback.function()
可以是任何函数,因为它是一个函数指针。
函数指针的使用
根据用户输入动态调用函数。在这种情况下,通过创建字符串和函数指针的映射。
#include<iostream>
#include<map>
using namespace std;
//typedef map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Multi(int x, int y)
{
return x*y;
}
void initializeFunc()
{
objFunMap["Add"]=Add;
objFunMap["Sub"]=Sub;
objFunMap["Multi"]=Multi;
}
int main()
{
initializeFunc();
while(1)
{
string func;
cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
int no, a, b;
cin>>no;
if(no==1)
func = "Add";
else if(no==2)
func = "Sub";
else if(no==3)
func = "Multi";
else
break;
cout<<"\nEnter 2 no :";
cin>>a>>b;
//function is called using function pointer based on user input
//If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
int ret = objFunMap[func](a, b);
cout<<ret<<endl;
}
return 0;
}
这样,我们就在实际的公司代码中使用了函数指针。您可以编写“n”个函数并使用此方法调用它们。
输出:
输入您的选择(1. 添加 2. 子 3. 多):1 输入 2 否 :2 4 6 输入您的选择(1. 添加 2. 子 3. 多):2 输入 2 否 : 10 3 7 输入您的选择(1. 添加 2. 子 3. 多):3 输入 2 否 : 3 6 18
一个不同的观点,除了这里的其他好的答案:
我的意思是,你写函数,但你不能操作函数。没有您可以使用的函数的运行时表示。您甚至不能调用“函数”。当你写:
my_function(my_arg);
您实际上是在说“my_function
使用指定的参数调用指针”。您正在通过函数指针进行调用。这种对函数指针的衰减意味着以下命令等价于前面的函数调用:
(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);
等等(感谢@LuuVinhPhuc)。
因此,您已经将函数指针用作值。显然,您会希望为这些值设置变量——这就是其他方法的所有用途:多态/定制(如在 qsort 中)、回调、跳转表等。
在 C++ 中,事情要复杂一些,因为我们有 lambdas,以及带有 的对象operator()
,甚至还有一个std::function
类,但原理仍然基本相同。
函数指针可以在 C 中用于创建编程的接口。根据运行时所需的特定功能,可以将不同的实现分配给函数指针。
我广泛使用函数指针来模拟具有 1 字节操作码的微处理器。一个由 256 个函数指针组成的数组是实现这一点的自然方式。
我将尝试在这里给出一个比较全面的列表:
回调:使用用户提供的代码自定义一些(库)功能。主要示例是qsort()
,但对于处理事件(如单击时调用回调的按钮)或启动线程所必需的事件( )也很有用pthread_create()
。
多态性:C++ 类中的 vtable 只不过是一个函数指针表。C 程序也可以选择为其某些对象提供 vtable:
struct Base;
struct Base_vtable {
void (*destruct)(struct Base* me);
};
struct Base {
struct Base_vtable* vtable;
};
struct Derived;
struct Derived_vtable {
struct Base_vtable;
void (*frobnicate)(struct Derived* me);
};
struct Derived {
struct Base;
int bar, baz;
}
的构造函数Derived
然后将其vtable
成员变量设置为具有派生类的实现的全局对象destruct
and frobnicate
,并且需要破坏 a 的代码struct Base*
将简单地调用base->vtable->destruct(base)
,这将调用正确版本的析构函数,而与派生类base
实际指向的无关.
如果没有函数指针,多态性将需要使用大量 switch 构造来编码,例如
switch(me->type) {
case TYPE_BASE: base_implementation(); break;
case TYPE_DERIVED1: derived1_implementation(); break;
case TYPE_DERIVED2: derived2_implementation(); break;
case TYPE_DERIVED3: derived3_implementation(); break;
}
这很快就会变得相当笨拙。
动态加载代码:当程序将模块加载到内存并尝试调用其代码时,它必须通过函数指针。
我见过的所有函数指针的使用都完全属于这三大类之一。
对于 OO 语言,在幕后执行多态调用(我猜这对 C 也是有效的)。
此外,它们对于在运行时向另一个函数 (foo) 注入不同的行为非常有用。这使得函数 foo 成为高阶函数。除了它的灵活性之外,这使得 foo 代码更具可读性,因为它让您可以从中提取“if-else”的额外逻辑。
它在 Python 中启用了许多其他有用的东西,如生成器、闭包等。
函数指针的一种用途可能是我们可能不想修改调用函数的代码(这意味着调用可能是有条件的,在不同的条件下,我们需要进行不同类型的处理)。这里的函数指针非常方便,因为我们不需要在函数被调用的地方修改代码。我们只需使用带有适当参数的函数指针来调用函数。可以使函数指针有条件地指向不同的函数。(这可以在初始化阶段的某个地方完成)。此外,如果我们无法修改调用它的代码(假设它是我们无法修改的库 API),上述模型非常有用。API 使用函数指针来调用适当的用户定义函数。