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

 

Code:

unit Unit2;

{©Drkb v.3(2007): www.drkb.ru,

®Vit (Vitaly Nevzorov) - Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.}

 

interface

 

Uses classes, Sysutils;

 

{Нам нужен процедурный тип для создания собственного события. Собственно - это описание процедуры которая должна будет исполнятся при каких-нибудь обстоятельствах}

 

Type

TError = procedure(Sender:TObject; Error: string) ofobject;

 

{Описание нашего класса, мы его наследуем от TObject, потому ?то нам практи?ески не нужна

никакия функциональность предков}

Type TStatistic=Class(TObject)

private{здесь описываются только внутренние переменные и процедуры - "для служебного пользования"}

{Описание полей, т.е. переменных которые работают только внутри класса, "снаружи" они не

доступны.}

FList:TStringList;

FPrecision: byte;

{Тоже переменная - для определения события}

FonError: TError;

{функция - будет использоваться только внутри класса, "снаружи" напрямую не доступна}

function GetCount: integer;

public{Описанное здесь доступно для пользователя класса}

{Конструктор - метод создания класса, имеет смысл его описывать только если он делает

?то-то специфи?еское - например нам надо будет создать переменную FList. В противном слу?ае

его описание можно опустить - будет работать конструктор родительского класса}

Constructor Create;

{Деструктор - метод разрушения класса}

Destructor Destroy; override;

{Описание методов - собственно методы мало ?ем отли?аются от процедур}

Procedure AddValue(Value:String);

Procedure Clear;

Function Solve:real;

{Описание свойств. Обратите внимание само свойство не способно хранить никакую информацию, это

только указатель на внутренюю струкруру. Например для хранения свойства Precision используется

переменная FPrecision. А для ?тение свойства Count используется функция GetCount}

Property Precision:byte read FPrecision write FPrecision;

Property Count:integer read GetCount;

{Описание событий. ?то такое событие? - Это указатель на процедуру. Сам класс реализации этой процедуры

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

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

Property onError:TError read FonError write FonError;

end;

 

implementation

 

{ TStatistic }

 

constructor TStatistic.Create;

begin

inherited; {Вна?але надо вызвать конструктор класса-родителя}

FList:=TStringList.create;{создаем структуры нашего класса}

end;

 

destructor TStatistic.Destroy;

begin

FList.Free;{Разрушаем структуры нашего класса}

inherited;{в последнюю о?ередь вызываем деструктор клсса-родителя}

end;

 

procedure TStatistic.AddValue(Value: String);

begin

FList.add(Value); {Примерно так мы реализуем метод}

end;

 

procedure TStatistic.Clear;

begin

FList.clear;

end;

 

function TStatistic.GetCount: integer;

begin

Result:=FList.count+1;

end;

 

function TStatistic.Solve: real;

var i:integer;

begin

result:=0;

for i:=0to FList.count-1do

begin

try

result:=result+(Sqr(strtofloat(FList[i])));

except

{интересная конструкция. "on e:exception do" - мы "отлавливаем" ошибку как переменную "e".

Эта переменная имеет о?ень полезное свойство e.message - оно содержит описание ошибки. Далее

следует вызов события. Вна?але мы проверяем использует ли пользователь событие:

"if Assigned(FOnError) then", если использует то вызываем его процедуру: FOnError, с параметрами:

self - зарезервированная переменная - указатель на экземпляр нашего класса, e.message - описание

ошибки}

on e:exception do

if Assigned(FOnError) then FOnError(Self, e.message);

end;

end;

end;

 

end.

 

Вот пример использования этого класса:

 

unit Unit1;

 

interface

 

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TForm1 = class(TForm)

Button1: TButton;

procedure Button1Click(Sender: TObject);

private

procedure OnError(Sender:TObject; Error: string);

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

uses Unit2;

 

{$R *.DFM}

 

procedure TForm1.Button1Click(Sender: TObject);

var Statistic:TStatistic;

begin

Statistic:=TStatistic.create;

Statistic.onError:=onError;

Statistic.AddValue('123423');

Statistic.AddValue('123423');

showmessage(floattostr(Statistic.solve));

Statistic.Clear;

Statistic.AddValue('123423');

Statistic.AddValue('12ssss3');

showmessage(floattostr(Statistic.solve));

Statistic.Free;

end;

 

procedure TForm1.OnError(Sender: TObject; Error: string);

begin

showmessage('Error inside class:'+Sender.ClassName+#13#10+Error);

end;

 

end.

 

 

 

Автор:Vit  

Взято с Vingradhttps://forum.vingrad

Пособиепонаписаниюсвоихкомпонентовна Delphi дляначинающих

 

Почемуяселписатьэтопособие

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

Все мои готовые компоненты можно найти на сайте https://delphid.dax 

 

Длячегонужныкомпоненты

Дельфи имеет открытую архитектуру - это значит, что каждый программист волен усовершенствовать эту среду разработки, как он захочет. К стандартным наборам компонентов, которые поставляются вместе с Дельфи можно создать еще массу своих интересных компонентов, которые заметно упростят вам жизнь (это я вам гарантирую). А еще можно зайти на какой-нибудь крутой сайт о Дельфи и там скачать кучу крутых компонентов, и на их основе сделать какую-нибудь крутую прогу. Так же компоненты освобождают вас от написания "тысячи тонн словесной руды". Пример: вы создали компонент - кнопку, при щелчке на которую данные из Memo сохранятся во временный файл. Теперь как только вам понадобится эта функция вы просто ставите этот компонент на форму и наслаждаетесь результатом. И не надо будет каждый раз прописывать это, для ваших новых программ - просто воспользуйтесь компонентом.

 

Шаг 1. Придумываниеидеи

Первым шагом нужно ответить себе на вопрос: "Для чего мне этот компонент и что он будет делать?". Затем необходимо в общих чертах продумать его свойства, события, на которые он будет реагировать и те функции и процедуры, которыми компонент должен обладать. Затем очень важно выбрать "предка" компонента, то есть наследником какого класса он будет являться. Тут есть два пути. Либо в качестве наследника взять уже готовый компонент (то есть модифицировать уже существующий класс), либо создать новый класс.

 

Для создания нового класса можно выделить 4 случая:

1. Создание Windows-элемента управления (TWinControl)

2. Создание графического элемента управления (TGraphicControl)

3. Создание нового класса или элемента управления (TCustomControl)

4. Создание невизуального компонента (не видимого) (TComponent)

 

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

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

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

 

Шаг 2. Созданиепустогомодулякомпонента

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

 

Чтобы приступить к непосредственному написанию компонента, вам необходимо сделать следующее:

· Закройте проекты, которые вы разрабатывали (формы и модули)
· В основном меню выберите Component -> New Component...
· Перед вами откроется диалоговое окно с названием "New Component"
· В поле Ancestor Type (тип предка) выберите класс компонента, который вы хотите модифицировать. В нашем случае вам надо выбрать класс TButton
· В поле Class Name введите имя класса, который вы хотите получить. Имя обязательно должно начинаться с буквы "T". Мы напишем туда, например, TCountBtn
· В поле Palette Page укажите имя закладки на которой этот компонент появиться после установки. Введем туда MyComponents (теперь у вас в Делфьи будет своя закладка с компонентами!).
· Поле Unit File Name заполняется автоматически, в зависимости от выбранного имени компонента. Это путь куда будет сохранен ваш модуль.
· В поле Search Path ничего изменять не нужно.
· Теперь нажмите на кнопку Create Unit и получите следующее:

 

Code:

unit CountBtn;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TCountBtn = class(TButton)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

published

{ Published declarations }

 

end;

 

procedureRegister;

 

implementation

 

procedureRegister;

begin

RegisterComponents('MyComponents', [TCountBtn]);

end;

 

end.

 

 

Шаг 3. Начинаемразбиратьсявовсехдирективах

Что же здесь написано? да собственно пока ничего интересного. Здесь объявлен новый класс TCountBtn и процедура регистрации вашего компонента в палитре компонентов.

Директива Private Здесь вы будете писать все скрытые поля которые вам понадобятся для создания компонента. Так же в этой директиве описываются процедуры и функции, необходимые для работы своего компонента, эти процедуры и функции пользователю не доступны. Для нашего компонент мы напишем туда следующее (запись должна состоять из буквы "F" имени поля: тип этого поля):

 

Code:

FCount:integer;

 

Буква "F" должна присутсвовать обязательно. Здесь мы создали скрытое поле Count, в котором и будет храниться число кликов по кнопке.

Директива Protected. Обычно я здесь пишу различные обработчики событий мыши и клавиатуры. Мы напишем здесь следующую строку:

 

Code:

procedure Click; override;

 

Это указывает на то, что мы будем обрабатывать щелчок мыши по компоненту. Слово "override" указывает на то, что мы перекроем стандартное событие OnClick для компонента предка.

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

 

Code:

procedure ShowCount;

 

Осталась последняя директива Published. В ней также используется объявления доступных пользователю, свойств и методов компонента. Для того, чтобы наш компонент появился на форме необходимо описать метод создания компонента (конструктор), можно прописать и деструктор, но это не обязательно. Следует обратить внимание на то, что если вы хотите, чтобы какие-то свойства вашего компонента появились в Инспекторе Объектов (Object Inspector) вам необходимо описать эти свойства в директиве Published. Это делается так: property Имя_свойства (но помните здесь букву "F" уже не нужно писать), затем ставиться двоеточие ":" тип свойства, read процедура для чтения значения, write функция для записи значения;. Но похоже это все сильно запутано. Посмотрите, что нужно написать для нашего компонента и все поймете:

 

Code:

constructor Create(aowner:Tcomponent);override; //Конструктор

property Count:integer read FCount write FCount; //Свойство Count

 

Итак все объявления сделаны и мы можем приступить к написанию непосредственно всех объявленных процедур.

 

 

Шаг 4. Пишемпроцедурыифункции.

Начнем с написания конструктора. Это делается примерно так:

 

Code:

constructor TCountBtn.Create(aowner:Tcomponent);

begin

inherited create(Aowner);

end;

 

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

 

Теперь мы напишем процедуру обработки щелчка мышкой по кнопке:

 

Code:

procedure Tcountbtn.Click;

begin

inherited click;

FCount:=FCount+1;

end;

 

"Inherited click" означает, что мы повторяем стандартные методы обработки щелчка мышью (зачем напрягаться и делать лишнюю работу:)).

У нас осталась последняя процедура ShowCount. Она может выглядеть примерно так:

 

Code:

procedure TCountBtn.ShowCount;

begin

Showmessage('По кнопке '+ caption+' вы сделали: '+inttostr(FCount)+' клик(а/ов)');

end;

 

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

И если вы все поняли и сделали правильно, то у вас должно получится следующее:

 

Code:

unit CountBtn;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ExtCtrls;

 

type

TCountBtn = class(TButton)

private

{ Private declarations }

FCount:integer;

protected

{ Protected declarations }

procedure Click;override;

public

{ Public declarations }

procedure ShowCount;

published

{ Published declarations }

property Count:integer read FCount write FCount;

constructor Create(aowner:Tcomponent);override;

end;

 

procedureRegister;

 

implementation

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TCountBtn]);

end;

 

constructor TCountBtn.Create(aowner:Tcomponent);

begin

inherited create(Aowner);

end;

 

procedure Tcountbtn.Click;

begin

inherited click;

FCount:=FCount+1;

end;

 

procedure TCountBtn.ShowCount;

begin

Showmessage('По кнопке '+ caption+' вы сделали: '+inttostr(FCount)+' клик(а/ов)');

end;

end.

 

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

 

 

Шаг 5. Устанавливаемкомпонент

