Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Задание. Измените программу выполнения лабораторной работы 1 «Организация списков с помощью динамических переменных»






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

Пример выполнения задания

// Лабораторная работа 2. Модули. Выполнил Сергеев Андрей, группа 999.

// Модуль подпрограмм обработки списков.

unit Lab2Un; // заголовок модуля обработки списков

interface // интерфейс модуля

type

tValue=Integer; // тип элемента списка – целый

pItem=^tItem; // тип указателя на элемент списка

tItem=record // тип элемента списка

Value: tValue; // содержательная часть элемента списка

Next: pItem; // указатель на следующий элемент списка

end; // record tItem

// Заголовки процедур работы со списками

procedure Create(var List: pItem); // создание пустого списка

procedure InsertFirst(var List: pItem; v: tValue); // включение в начало

procedure InsertLast(var List: pItem; v: tValue); // включение в конец

procedure WriteList(var f: Text; List: pItem); // вывод списка в файл

function Size(List: pItem): Word; // вычисление размера

procedure Clear(var List: pItem); // удаление элементов

implementation // Секция реализации модуля

// Реализация процедур работы со списками

procedure Create(var List: pItem);

Begin

// Текст в лабораторной работе 1

end; //procedure Create

procedure InsertFirst(var List: pItem; v: tValue);

// Текст в лабораторной работе 1

end; // procedure InsertFirst

procedure InsertLast(var List: pItem; v: tValue);

// Текст в лабораторной работе 1

end; // procedure InsertLast

procedure WriteList(var f: Text; List: pItem);

// Текст в лабораторной работе 1

end; // procedure WriteList

function Size(List: pItem): Word;

// Текст в лабораторной работе 1

end; // function Size

procedure Clear(var List: pItem);

// Текст в лабораторной работе 1

end; // procedure Clear

end.

// Лабораторная работа 2. Модули.

// Использование модуля подпрограмм обработки обработки списков.

// Выполнил Сергеев Андрей, группа 999.

// Исходные данные – элементы основного списка – в файле LW4Dat.txt

// Результаты работы помещаются в файл LW2Res.txt

program LW2;

uses

SysUtils,

LW2Un in 'LW2Un.pas'; // использование модуля обработки списков

procedure FormLists(L: pItem; var L1, L2: pItem); //формирование L1, L2

// Текст в лаб.раб. 1

end; // procedure FormLists

 

var

fDat, fRes: Text; // файлы с исходными данными и результатами

L, L1, L2: pItem; // указатели на исходный и полученные списки

v: tValue; // значение элемента списка

begin

Assign(fDat, 'LW2Dat.txt'); Reset(fDat); // открытие файла для чтения

Assign(fRes, 'LW2Res.txt'); Rewrite(fRes); // открытие файла для записи

Create(L); Create(L1); Create(L2); // создание пустых списков

while not eof(fDat) do begin // пока не достигнут конец файла fDat

Read(fDat, v); // чтение из файла очередного значения

InsertFirst(L, v); // вставка элемента со значением v в начало списка L

end; // while

// Вывод списка L и его размера:

Writeln(fRes, 'Исходный список: '); WriteList(fRes, L);

Writeln(fRes, 'Число элементов списка: ', Size(L));

FormLists(L, L1, L2); // формирование списков L1 и L2

Writeln(fRes, 'Список отрицательных нечетных элементов: '); WriteList(fRes, L1);

Writeln(fRes, 'Список положительных четных элементов: '); WriteList(fRes, L2);

Clear(L); Clear(L1); Clear(L2); // удаление списков

Close(fDat); Close(fRes); // закрытие файлов

end.

Тема 3. ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ

1. Основные концепции ООП

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

К основным концепциям ООП относятся следующие: инкапсуляция, наследование, полиморфизм.

Ø Инкапсуляция представляет собой объединение данных и обрабатывающих их методов (подпрограмм) внутри класса (объекта). Это означает, что в классе инкапсулируются (объединяются и помещаются внутрь) поля, свойства и методы. При этом класс получает определенную функциональность, например, обеспечивая полный набор средств для работы с какой-либо динамической структурой данных.

Ø Наследование – это процесс порождения новых объектов-потомков от существующих объектов-родителей, при этом потомок наследует от родителя все его поля, свойства и методы. Просто наследование большого смысла не имеет, поэтому в объект-потомок добавляются новые элементы, определяющие его особенность и функциональность. Удалить какие-либо элементы родителя в потомке нельзя. В свою очередь, от нового объекта можно породить следующий объект, в результате образуется дерево объектов (называемое также иерархией классов).

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

