在本节中,我们将学习并实现一个通用的单向链表。先来看一下单向链表在内存中的存储形式:
链表中有3个属性,id为数据节点的唯一标识,在构建时按这个id来排序,也就是说我们要构建一个按id升序组成的单向链表。来看一下链表节点的数据结构:
//链表节点数据结构
typedef struct linknode
{
//唯一标识,按大小升序
int id;
//数据通用指针
void *node;
//下一节点指针
struct linknode *next;
} linknode;
另外还要定义链表数据结构,链表头节点和其它个重要的内容都在这个结构中:
//链表数据结构
typedef struct linklist
{
//链表头节点
linknode *header;
//取得数据唯一标识函数指针,回调函数
int (*get_id)(void *);
//显示数据的函数指针,回调函数
void (*display)(void *);
//销毁数据函数指针,回调函数
void (*destructor)(void *);
} linklist;
接下来,我们来实现构建和销毁链表的两个函数:
//构建链表函数
linklist* linklist_init(int (*get_id)(void *), void (*display)(void *), void (*destructor)(void *))
{
//申请链表内存
linklist *l = malloc(sizeof(linklist));
//头节点
l->header = NULL;
//取得数据唯一标识函数指针,回调函数
l->get_id = get_id;
//显示数据的函数指针,回调函数
l->display = display;
//销毁数据函数指针,回调函数
l->destructor = destructor;
return l;
}
//销毁链表函数
void linklist_des(linklist *l)
{
//取得头节点
linknode *p = l->header;
//循环销毁所有节点数据
while (p != NULL)
{
//取得下一个节点
linknode *pNext = p->next;
//销毁节点数据
l->destructor(p->node);
//释放节点内存
free(p);
//下一个节点
p = pNext;
}
//释放链表内存
free(l);
}
然后再来看一下链表的“插入”操作。为链表插入一个新的节点有以下几个步骤:
插入节点的函数如下:
//插入新节点
void linklist_insert(linklist *l, void *node)
{
//取得id
int id = l->get_id(node);
//申请内存
linknode *newNode = malloc(sizeof(linknode));
newNode->id = id;
newNode->node = node;
newNode->next = NULL;
//二级指针
linknode **p = &l->header;
//找到第一个大于key的节点
while ((*p) != NULL && (*p)->id < id)
{
p = &(*p)->next;
}
//插入新节点
newNode->next = *p;
*p = newNode;
}
删除节点操作与插入相反,执行下面步骤:
再来看一下实现函数:
//删除节点
void linklist_del(linklist *l, int id)
{
//如果头节点为空
if (l->header == NULL)
{
return;
}
//二级指针
linknode **p = &l->header;
while ((*p) != NULL && (*p)->id != id)
{
p = &(*p)->next;
}
//等待释放节点
linknode *del = *p;
if (del == NULL)
{
return;
}
//释放内存
free(del);
*p = (*p)->next;
}
还有两个比较简单的“查找”函数和“显示”函数:
//查找
void* linklist_find(linklist *l, int id)
{
//取得头节点
linknode *p = l->header;
//遍历链表
while (p != NULL)
{
//如果id相等
if (p->id == id)
{
//返回数据节点
return p->node;
}
//下一个节点
p = p->next;
}
return NULL;
}
//显示链表内容
void linklist_display(linklist *l)
{
//取得头节点
linknode *p = l->header;
//遍历链表
while (p != NULL)
{
//显示数据内容
l->display(p->node);
//下一个节点
p = p->next;
}
}
最后通过一个例子来测试一下:
//学生数据结构
typedef struct
{
//学号
int no;
//姓名
char *name;
//性别
char sex;
//年龄
int age;
} student;
//取得学生学号,唯一id
int get_id_student(void *p)
{
student *h = (student *) p;
return h->no;
}
//显示学生信息
void display_student(void *p)
{
student *h = (student *) p;
printf("%d\t%s\t%c\t%d\n", h->no, h->name, h->sex, h->age);
}
//这是专门用于释放student类型元素的回调函数
void destructor_student(void *p)
{
student *h = (student *) p;
//释放名字属性
free(h->name);
//释放student
free(h);
}
主函数实现如下:
#define NAME_LEN (20)
int main(int argc, char **args)
{
linklist *link = linklist_init(&get_id_student, &display_student, &destructor_student);
student *h1 = malloc(sizeof(student));
h1->no = 20150501;
h1->name = malloc(NAME_LEN);
memcpy(h1->name, "Tomsen", NAME_LEN);
h1->sex = 'F';
h1->age = 22;
linklist_insert(link, h1);
student *h2 = malloc(sizeof(student));
h2->no = 20150502;
h2->name = malloc(NAME_LEN);
memcpy(h2->name, "Jerry", NAME_LEN);
h2->sex = 'M';
h2->age = 21;
linklist_insert(link, h2);
student *h3 = malloc(sizeof(student));
h3->no = 20150504;
h3->name = malloc(NAME_LEN);
memcpy(h3->name, "Lily", NAME_LEN);
h3->sex = 'F';
h3->age = 23;
linklist_insert(link, h3);
student *h4 = malloc(sizeof(student));
h4->no = 20150503;
h4->name = malloc(NAME_LEN);
memcpy(h4->name, "Wellean", NAME_LEN);
h4->sex = 'M';
h4->age = 22;
linklist_insert(link, h4);
printf("\nDisplayer all: \n");
linklist_display(link);
printf("\nFind 20150502:\n");
student *h = linklist_find(link, 20150502);
display_student(h);
printf("\nDelete 20150512:\n");
linklist_del(link, 20150502);
printf("\nDisplay all:\n");
linklist_display(link);
linklist_des(link);
return 0;
}
运行结果:
Displayer all: 20150501 Tomsen F 22 20150502 Jerry M 21 20150503 Wellean M 22 20150504 Lily F 23 Find 20150502: 20150502 Jerry M 21 Delete 20150512: Display all: 20150501 Tomsen F 22 20150503 Wellean M 22 20150504 Lily F 23
Copyright © 2015-2023 问渠网 辽ICP备15013245号