Проблема успадковування

Дещо видозмінимо попередньо створені об’єкти TStudent і TStudent1 (Програма 5.1)

program prakt1;

uses crt;

type

TStudent=object

Name:String[30];

Date:string[10];

rate:real;

procedure init(nm,Dt:String;rt:real);

function GetName:string;

function getdate:string;

function getrate:real;

procedure showname;

procedure showdate;

procedure showrate;

end;

 

TStudent1=object(TStudent)

Bal:real;

procedure init(nm,dt:string;rt,bl:real);

function getname:string;

function getbal:real;

function getsum:real;

procedure showbal;

procedure showall;

end;

 

procedure TStudent.init(nm,dt:string;rt:real);

begin

name:=nm;

date:=dt;

rate:=rt;

end;

 

function TStudent.GetName:string;

begin

getname:=name;

end;

 

procedure TStudent.showname;

begin

writeln(getname);

end;

 

procedure TStudent1.init(nm,dt:string;rt,bl:real);

begin

TStudent.Init(nm,dt,rt);

bal:=bl;

end;

 

function TStudent1.GetName:string;

begin

getname:='~'+name+'~';

end;

 

 

(далі йде текст всіх раніше створених методів)

 

var st:TStudent;

st1:TStudent1;

begin

clrscr;

writeln('батьківський обєкт');

st.init('Ляшук','01.02.1995',700);

st.showname;

writeln('спадкоємець');

with st1 do begin

init('Panko','10.10.1995',550,4.8);

showname;

end;

writeln('=======================');

repeat until keypressed;

end.

Програма 5.1

Метод showname успадковується об’єктом TStudent1 від об’єкту TStudent. В свою чергу, цей метод використовує інший: getname, який ми для TStudent і TStudent1 створили трохи по-різному:

function TStudent.GetName:string;

begin

getname:=name;

end;

 

function TStudent1.GetName:string;

begin

getname:='~'+name+'~';

end;

 

Але який варіант методу getname виконається, якщо ми застосуємо метод showname до змінної об’єктного типу TStudent1: власний метод цього типу чи відповідний метод батьківського типу? Склавши відповідну програму і виконавши її, переконаємося, що буде виконуватися метод батьківського типу.

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

Віртуалізація методів

З правил сумісності фактичних і формальних параметрів типу «об’єкт» випливає, що в якості фактичного параметра може виступати об’єкт будь-якого похідного типу від типу формального параметра. Таким чином, під час компіляції процедури невідомо, об’єкт якого типу буде їй переданий в якості фактичного параметра (такий параметр називається поліморфним об’єктом). Повною мірою поліморфізм об’єктів і методів реалізується за допомогою віртуальних методів.

Метод стає віртуальним, якщо після його визначення в типі об’єкта проставлене службове слово VIRTUAL.

PROCEDURE ІмяМетоду(параметри); VIRTUAL;

 

або

FUNCTION ІмяМетоду(параметри):ТипЗначення; VIRTUAL;

При віртуалізації методів повинні виконуватися наступні умови:

1. Якщо прабатьківський тип об’єкту описує метод як віртуальний, то всі його похідні типи, що реалізують метод з таким самим іменем, повинен описувати цей тип теж як віртуальний. Іншими словами, не можна заміняти віртуальний метод статичним. Якщо ж це трапиться, компілятор повідомить про помилку номер 149 VIRTUAL expected (очікується службове слово VIRTUAL)

2. Якщо перевизначається реалізація віртуального методу, то заголовок заново визначеного віртуального методу у похідному типі не може бути зміненим. Інакше кажучи, повинні залишатися незмінними порядок розміщення, кількість і типи формальних параметрів в однойменних віртуальних методах. Якщо цей метод реалізується функцією, то не може мінятися і тип результату. При зміні заголовку методу компілятор видасть повідомлення про помилку номер 131 Header does not match previous definition («Заголовок не відповідає попередньом визначенню»).

3. В описі об’єкту повинен обов’язково описуватися спеціальний метод, що ініціалізує об’єкт (звичайно його називають Init). В цьому методі службове слово PROCEDURE в оголошенні і реалізації повинне бути замінене на слово CONSTRUCTOR. Конструктор завжди викликається до першого виклику віртуального методу. Виклик віртуального методу без попереднього виклику конструктора може привести систему до тупикового стану, а компілятор не перевіряє порядку виклику методів. Пам’ятаймо про це! Об’єкт може мати кілька конструкторів. Конструктор є статичним методом.

