Перейти к содержимому

Goto что это в программировании

  • автор:

Оператор goto (C++)

Оператор goto безусловно передает управление инструкции, помеченной указанным идентификатором.

Синтаксис

goto identifier; 

Замечания

Оператор, метка которого задана в параметре identifier , должен находиться в текущей функции. Все имена, заданные в параметре identifier , являются членами внутреннего пространства имен и, следовательно, не пересекаются с другими идентификаторами.

Метка оператора имеет смысл только для goto оператора. В противном случае метки операторов игнорируются. Повторное объявление меток невозможно.

Оператор goto не может передавать управление в расположение, пропускающее инициализацию любой переменной, которая находится в область в этом расположении. В следующем примере возникает ошибка C2362:

int goto_fn(bool b) < if (!b) < goto exit; // C2362 >else < /*. */ >int error_code = 42; exit: return error_code; > 

Это хороший стиль программирования для использования break continue операторов , а return не инструкций goto , когда это возможно. Тем не менее, поскольку break оператор выходит из только одного уровня цикла, может потребоваться использовать goto оператор для выхода из глубоко вложенного цикла.

Дополнительные сведения о метках и goto инструкции см. в разделе «Операторы с метками».

Пример

В этом примере goto оператор передает элемент управления в точку, помеченную stop i при значении 3.

// goto_statement.cpp #include int main() < int i, j; for ( i = 0; i < 10; i++ ) < printf_s( "Outer loop executing. i = %d\n", i ); for ( j = 0; j < 2; j++ ) < printf_s( " Inner loop executing. j = %d\n", j ); if ( i == 3 ) goto stop; >> // This message does not print: printf_s( "Loop exited. i = %d\n", i ); stop: printf_s( "Jumped to stop. i = %d\n", i ); > 
Outer loop executing. i = 0 Inner loop executing. j = 0 Inner loop executing. j = 1 Outer loop executing. i = 1 Inner loop executing. j = 0 Inner loop executing. j = 1 Outer loop executing. i = 2 Inner loop executing. j = 0 Inner loop executing. j = 1 Outer loop executing. i = 3 Inner loop executing. j = 0 Jumped to stop. i = 3 

GOTO в прикладном программировании

Мотивы для использования GOTO и альтернативы ему принципиально отличаются для системного и прикладного программирования — это является и важной причиной холиваров. Для прояснения ситуации рассмотрим GOTO только в разрезе прикладного программирования.

Основной тезис: в прикладном программировании GOTO однозначно лучше обходить.

  1. В прикладном программировании критически важен один параметр кода — сопровождаемость.
  2. Goto не ухудшает однозначно сопровождаемость только в небольшом проценте случаев, и даже в этих случаях принципиально от альтернатив не отличается
  3. Ради небольшого процента случаев его использовать вредно:
    1) очень низкоуровневое, поэтому сильно развращает программиста (возникает соблазн использовать и в других местах) — большой вред из-за небольшого процента случаев, когда GOTO можно применить;
    2) даже в таких случаях есть более красивые альтернативы.
GOTO — свойства и влияние на качество кода
Параметры качества кода
  1. Потребление ресурсов (памяти, процессорных тактов) — приоритет «от машины»
  2. Сопровождаемость (легкость сопровождения) кода — приоритет «от человека»:
    1) читаемость кода — насколько легко понять написанный код (соответственно, насколько легко и написать, и отладить),
    2) ошибкоустойчивость при изменениях — насколько сложно внести ошибку/насколько легко ее заметить при изменении кода,
    3) легкость поддержки общего стандарта — насколько написание кода приучает программиста к отклонениям от неких общих стандартов
Общие свойства GOTO:
  1. неструктурировано: можно вставить в почти произвольное место, сложно понять, как мы туда попали, в отличие от остальных конструкций ветвления;
  2. закрепощает исходный код: если структурированные блоки можно менять по-разному, перестраивать их порядок, как в конструкторе, то GOTO — это гвоздь, соединяющий какие-то блоки конструктора — после его внедрения код изменить уже очень непросто.
Что не является GOTO:
  1. другие конcтрукции управления потоком выполнения — if,switch,while и т.п.: в них всех ветвления потока жестко заданы синтаксисом, находятся на границе того же блока — верхней или нижней (для return — граница функции), а GOTO можно размещать произвольно
  2. автоматически сгенерированный код — как и в сгенерированном ассемблере, в нем копаться и его поддерживать не приходится.
