数据结构【第4章】——栈与队列
队列是只允许在一端进行插入操作、而在另-端进行删除操作的线性表。
栈
栈与队列:栈是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

问题:最先进栈的元素,是不是就只能是最后出栈呢?
不一定。栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,也就是说,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。
栈的抽象数据类型
对干栈来讲,理论上线性表的操作特性它都具备,可由干它的特殊性,所以针对它在操作上会有些变化。特别是插入和删除操作,我们改名为push和pop。

由于栈本身就是一个线性表,因此线性表的顺序存储和链式存储,对于栈来说,也是同样适用。
栈的顺序存储结构及实现
栈是线性表的特例,因此栈的顺序存储也是线性表顺序存储的简化,我们简称为顺序栈。
线性表是用数组来实现的,用数组的下标0作为栈底比较好,因为首元素都会存在栈底,变化量小。
我们定义一个top变量来指示栈顶元素在数组中的位置,它可以来回移动,意味着栈顶的top可以变大变小,若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在—个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1。


#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int *//* 顺序栈结构 */
typedef struct
{SElemType data[MAXSIZE];int top; /* 用于栈顶指针 */
}SqStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 构造一个空栈S */
Status InitStack(SqStack *S)
{ /* S.data=(SElemType *)malloc(MAXSIZE*sizeof(SElemType)); */S->top=-1;return OK;
}/* 把S置为空栈 */
Status ClearStack(SqStack *S)
{ S->top=-1;return OK;
}/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(SqStack S)
{ if (S.top==-1)return TRUE;elsereturn FALSE;
}/* 返回S的元素个数,即栈的长度 */
int StackLength(SqStack S)
{ return S.top+1;
}/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(SqStack S,SElemType *e)
{if (S.top==-1)return ERROR;else*e=S.data[S.top];return OK;
}/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{if(S->top == MAXSIZE -1) /* 栈满 */{return ERROR;}S->top++; /* 栈顶指针增加一 */S->data[S->top]=e; /* 将新插入元素赋值给栈顶空间 */return OK;
}/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{ if(S->top==-1)return ERROR;*e=S->data[S->top]; /* 将要删除的栈顶元素赋值给e */S->top--; /* 栈顶指针减一 */return OK;
}/* 从栈底到栈顶依次对栈中每个元素显示 */
Status StackTraverse(SqStack S)
{int i;i=0;while(i<=S.top){visit(S.data[i++]);}printf("\n");return OK;
}int main()
{int j;SqStack s;int e;if(InitStack(&s)==OK)for(j=1;j<=10;j++)Push(&s,j);printf("栈中元素依次为:");StackTraverse(s);Pop(&s,&e);printf("弹出的栈顶元素 e=%d\n",e);printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));GetTop(s,&e);printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));ClearStack(&s);printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
两栈共享空间
前提:两个栈中的数据类型相同
使用场景:通常是当两个栈的空间需求有相反关系时,即一个栈增长时另一个栈在缩短。

#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */typedef int Status; typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int *//* 两栈共享空间结构 */
typedef struct
{SElemType data[MAXSIZE];int top1; /* 栈1栈顶指针 */int top2; /* 栈2栈顶指针 */
}SqDoubleStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 构造一个空栈S */
Status InitStack(SqDoubleStack *S)
{ S->top1=-1;S->top2=MAXSIZE;return OK;
}/* 把S置为空栈 */
Status ClearStack(SqDoubleStack *S)
{ S->top1=-1;S->top2=MAXSIZE;return OK;
}/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(SqDoubleStack S)
{ if (S.top1==-1 && S.top2==MAXSIZE)return TRUE;elsereturn FALSE;
}/* 返回S的元素个数,即栈的长度 */
int StackLength(SqDoubleStack S)
{ return (S.top1+1)+(MAXSIZE-S.top2);
}/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{if (S->top1+1==S->top2) /* 栈已满,不能再push新元素了 */return ERROR; if (stackNumber==1) /* 栈1有元素进栈 */S->data[++S->top1]=e; /* 若是栈1则先top1+1后给数组元素赋值。 */else if (stackNumber==2) /* 栈2有元素进栈 */S->data[--S->top2]=e; /* 若是栈2则先top2-1后给数组元素赋值。 */return OK;
}/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{ if (stackNumber==1) {if (S->top1==-1) return ERROR; /* 说明栈1已经是空栈,溢出 */*e=S->data[S->top1--]; /* 将栈1的栈顶元素出栈 */}else if (stackNumber==2){ if (S->top2==MAXSIZE) return ERROR; /* 说明栈2已经是空栈,溢出 */*e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */}return OK;
}Status StackTraverse(SqDoubleStack S)
{int i;i=0;while(i<=S.top1){visit(S.data[i++]);}i=S.top2;while(i<MAXSIZE){visit(S.data[i++]);}printf("\n");return OK;
}int main()
{int j;SqDoubleStack s;int e;if(InitStack(&s)==OK){for(j=1;j<=5;j++)Push(&s,j,1);for(j=MAXSIZE;j>=MAXSIZE-2;j--)Push(&s,j,2);}printf("栈中元素依次为:");StackTraverse(s);printf("当前栈中元素有:%d \n",StackLength(s));Pop(&s,&e,2);printf("弹出的栈顶元素 e=%d\n",e);printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));for(j=6;j<=MAXSIZE-2;j++)Push(&s,j,1);printf("栈中元素依次为:");StackTraverse(s);printf("栈满否:%d(1:否 0:满)\n",Push(&s,100,1));ClearStack(&s);printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
栈的链式存储结构及实现
栈的链式存储结构,简称为链栈。
栈只是栈顶来做插入和删除操作,栈顶应该在链表的头部还是尾部呢?由于单链表有头指针,而栈顶指针也是必需的,所以好的办法是把栈顶放在单链表的头部。
因为已经有栈顶在头部了,所以单链表中的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。

对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。

#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int *//* 链栈结构 */
typedef struct StackNode
{SElemType data;struct StackNode *next;
}StackNode,*LinkStackPtr;typedef struct
{LinkStackPtr top;int count;
}LinkStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 构造一个空栈S */
Status InitStack(LinkStack *S)
{ S->top = (LinkStackPtr)malloc(sizeof(StackNode));if(!S->top)return ERROR;S->top=NULL;S->count=0;return OK;
}/* 把S置为空栈 */
Status ClearStack(LinkStack *S)
{ LinkStackPtr p,q;p=S->top;while(p){ q=p;p=p->next;free(q);} S->count=0;return OK;
}/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(LinkStack S)
{ if (S.count==0)return TRUE;elsereturn FALSE;
}/* 返回S的元素个数,即栈的长度 */
int StackLength(LinkStack S)
{ return S.count;
}/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(LinkStack S,SElemType *e)
{if (S.top==NULL)return ERROR;else*e=S.top->data;return OK;
}/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode)); s->data=e; s->next=S->top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */S->top=s; /* 将新的结点s赋值给栈顶指针,见图中② */S->count++;return OK;
}/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{ LinkStackPtr p;if(StackEmpty(*S))return ERROR;*e=S->top->data;p=S->top; /* 将栈顶结点赋值给p,见图中③ */S->top=S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */free(p); /* 释放结点p */ S->count--;return OK;
}Status StackTraverse(LinkStack S)
{LinkStackPtr p;p=S.top;while(p){visit(p->data);p=p->next;}printf("\n");return OK;
}int main()
{int j;LinkStack s;int e;if(InitStack(&s)==OK)for(j=1;j<=10;j++)Push(&s,j);printf("栈中元素依次为:");StackTraverse(s);Pop(&s,&e);printf("弹出的栈顶元素 e=%d\n",e);printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));GetTop(s,&e);printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));ClearStack(&s);printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
顺序栈与链栈对比
对比一下顺序栈与链栈,它们在时间复杂度上是一样的,均为O(1)。对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了—些内存开销,但对于栈的长度无限制。
所以它们的区别和线性表—样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈。反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。
而像线性表顺序存储结构用到的数组,因为要分散精力去考虑数组的下标增减等细节问题反而掩盖了问题的本质。
栈的应用——递归
斐波那契数列


#include "stdio.h"/* 斐波那契的递归函数 */
int Fbi(int i)
{if( i < 2 )return i == 0 ? 0 : 1; return Fbi(i - 1) + Fbi(i - 2); /* 这里Fbi就是函数自己,等于在调用自己 */
} int main()
{int i;int a[40]; printf("迭代显示斐波那契数列:\n");a[0]=0;a[1]=1;printf("%d ",a[0]); printf("%d ",a[1]); for(i = 2;i < 40;i++) { a[i] = a[i-1] + a[i-2]; printf("%d ",a[i]); } printf("\n");printf("递归显示斐波那契数列:\n");for(i = 0;i < 40;i++) printf("%d ", Fbi(i)); return 0;
}
迭代和递归的区别是:迭代使用的是循环结构,递归使用的是选择结构。递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存。迭代则不需要反复调用函数和占用额外的内存。因此我们应该视不同情况选择不同的代码实现方式。
递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。
这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符台栈这样的数据结构,因此,编译器使用栈实现递归就没什么好惊讶的了。
简单地说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
当然,对于现在的高级语言,这样的递归问题是不需要用户来管理这个栈的,一切都由系统代劳了。
栈的应用—四则运算表达式求值

"9 3 1 -3 *+10 2/+”,这样的表达式称为后缀表达式,叫后缀的原因在于所有的符号都是在要运算数字的后面出现。
相关文章:
数据结构【第4章】——栈与队列
队列是只允许在一端进行插入操作、而在另-端进行删除操作的线性表。 栈 栈与队列:栈是限定仅在表尾进行插入和删除操作的线性表。 我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)&…...
android webview 显示灰度网页
要在WebView中显示网页灰度显示,您可以通过以下步骤操作: 在您的布局文件中添加WebView组件: <WebViewandroid:id"id/webview"android:layout_width"match_parent"android:layout_height"match_parent" /…...
Linux操作系统的基础使用技能的训练大纲(超级详细版本适合于初学者)
RHCE红帽认证工程师课程对应考试课 程 纲 要 第一部分 网络基础 RH033RH302 Linux基础: 1) 在bashshell命令行模式下运行基本的Linux命令 2) 从命令行及GNOME界面启动应用程序 3) 使用及配置Xwindow系统及GNOME桌面环境 4) 使用GNOME GUI应用程序完成一般的工作 5) 了解Linu…...
【变形金刚02】注意机制以及BERT 和 GPT
一、说明 我已经解释了什么是注意力机制,以及与转换器相关的一些重要关键字和块,例如自我注意、查询、键和值以及多头注意力。在这一部分中,我将解释这些注意力块如何帮助创建转换器网络,注意、自我注意、多头注意、蒙面多头注意力…...
一个脚本 专治杂乱
背景 之前不是自己手动搞了一个COS嘛,直接复制粘贴图片,上传到后端的服务器,返回一个可访问的地址。我在哔哩哔哩上也分享过这样的一期视频。 今天偶尔上服务器一看,我靠,我的文件真的乱! 这还得了了&…...
springboot 基础
巩固基础,砥砺前行 。 只有不断重复,才能做到超越自己。 能坚持把简单的事情做到极致,也是不容易的。 SpringBoot JavaEE 简介 JavaEE的局限性: 1、过于复杂,JavaEE正对的是复杂的分布式企业应用,然而现实…...
web集群学习:基于nginx的反向代理和负载均衡
目录 一,反向代理 1,环境准备 2,配置代理服务器 3,在物理机上一管理员身份打开文本编辑器,编辑C:\Windows\System32\drivers\etc目录下的hosts文件 4,访问测试 5,查看日志,并记…...
编程小窍门: 一个简单的go mutex的小例子
本期小窍门用到了两个组件 mutex 这个类似其他语言的互斥锁waitGroup 这个类似其他语言的信号量或者java的栅栏锁 示例如下 func TestDoSomething04(t *testing.T) {total : 0var wg sync.WaitGroup{}var mut sync.Mutex{} for i : 0; i < 5000; i {go func() {wg.Ad…...
【工作记录】mysql中实现分组统计的三种方式
前言 实际工作中对范围分组统计的需求还是相对普遍的,本文记录下在mysql中通过函数和sql完成分组统计的实现过程。 数据及期望 比如我们获取到了豆瓣电影top250,现在想知道各个分数段的电影总数. 表数据如下: 期望结果: 实现方案 主要思路是根据s…...
马来西亚的区块链和NFT市场调研
马来西亚的区块链和NFT市场调研 基本介绍 参考: https://zh.wikipedia.org/wiki/%E9%A9%AC%E6%9D%A5%E8%A5%BF%E4%BA%9A zz制度:联邦议会制 语言文字: 马来语 民族: 69.4%原住民(土著),23.2%…...
[保研/考研机试] KY109 Zero-complexity Transposition 上海交通大学复试上机题 C++实现
描述: You are given a sequence of integer numbers. Zero-complexity transposition of the sequence is the reverse of this sequence. Your task is to write a program that prints zero-complexity transposition of the given sequence. 输入描述…...
Linux零基础快速入门到精通
一、操作系统概述 二、初始Linux Linux的诞生 Linux内核 Linux发行版 小结 三、虚拟机 认识虚拟机 虚拟化软件及安装 VMware Workstation 17 Pro安装教程https://blog.csdn.net/weixin_62332711/article/details/128695978 远程连接Linux系统 小结 扩展-虚拟机快照 …...
ARM02汇编指令
文章目录 一、keil软件介绍1.1 创建工程1.2 解析start.s文件(重点)1.3 乱码解决1.4 更换背景颜色1.5 C语言内存分布1.6 解析map.lds文件(重点)1.7 常见错误信息1.8 仿真 二、汇编三种符号2.1 汇编指令2.2 伪指令2.3 伪操作 三、汇编指令格式3.1 格式3.2 注意事项 四、数据操作指…...
从初学者到专家:Java方法的完整指南
目录 一.方法的概念及使用 1.1什么是方法 1.2方法的定义 1.3方法的调用 1.4实参和形参的关系 1.5没有返回值的方法 1.6方法的意义 二.方法重载 2.1方法重载的实现 2.2方法重载的意义 2.3方法签名 一.方法的概念及使用 1.1什么是方法 方法就是一个代码片段. 类似于 …...
【生成式AI】ProlificDreamer论文阅读
ProlificDreamer 论文阅读 Project指路:https://ml.cs.tsinghua.edu.cn/prolificdreamer/ 论文简介:截止2023/8/10,text-to-3D的baseline SOTA,提出了VSD优化方法 前置芝士:text-to-3D任务简介 text-to-3D Problem text-to-3D…...
C++元编程——模拟javascript异步执行
javascript有一个期约调用,就是利用内部的一种协程机制实现的类似并行的操作。以下是用ChatGPT搞出来的一块演示代码: // 异步任务 function asyncTask() {return new Promise((resolve, reject) > {setTimeout(() > {const randomNumber Math.f…...
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL
【JavaEE】MyBatis框架要点总结(3) 文章目录 【JavaEE】MyBatis框架要点总结(3)1. 多表查询1.1 映射表resultMap1.2 只有部分属性跨表查询1.2.1 依照常规去写代码1.2.2 用标签去实现接口 1.3 分多步的解决方案1.4 与多线程的结合 …...
Springboot 默认路径说明
Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的样板配置,极大的方便了开发者,其默认识别路径如下: Spring Boot 作为Spring默认将 /** 所有访问映射到以下目录: 1、classpath:/static 用于加…...
springboot注册拦截器与返回统一标准响应格式
响应对象ResultVO package com.example.poi.utils;import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.io.Serializable;/*** A…...
卷王特斯拉又全网降价了,卷死车企们
哈喽,大家好,今天媒介盒子小编又来跟大家分享软文推广的干货知识了,本篇分享的主要内容是:特斯无孔不入的营销手段。 1、特斯拉Model Y降价 车企要打架 自2023 年 8 月 14 日起,Model Y 长续航版起售价从 31.39 万元调整为 29.99 万元,Mode…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
