如何在C结构体中使用函数指针?

9
我希望了解如何在C结构体中使用函数指针来模拟面向对象编程,但在我的搜索中,我只发现了一些问题,例如这个,其中的答案仅仅是使用函数指针,而没有描述它的工作原理。
我的最佳猜测是像这样:
#include <stdio.h>
#include <stdlib.h>

struct my_struct
{
    int data;
    struct my_struct* (*set_data) (int);
};

struct my_struct* my_struct_set_data(struct my_struct* m, int new_data)
{
    m->data = new_data;
    return m;
}

struct my_struct* my_struct_create() {
    struct my_struct* result = malloc((sizeof(struct my_struct)));
    result->data = 0;
    result->set_data = my_struct_set_data;
    return result;
}

int main(int argc, const char* argv[])
{
    struct my_struct* thing = my_struct_create();
    thing->set_data(1);
    printf("%d\n", thing->data);
    free(thing);
    return 0;
}

但是这给我带来了编译器警告warning: assignment from incompatible pointer type,所以显然我做错了什么。有人能否提供一个小而完整的示例,说明如何正确地在C结构中使用函数指针?

我的C语言课程甚至没有提到这些。这让我想知道C程序员是否真正使用它们。在C结构中使用函数指针的优缺点是什么?


3
编译器警告可能是因为实际函数的参数列表与结构体变量中的参数列表不匹配:实际函数具有 (struct my_struct*,int),而结构定义仅具有 (int) - pb2q
@pb2q 我应该改哪一个? - Eva
@Eva 如果你想将 my_struct_set_data() 分配给它,那么你应该将结构定义中的函数指针更改为 struct my_struct* (*set_data) (struct my_struct*, int); - eddieantonio
2个回答

14
Andy Stow Away的答案解决了我的编译警告问题,但没有回答我的第二个问题。eddieantonio和Niklas R在对该答案的评论中回答了我的第二个问题,但并没有解决我的编译警告问题。因此,我将它们汇总成一个答案。
C语言不是面向对象的,试图在C语言中模拟面向对象的设计通常会导致不好的风格。像我在示例中所做的那样,复制在结构体上调用的方法,以便可以使用指向结构体的指针调用它们,没有例外地都是这种情况。(而且,它违反了DRY原则。) 结构体中的函数指针对于多态性更有用。例如,如果我有一个表示线性元素序列的通用容器的结构体vector,存储一个指向比较函数的函数指针成员可能很有用,以便对vector进行排序和搜索。每个vector实例可以使用不同的比较函数。然而,在涉及到作用于结构体本身的函数时,更好的风格是拥有单独的、不在结构体内重复的函数。
这使得正确的答案更加复杂。什么才是正确的呢?是如何让我的上面的示例编译通过呢?还是如何重新格式化我的上面的示例,使其具有良好的风格?还是什么样的结构体示例会以C程序员的方式使用函数指针呢?在制定我的问题时,我没有预料到答案会是我的问题是错误的。为了完整起见,我将提供每个问题的答案示例。

修复编译警告

#include <stdio.h>
#include <stdlib.h>

struct my_struct
{
    int data;
    struct my_struct* (*set_data) (struct my_struct*, int);
};

struct my_struct* my_struct_set_data(struct my_struct* m, int new_data)
{
    m->data = new_data;
    return m;
}

struct my_struct* my_struct_create()
{
    struct my_struct* result = malloc((sizeof(struct my_struct)));
    result->data = 0;
    result->set_data = my_struct_set_data;
    return result;
}

int main(int argc, const char* argv[])
{
    struct my_struct* thing = my_struct_create();
    thing->set_data(thing, 1);
    printf("%d\n", thing->data);
    free(thing);
    return 0;
}

重新格式化样式

#include <stdio.h>
#include <stdlib.h>

struct my_struct
{
    int data;
};

void my_struct_set_data(struct my_struct* m, int new_data)
{
    m->data = new_data;
}

struct my_struct* my_struct_create()
{
    struct my_struct* result = malloc((sizeof(struct my_struct)));
    result->data = 0;
    return result;
}

int main(int argc, const char* argv[])
{
    struct my_struct* thing = my_struct_create();
    my_struct_set_data(thing, 1);
    printf("%d\n", thing->data);
    free(thing);
    return 0;
}

展示在结构体中使用函数指针的用法

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct my_struct
{
    void* data;
    int (*compare_func)(const void*, const void*);
};

int my_struct_compare_to_data(struct my_struct* m, const void* comparable)
{
    return m->compare_func(m->data, comparable);
}

struct my_struct* my_struct_create(void* initial_data,
        int (*compare_func)(const void*, const void*))
{
    struct my_struct* result = malloc((sizeof(struct my_struct)));
    result->data = initial_data;
    result->compare_func = compare_func;
    return result;
}

int int_compare(const void* a_pointer, const void* b_pointer)
{
    return *(int*)a_pointer - *(int*) b_pointer;
}

int string_compare(const void* a_pointer, const void* b_pointer)
{
    return strcmp(*(char**)a_pointer, *(char**)b_pointer);
}

int main(int argc, const char* argv[])
{
    int int_data = 42;
    struct my_struct* int_comparator =
            my_struct_create(&int_data, int_compare);

    char* string_data = "Hello world";
    struct my_struct* string_comparator =
             my_struct_create(&string_data, string_compare);

    int int_comparable = 42;
    if (my_struct_compare_to_data(int_comparator, &int_comparable) == 0)
    {
        printf("The two ints are equal.\n");
    }

    char* string_comparable = "Goodbye world";
    if (my_struct_compare_to_data(string_comparator,
            &string_comparable) > 0)
    {
        printf("The first string comes after the second.\n");
    }

    free(int_comparator);
    free(string_comparator);

    return 0;
}

1
将以下与编程有关的内容从英语翻译为中文。仅返回翻译后的文本:+1 用于综合。另请参见此相关 答案 - trashgod

11

在你的结构定义中,将其更改为

struct my_struct
{
    int data;
    struct my_struct* (*set_data) (struct my_struct*,int);
};

现在在主函数中使用上面的函数指针:

thing->set_data(thing,1);

2
@Eva,您能详细说明一下您的问题吗?如果您的意思是,在C语言中使用包含函数指针的结构体的优点(假设这些函数指针操作的是结构体本身),那么我能想到最有用的优点就是多态性。例如,您可以定义几个set_data()函数,任何结构体都可以选择适合它的特定set_data()函数。然后,当您处理多个结构体时,您不需要选择哪个设置数据函数该特定结构体将使用;它已经“内置”在结构体中了。 - eddieantonio
3
@Eva,你不能期望thing->set_data(value)能够像在C++中一样运行。这个函数没有引用到thing。你可以将C++中的方法调用想象成被转换为ThingsClass_do_stuff(thing)或者是如果该方法是虚拟的,甚至可以被转换为thing->_vtable->do_stuff(thing) - Niklas R
@eddieantonio 我的真正问题是在优缺点方面,何时使用其中之一?在我的结构体中是否惯常同时拥有 my_struct_set_data 方法和 set_data 方法,还是 C 程序员通常只使用第一个,并主要使用第二个进行多态性操作? - Eva
@Eva 你好,这是关于正确性的问题。编译器警告并不一定意味着你的代码没问题。只有当你运行代码时才能知道它是否正常工作(请运行之前的程序并查看输出)。在你的情况下,my_struct_set_data函数的第一个参数是struct my_struct* m,但是你的函数指针定义没有相同的签名。 - Andy Stow Away
1
@Eva 个人而言,同时使用这两种方法的想法有点让我感到害怕。我会只编写一个 my_struct_set_data() 方法,并将结构体保留为没有任何函数指针的状态,但这只是我的个人偏好。因此,如果我代表了典型的 C 程序员,那么通常情况下,C 程序员只使用第一种方法,并主要使用第二种方法来实现多态性! - eddieantonio
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接