C++面向對象語言自製多級菜單

来源:https://www.cnblogs.com/hele-two/p/18242492
-Advertisement-
Play Games

在處理PDF文件時,我們可能會遇到這樣的情況:原始PDF文檔不符合我們的閱讀習慣,或者需要適配不同顯示設備等。這時,我們就需要及時調整PDF文檔中的頁面尺寸,以滿足不同應用場景的需求。 利用Python語言的高效性和靈活性,再結合Spire.PDF for Python 庫的強大功能,我們可以通過P ...


因為要做一個小應用,需要一個菜單類,在網上找了許久,也沒有找到一款心儀的菜單類,索性用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較小的單片機,容易記憶體溢出。

五、參考資料

六、源碼下載


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基於Vue-Cli創建 現在官方推薦使用 create-vue 來創建基於 Vite 的新項目(⚠️ Vue CLI 現已處於維護模式!) # 查看@vue/cli版本號,確保@vue/cli版本在4.5.0以上 vue --version # 沒有安裝@vue/cil或者版本不在4.5.0以上執行 ...
  • 寫在前面 這是PB案例學習筆記系列文章的第5篇,該系列文章適合具有一定PB基礎的讀者。 通過一個個由淺入深的編程實戰案例學習,提高編程技巧,以保證小伙伴們能應付公司的各種開發需求。 文章中設計到的源碼,小凡都上傳到了gitee代碼倉庫https://gitee.com/xiezhr/pb-proje ...
  • Cursor 是一個基於 Visual Studio Code(VS Code)技術構建的高級代碼編輯器,專為提高編程效率並更深度地整合 AI 功能而設計。它不僅繼承了 VS Code 的強大功能和用戶界面,還增加了專門針對 AI 支持的特色功能。Cursor 是 VS Code 的一個分支,這意味... ...
  • 1 maven介紹 1)為什麼使用maven Maven是一個強大的項目管理和構建工具,它能夠簡化Java項目的構建、依賴管理和發佈過程。以下是Maven的一些主要特點和功能: 項目結構管理:Maven採用約定優於配置的原則,提供了標準的項目結構模板,使得開發人員可以快速創建和維護項目。 依賴管理: ...
  • NumPy 中的簡單算術運算可以通過 `add`, `subtract`, `multiply`, `divide`, `power`, `mod`, `remainder` 等函數實現,這些函數支持條件運算,並接受 `where` 參數。例如,`add()` 實現加法,`subtract()` 表... ...
  • JSON 簡介 JSON(JavaScript Object Notation,JavaScript對象表示法)是一種輕量級的數據交換格式。它基於ECMAScript(歐洲電腦製造商協會制定的js規範)的一個子集,採用完全獨立於編程語言的文本格式來存儲和表示數據。簡潔和清晰的層次結構使得JSON成 ...
  • 關註作者,復旦AI博士,分享AI領域與雲服務領域全維度開發技術。擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩博,復旦機器人智能實驗室成員,國家級大學生賽事評審專家,發表多篇SCI核心期刊學術論文,阿裡雲認證的資深架構師,項目管理專業人士,上億營收AI產品研發負責人。 精講 ...
  • 在MyBatis中,可以通過使用一些特定的標簽(if、choose...)以及其他動態SQL功能來實現條件判斷。 這使得SQL查詢可以根據不同的條件動態生成,從而提高查詢的靈活性和可維護性。 本文以訂單列表簡單查詢為例, 對mybatis條件判斷及動態sql進行簡單拓展。 建表語句 CREATE T ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...