Шаг 62.
Использование ссылок. Замыкания

    На этом шаге мы рассмотрим пример использования ссылок.

    Начиная с этого шага мы рассмотрим некоторые примеры, связанные с основным применением ссылок - конструированием структур данных.

    В качестве первой структуры построим массив массивов или двумерный массив. Для примера рассмотрим массив @calendar, содержащий календарь, например, на 2006 год. Значением элемента $calendar[$i][$j] является название дня недели, приходящегося на (j+1)-й день (i+l)-гo месяца: i=(0..11), j=(0..30):


Рис.1. Структура массива @calendar

    Для заполнения массива @calendar нам потребуется функция, которая по заданному году, месяцу и дню месяца вычисляет соответствующий день недели. В реализованном ниже примере использовано одно интересное свойство анонимных подпрограмм.

    Вычисление дня недели основано на том, что:

sub GetDay {
my $year = shift;
my @days = (0,31,59,90,120,151,181,212,243,273,304,334);
my @week = ("Monday (Понедельник)", "Tuesday (Вторник)", "Wednesday (Среда)", 
    "Thursday (Четверг)", Friday (Пятница)", "Saturday (Суббота)", 
    "Sunday (Воскресенье)"); 
my $previous_years_days = ($year - 1)*365 + int ( ($year-1) /4) - 
       int ( ($year-1)/100) + int ( ($year-1)/400); 
return sub { my ($month, $day) =@_;
             my $n = $previous_years_days + $days[$month-1] + $day - 1; 
             $n++ if ($year%4 == 0 and $year%100 != 0 or $year%400 == 0 
                            and $month > 2); 
             return $week[$n%7]; }
}

    Аргументами функции GetDay() являются номер года, номер месяца и номер дня месяца. Внутри тела функции им соответствуют переменные $уеаr, $month и $day. Функция подсчитывает число дней $n, прошедших с 1 января 1 года. Остаток от деления этого числа на 7 - $n%7 - определяет день недели как элемент массива $week[$n%7] .

    Прокомментируем приведенный текст функции.

    Для передачи параметров в подпрограмму используется предопределенный массив @_. Встроенная функция shift() без параметров, вызванная внутри подпрограммы, возвращает первый элемент массива @_ и осуществляет сдвиг всего массива влево, так, что первый элемент пропадает, второй становится первым и т.д. Элемент массива $days[$i] равен суммарному числу дней в первых i месяцах не високосного года, i = (0..11). В переменной $previous_years_days запоминается вычисленное значение общего количества дней, прошедших с 1 января 1 года до начала заданного года.

    Обратите внимание на то, что значением функции GetDay() является не название дня недели, а ссылка на анонимную функцию, которая возвращает название дня недели. Объясним, зачем это сделано.

    Если бы функция GetDay() возвращала день недели, то для заполнения календаря на 2006 год, к ней необходимо было бы сделать 366 обращений, вычисляя каждый раз значение переменной $previous_years_days. Для каждого года это значение постоянно, поэтому его достаточно вычислить всего один, а не 366 раз.

    На время вычисления функции формируется ее вычислительное окружение, включающее совокупность действующих переменных с их значениями. После завершения вычисления функции ее вычислительное окружение пропадает, и на него невозможно сослаться позже. Часто бывает полезным, чтобы функция для продолжения вычислений могла запомнить свое вычислительное окружение. В нашем примере полезно было бы запомнить значение переменной $previous_years_days, чтобы не вычислять его повторно. В языках программирования существует понятие замыкание, пришедшее из языка Lisp. Это понятие обозначает совокупность, состоящую из самой функции как описания процесса вычислений и ее вычислительного окружения в момент определения функции.

    Анонимные процедуры в Perl обладают тем свойством, что по отношению к лексическим переменным, объявленным при помощи функции mу(), выступают в роли замыканий. Иными словами, если определить анонимную функцию в некоторый момент времени при некоторых значениях лексических переменных, то в дальнейшем при вызове этой функции ей будут доступны значения этих лексических переменных, существовавшие на момент ее определения.

    В нашем примере указанное свойство анонимных функций используем следующим образом. Чтобы анонимной функцией можно было воспользоваться в дальнейшем, присвоим ссылку на нее скалярной переменной:

  $f = GetDay(2006,1,1);

    Во время обращения к GetDay() было сформировано вычислительное окружение анонимной функции, на которую сейчас указывает переменная $f. Вычислительное окружение включает, в том числе, и переменную $previous_years_days с ее значением. Обратите внимание, что внутри анонимной функции значение этой переменной не вычисляется. В дальнейшем для заполнения календаря мы будем вызывать анонимную функцию через ссылку $f.

    На следующем шаге мы рассмотрим использование созданной на этом шаге функции.




Предыдущий шаг Содержание Следующий шаг