数据结构双向链表

news2024/11/28 12:52:57

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

那么双向链表相对于单项链表就是可以即向后访问又可以向前访问

那么他的结构相对于单链表就复杂很多了

 

可以看到每一个结点 都有 自己的地址 然后是 指向下一个结点的next域 以及指向上一个地址的prior域

那么在插入删除的时候 我们需要修改更多地方 

例如 需要修改我们的 next prior 以及下一个结点的prior和上一个结点的next

而且我们在修改和使用时要考虑到是否存在 next或者prior

那么我们在结构体设计上就要有所不同

#pragma once 

//双向链表的结构体设计:

typedef int ELEM_TYPE;
typedef struct DNode
{
	ELEM_TYPE data;//数据域
	struct DNode *next;//直接后继指针
	struct DNode *prior;//直接前驱指针
}DNode, *PDNode;

可实现的操作:
//初始化
void Init_dlist(struct DNode * pdlist);

//头插
bool Insert_head(struct DNode *pdlist, ELEM_TYPE val);

//尾插
bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val);

//按位置插
bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val);

//头删
bool Del_head(struct DNode *pdlist);

//尾删
bool Del_tail(struct DNode *pdlist);

//按位置删
bool Del_pos(struct DNode *pdlist, int pos);

//按值删
bool Del_val(struct DNode *pdlist, ELEM_TYPE val);

//查找 //查找到,返回的是查找到的这个节点的地址
struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val);

//获取有效值个数
int Get_length(struct DNode *pdlist);

//判空
bool IsEmpty(struct DNode *pdlist);

//清空
void Clear(struct DNode *pdlist);

//销毁1 无限头删
void Destroy1(struct DNode *pdlist);

//销毁2 不借助头结点,有两个辅助指针
void Destroy2(struct DNode *pdlist);

//打印
void Show(struct DNode *pdlist);




我们可以看到 结构体设计时我们需要设计 一个数据域 和 两个指针域

然后其他的实现函数的参数和之前学习的单链表没有什么不同 接下来我们重点看实现代码

首先是初始化 

我们只使用next和prior域

 

void Init_dlist(struct DNode * pdlist)
{
	assert(pdlist != NULL);

	//pdlist->data 头结点的数据域不使用
	pdlist->next = NULL;
	pdlist->prior = NULL;
}

我们把他的头节点的next和prior赋值为空 

然后是头插

但是头插时我们就要考虑顺序问题和 待插入结点的下一个结点的存在问题

如果我们先修改 待插入结点的上一个结点的next域 那么我们在后续使用时就会因为顺序

产生问题

比如

 因此我们可以调转其他步骤的顺序 但是三号 顺序一定是最后一个

bool Insert_head(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理
	assert(pdlist != NULL);

	//1.购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.找到合适的插入位置(其实就是找到插入在哪一个节点后边,用指针p指向)
	//因为是头插,所以直接使用pdlist即可

	//3.插入   我们的规则是:1,2,4,3
	//         先处理自身的两个指针域(1,2)
	//         再处理插入位置的下一个节点的prior域(4),但是4有特例(空链表进行头插),不存在
	//         最后处理插入位置的上一个节点的next域(3)

	pnewnode->next = pdlist->next;//1
	pnewnode->prior = pdlist;//2
	if(pdlist->next != NULL)//说明不是空链表,则不是特例,4存在
	{
		//此时,插入位置的下一个节点可以通过pdlist->next访问到,还可以通过pnewnode->next访问到
		pdlist->next->prior = pnewnode;//4       
		//pnewnode->next->prior = pnewnode;//4
	}
	pdlist->next = pnewnode;

	return NULL;
}

 可以看到 我们实现了第一步和第二步时我们 要判断是否存在四

因为如果不存在四的话 我们对四操作就会变成野指针问题 可能会访问到不能访问的空间 有可能会引发崩溃

然后是尾插

