在前面的文章中,我们讨论了如何实现通用类型的链表,方法是用void *类型的指针,指向数据。那么还有其他的方法吗(不考虑内核链表)?

答案是肯定的。用零长数组也可以实现。

struct node_info
{
struct node_info *next;
struct node_info *prev;
char data[0];
};

这里的最后一个元素,是元素个数为0的数组。其不占用任何空间,甚至是一个指针的空间都不占!

注意:在标准C和C++中,长度为0的数组是被禁止使用的。不过在GNU C中,存在一个非常奇怪的用法,那就是长度为0的数组。

在一个结构体的最后 ,定义一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,这个长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量, 数组名这个符号本身代
表了一个不可修改的地址常量

先来看看整个代码的头文件吧

#pragma once
struct node_info
{
struct node_info *next;
struct node_info *prev;
char data[0];
}; struct student
{
char name[20];
unsigned char age; };//for test //有头双向循环链表
struct dlist_info
{
struct node_info *head;
void (*add_head)(struct dlist_info *info,
const void *data, size_t size);
void (*add_tail)(struct dlist_info *info,
const void *data, size_t size);
void (*del)(struct node_info *node);
struct node_info* (*find)(struct dlist_info *info,
int (*compare)(void *dest_data, void *key_data), void *key_data);
void (*for_each_safe)(struct dlist_info *info,void (*todo)(struct node_info *)); }; int dlist_init(struct dlist_info *info);
void dlist_destroy(struct dlist_info *info); #define node_init(node) \
do\
{\
(node)->next = (node);\
(node)->prev = (node);\
}while(0) #define dlist_is_empty(info) \
((info)->head->next == (info)->head)

接下来我们实现一些方法

1.头插

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "dlist.h" /* 有头循环双链表*/ static void dlist_add_head(struct dlist_info *info,
const void *my_data, size_t size)
{
assert(info != NULL && my_data != NULL); if (size == 0) {
return ;
} struct node_info *new_node = (struct node_info *)malloc(sizeof(struct node_info) + size); if (new_node == NULL) {
fprintf(stderr, "out of memory\n");
return ;
}
//数据域,内存拷贝
memmove(new_node->data, my_data, size); //指针域修改
new_node->next = info->head->next;
new_node->prev = info->head; info->head->next = new_node;
new_node->next->prev = new_node;
}

size 表示数据域占用了多少个字节。memmove(new_node->data, my_data, size); 这句话把用户的数据拷贝到了结构体的最后。关于指针域的修改,是不是有点绕呢?没有关系,画图就明白了。

2.尾插

static void dlist_add_tail(struct dlist_info *info,
const void *my_data, size_t size)
{ assert(info != NULL && my_data != NULL); if (size == 0) {
return ;
} struct node_info *new_node = (struct node_info *)malloc(sizeof(struct node_info) + size); if (new_node == NULL) {
fprintf(stderr, "out of memory\n");
return ;
}
//数据域,内存拷贝
memmove(new_node->data, my_data, size); //指针域修改
new_node->next = info->head;
new_node->prev = info->head->prev; info->head->prev->next = new_node;
info->head->prev = new_node; }

3.删除

static void dlist_del(struct node_info *node)
{
assert(node != NULL); node->next->prev = node->prev;
node->prev->next = node->next; node_init(node);
free(node);
}

因为申请空间的时候是带着size一起申请的,所以这里的释放就全部释放了,不存在内存泄漏。

4.查找

static struct node_info *dlist_find(struct dlist_info *info,
int (*key)(void *dest_data, void *key_data), void *key_data)
{
assert(info != NULL && key != NULL); if (dlist_is_empty(info)) {
fprintf(stderr, "dlist is empty\n");
return NULL;
} struct node_info *cur = NULL; for (cur = info->head->next; cur != info->head;
cur = cur->next) {
if (key(cur->data, key_data) != 0) {
return cur;
}
}
return NULL;
}

回调函数需要用户自己实现,不用多说。

5.安全遍历

static void dlist_for_each_safe(struct dlist_info *info,
void (*todo)(struct node_info *))
{
assert(info != NULL && todo != NULL); struct node_info *cur = NULL;
struct node_info *Next = NULL;
for (cur = info->head->next; cur != info->head;
cur = Next) {
Next = cur->next;
todo(cur);
}
}

6.构造和析构

