双向链表实现简单的增删查改

news2024/12/23 9:08:22

前言:上次分享了单向链表的增删查改,这次要介绍双向链表的增删查改,其实双向链表也有多种,这次主要介绍结构最复杂但是实现起功能反而最简单的带头双向循环链表,希望我的分享对各位有些许帮助。学习这篇文章的内容最好有这篇文章的基础


目录

一:双向链表的简单介绍

(1) 概述

(2) 图示 (带头双向循环链表)

二:用带头双向循环链表实现简单的增删查改

(1) 大致框架

(2) 四大基本功能函数的实现

1. 创建哨兵位头节点

2. 创建新节点

3. 打印链表

4. 销毁链表

(3) 尾插尾删的实现

1. 尾插

2. 尾删

(3) 头插头删的实现

1. 头插

2. 头删

(4) 特定位置的插入与删除

1. 特定位置的寻找

2. 特定位置的插入

3. 特定位置的删除

三:完整代码及测试样例的展示

(1) Test.c

(2) List.h

(3) List.c

(4) 测试样例的展示


一:双向链表的简单介绍

(1) 概述

双向链表的种类有多种——不带头,带头,循环,非循环等等,这里我们主要介绍最复杂的带头双向循环链表:结构最复杂,有一个存储无效数据的哨兵位头节点,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们函数功能的实现可以很好地体现。

而双向链表与单向链表最本质的区别就是: 单向链表只有一个指针域,而双向链表有两个指针域,所以双向链表可以灵活的找到一个节点的左右节点,使得相对于单向链表而言有很大的优势。

(2) 图示 (带头双向循环链表)


二:用带头双向循环链表实现简单的增删查改

(1) 大致框架

 Test.c——用于测试接口函数的功能

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
void Test1()//测试尾插尾删
{

}
void Test2()//测试头插头删
{

}
void Test3()//测试特定位置的插入与删除
{

}
int main()
{
	Test1();//测试尾插尾删

	//Test2();//测试头插头删

	//Test3();//测试特定位置的插入与删除
	return 0;
}

List.h——用于各种定义

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

//将int重定义为LTDataType,方便链表数据类型的更改(eg:int->double)
typedef int LTDataType;

//定义一个带头双向循环链表
typedef struct ListNode
{
	LTDataType data;//数据域
	struct ListNode* prev;//可以链接前一个节点的指针域
	struct ListNode* next;//可以链接后一个节点的指针域
} LTNode;

LTNode* ListInit();//创建哨兵位头节点
void ListPrint(LTNode* phead);//打印
LTNode* BuyListNode(LTDataType x);//创建新节点
void ListDestroy(LTNode* phead);//销毁链表

void ListPushBack(LTNode* phead, LTDataType x);//尾插
void ListPopBack(LTNode* phead);//尾删
void ListPushFront(LTNode* phead, LTDataType x);//头插
void ListPopFront(LTNode* phead);//头删

LTNode* ListNodeFind(LTNode* phead, LTDataType x);//找到链表中数据为x的第一个节点
void ListInsertNode(LTNode* pos, LTDataType x);//在pos节点前插入节点
void ListEraseNode(LTNode* pos);//删除pos节点

List.c——用于接口函数功能的实现

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* ListInit()//创建哨兵位头节点
{

}

void ListPrint(LTNode* phead)//打印
{

}

LTNode* BuyListNode(LTDataType x)//创建新节点
{

}

void ListPushBack(LTNode* phead, LTDataType x)//尾插
{

}

void ListPopBack(LTNode* phead)//尾删
{

}

void ListPushFront(LTNode* phead, LTDataType x)//头插
{

}

void ListPopFront(LTNode* phead)//头删
{

}

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//找到链表中数据为x的第一个节点
{

}

void ListInsertNode(LTNode* pos, LTDataType x)//在pos节点前插入节点
{

}

void ListEraseNode(LTNode* pos)//删除pos节点
{

}

void ListDestroy(LTNode* phead)//销毁链表
{

}

(2) 四大基本功能函数的实现

1. 创建哨兵位头节点

