【数据结构】手把手教你单链表(c语言)(附源码)

🌟🌟作者主页:ephemerals__

🌟🌟所属专栏:数据结构

目录

前言

1.单链表的概念与结构

2.单链表的结构定义

3.单链表的实现

3.1 单链表的方法声明

3.2 单链表方法实现

3.2.1 打印链表

3.2.2 创建新节点

3.2.3 尾插

3.2.4 头插

3.2.5 尾删

3.2.6 头删

3.2.7 查找

3.2.8 指定位置之前插入

3.2.9 指定位置之后插入

3.2.10 删除指定位置的节点

3.2.11 销毁链表

4.程序全部代码

总结


前言

        之前我们学习了顺序表,基于顺序表的结构和实现方式,它有以下缺陷

1.指定位置、头部的插入/删除的时间复杂度是O(N),效率并不是很高。

2.在增容时,需要申请额外的空间,当连续的空间不足时,就需要重新开辟空间并且拷贝数据,消耗较大。

3.由于增容操作每次都是以2倍的形式增长,所以势必会造成一定的空间浪费。

如何解决以上问题呢?这就需要我们学习一个新的数据结构:单链表

1.单链表的概念与结构

链表的概念:链表是一种数据内存地址不连续、但是逻辑顺序连续的数据结构。它的逻辑顺序由链表中节点的指针相连接。

节点:由两部分组成:存储数据元素的部分称之为“数据域”,存放其他节点地址的部分称之为“指针域”。每一个数据元素存放于一个“节点”中。

单链表,也叫做单向链表,它的节点的指针域中存放的是下一个节点的地址。这样节点与节点之间互相连接,就像链条一样将数据串联起来。

单链表的结构如图:

可以看到,单链表就像火车一样,而每一个节点就相当于是一节车厢,它们之间用指针串联在一起。注意:单链表只能做到由前一个节点找到后一个几点,无法逆转;最后一个节点的指针域为空指针。

2.单链表的结构定义

        我们在定义单链表的结构时,定义的是它的节点的结构。代码如下:

typedef int SLTDataType;//定义单链表的节点
typedef struct SListNode
{SLTDataType data;//数据域struct SListNode* next;//指针域
}SLTNode;

可以看到,它的指针域是指向自己本身类型的指针,这种定义方式也叫做结构体的自引用。它可以使得该节点能够存放一个相同类型节点的地址,并且进行访问操作

3.单链表的实现

3.1 单链表的方法声明

        单链表的一些常用方法的声明如下:

//打印链表
void SLTPrint(SLTNode* phead);//创建新节点
SLTNode* SLTBuyNode(SLTDataType n);//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n);//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n);//尾删
void SLTPopBack(SLTNode** pphead);//头删
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n);//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n);//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n);//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//销毁链表
void SLTDestroy(SLTNode** pphead);

接下来,我们尝试逐一实现以上方法。

3.2 单链表方法实现

3.2.1 打印链表

        打印链表时,我们需要定义一个指针,通过它遍历链表并访问它的数据元素:

//打印链表
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//定义指针指向头节点while (cur != NULL)//最后一个节点的next为空,cur等于空则说明遍历结束{printf("%d ", cur->data);//访问数据并打印cur = cur->next;//对cur解引用拿到下一个节点的地址,然后赋值给cur,cur就指向了下一个节点}printf("\n");
}

这里我们需要注意理解语句“cur = cur->next”,由于next存放的是下一个节点的地址,所以将其赋值给cur,cur就指向了下一个节点,循环往复,就达到了遍历的效果。

3.2.2 创建新节点

        在我们进行元素插入操作时,往往要将数据存放在一个节点当中,然后将这个节点插入链表。所以我们将创建节点的操作封装成一个函数:

//创建新节点
SLTNode* SLTBuyNode(SLTDataType n)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//动态开辟一个节点大小的内存if (newnode == NULL)//内存开辟失败,则直接退出程序{perror("malloc");exit(1);}newnode->data = n;//将数据赋值给节点的数据域newnode->next = NULL;//为了确保链表末尾为空指针,所以创建的所有节点默认next为空return newnode;//将节点返回
}

3.2.3 尾插

        接下来我们学习尾插操作。既然要在链表尾部插入数据,那么就需要我们顺着链表的头节点找到尾部的节点,然后将其指针域指向我们的新节点就好。代码如下:

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n)
{assert(pphead);SLTNode* newnode = SLTBuyNode(n);//创建新节点if (*pphead == NULL)//头指针为空说明链表为空{//链表为空,此时插入第一个元素,需要将头指针指向新节点,//而在函数内修改头指针就要传入头指针的地址,也就是二级指针*pphead = newnode;}else//链表不为空的情况{SLTNode* cur = *pphead;while (cur->next != NULL)//从头节点开始,循环遍历找到最后一个节点{cur = cur->next;}cur->next = newnode;//将新节点的地址赋值给最后一个节点的指针域}
}

这里需要注意:当链表为空时,如果我们进行循环遍历,就会发生对空指针解引用的错误,所以直接使头指针指向新节点就好。由于要在函数体内改变参数的值,并且参数是一个一级指针变量,所以要传入一级指针的地址,也就是二级指针。

3.2.4 头插

        对于头插操作,我们需要将新节点的next指向原来的第一个节点,然后将头指针指向新节点。

我们画图表示一下:

代码实现:

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n)
{assert(pphead);//确保传入的不是空指针SLTNode* newnode = SLTBuyNode(n);//创建新节点newnode->next = *pphead;//使新节点的next指针指向原来的第一个节点*pphead = newnode;//头指针指向新节点
}

注意:最后两句代码的顺序不能颠倒,因为如果先让头指针指向新节点,原来的链表的地址就会丢失,无法访问到了。

3.2.5 尾删

        进行尾删操作时,我们也需要遍历链表,找到链表的末尾并释放内存。实际操作要做一些特殊情况和细节的处理:

//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//确保传入的不是空指针并且链表不为空if ((*pphead)->next = NULL)//链表只有一个节点的情况{free((*pphead)->next);//释放该节点的空间*pphead == NULL;//改变了头节点的值,所以也要传二级指针}else//节点大于1的情况{SLTNode* prev = *pphead;while (prev->next->next != NULL)//循环遍历,使prev指向倒数第二个节点{prev = prev->next;}free(prev->next);//释放最后一个节点的空间prev->next = NULL;//将此时的最后一个节点的next制为空}
}

3.2.6 头删

        对于头删操作,我们需要记录第二个节点,然后再将第一个节点释放,最后使头指针指向记录的节点即可。当链表只有一个节点时,我们记录的就是NULL,最后将NULL赋值给头指针也合情合理。无需分类讨论。

//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;//保存第二个节点的地址/空指针(只有一个节点时)free(*pphead);//释放第一个节点的空间*pphead = next;//让头指针指向刚才保存的节点/空指针,也要传二级指针
}

3.2.7 查找

        查找操作十分简单,只需要遍历链表,如果有匹配的节点,将其地址返回即可。

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n)
{SLTNode* cur = phead;while (cur != NULL)//遍历链表的所有节点{if (cur->data == n)//匹配成功,返回该节点的地址{return cur;}cur = cur->next;}return NULL;//没有找到,返回空指针
}

3.2.8 指定位置之前插入

        进行指定位置之前插入时,要进行分类讨论:如果指定位置是头节点,则进行头插;其他情况遍历找到该节点的前驱节点prev,然后进行插入操作:

代码实现:

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n)
{assert(pphead && pos);SLTNode* newnode = SLTBuyNode(n);if (*pphead == pos)//指定位置是头节点的情况{//进行头插newnode->next = *pphead;*pphead = newnode;}else{SLTNode* prev = *pphead;while (prev->next != pos)//遍历找到pos节点的前驱节点{prev = prev->next;}newnode->next = pos;//新节点的next指针指向posprev->next = newnode;//前驱节点的next指针指向新节点}
}

3.2.9 指定位置之后插入

        对于指定位置之后插入元素,由于已经找到了前驱节点和后继节点,相比就没有那么麻烦了,只需要直接插入即可。

代码实现:

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n)
{assert(pos);//确保pos不为空指针SLTNode* newnode = SLTBuyNode(n);newnode->next = pos->next;//newnode的next指向后继节点pos->next = newnode;//前驱节点的next指向newnode
}

3.2.10 删除指定位置的节点

        对于指定位置的删除,我们需要分类讨论:如果此位置是头节点,就进行头删;否则就要找到其前驱节点和后继节点,然后进行删除操作。

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos && *pphead);//确保传入的不是空指针并且链表不为空if (pos == *pphead)//指定位置是头节点情况{//头删*pphead = pos->next;//先使头指针指向第二个节点free(pos);//释放掉pos节点}else{SLTNode* prev = *pphead;while (prev->next != pos)//循环遍历,找到pos的前驱节点{prev = prev->next;}prev->next = pos->next;//使前驱节点的next指针指向pos的后继节点free(pos);//释放掉pos节点}pos = NULL;//对野指针及时制空
}

3.2.11 销毁链表

        当我们使用完链表之后,应当及时释放掉链表的所有节点内存,这个过程称之为销毁链表。代码如下:

//销毁链表
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;//从头节点开始遍历while (cur != NULL){SLTNode* next = cur->next;//先记录下一个节点free(cur);//释放当前节点cur = next;//释放后,cur指向记录的节点}*pphead = NULL;//将头指针制空
}

4.程序全部代码

        程序全部代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;//定义单链表的节点
typedef struct SListNode
{SLTDataType data;//数据域struct SListNode* next;//指针域
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);//创建新节点
SLTNode* SLTBuyNode(SLTDataType n);//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n);//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n);//尾删
void SLTPopBack(SLTNode** pphead);//头删
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n);//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n);//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n);//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//销毁链表
void SLTDestroy(SLTNode** pphead);//打印链表
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//定义指针指向头节点while (cur != NULL)//最后一个节点的next为空,cur等于空则说明遍历结束{printf("%d ", cur->data);//访问数据并打印cur = cur->next;//对cur解引用拿到下一个节点的地址,然后赋值给cur,cur就指向了下一个节点}printf("\n");
}//创建新节点
SLTNode* SLTBuyNode(SLTDataType n)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//动态开辟一个节点大小的内存if (newnode == NULL)//内存开辟失败,则直接退出程序{perror("malloc");exit(1);}newnode->data = n;//将数据赋值给节点的数据域newnode->next = NULL;//为了确保链表末尾为空指针,所以创建的所有节点默认next为空return newnode;//将节点返回
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n)
{assert(pphead);SLTNode* newnode = SLTBuyNode(n);//创建新节点if (*pphead == NULL)//头指针为空说明链表为空{//链表为空,此时插入第一个元素,需要将头指针指向新节点,//而在函数内修改头指针就要传入头指针的地址,也就是二级指针*pphead = newnode;}else//链表不为空的情况{SLTNode* cur = *pphead;while (cur->next != NULL)//从头节点开始,循环遍历找到最后一个节点{cur = cur->next;}cur->next = newnode;//将新节点的地址赋值给最后一个节点的指针域}
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n)
{assert(pphead);//确保传入的不是空指针SLTNode* newnode = SLTBuyNode(n);//创建新节点newnode->next = *pphead;//使新节点的next指针指向原来的第一个节点*pphead = newnode;//头指针指向新节点
}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//确保传入的不是空指针并且链表不为空if ((*pphead)->next = NULL)//链表只有一个节点的情况{free((*pphead)->next);//释放该节点的空间*pphead == NULL;//改变了头节点的值,所以也要传二级指针}else//节点大于1的情况{SLTNode* prev = *pphead;while (prev->next->next != NULL)//循环遍历,使prev指向倒数第二个节点{prev = prev->next;}free(prev->next);//释放最后一个节点的空间prev->next = NULL;//将此时的最后一个节点的next制为空}
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;//保存第二个节点的地址/空指针(只有一个节点时)free(*pphead);//释放第一个节点的空间*pphead = next;//让头指针指向刚才保存的节点/空指针,也要传二级指针
}//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n)
{SLTNode* cur = phead;while (cur != NULL)//遍历链表的所有节点{if (cur->data == n)//匹配成功,返回该节点的地址{return cur;}cur = cur->next;}return NULL;//没有找到,返回空指针
}//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n)
{assert(pphead && pos);SLTNode* newnode = SLTBuyNode(n);if (*pphead == pos)//指定位置是头节点的情况{//进行头插newnode->next = *pphead;*pphead = newnode;}else{SLTNode* prev = *pphead;while (prev->next != pos)//遍历找到pos节点的前驱节点{prev = prev->next;}newnode->next = pos;//新节点的next指针指向posprev->next = newnode;//前驱节点的next指针指向新节点}
}//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n)
{assert(pos);//确保pos不为空指针SLTNode* newnode = SLTBuyNode(n);newnode->next = pos->next;//newnode的next指向后继节点pos->next = newnode;//前驱节点的next指向newnode
}//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos && *pphead);//确保传入的不是空指针并且链表不为空if (pos == *pphead)//指定位置是头节点情况{//头删*pphead = pos->next;//先使头指针指向第二个节点free(pos);//释放掉pos节点}else{SLTNode* prev = *pphead;while (prev->next != pos)//循环遍历,找到pos的前驱节点{prev = prev->next;}prev->next = pos->next;//使前驱节点的next指针指向pos的后继节点free(pos);//释放掉pos节点}pos = NULL;//对野指针及时制空
}//销毁链表
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;//从头节点开始遍历while (cur != NULL){SLTNode* next = cur->next;//先记录下一个节点free(cur);//释放当前节点cur = next;//释放后,cur指向记录的节点}*pphead = NULL;//将头指针制空
}

总结

        相比于顺序表,单链表采用了不同的物理结构,这使得头插、头删等操作的效率高于顺序表,并且插入一个数据就会创建一个节点,避免了空间的浪费

        学习单链表是数据结构中相当重要的一个环节,学会了单链表,才会更容易地理解其他数据结构的底层逻辑。我们在学习数据结构时,要注意勤画图,勤调试,才能让我们的编程能力更上一层楼。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

四、GD32 MCU 常见外设介绍(8)SPI 模块介绍

串行外设接口&#xff08;Serial Peripheral Interface&#xff0c;缩写为 SPI&#xff09; 提供了基于SPI 协议的数据发送和接收功能&#xff0c; 可以工作于主机或从机模式。 SPI 接口支持具有硬件 CRC 计算和校验的全双工和单工模式。 8.1.SPI 基础知识 SPI 物理层 SPI接…

基于dcm4chee搭建的PACS系统讲解(三)服务端使用Rest API获取study等数据

文章目录 DICOMWeb Support模块主要数据结构ER查询信息基本信息metadata信息统计信息 实践查询API及参数解析API返回的json数组定义VRObjectNodeObjectMapper解析显示指定tag并解析 后记 前期预研的PACS系统&#xff0c;近期要在项目中上线了。因为PACS系统采用无权限认证&…

qt--电子相册

一、项目要求 设计一个电子相册&#xff0c;点击上一张&#xff0c;切换到上一张图片&#xff0c;点击下一张&#xff0c;切换到下一张图片。 要求&#xff1a;图片的展示可以循环&#xff08;QList<QString>&#xff09; 要求&#xff1a;界面美观 二、项目代码 本质是通…

概率论--矩估计

目录 简介 矩估计法的基本步骤 延伸 矩估计法在大样本情况下的准确性和有效性如何评估&#xff1f; 在实际应用中&#xff0c;矩估计法的局限性有哪些具体例子&#xff1f; 如何处理矩估计法在某些情况下可能出现的不合理解或无法唯一确定参数的问题&#xff1f; …

vue3前端开发-小兔鲜项目-form表单的统一校验

vue3前端开发-小兔鲜项目-form表单的统一校验&#xff01;实际上&#xff0c;为了安全起见&#xff0c;用户输入的表单信息&#xff0c;要满足我们的业务需求&#xff0c;参数类型等种种标准之后&#xff0c;才会允许用户向服务器发送登录请求。为此&#xff0c;有必要进行一次…

gstreamer使用cairo实现视频OSD叠加

前言 gstreamer中视频叠加OSD有很多种方式&#xff0c;比如textoverlay添加文字&#xff0c;gdkpixbufoverlay添加图片&#xff0c;clockoverlay或timeoverlay插件显示时间&#xff0c;pango插件进行复杂文本渲染&#xff0c;使用cairo插件绘制图形或者文字。 今天使用最后一…

【React】详解样式控制:从基础到进阶应用的全面指南

文章目录 一、内联样式1. 什么是内联样式&#xff1f;2. 内联样式的定义3. 基本示例4. 动态内联样式 二、CSS模块1. 什么是CSS模块&#xff1f;2. CSS模块的定义3. 基本示例4. 动态应用样式 三、CSS-in-JS1. 什么是CSS-in-JS&#xff1f;2. styled-components的定义3. 基本示例…

ADS 使用教程(二十八)Working with FEM Mesh Field Data in ADS

ADS 使用教程&#xff08;二十七&#xff09;Getting Started with Full 3D FEM Simulation in ADS 在这一节中&#xff0c;我们来谈论一下在ADS中处理有限元法&#xff08;FEM&#xff09;网格和场数据的步骤。 在上一节中&#xff0c;我们进行了FEM仿真&#xff0c;并保存了…

在 MinIO 使用 SVE 将 ARM 带入人工智能数据基础设施领域

MinIO 性能如此之高的原因之一是&#xff0c;我们做了其他人不会或不能做的细粒度工作。从 SIMD 加速到 AVX-512 优化&#xff0c;我们已经完成了艰巨的任务。ARM CPU 架构的最新发展&#xff0c;特别是可扩展矢量扩展 &#xff08;SVE&#xff09;&#xff0c;为我们提供了比前…

