Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Спадкування




Припустимо, ми створюємо програму, у якій крім об'єкта-точки, фігурує, також об'єкт-коло. Звичайно, можна було б опис цього нового типу створити з нуля, подібно тому, як ми описали тип Dot у попередньому розділі. Однак цей новий тип коло (назвемо його Ring) має багато загального з існуючим типом Dot. Насправді, як і точка, коло характеризується двома координатами: А и В, що визначають положення її центра. Однак для створюваного об'єкта необхідно додати поле, що задає радіус кола.

Що стосується методів, то для нового типу Ring підійшов би метод Move, оскільки переміщення точки і переміщення кола (власне, її центра) по екрану здійснюється однаково. Однак метод, що ініціалізує об'єкт (Init), а також методи, що роблять коло видимим чи невидимим (Show і Hide), прийдеться створити нові.

Отже, ми з'ясували, що новий тип Rіng де в чому повторює тип Dot. А чи не можна новий тип створити на основі вже існуючого, просто додавши в нього нові поля і методи? Виявляється, це можливо завдяки властивості об'єктних типів, відомому як спадкування. Оскільки мова йде про спадкування, відповідно до генеалогічної термінології існуючий тип, що є основою для створення нового об'єкта, називають предком, чи батьківським типом, а створюваний об'єкт:нащадком, чи дочірнім типом. Нащадок автоматично успадковує всі поля і методи свого предка.

Властивість спадкування широко використовується в об’єктно-орієнтованому програмуванні. Завдяки йому на основі існуючого об'єкта можна створити будь-яку кількість нових об'єктів. Ґрунтуючись на цих нових об'єктах, можна створювати ще об'єкти — і т.д., причому довжина ланцюжка спадкування нічим не обмежується. При цьому кожен об'єкт може мати будь-яку кількість нащадків, але тільки єдиного предка.

А тепер повернемося до наших типів Dot і Ring. B описі типу-нащадка повинний вказуватися ім'я батьківського типу (у круглих дужках після зарезервованого слова OBJECT):

type Ring = object (Dot)

Rad: integer; end;

У приведеному описі типу Ring мається поле Rad, яке визначає радіус кола, якого не було в типу Dot. Крім того, тип Ring успадкував усі поля свого предка (два значення типу INTEGER, що визначають положення точки на екрані). Однак тут поки відсутні методи. От як може виглядати опис типу Ring, доповнене відповідними методами:

Ring = object (Dot)

Rad: integer

procedure Init (x, у, r: integer)

procedure Show;

procedure Hide;

end;

procedure Ring.Init; begin

a:=x; b:=y; Rad:=r; end;

procedure Ring.Show; begin

SetColor(White);

Circle(a,b,Rad); end;

procedure Ring.Hide; begin

SetColor(Black);

Circle(a,b,Rad); end;

end;

