首页 > 编程语言 >C++面向对象语言自制多级菜单

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

时间:2024-06-11 18:03:23浏览次数:17  
标签:菜单 void C++ 面向对象 m2 action HeleMenu display

因为要做一个小应用,需要一个菜单类,在网上找了许久,也没有找到一款心仪的菜单类,索性用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较小的单片机,容易内存溢出。

五、参考资料

六、源码下载

标签:菜单,void,C++,面向对象,m2,action,HeleMenu,display
From: https://www.cnblogs.com/hele-two/p/18242492

相关文章

  • C++ try-catch 语句的注意事项
    在C++中,try-catch 语句用于处理异常。当在 try 块中的代码抛出一个异常时,程序会立即跳出 try 块,并查找与之匹配的 catch 块来执行。以下是使用 try-catch 语句时需要注意的一些事项:异常类型匹配:catch 块后面必须跟上一个异常类型(或者是省略类型以捕获所有类型的......
  • 【Go语言】面向对象编程(二):通过组合实现类的继承和方法重写
    通过组合实现类的继承和方法重写要实现面向对象的编程,就必须实现面向对象编程的三大特性:封装、继承和多态。1封装类的定义及其内部数据的定义可以看作是类的属性,基于类定义的函数方法则是类的成员方法。2继承Go语言中,没有直接提供继承相关的语法实现,可以通过组合......
  • OpenCV实战案例——直线检测[C++]
    0.前言本文以实战案例为背景,一步步讲述如何使用计算机图像处理相关知识提取图片中英语填空题答题线。1.需求背景某公司打算设计一款英语题目批改APP,要求学生上传英语填空题图片,然后该APP自动标注答题线位置(使用红线标注),方便后续定位和批改答案。下图(图1-1)为某一学生上传的......
  • 基于Vue+Node.js的高校学业预警系统+10551(免费领源码)可做计算机毕业设计JAVA、PHP、爬
    NodeJS高校学业预警系统摘 要随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,教育行业当然也不能排除在外。高校学业预警系统是以实际运用为开发背景,运用软件工程开发方法,采用Node.JS技术构建的一个管理系统。......
  • 华为OD机试 C++ - 中文分词模拟器
    中文分词模拟器前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述给定一个连续不......
  • 华为OD机试 C++ - 找数字
    找数字前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述小扇和小船今天又玩起来......
  • 华为OD机试 C++ - 根据IP查找城市
    根据IP查找城市前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述某业务需要根据......
  • 华为OD机试 C++ - 文件缓存系统
    文件缓存系统前言:本专栏将持续更新互联网大厂机试真题,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于大厂机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:[email protected];备注:CSDN。题目描述请设计一个文件缓......
  • C++中的继承
    目录继承的概念及定义继承的概念继承定义定义格式 继承关系和访问限定符继承基类成员访问方式的变化基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员        我们都知道,面向对象的三个基本特征是:封装,继承以及多态......
  • C/C++单元测试如何解决非虚函数对象依赖
    如何解决非虚函数对象依赖随着事物的接触越来越多,了解的越来越深入,我们总会发现一些新的问题或者不足。就像前文提到的一样,我们在面对有对象的虚函数依赖的时候,可以使用gmock框架来为我们提供方便的模拟期望值,以便我们能撇除外界的影响(依赖)从逻辑上设计单元测试并持续的......