数据结构之链表(1),单链表
目录
前言
一、什么是链表
二、链表的分类
三、单链表
四、单链表的实现
五、SList.c文件完整代码
六、使用演示
总结
前言
本文讲述了什么是链表,以及实现了完整的单链表。
❤️感谢支持,点赞关注不迷路❤️
一、什么是链表
1.概念
概念:链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
简单来说,链表属于线性表的一种,在逻辑结构上是连续的,物理结构上不一定是连续的,通过指针连接。
2.节点
与顺序表不同的是,链表里的每个节点都是独立申请下来的空间,我们称之为“结点/结点”
结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。也可以说成数据域和指针域两部分
特点:链表中每个结点都是独立申请的(即需要插入数据时才去申请一块结点的空间),我们需要通过指针变量来保存下一个结点位置,才能从当前结点找到下⼀个结点。
3.链表的性质
- 链式结构在逻辑上是连续的,在物理结构上不⼀定连续
- 结点一般是从堆上申请的
- 从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
4.与顺序表的对比
- 顺序表的 中间/头部 插入删除数据的时间复杂度为O(n),而链表相同功能时间复杂度为O(1)
- 顺序表增容需要申请请新空间,拷贝数据,释放旧空间。会有不小的消耗。链表每次申请一个节点,不存在拷贝,释放旧空间。
- 顺序表增容一般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200, 我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。链表不存在空间浪费
以上是链表相对与顺序表的优点,但不代表顺序表一定不如链表,顺序表在有些场景下效率还是非常高,因此选择使用什么数据结构是看场景的要求
二、链表的分类
链表的结构非常多样,有带头不带头,单向或者双向,循环不循环,这三种属性情况组合起来就有8种(2x2x2)链表结构:
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表:
- 无头单向非循环链表(单链表):结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现该结构会带来很多优势,后面我们代码实现了就知道了。
本文主要讲述单链表
三、单链表
单链表的结构如下图:
单链表的结构声明:
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;//节点数据
struct SListNode* next;//指向下一节点的指针变量
}SLTNode;
以下单链表同样由3个文件组成:
- SList.h:单链表的结构声明,各种函数声明
- SList.c:用于函数的具体实现
- test.c:用于测试单链表(自行测试)
四、单链表的实现
1.SList.h文件
以下是该文件中的代码:
#pragma once
#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);//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//尾删
void SLTPopBack(SLTNode** pphead);//头删
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除指定位置pos的数据
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos之后的数据
void SLTEraseAfter(SLTNode* pos);//销毁链表
void SListDestroy(SLTNode** pphead);
2.SList.c文件
1.SLTPrint函数
//打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;//循环打印while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
解析:该函数用于打印链表数据,使用一个pcur指针,只要不为空指针,就循环遍历下一个节点
2.SLTBuyNode函数
//申请节点
SLTNode* SLTBuyNode(SLTDataType x)
{//申请一个节点SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}//赋值node->data = x;node->next = NULL;return node;
}
解析:
- 该函数用于申请新节点,一个参数,其为新节点存储的数据
- 使用malloc函数申请,申请成功后将其数据域赋值为参数值,指针域赋值为空指针NULL
- 因为该函数主要为其他功能函数服务,因此只需写在 SList.c 文件中即可,无需在头文件SList.h 中声明
3.SLTPushBack函数
//尾插
//因为要修改plist本身,因此需要二级指针来接收
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//断言pphead不能为空指针assert(pphead);//申请新节点SLTNode* newnode = SLTBuyNode(x);//如果pphead指向的第一个节点就是空指针if (*pphead == NULL){*pphead = newnode;}else{//找尾结点SLTNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}
解析:
- 该函数功能为:在链表尾部插入一个数据。因此需要链表的头部指针和需要插入的数据x
- 为什么头节点参数是二级指针?答:因为需要修改原指针变量本身,我们知道函数传参分为传值传参和传址传参(&),想要修改原变量内容就需要传址传参,而这里原变量就是一个指向链表头节点的指针变量,因此原变量通过&传参,就需要二级指针来接收一级指针变量的地址。所以这里就是用二级指针,下面其他的函数同理。
- 申请完新节点,插入到链表尾部时要分2种情况,1链表为空,2链表不为空。
- 链表为空则直接将新节点插入链表即可,也就是将指向头结点的指针指向新节点。链表不为空就需要寻找尾结点,尾节点的特点就是next指针指向空。循环之后将尾节点的next指针修改为新节点即可。
4.SLTPushFront函数
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//直接申请节点,然后修改新节点指向即可SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
解析:
- 功能:在链表头部插入一个新节点。参数同样是一个二级指针和新节点的数据
- 头插比较简单,只需将新申请的节点的 next 指针指向链表头结点,然后让指向链表头结点的指针指向新节点即可。
5.SLTPopBack函数
//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//只有一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//两个及以上节点的情况//申请两个节点用于指向尾节点和倒数第二个节点SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//销毁尾结点,然后让倒数第二个节点next指向空free(ptail);ptail = NULL;prev->next = NULL;}
}
解析:
- 功能:删除链表的最后一个节点,参数为一个二级指针
- 首先断言,不能传空指针并且链表不能为空
- 尾删也要分两种情况,因为如果只要一个节点,那么就不需要找倒数第二个节点。所以分为链表只有一个节点的情况和有多个节点的情况
- 只有一个节点,那么我们直接 free 掉该节点,然后让指向头结点的指针置空。多个节点,那么我们就需要找到最后一个节点 ptail 以及倒数第二个节点 prev,使用while循环即可,只要ptail 的下一个节点为空就跳出循环,此时 prev 为倒数第二个节点,然后释放掉最后一个节点,修改倒数第二个节点的next指针即可。
6.SLTPopFront函数
//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
解析:
- 功能:删除链表的头部节点
- 因为参数 *pphead 就是指向链表的头结点,因此删除简单,先创建一个next指针保存头结点的下一个节点,然后释放掉头节点,更改 *pphead 的指向即可
7.SLTFind函数
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){//找到了if (pcur->data == x){return pcur;}pcur = pcur->next;}//没找到return NULL;
}
解析:
- 功能:查找数据为 x 的节点,返回该节点的地址。
- 因为查找操作不会影响头指针 phead,因此参数为一级指针即可
- 查找很简单,循环遍历链表,让 pcur 一直往后走,直到找到存储 x 的节点,返回该节点,没找到就返回空指针
8.SLTInsert函数
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//如果指定位置是头结点if (*pphead == pos){//头插SLTPushFront(pphead, x);}else{//新节点SLTNode* newnode = SLTBuyNode(x);//prev用于找到pos前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}
解析:
- 功能:在指定位置之前插入数据,参数为头指针地址、指定位置(用SLTFind函数确定)、需插入的数据x。
- 首先断言,头指针和pos指针都不能为空,也就是链表不能为空
- 然后,也分两种情况,因为如果指定位置刚好是头结点,那么只需要使用头插函数即可。如果指定位置不是头结点,我们就需要找到 pos 指向的结点的前一个节点。定义一个指针变量 prev,让它从头往后找,只要它的 next 指针不是 pos,那么就继续往下个节点走,直到找到pos的前一个节点。
- 找到之后,让 prev 的 next 指针指向新节点,再让新节点的 next 指针指向pos就完成了在指定位置前插入数据。
7.SLTInsertAfter函数
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//先让newnode的next指向下一个节点newnode->next = pos->next;//再修改pos的next指针pos->next = newnode;
}
解析:
- 功能:在指定位置之后插入数据。
- 因为受影响的节点只有pos指向的节点,所以只需要传pos指针和需要增加的数据 x 即可。
- pos指针使用SLTFind函数指定即可
- 实现简单,注意顺序即可,将新节点的next指针指向pos节点的下一个节点,然后让pos指针的next指针指向新节点即可。
8.SLTErase函数
//删除指定位置pos的数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//如果pos位置是第一个位置if (*pphead == pos){//头删SLTPopFront(pphead);}else{//找到pos前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//释放prev->next = pos->next;free(pos);pos = NULL;}
}
解析:
- 功能:删除指定位置的节点。
- 参数需用到头节点和pos指向节点
- 也要分两种情况,因为如果需删除节点刚好是头结点,直接头删即可,如果不是头结点,则需要找到指定位置的前一个节点,老套路,找到之后,先修改 prev 的next指针,然后释放pos指向的节点。
9.SLTEraseAfter函数
//删除pos之后的数据
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;//让pos指向它之后的之后的数据pos->next = pos->next->next;free(del);del = NULL;
}
解析:
- 功能:删除pos位置后一个节点
- 只需要pos指针即可
- 实现简单,先保存需删除的节点地址,然后改变pos的next指针指向,最后释放掉 del 即可。
10.SListDestroy函数
//销毁链表
void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;//循环销毁链表while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}//记得将头指针指置空*pphead = NULL;
}
解析:
- 功能:销毁整个链表
- 断言确保链表不为空
- 创建pcur循环遍历链表,只要pcur不为空,释放该空间。
- 最后记得将头指针置空
五、SList.c文件完整代码
#include "SList.h"//打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;//循环打印while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}//申请节点
SLTNode* SLTBuyNode(SLTDataType x)
{//申请一个节点SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}//赋值node->data = x;node->next = NULL;return node;
}//尾插
//因为要修改plist本身,因此需要二级指针来接收
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//断言pphead不能为空指针assert(pphead);//申请新节点SLTNode* newnode = SLTBuyNode(x);//如果pphead指向的第一个节点就是空指针if (*pphead == NULL){*pphead = newnode;}else{//找尾结点SLTNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);//直接申请节点,然后修改新节点指向即可SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//只有一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//两个及以上节点的情况//申请两个节点用于指向尾节点和倒数第二个节点SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}//销毁尾结点,然后让倒数第二个节点next指向空free(ptail);ptail = NULL;prev->next = NULL;}
}//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//查找
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(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//如果指定位置是头结点if (*pphead == pos){//头插SLTPushFront(pphead, x);}else{//新节点SLTNode* newnode = SLTBuyNode(x);//prev用于找到pos前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);//先让newnode的next指向下一个节点newnode->next = pos->next;//再修改pos的next指针pos->next = newnode;
}//删除指定位置pos的数据
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//如果pos位置是第一个位置if (*pphead == pos){//头删SLTPopFront(pphead);}else{//找到pos前一个节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//释放prev->next = pos->next;free(pos);pos = NULL;}
}//删除pos之后的数据
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;//让pos指向它之后的之后的数据pos->next = pos->next->next;free(del);del = NULL;
}//销毁链表
void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;//循环销毁链表while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}//记得将头指针指置空*pphead = NULL;
}
六、使用演示
不完全演示:
#include "SList.h"void SListTest1()
{//指针要初始化为空SLTNode* plist = NULL;//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);printf("尾插:");SLTPrint(plist);//头插SLTPushFront(&plist, 11);SLTPushFront(&plist, 12);SLTPushFront(&plist, 13);SLTPushFront(&plist, 14);SLTPushFront(&plist, 15);printf("头插:");SLTPrint(plist);//尾删SLTPopBack(&plist);printf("尾删:");SLTPrint(plist);//头删SLTPopFront(&plist);printf("头删:");SLTPrint(plist);//指定位置之前插入数据SLTInsert(&plist, SLTFind(plist, 2), 66);printf("在2的前面插入66:");SLTPrint(plist);//删除指定位置数据SLTErase(&plist, SLTFind(plist, 66));printf("删除66:");SLTPrint(plist);//销毁链表SListDestroy(&plist);printf("销毁:");SLTPrint(plist);
}int main()
{SListTest1();return 0;
}
运行结果:
总结
以上就是本文的全部内容,感谢支持。
相关文章:

数据结构之链表(1),单链表
目录 前言 一、什么是链表 二、链表的分类 三、单链表 四、单链表的实现 五、SList.c文件完整代码 六、使用演示 总结 前言 本文讲述了什么是链表,以及实现了完整的单链表。 ❤️感谢支持,点赞关注不迷路❤️ 一、什么是链表 1.概念 概念:链…...

如何构建鲁棒高性能 Prompt 的方法?
你好,我是三桥君 在当今时代,利用大型语言模型如ChatGPT进行文本生成和交互已成为一种趋势。然而,要充分发挥这些模型的能力,尤其是在生产环境中,我们需要精心设计和优化我们的提示词(prompt)。…...

基于Springboot+微信小程序 的高校社团管理小程序(含源码+数据库+lw)
1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…...

Vue 响应式监听 Watch 最佳实践
一. 前言 上一篇文章我们学习了 watch 的基础知识,了解了它的基本使用方法及注意事项,本篇文章我们继续了解在Vue 中 响应式监听 watch 的妙用。了解 watch 的基础使用请参考上一篇文章: 详解 Vue 中 Watch 的使用方法及注意事项https://bl…...
md编辑器语法
这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…...

RabbitMQ常用管理命令及管理后台
RabbitMQ管理命令 1、用户管理1.1、新增一个用户1.2、查看当前用户列表1.3、设置用户角色1.4、设置用户权限1.5、查看用户权限 2、RabbitMQ的web管理后台2.1、查看rabbitmq 的插件列表2.2、启用插件2.3、禁用插件2.4、访问RabbitMQ的web后台2.4、通过web页面新建虚拟主机 ./rab…...