int dlist_init(struct dlist_info *info)
{
info->head = (struct node_info *)malloc(sizeof(struct node_info)); if (info->head == NULL) {
fprintf(stderr, "Error:Out of memory\n");
return -1;
} /*头节点空间的初始化*/
node_init(info->head); /*函数指针的挂接*/
info->add_head = dlist_add_head;
info->add_tail = dlist_add_tail;
info->del = dlist_del;
info->find = dlist_find;
info->for_each_safe = dlist_for_each_safe;
return 0;
} void dlist_destroy(struct dlist_info *info)
{
// 依次删除,直到为空
while (!dlist_is_empty(info)) {
dlist_del(info->head->next);
} free(info->head);
}

接下来是单元测试。

测试一下头插和尾插吧。

#include "uni_test.h"

#include "dlist.h"

#include <stdio.h>

void setup (void)
{ // will excute in every case
} void teardown (void)
{ } void print_student(struct node_info *node)
{
struct student *p = (struct student *)(node->data);
printf("Name: %15s Age:%d\n",p->name,p->age); } int compare_student(void *dest,void *src)
{
struct student *p1 = dest;
struct student *p2 = src;
if(strcmp(p1->name,p2->name)==0)
return 1;
else
return 0;
} START_TEST(my_dlist_1)
{
struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\
{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};
struct dlist_info list;
dlist_init(&list);
int i = 0;
for(;i<sizeof(students)/sizeof(students[0]);++i)
list.add_head(&list,students+i,sizeof(students[0]));
list.for_each_safe(&list,print_student);
printf("===========\n");
dlist_destroy(&list); }
END_TEST START_TEST(my_dlist_2)
{
struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\
{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};
struct dlist_info list;
dlist_init(&list);
int i = 0;
for(;i<sizeof(students)/sizeof(students[0]);++i)
list.add_tail(&list,students+i,sizeof(students[0]));
list.for_each_safe(&list,print_student);
printf("===========\n");
dlist_destroy(&list); }
END_TEST

运行结果如图

Running suite(s): two_way_list_with_head

Name:     WangGuozhen  Age:48

Name:        LiuDehua  Age:53

Name:    ZhangGuorong  Age:47

Name:       LiuXuewei  Age:28

Name:          ChenYu  Age:27

Name:       SunYazhou  Age:21

Name:         LiuMing  Age:19

Name:        WangDong  Age:18

===========

Name:        WangDong  Age:18

Name:         LiuMing  Age:19

Name:       SunYazhou  Age:21

Name:          ChenYu  Age:27

Name:       LiuXuewei  Age:28

Name:    ZhangGuorong  Age:47

Name:        LiuDehua  Age:53

Name:     WangGuozhen  Age:48

===========


测试一下遍历,在遍历的过程中,我们把节点给删除了。这可以体现出安全遍历的好处。

START_TEST(my_dlist_3)//遍历删除
{
struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\
{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};
struct dlist_info list;
dlist_init(&list);
int i = 0;
for(;i<sizeof(students)/sizeof(students[0]);++i)
list.add_tail(&list,students+i,sizeof(students[0]));
list.for_each_safe(&list,list.del); list.for_each_safe(&list,print_student);
printf("===========\n");
dlist_destroy(&list); }
END_TEST

运行结果是:

===========


果然没有节点了。

查找并删除。

START_TEST(my_dlist_4)//查找并删除
{
struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\
{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};
struct dlist_info list;
dlist_init(&list);
int i = 0;
for(;i<sizeof(students)/sizeof(students[0]);++i)
list.add_tail(&list,students+i,sizeof(students[0])); list.del(list.find(&list,compare_student,"ChenYu")); list.for_each_safe(&list,print_student);
printf("===========\n"); dlist_destroy(&list); }
END_TEST

Name:        WangDong  Age:18

Name:         LiuMing  Age:19

Name:       SunYazhou  Age:21

Name:       LiuXuewei  Age:28

Name:    ZhangGuorong  Age:47

Name:        LiuDehua  Age:53

Name:     WangGuozhen  Age:48

===========


(完)

