Переменные, присваивание и ссылки
Математика - статична, ПО - динамично
В программировании мы не описываем результаты заданием свойств, которым они должны удовлетворять. Мы должны вычислить результаты по некоторому алгоритму, используя компьютер и его память. При выполнении алгоритма последовательно вычисляемые значения записываются в память. Если бы память была бесконечно большой и бесконечно дешевой, то можно было бы для каждого нового значения выделять новую ячейку. Но память - хотя и большая, но конечная, поэтому приходится повторно использовать одни и те же ячейки, в которых будут храниться новые значения, когда старые уже не нужны.
Поэтому в программировании переменные, в отличие от своих двойников в математике, соответствуют своему имени, поскольку изменяют свои значения во время выполнения. Присутствие таких изменений - один из главных вызовов в нашем стремлении вывести свойства программ, используя базисные инструменты, имеющиеся в нашем распоряжении: логику, вообще математику.
Степень различия математики и программирования различна. Функциональное программирование и поддерживающие его функциональные языки вводят конструкции, близкие к математическому выводу, исключая или строго ограничивая изменения и побочные эффекты в процессе вычислений. Базисной конструкцией является функция в ее математическом смысле без побочных эффектов. В одной из последующих глав эта проблема обсуждается подробнее и рассматриваются основные конструкции функциональных языков.
Eiffel принадлежит к классу императивных языков. Благодаря принципу разделения команд и запросов побочный эффект разрешается только в процедурах, что облегчает выводы о программах в стиле, подобном математике.
9.1. Присваивание
Присваивание — это оператор, разрешающий изменять значение переменной.
Для примеров и упражнений этой главы следует использовать новый класс, названный ASSIGNMENTS.
Суммируем время поездки
Следующая простая задача будет служить примером: зная среднее время поездки между двумя соседними станциями, вычислить общее среднее время, требуемое на поездку в метро от начальной до конечной станции линии. Добавим в класс LINE функцию total_time, занимающуюся этим вычислением.
Алгоритм прямолинеен: последовательно проходя остановки на линии, следует к общему времени добавлять время до предыдущей остановки. Необходимая информация может быть получена по запросу из класса STOP:
time_to_next: REAL - Ожидаемое время проезда до следующей остановки: - от отправления до отправления, - исключение для последней остановки: от отправления до прибытия. require has_next: is_linked
Предусловие метода is_linked требует, чтобы остановка была связана со следующей (не была последней).
Наша желаемая функция total_time имеет следующую общую форму:
total_time: REAL — Оценка времени поездки по всей линии do from start — "Установить Result в ноль" invariant — "Значение Result - это время поездки от первой станции — до текущей станции, заданной позицией курсора" until is_last loop — "Увеличить Result на время до следующей станции" forth variant count - index end end
Переменная Result обозначает результат, возвращаемый функцией. Две команды псевдокода будут заменены присваиваниями.
Булевская функция is_last говорит нам, установлен ли курсор на последней станции. Заметьте разницу между схемой цикла, рассмотренной в предыдущей главе, где в конце цикла курсор имел значение after, а не is_last.
Время теста
Почему цикл для total_time использует is_last в качестве условия выхода, а не обычный after?
(Подсказка: сравните число остановок с числом интервалов. Сравните также выражение "variant" для двух циклов)
Команды псевдокода должны обновлять значение Result. Присваивание предназначено именно для этого.
Оператор присваивания имеет вид:
target := source
Здесь source — это выражение, а target — переменная, такая как Result. Левая часть присваивания target называется целью присваивания, а правая часть — source — источником. Эффект выполнения состоит в замене значения target на source. Чтобы быть точным:
Почувствуй семантику
Выполнение оператора присваивания target:= source состоит из:
А1. вычисления (computing) значения выражения source;
А2. переменная target получает это значение, начиная с момента присваивания, и сохраняет его до очередного присваивания target.
Это единственный эффект оператора. В частности, это никак не отражается на source.
Если вы программировали до чтения этой книги и хорошо знакомы с присваиванием, то, возможно, сочтете это определение педантичным. Но следует быть точным. Новички в программировании, встречаясь с присваиванием x:= y, иногда интуитивно воспринимают присваивание как передачу денег: если y передал свое значение x, то сам y теряет его и получает значение по умолчанию. Ничего подобного, y остается неизменным.
Выражение, такое как source, обычно включает переменные; "вычисление" его (A1) будет использовать их значения, полученные как результат предыдущих присваиваний.
Используем присваивание для уточнения функции нашего примера:
total_time: REAL — Оценка времени проезда по всей линии. do from start Result : = 00.0 invariant — "Значение Result — это время поездки от первой станции — до текущей станции, заданной позицией курсора" until is_last loop Result := Result + item.time_to_next forth variant count - index end end
На каждом шаге цикла мы добавляем к текущему значению Result время до следующей станции. Так как мы также выполняем forth, инвариант цикла сохраняется. При выходе из цикла инвариант скажет, что Result задает время от первой станции до станции в позиции курсора, но так как теперь is_last истинно, Result дает общее время проезда по линии.