【应用笔记】Cot Menu 轻量级多级菜单控制框架程序(C语言)

【应用笔记】Cot Menu 轻量级多级菜单控制框架程序(C语言)

前言: 工作需要, 实现一个串口打印的类shell菜单. 如果按照以往的习惯我会自己重新"构思"(狗屎)一个菜单框架.之前用oled和lcd时,我都从零重复造轮子.
作为一个成熟的程序员, 应该要学会使用前人大佬总结的框架库,既能学习也能省时间.
经过ai搜索,最终找到一个今年年初(2024年1月)刚发布的轻量级框架.经过一方学习后感觉很不错,故此写下笔记.

一、准备工作

  • 可以使用vc++6.0测试,也可以使用任意单片机测试,写好的代码是基本通用的.

Vc++安装包_Visual C++ 6.0中文版安装包下载及win11安装教程
https://blog.csdn.net/weixin_46274254/article/details/123255025

  • 推荐在线思维导图工具和在线ai搜索工具;

在线思维导图
https://www.jyshare.com/more/kitymind/index.html
智谱清言
https://chatglm.cn/main/alltoolsdetail?lang=zh

  • 下载源码,作者仓库里还有其他轻量级库.

gitee仓库 cot软件包/cotMenu
https://gitee.com/cot_package/cot_menu
轻量级多级菜单控制框架程序(C语言)
https://blog.csdn.net/qq_24130227/article/details/121167276
用C语言写一个耦合性低、完全可移植的轻量级菜单框架
https://blog.csdn.net/2401_82584055/article/details/139414585

二、介绍

  • cotmenu库下载后打开,里面只有一个例程examples和库的本体cot_menu.c, cot_menu.h.
  • 本体只有2个文件,总共不超过1000行代码. 极简-轻量级.
  • 本身只实现了对菜单的控制,比如多级菜单的进入和退出,不包含菜单按键的输入和处理,还有菜单界面的定义和输出等等.
  • 所以它既可以使用按键,串口,窗口作为输入,也可以使用屏幕,串口,窗口作为输出.这部分代码需要自己实现,库中只提供了一个指针.
  • 文件夹里包含一个examples文件夹,是在电脑端运行的例程.大致演示了效果.

三、实践

  • 第一步.先创建一个工程,然后添加库,编译运行,没有报错.
    在这里插入图片描述
  • 唯一需要注意的就是那几个c语言通用库;
// #include "stdint.h" // 这个库是为了定义数据类型,如果找不到可以直接替换成下面这些
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;// #include <stdbool.h> // 这个库是为了定义bool,如果找不到可以直接使用枚举自己定义.
typedef enum Bool{false = 0,true = 1
}bool;// 其他遇到再说...

1.规划多级菜单

  • 建议使用图表或是思维导图,随意罗列一下.假设我需要实现以下菜单结构.
    在这里插入图片描述

2.准备文本

  • 无论是使用串口打印,还是屏幕显示,都需要先准备好文本,条件允许还需要中英文双语可以选择.例程cot_menu-master\examples\language中就示例.其中使用到了枚举+数组的指定索引初始化的方法,我还是第一次见,我一直以为数组是不支持乱序初始化,没想到能采用这种语法.
  • 有点小尴尬的是在vc6.0中编译报错,应该不支持这种语法.在iar的单片机工程中倒是正常编译的.
  • 注意到我把英文放在0位,中文放在1位,是为了能代码对齐,所以把中文字样放一行的最后.
