目录

链表是什么:

节点与结构体:

节点与链:

头结点与头指针:

多节点的链表:


链表是什么:

链表是链式存储结构的典例,相邻两个节点间是靠相连的。与链式存储结构相对应的是顺序存储结构,我们所熟知的数组便是顺序存储结构。顺序存储结构的一个明显特点便是需要提前在内存中分配好一定大小的空间,而空间是否充足在数据完全被导入前是不知道的,最终的结果可能是未充分利用已分配的内存空间而造成浪费或者是分配的内存空间不足而导致数据的溢出。这都是我们不愿看到的,于是链式存储结构出现了。

在出现新的一个节点时(这个节点内的部分分为数据域指针域数据域用来存放各种实际的数据指针域用来存放上一个或下一个节点的地址),我们只需要将这个新的节点纳入到这条数据链当中来。而具体用的方法,便是让每个节点有上一个或下一个节点的地址,而这个就是开头说到的

可以看到,链表的实现依赖于节点的建立,而节点的建立又依赖于结构体的应用


节点与结构体:

前面讲到,链表中每个节点由数据域和指针域组成,这也就是说,对应的结构体包含了某些数据类型的变量以及与该结构体同类型的指针变量

举一个小例子:

struct stu
{
int iScore;
char cName[10];
struct stu* pNext;
};

这个结构体包含了一个整型变量、一串字符以及一个与该结构体同类型的指针变量。然后我们继续在 main() 函数中定义两个变量 stu1 以及 stu2 ,为它们赋值:

int main()
{struct stu stu1;stu1.iScore = 89;strcpy(stu1.cName,"log");stu1.pNext = NULL;printf("%d %s", stu1.iScore, stu1.cName);struct stu stu2;stu2.iScore = 99;strcpy(stu2.cName, "rog");stu2.pNext = NULL;printf("%d %s", stu2.iScore, stu2.cName);return 0;
}

我们运行一下,得到结果:


节点与链:

如何让这两个变量建立链接呢?我们可以让 stu1 这个结点的指针域储存 stu2 这个结点的首地址,即添加语句

stu1.pNext = &stu2;

使得整个程序变为:

#include<stdio.h>
#include<string.h>
struct stu
{int iScore;char cName[10];struct stu* pNext;
};
int main()
{struct stu stu1;stu1.iScore = 89;strcpy(stu1.cName,"log");stu1.pNext = NULL;printf("%d %s\n", stu1.iScore, stu1.cName);struct stu stu2;stu2.iScore = 99;strcpy(stu2.cName, "rog");stu2.pNext = NULL;stu1.pNext = &stu2;printf("%d %s", stu1.pNext->iScore, stu1.pNext->cName);return 0;
}

而我们这个程序原本的目的就是将 stu1 和 stu2 两个结点数据域内储存的值打印出来,那在上述操作的基础上已经建立起这两个结点间的链,我们是不是可以直接定义一个 struct stu 类型的辅助变量并通过它和首元结点 stu1 就能够访问 节点 stu2 呢?例如:

#include<stdio.h>
#include<string.h>
struct stu
{int iScore;char cName[10];struct stu* pNext;
};
int main()
{struct stu stu_temp;struct stu stu1;stu1.iScore = 89;strcpy(stu1.cName, "log");stu1.pNext = NULL;struct stu stu2;stu2.iScore = 99;strcpy(stu2.cName, "rog");stu2.pNext = NULL;stu1.pNext = &stu2;stu_temp.pNext = &stu1;while (stu_temp.pNext != NULL){printf("%d %s\n", stu_temp.pNext->iScore, stu_temp.pNext->cName);stu_temp.pNext = stu_temp.pNext->pNext;}return 0;
}

在这段代码中,我们定义了一个临时的变量 stu_temp ,并将 stu1 的地址添加到了 stu_temp 的指针域,通过一个 while循环 ,我们可以按序打印出这条链上所有非空结点的数据。

另外,我们称 stu1 为首元结点,当然也可以叫队头结点或是其它的;而 stu_temp 被称为头结点,需要注意的是,头结点的数据域是空域,而它的指针域储存着首元结点的地址

图示的逻辑关系为:

