我知道这听起来很傻,我知道C不是面向对象的语言。
但有没有办法在C中实现动态方法调度? 我考虑过函数指针,但没有得到完整的想法。
我怎么能实现这个?
答案 0 :(得分:33)
正如其他人所指出的那样,在C中实现这一点当然是可能的。这不仅是可能的,而且是一种相当普遍的机制。最常用的示例可能是UNIX中的文件描述符接口。对文件描述符的read()
调用将调度到特定于提供该文件描述符的设备或服务的读取函数(它是一个文件吗?它是一个套接字吗?它是其他类型的设备吗?)。 / p>
唯一的技巧是从抽象类型中恢复指向具体类型的指针。对于文件描述符,UNIX使用包含特定于该描述符的信息的查找表。如果使用指向对象的指针,则接口用户持有的指针是“基本”类型,而不是“派生”类型。 C没有继承本身,但它确保指向struct
的第一个元素的指针等于包含struct
的指针。因此,您可以通过使“base”的实例成为“derived”的第一个成员来使用它来恢复“派生”类型。
这是一个带有堆栈的简单示例:
struct Stack {
const struct StackInterface * const vtable;
};
struct StackInterface {
int (*top)(struct Stack *);
void (*pop)(struct Stack *);
void (*push)(struct Stack *, int);
int (*empty)(struct Stack *);
int (*full)(struct Stack *);
void (*destroy)(struct Stack *);
};
inline int stack_top (struct Stack *s) { return s->vtable->top(s); }
inline void stack_pop (struct Stack *s) { s->vtable->pop(s); }
inline void stack_push (struct Stack *s, int x) { s->vtable->push(s, x); }
inline int stack_empty (struct Stack *s) { return s->vtable->empty(s); }
inline int stack_full (struct Stack *s) { return s->vtable->full(s); }
inline void stack_destroy (struct Stack *s) { s->vtable->destroy(s); }
现在,如果我想使用固定大小的数组实现堆栈,我可以这样做:
struct StackArray {
struct Stack base;
int idx;
int array[STACK_ARRAY_MAX];
};
static int stack_array_top (struct Stack *s) { /* ... */ }
static void stack_array_pop (struct Stack *s) { /* ... */ }
static void stack_array_push (struct Stack *s, int x) { /* ... */ }
static int stack_array_empty (struct Stack *s) { /* ... */ }
static int stack_array_full (struct Stack *s) { /* ... */ }
static void stack_array_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_array_create () {
static const struct StackInterface vtable = {
stack_array_top, stack_array_pop, stack_array_push,
stack_array_empty, stack_array_full, stack_array_destroy
};
static struct Stack base = { &vtable };
struct StackArray *sa = malloc(sizeof(*sa));
memcpy(&sa->base, &base, sizeof(base));
sa->idx = 0;
return &sa->base;
}
如果我想使用列表来实现堆栈:
struct StackList {
struct Stack base;
struct StackNode *head;
};
struct StackNode {
struct StackNode *next;
int data;
};
static int stack_list_top (struct Stack *s) { /* ... */ }
static void stack_list_pop (struct Stack *s) { /* ... */ }
static void stack_list_push (struct Stack *s, int x) { /* ... */ }
static int stack_list_empty (struct Stack *s) { /* ... */ }
static int stack_list_full (struct Stack *s) { /* ... */ }
static void stack_list_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_list_create () {
static const struct StackInterface vtable = {
stack_list_top, stack_list_pop, stack_list_push,
stack_list_empty, stack_list_full, stack_list_destroy
};
static struct Stack base = { &vtable };
struct StackList *sl = malloc(sizeof(*sl));
memcpy(&sl->base, &base, sizeof(base));
sl->head = 0;
return &sl->base;
}
堆栈操作的实现只会将struct Stack *
转换为它应该知道的内容。例如:
static int stack_array_empty (struct Stack *s) {
struct StackArray *sa = (void *)s;
return sa->idx == 0;
}
static int stack_list_empty (struct Stack *s) {
struct StackList *sl = (void *)s;
return sl->head == 0;
}
当堆栈的用户在堆栈实例上调用堆栈操作时,操作将调度到vtable
中的相应操作。此vtable
由创建函数初始化,其函数与其特定实现相对应。所以:
Stack *s1 = stack_array_create();
Stack *s2 = stack_list_create();
stack_push(s1, 1);
stack_push(s2, 1);
在stack_push()
和s1
上都会调用 s2
。但是,对于s1
,它会发送到stack_array_push()
,而对于s2
,它会发送到stack_list_push()
。
答案 1 :(得分:3)
是。它很容易实现。您将使用函数指针数组,然后使用这些函数指针进行调用。如果要“覆盖”某个功能,只需将相应的插槽设置为指向新功能即可。这正是C ++实现虚函数的方式。
答案 2 :(得分:1)
C ++(最初)建立在C之上。第一个C ++编译器实际上生成了C作为中间步骤。是的,是的,这是可能的。
Here's C ++如何做到这一点。
网上有大量可靠的信息,比我们几分钟内可以在这里输入更多。 “谷歌,你会发现。”
你在上面的评论中说:
如果他们在c中已经编写了一些代码但是添加了一些功能,那么有人会更喜欢。而不是使用OOlanguage从头开始编写。
要在C中使用这样的功能,您基本上需要重新实现OO语言功能。让人们使用这种新的OO方法是防止可用性的最大因素。换句话说,通过创建另一种重用方法,您实际上可以减少可重用性。
答案 3 :(得分:0)
我有点惊讶,没有人添加glib和/或整个gtk的东西作为例子。所以请检查: http://www.gtk.org/features.php
我知道你使用gtk时必须要有一些样板代码,而不是那么简单"容易"第一次正确。但是,如果一个人使用它,它是非常了不起的。你唯一要记住的是使用一种"对象"作为您的功能的第一个参数。但是如果你查看API,你会发现它在任何地方都被使用过。恕我直言,这真的是一个很好的例子,可能与OOP的优点和问题 -