单链表及其代码实现

目录

  • 前言
  • 单链表
    • 1.1 单链表的定义
    • 1.2单链表代码实现
      • 1.2.1 头文件
      • 1.2.2 函数实现文件
      • 1.2.3 测试文件
      • 1.2.4 野指针问题
  • 总结

前言

本文介绍单链表,主要是创销、增删改查代码实现。
注:文章中函数命名采取STL库。

单链表

1.1 单链表的定义

单链表是链线性表的一种,线性表是一种逻辑结构,根据不同的物理(存储)结构即顺序存储和链式存储,分为顺序表和链表。
链表顾名思义,像链条一样,将存储数据的结点一个一个串起来;每一个节点不仅存储数据,也存储指向下一个节点的指针。
不同于顺序表,其优点是,不需要一次开辟大量空间进行存储,根据需要随时动态申请内存。
缺点是,需要存储额外的指针。
而单链表,是每个节点只有一个指针
单链表在内存中的分布可以抽象为下图:
在这里插入图片描述

链表插入时是否需要对传入的地址进行断言?

  1. 如果传入的是单链表头指针,是不需要断言的,因为即使是空链表,也可以插入呀~
  2. 但如果是顺序表的话,是需要对传入的指针进行断言的,因为如下图,传入的结构体指针是有数据的,数组指针、size、capacity,如果传入的指针为NULL,说明该顺序表不存在!
    在这里插入图片描述

顺序表的代码实现

//顺序表的定义
#include <assert.h>
typedef int SLDataType;
#define INIT_CAPACITY 4
typedef struct SeqList
{SLDataType* a;int size;int capacity;
}SL;//顺序表的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos)ps->a[end + 1] = ps->a[end--];ps->a[pos] = x;ps->size++;
}

1.2单链表代码实现

单链表的创销、增删改查函数代码实现如下

1.2.1 头文件

SList.h

//将所有可能用到的库文件中的头文件在.h文件中声明
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SLTNode* next;
}SLTNode;//定义一个单链表//单链表的打印、插入、删除函数声明
void SLTPrint(SLTNode* phead);//打印
SLTNode* BuySLTNode(SLTDataType x);//动态申请一个节点,用于插入
void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLTPopFront(SLTNode** pphead);//头删
void SLTPopBack1(SLTNode** pphead);//尾删方式1
void SLTPopBack2(SLTNode** pphead);//尾删方式2
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTEraseAfter(SLTNode* pos);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTDestroy1(SLTNode* phead);//销毁链表
void SLTDestroy2(SLTNode** phead);//传二级指针销毁链表

1.2.2 函数实现文件

SList.c

//单链表的打印、插入、删除函数实现
#include "SList.h"
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}//封装的思想,头插、尾插都要申请节点,将其封装为函数,一方面调用方便;另一方面,方便维护,如果出了问题,在函数内部修复即可,如果没有封装,则每次使用这个功能的地方都要修复。
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc failed");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}//插入没有必要对顺序表是否为空进行讨论,因为与非空操作一致
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL)*pphead = newnode;else{//找尾节点SLTNode* tail = *pphead;//命名的可读性while (tail->next != NULL)tail = tail->next;tail->next = newnode;}
}//删除需要对链表为空的情况单独讨论,没有节点怎么删呀
//删除也要对只有一个节点情况进行讨论,因为第一个节点的内存被释放,与删除非第一个结点相比,要修改头指针,否则头指针成为野指针
//尾删方式1
void SLTPopBack1(SLTNode** pphead)
{assert(pphead);assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* tail = *pphead;SLTNode* prev = NULL;while (tail->next != NULL){prev = tail;//记录尾节点的前一个节点的位置tail = tail->next;}free(tail);tail = NULL;//VS2022语法较为严格if (prev == NULL)return;prev->next = NULL;}
}//尾删方式2
void SLTPopBack2(SLTNode** pphead)
{assert(pphead);assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* tail = *pphead;//VS2022语法较为严格if (tail == NULL || tail->next==NULL)return;//找到尾节点的前一个节点while (tail->next->next != NULL)tail = tail->next;free(tail->next);//释放尾节点所在内存tail->next = NULL;//将指向尾节点的指针置为空}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (*pphead == pos)SLTPushFront(pphead,x);else{SLTNode* prev = *pphead;while (prev->next!=pos)prev = prev->next;SLTNode* newnode = BuySLTNode(x);newnode->next = pos;prev->next = newnode;}
}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (*pphead == pos)SLTPopFront(pphead);else{SLTNode* prev = *pphead;while (prev->next != pos)prev = prev->next;prev->next = pos->next;free(pos);}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}void SLTDestroy1(SLTNode* phead)
{SLTNode* cur = phead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}
}void SLTDestroy2(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}*pphead = NULL;
}

