C中的通用列表操作函数?

Posted

技术标签:

【中文标题】C中的通用列表操作函数?【英文标题】:Generic list manipulation function in C? 【发布时间】:2010-09-24 11:33:58 【问题描述】:

什么是 C 中的通用列表操作函数? (我在浏览一些材料时看到了这一点。)

这个函数和可以接受任何类型元素的函数有什么区别?

它们是一样的吗...?如果它们不同,我们如何单独实现它们?

【问题讨论】:

【参考方案1】:

一般列表可能是单链接的,并且可能假定列表中的项目具有如下结构:

typedef struct list_item list_item;

struct list_item

    list_item *next;
    ...data for node...
;

使用此布局,您可以编写函数来仅使用 next 指针来操作列表。

有时,“...data for node...”将是一个简单的“void *”;也就是说,列表项将包含指向列表中下一个节点的指针(如果没有下一个节点,则为 NULL)和指向数据的指针。

typedef struct list list;

struct list

    list *next;
    void *data;
;

由于您可以将任何指针强制转换为“void *”,因此您可以在列表中混合任何数据类型 - 但您的代码必须知道如何处理它们。

您询问的是“一个”通用列表函数,但可能没有一个单一功能的万能设计,当然也不是一个简单的设计。有许多可能的函数集可以生成通用列表函数。一组受 Lisp 启发,包括:

void *car(list *lp);    // Return the data for the first item on the list
list *cdr(list *lp);    // Return the tail of the list
list *cons(list *lp1, list *lp2);   // Construct a list from lists lp1 and lp2

list *cond(list *lp, void *data);  // Append data item to list

您可能希望提供测试列表是否为空以及其他一些项目的功能。

在 Koenig 的“Ruminations on C++”中可以找到一个很好的说明,诚然是在 C++ 中。这些想法可以很容易地应用到 C 中——它并不难(尽管 C 中的存储管理比 C++ 中的更难)。

【讨论】:

【参考方案2】:

C 没有“通用”指针或对象的概念——你能得到的最接近的是使用void* 指针。如果您希望一段代码能够处理任何数据类型,您几乎必须使用void* 指针。对于大小不大于指针的数据类型,可以在类型和void*之间进行转换;对于较大的数据类型,您必须使用动态内存并让void* 成员指向动态内存。请注意内存泄漏!

typedef struct list_node 
  struct list_node *next;
  void *data;
 list_node;

void list_insert(list_node *node, void *data) 
  // ...

另一方面,如果您想为每种可能的数据类型生成代码,则必须使用宏来完成,然后为您可能使用的每种数据类型实例化宏。例如:

#define DEFINE_LIST(type) \
  typedef struct list_node_##type  \
    struct list_node_##type *next; \
    type data; \
  

#define IMPLEMENT_LIST_INSERT(type) \
  void list_##type##_insert(list_node_##type *node, type data)  \
    ... \
  

DEFINE_LIST(int);     // defines struct list_node_int
DEFINE_LIST(double);  // defines struct list_node_double
IMPLEMENT_LIST_INSERT(int);    // defines list_int_insert
IMPLEMENT_LIST_INSERT(double); // defines list_double_insert

【讨论】:

【参考方案3】:

Linux 内核在其linux/list.h 标头上有一个有趣的C 通用链表实现。它是一个带有头节点的双向链表,使用如下:

struct mystruct 
    ...
    /* Contains the next and prev pointers */
    struct list_head mylist;
    ...
    /* A single struct can be in several lists */
    struct list_head another_list;
    ...
;

struct list_head mylist_head;
struct list_head another_list_head;

这个小例子中的一些有趣的东西:

列表节点嵌入在目标结构中,不需要数据指针。 列表节点不必位于目标结构的开头。 一个结构可以同时在多个链表中。 列表中的 next 和 prev 指针指向 struct list_head,而不是目标结构(在上面的示例中,它们指向第一个列表的 &(foo->mylist) 和第二个列表的 &(foo->another_list))。

所有列表操作函数都采用指向struct list_head 的指针(并且大多数函数根本不关心它是单独的头节点还是嵌入节点之一)。要从 struct list_head 获取目标结构,请使用 list_entry 宏(与 linux/kernel.h 标头中的 containter_of 宏相同),它扩展为简单的指针减法。

由于是带头节点的双向链表,可以在O(1):