// A_language.c 文件内容如下
#include "A_language.h"// 局部变量 语言类型
static SystemLanguage_e sg_eSystemLanguage = SYSTEM_LANGUAGE_ENGLISH;// 局部数组 语言文本
const char *(sg_kSystemLanguage[TEXT_ALL])[SYSTEM_LANGUAGE_ALL] =
{/*[TEXT_MENU]     = */{"Main menu", "主菜单"},/*[TEXT_MENU1A]   = */{"L1A menu", "一级菜单A"},/*[TEXT_MENU1B]   = */{"L1B menu", "一级菜单B"},/*[TEXT_MENU1C]   = */{"L1C menu", "一级菜单C"},/*[TEXT_MENU1D]   = */{"L1D menu", "一级菜单D"},/*[TEXT_MENU2A]   = */{"L2A menu", "二级菜单A"},/*[TEXT_MENU2B]   = */{"L2B menu", "二级菜单B"},/*[TEXT_MENU2C]   = */{"L2C menu", "二级菜单C"},/*[TEXT_MENU2D]   = */{"L2D menu", "二级菜单D"},/*[TEXT_MENU3A]   = */{"L3A menu", "三级菜单A"},/*[TEXT_MENU3B]   = */{"L3B menu", "三级菜单B"},/*[TEXT_SELECT_OPTION]    = */{"select option",     "选择操作"},/*[TEXT_ENTER]            = */{"enter",             "进入"},/*[TEXT_EXIT]             = */{"exit",              "退出"},/*[TEXT_NEXT]             = */{"next",              "下一个"},/*[TEXT_PREVIOUS]         = */{"previous",          "上一个"},
};// 设置当期语言类型
void set_language(SystemLanguage_e lang)
{if (lang >= 0 && lang < SYSTEM_LANGUAGE_ALL){sg_eSystemLanguage = lang;}
}// 获取当前语言类型
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id)
{static const char *pszNullString = "N/A";if (id >= 0 && id < TEXT_ALL){return sg_kSystemLanguage[id][lang];}return pszNullString; // 未找到对应的文本
}// 获取文本
const char *get_text(TextId_e id)
{static const char *pszNullString = "N/A";if (id >= 0 && id < TEXT_ALL){return sg_kSystemLanguage[id][sg_eSystemLanguage];}return pszNullString; // 未找到对应的文本
}
// A_language.h 文件内容如下
#ifndef LANGUAGE_H
#define LANGUAGE_H// 声明全局枚举
typedef enum
{SYSTEM_LANGUAGE_ENGLISH = 0,SYSTEM_LANGUAGE_CHINESE,SYSTEM_LANGUAGE_ALL,
} SystemLanguage_e;typedef enum
{TEXT_MENU = 0,TEXT_MENU1A,TEXT_MENU1B,TEXT_MENU1C,TEXT_MENU1D,TEXT_MENU2A,TEXT_MENU2B,TEXT_MENU2C,TEXT_MENU2D,TEXT_MENU3A,TEXT_MENU3B,TEXT_SELECT_OPTION,TEXT_ENTER,TEXT_EXIT,TEXT_NEXT,TEXT_PREVIOUS,TEXT_ALL,
} TextId_e;// 声明全局函数
extern void set_language(SystemLanguage_e lang);    // 设置当期语言类型
extern const char *get_text(TextId_e id);           // 获取文本
extern const char *get_text_by_language(SystemLanguage_e lang, TextId_e id); // 获取当前语言类型#endif
  • 然后编译没有问题
    在这里插入图片描述

3.分析框架

  • 打开cotmenu.h,开头是c语言头文件导入,略过.然后是宏定义配置项. 只有4个,原本注释也得明明白白了.可以右键搜索哪里使用.是数组长度的定义和函数的定义.
/******************************************* 配置项 ********************************************************************//* 定义 _COT_MENU_USE_MALLOC_ 则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 */
// #define _COT_MENU_USE_MALLOC_/* 定义 _COT_MENU_USE_SHORTCUT_ 则启用快捷菜单选项进入功能 */
#define _COT_MENU_USE_SHORTCUT_/* 多级菜单深度 */
#define COT_MENU_MAX_DEPTH              10/* 菜单支持的最大选项数目 */
#define COT_MENU_MAX_NUM                20/******************************************* 配置项 ********************************************************************/
  • 然后直接跳到最后,看函数的定义. 我大致进行如下分类.

在这里插入图片描述

/* Exported functions ------------------------------------------------------------------------------------------------*//* 菜单初始化和反初始化 */extern int cotMenu_Init(cotMainMenuCfg_t *pMainMenu);
extern int cotMenu_DeInit(void);extern int cotMenu_Bind(cotMenuList_t *pMenuList, menusize_t menuNum, cotShowMenuCallFun_f pfnShowMenuFun);/* 菜单选项显示时需要使用的功能扩展函数 */extern int cotMenu_LimitShowListNum(cotMenuShow_t *ptMenuShow, menusize_t *pShowNum);
extern int cotMenu_QueryParentMenu(cotMenuShow_t *ptMenuShow, uint8_t level);/* 菜单操作 */extern int cotMenu_MainEnter(void);
extern int cotMenu_MainExit(void);extern int cotMenu_Reset(void);
extern int cotMenu_Enter(void);
extern int cotMenu_Exit(bool isReset);
extern int cotMenu_SelectPrevious(bool isAllowRoll);
extern int cotMenu_SelectNext(bool isAllowRoll);
extern int cotMenu_Select(menusize_t selectItem);extern int cotMenu_ShortcutEnter(bool isAbsolute, uint8_t deep, ...);/* 菜单轮询处理任务 */extern int cotMenu_Task(void);
  • 举个简单例子, 一开始使用框架先需要列出如下内容.
  • 要使用框架就先初始化, 然后进入菜单, 就可以使用菜单, 不需要时退出, 去初始化, 一条龙标准化流程.
