数据结构之双向链表(赋源码)

数据结构之双向链表(源码)

线性表

双向链表是线性表链式存储结构的一种,若对链式存储结构进行分类可以分为八种。

  • 带头、不带头:指的是该连链表有无头节点,头节点不存放任何内容,它不一定是链表必备的元素,而一个链表拥有一个头节点就可以对后续的插入、删除等操作进行统一而不需要判空等情况。
  • 单向、双向:双向链表与单向链表的区别在于,它多了一个指针用来存放上一个节点的指针,这样就可以通过一个节点任意的向前、向后遍历。
  • 循环、不循环:链表的循环结构是将不循环链表的尾节点从指向空指针改为指向头指针。完成链表的自循环。

在这里插入图片描述

本篇所述:带头双向循环链表,简称双链表,双链表和单链表(不带头单向不循环链表)是八种链表中常用的两个链表,

双向链表

双链表结构

双链表是一个带头双向循环链表,更据它的特性不能想出,在一个双链表的一个节点里它的指针域用来存放前一个节点的地址和存放下一个节点的地址

typedef int ListNodeDataType;
typedef struct ListNode
{ListNodeDataType data;struct ListNode* prev;struct ListNode* next;
}ListNode;

prev是前驱指针存放前一个节点的地址,next是后继指针存放下一个节点的指针,而双链表是循环链表,尾节点没有下一个节点,它是指向头节点,头节点的prec指针指向尾节点。

功能实现

//初始化
void ListNodeInit2(ListNode** pphead);
ListNode* LTInit();
//打印
void ListPrint(ListNode* phead);
//尾插
void ListNodePushBack(ListNode* phead, ListNodeDataType x);
//头插
void ListNodePushFront(ListNode* phead, ListNodeDataType x);
//尾删
void ListNodePopBack(ListNode* phead);
//头删
void ListNodePopFront(ListNode* phead);
//查找
ListNode* ListNodeFind(ListNode* phead, ListNodeDataType x);
//指定位置删除
void ListNodeErase(ListNode* pos);
//指定位置插入(之后)
void ListNodeInsert(ListNode* pos, ListNodeDataType x);
//销毁
void ListDestory(ListNode* phead);
void ListDestory2(ListNode** pphead);

创建节点、初始化、打印

创建节点

//创建节点
ListNode* ListBuyNode(ListNodeDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc :");exit(1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;
}

我们说双链表是由一个一个的节点组成,而节点里存放的有数据,前驱指针用来存放上一个节点的地址和后继指针

用来存放下一个节点的地址,在创建节点时,使用malloc函数开辟空间即可,realloc适合对一块连续存储的空间开辟新的更大的空间,而节点用不是realloc函数,它们在物理空间上不一定是连续的。

使用malloc函数开辟完空间后别忘了判断是否开辟成功,这是为了养成一个良好的习惯,如今的计算机想要开辟空间是否,一般都是内存被用完了 。

最后,将x值放入节点里,由于它只是一个节点,没有前驱节点,和后继节点,而双向链表是循环链表所以让这两个指针指向自己,实现自循环,而不会去指向空指针。

初始化

//初始化
ListNode* LTInit()
{ListNode* phead = ListBuyNode(-1);return phead;
}
//  初始化
void ListNodeInit2(ListNode** pphead)
{*pphead = ListBuyNode(-1);
}

初始化头节点,使双链表带头,为了后续插入、删除等操作同意而设置的,数据域一般无意义,在链表里不一定有头节点。

初始化双链表有两种写法:

  • 创建头节点让将其地址返回

    • 不需要传递参数,调用创建节点函数后将开辟的空间返回即可
    • 头节点不需要存储有效信息,所以在调用ListBuyNode函数是任意传递了一个整形变量。
  • 将头节点声明后传递它的地址给ListNodeInit2初始化函数,在函数内完成空间的开辟。

    • 需要传递参数,且是二级指针,指向双链表的变量已经是一级指针,想要改变一级指针的内容必须取出其地址,使用二级指针接收。
    • 传递的是二级指针在函数内对形参的开辟,影响到了实参,不需要将地址返回。

再重述一遍,指向双链表的变量是一级指针,也就是头指针它是链表不可缺少的元素,链表里有头节点他就指向头节点,没有头节点它就指向第一个节点。想要对头指针修改,得传递它的地址,才能使函数内对形参的改变影响到实参。而一级指针的地址需要二级指针接收。

