C++面向对象语言自制多级菜单
因为要做一个小应用,需要一个菜单类,在网上找了许久,也没有找到一款心仪的菜单类,索性用C++语言,自制一个命令行级别的菜单类,并制作成库,现记录下来,供以后借鉴。
一、特性
- 无限制条目
- 无限制层级
- 用户自定义条目和动作
- 脚本式生成菜单类
二、代码实现
(一)菜单类
菜单类主要负责菜单的创建、修改、删除,是包含菜单结构组织和响应函数的模型,用户拥有充分的自主性,可根据需要自定义菜单显示和响应函数。其中菜单项使用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个符号均是关键词,均是英文字符。所有菜单除内容中间可以有空格外,其余地方不能有多余的空格。支持中文。
(三)演示
主要使用上、下、左、右、空格、回车、退出这些按键,只实现部分功能。
四、不足与展望
运用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
C++面向对象语言自制多级菜单的更多相关文章
- 用C表达面向对象语言的机制——C#版
PS:本文PDF版在这里(格式更好看一些).最新的源代码请在本页面文末下载,PDF中的链接不是最新的. 用C表达面向对象语言的机制——C#版 我一直认为,面向对象语言是对面向过程语言的封装.如果是这样 ...
- 前端开发css实战:使用css制作网页中的多级菜单
前端开发css实战:使用css制作网页中的多级菜单 在日常工作中,大家都会遇到一些显示隐藏类菜单,比如页头导航.二维码显示隐藏.文本提示等等......而这些效果都是可以使用纯css实现的(而且非常简 ...
- 用C表达面向对象语言的机制2——颠覆你对方法调用的看法!
用C表达面向对象语言的机制2——颠覆你对方法调用的看法! 源代码在文末.推荐阅读本文PDF版,格式更好看. 在上一篇<用C表达面向对象语言的机制——C#版>中,我们获知了如何用C表达面向对 ...
- MVC5+EF6 入门完整教程13 -- 动态生成多级菜单
稍微有一定复杂性的系统,多级菜单都是一个必备组件. 本篇专题讲述如何生成动态多级菜单的通用做法. 我们不用任何第三方的组件,完全自己构建灵活通用的多级菜单. 需要达成的效果:容易复用,可以根据mode ...
- java 24 - 7 GUI之 创建多级菜单窗体
需求: 创建多级菜单 步骤: A:创建窗体对象(并设置属性和布局) B:创建菜单栏 C:创建菜单和子菜单 D:逐步添加菜单(子菜单添加到菜单中,菜单添加到菜单栏中) E:窗体中设置菜单栏(菜单栏并不是 ...
- 单片机C语言下LCD多级菜单的一种实现方法
摘要: 介绍了在C 语言环境下,在LCD 液晶显示屏上实现多级嵌套菜单的一种简便方法,提出了一个结构紧凑.实用的程序模型. 关键词: 液晶显示屏; 多级菜单; 单片机; C 语言; LCD 中 ...
- zTree下拉菜单多级菜单多选实现
惯例,先上图: 这是在一个项目中,为了满足样式美观.多级菜单以及多选而将zTree插件更改过后的效果. 在实际的开发过程中,本来zTree也是可以满足需求的,但是zTree多选的话需要checkbox ...
- Jquery多级菜单插件Slimmenu使用说明
Jquery多级菜单插件Slimmenu使用说明 现在扁平化设计逐渐的成为了趋势,不管是pc web,还是移动互联网的应用开发,都在研究和设计Flat ui, 这里有一篇文章说明扁平化的设计的一些想法 ...
- python作业设计:多级菜单,并可依次进入各级子菜单
'''作业三:多级菜单 三级菜单 可依次选择进入各子菜单 所需新知识点:列表.字典 ''' data = { "北京":{ "昌平":{ "沙河&qu ...
- Python练习----多级菜单
多级菜单要求: 1.三级菜单 2.可依次选择进入各子菜单 3.可以返回上一层 4.输入'q'可以退出 脚本: zone = { '北京' : { ' ...
随机推荐
- 技术干货丨云企业网CEN2.技术揭秘
简介:随着企业数字化转型的加速,越来越多的企业选择了将业务部署在云上,这其中有超过20%的企业有全球组网的需求,这就使得云上网络的规模越来越大,复杂度也越来越高,为了应对这些变化,阿里云推出了升级版 ...
- Arthas 初探--安装初步适用
简介: 由于在项目中遇到一种情况,某段代码在进行单元测试和在 tomcat 容器中运行的性能相差数百倍,因此需要分析在不同环境下某个方法执行的具体时间,从而确定问题.Arthas 可以做到无侵入的监控 ...
- 网易云音乐基于 Flink + Kafka 的实时数仓建设实践
一.背景介绍 (一)流平台通用框架 目前流平台通用的架构一般来说包括消息队列.计算引擎和存储三部分,通用架构如下图所示.客户端或者 web 的 log 日志会被采集到消息队列:计算引擎实时计算消息队列 ...
- 内含干货PPT下载|一站式数据管理DMS关键技术解读
简介: 深入解读实时数据流.库仓一体数据处理等核心技术 "数聚云端·智驭未来"--阿里云数据库创新上云峰会暨第3届数据库性能挑战赛决赛颁奖典礼已圆满结束,更多干货内容欢迎大家观看 ...
- [Caddy2] Caddyfile 指令
以下是 Caddyfile 的标准指令. acme_server An embedded ACME server basicauth Enforces HTTP Basic Authenticatio ...
- dotnet 读 WPF 源代码笔记 WriteableBitmap 的渲染和更新是如何实现
在 WPF 框架提供方便进行像素读写的 WriteableBitmap 类,本文来告诉大家在咱写下像素到 WriteableBitmap 渲染,底层的逻辑 之前我使用 WriteableBitmap ...
- 提示工程(Prompt Engineering)将ChatGPT调教为傲娇猫娘~喵
Prompt Engineering(提示工程)是指通过设计精心构造的提示(prompt)或者输入,来引导大型语言模型生成特定类型的输出.这个技术背后的原理是利用模型对输入的敏感性,通过提供特定格式或 ...
- Halo博客搭建小记
准备工作 阿里云服务器,操作系统为CentOS 7.9.2009 x86_64(Py3.7.9) 宝塔面板 Nginx 1.24.0(用于反向代理) 已备案的域名 ssl证书(https访问) 参考官 ...
- 版本管理工具 nvm WIN版
nvm -h //查看nvm的指令 nvm list //查看本地已经安装的node版本列表 nvm list available //查看可以安装的node版本 nvm install latest ...
- c语言在Linux中的使用
gcc版本升级 如何验证gcc正常使用,编译c以及运行 过程 要验证GCC(GNU Compiler Collection)是否正常使用,您可以按照以下步骤进行操作: 检查GCC是否安装:打开终端或命 ...