函数指针有什么意义?

时间:2010-04-07 11:55:18

标签: c++ c function pointers c++-faq

我无法看到函数指针的实用程序。我想它在某些情况下可能很有用(毕竟它们存在),但我想不出使用函数指针更好或不可避免的情况。

你能举一些好用的函数指针(用C或C ++)吗?

17 个答案:

答案 0 :(得分:101)

大多数示例归结为 回调 :您调用函数f()传递另一个函数g()的地址,并{{1}为某些特定任务调用f()。如果您改为g()地址f(),则h()会回拨f()

基本上,这是一种 参数化 函数的方法:它的某些部分行为不是硬编码到h()中,而是硬编码到回调函数中。调用者可以通过传递不同的回调函数使f()表现不同。来自C标准库的经典是f(),它将其排序标准作为指向比较函数的指针。

在C ++中,通常使用 函数对象 (也称为仿函数)来完成。这些是重载函数调用操作符的对象,因此您可以像调用函数一样调用它们。示例:

qsort()

这背后的想法是,与函数指针不同,函数对象不仅可以携带算法,还可以携带数据:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

另一个优点是,与函数指针调用相比,内联对函数对象的调用更容易。这就是为什么在C ++中排序有时比在C中排序更快的原因。

答案 1 :(得分:39)

好吧,我通常在jump tables中使用它们(专业)(另见this StackOverflow question)。

跳转表通常(但不是唯一地)用于finite state machines以使它们受数据驱动。而不是嵌套的开关/案例

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

你可以制作一个二维数组或函数指针,然后调用handleEvent[state][event]

答案 2 :(得分:23)

示例:

  1. 自定义排序/搜索
  2. 不同     模式(如战略,观察员)
  3. 回调

答案 3 :(得分:10)

函数指针有用性的“经典”示例是C库qsort()函数,它实现了快速排序。为了对用户可能提出的任何和所有数据结构进行通用,它需要几个指向可排序数据的void指针和一个指向知道如何比较这些数据结构的两个元素的函数的指针。这允许我们为作业创建我们的选择功能,并且实际上甚至允许在运行时选择比较功能,例如,用于分类升序或降序。

答案 4 :(得分:6)

我会反对现在的情况。

在C中,函数指针是实现自定义的唯一方法,因为没有OO。

在C ++中,您可以使用函数指针或函子(函数对象)获得相同的结果。

由于它们的对象特性,仿函数比原始函数指针有许多优点,特别是:

  • 他们可能会出现operator()
  • 的多次重载
  • 他们可以拥有现有变量的状态/引用
  • 它们可以在现场构建(lambdabind

我个人更喜欢函子指针(尽管有样板代码),主要是因为函数指针的语法很容易变得毛茸茸(来自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;

但由于可变参数模板和lambdas即将到来,我不确定我们是否会在纯C ++代码中使用函数指针。

答案 5 :(得分:5)

在C中,经典用法是qsort function,其中第四个参数是指向用于在排序中执行排序的函数的指针。在C ++中,人们倾向于使用仿函数(看起来像函数的对象)来做这种事情。

答案 6 :(得分:5)

同意上述所有内容,加上.... 当你在运行时动态加载一个DLL时,你需要函数指针来调用函数。

答案 7 :(得分:5)

我最近使用函数指针创建了一个抽象层。

我有一个用纯C编写的程序,可以在嵌入式系统上运行。它支持多种硬件变体。根据我运行的硬件,它需要调用某些函数的不同版本。

在初始化时,程序会计算出运行的硬件并填充函数指针。程序中的所有高级例程都只调用指针引用的函数。我可以添加对新硬件变体的支持,而无需触及更高级别的例程。

我曾经使用switch / case语句来选择正确的函数版本,但随着程序越来越多以支持越来越多的硬件变体,这变得不切实际。我不得不在整个地方添加案例陈述。

我还尝试过中间函数层来确定要使用哪个函数,但它们没有多大帮助。每当我们添加新变体时,我仍然需要在多个位置更新case语句。使用函数指针,我只需要更改初始化函数。

答案 8 :(得分:3)

与上面说的 Rich 一样,Windows中的函数指针通常会引用一些存储函数的地址。

当您在Windows平台上C language编程时,基本上在主存储器中加载一些DLL文件(使用LoadLibrary)并使用存储在DLL中的函数,您需要创建函数指针并指向这些地址(使用GetProcAddress)。

<强>参考文献:

答案 9 :(得分:2)

可以在C中使用函数指针来创建要编程的接口。根据运行时所需的特定功能,可以为函数指针分配不同的实现。

答案 10 :(得分:2)

我主要使用它们是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()可以是任何函数,因为它是一个函数指针。

答案 11 :(得分:1)

使用函数指针

根据用户输入动态调用功能。 在这种情况下,通过创建字符串和函数指针的映射。

#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;
}

这样我们在实际的公司代码中使用了函数指针。 你可以写'&#39; n&#39;函数的数量,并使用此方法调用它们。

<强>输出:

    Enter your choice( 1. Add 2. Sub 3. Multi) : 1
    Enter 2 no :2 4
    6
    Enter your choice( 1. Add 2. Sub 3. Multi) : 2
    Enter 2 no : 10 3
    7
    Enter your choice( 1. Add 2. Sub 3. Multi) : 3
    Enter 2 no : 3 6
    18

答案 12 :(得分:0)

函数指针的一个用途可能是我们可能不希望修改调用函数的代码(意味着调用可能是有条件的,在不同条件下,我们需要进行不同类型的处理)。 这里的函数指针非常方便,因为我们不需要在调用函数的地方修改代码。我们只需使用带有适当参数的函数指针调用该函数。 可以使函数指针有条件地指向不同的函数。 (这可以在初始化阶段的某个地方完成)。此外,如果我们无法修改被调用的代码,那么上面的模型非常有用(假设它是一个我们无法修改的库API)。 API使用函数指针来调用适当的用户定义函数。

答案 13 :(得分:0)

我广泛使用函数指针,用于模拟具有1字节操作码的微处理器。 256个函数指针的数组是实现它的自然方式。

答案 14 :(得分:0)

对于OO语言,在幕后执行多态调用(这对C来说也是有效的,我猜)。

此外,它们在运行时将不同的行为注入另一个函数(foo)非常有用。这使得函数foo更高阶函数。除了它的灵活性之外,这使得foo代码更具可读性,因为它让你将“if-else”的额外逻辑拉出来。

它支持Python中的许多其他有用的东西,如生成器,闭包等。

答案 15 :(得分:0)

除了其他好的答案之外,还有不同的观点:

在C中,只有 函数指针,没有任何函数。

我的意思是,你编写函数,但你不能操作函数。这样的函数没有运行时表示。你甚至不能称之为“功能”。当你写:

my_function(my_arg);

你实际上说的是“用指定的参数执行对my_function指针的调用”。你正在通过一个函数指针进行调用。此decay to function pointer表示以下命令与上一个函数调用等效:

(&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类,但原则仍然大致相同。

答案 16 :(得分:0)

我将在此处尝试提供一些较为全面的列表:

  • 回调:使用用户提供的代码自定义某些(库)功能。主要示例是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成员变量设置为具有destructfrobnicate的派生类的实现以及需要破坏的代码的全局对象。 struct Base*会简单地调用base->vtable->destruct(base),这将调用析构函数的正确版本,而与派生类base实际指向哪个无关。

    没有函数指针,就需要使用大量的开关构造(如p

    )来编码多态性
    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;
    }
    

    这很快变得很笨拙。

  • 动态加载的代码:当程序将模块加载到内存中并尝试调用其代码时,它必须通过函数指针。

我所看到的所有对函数指针的使用都完全属于这三个大类之一。

相关问题