Учебное пособие-конспект по языку Pascal - Часть 2

ЧАСТЬ 2. Некоторые сложные типы данных – множества, записи, файлы.  Модульное программирование.

"Всё выше, выше и выше
Стремим мы полёт наших птиц."
(Из песни)

Тема 10. МНОЖЕСТВА

ЧТО ТАКОЕ МНОЖЕСТВО
Множественный тип Паскаля set почти соответствует математическому понятию множества (многое, мыслимое как единое). Отличается от математического понятия лишь тем, что set может состоять из элементов лишь одного и того же, любого упорядоченного, типа (тогда этот тип называют "базовым" типом множества). В математике множества обозначаются фигурными скобками, но в Паскале они обозначаются квадратными скобками, например, [7,5,1], ['А','Б','В','Щ','7','Z']. Непустое множество состоит из элементов. Список элементов ("конструктор" множества) перечисляется через запятую или интервально, например, [1..3,5,10,15,20,50] {номиналы советской денежной мелочи}, ['A'..'Z','a'..'z'] {заглавные и строчные латинские буквы}. Порядок элементов во множестве не важен. Например, [7,5,1]=[1,7,5].
Пустое множество [] – множество, не содержащее элементов. Подмножество какого-либо данного множества – это любое множество, состоящее только из элементов данного множества. Определена операция логического типа над множествами in : принадлежность (включение) одного множества другому. Пустое множество принадлежит любому множеству. Любое множество всегда принадлежит самому себе. Несобственные подмножества множества – это множество и пустое множество. Собственные подмножества множества – любые его подмножества, кроме несобственных.

КАК МНОЖЕСТВО ЗАДАЁТСЯ В ПАСКАЛЕ
Количество элементов множества – величина переменная, но при задании типа set заказывается вполне определённое  количество памяти. В Т-Паскале количество непустых элементов множества может быть от 0 до 255. Для хранения каждых 8 элементов множества Паскаль заказывает 1 байт памяти (по 1 биту на каждый элемент), т.е. для всех 256 (включая и пустое множество) возможных элементов заказывается от 1 до 16 байт памяти.
Если нужно множество из большего количества элементов, то придётся самостоятельно делать объект (совокупность данных и обрабатывающих их действий) большой длины (про объекты см. далее).
Описание обычного множества в Паскале делается по форме:
ИмяМножества: set of БазовыйТипЭлементов; {в том числе перечислимый или интервальный:}
ИмяМножества: set of (ПеречислениеВозможныхЭлементов);
ИмяМножества: set of ИнтервалВозможныхЭлементов;
Примеры описания:
type {ограничивает возможные значения элементов множества одним и тем же упорядоченным типом}
Chisla: set of integer;
Children: set of (Son,Daughter);
{при этом множество Children может принимать значения [] , [Son] , [Daughter] , [Son,Daughter] }
Bukvy: set of  'A'..'F';
Brukvy: set of  7..15;
var  {вводит экземпляры переменных типа множество указанного типа}
M1,M2: chisla;
M3,M4: children;
M5,M6: bukvy;
M7,M8: brukvy;
Примеры конструкторов множества (т.е. придание переменной типа множество конкретного значения):
begin
M1:=[1,2,3,7,77,44,22];
M2:=[1,2,3,8..10];
M3:=[Son];
M5:=['B'..'D','F'];
M7:=[7,9..13];
end;

ОПЕРАЦИИ НАД МНОЖЕСТВАМИ
Объединение (сложение) множеств:
setOne:=[1..3]+[5..10]+[15,20]+[50];    {Теперь setOne=[1..3,5..10,15,20,50]   }
Для объединения множества и одиночного элемента быстрее работает процедура:
include(Множество,НовыйЭлемент);
Разность (исключение) множеств:
setOne:=setOne-[6..9];   {теперь setOne=[1..3,5,10,15,20,50 ] – номиналы мелочи времён СССР}
Для исключения из множества одиночного элемента быстрее работает процедура:
exclude(Множество,ИсключаемыйЭлемент);
Пересечение множеств:
Cuprum:=setOne*[1..5];   {теперь Cuprum=[1..3,5]  – номиналы медной мелочи времён СССР}
Логическая проверка принадлежания элемента множеству (вхождения элемента во множество):
Элемент    in   Множество
Примеры:
5 in Cuprum                {  =true}
[] in Cuprum                {  =true}
25 in Cuprum                {  =false}
if 5 in Cuprum then writeln('Пятак в этом множестве есть');
Проверка равенства множеств:
Множество1=Множество2
Проверка неравенства множеств:
Множество1<>Множество2
Проверка принадлежания подмножества множеству (вхождения, включения одного множества в другое):
Множество1<=Множество2      {Первое – подмножество второго}
Множество1>=Множество2      {Второе – подмножество первого}
Пример:
if Cuprum<=setOne then writeln('Медяки - подмножество мелочи.');

ПОЛЕЗНЫЕ ПРИМЕРЫ РАБОТЫ СО МНОЖЕСТВАМИ
program Druzja;
const
b1='A', b2='Z';
bukvy=b1..b2;
type
names=array [b1..b2] of string [20];   {Имена людей}
FriendsType: set of  bukvy;                {Множество первых букв имён друзей}
var
Friends: FriendsType;
b: char;
begin
names['A']:='Anna';
names['B']:='Boris';
names['F']:='Fedor';
names['V']:='Vasia';
names['G']:='Goga';
Friends:=['A','B','F','V'];
for b:=b1 to b2 do   if    b   in   Friends   then write(names[b],'   ,    ');
writeln;
Friends:=Friends+'G'-'B';
for b:=b1 to b2 do   if    b   in   Friends   then write(names[b],'   ,    ');
writeln;
readln;
end.

{ИСПРАВЛЕННЫЙ пример из книги : Абрамов, Трифонов, Трифонова  "Введение в язык Паскаль".}
program ReshetoEratosfena;  {расчёт первых простых чисел <= 255 методом Эратосфена}
const
        N=255;
type
        MnNumbers=set of 2..N;
var
        Numb,Simple: MnNumbers;
         p,k                : 2..2*N;
begin
        Numb:=[2..N];      {множество чисел от 2 до N }
         Simple:=[];             {множество найденных простых чисел}
         p:=2;                {первое простое число}
         repeat begin
           {поиск ближайшего невычеркнутого числа:}
           while not (p in Numb) do p:=p+1;    {если число p уже вычеркнуто, то перейти на следующее}
           Simple:=Simple+[p];  {Нашли первое невычеркнутое - значит, простое - число. Добавили его во множество}
{Теперь вычёркиваем его и все кратные ему из проверяемого множества Nump ...}
           k:=p;
           repeat begin
                Numb:=Numb-[k];      {вычёркивание}
                k:=k+p;                {переменная k – последовательные значения, кратные p}
           end  until k>N;     {... до тех пор, пока не превышено наибольшее число во множестве Numb.}
         end  until Numb=[];   {... искать и вычёркивать невычеркнутые до тех пор, пока они не исчерапются.}
        {Выведем все найденные простые числа на экран. Здесь переменная k уже не кратна p, а просто идёт подряд:}
         for k:=2 to N do  if (k in Simple) then writeln(k);
         readln;
end.

Тема 11. ЗАПИСИ

ТИП КОМБИНИРОВАННЫЙ (запись, record)
Это УПОРЯДОЧЕННЫЙ набор однородных или НЕОДНОРОДНЫХ элементов. Как анкета с данными различного типа, как страница школьного дневника. (Для сравнения, массив – это упорядоченый набор только однородных элементов.)
Элементы записи называются ПОЛЯМИ. У них есть имена. Поля тоже могут быть записями (более низкой ступени иерархии). Каждое поле должно иметь ФИКСИРОВАННЫЙ размер в байтах. (Следовательно, использовать тип string нельзя, а вот, например, тип string[80] можно.)
Удобство записей состоит в том, что к ним можно обращаться и как к единому целому (вся анкета), и к их отдельным полям (отдельные данные из анекты).
Объем записи в Т-Паскаль не должен превышать 256*256 байт, т.е. 64 Кбайт. В Б- и О-Паскаль для 32-разрядных процессоров это ограничение снято.
Общий вид объявления типа и экземпляров записи таков:
type
ИмяТипаЗаписи=record
Имя1гоПоля:ЕгоТип;
Имя2гоПоля:ЕгоТип;
Имя3,Имя4,Имя5итдПолей:ИхТип;
ИТакДалее:ИхТип;
end;  {конец описания типа записи}
var
Имя1Записи,Имя2Записи:ИмяТипаЗаписи;

ОБРАЩЕНИЕ К ПОЛЯМ ЗАПИСИ
Есть 2 способа обращения к полям.
Способ А) по имени записи в целом ("квалифицирующий идентификатор"), затем точка, затем имя поля:
ИмяЗаписи.ИмяПоля  , например:
Anketa.Name:='Вася';
Способ Б) внутри действия ОПЕРАТОРА ПРИСОЕДИНЕНИЯ with просто по имени поля:
with ИмяЗаписи do ОператорСИменемПоля;
например:
with Anketa do Name:='Вася';

ПРИМЕР ИСПОЛЬЗОВАНИЯ ЗАПИСЕЙ
program Ankety;
type
        Blank=record                {тип Запись. Далее описание её полей:}
            Number:integer;
                Family,Name,Surname:string[20];
                Age:real;
         end; {record Blank}
var
        a1,a2,a3:blank;          {переменные-анкеты  типа blank}
begin
    {Способ обращения А:}
    a1.Number:=1;
    a1.Family:='Иванов';
    a1.Name:='Иван';
    a1.surname:='Иваныч';
    a1.age:=17.5;
    {Способ обращения Б:}
    with a2 do begin
          Number:=2;
          Family:='Петров';
          Name:='Пётр';
          surname:='Петрович';
          age:=16.0;
    end; {a2}
    {Можно обращаться целиком ко всей записи:}
    a3:=a2;
    {Вывод результатов:}
    with a3 do begin
writeln('Анкета №',number);
writeln(Name,'  ',Surname,'  ',Family,'   ,  ',age:0:1,' лет.');
    end;
    readln;
end.

Для студентов особое задание на самостоятельную работу: сконструируйте тип данных "комплексные числа" через тип-запись с полями "действительная часть" и "мнимая часть". Сконструируйте процедуры и функции для арифметических операций с комплексными числами.
Для особо грамотных студентов – задание повышенной трудности: сконструируйте тип данных "винт" через тип-запись и процедуры и функции для осуществления винтового исчисления.

Тема 12. ФАЙЛЫ

Одной из разновидностей динамических структур данных является файл (тетрадь, стопка листов). Файл (тетрадь) – это набор неограниченной длины данных определённого формата (т.е. одного и того же типа), предназначенный для долговременного хранения под управлением операционной системы. Для сравнения, все предыдущие типы данных обладали конечной, наперёд заданной, длиной. Файл удобен тем, что мы расходуем место на долговременном носителе по мере возникновения потребности в нём, а не заказываем заранее "с большим запасом на всякий случай". Части файла ОС может размещать на диске физически не подряд, а в разных местах. Список частей файла, как они хранятся на диске, ОС содержит в специальном служебном месте – таблице размещения файлов. Есть разные варианты ведения таких списков (FAT, RAW и др.), но это забота ОС, мы это пока на это не будем обращать внимания.
С точки зрения программ, работающих с файлами, файл похож на киноленту с последовательными кадрами одинакового типа. Вот такой отдельный кадр файла, с которым ОС дозволяет работать в кадждое мгновение времени, называется ФАЙЛОВОЙ (или БУФЕРНОЙ) ПЕРЕМЕННОЙ. Тип "файловый" Паскаля описывает именно файловую (она же буферная) переменную.
В Паскале предусмотрено 3 типа файловой переменной (файлового буфера):
1) буфер типизированных файлов: file of ТипКомпонент;
2) буфер текстовых файлов : text;    {В О-Паскаль этот тип называется иначе, TextFile}
3) буфер нетипизированных файлов: file;
Типизированные файлы состоят из переменных (компонент) указанного типа, одинаковой длины. Удобен для записи больших массивов, записей, объектов. Текстовые файлы состоят из строк, строки могут быть переменной длины. Нетипизированные файлы состоят из кадров указанного при открытии файла размера. Быстрее всего чтение и запись происходит с нетипизированными файлами. Обычно чем больше блок данных, тем быстрее работа, причём желательно, чтобы размер блока данных был бы кратен 1 дисковому сектору (512 байт). В DOS наибольшая скорость досигалась, когда размер блока данных был равен кадру и при этом равен 1 сегменту (64 КБайт, т.е. 64*1024 байт). В Delphi размер блока данных может быть до 2 ГБ.
В Т-, Б- и О-Паскаль в модулях Dos и Windows есть много десятков процедур и функций для работы с файлами. Укажем лишь необходимые. Остальные упомянем в описании содержимого модуля dos (см. далее).

