C语言数据结构之链表

目录

在这里插入图片描述

在这里插入图片描述

前言 \color{maroon}{前言} 前言

在上一篇博客中我们提到,线性表包括顺序表和链表,顺序表在上篇博客中已经介绍,本篇博客介绍一下另一种线性表——链表

1.链表的概念及结构

概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

在这里插入图片描述

  • 链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉或者加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。

  • ⻋厢是独⽴存在的,且每节⻋厢都有⻋⻔。想象⼀下这样的场景,假设每节⻋厢的⻋⻔都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从⻋头⾛到⻋尾?

  • 最简单的做法:每节⻋厢⾥都放⼀把下⼀节⻋厢的钥匙。

  • 在链表⾥,每节“⻋厢”是什么样的呢?

在这里插入图片描述

  • 与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点” (顺序表申请的空间是连续的,一次性申请下来的)

  • 节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。

  • 图中指针变量 plist 保存的是第⼀个节点的地址,我们称 plist 此时“指向”第⼀个节点,如果我们希望 plist “指向”第⼆个节点时,只需要修改 plist 保存的内容为0x0012FFA0。

为什么还需要指针变量来保存下⼀个节点的位置?

链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点

结合前⾯学到的结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整型:

struct ListNode
{
	int data;              //节点储存的数据
	struct ListNode* next; //指向节点的下一个节点
};
  • 当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)

  • 当我们想要从第⼀个节点⾛到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。

我们以打印链表数据为例,看看是怎么拿到链表每一个数据的

在这里插入图片描述

注意 : \color{red}{注意:} 注意:

1.链式机构在逻辑上是连续的,在物理结构上不⼀定连续

2.节点⼀般是从堆上申请的

3.从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

2.链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

在这里插入图片描述

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表

1.⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。
2.带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了,后⾯我们代码实现了就知道了。

3.无头单向非循环链表的实现

test.c

#include "Simple_List.h"

void menu()
{
	printf("******************************************\n");
	printf("***************   请选择   ***************\n");
	printf("******  1.PushFront     2.PushBack  ******\n");
	printf("******  3.Insert        4.PopFront  ******\n");
	printf("******  5.PopBack       6.Del       ******\n");
	printf("******  7.Modify        8.Print     ******\n");
	printf("******            0.Exit            ******\n");
	printf("******************************************\n");
}

int main()
{
	int pos = 0;
	int input = 0;
	int value = 0;

	ListNode* head = NULL;

	do 
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要头插的值>:");
			scanf("%d", &value);
			Push_Front_List(&head, value);
			break;
		case 2:
			printf("请输入你要尾插的值>:");
			scanf("%d", &value);
			Push_Back_List(&head, value);
			break;
		case 3:
			printf("请输入你要插入的位置和要插入的值>:");
			scanf("%d %d", &pos, &value);
			Insert_List(&head, pos, value);
			break;
		case 4:
			Pop_Front_List(&head);
			break;
		case 5:
			Pop_Back_List(&head);
			break;
		case 6:
			printf("请输入你要删除的值>:");
			scanf("%d", &pos);
			Del_List(&head, pos);
			break;
		case 7:
			printf("请输入你要修改的位置和要修改的值>:");
			scanf("%d %d", &pos, &value);
			Modify_List(head, pos, value);
			break;
		case 8:
			Print_List(head);
			break;
		case 0:
			Destroy(&head);
			printf("链表销毁成功\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

Simple_List.c

#include "Simple_List.h"


//打印链表数据
void Print_List(ListNode* phead)
{
	ListNode* cur = phead;

	while (cur != NULL)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}


//创建新节点
void Buy_Node(ListNode** ptr) //传二级指针是因为只传一级指针的话,我们改变形参不能改变实参的地址
{
	ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));

	if (tmp == NULL) //可能开辟空间不成功
	{
		perror("Buy_Node");
		return;
	}

	*ptr = tmp;
}


//头插
void Push_Front_List(ListNode** phead, LDatatype val)
{
	assert(phead); //phead要解引用,不能为空

	ListNode* newnode = NULL; 
	Buy_Node(&newnode); //插入一个新的数据需要先创建一个新节点插入到链表
	newnode->data = val;
	newnode->next = *(phead);
	*phead = newnode; //因为是头插一个节点,所以头指针要指向新的节点
}


//尾插
void Push_Back_List(ListNode** pphead, LDatatype val)
{
	assert(pphead); //phead要解引用,不能为空

	ListNode* newnode = NULL;
	Buy_Node(&newnode);
	newnode->data = val;
	newnode->next = NULL;

	ListNode* tail = *pphead;
	if (*(pphead) == NULL) //当链表为空时,头指针也为空,尾插需要把新节点地址给予头指针
	{
		*(pphead) = newnode;
		return;
	}

	while (tail->next != NULL) //找到最后一个节点,将新节点插入
	{
		tail = tail->next;
	}
	tail->next = newnode;
}


//寻找指定的的节点的地址
void Find_Ptr_List(ListNode** pphead, LDatatype pos,ListNode** pcur,ListNode** pfront)
{
	assert(pphead && pcur && pfront && *pphead); //这些指针需要解引用,不能为空
	
	*pcur = *pfront = *pphead;
	
	while (*pcur != NULL) // 这样的目的是,如果*pcur找到了指定数据的节点,*pfront就是*pcur的上一个节点
	{
		if ((*pcur)->data == pos) //先判断再让*pcur多走一步的原因是:可能头节点就是指定的数据,先走一步会漏掉判断
			break;
		if (*pcur == *pfront) // 让*pcur比*pfront多走一步,即*pcur = (*pfront)->next
			*pcur = (*pcur)->next;
		else
		{
			*pcur = (*pcur)->next;
			*pfront = (*pfront)->next;
		}
	}
}


//在指定数据的节点前插入数据
void Insert_List(ListNode** pphead,LDatatype pos,LDatatype val)
{
	ListNode* newnode = NULL;
	Buy_Node(&newnode);

	ListNode* cur = NULL;
	ListNode* front = NULL;
	Find_Ptr_List(pphead, pos, &cur, &front);

	if (cur == NULL) //说明整个链表已经判断完了,也没有找到指定数据
	{
		printf("找不到该位置\n");
		return;
	}

	if (cur == front) //说明指定数据就是头节点,直接头插
	{
		Push_Front_List(pphead, val);
		return;
	}

	front->next = newnode; //指定数据不是头节点的其他情况
	newnode->data = val;
	newnode->next = cur;
}


//头删
void Pop_Front_List(ListNode** pphead)
{
	assert(pphead && *pphead); //这些指针需要解引用,不能为空

	ListNode* front = *pphead;
	*pphead = (*pphead)->next; //头删后头指针需要指向新的头节点
	free(front);
	front = NULL;
}


//尾删
void Pop_Back_List(ListNode** pphead)
{
	assert(pphead && *pphead); //这些指针需要解引用,不能为空

	ListNode* tail = NULL;
	tail = *pphead;

	ListNode* front = NULL;
	front = *pphead;

	while (tail->next != NULL) 
	{
		if (front == tail) //让tail多走一步,即tail = front->next;
		{
			tail = tail->next;
		}
		else
		{
			tail = tail->next;
			front = front->next;
		}
	}

	if (front == tail) //说明链表只有一个数据,直接执行头删
	{
		Pop_Front_List(pphead);
		return;
	}

	front->next = NULL; //链表不止一个数据的情况,就需要把tail节点去掉
	free(tail); //找不到cur对应的实参所以不用置空,只需要释放空间即可
}


//删除指定数据
void Del_List(ListNode** pphead, LDatatype pos)
{
	assert(pphead); //pphead需要解引用,不能为空

	ListNode* cur = *pphead;
	ListNode* front = *pphead;

	Find_Ptr_List(pphead, pos, &cur, &front);

	if (cur == NULL) //说明链表遍历完了还没有找到指定数据
	{
		printf("找不到该位置\n");
		return;
	}

	if (cur == front) //说明头节点就是指定的数据,直接头删
	{
		Pop_Front_List(pphead);
		return;
	}

	front->next = cur->next; //头节点不是指定的数据的其他情况
	free(cur); //找不到cur对应的实参所以不用置空,只需要释放空间即可
}


