1、链表的引入
1.1、数组的缺陷
(1)数组有两个缺陷:一个数组中所有的元素类型必须一致;数组中的元素个数必须事先制定并且一旦制定之后不能更改
(2)数组缺陷的解决方法:第一个缺陷靠结构体去解决,结构体允许的元素的类型不相同。
(3)数组缺陷的解决方法:第二个缺陷制作链表,因为链表实际上就是一个元素个数可变的数组。
1.2、链表是怎样的
(1)链表就是用锁链连接起来的表。这里的表指的是一个一个的节点,节点中有一些内存可以用来存储数据;这里的锁链指的是链接各个表的方法,C语言中用来连接2个表的方法就是指针。
(2)链表是若干个节点组成的(链表的各个节点结构是完全类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。
1.3、链表的作用
(1)链表就是解决数组大小不能动态扩展的问题,所以其实链表就是当数组用。链表能完成的用数组也能完成,数组能完成的链表也可以,但是链表比数组灵活。
(2)链表就是用来存储数据的,链表的优势是灵活,数组的优势是使用简单
1.4、单链表的实现
1.4.1、单链表的节点构成
(1)链表是由节点构成的,节点中包含:有效数据和指针。
#include <stdio.h>
//构建一个节点
struct node
{
int data; //有效数据
struct node*pnext; //指向下一个节点的指针
};
(2)定义的struct node只是一个结构体,本身没有变量生成,也不占用内存。结构体定义相当于为链表定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。
1.4.2、堆内存的申请和使用
(1)链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
(2)使用堆内存来创建一个链表节点的步骤:
1、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);
2、清理申请到的堆内存;
3、把申请到的堆内存当作一个新节点;
4、填充你这个新节点的有效数据和指针区域。
#include <stdio.h>
//构建一个节点
struct node
{
int data; //有效数据
struct node*pnext; //指向下一个节点的指针
};
int main(void)
{
//创建一个链表节点
struct node *p=(struct node*)malloc(sizeof(struct node));
if(NULL==p)
{
printf("malloc error:\n");
return -1;
}
//清理申请到的堆内存
bzero(p, sizeof(struct node));
//填充节点
p->data=1;
p->pnext=NULL; //将来要指向下一个节点的首地址
//实际操作中将下一个节点malloc返回的指针赋值给这个即可
}
1.4.3、链表的头指针
(1)头指针并不是节点,而是一个普通指针,只占4字节。头指针的类型是struct node *类型的,所以它才能指向链表的节点。
(2)一个典型的链表的实现就是:头指针指向链表的第1个节点,然后第1个节点中的指针指向下一个节点,然后依次类推一直到最后一个节点。这样就构成了一个链。
1.4.4、实战:构建一个简单的单链表
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
//构建一个节点
struct node
{
int data; //有效数据
struct node*pnext; //指向下一个节点的指针
};
int main(void)
{
//定义指针
struct node *pHeader=NULL;
//创建一个链表节点
struct node *p=(struct node*)malloc(sizeof(struct node));
if(NULL==p)
{
printf("malloc error:\n");
return -1;
}
//清理申请到的堆内存
bzero(p, sizeof(struct node));
//填充节点
p->data=1;
p->pnext=NULL; //将来要指向下一个节点的首地址
//实际操作中将下一个节点malloc返回的指针赋值给这个即可
pHeader=p; //将本节点和它前面的头指针关联起来
//每创建一个新的节点,把这个新的节点和它前一个节点关联起来
//创建一个链表节点
struct node *p1=(struct node*)malloc(sizeof(struct node));
if(NULL==p1)
{
printf("malloc error:\n");
return -1;
}
//清理申请到的堆内存
bzero(p1, sizeof(struct node));
//填充节点
p1->data=2;
p1->pnext=NULL; //将来要指向下一个节点的首地址
//实际操作中将下一个节点malloc返回的指针赋值给这个即可
p->Next=p2; //将本节点和它前面的头指针关联起来
//每创建一个新的节点,把这个新的节点和它前一个节点关联起来
//创建一个链表节点
struct node *p2=(struct node*)malloc(sizeof(struct node));
if(NULL==p2)
{
printf("malloc error:\n");
return -1;
}
//清理申请到的堆内存
bzero(p2, sizeof(struct node));
//填充节点
p2->data=3;
p1->pnext=p2; //将来要指向下一个节点的首地址
//实际操作中将下一个节点malloc返回的指针赋值给这个即可
//至此创建了一个有1个头指针+3个完整节点的链表
//访问链表中的各个节点的有效数据,这个访问必须注意不能使用p、p1、p2,而只能使用pHeader。
//访问链表第1个节点的有效数据
printf("node1 data:%d.\n",pHeader->data);
printf("p->data:%d.\n",p->data); //pHeader->data等同于p->data
//访问链表第2个节点的有效数据
printf("node1 data:%d.\n",pHeader->pNext->data);
printf("p->data:%d.\n",p1->data); //pHeader->pNext->data等同于p1->data
//访问链表第3个节点的有效数据
printf("node1 data:%d.\n",pHeader->data);
printf("p->data:%d.\n",p2->data); //pHeader->pNext->data等同于p2->data
}