Пример объявления класса-потомка:

tAnyClass = class (tParentClass)

// Добавление к классу tParentClass новых

// и переопределение существующих элементов

end;

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

Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нём нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноимённых метода (возможно с разным набором параметров), имеющие разную реализацию и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.

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

2. Классы и объекты

Классом в Delphi называется особая структура, состоящая из полей, методов и свойств. Такой тип также будем называть объектным типом.

Type

tMyClass = class (tObject) // класс tMyClass

fMyField: Integer; // поле fMyField

function MyMethod(Data: tData): Integer; // метод MyMethod

end;

Поскольку класс – это тип, то принято перед собственно именем записывать префикс t (от слова type – тип). Переменная типа класс называется экземпляром класса, или объектом:

var AMyObject: tMyClass; // объект класса tMyClass

Поля класса – это данные, уникальные для каждого созданного в программе экземпляра класса. Они могут иметь любой тип, в том числе – тип класс. В Delphi перед именами полей принято ставить символ f (от field – поле): fLength, fWidth, fMyFileld и т. п.

Методы – это процедуры и функции, описанные своими заголовками внутри класса и предназначенные для операций над его полями. В отличие от полей, методы у всех объектов одного класса общие. От обычных процедур и функций методы отличаются тем, что им при вызове передается (неявно) указатель на тот объект, который их вызвал. Поэтому обрабатываться будут поля именно того объекта, который вызвал метод. Внутри метода указатель на вызвавший его объект доступен под зарезервированным именем Self.

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

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

Методы класса реализуются в разделе описания процедур и функций программы или разделе implementation модуля. При реализации метода указывается его полное имя, состоящее из имени класса, точки и имени метода класса:

function tMyClass.MyMethod(Data: tData): Integer; // заголовок метода tMyClass. MyMethod

Begin

// тело метода tMyClass.MyMethod

end;

Разрешено опережающее объявление классов единственным словом class с последующим полным описанием:

Type

tFirstClass = class;

tSecondClass = class (tObject)

f1: tFirstClass;

end;

tFirstClass = class (tObject)

f2: tSecondClass;

end;

3. Создание и уничтожение объектов

В Delphi объекты могут быть только динамическими! Любая переменная объектного типа есть указатель, причем для доступа к данным, на которые ссылается указатель объекта не нужно применять символ ^.

Для выделения памяти экземпляру любой динамической переменной используется процедура New, для уничтожения – процедура Dispose. С объектами эти процедуры не используются: объект создается специальным методом – конструктором, а уничтожается – деструктором:

AMyObject: = tMyClass.Create; // создание объекта AMyObject класса tMyClass

… // действия с созданным объектом

AMyObject.Destroy; // уничтожение объекта AMyObject

Конструктор – это специальный метод, заголовок которого начинается зарезервированным словом constructor. Функция конструктора заключается в выделении памяти под экземпляр класса (объект) и установлении связи между созданным объектом и специальной информацией о классе. В теле конструктора можно расположить любые операторы, которые необходимо выполнить при создании объекта, например, присвоить полям начальные значения. В Delphi конструкторов у класса может быть несколько. Общепринято называть конструктор Create.

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

Конструктор создает новый экземпляр объекта только в том случае, если перед его именем указано имя класса. Если указать имя уже созданного объекта, он поведет себя по-другому: не создаст новый экземпляр, а только выполнит код, содержащийся в теле конструктора.

Конструктор класса-потомка практически всегда должен перекрывать конструктор класса-предка. Чтобы правильно проинициализировать в создаваемом объекте поля, относящиеся к классу-предку, нужно в начале кода конструктора вызвать конструктор предка с помощью зарезервированного слова inherited:

constructor tMyClass.Create;

Begin

inherited Create;

...

end;

Деструктор – это специальный виртуальный (см. ниже) метод, заголовок которого начинается зарезервированным словом destructor. Деструктор предназначен для удаления объектов. Типичное название деструктора – Destroy. Пример описания класса с конструктором и деструктором:

Type

tMyClass = class (tObject) // класс tMyClass

f MyField: Integer; // поле f MyField

constructor Create; // конструктор Create

destructor Destroy; override; // деструктор Destroy

function MyMethod(Data: tData): Integer; // метод MyMethod

end;

Для уничтожения объекта рекомендуется использовать метод Free (он есть у базового класса tObject), который первоначально проверяет указатель на объект (не равен ли он nil) и только затем вызывает деструктор Destroy:

