链表创建的陷阱与细节

链表是线性表的一种,它在逻辑结构上是连续的,在物理结构上是非连续的。

也就是说链表在物理空间上是独立的,可能是东一块西一块的。如下顺序表和链表在内存空间上的对比:

而链表的每一块空间是如何产生联系实现在逻辑结构上是连续的呢?

链表的每一块内存称为一个结点(或节点),结点我们用结构体类型的变量来申请空间,其中的一个(或多个)成员用来存放有效数据,另一个成员来存放下一个结点的地址,那样的话我们就可以通过地址访问到下一个结点。

如下:

本章只是对链表创建过程的细节和陷阱进行分析解决,并不会对链表的各种增删查改进行一一讲解,但这些操作会在文章末尾给出原码。

关于一个简单的链表,以下是头文件的声明:

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDatatype;
typedef struct SLNode
{SLDatatype val;struct SLNode* next;
}SLNode;
void SLN_HeadAdd(SLNode** pphead, SLDatatype x);//插入结点(头插)
void SLN_EndAdd(SLNode** pphead, SLDatatype x);//插入结点(尾插)
SLNode* SLN_Find(SLNode** pphead, SLDatatype x);//查找元素所在的结点
void SLN_fLocAdd(SLNode** pphead, SLNode* Loc, SLDatatype x);//指定位置后插入
void SLN_pLocAdd(SLNode* Loc, SLDatatype x);//指定位置后插入void SLN_Print(SLNode* phead);//打印链表void SLN_HeadDele(SLNode** pphead);//删除结点(头删)
void SLN_EndDele(SLNode** pphead);//删除结点(尾删)
void SLN_LocDele(SLNode** pphead, SLNode* Loc);//指定位置删除void SLN_Free(SLNode** pphead);//销毁链表释放内存

 细节1:成员类型的重命名

在这个声明结点的过程把int类型重命名为SLDatatype,后面要改储存的数据类型的话,不必再去写的函数中一个一个的修改,只需要在重命名这里一次性修改就可以。

然后这里还把 struct SLNode 重命名为SLNode可以方便后面简写。

现在我们来看插入

因为每次插入必然需要申请结点空间很繁琐,所以我们来封装一个函数专门用来申请结点空间

如下:

SLNode* SLNAdd(SLDatatype x)//x为需要储存的元素
{SLNode* pk = (SLNode*)malloc(sizeof(SLNode));assert(pk);pk->val = x;pk->next = NULL;return pk;
}

头插

陷阱1:是否使用二级指针

你真正理解什么情况需要传二级指针参数,什么情况不用,为什么用吗?

对于头插,初学者可能会写出下面这样的代码:

void SLN_HeadAdd(SLNode* phead, SLDatatype x)
{SLNode* padd = SLNAdd(x);if (ps==NULL)phead = padd;else{padd->next = phead;phead = padd;}
}

可以看出这里对参数phead做了更改,如这个表达式

                phead = padd;

但显然这里phead是临时变量,当函数结束后就销毁了,而实参并没有任何变化。

                                                        注意:malloc申请的空间并没有销毁

如何解决?

有两个方法:

就是把更改后的头结点返回去,再用原头结点去接收,如下:

SLNode* SLN_HeadAdd(SLNode* phead, SLDatatype x)
{SLNode* padd = SLNAdd(x);if (phead==NULL)phead = padd;else{padd->next = phead;phead = padd;}return phead;
}

此外还可以通过直接改变原头结点解决,即通过对指针的解引用来实现在函数内改变,而头结点是一个指向结构体的地址(即一个一级指针),改变它就需要一个二级指针参数来接收头结点的地址,然后对这个参数解引用从而达到改变头结点(也就是结构体的地址)的目的。

如下:

void SLN_HeadAdd(SLNode** pphead, SLDatatype x)
{SLNode* padd = SLNAdd(x);if (*pphead==NULL)*pphead = padd;else{padd->next = *pphead;*pphead = padd;}
}

细节2:指针作为形参目的

下面函数实现的是链表的尾删:


void SLN_EndDele(SLNode* phead)
{SLNode* ph = phead;if (!ph)return;SLNode* pk = ph;while (ph->next){pk = ph;ph = ph->next;}pk->next = NULL;free(ph);
} 

我们来思考这个函数为什么不用二级指针也不用返回头结点就能完成,又为什么不用指针完成不了。注意该过程只对结点的成员进行了改变结点的销毁,而要在一个函数内改变结构体成员,是需要得到该成员的地址的,而一个一级指针就刚好满足了这个要求的->就相当于得到该成员的地址并且对他它解引用。而这里这个函数用一级指针作为参数的作用真正用于的是以下两部

                 pk->next = NULL;
                 free(ph);

