我最近在C语言中有一些函数指针的经验。

因此,按照回答自己问题的传统,我决定对最基本的问题做一个简短的总结,以供那些需要快速切入主题的人使用。


当前回答

指向函数的指针很有用,因为正如《C编程语言》一书所说,C中的函数不是变量。这意味着,

// Say you have add function
int add(int x, int y){
    return x + y;
}

// Say you have another add function
int another_add(int x, int y){
    return y + x;
}


int main(){
    // Although the types of another_add and add are same
    // You can't do
    another_add = add
    
    // You have a compute function that takes a function of int's signature
    int (*compute)(int, int);
   
    // You won't even be able to pass functions to other functions
    // (Although when you do, C is just passing the pointer to that function)
    // So, compute(add) is really compute(&add)
    // But you can create a pointer to functions that are variables
    // you can assign to and/or pass to other functions

    int (*operation)(int, int);
    // Now you can do
    operation = &add;
    // You could also do, the following to do the same thing
    // When a function is passed in right hand side of assignment,
    // C knows that you mean pointer, and you don't need explicit &
    operation = add;
}

同样,数组也不是C中的变量。您可以编写一个与上面类似的示例并进行测试。

其他回答

C中的函数指针可以用于在C中执行面向对象的编程。

例如,以下行用C编写:

String s1 = newString();
s1->set(s1, "hello");

是的,->和缺少新运算符是一个致命的漏洞,但这似乎意味着我们正在将某些String类的文本设置为“hello”。

通过使用函数指针,可以模拟C语言中的方法。

这是如何实现的?

String类实际上是一个带有一组函数指针的结构,用作模拟方法的方法。以下是String类的部分声明:

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

可以看出,String类的方法实际上是指向声明函数的函数指针。在准备String实例时,调用newString函数以设置指向其各自函数的函数指针:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

例如,通过调用get方法调用的getString函数定义如下:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

需要注意的一点是,没有对象实例的概念,也没有方法实际上是对象的一部分,因此每次调用都必须传递一个“self-object”。(内部结构只是一个隐藏结构,它在前面的代码列表中被省略了——这是一种执行信息隐藏的方式,但这与函数指针无关。)

因此,与其能够执行s1->set(“hello”);,必须传入对象才能对s1->set(s1,“hello”)执行操作。

由于这个小的解释必须传递给你自己一个引用,我们将转到下一部分,这是C语言中的继承。

假设我们要创建String的子类,比如ImmutableString。为了使字符串不可变,set方法将不可访问,同时保持对get和length的访问,并强制“构造函数”接受char*:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

基本上,对于所有子类,可用的方法都是函数指针。这一次,set方法的声明不存在,因此不能在ImmutableString中调用它。

至于ImmutableString的实现,唯一相关的代码是“构造函数”函数newImmutableString:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

在实例化ImmutableString时,指向get和length方法的函数指针实际上引用String.get和String.length方法,方法是通过内部存储的String对象的基变量。

使用函数指针可以从超类继承方法。

我们可以进一步研究C。

例如,如果出于某种原因,我们希望将长度方法的行为更改为在ImmutableString类中始终返回0,那么所要做的就是:

添加将用作重写长度方法的函数。转到“构造函数”并将函数指针设置为重写长度方法。

在ImmutableString中添加重写长度方法可以通过添加lengthOverrideMethod来执行:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

然后,构造函数中length方法的函数指针连接到lengthOverrideMethod:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

现在,长度方法将引用lengthOverrideMethod函数中定义的行为,而不是ImmutableString类中的长度方法与String类具有相同的行为。

我必须补充一句免责声明,我仍在学习如何在C语言中使用面向对象的编程风格进行编写,因此可能有一些点我没有很好地解释,或者在如何在C中最好地实现OOP方面可能是不正确的。但我的目的是试图说明函数指针的多种用途之一。

有关如何在C语言中执行面向对象编程的更多信息,请参阅以下问题:

C语言中的对象定向?你能用C语言编写面向对象的代码吗?

一旦有了基本的声明器,函数指针就很容易声明:

id:id:id是指针:*D:D指针指向函数:D(<parameters>):D函数获取<parameter>返回

而D是使用相同规则构建的另一个声明器。最后,在某个地方,它以ID结尾(参见下面的示例),这是声明实体的名称。让我们尝试构建一个函数,该函数获取指向不获取任何内容并返回int的函数的指针,并返回指向获取char并返回int函数的指针

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

