为什么要使用函数指针?

时间:2018-05-30 16:00:39

标签: c++ c programming-languages

我希望这是一个非常重复的问题。我向所有发现令人讨厌的观众提出了借口。

虽然我是有经验的程序员,但我无法证明使用函数指针超过直接调用。我无法找到差异的情景是 -

1)回调 - 同样可以通过直接电话来实现。

2)异步或同步事件处理 - 无论如何必须根据哪个元素号识别事件。在函数指针数组中得到更新。但同样也可以通过直接电话来完成。

3)在某些帖子中,我看到有人评论它是在不知道要调用哪个函数时使用。我没有得到任何正确的理由。

如果有人能够通过实际且非常简单的现实示例使用上述场景来解释我,我真的很感激。

6 个答案:

答案 0 :(得分:3)

函数指针通常用于:

  • 运行时多态:您可以定义封装函数指针的结构,或指向函数表的指针。这使您可以在运行时调用指定的函数,即使是在编写库时不存在的一种客户端对象也是如此。您可以使用它来实现多个调度或类似C中的访问者设计模式。这也是C ++类及其SELECT number, firstName +' '+ surName as 'full name' FROM Dealer where exists (SELECT gameTypeName,COUNT( gameTypeName) FROM Game GROUP BY gameTypeName) LIMIT 1; 成员函数最初在幕后实现的方式。
  • 闭包:这些可以是包含函数指针及其一个或多个参数的结构。
  • 状态机:而不是每个州标签都带有virtual的{​​{1}},我经常发现为每个州的处理程序提供自己的功能很方便。当前状态是您所处的函数,状态转换是尾递归调用,程序变量是参数。然后状态标签成为函数指针,您可以将其存储在表中或从函数返回。
  • 高阶函数:C标准库中的两个示例是switchcase,它们概括了元素的类型和比较函数。
  • 低级别支持:例如,共享库加载器需要这样做。

答案 1 :(得分:2)

  

1)回调 - 直接调用也可以实现。

不正确。对于直接调用,调用者在编译代码时必须知道函数名称​​和签名,并且只能调用该函数。回调是在运行时定义的,可以动态更改,而调用者只需知道签名,而不是名称。此外,对象的每个实例可能具有不同的回调,而通过直接调用,所有实例都必须调用相同的函数。

  

2)异步或同步事件处理 - 无论如何必须发生事件   确定,基于哪个元素没有。在函数指针数组得到   更新。但同样也可以通过直接电话来完成。

不确定你的意思,但事件处理程序只是一种回调。事件可以由调用者和通过指针调用的不同回调处理程序来识别。您的观点仅代表所有事件类型都有一个事件处理程序,并且用户负责识别。

  

3)在某些帖子中,我看到人们评论它时,如果不知道要调用哪个函数,则会使用它。我没有得到任何正当理由。

见上文(1)和(2)。通常,它是将平台独立的第三方库代码挂钩到特定平台的手段,而无需提供源代码或需要用户/应用程序定义的处理程序的系统事件。

但是我不会这么做 - 如果可以在不使用指向函数的指针的情况下解决所有应用程序需求,那么就不需要指向函数的指针。如果您需要,您可能会知道。在您自己实现接口之前必须使用需要它的API时,您很可能会遇到它。例如,在标准库中,qsort()函数需要一个指向函数的指针,以便定义如何对两个任意类型的对象进行排序 - 允许qsort()支持任何类型的对象 - 它是一个在C中创建函数“ polymorphic ”的方法。 C ++直接支持多态,因此在C ++中通常不需要显式的函数指针 - 尽管在任何情况下都使用函数指针实现内部多态。

答案 2 :(得分:1)

如果没有函数指针,你将如何实现一个计算任何实值函数积分的函数?

typedef double (*Function)(double);

double Integral(Function f, double a, double b);

答案 3 :(得分:0)

编程中有一个名为DRY的概念 - 不要重复自己。

假设您的UI中有121个按钮。它们中的每一个都表现得大致相同,除非按下按钮,会发生不同的操作。

您可以(A)使用虚拟继承调度到正确的操作(每个按钮需要一个类),或者(B)使用存储在类中的函数指针(或std::function)来调用正确的“点击”处理程序,或(C)每个按钮都是一个不同的类型。

虚拟函数在我检查过的每个编译器中实现为一个复杂的表,最后是一个函数指针的集合。

因此,您的选择是函数指针或生成121个完全不同的按钮,这些按钮恰好表现相同。

在您想要解调调用者和被调用者的任何情况下,您必须使用类似于函数指针的东西。有很多案例,从工作队列到线程关闭任务,回调等等。

在所有硬编码的小型程序中,每次调用的硬编码都可以正常工作。但像这样的硬编码内容无法扩展。当你想要更新每个手工实现的121个按钮时,知道他们的定制点将是非常困难的。他们失去同步。

121是一个适度数量的按钮。一个拥有10,000的应用程序怎么样?并且您想更新每个按钮的行为以处理基于触摸的输入?

更重要的是,当您键入erase时,可以显着减少二进制大小。实现按钮的类的121个副本将占用比1个类更多的可执行空间,每个类存储一个或两个函数指针。

函数指针只是一种“类型擦除”。类型擦除减少了二进制大小,在提供者和消费者之间提供了更清晰的契约,并且更容易围绕类型擦除数据重构行为。

答案 4 :(得分:0)

  

1)回调 - 直接调用也可以实现。

并非在所有情况下,因为调用者在编译时可能不知道必须调用哪个函数。例如,这在图书馆中是典型的,因为他们无法提前知道您的代码。

但是,它也可能发生在您自己的代码中:每当您想要部分重用某个函数时,您可以:

  • 创建该函数的多个版本,每个版本调用不同的函数。复制代码,非常糟糕的维护。除非遇到代码膨胀,否则表现良好。
  • 传递一个函数指针(或者通常在C ++中可调用)。在某些情况下,灵活,少代码,性能可能会受到影响。
  • 如果您事先知道要调用的可能函数集,请创建一组分支(if / switch链)。刚性,但可能比少数分支的函数指针快。
  • 在C ++中,创建模板化版本。与第一种情况相同,但是自动化;这么好的维护。代码膨胀可能是一个问题。
  • 将公共代码分解出来,以便呼叫者可以逐个调用他们需要的任何内容。有时候这是不可能/容易的 - 特别是在参数化要保持可重用的复杂算法时(例如qsort())。在C ++中,请参阅STL(标准模板库)。
  

2)异步或同步事件处理 - 无论如何必须根据哪个元素号识别事件。在函数指针数组中得到更新。但同样也可以通过直接电话来完成。

某些事件系统的设计使您可以简单地配置在给定事件发生时将触发哪些功能。如果这是一个带有C接口的外部库,他们别无选择,只能使用函数指针。

其他一些系统允许您创建自己的事件循环,并以某种方式获取事件并随意执行任何操作;所以他们避免回调。

  

3)在某些帖子中,我看到人们评论它时,如果不知道要调用哪个函数,则会使用它。我没有得到任何正当理由。

见第一个案例。

答案 5 :(得分:0)

感谢所有积极参与此讨论的人。感谢您提供实用示例,例如 -

1)实施图书馆功能

2)看看qsort

3)参考Linux内核

4)C

中的通用堆数据结构

我觉得qsort()void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) s足以清除我的1)& 3)点。

1)回调 - 同样可以通过直接调用来实现。 3)在某些帖子中,我看到人们评论它时,如果不知道要调用哪个函数,就要使用它。我没有得到任何正当理由。

主要通过回调 - 它是一个调用尚未定义主体的函数的规定。并且它期望稍后在运行期间提供函数的定义。因此,由于缺乏函数定义,编译不会受到阻碍。如果有人考虑上面的qsort()函数,请务必使用。在此,用户负责为compare()提供函数定义,如 -

int compare (int* a, int* b)
{
  //User defined body based on problem requirement
}

让我们考虑一个实际场景,其中多个线程具有各自的比较功能。在直接调用的情况下,每个线程都需要实现自己的排序函数,或者如果是常用函数,那么实现将更加庞大。但是通过使用回调方法,所有线程都可以使用相同的函数进行排序,因为排序算法对于所有线程保持相同。 考虑到分层架构,主要是较高层具有较低层的抽象视图。所以,这里假设我们在应用层实现了qsort()函数[用户定义的qsort],并且让我们说底层应用程序有一个ADC驱动层,它捕获样本并提供给应用程序进行排序。然后,对于应用,没有必要理解负责收集和提供样品的功能的定义。但应用程序只关注获取样本。因此,该主应用程序不知道要调用哪个函数。各个ADC驱动程序只需使用qsort()调用应用程序并提供必要的数据。

关于2点仍然困惑 - 2)异步或同步事件处理 - 无论如何必须根据哪个元素号识别事件。在函数指针数组中得到更新。但同样也可以通过直接电话来完成。

从上面的讨论中我得出结论,如果事件处理程序指向某个库函数,那么它需要通过指向函数的指针来实现。其次,要创建一个独立且方便的代码,必须维护函数指针。让我们说在应用程序和驱动程序之间我们有一个接口层。因此,如果应用程序或驱动程序随时更改,它将不会影响或至少相互影响。此接口层使用指向函数的指针实现。但请考虑以下情况 -

int (*fptr[10]) (void) =
{
    function1;     //function for starting LED
    function2;     //function for relay operation
      .
      .
    function10;    //function for motor control
}

假设我们有GPIO0.0 - GPIO0.10已映射到函数指针数组。即GPIO0.0 - fptr的第0个元素              。              。             GPIO0.10 - fptr的第10个元素 这些GPIO引脚已配置为电平触发中断,它们各自的ISR将更新数组元素号。 i=GPIO_Value;调度程序还有一个调用函数指针数组的线程 -

fptr[i]();

这里使用函数指针是否合理?