其中,头结点 stu_temp 的数据域为空,用希腊字母  表示。


头结点与头指针:

头结点相对应的概念为头指针,头结点是可有可无的,若设置头结点,那么头指针的值为头结点的地址;若不设置头结点,那么头指针的值为队头结点的地址。逻辑关系是这样的:

一般说来,头指针有标识作用,可以用头指针表示链表的名字。这么说的原因是在编写程序时对两个或两个以上的链表进行数据的读入、修改或是连接两个装有同种类型元素的链表等操作时可以很简单地区分出不同的链表。并且,头指针是链表的必要元素,无论链表是否为空,头指针都不为空

头结点则是为了方便起见,放在队头结点之前,其数据域一般无意义。说到头结点带来的方便,主要体现在有了头结点后,如果我们要在队头结点前插入一个新的结点或是删除队头结点使其下一个结点成为新的队头结点时都是很有帮助的。

上面的代码添加头指针后变为:

#include<stdio.h>
#include<string.h>
struct stu
{int iScore;char cName[10];struct stu* pNext;
};
int main()
{struct stu stu_temp;struct stu* pHead=NULL;struct stu stu1;stu1.iScore = 89;strcpy(stu1.cName, "log");stu1.pNext = NULL;struct stu stu2;stu2.iScore = 99;strcpy(stu2.cName, "rog");stu2.pNext = NULL;stu1.pNext = &stu2;stu_temp.pNext = &stu1;stu_temp.iScore = -1;pHead = &stu_temp;while (pHead != NULL){if (pHead->iScore == -1){pHead = pHead->pNext;continue;}printf("%d %s\n", pHead->iScore, pHead->cName);pHead = pHead->pNext;}return 0;
}

pHead 是我们定义的头指针,在为 stu1 和 stu2 两个结点赋初值后,我们将 stu2 的地址存放到 stu1 的指针域,将 stu1 的地址存放到 stu_temp 的指针域,并将 stu_temp 的数据域中的 iScore 的值设置为 -1 用以表明其为头结点。


多节点的链表:

在上文中,我们看到在连接较少的结点时,直接采取先定义各个结点,然后将它们连接起来的方式是可行的。可是,当我们要连接起来的节点的数量不在一个具体的范围时,需要采取的工作量是不确定的,也许你会说可以采取分配一块固定大小的内存来存储这些数据,这就回到了我们在文章开头提到的顺序存储结构,违背了建造链式存储结构的目标。

那么我们完全可以只定义一个代表该节点的变量,然后通过给这个节点不断赋新值,然后将这个节点纳入到已有的链当中来。按照前面的思路,是建立起已有的 n 个结点之间的链接,而新的思路则是建立一个新结点就将它纳入到前 n 个结点构成的链当中来

这也就是说,我们需要重复使用同一个变量来开辟新的结点,即以一种不定义新的变量的方式来不断开辟空间的。这话有点拗口,我们以上面的代码为例,我们开辟 stu1 和 stu2 这两个结点是通过定义这两个变量的方式,如果我们要定义 stu3 、stu4 ······ stu n 这一系列结点,就要有 n 条定义变量的语句,显得重复啰嗦。而定义变量的目的就是为这 n 个结点开辟内存空间,如果有另外一种方式可以使我们不用这么啰嗦的方式就为各个结点开辟内存空间,肯定是采用其它的。

我们可以利用 <stdlib.h> 中的函数 malloc() 或 calloc() 来开辟一定大小的内存空间,其中常用 malloc() 函数。

malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。一般它需和free函数配对使用。free函数能释放某个动态分配的地址,表明不再使用这块动态分配的内存了,实现把之前动态申请的内存返还给系统。                               ————Baidu

malloc() 函数的原型为:

void *malloc(unsigned int size);

返回值是一个指定类型的指针。

例如,使用 malloc() 函数分配一个整型内存空间:

int *pInt;pInt=(int*)malloc(sizeof(int));

注意 malloc() 前面连接的是返回值转化成的指针类型,括号内的是数据类型的字节数目。

calloc() 函数则是在 malloc() 函数的基础上增加了一个参数 n ,其原型为:

void *calloc(unsigned n,unsigned size);

意为分配 n 块 size 字节大小的连续的内存空间,有点像数组。

利用 malloc() 函数,我们可以改变上述代码得:

#include<stdio.h>
#include<string.h>
struct stu
{int iScore;char cName[10];struct stu* pNext;
};
int main()
{struct stu stu_temp;struct stu* pHead=NULL;struct stu* stu_new=(struct stu*)malloc(sizeof(struct stu));struct stu* stu_end=NULL;stu_temp.pNext = stu_new;//将队头结点与头结点连接起来stu_temp.iScore = -1;pHead = &stu_temp;while (scanf("%d", &stu_new->iScore)&&stu_new->iScore != -1)//输入-1,循环停止{scanf("%s", stu_new->cName);stu_end = stu_new;stu_new= (struct stu*)malloc(sizeof(struct stu));stu_end->pNext = stu_new;}stu_end->pNext = NULL;free(stu_new);printf("打印结果:\n");while (pHead != NULL){if (pHead->iScore == -1){pHead = pHead->pNext;continue;}printf("%d %s\n", pHead->iScore, pHead->cName);pHead = pHead->pNext;}return 0;
}

我们先来打印一下输出结果:

注:上面代码中 stu_new 、stu_end 为指向某个特定结点的指针,为方便起见,以下称某个特定结点的名字就是指向该特定结点的指针的名字,例如将指向某个特定结点的指针 stu_new 作为该特定结点的名字。

下面的这些语句最为重要:

while (scanf("%d", &stu_new->iScore)&&stu_new->iScore != -1)//输入-1,循环停止{scanf("%s", stu_new->cName);stu_end = stu_new;stu_new= (struct stu*)malloc(sizeof(struct stu));stu_end->pNext = stu_new;}stu_end->pNext = NULL;free(stu_new);

这段语句用来开辟一个新节点 stu_new,然后在设置好了该新结点的数据域后,将 stu_end 作为该结点的另一个名字,然后利用下面这个语句继续开辟一个新的内存空间:

stu_new= (struct stu*)malloc(sizeof(struct stu));

开辟成功后,用 stu_new 来命名这个新空间,即这个新结点的名字为 stu_new 。再来看一下这个循环的终止条件:

while (scanf("%d", &stu_new->iScore)&&stu_new->iScore != -1)

将下面这个赋值语句放置在这个终止条件中,可以很方便地在后面通过 stu_new->iScore 是否等于 -1 来决定是否终止,故只有当我们为 stu_new->iScore 赋的值不为 -1 时才能开辟新的结点并将这个新结点纳入到旧链当中来

scanf("%d", &stu_new->iScore)

当跳出循环时,说明这个新结点的数据域的 iScore 为 -1 ,我们不需要这个新结点,不用将它纳入到旧链当中来。而已有的旧链最后一个结点为 stu_end ,我们将它的指针域设置为 NULL ,意即 stu_end 后面不再连接新结点,同时我们将为新结点开辟的空间还给系统,就有了下面的语句:

stu_end->pNext = NULL;
free(stu_new);

        截取的这段关键代码的逻辑图示为:

直到下图的出现,4、5过程才不会继续:

单向链表的讲解就到此为止,在后续的博客里博主会谈谈对循环链表和双向链表的理解。


欢迎指正我的上一篇博客:实现简单的网络通讯(C语言)

我的下一篇博客:循环链表×双向链表(C语言)

链表的实现(C语言)相关推荐

  1. 链表相关代码(C语言)

    链表相关代码(C语言),包含单链表.双链表.双向循环链表增删改查 链表反转(逆序) //链表逆序 bool reverse_linklist(struct node *ph){struct node ...

  2. c语言 静态链表插入排序,数据结构C语言版 表插入排序

    西门豹治邺奇计 数据结构C语言版 表插入排序.txt两个人吵架,先说对不起的人,并不是认输了,并不是原谅了.他只是比对方更珍惜这份感情./* 数据结构C语言版 表插入排序 算法10.3 P267-P2 ...

  3. c语言将一个已知头结点的单链表逆序_C语言数据结构实现链表逆序并输出

    C语言数据结构实现链表逆序并输出 将一个链表逆序并输出.我用了两种方法来实现,第一种是借助了一个新的空链表:第二种是在原来链表的基础上直接实现逆序. 实例代码: 头文件: #include #incl ...

  4. 单链表折半查找c语言,为什么有序的单链表不能进行折半查找

    公告: 为响应国家净网行动,部分内容已经删除,感谢读者理解. 话题:为什么有序的单链表不能进行折半查找 问题详情:急!!!!拜托高手解答 等级低 分数不高 请帮帮忙 谢谢了回答:这是由链表的特决定的. ...

  5. 用c语言描述单链表的数据类型,数据结构—单链表(类C语言描述)

    单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List).     链表的具体存储表示为: ① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不 ...

  6. c语言 链表 删除节点,C语言实现单链表节点的删除(不带头结点)

    对单链表节点进行增删改查是最基本的操作,这篇博客将会来实现对节点的删除.其他的操作可参考<C语言实现链表的基本操作>这篇博客.删除某个节点有两个类型: (1)删除i某个位置的节点: (2) ...

  7. c语言将一个已知头结点的单链表逆序_C语言实现常用数据结构:静态链表数组实现(第5篇)...

    「今天是学习C语言第 148 天」 纸上学来终觉浅,绝知此事要躬行.-- 陆游「冬夜读书示子聿」 # 静态链表 使用数组实现,利用数组下标代替指针,从而实现数据结点之间的先后关系.实现要点: 1.数组 ...

  8. 数据结构—单链表(类C语言描写叙述)

    单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List).     链表的详细存储表示为: ① 用一组随意的存储单元来存放线性表的结点(这组存储单元既能够是连续的.也能够是不 ...

  9. c语言用指针删除链表free,leetcode剑指 Offer 18(删除链表的节点)--C语言实现

    求: 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点. 返回删除后的链表的头节点. 注意:此题对比原题有改动 示例 1: 输入: head = [4,5,1,9], val = 5 ...

  10. 数据结构—单链表(类C语言描述)

    单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List). 链表的具体存储表示为: ① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的) ...

最新文章

  1. 联想x250为什么这么贵_为什么ThinkPad这么贵?
  2. Ubuntu下如何禁用IPv6
  3. 11.1 JavaScript介绍
  4. jest java_✅使用jest进行测试驱动开发
  5. 边缘检测robert原理_机器视觉尺寸检测基础
  6. 第三届(2016)科学数据大会——科学数据与创新发展征文通知
  7. 习题2.6 递归求简单交错幂级数的部分和 (15 分)
  8. 使用STL标准模板库实现的个人通讯录
  9. python自动生成采集规则_python 织梦自动采集更新脚本
  10. Vue Echarts飞机航线图
  11. YOLOv5 完美实现中文标签显示
  12. php如何实现mac端重签ipa,越狱插件:实现自动重签,让你不再掉签的越狱插件ReProvision...
  13. 【AGC035F】Two Histograms
  14. FPGA学习经验(BBS论坛)
  15. 报错FileSystemException: /datas/nodes/0/indices/gtTXk-hnTgKhAcm-8n60Jw/1/index/.es_temp_file:结构需要清理
  16. 计算机python教程_Python 如何入门?附Python教程下载
  17. [typescript] ERROR TS18003: No inputs were found in config file ‘tsconfig.json‘. Specified ‘include‘
  18. 读书笔记——《Python编程从入门到实践》第二章
  19. TensorFlow实现Word2Vec
  20. [JavaScript]_[初级]_[使用HTMLElement.dataset快速读写自定义属性]

热门文章

  1. Oracle 监控索引使用率脚本分享
  2. SOLIDWORKS软件转Dwg格式教程
  3. 体验Windows Mobile 6.5 (模拟器)
  4. ghost之后仍然中病毒----与病毒的斗争
  5. 寻星计划|Apache Doris 社区4月职位广场,IDG资本、Shopee 多个岗位
  6. am3352 软时钟老是漂移 rx-8025时钟 rx-8025SA时钟
  7. MapABC 地图定位
  8. windows 搭建代理服务器 - Apache httpd
  9. python神经网络编程 豆瓣,神经网络算法python实现
  10. Epub与Kindle