我提出了以下选项:
使用goto语句:
Start:
goto Data
Data:
goto Finish
Finish:
;
使用switch语句:
switch(m_state) {
case State.Start:
m_state = State.Data;
break;
case State.Data:
m_state = State.Finish;
break;
case State.Finish:
break;
}
使用goto并一起切换:
switch(m_state) {
case State.Start:
goto case State.Data2;
case State.Data1:
goto case State.Finish;
case State.Data2:
m_state = State.Data1;
//call to a function outside the state machine
//that could possibly change the state
break;
case State.Finish:
break;
}
我更喜欢使用goto语句的第一个选项,因为它更快,更简洁。但我不确定它是不是最好的选择。表现明智,但是当谈到可读性我不知道。这就是我问这个问题的原因。您更喜欢哪个选项?为什么?
答案 0 :(得分:3)
我更喜欢相互调用/递归函数。为了适应你的榜样:
returnvalue Start() {
return Data();
}
returnvalue Data() {
return Finish();
}
returnvalue Finish() {
…
}
理论上,此可以完全内联,以便编译器输出等同于goto
解决方案(因此速度相同)。实际上,the C# compiler /JITter probably won’t do it。但是由于解决方案的可读性更高(嗯,恕我直言),我只会在经过非常仔细的基准测试后将其替换为goto
解决方案,证明它 在速度方面确实较差,或者发生堆栈溢出(不是在这个简单的解决方案中,但是更大的自动机会遇到这个问题)。
即使这样,我也肯定坚持goto case
解决方案。为什么?因为那时你的整个凌乱的goto
意大利面被封装在一个块状结构(switch
块)中,你的意大利面条不会破坏其余的代码,阻止了博洛尼亚。
总之:功能变体很明显,但通常容易出问题。 goto
解决方案很麻烦。只有goto case
提供了一个干净,高效的解决方案。如果性能确实是最重要的(并且自动机是瓶颈),那么请选择结构化的goto case
变体。
答案 1 :(得分:2)
切换goto的优点是你在变量中有状态,而不仅仅是在指令指针中。
使用goto方法,状态机必须是控制其他所有内容的主循环,因为你不会因为失去状态而退出它。
使用switch方法,状态机是隔离的,您可以到任何想要处理外部事件的地方。当你返回状态机时,它会继续在yuu停止的地方继续。您甚至可以并排运行多个状态机,这是goto版本无法实现的。
我不确定你选择第三种替代方案,它看起来就像第一种替代方案,它周围有一个无用的开关。
答案 2 :(得分:2)
如果您想将状态机转换逻辑分解为单独的函数,则只能使用switch语句来执行此操作。
switch(m_state) {
case State.Start:
m_state = State.Data;
break;
case State.Data:
m_state = ComputeNextState();
break;
case State.Finish:
break;
}
它也更具可读性,并且switch语句(与Goto相比)的开销只会在极少数情况下产生性能差异。
编辑:
您可以使用“转到大小写”来提高性能:
switch(m_state) {
case State.Start:
m_state = State.Data; // Don't forget this line!
goto case State.Data;
case State.Data:
m_state = ComputeNextState();
break;
case State.Finish:
break;
}
但是你冒着忘记更新状态变量的风险。这可能会在以后导致细微的错误(因为你假设“m_state”被设置),所以我建议避免它。
答案 3 :(得分:2)
答案 4 :(得分:0)
我个人更喜欢第二个使用goto,因为第一个需要不必要的循环步骤(例如)才能进入新状态