函数指针的意义是什么?


94

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

您能否举出一些很好使用函数指针的示例(在C或C ++中)?


1
这个相关的SO问题中,您可以找到很多有关函数指针的讨论。
itsmatt 2010年

20
@itsmatt:不是。“电视如何工作?” 与“我要用电视做什么?”完全不同。
2010年

6
在C ++中,您可能会使用函子(en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B)。
kennytm 2010年

11
在过去的C ++被“编译”为C的黑暗年代,您实际上可以看到如何实现虚拟方法-是的,带有函数指针。
sbk 2010年

1
当您想将C ++与托管C ++或C#一起使用时非常重要,例如:委托和回调
Maher 2014年

Answers:


108

大多数示例都归结为回调:您调用一个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中进行排序更快的原因。


1
+1,也看到这个答案又如:stackoverflow.com/questions/1727824/...
锐齿

您忘记了虚拟函数,实际上它们也是函数指针(与编译器生成的数据结构耦合)。此外,在纯C中,您可以自己创建这些结构来编写面向对象的代码,如Linux内核的VFS层(以及许多其他地方)所示。
弗洛里安

2
@krynr:虚拟函数是仅指向编译器实现者的函数指针,如果您不得不问它们有什么用,您可能(希望!)不太可能需要实现编译器的虚拟函数机制。
2012年

@sbi:您当然是对的。但是,我认为这有助于理解抽象内部的情况。另外,用C实现自己的vtable并编写面向对象的代码,会带来非常好的学习体验。
Florian 2012年

布朗尼点,用于提供生命,宇宙和一切的答案,以及OP的要求
简单名称

41

好吧,我通常在跳转表中(专业地)使用它们(另请参见此StackOverflow问题)。

跳转表通常(但不是排他性地)用于有限状态机中,以使其成为数据驱动的。代替嵌套开关/盒

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

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


24

例子:

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

1
跳转表是它的重要用途之一。
Ashish

如果有一些可行的例子,这将使我感到欣慰。
Donal Fellows 2010年

1
如果可以使用C ++,则可以使用虚拟函数更好地实现策略和观察者。否则+1。
Billy ONeal 2010年

我认为明智地使用函数指针可以使观察者更紧凑,更轻量
Andrey

@BillyONeal仅当您严格遵守GoF定义时,才会泄漏Javaisms。我会将std::sort'形comp参'描述为一种策略
Caleth

10

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


7

同意以上所有内容,以及...。在运行时动态加载dll时,将需要函数指针来调用函数。


1
我一直在这样做,以支持Windows XP,并且仍然使用Windows 7。+1。
Billy ONeal 2010年

7

我要与这里的潮流背道而驰。

在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;

但是由于可变参数模板和lambda指日可待,所以我不确定我们是否会长期在纯C ++代码中使用函数指针。


仅仅因为您没有看到函数指针并不意味着您不使用它们。每次(除非编译器可以对其进行优化),您都调用虚拟函数,使用boost bindfunction使用函数指针。这就像说我们在C ++中不使用指针,因为我们使用智能指针。无论如何,我在挑剔。
Florian 2012年

3
@krynr:我会礼貌地不同意。重要的是您看到键入的内容,即所使用的语法。无关紧要的是,它们如何在后台运行:这就是抽象的含义。
Matthieu M.

5

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


2
@KennyTM:我指出了C标准库中唯一的其他实例。您引用的示例是第三方库的一部分。
Billy ONeal

5

我最近使用函数指针来创建抽象层。

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

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

我曾经使用switch / case语句选择适当的函数版本,但是随着程序逐渐支持越来越多的硬件变体,这变得不切实际。我不得不到处添加案例陈述。

我还尝试了中间函数层,以确定要使用的函数,但是它们并没有太大帮助。每当我们添加新的变体时,我仍然不得不在多个地方更新case语句。使用函数指针,我只需要更改初始化函数。


3

就像上面的Rich所说的,Windows中的函数指针通常引用一些存储函数的地址。

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

参考文献:


2

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


2

它们的主要用途是回叫:当您需要保存有关函数的信息以供以后调用时

假设您正在写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()可以是任何函数,因为它是一个函数指针。


该问题已标记为[C]和[C ++],没有其他任何语言标记。因此,提供另一种语言的代码片段有点不合时宜。
cmaster-恢复莫妮卡

2

使用功能指针

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

#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

2

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

在C语言中,您使用函数指针,而不(直接)使用函数。

我的意思是,您编写函数,但无法操作函数。没有可以使用的函数的运行时表示形式。您甚至不能调用“函数”。当您写:

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类,但是原理仍然基本相同。


2
更有趣的是,你可以调用函数为(&my_function)(my_arg)(*my_function)(my_arg)(**my_function)(my_arg)(&**my_function)(my_arg)(***my_function)(my_arg)...因为功能衰减函数指针
phuclv

1

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

而且,它们在运行时将不同的行为注入到另一个函数(foo)中非常有用。这使函数foo成为高阶函数。除了灵活性之外,这还使foo代码更具可读性,因为它使您可以从中提取出“ if-else”的额外逻辑。

它在Python中启用了许多其他有用的功能,例如生成器,闭包等。


0

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


0

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


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实际点。

    如果没有函数指针,则需要使用大量的开关构造(例如:

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

    这很快变得很笨拙。

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

我所见过的所有函数指针用法都可以直接归入这三大类之一。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.