1.2.3 测试文件

Test.c

#include "SList.h"
//单链表的打印、插入、删除函数测试
void TestList1()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushFront(&plist, i);SLTPrint(plist);SLTPopBack1(&plist);SLTPrint(plist);SLTPopBack2(&plist);SLTPrint(plist);
}//对头删函数进行测试
void TestList2()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i);SLTPopFront(&plist);SLTPrint(plist);
}
void TestList3()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i);for (int i = 0; i < 10; i++){SLTPopFront(&plist);SLTPrint(plist);}//SLTPopFront(&plist);
}//对尾删函数进行测试
void TestList4()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i);for (int i = 0; i < 10; i++){SLTPopBack1(&plist);SLTPrint(plist);}//SLTPopBack1(&plist);
}//对寻找、插入、删除、后插函数进行测试
void TestList5()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i+1);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 3);ret->data *= 5;SLTPrint(plist);//SLTInsert(&plist, ret, 30);SLTErase(&plist, ret);SLTPrint(plist);
}
void TestList6()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i+1);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 1);ret->data *= 5;SLTPrint(plist);//SLTInsert(&plist, ret, 30);//SLTErase(&plist, ret);//SLTInsertAfter(ret, 30);SLTEraseAfter(ret);ret = NULL;SLTPrint(plist);
}//对链表销毁函数进行测试
void TestList7()
{SLTNode* plist = NULL;for (int i = 0; i < 10; i++)SLTPushBack(&plist, i + 1);SLTPrint(plist);/*SLTDestroy1(plist);plist = NULL;*/  //传头指针进行销毁,要将头指针置为NULLSLTDestroy2(&plist);
}
int main()
{TestList7();return 0;
}

1.2.4 野指针问题

对于销毁链表操作,有一个经典错误,就是野指针,其实其他场景下也会出现,代码示例如下。首先我们要清楚free(cur)到底做了什么,就是将cur指向的原本动态开辟的内存空间的使用权还给操作系统,cur的值可能变,也可能不变,但是即使不变,也不能通过cur再去使用其指向的那块空间了。通过调试我们可以看到,虽然free前后plist的值没有改变(下图红色框),但是free后,plist指向区域的data是随机值(下图橙色框),并且后续节点不能被访问(下图蓝色框),在free(plist)后打开plist的next域显示如下图黄色框。

在这里插入图片描述
举个例子,这就像住酒店,指针就像房卡,显示房间号,可以找到房间,也就是内存区域,free就像退房,退房后还有可能通过房卡找到房间吗?当然可以,可是没有使用权。用一个临时变量存储cur,并free(cur),之后通过临时变量访问该地址,就像将房卡给别人,退房后还是不能使用该房间。

//错误范例1
void SLTDestroy2(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){free(cur);//此时cur已经是野指针了,下面不能在调用了cur = cur->next;}*pphead = NULL;
}
//错误范例2
void SLTDestroy2(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* tmp = cur->next;free(tmp);//此时tmp已经是野指针了,下面不能在调用了cur = tmp->next;}*pphead = NULL;
}

总结

写代码时几个注意的点:

  1. 指针不一定要断言,只有一定不为空的指针才需要断言,具体情况具体分析;
  2. 要有封装的思想,多次使用的功能封装为函数;
  3. 在实际应用中,头插、尾插不一定要调用函数;
  4. 代码有不同的实现逻辑和方式,注意区分差别;
  5. 野指针问题经常出现,注意规避;
  6. 出现问题进行调试,观察出错步骤,进行修改。

上面的代码即思想在做链表相关的题时很有用,希望大家可以动手写一写~

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

北京市大兴区启动乐享生活 寻味大兴 美食嘉年华 系列促销费活动

北京市大兴区启动乐享生活 寻味大兴 系列促销费活动 区商务局副局长 兰莉 致开幕辞 区餐饮行业协会会长 董志明 介绍活动内容 2024年9月30日&#xff0c;由大兴区商务局主办、大兴区餐饮行业协会承办&#xff0c;并得到高米店街道和大兴绿地缤纷城大力支持的“乐享生活 寻味大…

OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试

OceanBase—02&#xff08;入门篇——对于单副本单节点&#xff0c;由1个observer扩容为3个observer集群&#xff09;——之前的记录&#xff0c;有的问题未解决&#xff0c;新版未尝试 1、前言—安装单副本单节点集群1.1 docker安装OB 2、查看现有集群情况2.1 进入容器&#x…

SOMEIP_ETS_147: SD_Send_triggerEventUINT8_Eventgroup_2

测试目的&#xff1a; 验证DUT在Tester订阅事件组后&#xff0c;能够响应Tester触发的triggerEventUINT8方法&#xff0c;并将TestEventUINT8事件发送到订阅请求中端点选项指定的IP地址和端口。 描述 本测试用例旨在确保DUT能够正确处理事件组的订阅请求&#xff0c;并且在T…

VSOMEIP代码阅读整理(1) - 网卡状态监听

一. 概述 ​ 在routing进程所使用的配置文件中&#xff0c;存在如下配置项目&#xff1a; {"unicast" : "192.168.56.101",..."service-discovery" :{"enable" : "true","multicast" : "224.244.224.245&q…

在2核2G服务器安装部署MySQL数据库可以稳定运行吗?

阿里云2核2G服务器可以安装MySQL数据库吗&#xff1f;当然可以&#xff0c;并且可以稳定运行MySQL数据库&#xff0c;目前阿里云服务器网aliyunfuwuqi.com使用的就是阿里云2核2G服务器&#xff0c;在云服务器上安装MySQL数据库&#xff0c;可以稳定运行。 目前阿腾云用于运行M…

C++系列-继承补充

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 继承和友元 友元关系不能继承&#xff0c;父亲的朋友不能是你的朋友 比如在这个例子当中&#xff1a; class Student; class Person { public:friend void Display(const Per…

厦门网站设计的用户体验优化策略

厦门网站设计的用户体验优化策略 在信息化快速发展的今天&#xff0c;网站作为企业与用户沟通的重要桥梁&#xff0c;用户体验&#xff08;UX&#xff09;的优化显得尤为重要。尤其是在交通便利、旅游资源丰富的厦门&#xff0c;吸引了大量企业进驻。在这样竞争激烈的环境中&am…

netty之NettyServer字符串编码器

前言 netty通信就向一个流水channel管道&#xff0c;我们可以在管道的中间插入一些‘挡板’为我们服务。比如字符串的编码解码&#xff0c;在前面我们使用new StringDecoder(Charset.forName(“GBK”))进行字符串解码&#xff0c;这样我们在收取数据就不需要手动处理字节码。那…

linux文件编程_进程通信

1.进程间通信介绍 进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09;是指在不同进程之间传播或交换信息。 进程是操作系统的概念&#xff0c;每当我们执行一个程序时&#xff0c;对于操作系统来讲就创建了一个进程&#xff0c;在这个过程中&…

已解决:org.springframework.web.HttpMediaTypeNotAcceptableException

文章目录 写在前面问题描述报错原因分析&#xff1a; 解决思路解决办法1. 确保客户端请求的 Accept 头正确2. 修改 Controller 方法的 produces 参数3. 配置合适的消息转换器4. 检查 Spring 配置中的媒体类型5. 其他解决方案 总结 写在前面 在开发过程中&#xff0c;Spring 框…

C++容器之list基本使用

