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

打印 上一主题 下一主题

主题 841|帖子 841|积分 2523

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


  • 无穷制条目
  • 无穷制层级
  • 用户自定义条目和动作
  • 脚本式天生菜单类
二、代码实现

(一)菜单类

菜单类重要负责菜单的创建、修改、删除,是包含菜单结构构造和响应函数的模子,用户拥有充实的自主性,可根据必要自定义菜单显示和响应函数。其中菜单项利用vector容器数据结构,根据用户下令可进行菜单创建、修改、删除,而显示和响应函数则利用函数指针进行生存,体现了回调函数的特性。
  1. /*
  2. 菜单类
  3. (c) hele 2024
  4. 菜单类主要负责菜单的创建、修改、删除,是包含菜单结构组织和响应函数的模型,用户拥有充分的自主性,需要自定义菜单显示和响应函数
  5. */
  6. class HeleMenu {
  7.     private:
  8.         void (*p_action)()=nullptr; //回调函数指针,菜单响应函数
  9.         void (*p_display)(string &name, vector<string> &content, unsigned int &index)=nullptr;//回调函数指针,菜单显示函数
  10.         
  11.    
  12.     public:
  13.         string name; //当前菜单名称
  14.         HeleMenu *p_father; //父节点指针,默认NULL        
  15.         vector<string> p_childrenName; //下级菜单项名称,默认empty无下级菜单
  16.         vector<HeleMenu *> p_children; //下级菜单项,默认empty无下级菜单  
  17.         unsigned int index; //菜单项选择指示位,默认0      
  18.                   
  19.         static HeleMenu * parseMenu(string bat, void (*p_display[])(string&, vector<string>&, unsigned int&), void (*p_action[])()); //解析脚本生成菜单
  20.         HeleMenu(string name = "", HeleMenu *p_father = nullptr); //带菜单名称、父节点指针的构造函数,默认菜单名为无名,默认父节点值为空
  21.         void addToMenus(); //添加菜单项到菜单本
  22.         void addValue(string value); //添加菜单单个叶节点
  23.         int addValues(vector<string> values); //添加菜单叶节点
  24.         void changeValue(string value); //修改菜单叶节点
  25.         HeleMenu *getChild(unsigned int index = 0); //获取指定序号的子节点,默认第0项子节点
  26.         string getValue(); //获取菜单叶节点
  27.         void removeAllItem(); //删除菜单所有节点
  28.         void attachAction(void (*p_action)()); //添加动作回调函数
  29.         void attachDisplay(void (*p_display)(string&, vector<string>&, unsigned int&)); //添加显示回调函数 0.菜单名,1.显示内容,2.选中项
  30.         void action(); //菜单响应函数
  31.         void display(); //菜单显示函数
  32. };