对于带头双向循环链表而言,哨兵位头节点起到了至关重要的作用,所以实现带头双向循环链表的第一步就是创建一个哨兵位头节点:

LTNode* ListInit()//创建哨兵位头节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* phead = tmp;
	phead->next = phead;
	phead->prev = phead;
	return phead;//返回开辟的头节点
}

带头双向循环链表中头节点的实际结构

2. 创建新节点

从之前分享的单链表增删查改的知识也可以很清楚的知道,在插入节点之前要先创建一个新节点,所以为了方便创建节点,直接分装在一个函数中实现即可:

LTNode* BuyListNode(LTDataType x)//创建新节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* newnode = tmp;
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;//数据域为新节点的有效数据x
	return newnode;
}

3. 打印链表

跟单链表思路相似,遍历链表即可,不同的是二者的终止条件不同,由于这是循环链表,所以链表的终点不是NULL:

void ListPrint(LTNode* phead)//打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)//带头双向循环链表遍历一遍后会回到哨兵位头节点
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4. 销毁链表

与打印的思路相同,遍历链表的过程中不断释放节点,回到头节点即可,这里需要特别注意的是:哨兵位头节点也是动态开辟出来的,所以也要进行释放并置空,而为了维持函数接口传递参数的一致性,该函数接口传递的是表示哨兵位头节点的一级指针,此时形参只是实参的一份临时拷贝,在函数内部改变无法引起其变化,所以哨兵位头节点的置空要在哨兵位头节点所在的作用域函数内实现。

void ListDestroy(LTNode* phead)//销毁链表————遍历即可
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
		phead->next = cur;
		cur->prev = phead;
	}
	free(phead);
}

(3) 尾插尾删的实现

1. 尾插

void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	//1.常规思路———利用结构的特殊,直接形成链接关系即可
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

	//2.调用函数ListInsertNode,简化尾插

}

2. 尾删

void ListPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路————记录尾节点的前一个节点,把前一个结点当作新的尾节点,释放旧的尾节点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	tail = NULL;

	//2.调用函数ListEraseNode,简化尾删

}

(3) 头插头删的实现

1. 头插

void ListPushFront(LTNode* phead,LTDataType x)//头插
{
	assert(phead);
	//1.常规思路————要知道链表真正的起点是哨兵位头节点的后一个节点
	LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;

	//2.调用函数ListInsertNode,简化尾删

}

2. 头删

void ListPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//删除前一定要判空,即不能删除哨兵位头节点
	//1.常规思路
	LTNode* cur = phead->next;
	LTNode* next = phead->next->next;
	free(cur);
	phead->next = next;
	next->prev = phead;

	//2.调用函数ListEraseNode,简化头删	
}

(4) 特定位置的插入与删除

1. 特定位置的寻找

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//找到链表中数据为x的第一个节点并返回
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)//遍历寻找
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2. 特定位置的插入

//在pos节点前插入节点,pos节点是调用ListNodeFind函数得到的
void ListInsertNode(LTNode* pos, LTDataType x)
{
	LTNode* newnode = BuyListNode(x);//得到一个新节点
    //简单的链接过程
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

3. 特定位置的删除

void ListEraseNode(LTNode* pos)//删除pos节点
{
//删除pos节点需要知道pos节点的前一个节点与后一个节点,这样才能在删除后重新形成链接关系
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

注意:对于带头双向循环链表而言,这三个接口函数的实现很重要,其实只要掌握了这三个解控函数,就可以很快的构建起一个带头双向循环链表,因为插入与删除的相关接口函数直接调用这三个函数就能够实现:

//复用两个接口函数实现这些相关函数最重要的一点是真正理解带头双向循环链表的结构(上面那张图很重要)
void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	//1.常规思路
	
	//2.调用函数ListInsertNode,简化尾插
	ListInsertNode(phead, x);//在哨兵位头节点前面插入就可以实现尾插
}

void ListPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	
	//2.调用函数ListEraseNode,简化尾删
	ListEraseNode(phead->prev);//删除哨兵位头节点的前一个节点可以实现尾删
}