打印

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

提前将打印功能的实现,是为了方便后续对双链表,插入数据、删除数据进行更方便的观察,而不是每一次调用插入、删除等函数都使用vs的调试功能。

首先、在打印单链表时,同样使用了循环,创建了一个临时指针来指向第一个节点,打印单链表数据的接收条件是pcur指向空时停止。而双链表是一个循环链表它没有一个节点的指针是指向空的。

所以这里以pcur指向头节点时为循环结束条件 pcur != phead, 它并不存放有效数据,不用打印它。

在打印时需要从第一个节点开始打印所以pcur指针的起始位置是 ListNode* pcur = phead->next;,通过将pcur的下一个节点的地址赋给它,完成自循环 pcur = pcur->next;

查找

//查找
ListNode* ListNodeFind(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

查找,数据有着比较重要的地位,涉及到了指定位置(之前)插入,和指定位置删除等。

双链表的查找很好理解,想要查找一个元素,只需要将链表里每一个数据一一比较大小,若两者刚好相等,这就是需要查找的数据,然后将其地址返回即可,若跳出循环后并没有找到对应的数据,返回空指针。

使用assert断言,防止传递的头指针为空,并在函数内对空指针解引用从而引发一系列报错。

循环遍历每一个节点时,并不会查找头节点,将临时指针的起始位置设为第一个节点 ListNode* pcur = phead->next;,在循环内使用if语句进行判断,形参x和pcur内的data大小是否相等,相等将其地址返回,若不等继续循环判断,出了循环后没有找到,返回空指针即可。

插入

插入函数,传递的都是一级指针,为啥不传递二级指针捏,执行对节点的插入并不会影响到头指针的指向,传递一级指针即可。

插入分为:

  • 头插、尾插
  • 指定位置之前插入、指定位置之后插入

在实现节点的插入时实现需要考虑的是,我插入如这个节点会影响到那些节点,该如何选择插入的顺序将影响最小化。

尾插

//尾插
void ListNodePushBack(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* newnode = ListBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}

在这里插入图片描述

第一步:使用assert断言判断,头指针是否为空,为空运行程序就会报错。

第二步:如图,想要在尾节点插入一个新的节点,那就需要创建新的节点调用 ListBuyNode函数。

第三步:首先尾插一个节点会影响到的节点有:phead(头节点)、phead->prev

以及待插入的newnode新节点,会影响到phead的prev指针、phead->next的next指针,newnode的next、prev指针,

其中影响最小的是新节点newnode的两个指针,先改变它们的指向,让其prev指向最后一个节点,next指向头指针
在这里插入图片描述

然后再改变phead->prev指向的节点,也就是尾节点,先改变尾节点是由于提前将尾节点的prev指针改为newnode,那就无法通过头指针找到尾节点了。phead->prev->next = newnode;,这里让尾节点的next指针的指向从头节点改为newnode。

所以先改变头指针之外的节点,然后再改变头指针。

改变头指针里prev的指向,让其指向新的节点,这样就完成了所有节点的改变,成功插入了新的节点。

头插

//头插
void ListNodePushFront(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* newnode = ListBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}

头插与尾插及其类似,只是改变了插入如新节点的位置。

第一步:使用assert断言判断,头指针是否为空,为空运行程序就会报错。

第二步:第二步:如图,想要在尾节点插入一个新的节点,那就需要创建新的节点调用 ListBuyNode函数。

通过代码观察可以发现,再头插和尾插里的第一步和第二步一模一样,当然指定位置之后插入的逻辑也是一样的。

最主要的区别还是它们影响的节点不同。对新节点进行头插,主要影响到头节点phead和头节点的下一个节点(第一个节点)phead->next

有了尾插的经验,插入头节点时。首先改变新节点的next、prev指针的指向让prev指向头节点 newnode->prev = phead;,和next指针指向第一个节点 newnode->next = phead->next;,完成了头节点的改变,接着就改变,第一个节点的prev指针,让它指向newnode,最后改变头节点的next指针让其指向newnode。
在这里插入图片描述

指定位置之后插入

//指定位置插入(之后)
void ListNodeInsert(ListNode* pos, ListNodeDataType x)
{assert(pos);//pos newnode pos->nextListNode* newnode = ListBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

指定位置之后插入,一开始的断言的创建新节点的操作理解是一样的,最主要的区别是,指定位置插入影响的节点不同,而指定位置之后插入的实现,我只能说与头插基本上没有区别,在代码上只是将phead指针改为了pos指针。

首先,在pos之后插入新的节点newnode,它会影响到pos节点,pso->next两个节点,其中需要改变pos的next指针的指向,以及pos->next->prev指针的指向,需要它们指向新的节点newnode,而newnode的next指针需要指向pos的下一个节点,pos->next,和newnode的prev指针需要指向pos节点。这样就完成了对新节点的插入。其逻辑与头插无异。

删除

判空

bool ListEmpty(ListNode* phead)
{assert(phead); return phead->next == phead;
}

在对删除双链表时有一种特殊情况,就是只有一个头节点,这时双链表被看作是空链表,这与前文实现的单链表不同,单链表是需要将所有节点删除才为空,因为它没有头节点,这才将所有节点删除。

对于这种特殊情况做出了判断,当头节点的next指针指向自己的时候说明双链表里只有一个头节点,此时双链表为空,返回true,双链表不为空的话返回false。

再删除函数里都需要调用这种方法,以免错误的将头节点删除。

使用assert断言,进行判断 assert(!ListEmpty(phead));,若双链表为空返回true,!true,而为假,运行代码时直接报警告。

尾删

//尾删
void ListNodePopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));//phead phead->prev  phead->prev->prevListNode* del = phead->prev;phead->prev = del->prev;del->prev->next = phead;free(del);del = NULL;
}