#include <stdio.h> // printf#include "A_language.h" // txt
#include "cot_menu.h" // menustatic cotMainMenuCfg_t sg_tMainMenu = {NULL, NULL, NULL, NULL, NULL}; // 主菜单对象int main (void)
{printf("你好 世界\n");cotMenu_Init(&sg_tMainMenu);    // 初始化cotMenu_MainEnter();            // 进入cotMenu_Task();     			// 使用cotMenu_MainExit();             // 退出cotMenu_DeInit();               // 去除初始化return 0;
}

4.菜单对象

  • 注意到初始化时需要一个菜单对象(结构体)cotMainMenuCfg_t / cotMenuList_t.定义了2个名字,代表主菜单对象其实和子菜单对象是一样的内容格式.
  • 第一个uMenuDesc一般是文本,就是上面定义的中英文, 使用了共用体cotMenuDsecStr_u,具体存数和取数都是自己定义.
  • 最后一个pExtendData一般是一个自定义结构体指针,用来传递所有想传递的东西.也是具体存数和取数都是自己定义.
  • 中间的几个都回调函数,在不同情况下会调用.
  • 进入回调 pfnEnterCallFun, 就是在 cotMenu_MainEntercotMenu_Enter中调用;
  • 退出回调 pfnExitCallFun, 就是在 cotMenu_MainExitcotMenu_Exit中调用;
  • 加载回调 pfnLoadCallFun, 就是在cotMenu_Task轮询内的开头调用, 且只调用一次;
  • 轮询回调 pfnRunCallFun, 就是在cotMenu_Task轮询内的结尾调用, 且每次都会调用;
  • 可以进入上面几个函数中查看具体实现了什么内容.这里不啰嗦了.
  • 还有一个需要额外注册的函数,也是最重要的显示菜单函数,
  • 显示回调,pfnShowMenuFun,在cotMenu_Task轮询内的中间调用, 且每次都会调用;
  • 如果不额外赋值的话,就会沿用上一个显示回调, 在cotMenu_MainEntercotMenu_Enter中实现.
/*** @brief 菜单信息注册结构体* */
typedef struct
{cotMenuDsecStr_u     uMenuDesc;        /*!< 当前菜单的描述 */cotMenuCallFun_f     pfnEnterCallFun;  /*!< 当前菜单选项进入时(从父菜单进入)需要执行一次的函数, 为NULL不执行 */cotMenuCallFun_f     pfnExitCallFun;   /*!< 当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数, 为NULL不执行 */cotMenuCallFun_f     pfnLoadCallFun;   /*!< 当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数, 为NULL不执行 */cotMenuCallFun_f     pfnRunCallFun;    /*!< 当前菜单选项的周期调度函数 */void                *pExtendData;      /*!< 当前选项的菜单显示效果函数扩展数据入参, 可自行设置该内容 */
} cotMenuList_t, cotMainMenuCfg_t;

5.回调函数

  • 如果有具体想执行的内容, 回调函数可以直接写空NULL,我这里为了演示都写一下.
  • 注意,注册函数cotMenu_Bind的对象我填的是0,一会再补上,现在主要是看其他回调函数的使用方法.
// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{
}// 进入时执行一次
void Enter_Main(const cotMenuItemInfo_t *pItemInfo)
{// cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}// 退出时执行一次
void Exit_Main(const cotMenuItemInfo_t *pItemInfo)
{
}// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{
}// 主菜单对象
static cotMainMenuCfg_t sg_tMainMenu = {(void *)TEXT_MENU, // "main menu" // 这个位置是共用体,如果存枚举,取数时就按枚举取,如果存字符串指针,取数时就按字符串指针取数Enter_Main, //Exit_Main, Load_Main, Run_Main}; 

6.轮询函数

  • 打开ccot_menu.c,拉到最后看cotMenu_Task()函数.
  • 可以看到其实主要是就是调用了3个函数.
  • 加载回调pfnLoadCallFun, 显示回调pfnShowMenuFun, 轮询回调pfnRunCallFun.
  • 其中显示回调会先拷贝子菜单列表pMenuList,主要就是用来打印显示的.
  • 而这个显示回调pfnShowMenuFun是需要通过调用注册函数cotMenu_Bind进行修改赋值.
int cotMenu_Task(void)
{int i;cotMenuList_t *pMenuList;cotMenuShow_t tMenuShow;if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0){return -1;}if (sg_tMenuManage.pfnLoadCallFun != NULL){cotMenuItemInfo_t tItemInfo;tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;sg_tMenuManage.pfnLoadCallFun(&tItemInfo);sg_tMenuManage.pfnLoadCallFun = NULL;}if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL){pMenuList = sg_tMenuManage.pMenuCtrl->pMenuList;tMenuShow.itemsNum = sg_tMenuManage.pMenuCtrl->itemsNum;tMenuShow.selectItem = sg_tMenuManage.pMenuCtrl->selectItem;tMenuShow.showBaseItem = sg_tMenuManage.pMenuCtrl->showBaseItem;tMenuShow.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;tMenuShow.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;for (i = 0; i < tMenuShow.itemsNum && i < COT_MENU_MAX_NUM; i++){tMenuShow.uItemsListDesc[i] = pMenuList[i].uMenuDesc;tMenuShow.pItemsListExtendData[i] = pMenuList[i].pExtendData;}if (sg_tMenuManage.pMenuCtrl->pfnShowMenuFun != NULL){sg_tMenuManage.pMenuCtrl->pfnShowMenuFun(&tMenuShow);}sg_tMenuManage.pMenuCtrl->showBaseItem = tMenuShow.showBaseItem;}if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL){cotMenuItemInfo_t tItemInfo;tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo);}

7.注册函数

  • 主菜单的内容基本就这些,接下来就是子菜单相关的内容.使用注册函数写入子菜单数据.
  • 子菜单的格式和主菜单的格式是一样的.就不赘述.
  • 总结,注册函数cotMenu_Bind就是写入子菜单数组sg_Menu1_Table, 总长度COT_GET_MENU_NUM(sg_Menu1_Table), 和显示回调函数Show_Main.
/* 一级菜单 */
cotMenuList_t sg_Menu1_Table[] = 
{COT_MENU_ITEM_BIND(TEXT_MENU1A,  NULL, NULL, NULL, NULL, NULL),COT_MENU_ITEM_BIND(TEXT_MENU1B,  NULL, NULL, NULL, NULL, NULL),COT_MENU_ITEM_BIND(TEXT_MENU1C,  NULL, NULL, NULL, NULL, NULL),COT_MENU_ITEM_BIND(TEXT_MENU1D,  NULL, NULL, NULL, NULL, NULL),/* 坑爹的vc6.0可能不支持指定结构体元素初始化,所以可以改成下面的写法.    {(void *)TEXT_MENU1A, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1B, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1C, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1D, NULL, NULL, NULL, NULL, NULL},*/
};
// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{cotMenu_Bind(sg_Menu1_Table, COT_GET_MENU_NUM(sg_Menu1_Table), Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

8.显示回调

  • 如果这里面写的LCD程序,那效果就是显示器菜单,如果写的串口,就是shell菜单.
  • 而且想显示横版还是竖版,亦或是图片都可以自己实现.
  • 下面是个简易的轮询显示.
// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{uint8_t showNum = 3; // 显示最大行数cotMenu_LimitShowListNum(ptShowInfo, &showNum); // 根据设定截取菜单内容printf("\n ------------- %s ------------- \n", get_text((TextId_e)ptShowInfo->uMenuDesc.textId)); // 显示总标题for (int i = 0; i < showNum; i++)   // 轮询显示菜单{menusize_t tmpselect = i + ptShowInfo->showBaseItem; // 计算当前实际行数printf(" %-10s %c \n",                                      // 显示内容get_text((TextId_e)ptShowInfo->uItemsListDesc[tmpselect].textId), // 根据枚举索引取出字符串(tmpselect == ptShowInfo->selectItem) ? ('<') : (' ')); // 箭头}printf("\n"); // 
}
  • 最后如果没有出差错,点击运行就能看到初步效果了.
  • 如果有条件可以进行在线调试,逐步卡断点,看看代码运行顺序和逻辑,因为赋值指针很多,会晕.

在这里插入图片描述

// main.c 文件
#include <stdio.h> // printf#include "A_language.h" // txt
#include "cot_menu.h" // menu// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{uint8_t showNum = 3; // 显示最大行数cotMenu_LimitShowListNum(ptShowInfo, &showNum); // 根据设定截取菜单内容printf("\n ------------- %s ------------- \n", get_text((TextId_e)ptShowInfo->uMenuDesc.textId)); // 显示总标题for (int i = 0; i < showNum; i++)   // 轮询显示菜单{menusize_t tmpselect = i + ptShowInfo->showBaseItem; // 计算当前实际行数printf(" %-10s %c \n",                                      // 显示内容get_text((TextId_e)ptShowInfo->uItemsListDesc[tmpselect].textId), // 根据枚举索引取出字符串(tmpselect == ptShowInfo->selectItem) ? ('<') : (' ')); // 箭头}printf("\n"); // 
}// 进入时执行一次
void Enter_Main(const cotMenuItemInfo_t *pItemInfo)
{// cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}// 退出时执行一次
void Exit_Main(const cotMenuItemInfo_t *pItemInfo)
{
}/* 一级菜单 */
static cotMenuList_t sg_Menu1_Table[] = 
{{(void *)TEXT_MENU1A, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1B, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1C, NULL, NULL, NULL, NULL, NULL},{(void *)TEXT_MENU1D, NULL, NULL, NULL, NULL, NULL},
};// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{cotMenu_Bind(sg_Menu1_Table, COT_GET_MENU_NUM(sg_Menu1_Table), Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{
}// 主菜单对象
static cotMainMenuCfg_t sg_tMainMenu = {(void *)TEXT_MENU, // "main menu" // 这个位置是共用体,如果存枚举,取数时就按枚举取,如果存字符串指针,取数时就按字符串指针取数Enter_Main, //Exit_Main, Load_Main, Run_Main}; int main (void)
{printf("你好 世界\n");cotMenu_Init(&sg_tMainMenu);    // 初始化cotMenu_MainEnter();            // 进入cotMenu_Task();                 // 使用cotMenu_MainExit();             // 退出cotMenu_DeInit();               // 去除初始化return 0;
}

9.按键控制输入

  • 剩下最后一个就是按键输入了, 例程中,将按键输入放在了轮询回调中执行pfnRunCallFun.
  • 这会有个问题, 是先显示完,然后再等待输入,并处理. 按照常规使用,更多的应该是先等待输入,然后根据输入处理,最后输出.
  • 为此我拆分了pfnRunCallFuncotMenu_Task中的调用. 拆分后就可以在前一半执行按键操作,后一般执行打印等其他操作了.
int cotMenu_Task(void)
{// 略...    if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL) // 在加载回调前调用{cotMenuItemInfo_t tItemInfo;tItemInfo.uMenuDesc = 0; // 传入固定空指针, 或者按其他方式区分,tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo); }if (sg_tMenuManage.pfnLoadCallFun != NULL){// 略...}if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL){// 略...}if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL){cotMenuItemInfo_t tItemInfo;tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo);}return 0;
}
  • 完成后就可以开始完善pfnRunCallFun的内容,加入等待按键

偷懒的我直接贴上例程的代码,

// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{int cmd;if (pItemInfo[0].uMenuDesc != 0) // 拆分2部分执行{printf("%s(1-%s; 2-%s; 3-%s; 4-%s(%s): ", get_text(TEXT_SELECT_OPTION), get_text(TEXT_ENTER), get_text(TEXT_EXIT),get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));scanf(" %d", &cmd); // 空格作用是忽略上次的回车}else  // 拆分2部分执行{switch (cmd){case 1:cotMenu_Enter();break;case 2:cotMenu_Exit(false);break;case 3:cotMenu_SelectNext(true);break;case 4:cotMenu_SelectPrevious(true);break;        default:break;    }}
}

上述代码还没写完, 剩下日后如果有兴致再补上,断断续续写了一下午,好累,明天周一还要上班.
不过如果看到这的人, 剩下的我不会应该也能自己领会了吧.

补充

1.0

  • 修改 cotMenu_Select函数,内置保护措施应该写错了.没有正确判断索引,防止越位
/*** @brief      选择指定的菜单选项** @param      selectItem 指定的菜单选项* @return     0,成功; -1,失败*/
int cotMenu_Select(menusize_t selectItem)
{if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0){return -1;}if (sg_tMenuManage.pMenuCtrl->selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum){return -1;}if (selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum) // 保护措施{return -1;}sg_tMenuManage.pMenuCtrl->selectItem = selectItem; // 直接赋值return 0;
}

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

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

相关文章

【机器学习(二)】分类和回归任务-决策树算法-Sentosa_DSML社区版

文章目录 一、算法概念二、算法原理&#xff08;一&#xff09;树的构造&#xff08;二&#xff09;划分选择1、信息增益2、基尼指数3、卡方检验 &#xff08;三&#xff09;停止标准&#xff08;四&#xff09;剪枝处理1、预剪枝2、后剪枝 三、决策树的优缺点四、决策树分类任…

ai扩图使用什么软件?无损扩图用这5个

你们知道ai扩图是什么吗&#xff1f;其实就是利用人工智能技术对图片进行无损放大处理&#xff0c;让低分辨率的图片变得清晰。通常在图像处理、设计和摄影领域尤为实用。 那么&#xff0c;你们知道ai扩图在线工具怎么选吗&#xff1f;别急&#xff0c;下面这篇文章分享5个超好…

11Python的Pandas:可视化

Pandas本身并没有直接的可视化功能&#xff0c;但它与其他Python库&#xff08;如Matplotlib和Seaborn&#xff09;无缝集成&#xff0c;允许你快速创建各种图表和可视化。这里是一些使用Pandas数据进行可视化的常见方法&#xff1a; 1. 使用Matplotlib Pandas中的plot()方法…

品读 Java 经典巨著《Effective Java》90条编程法则,第4条:通过私有构造器强化不可实例化的能力

文章目录 【前言】欢迎订阅【品读《Effective Java》】系列专栏java.lang.Math 类的设计经验总结 【前言】欢迎订阅【品读《Effective Java》】系列专栏 《Effective Java》是 Java 开发领域的经典著作&#xff0c;作者 Joshua Bloch 以丰富的经验和深入的知识&#xff0c;全面…

【软件文档】软件系统应急处理方案(word原件)

1 总则   1.1 目的 1.2 工作原则   2 应急工作小组机构及职责 3 预警和预防机制 3.1 系统监测及报告   3.2 预警 3.3 预警支持系统 3.4 预防机制 4 应急处理程序 4.1 系统突发事件分类分级的说明 4.2 系统应急预案启动 4.3 现场应急处理 5 保障措施   5.1 应急演练 5.2…

时间复杂度计算 递归

我们先拿出 2021 csp-s 程序题中一道看着就头大的程序题&#xff0c;要求分析 solve1 的复杂度。 设 T(n) ⁡ \operatorname{T(n)} T(n) 表示数组长度为 n n n 时的复杂度&#xff08;即 m − h 1 n m-h1n m−h1n&#xff09;。 T ( 1 ) 1 T(1)1 T(1)1&#xff0c;根据…

R语言机器学习算法实战系列(一):XGBoost算法(eXtreme Gradient Boosting)

介绍 XGBoost(eXtreme Gradient Boosting)是一种基于梯度提升决策树(GBDT)的优化算法,它在处理大规模数据集和复杂模型时表现出色,同时在防止过拟合和提高泛化能力方面也有很好的表现。以下是XGBoost算法的原理和应用方向的详细介绍: 算法原理 目标函数:XGBoost的目标…

华为ensp中vlan与静态路由技术的实现

vlan 同一网段的设备&#xff0c;可以互通&#xff1b; 虚拟局域网&#xff1a;将局域网从逻辑上划分为多个局域网&#xff0c;不同通过vlan编号区分&#xff1b; 实现网络隔离。提高了网络安全性&#xff1b; vlan编号为12位&#xff1b; 范围1-4094可以用来配置 默认处于…

pytorch-AutoEncoders实战

目录 1. AutoEncoders回顾2. 实现网络结构3. 实现main函数 1. AutoEncoders回顾 如下图&#xff1a;AutoEncoders实际上就是重建自己的过程 2. 实现网络结构 创建类继承自nn.Model&#xff0c;并实现init和forward函数&#xff0c;init中实现encoder、decoder 直接上代码&a…

代码随想录算法训练营第13天|二叉树基础知识、递归遍历、迭代遍历、层序遍历、116. 填充每个节点的下一个右侧节点指针