Особенности GOTO в прикладном программировании

Прикладное программирование здесь — программирование на языках высокого уровня, поддерживающих структурирование кода, в том числе структурный подход к обработке исключений: Java, C#, C++, интерпретируемые языки и т.п. — в общем, стандартный прикладной мэйнстрим. C не рассматриваю как низкоуровневый язык, используемый сейчас в основном для системного программирования.

Особенности прикладного программирования:
  1. нет необходимости в точечных оптимизациях — отдельных тактов или ячеек памяти, поэтому экономию ресурсов можно отбросить из рассмотрения GOTO — остается только сопровождаемость
  2. есть возможность как угодно структурировать логику — как угодно объединять в функции/методы, заводить сколько угодно переменных, классов и т.п. с любыми названиями, возможность бросать исключения
GOTO только ухудшает сопровождаемость кода

В системном программировании важна максимальная экономия ресурсов, поэтому там, возможно, применение GOTO для этой цели оправдано.
А в прикладном программировании параметр «потребление ресурсов» можно отбросить, остается только параметр сопровождаемости, который GOTO ухудшает.

GOTO — проблемы и варианты исправлений

Рассмотрим применение GOTO в различных вариантах перемещения по структуре кода и альтернативы ему:

1. Вход в блок извне:
1.1 Вход в «не цикл»:

легко и очевидно переписывается без GOTO:

 if(a) GOTO InsideIf; if(b)
 if(b) < foo(); >if(a || b)
1.2 Вход в цикл:

нельзя: вообще непонятен поток выполнения:

 if(goIntoCycle) GOTO MiddleOfCycle; for(. )
2. Переход внутри одного блока:

нет необходимости, легко переписывается, обычно на if/else:

 if(bNotNeeded) GOTO startFromC: B(); startFromC: C();
 if(bNeeded) < B(); >C();
3. Выход из блока наружу

Это основной случай возможного применения GOTO. Разобьем его на еще более мелкие и рассмотрим подробно на примерах.
Общий подход — максимально декомпозируем: разбиваем на методы по смыслу, логику фиксируем в флагах с говорящими названиями — получаем читабельный и самодокументированный код.

Важные правила:
  1. НЕ ИСПОЛЬЗУЕМ ИСКЛЮЧЕНИЯ:

1) исключения всегда используем для обработки ошибок и внештатных ситуаций, поэтому не используем их для чего-либо еще, чтобы не мозолить глаз;

2) можем случайно «проглотить» исключение с внутреннего уровня вложенности;

3.1. Единственный выход из одного уровня вложенности:

тривиально заменяется if/break и т.п.

3.2. Несколько выходов из одного уровня вложенности:
3.2.1 Обработка ошибок — только через исключения

(надеюсь, это очевидно; если нет — могу объяснить в отдельной статье)

3.2.2 Перебор вариантов — на примере if:
 class Tamagochi < function recreate() < if(wannaEat) < eat(); GOTO Cleanup; >if (wannaDrink) < drink(); GOTO Cleanup; >if(wannaDance) < Dance(); >return HAPPY; //true Cleanup: return washHands() && cleanKitchen(); > >

Проблемы (кроме всегда присущей GOTO неочевидности потока выполнения):
захотели добавить поведение sleep в случаях wannaEat и wannaDance — все, обобшение для wannaEat и wannaDrink разрушено.

Как сделать красиво (сразу расширенный вариант):

 function recreate() < if(wannaEat) < eat(); needCleanup = true; needSleep = true; >if (wannaDrink) < drink(); needCleanup = true; >if(wannaDance) < Dance(); needSleep = true; >result = HAPPY; //true if(needCleanup) result = result && washHands() && cleanKitchen(); if(needSleep) result = result && sleep(); return result; >
3.3. Выход из нескольких уровней вложенности.
3.3.1 Если легко выделить разную логику (разные ответственности):
 class BatchInputProcessor < function processInput(inputRecords) < for (inputRecord in inputRecords) < for (validator in validators) < if (!validator.check(inputRecord) GOTO ValidationFailed; >item = new Item(); for (fieldValue in inputRecord.fieldValues) < setFieldValue(fieldValue.field, fieldValue.value); >itemsToStore.add(item); > return store(itemsToStore); ValidationFailed: log(failedValidation); tryToCorrect(inputRecords); . > >
 function processInput(inputRecords) < allRecordsAreValid = true; for(inputRecord in inputRecords) < recordIsValid = validateRecord(inputRecord, validators); if(!recordIsValid) < allRecordsAreValid = false; break; >else < itemToStore = createItemFromRecord(inputRecord); itemsToStore.add(item); >> if(allRecordsAreValid) < result = store(itemsToStore); >else < log(failedValidation); tryToCorrect(inputRecords); . >>
3.3.2 Сложнее выделить разную логику или при этом усложняется код.

Как правило, это может быть в случае однотипных вложенных циклов:

 function findOrCreateTriadaOfEqual(firstArray, secondArray, thirdArray) < result = null; for(first in firstArray) < for(second in secondArray) < for(third in thirdArray) < if(first == second && second == third) < result = array(first, second, third); GOTO Found: >> > > equal = new Item(); result = array(equal, equal, equal); Found: log(result); return result; >
  1. вынести в отдельную подфункцию с return из внутреннего цикла — самый простой
  2. обобщить — сделать рекурсивную функцию вида findEqualInArrays(arrayOfArrays, currentArrayIndex, currentFoundItemsArray);
  3. «if(result) break» — самый топорный:
 result = null; for(first in firstArray) < for(second in secondArray) < for(third in thirdArray) < if(first == second && second == third) < result = array(first, second, third); break; >> if(result) break; > if(result) break; >

Это — единственный вариант, который смотрится хуже, чем GOTO, и GOTO даже понятнее. Но практически всегда есть и другие варианты.

Для оставшегося исчезающе малого процента случаев, когда других вариантов нет, нужно просто решить, что все равно можно сделать хотя бы флагами, зато гайдлайны будут проще — «Без GOTO!».

Резюме:

Важнее всего — сопровождаемость.

GOTO всегда ухудшает сопровождаемость, поэтому

  1. обработка ошибок через исключения;
  2. декомпозиция — большой метод, решающий много задач, разбивается на маленькие, решающщие отдельные задачи;
  3. фиксация логики (вычисленных условий и выражений) в переменных с говорящими названиями.

Arduino.ru

Условное «перемещение» выполнения программы к определенной метке-указателю в самой программе, при этом пропускается весь код до самой метки, а исполняется — после нее.

Синтаксис:
label: // // какой-либо код // goto label; // переходим к метке label
Замечание по использованию

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

При разумном применении, команда может значительно упростить код программы и сохранить время программиста. Например, в случае необходимости выхода из глубоких циклов for, while, проверок if и прочих многократно вложенных контрукций.

Пример
for(byte r = 0; r < 255; r++)< for(byte g = 255; g >-1; g--) < for(byte b = 0; b < 255; b++)< if (analogRead(0) >250) < goto bailout;>// еще код > > > bailout:
Смотрите также

Оператор безусловного перехода goto

Он переносит выполнение программы к оператору, помеченному меткой метка .

Метка представляет собой идентификатор или целое без знака. Чтобы пометить оператор меткой, необходимо перед оператором указать метку с последующим двоеточием:

Метки должны быть описаны в разделе меток с использованием служебного слова label :

Например, в результате выполнения программы

будет выведено 543210 .

Метка должна помечать оператор в том же блоке, в котором описана. Метка не может помечать несколько операторов.

Переход на метку может осуществляться либо на оператор в том же блоке, либо на оператор в объемлющей конструкции. Так, запрещается извне цикла переходить на метку внутри цикла.

Использование оператора безусловного перехода в программе считается признаком плохого стиля программирования. Для основных вариантов использования goto в язык Паскаль введены специальные процедуры: break — переход на оператор, следующий за циклом, exit — переход за последний оператор процедуры, continue — переход за последний оператор в теле цикла.

Один из немногих примеров уместного использования оператора goto в программе — выход из нескольких вложенных циклов одновременно. Например, при поиске элемента k в двумерном массиве:

var a: array [1..10,1..10] of integer;
.
var found := False;
for var i:=1 to 10 do
for var j:=1 to 10 do
if a[i,j]=k then
begin
found := True;
goto c1;
end;
c1: writeln(found);

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *