多态性(在C语言中)

27

8
你可以通过实现一些虚函数表技术来编写具有多态性的代码。这只是通过玩弄函数指针来创建适当的间接层次关系而已。 - Kerrek SB
2
https://dev59.com/4XRB5IYBdhLWcg3wv5tA - 0x90
4个回答

51

这是Nekuromento的第二个示例,按照我认为适用于面向对象C的习惯方式进行了重构:

animal.h

#ifndef ANIMAL_H_
#define ANIMAL_H_

struct animal
{
    // make vtable_ a pointer so they can be shared between instances
    // use _ to mark private members
    const struct animal_vtable_ *vtable_;
    const char *name;
};

struct animal_vtable_
{
    const char *(*sound)(void);
};

// wrapper function
static inline const char *animal_sound(struct animal *animal)
{
    return animal->vtable_->sound();
}

// make the vtables arrays so they can be used as pointers
extern const struct animal_vtable_ CAT[], DOG[];

#endif

cat.c

#include "animal.h"

static const char *sound(void)
{
    return "meow!";
}

const struct animal_vtable_ CAT[] = { { sound } };

dog.c

#include "animal.h"

static const char *sound(void)
{
    return "arf!";
}

const struct animal_vtable_ DOG[] = { { sound } };

main.c

#include "animal.h"
#include <stdio.h>

int main(void)
{
    struct animal kitty = { CAT, "Kitty" };
    struct animal lassie = { DOG, "Lassie" };

    printf("%s says %s\n", kitty.name, animal_sound(&kitty));
    printf("%s says %s\n", lassie.name, animal_sound(&lassie));

    return 0;
}

这是运行时多态的一个示例,因为方法解析发生在运行时。

C1x添加了泛型选择,通过宏实现编译时多态成为可能。下面的示例摘自C1x 4月份草案第6.5.1.1 §5节:

#define cbrt(X) _Generic((X), \
    long double: cbrtl, \
    default: cbrt, \
    float: cbrtf \
)(X)

C99中已经通过头文件tgmath.h提供了用于数学函数的类型通用宏,但是没有办法让用户定义自己的宏而不使用编译器扩展。


DOGCAT 放在 animal.h 文件中是惯例吗?我可以想象它们也可以放在各自的头文件中。 - Brady Dean

24

在C语言中,几乎所有的运行时多态实现都将使用函数指针,因此这是基本的构建块。

以下是一个简单的示例,其中过程的运行时行为取决于其参数。

#include <stdio.h>

int tripple(int a) {
    return 3 * a;
}

int square(int a) {
    return a * a;
}

void transform(int array[], size_t len, int (*fun)(int)) {
    size_t i = 0;
    for(; i < len; ++i)
        array[i] = fun(array[i]);
}

int main() {
    int array[3] = {1, 2, 3};
    transform(array, 3, &tripple);
    transform(array, 3, &square);

    size_t i = 0;
    for (; i < 3; ++i)
        printf("%d ", array[i]);

    return 0;
}

通过使用函数指针,您可以创建虚拟表并将其用于创建“对象”,这些对象在运行时会被统一处理,但会有不同的行为。

#include <stdio.h>

struct animal_vtable {
    const char* (*sound)();
};

struct animal {
    struct animal_vtable methods;
    const char* name;
};

const char* cat_sound() {
    return "meow!";
}

const char* dog_sound() {
    return "bark!";
}

void describe(struct animal *a) {
    printf("%s makes \"%s\" sound.\n", a->name, a->methods.sound());
}

struct animal cat = {{&cat_sound}, "cat"};
struct animal dog = {{&dog_sound}, "dog"};

int main() {
    describe(&cat);
    describe(&dog);

    return 0;
}

在C语言中,进行面向对象编程的“惯用方式”(如果有这样一种方式)是将struct animalmethods成员定义为指针,以便同一“类”的不同实例可以共享它们的虚函数表。 - Christoph
@Christoph:这是一种额外的间接层,可能是好事也可能是坏事。这是空间与速度之间的权衡。 - Karoly Horvath
@yi_H:当然可以,但我仍认为使用指针是惯用的方式,因为虚函数表对应于对象的类,而类(至少从概念上)是在实例之间共享的;如果你这样看它,将虚函数表包含在实例中提供了一个方法缓存,也就是一种优化方式... - Christoph
1
@Christoph:那是因为你在的思维模式下。想象一下一个系统,其中每个虚函数表项都可以独立设置。在这种情况下,采用类的方法会导致组合爆炸。再次强调,我只是说没有正确的方法,只有权衡取舍。 - Karoly Horvath
5
“Woof”是狗在RFC K9中确认的习惯性发出的声音。 - Matt Montag

3

C语言本身并没有多态的内在支持,但是可以使用函数指针、基类(结构体)转换等设计模式来提供动态调度的逻辑等价物。 GTK 库 是一个很好的例子。


2
我猜你已经查看了多态性的维基百科文章。
在计算机科学中,多态性是一种编程语言特性,允许使用统一接口处理不同数据类型的值。
根据这个定义,C语言本身不支持多态性。例如,没有通用函数可以获取一个数字的绝对值(abs和fabs分别为整数和双精度浮点数)。
如果你还熟悉C ++,可以看看面向对象的继承和模板 - 这些是多态性的机制。

你需要将指针进行强制类型转换,因此从技术上讲,它们不再是不同的类型。虽然这是可能的解决方法之一。 - aztek
2
C99在名为tgmath.h的头文件中添加了适用于算术类型的多态数学函数;C1x则增加了_Generic,使得用户定义的多态宏成为可能。 - Christoph

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