通用单链表
我们在学习链表的时候,大部分老师和大部分书籍都只会教我们常规的链表,此处常规是指链表的数据部分只适用单一的数据类型,对于怎么扩展到适用所有的数据类型,大多都是让学生自己去摸索,甚至根本就没有提及,可能中国的老师和作者都觉得每个学习数据结构的人都跟linux的创始人一样聪明吧!
我在学习和写代码的过程中,觉得链表两个比较重要点大多数老师和书籍都语焉不详。
1. 链表节点的结构
2. 如何动态的添加节点
下面我会尽自己所能用最通俗的语言和最简单的代码把这两个问题解释清楚,如有写的不好的地方,欢迎随时指出,谢谢!
就我所知道的,一个通用链表,可以用两种结构来实现
# 第一种:节点的数据部分用一个void类型的指针来表示
typedef struct _node {
struct _node *next; /* 指针域 */
void *data; /* 数据域 */
} node_t;
/* 链表的头结点 */
typedef struct _head {
int length; /* 链表长度 */
node_t head;
}
# 第二种:节点只包含一个指针域
typedef struct _node {
struct _node *next; /* 指针域 */
} node_t;
节点结构定义好了,最关键是如何动态添加节点,对于第一种结构:
先定义一个你需要的数据元素的数据类型,为了例子能具有通用性,这里我定义一个结构体,结构体有两个成员变量,一个int型,一个字符数组型。
typedef struct _student {
int id;
char name[64];
} student_t;
int main(void)
{
student_t stu;
stu.id = 1;
strcpy(stu.name, "James");
/* 一. 创建一个单链表 此处只是创建了一个链表的头结点并初始化 */
head_t *list = list_create();
/*
二. 插入节点至该链表
假设我现在要把main函数里面的局部变量stu动态插入到链表节点中
分析:
要把一个局部变量动态插入到一个链表中,需要在堆内存中为该节点动态申请一块内存
注意是动态插入,网上好多文章都是先在main函数的栈空间中定义一个变量,然后把这块栈空间插入链表中,想象一下,假如你需要添加100个节点,你需要在main函数
中定义100个这样的变量,是不是要骂娘了?所以这里需要在堆中创建内存,只需要在main函数中定义一个临时变量即可
再思考一个问题,在插入的时候都需要哪些参数?
1:插入到哪个链表 2:插入到什么位置 3:插入的数据
现在你能在网上找到的链表插入的函数,百分之八九十都是这三个参数,但是如果要使用malloc在堆中分配内存,需要分配多大的内存呢?我们在写
链表的插入函数时并不知道,所以需要从调用者那里传递过来,也就是我们的第四个参数,插入数据的大小, 如下:
函数原型:list_insert(head_t *list, int pos, void *data, int datasize);
*/
list_insert(list, 0, (void *)&stu, sizeof(student_t));
return 0;
}
接下来也就是最重要,插入函数该如何实现:
/* 创建链表 */
head_t *list_create()
{
head_t *list = (head_t *)malloc(sizeof(head_t));
if ( list == NULL ) {
printf("Error: list create failed!\n");
return NULL;
}
list->length = 0;
list->head.next = NULL; /* 初始化时只有一个头结点,所以这里为NULL */
}
/* 动态插入节点 */
void list_insert(head_t *list, int pos, void *data, int datasize)
{
/* 判断参数合法性 */
if ( list == NULL || data == NULL ) {
printf("invalid list or data point to NULL!\n");
return;
}
if ( pos < 0 || pos > list->length ) {
printf("position error!\n");
return;
}
int i;
node_t *p_cur = &list->head; /* 定义一个辅助指针 */
/* 1. 给新节点分配一块内存空间 内存布局如下图 */
node_t *tmp = (node_t *)malloc(sizeof(node_t)+datasize);
tmp->data = (void *)(tmp + 1);
/* 2. 将参数data传递进来的数据复制给新创建节点的void *data成员, 大小为datasize */
memcpy(tmp->data, data, datasize);
/* 3. 插入操作 */
for ( i=0; i<pos; i++ ) {
p_cur = p_cur->next;
}
tmp->next = p_cur->next;
p_cur->next = tmp;
list->length++;
}
至于第二种结构的实现其实差不多,有兴趣的可以自己试试。自己实现和看别人的思路再去实现其实区别很大,因为中间会有一个思考的过程,这个过程的作用可能短时间体现不出来,但只要你长期坚持去思考,最后肯定会收获不一样的东西的。
此链表完整的函数实现和测试程序请移步github: