while(true)+break и альтернативы
Можно делать так:
{
// ...
if(condition_func()) break;
// ...
}
или так:
{
// ...
}
Можно использовать переменную вместо функции, но это менее изящно, хотя в конечном счёте всё сведётся именно к этой переменной.
Первый способ плох тем, что придётся искать условие выхода внутри тела цикла. И это может породить бесконечный цикл при внесении изменений в код цикла. Второй плох тем, что условие выхода привязано к определённой точке. То есть, он менее гибок.
Может есть ещё какие-то альтернативы? Или может есть какие-то соображения, чтобы не использовать один из подходов вообще?
{
// ...
if(condition_func()) return;
// ...
}
{
// ...
if(condition_func()) break;
// ...
}
Эти тоже имеют свои плюсы и минусы.
а по теме - все зависит от ситуации, например чтение из сокета 1-й вариант 1-го поста. И сдается что все эти выкрутасы компилятор преобразует к единому коду на низком уровне.
По сабжу - последняя форма с for(;; ) по-моему очевидно проигрывает while (true) в наглядности, таких использовать не стоит.
По поводу while/do..while - по возможности я стараюсь не использовать break, поэтому пытаюсь использовать условия циклов, но когда приходится писать что-то вроде такого:
cond = getCond();
if (cond) {
...
}
while (!cond);
...то я вместо повторов условий ставлю просто break/return, как в первом/третьем примерах.
Благодаря тому, что функции мелкие, идея видна сразу и сложнее что-то напутать с условиями выхода.
У for есть 2 очень спорных плюса. Теоретически древние компиляторы (или компиляторы для всяких контроллеров) могут в этом случае создать более оптимальный код. Кроме того, старые сишные компиляторы на while(1) могут предупреждение выдать, что в условии используется константа.
По поводу while/do..while - по возможности я стараюсь не использовать break, поэтому пытаюсь использовать условия циклов, но когда приходится писать что-то вроде такого:
cond = getCond();
if (cond) {
...
}
while (!cond);
...то я вместо повторов условий ставлю просто break/return, как в первом/третьем примерах.
Тут ещё переменная cond мешается. Её придётся создавать и инициализировать где-то за пределами цикла. Не ФП-шно :)
Как компилятор переварит выкрутасы - не очень важно. Более важна читаемость, лаконичность, свобода от "мин замедленного действия".
Блок-схему проще нарисовать с меньшим числом выходов из цикла, но, в конечном итоге, смотреть и работать придется с кодом, а в нем удобнее кучей break'ов оперировать, imvho.
--
Про теоретическую оптимальность для старых/мкшных компилеров не очень понял. =)
Да, это я наверное попутал. Теоретически небесконечный while будет не медленнее, чем for, а do-while даже быстрее, так как ему нужен только один "джамп". Это если без оптимизации. Но теоретически компилятору легче оптимизировать код цикла, в котором число итераций заранее известно. Вот только к бесконечным циклам это не относится...
{
// ...
if(condition_func()) break;
// ...
}
Это выглядит как уродство, или вполне допустимо?
Кстати, если уж говорим о чистом коде - то в while(1) vs. while(true) к первому я отношусь хуже, чем к for(;; ) - что обозначает цифра 1 на первый взгляд (/на нетрезвую голову :D)? Одну итерацию? А после (bool)1 получается, что совсем наоборот.
Так что этой конструкции тоже стоит избегать, ИМХО.
Тут ещё переменная cond мешается. Её придётся создавать и инициализировать где-то за пределами цикла. Не ФП-шно :)
Так нет, в том-то и смысл, что она инициализируется внутри цикла. Просто она проверяется в двух местах + появляется лишний if, что вообще говоря перебор, ибо можно поставить один break и убрать несколько инструкций и переменную.
В длинном цикле, однако, я использую именно if'ы, так как где-то в середине цикла break можно не заметить (особенно если их несколько и в разных местах), а вот indentation - намного нагляднее.
while(!condition_func()) { ... if(condition_func()) break; ... }
А нафиг такая форма может быть нужна? :eek:
По-моему изврат, лучше уж do..while из моего примера, там по крайней мере условие инициализируется внутри цикла.
Но в си до недавнего времени не было булевского типа. Да и сейчас не всё просто. Потому и 1. Для C++ - конечно же true.
Как это она инициализируется внутри цикла? Так что-ли:
bool cond = getCond();
if (cond) {
...
}
while (!cond);
Такое работать не должно, ибо переменная исчезнет раньше while. Значит объявляется и инициализируется (явно или неявно) снаружи цикла.
По-моему изврат, лучше уж do..while из моего примера, там по крайней мере условие инициализируется внутри цикла.
Например, если условие проверяется вначале или в конце тела цикла. Тут экономим строчку или две. Но пурист скажет, что такой код запутает читающего, так как тот может решить, что выход из цикла идёт в одном месте.
Кроме того, это может быть перестраховкой. Но перестраховки вредны тем, что могут скрыть дефект ПО, и это сокрытие в лучшем случае может внести ненужные задержки.
А, да, про bool в С я не раз слышал. Но и сейчас часто встречаю использование while(1) в самых разных языках, в том же PHP, к примеру - видимо народ ведётся на то, что оно короче пишется.
Такое работать не должно, ибо переменная исчезнет раньше while. Значит объявляется и инициализируется (явно или неявно) снаружи цикла.
Ну, объявить переменную вне цикла по-моему не проблема, я имел в виду именно место, где ей присваивается значение - это происходит в одной точке внутри цикла, а не один раз до и ещё раз внутри.
Да и потом далеко же не все языки дают циклам свою зону видимости, так что для них даже объявления снаружи ни к чему.
Я не пурист, но я тоже так скажу :)
Плюс, там происходит два вызова функции проверки условия на одну итерацию - становится не понятно, от чего это условие зависит. Например, когда каждый её вызов сдвигает какой-то внутренний указатель, то почему она вызывается два раза - фактически две итерации в одной? Ещё и разбитых пополам break'ом.
В таком случае нужно использовать переменную, а её нужно будет где-то устанавливать - если не хотим делать этого два раза, нужно юзать do..while + if'ы, получается ещё больше кода.
И так далее.
+1
народ исходит из того, все что не ноль - то true иначе false. Это общее правило для приведения типов к булевскому. Потому пожалуй у крайне ограниченного круга людей тут могут возникнуть проблемы с чтением. И нормальных среди них ... мало. :)
А что типа уважаемым участникам дискуссии слабо взять и скомпилировать и посмотреть, что собственно генерирует компилятор? Или просто хочется почесать языками? Или может просто не знают о такой возможности? Так она есть.
[highlight=javascript]
var f=true;
while(f) {
// do something
// turn off 'f' if you need it
}
window.onclick=function(){
/* using closure */
f=false; // stop iterations
}
[/highlight]
а можно вообще организовать цикл при помощи ассемблерных вставок, как то так
//cycle body
__asm {
call getCondition
test eax,eax
jz mylabel
}
Ну, если исходить из того, что и глобальные переменные существуют и работают на ура, то почему бы не использовать только их, ведь меньше кода и удобно?
Так мы не бестродействие обсуждаем, что генерит компилер он знает лучше. Интересны сами конструкции.
И языками почесать тоже - это же холивар :)
отнюдь не параллель. Кроме того, и глобальные переменные и гоуту - имеют свою нишу применения в программировании между прочем - но в прочем речь не про то. Требования к оформлению бесконечного цикла абсолютно произвольно и на качество кода никакого влияния не оказывает.
Так мы не бестродействие обсуждаем, что генерит компилер он знает лучше. Интересны сами конструкции.
И языками почесать тоже - это же холивар :)
чесание языками без причины - признак отнюдь не холивара :)
Ну а вообще всё надо писать на ассемблере :-)
Два раза вызов одной и той же функции для проверки условия выхода в одной итерации - зло, потому что:
1. Потери скорости. Особенно если функция проверки не состоит из одной проверки условия и возвращения результата.
2. Функция может что-то изменять при проверке, как уже сказали ранее, и поэтому могут возникнуть проблемы
3. Код понимается хуже
Да не слабо, просто не вижу смысла. Например, возьмём первые два примера и имеющийся под рукой компилятор:
def func1():
while True:
if condition_func(): break
dis.dis(func1)
print '-' * 20
def func2():
while condition_func(): pass
dis.dis(func2)
Ну и вывод:
>> 3 LOAD_GLOBAL 0 (True)
6 JUMP_IF_FALSE 19 (to 28)
9 POP_TOP
7 10 LOAD_GLOBAL 1 (condition_func)
13 CALL_FUNCTION 0
16 JUMP_IF_FALSE 5 (to 24)
19 POP_TOP
20 BREAK_LOOP
21 JUMP_ABSOLUTE 3
--------------------
14 0 SETUP_LOOP 15 (to 18)
>> 3 LOAD_GLOBAL 0 (condition_func)
6 CALL_FUNCTION 0
9 JUMP_IF_FALSE 4 (to 16)
12 POP_TOP
13 JUMP_ABSOLUTE 3
Лишнее (возврат пустоты) убрал. Во втором случае короче для данного конкретного примера, но это мало о чём говорит, ибо случай простейший.
Приоритет то в том, чтобы не запутаться в своём коде, чтобы не запутать читающего, а не в том, как будет это скомпилировано, и как сэкономить пару наносекунд.
"Требования к оформлению бесконечного цикла абсолютно произвольно" только со стороны стандартов языка, компиляторов. Но со стороны программиста и читающего - это не так.
Флаг - это переменная, в данном случае, даже глобальная (для показанной области видимости). Для маленького кода - оно самое то. Но функция гибче тем, что, например, не нужно знать, сколько флагов применяются для формирования условия выхода, с помощью каких логических операций получается результирующий флаг.
Короче, тут всё зависит от конкретного кода.
Мне представляется крайне узким список задач, где было бы полезно в каждой итерации вызывать свою функцию проверки, в ущерб безболезненной проверке флага.
Проблема доступа к флагу может решаться посредством сокрытия его в объекте, но мне этот способ не нравится из-за сложности применения на практике и очевидной бесполезности. Количество флагов влияющих на работу цикла в идеале должно стремится к единице. Все остальные флаги могут быть скрыты в конкретных функциях обработчиках.
т.е. видимо есть некий абсолют гарантирующий успешность одного варианта кода перед другим? Или что?
Утверждая - "Требования к оформлению бесконечного цикла абсолютно произвольно" - я вполне обосновано говорю, если во всех случаях бесконечного цикла вы используете единый подход - то абсолютно пох, что вы конкретно используете.
Те кто утверждают что в стопятсоттысячный раз им трудно понять что while(1){... //тут условие выхода } - всего навсего бесконечный цикл - вероятно имеют проблемы с задержкой развития - ввиду того, что конструкция вполне стандартная для языка С/С++. Конкретная же конструкция не имеет никаких особых преимуществ перед любыми остальными.
Вот я и хотел выявить для себя такой подход. Пока вижу только одну нечёткую рекомендацию: если внутри тела цикла есть из него выход через break/return, то лучше сделать цикл бесконечным. Это намекнёт читающему поискать выход из цикла внутри.
Я и сам не совсем понял чего тут непонятного. Разве что while(true) больше на человеческий язык похоже.
Вот я и хотел выявить для себя такой подход. Пока вижу только одну нечёткую рекомендацию: если внутри тела цикла есть из него выход через break/return, то лучше сделать цикл бесконечным. Это намекнёт читающему поискать выход из цикла внутри.
Хм. Если внутри цикла есть выход через break/return - то далеко не факт, что его всегда надо делать бесконечным. Если из цикла выход возможен только через break/return - это одно. но например в чтении некоторых данных по определенному условию из источника - то вариант завершения самый очевидный - либо выполнение условия, либо окончание чтения данных. Зачем же его делать бесконечным? читаем до конца данных - условие выполнилось - то break/return. Я чего то не понял?
Не совсем понял, но походит на грабли какие-то. С трудом верится, что стремление писать исключительно экстремально неветвистый код может оказаться целесообразным в невырожденном случае.
Если тело цикла маленькое (не более 3 строчек), то, вероятно, так. Иначе человек, сопровождающий код, может долго ломать голову, почему файл иногда читается не полностью. Конечно, можно поставить предупреждающий комментарий, но это похоже на костыль.
Можно сделать так:
{
// ...
if (EofFlag || myFlag) break;
// ...
}
и выход будет в одной точке. Можно это внести в само условие цикла, но тогда флаг придётся выносить за цикл.
Знаешь, я тоже иногда считаю окружающих дебилами - но в целом это не так :)
Если ты твердо уверен что человек, читающий твой код не способен удержать в памяти три строчки - я уж и не уверен что это забота о читаемости кода - это какое то завуалированное презрение к людям. Уж не знаю. Ты вообще к виду Homo Sapiens относишься? ))
На самом деле ты не прав - если отбросить абсолютно дебильную идею, что изучающий код способен помнить только то, что видит на экране - твоя идея сама по себе ошибка.
Почему? Потому что - если поток данных завершен - прочитан файл, вычитан рекордсет и т.п. - это одно состояние программы. Если же выполнено сравнение успешно - это вероятно совсем другое состояние. Конечно здесь надо опираться на конкретную ситуацию - но имхо не правомерно - если я ищу совпадение (например) - то как раз неожиданной будет единая точка выхода - очень приятно, нашел я что либо - или нет - но выйти будьте добры в одном месте. Зачем? Это кардинально разные события - то ли я вышел с искомым, то ли потому что кончилось где искать? Твой подход может быть вполне назван клоачным :) по большому и малому все в одно :) Но как бы там ни было, что одно решение, что второе - абсолютно произвольно. ВОЗМОЖНО твое код может усложнить - но это опять же от конкретной задачи. И по сути ни на качество ни на читаемость как правило влияния не оказывает.
Если ты твердо уверен что человек, читающий твой код не способен удержать в памяти три строчки - я уж и не уверен что это забота о читаемости кода - это какое то завуалированное презрение к людям. Уж не знаю. Ты вообще к виду Homo Sapiens относишься? ))
Я не считаю окружающих дебилами. Человек может решить сложные задачи, если ожидает их. Но ведь человек должен как-то определить, где надо запоминать строки, искать выходы, а где код делает то, что объявлено в заголовке.
То есть тут нужно определить, где будут "спагетти", а где - нет. В идеале должен быть какой-то такой список циклов:
1. for (iter in iters) - то есть обход итераторами. Тут не должно быть ни break, ни continue.
2. Вариант первого for (i in n..m), например, for (i in 0..10). Тоже без неожиданных выходов.
3. while(flag), где флаг не может быть константой. Тоже выход один.
4. loop - бесконечный цикл. Вот тут уж допустимо всё: и break, и continue и ещё какая-нибудь "лапша".
Запрещать return в первых трёх видах циклов наверное не имеет смысла. Но увлекаться им тоже не следует.
Такую схему можно пытаться переводить в реальные языки, чтобы не путаться.
Мысль только еще такая, что зная задачу, решаемую алгоритмом, должно быть, по-моему, ясно, какие могут быть исключительные ситуации (т. е. breakes). Мне без примера, а его у меня нет, не очень верится, что можно забыть break'и.
Всё-таки там предположение. И на счет continue я наверное погорячился. Давно не использовал - вот и решил, что он не нужен.
Вот это я не совсем распарсил, потому ответ может быть неточным:
else if (...) ...
else if (...) ...
else if (...) ...
else if (...) ...
else if (...) ...
else ...
и ни одного break. То есть, это ещё может навести на мысль, что и switch-case не нужен. Но это уже отдельная тема.
Кроме того, я не говорил, что про break-и надо забыть. Но вроде бы надо предупредить читателя, что они будут в конкретном цикле.
Конечно, отшлифовать надо идею, вот continue я тоже забыл, и не очевидно, в каких из 4 вариантов следует его допускать. А свитч, по-моему, можно поковырять разве что чтобы новое что-то узнать, т. к. он точно эффективнее if'а, проще (если я не ошибаюсь) в оптимизации, и вокруг него как антогониста if'а ведутся особые свистопляски в языках описания аппаратуры (VHDL, Verilog и т. п.).
странное у тебя понятие идеала. Ну хотя если учесть место твоего проживания - то не удивительно. Мне казалось что возможность выйти условно из цикла - это как бы естественно. Это выбор пользователя - а не свойство цикла. Ты же предлагаешь этим процессом управлять - тогда надо дополнительную спецификацию ввести - оставь надежду, всяк сюда входящий.... И возможность выйти из цикла становится свойством цикла. Не ну я понимаю - у вас счас эпоха супердзюдоистов - противовес силиконовой долине - сколково, противовес нормальному циклу - список циклов. Нормально вьюноша. Я думаю твои идеи будут подхвачены.
Зато видно: вот этот цикл - гибкий, а вот этот - предсказуемый.
Возможно, такая ясность может пригодиться только для while. Про for - сильно не уверен. Например, для поиска элемента в списке может потребоваться дополнительный выход. Но тут лучше будет использовать return со значением индекса, а не пустой break.
В общем-то, я перечислил циклы, которые есть в современных языках программирования. Более того, некоторые из видов циклов, типа do-while я убрал. С другой стороны, while(true) (оно же for( ; ; )) - настолько привычная конструкция, что её можно выделить в отдельный вид циклов. Не обязательно придумывать специальное слово - главное помнить, что это уже не while и не for.
statement;
...
}
В любом случае, fail или нет — покажет время. Если, конечно, не забыть это дело сразу как топик отвалится. А do-while надо вернуть. Чем он хуже-то? И мне интересно, а как часто встречаются циклы, которые без прокрутки не охватишь? На вскидку так.
Чем хуже цикл с постусловием? Это субъективное. Конечно, он может сэкономить строчку или несколько, но зато первая итерация проходит без условия, то есть отличается от других. Да и не так часто используются такие циклы.
Кроме того, есть ещё недостаток, но несерьёзный, в качестве юмора. Если случайно убрать ключевое слово do (или забыть), то код может спокойненько скомпилироваться и даже работать.
Не понял вопроса.
Ещё один заговорил загадками...
Если речь про while(true), то правильный компилятор должен отличать такую конструкцию от нормального while и не проверять каждый раз, что true == true. То есть, уже компилятор должен выделить такой цикл в отдельный вид.
Но нет гарантий, что конкретный компилятор (интерпретатор) так поступит. Если вынести бесконечный цикл в конструкцию языка, то можно будет прямо указывать компилятору, что проверка не нужна.
Другое дело, что может и while тогда будет не нужен... шучу.
А про нет гарантии, гарантий вообще нет, что компилятор оптимизировать умеет. =) И я вообще ни йоты не знаю про мультипоточное программирование, векторизацию и т. п., но, может, там есть реальные рекомендации, которые созвучны с темой обсуждения.