Если вы сумели написать и понять, все то что здесь предложено, то установка компонента не должна вызвать у вас никаких проблем. Все здесь делается очень просто. В главном меню выберите пункт Component -> Install Component. перед вами открылось диалоговое окно Install Component. В нем вы увидите две закладки: Into exsisting Package и Into new Package. Вам предоставляется выбор установить ваш компонент в уже существующий пакет или в новый пакет соответственно. Мы выберем в уже существующий пакет.

В поле Unit File Name напишите имя вашего сохранненого модуля (естественно необходимо еще и указать путь к нему), а лучше воспользуйтесь кнопкой Browse и выберите ваш файл в открывшемся окне.

В Search Path ничего изменять не нужно, Делфьи сама за вас все туда добавит.

В поле Package File Name выберите имя пакета, в который будет установлен ваш компонент. Мы согласимся с предложенным по умолчанию пакетом.

Теперь нажимаем кнопочку Ok. И тут появиться предупреждение Package dclusr30.dpk will be rebuilt. Continue? Дельфи спрашивает: "Пакет такой-то будет изменен. Продолжить?". Конечно же надо ответить "Да". И если вы все сделали правильно, то появиться сообщение, что ваш компонент установлен. Что ж можно кричать Ура! Это ваш первый компонент.

 

Созданиесвойствсвоеготипа

Теперь мы попробуем создать свойство нестандартного типа. Рассмотрим это на примере метки - TLabel. У этого компонента есть такое свойство: Alignment. Оно может принимать следующие значения: taLeftJustify, taCenter, taRightJustify. Приступаем к созданию свойства. Ничего интересного мне придумать не удалось, но тем не менее я вам покажу это на примере того свойства, которое я придумал. Оно очень простое и поможет вам разобраться. Свойство будет называться ShowType (тип TShowTp), в нашем компоненте оно будет отвечать за отображение свойства Count. Если пользователь установит свойство ShowType в Normal, то кнопка будет работать, как и работала. А если пользователь присвоит этому свойтсву значение CountToCaption, то количество кликов, будет отображаться на самой кнопке.

 

Для начале нам необходимо объявить новый тип. Описание типа нужно добавить после слова Type. Вот так это выглядело вначале:

 

Code:

type

TCountBtn = class(TButton)

 

Воттакэтодолжновыглядеть:

Code:

type

TShowTp = (Normal, CountToCaption);

TCountBtn = class(TButton)

 

Здесь мы объявили новый тип TShowTp, который может принимать только два значения. Все значения, которые вы хотите добавить перечисляются через запятую. Теперь нам понадобиться создать поле этого типа. Это мы уже умеем и делать и поэтому не должно вызвать никаких сложностей. В директиву Private напишите:

 

Code:

FShowType:TShowTp;

 

Мы создали поле ShowType, типа TShowTp.

Конечно же необходимо добавить это свойство в инспектор объектов:

property ShowType: TshowTp read FshowType write FShowType;

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

 

Code:

procedure Tcountbtn.Click;

begin

inherited click;

FCount:=Fcount+1;

if ShowType = Normal then

Caption:=Caption;

if ShowType = CountToCaption then

Caption:='Count= '+inttostr(count);

end;

 

Объясню что произошло. Вначале мы увеличиваем счетчик на единицу. Затем проверяем какое значение имеет свойство ShowType. Если Normal, то ничего не делаем, а если CountToCaption, то в надпись на кнопке выводим количество кликов. Не так уж и сложно как это могло показаться с первого раза.

 

Имплантируемтаймервкомпонент

 

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

 

После раздела uses, где описаны добавленные в программу модули, объявите переменную типа TTimer. Назовем ее Timer. Приведу небольшой участок кода:

 

Code:

unit CountBtn;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ExtCtrls;

 

var Timer: TTimer;

type

 

Дальше в директиву Protected необходимо добавить обработчик события OnTimer для нашего таймера. Это делается так:

procedure OnTimer(Sender: TObject);

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

 

Code:

constructor TCountBtn.Create(aowner:Tcomponent);

begin

inherited create(Aowner);

Timer:=TTimer.Create(self);

Timer.Enabled:=true;

Timer.OnTimer:=OnTimer;

Timer.Interval:=10000;

end;

 

Здесь создается экземпляр нашего таймера и его свойству Iterval (измеряется в миллисекундах) присваивается значение 10000 (то есть 10 секунд если по простому).

Собственно осталось написать саму процедуру OnTimer. Я сделал это так:

 

Code:

procedure TCountBtn.OnTimer(Sender: TObject);

begin

FCount:=FCount*2;

end;

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

unit CountBtn;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ExtCtrls;

 

var Timer: TTimer;

type

TShowTp = (Normal, CountToCaption);

TCountBtn = class(TButton)

 

private

{ Private declarations }

 

FCount:integer;

FShowType:TShowTp;

protected

{ Protected declarations }

procedure OnTimer(Sender: TObject);

procedure Click;override;

public

{ Public declarations }

procedure ShowCount;

published

{ Published declarations }

property Count:integer read FCount write FCount;

constructor Create(aowner:Tcomponent);override;

property ShowType: TshowTp read FshowType write FShowType;

end;

 

procedureRegister;

 

implementation

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TCountBtn]);

end;

 

constructor TCountBtn.Create(aowner:Tcomponent);

begin

inherited create(Aowner);

Timer:=TTimer.Create(self);

Timer.Enabled:=false;

Timer.OnTimer:=OnTimer;

Timer.Interval:=1000;

end;

 

procedure Tcountbtn.Click;

begin

inherited click;

FCount:=Fcount+1;

Timer.Enabled:=true;

if ShowType = Normal then

Caption:=Caption;

if ShowType = CountToCaption then

Caption:='Count= '+inttostr(count);

end;

 

procedure TCountBtn.ShowCount;

begin

Showmessage('По кнопке '+ caption+' вы сделали: '+inttostr(FCount)+' клик(а/ов)');

end;

 

procedure TCountBtn.OnTimer(Sender: TObject);

begin

FCount:=FCount*2;

end;

 

end.

 

Если у вас что-то не сработало, то в начале проверьте все ли у вас написано правильно. Затем проверьте может у вас не хватает какого-нибудь модуля в разделе Uses.

 

Переустановкакомпонента

Очень часто бывает необходимо переустановить ваш компонент. Если вы попробуете сделать это путем выбора Component->Install Component, то Дельфи вас честно предупредит о том, что пакет уже содержит модуль с таким именем. Перед вами открывается окно с содержимым пакета. В нем вы должны найти имя вашего компонента и удалить его (либо нажать кнопочку Remove). Теперь в пакете уже нет вашего компонента. Затем проделайте стандартную процедуру по установке компонента.

 

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

Простой пример. Допустим у нас есть компонент (основанный на Tedit), у него есть два свойства: FirstNumber и SecondNumber. И у него есть процедура Division, в которой первое число делится на второе и результат присаивается свойству текст нашего компонента. Вот код этого компонента:

 

Code:

 

unit DivEdit;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TDivEdit = class(Tedit)

private

{ Private declarations }

FFirstNumber:integer;

FSecondNumber:integer;

FResult:Single; //в компонентах нельзя использовать Real!!!

protected

{ Protected declarations }

public

{ Public declarations }

procedure Division;

published

{ Published declarations }

constructor create(aowner:Tcomponent);override;

property FirstNumber:integer read FFirstNumber write FFirstNumber;

property SecondNumber:integer read FSecondNumber write FSecondNumber;

property Result:Single read Fresult write FResult;

end;

 

procedureRegister;

 

implementation

 

Constructor TDivEdit.create(aowner:Tcomponent);

begin

inherited create(aowner);

FFirtsNumber:=1;

FSecondNumber:=1;

end;

 

procedure TDivEdit.Division;

begin

FResult:=FFirstNumber/FSecondNumber;

text:=floattostr(FResult);

end;

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TDivEdit]);

end;

 

end.

 

Хочется обратить ваше внимание на то, что в компонентах нельзя использовать переменные и поля типа Real, вместо него нужно брать переменные типов Single, Double, Extended.

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

Code:

procedure SetSecondNumber(value:integer);

 

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

 

Code:

property SecondNumber:integer read FSecondNumber write SetSecondNumber;

 

А теперь напишем саму процедуру:

 

Code:

procedure TDivEdit.SetSecondNumber(value:Integer);

begin

if value<>FSecondNumber then//надо проверить совпадают ли исходное и вводимое значения

FSecondNumber:=value; //если нет, то изменить значение

if FSecondNumber=0then

FSecondNumber:=1;

end;

 

 

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

 

Использованиедругогокомпонентаввашем

 

Попробуем создать такой компонент. Это будет обычная метка (Label), у которой будет две процедуры: ChangeBackColor и ChangeFontColor, которые соответственно будут менять цвет фона метки и цвет текста. Для этого нам понадобиться ColorDialog, который будет создаваться вместе с компонентом, а потом с помощью процедур он будет активироваться. Назовем компонент ColorLabel. Вначале добавим в uses два модуля: Dialogs, StdCtrls (в них находятся описания классаов диалога и метки). Теперь нам надо объявить переменную типа TColorDialog. Объявление идет сразу после секции Uses.

 

Примерно это выглядит так:

Code:

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls;

var ColorDialog:TColorDialog;

type

...

 

Теперь в конструкторе (Create), нам надо создать этот компонент:

Code:

constructor TColorLabel.create(aowner:Tcomponent);

begin

Inherited Create(aowner);

ColorDialog:=TColorDialog.Create(self);

end;

 

Теперь надо объявить процедуры ChangeBackColor, ChangeFontColor. Чтобы они были доступны пользователю их надо поместить в директиву Public:

Code:

public

{ Public declarations }

procedure ChangeBackColor;

procedure ChangeFontColor;

published

 

Осталось написать сами процедуры. Все очень просто: открываете диалог методом Execute, а затем присваиваете полученное значение цвета метке. У меня эти процедуры имеют такой вид:

Code:

 

procedure TColorLabel.ChangeBackColor;

begin

if ColorDialog.Execute then

color:=ColorDialog.color;

end;

 

procedure TColorLabel.ChangeFontColor;

begin

if ColorDialog.Execute then

font.color:=ColorDialog.color;

end;

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

unit ColorLabel;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls;

var ColorDialog:TColorDialog;

type

TColorLabel = class(Tlabel)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

procedure ChangeBackColor;

procedure ChangeFontColor;

published

{ Published declarations }

constructor create(aowner:tcomponent);override;

end;

 

procedureRegister;

 

implementation

 

constructor TColorLabel.create(aowner:Tcomponent);

begin

Inherited Create(aowner);

ColorDialog:=TColorDialog.Create(self);

end;

 

procedure TColorLabel.ChangeBackColor;

begin

if ColorDialog.Execute then

color:=ColorDialog.color;

end;

 

procedure TColorLabel.ChangeFontColor;

begin

if ColorDialog.Execute then

font.color:=ColorDialog.color;

end;

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TColorLabel]);

end;

end.

 

Доступксвойствамдругогокомпонента

Сейчас нам предстоит более сложная задача. Мы будем создавать компонент, вместе с которым будет создаваться какой-нибудь визуальный компонент. Например создадим кнопку, которая будет сопровождаться поясняющей надписью сверху. За основу возмем тип TButton. Нам надо будет создать еще и Label. Здесь существует одна проблемка: при перемещении компонента по форме, метка должна двигаться вместе с кнопкой, поэтому нам придется обрабатывать сообщение WmMove. Итак, объявляем переменную Label (в данном примере она объявлена в директиве Private, что тоже допустимо):

 

Code:

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls,buttons;

 

type

TLabelButton = class(TButton)

private