尾插的话 那么它没有 第四步骤 

 

bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val)
{
	//0:
	assert(pdlist != NULL);
	
	//1.购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.找到合适的插入位置,用指针p指向插入位置的前一个节点
	//判断是否使用带前驱的for循环
	struct DNode *p = pdlist;
	for(; p->next!=NULL; p=p->next);

	//3.插入(不存在特殊情况,每一种情况都需要修改三个指针域)
	//按照之前编号顺序,修改的这三个指针域分别是1,2,(4不存在),3
	pnewnode->next = p->next;//pnewnode->next = NULL;//1
	pnewnode->prior = p;//2
	//4不存在
	p->next = pnewnode;//3
	
	return true;
}

 然后是按位置插入 

只要掌握了插入时的顺序问题 在按照函数功能的要求改变即可

 

bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val)
{
	//因为,在写这个函数之前,头插和尾插已经实现,所以这里可以直接调用
	//0.安全性处理
	assert(pdlist != NULL);
	assert(pos>=0 && pos<=Get_length(pdlist));

	//1.分类处理,将头插和尾插的情况,分别调用对应的函数处理
	if(pos == 0)//头插
	{
		return Insert_head(pdlist, val);
	}
	if(pos == Get_length(pdlist))//尾插
	{
		return Insert_tail(pdlist, val);
	}
	//如果既不是头插,也不是尾插,则只有可能是中间插入,1,2,4,3,都存在

	//2.剩下来的都是中间位置插入,都正常情况,修改4个指针域
	//2.1 购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.2 找到合适的插入位置,指针p(pos=="几",指针p从头结点出发向后走pos步)
	struct DNode *p = pdlist;
	for(int i=0; i<pos; i++)
	{
		p = p->next;
	}

	//2,3 正常插入,1,2,4,3 都存在,4不需要去判断
	pnewnode->next = p->next;//1
	pnewnode->prior = p;//2
	p->next->prior = pnewnode;//4 pnewnode->next->prior = pnownode; 
	p->next = pnewnode;//3

	return true;
}

 这里需要注意 根据 位置不同 我们可以直接调用 头插 尾插 和普通插入

然后是删除 在插入和删除前我们都需要判断 

但是这里的双向链表没有大小 因此 插入时不需要判满

但是会存在空链表 我们需要在删除时进行 判空操作

bool Del_head(struct DNode *pdlist)
{
	//0.安全性处理
	assert(pdlist != NULL);
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.用指针p指向待删除节点
	struct DNode *p = pdlist->next;

	//2.用指针q指向待删除节点的上一个节点
	//因为是头删,所以这里指针q用指针pdlist代替

	//3.跨越指向(存在特例,正常情况下需要修改两个指针域,而特例时,只需要修改一个指针域)
	pdlist->next = p->next;
	if(p->next != NULL)//先得判断待删除节点的下一个节点是否存在
	{
		p->next->prior = pdlist;
	}

	//4.释放
	free(p);

	return true;
}
bool IsEmpty(struct DNode *pdlist)
{
	return pdlist->next == NULL;
}

 头删时也存在特列 我们头删时

如果只剩下最后一个结点 我们就只需要改变一个地方即可

尾删

 

bool Del_tail(struct DNode *pdlist)
{
	//0.
	assert(pdlist != NULL);
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.用指针p指向待删除节点
	struct DNode *p = pdlist;
	for(; p->next!=NULL; p=p->next);

	//2.用指针q指向待删除节点的上一个节点
	struct DNode *q = pdlist;
	for(; q->next!=p; q=q->next);


	//3.跨越指向(不存在特例,永远只需要去修改待删除节点的前一个节点的next域)
	q->next = p->next;//q->next = NULL;

	//4.释放
	free(p);

	return true;
}

 按位置删除

 

