前言
- 本博文代码基于VC++6.0开发;
- 本博文只是对链表的一个简单介绍;
什么是链表
字面理解就是像链子一样的表格,表示表格环环相扣,而表格是用来存储数据的,所以链表就是一种动态进行存储分配的数据结构;(功能/优点方面)在学数组二维数组的时候都知道,二维数组的第二下标位代表全部一维数组中长度最大的一个;这里就出现一个问题,数组长度上的参差不齐导致内存的浪费;然而,链表更好可以解决这个问题,对链表来说,存多少数据,就相应的开辟多少的内存;
链表的结构,概念,特点,分类;
结构如图:
透过这张图片说明几个概念:
- 头指针: 是指针变量;位于整个链表的最前端,指向第一个“数据块”的起始地址,是对本链表访问的必要条件,也就是说要操作这个链表,首先要知道知道头指针;
- 结点: 如下图,从每个结点上可以看出来,一个结点至少包括两个内容:数据变量和指向下一个结点的指针变量;要想让两个变量变成一个整体,显然每个结构体都是一个结构体;
- 头结点:头指针所指向的结点;(变量A所在结构体)
- 尾结点:整个链表的倒数第二个结点,也及时表尾的前一个节点;(变量C所在结构体)
- 表尾:链表最后一个结点,其中指向下一个结点的指针指向NULL,即不再指向;(变量D所在结构体)
特点:
- 链表中各结点在内存中的地址可以使不连续的;
- 如果不提供头指针,整个链表将无法访问;并且头指针也必须是与结点相同数据类型的结构体指针;
- 基于链表的这种特殊结构开看,对于它的访问只能用指针;
- 链表的思想有些类似于递归函数,都有自己包含并调用自己的含义;
分类:
静态链表:结点都是在程序中定义的,不是临时开辟的,且用完后并没有释放;
动态链表:在程序执行过程中,从无到有地建立起一个链表,逐个开辟结点的同时输入各结点的值并建立各结点之间的关系;
静态链表建立
问题:将3个学生的学号和成绩存储在链表中,并完成输出;
#include <stdio.h>
struct Student
{
int num;
float score;
struct Student *next;
};
//结构体定义时,并不开辟内存空间,只有定义结构体变量的时候才会开辟内存空间;
void main()
{
struct Student *heap_Pointer,*p,a,b,c;
int i=0;
a.num = 1001;a.score = 32.0;
b.num = 1002;b.score = 56.0;
c.num = 1003;c.score = 89.0;
heap_Pointer = &a;
a.next = &b;
b.next = &c;
c.next = NULL;
//从14到21行代码是静态链表的特点:结点以及结点内数据以及结点见的关系都是在程序中建立;
p = heap_Pointer;
do
{
printf("学号是:%d\t成绩是:%2.1f\n",p->num,p->score);
p = p->next;
}while(p != NULL);
}
运行结果:
从静态链表和这个实例不难看出来,静态链表显得并不是那么灵活,似乎很多工作都是由程序猿们去完成,而且在真正的大型程序中,往往会用到动态链表比较多;
动态链表的建立:
在以往的学习中,对于批量常量数据的存储,都是靠数组来完成的,但是实际大型的程序中,数组的局限性很大,这是因为数组在一开始定义的时候就确定了数组的长度是多少,那么就存在一个问题,后续程序里存储数据的时候数组大了浪费内存,数组小了数据没法存,很难受;然而动态链表就解决了这个问题,它实现了随时对于内存的开辟和释放,实现了内存的合理利用;
注意:动态列表每次增加一个结点,需要开辟一段大小等同于结点代表的结构体大小的内存,这里就用到了动态内存分配函数(malloc/new);
举例说明:
问题:将若干同学的学号和成绩从键盘输入,存储在链表中,并输出;
#include <stdio.h>
#include <malloc.h>
struct Student
{
int num;
float score;
struct Student *next;
};
void main()
{
struct Student *p1,*p2,*head,*temp;
temp = head = p1 = p2 = (struct Student *)malloc(sizeof(struct Student)); //第一步
printf("请分别输入学号和成绩:\n");
scanf("%d%f",&(p1->num),&(p1->score)); //第一结点赋值;
while(p1->num != 0) //在循环内开辟若干内存和相对应的若干结点;
{
p1 = (struct Student *)malloc(sizeof(struct Student)); //第二步
p2->next = p1; //第三步
p2 = p1; //第四步
scanf("%d%f",&(p1->num),&(p1->score)); //第二节点赋值
}
p1->next = NULL; //第五步
//遍历动态链表
printf("请输出各个节点数据:\n");
do
{
printf("学号是:%d\t成绩是:%f\n",temp->num,temp->score);
temp = temp->next;
}while(temp != NULL);
}
运行结果:
下面通过图片将程序中的4个步骤重点说明:(建立新结点并建立连接的过程)
第一步:开辟结点1,定义结构体指针变量p1,p2,heap和temp,并指向新开辟的内存空间入口地址;
第二步:开辟结点2,使p1指向其入口地址;
第三步:将结点2的入口地址赋值给结点1的尾结构体指针,使得两结点建立联系;
第四步:令p2指向结点2入口地址;
第五步:这一步很关键,这是此链表的表尾;
总结:从结点数量变化可以看出来,从一个结点,到两个结点过程中,p1和p2扮演着各结点间关系的连接作用,head是不会变化的(它不会变化不是不能变,而是不能随意变),之前说过它是访问整个动态链表的关键;temp的定义时为了遍历这个动态链表;所以在生成更多的节点时,只需使用p1和p2即可进行连接结点;
未完待续……