void ListPushFront(LTNode* phead, LTDataType x)//头插
{
	assert(phead);
	//1.常规思路
	
	//2.调用函数ListInsertNode,简化尾删
	ListInsertNode(phead->next, x);//在哨兵位头节点后面插入就可以实现头插
}

void ListPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	
	//2.调用函数ListEraseNode,简化头删
	ListEraseNode(phead->next);//删除哨兵位头节点的后一个节点可以实现尾删
}

三:完整代码及测试样例的展示

(1) Test.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

void Test1()//测试尾插尾删
{
	LTNode* phead = ListInit();//哨兵位头节点

	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPushBack(phead, 5);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

void Test2()//测试头插头删
{
	LTNode* phead = ListInit();//哨兵位头节点

	ListPushFront(phead, 1);
	ListPushFront(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

void Test3()//测试特定位置的插入与删除
{
	LTNode* phead = ListInit();//哨兵位头节点
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	LTNode* pos = ListNodeFind(phead, 1);
	if (pos)
	{
		ListInsertNode(pos, 6);
	}
	ListPrint(phead);
	pos = ListNodeFind(phead, 3);
	if (pos)
	{
		ListInsertNode(pos, 7);
	}
	ListPrint(phead);

	pos = ListNodeFind(phead, 5);
	if (pos)
	{
		ListEraseNode(pos);
	}
	ListPrint(phead);
	pos = ListNodeFind(phead, 4);
	if (pos)
	{
		ListEraseNode(pos);
	}
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

int main()
{
	//Test1();//测试尾插尾删

	//Test2();//测试头插头删

	Test3();//测试特定位置的插入与删除
	return 0;
}

(2) List.h

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

//将int重定义为LTDataType,方便链表数据类型的更改(eg:int->double)
typedef int LTDataType;

//定义一个带头双向循环链表
typedef struct ListNode
{
	LTDataType data;//数据域
	struct ListNode* prev;//可以链接前一个节点的指针域
	struct ListNode* next;//可以链接后一个节点的指针域
} LTNode;

LTNode* ListInit();//创建哨兵位头节点
void ListPrint(LTNode* phead);//打印
LTNode* BuyListNode(LTDataType x);//创建新节点
void ListDestroy(LTNode* phead);//销毁链表

void ListPushBack(LTNode* phead, LTDataType x);//尾插
void ListPopBack(LTNode* phead);//尾删
void ListPushFront(LTNode* phead, LTDataType x);//头插
void ListPopFront(LTNode* phead);//头删

LTNode* ListNodeFind(LTNode* phead, LTDataType x);//找到链表中数据为x的第一个节点
void ListInsertNode(LTNode* pos, LTDataType x);//在pos节点前插入节点
void ListEraseNode(LTNode* pos);//删除pos节点

(3) List.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* ListInit()//1.创建哨兵位头节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* phead = tmp;
	phead->next = phead;
	phead->prev = phead;
	return phead;//返回开辟的头节点
}

LTNode* BuyListNode(LTDataType x)//2.创建新节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* newnode = tmp;
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;//数据域为新节点的有效数据x
	return newnode;
}

void ListPrint(LTNode* phead)//3.打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)//带头双向循环链表遍历一遍后会回到哨兵位头节点
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(LTNode* phead, LTDataType x)//4.尾插
{
	assert(phead);
	//1.常规思路
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

	//2.调用函数ListInsertNode,简化尾插
	//ListInsertNode(phead, x);//在哨兵位头节点前面插入就可以实现尾插
}

void ListPopBack(LTNode* phead)//5.尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	tail = NULL;

	//2.调用函数ListEraseNode,简化尾删
	//ListEraseNode(phead->prev);
}

void ListPushFront(LTNode* phead,LTDataType x)//6.头插
{
	assert(phead);
	//1.常规思路
	LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
	//2.调用函数ListInsertNode,简化尾删
	//ListInsertNode(phead->next, x);
}

void ListPopFront(LTNode* phead)//7.头删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	LTNode* cur = phead->next;
	LTNode* next = phead->next->next;
	free(cur);
	phead->next = next;
	next->prev = phead;
	//2.调用函数ListEraseNode,简化头删
	//ListEraseNode(phead->next);
}

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//8.找到链表中数据为x的第一个节点并返回
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)//遍历寻找
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ListInsertNode(LTNode* pos, LTDataType x)//9.在pos节点前插入节点,pos节点是调用ListNodeFind函数得到的
{
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

void ListEraseNode(LTNode* pos)//10.删除pos节点
{
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

void ListDestroy(LTNode* phead)//11.销毁链表————遍历即可
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
		phead->next = cur;
		cur->prev = phead;
	}
	free(phead);
}