bool Del_pos(struct DNode *pdlist, int pos)
{
	//0.安全性处理
	assert(pdlist != NULL);
	assert(pos >=0 && pos<Get_length(pdlist));
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.分类处理,将头删和尾删的情况,分别调用对应的函数处理
	if(pos == 0)//头删
	{
		return Del_head(pdlist);
	}
	if(pos == Get_length(pdlist)-1)//尾删
	{
		return Del_tail(pdlist);
	}
	//如果既不是头删,也不是尾删,则只有可能是中间删除,则统一需要修改两个指针域


	//2.剩下来的都是中间位置删除,统一需要修改两个指针域
	//2.1 找到q,让q从头结点开始向后走pos步
	struct DNode *q = pdlist;
	for(int i=0; i<pos; i++)
	{
		q = q->next;
	}

	//2.2 找到p,p=q->next
	struct DNode *p = q->next;

	//2.3 跨越指向+释放
	q->next = p->next;
	p->next->prior = q;

	free(p);

	return true;

}

还有个按值删除 然后 

bool Del_val(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理

	//1.用指针p指向待删除节点,用search函数
	struct DNode *p = Search(pdlist, val);
	if(p == NULL)
	{
		return false;
	}
	//此时,代码执行到这里,可以保证待删除节点存在,且现在用指针p指向

	//2.用指针q指向待删除节点的上一个节点
	struct DNode *q = pdlist;
	for( ; q->next!=p; q=q->next);

	//3.跨越指向(有可能存在特例,例如如果待删除节点是尾结点,则只需要处理一个指针域,反之都是两个)
	if(p->next == NULL)//判断待删除节点是否是尾结点
	{
		q->next = NULL;//q->next = p->next;
	}
	else//如果待删除节点不是尾结点
	{
		q->next = p->next;
		p->next->prior = q;
	}

	//4.释放
	free(p);

	return true;
}

//查找 //查找到,返回的是查找到的这个节点的地址
struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理
	//1.判断使用哪种for循环
	//使用不需要前驱的for循环

	struct DNode *p = pdlist->next;
	for(; p!=NULL; p=p->next)
	{
		if(p->data == val)
		{
			return p;
		}
	}

	return NULL;
}

还有使用 不带头节点的循环获取有效值个数

int Get_length(struct DNode *pdlist)
{
	//assert
	//使用不需要前驱的for循环
	int count = 0;

	struct DNode *p = pdlist->next;
	for(; p!=NULL; p=p->next)
	{
		count++;
	}

	return count;
}

之后依旧是和之前一样的双销毁

void Clear(struct DNode *pdlist)
{
	Destroy1(pdlist);
}

//销毁1 无限头删
void Destroy1(struct DNode *pdlist)
{
	//assert
	while(!IsEmpty(pdlist))
	{
		Del_head(pdlist);
	}

}

//销毁2 不借助头结点,有两个辅助指针
void Destroy2(struct DNode *pdlist)
{
	assert(pdlist != NULL);
	struct DNode *p = pdlist->next;
	struct DNode *q = NULL;

	pdlist->next = NULL;

	while(p != NULL)
	{
		q = p->next;
		free(p);
		p = q;
	}
}

完整代码

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "dlist.h"

可实现的操作:
//初始化
void Init_dlist(struct DNode * pdlist)
{
	assert(pdlist != NULL);

	//pdlist->data 头结点的数据域不使用
	pdlist->next = NULL;
	pdlist->prior = NULL;
}

//头插
bool Insert_head(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理
	assert(pdlist != NULL);

	//1.购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.找到合适的插入位置(其实就是找到插入在哪一个节点后边,用指针p指向)
	//因为是头插,所以直接使用pdlist即可

	//3.插入   我们的规则是:1,2,4,3
	//         先处理自身的两个指针域(1,2)
	//         再处理插入位置的下一个节点的prior域(4),但是4有特例(空链表进行头插),不存在
	//         最后处理插入位置的上一个节点的next域(3)

	pnewnode->next = pdlist->next;//1
	pnewnode->prior = pdlist;//2
	if(pdlist->next != NULL)//说明不是空链表,则不是特例,4存在
	{
		//此时,插入位置的下一个节点可以通过pdlist->next访问到,还可以通过pnewnode->next访问到
		pdlist->next->prior = pnewnode;//4       
		//pnewnode->next->prior = pnewnode;//4
	}
	pdlist->next = pnewnode;

	return NULL;
}