目录 二叉树基础深度和高度满二叉树和完全二叉树二叉搜索树和平衡二叉搜索树二叉树节点定义前中后序遍历 递归遍历前序递归遍历—144. 二叉树的前序遍历 迭代遍历层序遍历116. 填充每个节点的下一个右侧节点指针1、题目描述2、思路3、code 二叉树基础 深度和高度 满二叉树和完…

XSS跨站脚本攻击及防护

什么是XSS攻击&#xff1f; XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码&#xff0c;当用户(被攻击者)登录网站时就会执行这些恶意代码&#xff0c;通过这些脚本可以读取cookie,session tokens&#xff0c;或者网站其他敏感的网…

Ubuntu WSL使用技巧

0 Preface/Foreword 1 默认为root用户 当下载完成Ubuntu之后&#xff0c;首次登录&#xff0c;当完成初始化后&#xff0c;提示输入新的用户名时候&#xff0c;直接点击右上角的X按钮&#xff0c;再重新登陆&#xff0c;系统会默认使用root权限登录。 2 root用户和普通用户切换…

阿里云社区领积分自动打卡Selenium IDE脚本

脚本 感觉打卡比较麻烦&#xff0c;要点开点按钮这种机械化的操作。 所以就自己整了个脚本&#xff1a; { “id”: “f9999777-9ad6-40e0-9435-4f105919c982”, “version”: “2.0”, “name”: “aliyun”, “url”: “https://developer.aliyun.com”, “tests”: [{ “id”…

ubuntu22安装docker

1、查看服务器系统信息 uname -a&#xff1a;显示内核名称、主机名、内核版本、处理器类型等信息。 lsb_release -a&#xff1a;显示有关 Ubuntu 发行版的详细信息&#xff0c;包括版本号、代号等。 free -h&#xff1a;查看系统内存使用情况。 df -h&#xff1a;查看磁盘空间使…

Vue2时间轴组件(TimeLine/分页、自动顺序播放、暂停、换肤功能、时间选择,鼠标快速滑动)

目录 1介绍背景 2实现原理 3组件介绍 4代码 5其他说明 1介绍背景 项目背景是 一天的时间轴 10分钟为一间隔 一天被划分成144个节点 一页面12个节点 代码介绍的很详细 可参考或者借鉴 2实现原理 对Element-plus滑块组件的二次封装 基于Vue2&#xff08;2.6.14&#x…

Vue3 : ref 与 reactive

目录 一.ref 二.reactive 三.ref与reactive的区别 四.总结 一.ref 在 Vue 3 中&#xff0c;ref 是一个用于创建可读写且支持数据跟踪的响应式引用对象。它主要用于在组件内部创建响应式数据&#xff0c;这些数据可以是基本类型&#xff08;如 number、string、boolean&…

深入理解全连接层:从线性代数到 PyTorch 中的 nn.Linear 和 nn.Parameter

文章目录 数学概念&#xff08;全连接层&#xff0c;线性层&#xff09;nn.Linear()nn.Parameter()Q1. 为什么 self.weight 的权重矩阵 shape 使用 ( out_features , in_features ) (\text{out\_features}, \text{in\_features}) (out_features,in_features)而不是 ( in_featur…

Vue的缓存组件 | 详解KeepAlive

引言 在Vue开发中&#xff0c;我们经常需要处理大量的组件渲染和销毁操作&#xff0c;这可能会影响应用的性能和用户体验。而Vue的KeepAlive组件提供了一种简便的方式来优化组件的渲染和销毁流程&#xff0c;通过缓存已经渲染的组件来提升应用的性能。 本文将详细介绍Vue的Ke…

即插即用篇 | YOLOv10 引入矩形自校准模块RCM | ECCV 2024

本改进已同步到YOLO-Magic框架! 语义分割是许多应用的重要任务,但要在有限的计算成本下实现先进性能仍然非常具有挑战性。在本文中,我们提出了CGRSeg,一个基于上下文引导的空间特征重建的高效且具有竞争力的分割框架。我们精心设计了一个矩形自校准模块,用于空间特征重建和…

经典RNA-seq分析流程1

RNA-seq分析有很多流程&#xff0c; 一般都是上游linux工具获取表达矩阵数据&#xff0c;然后就可以使用下游R包进行处理了&#xff0c;要么是差异DEG表达gene等分析&#xff1b; 因为下游分析其实R包是明确的&#xff0c;毕竟有很多生信分析教程&#xff0c;但是上游的linux…