Энциклопедия Turbo Pascal. Главы 9-11

ОГЛАВЛЕНИЕ


 

Глава 9. Инструментарий баз данных Turbo Pascal

Мощным средством, существующим в Турбо Паскале, является ин струментарий баз данных. Он содержит процедуры, которые поддержи вают операции в базах данных типа В-дерева, сортировки и установ ку терминалов оконечных пользователей. Они называются  Turbo Access,  Turbosort и GINST. В данной главе рассматривается каждая из них и делается ударение на процедурах баз данных. Примеры, данные в этой главе, отражают инструментарий баз данных, предназначенный для использования с Турбо Паскалем версии 3. Инструментарий баз данных, предназначенный для Турбо Паскаля версии 4 и последующих, существенно отличается от предназначенных для более ранних версий. Наиболее важное отличие заключается в том, что процедуры инструментария версии 4 содержатся в блоках, которые связываются с вашей программой, как это необходимо, вмес то включения с использованием директивы компилятора SI, как в ин струментарии версии 3.


Turbo Access

Процедуры баз данных TURBO ACCESS реализуют полную файловую структуру типа В-дерева. В-дерево названо по имени его изобретателя  R.Bayer. Оно отличается от обычного двоичного дерева тем, что каждый корневой узел может иметь более двух детей, как показано на рис.9-1. Из-за организации В-дерева файлы, размещающиеся на диске, могут быть найдены очень быстро. Хотя реализация В-дерева может быть довольно трудной, вам не надо понимать, как функции используют процедуры инструментария - фирма  Borland  сделала за вас всю трудную работу.

                        -----¬
                 / -----L----- -----\
               /           |          \
           -----¬       -----¬        -----¬
         / L-----       L-----        L----
       /      |       /        \         |     \
   -----¬  -----¬  -----¬     -----¬  -----¬   -----¬
   L-----  L-----  L-----     L-----  L-----   L-----

 

Рис.9-1. Пример В-дерева


Необходимые файлы

Прежде, чем вы сможете использовать любую процедуру инструментария, вы должны иметь в наличие следующие файлы:

Имя файла          Функция
     ACCESS.BOX         Основной файл данных и индексов для инициализации и установки
     ADDKEY.BOX         Добавляет ключи к индексным файлам
     DELKEY.BOX         Удаляет ключи из индексных файлов
     GETKEY.BOX         Находит ключи

Отметим, что инструментарий баз данных поставляется с двумя версиями базовых процедур ввода/вывода: одна для версии 2 Турбо Паскаля, другая для версии 3  Турбо Паскаля. Они называются ACCESS.BOX и ACCESS3.BOX, соответственно. Вы должны скопировать соответствующий файл в ACCESS.BOX для того, чтобы включить соответствующие процедуры, как показано в примерах в данной главе. С этого момента будем полагать, что процедуры ввода/вывода баз данных находятся в файле ACCESS.BOX.

В дополнение к этим файлам вам еще нужна программа SETCONST. PAS, которая прилагается к инструментарию для вычисления нескольких констант, необходимых для процедур В-дерева.


Файлы данных и индексные файлы

В Turbo Access файл данных используется для хранения записей, которые содержат запоминаемую вами информацию. Каждый файл данных, созданный и поддерживаемый Turbo Access, может содержать до 65 536 записей. Однако, в данное число входит и первая запись, которая зарезервирована для использования Turbo Access. Таким образом вы в действительности можете запомнить 65535 записей. Хотя длина каждой записи может быть до 65536 байт, это не рекомендуется. Наименьшая допустимая длина записи - 8 байт. Все переменные файлов данных в Turbo Access должны быть декларированы как тип DataFile, который задан в ACCESS.BOX.

Индексный файл содержит ключи, которые используются для идентификации специфических записей и номеров записей, связанных с ключем. Каждый индексный файл может содержать до 65535 ключей. Индексный файл обеспечивает быстрый способ определения местоположения специфических записей в файле данных. Важно понять, что все ключи должны быть строками. Все переменные индексных файлов в Turbo Access должны быть декларированы как IndexFile.


Константы В-дерева в Turbo Access

Шесть специальных констант должно быть задано любой программой, которая использует систему Turbo Access. Эти константы управляют такими спецификациями, как высота дерева и число узлов, подсоединенных к каждому корню. Имеются следующие константы:

     MaxDataRecSize
     MaxKeyLen
     PageSize
     Order
     PageStackSize
     MaxHeight

Значение данных констант лучше всего вычислять с помощью программы SETCONST.PAS. Используя данную программу вы должны ввести длину записи данных, которые вы будете запоминать, и длину ключа, используемого для индексирования каждой записи. Мы рассмотрим использование SETCONST.PAS далее в данной главе.


Зарезервированные имена переменных и коды ошибок

Turbo Access использует глобальные переменные, имена которых начинаются с буквы "ТА". Хотя вы также можете объявить переменные, которые начинаются с "ТА", но это не рекомендуется. Это может привести к сообщениям об ошибках во время компиляции, указывающим на дублирование имен  идентификаторов.  Если  ошибка ввода/вывода происходит, когда вы используете процедуры Turbo
Access, отображается один из кодов, показанных в таблице 9-1.

Таблица 9-Коды ошибок ввода/вывода Turbo Access

--------------------------------------------------------------
Код             Смысл
--------------------------------------------------------------
  1       Неожидаемая длина записи
  2       Несуществующий файл
  3       Директория переполнена
  4       Файл не открыт
  5       Файл не открыт для ввода
  6       Файл не открыт для вывода
  7       Преждевременный EOF(конец файла)
  8       Ошибка записи
  9       Ошибка формата числа
 10       Преждевременный конец файла
 11       Размер файла превысил 65535 байт
 12       Преждевременный конец файла
 13       Ошибка записи
 14       Попытка поиска за концом файла
 15       Файл не открыт
 16       Недопустимая операция
 17       Недопустимое использование непосредственного режима
 18       Недопустимое использование присваивания стандартным
          файлам
 144      Неожидаемая длина записи
 145      Попытка поиска за концом файла
 153      Преждевременный конец файла
 240      Ошибка записи
 243      Слишком много открытых файлов
---------------------------------------------------------------

 


 

Процедуры для работы с базами данных

AddRec

      procedure AddRec(var dfile: DataFile, var RecNum,
                       var buffer);

Процедура AddRec добавляет записи, содержащиеся в buffer, к файлу данных dfile и возвращает номер записи, в которой осуществлено запоминание, в RecNum переменная buffer не имеет типа, но она должна содержать правильную запись. При успешном выполнении ОК содержит TRUE; в противном случае она равна FALSE. RecNum часто используется в последующем вызове AddKey.

ClearKey

Процедура ClearKey используется для сброса указателя индекса индексного файла в начало файла. Она объявляется следующим образом:

    procedure ClearKey(var ifile: IndexFile);

CloseFile и CloseIndex

Данные процедуры используются для закрытия файла данных и индексного файла, соответственно. Они объявляются следующим образом:

     procedure CloseFile(var dfile: DataFile);
     procedure CloseIndex(var ifile: IndexFile);

Вы должны закрыть все файлы, которые были открыты процедурами Turbo Access. Если этого не делать, то могут быть потеряны данные или разрушен индексный файл.

DeleteKey

Данная процедура удаляет ключ из индексного файла. Она объявляется следующим образом:

    procedure DeleteKey(var ifile: Indexfile, var RecNum: integer, var key);

Если в файле допускаются дублированные ключи, то  RecNum  должна содержать номер записи данных, связанной с ключем, который должен быть удален. Если операция завершилась успешно, то ОК равно TRUE, в противном случае - FALSE.

DeleteRec

Процедура DeleteRec  удаляет указанную запись из файла данных. Она объявляется следующим образом:

    procedure DeleteRec(var dfile.  DataFile,var RecNum:integer);

Данная процедура осуществляет удаление записи с номером RecNum. Если операция выполняется успешно, ОК равно  TRUE, в  противном случае - FALSE. Уничтожаемая запись удаляется из файла логически, но она остается в файле физически и помещается в связанный список уничтоженных записей для возможного последующего использования. Важно, чтобы вы никогда не пытались уничтожить уже уничтоженную запись. Делая так, можно разрушить связанный список уничтоженных записей.

FileLen

Функция FileLen возвращает число записей в файле данных.

Она объявляется следующим образом:

     function FileLen(var dfile: DataFile): integer;

Помните, что все файлы Turbo Access используют первую запись для внутренних целей так, что число записей, действительно содержащих ваши данные, меньше на 1 возвращаемого данной функцией.

FindKey

Данная процедура определяет местоположение заданного ключа в индексном файле и возвращает соответствующий номер записи файла данных. Она объявляется следующим образом:

     procedure FindKey(var ifile: IndexFile,var RecNum:integer, var key);

Если key является ключем в заданном файле, то RecNum устанавливается на запись, связанную с ключем в файле данных. Если операция выполнилась успешно, то ОК устанавливается в значение  TRUE, в противном случае - в FALSE.

GetRec

Процедура GetRee осуществляет считывание записи информации с заданным номером из файла данных. Она объявляется следующим образом:

     procedure GetRec(var dfile: DataFile, var RecNum: integer, var buffer);

Запись с номером RecNum считывается в buffer, который должен по размеру соответствовать записи.

InitIndex

Данная процедура использует для инициализации таблицы, используемой Turbo Access для установки индексных файлов. Она не имеет параметров. Данная процедура должна вызываться однажды в начале вашей программы до вызова других процедур Turbo Access.

MakeFile MakeIndex

Данные процедуры используются для создания файлов данных и индексных файлов Turbo Access. Они объявляются следующим образом:

     procedure MakeFile(var dfile: DataFile, fname: string[14], RecLen: integer);
     procedure MakeIndex(var ifile: IndexFile, fname:string[14], KeyLen,DupKeys: integer);

Параметр RecLen  в  MakeFile должен содержать значение длины данных, которые будут запоминаться. Лучший способ сделать это - использование  SizeOf  вместо подсчета байт. Параметр  KeyLen  в MakeIndex должен содержать значение длины ключа. Если DopKey установлен в 0, то дублирование ключей не допускается, а если в 1, то допускается. Успешное выполнение операции устанавливает ОК в TRUE, в противном случае - в FALSE.

NextKey

Данная процедура возвращает соответствующий номер записи файла данных, связанный со следующим ключем в индексном файле. Она объявляется следующим образом:

     procedure NextKey(var   ifile:   IndexFile,  var  RecNum: integer, var key);

После обращения к процедуре RecNum будет содержать номер записи в файле данных, соответствующей следующему ключу в индексном файле. Переменной key будет присвоено значение следующего ключа. При успешном обращении к NextKey ОК устанавливается в  TRUE, в противном случае - в FALSE.

OpenFile и OpenIndex

Данные процедуры используются для открытия существующих файла данных и индексного файла, соответственно. Они объявляются следующим образом:

     procedure OpenFile(var dfile: DataFile, fname: string[14], RecLen: integer);
     procedure OpenIndex(var ifile:IndexFile,fname: string[14], KeyLen, DupKeys: integer);

Параметр RecLen в OpenFile должен быть установлен в значение длины данных, которые будут запоминаться. Лучший способ сделать это состоит в том, чтобы применить SizeOf вместо подсчета байт. Параметр RecLen в OpenIndex должен быть установлен в значение длины ключа. Если параметр DupKey установлен в 0, дублирование ключей не допускается, а если 1, то разрешается. При успешном выполнении данных процедур ОК устанавливается в TRUE, в противном случае - в FALSE.

PrevKey

Данная процедура используется для возврата номера записи в файле данных, связанной с предыдущим ключем в индексном файле. Она объявляется следующим образом:

     procedure PrevKey(var ifile: IndexFile, var RecNum: integer, var key);

После обращения к процедуре параметр RecNum будет содержать номер записи в  файле данных, соответствующей предыдущему ключу в индексном файле. Переменной key будет присвоено значение предыдущего ключа. При успешном выполнении данной процедуры ОК устанавливается в TRUE, в противном случае - в FALSE.

PutRec

Данная процедура помещает запись информации с заданным номером в файл данных. Она объявляется следующим образом:

     procedure PutRec(var dfile: DataFile, var RecNum:integer, var buffer);

Информация из buffer помещается в запись с номером RecNum. Переменная buffer должна иметь размер соответствующий длине записи.

SearchKey

Данная процедура используется для поиска в индексном файле первого ключа, который не меньше ключа, заданного в вызове процедуры. Она объявляется следующим образом:

     procedurte SearchKey(var ifile: IndexFile, var RecNum: integer, var key);

При успешном выполнении процедура возвращает номер записи в файле данных, связанной с ключем в RecNum и устанавливает ОК в TRUE. В противном случае ОК устанавливается в FALSE.

UsedRec

Данная процедура возвращает число записей в файле данных, содержащих истинные данные. Уничтоженные записи не подсчитываются. Процедура объявляется следующим образом:

     function UsedRecs(var dfile: DataFile): integer;


Пример простого почтового списка

В качестве примера использования Turbo Access будет заново с применением его возможностей написана программа ведения простого почтового списка, разработанная в главе 2, которая использовала связанные ссылки. Во-первых, нам необходимо найти размер записи, используемой для хранения адресной информации, а затем вы примените программу  SETCONST для вычисления соответствующих значений шести констант, требующихся для процедур Turbo Access.

Так как  Turbo  Access  использует первые два байта в каждой записи в качестве флага индикации удаления, необходимо добавить поле целой переменной к исходной адресной записи. Кроме того, поля "предыдущий" и "следующий" больше не используются. Результирующая запись выглядит следующим образом:

     type
       address = record
          status: integer; { используется Turbo Access }
          name: string[30];
          street: string[40];
          city: string[20];
          state: string[2];
          zip: string[9];
       end;

Длина в байтах поля adress, которая найдена, используя  SizeOf, равна 108. Вам нужно будет данное число при выполнении программы SETCONST.PAS.

Программа SETCONST.PAS определяет значения констант, необходимых для Turbo Access. При запуске программы появляется экран, аналогичный показанному на рис.9-2, со значениями, принимаемыми по умолчанию. Руководство пользователя по инструментарию баз данных говорит, что для большинства применений вам необходимо изменить только длину записи и длину ключа, чтобы подстроиться под ваши особенности. Необходимо также изменить число записей, запоминаемых в базе данных, если оно превышает 10000. Размер страницы и размер стека страниц изменять не надо. Длина записи adress равна 108, так что сначала надо ввести это значение. В данном примере поле name будет использоваться в качестве ключа, поэтому значение 30 вводится как длина ключа. Клавишу  RETURN  нажать для оставшихся полей. Когда информация введена, SETCONT.PAS отображает экран, показанный на рис.9 -3.

При выходе из  SETCONST.PAS вы можете автоматически создать соответствующую декларацию const. Декларация для программы

** 1 - рабочий лист задания констант Turbo Access, версия
                                               1.10А *.

размер записи данных (байт)                 200   20.

длина строки ключа (символов)                1.

размер базы данных (записей)              1000.

размер страницы (ключей)                     2.

размер стека страниц (страниц)               1.

плотность (процент используемых элементов в средней странице)
                                               50% 75% 100%

общее количество страниц индексного файл.

память, используемая для стека страниц (байт.

размер страницы индексного файла (байт.

размер индексного файла (байт.

размер файла данных (байт)

порядо.

максимальная высот.

среднее количество просмотров, необходимое для нахождения ключ.

среднее количество просмотров, удовлетворяемое стеком страни.

среднее количество просмотров на диске для нахождения ключа

нажмите ESC для завершения программы

Рис.9-2. Начальный экран SETCONST.PAS

  ** Turbo Access constant determination worksheet, Version  1.10A
Data record size (bytes)
                       108 Key string Iength (characters)
                  30 Size of the database (records)
               10000 Page size (keys)
                                24 Page stack size (pages)
                         10

Density (Percent of Items in use per average Page)50% 75% 100%

Total Index file pages
                           834 556 417 Memory used for page stack (bytes)
              8430 8430 8430 Index file page size (bytes)
                     843 843 843 Index file size (bytes)
Data file size (bytes)

Order                                             12  12  12 MaxHeight
                                          4   4   3 Average searches needed to find a key
           3.71 3.19 2.90 Average searches satisfied by page stack
        1.75 1.50 1.38 Average disk searches needed to find a key
      1.96 1.69 1.52

ESC to end program


Рис.9-3. Экран   SETCONST.PAS  с  вычисленными  значениями: почтового списка показаны ниже. Комментарии даны автором.

     Const
       {данные константы сгенерированы программой SETCONST.PAS  предоставляемой инструментарием баз данных.}

       MaxDataRecSize = 108;
       MaxKeyLen      = 30;
       PageSize       = 24;
       Order          = 12;
       PageStackSize  = 10;
       MaxHeight      =  4;

данные константы сгенерированы программой SETCONST.PAS, предоставляемой инструментарием баз данных.

С данной информацией и  включенными необходимыми файлами Turbo Access первая часть программы почтового списка выглядит следующим образом:

     program db_example;

     Const
       {данные константы сгенерированы программой SETCONST.PAS предоставляемой инструментарием баз данных.}

       MaxDataRecSize = 108;
       MaxKeyLen      =  30;
       PageSize       =  24;
       Order          =  12;
       PageStackSize  =  10;
       MaxHeight      =  4;

     type
       address = record
         status: integer; {используется Turbo Access }
         name: string[30];
         street: string[40];
         city: string[20];
         state: string[2];
         zip: string[9];
       end;
     {следующие файлы содержат процедуры баз данных}
     {$i access.box} {основные процедуры баз данных}
     {$i addkey.box} {добавить элементы            }
     {$i delkey.box} {удалить элементы             }
     {$i getkey.box} {поиск по дереву              }

     var
       dbfile: DataFile;
       ifile: IndexFile;
       done: boolean;

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

     begin
       InitIndex;
       OpenFile(dbfile, 'mail.lst', SizeOf(address));
       if not OK then
       begin
         WriteLn('creating new data file');
         MakeFile(dbfile, 'mail.lst', SizeOf(address));
       end;
       OpenIndex(ifile, 'mail.ndx', 30, 0);
       if not OK then
       begin
         WriteLn('creating new index file');
         MakeIndex(ifile, 'mail.ndx', 30, 0);
       end;
       done:=false;
       repeat
         case MenuSelect of
           '1': Enter;
           '2': Remove;
           '3': ListAll;
           '4': Search;
           '5': Update;
           '6': done:=true;
         end;
       until done;
       CloseFile(dbfile);
       CloseIndex(ifile);
     end.

Отметим, что больше не необходимо явно загружать и сохранять почтовый список: процедуры Turbo Access объявляют файлы автоматически.

Процедура Enter, показанная далее, вводит адресную информацию, запоминает ее в файле данных и помещает ключ в  индексный файл. Отметим, что поле status каждой записи установлено в 0. По соглашению Turbo Access использует 0 для обозначения активной записи, а не нулевое значение для обозначения уничтоженных элементов.

       temp: string[30];
       info: address;
     begin
       done:=FALSE;
       repeat
         Write('Enter name:');
         Read(info.name); WriteLn;
         if Length(info.name)=0 then done:=TRUE
         else
         begin
           Write('Enter street: ');
           Read(info.street); WriteLn;
           Write('Enter city: ');
           Read(info.city); WriteLn;
           Write('Enter state: ');
           Read(info.state); WriteLn;
           Write('Enter zip: ');
           Read(info.zip); WriteLn;
           info.status:=0; {сделать активной }
           FindKey(ifile, recnum, info.name);
           if not OK then {убедитесь, что нет дублированных
                            ключей }

           begin
             AddRec(dbfile, recnum, info);
             AddKey(ifile, recnum, info.name);
           end else WriteLn('Duplicate key ignored');
         end;
       until done;
     end; {Enter}

Как вы видите, данная процедура осуществляет проверку на дублирование ключей. Так как дублированные имена не допускаются, процедура, во-первых, проверяет, соответствует ли новый ключ какому-либо уже существующему в файле. Если это так, то он игнорируется. Порядок вызова AddRec и AddKey критичен, так как переменная RecNum должна быть сначала установлена процедурой  AddRec  и затем запомнена процедурой AddKey (Помните, что номер записи файла данных связан с ключем в индексном файле и  используется для последующего нахождения данных).

Процедура ListAll  рассчитывает все содержимое  почтового списка:

     procedure ListAll;

     {добавить адрес к списку}
     procedure Enter;
     var
       done: boolean;
       recnum: integer;

     var
       info: address;
       len, recnum: integer;
     begin
       len: = filelen(dofile) -1;
       for recnum:=1 to len do
       begin
       GetRec(dbfile, recnum, info);
       {display if not deleter}
       if info.status = 0 then display(info);
     end;
   end; {ListAll}

Процедура FileLen  возвращает число записей активных или уничтоженных в файле данных, включая первую запись, которая зарезервирована для использования Turbo Access. Следовательно, действительное число пользовательских записей на единицу меньше. Кроме того, из-за того, что некоторые записи могут быть уничтожены, необходимо проверять поле status до отображения информации.

Поиск определенного адреса включает в себя поиск ключа в индексном файле с  помощью процедуры FindKey. Когда ключ найден, возвращается соответствующий номер записи в файле данных и он используется процедурой  GetRec  для получения нужной информации. Процедура Search, показанная далее реализует данный подход:

     {найти заданный элемент}
     procedure Search;
     var
       name: string[30];
       recnum: integer;
       info: address;
     begin
       Write('Enter name: ');
       ReadLn(name);

       {найти ключ,если он существует}
       FindKey(ifile, recnum, name);
       if OK then { если найден }
       begin
         GetRec(dbfile, recnum, info);
         {display if not deleter}
         if info.status = 0 then Display(info);
       end else WriteLn('not found');
     end; {Search}

Наконец, модификация существующей записи предполагает, что вы должны сначала найти запись, считать ее, модифицировать и записать обратно в файл данных. Процедура Update иллюстрирует простой метод обновления, в котором пользователь должен ввести заново всю информацию. Более сложные подходы требуют перевода только изменяемых полей.

     { изменение адреса в списке, исключая поле имени }
     procedure Update;
     var
       done: boolean;
       recnum: integer;
       temp: string[30];
       info: address;
     begin
       Write('Введите имя: ');
       Read(info.name); WriteLn;
       FindKey(ifile, recnum, info.name);
       if OK then
       begin
         Write('Введите улицу: )';
         Read(info.street); WriteLn;
         Write('Введите город: ');
         Read(info.city); WriteLn;
         Write('Введите штат: ');
         Read(info.state); WriteLn;
         Write('Введите индекс: ');
         Read(info.zip); WriteLn;
         info.status:=0; {сделать активной}
         PutRec(dbfile, recnum, info);
       end else WriteLn('ключ не найден');
     end; {Update}


Программа, использующая Turbo Access для работы с файлами

Целиком программа, использующая Turbo Access для работы с файлами, выглядит следующим образом:

     program db_example;

     Const
       {данные константы сгенерированы программой SETCONST.PAS.

 

предоставляемой инструментарием баз данных.         }
       MaxDataRecSize = 108;
       MaxKeyLen      = 30;
       PageSize       = 24;
       Order          = 12;
       PageStackSize  = 10;
       MaxHeight      =  4;

     type
       address = record
         status: integer; {используется Turbo Access }
         name: string[30];
         street: string[40];
         city: string[20];
         state: string[2];
         zip: string[9];
       end;
     {следующие файлы содержат процедуры баз данных}
     {$i access.box} {основные процедуры баз данных}
     {$i addkey.box} {добавить элементы            }
     {$i delkey.box} {удалить элементы             }
     {$i getkey.box} {поиск по дереву              }

     var
       dbfile: DataFile;
       ifile: IndexFile;
       done: boolean;

     function MenuSelect:char; {возврат пользовательского
                            выбора }

     var
       ch:char;
     begin
       WriteLn('1.       ');
       WriteLn('2. Удалить имя      ');
       WriteLn('3. Отобразить список');
       WriteLn('4. Обновление       ');
       WriteLn('5. Поиск по имени   ');
       WriteLn('6. Выход            ');
       repeat
         WriteLn;
         Write('Введите ваш выбор:');
         Read(ch); ch:=UpCase(ch); WriteLn;
         until (ch>='1') and (ch<='6');
         MenuSelect:=ch;
       end; {MenuSelect}

     {добавить адрес к списку}
       procedure Enter;
       var
         done: boolean;
         recnum: integer;
         temp: string[30];
         info: address;
       begin
         done:=FALSE;
         repeat
           Write('Введите имя: ');
           Read(info.name); WriteLn;
           if Length(info.name)=0 then done:=TRUE

         else

         begin
         Write('Введите улицу: ');
         Read(info.street); WriteLn;
         Write('Введите город: ');
         Read(info.city); WriteLn;
         Write('Введите штат: ');
         Read(info.state); WriteLn;
         Write('Введите индекс: ');
         Read(info.zip); WriteLn;
         info.status:=0; {сделать активной}
           FindKey(ifile, recnum, info.name);
           if not OK then {убедитесь, что нет дублированных
                            ключей }
           begin
             AddRec(dbfile, recnum, info);
             AddKey(ifile, recnum, info.name);
           end else WriteLn('Дублированный ключ игнорирован');
         end;
       until done;
     end; {Enter}

     { изменение адреса в списке, исключая поле имени }
     procedure Update;
     var
       done: boolean;
       recnum: integer;
       temp: string[30];
       info: address;
     begin
       Write('Введите имя: ');
       Read(info.name); WriteLn;
       FindKey(ifile, recnum, info.name);
       if OK then
       begin
         Write('Введите улицу: ');
         Read(info.street); WriteLn;
         Write('Введите город: ');
         Read(info.city); WriteLn;
         Write('Введите штат: ');
         Read(info.state); WriteLn;
         Write('Введите индекс: ');
         Read(info.zip); WriteLn;
         info.status:=0; {сделать активной}
         PutRec(dbfile, recnum, info);
       end else WriteLn('ключ не найден');
     end; {Update}

     {удалить адрес из списка }

     procedure Remove;
     var
       recnum: integer;
       name: string[30];
       info: address;
     begin
       Write('Введите имя для удаления : ');
       Read(name); WriteLn;
       FindKey(ifile, recnum, name);
       if OK then
       begin
         DeleteRec(dbfile, recnum);
         DeleteKey(ifile, recnum, name);
       end else WriteLn('Не найдено');
     end; {Remove}

     procedure Display(info: address);
     begin
       WriteLn(info.name);
       WriteLn(info.street);
       WriteLn(info.city);
       WriteLn(info.state);
       WriteLn(info.zip); WriteLn;
     end; {Display}

     procedure ListAll;
     var
       info: address;
       len, recnum: integer;
     begin
       len := fileLen(dbfile) -1;
       for recnum:=1 to len do
       begin
         GetRec(dbfile, recnum, info);
         {отобразить, если не уничтожен}
         if info.status = 0 then display(info);
       end;
     end; {ListAll}

     {Найти заданный элемент }
     procedure Search;
     var
       name: string[30];
       recnum: integer;
       info: address;
     begin
       Write('Введите имя: ');
       ReadLn(name);

       {найти ключ, если существует}
       FindKey(ifile, recnum, name);
       if OK then
       begin
         GetRec(dbfile, recnum, info);
         {отобразить, если не уничтожен}
         if info.status = 0 then Display(info);
       end else WriteLn('не найден');
     end; {Search}

     begin
       InitIndex;
       OpenFile(dbfile, 'mail.lst', SizeOf(address));
       if not OK then
       begin
         WriteLn('Cоздание нового файла');
         MakeFile(dbfile, 'mail.lst', SizeOf(address));
       end;
       OpenIndex(ifile, 'mail.ndx', 30, 0);
       if not OK then
       begin
         WriteLn('Cоздание нового файла ');
         MakeIndex(ifile, 'mail.ndx', 30, 0);
       end;
       done:=false;
       repeat
         case MenuSelect of
           '1': Enter;
           '2': Remove;
           '3': ListAll;
           '4': Search;
           '5': Update;
           '6': done:=true;
         end;
       until done;
       CloseFile(dbfile);
       CloseIndex(ifile);
     end.


Пример программы инвентаризации

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

     type
       inv = record
          status: integer;
          name: string[30];
          descript := string[40];
          guantity: integer;
          cost: real;
        end;

Длина ее, найденная с помощью SizeOf, равна 83. Используя данную длину и длину ключа, равную 30, программа SETCONST.PAS создает определение констант

     Const
       MaxDataRecSize = 82;
       MaxKeyLen      = 30;
       PageSize       = 24;
       Order          = 12;
       PageStackSize  = 10;
       MaxHeight      =  4;

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

     program inventory;

     Const
       { данные константы генерируются программой SETCONST.PA.

 

предоставляемой инструментарием баз данных }
       MaxDataRecSize = 82;
       MaxKeyLen      = 30;
       PageSize       = 24;
       Order          = 12;
       PageStackSize  = 10;
       MaxHeight      =  4;

     type
       inv = record
       status: integer;
       name: string[30];
       descript: string[40];

       guantity: integer;
       cost: real;
     end;

     {следующие файлы содержат процедуры баз данных}
     {$i access.box} {основные процедуры баз данных}
     {$i addkey.box} {добавить элементы            }
     {$i delkey.box} {удалить элементы             }
     {$i getkey.box} {поиск по дереву              }

     var
       dbfile: DataFile;
       ifile: IndexFile;
       done: boolean;

     function MenuSelect:char; {возврат пользовательского
                            выбора }
     var
       ch:char;

     begin
       WriteLn('1. Введите элемент              ');
       WriteLn('2. Удалить элемент              ');
       WriteLn('3. Отобразить инвентарный список');
       WriteLn('4. Поиск элементов              ');
       WriteLn('5. Обновление                   ');
       WriteLn('6. Выход                        ');

       repeat
         WriteLn;
         Write('Введите ваш выбор: ');
         Read(ch); ch:=UpCase(ch); WriteLn;
       until (ch>='1') and (ch<='6');
       MenuSelect:=ch;
     end; {MenuSelect}

     {добавить элемент к списку}
     procedure Enter;
     var
       done: boolean;
       recnum: integer;
       temp: string[30];
       info: inv;
     begin
       done:=FALSE;
       repeat
         Write('Введите имя элемента: ');
         Read(info.name); WriteLn;

         if Length(info.name)=0 then dont:=TRUE
         else
         begin
           Write('Введите описание: ');
           Read(info.descript); WriteLn;
           Write('Введите количество: ');
           Read(info.guantity); WriteLn;
           Write('Введите стоимость: ');
           Read(info.cost); WriteLn;
           info.status:=0; { сделать активной }
           FindKey(ifile, recnum, info.name);
           if not OK then
           begin
             AddRec(dbfile, recnum, info);
             AddKey(ifile, recnum, info.name};
           end else WriteLn('дублированный ключ игнорирован');
         end;
       until done;
     end; {Enter}

     {изменение элемента в списке с сохранением поля имени}
     procedure Update;
     var
       done: boolean;
       recnum: integer;
       temp: string[30];
       info: inv;

     begin
       Write('Enter item name: ');
       Read(info.name); WriteLn;
       FindKey(ifile, recnum, info.name);
       if OK then
       begin
           Write('Введите описание: ');
           Read(info.descript); WriteLn;
           Write('Введите количество: ');
           Read(info.guantity); WriteLn;
           Write('Введите стоимость: ');
           Read(info.cost); WriteLn;
           info.status:=0;
         info.status:=0; {сделать активной}
         PutRec(dbfile, recnum, info);
       end else WriteLn('ключ не найден');
     end; {Update}

     {удалить элемент из инвентарного списка}
     procedure Remove;

     var
       recnum: integer;
       name: string[30];
       begin
         Write('Введите имя уничтожаемого элемента: ');
         Read(name); WriteLn;
         FindKey(ifile, recnum, name);
         if OK then
         begin
           DeleteRec(dbfile, recnum);
           DeleteKey(ifile, recnum, name);
         end else WriteLn('Не найдено');
     end; {Remove}

     procedure Display(info: inv);
     begin
       WriteLn('Item name: ',info.name);
       WriteLn('Description: ',info.descript);
       WriteLn('Quantity on hand: ',info.quantity);
       WriteLn('Initial cost: ',info.cost:10:2);
       WriteLn;
     end; {Display}

     procedure ListAll;
     var
       info: inv;
       len, recnum: integer;

     begin
       len := filelen(dbfile) -1;
       for recnum:=1 to len do
       begin
         Getrec(dbfile, recnum, info);
         if info.status = 0 then display(info);
       end;
     end; {ListAll}

     {поиск элемента}
     procedure Search;
     var
       name: string[30];
       recnum: integer;
       info: inv;
     begin
       Write('Введите имя элемента: ');
       ReadLn(name);

       {найти ключ, если он существует}

       FindKey(ifile, recnum, name);
       if OK then {если найден}
       begin
         GetRec(dbfile, recnum, info);
         if info.status = 0 then Display(info);
       end else WriteLn('не найден');
     end; {Search}

     begin
       InitIndex;
       OpenFile(dbfile, 'inv.lst', SizeOf(inv));
       if not OK then
       begin
         WriteLn('Cоздание нового файла');
         MakeFile(dbfile, 'inv.lst', SizeOf(inv));
       end;
       OpenIndex(ifile, 'inv.ndx', 30, 0);
       if not OK then
       begin
         WriteLn('Cоздание нового файла');
         MakeIndex(ifile, 'inv.ndx', 30, 0);
       end;
       done:=false;
       repeat

         case MenuSelect of
            '1': Enter;
            '2': Remove;
            '3': ListAll;
            '4': Search;
            '5': Update;
            '6': done:=true;
          end;
        until done;
        CloseFile(dbfile);
        CloseIndex(ifile);
     end.

Программа ведения почтового списка и данная программа имеют один базовый скелет. Он может быть модифицирован для различных ситуаций использования баз данных.


TurboSort

Инструментарий баз данных предоставляет функцию  TurboSort, которая является разновидностью QuickSort. Она может быть использована для сортировки любого типа данных, если их длина равна по меньшей мере двум байтам. QuickSort используется, так как это самая быстрая сортировка в большинстве ситуаций, как вы видели в Главе 1.  TusboSort  находится в файле SORT.BOX, который должен быть включен в каждую программу, использующую ее. TurboSort объявляется следующим образом

  function TurboSort(ItemSize: integer): integer; 

ItemSize - это размер данных, которые будут сортироваться; он должен быть вычислен с помощью  SizeOf. Значение, возвращаемое TurboSort, интерпретируется, как показано в таблице 9-3. TurboSort может сортировать до 32767 элементов. Хотя функция будет пытаться сортировать их в оперативной памяти для скорости, она будет использовать временный файл на диске, когда это необходимо.

Таблица 9-3  Коды возврата TurboSort

-------------------------------------------------------------
Значение           Смысл
-------------------------------------------------------------
    0        Сортировка выполнена успешно
    3        В компьютере нет достаточной памяти
    8        Длина элемента меньше 2
    9        Более 32767 входных элементов для сортировки
    10       Ошибка записи на диск
    11       Ошибка чтения с диска
    12       Нет возможности создать временный файл на диске
--------------------------------------------------------------

 

InP, OutP и Less

TusboSort имеет три фазы работы: ввод данных, которые будут сортироваться, сортировка данных и вывод отсортированных данных. Для того, чтобы помочь TurboSort выполнить свою работу, вы должны создать три процедуры, называемые InP, OutP, Less. Данные процедуры декларируются как forward с помощью SORT.BOX, но вы должны создать действительную реализацию.

Процедура InP используется для передачи  TurboSort  данных, которые должны сортироваться, по одному элементу за раз. Действительная передача выполняется, используя SortRelease, которая также определена в SORT.BOX. Она объявляется следующим образом

     procedure SortRelease(item); 

Так как тип параметра item не задан, могут сортироваться данные любого типа. Простая процедура InP, которая считывает десять целых, введенных с клавиатуры, и передает их TurboSort для сортировки, показана ниже:

     procedure InP;
     var
       f: integer;
     begin
       for i:=1 to 10 do ReadLn(data[i]);
       for i:=1 to 10 do SortRelease(data[i]);
     end; {InP}

Процедура OutP  используется для поэлементного чтения отсортированных данных из TurboSort с помощью SortReturn, определенной в SORT.BOX. SortReturn объявляется следующим образом:

     procedure SortReturn(item); 

Так как тип параметра  item не задан, то могут быть возвращены данные любого типа. Процедура OutP может не обладать информацией о том, сколько элементов данных будет возвращаться, поэтому функция SorEOS может быть использована для проверки на конец данных. Следующая простая процедура OutP может быть использована с целыми числами, генерируемыми процедурой InP, показанной ранее:

     procedure OutP;
     var
       data: integer;
     begin
       repeat
         SortReturn(data);
         write(data,' ');
       until SortEOS;
     end; {OutP}

Функция Less является наиболее важной из трех рассматриваемых, так как она вызывается каждый раз, когда сравниваются два элемента. Функция Less возвращает  TRUE, если первый аргумент меньше второго.  Less декларирована в SORT.BOX, как имеющая два параметра, называемые Х и У, которые вы должны перекрыть с двумя логическими переменными, имеющими тот же самый тип, что и сортированные данные. Перекрытие реализуется с  помощью  команды alsolute. Например, данная версия Less может быть использована для сравнения двух целых переменных:

     function Less;
     var
       first: char absolute X;
       second: char absolute Y;
     begin
       Less := first < second;
     end; {Less}

В качестве примера связи этих процедур рассмотрим короткую программу, которая считывает десять целых числе, сортирует их и отображает отсортированный список:

     program simple_sort;

     var
       data: array [1..10] of integer;
       result: integer;

     {$i sort.box} {read in the sort routines}

     procedure InP;
     var
       f: integer;
     begin
       for i:=1 to 10 do ReadLn(dara[i]);
       for i:=1 to 10 do SortRelease(data[i]);
     end; {InP}

     function Less;
     var
       first: char absolute X;
       second: char absolute Y;
     begin
       Less := first < second;
     end; {Less}

     procedure OutP;
     var
       data: integer;
     begin
       repeat
         SortReturn(data);
         write(data, ' ');
       until SortEOS;
     end; {OutP}

     begin
       result:=TurboSort(sizeOf(integer));
       writeLn('sort result; ', result);
     end.


GINST

Программа GINST, предоставляемая инструментарием баз данных, позволяет вам создать программу подготовки терминалов, которая дает возможность пользователям устанавливать свои программы на Турбо Паскале на своих компьютерах. Существует много типов компьютеров, терминалов и прочего; программа GINST позволяет вам создать программы на Турбо Паскале, которые могут быть установлены в различных условиях. Как таковая данная программа не будет рассматриваться. Однако, интересующиеся читатели могут обратиться к руководству пользователя инструментария баз данных.


Глава 10. Графический инструментарий Turbo Pascal

Графический инструментарий Турбо Паскаля является необязательным добавляемым для расширения Турбо Паскаля средством, которое представляет большой и разнообразный набор графических процедур. Эти процедуры делятся на пять основных категорий:
    - базовые графические элементы точки, линии, квадраты, круги;
    - тексты;
    - окна;
    - гистограммы;
    - графики.

Количество различных процедур, функций и опций делает невозможным рассмотрение всего графического инструментария в данной главе (Руководство по графическому инструментарию имеет объем 256 страниц). Однако, будет дан обзор средств каждой категории с примерами.

Примеры, данные в этой главе, отражают графический инструментарий, предназначенный для версии 3 Турбо Паскаля. Графический инструментарий, предназначенный для версии 4 и последующих, значительно отличается от предназначенного для более ранних версий. Наиболее существенное отличие инструментария, предназначенного для версии 4, состоит в том, что процедуры содержаться в блоках, которые связываются с вашей программой, а не включаются в нее с помощью директивы компилятора SI, как в версии 3 инструментария.


Требования к аппаратному обеспечению

Для персонального компьютера IBM PC и совместимых с ним процедуры графического инструментария требуют, чтобы у вас в системе был один из следующих графических аппаратов:
    - CGA (цветной графический адаптер);
    - EGA (расширенный графический адаптер);
    - монохромный графический адаптер Hercules. Вы можете настроить вашу копию графического инструментария в соответствии с инструкцией в руководстве пользователя графического инструментария для конкретного типа графического адаптера вашей системы. Кроме того, для функционирования инструментария в системе необходима память по крайней мере 192 Кбайта.


Системы координат

Все графические процедуры в  инструментарии используют две отдельные координатные системы. Первая называется абсолютной координатной системой, а вторая - планетной координатной системой.

Абсолютная координатная система определяется используемым вами графическим адаптером. Она представляется числом пикселей в горизонтальном и вертикальном направлениях (Пиксель  - это наименьшая адресуемая точка на экране). Например,  CGA в режиме 6 имеет 640 пикселов в ширину и 200 в высоту. Процедуры инструментария используют координатную систему Х-Y, где ось Х представляет горизонтальное направление, а ось Y - вертикальное. По соглашению верхний левый угол экрана имеет координаты (0,0) и в случае контроллера CGA левый нижний угол имеет координаты (639,200). Хотя вы можете использовать абсолютную координатную систему, это делается редко из-за больших преимуществ планетной координатной системы.

Планетная координатная    система   задается   процедурой DefineWold, которая используется для определения начальной и конечной точек координатной системы. Например, строка

     DefineWord(1, 0, 0, 1000, 1000);

определяет координатную систему для планеты номер 1. Она делает 0,0 левым верхним углом и 1000,1000 правым нижним. Когда это сделано все графические процедуры в инструментарии будут настроены на координатную систему. Это позволяет создавать графические программы без учета того, какое графическое оборудование будет использоваться, таким образом давая большую независимость от аппаратуры. Та же самая программа может быть скомпилирована для работы либо с контроллером EGA в режиме 640х350, либо с CGA с разрешением 640х200 без изменений.

Для использования планетной координатной системы требуется трехступенчатый процесс. Во-первых, вы должны осуществить задание параметров планеты с помощью DefineWold. Во-вторых, вы должны выбрать одну из предварительно заданных планет  с  помощью SelectWold. Наконец, вы должны выбрать окно в планете с помощью SelectWindom. Процедура объявляется следующим образом:

    procedure DefineWorld(WorlidNum:integer,StartX,StartY,EndX,EndY: real);
    procedure SelectWorid(WoridNum: integer);
    procedure SelectWindom(WindomNum: integer);

WorldNum - это номер планеты, а  WindomNum - номер окна. StartX и StartY определяют координаты верхнего левого угла, а EndX  и  EndY - правого нижнего угла. Окно, которое вы выбрали, должно иметь тот же номер, что и номер планеты.

Следующий фрагмент кода определяет две планеты и  выбирает первую из них в качестве текущей среды:

    DefineWorld(1, 0, 0, 1000, 1000);
    DefineWorld(2, 0, 0, 2000, 2000);

    SelectWorld(1);
    SelectWorld(2);

После данной последовательности процедуры инструментария будет работать в координатном пространстве 1000х1000.

Следующим преимуществом планетной координатной системы является то, что она позволяет увеличивать масштаб изображения. Это реализуется уменьшением координат планеты при неизменности всех других параметров. Примеры этого будут даны позднее.


Требования по инициализации

Файлы typed.sys, graphix.sys, kernel.sys должны быть включены в любую программу, которая использует графический инструментарий. Порядок включения важен и должен быть следующим:
    {Si typedef.sys}
    {Si graphix.sys}
    {Si kernel.sys}
Прежде, чем можно будет использовать какую-либо процедуру инструментария, должна быть вызвана процедура InitGraphic для инициализации графической системы. После завершения использования графики вы должны вызвать процедуру LeaveGraphic для сброса экрана в принимаемый по умолчанию текстовый режим.


Базовые графические элементы

Графический инструментарий представляет процедуры реализации базовых графических элементов, которые на первый взгляд дублируют аналогичные процедуры, имеющиеся в Турбо Паскале. Однако, это не так. Процедуры инструментария могут работать в планетной координатной системе, тогда как соответствующие процедуры Турбо Паскаля не могут. Процедуры реализации базовых графических процедур описаны в таблице 10-1.

Таблица 10-Процедуры реализации базовых графических элементов

Имя                  Функция

DrawPoint            Нарисовать точку в заданном месте
DrawLine             Нарисовать линию в заданном месте
DrawSguare           Нарисовать квадрат в заданном месте
DrawCircle           Нарисовать круг в заданном месте
DrawCircleSegment    Нарисовать круг в заданном месте
DrawCircleDirect     Нарисовать круг, используя абсолютные
                     координаты
SetAspect            Установить коэффициент взгляда для
                     процедур реализации круга
GetAspect            Возврат к текущему коэффициенту взгляда

Следующая простая программа выбирает планету и окно и рисует круги, квадраты и линию. Результаты показаны на рис.10-1.

    program simple_graphics;

    {Si typedef.sys}
    {Si graphix.sys}
    {Si kernel.sys}

    var
      radius: real;
      i:integer;

    begin
      InitGraphic;
      DefineWorld(1, 0, 0, 1000, 1000);
      SelectWorld(1);
      SelectWindom(1);
      DrawBorder; {установить границу вокруг окна }

      SetAspect(1);

      radius: = 0.05;

      for i: = 1 to 10 do
      begin
       DrawCircle(500,500,radius);
       radius: = radius + 0.2;
      end;
      repeat until KeyPressed;
      ReadLn;
      DrawSguare(100, 100, 900, 900, false);
      DrawSguare(400, 400, 600, 600, false);
      repeat until KeyPressed;
      ReadLn;
      DrawLine(0, 0, 1000, 1000);
      repeat until KeyPressed;
      LeaveGraphic;
    end.

Процедура SetAspect используется для установки коэффициента взгляда, который определяет способ рисования круга. Любое значение, отличное от 1, будет порождать элипс вместо круга. Процедура DrawBorder помещает границу вокруг активного окна.

      --------------------------------------------------¬
      ¦   --------------------------------------------¬ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   ¦                                           ¦ ¦
      ¦   L-------------------------------------------- ¦
      L-------------------------------------------------.

 

Рис.10-1. Круги, квадраты и линии в области 1000х1000

Чтобы понять влияние планетных координат, изменим предложение DefineWold и запустим программу снова. Окончательное отображение будет выглядеть, как показано на рис.10-2. Отметим, что не все круги влезли в область 2000х2000. Когда что-нибудь не влезает в область, процедуры инструментария автоматически "отстригают" его по краю.

    ---------------------------------------------------¬
    ¦  ---------------------¬                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  ¦                    ¦                          ¦
    ¦  L---------------------                          ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    ¦                                                  ¦
    L--------------------------------------------------.

 

Рис.10-2. Круги, квадраты и линия в области 2000х2000


Графические текстовые процедуры

Графический инструментарий представляет два метода отображения текста на экране графического дисплея. Первый метод основывается на  использовании стандартных процедур ввода/вывода  Write  и WriteLn, которые отображают машинно зависимый набор символов. Этот набор определяется аппаратным обеспечением компьютера и содержит типы символов, которые вы обычно используете. Однако, инструментарий также позволяет вам отображать машинно независимые символы, используя процедуры DrawText и DrawTextW, которые отображают буквы переменного размера на графическом экране или в окне. Машинно независимые процедуры наиболее интересны.


Машинно-независимый набор символов

Каждый машинно-независимый символ конструируется, используя матрицу 4х6 пикселов. Например, буква "Е" конструируется, как это показано на рис.10-3. Так как машинно-независимый набор символов создается с помощью графического инструментария, то можно изменять размер букв, используя масштабирующий коэффициент.

           ----------------------------------------
                   ---T--T--T--¬
                   +--+--+--+--+
                   +--+--+--+--+
                   +--+--+--+--+
                   +--+--+--+--+
                   +--+--+--+--+
                   L--+--+--+--
           ---------------------------------------.

 

Рис.10-3. Построение буквы "Е" в матрице 4х6 пикселов

DrawText и DrawTextW

Процедуры DrawText и DrawTextW объявляются следующим образом:

    procedure DrawText(X, Y, Scale: integer, Msg: WrkString);
    procedure DrawTextW(X, Y, Scale: integer, Msg: WrkString); 

Данные процедуры выводят сообщение Msg, начиная с координат X, Y в масштабе, заданном параметром Scale.  WrkString  декларируется инструментарием, как строка максимальной длины. Однако, вы можете использовать любой тип, который пожелаете.

Простая программа, показанная далее, отображает примеры первых шести размеров шрифта.

    program text_graphics;

    {Si typedef.sys}
    {Si graphix.sys}
    {Si kernel.sys}

    var
      i:integer;

    begin
      InitGraphic;
      DefineWorld(1, 0, 0, 1000, 1000);
      SelectWorld(1);
      SelectWindom(1);
      DrawBorder;

      for i: = 1 to 6 do
      begin
       DrawTextW(10, i*140, i, 'Это      тест')
      end;
      repeat until KeyPressed;
      LeaveGraphic;
    end.

Главным преимуществом применения машинно-независимых символов является то, что их размер может быть изменен для удовлетворения специфическим нуждам вашей программы.


Окна

Графический инструментарий позволяет вам создавать и обрабатывать одно или несколько окон. Каждое окно может быть связано со своей отдельной планетной координатной системой. Кроме того, окно может иметь заголовок и быть заключенным в границы. Две основные процедуры используются для работы с  окнами:  DefincWindow  и SelectWindow. Они декларируются следующим образом:

    procedure DefineWindow(WindomNum, X1, Y1, X2, Y2: integer);
    procedure SelectWindow(WindomNum: integer); 

WindoNum - это номер окна. В процедуре DefineWindow параметры X1, Y1 определяют местоположение верхнего левого угла, а  X2,  Y2 правого  нижнего  угла.  Одним необычным аспектом процедуры DefineWindow является измерение координаты Х единицами, равными 8 пикселам. Следовательно, предложение

    Defin_Window(1, 0, 0, 10, 10); 

задает окно, которое имеет 10 пикселов высоты и 80 пикселов ширины (такой подход используется, так как все окна должны быть выравнены в памяти отображения на границу байта).

Для связи планеты с окном необходимо придерживаться следующей последовательности:
    1. выбрать планету;
    2. выбрать окно.

Для отображения сообщения в заголовке вы должны сначала связать заголовок с окном, а затем заголовок в соответствие "включено". Для реализации этого используйте процедуры DefineHeader  и SetHeaderOn, которые декларируют следующим образом:

    procedure DefineHeader(WindowNum: integer, Msg: WrkString);
    procedure SetHeaderOn; 

Вызов DrawBorder помещает границу вокруг активного окна. Данная процедура не имеет параметров.

Следующая программа иллюстрирует правильный порядок вызова различных процедур работы с окном для того, чтобы активизировать окно с границей и заголовком. Ее вывод показан на рис.10-5.

    program One_Window;

    {Si typedef.sys}
    {Si graphix.sys}
    {Si kernel.sys}

    var
      i:integer;

    begin
      InitGraphic;
      DefineWorld(1, 0, 0, 1000, 1000);
      DefineWindow(1, 20, 20, 40, 100);

      DefineHeader(1, 'Заголовок');
      SetHeaderOn;
      SekectWorld(1);
      SekectWindow(1);
      DrawBorder;
      repeat until KeyPressed;
      LeaveGraphic;
    end.
-------------------------------------------------
             -----------------¬
             L----------------
             -----------------¬
             ¦                ¦
             ¦                ¦
             ¦                ¦
             ¦                ¦
             ¦                ¦
             ¦                ¦
             L----------------
    --------------------------------------------------.

Рис.10-5. Простое окно с границей и заголовком


Увеличение масштаба изображения

Созданием окон с различными координатами вы можете добиться эффекта увеличения масштаба графического отображения. Рассмотрим следующее предложение для рисования линии:

    DrawLine(0, 0, 100, 100); 

Если активная планета, в которой рисуется данная линия, задана следующим образом

    DefineWorld(1, 0, 0, 100, 100); 

то линия будет идти диагонально из одного угла в другой. Однако, если планета будет задана, как

    DefineWorld(1, 0, 0, 200, 200); 

то та же самая линия будет проходить половину дистанции из одного угла в другой. Графические объекты будут всегда заполнять экран в пропорции к системе координат планеты. В большей планете объект будет выглядеть меньше, а в меньшей будет выглядеть больше.

Следующая программа создает эффект увеличения масштаба изображения в углу квадрата. Вывод программы показан на рис.10-6.

    ------------------------------------------------------
    --------------¬
    L-------------
    --------------¬
    ¦---¬         ¦
    ¦¦  ¦         +------------¬
    ¦L---         +------------
    ¦
                  +------------¬
    ¦             ¦ ------¬    ¦
    ¦             ¦ ¦     ¦    +------------¬
    L-------------+ ¦     ¦    +------------
                  ¦ L------    +------------¬
                  ¦            ¦  ----------+
                  ¦            ¦  ¦         ¦
                  L------------+  ¦         ¦
                               ¦            ¦
                               ¦  ¦         ¦
                               ¦  ¦         ¦
                               L--+----------

 

Рис.10-6. Эффект увеличения масштаба изображения за счет использования окна

    program Windows;

    {Si typedef.sys}
    {Si graphix.sys}
    {Si kernel.sys}
    {Si windows.sys}

    var
      i: integer;

    procedure SetUpWindow;
    begin

      DefineWindow(1, 0, 0, 20, 100);
      DefineHeader(1, 'Sguare');
      DefineWorld(1, 0, 0, 400, 400);
      SelectWorld(1);
      SelectWindow(1);
      SetHeaderOn;
      SetBackground(0);
      DrawBorder;

      DefineWindow(2, 20, 40, 40, 140);
      DefineHeader(2, 'Zoom in a little');
      DefineWorld(2, 0, 0, 200, 200);
      SelectWorld(2);
      SekectWindow(2);
      SetHeaderOn;
      SetBackground(0);
      DrawBorder;

      DefineWindow(3, 40, 80, 60, 180);
      DefineHeader(3, 'Ah...much clearer');
      DefineWorld(3, 0, 0, 100, 100);
      SekectWorld(3);
      SelectWindow(3);
      SetHeaderOn;
      SetBackground(0);
      DrawBorder;
    end;

    begin
      InitGraphic;
      SetUpWindow;

      {появиться для увеличения в углу}
      for i: = 1 to 3 do
      begin
       SelectWorld(1);
       SelectWindow(1);
       DrawSguare(10, 10, 120, 120, false);
       DrawLine(10, 10, 20, 20);
      end;
      repeat until KeyPressed;
      LeaveGraphic;
    end.


Гистограммы и круговые диаграммы

Графический инструментарий предоставляет процедуры, позволяющие вам нарисовать гистограммы и круговые диаграммы. Существуют две процедуры для реализации круговых диаграмм:  DrawPolarPie и DrawCartPie. Они отличаются способом задания местоположения и радиуса круговой диаграммы: в полярных или декартовых координатах. Легче пользоваться процедурой DrawPolarPie. Она задается следующим образом

    procedure DrawPolarPie(X, Y, Radius, Theta, Inner, Outer:real; Info: PieArray; Num, Option,TextSize: integer);

X,Y - координаты центра,  radius - радиус круговой диаграммы и  Theta  - угол (в градусах) первого сегмента. Параметр Inner и Outer определяют длину линии метки указателя.  Info - это матрица типа PieArray, которая определяется следующим образом:

    type
      PieType = record
       Area: real;
       Text: wrkstring;
      end;
      PieArray = array[1..MaxPiesGlb] of PieType;

Параметр Num должен определять число сегментов, а Option устанавливается в соответствии со следующей таблицей:

Значение Option          Смысл
        0                Нет меток
        1                Только текстовые метки
        2                Метка из текста и значений
        3                Метки только из значений

Наконец, параметр TextSize определяет размер машинно-независимых символов, используемых в метках. Программисты, которые используют процедуру DrawPolarPie, должны также включить следующие файлы в указанном порядке:

    {Si circsegm.hgh}
    {Si pie.hgh}

Для использования DrawPolarPie вы должны сначала загрузить 0 значения сегментов в Info.Area и метки, связанные с этими сегментами в Info.Text. Наконец, вы присваиваете соответствующие значения остальным параметрам и рисуете круговую диаграмму. Процедура, следующая далее, рисует программу, показанную на рис.10-7.

    procedure PlotPie;
    var
      Radius, Theta, InRad, OuterRad: real;
      Mode, Size: integer;
      Products: PieArray;

    begin
      DefineWindow(1, 0, 0, 50, 100);
      DefineHeader(1, 'Exports in Billions');
      DefineWorld(1, 0, 0, 1000, 1000);
      SelectWorld(1);
      SelectWindow(1);
      SetHeaderOn;
      DrawBorder;

      Products[1].Text: = 'Wheat S';
      Products[2].Text: = 'Corn S';
      Products[3].Text: = 'Manu. Goods S';
      Products[4].Text: = 'Electronics S';
      Products[5].Text: = 'Misc. S';
      Products[1].Area: = 15;
      Products[2].Area: = 12.4;
      Products[3].Area: = 7.34;
      Products[4].Area: = -24; {вытолкнуть}
      Products[5].Area: = 16;
      Radius: = 125;
      Theta: = 60;

      SetAspect(1.0);

      InRad: = 0.85;
      DuterRad: = 1.5;

      Mode: = 2; {установить рисование обеих меток}
      Size: = 1; {установить малый размер текста}

      DrawPolarPie(500, 500, Radius, Theta, InRad,
                 OuterRad, Products, 5, Mode, Size);
    end; {PlotPie}

Для того, чтобы нарисовать гистограмму, требуется использовать процедуру DrawHistogram, которая декларируется следующим образом:

    procedure DrawHictogram(Info: PlotArray; Num: integer;
                         Hatch: Boolean; HatchStyle:integer);

Параметр Info содержит значение, связанное с каждой полосой.

    ------------------------------------------------------
    ----------------------------¬
    L---------------------------
    ----------------------------¬
    ¦                           ¦
    ¦                           ¦
    ¦                           ¦
    ¦                           ¦
    ¦                           ¦
    ¦                           ¦
    L---------------------------
                           --------------------¬
                           L-------------------
                           --------------------¬
                           ¦                   ¦
                           ¦                   ¦
                           ¦                   ¦
                           ¦                   ¦
                           ¦                   ¦
                           ¦                   ¦
                           L--------------------

 

Рис.10-7. Круговая диаграмма и гистограмма

Это двумерная матрица, где для любого Info[i,1] зарезервирован для внутреннего использования, а Info[i,2] должен содержать значение полосы для i. Если параметр Hatch равен TRUE, то каждая полоса заштриховывается. Параметр HatchStyle определяет частоту штрихования; значение 1 задает самую частую штриховку. Нельзя рисовать метки непосредственно используя процедуру DrawHistogram.

Вы должны включить следующие файлы в порядке, показанном далее, в любую программу, которая использует DrawHistogram:

     {Si hatch.hgh}
     {Si histogrm.hgh}

Процедура, показанная далее,  иллюстрирует  использование DrawHistogram. Она порождает гистограмму, показанную на рис.10-7.

    procedure PlotBar;
    var
      Products: PlotArray;
    begin
      DefineWindow(2, 40, 110, 70, 180);
      DefineHeader(2, 'Exports in Billions');

      DefineWorld(2, 0, 0, 30, 35);
      SelectWorld(2);
      SelectWindow(2);
      SetHeaderOn;
      SetBackground(0);
      DrawBorder;

      Products[1,2]: = 15;
      Products[2,2]: = 12.4;
      Products[3,2]: = 7.34;
      Products[4,2]: = 24;
      Products[5,2]: = 16;

      DrawHistogram(Products, 5, true, 5);
      DrawTextW(1, 2, 1, 'Wheat   Corn  Manu. Elec. Misc.');
      DrawTextW(1, 7, 1, 'S15     S12.4 S7.34 S24   S16');
    end; {PlotBar}

Отметим, что метки естественным образом включаются с помощью процедуры DrawTextW.

Для вашего удобства гистограмма и круговая диаграмма показаны далее в одной программе:

     program Presentation_graphics;

     {Si typedef.sys}
     {Si graphix.sys}
     {Si kernel.sys}
     {Si circsegm.hgh}
     {Si pie.hgh}
     {Si hatch.hgh}
     {Si histogrm.hgh}

     procedure PlotPie;
     var
       Radius, Theta, InRad, OuterRad: real;
       Mode, Size: integer;
       Products: PieArray;
     begin
       ClearScreen;
       SetColorWhite;
       DefineWindom(1, 0, 0, 50, 100);
       DefineHeader(1, 'Exports in Billions');
       DefineWorld(1, 0, 0, 1000, 1000);
       SelectWorld(1);
       SelectWindom(1);
       SetHeaderOn;
       SetBackground(0);
       DrawBorder;
       Products[1].Text:='Wheat S';
       Products[2].Text:='Corn S';
       Products[3].Text:='Manu. Goods S';
       Products[4].Text:='Electronics S';
       Products[5].Text:='Mics. S';
       Products[1].Area:=15;
       Products[2].Area:=12.4;
       Products[3].Area:=7.34;
       Products[4].Area:=-24; {вытолкнуть}
       Products[5].Area:=16;

       Radius: = 125;
       Theta: = 60;

       SetAspect(1.0);

       InRad: = 0.85;
       OuterRad: = 1.5;

       Mode:=2; {установить рисование обеих меток}
       Size:=1; { установить малый размер текста }

       DrawPolarPie(500, 500, Radius, Thete, InRad,
                  OuterRad,Products, 5,Mode, Size);
     end; {PlotPie}

     procedure PlotBar;
     var
       Products: PlotArray;
     begin
       DefineWindow(2, 40, 110, 70, 180);
       DefineHeader(2, 'Exports in Billions');
       DefineWorld(2, 0, 0, 30, 35); 1
       SelectWorld(2);
       SelectWindow(2);
       SetHeaderOn;
       SetBackground(0);
       DrawBorder;

       Products[1,2]: = 15;
       Products[2,2]: = 12.4;
       Products[3,2]: = 7.34;
       Products[4,2]: = 24;
       Products[5,2]: = 16;

       DrawHistogram(Products, 5, true, 5);

       DrawTextW(1, 2, 1, 'Wheat  Corn  Manu. Elet. Mias.');
       DarwTextW(1, 7, 1,  S15         S12.4 S7.34 S24   S16');
     end; {PlotBar}

     begin
       InitGraphic;
       PlotPie;
       PlotBar;
       repeat untei KeyPressed;
       LeaveGraphic;
     end.


Рисование графиков

Графический инструментарий содержит прекрасный набор процедур рисования графиков и сглаживания. Далее будут рассмотрены две процедуры DrawPoly и Spline.

Процедура DrawPoly  используется для рисования на экране любой произвольной кривой по ее конечным точкам. Она объявляется следующим образом:

     procedure DrawPole(Info: PlotArray; Start, End, Code, Scale, Line: integer);

Info - это матрица рисования, которая содержит координаты каждой рисуемой точки.  Start и End - это матрица индексов первой и последней точки. Параметр Code определяет символ, который будет использоваться для рисования точки. Он имеет следующие значения:

Код            Смысл
     0         Используется линия между точками
     1           +
     2          х

Так как точки генерируются с помощью генератора случайных чисел  Random, каждый прогон процедуры порождает особую кривую.


Глава 11. Эффективность, мобильность и отладка

Способность писать программы, которые эффективно используют системные ресурсы, переносимы на другие компьютеры и свободны от ошибок - это признак профессионального программиста. В  данной главе рассматриваются некоторые методы, с помощью которых достигается эффективность, мобильность и отладка программ.


Эффективность

В применении к программам термин эффективность относится либо к использованию ресурсов системы, либо скорости выполнения, либо к тому и другому. К ресурсам системы относится оперативная память, дисковое пространство, устройства, то есть то, что может выделяться и использоваться. Суждение о том, является программа эффективной или нет, субъективно, оно зависит от ситуации. Рассмотрим программу, которая при выполнении использует 147 Кбайт оперативной памяти, 2 Мбайта дискового пространства и затрачивает в среднем 70 минут. Если это короткая программа, выполняющаяся на персональном компьютере Apple 2, то по всей видимости не очень эффективна. Однако, если это программа, выполняющаяся на суперкомпьютере Cray, то вероятно, она эффективна.

С другой стороны, когда вы добиваетесь эффективности по какому-либо одному параметру, часто при этом ухудшаются другие показатели. Например, достижение более быстрого выполнения программы ведет к ее увеличению, если вы используете линейную программу вместо вызовов функций для увеличения скорости. Кроме того, достижение более эффективного использования дискового пространства за счет упаковки данных замедляет доступ к диску.

В свете этих проблем вы можете поставить вопрос. а как эффективность вообще может обсуждаться. В действительности существует несколько методик программирования, которые всегда дают эффективные программы или по крайней мере более эффективные, чем в других случаях. Существует также ряд методов, которые делают программу как быстрее, так и меньше.


Предотвращение дублировния кода

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

    Read(a);
    Read(y);
    if a<10 then WriteLn('Недопустимый ввод');
    if Length(y)=0 then WriteLn('Недопустимый ввод');

В данном случае предложение  WriteLn('Недопустимый ввод') встречается дважды. Однако, это не необходимо, так как фрагмент может быть переписан следующим образом

    Read(a);
    Read(y);
    if (a<10 or (Length(y)=0) then WriteLn('Недопустимый ввод');

В таком варианте код не только короче, но и будет в действительности выполняться быстрее, так как выполняется только одно предложение if/then вместо двух.

Данный пример возможно не встретится в реальной программе, так как избыточные предложения являются соседними и их легко обнаружить. Однако, так как избыточные предложения часто разнесены в программе, то такие коды встречаются в большинстве программ.

Избыточность иногда является следствием методов, выбранных для кодирования процедур. Например, далее представлены два метода кодирования функции, которая осуществляет поиск заданного слова в матрице строк:

    type
      str80 = string[80];
      StrArray = array [1..100] of str80;

    function StrSearch1(str: StrArray; word: str80): boolean;
    { правильный, неизбыточный код }
    var
      t: integer;
    begin
      StrSearch1 := FALSE;
      for t := 1 to 100 do
       if str[t]=word then StrSearch1 := TRUE;
    end;

    Function StrSeach2(str: StrArray; word: str80): boolean;
    {неправильный, избыточный код }
    var
      t: integer;
    begin
      t :=1;
      StrSearch := FALSE;
      if str[t]=word then StrSearch2 := TRUE
      else
      begin
      t := 2;
      while(t<=100) do
      begin
       if str[t]=word then StrSearch2 := TRUE;
       t := t+1;
      end;
     end;
    end;

При втором методе не только дублируются предложения сравнения if/then, но также имеются два предложения присваивания (t:=1 и t:=2). Первая версия работает быстрее и  требует значительно меньше памяти.

Коротко говоря, избыточный код может быть следствием либо неряшливости при программировании, либо неверного выбора метода реализации процедуры.


Использование процедур и функций

Всегда помните, что использование процедур и функций с локальными переменными составляет основу структурного программирования. Процедуры и функции являются строительными блоками программ на Турбо Паскале и составляют самое главное положительное качество данного языка. Вам следует знать несколько особенностей функций Турбо Паскаля, которые влияют на размер и скорость выполнения вашего кода.

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

Чтобы понять, как вызов функции может замедлить вашу программу, рассмотрим два примера:

   Версия 1                     Версия 2
    for x:=1 to 100 do                for x:=1 to 100
      t:=compute(x);                  t:=Abs(Sin(q)/100/3.1416);

    function compute(q: integer): real;
    var
      t:real;
    begin
      compute:=Abs(Sin(q)/100/3.1416);
    end;

Хотя каждый цикл выполняет одну и ту же функцию, версия 2 гораздо быстрее, так как использование непосредственного кода устраняет задержки, связанные с последовательностями вызова и возврата. Для понимания того, сколько времени тратится, рассмотрим следующий код на псевдоассемблере, который демонстрирует в теории последовательности вызова и возврата для функции compute:

    ; последовательность вызова
    move A, x ; поместить значение х в аккумулятор
    push A
    call compute ; инструкция вызова помещает адрес
               ; возврата в стек

    ; последовательность возврата
    ; значение      возврата функции должно быть помещено в
    ; регистр - мы используем В
    move B, stack-1 ; доставить значение во временное t
    return ; возврат к вызвавшей процедуре
    ; вызвавшая процедура затем выполняет следующие действия

Использование функции compute внутри цикла ведет к тому, что последовательности вызова и возврата будут выполнены 100 раз. Если вы хотите написать быстрый код, то использование данной функции внутри цикла - это не самый лучший подход.

Теперь вы можете подумать, что следует писать программы, которые состоят из нескольких больших процедур и которые, следовательно, будут работать быстрее. В большинстве случаев, однако, небольшие различия во времени выполнения не важны, а вот потеря структуры будет ощутимой. Но это другая проблема. Замена вызовов подпрограмм, которые используются различными процедурами, на непосредстевнные коды сделает вашу программу очень большой, так как один и  тот же код будет дублироваться большее количество раз. Помните, что подпрограммы были выдуманы главным образом как средство повышения эффективности использования памяти. Положение таково, что убыстрение программы ведет к ее увеличению, а уменьшение программы ведет к ее замедлению. Использовать непосредственный код вместо вызовов функций следует только тогда, когда скорость имеет абсолютный приоритет. В противном случае рекомендуется повсеместное применение процедур и функций.


Предложение Case против цепочки if/then/else

   Следующие фрагменты кодов функционально эквивалентны. Однако, один является более эффективным, чем другой. Можете ли вы сказать какой?

    case ch of                  if ch='a' then f1(ch)
      'a': f1(ch);          else if ch='b' then f2(ch)
      'b': f2(ch);          else if ch='c' then f3(ch)
      'c': f3(ch);          else if ch='d' then f4(ch)
      'd': f4(ch);
    end;

Левый фрагмент кода гораздо более эффективен, чем первый, так как в общем случае предложение case порождает более компактный и  быстрый  объектный  код,  нежели  серия  предложений if/then/else.

Цепочка if/then/else важна, так как она позволяет вам выполнить переходы по множеству ветвей с анализом данных различных типов, что не может быть сделано с помощью предложения case. Однако, если вы используете скалярные данные целые, действтительные числа, символьные данные и перечисления, то следует применять предложение case.


Мобильность программ

Довольно частое явление - это перенос программы, написанной на одной машине, на другую, которая отличается процессором, операционной системой или тем и другим. Данный процесс может быть и очень простым и крайне трудным в зависимости от того, как была написана программа. Программа мобильна, если она может быть легко перенесена. Программа переносится трудно, если она содержит много машинно-зависимых вещей: фрагментов кодов, которые работают только со специфической операционной системой или процессором. Турбо Паскаль допускает перенесение кода между всеми его версиями,но все *таки требует внимания к деталям и часто при этом теряется эффективность из-за отличий в операционных системах.

Перенесение кода, написанного с использованием какого-либо компилятора Паскаля, в Турбо Паскаль может вызвать проблемы из-за применений различных наборов расширений. Обратная задача также проблематична: если были использованы расширения Турбо Паскаля, написанный код должен быть модифицирован при использовании другого компилятора.

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


Применение констант

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

Например, далее приводятся две декларации матриц и две процедуры, которые обращаются к ним. В первом варианте размерности матриц являются жестко заданными, а во втором _ помещаются в декларацию const.

    {первая версия}
    var
      count: array[1..100] of integer;

    procedure f1;
    var

      t: integer;
    begin
      for t := 1 to 100 do count[t] := t;
    end;

    {вторая версия}
    const
      MAX = 100;
    var
      count: array[1..MAX] of integer;

    procedure f2;
    var
      t: integer;
    begin
      for t := 1 to MAX do coun[t] := t;
    end;

Вторая версия лучше в том случае, если вы хотите переносить данную программу на машину, которая, например, допускает больший размер матрицы. В данном случае необходимо изменить только МАХ и все ссылки на МАХ будут автоматически скорректированы. Данную версию не только легко модифицировать, но она также позволяет избежать большого количества ошибок редактирования. Помните, что в реальной программе будет возможно много ссылок на МАХ, поэтому выигрыш в мобильности часто вполне весомый.


Зависимость от операционной системы

Практически все коммерческие программы имеют коды, которые являются специфическими по отношению к операционной системе. Например, программа может использовать экранную память персонального компьютера  IBM PC для быстрого переключения экрана или специальные графические команды, применимые в данной операционной системе. Некоторые особенности, связанные с операционной системой, необходимы для быстрых коммерческих программ. Однако, нет причин делать код более зависимым, чем это необходимо.

Когда вы должны использовать системные вызовы для доступа к операционной системе, лучше сделать это в одной главной процедуре так, чтобы только ее надо было изменять при переводе в  другую операционную систему, оставив все остальные без изменения. Например, если системный вывод необходим для очистки экрана и помещения курсора в точку с координатами X,Y, то вы могли бы создать главную процедуру, аналогичную OpSysCall, показанной далее:

    {интерфейс с операционной системой}

    procedure OpSysCall(op, x, y: integer);
    begin
      case op if
      1: ClearScreen;
      2: ClearEOL;
      3: CotoXY(x,y);
    end;

Хотя эти вызовы стандартны для всех версий Турбо Паскаля, если вы будете переносить программу на другой компилятор Паскаля, то вы поймете ценность такого метода взаимодействия. Должен быть изменен только код, который формирует действительные функции, зависимые от операционной системы, оставляя нетронутым общий интерфейс.


Расширение Турбо Паскаля

Если вы осуществляете перенос из Паскаля в  Турбо Паскаль, расширения и улучшения Турбо Паскаля могут сделать работу легче и даже повысить эффективность программы. Например,  расширение string значительно облегчает работу со строками.

Однако, если вы переносите код из Турбо Паскаля в  другой Паскаль, то вы почти наверняка должны удалить все расширения и улучшения из вашей программы. Наихудшей задачей будет замена всех переменных типа string на матрицы символов. Но это только первый шаг, вы также потеряете все процедуры, которые поддерживают строки, такие как Copy, Concat и Pos. Если ваша программа использует такие процедуры, то в новой версии вы должны создать их сами.

В общем случае, если вы до написания программы знаете, что будете переносить ее в другой Паскаль, то следует избегать применения расширений и улучшений Турбо Паскаля.


Отладка

Перефразируя Томаса Эдисона можно сказать, что программирование - это на 10% вдохновение и на 90% отладка. Хорошие программисты являются хорошими отладчиками. Если вы и  имеете большой опыт отладки, вам надо следить за различными типами мелочей, которые имеют место при использовании Турбо Паскаля.


Проблема указателей

Наиболее часто ошибки в Турбо Паскале связаны с неправильным употреблением указателей. Проблемы указателей делятся на две основные категории: недоразумения с действиями над указателями и случайное применение недопустимых указателей. Для разрешения проблем первого типа вы должны разобраться с указателями в языке Паскаль; для разрешения проблем второго типа вы должны всегда проверять допустимость указателей перед их применением.

Следующая программа иллюстрирует типичную ошибку с указателем, которую допустил программист:

    program WRONL; {данная программа имеет ошибку}

    type
      pntr = ^obiect;

      obiect = record
       x: char;
       y: integer;
       name:string[80];
      end;

    var
      p: pntr;

    begin
      p^.name := 'tom';
      p^.x := 'g';
      p^.y := 100;
    end.

Данная программа может аварийно завершиться, так как указатель р не принимает значение, используя New. Указатель р содержит неизвестное произвольное число, которое может указывать куда угодно в памяти компьютера. Это совсем не то, что вы хотели. Для исправления этой программы вы должны добавить строку

    New(p);

до первого использования р.

"Дикий" указатель крайне трудно выследить. Если вы осуществляете присваивание указателю значение переменной, которая не содержит недопустимый адрес, ваша программа некоторое время может работать правильно, а крах наступит после. Статистически, чем меньше ваша программа, тем больше вероятность того, что она будет работать правильно даже со сбившимся указателем, так как используется меньше памяти. По мере роста вашей програмы ошибка становится более вероятной.

Во-вторых, наиболее коварные проблемы могут возникнуть, когда используются указатели: вы можете выйти за пределы памяти в течение вызова New во время выполнения. Это вызывает ошибку во время выполнения и оно прекращается. К счастью в Турбо Паскале есть специальная встроенная функция MemAvail так, что вы можете избежать этих проблем.  MemAvail возвращает число свободных байт слева от неупорядоченного массива. В версиях, предшествующих 4.0, MemAvail  возвращает число параграфов свободной памяти. Следовательно, чтобы сделать программу полностью правильной, проверяйте наличие свободной памяти перед ее выполнением. Чтобы сделать это, вы должны знать число байт, необходимое для каждого типа данных, которые вы размещаете. Однако, это число может изменяться в зависимости от процессора и операционной системы, поэтому используйте функцию  SizeOf, которая возвращает число байт, требующихся для переменной. Измененная программа выглядит следующим образом:

     program RIOHL; {это верная программа}

     type
       pntr = ^object;

       object = record
        x: char;
        y: integer;
        name:string[80];
       end;

     var
       p: pntr;

     begin
       if MaxAvail>=SizeOf(object) then
       begin  {свободная память}
        New(p);
        p^.name := 'tom';
        p^.x := 'g';
        p^y := 100;

       end;
     end.

Одним из указаний на проблему с указателем является то, что ошибка ведет к неустойчивости в поведении. Ваша программа может работать один раз правильно, а другой раз неправильно. Иногда другие переменные будут содержать мусор без видимых причин. Если такое происходит, проверьте ваши указатели.

Хотя указатели являются вещью хлопотной, они также являются одним из самых мощных и полезных аспектов языка Паскаль. Приложите усилия заранее, чтобы научиться правильно ими пользоваться.


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

Хотя Турбо Паскаль не допускает переопределения ключевых слов, которые входят в язык, он позволяет вам переопределять слова, которые отсылают к стандартным процедурам и функциям. Программисты иногда думают, что это интересно по многим причинам; однако, это может привести только к неприятностям. Далее дается пример проблемы, вызванной переопределением встроенной процедуры:

     program WRONG; {данная программа не вернa}
     var
       t: integer;
       units_processed: integer;

     procedure WriteLn(t: integer);
     begin
       write(t, ' ', 'байт свободной памяти в неупорядоченном
                   массиве');
     end;

     begin
       {вычислить объем свободной памяти в неупорядоченном
                        массиве}
       WriteLn(NemAvail);
       .
       .
       .
       WriteLn(units_processed);
     end.

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

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

     {проверить окно, оставшееся в глобальной матрице count }
     function MemAvail: boolean;
     var
       t: integer;

     begin
       MemAvail := FALSE;
       for t := 1 to MAX do if count[t]=0 then MemAvail:=TRUE;
     end;

Переопределение MemAvail  хорошо, пока выполняется данный пример. Проблема возникает, если матрица count превращается позднее из глобальной переменной в динамическую переменную, которая выделяется из неупорядоченного массива. В данном случае, если вы пытаетесь использовать MemAvail для просмотра того, осталось ли достаточно свободного пространства, ваша программа выйдет из строя.

Единственный способ избежать таких проблем заключается в том, чтобы никогда не иметь процедур и функций, написанных вами, с именами как у встроенных функций. Если вы не уверены, присоедините ваши инициалы в начало имени, например,  HSMemAvail  вместо MemAvail.


Неожиданные синтаксические ошибки

Иногда вы будете встречаться с синтаксическими ошибками, которые трудно понять или даже распознать, как ошибки. В частности, необнаруживаемая ошибка произойдет, когда вы пытаетесь скомпилировать данный код:

     program Errors; {данная программа не компилируется}

     var
       s: string[80];

     procedure F1(x: string[80]);
     begin
       WriteLn(s);
     end;

     begin
       ReadLn(s);
       F1(s);
     end.

Если вы попытаетесь скомпилировать эту программу, то увидите следующее сообщение об ошибке:
    
Error 89: ")" expected
     /ошибка 89: ожидалась ")"/

После нажатия клавиши ESC, вы обнаружите, что Турбо Паскаль указывает на строку

     procedure F1(x:string[80]);

с курсором в позиции, показанной стрелкой. Это недостаток Турбо Паскаля?  Нет. Турбо Паскаль не может использовать тип string  в вызовах процедур и функций. Вы должны явно декларировать свой тип и применить его. В данном примере вы, во-первых, декларируете тип, называемый str80 с помощью следующего предложения:

     type
       str80 = string[80];

Затем вы используете заново созданный тип str80, как тип параметра для функции F1. Правильная программа выглядит следующим образом:

     program CorrectecError; {данная программа будет
                     скомпилирована}
     type
       str80 = string[80];

     var
       s := str80;

     procedure F1(x: str80);
     begin
       WriteLn(x);
     end;

     begin
       ReadLn(s);
       F1(s);
     end.

Другая, вносящая путаницу, синтаксическая ошибка порождается следующей программой:

     program Error; {данная программа не будет скомпилирована}

     procedure F2;
     var
       t: integer;
     begin
       for t := 1 to 10 do WriteLn('hi there');
     end

     begin
       F2;
     end.

Ошибка здесь состоит в том, что точка с запятой отсутствует после end в процедуре F2. Однако, Турбо Паскаль укажет на ошибку в следующей строке. В данной простой программе легко обнаружить ошибку. Однако, в некоторых ситуациях вам пришлось бы потрудиться, чтобы найти то место, где пропущена точка с запятой.


Ошибки if/then/if/else

Даже очень опытные программисты могут допустить ошибку в конструкции if/then/if/else. Например, уверены ли вы, что следующий код работает правильно

     if count<100 then
       if count>50 then F1
     else F2;

Не шутите с  соответствующим форматированием. Предложение else не ассоциируется с первым if, а только со вторым if. Помните, что else всегда ассоциируется с ближайшим if. В данном примере вместо выполнения F2, когда count больше 100, Турбо Паскаль не делает ничего. Кроме того,  F2 будет выполняться только, если count меньше 100 и - если меньше 50. Вы увидите это когда код правильно отформатирован:

     if count<100 then
       if count>50 then F1
     else F2;

Если вы хотите просто выполнить F2, когда count больше 100, вам надо было бы использовать конструкцию begin/end, как показано далее:

     if count<100 then
     begin
       if count>50 then F1;
     end
     else F2;


Забывание о параметрах var в процедурах и функциях

Иногда в пылу программирования легко забыть, что, если процедура или функция изменяет свои аргументы, они должны быть определены, как параметры типа var. Забывание этого может вызвать причудливые результаты и потребовать часов на отладку. Например, обсудим неправильную программу:

     program Error;{ данная программа не верна }
     var
       t: integer;

     procedure F1(x: integer);

     begin
       Write('Введите значение: ');
       ReadLn(x);
     end;

     begin
       F1(t); {получить значение t}
       writeLn('t  имеет значение: ', t);
     end.

Данная программа не работает, так как значение назначается только локальной переменной х, а затем F1 возвращает управление и t не модифицируется. Чтобы сделать эту программу работающей, вы должны объявить х внутри F1, как параметр var. Это будет означать, что переменная t будет модифицирована. Правильная программа выглядит следующим образом:

     program Fixed; {данная программа верна}
     var
       t: integer;

     procedure F1(var x: integer);
     begin
       Write('Введите значение: ');
       ReadLn(x);

     end;

     begin
       F1(t); {получить значение t}
       writeLn('t имеет значение: ', t);
     end.

Хотя эту простую программу легко исправить, когда такая ошибка произойдет в большой программе, она может оказаться одной из наиболее трудных для нахождения.


Общие соображения по отладке

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

Инкрементное тестирование - это простой процесс, всегда имеющий рабочий код. Как только можно будет запустить кусок вашей программы, вам следует сделать это, проверяя эту секцию полностью. По мере расширения вашей программы продолжайте проверять новые секции. При таком методе вы можете быть уверены, что любая возможная ошибка находится в малой области кода.

Теория инкрементного тестирования основывается в основном на вероятности и областях. Каждый раз, как вы добавляете длину, вы удваиваете области. Следовательно, по мере роста вашей программы образуется и рост областей, в которых вы должны осуществлять поиск ошибки. При отладке вы как программист хотите иметь дело с как можно меньшей областью. С помощью данного метода вы можете отделить область, которая уже проверена от всего кода и, следовательно, уменьшить область, которая может содержать ошибки.