//尾插
bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val)
{
	//0:
	assert(pdlist != NULL);
	
	//1.购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.找到合适的插入位置,用指针p指向插入位置的前一个节点
	//判断是否使用带前驱的for循环
	struct DNode *p = pdlist;
	for(; p->next!=NULL; p=p->next);

	//3.插入(不存在特殊情况,每一种情况都需要修改三个指针域)
	//按照之前编号顺序,修改的这三个指针域分别是1,2,(4不存在),3
	pnewnode->next = p->next;//pnewnode->next = NULL;//1
	pnewnode->prior = p;//2
	//4不存在
	p->next = pnewnode;//3
	
	return true;
}

//按位置插
bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val)
{
	//因为,在写这个函数之前,头插和尾插已经实现,所以这里可以直接调用
	//0.安全性处理
	assert(pdlist != NULL);
	assert(pos>=0 && pos<=Get_length(pdlist));

	//1.分类处理,将头插和尾插的情况,分别调用对应的函数处理
	if(pos == 0)//头插
	{
		return Insert_head(pdlist, val);
	}
	if(pos == Get_length(pdlist))//尾插
	{
		return Insert_tail(pdlist, val);
	}
	//如果既不是头插,也不是尾插,则只有可能是中间插入,1,2,4,3,都存在

	//2.剩下来的都是中间位置插入,都正常情况,修改4个指针域
	//2.1 购买新节点
	struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.2 找到合适的插入位置,指针p(pos=="几",指针p从头结点出发向后走pos步)
	struct DNode *p = pdlist;
	for(int i=0; i<pos; i++)
	{
		p = p->next;
	}

	//2,3 正常插入,1,2,4,3 都存在,4不需要去判断
	pnewnode->next = p->next;//1
	pnewnode->prior = p;//2
	p->next->prior = pnewnode;//4 pnewnode->next->prior = pnownode; 
	p->next = pnewnode;//3

	return true;
}

//头删   //这里写的也要注意,也存在特例
bool Del_head(struct DNode *pdlist)
{
	//0.安全性处理
	assert(pdlist != NULL);
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.用指针p指向待删除节点
	struct DNode *p = pdlist->next;

	//2.用指针q指向待删除节点的上一个节点
	//因为是头删,所以这里指针q用指针pdlist代替

	//3.跨越指向(存在特例,正常情况下需要修改两个指针域,而特例时,只需要修改一个指针域)
	pdlist->next = p->next;
	if(p->next != NULL)//先得判断待删除节点的下一个节点是否存在
	{
		p->next->prior = pdlist;
	}

	//4.释放
	free(p);

	return true;
}

//尾删
bool Del_tail(struct DNode *pdlist)
{
	//0.
	assert(pdlist != NULL);
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.用指针p指向待删除节点
	struct DNode *p = pdlist;
	for(; p->next!=NULL; p=p->next);

	//2.用指针q指向待删除节点的上一个节点
	struct DNode *q = pdlist;
	for(; q->next!=p; q=q->next);


	//3.跨越指向(不存在特例,永远只需要去修改待删除节点的前一个节点的next域)
	q->next = p->next;//q->next = NULL;

	//4.释放
	free(p);

	return true;
}

