数据结构 ——— C语言实现无哨兵位单向不循环链表

目录

前言

动态顺序表的缺陷

单链表的概念

单链表中节点的结构

单链表逻辑结构示意图​编辑

实现单链表前的准备工作

实现单链表

1. 定义节点的指针

2. 创建节点

 3. 打印单链表中的所有数据

4. 在单链表头部插入数据

5. 在单链表尾部插入数据

6. 在单链表头部删除数据

7. 在单链表尾部删除数据

8. 在单链表中查找指定的节点

9. 在单链表中 pos 节点插入数据

1. 在 pos 节点之前插入

 2. 在 pos 节点之后插入

10. 在单链表中 pos 节点删除数据

1. 删除 pos 节点

2. 删除 pos 节点后一个节点

11. 释放单链表的所有节点

SList.h 的所有代码

SList.c 的所有代码


前言

在前几章,讲解了动态顺序表的实现和相关oj题

数据结构 ——— C语言实现动态顺序表-CSDN博客

数据结构 ——— 顺序表oj题:移除 nums 数组中的 val 元素(快慢指针)-CSDN博客

数据结构 ——— 顺序表oj题:编写函数,删除有序数组中的重复项-CSDN博客

接下来讲解的是动态顺序表的缺陷和单链表的概念以及实现


动态顺序表的缺陷

  1. 顺序表中间部分的插入删除函数和头部插入删除函数,时间复杂度都为O(N)
  2. 增容需要申请新的空间,拷贝数据,释放旧空间,会有不小的空间消耗,且还存在原地扩容或者异地扩容的问题
  3. 合理的增容机制一般是乘以原空间的2倍的增长,但势必还是会有一定的空间浪费

由以上的顺序表的缺陷,给出了新的数据结构 —— 单链表,单链表的结构可以完美的解决以上问题


单链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,那么链表的顺序逻辑是通过链表中的指针链接(串联)次序实现的,且链表中的每一个节点都是独立的空间

链表这样设计的好处有:

  1. 解决了头部/中部插入删除时需要挪动数据的问题
  2. 按需申请或释放,不会存在浪费空间的问题
  3. 不会扩容,也不会存在扩容时的原地扩容或者异地扩容

单链表中节点的结构

// 单链表中数据的类型
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data; //单链表的数据

	struct SListNode* next; //指向下一个节点的指针
}SLTNode;

以上的结构是单链表中每一个节点的结构
节点的结构由两部分组成:当前节点的数据和指向下一个节点的指针

通过 next 指针把各个节点串联起来,最后一个节点的指针指向 NULL 表示链表的尾节点,这样的结构就称之为单链表


单链表逻辑结构示意图

单链表中的每一个节点存储的都是数据和下一个节点的地址(指针)

只有最后一个节点的地址存储的是NULL(作用是用来标记结束) 


实现单链表前的准备工作

和实现顺序表一样,需要准备3个文件

test.c 文件:用来测试代码功能的完善性

SList.h 文件:用来声明头文件和定义单链表的结构以及函数的声明

SList.c 文件:用来实现单链表的功能函数


实现单链表

1. 定义节点的指针

SLTNode* plist = NULL;

2. 创建节点