尾删,首先考虑的是删除尾节点会影响到那些节点,而这些节点有需要做出那些改变,改变的顺序

影响的节点:删除尾节点会影响到头节点 phead,待删除的节点phead->perv,尾节点的前一个节点phead->prev->prev.

为了避免出现过多的结构体访问操作符从而降低代码的可读性,以及方边后续的删除操作,这里将 ListNode* del = phead->prev;

删除节点重命名,也就是del节点。这样 phead->prev->prev,又为 del->prev
在这里插入图片描述

如图需要对del节点进行删除,首先需要改变 del->prev节点的next指针,然后再改变头节点的prev指针,这是为了先将头节点的prev指针修改后而找不到del节点,但不过我们以及提前将del节点保存的所以不用担心它的发生。

接下来就是修改指针的指向了,先修改 del前一个节点的指向del->prev,删除尾节点后他就是新的尾,将它的next指向头节点 del->prev->next = phead;,然后修改头节点的prec指针让其指向新的尾节点,最后将del节点释放即可,释放完别忘了将其置为空,他此时可是一个野指针~。

头删

//头删
void ListNodePopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));//phead phead->next phead->next->nextListNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}

头删的逻辑与尾删类似,首先删除头节点会影响到那些节点:pheadphead->nextphead->next->next,这三个节点,同样为了避免出现过多的结构体访问操作符,以及后续的操作,将 phead->next,保存下来赋给del指针。
在这里插入图片描述

将这两个指针撇开重新指向新的节点
在这里插入图片描述

按照图示,需要将 del->next,的prev指针指向头节点,将头节点的next指针指向新的next节点。

将指针的指向修改完后,将del释放即完成了头删操作,别忘了将del置空。

指定位置删除

//指定位置删除
void ListNodeErase(ListNode* pos)
{assert(pos);assert(!ListEmpty(pos));//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;}

指定位置删除的思考逻辑与头删尾删一样,首先考虑删除指定的节点pos会影响到那些节点:pos->prev pos pos->next,这三个节点

在这里插入图片描述

pos->prev 节点的next指针撇开和, pos->next的prec指针撇开,这样该是容易理解的多。

  • 然后改变其指向,让pos->prev 节点的next指针指向 pos->next节点。

  • pos->next节点的prev指针指向 pos->prev 节点。

  • 最后将pos释放,置空即可。

在这里插入图片描述

销毁

//销毁
void ListDestory(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){ListNode* nextnode = pcur->next;free(pcur);pcur = nextnode;}phead = NULL;pcur = NULL;
}void ListDestory2(ListNode** pphead)
{ListNode* pcur = (*pphead)->next;while(pcur != *pphead){ListNode* nextnode = pcur->next;free(pcur);pcur = nextnode;}*pphead = NULL;pcur = NULL;
}

