Это отличная практика.
Создавая переменные внутри циклов, вы гарантируете, что их область действия ограничена внутри цикла. На него нельзя ссылаться и вызывать вне цикла.
Сюда:
Если имя переменной немного «универсально» (например, «i»), нет риска смешать ее с другой переменной с тем же именем где-нибудь позже в вашем коде (это также можно уменьшить с помощью -Wshadow
инструкции предупреждения в GCC)
Компилятор знает, что область действия переменной ограничена внутри цикла, и, следовательно, выдаст правильное сообщение об ошибке, если на переменную по ошибке ссылаются в другом месте.
И последнее, но не менее важное: некоторая специальная оптимизация может выполняться компилятором более эффективно (наиболее важно распределение регистров), поскольку он знает, что переменная не может использоваться вне цикла. Например, нет необходимости сохранять результат для последующего повторного использования.
Короче говоря, вы правы сделать это.
Однако обратите внимание, что переменная не должна сохранять свое значение между каждым циклом. В этом случае вам может потребоваться инициализировать его каждый раз. Вы также можете создать больший блок, охватывающий цикл, единственной целью которого является объявление переменных, которые должны сохранять свое значение от одного цикла к другому. Обычно это включает сам счетчик циклов.
{
int i, retainValue;
for (i=0; i<N; i++)
{
int tmpValue;
/* tmpValue is uninitialized */
/* retainValue still has its previous value from previous loop */
/* Do some stuff here */
}
/* Here, retainValue is still valid; tmpValue no longer */
}
На вопрос № 2: переменная выделяется один раз, когда вызывается функция. Фактически, с точки зрения распределения, это (почти) то же самое, что и объявление переменной в начале функции. Единственное отличие заключается в объеме: переменную нельзя использовать вне цикла. Возможно даже, что переменная не будет выделена, просто повторно используя какой-то свободный слот (из другой переменной, область которой закончилась).
С ограниченным и более точным охватом приходят более точные оптимизации. Но что еще более важно, это делает ваш код более безопасным, с меньшим количеством состояний (т.е. переменных), о которых нужно беспокоиться при чтении других частей кода.
Это верно даже за пределами if(){...}
блока. Как правило, вместо:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
безопаснее написать:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
Разница может показаться незначительной, особенно на таком маленьком примере. Но на большую базу кода, это поможет: в настоящее время не существует никакого риска транспортировать некоторое result
значение из f1()
к f2()
блоку. Каждый result
строго ограничен своей областью действия, что делает его роль более точной. С точки зрения рецензента, это гораздо приятнее, так как у него меньше переменных состояния на большие расстояния, о которых нужно беспокоиться и отслеживать.
Даже компилятор поможет лучше: предполагая, что в будущем, после некоторого ошибочного изменения кода, result
не будет должным образом инициализироваться с f2()
. Вторая версия просто откажется работать, сообщив об ошибке во время компиляции (лучше, чем во время выполнения). Первая версия ничего не обнаружит, результат f1()
будет просто проверен во второй раз, будучи запутанным для результата f2()
.
Дополнительная информация
Инструмент с открытым исходным кодом CppCheck (инструмент статического анализа для кода C / C ++) предоставляет несколько полезных советов относительно оптимального диапазона переменных.
В ответ на комментарий о распределении: вышеприведенное правило верно для C, но может не подходить для некоторых классов C ++.
Для стандартных типов и структур размер переменной известен во время компиляции. В C нет такого понятия, как «конструкция», поэтому пространство для переменной будет просто выделено в стек (без какой-либо инициализации) при вызове функции. Вот почему при объявлении переменной внутри цикла существует «нулевая» стоимость.
Тем не менее, для классов C ++ есть конструктор, о котором я знаю гораздо меньше. Я предполагаю, что распределение, вероятно, не будет проблемой, так как компилятор должен быть достаточно умным, чтобы повторно использовать то же пространство, но инициализация, вероятно, будет происходить на каждой итерации цикла.