【C】初阶数据结构3 -- 单链表

news2025/1/15 15:15:34

  之前在顺序表那一篇文章中,提到顺序表具有的缺点,比如头插,头删时间复杂度为O(n),realloc增容有消耗等。而在链表中,这些问题将得到解决。所以在这一篇文章里,我们将会讲解链表的定义与性质,以及最简单的链表 -- 单链表的结构,以及基础方法的实现。


目录

1  链表

  1) 链表的概念

 2) 结点(节点)

3) 链表结点的结构

4) 链表的性质 

 2  单链表

1) 单链表的头插、尾插

2) 单链表的头删、尾删

3) 单链表在指定位置之前、之后插入数据 


重点一  链表

1  链表

  1) 链表的概念

链表是一种物理结构上非连续、非顺序的存储结构,数据元素的逻辑结构通过链表中的指针链接
次序实现的

一个链表(这里举例为单链表)的逻辑结构如图所示:

 2) 结点(节点)

  从上面那个链表的结构的图来看,结点就是指上面那个链表中每个独立存在的一个方块,所以一个结点里面包含了两部分内容,一个是这个结点里面存储的数据域,另一个是指向在一个结点的指针域,用来找到下一个结点,然后这里的plist是一个指向链表中第一个结点的指针,里面存储的是第一个结点的地址,可以通过plist来找到后面的结点,从结点的结构来看,知道了plist,也就是链表第一个结点的地址,我们就可以顺着结点中的指针,依次找到每一个结点

3) 链表结点的结构

  上面知道了结点的定义,结点的结构就很容易实现了,就是一个保存结点数据的数据域,还有一个指向下一个结点的指针:

#typedef int ListDataType;
typedef struct ListNode
{
  ListDataType data;//结点数据
  struct ListNode* next;//指向下一个结点的指针
}ListNode;

4) 链表的性质 

(1) 由于链表每个结点都是独立的一块空间,结点之间是通过指针连在一起,所以链表在物理结构
      上不一定是连续的,但是在逻辑结构上是连续的,所以是属于线性表的

(2) 每个结点是动态开辟的,而动态开辟的空间都是在内存里一个叫做堆区的空间开辟的

(3) 由于每个结点是独立申请的,所以每个结点的物理地址不一定连续

 

重点二  单链表

 2  单链表

  单链表是链表的一种,其结点的结构就是上面那个图那样,每个结点里面就只有一个数据域,还有指向下一个结点的指针域,所以对于单链表来说,只能通过前面的结点找到后面的结点,是不能通过后面的结点找到后面的结点的

  单链表的结构:

typedef int SLTDataType;//singal list node -- 单链表结点

typedef struct SListNode
{
  SLTDataType data;//结点的数据
  struct SListNode* next;//指向下一个结点的指针
}SLTNode;

  对于一个单链表来说,它的基础方法有链表的头插,头删,尾插,尾删,打印,查找,在指定位置之前插入数据,删除pos(这里的pos是一个节点的地址)结点,在指定位置之后插入数据,删除pos之后的节点, 销毁链表,同样,我们先讲解每一种方法的画图实现,之后再附上代码。


1) 单链表的头插、尾插

  既然是插入结点,那么就肯定会需要新开辟一个结点,而且只要插入结点就需要开辟新结点,所以可以把开辟新结点写成一个函数(buyNode),开辟新结点的功能也很容易实现,只要用malloc函数动态开辟出一个结点大小的空间,然后再让结点的data值赋值为想要开辟新结点的值,然后再让next指针为NULL,就开辟出了一个新结点。

  链表的头插的实现如图

但是我们在实现头插的过程中,发现phead(形参,链表的头节点)会改变指向,会从指向旧链表的头节点改为指向插入的新结点,要想让传过来的实参头插后链表新的首节点,这里必须改变实参(也就是原来链表头节点指针的指向,形参改变影响实参),所以这里必须传指针变量的地址,也就是二级指针

  尾插的实现也类似,如图

在第二步里面,需要通过头节点来找到尾节点,通过头节点找到尾节点的实现思路为:先创建一个节点指针变量 ptail 指向首结点,之后再让循环判断 ptail 的next指针为不为NULL,如果不为NULL,那就让ptail走向下一个结点(注意,这里判断条件不能是ptail为不为NULL,如何是这个判断条件,那么ptail就会走向NULL,后面势必会造成对NULL指针的解引用),代码为:

SLTNode* ptail = phead;
while (ptail->next != NULL)
{
  ptail = ptail->next;
}

  那么尾插是否需要传二级指针呢?

  其实不管是头插还是尾插,都需要考虑一种特殊情形,那就是链表为空的情况(就是指向头结点的指针为NULL),如果链表为空,上面的两种情况都会造成对空指针的解引用,所以如果链表为空,那就让指向头节点的指针指向新开辟的结点newnode就可以了,但是这里改变的只是形参,所以如果想要让实参也变为指向newnode的指针,就必须传指针的地址,也就是二级指针了。(这里如果看不懂的话,可以看下面的代码理解一下)。

  所以上面找尾节点的代码应该为:

//传过来的二级指针为SLTNode** pphead,指向头节点的指针为*pphead
SLTNode* ptail = *pphead;
while (ptail != NULL)
{
  ptail = ptail->next;
}

2) 单链表的头删、尾删

  既然是删除结点,那么就需要判断链表为不为空,也就是判断传过来的指向首节点的指针为不为NULL,如果为NULL,就不能删除结点。

  头删的过程如图:

头删也需要注意传递的参数也需要是二级指针(因为形参的改变要影响实参) 。

  尾删的过程如下:

寻找prev和ptail的代码如下:

//传过来的为二级指针**pphead
SLTNode* ptail = *pphead, *prev = NULL;
while (ptail->next != NULL)
{
  prev = ptail;
  ptail = ptail->next;
}

   头删和尾删的时候需要注意一种极端情况,那就是只有一个结点的情况((*pphead)->next == NULL),如果只有一个结点,那么prev指针就是NULL指针,那么就会出现NULL指针的解引用,但是对于头删的代码来说,就不会出现这种情况(可以结合下面的代码)。所以尾删只有一个结点的情况需要特殊处理,其实删除只有一个结点的链表尾删和头删都是一样的,所以链表只有一个结点时,尾删也就是头删


3) 单链表在指定位置之前、之后插入数据 

  同样的,既然是插入数据,就需要和头插、尾插一样开辟新的节点(这里与头插、尾插开辟新节点逻辑相同,不再赘述)。这里的位置指的是一个节点,这里叫做pos节点,那么实现逻辑如下图所示:

在pos节点之前插入数据

找到pos节点之前的prev节点的逻辑类似于尾插中找到尾节点,具体逻辑为:先将prev节点指定为首节点,如果prev的next指针不指向pos节点,那就让prev走到它的next指针,写成代码为:

//*pphead为首节点
SLTNode* prev = *pphead;

while(prev->next != pos)
{
  prev = prev->next;
}

   我们来考虑一下特殊情况,那就是如果pos节点就是首节点的话(pos == *pphead),如果继续按照这个逻辑的话,prev节点是首届点,而pos节点也是首节点,那么prev的next指针始终不等于pos,所以最终会造成对NULL指针的解引用,所以如果pos是首节点,这时候需要特殊处理一下,仅需要调用一下头插的函数就可以了(可以结合下面代码思考一下)

在pos节点之后插入数据

   我们再来考虑一下特殊情况,就是如果pos是尾节点的情况,如果再按这个逻辑的话,会发现是没有问题的,不会出现对于NULL指针的解引用情况。但是有一个点需要注意,就是在第二步里面,一定要先让newnode的next指针先改变,再让pos的next指针改变,因为pos的next指针如果先改变的话,那就找不到pos的下一个节点了,且newnode会指向自己的。

  还有就是我们可以看到在pos节点之后插入数据,实现逻辑是没有用到首节点的,所以对于该函数实现的时候,只需要传pos参数和插入的数据 X 两个参数就可以了

  那么在指定位置之前和之后插入数据是否需要判断单链表是否为空呢?实际上是不需要的,因为在pos节点之前、之后插入数据就保证了该单链表是非空的,所以只需要判断pos节点是否为NULL就可以了。


4) 删除指定位置(pos)节点,删除指定位置(pos)之后的节点

  既然是删除节点,那就需要判断链表是否为空(判断逻辑与头删、尾删相同,不再赘述),这两个方法实现逻辑如下:

删除pos节点

  我们来考虑一下特殊情况,那就是如果pos节点是首节点、尾节点时。如果pos是尾节点,根据这个逻辑来实现的话,发现是没有问题的;但是如果pos是首节点,那么根据我们之前找prev节点的逻辑,会出现对NULL指针的解引用的,所以pos是首节点的情况需要特殊处理一下,只需要调用一下头删的代码就可以了。

删除pos节点之后的节点

  该方法的实现时有一个地方需要注意,就是要删除的节点,也就是pos的下一个节点不能为NULL,且该方法的实现只需要传递一个pos参数就可以了。


5) 打印、查找、销毁

  打印函数和查找函数的逻辑比较好实现,只要遍历整个单链表,然后打印每个节点的值或者比较节点的值要和查找的值相不相等就可以了,如果查找到了就返回对应节点的地址,如果没有查找到,就返回NULL。

  销毁链表也很简单,只要从首节点开始遍历,然后销毁每一个节点就可以了,只不过需要注意在释放当前节点之前,需要先把下一个节点的地址存起来,避免找不到下一个节点了。


6) 代码实现 

SList.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;//singal list node -- 单链表结点

typedef struct SListNode
{
  SLTDataType data;//结点的数据
  struct SListNode* next;//指向下一个结点的指针
}SLTNode;

//打印
void SLTPrint(SLTNode* phead);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//头删
void SLTPopFront(SLTNode** pphead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTDataType** pphead, SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SLTDestroy(SLTNode** pphead);

SList.c文件:

#include"SList.h"


//开辟结点的函数
SLTNode* buyNode(SLTDataType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  //开辟失败
  if (newnode == NULL)
  {
    perror("malloc fail!\n");//打印错误信息
    exit(1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}

//打印
void SLTPrint(SLTNode* phead)
{
  SLTNode* pcur = phead;
  while (pcur)
  {
    printf("%d->", pcur->data);
    pcur = pcur->next;
  }
  printf("NULL\n");
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
  assert(pphead);//传过来的不能是空指针
  if ((*pphead) == NULL)
  {
    *pphead = buyNode(x);
  }
  else
  {
    SLTNode* newnode = buyNode(x);
    //让新开辟的结点next指针指向链表第一个结点
    newnode->next = *pphead;
    //再让指向第一个结点的指针指向新开辟的结点
    *pphead = newnode;
  }
}

//头删
void SLTPopFront(SLTNode** pphead)
{
  //传过来的指针不能为空,并且链表不能为空
  assert(pphead && *pphead);
  //得先让首结点指向首结点的下一个结点,要不然会成野指针
  SLTNode* next = (*pphead)->next;
  free(*pphead);
  *pphead = next;
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
  assert(pphead);
  //如果是空链表的话,就让指针指向新的结点
  if (*pphead == NULL)
  {
    *pphead = buyNode(x);
  }
  //链表不为空,得先找到尾节点
  else
  {
    SLTNode* ptail = *pphead;
    while (ptail->next)
    {
      ptail = ptail->next;
    }
    //ptail为尾结点
    SLTNode* newnode = buyNode(x);
    ptail->next = newnode;
  }
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
  //传过来的地址不能为NULL并且链表为空
  assert(pphead && *pphead);
  //如果链表中只有一个结点,那就是头删除
  if ((*pphead)->next == NULL)
  {
    SLTPopFront(pphead);
  }
  else
  {
    //得先找到尾节点和尾节点的前一个结点
    SLTNode* ptail = *pphead;
    SLTNode* prev = NULL;
    while (ptail->next)
    {
      prev = ptail;
      ptail = ptail->next;
    }
    prev->next = NULL;
    free(ptail);
    ptail = NULL;
  }
}
  
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
  assert(phead);
  SLTNode* pcur = phead;
  while (pcur)
  {
    if (pcur->data == x)
      return pcur;
    pcur = pcur->next;
  }
  return NULL;
}

//在指定位置之前插入数据
void SLTInsert(SLTDataType** pphead, SLTNode* pos, SLTDataType x)
{
  assert(pphead);
  assert(pos);
  //如果pos节点就是首节点
  if (pos == *pphead)
  {
    //头插
    SLTPushFront(pphead, x);
  }
  //如果pos不是首节点
  else
  {
    SLTNode* prev = *pphead;
    SLTNode* newnode = buyNode(x);
    while (prev->next != pos)
    {
      prev = prev->next;
    }
    prev->next = newnode;
    newnode->next = pos;
  }
}

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead && *pphead);
  assert(pos);
  //如果pos节点是首节点
  if (pos == *pphead)
  {
    SLTPopFront(pphead);
  }
  else
  {
    SLTNode* prev = *pphead;
   while (prev->next != pos)
    {
      prev = prev->next;
    }
    prev->next = pos->next;
    free(pos);
    pos = NULL;
  }
  
}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
  assert(pos);
  SLTNode* newnode = buyNode(x);
  //一定得先让newnode->next = pos->next
  //再让pos->next = newnode
  newnode->next = pos->next;
  pos->next = newnode;
}

