0

我是一个有很多 OOP 经验(C#)的 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 中的多态性”的帖子,但我想看看如何干净有效地构建像这样的多态数据结构(就这些结构的未使用成员浪费的内存而言)。

谢谢。

4

3 回答 3

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;

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

也检查一下:如何在 C 中模拟 OO 风格的多态性?

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

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

typedef struct
{
  int type;
  // data for folder - list, etc
} Folder;
于 2013-08-09T15:46:28.743 回答
2

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

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

这里有一些想法:

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

多态性通常与行为有关。您可以在结构中存储函数指针(“方法”),指向不同函数的指针为不同的对象实例提供不同的行为。C++ 的处理方式是为每个类提供一个函数指针表,然后每个对象实例为其类引用该表(顺便说一下,表指针也可以充当 RTTI 的 node_type 的角色)。这称为虚拟方法表

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

typedef struct BaseClass {
  int baseMember;
} BaseClass;

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

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

于 2013-08-09T16:03:54.680 回答
1

这是基于 X/Motif 的古老记忆的老式 C 多态性的图示。

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

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_file并将create_folder其设置为适当的功能(例如,visit_filevisit_folder)。然后,而不是打开枚举类型,walk只需调用

cur->visit(cur, on_file, on_folder);
于 2013-08-09T16:48:27.013 回答