双向链表专题
在之前的单链表专题中,了解的单链表的结构是如何实现的,以及学习了如何实现单链表得各个功能。单链表虽然也能实现数据的增、删、查、改等功能,但是要找到尾节点或者是要找到指定位置之前的节点时,还是需要遍历链表,这就会使得程序的效率受到影响。因此在本篇中就要来学习另外一种链表——双向链表,在此链表中在实现以上的功能时,不再需要遍历。接下来就开始双向链表专题的学习吧!!!
1.链表的分类
在学习双向链表的实现前我们先要来了解链表可以分为哪些,链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构
列表的带头是指在链表中存在“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的” 。单向还是双向就表示链表是在一个链表的节点中是只能找到下一个节点还是能找到下一个节点并且还能找到前一个节点。循环还是不循环是指链表是否能通过尾节点找到头节点。
因此这些链表就可以按照带头还是不带头,单向还是双向,循环还是不循环分为以下的形式
在这些链表中虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表
单链表就是之前我们实现过的,其具体是不带头单向不循环的链表,而在本篇中要学习的双链表具体就是带头双向循环的链表。
2. 双向链表的实现
2.1双链表的结构
在实现双链表中也是和单链表一样用结构体来表示节点,但在双链表中中表示结构体的节点内部与单链表中有所不同,在其内部会多一个指向前节点的指针变量。
在此在实现节点时定义一个结构体struct ListNode来表示结构体的节点,在其内部有三个成员变量,第一个是一个整型变量data来表示节点中存放的数据信息,第二个是一个结构体指针来存放上一个节点的地址,第三个是一个结构体指针来存放下一个节点的地址。
typedef int LTDataType;//将int重命名,方便之后要修改数据类型时修改
typedef struct ListNode//将结构体重命名,简化结构体的名称
{LTDataType data;//节点内数据struct ListNode* prev;//上一个节点的指针struct ListNode* next;//下一个节点的指针
}LTNode;
2.2程序文件的设置
以下是双链表文件的设置以及各文件中要实现的内容
注:在SList.h中在文件的头部写入以下代码中要使用到的库函数的头文件
2.3初始化双链表
由于我们实现的双链表是带头的,也就是带有哨兵位节点的,所以在双链表中不同于单链表是需要先初始化的。在初始化中就是要创建一个哨兵位的节点。
首先要在List.h内内完成初始化双链表函数的声明
void LTInit(LTNode** pphead);//初始化双链表
将该函数命名为LTInit,该函数的的参数是链表中第一个结构体指针的地址
由于在双链表中也是要在插入等函数中都创建新的节点,所以将创建新节点封装到函数NewNode内,之后再要创建新节点就只需调用该函数
LTNode* NewNode(LTDataType x)//创建新节点 {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("newnode!");exit(1);}newnode->data = x;newnode->prev = newnode->next = newnode; }
在NewNode函数内在使用malloc申请内存空间后就将要存入的数据赋值给节点中的data,并且要在创建新的节点后就要使得该节点是循环的,这就要让newnode内的next指针和prev指针都指向该新节点
之后就是在List.c内完成函数的实现
因为在哨兵位节点内的数据应是随机的,所以在此初始化双链表中创建哨兵位节点时就只需要在调用NewNode函数时传的参数为之后都不会使用到的值,在此传的是-1
void LTInit(LTNode** pphead)//初始化双链表(即创建哨兵位节点)
{*pphead = NewNode(-1);
}
2.4展示双链表
首先要在List.h内完成对打印双链表函数的声明
void LTPrint(LTNode* phead);//打印双链表
将该函数命名为LTPrint,由于不用改变函数的参数,也就是不需要改变指向链表中第一个节点的指针的指向,使用该打印函数进行的是传值调用,函数的参数就是指向链表第一个节点的指针
之后就是在List.c内完成对打印函数的实现
在实现打印函数的代码前先来分析如何来遍历双链表,如以下双链表
一开始让pcur指向头节点的下一个节点,之后打印节点数据后就使得pcur指向该节点的下一个节点,也就是让pcur等于pcur->next,在此一直遍历下去直到pcur指向头节点时就停止pcur的移动。这样就可以完成对双链表的遍历。
因此通过以上的示例分析就可以得出循环的结束条件为pcur=phead,所以在while循环的判断部分就为pcur!=phead
void LTPrint(LTNode* phead)//打印双链表
{LTNode* pcur = phead->next;while (pcur!=phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}
2.5在链表中查找指定数据
要实现一个函数能查找在双链表中是否存在要查找的数据,首先就要在List.h内声明该函数
LTNode* LTFind(LTNode* phead, LTDataType x);//查找指定数据
将该函数命名为LTFind,由于查找是未对函数的参数作出改变,所以该函数进行的也是传值调用,函数的参数有两个,一个是节点的结构体指针,另一个是要查找的数据,函数的返回类型是LTNode*的结构体指针
之后就是在List.c内完成查找函数的实现
在查找数据是否存在也是要遍历双链表,判断每个节点内的数据是否与要查找的数据相同,若相同就返回节点的指针。如果在遍历完双链表后都未找到与要查找数据相同的节点,这时就返回NULL
LTNode* LTFind(LTNode* phead, LTDataType x)//查找 {LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur ->next;}return NULL; }
2.6双链表各功能的实现
2.6.1尾插
要实现一个函数能在双链表的尾部插入数据,首先要在List.h内完成对尾插函数的声明
void LTPushBack(LTNode* phead, LTDataType x);//尾插
将该函数命名为LTPushBack,函数的参数有两个,一个是节点的结构体指针 ,另一个是要插入的数据
接下来我们就来分析如何在双链表中实现尾插,例如以下图示要在双链表的末尾插入节点,也就是要在在d3节点后插入新的节点要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现尾插
将新节点内的prev指针指向d3节点;next指针指向phead节点,再将d3的next指针指向新节点,再将phead的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中尾插新的节点
完成的尾插的分析后之后就是在List.c内完成尾插函数的实现
在尾插过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向头节点phead;将nownode的next指针指向原尾节点。将头节点phead的prev指针指向newnode,将原链表的尾节点phead->prev的next指针指向newnode
在尾插过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空,所以要给phead进行assert断言
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{assert(phead);LTNode* newnode = NewNode(x);//phead phead->prev newnode //改变新节点newnode的指向newnode->next = phead;newnode->prev = phead->prev;//改变尾节点的指向phead->prev->next = newnode;//改变头节点的指向phead->prev = newnode;
}
2.6.2头插
要实现一个函数能在双链表的头部插入数据,首先要在List.h内完成对头插函数的声明
void LTPushFront(LTNode* phead, LTDataType x);//头插
将该函数命名为LTPushFront,函数的参数有两个,一个是节点的结构体指针 ,另一个是要插入的数据
接下来我们就来分析如何在双链表中实现头插,例如以下图示要在双链表的头部插入节点,也就是要在在head节点后插入新的节点要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现头插
将新节点内的prev指针指向phead节点;next指针指向d1节点,再将phead的next指针指向新节点,再将d1的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中头插新的节点
完成的头插的分析后之后就是在List.c内完成头插函数的实现
在头插过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向头节点phead;将nownode的next指针指向原头节点的下一个节点phead->next。将头节点phead的next指针指向newnode,将原链表的原头节点的下一个节点phead->next的prev指针指向newnode
在头插过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空,所以要给phead进行assert断言
void LTPushFront(LTNode* phead, LTDataType x)//头插
{assert(phead);LTNode* newnode = NewNode(x);//phead newnode phead->next//改变新节点newnode的指向newnode->next = phead->next;newnode->prev = phead;//改变头节点下一个节点的指向phead->next->prev = newnode;//改变头节点的指向phead->next = newnode;
}
2.6.3尾删
要实现一个函数能删除双链表尾部的数据,首先要在List.h内完成对尾删函数的声明
void LTPopBack(LTNode* phead);//尾删
将该函数命名为LTPopBack,函数的参数为节点的结构体指针
接下来我们就来分析如何在双链表中实现尾部删除,例如以下图示要删除双链表尾部节点,也就是要释放d3节点的空间要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现尾删
先让头节点的prev指针指向原尾节点前的节点,再让原尾节点前的节点的next指针指向头节点,再释放原来的尾节点。完成这些操作就可以实现在原本的双链表中删除尾部的节点
完成的尾删的分析后之后就是在List.c内完成尾删函数的实现
在尾删过程中本质就是按照以上图所示进行相关的更改,以上创建一个指向原尾节点的指针del,这样可以避免改变指针节点指向后找不到原来的尾节点。之后将头节点phead的prev指针指向del之前的节点del->prev,将原链表的del指向的节点的前一个节点del->next的next指针指向phead。最后再将del指针指向的节点释放,后将指针del置为NULL
在尾删过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空,所以要给phead进行assert断言
同时尾删过程中只有哨兵节点时,无法完成删除,所以要给phead->next!=phead进行assert断言
void LTPopBack(LTNode* phead)//尾删
{assert(phead && phead->next != phead);//phead phead->prev->prev phead->prevLTNode* del = phead->prev;//phead del->prev delphead->prev = del->prev;del->prev->next = phead;free(del);//删除del节点del = NULL;
}
2.6.4头删
要实现一个函数能删除双链表尾部的数据,也就是删除头节点之后的第一个节点,首先要在List.h内完成对头删函数的声明
void LTPopFront(LTNode* phead);//头删
将该函数命名为LTPopFront,函数的参数为节点的结构体指针
接下来我们就来分析如何在双链表中实现头部删除,例如以下图示要删除双链表头部节点,也就是要释放d1节点的空间要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现尾删
先让头节点的next指针指向原尾节点前的节点,再让原尾节点后两位的节点的prev指针指向头节点,再释放原来的head节点之后的节点。完成这些操作就可以实现在原本的双链表中删除尾部的节点
完成的头删的分析后之后就是在List.c内完成头删函数的实现
在头删过程中本质就是按照以上图所示进行相关的更改,以上创建一个指向原头节点之后的第一个节点的指针del,这样可以避免改变指针节点指向后找不到原来的尾节点。之后将头节点phead的next指针指向del之后的节点del->next,将原链表的del指向的节点的后一个节点del->next的prev指针指向phead。最后再将del指针指向的节点释放,后将指针del置为NULL
在头删过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空,所以要给phead进行assert断言
同时头删过程中只有哨兵节点时,无法完成删除,所以要给phead->next!=phead进行assert断言
void LTPopFront(LTNode* phead)//头删
{assert(phead &&phead->next!=phead);//phead phead->next phead->next->nextLTNode* del = phead->next;//phead del del->nextphead->next = del->next;del->next->prev = phead;free(del);//删除del节点del = NULL;
}
2.6.5 在指定位置之后插入
要实现一个函数能在双链表指定位置后插入数据,首先要在List.h内完成对在指定位置之后插入函数的声明
void LTInsert(LTNode* pos, LTDataType x);//在指定pos位置插入
将该函数命名为LTInsert,函数的参数有两个,一个是要插入位置节点的结构体指针,另一个是要插入的数据
接下来我们就来分析如何在双链表中实现在指定位置之后插入节点,例如以下图示要在双链表d1节点后插入新的节点,要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现在指定位置之后插入
将新节点内的prev指针指向d1节点;next指针指向d2节点,再将d1的next指针指向新节点,再将d2的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中在指定位置之后插入新的节点
完成的在指定位置之后插入的分析后之后就是在List.c内完成在指定位置之后插入函数的实现
在在指定位置之后插入的过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向pos节点;将nownode的next指针指向原pos节点的下一个节点pos->next。将pos节点的next指针指向newnode,将原链表的原pos节点的下一个节点pos->next的prev指针指向newnode
在指定位置之后插入过程中要对pos节点的结构体指针进行解引用,所以pos指向的节点指针不能为空,所以要给pos进行assert断言
void LTInsert(LTNode* pos, LTDataType x)//在指定pos位置之后插入
{assert(pos);//pos newnode pos->nextLTNode* newnode = NewNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
2.6.6删除指定位置
要实现一个函数能删除双链表指定位置的数据,首先要在List.h内完成对指定位置删除函数的声明
void LTErase(LTNode* pos);//删除指定pos位置的节点
将该函数命名为LTErase,函数的参数为要删除位置节点的结构体指针
接下来我们就来分析如何在双链表中实现指定位置删除,例如以下图示要删除d2节点,也就是要释放d2节点的空间要进行什么样的操作呢?
这时就只需要作出以下的改变就可以实现删除指定位置的节点
先让d1节点的next指针指向d3节点,再让d3节点的prev指针指向d1节点,再释放原来的d2节点。完成这些操作就可以实现在原本的双链表中删除d2节点
完成的指定位置删除的分析后之后就是在List.c内完成指定位置删除函数的实现
在删除指定位置也就是删除pos指针指向的节点过程中本质就是按照以上图所示进行相关的更改,将pos指针指向的节点的前一个节点pos->prev的next指针指向pos指针指向的节点之后的节点pos->next,将pos指针指向的节点的后一个节点pos->next的prev指针指向pos指针指向的节点之前的节点pos->prev。最后再将pos指针指向的节点释放,后将指针pos置为NULL
在删除指定位置过程中要对pos节点的结构体指针进行解引用,所以pos指向的节点指针不能为空,所以要给pos进行assert断言
void LTErase(LTNode* pos)//删除指定pos位置的节点
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
但这时就会存在一个问题:销毁双链表函数进行的是传值调用,形参的改变无法影响实参,所以在以下代码中最后将pos=NULL,不会使得调用函数外的结构体指针置为NULL,这就需要在调用完该函数后再将结构体指针置为NULL
2.7销毁双链表
由于我们实现的双链表的节点空间是动态内存开辟的,所以在双链表中是需要在使用完后销毁节点的。
首先要在List.h内内完成销毁双链表函数的声明
void LTDestory(LTNode* phead);//销毁双链表
将该函数命名为LTDestory,该函数的的参数是链表中第一个结构体指针的地址
之后就是在List.c内实现销毁函数
以下函数是通过遍历的方法来将链表中的节点一个个释放,最后遍历完出函数时就只剩下phead指向的头节点,这时再释放该节点就可以将双链表全部销毁
void LTDestory(LTNode* phead)//销毁双链表
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
但这时就会存在一个问题:销毁双链表函数进行的是传值调用,形参的改变无法影响实参,所以在以下代码中最后将phead=NULL,不会使得调用函数外的结构体指针置为NULL,这就需要在调用完该函数后再将结构体指针置为NULL
#include"List.h"void test()
{LTNode* plist = NULL;plist=LTInit();//测试尾插LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPrint(plist);//测试销毁双链表LTDestory(plist);plist = NULL;
}int main()
{test();return 0;
}
那么有的读者就会发出疑问:为什么不直接在LTErase函数和LTDestory的参数直接传传二级指针呢?
其实理论上LTErase和LTDestory参数理论上是要传二级,因为我们需要让形参的改变影响到实参,但是为了保持接口的一致性才传的一级指针
传一级指针的问题是:当形参phead置为NULL后,实参plist不会被修改为NULL,因此解决方法是调用完函数后手动将实参置为NULL
3.双链表完整代码
为了保持接口的一致性可以将初始化函数修改为以下形式
LTNode* LTInit()//初始化双链表
{LTNode* phead = NewNode(-1);return phead;
}
List.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}LTNode;//void LTInit(LTNode** pphead);//初始化双链表
LTNode* LTInit();//初始化双链表void LTDestory(LTNode* phead);//销毁双链表void LTPrint(LTNode* phead);//打印双链表
LTNode* LTFind(LTNode* phead, LTDataType x);//查找void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPushBack(LTNode* phead, LTDataType x);//尾插void LTPopFront(LTNode* phead);//头删
void LTPopBack(LTNode* phead);//尾删void LTInsert(LTNode* pos, LTDataType x);//在指定pos位置插入
void LTErase(LTNode* pos);//删除指定pos位置的节点
List.c
#include"List.h"LTNode* NewNode(LTDataType x)//创建新节点
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("newnode!");exit(1);}newnode->data = x;newnode->prev = newnode->next = newnode;
}void LTPrint(LTNode* phead)//打印双链表
{LTNode* pcur = phead->next;while (pcur!=phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}LTNode* LTFind(LTNode* phead, LTDataType x)//查找
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur ->next;}return NULL;
}//void LTInit(LTNode** pphead)//初始化双链表(即创建哨兵位节点)
//{
// *pphead = NewNode(-1);
//}LTNode* LTInit()//初始化双链表
{LTNode* phead = NewNode(-1);return phead;
}void LTDestory(LTNode* phead)//销毁双链表
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;}void LTPushFront(LTNode* phead, LTDataType x)//头插
{assert(phead);LTNode* newnode = NewNode(x);//phead newnode phead->nextnewnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}void LTPushBack(LTNode* phead, LTDataType x)//尾插
{assert(phead);LTNode* newnode = NewNode(x);//phead phead->prev newnode newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}void LTPopFront(LTNode* phead)//头删
{assert(phead &&phead->next!=phead);//phead phead->next phead->next->nextLTNode* del = phead->next;//phead del del->nextphead->next = del->next;del->next->prev = phead;free(del);//删除del节点del = NULL;}void LTPopBack(LTNode* phead)//尾删
{assert(phead && phead->next != phead);//phead phead->prev->prev phead->prevLTNode* del = phead->prev;//phead del->prev delphead->prev = del->prev;del->prev->next = phead;free(del);//删除del节点del = NULL;
}void LTInsert(LTNode* pos, LTDataType x)//在指定pos位置插入
{assert(pos);//pos newnode pos->nextLTNode* newnode = NewNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;}void LTErase(LTNode* pos)//删除指定pos位置的节点
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
相关文章:

双向链表专题
在之前的单链表专题中,了解的单链表的结构是如何实现的,以及学习了如何实现单链表得各个功能。单链表虽然也能实现数据的增、删、查、改等功能,但是要找到尾节点或者是要找到指定位置之前的节点时,还是需要遍历链表,这…...

SpringCoud组件
一、使用SpringCloudAlibaba <dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2023.0.1.0</version><…...

向量的定义和解释
这是一个向量: 向量具有大小(大小)和方向: 线的长度显示其大小,箭头指向方向。 在这里玩一个: 我们可以通过将它们从头到尾连接来添加两个向量: 无论我们添加它们的顺序如何,我们都…...

IoTDB 集群高效管理:一键启停功能介绍
如何快速启动、停止 IoTDB 集群节点的功能详解! 在部署 IoTDB 集群时,对于基础的单机模式,启动过程相对简单,仅需执行 start-standalone 脚本来启动 1 个 ConfigNode 节点和 1 个 DataNode 节点。然而,对于更高级的分布…...

一个spring boot项目的启动过程分析
1、web.xml 定义入口类 <context-param><param-name>contextConfigLocation</param-name><param-value>com.baosight.ApplicationBoot</param-value> </context-param> 2、主入口类: ApplicationBoot,SpringBoot项目的mian函数 SpringBo…...

智驭未来:人工智能与目标检测的深度交融
在科技日新月异的今天,人工智能(AI)如同一股不可阻挡的浪潮,正以前所未有的速度重塑着我们的世界。在众多AI应用领域中,目标检测以其独特的魅力和广泛的应用前景,成为了连接现实与智能世界的桥梁。本文旨在…...

01MFC建立单个文件类型——画线
文章目录 选择模式初始化文件作用解析各初始化文件解析 类导向创建鼠标按键按下抬起操作函数添加一个变量记录起始位置注意事项代码实现效果图 虚实/颜色线 选择模式 初始化文件作用解析 运行: 各初始化文件解析 MFC(Microsoft Foundation Classes&am…...
免杀中用到的工具
🟢 绝大部分无法直接生成免杀木马,开发、测试免杀时会用到。 工具简称 概述 工具来源 下载路径 x64dbg 中文版安装程序(Jan 6 2024).exe 52pojie hellshell 官方的加密或混淆shellcode github Releases ORCA / HellShell GitLab hellshe…...
[vite] Pre-transform error: Cannot find package pnpm路径过长导致运行报错
下了套vue3的代码,执行pnpm install初始化,使用vite启动,启动后访问就会报错 报错信息 ERROR 16:40:53 [vite] Pre-transform error: Cannot find package E:\work\VSCodeProjectWork\jeecg\xxxxxxxxx-next\xxxxxxxxx-next-jeecgBoot-vue3\…...
Promise总结
Promise.then() 的返回值仍然是 Promise 对象 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>D…...
ROI 接口便捷修改
传入的图片截取ROI后再进入识别接口 (识别接口比ROI接口的函数参数少一个传入的ROI) 无点只有点集 返回双点集 //平直冷侧翅片 bool ImageProcessingTest::straightColdSideFin_ROI(cv::Mat img, cv::Rect ROI, std::vector<cv::Point>& topL…...

jenkins打包java项目报错Error: Unable to access jarfile tlm-admin.jar
jenkins打包boot项目 自动重启脚本失败 查看了一下项目日志报错: Error: Unable to access jarfile tlm-admin.jar我检查了一下这个配置,感觉没有问题,包可以正常打, cd 到项目目录下面,手动执行这个sh脚本也是能正常…...
SQL Server设置端口:跨平台指南
在使用SQL Server时,设置或修改其监听的端口是确保数据库服务安全访问和高效管理的重要步骤。由于SQL Server可以部署在多种操作系统上,包括Windows、Linux和Docker容器等,因此设置端口的步骤和方法也会因平台而异。本文将为您提供一个跨平台…...

ActiveMQ-CVE-2023-46604
Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞 OpenWire协议在ActiveMQ中被用于多语言客户端与服务端通信。在Apache ActvieMQ5.18.2版本以及以前,OpenWire协议通信过程中存在一处反序列化漏洞,该漏洞可以允许具有网络访问权限的远程攻击者通过操作…...

TensorBoard ,PIL 和 OpenCV 在深度学习中的应用
重要工具介绍 TensorBoard: 是一个TensorFlow提供的强大工具,用于可视化和理解深度学习模型的训练过程和结果。下面我将介绍TensorBoard的相关知识和使用方法。 TensorBoard 简介 TensorBoard是TensorFlow提供的一个可视化工具,用于&#x…...

【超音速 专利 CN117576413A】基于全连接网络分类模型的AI涂布抓边处理方法及系统
申请号CN202311568976.4公开号(公开)CN117576413A申请日2023.11.22申请人(公开)超音速人工智能科技股份有限公司发明人(公开)张俊峰(总); 杨培文(总); 沈俊羽…...

iPhone数据恢复篇:iPhone 数据恢复软件有哪些
问题:iPhone 15 最好的免费恢复软件是什么?我一直在寻找一个恢复程序来恢复从iPhone中意外删除的照片,联系人和消息,但是我有很多选择。 谷歌一下,你会发现许多付费或免费的iPhone数据恢复工具,声称它们可…...

Html5+Css3学习笔记
Html5 CSS3 一、概念 1.什么是html5 html: Hyper Text Markup Language ( 超文本标记语言) 文本:记事本 超文本: 文字、图片、音频、视频、动画等等(网页) html语言经过浏览器的编译显示成超文本 开发者使用5种浏览器…...

WPF学习(2) -- 样式基础
一、代码 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/2008&…...

独家揭秘!五大内网穿透神器,访问你的私有服务
本文精心筛选了五款炙手可热的内网穿透工具,它们各怀绝技,无论您是企业用户、独立开发者,还是技术探索者,这篇文章都物有所值,废话不多说,主角们即将上场。 目录 1. 巴比达 - 安全至上的企业护航者 2. 花…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...