AMyObject.Free;

Деструктор очень часто не переопределяется в классе-потомке.

4. Инкапсуляция. Свойства

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

Обычно свойство определяется тремя своими элементами: полем и двумя методами, которые осуществляют его чтение/запись:

Type

tMyClass = class (tObject)

function GetAProperty: tSomeType;

procedure SetAProperty(ANewValue: tSomeType);

property AProperty: tSomeType read GetAProperty write SetAProperty;

end;

В контексте свойства слова read и write являются зарезервированными. Для доступа к значению свойства AProperty явно достаточно написать

AMyObject.AProperty: = AValue;

AVariable: = AMyObject.AProperty;

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

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

tPropClass = class (tObject)

fValue: tSomeType;

procedure DoSomething;

function Correct(AValue: Integer): Boolean;

procedure SetValue(NewValue: Integer);

property AValue: Integer read fValue write SetValue;

end;

procedure tPropClass.SetValue(NewValue: Integer);

Begin

if (NewValue< > fValue) and Correct (NewValue) then fValue: = NewValue;

DoSomething;

end;

В этом примере чтение значения свойства AValue означает просто чтение поля fValue. Зато при присвоении ему значения внутри SetValue вызывается сразу два метода.

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

tMyClass = class (tObject)

property AProperty: tSomeType read GetValue;

end;

В этом примере вне объекта значение свойства можно лишь прочитать; попытки присвоить AProperty значение вызовет ошибку компиляции.

Для присвоения свойству значения по умолчанию используется ключевое слово default:

property Visible: Boolean read fVisible write SetVisible default True

Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:

property APoints[Index: Integer]: tPoint read GetPoint write SetPoint;

На самом деле, среди полей класса может и не быть поля-массива.

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

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

function GetPoint(Index: Integer): TPoint;

Метод записи значения в такое свойство должен первым параметром иметь индекс, а вторым – переменную нужного типа (которая может быть передана как по ссылке, так и по значению):

procedure SetPoint(Index: Integer; NewPoint: tPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки, наборы строк) «построены» вокруг векторного свойства. Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано как default:

tМуClass = class

property Strings[Index: Integer]: string read Get write Put; default;

end;

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

var AMyObject: tMyClass;

Begin

AMyObject.Strings[1]: = 'First'; // первый способ

AMyObject[2]: = 'Second'; // второй способ

...

end;

Будьте внимательны, применяя зарезервированное слово default, – для обычных и векторных свойств оно употребляется в разных случаях и с различным синтаксисом.

 

О роли свойств в Delphi красноречиво говорит следующий факт: у всех имеющихся в вашем распоряжении стандартных классов 100% полей недоступны (помещены в секцию private) и заменены базирующимися на них свойствами. Рекомендуем при разработке своих классов придерживаться этого же правила.

5. Наследование. Методы

Принцип наследования позволяет объявить класс

tNewObject = class (tOldObject);

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

В Delphi все классы являются потомками класса tObject. При построении дочернего класс прямо от tObject в определении его можно не упоминать. Следующие объявления одинаково верны:

tMyObject = class (tObject);

tMyObject = class;

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

Унаследованные от предка поля и методы доступны в дочернем классе; если имеет место совпадение имен методов, то говорят, что они перекрываются.

По тому, какие действия происходят при вызове, методы делятся на три группы: 1) статические, 2) виртуальные (virtual) и динамические (dynamic), 3) перегружаемые(overload) методы.

Статические методы, а также любые поля в объектах-потомках ведут себя одинаково: можно без ограничений перекрывать старые имена, и при этом изменять тип методов. Перекрытое поле предка недоступно в потомке. Перекрытый метод доступен при указании зарезервированного слова inherited. Методы объектов по умолчанию являются статическими – их адрес определяется еще на стадии компиляции проекта. Они вызываются быстрее всего.

Принципиально отличаются от статических виртуальные и динамические методы (директива virtual или dynamic). Их адрес определяется во время выполнения программы по специальной таблице. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе только одноименными методами, имеющимитот же тип.

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

6. Полиморфизм. Виртуальные и динамические методы

Рассмотрим пример. Пусть у нас имеются некое обобщенное поле для хранения данных – абстрактный класс tField и три его потомка – для хранения строк, целых и вещественных чисел:

tField = class function GetData: string; virtual; abstract; end; tStringField = class(tField) fData: string; function GetData: string; override; end; tIntegerField = class(tField) fData: Integer; function GetData: string; override; end; tExtendedField = class(tField) fData: Extended; function GetData: string; override; end; function tStringField.GetData; begin Result: = fData; end; function tIntegerField.GetData; begin Result: = IntToStr(fData); end; function tExtendedField.GetData; begin Result: =FloatToStr(fData); end; procedure ShowData(AField: tField); begin Writeln(AField.GetData); end;

В этом примере классы содержат разнотипные данные и «умеют» сообщать о значении этих данных текстовой строкой (при помощи метода GetData). Внешняя по отношению к ним процедура ShowData получает объект в виде параметра и показывает эту строку.

Правила контроля соответствия типов (typecasting) языка Delphi гласят, что объекту как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. В процедуре ShowData параметр описан как tField – это значит, что в нее можно передавать объекты классов и tStringField, и tIntegerField, и tExtendedField, и любого другого потомка tField.

Но чей метод GetData при этом будет вызван? Тот, который соответствует классу фактически переданного объекта. Этот принцип называется полиморфизмом и он, пожалуй, представляет собой наиболее важный принцип ООП. Например, чтобы смоделировать некоторую совокупность явлений или процессов средствами ООП, нужно выделить их самые общие, типовые черты. Те из них, которые не изменяют своего содержания, должны быть реализованы в виде статических методов. Те же, которые варьируются при переходе от общего к частному, лучше облечь в форму виртуальных методов. Основные, «родовые» черты (методы) нужно описать в классе-предке и затем перекрывать их в классах-потомках.

При вызове виртуальных и динамических методов адрес определяется не во время компиляции, а во время выполнения – это называется поздним связыванием (late binding). Позднее связывание реализуется с помощью таблицы виртуальных методов (Virtual Method Table, VMT) и таблицы динамических методов (Dynamic Method Table, DMT).

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

Динамические методы вызываются медленнее, но позволяют более экономно расходовать память. Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице; в случае неудачи просматриваются таблицы DMT всех классов-предков в порядке иерархии и, наконец, DMT класса tObject, где имеется стандартный обработчик вызова динамических методов. Экономия памяти налицо.

Для перекрытия и виртуальных, и динамических методов служит директива override, с помощью которой (и только с ней!) можно переопределять оба этих типа методов. Приведем пример:

tClass1 = class fField1: Integer; fField2: Longint; procedure stMet; procedure vrMet1; virtual; procedure vrMet2; virtual; procedure dnMet1; dynamic; procedure dnMet2; dynamic; end; tClass2 = class(tClass1) procedure stMet; procedure vrMet1; override; procedure dnMet1; override; end; var Obj1: tClass1; Obj2: tClass2;

Первый метод класса tClass2 создается заново, остальные два – перекрываются. Попытка применить директиву override к статическому методу вызовет ошибку компиляции.

7. Перегрузка методов

В последних версиях Delphi появилась новая разновидность методов – перегружаемые методы. Эту категорию методов нельзя назвать антагонистом двух предыдущих: и статические и динамические методы могут быть перегружаемыми. Перегрузка нужна, чтобы выполнить одинаковые или похожие действия с разнотипными данными. Рассмотрим пример:

Type

tClass1 = class

i: Extended;

procedure SetData(AValue: Extended);

end;

 

tClass2 = class (tClass1)

j: Integer;

procedure SetData(AValue: Integer);

end;

Var

Obj1: tClass1;

Obj2: tClass2;

Попытка вызова методов

Obj2.SetData(1.0);

Obj2.SetData(1);

вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри Obj2 статический метод с параметром типа Extended перекрыт, и он его не «признает». Объявить методы виртуальными нельзя, так как тип и количество параметров в одноименных виртуальных методах должны совпадать. Чтобы указанные вызовы были верными, необходимо объявить методы перегружаемыми, для чего используется директива overload:

Type

tClass1 = class

i: Extended;

procedure SetData(AValue: Extended); overload;

end;

tClass2 = class (tClass1)

j: Integer;

procedure SetData(AValue: Integer); overload;

end;

Объявив метод SetData перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или с плавающей точкой) и в зависимости от этого подставляет вызов соответствующего метода.

Можно перегружать и виртуальные методы. В этом случае необходимо добавить директиву reintroduce:

Type

tClass1 = class

i: Extended;

procedure SetData(AValue: Extended); overload; virtual;

end;

 

tClass2 = class (tClass1)

j: Integer;

procedure SetData(AValue: Integer); reintroduce; overload;

end;

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

8. Абстрактные методы