Спробує розв’язати проблему успадковування, поставлену у попередньому підрозділі, з використанням віртуалізації методів (Програма 5.2).

program prakt1;

uses crt;

type

TStudent=object

Name:String[30];

Date:string[10];

rate:real;

constructor init(nm,Dt:String;rt:real);

function GetName:string; virtual;

function getdate:string;

function getrate:real;

procedure showname;

procedure showdate;

procedure showrate;

end;

 

TStudent1=object(TStudent)

Bal:real;

constructor init(nm,dt:string;rt,bl:real);

function getname:string;virtual;

function getbal:real;

function getsum:real;

procedure showbal;

procedure showall;

end;

 

constructor TStudent.init(nm,dt:string;rt:real);

begin

name:=nm;

date:=dt;

rate:=rt;

end;

 

function TStudent.GetName:string;

begin

getname:=name;

end;

 

 

procedure TStudent.showname;

begin

writeln(getname);

end;

 

 

constructor TStudent1.init(nm,dt:string;rt,bl:real);

begin

name:=nm;

date:=dt;

rate:=rt;

bal:=bl;

end;

 

function TStudent1.GetName:string;

begin

getname:=’~’+name+’~’;

end;

 

(далі йде текст всіх раніше створених методів)

 

 

var st:TStudent;

st1:TStudent1;

begin

clrscr;

writeln(‘батьківський обєкт’);

st.init(‘Ляшук’,’01.02.1995’,700);

st.showname;

writeln(‘спадкоємець’);

with st1 do begin

init(‘Panko’,’10.10.1995’,550,4.8);

showname;

end;

writeln(‘=======================’);

repeat until keypressed;

end.

Програма 5.2

Метод getname ми описали як віртуальний і це дало нам змогу по-справжньом використати поліморфізм. Виконавши програму, переконаємося, що поле name батьківського і дочірнього об’єкту виводяться дещо по-різному:

Раннє і пізнє зв’язування

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

Якщо використовувати раніше створений приклад у «статичному» варіанті, то виклик процедури getname міг привести тільки до виконання найближчого в об’єктній ієрархії getsum, тобто TStudent.getname. Якщо припустити, що власний метод дочірнього типу, котрий би перевизначав TStudent.showname, не існує, то будь-який породжений по відношенню до TStudent тип буде працювати саме з тим showname, котрий визначений у батьківському типі і, відповідно, з тим самим getname, котрий визначений у батьківському типі. Рішення про це приймається на етапі компіляції, а таке зв’язування називається раннім зв’язуванням.

Якщо ж ми використовуємо віртуалізацію методів, то все відбувається інакше. Кожен тип об’єктів має власний метод getname, тому від того, який екземпляр викликає метод showname, залежить, який саме getname виконається. Тому рішення про виклик методу getname всередині showname доводиться відкласти, його неможливо прийняти при компіляції коду showname. Інформація про те, об’єкт якого типу викликав showname, недоступна під час компіляції, тому розв’язок відкладається до моменту, поки програма не почне виконуватися і поки не можна буде встановити, об’єкт якого типу викликав цей метод. Таке зв’язування називається пізнім.

Підсумок

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

Методом розв’язку проблеми успадкування є віртуалізація методів, яка дає нам змогу по-справжньому використовувати поліморфізм: той сам метод може працювати по-різному в залежності від того, який об’єкт його викликає. Зв’язування об’єкту з методом на етапі компіляції називається раннім, на етапі виконання – пізнім.

Якщо об’єкт має віртуальний метод, то один з методів цього об’єкту, який його ініціалізує, повинен бути описаний як конструктор.

 


Питання по темі

1. Якщо метод описаний без жодних спеціальних уточнень, то він є

а) статичним

б) віртуальним

г) це залежить від того, який об’єкт його викликає.

 

2. Вкажіть правильний опис віртуального методу

а) constructor init(nm,dt:string;rt,bl:real);

б) function getname:string;virtual;

в) function getbal:real;

 

3. Якщо метод працює по-різному в залежності від того, екземпляр якого типу його викликає, то це називається:

а) інкапсуляцією

б) успадковуванням

в) поліморфізмом

 

