数据结构初阶--带头双向循环链表

news2024/11/26 21:37:45

目录

一.带头双向循环链表的定义

二.带头双向循环链表的功能实现

2.1.带头双向循环链表的定义

2.2.带头双向循环链表的结点创建

2.3.带头双向循环链表的初始化

2.4.带头双向循环链表的打印

2.5.带头双向循环链表的判空

2.6.带头双向循环链表的尾插

2.7.带头双向循环链表的头插

2.8.带头双向循环链表的尾删

2.9.带头双向循环链表的头删

2.10.带头双向循环链表的在pos位置之前插入

2.11.带头双向循环链表的删除pos位置的结点

2.12.带头双向循环链表的求链表长度

2.13.带头双向循环链表的销毁

2.14.完整程序

List.h

List.c

test.c

三. 顺序表和链表的比较

逻辑结构

存储结构

基本操作

创建

销毁

增加与删除

查找


一.带头双向循环链表的定义

循环单链表虽然能够实现从任一结点出发沿着链能找到其前驱结点,但时间耗费是O(n)。如果希望从表中快速确定某一个结点的前驱,另一个解决方法就是在单链表的每个结点里再增加一个指向其前驱的指针域prior。这样形成的链表中就有两条方向不同的链,称之为双(向)链表。

与单链表类似,双链表也可增加头结点使双链表的某些运算变得方便。同时双向链表也可以有循环表,称为双向循环链表。

由于在双向链表中既有前向链又有后向链,寻找任一结点的直接前驱结点与直接后继结点都变得非常方便了。

二.带头双向循环链表的功能实现

2.1.带头双向循环链表的定义

//定义
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* pre;
	LTDataType data;
}LTNode;

 与单链表的定义不同,带头双向循环链表要定义两个指针:前驱指针pre和后继指针next。前驱指针pre用于指向当前结点的上一个结点,后继指针next用于指向当前结点的下一个结点。

2.2.带头双向循环链表的结点创建

LTNode* BuyListNode(LTDataType x)
{
	//动态开辟一个结点node
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));

	//判空
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}

	//前驱与后继结点均置为空
	node->data = x;
	node->next = NULL;
	node->pre = NULL;

	return node;
}

结点的创建主要是通过调用malloc函数来实现,初始化时要将前驱指针和后继指针都置为NULL。

2.3.带头双向循环链表的初始化

版本一:

void ListInit(LTNode** phead)
{
	//这里需要传入二级指针,即传地址,才能实现对链表的修改

	//判空
	assert(phead);
	
	//创建头结点
	*phead = BuyListNode(-1);

	//将头结点的前驱指针和后继指针均指向自身
	(*phead)->next = *phead;
	(*phead)->pre = *phead;
}

版本二:

LTNode* ListInit()
{
	//创建头结点
	LTNode* phead = BuyListNode(-1);

	//将头结点的前驱指针和后继指针均指向自身
	phead->next = phead;
	phead->pre = phead;

	//返回头结点
	return phead;
}

链表的初始化采用了两种方式:传二级指针和设置返回值。

总结:

如果要改变头指针,就要传二级指针。不需要改变头指针的话,则传入一级指针。

在使用带头结点的单链表时:

  1. 初始化链表头指针需要传二级指针;
  2. 销毁链表需要传二级指针;
  3. 插入、删除、遍历、清空结点用一级指针即可。

不带头结点的单链表,除了初始化和销毁,插入、删除和清空结点也需要二级指针。

调试分析:

2.4.带头双向循环链表的打印

void ListPrint(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向链表的第一个结点
	LTNode* cur = phead->next;

	//cur依次向后遍历,直到cur重新回到头结点
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}

	printf("\n");
}

设置一个临时变量cur,指向当前链表的第一个结点(非头结点),然后依次向后遍历该链表,直到cur重新回到头结点phead的位置。

2.5.带头双向循环链表的判空

bool ListEmpty(LTNode* phead)
{
	//判空
	assert(phead);

	//如果phead->next等于phead,则链表为空,返回true
	//如果phead->next不等于phead,则链表不为空,返回false
	return phead->next == phead;
}

如果phead->next等于phead,则链表为空,返回true;如果phead->next不等于phead,则链表不为空,返回false。

2.6.带头双向循环链表的尾插

void ListPushBack(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//查找尾结点
	LTNode* tail = phead->pre;

	//尾插
	//原尾和新尾相互链接
	tail->next = newnode;
	newnode->pre = tail;
	//头结点和新尾相互链接
	newnode->next = phead;
	phead->pre = newnode;
}