ПРОЦЕДУРЫ И ФУНКЦИИ, НЕОБХОДИМЫЕ ДЛЯ РАБОТЫ С ФАЙЛАМИ
Assign(ФайловаяПеременная,ИмяФайла) – сопоставляет файловую переменную с файлом определённого названия (ИмяФайла – типа строка). В О-Паскаль эта процедура называется иначе – AssignFile.
Rewrite(ФайловаяПеременная) – открывает файл для записи впервые или заново, с самого начала (если в файле были данные, то они пропадут). Файлы, открытые процедурой rewrite, доступны не только для записи, но и для чтения (но только этих новых данных).
Reset(ФайловаяПеременная) – открывает файл для чтения и продолжения записи (если в файле были данные, то они не пропадут). Файлы, открытые процедурой reset, доступны не только для чтения, но и для записи (в Б-Паскале режим доступа к файлу ппо процедуре reset можно ограничить).
В Т-Паскале есть функция FileSize(ФайловаяПеременная):longint – даёт размер файла. Размер даётся в единицах, равных размеру файловой переменной (у типизированных и нетипизированных) либо в байтах (у текстовых). Файл должен быть при этом открыт (для чтения или записи).
Close(ФайловаяПеременная) – закрывает файл. В О-Паскаль это называется CloseFile.

ОСОБЕННОСТИ ТЕКСТОВЫХ ФАЙЛОВ
Состоит из строк переменной длины. Читать и писать строки можно только последовательно. В конце каждой строки автоматически добавляется записывается пара байтов #13 и #10. В конце файла автоматически добавляется  байт #26. Строки переменной длины не обладают последовательными номерами, строки можно читать и писать только последовательно либо писать сразу после конца последней строки.
Процедура Append(ФайловаяПеременная) – открыть текстовый файл для добавления очередной строки в его конец.
Процедуры Writeln(ФайловаяПеременная,Строки) и Readln(ФайловаяПеременная,Строки) можно использовать только с текстовыми файлами, они работают целиком со строкой, состоящей из символов и чисел.
Процедуры Write(ФайловаяПеременная,Строки) и Read(ФайловаяПеременная,Строки) работают с подстроками из символов и чисел, но игнорируют признак "конец строки".
В списке ввода-вывода можно указать сразу несколько строк или чисел (через запятую).
EOLn(var f:text):boolean – если текущая позиция – в конце строки, то даёт true.
SeekEOLn(var f:text):boolean – передвигается на первый значащий (т.е. кроме пробелов и табуляций) символ и даёт false, иначе (т.е. если достигнут конец строки) даёт true.
Для вывода чисел можно использовать форматный вывод (см. выше).

ОБЩИЕ ОСОБЕННОСТИ ТИПИЗИРОВАННЫХ И НЕТИПИЗИРОВАННЫХ ФАЙЛОВ
Нумерация элементов файла (кадров) N ведётся начиная с 0 и заканчивая FileSize(ФайловаяПеременная)-1 .
Функция FilePos(ФайловаяПеременная):longint даёт номер N текущей позиции кадра в файле.
Процедура Seek(ФайловаяПеременная,N) ставит текущую позицию в этом файле на элемент  (кадр) с номером N.

ОСОБЕННОСТИ ТИПИЗИРОВАНЫХ ФАЙЛОВ
Запись делается  процедурой Write(ФайловаяПеременная,БлокДанных).
Чтение –  процедурой Read(ФайловаяПеременная,БлокДанных).
В списке ввода-вывода можно указать сразу несколько блоков данных (через запятую).

ОСОБЕННОСТИ НЕТИПИЗИРОВАННЫХ ФАЙЛОВ
Самые быстродействующие. Легко совместимы при чтении и записи с любыми типизированными файлами.
Открытие файла для записи должно включать в себя указание на размер одного кадра Rewrite(ФайловаяПеременная,РазмерКадра) в байтах.
Запись делается процедурой
BlockWrite(var ФайловаяПеременная; var БлокДанных; ЧислоКадров:integer; var РезультатКадров:integer);
здесь ЧислоКадров – сколько кадров файла пишется за одно обращение к файлу,
причём необходимо, чтобы размер блока данных<=размера нетипизированного кадра * ЧислоКадров ,
и желательно, чтобы размер блока данных=размеру нетипизированного кадра * ЧислоКадров.
Сколько кадров было действительно записано за одно обращение, даётся переменной РезультатКадров.
Переменная РезультатКадров – необязательная. В простейшем  варианте ЧислоКадров=1.
Открытие файла для чтения должно включать в себя указание на размер одного кадра Reset(ФайловаяПеременная,РазмерКадра) в байтах.
Чтение делается процедурой
BlockRead(var ФайловаяПеременная; var БлокДанных; ЧислоКадров:integer; var РезультатКадров:integer).

ПРИМЕР РАБОТЫ С ФАЙЛАМИ
program Files;
uses
crt,dos;
const
        NumStud=10; LengthName=30;                {глобальные постоянные}
type
        NamesType=array[1..NumStud] of string[LengthName]; {до 10 студентов с именами длиной до 30 букв}
var
    Names:NamesType;    {массив типа NamesType}
    f1:text;                {текстовый файл}         
    f2:file of NamesType;  {типизированный файл – файл массивов типа NamesType}
    f3:file;                {нетипизированный файл}

procedure ReadData;  {Чтение данных с клавиатуры}
var
  i:integer;
begin
 for i:=1 to NumStud do begin
       write('Человек №',i,' :  его зовут ');
       readln(Names[i]);
 end;
end; {ReadData}

procedure WriteF1;   {Как записывать в текстовый файл}
var
   i:integer;
begin
  rewrite(f1);
  for i:=1 to NumStud do writeln(f1,Names[i]);    {Запись в файл построчно}
  close(f1);
end; {writeF1}

procedure WriteF2;  {Как записывать в типизированный файл}
begin
  rewrite(f2);
  write(f2,Names);    {Запись в файл целиком кадром}
  close(f2);
end; {writeF2}

procedure WriteF3;  {Как записывать в нетипизированный файл}
var
   result:word;
begin
  rewrite(f3,NumStud*(LengthName+1));  {Объём блока = NumStud * объём, требуемый для одной строки,
с учётом и значащих байтов, и служебного}
  BlockWrite(f3,Names,1,result);                {Запись в файл по одному блоку}
  close(f3);
end; {writeF3}

procedure ReadF1; {чтение из текстового файла}
var
   i:integer;
begin
  reset(f1);
  for i:=1 to NumStud do readln(f1,Names[i]);   {чтение построчно}
  close(f1);
end; {readF1}

procedure ReadF2; {чтение из типизированного файла}
begin
  reset(f2);
  read(f2,Names); {чтение из файла целиком кадром}
  close(f2);
end; {readF2}

procedure ReadF3;    {чтение из нетипизированного файла}
var
   result:word;
begin
  reset(f3,NumStud*(LengthName+1));
  BlockRead(f3,Names,1,result); {чтение по одному блоку}
  close(f3);
end; {readF3}

procedure WriteData;  {Запись данных на экран}
var
  i:integer;
begin
 for i:=1 to NumStud do begin
       write('Человек №',i,' :  его зовут ');
       writeln(Names[i]);
 end;
end; {ReadData}

begin
        Assign(f1,'names.aaa');
        Assign(f2,'names.bbb');
        Assign(f3,'names.ccc');
        ReadData;
        WriteF1;
        WriteF2;
        WriteF3;
        ReadF1;  writeln('ПРОВЕРКА правильности текстового файла');  WriteData; readln;
        ReadF2; writeln('ПРОВЕРКА правильности типизированного файла');  WriteData; readln;
        ReadF3; writeln('ПРОВЕРКА правильности нетипизированного файла');  WriteData; readln;
end.

Контрольный вопрос: можно ли в типизированный файл записывать строки?
Ответ: строки переменной длины – нельзя, строки фиксированной длины – можно.

Тема 13. ОСОБЫЕ СЛУЧАИ

При исполнении программы не исключена возможность ошибок ввода-вывода данных и даже отсутствия требуемых файлов, деления на машинный ноль, выхода значения переменной  за допустимые пределы и др. "особых случаев", "исключительных ситуаций", "чрезвычайных положений". Конечно, в тех случаях, когда такие случаи можно предвидеть заранее (типа деления на машинный ноль) можно поставить в алгоритм дополнительную проверку (например, не меньше ли делитель по абсолютной величине некоторого значения; но рассчитать такое предельно допустимое значение на все случаи жизни бывает непросто, и программистам приходилось раньше "отсекать" подозрительно малые значения с большим запасом), но обычно она ухудшает свойства программы. Есть ли средства проще, надёжнее, удобнее, лучше, работающие именно тогда, когда надо? Есть: это некоторые из директив компилятору и обработчики исключительных (аварийных) ситуаций.

НЕКОТОРЫЕ ДИРЕКТИВЫ КОМПИЛЯТОРУ

Директивы компилятору располагаются внутри фигурных скобок и начинаются со знака $, затем идёт обозначение директивы и опция включить/выключить. Можно располагать сразу несколько директив через запятую, например, {$ A+,B-,C-,D+}. Приведём наиболее часто используемые директивы.
{$I-} – отключить фатальное прерывание исполнения программы при ошибке ввода-вывода, {$I+} – включить его. Контроль за правильностью ввода-вывода остаётся, стандартная переменная ioresult принимает значение 0, если ошибки ввода-вывода не было.
Примеры:   
repeat {$I-} readln(x) {$I+} until ioresult=0;  {При вводе неправильных данных, например, с десятичной запятой вместо точки, или символа вместо числа, попытка ввода будет повторена}
Assign(f,'FileName.txt'); {$I-} Reset(f);  {$I+}  if ioresult=0 then Readln(f,s) else writeln('Нет такого файла');

{$I ИмяФайла} – при компиляции включить (include) в качестве  фрагмента текст из указанного файла.
{$Q-} – отключить контроль переполнения разрядов чисел при арифметических операциях, {$Q+} – включить.
{$R-} – отключить контроль выхода за пределы допустимого диапазона, {$R+} – включить.
{$S-} – отключить контроль переполнения стека (директива действует только локально!), {$S+} – включить.
{$O+} – включить режим оптимизации при компиляции (компиляция будет медленнее, зато исполняться программа будет быстрее).
{$L  ИмяОбъектногоФайла} – компоновать дополнительный объектный файл в состав исполняемого.
{$R ИмяФайлаРесурса} – подключение внешних ресурсов.
{$N+} – использовать числовой сопроцессор (для операций с плавающей точкой). Полезно было на старых машинах. В частности, результат арифметической операции с сопроцессором – повышенного типа чисел, например, вместо real будет extended. На новых машинах эта директива действует по умолчанию.
{$E+} – если сопроцессор отсутствует, а работа происходит в реальном или защищённом режимах DOS, то эмулировать наличие сопроцессора программно (это позволяет использовать все типы чисел для переменных, а не только real и integer). Полезно было на старых машинах. На новых эта директива действует по умлочанию.
{$U+} – включить генерацию кода исправления ошибок вещественного деления (полезно было для ранних Пентиумов).
Ещё в Т-, Б- и О-Паскаль есть директивы условной компиляции и несколько десятков других директив.