从准备面试八股文,感悟到技术的本质
工作前几年听说过,大学最重要的几门课其实是数据结构和算法、操作系统、计算机组成原理、计算机网络。 初听时不以为然,感觉没什么用。 近期准备面试八股文得到了一些感悟。这句话随着工作年限和对程序的理解越来越深入,含金量越来越高。 最…...

云手机的默认ip地址是什么
云手机(Cloud Phone)是一种基于云计算技术的虚拟手机,它可以在云端运行,使用户能够通过互联网访问手机应用和服务。云手机的IP地址通常取决于以下几个因素: 1. 云服务提供商 不同的云服务提供商(如AWS、G…...

对接阿里asr和Azure asr
1:对接阿里asr 1.1:pom <dependency><groupId>com.alibaba.nls</groupId><artifactId>nls-sdk-recognizer</artifactId><version>2.2.1</version> </dependency>1.2:生成token package c…...

未来数字世界相关技术、应用:AR/VR/MR;数字人、元宇宙、全息显示
一、AR/VR/MR 增强现实(AR)、虚拟现实(VR)和混合现实(MR)是三种不同的技术,它们都旨在增强用户对现实世界的感知和交互体验。以下是它们的详细介绍: 增强现实(AR) 增强现实(Augmented Reality, AR) 是一种将虚拟信息叠加到现实世界中的技术。通过AR技术,用户可…...

在 Java 中提供接口方法而不是实现接口
问题 我正在阅读有关Java中的接口的文章。其中提到我们必须实现compareTo方法才能在ArrayList容器上调用sort,例如Employee类应该实现 Comparable接口。 后面解释了为什么Employee类不能简单地提供compareTo方法而不实现Comparable接口?之所以需要接口…...

伪类选择器
一、基本概念 伪类选择器以冒号(:)开头,后面跟着伪类名。它不直接对应DOM中的任何元素,而是用于描述元素的特殊状态或位置。通过使用伪类选择器,可以在不修改HTML文档结构的情况下,为元素添加或修改样式。…...

亚信安全天穹5分钟勒索体检 免费试用今起上线
对于勒索攻击的认知 你是否还停留在“2.0时代”? 勒索攻击无疑是企业面临的最大威胁,2024年上半年,勒索组织数量同步增长超过50%,勒索攻击数量也持续攀升,平均勒索赎金突破520万美元。 当前,勒索攻击治理…...

高校竞赛管理系统的设计与实现
摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统高校竞赛管理系统信息管理难度大,容错率低&am…...

物联网行业中通信断线重连现象介绍以及如何实现
01 概述 断线重连是指在计算机网络中,当网络连接遇到异常中断或者断开时,系统会自动尝试重新建立连接,以保证网络通信的连续性和稳定性。这是一种常见的网络通信技术,广泛应用于各种计算机网络场景,包括互联网、局域…...

新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL
文章目录 前言步骤 1: 安装 Anaconda步骤 2: 创建新的 Anaconda 环境步骤 3: 安装最新版本的 PyTorch步骤 4: 安装特定版本的 PyTorch步骤 5: 安装最新版本的 DGL步骤 6: 安装特定版本的 DGL步骤 7: Pycharm中使用虚拟环境解释器第一种情况:创建新项目第二种情况&am…...

centos7系统安装宝塔面板
1、开始安装 适用系统 Centos/OpenCloud/Alibaba 稳定版9.0.0 urlhttps://download.bt.cn/install/install_lts.sh;if [ -f /usr/bin/curl ];then curl -sSO $url;else wget -O install_lts.sh $url;fi;bash install_lts.sh ed8484bec等待命令执行,安装完成&#…...

汽车总线之----J1939总线
instruction SAE J1939 是由美国汽车工程协会制定的一种总线通信协议标准,广泛应用于商用车,船舶,农林机械领域中,J1939协议是基于CAN的高层协议,我们来看一下两者之间的关系。在J1939 中,物理层和数据链路…...

基于skopt的贝叶斯优化基础实例学习实践
贝叶斯方法是非常基础且重要的方法,在前文中断断续续也有所介绍,感兴趣的话可以自行移步阅读即可: 《数学之美番外篇:平凡而又神奇的贝叶斯方法》 《贝叶斯深度学习——基于PyMC3的变分推理》 《模型优化调参利器贝叶斯优化bay…...

OJ在线评测系统 后端 用策略模式优化判题机架构
判题机架构优化(策略模式) 思考 我们的判题策略可能会有很多种 比如 我们的代码沙箱本身执行程序需要消耗时间 这个时间可能不同的编程语言是不同的 比如沙箱执行Java要额外花费2秒 我们可以采用策略模式 针对不同的情况 定义不同独立的策略 而不是把所有情况全部放在一个i…...

element ui 精确控制日期控件 date-picker
https://github.com/element-plus/element-plus/discussions/17378 -- 某组件 xxx.vue ... <el-date-pickerv-model"timeRange"type"daterange"range-separator"-"start-placeholder"开始日期"end-placeholder"结束日期"…...

centos7安装指定版本php及扩展
安装EPEL仓库(如果尚未安装) sudo yum install epel-release导入REMI仓库的公钥: sudo rpm --import http://rpms.remirepo.net/RPM-GPG-KEY-remi启用REMI仓库(你可以选择PHP 7.0或者7.4,以下以7.0为例)&am…...

后端-对表格数据进行添加、删除和修改
一、添加 要求: 按下添加按钮出现一个板块输入添加的数据信息,点击板块的添加按钮,添加;点击取消,板块消失。 实现: 1.首先,设计页面输入框格式,表格首行 2.从数据库里调数据 3.添加…...

【学习笔记】手写 Tomcat 七
目录 一、优化 Dao 1. 设置 UserDaoImpl 为单例模式 2. 创建 Dao 工厂 3. 在 Service 层获取 UserDao 的实例 二、优化 Service 1. 设置 UserServiceImpl 为单例模式 2. 创建 Service 工厂 3. 在 Servlet 层获取 Service 实现类的对象 三、优化 Servlet 1. 使用配置…...

QT开发:详解 Qt 多线程编程核心类 QThread:基本概念与使用方法
1. 引言 在现代应用程序开发中,多线程编程是一个关键技术,能够显著提高程序的效率和响应速度。Qt 是一个跨平台的 C 框架,其中 QThread 类是实现多线程编程的核心类。本文将深入详解 QThread 的基本概念、使用方法及其在实际应用中的重要性。…...

【芋道源码】gitee很火的开源项目pig——后台管理快速开发框架使用笔记(微服务版之本地开发环境篇)
后台管理快速开发框架使用笔记(微服务版之本地开发环境篇) 后台管理快速开发框架使用笔记(微服务版之本地开发环境篇) 后台管理快速开发框架使用笔记(微服务版之本地开发环境篇)前言一、如何获取项目&#…...

设计模式、系统设计 record part01
技术路线: 工程师》设计师》分析师》架构师 管理路线: 项目经理》技术经理 工程师: 编程技术、测试技术 设计师: 工程师设计技术 分析师: 设计师分析技术 架构师: 分析师架构技术 项目经理: 时间…...

服务器与普通电脑的区别是什么?
服务器作为企业进行线上业务所使用的网络设备,大多数的用户对于服务器都有一定的了解,而普通的电脑则是人们在进行日常娱乐活动中经常会用到的设备,本文就来探讨一下服务器与普通电脑之间的区别是什么吧! 普通的电脑就是我们通常所…...

Vue3学习(六)Vue3 + ts几种写法
前言 官网提到组合式api和选项式api 选项式api其实就是vue2的写法,组合式api是vue3的新写法(组合式api可以在script中使用setup()也可以使用<script setup>,<script setup>是setup(ÿ…...

【前端】ES6:Proxy代理和Reflect对象
文章目录 1 Proxy代理1.1 get方法1.2 set方法1.3 has方法1.4 this问题 2 Reflect对象2.1 代替Object的某些方法2.2 修改某些Object方法返回结果2.3 命令式变为函数行为2.4 配合Proxy 1 Proxy代理 Proxy如其名,它的作用是在对象和和对象的属性值之间设置一个代理&am…...