Использовать goto или нет?

Попробуйте использовать это: http://home.wlu.edu/~levys/software/kbhit.py Это неблокирование (это означает, что вы можете иметь цикл while и обнаруживать ключ нажмите, не останавливая его) и кросс-платформенный.

import os

# Windows
if os.name == 'nt':
    import msvcrt

# Posix (Linux, OS X)
else:
    import sys
    import termios
    import atexit
    from select import select


class KBHit:

    def __init__(self):
        '''Creates a KBHit object that you can call to do various keyboard things.'''

        if os.name == 'nt':
            pass

        else:

            # Save the terminal settings
            self.fd = sys.stdin.fileno()
            self.new_term = termios.tcgetattr(self.fd)
            self.old_term = termios.tcgetattr(self.fd)

            # New terminal setting unbuffered
            self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)

            # Support normal-terminal reset at exit
            atexit.register(self.set_normal_term)


    def set_normal_term(self):
        ''' Resets to normal terminal.  On Windows this is a no-op.
        '''

        if os.name == 'nt':
            pass

        else:
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)


    def getch(self):
        ''' Returns a keyboard character after kbhit() has been called.
            Should not be called in the same program as getarrow().
        '''

        s = ''

        if os.name == 'nt':
            return msvcrt.getch().decode('utf-8')

        else:
            return sys.stdin.read(1)


    def getarrow(self):
        ''' Returns an arrow-key code after kbhit() has been called. Codes are
        0 : up
        1 : right
        2 : down
        3 : left
        Should not be called in the same program as getch().
        '''

        if os.name == 'nt':
            msvcrt.getch() # skip 0xE0
            c = msvcrt.getch()
            vals = [72, 77, 80, 75]

        else:
            c = sys.stdin.read(3)[2]
            vals = [65, 67, 66, 68]

        return vals.index(ord(c.decode('utf-8')))


    def kbhit(self):
        ''' Returns True if keyboard character was hit, False otherwise.
        '''
        if os.name == 'nt':
            return msvcrt.kbhit()

        else:
            dr,dw,de = select([sys.stdin], [], [], 0)
            return dr != []

Пример использования этого:

import kbhit

kb = kbhit.KBHit()

while(True): 
    print("Key not pressed") #Do something
    if kb.kbhit(): #If a key is pressed:
        k_in = kb.getch() #Detect what key was pressed
        print("You pressed ", k_in, "!") #Do something
kb.set_normal_term()

Или вы могли бы использовать модуль g6 getch из PyPi . Но это блокирует цикл while

20
задан Peter Mortensen 16 May 2016 в 06:49
поделиться

9 ответов

Использование goto для реализации конечного автомата часто имеет смысл. Если вас действительно беспокоит использование goto, разумной альтернативой часто является изменяемая вами переменная состояния и оператор 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;
      }
19
ответ дан 29 November 2019 в 22:26
поделиться

В goto нет ничего плохого по своей сути. Причина, по которой они часто считаются "запретными", заключается в том, что некоторые программисты (часто пришедшие из мира ассемблера) используют их для создания "спагетти" кода, который практически невозможно понять. Если вы можете использовать операторы goto, сохраняя при этом чистоту, читабельность и отсутствие ошибок в коде, то да будет вам счастье.

Использование операторов goto и участка кода для каждого состояния - это определенно один из способов написания машины состояний. Другой способ заключается в создании переменной, которая будет хранить текущее состояние, и использовании оператора switch (или аналогичного) для выбора блока кода для выполнения на основе значения переменной состояния. Хороший шаблон второго метода см. в ответе Эйдана Калли.

В действительности эти два метода очень похожи. Если вы напишете машину состояний, используя метод переменных состояния, и скомпилируете ее, то сгенерированный ассемблер может очень напоминать код, написанный с использованием метода goto (в зависимости от уровня оптимизации вашего компилятора). Метод goto можно рассматривать как оптимизацию дополнительной переменной и цикла из метода переменных состояния. Какой метод вы используете - это вопрос личного выбора, и пока вы создаете рабочий, читабельный код, я надеюсь, что ваш начальник не будет думать о вас иначе за использование одного метода вместо другого.

Если вы добавляете этот код в существующую базу кода, которая уже содержит машины состояний, я бы рекомендовал вам следовать той конвенции, которая уже используется.

36
ответ дан 29 November 2019 в 22:26
поделиться

Я бы использовал переменную, которая отслеживает, в каком состоянии вы находитесь, и переключатель для их обработки:

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;
    }
}
10
ответ дан 29 November 2019 в 22:26
поделиться