复制代码
  1. /*解析脚本生成菜单*/
  2. HeleMenu * HeleMenu::parseMenu(string bat, void (*p_display[])(string&, vector<string>&, unsigned int&), void (*p_action[])()) {
  3. vector<char> stack_simul; //符号栈
  4. vector<HeleMenu*> stack_p; //指针栈
  5. vector<string> stack_name; //名称栈
  6. HeleMenu * root = nullptr; //初始化根菜单指针
  7. uint8_t countAction = 0;
  8. for (uint8_t i = 0; i < bat.length();) {
  9.   string name = ""; //菜单名
  10.   uint8_t step = 0; //菜单名长,即距下次读取的步长
  11.   char a = bat[i];
  12.   if ('{' == a) { //有子菜单
  13.    HeleMenu *father = nullptr;
  14.    if (stack_p.size() > 0) { //有父菜单
  15.     father = stack_p.back(); //弹出指针栈
  16.    }
  17.    if (stack_name.size() > 0) {
  18.     name = stack_name.back(); //读取名称
  19.    }
  20.    HeleMenu * m1 = new HeleMenu(name, father); //构建菜单
  21.    m1->attachDisplay(p_display[countAction]);
  22.    m1->attachAction(p_action[countAction++]);
  23.    m1->addToMenus();
  24.    stack_simul.push_back('{'); //压入符号栈
  25.    stack_p.push_back(m1); //压入菜单指针
  26.    
  27.    i++;
  28.   } else if ('}' == a) { //子菜单结束
  29.    stack_simul.pop_back(); //弹出符号栈
  30.    root = stack_p.back(); //弹出菜单指针栈
  31.    stack_p.pop_back();
  32.    stack_name.pop_back(); //弹出名称栈
  33.    i++;
  34.   } else if (',' == a) {
  35.    i++;
  36.   } else { //若有菜单名或值
  37.    for (uint8_t j = i; j < bat.length() && '{' != bat[j] && '}' != bat[j] && ',' != bat[j]; j++,step++) { //读菜单名或值
  38.     name.append(&bat[0]+j,1); //适应中文
  39.    }
  40.    stack_name.push_back(name);
  41.    i += step;
  42.    if (stack_simul.size() > 0 && '{' == stack_simul.back() && ('}' == bat[i] || ',' == bat[i])) { //若为值
  43.     stack_p.back()->addValue(name);
  44.    }
  45.    
  46.   }
  47. }
  48. return root;
  49. }
  50. /*带菜单名称、父节点指针的构造函数,默认菜单名为无名,默认父节点值为空*/
  51. HeleMenu::HeleMenu(string name, HeleMenu *p_father) {
  52. this->name = name;
  53.     this->p_father = p_father;
  54.     this->index = 0;
  55.     this->p_action = nullptr;
  56.     this->p_display = nullptr;
  57. }
  58. /*添加菜单项到菜单本*/
  59. void HeleMenu::addToMenus() {
  60. if (this->p_father != nullptr) {
  61.   this->p_father->p_childrenName.push_back(this->name); //赋予菜单内容
  62.   this->p_father->p_children.push_back(this); //将菜单指针注册到父菜单
  63. }
  64. }
  65. /*添加菜单单个叶节点*/
  66. void HeleMenu::addValue(string value) {
  67. this->p_childrenName.push_back(value);
  68. this->p_children.push_back(nullptr); //添加空指针,表示无下级菜单,即叶节点
  69. }
  70. /*添加菜单叶节点*/
  71. int HeleMenu::addValues(vector<string> values) {
  72. unsigned int i = 0;
  73. for (; i < values.size(); i++) {
  74.   this->p_childrenName.push_back(values[i]); //赋予值项
  75.   this->p_children.push_back(nullptr); //添加空指针,表示无下级菜单,即叶节点
  76. };
  77. return i;
  78. }
  79.   
  80. /*添加动作回调函数*/
  81. void HeleMenu::attachAction(void (*p_action)()) {
  82. this->p_action = p_action;
  83. }
  84. /*添加显示回调函数*/
  85. void HeleMenu::attachDisplay(void (*p_display)(string&, vector<string>&, unsigned int&)) {
  86. this->p_display = p_display;
  87. }
  88. /*菜单响应函数*/
  89. void HeleMenu::action() {
  90. this->p_action(); //回调动作函数
  91. }
  92. /*将某项叶节点改成对应值,实现菜单动态显示*/
  93. void HeleMenu::changeValue(string value) {
  94. this->p_childrenName.at(this->index) = value;
  95. }
  96. /*菜单显示函数*/
  97. void HeleMenu::display() {
  98. this->p_display(this->name, this->p_childrenName, this->index); //回调显示函数,1.显示内容,2.选中项
  99. }
  100. /*获取指定序号的子节点*/
  101. HeleMenu * HeleMenu::getChild(unsigned int index) {
  102. return this->p_children.at(index);
  103. }
  104. /*获取某项叶节点值,实现菜单动态显示*/
  105. string HeleMenu::getValue() {
  106. return this->p_childrenName.at(this->index);
  107. }
  108. /*删除菜单所有节点*/
  109. void HeleMenu::removeAllItem() {
  110. for(auto i:this->p_children) { //释放子节点内存
  111.   free(i);
  112. }
  113. this->p_childrenName.clear();
  114. this->p_children.clear();
  115. this->index = 0; //序号防越界,恢复默认值
  116. }
复制代码
(二)菜单欣赏器类

菜单欣赏器类重要负责菜单结构的欣赏导航。私有变量是2个菜单类指针,1个是根目录指针,1个是当前目录指针。
  1. /*
  2. 菜单浏览器类
  3. (c)hele 2024
  4. 菜单浏览器主要负责菜单结构的浏览
  5. */
  6. class HeleMenuViewer {
  7. private:
  8.   static HeleMenu *p_rootMenu, *p_currentMenu; //内置根菜单指针、当前菜单项指针
  9. public:
  10.   static void init(HeleMenu *p_rootMenu); //初始化函数
  11.   static HeleMenu * getCurrentMenu(); //获取当前菜单指针
  12.   static HeleMenu * getRootMenu(); //获取根菜单指针
  13.   static void gotoFather(); //跳转到父菜单
  14.   static void gotoChild(); //跳转到子菜单,序号指示在菜单类里
  15.   static void gotoChild(unsigned int index, unsigned int selected=0); //跳转到指定序号的菜单,默认其选中子菜单第0项
  16.   static void gotoRoot(); //跳转到根菜单
  17.   static void selectUp(); //向上选择
  18.   static void selectDown(); //向下选择
  19.   static void action(); //当前菜单响应
  20.   static void display(); //显示当前菜单
  21. };
复制代码
  1. /*初始化菜单管理类的内置根菜单和当前菜单指针为空指针*/
  2. HeleMenu *HeleMenuViewer::p_rootMenu = nullptr;
  3. HeleMenu *HeleMenuViewer::p_currentMenu = nullptr;
  4. /*当前菜单响应*/
  5. void HeleMenuViewer::action() {
  6.     p_currentMenu->action();
  7. }
  8. /*显示当前菜单*/
  9. void HeleMenuViewer::display() {
  10.     p_currentMenu->display();
  11. }
  12. /*获取当前菜单指针*/
  13. HeleMenu * HeleMenuViewer::getCurrentMenu() {
  14.     return p_currentMenu;
  15. }
  16. /*获取根菜单指针*/
  17. HeleMenu * HeleMenuViewer::getRootMenu() {
  18.     return p_rootMenu;
  19. }
  20. /*跳转到父菜单*/
  21. void HeleMenuViewer::gotoFather() {
  22.     if (p_currentMenu->p_father != nullptr) {
  23.         p_currentMenu = p_currentMenu->p_father;   
  24.     }
  25. }
  26. /*跳转到子菜单,序号指示在菜单类里*/
  27. void HeleMenuViewer::gotoChild() {
  28.     if (p_currentMenu->p_children.size() > 0 && p_currentMenu->p_children[p_currentMenu->index] != nullptr) { 防止无子项
  29.         p_currentMenu = p_currentMenu->p_children[p_currentMenu->index];
  30.     }
  31. }
  32. /*跳转到指定序号的菜单,默认其选中子菜单第0项*/
  33. void HeleMenuViewer::gotoChild(unsigned int index, unsigned int selected) {
  34.     if (index < p_currentMenu->p_children.size()) { //防止越界
  35.         p_currentMenu->index = index; //更新菜单指示位置
  36.         gotoChild();
  37.         if (selected < p_currentMenu->p_children.size()) {
  38.             p_currentMenu->index = selected;
  39.         }
  40.     }
  41. }
  42. /*跳转到根菜单*/
  43. void HeleMenuViewer::gotoRoot() {
  44.     p_currentMenu = p_rootMenu;
  45. }
  46. /*初始化函数,为根菜单指针赋值*/
  47. void HeleMenuViewer::init(HeleMenu *p_rootMenu) {
  48.     HeleMenuViewer::p_rootMenu = p_rootMenu;
  49. }
  50. /*向下选择*/
  51. void HeleMenuViewer::selectDown() {
  52.     if (p_currentMenu->p_childrenName.size() > 0) { //防除0错误
  53.         p_currentMenu->index = (p_currentMenu->index + 1) % p_currentMenu->p_childrenName.size();
  54.     }
  55. }
  56. /*向上选择*/
  57. void HeleMenuViewer::selectUp() {
  58.     if (p_currentMenu->p_childrenName.size() > 0) { //防除0错误
  59.         p_currentMenu->index = (p_currentMenu->index - 1 + p_currentMenu->p_childrenName.size()) % p_currentMenu->p_childrenName.size();
  60.     }
  61. }
复制代码
(三)库的天生

网上的资料有很多了,在此仅简单记载。
  1. ##静态库生成与调用,用HeleMenu.cpp生成库##
  2. #编译生成.o链接文件
  3. g++ -c HeleMenu.cpp
  4. #利用.o文件生成静态库,文件名必须以lib***.a形式命名
  5. ar -crv libHeleMenu.a HeleMenu.o
  6. #调用静态库生成目标程序
  7. g++ -o main.exe CreateAndShowMenu.cpp -L . -lHeleMenu
  8. g++ CreateAndShowMenu.cpp libHeleMenu.a -o main.exe
  9. -------------------------------------------------
  10. ##动态库生成与调用,用HeleMenu.cpp生成库##
  11. #生成.o链接文件
  12. g++ -c HeleMenu.cpp
  13. #利用.o文件生成动态库,文件名必须以lib***.so形式命名
  14. g++ -shared -fpic -o libHeleMenu.so HeleMenu.o
  15. #调用动态库生成目标程序
  16. g++ -o main.exe CreateAndShowMenu.cpp -L . -lHeleMenu
  17. g++ CreateAndShowMenu.cpp libHeleMenu.so -o main.exe
  18. -------------------------------------------------
  19. 代码编译优化
  20. -O0 禁止编译器优化,默认此项
  21. -O1 尝试优化编译时间和可执行文件大小
  22. -O2 更多的优化,尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法
  23. -O3 在-O2基础上再打开一些优化选项
  24. -Os 对生成文件大小进行优化。会打开-O2开的全部选项,除了那些会增加文件大小的
复制代码
三、利用示例