static SLTNode* BuyLTNode(SLTDataType x)
{
	// 开辟节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));

	// 判断是否开辟成功
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

添加 static 关键字用作保护,因为 创建节点函数 只会在 插入节点相关的函数 中使用,避免被用户直接使用,防止内存泄漏

newnode->data 存储数据 X
newnode->next 默认指向 NULL


 3. 打印单链表中的所有数据

void SLTPrint(SLTNode* phead)
{
	// 判断 phead 指针的有效性
	assert(phead != NULL);

	SLTNode* cur = phead;

	// 当前单链表中无数据时
	if (cur == NULL)
	{
		printf("当前链表无数据\n");
		return;
	}

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

一般在操作单链表时,不会直接使用 phead 指针,而是将 phead 指针赋值给同类型的 cur 指针来进行操作(因为会对指针的指向进行改动)

cur->data 是当前节点的数据,先打印当前数据
cur->next 是指向下一个节点的地址(指针)
cur->next 的类型是 struct SListNode* ,cur 的类型同样是 struct SListNode* ,所以把 cur->next 赋值给 cur ,那么 cur 这个指针就指向了下一个节点
直到 cur 为 NULL 就停止,因为单链表最后一个节点的 next 是NULL


4. 在单链表头部插入数据

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

plist 本身就是 SLTNode* 类型的指针,且不论是头插还是尾插,只要涉及到要改变 plist 的指向或者节点中的数据时,那么就要传递 plist 指针的地址才可以,所以函数的形参部分就要用二级指针接收(*pphead == plist

第一次头插时:将 newnode 的 next 指向 *pphead(因为第一次头插时的 *pphead 就是 NULL ,那么 newnode 就是尾节点,所以刚好让尾节点的 next 指向了 NULL)
最后将 *pphead 指向新节点 newnode ,这样就完成了在单链表的头部插入数据

多次头插时:*pphead 就是原链表头节点的地址,直接将要头插的新节点 newnode 的 next 指向 *pphead ,这样就完成了头插和节点的链接,最后再将 *pphead 指向新的头节点 newnode

测试代码:


5. 在单链表尾部插入数据

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		
		// 找到尾节点
		while (cur->next != NULL)
			cur = cur->next;

		cur->next = newnode;
	}
}

尾插数据要分两种情况看待:

当单链表为空链表时:那么直接将 *pphead 指向 newnode 即可

当单链表不为空链表时:先要找到尾节点,再把尾节点的 next 指向 newnode ,这样就实现了在单链表尾部插入数据

测试代码:


6. 在单链表头部删除数据

void SLTPopFront(SLTNode** pphead)
{
	// 当链表为空时
	if (*pphead == NULL)
	{
		printf("无数据可删除\n");
		return;
	}

	SLTNode* cur = *pphead;
	*pphead = cur->next;

	free(cur);
}

头删数据要分两种情况看待:

当单链表为空时:没有数据可删除,提醒用户后返回即可

当单链表不为空时(一个或多个节点时):利用 cur 指针记录头节点的地址也就是 *pphead ,cur->next 就是第二个节点的地址,直接赋值给 *pphead 即可,这样就改变了 plist 的指向,最后再用 free 把头节点释放掉(以上代码逻辑在只有一个节点时同样适用)

测试代码:


7. 在单链表尾部删除数据

void SLTPopBack(SLTNode** pphead)
{
	// 当前链表为空时
	if (*pphead == NULL)
	{
		printf("无数据可删除\n");
		return;
	}

	// 当只有一个节点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else // 有多个节点时
	{
		SLTNode* cur = *pphead;

		// 找到尾节点的前一个节点
		while (cur->next->next != NULL)
			cur = cur->next;

        free(cur->next);
        cur->next = NULL;
	}
}

尾删数据要分三种情况看待:

当单链表为空时:没有数据可删除,同样提醒用户后返回即可

当单链表只有一个节点时:直接 free 当前节点,也就是 *pphead 指向的节点,再置空

当单链表有多个节点时:利用 cur->next->next != NULL 找到尾节点的前一个节点,找到后,那么 cur->next 就是尾节点的地址,直接 ferr 再置空即可

测试代码:


8. 在单链表中查找指定的节点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;

	while (cur != NULL)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}

	return NULL;
}

根据数据找指定的节点,数据 X 和每个节点依次比较,找到了指定节点就返回指定节点的地址(指针),没有找到就返回 NULL 

且 SLTFind 函数同时具备读和写的功能,因为返回的是一个节点的指针,所以可以接收返回值对节点的数据进行修改


9. 在单链表中 pos 节点插入数据

1. 在 pos 节点之前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	// pos 节点会通过 SLTFind 函数查找,所以要确保 pos 节点的存在
	assert(pos != NULL);

	// 当第一个节点就是 pos 时
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		// 找到 pos 节点的前一个节点
		while (cur->next != pos)
			cur = cur->next;

		prev = cur;

		// 申请新节点
		SLTNode* newnode = BuyLTNode(x);

		prev->next = newnode;
		newnode->next = pos;
	}
}

