链表创建的陷阱与细节

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

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

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

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

如下:

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

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

#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.rhkb.cn/news/309527.html

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

相关文章

欢乐钓鱼大师秒杀源码

gg修改器设置里面单选a内存然后去试试e类型搜索鱼竿的拉杆速度然后点修改点很多增加1然后游戏返回在进去看鱼竿拉速然后在修改器的里面找到拉速一样的数值其他恢复全移除不恢复移除会闪退然后点开保留下来的拉速数值点转到会有一堆数值你得找里面找到鱼竿的伤害距离等数值就可以…

Linux内核常见的丢包场景有哪些

目录 摘要 1 收发包处理流程 2 硬件网卡相关 2.1 ring buffer满 2.2 利用 ntuple 保证关键业务 3 arp丢包 3.1 neighbor table overflow 3.2 unresolved drops 4 conntrack丢包&#xff1a;nf_conntrack: table full 5 udp接收buffer满 6 丢包定位 6.1 dropwatch 查看丢包 6.2…

线程池与工厂模式

线程池 如果我们需要频繁的创建销毁线程,此时创建销毁线程的成本,就不能忽视了.因此就可以使用线程池.即,提前搞好一波线程,后续需要使用线程就直接从池子里拿一个即可.当线程不再使用,就放回池子里. 本来,是需要创建线程/销毁线程.现在是从池子里获取到现成的线程,并且把线程…

BackTrader 中文文档(十)

原文&#xff1a;www.backtrader.com/ 用户自定义佣金 原文&#xff1a;www.backtrader.com/docu/user-defined-commissions/commission-schemes-subclassing/ 重塑 CommInfo 对象到实际形式的最重要部分涉及&#xff1a; 保留原始的 CommissionInfo 类和行为 为轻松创建用户定…

前端三剑客 —— JavaScript (第六节)

目录 内容回顾 BOM编程 DOM编程* document对象 document对象的属性 document对象的方法 DOM对象节点 操作DOM对象内容 操作DOM对象的属性 --- DOM对象.属性名称 --- DOM对象[属性名称] --- 调用系统API &#xff08;Application Program interface&#xff09;&#…

行业模板|DataEase批发零售大屏模板推荐

DataEase开源数据可视化分析平台于2022年6月发布模板市场&#xff08;https://templates-de.fit2cloud.com&#xff09;&#xff0c;并于2024年1月新增适用于DataEase v2版本的模板分类。模板市场旨在为DataEase用户提供专业、美观、拿来即用的大屏模板&#xff0c;方便用户根据…

2024 年江苏省职业院校技能大赛“区块链技术应用” 赛项赛卷(样卷)运维题解析一

运维题 环境: ubuntu20 fisco 2.8.0 前言 准备两台机子,并且可以能相互pin通 192.168.19.133 [M1-A] 192.168.19.137 [M2-B] 子任务 1-2-1: 搭建区块链系统并验证 基于给定服务器环境以及软件,搭建一条双机 1 机构 8 节点 1 群组的区块 链系统(默认端口开始[30300,2020…

最优算法100例之43-包含min函数的栈

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数,在该栈中,调用min,push及pop的时间复杂…

idea 中运行spring boot 项目报 Command line is too long的解决办法。

Command line is too long 在这里选择edit configures 选择shrten command line , 选择 jar manifest 运行即可。

5G网络开通与调测ipv4

要求如下&#xff1a; 1. 勘站规划 1. 【重】首先观察NR频点&#xff0c;完成设备选型 2645--选择N41 3455--选择N78 4725--选择N79 设备选型如下&#xff1a;观察AAU的通道数&#xff0c;最大发射功率&#xff1b;选择N41的选型频段也要选41 2. …

04—常用方法和正则表达式

一、字符串 1.length 属性返回字符串的长度(字符数)。 2.在字符串中查找字符串 indexOf() 字符串使用 indexOf() 来定位字符串中某一个指定的字符首次出现的位置 如果没找到对应的字符函数返回-1 lastIndexOf() 方法在字符串末尾开始查找字符串出现的位置。 3.replace() 方…

WordPress 图片压缩插件:Compress JPEG PNG images 使用方法

插件介绍 Compress JPEG & PNG images是一款非常好用的图片压缩插件:&#xff0c;非常值得大家安装使用&#xff1b;特别是图片类型网站。其实我们很多服务器磁盘空间是不在乎多那么几十 MB 大小的&#xff0c;但是压缩了图片能提升网站速度&#xff0c;节省宽带&#xff…

计算机本科毕业,「就业」还是「读研」?

如果本科不错能找到较好的工作&#xff0c;建议直接工作&#xff0c;否则可以选择读研。 如果你本科毕业于一所顶尖学府&#xff0c;且技术实力雄厚&#xff0c;那么直接就业可能更为明智&#xff1b;对比而言读研可以为你提供更多的时间和机会去提升自己&#xff0c;尤其是在…

算法1: 素数个数统计

统计n以内的素数个数 素数&#xff1a;只能被1和自身整除的自然数&#xff0c;0和1除外&#xff1b; 举例&#xff1a; 输入&#xff1a;100 输出&#xff1a;25 import java.util.*; class Test1{public static void main(String[] args){int a 100; //输入数字//…

智慧矿山视频智能监控与安全监管方案

一、行业背景 随着全球能源需求的日益增长&#xff0c;矿业行业作为国民经济的重要支柱&#xff0c;其发展日益受到广泛关注。然而&#xff0c;传统矿山管理模式的局限性逐渐显现&#xff0c;如生产安全、人员监管、风险预警等方面的问题日益突出。因此&#xff0c;智慧矿山智…

回归预测 | Matlab实现WOA-BP鲸鱼算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现WOA-BP鲸鱼算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现WOA-BP鲸鱼算法优化BP神经网络多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现WOA-BP鲸鱼算法优化BP神经网络多变量回归预测&#xff08;完整源码…

文献阅读:Viv:在 web 上多尺度可视化高分辨率多重生物成像数据

文献介绍 「文献题目」 Viv: multiscale visualization of high-resolution multiplexed bioimaging data on the web 「研究团队」 Nils Gehlenborg&#xff08;美国哈佛医学院&#xff09; 「发表时间」 2022-05-11 「发表期刊」 Nature Methods 「影响因子」 47.9 「DOI…

计算机网络—传输层UDP协议:原理、应用

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;2月のセプテンバー 1:21━━━━━━️&#x1f49f;──────── 5:21 &#x1f504; ◀️ ⏸ ▶️ ☰ &am…

RMT: Retentive Networks Meet Vision Transformers学习笔记

代码地址&#xff1a;GitHub - qhfan/RMT: (CVPR2024)RMT: Retentive Networks Meet Vision Transformer 论文地址&#xff1a;https://arxiv.org/pdf/2309.11523.pdf Transformer首次出现在自然语言处理领域&#xff0c;后来迁移到计算机视觉领域&#xff0c;在视觉任务中表现…

web3项目自动连接小狐狸以及小狐狸中的各种“地址”详解

刚做web3的时候&#xff0c;比较迷糊的就是人们口中说的各种地址&#xff0c;小狐狸钱包地址&#xff0c;私钥地址&#xff0c;跳转地址&#xff0c;接口地址&#xff0c;交易地址&#xff0c;等等XX地址&#xff0c;常常感觉跟做链的同事们说话不在一个频道。 这一小节&#x…