FLabel : TLabel ;

 

 

Теперь я приведу весь код этого компонента и походу буду вставлять необходимые пояснения:

Code:

unit LabelBtn;

interface

 

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls,buttons;

 

type

TLabelButton = class(TButton)

private

FLabel : TLabel ; {создаем поле типа Tlabel}

procedure WMMove( var Msg : TWMMove ) ; message WM_MOVE ;{процедура для обработки сообщения Wm_move, чтобы метка перемещалась вместе с кнопкой}

protected

procedure SetParent( Value : TWinControl ) ; override ;{необходимо воспользоваться и этой процедурой, так как нужно убедиться, имеют ли кнопка и метка общего предка}

function GetLabelCaption : string ; virtual ; {Вот пример доступа из компонента к свойствам другого. Эти две процедуры для изменения текста метки}

procedure SetLabelCaption( const Value : string ) ; virtual ;

public

constructor Create( AOwner : TComponent ) ; override ;

destructor Destroy ; override ;

published

property LabelCaption : stringread GetLabelCaption write

SetLabelCaption ;

end;

 

procedureRegister;

 

implementation

 

constructor TLabelButton.Create( AOwner : TComponent ) ;

begin

inherited Create( AOwner ) ;

{ создаем TLabel }

FLabel := TLabel.Create( NIL ) ;

FLabel.Caption := 'Описание:' ;

end ;

 

procedure TLabelButton.SetParent( Value : TWinControl ) ;

begin

{надо убедиться, что у них предок один, чтоб проблем потом не было}

if ( Owner = NIL ) ornot ( csDestroying in Owner.ComponentState ) then

FLabel.Parent := Value ;

inherited SetParent( Value ) ;

end ;

 

destructor TLabelButton.Destroy ;

begin

if ( FLabel <> NIL ) and ( FLabel.Parent = NIL ) then

FLabel.Free ;{Уничтожаем метку, т.к. она нам больше не нужна}

inherited Destroy ;

end ;

 

function TLabelButton.GetLabelCaption : string ;

begin

Result := FLabel.Caption ;

end ;

 

procedure TLabelButton.SetLabelCaption( const Value : string ) ;

begin

FLabel.Caption := Value ;

end ;

 

procedure TLabelButton.WMMove( var Msg : TWMMove ) ;

begin

inherited ;

if FLabel <> NILthenwith Flabel do

SetBounds( Msg.XPos, Msg.YPos - Height, Width,Height ) ; {изменяем левое и верхнее положение метки исходя из полученных координат}

end;

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TLabelButton]);

end;

 

initialization

RegisterClass( TLabel ) ; {Это делается для обеспечения поточности, но об этом не думайте, этим редко придется пользоваться}

end.{Вы можете пользоваться этим компонентом сколько угодно, но распространять его можно только указывая авторство}

 

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

 

Использованиевкачествепредкакласс TWinControl

Предыдущий пример был очень сложным, к тому же пришлось обрабатывать системные сообщения. Есть другое решение этой проблемы, более простое для понимания и для реализации: использовать в качестве контейнера класс TWinControl и в этот контейнер помещать другие компоненты. Теперь попробуем совместить Edit и Label. Давайте вместе создадим такой компонент. В качестве предка нужно выбрать класс TWinControl, а в качестве типа вашего компонента выберите TlabelEdit. Будем разбирать код по кусочкам.

 

Code:

unit LabelEdit;

interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

Forms, Dialogs, stdctrls;

type

TLabelEdit = class(TWinControl)

private

{ Private declarations }

FEdit: TEdit;

FLabel: TLabel;

//Здесь объявляются поля для метки и для Edita.

function GetLabelCaption: string;

procedure SetLabelCaption(LabelCaption: string);

function GetEditText: string;

procedure SetEditText(EditText: string);

//Здесь объявлены функции для работы со свойствами Caption у метки и Text у Edita.

protected

{ Protected declarations }

public

{ Public declarations }constructor Create(AOwner: TComponent); override; published

property LabelCaption: stringread GetLabelCaption write SetLabelCaption;

property EditText: stringread GetEditText write SetEditText;

{ Published declarations }

end;

 

procedureRegister;

 

implementation

 

constructor TLabelEdit.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FEdit := TEdit.Create(self);{создаем поле редактирования Edit}

FLabel := TLabel.Create(self);{создаем Label}

with FLabel dobegin

Width := FEdit.Width;

visible := true;

Parent := self;

Caption := 'Описание:';

end;

with FEdit dobegin

Top := FLabel.Height+2;

Parent := self;

Visible := true;

end;

Top := 0;

Left := 0;

Width := FEdit.Width;

Height := FEdit.Height+FLabel.Height;{определяются размеры и положение компонентов}

Visible := true;

end;

 

function TLabelEdit.GetLabelCaption: string;

begin

Result := FLabel.Caption;

end;

 

procedure TLabelEdit.SetLabelCaption(LabelCaption: string);

begin

FLabel.Caption := LabelCaption;

end;

 

function TLabelEdit.GetEditText: string;

begin

Result := FEdit.Text;

end;

 

procedure TLabelEdit.SetEditText(EditText: string);

begin

FEdit.Text := EditText;

end;

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TLabelEdit]);

end;

 

end.

 

Попробуйте установить этот компонент. Когда вы будете размещать его на форме, то будет виден "контейнер", на котором располагаются Edit и Label. Использование в качестве предка компонента класса TWinControl, очень удобно если вы хотите объединить несколько визуальных компонентов.

 

 

Обработкасобытий OnMouseDown, OnMouseMove и OnMouseUp

Часто возникает необходимость обработки событий нажатия и отпускания кнопки в вашем компоненте. Сейчас мы это и рассмотрим. Только ради примера сделаем компонент, который будет считать количество нажатий и отпусканий кнопки в его области, допустим это будет панель (Tpanel). Для этого в директиве Private надо объявить следующие процедуры и поля:

Code:

FClickCount:integer;

FUpCount:integer;

procedure MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); override;

procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;

procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); override;

А в директиве Published надо написать:

Code:

constructor create(aowner:tcomponent);override;

property ClickCount:integer read FclickCount write FClickCount;

property UpCount:integer read FUpCount write FUpCount;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

 

Ну и теперь осталось описать нужные процедуры:

Code:

procedure TMpanel.MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer);

begin

FClickCount:=FClickCount+1;

end;

 

procedure TMpanel.MouseMove(Shift: TShiftState; X, Y: Integer);

begin

caption:=inttostr(x)+' '+inttostr(y);{для демонстрации работы этой процедуры. Надпись на панели будет отражать координаты курсора мыши над этой панелью}

end;

 

procedure TMpanel.MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer);

begin

FUpCount:=FUpCount+1;

end;

 

Таким образом весь код компонента был таким:

Code:

unit Mpanel;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

ExtCtrls;

 

type

TMpanel = class(TPanel)

private

{ Private declarations }

FClickCount:integer;

FUpCount:integer;

procedure MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); override;

procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;

procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); override;

 

protected

{ Protected declarations }

public

{ Public declarations }

published

{ Published declarations }

constructor create(aowner:tcomponent);override;

property ClickCount:integer read FclickCount write FClickCount;

property UpCount:integer read FUpCount write FUpCount;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

end;

 

procedureRegister;

 

implementation

 

constructor TMpanel.create(aowner:Tcomponent);

begin

inherited create(aowner);

end;

 

procedure TMpanel.MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer);

begin

FClickCount:=FClickCount+1;

end;

 

procedure TMpanel.MouseMove(Shift: TShiftState; X, Y: Integer);

begin

caption:=inttostr(x)+' '+inttostr(y);

end;

 

procedure TMpanel.MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer);

begin

FUpCount:=FUpCount+1;

end;

 

procedureRegister;

begin

RegisterComponents('Mihan Components', [TMpanel]);

end;

 

end.

 

 

Созданиеииспользованиесвоейиконкидлякомпонента

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

Откройте Image Editor (Tools->Image Editor) и выберите File->New->Component Resourse File. Перед вами появится небольшое окно с надписью Untitled.dcr в нем будет только одно слово: Contents. Нажмите на него правой кнопкой и в появившемся меню выберите New->Bitmap. Откроется диалоговое окно для настройки параметров изображения. Они должны быть такими: Размер 32x32, цветовой режим VGA (16 colors). Теперь нажмите ok. Теперь надо нажать правой кнопкой на появившейся надписи Bitmap1 и выбрать пункт Rename. Название картинки должно совпадать с названием класса компонента, для которого вы делаете эту иконку (например, TMPanel). Нажмите два раза на Bitmap1 и перед вами появится окно для рисования. Нарисуйте, что вам надо и перейдите на окно с надписью Untitled.dcr и в меню File выберите Save. Имя файла ресурса компонента должно совпадать с именем модуля компонента (без расширения конечно же, например, Mpanel). Файл ресурса готов. Теперь установите ваш компонент заново и в палитре компонентов ваш компонент будет уже с новой иконкой.

Источник: https://delphid.dax

 

Code:

uses TypInfo;

 

{ .... }

 

procedure TForm1.Button1Click(Sender: TObject);

var

x, y, z: Word;

pl: PPropList;

begin

y := GetPropList(Self, pl);

for x := 0to y - 1do

begin

if Copy(pl[x].Name, 1, 2) <> 'On'then Continue;

if GetMethodProp(Self, pl[x].Name).Code <> nilthen

Memo1.Lines.Add(Self.Name + ' - ' + pl[x].Name);

end;

for z := 0to Self.ComponentCount - 1do

begin

y := GetPropList(Self.Components[z], pl);

for x := 0to y - 1do

begin

if Copy(pl[x].Name, 1, 2) <> 'On'then Continue;

if GetMethodProp(Self.Components[z], pl[x].Name).Code <> nilthen

Memo1.Lines.Add(Self.Components[z].Name + ' - ' + pl[x].Name);

end;

end;

end;

 

 

 

 

 

Взято с сайтаhttps://www.swissdelphicenter.ch/en/tipsindex

Перед созданием своего компонента нужно выбрать для него предка. Кто же может быть предком для вашего компонента? Как правило, используются в виде предков TComponent, TControl, TWinControl, TGraphicControl, TCustomXXXXXX, а также все компоненты палитры компонентов. Возьмем для примера компонент TOpenDialog, который находится на странице Dialogs палитры компонентов. Он хорошо справляется со своей задачей, но у него есть одно маленькое неудобство. Каждый раз, когда его используешь необходимо каждый раз изменять значение свойства Options. И причем это, как правило, одни и те же действия.

 

Code:

OpenDialog1.Options := OpenDialog1.Options + [ofFileMustExist, ofPathMustExist];

 

чтобы файл, который мы пытаемся открыть с помощью этого диалогового окна, действительно существовал на диске.
Задание для себя мы уже выбрали, осталось за малым - создать компонент. Заготовку для компонента создаем, выбирая из меню команду Component/New Component... и в диалоговом окне выбираем
Ancestor type: TOpenDialog
Class Name: TOurOpenDialog
Palette Page: Our Test
Нажали Ok и у нас появился шаблон нашего будущего компонента. Переопределяем конструктор у этого компонента, т.е. в секции public вставляем строку:

Code:

constructor Create(AOwner: TComponent); override;

нажатие на этой строке Ctrl + Shift + C создает шаблон для этого метода, внутри которого мы вставляем такие строки:

Code:

inherited Create(AOwner); {Вызываем унаследованный конструктор}

Options := Options + [ofFileMustExist, ofPathMustExist]; {Выполняем необходимые нам действия}

