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
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...