当前位置: 首页 > news >正文

LVGL实战训练——计算器实现

目录

一、简介

二、部件知识

2.1  按钮矩阵部件(lv_btnmatrix)

2.1.1 按钮矩阵部件的组成

2.1.2 按钮文本设置 

2.1.3 按钮索引

2.1.4 按钮宽度

2.1.5 按钮属性

2.1.6 按钮互斥

2.1.7 按钮文本重着色

2.1.8 按钮矩阵部件的事件

2.1.9 按钮矩阵部件的 API 函数

2.2 文本区域部件(lv_textarea)

2.2.1 文本区域部件的组成

2.2.2 创建文本区域部件 

2.2.3 添加与删除字符 

2.2.4 占位符文本

2.2.5 移动光标

2.2.6 文本区域部件的特殊模式

2.2.7 限制输入的字符 

2.2.8 文本区域部件的 API 函数

三、计算机实现 

3.1 StrCalculate.c 计算器管理函数 

3.2.1 文本区域初始化

3.2.2 矩阵按钮初始化

3.2.3 按钮初始化

3.2.4 按钮事件回调以及计算器逻辑

四、总结


一、简介

        我们之前在我的LVGL专栏里面讲了部分控件(LVGL专栏),我们这里决定把部件知识用到实战中,在实战中来学习LVGL各个部件的使用,这里我们利用CodeBlock这个模拟器,使用LVGL来设计出一个计算器,我们看一下演示的视频,视频如下。

模拟器上用LVGL实现计算器功能

二、部件知识

2.1  按钮矩阵部件(lv_btnmatrix)

        在 LVGL中,按钮矩阵部件相当于一系列伪按钮的集合,它按一定的序列来排布这些按钮。值得注意的是,这些伪按钮并不是真正的按钮部件(lv_btn),它们只是具有按钮外观的图形,但这些图形具有和按钮一样的点击效果。伪按钮所占的内存非常小,一个伪按钮大概占用 8 个字节,而一个普通按钮部件所占的内存大概为 100~150个字节,由此可见,如果我们 GUI界面中使用较多的按钮时,按钮矩阵的优势就尤为明显了。

2.1.1 按钮矩阵部件的组成

按钮矩阵部件由两个部分组成:主体背景和按钮,示意图如下:

2.1.2 按钮文本设置 

在 LVGL中,按钮矩阵部件中的每个按钮都可以设置文本,如果用户想设置这些按钮文本,则需要定义一个字符串数组(指针),并在该数组中传入所需的文本内容,最后通过 lv_btnmatrix_set_map 函数设置按钮文本,我们计算机矩阵按钮代码如下:

static const char *screen_btnm_1_text_map[] = {
     "1","2","3","\xEF\xBC\x8B",
     "\n","4","5","6",
     "\xEF\xBC\x8D","\n","7","8",
     "9","\xC3\x97","\n",".",
     "0","\xEF\xBC\x9D","\xC3\xB7","",
     };

 //这些十六进制转义序列使用的是 UTF-8 编码,将 Unicode 字符转换为多字节表示,本质就是+、-、×、÷、=,这五个中文字符,不过得确保字体里面包含这几个字符。

lv_btnmatrix_set_map(ui->screen_btnm_1, screen_btnm_1_text_map);

在上述源码中,我们首先定义了字符串数组,里面传入了 16个按钮的对应文本,注意:该数组最后一个元素必须为空,且如果需要在多一行按键的话,需要多加一个"\n",有了按钮数组后,再调用 lv_btnmatrix_create 函数创建按钮矩阵,最后通过 lv_btnmatrix_set_map 函数把字符串数组映射到按钮矩阵当中。 

2.1.3 按钮索引

索引就相当于一个 ID,在按钮矩阵部件中,每一个按钮都有对应的索引。我们这个示例有16个按键,索引如下:

注意:索引对于按钮的属性设置非常重要,大家一定要理解它和实际按钮的对应关系。

2.1.4 按钮宽度

        在默认情况下,按钮矩阵每一行按钮的宽度都是自动计算的,如果用户想改变按钮的宽度,可以调用 lv_btnmatrix_set_btn_width 函数来进行设置。值得注意的是,在按钮矩阵部件中,按钮只能设置相对宽度。
        接下来,我们举一个例子,帮助大家理解按钮的相对宽度:假设按钮矩阵的某一行中存在3 个按钮(btn1~btn3),btn1~btn3 的相对宽度比为 1:1:2,此时,btn1 和 btn2 将各占该行25%的宽度,而 btn3 将占该行 50%的宽度,示意图如下:

2.1.5 按钮属性

用户可以调用 lv_btnmatrix_set_btn_ctrl 函数,为按钮添加、清除指定的属性,这些属性的相关枚举如下:

① LV_BTNMATRIX_CTRL_HIDDEN:将按钮隐藏;
② LV_BTNMATRIX_CTRL_NO_REPEAT:禁用长按;
③ LV_BTNMATRIX_CTRL_DISABLED:禁用按钮;
④ LV_BTNMATRIX_CTRL_CHECKABLE:启用按钮状态切换;
⑤ LV_BTNMATRIX_CTRL_CHECKED:选中按钮;
⑥ LV_BTNMATRIX_CTRL_POPOVER:按下此按钮时在弹出窗口中显示按钮标签;
⑦ LV_BTNMATRIX_CTRL_RECOLOR:启用按钮文本的重新着色功能。

接下来,我们以简单示例来理解按钮属性的设置,示例代码如下所示:

const char * map [] = { "btn1" , "btn2" , "btn3" , "" };
void lv_mainstart ()
{
lv_obj_t * btnm1 = lv_btnmatrix_create ( lv_scr_act ());
lv_btnmatrix_set_map ( btnm1 , map );
lv_obj_set_size ( btnm1 , 800 , 480 / 2 );
lv_btnmatrix_set_btn_ctrl ( btnm1 , 0 , LV_BTNMATRIX_CTRL_HIDDEN );
lv_btnmatrix_set_btn_ctrl ( btnm1 , 1 , LV_BTNMATRIX_CTRL_DISABLED );
lv_btnmatrix_set_btn_ctrl ( btnm1 , 2 , LV_BTNMATRIX_CTRL_CHECKABLE );
lv_btnmatrix_set_btn_width ( btnm1 , 2 , 2 );
}
        在上述源码中,我们调用 lv_btnmatrix_set_btn_ctrl 函数,为 btn1 添加隐藏的属性,为 btn2
添加禁用的属性 ( 不能点击 ) ,示例代码可以在 PC 模拟器中运行,效果图如下所示:

如果用户想要清除按钮的指定属性,可以调用 lv_btnmatrix_clear_btn_ctrl 函数。  

2.1.6 按钮互斥

按钮互斥是指:在某一时刻,只允许有一个按钮处于按下不弹起状态(被选中),当我们选中一个按钮之后,其他的按钮将会自动清除选中属性,示意图如下:

用户可以调用 lv_btnmatrix_set_one_checked 函数,开启按钮互斥功能。 

2.1.7 按钮文本重着色 

        在默认情况下,按钮矩阵中的按钮文本都是黑色的,如果用户需要设置文本为其他的颜色,则必须先调用 lv_btnmatrix_set_btn_ctrl 函数,为按钮添加文本重着色的属性。接下来,我们以简单示例来帮助大家理解按钮文本的重着色,示例代码如下所示:

const char * map [] = { "#FF0000 btn1#" , "btn2" , "btn3" , "" };
void lv_mainstart ()
{
        lv_obj_t * btnm1 = lv_btnmatrix_create ( lv_scr_act (), NULL );
        lv_btnmatrix_set_map ( btnm1 , map );
        lv_btnmatrix_set_btn_ctrl ( btnm1 , 0 , LV_BTNMATRIX_CTRL_RECOLOR );
}

 

        由上述源码可知,我们在定义按钮数组时,为 btn1 的文本设置了颜色(红色),其通用的格式为:# + 16 进制颜色 + 按钮文本 + #,例如设置红色文本:#FF0000 btn1#。值得注意的是,在设置完文本颜色之后,我们还需要为按钮添加文本重着色的属性,其相关的枚举为LV_BTNMATRIX_CTRL_RECOLOR。示例代码可以在 PC 模拟器中运行,效果图如下所示: 

2.1.8 按钮矩阵部件的事件