Goto не является обязательным злом, и я не могу не согласиться с Денисом, да, goto может быть плохой идеей в большинстве случаев, но ему есть применение. Самый большой страх с goto - это так называемый "спагетти-код", неотслеживаемые пути кода. Если вы можете этого избежать и если всегда будет понятно, как ведет себя код, и вы не выскочите из функции с помощью goto, то ничего против goto не имею. Просто используйте его с осторожностью, и если у вас возникнет соблазн использовать его, реально оцените ситуацию и найдите лучшее решение. Если вы не можете этого сделать, можно использовать goto.

8
ответ дан 29 November 2019 в 22:26
поделиться

Я бы порекомендовал вам « Dragon book »: Составители, принципы-методы-инструменты от Ахо, Сетхи и Уллмана. (Достаточно дорого, но вы наверняка найдете в библиотеке). Там вы найдете все, что вам нужно для синтаксического анализа строк и построения конечных автоматов. Я не могу найти места с goto . Обычно состояния представляют собой таблицу данных, а переходы - это функции типа accept_space ()

1
ответ дан 29 November 2019 в 22:26
поделиться

Я не вижу особой разницы между goto и switch. Я бы предпочел switch/while, потому что он дает вам гарантированное место для выполнения после перехода (где вы можете вести логирование и рассуждать о своей программе). В GOTO вы просто продолжаете переходить от метки к метке, поэтому, чтобы добавить логирование, вам придется поместить его на каждую метку.

Но кроме этого, особой разницы быть не должно. В любом случае, если вы не разбили его на функции и не каждое состояние использует/инициализирует все локальные переменные, вы можете получить в итоге беспорядочный, почти спагетти-код, не знающий, какие состояния изменили какие переменные, что очень затруднит отладку/рассуждения.

В качестве отступления, может быть, вы можете разобрать строку с помощью регулярного выражения? Большинство языков программирования имеют библиотеки, позволяющие их использовать. Регулярные выражения часто создают FSM как часть своей реализации. Обычно регулярные выражения работают для не произвольно вложенных элементов, а для всего остального есть генератор парсера (ANTLR/YACC/LEX). Как правило, гораздо проще поддерживать грамматику/регекс, чем лежащую в основе государственную машину. Кроме того, вы сказали, что проходите стажировку, а обычно они могут дать вам более легкую работу, чем, скажем, старшему разработчику, так что есть большая вероятность, что регекс может работать над строкой. Кроме того, регулярным выражениям обычно не уделяют особого внимания в колледже, поэтому попробуйте использовать Google, чтобы почитать о них.

1
ответ дан 29 November 2019 в 22:26
поделиться

Я не знаю ваш конкретный код, но есть ли причина, по которой что-то вроде этого:

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, и его относительно легко выполнить.

Edit: Все эти State = фрагменты нарушают DRY, поэтому я мог бы вместо этого сделать что-то вроде:

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;
    }
}

что, конечно, намного длиннее, чем первая попытка (забавная штука DRY). Но это также более надежно - неспособность вернуть состояние из одной из функций состояния приведет к предупреждению компилятора, а не молчаливому игнорированию отсутствующего State = в предыдущем коде.

4
ответ дан 29 November 2019 в 22:26
поделиться

Я бы использовал генератор FSM, например Ragel, если бы хотел произвести хорошее впечатление на своего босса.

Главное преимущество этого подхода в том, что вы можете описать свою машину состояний на более высоком уровне абстракции и вам не нужно беспокоиться о том, использовать ли goto или switch. Не говоря уже о том, что в конкретном случае с Ragel вы можете автоматически получать красивые диаграммы вашей FSM, вставлять действия в любой момент, автоматически минимизировать количество состояний и получать различные другие преимущества. Я уже упоминал, что сгенерированные FSM также очень быстры?

Недостатки: их сложнее отлаживать (здесь очень помогает автоматическая визуализация) и вам нужно изучить новый инструмент (что, вероятно, не стоит того, если у вас простая машина и вы вряд ли будете часто писать машины)

.
15
ответ дан 29 November 2019 в 22:26
поделиться

Избегайте goto , если добавленная сложность (во избежание) более запутанная.

В практических инженерных задачах есть место для goto, которое используется очень экономно. Ученые и не инженеры без нужды заламывают пальцы, используя goto . Тем не менее, если вы попадаете в угол реализации, где большое количество goto является единственным выходом, переосмыслите решение.

Правильно работающее решение обычно является основной задачей. Если сделать его правильным и поддерживаемым (за счет минимизации сложности), это дает много преимуществ в течение жизненного цикла. Сначала заставьте его работать, а затем постепенно очищайте его, желательно упрощая и удаляя безобразие.

8
ответ дан 29 November 2019 в 22:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: