基于按键开源MultiButton框架深入理解代码框架(二)(指针的深入理解与应用)

文章目录

  • 2、针对该开源框架理解
  • 3、分析代码
    • 3.1 再谈指针、数组、数组指针
    • 3.2 继续分析源码


2、针对该开源框架理解

在编写按键模块的框架中,一定要先梳理按键相关的结构体、枚举等变量。这些数据是判断按键按下、状态跳转、以及绑定按键事件的核心。
这一部分定义是在驱动层文件 "multi_button.h",这个里面的数据类型虽然都是跟按键有关的,并且主要是驱动层在使用,但是这个地方需要走出一个误区:

关于按键的相关的结构体、枚举等变量。这一部分定义是在驱动层文件 "multi_button.h",这个里面的数据类型虽然都是跟按键有关的,并且主要是驱动层在使用,但是应用层也需要知道按键的样子是什么样子,这样的目的是为了保证数据流的有效流动,是必要的。如果不这样,每一个模块的数据都是孤立存在的,产生不了联系,这不算是使用全局变量。

multi_button.h 中定义的按键结构体(如 Button)和事件枚举(如 PRESS_DOWNLONG_PRESS 等)属于驱动层对应用层的接口规范

  • 应用层需要知道数据结构格式​:因应用层需创建按键实例(如 Button btn1;)并传递给 button_init(),必须了解结构体成员(如 pinevent)以正确初始化和处理事件回调。

  • 驱动层隐藏实现细节​:虽然结构体定义在头文件中,但驱动层内部的状态机逻辑、链表管理等实现仍封装在 .c 文件中,对应用层不可见。
    应用层通过 read_button_gpio() 回调函数向驱动层提供硬件状态,驱动层通过事件枚举(如 CLICK)向应用层传递抽象结果。结构体/枚举作为数据传递的载体,是层间通信的契约,确保数据格式一致。

  • 按键结构体/枚举在 multi_button.h 中的定义是必要的接口契约,用于保证驱动层与应用层间数据流的有效流动,不属于全局变量滥用。

  • 这种设计在满足数据交互需求的同时,通过封装驱动实现细节(如链表管理、状态机),依然符合分层架构的高内聚、低耦合原则。

  • 若需进一步隔离,可采用“句柄化”或标准化事件接口,但需权衡实现复杂度与资源消耗。

我觉得一定要带着这种思想才能深入的去理解该按键框架的意义,体会作者的用意,知其所以然。

3、分析代码

// 按钮事件类型
typedef enum {BTN_PRESS_DOWN = 0,     // 按钮按下(物理按下动作)BTN_PRESS_UP,           // 按钮释放(物理释放动作)BTN_PRESS_REPEAT,       // 检测到重复按下(连按事件)BTN_SINGLE_CLICK,       // 单击完成(短按后释放)BTN_DOUBLE_CLICK,       // 双击完成(两次快速单击)BTN_LONG_PRESS_START,   // 长按开始(达到长按阈值)BTN_LONG_PRESS_HOLD,    // 长按保持(持续按住状态)BTN_EVENT_COUNT,        // 事件总数(用于数组定义)BTN_NONE_PRESS          // 无事件(空闲状态)
} ButtonEvent;// 按钮状态机状态
typedef enum {BTN_STATE_IDLE = 0,     // 空闲状态(等待按下)BTN_STATE_PRESS,        // 按下状态(检测到有效按下)BTN_STATE_RELEASE,      // 释放状态(等待超时以区分单击/双击)BTN_STATE_REPEAT,       // 重复按下状态(连按计数中)BTN_STATE_LONG_HOLD     // 长按保持状态(持续检测长按)
} ButtonState;// 按钮控制结构体
struct _Button {uint16_t ticks;                     // 时间戳计数器(用于超时判断)uint8_t  repeat : 4;                // 重复按下计数(4位,范围0-15)uint8_t  event : 4;                 // 当前事件类型(4位,对应ButtonEvent)uint8_t  state : 3;                 // 状态机状态(3位,对应ButtonState)uint8_t  debounce_cnt : 3;          // 消抖计数(3位,范围0-7)uint8_t  active_level : 1;          // 有效触发电平(1位,0=低电平有效,1=高电平有效)uint8_t  button_level : 1;          // 当前按钮电平(1位,实时GPIO状态)uint8_t  button_id;                 // 按钮标识符(用于多按钮区分)uint8_t  (*hal_button_level)(uint8_t button_id);  // HAL层按钮电平读取函数指针BtnCallback cb[BTN_EVENT_COUNT];    // 事件回调函数数组(按事件类型注册)Button* next;                       // 链表指针(支持多按钮管理)
};

主要需要区分的是按键状态机和按键事件类型。

按键事件的目的是为了和该时间相应的动作绑定,也就是在什么状态下,执行什么应用逻辑。通过函数 button_attach 进行绑定,

这是调用文件:
button_attach(&btn1, BTN_PRESS_REPEAT, btn1_press_repeat_handler);具体实现是在multi_button.c文件
void button_attach(Button* handle, ButtonEvent event, BtnCallback cb)
{if (!handle || event >= BTN_EVENT_COUNT) return;  // parameter validationhandle->cb[event] = cb;
}

其中 handle->cb[event] = cb; 这个目的就是接收传递的相关事件处理函数。
并且要想保证传递的按键处理函数有消息,我们其实是需要定义一个指针函数的,只有这样传递进来的按键处理函数入口地址才能被编译器有效识别为函数入口地址,不然编译器只是知道这是一个地址。这也就是在之前工作中 胡哥 告诉我的。在函数传递的时候无所谓什么,只要是一个地址就行 ,但是在函数定义的时候,定义输入参数的时候,我们必须要对这个参数是什么类型进行说明,例如是地址,我们就要声明这是什么地址,是函数指针地址、一个指针变量还是单纯的一个数组的入口地址。(可以理解为强转,也可以理解为告诉编译器或者告诉这个函数我这个地址本质是什么地址)。根本原因就是上述分析。

所以我们能看到在文件 multi_button.h 这行代码声明一个函数指针,就用来定义按键事件处理函数的入口地址。

typedef void (*BtnCallback)(Button* btn_handle);

3.1 再谈指针、数组、数组指针

此外由于这里使用到了==指针、数组、数组指针==,所以有必要进行说明一下其中的区别与联系:

在 C 语言中,数组的本质是连续内存块,其内容完全由定义时的类型决定。

在C语言中,数组确实可以存储任何类型,但必须明确定义元素类型,这样才能保证正确访问内存和调用函数。

数组和指针的本质理解:

数组是什么,数组的本质是连续的内存块,注意是连续,也就是说可以通过下标进行访问,并且申请的内存空间是和存储的数据类型强相关的。本质也是一个个的连续地址。

指针是什么,指针就是地址,如果我们需要存储一个函数,那么就需要将这个函数存储到一块内存空间,相当于是申请了一块内存,那么编译器在访问的时候其实是先找到这个函数的入口地址从而进行访问。而我们的指针变量就是存储这个函数入口地址的内存空间,纵使这个函数的内存很大,需要很多个地址存储,但是体现在指针变量里面就只是保存了一个入口地址,至于剩下的怎么执行完整这个函数,就不需要我们操心了。这个地方就是区别于数组,数组存储一些内容需要的申请完整的内存空间,来存储我们需要存储数组里面的所有内容,也就是都要找到对应的地址空间。

数组是申请内存空间存储内容,数组元素存储的是实际数据值,除非该数组是指针数组(如 int* arr[5])。

int arr[3] = {10, 20, 30};
那这个10是怎么存储的呐?  10表示的是0x0A 00 00 00
首先是申请12个字节内存空间,毕竟一个int数据占用的是四个字节,一个地接就需要一个地址存储。
- 字节 0(低地址):`00001010` → `0x0A`
- 字节 1:`00000000` → `0x00`
- 字节 2:`00000000` → `0x00`
- 字节 3(高地址):`00000000` → `0x00`
这里其实我们经常说的一个字节,指的就是内存空间中的一个地址,对的就是一个实实在在的物理地址。

所以12个字节,其实就是需要12个实实在在的物理地址空间做支撑。

如果是该数组是指针数组,其实数组存储的原本的10=0x0A 00 00 00此时存储的是一个地址,毕竟地址也是一个16进制数,所以也只是将这个地址拆分,然后存储到数组申请的实实在在的物理空间。例如

int a = 10, b = 20;
int* ptr_arr[2] = {&a, &b}; 
// ptr_arr[0]存储a的地址(如0x2000),ptr_arr[1]存储b的地址(如0x2004)
`&a`=`0x1000`32 位系统:地址值占 ​**4 字节**​(如 `0x00001000`)  也是四个字节。
虽然我们存储的是地址,但是有点类似于int数据类型的存储。