接口:

销毁函数的功能是将双链表所有内容全部清除,包括头节点。所以不许要调用判空函数。而销毁函数的两种方法一是传递二级指针,而是传递一级指针,这里与之前的初始化函数类似,分为了两种情况。而更推荐使用一级指针。

其原因是为了保证双链表接口的一致性,接口:现阶段不考虑吧过多的话,接口就是指函数。

保证接口的一致性是为了当实现的函数功能过多时,一些函数传递一级指针,一些函数传递二级指针,为了防止调用者的混淆,所以这里将双链表的所有功能均采用一级指针来实现。

销毁:

在进行销毁时需要创建一个临时变量用来遍历所有节点进行销毁,这里销毁的逻辑与单链表的销毁的逻辑时一样的,然后再循环内遍历销毁时需要使用一个next指针一便于将pcur释放后重新找到下一个节点释放,循环结束条件为 pcur != *pphead

最后将节点释放完后将pcur置为空,此时它为空指针。

缺陷

前文说过,需要对头节点进行更改就需要传递它的地址,也就是二级指针,而这里使用一级指针来实现对双链表的销毁就丢失了这种特性,所以在销毁完双链表后,还需要手动的将头节点置为空。

总结

总的来说,在实现双链表的算法时,在插入和删除上优先考虑的是插入一个节点会影响到那些节点、删除一个节点又会影响到那些节点,以及被影响节点的指针的指向。这里最好画图加以理解。

在插入、删除、查找等功能里均使用assert断言,这样做的目的是提高函数的健壮性、而不是在传递空指针时函数无法解决而产生一系列未知异常的情况。

还强调了在实现函数功能是统一双链表的一致性、这样虽然保证了所有函数传递的都是一级指针,但不可否认的是这样又会丢失一些功能,需要手动去实现,如传递一级指针,将一个双链表销毁后需要手动置空。

源码

List.h

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int ListNodeDataType;
typedef struct ListNode
{ListNodeDataType data;struct ListNode* prev;struct ListNode* next;
}ListNode;
//初始化
void ListNodeInit2(ListNode** pphead);
ListNode* LTInit();
//打印
void ListPrint(ListNode* phead);
//尾插
void ListNodePushBack(ListNode* phead, ListNodeDataType x);
//头插
void ListNodePushFront(ListNode* phead, ListNodeDataType x);
//尾删
void ListNodePopBack(ListNode* phead);
//头删
void ListNodePopFront(ListNode* phead);
//查找
ListNode* ListNodeFind(ListNode* phead, ListNodeDataType x);
//指定位置删除
void ListNodeErase(ListNode* pos);
//指定位置插入(之后)
void ListNodeInsert(ListNode* pos, ListNodeDataType x);
//销毁
void ListDestory(ListNode* phead);
void ListDestory2(ListNode** pphead);

