while(true)+break и альтернативы
Можно делать так:
{
// ...
if(condition_func()) break;
// ...
}
или так:
{
// ...
}
Можно использовать переменную вместо функции, но это менее изящно, хотя в конечном счёте всё сведётся именно к этой переменной.
Первый способ плох тем, что придётся искать условие выхода внутри тела цикла. И это может породить бесконечный цикл при внесении изменений в код цикла. Второй плох тем, что условие выхода привязано к определённой точке. То есть, он менее гибок.
Может есть ещё какие-то альтернативы? Или может есть какие-то соображения, чтобы не использовать один из подходов вообще?
Можно делать так:
{
// ... x()
if(condition_func()) break;
// ... y()
}
В общем случае я пытаюсь выявить рекурсивную зависимость и бывает что пишу так (точный аналог цитаты):
x();
unless(condition_func()) {
y();
loop();
}
}
loop();
Решение справедливо для языков с локальными функциями и оптимизацией хвостовой рекурсии.
MESSAGE_HANDLER(WM_SOME_MESSAGE, TMessage, OnSomeMessage);
END_MESSAGE_MAP(TForm)
Вроде бы ты разбирался со SmartWin++, WTL. Вроде бы они как-то без этих карт обходятся. То есть тут у тебя надо спросить, как они вывернулись без макросов.
hardcase, пример с рекурсией - ещё одна альтернатива, да, но пока я не уловил её преимуществ.
Кстати, на счёт макросов. hardcase утверждает, что так называемые "гигиенические макросы" - это хорошо. Вот только я не совсем понял их суть. По мне - это какой-то гибрид перегрузки операторов и сишных макросов. В результате получают более гибкую возможность изобретения дополнительных "велосипедных" операторов.
WTL - с картами
SmartWin++ (пример из их документации):
using namespace SmartWin;
class HelloWinClass: public WidgetFactory< WidgetWindow, HelloWinClass >
{
private:
WidgetButtonPtr itsButton;
public:
void buttonClicked( WidgetButtonPtr button )
{
createMessageBox().show( _T("Hello World!" ), button->getText() );
}
void initAndCreate()
{
createWindow();
itsButton = createButton();
itsButton->setText( _T("Hello from a button") );
[COLOR="red"]itsButton->onClicked( &HelloWinClass::buttonClicked );[/COLOR]
itsButton->setBounds( itsButton->getPosition(), Point( 200, 30 ), true );
}
};
int SmartWinMain( Application & app )
{
HelloWinClass *testHello = new HelloWinClass;
[COLOR="Red"]testHello->initAndCreate();[/COLOR]
return app.run();
}
hardcase, пример с рекурсией - ещё одна альтернатива, да, но пока я не уловил её преимуществ.
Преимущество одно - функциональный стиль записи программы (ну и все вытекающее из этого).
Кстати, на счёт макросов. hardcase утверждает, что так называемые "гигиенические макросы" - это хорошо. Вот только я не совсем понял их суть. По мне - это какой-то гибрид перегрузки операторов и сишных макросов. В результате получают более гибкую возможность изобретения дополнительных "велосипедных" операторов.
Эмм гигиеничные? Я вроде говорил о типобезопасных макросах. Гигиеничность их заключается в способе сокрытия имен, используемых в коде генерируемом макросами, от кода, полученного от пользователя.
Макросы Си осуществляют всеголишь текстовую подстановку. Макросы Лиспа, Бу и Немерла выполняют трансформацию кода и с исходным текстом не работают.
Ну, это не преимущество - это запутанность. Шучу. Но всё-таки ненужные ребусы в ФП встречаются очень часто.
Читал я про макросы в Немерле. Всё-таки они ближе к перегрузке операторов в C++, чем к сишным макросам. Если не учитывать, что одно будет на этапе компиляции (а так ли?), а другое на этапе исполнения, то получается более гибкая перегрузка операторов. Ну ладно, создание операторов.
Что-то не разобрал, что за макросы в Бу. Какие-то они навороченные. Кстати, в Википедии, в статье про Бу, размещён код на Python с ошибкой. Но это так, между прочим.
Это смотря в какой пропорции и в каком месте. Мне зачастую проще понять рекурсивный алгоритм нежели циклический.
Читал я про макросы в Немерле. Всё-таки они ближе к перегрузке операторов в C++, чем к сишным макросам. Если не учитывать, что одно будет на этапе компиляции (а так ли?), а другое на этапе исполнения, то получается более гибкая перегрузка операторов. Ну ладно, создание операторов.
Это в каком месте они эквивалентны перегрузке операторов?
Да, можно задавать совершенно произвольные макросы-операторы, но это лишь малая часть возможностей. Кстати, недавно сделали макрос для оператора ?. (обращение к члену с проверкой this на null). Оператор C++ - суть функция, и по месту использования компилятор подставляет вызов этой функции, макрос Nemerle раскрывается (генерирует код) по месту использования. Но честно говоря общего с перегрузкой операторов я вообще не вижу: в Nemerle есть честная перегрузка операторов, подобная C# и C++, и она именно так и называется - перегрузкой операторов. Применение такого оператора выливается в вызов функции соответствующей этому оператору (типа op_Addition для +, op_Subtract для -, и т.д.), применение макроса-оператора приводит к вызову макроса, который занимается трансформацией синтаксических деревьев, переданных ему аргументами, а на выходе получается код, соответствующий этому оператору, но это совсем не обязательно единичный вызов некой функции.
Я также замечу, что так называемые макросы-операторы относятся к макросам уровня выражений, с помощью которых в Nemerle реализованы все классические конструкции управления: for, while, if-else, и др., а также конструкции прерывания: break, continue, return. Учитывая этот факт я точно вижу что ты заблуждаешься, рассуждая о "похожести".
Ну и не стоит забывать, что помимо макросов уровня выражений есть мета-атрибуты, применяемые к декларациям (типов их членам: полям, свойствам, методам) на разных стадиях компиляции.
Boo вообще-то задумывался как строгий статически типизированный вариант Python.
В Boo присутствуют макросы по возможностям аналогичные макросам Nemerle - я толком не разбирался, лишь сильно поверхностно оценил. За исключением отсутствия квазицитирования (что сильно препятствует их написанию, это кстати про "наворочнность") принципиальных различий не обнаружил - точно также есть макросы уровня выражений и есть макросы для трансформации деклараций.
x();
unless(condition_func()) {
y();
loop();
}
}
loop();
Решение справедливо для языков с локальными функциями и оптимизацией хвостовой рекурсии.
Тут, справедливости ради, тоже можно порассуждать о спорности преимущества волшебного слова loop(), переводящего поток управления в начало цикла, перед волшебным словом break, переводящего поток управления в конец цикла. Мне кажется, что в этом случае - одинаково приятно. Впрочем, break подсвечивается жирным, а loop() - нет.
Вообще-то break передает управления за цикл... В конец тела цикла передает управление continue.
Простите, а вы правда считаете что без препроцессора (который без define'а вообще не пойми что) возможна серьезная разработка на C/C++?
Я бы не стал вот так записывать: C/C++. C - это одно, C++ - другое.
В C без макросов работать сложно - будет много дублирования. В C++ полно альтернативных, лучших решений, но есть и исключения. И эти исключения большей частью являются следствием относительной совместимостью с C.
Например, стражи включения. Это уродство могло бы стать ненужным, если бы в C++ использовали нормальные модули, как, например, в Python или D. Заодно избавились бы от namespace-ов.
Но вообще, такие вопросы немного удивляют. Я так понимаю, что мне придётся скоро переписывать статьи из учебников по C++ сюда.
Хорошо, давайте уберем C. Это не изменит сути вопроса:
Возьмем того же Александреску и посмотрим на исходники loki или в его книжку "Современное проектирование на С++". Как мы удивимся когда увидим, что там есть много define'ов, и они не являются стражами включения.
Ок. Возьмите эту библиотеку. Если найдёте примеры типа
#define MY_CONST 31
#define LOOP while(true)
то и будет о чём вести речь. Так как тут осуждались именно такие макросы.
Примеры условного включения не в счёт, ибо тут аналогия со стражами включения. Примеры с использованием встроенных макросов типа __FUNCTION__ тоже не в счёт, так как я говорил о таком исключении.
Подозреваю, что там есть и другие исключения. Будем разбирать, если найдём.
#define MY_CONST 31
#define LOOP while(true)
то и будет о чём вести речь. Так как тут осуждались именно такие макросы.
Примеры условного включения не в счёт, ибо тут аналогия со стражами включения. Примеры с использованием встроенных макросов типа __FUNCTION__ тоже не в счёт, так как я говорил о таком исключении.
Подозреваю, что там есть и другие исключения. Будем разбирать, если найдём.
Вот я сейчас сделал поиск define по исходникам loki и нашел:
#define LOKI_MAX_SMALL_OBJECT_SIZE 256
#define LOKI_DEFAULT_OBJECT_ALIGNMENT 4
Но это плохие примеры, так делать в общем случае не нужно. Хотя бывает нужно, если эта константа используется в #if.
Вообще-то это не так, тут обсуждались макросы вообще.
Обсуждались вообще, а осуждались конкретные.
Я же не говорил, что без макросов в C++ совсем можно обойтись. Я говорил, что в C++ они плохие. Препроцессор примитивный, не учитывает особенности C++. Поэтому не стоит использовать макросы, если есть такая возможность.
Попробую подвести итог всему обсуждению. Имхо, всё сводится к тому, нужно ли иметь много узкоспециализированных видов цикла или мало (один) универсальных. Если грубо разделить всех программистов на новичков и профессионалов, то новичку, вполне естественно, будет проще запомнить для начала малое количество языковых конструкций, в том числе и видов циклов. Но в дальнейшем он будет испытывать затруднения, пытаясь сделать цикл, который ни разу не попадался в учебнике. Профи в какой-то мере без разницы: хоть большое количество видов циклов запомнить (на то он и профи, чтобы в мануале забытое быстро найти), хоть соорудить любой цикл из if и goto (утрированно).
Помнится, где-то я читал такой взгляд на это: представим два языка, в одном циклов много, в другом мало (или вообще лишь условный оператор и goto). В учебнике по первому языку, скажем в пятом уроке, будет рассказываться о цикле for, в шестом - о цикле while. В учебнике по второму языку в пятом и шестом уроках будет рассказываться о том, как реализовать те же циклы с помощью if и goto.
Хочу спросить мнение сообщества о цикле Дейкстры и цикле "паук" (встретил их в Википедии). Как они вам?
Там же вычитал о возможности выхода из любого вложенного цикла (не только из самого внутреннего) в языке Ada с помощью break с меткой. Имхо, удобно. В C/C++/C# для этого иногда приходится выносить циклы в отдельную функцию и юзать return.
И ещё, когда-то давно читал о цикле с фиксированным числом повторений в языке Лого. Понравился он мне: никаких условий как в while, никаких счётчиков как в for. Почему не применяется широко?
Цикл сам по себе лишняя конструкция ибо всегда сводится к рекурсии. Советую почитать следующую статью.
Касательно цикла Дейкстры, этот цикл сводится к использованию конструкции сопоставления с образцом, например обработку списка (хитрое синтетическое суммирование элементов) можно выполнить следующим образом:
| [] => 0;
| a :: [] => a;
| a :: b :: tail when a > b => (a + 1) + loop(b :: tail);
| a :: b :: tail when a == b => (a + b) + loop(b :: tail);
| a :: b :: tail => b + loop(tail);
}
def sum = loop([1, 2, 7, 4, 0, 10]);
Не совсем. Всё сводится к смыслу выражения while(true). Посмотрел описание языка Ада и увидел, что там оно заменено на loop. То есть то, что я тут предлагал сделать, в неизвестном для меня языке давно сделано.
Там же вычитал о возможности выхода из любого вложенного цикла (не только из самого внутреннего) в языке Ada с помощью break с меткой. Имхо, удобно. В C/C++/C# для этого иногда приходится выносить циклы в отдельную функцию и юзать return.
Немного напоминает конструкцию try-except-else-finally. Правда, там не цикл.
На счёт именованных циклов (то есть тех, из которых выходят по метке) - есть сомнения. Не знаю, что лучше - функция или именованный цикл. Наверное, именованный цикл - это хорошо.
Наверное, в большинстве случаев применяется этот счетчик или итератор. Этот цикл типа while - добавляет лаконичности. Но горячие головы могут пытаться использовать его с внешним счетчиком, то есть создавая замену for, что тоже будет некрасиво.
Как, в прочем, верно и обратное - рекурсия всегда может быть заменена циклом.
Ну а вообще, обсуждение нового оператора цикла... - проблема на ровном месте.
Зачем вводить новую конструкцию для того, что и так не сложно пишется?
"Не плодите сущностей сверх необходимого."
Кстати, такая конструкция:
{
if (условие1) break;
if (условие2) break;
if (условие3) break;
// ...
}
кроме своей избыточности, ещё и противоречит семантике C++.
По сути топика, я пишу так:
{
if(...) break;
if(...) break;
}
Все равнозначные условия помещаю в if(...) break.
А while(true) как бы подчеркивает равнозначность и возможность выхода по нескольким условиям.
Т.о. в дальнейшем появляется возможность изменять код, включать/выключать условия простым добавлением/удалением блоков if(...) break. В т.ч. и через условные макросы (#if).
А это и хорошо, что "приходится выносить циклы в отдельную функцию", ибо структурирует код.
С другой стороны, у нас уже есть конструкция вызова функции в языке. ;)
А while(true) как бы подчеркивает равнозначность и возможность выхода по нескольким условиям.
Если говорить о существующих языках, типа C++, то я и не требую внесения изменений в стандарт. Я говорю, что while(true) - для нескольких выходов, а while(обычное условие) - для одного.
Но в скриптах, или даже в новых языках, чистый loop предпочтительнее, так как именно он "не плодит сущностей сверх необходимого". А while - это лишь более лаконичная форма loop, если условие одно и оно не константа.
Субъективно, тут одна проблема. Цикл он как-то ближе человеческому языку. Цикл с предусловием: "Пока есть деньги, будем покупать яблоки", цикл с постусловием "создавай сообщения на форуме, пока не уснёшь", цикл for: "вымой все яблоки".
А рекурсия - противоестественное понятие для обычного человека. Грубо говоря, будет так: "Не спишь? Пиши сообщение на форум. Спишь? Не пиши сообщение на форум. Это предложение". Проблема в том, что человек не поймет, что здесь цикл и создаст только одно сообщение и после этого загрузится.
Но в скриптах, или даже в новых языках, чистый loop предпочтительнее, так как именно он "не плодит сущностей сверх необходимого". А while - это лишь более лаконичная форма loop, если условие одно и оно не константа.
Речь то зашла об основе "циклов" - некоей базовой конструкции. Рекурсия видится мне наиболее базовой, и остальные случаи я легко вывожу из нее - этой точки зрения я и придерживаюсь.
А рекурсия - противоестественное понятие для обычного человека.
В конечном счете, когда программирование было естественным для обычного человека? Одна операция присваивания чего только стоит. ;) А тот же Дейкстра считал программирование вообще разделом математики, так что не надо сюда "обычных людей" приплетать.
Может это личное предпочтение, но я согласен с Когромом - рекурсия выворачивает мозги наизнанку, совсем не то, что обычный цикл :)
В Ruby, кстати, можно иметь цикл for как бы без переменной:
// или так, с переменной:
5.times { |i| print i }
По-моему очень удобная конструкция, эти блоки.
Чистого loop правда даже там нет, а жаль, удобная была бы штука.
А в чём проблема с присваиванием? О_о
это в языках типа С++ она выворачивает мозги, т.к. применяется не часто и её учат избегать(обоснованно вообще говоря).
но в функциональных языках её применение становится естественным через 1-2 дня изучения таких языков(возьмите тот же haskell и вы увидите естественность рекурсии там)
hardcase, цикл поддерживается на уровне инструкций процессора;) так что его наличие обоснованно с технической точки зрения... да и не в него ли раскрывается хвостовая рекурсия в твоём любимом Nemerle?))
hardcase, цикл поддерживается на уровне инструкций процессора;) так что его наличие обоснованно с технической точки зрения... да и не в него ли раскрывается хвостовая рекурсия в твоём любимом Nemerle?))
Если ты о семействе операций перехода, то они позволяют реализовать цикл (рекурсивную функцию оптимизируемую для использования константной памяти), так же как позволяют реализовать операторы какого либо ветвления.
вопрос в том, насколько это будет эффективно.
просто для for(;; ) (цикла с заранее известным числом итераций) компилятору логичней использовать специальную инструкцию условного перехода, которая сравнивает регистр ECX с нулём и автоматически производит декремент оного.
а когда используется хвостовая рекурсия для организации цикла, то непонятно, создаст ли компилятор оптимальный код с использованием наиболее подходящих инструкций перехода или же использует более общие инструкции.
*вопрос о том, насколько эффективнее использование того или иного оператора условного перехода для меня не известен, но было бы интересно узнать это)
просто для for(;; ) (цикла с заранее известным числом итераций) компилятору логичней использовать специальную инструкцию условного перехода, которая сравнивает регистр ECX с нулём.
а когда используется хвостовая рекурсия для организации цикла, то непонятно, создаст ли компилятор оптимальный код с использованием наиболее подходящих инструкций перехода или же использует более общие инструкции.
Естественно, задача оптимизации конструкций управления будет сводиться к оптимизации рекурсии. Но стоит заметить, что Nemerle компилируется в CIL, а в этом ассемблерном языке понятия "регистр" нет вообще - есть стек и локальные переменные. Разбором полетов о том, какую инструкцию на физическом процессоре использовать в том, или ином случае занимается JIT-компилятор.
Чистого loop правда даже в Ruby нет, а жаль, удобная была бы штука.
Оказывается я про неё просто не знал, сегодня как раз наткнулся.
loop do
print "infinity"
end