//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
  //pos的下一个结点不能为空,否则不能删除
  assert(pos && pos->next);
  SLTNode* del = pos->next;
  pos->next = del->next;
  free(del);
  del = NULL;
}

//销毁链表
void SLTDestroy(SLTNode** pphead)
{
  assert(pphead && *pphead);
  SLTNode* pcur = *pphead;
  //先存下一个结点
  SLTNode* next = pcur->next;
  while (pcur)
  {
    next = pcur->next;
    free(pcur);
    pcur = next;
  }
  *pphead = NULL;
}

test.c:

#include"SList.h"

void Test()
{
  SLTNode* plist = NULL;
  测试头插
 /*SLTPushFront(&plist, 1);
  SLTPushFront(&plist, 2);
  SLTPushFront(&plist, 3);
  SLTPushFront(&plist, 4);
  SLTPrint(plist);*/
  //测试头删
  //SLTPopFront(&plist);
  /*SLTPopFront(&plist);
  SLTPopFront(&plist);
  SLTPopFront(&plist);*/
  //SLTPopFront(&plist);
  //测试尾插
  SLTPushBack(&plist, 1);
  SLTPushBack(&plist, 2);
  SLTPushBack(&plist, 3);
  SLTPushBack(&plist, 4);
  SLTPrint(plist);

  //测试尾删
  /*SLTPopBack(&plist);
  SLTPopBack(&plist);
  SLTPopBack(&plist);
  SLTPopBack(&plist);*/
  //SLTPopBack(&plist);
  //测试查找
 /* SLTNode* pfind = SLTFind(plist, 10);
  if (pfind == NULL)
  {
    printf("没找到\n");
  }
  else
  {
    printf("找到了\n");
  }*/

  SLTNode* pfind = SLTFind(plist, 1);
  if (pfind == NULL)
  {
    printf("没找到\n");
  }
  else
  {
    printf("找到了\n");
  }

  //测试在指定位置之前插入数据
  //可以测试在1,2,4节点之前插入数据
  //SLTInsert(&plist, pfind, 5);
 
  //测试在指定节点之后插入数据
  //可以测试在1,2,4节点之后插入数据
  //SLTInsertAfter(pfind, 5);

  //测试删除pos节点
  //可以测试删除1,2,4节点
  //SLTErase(&plist, pfind);

  //测试删除pos之后的节点
  //可以测试删除1,2,3之后的节点
  //SLTEraseAfter(pfind);

  //可以使用调试测试销毁是否成功
  //如果销毁成功,下面打印结果就是NULL
  SLTDestroy(&plist);

  //打印
  SLTPrint(plist);
}


int main()
{
  Test();

  return 0;
}

  单链表是链表里面结构最简单的一种链表,链表共分为8类(在双向链表里面会讲解),但是由于结构最简单,所以遍历只能从头开始遍历且在方法实现中涉及的细节点很多。同时,单链表中为什么传二级指针也需要自己理解。总之,如果刚开始学习单链表,并不会很容易理解;但是,一旦理解了,相信会对指针的理解更加深刻。

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

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

相关文章

Python----Python高级(函数基础,形参和实参,参数传递,全局变量和局部变量,匿名函数,递归函数,eval()函数,LEGB规则)

一、函数基础 1.1、函数的用法和底层分析 函数是可重用的程序代码块。 函数的作用&#xff0c;不仅可以实现代码的复用&#xff0c;更能实现代码的一致性。一致性指的是&#xff0c;只要修改函数的代码&#xff0c;则所有调用该函数的地方都能得到体现。 在编写函数时&#xf…

《leetcode-runner》如何手搓一个debug调试器——架构

本文主要聚焦leetcode-runner对于debug功能的整体设计&#xff0c;并讲述设计原因以及存在的难点 设计引入 让我们来思考一下&#xff0c;一个最简单的调试器需要哪些内容 首先&#xff0c;它能够接受用户的输入 其次&#xff0c;它能够读懂用户想让调试器干嘛&#xff0c;…

食堂采购系统源码:基于PHP的校园食堂供应链管理平台开发全解析

传统的食堂采购管理普遍存在信息不透明、流程繁琐、效率低下等问题&#xff0c;这使得开发一款高效、智能的食堂采购系统变得尤为重要。本篇文章&#xff0c;笔者将详细解析基于PHP开发的校园食堂供应链管理平台&#xff0c;从功能设计、系统架构到技术实现&#xff0c;全方位剖…

WEB 攻防-通用漏-XSS 跨站脚本攻击-反射型/存储型/DOMBEEF-XSS

XSS跨站脚本攻击技术&#xff08;一&#xff09; XSS的定义 XSS攻击&#xff0c;全称为跨站脚本攻击&#xff0c;是指攻击者通过在网页中插入恶意脚本代码&#xff0c;当用户浏览该网页时&#xff0c;恶意脚本会被执行&#xff0c;从而达到攻击目的的一种安全漏洞。这些恶意脚…

重生之我在21世纪学C++—函数与递归

一、函数是什么&#xff1f; 相信我们第一次接触函数是在学习数学的时候&#xff0c;比如&#xff1a;一次函数 y kx b &#xff0c;k 和 b 都是常数&#xff0c;给一个任意的 x &#xff0c;就会得到一个 y 值。 其实在 C 语言中就引入了函数&#xff08;function&#xf…

Mac——Cpolar内网穿透实战

摘要 本文介绍了在Mac系统上实现内网穿透的方法&#xff0c;通过打开远程登录、局域网内测试SSH远程连接&#xff0c;以及利用cpolar工具实现公网SSH远程连接MacOS的步骤。包括安装配置homebrew、安装cpolar服务、获取SSH隧道公网地址及测试公网连接等关键环节。 1. MacOS打开…

Ubuntu中双击自动运行shell脚本

方法1: 修改文件双击反应 参考: https://blog.csdn.net/miffywm/article/details/103382405 chmod x test.sh鼠标选中待执行文件&#xff0c;在窗口左上角edit菜单中选择preference设计双击执行快捷键&#xff0c;如下图&#xff1a; 方法2: 设置一个应用 参考: https://blo…

力扣 全排列

回溯经典例题。 题目 通过回溯生成所有可能的排列。每次递归时&#xff0c;选择一个数字&#xff0c;直到选满所有数字&#xff0c;然后记录当前排列&#xff0c;回到上层时移除最后选的数字并继续选择其他未选的数字。每次递归时&#xff0c;在 path 中添加一个新的数字&…

arcgis提取不规则栅格数据的矢量边界

效果 1、准备数据 栅格数据:dem或者dsm 2、栅格重分类 分成两类即可 3、新建线面图层 在目录下选择预先准备好的文件夹,点击右键,选择“新建”→“Shapefile”,新建一个Shapefile文件。 在弹出的“新建Shapefile”对话框内“名称”命名为“折线”,“要素类型”选…

【DB-GPT】开启数据库交互新篇章的技术探索与实践

一、引言&#xff1a;AI原生数据应用开发的挑战与机遇 在数字化转型的浪潮中&#xff0c;企业对于智能化应用的需求日益增长。然而&#xff0c;传统的数据应用开发方式面临着诸多挑战&#xff0c;如技术栈复杂、开发周期长、成本高昂、难以维护等。这些问题限制了智能化应用的…

