繁体   English   中英

声明和定义互相引用的初始化构造的最佳方法

[英]Best Way to Declare and Define Initialized Const Structs that Reference each Other

关于如何定义相互引用的结构,有很多文章,但是我看不到有关使用这些定义的结构创建的变量的文章。

我有许多可以在我的产品中浏览的菜单,这些菜单由两种结构表示。 这段代码构建了我想要的结构:

typedef void (*MENU_FUNCTION_TYPE)();

typedef enum
{
  MENU_INPUT_NAV,     // Menu just navigates to other menus.
  MENU_INPUT_ACTION,  // Menu displays the results of an action.
  MENU_INPUT_NUM,     // Menu take numeric input and processes it.
  NUM_MENU_INPUTS
} MENU_INPUT_TYPE;

// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
struct MENU_ITEM_TYPE;
struct MENU_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu name.
  const MENU_ITEM_TYPE* menu_items;   // Pointer to structures describing each menu item in this menu.
  uint8_t num_items;                  // The number of menu item in this menu.
  MENU_INPUT_TYPE type;               // Specifies how this menu processes keyboard input.
};

// This structure defines a menu item and what it does when you select it.
struct MENU_ITEM_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu item name.
  const MENU_TYPE* link;              // Pointer to which menu selecting this menu item takes you.
  MENU_FUNCTION_TYPE action;          // Pointer to function that is executed when this menu item is selected.
};

extern const MENU_TYPE PROGMEM main_menu;
extern const MENU_TYPE PROGMEM todo_menu;
extern const MENU_TYPE PROGMEM todo2_menu;
extern const MENU_TYPE PROGMEM todo3_menu;
extern const MENU_TYPE PROGMEM todo4_menu;   

// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
    {main_menu_item_text1, &todo_menu, NULL},
    {main_menu_item_text2, &todo2_menu, NULL},
    {main_menu_item_text3, &todo3_menu, NULL},
    {main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
    main_menu_text,
    main_menu_items,
    LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
    {todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
    todo_menu_text,
    todo_menu_items,
    LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
    {todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
    todo2_menu_text,
    todo2_menu_items,
    LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
    {todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
    todo3_menu_text,
    todo3_menu_items,
    LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
    todo4_menu_text,
    NULL,
    0,
    MENU_INPUT_NUM
};

使我对此代码感到困扰的是“ extern”关键字。 我需要它进行编译,但是由于结构是在同一文件中定义的,因此感觉应该有一种更好的代码编写方法。 我绝对不希望将所有声明放在头文件中,因为使用该终端的任何代码都不应直接访问这些结构。 通常情况下,我会避免被bug困扰并继续编码,但是实际上该资源将由客户看到。

有没有办法不用“ extern”关键字就能编写此代码?

在标准C中,结构不等同于typedef
修改后的代码可在标准C编译器上编译:

typedef void (*MENU_FUNCTION_TYPE)();

typedef enum
{
  MENU_INPUT_NAV,     // Menu just navigates to other menus.
  MENU_INPUT_ACTION,  // Menu displays the results of an action.
  MENU_INPUT_NUM,     // Menu take numeric input and processes it.
  NUM_MENU_INPUTS
} MENU_INPUT_TYPE;

// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
typedef struct tag_MENU_ITEM_TYPE MENU_ITEM_TYPE;
typedef struct tag_MENU_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu name.
  const MENU_ITEM_TYPE* menu_items;   // Pointer to structures describing each menu item in this menu.
  uint8_t num_items;                  // The number of menu item in this menu.
  MENU_INPUT_TYPE type;               // Specifies how this menu processes keyboard input.
} MENU_TYPE;

// This structure defines a menu item and what it does when you select it.
typedef struct tag_MENU_ITEM_TYPE
{
  PGM_P text;                         // Pointer to text to display as menu item name.
  const MENU_TYPE* link;              // Pointer to which menu selecting this menu item takes you.
  MENU_FUNCTION_TYPE action;          // Pointer to function that is executed when this menu item is selected.
} MENU_ITEM_TYPE;

//These now acts as forward declarations
const MENU_TYPE PROGMEM main_menu;
const MENU_TYPE PROGMEM todo_menu;
const MENU_TYPE PROGMEM todo2_menu;
const MENU_TYPE PROGMEM todo3_menu;
const MENU_TYPE PROGMEM todo4_menu;   

// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
    {main_menu_item_text1, &todo_menu, NULL},
    {main_menu_item_text2, &todo2_menu, NULL},
    {main_menu_item_text3, &todo3_menu, NULL},
    {main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
    main_menu_text,
    main_menu_items,
    LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
    {todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
    todo_menu_text,
    todo_menu_items,
    LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
    {todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
    todo2_menu_text,
    todo2_menu_items,
    LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
    {todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
    todo3_menu_text,
    todo3_menu_items,
    LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
    MENU_INPUT_NAV
};

// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
    todo4_menu_text,
    NULL,
    0,
    MENU_INPUT_NUM
};

如果这是用户可见的代码,您希望它不使用错误或警告编译avr-gcc -std=c99 -Wall -Wextraavr-g++ -std=c++11 -Wall -Wextra (加任何-O-mmcu=等所需的选项),建议您将宏中的易碎部分隐藏起来,并使宏健壮。 例如,使用与原始问题中显示的结构紧密匹配的结构:

#include <avr/pgmspace.h>
#define JOIN2TOKENS(token1, token2)  JOIN2TOKENS_(token1, token2)
#define JOIN2TOKENS_(token1, token2) token1 ## token2

struct menu_st {
    const char                      *const title;
    unsigned char                    const items;
    const struct menuitem_st *const *const item;
};

struct menuitem_st {
    const char                      *const label;
    void                           (*const action)();
    const struct menu_st            *const submenu;
};

#define DECLARE_MENU(name, titlestr) \
    extern const struct menu_st name PROGMEM; \
    static const PROGMEM char JOIN2TOKENS(title_of__, name) [] PROGMEM = titlestr

#define DECLARE_MENUITEM(name, labelstr, funcptr, menuptr) \
    static const char JOIN2TOKENS(label_of__, name) [] PROGMEM = labelstr; \
    static const struct menuitem_st name [1] PROGMEM = { { JOIN2TOKENS(label_of__, name), funcptr, menuptr } }

#define DECLARE_SUBMENU(name, labelstr, tomenu) \
    DECLARE_MENUITEM(name, labelstr, (void (*)())0, &(tomenu))

#define DECLARE_ACTION(name, labelstr, funcname) \
    DECLARE_MENUITEM(name, labelstr, funcname, (const struct menu_st *const )0)

#define DECLARE_ACTSUB(name, labelstr, funcname, tomenu) \
    DECLARE_MENUITEM(name, labelstr, funcname, &(tomenu))

#define DEFINE_MENU(name, ...) \
    static const struct menuitem_st *const JOIN2TOKENS(menu_items_of__, name) [] PROGMEM = { __VA_ARGS__ }; \
    const struct menu_st name PROGMEM = { \
        JOIN2TOKENS(title_of__, name), \
        sizeof (JOIN2TOKENS(menu_items_of__, name)) / sizeof (JOIN2TOKENS(menu_items_of__, name)[0]), \
        JOIN2TOKENS(menu_items_of__, name), \
    }


/* 0. Declare or define menu action functions.
 *    These are declared as static, so that they are only visible
 *    in the current compilation unit.
 *
 *    As an example, action_1() and action_2() are defined here,
 *    but defaults() is only declared.
*/
static void action_1(void) { return; }
static void action_2(void) { return; }
static void defaults(void);

/* 1. Declare each menu.
 *
 *    DECLARE_MENU(name, string)
 *        'name' is the menu variable name, and
 *        'string' is the menu title, a string literal.
*/
DECLARE_MENU(main_menu, "Main menu");
DECLARE_MENU(submenu_1, "First submenu");
DECLARE_MENU(submenu_2, "Second submenu");

/* 2. Declare each menu item. Below,
 *        'name' is the local variable name (used in DEFINE_MENU()),
 *        'string' is the text for this menu item,
 *        'function' is the function called when the menu item is activated, and
 *        'menu' is the name of the menu the menu item takes to.
 *
 *    DECLARE_SUBMENU(name, string, menu)
 *        Declare a menu item, that takes to another menu.
 *
 *    DECLARE_ACTION(name, string, function)
 *        Declare a menu item, that causes a function to be called.
 *
 *    DECLARE_ABTSUB(name, string, function, menu)
 *        Declare a menu item, that causes a function to be called,
 *        and then changes to another menu.
 *
*/
DECLARE_SUBMENU(to_main_menu, "Back to main menu", main_menu);
DECLARE_SUBMENU(to_submenu_1, "To first submenu",  submenu_1);
DECLARE_SUBMENU(to_submenu_2, "To second submenu", submenu_2);
DECLARE_ACTION(do_action_1,   "Action 1", action_1);
DECLARE_ACTION(do_action_2,   "Action 2", action_2);
DECLARE_ACTSUB(do_defaults,   "Reset to defaults", defaults, main_menu);

/* 3. Define which menus have which menu items.
 *
 *    DEFINE_MENU(name, menu_item_1 [, ..., menu_item_N ])
 *        'name' is the menu variable name, and should have been
 *               declared using DECLARE_MENU(name, string) before.
 *        'menu_item_1' (and all additional parameters) are
 *               menu items declared before using
 *               DECLARE_SUBMENU(), DECLARE_ACTION(), or DECLARE_ACTSUB().
*/    
DEFINE_MENU(main_menu, to_submenu_1, to_submenu_2);
DEFINE_MENU(submenu_1, to_submenu_2, do_action_1, do_action_2, to_main_menu);
DEFINE_MENU(submenu_2, to_submenu_1, do_defaults, to_main_menu);

/* 4. If the menu action functions were only declared earlier,
 *    define them here.
*/
static void defaults(void) { return; }

DECLARE_MENU()宏确实使用extern const来向前声明菜单结构。 只有菜单名称- main_menusubmenu_1submenu_2 ,以上-是外部可见的。 这恰好在C99和C ++ 11中都能正常工作。 如果您希望符号在本地使用,则需要为C99和C ++ 11使用不同的代码(可以通过#ifdef __cplusplus #else #endif ,但是如果您问我,则很难看)。

avr-gcc-4.8.2 -std=c99 -Wall -Wextraavr-g++-4.8.2 -std=c++11 -Wall -Wextra将所有数据结构存储在.progmem.data节中。

请注意,由于GCC不支持多个地址空间(我最后检查的是avr-gcc-4.8.2),因此必须使用<avr/pgmspace.h>定义的pgm_read_byte()pgm_read_word()宏来访问数据结构。 例如,而不是main_menu.items你需要使用pgm_read_byte(&(main_menu.items)) 类似于以下示例-但请注意,该部分未经测试,并且可能包含错别字/思维方式,尤其是在取消引用指针方面:

extern void print_clear_all(void);
extern void print_menu_title(const char *const pgm_string);
extern void print_menu_item(const char *const pgm_string, const unsigned char is_focus);
extern void print_clear_rest();

static const menu_t *current_menu = &main_menu;
static unsigned char current_item = 0;

void menu_refresh(void)
{
    const unsigned char n = pgm_read_byte(&(current_menu->items));
    const void *const *const itemarray = (const void *const *)pgm_read_word(&(current_menu->item));
    unsigned char i;
    print_clear_all();
    print_menu_title((const char *)pgm_read_word(&(current_menu->title)));
    for (i = 0U; i < n; i++)
        print_menu_item((const char *)pgm_read_word(itemarray + i), i == current_item);
    print_clear_rest();
}

void menu_prev(void)
{
    if (current_item > 0U)
        --current_item;
    menu_refresh();
}

void menu_next(void)
{
    if (current_item + 1U < pgm_read_byte(&(current_menu->items)))
        ++current_item;
    menu_refresh();
}

void menu_enter(void)
{
    const void *const *const itemarray = (const void *const *const)pgm_read_word(&(current_menu->item));
    const menuitem_t *const item = (const menuitem_t *const)pgm_read_word(itemarray + current_item);
    void (*const action)() = (void (*)())pgm_read_word(&(item->action));
    const menu_t *const submenu = (const menu_t *const)pgm_read_word(&(item->submenu));
    if (action)
        action();
    if (submenu)
        current_menu = submenu;
    menu_refresh();
}

在我看来,使用指针会使代码非常密集。 对我来说,一种完全不同的方法似乎更可取,一种方法是使用数组索引标识菜单,另一种方法标识选项。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM