【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)

目录

一、简介

二. 双链表的实现

1.准备工作及其注意事项

1.1 先创建三个文件

1.2 注意事项:帮助高效记忆

1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收?

1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗? 

2.双链表的基本功能接口

2.1 初始化哨兵位

 2.2 链表的创建新节点接口

2.3 打印

3. 插入 接口

3.1 尾插法

3.2 头插法

3.3 在 pos 位置之后插入数据

4. 查找

5.删除  接口

5.1 尾删法

5.2 头删法

5.3  删除 pos 位置的数据

6. 销毁链表 接口

6.1 二级指针版 

6.2 一级指针版

7. 总代码概览

List.h

List.c

test.c

三. 顺序表和双向链表的优缺点分析



一、简介

        单向链表在解决那些需要大量查找前趋节点的问题时,效率较为低下,因为单向链表适合“从前往后”查找,并不适合“从后往前”查找。

(若想要看 单链表,可以点击跳转:【数据结构】单向链表实现 超详细)

如果要提高链表的查找效率,那  双向链表(双链表)无疑是首选。

双向链表:即为 字面上的意思是“双向”的链表,如下图所示。


       

  • 双向 指各个节点之间的逻辑关系是 双向的,该链表通常 有一个头节点 -- 称为 哨兵位
  • 概念: 当链表中只有哨兵位节点的时候,我们称该链表为空链表,即哨兵位是不能删除的
  • 从上图还可以看出,双向链表中每个节点包括一下3个部分,分别是:
  • 指针域(前驱节点 prev )、
  • 数据域(用于存储数据元素 data)
  • 指针域(后继节点 next)。

二. 双链表的实现

1.准备工作及其注意事项

1.1 先创建三个文件

 解释这三个文件的作用
 1、头文件List.h  是来声明接口函数,定义链表,将几个公共用到的库函数集合起来
 2、源文件List.c  是用来具体实现接口
 3、源文件test.c  用于接口的测试工作 ,即具体的使用场景

1.2 注意事项:帮助高效记忆

1. 传递指针,都要断言 不能为 NULL ,指针不能为空:assert(phead);
2. 存在 关系到  前趋节点prev 或 后继节点next  的 情况,

    可以 直接定义变量 Prev = .... Next = ....   便于 理清思路,不易乱
3. 所有的 删除接口 都需要 断言链表不能只有 哨兵位: assert(phead->next != phead); 
4. 尾删法: 记得双链表的找尾 不像 单链表需要循环去找尾!!ptail = phead->prev;

5. 初始化创建头节点:推荐使用 调用 创建节点接口  的 方法
5. 销毁接口:推荐使用 传一级指针的方法

1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收?(不看水话,可以直接 看下面 总结 部分

(有点水话,实在不明白思路的可以看一下详细解说的 ”水话“)

         在 单向链表 中,有一个指向 第一个节点的指针 plist,由于 头插法等操作,可能会改变 第一个节点,则 plist 要对应进行 更新,而 要想直接改变一个变量(plist是指针变量)的值,需要传地址,plist 的 &plist 是 一级指针的地址,就要用 二级指针 来接收

         在 双向链表 中,存在 头节点 head ,即 哨兵位,哨兵是 不用进行 改变本身这个节点的 地址的!!

那就有铁铁要问了,不是还要改变 头节点 head( 哨兵位 ) 的指向,要指向 第一个 节点 或 尾节点 吗? 

回答:因为 我们 要改变的 是 双链表节点 结构 中 的 结构体成员变量prev 和 next ,改变 结构体的成员变量 只需要利用 结构体指针 p->prev = .... 或 p->next   = .... 就达到 修改 双链表节点指向 问题了,而你本身并不需要改变 一个节点的地址 p

总结 

总结:修改 双链表节点 的指向,是通过  修改节点结构体内部的 两个成员变量 来实现的只需要用到 结构体指针(即该节点的地址 p),找到  两个成员变量,即可完成修改,因而传递 一级指针就好,不用像 单链表那样还要 传递 一级指针的 地址

1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗? 

回答:不用 因为节点的空间已经还给操作系统了 那个指针域的指针所占据的空间也还回去了 操作系统后续分配内存就会把那块空间覆盖掉 就不会又啥影响

2.双链表的基本功能接口

2.1 初始化哨兵位

初始化哨兵位 第一种方法:传入 指针,进行"加工" 成 哨兵位

// 初始化一个哨兵位:
void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	// 开辟空间是否成功的判断:一般malloc不会失败,失败证明内存不够了,写下面的证明算是好习惯,不写一般没问题
	if (*pphead == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead; // 哨兵位初识化,两个指针都指向自己
}

初始化哨兵位第二种方法:直接一个函数生成一个哨兵位,返回哨兵位就好,不用传指针

LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	// 开辟空间是否成功的判断
	if (phead == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	phead->data = -1; // data 随便定,反正哨兵位data无效
	phead->next = phead->prev = phead;
	return phead;
}

初始化哨兵位 第二种方法的2.0 版本:因为哨兵位的初始化 和 2.2 创建新新节点的方法一样,可以合并调用


LTNode* LTInit_2()
{
	LTNode* phead = LTCreatNode(-1);
	return phead;
}

 2.2 链表的创建新节点接口

// 创建新节点
LTNode* LTCreatNode(LTDataType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof (LTNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->prev = newNode->next = newNode; // 都是 指向自己
	return newNode;
}

2.3 打印

双链表的打印
和 单链表打印一样,都需要循环,但是结束条件不一样
单链表以 pcur = NULL 为结束条件,双链表是 一种循环链表,头尾相连,不会有  pcur = NULL 的情况
正解:既然 哨兵位无有效数据,同时 循环一轮 还是 回到头(哨兵位),干脆:while (pcur != phead)


void LTPrint(LTNode* phead)
{
	assert(phead);// 哨兵位不能为空
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

3. 插入 接口

前言:

// 不需要改变哨兵位,则不需要传二级指针
// 如果需要修改哨兵位的话,则传二级指针

3.1 尾插法

  

双链表的 尾插法
双向链表尾插需不需要找尾的操作 ?

不需要 :ptail = head->prev;  注意这个 等价关系,便于理解下面的代码
尾插法 也称作 在哨兵位之前插入节点/最后一个有效节点之后插入数据

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);// 哨兵位不能为空
	LTNode* newNode = LTCreatNode(x); 
	LTNode* ptail = phead->prev;
	// 三者关系:phead->prev(即ptail)    newNode      phead
	// 处理顺序:先 newNode,  再 phead->prev(即ptail) ,最后  phead:否则会乱套
	// 先 newNode
	newNode->next = phead;
	newNode->prev = ptail; // 就是  phead->prev
	// 再尾节点 ptail = head->prev  ;   ptail  -> next = head-> prev -> next;
	ptail->next = newNode;
	// 最后头节点 
	phead->prev = newNode; 
}

3.2 头插法

双链表的 头插法
注意:头插 是在第一个有效位置进行插入,不是在哨兵位 前面,由于 双链表的循环成环状的特性,若在哨兵位前面,就是尾插了

 

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:phead   newNode   phead->next (即 第一个 有效节点,命名为Next) ;
	// 处理顺序:先 newNode,  再 phead->next(即 第一个 有效节点,命名为Next) ,最后  phead:否则会乱套
	LTNode* Next = phead->next; // 这里就是定义了个变量,便于梳理
	// 先 newNode
	newNode->next = Next;
	newNode->prev = phead;
	// 再 phead->next(即 第一个 有效节点) 
	Next->prev = newNode;
	// 最后头节点 
	phead->next = newNode;
}

3.3 在 pos 位置之后插入数据

在 pos 位置之后插入数据:要配合   4.查找   接口实现

和 头插法思路一样:只是将 哨兵的带头作用 换成了 pos

pos 是通过  4.查找  的接口找的

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	// 先创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 关系三个节点:pos     newNode    pos->next
	// 先处理newNode,后 pos->next , 最后 pos 
	// 注意三者的执行顺序 不能换!! 
	LTNode* Next = pos->next; // 这里将 pos 的下一个节点(pos->next) 命名成 Next (避免和 next 混淆)
	newNode->next = Next;
	newNode->prev = pos;
	Next->prev = newNode;
	pos->next = newNode;
}

4. 查找

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	// 下面是遍历 双链表的模板操作
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

5.删除  接口

5.1 尾删法

// 思路:让 倒数第二个 节点 next 指向 head ,head 指向 倒数第二个节点,最后 free 掉 ptail
// 尾节点前一个节点:ptail->prev = phead->prev->prev
// 尾节点:ptail = phead->prev
// "尾节点前一个节点" 指向 head哨兵位:ptail->prev->next = phead;
// head哨兵位 指向 "尾节点前一个节点" :phead->prev = ptail->prev;
// 最后 free 掉 ptail

 

// 尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//链表为空:只有一个哨兵位节点
	assert(phead->next != phead);
	
	LTNode* ptail = phead->prev;
	ptail->prev->next = phead;
	phead->prev = ptail->prev; 
	free(ptail);
	ptail = NULL;
}

5.2 头删法

    //  被删除的 第一个有效节点:pFirst = phead->next; 

    //  下一个节点 pSecond = pFirst->next;
    //  先 下一个节点 指向 head : pSecond->prev = phead;
    //  后 head 指向 下一个节点 :phead->next = pSecond; 
    //  注意:因为对于循环链表来说, 第一个节点的下个节点 也可以是 哨兵位,所以 只存在一个有效节点,也是可以直接删除第一个节点的、

// 头删
// 删除的是 第一个有效节点
void LTPopFront(LTNode* phead)
{
	assert(phead); // 地址不能为NULL
	assert(phead->next != phead); // 链表不能只有 哨兵位

	LTNode* pFirst = phead->next;  // 要被删除的 第一个节点
	LTNode* pSecond = pFirst->next;
	pSecond->prev = phead;
	phead->next = pSecond;
	free(pFirst);
	pFirst = NULL;
}

5.3  删除 pos 位置的数据

删除 pos 位置的数据:要配合   4.查找   接口实现

void LTErase(LTNode* pos)
{
    assert(pos);
    // 关系三个节点: pos->prev      pos     pos->next     
    LTNode* Prev = pos->prev; //  pos 的前一个 
    LTNode* Next = pos->next; //  pos 的下一个
    Next->prev = Prev;
    Prev->next = Next;
    free(pos);
    pos = NULL;
}

6. 销毁链表 接口

更推荐 一级指针版:手动置为NULL

为了保持 接口的一致性:不然前面接口都是 一级指针,到这里突然 二级指针,当程序交给用户时,会增加记忆的成本

6.1 二级指针版 

// 思路:先将 有效节点删除,后删除哨兵位
// 删除有效节点, 要将下个节点保存起来,不然找不到
// 注意:这里 最后需要   改变  哨兵位 为NULL ,因而要传递地址,用二级指针接收
// 否则,传值 只会影响 形参,哨兵位 需要手动 置为NULL


void LTDestory(LTNode** pphead) 
{
	assert(pphead);// 指针本身不能为 空
	assert(*pphead); // 哨兵位 不能为 空
	LTNode* pcur = (*pphead)->next; // *pphead = phead 即哨兵位 ;还有记得 加括号
	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	// 最后删除 哨兵位
	free(*pphead);
	*pphead = NULL;
}

6.2 一级指针版

// 双链表的 销毁:一级指针版:哨兵位 需要 手动额外 置为空

void LTDestory(LTNode* phead)
{
    assert(phead);// 哨兵位 不能为 空
    LTNode* pcur = (phead)->next; // *pphead = phead 即哨兵位 ;还有记得 加括号
    while (pcur != phead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    // 最后释放掉 形参的指针:这里不是释放 哨兵位
    free(phead);
    phead = NULL;
}

7. 总代码概览

List.h

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

// 创建新节点
typedef int  LTDataType;
typedef struct LTistNode
{
	LTDataType data;
	struct LTistNode* prev;
	struct LTistNode* next;
}LTNode;

// 创建新节点
LTNode* LTCreatNode(LTDataType x);

// 初始化:生成哨兵位
LTNode* LTInit();

// 打印函数
void LTPrint(LTNode* phead);

// 尾插法
void LTPushBack(LTNode* phead, LTDataType x);

// 头插法
void LTPushFront(LTNode* phead, LTDataType x);

// 在 pos 之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

// 尾删法
void LTPopBack(LTNode* phead);

// 头删法
void LTPopFront(LTNode* phead);

// 删除 pos 位置节点
void LTErase(LTNode* pos);

// 查找
LTNode* LTFind(LTNode* phead, LTDataType x);

// 销毁
void LTDestory(LTNode* phead);

List.c

#include"List.h"

// 创建新节点
LTNode* LTCreatNode(LTDataType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->prev = newNode->next = newNode;
	return newNode;
}

// 初始化:生成哨兵位
LTNode* LTInit()
{
	LTNode* head = LTCreatNode(-1);
	return head;
}

// 打印函数
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

// 尾插法
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	LTNode* ptail = phead->prev;
	// 三者关系:ptail   newNode   phead
	newNode->next = phead;
	newNode->prev = ptail;
	ptail->next = newNode;
	phead->prev = newNode;
}

