C语言链表

前言

  1. 本博文代码基于VC++6.0开发;
  2. 本博文只是对链表的一个简单介绍;

什么是链表

字面理解就是像链子一样的表格,表示表格环环相扣,而表格是用来存储数据的,所以链表就是一种动态进行存储分配的数据结构;(功能/优点方面)在学数组二维数组的时候都知道,二维数组的第二下标位代表全部一维数组中长度最大的一个;这里就出现一个问题,数组长度上的参差不齐导致内存的浪费;然而,链表更好可以解决这个问题,对链表来说,存多少数据,就相应的开辟多少的内存;

链表的结构,概念,特点,分类;

结构如图:
在这里插入图片描述
透过这张图片说明几个概念

  1. 头指针: 是指针变量;位于整个链表的最前端,指向第一个“数据块”的起始地址,是对本链表访问的必要条件,也就是说要操作这个链表,首先要知道知道头指针;
  2. 结点: 如下图,从每个结点上可以看出来,一个结点至少包括两个内容:数据变量和指向下一个结点的指针变量;要想让两个变量变成一个整体,显然每个结构体都是一个结构体;
    在这里插入图片描述
  3. 头结点:头指针所指向的结点;(变量A所在结构体)
  4. 尾结点:整个链表的倒数第二个结点,也及时表尾的前一个节点;(变量C所在结构体)
  5. 表尾:链表最后一个结点,其中指向下一个结点的指针指向NULL,即不再指向;(变量D所在结构体)

特点:

  1. 链表中各结点在内存中的地址可以使不连续的;
  2. 如果不提供头指针,整个链表将无法访问;并且头指针也必须是与结点相同数据类型的结构体指针;
  3. 基于链表的这种特殊结构开看,对于它的访问只能用指针;
  4. 链表的思想有些类似于递归函数,都有自己包含并调用自己的含义;

分类
静态链表:结点都是在程序中定义的,不是临时开辟的,且用完后并没有释放;
动态链表:在程序执行过程中,从无到有地建立起一个链表,逐个开辟结点的同时输入各结点的值并建立各结点之间的关系;

静态链表建立

问题:将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即可进行连接结点;

未完待续……

  • 15
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值