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

## урок 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`.

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

- Счетчик цикла `i` можно описывать там же, где описываются другие переменные в начале функции или скрипта.
- Счетчик обязательно нужно описывать в общем месте, если он используется в нескольких циклах.
- Если в функции цикл единственный, то можно записывать `for(var i=...; ...; ...)` — это красиво, и подчеркивает вспомогательный характер переменной `i`, недостойной более “солидных” описаний.

А что происходит, когда описание переменной помещается внутрь цикла `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`.