ОБРАБОТЧИКИ ИСКЛЮЧЕНИЙ
В О-Паскаль есть операторы "защищённого блока". Их видов два. Первый вид   try ... except :
try
{здесь размещают пытаемые операторы, защищаемые от аварии. Если всё хорошо, то после них всех переход на end; если где-то возникло исключение, то исполнение секции try прекращается досрочно}
except
{здесь размещают обработчики исключений, то есть действия, если возникла исключительная ситуация,
СООТВЕТСТВУЮЩИЕ типу возникшего исключения. Вид обработчиков исключений:}
on КлассИсключения1 do ОператорКромеGotoНаружуБлокаExcept;
on КлассИсключения2 do ОператорКромеGotoНаружуБлокаExcept;
{и т.д.}
{Если важен не конкретный класс исключения, а сам факт возникновения ошибки, то можно размещать здесь операторы обычного вида, кроме goto наружу блока Except}
ПростоОператорРеакцииНаИсключение;
else        {раздел else необязателен}
{здесь размещают альтернативный обработчик, т.е. операторы, действующие, если возникла исключительная ситуация и при этом соответствующий обработчик в секции except не обнаружен. Если при этом всё-таки else отсутствует, то выполняется вариант обработки исключения "по умолчанию".}
end        {конец оператора try ...}
В Delphi определён родительский класс Exception и его многочисленные потомки – классы исключений. Здесь мы не будем их рассматривать. Для возбуждения нестандартного исключения можно использовать оператор raise без параметров (при этом возбудится исключение класса Exception) или с параметром (указанием конструктора объекта исключения). Про объекты, классы и конструкторы – узнаем значительно позже.
Второй вид  операторов защищённого блока try ... finally:
try
{здесь размещают пытаемые операторы, защищаемые от аварии. Если всё хорошо, то после них всех переход на finally; если где-то возникло исключение, то исполнение секции try прекращается досрочно}
finally
{здесь размещают операторы, выполняемые независимо от того, возникло исключение либо нет}
end {конец оператора try ...}
Простейший пример:
begin
Assign(f,ИмяФайла);
try
Reset(f);
except begin
writeln('Такого файла нет'); exit;
end;
end;
writeln('Такой файл есть');
end.
Ещё пример:
begin
Assign(f,ИмяФайла);
try {внутри try писать у сложного оператора begin ... end не обязательно}
Reset(f);
Close(f);
Erase(f);
except begin {внутри except писать у сложного оператора begin ... end не обязательно}
writeln('Такого файла нет'); exit;
end;
end;
writeln('Такой файл был, мы его удалили.');
end.

Самостоятельное упражнение: ЕСЛИ в Вашей версии Паскаля есть оператор try, ТО сделайте блок арифметических операторов, защищённый от деления на ноль, или выхода за пределы диапазона, или от переполнения разрядов, или от переполнения стека, который при возникновении исключения сообщал бы, мол, недопустимые данные – ошибка при вычислениях, и требовал ввести правильные данные.

Тема 14. МАЛЕНЬКИЕ ХИТРОСТИ.

ФУНКЦИИ В ОПРЕДЕЛЕНИЯХ ПОСТОЯННЫХ
В определениях констант допускается использовать стандартные функции: Abs, Chr, Hi, High, Length, Lo, Low, Odd, Ord, Pred, Ptr, Round, SizeOf, Succ, Swap, Trunc (см. Темы 6 и 15).

СЛОЖНЫЕ ПОСТОЯННЫЕ

"ТИПИЗИРОВАННЫЕ ПОСТОЯННЫЕ", т.е. ПЕРЕМЕННЫЕ С НАЧАЛЬНЫМ ЗНАЧЕНИЕМ
Напомним, что общий вид такого определения:
const
ИмяТипизированнойКонстанты:ЕёТип=ЕёНачальноеЗначение;
по такой же схеме объявляются начальные значения более сложных типов.
Начальное значение присваивается только 1 раз при запуске программы, в ходе её может изменяться.
Внимание ! Типизированные константы нельзя использовать при определении других типов.
Простейшие примеры переменных с начальным значением (типизированных постоянных):
const
klass:integer=8; {тип целый}
days:1..31=5; {тип ограниченный}
want:string[3]='Yes'; {тип строка длиной 3}
eiler:real=2.73; {тип действительный}
electron:real=1.6e-19; {тип действительный}
Постоянная pi уже предопределена в стандартных модулях Паскаля (см. выше).

Общий вид объявления МАССИВОВ с начальным значением (переводчик назвал их "константы-массивы"):
ИмяКонстМассива:array[Наименьшее..Наибольшее] of ТипЭлементов =(ЗначМин,Знач2,...,ЗначМакс);
Многомерные массивы с начальным значением объявляются аналогично. Поясним на примерах:
const
ZeroVector:array[1..3] of integer=(0,0,0); {массив целых}
Rabbit:array[0..29] of char='12345 вышел зайчик погулять...'; {массив символов}
MatrixTable:array[1..2,1..3] of integer =
                ((0,1,2),
                (3,4,5)); {2-мерный массив целых}
TensorCube:array[0..1,0..1,0..2] of integer =
                (((0,1,2),(3,4,5)),
                ((6,7,8),(9,10,11))); {3-мерный массив целых}

Общий вид объявления ЗАПИСЕЙ с начальным значением (переводчик назвал их "константы-записи"):
ИмяЗаписи:ТипЭлементов=(СписокЗначенийПолей);
где каждый элемент СпискаЗначенийПолей отделён точкой с запятой:
ИмяПоля:Константа;
Внимание! При этом необходимо строго соблюдать последовательность полей.
Внимание! Для записей с вариантными полями (см. Тему 19) указывается только один из возможных вариантов.
Внимание! Если в записи есть хотя бы одно поле файлового типа, то эту запись нельзя объявить типизированной константой (т.е. задать ей начальное значение).
Пример:
type
TPoint=record
x,y:integer;
end; {TPoint}
const
ZeroPoint:TPoint=(x:0; y:0);   

Общий вид объявления МНОЖЕСТВ с начальным значением (переводчик назвал их "константы-множества"):
Внимание! Словосочетание set of , используемое при определении обычного множества, здесь не пишется.
Пример:
const
PrizovyeMesta:integer=[1,2,3];

Общий вид объявления УКАЗАТЕЛЕЙ (см. Тему 22) с начальным значением (переводчик назвал их "константы-указатели") может быть только в адрес nil.
Примеры:
const
p:pointer=nil; {нетипизированный указатель}
pr:^real=nil; {типизированный указатель}

ПОБОЧНЫЕ ЭФФЕКТЫ ВЫЗОВА
У вызова процедур, функций и циклов могут быть побочные эффекты – если мы о них не знаем, то они вредны, а если знаем, то можем "использовать с пользой". При вызове цикла обычно изменяется значение переменной-счётчика, и об этом полезно помнить. При вызове подпрограмм могут измениться их аргументы-переменные. Если в теле подпрограммы есть глобальные переменные и операторы, изменяющие их значения, то эти переменные могут измениться и повлиять на результат вычислений в другом месте с их участием. В программировании известен такой нестандартный приём, как "формальное  вычисление функций", сущность которого состоит в том, что программист вызывает функцию не ради её значения, а ради изменения её аргументов-переменных или глобальных переменных.
До появления идеологии модульного программирования вызов функции или процедуры ради изменения глобальных переменных был вполне рядовым явлением. Но разработка сверхбольших программных комплексов сделала это действие (тем более формальное вычисление функций) источником возможных ошибок программирования: действительно, позабытые-позаброшенные одним программистом глобальные переменные в одной части программы могут повлиять на другую часть программы. Поэтому и появилось модульное программирование, где все переменные, предназначенные для взаимодействия разных частей программы, должны бытьобъявлены в особой, легче обозримой, интерфейсной части. Но и это не гарантирует от ошибок разработчиков. Как быть? В ближайшем будущем  мы узнаем приёмы динамического программирования, в т.ч. передачу данных на обработку не по их фактическому значению, а по ссылке на них (т.е. по их адресу в памяти, доступной для разных частей программы).

РЕКУРСИЯ
Рекурсия – это обращение подпрограммы к самой себе. У этого приёма есть и положительные, и отрицательные свойства.
Положительные состоят в том, что некоторые сложные задачи (например, где функция от некоторого аргумента задана при вычислении через значение этой же функции, но от другого аргумента) гораздо проще программируются посредством рекурсии, нежели с соблюдением  принципов структурного программирования.
Отрицательные стороны значительно существеннее. Во-первых, с рекурсией можно нечаянно построить такие алгоритмы, для которых задача проверки на потенциальную завершаемость станет алгоритмически неразрешимой. Во-вторых, при осуществлении рекурсии функций для сохранения адресов команд в точках входа и выхода в рекурсивные процедуры, а также локальных данных функции, используется стек, а он всегда конечной, и обычно небольшой глубины, следовательно, глубина рекурсии не может быть большой. В-третьих, рекурсивный вызов функций медленнее, чем структурный.
По этим трём причинам во многих структурных языках программирования (в т.ч. в каноническом Паскале) рекурсия запрещена. Действительно, математиками доказано, что всё, что можно сделать полезного с помощью рекурсии, можно сделать и с помощью структурного программирования. Однако иные версии Паскаля, делающие послабление для оператора goto , делают послабление и для рекурсии (Б- и О-Паскаль). Сделано это ради возможности перевода программных библиотек  с иных языков на О-Паскаль.

НЕПОСРЕДСТВЕННАЯ РЕКУРСИЯ
Для непосредственной рекурсии в О-Паскале можно использовать обращение подпрограммы к самой себе:
{Пример: Вычисление факториала по свойству по свойству N!=(N-1)!*N . Напомним, что пример вычисления факториала без помощи рекурсии был в теме "циклы со счётчиком". Там было всё просто и ясно, 2 строчки.}
var
Result:longint;  {глобальная переменная, вычисляемая при формальном обращении к функции factorial}
function Factorial(N:longint):longint;
begin
if N=0 then Result:=1 else Result:=N*Factorial(N-1);     { т.е. вызов себя  с  фактическим параметром N-1  }
end;                {как только будет задан N=0, рекурсия закончится}
var
N: longint;
begin
readln(N);
writeln(factorial(N));   {обращение к функции Factorial с фактическим параметром N}
     readln;
end.
Итак, в этом примере рекурсия сократила исполняемые операторы до 1 строчки, хотя описаний стало больше.
Простота, как известно, иногда бывает хуже воровства. Самостоятельное упражнение: проверить, будет ли ругаться компилятор вашей версии Паскаля, если непосредственную рекурсию упростить ещё до вида без глобальной переменной:
function Factorial(N:longint):longint;
begin
if N=0 then Factorial:=1 else Factorial:=N*Factorial(N-1);    
end;               
var
N: longint;
begin
readln(N);
writeln(factorial(N));
     readln;
end.

КОСВЕННАЯ РЕКУРСИЯ
Для косвенной рекурсии в Б- и О-Паскале предусмотрена директива опережающего описания forward.
Вид использования косвенной рекурсии – Проц1 вызывает Проц2, а Проц2 вызывает Проц1:
procedure Проц2(СписокПараметров2); Forward; {опережающее описание – со списком формальных параметров}
procedure Проц1(СписокПараметров1);
begin
{Разные операторы, в том числе вызов} Проц2(Список Параметров1); {с фактическими параметрами}
end;
procedure Проц2; {описание без формальных параметров – оно было в опережалке}
begin
{Разные операторы, в том числе вызов} Проц1(СписокПараметров2); {с фактическими параметрами}
end;
А вот пример вычисления факториала с помощью косвенной рекурсии (по свойству N!=(N-1)!*N   ):
function F2(N2:longint):longint; Forward;                {F2 с формальным параметром N2 }
function F1(N1:longint):longint;                {F1 с формальным параметром N1}
begin
F2(N1);                {т.е. будет вызов F2 с фактическим параметром N-1, см. ниже }
end;
function F2;
begin
if N2=0 then F2:=1 else F2:=F1(N2-1); { т.е. вызов  F1  с  фактическим параметром N-1  }
end; {как только будет задан N2=0, рекурсия закончится}
var
N,NF: longint;
begin
readln(N);
NF:=F2(N);                {обращение с фактическим параметром N}
writeln(NF); readln;
end.
Неужели это проще, чем цикл со счётчиком ?! Для вычисления факториала – нет. Но ведь бывают неявные вычисления сложных функций, когда даже косвенную рекурсию в самом деле запрограммировать проще и нагляднее, чем запрограммировать структурно.

ПРИОРИТЕТ ОПЕРАТОРОВ
Обычно легче ставить скобки, чем задумываться над тем, какие операторы будут в выражении без скобок выполняться первыми, какие – вторыми и т.д.  (потому что к правилам математики это имеет отношение очень уж отдалённое). Но иногда не вредно знать, что без скобок выполняются операторы в таком порядке:
1. @ , not
2. * , / , div , mod , and , shl , shr
3. + , - , or , xor
4. = , <> , < , > , <= , >= , in
Операнд, стоящий между двумя операторами разного уровня приоритета, относится к оператору более высокого приоритета. Операнд, стоящий между операторами одного уровня приоритета, относится тому оператору, который левее. Операторы одного уровня приоритета выполняются слева направо.