Необхідно помітити, що методи об'єкта-нащадка заміняють собою (чи перевизначають) для нащадка однойменні методи батьківського об'єкта. Іншими словами, якщо має місце звертання до методу Rіng.Move, система дивиться, чи не заданий метод з таким ім'ям в описі об'єкта Rіng. Якщо так, використовується цей новий метод; якщо ні, використовується метод, успадкований від предка. (При цьому необхідно пам'ятати, що перевизначати можна тільки методи, імена полів в об'єкті-нащадку не повинні збігатися з іменами полів батьківського об'єкта.).

Після того як тип-нащадок оголошен, перш ніж приступити до маніпулювання його полями, необхідно створити екземпляр об'єкта:

var ring1:ring;

Віртуальні методи і поліморфізм

Давайте повернемося до методу Move з попередньої програми. От його опис:

procedure Dot.Move;

begin

Hide;

a:=a+Da; b:=b+Db;

show

end;

Слід звернути увагу, на те, що з середини методу Move мають місце звертання до методів Hide і Show. Зміст цих звертань очевидний: спочатку об'єкт робиться невидимим на екрані (викликається метод Hide), потім переміщається (координати точки на екрані потрібним чином змінюються) і, нарешті, знову робиться видимим (викликається метод Show).

Але що відбудеться, якщо метод Move у даному виді використовувати в програмі з двома об'єктами: Dot і Ring? Згадаємо, що метод Move успадковується об'єктом Ring від об'єкта Dot, а методи Hide і Show, оскільки приховання і показ окружності на екрані здійснюється не так, як точки, в об'єкті Ring перевизначаються. Отож, для точки при цьому ніяких ускладнень не виникає. Точка без проблем "гасне", змінює координати і потім знову "запалюється", оскільки усі викликувані тут методи (спочатку Move, а з нього — Hide і Show) для об'єкта Dot є "рідними".

Що стосується кола, то при виклику методу Ring.Move система намагається знайти метод з таким ім'ям в описі об'єктного типу Ring і, не знайшовши його, продовжує пошук в об'єкті-предку. У результаті має місце звертання до методу Dot.Move. Після цього з методу Move повинні бути викликані (перевизначені) методи Ring.Hide і Ring.Show, однак цього не відбувається з успадкованого методу Dot.Move, екземпляр об'єкта Ringl, замість Ring.Hide і Ring.Show, викликає однойменні методи об'єкта Dot. Це пояснюється тим, що методи Dot.Move, Dot.Hide і Dot.Show жорстко зв'язані, оскільки вони були відкомпільовані в єдиному контексті — об'єктному типі Dot. Іншими словами, зв'язок між цими методами, що був встановлена при компіляції, має статичний характер. У цьому випадку виявиться переміщується також точка, а не коло. (Правда, текст відповідної програми тут не приводиться, однак читач може самостійно перевірити, що це саме так — для цього досить потрібним чином перетворити текст попередньої програми. При натисканні потрібних клавіш, що повинні б переміщати коло,, на екрані з'являється друга точка, що і переміщається замість кола.)

Як же зробити так, щоб методи Hide і Show викликалися в залежності від того, екземпляр якого об'єкта ініціював звертання до методу Move? Тут на допомогу приходить механізм, відомий як динамічне (чи пізнє) зв'язування— на відміну від статичного (чи раннього) зв'язування. Зазначений механізм реалізується за допомогою віртуальних методів.

Заголовок віртуального методу в описі об'єктного типу доповнюється зарезервованим словом VIRTUAL. Якщо в нащадках цього об'єктного типу маються перевизначальні методи (тобто методи з тим же ім'ям), вони також повинні бути оголошені як віртуальні і при цьому мати той же набір формальних параметрів, що і метод об'єкта-предка. От як виглядають опису об'єктних типів Dot і Rlng з віртуальними методами:

Dot=object

a, b:integer;

constructor Init (x,y:integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer); end;

Ring = object (Dot)

Rad: integer;

constructor Init (x, y, r: integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

(Тут CONSTRUCTOR— це зарезервоване слово, що позначає особливий вид процедури. Виклик віртуального методу повинний бути випереджений звертанням до такої процедури.)

Тепер повернемося до виклику екземпляром об'єкта Ring методу Move, a з нього — методів Hide і Show. Тут спочатку система звертається до методу Dot.Move (попередньо метод з таким ім'ям шукається в описі об'єкта Ring і, оскільки його тут ннем, має місце звертання до відповідного методу об'єкта-предка). Після цього для переміщення кола спочатку потрібно зробити його невидимим (викликати метод Ring.Hide), потім потрібним чином змінити координати центра і, нарешті, відобразити коло у новому місці на екрані (викликати метод Ring.Show). А оскільки при виконанні програми Turbo Pascal забезпечує звертання саме до того віртуального методу, що визначений для викликаючого об'єкта, з методу Dot.Move мають місце звертання до методів Ring.Hide і Ring.Show.

На закінчення приведемо повний текст програми, що відображає на екрані крапку і коло, які можна переміщати, натискаючи відповідні клавіші.

program ObjDotCirc;

uses crt, graph;

type

Dot=object

a, b:integer;

constructor Init (x,y:integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer);

end;

Ring = object (Dot)

Rad: integer;

constructor Init (x, y, r: integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

constructor Dot.Init; begin

a:=x; b:=y;

end;

procedure Dot.Show;

begin

PutPixel(a,b,White);

end;

procedure Dot.Hide;

begin

PutPixel(a,b,Black);

end;

procedure Dot.Move;

begin

Hide;

a:=a+Da; b:=b+Db;

Show

end;

constructor Ring.Init;

begin

a:=x;

b:=y;

Rad:=r;

end;

procedure Ring.Show;

begin

SetColor(White);

Circle(a,b,Rad);

end;

procedure Ring.Hide;

begin

SetColor(Black);

Circle(a,b,Rad);

end;

var i,j,k,Err:integer;

a:char;

Dotl:Dot; Ringl:Ring;

begin {тіло програми}

i:=detect; initgraph(i,j,");

Err:=GraphResult;

If Err <> grOK then WriteLn (GraphErrorMsg (Err))

else

begin

Dotl.Init(GetMaxX div 2, GetMaxY div 2);

Dotl.Show;

Ringl.Init(GetMaxX div 2, GetMaxY div 2, GetMaxY div 6);

Ringl.Show;

while KeyPressed do

a:=ReadKey;

repeat

begin

a:=ReadKey;

case ord(a) of

72:Dotl.Move(0,-5);

80:Dotl.Move(0,5);

77:Dotl.Move(5,0);

75:Dotl.Move(-5,0);

73:Ringl.Move(0,-5);

81:Ringl.Move(0,5);

79:Ringl.Move(5,0); 71:Ringl.Move(-5,0);

end;

end;

until a = chr(27);

end;

end.

Дана програма відрізняється від попередньої програми, тільки тим, що тут додатково оголошено тип-нащадка Ring. Крім того, методи Show і Hide обох об'єктів оголошені як віртуальні, а методи Init перетворені в конструктори

Також оператор CASE у новій програмі доповнений наступними варіантами:

73:Ringl.Move(0,-5);

81:Ringl.Move(0,5);

79:Ringl.Move(5,0);

71:Ringl.Move(-5,0);

Представлені варіанти забезпечують переміщення кола по екрану в чотирьох напрямках. Переміщення має місце при натисканнях клавіш <PgUp> (нагору), <PgDn> (униз), <End> (вправо) і <Home> (уліво), що генерують скэн-коды відповідно 73, 81, 79і 71.

У цій програмі особливу увагу варто звернути на виклики методу Move у тілі оператора CASE. У перших чотирьох випадках має місце звертання до методу Dot.Move, а в інших випадках — до методу Ring.Move. Однак ми знаємо, що метод Move— єдиний. Він оголошений в об'єкті Dot і успадкований об'єктом Rіng. Іншими словами, той самий метод Move працює по-різному (переміщає точку чи коло), у залежності від того, який об'єкт його викликає. Така властивість об'єктів називається поліморфізмом.

Конструктори, динамічні об'єкти і деструктори

Якщо в об'єктному типі є хоча б один віртуальний метод, у ньому повинний бути і спеціальний метод, відомий як конструктор, який обов’язково повинний бути застосований до екземпляра об'єкта до першого звертання до віртуального методу. У силу цього конструктор звичайно являє собою метод, що задає для об'єкта деякі початкові значення (тобто виконуючий його ініціалізацію). Конструктор може бути визначений або в даному об'єкті, або успадкований від об'єкта-предка. При цьому сам конструктор віртуальним методом бути не може.

В описі об'єктного типу заголовок конструктора відрізняється від заголовка звичайного методу тільки тим, що в ньому зарезервоване слово PROCEDURE замінене на CONSTRUCTOR. Чим реально конструктор відрізняється від звичайного методу? Конструктор, крім описаних у ньому дій, установлює зв'язок між об'єктом і спеціальною таблицею віртуальних методів, що містить адреси кодів, які реалізують віртуальні методи.

Як уже відзначалося в цій главі вище, екземпляри об'єктних типів можуть визначатися як статичні (у розділі описів змінних) і як динамічні, причому останнє має місце найчастіше. От як можна створити динамічні екземпляри об'єктів Dotl і Rlngl:

var

Dotl: ^Dot

Ringl: ^Ring; begin

New(Dotl, lnit);

New(Ringl, Init);

Вище згадувалося, що якщо в об'єкті маються віртуальні методи, перед звертанням до них екземпляр даного об'єкта повинний викликати конструктор. Саме таким конструктором тут є Init, що викликається процедурою New (як свій другий параметр, використовуючи при цьому розширений синтаксис). (Приклад програми, у якій екземпляри об'єктів Dotl і Ringl оголошені в якості динамічних, мається наприкінці даного розділу.)

Після завершення роботи з динамічним об'єктом виділену для нього пам'ять варто звільнити. Це виконується за допомогою стандартної процедури DISPOSE:

Dispose (Dotl, Done);

Dispose (Ringl, Done);

Тут процедура DISPOSE звертається до деструктора DONE, що зазначений у якості її другого параметра. В описі об'єктного типу заголовок деструктора відрізняється від заголовка звичайного методу тільки тим, що в ньому зарезервоване слово PROCEDURE замінене на DESTRUCTOR. Однак, на відміну від конструктора, зарезервоване слово DESTRUCTOR є синонімом PROCEDURE. Іншими словами, метод, що завершує роботу з об'єктом, оформляється спеціальним зарезервованим словом тільки для того, щоб дотримати стилістичної симетрії (якщо є спеціальний починаючий метод, логіка диктує, що повинено бути і відповідний завершальний метод). Крім того, на відміну від конструкторів, деструктори можуть являти собою віртуальні методи. При цьому необхідно розуміти що виклик деструктора Done не як параметр процедури Dispose, а самого по собі, динамічну пам'ять не звільнить.На закінчення приведемо текст програми, аналогічної попередній, у якій об'єкти Dotl і Ringl оголошені в якості динамічних.

program ObjDotCircl;

uses crt, graph;

type Dot=object

a, b:integer;

constructor Init (x,y:integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer);

destructor Done;

end;

Ring = object (Dot);

Rad: integer;

constructor Init (x, y, z: integer);

procedure Show; virtual;

procedure Hide; virtual;

destructor Done;

end;

constructor Dot.Init;

begin

a:=x;

b:=y;

end;

procedure Dot.Show;

begin

PutPixel(a,b,White);

end;

procedure Dot.Hide;

begin

PutPixel(a,b,0);

end;

procedure Dot.Move;

begin

Hide;

a:=a+Da; b:=b+Db;

Show

end;

constructor Ring.Init;

begin

a:=x;

b:=y;

Rad:=z;

end;

procedure Ring.Show;

begin

SetColor(Black);

Circle(a,b,Rad);

end;

procedure Ring.Hide;

begin

SetColor(0);

Circle{a,b,Rad);

end;

destructor Dot.Done;

begin

Hide;

end;

destructor Ring.Done;

begin

Hide;

end;

var i,j,k,Err:integer;

a:char;

Dotl:^Dot; Ringl:^Ring;

begin {тіло програми}

i:=detect;

initgraph(i,j,");

Err:=GraphResult;

If Err <> grOK then WriteLn (GraphErrorMsg(Err))

else

begin

New(Dotl,Init(GetMaxX div 2, GetMaxY div 2))

Dotl^.Show;

New(Ringl,Init(GetMaxX div 2, GetMaxY div 2, GetMaxY div 6));

Ringl^.Show;

while KeyPressed do

a:=ReadKey;

repeat

begin

a:=ReadKey;

case ord(a) of

72:Dotl^.Move(0,-5);

80:Dotl^.Move(0,5);

77:Dotl^.Move(5,0);

75:Dotl^.Move(-5,0);

73:Ringl^.Move(0,-5);

81:Ringl^.Move(0,5);

79:Ringr.Move(5,0);

71:Ringl^.Move(-5,0); end;

end;

until a = chr(27);

Dispose (Dotl, Done);

Dispose(Ringl,Done)

end;

end.

У яких випадках має сенс використовувати динамічні змінні, ми з'ясували при вивченні покажчиків і використання динамічної пам'яті. Наприклад, якщо програма оперує множиною об'єктів (а не всього двома, як у нашому випадку) і звичайної (чи статичної) пам'яті для всіх цих об'єктів не вистачає, варто помістити їх у динамічну пам'ять. Крім того, якщо використання об'єкта починається в середині програми чи завершується задовго до її кінця, щоб не займати пам'ять увесь час, також доцільно оголосити такі об'єкти в якості динамічних. (Останнє для нашого приклада також не актуально, оскільки екземпляри об'єктів тут використовуються від початку і до кінця; тобто строго говорячи, пам'ять за допомогою деструкторів у нашій програмі можна було б і не звільняти.)

Поля і методи: сховані і загальнодоступні

Поля і методи в описі об'єктного типу можуть оголошуватися як сховані, або як загальнодоступні. Відповідні розділи в описі об'єкта відкриваються директивами PRTVATE і PUBLIC. От так можна використовувати директиви в описі типу Dot із програми, з яким ми мали справу в попередньому розділі:

Dot=object

private

a, b:integer;

public

constructor Init (x,y:integer);

procedure Show; virtual;

private

procedure Hide; virtual;

procedure Move (Da, Db: integer);

public

destructor Done;

end;

У цьому прикладі в якості схованих оголошені поля А и В, а також методи Hide і Move. У той же час методи Init, Show і Done мають тут статус загальнодоступних. Читач уже зрозумів, що кожна чергова директива PRVATE чи PUBLIC скасовує дію попередньої директиви. При цьому, якщо в описі типу зазначених директив нема, за замовчуванням вважається, що всі поля і методи цього типу загальнодоступні.

Поля і методи об'єкта, оголошені після директиви РRVАTЕ, будуть доступні тільки в межах даної програми чи модуля. Однак якщо цей об'єкт міститься в підключеному до програми модулі, ідентифікатори прихованих полів і методів виявляться для програміста невидимі. При цьому сам об'єкт буде цілком відкритий для використання. Для чого це потрібно? Наприклад, якщо це комерційний додаток, цілком природно, що його творці обмежують доступ користувачів до подробиць реалізації даного додатка (у результаті додатком можна користатися, але його не можна модифікувати на свій смак). Крім того, часто має сенс обмежити доступ до деяких полів і методів об'єкта, щоб програміст випадково (по недогляду) не вніс у них змін, які можуть спричинити непередбачені наслідки.


Системно- залежні розширення

У даній главі зосереджені описи вбудованих у Turbo Pascal засобів, орієнтованих на безпосередній доступ до можливостей операційного середовища, в оточенні якого працює система Turbo Pascal, - операційної системи MS-DOS, a також до оперативної пам'яті і пристроїв персонального комп'ютера.

Набір цих засобів включає безпосередній доступ до оперативної пам'яті по фізичних адресах, включаючи настроювання Pascal-змінних на конкретну адресу, доступ до портів (також по їх номерах), виклик переривань і функцій операційної системи, явну роботу з кодами команд центрального процесора. Сюди ж примикає оверлейний механізм, який дозволяє будувати великі програми, що перевершують по розмірах об’эм оперативної пам'яті.

Дані засоби в сукупності дозволяють у максимальный ступені використовувати можливості апаратури й операційної системи персонального комп'ютера і значно збільшують потужність мови Turbo Pascal. Для детального ознайомлення із системно-орієнтованими розширеннями мови варто звернутися до фірмових матеріалів поТurbоРаsсаl

Налагодження змінних

Як відомо, пам'ять під змінні приділяється автоматично при вході в ту підпрограму, де вони описані. Однак Тurbо Pascal містить засоби, що дозволяють впливати на розміщення змінних в оперативній пам'яті. Конкретно, є два способи завдання розташування змінних. По-перше, можна визначити розміщення змінної в тій області пам'яті, що уже відведена для збереження деякої іншої змінної. Наприклад, нехай у поточному (чи в деякому охоплюючому) блоці описана змінна А.Тоді опис змінної В виду

var В: Т absolute A;

де Т - деякий тип, задає розміщення змінної в тій же області пам'яті, у якій міститься перемінна А. Важливо розуміти, що у випадку такого опису нова область для збереження змінної В НЕ ВИДІЛЯЄТЬСЯ. Крім того, істотним є той факт, що змінні А и В можуть бути будь-яких (зокрема, різних) типів і отже можуть займати різні об’єми пам'яті. Ніяких перевірок на цей рахунок не робиться. Таким чином, можна вважати, що приведений приклад задає розміщення змінних А и В ПОЧИНАЮЧИ з тої самої адреси.

Таке сполучення пам'яті для різних змінних використовується, в основному, для досягнення більшої ефективності об'єктного коду, а також для виключення громіздких операцій перетворення типів у деяких спеціальних випадках.

Розглянемо кілька прикладів.

У випадку інтенсивних операцій над рядками приходиться досить часто звертатися до стандартної функції Length, яка повертає поточну довжину рядка-параметра. З іншого боку, структура представлення рядків у пам'яті однозначно визначає нульовий байт рядка як місце збереження її поточної довжини. Тому можна використовувати, таку техніку роботи з рядками: крім опису самого рядка, увести додаткову змінну типу byte:

var

S: string[80];

L: byte absolute S;

Пам'ять під змінну L буде відведена там же, де по визначенню розміщений нульовий байт рядка. Отже, значення змінної завжди буде збігатися з результатом виклику Length (S). Природно, доступ до байта з довжиною за допомогою простої змінної приводить до більш компактного тексту програми і більш ефективному коду.

Наступний приклад ілюструє сполучення в пам'яті, змінних різних типів. Наступне опис:

var

I: longint;

R: record;

HiI, Loi: word

end

L absolute I;

уможливлює безпосереднє звертання до частин змінної I за допомогою конструкцій R.Hi і R.Loi. Tаким чином, Turbo Pascal, власне кажучи, допускає ще один спосіб неявного перетворення типів.

Конструкція зі службовим словом absolute застосовна і до параметрів підпрограм. Так, якщо параметр процедури специфікований як безтиповий, то можна інтерпретувати цей параметр необхідним чином, наприклад:

procedure P (var Par);

var

M: array[1..100] of real absolute Par;

begin

end;

Усередині процедури P безтиповий параметр Par буде трактуватися як масив зі ста дійсних чисел, при цьому фактичний параметр може іменувати змінну будь-якого типу. Ще раз нагадаємо, що ніяких перевірок на відповідність у таких випадках не робиться, тому при роботі з такими змінними необхідна підвищена акуратність.

Другий спосіб настроювання дозволяє розмістити змінну довільного типу в деякомусь (буд-якому) місці оперативної пам'яті, починаючи з заданої фізичної адреси, що повинна бути зазначеною після службового слова absolute у виді пари "сегмент: зсув". Наприклад, опис:

var CrtMode: byte absolute $0040:$0049

визначає розташування змінної CrtMode у сегменті з адресою $0040 і зсувом відносно початку цього сегмента $0049. Цей спосіб настроювання змінних застосовується, в основному, для ефективного доступу до внутрішніх даних операційної системи і вимагає особливої уважності і точного знання адрес розташування системної інформації. Обидві константи у випадку завдання фізичної адреси повинні бути цілого типу і не можуть виходити за межі діапазону $0000.. $FFFF (тобто 0.. 65535).

Як приклад розглянемо техніку прямого звертання до відеопам'яті дисплейного адаптера, що часто використовується для побудови багатовіконних інтерфейсів і систем меню в текстовому режимі роботи дисплея.

Відомо, що для кольорового адаптера область пам'яті, у якій зберігається "образ" екрана дисплея в текстовому режимі роботи, починається із сегмента з адресою $B800 (точніше, така адреса власної відеопам'яті адаптера); у цій відеопам'яті кожному знакомісцю (одній позиції символу на екрані) відповідає два байти: один байт для коду відображуваного символу й один байт для колірних атрибутів символу (колір фону, колір самого символу, ознака мерехтіння). Таким чином, для звичайного режиму роботи дисплея (25 рядків по 80 символів у рядку) можна в такий спосіб описати структуру відеопам'яті

var

ColorBuffer: array[1..25,1..80] of

record

Symbol: char;

Attribute: byte

end

absolute $B800:$0000;

Далі в програмі присвоювання окремим елементам масиву ColorBuffer будуть приводити до негайного відображення отриманих значень на екрані (при цьому необхідно ще знати структуру байта з колірними атрибутами). Так наприклад, використовуючи системні константи, що позначають припустимі кольори, можна в такий спосіб формувати зображення на екрані:

with ColorBuffer[13,40] do

begin Symbol:= '*';

Attribute:= (Red shi 4) and Yellow

end;

Цей оператор виведе в середину екрана символ '*' жовтого кольору на червоному фоні.

Оверлеї

Використання мови Turbo Pascal для програмуванні реальних задач у ряді випадків ускладнюється обмеженнями зв'язаними з розміщенням великих програм в оперативній пам'яті. У загальному випадку без використання спеціальних засобів максимальний розмір програми не може перевищувати обсяг вільної оперативної пам'яті.

З метою подолання цього природного обмеження в системі Turbo Pascal мається спеціальний механізм, що називається оверлейним. Застосування оверлейного механізму дозволяє розробляти великі і складні програми, розмір яких значно перевищує об’єм оперативної пам'яті.

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

Оверлейний механізм є відомим і найбільш поширеним методом подолання обмежень по оперативній пам'яті при відсутності в операційної системи засобів віртуалізації пам'яті. Крім системи Turbo Pascal, розвитий оверлейний механізм використовується, наприклад, при формуванні об'єктного коду в системі Сlіррег. Як правило, алгоритм розміщення і видалення оверлейних фрагментів реалізується спеціальним системним монітором, який розташовують в коді розроблювальної програми.

Природно, оверлейна програма буде виконуватися повільніше, ніж виконується (чи могла б виконуватися) еквівалентна їй звичайна програма, за рахунок часу підкачування оверлейних фрагментів. Однак це уповільнення є необхідною платою за виграш у загальному розмірі програми.

Засоби побудови оверлейних структур у мові Turbo Pascal досить прості для використання (зберігаючи в цьому відношенні загальний дух ясності і компактності мови Pascal), і базуються на наступних основних принципах.

1. Оверлейна програма повинна бути реалізована як керуюча (резидентна) частина, що постійно буде знаходитися в пам'яті, і декілька оверлейних фрагментів, коди яких будуть поперемінно завантажуватися в спеціальний оверлейний буфер в оперативній пам'яті, що автоматично виділяється між сегментом стека і динамічною областю пам'яті. За замовчуванням для оверлейного буфера вибирається мінімальний можливий розмір, але під час виконання програми його розмір може бути легко збільшений шляхом виділення додаткової області.

2. Оверлейні фрагменти повинні бути оформлені у виді МОДУЛІВ, з спеціальною директивою {$О+}.

3. Ніяких додаткових мовних конструкцій для оверлейного механізму не передбачено; усі засоби керування оверлеями зосереджені в системному модулі Overlay.

4. При трансляції оверлейної програми Turbo Pascal-компілятор працює в так: керуюча (не-оверлейна) частина програми оформляється у виді EXE-файлу; всі оверлейні частини (тобто коди модулів) об’єднуються у файл із тим же самим ім'ям, але з розширенням. OVR:

 
 

Усі звертання до оверлейних процедур і фуикцій повинні здійснюватися за допомогою далекого типу викликів. Для цього такі підпрограми (чи оверлейні модулі в цілому), а також основну програму необхідно компілювати з включеною директивою $F чи додавши після заголовка підпрограм службове слово far.

5. Основна частина оверлейнї програми повинна, зрозуміло, містити оператор uses, у якому перелічуються використовувані оверлейні модулі (у цьому ж описі можуть бути зазначені і не оверлейні модулі, використовувані програмою). Першим модулем в описі повинний бути зазначений системний модуль Overlay. Крім того, слідом за цим описом повинні розміщатися директиви компілятора, що вказують, які модулі з перерахованих в описі uses є оверлейними. Кожна така директива має вид:

{$О Ім'я_модуля}

Ім'я модуля в цій директиві повинне ідентифікувати дисковий файл із кодом оверлейного модуля. Допускається вказівка дисковода і/або адреси модуля. Розширення імені (. TPU) може бути опущено.

Помітимо, що з усіх системних модулів TurboPascal у якості оверлейного може бути використаний ТІЛЬКИ модуль Dos.

6. У тілі головної програми перед першим звертанням до якої-небудь оверлейної підпрограмі повинний бути виклик стандартної процедури OvrInit з модуля Overlay. Ця процедура ініціалізує підсистему керування оверлеями; єдиним її параметром є рядок з ім'ям файлу, у якому зібрані коди оверлейних модулів (ovr-файл).

Нижче приведений простий приклад оверлейної програми, що складається з головної програми і двох оверлейних модулів. Цей приклад входить у групу демонстраційних файлів стандартного постачання системи Turbo Pascal.

{$F+,O+} { директиви $F, $0 включені }

program OvrDemo;{ головна частина оверлейної

програми }

{ У uses-списку повинний бути зазначений модуль Overlay, a також оверлейні модулі; указівка модуля Crt показує, що в списку повинні бути усі використовувані в програмі модулі }

uses

Overlay, Crt, OvrDemol, OvrDemo2;

{$0 OvrDemol} { Вказівка оверлейних модулів з }

{$0 OvrDemo2} { числа зазначених у uses-списку }

begin

{ Використовуємо засоби модуля Crt }

TextAttr:= White; ClrScr;

{ Ініціалізація системи керування оверлеями }

OvrInit('OVRDEMO.OVR')

if OvrResult <> 0 then

begin

Writeln('Помилка: ', OvrResult); Halt(l)

end;

{Коди процедур Writel і Write2, розташовані в оверлейних модулях, будуть при виконанні

програми поперемінно завантажуватися в оверлейний буфер }

repeat

Writel; Write2 until

KeyPressed

end.

Файл OVRDEM01.PAS {$0+,F+} unit OvrDemol; interface procedure Writel; implementation procedureWritel; begin Writeln('Один... '); end; end.   Файл OVRDEMO2.PAS {$0+,F+} unit OvrDemo2; interface procedure Write2; implementation procedure Write2; begin Writeln('Два...'); end; end.

 

Переривання і системні виклики

Стандартні модулі системи Turbo Pascal містять дуже великий набір засобів для різних застосувань, що використовують більшість можливостей операційної системи MS-DOS. Проте в багатьох випадках виникає необхідність прямого звертання до MS-DOS як для одержання більшої ефективності програми, так і з метою використання деяких специфічних засобів операційної системи.

Для цих цілей у системному модулі Dos маються дві стандартні процедури. Перша процедура має ім'я Intr і дозволяє безпосередньо викликати програмні переривання, через які і реалізується доступ до всіх ресурсів операційної системи. Заголовок цієї процедури виглядає в так:

Intr (IN: byte; var Regs: Registers)

Зміст параметрів наступний:

- IN - номер програмного переривання, яке необхідно викликати (0..255);

- Regs – запис за допомогою якого передаються параметри зазначеного переривання. Тип цього запису Registers визначається в модулі Dos так:

type

Registers = record

case integer of 0:(AX,BX,CX,DX,BP,SI,DI,DS,ES,

Flags:word);

l:(AL,AH,BL,BH,CL,CH,DL,DH:byte)

end;

Структура цього запису моделює набір регістрів центрального процесора (варіанти в цьому записі відбивають той факт, що з першими чотирма регістрами можна працювати або як з єдиними словами, або з їх старшими і молодшими частинами по окремості).

При виконанні процедури Intr значення полів AX, BX, СХ, DX, ВР, SI, DІ, DS і ЕS запису-параметра завантажуються в однойменні регістри ЦП. Після цього процедура реалізує виклик переривання з номером IN. Після того, як переривання буде виконане, новий уміст цих регістрів записується в запис Regs (цей запис передається в процедуру як параметр-змінна) у ті ж поля, а вміст регістрів прапорів розташовується в поле Flags. Таким чином, у програмі стають доступними результати виклику переривання.

Ця процедура не може використовуватися для виклику тих переривань, що вимагають завдання визначених значень регістрів SP чи SS чи змінюють значення цих регістрів. Для роботи з процедурою Intr потрібно детальне знання семантики, параметрів і результатів викликуваного переривання. Ці відомості містяться в технічних посібниках no операційній системі MS-DOS. Нижче приводиться простий приклад використання процедури Intr для визначення розмірів основної і розширений (extended) оперативної пам'яті ЕОМ. Параметрами процедури TotalMemory є змінні DosMemory і ExtMemory, у которі будуть поміщений розмір пам'яті, що адресується DOS, в одиницях по lК, і розмір розширеної оперативної пам'яті (з адресами, більшими 1 МБ). Процедура використовує переривання із шістнадцятьковими номерами $12 і $15, які, власне, і реалізують відповідні запити до операційної системи.

procedure TotalMemory(var DosMemory,ExtMemory:word);

var

Regs; Registers;

begin

with Regs do

begin

Intr($12,Regs);

DosMemory:= АХ;{у полі АХ повертається розмір основної пам'яті};

AX:=$8800;{параметр виклику}

Intr{$15,Regs);
ExtMemory:= АХ {у полі АХ повертається розмір розширеної пам'яті}

end

end;

Ця процедура може бути використана, наприклад, так:

uses

Dos; var

DosM, ExtM: word;

procedure TotalMemory (varDosMsmory,ExtMamory: word);

begin

TotalMemory(DosM,ExtM);

Writeln('Ocновна пам'ять DOS = ',Dos,'K');

Writeln('Pозширена пам'ять = ',Ext,'K') end.

Друга процедура, використовувана для звертання до операційної системи, має ім'я MsDos і реалізує виклик конкретного переривання із шістнадцятьковим номером $21. Справа в тім, що дане переривання виконує велику кількість дій, об'єднаних під загальною назвою "функції MS-DOS". Тому для більш комфортабельного звертання до цих функцій доступ до переривання $21 виділений в окрему процедуру.

Єдиним параметром процедури MsDos є запис типу Registers.

MsDos (var Regs: Registers)

Результат звертання до процедури MsDos буде той же самий, що і звертання до функції Intr з номером переривання $21.

Простий приклад використання процедури MsDos приведений нижче. Програма використовує функцію з номером 9 (цей номер передається через регістр АН) для виведення на екран тексту вітання. Через регістри DS і DX передається адреса першого байта виведеного рядка, що повинний завершуватися символом '$'.

program UseMsDos; uses

Dos; const

Msg: string = 'Привіт!$'; var

Regs: Registers; begin

with Regs do begin АН:= 9; DS:= Seg>(Msg), DX:= Ofs(Msg[l]) end;

MsDos(Regs) end.

Доступ до пам'яті і портів

У мові Turbo Pascal реалізовані три визначених масиви, що позначаються наступними ідентифікаторами:

Mem

MemW

MemL

Ці масиви використовуються для прямого доступу до пам'яті, інтерпретуючи її як таку, що складається з елементів різного формату. Кожен елемент масиву Mem являє собою байт (має тип-byte), кожен елемент Mem -слово (тип word), а елемент масиву Mem - тип longint (подвійне слово).

Для доступу до елементів цих масивів використовується спеціальний синтаксис, аналогічний адресації в конструкції absolute: як індекс цих масивів указуються два вирази типу word, розділені символом ':' двокрапка). Значення цих виразів задають фізичну адреса елемента пам'яті. Приведемо кілька прикладів:

Mem[$0040:$0049]:= 7;

Data:=Mem[Seg(V):Ofs(V)];

MemLong:=MemL[64:3*4];

Перший оператор зберігає значення 7 у байті з адресою$0040: $0049. Другий оператор поміщає значення довжиною слово, записане в перших двох байтах змінної v, у змінну Data. Третій оператор поміщає значення типу longint, записане за адресою $0040:$OOOC, у змінну МемLоng.

Звертання до оперативної пам'яті по фізичних адресах використовується, як правило, у тих випадках, коли необхідне максимально ефективне (без звертань до переривань) коректування системної інформації MS-DOS.

Для доступу до портів введення-виведення центрального процесора Intel 80x86 у мові Turbo Pascal реалізовані два визначених масиви, що позначаються ідентифікаторами

Port

PortW

Обидва масиви є одномірними і кожним їхнім елементом є якийсь порт введення-виведення; індекс масиву відповідає адресі порту. Як індекс допускється вираз типу word. Елементи масиву Port мають тип byte, елементи масиву PortW- тип word.

Коли елементу масиву Port чи РоrtW присвоюється значення, ця дія інтерпретується як виведення значення в обраний порт. Коли елементи масиву Port чи PortW виступають як операнди у виразах, то доступ до порту розуміється як введення з обраного порту. Приведемо кілька прикладів:

Port[$20,]:=$20;

Port[Base]:= Port[Base] xor Mask;

while Port[$B2] and $80= 0 do; { чекання}

Використання масивів Port і PortW обмежено тільки в тім відношенні, що елементи цих масивів не можуть використовуватися в якості параметрів-змінних. Більш, того, посилання на весь масив Port чи PortW (без вказівки індексів) не допускаються.

Приведемо приклад доступу до порту клавіатури (номер порту $60 чи $64) для керування швидкістю руху курсору (маються на увазі дві характеристики -частота переміщення курсору на екрані при довготривалому натисканні клавіші керування курсором і початкова затримка, переміщення після натискання цих клавіш). Пояснення містяться в програмі у виді коментарів; програма складена на основі рекомендацій електронного довідника PC-Help. Коди режимів визначаються значенням змінної Mode.

program RepeatMode; {Установка режимів руху курсора}

uses

Crt;

var

Mode: byte, { Значення, що розташовується в порт $60 ($64) і містить коди режимів:

}

begin

{ Mode:=$7F,- } {усе саме повільне}

Mode:=$00; { усе найшвидше }

Port[$60]:=$F3; { Посилаємо в порт команду }

Delay(10); { Затримку велить робити PC Help}

Port[$60]:=Mode { Посилаємо в порт значення Mode}

end.




Поделиться с друзьями:


Дата добавления: 2014-12-16; Просмотров: 507; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.007 сек.