数据结构:为什么说链表是顺序表的升级版(c语言实现)

前言:

    我们在之前的几篇文章中详细的讲解了顺序表的特点,增删改查操作和动态顺序表的优点,并使用顺序表的底层结构实现了通讯录项目,似乎顺序表是一个非常完美的数据结构,它可以实现按照需求实现增删查改,对内存的控制也较为合理,空间都是在需要时手动开辟的。但是顺序表真的完美吗?事实上它并不完美,经过我们思考,顺序表还是存在一些问题,例如:(1)顺序表中间/头部的插入删除,时间复杂度为O(N) ( 2) 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 (3)增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们 再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。这些问题都是我们应该考虑的,而我们接下来要介绍的另一种数据结构——链表,就能在实现属性表的各个功能的前提下很好的解决这些问题。

1.链表

1.1 链表的概念及结构

    链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。换句话说,链表就是一块一块的空间由指针像链子一样将它们链接起来了,方便我们去访问链表的每一个节点。

    它的结构像极了图中的小火车:

我们来看看真实链表的结构 :

      链表是由一个指针指向链表的头节点,每个节点分为两个部分,分别是数据部分和指针部分,数据部分负责存储我们要存储的数据,指针部分负责存储下一个节点的地址,链表的每一个节点都储存着下一个节点的地址,最后一个节点的指针存储的是空指针,这使我们能够很方便的访问整个链表。

链表的特点:

1.从图中可以看出,链表的的结构在逻辑上是连续的,但是在物理上不一定连续。

2.每一个节点一般都是从堆上申请的。

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

1.2 链表的分类

链表的实际分类种类多达八种:

     链表分为单向和双向链表,带头和不带头链表,循环或者非循环链表,虽然由有这么多种链表,但是我们实际使用时最常用的只有两种:分别是无头单向非循环链表和带头双向循环链表。

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

1.3 单链表的实现

    我们这一期用c语言代码来实现单链表。与顺序表相同,我们将实现链表的文件分成三个,分别是头文件SList.h文件,SLIst.c方法实现文件和测试文件test.c文件:

1.3.2 链表实现 

     链表由数据和指针两部分组成(前面已经详细解释),由于我们不确定会存储哪种类型的数据,使用typedef来作为我们的数据类型,要更数据类型时只需要更改typedef重命名的数据类型就可以:

 1、无头+单向+非循环链表增删查改实现
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;}SLTNode;

在这里我们使用的是int类型,最后将链表的名字改为SLTNode。

实现一个顺序表要实现许多方法,链表也是如此:

SLTNode* SLTBuyNode(SLTDataType x);//申请一个节点
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾删void SLTPrint(SLTNode* phead);//打印void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插void SLTPosBack(SLTNode** pphead);//尾删void SLTPosFront(SLTNode** pphead);//头删SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//查找void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//指定位置之前插入void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//指定位置之后插入void SLTErase(SLTNode** pphead, SLTNode* pos);
//指定位置删除void SLTEraseAfter(SLTNode* pos);
//指定位置之后删除void SLTDestory(SLTNode** pphead);
//销毁
(1)申请一个节点 

     增删查改,只有里面有数据才能使用其他三个功能,所以我们首先实现插入功能,每次插入数据又需要申请一块空间,这会使得程序多出许多相同的代码,所以我们将申请空间封装成一个函数,在我们需要插入数据时,调用这个函数就可以了。而实现这个函数也非常的简单,只需要申请一块空间,将我们要插入的数据给它的data,next指针指向空就可以了,执行完这些操作后,返回这块空间:

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail!");}newnode->data = x;newnode->next = NULL;return newnode;}//申请节点
(2)尾插 

     尾插我们需要考虑到我们要插入数据,传递的是一级指针的地址,就需要用二级指针来接收,传过来的指针不能为空,否则会出现非法访问的错误,如果传过来的是一个空链表,我们直接让头执针指向我们新开辟的空间就可以,如果不是链表,我们定义一个尾指针,让它遍历整个链表最终走到结尾,然后让它指向我们新开辟的节点,实现尾插操作:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
//尾插
(3)头插 

  头插的操作比较简单,只需要申请一个新节点,让它的next指针指向头节点,再让头指针指向它:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}//头插
(4)查找

     为什么先讲查找呢?因为我们接下来的指定位置插入和删除需要用到它。我们要先判断传过来的地址和指针是否为空,如果为空,则没有查找的必要,确保地址和指针都不为空的情况下,我们才能进行查找操作。将链表遍历,如果发现数据内容相等,则视为找到了,返回这个节点,如果遍历完整个数组还没有找到这个节点,则说明链表里面没有这个节点,返回空指针:

SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//查找函数
(5)指定位置之前插入 

  我们首先要确保传过来的地址和链表不为空,指定的那个节点也不能为空,如果为空,我们就无法执行插入操作,还要考虑如果我们指定的节点就是头节点和指定的节点在后面的情况能不能用同一种方法解决,我们发现这两种情况不能用同一种方法解决,所以如果指定的位置就是头节点,我们就使用头插的方法,如果在后面,就需要找到指定位置的前一个节点,让新节点的next指针指向我们指定的那个节点,然后让指定位置的前一个节点的next指针指向新节点:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTBuyNode(x);if (*pphead == pos){SLTPushFront(pphead,x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* next = prev->next;newnode->next = next;prev->next = newnode;}}//指定位置之前插入数据
(6)指定位置之后插入

    指定位置之后插入数据需要将它的next指针存储起来,让新节点的next指针存储它的next指针,再将我们指定节点的next指针存储新节点的地址:

void SLTInsertAfter( SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//指定位置后插入
(7)尾删 

      删除操作我们要确保链表不能为空,传过来的地址也不能为空,而链表只有一个节点和多个节点的情况也是不同的,如果链表只有一个节点,我们只要将它置空就可以了,如果有多个节点,我们则需要遍历链表,先将最后一个节点释放,然后将指向它的前一个节点的next指针置空:

void SLTPosBack(SLTNode** pphead)
{(pphead && *pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* pcur = *pphead;SLTNode* prev = *pphead;while (pcur->next != NULL){prev = pcur;pcur = pcur->next;}free(pcur);prev->next = NULL;pcur = prev;}
}//尾删
(8)头删 

    头删的实现也比较简单,只需要将头节点的next指针存起来,然后将头节点指向的空间释放,最后让头节点指向我们存起来的next指针:

void SLTPosFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//头删
(9)指定位置删除 

     删除也要确保地址和指针不能为空,我们指定的节点也不能为空,否则无法进行删除操作,链表内只有一个节点和有多个节点的情况也是不一样的,如果只有一个节点,我们调用头删函数就可以了,如果有多个节点,就需要找到指定位置的前一个节点,将我们指定的节点释放后,再将它的前一个节点的next指针置空:

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);SLTNode* prev = *pphead;if (pos == *pphead){SLTPosFront(pphead);}else{while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}
//指定位置删除
(10)指定位置之后删除 

   我们要确保链表不能为空,也要确保链表呢有两个及以上的节点,否则我们无法指定删除某一个节点的后一个节点。先将我们要删除的节点的后一个节点存起来,将我们删除的节点释放后,用它的前一个节点的next指针指向那个存起来的节点,我们就能实现指定位置后删除:

void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//指定位置之后删除
(11)打印链表数据 

   打印链表要先确保链表不为空,为空则无法调用该函数,如果确保不为空,我们只需要遍历打印就可以:

void SLTPrint(SLTNode* phead)
{assert(phead);while (phead){printf("%d->", phead->data);phead = phead->next;}printf("NULL\n");
}//打印
(12)销毁链表

   由于链表的空间是使用malloc在堆上开辟的,只有在程序结束之后才会释放,所以我们使用完这些空间后要手动释放:

(13)测试

  实现完所有的方法,我们来测试一下吧:

我们调用这些方法都是没有问题的。 

以上就是这一期单链表的所有内容了,它不需要像顺序表一样插入删除数据时要将数据频繁挪动,空间也是插入一个数据开辟一块空间,毫无疑问它称得上是顺序表的升级版,我将源码放在下面,感兴趣的小伙伴可以试试哦。

SList.h :

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;}SLTNode;SLTNode* SLTBuyNode(SLTDataType x);//申请一个节点
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾删void SLTPrint(SLTNode* phead);//打印void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插void SLTPosBack(SLTNode** pphead);//尾删void SLTPosFront(SLTNode** pphead);//头删SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);//查找void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//指定位置之前插入void SLTInsertAfter( SLTNode* pos, SLTDataType x);
//指定位置之后插入void SLTErase(SLTNode** pphead, SLTNode* pos);
//指定位置删除void SLTEraseAfter(SLTNode* pos);
//指定位置之后删除void SLTDestory(SLTNode** pphead);
//销毁