目录 前言 一、list的介绍&#xff1f; 二、使用 1.list的构造 2.list iterator的使用 3.list capacity &#x1f947; empty &#x1f947;size 4.list element access &#x1f947; front &#x1f947; back 5.list modifiers &#x1f947; push_front &#x1f947; po…

从零到一构建解释器-【1-基础概念】

文章目录 扫描器词法分析语法分析 静态分析中间代码优化代码生成运行时单遍编译器数遍历解释器转译器即使编译编译器与解释器 本教程参考【手搓解释器】 这里只是过一遍基本概念&#xff0c;后面会有涉及到具体解析 扫描器 词法分析 接受字符流忽略无意义符号&#xff0c;如…

【Git】一文看懂Git

Git 一、简介1. Git 与 SVN 区别1.1 Git 是分布式的&#xff0c;SVN 不是1.1.1 分布式版本控制系统Git1.1.2 集中式版本控制系统SVN 1.2 Git 把内容按元数据方式存储&#xff0c;而 SVN 是按文件1.3 Git 分支和 SVN 的分支不同1.4 Git 没有一个全局的版本号&#xff0c;而 SVN …

《Windows PE》3.2.4节表

节表由多个节表项&#xff08;IMAGE_SECTION_ HEADER&#xff09;组成&#xff0c;每个节表项&#xff08;40个字节&#xff09;记录了 PE中与某个特定的节有关的信息&#xff0c;如节的属性、节 的大小、在文件和内存中的起始位置等。节表中节的数量由字段IMAGE_FILE_HEADER. …

迷宫中的最短路径:如何用 BFS 找到最近出口【算法模板】

如何通过广度优先搜索&#xff08;BFS&#xff09;求解迷宫问题 在这篇文章中&#xff0c;我们将学习如何使用 广度优先搜索&#xff08;BFS&#xff09; 解决一个典型的迷宫问题&#xff0c;具体是从迷宫的一个入口出发&#xff0c;找到最近的出口。我们将一步步分析 BFS 是如…

超声波扫描显微镜SAM有什么作用?

知识星球里的学员问&#xff1a;在晶圆厂中很少见到超声波扫描显微镜&#xff0c;但是在封测厂中会经常用到&#xff0c;麻烦讲一下超声波扫描显微镜的原理与用途 什么是超声波扫描显微镜&#xff1f; 超声波扫描显微镜&#xff0c;英文名scanning acoustic microscope&#…

【论文阅读】Equivariant Multi-Modality Image Fusion(CVPR2024)

Equivariant Multi-Modality Image Fusion&#xff08;CVPR2024&#xff09; 现有方法存在的问题 由于现实中没有一种传感器可以同时捕捉所有模态的信息&#xff0c;因此缺乏真实的融合图像作为训练的参照标准&#xff0c;这对深度学习模型的训练带来了挑战。 基于生成对抗网…

2024 全新体验:国学心理 API 接口来袭

在当今快节奏的生活中&#xff0c;人们对于心理健康越来越重视。而研究发现&#xff0c;国学心理学乃至传统文化中的思想智慧&#xff0c;对于人们的心理健康有着独特且深远的影响。为了让更多人能够体验到国学心理的魅力&#xff0c;2024年全新推出的国学心理 API 接口&#x…

基于单片机的两轮直立平衡车的设计

本设计基于单片机设计的两轮自平衡小车&#xff0c;其中机械部分包括车体、车轮、直流电机、锂电池等部件。控制电路板采用STC12C5A60S2作为主控制器&#xff0c;采用6轴姿态传感器MPU6050测量小车倾角&#xff0c;采用TB6612FNG芯片驱动电机。通过模块化编程完成了平衡车系统软…

变电站红外检测数据集 1180张 变电站红外 标注voc yolo 13类

变电站红外检测数据集 1180张 变电站红外 标注voc yolo 13类 变电站红外检测数据集 名称 变电站红外检测数据集 (Substation Infrared Detection Dataset) 规模 图像数量&#xff1a;1185张图像。类别&#xff1a;13种设备类型。标注个数&#xff1a;2813个标注。 数据划分…