РАЗНОГЛАСИЯ В КЛАССИФИКАЦИЯХ ТИПОВ
По загадочным причинам в разных источниках детали классификации типов различаются. Одна классификация была приведена в Теме 6 и Теме 9. А в фирменном описании Б-Паскаль 7.0  она (по вине переводчика ещё и слова не все удачные) такова (сравните с классификацией в Темах 6 и 9):
ПРОСТЫЕ (ОСНОВНЫЕ, ПЕРВИЧНЫЕ, БАЗОВЫЕ) ТИПЫ:
1. "Простой" (неудачное название – лучше было бы переводчику назвать его "скалярным", как общепринято – разновидность общепринятых простых, они же основные, типов). Называйте его "скалярным" !
• Упорядоченный (он же "ординарный", он же "порядковый", переводчик назвал его "последовательным" и "регулярным", из-за этого возникает путаница с массивами, ибо их тоже называли ранее "регулярными"; поэтому мы настаиваем на общепринятом названии "упорядоченный".)
; Целые типы (их много, см. Тему 6).
; Целые беззнаковые типы (их много, см. Тему 6).
; Символьный.
; Логические.
; Перечислимый.
; Поддиапазон (ограниченный).
• Вещественный (их много, см. Тему 6).
2. Указатели (они тоже относятся к общепринятым простым типам)
• Нетипированный указатель Pointer
• Указатель на строки с нуль-окончанием PChar
• Другие типированные указатели
3. (В О-Паскале есть ещё "как бы простой" тип Дата+Время, см. Тему 6)
СЛОЖНЫЕ (СОСТАВНЫЕ, ПРОИЗВОДНЫЕ) ТИПЫ:
1. Строковый (вообще-то ранее его относили к разновидности структурированных; не вполне понятно, зачем выделять его отдельно, вероятно, они это сделали из-за разнообразия типов строк в О-Паскале, см. Тему 9).
2. Структурированный
• Массив (тензор).
• Запись (бланк).
• Множество.
• Файловый тип (3 разновидности, см. в Темах 9 и 12).
• Объект (алгебра; в О-Паскале объекты почему-то выделили из числа структурированных в отдельный сложный тип).
• (Ещё к структурированным ранее относили строковый тип, составные указатели и Дата+Время.)
3. Процедурный (подпрограммный; это не подпрограммы, а такой тип данных, не путайте).
• Типы-процедуры
• Типы-функции
{Напомним,  что в О-Паскале есть и ещё сложные типы:
4. Тип-вариант (это не оператор варианта, см. выше, а тип данных такой – "вариант". О нём – позже.)
5. Классы (можно рассматривать класс как объект, снабжённый, кроме данных и действий-методов, ещё и "свойствами"; о классах – позже).
}

Обязательное самостоятельное упражнение: аккуратно нарисуйте на большом плакате древовидную схему-классификацию типов !!!

Тема 15. СОДЕРЖАНИЕ ОСНОВНЫХ МОДУЛЕЙ

СТАНДАРТНЫЕ МОДУЛИ Т- и Б-ПАСКАЛЯ.
О том, что такое модульное программирование и как объявлять используемые модули см. выше. Напомним, что модуль System компонуется с программой даже по умолчанию. В состав библиотеки TURBO.TPL для реального режима DOS (о режимах операционных систем DOS и Windows см. далее) входят модули:
• System (обмен файлами, обработка строк, вычисления с плавающей точкой, управление распределением памяти) – стандарт языка Паскаль и некоторые борландовские;
• Crt (расширенные возможности работы со стандартным устройством ввода-вывода непосредственно через видеопамять, цвет, звук, окна);
• Dos (работа с ОС DOS) – процедуры и функции, не входящие в стандарт языка;
• Overlay (работа с динамически подгружаемыми сменными блоками исполняемого кода – перекрытиями, оверлеями);
• Printer (работа с принтером);
В отдельных библиотеках (с расширениями *.TPU) размещены модули:
• Graph (работа с графическим режимом экрана);
• Strings (совместимость со строками с нуль-окончанием);
• WinDos;
• Turbo3;
• Graph3.
Библиотека TPP.TPL для защищённого режима DOS содержит модули System, Crt, Dos, Printer, Strings, WinDos, WinAPI, отдельно Graph.tpp.
Библиотека TPW.TPL для Windows содержит модули System, Strings, WinTypes, WinProcs, Win31, WinAPI, WinDos, WinCrt, WinPrn, и ряд дополнительных модулей в виде исходного текста.
В Delphi есть модуль Windows (работа с дисковой файловой системой, буфером обмена данными, окнами, приложениями и др. составляющими Windows).
В Б-Паскале и Delphi есть и много других модулей. О них – позже. О модуле Overlay – тоже позже, после основ динамического и системного программирования. Как сказал Козьма Прутков, "нельзя объять необъятное", и о содержании даже основных модулей мы расскажем не полностью (см. [4, с.161–294]), а лишь то, что необходимо или полезно начинающему программисту. Подробное и полное описание проще всего получить, нажав в среде программирования кнопку F1 и далее действуя по оглавлению. Другой вариант получения полного описания – взять соответствующую толстую книгу из списка литературы (см. выше).
Некоторые постоянные, переменные, процедуры и функции этих модулей мы уже узнали выше. Узнаем ещё.

МОДУЛЬ SYSTEM
Стандартные паскалевские процедуры и функции
Break, Continue, Exit, Halt, RunError;
Chr, High, Low, Ord, Round, Trunc;
Abs, ArcTan, Cos, Exp, Frac, Int, Ln, Pi, Sin, Sqr, Sqrt;
Dec, Inc, Odd, Pred, Succ;
Concat, Copy, Delete, Insert, Length, Pos, Str, Val;
Dispose, FreeMem, GetMem, MaxAvail, MemAvail, New;
Addr, Assigned, CSeg, DSeg, Ofs, Ptr, Seg, SPtr, SSeg;
Exclude, FillChar, Hi, Include, Lo, Move, ParamCount, ParamStr, Random, Randomize, SizeOf, Swap, TypeOf, UpCase.
Процедуры и функции ввода-вывода Борланд Паскаль
Append, Assign, BlockRead, BlockWrite, ChDir, Close, Eof, Eoln, Erase, FilePos, FileSize, Flush, GetDir, IOResult, MkDir, Read, Readln, Rename, Reset, Rewrite, RmDir, Seek, SeekEof, SeekEoln, SetTextBuf, Truncate, Write, Writeln.
В Т- и Б-Паскале есть несколько десятков предопределённых в модулях переменных  [4, с.170–175]. Начинающему программисту нужны далеко не все из них. Они дают адреса и коды стандартных ошибок, областей памяти, буферов, прерываний ОС, и др. Например, для реального режима DOS определены переменные:
Input – текстовый файл стандартного ввода,
Output – текстовый файл стандартного вывода,
      FileMode – байтовый код текущего режима открытого файла
(0 – только чтение, 1 – только запись, 2 – чтение и запись),
ExitCode – целый код выхода.
Для работы с динамической памятью и прерываниями ОС (см. далее) в реальном режиме DOS потребуются:
HeapOrg – указатель на начало динамической области памяти ("кучи"),
HeapEnd – конец "кучи",
HeapPtr – текущий указатель на "кучу",
HeapError –  указатель на функцию ошибки памяти,
Seg0040 – слово-селектор стандартного сегмента $0040 (аналогично $A000, $B000, $B800)
SelectorInc – инкремент селектора,
SaveInt00 – указатель на системное прерывание $00 "особый случай"
(аналогично прерывания $02, $1b, $21–$24, $34–$3f, $75),
МАСКИ. В параметрах, где нужно имя файла, можно использовать символьные маски DOS * и ?, позволяющие обрабатывать сразу группы файлов. Маска "?" заменяет собой ровно один символ на том месте в имени, где он стоит. Маска "*" заменяет собой любое число любых символов, начиная с того места, где она стоит, до конца той части имени файла, где она стоит.
ЧАСТО ИСПОЛЬЗУЕМЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ модуля System, кроме уже описанных в предыдущих темах:
SizeOf(Аргумент):longint – даёт размер своего аргумента в батйах.
ioResult:integer – даёт код результата последней операции ввода-вывода (если всё в порядке, даёт 0). Доступна только вследствие директивного отключения автоматического контроля ввода-вывода {$I-}.
EOF(var f):boolean – если текущая позиция – в конце файла, то даст true.
FileExists(ИмяФайла:string):boolean – если файл с указанным именем есть, то даёт true.
Erase(var f) – уничтожает (предварительно закрытый) файл, если он есть, иначе ошибка.
Flush(var f) – "сохраняет" файл (только открытый процедурой Rewrite), т.е. все новые записи действительно записываются на диск, очищает внутренний буфер файла. Иначе реальная запись на диск будет только когда буфер файла полностью заполнится или при закрытии файла.
DiskFree(Диск:byte):longint – даёт объём свободного места на диске. Если указанного диска нет, то даёт -1.
DiskSpace(Диск:byte):longint – даёт полный объём диска. Если указанного диска нет, то даёт -1.
Rename(var f, НовоеИмяФайла:string) – если файла с именем НовоеИмяФайла пока нет, то переименовывает (предварительно закрытый) файл, связаный с файловой переменной f, а иначе даёт ошибку исполнения.
GetDir(Диск:byte, var Путь:string) – даёт полное имя текущего каталога. (Параметр Диск: $00 – диск по умолчанию, $01 – диск А:, $02 – B:, $03 – C: и т.д.) Если указанного диска нет, то ошибка.
ChDir(НовыйПуть:string) – переход в другой каталог, если он существует, иначе ошибка.
MkDir(ИмяНовогоКаталога:string) – создаёт новый каталог, если такой ещё не существует и его возможно создать, иначе ошибка.
RmDir(ИмяУдаляемогоКаталога:string) – удаляет (только пустой) каталог. Если каталога нет или он непустой, то ошибка.
В состав модуля DOS в Б- и О-Паскаль входят и другие процедуры и функции (например, для поиска файлов в каталоге FindFirst,  FindNext, FindClose) и др.

МОДУЛЬ CRT
Управление режимами монитора, цветом, звуком, расширенными кодами клавиатуры, окнами (дисплеями). Исполняются через БСВВ (BIOS), минуя DOS, что обеспечивает повышенное быстродействие.  Для использования модуля Crt его необходимо объявить в разделе uses.
При инициализации этого модуля автоматически выполняется связывание стандартных файлов ввода-вывода с параметрами окна Crt, т.е., то же самое, что вставить операторы AssignCrt(Input); Reset(Input); AssignCrt(Output); Rewrite(Output); если вдруг нужно связать стандартные файлы ввода-вывода обратно, то придётся явно писать Assign(Input,''); Reset(Input); Assign(Outpur,''); Rewrite(Output); но начинающим это ни к чему.
Window – процедура, задающая окно (дисплей) в часть экрана монитора (по умолчанию – во весь экран).
KeyPressed – функция, дающая true, если буфер клавиатуры непустой.
ReadKey – функция, дающая очередной символ из буфера клавиатуры как очереди.
Delay – процедура, задержка на указанное количество миллисекунд.
Sound – процедура, даёт звук указанной частоты в герцах.
NoSound – процедура, выключает звук.
ClrScr – процедура, очищает окно и возвращает кусор в его левый верхний угол.
TextColor – функция, даёт  шрифт указанного цвета.
GotoXY – процедура, перемещает курсор на заданные координаты (ось Х слева направо, ось Y сверху вниз).
WhereX – функция, даёт координату Х курсора в окне.
WhereY – функция, даёт координату Y курсора в окне.
Реже используемые процедуры и функции:
TextMode – выбор текстового режима.
HighVideo – процедура, даёт яркий шрифт.
NormVideo – процедура, даёт нормальную яркость шрифта.
LowVideo – процедура, даёт бледный шрифт.
ClrEol – процедура, удаляет символы от курсора до конца строки.
DelLine – процедура, удаляет текущую строку, поднимает остальные.
InsLine – процедура, опускает строки ниже курсора, вставляет пустую строку.
Переменные:
TextAttr – атрибуты текста вместе (цвет текста 0..15, цвет фона 0..7, моргание 0..1).
WindMin – координаты левого верхнего угла окна (дисплея) на экране монитора.
WindMax – координаты правого нижнего угла окна.
CheckEOF – даёт true, если текущая позиция в файле – его конец,
Реже используются переменные CheckBreak, CheckSnow, DirectVideo, LastMode.
Постоянные:
black,blue,green,red,yellow,magenta,cyan, lightblue,lightgreen,lightred, white и др. – предопределённые цвета 0..15.
Blink – то же, что textattr>=127 (атрибут моргания текста =1).
bell – то же, что #7 (звук)
bs – то же, что #8 (курсор левее)
lf – то же, что #10 (курсор ниже, а если самый низ окна, то прокрутка его вверх)
cr – то же, что #13 (курсор в начало строки).
А также постоянные, определяющие текстовый режим.
Редактировать строку можно клавишами Enter (ввод конца строки), Ctrl+Z (ввод  конца файла), BackSpace или Ctrl+S (забой последнего символа), Esc или Ctrl+A (забой последней строки), Ctrl+D (даёт предыдущий символ), Ctrl+F (даёт предыдущую строку).