当第一个节点就是 pos 时:在 pos 之前插入就是头插的逻辑,直接调用 SLTPushFont 函数即可

其他情况:先要从头找到 pos 节点的前一个节点 prev,再通过各自的 next 串联起来

测试代码:


 2. 在 pos 节点之后插入

void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	// pos 节点会通过 SLTFind 函数查找,所以要确保 pos 节点的存在
	assert(pos != NULL);

	// 找到 pos 节点的下一个节点
	SLTNode* nextnode = pos->next;

	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	pos->next = newnode;
	newnode->next = nextnode;
}

需要注意先后顺序,要先找到 pos 节点的下一个节点 nextnode,再通过各自的 next 串联起来

测试代码:


10. 在单链表中 pos 节点删除数据

1. 删除 pos 节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos != NULL);

	/// 当第一个节点就是 pos 时
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		// 找到 pos 节点的前一个节点
		while (cur->next != pos)
			cur = cur->next;

		prev = cur;

		prev->next = pos->next;

		free(pos);
	}
}

当第一个节点就是 pos 时:删除 pos 节点就是头删的逻辑,调用 SLTPopFront 函数即可

其他情况:先要找到 pos 节点的前一个节点 prev ,再和 pos 的下一个节点通过 next 串联

测试代码: 


2. 删除 pos 节点后一个节点

void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos != NULL);

	// 当 pos 是最后一个节点时
	if (pos->next == NULL)
	{
		printf("无数据可删除\n");
		return;
	}
	else
	{
		// 找到 pos 节点的下两个节点
		SLTNode* nextnode = pos->next->next;

		free(pos->next);

		pos->next = nextnode;
	}
}

pos 节点为最后一个节点时:那么 pos 后就没有节点,提醒用户后返回即可

其他情况:先找到 pos 节点的后两个节点 nextnode,在释放 pos 节点的下一个节点,最后再将 pos 节点和 nextnode 节点串联

测试代码:


11. 释放单链表的所有节点

void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;

	while (cur != NULL)
	{
		prev = cur->next;

		free(cur);

		cur = prev;
	}
}

释放当前 cur 节点前要先存储下一个节点的地址到 prev ,否则释放了当前 cur 节点就找不到下一个节点了


SList.h 的所有代码

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

// 单链表中数据的类型
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data; //单链表的数据

	struct SListNode* next; //指向下一个节点的指针
}SLTNode;

// 打印
void SLTPrint(SLTNode* phead);

// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

// 头删
void SLTPopFront(SLTNode** pphead);

// 尾删
void SLTPopBack(SLTNode** pphead);

// 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

// pos位置插入(在 pos 之前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

// pos位置插入(在 pos 之后插入)
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);

// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);

// pos位置之后删除
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);

// 释放
void SLTDestroy(SLTNode** pphead);

SList.c 的所有代码

#include"SList.h"

// 打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;

	// 当前单链表中无数据时
	if (cur == NULL)
	{
		printf("当前链表无数据\n");
		return;
	}

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

// 创建节点
static SLTNode* BuyLTNode(SLTDataType x)
{
	// 开辟节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));

	// 判断是否开辟成功
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		
		// 找到尾节点
		while (cur->next != NULL)
			cur = cur->next;

		cur->next = newnode;
	}
}
// 头删
void SLTPopFront(SLTNode** pphead)
{
	// 当前链表为空时
	if (*pphead == NULL)
	{
		printf("无数据可删除\n");
		return;
	}

	SLTNode* cur = *pphead;
	*pphead = cur->next;

	free(cur);
}

// 尾删
void SLTPopBack(SLTNode** pphead)
{
	// 当前链表为空时
	if (*pphead == NULL)
	{
		printf("无数据可删除\n");
		return;
	}

	// 当只有一个节点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else // 有多个节点时
	{
		SLTNode* cur = *pphead;

		// 找到尾节点的前一个节点
		while (cur->next->next != NULL)
			cur = cur->next;

		free(cur->next);
		cur->next = NULL;
	}
}

// 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;

	while (cur != NULL)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}

	return NULL;
}

// 任意位置插入(在 pos 之前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	// pos 节点会通过 SLTFind 函数查找,所以要确保 pos 节点的存在
	assert(pos != NULL);

	// 当第一个节点就是 pos 时
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		// 找到 pos 节点的前一个节点
		while (cur->next != pos)
			cur = cur->next;

		prev = cur;

		// 申请新节点
		SLTNode* newnode = BuyLTNode(x);

		prev->next = newnode;
		newnode->next = pos;
	}
}

// 任意位置插入(在 pos 之后插入)
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	// pos 节点会通过 SLTFind 函数查找,所以要确保 pos 节点的存在
	assert(pos != NULL);

	// 找到 pos 节点的下一个节点
	SLTNode* nextnode = pos->next;

	// 申请新节点
	SLTNode* newnode = BuyLTNode(x);

	pos->next = newnode;
	newnode->next = nextnode;
}

// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos != NULL);

	/// 当第一个节点就是 pos 时
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		SLTNode* prev = NULL;

		// 找到 pos 节点的前一个节点
		while (cur->next != pos)
			cur = cur->next;

		prev = cur;

		prev->next = pos->next;

		free(pos);
	}
}

// pos位置之后删除
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos != NULL);

	// 当 pos 是最后一个节点时
	if (pos->next == NULL)
	{
		printf("无数据可删除\n");
		return;
	}
	else
	{
		// 找到 pos 节点的下两个节点
		SLTNode* nextnode = pos->next->next;

		free(pos->next);

		pos->next = nextnode;
	}
}

// 释放
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;

	while (cur != NULL)
	{
		prev = cur->next;

		free(cur);

		cur = prev;
	}
}

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

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

相关文章

脏读、不可重复读、幻读的解决方法

上一篇博客提到了脏读、不可重复读、幻读的含义&#xff0c;也知道了是因为什么情况导致出现的这些问题&#xff0c;这篇博客就带大家一起来了解一下他们的解决办法~ 脏读&#xff1a;脏读出现的原因主要是因为一个事务读取了另外一个事务未提交的数据&#xff0c;就可能出现脏…

掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系

在日常开发中&#xff0c;我们常常需要对复杂的 SQL 进行数据血缘分析。 本文重点讨论在具有 * 列的嵌套子查询中建立表和列之间正确关系的挑战。使用 Teradata SQL 代码示例来说明该过程。 本文聚焦于一个别名为 SUBSCRIBER_ 的子查询及其派生的列&#xff0c;这些列在外层查…

无需VPN!大厂力作:免费AI对口型神器登场,让你的视频制作更简单!

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 &#xff08;偶尔会因为推荐工具&#xff…

《深度学习》OpenCV 图像拼接 原理、参数解析、案例实现

目录 一、图像拼接 1、直接看案例 图1与图2展示&#xff1a; 合并完结果&#xff1a; 2、什么是图像拼接 3、图像拼接步骤 1&#xff09;加载图像 2&#xff09;特征点检测与描述 3&#xff09;特征点匹配 4&#xff09;图像配准 5&#xff09;图像变换和拼接 6&am…

实验3 选择结构

