Как видно из этой таблицы используются далеко не все коды и воз- можны не все комбинации клавишей. При вводе не предусмотрен- ных комбинаций драйвер их просто игнорирует. Некоторые комби- нации имеют специальное назначение – они не могут восприни- маться программой, но обрабатываются операционной системой. Так, например, Ctrl-S(Ctrl-C)приводит к приостановке работы про- граммы, Ctrl-Breakвообще прекращает ее выполнение, Ctrl-Alt-Delвызывает перезагрузку компьютера, PrintScreen приводит к копиро- ванию содержимого экрана с помощью принтера.
Кроме клавишей смещения на клавиатуре имеются четыре клавиши переключателя: Insert (включение/отключение режима вставки сим- волов), CapsLock (фиксация режима заглавных букв), NumLock (фиксация дополнительных цифровых и служебных клавишей) и ScrollLock (фиксация режима прокрутки экрана). В DOS выделены
234
Лабораторная работа № 21
два байта с адресами 1047 и 1048, в которых хранится информация о состоянии этих переключателей.
Комбинация Ctrl-MumLockприводит к так называемому состоянию захвата: в этом состоянии драйвер непрерывно сканирует клавиату- ру, ожидая нажатие на любую клавишу. В состоянии захвата не может исполняться ни одна программа, кроме процедур обработки прерываний.
Управление клавиатурой. Для управления клавиатурой в Turbo Pascal имеется несколько возможностей разного уровня. Прежде всего это стандартные процедуры Read и Readln. Это процедуры самого верхнего логического уровня – они не только осуществляют взаимодействие с драйвером клавиатуры, но и преобразуют сим- вольные данные во внутренний формат представления соответст- вующих переменных. Однако они обладают тремя существенными недостатками. Во-первых,с их помощью невозможно опознать на- жатие на любые не литерные клавиши (например, на клавиши управления курсором). Во-вторых,ввод символов с помощью этих процедур сопровождается их воспроизведением (эхо–повтором)на экране, что не всегда удобно. В-третьих,обращение к этим проце- дурам приостанавливает выполнение программы до нажатия кла- виши ввода.
Почти все перечисленные недостатки можно преодолеть, если об- ратиться к двум функциям стандартного модуля CRT.
Функция KeyPressed. Возвращает значение типа Boolean, указы- вающая была ли нажата какая–нибудь клавиша после последнего чтения из буфера клавиатуры или нет. Обращение
KeyPressed;
Обращение к этой функции никак не влияет ни на подготовленный клавиатурой код нажатой клавиши, ни на саму функцию KeyPressed: сколько бы раз мы не обращались к ней, она будет воз- вращать True, если была нажата какая–либо клавиша, – до тех пор, пока буфер клавиатуры будет прочитан процедурами Read/Readln или функцией ReadKey. Функция не задерживает работу програм- мы и возвращает False, если ни одна клавиша не была нажата.
Функция ReadKey. Возвращает значение типа Char – код очередной нажатой клавиши. Обращение
ReadKey;
Управление клавиатурой
235
В отличие от KeyPressed эта функция приостанавливает работу программы до тех пор, пока не будет нажата любая клавиша. Заме- чательной особенностью этой функции является то, что с ее помо- щью можно опознать нажатие почти на любую клавишу – исклю- чение составляют только клавиши смещения и клавиши– переключатели. Кроме того, функция вводит символ “вслепую”, т.е. без эхо–повтора на экране.
Приводимый ниже пример иллюстрирует возможность опознавания нажатия на клавиши, генерирующие расширенные коды:
Uses CRT; Var
ExtChar: Boolean;
{флаг – признак расширенного кода}
c: Char;
{вводимый символ}
................
Begin
................
c:=ReadKey;
if c=#0 then ExtChar:=True
else ExtChar:=False;
if ExtChar then c:=ReadKey;
................
Следующая программа читает клавиатуру и выводит на экран коды клавишей, до тех пор, пока не будет нажата комбинация клавиш
Ctrl+2:
Program TestOfKeyboard; Uses CRT;
Var
c1,c2: char; BEGIN
repeat c1:=ReadKey;
if c1 = #0 then c2:=ReadKey;
if c1 = #0 then writeln(ord(c1):5, ord(c2):5) else writeln(ord(c1):5)
until (c1 = #0) and (c2 = #3) END.
Функции KeyPressed и ReadKey относятся к среднему логическому уровню работы с клавиатурой. Как уже отмечалось, с их помощью
невозможно опознать нажатие на клавиши смещения и клавиши переключатели. Это становится возможным при переходе на самый нижний логический уровень, связанный с непосредственным обра-
236
Лабораторная работа № 21
щением к драйверу клавиатуры или самой клавиатуре. Такие обра- щения реализуются с помощью двух прерываний – прерывания с номером 9 – от микропроцессора клавиатуры и с номером 22 – об- ращение к драйверу.
Прерывание с номером 22 содержит следующие функции: AH=0 – читать символ с ожиданием нажатия на клавишу; AH=1 – проверить готовность клавиатуры;
AH=2 – прочитать статус (байт с адресом 1047).
При обращении к функции АН=0 в AL возвращается код нажатой клавиши или 0; если эта клавиша генерирует код из расширенного набора, в АН возвращается код сканирования клавиши. При обра- щении к функции АН=2 в регистре АН возвращается содержимое байта с адресом 1047.
Следующая программа читает и выводит коды сканирования кла- вишей до тех пор, пока не будетнажата клавиша Esc:
Program ScanCodesDemo; Uses DOS;
Var
r: registers; BEGIN
with r do begin
repeat repeat
Flags:=0;
AH:=1;
Intr(22,r);
until Flags<>2022; AH:= 0;
Intr(22,r); writeln(AL:5, AH:5);
until (AL=27) and (AH=1) end
END.
Довольно часто возникает необходимость очистить буфер ввода клавиатуры. Это позволяет сделать следующий прием:
Var
c: Char;
.................
while KeyPressed do c:=ReadKey;
Управление клавиатурой
237
КОНТРОЛЬНЫЕ ЗАДАНИЯ
I. При запуске файла Turbo.exe на экране монитора появляется исходное изображение:
Составить программу рисующую эту заставку и позволяющую выполнять операции по активизации опций основного меню (верхняя строка). Выбор опций производится клавишами “стрелка вправо”, “стрелка влево”, “стрелка вниз”, “стрелка вверх”. При нажатии клавиши “ввод” активизируется соот-
ветствующее подменю: File, Edit, Run, Compile, Options, Debug, Break/watch и
II. Составить программу управления объектом при помощи клавиш управления курсором. При это нажатие клавиши “стрелка вправо”, “стрелка влево”, “стрелка вверх”, “стрелка вниз” изменяет координаты объекта на dX и dY соответственно, а попарное нажатие этих клавиш (например, “стрелка влево” + “стрелка вверх”) проводит к одновремен- ному изменению координат X и Y. Значение текущих координат объекта индицируется в левом верхнем углу экрана. Для вывода значений коор- динат использовать процедуры write/writeln и OutTextXY. В качестве объ- екта использовать точку, оставляющую след на экране при перемеще- нии. Предусмотреть возможность отключения “следа”.
238
Лабораторная работа № 22
ОСНОВНЫЕ ПОНЯТИЯ ОБЪЕКТНООРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ
В Турбо Паскале, начиная с версии 5.0, наряду со стандартными ти- пами данных, введен процедурный тип. Он позволяет рассматри- вать обычные процедуры и функции как новую разновидность пе- ременных.
Описание этого типа производится по принятым в Турбо Паскале правилам, например:
Из этого примера видно, что при объявлении процедурного типа используются зарезервированные слова Procedure и Function, после которых производится описание всех параметров, а для функции дополнительно указывается тип результата.
После того, как описан процедурный тип данных, становится воз- можным использование в программе и переменных данного типа. Их объявление производится обычным способом:
var
a, b: NewProc; c, d: NewFunc;
Если в программе встречается переменная процедурного типа, то
происходит выполнение соответствующей процедуры или функции с заданными после идентификатора переменной параметрами. Про- цедурный тип совместим со стандартным типом Pointer, и проце- дурная переменная после установки ее значения содержит указа- тель на выполнимый код процедуры или функции.
Для корректного выполнения присваивания значений переменных процедурного типа программа, содержащая такие переменные,
должна компилироваться с использованием дальней модели памя-
ти, то есть с установленной опцией OPTION/COMPILLER/FORCE FAR (директива компилятора {$F+}). Кроме того, в объявлении про-
Основные понятия объектно-ориентированного программирования
239
цедурного типа не могут быть использованы процедуры и функции, зарезервированные в любом из стандартных модулейТурбо Паскаля и процедуры типа Inline. После выполнения операции присваивания
имя переменной становится синонимом имени соответствующей процедуры или функции.
Следующий простой пример иллюстрирует использование данных процедурного типа:
Program DataProc; Type
Sum= Function(X,Y: integer): integer; var
S: Sum; I,J: integer;
{$F+}
Function Summa(X,Y: integer): integer; Begin
Summa:=X+Y;
End;
{$F-}
BEGIN
Write('Введите первое слагаемое '); Readln(I); Write('Введите второе слагаемое '); Readln(J); S:=Summa;
Writeln('Сумма чисел ',I,' и ',J,' равна ', S(I,J)); END.
Следует отметить, что в Турбо Паскале в передаваемой процедур- ной переменной могут быть использованы как параметры-значения,так и параметры-переменные.
Следующим шагом в развитии типов данных является появление в
Турбо Паскале (начиная с версии 5.5) объектного типа данных, в
котором происходит объединение данных и операций над ними. Объектный тип составляет основу объектно-ориентированного программирования (ООП).
Описание этого типа происходит в разделе Type с использованием зарезервированных слов Object и End. Данные объектного типа оп- ределяются значениями своих полей (как и записи – данные типа
240
Лабораторная работа № 22
Record) и процедурами и функциями, определяющими правила об- работки полей.
В этом примере тип данных StudentType определен как объект. Он содержит три поля Famaly, Name и Grup, принадлежащие к типу string. Процедуры Init и Show устанавливают правила или методы работы с полями объекта. Естественно, что они должны быть опи- саны в вашей программе. Тип данных DataType описан как объект, порожденный объектом StudentType или, как говорят, объект DataType является потомком объекта StudentType. В описании ти- пов этому соответствует слово object и идентификатор объекта- родителя. В Турбо-Паскале принято соглашение, согласно которо- му объект-потомок наследует все поля и методы объекта-родителя.Поэтому объект типа DataType, кроме своих собственных полей DataMath, DataPhis и DataInf будет содержать и наследуемые поля
Famaly, Name и Grup.
Непосредственное определение объекта (экземпляра объекта) про- исходит в разделе описания переменных, например:
var
Std1, Std2: StudentType; Dt1, Dt2: DataType;
В результате такого объявления в памяти будет выделено соответ- ствующее место для размещения объектов Std1 и Std2 типа
Основные понятия объектно-ориентированного программирования
241
StudentType и объектов Dt1 и Dt2 типа DataType, после чего стано- вится возможным обращение к полям и методам этих объектов. Как и в случае записей, для этих целей можно использовать оператор With или непосредственное обращение к нужному полю:
With Std1 do begin Famaly:=‘Иванов’; Name:=‘Сергей’; Grup:=‘542’;
Еnd; Dt1.DataPhis:=4; Dt1.DataMath:=4;
Вместе с тем, как будет видно из дальнейшего знакомства с мето- дами ООП, прямое обращение является не лучшим способом ини- циализации полей объекта. Более корректно для этих целей исполь- зовать специальные правила – каковыми и являются методы Init, включенные в описания типов StudentType и DataType.
Описанием типа, разумеется, не заканчивается формирование объ- екта – необходимонаписать еще коды процедур и функций (то есть методов), включенных в описание типа объекта. Лишь после этого объект сможет выступать как единое целое, в неразрывной связи своих свойств и поведения. Такое объединение кода и данных при создании типа объекта носит название инкапсуляции.
Заголовки методов при их описании должны соответствовать тем, которые указаны в разделе Type. Так как сразу несколько типов объектов могут иметь правила с одинаковыми именами, то перед
заголовком метода указывается через точку наименование типа объекта. Так, например, описание методов Init и Show для объектов типа StudentType может выглядеть следующим образом:
Procedure StudentType.Init(Fm,Nm,Gr: Str); Begin
Famaly:=Fm;
Name:=Nm;
Grup:=Gr;
End;
Procedure StudentType.Show;
Begin
Writeln;
Writeln('ФАМИЛИЯ: ',Famaly);
Writeln('ИМЯ: ',Name);
242
Лабораторная работа № 22
Writeln('ГРУППА: ',Grup); End;
Первая процедура предназначена для инициализации полей объек- та, вторая – для вывода их значений на экран. Объединение через точку имен Init и StudentType служит для указания компилятору, что процедура Init является методом, принадлежащим объекту типа StudentType. Обращение к полям объекта внутри процедуры проис- ходит как к обычным переменным, без непосредственного указания на то, какому объекту эти поля принадлежат.
Как указывалось выше, объект DataType является потомкомобъекта StudentType, соответственно последний выступает в роли родителя или предка. Если теперь построить объект-потомок от объекта DataType, то он будет потомком и объекта StudentType, но уже бо- лее далеким. В устанавливаемых отношениях наследования важ- ным является то, что каждый объект может иметь сколько угодно потомков, но лишь одного близкого предка. Используя один роди- тельский тип, можно построить иерархию или дерево порожденных объектов.
В механизме наследования можно условно выделить два основных момента: наследование полей и наследование методов. Объект типа
DataType автоматически унаследовал поля Famaly, Name и Grup. По- этому в его описании нет упоминания об этих полях.
Наследование правил имеет свои особенности. Тип DataType насле- дует от своего предка два метода Init и Show, служащие для инициа- лизации полей и вывода их значений на экран. Однако, у объекта DataType число полей на три больше, поэтому для него придется ввести новые правила:
Procedure DataType.Init(Fm,Nm,Gr: Str; Mt,Ph,Inf: Data); Begin