МОДУЛЬ WinCRT
Позволяет в Windows-программах использовать текстовые окна и обычные процедуры Read, Readln, Write, Writeln.
Процедуры InitWinCrt и DoneWinCrt создают и удаляют текстовые окна с прокруткой и иными обычными элементами управления Windows. ReadBuf – чтение строки из окна. WriteBuf – запись строки в окно. ScrollTo – прокрутка окна. TrackCursor – удержание курсора в окне.
Переменные: WindowTitle – имя окна. WindowOrg – начальное положение окна на экране. Origin – левый верхний угол окна. WindowSize – начальные размеры окна. ScreenSize – размеры полезной части окна. Cursor – координаты курсора.
Есть также переменные InactiveTitle, AutoTracking и др.

МОДУЛЬ DOS
Даёт возможность взаимодействовать с ОС  DOS средствами своих переменных, процедур и функций. Для переносимости под Windows или для взаимодействия с C-программами предназначены другие модули – WinDos и Strings.
В модуле DOS определены много типов, постоянных, переменных: флаги, атрибуты, регистры, дата и время, файловые записи, строковые типы, и др.
Атрибуты файла (типа byte):
$01 только чтение
$02 скрытый
$04 системный
$08 том
$10 каталог
$20 архивный
$3f  произвольный
Процедуры и функции:
GetFAttr – даёт атрибуты файла. SetFAttr – ставит атрибуты файла. GetDate – даёт текущую системную дату. SetDate – ставит ситсемную дату. GetTime – даёт текущее системное время. SetTime – ставит ситсемное время. GetFTime –  даёт дату и время файла. SetFTime – ставит дату и время файла. DiskSize – даёт общий объём диска. DiskFree – даёт свободный объём диска. FExpand – даёт полное имя файла по указанному краткому.  FSplit – разделяет полное имя файла на путь, краткое имя и расширение. FSearch – ищет указанный файл в указанных каталогах. FindFirst – ищет первый файл в указанном каталоге, удовлетворяющий указанным параметрам; FindNext – следующий такой.
Процедуры и функции для управления другими программами и прерываниями DOS:
EnvCount – даёт число строк, описывающих "окружение среды" DOS.
EnvSrt – даёт указанную строку окружения.
GetEnv – даёт указанную переменную окружения.
Exec – выполняет указанную программу с указанными параметрами.
Keep – завершает указанную программу, но оставляет её в оперативной памяти.
GetIntVec – даёт адрес вектора прерывания.
SetIntVec – ставит вектор прерывания на указанный адрес.
SwapVectors – меняет местами хранящиеся и текущие значения векторов прерываний.
MsDOS – выполняет функцию DOS с указанным содержимым регистров.
Intr – выполняет указанное прерывание DOS с указанным содержимым регистров.
Есть в модуле Dos и другие процедуры и функции (DosVersion, GetCBreak, SetCBreak, GetVerify, SetVerify), не нужные начинающим.

МОДУЛЬ WinDOS
В модуле WinDos есть много других процедур и функций, в т.ч.:
GetCurDir – даёт текущий каталог. CreateDir – создаёт новый каталог. SetCurDir – переход в указанный каталог. RemoveDir – удаляет указанный каталог.

ИМЕНА ВНЕШНИХ УСТРОЙСТВ в Т- и Б-ПАСКАЛЕ
С точки зрения программирования со стандартными модулями Т-, Б-Паскаля внешние устройства выглядят как файлы, поэтому действовать с ними можно вышеуказанными файловыми процедурами. В Б-Паскале есть 2 типа внешних устройств: текстовые и DOS-устройства.
Con – стандартное устройство ввода-вывода консоль, терминал (монитор+клавиатура). Для задания признака "Конец файла" удобно использовать комбинацию клавиш Ctrl+Z.
Crt – консоль с расширенными возможностями (цветовые атрибуты, окна), есть в модуле Crt.
Lst, Lpt1, Prn – принтер в параллельном порту Lpt1. Для Lst разрешена только запись.
Lpt2,Lpt3 –  другие параллельные порты.
Aux, Com1 – последовательный порт Com1.
Com2 – другой последовательный порт.
Nul – "чёрная дыра", что в неё направлено, пропадает. Разрешена только запись.

ТЕМА 16. ГРАФИКА.

ВОЗМОЖНОСТИ
В Паскале в графическом режиме экрана можно писать текст, рисовать поточечные (растровые) изображения, рисовать векторные изображения (фигуры). Т-, Б- и О-Паскаль предусматривают ряд драйверов графического режима для разных типов мониторов. Мы начнём с достаточно простых (распространённых в конце 80-х – начале 90-х гг.), EGA и VGA. Эти драйверы предусматривают изменение параметров графического экрана, различные цветовые палитры, и не менее 2-х видеостраниц (рисовать можно на любой видеостранице, показывается только одна из них, поэтому можно долго рисовать на скрытой видеостранице и потом быстро показать её всю сразу). Координаты сверху вниз, слева направо. Левая верхняя точка обладает координатами (0,0).

НЕОБХОДИМЫЕ СОСТАВЛЯЮЩИЕ И НАСТРОЙКА
При компиляции для реального режима DOS необходим модуль Graph.tpu , а для защищённого – Graph.tpp. При запуске программы должен быть (лежать отдельно либо быть скомпонован в составе исполняемого модуля) графический драйвер *.BGI (на современных мониторах – как минимум, EgaVga.bgi), а при использовании векторных шрифтов и файлы *.CHR (по умолчанию используется матричный шрифт 8х8 точек).
Настройте F10–Options–Directories :  правильно укажите каталог с модулями (он обычно называется UNITS) в строках меню настройки  Include Directories  и TPU Unit Directories.
Найдите в каталоге BGI и скопируйте в текущий каталог файл egavga.bgi (потом мы узнаем более удобный способ –  как его компилировать в состав исполняемого модуля). Он позволяет назначать и использовать палитру объёмом в 16 цветов, выбранных из 256 возможных.
Перед началом использования графических процедур должен быть инициализирован ("зачат") графический          режим. При этом надо указать режим работы (например, нормальный или максимальный) монитора установленного типа и (если графический драйвер не скомпонован вместе с исполняемым модулем, а лежит отдельно) указать маршрут поиска файла графического драйвера (например, в текущем каталоге).
Пример простой графической программы (будет рисовать цветной дождик):
program GraphDemo;
uses
        crt,graph;                {подключение модуля графических процедур и функций}

procedure BeginGraph;               {процедура инициализаци графического режима}
var
        Gd,Gm:integer;                {коды  типа монитора и его режима}
begin
        DetectGraph(Gd,Gm);       {даёт  текущий тип монитора и главный режим его работы}
       InitGraph(Gd,Gm,'');          {инициализирует графический режим с помощью драйвера в текущем каталоге}
end; {BeginGraph}

procedure Zvuk;
begin
Sound(500+Random(2000));        {звук случайной частоты}
Delay(20);        {задержка на 20  мс}
NoSound;
end; {Zvuk}

procedure OchistkaKlav;                {Очистка буфера клавиатуры  посимвольно}
var
        ch:char;
begin
while keypressed do ch:=readkey;
end;  {OchistkaKlav}

procedure Rain;       {рисует цветные штрихи-дождинки и круги от них, пока не нажмут клавишу}
begin
   OchistkaKlav;
   repeat begin
     setcolor(Random(16));                {задаёт случайный цвет}
     Circle(Random(GetMaxX),Random(GetMaxY),Random(50));     {окружность случайного радиуса}
      Zvuk;        {булькает}
     Delay(200);
   end
   until keypressed;        {пока не нажмут клавишу}
   OchistkaKlav;
end; {Rain}

begin                {главная часть программы}
    TextСolor(Green); writeln('Текстовый режим');  Delay(3000);    {3 секунды текстового режима}
    BeginGraph; {вызов процедуры инициализации графики}
    Setcolor(LightRed); {розовый цвет}
   OutTextXY(200,100,'Graphics Mode - "И дождь смывает все следы ..."   '); {надпись}
    Rain; {вызов процедуры рисования дождика на лужах}
    RestoreCrtMode; {снова текстовый режим}
    Textcolor(LightBlue); writeln('Снова Text'); Delay(3000); {3 секунды текстового режима}
    SetGraphMode(1); {снова графический режим, причём режим №1}
    Rain; {вызов процедуры рисования дождика на лужах}       
    CloseGraph; {закрыть графический режим}
end.