(4) 测试样例的展示


总结:

虽然带头双向循环链表的结构比单向链表复杂很多,但是它对功能实现的难易程度以及自身的价值都优于单链表,所以大家在掌握单链表的同时也要掌握好双向链表,再次提醒各位,结构十分重要,结构又可以在图示中很好的体现出来,所以各位在数据结构的学习过程中一定要重视画图以及理解所化的图。就这样,我又分享了一次自己在学习道路上的理解,希望对各位有所帮助,再见。

 

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

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

相关文章

[虾说IT]GIS与三高架构(一)什么是高性能

大家好&#xff0c;我是消失了一个年假的不愿意透露姓名的神秘虾神&#xff0c;这是癸卯兔年虾神的第一个系列&#xff0c;聊聊GIS中的架构设计&#xff0c;不过你如果是做其他架构的也差不多……总之是架构是虾神的本职工作之一&#xff0c;那么培养更多的架构设计者和爱好者&…

基于前馈补偿的PID控制算法及仿真

在高精度伺服控制中&#xff0c;前馈控制可用来提高系统的跟踪性能。经典控制理论中的前馈控制设计是基于复合控制思想&#xff0c;当闭环系统为连续系统时&#xff0c;使前馈环节与闭环系统的传递函数之积为1&#xff0c;从而实现输出完全复现输入。利用前馈控制的思想&#x…

剑指 Offer 05. 替换空格 [C语言]

目录题目思路1代码1结果1思路2代码2结果2该文章只是用于记录考研复试刷题题目 请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20"。 示例 1&#xff1a; 输入&#xff1a;s “We are happy.” 输出&#xff1a;“We%20are%20happy.” 限制&#xff…

pnpm 简介

本文引用自 摸鱼wiki 1. 与npm&#xff0c;yarn性能比较 actioncachelockfilenode_modulesnpmpnpmYarnYarn PnPinstall33.8s20.1s20.3s40.7sinstall✔✔✔2.1s1.4s2.6sn/ainstall✔✔9.1s5.3s7.8s1.7sinstall✔13.5s9.3s14.1s7.7sinstall✔15s17.2s14.2s33.4sinstall✔✔2.5s3s…

2.JSX

JSX(JavaScript XML) 是 JavaScript 的语法扩展&#xff0c;格式上比较像模板语言。React支持JSX 下面两个代码可以实现相同的功能&#xff0c;JSX看起来要简洁一些 目录 1 使用环境 2 React中的JSX 2.1 特殊的属性 2.2 没有子节点的标签 2.3 小括号包裹 3 JSX使用…

vue 实现动态路由

vue-router对象中的addRoutes&#xff0c;用它来动态添加路由配置格式&#xff1a;router.addRoutes([路由配置对象]) this.$router.addRoutes([路由配置对象])举个例子&#xff1a;// 按钮 <button click"hAddRoute">addRoute</button>// 回调 hAddRout…

感染了恶意软件怎么办?

近日&#xff0c;研究人员披露了一种恶意软件&#xff0c;这种恶意软件已经感染了一系列广泛的 Linux 和 Windows 设备。恶意软件攻击事件的频繁发生&#xff0c;除了黑客的恶意攻击外&#xff0c;还有企业内部自身的问题&#xff0c;下面列举了7种容易感染恶意软件的途径和解决…

2023年2月软考高级-信息系统项目管理师【报名入口】

信息系统项目管理师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资…

coresight(六) power requestor

power requestor power requestor属于coresight组件。这个组件用来控制系统的power domain&#xff0c;最多可以控制32个。 如果没有power requestor&#xff0c;通过DAP&#xff0c;只能对整个coresight系统进行上下电操作&#xff0c;但是有了power requestor&#xff0c;可…

