单链表的相关操作(初阶--寥寥万字不成敬意)

news2025/1/14 20:36:16

目录

链表的概念

链表的相关操作:

链表的创建:

打印链表:

申请新节点:

链表的尾插:

 !!!对于传参中二级指针的解释:

链表的头插:

链表的尾删: 

链表的头删:

寻找结点:

在链表的指定位置前插入:

在链表的指定位置后插入:

删除pos位置的结点:

删除pos位置后的结点:

销毁链表:

最终结果:

SList.h文件:

SList.c文件:

test.c文件:


链表的概念

        链表是线性表的一种,它是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻

辑顺序是通过链表中的指针链接次序实现的 。其实链表就相当于一列火车:

        链表的结构跟⽕⻋⻋厢相似,淡季⻋厢会相应减少,旺季时⻋厢会额外增加,减少和增加车厢并不会影响其它车厢,每节⻋厢都是独⽴存在的。且每节⻋厢都有⻋⻔。
        假设每节⻋厢的⻋⻔都被锁上,且打开这些车厢所需要的钥匙各不相同,那么如果乘务员从第一节车厢开始向后面的车厢走去,如何从⻋头⾛到⻋尾?
答案:每节⻋厢⾥都放⼀把下⼀节⻋厢的钥匙。
而每节车厢里面肯定是有人或者货物的,所以这就引申出了一条新的概念:在链表⾥,每节“⻋厢”是由 下一个车厢的🔑 本节车厢中存储的“数据" 组成的。

!!当你对后续的链表内容有问题,请重新回来仔细观看下图!!
!!当你对后续的链表内容有问题,请重新回来仔细观看上图!!

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“ 结点/节点 ”。
结点 =  当前节点中保存的数据data + 保存下⼀个节点地址的指针变量next
        有了车厢,我们就需要有一节火车头来带动这些车厢,链表中的火车头就是 头结点(头结点不存储数据),指向链表的第一个有效结点(存储数据)的地址,在这里plist就为头结点
为什么需要指针变量来保存下⼀个节点的位置?
        因为链表在内存空间上的存储是非连续的 ,就和火车车厢一样,根据需求进行增加和删除,通俗来讲就是,用到你这块儿了我用指针(火车挂钩)给你连上你就得给我进链表不用你的时候把脸上你的指针(火车挂钩)断开你就一边闲着去。

链表的相关操作:

以下链表内容为不带头单向不循环链表

链表的创建:

创建链表需要经历以下操作:

1、定义一个结构体来表示链表的结点(SList.h文件)

//定义一种链表节点的结构(实际应用中有多种,这里只演示最基本的结构)
typedef int SLDataType; //便于切换链表中存储数据的类型
struct SListNode {   
	SLDataType data;  //存储数据
	struct SListNode* next;  //用来保存下一个节点地址的指针变量next
};
typedef struct SListNode SLNode;  //将链表结点的结构体重命名为SLNode
2、编写创建链表的函数(第二点这里的 内容只是为了方便理解后续内容,具体情况请看下面的实际操作,有个简单的了解)
该函数主要进行的操作是:        
①创建新节点并为其开辟内存空间
②将新结点的next指针指向下一个结点
//申请结点函数
void slttest()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode)); 
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表
	SLNode* plist = node1;  
	SLPrint(plist);
}

打印链表:

//用phead表示头结点,它指向链表的第一个结点(如果思路出现混乱,一定要再看一边前面的链表图)
void SLPrint(SLNode* phead) 
{	
	//循环打印
	//为了能在遍历后仍能找到刚开始的起点,我们就需要利用一个临时指针pcur来存储头结点的地址
	SLNode* pcur = phead;
    //当头结点不为空时进行循环
	while (pcur)
	{
    //打印此时所处结点中的数据
		printf("%d ->", pcur->data);
    //打印结束后让pcur指向下一个结点的地址
		pcur = pcur->next;
	}
    //到最后时现有结点遍历完成,空间为NULL
	printf("NULL\n");
}

申请新节点:

//申请有效结点函数(并在该结点中存储数据)
SLNode* SLByNode(SLDataType x) 
{
    //为链表的新结点申请一个新的空间
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
    //该节点中存储的数据为x
	node->data = x;
    //将该结点的下一个结点置为空,因为我们也不知道它后面到底还要不要结点了
	node->next = NULL;
    //返回申请的新结点
	return node;
}

链表的尾插:

//链表的尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
    //判断传入的头结点plist是否为空
	assert(pphead);
	//如果存在头结点则进行后续操作
    //先申请一个新的有效结点
	SLNode* node = SLByNode(x);
    //如果第一个有效结点为空,则令*pphead指向新创建的有效结点
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
    //如果第一个有效结点不为空,则通过循环读取至链表的结尾
    //先定义一个临时的指针变量pcur,令pcur指向第一个有效结点
	SLNode* pcur = *pphead;
    //然后利用pcur->next遍历至链表的末尾
	while (pcur->next)
	{
		pcur = pcur->next;
	}
    //当遍历至链表的末尾时,让pcur指向新的有效结点
	pcur->next = node;
}

 !!!对于传参中二级指针的解释:

//pphead是一个二级指针,通过判断它的非空情况可以得到整个链表是否存在
&plist (获取的是plist这个指针的地址)==  pphead

//对于pphead的解引用
plist(头结点的地址) == *pphead
如果第一个结点为空那么该结点的地址为空

//对于*pphead的解引用,得到头节点中的地址
*plist(第一个结点的内存块)== **pphead

链表的头插:

//链表的头插
void SLPushFront(SLNode** pphead, SLDataType x)//相当于两个互相赋值
{
    //判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* node = SLByNode(x);
    //下面两条进行的其实就是简单的交接工作
	//先将当前头指针指向的结点交给了node->next
	node->next = *pphead;
	//然后让头指针指向新节点的地址
	*pphead = node;
}

链表的尾删: 

//链表的尾删(链表为空的情况下不能尾删)
void SLPopBack(SLNode** pphead)
{    
	//判断传入的头结点plist是否为空
	assert(pphead);
    //判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
    else
    {
    //当不止一个有效结点时
    //未防止删除后空指针的出现,在寻找尾节点的时候我们也要找到尾节点的前一个节点
	//找尾结点和尾结点的前一个结点
    //定义prev为尾结点的前一个结点
	SLNode* prev = NULL;
    //定义ptai为用于找尾结点的指针,先让它接收第一个有效结点的地址
	SLNode* ptail = *pphead;
	while (ptail->next != NULL)
	{
        //先令prev将ptail保存下来,当ptail->next为空时(此时到达尾指针)就不会进入循环将ptail    
        //存入prev中,此时prev保存的就是尾结点的前一个结点
		prev = ptail;
		ptail = ptail->next;
	}
	//此时prev(尾结点的前一个结点)的next指针不再指向ptail(尾结点)而是指向ptail的下一个结点
	prev->next = ptail->next;
	free(ptail);
	ptail = NULL;
    }
}

链表的头删:

//链表的头删
void SLPopPront(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
    //判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
    //当整个链表
	//使用临时指针指向头结点
	SLNode* del = *pphead;
	//令头结点指向新的头结点
	*pphead = (*pphead)->next;
	//将临时指针指向的结点(头结点)释放掉
	free(del);
	del = NULL;
}

寻找结点:

//查找结点
SLNode* SLFind(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//该函数需要与在指定位置插入删除结合,返回的结果使用一个指针来接收,在test.c文件中的使用情况如下:
SLNode* find = SLFind(&plist,2);//查找数据为2的结点
SLInsert(&plist,find,x)//在find(数据为2)的结点前插入含有数据x的新节点

在完成以下代码后需要考虑的三种情况:

1、pos是头结点

2、pos是中间结点

3、pos是最后一个结点

在链表的指定位置前插入:

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
    //约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLByNode(x);

	//有且只有一个有效结点,此时在该有效结点前进行插入操作就相当于头插
	if(pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//当不只有一个有效结点的时候,先通过循环找到pos的前一个结点
	SLNode* prev = *pphead; 
    //当prev->next指向pos的时候跳出循环
	while (prev->next != pos)
	{
		prev = prev->next;
	}
    //此时循环结束,prev指向pos

    //最后,处理插入位置两边的结点与新结点三者之间的关系prve node pos
    //此时下面的两个操作顺序可以交换
	node->next = pos;
	prev->next = node;
}

在链表的指定位置后插入:

//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    //确定能找到该结点
	assert(pos);
	SLNode* node = SLByNode(x);
	//pos node pos->next
	node->next = pos->next;
	pos->next = pos;
}

//使用案例:
//SLNode* find = SLFind(&plist,1);
//SLInsertAfter(find,100);

删除pos位置的结点:

//删除pos结点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
    
    //当pos为第一个有效结点时
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
    //当pos不为第一个有效结点时
	//先找到pos的前一个结点,然后(后续内容与之前的操作类似)
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
    //先完成pos两边结点的交接工作,然后再释放pos结点
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

删除pos位置后的结点:

//删除pos结点之后的数据
void SLEraseAfter(SLNode* pos)
{
    //除了pos不为空以外,还需要pos->next不为空,因为pos刚好是最后一个结点你总不能删除一个NULL
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

销毁链表:

//销毁链表
void SLDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
    //此时链表所有的有效结点已经结束了,最后将头结点置为空即可
	*pphead = NULL;
}

最终结果:

SList.h文件:

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

//定义链表节点的结构
typedef int SLDataType;
struct SListNode {   //定义一个表示链表节点的结构体
	SLDataType data;  //链表中用于存储数据的成员(某个节点的数据)
	struct SListNode* next;  //用来保存下一个节点地址的指针变量next
};
typedef struct SListNode SLNode;  //将指向下一个节点的指针类型重命名为SLNode

//创建几个结点组成的链表,并打印链表
void SLPrint(SLNode* phead);  
//链表的尾插
void SLPushBack(SLNode** phead, SLDataType x);
//链表的头插
void SLPushFront(SLNode** phead, SLDataType x);
//链表的尾删
void SLPopBack(SLNode** pphead);
//链表的头删
void SLPopPront(SLNode** pphead);
//找结点,这里传一级指针实际上就可以了,因为不改变头节点,但是这里还是要写成二级指针,因为要保证接口一致性
SLNode* SLFind(SLNode** pphead,SLDataType x);

//链表的在指定位置之前插入
void SLInsert(SLNode** phead, SLNode* pos,SLDataType x);
//链表的指定位置删除
void SLInsertAfter(SLNode* pos, SLDataType x);//此时不需要第一个参数
//删除pos位置的结点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos后的结点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SLDestroy(SLNode** pphead);

SList.c文件:

#include "SList.h"
//用phead表示头结点,它指向链表的第一个结点(如果思路出现混乱,一定要再看一边前面的链表图)
void SLPrint(SLNode* phead)
{
	//循环打印
	//为了能在遍历后仍能找到刚开始的起点,我们就需要利用一个临时指针pcur来存储头结点的地址
	SLNode* pcur = phead;
	//当头结点不为空时进行循环
	while (pcur)
	{
		//打印此时所处结点中的数据
		printf("%d ->", pcur->data);
		//打印结束后让pcur指向下一个结点的地址
		pcur = pcur->next;
	}
	//到最后时现有结点遍历完成,空间为NULL
	printf("NULL\n");
}

//申请有效结点函数(并在该结点中存储数据)
SLNode* SLByNode(SLDataType x)
{
	//为链表的新结点申请一个新的空间
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	//该节点中存储的数据为x
	node->data = x;
	//将该结点的下一个结点置为空,因为我们也不知道它后面到底还要不要结点了
	node->next = NULL;
	//返回申请的新结点
	return node;
}

//链表的尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//如果存在头结点则进行后续操作
	//先申请一个新的有效结点
	SLNode* node = SLByNode(x);
	//如果第一个有效结点为空,则令*pphead指向新创建的有效结点
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	//如果第一个有效结点不为空,则通过循环读取至链表的结尾
	//先定义一个临时的指针变量pcur,令pcur指向第一个有效结点
	SLNode* pcur = *pphead;
	//然后利用pcur->next遍历至链表的末尾
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	//当遍历至链表的末尾时,让pcur指向新的有效结点
	pcur->next = node;
}

//链表的头插
void SLPushFront(SLNode** pphead, SLDataType x)//相当于两个互相赋值
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* node = SLByNode(x);
	//下面两条进行的其实就是简单的交接工作
	//先将当前头指针指向的结点交给了node->next
	node->next = *pphead;
	//然后让头指针指向新节点的地址
	*pphead = node;
}

//链表的尾删(链表为空的情况下不能尾删)
void SLPopBack(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//当不止一个有效结点时
	//未防止删除后空指针的出现,在寻找尾节点的时候我们也要找到尾节点的前一个节点
	//找尾结点和尾结点的前一个结点
	//定义prev为尾结点的前一个结点
	
	else
	{
		SLNode* prev = NULL;
		//定义ptai为用于找尾结点的指针,先让它接收第一个有效结点的地址
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			//先令prev将ptail保存下来,当ptail->next为空时(此时到达尾指针)就不会进入循环将ptail    
			//存入prev中,此时prev保存的就是尾结点的前一个结点
			prev = ptail;
			ptail = ptail->next;
		}
		//此时prev(尾结点的前一个结点)的next指针不再指向ptail(尾结点)而是指向ptail的下一个结点
		prev->next = ptail->next;
		free(ptail);
		ptail = NULL;
	}
}

//链表的头删
void SLPopPront(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
	//当整个链表
	//使用临时指针指向头结点
	SLNode* del = *pphead;
	//令头结点指向新的头结点
	*pphead = (*pphead)->next;
	//将临时指针指向的结点(头结点)释放掉
	free(del);
	del = NULL;
}

//查找结点
SLNode* SLFind(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//
该函数需要与在指定位置插入删除结合,返回的结果使用一个指针来接收,在test.c文件中的使用情况如下:
//SLNode* find = SLFind(&plist, 2);//查找数据为2的结点
//SLInsert(&plist, find, x)//在find(数据为2)的结点前插入含有数据x的新节点

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLByNode(x);

	//有且只有一个有效结点,此时在该有效结点前进行插入操作就相当于头插
	if (pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//当不只有一个有效结点的时候,先通过循环找到pos的前一个结点
	SLNode* prev = *pphead;
	//当prev->next指向pos的时候跳出循环
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//此时循环结束,prev指向pos

	//最后,处理插入位置两边的结点与新结点三者之间的关系prve node pos
	//此时下面的两个操作顺序可以交换
	node->next = pos;
	prev->next = node;
}

//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
	//确定能找到该结点
	assert(pos);
	SLNode* node = SLByNode(x);
	//pos node pos->next
	node->next = pos->next;
	pos->next = pos;
}

//使用案例:
//SLNode* find = SLFind(&plist,1);
//SLInsertAfter(find,100);

//删除pos结点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	//当pos为第一个有效结点时
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//当pos不为第一个有效结点时
	//先找到pos的前一个结点,然后(后续内容与之前的操作类似)
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//先完成pos两边结点的交接工作,然后再释放pos结点
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

//销毁链表
void SLDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时链表所有的有效结点已经结束了,最后将头结点置为空即可
	*pphead = NULL;
}

test.c文件:

#include "SList.h"
//申请结点函数
//void slttest()
//{
//	//使用malloc函数动态分配,创建链表的头节点,它不包含任何数据,知识用来指向链表的第一个实际节点
//	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode)); 
//	//head
//	node1->data = 1;
//	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
//	node2->data = 2;
//	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
//	node3->data = 3;
//	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
//	node4->data = 4;
//
//	//实现四个节点的链接
//	//初始化头节点的next指针为node2指针变量
//	node1->next = node2;
//	node2->next = node3;
//	node3->next = node4;
//	node4->next = NULL;
//
//	//打印链表
//	SLNode* plist = node1;  //定义一个SLNode*类型的指针变量plist,他也叫头指针,我们用它指向链表的头节点
//	
//	//注意头节点和头指针的概念是不同的:
//	/*在链表的上下文中,通常将链表的第一个节点称为头节点(Head Node),但是头节点和头指针(Head Pointer)是不同的概念。
//	头节点是链表中的第一个实际节点,它包含数据和指向下一个节点的指针。头节点是链表的起始点,它可以存储实际的数据,也可以只是一个占位符节点,不存储实际的数据。
//	头指针是指向链表的头节点的指针。它是一个指针变量,存储着头节点的地址。通过头指针,我们可以访问链表中的每个节点,或者进行其他链表操作。
//	因此,头节点是链表中的一个节点,而头指针是指向头节点的指针。它们是不同的概念,但在某些情况下,人们可能会将它们混用或将它们视为相同的概念,因为头节点通常通过头指针来访问。*/
//	
//	SLNPrint(plist);
//}

void slttest()
{

	SLNode* plist = NULL;	
	//尾插
	SLPushBack(&plist, 1);  
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);//1->2->3->4->NULL
	SLPrint(plist);
	头插
	//SLPushFront(&plist, 1);
	//SLPushFront(&plist, 2);
	//SLPushFront(&plist, 3);
	//SLPushFront(&plist, 4);//4->3->2->1->NULL
	//SLPrint(plist);
	//尾删
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	头删
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	指定位置插入
	//SLNode* find = SLFind(&plist, 4);
	//SLInsert(&plist, find,11);//1->11->2->3->4->NULL
	在指定位置之后插入数据
	//SLInsertAfter(find, 100);
	删除pos位置的节点
	//SLErase(&plist, find);//1->2->3->NULL
	删除pos之后的节点
	//SLEraseAfter(find);
	//
	//销毁链表
	//SLDestory(&plist);
	//检验是否成功销毁
	SLPrint(plist);
}

int main()
{
	slttest();
	return 0;
}

注意事项:

1、判断条件的等号都是==

2、冒号是否写了

3、函数或者指针变量的名字是否书写正确 

4、最后的test.c文件实验时可能会存在一些多删之类的问题(函数写多了)请自行检查~

如果还有其它你编写时出现的错误也可以说一下~ 

~over~

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

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

相关文章

常用JS加密/解密类型以及案例

简介 这里给大家汇总常用不常用的JS加密案例&#xff0c;免得大家用的时候到处去找例子。 正题 对称加密&#xff1a; 替代字符表示法&#xff1a;使用Base64或类似的编码对数据进行简单的转换&#xff0c;不过这并不是真正的加密&#xff0c;而只是一种表示形式的转换。 &l…

uni-app--》基于小程序开发的电商平台项目实战(七)完结篇

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

OSG粒子系统特效-----雨雪、爆炸、烟雾

1、烟雾效果 飞机坠毁 陨石坠落 源码&#xff1a; // CMyOSGParticle.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include <windows.h> #include <osgViewer/Viewer>#include <osg/Node> #inc…

动态规划:从入门到入土系列(二)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 前言 一、…

AI AIgents时代-(五.)Autogen

由微软开发的 Autogen 是一个新的 Agents 项目&#xff0c;刚一上线就登上GitHub热榜&#xff0c;狂揽11k星✨✨✨ 项目地址&#xff1a;https://github.com/microsoft/autogen Autogen 允许你根据需要创建任意数量的Agents&#xff0c;并让它们协同工作以执行任务。它的独特之…

LeetCode算法栈—有效的括号

目录 有效的括号 用到的数据结构&#xff1a; 位运算、Map 和 Stack Stack 常用的函数&#xff1a; 题解&#xff1a; 代码&#xff1a; 运行结果; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符…

SpringBoot2.x简单集成Flowable

环境和版本 window10 java1.8 mysql8 flowable6 springboot 2.7.6 配置 使用IDEA创建一个SpringBoot项目 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.…

GitHub下载太慢的解决方案

修改hosts文件&#xff1a; windows的hosts文件在 C:\Windows\System32\drivers\etc\hosts cmd管理员运行命令notepad C:\Windows\System32\drivers\etc\hosts 然后cmd命令重启网络ipconfig /flushdns windows修改hosts Ubuntu22.04修改hosts sudo vim /etc/hosts # This fil…

RK3288 Android11 mini-pcie接口 4G模组EC200A适配(含自适应功能)

这里写目录标题 1、修改驱动内核配置①使能USBNET功能②使能 USB 串口 GSM、CDMA 驱动③使能 USB 的 CDC ACM模式④使能PPP功能 2、使用lsusb命令查看是否识别到usb接口的“EC200A”4G模组3、在drivers/usb/serial/option.c添加VID和PID信息①添加VID和PID定义②在option_ids 数…