//按位置删
bool Del_pos(struct DNode *pdlist, int pos)
{
	//0.安全性处理
	assert(pdlist != NULL);
	assert(pos >=0 && pos<Get_length(pdlist));
	if(IsEmpty(pdlist))
	{
		return false;
	}

	//1.分类处理,将头删和尾删的情况,分别调用对应的函数处理
	if(pos == 0)//头删
	{
		return Del_head(pdlist);
	}
	if(pos == Get_length(pdlist)-1)//尾删
	{
		return Del_tail(pdlist);
	}
	//如果既不是头删,也不是尾删,则只有可能是中间删除,则统一需要修改两个指针域


	//2.剩下来的都是中间位置删除,统一需要修改两个指针域
	//2.1 找到q,让q从头结点开始向后走pos步
	struct DNode *q = pdlist;
	for(int i=0; i<pos; i++)
	{
		q = q->next;
	}

	//2.2 找到p,p=q->next
	struct DNode *p = q->next;

	//2.3 跨越指向+释放
	q->next = p->next;
	p->next->prior = q;

	free(p);

	return true;

}

//按值删
bool Del_val(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理

	//1.用指针p指向待删除节点,用search函数
	struct DNode *p = Search(pdlist, val);
	if(p == NULL)
	{
		return false;
	}
	//此时,代码执行到这里,可以保证待删除节点存在,且现在用指针p指向

	//2.用指针q指向待删除节点的上一个节点
	struct DNode *q = pdlist;
	for( ; q->next!=p; q=q->next);

	//3.跨越指向(有可能存在特例,例如如果待删除节点是尾结点,则只需要处理一个指针域,反之都是两个)
	if(p->next == NULL)//判断待删除节点是否是尾结点
	{
		q->next = NULL;//q->next = p->next;
	}
	else//如果待删除节点不是尾结点
	{
		q->next = p->next;
		p->next->prior = q;
	}

	//4.释放
	free(p);

	return true;
}

//查找 //查找到,返回的是查找到的这个节点的地址
struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val)
{
	//0.安全性处理
	//1.判断使用哪种for循环
	//使用不需要前驱的for循环

	struct DNode *p = pdlist->next;
	for(; p!=NULL; p=p->next)
	{
		if(p->data == val)
		{
			return p;
		}
	}

	return NULL;
}

//获取有效值个数
int Get_length(struct DNode *pdlist)
{
	//assert
	//使用不需要前驱的for循环
	int count = 0;

	struct DNode *p = pdlist->next;
	for(; p!=NULL; p=p->next)
	{
		count++;
	}

	return count;
}

//判空
bool IsEmpty(struct DNode *pdlist)
{
	return pdlist->next == NULL;
}

//清空
void Clear(struct DNode *pdlist)
{
	Destroy1(pdlist);
}

//销毁1 无限头删
void Destroy1(struct DNode *pdlist)
{
	//assert
	while(!IsEmpty(pdlist))
	{
		Del_head(pdlist);
	}

}

//销毁2 不借助头结点,有两个辅助指针
void Destroy2(struct DNode *pdlist)
{
	assert(pdlist != NULL);
	struct DNode *p = pdlist->next;
	struct DNode *q = NULL;

	pdlist->next = NULL;

	while(p != NULL)
	{
		q = p->next;
		free(p);
		p = q;
	}
}

//打印
void Show(struct DNode *pdlist)
{
	//assert
	//使用不需要前驱的for循环

	struct DNode *p = pdlist->next;
	for(; p!=NULL; p=p->next)
	{
		printf("%d ", p->data);
	}
	printf("\n");

}

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

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

相关文章

WPF入门第三篇 ControlTemplate、Trigger与Storyboard

ControlTemplate、Trigger与Storyboard ControlTemplate通常用在Style中&#xff0c;Trigger通常作为ControlTemplate的一部分&#xff0c;StoryBoard表示动画效果&#xff0c;下面将通过对Button按钮设置这几项来简单说明这几项的用法。 在MainWindow中添加一个Button按钮&am…

Prometheus技术分享——如何监控宿主机和容器

这一期主要来跟大家聊一下&#xff0c;使用node_exporter工具来暴露主机和因公程序上的指标&#xff0c;利用prometheus来监控宿主机&#xff1b;以及通过通过Cadvisor监控docker容器。 一、部署node_exporter监控宿主机 1 下载软件包 wget https://github.com/prometheus/n…

分布式链路追踪SkyWalking

文章目录目录介绍服务端搭建注册中心启动注册中心修改持久化配置UI服务配置启动服务客户端搭建目录介绍 重要的目录结构分析如下&#xff1a; agent&#xff1a;客户端需要指定的目录&#xff0c;其中有一个jar&#xff0c;就是负责和客户端整合收集日志bin&#xff1a;服务端…

深入理解Linux网络技术内幕(十三)——协议处理函数

文章目录前言网络协议栈概论大蓝图Ethernet的链路层的选择&#xff08;LLC和SNAP&#xff09;网络协议栈的操作方式执行正确的协议处理函数特殊媒介封装协议处理函数的组织协议处理函数的注册Ethernet与IEEE 802.3帧设置封包类型设置Ethernet协议及长度逻辑链接控制&#xff08…

python+django球鞋商品竞拍卖网站vue

管理员功能模块 管理员登录&#xff0c;通过填写用户名、密码、角色等信息&#xff0c;输入完成后选择登录即可进入网上球鞋竞拍系统 管理员登录进入网上球鞋竞拍系统可以查看球鞋分类管理、热门竞拍管理、科比展区管理、用户管理、竞拍信息管理、消息通知管理、用户评价管理、…

单人脸的关键点检测

闲暇之余做了一个简单的单人的脸部关键点检测&#xff0c;使用的pytorch框架&#xff0c;别人训练好的现成模型。其中人脸检测模型是YOLOface5&#xff08;onnx格式的权重&#xff09;&#xff0c;关键点检测模型是PFLD&#xff08;能检测98个关键点&#xff09;&#xff0c;是…

计算机网络学习笔记(Ⅳ):网络层

目录 1 网络层内容 1.1 功能概述 1.任务 2.主要功能 1.2 数据交换方式 1.电路交换 2.报文交换 3.分组交换 4.方法对比 1.3 分组交换 1.数据报方式 2.虚电路方式 3.对比 2 路由算法与路由协议 2.1 路由算法 2.2 路由选择协议 3 IPv4 3.1 IP数据报格式 1.TCP/…

快2023年了,还不会性能调优?阿里技术官亲授“Java性能调优技术宝典”看完直接涨薪5K

一、前言 什么是性能调优&#xff1f; 性能调优其实很好理解&#xff0c;就是优化硬件、操作系统、应用之间的一个充分的协作&#xff0c;最大化的发挥出硬件的极致性能&#xff0c;来应对高负载的业务需求。 为什么需要性能优化&#xff1f; 其实说到底就是两个原因&#…

2023年湖北安全员ABC三类人员延期多久一次?甘建二

2023年湖北安全员ABC三类人员延期多久一次&#xff1f; 2023年湖北安全员ABC延期快来找甘建二报名&#xff0c;建设厅指定的 2023年湖北安全员ABC新办快来找甘建二报名&#xff0c;建设厅指定的 首先安全员分为三类&#xff1a;A证、B证、C证&#xff0c;每个证书都有相应的…

Spring源码解析-环境变量(下)

“不积跬步&#xff0c;无以至千里”。 接着聊上一篇文章中遗留的两个重要问题&#xff1a; 如何往Spring环境变量中添加自定义的环境变量&#xff1f;工作原理是什么&#xff1f;PropertyPlaceholderConfigurer这个类是怎么完成bean属性填充时“$”符号解析工作的&#xff1f…

数据库系统概论第2章 关系数据库

