【问题标题】:C, generalize functions argument's typeC、泛化函数参数的类型
【发布时间】:2018-05-27 12:43:13
【问题描述】:

我正在为链表和树编写一个 c 库,我正在寻找一种解决方案来概括这些库处理的数据类型,而不需要为我需要处理的每种类型创建一个列表/树库。

例如,我的列表库有这些功能:

/* list.h */
typedef int list_element; // <-- need to generalize that

 struct list_node {   
  list_element value;    
  struct list_node* next;
};

typedef struct list_node list_node;
typedef struct list_node* list;

extern list list_cons(list_element d, list l)

然后是我的 list.c:

/* list.c */
#include <list.h>

list list_cons(list_element d, list l){
    list m = malloc(sizeof(list_node));
    m->value = element_copy(d);
    m->next = l;
    return m;
}

现在假设在我的主程序中我必须使用一个 int 列表和一个 double 列表,我应该创建 2 对文件,例如 list_float.c/.h 和 list_int.c/.h
还有一些 list_element 可以是结构体,需要像 copy/isLess/isEqual 这样的函数来比较自己 我想在我的代码中写这样的东西:

/* main.c */
list_cons(void *data, list l);

data 是指向我想要的任何类型的指针,在 list_cons 内部,element_copy 适用于我传递的任何类型的数据(显然我需要复制数据而不是指向数据的指针,void* 是唯一的想法是我必须概括函数的参数类型)

【问题讨论】:

  • 基本上你想先 typedef int list_element 然后 typedef float list_element?不,你不能这样做。
  • 你可以这样做,但你不能在一个程序中使用它
  • @purec 我知道我不能,这就是问题所在。如果我主要使用不同类型的不同列表,我必须为每种类型创建一个库,并为每个函数使用不同的名称,这太痛苦了。我一直在寻找避免这种情况的解决方案
  • 您可以尝试使用可以轻松进行类型转换的指针,而不是 int、float 类型。例如 void* 数据
  • 如果您使用 C++,这可以通过模板轻松完成。

标签: c


【解决方案1】:

作为一般建议,您不应假设list_cons 将是构造链表的唯一方法。有时malloc 不可用,或者用户想要预分配静态数组中的所有内容,或者想要使用自定义分配器或......

作为具体示例,您可以查看https://github.com/torvalds/linux/blob/master/include/linux/list.h

如果您的代码需要其他许可,请在 xBSD Unix 源代码中搜索数据结构实现。

一般的想法是你只需要一个链表结构来包含下一个/上一个和类似的字段,而不是限制用户使用你的类型名称。所有迭代和基本操作都被定义为预处理器宏,您可以在它们之上实现复杂的算法。

【讨论】:

  • list 和 list_cons 只是具体化我的问题的示例。其他数据结构也有同样的问题。但是谢谢,我会更深入地研究宏,也许我可以找到解决方案和/或更好地理解 linux 中的链表
  • @LucaLumetti Linux 实现的想法非常简单。你有某种struct list_head { struct list_head *next; };,你将它与你的数据一起嵌入到一些结构中,然后在不介意容器的情况下对头部进行处理。如果您需要容器,您可以使用 container_of 宏来获得它,它基本上是标准 offsetof() 宏的逆。但是链接列表也很简单,这种list_head 抽象可能甚至不值得(IMO)。
  • @PSkocik 是的,明白了。我正在为大学考试编写这些库,在课程中我们总是将列表定义为struct node { struct node* next, element value },然后不得不在问题中使用 3 个不同结构的 3 个列表,我首先想到的只是 ctrl+c 和 ctrl+v 代码对于 3 个不同库中的列表(如 structA_list.c/.h、structB_list.c/.h、structC_list.c/.h)并更改每个列表的类型,然后我还必须更改函数名称和代码开始看起来真的很糟糕。所以我一直在寻找一个与教授教我们不太远的解决方案
【解决方案2】:

请注意,在 C11 中(阅读其标准 n1570 和一些 C reference site)可以以不同的方式处理不同的类型(参见其 §6.5.2)。

特别是,实现可以不同地处理intdouble 和指针值(它们的大小和alignment 通常不同,请参阅sizeofalignof),并且一些ABI 约定决定它们在不同的寄存器中传递(例如在函数调用中)。

因此,您不能以相同的方式编写(可移植地)处理所有intdouble 等的东西(除非您有variadic 函数;使用&lt;stdarg.h&gt;

您可能决定实现一些“通用”列表,其内容是一些任意指针。但是你需要一些关于它们的约定(谁在分配那个指针,谁在释放它,也许允许什么操作,等等......)。查看Glib doubly-linked lists 以获得灵感。

您还可以使用预处理器技术来生成abstract data type(在您的列表中)和实现它的函数,并为内容提供一些类型名称。查看SGLIB 以获得灵感。

您还可以使用一些metaprogramming 技术:您将以某种方式描述元素的类型,然后将该描述输入到您的元程序中,元程序是一些C 代码生成器。查看SWIG 以获得灵感。生成的 C 代码将实现列表的抽象数据类型。

不要忘记记忆management issues,并清楚地描述和记录您围绕它们的约定。阅读RAII

还可以考虑一些复杂的情况,例如一些字符串列表(可能是动态分配的strdup 或使用asprintf 获得)。你会发现事情并不简单,你需要显式约定(例如,某些字符串可以在两个子列表之间共享吗?那个字符串什么时候是@987654344 @-d, ...)。

【讨论】:

    【解决方案3】:

    这可能是使用联合的好地方。

    您可以将基本数据类型定义为您想要支持的最常见类型的联合。然后,您将定义一个有问题的类型的枚举,以使用该值来标记联合包含的内容。

    enum element_type {
        TYPE_INT,
        TYPE_DOUBLE
    };
    
    typedef union {
        int e_int;
        double e_double;
    } list_element;
    
    struct list_node {   
      enum element_type type;
      list_element value;    
      struct list_node* next;
    };
    

    然后你像这样添加到列表中:

    list list_cons(list_element d, enum element_type type, list l){
        list m = malloc(sizeof(list_node));
        m->type = type;
        m->value = element_copy(d);
        m->next = l;
        return m;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-25
      • 2010-10-04
      相关资源
      最近更新 更多