Абстрактными называются методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в потомках класса. Абстрактными могут быть только виртуальные и динамические методы. В Delphi есть одноименная директива (abstract), указываемая при описании метода:

procedure NeverCallMe; virtual; abstract;

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

Пример с классом tField из раздела 6 «Полиморфизм» поясняет, для чего нужно использование абстрактных методов. В данном случае класс tField не используется сам по себе; его основное предназначение – быть родоначальником иерархии конкретных классов-«полей» и дать возможность абстрагироваться от частностей. Хотя параметр процедуры ShowData и описан как tField, но если передать в нее объект этого класса, произойдет исключительная ситуация вызова абстрактного метода.

9. Области видимости

Области видимости – это возможности доступа к составным частям объекта. В Delphi поля и методы могут относиться к четырем группам: «общие» (public), «личные» (private), «защищенные» (protected) и «опубликованные» (published).

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

2. Поля, свойства и методы, находящиеся в секции private, доступны только в методах класса и в функциях, содержащихся в том же модуле, что и описываемый класс. Такая директива позволяет скрыть детали внутренней реализации класса от всех. Элементы из секции private можно изменять, и это не будет сказываться на программах, работающих с объектами этого класса. Обратиться к ним можно, только переписав содержащий их модуль.

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

4. Область видимости, определяемая четвертой директивой – published, имеет особое значение для интерфейса визуального проектирования Delphi. В этой секции должны быть собраны те свойства объекта, которые будут видны не только во время исполнения приложения, но и из среды разработки. Все свойства компонентов, доступные через Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как и public.

 

 

Пример, иллюстрирующий первые три варианта областей видимости:

unit First; interface type tClass1 = class public procedure Method1; private procedure Method2; protected procedure Method3; end; procedure TestProc1; implementation var Obj1: tClass1; procedure TestProc1; begin Obj1: = tClass1.Create; Obj1.Method1; // допустимо Obj1.Method2; // допустимо Obj1.Method3; // недопустимо Obj1.Free; end; end. unit Second; interface uses First; type tClass2 = class(tClass1) procedure Method4; end; procedure TestProc2; implementation var Obj2: tClass2; procedure tClass2.Method4; begin Method1; // допустимо Method2; // недопустимо Method3; // допустимо end; procedure TestProc2; begin Obj2: =tClass2.Create; Obj2.Method1; // допустимо Obj2.Method2; // недопустимо Obj2.Method3; // недопустимо Obj2.Free; end; end.

При описании дочернего класса можно переносить методы и свойства из одной сферы видимости в другую, не переписывая их заново и даже не описывая – достаточно указать новую сферу видимости наследуемого метода или свойства в описании дочернего класса. Разумеется, если вы поместили свойство в область private, «достать» его оттуда в потомках возможности уже нет.

10. Объект изнутри

Рассмотрим пример из раздела «Полиморфизм»:

tClass1 = class

fField1: Integer;

fField2: Longint;

procedure stMet;

procedure vrMet1; virtual;

procedure vrMet2; virtual;

procedure dnMet1; dynamic;

procedure dnMet2; dynamic;

end;

tClass2 = class (tClass1)

procedure stMet;

procedure vrMet1; override;

procedure dnMet1; override;

end;

Var

Obj1: tClass1;

Obj2: tClass2;

Внутренняя структура объектов Obj1 и Obj2 имеет вид:

Указатель на объект Obj1   VMT класса tClass1   DMT класса tClass1
       
Указатель на класс tClass1   RTTI класса tClass1   Число динамических методов: 2
Поле fField1 @tClass1.vrMet1 Индекс tClass1.dnMet1 (-1)
Поле fField2   @tClass1.vrMet2   Индекс tClass1.dnMet2 (-2)
    @tObject.Destroy   @tClass1.dnMet1
        @tClass1.dnMet2
         
Указатель на объект Obj2   VMT класса tClass2   DMT класса tClass2
         
Указатель на класс tClass2 RTTI класса tClass2 Число динамических методов: 1
Поле fField1 @tClass2.vrMet1 Индекс tClass2.dnMet1 (-1)
Поле fField2   @tClass1.vrMet2   @tClass2.dnMet1
    @tObject.Destroy    

