我觉得我对 C 中的指针有很好的处理。大多数情况下,我使用它们将数组传递给函数。
但是我在查看许多不同的代码示例时注意到,看到指向函数的指针并不少见。我根本不清楚为什么这会有用?是否存在一些经典实例,其中指向函数模型的指针很方便或必须实现?
我觉得我对 C 中的指针有很好的处理。大多数情况下,我使用它们将数组传递给函数。
但是我在查看许多不同的代码示例时注意到,看到指向函数的指针并不少见。我根本不清楚为什么这会有用?是否存在一些经典实例,其中指向函数模型的指针很方便或必须实现?
qsort
经典示例是使用函数对元素数组进行排序。它的最后一个参数是一个函数指针,指向比较两个元素的例程,作为 void 指针传递。
void qsort(void *base,
size_t nmemb,
size_t size,
int(*compar)(const void *, const void *));
在这里使用函数指针是必不可少的,因为无法使排序例程通用到足以知道您想要如何比较元素,因此函数指针本质上是排序函数用来让您扩展它的“回调”。
[编辑]例如,假设您定义了以下结构:
struct president {
char * fname;
char * lname;
unsigned int number;
};
如果你想通过它们的“数字”字段对它们的数组进行排序,你可以实现一个“比较”(比较器)函数,如下所示:
int compare_presidents(const void * v1, const void * v2) {
struct president p1 = * ((struct president *) v1);
struct president p2 = * ((struct president *) v2);
return (p1.number - p2.number); // Compare by number ascending.
}
您可以对这样的结构数组进行排序:
qsort(presidents, num_presidents, sizeof(struct president), compare_presidents);
将指向函数的指针视为实现(好吧,描述)我们现在优雅地称为回调的“石刀和熊皮”方式。这个类比并不完美,但这就是想法 - 通过指针引用提供一种快速但相当危险的方式来调用完全不同的函数。
启动线程是另一个例子。与 POSIX 类似,C11 具有用于启动线程的接口:
typedef int (*thrd_start_t)(void*);
int thrd_create(thrd_t *, thrd_start_t, void *);
这里thrd_start_t
函数指针指向将由新线程执行的函数,void*
参数是传递给该函数的参数。
一个明显的例子是qsort
。您将一个指针传递给它将用于比较项目的函数。由于它不知道您要排序的数据类型,因此它无法真正独立完成该部分。
另一个常见的例子是进行数值积分的函数。您向它传递一个指向要集成的函数的指针以及上下限,然后它调用该函数来查找这些边界之间的各个点的值,从而为您提供答案。
示例 1:想象有 5 种不同的排序算法。您只想在各种地方进行排序,但不想在每个地方的 5 个中进行选择。相反,您有一个指向排序算法的指针(它们都采用相同的参数、类型等)。只需选择一次算法(设置指针)并使用指针在任何地方进行排序。
示例 2:回调...一个函数在循环中执行某些操作,但每次迭代都需要应用程序反馈。对于该函数,回调是作为参数传递的函数指针。
函数指针的另一个常见用例是插件。插件可以实现为可动态加载的库。当应用程序加载一个插件时,它可以调用一个预定义的函数,传入一个带有应用程序函数指针的结构体,并返回一个带有插件函数指针的结构体。现在插件和应用程序可以互相调用对方的函数了。
一个非常常见的用途是事件处理程序的回调函数。例如,考虑一个按钮按下处理程序。您的 button_update() 可能类似于:
void button_update(struct button* button, void* data) {
if (button_pressed(button)) {
button.callback(data);
}
}
在这种情况下, button.callback 将是一个函数指针。
更重要的是:函数指针也有函数签名给出的类型。您还可以 [小心] 转换为特定的函数指针类型:
在 qsort 示例中,预期的比较器在签名中具有 const void*:
int (*comparator)(const void *, const void *)
.
但是,如果您有特定(非 void*)ptr 类型的比较函数,您可以简单地将其转换为 qsort 预期签名:
(int (*)(const void *, const void *))cmpr_specific
类似地,typedef 可用于定义函数指针的签名,从而省去计算括号的痛苦:
typedef int (*MYFUNC_T)(int arg);
MYFUNC_T callptr = myfunc; callptr(10);
,其中 myfunc 是具体的int myfunc(int arg)
现在想想它被定义为:int (*fptr_arr[10])(int (*)(int));
?
好吧,typedef 使它清晰易读:typedef int (*FUNC_T)(int arg); typedef int (*FUNC2_T)(FUNC_T func);
-- 一个接受函数指针参数的函数。
FUNC2_T fptr_arr[10]; fptr_arr[0]=myfunc;
另一个例子是一些重型系统编程或实时嵌入式编程。处理器中断向量表可以实现为函数指针数组。它们在汇编程序中以这种方式实现,如果您有一种(非 C 标准)指定函数表的内存位置的方式,也可以在 C 中完成。