《Cross-Modal Dynamic Transfer Learning for Multimodal Emotion Recognition》

Multi-modal系列论文研读目录 文章目录 Multi-modal系列论文研读目录1.ABSTRACT2.INDEX TERMS3.INTRODUCTION4.RELATED WORKSA. MULTIMODAL EMOTION RECOGNITION 多模态情感识别1) CONVENTIONAL FUSION METHODS 常规融合方法2) TRANSFORMER-BASED FUSION METHODS 基于变压器的融…

2023河南萌新联赛第(二)场 南阳理工学院

A. 国际旅行Ⅰ 题目&#xff1a; 思路&#xff1a; 因为题意上每个国家可以相互到达&#xff0c;所以只需要排序&#xff0c;输出第k小的值就可以了。 AC代码&#xff1a; #include<bits/stdc.h> #define int long long #define IOS ios::sync_with_stdio(0);cin.tie…

2024 微信小程序 学习笔记 第二天

1. WXML 模板语法 数据绑定 事件绑定 条件渲染 列表渲染 2. WXSS 模板样式 rpx 样式导入 全局和局部样式 3. 全局配置 window tabBar 配置tabBar案例 4. 网络数据请求 Get请求 Post 请求 加载时请求 5. 案例 -本地生活&#xff08;首页&#xff09; 导航栏 轮播图 九宫格效果…

webpack插件给所有的:src文件目录增加前缀

1.webpack4的版本写法 class AddPrefixPlugin {apply(compiler) {compiler.hooks.compilation.tap(AddPrefixPlugin, (compilation) > {HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(AddPrefixPlugin,(data, cb) > {// 使用正则表达式替换所有包含 /st…

无人机之环保监控篇

随着科技的不断进步&#xff0c;无人机作为一种创新的技术手段&#xff0c;在环保监控领域发挥着越来越重要的作用。 一、覆盖范围广 无人机能够轻松覆盖广阔的地理区域&#xff0c;无论是偏远的山区、广袤的森林还是大型的工业园区。相比传统的地面检测方式&#xff0c;其不…

关于promise的一些例题(运行步骤详细说明)

关于promise的一些例题(详细说明) 基本例题 // 直接运行 输出 1 2 const promise new Promise((resolve, reject) > {console.log(1);resolve();console.log(2); });// then后面放入微队列 promise.then(() > {console.log(3); });// 输出4 之后没有代码了所以运行为队…

17 敏捷开发—Scrum(2)

从上一篇 「16 敏捷开发实践&#xff08;1&#xff09;」中了解了Scrum是一个用于开发和维护复杂产品的框架&#xff0c;是一个增量的、迭代的开发过程。一般由多个Sprint&#xff08;迭代冲刺&#xff09;组成&#xff0c;每个Sprint长度一般为2-4周。下面全面介绍Scrumde 角色…

PostgreSQL 数据库 安装

1、官网下载 起源与发展&#xff1a;PostgreSQL最初起源于加州大学伯克利分校的Postgres项目&#xff0c;该项目始于1986年&#xff0c;并一直演进到1994年。在1995年&#xff0c;Postgres项目增加了SQL翻译程序&#xff0c;并更名为Postgres95。随后&#xff0c;在1996年&…

Linux:core文件无法生成排查步骤

1、进程的RLIMIT_CORE或RLIMIT_SIZE被设置为0。使用getrlimit和ulimit检查修改。 使用ulimit -a 命令检查是否开启core文件生成限制 如果发现-c后面的结果是0&#xff0c;就临时添加环境变量ulimit -c unlimited&#xff0c;之后在启动程序观察是否有core生成&#xff0c;如果…

Linux网络:传输层协议TCP(一)

目录 一、TCP协议的定义 二、确认应答机制ACK 三、序号、确认序号 四、超时重传机制 一、TCP协议的定义 TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传 输进行一个详细的控制; TCP 协议段格式 • 源/目的端口号: 表示数据…

解决Windows 11更新错误0x800f081f的详细指南

在尝试更新Windows 11时&#xff0c;用户可能会遇到各种错误代码&#xff0c;其中之一是0x800f081f。这个错误通常与Windows更新组件或系统文件的损坏有关。本文将提供解决这一特定错误的详细步骤&#xff0c;并解释可能的原因。 错误代码0x800f081f概述 错误代码0x800f081f指…