至此我们明白了数组存内容是怎么储存的,

接下来再看指针:

指针也是申请内存空间存储内容:存储的是地址,不会存储数据。

定义一个指针变量,其实本质也是在物料地址空间,申请位置,但是需要告诉编译器我们是什么地址,毕竟内容不一样需要申请的空间不一样,但是需要注意的是这个指针变量如果是存储函数或者是数组,仅仅只存储它们的入口地址或者是首地址。

如果我们把这个这个函数存在数组中,其实也只是存储的这个函数的入口地址,但是我们需要将这个数组类型进行声明,因为只有这样编译器才知道我们使用的这个数组存储的是一个指针数组。

从某种程度来说指针和数组没有区别,但是又存在一些细微的区别。
数组更像是一种数据集合,固定大小,类型一致。
地址更像是一个地址容器,动态指向。
并且他们访问形式是不一样的。

3.2 继续分析源码

继续言归正传分析按键开源框架:

接下来就是分析按键的状态机跳转,也就是如何判断按键状态的核心逻辑。

/*** @brief  Button driver core function, driver state machine* @param  handle: the button handle struct* @retval None*/static void button_handler(Button* handle)
{uint8_t read_gpio_level = button_read_level(handle);// Increment ticks counter when not in idle stateif (handle->state > BTN_STATE_IDLE) {handle->ticks++;}/*------------Button debounce handling---------------*/if (read_gpio_level != handle->button_level) {// Continue reading same new level for debounceif (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {handle->button_level = read_gpio_level;handle->debounce_cnt = 0;}} else {// Level not changed, reset counterhandle->debounce_cnt = 0;}/*-----------------State machine-------------------*/switch (handle->state) {case BTN_STATE_IDLE:if (handle->button_level == handle->active_level) {// Button press detectedhandle->event = (uint8_t)BTN_PRESS_DOWN;EVENT_CB(BTN_PRESS_DOWN);handle->ticks = 0;handle->repeat = 1;handle->state = BTN_STATE_PRESS;} else {handle->event = (uint8_t)BTN_NONE_PRESS;}break;case BTN_STATE_PRESS:if (handle->button_level != handle->active_level) {// Button releasedhandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);handle->ticks = 0;handle->state = BTN_STATE_RELEASE;} else if (handle->ticks > LONG_TICKS) {// Long press detectedhandle->event = (uint8_t)BTN_LONG_PRESS_START;EVENT_CB(BTN_LONG_PRESS_START);handle->state = BTN_STATE_LONG_HOLD;}break;case BTN_STATE_RELEASE:if (handle->button_level == handle->active_level) {// Button pressed againhandle->event = (uint8_t)BTN_PRESS_DOWN;EVENT_CB(BTN_PRESS_DOWN);if (handle->repeat < PRESS_REPEAT_MAX_NUM) {handle->repeat++;}EVENT_CB(BTN_PRESS_REPEAT);handle->ticks = 0;handle->state = BTN_STATE_REPEAT;} else if (handle->ticks > SHORT_TICKS) {// Timeout reached, determine click typeif (handle->repeat == 1) {handle->event = (uint8_t)BTN_SINGLE_CLICK;EVENT_CB(BTN_SINGLE_CLICK);} else if (handle->repeat == 2) {handle->event = (uint8_t)BTN_DOUBLE_CLICK;EVENT_CB(BTN_DOUBLE_CLICK);}handle->state = BTN_STATE_IDLE;}break;case BTN_STATE_REPEAT:if (handle->button_level != handle->active_level) {// Button releasedhandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);if (handle->ticks < SHORT_TICKS) {handle->ticks = 0;handle->state = BTN_STATE_RELEASE;  // Continue waiting for more presses} else {handle->state = BTN_STATE_IDLE;  // End of sequence}} else if (handle->ticks > SHORT_TICKS) {// Held down too long, treat as normal presshandle->state = BTN_STATE_PRESS;}break;case BTN_STATE_LONG_HOLD:if (handle->button_level == handle->active_level) {// Continue holdinghandle->event = (uint8_t)BTN_LONG_PRESS_HOLD;EVENT_CB(BTN_LONG_PRESS_HOLD);} else {// Released from long presshandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);handle->state = BTN_STATE_IDLE;}break;default:// Invalid state, reset to idlehandle->state = BTN_STATE_IDLE;break;}
}

