C++面向对象语言自制多级菜单

news/2024/10/5 11:15:56

因为要做一个小应用,需要一个菜单类,在网上找了许久,也没有找到一款心仪的菜单类,索性用C++语言,自制一个命令行级别的菜单类,并制作成库,现记录下来,供以后借鉴。

一、特性

  1. 无限制条目
  2. 无限制层级
  3. 用户自定义条目和动作
  4. 脚本式生成菜单类

二、代码实现

(一)菜单类

菜单类主要负责菜单的创建、修改、删除,是包含菜单结构组织和响应函数的模型,用户拥有充分的自主性,可根据需要自定义菜单显示和响应函数。其中菜单项使用vector容器数据结构,根据用户命令可进行菜单创建、修改、删除,而显示和响应函数则利用函数指针进行保存,体现了回调函数的特性。

/*
菜单类
(c) hele 2024
菜单类主要负责菜单的创建、修改、删除,是包含菜单结构组织和响应函数的模型,用户拥有充分的自主性,需要自定义菜单显示和响应函数
*/
class HeleMenu {private:void (*p_action)()=nullptr; //回调函数指针,菜单响应函数void (*p_display)(string &name, vector<string> &content, unsigned int &index)=nullptr;//回调函数指针,菜单显示函数public:string name; //当前菜单名称HeleMenu *p_father; //父节点指针,默认NULL        vector<string> p_childrenName; //下级菜单项名称,默认empty无下级菜单vector<HeleMenu *> p_children; //下级菜单项,默认empty无下级菜单  unsigned int index; //菜单项选择指示位,默认0      static HeleMenu * parseMenu(string bat, void (*p_display[])(string&, vector<string>&, unsigned int&), void (*p_action[])()); //解析脚本生成菜单HeleMenu(string name = "", HeleMenu *p_father = nullptr); //带菜单名称、父节点指针的构造函数,默认菜单名为无名,默认父节点值为空void addToMenus(); //添加菜单项到菜单本void addValue(string value); //添加菜单单个叶节点int addValues(vector<string> values); //添加菜单叶节点void changeValue(string value); //修改菜单叶节点HeleMenu *getChild(unsigned int index = 0); //获取指定序号的子节点,默认第0项子节点string getValue(); //获取菜单叶节点void removeAllItem(); //删除菜单所有节点void attachAction(void (*p_action)()); //添加动作回调函数void attachDisplay(void (*p_display)(string&, vector<string>&, unsigned int&)); //添加显示回调函数 0.菜单名,1.显示内容,2.选中项void action(); //菜单响应函数void display(); //菜单显示函数
};
/*解析脚本生成菜单*/HeleMenu * HeleMenu::parseMenu(string bat, void (*p_display[])(string&, vector<string>&, unsigned int&), void (*p_action[])()) {vector<char> stack_simul; //符号栈vector<HeleMenu*> stack_p; //指针栈vector<string> stack_name; //名称栈HeleMenu * root = nullptr; //初始化根菜单指针uint8_t countAction = 0;for (uint8_t i = 0; i < bat.length();) {string name = ""; //菜单名uint8_t step = 0; //菜单名长,即距下次读取的步长char a = bat[i];if ('{' == a) { //有子菜单HeleMenu *father = nullptr;if (stack_p.size() > 0) { //有父菜单father = stack_p.back(); //弹出指针栈}if (stack_name.size() > 0) {name = stack_name.back(); //读取名称}HeleMenu * m1 = new HeleMenu(name, father); //构建菜单m1->attachDisplay(p_display[countAction]);m1->attachAction(p_action[countAction++]);m1->addToMenus();stack_simul.push_back('{'); //压入符号栈stack_p.push_back(m1); //压入菜单指针i++;} else if ('}' == a) { //子菜单结束stack_simul.pop_back(); //弹出符号栈root = stack_p.back(); //弹出菜单指针栈stack_p.pop_back();stack_name.pop_back(); //弹出名称栈i++;} else if (',' == a) {i++;} else { //若有菜单名或值for (uint8_t j = i; j < bat.length() && '{' != bat[j] && '}' != bat[j] && ',' != bat[j]; j++,step++) { //读菜单名或值name.append(&bat[0]+j,1); //适应中文}stack_name.push_back(name);i += step;if (stack_simul.size() > 0 && '{' == stack_simul.back() && ('}' == bat[i] || ',' == bat[i])) { //若为值stack_p.back()->addValue(name);}} }return root;}/*带菜单名称、父节点指针的构造函数,默认菜单名为无名,默认父节点值为空*/HeleMenu::HeleMenu(string name, HeleMenu *p_father) {this->name = name;this->p_father = p_father;this->index = 0;this->p_action = nullptr;this->p_display = nullptr;}/*添加菜单项到菜单本*/void HeleMenu::addToMenus() {if (this->p_father != nullptr) {this->p_father->p_childrenName.push_back(this->name); //赋予菜单内容this->p_father->p_children.push_back(this); //将菜单指针注册到父菜单 }}/*添加菜单单个叶节点*/void HeleMenu::addValue(string value) {this->p_childrenName.push_back(value);this->p_children.push_back(nullptr); //添加空指针,表示无下级菜单,即叶节点}/*添加菜单叶节点*/int HeleMenu::addValues(vector<string> values) {unsigned int i = 0;for (; i < values.size(); i++) {this->p_childrenName.push_back(values[i]); //赋予值项this->p_children.push_back(nullptr); //添加空指针,表示无下级菜单,即叶节点};return i;}/*添加动作回调函数*/void HeleMenu::attachAction(void (*p_action)()) {this->p_action = p_action;}/*添加显示回调函数*/void HeleMenu::attachDisplay(void (*p_display)(string&, vector<string>&, unsigned int&)) {this->p_display = p_display;}/*菜单响应函数*/void HeleMenu::action() {this->p_action(); //回调动作函数}/*将某项叶节点改成对应值,实现菜单动态显示*/void HeleMenu::changeValue(string value) {this->p_childrenName.at(this->index) = value;}/*菜单显示函数*/void HeleMenu::display() {this->p_display(this->name, this->p_childrenName, this->index); //回调显示函数,1.显示内容,2.选中项}/*获取指定序号的子节点*/HeleMenu * HeleMenu::getChild(unsigned int index) {return this->p_children.at(index);}/*获取某项叶节点值,实现菜单动态显示*/string HeleMenu::getValue() {return this->p_childrenName.at(this->index);}/*删除菜单所有节点*/void HeleMenu::removeAllItem() {for(auto i:this->p_children) { //释放子节点内存free(i);}this->p_childrenName.clear();this->p_children.clear();this->index = 0; //序号防越界,恢复默认值}

(二)菜单浏览器类

菜单浏览器类主要负责菜单结构的浏览导航。私有变量是2个菜单类指针,1个是根目录指针,1个是当前目录指针。

/*菜单浏览器类(c)hele 2024菜单浏览器主要负责菜单结构的浏览*/class HeleMenuViewer {private:static HeleMenu *p_rootMenu, *p_currentMenu; //内置根菜单指针、当前菜单项指针public:static void init(HeleMenu *p_rootMenu); //初始化函数static HeleMenu * getCurrentMenu(); //获取当前菜单指针static HeleMenu * getRootMenu(); //获取根菜单指针static void gotoFather(); //跳转到父菜单static void gotoChild(); //跳转到子菜单,序号指示在菜单类里static void gotoChild(unsigned int index, unsigned int selected=0); //跳转到指定序号的菜单,默认其选中子菜单第0项static void gotoRoot(); //跳转到根菜单static void selectUp(); //向上选择static void selectDown(); //向下选择static void action(); //当前菜单响应static void display(); //显示当前菜单};
/*初始化菜单管理类的内置根菜单和当前菜单指针为空指针*/HeleMenu *HeleMenuViewer::p_rootMenu = nullptr;HeleMenu *HeleMenuViewer::p_currentMenu = nullptr;/*当前菜单响应*/void HeleMenuViewer::action() {p_currentMenu->action();}/*显示当前菜单*/void HeleMenuViewer::display() {p_currentMenu->display();}/*获取当前菜单指针*/HeleMenu * HeleMenuViewer::getCurrentMenu() {return p_currentMenu;}/*获取根菜单指针*/HeleMenu * HeleMenuViewer::getRootMenu() {return p_rootMenu;}/*跳转到父菜单*/void HeleMenuViewer::gotoFather() {if (p_currentMenu->p_father != nullptr) {p_currentMenu = p_currentMenu->p_father;    }}/*跳转到子菜单,序号指示在菜单类里*/void HeleMenuViewer::gotoChild() {if (p_currentMenu->p_children.size() > 0 && p_currentMenu->p_children[p_currentMenu->index] != nullptr) { 防止无子项p_currentMenu = p_currentMenu->p_children[p_currentMenu->index];}}/*跳转到指定序号的菜单,默认其选中子菜单第0项*/void HeleMenuViewer::gotoChild(unsigned int index, unsigned int selected) {if (index < p_currentMenu->p_children.size()) { //防止越界p_currentMenu->index = index; //更新菜单指示位置gotoChild();if (selected < p_currentMenu->p_children.size()) {p_currentMenu->index = selected;}}}/*跳转到根菜单*/void HeleMenuViewer::gotoRoot() {p_currentMenu = p_rootMenu;}/*初始化函数,为根菜单指针赋值*/void HeleMenuViewer::init(HeleMenu *p_rootMenu) {HeleMenuViewer::p_rootMenu = p_rootMenu;}/*向下选择*/void HeleMenuViewer::selectDown() {if (p_currentMenu->p_childrenName.size() > 0) { //防除0错误p_currentMenu->index = (p_currentMenu->index + 1) % p_currentMenu->p_childrenName.size();}}/*向上选择*/void HeleMenuViewer::selectUp() {if (p_currentMenu->p_childrenName.size() > 0) { //防除0错误p_currentMenu->index = (p_currentMenu->index - 1 + p_currentMenu->p_childrenName.size()) % p_currentMenu->p_childrenName.size();}}

(三)库的生成

网上的资料有很多了,在此仅简单记录。

##静态库生成与调用,用HeleMenu.cpp生成库##
#编译生成.o链接文件
g++ -c HeleMenu.cpp#利用.o文件生成静态库,文件名必须以lib***.a形式命名
ar -crv libHeleMenu.a HeleMenu.o#调用静态库生成目标程序
g++ -o main.exe CreateAndShowMenu.cpp -L . -lHeleMenu
g++ CreateAndShowMenu.cpp libHeleMenu.a -o main.exe
-------------------------------------------------
##动态库生成与调用,用HeleMenu.cpp生成库##
#生成.o链接文件
g++ -c HeleMenu.cpp#利用.o文件生成动态库,文件名必须以lib***.so形式命名
g++ -shared -fpic -o libHeleMenu.so HeleMenu.o#调用动态库生成目标程序
g++ -o main.exe CreateAndShowMenu.cpp -L . -lHeleMenu
g++ CreateAndShowMenu.cpp libHeleMenu.so -o main.exe-------------------------------------------------
代码编译优化
-O0 禁止编译器优化,默认此项
-O1 尝试优化编译时间和可执行文件大小
-O2 更多的优化,尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法
-O3 在-O2基础上再打开一些优化选项
-Os 对生成文件大小进行优化。会打开-O2开的全部选项,除了那些会增加文件大小的

三、使用示例

主要有2种方法实现用户自定义的菜单类,共同点是attachDisplay和attachAction所带的参数均为用户自定义的函数。

(一)手动生成

/*手动生成菜单*/HeleMenu *m1 = new HeleMenu();m1->attachDisplay(display_root);m1->attachAction(action_root);HeleMenuViewer::init(m1); //初始化根菜单//    HeleMenu *m2 = new HeleMenu("历史记录",m1);m2->attachDisplay(display);m2->attachAction(action);m2->addToMenus();m2 = new HeleMenu("操作",m1);m2->addValues({"保存","不保存"});m2->attachDisplay(display);m2->attachAction(action_operate);m2->addToMenus();m2 = new HeleMenu("菜单",m1);m2->attachDisplay(display);m2->attachAction(action);m2->addToMenus();m1 = m2; //构建下一层子菜单m2 = new HeleMenu("对比度", m1);m2->addValues({"1", "2", "3", "4"});m2->attachDisplay(display_compare);m2->attachAction(action_compare);m2->addToMenus();m1->addValues({/*对比度,*/ "全部清除","重启","关机"/*,"校准","关于"*/});m2 = new HeleMenu("校准",m1);m2->addValue("确认");m2->attachDisplay(display);m2->attachAction(action_adjust);m2->addToMenus();    m2 = new HeleMenu("关于",m1);m2->addValue("(c)hele 2024\n这是一个菜单类,可以帮助你生成自定义菜单,同时还可以设置动作");m2->attachDisplay(display);m2->attachAction(action);m2->addToMenus();HeleMenuViewer::gotoRoot();     //到达根菜单while (true) { //启动system("cls");HeleMenuViewer::display();HeleMenuViewer::action();}

(二)脚本生成

主要利用菜单类parseMenu函数实现,写了1个解析器,可以实现菜单类的自动生成。

/*脚本生成菜单*/void (*p_display[])(string&, vector<string>&, unsigned int&) = {/*root*/display_root, /*log*/display, /*operate*/display, /*menu*/display, /*constrast*/display_compare, /*adjust*/display, /*about*/display};void (*p_action[])() = {/*root*/action_root, /*log*/action, /*operate*/action_operate, /*menu*/action, /*constrast*/action_compare, /*adjust*/action_adjust, /*about*/action};HeleMenu *m1 = HeleMenu::parseMenu("{历史{},操作{save,unsave},菜单{对比度{1,2,3,4},clearAll,rePower,shutdown,校准{confirm},关于{(c)hele 2024,这是一个菜单}}}", p_display, p_action);HeleMenuViewer::init(m1);HeleMenuViewer::gotoRoot();     //到达根菜单while (true) { //启动system("cls");HeleMenuViewer::display();HeleMenuViewer::action();}

parseMenu所带的参数共3个,第1个是菜单结构字符串,也就是生成菜单结构的脚本,后面2个参数分别是显示函数指针数组和响应函数指针数组。为便于理解,下面我将用户自定义菜单结构展开:

{log{},operate{save,unsave},menu{constrast{1,2,3,4},clearAll,rePower,shutdown,adjust{confirm},about{(c)hele 2024}}
}

每一个'{'都意味着该项目有子菜单,每一个'}'意味着该菜单结束,每一个','都意味着有同级菜单,以上这3个符号均是关键词,均是英文字符。所有菜单除内容中间可以有空格外,其余地方不能有多余的空格。支持中文。

(三)演示

主要使用上、下、左、右、空格、回车、退出这些按键,只实现部分功能。
image

四、不足与展望

运用C++面向对象思维进行编程,代码体积大,效率低;

利用脚本生成菜单是个新颖的思路,但容错性不好,没有对脚本进行规范化的检查,对用户不友好;

可以利用ArduinoSTL库,将此库移植进Arduino系列单片机项目中;

使用了动态分配内存技术,对于RAM较小的单片机,容易内存溢出。

五、参考资料

  • GCC编译步骤、动态库和静态库的创建和使用、ELF库简介及查看方法
  • C语言(函数指针数组)详解
  • 字符编码详解及利用C++ STL string遍历中文字符串
  • 自制的Arduino多级菜单类
  • C++库文件string,vector

六、源码下载

  • https://www.aigei.com/item/c_mian_xiang.html
  • https://download.csdn.net/download/hele_two/89414475?spm=1001.2014.3001.5503

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Redis之主从同步

概念主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。 前者称为主节点(master/leader),后者称为从节点(slave/follower); 数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。 默认情况下,每台Redis服务器都是主节点;且一个主节点可…

VulnHub - breach系列:breach-2.1

信息收集真的很重要,在不注意的角落可能藏着可用信息VulnHub - breach系列:breach-2.1 靶机描述 Breach 2.0 是多部分系列中的第二部分,是一项 boot2root/CTF 挑战,旨在展示真实场景,沿途充满了曲折和恶作剧。 VM 配置了静态 IP(192.168.110.151),因此您需要将主机适配…

[OpenBMC] LDAP 设定(一) - nss-pam-ldapd

OpenBMC ldap设定和验证,可分成几个部分,本篇会先介绍第一个部分nss-pam-ldapdnss-pam-ldapd LDAP server 架设 Redfish/Web设定 nss-pam-ldapd 底下 OpenBMC 对nss-pam-ldapd 的描述 Bringing the LDAP authentication module support in openbmc stack requires to pull…

某东 h5st 4.7 逆向分析

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术…

贪吃蛇小游戏Python Pygame实现

运行结果 游戏规则 1. ↑ ↓ ← → 来控制蛇的移动方向 2. 蛇吃到自己身体的任意一部分游戏结束, 自动退出窗口 3. 蛇的速度会随游戏时间增长越来越快, 与吃食物的多少 (分数) 无关 4. 蛇可以穿过边界到达另一边 5. 场上食物同时只会存在一个, 颜色随机, 但每个颜色的所得分数…

在线RSA公私钥PKCS格式互转工具

在线公私钥PKCS格式转换,支持公钥PKCS1与PKCS8格式之间相互转换,私钥PKCS1与PKCS8格式之间相互转换;PKCS1定义RSA公开密钥算法加密和签名机制,PKCS8描述私有密钥信息格式,该信息包括公开密钥算法的私有密钥以及可选的属性集等。在线RSA公私钥PKCS格式互转工具

【0基础学爬虫】爬虫基础之自动化工具 DrissionPage 的使用

概述 前三期文章中已经介绍到了 Selenium 与 Playwright 、Pyppeteer 的使用方法,它们的功能都非常强大。而本期要讲的 DrissionPage 更为独特,强大,而且使用更为方便,目前检测少,强烈推荐!!! 这里推荐观看十一姐 B 站 DrissionPage 系列视频,很详细:合集爬虫自动化 …

dynamics 365 online仪表板提示:超出最大记录限制。请减少记录数量

1、Dynamics 365 online的仪表板确实有数量限制,当记录条数超过5万的时候,就会提示:超出最大记录限制。请减少记录数量 https://learn.microsoft.com/zh-cn/power-apps/maker/model-driven-apps/create-edit-system-chart https://learn.microsoft.com/zh-cn/power-apps/mak…