3

我想出了以下选项:

使用 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:

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 语句的第一个选项,因为它更快且更简洁。但我不确定这是否是最佳选择。也许性能明智,但谈到可读性我不知道。这就是我问这个问题的原因。你更喜欢哪个选项,为什么?

4

5 回答 5

3

我更喜欢相互调用/递归函数。要调整您的示例:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

从理论上讲,这可以完全内联,以便编译器输出与您的goto解决方案等效(因此,速度相同)。实际上,C# 编译器 /JITter 可能不会这样做。但是由于该解决方案更具可读性(嗯,恕我直言),我只会goto在非常仔细的基准测试证明它在速度方面确实较差,或者发生堆栈溢出(不是在这个简单的解决方案中而是更大的自动机遇到这个问题)。

即便如此,我肯定会坚持goto case解决方案。为什么?因为那时你的整个凌乱的goto意大利面被很好地包裹在一个块结构(switch块)内,你的意大利面不会破坏其余的代码,防止肉酱。

总之:功能变体很清楚,但通常容易出现问题。goto解决方案很混乱。只goto case提供半干净、高效的解决方案。如果性能确实是最重要的(而自动机是瓶颈),那就选择结构化的goto case变体。

于 2009-10-24T12:51:38.127 回答
2

切换到 goto 的优点是您在变量中拥有状态,而不仅仅是在指令指针中。

使用 goto 方法,状态机必须是控制其他一切的主循环,因为您无法离开它,因为您会丢失状态。

使用 switch 方法,状态机被隔离,你可以去任何你想从外部处理事件的地方。当您返回状态机时,它会从 yuu 停止的地方继续。您甚至可以同时运行多个状态机,这在 goto 版本中是不可能的。

我不确定您将使用第三个替代方案去哪里,它看起来就像第一个替代方案一样,周围有一个无用的开关。

于 2009-10-24T12:50:58.350 回答
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)的开销只会在极少数情况下产生性能差异。

编辑:

您可以使用“goto case”来进行小的性能改进:

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”),所以我建议避免它。

于 2009-10-24T13:06:18.570 回答
2

还有第四个选项。

使用迭代器来实现状态机。这是一篇不错的短文,向您展示如何

虽然它有一些缺点。从迭代器外部操作状态是不可能的。

我也不确定它是否很快。但是你总是可以做一个测试。

于 2009-10-24T13:12:41.677 回答
0

我个人更喜欢带有 goto 的第二个,因为第一个将需要不必要的循环步骤(例如)才能进入新状态

于 2009-10-24T12:37:49.547 回答