ESP32学习笔记_FreeRTOS(3)——SoftwareTimer
摘要(From AI):
这篇笔记全面介绍了 FreeRTOS 软件定时器的核心概念和使用方法,包括定时器的创建、管理、常用 API 和辅助函数,并通过示例代码演示了如何启动、重置和更改定时器的周期。它强调了软件定时器的灵活性、平台无关性以及与硬件定时器的对比
前言:本文档是本人在依照B站UP:Michael_ee的视频教程进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,望指正。
上一篇:ESP32学习笔记_FreeRTOS(2)——Queue
文章目录
- Software Timer
- Creating a Software Timer
- xTimerCreate()
- Managing Software Timers
- xTimerStart()
- xTimerStop()
- pcTimerGetName()
- pvTimerGetTimerID()
- xTimerReset()
- xTimerChangePeriod()
- Example Code:Using Software Timers
参考资料:
[Michael_ee视频教程] https://www.bilibili.com/video/BV1Nb4y1q7xz?spm_id_from=333.788.videopod.sections&vd_source=4d8bd0ed3878ef81b239bf69bf38e741
freeRTOS官网
espressif 在线文档
Software Timer
硬件定时器会有数量等方面的限制,使用较不灵活,而软件定时器使用更为灵活,其与硬件、平台无关,在不同的 MCU 都可以使用 FreeRTOS API 进行调用
| 特性 | 硬件定时器 | 软件定时器 |
|---|---|---|
| 数量 | 固定,受 MCU 硬件资源限制(通常只有几个) | 灵活,可以根据需要动态创建(受内存和任务管理能力限制) |
| 依赖性 | 依赖具体硬件平台,配置方式和功能因芯片而异 | 与硬件平台无关,可通过 FreeRTOS API 在不同 MCU 上使用 |
| 精度 | 高精度,直接由硬件计时,通常用于实时性要求高的场景 | 精度依赖于 RTOS 的调度周期(tick 周期),不适合极高实时性场景 |
| 性能 | 高性能,独立运行,不占用 CPU 资源 | 运行在 RTOS 守护任务上下文中,占用 CPU 资源 |
| 适用场景 | 适合时间敏感的应用,如 PWM 信号生成、脉冲捕获、输入输出事件计时等 | 适合通用定时功能,如定时任务执行、软件超时处理等 |
| 灵活性 | 配置固定,功能和用途受限 | 灵活性高,可动态调整超时时间、回调函数等 |
| 使用复杂度 | 配置复杂,需根据芯片手册手动设置寄存器 | 使用方便,通过 FreeRTOS API 调用 |
| 移植性 | 差,代码与硬件平台强耦合 | 好,代码与硬件无关,便于跨平台移植 |
所有软件定时器的回调函数都在同一个RTOS守护任务(也称为“定时器服务任务”)的上下文中执行(该任务最初被称为“定时器服务任务”,因为最初它只用于执行软件定时器回调函数。现在同一任务也用于其他用途,因此被改名为更通用的“RTOS 守护任务”)
守护任务是一个标准的FreeRTOS任务,会在调度器启动时自动创建。其优先级和堆栈大小由编译时配置常量configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH分别设置,这两个常量在FreeRTOSConfig.h中定义
需要注意,软件定时器的回调函数是在 RTOS 守护任务的上下文中执行的,而不是在独立的任务中运行。因此,回调函数中不能调用可能使任务进入阻塞状态的 FreeRTOS API 函数,因为这会阻塞整个守护任务,导致系统运行异常
Creating a Software Timer
xTimerCreate()
xTimerCreate() 用于创建一个新的软件定时器,并返回一个句柄以引用创建的定时器
#include “FreeRTOS.h”
#include “timers.h”TimerHandle_t xTimerCreate( const char *pcTimerName,const TickType_t xTimerPeriod,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction );
参数
pcTimerName定时器的名称,仅用于调试xTimerPeriod定时器周期,单位为系统 tick,不能为 0。可以使用pdMS_TO_TICKS()` 宏将毫秒转换为 tick。例如:- 100 tick直接设置为 100
- 500ms可使用
pdMS_TO_TICKS(500),前提是configTICK_RATE_HZ <= 1000
uxAutoReload设置定时器类型:pdTRUE自动重载定时器(周期性触发)pdFALSE一次性定时器(仅触发一次,可手动重新启动)
pvTimerID定时器标识符,用于在回调函数中区分不同的定时器,或在回调调用之间存储值pxCallbackFunction:定时器到期时执行的回调函数,需符合TimerCallbackFunction_t原型:
void vCallbackFunctionExample(TimerHandle_t xTimer);
configTICK_RATE_HZ是 FreeRTOS 配置文件FreeRTOSConfig.h中定义的一个宏,它表示 每秒系统 Tick 的次数,即 FreeRTOS 的调度器每秒中断的频率(单位为 Hz)
例如:
如果configTICK_RATE_HZ = 1000,表示系统每 1 毫秒触发一次 Tick 中断
如果configTICK_RATE_HZ = 100,表示系统每 10 毫秒触发一次 Tick 中断
返回值:
NULL定时器创建失败,原因可能是 FreeRTOS 堆内存不足其他值定时器创建成功,返回的句柄可用于引用该定时器
配置要求(一般不用动)
- 在
FreeRTOSConfig.h文件中,configUSE_TIMERS和configSUPPORT_DYNAMIC_ALLOCATION必须都设置为1 - 如果
configSUPPORT_DYNAMIC_ALLOCATION未定义,其默认值为1
创建定时器并不会立即启动。可以使用以下函数来启动或管理定时器
// 启动定时器。如果定时器已经在运行,则从当前时间重新开始。
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );// 重置(重新启动)定时器。确保定时器启动或重新计算到期时间。
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );// 从中断上下文启动定时器。等效于 xTimerStart(),用于中断中调用。
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );// 从中断上下文重置(重新启动)定时器。等效于 xTimerReset(),用于中断中调用。
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );// 更改定时器的周期。如果定时器未运行,则会启动定时器。
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait );// 从中断上下文更改定时器的周期。等效于 xTimerChangePeriod(),用于中断中调用。
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,TickType_t xNewPeriod,BaseType_t *pxHigherPriorityTaskWoken );
Managing Software Timers
xTimerStart()
xTimerStart() 用于启动一个软件定时器的运行
- 如果定时器尚未运行,
xTimerStart()会计算一个到期时间,该时间相对于调用xTimerStart()的时刻 - 如果定时器已经在运行,则
xTimerStart()相当于调用了xTimerReset(),即重置定时器 - 定时器会在定义的周期后(即
xTimerStart()调用后n个 tick)触发回调函数,除非定时器在此期间被停止、删除或重置
#include “FreeRTOS.h”
#include “timers.h”BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数
xTimer:要启动、重置或重新启动的定时器句柄xTicksToWait指定调用任务在timer command queue队列已满的情况下,等待空间可用的最大时间(单位为 tick)。这是任务在进入 Blocked 状态时的阻塞时间。如果队列已满,任务会被阻塞,直到有足够的空间来发送命令- 设置
xTicksToWait为portMAX_DELAY将导致任务无限期等待,直到队列中有空间 - 如果在调度器启动之前调用
xTimerStart(),则xTicksToWait会被忽略
- 设置
当任务调用
xTimerStart()或其他定时器相关 API 时,这些命令并不会立即由任务执行,而是通过一个队列传递给定时器服务任务
如果队列已满,新的命令会被阻塞,直到队列有空间可用。这时,调用xTimerStart()等 API 的任务会根据指定的阻塞时间(xTicksToWait)进入阻塞状态,等待队列空间变得可用
timer command queue的大小由 FreeRTOS 的配置项决定。队列的大小设置影响系统可以同时处理多少个定时器命令。如果队列大小太小,可能会导致命令丢失或任务阻塞:
configTIMER_QUEUE_LENGTH:定义了timer command queue队列的最大长度(即可以存放多少个定时器命令)
configUSE_TIMERS:必须设置为 1,才能启用定时器功能和相关队列
返回值
pdPASS启动命令成功发送到定时器命令队列。如果指定了阻塞时间(即xTicksToWait不为零),则可能会因为队列已满,任务进入阻塞状态等待空间释放,直到数据成功写入队列- 定时器命令的处理时间会根据定时器服务任务的优先级而有所不同,但定时器的到期时间是相对于实际调用
xTimerStart()时刻(从队列中取出命令并实际启动定时器)的 - 定时器命令的处理时间受定时器服务任务优先级的影响,定时器服务任务的优先级由
configTIMER_TASK_PRIORITY配置常量设置
- 定时器命令的处理时间会根据定时器服务任务的优先级而有所不同,但定时器的到期时间是相对于实际调用
pdFAIL启动命令未成功发送到定时器命令队列,原因是队列已满。如果指定了阻塞时间,任务会被阻塞等待队列有空间,直到指定的阻塞时间过期,但仍未成功写入数据到队列
注意事项(一般不用动)
在 FreeRTOSConfig.h 中,configUSE_TIMERS 必须设置为 1,才能使用 xTimerStart() 函数
xTimerStop()
xTimerStop() 用于停止一个运行中的软件定时器
- 调用
xTimerStop()可以停止一个正在运行的定时器。如果定时器已经停止或已过期,则调用xTimerStop()不会产生影响。 xTimerStop()向定时器命令队列发送停止命令,定时器服务任务会在稍后处理该命令。
#include “FreeRTOS.h”
#include “timers.h”BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数
xTimer要停止的定时器句柄。xTicksToWait指定任务在定时器命令队列已满的情况下,最大等待时间(单位为 ticks)
返回值
pdPASSpdFAIL
pcTimerGetName()
pcTimerGetName() 用于返回在创建定时器时分配的可读文本名称
#include “FreeRTOS.h”
#include “timers.h”const char * pcTimerGetName( TimerHandle_t xTimer );
返回值
- 返回值为一个指向定时器名称的指针。
- 定时器名称是一个标准的以
NULL结尾的 C 字符串。
pvTimerGetTimerID()
pvTimerGetTimerID() 用于返回与定时器关联的标识符(ID)
- 返回在创建定时器时分配的标识符,该标识符可以通过
vTimerSetTimerID()API 更新 - 在回调函数中可以使用该标识符区分哪个定时器到期,特别是在多个定时器共享相同的回调函数时
#include “FreeRTOS.h”
#include “timers.h”void *pvTimerGetTimerID( TimerHandle_t xTimer );
返回值
- 返回与指定定时器关联的标识符(指针类型)
xTimerReset()
xTimerReset() 用于重置、启动或重新启动一个软件定时器,能够起到 Watch Dog 的作用
- 如果定时器正在运行,
xTimerReset()会将定时器的到期时间重新计算为相对于调用时间的周期 - 如果定时器未运行,
xTimerReset()会启动定时器,并将到期时间计算为相对于调用时间的周期。此时等效于xTimerStart() - 无论定时器当前是否运行,调用
xTimerReset()后,定时器都将开始运行
#include “FreeRTOS.h”
#include “timers.h”BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
返回值
pdPASSpdFAIL
xTimerChangePeriod()
xTimerChangePeriod() 用于更改软件定时器的周期
- 更改运行中定时器的周期
- 如果定时器正在运行,则新周期将用于重新计算到期时间。
- 新的到期时间相对于调用
xTimerChangePeriod()的时刻,而不是定时器最初启动的时刻。
- 启动未运行的定时器
- 如果定时器未运行,则定时器将使用新的周期值计算到期时间,并开始运行。
#include “FreeRTOS.h”
#include “timers.h”BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait );
参数
xTimer需要更改周期的定时器句柄xNewPeriod定时器的新周期(单位为 tick)。可使用pdMS_TO_TICKS()将毫秒转换为 tickxTicksToWait阻塞任务的最大时间(单位为 tick),如果定时器命令队列已满,则等待空间可用
返回值
pdPASSpdFAIL
Example Code:Using Software Timers
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h" #include "freertos/timers.h" // 定时器头文件 void TimerCallback(TimerHandle_t xTimer)
{ const char *pcTimerName = pcTimerGetName(xTimer);// 获取定时器名称 uint32_t *uiTimerID = (uint32_t *)pvTimerGetTimerID(xTimer);// 获取定时器ID printf("%s expired, ID: %lu.\n", pcTimerName, *uiTimerID);// 打印定时器名称和ID
} int id1 = 0;
int id2 = 1; void app_main(void)
{ TimerHandle_t TimerHandle1 = NULL; TimerHandle1 = xTimerCreate("Timer1", pdMS_TO_TICKS(1000), pdTRUE, (void *)&id1, TimerCallback);// 创建一个周期为1000ms的定时器 xTimerStart(TimerHandle1, 0);// 启动定时器 TimerHandle_t TimerHandle2 = NULL; TimerHandle2 = xTimerCreate("Timer2", pdMS_TO_TICKS(2000), pdTRUE, (void *)&id2, TimerCallback);// 与Timer1公用同一个回调函数,观察pcTimerGetName的输出 xTimerStart(TimerHandle2, 0);// 启动定时器 // for(int i = 0; i < 10; i++) // { // vTaskDelay(pdMS_TO_TICKS(1000)); // xTimerReset(TimerHandle2, 0);// 重置定时器,观察pcTimerGetName的输出,此时Timer2不会被打印 // } vTaskDelay(pdMS_TO_TICKS(5000)); xTimerChangePeriod(TimerHandle2, pdMS_TO_TICKS(1000), 0);// 修改Timer2的周期为1000ms vTaskDelay(pdMS_TO_TICKS(5000)); xTimerStop(TimerHandle1, 0);// 停止定时器 xTimerStop(TimerHandle2, 0);// 停止定时器
}
相关文章:
ESP32学习笔记_FreeRTOS(3)——SoftwareTimer
摘要(From AI): 这篇笔记全面介绍了 FreeRTOS 软件定时器的核心概念和使用方法,包括定时器的创建、管理、常用 API 和辅助函数,并通过示例代码演示了如何启动、重置和更改定时器的周期。它强调了软件定时器的灵活性、平台无关性以及与硬件定时器的对比 …...
文心一言与千帆大模型平台的区别:探索百度AI生态的双子星
随着人工智能技术的迅猛发展,越来越多的公司开始投入资源开发自己的AI解决方案。在中国,百度作为互联网巨头之一,不仅在搜索引擎领域占据重要位置,还在AI领域取得了显著成就。其中,“文心一言”和“千帆大模型平台”便…...
【c语言】文件操作详解 - 从打开到关闭
文章目录 1. 为什么使用文件?2. 什么是文件?3. 如何标识文件?4. 二进制文件和文本文件?5. 文件的打开和关闭5.1 流和标准流5.1.1 流5.1.2 标准流 5.2 文件指针5.3 文件的打开和关闭 6. 文件的读写顺序6.1 顺序读写函数6.2 对比一组…...
Flink Sink的使用
经过一系列Transformation转换操作后,最后一定要调用Sink操作,才会形成一个完整的DataFlow拓扑。只有调用了Sink操作,才会产生最终的计算结果,这些数据可以写入到的文件、输出到指定的网络端口、消息中间件、外部的文件系统或者是…...
pcl::PointCloud<PointType>::Ptr extractedCloud; 尖括号里的值表示什么含义?
在C中,pcl::PointCloud<PointType>::Ptr是一种智能指针,它是Point Cloud Library (PCL)中用于管理pcl::PointCloud对象的智能指针类型。这里的<pcl::PointCloud<PointType>::Ptr>尖括号里的值表示智能指针所指向的对象类型。 让我们分…...
《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试
一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器,旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…...
VsCode 插件推荐(个人常用)
VsCode 插件推荐(个人常用)...
路由策略与路由控制实验
AR1、AR2、AR3在互联接口、Loopback0接口上激活OSPF。AR3、AR4属于IS-IS Area 49.0001,这两者都是Level-1路由器,AR3、AR4的系统ID采用0000.0000.000x格式,其中x为设备编号 AR1上存在三个业务网段A、B、C(分别用Loopback1、2、3接…...
训练的decoder模型文本长度不一致,一般设置为多大合适,需要覆盖最长的文本长度么
在训练解码器模型时,文本长度不一致是常见的情况,需要根据任务的特性和数据集的长度分布来设置合理的最大长度 (max_length)。以下是一些指导原则,帮助你设置合适的最大长度: 1. 是否需要覆盖最长文本长度 覆盖最长文本长度: 如果任务对完整性要求很高(例如生成数学公式、…...
过滤条件包含 OR 谓词,如何进行查询优化——OceanBase SQL 优化实践
这篇博客涉及两个点,一个是 “OR Expansion 改写”,另一个是 “基于代价的改写”。 背景 在写SQL查询时,难以避免在过滤条件中使用 OR 谓词,但其往往会导致索引利用效率下降的问题 。本文将分享如何通过查询改写的2种方式进行优化…...
通过异步使用消息队列优化秒杀
通过异步使用消息队列优化秒杀 同步秒杀流程异步优化秒杀异步秒杀流程基于lua脚本保证Redis操作原子性代码实现阻塞队列的缺点 同步秒杀流程 public Result seckillVoucher(Long voucherId) throws InterruptedException {SeckillVoucher seckillVoucher iSeckillVoucherServi…...
AI产业告别“独奏”时代,“天翼云息壤杯”高校AI大赛奏响产学研“交响乐”
文 | 智能相对论 作者 | 陈泊丞 人工智能产业正在从“独奏”时代进入“大合奏”时代。 在早期的AI发展阶段,AI应用主要集中在少数几个领域,如语音识别、图像处理等。这些领域的研究和开发工作往往由少数几家公司或研究机构即可独立完成,犹…...
Hot100 - 字母异位词分组
Hot100 - 字母异位词分组 最佳思路:排序 时间复杂度: O(nmlogm),其中 n 为 strs 数组的长度,m 为每个字符串的长度。 代码: class Solution {public List<List<String>> groupAnagrams(String[] strs) …...
力扣hot100-->排序
排序 1. 56. 合并区间 中等 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例 1: 输…...
【VRChat 全身动捕】VIVE 手柄改 tracker 定位器教程,低成本光学动捕解决方案(持续更新中2024.11.26)
更新 0.0.1(2024/11/26): 1.解决了内建蓝牙无法识别、“steamVR 蓝牙不可用” 的解决方案 2.解决了 tracker 虽然建立了连接但是在 steamVR 界面上看不到的问题 3.解决了 VIVE 基站1.0 无法被蓝牙识别 && 无法被 steamVR 搜索到 &…...
【Nginx】核心概念与安装配置解释
文章目录 1. 概述2. 核心概念2.1.Http服务器2.2.反向代理2.3. 负载均衡 3. 安装与配置3.1.安装3.2.配置文件解释3.2.1.全局配置块3.2.2.HTTP 配置块3.2.3.Server 块3.2.4.Location 块3.2.5.upstream3.2.6. mine.type文件 3.3.多虚拟主机配置 4. 总结 1. 概述 Nginx是我们常用的…...
Qt界面篇:QMessageBox高级用法
1、演示效果 2、用法注意 2.1 设置图标 用于显示实际图标的pixmap取决于当前的GUI样式。也可以通过设置icon pixmap属性为图标设置自定义pixmap。 QMessageBox::Icon icon(...
【二叉树】【2.1遍历二叉树】【刷题笔记】【灵神题单】
关注二叉树的三个问题: 什么情况适合自顶向下?什么时候适合用自底向上?一般来说,DFS的递归边界是空节点,什么情况下要额外把叶子节点作为递归边界?在什么情况下,DFS需要有返回值?什…...
Mongo数据库 --- Mongo Pipeline
Mongo数据库 --- Mongo Pipeline 什么是Mongo PipelineMongo Pipeline常用的几个StageExplanation with example:MongoDB $matchMongoDB $projectMongoDB $groupMongoDB $unwindMongoDB $countMongoDB $addFields Some Query Examples在C#中使用Aggreagtion Pipeline**方法一: …...
Adobe Illustrator 2024 安装教程与下载分享
介绍一下 下载直接看文章末尾 Adobe Illustrator 是一款由Adobe Systems开发的矢量图形编辑软件。它广泛应用于创建和编辑矢量图形、插图、徽标、图标、排版和广告等领域。以下是Adobe Illustrator的一些主要特点和功能: 矢量绘图:Illustrator使用矢量…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