易错点1&#xff1a;实体完整性 实体完整性要求主属性不能取空值 而主属性不能取空值≠候选码不为空 因为候选码可以是两个属性的组合&#xff0c;而主属性是候选码的属性 举个例子&#xff1a; SC表中候选码为&#xff08;学号&#xff0c;课程号&#xff09; 主属性为学…

fetch向后端请求数据:get/post/put/patch/delete方式、解决catch不能主动捕获错误问题(超详细笔记)

1、什么是fetch&#xff1a; fetch是ES6出现的&#xff0c;它使用了 ES6 提出的 promise 对象&#xff0c;为了取代XMLHttpRequest而诞生的&#xff1b;提到XMLHttpRequest就不得不提ajax&#xff0c;ajax是一种实现前后端交互的技术&#xff0c;而ajax是基于XMLHttpRequest模块…

C++ Reference: Standard C++ Library reference: Containers: map: map: count

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/count/ 公有成员函数 <map> std::map::count size_type count (const key_type& k) const;计数具有特定键的元素 在容器中搜索键值等于k的元素&#xff0c;并返回匹配的数量。 因为map容器中的所…

搞懂商业智能 BI 、数据仓库、数据中台及其关系,此文足以

数字化如火如荼&#xff0c;企业的 IT 信息化也越演越烈&#xff0c;企业管理者对数据管理也是越来越重视&#xff0c;认识到数据资产带来的价值&#xff0c;本文对这些名词术语及内涵进行系统的解析&#xff0c;便于读者对数据平台相关的概念有全面的认识。 商业智能BI 商业…

本文分享Unity中的AI算法和实现3-有限状态机FSM(下)

本文分享Unity中的AI算法和实现3-有限状态机FSM(下) 回家生孩子, 暂停了一个多月的更新, 今天重新续上, ^_^. 在上一篇文章中, 我们分享了状态机的基本理论, 然后结合Unity的Animator来熟悉了这些理论, 最后设计了我们自己的状态机并实现了框架部分. 这一篇文章, 我们将继续…

20221212 SpringCloud Alibaba

Spring Cloud Alibaba介绍主要功能组件注册中心脚手架创建实例使用RestTemplate实现微服务调用使用openfeign实现微服务调用负载均衡的使用创建多实例修改负载均衡Spring Cloud Alibaba 介绍 官方文档&#xff1a; https://spring.io/projects/spring-cloud-alibaba https://…

git stash 命令详解

1. 应用场景 2. 添加储藏 3. 查看储藏 4. 删除储藏 5. 使用储藏 6. 常见用法 1. 应用场景 git stash 命令用于将工作区中的更改和暂存区中的内容储存起来 日常开发中&#xff0c;会经常遇到这种场景 我们正在 dev 分支开发新功能&#xff0c;做到一半时&#xff0c;产品经理…

模块化、组件化和插件化

模块化&#xff1a;业务解耦、代码重用 组件化&#xff1a;模块化为基础、开发阶段每个moudle都是一个app &#xff0c;可以单独编译,并行开发 互不干扰&#xff0c;不用编译整个工程&#xff0c;打包的时候每个moudle又是moudle 不是app 只有一个app 插件化&#xff1a;也是…

【愚公系列】2022年12月 Elasticsearch数据库-ELK环境的搭建(一)

文章目录前言一、ELK环境的搭建1.前提条件2.启动Elasticsearch3.配置可视化工具 head-master3.配置kibana前言 ELK是三个开源软件的缩写&#xff0c;分别表示&#xff1a;Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat&#xff0c;它是一个轻量级…

大学生可以在校搞搞副业吗?尝试做外卖跑腿项目有没有市场?

随着大学寒假的即将到来&#xff0c;40多天的假期&#xff0c;为什么大学生不利用这个机会去想明年的校园生活该如何度过&#xff0c;想要自己的校园生活过得精彩&#xff0c;就给自己找一个副业吧&#xff01; 副业&#xff01;这两个词应该是针对工作的&#xff0c;而不是针…