[英]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 -Wextra
和avr-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_menu
, submenu_1
和submenu_2
,以上-是外部可见的。 这恰好在C99和C ++ 11中都能正常工作。 如果您希望符号在本地使用,则需要为C99和C ++ 11使用不同的代码(可以通过#ifdef __cplusplus
#else
#endif
,但是如果您问我,则很难看)。
avr-gcc-4.8.2 -std=c99 -Wall -Wextra
和avr-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.