Перейти в начало страницы

Здравствуйте, гость ( Авторизация | Регистрация )

Gameru.net останавливает работу в связи с вторжением армии РФ в Украину. Следите за дальнейшими анонсами.
Support Gameru!
 
Тема закрытаНачать новую тему
> Памятка программиста, Стиль оформления кода,советы,компоненты
Neo][
сообщение 28.08.2006, 01:10
Сообщение #1


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


Для совместной работы над проетом нам необходио придерживаться единого стиля оформления кода.

Изложу некоторые принципы.

1. Структурирование кода:

Не структурированный код
Цитата
procedure TForm1.Button1Click(Sender: TObject);
const tt=12;
var i:integer; b:boolean;
begin
i:=1212*12;
if i>1000 then
begin
b:=true;
i:=1212*2;
if i<12 then
b:=false;
end
else
b:=false;
end;


Структурированный код
Цитата
procedure TForm1.Button1Click(Sender: TObject);
const
  tt = 12;
var
  i: integer;
  b: boolean;
begin
  i := 1212 * 12;
  if i > 1000 then
  begin
    b := true;
    i := 1212 * 2;
    if i < 12 then
      b := false;
  end
  else
    b := false;
end;


Какой легче понять?

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

Предлагаю делать так
Цитата
_s_FileName: String;


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

Цитата
Сокращения для указания типов.
integer      i
string      s
array        a
const        ct
record      rd
real        rl
double      dl
word        wd
dword        dwd


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

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

Цитата
bSaveTexture
lbListOfStuff


3. Комментарии и поясняющие записи.

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

Цитата
//////////////////////////////////////////////////////
// Описание: Расчёт расстояния между двумя точками.
// Параметры:
// _rd_FirstPoint - координаты  1 точки
// _rd_SecondPoind - координаты 2 точки
///////////////////////////////////////////////////////

procedure CalcLenght(_rd_FirstPoint, _rd_SecondPoind: CoordinatesRecord)
begin

end


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

Согласитесь раньше вы не видели процедуры CalcLenght, но по комментарию вы сразу поняли:
- Она считает расстояние между двумя точками.
- В качестве параметров ей передаются координаты точки имеющие тип: запись(record), надеюсь поняли по префиксу _rd_


--------------------
Перейти в начало страницы
 
Neo][
сообщение 28.08.2006, 01:25
Сообщение #2


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


Скачать:
Эксперт для Delphi, который поможет в структурировании кода
]]>DelForEx]]>

Модифицированная библиотека ODE
]]>oxGLODE (~3.3 mb)]]>

GLScene
]]>GLScene 1.0.0.0714]]>

Содержание темы:
1. Правила оформления кода.
2. Установка компонента oxGLODE.
3. Глава из книги, посвящённая свойствам класса.
4. Статья по ООП.

Сообщение отредактировал Neo][ - 03.11.2006, 21:10


--------------------
Перейти в начало страницы
 
Neo][
сообщение 31.08.2006, 23:28
Сообщение #3


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


Инструкция по установке компонента oxGLODE.


Опишу способ установки по-русски, но опять же с опорой на оригинал.

1. Распаковываешь скачанный файл, допустим в папку ODE.
2. Идешь в ODE\ODE_units&dlls
3. Запускаешь файл InstalloxDLL.bat, чтобы скопировать библиотеку в System32, т.е. установить её.
4. Допустим папка со сценой у тебя называется GL. Копируешь файлы oxODEGL.pas и oxodeimport.pas(из папки ODE\ODE_units&dlls) в папку GL\Source.
5. Идёшь в папку ODE\oxGLODE_Component.
6. Копируешь файлы GLOxOde.pas и GLVectorFileObjects.pas папку GL\Source
7. Идёшь в папку GL\Source\DesignTime.
8. Открываешь файл GLSceneRegister.pas.
9. В раздел uses добавляешь модуль GLOxOde.
10. Пролистываешь этот файл в конец и находишь такую строчку

Цитата
RegisterSceneObject(TGLTrail, 'GLTrail', glsOCSpecialObjects);


11. После неё(строчки) вставляешь следующий код:

Цитата
RegisterSceneObject(TGLOXDummyCube, 'GLOXDummyCube', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDummyBone, 'GLOXDummyBone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXOdeEngine, 'GLOXOdeEngine', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaBall, 'GLOXStaBall', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCylinder, 'GLOXStaCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCCylinder, 'GLOXStaCCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaBox, 'GLOXStaBox', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaMesh, 'GLOXStaMesh', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCone, 'GLOXStaCone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXZStaTerrain, 'GLOXZStaTerrain', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynMesh , 'GLOXDynMesh ', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynBall, 'GLOXDynBall', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynBox, 'GLOXDynBox', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCylinder, 'GLOXDynCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCCylinder, 'GLOXDynCCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCCylinder, 'GLOXDynCone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCar, 'GLOXDynCar', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDummyRagDoll, 'GLOXDummyRagDoll', glsOCSpecialObjects);
RegisterSceneObject(TGLOXSkinRagDoll, 'GLOXSkinRagDoll', glsOCSpecialObjects);
RegisterSceneObject(TGLOXAMotor3Axis, 'GLOXAMotor3Axis', glsOCSpecialObjects);


Т.е. должно получиться:

Цитата
RegisterSceneObject(TGLTrail, 'GLTrail', glsOCSpecialObjects);
//////////////////////////////////////////////////////////////////////////
// GLOxOde
RegisterSceneObject(TGLOXDummyCube, 'GLOXDummyCube', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDummyBone, 'GLOXDummyBone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXOdeEngine, 'GLOXOdeEngine', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaBall, 'GLOXStaBall', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCylinder, 'GLOXStaCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCCylinder, 'GLOXStaCCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaBox, 'GLOXStaBox', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaMesh, 'GLOXStaMesh', glsOCSpecialObjects);
RegisterSceneObject(TGLOXStaCone, 'GLOXStaCone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXZStaTerrain, 'GLOXZStaTerrain', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynMesh , 'GLOXDynMesh ', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynBall, 'GLOXDynBall', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynBox, 'GLOXDynBox', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCylinder, 'GLOXDynCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCCylinder, 'GLOXDynCCylinder', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCCylinder, 'GLOXDynCone', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDynCar, 'GLOXDynCar', glsOCSpecialObjects);
RegisterSceneObject(TGLOXDummyRagDoll, 'GLOXDummyRagDoll', glsOCSpecialObjects);
RegisterSceneObject(TGLOXSkinRagDoll, 'GLOXSkinRagDoll', glsOCSpecialObjects);
RegisterSceneObject(TGLOXAMotor3Axis, 'GLOXAMotor3Axis', glsOCSpecialObjects);
//////////////////////////////////////////////////////////////////////////
{$endif}


12. Сохраняешь файл.
13. Идешь в папку GL\DelphiX (где X версия твоего Delphi)
14. Открываешь файл GLSceneX.dpk (где X версия твоего Delphi)
15. Нажимаешь Compile.


--------------------
Перейти в начало страницы
 
Neo][
сообщение 01.10.2006, 15:58
Сообщение #4


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


Глава из книги "Программирование в Delphi 7. Петр Дарахвелидзе, Евгений Марков", посвящённая свойствам классов.

Поля, свойства и методы.

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

Итак, поля предназначены для использования внутри класса. Однако класс должен каким-либо образом взаимодействовать с другими классами или программными элементами приложения. В подавляющем большинстве случаев класс должен выполнить с некоторыми данными определенные действия и представить результат.
Для получения и передачи данных в классе применяются свойства. Для объявления свойств в классе используется зарезервированное слово property.
Свойства представляют собой атрибуты, которые составляют индивидуальность объекта и помогают описать его. Например, обычная кнопка в окне приложения обладает такими свойствами, как цвет, размеры, положение. Для экземпляра класса "кнопка" значения этих атрибутов задаются при помощи свойств — специальных переменных, определяемых ключевым словом property. Цвет может задаваться свойством color, размеры — свойствами Width И Height И Т. Д.
Так как свойство обеспечивает обмен данными с внешней средой, то для доступа к его значению используются специальные методы класса. Поэтому обычно свойство определяется тремя элементами: полем и двумя методами, которые осуществляют его чтение/запись:
Код
type
TAnObject = class(TObject)
function GetColor: TSomeType;
procedure SetColor(ANewValue: TSomeType);
property AColor: TSomeType read GetColor write SetColor;
end;

В данном примере доступ к значению свойства AColor осуществляется через вызовы методов GetColor и SetColor. Однако в обращении к этим методам в явном виде нет необходимости: достаточно написать:
Код
AnObject.AColor := AValue; AVariable := AnObject.AColor;

и компилятор самостоятельно оттранслирует обращение к свойству AColor в вызовы методов GetColor или Setcolor. To есть внешне свойство выглядит в точности как обычное поле, но за всяким обращением к нему могут стоять нужные вам действия. Например, если у вас есть объект, представляющий собой квадрат на экране, и его свойству "цвет" вы присваиваете значение "белый", то произойдет немедленная перерисовка, приводящая реальный цвет на экране в соответствие со значением свойства. Выполнение этой операции осуществляется методом, который связан с установкой значения свойства "цвет".
В методах, входящих в состав свойств, может осуществляться проверка устанавливаемой величины на попадание в допустимый диапазон значений и вызов других процедур, зависящих от вносимых изменений. Если же потребности в специальных процедурах чтения и/или записи нет, можно вместо имен методов применять имена полей. Рассмотрим следующую конструкцию:
Код
TPropObject = class(TObject)
FValue: TSomeType;
procedure DoSomething;
function Correct(AValue: Integer):boolean;
procedure SetValue(NewValue: Integer);
property AValue: Integer read FValue write SetValue;
end;

procedure TPropObject.SetValue(NewValue: Integer);
begin
if (NewValue<>FValue) and Correct(NewValue) then FValue := NewValue;
DoSomething;
end;

В этом примере чтение значения свойства AValue означает просто чтение поля FValue. Зато при присвоении значения внутри SetValue вызывается сразу два метода.
Если свойство должно только читаться или записываться, в его описании может присутствовать соответствующий метод:
Код
type
TAnObject = class(TObject)
property AProperty: TSomeType read GetValue;
end;

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

Для присвоения свойству значения по умолчанию используется ключевое слово default:
Код
property Visible: boolean read FVisible write SetVisible default True;

Это означает, что при запуске программы свойство будет установлено компилятором в True.
Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:
Код
property APoints[Index : Integer]:TPoint read GetPoint write SetPoint;

На самом деле в классе может и не быть соответствующего поля — массива. Напомним, что вся обработка обращений к внутренним структурам класса может быть замаскирована.
Для векторного свойства необходимо описать не только тип элементов массива, но также имя и тип индекса. После ключевых слов read и write в этом случае должны стоять имена методов — использование здесь полей массивов недопустимо. Метод, читающий значение векторного свойства, должен быть описан как функция, возвращающая значение того же типа, что и элементы свойства, и имеющая единственный параметр того же типа и с тем же именем, что и индекс свойства:
Код
function GetPoint(Index:Integer):TPoint;

Аналогично, метод, помещающий значения в такое свойство, должен первым параметром иметь индекс, а вторым — переменную нужного типа (которая может быть передана как по ссылке, так и по значению):
Код
procedure SetPoint(Index:Integer; NewPoint:TPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки TList, наборы строк TStrings) "построены" вокруг основного векторного свойства. Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются как бы вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано с ключевым словом default:
Код
type
TMyObject = class;
property Strings[Index: Integer]: string read Get write Put; default;
end;

Если у объекта есть такое свойство, то можно его не упоминать, а ставить индекс в квадратных скобках сразу после имени объекта:
Код
var AMyObject:   TMyObject;
begin
...
AMYObject.Strings[1] := 'First'; {первый способ}
AMYObject[2] := 'Second'; {второй способ}


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

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


--------------------
Перейти в начало страницы
 
Neo][
сообщение 03.11.2006, 21:01
Сообщение #5


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


Объектно-ориентированное программирование (ООП)

Объекты – это крупнейшее достижение в современной технологии программирования. Смеем утверждать, что изобретение и практическая реализация объектов являются подвигом человеческого гения. Это не пустые слова: объекты позволили строить программу не из чудовищных по сложности процедур и функций, а из кирпичиков-объектов, заранее наделенных нужными свойствами. Самое приятное в этом то, что внутренняя сложность объектов скрыта от программиста, он просто пользуется готовым строительным материалом.
Сейчас преимущества использования объектов очевидны для всех. Однако так было всегда. Сначала старая гвардия не поняла и не приняла объекты, поэтому они почти лет потихоньку развивались в различных языках, первыми из которых были Simula 67 и Smalltalk 72. Постепенно объектно-ориентированный подход нашел себе место и в более мощных языках: С++, Turbo Pascal 6.0/7.0, Modula, ADA и множестве других. Блестящим примером реализации объектов была библиотека Turbo Vision, предназначенная для построения пользовательского интерфейса DOS-программ.
Полную победу объекты одержали после воцарения Windows: теперь без них в программировании просто не обойтись. Delphi – не исключение, она в своей основе является объектно-ориентированной и говорить о программировании без объектов про бессмысленно. Чтобы вы не рылись в других книгах, собирая информацию по крохам, мы не поленились и собрали в этой главе все, что нужно знать об объектах Delphi. Для новичка важнейшее здесь: инкапсуляция, наследование, полиморфизм, остальное можно просто просмотреть и возвращаться к материалу по мере накопления опыта. Профессионалу полезно прочитать внимательно все от начала до конца. Так что давайте засучим рукава и приступим к делу.

КРАЕУГОЛЬНЫЕ КАМНИ ООП

ФОРМУЛА ОБЪЕКТА
Авторы надеются, что читатель помнит кое-что из второй главы и такие понятия, как тип данных, процедура, функция, запись для него не в новинку. Это прекрасно. Та вот, в конце 60-х годов кому-то пришло в голову объединить эти понятия и то, что получилось, назвать объектом. Рассмотрение данных в неразрывной связи с методами их обработки позволило вывести формулу объекта:
Объект = Данные + Операции

На основании этой формулы была разработана методология объектно-ориентированного программирования (ООП).

ПРИРОДА ОБЪЕКТА
Об объектах можно думать как о полезных существах, которые «живут» в вашей программе и коллективно решают некоторую прикладную задачу. Вы, как Демиург, лепите этих существ, распределяете между ними обязанности и устанавливаете правила их взаимодействия.
В общем случае каждый объект «помнит» необходимую информацию, «умеет» выполнять некоторый набор действий и характеризуется набором свойств. То, что объект «помнит», хранится в его полях. То, что объект «умеет делать», реализуется в виде его внутренних процедур и функций, называемых методами. Свойства объектов аналогичны свойствам, которые мы наблюдаем у обычных предметов. Значения свойств можно устанавливать и читать. Программно свойства реализуются через поля и методы.
Например, объект «кнопка» имеет свойство «цвет». Значение цвета кнопка запоминает в одном из своих полей. При изменении значения свойства «цвет» вызывается метод, который перерисовывает кнопку.
Кстати, этот пример позволяет сделать важный вывод: свойства имеют первостепенное значение для программиста, использующего объект. Чтобы понять суть и назначение объекта, вы обязательно должны знать его свойства, иногда — методы, очень редко — поля (объект и сам знает, что с ними делать).

ОБЪЕКТЫ И КОМПОНЕНТЫ
Когда прикладные программы создавались для операционной системы MS-DOS и были консольно-ориентированными, объекты казались пределом развития программирования, поскольку были идеальным средством разбиения сложных задач на простые подзадачи. Однако с появлением графических систем, в частности Windows, программирование пользовательского интерфейса резко усложнилось. Программист в какой-то мере стал дизайнером, а визуальная компоновка и увязка элементов пользовательского интерфейса (кнопок, меток, строк редактора) начали отнимать основную часть времени. И тогда программистам пришла в голову идея визуализировать объекты, объединив программную часть объекта с его видимым представлением на экране дисплея в одно целое. То, что получилось в результате, было названо компонентом.
Компоненты в Delphi — это особые объекты, которые являются строительными кирпичиками среды визуальной разработки и приспособлены к визуальной установке свойств. Чтобы превратить объект в компонент, первый разрабатывается по определенным правилам, а затем помещается в Палитру Компонентов. Конструируя приложение, вы берете компоненты из Палитры Компонентов, располагаете на форме и устанавливаете их свойства в окне Инспектора Объектов. Внешне все выглядит просто, но чтобы достичь такой простоты, потребовалось создать механизмы, обеспечивающие функционирование объектов-компонентов уже на этапе проектирования приложения! Все это было придумано и блестяще реализовано в среде Delphi. Таким образом, компонентный подход значительно упростил создание приложений с графическим пользовательским интерфейсом и дал толчок развитию новой индустрии компонентов.
В данной главе мы рассмотрим лишь вопросы создания и использования объектов, Чуть позже мы научим вас превращать объекты в компоненты.

КЛАССЫ ОБЪЕКТОВ
Каждый объект всегда принадлежит некоторому классу. Класс — это обобщенное (абстрактное) описание множества однотипных объектов. Объекты являются конкретными представителями своего класса, их принято называть экземплярами класса. Например, класс СОБАКИ — понятие абстрактное, а экземпляр этого класса МОЙ ПЕС БОБИК — понятие конкретное.

ТРИ КИТА ООП
Весь мир ООП держится на трех китах: инкапсуляции, наследовании и полиморфизме. Для начала о них надо иметь только самое общее представление.
Наблюдаемое в объектах объединение данных и операций в одно целое было обозначено термином инкапсуляция (первый кит ООП). Применение инкапсуляции сделало объекты похожими на маленькие программные модули и обеспечило сокрытие их внутреннего устройства. Для объектов появилось понятие интерфейса, что значительно повысило их надежность и целостность.
Второй кит ООП — наследование. Этот простой принцип означает, что если вы хотите создать новый класс, лишь немногим отличающийся от того, что уже существует, то нет необходимости в переписывании заново всех полей, методов и свойств. Вы объявляете, что новый класс является потомком (или дочерним классом) имеющегося класса, называемого предком (или родительским классом), и добавляете к нему новые поля, методы и свойства. Иными словами добавляется то, что нужно для перехода от общего к частному. Процесс порождения новых классов на основе других классов называется наследованием. Новые классы имеют как унаследованные признаки, так и, возможно, новые. Например, класс СОБАКИ унаследовал многие свойства своих предков — ВОЛКОВ.
Третий кит — это полиморфизм. Он означает, что в производных классах вы можете изменять работу уже существующих в базовом классе методов. При этом весь программный код, управляющий объектами родительского класса, пригоден для управления объектами дочернего класса без всякой модификации. Например, вы можете породить новый класс кнопок с рельефной надписью, переопределив метод отрисовки кнопки. Новую кнопку можно «подсунуть» вместо стандартной в какую-нибудь подпрограмму, вызывающую отрисовку кнопки. При этом подпрограмма «думает», что работает со стандартной кнопкой, но на самом деле кнопка принадлежит производному классу и отображается в новом стиле.
Пока достаточно самого поверхностного понимания всех приведенных выше понятий, ниже мы рассмотрим их подробнее и покажем, как они реализованы в Delphi.

КЛАССЫ
Delphi поддерживает две модели представления объектов — старую и новую. Старая модель существует лишь для совместимости с более ранними версиями компилятора, в частности с Borland Pascal 7.0, поэтому мы не будем ее рассматривать. Все, что сказано ниже, относится к новой модели представления объектов, более мощной и богатой по своим возможностям.
Для поддержки ООП в язык Object Pascal введены объектные типы данных, с помощью которых одновременно описываются данные и операции над ними. Объектные типы называют классами, а их экземпляры — объектами.
Классы объектов определяются в секции type глобального блока. Описание класса начинается словом class и заканчивается словом end. По форме объявления классы похожи на обычные записи, но помимо полей данных могут содержать объявления пользовательских процедур и функций. Такие процедуры и функции обобщенно называют методами, они предназначены для выполнения над объектами различных операций. Приведем пример объявления класса:
type
TDiskGauge = class { измеритель дискового пространства}
DriveLetter: Char; { буква дискового накопителя}
PercentCritical: Integer; { критический процент свободного пространства}
function GetPercentFree: Integer;
procedure CheckStatus;
end;


Заголовки методов, следующие за списком полей, играют роль предварительных (forward) объявлений. Программный код методов помещается ниже определения класса и будет приведен позже.
Класс обычно описывает сущность, моделируемую в программе. Например, класс TDiskGauge описывает измеритель дискового ресурса. Класс содержит два поля: DriveLetter — буква находящегося под наблюдением накопителя, и PercentCritical — процент свободного пространства на диске, с которым работает программа. Когда объем свободных ресурсов снижается до этого порога, пользователю выдается звуковое предупреждение. Функция GetPercentFree определена как метод работы над любым объектом класса TDiskGauge и возвращает процент свободного пространства на диске. Процедура CheckStatus служит для проверки состояния ресурса и выдачи звукового предупреждения.
Обратите внимание, что приведенное выше описание является не чем иным, как декларацией интерфейса для управления объектами класса TDiskGauge. Реализация методов GetPercentFree и CheckStatus отсутствует, но для создания и использования экземпляров класса она пока и не нужна. В этом как раз и состоит сила инкапсуляции, Которая делает объекты аналогичными программным модулям. Для использования модуля необходимо изучить лишь его интерфейсную часть, раздел реализации для этого изучать не требуется. Поэтому дальше от описания класса мы перейдем не к реализации методов, а к созданию на их основе объектов.

ОБЪЕКТЫ
Чтобы от описания класса перейти к объекту, следует выполнить соответствующее объявление в секции var:

var DiskGauge: TDiskGauge;

При работе с обычными типами данных этого объявления было бы достаточно для получения экземпляра типа. Однако объекты в Delphi являются динамическими данными, т.е. распределяются в «куче» (heap). Поэтому переменная DiskGauge — это просто ссылка на экземпляр объекта, которого физически еще не существует. Чтобы сконструировать объект класса TDiskGauge и связать с ним переменную DiskGauge, нужно в текст программы поместить следующий оператор (statement):
DiskGauge: = TDiskGauge.Create;


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

DiskGauge.DriveLetter: = 'С';
DiskGauge. PercentCritical: = 10;
DiskGauge.CheckStatus;

Кроме того, как и при работе с записями, допустимо использование оператора with, например:

with DiskGauge do begin
DriveLetter: = 'С';
PercentCritical: = 10;
CheckStatus;
end;

Если наступает время, когда объект становится не нужен в программе, он должен быть удален вызовом специального метода Destroy, например:

DiskGauge.Destroy;


Destroy — это так называемый деструктор объекта; он присутствует в классе наряду с конструктором и служит для удаления объекта из динамической памяти. После вызова деструктора переменная DiskGauge становится несвязанной и не должна использоваться для доступа к полям и методам уже несуществующего объекта. Чтобы отличать в программе связанные объектные переменные от несвязанных, последние следует инициализировать значением nil. Например, в следующем фрагменте обращение к деструктору Destroy выполняется только в том случае, если объект реально существует.
DiskGauge: = nil;
if DiskGauge <> nil then DiskGauge.Destroy;


Вызов деструктора для несуществующих объектов недопустим и при выполнении программы приведет к ошибке. Чтобы избавить программистов от лишних ошибок, в объекты ввели предопределенный метод Free, который следует вызывать вместо деструктора. Метод Free сам вызывает деструктор Destroy, но только в том случае, если значение объектной переменной не равно nil. Поэтому последнюю строчку в приведенном выше примере можно переписать следующим образом:
DiskGauge.Free;


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

var
DiskGaugel, DiskGauge2: TDiskGauge;
begin
{ Переменные DiskGauge1 и DiskGauge2 не связаны с объектом}
DiskGauge1: = TDiskGauge.Create; { Переменная DiskGauge1 связана с объектом, а DiskGauge2 — нет}
DiskGauge2: = DiskGauge1; { Обе переменные связаны с одним объектом}
DiskGauge2.Free; {Объект удален, переменные DiskGauge1 и DiskGauge2 с ним не связаны}
end;

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

TGaugeList = class; { предварительное объявление класса TGaugeList }

TDiskGauge = class Owner: TGaugeList;

end;

TGaugeList = class Gauges: array [0..2] of TDiskGauge;

end;


Первое объявление класса TGaugeList называется предварительным (от англ. forward). Оно необходимо для того, чтобы компилятор нормально воспринял объявление поля Owner в классе TDiskGauge.
Итак, вы уже имеете некоторое представление об объектах, перейдем теперь к вопросу реализации их методов.


--------------------
Перейти в начало страницы
 
Neo][
сообщение 03.11.2006, 21:04
Сообщение #6


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


МЕТОДЫ
Процедуры и функции, предназначенные для выполнения над объектами действий, называются методами. Предварительное объявление методов выполняется при описании класса в секции interface модуля, а их программный код записывается в секции implementation. Однако в отличие от обычных процедур и функций заголовки методов должны иметь уточненные имена, т.е. содержать наименование класса. Приведем, например, возможную реализацию методов в классе TDiskGauge:
function TDiskGauge.GetPercentFree: Integer; { uses SysUtils; }
var

Drive: Byte;

begin

Drive := Ord(DriveLetter) - Ord('A') + 1;
Result := DiskFree(Drive) * 100 div DiskSize(Drive);

end;

procedure TDiskGauge.CheckStatus; { uses Windows; }

begin

if GetPercentFree <= PercentCritical
then Beep;

end;


Обратите внимание, что внутри методов обращения к полям и другим методам выполняются как к обычным переменным и подпрограммам без уточнения экземпляра объекта. Такое упрощение достигается путем использования в пределах метода псевдопеременной Self (стандартный идентификатор). Физически Self представляет собой дополнительный неявный параметр, передаваемый в метод при вызове. Этот параметр и указывает экземпляр объекта, к которому данный метод применяется. Чтобы пояснить сказанное, перепишем метод CheckStatus, представив его в виде обычной процедуры:
procedure TDiskGauge_CheckStatus (Self: TDiskGauge);

begin
with Self do
if GetPercentFree <= PercentCritical then Beep;

end;


Согласитесь, что метод CheckStatus выглядит более предпочтительно, чем процедура TDiskGauge_CheckStatus.
Практика показывает, что псевдопеременная Self редко используется в явном виде. Ее необходимо применять только тогда, когда при написании метода может возникнуть какая-либо двусмысленность для компилятора, например при использовании одинаковых имен и для локальных переменных, и для полей объекта.
Если выполнить метод CheckStatus
DiskGauge.CheckStatus;


то произойдет проверка состояния дискового ресурса. При этом неявный параметр Self будет содержать значение переменной DiskGauge. Такой вызов реализуется обычными средствами процедурного программирования приблизительно так:
TDiskGauge_CheckStatus(DiskGauge) ;


КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ
Особой разновидностью методов являются конструкторы и деструкторы. Напомним, что конструкторы создают, а деструкторы — разрушают объекты. Создание объекта включает выделение памяти под экземпляр и инициализацию его полей, а разрушение — очистку полей и освобождение памяти.
Очевидно, что выполняемые при инициализации и деинициализации действия специфичны для каждого конкретного класса объектов. По этой причине Object Pascal позволяет переопределить стандартные конструктор Create и деструктор Destroy для выполнения любых полезных действий. Можно даже определить несколько конструкторов и деструкторов (имена им назначает сам программист), чтобы обеспечить различные способы создания и разрушения объектов.
Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированного слова procedure (или function) используются слова constructor и destructor:
type TDiskGauge = class DriveLetter: Char;

PercentCritical: Integer;
constructor Create;
destructor Destroy;

...

end;


Приведем их возможную реализацию:
constructor TDiskGauge.Create;
begin
DriveLetter := 'C';
PercentCritical := 10;
end;
destructor TDiskGauge.Destroy;
begin
{ Разрушение встроенных объектов и освобождение динамических данных}

end;


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

DiskGauge: = TDiskGauge.Create;


то выполняется следующая последовательность действий:
• в динамической памяти выделяется место для нового объекта;
• выделенная память заполняется нулями; в результате все числовые поля и поля порядкового типа приобретают нулевые значения, строковые поля становятся пустыми, а поля, содержащие указатели и объекты, получают значение nil;
• затем выполняются заданные программистом действия конструктора;
• ссылка на созданный объект возвращается в качестве значения конструктора; тип возвращаемого значения совпадает с типом класса, использованного при вызове (в нашем примере это тип TDiskGauge).

Если конструктор применяется к объекту
DiskGauge.Create;

то новый объект не создается, а происходит переинициализация полей существующего. В этом случае конструктор не возвращает никакого значения.
Деструктор уничтожает объект, к которому применяется:
DiskGauge.Destroy;

В результате:
• выполняется заданный программистом код деинициализации;
• освобождается занимаемая объектом динамическая память.

В теле деструктора обычно должны уничтожаться встроенные объекты и динамические данные, созданные конструктором.

СВОЙСТВА
ПОНЯТИЕ СВОЙСТВА
Помимо полей и методов в объектах существуют свойства. При работе с объектом свойства выглядят как поля: они принимают значения и участвуют в выражениях. Но в отличие от полей свойства не занимают места в памяти, а операции их чтения и записи ассоциируются с обычными полями или методами. Это позволяет создавать необходимые побочные эффекты при обращении к свойствам. Например, присваивание свойству Visible значения True вызовет отображение графического объекта на экране, а значения False — его исчезновение.
Объявление свойства выполняется с помощью зарезервированного слова property, например:
type

TDiskGauge = class FPercentCritical: Integer;

procedure SetPercentCritical (Percent: Integer);

property PercentCritical: Integer read FpercentCritical

write SetPercentCritical;

end;


После слова read указывается поле или метод, к которому происходит обращение при чтении значения свойства, а после слова write — поле или метод, к которому происходит обращение при записи значения свойства. Например, чтение свойства PercentCritical заменяется на чтение поля FPercentCritical, а установка свойства — на вызов метода SetPercentCritical. Чтобы имена свойств не совпадали с именами полей, последние принято писать с буквы F (от англ. field).
Атрибуты read и write называются спецификаторами доступа. Если один из них опущен, то значение свойства можно либо только читать (задан спецификатор read), либо только записывать (задан спецификатор write). В следующем примере объявлено свойство, значение которого можно только читать:
type
TDiskGauge = class
property PercentFree: Integer read GetPercentFree;
end;


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

DiskGauge: TDiskGauge;
A : Integer;
А := DiskGauge.PercentCritical;
{ эквивалентно А := DiskGauge.FPercentCritical;}
DiskGauge.PercentCritical := A + 10;
{ эквивалентно DiskGauge.SetPercentCritical(A + 10);}


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

МЕТОДЫ ПОЛУЧЕНИЯ И УСТАНОВКИ СВОЙСТВ
Методы чтения и записи свойств подчиняются определенным правилам. Метод чтения свойства — это всегда функция, возвращающая значение того же типа, что и тип свойства. Метод записи свойства — это обязательно процедура, принимающая параметр того же типа, что и тип свойства. В остальном это обычные методы объекта. Примерами методов чтения и записи свойств являются GetPercentFree и SetPercentCritical в классе TDiskGauge:
type TDiskGauge = class

FPercentCritical: Integer;
function GetPercentFree: Integer;
procedure SetPercentCritical (Value: Integer);
property PercentFree: Integer read GetPercentFree;
property PercentCritical: Integer
read FPercentCritical write SetPercentCritical;
end;


Использование методов для получения и установки свойств позволяет проверить корректность значения свойства, сделать дополнительные вычисления, установить значения зависимых полей и т.д. Например, в методе SetPercentCritical целесообразно сделать проверку на то, что устанавливаемое значение находится в диапазоне от 0 до 100:
procedure TDiskGauge.SetPercentCritical (Value: Integer);
begin
if (Value >= 0) and (Value < 100)
then FpercentCritical := Value;
end;


МЕТОДЫ, ОБСЛУЖИВАЮЩИЕ НЕСКОЛЬКО СВОЙСТВ
0дин и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод первым параметром. В следующем примере методы Get и Set обслуживают три свойства: GaugeA, GaugeB и GaugeC:
type
TGaugeList = class
FGauges: array [0..2] of TDiskGauge;
...
function Get (Index: Integer): TDiskGauge;
procedure Set (Index: Integer; Value: TDiskGauge);

...
property GaugeA: TDiskGauge index 0 read Get write Set;

property GaugeB: TDiskGauge index 1 read Get write Set;

property GaugeC: TDiskGauge index 2 read Get write Set;

...
end;


function TGaugeList.Get (Index: Integer): TDiskGauge;
begin
Result := FGauges [Index];
end;

procedure TGaugeList.Set (Index: Integer; Value: TDiskGauge);
begin
FGauges [Index] := Value;
end;


Обращения к свойствам GaugeA, GaugeB и GaugeC заменяются на соответствующие Вызовы методов Get и Set:
var
GaugeList: TGaugeList;
DiskGauge: TDiskGauge;
...
GaugeList.GaugeC := DiskGauge;
{ эквивалентно GaugeList.Set (2, DiskGauge) }

GaugeList.GaugeC.CheckStatus;
{ эквивалентно GaugeList.Get(2).CheckStatus }
...


СВОЙСТВА-МАССИВЫ
Кроме обычных свойств в объектах существуют свойства-массивы (array properties), Свойство-массив — это индексированное множество свойств. В виде свойства-массива удобно, например, представить множество измерителей ресурсов в классе TGaugeList
type

TGaugeList = class
...

property Gauges[Index: Integer]: TdiskGauge
read Get write Set;
...

end;

Обратите внимание, что методы Get и Set обслуживают и свойство-массив Gauges, и индексированные свойства GaugeA, GaugeB и GaugeC. Если в описании обычных свойств могут участвовать поля, то в описании свойств-массивов разрешено использовать только методы.
Основная выгода от применения свойств-массивов — возможность выполнения итераций с помощью цикла for, например:
var
GaugeList: TGaugeList;
I: Integer;
...
for I := 0 to 2 do
with GaugeList do Gauges [I] .CheckStatus;
...


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

GaugeList.Gauges['GaugeA'] := DiskGauge;

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

СВОЙСТВО-МАССИВ КАК ОСНОВНОЕ СВОЙСТВО ОБЪЕКТА
Свойство-массив можно сделать основным свойством объектов данного класса. Для этого в его описание добавляется слово default:
type
TGaugeList = class
...

property Gauges [Index: Integer]: TdiskGauge
read Get write Set; default;
...

end;


Такое объявление свойства Gauges позволяет рассматривать сам объект класса TGaugeList как массив и опускать имя свойства-массива при обращении к нему из программы, например:
var
GaugeList: TGaugeList;
I: Integer;
...
for I := 0 to 2 do
GaugeList [I] := nil; { эквивалентно GaugeList.Gauges[I] := nil; }


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

НАСЛЕДОВАНИЕ
ПОНЯТИЕ НАСЛЕДОВАНИЯ
Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее важная черта классов — способность наследовать поля, методы и свойства других классов. Чтобы пояснить сущность наследования, обратимся к примеру с измерителями ресурсов.
Класс TDiskGauge описывает измеритель дискового ресурса и непригоден для измерения ресурса другого типа, например оперативной памяти. С появлением измерителя оперативной памяти нужен новый класс объектов:
type
TMemoryGauge = class
FPercentCritical: Integer;
constructor Create;
function GetPercentFree: Integer;
procedure SetPercentCritical (Value: Integer) ;
procedure CheckStatus;
property PercentFree: Integer read GetPercentFree;
property PercentCritical: Integer
read FPercentCritical write SetPercentCritical;
end;

Поля, методы и свойства класса TMemoryGauge аналогичны тем, что определены в классе TDiskGauge. Отличие состоит в отсутствии поля DriveLetter и другой реализации конструктора Create и метода GetPercentFree. Если в будущем появится класс, описывающий измеритель ресурса какого-то нового типа, то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования атрибутов при определении новых классов, воспользуемся механизмом наследования. Прежде всего выделим атрибуты, общие для всех измерителей ресурсов, в отдельный класс TResourceGauge:
type
TResourceGauge = class
FPercentCritical: Integer;
constructor Create;
function GetPercentFree: Integer;
procedure SetPercentCritical (Value: Integer) ;
procedure CheckStatus;
property PercentFree : Integer read GetPercentFree;

property PercentCritical: Integer
read FPercentCritical write SetPercentCritical;

end;

constructor TResourceGauge.Create;
begin
FPercentCritical := 10;
end;

function TResourceGauge.GetPercentFree: Integer;
begin
Result := 0;
end;

procedure TResourceGauge.SetPercentCritical (Value: Integer);
begin
if (Value >= 0) and (Value < 100) then FPercentCritical := Value;
end;

procedure TResourceGauge.CheckStatus;
begin
if GetPercentFree <= FPercentCritical then Beep;
end;

При реализации класса TResourceGauge ничего не известно о том, что в действительности представляет собой ресурс, поэтому функция GetPercentFree возвращает нуль. Очевидно, что создавать объекты класса TResourceGauge не имеет смысла. Для чего тогда нужен класс TResourceGauge? Ответ: чтобы на его основе породить два других класса — TDiskGauge и TMemoryGauge, описывающих конкретные виды измерителей ресурсов, — измеритель диска и измеритель памяти:
type
TDiskGauge = class(TResourceGauge)
DriveLetter: Char;
constructor Create (ADriveLetter: Char) ;
function GetPercentFree: Integer;
end;


TMemoryGauge = class(TResourceGauge)
function GetPercentFree: Integer;

end;

Классы TDiskGauge и TMemoryGauge определены как наследники TResourceGauge (об этом говорит имя в скобках после слова class). Они автоматически включают в себя все описания, сделанные в классе TResourceGauge и добавляют к ним некоторые новые. В результате формируется следующее дерево классов (рис. 1):

Рисунок 1
Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком. Естественно, что класс, от которого происходит наследование, выступает в роли базового, или предка. В примере класс TDiskGauge является непосредственным потомком класса TResourceGauge. Если от TDiskGauge породить новый класс, то он тоже будет потомком TResourceGauge, но уже не таким близким, как TDiskGauge.
Очень важно, что в отношениях наследования любой класс может иметь только одного непосредственного предка и сколь угодно много потомков. Поэтому все связанные отношением наследования классы образуют иерархию. Примером иерархии классов является библиотека Visual Component Library (VCL); с ее помощью в Delphi обеспечивается разработка Windows-приложений.


--------------------
Перейти в начало страницы
 
Neo][
сообщение 03.11.2006, 21:06
Сообщение #7


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


ПРЕДОК ПО УМОЛЧАНИЮ
В языке Object Pascal существует предопределенный класс TObject, который служит неявным предком тех классов, для которых предок не указан. Это означает, что объявление
type
TResourceGauge = class
...
end;

эквивалентно следующему:

type
TResourceGauge = class(TObject)
...
end;

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

Рисунок 2
ПЕРЕКРЫТИЕ АТРИБУТОВ В НАСЛЕДНИКАХ
В механизме наследования можно условно выделить три основных момента:
 наследование полей;
 наследование свойств;
 наследование методов.

Любой порожденный класс наследует от родительского все поля данных, поэтому классы TDiskGauge и TMemoryGauge автоматически содержат поле FPercentCritical, объявленное в классе TResourceGauge. Доступ к полям предка осуществляется по имени, как если бы они были определены в порожденном классе. В наследниках можно определять новые поля, но их имена должны отличаться от имен полей предка.
Наследование свойств и методов имеет свои особенности.
Свойство базового класса можно перекрыть (от англ. override) в производном классе, например чтобы добавить ему новый атрибут доступа или связать с другим полем или методом.
Метод базового класса тоже можно перекрыть в производном классе, например чтобы изменить логику его работы. Обратимся, например, к классам TDiskGauge и TMemoryGauge. В них методы SetPercentCritical и CheckStatus унаследованы от TResourceGauge, так как логика их работы не зависит от типа ресурса. А вот метод GetPercentFree перекрыт, так как способ вычисления процента свободного пространства специфичен для диска и оперативной памяти:
function TDiskGauge.GetPercentFree: Integer;
var
Drive: Byte;
begin
Drive := Ord(DriveLetter) - Ord('A') + 1;
Result := DiskFree(Drive) * 100 div DiskSize(Drive) ;
end;

function TMemoryGauge.GetPercentFree: Integer; { uses Windows; }
var
MemoryStatus: TMemoryStatus;
begin
MemoryStatus.dwLength := SizeOf(MemoryStatus);
GlobalMemoryStatus(MemoryStatus);
Result := 100 - MemoryStatus.dwMemoryLoad;
end;

В классе TDiskGauge перекрыт еще и конструктор Create. Это необходимо для инициализации дополнительного поля DriveLetter:
constructor TDiskGauge.Create (ADriveLetter: Char) ;
begin
inherited Create;
DriveLetter := ADriveLetter;
end;

Как видно из примера, в наследнике можно вызвать перекрытый метод предка, указав перед именем метода зарезервированное слово inherited. Кстати, данный пример демонстрирует важный принцип реализации конструкторов: сначала вызывается конструктор предка, а затем инициализируются дополнительные поля данных. В деструкторах применяется обратная последовательность действий: сначала разрушаются данные, недоступные предку, а затем вызывается унаследованный деструктор.
СОВМЕСТИМОСТЬ ОБЪЕКТОВ РАЗЛИЧНЫХ КЛАССОВ
Для классов, связанных отношением наследования, вводится новое правило совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно. Например, переменной типа TResourceGauge можно присвоить значение переменной типа TDiskGauge:
var
R: TResourceGauge;
...
R := TDiskGauge.Create;
...

Объектная переменная R формально имеет тип TResourceGauge, а фактически связана с экземпляром класса TDiskGauge.
Правило совместимости классов чаще всего применяется при передаче объектов в параметрах процедур и функций. Например, если процедура работает с объектом класса TResourceGauge, то вместо него можно передать объект класса TDiskGauge или TMemoryGauge.
КОНТРОЛЬ И ПРЕОБРАЗОВАНИЕ ТИПОВ
Поскольку реальный экземпляр объекта может оказаться наследником класса, указанного при описании объектной переменной или параметра, бывает необходимо проверить, к какому классу принадлежит объект на самом деле. Чтобы программист мог выполнять такого рода проверки, каждый объект хранит информацию о своем классе. В Object Pascal существуют операторы is и as, с помощью которых выполняется соответственно проверка на тип (type checking) и преобразование к типу (type casting).
Например, чтобы выяснить, принадлежит ли некоторый объект Obj, объявленный в программе как
var
Obj: TObject;

к классу TResourceGauge или его наследнику, следует записать
if Obj is TResourceGauge then { да, принадлежит } ;

Для преобразования объекта к нужному типу используется оператор as, например:
with Obj as TResourceGauge do CheckStatus;

Стоит отметить, что для объектов применим и обычный способ приведения типа:
with TResourceGauge(Obj ) do CheckStatus;

Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее, исключительную ситуацию) при выполнении программы (run-time error), если реальный экземпляр объекта Obj несовместим с классом TResourceGauge. Забегая вперед, скажем, что ошибку приведения типа можно обработать и таким образом избежать досрочного завершения приложения.
ВИРТУАЛЬНЫЕ МЕТОДЫ
ПОНЯТИЕ ВИРТУАЛЬНОГО МЕТОДА
Все методы, которые до сих пор рассматривались, имеют одну общую черту — все они статические. При обращении к статическому методу компилятор точно знает класс, которому данный метод принадлежит. Поэтому, например, обращение к статическому методу GetPereentFree в методе CheckStatus компилируется в вызов TResourceGauge.GetPercentFree:
procedure TResourceGauge.CheckStatus;
begin
if GetPereentFree <= FPercentCritical then Beep;
{ if TResourceGauge.GetPereentFree <= FPercentCritical then Beep; }
end;

Метод CheckStatus работает неправильно в наследниках TResourceGauge, так как внутри него вызов перекрытого метода GetPereentFree не происходит. Конечно, в классах TDiskGauge и TMemoryGauge можно продублировать все методы и свойства, которые прямо или косвенно вызывают GetPereentFree, но при этом теряются преимущества наследования. ООП предлагает изящное решение этой проблемы — метод GetPereentFree всего-навсего объявляется виртуальным:
type
TResourceGauge = class
...
function GetPereentFree: Integer; virtual;
...
end;

В производных классах виртуальный метод перекрывается с использованием ключевого слова override. Перекрытый метод должен иметь точно такой же формат (список параметров, а для функций еще и тип возвращаемого значения), что и перекрываемый:
type
TDiskGauge = class(TResourceGauge)
...
function GetPercentFree: Integer; override;
end;

TMemoryGauge = class(TResourceGauge)
function GetPercentFree: Integer; override;
end;

Суть виртуальных методов в том, что они вызываются по фактическому типу экземпляра, а не по формальному типу, записанному в программе. Поэтому после сделанных изменений метод CheckStatus будет работать так, как ожидает программист:
procedure TResourceGauge.CheckStatus;
begin
if GetPercentFree <= FPercentCritical then Beep;
{ if «фактический класс>.GetPercentFree <= FpercentCritical then Beep; }
end;

Работа виртуальных методов основана на механизме позднего связывания (late binding). В отличие от раннего связывания (early binding), характерного для статических методов, позднее связывание основано на вычислении адреса вызываемого метода при выполнении программы. Метод вычисляется по хранящемуся в каждом объекте описателю типа.
Благодаря механизму наследования и виртуальным методам, в Delphi реализуется такая концепция ООП как полиморфизм. Полиморфизм существенно облегчает труд программиста, так как обеспечивает повторное использование кода уже написанных методов.
АБСТРАКТНЫЕ ВИРТУАЛЬНЫЕ МЕТОДЫ
При построении иерархии часто возникает ситуация, когда работа виртуального метода в базовом классе неизвестна и наполняется содержанием только в наследниках. Так случилось, например, с методом GetPercentFree, который в классе TResourceGauge состоит всего из одного оператора: Result := 0. Конечно, тело метода можно сделать пустым или почти пустым (так мы и поступили), но лучше воспользоваться директивой abstract:
type
TResourceGauge = class
...
function GetPercentFree : Integer; virtual; abstract;
...
end;

Директива abstract записывается после слова virtual и исключает необходимость написания кода виртуального метода для данного класса. Такой метод называется абстрактным, т.е. подразумевает конкретное логическое действие, а не способ его реализации. Абстрактные виртуальные методы часто используются при создании классов-полуфабрикатов. Свою реализацию такие методы получают в законченных наследниках.
ДИНАМИЧЕСКИЕ МЕТОДЫ
Разновидностью виртуальных методов являются так называемые динамические методы. При их объявлении вместо слова virtual записывается ключевое слово dynamic, например:
type
TResourceGauge = class
...
function GetPercentFree: Integer; dynamic; abstract;
...
end;

В наследниках динамические методы перекрываются так же, как и виртуальные, т.е. с помощью зарезервированного слова override.
Семантически динамические и виртуальные методы идентичны. Различие состоит только в механизме их вызова. Методы, объявленные с директивой virtual, вызываются максимально быстро, но платой за это является большой размер системных таблиц, с помощью которых происходит их диспетчеризация. Размер этих таблиц начинает сказываться с увеличением числа классов в иерархии. Методы, объявленные с директивой dynamic, вызываются несколько дольше, но при этом таблицы диспетчирования имеют более компактный вид, что способствует экономии памяти. Таким образом, программисту предоставляются два способа оптимизации объектов: по скорости работы (virtual) или по объему памяти (dynamic).
МЕТОДЫ ОБРАБОТКИ СООБЩЕНИЙ
Специализированной формой динамических методов являются методы обработки сообщений. Они объявляются с помощью ключевого слова message, за которым следует целочисленная константа — номер сообщения, например:
type
TMyControl = class(TWinControl)
procedure WMPaint (var Message: TWMPaint) ; message WM_PAINT;
end;

Методы обработки сообщений всегда имеют формат процедуры и содержат единственный var-параметр. При перекрытии метода его название и имя параметра не имеют значения. Вызовом соответствующего обработчика занимается метод Dispatch, наследуемый из класса TObject.
Методы обработки сообщений применяются внутри библиотеки VCL для обработки сообщений Windows и редко нужны ее пользователям, т.е. нам с вами.
КЛАССЫ В ПРОГРАММНЫХ МОДУЛЯХ
Классы очень удобно собирать в модули. При этом их описание помещается в секцию interface, а код методов — в секцию implementation. Создавая модули классов, нужно придерживаться следующих правил:
 все классы, предназначенные для использования за пределами модуля, следует определять в секции interface;
 описание классов, предназначенных для употребления внутри модуля, следует располагать в секции implementation;
 если модуль В использует модуль А, то в модуле В можно определять классы, порожденные от классов модуля А.

Соберем рассмотренные ранее классы TResourceGauge, TDiskGauge и TmemoryGauge в отдельный модуль Resgauge:
unit Resgauge;

interface

type

TResourceGauge = class
private
FPercentCritical: Integer;
procedure SetPercentCritical(Value: Integer);
protected
function GetPercentFree: Integer; virtual; abstract;
public
constructor Create;
procedure CheckStatus;
property PercentFree: Integer read GetPercentFree;
property PercentCritical: Integer
read FPercentCritical write SetPercentCritical;
end;


TDiskGauge = class(TResourceGauge)
private
DriveLetter: Char;
protected
function GetPercentFree : Integer; override;
public
constructor Create (ADriveLetter: Char) ;
end;

TMemoryGauge = class (TResourceGauge)
protected
function GetPercentFree: Integer; override;
end;

implementation

uses
SysUtils, Windows;

{ TResourceGauge }

constructor TResourceGauge.Create;
begin
FPercentCritical := 10;
end;

procedure TResourceGauge.SetPercentCritical(Value: Integer);
begin
if (Value >= 0) and (Value < 100) then FPercentCritical := Value;
end;

procedure TResourceGauge.CheckStatus;
begin
if PercentFree <= PercentCritical then Beep;
end;

{ TDiskGauge }

constructor TDiskGauge.Create (ADriveLetter: Char) ;
begin
inherited Create;
DriveLetter := ADriveLetter;
end;

function TDiskGauge.GetPercentFree: Integer;
var
Drive: Byte;
begin
Drive := Ord (DriveLetter) - Ord('A') + 1;
Result := DiskFree(Drive) * 100 div DiskSize(Drive) ;
end;

{ TMemoryGauge }

function TMemoryGauge.GetPercentFree: Integer;
var
MemoryStatus: TMemoryStatus ;
begin
MemoryStatus.dwLength := SizeOf(MemoryStatus);
GlobalMemoryStatus(MemoryStatus);
Result := 100 - MemoryStatus.dwMemoryLoad;
end;

end.

Как можно заметить, в описании классов присутствуют новые слова private, protected и public. С их помощью регулируется видимость частей класса для других модулей и основной программы. Назначение каждой директивы поясняется ниже.
ВИДИМОСТЬ АТРИБУТОВ ОБЪЕКТА
Программист имеет возможность ограничить видимость атрибутов класса для других программистов (и для себя в том числе). Для этого служат директивы private, protected, public, published, automated (последние две директивы не используется в модуле Resgauge).
Private. Все, что объявлено в секции private, недоступно за пределами модуля. Секция private позволяет скрыть те поля и методы, которые относятся к так называемым особенностям реализации. Например, в этой секции объявлены поле FPercentCritical и метод SetPercentCritical.
Public. Поля, методы и свойства, объявленные в секции public, не имеют никаких ограничений на использование, т.е. всегда видны за пределами модуля. Все, что помещается в секцию public, служит для манипуляций с объектами и составляет программный интерфейс класса. Например, в этой секции объявлены конструктор Create, процедура CheckStatus, свойства PercentFree и PercentCritical.
Protected. Поля, методы и свойства, объявленные в секции protected, видны за пределами модуля только потомкам данного класса; остальным частям программы они не видны. Так же как и private, директива protected позволяет скрыть особенности реализации класса, но в отличие от нее разрешает другим программистам порождать новые классы и обращаться к полям, методам и свойствам, которые составляют так называемый интерфейс разработчика. В эту секцию обычно помещаются виртуальные методы чтения и записи свойств. Примером такого метода является GetPercentFree.
Published. Устанавливает правила видимости те же, что и директива public. Особенность состоит в том, что для элементов, помещенных в секцию published, компилятор генерирует информацию о типе, которая позволяет превращать объекты в компоненты визуальной среды разработки. Секцию published разрешено использовать только тогда, когда для самого класса или его предка включена директива компилятора $TYPEINFO.
Automated. Устанавливает правила видимости те же, что и директива public. Директива automated используется в наследниках класса TAutoObject при создании серверов OLE Automation. Для помещенных в эту секцию методов и свойств компилятор генерирует специальную информацию о типе, которая обеспечивает их видимость за пределами приложения.
Перечисленные секции могут чередоваться в объявлении класса в произвольном порядке, однако в пределах секции сначала следует описание полей, а потом методов и свойств. Если в определении класса нет ключевых слов private, protected, public, published и automated, то для обычных классов всем полям, методам и свойствам приписывается атрибут видимости public, а для тех классов, что порождены от классов VCL — атрибут видимости published.
Заметим, что внутри модуля не действуют никакие ограничения видимости на атрибуты реализованного в модуле класса. Это, кстати, отличается от соглашений, принятых в других языках программирования, в частности в C++.
УКАЗАТЕЛИ НА МЕТОДЫ ОБЪЕКТОВ
В Object Pascal существуют процедурные типы данных для методов объектов. Внешне объявление процедурного типа для метода отличается от обычного словосочетанием of object, записанным после прототипа процедуры или функции:
type
TFewResourcesEvent = procedure (Sender: TObject) of object;

Переменная такого типа называется указателем на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода:
var
OnFewResources: TFewResourcesEvent = nil;

Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу с переменной OnFewResources.
type
Tform1 = class(TForm)
procedure FewResources (Sender: TObject) ;
end;
var
Form1: Tform1;

Бели переменную OnFewResources связать с методом FewResources объекта Form1
OnFewResources:= Form1.FewResources;

и переписать метод CheckStatus,
procedure TResourceGauge.CheckStatus ;
begin
if PercentFree <= PercentCritical then
if Assigned(OnFewResources) then OnFewResources(Self) ;
end;

то выдача предупреждения о нехватке ресурсов будет переадресована (говорят еще делегирована) методу FewResources объекта Form1. Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil. Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.
Делегирование позволяет сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в Delphi. Например, все компоненты делегируют обработку своих событий форме, на которой они находятся.

МЕТАКЛАССЫ
ССЫЛКИ НА КЛАССЫ
Язык Object Pascal позволяет рассматривать классы как своего рода объекты, которыми можно манипулировать в программе. Такая возможность рождает новое понятие — класс класса; его принято обозначать термином метакласс.
Для поддержки метаклассов введен специальный тип данных — ссылка на класс (class reference). Он описывается с помощью словосочетания class of, например:
type
TResourceGaugeClass = class of TResourceGauge;

Переменная типа TResourceGaugeClass объявляется в программе обычным образом:
var
ClassRef: TResourceGaugeClass;

Значениями переменной ClassRef могут быть класс TResourceGauge и все порожденные от него классы. Допустимы, например, следующие операторы:
ClassRef := TResourceGauge;
ClassRef := TDiskGauge;
ClassRef := TMemoryGauge;

По аналогии с тем, как для всех классов существует общий предок TObject, у ссылок на классы существует базовый тип TCIass:
type TCIass = class of TObject;

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


--------------------
Перейти в начало страницы
 
Neo][
сообщение 03.11.2006, 21:07
Сообщение #8


The One
****************

Репутация:   744  
Группа: Участник
Сообщений: 2715
Награды: 5
Регистрация: 05.12.2005




Вставить ник Цитировать выделенное в форуму быстрого ответа


МЕТОДЫ КЛАССОВ
Метаклассы привели к возникновению нового типа методов — методов класса. Метод класса оперирует не экземпляром объекта, а непосредственно классом. Он объявляется как обычный метод, но перед словом procedure или function записывается зарезервированное слово class, например:
type
TResourceGauge = class
...
class function GetClassName : string;
end;

Псевдопараметр Self, передаваемый в метод класса, содержит не ссылку на объект, а ссылку на класс, поэтому в теле метода нельзя обращаться к полям, методам и свойствам объекта. Зато можно вызывать другие методы класса, например:
class function TResourceGauge.GetClassName: string;
begin
Result := ClassName;
end;

Метод ClassName объявлен в классе TObject и возвращает имя класса, к которому применяется. Очевидно, что надуманный метод GetClassName просто дублирует эту функциональность для класса TResourceGauge и всех его наследников.
Методы класса применимы и к классам, и к объектам. В обоих случаях в параметре Self передается ссылка на класс объекта. Пример:
var
Gauge: TResourceGauge;
S: string;
begin
{ Вызов метода с помощью ссылки на класс }
S := TDiskGauge.GetClassName; { S получит значение 'TDiskGauge' }
Gauge := TDiskGauge.Create('С');
{ Вызов метода с помощью ссылки на объект }
S := Gauge.GetClassName; { S получит значение 'TDiskGauge' }
end;

Методы классов могут быть виртуальными. Например, в классе TObject определен виртуальный метод класса Newlnstance. Он служит для распределения памяти под объект и автоматически вызывается конструктором. Его можно перекрыть в своем классе, чтобы обеспечить нестандартный способ выделения памяти для экземпляров. Метод Newlnstance должен перекрываться вместе с другим методом Freelnstance, который автоматически вызывается из деструктора и служит для освобождения памяти. Добавим, что размер памяти, требуемый для экземпляра, можно узнать вызовом предопределенного метода класса InstanceSize.
ВИРТУАЛЬНЫЕ КОНСТРУКТОРЫ
Особая мощь ссылок на классы проявляется в сочетании с виртуальными конструкторами. Виртуальный конструктор объявляется с ключевым словом virtual. Вызов виртуального конструктора происходит по фактическому значению ссылки на класс, а не по ее формальному типу. Это позволяет создавать объекты, классы которых неизвестны на этапе компиляции. Механизм виртуальных конструкторов применяется в Delphi при создании форм и компонентов.
На этом закончим изучение теории объектно-ориентированного программирования и в качестве практики рассмотрим несколько широко используемых инструментальных классов Delphi. Разберитесь с их назначением и работой. Это поможет глубже понять ООП и пригодится на будущее.
КЛАССЫ ОБЩЕГО НАЗНАЧЕНИЯ В DELPHI
Как показывает практика, в большинстве задач приходится использовать однотипные структуры данных: списки, массивы, множества и т.д. От задачи к задаче изменяются только их элементы, а методы работы сохраняются. Например, для любого списка нужны процедуры вставки и удаления элементов. В связи с этим возникает естественное желание решить задачу «в общем виде», т.е. создать универсальные средства для управления основными структурами данных. Эта идея не нова. Она давно пришла в голову разработчикам инструментальных пакетов, которые быстро наплодили множество вспомогательных библиотек. Эти библиотеки содержали классы объектов для работы со списками, коллекциями (динамические массивы с переменным количеством элементов), словарями (коллекции, индексированные строками) и другими «абстрактными» структурами. Для Delphi тоже разработаны аналогичные классы объектов. Их большая часть сосредоточена в модуле Classes. Наиболее нужными для вас являются списки строк (TStrings, TStringList) и потоки (TSream, THandleSream, TFileStream, TMemoryStream и TBIobStream). Рассмотрим кратко их назначение и применение.
КЛАССЫ ДЛЯ ПРЕДСТАВЛЕНИЯ СПИСКА СТРОК
Для работы со списками строк служат классы TStrings и TStringList. Они используются в библиотеке VCL повсеместно и имеют гораздо большую универсальность, чем та, что можно почерпнуть из их названия. Классы TStrings и TStringList служат для представления не просто списка строк, а списка элементов, каждый из которых представляет собой пару строка-объект. Если со строками не ассоциированы объекты, получается обычный список строк.
Класс TStrings используется визуальными компонентами и является абстрактным. Он не имеет собственных средств хранения строк и определяет лишь интерфейс для работы с элементами. Класс TStringList является наследником TStrings и служит для организации списков строк, которые используются отдельно от управляющих элементов. Объекты TStringList хранят строки и объекты в динамической памяти.
Свойства класса TStrings описаны ниже.
Count: Integer — число элементов в списке.
Strings[lndex: Integer]: string — обеспечивает доступ к массиву строк по индексу. Первая строка имеет индекс, равный 0. Свойство Strings является основным свойством объекта.
Objects[lndex: Integer]: TObject — обеспечивает доступ к массиву объектов. Свойства Strings и Objects позволяют использовать объект TStrings как хранилище строк и ассоциированных с ними объектов произвольных классов.
Text: string — позволяет интерпретировать список строк как одну большую строку, в которой элементы разделены символами #13#10 (возврат каретки и перевод строки),
Наследники класса TStrings иногда используются для хранения строк вида Имя=3начение, в частности, строк INI-файлов (см. гл. 6). Для удобной работы со строками такой структуры в классе TStrings дополнительно имеются следующие свойства.
Names[lndex: Integer]: string — обеспечивает доступ к той части строки, в которой содержится имя.
Values[const Name: string]: string — обеспечивает доступ к той части строки, в которой содержится значение. Указывая вместо Name ту часть строки, которая находится слева от знака равенства, вы получаете ту часть, что находится справа.
Управление элементами списка осуществляется с помощью следующих методов:
Add(const S: string): Integer — добавляет новую строку S в список и возвращает ее позицию. Новая строка добавляется в конец списка.
Add0bject(const S: string; AObject: TObject): Integer — добавляет в список строку S и ассоциированный с ней объект AObject. Возвращает индекс пары строка—объект.
AddStrings(Strings: TStrings) — добавляет группу строк в существующий список. Append(const S: string) — делает то же, что и Add, но не возвращает значения. Clear — удаляет из списка все элементы.
Delete(lndex: Integer) — удаляет строку и ассоциированный с ней объект. Метод Delete, так же как метод Clear, не разрушает объектов, т.е. не вызывает у них деструктор. Об этом вы должны позаботиться сами.
Equals(Strings: TStrings): Boolean — возвращает True, если список строк в точности равен тому, что передан в параметре Strings.
Exchange(lndex1, lndex2: Integer) — меняет два элемента местами.
GetText: PChar — возвращает все строки списка в виде одной большой нуль-терминированной строки.
lndex0f(const S: string): Integer — возвращает позицию строки S в списке. Если заданная строка в списке отсутствует, функция возвращает значение —1.
lndexOfName(const Name: string): Integer — возвращает позицию строки, которая имеет вид Имя=3начение и содержит в себе Имя, равное Name.
lndexOfObject(AObject: TObject): Integer — возвращает позицию объекта AObject в массиве Objects. Если заданный объект в списке отсутствует, функция возвращает значение —1.
lnsert(lndex: Integer; const S: string) — вставляет в список строку S в позицию Index.
lnsert0bject(lndex: Integer; const S: string; AObject: TObject) — вставляет в список строку S и ассоциированный с ней объект AObject в позицию Index.
LoadFromFile(const FileName: string) — загружает строки списка из текстового файла.
LoadFromStream(Stream: TStream) — загружает строки списка из потока данных (см. ниже).
Move(Curlndex, Newlndex: Integer) — изменяет позицию элемента (пары строка-объект) в списке.
SaveToFile(const FileName: string) — сохраняет строки списка в текстовом файле.
SaveToStream(Stream: TStream) — сохраняет строки списка в потоке данных.
SetText(Text: PChar) — загружает строки списка из одной большой нуль-терминированной строки.
Класс TStringList добавляет к TStrings несколько дополнительных свойств и методов, а также два свойства-события для уведомления об изменениях в списке. Они описаны ниже.
Свойства:
Duplicates: TDuplicates — определяет, разрешено ли использовать дублированные строки в списке. Свойство может принимать следующие значения: duplgnore (дубликаты игнорируются), dupAccept (дубликаты разрешены), dupError (дубликаты запрещены, попытка добавить в список дубликат вызывает ошибку).
Sorted: Boolean — если имеет значение True, то строки автоматически сортируются в алфавитном порядке.
Методы:
Find(const S: string; var Index: Integer): Boolean — выполняет поиск строки S в списке строк. Если строка найдена, Find помещает ее позицию в переменную, переданную в параметре Index, и возвращает True.
Sort — сортирует строки в алфавитном порядке.
События:
OnChange: TNotifyEvent — указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChange генерируется после того, как были сделаны изменения.
OnChanging: TNotifyEvent — указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChanging генерируется перед тем, как будут сделаны изменения.
Ниже приводится фрагмент программы, демонстрирующий создание списка строк и манипулирование его элементами:
var
Items: TStrings;
I: Integer;
begin
{ Создание списка }
Items := TStringList.Create;
Items.Add('Туризм');
Items.Add('Наука');
Items.Insert(1, 'Бизнес');
...
{ Работа со списком }
for I := 0 to Items. Count - 1 do
Items[I] := Uppercase(Items [I]);
...
{ Удаление списка }
Items.Free;
end;

КЛАССЫ ДЛЯ ПРЕДСТАВЛЕНИЯ ПОТОКА ДАННЫХ
В Delphi существует иерархия классов для хранения и последовательного ввода-вывода данных. Классы этой иерархии называются потоками. Потоки лучше всего представлять как файлы. Классы потоков обеспечивают различное физическое представление данных:
файл на диске, раздел оперативной памяти, поле в таблице базы данных (см. табл. 1).
Таблица 1.
Класс Описание
TStream Абстрактный поток, от которого наследуются все остальные. Свойства и методы класса TStream образуют базовый интерфейс потоковых объектов.
THandleStream Поток, который хранит свои данные в файле. Для чтения-записи файла используется дескриптор (handle), поэтому поток называется дескрипторным. Дескриптор - это номер открытого файла в операционной системе. Его возвращают низкоуровневые функции создания и открытия файла.
TFileStream Поток, который хранит свои данные в файле. Отличается от ThandleStream тем, что сам открывает (создает) файл по имени, переданному в конструктор.
TMemoryStream Поток, который хранит свои данные в оперативной памяти. Моделирует работу с файлом. Используется для хранения промежуточных результатов, когда файловый поток не подходит из-за низкой скорости передачи данных.
TResourceStream Поток, обеспечивающий доступ к ресурсам в Windows-приложении.
TBIobStream Обеспечивает последовательный доступ к большим полям таблиц в базах данных.
Потоки широко применяются в библиотеке VCL и наверняка вам понадобятся. Поэтому ниже кратко перечислены их общие ключевые свойства и методы.
Общие свойства:
Position: Longint — текущая позиция чтения-записи.
Size: Longint — текущий размер потока в байтах.
Общие методы:
CopyFrom(Source: TStream; Count: Longint): Longint — копирует Count байт из потока Source в свой поток.
Read(var Buffer; Count: Longint): Longint — читает Count байт из потока в буфер Buffer, продвигает текущую позицию на Count байт вперед и возвращает число прочитанных байт. Если значение функции меньше значения Count, то в результате чтения был достигнут конец потока.
ReadBuffer(var Buffer; Count: Longint) — читает из потока Count байт в буфер Buffer и продвигает текущую позицию на Count байт вперед. Если выполняется попытка чтения за концом потока, то генерируется ошибка.
Seek(0ffset: Longint; Origin: Word): Longint — продвигает текущую позицию в потоке на Offset байт относительно позиции, заданной параметром Origin. Параметр Origin может иметь одно из следующих значений: 0 — смещение задается относительно начала потока; 1 — смещение задается относительно текущей позиции в потоке; 2 — смещение задается относительно конца потока.
Write(const Buffer; Count: Longint): Longint — записывает в поток Count байт из буфера Buffer, продвигает текущую позицию на Count байт вперед и возвращает реально записанное количество байт. Если значение функции отличается от значения Count, то при записи была ошибка.
WriteBuffer(const Buffer; Count: Longint) — записывает в поток Count байт из буфера Buffer и продвигает текущую позицию на Count байт вперед. Если по какой-либо причине невозможно записать все байты буфера, то генерируется ошибка.
Ниже приводится фрагмент программы, демонстрирующий создание файлового потока и запись в него строки:
var
Stream: TStream;
S: AnsiString;
StrLen: Integer;
begin
{ Создание файлового потока }
Stream := TFileStream.Create('Sample.Dat', fmCreate);
...
{ Запись в поток некоторой строки }
StrLen := Length(S) * SizeOf(Char);
Stream.Write (StrLen, SizeOf (Integer) ) ; { запись длины строки }
Stream.Write (S, StrLen); { запись символов строки }
...
{ Закрытие потока }
Stream.Free;
end;


итоги
Теперь для вас нет секретов в мире ООП. Вы на достаточно серьезном уровне познакомились с объектами и их свойствами; узнали, как объекты создаются, используются и уничтожаются. Если не все удалось запомнить сразу — не беда. Возвращайтесь к материалам главы по мере решения стоящих перед вами задач, и работа с объектами станет простой, естественной и даже приятной. Когда вы добьетесь понимания того, как работает один объект, то автоматически поймете, как работают все остальные. Теперь мы рассмотрим то, с чем вы встретитесь очень скоро — ошибки программирования.


--------------------
Перейти в начало страницы
 

Тема закрытаНачать новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 



Текстовая версия Сейчас: 28.03.2024, 18:15