Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_answers (1).docx
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
2.9 Mб
Скачать

3. Понятие свойства. Методы получения и установки значений свойств. Свойства-массивы (в некоторых языках программирования). Индексаторы (в некоторых языках программирования).

Понятие свойства

Свойство – виртуальное поле, физически не существующее (не занимают места в памяти). При чтении/записи отображается на поле, метод.

Свойства не добавляют прикладных расходов. Они – синтаксический сахар.

В Java, C++, Oberon свойства отсутствуют, в C#, Delphi – присутствуют. Свойства нужны для построения пользовательских интерфейсов.

При работе с объектом свойства выглядят как поля: они принимают значения и участвуют в выражениях.

В отличие от полей свойства не имеют адреса в памяти, поэтому к ним запрещено применять операцию @. Как следствие, их нельзя передавать в var- и out-параметрах процедур и функций.

Методы получения и установки значений свойств

type

TDelimitedReader = class

...

FActive: Boolean;

...

procedure SetActive(const AActive: Boolean);

property Active: Boolean read FActive write SetActive; // Свойство

end;

Ключевые слова read и write называются спецификаторами доступа. После read указывается поле или метод, к которому происходит обращение при чтении значения свойства, а после write –поле или метод, к которому происходит обращение при записи значения свойства. Чтобы имена свойств не совпадали с именами полей, последние принято писать с буквы F(от англ. Field).

Обращение к свойствам выглядит в программе как обращение к полям:

var

Reader: TDelimitedReader;

IsOpen: Boolean;

...

Reader.Active := True; // ЭквивалентноReader.SetActive(True);

IsOpen := Reader.Active; // ЭквивалентноIsOpen := Reader.FActive

Если один из спецификаторов доступа опущен, то значение свойства можно либо только читать, либо только записывать.

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

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

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

Свойства-массивы

Кроме обычных свойств в объектах существуют свойства-массивы (array properties).

Свойство–массив – это индексированное множество значений.

type

TDelimitedReader = class

...

FItems: array of string;

...

function GetItem(Index: Integer): string;

...

property Items[Index: Integer]: string read GetItem;

end;

function TDelimitedReader.GetItem(Index: Integer): string;

begin

Result := FItems[Index];

end;

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

Основная выгода от применения свойства-массива – возможность выполнения итераций с помощью цикла for, например:

for i := 0 toReader.ItemCount - 1 do

Writeln(Reader.Items[i]);

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

Свойства-массивы имеют два важных отличия от обычных массивов:

1) их индексы не ограничиваются диапазоном;

2) индексы могут иметь любой тип данных, а не только Integer:

Reader.Items['FirstName'] := 'Alexander';

Операции целиком со всем свойством-массивом запрещены; разрешены операции только с его элементами.

Свойство-массив можно сделать основным свойством объектов данного класса. Для этого в описание свойства добавляется слово default:

type

TDelimitedReader = class

...

Property Items[Index: Integer]: string readGetItem; default;

...

end;

Такое объявление свойства Items позволяет рассматривать сам объект класса TDelimitedReader как массив и опускать имя свойства-массива при обращении к нему из программы, например:

For i := 0 to Reader.ItemCount - 1 do

Writeln(Reader [i]);

Следует помнить, что только свойства-массивы могут быть основными свойствами объектов; для обычных свойств это недопустимо.

Один и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод чтения (записи) первым параметром.

type

TDelimitedReader = class

...

property FirstName : string index 0 read GetItem;

property LastName : string index 1 read GetItem;

property Phone : string index 2 read GetItem;

end;

Обращения к свойствам FirstName, LastName и Phone заменяются компилятором на вызовы одного и того же метода GetItem, но с разными значениями параметра Index:

var

Reader: TDelimitedReader;

...

Writeln(Reader.FirstName); // Эквивалентно: Writeln(Reader.GetItem(0));

Writeln(Reader.LastName); // Эквивалентно: Writeln(Reader.GetItem(1));

Writeln(Reader.Phone); // Эквивалентно: Writeln(Reader.GetItem(2));

Индексаторы

C#

Индексаторы очень похожи на свойства. Формально синтаксис определения индексатора таков:

[атрибуты] [модификаторы] тип this [список-формальных-параметров] { set get } Или, если это индексатор в интерфейсе, таков: [атрибуты] [модификаторы] тип интерфейс.this [список формальных параметров] { set get }

АТРИБУТЫ — дополнительная информация об индексаторе. Наиболее значимым для индексаторов является атрибут Name. Задание Name позволяет дать имя индексатору для того, чтобы его могли использовать другие языки, не поддерживающие индексаторы. По умолчанию все индексаторы вашего класса имеют Name, равный Item;

МОДИФИКАТОРЫ — модификаторы доступа и директивы. К индексатору применимы почти все стандартные директивы С#. Он может быть скрыт, перегружен, сделан виртуальным, но есть одно исключение, индексатор не может быть static;

СПИСОК ФОРМАЛЬНЫХ ПАРАМЕТРОВ — указывает параметры, посредством которых осуществляется индексация. Передается в get и set, которые используются в индексаторе так же, как в свойствах, get применяется для вычисления индексатора по заданному списку формальных параметров, a set – для изменения индексатора, set получает в качестве дополнительного параметра value того же типа, что и индексатор.

Следует отметить, что доступ к индексатору осуществляется посредством сигнатуры, в отличие от свойства, доступ к которому осуществляется посредством имени. Сигнатурой индексатора считаются число и тип формальных параметров. Тип самого индексатора и имена параметров в сигнатуру не входят. Естественно, в классе не может быть двух индексаторов с одинаковой сигнатурой. К тому же, индексатор не считается переменной и не может быть передан в качестве ref или out параметра.

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

using System;

using System.Collections;

using System.Collections.Generic;

namespace IndexatorSimple

{

class Program

{

class Person

{

public string Name { get; set; }

public string Surname { get; set; }

public int Age { get; set; }

public ArrayList arPeople = new ArrayList();

public Person this[int index]

{

get { return (Person)arPeople[index]; }

set { arPeople.Insert(index, value); }

}

}

static void Main(string[] args)

{

Person person = new Person();

person[0] = new Person { Name = "John", Surname = "Lenon", Age = 53 };

person[1] = new Person { Name = "Ashly", Surname = "Menoton", Age = 27 };

Person p1 = new Person { Name = "Pol", Surname = "Sarmey", Age = 18 };

person[2] = p1;

for (int i = 0; i < 3; i++)

{

Console.WriteLine("{0} {1} {2}", person[i].Name, person[i].Surname,

person[i].Age);

}

Console.ReadLine();

}

}

}

Сначала определяется класс Person в котором объявлены три свойства Name, Surname и Age, потом объявляется список arPeople типа ArrayList. Следующий член класса и есть индексатор, который определен с ключевым словом this и квадратными скобками

public Person this[int index].

Метод принимает единственный параметр позиции - index. Как вы уже видите реализация индексатора такая же, как у свойств.

В Main() объявляется объект person, потом с помощью индексатора добавляются члены к этому объекту. Вывод результата в консоль такой.

John Lenon 53

Ashly Menoton 27

Pol Sarmey 18

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

using System;

using System.Collections;

using System.Collections.Generic;

namespace IndexatorSimple

{

class Program

{

class Person

{

public string Name { get; set; }

public string Surname { get; set; }

public int Age { get; set; }

public Dictionary arPeople = new Dictionary();

public Person this[string name]

{

get { return arPeople[name]; }

set { arPeople[name] = value; }

}

}

static void Main(string[] args)

{

Person person = new Person();

person["John"] = new Person {Name="John", Surname = "Lenon", Age = 53 };

person["Ashly"] = new Person {Name="Ashly", Surname = "Menoton",

Age = 27 };

Person p1 = new Person {Name="Pol", Surname = "Sarmey", Age = 18 };

person["Pol"] = p1;

string[] names = new string[] { "John", "Ashly", "Pol" };

for (int i = 0; i < names.Length; i++)

{

Console.WriteLine("{0} {1} {2}", person[names[i]].Name,

person[names[i]].Surname, person[names[i]].Age);

}

Console.ReadLine();

}

}

}

4. Расширение класса путем создания производного класса. Термин «наследование». Существование «прародителя» всех классов. Перекрытие элементов класса в производных классах. Совместимость объектов различных классов. Контроль и преобразование типов. Информация о типе времени выполнения программы.

Расширение класса путем создания производного класса. Термин «наследование».

Наследование – расширение типа.

Любая процедура базового класса м.б. вызвана производным от него классом.

В Delphi отсутствует множественное наследование.

В С++ отсутствует общий базовый класс.

В С++ память инициализируется 0 только в отладочном режиме.

В конце деструктора, вы должны вызвать Inherited для вызова родительского деструктора.

Для классов, связанных наследованием, существует расширенная совместимость типов (совместимы по представлению в памяти).

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

Иерархия – набор классов, связанных отношением наследования. Наследование призвано отобразить такое свойство реального мира, как иерархичность.

Порожденный класс (потомок) – класс, который наследует атрибуты другого класса.

Базовый класс (предок) – класс, от которого происходит наследование.

Примером иерархии классов является библиотека VCL; с ее помощью в среде Delphi обеспечивается разработка GUI-приложений.

Существование «прародителя» всех классов

В языке Delphi существует предопределенный класс TObject, который служит неявным предком тех классов, для которых предок не указан. Это означает, что объявление

type type

TTextReader = class ~ TTextReader = class(TObject)

... …

end; end;

Класс TObject – корень любой иерархии классов. Он содержит ряд методов, которые по наследству передаются всем остальным классам. Среди них конструктор Create, деструктор Destroy, метод Free и некоторые другие методы.

TObject = class

constructor Create;

procedure Free;

class function InitInstance(Instance: Pointer): TObject;

procedure CleanupInstance;

function ClassType: TClass;

class function ClassName: ShortString;

class function ClassNameIs(constName: string): Boolean;

class function ClassParent: TClass;

class function ClassInfo: Pointer;

class function InstanceSize: Longint;

class function InheritsFrom(AClass: TClass): Boolean;

class function MethodAddress(constName: ShortString): Pointer;

class function MethodName(Address: Pointer): ShortString;

function FieldAddress(constName: ShortString): Pointer;

function GetInterface(constIID: TGUID; out Obj): Boolean;

class function GetInterfaceEntry(constIID: TGUID): PInterfaceEntry;

class function GetInterfaceTable: PInterfaceTable;

function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; virtual;

procedure AfterConstruction; virtual;

procedure BeforeDestruction; virtual;

procedure Dispatch(var Message); virtual;

procedure DefaultHandler(var Message); virtual;

class function NewInstance: TObject; virtual;

procedure FreeInstance; virtual;

destructor Destroy; virtual;

end;

Create – стандартный конструктор.

Free – уничтожает объект: вызывает стандартный деструктор Destroy, если значение псевдопеременной Self не равно nil.

InitInstance(Instance: Pointer): TObject – при создании объекта инициализирует нулями выделенную память. На практике нет необходимости вызывать этот метод явно.

CleanupInstance ­– освобождает память, занимаемую полями с типом string, Variant, динамический массив и интерфейс. На практике нет необходимости вызывать этот метод явно.

ClassType: TClass – возвращает описатель класса (метакласс).

ClassName: ShortString – возвращает имя класса.

ClassNameIs(const Name: string): Boolean – проверяет, является ли заданная строка именем класса.

ClassParent: TClass – возвращает описатель базового класса.

ClassInfo: Pointer – возвращает указатель на соответствующую классу таблицу RTTI (от англ. Runtime Type Information). Таблица RTTI используется для проверки типов данных на этапе выполнения программы.

InstanceSize: Longint – возвращает количество байт, необходимых для хранения в памяти одного объекта соответствующего класса. Заметим, что значение, возвращаемое этим методом и значение, возвращаемое функцией SizeOf при передаче ей в качестве аргумента объектной переменной – это разные значения. Функция SizeOf всегда возвращает значение 4 (SizeOf(Pointer)), поскольку объектная переменная – это ни что иное как ссылка на данные объекта в памяти. Значение InstanceSize – это размер этих данных, а не размер объектной переменной.

InheritsFrom(AClass: TClass): Boolean – проверяет, является ли класс AClass базовым классом.

MethodAddress(const Name: ShortString): Pointer – возвращает адрес published-метода, имя которого задается параметром Name.

MethodName(Address: Pointer): ShortString – возвращает имя published-метода по заданному адресу.

FieldAddress(const Name: ShortString): Pointer – возвращает адрес published-поля, имя которого задается параметром Name.

GetInterface(const IID: TGUID; out Obj): Boolean – возвращает ссылку на интерфейс через параметр Obj; идентификатор интерфейса задается параметром IID.

GetInterfaceEntry(const IID: TGUID): PInterfaceEntry – возвращает информацию об интерфейсе, который реализуется классом. Идентификатор интерфейса задается параметром IID.

GetInterfaceTable: PInterfaceTable – возвращает указатель на таблицу с информацией обо всех интерфейсах, реализуемых классом.

AfterConstruction – автоматически вызывается после создания объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия уже после создания объекта (для этого его необходимо переопределить в производных классах).

BeforeDestruction ­– автоматически вызывается перед уничтожением объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия непосредственно перед уничтожением объекта (для этого его необходимо переопределить в производных классах).

Dispatch(var Message) – служит для вызова методов, объявленных с ключевым словом message.

DefaultHandler(var Message) – вызывается методом Dispatch в том случае, если метод, соответствующий сообщению Message, не был найден.

NewInstance: TObject – вызывается при создании объекта для выделения динамической памяти, чтобы разместить в ней данные объекта. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.

FreeInstance – вызывается при уничтожении объекта для освобождения занятой объектомдинамической памяти. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.

Destroy – стандартный деструктор.

Перекрытие элементов класса в производных классах

В механизме наследования можно условно выделить три основных момента:

ƒ наследование полей;

ƒ наследование свойств;

ƒ наследование методов.

Свойство базового класса можно перекрыть (override) в производном классе, например, чтобы добавить ему новый атрибут доступа или связать с другим полем или методом.

В наследнике можно вызвать перекрытый метод предка, указав перед именем метода зарезервированное слово inherited. Когда метод предка полностью совпадает с методом потомка по формату заголовка, то можно использовать более короткую запись.

Главные черты классов:

1) Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства;

2) Способность наследовать поля, методы и свойства других классов.

При реализации класса TTextReader ничего не известно о том, как хранятся элементы в считываемых строках, поэтому метод ParseLine ничего не делает. Очевидно, что создавать объекты класса TTextReader не имеет смысла. Для чего тогда нужен классTTextReader?

Ответ: чтобы на его основе определить (породить) два других класса— TDelimitedReader и TFixedReader, предназначенных для чтения данных в конкретных форматах.

Класс TDelimitedReader является прямым потомком класса TTextReader. Если от TDelimitedReader породить новый класс, то он тоже будет потомком класса TTextReader, но уже не прямым.

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

Наследование свойств и методов имеет свои особенности. Свойство базового класса можно перекрыть (от англ. override) в производном классе, например, чтобы добавить ему новый атрибут доступа или связать с другим полем или методом.

Метод базового класса тоже можно перекрыть в производном классе, например чтобы изменить логику его работы.

сonstructor TDelimitedReader.Create(const FileName: string; const ADelimiter: Char = ';');

begin

inherited Create(FileName);

FDelimiter := ADelimiter;

end;

Как видно из примера, в наследнике можно вызвать перекрытый метод предка, указав перед

именем метода зарезервированное слово inherited. Когда метод предка полностью совпадает

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

Destructor TTextReader.Destroy;

begin

Active := False;

inherited; // Эквивалентно: inherited Destroy;

end;

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

Совместимость объектов различных классов

Для классов, связанных отношением наследования, вводится новое правило совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно.

var

Reader: TTextReader;

...

Reader :=TDelimitedReader.Create('MyData.del', ';');

Объектная переменная Reader формально имеет тип TTextReader, а фактически связана с экземпляром класса TDelimitedReader.

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

Контроль и преобразование типов

В языке Delphi существуют операторы is и as, с помощью которых выполняется соответственно проверка на тип (typechecking) и преобразование к типу (typecasting).

Например, чтобы выяснить, принадлежит ли некоторый объект Obj к классу TTextReader или его наследнику, следует использовать оператор is:

var

Obj: TObject;

...

If Obj is TTextReader then ...

Для преобразования объекта к нужному типу используется оператор as, например

With Obj as TTextReader do

Active := False;

Стоит отметить, что для объектов применим и обычный способ приведения типа:

With TTextReader(Obj) do

Active := False;

Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее исключительную ситуацию) при выполнении программы (run-time error), если реальный экземпляр объекта Obj не совместим с классом TTextReader. Ошибку приведения типа можно обработать и таким образом избежать досрочного завершения программы.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]