Module = module ModuleName ";"
{ const { ConstantDeclaration
";" }{ var { VariableDeclaration ";" }}{ ProcedureDeclaration
}BlockStatement
SimpleName ".".
Рис. 2.9. Общий вид Zonnon-программы
В языке Zonnon помимо стандартных процедур и функций разрешены процедуры и функции, определяемые программистом. Каждая вводимая в употребление процедура (функция) должна быть описана (определена) -- см. п. 2.2.7. Описание функции состоит из ее заголовка, в котором задается имя функции, фиксируются область ее определения (число и типы ее аргументов) и область ее значения (тип результата), а также тела функции, в котором определяются правила вычисления результата функции по ее аргументам. Аналогично обращению к стандартной функции вызов (или, что то же самое, указатель) определенной в программе функции может являться частью любого выражения (см. п. 2.2.1) и имеет вид имени функции, за которым в скобках перечисляются его фактические параметры -- выражения, вычисляющие те конкретные значения аргументов, к которым при данном обращении должна быть применима указанная функция.
Определение константы вводит имя (идентификатор) как синоним
некоторой константы и записывается по правилам рис. 2.10. Константное выражение
– это выражение, которое может быть вычислено при простом текстовом просмотре,
без реального выполнения программы. Его операндами должны быть константы
или вызовы предопределенных функций.
ConstantDeclaration = ident "=" ConstExpression.
ConstExpression = Expression.
Рис. 2.10. Определение константы
Использование таких именованных констант делает программу более наглядной и понятной и позволяет легко модифицировать программу, если возникает потребность в изменении константы в тексте программы.
Совокупность описаний переменных состоит из списка имен (идентификаторов), обозначающих новые переменные, за которыми через двоеточие следует имя типа (рис. 2.11).
VariableDeclaration = ident { "," ident } ":" Type.
Рис. 2.11. Совокупность описаний переменных
Например, программа вычисления длины окружности может иметь следующий вид:
module ДлинаОкружности;
const Два = 2; Пи = 3.14; Текст = 'Длина
окружности равна';
var R, Длина : real;
begin read(R); Длина:=Два
Пи
R ; write(Текст, Длина) end ДлинаОкружности.
Рассмотрим задачу вычисления корней уравнения , заданного коэффициентами , и . Предположим, что и что корни действительны. Тогда возможным решением является
module Корни1;
var A, B ,C, X1, X2 : real;
begin
read(A,B,C);
X1 := (-B+Sqrt(Sqr(B)-4AC))/(2A);
X2 := (-B-Sqrt(Sqr(B)-4AC))/(2A);
write(X1, X2)
end Корни1.
Недостаток этой программы заключается в том, что в ней дважды вычисляются значение Sqrt(Sqr(B)-4 A C) и значение 2А. Запоминая значения выражений в подходящих переменных, можно избежать необходимости повторных вычислений их значений.
module Корни2;
var A,B,C,X1,X2,D,E : real;
begin
read(A,B,C);
D := Sqrt(Sqr(B) - 4AC);
E :=2A;
X1 := (-B+D)/E;
X2 := (-B-D)/E; write(X1,X2)
end Корни2.
Сравнивая Корни1 и Корни2, нетрудно понять, что программа Корни1 заметно уступает программе Корни2 "по времени" (по числу шагов, которые необходимо выполнить для получения результата), а если и выигрывает "по памяти" (по числу ячеек, отводимых при выполнении программы под ее команды и данные), то незначительно.
Вместе с тем программы Корни1 и Корни2 имеют один общий недостаток, связанный со свойствами машинной арифметики: из-за ограниченного числа значащих цифр в вещественных значениях, представимых в ЭВМ, в результате вычисления по программам можно получить совершенно неверные результаты.
Поясним это на примере работы программы Корни2. Для простоты изложения предположим, что все арифметические операции выполняются с четыремя значащими десятичными цифрами, и рассмотрим, что произойдет, если a=1.000, b=-200.0, c=1.000. В результате выполнения программы Корни2 мы получим: D=Sqrt(40000-4.000)=200.0; X1=400.0/2.000=200.0; X2=0.000/2.000=0.000.
Правильными результатами, однако, будут X1=200.0 и X2=0.005. Если качество программы Корни2 оценивать с точки зрения относительной точности вычисляемых значений, то результат X2 следует отвергнуть как совершенно неверный.
Причина неудовлетворительного результата состоит в том, что программа Корни2 не учитывает "опасности", которая связана с операциями сложения и вычитания. Они могут явиться причиной значительных ошибок, особенно при вычитании двух почти равных значений. В этом случае большое количество значащих цифр взаимно уничтожаются, и в полученной разности может не оказаться нескольких, а может быть всех значащих цифр.
Алгоритм, в котором учтены указанные эффекты, может быть построен с использованием правила Виета. Нужно производит вычисления таким образом, чтобы сначала в X1 вычислялся максимальный по модулю корень (т.е. X1 нужно вычислять по формуле -(B+D)/(2A), если B - положительно, либо по формуле (-B+D)/(2A), в противном случае), а уж затем вычислить второй корень X2 по формуле C/(X1A). (Заметим, однако, что мы рассмотрели еще недостаточно конструкций языка Паскаль, чтобы программно реализовать указанный алгоритм, и отложим его построение до следующей главы -- см. программу Корни3 в п.3.1.2.)
Рассмотренный пример нахождения корней квадратного уравнения демонстрирует довольно характерный случай, когда математические методы, известные еще со школьной скамьи, требуют дополнительного и тщательного исследования перед тем как их использовать для решения задач на ЭВМ.
Рассмотрим задачу вычисления функции , где -- натуральное число. Ее решением является
module X10;
var X,Y : integer;
begin read(X); Y := XXXXXXXXXX;
write(Y) end X10.
Однако эта программа хотя и является весьма короткой, но долго работает, осуществляя вычисление за 9 умножений. В то же время можно решить задачу за 4 умножения:
module X10A ;
var X,X2,X4,X8,X10 : Integer;
begin read(X); X2 := XX;
X4 := X2X2;
X8 := X4X4;
X10 := X8X2;
write(X10) end X10A.
В программе X10A из соображений наглядности заведены различные переменные для хранения входного значения, промежуточных значений и результата программы. Поэтому она не только длиннее первой программы, но и использует большее число переменных. Однако программу X10A можно переписать так, чтобы в ней было только две переменные. Для этого нужно использовать тот факт, что переменная в программе отличается от математической переменной и является по существу элементом памяти, своеобразной камерой хранения одного любого значения некоторого (в данном случае целого) типа. Это значение поступает туда в результате выполнения оператора присваивания или ввода; при этом старое значение переменной перестает существовать. Ясно, что в некоторой точке программы переменная "неприкосновенна" для записи в нее, если текущее значение переменной позднее может использоваться в качестве аргумента при исполнении какого-либо оператора, и ей можно присвоить новое значение, если текущее значение переменной уже не потребуется в дальнейшем. В результате сокращения числа переменных в программе X10A получаем
module X10B ;
var X,X2: integer;
begin read(X); X2 := XX;
X := X2X2;
X := XX;
X:= XX2;
write(X) end X10B.
Нетрудно понять, что количество и обозначения переменных, используемых в различных вариантах программы, не столь существенны для результата выполнения программы. Различные (по обозначениям переменных в операторах) варианты программы будут работать одинаково, если сохраняются все так называемые информационные связи в программе, т.е. "передачи" значений от одних операторов к другим. Например, можно, не нарушая смысла, переписать программy X10B:
module X10C ;
var A,B: integer;
begin read(A); B := AA;
A:= BB;
A := AA;
A := AB;
write(A) end X10C.
Next:2.2.7
Определяемые функции
Up:2.2
Выражения, операторы и функции
Previous:2.2.5
Операторы ввода-вывода