加载中...

C语言多级菜单实现思路——基于结构体数组和函数指针(新手向)

基于结构体数组和函数指针的C语言多级菜单实现思路

menu

前言:

在做机器人类项目嵌入式开发时经常希望能实时读取和修改一些参数,这样的功能通过一个OLED显示屏和几个物理按键就能完整的实现。但随着项目复杂程度的提高,通过一个简洁美观的菜单将所有的交互功能打包起来就显得尤为重要。所以在假期期间我尝试着用电脑编译器做一个稳定,简洁,不占用太多软件资源小菜单。等返校后有机会再移植到单片机中,对功能做进一步的验证。

受启发于使用函数指针实现多级菜单我最后选择用结构体数组和函数指针来实现这个功能,因为这种方式更符合直觉,后期对菜单的编辑和管理也更加得简单。顺便锻炼一下自己写代码的能力。本人在半年前刚刚接触编程,学习的第一门语言就是c与语言,所以本文对新手比较友好。不足之处还望各路大佬多多指教。

实现思路

首先建立一个包含文字显示“string”和函数指针“func”的结构体。

typedef struct _MENU_OptionTypeDef // 选项结构体
{
    char* String;       // 选项字符串
    void (*func)(void); // 选项功能 函数指针
} MENU_OptionTypeDef;

一个结构体数组内的所有元素为同一级下的菜单,通过函数指针来调用,从而实现相应功能或进入下一级菜单。键盘输入来控制光标的移动:输入1代表光标上移,2代表光标下移,0代表选中。文字显示被选中后相应的函数指针被调用

  static MENU_OptionTypeDef MENU_OptionList[] = { {"####MENU####"},               // 主菜单提示栏
                                                {"Tools", MENU_RunToolsMenu},      
                                                {"Games", MENU_RunGamesMenu},      
                                                {"Setting", NULL},                // 功能待开发
                                                {"Information", NULL}, 
                                                {".."} };

通过函数MENU_RunMenu(MENU_OptionTypeDef* OptionList)来实现菜单显示,光标移动和函数的调用。改变传入的结构体指针来实现菜单刷新

代码展示

talk is cheap

点击查看代码
# include <stdlib.h>
# include <stdint.h>
# include <stdio.h>



void MENU_RunMainMenu(void);
void MENU_RunToolsMenu(void);
void MENU_RunGamesMenu(void);
void MENU_Information(void);
void MENU_FuncTest(void);

typedef struct _MENU_OptionTypeDef // 选项结构体
{
    char* String;                  // 选项字符串
    void (*func)(void);            // 选项功能 函数指针
} MENU_OptionTypeDef;

void MENU_RunMenu(MENU_OptionTypeDef* OptionList)
{
    int key =9;                      // 按键输入值(此处将其初始化为非按键值,无特殊含义)
    static uint8_t Catch_i = 1;      // 选中下标默认为1,(因为OptionList[0]为"<<<")
    int8_t Show_i = 0;               // 显示(遍历)起始下标
    int8_t Option_Max_i = 0;         // 选项列表长度

    for (Option_Max_i = 0; OptionList[Option_Max_i].String[0] != '.'; Option_Max_i++) // 计算选项列表长度
    {
        ;
    }

    Option_Max_i--; // 不显示".."

        for (int i = 0;i <= Option_Max_i;i++)        //显示移动光标
        {
            if (i == Catch_i) printf(" * ");
            else printf("   ");
            printf("%s\n", OptionList[i].String);
            
        }


    scanf_s("%d", &key);               //输入按键值:1代表光标上移,2下移,0选择
    if (key == 1 )
    {
        if (Catch_i >= 1)              //光标显示限位
        {
            Catch_i--;
        }
        else Catch_i = 0;
       
        system("cls");                 //清空显示区
        MENU_RunMenu(OptionList);      //刷新菜单(其实是为了刷新光标)
    }
    if (key == 2 ) 
    {
        if (Catch_i < Option_Max_i)
        {
            Catch_i++;
        }
        else  Catch_i = Option_Max_i;

        system("cls");
        MENU_RunMenu(OptionList);
        
    }
    if (key == 0 && OptionList[Catch_i].func != NULL)    //选中后若对应函数指针不为空,则调用对应函数
    {
        system("cls");
        Catch_i = 1;
        system("cls");
        OptionList[key].func();
    }
    
    else
    {
        system("cls");
        MENU_RunMenu(OptionList);
    }
}

           
/*这里菜单的设置我直接复制了开头提到那边文章中博主的设置,很多选项暂未配置实际功能
void MENU_RunMainMenu(void)                                                           // 主菜单
{
    static MENU_OptionTypeDef MENU_OptionList[] = { {"####MENU####"},
                                                   {"Tools", MENU_RunToolsMenu},      // 工具
                                                   {"Games", MENU_RunGamesMenu},      // 游戏
                                                   {"Setting", NULL},                 // 设置
                                                   {"Information", NULL},             // 信息
                                                   {".."} };

    MENU_RunMenu(MENU_OptionList);                                                    //在多级菜单下改变传入指针重新运行菜单程序
}

void MENU_RunGamesMenu(void)                                                          // 游戏菜单
{                                                                        
    static MENU_OptionTypeDef MENU_OptionList[] = { {"<<<",MENU_RunMainMenu},         // 返回上一级菜单
                                                   {"Snake", MENU_FuncTest},          // 贪吃蛇(这里写了一个测试用的输出程序)
                                                   {"Snake II", NULL},                // 贪吃蛇2
                                                   {"Snake III", NULL},               // 贪吃蛇3
                                                   {"Game of Life", NULL},            // 康威生命游戏
                                                   {".."} };
    MENU_RunMenu(MENU_OptionList);
}

void MENU_RunToolsMenu(void)                                                         // 工具菜单
{
    static MENU_OptionTypeDef MENU_OptionList[] = { {"<<<", MENU_RunMainMenu},
                                                   {"Seria", NULL},         
                                                   {"Oscilloscope ", NULL}, 
                                                   {"PWM Output", NULL},    
                                                   {"PWM Input", NULL},     
                                                   {"IIC ", NULL},
                                                   {".."} };

    MENU_RunMenu(MENU_OptionList);
}

void MENU_FuncTest(void)                                                              // 贪吃蛇下的测试程序(挂羊头卖狗肉bushi)
{
    int exit;
    printf("hello,world!\n请输入任意数字以继续");

    scanf_s("%d", &exit);
    system("cls");
    MENU_RunGamesMenu();
}


void main(void)
{
   
        MENU_RunMainMenu();
       
}

写在后面

本人刚接触编程不久,这第一次在电脑上编译运行此类代码(之前都是在单片机上),写的时候也是缝缝补补,哪里出问题改哪里。等返校后再移植到0.96寸的OLED上对功能进行打包来实现对例如陀螺仪解算度数等参数的显示和pid实时调试。
在单片机中,函数的调用可以通过按键触发外部中断,但在电脑上运行c语言时限于本人水平只能对函数进行循环嵌套,不知道这样有什么弊端,希望有大佬路过的话能不吝赐教。
————8.17

posted @ 2024-08-17 18:01  Primary_mage  阅读(10)  评论(0编辑  收藏  举报