检查列表是否为空,或者节点是否不在任何列表中。 获取任何其他节点之后或之前的节点(如果该节点是头部,则获取列表的第一个或最后一个元素)。 在任何其他节点之后或之前插入一个节点(如果该节点是头部,则插入到列表的开头或结尾)。 从列表中删除一个节点(您只需要指向节点本身的指针即可执行此操作)。 其他几个操作。

【讨论】:

【参考方案4】:

C 及其标准库不提供任何特定于列表的函数。

但有些库包含许多有用的 C 函数,支持其他编程语言中已知的数据类型:http://library.gnome.org/devel/glib/2.18/glib-data-types.html

【讨论】:

【参考方案5】:

如上所述,我尝试使用 MACROS 方法来创建列表操作函数。 创建 INSERT 操作例程很容易,但很难创建 Delete 和遍历操作。其后是列表结构和 INSERT 例程签名:

#define LIST_DEFINE(type) \
    struct list_node_##type \
     \
        type *data; \`
        struct list_node_##type *next; \
   ;

LIST_INSERT(&ListHead,&Data, DataType);

其中:ListHead - 链表头Data - 将为其创建新节点并将数据插入节点的数据 DataType - 是传递的数据的数据类型

仅供参考,我在函数中分配内存并复制新创建的节点中传递的所有数据,并将节点附加到链表中。

现在,当创建LIST_DELETE 例程时,将使用数据中的唯一标识符来标识需要删除的节点。该标识符也作为键在MACRO 例程中传递,将在MACRO 扩展中被替换。例程签名可以是:

LIST_DELETE(&ListHead, DataType, myvar->data->str, char*);

其中:ListHead - 链表头DataType - 是数据的数据类型myvar->data->str - 唯一键char* - 键类型

现在,当 key 展开时,不能像我们写的那样使用相同的 key 进行比较

if((keytype)ListHead->data->key == (keytype)key)

它扩展为

ListHead->data->myvar->data->str == myvar->data->str

这里没有变量像:ListHead->data->myvar->data->str

因此,这种方法无法编写删除例程,并且由于遍历和搜索例程也使用唯一键,因此它们也会面临同样的问题。

另外,关于如何确定唯一键的匹配逻辑,因为唯一键可以是任何东西。

【讨论】:

【参考方案6】:

根据我的教义,我来开发这个“通用”列表模块,可能是 linux 内核模块的简化版本,包含其他但未被发现的错误,并且使用 gcc 扩展... 欢迎任何cmets!

#ifndef _LISTE
#define _LISTE
#include <stdlib.h>
typedef struct liste_s 
  struct liste_s * suivant ;
 * liste ;


#define newl(t) ( (liste) malloc ( sizeof ( struct liste_s ) + sizeof ( t ) ) )
#define elt(l,t) ( * ( ( t * ) ( l + 1 ) ) )

#define liste_vide NULL
#define videp(l) ( l == liste_vide )
#define lvide() liste_vide
#define cons(e,l) \
  ( liste res = newl(typeof(e)) ;      \
     res->suivant = l ; \
     elt(res,typeof(e)) = e ;           \
    res ; ) 

#define hd(l,t) ( liste res = l ; if ( videp(res) ) exit ( EXIT_FAILURE ) ; elt(res,t) ; )
#define tl(l)   ( liste res = l ; if ( videp(res) ) exit ( EXIT_FAILURE ) ; res->suivant ;)


#endif

【讨论】:

【参考方案7】:

我一直在尝试不同的东西。这是另一个视角 登机问题

如果我们有以下结构:

typedef struct token 
    int id;
    char *name;
    struct token *next;
 Token;

我们需要创建一个返回链表尾部的函数,但该函数对于任何链表都应该是通用的,所以:

void* tail(void* list, void* (*f)(void *)) 
    void *head = list;

    while(f(head) != NULL) 
        head = f(head);
    

    return head;

现在有必要创建一个函数,负责在我们的自定义结构与尾部函数中的通用可用性之间建立桥梁。 这样,我们就有:

void* nextToken(void *a) 
    Token *t = (Token *) t;
    return (void *) (a->next);

最后我们可以简单地使用:

Token *listTokens;
(...)
Token *lastToken = tail(listTokens, nextToken);

【讨论】:

以上是关于C中的通用列表操作函数?的主要内容,如果未能解决你的问题,请参考以下文章

字符串,列表,元组,字典,集合,序列通用操作

列表通用操作

ex32 循环和列表

Numpy中的数组拼接、合并操作(concatenate, append, stack, hstack, vstack, r_, c_等)

python学习DAY3(列表)

python学习 - 列表操作