// 头插法
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:phead   newNode   phead->next (定为Next);
	LTNode* Next = phead->next; // 这里就是定义了个变量,便于梳理
	newNode->next = Next;
	newNode->prev = phead;
	Next->prev = newNode;
	phead->next = newNode;
}

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

// 在 pos 之后插入数据:头插法实现逻辑一样
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:pos   newNode   pso->next (定为Next);
	LTNode* Next = pos->next;
	newNode->next = Next;
	newNode->prev = pos;
	Next->prev = newNode;
	pos->next = newNode;
}

// 尾删法:记得双链表的找尾 不像 单链表需要循环去找尾
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 链表不能只有 哨兵位 (删除的接口 都要断言这条)
	// 三者关系:ptail->prev   ptail   head
	LTNode* ptail = phead->prev;
	ptail->prev->next = phead;
	phead->prev = ptail->prev;
	free(ptail);
	ptail = NULL;

}

// 头删法
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead); // 链表不能只有 哨兵位 (删除的接口 都要断言这条)
	// 三者关系:phead    phead->next    phead->next->next(命名为pSecond)
	LTNode* pFirst = phead->next;  // 要被删除的 第一个节点  一定要先保存下来!!
	LTNode* pSecond = pFirst->next;
	pSecond->prev = phead;
	phead->next = pSecond;
	free(pFirst);
	pFirst = NULL;
}

// 删除 pos 位置节点
void LTErase(LTNode* pos)
{
	assert(pos);
	// 关系三个节点:pos->prev    pos     pos->next
	LTNode* Prev = pos->prev; //  pos 的前一个 
	LTNode* Next = pos->next; //  pos 的下一个
	Next->prev = Prev;
	Prev->next = Next;
	free(pos);
	pos = NULL;
}


// 销毁
void LTDestory(LTNode* phead)
{
	assert(phead);
	// 先全部删除有效节点,后删除头节点
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	// 最后释放掉 形参的指针:这里不是释放 哨兵位
	free(phead);
	phead = NULL;
}

test.c

#include"List.h"

void LTTest()
{
	// 创建哨兵位
	LTNode* head = LTInit();  // 这里直接用 head 代表哨兵位:更直观一点,和 前面讲解用的 plist 是一样的

	// 尾插法
	LTPushBack(head, 1);
	LTPushBack(head, 2);
	LTPushBack(head, 3);
	LTPushBack(head, 4); // 1 -> 2 -> 3 -> 4 ->
	printf("测试尾插:");
	LTPrint(head);

	// 头插法
	LTPushFront(head, 5);
	LTPushFront(head, 6);// 6 -> 5 -> 1 -> 2 -> 3 -> 4 ->
	printf("测试头插:");
	LTPrint(head);

	// 查找
	//LTFind(head, 1);

	// 在 pos 之后插入数据:头插法实现逻辑一样
	LTNode* FindRet1 = LTFind(head, 1);
	LTInsert(FindRet1, 100);
	LTNode* FindRet2 = LTFind(head, 2);
	LTInsert(FindRet2, 200); // 6 -> 5 -> 1 -> 100 -> 2 -> 200 -> 3 -> 4 ->
	printf("测试pos 之后插入:");
	LTPrint(head);

	// 头删法
	LTPopFront(head);
	LTPopFront(head); // 1 -> 100 -> 2 -> 200 -> 3 -> 4 ->
	printf("测试头删:");
	LTPrint(head);

	// 尾删法:记得双链表的找尾 不像 单链表需要循环去找尾
	LTPopBack(head);
	LTPopBack(head); // 1 -> 100 -> 2 -> 200 ->
	printf("测试尾删:");
	LTPrint(head);

	// 删除 pos 位置节点
	LTNode* FindRet3 = LTFind(head, 1);
	LTErase(FindRet3); // 100 -> 2 -> 200 ->
	printf("测试删除 pos 位置节点:");
	LTPrint(head);

	// 双链表的 销毁:这里就不演示销毁了
	//LTDesTroy(head);
	//head= NULL;  //哨兵位 需要手动 置为NULL
}
int main()
{
	LTTest();
	return 0;
}

三. 顺序表和双向链表的优缺点分析

不同点

顺序表

链表(单链表)

存储空间

物理上⼀定连续

逻辑连续,但物理上不⼀定连续

机访问

支持O(1)

不支持:O(N)

任意位置插或者删除

可能要搬移素,率低O(N)

指针指向

插入

动态顺序表,空间够时要扩容

没有容的概念

应用场景

素高存储+频繁访问

任意位置插和删除频繁

完。

若上述文章有什么错误,欢迎各位大佬及时指出,我们共同进步!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何过滤离线logcat日志文件?

1.需求&#xff1a; How did Android Studio Logcat to read the files which have save in logcat? I saved some logs and would like to open them with Android Studio - Logcat interface and be able to see the colours and apply some filters just as if the pho…

T113-Pro的buildroot添加gdisk ( GPT disks )出现gptfdisk needs a toolchain w/ C++的解决方法

问题背景&#xff1a; 最近入手了百问网的全志T113-Pro&#xff0c;用Emmc启动发现一张32GB的SD卡在烧录了百问网镜像 100ask-t113-pro_sdcard.img 的系统后&#xff0c;仅有200多M的存储空间。第一时间上百问网论坛看是否有板友也出现类似情况&#xff0c;发现了一个帖子正是描…

7.NFS服务器

目录 1. 简介 1.1. NFS背景介绍 1.2. 生产应用场景 2. NFS工作原理 2.1. 示例图 2.2. 流程 3. NFS的使用 3.1. 安装 3.2. 配置文件 3.3. 主配置文件分析 3.4. 实验1 3.5. NFS账户映射 3.5.1. 实验2&#xff1a; 3.5.2. 实验3 4. autofs自动挂载服务 4.1. 产生原…

Transformer 代码补充

本文是对Transformer - Attention is all you need 论文阅读-CSDN博客以及【李宏毅机器学习】Transformer 内容补充-CSDN博客的补充&#xff0c;是对相关代码的理解。 先说个题外话&#xff0c;在之前李宏毅老师的课程中提到multi-head attention是把得到的qkv分别乘上不同的矩…

030-安全开发-JS应用NodeJS指南原型链污染Express框架功能实现审计

030-安全开发-JS应用&NodeJS指南&原型链污染&Express框架&功能实现&审计 #知识点&#xff1a; 1、NodeJS-开发环境&功能实现 2、NodeJS-安全漏洞&案例分析 3、NodeJS-开发指南&特有漏洞 演示案例&#xff1a; ➢环境搭建-NodeJS-解析安装&…

常用换源总结

1.Ubuntu16.04更换国内源 在Ubuntu系统上使用apt-get install进行软件安装或更新的时候&#xff0c;由于使用的是国外源&#xff0c;导致下载速度很慢或者连接超时&#xff0c;需要更换下载源。 1.将系统原始的源文件进行备份 sudo cp /etc/apt/sources.list /etc/apt/source…

c语言--二进制和其他进制之间的转换

目录 一、前言二、二进制、十进制、十六进制、八进制的组成2.1二进制的组成2.2十进制的组成2.3八进制的组成2.4十六进制的组成 三、二进制转换为十进制3.1 二进制转换为十进制3.2十进制转换为二进制 四、二进制转八进制和十六进制4.1二进制转八进制4.2二进制转换为十六进制 五、…

【安装指南】maven下载、安装与配置详细教程

&#x1f33c;一、概述 maven功能与python的pip类似。 Apache Maven是一个用于软件项目管理和构建的强大工具。它是基于项目对象模型的&#xff0c;用于描述项目的构建配置和依赖关系。以下是一些关键的 Maven 特性和概念&#xff1a; POM&#xff08;Project Object Model&…

Mybatis基础教程及使用细节

本篇主要对Mybatis基础使用进行总结&#xff0c;包括Mybatis的基础操作&#xff0c;使用注解进行增删改查的练习&#xff1b;详细介绍xml映射文件配置过程并且使用xml映射文件进行动态sql语句进行条件查询&#xff1b;为了简化java开发提高效率&#xff0c;介绍一下依赖&#x…

安科瑞电气火灾监控系统在海尔(合肥)创新产业园一期厂房改扩建项目的设计与应用

摘要&#xff1a;介绍海尔&#xff08;合肥&#xff09;创新产业园一期厂房改扩建项目采用安科瑞剩余电流式电气火灾探测器&#xff0c;就地组网方式&#xff0c;通过现场总线通讯远传至后台&#xff0c;从而实现剩余电流式电气火灾监控系统的搭建&#xff0c;完成对现场配电回…

万户 ezOFFICE wpsservlet SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

微信小程序应用商店源码系统 带完整的安装代码包以及搭建教程

随着微信小程序的普及&#xff0c;越来越多的企业和开发者开始关注小程序的开发与运营。为了满足市场需求&#xff0c;小编给大家分享一款微信小程序应用商店源码系统。该系统集成了完整的安装代码包&#xff0c;方便用户快速搭建自己的小程序应用商店。 以下是部分代码示例&a…

typedef

typedef typedef &#xff0c;type表示类型&#xff0c; def就是define&#xff0c; 定义的意思。所以&#xff0c;根据名字我们就可以知道typedef就是类型定义的意思。可以对一个类型进行重新定义。 一般对一个类型重新定义都是这种形式: typedef 类型 重定义 如&#xff…

web学习笔记(十九)

目录 1.作用域 1.1作用域的概念 1.2作用域的分类 1.2.1全局作用域 1.2.2局部作用域 1.2.3块级作用域&#xff08;ES6新增 &#xff09; 2.变量作用域 2.1全局变量 2.2局部变量 3.作用域链 3.1作用域链的定义 4.垃圾回收机制 4.1定义 4.2如何避免内存泄漏 5.预…

1895_分离进程的能力

1895_分离进程的能力 全部学习汇总&#xff1a; g_unix: UNIX系统学习笔记 (gitee.com) 有些理念可能在控制类的嵌入式系统中不好实施&#xff0c;尤其是没有unix这样的系统搭载的情况下。如果是考虑在RTOS的基础上看是否有一些理念可以做尝试&#xff0c;我觉得还是可以有一定…

Android: 深入理解 ‘companion object {}‘

Android: 深入理解 ‘companion object {}’ Kotlin是一种现代的、静态类型的编程语言&#xff0c;它在设计时充分考虑了开发者的生产力和代码的可读性。其中一个独特的特性就是companion object。在本篇博客中&#xff0c;我们将深入探讨这个特性&#xff0c;理解它的工作原理…

一款轻量级、高性能、功能强大的内网穿透代理服务器

简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协议&#xff08;访问内网网站、本地支付接口调试、ssh访问、远程桌面&#xff0c;内网dns解析等等……&#xff09;&#xff0c;此外还支持内网htt…

Node需要了解的知识

Node能执行javascript的原因。 浏览器之所以能执行Javascript代码&#xff0c;因为内部含有v8引擎。Node.js基于v8引擎封装&#xff0c;因此可以执行javascript代码。Node.js环境没有DOM和BOM。DOM能访问HTML所有的节点对象&#xff0c;BOM是浏览器对象。但是node中提供了cons…

网络攻防模拟与城市安全演练 | 图扑数字孪生

在数字化浪潮的推动下&#xff0c;网络攻防模拟和城市安全演练成为维护社会稳定的不可或缺的环节。基于数字孪生技术我们能够在虚拟环境中进行高度真实的网络攻防模拟&#xff0c;为安全专业人员提供实战经验&#xff0c;从而提升应对网络威胁的能力。同时&#xff0c;在城市安…

【Android】二级分类双列表联动Demo

先上图&#xff1a; Demo解释 demo使用的是双列表展示&#xff08;准确的说是三个&#xff0c;二级分类那里嵌套了一个&#xff09;&#xff0c;点击左边的条目&#xff0c;右边的列表会跳转相应的条目&#xff0c;滑动右边的列表&#xff0c;左边的列表也会相应的滑动。 代…