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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...