List.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"ListNode* ListBuyNode(ListNodeDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc :");exit(1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;
}
ListNode* LTInit()
{ListNode* phead = ListBuyNode(-1);return phead;
}
void ListNodeInit2(ListNode** pphead)
{*pphead = ListBuyNode(9);
}
//打印
void ListPrint(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}//尾插
void ListNodePushBack(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* newnode = ListBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}
//头插
void ListNodePushFront(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* newnode = ListBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}bool ListEmpty(ListNode* phead)
{assert(phead); return phead->next == phead;
}//尾删
void ListNodePopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));//phead phead->prev  phead->prev->prevListNode* del = phead->prev;phead->prev = del->prev;del->prev->next = phead;free(del);del = NULL;
}
//头删
void ListNodePopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));//phead phead->next phead->next->nextListNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}//查找
ListNode* ListNodeFind(ListNode* phead, ListNodeDataType x)
{assert(phead);ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//指定位置删除
void ListNodeErase(ListNode* pos)
{assert(pos);assert(!ListEmpty(pos));//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;}
//指定位置插入(之后)
void ListNodeInsert(ListNode* pos, ListNodeDataType x)
{assert(pos);//pos newnode pos->nextListNode* newnode = ListBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}//销毁
void ListDestory(ListNode* phead)
{ListNode* pcur = phead->next;while (pcur != phead){ListNode* nextnode = pcur->next;free(pcur);pcur = nextnode;}phead = NULL;pcur = NULL;
}
void ListDestory2(ListNode** pphead)
{ListNode* pcur = (*pphead)->next;while(pcur != *pphead){ListNode* nextnode = pcur->next;free(pcur);pcur = nextnode;}*pphead = NULL;pcur = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
void rest()
{ListNode* head = NULL;//ListNodeInit2(&head);head = LTInit();//尾插ListNodePushBack(head, 1);ListNodePushBack(head, 2);ListNodePushBack(head, 3);ListNodePushBack(head, 4);ListPrint(head);//头插//ListNodePushFront(head, 1);//ListNodePushFront(head, 2);//ListNodePushFront(head, 3);//ListNodePushFront(head, 4);//ListPrint(head);//尾删//ListNodePopBack(head);//ListPrint(head);//ListNodePopBack(head);//ListPrint(head);//ListNodePopBack(head);//ListPrint(head);//ListNodePopBack(head);//ListPrint(head);//头删//ListNodePopFront(head);//ListPrint(head);//ListNodePopFront(head);//ListPrint(head);//ListNodePopFront(head);//ListPrint(head);//ListNodePopFront(head);//ListPrint(head);ListNode* ret = ListNodeFind(head,4);if (ret != NULL){printf("有了!\n");}else{printf("没有!\n");}//ListNodeErase(ret);//ListNodeInsert(ret, 6);ListPrint(head);//ListDestory2(&head);ListDestory(head);head = NULL;}
int main()
{rest();return 0;
}

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

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

相关文章

十五、【机器学习】【监督学习】- 神经网络回归

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

C1W4.Assignment.Naive Machine Translation and LSH

理论课&#xff1a;C1W4.Machine Translation and Document Search 文章目录 1. The word embeddings data for English and French words1.1The dataThe subset of dataLoad two dictionaries 1.2 Generate embedding and transform matricesExercise 1: Translating English…

华为的热机备份和流量限制

要求&#xff1a; 12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW4&#xff0c;生产区和办公区的流量走FW5 13&#xff0c;办公区上网用户限制流量不超过100M&#xff0c;…

智慧博物馆的“眼睛”:视频智能监控技术守护文物安全与智能化管理

近日&#xff0c;位于四川德阳的三星堆博物馆迎来了参观热潮。据新闻报道&#xff0c;三星堆博物馆的日均参观量达1.5万人次。随着暑假旅游高峰期的到来&#xff0c;博物馆作为重要的文化场所&#xff0c;也迎来了大量游客。博物馆作为文化和历史的重要载体&#xff0c;其安全保…

QT应用编程: window下QT程序异常捕获并生成DMP文件、PDB文件

文章目录 main.cpp代码捕获异常 生成dmp文件和pdb文件DebugTest生成错误代码注意 分析软件崩溃&#xff0c;除了需要dmp&#xff0c;还需要这个pdb文件 dmp&#xff0c;文件记录了崩溃的信息&#xff0c;而pdb&#xff08;代码工程数据库&#xff09;&#xff0c;则包含了你写的…

全局 loading

好久不见&#xff01; 做项目中一直想用一个统一的 loading 状态控制全部的接口加载&#xff0c;但是一直不知道怎么处理&#xff0c;最近脑子突然灵光了一下想到了一个办法。 首先设置一个全局的 loading 状态&#xff0c;优先想到的就是 Pinia 然后因为页面会有很多接口会…

AI软件小说推文直接生成漫画短视频,小说推广项目的辅助工具,前端uniapp。

有哪些AI的软件是比较热门的&#xff1f; 以下是一些常用的网页AI软件&#xff0c;可以用于绘图&#xff1a; Canva&#xff1a;Canva是一个非常受欢迎的网页平台&#xff0c;提供各种图形设计和绘图工具。它具有易于使用的界面和大量的模板和元素&#xff0c;可以帮助你创建出…

Qt 使用Installer Framework制作安装包

Qt 使用Installer Framework制作安装包 引言一、下载安装 Qt Installer Framework二、简单使用2.1 创建目录结构 (文件夹结构)2.2 制作程序压缩包2.3 制作程序安装包 引言 Qt Installer Framework (安装程序框架)是一个强大的工具集&#xff0c;用于创建自定义的在线和离线安装…

【网络安全】PostMessage:分析JS实现XSS

未经许可&#xff0c;不得转载。 文章目录 前言示例正文 前言 PostMessage是一个用于在网页间安全地发送消息的浏览器 API。它允许不同的窗口&#xff08;例如&#xff0c;来自同一域名下的不同页面或者不同域名下的跨域页面&#xff09;进行通信&#xff0c;而无需通过服务器…

【线程系列之五】线程池介绍C语言

一、基本概念 1.1 概念 线程池&#xff08;Thread Pool&#xff09;是一种基于池化技术管理线程的机制&#xff0c;旨在减少线程创建和销毁的开销&#xff0c;提高系统资源的利用率&#xff0c;以及更好地控制系统中同时运行的线程数量。线程池通过预先创建一定数量的线程&am…

Qt模型/视图架构——委托(delegate)

一、为什么需要委托 模型&#xff08;model&#xff09;用来数据存储&#xff0c;视图&#xff08;view&#xff09;用来展示数据。因此&#xff0c;模型/视图架构是一种将数据存储和界面展示分离的编程方法。具体如下图所示&#xff1a; 由图可知&#xff0c;模型向视图提供数…

Python | Leetcode Python题解之第238题除自身以外数组的乘积

题目&#xff1a; 题解&#xff1a; class Solution:def productExceptSelf(self, nums: List[int]) -> List[int]:length len(nums)# L 和 R 分别表示左右两侧的乘积列表L, R, answer [0]*length, [0]*length, [0]*length# L[i] 为索引 i 左侧所有元素的乘积# 对于索引为…

一文掌握Prometheus实现页面登录认证并集成grafana

一、接入方式 以保护Web站点的访问控制&#xff0c;如HTTP 服务器配置中实现安全的加密通信和身份验证&#xff0c;保护 Web 应用程序和用户数据的安全性。 1.1 加密密码 通过httpd-tools工具包来进行Web站点加密 yum install -y httpd-tools方式一&#xff1a;通过htpasswd生…

人工智能 (AI) 应用:一个异常肺呼吸声辅助诊断系统

关键词&#xff1a;深度学习、肺癌、多标签、轻量级模型设计、异常肺音、音频分类 近年来&#xff0c;流感对人类的危害不断增加&#xff0c;COVID-19疾病的迅速传播加剧了这一问题&#xff0c;导致大多数患者因呼吸系统异常而死亡。在这次流行病爆发之前&#xff0c;呼吸系统…

【时时三省】(C语言基础)变量

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 变量 可以改变的量 比如 int age&#xff1d;20 &#xff08;类型 变量的名字&#xff1d;0&#xff09; 如果后面要改可以直接代入 age&#xff1d;age1 age可以是任何字母 变量的分类…

微信小游戏 彩色试管 倒水游戏 逻辑 (四)

最近开始研究微信小游戏&#xff0c;有兴趣的 可以关注一下 公众号&#xff0c; 记录一些心路历程和源代码。 定义了一个名为 WaterFlow class&#xff0c;该类继承自 cc.Graphics&#xff0c;用于在 Cocos Creator 中创建和显示水流的动画效果。下面是对代码的详细解释&#x…

Qt实现简单的导航进度条——自定义控件

导航进度条通过其动态的视觉效果&#xff0c;‌不仅提供了任务进度的实时反馈&#xff0c;‌还增强了用户体验的流畅性和直观性。‌“进度”的设计方式多种多样&#xff0c;不同种类的运用需要根据具体场景来规划具体的进度方式&#xff0c;一般都要在清楚了解了每个方式的设计…

MySQL数据库慢查询日志、SQL分析、数据库诊断

1 数据库调优维度 业务需求&#xff1a;勇敢地对不合理的需求说不系统架构&#xff1a;做架构设计的时候&#xff0c;应充分考虑业务的实际情况&#xff0c;考虑好数据库的各种选择(读写分离?高可用?实例个数?分库分表?用什么数据库?)SQL及索引&#xff1a;根据需求编写良…

JavaEE--JavaWeb服务器的安装配置(Tomcat服务器安装配置)

前言: 本文介绍了 Java Web 服务器 Tomcat 的安装配置&#xff0c;并详细说明了如何在 IntelliJ IDEA 中配置服务器&#xff0c;创建 JavaEE 项目&#xff0c;并发布文章。文章首先解释了前端程序如何访问后端程序以及 Web 服务器的概念&#xff0c;然后详细介绍了安装 Tomcat…