【发布时间】:2020-11-20 23:58:20
【问题描述】:
我大约一周前开始学习 C。为了在一定程度上测试我对指针的理解,我一直在尝试创建一个链表类型结构,它表示您可以在任何 GUI 中找到的菜单栏。
与其解释我想出的那种结构,我决定用粗略的图表来表示它会更容易......(希望这里没有错误)。
所以我想出了下面的 C 代码。它所做的只是创建一个菜单结构。它应该循环遍历整个菜单并将其输出为文本形式...
#include <stdlib.h>
#include <stdarg.h>
typedef enum menu_item_type {
ITEM,
MENU,
TERMINATOR
} menu_item_type;
typedef struct menu_item menu_item;
typedef struct menu_item {
const char* label; // label text for item shown to user
menu_item_type type; // item type. see above enum for possibilities
menu_item** next; // pointer to next item
menu_item* below; // item below in same menu
} menu_item;
void menubar(const char* menubar_label, ...);
menu_item* menu(const char* label, ...);
menu_item* menu_va(const char* label, va_list args);
menu_item* item(const char* label);
menu_item* terminator();
/*
setup menubar. variadic function.
*/
void menubar(const char* menubar_label, ...) {
va_list args;
va_start(args, menubar_label);
menu_item* menubar = menu_va(menubar_label, args); // menubar is a menu
va_end(args);
menu_item* item = menubar;
menu_item* next;
int ident = 0;
do {
if(item->type == TERMINATOR) {
ident--; // unindent after submenu finishes
}else{
for(int i = 0; i < ident; i++) {
printf(" "); // ident each submenu further
}
printf("[%s]\n", item->label); // print item label
}
if(item->type == MENU) {
ident++; // add ident when submenu starts
}
next = *item->next;
free(item); // item printed. no longer needed.
item = next;
} while(item);
}
/*
single menu/submenu. overload variadic function for menu_va.
*/
menu_item* menu(const char* label, ...) {
va_list args;
va_start(args, label);
menu_item* menu = menu_va(label, args);
va_end(args);
return menu;
}
/*
create a menu/submenu with a set label. pass items in a va_list.
*/
menu_item* menu_va(const char* label, va_list args) {
// head item is the item that starts a menu off (e.g item 'File')...
menu_item* head = item(label);
head->type = MENU;
menu_item* i = head;
menu_item* next;
while(i->type != TERMINATOR) {
next = va_arg(args, menu_item*);
if(i != head) {
i->below = next; // next item is item below unless it's head item.
}
i->next = &next; // set next item
i = next;
}
// next item after terminator is item below head item
i->next = &head->below;
return head;
}
/*
create item with label.
*/
menu_item* item(const char* label) {
menu_item* i = malloc(sizeof(menu_item));
i->label = label;
i->type = ITEM;
i->next = NULL;
i->below = NULL;
return i;
}
/*
create terminator item. signals end of a menu.
*/
menu_item* terminator() {
menu_item* i = item(NULL);
i->type = TERMINATOR;
return i;
}
int main() {
// menubar structure is...
menubar("Menubar Test",
menu("File",
item("New"),
item("Save"),
item("Save As..."),
menu("Export To...",
item("PDF File"),
item("JPG File"),
terminator()
),
terminator()
),
menu("Edit",
item("Copy"),
item("Paste"),
menu("Remove...",
item("All"),
item("Selection"),
terminator()
),
item("Undo"),
terminator()
),
menu("Help",
item("About"),
terminator()
),
terminator()
);
return 0;
}
从这段代码中,我想要这样的输出:
[Menubar Test]
[File]
[New]
[Save]
[Save As...]
[Export To...]
[PDF File]
[JPG File]
[Edit]
[Copy]
[Paste]
[Remove...]
[All]
[Selection]
[Undo]
[Help]
[About]
我得到的只是这个:
[Menubar Test]
只打印第一项。我发现第一项的->next 指针是NULL,但我真的不知道为什么。我有点以为我理解指针,但显然我错过了一些东西。这不是我知道如何调试的问题。尝试调试它产生了奇怪的结果。有时我什至发现指针地址会因为未知原因随机更改。 (printf("%p"))。大多数时候,虽然我只是以段错误结束。
如果有人能指出(哈)我正确的方向,我将不胜感激。
【问题讨论】:
-
建议你简化你的测试用例。您可能不需要完整的菜单结构来重现问题。从更简单的菜单结构开始 - 您和其他人更容易调试。使用越来越复杂的菜单构建不同的测试用例,在所有以前的用例都通过之前不要继续下一个更复杂的用例。
-
为什么
next是**?您将其设置为导致未定义行为的局部变量的地址。 -
C 代码通常不会大量使用可变参数函数,除了标准库中的格式化 I/O 函数。程序定义自己的可变参数函数并不常见。由于您是 C 新手,我建议您暂时关注其他语言功能。
-
意见各不相同,但我个人建议在大多数情况下避免使用
typedef。在某些情况下,typedef可以提供很大帮助,但在大多数情况下,我发现 typedefed 类型标识符并没有产生足够的优势来抵消其固有的混淆性质。在某些情况下,例如类型定义的对象指针类型,固有的混淆实际上是一个很大的负面因素。还要注意,与它的名字相反,typedef没有定义新类型。它将标识符定义为另一种类型的别名。你总是可以避免声明任何类型定义。 -
@JohnBollinger 我意识到
typedef只是另一种类型的别名。我认为使用它一定是一个好习惯,因为我正在查看的很多库似乎一直在使用它们(例如 SDL2)。老实说,我看不出它是如何令人困惑的?这对我来说意味着我不会每次都写struct menu_item。我对可变参数函数的使用既是出于好奇,也是因为我认为这是一种表达菜单结构的好方法。
标签: c pointers linked-list variadic-functions