我正在用C语言重写我几年前学到的所有数据结构,以增加对数据结构和C语言的理解。我正在做的第一个是单链接列表,所以我正在为它们设计一个API。我还没有编写任何实现,但我希望得到一些关于我的函数原型的反馈,以确保我所做的事情是明智和合乎逻辑的。
首先是一些评论:
我希望很多人会为节点结构建议一个typedef,但这是个人决定,我不想输入结构。
我最不确定我对返回类型和值的选择。我有两个函数返回指向已删除节点的数据变量的指针。这些函数从列表中删除节点,保存指向数据的指针,并释放节点。这意味着用户需要释放返回的数据。这是不好的形式?我不确定如何以允许静态返回数据的方式来执行此操作,因为这需要提前知道数据的大小,这会削弱库的有用性。
我想让API支持所有可以通过单链表实现的有用的东西,其中一个是实现堆栈。在单链表上推送/弹出操作困扰我的一件事是,似乎有些人头脑中的概念是推/弹应该只发生在列表的 end 。如果您需要在尾部(结束)执行推/弹操作,则堆栈作为后进/先出机制(LIFO)不适用于单链接列表。这显然是因为使用尾部而不是列表的头部效率非常低,因为只能通过遍历列表来到达最后一个元素。下面的push / pop函数模仿堆栈的行为,但操作发生在列表的前面。这是一个糟糕的设计吗?
以下是包含元素的列表示例:
[SENTINEL] - GT; [0] - > [1] - > [2] - > [3] - > NULL
每个节点都是struct node类型。 struct node是一个带有数据和下一个字段的结构:
struct node {
void *data;
struct node *next;
}
此实现将使用sentinel节点来简化边界条件。每个列表都有一个标记节点,位于列表的前面。
空列表包含一个指向null的sentinel节点,如下所示: [SENTINEL] - GT; NULL
列表的结构为struct sllist。 struct sllist有一个sentinel字段,其中包含指向sentinel节点的指针:
struct sllist {
struct node *sentinel;
}
最后,这是一个操作列表及其相关的函数原型(带描述):
//create new list:
struct *sllist new_sllist();
//Returns a pointer to a new, empty sllist.
//destroy list:
void destroy_sllist(struct sllist *sllist);
//Frees the memory of the list struct and all associated nodes.
//insert after node:
int sllist_insert_after(struct sllist *sllist, struct *node, void *data);
//Adds a node after the passed node.
//If allocation fails, returns -1, otherwise returns 0.
//prepend node to the list:
int sllist_push(struct sllist *sllist, void *data);
//Adds a node to the front of the list. If allocation fails, returns -1, otherwise returns 0.
//Note that the front of the list is defined to be the first node after the sentinel node.
//extract after node:
void* sllist_extract_after(struct sllist *sllist, struct node *node);
//Removes a node from the linked list, save a pointer to the data, free the node
//(but do not the data itself), and return a pointer to the data so that it can be used.
//If the node doesn't exist in the list, returns NULL.
//extract first node:
void* sllist_pop(struct sllist *sllist);
//Same as extract after node, but restricted to the first node (first node after sentinel.)
//Analogous to sllist_push(), the name sllist_pop suggests usage as a stack.
//destroy after node:
void sllist_destroy_after(struct sllist *sllist, struct node *node);
//Removes from the list the node after the passed node and frees all associated memory.
如果有什么东西看起来不合时宜,很奇怪或设计不合理,请告诉我。
答案 0 :(得分:0)
链表通常是更高级数据结构的实现细节,即队列,列表,堆栈等......所以,我确保实现每个所需的所有操作。 push_back
,push_front
,pop_front
,pop_back
,front
,back
,当然还有随机访问权限。
对于有效负载,用户应负责分配和解除分配。毕竟,他们可能正在向堆栈上的内存传递一个指针,你当然不希望释放那个内存。另一种方法是在插入时制作有效载荷的副本(即memcpy),然后你可以确保你的内存在弹出时释放,并且你不会对用户造成任何意外行为:这是以牺牲但是数据的线性副本。
此外,您可能会发现将链接列表修改为以下内容很有帮助:
struct sllist {
struct node *head;
struct node *tail;
struct node *current;
unsigned size;
}