我有一个使用Lua编写脚本的C程序。为了保持可读性并避免在各个Lua状态中导入几个常量,我在一个简单的调用中压缩了大量函数(例如“ObjectSet(id,”ANGLE“,45)”),通过使用“action”字符串。
为此,我有一个大的if树,将动作字符串与列表进行比较(例如“if(stringcompare(action,”ANGLE“)... else if(stringcompare(action,”X“)...等“)
这种方法效果很好,在程序中它并不是很慢,而且添加新动作相当快。但我觉得完美主义者。在C中有更好的方法吗?
让Lua大量使用,也许有办法将它用于此目的? (嵌入式“块”制作字典?)虽然这部分主要是好奇心。
编辑:我应该注意我没有使用C ++
答案 0 :(得分:4)
您可以切换到使用枚举和案例陈述。虽然该函数只是用大的case语句替换if树,但是每次比较都不需要进行字符串比较,而只需要进行数字比较。
typedef enum objectsetAction {
objectset_angle = 0,
objectset_other,
...
} objectsetAction;
然后定义ObjectSet函数以获取objectsetAction参数而不是字符串。
这样你就可以写
了switch(action) {
case objectset_angle:
//dostuff
break;
case objectset_other:
//dostuff
break;
...
}
答案 1 :(得分:1)
你可以自己构建一个automata来逐个字符地检查,比如
if (curChar == 'a')
{
curChar = next;
if (curChar == 'b')
{
// all strings that begin with "ab"
}
}
这将优化具有O(n)的比较,而不是整个if链的O(n * m)。当然,你不必手动完成:你可以搜索一个自动化工具,它可以根据你需要的字符串构建匹配的自动机。实际上这与正则表达式匹配器的工作类似。
否则,如果你想要 O(1) O(nlogn)(抱歉,认为它使用的是散列图而不是二叉树),你可以使用std::map
并将字符串存储为键和函数作为值的指针,它们表示每个不同字符串执行的行为。这样使用O(1)就可以获得正确的指针,然后调用该函数。
好的,所以你只需要C.你必须实现自己的hashmap(或使用一个先前存在的hashmap,你可以找到很多)或二叉树结构。
使用前一种方法,你将有效地拥有O(1)(除非hashmap与字符串的数量相比太小),而另一种方法你最终会得到一个自动机的微笑方法。
对于您只需要的hashmap:
int hash = string[0]^31 + string[1]^30 + ...
即可获得代表字符串的唯一数字。当然这个解决方案对于像你这样的简单任务而言非常大,另一方面它会很有趣,它会教你很多东西......但是如果你真的需要它会三思而后行!
很抱歉,如果我建议使用C ++但不考虑您是否询问普通C,std::map
是标准模板库的一个组件,用于存储值<key, value>
对。在您的情况下,您将拥有<string, funcPointer>
,但正如评论中所建议的,复杂性将是O(nlogn)来搜索它们。
答案 2 :(得分:1)
由于您已经嵌入了Lua并且可用,因此您可以使用它。 Lua表是一个关联数组,可以通过任何Lua值(nil
除外)进行索引并存储任何值。字符串作为键可以很好地工作,并且可以用作值。
您可以轻松将ObjectSet(id, "ANGLE", 45)
之类的通话转换为actions.ANGLE(id,45)
等通话。
为此,请安排创建包含用于实现每个操作的函数的actions
表。简单的方法可能涉及一块初始化表的Lua代码,但它当然也可以从C方面完成。
actions = { ANGLE = function(id,theta) -- do something with id and theta end, X = function (id, x) -- do something with id and x end, }
或者可能更清楚
module("actions") function ANGLE(id,theta) -- ... end function X(id,theta) -- ... end
从C开始,你可以实现ObjectSet()
这样的事情(未经测试):
void ObjectSet(int id, const char *action, int arg) {
lua_getglobal(L,"actions");
lua_getfield(L,-1,action);
lua_remove(L,-2);
lua_pushinteger(L,arg);
if (lua_pcall(L,1,0,0)) {
fprintf(stderr, "Lua error: %s\n", lua_tostring(L,-1));
}
return;
}
真正的错误处理留作练习。请注意,此处使用lua_pcall()
,以便Lua错误不会传播出ObjectSet()
。如果您使用的是C ++,则需要小心,因为Lua使用setjmp()
和longjmp()
来处理错误,这些错误通常必须通过捕获Lua错误并抛出适当的异常来转换为C ++异常。
我自然也将对象id与Lua侧的实际对象关联起来作为练习。但是,您可以在C中的actions
表中实现所有函数,并在很大程度上避开此问题。
答案 3 :(得分:1)
很难准确地说出什么可能更好,因为你不清楚你的约束是什么。其他答案显示如果您愿意将更多符号导出到Lua或将更多工作委托给Lua,您可以做些什么。我的回答解决了一个狭隘的问题:如果不改变与Lua交互的方式,你怎么能重构你的C代码?我建议你让代码表驱动。
这是一个设计草图:
typedef struct action {
const char *name;
int (*doit)(lua_State *L, int idx);
} *Action;
static Action my_actions[] = {
{ "angle", set_angle },
{ "color", set_color },
...
{ NULL, NULL }
};
int i_replace_nest_of_ifs(lua_State *L) {
const char *action = luaL_checkstring(L, 1);
for (int i = 0; my_actions[i].name && strcmp(my_actions[i].name, action); i++)
;
if (my_actions[i].name)
return my_actions[i].doit(L, 2);
else
return luaL_error("Asked for unknown action '%s'", action);
}
如果线性搜索操作过于昂贵,您可以在打开库时按名称排序,然后调用bsearch
。
答案 4 :(得分:0)
你可以随时使用三元运算符。例如,而不是做像
这样的事情 if(condition){
a=b+c;
}else{
a=b+d;
}
你可以用
替换它 a=b+(condition?c:d);
有很多小的情况可以减少代码(以较低的可读性和无实际的速度提升)。但如果没有,没有任何真正的方法可以做得更好。例外情况是,如果您可以将其描述为状态机,那么您可以使用交换机案例系统。
答案 5 :(得分:0)
简单方法:
一个简单的替代方法是用你定义的enum
替换你的“动作字符串”;除了switch
(基本上是一个整数)上的enum
语句比进行字符串比较要快得多之外,它的工作方式完全相同。与switch
- 树一样,if
看起来也会比你描述的更漂亮( imo:)。
强大的方式:
更简洁的解决方案是使用function pointers - 只要您需要执行的所有不同操作都包含在具有相同签名的单独函数中,您可以简单地绑定相应的函数,并将其调用自动。
答案 6 :(得分:0)
有一个名为trie的数据结构,可以快速以内存有效的方式选择字符串。如果这是一个巨大的性能瓶颈,并且你传递了一些你不可能改变的字符串,这可能是正确的做法。
但是我觉得它太复杂了。如果您在lua和C中保持某种枚举同步并执行切换案例或为其创建跳转表,它将提供更好的性能并且更容易开发。
答案 7 :(得分:0)
由于您指定C而不是C ++,因此排序并行数组:
#define VERB_COUNT 10
void *context_data;
char *verbs[VERB_COUNT] = { "ABC", "DEF", "GHI", ... }; // sorted list
int (*actions[VERB_COUNT])(void *) = { abc_func, def_func, ghi_func, ... };
int idx, ret = -1;
int idx = bsearch(action, verbs, VERB_COUNT, sizeof char*, strcmp); // I think I got these in the right order
if (idx >= 0)
ret = (*actions[idx])(context_data);
return ret;