没有合适的资源?快使用搜索试试~ 我知道了~
链表基础知识详解
资源推荐
资源详情
资源评论
链表基础知识详解
本文档通过解释、图示、示例代码和练习相结合的方式,介绍了构建链表的基本结构和技术。
如果你想要理解链表,或者想要看到一个现实且实用的、涉及大量指针操作的代码示例,那么
这份资料将对你非常有用。
学习链表有两个主要原因。最显而易见的是,链表是一种你可能想在真实程序中使用的数据结
构。了解链表的优点和缺点将使你更加深入地理解在考虑任何数据结构时都需要关注的时间、
空间和代码问题。另一个不那么显而易见的原因是,链表是学习指针的绝佳方式。事实上,你
可能从未在真实程序中使用过链表,但你肯定会频繁使用指针。链表问题巧妙地结合了算法和
指针操作。传统上,链表一直是初学者程序员真正理解指针的实践领域。
第 1 节 — 链表基础
为何使用链表?
链表和数组在存储数据集合方面具有相似性。术语上,数组和链表都是代表“客户端”代码存储
“元素”。元素的具体类型并不重要,因为相同的结构基本上可以存储任何类型的元素。理解链
表的一种方式是,先了解数组的工作原理,然后思考其他可能的替代方法。
数组回顾
数组可能是用于存储元素集合的最常见数据结构。在大多数编程语言中,数组声明起来很方便,
并且提供了便捷的[ ]语法来通过索引号访问任何元素。以下示例展示了一些典型的数组代码,
以及数组在内存中可能呈现的图示。该代码分配了一个名为 int scores[100]的数组,将前三个
元素设置为包含数字 1、2、3,而数组的其余部分则保持未初始化状态……
void ArrayTest() {
int scores[100];
// operate on the elements of the scores array...
scores[0] = 1;
scores[1] = 2;
scores[2] = 3;
}
以下是 scores 数组在内存中可能呈现的图示说明。关键点在于,整个数组是作为一块连续的内
存块来分配的。数组中的每个元素都在该数组中有自己的空间。可以使用[ ]语法直接访问任何
元素。
一旦数组设置完成,使用[ ]操作符访问任何元素都变得方便且快速。(专家额外信息)通过诸
如 scores[i]的表达式访问数组几乎总是使用快速地址算术来实现的:元素的地址是通过从数组
起始位置计算偏移量得出的,这仅需要一次乘法和一次加法。
数组的缺点包括……
1)数组的大小是固定的——在此例中为 100 个元素。大多数情况下,这个大小是在编译时通
过简单的声明(如上述示例)来指定的。虽然通过一些额外的努力,可以将数组的大小推迟到
运行时再确定,但之后它仍然保持不变。(专家额外信息)你可以费心地在堆中动态分配一个
数组,然后使用 realloc()动态调整其大小,但这需要程序员付出真正的努力。
2)由于(1)的原因,程序员最方便的做法是分配看起来“足够大”的数组(例如 scores 示例中
的 100)。虽然这样做很方便,但这种策略有两个缺点:(a)大多数情况下数组中只有 20 或
30 个元素,而数组中 70%的空间实际上都被浪费了。(b)如果程序需要处理超过 100 个分数,
代码就会崩溃。令人惊讶的是,大量商业代码都采用了这种天真的数组分配方式,这在大多数
情况下会浪费空间,并在特殊情况下导致崩溃。(专家额外信息)对于相对较大的数组(大于
8KB),虚拟内存系统可能会部分补偿这个问题,因为“浪费”的元素从未被访问过。
3)(次要)在数组前面插入新元素可能代价很高,因为需要移动现有元素以腾出空间。
链表有自己的优势和劣势,但它们恰好在数组薄弱的地方表现出色。数组的所有特性都源于其
为所有元素在一块内存中分配内存的策略。链表则使用了一种完全不同的策略。我们将看到,
链表为每个元素单独分配内存,并且只在必要时进行分配。
指针复习
以下是关于指针的术语和规则的快速回顾。接下来的链表代码将依赖于这些规则。
指针/指向物:“指针”存储了对另一个变量的引用,有时这个变量也被称为其“指向物”。另
外,指针也可以被设置为 NULL 值,表示它当前不指向任何指向物。(在 C 和 C++中,NULL
值可以用作布尔假值。)
解引用 Dereference:对指针进行解引用操作可以访问其指向物。只有在指针被设置为指向
特定的指向物之后,才能对其进行解引用。没有指向物的指针是“坏的”(下文详述),并
且不应该被解引用。
坏指针:没有分配指向物的指针是“坏的”,并且不应该被解引用。在 C 和 C++中,对坏指
针进行解引用有时会导致立即崩溃,有时则会随机破坏正在运行的程序的内存,导致后续
崩溃或计算错误。这种随机出现的错误很难追踪。在 C 和 C++中,所有指针一开始都有坏
值,因此很容易意外地使用坏指针。正确的代码会在使用指针之前为其设置一个有效的值。
意外使用坏指针是指针代码中最常见的错误。在 Java 和其他面向运行时的语言中,指针自
动以 NULL 值开始,因此解引用坏指针会立即被检测到。出于这个原因,Java 程序更容易
调试。
指针赋值:两个指针之间的赋值操作,如 p=q;,会使两个指针指向同一个指向物。它不会
复制指向物的内存。赋值后,两个指针将指向相同的指向物内存,这被称为“共享”情况。
malloc():malloc()是一个系统函数,它在“堆”中分配一块内存,并返回一个指向新内存块
的指针。malloc()和其他堆函数的原型在 stdlib.h 中。malloc()的参数是需要分配的内存块的
大小(以字节为单位)。与局部(“栈”)变量不同,堆内存不会在创建它的函数退出时自
动释放。如果 malloc()无法满足请求,它会返回 NULL。(专家额外信息)如果你希望确保
安全,可以使用 assert()来检查 NULL 情况。大多数现代编程系统会在内存分配器中抛出异
常或进行其他自动错误处理,因此源代码需要显式检查分配失败的情况变得越来越少见。
free():free()是 malloc()的反操作。对堆内存块调用 free()以向系统指示你不再需要它。free()
的参数是指向堆中内存块的指针——这个指针之前是通过调用 malloc()获得的。
链表的样子:
数组为其所有元素分配了一块连续的内存空间。相比之下,链表则为每个元素单独分配内存空
间,这个空间被称为“链表元素”或“节点”。链表通过使用指针将所有节点像链条中的链环一样
连接在一起,从而形成了其整体结构。
在链表中,每个节点都包含两个字段:一个是“数据”字段,用于存储列表为其客户端所持有的
任何元素类型;另一个是“next”字段,它是一个指针,用于将一个节点链接到下一个节点。每
个节点都是通过调用 malloc()在堆上分配的,因此节点内存会一直存在,直到显式地通过调用
free()进行释放。链表的头部是一个指向第一个节点的指针。
下面是一个包含数字 1、2 和 3 的链表的图示:
上述图示展示了由 BuildOneTwoThree()函数(该函数的完整源代码在下方)在内存中构建的链
表。链表的起始位置存储在一个名为“head”的指针中,该指针指向第一个节点。第一个节点包
含一个指向第二个节点的指针。第二个节点包含一个指向第三个节点的指针,以此类推。列表
中的最后一个节点将其.next 字段设置为 NULL,以标记列表的结束。代码可以通过从头部开始
并跟随.next 指针来访问列表中的任何节点。对列表前部的操作是快速的,而访问列表中更远
的节点的操作则随着它们离前部的距离增加而变得更慢。这种访问节点的“线性”成本在根本上
比数组提供的常数时间访问更昂贵。在这方面,链表确实比数组效率低。
诸如上述的图示对于思考指针代码非常重要,因此本文中的大多数示例都将代码与其内存图示
相关联,以强调这种习惯。在这种情况下,head 指针是一个普通的局部指针变量,因此它单
独绘制在左侧,以显示它位于栈中。列表节点绘制在右侧,以显示它们在堆中分配。
空列表 — NULL
上面由 head 指针指向的列表被描述为“长度为三”,因为它由三个节点组成,且最后一个节点
的.next 字段设置为 NULL。需要有一种表示空列表(即没有节点的列表)的方式。空列表最常
见的表示方法是使用 NULL 的 head 指针。空列表情况是链表代码中的一个常见且奇特的“边界
情况”。本文中呈现的所有代码都能正确处理空列表情况,但这并非没有付出努力。在处理链
表代码时,记住检查空列表情况以验证其是否也能正常工作是一个好习惯。有时空列表情况与
所有其他情况相同,但有时它需要一些特殊情况的代码。无论如何,至少考虑一下这个情况是
有好处的。
链表类型:节点和指针
在编写构建上述链表的代码之前,我们需要两种数据类型……
节点:这是构成链表主体的节点的类型。这些节点在堆中分配。每个节点包含一个单一的
客户端数据元素和一个指向列表中下一个节点的指针。类型:struct node。
struct node {
int data;
struct node* next;
};
节点指针:这是指向节点的指针的类型。这将是头指针以及每个节点内部.next 字段的类型。
在 C 和 C++中,不需要单独的类型声明,因为指针类型只需在节点类型后加上一个*即可。
类型:struct node*。
BuildOneTwoThree()函数
以下是一个简单的函数,它使用指针操作来构建列表{1, 2, 3}。上面的内存图示对应于此函数结
束时的内存状态。此函数演示了如何通过调用 malloc()和指针赋值(=)操作在堆中构建一个指
针结构。
/*
Build the list {1, 2, 3} in the heap and store
its head pointer in a local stack variable.
Returns the head pointer to the caller.
*/
struct node* BuildOneTwoThree() {
struct node* head = NULL;
struct node* second = NULL;
struct node* third = NULL;
head = malloc(sizeof(struct node));
// allocate 3 nodes in the heap
second = malloc(sizeof(struct node));
剩余28页未读,继续阅读
资源评论
icysmile131
- 粉丝: 4631
- 资源: 735
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 基于matlab实现的锁模光纤激光器仿真源码+文档说明(高分项目)
- 基于OpenCV全景图像拼接系统源代码(完整前后端+mysql+说明文档+LW).zip
- 基于matlab实现的锁模光纤激光器仿真源码(高分项目)
- 基于python的大学生就业信息管理系统(django)源代码(完整前后端+mysql+说明文档+LW).zip
- 简单好用的移动手机端ASP报名程序(含access数据库)
- 基于深度学习的安全帽佩戴检测wlw源代码(完整前后端+mysql+说明文档+LW).zip
- 群晖NAS中搭建WordPress站点
- 基于python的语音和背景音乐分离算法及系统源代码(完整前后端+mysql+说明文档+LW).zip
- 2023-2008年上市公司企业耐心资本数据、耐心资本所占比重数据集.txt
- 三菱电梯主板地址表参数 三菱电梯地址码, KCD-116主板地址参数, MAXIEZ电梯主板地址参数, VFGLC电梯主板地址参数, 可以修改电梯楼层显示、基站、强迫关门、消防功能、开关门时间等参数
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功