一个是需要改变成员所以需要一级指针,另一个是需要释放内存所以需要一级指针,没有这两部这个函数的参数完全可以不是指针变量。

例如一个链表的打印函数可以这么写:

void SLN_Print(SLNode head)
{while (head.next){printf("%d->", head.val);head = *(head.next);}printf("NULL\n");
}

要注意的是这个表达式head = *(head.next);因为head.next是struct SLNode* 类型head是struct SLNode类型所以这个需要对head.next解引用。

总结:(1)当一个函数需要改变结点的地址时需要传二级指针(或传一级指针但要返回头结点)。(2)当一个函数只需要改变结点成员或销毁结点的时候只需要传一级指针。(3)当一个函数对结点以及成员不做任何改变的时候只需要传一个结构体变量

注意:这里结点指的是结构体的地址。

 陷阱2:运算符优先级问题

下面是一个关于头插的函数:

void SLN_HeadDele(SLNode** pphead)
{assert(pphead);if (*pphead == NULL)return;SLNode* ph = (*pphead)->next;free(*pphead);*pphead = ph;
}

我们要注意的是这个语句:

        SLNode* ph = (*pphead)->next;

在初学者很容易写为 SLNode* ph = *pphead->next;但要注意->成员访问运算符的优先级是比解引用操作符的优先级要高的,这里要不要忘记用()明确运算的优先。

陷阱3:结点前驱丢失问题

在初学者经常犯的一个错误就在对链表进行增删查该等操作过程中常常会把操作的结点后面的结点给弄丢,一旦丢失再也无法找到。

例如一个链表的指定结点之后插入结点的函数这样写:

void SLN_pLocAdd(SLNode* Loc, SLDatatype x)
{SLNode* padd = SLNAdd(x);Loc->next = padd;
}

或这么写:

void SLN_pLocAdd(SLNode* Loc, SLDatatype x)
{SLNode* padd = SLNAdd(x);Loc->next = padd;padd->next=Loc->next;
}

两种写法都是错误的,第一种写法直接将Loc原本所指向的结点丢失

第二种写法相当于padd->next=padd;不仅丢失了Loc用来指向的结的,还造成打印和尾插等操作的死循环。

正确的写法是

void SLN_pLocAdd(SLNode* Loc, SLDatatype x)
{SLNode* padd = SLNAdd(x);padd->next = Loc->next;Loc->next = padd;
}

 陷阱4:不可逆向遍历缺陷

要知道链表不可逆向遍历的,得到一个结点是无法访问到上一个结点的,只能往下访问。所以如果害怕某个结点在遍历过程中丢失的话,就需要新的变量来把它储存。

例如一个链表的尾删函数:

void SLN_EndDele(SLNode* phead)
{SLNode* ph = phead;if (!ph)return;SLNode* pk = ph;while (ph->next){pk = ph;ph = ph->next;}pk->next = NULL;free(ph);ph = NULL;
}

其中表达式

                pk = ph;

用变量pk对ph的值进行储存之后再改变ph。

