Здравствуйте, karbofos42, Вы писали:
K>Здравствуйте, fk0, Вы писали:
fk0>> Нет. Есть принципиальная разница между циклом с goto и циклом с использованием операторов структурного программирования: fk0>>при использовании последних все объекты создаваемые в цикле, в каждом цикле вначале заново инициализируются, а в конце fk0>>цикла неявным образом уничтожаются. И вообще могут иметь разные адреса в каждом цикле. А с goto получается чёрти что.
K>цикл на нижнем уровне — это просто условный переход jmp или как там уж это называется. K>бесконечный цикл может быть представлен как безусловный переход goto.
НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты
созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает,
что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для
этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла
не обязаны сохранять свои значения (и адреса) в каждой новой итерации. Последнее легко заметно
в каком-нибудь интерпретаторе C, вроде Ch.
K>И выполнение функции — это тоже переход на нужный адрес в памяти.
Я знаю ассемблер. Но цикл и вызов функции -- это не только переход.
Переход лишь как средство реализации. И оно может оказаться совершенно другим.
Цикл может быть развёрнут, может быть tail call оптимизация, функция может быть
заинлайнена, да что угодно ещё...
K>Всё это управление стеком, инициализацию переменных, очистку памяти,... добавляет компилятор. K>Никто не мешает этому самому компилятору нормально обрабатывать и goto и уведомлять о существующих проблемах. K>Собственно, в C# goto из цикла превращается в break.
Но никто не мешает порождать код с UB и молча молчать. И получать неинициализированные
переменные например. При прыжке вперёд через инициализацию переменной (C++ запрещает)
или прыжке назад в середину тела цикла в котором декларировались переменные.
K>А без дублирования return или break, как в структурном программировании предлагается сделать банальный поиск элемента в массиве?
Любой код может быть написан через рекурсию...
K>
K>int FindIndex(int[] arr, int value)
K>{
K> for (int i = 0; i < arr.Length; ++i)
K> {
K> if (arr[i] == value)
K> return i;
K> }
K> return -1;
K>}
K>
K>даже если первый элемент подойдёт, продолжим пробегать всю коллекцию, чтобы было структурно и молодёжно?
Нет. Условие прерывания цикла может переехать в оператор for, например.
K>
K>int FindIndex(int[] arr, int value)
K>{
K> int result = -1;
K> for (int i = 0; i < arr.Length; ++i)
K> {
K> if (result == -1 && arr[i] == value)
K> result = i;
K> }
K> return result;
K>}
K>
K>А то ведь return внутри цикла может привести к тому, что разработчик что-то забудет удалить или сделать, должна быть одна точка выхода из функции.
Вообще-то да. В голом C или паскале. В языках с RAII это не так критично.
K>Я сам как-то goto не использую и редко такое нужно, но вот думаю: а зачем я для выхода из вложенных циклов горожу флаги и леплю лишние проверки, когда можно просто goto написать и перейти в нужную точку.
И сразу вопрос, что делать компилятору с переменными созданными в цикле?
Деструктор для них же надо вызывать? И становится goto очень неочевидным оператором,
с очень неявным control flow...
Здравствуйте, fk0, Вы писали:
fk0> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты созданные в теле цикла.
В С всё на ручнике
Потом, тело цикла это то, что под for а не внутри.
fk0>Для C++ программистов оно означает, что "for (auto& x : MakeTempObject())" не будет работать
Это как раз работает даже в C++11, где range-for только появился.
Ибо range for это синтаксический сахар, который разворачивается в как минимум вот такое:
{
auto&& __range = range-expression; <<< результат moves в __range который спокойно себе живёт вне for-loop, и переживает его
for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}
}
Потом ещё больше вынесли наружу, но это не суть.
Проблемы будут с "for (auto& x : MakeTempObject().getObjects())"
Но совсем не потому что for.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Здравствуйте, fk0, Вы писали:
fk0> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты fk0>созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает, fk0>что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для fk0>этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла fk0>не обязаны сохранять свои значения (и адреса) в каждой новой итерации.
Ну, вот код на C++:
for (int i = 0; i < 10; ++i)
{
A *arr = new A();
}
цикл каждую итерацию будет удалять созданный объект класса A или всё же память утечёт и нет никакой гарантии про все объекты?
fk0> Я знаю ассемблер. Но цикл и вызов функции -- это не только переход. fk0>Переход лишь как средство реализации. И оно может оказаться совершенно другим. fk0>Цикл может быть развёрнут, может быть tail call оптимизация, функция может быть fk0>заинлайнена, да что угодно ещё...
Но технически все эти функции и циклы преобразуются в банальные goto или что-то подобное, а вся эта инициализация переменных, очистка стека — это добавляет компилятор.
fk0> Но никто не мешает порождать код с UB и молча молчать. И получать неинициализированные fk0>переменные например. При прыжке вперёд через инициализацию переменной (C++ запрещает) fk0>или прыжке назад в середину тела цикла в котором декларировались переменные.
Вопрос к создателям языка и компиляторов к нему. Никто им не мешает обойтись без UB
fk0> Нет. Условие прерывания цикла может переехать в оператор for, например.
В простейшем случае в итоге получим некрасивую запись и лишнюю проверку в конце, где будем выяснять причины попадания в строку
(коллекция кончилась и ничего нужного не нашлось или же наткнулись на искомый элемент и нужно его вернуть).
На деле же условие выхода из цикла будет с предварительными расчётами и в for не влезет.
Можно конечно всякого нагородить, но не понятно зачем.
Там же и читабельность кода снизится и лишние операции добавятся.
fk0> И сразу вопрос, что делать компилятору с переменными созданными в цикле? fk0>Деструктор для них же надо вызывать?
примерно то же самое, что компилятор делает при break
fk0>И становится goto очень неочевидным оператором, fk0>с очень неявным control flow...
у исключений с try/catch не проще и потенциальных проблем от них не меньше, но народ как-то живёт.
Ну, сложно с goto — пусть дадут оператор для выхода из всех циклов.
Бывают ситуации типа:
int value = 0; // стандартное значениеfor (int i = 0; i < 10; ++i)
{
for (int j = i + 1; j < 11; ++j)
{
for (int k = j + 1; k < 12; ++k)
{
if (...)
value = ...; // меняем значение на расчётноеbreak; // тут хочется выйти из всех 3-х циклов
}
}
}
// Дальше используем value вне зависимости от того какое значение (стандартное или расчётное)
break из одного цикла почему-то не является проблемой для создателей языков.
Как и return из любой строки.
Не понимаю что не так с break из пары вложенных циклов и чем это принципиально отличается.
Можно конечно всё это в единственный while переписать или ещё чего намудрить, но читаемость кода снизится,
а по факту будет выполняться примерно то же самое, что можно с goto написать и не терять в читаемости.
И в итоге на практике не понятно что выйдет надёжнее в поддержке:
странно написанный и непонятный код по всем правилам структурного программирования или лаконичный код с сомнительным goto.
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, fk0, Вы писали:
fk0>> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты созданные в теле цикла. CC>В С всё на ручнике CC>Потом, тело цикла это то, что под for а не внутри.
Всё да не всё. Формально "рантайм" освобождает память из под переменных скоупе тела цикла
и формально может назначить на следующей итерации другие адреса. Повторюсь, в каком-нибудь Ch
можно запросто напороться на то, что не работает, но строго соответствует букве стандарта.
Или второй операнд оператора for (где может быть декларация переменной) будет вычисляться каждый
раз в начале цикла и уничтожаться в конце цикла. Это же не просто так.
fk0>>Для C++ программистов оно означает, что "for (auto& x : MakeTempObject())" не будет работать CC>Это как раз работает даже в C++11, где range-for только появился.
Да, я не прав. Не рабоатет MakeTempObject().getter(), но это другое.
Здравствуйте, karbofos42, Вы писали:
K>Здравствуйте, fk0, Вы писали:
fk0>> НЕТ, ещё раз: цикл в распространённых языках C/C++ уничтожает и создаёт заново все объекты fk0>>созданные в теле цикла. Это ПРИНЦИПИАЛЬНОЕ свойство цикла. Для C++ программистов оно означает, fk0>>что "for (auto& x : MakeTempObject())" не будет работать (вроде как будет пофикшено в C++23 для fk0>>этого частного случая), для C-программистов означает, что переменные объявленные в теле цикла fk0>>не обязаны сохранять свои значения (и адреса) в каждой новой итерации.
K>Ну, вот код на C++: K>
K>for (int i = 0; i < 10; ++i)
K>{
K> A *arr = new A();
K>}
K>
K>цикл каждую итерацию будет удалять созданный объект класса A или всё же память утечёт и нет никакой гарантии про все объекты?
Причём здесь new? Если A() будет без new (на стеке) и написать деструктор, то будет видно, что он вызывается...
А по-указателю C++-компилятор сам ничего удалять не будет. Вот переменную указателя он "удалит". Но у ней деструктор
пустой.
K>у исключений с try/catch не проще и потенциальных проблем от них не меньше, но народ как-то живёт. K>Ну, сложно с goto — пусть дадут оператор для выхода из всех циклов. K>Бывают ситуации типа:
K>
K>int value = 0; // стандартное значение
K>for (int i = 0; i < 10; ++i)
K>{
K> for (int j = i + 1; j < 11; ++j)
K> {
K> for (int k = j + 1; k < 12; ++k)
K> {
K> if (...)
K> value = ...; // меняем значение на расчётное
K> break; // тут хочется выйти из всех 3-х циклов
K> }
K> }
K>}
K>// Дальше используем value вне зависимости от того какое значение (стандартное или расчётное)
K>
Оператора выхода из всех циклов потому и нет, что он порождает ошибки и непонятно
как его сделать. Вместо него может выступать goto, может выступать return, могут выступать
более сложные условия. В некоторых языках... (bash) такой оператор есть, но лучше б не было...
В некоторых других (tcl) нет и невозможен goto, и break делается... через исключение!
K>Не понимаю что не так с break из пары вложенных циклов и чем это принципиально отличается.
Из скольки именно циклов? А если циферку числа циклов впишут, а потом код поменяют?
K>Можно конечно всё это в единственный while переписать или ещё чего намудрить, но читаемость кода снизится, K>а по факту будет выполняться примерно то же самое, что можно с goto написать и не терять в читаемости.
Вот полезные применения goto как раз:
1) обработка ошибок, вместо отсутствующего оператора defer: https://gustedt.gitlabpages.inria.fr/defer/
2) прерывание нескольких вложенных циклов;
3) возможно ещё какие-то экзотические случаи, которые сходу не припомнить.
Но ведь заставь дурака богу молиться -- лоб расшибёт.
K>И в итоге на практике не понятно что выйдет надёжнее в поддержке: K>странно написанный и непонятный код по всем правилам структурного программирования или лаконичный код с сомнительным goto.