逐段代码进行分析:

static void button_handler(Button* handle)
{}
    uint8_t read_gpio_level = button_read_level(handle);

我们首先要依据GPIO口的高低电平来进行按键的判断按键是否按下,因此首先就要将GPIO读取函数给获取到,因为在初始化按键结构体的时候我们已经装填了获取GPIO电平的函数,这个按键的结构体包含了按键判断所有的条件,包括但不限于按键事件、按键状态、按键电平、按键什么电平有效等等,前面已经解释过了,这里就不一一赘述了,只是简单提醒,要带着这种思想。并且这些信息都存在了函数的输入参数里面 handle 并且每一个按键的这些时间都是独立计算的。按键与按键之间分开计算。

    // Increment ticks counter when not in idle stateif (handle->state > BTN_STATE_IDLE) {handle->ticks++;}

这个时间的递增是怎么实现的?
通俗来讲,如果是 5ms 一次调用这个函数,那就可以理解为 5ms 变量 ticks 就会增加1,以此类推,从而实现了时间累计,并将这个是时间应用到下面代码的逻辑跳转。
这里在定时器还需要解决一个问题:定时精度依赖​:若 button_ticks() 调用间隔不稳定(如被高优先级中断阻塞),会导致时间计算误差。也就是在滴答定时器章节留下的问题。
ARM单片机滴答定时器理解与应用(一)(详细解析)-CSDN博客

ARM单片机滴答定时器理解与应用(二)(详细解析)(完)-CSDN博客