Обратите внимание: Комбинации клавиш Ctrl + Shift + стрелки вверх/вниз позволяют перемещаться между объявлением метода и его реализацией. Установка созданного компонента Component/Install Component...
Install Into New Package
Package file name: C:\Program Files\Borland\Delphi4\Lib\OurTest.dpk
Package description: Our tested package Вам не нравится, что у нашего компонента иконка такая же как у стандартного? Тогда создадим для него свою собственную. Для этого нам необходимо вызвать Tools/Image Editor. Создаем новый *.dcr файл.
Вставляем в него рисунок Resource/New/Bitmap. Устанавливаем размер картинки 24x24 точек. А дальше - ваше творчество... Обратите внимание: цвет точек, совпадающий с цветом точки в левом нижнем углу рисунка, будет считаться ПРОЗРАЧНЫМ! После того как вы создали свой рисунок, переименуйте его из Bitmap1 в TOurOpenDialog и сохраните файл с именем OurOpenDialog.dcr. Удалите компонент из пакета и установите его снова (только в этом случае добавится и ссылка на *.dcr файл). Compile, Install и удачи!

 

Code:

{======================================================}

unit OurOpenDialog;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

 

type

TOurOpenDialog = class(TOpenDialog)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

published

{ Published declarations }

end;

 

procedure register;

 

implementation

 

procedure register;

begin

RegisterComponents('Samples', [TOurOpenDialog]);

end;

 

{ TOurOpenDialog }

 

constructor TOurOpenDialog.Create(AOwner: TComponent);

begin

inherited Create(AOwner); {Вызываем

унаследованный конструктор}

Options := Options + [ofFileMustExist, ofPathMustExist];

{Выполняем необходимые нам действия}

end;

end.

 

Объявление компонента состоит из секций, таких как private, protected, public и published. Что они означают?
Это директивы видимости. Все что объявлено в секции private, доступно только внутри модуля в котором объявлен класс (приватные объявления). Здесь как правило объявляются переменные, в которых хранятся значения свойств, а также методы (процедуры или функции) доступа к ним. Все что объявлено в секции protected, доступно как и в секции private, а также наследникам данного класса (интерфейс разработчика). Здесь можно объявить методы доступа к значениям свойств (если вы хотите позволить изменять эти методы потомкам вашего компенента),
а также свойства, методы и события (методы реакции на события) в компонентах типа TCustomXXX.
Все что объявлено в секции public, доступно любому пользователю компонента (интерфейс этапа выполнения).
Здесь объявляются, как правило методы. В секции published можно объявлять только свойства и события (они объявляются в виде свойств). Они доступны во время проектирования приложения (интерфейс этапа проектирования).

 

Свойства

Свойства типа масив - обычные массива Object Pascal, но в отличии от последних могут индексироваться не только числовыми значениями но и строковыми. К сожалению этот тип свойства требует пользовательского редактора свойств (в инспекторе объектов редактор свойства имеет кнопку с тремя точками [...]), по-этому в указанном ниже примере свойство ArrayProp объявлено в секции public.

 

Code:

type

TOurComponent = class(TComponent)

private

{ Private declarations }

FArrayProp: array[0..9] of integer;

function GetArrayProp(aIndex: integer): integer;

procedure SetArrayProp(aIndex: integer; const

Value: integer);

protected

{ Protected declarations }

public

{ Public declarations }

property ArrayProp[aIndex: integer]: integer read

GetArrayProp

write SetArrayProp;

published

{ Published declarations }

end;

 

Спецификаторысвойств

Спецификаторdefaultуказываетсохранятьзначениесвойствавфайлеформыилинет. Еслизначениесвойствасовпадаетсозначениемdefault - значениевфайлеформынесохраняется, еслизначениянеравны - сохраняется. Этоможнопроверить, положивкомпонентнаформуивыбратьправойкнопкоймышипунктменю "View as Text". Defaultнеустанавливаетпервоначальноезначениесвойствакуказанному. Этонеобходимосделатьвконструкторекомпонента.

Code:

unit OurComponent;

 

interface

 

uses Windows, SysUtils, Classes, Graphics, Forms, Controls;

 

type

TOurComponent = class(TComponent)

private

{ Private declarations }

FMyInteger: Integer;

protected

{ Protected declarations }

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

published

{ Published declarations }

property MyInteger: Integer read FMyInteger

write FMyInteger default10;

end;

 

implementation

 

constructor TOurComponent.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FInteger := 10;

end;

 

end.

 

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

Например:

Code:

property AutoSize nodefault;

 

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

Code:

property OneProp: integer read FOneProp

write

SetOneProp

stored False;

property TwoProp: integer read FTwoProp

write

SetTwoProp

stored True;

property ThreeProp: integer read FThreeProp

write SetThreeProp

stored Fuct;

 

 

Автор: АлександрВасилевский
Источник: https://alvas.hypermart

Пример стандартного присвоения события в run-time:

 

Code:

type

{©Drkb v.3(2007): www.drkb.ru,

®Vit (Vitaly Nevzorov) - Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.}

 

TForm1 = class(TForm)

Button1: TButton;

procedure FormCreate(Sender: TObject);

private

procedure Click(Sender: TObject);

end;

 

var Form1: TForm1;

 

implementation

 

procedure TForm1.Click(Sender: TObject);

begin

// do something

end;

 

procedure TForm1.FormCreate(Sender: TObject);

begin

button1.OnClick:=Click;

end;

 

end.

 

 

 

 

Автор:Vit  

Взято с Vingradruhttps://forum.vingrad

Self - это явное задание экземпляра класса в его методе.

 

Например для твоей формы это указание на саму форму:

 

Code:

procedure TForm1.Button1Click(Sender: TObject);

begin

showmessage(self.classname+#13#10+self.name);

end;

 

 

Если например это MDI форма то это будет указатель именно на тот экземпляр для которого выполняется этот код. На практике Self обычно применяется при написании своих классов, когда ты пишешь класс или компонент, то у тебя нет переменной с экземпляром этого компонента, следовательно чтобы обратится к экземпляру (который появится только в коде конечного пользователя, который будет использовать компонент) класса нужна переменная - вот она и берётся за self.

 

 

Автор:Vit  

Взято с Vingrad.ruhttps://forum.vingrad

 

 

 


 

 

Чтобы понять, что такое self надо понять что такое метод класса. Метод класса - это просто функция(процедура) который имеет дополнительный неявный параметр - указатель на экземпляр класса. То есть:

 

Code:

TMy=class

x:integer;

procedure Proc(val:integer);

end;

 

procedure TMy.Proc(val:integer);

begin

x:=val;

end;

 

После компиляции это будет практически то же самое, что:

 

Code:

procedure Proc(self:TMy;val:integer);

begin

self.x:=val;

end;

 

 

То есть на самом деле в методе Proc обращаясь к x мы на самом деле обращаемся к self.x, просто переменная self опускается. В откомпилированном коде нет такого понятия как классы - есть только код и память. Все методы классов превращаются в обыкновенные функции, в которым качестве первого параметра передается указатель на область памяти где лежит созданный пользователем экземпляр класса, который они и используют для чтения или записи(а так же для вызова) того, что мы называем членами класса.

Code:

var

m1,m2:TMy;

begin

.....

m1.Proc(4); // -> Proc(m1,4)

m2.Proc(4); // -> Proc(m2,4)

end;

 

 

 

 

 

Автор:Fantasist

Взято с Vingradruhttps://forum.vingrad

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

 

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

 

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

 

Полную победу объекты одержали после воцарения Windows: теперь без них в программировании просто не обойтись. Delphi – не исключение, она в своей основе является объектно-ориентированной и говорить о программировании без объектов про бессмысленно. Чтобы вы не рылись в других книгах, собирая информацию по крохам, мы не поленились и собрали в этой главе все, что нужно знать об объектах Delphi. Для новичка важнейшее здесь: инкапсуляция, наследование, полиморфизм, остальное можно просто просмотреть и возвращаться к материалу по мере накопления опыта. Профессионалу полезно прочитать внимательно все от начала до конца. Так что давайте засучим рукава и приступим к делу.

Self может быть использовано только в методе класса, и ссылается на текущий экземпляр класса. Таким образом "Self" в методе класса TForm1 ссылается на текущий экземпляр TForm1. При создании компонента Вы передаете его владельца (owner) в конструктор. При уничтожении формы или компонента автоматически уничтожаются и все компоненты владельцем которого она является. Таким образом если при создании формы передать в качестве владельца Application эта форма будет автоматически уничтожена при уничтожении Application. Если же при создании формы передать в качестве владельца другую форму, вновь созданная форма будет автоматически уничтоженна при уничтожении формы-владельца.

Автор: Владимир Волосенков

 

Музыку любите, а на инструменте неприличное слово нацарапали.

"Республика ШКИД"

 

 

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

 

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

Исходный текст я буду приводить курсивом. Т.к. в статье в основном сравнивается C++ и Delphi, то вместо Pascal или Object Pascal будет использоваться сокращение ОР. Т.к. автор в своем повествовании не ограничивался сравнением только безопасности программирования в С++ и ОР, то я также позволю себе сравнения по всем аспектам. Конечно, только в рамках технических фактов.

 

Кроме того, в скобках иногда будут встречаться комментарии за подписью КоТ. Это замечания одного непрофессионального программиста по поводу моих и Дмитрия размышлений. Пишет он в С++ и исключительно под Linux, называет себя не иначе, как глупым ламером. Впрочем, исходя хотя бы из того, что обычно настоящие ламеры себя таковыми не считают, его высказывания весьма интересны и часто к месту. Итак, приступим.

 

Сразу замечу, что размер страницы памяти для процессоров Intel и MIPS составляет 4К, а для Alpha - 8K (а не 2 и 4К соответственно).

 

Начнем с принципиальных отличий в модели обработки исключений в С++ от Делфи. И какие это порождает гадости (КоТ: почему именно гадости?). В первую очередь, Борланд ввел некоторые ограничения на перегенерацию собственных исключений. Вырезка из Help:

1) You cannot rethrow an operating system exception once the catch frame has been exited and have it be caught by intervening VCL catch frames.

2) You cannot rethrow an operating system exception once the catch frame has been exited and have it be caught by intervening operating system catch frames.

3) You cannot use "throw"(аналог Делфийского raise) to reraise an exception that was caught within VCL code.

 

Приведенная автором вырезка в Delphi Help отсутствует. Да и с какой стати там будет указываться ключевое слово "throw" из С++? Более всего это похоже на вырезку из хелпа C++ Builder. Соответственно и ограничения на работу с более мощной моделью исключений ОР, используемой в VCL (доказательства будут ниже). Выводы делаем сами…

 

Рассматривая модель ООП в Делфях и модель ООП в С++, легко прийти к выводу, что функционально модель С++ шире, и поэтому Борландовский Буилдер легко "глотает" делфийский VCL.

 

Используя модель ООП С++, создать, к примеру, среду Delphi или библиотеку VCL невозможно в принципе (если не касаться разработки новых компиляторов). Это было неоднократно доказано в дискуссиях с другими фанатами С++. Как ограничения выступают отсутствие классовых ссылок, виртуальных конструкторов и ущербная модель RTTI в С++. Не буду утверждать, как работает C++ Builder, но подозреваю, что ключевые моменты работы среды с компонентами написаны на ОР.

 

Думаю, если бы С++ позволял написать VCL, то Delphi пришлось бы сейчас "глотать" чужой код. Но пока все наоборот. Кстати, Borland имела прекрасную возможность пересмотреть свои воззрения на языковую основу VCL при разработке Kylix (этот проект включает и ОР и С++). Однако революции не произошло. Революция уже случилась в 95-м году с выходом Delphi 1 :)

 

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

 

(КоТ: Никакого плюса не вижу, говорю как С++ - программер. Мало геморроя с распределением динамической памяти, так еще и со всеми другими. Из-за этого я на линух от доса перешел - кстати. И вообще, на мой (ламерский) взгляд, распределение памяти - вопрос не к языку, а к мемори-модели операционной системы.)

 

В Делфи классы (объекты) могут располагаться только в динамической памяти

 

Что, безусловно, добавляет той самой безопасности программирования. К примеру, функция может вернуть ссылку на объект в стеке, который уже уничтожен. (КоТ: такие ошибки у меня часто были в досовском паскале. Именно тогда я привык инициализировать все переменные в процессе декларирования.) Если Дмитрий интересовался вопросами сборки мусора, то не мне ему объяснять, что сделать это в одной динамической памяти куда легче.

 

Из этого вытекает следующее отличие. Все конструкторы и деструкторы классов Паскаля вызываются явно.

object := TMyObject.Create. // где-то в начале

//....

object.Free; // где-то в конце

С одной стороны хорошо. Все ясно, как никогда. Но это специфика Паскаля заставляет программера делать уйму работы, и порой ошибаться (КоТ: а что, С++ прямо так вот и гарантирует безошибочность?) Частенько бывает необходимо иметь "неявный" вызов или конструктор "по умолчанию". Конструктор класса С++, например, вызывается как только встречается описание экземпляра (переменной) класса. И, соответственно, деструктор вызовется, как только класс "выйдет из области видимости".

 

(КоТ: опять подмена терминов. Т.е. банально нечестная игра. Справедливей, имхо, сказать, что в определенных задачах приходится не надеяться на механизм порождения классов дельфы. Но ведь и в С++ есть точно такие же ситуации - где-то ты можешь положиться на язык (компилятор), где-то - не можешь. Так в чем же преимущество?)

 

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

 

А что касается области видимости класса и времени жизни, то это элементарно организуется использованием интерфейсов. Всю работу по подсчету количества ссылок и автоматическому менеджменту памяти возьмет на себя Delphi. (КоТ: кстати, в том же С++ такой же механизм я сам организовывал часов за восемь. Не скажу, что просто и легко, но возможно. Минус - лишний геморрой, плюс - можешь сделать сам, какой нужно, с точностью до битовых полей и регистров.) В Delphi этот механизм также может быть легко реализован по-своему.

 

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

TComponentClass = class of TComponent; //ссылка на класс

Code:

function CreateAny(AType: TComponentClass): TComponent;

begin

Result := AType.Create(nil);

end;

Form1.InsertComponent(CreateAny(TButton));

// Создали кнопку

Form1.InsertComponent(CreateAny(Edit1.ClassType));

// Создали еще одно поле редактирования

 

Скажем прямо, такие решения в С++ недоступны. В качестве лирического отступления можно сказать, что именно на этом основана работа Delphi IDE с любым компонентом.

 

Вас не удивляло, что Delphi способна не то что без перекомпиляции, а даже без перезапуска брать внешние, абсолютно не знакомые ей классы (компоненты, которые можно инсталлировать хоть каждые 5 минут, тип которых, конечно, неизвестен) и строить на их основе другие классы (формы и т.д.) в run-time (для разработчика design-time)?

 

(КоТ: круто, конечно)

 

Очень занимательный вопрос, скажу я вам. Прикиньте, как бы вы реализовали это в своем приложении. Механизм должен быть очень универсальным, работающим для любого компонента. Компоненты поставляются, например, в виде DLL (или packages - разновидность DLL). Тут никакая RTTI в чистом виде не поможет. Применительно к этой задаче даже шаблоны С++ абсолютно бесполезны, т.к. они являются механизмом compile-time only.

 

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

 

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

 

TComplex* c; // переменная указатель на тип TComplex - ниче не вызывается.

 

(КоТ: ну кто же в софтине будет САМ создавать указатель на пустое место? Зачем? Чтобы stack error'ом по хоботу получить? Объявил переменную - инициализируй!!! Вот так:

TСomplex* c=new TComplex(1,1)

// "а будешь делать не так, надеру уши" (с) Зеф, "Обитаемый остров")

 

c = new TComplex(1,1); // выделяется память под TComplex и вызывается его конструктор с параметрами.

delete c; // освобождаем память предварительно вызвав деструктор Tcomplex

 

Вот здесь работа с классами похожа на Делфийскую работу. Похожа-то, похожа - да не совсем.

. (КоТ: на CENSORED похожа, да и работы я здесь не вижу что-то.)

 

Во-первых: как вы успели заметить new и delete - это операторы. Значит их можно переопределять (КоТ: кстати, НЕ ВСЯКИЙ оператор С++ переопределяется). Значит, где захочу - там и будут лежать мои классы. Так можно организовать несколько куч, даже не имея "много-кучевого" менеджера ОС. Я позже опишу, как это влияет на безопасность

 

Странно, но Дмитрию не известно, что управление памятью классов в ОР реализовано даже не с помощью операторов, а гораздо красивее - на уровне TObject, виртуальными (!) методами NewInstance и FreeInstance. Таким образом, абсолютно ЛЮБОЙ класс может переопределить эти методы для осуществления желания "где хочу - там и буду лежать". Соответственно организуется и "многокучность".

 

Во-вторых: здесь всплывает понятие "ВРЕМЯ ЖИЗНИ КЛАССА" и то, как обрабатываются исключения в конструкторах и деструкторах. Рассмотрим это поближе. В Делфи время жизни класса таково:

Рождение: Класс начинает свое существование сразу ПОСЛЕ окончания работы КОНСТРУКТОРА(вызов AfterConstruction).

Смерть: Класс заканчивает свое существование сразу ПОСЛЕ окончания работы ДЕСТРУКТОРА(вызов BeforeDestruction).

 

Неправильно. Before он на то и Before, чтобы отрабатывать ДО вызова деструктора. И это вовсе не значит, что класс уже уничтожен. Для справки: BeforeDestruction введен для того, чтобы создатель класса был уверен, что необходимые действия перед его уничтожением будут выполнены всегда, независимо от того, вызовут или нет его потомки унаследованный деструктор. По поводу AfterConstruction разговор будет чуть позже.

 

Кроме того, и конструкторы и деструкторы в ОР имеют приятную особенность (и далеко не одну). Они могут вызываться как обычные методы. Для этого в них передается неявный параметр. Не путать с неявным Self или this. Кстати, Self в классовых методах ОР является классовой ссылкой, а не объектной.

 

Так что вопросы рождения и смерти в ОР далеко не так тривиальны. Впрочем, самые интересные подробности еще впереди.

 

В С++ немножечко по другому:

Рождение: Сразу ПЕРЕД телом конструктора.

Смерть: Сразу ПОСЛЕ тела деструктора.

Это несколько меняет работу с конструкторами/деструкторами родителями и конструкторами/деструкторами членами. Вот С++:

class TChild : public TMama,TPapa{ // :o)

TMemberOne member_1_;

TMembarTwo member_2_;

public:

TChild() { cout<<"TChild created!"; }

}

 

Порядок конструкторов будет следующий: TMama, TPapa, TMemberOne, TMemberTwo и только потом вызовется ТЕЛО конструктора TChild. Это логично и похоже на правду. (КоТ: немножко беременной быть можно? Это похоже на правду, или это правда? Разницу чувствуете?) Действительно, когда мы можем получить доступ к методам и полям(переменным класса) родителей и классов-членов(конкретных классов)? Мы можем получить этот доступ только, когда они сконструированы. И это лучше оставить на совести компилятора, чем надеяться на программера.

 

Вообще типичной идеологией компилятора С++ считается: "Ну, парень, если ты хочешь сделать именно так, делай, а я умываю руки". А тут такая удивительная забота о программере! Только вот она в данном случае совсем не к месту, по крайней мере, в таком виде. Как контраст - конструкторы ОР.

 

Допустим, у нас есть иерархия классов A -> B -> C. Мы конструируем класс С. Действительно, в С++ последовательность конструирования будет A -> B -> C. И никак иначе.

 

Теперь признайтесь, когда вы пишите конструктор в ОР, вы ведь первым делом указываете вызов inherited. Да? В этом случае последовательность конструирования абсолютно аналогична. Но! Стоит вам убрать inherited, и Delphi будет конструировать класс C в последовательности C -> B -> A. Неплохо для начала, но это еще цветочки.

 

Незаметное inherited дает вам полный контроль над тем КАК, КОГДА и КАКИЕ конструкторы будут вызываться (и будут ли вызываться вообще, ведь inherited можно и в if засунуть). Нет никаких ограничений на расположение inherited в теле конструктора. А ведь его еще можно дополнить именем конкретного конструктора предка с указанием нужных параметров. Ну и, конечно, можно вызывать собственные конструкторы. (КоТ: это здорово, однако).

 

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

 

Прозаический пример. В конструкторе С создается некоторый объект (аллокатор памяти, например), который используется для работы в конструкторе предка B. Другой наследник, класс D, может создавать совершенно другой объект. Создание этого объекта можно вынести в виртуальную функцию, которую вызывать перед inherited.

 

Да, такие возможности используются не слишком часто, но им есть реальное применение. Показательно, что подобный подход нереализуем в С++ никакими способами. Он никогда не даст создать что-то ПЕРЕД работой конструктора предка.

 

(КоТ: сорри, сир! Переопределив new под это дело (кстати, одно из упражнений в каком-то С++-учебнике), вполне возможно и вызвать. Только потом приходится delete лечить - он-то базируется на стандартных умолчаниях. То есть, данных объекта нет, и объекту адрес не выделен, но VMT его есть. В библиотеке или там где еще, не суть. И к этой VMT можно добраться через разную там… гм… CENSORED Плюс дельфы в откровенности доступа ко всем VMT проекта, независимо, созданы ли объекты соответствующих классов).

 

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

 

(КоТ: От слабости кричать. Т.к. это, безусловно, бонус дельфе перед С++, но и С++-модель определенные преимущества все-таки имеет).

 

Более того, вызов указанной виртуальной функции совершенно бесполезен. Почему? В С++ при работе каждого из конструкторов A, B, C таблица виртуальных методов VMT будет соответствовать именно тому классу, к которому принадлежит конструктор. Т.е. вызов ЛЮБЫХ виртуальных методов в конструкторе С++ теряет всякий смысл, т.к. не является виртуальным (будет вызван соответствующий метод для класса А или В, а не для С). То же касается и деструкторов С++.

 

В ОР при работе любого из конструкторов предков VMT всегда соответствует РЕАЛЬНОМУ создаваемому классу, т.е. классу С. Вызовы виртуальных методов будут правильными. В принципе, это может создать опасную ситуацию, когда в данном виртуальном методе какой-то из наследников подразумевает, что класс уже полностью сконструирован. Именно для разрешения этой проблемы и существует виртуальный метод AfterConstruction.

 

Теперь мы четко видим, что конструкторы ОР обладают НАМНОГО большей гибкостью и мощью. Конечно, при условии, что программист понимает, что делает. (КоТ: при условии, что программист понимает, что делает, и С++ не так уж плох ;-) А это не так уж и сложно. По крайней мере, практика показывает, что эти конструкторы не доставляют никаких хлопот программистам. А значит, увеличение мощи не уменьшило "безопасности программирования" :) Продолжим.

Code:

class E: public A,B {

C* c_;

D* d_;

public:

E(); // реализацию см.ниже

~E() { delete d_; delete c_; }

}

E::E() // конструктор класса E

try

: A(1), B(1), c_( new C ), d_( new D ) // список инициализации

{ //начало тела конструктора

cout<<"Constructor body";

}// конец тела конструктора

catch(...){ // ловим любое исключение

A::~A();

B::~B();

delete c_;

delete d_;

}

 

 

 

Непривычное написание, не так ли? Да, в Делфях нельзя ВЕСЬ процесс конструирования поместить в блок try except.

 

Неправильно! Скорее можно сказать, что в ОР нельзя НЕ поместить весь процесс конструирования в блок try…except. При вызове конструктора ОР как классового (статического, в терминах С++) метода (т.е. через классовую ссылку) блок try…except устанавливается АВТОМАТИЧЕСКИ. При возникновении любого необработанного исключения в конструкторе автоматически вызывается деструктор. Это, однако, не мешает вписать в тело конструктора свои блоки обработки исключений, в том числе и для полной, безопасной обработки некоторых их типов.

 

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

 

В случае классового метода:

устанавливается блок try…except;

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

размер экземпляра можно получить методом InstanceSize;

память нужно очистить методом InitInstance;

отрабатывает тело конструктора;

вызывается виртуальный метод AfterConstruction.

В случае обычного метода выполняется только тело конструктора. Блок try…except НЕ устанавливается. Так вызываются все собственные конструкторы и конструкторы предков из тела какого-либо конструктора класса (они все равно попадут в установленный блок обработки исключений). Конструктор может быть вызван где угодно. Главное - использовать объектную ссылку (Self.Create), а не классовую. Условно, реальный код конструктора мог бы выглядеть так:

Code:

function TSomething.Create(IsClassRef: boolean): TSomething;

begin

if IsClassRef then

try

Self := TSomthing.NewInstance;

InitInstance(Self);

Self.Create(False); // Тело конструктора,

// написанное разработчиком

Self.AfterConstruction;

except

Self.Destroy; // Если что - харакири :)

end

else

Self.Create(False); // Тело конструктора

Result := Self;

end;

 

 

Аналогичная песня с деструкторами. Но здесь обойдемся без лишних объяснений:

Code:

procedure TSomething.Destroy(Deallocate: boolean);

begin

if Deallocate then

Self.BeforeDestruction;

Self.Destroy(False);

if Deallocate then

begin

Self.CleanupInstance;

Self.FreeInstance;

end;

end;

 

 

 

Еще раз замечу, что это чисто гипотетический код, создаваемый компилятором, а не реализация конкретного класса. Конечно, в нем нет никаких рекурсивных вызовов. Продолжим.

 

Но как же быть с динамическими ресурсами? Спросите вы. Все очень просто:

Code:

E::E() // конструктор класса E

try

: A(1), B(1), c_( NULL ), d_( NULL ) // список инициализации

{ //начало тела конструктора

try{

c_ = new C;

d_ = new D;

cout<<"Constructor body";

}

catch(...){

if(c_) delete C;

if(d_) delete D;

throw;

}

} // конец тела конструктора

catch(...){ // ловим любое исключение

throw E_ErrorCreate();

}

 

 

Видно, что я использовал блок try...catch только для "перевода" одного исключения в другое. И назначение этого блока только такое и никакого другого. Использование его в других целях может привести к гадостям (КоТ: если ножом кухонным неправильно пользоваться, это МОЖЕТ привести даже к смерти… Но ведь не обязательно же приводит! Так претензии к ножу (языку), или к кривым рукам?) ,поэтому в некоторых С++ компиляторах (фирмы Борланд например) эта возможность от греха подальше убрана. Вы еще не заскучали?

 

Нет, Дмитрий, с Вами не соскучишься :)

 

Здесь хочу лишь заметить, что в ОР нет необходимости чистить ресурсы в конструкторе. На это есть деструктор! (КоТ: Вот!!!) Логично, не так ли? Зачем плодить двойной код. А вот конструкции вида

if Assigned(MyObject1) then

FreeAndNil(MyObject1);

if Assigned(MyObject2) then

FreeAndNil(MyObject2);

ОЧЕНЬ рекомендуется использовать именно в деструкторе. Это хороший стиль. (КоТ: что да, то да.) Конструкция аналогичная if (c_) delete c_ (кстати, здесь была ошибка).

 

(КоТ: с != 0 бывает, т.к NULL-тип машинно-зависимый. Но пустой указатель где-то представлен, напр, отрицательным числом. Если мне понадобилось, я бы писал

if (С ! = NULL) // что надо сделать с С

хотя Страуструп и советует использовать 0 вместо NULL - в третьей редакции книги. В первой, помнится, советовал обратное ;)

 

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

 

Привел я этот пример не для демонстрации возможностей блока try...catch, а для того чтобы показать как С++ сам делает безопасным процесс "конструирования" класса. В Делфи все это ложиться на хрупкие плЭчи программера.

(КоТ: "Врать не надо по телефону" (с) Булгаков)

 

Теперь мы видим, кто действительно "сам делает безопасным процесс конструирования класса", а кто перекладывает все это на чьи-то "хрупкие плЭчи".

 

Кстати о Делфях, я там не нашел аналог функции С++ - uncaught_exception() - показывает статус стека исключений. Благодаря этой функции ваш деструктор знает - нормальное это "устранение" класса или не нормальное. По-моему, очень даже пользительно.

 

Что значит ненормальное устранение класса? Может, мы еще будем считать возникновение исключения ненормальной ситуацией? Между прочим, на исключениях вполне можно выстроить логику работы класса или библиотеки. В ОР этому, кстати, очень способствуют такие преимущества модели исключений перед ANSI C++, как наличие общего предка исключений (и то, что это вообще классы, а не абы что) и наличие блока try…finally (ну это просто добавляет удобств по сравнению с try…catch(…){ throw; })

 

Поэтому не совсем понятно, зачем понадобилась некая функция uncaught_exception(). Зачем лезть в идеологию работы исключений со своим уставом? Ведь они как раз и избавляют разработчика от чрезмерного применения if. Это еще называется реактивной моделью программирования. Но, раз есть спрос, то есть и предложение:

ExceptAddr function - returns the address at which the current exception was raised.

ExceptObject function - returns a reference to the object associated with the current exception.

ExceptProc variable - points to the lowest-level RTL exception handler.

Фича в том, что оператор new уже выделил память для экземпляра класса TObject, а тут ррраз! И исключение! Что делает С++? Он тут же освободит память - не надо ставить блок try...catch. Все сделает С++. Ну, как говориться, приятная неожиданность.

 

(КоТ: это стандарт, и кто не знает его, как может говорить, что знает С++?)

 

Ну, это только для тех, кто не очень хорошо знает ОР и С++. Впрочем, такие "детские" неожиданности не избавляют программера в С++ от необходимости защиты динамических ресурсов. В ОР же в это время можно попить пива ;)

 

Я не привожу примеры реализации более полезных УМНЫХ указателей, реализующих сборку мусора и правильную работу с ресурсами вообще.

 

Судя по всему, автор прочитал книгу Джефа Элджера "C++", испестренную идеями УМНЫХ, ВЕДУЩИХ, ГЕНИАЛЬНЫХ указателей и сборки мусора. Здесь хочу заметить, что я иногда читаю книги с карандашом в руке. Это очень хорошая, умная (КоТ: ведущая и гениальная ;-) книга про С++. Только во время ее чтения, постоянно задумываешься, а как можно сделать тоже самое в ОР. В результате после прочтения книга превратилась в записную книжку, испестренную замечаниями о том, насколько проще и красивее выглядела бы в ОР большая часть предлагаемых решений. Для интересующихся - основная идея в использовании свойств и интерфейсов.

 

1) Работа с несколькими "собственными" кучами. Например, все покупатели складываются в одну кучу. А поступаемые товары в другую… Как видите осталось только реализовать менеджер кучи, что в рамках С++ вещь простая и ведущая себя незаметно (как встроенная фича). Можно так извернуться в Делфях? Нет

 

(КоТ: Если можно проще и лучше, так изворачиваться-то нафиг?)

 

Мы уже выяснили, что можно. И как-то изворачиванием это и не назовешь. Обычная работа. Хочешь, переопределяй работу с памятью на уровне классов, хочешь, глобальный менеджер памяти напиши.

 

2) Помимо "многокучности", оператор new предоставляет вам возможность "виртуального" размещения объекта. Например в файле, в Сети, где вашей душеньке будет угодно. Это тоже недоступно в Делфях.

 

(КоТ: Это и в С++ без корбы тоже не особенно хорошо получается, кстати. И опять же, нафига? чтобы без спроса прога в своп лазила? Или в инет звонила?)

 

Откуда такая категоричность? Возможностей "виртуального" размещения у ОР ничуть не меньше. Или операционная система предоставляет для программ на С++ особые механизмы работы с файлами, с сетью и т.д.?

 

Кстати о преобразовании типов. Делфи обязан безопасному преобразованию типов(as и is) C++, а точнее шаблону dynamic_cast.

 

(КоТ: Страуструп: "как правило, НЕБЕЗОПАСНО (выделено мной - КоТ) использовать указатель, преобразованный или приведенный с помощью функций …_cast к типу, отличному от типа объекта, на который он указывает.") Да, а в ОР эта вещь абсолютно безопасна…

 

Не уверен, что кто-то кому-то обязан, тем более шаблону. У ОР всегда была и остается система RTTI, намного превосходящая возможности С++. Да и вообще, RTTI - это обобщенный языковой механизм. При чем здесь конкретные реализации?

 

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

 

Что за терминология? Дмитрий, наверное, имел в виду, что в списке предков класса ДОЛЖЕН быть указан один класс, и МОЖЕТ быть указано сколько угодно интерфейсов.

 

Дело в том, что в Делфи тип class реализован через одно очень загадочное место. Связано это с большой нелюбовью паскаля к памяти

 

(КоТ: это БЫЛО в ДОСе, десять лет назад, но ведь с тех пор воды утекло - !!!)

 

Можно, конечно, и так сказать. Все в мире относительно. Но я до сих пор встречал очень мало людей, достаточно глубоко знающих устройство классов в Delphi, точнее мне доводилось только читать их труды. И статью Дмитрия тяжело отнести к таким трудам. И почему он решил, что ОР не любит память?

 

Тип указатель в паскале создан только для того, чтобы указывать на что-то в динамической памяти(куче). Он создан, как шлюз между статической памятью паскаля и кучей. Странно, но зачем-то разработчики языка оставили возможность приводить целое к указателю (КоТ: к дождю, может быть? ;-)

 

Ни разу не доводилось слышать об ограничении указателей ОР на работу только с кучей. Возможность же приводить целое к указателю позволяет "двигаться" по памяти (не думаю, что это секрет для Дмитрия). Кстати, для указателей на строки допустимы операции "+" и "-" (в том числе в комбинации с целыми) без приведения типов.

 

Такое понятие как ссылка не знакомо паскалю

 

(КоТ: тогда в 6.0 под ДОС я работал не с ссылками… а с чем???)

 

Ссылка в терминологии ОР - это типизированный указатель. А используя термины С++ (КоТ: Страуструп: "Ссылка является альтернативным именем объекта.") ссылкой в ОР являются формальные параметры методов, объявленные с использованием var или out (возможно кто-то не знал, out - то же, что и var, только работает исключительно на возврат значения). Кроме того, чистой воды ссылками являются объектные переменные (Button1: TButton).

 

Если вы пишите класс "комплексное число", а затем решаете создать массив чисел, то array [1..10] of TComplex; будет на самом деле занимать в памяти 4*10 байт плюс выравнивание. Т.о. вы может быть хотели именно массив ТОЛЬКО КОМПЛЕКСНЫХ чисел, а не указателей на них. Но вместо этого, после инициализации, у вас будет израсходовано (4*10 + 10*sizeof(TComplex)) байт памяти. Короче сами считайте

 

Действительно, использовать классы ОР в массивах не очень удобно. Есть несколько более экономичных решений:

1) Можно организовать свой менеджмент памяти для TComplex, размещая экземпляры в памяти подряд (например, в заранее выделенном пуле), и работу, скажем, на основе динамического массива. Не самое простое решение, но весьма эффективное и красивое (КоТ: Кстати, активно применяется в С++-модели).

2) Можно вместо классов использовать записи record, организовав их в массив, являющийся свойством по умолчанию какого-то класса:

Code:

 

TItem = record

end;

TArray = class

public

property Items[Index: integer]: TItem

read GetItem

write SetItem;

default;

end;

 

Это будет самое экономичное решение, т.к. каждый экземпляр любого класса имел бы как минимум ссылку на таблицу VMT. А запись содержит только необходимые данные. Вся же логика работы - в классе TArray.

3) Можно использовать старые "объекты" Паскаля вместо "классов": TComplex = object … end; И массив таких объектов будет содержать сами объекты, а не ссылки на них. Это будет самое оригинальное решение. Кстати, на таких объектах построена библиотека KOL (https://xcl.cjb.net/) - аналог VCL. Размер EXE файлов с использованием этой библиотеки начинается от 4.5К (если не изменяет склероз :)

Одним словом, проблема в разработчике, а не в языке. (КоТ: Вот!!!)

 

Паскаль маленький язык и это не недостаток. (КоТ: Уф!. Ну сколько можно говорить о паскале 10-летней давности?) ?) Его не замечаешь, когда пишешь прогу большую или маленькую. (КоТ: это высшая похвала паскалю вообще. Лучшая одежда - та, которой не замечаешь.) Почему? (КоТ: Потому, что это хороший язык.)

 

Потому что Паскаль от Борланд специальный язык, т.е. предназначен для узкой области. Узкая - это не значит, что программ мало, просто цели в этой области отличаются не намного.

 

(КоТ: "С++ создавался для того, чтобы ИЗБАВИТЬ автора и его друзей ОТ ПРОГРАММИРОВАНИЯ НА АССЕМБЛЕРЕ" - (с) Бьерн Страуструп. Дельфа, возможно, создавалась для того, чтобы избавить автора от программирования на паскале, который вообще создавался изначально для ОБУЧЕНИЯ ПОНЯТИЯМ информатики. Оба эти языка свои цели выполнили с блеском. Ну так о чем же спор, цели-то разные?)

 

Не знаю, может быть ОР и маленький язык. Однако я оцениваю свои знания ОР не более, чем на 60-70% (хотя меня как-то угораздило сдать экзамен на сертификат Brainbench Certified Master Delphi Programmer :), не включая сюда VCL или среду Delphi, разговор только о самом языке. Если охватить все, то я вообще ничего не знаю. Поэтому мне даже как-то неловко заниматься здесь исправлениями. Я считаю, что для этого необходим куда больший кругозор. Но пока за эту задачу никто не взялся. Видимо настоящим профессионалам просто не до этого.

 

По поводу узости области применения. До последнего времени я считал, что единственное, что нельзя делать в Delphi - это писать драйвера (ОР тут ни причем, это не языковое ограничение). Но недавно натолкнулся на пример создания в Delphi 3 драйвера VxD. Что еще? Игрушки, сетевые сервисы, системные утилиты, распределенные базы данных, научные программы, средства мультимедиа в Delphi пишут и очень успешно. Так о чем речь?

 

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

 

После таких утверждений становится странно, как человек позволяет себе критиковать продукт, о котором имеет лишь зачаточное представление. За что "все остальное" отвечает среда? В виде списка, пожалуйста.

 

Delphi IDE - это, по большому счету, лишь оболочка, набор зацепок к возможностям библиотеки VCL и шикарный пример использования возможностей языка. Это продукт, тратящий наименьшее количество усилий для выполнения одной и той же работы в сравнении с аналогами. Ведь в лице библиотеки VCL он в design-time использует тот же самый код, который работает в готовом приложении. Для сохранения спроектированной формы со всеми компонентами в ресурс Delphi достаточно одной строчки кода!

 

И потом, "склеивание" и "написание" компонент - вещи по своей сложности абсолютно разные. Visual Basic тоже хорошо склеивает COM-компоненты, только вот с их написанием у VB как-то не очень… То, что ОР позволяет легко и непринужденно создавать и склеивать любые компоненты говорит лишь о его мощности, продуктивности и универсальности. Совершенно очевидно, что сегодня ОР по этому показателю не имеет не то что конкурентов, а даже толковых аналогов.

 

VCL не является языковым расширением Паскаля - это "ОО" библиотека. Транспортом же между такими библиотеками и отдельными компонентами выступает некая переделка СОМ от Борланд.

 

Да, VCL - вещь самостоятельная, пока она строится на ОР. Далее автор, похоже, говорит о RTTI. Но причем здесь транспорт между библиотеками и отдельными компонентами? Библиотека - понятие чисто условное. Каждый написанный мной компонент становится полноправной частью VCL. Правильнее, наверно, говорить о транспорте между компонентами и их пользователями, в частности средой Delphi IDE.

 

К вопросу о переделках. Delphi начинала разрабатываться где-то в 92-93 году. Трудно говорить, кто кого переделал. Да это и не важно. Важно то, что компонент Delphi в полной мере обладает обоими механизмами.

 

И опять же отбросьте этот транспорт, который не является частью языка, и от Делфи ничего не останется. (КоТ: Отбрось Gdb\Gtk, STL - что останется от милого нашему сердцу С++?) Поэтому Делфи очень гармоничная со своими недостатками среда для разработки GUI приложений под винды.

 

Да, RTTI - незаметная, но ключевая для Delphi технология. А вот с тем, что она не является частью языка можно крепко поспорить. Достаточно вспомнить операторы AS и IS, которые целиком базируются на RTTI. Да и от TObject никуда не убежишь. Попробуй скажи, что это не часть языка. А ведь основное содержание TObject - реализация RTTI.

 

Кроме того, Delphi идеально подходит не только для создания GUI приложений, но и консольных, и приложений без визуального интерфейса вообще (например, сервисы Windows NT). К счастью компоненты Delphi не ставят во главу угла визуальность/невизуальность. Это абсолютно универсальные в применении классы. Уже поэтому Delphi разительно отличается от, например, MSVC++, где в основе слова "Visual" лежит наличие у компонента оконного идентификатора и множество маловразумительных макросов и комментариев по тексту, которые нельзя (!) редактировать. Вот где действительно безопасный язык! Ведь программист может все испортить :)

 

По поводу недостатков можно сказать лишь то, что вряд ли у конкурирующих с Delphi продуктов их меньше. А вообще, давайте взглянем на Delphi 6 и Kylix. Уверен, что сюрпризов там будет более чем достаточно.

(Кот: я очень надеюсь, что у нас научатся, наконец, считать "Итого", а не только недостатки и достоинства отдельно).

 

Похоже, Дмитрий применительно к безопасности программирования рассматривал только те моменты, которые, по его мнению, хорошо смотрелись в С++ в сравнении с ОР. Здесь, кстати, стоит упомянуть такие преимущества С++ над ОР, как возможность понижать видимость членов класса, а также указание const при объявлении метода, что гарантирует неизменность атрибутов объекта при вызове метода.

 

Однако не стоит забывать, что ОР является языком, который действительно ставит во главу угла безопасность практически во всем. Можно долго перечислять все его тонкости, избавляющие программера от головной боли и рутиной работы. Как пример, можно привести директиву implements для свойств (делегирование реализации) или объявление глобальных переменных в разделе threadvar для поддержки многопоточности, или замечательную реализацию работы со строками и динамическими массивами на уровне компилятора. Очень важное для безопасности программирования свойство - объявление новых типов.

 

Гради Буч: "К сожалению, конструкция typedef не определяет нового типа данных и не обеспечивает его защиты. Например, следующее описание в С++:

typedef int Count;

просто вводит синоним для примитивного типа int."

 

В ОР же мы можем создать абсолютно новый тип. Для этого надо применить ключевое слово type:

type

Count = type int64; // другой тип

Alias = int64; // синоним

Типы Count и int64 уже не будут совместимы без приведения типов.

 

А вот пример стандартизованной фичи компилятора (соответственно и языка) С++:

long FileSize = 256 * 1024;

 

В 16-битном компиляторе вы в результате получите 0. Очень приятный сюрприз! А дело в том, что 256 и 1024 по отдельности попадают в int (2 байта), а их произведение уже в long (4 байта). Однако стандарт С++ как раз в том и заключается, что произведение будет также помещено в int. И уже только после этого произойдет присвоение к long. Соответственно туда попадает только младшая часть произведения, которая равна нулю. Спасает написание в форме 256 * 1024L. На 32 битах все будет нормально, т.к. размеры типов int и long совпадают (4 байта).

 

Некоторые начинают вяло возражать, что такие вещи нужно помнить, что это, мол, нормально. Однако в эту проблему (обнаружили ее случайно) конкретно уперлись два программера на С++ с очень хорошим опытом работы и в течение получаса не смогли ее решить. С ходу помог только действительно матерый эксперт С++. Ну, и как это выглядит с точки зрения безопасности программирования?

(КоТ: плохо выглядит).

 

А как "эстетичны" в каждом header'е С++ конструкции типа:

#ifndef _MYHEADER_H

#define _MYHEADER_H

… body of the header …

#endif

 

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

 

Кстати, особенности ОР как языка обеспечивают не только безопасность программирования, но и безопасность полученного софта, как таковую. К примеру, более половины дыр в безопасности программ отраженных в Bugtraq возникают из-за проблемы переполнения буфера. А эта проблема является визитной карточкой С/С++. Дошло до того, что выпускаются специальные пакеты, которые патчят исходники С++. Как один из вариантов решения проблем безопасности предлагают писать на Pascal…

 

После всего, что я тут наговорил, может возникнуть мысль: "А почему же тогда Борланд двигает Делфи?". "И почему VCL написан на паскале, а не на С++?". Резонно. Мыслям вообще свойственно появляться в головах человеков.

 

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

 

Возникает другая мысль. Почему уровень знаний ОР у очень многих программистов так удручающе низок (КоТ: С С++ ситуация ничуть не лучше. Груда книг всяких, прости господи, пересмешников. А если прочитать 1 (один) раз Страуструпа, множество вопросов просто отпадет). Понятно, что литература у нас в основном "для чайников". Но иногда надо хотя бы help читать. Похоже считается делом чести начитаться умных книжек "с примерами приложений на С++", а для Delphi, мол, можно ограничиться знанием Object Inspector'а.

 

При этом многие такие программисты почему-то считают для себя возможным критиковать возможности ОР. Может быть потому, что Delphi дала им возможность быстро и легко воплотить свои идеи? А потом вдруг что-то не получилось… И вот, виновата Delphi. Можно с уверенностью сказать, что С++ такому программисту все равно не поможет.

 

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

 

(КоТ: за что я вообще и выбрал линух - это система людей, имеющих роскошь на рынок в некоторых местах вообще плевать. Хотя от рынка, конечно уйти нельзя. Да и зачем? В умеренных дозах рынок - это очень хорошо.)

 

Для любящих спорить. Не стоит критиковать какие-то возможности продукта, не до конца в них разобравшись. Современные языки слишком многогранны, чтобы один человек досконально знал хотя бы два языка. Я лично не уверен, что все мои рассуждения на 100% достоверны, но старался, как мог. Поэтому буду рад техническим исправлениям.

"Портос, если Вы говорите глупости, то делайте это, пожалуйста, только от своего имени"

 

P.S. Красота драгоценного камня, как известно, зависит не только от породы, но и от мастерства огранщика. Только тогда обычный белый свет превращается в нем в причудливую игру разноцветных искр. Так что учите матчасть, и Delphi вас не подведет :)

 

(КоТ: Два слова напоследок - не удержался. Я полагаю, что С++, что дельфа - языки одного уровня, но разных подуровней. Бессмысленно их сравнивать вообще. С++ старше - хотя бы поэтому дельфа лучше, т.к написана на его крови, если можно так сказать.

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

А что до С++ - учите матчасть… И не хуже будет, чем в дельфе. ;)

 

P.P.S. Да, в конце концов, все измеряется способностями конкретного "юзера" языка. Хочется верить, что эта статья поможет кому-нибудь сделать очередной шаг на длинном пути от "чайника" к "профи".

 

 

https://delphiworld.narod

DelphiWorld 6.0

 

Code:

unit TestInputForm;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, DdhInpuB;

 

type

TForm1 = class(TForm)

Edit1: TEdit;

Label1: TLabel;

DdhInputButton1: TDdhInputButton;

DdhInputButton2: TDdhInputButton;

DdhInputButton3: TDdhInputButton;

procedure DdhInputButtonClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

procedure TForm1.DdhInputButtonClick(Sender: TObject);

begin

ShowMessage ('You have clicked the ' +

(Sender as TButton).Name + ','#13 +

'having the caption ' +

(Sender as TButton).Caption);

end;

 

end.

 

 

 

 

Взято с https://delphiworld.narod

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

 

ФОРМУЛА ОБЪЕКТА

 

Авторы надеются, что читатель помнит кое-что из второй главы и такие понятия, как тип данных, процедура, функция, запись для него не в новинку. Это прекрасно. Та вот, в конце 60-х годов кому-то пришло в голову объединить эти понятия и то, что получилось, назвать объектом. Рассмотрение данных в неразрывной связи с методами их обработки позволило вывести формулу объекта:

Объект = Данные + Операции

 

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

 

ПРИРОДА ОБЪЕКТА

 

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

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

 

Например, объект «кнопка» имеет свойство «цвет». Значение цвета кнопка запоминает в одном из своих полей. При изменении значения свойства «цвет» вызывается метод, который перерисовывает кнопку.

 

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

 

ОБЪЕКТЫ И КОМПОНЕНТЫ

Когда прикладные программы создавались для операционной системы MS-DOS и были консольно-ориентированными, объекты казались пределом развития программирования, поскольку были идеальным средством разбиения сложных задач на простые подзадачи. Однако с появлением графических систем, в частности Windows, программирование пользовательского интерфейса резко усложнилось. Программист в какой-то мере стал дизайнером, а визуальная компоновка и увязка элементов пользовательского интерфейса (кнопок, меток, строк редактора) начали отнимать основную часть времени. И тогда программистам пришла в голову идея визуализировать объекты, объединив программную часть объекта с его видимым представлением на экране дисплея в одно целое. То, что получилось в результате, было названо компонентом.

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

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

 

КЛАССЫ ОБЪЕКТОВ

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

 

ТРИ КИТА ООП

Весь мир ООП держится на трех китах: инкапсуляции, наследовании и полиморфизме. Для начала о них надо иметь только самое общее представление.

 

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

 

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

 

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

Пока достаточно самого поверхностного понимания всех приведенных выше понятий, ниже мы рассмотрим их подробнее и покажем, как они реализованы в Delphi.

 

КЛАССЫ

Delphi поддерживает две модели представления объектов — старую и новую. Старая модель существует лишь для совместимости с более ранними версиями компилятора, в частности с Borland Pascal 7.0, поэтому мы не будем ее рассматривать. Все, что сказано ниже, относится к новой модели представления объектов, более мощной и богатой по своим возможностям.

 

Для поддержки ООП в язык Object Pascal введены объектные типы данных, с помощью которых одновременно описываются данные и операции над ними. Объектные типы называют классами, а их экземпляры — объектами.

 

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

 

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

 

Code:

type

TDiskGauge = class { измеритель дискового пространства}

DriveLetter: Char; { буква дискового накопителя}

PercentCritical: Integer; { критический процент свободного пространства}

function GetPercentFree: Integer;

procedure CheckStatus;

end;

 

 

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

 

Класс обычно описывает сущность, моделируемую в программе. Например, класс TDiskGauge описывает измеритель дискового ресурса. Класс содержит два поля: DriveLetter буква находящегося под наблюдением накопителя, и PercentCritical процент свободного пространства на диске, с которым работает программа. Когда объем свободных ресурсов снижается до этого порога, пользователю выдается звуковое предупреждение. Функция GetPercentFree определена как метод работы над любым объектом класса TDiskGauge и возвращает процент свободного пространства на диске. Процедура CheckStatus служит для проверки состояния ресурса и выдачи звукового предупреждения.

 

Обратите внимание, что приведенное выше описание является не чем иным, как декларацией интерфейса для управления объектами класса TDiskGauge. Реализация методов GetPercentFree и CheckStatus отсутствует, но для создания и использования экземпляров класса она пока и не нужна. В этом как раз и состоит сила инкапсуляции, Которая делает объекты аналогичными программным модулям. Для использования модуля необходимо изучить лишь его интерфейсную часть, раздел реализации для этого изучать не требуется. Поэтому дальше от описания класса мы перейдем не к реализации методов, а к созданию на их основе объектов.

 

ОБЪЕКТЫ

Чтобы от описания класса перейти к объекту, следует выполнить соответствующее объявление в секции var:

Code:

var DiskGauge: TDiskGauge;

 

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

 

Code:

DiskGauge: = TDiskGauge.Create;

 

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

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

 

Code:

DiskGauge.DriveLetter: = 'С';

DiskGauge. PercentCritical: = 10;

DiskGauge.CheckStatus;

 

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

 

Code:

with DiskGauge dobegin

DriveLetter: = 'С';

PercentCritical: = 10;

CheckStatus;

end;

 

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

 

Code:

DiskGauge.Destroy;

 

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

 

Code:

DiskGauge: = nil;

if DiskGauge <> nilthen DiskGauge.Destroy;

 

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

 

Code:

DiskGauge.Free;

 

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

 

Code:

var

DiskGaugel, DiskGauge2: TDiskGauge;

begin

{ Переменные DiskGauge1 и DiskGauge2 не связаны с объектом}

DiskGauge1: = TDiskGauge.Create;{Переменная DiskGauge1 связана с объектом, а DiskGauge2 — нет}

DiskGauge2: = DiskGauge1;{Обе переменные связаны с одним объектом}

DiskGauge2.Free; {Объект удален, переменные DiskGauge1 и DiskGauge2 с ним не связаны}

end;

 

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

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

 

Code:

 

type

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

TDiskGauge = class Owner: TGaugeList;

 

...

 

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

end;

 

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

Итак, вы уже имеете некоторое представление об объектах, перейдем теперь к вопросу реализации их методов.

 

МЕТОДЫ

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

 

Code:

function TDiskGauge.GetPercentFree: Integer; { uses SysUtils; }

var Drive: Byte;

begin

Drive := Ord(DriveLetter) - Ord('A') + 1;

Result := DiskFree(Drive) * 100div DiskSize(Drive);

end;

 

procedure TDiskGauge.CheckStatus; { uses Windows; }

begin

if GetPercentFree <= PercentCritical then Beep;

end;

 

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

 

Code:

procedure TDiskGauge_CheckStatus (Self: TDiskGauge);

begin

with Self do

if GetPercentFree <= PercentCritical then Beep;

end;

 

Согласитесь, что метод CheckStatus выглядит более предпочтительно, чем процедура TDiskGauge_CheckStatus.

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

Если выполнить метод CheckStatus

 

Code:

DiskGauge.CheckStatus;

 

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

 

Code:

TDiskGauge_CheckStatus(DiskGauge);

 

 

КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ

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

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

Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированного слова procedure (или function) используются слова constructor и destructor:

Code:

type TDiskGauge = class DriveLetter: Char;

PercentCritical: Integer;

constructor Create;

destructor Destroy;

...

end;

 

Приведем их возможную реализацию:

Code:

constructor TDiskGauge.Create;

begin

DriveLetter := 'C';

PercentCritical := 10;

end;

destructor TDiskGauge.Destroy;

begin

{ Разрушение встроенных объектов и освобождение динамических данных}

end;

 

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

Конструктор применяется к классу или к объекту. Если он применяется к классу

 

Code:

DiskGauge: = TDiskGauge.Create;

 

то выполняется следующая последовательность действий:

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

 

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

 

Деструктор уничтожает объект, к которому применяется (DiskGauge.Destroy;)

В результате:

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

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

 

СВОЙСТВА

ПОНЯТИЕ СВОЙСТВА

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

Объявление свойства выполняется с помощью зарезервированного слова property, например:

Code:

 

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). В следующем примере объявлено свойство, значение которого можно только читать:

Code:

type

TDiskGauge = class

property PercentFree: Integer read GetPercentFree;

end;

 

 

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

 

Code:

var

DiskGauge: TDiskGauge;

A : Integer;

 

А := DiskGauge.PercentCritical;

{ эквивалентно А := DiskGauge.FPercentCritical;}

DiskGauge.PercentCritical := A + 10;

{ эквивалентно DiskGauge.SetPercentCritical(A + 10);}

 

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

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

 

МЕТОДЫ ПОЛУЧЕНИЯ И УСТАНОВКИ СВОЙСТВ

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

 

Code:

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:

Code:

procedure TDiskGauge.SetPercentCritical (Value: Integer);

begin

if (Value >= 0) and (Value < 100)

then FpercentCritical := Value;

end;

 

 

МЕТОДЫ, ОБСЛУЖИВАЮЩИЕ НЕСКОЛЬКО СВОЙСТВ

0дин и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод первым параметром. В следующем примере методы Get и Set обслуживают три свойства: GaugeA, GaugeB и GaugeC:

Code:

type

TGaugeList = class

FGauges: array [0..2] of TDiskGauge;

...

function Get (Index: Integer): TDiskGauge;

procedureSet (Index: Integer; Value: TDiskGauge);

...

property GaugeA: TDiskGauge index0read Get writeSet;

property GaugeB: TDiskGauge index1read Get writeSet;

property GaugeC: TDiskGauge index2read Get writeSet;

...

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:

 

Code:

var

GaugeList: TGaugeList;

DiskGauge: TDiskGauge;

...

GaugeList.GaugeC := DiskGauge;

{ эквивалентно GaugeList.Set (2, DiskGauge) }

 

GaugeList.GaugeC.CheckStatus;

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

...

 

СВОЙСТВА-МАССИВЫ

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

 

Code:

type

TGaugeList = class

...

property Gauges[Index: Integer]: TdiskGauge

read Get writeSet;

...

end;

 

 

Обратите внимание, что методы Get и Set обслуживают и свойство-массив Gauges, и индексированные свойства GaugeA, GaugeB и GaugeC. Если в описании обычных свойств могут участвовать поля, то в описании свойств-массивов разрешено использовать только методы.

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

 

Code:

var

GaugeList: TGaugeList;

I: Integer;

...

for I := 0to2do

with GaugeList do Gauges [I] .CheckStatus;

...

 

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

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

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

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

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

 

СВОЙСТВО-МАССИВ КАК ОСНОВНОЕ СВОЙСТВО ОБЪЕКТА

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

Code:

type

TGaugeList = class

...

property Gauges [Index: Integer]: TdiskGauge

read Get writeSet; default;

...

end;

 

 

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

 

Code:

var GaugeList: TGaugeList;

I: Integer;

...

for I := 0to2do GaugeList [I] := nil; { эквивалентно GaugeList.Gauges[I] := nil; }

 

 

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