如您所见,使用typedef构建它非常容易。如果没有typedef,上面的声明器规则也不难,而且应用一致。如您所见,我漏掉了指针指向的部分,以及函数返回的内容。这是出现在声明最左边的内容,并不重要:如果已经建立了声明人,则在末尾添加。让我们这样做吧。始终如一地构建它,首先使用[和]显示结构:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

如您所见,可以通过一个接一个地附加声明符来完全描述类型。施工有两种方式。一种是自下而上的,从非常正确的事情(树叶)开始,一直到标识符。另一种方式是自上而下的,从标识符开始,一直到叶子。我会双向展示。

自下而上

构造从右边的东西开始:返回的东西,它是取char的函数。为了保持声明人的不同,我将对他们进行编号:

D1(char);

直接插入了char参数,因为它很简单。通过用*D2替换D1来添加指向声明器的指针。注意,我们必须用括号括住*D2。通过查找*-运算符和函数调用运算符()的优先级可以知道这一点。如果没有括号,编译器会将其读为*(D2(char p))。当然,这不会再简单地用*D2取代D1。声明符周围始终允许使用括号。实际上,如果你添加了太多,你不会出错。

(*D2)(char);

返回类型已完成!现在,让我们用返回<parameters>的函数声明器函数来替换D2,这就是我们现在的D3(<parameter>)。

(*D3(<parameters>))(char)

注意,不需要括号,因为我们希望D3这次是函数声明器,而不是指针声明器。很好,唯一剩下的就是它的参数。该参数的处理方式与我们处理返回类型的处理方式完全相同,只是将char替换为void。所以我会复制它:

(*D3(   (*ID1)(void)))(char)

我已经用ID1替换了D2,因为我们已经完成了该参数(它已经是一个指向函数的指针,不需要另一个声明器)。ID1将是参数的名称。现在,我在上面的结尾告诉我们,一个添加了所有声明者修改的类型,即出现在每个声明的最左边的类型。对于函数,这将成为返回类型。对于指向类型等的指针……写下类型时很有趣,它将以相反的顺序出现在最右边:)无论如何,替换它会产生完整的声明。当然,这两次都是int。

int (*ID0(int (*ID1)(void)))(char)

在该示例中,我调用了函数ID0的标识符。

自上而下

这从类型描述中最左边的标识符开始,当我们从右边走过时,将该声明符包装起来。从函数获取<parameters>开始返回

ID0(<parameters>)

描述中的下一件事(在“返回”之后)是指针指向。让我们合并它:

*ID0(<parameters>)

然后,下一件事是functon获取<parameters>返回。该参数是一个简单的字符,因此我们再次将其放入,因为它非常简单。

(*ID0(<parameters>))(char)

注意我们添加的括号,因为我们再次希望先绑定*,然后绑定(char)。否则它将读取获取<parameters>返回函数的函数。。。。不,甚至不允许返回函数的函数。

现在我们只需要放置<parameters>。我将展示一个简短的嘲笑版本,因为我认为你现在已经知道该怎么做了。

pointer to: *ID1
... function taking void returning: (*ID1)(void)

就像我们使用自底向上方法一样,只需将int放在声明符之前,我们就完成了

int (*ID0(int (*ID1)(void)))(char)

好东西

自下而上还是自上而下更好?我习惯于自下而上,但有些人可能更喜欢自上而下。我认为这是品味问题。顺便说一句,如果您在该声明中应用所有运算符,您将得到一个int:

int v = (*ID0(some_function_pointer))(some_char);

这是C中声明的一个很好的属性:声明断言,如果在使用标识符的表达式中使用这些运算符,那么它将在最左边生成类型。阵列也是如此。

希望你喜欢这个小教程!现在,当人们对函数奇怪的声明语法感到疑惑时,我们可以联系到这一点。我尽量少放C内部构件。随意编辑/修复其中的内容。

函数指针通常由typedef定义,并用作参数和返回值。

上面的答案已经解释了很多,我只举一个完整的例子:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

C中函数指针的一个主要用途是调用在运行时选择的函数。例如,C运行时库有两个例程,qsort和bsearch,它们获取一个指向函数的指针,该函数被调用来比较正在排序的两个项目;这允许您根据希望使用的任何条件分别对任何内容进行排序或搜索。

一个非常基本的例子是,如果有一个名为print(int x,int y)的函数,反过来可能需要调用一个函数(add()或sub(),它们是相同类型的),那么我们将做什么,我们将向print()函数添加一个函数指针参数,如下所示:

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

输出为:

值为:410值为:390

我最喜欢的函数指针用途之一是迭代器-

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}