相较于单链表的尾插,带头双向循环链表的尾插不需要从头结点开始依次向后遍历,因为头结点的前驱结点便指向尾结点tail。在找到尾结点tail之后,便可将新结点newnode插入到尾结点tail的后面。此时newnode变为新的尾结点。

调试分析:

运行结果:

2.7.带头双向循环链表的头插

void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//头插
	//phead newnode next:三者不分先后顺序
	//法一:
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->pre = phead;
	newnode->next = next;
	next->pre = newnode;

	//phead newnode phead->next:先处理后两个,再处理前两个
    //法二:
	//phead->next->pre = newnode;
	//newnode->next = phead->next;
	//phead->next = newnode;
	//newnode->pre = phead;
}

在进行头插时,要注意结点之间插入的先后顺序,这里主要介绍两种方式。方式一:创建一个临时变量next,然后将头结点的下一个结点保存在next当中。首先调用BuyListNode(x)创建一个新结点newnode,然后将phead,newnode和next三个结点进行链接。三个结点不分先后顺序,直接进行链接即可。该方式最为简单,也最不容易出错;方式二:不创建临时变量next。首先调用BuyListNode(x)创建一个新结点newnode,然后将phead,newnode和phead->next三个结点进行链接。链接是关键:要先将后两个结点进行链接,然后再将前两个结点进行链接。三个结点一定要注意先后顺序,不可随意链接。

调试分析:

运行结果:

2.8.带头双向循环链表的尾删

void ListPopBack(LTNode* phead)
{
	//判空
	assert(phead);

	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));

	//找尾结点
	LTNode* tail = phead->pre;

	//找尾结点的前一结点
	LTNode* tailPre = tail->pre;

	//释放尾结点
	free(tail);

	tailPre->next = phead;
	phead->pre = tailPre;
}

在进行尾删之前,首先要判断链表是否为空,可以通过phead->next != phead进行判断,也可以调用ListEmpty(phead)函数进行判断;然后找到链表的尾结点tail,以及链表尾结点的前一个结点tailPre;接着调用free函数释放尾结点tail,并将tailPre作为新的尾结点;最后再将新的尾结点与头结点phead进行相连即可。

调试分析:

运行结果:

2.9.带头双向循环链表的头删

void ListPopFront(LTNode* phead)
{
	//判空
	assert(phead);

	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));

	//tail记录第一个结点之后的下一个结点
	LTNode* tail = phead->next->next;
	
	//释放第一个结点
	free(phead->next);

	//将头结点和tail相链接
	phead->next = tail;
	tail->pre = phead;
}

在进行头删之前,首先要判断链表是否为空,可以通过phead->next != phead进行判断,也可以调用ListEmpty(phead)函数进行判断;然后找到链表的第二个有效结点tail;接着调用free函数释放掉第一个有效结点,并将tail作为新的第一个有效结点;最后再将新的第一个结点tail与头结点phead进行相连即可。

调试分析:

运行结果:

2.10.带头双向循环链表的在pos位置之前插入

void ListInsert(LTNode* pos, LTDataType x)
{
	//判空
	assert(pos);

	//查找pos的前一个结点
	LTNode* pre = pos->pre;

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//pre newnode pos
	pre->next = newnode;
	newnode->pre = pre;
	newnode->next = pos;
	pos->pre = newnode;
}

给定一个结点pos,如果是带头双向循环链表,那么pos之前的结点和pos之后的结点都是可知的。要在pos位置之前插入,首先要找到pos的前一结点pre,然后调用BuyListNode(x)创建一个新结点newnode,接着将pre,newnode和pos三个结点进行链接即可。此时pos位置的结点将由pos变为newnode。

调试分析:

运行结果:

2.11.带头双向循环链表的删除pos位置的结点

void ListErase(LTNode* pos)
{
	//判空
	assert(pos);

	//查找pos的前一个结点
	LTNode* pre = pos->pre;
	
	//查找pos的后一个结点
	LTNode* next = pos->next;

	//将前一个结点pre与后一个结点next相链接
	pre->next = next;
	next->pre = pre;

	//释放pos结点
	free(pos);
}

在删除pos位置的结点之前,首先要找到pos位置的前一个结点pre,然后找到pos位置的后一个结点next,接着将结点pre与next相链接,最后再调用free函数释放掉pos结点即可。

调试分析:

运行结果:

2.12.带头双向循环链表的求链表长度

int ListSize(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向当前链表的第一个结点
	LTNode* cur = phead->next;
	
	//用于记录遍历过的结点数
	int size = 0;

	//从第一个结点开始依次向后遍历,直到遍历到头结点
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

调试分析:

运行结果:

2.13.带头双向循环链表的销毁

void ListDestory(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向当前第一个结点
	LTNode* cur = phead->next;

	while (cur != phead)
	{
		//保存cur的下一个结点
		LTNode* next = cur->next;

		//删除cur
		ListErase(cur);

		//更新cur
		cur = next;
	}

	//释放头结点
	free(phead);
}

调试分析:

运行结果:

2.14.完整程序

List.h

#pragma once

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

//带头双向循环链表

//定义
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* pre;
	LTDataType data;
}LTNode;

//创建结点
LTNode* BuyListNode(LTDataType x);

//初始化:版本一
//void ListInit(LTNode** phead);

//初始化:版本二
LTNode* ListInit();

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

//判空
bool ListEmpty(LTNode* phead);

//尾插
//不用二级指针的原因:尾插时不会改变phead,因为它带哨兵位,尾插时不会对哨兵位进行修改
void ListPushBack(LTNode* phead, LTDataType x);

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

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

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

//在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x);

//删除pos位置的结点
void ListErase(LTNode* pos);

//链表长度
int ListSize(LTNode* phead);

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

List.c

#define _CRT_SECURE_NO_WARNINGS 1


#include"List.h"


//创建结点
LTNode* BuyListNode(LTDataType x)
{
	//动态开辟一个结点node
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));

	//判空
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}

	//前驱与后继结点均置为空
	node->data = x;
	node->next = NULL;
	node->pre = NULL;

	return node;
}


//初始化
/*
void ListInit(LTNode** phead)
{
	//这里需要传入二级指针,即传地址,才能实现对链表的修改

	//判空
	assert(phead);
	
	//创建头结点
	*phead = BuyListNode(-1);

	//将头结点的前驱指针和后继指针均指向自身
	(*phead)->next = *phead;
	(*phead)->pre = *phead;
}
*/


//初始化
LTNode* ListInit()
{
	//创建头结点
	LTNode* phead = BuyListNode(-1);

	//将头结点的前驱指针和后继指针均指向自身
	phead->next = phead;
	phead->pre = phead;

	//返回头结点
	return phead;
}


//打印
void ListPrint(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向链表的第一个结点
	LTNode* cur = phead->next;

	//cur依次向后遍历,直到cur重新回到头结点
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}

	printf("\n");
}


//判空
bool ListEmpty(LTNode* phead)
{
	//判空
	assert(phead);

	//如果phead->next等于phead,则链表为空,返回true
	//如果phead->next不等于phead,则链表不为空,返回false
	return phead->next == phead;
}


//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	/*
	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//查找尾结点
	LTNode* tail = phead->pre;

	//尾插
	//原尾和新尾相互链接
	tail->next = newnode;
	newnode->pre = tail;
	//头结点和新尾相互链接
	newnode->next = phead;
	phead->pre = newnode;
	*/

	//尾插
	ListInsert(phead, x);//是phead而不是phead->pre
}


//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	/*
	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//头插
	//phead newnode next:三者不分先后顺序
	//法一:
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->pre = phead;
	newnode->next = next;
	next->pre = newnode;

	//phead newnode phead->next:先处理后两个,再处理前两个
    //法二:
	//phead->next->pre = newnode;
	//newnode->next = phead->next;
	//phead->next = newnode;
	//newnode->pre = phead;
	*/

	//头插
	ListInsert(phead->next, x);
}


//尾删
void ListPopBack(LTNode* phead)
{
	//判空
	assert(phead);

	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));

	/*
	//找尾结点
	LTNode* tail = phead->pre;

	//找尾结点的前一结点
	LTNode* tailPre = tail->pre;

	//释放尾结点
	free(tail);

	tailPre->next = phead;
	phead->pre = tailPre;
	*/

	//尾删
	ListErase(phead->pre);
}


//头删
void ListPopFront(LTNode* phead)
{
	//判空
	assert(phead);

	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));

	/*
	//tail记录第一个结点之后的下一个结点
	LTNode* tail = phead->next->next;
	
	//释放第一个结点
	free(phead->next);

	//将头结点和tail相链接
	phead->next = tail;
	tail->pre = phead;
	*/

	//头删
	ListErase(phead->next);
}


//在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x)
{
	//判空
	assert(pos);

	//查找pos的前一个结点
	LTNode* pre = pos->pre;

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//pre newnode pos
	pre->next = newnode;
	newnode->pre = pre;
	newnode->next = pos;
	pos->pre = newnode;
}


