2012-01-23 58 views

回答

18

当您想创建callback mechanism并且需要将函数的地址传递给另一个函数时,函数指针可能很有用。

当你想存储一组函数,例如动态调用时,它们也可能很有用。

8

一个常见用途是实现callback function

尝试使用qsort库函数对某些东西进行排序。它的最后一个参数是指向您写的比较函数的指针。

5

我觉得作为一个非常有用的应用程序的第一件事是一个按钮。看看下面的代码:

int buttonID = CreateButton ("Click Me!", 100, 100, 200, 100, onClick); 

这将创建一个宽度200,高度100。每当你点击它在(100,100)的按钮,onClick函数调用。

我在个人Windows API包装中使用了类似的东西。它使得创建按钮等变得更容易。

3

那么,#1股票的答案是:qsortqsort将使用的比较器例程作为函数指针传递。许多其他“通用算法”功能将以类似的方式采用比较器;例如也许散列表实现可能会接受你的散列函数。 C语言GUI工具包和应用程序框架(例如Gnome/Gtk +/Glib)经常接受函数指针作为定时器或用户界面事件的“回调函数”。 (EG:“单击此按钮时调用此函数”或“...只要此计时器过期”)

实际上,C中的大多数“OOP-like”或“事件驱动”代码将接受函数指针类似的原因。

4

有函数指针两个主要用途:

  • 回调 - 用于事件处理程序,解析器专业化,比较器功能的传球...
  • 插件和扩展 - 通过插件或提供的函数指针库扩展由标准函数GetProcAddress,dlsym或类似的函数收集,它们将函数标识符作为名称并返回一个函数指针。对于像OpenGL这样的API来说绝对至关重要。
1

您可以使用它将回调传递给函数。例如,您可能想要使用qsort()对数组进行排序。此功能需要一个比较函数作为它的一个参数,这意味着你可以使用自己的排序顺序:

// All odd numbers are before even numbers 
int cmpoddeven(const void *xp, const void *yp) { 
    int x = *((int*) xp); 
    int y = *((int*) yp); 
    if(x == y) 
    return 0; 
    if(x % 2 == y % 2) { 
    return (x < y ? -1 : 1); 
    if(x % 2 == 1) 
    return -1; 
    return 1; 
} 

int main() { 
    int array[] = {1, 2, 3, 4, 5}; 
    // calling qsort with cmpoddeven as the comparison function 
    qsort(array, 5, sizeof(int), &cmpoddeven); 
    // array == {1, 3, 5, 2, 4}; 
} 
1

在大多数情况下,它本质上是做dependency inversion的C-方式。该wiki文章指出:

答:高级模块不应该依赖低级模块。两者都应该依赖于抽象。 B.抽象不应该取决于细节。细节应该取决于抽象。

qsort的经典示例是这样做的,即更高级别的排序函数不依赖于要排序的数据的类型,大小或比较方法。因此,如果您使用的是整数的qsort(),则详细信息为sizeof(int)和您的比较实现。抽象是一个任意大小的元素数组和一个比较该类型元素的函数。另外:Inversion of Control

我很惊讶没有人提到过pthread_create()作为例子。

我能想到的惟一常见用法不能一概而论,因为依赖倒置在非可切换数据类型上实现类似开关的流量控制。例如,如果您曾经想要打开字符串,请创建将排序后的字符串键映射到函数指针并进行二分搜索的数组。它不像开关那样是O(1),而是比在一个大的if-else中盲目地执行strcmp(),直到你找到一个匹配更好。但也许并不比标记字符串和使用实际开关更好。

4

回调例程似乎是迄今为止提出的最常见的场景。然而,还有许多其他的...

有限状态机其中(多维)数组元素指示处理/处理下一个状态的例程。这将FSM的定义保留在一个地方(数组)。

启用功能和禁用功能可以使用函数指针来完成。您可能具有希望启用或禁用的功能,可以执行类似但不同的操作。您可以使用if-else构造测试变量来代替填充和混淆代码,以便使用函数指针进行编码,然后通过更改/分配函数指针来启用/禁用功能。如果添加新的变体,则不必追踪所有if-else或switch案例(并且可能会丢失一个)。相反,您只需更新函数指针以启用新功能,或者禁用旧功能。

减少代码混乱我在前面的例子中谈到了这一点。例如,如...

switch (a) { 
case 0: 
    func0(); 
    break; 
case 1: 
    func1(); 
    break; 
case 2: 
    func2(); 
    break; 
case 3: 
    func3(); 
    break; 
default: 
    funcX(); 
    break; 
} 

可以简化为...

/* This declaration may be off a little, but I am after the essence of the idea */ 
void (*funcArray)(void)[] = {func0, func1, func2, func3, funcX}; 
... appropriate bounds checking on 'a' ... 
funcArray[a](); 

还有更多。希望这可以帮助。

+1

FWIW,声明应该是'void(* funcArray [])(void)= {func0,func1,func2 ...};'('funcArray'是一个指向函数的指针数组...)。记住声明模仿使用;如果代码中的表达式是'funcArray [a]()',那么声明将以相同的方式构造。 –

+1

@JohnBode - 非常感谢。尽管我已经和C一起工作了20多年,但我仍然混淆了函数指针语法。通常我选择更简单的(对我来说)typedef函数指针的路由,然后声明typedef变量的数组。 – Sparky

+0

@Sparky就typedef达成一致意见。它使诸如函数指针数组或指向函数指针的指针变得更容易阅读。 –