Презентацию к данной работе Вы можете скачать здесь.
С увеличением объема программы становится невозможным удерживать в памяти все детали. Естественным способом борьбы со сложностью любой задачи является ее разбиение на части. В Паскале задача может быть разделена на более простые и понятные фрагменты — подпрограммы, после чего программу можно рассматривать в более укрупненном виде — на уровне взаимодействия подпрограмм.
Использование подпрограмм является первым шагом к повышению степени абстракции программы и ведет к упрощению ее структуры. Разделение программы на подпрограммы позволяет также избежать избыточности кода, поскольку подпрограмму записывают один раз, а вызывать ее на выполнение можно многократно из разных точек программы.
Следующим шагом в повышении уровня абстракции программы является группировка подпрограмм и связанных с ними данных в отдельные файлы ( модули ), компилируемые раздельно. Разбиение на модули уменьшает время перекомпиляции и облегчает процесс отладки, скрывая несущественные детали за интерфейсом модуля и позволяя отлаживать программу по частям (при этом, возможно, разным программистам).
Подпрограммы нужны для того, чтобы упростить структуру программы и облегчить ее отладку. В виде подпрограмм оформляются логически законченные части программы.
Подпрограмма — это фрагмент кода, к которому можно обратиться по имени. Она описывается один раз, а вызываться может столько раз, сколько необходимо. Одна и та же подпрограмма может обрабатывать различные данные, переданные ей в качестве аргументов.
В Паскале имеется два вида подпрограмм: процедуры и функции. Они определяются в соответствующих разделах описания (до начала блока исполняемых операторов) и имеют незначительные отличия в синтаксисе и правилах вызова.
Само по себе описание не приводит к выполнению подпрограммы. Чтобы подпрограмма выполнилась, ее надо вызвать. .Подпрограмма вызывается по имени, за которым следует список аргументов в круглых скобках. Если аргументов нет, скобки не нужны. Список аргументов при вызове как бы накладывается на список параметров, поэтому они должны попарно соответствовать друг другу. Правила соответствия рассматриваются далее.
Процедура вызывается с помощью отдельного оператора, а функция — в правой части оператора присваивания, например:
inc(i); writeln(a, b, c); { вызовы процедур }
y := sin(x) + 1; { вызов функции }
Внутри подпрограмм можно описывать другие подпрограммы. Они доступны только из той подпрограммы, в которой описаны.
Структура процедуры аналогична структуре основной программы:
procedure имя [(список параметров)]; { заголовок }
разделы описаний
begin
раздел операторов
end;
Квадратные скобки в данном случае не являются элементом синтаксиса, а означают, что список параметров может отсутствовать. Рассмотрим простой пример.
Пример. Найти разность средних арифметических значений двух вещественных массивов из 10 элементов.
Как видно из условия, для двух массивов требуется найти одну и ту же величину — среднее арифметическое. Следовательно, логичным будет оформить его нахождение в виде подпрограммы, которая сможет работать с разными массивами ( ).
program dif_average;
const n = 10;
type mas = array[1 .. n] of real;
var a, b : mas;
i : integer;
dif, av_a, av_b : real;
procedure average(x : mas; var av : real); { 1 }
var i : integer;
begin
av := 0;
for i := 1 to n do av := av + x[i];
av := av / n;
end; { 2 }
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
average(a, av_a); { 3 }
average(b, av_b); { 4 }
dif := av_a – av_b;
writeln('Разность значений ', dif:6:2);
end.
Описание процедуры average расположено в строках с {1} по {2 }. В строках {3} и {4} эта процедура вызывается сначала для обработки массива а, затем — массива b. Массивы передаются в качестве аргументов. Результат вычислений возвращается в главную программу через второй параметр процедуры.
Описание функции отличается от описания процедуры незначительно:
function имя [(список параметров)] : тип; { заголовок }
разделы описаний
begin
раздел операторов
имя := выражение;
end;
Квадратные скобки в данном случае означают, что список параметров может отсутствовать. Функция вычисляет одно значение, которое передается через ее имя. Следовательно, в заголовке должен быть описан тип этого значения, а в теле функции — оператор, присваивающий вычисленное значение ее имени. Он не обязательно должен находиться в конце функции. Более того, таких операторов может быть несколько — это определяется алгоритмом. Рассмотрим пример применения функции для программы, приведенной в предыдущем разделе.
).
program dif_average1;
const n = 10;
type mas = array[1 .. n] of real;
var a, b : mas;
i : integer;
dif : real;
function average(x : mas) : real; { 1 }
var i : integer; { 2 }
av : real;
begin
av := 0;
for i := 1 to n do av := av + x[i];
average := av / n; { 3 }
end;
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
dif := average(a) – average(b); { 4 }
writeln('Разность значений ', dif:6:2)
end.
Оператор {1} представляет собой заголовок функции. Тип функции определен как вещественный, потому что к такому типу относится среднее арифметическое элементов вещественного массива. Оператор {3} присваивает имени {4} функция вызывается дважды: сначала для одного массива, затем для другого.
Глобальными называются переменные, описанные в главной программе. Переменные, которые не были инициализированы явным образом, перед началом выполнения программы обнуляются. Время жизни глобальных переменных — с начала программы и до ее завершения.
Внутри подпрограмм описываются локальные переменные. Время их жизни — с начала работы подпрограммы и до ее окончания. Значения локальных переменных между двумя вызовами одной и той же подпрограммы не сохраняются, и эти переменные предварительно не обнуляются.
Глобальные переменные доступны в любом месте программы или подпрограммы, кроме тех подпрограмм, в которых описаны локальные переменные с такими же именами. Локальные переменные могут использоваться только в подпрограмме, в которой они описаны, и всех вложенных в нее.
Понятно, что никаких дополнительных усилий по передаче глобальных переменных в подпрограмму не требуется: они видны в ней естественным образом. Этот способ обмена информацией между главной программой и подпрограммой — самый простой, но он же и самый плохой.
В подавляющем большинстве случаев для обмена данными между вызывающей и вызываемой подпрограммами предпочтительнее использовать механизм параметров. Если все данные передаются подпрограммам через списки параметров, для локализации места ошибки достаточно просмотреть заголовки подпрограмм, а затем — тексты только тех из них, в которые передается интересующая нас переменная.
Список параметров, то есть величин, передаваемых в подпрограмму и обратно, содержится в ее заголовке. Для каждого параметра обычно задаются его имя, тип и способ передачи. Либо тип, либо способ передачи могут не указываться.
В заголовке подпрограммы нельзя вводить описание нового типа — там должны использоваться либо имена стандартных типов, либо имена типов, описанных программистом ранее в разделе type.
В Паскале существует четыре вида параметров: значения, переменные, константы и нетипизированные параметры.
Кроме того, по другим критериям можно выделить особые виды параметров:
Параметр-значение описывается в заголовке подпрограммы следующим образом:
имя : тип;
Например, передача величины целого типа в процедуру Р записывается так:
procedure P(x : integer);
Имя параметра может быть произвольным. Параметр х можно представить себе как локальную переменную, которая получает свое значение из главной программы при вызове подпрограммы. В подпрограмму передается копия значения аргумента.
Механизм передачи следующий: из ячейки памяти, в которой хранится переменная, передаваемая в подпрограмму, берется ее значение и копируется в область сегмента стека, называемую областью параметров. Подпрограмма работает с этой копией, следовательно, доступа к ячейке, где хранится сама переменная, не имеет. По завершении работы подпрограммы стек освобождается. Такой способ называется передачей по значению. Ясно, что им можно пользоваться только для величин, которые не должны измениться после выполнения подпрограммы, то есть для ее исходных данных.
При вызове подпрограммы на месте параметра, передаваемого по значению, может находиться выражение (а также, конечно, его частные случаи — переменная или константа). Тип выражения должен быть совместим по присваиванию с типом параметра.
Например, если в вызывающей программе описаны переменные
var x : integer; c : byte; y : longint;
то следующие вызовы подпрограммы Р, заголовок которой описан выше, будут синтаксически правильными:
P(x); P(c); P(y); P(200); P(x div 4 + 1);
Недостатками передачи по значению являются затраты времени на копирование параметра, затраты памяти в стеке и опасность его переполнения, когда речь идет о параметрах, занимающих много места, например массивах большого размера.
Признаком var перед описанием параметра:
var имя : тип;
Например, параметр-переменная целого типа в процедуре Р записывается так:
procedure P(var x : integer);
При вызове подпрограммы в область параметров копируется не значение переменной, а ее адрес, и подпрограмма через него имеет доступ к ячейке, в которой хранится переменная. Этот способ передачи параметров называется передачей по адресу. Подпрограмма работает непосредственно с переменной из вызывающей программы и, следовательно, может ее изменить, поэтому результаты работы подпрограммы должны быть только
При вызове подпрограммы на месте параметра-переменной может находиться только ссылка на переменную точно того же типа. Исходные данные в подпрограмму передавать по адресу не рекомендуется, чтобы исключить возможность их непреднамеренного изменения.
Проиллюстрируем передачу параметров-значений и параметров-переменных на примере ().
var a, b, c, d, e : word;
procedure X(a, b, c : word; var d : word);
var e : word;
begin
c := a + b; d := c; e := c;
writeln ('Значения переменных в подпрограмме:');
writeln ('c = ', c, ' d = ', d, ' e = ', e);
end;
begin
a := 3; b := 5;
x(a, b, c, d);
writeln ('Значения переменных в главной программе:');
writeln ('c = ', c, ' d = ', d, ' e = ', e);
end.
Результаты работы этой программы приведены ниже.
Значения переменных в подпрограмме: c = 8 d = 8 e = 8 Значения переменных в главной программе: c = 0 d = 8 e = 0
Как видите, значение переменной с в главной программе не изменилось, поскольку переменная передавалась по значению, а значение переменной е не изменилось потому, что в подпрограмме была описана локальная переменная с тем же именем.
const перед описанием параметра. Оно говорит о том, что в пределах подпрограммы данный параметр изменить невозможно:
const имя : тип;
При вызове подпрограммы на месте параметра-константы, как и в случае параметра-значения, может быть записано выражение, тип которого совместим по присваиванию с типом параметра. Однако компилятор при передаче параметров-констант формирует более эффективный код, чем для параметров-значений. Фактически
Например, параметр-константа целого типа в процедуре Р записывается так:
procedure P(const x : integer);
Подведем итоги. Если данные передаются в подпрограмму по значению, их можно изменять, но эти изменения затронут только копию в области параметров и не отразятся на значении аргумента в вызывающей программе. Если данные передаются как
Параметры составных типов (массивы, записи, строки) предпочтительнее передавать как константы, потому что при этом не расходуется время на копирование и место в стеке (размер стека не может превышать 64 Кбайт, а по умолчанию устанавливается равным 16 Кбайт).
Результаты работы процедуры следует передавать через
Чтобы можно было передавать в подпрограмму массивы с различным количеством элементов, в списке ее параметров разрешается определять открытый массив, например:
procedure P(a : array of real);
Он может быть только одномерным и состоять из элементов любого типа, кроме файлового. На место открытого массива можно передавать одномерный массив любой размерности, состоящий из элементов такого же типа. Передавать открытый массив можно как значение, переменную или константу.
Его элементы нумеруются с нуля. Номер максимального элемента в массиве можно определить с помощью функции ).
function max_el(const mas : array of integer) : integer;
var i, max : integer;
begin
max := mas[0];
for i := 0 to High(mas) do
if mas[i] > max then max := mas[i];
max_el := max;
end;
Для передачи в подпрограмму по адресу строк любой длины используется либо специальный тип OpenString, называемый открытой строкой, либо тип string при включенном режиме {$P+} (по умолчанию этот режим выключен).
Напомню, что если параметр передается в подпрограмму как значение или константа, от него не требуется точного совпадения с типом аргумента — достаточно соответствия по присваиванию. Поскольку присваивать друг другу строки разной длины можно, их можно использовать и в качестве параметров, то есть на место параметра-значения или параметра-константы типа string можно передавать строку любой длины без использования открытых строк.
Пример передачи строк в подпрограмму:
type s20 = string[20];
var s1 : string[40];
s2 : string[10];
procedure P(const x : s20; y : string; var z : openstring);
...
begin
... P(s2, s1, s1); ...
end.
Все рассмотренные параметры подпрограмм позволяли выполнять один и тот же алгоритм с различными данными. В Паскале есть и другая возможность — параметризовать алгоритм функциями и процедурами. Это может пригодиться, если требуется выполнить одну и ту же последовательность действий, внутри которой выполняется обращение к разным функциям или процедурам.
Описание параметра подпрограммы в большинстве случаев состоит из имени и типа. Имя функции является константой процедурного ( функционального ) типа, который требуется описать в разделе type, например:
type fun = function(x : real) : real;
pr = procedure;
proc = procedure(a, b : word; var c : word);
Здесь вводится описание трех типов. Первый из них соответствует любой функции с одним аргументом вещественного типа, возвращающей вещественное значение, второй — процедуре без параметров, а третий — процедуре с тремя параметрами типа word. Как видно из примеров, описание процедурного (функционального) типа соответствует заголовку подпрограммы без имени. Имя типа используется затем в списке параметров подпрограммы аналогично другим типам.
Пример. Программа, вычисляющая определенные интегралы методом прямоугольников для двух функций
$$q=\frac{2x}{\sqrt{1-sin\:2x}}\qquad\gamma=cos\:x-0,2x $$
).
program integrals;
type fun = function(x : real) : real; { 1 }
var a, b : real;
n : integer;
{$F+}
function Q(x : real) : real;
begin
Q := 2 * x / sqrt(1 – sin(2 * x));
end;
function R(x : real) : real;
begin
R := cos(x) – 0.2 * x;
end;
{$F–}
function integr(f : fun; a, b : real; n : integer) : real;
var sum, x, h : real;
i : integer;
begin
h := (b – a) / n; sum := 0; x := a;
for i := 1 to n do begin
sum := sum + f(x); x := x + h;
end;
integr := sum * h;
end;
begin
writeLn('Введите интервал и количество шагов');
readln(a, b, n);
writeln('Интеграл для первой функции: ', integr(Q, a, b, n):8:3);
writeln(' Интеграл для второй функции: ', integr(R, a, b, n):8:3);
end.
Вычисление определенного интеграла
Итак, чтобы передать имя функции или процедуры в подпрограмму, необходимо:
{$F +}, определяющий дальнюю адресацию. При этом компилятор формирует полный адрес, состоящий из сегмента и смещения. Альтернативный способ — указать в заголовке каждой функции директиву far:function Q(x : real) : real; far;
Рекурсивной называется подпрограмма, в которой содержится обращение к самой себе. Такая рекурсия называется прямой. Есть также косвенная рекурсия, когда две или более подпрограмм вызывают друг друга.
При обращении подпрограммы к самой себе происходит то же самое, что и при обращении к любой другой функции или процедуре: в стек записывается адрес возврата, резервируется место под локальные переменные, происходит передача параметров, после чего управление передается первому исполняемому оператору подпрограммы. При повторном вызове этот процесс повторяется. Для завершения вычислений каждая рекурсивная подпрограмма должна содержать хотя бы одну нерекурсивную ветвь, заканчивающуюся возвратом в вызывающую программу.
При завершении подпрограммы область ее локальных переменных освобождается, а управление передается на оператор, следующий за рекурсивным вызовом.
Простой пример рекурсивной функции — вычисление факториала (это не означает, что факториал следует вычислять именно так). Чтобы получить факториал числа n, требуется умножить на n факториал ( n – 1)!. Известно также, что 0! = 1 и 1! = 1.
function fact(n : byte) : longint;
begin
if (n = 0) or (n = 1) then fact := 1
else fact := n * fact(n – 1);
end;
Рекурсивные подпрограммы чаще всего применяют для компактной записи рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно, например с двоичными деревьями. Любую рекурсивную функцию можно реализовать без применения рекурсии: для этого программист должен сам обеспечить распределение памяти под необходимое количество копий параметров.
Достоинством рекурсии является компактная запись. К недостаткам относятся расход времени и памяти на повторные вызовы функции и передачу ей параметров, а главное, опасность переполнения стека.
Модуль — это подключаемая к программе библиотека ресурсов. Он может содержать описания типов, констант, переменных и подпрограмм. В модуль обычно объединяют связанные между собой ресурсы: например, в составе оболочки есть модуль Graph для работы с экраном в графическом режиме. Модули применяются как библиотеки, которые могут использоваться различными программами, и для разбиения сложной программы на составные части.
Чтобы использовать модуль, достаточно знать только его интерфейс: детали реализации модуля скрыты от его пользователя. Это позволяет успешно создавать программы большого объема, поскольку мозг человека может хранить одновременно довольно ограниченный объем информации. Кроме того, использование модулей позволяет преодолеть ограничение в один сегмент на объем кода исполняемой программы, поскольку код каждого подключаемого к программе модуля содержится в отдельном сегменте.
Модули можно разделить на стандартные, которые входят в состав системы программирования, и пользовательские, то есть создаваемые программистом. Чтобы подключить модуль к программе, его требуется предварительно скомпилировать. Результат компиляции каждого модуля хранится на диске в отдельном файле с расширением .tpu.
Исходный текст каждого модуля хранится в отдельном файле с расширением .pas. Модуль состоит из секций (разделов). Общая структура модуля:
unit имя; { заголовок модуля }
interface { ------------- интерфейсная секция модуля }
{ описание глобальных элементов модуля (видимых извне) }
implementation { --------------- секция реализации модуля }
{ описание локальных (внутренних) элементов модуля }
begin { ------------------- секция инициализации }
{ может отсутствовать }
end.
unit.Модуль может использовать другие модули, для этого их надо перечислить в операторе uses, который может находиться только непосредственно после ключевых слов interface или implementation. Если модули подключаются к интерфейсной части, все константы и типы данных, описанные в интерфейсной секции этих модулей, могут использоваться в любом описании в интерфейсной части данного модуля. Если модули подключаются к части реализации, все описания из этих модулей могут использоваться только в секции реализации.
В интерфейсной секции модуля определяют константы, типы данных, переменные, а также заголовки процедур и функций. Полностью же подпрограммы описываются в секции реализации, скрытой от пользователя модуля. Это естественно, поскольку для применения подпрограммы требуется знать только информацию, которая содержится в ее заголовке.
В секции реализации описываются подпрограммы, заголовки которых приведены в интерфейсной части. Заголовок подпрограммы должен или быть идентичным указанному в секции интерфейса, или состоять только из ключевого слова procedure или function и имени подпрограммы. Для функции также указывается ее тип.
Кроме того, в этой секции можно определять константы, типы данных, переменные и внутренние подпрограммы. Они используются внешними элементами модуля и видны только в секции реализации.
Секция инициализации предназначена для присваивания начальных значений переменным, используемым в модуле или в программе, к которой он подключен. Операторы, расположенные в секции инициализации модуля, выполняются перед операторами основной программы. Если к программе подключено более одного модуля, их секции инициализации вызываются на выполнение в порядке, указанном в операторе uses.
В оболочках Borland Pascal и Turbo Pascal результат компиляции по умолчанию размещается в оперативной памяти и на диск не записывается. Поэтому для сохранения скомпилированного модуля на диске требуется установить значение пункта Compile ( Destination в значение Disk. Компилятор создаст файл с расширением .tpu, который надо переместить в специальный каталог, путь к которому указан в пункте меню Options ( Directories в поле Unit Directories.
В качестве примера оформим в виде модуля подпрограмму вычисления среднего арифметического значения элементов массива из ().
unit Average;
interface
const n = 10;
type mas = array[1 .. n] of real;
procedure average(x : mas; var av : real);
implementation
procedure average(x : mas; var av : real);
var i : integer;
begin
av := 0;
for i := 1 to n do av := av + x[i];
av := av / n;
end;
end.
Список параметров подпрограммы в разделе реализации указывать не обязательно.
Чтобы использовать в программе величины, описанные в интерфейсной части модуля, имя модуля следует указать в разделе uses. Можно записать несколько имен модулей через запятую, например:
program example; uses Average, Graph, Crt;
После этого все описания, расположенные в интерфейсных секциях модулей, становятся известными в программе, и ими можно пользоваться точно так же, как и величинами, определенными в ней непосредственно. Поиск модулей выполняется сначала в библиотеке исполняющей системы, затем в текущем каталоге, а после этого — в каталогах, заданных в диалоговом окне Options ( Directories.
Если в программе описана величина с тем же именем, что и в модуле, для обращения к величине из модуля требуется перед ее именем указать через точку имя модуля.
System, который содержит библиотеку исполняющей системы Паскаля.Пример использования модуля из приведен в . Программа находит разность средних арифметических значений двух вещественных массивов.
program dif_average;
uses Average;
var a, b : mas;
i : integer;
dif, av_a, av_b : real;
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
average(a, av_a);
average(b, av_b);
dif := av_a – av_b;
writeln('Разность значений ', dif:6:2);
end.
В Паскале имеется ряд стандартных модулей, в которых описано большое количество встроенных констант, типов, переменных и подпрограмм. Каждый модуль содержит связанные между собой ресурсы. Ниже приводится краткая характеристика модулей Паскаля.
Модуль System содержит базовые средства языка, которые поддерживают ввод-вывод, работу со строками, операции с плавающей точкой и динамическое распределение памяти. Этот модуль автоматически используется во всех программах, его не требуется указывать в операторе uses. Он содержит все стандартные и встроенные процедуры, функции, константы и переменные Паскаля.
Модуль предназначен для организации эффективной работы с экраном, клавиатурой и встроенным динамиком. При
В текстовом режиме экран представляется как совокупность строк и столбцов. Каждый символ располагается на так называемом знакоместе на пересечении строки и столбца. Символы хранятся в специальной части оперативной памяти, называемой видеопамятью. Ее содержимое отображается на экране.
Модуль позволяет:
).
program luck;
uses crt;
const max = 10;
var i, k, n : integer;
begin
clrscr; { очистить экран }
randomize;
i := random(max); { загадать число }
window(20, 5, 60, 20); { определить окно }
TextBackGround(Blue); { цвет фона – синий }
clrscr; { залить окно фоном }
TextColor(LightGray); { цвет символов – серый }
k := –1; { счетчик попыток }
GotoXY(12, 5); writeln(' Введите число : ');
repeat { цикл ввода ответа }
GotoXY(20, 9); { установить курсор }
readln(n); { ввести число }
inc(k);
until i = n;
window(20, 22, 60, 24); { определить окно результата }
TextAttr := 2 shl 4 + 14; { желтые символы за зеленом фоне }
clrscr; { залить окно фоном }
GotoXY(6, 2); { установить курсор }
writeln(' Коэффициент невезучести : ', k / max :5:1);
readkey; { ждать нажатия любой клавиши }
TextAttr := 15; { белые символы на черном фоне }
clrscr; { очистить после себя экран }
end.
Генератор случайных чисел формирует число, находящееся в диапазоне от нуля до max – 1. Пользователь вводит числа в одну и ту же позицию на экране до тех пор, пока не угадает это число. При угадывании с первого раза коэффициент невезучести равен нулю.
Модули Dos и WinDos содержат подпрограммы, реализующие возможности операционной системы MS-DOS, такие как переименование, поиск и удаление файлов, получение и установка системного времени, выполнение
Модуль Dos использует строки Паскаля, а WinDos — строки с завершающим нулем.
Модуль обеспечивает работу с экраном в графическом режиме.
Экран в графическом режиме представляется в виде совокупности точек — пикселов (pixel, сокращение от picture element ). Цвет каждого пиксела можно задавать отдельно. Начало координат находится в левом верхнем углу экрана и имеет координаты (0, 0). Количество точек по горизонтали и вертикали ( разрешение экрана ) и количество доступных цветов зависят от графического режима.
В состав оболочки входят несколько драйверов, каждый из которых может работать в нескольких режимах. Режим устанавливается при инициализации графики либо автоматически, либо программистом. Самый "мощный" режим, поддерживаемый модулем Graph, — 640 480 точек, 16 цветов. Модуль Graph обеспечивает:
Модуль Strings предназначен для работы со строками, заканчивающимися нуль-символом, то есть символом с кодом 0 (их часто называют ASCIIZ-строки). Этот вид строк введен в Паскаль специально для работы с длинными строками и программирования под Windows. Модуль Strings содержит функции копирования, сравнения, слияния строк, преобразования их в строки типа string, поиска подстрок и символов.
Презентацию к данной работе Вы можете скачать здесь.
С увеличением объема программы становится невозможным удерживать в памяти все детали. Естественным способом борьбы со сложностью любой задачи является ее разбиение на части. В Паскале задача может быть разделена на более простые и понятные фрагменты — подпрограммы, после чего программу можно рассматривать в более укрупненном виде — на уровне взаимодействия подпрограмм.
Использование подпрограмм является первым шагом к повышению степени абстракции программы и ведет к упрощению ее структуры. Разделение программы на подпрограммы позволяет также избежать избыточности кода, поскольку подпрограмму записывают один раз, а вызывать ее на выполнение можно многократно из разных точек программы.
Следующим шагом в повышении уровня абстракции программы является группировка подпрограмм и связанных с ними данных в отдельные файлы ( модули ), компилируемые раздельно. Разбиение на модули уменьшает время перекомпиляции и облегчает процесс отладки, скрывая несущественные детали за интерфейсом модуля и позволяя отлаживать программу по частям (при этом, возможно, разным программистам).
Подпрограммы нужны для того, чтобы упростить структуру программы и облегчить ее отладку. В виде подпрограмм оформляются логически законченные части программы.
Подпрограмма — это фрагмент кода, к которому можно обратиться по имени. Она описывается один раз, а вызываться может столько раз, сколько необходимо. Одна и та же подпрограмма может обрабатывать различные данные, переданные ей в качестве аргументов.
В Паскале имеется два вида подпрограмм: процедуры и функции. Они определяются в соответствующих разделах описания (до начала блока исполняемых операторов) и имеют незначительные отличия в синтаксисе и правилах вызова.
Само по себе описание не приводит к выполнению подпрограммы. Чтобы подпрограмма выполнилась, ее надо вызвать. .Подпрограмма вызывается по имени, за которым следует список аргументов в круглых скобках. Если аргументов нет, скобки не нужны. Список аргументов при вызове как бы накладывается на список параметров, поэтому они должны попарно соответствовать друг другу. Правила соответствия рассматриваются далее.
Процедура вызывается с помощью отдельного оператора, а функция — в правой части оператора присваивания, например:
inc(i); writeln(a, b, c); { вызовы процедур }
y := sin(x) + 1; { вызов функции }
Внутри подпрограмм можно описывать другие подпрограммы. Они доступны только из той подпрограммы, в которой описаны.
Структура процедуры аналогична структуре основной программы:
procedure имя [(список параметров)]; { заголовок }
разделы описаний
begin
раздел операторов
end;
Квадратные скобки в данном случае не являются элементом синтаксиса, а означают, что список параметров может отсутствовать. Рассмотрим простой пример.
Пример. Найти разность средних арифметических значений двух вещественных массивов из 10 элементов.
Как видно из условия, для двух массивов требуется найти одну и ту же величину — среднее арифметическое. Следовательно, логичным будет оформить его нахождение в виде подпрограммы, которая сможет работать с разными массивами ( ).
program dif_average;
const n = 10;
type mas = array[1 .. n] of real;
var a, b : mas;
i : integer;
dif, av_a, av_b : real;
procedure average(x : mas; var av : real); { 1 }
var i : integer;
begin
av := 0;
for i := 1 to n do av := av + x[i];
av := av / n;
end; { 2 }
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
average(a, av_a); { 3 }
average(b, av_b); { 4 }
dif := av_a – av_b;
writeln('Разность значений ', dif:6:2);
end.
Описание процедуры average расположено в строках с {1} по {2 }. В строках {3} и {4} эта процедура вызывается сначала для обработки массива а, затем — массива b. Массивы передаются в качестве аргументов. Результат вычислений возвращается в главную программу через второй параметр процедуры.
Описание функции отличается от описания процедуры незначительно:
function имя [(список параметров)] : тип; { заголовок }
разделы описаний
begin
раздел операторов
имя := выражение;
end;
Квадратные скобки в данном случае означают, что список параметров может отсутствовать. Функция вычисляет одно значение, которое передается через ее имя. Следовательно, в заголовке должен быть описан тип этого значения, а в теле функции — оператор, присваивающий вычисленное значение ее имени. Он не обязательно должен находиться в конце функции. Более того, таких операторов может быть несколько — это определяется алгоритмом. Рассмотрим пример применения функции для программы, приведенной в предыдущем разделе.
).
program dif_average1;
const n = 10;
type mas = array[1 .. n] of real;
var a, b : mas;
i : integer;
dif : real;
function average(x : mas) : real; { 1 }
var i : integer; { 2 }
av : real;
begin
av := 0;
for i := 1 to n do av := av + x[i];
average := av / n; { 3 }
end;
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
dif := average(a) – average(b); { 4 }
writeln('Разность значений ', dif:6:2)
end.
Оператор {1} представляет собой заголовок функции. Тип функции определен как вещественный, потому что к такому типу относится среднее арифметическое элементов вещественного массива. Оператор {3} присваивает имени {4} функция вызывается дважды: сначала для одного массива, затем для другого.
Глобальными называются переменные, описанные в главной программе. Переменные, которые не были инициализированы явным образом, перед началом выполнения программы обнуляются. Время жизни глобальных переменных — с начала программы и до ее завершения.
Внутри подпрограмм описываются локальные переменные. Время их жизни — с начала работы подпрограммы и до ее окончания. Значения локальных переменных между двумя вызовами одной и той же подпрограммы не сохраняются, и эти переменные предварительно не обнуляются.
Глобальные переменные доступны в любом месте программы или подпрограммы, кроме тех подпрограмм, в которых описаны локальные переменные с такими же именами. Локальные переменные могут использоваться только в подпрограмме, в которой они описаны, и всех вложенных в нее.
Понятно, что никаких дополнительных усилий по передаче глобальных переменных в подпрограмму не требуется: они видны в ней естественным образом. Этот способ обмена информацией между главной программой и подпрограммой — самый простой, но он же и самый плохой.
В подавляющем большинстве случаев для обмена данными между вызывающей и вызываемой подпрограммами предпочтительнее использовать механизм параметров. Если все данные передаются подпрограммам через списки параметров, для локализации места ошибки достаточно просмотреть заголовки подпрограмм, а затем — тексты только тех из них, в которые передается интересующая нас переменная.
Список параметров, то есть величин, передаваемых в подпрограмму и обратно, содержится в ее заголовке. Для каждого параметра обычно задаются его имя, тип и способ передачи. Либо тип, либо способ передачи могут не указываться.
В заголовке подпрограммы нельзя вводить описание нового типа — там должны использоваться либо имена стандартных типов, либо имена типов, описанных программистом ранее в разделе type.
В Паскале существует четыре вида параметров: значения, переменные, константы и нетипизированные параметры.
Кроме того, по другим критериям можно выделить особые виды параметров:
Параметр-значение описывается в заголовке подпрограммы следующим образом:
имя : тип;
Например, передача величины целого типа в процедуру Р записывается так:
procedure P(x : integer);
Имя параметра может быть произвольным. Параметр х можно представить себе как локальную переменную, которая получает свое значение из главной программы при вызове подпрограммы. В подпрограмму передается копия значения аргумента.
Механизм передачи следующий: из ячейки памяти, в которой хранится переменная, передаваемая в подпрограмму, берется ее значение и копируется в область сегмента стека, называемую областью параметров. Подпрограмма работает с этой копией, следовательно, доступа к ячейке, где хранится сама переменная, не имеет. По завершении работы подпрограммы стек освобождается. Такой способ называется передачей по значению. Ясно, что им можно пользоваться только для величин, которые не должны измениться после выполнения подпрограммы, то есть для ее исходных данных.
При вызове подпрограммы на месте параметра, передаваемого по значению, может находиться выражение (а также, конечно, его частные случаи — переменная или константа). Тип выражения должен быть совместим по присваиванию с типом параметра.
Например, если в вызывающей программе описаны переменные
var x : integer; c : byte; y : longint;
то следующие вызовы подпрограммы Р, заголовок которой описан выше, будут синтаксически правильными:
P(x); P(c); P(y); P(200); P(x div 4 + 1);
Недостатками передачи по значению являются затраты времени на копирование параметра, затраты памяти в стеке и опасность его переполнения, когда речь идет о параметрах, занимающих много места, например массивах большого размера.
Признаком var перед описанием параметра:
var имя : тип;
Например, параметр-переменная целого типа в процедуре Р записывается так:
procedure P(var x : integer);
При вызове подпрограммы в область параметров копируется не значение переменной, а ее адрес, и подпрограмма через него имеет доступ к ячейке, в которой хранится переменная. Этот способ передачи параметров называется передачей по адресу. Подпрограмма работает непосредственно с переменной из вызывающей программы и, следовательно, может ее изменить, поэтому результаты работы подпрограммы должны быть только
При вызове подпрограммы на месте параметра-переменной может находиться только ссылка на переменную точно того же типа. Исходные данные в подпрограмму передавать по адресу не рекомендуется, чтобы исключить возможность их непреднамеренного изменения.
Проиллюстрируем передачу параметров-значений и параметров-переменных на примере ().
var a, b, c, d, e : word;
procedure X(a, b, c : word; var d : word);
var e : word;
begin
c := a + b; d := c; e := c;
writeln ('Значения переменных в подпрограмме:');
writeln ('c = ', c, ' d = ', d, ' e = ', e);
end;
begin
a := 3; b := 5;
x(a, b, c, d);
writeln ('Значения переменных в главной программе:');
writeln ('c = ', c, ' d = ', d, ' e = ', e);
end.
Результаты работы этой программы приведены ниже.
Значения переменных в подпрограмме: c = 8 d = 8 e = 8 Значения переменных в главной программе: c = 0 d = 8 e = 0
Как видите, значение переменной с в главной программе не изменилось, поскольку переменная передавалась по значению, а значение переменной е не изменилось потому, что в подпрограмме была описана локальная переменная с тем же именем.
const перед описанием параметра. Оно говорит о том, что в пределах подпрограммы данный параметр изменить невозможно:
const имя : тип;
При вызове подпрограммы на месте параметра-константы, как и в случае параметра-значения, может быть записано выражение, тип которого совместим по присваиванию с типом параметра. Однако компилятор при передаче параметров-констант формирует более эффективный код, чем для параметров-значений. Фактически
Например, параметр-константа целого типа в процедуре Р записывается так:
procedure P(const x : integer);
Подведем итоги. Если данные передаются в подпрограмму по значению, их можно изменять, но эти изменения затронут только копию в области параметров и не отразятся на значении аргумента в вызывающей программе. Если данные передаются как
Параметры составных типов (массивы, записи, строки) предпочтительнее передавать как константы, потому что при этом не расходуется время на копирование и место в стеке (размер стека не может превышать 64 Кбайт, а по умолчанию устанавливается равным 16 Кбайт).
Результаты работы процедуры следует передавать через
Чтобы можно было передавать в подпрограмму массивы с различным количеством элементов, в списке ее параметров разрешается определять открытый массив, например:
procedure P(a : array of real);
Он может быть только одномерным и состоять из элементов любого типа, кроме файлового. На место открытого массива можно передавать одномерный массив любой размерности, состоящий из элементов такого же типа. Передавать открытый массив можно как значение, переменную или константу.
Его элементы нумеруются с нуля. Номер максимального элемента в массиве можно определить с помощью функции ).
function max_el(const mas : array of integer) : integer;
var i, max : integer;
begin
max := mas[0];
for i := 0 to High(mas) do
if mas[i] > max then max := mas[i];
max_el := max;
end;
Для передачи в подпрограмму по адресу строк любой длины используется либо специальный тип OpenString, называемый открытой строкой, либо тип string при включенном режиме {$P+} (по умолчанию этот режим выключен).
Напомню, что если параметр передается в подпрограмму как значение или константа, от него не требуется точного совпадения с типом аргумента — достаточно соответствия по присваиванию. Поскольку присваивать друг другу строки разной длины можно, их можно использовать и в качестве параметров, то есть на место параметра-значения или параметра-константы типа string можно передавать строку любой длины без использования открытых строк.
Пример передачи строк в подпрограмму:
type s20 = string[20];
var s1 : string[40];
s2 : string[10];
procedure P(const x : s20; y : string; var z : openstring);
...
begin
... P(s2, s1, s1); ...
end.
Все рассмотренные параметры подпрограмм позволяли выполнять один и тот же алгоритм с различными данными. В Паскале есть и другая возможность — параметризовать алгоритм функциями и процедурами. Это может пригодиться, если требуется выполнить одну и ту же последовательность действий, внутри которой выполняется обращение к разным функциям или процедурам.
Описание параметра подпрограммы в большинстве случаев состоит из имени и типа. Имя функции является константой процедурного ( функционального ) типа, который требуется описать в разделе type, например:
type fun = function(x : real) : real;
pr = procedure;
proc = procedure(a, b : word; var c : word);
Здесь вводится описание трех типов. Первый из них соответствует любой функции с одним аргументом вещественного типа, возвращающей вещественное значение, второй — процедуре без параметров, а третий — процедуре с тремя параметрами типа word. Как видно из примеров, описание процедурного (функционального) типа соответствует заголовку подпрограммы без имени. Имя типа используется затем в списке параметров подпрограммы аналогично другим типам.
Пример. Программа, вычисляющая определенные интегралы методом прямоугольников для двух функций
$$q=\frac{2x}{\sqrt{1-sin\:2x}}\qquad\gamma=cos\:x-0,2x $$
).
program integrals;
type fun = function(x : real) : real; { 1 }
var a, b : real;
n : integer;
{$F+}
function Q(x : real) : real;
begin
Q := 2 * x / sqrt(1 – sin(2 * x));
end;
function R(x : real) : real;
begin
R := cos(x) – 0.2 * x;
end;
{$F–}
function integr(f : fun; a, b : real; n : integer) : real;
var sum, x, h : real;
i : integer;
begin
h := (b – a) / n; sum := 0; x := a;
for i := 1 to n do begin
sum := sum + f(x); x := x + h;
end;
integr := sum * h;
end;
begin
writeLn('Введите интервал и количество шагов');
readln(a, b, n);
writeln('Интеграл для первой функции: ', integr(Q, a, b, n):8:3);
writeln(' Интеграл для второй функции: ', integr(R, a, b, n):8:3);
end.
Вычисление определенного интеграла
Итак, чтобы передать имя функции или процедуры в подпрограмму, необходимо:
{$F +}, определяющий дальнюю адресацию. При этом компилятор формирует полный адрес, состоящий из сегмента и смещения. Альтернативный способ — указать в заголовке каждой функции директиву far:function Q(x : real) : real; far;
Рекурсивной называется подпрограмма, в которой содержится обращение к самой себе. Такая рекурсия называется прямой. Есть также косвенная рекурсия, когда две или более подпрограмм вызывают друг друга.
При обращении подпрограммы к самой себе происходит то же самое, что и при обращении к любой другой функции или процедуре: в стек записывается адрес возврата, резервируется место под локальные переменные, происходит передача параметров, после чего управление передается первому исполняемому оператору подпрограммы. При повторном вызове этот процесс повторяется. Для завершения вычислений каждая рекурсивная подпрограмма должна содержать хотя бы одну нерекурсивную ветвь, заканчивающуюся возвратом в вызывающую программу.
При завершении подпрограммы область ее локальных переменных освобождается, а управление передается на оператор, следующий за рекурсивным вызовом.
Простой пример рекурсивной функции — вычисление факториала (это не означает, что факториал следует вычислять именно так). Чтобы получить факториал числа n, требуется умножить на n факториал ( n – 1)!. Известно также, что 0! = 1 и 1! = 1.
function fact(n : byte) : longint;
begin
if (n = 0) or (n = 1) then fact := 1
else fact := n * fact(n – 1);
end;
Рекурсивные подпрограммы чаще всего применяют для компактной записи рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно, например с двоичными деревьями. Любую рекурсивную функцию можно реализовать без применения рекурсии: для этого программист должен сам обеспечить распределение памяти под необходимое количество копий параметров.
Достоинством рекурсии является компактная запись. К недостаткам относятся расход времени и памяти на повторные вызовы функции и передачу ей параметров, а главное, опасность переполнения стека.
Модуль — это подключаемая к программе библиотека ресурсов. Он может содержать описания типов, констант, переменных и подпрограмм. В модуль обычно объединяют связанные между собой ресурсы: например, в составе оболочки есть модуль Graph для работы с экраном в графическом режиме. Модули применяются как библиотеки, которые могут использоваться различными программами, и для разбиения сложной программы на составные части.
Чтобы использовать модуль, достаточно знать только его интерфейс: детали реализации модуля скрыты от его пользователя. Это позволяет успешно создавать программы большого объема, поскольку мозг человека может хранить одновременно довольно ограниченный объем информации. Кроме того, использование модулей позволяет преодолеть ограничение в один сегмент на объем кода исполняемой программы, поскольку код каждого подключаемого к программе модуля содержится в отдельном сегменте.
Модули можно разделить на стандартные, которые входят в состав системы программирования, и пользовательские, то есть создаваемые программистом. Чтобы подключить модуль к программе, его требуется предварительно скомпилировать. Результат компиляции каждого модуля хранится на диске в отдельном файле с расширением .tpu.
Исходный текст каждого модуля хранится в отдельном файле с расширением .pas. Модуль состоит из секций (разделов). Общая структура модуля:
unit имя; { заголовок модуля }
interface { ------------- интерфейсная секция модуля }
{ описание глобальных элементов модуля (видимых извне) }
implementation { --------------- секция реализации модуля }
{ описание локальных (внутренних) элементов модуля }
begin { ------------------- секция инициализации }
{ может отсутствовать }
end.
unit.Модуль может использовать другие модули, для этого их надо перечислить в операторе uses, который может находиться только непосредственно после ключевых слов interface или implementation. Если модули подключаются к интерфейсной части, все константы и типы данных, описанные в интерфейсной секции этих модулей, могут использоваться в любом описании в интерфейсной части данного модуля. Если модули подключаются к части реализации, все описания из этих модулей могут использоваться только в секции реализации.
В интерфейсной секции модуля определяют константы, типы данных, переменные, а также заголовки процедур и функций. Полностью же подпрограммы описываются в секции реализации, скрытой от пользователя модуля. Это естественно, поскольку для применения подпрограммы требуется знать только информацию, которая содержится в ее заголовке.
В секции реализации описываются подпрограммы, заголовки которых приведены в интерфейсной части. Заголовок подпрограммы должен или быть идентичным указанному в секции интерфейса, или состоять только из ключевого слова procedure или function и имени подпрограммы. Для функции также указывается ее тип.
Кроме того, в этой секции можно определять константы, типы данных, переменные и внутренние подпрограммы. Они используются внешними элементами модуля и видны только в секции реализации.
Секция инициализации предназначена для присваивания начальных значений переменным, используемым в модуле или в программе, к которой он подключен. Операторы, расположенные в секции инициализации модуля, выполняются перед операторами основной программы. Если к программе подключено более одного модуля, их секции инициализации вызываются на выполнение в порядке, указанном в операторе uses.
В оболочках Borland Pascal и Turbo Pascal результат компиляции по умолчанию размещается в оперативной памяти и на диск не записывается. Поэтому для сохранения скомпилированного модуля на диске требуется установить значение пункта Compile ( Destination в значение Disk. Компилятор создаст файл с расширением .tpu, который надо переместить в специальный каталог, путь к которому указан в пункте меню Options ( Directories в поле Unit Directories.
В качестве примера оформим в виде модуля подпрограмму вычисления среднего арифметического значения элементов массива из ().
unit Average;
interface
const n = 10;
type mas = array[1 .. n] of real;
procedure average(x : mas; var av : real);
implementation
procedure average(x : mas; var av : real);
var i : integer;
begin
av := 0;
for i := 1 to n do av := av + x[i];
av := av / n;
end;
end.
Список параметров подпрограммы в разделе реализации указывать не обязательно.
Чтобы использовать в программе величины, описанные в интерфейсной части модуля, имя модуля следует указать в разделе uses. Можно записать несколько имен модулей через запятую, например:
program example; uses Average, Graph, Crt;
После этого все описания, расположенные в интерфейсных секциях модулей, становятся известными в программе, и ими можно пользоваться точно так же, как и величинами, определенными в ней непосредственно. Поиск модулей выполняется сначала в библиотеке исполняющей системы, затем в текущем каталоге, а после этого — в каталогах, заданных в диалоговом окне Options ( Directories.
Если в программе описана величина с тем же именем, что и в модуле, для обращения к величине из модуля требуется перед ее именем указать через точку имя модуля.
System, который содержит библиотеку исполняющей системы Паскаля.Пример использования модуля из приведен в . Программа находит разность средних арифметических значений двух вещественных массивов.
program dif_average;
uses Average;
var a, b : mas;
i : integer;
dif, av_a, av_b : real;
begin
for i := 1 to n do read(a[i]);
for i := 1 to n do read(b[i]);
average(a, av_a);
average(b, av_b);
dif := av_a – av_b;
writeln('Разность значений ', dif:6:2);
end.
В Паскале имеется ряд стандартных модулей, в которых описано большое количество встроенных констант, типов, переменных и подпрограмм. Каждый модуль содержит связанные между собой ресурсы. Ниже приводится краткая характеристика модулей Паскаля.
Модуль System содержит базовые средства языка, которые поддерживают ввод-вывод, работу со строками, операции с плавающей точкой и динамическое распределение памяти. Этот модуль автоматически используется во всех программах, его не требуется указывать в операторе uses. Он содержит все стандартные и встроенные процедуры, функции, константы и переменные Паскаля.
Модуль предназначен для организации эффективной работы с экраном, клавиатурой и встроенным динамиком. При
В текстовом режиме экран представляется как совокупность строк и столбцов. Каждый символ располагается на так называемом знакоместе на пересечении строки и столбца. Символы хранятся в специальной части оперативной памяти, называемой видеопамятью. Ее содержимое отображается на экране.
Модуль позволяет:
).
program luck;
uses crt;
const max = 10;
var i, k, n : integer;
begin
clrscr; { очистить экран }
randomize;
i := random(max); { загадать число }
window(20, 5, 60, 20); { определить окно }
TextBackGround(Blue); { цвет фона – синий }
clrscr; { залить окно фоном }
TextColor(LightGray); { цвет символов – серый }
k := –1; { счетчик попыток }
GotoXY(12, 5); writeln(' Введите число : ');
repeat { цикл ввода ответа }
GotoXY(20, 9); { установить курсор }
readln(n); { ввести число }
inc(k);
until i = n;
window(20, 22, 60, 24); { определить окно результата }
TextAttr := 2 shl 4 + 14; { желтые символы за зеленом фоне }
clrscr; { залить окно фоном }
GotoXY(6, 2); { установить курсор }
writeln(' Коэффициент невезучести : ', k / max :5:1);
readkey; { ждать нажатия любой клавиши }
TextAttr := 15; { белые символы на черном фоне }
clrscr; { очистить после себя экран }
end.
Генератор случайных чисел формирует число, находящееся в диапазоне от нуля до max – 1. Пользователь вводит числа в одну и ту же позицию на экране до тех пор, пока не угадает это число. При угадывании с первого раза коэффициент невезучести равен нулю.
Модули Dos и WinDos содержат подпрограммы, реализующие возможности операционной системы MS-DOS, такие как переименование, поиск и удаление файлов, получение и установка системного времени, выполнение
Модуль Dos использует строки Паскаля, а WinDos — строки с завершающим нулем.
Модуль обеспечивает работу с экраном в графическом режиме.
Экран в графическом режиме представляется в виде совокупности точек — пикселов (pixel, сокращение от picture element ). Цвет каждого пиксела можно задавать отдельно. Начало координат находится в левом верхнем углу экрана и имеет координаты (0, 0). Количество точек по горизонтали и вертикали ( разрешение экрана ) и количество доступных цветов зависят от графического режима.
В состав оболочки входят несколько драйверов, каждый из которых может работать в нескольких режимах. Режим устанавливается при инициализации графики либо автоматически, либо программистом. Самый "мощный" режим, поддерживаемый модулем Graph, — 640 480 точек, 16 цветов. Модуль Graph обеспечивает:
Модуль Strings предназначен для работы со строками, заканчивающимися нуль-символом, то есть символом с кодом 0 (их часто называют ASCIIZ-строки). Этот вид строк введен в Паскаль специально для работы с длинными строками и программирования под Windows. Модуль Strings содержит функции копирования, сравнения, слияния строк, преобразования их в строки типа string, поиска подстрок и символов.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.