//删除pos位置的结点
void ListErase(LTNode* pos)
{
	//判空
	assert(pos);

	//查找pos的前一个结点
	LTNode* pre = pos->pre;
	
	//查找pos的后一个结点
	LTNode* next = pos->next;

	//将前一个结点pre与后一个结点next相链接
	pre->next = next;
	next->pre = pre;

	//释放pos结点
	free(pos);
}


//链表长度
int ListSize(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向当前链表的第一个结点
	LTNode* cur = phead->next;
	
	//用于记录遍历过的结点数
	int size = 0;

	//从第一个结点开始依次向后遍历,直到遍历到头结点
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}


//销毁
void ListDestory(LTNode* phead)
{
	//判空
	assert(phead);

	//cur指向当前第一个结点
	LTNode* cur = phead->next;

	while (cur != phead)
	{
		//保存cur的下一个结点
		LTNode* next = cur->next;

		//法一:删除cur
		//ListErase(cur);
		
		//法二:删除cur
		free(cur);

		//更新cur
		cur = next;
	}

	//释放头结点
	free(phead);
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

void test()
{
	LTNode* plist = NULL;

	//初始化
	plist = ListInit();

	//头插
	ListPushFront(plist, 1);
	ListPushFront(plist, 2);
	ListPushFront(plist, 3);
	ListPushFront(plist, 4);
	ListPushFront(plist, 5);
	ListPrint(plist);

	ListDestory(plist);
	ListPrint(plist);
}

int main()
{
	test();

	return 0;
}

三. 顺序表和链表的比较

下面分别从逻辑结构,存储结构,基本操作的角度对顺序表和链表进行比较。

逻辑结构

都属于线性表,都是线性结构。

存储结构

基本操作

对于任何一个数据结构,基本操作基本都能归纳为创销,增删改查。其中改建立在查的基础上。

创建

销毁

增加与删除

查找

用链表还是顺序表

          顺序表          链表
弹性(可扩容)             ×           √
     增,删             ×           √
        改             √           ×

表长难以预估,经常需要增加/删除元素--链表

表长可预估,查询(搜索)操作较多--顺序表

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

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

相关文章

要做接口并发性能测试,总得先学会分析吧!

引言 这篇是我3月份在公司内部做的技术分享内容&#xff0c;由于我在公司内部分享的内容较多以及一些特殊性&#xff0c;我摘取了接口并发测试从设计思路整理→测试方案设计→设计分析→代码编写这套流程&#xff08;我不会承认我把40多页PPT的内容都放在这篇博文里&#xff0…

高压放大器在无线电能传输中的作用和用途

高压放大器是一种电子放大器&#xff0c;主要用于放大输入信号的幅度&#xff0c;通过输出端输出一个较大的电压信号&#xff0c;以达到强化原始信号的功能。在无线电能传输中&#xff0c;高压放大器扮演着非常重要的角色。本文将从无线电能传输的定义、高压放大器的特性以及高…

mysql数据备份与还原进阶操作

创建以下表 创建普通用户backup并给权限 备份数据库school到/backup目录 mysqldump -u backup -p school > /backup/school.sql备份MySQL数据库为带删除表的格式&#xff0c;能够让该备份覆盖已有数据库而不需要手动删除原有数据库 mysqldump -u backup -p --add-drop-d…

mysql mha高可用

目录 1.MHA是什么以及作用 2.工作原理 3.案例 1.MHA是什么以及作用 MHA(Master High Availability) MySQL高可用环境下故障切换和主从复制的软件 MHA 的出现就是解决MySQL单点故障 MySQL故障切换过程中 MHA能做到0-30秒内自动完成故障切换操作 MHA能在故障切换的过程中最大程…

行业追踪,2023-07-26,如果主力不骗人,化工原料和磷化工有第一波机会

自动复盘 2023-07-26 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

1.java入门(基础概念与常识)

文章目录 1.1.java语言有哪些特点&#xff1f;1.2.关于JVM JDK和JRE最详细通俗的解答1.2.1JVM1.2.2 JDK和JRE 1.3. Oracle JDK 和 OpenJDK 的对比1.4. Java 和 C的区别?1.5. import java 和 javax 有什么区别&#xff1f;1.6. 为什么说 Java 语言“编译与解释并存”&#xff1…

Docker仓库

Docker仓库 一、docker Harbor简介 Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。Harbor以 Docker 公司开源的 Registry 为基础&#xff0c;提供了图形管理 UI 、基于角色的访问控制(Rol…

Python 基础(十八):异常处理

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、异常是什么&#xff1f;二、异常处理的基本语法三、捕获特定的异常类型四、finall…

P3183 [HAOI2016] 食物链

题目描述 如图所示为某生态系统的食物网示意图&#xff0c;据图回答第 1 小题 现在给你 n 个物种和 m 条能量流动关系&#xff0c;求其中的食物链条数。物种的名称为从 1 到 n 编号 M 条能量流动关系形如 1,2,3​,…,am−1​,bm−1​,am​,bm​。其中 ai​ 和 bi​ 表示能量从物…

《零基础入门学习Python》第067讲:GUI的终极选择:Tkinter4

今天我们来学习 Entry 组件&#xff0c;也就是我们平时所说的 输入框。 输入框是跟程序打交道的途径&#xff0c;比如 程序要求你输入 账号 和 密码。那么它就要提供两个输入框&#xff0c;并且接收密码的输入框还会用 星号 * 将实际的内容给隐藏起来。 我们学了还几个 tkint…

uniapp 微信小程序 navigationBarBackgroundColor 标题栏颜色渐变

大体思路&#xff1a; 第一步&#xff1a;“navigationStyle”:“custom” 第二步&#xff1a; template内 重点&#xff1a;给view添加ref“top” 第三步&#xff1a;添加渐变色样式 1、pages.json {"path" : "pages/user/user","style" : …

FAQ文档的重点注意事项!别踩坑了

在很多优秀的大企业中&#xff0c;FAQ文档是企业运营管理中不可或缺的重要部分。但是也仅限大企业&#xff0c;很多企业目前还是没有这个意识的。一方面原因是因为缺乏这个客户服务的意识&#xff0c;另一方面也和技术水平不足有关。但是其实现在有不少的第三方搭建平台可以帮助…

L---泰拉瑞亚---2023河南萌新联赛第(三)场:郑州大学

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 示例1 输入 1 10 3 5 输出 3 说明 只有一把回旋镖&#xff0c;你可以先打两次伤害为3的&#xff0c;再打一次倾尽全力的&#xff0c;造成的伤害为5。总伤害为33511&#xff0c;即可获得胜…

【Java中间件】RocketMQ

RocketMQ 一、MQ概述 Message Queue&#xff0c;是一种提供消息队列服务的中间件。提供了消息生产、存储、消费全过程API的软件系统。 MQ的作用 限流削峰&#xff1a;当用户发送超量请求时&#xff0c;将请求暂存&#xff0c;以便后期慢慢处理。如果不使用MQ暂存直接请求到…

抖音小店开通指南:轻松打造个人电商王国

抖音小店是抖音推出的一个电商平台&#xff0c;为用户提供了开店、上传商品、管理店铺等功能&#xff0c;让用户可以在抖音上直接进行购物。下面不若与众就来谈谈抖音小店的开通的几个步骤&#xff1a; 1. 注册抖音账号&#xff1a;首先&#xff0c;你需要在抖音上注册一个账号…

什么是Koala?

Koala 介绍 koala 是一个前端预处理器语言图形编译工具&#xff0c;支持 Less、Sass、Compass、CoffeeScript&#xff0c;帮助 web 开发者更高效地使用它们进行开发。跨平台运行&#xff0c;完美兼容 windows、linux、mac。 关键特性 多语言支持 支持 Less、Sass、CoffeeSc…

使用PHP实现登录注册功能的完整指南

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;2023年6月csdn上海赛道top4。多年电商行业从业经验&#xff0c;对系统架构&#xff0c;数据分析处理等大规模应用场景有丰富经验。 &#x1f3c6;本文已收录于PHP专栏&#xff1a;PHP…

无涯教程-jQuery - trigger( event, data )方法函数

trigger(event&#xff0c;[data])方法在每个匹配的元素上触发一个事件。 触发事件不仅限于基于浏览器的事件&#xff0c;还可以触发向bind注册的自定义事件。 trigger( event, [data] ) - 语法 selector.trigger( event, [data] ) 这是此方法使用的所有参数的描述- event…

【技术干货】工业级BLE5.2蓝牙模块SKB378 使用教程,AT指令集

SKB378是一个高度集成的蓝牙5.2模组&#xff0c;可用来在2.4GHz ISM频段内做高速率、短距离无线通信。工业级标准&#xff0c;支持主从模式(1主对8从)&#xff0c;支持串口透传&#xff0c;AT指令控制&#xff0c;且支持AoA蓝牙高精度室内定位&#xff0c;模组内部集成32位ARM …

存储过程——存储函数

1.存储函数 存储函数的弊端&#xff0c;必须要有返回值&#xff0c;能使用存储函数的地方也能使用存储过程。 案例需求 create function fun1(n int) returns int deterministic begindeclare total int default 0;while n>0 doset total : total _n;set n : n - 1;end …