ПОСТОЯННЫЕ, ПЕРЕМЕННЫЕ, ПРОЦЕДУРЫ И ФУНКЦИИ МОДУЛЯ GRAPH
Основные подпрограммы (нужные очень часто):
DetectGraph(Драйвер,Режим) – узнать тип монитора и главный режим его работы.
GetMaxMode – даёт наибольший режим для текщего драйвера.
InitGraph(Драйвер,Режим, ПутьКГрафическомуДрайверу) – инициализировать графический режим.
SetGraphMode(Режим) – установить графический режим (только после инициализации).
RestoreCRTmode – 1) запомнить параметры текстового режима, 2) вернуть текстовый режим, не закрывая графического.
CloseGraph – закрыть графический режим.
SetColor(Цвет) – задать цвет линий, текста и фигур.
SetBkColor(Цвет) – задаёт цвет фона.
GetMaxX – узнать наибольшую правую координату.
GetMaxY – узнать наибольшую нижнюю координату.
Circle(Xцентра,Yцентра,Радиус) – окружность.
FillEllipse(Xцентра,Yцентра,полуосьX,полуосьY) – заполненный эллипс, в т.ч. круг.
OutTextXY(X,Y,Строка) – текст.
PutPixel (X,Y;Цвет: 0..15) – точка указанного цвета.
Rectangle(Лево,Верх,Право,Низ) – контур прямоугольника.
Bar(Лево,Верх,Право,Низ) – сплошной прямоугольник (текущего стиля).
Line – отрезок.
LineRel – отрезок, относительно текущей точки.
LineTo – отрезок из текущей точки в указанную.
FloodFill(X,Y,Цвет) – залить текущим цветом и стилем до границы области (т.е. до точек смены цвета).
ClearDevice – очистка всего экрана.
SetViewPort – ограничить графическое окно (дисплей);  по умолчанию окно во весь экран. 5 параметров: 4 границы и значение "обрезания" (ClipOn , ClipOff).
ClearViewPort – очистка окна.
Подпрограммы,  полезные для быстрой смены кадров (при первом чтении можно пропустить):
MoveTo(X,Y) – задать текущее положение (оно хранится в переменной-указателе CP).
GetPixel(X,Y) – даёт цвет точки с указанными координатами.
SetActivePage(Номер) –  назначает рисуемую видеостраницу, по умолчанию №0.
SetVisualPage(Номер) – назначает отображаемую видеостраницу, по умолчанию №0.
ImageSize(началоX,началоY,конецX,конецY) – даёт число байт в куске изображения.
В модуле System (см.выше) есть процедура GetMem(Указатель, Размер) – выделяет в динамической области памяти место заданого размера и присваивает адрес его начала переменной Указатель.
GetImage(началоX,началоY,конецX,конецY,Указатель^) – сохранить прямоугольный кусок изображения в памяти начиная с адреса, заданного значением Указателя.
PutImage(началоX,началоY,Указатель^,РежимВставки) – вставить из памяти, начиная с адреса, заданного значением Указателя, прямоугольный кусок изображения, начиная с указанных координат окна. При этом режим вставки по умолчанию – NormalPut=замена, иначе OrPut, XorPut, AndPut или NotPut.
SetWriteMode – назначает логический режим наложения нового изображения на старое при выводе на экран (по умолчанию – замена, иначе OrPut, XorPut, AndPut, NotPut).
Реже используемые подпрограммы (при первом чтении можно пропустить):
GetMaxColor – узнать наибольшее значение цвета.
MoveRel – изменить CP относительно текущего положения.
GetViewSettings – даёт параметры окна и способ усечения того, что в окно не влезло.
GetTextSettings – узнать текущие параметры вывода текстовых сообщений.
OutText(Строка) – вывод строки в текущие координаты.
TextHeight – даёт высоту символов (в точках) в строке.
TextWidth – даёт ширину символов (в точках) в строке.
SetTextStyle(КодШрифта,Направление,МножительРазмера) – задать стиль текста: шрифт (0=матричный, остальные векторные 1=Triplex, 2=Little, 3=SanSerif, 4=Gothic), направление (0=HorizDir – горизонтально, 1=VertirDir – вертикально), размер в тчоках (8*множитель размера).
SetTextJustify(поХ,поY) – задать способ выравнивания текста (по умолчанию – по его левому верхнему углу, CenterText – посередине, RightText – справа, LeftText – слева).
SetUserCharSize (4 параметра) – задать размер векторного шрифта.
GetLineSettings – узнать текущие параметры рисования линий.
SetLineStyle(параметр1,параметр2,Толщина) – задать тип линий (0=сплошная) и толщину.
Arc (5 параметров) – дуга окружности.
Ellipse (6 параметров) – дуга эллипса.
FillEllipse – заполненный эллипс (см. выше).
Bar3D – параллелепипед. 6 параметров: стороны, толщина и логическое значение, определяющее прозрачность.
DrawPoly – контур многоугольника.
FillPoly – многоугольник.
PieSlice (5 параметров) – сплошной сектор круга.
Sector (6 параметров) – сплошной сектор эллипса.
GetAspectRatio – даёт отношение размеров экрана в точках.
SetAspectRatio – изменяет пропорции изображения.
GetArcCoord – даёт параметры последнего рисования дуги.
GetColor – даёт текущий цвет рисования.
GetBkColor – даёт текущий цвет фона.
GetDriverName – даёт имя текущего драйвера.
GetGraphMode – даёт текущее значение режима.
GetModeName – даёт название текущего значения режима.
GetModeRange – даёт наибольшее и наименьшее значение режима.
Способы заливки фигур (при первом чтении можно пропустить):
SetFillStyle(типЗаливки,Цвет) – назначает стиль заливки. ТипЗаливки: 1=сплошная, 7=в клеточку, есть и другие.
SetFillPattern – назначает образец  (узор) заливки.
FillPoly – назначает заливку многоугольника (см. выше).
FloodFill – заливка ограниченной области (см. выше).
GetFillPattern – даёт текущий стиль заливки.
GetFillSettings – даёт текущий цвет и стиль заливки.
Смена палитры (при первом чтении можно пропустить):
GetPaletteSize – даёт размер палитры.
GetPalette – даёт текущую палитру и её размер.
GetDefaultPalette – даёт исходную палитру.
SetAllPalette – заменяет все цвета палитры на указанные.
SetPalette – задаёт один из цветов палитры.
SetRGBpalette – изменить цвета палитры по цветовым координатам "красный, зелёный, синий" для драйверов VGA и  IBM-8514.
Функция GraphResult даёт код ошибки при выполнении графических операций либо 0 (оно же GrOK), если безошибочно). Внимание! При каждом обращении к этой функции она сначала автоматически обнуляется. Поэтому если надо использовать её значение больше одного раза, то надо сразу присвоить её значение вспомогательной переменной.
Постоянные:
драйвер, режим, код ошибки, ёмкость палитры (MaxColors), цвета для 16-цветовых палитр, цвета для RGB-палитр, шрифт, стиль линий, стиль заливки, выравнивание, обрезание на границах, параметры параллелепипеда, логический режим наложения изображения,
Типы (при первом чтении можно пропустить):
PaletteType – запись "палитра".
LineSettingsType – запись "свойства линий".
TextSettingsType – запись "свойства текста".
FillSettingsType – запись "цвет и стиль заливки".
FillPatternType – запись "образец узора заливки".
ArcCoordsType – запись "свойства последнего рисования дуги или эллипса".
ViewPortType – запись "свойства текущего окна".
PoinType – какой-то ещё тип (начинающим не нужен).
Переменные:
CP – указатель текущей координаты.
DefaultFont – код исходного шрифта (по умолчанию).
В модуле Graph есть другие процедуры, функции и переменные, начинающим навряд ли нужные: GraphDefaults, GraphErrorMsg, InstallUserDriver, InstallUserFont, RegisterBGIdriver, RegisterBGIFont, GraphGetMem, GraphFreeMem, GraphGetMemPtr, GraphFreeMemPtr, SetGraphBufSize.

256-ЦВЕТНАЯ ГРАФИКА. Начиная с Т- и Б-Паскаль, можно использовать драйвер IBM8514, содержащий палитру объёмом 256 цветов, выбранных из 256*1024 цветов, размер экрана до 768х1024 точек (dot). Внимание! При инициализации этого драйвера нельзя использовать процедуру DetectGraph (она привела бы к режиму VGA), надо явно указать тип используемого драйвера. При выборе IBM8514LO размер будет 480х640 точек, при выборе IBM8514Hi размер будет 768х1024 точек. Пример: InitGraph(ibm8514,ibm8514hi). Цвет определяется в модели RGB (красный, зелёный, синий), по 6 бит на каждый цветовой канал. По умолчанию первые 16 цветов палитры сопадают с цветами палитры EGA/VGA, но можно задавать значения компонентов цвета в палитре процедурой:
SetRGBPalette(НомерПалитры, Красный, Зелёный, Синий: word), где НомерПалитры может быть от 0 до 255.
Процедуры GetPalette, SetAllPalette, SetPalette в драйвере IBM8514 не используются.

ПОЛНОЦВЕТНАЯ ГРАФИКА 2563 цветов предусмотрена в Б- и О-Паскале. Про неё – позже.

КОМПОНОВКА ВЕКТОРНЫХ ШРИФТОВ В СОСТАВ ИСПОЛНЯЕМОГО МОДУЛЯ
Файлы векторных шрифтов *.CHR можно с помошью программы BinObj.exe перевести в форму объектных модулей *.OBJ , а затем с помощью директивы компилятора и компоновщика {$L ИмяОбъектногоФайла} создать исполняемый модуль. В его состав будет входить и файл векторного шрифта. Форма Борланд в пользовательском соглашении явно разрешает продавать программы, содержащие борландовские векторные шрифты, без особого её разрешения и вознаграждения.

КОМПОНОВКА ГРАФИЧЕСКОГО ДРАЙВЕРА В СОСТАВ ИСПОЛНЯЕМОГО МОДУЛЯ
Если работает много мелких программ, то графический драйвер экономичнее содержать в отдельном файле. Но в достаточно большую графическую программу графический драйвер удобнее включить, чтобы не было отдельного файла. Это делается аналогично компоновке с векторными шрифтами. Форма Борланд в пользовательском соглашении явно разрешает продавать программы, содержащие борландовские графические драйверы, без особого её разрешения и вознаграждения.

ЕЩЁ 5 НЕСЛОЖНЫХ, НО СИМПАТИЧНЫХ ПРИМЕРОВ ГРАФИЧЕСКИХ ПРОГРАММ

program Pulsar;  {Звезда-пульсар, 1995 г., А.В. Горшков}
uses
crt,graph;
var
x,y,gd,gm,i,rx,ry,s:integer;
begin
initgraph(gd,gm,'');
detectgraph(gm,gd);
randomize;
repeat begin {до тех пор, пока не нажмут какую-нибудь клавишу}
for i:=0 to (GetMaxX+GetMaxY) div 4 do begin
s:=random(16);
setcolor (s); {случайный цвет}
setfillstyle(1,s);
circle(GetMaxX div 2,GetMaxY div 2,i);              {окружности}
x:=random(GetMaxX);
y:=random(GetMaxY);
line(GetMaxX div 2,GetMaxY div 2,x,y);             {лучи}
end; {for}
for i:=0 to (GetMaxX+GetMaxY) div 4 do begin
s:=random(16);
setcolor(s);
setfillstyle(1,s);
x:=random(GetMaxX);
y:=random(GetMaxY);
line(GetMaxX div 2,GetMaxY div 2,x,y);             {ещё лучи}
setcolor(0); {чёрный цвет – для стирания}
circle(GetMaxX div 2,GetMaxY div 2,i);               {стирание окружностей}
end; {for}
end until keypressed;
closegraph;
end.

program Glaz;   {Светящийся глаз, 1995, А.В. Горшков}
uses
crt,graph;
var
r,x,y,gd,gm,i,rx,ry,s:integer;
begin
detectgraph(gm,gd);
initgraph(gd,gm,'');
randomize;
repeat begin
for i:=50 to (GetMaxY div 2) do begin
s:=random(16);
setcolor (s);                {случайный цвет}
setfillstyle(1,s);                {сплошная тонкая линия}
r:=random(GetMaxY div 2);             {радиус}
x:=random(GetmaxX);
y:=random(GetMaxY);
fillellipse(GetMaxX div 2,GetMaxY div 2,r,i);    {волшебный глаз}
line(GetMaxX div 2,GetMaxY div 2,x,y);            {лучи из середины}            
end; {for}
end until keypressed;
closegraph;
end.

program Slova; {Рост текста и его уползание. Ученик Коля Андреев, 1999 г., 6A класс, школа №150 г.Челябинска}
{В текущем каталоге необходим файл драйвера Goth.chr}
uses
crt,graph;

procedure BeginGraph;   {Инициализация графического режима}
var
gm,gd:integer;
begin
detectgraph(gd,gm);
initgraph(gd,gm,'');
end;{begingraph}

const                {описания главной части}
s='Nikolay';
var
size,x,y,color:integer;
begin {главная часть}
begingraph;
x:=getmaxx div 2; y:=getmaxy div 2;
settextjustify(centertext,centertext);   {выравнивание посередине}
for size:= 1 to 10 do begin
setcolor(lightred);        {розовый цвет}
settextstyle(4,0,size);              {готический шрифт}
outtextxy(x,y,s);                {написание текста}
delay(3000);        {подождать 3 секунды}
setcolor(0);        {чёрный цвет – для стирания}
outtextxy(x,y,s);        {стирание текста}
end; {for}
randomize;
repeat   begin                {повторять до тех пор, пока не нажмут какую-либо клавишу}
color:=random(16);
x:=x+random (11)-5; y:=y+random (11)-5;      {случайные смещения от -5 до +5 точек}
if x<0 then x:=0;                {проверка невыхода за пределы экрана}
if y<0 then y:=0;
if x>getmaxx then x:=getmaxx;
if y>getmaxy then y:=getmaxy;
setcolor(14);  outtextxy(x,y,s);  {рисование текста}
delay(1000); {подождать}
setcolor(0); outtextxy(x,y,s); {стирание текста}
end
until keypressed;
closegraph;
end.

program Chasiki; {Часики, стирающие время. Ученик Коля Андреев, 1999 г., 6A класс, школа №150 г.Челябинска}
uses
crt,graph;

procedure begingraph;   {инициализация графического режима}
var
gd,gm:integer;
begin
detectgraph(gd,gm);
initgraph(gd,gm,'');
end;{begingraph}

