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中的通用列表操作函数?的主要内容,如果未能解决你的问题,请参考以下文章
Numpy中的数组拼接、合并操作(concatenate, append, stack, hstack, vstack, r_, c_等)