Роботландский Университет © А.А.Дуванов

урок 4: повторения
дополнительный материал

оптимизация: программирование циклов с конца

Подсчитать сумму элементов массива set можно так (нумерация элементов массива в JavaScript начинается с нуля; set.length — свойство массива — его длина):

var sum = 0;
for(var i=0; i < set.length; i++) sum += set[i];

А можно и так:

var sum = 0;
for(var i=set.length; --i>=0; ) sum += set[i];

Перепишем этот вариант суммирования на “русском” языке:

var s = 0;
var i=set.length;
ПОВТОРЯТЬ
{
  уменьшить i на 1.
  проверить i: если i>=0 то s += set[i];
               иначе цикл закончить
}

Второй способ предпочтительнее.

Сравнение с нулем обычно более эффективно, чем сравнение с другим числом, тем более, сравнение с переменной, которая является собственностью объекта set (до нее еще нужно добраться).

Иными словами “--i>=0” работает быстрее, чем “i<set.length”.

Что касается “i=set.length” во втором варианте, то это присваивание не “крутится” в цикле, а работает ровно один раз до входа в него.

Общее правило: в цикле со счетчиком значение счетчика должно по возможности уменьшаться.

Циклы — это как раз то место, где имеет смысл оптимизировать программу. Понятно почему: ведь выполнение тела цикла повторяется многократно, и малое улучшение помножается на большое “число оборотов”. Если цикл выполняется 1000 раз, то такая оптимизация дает тысячекратный выигрыш.

Заметим, что такой вариант:

var sum = 0;
for(var i=set.length; (i--)>=0; ) sum += set[i];

является ошибочным, ведь в нем уменьшение i на единицу происходит после проверки условия, а не до, как в правильном варианте.

Описание переменной цикла

Можно написать так:

for(var i=100; i; i--) ...

— или так:

var i;
for(i=100; i; i--)  ...

Для браузера эти записи эквивалентны. Вспомним, что в конструкции цикла:

for(начало; условие; приращение) команда;

команда начало выполняется один раз перед первым оборотом цикла. Поэтому ясно, что переменная i не определяется 100 раз описателем var.

Рекомендации такие:

А что происходит, когда описание переменной помещается внутрь цикла while?

Если написать в С++ так:

while(...)
{
  int digit = ...
  ...
}

то переменная digit будет видна только внутри цикла и не доступна вне его. Дело здесь не в цикле, а в блоке {...}. Описания внутри блока локальны. Это означает, что переменная digit будет создаваться при входе в цикл и уничтожаться при выходе из него.

Такая инкапсуляция переменных имеет смысл, как и инкапсуляции в программировании вообще. Программисты всегда стараются сделать область видимости переменной как можно более узкой. Это обычная практика оборонительного программирования.

Если переменная, подобная digit, используется как рабочая внутри цикла и не имеет смысла вне его, то разумнее объявить ее локальной внутри этого цикла, чем объявлять ее в начале функции с комментарием “рабочая переменная цикла такого-то”.

Однако, к сожалению, здесь C++ расходится с JavaScript. Для JavaScript запись:

while(...)
{
  var digit = ...
  ...
}

эквивалентна такой:

var digit;
...
while(...)
{
  digit =....
}

С этим печальным фактом приходится считаться. Но в обоих случаях переменная digit создается один раз. Ведь описание — это не команда, а декларация. Браузер просматривает скрипт и выделяет под все переменные память, а только потом начинает выполнять команды.

Конструкция, в которой совмещены декларация и команда:

var digit = 10;

выполняется браузером так: сначала под переменную отводится память, а затем в нее помещается число 10.

Если браузер не может выполнить инициализацию переменной при ее создании:

function fun(t)
{
  var x = t+1;
  ...
}

— то он отделяет создание переменной от ее вычисления. Переменная создается один раз при первом просмотре функции (подготовка к работе), затем работают команды функции и среди них команда x = t+1.

 

урок 2 содержание письмо автору об авторах