代码之家  ›  专栏  ›  技术社区  ›  Abhinav Upadhyay

是否使用goto?

  •  20
  • Abhinav Upadhyay  · 技术社区  · 16 年前

    我正在尝试实现一个有限状态自动机来解析C中的某个字符串。当我开始编写代码时,我意识到如果我使用标签来标记不同的状态,并根据情况使用goto从一个状态跳转到另一个状态,代码的可读性可能会更高。

    什么方法更好?我最担心的是,这会给我的老板留下不好的印象,因为我正在实习。

    9 回复  |  直到 10 年前
        1
  •  19
  •   Jerry Coffin    16 年前

    使用 goto state 修改的变量,以及 switch 基于此的声明:

    typedef enum {s0,s1,s2,s3,s4,...,sn,sexit} state;
    
    state nextstate;
    int done = 0;
    
    nextstate = s0;  /* set up to start with the first state */
    while(!done)
       switch(nextstate)
          {
             case s0:
                nextstate = do_state_0();
                break;
             case s1:
                nextstate = do_state_1();
                break;
             case s2:
                nextstate = do_state_2();
                break;
             case s3:
                 .
                 .
                 .
                 .
             case sn:
                nextstate = do_state_n();
                break;
             case sexit:
                done = TRUE;
                break;
             default:
                /*  some sort of unknown state */
                break;
          }
    
        2
  •  36
  •   bta    16 年前

    没有什么内在的错误 goto 语句,同时保持代码的干净、可读性和无bug,从而为您提供更多的功能。

    使用 转到

    转到 方法(取决于编译器的优化级别)。这个 方法可以看作是从状态变量方法中优化出额外的变量和循环。你使用哪种方法是个人选择的问题,只要你能产生工作的、可读的代码,我希望你的老板不会认为你使用一种方法而不是另一种方法有什么不同。

    如果要将此代码添加到已包含状态机的现有代码库中,我建议您遵循已在使用的约定。

        3
  •  15
  •   Vinko Vrsalovic    16 年前

    Ragel 如果我想给老板留下好印象的话。

    这种方法的主要好处是,您能够在更高的抽象级别上描述您的状态机,并且不需要担心是使用goto还是switch。更不用说在Ragel的特殊情况下,您可以自动获得FSM的漂亮图表,在任何点插入操作,自动最小化状态量和各种其他好处。我提到过生成的fsm也非常快吗?

        4
  •  10
  •   R Samuel Klatchko    16 年前

    我会使用一个变量来跟踪您所处的状态,并使用一个开关来处理它们:

    fsm_ctx_t ctx = ...;
    state_t state = INITIAL_STATE;
    
    while (state != DONE)
    {
        switch (state)
        {
        case INITIAL_STATE:
        case SOME_STATE:
            state = handle_some_state(ctx)
            break;
    
        case OTHER_STATE:
            state = handle_other_state(ctx);
            break;
        }
    }
    
        5
  •  8
  •   Femaref    16 年前

    Goto不是必要的邪恶,我不得不强烈反对丹尼斯,是的Goto可能是一个坏主意,在大多数情况下,但有使用。goto最大的恐惧就是所谓的“spagetti代码”,即无法追踪的代码路径。如果您可以避免这种情况,并且代码的行为总是很清楚,并且您没有使用goto跳出函数,那么就没有什么不利于goto的了。只要小心使用它,如果你想使用它,真的评估情况,并找到更好的解决办法。如果你不能做到这一点,goto可以使用。

        6
  •  8
  •   wallyk    16 年前

    避免 goto

    在实际工程问题中,goto的使用空间非常有限。学术界和非工程界人士在使用上不必要地绞尽脑汁 转到 . 也就是说,如果你把自己描绘成一个实现的角落 转到 是唯一的出路,重新思考解决方案。

    正确的解决方案通常是首要目标。使其正确 和 可维护性(通过最小化复杂性)有许多生命周期好处。先让它起作用,然后逐步清理,最好是通过简化和去除丑陋。

        7
  •  4
  •   Aidan Cully    16 年前

    我不知道你的具体代码,但有这样的原因吗:

    typedef enum {
        STATE1, STATE2, STATE3
    } myState_e;
    
    void myFsm(void)
    {
        myState_e State = STATE1;
    
        while(1)
        {
            switch(State)
            {
                case STATE1:
                    State = STATE2;
                    break;
                case STATE2:
                    State = STATE3;
                    break;
                case STATE3:
                    State = STATE1;
                    break;
            }
        }
    }
    

    goto

    编辑: 所有这些 State =

    typedef int (*myStateFn_t)(int OldState);
    
    int myStateFn_Reset(int OldState, void *ObjP);
    int myStateFn_Start(int OldState, void *ObjP);
    int myStateFn_Process(int OldState, void *ObjP);
    
    myStateFn_t myStateFns[] = {
    #define MY_STATE_RESET 0
       myStateFn_Reset,
    #define MY_STATE_START 1
       myStateFn_Start,
    #define MY_STATE_PROCESS 2
       myStateFn_Process
    }
    
    int myStateFn_Reset(int OldState, void *ObjP)
    {
        return shouldStart(ObjP) ? MY_STATE_START : MY_STATE_RESET;
    }
    
    int myStateFn_Start(int OldState, void *ObjP)
    {
        resetState(ObjP);
        return MY_STATE_PROCESS;
    }
    
    int myStateFn_Process(int OldState, void *ObjP)
    {
        return (process(ObjP) == DONE) ? MY_STATE_RESET : MY_STATE_PROCESS;
    }
    
    int stateValid(int StateFnSize, int State)
    {
        return (State >= 0 && State < StateFnSize);
    }
    
    int stateFnRunOne(myStateFn_t StateFns, int StateFnSize, int State, void *ObjP)
    {
        return StateFns[OldState])(State, ObjP);
    }
    
    void stateFnRun(myStateFn_t StateFns, int StateFnSize, int CurState, void *ObjP)
    {
        int NextState;
    
        while(stateValid(CurState))
        {
            NextState = stateFnRunOne(StateFns, StateFnSize, CurState, ObjP);
            if(! stateValid(NextState))
                LOG_THIS(CurState, NextState);
            CurState = NextState;
        }
    }
    

    州= 在前面的代码中。

        8
  •  1
  •   jdehaan    16 年前

    Dragon book ": Compilers, Principles-Techniques-Tools goto . 通常状态是一个数据表,转换是函数,如 accept_space()

        9
  •  1
  •   Cervo    16 年前

    但除此之外,应该没有太大的区别。不管怎样,如果你没有把它分解成函数,并且不是每个状态都使用/初始化所有的局部变量,你可能会得到一堆乱七八糟的代码,不知道哪些状态改变了哪些变量,这使得调试/推理变得非常困难。