我很难看到函数指针的实用程序。我猜它在某些情况下可能是有用的(毕竟它们确实存在),但是我无法想到使用函数指针更好或不可避免的情况。
您能否举出一些很好使用函数指针的示例(在C或C ++中)?
我很难看到函数指针的实用程序。我猜它在某些情况下可能是有用的(毕竟它们确实存在),但是我无法想到使用函数指针更好或不可避免的情况。
您能否举出一些很好使用函数指针的示例(在C或C ++中)?
Answers:
大多数示例都归结为回调:您调用一个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]
例子:
std::sort
'形comp
参'描述为一种策略
同意以上所有内容,以及...。在运行时动态加载dll时,将需要函数指针来调用函数。
我要与这里的潮流背道而驰。
在C语言中,函数指针是实现自定义的唯一方法,因为没有OO。
在C ++中,可以将函数指针或函子(函数对象)用于相同的结果。
由于它们的对象性质,这些函子比原始函数指针具有许多优点,尤其是:
operator()
lambda
和bind
)建造我个人更喜欢函子而不是函数指针(尽管有样板代码),主要是因为函数指针的语法很容易变得毛茸茸(来自Function Pointer Tutorial):
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 ++代码中使用函数指针。
bind
或function
使用函数指针。这就像说我们在C ++中不使用指针,因为我们使用智能指针。无论如何,我在挑剔。
在C语言中,经典用法是qsort函数,其中第四个参数是指向用于在排序中执行排序的函数的指针。在C ++中,人们倾向于将仿函数(看起来像函数的对象)用于此类事情。
我最近使用函数指针来创建抽象层。
我有一个用C语言编写的程序,可以在嵌入式系统上运行。它支持多种硬件变体。根据我运行的硬件,它需要调用某些功能的不同版本。
在初始化时,程序会找出正在运行的硬件,并填充函数指针。程序中的所有更高级别的例程仅调用指针引用的函数。我可以添加对新硬件变体的支持,而无需接触更高级别的例程。
我曾经使用switch / case语句选择适当的函数版本,但是随着程序逐渐支持越来越多的硬件变体,这变得不切实际。我不得不到处添加案例陈述。
我还尝试了中间函数层,以确定要使用的函数,但是它们并没有太大帮助。每当我们添加新的变体时,我仍然不得不在多个地方更新case语句。使用函数指针,我只需要更改初始化函数。
就像上面的Rich所说的,Windows中的函数指针通常引用一些存储函数的地址。
C language
在Windows平台上进行编程时,基本上是将一些DLL文件加载到主内存中(使用LoadLibrary
),并使用DLL中存储的函数,您需要创建函数指针并指向这些地址(使用GetProcAddress
)。
参考文献:
它们的主要用途是回叫:当您需要保存有关函数的信息以供以后调用时。
假设您正在写Bomberman。人员放下炸弹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)。
因此,您已经在使用函数指针作为values。显然,您希望为这些值使用变量-这是其他用途的所有用途的体现:多态性/自定义(如qsort),回调,跳转表等。
在C ++中,事情有点复杂,因为我们有lambda,并且对象带有operator()
,甚至还有一个std::function
类,但是原理仍然基本相同。
函数指针的一种用法是我们可能不想在调用函数的地方修改代码(这意味着调用可能是有条件的,并且在不同的条件下,我们需要进行不同种类的处理)。这里的函数指针非常方便,因为我们不需要在调用函数的地方修改代码。我们只需使用带有适当参数的函数指针来调用函数。可以使函数指针有条件地指向不同的函数。(这可以在初始化阶段的某处完成)。此外,如果我们无法修改调用代码的位置(假设它是我们无法修改的库API),那么上述模型非常有用。API使用函数指针来调用适当的用户定义函数。
我将在这里尝试给出一些比较全面的清单:
回调:使用用户提供的代码自定义某些(库)功能。最主要的示例是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
和frobnicate
,和代码需要破坏一个struct Base*
只会叫base->vtable->destruct(base)
,这将调用析构函数的正确版本,其中独立的派生类base
实际点。
如果没有函数指针,则需要使用大量的开关构造(例如:
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;
}
这很快变得很笨拙。
动态加载的代码:当程序将模块加载到内存中并尝试调用其代码时,它必须通过函数指针。
我所见过的所有函数指针用法都可以直接归入这三大类之一。