Академический Документы
Профессиональный Документы
Культура Документы
Скопин
ОСНОВАНИЯ
ПРОГРАММИРОВАНИЯ
УДК 519.682
Основания программирования
i
ii
Иными словами, как конкретные языки, так и алгоритмы служат для предла-
гаемого учебника в первую очередь иллюстративным материалом.
Внимание!
Базовые понятия
1
2
/*Язык C.*/
#include <stdio.h>
int main(void)
{printf("Hello World!");
return 0;}
3
4 ГЛАВА 1. ПОНЯТИЯ
Программа 1.1.2
//Java
public class HelloWorld {
public static void main (String[] args) {
System.out.println("Hello World!");
}}
Программа 1.1.3
(*Паскаль*)
program First (Output);
begin
writeln(’Hello World!’)
end.
Программа 1.1.4
comment Algol 68 comment
begin
println(‘Hello World!’)
end
начало
печатать(‘Hello World!’)
конец
(println(‘Hello World!’))
(печатать(‘Hello World!’))
1.1. ЯЗЫКИ И СИСТЕМЫ ПРОГРАММИРОВАНИЯ 5
Программа 1.1.5
Лисп:
( PRINT “Hello World!”)
Программа 1.1.6
Рефал
$ENTRY GO{=<Prout ’Hello World!’>};
Программа 1.1.7
Prolog
:-Print(’Hello World!’);
Сравним все эти программы. Что у них является общим?
1. Все они представлены в виде текстов: последовательностей символов,
размещенных на нескольких строчках.
2. В каждом языке имеется понятие строки (последовательности симво-
лов нефиксированной длины). Строки оформляются во всех языках при-
мерно одинаково: с использованием кавычек как обрамляющих симво-
лов. Правда, вид конкретных кавычек меняется от языка к языку.
3. Каждая из этих программ имеет конструкцию, выполнение которой при-
водит к распечатыванию строки.
4. Все они при их выполнении делают одно и то же: печатают строку
«Hello World!»
В чем же их отличия? Видны некоторые различия в записи, а также в пра-
вилах построения. Таким образом, напрашивается вывод, что, освоив один
язык программирования, можно понимать тексты программ на большинстве
других языков практически так же легко, как образованный русский человек
может читать украинский либо, в крайнем случае, польский (это чуть труд-
нее) текст.1
Казалось бы, что все эти программы, естественно, после преобразования
каждого текста в исполняемый код, приводят к одной и той же машинной
программе, исполняемой компьютером. Как мы дальше увидим, это не так.
Конечно же, и здесь могут быть исключения, но это верно практически для всех т. н.
языков высокого уровня
1
6 ГЛАВА 1. ПОНЯТИЯ
закрепляется их смысл.
Не все операционные языки мы относим к языкам традиционного типа. В частности, язык
Java является операционным, но из-за некоторых принципиальных отличий данного языка
3
/*Язык C:*/
#include <stdio.h>
int main(void)
{printf("Hello World!");
return 0;}
Строка
#include <stdio.h>
В принципе это не исключает переносимости (т. е. возможности исполнять программу
без всяких изменений в разных вычислительных обстановках), и уже были эксперименты
4
отражает то, что написанный текст программы должен быть расширен путем
вставки вместо этой строки текста, именуемого stdio.h, и уже такой расши-
ренный текст подается транслятору. одно из правил формирования текстов
программ на языке С). Таким образом, stdio.h является библиотекой периода
трансляции. Фактически stdio.h содержит все то, что нужно для организации
ввода и вывода данных, описывая компоненты библиотеки периода исполне-
ния.
Текст после #include <stdio.h> — это описание функции без параметров,
вырабатывающей целое (ее заголовок — int main (void)), которая печатает
строку:
// Java:
public class HelloWorld {
public static void main (String[] args) {
System.out.println("Hello World!");
}}
Строка
(* Pascal:*)
program First (Output);
1.1. ЯЗЫКИ И СИСТЕМЫ ПРОГРАММИРОВАНИЯ 11
begin
writeln(’Hello World!’)
end.
(*PASCAL*)
PROGRAM FIRST (OUTPUT);
BEGIN
WRITELN(’Hello World!’)
END.
begin
println(‘Hello World!’)
end
начало
печатать(‘Hello World!’)
конец
(печатать(‘Hello World!’))
ботает с полем зрения, которое уже не может быть прямо представлено как
совокупность ячеек вычислителя. Она проверяет, что поле зрения пусто, и
подставляет вместо пустого выражения то, что идет справа от знака равен-
ства: вызов стандартной функции, печатающей строку и опять очищающей
поле зрения. Поскольку в поле зрения функций больше не осталось, програм-
ма заканчивает работу, а поскольку поле зрения пусто, больше ничего не пе-
чатается.
Пояснение к программе 1.1.7.
:-Print(’Hello World!’);
1.1. ЯЗЫКИ И СИСТЕМЫ ПРОГРАММИРОВАНИЯ 13
Память Управляющее
0 устройство
+1
3
Процессор
К О1 О2
Канал связи
ние — далеко не единственные решения этого плана. Нарушение принципа здесь лишь ча-
стичное: ячейка любого из видов по-прежнему способна принимать значения разного типа,
по-прежнему смысл значения раскрывается только процессором. Заслуживают упоминания
и более радикальные архитектуры, предусматривающие тегирование (см. стр. 62), а также
полное отделение в памяти областей, в которые можно записывать данные и команды (в
обычном режиме выполнения программ процессору не разрешается записывать что-либо в
область команд, в результате повышается надежность программ).
1.2. МОДЕЛЬ ВЫЧИСЛЕНИЙ ФОН НЕЙМАНА И ТРАДИЦИОННЫЕ ЯЗЫКИ 25
академических исследований.
Отечественный опыт разработки машинно-ориентированных языков де-
монстрирует поддержку архитектуры, отличную от Intel-подобной. Укажем
на два проекта этого рода. Первый — язык Ярмо (аббревиатура: язык реа-
лизации машинно-ориентированный), построенный для ЭВМ БЭСМ-6 и от-
ражающий все современные веяния в языкотворчестве. О качестве и востре-
бованности этого языка можно судить хотя бы по тому, что было реализо-
вано несколько его версий. Второй пример — Эль-76, разработанный в ка-
честве аналога ассемблерного языка для многопроцессорного вычислитель-
ного комплекса Эльбрус. Оставаясь в целом фон Неймановской машиной,
архитектура этого комплекса далеко отходит от канонических принципов. В
частности, в ней предусмотрена аппаратная поддержка вызова процедур, сте-
ковая организация памяти и другие высокоуровневые средства программиро-
вания. Особенностью этой архитектуры является тегирование: каждое зна-
чение сопровождается тегом, т. е. описателем того, к какому типу относится
информационная часть значения. По существу это отказ от строго однород-
ной памяти. Все архитектурные особенности Эльбруса отражены в Эль-76,
что позволило рассматривать данный язык в качестве единственного инстру-
мента программирования системных программ. Конечно, нельзя говорить о
механическом переносе этого языка в архитектурную среду другого типа, а
потому время использования его, как и любого машинно-ориентированного
языка, ограничено временем жизни данной архитектуры12 .
8. Язык Ada. Он разрабатывался по заказу Министерства обороны США
на конкурсной основе с предварительным сбором и анализом требований,
с обширной международной экспертизой. По существу в нем воплощена по-
пытка определить язык программирования как экспертно обоснованный ком-
плекс средств программирования. На завершающем этапе конкурса приняли
участие около ста коллективов. В результате проверки соответствия предста-
вленных разработок сформулированным требованиям, для обсуждения об-
щественности было отобрано четыре языка, зашифрованных как Red, Green,
Blue и Yellow. В разной степени критике подверглись все четыре кандидата.
Особенно острым критиком оказался Э. Дейкстра, который камня на камне
не оставил от Red, Blue и Yellow, но чуть-чуть ‘пожалел’ Green, доказывая,
Это утверждение не противоречит долголетию С. Как уже отмечалось, данный язык сего-
дня навязывает архитектуру машин, которая бы не противоречила его эффективной реализа-
12
ции. Например, использование С в качестве базового языка для Эльбруса явно ограничивало
бы доступ ко многим развитым машинным возможностям.
32 ГЛАВА 1. ПОНЯТИЯ
го подхода стало появление языка Java, который был предложен как средство
программирования не для отдельного компьютера, а сразу для всех машин,
имеющих реализацию так называемой Java-машины — исполнителя про-
грамм на промежуточном языке более высокого уровня, нежели командный
язык обычных машины. Иными словами, провозглашается система програм-
мирования с явно и точно определенным промежуточным языком. Другая
особенность Java-проекта — его ориентация на Интернет программирова-
ние: поддерживается возможность построения приложений, запускаемых на
сервере от клиентской рабочей станции и взаимодействующих с клиентом
через произвольный браузер.
Схема трансляции с выделенным промежуточным языком, независящим
от исходного языка, очень даже не новая. В шестидесятые годы ее пытались
применять для сокращения расходов на разработку трансляторов (например,
в качестве промежуточного языка в США был разработан специальный язык
Uncol, в Советском Союзе для тех же целей предлагался язык АЛМО). Чисто
умозрительно можно представить ситуацию. Требуется реализация m язы-
ков на n машинах. В схеме без промежуточного языка в этом случае нуж-
но запрограммировать m × n трансляторов, тогда как использование такого
языка позволяет сократить это число до m + n: для каждого языка пишет-
ся транслятор в промежуточный язык (m) и для каждой машины создается
транслятор или интерпретатор промежуточного языка (n). К тому же, опять-
таки умозрительно, можно предположить, что затраты на ‘половинки’ транс-
ляторов сократятся. Все получается, если удается построить промежуточный
язык удовлетворяющий следующим условиям:
Выполнить эти условия весьма сложно даже для близких языков и машин.
Точнее сказать, что затраты на решение этих задач неизмеримо и неоправ-
данно превышают стоимость пресловутых m × n трансляторов. Поэтому по-
сле серии экспериментальных проектов идея промежуточного языка была
предана забвению. В проекте Java она возродилась, правда, в урезанном до
1.2. МОДЕЛЬ ВЫЧИСЛЕНИЙ ФОН НЕЙМАНА И ТРАДИЦИОННЫЕ ЯЗЫКИ 35
тем, что в прежние времена эту идею изучали в университетах, тогда как сегодня за нее взя-
лись промышленники.
В этой связи уместно следующее замечание. Если бы книга [68] не была бы сегодня би-
блиографической редкостью, то ее главу 2 «Рабочая программа» можно было бы рекомен-
14
довать в качестве первоначального введения для тех, кто желает изучить устройство Java-
машины. После прочтения этого изложения понимание Java-машины окажется более глубо-
ким.
36 ГЛАВА 1. ПОНЯТИЯ
Программа 1.3.2
/* начало блока */
int main (void)
{
scanf ( "d", &X );
X23 = X * X;
X23 = X23 * X;
Y = X23 * X23 * X;
printf (“\n7 power of %d is %d\n”, X, Y);
/* конец блока */
return 0;
}
38 ГЛАВА 1. ПОНЯТИЯ
Программа 1.3.3
/* начало блока */
int main (void)
{
Y = X * X * X;
Y = Y * Y * X;
printf ("\n7 power of %d is %d\n", X, Y );
/* конец блока */
return 0;
}
Строка
тексту программы (см. рис. 1.2, на котором простые стрелки изображают ука-
затели мест хранения нужных текстовых фрагментов, а жирные стрелки по-
казывают процесс включения файла в текст). Из схемы видно, что в С/С++/C#
подключение внешних библиотек — двухступенчатый процесс: в исходном
тексте программы указывается только заголовочный файл. Встраивание его
в тест позволяет транслятору корректно работать с именами, т. е. считать их
описанными и доступными в исходном тексте. Для выполнения транслируе-
мой программы нужно иметь следующую ступень: тексты или отдельно от-
транслированные фрагменты программ, представленных внутри заголовоч-
ных файлов. В С/С++ задачу обеих ступеней решает препроцессор.
В других языках подключение библиотек организовано по-разному. Так,
в Algol 60 понятие внешней библиотеки вообще никак не определялось. Счи-
талось, что достаточно говорить об отдельных процедурах. То же можно ска-
зать и про большинство других ранних языков.
Для внешних библиотек, которые определены языком, нет проблемы, как
указать их подключение — система программирования их “знает”, а потому
может считать их частью программы, которую программист писать не дол-
жен. Эта идея в законченном виде представлена в Алголе 68, в котором лю-
бая программа считается блоком, погруженным в стандартный блок, начина-
ющийся библиотечным вступлением и заканчивающийся библиотечным за-
ключением (см. § 1.1.1). Проблема может возникнуть, когда в распоряжении
программиста есть несколько библиотек, выполняющих похожие функции и
поименованных одинаково. В этом случае нужно явно указывать, какую из
библиотек подключать17 .
В ряде языков приняты более строгие соглашения: программисту пред-
писывается указывать источник подключения (подобно тому, как в С/С++/C#
указывается заголовочный файл) и конкретные подключаемые средства, в
дальнейшем используемые в программе. Если это соглашение оформляется
в виде языковой конструкции, то появляется возможность на уровне языка
конструировать библиотеки. В таком случае можно корректно подключать
не только процедуры и функции, но иные программные фрагменты (напри-
мер, описания переменных). В законченном виде этот подход представлен в
языке Modula-2, в котором программа рассматривается как набор модулей,
Именно это сделано в С/С++/C#, причем для всех, как стандартизованных языком, так
и для других доступных библиотек. С точностью до того, что подключение выполняется в
17
Файлы
Библиотека stdc заголовков
stdio.h
...
scanf
...
Исходный текст программы
Реализация string.h
scanf ...
... #include <stdio.h>
...
...
mem.h scanf
... ...
...
...
scanf
...
………………….
Рис. 1.4. Схема подключения модуля в языках Turbo Pascal и Object Pascal.
46 ГЛАВА 1. ПОНЯТИЯ
1.3.5. Действия
или
Y = X23 * X23 * X;
Y = X * X * X;
***
48 ГЛАВА 1. ПОНЯТИЯ
Для того, чтобы разобраться с тем, как порядок конструкций в тексте про-
граммы соотносится с порядком их исполнения абстрактным вычислителем
и с представлением абстрактного вычислителя в конкретном, целесообразно
рассмотреть понятия последовательного, параллельного и совместного ис-
1.4. ВЫЧИСЛЕНИЯ И ТЕКСТ 49
Действия окружения
Активизация main
В любом порядке
Выделение памяти для X Выделение памяти для Y
Чтение X
Y = X * X * X;
В любом порядке
Y = Y * Y * X;
Вывод X и Y
Завершение вычислений
Программа
Подключение
Заголовок Блок
внешних текстов
#include <stdio.h>
Y = Y * Y * X; Оператор присваивания
Оператор Операторы
Оператор
полнения.
При последовательном вычислении последовательность языковых кон-
струкций переходит в команды абстрактного вычислителя, исполняемые стро-
го в том порядке, который явно задан в тексте программы (именно так, в пер-
вом приближении, интерпретируется последовательность операторов, разде-
ленных ";", в языках С и Pascal).
Параллельное вычисление означает, что для выполнения двух и более ко-
манд абстрактного вычислителя выделяется соответствующее число процес-
соров, каждый из которых выполняет свою команду (в стандартном С по-
нятие параллельности отсутствует, в Алголе-68, Concurrent Pascal и других
языках параллельные участки часто выделяются отдельными программными
скобками, например, parbegin и parend; обрамляемые ими операторы могут
быть выполнены параллельно).
Даже если на машине всего один процессор, указание на параллельность
процессов часто полезно, поскольку оно позволяет организовать квазипарал-
лельное исполнение, при котором команды каждого из процессов исполняют-
ся в строгом порядке, но они могут перемежаться в абстрактном вычислителе
командами другого, квазипараллельно исполняемого, процесса. В частности,
это позволяет более производительно использовать время, которое процесс
тратит на ожидание завершения операции ввода-вывода. Например, при та-
ком исполнении отрезок программы
printf("Первый результат: %d, %d\n", X, Y);
Оператор 1;
...
того из подвыражений a*b, c*d, для которого уже готовы исходные данные.
Если a, b, c, d являются простыми переменными, то действительно безраз-
лично с точки зрения результата, в каком порядке их вычислять. Но вычи-
сление операнда может иметь побочный эффект, то есть вызывать не отно-
сящиеся к использующему оператору и не обозначенные явно действия. На-
пример, побочный эффект появляется при вызове функции, в теле которой
меняются значения глобальных переменных. Распознавать такие ситуации в
общем случае невозможно. И поэтому в руководствах по языкам можно часто
увидеть загадочную фразу «побочный эффект игнорируется», смысл которой
соответствует тому, что в данном месте могут быть элементы совместного
вычисления.
Явно и достаточно четко концепция совместного исполнения была прове-
дена лишь в Алголе 68, в котором был введен другой разделитель операторов:
“,” вместе с оставшимся как признак строго последовательного исполнения
“;”.
x := 1, y := 2, z := 3;
(1.1)
a[x] := b[y], d[z] := e[x];
На примере данного текста программы можно проиллюстрировать понятие
строгого совместного вычисления. При таком исполнении результаты фраг-
мента программы одни и те же при любой реализации совместного испол-
нения. В приведенном выше фрагменте совершенно безразлично, в каком
порядке присваивать значения переменным и в каком — элементам масси-
вов, лишь бы сначала были сделаны присваивания переменным, а затем —
присваивания элементам массивов. Казалось бы, такие случаи можно было
Если изучить стандарт языка Java, то оказывается, что он специфицирует в данном случае
слабое совместное исполнение (см. ниже) в чистом виде.
18
1.4. ВЫЧИСЛЕНИЯ И ТЕКСТ 53
Охраняемые команды:
if
A1 → S1 ,
(1.2)
... ,
An → Sn
fi
if
x 6 y → z := x,
y 6 x → z := y (1.3)
fi
return(z);
Если наши данные не являются числами и лишь предупорядочены (см., напр. [34]),
то не определено, какое значение получит z в итоге, но это нам и безразлично.
19
К сожалению, в общераспространенных системах таких средств не предусмотрено.
Мы в идущем ниже примере чуть-чуть изменили обозначения, чтобы привести их в боль-
шее единообразие с тем единственным случаем, когда совместные исполнения были явно
20
X := 1;
parbegin
X := X + 1;
Y := 5 + X;
parend;
с плавающей точкой! */
float P, q;
float D;
Адреса Команды
//команды, вычисляющие условие и
//помещающие результат в процессорный регистр S
m
m+1
ПЕРЕЙТИ К m+k+i+j
... ...
Программа 1.4.2
if ( D > 0 )
{
D = sqrt ( D );
X1 = r + D;
X2 = r - D;
printf ("\nX1 = %f, X2 = %f\n", X1, X2);
}
else if ( D == 0 )
{
X1 = r;
printf ("\nX1 = X2 = %f\n", X1);
return 0;
}
Оба приведенных выше представления условного оператора по сути изо-
морфны, и при каждом из них возникает одна и та же проблема. Рассмотрим
запись
if . . . then if . . . then . . . else . . . ;
К какому из if . . . then относится else? Есть два, с внешней содержательной
точки зрения совершенно равноправных ответа:
if . . . then
if . . . then . . .
и
else . . . ;
if . . . then
if . . . then . . . else . . . ;
Здесь использованы отступы и концы строк, чтобы отразить в записи, что к
чему относится. Решения данной проблемы известны следующие:
1. Запретить использование условного оператор в качестве действия T.
Такое решение, принятое в Алголе 60, приводит тому, что при необхо-
димости нужно превращать условный оператор в безусловный, обрам-
ляя его блочными скобками:
if . . . then begin if . . . then . . . else . . . end;
if . . . then begin if . . . then . . . end else . . . ;
нии (x + y) ∗ z, и литералы.
1.5. РАБОТА СО ЗНАЧЕНИЯМИ 61
пустимая операция.
62 ГЛАВА 1. ПОНЯТИЯ
Тег Значение
определяет тип того, что хранится в основной части ячейки (тип значения)
<Int a> + <Float b> → <Float aF > + <Float b> Выполняется
процессором
где loc type — это выражение, вырабатывающее новое значение типа ref type
(локальный генератор адреса для размещения значений типа type в качестве
содержимого по указанному адресу; это — один из исключительно редко
встречающихся случаев, когда переменная задается не именем, а значением
некоторого выражения). Таким образом, по-прежнему определяется констан-
та, значением которой обладает имя Имя1.
1.5. РАБОТА СО ЗНАЧЕНИЯМИ 65
Но это значение есть адрес (о чем говорит служебное слово ref), и появля-
ется возможность использовать Имя1 как переменную: можно присваивать
ей значение типа type.
Аналогично
ref type Имя2;
сокращение для
ref ref type Имя2 = loc ref type;
Таким образом, есть возможность присваивать Имя2 значение типа ref type.
Иными словами, Имя2 — это переменная, предназначенная для размещения
в ней адресов, или, что то же самое, имен других переменных.
Таким образом, последовательность предложений
real A = 53/5;
real B;
описывает константу A со значением, равным результату деления числа 53 на
5, и переменную B вещественного типа. Можно записать и выполнить при-
сваивание B:=A, но не наоборот. Если же записать
real A := 53/5;
real B;
то A оказывается переменной и присваивание A:=B становится законным.
В языке С все, казалось бы, проще. Понятие констант и переменных опре-
деляется на уровне соглашений об их отношении к памяти вычислителя. Опи-
сание
int X, Y;
определяет имена X и Y как переменные, способные принимать целочислен-
ные значения, а
float X, Y;
как вещественные переменные. Но, как и обычно, иная простота хуже воров-
ства. За недоработку в базовых понятиях приходится многократно платить
целой серией соглашений, которые сейчас будут описаны для простейшего
случая, и которые отдельно и достаточно сложно вводятся для более сложных
случаев, возникающих, в частности, в объектно-ориентированном програм-
мировании.
Для описания соглашений об отношении между переменными и памятью
вычислителя в C вводится понятие класса памяти, отражающее различные
66 ГЛАВА 1. ПОНЯТИЯ
Понятие класса памяти подтверждает тот факт, что С создавался как язык
программирования для вычислителя вполне определенной архитектуры. Сей-
час язык C++ реализован практически для всех архитектур. Но для них, с
одной стороны, некоторые из конкретных средств С оказались избыточны-
ми (например, регистровые переменные на архитектуре Intel), с другой сто-
роны, для некоторых из ценных качеств, предоставляемых архитектурно, в
С++ нет средств для их эффективного использования (примером является те-
гирование). В целом в языке C и его наследниках догматизируется принцип
однородности памяти.
Особенно вредной догматизация этого принципа оказалась в случае кон-
стант. Поскольку с точки зрения процессора традиционной архитектуры кон-
тейнер, содержащий константу, ничем не отличается от контейнера, в ко-
тором находится переменная того же типа, сам язык С дает возможность
использования только литералов. Однако с помощью препроцессора можно
приписать константам имена, что широко применяется на практике. Соот-
ветствующий пример показан в программе 1.5.1.
Программа 1.5.1
и ссылки:
int **a;
int &r;
68 ГЛАВА 1. ПОНЯТИЯ
(A + B) + C и B + (C + A)
Синтаксис, семантика и
прагматика языка
программирования
69
70 ГЛАВА 2. ССП
Определение 2.1.1.
Синтаксис алгоритмического языка — совокупность правил, позволяю-
щая формально проверить текст программы, выделив тем самым множество
синтаксически правильных программ, и разбить эти программы на составля-
ющие конструкции и в конце концов на лексемы.
Семантика алгоритмического языка — соответствие между синтаксиче-
ски правильными программами и действиями абстрактного исполнителя, по-
зволяющее определить, какие последовательности действий абстрактного ис-
полнителя будут правильны в случае, если мы имеем данную программу и
данное ее внешнее окружение.
Под внешним окружением понимаются характеристики машины, на кото-
рой исполняется программа (точность представления данных, объем памяти,
другие программы, которые можно использовать при выполнении данной, и
т. д.), и потоки входных данных, поступающие в программу в ходе ее испол-
нения.
Конец определения 2.1.1.
Задаваемое семантикой соответствие между входными данными, програм-
мой и действиями, вообще говоря, определяется лишь полным текстом про-
граммы, но для понимания программы и работы над ней необходимо, что-
бы синтаксически законченные фрагменты программы могли интерпретиро-
ваться автономно от окружающего их текста.
При создании описания языка следует стремиться к выполнению следу-
ющих требований:
определять нет смысла, поскольку за понятием КС-грамматики, при внешней его простоте,
стоит достаточно серьезная теория. Эта грамматика представляется во многих формах (син-
таксические диаграммы, металингвистические формулы Бэкуса-Наура либо расширенные
металингвистические формулы), и, как правило, сопровождает любое систематизированное
изложение конкретного языка. Любое такое конкретное представление КС-грамматики до-
статочно понимаемо.
74 ГЛАВА 2. ССП
ского языка, обрамляемые угловыми скобками < и >. Эти слова и сочета-
ния слов называются наименованиями понятий.
что смысле.
Используя контекстно-свободную грамматику, можно следующим обра-
зом записать определения для конструкций, уже показанных к настоящему
моменту в примерах программ:
<программа /* на языке С*/> ::= <заголовок> [ <описание функции /* одна
из которых имеет имя main */> ]+
<заголовок /* это определение упрощено4 */> ::= [<объявление подключа-
емого файла>]*
<объявление подключаемого файла> ::= “#include” <указание имени фай-
ла>
<указание имени файла> ::= “<” <наименование файла> “>” |<наимено-
вание файла>
<наименование файла> ::=
<имя файла> [ “.” <расширение> ]?
Комментарий /*это определение упрощено*/ здесь и далее означает, что в стандарте языка
данное понятие определено более полно. Некоторые из таких понятий в дальнейшем будут
4
дополняться.
2.1. СИНТАКСИС, СЕМАНТИКА 77
<имя функции > “(” [ <параметры> [“,” <параметры> ]* ]? “)” “{” <описания>
<параметры> ::= <описатель> < имя параметра > [ “,” <имя параметра>
тор> ]?
<условие> ::= <выражение >
<пустой оператор>
<имя функции>
<тип функции>
{<описания> <операторы>}
(<параметры>)
<описание функции>
Следуя этой точке зрения, мы, говоря о модели программы, всегда имеем в
виду ее образ в виде команд абстрактного, а не конкретного вычислителя.
Понятие модели вычислений языка естественно распространяется на слу-
чаи, когда используются библиотеки программ. Библиотеки, стандартизован-
ные описанием языка, можно считать частью реализации языка независимо
от того, как реализуются библиотечные средства: на самом языке или нет.
80 ГЛАВА 2. ССП
Показательный пример такого рода дает язык FORTRAN. В этом языке разрешается ис-
пользование идентификаторов с пробелами (причем пробелы внутри идентификаторов про-
6
Как он это ухитряется делать, рассматривать нет смысла, но гораздо практичнее и безопас-
DO I=1,. . .
нее запретить пробелы в идентификаторах, что сегодня можно считать стандартом почти
для всех языков программирования.
2.2. ПРАГМАТИКА 81
§ 2.2. ПРАГМАТИКА
и
Inc (<переменная>)
числений языка, так как для нее постулируется, что язык является машинно-
ориентированным и отражает в себе особенности архитектуры вычислитель-
ного оборудования, а команды увеличения и уменьшения на единицу предо-
ставляются программисту на уровне оборудования достаточно часто.
Если же рассматривать Turbo Pascal как правильное расширение стан-
дартного языка Pascal, не содержащего обсуждаемые команды из-за стрем-
ления к минимизации средств, то эти команды есть не что иное, как подсказка
транслятору, как надо программировать данное вычисление. Следовательно,
указанные операторы для данного языка нужно относить к прагматике. 8
Семантическая прагматика — это уже упомянутые определения того,
что в описании языка оставлено на усмотрение реализации или предписыва-
ется в качестве вариантов задания вычислений.
Пример 2.2.2. Стандарт языка Pascal утверждает, что при использовании
переменной с индексом на уровне вычислений контролируется выход индек-
са за диапазон допустимых значений. Однако в объектом коде постоянные
проверки этого свойства могут оказаться накладными и избыточными (на-
пример, когда можно гарантировать соответствующие значения индексов).
Стандарт языка для таких случаев предусматривает сокращенный, т. е. без
Вообще говоря, данная подсказка избыточна для современных трансляторов, поскольку
распознать шаблон вида
8
Даже сам автор данной программы через некоторое время не поймет, почему
же она у него так себя ведет. . .
Конец примера 2.2.3.
Как это ни странно, подобные построения используются в практике програм-
мирования на С: они применяются, чтобы в одном общем тексте задать не-
сколько вариантов выполняемых программ, которые разграничиваются при
работе препроцессора, т. е. до выполнения.
Наложение команд препроцессора на текст программы — это смешение
двух моделей вычислений: одна из них — модель базового языка С, другая —
модель препроцессора.9 В результате программист при составлении и изу-
чении программ вынужден думать на двух уровнях сразу, а это и трудно, и
провоцирует ошибки.
Модель вычислений препроцессора можно охарактеризовать следующими свойствами.
Исходные перерабатываемые данные — это текст (любой структуры, не обязательно на язы-
9
ке С), в котором имеются строки, начинающиеся с символа ‘#’. Такие строки представляют
команды, управляющие работой препроцессора. Например,
дает указание препроцессору на то, что в оставшейся части текста идентификатор two дол-
#define two 2
АБСТРАКТНОЕ И КОНКРЕТНОЕ
ПРЕДСТАВЛЕНИЯ ПРОГРАММЫ
§ 2.3.
X = a ∗ b + c ∗ d;
X = (a ∗ b) + (c ∗ d);
X = ((a ∗ b) + (c ∗ d));
2.3. ПРЕДСТАВЛЕНИЯ ПРОГРАММЫ 87
A + B ↔ B + A.
+ *
a + + +
* d a b c d
b c
a+b*c+d (a + b)*(c + d)
a b c d
a + (b*c) + d
Рис. 2.2. Абстрактно-синтаксическое представление выражений
90 ГЛАВА 2. ССП
printf
Оператор
присваивания
Получатель Источник
Адрес Выражение
Выражение,
Выражение Выражение Адрес
Сложение Вычитание Переменная
+ + Имя
Операнд Операнд
Выражение Выражение
...
Выражение
Сложение
Операнд Операнд
Операнд Операнд
Выражение Выражение
Переменная Переменная
Имя = b Имя = c
Операторы Совместные
операторы
Последовательно Совместно
выполняемые выполняемые
* *
Оператор Оператор
Оператор
условный
Оператор
условный
?
Условие Действия T Действия F
Выражение Операторы Операторы
Выражение, Выражение,
Оператор Оператор
Вызов Имя
функции функции
Имя * *
функции Параметр Параметр
Имя
Выражение Выражение
Имя
(a) (b)
...
if ( D > 0 )
{
D = sqrt ( D );
X1 = r + D;
X2 = r - D;
printf ("\nX1 = %f, X2 = %f\n", X1, X2);
}
else
...
…
Усло вный
Имя = printf
Имя = D Значение = 0
Параметр Параметр
Получатель Источник Переменная
Константа
Получатель Источник Переменная —
Адрес Имя
функции
Имя = X2
Операнд Операнд
Имя = D Переменная Переменная
Получатель Источник Параметр
Имя = sqrt
Переменная + Имя = X2 Переменная
Имя = r Имя = D
Операнд
Переменная Операнд Операнд Имя = X1
Переменная Переменная
для его точного определения нужно задать его синтаксис и семантику. Мето-
ды определения синтаксиса рассматриваются во вводных курсах программи-
рования для будущих профессионалов. Хотя полноты определения синтакси-
са обычно не достигают, но изложение этих методов достаточно последова-
тельное и строгое (как Вы могли увидеть в предыдущих главах данной кни-
ги). Даже если нечто объясняется на пальцах вместо кропотливого точного
определения (как, например, соглашения о том, какое описание соответству-
ет какому вхождению идентификатора), об этом говорится прямо.
Семантику же обычно рассказывают на поверхностном уровне, без те-
ни точных определений, в выражениях типа «При исполнении присваивания
x:=S старое значение, содержавшееся в памяти, обозначенной именем x, ис-
пользуется для вычисления S, после чего заменяется на полученное значе-
ние.» Это скорее беда, чем вина начальных (да и профессиональных) курсов
программирования. Ведь смысл — многоаспектное, крайне сложное и не-
формализуемое понятие. Это означает, что любое его уточнение приемлемо
лишь для некоторых целей и отказывает в других случаях. Но это не озна-
чает, что формализовывать семантику не нужно. Ведь если мы откажемся от
ее формализации, мы тем самым сползем на позицию первых языков типа
FORTRAN: смысл выражений языка определяется тем, как они переводятся
транслятором и исполняются компьютером.11
Далее, поскольку алгоритмические языки делались прежде всего не для
удобства определения их свойств, а для удобства их использования, работа-
ет концептуальное противоречие, которое вы могли бы уже заметить в кур-
се, например, логики: чем удобнее представление формального понятия для
применения, тем тяжелее его строгое формальное определение. Определе-
ние семантики реальных алгоритмических языков намного сложнее семан-
тик математических языков (хотя и намного проще семантик естественных
языков). Оно находится как раз на той грани, когда считанные по пальцам
профессионалы еще могут создать точное и полное определение и восполь-
зоваться им, но при этом практически все их интеллектуальные ресурсы ухо-
дят на выработку и освоение данного понятия. Соответственно, данное точ-
ное определение задает лишь значение либо действие конструкций языка,
Конечно, такое определение семантики алгоритмического языка в принципе является не-
погрешимым для каждой конкретной его реализации, применяемой в конкретной среде про-
11
граммирования. Но оно, мало того, что ничего не дает для осмысленного применения чело-
веку, еще и меняется непредсказуемо при изменениях версии или настроек транслятора и
всей вычислительной системы.
2.4. ПРИНЦИПИАЛЬНЫЕ ТРУДНОСТИ, СВЯЗАННЫЕ С СЕМАНТИКОЙ 103
Стили программирования,
или программирование с
птичьего полета
106
3.1. СТИЛИ ПРОГРАММИРОВАНИЯ 107
Предупреждение!1
То, что в данном тексте называется “методикой программирования”, в ли-
тературе традиционно называется “методологией”, а то, что у нас обознача-
ется этим словом, называется “парадигмой программирования”.
Остановимся на методиках. Они направлены на поддержку деятельности
коллектива, работающего с программой, в том числе и ее автора (авторов).
Поскольку методики включают в себя организационные и административ-
ные аспекты, они чаще всего даются в виде рецептов, следование которым, по
мнению их разработчиков, приводит к хорошим результатам. Обычно пред-
полагается (иногда об этом говорится явно), что методика фиксирует опыт
практической разработки хороших и неудачных программ. Однако разные
методики чаще противоречат друг другу, чем согласуются, а потому появля-
ется конкуренция между ними за сферы влияния. В рекламных целях опыт,
на базе которого возникла методика, затушевывается, и утверждается, что
методика годится на все случаи жизни. То же самое, хоть и не столь регуляр-
но, происходит и с методологиями. Это ведет к фетишизации, которая про-
воцирует применение методологий и методик в неподходящих ситуациях, а
следствием этого является их дискредитация. В результате эти действитель-
но полезные методологии и методики порой отвергаются на некоторое время.
Важнейший аспект стиля и соответствующей ему методологии: запре-
щения и самоограничения. Необходимо твердо усвоить, что многие базо-
вые концепции плохо согласуются друг с другом. В результате, будучи со-
единенными вместе, они утрачивают свои лучшие стороны, зато многократ-
но усиливают худшие (как говорят, они концептуально противоречат друг
другу). Поэтому каждый стиль отгораживается от тех конструкций, которые
концептуально противоречат ему (если уж вам такая конструкция необходи-
ма, выносите ее в под-, над- или сопрограмму)2 . Большой бедой является то,
что, запрещая не согласующиеся со стилем конструкции, практически все-
гда представляют их как абсолютно плохие, хотя на своем месте они часто
Заметим, что в программировании происходит систематическая ‘возгонка’ понятий. То,
что в других местах называется орудием, здесь называется методом; то, что называется мето-
1
дом или методикой, называется методологией; то, что называется методологией, возводится
в ранг парадигмы, и т. д. В данном пособии мы придерживаемся выражений, более адекват-
но передающих смысл используемых понятий и согласующихся с общечеловеческой прак-
тикой.
2
На худой конец, используйте комментарии, чтобы явно выделить отступления от стиля.
3.1. СТИЛИ ПРОГРАММИРОВАНИЯ 109
великолепно работают.
Понятие стиля программирования требует глубокой концептуальной про-
работки. При этом требуется обобщение большого количества практическо-
го материала и работа на стыке нескольких сложных теоретических напра-
влений. Из-за трудности задачи и во многих случаях из-за недостаточности
имеющегося теоретического и практического аппарата, анализ ряда стилей
вообще не делался. Поэтому в данном курсе мы приводим только извест-
ные нам результаты анализа, излагая их в как можно более доступной фор-
ме. Знать эти результаты особенно важно из-за того, что они дают не толь-
ко оптимистические заключения, но еще и серьезные предупреждения. Эти
предупреждения гораздо важнее для практика, чем гладенькие результаты о
принципиальной возможности что-то сделать (можно сравнить с прохожде-
нием программы через транслятор: то, что она успешно оттранслировалась,
почти ничего не говорит о ее правильности, а вот выданные при трансляции
предупреждения часто содержат ценнейшую информацию).
Так что в отношении стилей возникает та же ситуация, которая была с по-
нятием программы до появления цельной системы концепций и разделения
конкретных и абстрактных аспектов: несущественные особенности конкрет-
ного представления часто мешают понять идейное единство. С другой сто-
роны, формальное внешнее сходство затушевывает серьезнейшие концепту-
альные различия. Как говорится, за деревьями не видно леса.
Следует учесть, что разные стили ориентированы на разные модели вы-
числений, и, соответственно, система программирования выглядит по-раз-
ному для тех, кто привык к одному или к другому стилю, причем одни и те
же слова, как правило, приобретают разные значения, и могут возникнуть
серьезнейшие трудности при передаче опыта, при общении программистов,
при взаимодействии созданных ими систем.
Обратим внимание на другую сторону стиля. Составление программы —
процесс, гораздо в большей степени аналогичный составлению текста на
естественном языке, чем это может показаться на первый взгляд. Конечно,
речь идет не о художественных текстах, а о тех, которые читают, чтобы по-
лучить информацию. При работе над такими текстами понимание того, кто
именно будет его читать, просто необходимо для качественного изложения3 .
Поэтому рассмотрим читателей программы.
Самый очевидный читатель программы — это вычислитель, для которого
А непонимание и неучет читателя, соответственно, один из распространеннейших недо-
статков.
3
110 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
очередь, понимается как: ‘всегда, если задача не заставляет нас явно заняться этим вопро-
сом’. А задача определяется как цель в заданных ограничениях, и тот компонент задачи, ко-
торый чаще всего заставляет заняться вопросами эффективности — это ограничения.
3.1. СТИЛИ ПРОГРАММИРОВАНИЯ 111
на шпильках.
112 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
языка. Кроме них, есть еще круг вопросов, ответы на которые устанавливают
связи стиля программирования с методами проектирования программы:
• Как структурировать априори однородную память?
• На какие логические и физические части должна разбиваться програм-
ма?
• Как преодолевать сложность процесса разработки программного обес-
печения?
• За счет чего можно достичь повторного использования накопленных
программных решений и облегчать модификации программ?
Ответы на подобные вопросы формирут методологии и методики, реализу-
ющие стили программированния.
Из анализа опыта работы с фон Неймановской моделью вычислений вы-
являются свойства операционных программ, которые при надлежащей ин-
терпретации применимы также для характеристики стилей программирова-
ния в нетрадиционных моделях вычислений. В частности, для этих моделей
можно говорить об уровнях абстрактности описания данных и действий, о
глубине структурированности действий и данных, о связи структурирован-
ности с содержательным смыслом переработки данных, о степени развет-
вленности вычислений (аналоги рекурсии, условных конструкций и циклов
операционных языков). Возникает контекст вычисления, можно оценивать
степень глобальности и локальности действий и данных. Поэтому возмож-
но проникновение стилей, сформировавшихся в рамках фон Неймановской
модели, в нетрадиционное программирование.
Более того, привязанность стиля к базису часто переоценивается. Напри-
мер, в книгах [37, 77] приведены методики, как структурно программиро-
вать для столь неструктурных языков, как старые ассемблеры, FORTRAN и
• Программирование от состояний;
• Структурное программирование;
• Сентенциальное программирование;
• Программирование от событий;
• Функциональное программирование;
• Объектно-ориентированное программирование;
• Программирование от переиспользования;
• Программирование от шаблонов.
a b c d
4
1 2 3 4 1 Q P
2 3 4 1 2
3 4 1 2 3 R
1 3
Здесь состояния идентифици-
4 1 2 3 4
A
◦
A∨B
A B
f g B
B ◦ C
B∨C
x:=a
y:=a
x:=f(x)
P(x)
1 0
x:=f(x) stop(x,y)
P(x)
1 0
y:=f(y)
x:=f(x)
P(y)
P(x) 1 0
1 0
y:=f(y)
x:=f(x)
P(y)
P(x) 1 0
1 0
y:=f(y)
x:=f(x)
P(y)
P(x) 1 0
1 0
y:=f(y)
x:=f(x)
P(y)
P(x) 1 0
1 0
Error
СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ —
САМЫЙ РАСПРОСТРАНЕННЫЙ СТИЛЬ
§ 3.3.
в них совпадают.
126 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
ческим стандартом.
3.3. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 127
торые не имеют никакого отношения к реальной действительности. Так что при переходе от
уровня рассмотрения программы самой по себе к уровню программы как части архитекту-
ры реальной человеко-машинной системы идеальные и реальные объекты порою меняются
местами.
128 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
◦ f
Q g P
◦
Программа 3.3.1
Программа 3.3.2
вых средств завершения конструкций была осознана еще в начале 70-х гг.
3.3. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ 131
E3 ⇒ {O2.hE3, O3.hE3}
Встраивание,
вызов и т.д.
(любой смысл)
Модули (библиотеки)
a) составление последовательного текста программы
Стандартизованный текст
программы (проект)
Встраивание
(любой смысл)
Подготовка данных;
{
Программа 3.6.2
стейших случаях. Например, если процесс долго ждет своей очереди на ис-
полнение, то естественно постепенно поднимать его приоритет. Но тогда при-
оритет такого «пользовательского» действия может превзойти приоритет си-
стемного процесса, и остается либо статически задавать каждому процессу
допустимый максимальный приоритет и мы вновь попадаем в ту же ловушку,
либо задавать разным классам процессов приоритеты просто несравнимые
по порядку, чтобы ни при каком нормальном исполнении приоритет пользо-
вательского процесса не дорос, скажем, до 109 , за которым начинаются при-
оритеты срочных системных демонов.
Заметим, что назначение приоритетов операционной системой, «сверху»,
принципиально ничего в данной картине не меняет. С точки зрения пользо-
вательских программ эти приоритеты все равно статические.
Рассмотрим теоретически, какие классы программ можно описать при
помощи статических и динамических приоритетов. Оказывается, при помо-
щи статических приоритетов, являющихся натуральными числами, невоз-
можно описать даже примитивно-рекурсивные композиции исходных дей-
ствий21 .
Таким образом, чисто концептуально натуральных чисел не хватает для
описания приоритетов, но оказывается, что для них хватает ординалов (см.
курс математической логики). Поскольку ординалы до ε0 имеют хорошую
вычислительную модель (см., напр. [63]), можно рассматривать целочислен-
ные приоритеты как конкретную реализацию абстрактного класса таких ор-
диналов.
А в случае реально распределенных или реально параллельных действий
проблема приоритетов еще важнее и еще сложнее. Часто управление по при-
оритетам — единственный шанс справиться со сложностью такой нелиней-
ной во времени системы. Тут уже и линейно упорядоченного множества при-
оритетов не хватает.
В целом программирование от приоритетов является мощным, но спе-
цифическим орудием для описания глобальных совместных, параллельных
или распределенных процессов. Его элементы проникают и в традиционные
системы программирования в виде обработки исключительных ситуаций в
программах. Здесь, как и в событийном программировании с обособленными
реакциями, всего два приоритета, которые налагаются на структуру исполь-
зующей конструкции. Противоречивость этого средства со структурным про-
Правда, здесь мы отвлекаемся от реальных ограничений, и считаем 1010 столь же легко
10
граммированием только в том, что не всегда ясно, что делается далее, когда
обработка исключения завершается. В сравнении со структурными перехо-
дами такое расширение представляется более искусственным.
λx.x(x)(λx.x(x)).
third(i) = i%3.
Для преподавания надо найти небольшую предметную область, для которой можно было
бы проделать системный анализ и построить обозримую открытую модель. Это еще возмож-
но (например, в качестве таковой можно рассматривать игру в ‘очко’ с учетом различных
стратегий игроков и их психологии.) Но на таких задачах удается показать лишь некоторые
преимущества и некоторые стороны объектной модели, которые почти уравновешиваются
недостатками, связанными с резким увеличением объема сопровождающей моделирующую
программу информации.
Можно привести содержательную аналогию. Если построен мощный танк, то даже для
того, чтобы его разместить, требуется значительное пространство, а уж чтобы ему развер-
нуться и показать свои возможности. . . Естественно, что его нельзя опробовать на столе в
комнате директора или в аудитории, рассчитанной на 10 человек.
154 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
принимаете на себя роль обучаемого, а авторы данной книги — роль наставников. Человек,
выступающий в разных ролях, на одни и те же раздражители реагирует по-разному. Если
же человек играет не ту роль, которую ожидают от него окружающие, то он ведет себя (с
точки зрения окружающих) неадекватно, что зачастую рассматривается как один из видов
асоциального поведения.
3.9. ТРИ ТЕХНОЛОГИЧЕСКИХ СТИЛЯ ПРОГРАММИРОВАНИЯ 155
Внимание!
жаргоне, на парсер и обработчик). Это как раз то, что дает объ-
ектная модель документа.
Если D~x = D~x × D~x , причем значения аргументов из D~x фиксированы (для
0 00 00
Y = V(P, X).
MC(P, X) v X
и
X ∪ (PEV(P, X, Y)2 = Y.
А вот это математическое определение прочитайте и разберитесь в нем сами!
Русский программист обязан хорошо владеть математическим языком, иначе
он неконкурентоспособен на мировом рынке, особенно после 30 лет.
В данном определении нет никакой привязки к модели вычислений язы-
ка: смешанные вычисления можно определить везде, где определено поня-
тие алгоритма абстрактного вычисления языка. Ссылка на память не озна-
чает действий в фон Неймановской модели, сравните наше определение с
описанием теории рекурсивных схем в § A.6. Задача специализации состоит
в том, чтобы реализовать требуемое деление алгоритма на остаточные дан-
ные и остаточную программу и предварительное выполнение той его части,
которая обусловлена сведениями о перерабатываемых данных.
Пример 3.9.1. Пусть требуется построить программу, вычисляющую функ-
цию λX. X n . Это означает, что программа должна работать для любых зна-
чений X и для конкретного фиксированного значения n. Начнем со следу-
ющей более универсальной программы, реализующей функцию λX, n. X n
(для простоты иллюстрации здесь и далее приводится лишь фрагмент про-
граммы, касающийся существа дела):
Программа 3.9.1
3.9. ТРИ ТЕХНОЛОГИЧЕСКИХ СТИЛЯ ПРОГРАММИРОВАНИЯ 173
Y := 1;
while n>0 do
begin if n MOD 2 = 1
then begin Y :=Y * X; n := n - 1 end;
n := n DIV 2; X := X * X
end
Y := X; X := X * X; X := X * X; Y := Y * X (3.1)
Y := 1 * X; X := X * X; X := X * X; Y := Y * X; X := X * X (3.2)
автор.»
3.10. ОБЩИЕ ВЫВОДЫ 179
то из-за отсутствия удобных средств привык чесать левой ногой за правым ухом или спать
на гвоздях, он зачастую будет делать это, даже когда необходимость пройдет, а если имеет
власть, то и других заставлять делать то же. Оглянитесь вокруг, и Вы увидите целую кучу
таких примеров.
180 ГЛАВА 3. ПРОГРАММИРОВАНИЕ С ПТИЧЬЕГО ПОЛЕТА
В свзязи с этим можно заметить, что популярная в настоящее время технология RUP явля-
ется средством автоматного представления динамики графики и следующей из нее динамики
34
§ 4.1. ВВЕДЕНИЕ
181
182 ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
Использование
Разработка
Продолжающаяся разработка
(сопровождение)
• сопровождение.
Определение требований
Спецификации
Реализация
Тестирование
Сопровождение
Фаза эксплуатации и
сопровождения
Развитие
Определение требований
Спецификации
Проектирование
Реализация
Тестирование и отладка
Эксплуатация и сопровождение
является стимулом творческой деятельности человека. Более того, задачу, которая требует
творческого подхода, лучше всего формулировать в виде проблемных противоречий, макси-
мально обостряя их. Именно тогда можно надеяться на то, что будет найдено действитель-
но хорошее и адекватное решение. Методология использования проблемных противоречий
в творческих рассуждениях была развита Г. С. Альтшуллером и советской школой теории
решения изобертательских задач (ТРИЗ). Программистам и особенно преподавателям про-
граммирования настоятельно рекомендуется прочитать книгу Г. С. Альтшуллера [4]. Хотя
методы ТРИЗ в большинстве своем прямо не могут быть применены при решении програм-
мистских задач, поскольку они в основном направлены на поиск элементарных решений,
преодолевающих «сопротивление материала», методология подхода исключительно полез-
на для любого творческого человека, занимающегося созданием реальных систем (именно
этим программисты, ученые и инженеры отличаются от гуманитариев).
4.2. ТРАДИЦИОННОЕ ПРЕДСТАВЛЕНИЕ 189
Определение требований
Спецификация требований:
• системные требования
Проектирование
Чем заканчиваются этапы
Реализация:
• кодирование и автономная отладка
Эксплуатация и сопровождение
Определение требований
Спецификация требований:
• системные требования
Проектирование
Чем заканчиваются этапы
Реализация:
• кодирование и автономная отладка
Эксплуатация и сопровождение
1. планирование,
2. разработка,
3. обслуживание,
4. выпуск документации,
5. испытания,
6. поддержка,
196 ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
7. сопровождение.
Пополнение базового
окружения проекта
Окончание работ
↔
Рис. 4.9. Фазовое измерение модели жизненного цикла при итетаривном раз-
витии проекта
4.3. ИТЕРАТИВНЫЕ МОДЕЛИ ЖИЗНЕННОГО ЦИКЛА 205
тем, когда речь идет о совместной поддержке нескольких версий (а именно такая ситуация
типична для итеративного проектирования) окончание работ игнорировать нельзя.
Явное отступление от этого постулата явилось одним из методологических достижений
объектно-ориентированного проектирования, значение которого выходит далеко за рамки
6
собственно ООП.
4.3. ИТЕРАТИВНЫЕ МОДЕЛИ ЖИЗНЕННОГО ЦИКЛА 207
Пополнение базового
окружения проекта
Окончание работ
Контрольные
точки
(события): 0 1 2 3 4 6 7 8 9 10 11 12
V
Пл| Ан| Ко | Пр| Те | Оц
IV
Пл| Ан| Ко | Пр| Те | Оц
III
Пл| Ан| Ко | Пр| Те | Оц
II
Пл| Ан| Ко | Пр| Те | Оц
I
Пл| Ан| Ко | Пр| Те | Оц
Время
Оценка Планирование
Реализация Анализ
(программирование и
тестирование)
Конструирование
требованиями.
8. Требования изменяются
Фиксируемые в заказе на разработку требования к системе, претенду-
ющей на широкую сферу применения и долгую жизнь, не являются за-
стывшими и незыблемыми. Они изменяются как из-за учета новых фак-
торов и пожеланий, так и в связи с выявлением особенностей проекта в
ходе его разработки. Следовательно, необходимо так строить аналити-
ческую работу, чтобы иметь возможность оперативно изменять полу-
чаемые результаты, учитывать в них изменения и дополнения исходной
информации;
2. Унифицированные представления
требований
Табстр
Глоссарий проекта
4. Модельные представления
уровня анализа
5. Модельные представления
уровня конструирования
6. Программные представления
7. Документные представления
Требование реализуется на
более поздней итерации
Пополнение базового
окружения проекта
Окончание работ
Контрольные ← Использование
точки ↔
(события): 0 1 2 3 4 5 6 7 8 9 10 11 12
Анализ нового требования Требование реализуется на текущей итерации
Требование
отклоняется ← (a) Требование поступило, начало мини-цикла
Начальная Продолжение
фаза проекта
проекта ← Мини-циклы разработки сценариев →
← Автономная← Интеграция →
работа →
Пополнение базового
окружения проекта
ние →
← Программирование →
Оценка
Контрольные
точки
(события): 0 1 2 3 4 5 6 7
Выбор сценариев для Начата ←5’’ Сценарии ←8 Требования к следующей
реализации сделан 2’→ интеграция реализованы, итерации приняты
сценариев 5’→ базовый набор
Совместная работа
требований
с выбранными
определен
сценариями
Модели сценариев построены 3’→ Демонстрационные испытания
завершены 9 →
Немедленная
Контрольные реализация
точки
(события): 0 1 2 3 4 5 6 7 8 9 10 Требование 11 12
Накопление, ←Реализация требований ←—— →
отклоняется
систематизация и →
Проверка реализации ← (b) Решение о требовании
группировка требований
Распростанение изменения начато→ принято
Решение о реализации
Извлечение требования
требований принято (c) → Сообщение поступило (a) →
Рис. 4.17.
руты, возникающие в связи с обработкой одного требования в ходе эксплуа-
тации программной системы. Для упрощения предполагается, что операци-
онные маршруты не зависят от накапливаемой информации. Это заведомо
огрубляющее предположение в том смысле, что принятие решений о требо-
вании фактически делается не только на основании первичных установок,
но и с использованием знаний о системе, пользователях, о текущем предста-
влении о приоритетах и предпочтениях. Можно считать, что подобные све-
дения используются в точках разветвления операционных маршрутов, но то,
как осуществляется такое использование, моделью не описывается.
Аналогично организации мини-цикла для трассировки требований, в мо-
дели периода эксплуатации началом обработки является поступление сооб-
щения о ходе эксплуатации системы, которое можно трактовать как содер-
жащее требования (контрольная точка (a)). Это событие может возникать в
любой момент периода сопровождения, т. е. обсуждаемая модель является
естественным продолжением модели с обработкой требования в мини-цикле
(см. § 4.4.3). Так как отобразить весь поток сообщений невозможно, рассма-
тривается операционный маршрут только одного сообщения, при этом по-
стулируется, что все сообщения обрабатываются подобно:
• поступление сообщения (контрольная точка (a));
234 ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
ентировано прежде всего на задачи, где заказчику важнее форма, чем содер-
жание, и где успех проекта зависит в первую очередь от упаковки, и лишь во
вторую — от качества начинки.
Вопросы, которые затрагивались в настоящей главе, освещены в многочи-
сленных публикациях, посвященных технологии программирования. Боль-
шинство из них соответствуют скорее текущей конъюнктуре, чем сути про-
блемы. В качестве приятного исключения, как классическую работу, выдер-
жавшую испытание времением, можно указать на книгу Ф. Брукса “The Mythical
Man-Month. Essay on Software Engineering” (русский перевод первого изда-
ния, вышедшего в 1975г., см. в [14], юбилейного издания 1995г. — в [15]). Эта
монография по праву считается одной из лучших книг не только по данной
тематике, но и по программированию вообще. Сопоставление двух ее изда-
ний явно показывает, что проблемы, которые приходится решать при упра-
влении программными проектами, почти не изменились со времени перфо-
карт. Меняется только техническая поддержка.
Из ранних работ, не потерявших своей актуальности, прежде всего сле-
дует обратить внимание на монографию Гантера [23], содержащую, кроме
представленной выше модели, много полезной информации для организа-
ции работ над программными проектами. Систематизированные сведения о
понятии жизненного цикла и его применении в промышленном программи-
ровании можно найти в книге [52], которая к тому же дает представление о
состоянии дел в этой области в СССР к началу восьмидесятых годов. Весь-
ма обстоятельное исследование задач и методов проектирования и разработ-
ки программного обеспечения выполнено Боэмом. Его книга [11] постоянно
цитируется и в наши дни.
Современное представление о технологии проектирования программных
систем прочно связано с методологией объектно-ориентированного програм-
мирования. Всестороннее изложение данного подхода, его концепций, а так-
же общих методов разработки проектов в объектно-ориентированном стиле
можно найти в книге Буча [16]. UML и методы его использования в практи-
ческой разработке программных проектов хорошо изложены авторами это-
го языка в монографии [17]. Понятия, связанные с CASE-технологиями, до-
статочно четко излагаются в работах [64, 18]. В частности, в последней из
упомянутых публикаций достаточно подробно освещаются вопросы CASE-
технологий, связанных проектированием информационных систем.
Следующие ссылки помогут получить сведения об упомянутых выше кон-
кретных разработках. Книга [70] дает наиболее полное представление о СУБД
Oracle, в частности, об Oracle Designer 2000 и его месте в системе. IDEF-тех-
242 ГЛАВА 4. ЖИЗНЕННЫЙ ЦИКЛ
Структуры программирования
244
245
Выражения
247
248 ГЛАВА 5. ВЫРАЖЕНИЯ
§ 5.1. ОПЕРАЦИИ
формы.
АЛМО — русская попытка (1967 г.) создать то, чем затем стал язык C: язык высокого
уровня, максимально приближенный к архитектуре машины. АЛМО до середины 80-х гг.
4
x := a/b;
(5.3)
x := a div b;
где x, a, b — целые переменные, могут быть различны.
Аналогичный эффект можно получить и в языке С, но здесь всплывает в
явном виде еще одно важное понятие современных языков.
x=float(a)/float(b);
(5.4)
x=a/b;
Программа 5.2.1
Y
Y=X
X2 + Y2 = 4
Y=1
Y=0
X
if ( x <= 1 )
if ( x <= y )
if ( x * x + y * y <= 4 )
printf( "Да\n");
else printf( "Нет\n");
else printf( "Нет\n");
else printf( "Нет\n");
else printf( "Нет\n");
return 0;
}
Программа 5.2.2
if ( 0 <= x )
if ( x <= 1 )
if ( x <= y )
if ( x * x + y * y <= 4 ) printf( "Да\n");
else goto lbl;
else goto lbl;
else lbl: printf( "Нет\n");
else goto lbl;
Программа 5.2.3
256 ГЛАВА 5. ВЫРАЖЕНИЯ
if ( 0 <= x )
if ( x <= 1 )
if ( x <= y )
if ( x * x + y * y <= 4 )
{
printf( "Да\n");
goto lb;
}
printf( "Нет\n");
lb:. . .
{ Действие_1_2_3} { Действие_1_2_3}
if (Условие3) && (Условие3))
Программа 5.2.5
которая напечатает
200 - это True.
-33 - это True.
Это еще раз подчеркивает, что С создавался для конкретной архитектуры ма-
шины с командами перехода по нулевому или ненулевому значению.
Так что вместо чего-либо подобного “! (a>d)” (как, к примеру, в С++, где
“!” — операция отрицания) приходится писать “(a>d)== 0”. Все булевы опе-
рации согласованы с таким представлением.10
Стоит подчеркнуть явно, что тип bool в C++ совсем не то же самое, что, к примеру, в
языке Pascal. Множество значений этого типа состоит из двух классов эквивалентности с
10
точки зрения логических операций: всех ненулевых значений и нулевого значения. Но экви-
258 ГЛАВА 5. ВЫРАЖЕНИЯ
Программа 5.2.6
if (init ( )
&& activate ( )
&& run ( )
&& terminate ( ))
{ ... }
x ∗ x + y ∗ y <= 4 (5.5)
x ∗ (x + y) ∗ y <= 4
текстуально очень похоже на исходное, хотя оно имеет совсем иную абстракт-
но-синтаксическую структуру. В то же время, структура выражения
совпадает с исходной.
Все дело в том, как определять текстовое (конкретно-синтаксическое) пред-
ставление выражений и его связь с абстрактно-синтаксическим представле-
нием. Можно следовать, например, такому определению:
Не путайте приоритеты операций с приоритетами процессов! Приоритеты операций —
понятие синтаксическое, необходимое для анализа текста программы, а приоритеты процес-
12
Условие
<=
Операнд Операнд
Константа
+
Значение = 4
Операнд Операнд
* *
<знак операции(n)><операнд>
...
<вторичное выражение> →
<выражение> →
property a
<условное выражение> →
Разветвление вычислений
269
270 ГЛАВА 6. РАЗВЕТВЛЕНИЕ ВЫЧИСЛЕНИЙ
симые подзадачи:
1. ввод данных в заданном формате;
/* Приглашение к вводу */
printf( <число><операция><число>:\n“);
return SYNTAX_ERROR;
ную информацию.
Подзадачу подсчета и вывода результата можно запрограммировать с ис-
пользованием набора вложенных условных операторов:
return ZERO_DIVISION;
};
else result = x / y;
else
return BAD_OPERATION;
};
6.1.2. Переключение
Программа 6.1.1
4,5=> стипендия;
3=>
аспирантура
конец
5=>
go to Метка_завершения;
Метка_завершения: . . .
...
Программа 6.1.2
Оператор break; является первым из рассматриваемых нами операторов структурного пе-
рехода, о которых упоминалось в главе о стилях программирования (см., напр., программу
3
3.3.1). Здесь его использование просто навязывается недоработками языка C/C++, а в дру-
гих случаях структурные переходы будут возникать как удобное средство прояснить логику
программы.
276 ГЛАВА 6. РАЗВЕТВЛЕНИЕ ВЫЧИСЛЕНИЙ
#include <stdio.h>
#include <math.h>
int main()
{
float x, y, result;
char operation;
switch( operation )
{
case ’+’:
result = x + y;
break;
case ’-’:
result = x - y;
break;
case ’*’:
result = x * y;
break;
case ’/’:
if ( y = = 0.0 )
6.1. РАЗБОР СЛУЧАЕВ 277
}
result = x / y;
break;
/* нормальное завершение */
printf( ”Результат = %f\n”, result );
return 0;
}
В других языках конструкция выбора представляется более логично. На-
пример, в языке Pascal выбор реализуется синтаксически единым операто-
ром вида
case <параметр выбора> of
<Список значений_1>: <Действие_1>;
<Список значений_2>: <Действие_2>;
typedef struct
{
int type_tag;
union
{
int x;
float y;
char a;
}
} tagged;
void smart_read(tagged *element);
{
...
}
int main();
tagged aa;
{
smart_read(&aa);
switch (aa->type_tag)
{
case 1: {process_integer(aa->x);break;};
case 2: {process_real(aa->y);break;};
case 3: {process_char(aa->a);break;};
}
...
}
Здесь функция умного чтения распознает, какого типа значение было введе-
но, и в соответствии с этим устанавливает тег в своем результате. По значе-
нию тега выбирается представление данных, соответствующее содержимому
переменной aa.
Именно подобное представление естественно и для результата, и для ар-
гумента оператора разбора случаев, если разобраться в логическом смысле
дизъюнкции. В самом деле, когда мы разбираем случаи A1 ∨ · · · ∨ An , мы
280 ГЛАВА 6. РАЗВЕТВЛЕНИЕ ВЫЧИСЛЕНИЙ
Сегодня этот метод анализа условий, кажется, уже прочно забыт. Тем не
менее, он весьма поучителен как с точки зрения обсуждения понятий языков
программирования, так и в практическом плане. Таблицы решений — это
способ раздельного задания (обдумывания, анализа и т. п.) составных усло-
вий и действий, связанных с вариантами. Достоинством их является то, что
значительно облегчается учет всех возможных вариантов разветвлений. При
этом забота о последовательности проверок и об алгоритмизации вычисли-
тельного процесса относится к реализации данного метода, и программист
не обязан об этом думать.
В общем случае таблица решений (см. табл. 6.1) состоит из двух частей:
условий и действий. Каждое из условий представлено перечнем значений
так, чтобы в колонках таблицы оказались представлены все сочетания зна-
чений всех условий. В перечень значений чаще всего входят T — истина
и F — ложь; допускается также указание, что значение некоторого условия
для выбора действия безразлично, это отмечается знаком −. Часть действия
представлена перечнем наименований действий, а в колонках под значения-
ми условий указывается метка (Х), если действие при данном наборе значе-
ний должно быть выполнено.
Что дает программисту использование таблиц решений? Во-первых, ра-
ботая с данными и действиями, организованными таким способом, можно
гарантировать, что будут рассмотрены все возможные сочетания условий.
При желании можно написать программу, которая проверяет это свойство
автоматически. Во-вторых, таблицы решений дают метод декомпозиции за-
282 ГЛАВА 6. РАЗВЕТВЛЕНИЕ ВЫЧИСЛЕНИЙ
Условие 1
Условие 2
T T T T F
T T F F
Условие N
...
T F T F F
Действие 1
Действие 2
X X X
X X X
Действие M
...
X X
Пол = муж
Нравится себе
T T T T T T
ся другим
T T T T
Не знает T T T T
Добавить к ГM
Добавить к ГP
X X
Добавить к ГT
X X
Добавить к ГW
X
X X X X X X
Пусть доказано A1 ∨ · · · ∨ An .
Если выполнено A1 , то получается B1 .
Значит, B1 ∨ · · · ∨ Bn .
на то, что случаи должны быть взаимоисключающими. Это требование избыточно в логике,
так же как и в программировании. А вот тем, что набор рассмотренных случаев должен быть
полон, пренебречь нельзя.
Наша формулировка формально эквивалентна тем, какие обычно приводятся в учебниках
логики, например, в [34].
5
6.2. ТАБЛИЧНОЕ ЗАДАНИЕ 285
(P?A:B)
В выражениях этого языка могут стоять даже разборы случаев типа case.
Дополнительной семантической функцией разделителя последовательно вы-
полняемых операторов “;” является забывание значения предшествующего
оператора.
Языки, в которых все операторы (а иногда и описания) вырабатывают зна-
чение, называются языками с концепцией значения. Такие языки хорошо со-
гласуются с некоторыми архитектурами компьютеров, в частности, со сте-
ковой архитектурой систем Barroughs и Эльбрус, где вырабатываемые зна-
чения помещаются в стек, вершиной которого служит последнее из получен-
ных значений, а также со старыми архитектурами, где был регистр (сумматор),
в котором всегда сохранялся результат предыдущей команды. Логически кон-
цепция значения соответствует взгляду на программу как на функцию, вычи-
сляющую результат по исходным данным. Концепция значения великолепно
сочетается со структурным программированием. Но ее отсутствие в языках
Pascal и C/C++ вполне естественно. Причина этого — в типах данных.
Современное программирование немыслимо без определения новых ти-
пов и сложных структур данных. Соответственно, если рассматривать все
выражения как вырабатывающие значения, нужно было бы строго опреде-
лить приведение данных: систему неявных преобразований данных предо-
ставленного типа в данные типа, требуемого по контексту.6 А соединение
В принципе это означает, что мы должны были бы определить для каждого вида контекста,
возникающего в программе, категорию (в смысле алгебры) всех имеющихся типов данных.
6
286 ГЛАВА 6. РАЗВЕТВЛЕНИЕ ВЫЧИСЛЕНИЙ
Циклические вычисления
#include <stdio.h>
#include <math.h>
int main()
287
288 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
{
float x, y, result;
switch( operation )
{
case ’+’:
result = x + y;
break;
case ’-’:
result = x - y;
break;
case ’*’: result = x * y;
break;
case ’/’:
if( y = = 0.0 )
return ZERO_DIVISION;
}
result = x / y;
break;
default:
printf( ”BAD_OPERATION: %c\n”, operation );
return BAD_OPERATION;
}
7.1. МОТИВАЦИЯ ЦИКЛИЧЕСКИХ ВЫЧИСЛЕНИЙ 289
}
while ( true );
}
чение.
Эти два вида цикла взаимозаменяемы. Цикл с предусловием эквивален-
тен условному оператору, у которого в качестве <Действия T> стоит цикл с
постусловием :
while <выражение-условие> do if (<выражение-условие>) do
условие>
<оператор> <оператор> while <выражение-
{ <инициализирующий оператор>
<выражение пересчета>;
while ( <выражение-условие> ) { <оператор>
}}
Этот заголовок указывает, что переменная i вне цикла не может быть исполь-
for ( int i = 0; i <= 10; i ++ ) <оператор>
Схема Пример
ИНИЦИАЛИЗАЦИЯ printf(”Введите N:”);
цикл
while (i < N) {
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
ЭЛЕМЕНТА
I++;
ОБРАБОТКА ЭЛЕМЕНТА
конец цикла
S = S + I;
);
Схема Пример
ИНИЦИАЛИЗАЦИЯ,
ВКЛЮЧАЮЩАЯ ГЕНЕРАЦИЮ
printf(”Введите N:”);
цикл
while (i <= N) {
ОБРАБОТКА ЭЛЕМЕНТА
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
S = S + A;
ЭЛЕМЕНТА
I++; A = I*I;
конец цикла
ЗАВЕРШЕНИЕ printf(“Сумма квадратов %i
}
чисел = %i \n“, i, S );
Разграничение порождения и об-
работки довольно условно. Напри-
S = 0; I = 1; A =0;
Схема Пример
ИНИЦИАЛИЗАЦИЯ, ВКЛЮЧА- printf("Input X:"); scanf("%i",&X);
ЮЩАЯ ПОРОЖДЕНИЕ
цикл ОБРАБОТКА ЭЛЕМЕНТА
ПОРОЖДЕНИЕ
do printf ("%c", ’0’ + X % 10);
ЗАВЕРШЕНИЕ
while ( X != 0 )
Схема Пример
ИНИЦИАЛИЗАЦИЯ
цикл ПОРОЖДЕНИЕ ЭЛЕМЕН-
printf(”Продолжить? (y|n)\n”);
ТА
do scanf ( "%c", c );
ai = exp(i ∗ ln(a))
7.2. ПОТОКОВАЯ ОБРАБОТКА 295
a2 a4 a6 a2i
S =1− + − + · · · + (−1)i + ··· .
1 1∗2 1∗2∗3 1 ∗ 2 ∗ ··· ∗ i
Два варианта программы приведены в таблице 7.5. Главный недостаток пер-
вого алгоритма: функциональная генерация элементов суммируемого потока
приводит к повторному счету. На каждой итерации внешнего цикла во вну-
треннем цикле заново считаются составляющие членов ряда, предшествую-
щих искомому (генерация локального по отношению к задаче суммирования
потока троек чисел sn , u и f ). Вместе с тем, рекуррентное соотношение:
a2 −a2
ti = ti−1 ∗ (−1) = ti−1 ∗
i i
296 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
float a; // параметр
I = 0; t = 1; {
него цикла
return (0);
... }
Программа 7.2.1
/* Prime_Numbers_1. Распечатка первых N простых чисел */
#include <stdio.h>
#include <math.h>
int main(void)
else
while (k<N)
// ФИЛЬТРУЮЩЕЕ УСЛОВИЕ
}
//(отфильтрованные нечетные)
k++;
printf(",%i",i);
}
printf(".\n");
return 0;
}
Схема Пример
ИНИЦИАЛИЗАЦИЯ printf("Введите
цикл
while (I < N) {
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
ЭЛЕМЕНТА
I++;
Схема Пример
ИНИЦИАЛИЗАЦИЯ, ВКЛЮЧА-
ЮЩАЯ ГЕНЕРАЦИЮ
printf("Введите N:");
scanf("%i",&N);S = 0; I = A =
цикл
while (I <= N) {
то ОБРАБОТКА ЭЛЕМЕНТА
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
S = S + A;
ЭЛЕМЕНТА
I++; A = I*I;
конец цикла
ЗАВЕРШЕНИЕ
}
printf("Сумма = %i \n", S );
Схема Пример
ИНИЦИАЛИЗАЦИЯ, ВКЛЮЧА-
ЮЩАЯ ГЕНЕРАЦИЮ
printf("Введите N:");
scanf("%i",&N);S = 0; I = A =
УСЛОВИЕ
do if ( I/10 % 2 )
то ОБРАБОТКА ЭЛЕМЕНТА
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
S = S + A;
ЭЛЕМЕНТА
I++; A = I*I;
Схема Пример
ИНИЦИАЛИЗАЦИЯ printf("Введите N:");
scanf("%i",&N);S1 = 0; S2 = 0;
цикл
while (I < N) {
ПОРОЖДЕНИЕ ОЧЕРЕДНОГО
ЭЛЕМЕНТА
I++;
ТА
else S2 += I;
конец цикла
ЗАВЕРШЕНИЕ printf("Сумма 1 = %I, Сумма 2 = %I
}
\n", S1, S2 );
Схема Пример
ИНИЦИАЛИЗАЦИЯ
пока ПОТОК НЕ ИСЧЕРПАН
int ch; int Sd=0, Sl=0, S=0;
цикл
while ((ch=getchar())!=’\n’) {
то ОБРАБОТКА 1 ЭЛЕМЕНТА
1
УСЛОВИЕ 2
else if (’a’<=ch && ch<=’z’)
то ОБРАБОТКА 2 ЭЛЕМЕНТА
иначе если РАЗДЕЛЯЮЩЕЕ
Sl ++;
УСЛОВИЕ 3
РАБОТКА
else S ++;
конец цикла
ЗАВЕРШЕНИЕ
}
printf ("Sd=%i, Sl=%i, S=%i,\n
Total=%i \n",Sd,Sl,S,Sd+Sl+S);
Схема Пример
ИНИЦИАЛИЗАЦИЯ
пока ПОТОК НЕ ИСЧЕРПАН
int ch; int Sa=0, Sb=0, S=0;
цикл
while ((ch=getchar())!=’\n’)
СПИСОК ЗНАЧЕНИЙ 1 :
ОБРАБОТКА 1 ЭЛЕМЕНТА
case ’a’:
СПИСОК ЗНАЧЕНИЙ 2 :
Sa ++; break;
ОБРАБОТКА 2 ЭЛЕМЕНТА
case ’b’:
СПИСОК ЗНАЧЕНИЙ 3 :
Sb ++; break;
РАБОТКА
default: S ++; }
конец цикла
ЗАВЕРШЕНИЕ
}
printf ("Sa=%i, Sb=%i, S=%i,\n
Total=%i \n",Sa,Sb,S,Sa+Sb+S);
Схема Пример
ИНИЦИАЛИЗАЦИЯ
пока ПОТОК НЕ ИСЧЕРПАН
char ch; int I=0;
цикл
while ((ch=getchar())!=’\n’)
То ОБРАБОТКА 1 ЭЛЕМЕНТА
if ( ch = = ’a’ ) {
ВЫХОД ИЗ ЦИКЛА
printf ("№первого a=%i",I);
конец цикла
ЗАВЕРШЕНИЕ
}
Программа 7.2.2
else {
i=1;
while (k<N) {
i+=2;
7.2. ПОТОКОВАЯ ОБРАБОТКА 307
Схема Пример
Если в языке можно использовать :МеткаУровня: пока
операторы завершения, то про-
грамма улучшается: цикл если ( ch = = ’a’ )
((ch=getchar())!=’\n’)
}
return 0;
}
Схема Пример
ИНИЦИАЛИЗАЦИЯ
пока ПОТОК НЕ ИСЧЕРПАН
char ch;
цикл
while ((ch=getchar())!=’\n’)
ВЫХОД ИЗ ИТЕРАЦИИ
ПОСТОБРАБОТКА ЭЛЕМЕНТА
continue;
конец цикла
printf ("%c", ch);
ЗАВЕРШЕНИЕ
}
printf (’\n’);
int n;
Read(n);
int[1:n] a;
между удаленными программами, она была введена через “черный ход”: че-
рез тип данных Variant, который является потенциально бесконечным объ-
единением типов. В частности, значениями типа Variant могут быть гибкие
массивы, имеющие тип SafeArray. Для гибких массивов имеется специальная
операция создания такого массива. Работу с гибкими массивами иллюстри-
рует следующий фрагмент програмы.
Программа 7.2.3
unsigned int n;
float x,y;
long i;
scanf(&n,&x);
SAFEARRAY *pSa = NULL;
SAFEARRAYBOUND sabound[1];
sabound[0].lLbound = 1;
sabound[0].cElements = n;
pSa = SafeArrayCreate(VT_R4, 1, sabound);
i=5;
SafeArrayPutElement(psa, &i,&x);
SafeArrayGetElement(psa, &i, &y);
if(x!=y){return -1;}
scanf(&sabound[0].cElements);
SafeArrayRedim(psa,sabound);
Prime = i%a[j];
Программа 7.2.4
else {
if (k>=N) i = a[N]; //!
else { //!
i=a[k]; //!
314 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
while (k<N) {
i+=2;
for( j=2, Prime = 1; Prime && a[j]<=sqrt(i); j++)
Prime=i%a[j];
if (Prime) a[++k]=i;
}
}
scanf("%c",&N);
return 0;
}
#include <math.h>
int N,i,j,k;
int Prime;
int a[1000];
FILE *f;
int main(void) {
// Фрагмент, ответственный за
f = fopen("prime.txt","r"); //
// инициализацию массива a
fscanf(f,"%i:",&k);
for (i=1;i<=k;i++)
fscanf(f,"%i",&a[i]); //
fclose(f); //
do {
printf("Write N:");
scanf ("%i",&N);
if (N<=0)
printf("If N <= 0 then there is no prime numbers\n");
else {
if (k>=N) i = a[N];
else {
i=a[k];
while (k<N) {
i+=2;
for (j=2,Prime=true; Prime && a[j]<=sqrt(i); j++)
Prime = i%a[j];
if (Prime) {
a[++k]=i;
printf("%i ", i);
}
}
}
printf("\nIf N = %i - %i\n", N, i);
}
scanf("%c",&N);
}
while (N != ’n’);
// Фрагмент, ответственный за
{ f = fopen("prime.txt","w"); //
// долговременное хранение
fprintf(f,"%i:",k);
for (i=1;i<=k;i++)
316 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
begin
if not eof
then begin
{1.1} read ( A ); X := A;
while (X > 0) and (X < 1000) and not eof do
begin
read ( X );
М11) можно описать как перепись (разумеется, без повторений) начала вход-
ного потока, а поиск убираемого элемента (см. строку 1.2) — с помощью
просмотра A и поиска в нем минимума. При переписи приходится ввести
индекс заполненности A (в дальнейшем он обозначается L), который растет,
когда место в A еще не исчерпано (L < N ). Если N невелико, вопрос о це-
не разделения переписи без повторений и просмотра не возникает, и вполне
приемлемо решение, когда работа с A организуется прямо, циклом с провер-
ками каждого элемента массива:
Программа 7.2.7
begin
if not eof
then
begin
L := 0;
while (L < N) and (X > 0) and (X < 1000) and not eof do
begin
read ( X ); i := 1;
while (i <= L) and (X <> A[i]) do i := i + 1;
then { Х в A не найден }
if i > L
begin L := L + 1; A[L] := X
{ массив A заполнен }
end;
end;
while (X > 0) and (X < 1000) and not eof do
begin
read ( X ); i := 1;
end;
end.
begin
if not eof
then
begin
L := 0;
while (X > 0) and (X < 1000) and not eof do
begin
read ( X ); i := 1;
while (i <= L) and (X <= A[i]) do i := i + 1;
if i <= L
{ исключение повторений }
then
j := i + 1; { кандидат на удаление }
then begin
A[j] := X; { модификация }
{ нужно ли пополнение A? }
else
if L < N
then begin L := i; A[i] := X end;
end;
while (X > 0) and (X < 1000) do
begin
read ( X ); i := 1;
При росте N программа MN2 все еще будет неэффективной из-за увеличения
накладных расходов на полный просмотр массива A. Как уже отмечалось,
это можно поправить за счет упорядочивания массива. Предварительно от-
метим, что формулировка решаемой задачи не совсем точна: она оставляет
свободу разработчику в выборе того, в какой последовательности надо вы-
водить полученные результаты. Если это безразлично, то решение вопроса,
использовать упорядочивание или нет, разработчик программы вправе моти-
вировать только логикой построения алгоритма и оценкой эффективности. В
противном случае, когда требуется вывести результат в том или ином поряд-
ке (для определенности — по возрастанию), необходимость упорядочивания
диктуется условием задачи. И тогда возможное решение, связанное с допол-
нительным упорядочиванием массива A после завершения работы с потоком,
становится совершенно неприемлемым: оно приведет к заведомой неэффек-
тивности программы.
Упорядочивание массива A при заполнении, кроме уже рассмотренного
пополнения числа элементов A, требует реализовать вставку X внутрь мас-
сива A в двух вариантах:
324 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
a) когда A не заполнен, и
b) когда A заполнен.
Программа 7.2.9
program MN3(Input, Output);
const N = 15;
var A : array [1..N] of Integer;
X, L, i, j : Integer;
incl : Boolean;
begin
if not eof
then
begin
read (A[1]); L := 1;
while not eof do
begin
read ( X ); i := L; incl := False;
Например, во фрагменте
другом порядке. Тем более бывает шокирован программист, когда после вставки в цикл вро-
де бы ничего не меняющего оператора программа перестает работать в том месте, где она
уже была полностью отлажена, а затем, после череды панических хаотических изменений,
внезапно вновь начинает работать как надо. А последствия паники и хаоса будут расхлебы-
ваться еще долго. . .
328 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
Путем анализа текста можно узнать, что этот цикл можно реализовать в ко-
дах процессора Intel-80?86 следующим образом (в частности, организовав
обратный порядок итераций):
// инициализация i(register ecx) i = 99
// запись i в b[i]
mov ecx,64h
// запись i в a[i]
c1: mov [b - ecx*4],ecx
Схема Пример
ИНИЦИАЛИЗАЦИЯ, ВКЛЮЧА- for (<инициализирующий опера-
ЮЩАЯ ГЕНЕРАЦИЮ тор>;
пока ПОТОК НЕ ИСЧЕРПАН
цикл
<выражение-условие>;
11
Здесь мы столкнулись с необходимостью введения сложных структур данных!
330 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
do
P1 → S1 ,
...
Pn → Sn ,
od
Таким образом, условие выхода из цикла неявно: это ложность всех охран.
Заметим, что Дейкстра противоречит сам себе в определении цикла и услов-
ного оператора. В условном операторе он резко выступал против умолчаний
и на этом основании отвергал часть else. В операторе цикла он же суще-
ственнейшим образом использовал умолчание как критерий завершения ци-
кла. Если уж явно выписывать условия, то все существенные. Альтернатива
возможна лишь для вылавливания непредусмотренных случаев, возможно,
вызванных неправильным использованием программы. Поэтому предлага-
ется следующий вариант цикла (цикл-‘паук’), в котором условия завершения
также перечислены явно и есть альтернативное завершение else, например,
для сообщения об ошибке.
do
P1 → S1 ,
...
Pn → Sn ,
out
Q1 → T1 ,
...
Qm → Tm ,
else
Error_Analysis
od
// Псевдослучайные числа
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
void main()
{
int i;
// Напечатать 10 чисел
srand( (unsigned)time( NULL ) );
for( i = 0; i < 10; i++ )
336 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
Программа 1 Программа 1
Приостановленны
Активный Потенциально
й процесс
процесс активный
П11 П12 П11 П12 процесс
Приостановленные
Порождение Порождение
(генерация) Обработанные элементы (генерация)
процессы
Общий ресурс
трех процессов Поток
Необработанные элементы
П21 П21
Потребление Потенциально Потребление
(обработка) активный процесс (обработка)
Программа 2 Программа 2
/* Binary Search*/
#include <stdio.h>
// Массив поиска
#define SZA 10
int a[SZA];
int main()
// Служебная переменная:
int ima = SZA;
Оператор Оператор
цикла с pred- цикла с post-
условием условием
Оператор Оператор
break continue
грамм.
Именно такое решение предлагается и при формальном устранении переходов в доказа-
тельстве теоремы Бема-Джакопини.
19
350 ГЛАВА 7. ЦИКЛИЧЕСКИЕ ВЫЧИСЛЕНИЯ
7. Перейти к 3.
Как легко видеть, здесь более подробно раскрыто, что означает приведенное
ранее «Вычислить последовательно все ветви вершины». Сброс флагов —
задача вычисления не <Последовательно выполняемых операторов>, а то-
го цикла или переключения, которое должно завершится. Значит, алгоритмы
работы абстрактного вычислителя для циклов с пред- и постусловием тоже
корректируются. Ниже приводится модифицированный алгоритм для <Ци-
кла с предусловием>:
6. Перейти к 1.
:Метка уровня:
начало // конструкции 1;
операторы
начало // конструкции 2;
операторы
начало // конструкции 3;
операторы
завершить Метка уровня; // т. е. прекратить
// выполнение конструкции 1 и
// всех вложенных конструкций,
// с передачей управления
// к строке !!!
операторы
конец // конструкции 3;
операторы
конец // конструкции 2;
операторы
конец // конструкции 1;
/*!!!*/
...
:Метка уровня:
начало // конструкции 1;
операторы
начало // конструкции 2;
операторы
начало // конструкции 3;
операторы
go to РМ // метка, добавляемая транслятором
операторы
конец // конструкции 3;
операторы
конец // конструкции 2;
операторы
конец // конструкции 1;
РМ: /*!!!*/
...
Любой конкретный вычислитель имеет среди своих команд оператор без-
условного перехода по адресу, а потому для реализации завершения доста-
точно следующего. Просматривая транслируемый текст, надо:
1. расставить метки, куда надо переходить при выполнении завершения
(а также в иных случаях, например, при организации циклов),
2. составить таблицу таких меток с полями: имя метки и адрес в про-
грамме,
3. вставить вместо завершителей команды безусловных переходов.
(несколько усложняют эту схему возможные описания, вложенные в кон-
струкции 1–3).
Эту идею не так просто применить к абстрактному вычислителю. Дело
в том, что здесь существенно используется предварительное преобразова-
ние текста, осуществляемое транслятором, которое никакого отношения к
выполнению программы не имеет. Определяя абстрактный вычислитель, мы
задавали его как интерпретатор, который не имеет предварительной глобаль-
ной информации о синтаксическом дереве исполняемой программы.
Тем не менее, имеется более эффективная, чем преобразование Бема-Джа-
копини, и, главное, более надежная и гибкая схема вычисления завершите-
лей, реализуемая интерпретатором. Но для этого необходима память вычи-
слителя, включающая структурированый стек контекстов (см. рис. 7.4).
7.4. АС-ПРЕДСТАВЛЕНИЕ ЦИКЛОВ 353
…
Стек растет вниз. Нынешний кон-
C текст назван Контекст 0. Самая
нижняя единица в стеке — текущий
результат. Стек структурирован
Контекст 2 как стек стеков. Каждый подстек
включает данные для блока про-
граммы. Таким образом, каждый
B
из подстеков задает контекст для
вычислений в соответствующем
Контекст 1 блоке. В начале каждого подстека
находится его маркер, который
A содержит всю необходимую слу-
жебную информацию о подстеке, в
Контекст 0 частности, ссылку на предыдущий
маркер.
Рис. 7.4. Стек и контексты
В результате активным окажется как раз тот контекст, который должен про-
должать свои действия после выполнения завершения, причем именно с те-
ми ветвями дерева абстрактного синтаксиса, которые должны обрабатывать-
ся после только что выполненной ветви. Вычислитель даже может не знать,
каким образом окончилось вычисление этой ветви: принудительно или есте-
ственно. Таким образом, за счет вынесения на стек информации о состоя-
нии контекстов вычислителя, которые были приостановлены до тех пор, пока
не будут обработаны вложенные в них конструкции, появилась возможность
очистить вычисления от инородных проверок.
При реализации языков высокого уровня на современных системах про-
граммирования практически всегда создается структура контекстов, и чаще
всего именно она используется для реализации структурных переходов. Струк-
тура контекстов воплощена даже на аппаратном уровне, в частности, в систе-
мах команд машин серий Barroughs и Эльбрус. Такие машины называются
машинами со стековой архитектурой. Часто стековая архитектура и тегиро-
вание (см. рис. 1.7) совмещаются.
В языках, разрабатывавшихся для машин со стековой архитектурой, прак-
тически всегда имеется самая общая конструкция завершения: завершитель
с меткой уровня. В частности, в языке Эль-76 (см. [25]: язык высокого уров-
ня, который одновременно мог рассматриваться как машинно-ориентирован-
ный язык для машин серии Эльбрус) метки могут ставиться только к завер-
шенным структурным конструкциям (блокам) программы и переход на метку
означает завершение соответствующего блока.
Пожалуй, только в языках ЯРМО и LISP концепция структурных перехо-
дов корректно соединена с концепцией вырабатываемых значений. Практи-
чески каждый оператор в ЯРМО может иметь часть иначе, в которой про-
изводится вычисление значения выражения после его неявного завершения.
При явном завершении (например, при применении структурного перехо-
да), программист должен позаботиться о том, чтобы последнее исполняемое
предложение давало значение нужного типа, семантически подходящее для
того, чтобы стать значением завершаемого блока. Насчет языка LISP смотри-
те главу о функциональном программировании.
При структурных переходах возникает еще одна трудная проблема. В дан-
ном контексте могут быть созданы динамические значения, которые физиче-
ски располагаются в другой части памяти, а контекст содержит лишь ссылку
на них. В частности, именно так работает выражение new в C/C++, см., на-
7.4. АС-ПРЕДСТАВЛЕНИЕ ЦИКЛОВ 355
float (*cp)[25][10];
(7.7)
cp = new float[10][25][10];
…
C
более унылый вид (рис. 7.6) и задача освобождения памяти становится тяже-
7.4.3. Исключения
try
Подпрограммы
362
363
пировать в одном модуле все средства работы с какой-либо структурой данных, включая
инициализацию, обработку и уничтожение. Более того, именно такая организация модулей
предписывается современными технологиями ООП как практически безальтернативная. Но
на уровне сегментной декомпозиции ситуация меняется: инициализация и уничтожение —
разовые действия, отнесение которых к одному сегменту с обработкой приводит к ничем
не оправданному разбуханию загрузочного модуля. Это давно знали программисты-прак-
тики и предлагали разделять виды модуляризации. В этом плане показательным является
язык ЯРМО, где возможности приписывания фрагментов кода к разным загрузочным сег-
ментам независимы от логической декомпозиции. Несмотря на очевидные преимущества
подобного разделения, еще и сегодня общераспространенные системы программирования
рассматривают сегментную декомпозицию в подчинении технологической, объявляя, что
программные единицы, которые можно подключать к сегментам, есть логические модули.
364 ГЛАВА 8. ПОДПРОГРАММЫ
Более того, даже для такого строгого языка как Pascal, стандарт кото-
рого с целью защиты от возможных ошибок запрещает использовать вызов
функции как процедуры (если Average — функция, оператор Average; был
бы некорректен), конкретные системы программирования (например, Turbo
Pascal) допускают такую “вольность”.
В настоящем изложении для подпрограмм обоих видов используется тер-
мин процедура. О функциях говорится либо тогда, когда хочется явно указать,
что процедура вырабатывает требующееся нам значение и используется вну-
три выражения, либо когда нужно сослаться на конкретный язык (например,
С/С++).
Некоторые подпрограммы оказываются одним из первых средств, с ко-
торыми приходится сталкиваться при знакомстве с языками программирова-
ния. Это такие подпрограммы, которые предоставляются языком или систе-
мой программирования:
оператор
// System Data
struct Temperature;
int GetTemperature(Temperature* x)
{
return x->outcoming_liquid;
}
// Implementation
protected:
typedef struct
{
int working_area,
368 ГЛАВА 8. ПОДПРОГРАММЫ
input_stream, outcoming_liquid;
} Temperature;
...
};
Здесь Temperature не дана пользователю данного класса как полностью из-
вестная ему структура, и он не знает внутреннего строения Temperature, ко-
торое может быть в любую секунду заменено без вреда для использующих
программ. Программист, использующий модуль, может даже не подозревать,
что на самом деле тип Temperature хранит не только температуру на выхо-
де системы, которая ему нужна, но и другие логически связанные с ней ха-
рактеристики, используемые внутренними процедурами моделирования или
управления.
Конец примера 8.1.2.
Несколько слов об употреблении терминов. Понятие ‘подпрограмма-модуль’
носит абстрактный характер: оно не связано ни с конкретным языком, ни
с какой-либо системой программирования или же операционной системой.
Если говорят о некотором языке, то для его средств модуляризации, есте-
ственно, используют термины этого языка. Например, когда требуется ука-
зать на подпрограмму-модуль конкретного языка, делается явное уточнение:
библиотека, библиотечный файл С и т. д. Однако термин ‘библиотека’ также
обозначает различное содержание. К примеру, как мы уже имели возмож-
ность убедиться (см. п. 1.1.2), в любой реализации языка С/С++ представле-
но по крайней мере три вида библиотек (если смотреть со стороны системы
программирования).
Если взглянуть со стороны операционной системы, то окажется, что при-
нято различать библиотеки по их отношению к процессу загрузки программ.
Появляется два вида библиотек: статически загружаемые (SLL — static linked
library) и динамически загружаемые (DLL — dynamic linked library). Не будет
большой ошибкой считать, что SLL транслируются вместе с основной про-
граммой и являются частью ее объектного кода (неточность здесь в том, что,
конечно же, эти библиотеки хранятся в почти до конца оттранслированном
виде, готовом к подключению на самой последней стадии работы транслято-
ра и загрузчика). Что касается DLL, то эти библиотеки отстоят от трансляции
языка программирования еще дальше: когда потребность в соответствующих
средствах выясняется в динамике выполнения программы, операционная си-
стема обращается к хранилищу DLL, извлекает из него нужную подпрограм-
му-модуль, приводит ее к виду, в котором программы библиотеки могут быть
8.1. ВИДЫ ПОДПРОГРАММ 369
Отмеченное ранее разграничение того, что делает модуль, и как он это дела-
ет, указывает на повышение уровня абстрактности при совместном описании
процедур в рамках единого модуля.
В С/С++ средства модуляризации программ связываются с понятием за-
головочного (header) файла, в котором явно описывается, что предоставляет-
ся модулем (текстовым файлом), озаглавленным этим заголовочным файлом.
Все остальное является локальным для данного модуля3 .
В стандартном языке Pascal средства модуляризации программ, позволя-
ющие объединять описания процедур и функций, отсутствуют. Можно ска-
зать, что язык предоставляет возможность модуляризации только на уровне
операционных единиц, т. е. он допускает лишь отдельные описания процедур
и функций. Это препятствует повышению уровня абстрактности программ,
делает невозможным осуществление контроля выполнения регламента ис-
пользования данных, имеет ряд других отрицательных следствий. Для улуч-
шения языка в данном отношении системы программирования расширяют
стандартный Pascal, вводя в него средства модуляризации. Что касается ис-
пользования стандартного языка Pascal, то он практически вышел из употре-
бления. Если же приходится его использовать, то модульность имитируется
при помощи организационных мер: выбор имен, специальное расположение
текстов программ при распечатке, комментирование. Впрочем, такого рода
моделирование — обычное дело программиста, когда нет желаемых средств
в используемом языке либо системе.
Для профессиональной разработки алгоритмов и реализации их на языке
необходимо научиться не только описывать свои подпрограммы, но и приме-
нять и перестраивать при необходимости чужие, овладеть практически все-
ми возможностями, предоставляемыми инструментальным языком в части
оперирования с процедурами.
свои места, когда в С++ строго определяется понятие пространства имен namespace.
372 ГЛАВА 8. ПОДПРОГРАММЫ
int MyFunc ()
// Тело процедуры
{ //
...
} //
Имя
/* процедуры */
Атрибут
тело
Оператор
Тело
процедуры
размер массива:
являть сигнатуру), и анализе контекстных зависимостей, когда сигнатуру вызова нужно со-
поставлять с сигнатурами описаний процедур. Придумайте алгоритмы, решающие поста-
вленную задачу.
Впрочем, это зависит от того, как понимается имя в данном языке. Иногда (особенно в
языках, ориентированных на интерпретацию) можно говорить о динамических именах объ-
5
ектов.
8.3. ЛОКАЛИЗАЦИЯ ИМЕН 377
NN
Неиспользованная часть
массива
NN
• тело.
#include <stdio.h>
#include <stdlib.h>
const NN = 100;
(8.1)
typedef float Matrix[NN][NN];
Matrix Matr; //(*)
int N;
Описание типа
Matrix Matr1,Matr2;
main f g
параметры
параметры
или даже
COMMON /COORD/ A(3)
382 ГЛАВА 8. ПОДПРОГРАММЫ
Общие блоки в языке FORTRAN понимаются лишь с точки зрения общей ма-
COMMON /COORD/ CHAR STR(10)
#include <stdio.h>
const N = 100,
N1 = 30;
int a, b, c, x, y, z, u, v;
void P1();
void P2();
// Контексты программы:
// локальный: const N 100; N1 30;
// переменные a, b, c, x, y, z, u, v;
// процедуры P1, P2;
// глобальный:то, что предоставляет <stdio.h>
void main()
{
const N1 = 50;
// локальный: (пустой)
// глобальный: const N 100; N1 30;
// переменные a, b, c, x, y, z, u, v;
// процедуры P1, P2;
// то, что предоставляет <stdio.h>
x = N1;
8.3. ЛОКАЛИЗАЦИЯ ИМЕН 385
y = x + N;
program PROG;
const N = 100;
x, y, z : . . . ; { типы переменных }
var
u, v : . . . ; { несущественны.}
procedure P1 . . . ;
const N1 = 50;
var x, y : . . . ;
u, v : . . . ;
procedure Pp1 . . . ;
const N1 = 40;
var x, z : . . . ;
глобальный:
var x, z, v;
const N1 = 50;
var y, u, v;
procedure Pp1;
const N = 100;
var a, b, c;
procedure P1;
}
локальной константы N1 = 40 }
{ переменной y, глобальной для Pp1 и локальной
для P1, присваивается значение суммы переменной x,
y := x + N;
локальный:
глобальный:
(пустой)
const N1 = 50;
var x, y, u, v;
procedure Pp2, Pp1;
const N = 100;
var a, b, c, z;
procedure P1; }
константы N1, N; }
{ Контексты процедуры P1
end; {Pp2}
{ Контексты процедуры P2
procedure P2 . . . ;
локальный: (пустой)
глобальный:
const N = 100; N1= 30;
var a,b,c, x,y,z, u,v;
procedure P2; P1; }
begin {P2}
...
локальный:
const N = 100; N1 = 30;
var a,b,c, x,y,z, u,v;
глобальный: (пустой) }
procedure P2, P1;
begin {PROG}
...
end. {PROG}
личие препроцессора, который в принципе ‘не знает’ языка, но, тем не менее,
оперирует с именами программного текста. Среди прочего действия препро-
цессора включают в себя подстановку определенным образом оформленных
фрагментов вместо их имен, которые определяются операторами #define. В
результате в программном тексте появляются как нормальные, относящие-
ся к языку имена, так и имена обозначений текстовых фрагментов. Так вот,
если первые подчиняются только что определенным правилам локализации,
то вторые — нет. Они подчиняются правилам, которые “знает” препроцес-
сор: если имя определено оператором #define как обозначение некоторого
фрагмента, то это обозначение действует, сразу же после данного операто-
ра и до конца текста либо до нового оператора #define, переопределяющего
данное имя. Слово ‘действует’ надо понимать следующим образом: препро-
цессор подставляет обозначенный именем фрагмент в то место текста, в ко-
тором встречается данное имя. Нужно осознавать, что при вставке фрагмента
встречающиеся в нем имена приобретают смысл из контекста вставки (но не
описания, как если бы действовали правила (a)–(e)). Таким образом, работа
препроцессора может создать впечатление нарушения правил локализации
имен.6
Следующие два программных текста представляют собой описания на-
ших сквозных примеров процедур MyOwnInpMatr и MyInpMatr. Они демон-
стрируют возможности определения функций в глобальном контексте для
языка С. В данном случае предлагается использовать контекст с описанием
одной матрицы:
printf("Row %3i:",i);
for(j=0; j < N; j++)
scanf("%f",& Matr [i][j]);
printf("\n");
}
}
Программа 8.3.4
int MyInpMatr()
{
после которого (при условии корректного ввода) матрица Matr получает зна-
MyOwnInpMatr();
Программа 8.4.1
1. program PROG; KPROG
2. ... KPROG
3. procedure P; KP KPROG
4. ... KP
5. begin
6. ...
7. end; {P}
8. procedure Q; KQ KPROG
9. ... KQ
10. procedure Q1; KQ1 KQ
11. ... KQ1
12. begin
13. P;
14. end; {Q1}
15. procedure Q2; KQ2 KQ
16. ... KQ2
17. begin KQ2
18. Q1; KQ2
19. end; {Q2}
20. begin
21. Q2;
22. end; Q
23. begin
24. Q;
25. ...
26. P;
27. end.
Локальный контекст основной программы KPROG, являющийся глобальным
для процедур P и Q, включает среди прочего имена этих процедур. Это по-
зволяет задать вызов процедуры Q в теле программы (строка 24). Тем самым
при вызове Q будет образована динамическая цепочка
P, строка 4, есть
При возврате из процедуры из них удаляется имя KP, а при выходе из всей
KPROG, KP.
ному взгляду на процедуру: когда при вычислении вызывается процедура, это приводит к
активизации операторов ее тела, но при этом не прекращаются, а лишь приостанавливают-
ся другие начатые действия. Таким образом, становятся активированными сразу несколько
вычислительных процессов.
8.4. ВЫЗОВ ПРОЦЕДУРЫ 397
§ 8.5. ПАРАМЕТРИЗАЦИЯ
Ключевой параметр-признак:
\includegraphics[scale=1.00, rotate=0]{ris2-1.eps}
целей, которые ставятся при разработке языка. Основная проблема здесь со-
стоит в противоречии между стремлением к повышению универсальности
использования процедуры (здесь это возможность вызова ее с комплектами
параметров разных типов) и контролем корректности вызова.
Для пояснения этого тезиса используется следующая иллюстрация. Впол-
не осмысленна такая процедура (записывается в нотации, напоминающей
Паскаль, но это совершенно другой язык):
complex(a+b) и string(a+b)
думался, как нужно преобразовать действительное в целое: округлить или усечь дробную
часть?
412 ГЛАВА 8. ПОДПРОГРАММЫ
...
var a : Integer;
procedure P(name x : Integer);
a := взять_значение x;
begin
взять_адрес x := 1;
end;
Словосочетания “взять_значение” и “взять_адрес” использованы для ука-
зания на необходимость перехода от оперирования ссылками к уровню значе-
ний и адресов, которые при использовании переменных обычно осуществля-
ется автоматически. В некоторых языках переходы такого рода при опери-
ровании со ссылками также делаются автоматически (на самом деле здесь
используются правила приведения типов), что позволяет считать синонима-
ми понятия передачи параметров как переменных и по ссылке. Понятно, что
по поводу моделирования назначения параметризации механизмом параме-
тров-ссылок можно сказать все то же, что и про параметры-переменные.
Именно передача параметра по ссылке в С/С++ реализуется с помощью
механизма передачи по значению. В этом языке весьма развиты средства опе-
рирования с указателями, а потому авторы языка предпочли “забыть о лиш-
них” механизмах. В самом деле, заголовок функции
при вычислении дает адрес переменной Variable. А это именно то, что нужно
&Variable
Гарантия Гарантия
неизменности неизменности
Перенос назначения
необходимости
емого значения. Напротив, для Object Pascal тот факт, что константные па-
раметры не могут служить получателями в операторах присваивания, позво-
ляет, в частности, организовывать передачу процедуре ссылки, когда фак-
тические параметры оказываются переменными, с сохранением требуемой
семантики. Это снимает противоречие между желанием передавать входные
параметры-массивы по значению и стремлением к эффективности, которое
заставляет во избежание копирования больших массивов данных пользовать-
ся параметрами-переменными. А константный указательный параметр языка
С++ непосредственно приспособлен к такому приему оперирования с масси-
вами, структурами и другими составными типами.
Резюмируя обсуждение, можно сказать, что константные параметры есть
всего лишь иное прагматическое оформление входных параметров.
TURBO Pascal версии 7.0 и Object Pascal Delphi предлагают еще один спо-
соб параметризации, который может добиваться некоторых эффектов, ана-
логичных механизму передачи параметров по именам. Речь идет о так назы-
ваемых нетипизированных параметрах. Синтаксически они задаются с по-
мощью спецификации var, const или out имени формального параметра, за
которой не указывается тип параметра:
const NN = 100;
type Index = 1..NN;
Matrix = array [ Index, Index ] of Real;
var N : Integer;
Matr, Matr1 : Matrix;
Программа 8.5.1
scanf("%f",& M[i][j]);
printf("\n");
}
}
N = 10;
MyOwnInpMatr (Matr, N);
MyOwnInpMatr (Matr1, 5);
Программа 8.5.2
// определения окончания
char c;
printf("Row 0:");
do
scanf("%f%c",&Matr[0][SizeM++],&c);
while (c!=’\n’);
for(i=1; i < SizeM; i++)
{
printf("Row %2i:",i);
for(j=0; j < N; j++)
scanf("%f",& Matr [i][j]);
do
scanf("%c",&c);
while(c!=’\n’);
}
return SizeM;
}
A[0][0] + MyInpMatr(A)
и
MyInpMatr(A) + A[0][0]
Быть может, последний термин и противоречит содержательному представлению о том,
что цель функции — ввод матрицы, но в большинстве случаев дисциплинированного про-
16
Это описание требуемого типа на языке Pascal. Для С/С++ оно выглядит сле-
дующим образом:
enum TansCode = {ok, er1, er2, unknown_er};
тризации.
440 ГЛАВА 8. ПОДПРОГРАММЫ
ции>, которая определяет шаблон для всех функций, подходящих для под-
становки в качестве фактического параметра. Совершенно аналогично зада-
ется параметр-процедура, для которого шаблоном служит заголовок проце-
дуры. Употребляемое в таких заголовках имя подпрограммы-параметра обо-
значает ее для использования в конструкции вызова процедуры или функ-
ции. Оно является именем формального параметра. Заголовок процедуры
или функции, используемый для определения формального параметра, на-
зывается спецификацией параметра.
Как видно из примера, правило задания спецификации параметров-функ-
ций и процедур избыточно: оно требует указания имен формальных пара-
метров специфицируемого параметра-подпрограммы (в примере — X), то-
гда как необходимой и достаточной информацией являются лишь их типы (в
примере — Real). При выборе изображения для таких спецификаций автор
языка Pascal стремился к минимизации синтаксических понятий языка, а по-
тому отдал предпочтение указанному виду. В этой связи стоит отметить, что
многие другие системы программирования в подобных случаях не требуют
указания избыточных имен.
При трансляции заголовка процедуры, имеющей формальный параметр-
процедуру, для такого параметра отводится элемент памяти, который пред-
ставляет формальный параметр в теле процедуры. Ему при вызове обеспе-
чивается присваивание адреса фактического параметра-процедуры, и это по-
зволяет осуществлять вызов формального параметра с помощью тех же ко-
манд, которые задаются при обычном вызове процедуры. Таким образом,
этот механизм в точности соответствует механизму передачи параметра по
ссылке, который, как было указано на стр. 419, специальным образом оформ-
ляется синтаксически с той целью, чтобы изображение вызова обычной про-
цедуры ничем не отличалось от вызова формального параметра. В результате
при написании тела процедуры можно не замечать их различий и абстрагиро-
ваться от того, какие фактические параметры-процедуры будут подставлять-
ся вместо используемого формального параметра.
В языке Pascal передача процедуре алгоритма в качестве параметра под-
чиняется существенному ограничению: все параметры процедур, которые
используются в качестве фактических параметров, подставляемых вместо
формальных параметров-процедур, должны быть параметрами-значениями.
В частности, невозможно, чтобы у таких параметров были бы свои параме-
тры-процедуры. Введение такого ограничение разумно, поскольку без него
442 ГЛАВА 8. ПОДПРОГРАММЫ
рода:
определяющих шаблоны:
var p : Prt;
f : Fut;
гола 68, и все версии расширений языка Pascal, с которыми ему приходилось иметь дело,
содержали в данном месте ошибки реализации.
444 ГЛАВА 8. ПОДПРОГРАММЫ
procedure PP (T : Prt);
...
begin
...
T (5, 5.5);
...
end; {PP}
и
name N : by need
name N ?
13. Сопоставтьте параметры-функции с параметрами, ппередаваемыми по
наименованию. Приведите примеры одинаковых и различных эффек-
тов.
8.6. МОДУЛЬНОСТЬ TURBO PASCAL 447
Свободные участки
Занятый участок
Container
IE
IB
Занятые участки
Свободный участок
Container
IE
IB
IB
гается в двух крайних случаях: когда очередь опустошается и когда она ста-
новится заполненной. Это равенство говорит, что очередь находится в одном
из состояний: Empty или Full. Какое из них фактически установилось, ясно,
если проанализировать, какая из операций GetElQueue или PutElQueue вы-
полнялась последней. Информация об этом сохраняется в переменных BFull
и BEmpty логического типа, которые должны рассматриваться в качестве со-
ставляющих представления очереди23 .
Заметим, что можно было не вводить две этих переменных, а вызывать соответствующую
реакцию на крайнее состояние в конце каждой из подпрограмм GetElQueue и PutElQueue.
23
const
MaxSize = 100; { public !!!}
NotEl = 0; { public !!!}
type
States = ( Full, Empty, Normal ); { public !!!}
var
Container : array [ 1..MaxSize ] of Integer; { private }
IB, IE : Integer; { private }
BFull, BEmpty : Boolean; { private }
procedure InitQueue; { public }
begin
IB := 1; IE := 1;
BFull := False; BEmpty := True;
end;
begin
Container [ IB ] := X;
IncI ( IB );
BEmpty := False;
BFull := IB = IE
end;
GetElQueue := NotEl
end
end;
Программа 8.6.2
unit IntQueue;
interface
const MaxSize = 100;
NotEl = 0;
type States = ( Full, Empty, Normal );
procedure PutElQueue ( X : Integer );
function GetElQueue : Integer;
function StateQueue : States;
implementation
var
Container : array [ 1..MaxSize ] of Integer;
IB, IE : Integer;
BFull, BEmpty : Boolean;
{ реализация процедур }
{ и функций }
procedure InitQueue;
...
procedure IncI ( var I : Integer ); { InitQueue, }
... { IncI, }
{ GetElQueue и }
procedure PutElQueue (X : Integer); { PutElQueue, }
...
{ в точности повторяет }
function GetElQueue : Integer; { StateQueue }
{ их тексты на }
...
{ стандартном Pascal’е }
function StateQueue : States;
...
begin
InitQueue;
end.
ThisUnit.Identifier
Программа 8.6.3
program WWW;
procedure InpW;
begin
repeat
’ Повторите ввод’);
until InpEl <= MaxW
end;
procedure ClearQueue;
begin
while StateQueue <> Empty do
Write ( GetElQueue, ’ ’ );
end;
then
begin
ClearQueue;
Write ( InpEl, ’ ’);
Write ( ’(’, S, ’) ’);
CurW := 0;
begin
while StateQueue <> Empty do begin
R := GetElQueue;
Sn:= S - R;
Write ( R, ’ ’ )
else begin
{ Завершение обмена }
ClearQueue;
InpEl := R;
CurW := Sn
end
end;
Write ( ’(’, CurW, ’) ’);
CurW := InpEl;
PutElQueue ( InpEl )
end
end;
begin
1 ∗ 2 ∗ ··· ∗ N
F = 1;
for (int i = 2; i<= N; i++) F = F*I;
1! = 1;
N ! = (N − 1)! ∗ N, при N > 1,
Программа 8.7.1
Φ(1) = Φ(2) = 1;
Φ(N ) = Φ(N − 1) + Φ(N − 2), при N > 1.
Функцию
8.7. РЕКУРСИВНЫЕ ПРОГРАММЫ 467
Программа 8.7.2
function F ( N : Integer ) : Integer;
begin
if N < 2
then F := 1
else F := F ( N - 1 ) + F ( N - 2 )
end;
заданную в точном соответствии с рекуррентным соотношением, нельзя при-
знать удовлетворительной.
В самом деле, вычисление F(5) требует вычислить F(4) и F(3), которые
затем складываются. Каждое из них есть вызов F без передачи какой-либо
информации об истории вычислений. В результате F(3) считается дважды:
сначала в рамках вычисления F(4), т. е. в первом аргументе выражения F ( N
- 1 ) + F ( N - 2 ), а затем повторно в ходе вычисления F(5) при вычислении
второго аргумента этого же выражения. В свою очередь, F(2) вычисляется
уже три раза: дважды при каждом вызове F(3) и еще один раз в ходе вы-
числения F(4). Здесь повторный счет возник в связи с тем, что алгоритм не
предусматривает сохранения информации о ранее вычисленных значениях
F(K), 1 6 K < N .
Таким образом, вычисление чисел Фибоначчи, буквально следующее опре-
деляющему рекуррентному соотношению, приводит к неэффективности. Для
обучаемых рекомендуется выполнить упражнения 1–4.
Конец примера 8.7.1.
На примере функции F легко видеть, что в комплекте локальных данных под-
программы должна запоминаться точка возврата: в зависимости от того, вы-
звана функция как первый или второй операнд сложения, требуется обеспе-
чить тот или иной корректный возврат при завершении экземпляра.
Пример 8.7.2. Вычисление наибольшего общего делителя (НОД) двух нео-
трицательных чисел. НОД(x, y) определяется как максимальное целое, ко-
торое делит без остатка x и y. Алгоритм основан на следующих свойствах
функции НОД:
НОД(x, x) = x;
НОД(x, y) = НОД(x, y − x), если y > x.
468 ГЛАВА 8. ПОДПРОГРАММЫ
Программа 8.7.3
procedure MCD (x,y : Integer 0..MAXINT; var R : Integer);
begin
if x = y
then R := x
else if x > y
then MCD ( y, x, R ) {*}
else MCD ( x, y - x, R )
end;
{ Параметр M не используется }
function mm ( X : Integer; M : fff ) : Real;
program SEQ;
const NN = 100;
K = 5;
Ms = ’3’;
MaxNstr = 23;
var i, N : Integer;
S : packed array [ 1 .. NN ] of Char;
sim: packed array [ 1 .. K ] of Char;
8.7. РЕКУРСИВНЫЕ ПРОГРАММЫ 473
procedure Init;
begin
for i := 1 to N do
S [i] := ’1’;
M := N;
end; { Init }
procedure Inpt;
var ch : Char;
N := 0;
repeat Read ( ch ); N := N + 1; S [ N ] := ch
until ( N >= NN ) or ( ch = #13 );
N := N - 1
end; { Inpt }
function Good1 ( ib, ie : Integer) : Boolean;
var i, l : Integer;
gl : Boolean;
function Eq ( i, j, l : Integer) : Boolean;
var imax : Integer;
r : Boolean;
begin
l := l - 1;
474 ГЛАВА 8. ПОДПРОГРАММЫ
imax := i + l;
r := ( l >= 0 ) and ( imax <= N ) and ( j + l <= N );
while r and ( i <= imax ) do
begin
r := S [i] = S [j];
i := i + 1; j := j + 1;
end;
Eq := r
end; { Eq }
begin
if ib + 1 = ie
then Good1 := TRUE
else if Good1 ( ib, ie - 1 )
then begin
gl := true; l := ( ie - ib ) div 2;
while gl and ( l > 0 ) do
begin
gl := not Eq ( ie-l-l, ie-l, l );
l := l - 1
end;
Good1 := gl
end
else Good1 := false
end; { Good1 }
end;
if Nstr >= MaxNstr then begin Nstr := 0; Readln end
until not Next;
Writeln;
end.
подобными мотивами. Однако есть контрпример: язык LISP, рекурсивные механизмы кото-
рого органичны для функционального стиля. Это один из самых старых языков програм-
мирования, применявшийся в тех же самых жестких условиях. В этом контексте следует
отметить Algol 60, разработчики которого пусть не во всем, пусть лишь на уровне идей, но
уже понимали, что потребуется не только программирование от состояний, но и значительно
476 ГЛАВА 8. ПОДПРОГРАММЫ
ми. Такой подграф может быть стянут в структурную вершину. Для точ-
ной интерпретации вычислений необходимо договориться о том, как
происходит транзитная передача данных по дугам графа через струк-
турные процедурные вершины. Здесь возможны такие режимы:
Структуры данных
482
9.1. СТРУКТУРИРОВАНИЕ ДАННЫХ 483
V[first] = 0; (9.2)
for all t > first V[t] = V[t-1] + C;
Нет даже никакого явного упоминания времени, а роль времени играет согла-
шение: каждая переменная является контейнером, содержащим поток значе-
ний, удовлетворяющий задаваемым соотношением.
Приведенный пример показывает, как принципы систем структурирова-
ния данных проявляют себя в стилях программирования, не являющихся опе-
рационными.
A : rep → A
A −−−→ A0
f
x x
0
A A (9.3)
repA −−−→ repA0
frep
9.2.1. Перечисления
но указывать явно.
Указанных выше операций достаточно для использования перечислений
в программах. Для повышения выразительности полезно согласование этих
операций с циклами for (в двух вариантах: по возрастанию и по убыванию)
и for all (порядок перебора не фиксируется). Естественно, что по значениям
типа перечисления можно организовывать выбирающие операторные кон-
струкции. С точностью до оператора for all так сделано в Pascal.
– область фиксирована
type MyInt1 is INTEGER range 0..1000;
– допустимое реализацией
Транслятор использует информацию об областях значений следующим
образом. Описание языка предписывает системам программирования обес-
печить математическую корректность вычислений в заданной области, но
при условии, что значения не превышают допустимых величин INTEGER’FIRST
и INTEGER’LAST. Для малых значений это указание означает, что можно ис-
пользовать короткие целые, для больших значений предписывается модели-
ровать арифметику, если вычислитель не позволяет соответствующие дей-
ствия выполнить стандартными командами.
Интересен случай динамических вычислений области. Он осмыслен для
динамически возникающих переменных и констант, область которых опре-
деляется в момент появления описания их типа (после этого, к примеру, из-
менение переменной V не повлияет на диапазон данных, тип которых опи-
сывается как MyInt3).
В Аде есть еще одно полезное свойство определения типов, применимое
и к типу целых. Можно описать новый тип, который имеет поведение уже
существующего типа, но им не является:
type MyNewInt is new INTEGER;
Гарантированные (доказательные) вычисления — вычислительные эксперименты, стро-
го обосновывающие оценки некоторой величины, например, α > 2 или 0.49999 < disp <
6
0.50001.
504 ГЛАВА 9. СТРУКТУРЫ ДАННЫХ
Беда в том, что заодно запрещается также умножение и (что самое обидное) деление яблок
на людей. Так что воспринимать этот полезный вид выводимых типов как приписывание
7
– область фиксирована,
– числа различимы с шагом 0.01
type MyFixed is delta 0.01 range 0..1000;
ных типов или его подтипов. Она дает возможность программисту осуще-
ствлять контролируемое компилятором “разделение мер”, фигурирующих в
содержательной задаче. Таким образом, имеет место удобное и недорогое
средство дополнительного контроля. Почему это средство редко встретишь
в практических языках? Во многом это объясняется традициями, но не толь-
ко они причина. К примеру, для С/С++ с его концепцией приведений просто
нет места для так называемых “новых” типов, которые все равно останутся
полностью эквивалентными свои базовым типам. В этом плане некоторые
дополнительные средства контроля предоставляются в рамках методологии
объектно-ориентированного программирования.
Что касается абстрактно-синтаксической структуры выводимых типов, то
в качестве примера рассмотрим рис. 9.1, на котором в приведена конструк-
... ...
Переменная Переменная
first = 1 last = 10
a) b)
а набор (неупорядоченых):
(K1 : T1; . . . ; Kn : Tn)
...
Переменная
Тип = Конструктор
Статический набор
Динамический набор
Гибкий набор
из вариантов структуры набора (см. рис. 9.3). Схемы для компонент набора
приведены на рис. 9.4.
Значением атрибута <Тип> может быть структурный или неструктурный
тип, представленный своим деревом либо именем. Если язык допускает толь-
ко статические типы, то в результате трансляции имена типов могут быть
заменены соответствующими им деревьями, которые используются для кон-
троля правильности задания оперирования с типизированными данными и
при необходимости для встраивания подпрограмм нужных приведений. В
статически типизированном языке после выполнения этих действий типовая
информация нигде не используется, и надобность в атрибуте <Тип> исчезает.
Для языков, которые допускают динамические вычисления с типами (хотя
бы в самой рудиментарной форме) атрибут <Тип> не может быть ликвидиро-
ван. Он (быть может, в трансформированной форме) используется в процессе
вычислений. Например, в объектно-ориентированных языках это ссылка на
таблицу виртуальных методов объекта.
Новым по отношению к рассмотренным ранее абстрактно-синтаксиче-
ским структурам у конструктора набора компонент является то, что дере-
во строится как часть атрибутной информации вершины. Эта ситуация ана-
логична процедурам: в данном случае атрибут фиксирует структуру, кото-
рая должна использоваться при вычислениях в качестве программы для аб-
страктного вычислителя.
Все теоретически возможное разнообразие вариантов набора компонент
в нынешних языках не используется. Конкретные формы конструкторов дан-
9.3. СТРУКТУРНЫЕ ТИПЫ 515
+ +
Компонент 1 … Компонент N
Конструктор Конструктор
Динамический Динамический
упорядоченный неупорядоченный
набор набор
* *
Компонент
(d) (e)
9.3.2. Записи
? ? ? Конструктор
Имя Тип Имя
комп онента компонента компонента Статический набор
Динамический набор
Гибкий набор
ных записей.
Средства оперирования для записей расширяются за счет оператора при-
соединения with, который позволяет однократно указывать запись, к которой
применяются селекторы для извлечения полей:
with <имя записи> do <оператор /* фрагмент */>
В результате имена полей пополняют контекст фрагмента, подчиняясь прави-
лам локализации имен. В частности, имена полей становятся приоритетными
при поиске определяющих вхождений идентификаторов в программе.
Оператор присоединения является средством скорописи для языка, но
очень часто он помогает повысить выразительность программ. Для фрагмен-
та после do он задает дополнительный контекст выполнения, причем поиме-
нованный (именем записи). Т. е. фрагмент может использовать поля в каче-
стве добавочной системы понятий. Полезность этого качества хорошо по-
нимали разработчики языка Симула-67, появившегося, когда записей еще не
придумали. Они отразили его в конструкции префикс-класса для (операцион-
ного) блока, тем самым дали возможность наиболее выразительно предста-
влять в программе средства, описанные при определении объектов. В С++,
как и в других языках того же рода, объектная ориентированность строит-
ся на базе понятия записи (структуры), но нет присоединений, которые мог-
ли бы в манере префикс-классов явно выделять то, что доступно от объекта
(в Object Pascal такая возможность есть). Мотивируется это тем, что из-за
возможного перемешивания селектирования и присоединения трудно разби-
раться с контекстами фрагмента, которые пополняются без явного разграни-
чения, какие имена к чему относятся.
Представление записей в программе допускает следующие варианты:
• стандартное, когда каждое поле получает собственный адрес. В этом
случае время доступа к полю оказывается минимальным;
(6). Время жизни оператора есть его выполнение после перехода к нему. Вре-
мя жизни записи шире, оно включает выполнение всего контекста, в ко-
тором запись описана. Здесь расхождение полное.
9.3.3. Объединения
зн1 : A : T1,
case tag : Tp of
зн2 : B : T2);
end;
(4). Именование в обоих случаях возможно через метку варианта, но для за-
писей допускаются еще и имена полей, что соответствует именам про-
цедур (вместе с сигнатурами, если возможен их полиморфизм).
9.3.4. Массивы
• тип компонент.
9.3. СТРУКТУРНЫЕ ТИПЫ 525
Если тип индексов всегда один и тот же (обычно отрезок целых неотрица-
тельных, как в С/С++), то он может опускаться.
Массивы бывают статическими, динамическими и гибкими. Для статиче-
ских массивов границы изменения индексов задаются статически вычисли-
мыми выражениями (состоящими из констант и переменных периода ком-
пиляции), для динамических массивов — выражениями, значения которых
должны быть вычислены до выполнения определения типа массивов. Про
гибкие массивы говорят, что они имеют подвижные границы, т. е. такие, ко-
торые могут изменяться в процессе оперирования с массивом.
Подвижные границы чаще всего указываются специальным служебным
словом. Если значение нижних границ всех массивов стандартизовано (в С/С++
это 0), оно опускается. В С/С++ задание верхней границы индекса массива —
это указание границ безопасного обращения к элементам массива. В строгом
смысле этого слова она границей не является, поскольку никакой ошибки не
будет, если индексное выражение выйдет за указанный предел. Последнее в
точности соответствует трактовке массивов как указателей на область памя-
ти.
Массив есть таблично задаваемая функция, переводящая область ин-
дексов в область компонент. Если D — область изменения индексов (одно-
мерная для векторов или многомерная в других случаях), а R — множество
(область) значений компонент, то
M : D → R.
мощность(D → R) = мощность(R)мощность(D) .
D[i, j] :
read(i, j) = (SV (D, i, j)) =
if i > j then(SV (D, j, i))
else(SV (D~ rep , ϕ(i, j)); (9.4)
write(i, j, x) = (SA (D, i, j) := x) =
if i > j then(SA (D, j, i) := x)
else(SA (D~ rep , ϕ(i, j)) := x)
a[0]
a[1]
a
...
A[n]
Первое измерение:
Второе
измерение
A:
…
d2 d2’
Остаток
…
этого ряда
d1 Нет значения
A[d1,d2’]
A[d1,d2]
… Остаток
d1’ этого ряда
A[d1’,d2’]
A[d1’,d2]
… … …
Граничная Остаток этого Остаток этого
цепочка d1 столбца столбца
Kij
… Kin
Ключ1 K1
................................................
Ключim1 Kim1
Ключim2 Kim2
...
Ключimgm Kimgm
следующую функцию:
если(0 < k 6 WA )&(0 < l 6 HAB )
D [k, l]=
( case F(k, l) of
1 : DA [k, l];
2 : DB [k - WA - WC , l - HAB ];
3 : DC [k - WC , l - HAB ];
end)
F ( k,l ) А
В
Для всех вариантов массивов циклы всех видов допустимы, причем они ре-
ализуются достаточно эффективно. Также для всех размеров массивов не-
обходимо иметь функцию вычисления размера массива, более того, отсут-
ствие такой встроенной функции в стандартных языках программирования
является, пожалуй, одним из видов глупости, передающейся из поколение в
поколение.
534 ГЛАВА 9. СТРУКТУРЫ ДАННЫХ
9.3.5. Множества
1. x ∈ S — предикат;
set of T ):
2. S1 = S2 — предикат;
3. S1 ∩ S2 — пересечение;
4. S1 ∪ S2 — объединение;
5. S1 − S2 — дополнение;
6. S1 ⊆ S2 — предикат включение;
A: 1 1 1 1 1 1 1 1
B: 1 1 1 1 1 1
A ∪ B: 1 1 1 1 1 1 1 1 1 1 1 1
НОД(x, y) =
max {n|1 6 n&n 6 min{x, y}&∃k1, k2 (x = k1 ∗ n&y = k2 ∗ n)}
таким образом:
Базис рекурсии.
M = array [] of A — массив с компонентами типа A с нулевым числом компонентов;
Шаг рекурсии.
Если M — массив с n или меньшим количеством компонентов типа A, то M M array [n+1] of
A — массив, у которого n + 1 компонентов типа A, где M — специальная операция над типа-
ми, предназначенная для добавления элементов в набор. Таким образом, в данном примере
определяется тип массивов с подвижной верхней границей.
Определение матрицы, т. е. двумерного массива, в языке С/С++/C# — это просто сокраще-
ние для того, чтобы сказать, что вводится указатель на указатель на компоненты некоторого
скалярного типа (это другой конструктор).
542 ГЛАВА 9. СТРУКТУРЫ ДАННЫХ
T10 = B1 ; . . . ; Tk0 = Bk ;
T1m = E1 (T1m−1 , . . . , Tkm−1 ); . . . ; Tkm = Ek (T1m−1 , . . . , Tkm−1 );
T1 = lim T1m ; . . . ; Tk = lim Tkm .
m→∞ m→∞
T1 = E1 (T1 , . . . , Tk );
...
Tk = Ek (T1 , . . . , Tk );
T10 ⊆ T11 ⊆ · · · ⊆ T1 ;
...;
Tk ⊆ Tk1 ⊆ · · · ⊆ Tk ;
0
и другие.
При работе с рекурсивными структурами данных, как и при рекурсии дей-
ствий, задаваемой рекурсивными процедурами, выстраиваются монотонно
убывающие последовательности. Но если при рекурсии действий вместо их
явного задания в программу включаются ограничивающие условия, то для
данных такой прием не годится: они должны быть представлены в програм-
ме так, чтобы любая конечная последовательность определяемого вида под-
ходила бы под шаблон описания структуры. Из этого следуют два противо-
положных решения:
Ts0 = {( )};
(x (y z)) x
y z
((x) (y z))
x y z
стройку сверху’. Еще раз повторим, что любое решение, принятое человеком, содержит в
себе в качестве атрибута человека ошибку. Гениальные решения, типа LISP, не являются
546 ГЛАВА 9. СТРУКТУРЫ ДАННЫХ
S = (x y S) ...
x y
Пусть
В = ( K1 , . . . , Kn : (В,T) ); n > 0
И пусть на операции формирования структуры и селекции компонентов на-
кладываются различные ограничения. Тогда получаются разные специаль-
ные варианты рекурсивных структур данных. Не все они общеупотребитель-
ны, но на некоторых стоит остановиться.
Стек и очередь
Стек
Push (S : В, E : T) = Push (( K1S , . . . , KnS ),E) ⇒
S = S + E = ( K1S , . . . , KnS , E );
Pop (S : В) : E ⇒
if S = ( K1S , . . . , KnS , E ) // или S.n > 0, что то же
then { S = ( K1S , . . . , KnS ), result = E }
...
551
E KS n KS 2 KS 1
Начало, Конец
S = S + E = ( K1S , . . . , KnS , E );
Delete (S : В) : E ⇒
if S = ( E, K1S , . . . , KnS ) // или S.n > 0, что то же
then { S = ( K1S , . . . , KnS ), result = E }
KS 1 ... KS 2 KS n E
Начало Конец
9.4.4. Деревья
Опять можно не говорить про указатели, но пока это только для дере-
tr = (. . . , B(tr, i),. . . )
Inf Inf
Ветви одного уровня:
Inf
Ссылки на ...
родителей
Inf Inf
Методы программирования
555
556
• И, наконец, опыт неудач! Рим был более велик, чем куча других антич-
ных государств, не потому, что он умел побеждать (это умели и другие),
но потому, что он умел переносить тяжелейшие поражения и неудачи,
и часто превращать победу противника в Пиррову или Ганнибаловы.
Пока Вы не потерпели серьезную неудачу и не встали на ноги после
нее, переоценив свои ценности, Вы не станете специалистом высшего
класса.20
В данном изложении мы, конечно, не ставим цели осветить все основ-
ные методы программирования — любые попытки такого рода пока что в
принципе ведут к энциклопедии и коллекции, а не к структуре знаний. На-
ша задача в том, чтобы помочь ориентироваться в применении методов (в
том числе и в первую очередь неописанных здесь!), в том, чтобы увидеть, как
метод может быть представлен в формах языка программирования. Мы пы-
таемся показать это на наиболее характерных примерах, в которых идея кон-
струирования программы не заслонялась бы излишними алгоритмическими
трудностями21 .
Понятие метода программирования в известной мере соотносится с по-
нятием стиля: для каждого стиля характерны свои методы. Но соотношение
между стилями и методами не следует сводить к утверждению о характерных
методах. Стиль программирования — это, прежде всего, образ мышления: «я
выбираю данную систему понятий, чтобы на ее основе строить программные
решения». Здесь нет явной привязки к какой-либо задаче. Метод же опреде-
ляет направление движения от задачи к решению. Он налагается на стиль,
т. е. формулируется в рамках системы первичных понятий стиля. Иногда та-
кое наложение осуществимо просто и естественно, и тогда можно говорить
о естественности метода для данного стиля. Иногда метод не вписывается в
стиль: приходится прибегать к новым понятиям, выходящим за рамки стиля,
от других понятий отказываться, и именно это сигнализирует о несоответ-
ствии метода стилю. Среди рассмотренных в главе 3 стилей есть такие, кото-
рые поощряют и даже пытаются предписывать использование определенных
методов или классов методов. Так, программирование от событий, постули-
рующее явное отделение в вычислительном процессе генерации событий от
За одного битого двух небитых дают
(Русская поговорка).
20
Методы программирования от
состояний
560
10.1. ОСНОВНЫЕ СТРУКТУРЫ 561
Система
S1 S2
SS2
SS1
SS3
Пояснения к картинке.
Светло-серые области — традиционный общий контекст системы и подсистемы.
Темно-серые иллюстрируют, что доступность может быть односторонней, и не
только по иерерхии. Одна из систем может влиять на часть информационного
пространства другой, а другая может лишь пассивно следить за тем, что натворил
непокорный вассал, либо сбрендивший начальник, либо просто коллега.
Области, связанные двусторонними стрелками, иллюстрируют прямое общение в
обход иерархии.
завершить процесс.
6. Символ конец строки: завершить процесс.
Символ
буква Инициализировать
обработку слова
Символ конец
строки
Завершить процесс
Выход
Продолжить
Символ обработку слова
Символ буква
буква Инициализировать
обработку слова
Символ конец
Символ конец Символ не буква строки
строки
Закончить обработку слова
Завершить процесс Завершить процесс
Выход
4. Выполнить Действие.
5. В качестве значения переменной Entry установить наименование со-
стояния-преемника (из четвертой колонки таблицы). Перейти к шагу
1.
В этом пункте может быть заложено недетерминированное поведение автомата. Очень
часто при применении метода конечных автоматов возможность недетерминированного по-
3
1. Оттранслировать вручную;
3. Использовать интерпретатор.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
void St1 ()
{
if (’a’<= symbol && symbol <= ’z’)
{
printf ("%c", symbol);
cnt = 1;
symbol = getchar ();
St2 ();
}
else if ( symbol != ’\n’ )
{
symbol = getchar ();
St1 ();
}
}
void St2 ()
{ if (’a’<= symbol && symbol <= ’z’)
{
printf ("%c",symbol);
10.2. ПОДСЧЕТ ДЛИН СЛОВ 573
cnt += 1;
symbol = getchar ();
St2 ();
}
else if ( symbol != ’\n’ )
{
printf (" - %i\n", cnt);
symbol = getchar ();
St1 ();
}
else printf (" - %i\n", cnt);
}
void main( void )
{
symbol = getchar ();
St1 ();
}
Это плохая программа, в частности, по той причине, что рекурсия есть фак-
тическая резервация памяти для вызовов функций, а, как уже отмечалось,
для конечного автомата динамического расходования памяти не требуется.
Одна из возможных причин появления такой программы в том, что факти-
ческая потоковая обработка, предполагаемая в таблице конечного автомата,
задана неявно. Попробуем сделать ее явной, соответствующей предположе-
ниям о виде потоковой обработки, сделанным в § 7.2.
Вариант 2: считать St1 и St2 значениями некоторого перечислимого типа
State.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
char symbol;
int cnt;
{
State = St1;
while ( (symbol = getchar ()) != ’n’)
switch ( State )
{ case St1: if (’a’<=symbol && symbol <= ’z’)
{ printf ("%c", symbol);
cnt = 1;
State = St2;
}
else // if (symbol != ’\n’)
// State = St1;
// else if (symbol == ’\n’) return;
break;
case St2: if (’a’<=symbol && symbol <= ’z’)
{ printf ("%c", symbol);
cnt++; // State = St2;
}
else if (symbol != ’\n’)
{ printf (" - %i\n", cnt);
State = St1;
}
else
{ printf (" - %i\n", cnt);
return;
}
}
}
влением.
Более технологичный вариант предыдущей программы можно постро-
ить, если каждый фрагмент программного кода, который отвечает за реализа-
цию действий, относящихся к разным состояниям, (разделы таблицы, поме-
ченные метками состояний), оформить в виде процедуры. Но чтобы не прий-
ти к варианту 1, необходимо помнить, что вызов такой процедуры не должен
приводить к выделению для нее собственной (локальной) области памяти —
конечный автомат работает с общей памятью!
При использовании многих языков (и систем программирования) выпол-
нения этого условия нельзя добиться, и остается только примиряться с тем,
что вызовы процедуры приводят к заниманию новой памяти. В качестве сред-
ства, пригодного для решения проблемы, обычно предлагается использовать
макрогенерацию (пример — препроцессор С). Для макрогенератора проце-
дуры, им обрабатываемые, становятся макросредствами:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
char symbol;
int cnt;
enum States { St1, St2, Exit } State;
Следует обратить внимание на то, что в данной программе явный цикл об-
работки потока исчез. Но стоит ли из-за этого считать вариант 3 ухудшени-
ем предыдущего? Конечно, нет. Программа наглядна, строго соответствует
таблице автомата. Можно сказать, что граф автомата определяет распреде-
ленную по состояниям схему потоковой обработки. И это нисколько не хуже
схем с явными циклами.
Проанализировав граф автомата или таблицу, можно заметить, что в дан-
ном примере наряду с циклом потоковой обработки имеется еще один цикл:
передачи управления между состояниями. Это причина, из-за которой в двух
578 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
State = <значение>;
switch ( State )
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
char symbol;
int cnt;
10.2. ПОДСЧЕТ ДЛИН СЛОВ 579
// Инициализация
int cnt;
... _4 St1 _5
_1 St1 _2 ’a’<=symbol && symbol <= ’z’ _3 printf ("%c", symbol);
cnt = 1; _4 St2 _5
_1 _2 //symbol <’a’ && ’z’< symbol && symbol != ’\n’
iSt = 0;
do {
iSt = handler ( iSt );
}
582 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
(a) iSt++;,
(b) Перейти к 1.
// поле условия
{
// поле действия
int (*condition)();
или
T_StructTableLine Table[]
— условие в Table[1]
и
int Cond_St1_1()
— условие в Table[2]
и
int Cond_St1_2()
и т. д.
Другим неприятным моментом данного представления является то, что
не удается единообразно с действиями представить их общий контекст (нуле-
вая строка таблицы), а поскольку инициализацию естественно рассматривать
как задание предварительных действий, готовящих собственно обработку, то
ее предпочтительнее соединять с контекстом, а не с функциями, реализую-
щими проверки и действия.
Последний недостаток неустраним ни в каком языке, если проверки и
действия трактовать как процедуры, которые работают в общем контексте,
584 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
задаваемом в заголовке таблицы. Так что, быть может лучше сразу отказать-
ся от описания этого контекста в рамках таблицы (задание инициализации
в ней возможно). Что касается первого недостатка, то он носит почти чисто
синтаксический характер, и, если бы можно было задавать тела процедур как
значения полей структур, то наглядность представления можно было бы со-
хранить. В стиле, похожем на язык С/С++/C#, это выглядело бы как следую-
щее присваивание значения Table в качестве инициализации:
Программа 10.2.6
1},
/* Переход не требует
чтения, symbol == ’\n’ не нужно читать */, 0},
{{return true},
Программа 10.2.7
10.2. ПОДСЧЕТ ДЛИН СЛОВ 585
4),
(symbol <> ‘\n’;), (печатать(symbol+‘- \n’,cnt), 1),
((истина), (печатать(symbol+‘- \n’,cnt), 0)
);
Но и для С/С++/C#, и для Алгола 68, как и для других языков, не ориенти-
рованных на работу с таблицами, говорить о естественности представления
не приходится. Так что, в таких ситуациях, по-видимому, наиболее разумно
строить автоматический преобразователь (типа препроцессора, но не путать
этот термин с С!), который составляет текст программы по таблице. В этом
случае снимается много проблем:
// функции-условия
// функции-действия
int c0(), c1(), c2();
// поле условия
{
// поле действия
int (*condition)();
// эффективно.
{c0,a0,0},
// в тексте.
{c2,a3,1},
{c0,a3,0}};
588 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
void main()
{
int iSt=0;
do {
iSt = handler ( iSt );
}
while ( iSt);
}
if (Table[i].condition()) {
Table[i].action();
if (Table[i].ref) symbol = getchar ();
return Table[i].ref;
} else return ++i;
void a0(){}
void a1(){printf ("%c", symbol);cnt = 1;}
void a2(){printf ("%c", symbol);cnt++;}
void a3(){printf ("- %i\n", cnt);}
Действие S12
a
b S2
S1
Действие S13
<Вложенная
конструкция>
Действие S14
S3
S4
ДействиеTS12
a
T
F ДействиеFSнеa
bT S2
S1
F
ДействиеFSнеb
<Вложенная констру кция> ДействиеTS13
E F T
ДействиеTS14
S3
ДействиеESне<Вложенная конс трукция> S4
…
Действие FSне<Вложенная констру кция>
<оператор присваивания>
<идентификатор> T /* определен получатель значения */
F /* это не оператор присваивания */
E /* это лишняя графа — см. ниже */
F
E
“=” T
ется на оператор) */
E
<выражение>
/* ошибка в множителе */
F F
E E
2 “+” T
/* зацикливание */
F 1
/* ошибка в выражении */
F E
E E
/* распознано выражение */
1 “-” T
F T
/* ошибка в выражении */
F E
E E
ние>) */
F 1
1 “/” T
F T
0 <множитель> T 2
F E
E E
int Assignment()
{
switch (Identifier())
{
case T:
if (current_symbol= =‘=‘)
{ get_next_symbol();
switch (Expression())
{
case T: return T;
case F: return F;
case E: return E;
}
}
else return E;
}
10.3. СИНТАКСИЧЕСКИЕ ТАБЛИЦЫ 603
/* терм — идентификатор */
<терм>
/* терм — идентификатор */
E E
“)” T T
F E
case F: return F;
case E: return E;
}
}
случае выбранный метод анализа ведет к зацикливанию работы автомата без продвижению
по анализируемой строке, т. е. к бесконечному зацикливанию.
10.3. СИНТАКСИЧЕСКИЕ ТАБЛИЦЫ 605
<выражение>
/* ошибка в множителе */
F F
E E
2 “+” T
/* зацикливание */
F 1
/* ошибка в множителе */
F E
E E
/* распознано выражение */
1 “-” T
F T
/* ошибка в множителе */
F E
E E
*/
F 1
1 “/” T
F T
0 <терм> T 2
F E
E E
3. В таблице конечного автомата нет иных меток перехода, кроме как на-
именований состояний (иное решение потребовало бы фиксации по-
рядка проверки условий переходов). Следовательно, в таблице конеч-
ного автомата нет возможности организовать иной цикл, кроме того,
который предписывается самой моделью вычислений.
играет еще и другие роли, одна из которых отмечена в следующем пункте, а вторая — за-
щита принятых решений от неквалифицированной критики и убеждение заказчика и/или
начальника и оппонентов в их обоснованности.
610 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
ний систем. В общем случае системного анализа эти понятия служат полезным маяком и
порождают аналогичную, но, конечно, не формализованную и неформализуемую, систему
оценок для нечисленных математических моделей.
612 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
и \end{example}.
\begin{example}\label{markupeample0}
сующее Вас место. Чтение книги как последовательного файла — слишком часто наименее
эффективный метод ее изучения.
618 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
чтении символа, не содержит условий и должно быть всегда первым. А, как будет понятно
в последствии, универсальность составных частей модели сильно упрощает работу с ней.
620 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
Программа 10.5.2
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<DIV STYLE="font-family:Courier; font-size:12pt">
<xsl:for-each select="automat">
<TABLE border="1" width="75%">
<TR>
<TD colspan="3"><xsl:value-of select="action" /></TD>
<TD width="10%"><xsl:value-of select="ref" /></TD>
</TR>
<xsl:for-each select="state">
<TR>
<td rowspan="3" width="10%" valign="top">
<xsl:value-of select="@name" /></td>
<td><xsl:value-of select="if/condition" /></td>
<td><xsl:value-of select="if/action" /></td>
<td><xsl:value-of select="if/ref" /></td>
</TR>
<xsl:for-each select="eif">
<TR>
<TD><xsl:value-of select="condition" /></TD>
<TD><xsl:value-of select="action" /></TD>
<TD><xsl:value-of select="ref" /></TD>
</TR>
</xsl:for-each>
</xsl:for-each>
</TABLE>
10.5. ПРОГРАММНЫЕ ПРЕДСТАВЛЕНИЯ ГРАФА СОСТОЯНИЙ 621
</xsl:for-each>
</DIV>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<blockquote>
<pre STYLE="font-family:Courier; font-size:12pt;" >
<xsl:for-each select = "automat">//C code for automat
"<xsl:value-of select="@name" />"
<![CDATA[
#include <stdio.h>
#define failure true
Как легко видеть, разница этой и предыдущей визуализаций только в том, что
там, где в таблице вставляются графические элементы, которые изображают
рамки, образующие саму таблицу, здесь находятся части синтаксиса языка
С++/C# и отступы для лучшей читаемости получаемой программы.
Итак, благодаря использованию формата представления данных XML, мы
получили возможность автоматически создавать программы для данной за-
дачи путем простой замены стилевых таблиц!
Отметим теперь еще несколько интересных возможностей, которые мы
бы могли использовать.
Хотя данная технология позволяет легко создавать С++/С# программы,
основой для них остается язык XML и чтобы составить новый автомат, про-
граммист должен как минимум знать синтаксис этого языка. Следующим
естественным шагом будет исключение этого звена: требуется скрыть вну-
треннее представление данных от конечного пользователя, оставив только
интуитивно понятное представление в виде таблицы. Но для этого в первую
очередь необходимо выбрать:
oRow = oTable.insertRow();
oCell = oRow.insertCell();
oCell.innerHTML = "This cell is <b>new</b>."
Точно так же можно создавать таблицы, строки и ячейки в них. Реализа-
ция же самого интерфейса (кнопочек, средств выделения, полей ввода) зави-
сит только от вашей фантазии.
Тот же DOM, точно таким же образом, может работать с XML, реплицируя
все действия конечного пользователя с таблицей в XML представление для
последующей записи последнего в виде готового файла со структурой нового
конечного автомата.
Еще одним шагом в развитии проекта, использующим язык XML может
быть формализация используемого представления: ведь как только опреде-
лены все теги представления, правила их вложения и способы задания, тем
самым получился новый язык (по аналогии с современными языками, по-
строенными таким же образом, мы можем назвать его “automatML”). Пока
теги и элементы XML используются исключительно ради удобства для ваше-
го собственного проекта (как если бы вы использовали CSS на своей домаш-
ней страничке), то не имеет никакого значения, что вы даете этим элементам
и тегам имена, смысл которых отличается от стандартного и известен только
вам. Если же, с другой стороны, вы хотите предоставлять данные внешнему
миру и получать информацию от других людей, то это обстоятельство при-
обретает огромное значение. Элементы и атрибуты должны употребляться
вами точно так же, как и всеми остальными людьми, или, по крайней мере,
вы должны документировать то, что делаете.
Для этого придется использовать определения типов документов (Document
Type Definition, DTD). Хранимые в начале файла XML или внешним образом в
виде файла *.DTD, эти определения описывают информационную структуру
документа. DTD перечисляют возможные имена элементов, определяют име-
ющиеся атрибуты для каждого типа элементов и описывают сочетаемость
одних элементов с другими.
Каждая строка в определении типа документа может содержать деклара-
цию типа элемента, именовать элемент и определять тип данных, которые
624 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
Например, декларация
где <Число k> — полное число вхождений слова αk в файл с учетом возмож-
ного перекрытия слов (например, в строке “*МАМАМА{}” два вхождения
слова “МАМА”).
Для заданных заранее слов легко построить граф, каждая вершина кото-
рого представляет символы внутри слов. Его вершины помечены символом.
Из такой вершины исходят две дуги: первая указывает на вершину, к которой
следует переходить, когда очередной читаемый символ совпадает с пометкой
вершины, а вторая — на вершину, которая должна стать преемником данной
в случае несовпадения. Легко видеть, что это одна из форм представления ко-
нечного автомата, каждое состояние которого кодирует множество всех вер-
шин, связанных дугами второго вида, а состояния-преемники определяются
дугами первого вида исходного графа. Чтобы этот автомат работал, т.е. решал
поставленную за-дачу нужно снабдить его действиями, которые сводятся к
увеличению счетчиков, соответ-ствующих найденным словам, а также опре-
делить начальное и конечное состояния. Мы не будем заниматься переделкой
исходного графа, поскольку такая его форма удобнее для интерпретации.
Если дуги первого вида изображать стрелками, исходящими в горизон-
тальном на-правлении, дуги второго вида - вертикальными стрелками, а дей-
ствия со счетчиками - соответствующими пометками при дугах, то, напри-
мер, для множества слов
1) МАМА,
2) МАШИНА,
3) ШИНА,
4) МАТ,
5) НА
10.6. ПЕРЕХОД ОТ ДАННЫХ К КОНЕЧНОМУ АВТОМАТУ 627
М
0. ’\n’ 111 1
А
1. 2 15
М
2. 3 0
А
3. 4 6
4. 5 0
Ш
5. <1> 3 -
И
6. 7 14
Н
7. 8 0
А
8. 9 0
9. 10 0
10. <2> 11 —
628 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
‘\n’
М А М А
1
Ш И Н А
2
3
5
Т
4
Ш И Н А
3
5
Н А
5
11. <3> 12 —
Т
12. <5> 1 —
13. 14 0
Ш
14. <4> 1 —
И
15. 16 19
Н
16. 17 0
А
17. 18 0
Н
18. 11 0
А
19. 20 0
20. 12 0
21 ∀ 0 —
s = getchar ();
i = 1;
for (;;) {
if (Table[i].Tag) {
if ( s != ’\n’)
s = getchar ();
else return;
}
}
else {
Final_Reaction(); // Распечатка M
}
630 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
‘\n’
a1 ... am E
k+1
E
который рассматривается как заготовка для пополнения графа G;
(b) для каждого слова α из {α1 , α2 , . . . , αk } ищутся такие β, γ 6= ε, δ
и ξ, что α = βγδ, αk+1 = γξ и (δ = xδ 0 &ξ = yξ 0 ⇒ x 6= y). В
графе G есть фрагменты, отвечающие за распознавание γ. Следо-
вательно, надо склеить заготовку с каждым из таких фрагментов,
т. е. вставить вертикальную дугу от вершины x или ее вертикаль-
ных преемников x1 , . . . , xi к остатку заготовки:
632 ГЛАВА 10. МЕТОДЫ ПРОГРАММИРОВАНИЯ ОТ СОСТОЯНИЙ
x ... x ...
... ⇒ ...
Xi ... Xi ...
E
y ... am E
k+1
***
10.6. ПЕРЕХОД ОТ ДАННЫХ К КОНЕЧНОМУ АВТОМАТУ 633
634
11.1. МЕХАНИЗМЫ РЕКУРСИИ 635
сальную схему.
Определение 11.1.1. Пусть Σ — набор данных, обрабатываемых при вы-
полнении некоторого действия ∆(Σ). Σ рассматривается как обстановка вы-
числений действия ∆ (см. п. 7.3.5 и § 8.3), и, таким образом, она может включать
как входные данные, так и результаты вычислений: ∆In ∪ ∆Out (∆In ∩ ∆Out = ∅
не обязательно выполнено). Если даны наборы данных Σ1 , . . . , Σn , действия
∆1 , . . . , ∆n , и схема программ Ω над ∆1 (Σ1 ), . . . , ∆n (Σn ), с входными данны-
ми ∆In и результатами ∆Out , такая, что:
• выполнение Ω приводит к выполнению ∆(Σ) в целом;
• каждое из ∆i является либо атомарным, либо в свою очередь описано
через декомпозицию;
• среди ∆1 , . . . , ∆n или среди реализующих их схем представлено один
или более раз ∆;
то ∆ называется рекурсивным, а Ω называется рекурсивной схемой программ
для ∆(Σ). Ω называется также декомпозицией ∆(Σ), а ∆1 (Σ1 ), . . . , ∆n (Σn ) —
базисом этой декомпозиции.
Конец определения 11.1.1.
Применение этого определения требует конкретизации большинства затра-
гиваемых им понятий. Во-первых, схема Ω здесь не обязательно схема про-
грамм в классическом смысле. Она может включать и совместные, и недетер-
минированные, и параллельные действия. Во-вторых, отнюдь не все элемен-
ты обстановки могут представляться реальными программными объектами,
они могут (и часто должны быть) призраками.
Рассмотрим пример. Если пополнить входные данные программы всеми
их возможными комбинациями при помощи имеющихся функций (постро-
ить т. н. сколемовский универс), то можно считать все Σi подмножествами
Σ. Но такое разбиение данных, хотя полезно для анализа программы, в боль-
шинстве случаев остается лишь концептуальным, и нет нужды строить его
явно: это не только нецелесообразно, но зачастую и просто невозможно. Бо-
лее того, если явное построение осуществимо, а не остается призраком, то
именно это свидетельствует о том, что, по-видимому, в данном случае более
разумно итеративное решение, которое не нуждается дополнительной памя-
ти рекурсивного вычисления.
В то же время, необходимо научиться выстраивать последовательность
данных (наборов данных), которая будет динамически предъявляться ∆1 , . . . ,
11.1. МЕХАНИЗМЫ РЕКУРСИИ 637
Σ0 Σ1j1 · · · Σkjk · · ·
ϕ(Σkjk ) ϕ(Σk+1
j(k+1) ).
∆1 (Σm m
1 ), . . . , ∆n (Σn ),
ние — это просто набор точек растра, называемых пикселями, которые раз-
личаются местоположением на экране и цветом.
Экранная область представляет собой связную группу пикселей, т. е. та-
кую, в которой между любыми двумя пикселями можно найти путь, целиком
лежащий в данной области. Соответственно, путь — последовательность
пикселей πi , таких, что πi и πi+1 — соседние. Области выделяются либо об-
щим цветом своих пикселей, и тогда они называются внутренне определен-
ными, либо цветом границы — линии, составленной из одноцветных сосед-
них пикселей, и тогда они называются гранично определенными. Чтобы эти
определения стали корректными, необходимо указать, как понимается тер-
мин “соседние пиксели”. Различаются 4-связное соседство и 8-связное со-
седство. В первом случае соседними считаются такие пиксели, у которых
либо горизонтальная, либо вертикальная координата отличаются на едини-
цу, во втором — такие, у которых обе координаты различаются не более чем
на 1 (допускается диагональное соседство).
В условиях приведенных определений задача закраски области ставит-
ся следующим образом. Дана замкнутая область и известны координаты ка-
кого-либо ее пикселя. Требуется, чтобы все пиксели области и только они
оказались закрашены одним цветом. Дополнительным условием для данной
задачи является то, что не делается никаких предположений относительно
формы закрашиваемой области.
Стратегия закраски внутренне определенной области, которую можно пред-
ложить для решения задачи, заключается в следующем. Для каждого пиксе-
ля, начиная с известного, проверяется его цвет. Если цвет старый, он заменя-
ется на новый и делается попытка запустить этот алгоритм рекурсивно для
каждого из соседей. Для гранично определенных областей выбор этих же
действий осуществляется по результату проверки несовпадения цвета пик-
селя с граничным цветом, дополненному отказом от перехода к соседу, если
очередной пиксель оказался уже перекрашенным. Понятно, что запуск алго-
ритма для соседей нужно осуществлять четыре раза в случае 4-связного и
восемь раз для 8-связного соседства.
Ниже приводится процедура закраски внутренне определенной области
при 4-связном соседстве. Остальные варианты предлагается запрограмми-
ровать читателю самостоятельно в качестве упражнения. Чтобы не привязы-
ваться к какой-либо графической библиотеке, в представленном алгоритме
вместо конкретных функции выявления и процедуры задания цвета пикселя
11.2. ЗАКРАШИВАНИЕ ЗАМКНУТЫХ ОБЛАСТЕЙ 643
if ЧитатьЦветПикселя ( x, y ) = OldColor
begin
ПисатьЦветПикселя ( x, y, NewColor );
then begin
end
end;
/* C */
ПисатьЦветПикселя (x,y,NewColor);
if (ЧитатьЦветПикселя (x,y) == OldColor) {
2
Чтобы подчеркнуть их условность, они записаны на русском языке.
644 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
}
}
А А
При решении данной задачи мы смогли убедиться в том, что то или иное
выстраивание разбиений данных приводит к различным алгоритмам. Так, ко-
гда для выбора рекурсивного вызова мы ограничивались лишь соседями, это
приводило к неоправданно большой глубине рекурсии, а когда мы перешли
к рассмотрению направлений, появилась опасность некорректного решения.
Сложность разбиения действий для выбранного разбиения данных различна.
Для простого соседства ничего, кроме явного указания нужных параметров и
совместного (а не последовательного, как приходится делать, используя тра-
диционные языки!) выполнения четырех рекурсивных действий, не требова-
лось. Пожалуй, это самый простой случай рекурсии. Следующий, чуть более
сложный, класс случаев был рассмотрен, когда мы сопоставляли рекурсив-
ную и итеративную схемы (см. п. 8.7.1). Здесь вся сложность прямо связано
со сложностью и с избыточностью рекуррентного соотношения.
Принципиально более сложными являются рекурсивные алгоритмы, ко-
торые используются для перебора и генерации вариантов. Методы организа-
ции таких программ обсуждаются в следующем параграфе.
ребора — это цикл, каждая итерация которого выделяет вариант данных для
конкретной обработки. Вложенные циклы позволяют выделять все пары, трой-
ки и тому подобные группы данных. Так, для массива A [1..N] в цикле
for i := 1 to N do
for j := 1 to N do
for k := 1 to N do . . .
C1 C1
1
E 2 F C2 E 12 F
… …
B C2 B
D D
3 3
… …
C3 C3
A A
… …
… …
I II
A, B, C2 , D
A, B, C1
ские) таблицы для классической логики высказываний являются недетерминированным ал-
горитмом проверки ывысказываний, приводящим к результату при любой последовательно-
сти действий. Но Вы могли сами убедиться на практике, насколько сильно зависят размер и
сложность структуры строения таблицы от избранного порядка действий.
11.3. ПЕРЕБОРНЫЕ АЛГОРИТМЫ И РЕКУРСИЯ 649
A, B, C2 , D, E
— текущая последовательность, а
A, B, C2 , E, F
1
2
3
4
41
42
43
44
441
...
Программа 11.3.2
Начало
Окончания
ie-ый элемент
2 1
2 2
1
2
2 1
2 2
2 1
2 2
for k := k to N do
begin
V [Ie] := k;
Handle ( V, Ie );
end;
Программа 11.3.4
procedure SelectF1( Ie, k : Integer);
begin
if not (k in Ma) then
begin
Ma := Ma + [k];
V [Ie] := k;
Handle ( V, Ie );
if Ie < N then SelectF( Ie + 1, 1 );
Ma := Ma - [k];
end
if k < N then SelectF( Ie, k + 1 );
end;
Проверка, добавление и удаление элемента множества — быстрые операции
при представлении малых множеств (см. п. 9.3.5), принятом в языке Pascal.
Поэтому получается практически чистый выигрыш в эффективности, и во
многих случаях такое прямое преобразование можно рекомендавать. Если
это не так, то требуется более точно (или порою более грубо, но более про-
сто; необходимо соблюдение баланса между сложностью проверки и слож-
ностью повторных вызовов) определить порядок кортежей, не имеющих по-
вторений. Использование множеств может быть рекомендовано для решения
еще одной переборной задачи, подобной только что рассмотренной: для по-
строения множеств неповторяющихся индексов (упражнение 3).
Все приведенные решения (а также другие решения, оставленные для
самостоятельного рассмотрения) демонстрируют ситуацию, в которой кон-
тексты рекурсий содержат существенную долю вычисляемых результатов.
Именно здесь обычно хорошо работает метод перебора/генерации. В сущно-
сти он представляет собой неявное задание отношения полного порядка на
данных, который согласован с естественным частичным порядком. То, что
такое доопределение чаще всего не является однозначным, понятно. Резуль-
таты решения, а также его эффективность обычно существенно зависят от
того, как именно произведено доопределение порядка.
Представленные программы корректны с точки зрения конечности вычи-
слений. Однако их анализ показывает, что все они не являются сильно кор-
ректными, и, соотвественно, вызывают повторный счет. Это хорошо видно из
схемы на рис. 11.4: окончания кортежей для разных значений ie-того элемен-
та оказываются одинаковыми. Для данной задачи повторный счет не очень
11.3. ПЕРЕБОРНЫЕ АЛГОРИТМЫ И РЕКУРСИЯ 657
Начало Общие
1 окончания
ie-ый элемент
type PL =^TL;
TL = record inf : Integer;
down, right : PL;
end;
где inf — поле для размещения индекса, а down и right — поля ссылок на
следующий элемент в вертикальном и горизонтальном направлениях. Пере-
менная, которая будет указывать на список в целом, описывается как:
Программа 11.3.5
Sta
2 2 … 2
… … … …
N N … N
с графами.
Структура получившегося графа решений вполне регулярна, и можно го-
ворить о построении ее иными методами, нежели те, что были применены
выше. Разумеется, это можно (и нужно!) было выяснить на этапе анализа
задачи, который устанавливает принимаемый метод решения. Но здесь хо-
телось бы подчеркнуть другое. Если сравнить различные решения задачи с
точки зрения того, как вовлекаются данные в вычисления, то становится яс-
но, что структура графа отражает именно эти потоки данных, т. е. фиксирует
в себе все допустимые с точки зрения задействованных алгоритмов порядки4
доступа к данным.
Каждый из путей из начальной вершины графа соответствует одному из
искомых решений. Ацикличность построенного графа позволяет при постро-
ении решений не заботиться о контроле длин проходимых путей. Если отка-
заться от использования этого свойства, то можно еще раз произвести склей-
ку графа по вертикальному направлению (см. рис. 11.7а). Поиск решений для
нового графа может осуществляться той же программой, которая применима
для графов обоих прежних видов (без учета и с учетом совпадений оконча-
ний), если только она контролирует длины проходимых путей. Это принци-
пиально, т. к. новый граф перестает быть ациклическим.
В списочном представлении нового графа оказалось, что все ссылки на-
право (поле right) имеют одно и то же значение. Зачем же тогда хранить их
в каждом элементе? Очевидный ответ на этот вопрос приводит к тому, что
требуемую списочную структуру целесообразно преобразовать в простой по-
следовательный список (см. рис. 11.7б). Прежняя программа для его обработ-
ки не годится, но из нее легко (даже механически!) можно построить новую
программу, соответствующую измененной структуре данных.
Содержимое элементов этого списка для решаемой задачи соответствует
следованию чисел отрезка натурального ряда, записанного в поле Inf. Поэто-
му сам такой список становится избыточным: вместо его просмотра более
эффективно реализовать непосредственное оперирования с этим отрезком.
А это возврат к тому, что было реализовано в первом или втором решении,
которые вовсе не используют граф. Впрочем, теперь они оказались упрятан-
ными в программе обработки (Handle).
Круг замкнулся. Приведенными построениями мы показали, что для ре-
В данном случае это слово практически является термином: поток данных задает частич-
ный порядок на них, фиксирующий разрешение доступа к данным: чтодбы данное a стало
4
Sta Sta’
1 1
2 2
… …
N N
Рис. 11.7. Два вида графа решений: (а) — список со склеенными вертикалями
и (б) — список-последовательность.
§ 11.4. ЛАБИРИНТ
31 30
23 22
1 2 3 4 5 6 7 8
15 14
32 29
24 7 6 21
16 13
8 5
1 4
9 12
2 3
17 20
25 28
10 11
18 19
26 27
б) Лабиринты с разметкой
{ представление комнат }
Row : TmaxY;
{ X, Y по направлению dir }
begin
case dir of
Lf : isWay := Rooms [ X - 1, Y ].Rt;
Rt : isWay := Rooms [ X, Y ].Rt;
Up : isWay := Rooms [ X, Y - 1 ].Dn;
Dn : isWay := Rooms [ X, Y ].Dn;
end
end;
Программа 11.4.2
const p = 0.5;
procedure ClearRooms;
var Col, Row : Integer;
begin
for Col := 0 to mX do
for Row := 0 to mY do
begin Rooms [ Col, Row ].Rt := True;
Rooms [ Col, Row ].Dn := True;
Rooms [ Col, Row ].Inf := 0;
end;
bX := 0; eX := 0;
bY := 0; eY := 0;
end;
procedure InitRooms;
var r : Real;
Col, Row : Integer;
begin
ClearRooms;
for Row := 1 to mY do
begin for Col := 1 to mX do
begin r := Random;
Rooms [ Col, Row ].Rt := r > part;
r := Random;
Rooms [ Col, Row ].Dn := r > part;
end;
Rooms [ 0, Row ].Rt := False;
Rooms [ 0, Row ].Dn := True;
Rooms [ mX, Row ].Rt := False;
end;
for Col := 1 to mX do
11.4. ЛАБИРИНТ 673
function FindWay1 (bX: TMaxX; bY: TMaxY; eX: TMaxX; eY: TMaxY): Boolean;
begin
if ( bX = eX ) and ( bY = eY )
then FindWay1:= True
else if isWay ( bX, bY, Lf )
then FindWay1:= FindWay ( bX - 1, bY, eX, eY )
else if isWay ( bX, bY, Rt )
then FindWay1:= FindWay ( bX + 1, bY, eX, eY )
else if isWay ( bX, bY, Up )
then FindWay1:= FindWay ( bX, bY - 1, eX, eY )
else if isWay ( bX, bY, Dn )
then FindWay1:= FindWay ( bX, bY + 1, eX, eY )
else FindWay1:= False
end;
• MXY — число, большее MaxXY, отмечает тот факт, что комната уже
посещалась (использование в качестве пометки особой ситуации зна-
чения, выходящего за диапазон осмысленных с точки зрения решения
задачи — достаточно стандартный технический прием).
варианта при правильном переходе (без прохода через закрытую дверь и без
повторов) в новую комнату. Если таких комнат несколько, то все следующие
комнаты могут быть упорядочены произвольно. Для определенности далее
выбирается такой порядок перебора: сначала проверяется возможность идти
налево, затем направо, вверх и, наконец, вниз. Если следующих вариантов
нет, то текущий вариант тупиковый, и нужно вернуться в предыдущую ком-
нату, чтобы попытаться выйти из нее в другую следующую комнату.
Для старого абстрактного представления каждый возможный вариант —
это значение построенной последовательности пар индексов (массив Way
и переменная Lway), начальным значением которой является одноэлемент-
ная последовательность с индексами, указывающими на начальную комнату.
Следующие варианты строятся из текущего после проверки, в какие сосед-
ние комнаты можно пройти, в сочетании с упорядочиванием выбора очеред-
ной следующей комнаты, если их несколько (аналогично предыдущему). Во-
прос зацикливания решается за счет выставления ненулевых значений атри-
бута Inf элементов массива Rooms для комнат, которые уже посещались.
Нужно заметить, что стратегия преодоления зацикливания в принципе
может быть различна и, в частности, не обязательно связывать ее с помет-
ками комнат. Необходимо лишь позаботиться о том, чтобы каждый выстраи-
ваемый путь не пересекался. Для этого достаточно проверять тем или иным
способом, что в очередном варианте комната, намеченная для перехода, уже
представлена в текущем пути (в старом абстрактном представлении это легко
сделать, проверяя массив Way). Однако при таком решении окажется, что не-
которые из ком-нат будут посещаться неоднократно, поскольку информация
о том, что все пути из данной комнаты тупиковые, не запоминается. Именно
поэтому предпочтительнее оставлять пометки (ненулевые значения атрибу-
та Inf элементов массива Rooms) для комнат, которые уже исследованы. Это
решение верно для статических лабиринтов, т. е. таких, у которых состояние
открытия и закрытия дверей не меняется. В противном случае отмечать ту-
пиковые комнаты бесполезно (впрочем, если удастся ввести понятие полно-
стью исследованной комнаты, разумеется, зависящее от правил открывания
и закрывания дверей, то пометки таких комнат могут оказаться целесообраз-
ными).
Прямое решение задачи блуждания связывается с тем, что при переходе
от варианта к варианту текущее состояние программы запоминается полно-
стью. Все, что изменяется в ходе работы с вариантом, восстанавливается при
отказе от него. Иными словами, предлагается запоминать текущие значения
переменных абстрактного представления. Проще всего это делать через па-
678 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
Программа 11.4.4
Rslt := FindWay2(bX+1,bY,eX,eY,Lway+1,Rooms);
FindWay2 := Rslt
end;
if Rslt then exit;
if isWay ( bX, bY, Up ) and ( Rooms [ bX, bY - 1 ] . inf = 0 )
then begin Rooms [ bX, bY - 1 ] . inf := Lway + 1;
Rslt:= FindWay2(bX,bY-1,eX,eY,Lway+1,Rooms);
FindWay2 := Rslt
end;
if Rslt then exit;
if isWay ( bX, bY, Dn ) and ( Rooms [ bX, bY + 1 ] . inf = 0 )
then begin Rooms [ bX, bY + 1 ] . inf := Lway+ 1;
Rslt := FindWay2(bX,bY+1,eX,eY,Lway+1,Rooms);
FindWay2 := Rslt
end
end;
end;
или false в зависимости от того, есть или нет путь между начальной и ко-
нечной комнатами. Здесь восстановление пути как последовательности ин-
дексов вершин потребовало бы на самом внутреннем рекурсивном вызове
осуществить обход массива Rooms, что явно избыточно. При использовании
абстрактного представления с массивом Way построенный путь запоминал-
ся бы, но и в этом случае понимаемая буквально установка на запоминание
всех переменных состояния программы привела бы к аналогичному резуль-
тату: возможность работы с найденным путем была бы ограничена самым
внутренним рекурсивным вызовом. Это мешает разграничению оперирова-
ния с абстрактным и конкретным представлениями.
Таким образом, желательно выделить в запоминаемом состоянии то, что
целесообразно вынести на уровень общего контекста, доступного каждому
рекурсивному вызову. Решению этой задачи способствует следующее наблю-
дение. Нет нужды передавать от вызова к вызову массив Rooms. Вместо этого
можно выставлять пометку исследуемой и уже исследованной комнаты не-
посредственно в Rooms общего контекста. При выяснении того факта, что из
комнаты нельзя выйти к цели ее вместо значения номера шага текущего пути
ее атрибуту Inf следует присвоить признак исследованности:
Это решает все проблемы абстрактного представления без массива Way. Тем
не менее, следует проанализировать и другие возможности минимизации объ-
ема запоминаемого состояния. Уже отмечалось, что включение переменных
eX и eY в запоминаемое состояние сделано для повышения алгоритмической
гибкости программы. Что касается Lway, то эту переменную можно также не
передавать между рекурсивными вызовами, но тогда переход от шага к ша-
гу нужно смоделировать: увеличивать Lway на единицу перед каждым вы-
зовом и уменьшать ее после вызова. Считать ли такую модификацию целе-
сообразной, зависит от глубины рекурсии. Если исследуемые лабиринты не
очень велики, то накладными расходами (требование к памяти), связанны-
ми с запоминанием, можно пренебречь, поскольку дополнительные вычи-
сления, пусть и весьма незначительные (как в данном случае) — это допол-
нительное время выполнения программы.
Окончательный алгоритм поиска пути в лабиринте представлен следую-
щей рекурсивной функцией.
Программа 11.4.5
11.4. ЛАБИРИНТ 683
procedure REV;
var S : Char;
begin
if not Eof
then begin
Read (S); { * }
REV; { ** }
Write(S); { *** }
end
end;
если A = ε;
(
R(A) = ε,
R(xA) = R(A)x, если A 6= ε.
11.5. РЕКУРСИЯ ПРИ ОБРАБОТКЕ СИМВОЛЬНОЙ ИНФОРМАЦИИ 685
а тексты
“([<()>])([])”,
“((”,
686 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
“([)]”,
“asd((ill0)[q]”,
“(ро+i<l><[]>(123)”,
не сбалансированы.
“)(”
Программа 11.5.3
E ::= M { + E | - E }* (1)
M ::= T { * M | / M }* (2)
T ::= I|(E) (3)
I ::= { a | b | c | . . . | z }+ (4)
Программа 11.6.1
program PARSER;
var S : Char;
696 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
procedure Scan;
begin
Read ( S ); Write (’ ’);
end; Scan
function E : Boolean;
var f : Boolean;
begin
if M
then begin
f := true;
while f and ((S = ’+’) or (S = ’-’)) do
begin
Scan; f := E
end;
E := f;
end
else E := false
end; {E}
function M : Boolean;
var f : Boolean;
begin
if T
then begin
f := true;
while f and ((S = ’*’) or (S = ’/’)) do
begin
Scan; f := M
end;
M := f;
end
else M := FALSE
end; {M}
function T : Boolean;
11.6. РЕКУРСИЯ В ТРАНСЛИРУЮЩИХ ПРОГРАММАХ 697
begin
if I
then T := true
else if (S = ’(’)
then begin
Scan;
if E
then if S = ’)’
then begin Scan; T := true
end
else T := false
end
else T:= false
end; {T}
function I : Boolean;
var f : Boolean;
begin
f := S in [’a’..’z’];
while S in [’a’..’z’] do Scan;
I := f
end; {I}
procedure P;
698 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
begin
. . . Q; . . .
end;
procedure Q;
begin
. . . P; . . .
end;
заменяется на
<левый операнд> <правый операнд> <знак операции>.
Программа 11.6.2
program POLIS;
var S : Char;
R : String;
function E : Boolean; forward;
function M : Boolean; forward;
function T : Boolean; forward;
function I : Boolean; forward;
procedure Scan;
702 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
begin
Read ( S ); Write (’ ’);
end; {Scan}
function E : Boolean;
var f : Boolean;
op: char;
begin
if M
then begin
f := TRUE;
while f and ((S = ’+’) or (S = ’-’)) do
begin
op := S;
Scan; f := E;
R := R + op + ’ ’
end;
E := f;
end
else E := FALSE
end; {E}
function M : Boolean;
var f : Boolean;
op: char;
begin
if T
then begin
f := TRUE;
while f and ((S = ’*’) or (S = ’/’)) do
begin
op := S;
Scan; f := M;
R := R + op + ’ ’
end;
M := f;
end
else M := FALSE
704 ГЛАВА 11. МЕТОДЫ, ОСНОВАННЫЕ НА РЕКУРСИИ
end; {M}
function T : Boolean;
begin
if I
then T := TRUE
else if (S = ’(’)
then begin
Scan;
if E
then if S = ’)’
then begin Scan; T := TRUE
end
else T := FALSE
end
else T:= FALSE
end; {T}
function I : Boolean;
var f : Boolean;
begin
f := S in [’a’..’z’];
while S in [’a’..’z’] do
begin
R := R + S;
Scan;
end;
if f
then R := R + ’ ’;
I := f
end; {I}
begin Writeln;
Writeln (’OK’);
R := R + ’|’;
end
else
begin Writeln (’^’);
Writeln (’ERR’)
end;
Writeln (R);
Readln;
end.
Здесь Вы сталкиваетесь с частым в практике случаем, когда точная оценка качества ре-
шения бессмысленна, оно будет оцениваться по неформальным критериям, в соответствии
9
Объектно-ориентированный
подход
§ 12.1. ОБЪЕКТЫ
708
12.1. ОБЪЕКТЫ 709
Внимание!
В C++ и C# значения и методы записи, никак не специфициро-
ванные, считаются по умолчанию открытыми, но и в этих языках,
и в Java, и в Object Pascal значения и методы класса по умолча-
нию считаются закрытыми!2
Программа 12.1.1
{ Object Pascal }
TBits = class(TObject)
private
FSize: Integer;
FBits: Pointer;
procedure Error;
procedure SetSize(Value: Integer);
procedure SetBit(Index: Integer; Value: Boolean);
function GetBit(Index: Integer): Boolean;
public
constructor Create; override;
destructor Destroy; override;
function OpenBit: Integer;
property Bits[Index: Integer]: Boolean read GetBit write SetBit; default;
property Size: Integer read FSize write SetSize;
end;
В одном из стандартных пакетов Visual C++ версии 6 имеется комментарий: «Данный тип
описан как структура, а не как класс, чтобы не писать многочисленные public.»
2
12.1. ОБЪЕКТЫ 711
...
implementation
...
constructor Tbits.Create;
begin
inherited Create;
Fsize:=0;
Fbits:=nil;
end
...
procedure TBits.Error;
begin
raise EBitsError.CreateRes(SBitsIndexError);
end;
TheBits.Bits[i]:=true;
модуля.
{Object Pascal}
end;
/* C++ */
(12.1)
{ Тело описания класса };
class CRoleOfActor:: public CRole
/* Java */
Это означает, что новый класс CRoleOfActor является наследником ранее опре-
деленного класса CActor, а он его предками. Отношение наследования тран-
зитивно, так что CRoleOfActor является также наследником любого из пред-
ков CActor4 . Наследник включает все данные и все методы предков. Наслед-
ник базируется именно на классах-предках, а не на конкретных объектах
этих классов, так что при создании наследника ссылаться на ранее создан-
ные объекты классов-предков, как правило, не приходится. Далее, скобки в
определении класса являются не ограничителем параметров, а скорее опре-
делением отношения порядка на множестве классов. Математический смысл
В Turbo Pascal и Object Pascal не имеет предков лишь один, предопределенный в системе,
класс TObject. Если предки класса не указаны, то он считается прямым наследнком TObject,
4
Hermit = сlass
Hermit = сlass(TObject)
эквивалентны.
12.1. ОБЪЕКТЫ 715
заменить на
typedef Concept_Realization = Abstract_Realization;
/* C++ */
Это означает, что новый класс CRoleOfActor является наследником сразу че-
тырех ранее определенных классов: CActor, CRole, CState, CAnimation, а все
они — его предками. Математический смысл (12.4) можно выразить следу-
ющей системой формул:
CRoleOfActor CActor
CRoleOfActor CRole
(12.5)
CRoleOfActor CState
CRoleOfActor CAnimation
Так что возможны все присваивания вида
TheActor := TheRoleOfActor;
TheRole := TheRoleOfActor;
(12.6)
TheState := TheRoleOfActor;
TheAnimation := TheRoleOfActor;
type
IMalloc = interface(IInterface)
[’{00000002-0000-0000-C000-000000000046}’]
function Alloc(Size: Integer): Pointer; stdcall;
function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;
procedure Free(P: Pointer); stdcall;
12.1. ОБЪЕКТЫ 721
CRoleOfActor = class(CRole,IActor,IState,IAnimation)
...
end;
/* Java*/
class CRoleOfActor extends CRole implements IActor,IState,IAnimation;
{. . . };
Заметим, что возникают те же самые проблемы с двусмысленностью, что
и при множественном наследовании в C++. Способы их разрешения в Java и
в Object Pascal умилительно расходятся. В Java сигнала ошибки не выдает-
ся, Вы можете пользоваться несовместимыми интерфейсами, но если Ваша
программа будет иметь глупость обратиться к неоднозначно понимаемому
методу или константе, произойдет ошибка. В Object Pascal Вы можете про-
сто назвать по-другому метод, примененный для реализации объявленного в
интерфейсе:
function IActor.Visualize = DrawFace;
procedure IAnimation.Visualize = PlayAnimation;
Решение, включающее интерфейсы, не дает возможности отождествить
подобъекты интерфейсов, но это и не нужно: ведь теперь не предки предо-
ставляют свои объекты и методы потомку, а, наоборот, все интерфейсы ссы-
лаются на один и тот же реализующий их объект, который и поддерживает
единство данных.
722 ГЛАВА 12. ООП
для очередей «разных типов» с содержимым одного типа, то для этого придется воспользо-
ваться более развитыми объектно-ориентированными средствами, чем необходимо для по-
яснения модуляризации.
724 ГЛАВА 12. ООП
unit AllQueue_5_5;
BEmpty : Boolean;
NotEl : PElemContainer;
constructor InitQueue ( InputMaxSize : Integer);
procedure PutElQueue ( X : PElemContainer );
function GetElQueue : PElemContainer;
function StateQueue : States;
procedure IncI ( var I : Integer );
destructor DoneQueue;
end;
unit AllQueue_7_0;
Pqueue = ^Tqueue;
Tqueue = object ( Container );
private
MaxSize,
IB,
IE : Integer;
BFull,
BEmpty : Boolean;
NotEl : PElemContainer;
public
constructor InitQueue ( InputMaxSize : Integer);
procedure PutElQueue ( X : PElemContainer );
function GetElQueue : PElemContainer;
function StateQueue : States;
private
procedure IncI ( var I : Integer );
public
destructor DoneQueue;
end;
...
пов, используя “^” для извлечения объекта по указателю. Таким образом, наш
пример изменится лишь в строках, описывающих класс объектов (по-старо-
му, объектный тип).
В последних примерах мы приводили описание только раздела интерфей-
са, поскольку реализация практически повторяет то же, что было написано в
программе 8.6.1, с учетом синтаксических модификаций, требуемых описа-
нием класса.
В языке Java возникает противоположная языкам линии Pascal задача.
Модуль является классом. Класс может быть создан и уничтожен. А если нам
этого не нужно, нужно лишь воспользоваться понятиями из данного класса?
Для этой цели в Java предлагается следующее решение. Описать лишь один
конструктор без параметров и объявить его private. Тем самым он не сможет
быть использован снаружи модуля, где описывается класс, и класс превра-
щается в модуль.
Сентенциальные методы
728
13.1. КОНКРЕТИЗАЦИЯ 729
§ 13.1. КОНКРЕТИЗАЦИЯ
Поле зрения выделено в поле памяти жирным шрифтом. Заметим, что в по-
AllRight Sort Perm ’QRTS’>’XZ<(’
< >
A B ( ) ( ) F
C D E
и объектное выражение
Правила отображения
1 2 5 4 3 6
’A’ ( e.1 t.2 ) s.3
Конец алгоритма
Этот сложный алгоритм упрятан для программиста в простые программ-
ные конструкции. Программа на Рефале состоит из последовательности опре-
делений функций. Относительное расположение членов этой последователь-
ности никакой роли не играет, и функции можно группировать из логических
или технологических соображений. Каждое описание функции в базисном
Рефале имеет вид
Имя функции {Последовательность сопоставлений}
Каждое сопоставление имеет вид
Образец = Метавыражение;
Пример 13.1.4. Рассмотрим пример Рефал-программы.
738 ГЛАВА 13. СЕНТЕНЦИАЛЬНЫЕ МЕТОДЫ
Before {
s.1 s.2 In e.A s.1 e.B s.2 e.C = T;
e.Z = F; }
Alphabet {
= ’abcdefghijklmnopqrstuvwxyz’; }
<Go >
$ENTRY Go{=<Init>;}
Непарные скобки
<Brackets>: e.A(s.Lbrack1 s.Rbrack ) e.B =
*
<Prout "Brackets Mismatch!"> Error;
(e.Unclosed ) s.Lbrack1 e.Last,
§ 13.2. УНИФИКАЦИЯ
соперника Рефала.
13.2. УНИФИКАЦИЯ 749
подстановкой
(13.10)
А в следующей формуле
логических связок.
13.2. УНИФИКАЦИЯ 753
parent(X,Y) :- mother(X,Y).
parent(X,Y) :- father(X,Y).
father(ivan,vasilij):-true.
:- op(1200,xfx,’:-’).
:- op(1200,fx, [’:-’,’?-’]).
:- op(1000,xfy,’,’).
:- op(700, xfx,[=,is,<,=<,==]).
:- op(500, yfx,[+,-]).
:- op(500, fx, [+,-,not]).
:- op(400, yfx,[*,/,div]).
Внимание!
greater(X,Y):-greater1(X,Y).
greater(X,Y):-greater1(Z,Y),greater(X,Z).
greater1(X,f(X)).
estimation(X,Y):-greater(X,Y),known(Y).
known(f(f(f(f(a))))).
unknown(a).
unknown(b).
то ответом на запрос
будет
?-unknown(Y),estimation(Y,X).
Y=a
X=(f(f(f(f(a))))
Yes
программа зациклится.14
Насколько коварны вроде бы невинные предположения, сделанные в язы-
ке PROLOG, видно из того, что при логически эквивалентной переформули-
ровке одного из предложений программы
way(X,X,[X]).
way(X,Y,[Y|Z]):-connect(U,Y), nomember(Y,Z),way(X,U,Z).
way(X,Y,[Y|Z]):-connect(U,Y), way(X,U,Z).
nomember(Y,Z):-member(Y,Z),!,fail.
nomember(Y,Z).
connect(begin,1).
connect(1,begin).
connect(1,2).
connect(2,3).
Если Вы проверяете данные примеры на системе SICS Prolog, то некоторые из них могут
вести себя менее гадко, поскольку ее алгоритм управления несколько более интеллектуален.
14
connect(3,1).
connect(3,4).
connect(4,end).
В ответ на запрос
программа выдаст
?-way(begin,end,X).
X = [end, 4, 3, 2, 1, begin]
Yes
repeat.
repeat:-repeat.
сал свою программу таким образом, что она стала изоморфной программе в обычном языке
программирования, зато быстро ее отладил.
Здесь мы не оговорились: в данном случае предложения лучше трактовать как операторы,
поскольку как утверждения их в данном случае рассматривать невозможно.
16
13.2. УНИФИКАЦИЯ 759
A x Sx
S2 x …
S1 a Sz
ε
… Sx S12 a z
… Sa … Sa
z
c Sz
Sc c
Sc
B
a S2 x
S1
… S2x a S23 x
S1
z … S2x
a S2z
z z
… x S2z
S3 x
… S3x S3c S3x
z
S3c
Некоторые из них совпадают
а, как уже говорилось, те, кто делают что-либо принципиально новое, почти
всегда забывают оглянуться кругом и согласовать свою находку с другими
принципиальными достижениями того же времени, предпочитая локализо-
вать новизну и в остальных пунктах работать как можно более традицион-
но). Грехи и беды начались, когда особенности конкретного упорядочивания
стали беспощадно использоваться в хакерском духе да еще и выставляться
как принципиальные новации.
Теорема про детерминирование конечного автомата дает лишь уверен-
ность в существовании успешного вычисления. Но она не дает никаких хоро-
ших оценок изменения сложности вычислений при детерминировании поис-
ка. Другой негативный аспект: даже если успешное вычисление существует,
это не означает, что легко трансформировать ассоциированные с переходами
действия и что после трансформации они будут хоть сколько-нибудь пони-
маемы. Поэтому часто удобнее обработку описывать как недетерминирован-
ную, поручая решение задачи детерминирования, или, что то же, перебора
вариантов системе программирования. Скорее всего, впервые эта идея полу-
чила языковые формы в языке PLANNER. PLANNER построен чисто прагма-
тически на базе FORTRAN и LISP для решения задач искусственного интел-
лекта. Не привязываясь к особенностям синтаксиса этого языка, детермини-
рование-перебор в нем можно описать следующей схемой:
backtracking: non-deterministic case
<вариант1>;
...
<вариантk>
end-case
дами, реализовал эту идею несравненно лучше. Здесь сработал еще и тот
эффект, что PLANNER не успел декларировать блестящие успехи17 и осуще-
Насколько они реальны, в подобных случаях уже второй вопрос. Главное: успеть прокри-
чать о своем успехе и загадить экологическую нишу.
17
762 ГЛАВА 13. СЕНТЕНЦИАЛЬНЫЕ МЕТОДЫ
way0(X,Y,Z):-consult(labyr),way(X,Y,Z).
way(X,X,[X]).
way(X,Y,[Y|Z]):-connect(U,Y), not member(Y,Z),way(X,U,Z).
way(X,Y,[Y|Z]):-connect(U,Y), way(X,U,Z).
connect(begin,1).
connect(1,begin).
connect(1,2).
connect(2,3).
connect(3,1).
connect(3,4).
connect(4,end).
TEX четко проводит основную идею языков разметки: команды языка раз-
TEX.
c) Атом, после которого в случае, если определение этого атома требует па-
раметров, идут соответствующие параметры, называется командой. Доба-
вление команды к обрабатываемому тексту дает обрабатываемый текст.
Внутри разных групп один и тот же текст может пониматься по-разному. Да-
же конкретный синтаксис атомов может быть переопределен!
Таким образом, структура текста носит подчеркнуто иерархический ха-
рактер. Иерархия перекрещивается с другими структурами. Прежде всего,
это структура результирующего текста.
В стандартном конкретном синтаксисе группировку задают, в частности,
символы {}. Имеется потенциально бесконечное число других пар скобок,
например, пара команд \begingroup и \endgroup, выполняющих ту же функ-
цию, что и фигурные скобки. Важнейшими и наиболее часто используемыми
скобочными структурами являются окружения. Окружения выделяются па-
рой команд, имеющих в конкретном синтаксисе чаще всего форму, подобную
следующей:
\begin{example} и \end{example}.
Некоторые окружения заданы в самом языке, а другие (как, например, при-
веденное только что) описываются программистом.
Далее, исключительно важными единицами структурирования являются
боксы, которым могут быть приписаны глобальные свойства, а внутри них
все форматирование и обработка осуществляется независимо от окружения.
Эта структура опускается вниз до результирующей последовательности ко-
манд (где уровни вложенности боксов отмечены точками в начале строк).
768 ГЛАВА 13. СЕНТЕНЦИАЛЬНЫЕ МЕТОДЫ
i. буквы,
ii. пробелы,
iii. переводы строки,
iv. другие символы;
где тег — имя, которое может быть распознано браузером и каким-то образом
трактовано им, <тег> и </тег> — открывающая и закрывающая скобки выде-
ленного содержания — элемента информационного наполнения (им может
быть как обычный текст, так и целая структура, составленная из аналогичных
Стоит заметить, что, хотя HTML чаще всего предоставляется серверами по HTTP, он также
может использоваться на CD-ROM или в локальной сети. Универсальные языки разметки не
22
Красный нормальный</FONT>
В примере не затронута такая сторона дизайна, как размещение текста на дву-
мерной плоскости экрана. А это еще один вид структурирования материала
для показа, который требуется наложить на другие структуры.
Практическая потребность одновременного оперирования с несколькими
структурами, одни из которых имеют отношение к содержанию материала,
а другие определяют форму его предъявления, в HTML решена с помощью
отказа от строгости языка. Взамен этого было достигнуто некоторое упро-
щение браузеров, которое ныне в связи с резко увеличившимися ресурсами
машин уже не является хоть сколько-нибудь актуальным. Но высокомерно
игнорировать принятое в HTML решение не стоит: оно впервые привлекло
внимание к тому, что обрабатываемые данные могут иметь полииерархи-
ческую структуру. Правда, в явном виде эта структура в HTML выделена не
была.
Более того, HTML рассматривался его создателями как ‘плоский’ язык,
т. е. его не предполагалось использовать для предоставления информации
об иерархии данных. Например, хотя большинство открывающих тегов (та-
кие, как <b> или <h1>) имеют соответствующие закрывающие теги, некото-
рые (например, <p>) их не имеют. Это лишь одна из непоследовательностей
статки HTML.
По сути, HTML — это лишь способ представления информации, он опи-
сывает то, как браузер должен скомпоновать текст, графики и другие элемен-
ты изображений на странице. В результате «то, что вы видите, — это все, что
вы получаете» (WYSIWYG)23 . Нет никакого способа описать данные неза-
висимо от отображения этих данных (за исключением чрезвычайно слабой
системы ключевых слов в заголовке страницы Web). В частности, это главная
причина, почему так трудно найти нужную информацию с помощью меха-
низма поиска. Клиент не имеет приемлемых средств извлечения данных со
страницы для дальнейшей работы с ними. На любой конкретной странице
клиент получает только одно представление конкретного множества данных.
Это подтверждает следующая иллюстрация.
Пример 13.3.2. Предположим, что вы просматриваете список аукционов,
упорядоченный по дате открытия торгов. Если вы захотите взглянуть на тот
же список, но отсортированный по дате закрытия торгов, то браузеру придет-
ся посылать новый запрос серверу. В свою очередь, серверу придется заново
отправлять полную страницу HTML со списком аукционов.
Конец примера 13.3.2.
Такого рода манипулирование данными ведет к значительному увеличению
числа обращений к серверам и к другим неприятным последствиям.
На примере задачи о словах и попыток адекватно представить таблицу
автомата с помощью разметки текста видна необходимость стандартной раз-
метки, а также то, что не всякая стандартная разметка годится. Последнее
демонстрирует язык HTML, который не в состоянии дать ничего существен-
но нового по отношению к показу таблицы. Ситуации, когда необходимы до-
полнительные возможности разметки, достаточно типична,
Простым решением для некоторых из перечисленных проблем было бы
введение дополнительных семантических тегов HTML, таких, как <DATE> и
<PRICE>, а в нашем случае <_1>, <_2> и т. д. С их помощью клиент мог бы
определить, каков смысл данных. История, однако, показывает, что введение
Термин WYSIWYG обычно применяется для характеристики качества интерфейса про-
граммных систем. Если система обладает этим качеством, то пользователю для понимания
23
того, что делает программа, нет необходимости заглядывать в исходный текст для выявления
действий, а дизайнер при написании исходного текста немедленно получает образ резуль-
тата своих действий. Пример задачи оперирования с таблицами демонстрирует ситуацию,
когда прямолинейное понимание WYSIWYG противоречит требованиям обработки данных.
13.3. ЯЗЫКИ РАЗМЕТКИ 779
<H1>Еditor Сontacts</H1>
<H2>Имя: Иван Дураков</H2>
<P>Должность: старший редактор</P>
<P>Издание: Сибирские сети</P>
<P>Улица и дом: Морской, 13 </P>
<P>Город: Академгородок</P>
<P>Штат: Сибирь</P>
в меру своей испорченности (так что, возможно, с неких высших позиций Кнут был и прав,
отфутболивая их).
13.3. ЯЗЫКИ РАЗМЕТКИ 781
Программа 13.3.2
<title>старший редактор</title>
<last_name>Дураков</last_name>
<publication>Сибирские сети</publication>
<address>
<street>Морской, 13</street>
<city>Академгородок</city>
<state>Сибирь</state>
<zip>630090</zip>
</address>
<e_mail>ivan_durak@uchenych.net</e_mail>
</editor>
</editor_contacts>
<article>
&introduction;
&body;
&sidebar;
&conclusion;
&resources;
</article>
Все, что рассмотрено при обсуждении XML до сих пор, не затрагивало два
]]>
важных вопроса:
XML — это нечто большее, чем просто преобразователи форматов: они также
предоставляют механизм для манипулирования данными. Например, данные
можно сортировать, производить по ним поиск, удалять или добавлять прямо
из браузера.
Давайте рассмотрим какую-либо простую таблицу стилей, которой мы
могли бы воспользоваться для приложения Editor Contacts.
Программа 13.3.3
<?xml version = "1.0" ?>
<HTML>
<BODY>
<H1>Editor Contacts</H1>
<xsl:for-each select="editor_contacts/editor">
<H2>Имя: <xsl:value-of select="first_name">
<xsl:value-of select="last_name"/></H2>
<P>Должность: <xsl:value-of select="title"/></P>
<P>Улица и дом:
<P>Издательство: <xsl:value-of select="publication"/></P>
<xsl:value-of select="address/street"/></P>
<P>Город: <xsl:value-of select="address/city"/></P>
<P>Страна: <xsl:value-of select="address/state"/></P>
<P>Zip: <xsl:value-of select="address/zip"/></P>
<P>E-Mail: <xsl:value-of select="e_mail"/></P>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
При сохранении на диск под именем EDITORS.XSL (или любым другим) этот
шаблон будет применен к EDITORS.XML при добавлении в него следующей
строки после первой:
<?xml-stylesheet type="text/xsl" href="editors.xsl" ?>
13.3. ЯЗЫКИ РАЗМЕТКИ 785
В конечном итоге текст на экране браузера будет выглядеть так же, как некий
фрагмент HTML, показывающий данные о редакторе.
Однако XSL может действовать как функция текстового процессора merge-
print. Определенный как часть пространства имен XSL, элемент xsl:for-each
сообщает процессору о том, что он должен циклически обрабатывать все уз-
лы в исходном файле XML. Атрибут xsl:value-of вставляет значение узла XML
в элемент HTML. Таким образом, если придется вернуться к EDITORS.XML и
вставить десятки или сотни контактных адресов, то они без каких-либо изме-
нений будут отображаться в соответствии с таблицей стилей. Благодаря тому,
что информацию о форматировании требуется передать только один раз, XML
и XSL экономят пропускную способность.
Таблицы стилей XSL имитируют функцию merge-print еще и в том, что
они позволяют избирательно опускать поля данных при отображении. Кроме
того, вывод может быть отсортирован по любому конкретному полю данных.
Для сортировки базы данных контактных адресов по фамилии редактора в
прямом алфавитном порядке элемент xsl:for-each следует изменить следую-
щим образом:
<xsl:for-each select="editor_contacts/editor" order-by="last_name">
greater(X,Y):-greater(X,Z),greater(Z,Y).
greater(X,f(X)).
790 ГЛАВА 13. СЕНТЕНЦИАЛЬНЫЕ МЕТОДЫ
Функциональное
программирование
791
792 ГЛАВА 14. ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ
Основная операция для задания списков (list a b . . . z). Она вычисляет свои
аргументы и собирает их в список. Для этой операции без вычисления аргу-
ментов есть скоропись ’(a b . . . z). Она является частным случаем функции
quote (сокращенно обозначаемой ’), которая запрещает всякие вычисления в
своем аргументе и копирует его в результат так, как он есть.
По традиции элементарные операции разбора списков обозначаются име-
нами, которые начинаются с c, кончаются на r, а в середине иден некоторая
последовательность букв a и b. (car s) выделяет голову (первый член) спис-
ка, (cdr s) — его хвост (подсписок всех членов, начиная со второго). Буквы a
и d применяются, начиная с конца. Рассмотрим маленький пример диалога,
иллюстрирующий эти операции.
Block 1 Block 2
( A ( A ) )
Значение Значение
Тип
A Тип
Val2
Val1
Программа 14.2.1
;==============================================================
; ((пример (ма (ш н)
;
(ш а) )
(шн))
;
; ((н ина))
;
)
(cond
((Null Vac) Nil)
((eq (caar Vac) Key) (cons (cdar Vac)
(ass-all Key (cdr Vac)) ))
(T (ass-all Key (cdr Vac)) )
))
;; определяемыя символы
(defun names (vac) (mapcar ’car vac))
;; используемые символы
(defun words (vac) (cond
;; неопределяемые лексемы
(defun lexs (vac) (set-difference (words vac) (names vac)))
;; самоопреление терминалов
(defun d-lex ( llex)
;; определение нетерминалов
(defun ( llex)
;; подстановка голов
(defun h-all (h lt)
;; подстановка хвостов
(defun all-t (lt tl)
;; присоединение предшественников
(defun pred (bnf tl)
(cond
((atom (eval elm)) (h-all (eval elm) tl))
(T (chain (eval elm) tl))
))
;; сборка цепочек
(defun chain (chl tl)
(cond
((null chl) tl)
((atom chl) (sb-nm chl tl))
;; выравнивание
(defun level1 (ll)
(cond
((null ll)NIL)
800 ГЛАВА 14. ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ
Программа 14.3.1
(defclass thing ()
((weight :initform ’(0 kg)
:accessor weightof
:initarg :weight)))
(defclass animal (thing)
((specie :accessor specieof
:initarg :spec)
(sex :accessor sexof
:initform ’m
:initarg :sex)))
(defclass possession (thing)
((owner :accessor ownerof
:initform ’nnn)
(cost :accessor costof
:initform ’(0 bucks)
:initarg :cost))
)
(defclass person (animal)
((specie :initform ’human)
(name :initarg :thename
:accessor nameof)))
(defclass pet (animal possession)
((nick :initarg :thenick
:accessor nickof)
(specie :initform ’cat)))
Моделирование
805
806 ГЛАВА 15. МОДЕЛИРОВАНИЕ
Ценность
информации для
управления Интервал максимальной
полезности информации
Время
Момент Критический интервал
возникновения полезности информации
информации
• моделирование времени.
t2
A1/A/ t1 A113/A, t1, t2/
A A111 /A, t1, t2/
A2/A/ B
A112/A, t1, t2/
A21/A, t3/ t A211/A, t3, t4/ A211/A, t3, t4, t5/
t6 t3 4 t5
A1/A/ t2
A t1 A /A, t1, t2/
111 A113/A, t1, t2/
A2/A/ A112/A, t1, t2/ B
t6 t3
A21/A, t3/ t A211/A, t3, t4/ t5 A2111 /A, t3, t4, t5/
4
A212 t2/B
A/t3 t1/t2 A2
A 211
A1211 A11 B
автоматически.
Если параллелизм возможен, но процессов недостаточно (например, если
Вы решите воспользоваться для реализации этих алгоритмов Prolog-систе-
мой Muse), то мы находимся в самой трудной ситуации, требующей творче-
ских решений, комбинации научного и инженерного подхода.6
Для языка, отвечающего фон Неймановской модели вычислений, парал-
лелизм очевидно невозможен, а значит, решение подсказывается само собой:
необходима имитация параллелизма. Одним из подходов к такой имитации
является, к примеру, хорошо известный метод волны, который исходит из
строго упорядоченного последовательного перебора вариантов, соответству-
ющих агентам-процессам. Но данный метод есть полное устранение агентов,
поскольку все их параллельные действия заменяются выстраиванием кон-
кретной последовательности их выполнения, не требующей синхронизации,
а потому агенты нужны лишь в качестве призраков. Метод выстраивания про-
цессов в линейную последовательность — одно из самых широко применя-
емых средств преодоления сложности реализации взаимодействий. По сути
дела все детерминированные переборные алгоритмы (с разным успехом) за-
нимаются именно таким выстраиванием.
Но с общих позиций линейное выстраивание — вырожденный метод. Бо-
лее того, он концептуально опасен. если призраки-агенты полностью удаля-
ются и вовсю эксплуатируются конкретные свойства конкретного линейного
порядка7 . Поэтому интересен классический подход языка SIMULA 67. Он в
явном виде провозглашает имитацию параллелизма и, следовательно, оста-
вляет для человека возможность мыслить в категориях действующих аген-
тов, что является и более гибким, и более естественным способом выражения
многих алгоритмов. Систему с дискретными событиями можно рассматри-
вать в качестве общего метода преодоления противоречия между принципи-
альным параллелизмом и реальной последовательностью реализации, име-
ющим то преимущество, что концепции остаются нетронутыми и тем самым
качество и гибкость программ резко повышаются.
Классическая реализация систем с дискретными событиями — это общая
Когда математику приносят задачу о расчете устойчивости стола с четырьмя ножками, он
быстро выдает результаты для стола с одной и с бесконечным числом ножек, а затем долго
6
b c d e
a b A1
A1 A11 A
2
τ(t5,B) (A
τ(t2×)
,t5)
τ(t2,B) τ(t4,t5)
τ(t2,t1)
a) модельное время
a b
A1 A →A
2 1
A1 → A2 A1 A11 A12 → A2 → A11
↓ ↓ ↓
A2 A11 A12 → A2
↓
A12 → A2
c
A2 → A11 → A12 A2 A21 → A11 → A12
↓
A21 → A11 → A12
d e
A11 → A12 → A21 A12 → A21 …
Внимание!
Подведение итогов
831
832 ГЛАВА 16. ПОДВЕДЕНИЕ ИТОГОВ
10. Уровни и стили разных частей системы могут быть независимы, вполне
возможно, когда интегрирующая программа написана с применением
низкоуровневых средств, а интегрируемые модули высокоуровневые3 .
Тем более это возможно в нынешней обстановке, когда средства поддержки взаимодей-
ствия модулей, написанных с помощью разного инструментария, отстают от развития про-
3
граммных инструментов.
834 ГЛАВА 16. ПОДВЕДЕНИЕ ИТОГОВ
12. Напротив, то, что рекламируется как универсальное решение всех про-
блем, должно вызывать настороженность. Может быть, некоторое ядро
этого средства — хороший специализированный инструмент для тех
задач, для которых он ранее не существовал. Но этот инструмент на-
верняка почти безнадежно испорчен дополнительными концептуаль-
но противоречивыми с ним и друг с другом возможностями. Так что
при использовании модного средства сразу вырабатывайте жестокий
внутренний регламент.
14. Если Вам предстоит несколько раз работать над сходными задачами ли-
бо даже длительное время заниматься одной и той же, не жалейте сил
на создание собственного инструментария и выработку собственного
регламента работы.
16. Читайте книги, которые выпущены 20 и более лет назад, если они при
первом просмотре показались Вам интересными. Они дадут Вам в жиз-
ни гораздо больше, чем какое-либо «Программирование в системе .Net
версии 2.00 для профессионалов», которое через два года надо будет
выбросить и забыть. Не думайте, что Ваши предки были глупее Вас!
Опыт показывает, что скорее наоборот.
17. То, что было отброшено, слишком часто содержит здравые идеи, не вы-
державшие в тот конкретный момент конкуренции. Их возрождение
в новых условиях может быть прекрасным решением концептуально
трудных проблем, вставших перед Вами.
835
20. Еще хуже упорствовать на своей ошибке, и тем более ставить психо-
логическую защиту, не давая себе осознать ее. Если Вы сталкиваетесь
с таким поведением Вашего коллеги или партнера, ситуация практи-
чески безнадежная и нужно лишь думать о том, как с наименьшими
потерями из нее выпутаться.
22. Помните, что дурно исполненная своя карма лучше хорошо исполнен-
ной чужой. Лучше быть полноценным русским, чем недоделанным аме-
риканцем. Не стесняйтесь своих особенностей мышления и работы, и
не выставляйте их как принцип. В любом обществе будьте свободным
от него, если у Вас достаточно сил, чтобы это реализовать5 , и сохра-
няйте в нем свое лицо, если Вы выбрали интеграцию.
24. Если Вам не удается сказать что-либо стихами, говорите прозой. Учи-
тесь отказываться от ограничений в нужный момент, но при этом не
забюывайте ставить себе другие!
Успехов Вам!
Приложение A
Математические модели
837
838 ПРИЛОЖЕНИЕ A. МАТЕМАТИЧЕСКИЕ МОДЕЛИ
0, 1, . . . , n, . . . ω, ω + 1, . . . , ω + n, . . . 2 · ω.
Далее:
2 · ω, 2 · ω + 1, . . . , 2 · ω + n, . . . 3 · ω.
Обобщая по всем натуральным числам, получаем следующий ряд:
0, 1, . . . , k, . . . ω, ω + 1, . . . , ω + k, . . . 2 · ω, . . . n · ω . . . .
ω 2 , ω 2 + 1, . . . , ω 2 + k, . . . ω 2 + ω, ω 2 + ω + 1, . . . , ω 2 + ω + k, . . .
ω 2 + 2 · ω, . . . ω 2 + n · ω . . . 2 · ω 2 .
За всеми k · ω 2 + l · ω + n стоит ω 3 .
Опять-таки продолжая по всем натуральным числам, получаем всевоз-
можные ординалы вида
kn · ω n + kn−1 · ω n−1 + · · · + k1 ω + k0 .
ω ω + kn · ω n + kn−1 · ω n−1 + · · · + k1 ω + k0 .
n раз.
···ω
ωω
x ← t,
z0 (x) = ⊥ иначе.
y = z ∗ hx, si или y = z [x | s] .
В нашем случае
zi+1 = zi ∗ hxi , si i ,
где si = zi (ti ). Часто такое преобразование применяется сразу к конечному
числу переменных и обозначается
A A
B - P
+
B
A + P - B
F1
F2 Q F3
Break
F4 P
Break
тие, используемое в тех случаях, когда изучается вычислимость относительно уже заданной
функции (записанной на входной ленте).
A.6. РЕКУРСИИ 849
a → b a → ·b
§ A.6. РЕКУРСИИ
(l1 , . . . , ln , k1 , . . . , km ).
проверяет, является ли x
элементом исходного множества U ;
ATOM
2. Условный терм
и
if if P (t1 , . . . , tn ) then if r1 then r else s fi else
if s1 then r else s fi fi
совпадают.
С помощью условных термов можно дать определение всех логических
связок.
Определение A.6.7. Пополним сигнатуру σ конечным числом функциональ-
ных символов f1 , . . . , fn арности соответственно m1 , . . . , mn . Обозначим по-
лученную сигнатуру σ1 Рекурсивная схема над σ — совокупность определе-
ний
f1 (x1 , . . . , xm1 ) ← t1 (x1 , . . . , xm1 )
... (A.2)
fn (x1 , . . . , xmn ) ← tn (x1 , . . . , xmn )
где все ti — условные термы сигнатуры σ1 , содержащие лишь переменные
Φ( lim fn ).
n→∞
f ← t(f )
[f1 , . . . , fn ] 4 [g1 , . . . , gn ]
тогда, когда для всех i fi 4 gi . Точно так же доказывается, что любая воз-
растающая последовательность кортежей функций имеет предел, и функция,
определяемая рекурсивной схемой общего вида — наименьшая неподвижная
точка функционала, перерабатывающего вектор функций f~ в вектор функций
~t(f~), или, другими словами, функционала λf~. ~t(f~).
Упражнения к § A.6
A.6.1. Постройте деревья, соответствующие спискам из примера A.6.2.
A.6.3. Является ли nil элементом списка (())? А списка ((()))? А списка ((), ())?
A.6.5. Пусть L есть (A, ((D, E), C), B), a есть (D, E). Постройте L[a | nil],
L[a | true], L[a | (F, F, F )].
A.6.6. Пусть L есть (A, ((D, E), C), B, (D, E)), a есть (D, E). Постройте L[aknil],
L[aktrue], L[ak(F, F, F )].
A.6.7. Имеет ли смысл выражение nil[nil|A]? Если да, то чему оно равно?
A.6.21. Проверка предиката P (x) для всех членов списка (вычисление логи-
ческой формулы ∀x ∈ L P (x)).
A.6.29. Есть ли в чуме функций наибольший элемент? Если его нет, есть ли
максимальные и чем они являются?
дом на метрическую систему. Было усажено несколько десятков грамотных солдат, которые
выполняли сложение либо вычитание результатов, полученных ими от заранее установлен-
ных товарищей. По команде они все начинали действия, по другой — передавали результаты
кому следует. В итоге мало того, что таблицы были пересчитаны в рекордно малое время и с
низкими затратами, они еще и стали самыми лучшими в мире (пока русские артиллеристы
под руководством гр. Аракчеева не составили свои таблицы).
Приложение B
Методологические результаты
863
864 ПРИЛОЖЕНИЕ B. МЕТОДОЛОГИЧЕСКИЕ РЕЗУЛЬТАТЫ
Оба этих парадокса основаны на том, что свойство “быть стандартным” при-
надлежит метаязыку. Первый из них послужил основой теории полумно-
жеств, в которой классы могут быть подклассами множеств.
(n раз).
...2
o
22
Внимание!
заться тяжелой. Все знают, как мучаются хорошие переводчики стихов. А А. Швейцер, так
877
878 ПРИЛОЖЕНИЕ C. ЗНАНИЯ, ДАННЫЕ, УМЕНИЯ
§ C.4. ТУПОСТЬ
§ C.6. ГЛУПОСТЬ
и превращено как
4
Экклезиаст, гл. 1, стих 10.
5
Экклезиаст, гл. 2, стих 14.
C.8. РЕЛЯТИВИЗМ 887
§ C.8. РЕЛЯТИВИЗМ
§ C.13. ДАО
§ C.14. ЛЖЕПРОРОКИ
Это те, кто застряли в пространстве перед уровнем дао. Они принимают
свои озарения, которые даются им ценой максимального напряжения интел-
лектуальных и духовных сил, за абсолютную истину, и тем самым убивают
элементы10 Истины, которые в них содержатся. Хотя такой самообман и по-
нятен, вред, приносимый подобными людьми, как правило, субъективно ис-
кренними, слишком велик.
10
Порою весьма значительные.
892 ПРИЛОЖЕНИЕ C. ЗНАНИЯ, ДАННЫЕ, УМЕНИЯ
Комментариев нет.
Общая картина сфер знаний и умений представлена на рис. C.1.
Химеры
Лжепророки
Мудрствование
Умничанье
Релятивизм
Глупость
Тупость
Коллапс
Насекомое
Стереотипы
Комбинации
Планы
Метод
Многоуровневость
Дао
Рис. C.1. Сферы Белосельского-Белозерского
Литература
[2] Абрамов В.Г., Трифонов Н.П., Трифонова Г.Н. Введение в язык Па-
скаль: Учеб. пособие. — М.: Наука, 1988. — 320 с.
893
894 ЛИТЕРАТУРА
[22] Гамма Э., Хелм Р., Джонсон Р., Влрссидес Дж. Приемы объектно-
ориентированного проектирования. Паттерны проекти-рования. —
СПб: Питер, 2001. — 368 с.
[31] Дейт К. Введение в системы баз данных. М.: Наука, 1980, 463 с.
[35] Зуев Е.А. Программирование на языке Turbo-Pascal 6.0, 7.0. - М.: Ве-
ста, Радио и связь, 1993. - 384 с.
[44] В. Е. Котов. Введение в теорию схем программ. М.: Наука, 1978, 257 с.
[68] Б. Ренделл и Л. Рассел. Реализация АЛГОЛА 60, М.: Мир, 1967. - 475
с. (Randell B, Russel L.J. ALGOL 60 Implementation, Academic Press,
London, New York, 1964)
[73] Уэзерелл Ч. Этюды для программистов: Пер. с англ. — М.: Мир, 1982.
— 288 с.
[82] Bacus, J. Can programming be liberated from the von Neuman style?
Comm. ACM, 21, 1978.
ЛИТЕРАТУРА 899
[85] IEEE IDEF0. “Standard Users Manual for the ICAM Function Modeling
Method — IDEF0”. IEEE draft standard P1320.1.1. 1997.
[89] Rumbaugh J., Jacobson I., Booch G. The Unified Modelling Language.
Reference Manual. Addison-Wesley, 1999.— 550p.
[90] Turchin V. F.. Refal-5, Programming Guide and Reference Manual. New
England Publishing Co., Holyoke, 1989.
Предметный указатель
Артефакт, 181
CSS, см. Таблицы стилей Архитектура
C
фон Неймановская, 15
Атом, 730, 769
dll, см. днамически загружаемая библио-
D
Атрибуты
тека368 атома в LISP, 792
Аттестация, 189
D. Hilbert (Д. Гильберт), 867
H
Б
База
данных, 752, 814
R
знаний, 752, 815
Rational Unified Process, 180, 829–830
Библиотека, 39–44
динамически загружаемая, 368
W
периода исполнения, 9, 15
WYSIWYG, 778
А периода трансляции, 8, 15
Автомат, 845 среды программирования, 14
Мили, 847 стандартная, 13, 15
Мура, 845, 847
В
входные символы, 845
Верификация, 189
конечный, 560, 564, 566, 573, 610,
Выражение
языка Рефал, 730
состояния, 845
611, 617, 619, 621, 845
Выражения, 247
Адрес, 16
Вычисление
Адресность, 16
квазипараллельное, 51
Алгоритм
параллельное, 51
Маркова, 729, 849
последовательное, 51
Алгорифм Маркова, см. Алгоритм Мар-
смешанное, 170
кова
совместное, 52, 52–54
Анализ, 201
слабое, 53
Анализ осуществимости, 194
Вычисления
Арность, 839
гарантированные, 503, 808
900
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 901
Формула
Хорнова, 747
FORTRAN, 7, 24–26, 68, 71, 80,
Функтор, 751
102, 251, 273, 326, 362, 381, 382,
Функция
414
расстановки, 529
Java, 33
рекурсивная, 856
LISP, 791–804
сигнализирующая, 638
Lisp, 5, 12
Х
PL/1, 26
новки
Prolog, 5, 12, 746–763
SNOBOL, 134
Ц
Алгол 68, 4, 7, 11, 27, 40, 42, 52,
SNOBOL-A, 134
Цель, 752
Цикл
Рефал, 5, 12, 13, 59, 132, 729–745
58, 280
Бема-Джакопини, 129, 843, 845
С, 29
Кнута, 843
Симула-67, 26
Ч прагматическое расширение, 13
Чум разметки, 614, 617, 765
фундированный, 639 LATEX, 615, 617, 766
Ш
TEX, 615, 765
Шаблон, 722
HTML, 615, 774
plain TEX, 766
Э
SGML, 774
Эквивалентность реализация, 7
XML, 615, 617, 781
схем реализованный, 14
логико-термальная, 843 эталонный, 13
функциональная, 842 Языки
Эклектика, 111 операционные, 7
Эпименид, 864 традиционные, 7, 15, 22
форматные, 39
Я
Ячейка, 16
Язык
Ящик
серый, 161
Ada, 31
черный, 161
Algol 60, 25, 26, 382, 390, 392, 395,
Предисловие i
I Базовые понятия 1
1. Введение в систему понятий программирования
1.1. Языки и системы программирования . . . . . . . . . . . . . .
3
1.3.4. Описания . . . . . . . . . . . . . . . . . . . . . . . . . .
39
1.3.5. Действия . . . . . . . . . . . . . . . . . . . . . . . . . .
44
нение . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2. Управление порядком вычислений . . . . . . . . . . . .
48
2.2. Прагматика . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
906
ОГЛАВЛЕНИЕ 907
цикла . . . . . . . . . . . . . .
4.4.4. Особенности первой итерации
. . . . . . . . . . . . . . 224
6. Разветвление вычислений
6.1. Разбор случаев . . . . . . . . . . . . . . . . . . . . . . . . . . .
269
6.1.2. Переключение . . . . . . . . . . . . . . . . . . . . . . .
269
7. Циклические вычисления
7.1. Мотивация циклических вычислений . . . . . . . .
287
7.4.3. Исключения . . . . . . . . . . . . . . . . . . . . . . . .
348
358
8. Подпрограммы
8.1. Виды подпрограмм . . . . . . . . . . . . . . . . . . . . . . . .
362
8.5. Параметризация . . . . . . . . . . . . . . . . . . . . . . . . . .
396
метров . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.5.3. Механизмы передачи параметров . . . . . . . . . . . .
406
дурный тип . . . . . . . . . . . . . . . . . . . . . . . .
8.6. Развитие языковых средств модульности в языках линии Turbo
437
9. Структуры данных
9.1. Общие концепции структурирования данных . . . . . . . . . . 482
482
9.2.1. Перечисления . . . . . . . . . . . . . . . . . . . . . .
. 499
9.3.2. Записи . . . . . . . . . . . . . . . . . . . . . . . . . .
. 510
9.3.3. Объединения . . . . . . . . . . . . . . . . . . . . . . .
. 515
9.3.4. Массивы . . . . . . . . . . . . . . . . . . . . . . . . .
. 519
9.3.5. Множества . . . . . . . . . . . . . . . . . . . . . . . .
. 524
9.4.4. Деревья . . . . . . . . . . . . . . . . . . . . . . . . . .
. 550
. 552
11.4. Лабиринт . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
646
13.2. Унификация . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 744
15. Моделирование
15.1. Модели и вычисления . . . . . . . . . . . . . . . . . . . . . . .
805
шений . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.5. Системы с дискретными событиями . . . . . . . . . . . . . . .
817
A. Математические модели
A.1. Несколько терминов . . . . . . .
837
A.6. Рекурсии . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 848
B. Методологические результаты
B.1. Логические парадоксы . . . . . . . . . . . . .
863
C.4. Тупость . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 880
C.6. Глупость . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 882
C.8. Релятивизм . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 885
C.13. Дао . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 891
C.14. Лжепророки . . . . . . . . . . . . . . . . . . . . . . . . . .
. . 891