SList.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail!");}newnode->data = x;newnode->next = NULL;return newnode;}//申请节点
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
//尾插void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}//头插
void SLTPosBack(SLTNode** pphead)
{(pphead && *pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* pcur = *pphead;SLTNode* prev = *pphead;while (pcur->next != NULL){prev = pcur;pcur = pcur->next;}free(pcur);prev->next = NULL;pcur = prev;}
}//尾删void SLTPosFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//头删SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//查找函数void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTBuyNode(x);if (*pphead == pos){SLTPushFront(pphead,x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* next = prev->next;newnode->next = next;prev->next = newnode;}}//指定位置之前插入数据void SLTInsertAfter( SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//指定位置后插入
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);SLTNode* prev = *pphead;if (pos == *pphead){SLTPosFront(pphead);}else{while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}
//指定位置删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//指定位置之后删除
void SLTDestory(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}//销毁
void SLTPrint(SLTNode* phead)
{assert(phead);while (phead){printf("%d->", phead->data);phead = phead->next;}printf("NULL\n");
}//打印

test.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"void test02()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPushFront(&plist, 9);SLTPushFront(&plist, 89);SLTPushFront(&plist, 9);SLTPrint(plist);SLTPosBack(&plist);SLTPrint(plist);SLTPosFront(&plist);/*SLTPosFront(&plist);SLTPosFront(&plist);SLTPosFront(&plist);SLTPosFront(&plist);SLTPosFront(&plist);*/SLTPrint(plist);SLTNode* find = SLTFind(&plist,2);SLTEraseAfter(find);//SLTErase(&plist, find);/*SLTInsertAfter(&plist, find, 32);*/SLTPrint(plist);SLTDestory(&plist);/*SLTInsert(&plist, find, 8);SLTPrint(plist);*//*if (find == NULL){printf("找不到!\n");}else{printf("找到了!\n");}*/
}
int main()
{test02();//test01();return 0;
}

 

 

 

    

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

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

相关文章

做好海外ASO优化的7大核心要素你了解几个?

海外App进行ASO优化时&#xff0c;需要综合考虑多个方面以确保应用在应用商店中获得更高的曝光率和下载量。以下是一些关键的ASO优化步骤&#xff0c;结合参考文章中的相关信息进行详细阐述&#xff1a; 1.关键词优化 调研目标市场的用户行为和检索习惯&#xff0c;挖掘与应用…

Gone框架介绍32 - 函数参数的依赖注入

gone是可以高效开发Web服务的Golang依赖注入框架 github地址&#xff1a;https://github.com/gone-io/gone 文档地址&#xff1a;https://goner.fun/zh/ 函数参数的依赖注入 函数参数的依赖注入&#xff0c;是v1.x版本正式发布的新功能&#xff0c;允许使用Goners仓库中的Gone…

锂磷硫(LPS)属于硫化物固态电解质 Li7P3S11是代表性产品

锂磷硫&#xff08;LPS&#xff09;属于硫化物固态电解质 Li7P3S11是代表性产品 锂磷硫&#xff08;LPS&#xff09;&#xff0c;为非晶态材料&#xff0c;是硫化物固态电解质代表性产品之一&#xff0c;具有热稳定性好、成本较低等优点&#xff0c;在固态电解质中离子电导率较…

【Deep Learning】Meta-Learning:训练训练神经网络的神经网络

元学习&#xff1a;训练训练神经网络的神经网络 本文基于清华大学《深度学习》第12节《Beyond Supervised Learning》的内容撰写&#xff0c;既是课堂笔记&#xff0c;亦是作者的一些理解。 1 Meta-Learning 在经典监督学习中&#xff0c;给定训练数据 { ( x i , y i ) } i \{…

使用Spring Boot实现用户认证和授权

文章目录 引言第一章 Spring Boot概述1.1 什么是Spring Boot1.2 Spring Boot的主要特性 第二章 用户认证和授权基础知识2.1 用户认证2.2 用户授权2.3 Spring Security概述 第三章 项目初始化第四章 实现用户认证和授权4.1 定义用户实体类和角色实体类4.2 创建Repository接口4.3…

IntelliJ IDE 插件开发 | (十)主题插件开发入门

系列文章 本系列文章已收录到专栏&#xff0c;交流群号&#xff1a;689220994&#xff0c;也可点击链接加入。 前言 在前面的章节中&#xff0c;我们介绍的都是功能性插件的开发内容&#xff0c;本文则会介绍一下主题类插件的开发方式。不过本文也只是带大家入个门&#xff…

linux下chromium/chrome中文字体粗体渲染问题

估计不少人更新后都遇到这个情况了吧&#xff0c;粗体渲染如然变得很模糊&#xff0c;很奇怪&#xff0c;Google下说是字体实现方式变了&#xff0c;国内一些网站用的中文字体都是宋体&#xff0c;但是宋体本身没有粗体&#xff0c;Win下的粗体是微软自己通过某种方式实现的&am…

靠3个字寻求机会,情商不够,别勉强自己