const {описания главной части}
dlina=200; {секундная}
dlina2=140; {минутная}
dlina3=160;         {ещё какая-то длина, пригодится, увидите}
cvetMinut=Green; TolMinut=3; {их цвет и толщина}
cvetSec=LightBlue; TolSec=1;
var
centerx,centery:integer;       {середина экрана}
i,j:integer;
x,y,x2,y2:real;
sec,min:integer; {полные секунды и минуты}
alfa,alfa2:real;    {углы поворота стрелок}
digits:string; {часовые надписи}
begin {главная часть}
begingraph;
centerx:=getmaxx div 2; centery:=getmaxy div 2;
setcolor(lightblue); {голубой цвет линий}
setfillstyle(8,lightred); {узор и розовый цвет заполнения}
fillellipse(centerx,centery,100,100);   {розовый круг с голубой границей}
setcolor(yellow); 
circle(centerx,centery,150);  {жёлтая окружность}
circle(centerx,centery,170);
circle(centerx,centery,30);
settextjustify(centertext,centertext);  {выравнивание текста посередине}
settextstyle(1,0,3); setcolor(lightgreen); {розовый шрифт "триплекс" 3-кратного размера}
outtextxy(centerx,centery-50,'KRISTALL');    {название часов}
settextstyle(4,0,2); {шрифт "готический" 2-кратного размера}
outtextxy(centerx,centery+40,'Kolya');
outtextxy(centerx,centery+60,'Andreev');
settextstyle(4,0,6); {шрифт "готический" 6-кратного размера}
for i:=1 to 12 do begin
alfa:=(90-i*30)*2*pi/360; {угол в радианах}
x:=cos(alfa)*dlina3; {координаты конца стрелки}
y:=sin(alfa)*dlina3;
str(i,digits); {преобразование числа i в строку digits}
outtextxy(centerx+round(x),centery-round(y), digits);    {надписи часов}
end;{for}
SetTextStyle(4,1,4); {вертикальный шрифт "готический" 4-кратного размера}
OutTextXY(centerx+220,centery,'Vostok');
OutTextXY(centerx-220,centery,'Zapad');
SetTextStyle(4,0,4); {вертикальный шрифт "готический" 4-кратного размера}
OutTextXY(centerx,centery-220,'Sever');
OutTextXY(centerx,centery+220,'Yug');
setwritemode (xorput); {режим наложения Xor – сложение по модулю 2}
min:=0; {текущее значение полных минут}
repeat begin
alfa2:=(90-min*6)*2*pi/360;  {угол минутной стрелки}
x2:=cos(alfa2)*dlina2;
y2:=sin(alfa2)*dlina2;
setlinestyle(0,0,TolMinut); setcolor(CvetMinut);
line(centerx,centery,centerx+round(x2),centery-round(y2)); {рисование минутной стрелки}
for sec:=0 to 59 do begin
alfa:=(90-SEC*6)*2*pi/360; {угол секундной стрелки}
x:=cos(alfa)*dlina;
y:=sin(alfa)*dlina;
setlinestyle(0,0,TolSec); setcolor(CvetSec);
line(centerx,centery,centerx+round(x),centery-round(y)); {рисование секундной стрелки}
delay(24900); {ПОДОГНАТЬ КОЭФФИЦИЕНТ ПОД ВАШ ПРОЦЕССОР !!!}
line(centerx,centery,centerx+round(x),centery-round(y));  {в режиме Xor повторное рисование фигуры – то же самое, что её отмена}
if keypressed then begin
CloseGraph; halt; {если клавиша нажата, то всё прекратить}
end;
end; {for}
setlinestyle(0,0,TolMinut); setcolor(CvetMinut);
{стирание минутной стрелки, режим Xor}
min:=min+1;    {следующее значение полных минут}
end until false;     {это не зацикливание, т.к. внутри предусмотрена возможность выхода в любую секунду }
{closegraph;} {при выходе из программы закрытие графрежима происходит всё равно автоматически}
end.

ПРИМЕР ПОДВИЖНОЙ ФИГУРЫ НА СЛОЖНОМ ФОНЕ
А заодно и сравнение 4-х способов рисования мультипликационных фильмов.

Program Movies_N_4;   {Игрушка –  гонять стрелками по экрану рисованную мордочку. А.В. Горшков, 1995}
Uses
Crt,Graph;
const
sposob=3; {задание способа осуществления смены картинок, выберите 0..3, их описание см. далее}
delayTime=5;    {время между кадрами}
step=5; {шаг перемещения, в точках}
razmer=20; {половина размера области мордочки}

Procedure Strelki(var ch:char; var xnew,ynew:integer);   {стрелками задать новые координаты}
{заодно даёт символ нажатой клавиши ch}
var
xold,yold :integer;
spec       :boolean;
begin
xold:=xnew; yold:=ynew; {запомнить старые  координаты}
while KeyPressed do ch:=ReadKey;  {если буфер клавиатуры не пуст, то его очистка}
if keypressed then  begin {если нажата клавиша, то обновить значение ch}
ch:=ReadKey;
if integer(ch)=0 then spec:=true     else spec:=false;    {специальная ли была клавиша}
if spec then ch:=Readkey; {если специальная, читать второй символ}
end;  {if}
Case integer(ch) of                {новые координаты в зависимости от последних нажатых стрелок}
72: ynew:=ynew-step;
75: xnew:=xnew-step;
77: xnew:=xnew+step;
80: ynew:=ynew+step;
27: begin CloseGraph; halt; end; {если была нажата клавиша Esc}
end; {Case} {Видно, что если была нажата не стрелка и не Esc, то x и y останутся старыми}
if xnew<Razmer then begin xnew:=Razmer; ch:=char(77); end;    {отражение слева}
if xnew>GetMaxX-Razmer then begin xnew:=GetMaxX-Razmer; ch:=char(75); end; {отражение справа}
if ynew>GetMaxY-Razmer then begin ynew:=GetMaxY-Razmer; ch:=char(72); end; {отражение снизу}
if ynew<Razmer then begin ynew:=Razmer; ch:=char(80); end; {отражение сверху}
{а если нет ни отражений, ни новых нажатий клавиши, то сохраняется старое значение переменной ch,
то есть продолжается движение в прежнем направлении}
end; {Strelki}

procedure BeginGraph; {инициализация графики, режим №1 и очистка экрана}
var
Gd,Gm      :integer;
begin
DetectGraph(Gd,Gm);
Gm:=1;
InitGraph(Gd,Gm,'');
ClearDevice;
end; {BeginGraph}

procedure Picture; {первичное рисование мордочки}
begin
setfillstyle(1,white); Circle(20,20,15); Bar(15,15,16,16); bar(25,15,26,16);
setfillstyle(1,lightblue); fillellipse(20,20,7,2);
setfillstyle(1,white); circle(15,15,5);  circle(25,15,5);  arc(20,20,190,350,10);  bar(19,0,21,0);
line(20,37,30,33); line(20,37,10,33);  line(20,37,30,39); line(20,37,10,39); line(30,33,30,39); line(10,33,10,39); 
arc(20,5,180,220,18); arc(20,5,320,360,18);  arc(20,0,200,250,19); arc(20,0,290,340,19); 
end; {Picture}

procedure Fon; {рисование фона с неким рисунком вдобавок}
begin
{ setlinestyle(1,0,1);}
SetFillStyle(7,12); {Заполнение в розовую клеточку} Bar(0,0,GetMaxX,GetMaxY);
setcolor(2); rectangle(200,200,250,250); line(200,200,225,125);  line(250,200,225,125);  circle(225,180,10);
setfillstyle(2,lightblue); {другое заполнение} fillellipse(400,200,100,50);
setcolor(lightgreen); {setlinestyle(0,0,3);}  line(150,300,150,100);
{  setlinestyle(1,0,1);}  line(150,100,170,130); line(150,100,130,150);  line(150,130,180,180); line(150,120,120,190);
line(150,160,180,210); line(150,140,120,210);  line(150,170,180,220); line(150,170,120,240);
end; {Fon}

procedure RememberPicture(var pointPic:pointer); {Запомнить картинку в динамически выделенную память}
{здесь PointPic – нетипизированный указатель}
var
size       :integer;
begin
Size:=ImageSize(0,0,Razmer*2,Razmer*2); {узнать требуемый размер памяти под картинку}
GetMem(pointPic,size); {заказать место в динамической памяти, её начало
запомнить в указатель pointPic}
GetImage(0,0,Razmer*2,Razmer*2,pointPic^); {помещает кусок изображения в выделенную память}
end; {RememberPicture;}

procedure Sposob0(x,y:integer; pointPic:pointer); {СПОСОБ 0 – перерисовка фона, тупой и медленный способ}
begin
Fon;
PutImage(x-Razmer,y-Razmer,pointPic^,OrPut);   
end; {sposob0}

procedure Sposob1(xold,yold,x,y:integer; pointPic:pointer); {СПОСОБ 1 – наложение морды на себя логическим XOR}
begin
PutImage(xold-Razmer,yold-Razmer,pointPic^,XorPut);   {аннигляция морды на старом месте}
PutImage(x-Razmer,y-Razmer,pointPic^,XorPut); {появление морды на новом месте}
end; {sposob1}

procedure Sposob2(xold,yold,x,y:integer; pointPic,PointFon:pointer); {СПОСОБ 2 – возвращение участка фона на старое место, запоминание фона на новом,
затем на это место накладывается морда}
begin
      PutImage(xold-Razmer,yold-Razmer,pointFon^,NormalPut); {возвратить фон на старом месте}
      GetImage(x-Razmer,y-Razmer,x+Razmer,y+Razmer,pointFon^); {запомнить фон на новом месте}
      PutImage(x-Razmer,y-Razmer,pointPic^,OrPut); {наложить морду на новое место}
{режим наложения морды здесь можно любой}
end; {sposob2}

procedure SmenaKart(page,xold,yold,x,y:integer; pointPic,pointFon:pointer);
begin
   PutImage(xold-Razmer,yold-Razmer,pointFon^,NormalPut); {возвратить фон на старом месте}
   if page=0 then GetImage(x-Razmer,y-Razmer,x+Razmer,y+Razmer,pointFon^); {если страница 0, фон запомнить}
   PutImage(x-Razmer,y-Razmer,pointPic^,OrPut);  {наложить морду на новое место}
{режим наложения морды здесь можно любой}
end; {SmenaKart}

procedure Sposob3(xold,yold,x,y:integer; pointPic,PointFon:pointer); {СПОСОБ 3 – со сменой видеостраниц}
begin
     SetVisualPage(0); SetActivePage(1);
          SmenaKart(1,xold,yold,x,y,pointPic,PointFon); {показывать 0-ю, а на 1-й сменить положение морды}
     SetVisualPage(1); SetActivePage(0);
          SmenaKart(0,xold,yold,x,y,pointPic,PointFon); {показывать 1-ю, а на 0-й запомнить новый фон и сменить
положение морды}
     {SetVisualPage(0); SetActivePage(0);} {в исходное положение – показывается и обрабатывается 0-я страница}
{закомментировано, т.к. это лишнее действие, замедляет работу}
end; {Sposob3}

var {описания главной части}
pointPic,pointFon :pointer;
x,y,xold,yold     :integer;
ch                :char;
Begin
BeginGraph;
setVisualPage(0); {чистый экран, показ страницы №0}
setActivePage(1); Picture; RememberPicture(pointPic); {скрытно нарисовали мордочку на чёрном=0  фоне,
чтобы цветной фон не мешал, и запомнили}
setActivePage(0); Fon;  RememberPicture(pointFon); {запомнили чистый фон на начальном месте,
без морды}
Picture; {показали на странице №0 ещё и морду, пусть рассматривают ...}
setActivePage(1); Fon; Picture; {... а в это время спокойно сделали 1-ю страницу такой же, как №0}
x:=razmer; y:=razmer; {текущее положение морды – в левом верхнем углу экрана}
ch:=' '; {какой-нибудь начальный символ, не стрелки и не Esc, например, пробел}
While true do begin
Strelki(ch,x,y); {новые значения x и y}
if (x<>xold) or (y<>yold) then     {если координаты морды изменились}
case Sposob of
0: Sposob0(x,y,pointPic);
1: Sposob1(xold,yold,x,y,pointPic);
2: Sposob2(xold,yold,x,y,pointPic,PointFon);
3: Sposob3(xold,yold,x,y,pointPic,PointFon);
end; {case}
end; {if}
xold:=x; yold:=y;   {координаты стали старыми}
delay(delayTime);  {задержка между кадрами}
end;  {While – это не зацикливание, т.к. внутри предусмотрена возможность останова при нажатии Esc}
End.