//修改指定数据
void Modify_List(ListNode* phead, LDatatype pos, LDatatype val)
{
	assert(phead); //phead需要解引用,不能为空

	while (phead != NULL) //遍历链表寻找指定数据,再修改
	{
		if (phead->data == pos)
		{
			phead->data = val;
			return;
		}
		phead = phead->next;
		
	}

	if (phead == NULL) //遍历完链表还未找到指定数据
		printf("找不到该位置\n");
}


//销毁链表
void Destroy(ListNode** phead)
{
	while((*phead)!=NULL)
	{
		ListNode* cur = *phead; //销毁需要先记录下这个节点,如果直接销毁会找不到后面的节点
		*phead = (*phead)->next;
		free(cur);
		cur = NULL; //这里要销毁cur,而不是*phead,销毁*phead后面节点就找不到了
	}
}

Simple_List.h

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

typedef int LDatatype;     //链表储存的数据类型

typedef struct ListNode
{
	LDatatype data;        //节点储存的数据
	struct ListNode* next; //指向节点的下一个节点
}ListNode;

void Print_List(ListNode* phead);

void Push_Front_List(ListNode** phead, LDatatype val);

void Push_Back_List(ListNode** pphead, LDatatype val);

void Insert_List(ListNode** pphead, LDatatype pos, LDatatype val);

void Pop_Front_List(ListNode** pphead);

void Pop_Back_List(ListNode** pphead);

void Del_List(ListNode** pphead, LDatatype pos);

void Destroy(ListNode** phead);

void Modify_List(ListNode* phead, LDatatype pos, LDatatype val);

4.带头双向循环链表的实现

test.c

#include "Complex_List.h"

void menu()
{
	printf("****************************************\n");
	printf("************     请选择     ************\n");
	printf("******  1.PushFront   2.PushBack  ******\n");
	printf("******  3.Insert      4.PopFront  ******\n");
	printf("******  5.PopBack     6.Erase     ******\n");
	printf("******  7.Print       0.Exit      ******\n");
	printf("****************************************\n");
}

int main()
{
	int input = 0;
	int value = 0;
	int pos = 0;

	ListNode* plist = NULL;
	Init_List(&plist);

	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要头插的值>:");
			scanf("%d", &value);
			Push_Front_List(plist, value);
			break;
		case 2:
			printf("请输入你要尾插的值>:");
			scanf("%d", &value);
			Push_Back_List(plist, value);
			break;
		case 3:
			printf("请输入你要插入的位置和要插入的值>:");
			scanf("%d %d", &pos, &value);
			Insert_List(plist, pos, value);
			break;
		case 4:
			Pop_Front_List(plist);
			break;
		case 5:
			Pop_Back_List(plist);
			break;
		case 6:
			printf("请输入你要删除的值>:");
			scanf("%d", &pos);
			Erase_List(plist, pos);
			break;
		case 7:
			Print_List(plist);
			break;
		case 0:
			Destroy_List(&plist);
			printf("销毁链表成功\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

Complex_List.c

#include "Complex_List.h"


//创建新节点
ListNode* Buy_Newnode()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

	return newnode;
}


//打印链表数据
void Print_List(ListNode* phead)
{
	assert(phead); //phead需要解引用,不能为空

	ListNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}


//初始化链表
void Init_List(ListNode** pphead) //要改变实参的地址,需要二级指针
{
	(*pphead) = Buy_Newnode();
	(*pphead)->next = (*pphead);
	(*pphead)->prev = (*pphead);
	(*pphead)->data = 0;
}


//销毁链表
void Destroy_List(ListNode** pphead)
{
	ListNode* phead = (*pphead);
	ListNode* cur = phead->next;

	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	*pphead = NULL;
}


//头插
void Push_Front_List(ListNode* phead,LDataType val)
{
	ListNode* newnode = Buy_Newnode();

	if (newnode == NULL) //有可能空间开辟失败
	{
		perror("Fail: Push_Front_List\n");
		return;
	}

	ListNode* headnext = phead->next;
	newnode->prev = phead; //头插是插入哨兵节点后面
	newnode->next = headnext;
	newnode->data = val;
	phead->next = newnode;
	headnext->prev = newnode;
}


//尾插
void Push_Back_List(ListNode* phead, LDataType val)
{
	ListNode* newnode = Buy_Newnode();

	if (newnode == NULL) //有可能开辟空间失败
	{
		perror("Fail: Push_Back_List\n");
		return;
	}

	ListNode* tail = phead->prev;
	newnode->next = phead; //尾删就是删除哨兵节点前一个节点,因为是循环链表
	newnode->prev = tail;
	newnode->data = val;
	phead->prev = newnode;
	tail->next = newnode;
}


//寻找指定数据节点
ListNode* Find_Ptr_List(ListNode* phead, LDataType pos)
{
	ListNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == pos)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}


//在指定数据前面插入数据
void Insert_List(ListNode* phead, LDataType pos, LDataType val)
{
	ListNode* tmp = Find_Ptr_List(phead, pos);

	if (tmp == NULL)
	{
		printf("找不到该位置\n");
		return;
	}

	ListNode* dest = tmp;
	ListNode* last = dest->prev;

	ListNode* newnode = Buy_Newnode();

	newnode->prev = last;
	newnode->next = dest;
	newnode->data = val;
	last->next = newnode;
	dest->prev = newnode;
}


//头删
void Pop_Front_List(ListNode* phead)
{
	if (phead->next == phead) //如果哨兵节点的next是它自己,那么就代表链表为空,不进行删除
		return;

	ListNode* head = phead->next; //头删是删除哨兵节点下一个节点
	phead->next = head->next;
	head->next->prev = phead;
	free(head);
}


//尾删
void Pop_Back_List(ListNode* phead)
{
	if (phead->prev == phead) //如果哨兵节点的prev是它自己,那么就代表链表为空,不进行删除
		return;

	ListNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next = phead;
	free(tail);
}


//删除指定数据
void Erase_List(ListNode* phead, LDataType pos)
{
	ListNode* tmp = Find_Ptr_List(phead, pos);

	if (tmp == NULL)
	{
		printf("找不到该位置\n");
		return;
	}

	ListNode* dest = tmp;
	ListNode* last = dest->prev;
	ListNode* next = dest->next;
	last->next = next;
	next->prev = last;
	free(dest);
}

Complex_List.h

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

typedef int LDataType;     //节点储存的数据类型

typedef struct ListNode
{
	struct ListNode* prev; //指向节点的上一个节点
	struct ListNode* next; //指向节点的下一个节点
	LDataType data;        //节点储存的数据
}ListNode;

void Print_List(ListNode* phead);

void Init_List(ListNode** phead);

void Push_Front_List(ListNode* phead, LDataType val);

void Push_Back_List(ListNode* phead, LDataType val);

void Insert_List(ListNode* phead, LDataType pos, LDataType val);

void Pop_Front_List(ListNode* phead);

void Pop_Back_List(ListNode* phead);

void Erase_List(ListNode* phead, LDataType pos);

void Destroy_List(ListNode** pphead);

通过对比代码,我们发现带头双向循环链表的实现要比无头单向非循环链表的实现简单不少,因为带头双向循环链表哨兵位和循环的设定可以很大程度上方便我们的头插尾插,头删尾删等接口。

5.顺序表和链表的对比

在这里插入图片描述

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

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

相关文章

【Java基础】23.接口

文章目录 一、接口的概念1.接口介绍2.接口与类相似点3.接口与类的区别4.接口特性5.抽象类和接口的区别 二、接口的声明三、接口的实现四、接口的继承五、接口的多继承六、标记接口 一、接口的概念 1.接口介绍 接口&#xff08;英文&#xff1a;Interface&#xff09;&#xf…

运维小技能:nacos部署(外接mysql)

文章目录 I 安装nacos(m1版本)1.1 镜像启动1.2 查看docker容器日志1.3 开启鉴权II 外接mysql的docker部署方式2.1 复制mysql-schema.sql2.2 导入mysql-schema.sqlIII 配置远程用户3.1 创建数据库远程用户3.2 查看远程用户是否有密码I 安装nacos(m1版本) docker search nacos:查…

【前端工程化指南】Git常见操作之仓库相关操作

初始化本地仓库&#xff08;init&#xff09; 我们可以使用git init命令在当前或指定目录中初始化一个新的本地仓库&#xff0c;创建.git目录并设置仓库的基本配置。初始化仓库完成后&#xff0c;你可以使用其他 Git 命令来进行版本控制、提交更改以及与远程仓库进行交互。 命…

3月衣物清洁行业数据概况和趋势分析:总销额环比上涨超60%!

人们日常生活离不开衣物清洁产品&#xff0c;同时随着生活品质得提高和消费者健康意识得增强&#xff0c;对于衣物清洁行业的需求量与日俱增。作为日常必备的消耗品&#xff0c;衣物清洁产品备受消费者关注。借此&#xff0c;衣物清洁行业在3月份表现出稳定的发展态势。 根据鲸…

HANA SQL消耗内存和CPU线程的限制参数

HANA再处理大数据表相关的复杂Sql时&#xff0c;如果没有设置Memory和CPU线程上限的话&#xff0c;会将HANA的资源占用殆尽&#xff0c;造成HANA无法响应其他Sql请求&#xff0c;导致表现在应用服务器上就是系统卡顿的情况。解决上述问题的办法就是按照下图设置Memory(图1&…

如何封装Vue组件并上传到npm

前言 环境准备 1.注册npm账号&#xff1a;npm | Home (npmjs.com) 2.保证当前环境安装了vue、webpack、node&#xff0c;以下工作将在该环境下进行&#xff08;没有的小伙伴自行百度安装哈~&#xff09; 3.一下用到的环境版本 webpack&#xff1a;v5.1.4node&#xff1a;v…

编程实践:使用C语言计算k阶常系数线性递归序列

开篇 本文的目的是使用C语言模拟k阶常系数线性递归的运算过程&#xff0c;题目来源为《编程珠玑》第3章【数据决定程序结构】的课后习题2。具体的题目概要和代码实现&#xff0c;请看下文。 问题概要 因为这种问题涉及到的数学公式不太方便打出来&#xff0c;我直接用我笔记的原…

c++ 二分查找

二分查找&#xff08;Binary Search&#xff09;是一种在有序数组中查找特定元素的高效算法。它通过不断将搜索范围减半来查找目标元素。其时间复杂度为 O(log n)&#xff0c;这是因为每一步都将搜索范围减半&#xff0c;因此算法的性能非常高。 二分查找的基本思想是&#xf…

openwrt局域网配置多个IP

在局域网配置过程中&#xff0c;若是DHCP服务器关闭&#xff0c;又忘记了配置的ip&#xff0c;将很难访问到路由器重新进行配置。这种情况可以在路由器出厂时做一个备用ip去避免。 1.配置 以下是备用ip的配置方法&#xff0c;以SKYLAB的SKW99 WIFI模组为例进行说明&#xff1…

Java 网络编程之TCP(一):基于BIO

环境&#xff1a; jdk 17 IntelliJ IDEA 2023.1.1 (Ultimate Edition) Windows 10 专业版 22H2 TCP&#xff1a;面向连接的&#xff0c;可靠的数据传送协议 Java中的TCP网络编程&#xff0c;其实就是基于常用的BIO和NIO来实现的&#xff0c;本文先讨论BIO&#xff1b; BIO…

Xilinx 7系列FPGA 高性能(HP)接口与2.5V/3.3V 外设IO接口设计考虑

引言&#xff1a;Xilinx 7系列FPGA IO Bank分为HP Bank和HR Bank&#xff0c;HP IO接口电压范围为1.2V~1.8V&#xff0c;可以实现高性能&#xff0c;HR IO接口电压范围为1.2V~3.3V。当HR Bank与2.5V或者3.3V外设互联时&#xff0c;需要考虑接口电平的兼容性。根据性能需求、功能…

在Linux操作系统中介绍文件属性

查看文件属性&#xff0c;&#xff0c;可以使用命令lsattr 文件路径 使用命令lsattr 文件路径 查看文件属性 如上图所示&#xff0c;没有给文件 /etc/fstab 文件设置任何属性。 设置文件属性&#xff0c;&#xff0c;可以使用命令chattr 需要为文件加上的属性&#xff0…

葡萄书--深度学习基础

卷积神经网络 卷积神经网络具有的特性&#xff1a; 平移不变性&#xff08;translation invariance&#xff09;&#xff1a;不管检测对象出现在图像中的哪个位置&#xff0c;神经网络的前面几层应该对相同的图像区域具有相似的反应&#xff0c;即为“平移不变性”。图像的平移…

DHT11实验

文章目录 11.11.2 234 DS18B20 只能检测温度 右边这几个 都能 1 1.1 数字信号输出 指 0/1使用单总线通信 1个IO口就能获取温湿度 T/H要有 模数转化&#xff08;内部还有个8位单片机&#xff09;电容感湿元件 白色的 还有个ic NTC测温 可能在ic内部 使用单片机内部测温 精确度不…

服务器渲染技术(JSPELJSTL)

目录 前言 一.JSP 1.基本介绍 3.page指令(常用) 4.JSP三种常用脚本 4.1 声明脚本 <%! code %> 4.2 表达式脚本 <% code %> 4.3 代码脚本 <% code %> 4.4 注释 <%-- 注释 --%> 5. JSP 内置对象 5.1 基本介绍 5.2 九个内置对象 6.JSP域对象 二…

Python 物联网入门指南(六)

原文&#xff1a;zh.annas-archive.org/md5/4fe4273add75ed738e70f3d05e428b06 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十七章&#xff1a;机器人学 101 一提到机器人&#xff0c;我们就会被科幻小说所包围。我们可能会想起动画片《杰森一家》或者电影《终结…

MySQL如何避免全表扫描?

MySQL如何避免全表扫描&#xff1f; 这篇文章解释了何时以及为什么MySQL会执行全表扫描来解析查询&#xff0c;以及如何避免在大型表上进行不必要的全表扫描。 何时会发生全表扫描 MySQL使用全表扫描&#xff08;在EXPLAIN输出中的type列显示为ALL&#xff09;来解析查询的几…

汇智知了堂晨会聚焦:NAS应用如何赋能网络安全实战

在近期汇智知了堂网络安全75班的晨会上&#xff0c;一场关于NAS应用的深入分享完美展开。学员们以饱满的热情投入到这场安全讨论中&#xff0c;共同探索网络安全的新天地。 此次分享会聚焦于NAS的应用&#xff0c;旨在帮助学员们更好地了解NAS的定义与功能&#xff0c;掌握其在…

Reddit数据API 获取reddit的帖子、评论、按关键字搜索

近期调研发现 iDataRiver平台 https://www.idatariver.com/zh-cn/ 提供开箱即用的Reddit数据采集API&#xff0c;是目前用下来最方便简单的API&#xff0c;可以抓取 reddit 公开数据&#xff0c;例如 subreddit 中的帖子、按关键字搜索以及文章评论等&#xff0c;供用户按需调用…

智慧养老平台|基于SprinBoot+vue的智慧养老平台系统(源码+数据库+文档)

智慧养老平台目录 基于SprinBootvue的外贸平台系统 一、前言 二、系统设计 三、系统功能设计 前台 后台 管理员功能 老人功能 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农…