Первое знакомство с объектно-ориентированным программированием

А.А.Семенов , А.Г.Юдина

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

(Из рекламного буклета фирмы Borland)

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

Попробуем разработать на Паскале программу — хранитель экрана (screen saver)с фигурками различной формы, гуляющими по экрану.

Для работы потребуется Турбо Паскаль версии не меньше 6. Никаких особенных требований к технике (от 286-го). Достаточно поставить только turbo.exe (интегрированную среду), turbo.tph (файл помощи) и turbo.tpl (библиотеки system и crt). Все вместе занимает 1,3 Мбайта и умещается на дискету. Необходимый уровень подготовки — умение использовать основные конструкции языка (ветвления и циклы), а также описывать и применять процедуры и функции для решения несложных задач вычислительного характера (включая работу с массивами).

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

Занятие 1

Итак, мы решили познакомиться с ООП на примере графических объектов.

Стандартная VGA-графика Паскаля (640x480 точек, 16 цветов) не очень годится для анимации — в ней маловато цветов. Поэтому попробуем использовать для работы графический режим VGA с шестнадцатеричным номером 13h (320x200 точек, 256 цветов). Доступ к видеопамяти в этом режиме очень простой, а перевести монитор в данный режим можно вызовом 10h прерывания BIOS. И никакого модуля Graph!

Пишем текст:

begin
   {установить видеорежим 13h — всего 2 строчки на встроенном ассемблере}
   asm
     mov ax, 13h {пересылка номера видеорежима в регистр}
     int 10h {вызов прерывания с шестнадцатеричным номером 10h}
   end;
Readln
end.

Запустив программу, мы увидим, что на черном экране нет курсора — это верный признак графического видеорежима. Прежде чем нажать <ENTER>, попробуем печатать на клавиатуре. Не правда ли, экзотический вид у букв, как в старых играх для DOS?

Теперь попытаемся зажечь на экране какой-нибудь пиксель. Для этого достаточно вывести в оперативную память видеокарты (она начинается с адреса $A000:0000 байт, значение которого равно номеру цвета в палитре (0ЈномерЈ255). Для доступа к памяти используем стандартный массив Паскаля Mem. (Об адресации оперативной памяти см. примечание.)

Итак,

begin
  asm
    mov ax, 13h
    int 10h
  end;
  Mem[$A000:0]:=14; {14 — желтый цвет}
  Readln
end.

Ура! В верхнем левом углу экрана загорелась желтая звездочка!

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

procedure PutPixel(x,y: integer; color: byte); {0ЈxЈ319;0ЈyЈ199;0ЈcolorЈ255}
    var a:word;
    begin
      a:=320*y+x;
      Mem[$A000:a]:=color
    end;
    var
     i,j: integer;
    begin
      asm
        mov ax, 13h
        int 10h
      end;
      for i:=0 to 255 do
         for j:=0 to 10 do
            PutPixel(i,j,i);
            Readln
         end.

Младшие 16 цветов соответствуют 16-цветной палитре режима VGA 640x480. Далее идут оттенки серого и 3 набора цветов различной интенсивности.

Палитра устанавливается 10h прерыванием BIOS во время инициализации видеорежима. Если она нас не устроит, ее нетрудно будет сменить.

Почему в процедуре PutPixel вспомогательная переменная a описана как word, а не как integer?

В нашем графическом режиме экран разбит на 320x200=64 000 точек, а максимальное значение для integer 32 767. Для типа word максимальное значение 65 535.

 

Домашнее задание

Написать следующие процедуры:

FillScreen(color: byte) — заливает экран цветом color;

HLine(x1,x2,y: integer; color: byte) — рисует горизонтальную линию цвета color;

VLine(x,y1,y2: integer; color: byte) — рисует вертикальную линию цвета color.

Примечание. Почему адрес ячейки оперативной памяти (например, $A000:16) записывается так странно?

Восьмиразрядные микропроцессоры (8080, 8085) имели 16-битовую адресную шину и как следствие объем адресуемой памяти 64 Кбайт. Первый 16-разрядный процессор Intel 8086 имел уже 20-битовую шину адреса и мог обращаться к 1 мегабайту оперативной памяти. Для программной совместимости с предшествующими моделями было решено организовать адресацию памяти с помощью двух 16-битовых регистров. Первый из них задает так называемый сегмент памяти, а второй — смещение в пределах данного сегмента. Физический адрес ячейки памяти процессор находит по формуле А=16*адрес сегмента + смещение. Таким образом, к одной ячейке памяти можно обратиться несколькими способами!

Довольно-таки странно, зато в пределах одного сегмента памяти поддерживается совместимость по адресации с 8-разрядными процессорами.

Обычно адрес записывают так: (адрес сегмента) :(смещение). Если адрес представлен в шестнадцатеричном формате, то вначале ставят знак $ (в Паскале это признак шестнадцатеричного числа).

Например, адрес $A000:16 можно записать как $A000:$10, или, если учесть, что 10•163= =40960, как 40960:16. Физический адрес будет равен 40960•16+16=655376.

Занятие 2

Теперь можно перенести все процедуры в самодельную библиотеку (файл сохранить как Graph13h.pas):

Unit Graph13h;
interface
procedure Screen13h;
procedure PutPixel(x,y: integer; color: byte);
procedure FillScreen(color: byte);
procedure FillRect(x1,y1,x2,y2:integer;color:byte);
procedure HLine(x1,y,x2: integer; color: byte);
procedure VLine(x,y1,y2: integer; color: byte);
implementation
procedure Screen13h; assembler;
 
asm
    mov ax, 13h
    int 10h
  end;

procedure PutPixel(x,y: integer; color: byte);
  var a:word;
  begin
    a:=320*y+x;
    mem[$a000:a]:=color
  end;

procedure FillScreen(color: byte);
  var m,n: integer;
  begin
    for m:=0 to 199 do
      for n:=0 to 319 do PutPixel(n,m,color)
  end;

{Заливка прямоугольника}
procedure FillRect(x1,y1,x2,y2:integer;color:byte);
var m,n:integer;
   begin
      for m:=x1 to x2 do
          for n:=y1 to y2 do PutPixel(m,n,color)
   end;

procedure HLine(x1,y,x2: integer; color: byte);
var m,n,k: integer;
   begin
      if x1<x2 then begin n:=x1;k:=x2 end else begin n:=x2;k:=x1 end;
      for m:=n to k do PutPixel(m,y,color)
   end;

procedure VLine(x,y1,y2: integer; color: byte);
var m,n,k: integer;
   begin
     if y1<y2 then begin n:=y1;k:=y2 end else begin n:=y2;k:=y1 end;
     for m:=n to k do PutPixel(x,m,color)
   end;
end.

Откомпилируем файл Graph13h.pas. Будет создан файл библиотеки Graph13h.tpu с объектным кодом процедур.

Программа проверки созданных инструментов существенно упростится:

uses Graph13h;
 
begin
   Screen13h;
   FillScreen(3);
   HLine (0, 150, 50, 2);
   VLine (150, 0, 100, 4);
   readln;
  end.

и можно будет приступить к созданию объектов.

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

Домашнее задание

Дополнить модуль Screen13h процедурой FillRect(x1,y1,x2,y2:word;color:byte), которая заливает прямоугольник с заданными вершинами цветом color.

Дополнительное задание “со звездочкой” — дополнить библиотеку процедурой Line(x1,y1,x2,y2:word;color:byte) для рисования произвольного отрезка, заданного двумя точками.

Продолжение читайте в № 15/99

TopList