#include"SLNode.h"
SLNode* SLNAdd(SLDatatype x)//
{SLNode* pk = (SLNode*)malloc(sizeof(SLNode));assert(pk);pk->val = x;pk->next = NULL;return pk;
}
void SLN_HeadAdd(SLNode** pphead, SLDatatype x)
{assert(pphead);SLNode* ps = *pphead;SLNode* padd = SLNAdd(x);if (!ps)//相当于if(pk==NULL)*pphead = padd;else{padd->next = ps;*pphead = padd;}
}
//SLNode* SLN_HeadAdd(SLNode* phead, SLDatatype x)
//{
//	SLNode* ph = SLNAdd(x);
//	if (phead == NULL)
//	{
//		return ph;
//	}
//	else
//	{
//		ph->next = phead;
//		return ph;
//	}
//
//}
void SLN_EndAdd(SLNode** pphead, SLDatatype x)
{assert(pphead);SLNode* ps = *pphead;SLNode* padd = SLNAdd(x);if (!ps)*pphead = padd;else{while (ps->next){ps=ps->next;}ps->next = padd;}
}
void SLN_Print(SLNode* phead)
{while (phead){printf("%d->", phead->val);phead = phead->next;}printf("NULL\n");
}
//void SLN_Print(SLNode head)
//{
//	while (head.next)
//	{
//		printf("%d->", head.val);
//		 head = *(head.next);
//	}
//	printf("NULL\n");
//}
SLNode* SLN_Find(SLNode** pphead, SLDatatype x)
{assert(pphead);SLNode* ph = *pphead;while (ph){if (ph->val == x)return ph;ph = ph->next;}return NULL;
}
void SLN_fLocAdd(SLNode** pphead, SLNode* Loc, SLDatatype x)
{assert(pphead);SLNode* ph = *pphead;SLNode* pd = ph, * padd = SLNAdd(x);if (Loc==ph){SLN_HeadAdd(pphead, x);return;}while (ph->next){if (ph == Loc){padd->next = Loc;pd->next = padd;return;}pd = ph;ph = ph->next;}
}
void SLN_pLocAdd(SLNode* Loc, SLDatatype x)
{SLNode* padd = SLNAdd(x);padd->next = Loc->next;Loc->next = padd;
}
void SLN_HeadDele(SLNode** pphead)
{assert(pphead);if (*pphead == NULL)return;SLNode* ph = (*pphead)->next;//陷阱free(*pphead);*pphead = ph;
}
void SLN_EndDele(SLNode** pphead)
{assert(pphead);SLNode* ph = *pphead;if (!ph)return;SLNode* pk = ph;while (ph->next){pk = ph;ph = ph->next;}pk->next = NULL;free(ph);ph = NULL;
}
//void SLN_EndDele(SLNode* phead)
//{
//	SLNode* ph = phead;
//	if (!ph)
//		return;
//	SLNode* pk = ph;
//	while (ph->next)
//	{
//		pk = ph;
//		ph = ph->next;
//	}
//	pk->next = NULL;
//	free(ph);
//	ph = NULL;
//}
void SLN_LocDele(SLNode** pphead, SLNode* Loc)
{assert(pphead);if (*pphead == NULL)return;SLNode* ph=*pphead,*pk=ph;if (Loc == ph){SLN_HeadDele(pphead);return;}while (ph){if (ph == Loc){pk->next = ph->next;free(ph);ph = NULL;return;}pk = ph;ph = ph->next;}
}
void SLN_Free(SLNode** pphead)
{assert(pphead);while (*pphead){SLNode* ph = (*pphead)->next;free(*pphead);*pphead = ph;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/817115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动应用安全合规动态:网信办、金管局发文强调数据安全;3月个人信息违规抽查结果出炉!(第五期)

一、监管部门动向&#xff1a;国家互联网信息办公室公布《促进和规范数据跨境流动规定》; 工信部发布《关于网络安全保险典型服务方案目录的公示》 二、安全新闻&#xff1a;恶意软件警报&#xff01;黑客利用软件即服务攻击印度安卓用户&#xff1b;Cerberus银行恶意软件的虚…

第十四届蓝桥杯题解:平方差 ,更小的数,买瓜,网络稳定性(货车运输)

目录 平方差 更小的数 买瓜 网络稳定性&#xff08;货车运输&#xff09; 货车运输 平方差 这道题就是数论的题&#xff0c;不难想到一个数m可以拆成(a-b)(ab)&#xff0c;其实(a-b)和(ab)就是m的一对因子&#xff0c;不妨设为x和y。 则有&#xff1a; abx; a-by; x*ym; 联…

JVM—jps、jstat、jinfo、jmap、jstack的使用

JVM—jps、jstat、jinfo、jmap、jstack的使用 jps jps全称&#xff1a;Java Virtual Machine Process Status Tool 可以查看Java进程&#xff0c;相当于Linux下的ps命令&#xff0c;只不过它只列出Java进程。 jps:列出Jav程序ID和Main函数名称 jps -q:只输出进程ID jps -m …

MATLAB4:数值计算

文章目录 一、实验目的二、实验内容三、仿真结果四、实践中遇到的问题及解决方法 一、实验目的 1. 熟悉根据已知数据进行回归法曲线拟合。   2. 熟悉根据已知数据进行多项式曲线拟合。   3. 熟悉根据已知数据利用指定方法进行数据插值&#xff08;临近插值、线性插值、立方…

小程序视频如何下载到电脑上

小程序视频如何下载到电脑上&#xff0c;很简单 1.利用Fiddler和Charles这些专业的抓包工具 2.利用录屏 3.利用专门抓取资源的工具(集成了抓取下载&#xff0c;而且对资源下载很友好) 工具我已经打包好了 下载高手链接&#xff1a;https://pan.baidu.com/s/1qJ81sNBzzzU0w…

Linux的学习之路:6、Linux编译器-gcc/g++使用

摘要 本文主要是说一些gcc的使用&#xff0c;g和gcc使用一样就没有特殊讲述。 目录 摘要 一、背景知识 二、gcc如何完成 1、预处理(进行宏替换) 2、编译&#xff08;生成汇编&#xff09; 3、汇编&#xff08;生成机器可识别代码 4、链接&#xff08;生成可执行文件或…

【我的代码生成器】生成React页面类

有了数据表的结构信息&#xff0c;就能生成React 的页面类&#xff0c;快捷方便。 生成界面如下&#xff1a; 生成的React FrmUser.js页面如下&#xff1a; 只需再写里面的操作逻辑代码。

银河麒麟之PaddleOCR模型部署

一、PaddleOCR简介 PaddleOCR是一个基于飞桨框架开发的开源OCR工具&#xff0c;提供了一系列强大的文本识别功能。PaddleOCR支持多种文本识别任务&#xff0c;包括文字检测、文字识别、文本方向检测等。它具有高效、准确的特点&#xff0c;适用于多种场景下的文本识别需求&…

IDP之Backstage - 环境搭建

0. 目录 1. 前言2. 环境准备&#xff08;Windows10下&#xff09;2.1 安装nvm2.2 git和docker安装 3. 创建模板项目3.1 典型错误: fails on the yarn install step3.2 再次启动3.3 验证 4. 相关 1. 前言 本不想写这篇&#xff0c;因为看着官网文档写着挺简单的&#xff0c;但实…

线上剧本杀小程序发展趋势如何?

随着几年的快速发展&#xff0c;剧本杀行业进入到了大众的视野&#xff0c;不过从2022年开始&#xff0c;行业就开始处于下滑趋势&#xff0c;但是行业反而变得更加的规范化。 经历过下滑发展后&#xff0c;剧本杀行业已经趋向专业化、精品化发展&#xff0c;行业正在复苏&…

高基报表是什么?高校各部门如何快速填报高基表?

高基报表——全称是《高等教育事业基层统计报表》&#xff08;以下简称《高基报表》&#xff09;&#xff0c;作为高等院校基本情况报表的代表&#xff0c;承载着学校办学实力的真实写照。填报高基报表是一项细致入微的工作&#xff0c;不仅关系到学校的科学管理和决策研究&…

第一节:什么是操作系统

什么是操作系统 一、一台计算机的组成部分1、计算机能干啥2、谈谈计算机硬件 二、什么是操作系统三、学习操作系统的层次 一、一台计算机的组成部分 如下图所示&#xff1a; 这就是就是构成一台计算机的组成部分 1、计算机能干啥 ∙ \bullet ∙计算机是我们专业吃饭的家伙&a…

前端知识学习笔记-六(vue)

简介 Vue是前端优秀框架是一套用于构建用户界面的渐进式框架 Vue优点 Vue是目前前端最火的框架之一 Vue是目前企业技术栈中要求的知识点 vue可以提升开发体验 Vue学习难度较低 Vue开发前准备 一、nodejs环境 Nodejs简介 Nodejs诞生于2009年&#xff0c;主攻服务器方向&#x…

数据结构OJ题——栈和队列

1. 用栈实现队列&#xff08;OJ链接&#xff09; 题目描述&#xff1a;请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09; void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回…

锂电池寿命预测 | Matlab基于BiLSTM双向长短期记忆神经网络的锂电池寿命预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 锂电池寿命预测 | Matlab基于BiLSTM双向长短期记忆神经网络的锂电池寿命预测 程序设计 完整程序和数据获取方式&#xff1a;私信博主回复Matlab基于BiLSTM双向长短期记忆神经网络的锂电池寿命预测。 参考资料 [1] h…

Python数据分析案例42——基于Attention-BiGRU的时间序列数据预测

承接上一篇的学术缝合&#xff0c;排列组合模型&#xff0c;本次继续缝合模型演示。 Python数据分析案例41——基于CNN-BiLSTM的沪深300收盘价预测-CSDN博客 案例背景 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领…

最新版守约者二级域名分发系统

主要功能 二级域名管理&#xff1a; 我们的系统提供全面的二级域名管理服务&#xff0c;让您轻松管理和配置二级域名。 域名分发&#xff1a;利用我们先进的域名分发技术&#xff0c;您可以自动化地分配和管理域名&#xff0c;确保每个用户或客户都能及时获得所需的域名资源。…

SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普 在此之前&#xff0c;简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中&#xff0c;延时队列可以实现很多功能&#xff0c;此类业务中&#xff0c;一般上是非实时的&#xff0c;需要延迟处理的&a…

【深入探讨】JavaScript 中的 forEach 和 map 区别

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端Clodplay &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨优质专栏&#xff1a;VS Code插件开…

房贷还款(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <math.h>int main() {//初始化变量值&#xff1b;double m, r 0.01;float d 300000;float p 6000;//运算还款所需月份&#xff1b;m log10…