1、计算分段函数的值 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <math.h> int main() {double x,y0;scanf("%lf",&x);if(x<0){printf("error!\n");return 0;}if(0<x&&x<1){ylog10(x);}else if(1<…

缓存数据减轻服务器压力

问题:不是所有的数据都需要请求后端的 不是所有的数据都需要请求后端的,有些数据是重复的、可以复用的解决方案:缓存 实现思路:每一个分类为一个key,一个可以下面可以有很多菜品 前端是按照分类查询的,所以我们需要通过分类来缓存缓存代码 /*** 根据分类id查询菜品** @pa…

Java | Leetcode Java题解之第459题重复的子字符串

题目&#xff1a; 题解&#xff1a; class Solution {public boolean repeatedSubstringPattern(String s) {return kmp(s s, s);}public boolean kmp(String query, String pattern) {int n query.length();int m pattern.length();int[] fail new int[m];Arrays.fill(fa…

54.二叉树的最大深度

迭代 class Solution {public int maxDepth(TreeNode root) {if(rootnull){return 0;}int de0;Queue<TreeNode> qunew LinkedList<>();TreeNode tn;int le;qu.offer(root);while(!qu.isEmpty()){lequ.size();while(le>0){tnqu.poll();if(tn.left!null){qu.offe…

学会这几个简单的bat代码,轻松在朋友面前装一波13[通俗易懂]

大家好&#xff0c;又见面了&#xff0c;我是你们的朋友全栈君。 这个标题是干什么用的? 最近看晚上某些人耍cmd耍的十分开心&#xff0c;还自称为“黑客”&#xff0c;着实比较搞笑.他们那些花里胡哨的东西在外行看来十分nb,但只要略懂一些&#xff0c;就会发现他们的那些十…

论文阅读笔记-A Comparative Study on Transformer vs RNN in Speech Applications

前言 介绍 序列到序列模型已广泛用于端到端语音处理中,例如自动语音识别(ASR),语音翻译(ST)和文本到语音(TTS)。本文着重介绍把Transformer应用在语音领域上并与RNN进行对比。与传统的基于RNN的模型相比,将Transformer应用于语音的主要困难之一是,它需要更复杂的配…

JavaScript 数组方法

数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始)&#xff0c;整个数组用方括号表示。两端的方括号是数组的标志。 var a["a","b","c"]; 除了在定义时赋值&#xff0c;数组也可以先定义后赋值。 var arr[];arr[1]"a"…

Qt_绘图

目录 1、绘图核心类 2、QPainter类的使用 2.1 绘制线段 2.2 绘制矩形 2.3 绘制圆形 2.4 绘制文本 3、QPen类的使用 3.1 使用画笔 4、QBrush类的使用 4.1 使用画刷 5、绘制图片 5.1 测试QPixmap 5.1.1 图片移动 5.1.2 图标缩小 5.1.3 旋转图片 5.1.4 将…

windows10或11家庭版实现远程桌面连接控制

远程协助是一种Windows工具&#xff0c;允许控制者使用鼠标和键盘远程控制接受者的计算机&#xff0c;从某种程度上讲&#xff0c;这也是Win10家庭版无法远程桌面的一个有效替代方案。 步骤1. 在使用Windows远程协助之前&#xff0c;您需要先更改某些设置&#xff0c;右键单击…

封装el-upload组件,用于上传图片和视频

使用环境 vue3element-ui plus 需要根据后端返回结构修改的函数&#xff1a;onPreview onRemove onSuccess 组件使用 基本使用 源代码&#xff1a; <script setup> import AutoUploadFile from /components/auto-upload-file/index.vue function change(urls){console.…

金智维KRPA之Excel自动化

Excel自动化操作概述 Excel自动化主要用于帮助各种类型的企业用户实现Excel数据处理自动化&#xff0c;Excel自动化是可以从单元格、列、行或范围中读取数据&#xff0c;向其他电子表格或工作簿写入数据等活动。 通过相关命令&#xff0c;还可以对数据进行排序、进行格式…

javaScript数组(16个案例+代码+效果图)

目录 1.数组的概念 2.创建数组 1.通过数组字面量创建数组 1.代码 2.效果 2.通过new Array()创建数组 1.代码 2.效果 3.数组的基本操作 1.获取数组的长度 案例:获取数组的长度 1.代码 2.效果 2.修改数组的长度 1.代码 2.效果 4.访问数组 案例:访问数组 1.代码 2.效果 5.遍历数组…

【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。使用的软件&#…

一个真实可用的登录界面!

需要工具&#xff1a; MySQL数据库、vscode上的php插件PHP Server等 项目结构&#xff1a; login | --backend | --database.sql |--login.php |--welcome.php |--index.html |--script.js |--style.css 项目开展 index.html&#xff1a; 首先需要一个静态网页&#x…

【HTML5】html5开篇基础(4)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

React 解释常见的 hooks: useState / useRef / useContext / useReducer

前言 如果对 re-render 概念还不清楚&#xff0c;建议先看 React & 理解 re-render 的作用、概念&#xff0c;并提供详细的例子解释 再回头看本文。 如果对 React 基础语法还不熟练&#xff0c;建议先看 React & JSX 日常用法与基本原则 再回头看本文。 useState useS…