找寻openSUSE的前世今生

找寻开始的地方 在一些资产管理平台、网管系统&#xff0c;完全依赖资产录入资产&#xff0c;假如存在SUSE类型网元&#xff0c;能否将SLES和openSUSE一同接入到SUSE类型下&#xff0c;就有待商榷了。 SLES和openSUSE是两个不同的Linux发行版&#xff0c;更准确说是两个不同的…

基于PHP的蛋糕甜品商店管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

python读写.pptx文件

1、读取PPT import pptx pptpptx.Presentation(rC:\Users\user\Documents\\2.pptx) # ppt.save(rC:\Users\user\Documents\\1.pptx) # slideppt.slides.add_slide(ppt.slide_layouts[1])# 读取所有幻灯片上的文字 for slide in ppt.slides:for shape in slide.shapes:if shape…

[尚硅谷React笔记]——第4章 React ajax

目录&#xff1a; 脚手架配置代理_方法一 server1.js开启服务器server1:App.js解决跨域问题&#xff1a;脚手架配置代理_方法二 ​​​​​​​server2.js开启服务器server2第一步&#xff1a;创建代理配置文件编写setupProxy.js配置具体代理规则&#xff1a;App.js运行结果&a…

js实现红包雨功能(canvas,react,ts),包括图片不规则旋转、大小、转速、掉落速度控制、屏幕最大红包数量控制等功能

介绍 本文功能由canvas实现红包雨功能&#xff08;index.tsx&#xff09;本文为react的ts版。如有其他版本需求可评论区观赏地址&#xff0c;需过墙 import React, { Component } from react; // import ./index.css; import moneyx from /assets/images/RedEnvelopeRain/bal…

SEO业务适合什么代理IP?2023海外代理IP推荐排名

随着数字营销趋势的变化&#xff0c;搜索引擎优化仍然是企业在网络世界中努力繁荣的重要组成部分。为了实现 SEO 成功&#xff0c;从搜索引擎获取准确且多样化的数据至关重要&#xff0c;然而可能会受到诸如基于位置的限制和被检测风险等限制的阻碍&#xff0c;IP代理则可以帮助…

百度开源分布式id生成器集成--真香警告

百度开源分布式id生成器集成–真香警告 文章目录 [toc] 1.为什么需要分布式id生成器&#xff1f;2.常见id生成方案2.1 数据库表主键自增2.2 uuid2.3 雪花算法2.3.1 实现代码2.3.2 缺点的解决方案百度开源的分布式唯一ID生成器UidGenerator(本文重点讲解这个)Leaf--美团点评分布…

gnome-terminal禁止关闭确认

当你想要关闭一个终端时&#xff0c;弹出“确认关闭&#xff1f;”多少有些烦。 比如当前为root下&#xff0c;要么一路exit&#xff0c;要么就点击确认&#xff1a; 解决方法&#xff1a; 安装一个配置编辑器来帮我们方便地编辑配置项 sudo apt install dconf-editor找到…

小程序搭建OA项目首页布局界面

首先让我们来学习以下Flex布局 一&#xff0c;Flex布局简介 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display属性 position属性 float属性 Flex布局简介 Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的…

tomcat、nginx实现四层转发+七层代理+动静分离实验

实验环境&#xff1a; nginx1——20.0.0.11——客户端 静态页面&#xff1a; nginx2——20.0.0.21——代理服务器1 nginx3——20.0.0.31——代理服务器2 动态页面&#xff1a; tomcat1——20.0.0.12——后端服务器1 tomcat2——20.0.0.22——后端服务器2 实验步骤&…

什么是4K三路虚拟情景互动教学软件?

4K三路虚拟情景互动教学软件具备了AI对话&#xff0c;场景库丰富自定义选择&#xff0c;画面色差调节&#xff0c;人物滤镜调节&#xff0c;截图编辑&#xff0c;视频录制与编辑&#xff0c;视频直播&#xff0c;画中画控制功能&#xff0c;字幕&#xff0c;图片和特效录入功能…