LVGL移植高通点阵字库GT30L24A3W

字库芯片: GT30L24A3W MCU:STM32F429 LVGL版本:V8.4 一、实现gt_read_data() 和 r_dat_bat() 请参考下面视频 如何在32位MCU上使用高通点阵字库_哔哩哔哩_bilibili 高通字库使用教程(1)硬件链接与注意事项部分_哔哩哔哩_bilibili 高通字库使用教程(2)SPI底层函数使用_哔哩…

一键掌握多平台短视频矩阵营销/源码部署

短视频矩阵系统的介绍与应用 随着数字化营销策略的不断演进&#xff0c;传统的短视频矩阵操作方法可能已显陈旧。为此&#xff0c;一款全新的短视频矩阵系统应运而生&#xff0c;它通过整合多个社交媒体账户、创建多样化的任务、运用先进的智能视频编辑工具、实现多平台内容的…

MySQL(高级特性篇) 06 章——索引的数据结构

一、为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构&#xff0c;就好比一本教科书的目录部分&#xff0c;通过目录找到对应文章的页码&#xff0c;便可快速定位到需要的文章。MySQL中也是一样的道理&#xff0c;进行数据查找时&#xff0c;首先查看查询条件…

源码安装httpd2.4

1、下载 wget https://archive.apache.org/dist/httpd/httpd-2.4.54.tar.gz 2.解压下载压缩包 tar -zxvf httpd-2.4.54.tar.gz cd httpd-2.4.54 3、安装httpd所需要的依赖 yum groupinstall "Development Tools" -y 4.配置httpd ./configure --prefix/usr/local/htt…

【算法学习】——整数划分问题详解(动态规划)

&#x1f9ee;整数划分问题是一个较为常见的算法题&#xff0c;很多问题从整数划分这里出发&#xff0c;进行包装&#xff0c;形成新的题目&#xff0c;所以完全理解整数划分的解决思路对于之后的进一步学习算法是很有帮助的。 「整数划分」通常使用「动态规划」解决&#xff0…

文件与IO流:一

一些常识 硬盘特点 擅长顺序读&#xff0c;不擅长随机读&#xff0c;尤其是机械硬盘。 随机读例如某个目录中的所有小文件的复制&#xff0c;顺序读是某个大文件的整体复制。 windows的文件系统是按照“树形结构”来组织文件。 路径的风格 1.绝对路径&#xff1a;从根节点…

计算机网络 (42)远程终端协议TELNET

前言 Telnet&#xff08;Telecommunication Network Protocol&#xff09;是一种网络协议&#xff0c;属于TCP/IP协议族&#xff0c;主要用于提供远程登录服务。 一、概述 Telnet协议是一种远程终端协议&#xff0c;它允许用户通过终端仿真器连接到远程主机&#xff0c;并在远程…

WPF系列十二:图形控件CombinedGeometry

简介 CombinedGeometry 是 WPF (Windows Presentation Foundation) 中的一个几何对象&#xff0c;用于将两个几何图形组合成一个新的几何图形。它允许你通过不同的组合模式&#xff08;如相交、并集、差集或异或&#xff09;来创建复杂的形状。常与 Path 控件一起使用来绘制组…

《计算机网络》课后探研题书面报告_网际校验和算法

网际校验和算法 摘 要 本文旨在研究和实现网际校验和&#xff08;Internet Checksum&#xff09;算法。通过阅读《RFC 1071》文档理解该算法的工作原理&#xff0c;并使用编程语言实现网际校验和的计算过程。本项目将对不同类型的网络报文&#xff08;包括ICMP、TCP、UDP等&a…

业务幂等性技术架构体系之接口幂等深入剖析

在实际应用中&#xff0c;由于网络不稳定、系统延迟等原因&#xff0c;客户端可能会重复发送相同的请求。如果这些重复请求都被服务器处理并执行&#xff0c;就可能导致意想不到的问题&#xff0c;比如重复扣款、多次下单或者数据不一致等。 这就是为什么我们需要接口幂等性。…