接着是按键软件消抖动:

    /*------------Button debounce handling---------------*/if (read_gpio_level != handle->button_level) {// Continue reading same new level for debounceif (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {handle->button_level = read_gpio_level;handle->debounce_cnt = 0;}} else {// Level not changed, reset counterhandle->debounce_cnt = 0;}

这要结合按键结构体的初始化,在初始化的时候我们将 handle->button_level 初始化为有效电平的相反数,

    handle->button_level = !active_level;  // initialize to opposite of active level

假设active_level​:表示按键按下时的有效逻辑电平(例如 0 表示低电平触发)。
并且假设系统启动时按键处于释放状态,也就是按键没有按下,此时物理电平应与有效电平相反(例如:若 active_level=0,则释放时应为高电平 1)。

避免首次扫描的误触发

  • 若初始化时 button_levelactive_level 相同(例如均为 0),则首次调用 button_handler 时:
read_gpio_level == handle->button_level  // 均为0 → 电平“未变化”

即使按键实际处于释放状态(物理电平应为 1),框架也会误判为“按键已按下”,导致状态机错误触发 PRESS_DOWN 事件。

确保消抖机制正确启动

  • 当按键首次被按下时,物理电平从释放状态(1)变为按下状态(0),此时:
read_gpio_level (0) != handle->button_level (1)  // 电平变化 → 启动消抖计数

若未初始化 button_level = !active_level,首次按下可能因电平“未变化”而跳过消抖计数,直接误判为稳定状态。

经过上述的初始化分析:保证了我们的按键函数可以准确的进入到消抖动分析,

整体逻辑如下:
A[检测电平变化] --> B{连续N次相同?}
B – 是 --> C[更新button_level]
B – 否 --> D[重置计数器]

体现在代码就是:

        if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {handle->button_level = read_gpio_level;handle->debounce_cnt = 0;}

首先进行次数判断,为何必须用前置 ++ 而非后置?

  • 逻辑一致性​:消抖需在连续 DEBOUNCE_TICKS 次变化后立即响应,前置 ++ 确保本次扫描被计入后立刻判断。
  • 避免漏判​:后置 ++ 会延迟计数更新,导致本次扫描的检测结果未被纳入当前判断。

假设连续三次稳定了,那么我们就认为电平稳定了,覆盖掉初始化的结果,将当前检测的结果赋值给 handle->button_level = read_gpio_level; 这样在下一次扫描就不会再执行这个函数,然后将消抖的计数器给清空。

接着就是状态机的跳转:

    case BTN_STATE_IDLE:if (handle->button_level == handle->active_level) {// Button press detectedhandle->event = (uint8_t)BTN_PRESS_DOWN;EVENT_CB(BTN_PRESS_DOWN);handle->ticks = 0;handle->repeat = 1;handle->state = BTN_STATE_PRESS;} else {handle->event = (uint8_t)BTN_NONE_PRESS;}break;

因为前面已经将电平更新为实际检测到的 handle->button_level = read_gpio_level; 之所以这样是因为消抖完成,所以在状态满足 if 语句 handle->button_level == handle->active_level 就是说我检测到的电平和有效电平是一致的,那就是有效按键,然后开始清空相关内容,并将状态设置为按键按下 handle->state = BTN_STATE_PRESS; 在消抖的时候按键事件一直是 handle->event = (uint8_t)BTN_NONE_PRESS;,按键状态一直是空闲状态(等待按下) handle->state = BTN_STATE_IDLE;

handle->ticks = 0; 清空的时机需要多留心一下。

然后接着跳转到:

    default:// Invalid state, reset to idlehandle->state = BTN_STATE_IDLE;break;

在消抖过程形成完美闭环。


文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。

【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。

感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言。

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

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

相关文章

web前端渡一大师课 CSS属性计算过程

你是否了解CSS 的属性计算过程呢? <body> <h1>这是一个h1标题</h1> </body> 目前我们没有设置改h1的任何样式,但是却能看到改h1有一定的默认样式,例如有默认的字体大小,默认的颜色 那么问题来了,我们这个h1元素上面除了有默认字体大小,默认颜色等…

Redis高频面试题:利用I/O多路复用实现高并发

Redis 通过 I/O 多路复用&#xff08;I/O Multiplexing&#xff09;技术实现高并发&#xff0c;这是其单线程模型能够高效处理大量客户端连接的关键。以下是通俗易懂的解释&#xff0c;结合 Redis 的工作原理&#xff0c;详细说明其实现过程。 1. 什么是 I/O 多路复用&#xff…

爬虫小知识(二)网页进行交互

一、提交信息到网页 1、模块核心逻辑 “提交信息到网页” 是网络交互关键环节&#xff0c;借助 requests 库的 post() 函数&#xff0c;能模拟浏览器向网页发数据&#xff08;如表单、文件 &#xff09;&#xff0c;实现信息上传&#xff0c;让我们能与网页背后的服务器 “沟通…

WPF学习(五)

文章目录一、FileStream和StreamWriter理解1.1、具体关系解析1.2、类比理解1.3、总结1.4、示例代码1.5、 WriteLine()和 Write&#xff08;&#xff09;的区别1.6、 StreamWriter.Close的作用二、一、FileStream和StreamWriter理解 在 C# 中&#xff0c;StreamWriter 和 FileS…

ctf.show-web习题-web2-最简单的sql注入-flag获取详解、总结

解题思路打开靶场既然提示是最简单的sql注入了&#xff0c;那么直接尝试永真登录1 or 11#这里闭合就是简单的单引号可以看到没登录成功&#xff0c;但是有回显&#xff1a;欢迎你&#xff0c;ctfshowsql注入最喜欢的就是回显了&#xff01;这题的思路就是靠这个回显&#xff0c…

upload-labs 靶场通关(1-20)

目录 Pass-01(JS 绕过) Pass-02(文件类型验证) Pass-03(黑名单验证) Pass-04(黑名单验证.htaccess) Pass-05(大小写绕过) Pass-06(末尾空格) Pass-07(增加一个.) Pass-08(增加一个::$DATA) Pass-09&#xff08;代码不严谨&#xff09; Pass-10&#xff08;PPHPHP&am…

[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+vue实现的酒店预订管理系统,推荐!

摘 要 使用旧方法对酒店预订信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在酒店预订信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的酒店预订管理系…

LSTM入门案例(时间序列预测)| pytorch实现(可复现)

需求 假如我有一个时间序列&#xff0c;例如是前113天的价格数据&#xff08;训练集&#xff09;&#xff0c;然后我希望借此预测后30天的数据&#xff08;测试集&#xff09;&#xff0c;实际上这143天的价格数据都已经有了。这里为了简单&#xff0c;每一天的数据只有一个价…

Axure RP 10 预览显示“无标题文档”的空白问题探索【护航版】

1. 安装情况 官网 Axure RP 10&#xff1a;Download Axure RP 10 - Axure &#xff08;PS&#xff1a;11都出了&#xff09; 版本&#xff1a;10.0.0.3924 激活码&#xff1a;49bb9513c40444b9bcc3ce49a7a022f9 &#xff08;10/11都可以用&#xff0c;但只尝试了10&#xff…

基于SpringBoot+Vue的汽车租赁系统(协同过滤算法、腾讯地图API、支付宝沙盒支付、WebsSocket实时聊天、ECharts图形化分析)

系统亮点&#xff1a;协同过滤算法、腾讯地图API、支付宝沙盒支付、WebsSocket实时聊天、ECharts图形化分析&#xff1b;01系统开发工具与环境搭建—前后端分离架构项目架构&#xff1a;B/S架构运行环境&#xff1a;win10/win11、jdk17前端&#xff1a;技术&#xff1a;框架Vue…

数据结构入门:像整理收纳一样简单!

在我们生活中&#xff0c;经常会面对这样的问题&#xff1a; “我要怎么整理我的衣柜&#xff1f;” “电脑里照片太多了&#xff0c;怎么归类才方便查找&#xff1f;” 其实&#xff0c;程序员也有类似的烦恼。他们不整理衣柜&#xff0c;而是“整理数据”。而这门关于如何“收…

力扣每日一题--2025.7.15

&#x1f4da; 力扣每日一题–2025.7.15 3135. 有效单词 &#xff08;简单&#xff09; 大家好&#xff01;今天我们要来聊聊一道有趣的编程题——有效单词 &#x1f4dd; 题目描述 题目分析 &#x1f4da; 题目要求我们判断一个字符串是否为有效单词。有效单词需要满足以下…

Mysql数据库——增删改查CRUD

文章目录一、数据库的基础命令二、创建表三、增(create)四、查询&#xff08;retrieve)五、条件查询&#xff08;where&#xff09;六、修改&#xff08;update&#xff09;七、删除&#xff08;delete&#xff09;一、数据库的基础命令 1.使用客户端连接服务器 mysql -u root…

关于pytorch虚拟环境及具体bug问题修改

本篇博客包含对于虚拟环境概念的讲解和代码实现过程中相关bug的解决关于虚拟环境我的pytorch虚拟环境在D盘&#xff0c;相应python解释器也在D盘&#xff08;一起&#xff09;&#xff0c;但是我的pycharm中的项目在C盘&#xff0c;使用的是pytorch的虚拟环境&#xff0c;这是为…

U盘量产工具与性能优化完全指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;U盘量产工具是IT行业中的专业软件&#xff0c;用于批量生产或修复U盘。安国和银灿是两个提供U盘量产工具的主控芯片制造商&#xff0c;提供初始化、格式化、分区管理、性能优化、故障修复、个性化定制、固件升级…

Golang http开发实战:构建RESTful API保姆级教程

目录 章节1:RESTful API的精髓与Go的Web开发哲学 RESTful API的设计原则 Go的http包核心组件 实战:第一个RESTful API端点 章节2:设计优雅的RESTful路由 路由设计的注意事项 使用Gorilla Mux实现动态路由 章节3:请求与响应的艺术:解析与格式化 解析请求数据 统一…

UGUI 性能优化系列:第一篇——基础优化与资源管理

UGUI 性能优化系列&#xff1a;第一篇——基础优化与资源管理 UGUI 性能优化系列&#xff1a;第二篇——Canvas 与 UI 元素管理 在 Unity 游戏中&#xff0c;用户界面&#xff08;UI&#xff09;是玩家与游戏交互的核心。然而&#xff0c;不当的 UGUI 使用常常成为游戏性能的…

多端协同的招聘系统源码开发指南:小程序+APP一体化设计

当下&#xff0c;很多企业选择搭建属于自己的多端协同招聘平台&#xff0c;尤其是中大型人力资源公司、连锁品牌企业&#xff0c;以及同城服务平台&#xff0c;更是将“小程序APP”一体化招聘系统视为提升效率、降低用工成本的利器。 今天&#xff0c;笔者将从源码开发的角度&a…

Maven 配置文件核心配置:本地仓库、镜像与 JDK 版本

Maven 配置文件核心配置&#xff1a;本地仓库、镜像与 JDK 版本 在 Maven 项目开发中&#xff0c;合理配置 settings.xml 文件能显著提升依赖管理效率。本文将聚焦本地仓库、镜像加速和 JDK 版本这三个核心配置&#xff0c;结合 IDEA 环境详细讲解配置方法与作用。 一、Maven 配…

【时时三省】(C语言基础)通过指针引用字符串

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省如在printf函数中输出一个字符串。这些字符串都是以直接形式&#xff08;字面形式&#xff09;给出的&#xff0c;在一对双撇号中包含若干个合法的字符。使用字符串的更加灵活方便的方法——通…