2Pai半导体-推出π122E61双通道数字隔离器 智能分压技术 兼容代替Si8622ET-IS

2Pai半导体-推出π122E61双通道数字隔离器 智能分压技术 兼容代替Si8622ET-IS 电路简单、稳定性更高 &#xff0c;具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。 产品传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&…

开源工作流可以解决什么问题?

要了解这个问题&#xff0c;就需要先弄清楚相关概念。为什么要使用开源工作流&#xff0c;可以解决什么问题&#xff1f;如果要实现某个业务目标&#xff0c;提高办公协作效率&#xff0c;就可以用开源工作流在多个参与者之间&#xff0c;借助计算机&#xff0c;按照某种预定规…

Oracle重写sql经典50题

Oracle重写sql经典50题oracle与mysql还是有区别的表的数据只能一条一条的插日期的插入不能想mysql一样直接插&#xff0c;得转换格式mysql里的ifnull&#xff0c;oracle里没有这个函数&#xff0c;用nvl代替mysql里的limit在oracle里也没有&#xff0c;要用rownum查询&#xff…

力扣 76. 最小覆盖子串

一、题目 二、 示例 三、提示 四、 思路与代码实现 1. 思路 本题&#xff0c; 套用的是滑动窗口算法模板;初始化左右窗口边界指针&#xff08;要方便源串取值&#xff09; left 0, right 0&#xff0c; 为什么这样初始化&#xff1f; 若设置窗口索引为左闭右闭区间&#xf…

英语学习打卡day8

2023.1.29 1. affluent adj.富裕的&#xff0c;富足的&#xff0c;流畅的n.支流&#xff0c;富人 flu交通流动、发达-流畅的 affluent society affluent neighborhood 2.conception 概念&#xff0c;观念;受孕&#xff0c;怀孕 conceive v.构思&#xff0c;设想;使受孕&…

【Redis | 黑马点评】短信登陆

文章目录项目概述项目前置准备短信登陆基于Session实现登录流程实现发送短信验证码功能实现短信验证码登录和注册功能实现登录校验拦截器隐藏用户敏感信息集群的Session共享问题基于Redis实现共享Session登录登录拦截器的优化项目概述 短信登录 这一块我们会使用redis共享sess…

ExecutorService线程池

文章目录ExecutorService线程池1 ExecutorService API 介绍1.1 api1.1.1 awaitTermination 方法1.1.2 invokeAll 方法1.1.3 invokeAny方法1.1.4 shutdown 方法1.1.5 shutdownNow方法1.1.6 isShutdown方法1.1.7 submit方法1.1.8 isTerminated方法ExecutorService线程池 1 Execu…

Makefile学习笔记(一)

背景 最近在看ATF代码的时候&#xff0c;想要编译下&#xff0c;实施起来遇到一些问题&#xff0c;其中makefile有些命令&#xff0c;语法不是很清晰&#xff0c;故希望重新系统学习下。学习主要参考跟我一起写Makefile-陈皓.pdf。 第一部分、概述 makefile解决的问题&#…

周期矩形波的傅里叶级数展开(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 当脉冲信号周期不变&#xff0c;脉冲宽度变大时&#xff0c;相邻谱线间隔不变&#xff0c;频谱包络线的零点频率逐渐变小&…

git查看分支、创建分支、合并分支

一、查看的git命令如下&#xff1a; git branch 列出本地已经存在的分支&#xff0c;并且当前分支会用*标记 git branch -r 查看远程版本库的分支列表 git branch -a 查看所有分支列表&#xff08;包括本地和远程&#xff0c;remotes/开头的表示远程分支&#xff09; git bran…

9、位和逗号的运算符与表达式

目录 一、位逻辑运算符与位逻辑表达式 1. 位逻辑运算符 2. 位逻辑表达式 二、逗号运算符与逗号表达式 一、位逻辑运算符与位逻辑表达式 1. 位逻辑运算符 位逻辑运算符包括位逻辑与、位逻辑或、位逻辑非和取补 注意&#xff1a;表中除了最后一个运算符是单目运算符外&…