带头双向循环链表:一种高效的数据结构


  • 💓 博客主页:江池俊的博客
  • ⏩ 收录专栏:数据结构探索
  • 👉专栏推荐:✅cpolar ✅C语言进阶之路
  • 💻代码仓库:江池俊的代码仓库
  • 🔥编译环境:Visual Studio 2022
  • 🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

    • 一、带头循环双向链表的概念及结构
    • 二、使用带头双向循环链表的优势及注意事项
    • 三、带头双向链表的创建
      • ✨3.1 准备工作✨
      • ✨3.2 创建返回链表的头结点✨
      • ✨3.3 双向链表销毁✨
      • ✨3.4 双向链表打印✨
      • ✨3.5 双向链表尾插✨
      • ✨3.6 双向链表尾删✨
      • ✨3.7 双向链表头插✨
      • ✨3.8 双向链表头删✨
      • ✨3.9 双向链表查找✨
      • ✨3.10 双向链表在pos的前面进行插入✨
      • ✨3.11 双向链表删除pos位置的节点✨
    • 四、源代码
      • 🌅4.1 List.h 文件
      • 🌅4.2 List.c 文件
      • 🌅4.3 Test.c 文件
      • 🌅4.4 测试结果

双向循环链表是一种复杂的数据结构,它结合了双向链表和循环链表的优点。与单向链表相比,双向链表可以双向遍历,而循环链表则可以在尾部链接到头部,形成一个闭环。这种数据结构在某些应用场景下非常有用,比如事件处理、图形界面、内存管理等。

一、带头循环双向链表的概念及结构

双向循环链表是一种特殊类型的链表,它由一系列节点组成,每个节点包含一个数据域两个指针域。其中一个指针指向下一个节点,另一个指针指向前一个节点。在双向循环链表中,首节点的前一个节点是尾节点,尾节点的下一个节点是首节点,形成一个闭环

在这里插入图片描述

二、使用带头双向循环链表的优势及注意事项

【优势】:

  1. 高效遍历:由于带头双向循环链表可以双向遍历,因此可以在O(1)时间内访问任何节点。
  2. 内存高效:与双向链表相比,带头双向循环链表不需要额外的内存来存储头部节点。
  3. 插入和删除操作高效:在带头双向循环链表中插入和删除节点时,只需调整指针即可,无需移动大量数据。

【注意事项】:

  1. 初始化:在创建带头双向循环链表时,需要正确初始化所有节点的指针。
  2. 异常处理:在进行插入、删除等操作时,需要处理指针异常情况,如空指针或无效指针。
  3. 内存管理:在使用带头双向循环链表时,需要正确管理内存,避免内存泄漏或野指针。

三、带头双向链表的创建

✨3.1 准备工作✨

将代码分成三个文件可以提高代码的可读性、可维护性和可重用性。具体来说,三个文件可以分别关注以下方面:

  1. 配置文件:用于存储应用程序的配置信息,如数据库连接信息、应用程序名称、应用程序版本等。该文件可以在应用程序的整个生命周期内进行维护和管理,并且可以轻松地与其他开发人员共享。
  2. 模块文件:用于存储应用程序的各个模块,如用户管理、订单管理、产品管理等。每个模块都可以单独维护和测试,并且可以在不同的应用程序中重复使用。这有助于降低代码冗余和提高代码的可重用性。
  3. 入口文件:用于定义应用程序的入口,如路由、请求处理等。该文件可以控制应用程序的整个流程,并且可以轻松地与其他开发人员共享。

通过将代码分成三个文件,可以更好地组织代码结构,使其更加清晰和易于维护。同时,这也使得代码更易于测试和调试,并且可以更好地支持代码重构和优化。

在这里插入图片描述

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType data; //节点存储的数据元素struct ListNode* next; //指向前驱节点struct ListNode* prev; //指向后继节点
}ListNode; //双链表结构//几大接口
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

✨3.2 创建返回链表的头结点✨

动态申请一个节点作为双向链表的头节点。并将头节点的 prev 指向自己,next也指向自己,表明这是一个双向链表,且链表为空。

// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));if (head == NULL){perror("ListCreate --> malloc");return;}head->data = -1;head->prev = head;head->next = head;return head;
}

✨3.3 双向链表销毁✨

从链表的第二个节点开始,逐个释放链表中的节点,直到回到头节点并释放头节点的内存空间。这样做可以确保链表中的所有节点都被正确释放,防止内存泄漏。

// 双向链表销毁
void ListDestory(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = next; }free(pHead);printf("双链表销毁成功!\n");
}

✨3.4 双向链表打印✨

// 双向链表打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("哨兵位 <--> ");while (cur != pHead){printf("%d <--> ", cur->data);cur = cur->next;}printf("哨兵位\n");
}

✨3.5 双向链表尾插✨

在进行插入节点之前,无论是头插还是尾插都需要申请一个新的节点,于是可以把此步骤成一个函数,减少代码的冗余。

//申请一个节点
ListNode* CreateLTNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("CreateLTNode --> malloc");return;}newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}
  1. 首先,创建一个新的节点newnode,并将数据x存储在其中。
  2. newnodeprev指针指向当前链表的第一个节点pHead的前一个节点,即pHead->prev
  3. newnodenext指针指向当前链表的第一个节点pHead
  4. 将当前链表的第一个节点pHead的前一个节点的next指针指向新节点newnode
  5. 将当前链表的第一个节点pHeadprev指针指向新节点newnode

通过以上步骤,新节点被插入到双向链表的尾部,并且链表中的其他节点仍然保持其原始顺序和链接关系。这样做可以确保新节点被正确地添加到链表中,并且不会破坏链表的结构。

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreateLTNode(x);//pHead           pHead->prev  newnodenewnode->prev = pHead->prev; newnode->next = pHead;pHead->prev->next = newnode;pHead->prev = newnode;
}

✨3.6 双向链表尾删✨

  1. 首先,获取链表的最后一个节点tail,它应该是头节点pHead的前一个节点(即pHead->prev)。
  2. 接着,获取最后一个节点的前一个节点tailPrev
  3. 将头节点pHeadprev指针指向最后一个节点的前一个节点tailPrev,从而将最后一个节点从链表中间删除。
  4. 将最后一个节点的前一个节点的next指针指向头节点pHead,从而将头节点和最后一个节点连接起来。
  5. 最后,释放最后一个节点的内存空间。
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);assert(pHead->next!=pHead);//链表不能为空ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;// pHead     tailPrev tailpHead->prev = tailPrev;tailPrev->next = pHead;free(tail);
}

✨3.7 双向链表头插✨

  1. 首先,创建一个新的节点newnode,并将数据x存储在其中。
  2. 将新节点的prev指针指向当前链表的第一个节点pHead
  3. 将新节点的next指针指向当前链表的第一个节点的下一个节点,即pHead->next
  4. 将当前链表的第一个节点的next指针指向新节点newnode
  5. 将当前链表的第一个节点的下一个节点的prev指针指向新节点newnode

通过以上步骤,新节点被插入到双向链表的头部,并且链表中的其他节点仍然保持其原始顺序和链接关系。这样做可以确保新节点被正确地添加到链表中,并且不会破坏链表的结构。

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreateLTNode(x);ListNode* rear = pHead->next;pHead->next = newnode;newnode->prev = pHead;newnode->next = rear;rear->prev = newnode;
}

✨3.8 双向链表头删✨

  1. 首先,获取链表的第一个节点cur,它应该是头节点pHead的下一个节点(即pHead->next)。
  2. 将头节点的next指针指向第一个节点的下一个节点,从而将第一个节点从链表中间删除。
  3. 将第一个节点的下一个节点的prev指针指向头节点pHead,从而将头节点和第一个节点连接起来。
  4. 最后,释放第一个节点的内存空间。
// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);ListNode* cur = pHead->next;pHead->next = cur->next;cur->next->prev = pHead;free(cur);
}

✨3.9 双向链表查找✨

  1. 首先,从链表的第二个节点开始(即pHead->next),遍历链表的每个节点。
  2. 对于每个节点,检查其存储的数据是否与要查找的数据x相等。
  3. 如果找到了匹配的节点,则返回该节点。
  4. 如果遍历完整个链表都没有找到匹配的节点,则返回空指针(NULL)。
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;//如果没找到,返回空
}

✨3.10 双向链表在pos的前面进行插入✨

  1. 首先,创建一个新的节点newnode,并将数据x存储在其中。
  2. 获取要插入位置的前一个节点_prev
  3. 将前一个节点的next指针指向新节点newnode
  4. 将新节点的prev指针指向前一个节点_prev
  5. 将新节点的next指针指向当前节点pos
  6. 将当前节点的prev指针指向新节点newnode

通过以上步骤,新节点被插入到指定位置的前面,并且链表中的其他节点仍然保持其原始顺序和链接关系。这样做可以确保新节点被正确地添加到链表中,并且不会破坏链表的结构。

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = CreateLTNode(x);ListNode* _prev = pos->prev;// _prev newnode pos_prev->next = newnode;newnode->prev = _prev;newnode->next = pos;pos->prev = newnode;
}

✨3.11 双向链表删除pos位置的节点✨

  1. 首先,确保要删除的节点pos不是空指针。
  2. 获取要删除节点的前一个节点_prev和后一个节点rear
  3. 将前一个节点的next指针指向后一个节点,从而将要删除的节点从链表中间删除。
  4. 将后一个节点的prev指针指向前一个节点,从而将前一个节点和后一个节点连接起来。
  5. 释放要删除的节点的内存空间。
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* _prev = pos->prev;ListNode* rear = pos->next;_prev->next = rear;rear->prev = _prev;free(pos);
}

四、源代码

🌅4.1 List.h 文件

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType data; //节点存储的数据元素struct ListNode* next; //指向前驱节点struct ListNode* prev; //指向后继节点
}ListNode; //双链表结构// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

🌅4.2 List.c 文件

#define _CRT_SECURE_NO_WARNINGS 1#include "List.h"//申请一个节点
ListNode* CreateLTNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("CreateLTNode --> malloc");return;}newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));if (head == NULL){perror("ListCreate --> malloc");return;}head->data = -1;head->prev = head;head->next = head;return head;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = next; }free(pHead);printf("双链表销毁成功!\n");
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("哨兵位 <--> ");while (cur != pHead){printf("%d <--> ", cur->data);cur = cur->next;}printf("哨兵位\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreateLTNode(x);//pHead           pHead->prev  newnodenewnode->prev = pHead->prev; newnode->next = pHead;pHead->prev->next = newnode;pHead->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);assert(pHead->next!=pHead);//链表不能为空ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;// pHead     tailPrev tailpHead->prev = tailPrev;tailPrev->next = pHead;free(tail);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = CreateLTNode(x);ListNode* rear = pHead->next;pHead->next = newnode;newnode->prev = pHead;newnode->next = rear;rear->prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);ListNode* cur = pHead->next;pHead->next = cur->next;cur->next->prev = pHead;free(cur);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;//如果没找到,返回空
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = CreateLTNode(x);ListNode* _prev = pos->prev;// _prev newnode pos_prev->next = newnode;newnode->prev = _prev;newnode->next = pos;pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* _prev = pos->prev;ListNode* rear = pos->next;_prev->next = rear;rear->prev = _prev;free(pos);
}

🌅4.3 Test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1#include "List.h"void Test1()
{ListNode* plist = ListCreate();//尾插1、2、3ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPrint(plist);//头插5、4ListPushFront(plist, 5);ListPushFront(plist, 4);ListPrint(plist);//查找元素3,找到返回节点地址,没找到返回空ListNode* pos = ListFind(plist, 3);if (pos){printf("找到了\n");}else{printf("没找到\n");}//在3前面插入30ListInsert(pos, 30);ListPrint(plist); //删除3if (pos){ListErase(pos);pos = NULL;}ListPrint(plist);//尾删两次ListPopBack(plist);ListPopBack(plist);ListPrint(plist);//头删两次ListPopFront(plist);ListPopFront(plist);ListPrint(plist);//销毁链表ListDestory(plist);plist = NULL;
}int main()
{Test1();return 0;
}

🌅4.4 测试结果

在这里插入图片描述


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

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

相关文章

基于maobox-gl 纯前端绘制全球色斑图

基于maobox-gl.js、turf.js 绘制全球色斑图绘制 1、准备全球的某一类的点位数据&#xff0c;可以使用turf.js 随机生成点&#xff0c;并点数据赋properties属性 let points turf.randomPoint(30, { bbox: [-180, -90, 180, 90]}); let interpolateOptions {gridType: "…

Linux下Python调用C语言

一&#xff1a;Python调用C语言场景 1&#xff0c;已经写好的C语言代码&#xff0c;不容易用Python实现&#xff0c;想直接通过Python调用写好的C语言代码 2&#xff0c;C比Python快&#xff08;只是从语言层面&#xff0c;不能绝对说C程序就是比Python快&#xff09; 3&…

某音订单接口在电商行业中的重要性及实践应用

一、引言 随着移动互联网的快速发展&#xff0c;短视频平台抖音已经成为人们日常生活中不可或缺的一部分。越来越多的商家开始利用抖音平台推广和销售商品&#xff0c;从而实现商业变现。在这个过程中&#xff0c;抖音订单接口起到了至关重要的作用。本文将详细探讨抖音订单接…

希尔排序(Java)

目录 希尔排序的步骤&#xff1a; Java实现希尔排序&#xff1a; 希尔排序&#xff08;Shell Sort&#xff09;是一种插入排序的改进算法&#xff0c;它通过比较距离较远的元素交换位置&#xff0c;从而实现数据局部的较小规模排序&#xff0c;逐渐减小元素之间的间隔&#x…

遥感方向期刊总结

开眼看期刊~ 期刊分区信息搜索网址中国科学院文献情报中心期刊分区表升级版科研通期刊查询&#xff1a; 遥感领域常见期刊Nature CommunicationsRemote Sensing of EnvironmentProceedings of the National Academy of Sciences &#xff08;PNAS&#xff09;ISPRS Journal of …

C++ day50 买卖股票最佳时机

题目1&#xff1a;123 买卖股票的最佳时机Ⅲ 题目链接&#xff1a;买卖股票的最佳时机Ⅲ 对题目的理解 prices[i]表示股票在第i天的价格&#xff0c;最多可以完成两笔交易&#xff0c;不能同时进行多笔交易 可以买卖一次&#xff0c;两次&#xff0c;也可以不买卖 动态规划…

[字符串操作] 分割乘积

​ 分割乘积 题目描述 给定一个整数&#xff0c;求插入两个乘号将该整数分割成三个数之后&#xff0c;三个数的的最大乘积。 关于输入 只有一行&#xff0c;一个整数。 关于输出 所求出的最大乘积。 例子输入 4242例子输出 336提示信息 整数有正有负&#xff0c;注意&…

yolov5 7.0版本部署手机端。通过pnnx导出ncnn。

yolov5 7.0版本部署手机端。通过pnnx导出ncnn。 流程配置ncnn android yolov5导出自己模型的ncnn修改yolo.py文件导出TorchScript文件pnnx转torchscript为ncnn 安卓运行权重路径输入输出anchors 大小类别名generate_proposals方法修改 结果 流程 网络yolov5 的部署已经有很多了…

最新最全的Postman接口测试: postman实现参数化

什么时候会用到参数化 比如&#xff1a;一个模块要用多组不同数据进行测试 验证业务的正确性 Login模块&#xff1a;正确的用户名&#xff0c;密码 成功&#xff1b;错误的用户名&#xff0c;正确的密码 失败 postman实现参数化 在实际的接口测试中&#xff0c;部分参数…

Redis Cluster

概念 Redis Cluster采用多主多从的方式&#xff0c;支持开启多个主节点&#xff0c;每个主节点可以挂载多个从节点。 Cluster会将数据进行分片&#xff0c;将数据分散到多个主节点上&#xff0c;而每个主节点都可以对外提供读写服务。这种做法使得Redis突破了单机内存大小的限制…

2023第二届全国大学生数据分析大赛AB题代码

看大家需求量比较大&#xff0c;所以更新了一下A、B题的示例代码&#xff0c;部分截图如下 A题部分截图 B题部分截图 免费获取代码 关注威信公众号 Python风控模型与数据分析&#xff0c;回复 23年数据分析大赛代码 文末查看如何免费获取代码&#xff1b;编写不易&#xff0c;…

低效的DOM操作(js的问题)

使用 JavaScript 操作DOM&#xff08;即添加、修改和删除元素&#xff09;是相对容易&#xff0c;但操作效率却不怎么样。 比如&#xff0c;每次添加一系列DOM元素。添加一个DOM元素是一个昂贵的操作。连续添加多个DOM元素的代码是低效的。 当需要添加多个DOM元素时&#xff…

【数据结构】AOV网与拓扑排序

一.AOV网的概念&#xff08;Activity On Vertex Network&#xff09; 在一个表示工程的有向图中&#xff0c;用顶点表示活动&#xff0c;用弧表示活动之间的优先关系。这样的有向图为顶点表示活动的网&#xff0c;我们称为AOV网&#xff08;Activity On Vertex Network&#xf…

scikit-learn线性回归法进行利润预测

大家好&#xff0c;生成式人工智能无疑是一个改变游戏规则的技术&#xff0c;但对于大多数商业问题来说&#xff0c;回归和分类等传统的机器学习模型仍然是首选。 私募股权或风险投资这样的投资者利用机器学习&#xff0c;首先必须了解关注的数据以及它是如何被使用的。投资公…

anaconda 笔记:安装anaconda之后显示conda command not found

解决方法是在.bashrc文件中添加如下一行文字&#xff08;如果没有.bashrc文件&#xff0c;就在root中新建一个这个文件&#xff09; export PATH$PATH:/home/Your_name/anaconda3/bin也就是安装anaconda3的路径 然后退出编辑&#xff0c;在命令行中敲入 source ~/.bashrc即可…

云原生Kubernetes系列 | init container初始化容器的作用

云原生Kubernetes系列 | init container初始化容器的作用 kubernetes 1.3版本引入了init container初始化容器特性。主要用于在启动应用容器(app container)前来启动一个或多个初始化容器,作为应用容器的一个基础。只有init container运行正常后,app container才会正常运行…

Spring——IOC,DI,AOP

Spring框架是一个轻量级的Java开发框架&#xff0c;它提供了许多功能强大的特性来简化Java应用程序的开发。其中&#xff0c;IOC&#xff08;控制反转&#xff09;、DI&#xff08;依赖注入&#xff09;和AOP&#xff08;面向切面编程&#xff09;是Spring框架的三大核心特性。…

k8s安装学习环境

目录 环境准备 配置hosts 关闭防火墙 关闭交换分区 调整swappiness参数 关闭setlinux Ipv4转发 时钟同步 安装Docker 配置Yum源 安装 配置 启动 日志 安装k8s 配置Yum源 Master节点 安装 初始化 配置kubectl 部署CNI网络插件 Node节点 检查 环境准备 准…

同步代码,异步代码-微任务 宏任务 事件循环(输出题)

1.知识点 事件循环&#xff1a; 由浏览器Web API管理的异步代码&#xff0c;如果有结果后&#xff0c;会根据这个异步代码的类型&#xff0c;被放入对应的微任务或宏任务当中。当执行栈的任务&#xff08;同步代码&#xff09;全部执行完毕后&#xff0c;再执行完所有微任务的…

springboot(ssm 二手图书交易系统 图书销售系统Java(codeLW)

springboot(ssm 二手图书交易系统 图书销售系统Java(code&LW) 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#…