重要有2种方法实现用户自定义的菜单类,共同点是attachDisplay和attachAction所带的参数均为用户自定义的函数。
(一)手动天生
  1. /*手动生成菜单*/
  2. HeleMenu *m1 = new HeleMenu();
  3. m1->attachDisplay(display_root);
  4. m1->attachAction(action_root);
  5. HeleMenuViewer::init(m1); //初始化根菜单
  6. //   
  7. HeleMenu *m2 = new HeleMenu("历史记录",m1);
  8. m2->attachDisplay(display);
  9. m2->attachAction(action);
  10. m2->addToMenus();
  11. m2 = new HeleMenu("操作",m1);
  12. m2->addValues({"保存","不保存"});
  13. m2->attachDisplay(display);
  14. m2->attachAction(action_operate);
  15. m2->addToMenus();
  16. m2 = new HeleMenu("菜单",m1);
  17. m2->attachDisplay(display);
  18. m2->attachAction(action);
  19. m2->addToMenus();
  20. m1 = m2; //构建下一层子菜单
  21. m2 = new HeleMenu("对比度", m1);
  22. m2->addValues({"1", "2", "3", "4"});
  23. m2->attachDisplay(display_compare);
  24. m2->attachAction(action_compare);
  25. m2->addToMenus();
  26. m1->addValues({/*对比度,*/ "全部清除","重启","关机"/*,"校准","关于"*/});
  27. m2 = new HeleMenu("校准",m1);
  28. m2->addValue("确认");
  29. m2->attachDisplay(display);
  30. m2->attachAction(action_adjust);
  31. m2->addToMenus();   
  32. m2 = new HeleMenu("关于",m1);
  33. m2->addValue("(c)hele 2024\n这是一个菜单类,可以帮助你生成自定义菜单,同时还可以设置动作");
  34. m2->attachDisplay(display);
  35. m2->attachAction(action);
  36. m2->addToMenus();
  37. HeleMenuViewer::gotoRoot();     //到达根菜单
  38. while (true) { //启动
  39.     system("cls");
  40.     HeleMenuViewer::display();
  41.     HeleMenuViewer::action();
  42. }
复制代码
(二)脚本天生

重要利用菜单类parseMenu函数实现,写了1个解析器,可以实现菜单类的自动天生。
  1. /*脚本生成菜单*/
  2. void (*p_display[])(string&, vector<string>&, unsigned int&) = {/*root*/display_root, /*log*/display, /*operate*/display, /*menu*/display, /*constrast*/display_compare, /*adjust*/display, /*about*/display};
  3. void (*p_action[])() = {/*root*/action_root, /*log*/action, /*operate*/action_operate, /*menu*/action, /*constrast*/action_compare, /*adjust*/action_adjust, /*about*/action};
  4. HeleMenu *m1 = HeleMenu::parseMenu("{历史{},操作{save,unsave},菜单{对比度{1,2,3,4},clearAll,rePower,shutdown,校准{confirm},关于{(c)hele 2024,这是一个菜单}}}", p_display, p_action);
  5. HeleMenuViewer::init(m1);
  6. HeleMenuViewer::gotoRoot();     //到达根菜单
  7. while (true) { //启动
  8.     system("cls");
  9.     HeleMenuViewer::display();
  10.     HeleMenuViewer::action();
  11. }
复制代码
parseMenu所带的参数共3个,第1个是菜单结构字符串,也就是天生菜单结构的脚本,后面2个参数分别是显示函数指针数组和响应函数指针数组。为便于明白,下面我将用户自定义菜单结构展开:
  1. {
  2.     log{
  3.     },
  4.     operate{
  5.         save,unsave
  6.     },
  7.     menu{
  8.         constrast{
  9.             1,2,3,4
  10.         },
  11.         clearAll,
  12.         rePower,
  13.         shutdown,
  14.         adjust{
  15.             confirm
  16.         },
  17.         about{
  18.             (c)hele 2024
  19.         }
  20.     }
  21. }
复制代码
每一个'{'都意味着该项目有子菜单,每一个'}'意味着该菜单结束,每一个','都意味着有同级菜单,以上这3个符号均是关键词,均是英文字符。全部菜单除内容中间可以有空格外,其余地方不能有多余的空格。支持中文。
(三)演示

重要利用上、下、左、右、空格、回车、退出这些按键,只实现部门功能。

四、不敷与展望

运用C++面向对象头脑进行编程,代码体积大,效率低;
利用脚本天生菜单是个新颖的思绪,但容错性不好,没有对脚本进行规范化的检查,对用户不友好;
可以利用ArduinoSTL库,将此库移植进Arduino系列单片机项目中;
利用了动态分配内存技术,对于RAM较小的单片机,容易内存溢出。
五、参考资料

六、源码下载


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万万哇

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表