СРАВНИМ способы мультипликации (рисования подвижных изображений), меняя перед компиляцией значение постоянной Sposob. Что понравилось, что нет? Способ №0 (полная перерисовка) – самый простой при программировании, но самый медленный при осуществлении (т.к. долго перерисовывается большой фон) и очень некрасив (т.к. заметно надолго исчезает  морда). Способ №1 (XOR мордочки) – самый быстрый при исполнении, но даёт "полупрозрачную", как привидение, мордочку, и заметно, как она исчезает с экрана. Способ №2 (с возвращением запомненного фона) – медленнее, чем способ 1, в полтора  раза, и процесс перерисовки виден, это неприятно глазу. Способ №3 (с возвращением запомненного фона и сменой видеостраниц) – самый сложный при программировании и самый медленный при исполнении (втрое медленнее, чем №1), но показывает уже полностью перерисованные видеостраницы, это вполне годится для глаза, особенно для  больших подвижных объектов.
Можно ли построить такую процедуру, которая была бы не такой медленной, как Способ №3, и в то же время давала бы движущееся изображение, приятно воспринимаемое глазом? Можно! Ведь глазу неприятен не сам процесс перерисовывания (всё равно движущийся объект человек воспринимает размазанным), а временное исчезновение движущегося объекта (и вид фона под ним) при перерисовывании способами №№0,1,2.
Задание: добавьте в оператор Case Sposob и в  описание процедур ещё и способ №4. Суть его состоит в следующем. Исходное положение: запомнена морда, запомнен старый фон, морда наложена на старый фон Ф1 xor M1. При движении: новый участок поксорим с Мордой (в области пересечения старого и нового будет Ф xor M1 xor M2, вне пересечения Ф2 xor M2);   затем на старый участок поксорим Морду (в области пересечения старого и нового останется Ф  xor M2, в остальной старой части только Ф); так останется новый участок фона и Морда на нём. Поцедура всего в 1,5 раза медленнее, чем №1, зато переползание мордочки пороисходит без резкого моргания голого фона, как в №3.
procedure Sposob4(xold,yold,x,y:integer; pointPic:pointer); {СПОСОБ 4 – переползание морды, с логическим XOR}
begin
PutImage(x-Razmer,y-Razmer,pointPic^,XorPut); {появление морды на новом месте}
PutImage(xold-Razmer,yold-Razmer,pointPic^,XorPut);   {аннигляция морды на старом месте}
end; {sposob4}
Задание повышенной трудности: недостатком способа №4 является "полупрозрачность" мордочки. Придумайте процедуру, которая давала бы непрозрачную (как в режиме OrPut) мордочку, движение было бы приятно для глаза и при этом быстрее, чем №3.

Тема 17. СОБСТВЕННЫЕ МОДУЛИ

В предыдущих темах мы кратко ознакомились с тем, что такое модульное программирование, изобретённое советским математиком-экономистом Канторовичем. Напомним, что это средства сокрытия (независимости) почти всех составляющих внутреннего содержания сложных подпрограмм (внутренних данных и действий над ними) от связанных с ними иных подпрограмм и программы, кроме тех составляющих, которые явно объявлены как взаимодействующие со внешними (интерфейсными).
ПОДПРОГРАММА-МОДУЛЬ – отдельный блок (обычно даже отдельный файл) текста  программы, могущий компилироваться отдельно от основной   части программы. Модули обычно используют (и создают),   
во-первых, если некоторые процедуры и функции часто   используются во многих наших программах (т.е. удобно написать   их один раз в отдельный, оригинальный, модуль, а затем просто к очередной программе подключать ещё и этот модуль объявлением его в разделе uses);   
во-вторых, если программа оказалась настолько громоздка,   что при компиляции не хватает оперативной памяти, и поэтому   приходится делить программу на несколько раздельно   компилируемых частей.
в-третьих, если программа в целом необозримо велика или над ней работает коллектив разных людей, и желательно, во избежание путаницы великого множества переменных, констант, типов, объектов и т.д.и т.п,, выдуманных разными авторами разных частей программы, сделать те переменные, которые только "внутреннего пользования", скрытыми от иных модулей и программы, а те, которые должны взаимодействовать "снаружи" модуля, объявить таковыми (составляющими интерфейса этого модуля). 
Кроме стандартных модулей среды программирования, программист может делать и использовать свои особенные, оригинальные, нестандартные, уникальные модули. При их использовании достаточно объявить его имя в разделе uses. А вот как  сделать оригинальный модуль?

ЧАСТИ МОДУЛЯ
1) заголовок:  unit ИмяМодуля;
2) обязательный раздел внешнего взаимодействия: interface.  В нём объявляются глобальные (и для модуля, и для использующих его других модулей и программ) константы, переменные, процедуры и функции, ДОСТУПНЫЕ извне (т.е. ради чего и создается модуль). Порядок оформления интерфейсного раздела такой же, как и у обычной программы (см. Тему 5), за исключением разделов меток label и экспорта export – т.е., возможны разделы uses, const, type, var, доступные извне, а также заголовки доступных извне процедур и функций (их тела будут описаны в разделе воплощения). Для заголовков этих подпрограмм в интерфейсной части приводить списки параметров, их типы и тип результата не обязательно.
2) Обязательный раздел воплощения (он же "реализации", "инструментальный", "определения", "исполнения"):   implementation. В нём содержатся конкретные тексты (тела) процедур и функций, объявленных в интерфейсной части; а также модули, метки (если метки используются в части зачатия), константы, переменные, типы, процедуры и функции, НЕдоступные извне, т.е. служебные, внутренние, т.е. локальные для модуля.
3) Необязательный раздел зачатия (инициализации): операторы, выполняемые при инициализации модуля, до передачи управления основной программе. Инициализация используемых программой модулей происходит в порядке их перечисления uses. Эти операторы, если они есть, ограничиваются сверху словом begin , а снизу словом end. Начинающим программистам этот раздел не нужен, поэтому в конце модуля можно поставить просто end. Опытные программисты могут использовать здесь присвоение начальных значений, открытие нужных файлов и др. В О-Паскаль для начала раздела зачатия можно использовать специальное слово initialization .
4) В О-Паскаль есть ещё и необязательный раздел завершения (финализации): finalization . Начинающим программистам этот раздел не нужен, поэтому в конце модуля можно поставить просто end. Опытные программисты могут использовать этот раздел для того, чтобы выполнять некоторые действия в момент окончания работы программы. Например, в стандартных модулях Паскаля уже предусмотрено автоматическое закрытие всех открытых файлов.
5) Окончание (прерыватель, прекратитель, терминатор): слово end.

Внимание! Если в разделе воплощения нашего модуля подключить какой-либо внешний модуль, например: implementation uses dos; то он (в этом примере – модуль dos) перестанет быть доступным из других модулей и из той программы, к которой подключается наш модуль.
В Делфи предусмотрены и специальные типы модулей – формы (с окном), модули данных, модули динамичеки связываемых библиотек (для согласования подпрограмм, написанных на разных языках), модули потоков команд (для распараллеливания процессов).

ИЗБЕЖАНИЕ ПЕРЕОПРЕДЕЛЕНИЯ В ЗАПИСЯХ, ОБЪЕКТАХ, МОДУЛЯХ
Если в разных модулях встретились одинаковые идентификаторы (имена), например, переменной Vasia, то, если обращаться к ним без квалификатора ("квалифицирующего идентификатора", т.е. имени "материнского", "родного" для них модуля), то это означает обращение к последнему из модулей в списке uses. Чтобы обратиться к предыдущим модулям, придётся указать квалификатор: например,  ИмяМодуля.Vasia.
Чтобы различать одноимённые переменные из разных "материнских" частей, в объявлениях (declaration) можно использовать "квалификаторы" – имена "материнских" не только модулей, но и записей (см. Тема 11) и объектов (см. Тема 20).
ИмяЗаписи.ИмяПоля;
ИмяОбъекта.ИмяПоляДанных;
ИмяОбъекта.ИмяМетода;
.

Напомним (см. выше), что начиная с T-Паскаля возможно приведение типов универсальной формулой вида
Переменная:=ИмяТипаЭтойПеременной(АргументДругогоТипа);
но – внимание! – использовать при этом квалификаторы  нельзя.

ПРИМЕР МОДУЛЯ
Краткое имя файла, в котором находится модуль, должно совпадать с названием модуля, а расширение имени этого файла должно быть *.pas . Пример текста модуля  – файл alexey.pas  :
unit Alexey; {это заголовок}

interface {это начало раздела интерфейса}

uses
Crt;

function Tg;      {Можно было описать и Tg(x:real):real; так было бы даже удобнее проверять}
procedure Beep;
procedure Pause;

implementation {это начало раздела воплощения}

function Tg(x:real):real; {тангенс с защитой от деления на 0}
begin
try
Tg:=Sin(x)/Cos(x);
except {если деление на 0 или как-то иначе выходит запредельное значение тангенса}
Tg:=1.1e39;      {Проверьте на опыте, позволит ли компилятор написать Tg:=High(real); }
{другой вариант – с учётом знака функции: }
{if sin(x)*cos(x)>=0 then Tg:=High(real) else Tg:=Low(real); }
end;
end; {Tg}

{Полезно сделать также функции арксинус и арккосинус, выразив их через арктангенс, квадраты и корни, не забыв и защиту от деления на 0}

Procedure Sounder(friq,time:integer);
begin
Sound(friq);
Delay(time);
Nosound;
end; {Sounder}

Procedure Beep;
begin
Sounder(3000,100);
end; {beep}

Procedure Pause; {это уже третий вариант осуществления паузы , два других см. в предыдущих темах выше}
var
ch : char;
begin
while Keypressed do ch:=Readkey;        {предварительная очистка буфера клавиатуры – на всякий случай}
writeln('Press any key to continue...');
repeat until keypressed;
while Keypressed do ch:=Readkey;         {очистка буфера клавиатуры за собой}
{а вот написать просто ch:=Readkey было бы чревато загрязнением буфера вторым байтом в случае нажатия двухбайтовой клавши
end; {pause}

End. {конец модуля без инициализации и финализации}

КОМПИЛЯЦИЯ МОДУЛЯ
Готовый модуль следует скомпилировать. Предварительно следует убедиться в том, что он скомпилируется на диск:   настройте среду программирования F10–Compile–Destination–Disk. Если при компиляции всё было в порядке, то появится файл с тем же кратким именем, но с расширением *.tpu  (Turbo Pascal Unit).
{О компиляции для различных режимов DOS и для Windows поговорим позже.}
Внимание! Если вы при редактировании изменили в модуле только части воплощения, инициализации, финализации, то достаточно перекомпилировать этот модуль. Но если вы изменили в модуле часть интерфейса, то надо будет последовательно перекомпилировать и все другие модули, использующие внутри себя этот (изменённый) модуль. Для безошибочности борландовские компиляторы автоматически порождают и хранят номер версии модуля, при несовпадении сообщают об ошибке. Для удобства при вызове режима компиляции Make или Build борландовский компилятор автоматически перекомпилирует все используемые модули.

ПРИМЕР ИСПОЛЬЗОВАНИЯ ОРИГИНАЛЬНОГО МОДУЛЯ
program ProbaMyModule;
uses
crt,dos,graph,alexey;
begin
clrscr;
Pause;
Beep;
writeln('Тангенс от четверти Пи =',Tg(pi/4):0:3);
Pause;
{Sounder(2000,1000);}   {Процедуру Sounder использовать невозможно – она не объявлена в interface ! }
Beep;
Pause;
end.

ЗАДАНИЕ НА САМОСТОЯТЕЛЬНУЮ РАБОТУ
Преобразуйте ваш набор процедур и функций основ вычислительной математики (см. в Теме 9) в один или несколько модулей.

Тема 18. ОТЛАДКА.

В среде программирования на Паскале есть средства отслеживания (трассировки, tracerouting) и отладки (debugging) программ, на любой вкус. Они облегчают испытание, проверку и, при необходимости, исправление программ.

Действия по ТРАССИРОВКЕ И ОТЛАДКЕ (DEBUG) программ:
FIND ERROR – отыскивает строку, вызвавшую ошибку при исполнении (RunTimeError).
Ctrl+K n – установка в программу невидимого маркера (пометки, закладки) с номерами n=0..3
Ctrl+Q n – поиск невидимой пометки с номером n.
Ctrl+F8 – установка/снятие контрольной точки без номера.
WATCHES – прослеживать за значением выбранной (указанной) переменной при исполнении программы.
DEBUG – EVALUATE/MODIFY : искусственное изменение пользователем значения переменной при пошаговой отладке. Допустим, хочется узнать, что будет, если его изменить вопреки алгоритму.
GO TO CURSOR (F4): начать или продолжить исполнение программы и остановиться перед исполнением той строки, на которой стоит курсор.
TRACE INTO (F7): выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), то войти в эту процедуру и остановиться перед исполнением первого её оператора.
STEP OVER (F8): выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), то исполнить ее, причем не прослеживать ее работу, и вернуться в программу.
Ctrl+F4 – показать все сведения о той переменной в тексте программы, на которую наведён курсор.
Ctrl+F2 – прекратить режим отладки.

(продолжение следует)


Рецензии