之前我分享了一篇文章寻求一个自由职业的前端伙伴&#xff0c;吸引了好几位朋友来咨询合作&#xff0c;中间出现了不少插曲&#xff0c;好在结果是令人满意的。 作为一名初次创业者&#xff0c;我承认很多地方做的不是那么到位&#xff0c;比如招聘合作伙伴&#xff0c;理想的状…

LLM2Vec论文阅读笔记

这是篇LLM论文&#xff0c;用decoder-like的LLM去提取embedding文章认为&#xff0c;decoder-like的LLM在text embedding task表现不优的一大原因就是其casual attention mechanism&#xff0c;其实就是mask的问题。所以只要对现有的decoder-only LLM进行如下三步改进&#xff…

从零到一学FFmpeg:av_compare_ts函数详析与实战

文章目录 前言一、函数原型二、功能描述三、使用场景四、使用实例 前言 av_compare_ts是FFmpeg库中的一个函数&#xff0c;用于比较两个时间戳&#xff08;Timestamps&#xff09;。这个函数广泛应用于视频处理、流媒体播放和多媒体同步等场景&#xff0c;特别是在需要精确控制…

2024 Jiangsu Collegiate Programming Contest C. Radio Direction Finding 题解 交互 二分

Radio Direction Finding 题目描述 This is an interactive problem. Radio direction finding, also known as radio orienteering or radio fox hunting, is a sport that combines radio technology with outdoor navigation. Participants use specialized receivers to…

Java面试题:对比不同的垃圾收集器(如Serial、Parallel、CMS、G1)及其适用场景

Java虚拟机&#xff08;JVM&#xff09;提供了多种垃圾收集器&#xff0c;每种垃圾收集器在性能和适用场景上各有不同。以下是对几种常见垃圾收集器&#xff08;Serial、Parallel、CMS、G1&#xff09;的对比及其适用场景的详细介绍&#xff1a; 1. Serial 垃圾收集器 Serial…

品牌价值超1592亿,九牧是如何炼成“六边形战士”?

作者 | 吉羽 来源 | 洞见新研社 经历了多年高速发展的中国市场开始慢慢减速&#xff0c;消费者正变得越来越“挑剔”&#xff0c;在信息爆炸的今天&#xff0c;企业面临“需求”与“流量”的双重考验。 市场凭什么记住你&#xff1f;选择你&#xff1f; 答案只有一个&#x…

关于飞浆文字识别技术的运用

飞桨PaddlePaddle-源于产业实践的开源深度学习平台&#xff0c;有关文章可以在此进行查询 飞桨&#xff08;PaddlePaddle&#xff09;是一个由百度开源的深度学习平台&#xff0c;它提供了丰富的机器学习算法库&#xff0c;支持多种深度学习模型的构建、训练和部署。飞桨平台具…

【漏洞复现】万户-ezOFFICE download_ftp.jsp 任意文件下载漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

[项目名称]项目介绍、代码解释及推荐理由

项目介绍 ----  [项目介绍文字描述&#xff0c;如果需要&#xff0c;可引入代码进行说明]  代码解释 ----  [详细解释代码&#xff0c;针对关键部分进行分析]  项目地址 ----  请查看[gitcode链接]中的项目&#xff1a;https://gitcode.com/[你的项目地址]  推荐理…

Java面试题:详细描述Java内存模型中的各个内存区域,以及它们的作用

Java内存模型&#xff08;Java Memory Model&#xff0c;JMM&#xff09;定义了Java程序中各种变量&#xff08;尤其是共享变量&#xff09;的访问规则和可见性&#xff0c;规定了不同线程之间如何通过内存进行交互。Java内存模型中的各个内存区域如下&#xff1a; 1. 堆&…

基于51单片机计步器—无线蓝牙APP上传

基于51单片机计步器设计 &#xff08;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 本设计由STC89C52单片机最小系统ADXL345加速度传感器lcd1602液晶电路蓝牙模块电路呼吸灯电路电源电路组成。 1.通过ADXL345检测步数&#xff0…

调试实战 | 记一次有教益的 vs2022 内存分配失败崩溃分析(续)

前言 前一阵子遇到了 vs2022 卡死的问题&#xff0c;在上一篇文章中重点分析了崩溃的原因 —— 当 vs2022 尝试分配 923MB 的内存时&#xff0c;物理内存页文件大小不足以满足这次分配请求&#xff0c;于是抛出异常。 本篇文章将重点挖掘一下 vs2022 在崩溃之前已经分配的内容…

HTML静态网页成品作业(HTML+CSS+JS)——动漫斗罗大陆介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现图片轮播和tab切换&#xff0c;共有3个页面。 …