① LV_EVENT_VALUE_CHANGED:当一个按钮被按下、释放或长按时发送。
② LV_EVENT_DRAW_PART_BEGIN:开始绘制按钮。
 

2.1.9 按钮矩阵部件的 API 函数

LVGL 官方提供了一些与按钮矩阵部件相关 API,如下表所示:

函数名称作用参数参数说明
lv_btnmatrix_create()创建按钮矩阵部件(lv_obj_t *parent, const lv_obj_t *copy)parent: 父对象;copy: 复制源(可选,通常为NULL
lv_btnmatrix_set_map()设置按钮的文本映射(定义按钮布局和文本)(lv_obj_t *btnm, const char *map[])btnm: 按钮矩阵对象;map[]: 文本数组(如screen_btnm_1_text_map
lv_btnmatrix_set_ctrl_map()设置多个按钮的属性(如禁用、可切换等)(lv_obj_t *btnm, const lv_btnmatrix_ctrl_t ctrl_map[])btnm: 按钮矩阵对象;ctrl_map[]: 控制属性数组(如LV_BTNMATRIX_CTRL_...
lv_btnmatrix_set_selected_btn()设置当前选中的按钮(通过索引)(lv_obj_t *btnm, uint16_t btn_id)btnm: 按钮矩阵对象;btn_id: 按钮索引(从0开始)
lv_btnmatrix_set_btn_ctrl()设置单个按钮的属性(如禁用、可切换等)(lv_obj_t *btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)btnm: 按钮矩阵对象;btn_id: 按钮索引;ctrl: 控制属性
lv_btnmatrix_clear_btn_ctrl()清除单个按钮的指定属性(lv_obj_t *btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)同上
lv_btnmatrix_set_btn_ctrl_all()设置所有按钮的同一属性(lv_obj_t *btnm, lv_btnmatrix_ctrl_t ctrl)btnm: 按钮矩阵对象;ctrl: 控制属性
lv_btnmatrix_clear_btn_ctrl_all()清除所有按钮的同一属性(lv_obj_t *btnm, lv_btnmatrix_ctrl_t ctrl)同上
lv_btnmatrix_set_btn_width()设置单个按钮的相对宽度(如宽度为2表示占两列)(lv_obj_t *btnm, uint16_t btn_id, uint8_t width)btnm: 按钮矩阵对象;btn_id: 按钮索引;width: 宽度比例(1~16)
lv_btnmatrix_set_one_checked()设置按钮互斥模式(同一时间只能选中一个按钮)(lv_obj_t *btnm, bool one_checked)btnm: 按钮矩阵对象;one_checkedtrue/false
lv_btnmatrix_get_map()获取按钮的文本映射数组(const lv_obj_t *btnm)btnm: 按钮矩阵对象;返回值:const char **(文本数组)
lv_btnmatrix_get_selected_btn()获取用户最后点击的按钮索引(const lv_obj_t *btnm)btnm: 按钮矩阵对象;返回值:uint16_t(索引)
lv_btnmatrix_get_btn_text()获取指定按钮的文本(const lv_obj_t *btnm, uint16_t btn_id)btnm: 按钮矩阵对象;btn_id: 按钮索引;返回值:const char*
lv_btnmatrix_has_btn_ctrl()检查按钮是否具有指定属性(const lv_obj_t *btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)btnm: 按钮矩阵对象;btn_id: 按钮索引;ctrl: 控制属性;返回值:bool
lv_btnmatrix_get_one_checked()判断是否启用了按钮互斥模式(const lv_obj_t *btnm)btnm: 按钮矩阵对象;返回值:bool

2.2 文本区域部件(lv_textarea)

2.2.1 文本区域部件的组成

文本区域部件由五个部分组成:
① 主体 LV_PART_MAIN:可设置背景属性以及文本样式属性;
② 滚动条 LV_PART_SCROLLBAR:可设置滚动条样式属性;
③ 所选文本 LV_PART_SELECTED:可设置所选文本的样式;
④ 光标 LV_PART_CURSOR:设置光标的位置、闪烁时间和样式属性;
⑤ 占位符 LV_PART_TEXTAREA_PLACEHOLDER:可设置占位符(提示文本)的样式。

2.2.2 创建文本区域部件 

在 LVGL 中,用户需要创建文本区域部件,可调用以下函数:

lv_obj_t * lv_textarea_create ( lv_obj_t * parent );

上述函数只有一个形参,该形参指向文本区域部件的父类。

2.2.3 添加与删除字符 

文本区域部件就是一个文本输入框, 用户可以在该部件的文本区域中输入字符和删除字符,下面我们分别介绍添加字符和删除字符的知识,如下所示:

1. 添加字符和文本
用户需要在文本区域中添加一个字符或者一段字符串,可分别调用 lv_textarea_add_char lv_textarea_add_text 函数。接下来,我们以简单示例来理解字符和文本的添加,示例代码如下所示:

void lv_mainstart ( void )
{
        lv_obj_t * textarea = lv_textarea_create ( lv_scr_act ()); /* 定义并创建文本框 */
        lv_textarea_add_char ( textarea , 'c' );
        /* 添加一个字符 */
        lv_textarea_add_text ( textarea , "insert this text" );
        /* 添加一个字符串 */
        lv_obj_center ( textarea );
        /* 中间对齐 */
}

在上述源码中,我们创建先文本区域部件,调用 lv_textarea_add_char 函数在文本区域添加“c”字符,然后调用 lv_textarea_add_text 函数在文本区域中添加字符串“insert this text”,最后把文本区域部件居中对齐。示例代码可以在 PC 模拟器中运行,效果图如下所示:

 

 2. 删除字符
在 LVGL 中,文本区域部件删除字符的方法有两种,如下所示:
① 调用 lv_textarea_del_char 函数,从光标位置的左侧删除一个字符;
② 调用 lv_textarea_del_char_forward 函数,从光标位置的右侧删除一个字符。

2.2.4 占位符文本

占位符(Placeholder)即默认显示的文本,常用于对用户进行默认的提示、说明或引导。在 LVGL 中,用户可调用 lv_textarea_set_placeholder_text 函数设置占位符。接下来,我们以简单示例来理解占位符文本的设置,示例代码如下所示:

在上述源码中,我们先创建文本区域部件,然后设置占位符提示文本为“Please enter text......”。示例代码可以在 PC 模拟器中运行,效果图如下所示:

从上图可知:占位符就是对用户进行提示和引导。 

2.2.5 移动光标

        在 LVGL中,文本区域的光标默认在左上角的位置。当我们添加一个字符时,该光标从左往右移动,默认情况下,光标会一直在文本最后一个字符的右侧。如果用户需要设置光标的位置,可调用 lv_textarea_set_cursor_pos 函数,该函数的第二个形参表示光标的移动位置,当该形参设置为 0,则光标在第一个字符之前;当该形参设置为 LV_TA_CURSOR_LAST,则光标在最后一个字符之后。

         如果用户不想使用上述的方式设置光标的位置,可使用以下函数,单步移动光标:

 

上述的光标移动函数常用于按键手动控制光标,当用户按下某个按键时,光标将会向指定的方向移动。

2.2.6 文本区域部件的特殊模式

在 LVGL 中,文本区域部件的特殊模式有两个,如下所示:
① 单行模式:默认情况下,文本区域的文本超出它的宽度时,该文本将自动换行。如果将文本区域部件设置为单行模式,当它的文本超出其宽度后,文本并不会自动换行,超出的文本将水平滚动显示。单行模式可通过 lv_textarea_set_one_line 函数进行设置。
② 密码模式:为了保证文本的机密性,LVGL 的文本区域部件为用户提供了密码模式,当用户输入文本之后,这些文本内容将以“*”字符替代。用户需要设置密码模式,可调用
lv_textarea_set_password_mode 函数。
注意:当用户使用密码模式时,原始文本会先显示一段时间,然后隐藏,该显示时间可以在 lv_conf.h 文件的 LV_TEXTAREA_DEF_PWD_SHOW_TIME 宏定义中设置。如果用户想获取密码框的文本内容,可调用 lv_textarea_get_text 函数,返回的文本并不是“*”字符,而是原始的文本内容。 

2.2.7 限制输入的字符 

文本区域部件可以限制输入字符的范围,例如取款机的密码框,它只允许输入 0~9 的字符,且密码长度固定为 6。LVGL 文本区域部件限制输入字符的内容有两个:限制字符类型和限制字符长度。
① 限制字符类型,比如 ATM 的密码框,它只能输入数字 0~9。用户需要限制字符类型,
可以调用 lv_textarea_set_accepted_chars 函数进行设置。
② 限制字符长度,比如 ATM 的密码框,它只能输入 6 位密码。用户需要限制字符长度,
可以调用 lv_textarea_set_max_length 函数进行设置。
接下来,我们以简单示例来理解字符输入的限制,示例代码如下所示:

void lv_mainstart ( void )
{
        /* 定义并创建文本框 */
        lv_obj_t * textarea = lv_textarea_create ( lv_scr_act ());
        /* 设置输入字符长度为 6 */
        lv_textarea_set_max_length ( textarea , 6 );
        /* 设置接收输入字符列表 */
        lv_textarea_set_accepted_chars ( textarea , "0123456789" );
        /* 一行模式 */
        lv_textarea_set_one_line ( textarea , true );
        /* 中间对齐 */
        lv_obj_center ( textarea );
        /* 输入的字符 */
        lv_textarea_add_char ( textarea , 'C' );
        lv_textarea_add_char ( textarea , 'a' );
        lv_textarea_add_char ( textarea , 'i' );
        lv_textarea_add_char ( textarea , 'X' );
        lv_textarea_add_char ( textarea , 'e' );
        lv_textarea_add_char ( textarea , 'u' );
        lv_textarea_add_char ( textarea , 'e' );
        lv_textarea_add_char ( textarea , 'F' );
        lv_textarea_add_char ( textarea , 'e' );
        lv_textarea_add_char ( textarea , 'n' );
        lv_textarea_add_char ( textarea , 'g' );
        lv_textarea_add_char ( textarea , '1' );
        lv_textarea_add_char ( textarea , '6' );
        lv_textarea_add_char ( textarea , '8' );
        lv_textarea_add_char ( textarea , '6' );
        lv_textarea_add_char ( textarea , '6' );
        lv_textarea_add_char ( textarea , '6' );
}

在上述源码中,我们调用 lv_textarea_set_max_length 函数,限制输入字符长度为 6,然后调用 lv_textarea_set_accepted_chars函数,限制输入字符类型范围为“0123456789”,最后调用多个 lv_textarea_add_char函数添加文本,该文本为“CaiXueFeng168666”。上示例代码可以在PC 模拟器中运行,效果图如下所示: 

 

由上图可知,文本区域部件最终显示的文本是“168666”,而我们输入的所有文本为“CaiXueFeng168666”,这说明我们设置的字符类型和长度限制生效了。

2.2.8 文本区域部件的 API 函数

函数名称作用参数参数说明
lv_textarea_create()创建文本区域对象(lv_obj_t *parent, const lv_obj_t *copy)parent: 父对象;copy: 复制源(可选,通常为NULL
lv_textarea_add_char()插入一个字符到当前光标位置(lv_obj_t *ta, uint32_t c)ta: 文本区域对象;c: 要插入的字符(Unicode 编码)
lv_textarea_add_text()将文本插入到当前光标位置(lv_obj_t *ta, const char *txt)ta: 文本区域对象;txt: 要插入的文本
lv_textarea_del_char()删除当前光标左侧的字符(lv_obj_t *ta)ta: 文本区域对象
lv_textarea_del_char_forward()删除当前光标右侧的字符(lv_obj_t *ta)同上
lv_textarea_set_text()设置文本区域的完整文本(lv_obj_t *ta, const char *txt)ta: 文本区域对象;txt: 新文本
lv_textarea_set_placeholder_text()设置占位符文本(未输入时显示的提示文本)(lv_obj_t *ta, const char *txt)ta: 文本区域对象;txt: 占位符文本
lv_textarea_set_cursor_pos()设置光标位置(基于字符索引)(lv_obj_t *ta, int32_t pos)ta: 文本区域对象;pos: 字符索引(从0开始)
lv_textarea_set_cursor_click_pos()启用/禁用通过点击移动光标(lv_obj_t *ta, bool en)ta: 文本区域对象;entrue启用,false禁用
lv_textarea_set_password_mode()设置密码模式(输入显示为*或其他字符)(lv_obj_t *ta, bool en)ta: 文本区域对象;entrue启用,false禁用
lv_textarea_set_one_line()设置单行模式(禁止换行)(lv_obj_t *ta, bool en)ta: 文本区域对象;entrue启用单行模式
lv_textarea_set_accepted_chars()限制可输入的字符类型(lv_obj_t *ta, const char *list)ta: 文本区域对象;list: 允许的字符列表(如"0123456789"
lv_textarea_set_max_length()限制输入的最大字符数(lv_obj_t *ta, uint32_t num)ta: 文本区域对象;num: 最大字符数
lv_textarea_set_insert_replace()设置输入时自动替换格式(如自动添加分隔符)(lv_obj_t *ta, const char *txt)ta: 文本区域对象;txt: 替换规则(如自动插入-
lv_textarea_set_text_selection()启用/禁用文本选择模式(lv_obj_t *ta, bool en)ta: 文本区域对象;entrue启用选择模式
lv_textarea_set_password_show_time()设置密码显示明文的时间(单位:毫秒)(lv_obj_t *ta, uint16_t time)ta: 文本区域对象;time: 明文显示时间
lv_textarea_get_text()获取文本内容(密码模式下返回实际文本)(const lv_obj_t *ta)ta: 文本区域对象;返回值:const char*
lv_textarea_get_placeholder_text()获取占位符文本(const lv_obj_t *ta)ta: 文本区域对象;返回值:const char*
lv_textarea_get_label()获取文本区域的标签对象(用于自定义样式)(const lv_obj_t *ta)ta: 文本区域对象;返回值:lv_obj_t*
lv_textarea_get_cursor_pos()获取当前光标的字符索引位置(const lv_obj_t *ta)ta: 文本区域对象;返回值:int32_t
lv_textarea_get_cursor_click_pos()判断是否启用了点击移动光标(const lv_obj_t *ta)ta: 文本区域对象;返回值:bool
lv_textarea_get_password_mode()判断是否启用了密码模式(const lv_obj_t *ta)ta: 文本区域对象;返回值:bool
lv_textarea_get_one_line()判断是否启用单行模式(const lv_obj_t *ta)ta: 文本区域对象;返回值:bool
lv_textarea_get_accepted_chars()获取允许输入的字符列表(const lv_obj_t *ta)ta: 文本区域对象;返回值:const char*
lv_textarea_get_max_length()获取最大允许输入字符数(const lv_obj_t *ta)ta: 文本区域对象;返回值:uint32_t
lv_textarea_text_is_selected()检查当前是否有文本被选中(const lv_obj_t *ta)ta: 文本区域对象;返回值:bool
lv_textarea_get_text_selection()判断是否启用了文本选择模式(const lv_obj_t *ta)ta: 文本区域对象;返回值:bool
lv_textarea_get_password_show_time()获取密码明文显示时间(const lv_obj_t *ta)ta: 文本区域对象;返回值:uint16_t
lv_textarea_clear_selection()清除当前选中的文本(lv_obj_t *ta)ta: 文本区域对象
lv_textarea_cursor_right()将光标向右移动一个字符(lv_obj_t *ta)ta: 文本区域对象
lv_textarea_cursor_left()将光标向左移动一个字符(lv_obj_t *ta)同上
lv_textarea_cursor_down()将光标向下移动一行(多行模式下生效)(lv_obj_t *ta)同上
lv_textarea_cursor_up()将光标向上移动一行(多行模式下生效)(lv_obj_t *ta)同上

三、计算机实现 

3.1 StrCalculate.c 计算器管理函数 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../Inc/StrCalculate.h"uint8_t strput(StrStack_t * st,char strin)
{if(st->Top_Point == 15 - 1){return -1;}st->strque[st->Top_Point++] = strin;return 0;
}uint8_t strdel(StrStack_t * st)
{if(st->Top_Point == 0){return -1;}st->strque[--st->Top_Point] = NULL;return 0;
}uint8_t strstack_isEmpty(StrStack_t* st)
{if(st->Top_Point == 0){return 1;}return 0;
}void strclear(StrStack_t* sq)
{while(!strstack_isEmpty(sq)){strdel(sq);}
}uint8_t NumStackPut(NumStack_t * st, float in)
{if(st->Top_Point == CAL_DEPTH - 1){return -1;}st->data[st->Top_Point++] = in;return 0;
}uint8_t NumStackDel(NumStack_t * st)
{if(st->Top_Point == 0){return -1;}st->data[st->Top_Point--] = 0;return 0;
}uint8_t NumStack_isEmpty(NumStack_t* st)
{if(st->Top_Point == 0){return 1;}return 0;
}void NumStackClear(NumStack_t* st)
{while(!NumStack_isEmpty(st)){NumStackDel(st);}
}uint8_t SymStackPut(SymStack_t * st, char in)
{if(st->Top_Point == CAL_DEPTH - 1){return -1;}st->data[st->Top_Point++] = in;return 0;
}uint8_t SymStackDel(SymStack_t * st)
{if(st->Top_Point == 0){return -1;}st->data[st->Top_Point--] = 0;return 0;
}uint8_t SymStack_isEmpty(SymStack_t* st)
{if(st->Top_Point == 0){return 1;}return 0;
}void SymStackClear(SymStack_t* st)
{while(!SymStack_isEmpty(st)){SymStackDel(st);}
}uint8_t SymisHighPriority(char top, char present)
{//乘除的优先级最大if(top == '*' || top == '/'){return 1;}else if(top == '+'){if(present == '-'){return 1;}else{return 0;}}else if(top == '-'){if(present == '+'){return 1;}else{return 0;}}
}void CalculateOne(NumStack_t * numstack, SymStack_t * symstack)
{caldata_t temp;temp.datatype = NUMBER_TYPE;temp.symbol = NULL;//计算数字栈中的顶部两数,结果存到temp中if(symstack->data[symstack->Top_Point-1] == '+')temp.number = (numstack->data[numstack->Top_Point-2]) + (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '-')temp.number = (numstack->data[numstack->Top_Point-2]) - (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '*')temp.number = (numstack->data[numstack->Top_Point-2]) * (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '/')temp.number = (numstack->data[numstack->Top_Point-2]) / (numstack->data[numstack->Top_Point-1]);//运算前两数出栈,运算结果数入栈NumStackDel(numstack);NumStackDel(numstack);NumStackPut(numstack,temp.number);SymStackDel(symstack);}uint8_t NumSymSeparate(char * str, uint8_t strlen, NumStack_t * NumStack, SymStack_t * SymStack)
{NumStackClear(NumStack);SymStackClear(SymStack);caldata_t temp,temp_pre;char NumBehindPoint_Flag = 0;//数字是否在小数点后,后多少位temp.datatype = NUMBER_TYPE;temp.number = 0;temp.symbol = NULL;temp_pre = temp;temp_pre.datatype = SYMBOL_TYPE;if(str[0]>'9' || str[0]<'0')return 1;//erroint i;for(i=0;i<strlen;i++){if(str[i]=='.'){temp.datatype = POINT_TYPE;if(temp_pre.datatype == NUMBER_TYPE){}else{return 2;}temp_pre = temp;}if(str[i]<='9' && str[i]>='0'){//溢出报错if(NumStack->Top_Point>CAL_DEPTH || SymStack->Top_Point>CAL_DEPTH){return 3;}//读取当前的字符到temp中temp.datatype = NUMBER_TYPE;temp.number = (str[i] - '0');temp.symbol = NULL;//如果为连续数字,需要进行进位,将数字栈顶读出进位,再加上现在位,再入栈if(temp_pre.datatype == NUMBER_TYPE){if(!NumBehindPoint_Flag){temp.number += NumStack->data[NumStack->Top_Point-1] * 10;}else{NumBehindPoint_Flag += 1;char i = NumBehindPoint_Flag;while(i--){temp.number /= 10;}temp.number += NumStack->data[NumStack->Top_Point-1];}NumStackDel(NumStack);NumStackPut(NumStack,temp.number);}//当前数字刚好是小数点后一位else if(temp_pre.datatype == POINT_TYPE){NumBehindPoint_Flag = 1;temp.number /= 10;temp.number += NumStack->data[NumStack->Top_Point-1];NumStackDel(NumStack);NumStackPut(NumStack,temp.number);}//前一位不是数字或小数点,现在读取的这一位是数字,直接入栈else{NumStackPut(NumStack,temp.number);}temp_pre = temp;}else if(str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/'){//溢出报错if(NumStack->Top_Point>CAL_DEPTH || SymStack->Top_Point>CAL_DEPTH){return 4;}//读取当前的字符到temp中temp.datatype = SYMBOL_TYPE;temp.symbol = str[i];temp.number = 0;NumBehindPoint_Flag = 0;//小数点计算已经结束//重复输入了运算符号if(temp_pre.datatype == SYMBOL_TYPE){return 5 ;//erro}else{if((!SymStack_isEmpty(SymStack)) && SymisHighPriority(SymStack->data[SymStack->Top_Point-1],temp.symbol)){CalculateOne(NumStack, SymStack);SymStackPut(SymStack,temp.symbol);}else{//符号压入符号栈SymStackPut(SymStack,temp.symbol);}temp_pre = temp;}}}return 0;
}uint8_t StrCalculate(char * str,NumStack_t * NumStack, SymStack_t * SymStack)
{if(NumSymSeparate(str,strlen(str),NumStack,SymStack)){//erro, clear allNumStackClear(NumStack);SymStackClear(SymStack);return -1;}else{while(!SymStack_isEmpty(SymStack)){CalculateOne(NumStack,SymStack);}}return 0;
}uint8_t isIntNumber(float number)
{if(number == (int)number){return 1;}return 0;
}

计算器的逻辑就是很经典的计算器问题,经典的就是开两个栈,一个存放符号,一个存数字,然后进行出栈计算等等操作。

具体过程是:

1、遍历表达式,当遇到操作数,将其压入操作数栈。

2、遇到运算符时,如果运算符栈为空,则直接将其压入运算符栈。

3、如果运算符栈不为空,那就与运算符栈顶元素进行比较:如果当前运算符优先级比栈顶运算符高,则继续将其压入运算符栈,如果当前运算符优先级比栈顶运算符低或者相等,则从操作数符栈顶取两个元素,从栈顶取出运算符进行运算,并将运算结果压入操作数栈。

4、继续将当前运算符与运算符栈顶元素比较。

5、继续按照以上步骤进行遍历,当遍历结束之后,则将当前两个栈内元素取出来进行运算即可得到最终结果。

这里我简单的介绍一下这个算法:

2.4.1.1 数据结构 

StrStack_t:字符栈

---用于临时存储输入字符

---供strput(入栈)、strdel(出栈)等操作

NumStack_t:数字栈(浮点数)

---存储运算中的数字

---深度为CAL_DEPTH(15)

SymStack_t:符号栈

---存储运算符(+-*/)

---同样具有栈操作函数

2.4.1.2 核心算法流程

uint8_t NumSymSeparate(...)

这个可以说是整个算法核心部分了,NumSymSeparate函数,它负责将输入的字符串分解为数字和运算符,并处理运算顺序的问题。这里需要特别注意数字的小数点处理和运算符优先级的判断。比如,当遇到小数点时,标记NumBehindPoint_Flag,并调整数字的位数。运算符处理时,通过SymisHighPriority函数比较栈顶运算符和当前运算符的优先级,决定是否立即进行计算,从而保持正确的运算顺序。另外,在NumSymSeparate函数中,当处理到运算符时,会检查前一个元素是否是符号类型,如果是则报错,这样处理连续的运算符(如"5++3")会被视为错误,这是正确的。但如果是负数的情况,这里就会导致错误,所以代码不支持负数的运算。前面做的所有都是为了这个函数进行铺垫,我们可以在函数调用关系看到:

优先级判断(SymisHighPriority函数)

uint8_t SymisHighPriority(...)

优先级规则:* / > + > -

栈顶运算符优先级 >= 当前运算符时返回1

例如:

栈顶+ vs 当前- → 同优先级,返回1

栈顶+ vs 当前* → 当前优先级高,返回0

void CalculateOne(NumStack_t * numstack, SymStack_t * symstack)

CalculateOne函数用于执行实际的运算操作,取出数字栈顶的两个数字和符号栈顶的运算符,计算结果后再将结果压回数字栈。这一步是实际计算的核心。

uint8_t StrCalculate(char * str,NumStack_t * NumStack, SymStack_t * SymStack)

1、调用NumSymSeparate进行表达式分解

2、循环执行CalculateOne直到符号栈为空

3、最终结果存储在数字栈顶

3.2 计算器UI编写

我们这里放部分关键的源码,关键源码如下:

3.2.1 文本区域初始化

	 //Write codes screen_ta_1ui->screen_ta_1 = lv_textarea_create(ui->screen);lv_obj_set_pos(ui->screen_ta_1, 4.5, 5);lv_obj_set_size(ui->screen_ta_1, 251, 51);lv_obj_set_scrollbar_mode(ui->screen_ta_1,LV_SCROLLBAR_MODE_OFF);lv_obj_clear_flag(ui->screen_ta_1, LV_OBJ_FLAG_SCROLLABLE);//Write style state: LV_STATE_DEFAULT for style_screen_ta_1_main_main_defaultstatic lv_style_t style_screen_ta_1_main_main_default;if (style_screen_ta_1_main_main_default.prop_cnt > 1)lv_style_reset(&style_screen_ta_1_main_main_default);elselv_style_init(&style_screen_ta_1_main_main_default);lv_style_set_radius(&style_screen_ta_1_main_main_default, 4);lv_style_set_bg_color(&style_screen_ta_1_main_main_default, lv_color_make(0xff, 0xff, 0xff));lv_style_set_bg_grad_color(&style_screen_ta_1_main_main_default, lv_color_make(0xff, 0xff, 0xff));lv_style_set_bg_grad_dir(&style_screen_ta_1_main_main_default, LV_GRAD_DIR_VER);lv_style_set_bg_opa(&style_screen_ta_1_main_main_default, 255);lv_style_set_border_color(&style_screen_ta_1_main_main_default, lv_color_make(0xe6, 0xe6, 0xe6));lv_style_set_border_width(&style_screen_ta_1_main_main_default, 2);lv_style_set_text_color(&style_screen_ta_1_main_main_default, lv_color_make(0x00, 0x00, 0x00));lv_style_set_text_font(&style_screen_ta_1_main_main_default, &ui_font_Cuyuan20);lv_style_set_text_letter_space(&style_screen_ta_1_main_main_default, 2);lv_style_set_text_align(&style_screen_ta_1_main_main_default, LV_TEXT_ALIGN_RIGHT);//lv_style_set_pad_left(&style_screen_ta_1_main_main_default, 2);//lv_style_set_pad_right(&style_screen_ta_1_main_main_default, 2);//lv_style_set_pad_top(&style_screen_ta_1_main_main_default, 2);//lv_style_set_pad_bottom(&style_screen_ta_1_main_main_default, 2);lv_obj_add_style(ui->screen_ta_1, &style_screen_ta_1_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);lv_obj_clear_flag(ui->screen_ta_1,LV_OBJ_FLAG_CLICKABLE);//Write style state: LV_STATE_DEFAULT for style_screen_ta_1_main_scrollbar_defaultstatic lv_style_t style_screen_ta_1_main_scrollbar_default;if (style_screen_ta_1_main_scrollbar_default.prop_cnt > 1)lv_style_reset(&style_screen_ta_1_main_scrollbar_default);elselv_style_init(&style_screen_ta_1_main_scrollbar_default);lv_style_set_radius(&style_screen_ta_1_main_scrollbar_default, 0);lv_style_set_bg_color(&style_screen_ta_1_main_scrollbar_default, lv_color_make(0x21, 0x95, 0xf6));lv_style_set_bg_grad_color(&style_screen_ta_1_main_scrollbar_default, lv_color_make(0x21, 0x95, 0xf6));lv_style_set_bg_grad_dir(&style_screen_ta_1_main_scrollbar_default, LV_GRAD_DIR_VER);lv_style_set_bg_opa(&style_screen_ta_1_main_scrollbar_default, 255);lv_obj_add_style(ui->screen_ta_1, &style_screen_ta_1_main_scrollbar_default, LV_PART_SCROLLBAR|LV_STATE_DEFAULT);lv_textarea_set_text(ui->screen_ta_1,"\n");

3.2.2 矩阵按钮初始化

//Write codes screen_btnm_1ui->screen_btnm_1 = lv_btnmatrix_create(ui->screen);lv_obj_set_pos(ui->screen_btnm_1, 3, 61);lv_obj_set_size(ui->screen_btnm_1, 316, 176);static const char *screen_btnm_1_text_map[] = {"1","2","3","\xEF\xBC\x8B","\n","4","5","6","\xEF\xBC\x8D","\n","7","8","9","\xC3\x97","\n",".","0","\xEF\xBC\x9D","\xC3\xB7","",};lv_btnmatrix_set_map(ui->screen_btnm_1, screen_btnm_1_text_map);//Write style state: LV_STATE_DEFAULT for style_screen_btnm_1_main_main_defaultstatic lv_style_t style_screen_btnm_1_main_main_default;if (style_screen_btnm_1_main_main_default.prop_cnt > 1)lv_style_reset(&style_screen_btnm_1_main_main_default);elselv_style_init(&style_screen_btnm_1_main_main_default);lv_style_set_radius(&style_screen_btnm_1_main_main_default, 4);lv_style_set_bg_color(&style_screen_btnm_1_main_main_default, lv_color_make(0xff, 0xff, 0xff));lv_style_set_bg_grad_color(&style_screen_btnm_1_main_main_default, lv_color_make(0xff, 0xff, 0xff));lv_style_set_bg_grad_dir(&style_screen_btnm_1_main_main_default, LV_GRAD_DIR_VER);lv_style_set_bg_opa(&style_screen_btnm_1_main_main_default, 255);lv_style_set_border_color(&style_screen_btnm_1_main_main_default, lv_color_make(0xff, 0xff, 0xff));lv_style_set_border_width(&style_screen_btnm_1_main_main_default, 1);lv_style_set_pad_left(&style_screen_btnm_1_main_main_default, 2);lv_style_set_pad_right(&style_screen_btnm_1_main_main_default, 2);lv_style_set_pad_top(&style_screen_btnm_1_main_main_default, 2);lv_style_set_pad_bottom(&style_screen_btnm_1_main_main_default, 2);lv_style_set_pad_row(&style_screen_btnm_1_main_main_default, 8);lv_style_set_pad_column(&style_screen_btnm_1_main_main_default, 8);lv_obj_add_style(ui->screen_btnm_1, &style_screen_btnm_1_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);//Write style state: LV_STATE_DEFAULT for style_screen_btnm_1_main_items_defaultstatic lv_style_t style_screen_btnm_1_main_items_default;if (style_screen_btnm_1_main_items_default.prop_cnt > 1)lv_style_reset(&style_screen_btnm_1_main_items_default);elselv_style_init(&style_screen_btnm_1_main_items_default);lv_style_set_radius(&style_screen_btnm_1_main_items_default, 4);lv_style_set_bg_color(&style_screen_btnm_1_main_items_default, lv_color_make(0xe6, 0xe6, 0xe6));lv_style_set_bg_grad_color(&style_screen_btnm_1_main_items_default, lv_color_make(0xe6, 0xe6, 0xe6));lv_style_set_bg_grad_dir(&style_screen_btnm_1_main_items_default, LV_GRAD_DIR_VER);lv_style_set_bg_opa(&style_screen_btnm_1_main_items_default, 255);lv_style_set_border_color(&style_screen_btnm_1_main_items_default, lv_color_make(0xd6, 0xdd, 0xe3));lv_style_set_border_width(&style_screen_btnm_1_main_items_default, 1);lv_style_set_text_color(&style_screen_btnm_1_main_items_default, lv_color_make(0x00, 0x00, 0x00));lv_style_set_text_font(&style_screen_btnm_1_main_items_default, &ui_font_Cuyuan20);lv_obj_add_style(ui->screen_btnm_1, &style_screen_btnm_1_main_items_default, LV_PART_ITEMS|LV_STATE_DEFAULT);

3.2.3 按钮初始化

ui_CompageBackBtn=lv_btn_create(ui->screen);lv_obj_align(ui_CompageBackBtn,LV_ALIGN_RIGHT_MID,-10,-90);lv_obj_set_width(ui_CompageBackBtn,40);lv_obj_set_height(ui_CompageBackBtn,30);lv_obj_set_style_bg_color(ui_CompageBackBtn,lv_color_make(0xAD, 0xD8, 0xE6), LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_set_style_bg_opa(ui_CompageBackBtn, LV_OPA_COVER, LV_PART_MAIN | LV_STATE_DEFAULT); // 设置背景不透明lv_obj_t * btnlabel = lv_label_create(ui_CompageBackBtn);lv_label_set_text(btnlabel, LV_SYMBOL_BACKSPACE);lv_obj_set_style_text_font(btnlabel, &lv_font_montserrat_24, 0);lv_obj_center(btnlabel);

3.2.4 按钮事件回调以及计算器逻辑

#define TEXT_FULL 100StrStack_t CalStr;
NumStack_t NumStack;
SymStack_t SymStack;
extern  lv_obj_t * ui_CompageBackBtn;static void my_event_handle(lv_event_t* e)
{lv_event_code_t code = lv_event_get_code(e);lv_obj_t * obj = lv_event_get_target(e);if (code == LV_EVENT_DRAW_PART_BEGIN){lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);if (dsc->id == 3 || dsc->id == 7 || dsc->id == 11 || dsc->id == 14 || dsc->id == 15){dsc->rect_dsc->radius = LV_RADIUS_CIRCLE;if (lv_btnmatrix_get_selected_btn(obj) == dsc->id){dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE, 3);// lv_btnmatrix_set_selected_btn(ui_CompageBtnM, NULL);}else{dsc->rect_dsc->bg_color = lv_palette_main(LV_PALETTE_BLUE);}}}if (code == LV_EVENT_DRAW_PART_END){lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);}if (code == LV_EVENT_VALUE_CHANGED){uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj); // 获取当前选中的按键的idconst char * txt = lv_btnmatrix_get_btn_text(obj, btn_id); // 获取当前按键的文本if (txt != NULL){if (guider_ui.screen_ta_1 != NULL){if (lv_textarea_get_cursor_pos(guider_ui.screen_ta_1) <= TEXT_FULL){lv_textarea_add_text(guider_ui.screen_ta_1, txt); // 文本框追加字符switch (btn_id){case 0:strput(&CalStr, '1');break;case 1:strput(&CalStr, '2');break;case 2:strput(&CalStr, '3');break;case 3:strput(&CalStr, '+');break;case 4:strput(&CalStr, '4');break;case 5:strput(&CalStr, '5');break;case 6:strput(&CalStr, '6');break;case 7:strput(&CalStr, '-');break;case 8:strput(&CalStr, '7');break;case 9:strput(&CalStr, '8');break;case 10:strput(&CalStr, '9');break;case 11:strput(&CalStr, '*');break;case 12:strput(&CalStr, '.');break;case 13:strput(&CalStr, '0');break;case 14:strput(&CalStr, '=');lv_textarea_add_text(guider_ui.screen_ta_1, "\n");strput(&CalStr, '\n');break;case 15:strput(&CalStr, '/');break;}}}}if (lv_btnmatrix_get_selected_btn(obj) == 14){// calculateif (StrCalculate(CalStr.strque, &NumStack, &SymStack)){lv_textarea_add_text(guider_ui.screen_ta_1, "erro");}else{char strout[10];if (isIntNumber(NumStack.data[NumStack.Top_Point - 1])){sprintf(strout, "%.0f", NumStack.data[NumStack.Top_Point - 1]);}else{sprintf(strout, "%.4f", NumStack.data[NumStack.Top_Point - 1]);}lv_textarea_add_text(guider_ui.screen_ta_1, strout);}strclear(&CalStr);lv_obj_clear_flag(guider_ui.screen_btnm_1, LV_OBJ_FLAG_CLICKABLE);}}
}void ui_CompageBackBtn_event_cb(lv_event_t * e)
{lv_event_code_t code = lv_event_get_code(e);lv_obj_t * obj = lv_event_get_target(e);if(code == LV_EVENT_CLICKED){if (guider_ui.screen_ta_1 != NULL){if(!strstack_isEmpty(&CalStr)){lv_textarea_del_char(guider_ui.screen_ta_1);strdel(&CalStr);}else{int i = 0;for (i = 0; i < (TEXT_FULL*2); i++){lv_textarea_del_char(guider_ui.screen_ta_1);}lv_obj_add_flag(guider_ui.screen_ta_1,LV_OBJ_FLAG_CLICKABLE);}}}if(code == LV_EVENT_LONG_PRESSED){if (guider_ui.screen_ta_1 != NULL){if(!strstack_isEmpty(&CalStr)){strclear(&CalStr);int i = 0;for (i = 0; i < (TEXT_FULL*2); i++){lv_textarea_del_char(guider_ui.screen_ta_1);}}lv_obj_add_flag(guider_ui.screen_btnm_1,LV_OBJ_FLAG_CLICKABLE);}}
}void custom_init(lv_ui *ui)
{strclear(&CalStr);NumStackClear(&NumStack);SymStackClear(&SymStack);lv_obj_add_event_cb(ui->screen_btnm_1, my_event_handle, LV_EVENT_ALL, ui);lv_obj_add_event_cb(ui_CompageBackBtn, ui_CompageBackBtn_event_cb, LV_EVENT_ALL, NULL);}

四、总结

这个计算器涉及了多个部件以及计算器管理的算法,个人觉得非常有意思,大家可以跟着讲解来进行复刻,需要模拟器工程的,在评论区留下邮箱即可。

相关文章:

LVGL实战训练——计算器实现

目录 一、简介 二、部件知识 2.1 按钮矩阵部件(lv_btnmatrix) 2.1.1 按钮矩阵部件的组成 2.1.2 按钮文本设置 2.1.3 按钮索引 2.1.4 按钮宽度 2.1.5 按钮属性 2.1.6 按钮互斥 2.1.7 按钮文本重着色 2.1.8 按钮矩阵部件的事件 2.1.9 按钮矩阵部件的 API 函数 2.2…...

代码随想录算法训练营Day30

力扣452.用最少数量的箭引爆气球【medium】 力扣435.无重叠区间【medium】 力扣763.划分字母区间【medium】 力扣56.合并区间【medium】 一、力扣452.用最少数量的箭引爆气球【medium】 题目链接&#xff1a;力扣452.用最少数量的箭引爆气球 视频链接&#xff1a;代码随想录 题…...

AIDL 语言简介

目录 软件包类型注释导入AIDL 的后端AIDL 语言大致上基于 Java 语言。AIDL 文件不仅定义了接口本身,还会定义这个接口中用到的数据类型和常量。 软件包 每个 AIDL 文件都以一个可选软件包开头,该软件包与各个后端中的软件包名称相对应。软件包声明如下所示: package my.pac…...

经典算法 判断一个图中是否有环

判断一个图中是否有环 问题描述 给一个以0 0结尾的整数对列表&#xff0c;除0 0外的每两个整数表示一条连接了这两个节点的边。假设节点编号不超过100000大于0。你只要判断由这些节点和边构成的图中是否存在环。存在输出YES&#xff0c;不存在输出NO。 输入样例1 6 8 5 3 …...

Transformer-PyTorch实战项目——文本分类

Transformer-PyTorch实战项目——文本分类 ———————————————————————————————————————————— 【前言】 这篇文章将带领大家使用Hugging Face里的模型进行微调&#xff0c;并运用在我们自己的新项目——文本分类中。需要大家提前下…...

Linux-服务器负载评估方法

在 Linux 服务器中&#xff0c;top 命令显示的 load average&#xff08;平均负载&#xff09;反映了系统在特定时间段内的负载情况。它通常显示为三个数值&#xff0c;分别代表过去 1 分钟、5 分钟和 15 分钟的平均负载。 1. 什么是 Load Average&#xff1f; Load average …...

Transformer编程题目,结合RTX 3060显卡性能和市场主流技术

以下是10道针对4年经验开发者的Transformer编程题目&#xff0c;结合RTX 3060显卡性能和市场主流技术&#xff0c;每题包含模型选择和实现逻辑描述&#xff1a; 题目1&#xff1a;医疗报告结构化提取 模型选择&#xff1a;BioBERT-base 要求&#xff1a; 开发从PDF医疗报告中提…...

Web三漏洞学习(其二:sql注入)

靶场&#xff1a;NSSCTF 、云曦历年考核题 二、sql注入 NSSCTF 【SWPUCTF 2021 新生赛】easy_sql 这题虽然之前做过&#xff0c;但为了学习sql&#xff0c;整理一下就再写一次 打开以后是杰哥的界面 注意到html网页标题的名称是 “参数是wllm” 那就传参数值试一试 首先判…...

VLAN的知识

1.什么是VLAN&#xff1f; VLAN是虚拟局域网&#xff0c;逻辑隔离广播域和网络区域 是一种通过将局域网内的设备逻辑地划分为一个个网络的技术 2.对比逻辑网络分割和物理网络分割&#xff1f; 逻辑网络分割是VLAN&#xff0c;隔离广播域和网络区域 物理网络分割是路由&…...

RFID 赋能部队智能物联网仓储建设:打造信息化高效解决方案

在当今军事现代化进程的宏大背景下&#xff0c;部队后勤保障工作无疑占据着举足轻重的地位&#xff0c;而仓储管理作为其中的核心环节&#xff0c;更是至关重要。传统的仓储管理模式在面对当下物资种类繁杂、数量庞大的现状时&#xff0c;已显得力不从心&#xff0c;效率低下、…...

结构型屏蔽在高频电子设备中的应用与优化

在当今高度电子化的时代&#xff0c;随着电子产品工作频率不断提高&#xff0c;设备内部温度上升&#xff0c;电磁环境日趋复杂&#xff0c;电磁兼容&#xff08;EMC&#xff09;问题成为设计和制造过程中必须重点解决的问题。EMC不仅关系到设备自身的稳定运行&#xff0c;更涉…...

【教程】Ubuntu修改ulimit -l为unlimited

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 问题描述 解决方法一 解决方法二 解决方法三 (终极) 问题描述 查系统资源限制 ulimit -l如果返回的是 64 或其他较小值&#xff0c;那么RDM…...

【HDFS】BlockPlacementPolicyRackFaultTolerant#getMaxNode方法的功能及具体实例

方法参数说明: numOfChosen:已经选择的节点数numOfReplicas:还需要选择的副本数方法的返回值是一个长度为2的数组:[调整后的要选出多少个节点(不包括已经选择的), 每个机架最大能选择的节点数] @Overrideprotected int[] getMaxNodesPerRack(int numOfChosen, int numOfR…...

水污染治理(生物膜+机器学习)

文章目录 **1. 水质监测与污染预测****2. 植物-微生物群落优化****3. 系统设计与运行调控****4. 维护与风险预警****5. 社区参与与政策模拟****挑战与解决思路****未来趋势** 前言&#xff1a; 将机器学习&#xff08;ML&#xff09;等人工智能技术融入植树生物膜系统&#xff…...

数模小白变大神的日记2025.4.15日

分工 1.论文:mathtype (Latex) 2.建模&#xff1b;相应的建模知识与撰写方法&#xff0c;写摘要 3.编程:matlab、SPSs、(Python) 评价模型 1. 层次分析法 ①层次分析法是一种多目标、多准则的决策问题 ②层次分析法是一种主观加权法 ③层次分析法通过以下步骤实现: 1.构…...

STM32提高篇: 以太网通讯

STM32提高篇: 以太网通讯 一.以太网通讯介绍二.W5500芯片介绍1.W5500芯片特点2.W5500应用目标3.接入框图 三.驱动移植四.tcp通讯五.udp通讯六.http_server 一.以太网通讯介绍 以太网&#xff08;Ethernet&#xff09;是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以…...

4-15记录(冒泡排序,快速选择排序)

算法稳定 简单选择排序的实质就是最后一个和第一个比较&#xff0c;小&#xff0c;就换位置&#xff0c;然后继续用最后一个数字和第二个比较&#xff0c;以此类推。 但是算法不稳定&#xff0c;本来下划线的2在后面&#xff0c;但是经过算法后去了前面 快速排序 实现过程&am…...

Ubuntu系统18.04更新驱动解决方法

原始是&#xff1a;ubuntu18.04里面的驱动是470&#xff0c;对应cuda11.4 现在需要更新为525&#xff0c;对应cuda为12.0 实现&#xff1a; 1、打开终端 Ctrl Alt T2、使用 lspci 命令&#xff08;快速查看显卡型号&#xff09; lspci | grep -i vga3、终端输入 ubuntu-d…...

Rocky Linux 9.x 基于 kubeadm部署k8s

搭建集群使用docker下载K8s&#xff0c;使用一主两从模式 主机名IP地址k8s- master192.168.1.141k8s- node-1192.168.1.142k8s- node-2192.168.1.143 一&#xff1a;准备工作 VMware Workstation Pro新建三台虚拟机Rocky Linux 9&#xff08;系统推荐最小化安装&#xff09; …...

MATLAB程序实现了一个物流配送优化系统,主要功能是通过遗传算法结合四种不同的配送策略,优化快递订单的配送方案

%% 主函数部分 % function main()clear; clc; close all;% 生成或加载算例 filename = D:\快递优化\LogisticsInstance.mat; if ~exist(filename, file)instance = generate_instance();save(filename, -struct, instance); elseinstance = load(filename); end% 遗传算法参数配…...

利用宝塔面板搭建RustDesk服务

一、介绍 1.1官网 https://rustdesk.com/ 1.2github仓库 https://github.com/rustdesk/rustdesk 1.3特点 RustDesk 支持多种操作系统&#xff0c;包括 Windows、macOS、Linux、Android 和 iOS。它甚至提供网页版客户端&#xff0c;可以在浏览器中直接使用。 用户可以通过…...

前端与Java后端交互出现跨域问题的14种解决方案

跨域问题是前端与后端分离开发中的常见挑战&#xff0c;以下是14种完整的解决方案&#xff1a; 1 前端解决方案( 开发环境代理) 1.1 Webpack开发服务器代理 // vue.config.js 或 webpack.config.js module.exports {devServer: {proxy: {/api: {target: http://localhost:8…...

PBKDF2全面指南(SpringBoot实现版)

文章目录 第一部分:PBKDF2基础概念1. 什么是PBKDF2?2. 为什么需要PBKDF2?3. PBKDF2的工作原理4. PBKDF2与其他密码散列函数的比较第二部分:在Java和SpringBoot中使用PBKDF21. Java内置的PBKDF2支持2. SpringBoot中集成PBKDF22.1 添加依赖2.2 配置PBKDF2密码编码器2.3 自定义…...

基于RV1126开发板的rknn-toolkit-lite使用方法

1. rknn-toolkit-lite介绍 rknn-toolkit-lite是用于python算法的推理的组件&#xff0c;当前已经在EASY-EAI-Nano完成适配&#xff0c;用户可以用它进行深度学习算法的纯python开发。而且同时支持已经进行了预编译的模型&#xff0c;短短几行代码即可完成算法的推理&#xff0c…...

一款轻量级的PHP地址发布页面源码

源码介绍 一款轻量级的PHP链接发布页面源码&#xff0c;适合快速搭建个性化的链接导航网站&#xff0c;支持动态链接管理和多种风格模板切换 1&#xff1a;后台登录地址为/admin/login.php&#xff0c;提供便捷的配置入口。 2&#xff1a;默认用户名是admin&#xff0c;密码为…...

分布式计算领域的前沿工具:Ray、Kubeflow与Spark的对比与协同

在当今机器学习和大数据领域&#xff0c;分布式计算已成为解决大规模计算问题的关键技术。本文将深入探讨三种主流分布式计算框架——Ray、Kubeflow和Spark&#xff0c;分析它们各自的特点、应用场景以及如何结合它们的优势创建更强大的计算平台。 Spark批量清洗快&#xff0c;…...

【专题刷题】双指针(一)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;1&#xff0c;本人解法 本人屎山代码&#xff1b;2&#xff0c;优质解法 优质代码&#xff1b;3&#xff0c;精益求精&#xff0c;…...

火山引擎旗下防御有哪些

首先&#xff0c;我需要确认用户是不是打错了&#xff0c;比如把“引擎”当成了“云”&#xff0c;或者他们真的想了解火山引擎的防御机制。火山引擎是字节跳动旗下的云服务平台&#xff0c;类似于阿里云或腾讯云&#xff0c;所以用户可能想了解的是其安全防护措施。 接下来&am…...

python程序打包——nuitka使用

目前python打包成exe的工具主要有&#xff1a;PyInstaller Briefcase py2exe py2app Nuitka CX_Freeze等。 不同于C代码&#xff0c;可以直接编译成可执行的exe文件&#xff0c;或者js代码在浏览器中就能执行&#xff0c;python代码必须通过python解释器来运行&#xff0c…...

编写了一个专门供强化学习玩的贪吃蛇小游戏,可以作为后续学习的playgraound

文章目录 **试玩效果****项目背景****核心设计思路****代码亮点解析****与强化学习算法的对接示例****扩展方向****总结****完整代码**把训练一个会玩小游戏的智能体,作为学习强化学习的一个目标,真的是很有乐趣的一件事。我已经不知为此花费了多少日夜了。如今已是着魔了一般…...

chain_type=“stuff 是什么 ? 其他方式有什么?

chain_type="stuff 是什么 ? 其他方式有什么? 目录 chain_type="stuff 是什么 ? 其他方式有什么?1. `chain_type="stuff"`2. `chain_type="map_reduce"`3. `chain_type="refine"`4. `chain_type="map_rerank"`在 LangCh…...

在IDEA里面建立maven项目(便于java web使用)

具体步骤&#xff1a; 第一次有的电脑你再创建项目的时候右下角会提醒你弹窗&#xff1a;让你下载没有的东西 一定要下载&#xff01;&#xff01;可能会很慢 运行结果&#xff1a; 因为他是默认的8080端口所以在运行的时候输入的url如下图&#xff1a; 新建了一个controller代…...

MyBatis 详解

1. 什么是 MyBatis&#xff1f; MyBatis 是一款优秀的 持久层框架&#xff0c;它通过 XML 或注解配置&#xff0c;将 Java 对象&#xff08;POJO&#xff09;与数据库操作&#xff08;SQL&#xff09;进行灵活映射&#xff0c;简化了 JDBC 的复杂操作。 核心思想&#xff1a;S…...

郑州工程技术学院党委书记甘勇一行莅临埃文科技调研交流

为深化产教融合、推动人工智能领域人才培养与产业需求精准对接&#xff0c;2025年4月9日下午&#xff0c;郑州工程技术学院党委书记甘勇、河南省人工智能产业创新发展联盟执行秘书长孟松涛等一行莅临埃文科技调研交流。 一、聚焦技术前沿 共话AI产业变革 座谈会上&#xff0c;…...

AI应用开发之扣子第一课-夸夸机器人

首先&#xff0c;进入官网&#xff1a;点击跳转至扣子。 1.创建智能体 登录进网站后&#xff0c;点击左上角&#xff0b;图标&#xff0c;创建智能体&#xff0c;输入智能体名称、功能介绍 2.输入智能体提示词 在“人设与回复逻辑”输入以下内容&#xff1a; # 角色 你是一…...

Node.js 数据库 CRUD 项目示例

希望使用Nodejs操作数据库做CRUD&#xff0c;用deepseek实战搜索“使用Nodejs对数据库表做CRUD的项目例子”&#xff0c;找到了解决方案&#xff0c;如下图所示&#xff1a; 项目结构 nodejs-crud-example/ ├── config/ │ └── db.js # 数据库连接配置 ├──…...

ESP8266/32作为AVR编程器(ISP programmer)的使用介绍

ESP8266作为AVR编程器( ISP programmer)的使用介绍 &#x1f33f;ESP8266自带库例程&#xff1a;https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266AVRISP&#x1f4cd;支持ESP8266/32的ESP_AVRISP其它开源工程&#xff08;个人没有再去验证&#xff09;&…...

union all几个常见问题及其解决方案

UNION ALL 是 SQL 中用于合并两个或多个 SELECT 语句结果集的操作符。与 UNION 不同&#xff0c;UNION ALL 不会去除重复的记录&#xff0c;它简单地将一个查询的结果附加到另一个查询的结果之后。尽管 UNION ALL 相对来说更高效&#xff08;因为它不需要检查重复项&#xff09…...

21.C++11

1.列表初始化 1.1C11中的{} •C11以后想统⼀初始化⽅式&#xff0c;试图实现⼀切对象皆可⽤{}初始化&#xff0c;{}初始化也叫做列表初始化。 • 内置类型⽀持&#xff0c;⾃定义类型也⽀持&#xff0c;⾃定义类型本质是类型转换&#xff0c;中间会产⽣临时对象&#xff0c;最…...

【交叉编译】目标机编译安装对应依赖库总结

1、解压目标机交叉编译工具链 # 创建工具链存放目录&#xff08;可选&#xff09; sudo mkdir -p /opt/toolchain# 解压到目标路径&#xff08;示例路径&#xff1a;/opt/toolchain&#xff09; sudo tar -xzvf 目标主机编译工具链.tar.gz -C /opt/toolchain# 查看解压后的目录…...

Docker华为云创建私人镜像仓库

Docker华为云创建私人镜像仓库 在华为云官网的 产品 中搜索 容器镜像服务 &#xff1a; 或者在其他页面的搜索栏中搜索 容器镜像服务 &#xff1a; 进入到页面后&#xff0c;点击 创建组织 &#xff08;华为云的镜像仓库称为组织&#xff09;&#xff1a; 设置组织名字后&…...

【15】数据结构之基于树的查找算法篇章

目录标题 二叉排序树 Binary Sort Tree二叉排序树的插入二叉树排序树的删除二叉排序树的查找二叉排序树的调试与代码集合 平衡二叉树-AV树平衡二叉树的平衡化旋转平衡二叉树的代码调试与代码集合 B树&#xff22;树的查找B树的插入B树和B*树 二叉排序树 Binary Sort Tree 二叉…...

自定义类型之结构体

1.结构体类型概述 结构体类型是一种用户自定义的数据类型&#xff0c;用于将不同类型的数据组合成一个整体。在C语言中&#xff0c;结构体使用struct关键字定义&#xff0c;由一系列具有相同类型或不同类型的数据构成的数据集合&#xff0c;也称为结构。结构体中的数据在逻辑上…...

SGFormer:卫星-地面融合 3D 语义场景补全

论文介绍 题目&#xff1a;SGFormer: Satellite-Ground Fusion for 3D Semantic Scene Completion 会议&#xff1a;IEEE / CVF Computer Vision and Pattern Recognition Conference 论文&#xff1a;https://www.arxiv.org/abs/2503.16825 代码&#xff1a;https://githu…...

应急响应篇钓鱼攻击邮件与文件EML还原蠕虫分析线索定性处置封锁

钓鱼邮件的eml中会有 邮件服务器地址域名&#xff08;发信人&#xff09;发送的本地IP和主机名发送的内容以及附件 邮件钓鱼&#xff1a; 攻击者目的&#xff1a;通过发信人&#xff0c;附件&#xff0c;取得突破 定性钓鱼邮件 威胁情报&#xff0c;人工分析来源&#xff0c…...

利用纯JS开发浏览器小窗口移动广告小功能

效果展示 直接上代码 如果要用到vue项目里面&#xff0c;直接按照vue的写法改动就行&#xff0c;一般没有多大的问题&#xff0c;顶部的占位是我项目需求&#xff0c;你可以按照要求改动。 <!DOCTYPE html> <html> <head><meta charset"utf-8"…...

java Stream流

Stream流 双列集合无法直接使用stream流&#xff0c;可以通过keyset&#xff08;&#xff09;或enteyset转换为单列集合&#xff0c;再进行操作 1.单列集合 package mystream;import java.util.ArrayList; import java.util.Collections;public class StreamDemo1 {public sta…...

【实战中提升自己】 防火墙篇之VPX部署–L2TP over IPSEC

1 VPx部署【L2TP Over ipsec】 说明&#xff1a;在VPX上面&#xff0c;我们希望与分部建立VPX&#xff0c;保证与分部的财务部正常通信&#xff0c;另外还提供L2TP Over ISPEC功能&#xff0c;方便远程接入访问内部服务器等。当然我们也可以做详细的控制&#xff…...

贪心算法(20)(java)整数替换

给定一个正整数 n &#xff0c;你可以做如下操作&#xff1a; 如果 n 是偶数&#xff0c;则用 n / 2替换 n 。如果 n 是奇数&#xff0c;则可以用 n 1或n - 1替换 n 。 返回 n 变为 1 所需的 最小替换次数 。 示例 1&#xff1a; 输入&#xff1a;n 8 输出&#xff1a;3 解…...

实验二.单按键控制LED

1.实验任务 如图4.1所示:在P0.0端口上接一个发光二极管L1,按键按一下灯亮,在按一下灯灭。 2.电路原理图 3.系统板上硬件连线 把“单片机系统”区域中的P0端口用导线连接到“八路发光二极管指示模块”区域中的L1端口上。 4.程序设计内容...