Vilken kompilator använder du? Kör du Linux eller Windows? Kompilerar min kod?
Här är ett exempel som inte har något med din specifika applikation (pannlampa) att göra utan som demonstrerar dels hur man kan göra meny som är enkel att ändra, och dels varför C är onödigt krångligt. Jag har bara testat med gcc i Ubuntu.
Koden är naturligtvis onödigt komplex för en meny med 5 olika val, men det handlar ju om hur man gör ett menysystem.
Med bara 5 enkla val fungerar det naturligtvis bra med en switch(), men vad händer i en mer realistisk situation där du har 200 menyval, eller 2000, och du behöver kunna köra speciell logik för vissa av valen?
Nästan allting går naturligtvis att skriva med ett jättelikt if-else-monster eller switch() men det är inte någon bra ide. Varje gång du skriver else if(){} eller case: så är det kodduplikation.
I största möjliga mån bör saker som väljs ur en tabell matas in i datorn som just en tabell. Varje sak ska endast definieras på ett ställe, och det gäller även logiken.
I exemplet nedan blir det ett antal rader med initialisering av menyträdet, och en del kodduplikation, men detta beror på att C bara har primitiva datastrukturer och inte som t.ex. Javascript har tuples.
Som du kan se så behöver man bara lägga till menyalternativ i denna initialiseringsblobb, allt annat sköts där det ska, men du kan lägga speciell logik i callback-funktionerna om det behövs, något som det ofta gör i en realistisk situation, särskilt när man ändrar i menyträdet i efterhand och lägger till nya saker.
Jag höll allting så förenklat det gick för läsbarheten, så exempelvis datapekarargumentet är inte realistiskt utan man skulle skicka en pekare till en struktur eller liknande. Det visar ändå hur du kan använda samma kod för flera menyval (undvika duplikation).
Kod: Markera allt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __linux__
#include <curses.h>
#else
#include <conio.h>
#endif
typedef void (*menu_funcptr(char *data));
typedef struct NODE_S {
char *name;
void *parent;
void **children_or_callback; /* points to either a function or an array of child nodes, of length childcount */
int childcount; /* zero means it's a leaf node */
} node_s;
void generic_menu_func(char* data) {
printf("Mitt namn är %s; James %s.\r\n", data, data);
}
void menu_10_func(char *data)
{
puts("\tVrooom\r");
};
void menu_11_func(char *data)
{
puts("\t*gnissel* *host* *plonk*\r");
};
void menu_2_func(char *data)
{
puts("\tMjau\r");
}
void init_nodes(node_s *c, node_s *parent)
/* Set up pointers to parent so we don't have to do that explicitly */
{
int j;
int cc = c->childcount;
if(NULL != parent) {
c->parent = parent;
}
if(cc>0) {
for(j=0; j<cc; j++) {
node_s *t = c->children_or_callback[j];
if(NULL != t) {
init_nodes(t,c);
}
}
}
}
/* This is a global that points to the current selected menu node.
I thought a global makes the code shorter and easier to read. */
node_s *current_menu_item;
void print_current_menu_level(void)
/* print the submenues at the current selected level */
{
node_s *c = current_menu_item;
printf("-%s-\r\n", c->name);
for(int i=0; i<c->childcount; i++) {
node_s *t = c->children_or_callback[i];
if(t->childcount > 0) {
printf("\t%d %s >>\r\n", i+1, t->name);
} else {
printf("\t%d %s\r\n", i+1, t->name);
}
}
printf("\n");
}
void init_data(node_s *tree) {
/* this is just a bunch of data initialization, but I thought
writing it out in full demonstrates the problem with C not
having any nice way of defining inline data. In a real
situation you would use macros or load it from a binary file.
Remember, data defined in a function needs to be static so that
it will not go out of scope when the function exits.
Childcount could be derived from the init arrays but that seems
to require non-standard compiler language extensions. */
static node_s menu_00, menu_01, menu_10, menu_11;
menu_00.name = "Banan";
menu_00.children_or_callback = (void *)&generic_menu_func;
menu_00.childcount=0;
menu_01.name = "Persika";
menu_01.children_or_callback = (void *)&generic_menu_func;
menu_01.childcount=0;
menu_10.name = "Porsche";
menu_10.children_or_callback = (void *)&menu_10_func;
menu_10.childcount=0;
menu_11.name = "Lada";
menu_11.children_or_callback = (void *)&menu_11_func;
menu_11.childcount=0;
static node_s menu_0_children[] = {&menu_00, &menu_01};
static node_s menu_1_children[] = {&menu_10, &menu_11};
static node_s menu_0, menu_1, menu_2;
menu_0.name = "Frukter";
menu_0.childcount = 2;
menu_0.children_or_callback = &menu_0_children;
menu_1.name = "Bilar";
menu_1.childcount = 2;
menu_1.children_or_callback = &menu_1_children;
menu_2.name = "Katt";
menu_2.childcount = 0;
menu_2.children_or_callback = &menu_2_func;
tree->name = "Menu";
tree->childcount = 3;
static node_s treechildren[] = {&menu_0, &menu_1, &menu_2};
tree->children_or_callback = treechildren;
init_nodes(tree, NULL); //traverse tree, set parents
}
int main(void)
{
node_s tree; /* the top node of the menu tree */
init_data(&tree); /* edit init_data to set menu items */
current_menu_item = &tree;
#ifdef __linux__
// make ncurses play nice
initscr();
timeout(-1);
noecho();
refresh();
#endif
printf("1-9 to select, backspace to go back, q to quit\r\n\n");
while(1) { /* main loop */
print_current_menu_level();
char choice = getch();
switch (choice) {
case 127: /* backspace key goes up in menu hierarchy */
if(NULL != current_menu_item->parent) {
current_menu_item = current_menu_item->parent;
}
break;
case 27:
case 'Q':
case 'q':
#ifdef __linux__
endwin();
#endif
exit(0);
default:
if(choice >= '1' && choice <= '9') {
int selected = choice - '1'; /* get index 0-8 */
if(selected >= 0 && selected < current_menu_item->childcount) {
node_s *t = current_menu_item->children_or_callback[selected];
if(t->childcount > 0) { /* if node is not a leaf */
current_menu_item = t;
} else { /* else execute leaf node's function */
menu_funcptr *f = t->children_or_callback;
(*f)(t->name);
}
}
}
}
}
}