Первое поле каждого экземпляра объекта содержит указатель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов, содержащая адреса всех виртуальных методов класса, включая унаследованные от предков. Перед таблицей виртуальных методов расположена специальная структура, которая называется информацией о типе времени выполнения (runtime type information, RTTI). В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, на имя класса и т. д. На рисунке она показана одним блоком.

Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат – в начале слово, содержащее количество элементов таблицы; затем – слова, соответствующие индексам методов. Нумерация индексов начинается с –1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Обратите внимание, что DMT объекта Obj1 состоит из двух элементов, Obj2 – из одного элемента, соответствующего перекрытому методу dnMet1. В случае вызова метода Obj2.dnMet2 индекс не будет найден в таблице DMT Obj2, поиск продолжится в DMT объекта Obj1. Именно так экономится память при использовании динамических методов.

Напомним, что указатель на класс указывает на адрес первого виртуального метода. Служебные данные размещаются перед таблицей VMT, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS.

В языке определены два оператора: is и as, неявно обращающиеся к информации о классе.

Оператор is предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Выражение вида:

AnObject is tObjectType

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

Оператор as введен в язык для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу:

with ASomeObject as tAnotherType do...

Использование оператора as отличается от стандартного способа приведения типов с помощью конструкции tAnotherType(ASomeObject) наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации EInvalidCast. После применения оператора as сам объект остается неизменным, но вызываются только те его методы, которые есть у присваиваемого класса. Присваиваемый фактически тип должен быть известен на стадии компиляции, поэтому на месте tObjectType (после is) и tAnotherType (после as) не может стоять переменная-указатель на класс.

Информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает вопрос: можно ли получить доступ к ней, не создавая экземпляр класса (объект)? Да, можно. Доступ к информации класса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс tObject описан в модуле SYSTEM.PAS и называется tClass:

Type

tObject = class;

tClass = class of tObject;

Указатели на классы подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы; обратное невозможно.

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта – с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное слово class:

Type

tMyClass = class (tObject)

class function GetSize: string;

end;

Var

MyObject: tMyClass;

AString: string;

Begin

AString: = tMyClass.GetSize; // обращение к методу класса до создания объекта этого класса

MyObject: = tMyObject.Create;

AString: = MyObject.GetSize;

end.

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

Некоторые важные методы класса tObject

Метод Описание
class functionClassName: ShortString; Возвращает имя класса.
class functionClassParent: tClass; Возвращает указатель на родительский класс (для tObject возвращает nil).
class functionInstanceSize: Longint; Возвращает размер экземпляра класса.
functionClassType: tClass; Возвращает указатель на класс данного объекта.
constructor Create; Конструктор. Создает новый экземпляр класса.
destructor Destroy; virtual; Деструктор. Уничтожает экземпляр класса.
procedure Free; Используется вместо деструктора. Проверяет передаваемый деструктору указатель на экземпляр.

В следующем примере переменная ObjectRef является указателем на класс; он по очереди указывает на tObject и tMyObject (то есть на их внутренние структуры). Посредством этой переменной-указателя вызывается функция класса ClassName:

Type

tMyObject = class;

tMyObjClass = class of tObject;

Var

ObjectRef: tMyObjClass;

s: String;

Begin

ObjectRef: = tObject;

s: = ObjectRef.ClassName; // s: ='TObject'

ObjectRef: = tMyObject;

s: = ObjectRef.ClassName; // s: ='TMyObject'

end.

Удобно использовать метод Free для уничтожения экземпляра класса, при этом необходимо переопределить метод Destroy, в котором описываются все действия по уничтожению экземпляра, например освобождение выделенной памяти или закрытие файлов. При вызове метода Free проверяется передаваемый указатель на экземпляр, после чего вызывается метод Destroy и происходит уничтожение объекта.

Контрольные вопросы

1. В чем сущность объектно-ориентированного программирования?

2. Что означает принцип инкапсуляции в ООП?

3. В чем заключается свойство наследования в ООП?

4. Что такое класс и как он описывается?

5. Как описываются и реализуются методы класса?

6. Что такое экземпляр класса?

7. Как осуществляется обращение к методам объектов?

8. Каким образом осуществляется перекрытие методов?

9. В каких случаях метод наследуется, а в каких перекрывается?

10. В чем состоит принцип полиморфизма в ООП?

11. В чем различие статических и виртуальных методов с точки зрения их выполнения?

12. В чем сущность позднего связывания?

13. С какой целью методы следует объявлять виртуальными?

14. Какие методы следует объявлять виртуальными?

15. Какие функции выполняет конструктор?

16. Какие функции выполняет деструктор?

17. Что представляет собой информация о классе?

18. Каков внутренний формат экземпляра объектного типа?

19. Какова структура таблицы виртуальных методов?

20. Какова структура таблицы динамических методов?

Лабораторная работа 3.
Создание класса – списка






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.