C语言实现通用链表初步(四)----双向链表的更多相关文章

  1. C语言实现通用链表初步(一)

    注意:本文讨论的是无头单向非循环链表. 假设不采用Linux内核链表的思路,怎样用C语言实现通用链表呢? 一种常用的做法是: typedef int element_t; struct node_in ...

  2. C语言实现通用链表初步(三)----单元测试

    前两节,我们已经完成了链表的一些操作,快来测试一下吧. 这里使用的单元测试工具名字叫"check". START_TEST(my_slist_1) { struct student ...

  3. C语言实现通用链表初步(二)

    接着上次的内容,我们继续! 还是无头单向非循环链表.假如要删除某个节点,如何实现? //删除成功返回0,失败返回-1 int slist_del(struct node_info *node, str ...

  4. (C语言版)链表(四)——实现双向循环链表创建、插入、删除、释放内存等简单操作

    双向循环链表是基于双向链表的基础上实现的,和双向链表的操作差不多,唯一的区别就是它是个循环的链表,通过每个节点的两个指针把它们扣在一起组成一个环状.所以呢,每个节点都有前驱节点和后继节点(包括头节点和 ...

  5. C语言数据结构-创建链表的四种方法

    结点类型: typedef int datatype; typedef struct NODE{ datatype data; struct NODE *next; }Node,*LinkList; ...

  6. 数组、单链表和双链表介绍 以及 双向链表的C/C++/Java实现

    概要 线性表是一种线性结构,它是具有相同类型的n(n≥0)个数据元素组成的有限序列.本章先介绍线性表的几个基本组成部分:数组.单向链表.双向链表:随后给出双向链表的C.C++和Java三种语言的实现. ...

  7. 拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)

    在实际的工作中,我们可能会经常使用链表结构来存储数据,特别是嵌入式开发,经常会使用linux内核最经典的双向链表 list_head.本篇文章详细介绍了Linux内核的通用链表是如何实现的,对于经常使 ...

  8. C语言实现单链表-03版

    在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4, ...

  9. C语言实现单链表-02版

    我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1 ...

随机推荐

  1. 点石成金:访客至上的网页设计秘笈(原书第2版) 中文PDF版

    可用性设计是Web设计中最重要也是难度最大的一项任务.本书作者根据多年从业的经验,剖析用户的心理,在用户使用的模式.为扫描进行设计.导航设计.主页布局.可用性测试等方面提出了许多独特的观点,并给出了大 ...

  2. 答C++复杂多余者之惑

    C和C++有太多的历史包袱而其它语言并没有这些,从另一个角度下来讲C和C++其实就是现代语言的活字典,也是现代软件设计的基础,当然有更古老的语言,但像C和C++影响力这么大的却无其它语言可以出其左右. ...

  3. http respose status code (记)

    1xx - 信息提示这些状态代码表示临时的响应.客户端在收到常规响应之前,应准备接收一个或多个 1xx 响应. · 100 - Continue 初始的请求已经接受,客户应当继续发送请求的其余部分.( ...

  4. java 集合(转载)

    一.集合与数组 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用. ...

  5. sonar Failed to execute goal org.sonarsource.scanner.maven:sonar-maven-plugin:3.4.0.905:sonar

    背景: 今天在项目根目录执行maven sonar:sonar ,报错信息如下,然后就各种的搜,折腾了多半天天也没找出解决办法,最后打算放弃时,看到一遍文章说是mysql  max_allowed_p ...

  6. CF70D(动态凸包)

    CF70D(动态凸包) 给出q(<=1e5)个询问,每次在加上一个点,维护凸包,或者询问某个点是否在凸包内(在边上也算). 听说可以用cdq做--但是并不会.我等蒟蒻只会用平衡树做. 首先,假设 ...

  7. (一)ByteDance编程题

    题目: 公司的程序员不够用了,决定把产品经理都转变为程序员以解决开发时间长的问题. 在给定的矩形网格中,每个单元格可以有以下三个值之一: 值0代表空单元格 值1代表产品经理 值2代表程序员 每分钟,任 ...

  8. tomcat启动优化

    tomcat的最佳实践运行模式 Tomcat Connector三种运行模式(BIO, NIO, APR)的比较和优化. org.apache.coyote.http11.Http11Protocol ...

  9. Idea如何设置代码超出长度限制时自动换行

    在[File]-->[Settings]-->[Code Sytle]中勾选[Wrap on typing]选项

  10. hive 存储格式及压缩

    -- 设置参数 set hivevar:target_db_name=db_dw; use ${hivevar:target_db_name}; -- 创建textfile表 create table ...