【C语言数据结构(基础版)】第四站:栈和队列

news2024/9/20 14:34:14

目录

一.栈的表示和实现

1.栈的概念及结构

 2.栈的实现

 二、栈的实现

1.栈的声明和定义

2.栈的初始化

3.栈的销毁

4.入栈

5.出栈

6.返回栈顶元素

7.返回栈的元素个数

8.栈是否为空

9.测试

 三、栈的完整代码

四、队列的表示和实现

1.队列的概念和结构

2.队列的实现

五、队列的实现

1.队列的声明和定义

2.队列的初始化

3.队列的销毁

4.队列的插入

5.队列的删除

6.队列的判空

7.取出队头的数据

8.取出队尾的数据

9.队列数据的个数

10.测试

 六、队列完整代码

总结


一.栈的表示和实现

1.栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵循后进先出LIFO(Last in First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

出栈:栈的删除操作叫做出栈。出数据也在栈顶

 2.栈的实现

从上面我们也可以看出来,栈的实现一般可以使用数组或者链表实现,相对而言,其实数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小

 二、栈的实现

1.栈的声明和定义

根据我们上面的分析,我们决定使用顺序表来实现栈。所以它的声明如下

typedef int STDateType;
typedef struct Stack
{
	STDateType* a;
	int top;
	int capacity;
}Stack;

2.栈的初始化

它的声明如下

//栈的初始化
void StackInit(Stack* ps);

它的函数实现也很简单

//栈的初始化
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = (STDateType*)malloc(4 * sizeof(STDateType));
	if (ps->a == NULL)
	{
		printf("malloc is fail\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;
}

注意这里的top可以为0也可以为-1

这两种其实也是有区别的,

如果top初始化为0的化,那就意味着top指向栈顶元素的下一个

如果top初始化欸-1的话,那么意味着top指向栈顶元素

3.栈的销毁

创建好那么必要要有销毁

销毁的声明为

//栈的销毁
void StackDestory(Stack* ps);

实现为

//栈的销毁
void StackDestory(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

4.入栈

这个也非常简单,函数声明为

//入栈
void StackPush(Stack* ps, STDateType x);

实现为

//入栈
void StackPush(Stack* ps, STDateType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		//扩容
		STDateType* tmp = (STDateType*)realloc(ps->a, sizeof(STDateType) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			printf("realloc is fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

5.出栈

函数声明为

//出栈
void StackPop(Stack* ps);

函数实现为

//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	//栈为空了,还想要继续删除直接报错
	assert(ps->top > 0);
	ps->top--;
}

6.返回栈顶元素

这是函数声明

//取出栈顶元素
STDateType StackTop(Stack* ps);

这是函数实现

//取出栈顶元素
STDateType StackTop(Stack* ps)
{
	assert(ps);
	//栈为空,还继续调用的话直接报错
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

7.返回栈的元素个数

这是函数声明

//栈的元素个数
int StackSize(Stack* ps);

这是函数实现

//栈的元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

8.栈是否为空

这是函数声明

//栈是否为空
bool StackEmpty(Stack* ps);

这是函数实现

//栈是否为空
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return (ps->top == 0);
}

9.测试

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);

	printf("%d ", StackTop(&s));
	StackPop(&s);

	printf("%d ", StackTop(&s));
	StackPop(&s);


	StackPush(&s, 4);
	StackPush(&s, 5);

	while (!StackEmpty(&s))
	{
		printf("%d ", StackTop(&s));
		StackPop(&s);
	}
	StackDestory(&s);
}

运行结果为

 三、栈的完整代码

Test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);

	printf("%d ", StackTop(&s));
	StackPop(&s);

	printf("%d ", StackTop(&s));
	StackPop(&s);


	StackPush(&s, 4);
	StackPush(&s, 5);

	while (!StackEmpty(&s))
	{
		printf("%d ", StackTop(&s));
		StackPop(&s);
	}
	StackDestory(&s);
}

Stack.h文件

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

typedef int STDateType;
typedef struct Stack
{
	STDateType* a;
	int top;
	int capacity;
}Stack;

//栈的初始化
void StackInit(Stack* ps);
//栈的销毁
void StackDestory(Stack* ps);
//入栈
void StackPush(Stack* ps, STDateType x);
//出栈
void StackPop(Stack* ps);
//取出栈顶元素
STDateType StackTop(Stack* ps);
//栈的元素个数
int StackSize(Stack* ps);
//栈是否为空
bool StackEmpty(Stack* ps);

Stack.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

//栈的初始化
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = (STDateType*)malloc(4 * sizeof(STDateType));
	if (ps->a == NULL)
	{
		printf("malloc is fail\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;
}
//栈的销毁
void StackDestory(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
//入栈
void StackPush(Stack* ps, STDateType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		//扩容
		STDateType* tmp = (STDateType*)realloc(ps->a, sizeof(STDateType) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			printf("realloc is fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	//栈为空了,还想要继续删除直接报错
	assert(ps->top > 0);
	ps->top--;
}
//取出栈顶元素
STDateType StackTop(Stack* ps)
{
	assert(ps);
	//栈为空,还继续调用的话直接报错
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}
//栈的元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
//栈是否为空
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return (ps->top == 0);
}

四、队列的表示和实现

1.队列的概念和结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First in First Out)入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

2.队列的实现

队列也可以数组和链表的结构实现,但使用链表的结构更优一些,因为如果使用数组的结构,出队列在数组的头上出数据,效率会比较低.

五、队列的实现

1.队列的声明和定义

我们想要使用链表的结构来实现队列,但是由于单链表的尾插过于麻烦,不妨我们直接声明头节点和尾结点来控制队列

typedef int QDateType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDateType date;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

如上所示,我们先定义了一个队列结点的结构体,里面包含了指向下一个结点的指针和一个队列的数据。然后我们定义了一个队列结构体,我们主要是使用队列这个结构体,它里面有头结点和尾结点,有头有尾刚好确定一个队列。

2.队列的初始化

接下来让我们先来实现一下队列的初始化

函数声明为

//队列初始化
void QueueInit(Queue* pq);

实现为

//队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

其实对这个实现,建议与前面的一些LeetCode题目和单链表的实现进行对比,我们可以看出来这里的妙处。

3.队列的销毁

有始有终,我们来实现一下队列的销毁

//队列的销毁
void QueueDestory(Queue* pq);

函数实现为

//队列的销毁
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

 

4.队列的插入

函数声明为

//队列的插入
void QueuePush(Queue* pq, QDateType x);

函数实现为

//队列的插入
void QueuePush(Queue* pq, QDateType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		printf("malloc is fail\n");
		exit(-1);
	}
	//对新节点进行初始化
	newnode->date = x;
	newnode->next = NULL;

	//对新结点进行连接
	if (pq->head == NULL)
	{
		//头结点是空的,也就是第一个数据的插入
		pq->head = pq->tail = newnode;
	}
	else
	{
		//非第一个数据的插入
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

5.队列的删除

函数声明为

//队列的删除
void QueuePop(Queue* pq);

函数实现为

//队列的删除
void QueuePop(Queue* pq)
{
	assert(pq);
	//确保队列不是空的队列
	assert(pq->head);
	//如果只有一个结点,防止tail形成野指针
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//不是只有一个结点
	else
	{
		//保存第二个队列结点
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
} 

6.队列的判空

 函数声明

//判断队列是否为空
bool QueueEmpty(Queue* pq);

函数实现

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

7.取出队头的数据

函数声明

//取出队头的数据
QDateType QueueFront(Queue* pq);

函数实现

//取出队头的数据
QDateType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->date;
}

8.取出队尾的数据

函数声明

//取出队尾的数据
QDateType QueueBack(Queue* pq);

函数实现

//取出队尾的数据
QDateType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->tail->date;
}

9.队列数据的个数

函数声明

//取出数据的个数
int QueueSize(Queue* pq);

 函数实现

//取出数据的个数
int QueueSize(Queue* pq)
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur)
	{
		cur = cur->next;
		size++;
	}
	return size;
}

10.测试


void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	QueueDestory(&q);
}
int main()
{
	//TestStack();
	TestQueue();
}

运行结果为

 六、队列完整代码

 Test.c

void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	QueueDestory(&q);
}
int main()
{
	//TestStack();
	TestQueue();
}

Queue.h

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

typedef int QDateType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDateType date;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

//队列初始化
void QueueInit(Queue* pq);
//队列的销毁
void QueueDestory(Queue* pq);
//队列的插入
void QueuePush(Queue* pq, QDateType x);
//队列的删除
void QueuePop(Queue* pq);
//取出队头的数据
QDateType QueueFront(Queue* pq);
//取出队尾的数据
QDateType QueueBack(Queue* pq);
//取出数据的个数
int QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
//队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
//队列的销毁
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}
//队列的插入
void QueuePush(Queue* pq, QDateType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		printf("malloc is fail\n");
		exit(-1);
	}
	//对新节点进行初始化
	newnode->date = x;
	newnode->next = NULL;

	//对新结点进行连接
	if (pq->head == NULL)
	{
		//头结点是空的,也就是第一个数据的插入
		pq->head = pq->tail = newnode;
	}
	else
	{
		//非第一个数据的插入
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}
//队列的删除
void QueuePop(Queue* pq)
{
	assert(pq);
	//确保队列不是空的队列
	assert(pq->head);
	//如果只有一个结点,防止tail形成野指针
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//不是只有一个结点
	else
	{
		//保存第二个队列结点
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
} 
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}
//取出队头的数据
QDateType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->date;
}
//取出队尾的数据
QDateType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->tail->date;
}
//取出数据的个数
int QueueSize(Queue* pq)
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur)
	{
		cur = cur->next;
		size++;
	}
	return size;
}

总结

本节实现了栈和队列的实现,难度不大,希望大家都能学会

如果对你有帮助,不要忘记点赞加收藏哦!!!

想获得更多优质的内容,一定要记得关注我哦!!!

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

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

相关文章

光环:工业互联网探索及案例——杨宝刚

摘要&#xff1a;文章内容主要来源于光环国际2022年第三届中国科创者大会杨宝刚老师的分享&#xff0c;原分享名称为"工业互联网助力企业数智化转型"。讲述了工业互联网概念及用友锅炉的一个实例运用。业互联网是一个整合平台&#xff0c;把你的需求告诉平台&#xf…

比亚迪携手亚洲足球小姐王霜发布品牌广告:为梦想,一路向前

在世界杯进入半决赛的最后一天——12月15日&#xff0c;比亚迪发布了全新的品牌宣传片《为梦想&#xff0c;一路向前》&#xff1a;携手亚洲足球小姐王霜&#xff0c;通过足球与梦想的故事&#xff0c;向每一位勇敢前行的追梦人致敬。业内几乎所有人都肯定了此次合作&#xff0…

java基于微信小程序的点餐系统-计算机毕业设计

开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;…

JavaEE【Spring】:SpringMVC 程序开发

文章目录一、概念1、MVC 定义2、MVC 和 Spring MVC 的关系3、Spring MVC 和 Spring 的关系二、Spring MVC 创建和连接1、创建 Spring MVC 项目① 对比② 注意2、RequestMapping 注解介绍3、RequestMapping 是 post 还是 get 请求&#xff1f;① GET② POST4、GetMapping 和 Pos…

Transformer16 ~Robotics

还是Transformer相关 ,谷歌机器人团队等提出了 Robotics Transformer 1 (RT-1)。这是一种多任务模型&#xff0c;可以 tokenize 机器人的输入和输出动作&#xff0c;从而在运行时实现高效推理&#xff0c;使实时控制成为可能。 论文 1&#xff1a;RT-1: Robotics Transformer …

最强工程项目进度计划管理系统Oracle Primavera P6软件最新版本 22.12发布啦,新特征介绍

目录 一&#xff1a;前言 二&#xff1a;P6 22.12 新特征&#xff08;EN&#xff09; 三&#xff1a;P6 22.12 新特征&#xff08;CN&#xff09; 一&#xff1a;前言 近日&#xff0c;甲骨文(Oracle)公司发布了最新版本Primavea P6 2022版&#xff0c;最新版本号 22.12 Or…

day23 整合xxl-job上传近三天秒杀商品

整合xxl-job 1.部署调度中心 将doc目录下的sql脚本导入数据库 修改日志位置 maven打包mvn pageage -Dmaven.skip.testtrue 后台方式启动 nohup java -jar xxl-job-admin-2.3.0.jar > tag-web.log 2>&1 & 访问localhost:端口/xxl-job-admin 2.部署执行器…

[附源码]Nodejs计算机毕业设计基于疫情防控的超市管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

Innodb存储引擎-表(约束、视图、物化视图、分区表)

文章目录约束(数据完整性)视图物化视图分区表概述分区类型子分区分区和性能在表和分区间交换数据约束(数据完整性) 关系型数据库系统和文件系统的一个不同点是&#xff0c;关系数据库本身能保证存储数据的完整性&#xff0c;不需要应用程序的控制&#xff0c;而文件系统一般需…

ArcGIS Pro 加载项(6)——样式符号属性对调

之前是已经通过Python构建脚本工具&#xff0c;实现了stylx文件的符号属性的对调。 ArcGIS Pro脚本工具&#xff08;12&#xff09;——样式符号属性对调_学学GIS的博客-CSDN博客为地类做样式符号匹配经常碰到这样的问题&#xff1a;属性表里面只有地类代码&#xff0c;但是做…

win10+VS2017+OpenGL ES3.0 环境配置 源码编译及示例运行

win10VS2017OpenGL ES3.0 环境配置 源码编译及示例运行 win10VS2017OpenGL ES3.0 环境配置 源码编译及示例运行 本人参考其他人的博客内容学习opengles3.0编程指南&#xff0c;并在win10系统上用VS2017对OpenGL ES3.0 进行了实际的环境配置及源码编译。 一、环境准备工作 1…

Java项目:SSM网上药品销售商城网站系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本系统分为前后台&#xff0c;分为管理员与普通用户两种角色&#xff0c;前台由普通用户登录&#xff0c;后台由管理员登录&#xff1b; 管理员…

架构师必读 —— 逻辑模型(3)

逻辑思考总是从提问“为什么”开始 培养逻辑思考习惯的捷径之一&#xff0c;就是经常问自己“为什么”。无论收到什么信息&#xff0c;都应该试着问一句“为什么”。 举个例子&#xff0c;A公司收购了其他竞争公司。为什么会收购呢&#xff1f; 这其中隐藏着A公司的发展战略。另…

闲置物品交易系统

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 基于55m闲置物品交易系统 网站前台&#xff1a;关于我们、联系我们、公告信息、闲置物品、求购信息 管理员功能&#xf…

多态的学习

目录什么是多态多态有啥限制条件吗&#xff1f;重写向下转型和向上转型向上转型向下转型多态的优点多态缺陷避免在构造方法中调用重写的方法什么是多态 多态的概念&#xff1a;就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 举个例子大家就知道了&…

vue router动态路由与路由的匹配

vue router动态路由与路由的匹配&#xff0c;路由使用正则语法 一、动态路由 1.1 新建一个 user.vue文件 在views文件夹&#xff0c;新建一个user.vue文件 <template><div>用户{{id}}</div> </template><script setup>import { useRoute } from…

SAP ABAP Algorithm 计算一组数据样本的常用统计值(最大值/最小值 /平均值/中位数/众数/总数/方差/标准差 )

SAP ABAP Algorithm 计算一组数据样本的常用统计值(最大值/最小值 /平均值/中位数/众数/总数/方差/标准差 ) 引言&#xff1a; 这是统计学常用统计值的 ABAP 实现&#xff0c;这些统计值可以用在一些统计分析型报表中。 关键字&#xff1a;SAP ABAP Algorithm 算法 统计值 文…

Pytest测试框架(4):conftest.py文件应用---实现数据共享

conftest.py文件&#xff1a; pytest中的fixture是pytest用于将测试前后进行预备&#xff0c;清理工作的代码分离出核心测试逻辑的一种机制。但是我们更加希望的是在一个测试套件中&#xff0c;能够共享fixture的机制&#xff0c;这样一个测试套件里面的所有测试点都能够共同使…

Innodb存储引擎-idb文件格式解析

文章目录ibd 文件格式解析idb文件page类型和格式(File Header & Trailer)FIL_PAGE_TYPE_FSP_HDR格式Extent Descriptor格式Extent Descriptor链表管理Inode page链表管理FIL_PAGE_INODE格式Segment inode链表管理FIL_PAGE_TYPE_XDESFIL_PAGE_INDEX格式记录存储格式compact …

免费分享一个SSM商城管理系统,很漂亮的

大家好&#xff0c;我是锋哥&#xff0c;看到一个不错的SSM商城管理系统&#xff0c;分享下哈。 项目介绍 这是基于SSM框架开发的项目&#xff0c;前端用户界面采用 HtmlcssJavaScriptAjax开发&#xff0c;后台管理页面采用Easyui技术开发。 项目展示 1、主界面 2、商品详…