4. Нехай об’єкт B є батьківським по відношенню до D. Один з типів об’єкту В з іменем М є віртуальним, об’єкт D його перевизначає. Виберіть з описів цього методу правильний

а) procedure B.M(a:string); virtual;

procedure D.M(a:string);

 

б) procedure B.M(a:string); virtual;

procedure D.M(a:string;l:integer); virtual

 

в) procedure B.M(a:string); virtual;

procedure D.M(a:string); virtual;

 

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

 


Лекція 6

Тема 2 Об’єктно-орієнтоване програмування в Pascal

 

Конструктори і таблиця віртуальних методів

Відлагодження програм з використанням віртуальних методів

Розширюваність об’єктів

Переваги і недоліки віртуальних методів

Проміжний контроль

Самостійна робота

 

Конструктори і таблиця віртуальних методів

Кожний екземпляр (змінна) типу «об’єкт», що містить віртуальні методи, повинен ініціалізуватися окремим викликом конструктора. Якщо змінна А ініціалізована викликом конструктора, а змінна В того ж типу не ініціалізована, то присвоювання В:=А не ініціалізує змінну В і при виклику її віртуальних методів програма може зависнути.

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

Важливо пам’ятати, що таблиця віртуальних методів – одна для кожного типу, а не в кожної змінної типу «об’єкт». Змінна лише підтримує зв’язок з таблицею свого типу, і цей зв’язок встановлюється конструктором. В об’єкті може бути визначено кілька конструкторів. Самі конструктори можуть бути лише статичними, хоча всередині конструктора можуть викликатися і віртуальні методи.

При передачі в процедуру чи функцію поліморфного об’єкту, що має віртуальні методи, адреси цих методів передаються через таблицю VMT, що відповідає цьому об’єкту. Це гарантує, що спрацюють саме ті методи, які малися на увазі при оголошенні тапу об’єкта. Крім того, якщо об’єкт Z успадковує від об’єкту Y віртуальний метод, що викликає інші методи, то ці останні виклики будуть відноситися до методів об’єкту Z, а не Y. У випадку статичних методів все було б навпаки (виклики «не повернулись» би в Y).

 

Відлагодження програм з використанням віртуальних методів

При відлагодженні програми для контролю правильності викликів віртуальних методів можна використовувати директиву компілятора $R. Якщо директива знаходиться у включеному стані {R+}, то всі виклики віртуальних методів будуть перевірятися на момент стану ініціалізації об’єкта, що виконує виклик методу. Якщо об’єкт, який виконує виклик, ще не був ініціалізований конструктором, відбудеться помилка виконання. за замовчуванням, встановлено значення {R+}

 

Розширюваність об’єктів

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

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

 

Переваги і недоліки віртуальних методів

В загальному випадку рекомендується робити методи віртуальними. Використання статичних методів має сенс, якщо потрібно здобути оптимальну швидкість часу виконання і використання пам’яті. Однак у цьому випадку втрачається можливість розширення.

Іноді при написанні програми точно невідомо, є метод віртуальним чи ні. В таких випадках краще визначити його віртуальним, особливо якщо ймовірно, що метод перекриватиметься ким-небудь з потомків, а його код повинен бути доступним і надалі

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

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

 


Лекція 7

Тема 2 Об’єктно-орієнтоване програмування в Pascal

(повторення матеріалу «Вказівники і динамічна пам’ять)

 

Вказівники

Типізовані вказівники

Нетипізовані вказівники

Операція розіменування

Присвоєння вказівників

Виділення і вивільнення динамічної пам’яті

Організація динамічної пам’яті

Процедура New

Процедура Dispose

Підсумок

Питання по темі

 

Вказівники

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

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

VAR Number:integer;

 

Цим рядком ми вказуємо компілятору на необхідність зарезервувати область в пам’яті, на яку ми будемо посилатися по імені Number.

Адресу, за якою розміщена змінна Number, в пам’яті можна визначити за допомогою оператора @

P1:=@Number;

чи за допомогою функції Addr.

В результаті виконання операції P1:=@Number; в змінну P1 буде записана адреса змінної Number.

Часто в процесі написання програми виникають ситуації, коли без застосування вказівників неможливо обійтися. Так трапляється, якщо:

1. Програма працює з великим об’ємом даних (більше 64 Кб)

2. Програма під час пам’яті використовує дані, об’єм пам’яті для збереження яких зарані невідомий (так часто трапляється при роботі з масивами)

3. В програмі використовується буфер пам’яті для тимчасового зберігання даних.

4. Ми використовуємо структуровані змінні, тобто записи, масиви чи множини, які мають різну структуру.

5. Програма використовує динамічні структури даних (стеки, однозв’язні і двозв’язні списки, черги, бінарні дерева).

 

Вказівники у Pascal-i можуть зв’язуватися з певним типом даних (типізовані вказівники) чи не зв’язуватися (нетипізовані вказівники). Ще для називання змінних цього типу використовуються терміни: посилання і reference.

 

Типізовані вказівники

Для оголошення типізованого вказівника звичайно використовується символ ^, який розміщується безпосередньо перед відповідним типом даних, наприклад:

TYPE

P1Ptr:=^INTEGER;

VAR

P1:^INTEGER;

В загальному випадку:

TYPE

ІмяВказівника=^ІмяБазисногоТипу

де ІмяБазисногоТипу – будь-який ідентифікатор типу. В результаті такого визначення створені потім вказівники будуть вказувати на об’єкти базового типу, визначаючи тим самим динамічні змінні базового типу. Зараз нас особливо цікавлять вказівники на об’єкти. Синтаксис такого вказівника наступний:

TYPE

ObjType=OBJECT

END;

ObjPtr=^ObjType

 

Нетипізовані вказівники

Можна оголосити вказівник, не зв’язуючи його з конкретним типом даних. З цією метою використовується стандартний тип даних POINTER

VAR

P1:Pointer;

Оскільки нетипізовані вказівники не зв’язані з конкретним типом даних, їх дуже зручно використовувати для динамічного розміщення даних, структура і тип яких міняються в ході виконання програми.

 

Операція розіменування

Основною операцією при роботі з вказівниками є розіменування. Суть її полягає в переході від вказівника до значення, на яке він вказує. Синтаксис цієї операції можна зрозуміти з прикладу:

program prob;

uses crt;

var I,j:integer;

ptri:^integer;

begin

i:=5;

ptri:=addr(i);

j:=ptri^;

writeln(‘j=’,j);

repeat until keypressed;

end.

 

Розіменувані вказівники на структури індексуются (у випадку масивів) чи поділяються на поля (записи, об’єкти) звичайним способом. Наприклад:

Pta^[i] – доступ до і-го елементу масиву

Ptrec^.поле – доступ до поля динамічного запису

ObjPtr^.Метод – доступ до методу динамічного об’єкта.

 

Присвоєння вказівників

Вказівнику можна присвоювати вміст іншого вказівника такого ж типу або нетипізованого вказівника, константу NIL (порожній вказівник), адресу об’єкта, визначену за допомогою функції addr. Наприклад, якщо ми оголосили такі вказівники:

Var

Pntr1,Pntr2:^Integer;

Pntr3:^Real;

Pntr:Pointer;

i:integer;

то присвоєння

Pntr1:=Nil;

Pntr1:=Pntr2;

Pntr2:=addr(i);

 

допустимі, а присвоєння

Pntr1:=Pntr3; - ні, оскільки ці змінні вказують на різні типи даних.

 

Виділення і вивільнення динамічної пам’яті

Організація динамічної пам’яті

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

Нижня границя Heap-області визначається стандартною змінною-вказівником HeapOrg, яка містить абсолютну адресу початку динамічної пам’яті. Верхня границя Heap-област визначається вказівником HeapEnd. Поточне значення вказівника що розділяє зайняту й незайняту частину Heap-області, містить вказівник HeapPtr. При кожному новому виділенні пам’яті система управління Heap-областю пересуває вказівник HeapPtr вгору, в бік збільшення адрес пам’яті. При звільненні розподілених в динамічній пам’яті змінних відбувається зворотній процес., тобто вказівник зсувається вниз.

Об’ємом динамічної розподіленої пам’яті можна керувати за допомогою директиви компілятора {$M}. Її синтаксис: {$M розмір_стеку,розмір_динамічної_памяті}. Розмір стеку є числом в межах від 1024 до 65520, розмір динамічної пам’яті – від 0 до 655360. Ця директива при використанні в модулі користувача не впливає на компіляцію програми.

 








Дата добавления: 2015-08-26; просмотров: 651;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

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

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.073 сек.