C中的多态数据结构

时间:2013-08-09 15:35:40

标签: c oop data-structures polymorphism

我是初学者,有很多OOP经验(C#),我无法理解如何在C中实现“多态”的概念。

现在,我正在考虑如何使用结构捕获文件系统的逻辑结构。我有一个包含文件夹和文件的文件夹。此文件夹中的文件夹可以包含其他文件和文件夹等。

我的方法:

typedef enum { file, folder } node_type;

struct node;
typedef struct {
    node_type type;
    char *name;
    struct node *next;
    struct node *children;
} node;

这是我能做的最好的吗?我发现了很多关于“C中的多态性”的帖子,但是我想看看这样的多态数据结构如何能够干净有效地构建(就这些结构中未使用的成员浪费的内存而言)。

感谢。

3 个答案:

答案 0 :(得分:2)

我希望我理解你想要的东西 - 我不确定,但我想你想做那样的事情:

typedef struct
{
   int type; // file or folder?
} Item;

typedef struct
{
   struct A;
   // data related to a file
} File;

typedef struct
{
   struct A;
   // data related to a folder - like pointer to list of Item
} Folder;

只要两个结构都遵循相同的内存映射(相同的变量)并将其作为子项添加,您就可以在两个结构中正确使用指针。

同时检查一下:How can I simulate OO-style polymorphism in C?

编辑:我不确定上面的语法(从上面的链接中获取)。我习惯这样写它:

typedef struct
{
  int type;
  // data for file
} File;

typedef struct
{
  int type;
  // data for folder - list, etc
} Folder;

答案 1 :(得分:2)

C没有内在的多态性概念。

您最终将从头开始实现您想要的机制。这不是一件坏事。它为您提供了更多的灵活性。例如,C ++虚方法每个类都是硬连线的,你不能每个实例改变方法指针。

以下是一些想法:

您的node_type字段提供了一种执行运行时类型查询的方法。更进一步,您可以使用区分(或标记)联合将多个类型打包到一个结构中:http://en.wikipedia.org/wiki/Tagged_union。我不确定变体类型是否有资格作为OO。

多态性通常与行为有关。您可以在结构中存储function pointers(“方法”),并指向不同的函数,为不同的对象实例提供不同的行为。 C ++的处理方式是每个类都有一个函数指针表,然后每个对象实例引用它的类的表(顺便提一下,表指针也可以扮演你的node_type for RTTI的角色)。这称为virtual method table

数据继承意味着子类包含所有基类的数据成员以及一些额外的东西。在C中,最简单的方法是将基类结构嵌入派生类结构的头部。这样一个指向derived的指针是指向base的指针。

typedef struct BaseClass {
  int baseMember;
} BaseClass;

typedef struct DerivedClass {
  BaseClass base;
  int derivedMember;
} DerivedClass;

你可能比阅读Stanley B. Lippman撰写的“Inside the C ++ Object Model”更糟糕。例如,如果您想了解如何实现多重继承,这将有所帮助。

答案 2 :(得分:1)

以下是基于X / Motif古代记忆的老派C多态性的例证。

如果你只想要一个有区别的联合(或者只是一个带有子指针的类型结构可能为null),那么在你的情况下它可能更简单。

enum NodeType { TFile, TFolder };
struct Node {
    enum NodeType type;
    const char *name;
    struct Node *next;
};

struct FileNode {
    struct Node base_;
};

struct FolderNode {
    struct Node base_;
    struct Node *children;
    /* assuming children are linked with their next pointers ... */
};

以下是构造函数 - 我将把链表填充为读者的练习......

struct Node* create_file(const char *name) {
    struct FileNode *file = malloc(sizeof(*file));
    file->base_.type = TFile;
    file->base_.name = name; /* strdup? */
    file->base_.next = NULL;
    return &file->base_;
}

struct Node* create_folder(const char *name) {
    struct FolderNode *folder = malloc(sizeof(*folder));
    folder->base_.type = TFolder;
    folder->base_.name = name;
    folder->base_.next = NULL;
    folder->children = NULL;
    return &folder->base_;
}

现在我们可以走一个层次结构,检查每个节点的类型并做出适当的响应。这依赖于第一个成员子对象具有零偏移到父对象 - 如果不成立(或者您需要多继承),则必须使用offsetof在基本类型和“派生”类型之间进行转换。

void walk(struct Node *root,
          void (*on_file)(struct FileNode *),
          void (*on_folder)(struct FolderNode *))
{
    struct Node *cur = root;
    struct FileNode *file;
    struct FolderNode *folder;

    for (; cur != NULL; cur = cur->next) {
        switch (cur->type) {
        case TFile:
            file = (struct FileNode *)cur;
            on_file(file);
            break;
        case TFolder:
            folder = (struct FolderNode *)cur;
            on_folder(folder);
            walk(folder->children, on_file, on_folder);
            break;
        }
    }
}

请注意,我们有一种多态基类型,但是我们可以使用虚函数进行更完整的多态设置,而不是打开类型枚举。只需添加一个指向Node的函数指针,如:

void (*visit)(struct Node *self,
             void (*on_file)(struct FileNode *),
             void (*on_folder)(struct FolderNode *));

并让create_filecreate_folder将其设置为适当的功能(例如visit_filevisit_folder)。然后,walk只会调用

,而不是打开枚举类型
cur->visit(cur, on_file, on_folder);