Это отлично практика.
Создавая переменные внутри циклов, вы гарантируете, что их область действия ограничена внутри цикла. На него нельзя ссылаться и вызывать вне цикла.
Таким образом:
Если имя переменной немного «универсальное» (например, «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 ++ есть одна вещь конструктора, о которой я знаю гораздо меньше.Я предполагаю, что распределение, вероятно, не будет проблемой, так как компилятор должен быть достаточно умным, чтобы повторно использовать то же пространство, но инициализация, вероятно, будет происходить на каждой итерации цикла.