On — Ключевое слово Delphi

Содержание

Используйте заголовок/текст управления вместо ключевого слова или оператора в Delphi

Является ли это возможным?

Пример: ComboBox1 имеет два элемента (или, и). ComboBox2 имеет два элемента ( ).

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

Любой из инструментов сценариев Pascal, qaru.site/questions/404744/. может работать для этого.

Но для вашего очень простого примера вам это не нужно, вы можете просто проверить, что ComboBoxx.Text или лучше ComboBox.ItemIndex вернется, а затем напишите код Delphi во время разработки.

Первое, что вам нужно понять, — это то, как выражения Delphi обрабатываются компилятором. Они указывают на то, что выражения анализируются во время компиляции. Под парсинг мы подразумеваем действие разложения выражения на его составные части: операции и связанные операнды.

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

Например, рассмотрим это выражение:

Здесь операция является дополнением. Оба операнда представляют собой переменную x и константу 1 . Все известно во время компиляции, кроме значения x .

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

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

Вместо этого вам нужно выполнить разбор выражений. Вы можете написать собственный анализатор/оценщик. Вы можете использовать анализатор выражения Delphi LiveBindings. Вы можете использовать сторонний оценщик выражений, например библиотеку JCL.

On — Ключевое слово Delphi

Прежде чем перейдем к дальнейшему описанию языка Delphi, формально опреде-
лим несколько терминов. Во-первых, это слово «идентификатор». Идентификатор —
это строка символов, используемая для именования некоторого элемента программы.
Это может быть переменная, запись, функция, процедура или конструкция более вы-
сокого уровня, например сама программа.
Идентификатор может иметь любую длину, однако в языке Delphi только первые
его 255 символов являются значимыми (что более чем достаточно!). Идентификатор
должен начинаться с буквы или символа подчеркивания (_) и не может содержать
пробелов. После первого символа идентификатора можно использовать буквы, цифры
и символы подчеркивания. Как и в зарезервированных словах, в идентификаторах
можно использовать как строчные, так и прописные буквы (компилятор их не разли-
чает). Приведенные ниже идентификаторы означают одно и то же.

CalculateValue
calculateValue
calculatevalue
CALCULATEVALUE
Ключевые слова не могут быть идентификаторами.
Далее рассмотрим лексемы. Это минимальные значимые единицы текста в про-
грамме. Они представлены такими категориями, как специальные символы, иденти-
фикаторы, метки, числа и строковые константы.
Программа, написанная на языке Delphi, состоит из лексем и разделителей, при-
чем разделитель представляет собой пробел или комментарий. Две соседних лексемы,
если они представляют собой зарезервированное слово, идентификатор, метку или
число, должны быть отделены друг от друга одним или несколькими разделителями.
В Delphi используются следующие подмножества набора символов кода ASCII.
• Буквы английского алфавита от А до Z и от а до z.
• Цифры — арабские цифры от 0 до 9.
• Шестнадцатеричные цифры — арабские цифры от 0 до 9, буквы от А до F и бу-
квы от а до f.
• Пробелы — символ пробела ($32) и все управляющие символы кода ASCII ($0-$31),
включая символ конца строки или символ возврата каретки ($13). Это шестна-
дцатеричные числа, так как перед ними стоит символ доллара «$».
Теперь определим смысл слова «выражение». Это фрагмент языка программирова-
ния, представляющий способ вычисления некоторого значения.
И наконец, определим смысл слов «операнд» и «оператор».
Операнд — часть выражения, над которым производятся операции. Например,
в выражении, присваивающем А сумму в и с (А := В+С;), А, в, с являются операн-
дами, а над значениями, представленными идентификаторами А й в , производится
операция суммирования. Идентификатор — это строка символов, используемая для именования некоторого
элемента программы.
Лексемы — это минимальные значимые единицы текста в программе.
Выражение — это фрагмент языка программирования, представляющий способ вы-
числения некоторого значения.
Операнд — часть выражения, над которым производятся операции.
Оператор — действие, которое может быть выполнено над одним или несколькими
операндами.
Оператор — действие, которое может быть выполнено над одним или несколькими
операндами. Если обратиться к вышеприведенному примеру, то оператором является
знак плюс (+). Хотя в некоторых случаях оператором можно назвать целое выражение,
заканчивающееся точкой с запятой. Более правильно такие операторы надо называть
структурированными операторами. Например, выражение
while i:=0 to 10 do x := i ;
можно назвать оператором, так как здесь выполняется операция цикла над пере-
менной X.
Теперь можно переходить непосредственно к ключевым словам. Обычно ключевые
слова пишутся строчными буквами, но Delphi безразличен к регистру клавиатуры, по-
этому можно использовать в своей программе как строчные (нижний регистр), так
и прописные (верхний регистр) буквы. Я рекомендую использовать какой-то один
стиль написания, например, тот, к которому вы уже привыкли. Но если вы только на-
чинаете программировать, то лучше использовать общепринятые правила и писать
ключевые слова строчными буквами. В табл. 3.1 приведен перечень всех ключевых
слов с кратким комментарием.
Таблица 3.1. Ключевые слова
Ключевое слово Комментарий
and Булев оператор И
array Массив
as Используется при проверке соответствия типов, определяет объект как операнд
asm Используется для выделения ассемблерного кода
begin Начало блока
case Оператор выбора. Используется при выборе из многих вариантов
class Определяет тип «класс»
const Определяет константы, т.е. неизменяемые переменные. Однако в Delphi есть
режим, допускающий изменение констант в теле программы
constructor Специальный метод класса, необходимый для создания и инициализации
экземпляра класса (объекта)
destructor Специальный метод класса, необходимый для разрушения объекта
d i s p i n t e r f a c e Определяет тип интерфейса
div Целочисленное деление
do Определяет начало исполнимой части в операторах цикла, конструкции
t r y . . . except и в операторе w i t h
downto Определяет направление итерации в операторе f o r
else Используется в операторах выбора case, условном операторе i f и в операторе
проверки исключений t r y . . .except
end
except
e x p o r t s
f i l e
f i n a l i z a t i o n
f i n a l l y
for
f u n c t i o n
goto
i f
implementation
i n
i n h e r i t e d
i n i t i a l i z a t i o n
i n l i n e
i n t e r f a c e
i s
l a b e l
l i b r a r y
mod
n i l
not
o b j e c t
of
or
out
Обычно используется совместно с ключевым словом begin и отмечает конец
блока. Также ставится в конце описания типа, например класса или записи
Используется в операторе проверки исключений t r y . . . except
Определяет список экспортируемых процедур, функций и переменных
Устанавливает тип переменной как файл. Используется при работе с файлами
Определяет начало раздела, который в программе всегда выполняется
последним
Используется в операторе проверки исключений t r y . . . f i n a l l y
Используется в операторах цикла f o r . . . to и f o r . . .downto
Используется при объявлении функций
Переход на метку
Используется в операторах выбора i f . . . then и i f . . . then. . .else
Определяет раздел реализации, в котором находятся описания процедур,
функций, методов и коды разрабатываемой программы
После этого ключевого слова может указываться путь к необходимому модулю.
Также используется при работе с множествами
Дословно можно перевести как «унаследованный». Используется при работе
с классами, поддерживая возможности полиморфизма
Определяет раздел инициализации, который всегда располагается перед
разделом f i n a l i z a t i o n . Если раздела f i n a l i z a t i o n нет, то раздел
инициализации находится перед завершением программы. Выполняется сразу
после запуска программы, перед всеми другими операторами. Обычно
используется для инициализации переменных
Используется при работе с ассемблерным кодом. Устаревшее ключевое слово
к применению не рекомендуется
Определяет тип интерфейса. Используется при опережающем объявлении
интерфейса
Используется при проверке типов
Метка. Используется совместно с ключевым словом goto. Может быть
выражена любым идентификатором или числом от 0 до 9999
Директива-напоминание или рекомендательная директива. Используется
наравне с директивами p l a t f o rm и deprecated для напоминания об
особенностях стандартных типов, методов или модулей. Во время компиляции
вызывает появление предупреждающего сообщения
Остаток от деления целых чисел
Специальная константа, которая может быть присвоена любому указателю,
после чего считается, что указатель не ссылается ни на что
Булев оператор отрицания
Используется как альтернатива слову class. Сохранено в языке для
совместимости со старыми версиями. Не рекомендуется к использованию
Используется во многих операторах как связующее ключевое слово
Булев оператор ИЛИ
Используется при объявлении параметров процедуры, функции или метода.
Предупреждает о том, что данный параметр используется только для
выдачи значений
packed
p r o c e d u r e
program
p r o p e r t y
r a i s e
r e c o r d
r e p e a t
r e s o u r c e s t r i n g
s e a l e d
set
shl
s h r
s t r i n g
then
t h r e a d v a r
t o
t r y
type
u n i t
u n t i l
uses
var
while
w i t h
xor
Используется для более плотного размещения данных в структурированных
типах (массивы, множества, записи, файлы, классы)
Используется при объявлении процедур
Определяет имя программы, которое должно быть выражено
идентификатором
Используется при объявлении свойств
Используется при генерации исключений
Определяет тип записи
Используется в операторе цикла repeat. . . u n t i l
Определяет раздел объявления ресурсов
Используется при объявлении класса, запрещая наследование данного класса
Ключевое слово для объявления множества
Логический оператор сдвига влево
Логический оператор сдвига вправо
Используется при объявлении строковых типов
Используется в операторах i f . . . then и i f . . . t h e n . . .else
Используется при разработке многопоточных приложений
Используется в операторе f o r . . . t o
Используется в операторе проверки исключений t r y . . . f i n a l l y ,
t r y . . .except и в операторе выбора case
Определяет раздел объявления типов
Модуль. Обычно это функционально законченный фрагмент программы,
сохраняемый в файле с таким же именем
Используется в операторе repeat. . . u n t i l
Определяет раздел подключаемых модулей
Определяет раздел переменных
Используется в операторе w h i l e . . . do
Используется для определения идентификатора, который всегда записывается
с другими идентификаторами. Код получается более компактным и понятным
Булев оператор Исключающее ИЛИ
Есть еще несколько ключевых слов, о которых мы поговорим при изучении объектно-
ориентированного программирования и которые могут использоваться как директивы.
Именно эти ключевые слова выделяются жирным шрифтом в редакторе кода, хотя
кое-что может вызвать недоумение: почему, например, слово Break, которое в других
языках программирования является ключевым, в редакторе кодов не подсвечивается?
В Delphi это не ключевое слово, а процедура, и для нее существует отдельное описа-
ние в библиотеке. А процедура write не описана в библиотеках, так как ее код встро-
ен в компилятор. Сейчас мы не будем разбираться в этих тонкостях, а только отме-
тим, что все ключевые слова обычно пишутся строчными буквами, а процедуры
обычно начинаются с прописной буквы.
Написание идентификаторов также можно сделать нагляднее, если использовать слова,
отражающие назначение идентификатора, и начинать каждое слово с прописной буквы.
Например, идентификатор для счетчика символов можно написать так: SymbolCounter. Здесь слова использованы полностью, а разделение достигается за счет того, что второе
слово начинается с большой буквы. Каждый, кто будет читать программу, где использо-
ваны подобные идентификаторы, сможет понять ее без дополнительных комментариев.
Используйте это правило, и ваши программы станут понятнее и для вас самих. (К сожа-
лению, все идентификаторы должны писаться только английскими буквами, поэтому
учите английский, в настоящее время это язык общения программистов.)

On — Ключевое слово Delphi

В первую очередь пошел искать в хелпе, не нашел (хотя узнал много интересного ^^), в яндексе тоже глухо. В книжках по делфи, которые мне попадались о сабже тоже нет упоминаний. Итак, что же делает ключевое слово «object»? Ссылки на документацию приветствуются, но и ответом «своими словами» не побрезгую.

ЗЫ В общем-то, какой-то конкретной цели не преследую, просто для повышения образованности.

Это устаревший способ работы с самодельными объектами. Теперь гораздо целесообразнее использовать class

> Это устаревший способ работы с самодельными объектами

Моё любопытсво все еще не удовлетворено :). Чем object отличается от class, что именно устарело. Попадался мне как то код с этим волшебным словом, так что хотелось бы понять что это из себя представляет.

Идеальное слово для описания всего.

Даже для искуственного интелекта.

> Desdechado © (03.03.07 17:06) [1]
> Это устаревший способ работы с самодельными объектами. Теперь гораздо целесообразнее использовать class

В Делфях классом считается все, что прождено от TObject. Но проблема в том, что нужно было объявить сам TObject в юните «system.pas», для чего и использовали ключевое слово языка Паскаль object. Ключевое слово class не является зарезервированным словом языка Паскаль, хотя поддерживается и зарезервировано сейчас почти всеми компиляторами (Delpi, FPC, Lazarus, Kylix, TNT).


> Чем object отличается от class, что именно устарело

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

Руководство щас погуглю.
А под статическим объектом понимается объект, все методы которого статические? А конструктор с деструктором присутствуют? (=

Servelat © (03.03.07 17:19) [6]
нет просто память под такой объект выделяется статически, то есть при загрузке программы в память, а не когда программист соизволит

> PEAKTOP © (03.03.07 17:14) [4]
TObject = class;
T >
купи «твикс». жуй. молчи. заодно почитай материалы по Object Pascal (тому, из которого упёрли концепцию обёектов в Turbo Pascal 5.5).

Собственно, вопрос закрыт; нашел у себя на винте UserGuide по BP70 (что прям удивительно, на русском), где тема полностью раскрыта, просвещаюсь. Всем спасибо за ответы.

Удобен тем, что содержит в себе идеи ООП (наследование) и не требует лишней работы и памяти. Посмотри, сколько всего делается и создается в конструкторе TObject! Если мне нужно будет использовать сотню-другую тысяч объектов, то выбор будет за object»ами, а не за классами, наверное.

> Если мне нужно будет использовать сотню-другую тысяч объектов,
> то выбор будет за object»ами, а не за классами, наверное.

Так и до record»a дойти не долго :))

> Ketmar © (03.03.07 17:34) [8]

> Loginov Dmitry © (03.03.07 18:58) [11]

Структурное программирование рулит. Я серьезно.


> Посмотри, сколько всего делается и создается в конструкторе
> TObject! Если мне нужно будет использовать сотню-другую
> тысяч объектов, то выбор будет за object»ами, а не за классами,
> наверное.

Гм. Наверное наооборот :-) class как раз под такие случаи и заточен :-)

> tesseract © (03.03.07 20:52) [14]

На пустых объектах/классах поэкспериментировал, 100000000 TObject»ов создано за 19 секунд и использовано 720 метров памяти, столько просто обджектов — за ноль секунд и использовано памяти метр с копейками. Если объекты буду содержать какие-то данные и выполнять какие-то действия, то и на сотне разницу почуствуем.

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

Сами-то в это верите?

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

Или эта разница просто-напросто исчезнет.

> Сами-то в это верите?

Четырехзначная цифирь, начинается на один .

to PEAKTOP © (03.03.07 17:14) [4]:
Что за трава? Или это были грибы? :)))

to TUser © (03.03.07 21:04) [15]:
>На пустых объектах/классах поэкспериментировал
Угу, а производительность будем на «Hello, World!» мерять. :)
Пустой object занимает 0 байт, в отличие от наследника TObject, который занимает 4 минимум байта.

>Если объекты буду содержать какие-то данные и выполнять какие-то
>действия, то и на сотне разницу почуствуем.
Фиг. Если в object будет поле, допустим integer, вес уравняется. Преимуществ не будет почти никаких. При одинаковом количестве полей будут те самые 400 байт на сотне.

И вообще, что-то цифры подозрительные. Что там в коде-то?

TUser © (03.03.07 21:04) [15]

Тут такой момент — пустые объекты, как и пустые классы никому не нужны. Давай ты полные насоздаешь ?

33 секунды для объектов vs минута думанья и Out of memory в случае классов :) Объекты и классы содержали по два поля типа integer + то, что от TObject. Говорю же — объекты (те, которые экземпляры классов) память любят кушать. Больше, чем объекты, которые просто, т.е. типа record с наследованием.

> Говорю же — объекты (те, которые экземпляры классов) память
> любят кушать. Больше, чем объекты, которые просто, т.е.
> типа record с наследованием.

Ай-ай! Какая беда! 100000000 объектов всю память скушали!
Только нюансик один: что вообще в принципе можно делать с таким количеством объектов? Их же надо где-то индексировать (в массиве там, или в списке), это еще 400 метров памяти надо. Нужен метод доступа, поиска и т.п., что при таком количестве объектов (будь то object или class) будет выполняться катастрофически долго.
То есть пример со 100000000 объектов выглядет очень уж надуманным.

to TUser © (04.03.07 04:27) [21]:
>Говорю же — объекты (те, которые экземпляры классов) память любят
>кушать. Больше, чем объекты, которые просто, т.е. типа record с
>наследованием.
А вот мои подсчеты говорят, что памяти в результате понадобится одинаковое количество. Догадываетесь, почему? А времени, тут да, создание экземпляра класса требует чуть больше, т.к. экземпляру класса требуется инициализация, а у object её по-умолчанию нет, но это опять же, до тех пор пока там данных в нормальном количестве нет.

Поэтому еще раз говорю, что-то там не то с методикой. Код в студию.

Чтобы вопросов не возникало:

uses
SysUtils,
Windows;

type
Cls = class
a,b: integer;
end;

PObj = ^Obj;
Obj = object
private
a,b: integer;
end;

procedure AllocCls(count: integer);
var
i: integer;
begin
for i := 0 to Count — 1 do
Cls.Create;
end;

procedure AllocObj(count: integer);
var
i: integer;
o: PObj;
begin
for i := 0 to Count — 1 do
new(o);
end;

var
T1, T2, T3: Cardinal;
S1, S2, S3: Cardinal;
begin
T1 := GetTickCount;
S1 := GetHeapStatus.TotalAllocated;
AllocCls(10000000);
S2 := GetHeapStatus.TotalAllocated;
T2 := GetTickCount;
AllocObj(10000000);
S3 := GetHeapStatus.TotalAllocated;
T3 := GetTickCount;
writeln(Format(«%d %d», [T2-T1, S2-S1]));
writeln(Format(«%d %d», [T3-T2, S3-S2]));
readln;
end.

Результат (CoreDuo 6600 2.4 ГГц, RAM 2ГБ):
500 120000000
109 120000000

Памяти требуют оба варианта, что естественно для данного случая, одинаково, несмотря на разницу в размерах экземпляров. А чем разница во времени обусловлена — см. выше.

Но проблема в том, что нужно было объявить сам TObject в юните «system.pas», для чего и использовали ключевое слово языка Паскаль object

О как. Оказывается, object использовали для создания class. Вот так и возникают религии. Хвала всевышнему, объект придумавшему, ибо класс и экземпляр класса — суть порождение его. Во имя object, class и instance его! Аминь.

Я, наверное, еретик. бо у меня возник вопрос, откуда же в таком случае взялся сам object :0)

и если от record»а, то откуда взялся record? и так далее. )

to Virgo_Style © (04.03.07 12:29) [26]:
>откуда взялся record?
record был всегда :)

record придумал Вирт, а Вирта придумали мама с папой. выводы очевидны :)

хех
неужто настолько делать нефига людям что ещё не влом думать о такой архаике как object:)

> PEAKTOP © (03.03.07 19:03) [12]
расслабься. приведённый мной код — цитата из system.pas. советую перед тем, как вещать, таки читать генофонд. ну и — да, язык, о котором вещаешь, тоже выучить бы не помешало.

>>неужто настолько делать нефига людям что ещё не влом думать о такой архаике как object:)
что за наезды на обжект? фундаментальная штука. это класс придумка борланда, насколько помню в стандарте паскаля нет такого словечка:)

object — это отнюдь не архаика.

Во-первых, без слова object затруднительно определить callback типа

type
TForProgressBar = procedure(Current, Total: Integer) of object;

Это, ясное дело, очень мощное и «современное» средство, не реализованное, к примеру, в С++. Там аналог убогий.

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

> просто так (04.03.07 14:07) [32]
я-таки скажу откровение: в том стандарте и object ни разу нет.


> я-таки скажу откровение: в том стандарте и object ни разу
> нет

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

_uw_ (04.03.07 14:08) [33]
ну я не про все применения ключевого слова object, а именно про то применение которое оставлено для совместимости

в .NET структуры наделённые свойствами ООП юзаются порой в качестве «легковесных» объектов ибо для них в стеке память выделяется(то есть быстро) и освобождается быстро(а не когда GC соизволит)
в каком-то таком же смысле использования object-объектов возможно и может быть оправдано

Исключительные ситуации и интерфейсы в Delphi

  • Понятие исключительной ситуации, ее обработка средствами Delphi
  • Обработка RTL-исключений.Иерархия исключений
  • Создание собственных исключений
  • Интерфейсы и их совместное использование классами
  • Интерфейс IUnknown
    • Класс TInterfasedObject
    • Использование оператора as
    • Использование ключевого слова implements
  • Использование инрефейсов в распределительных приложениях

Большинство разработчиков на Delphi создают довольно сложные программы. Сложная программа подразумевает разносторонее взаимодействие с операционной системой и приложениями операционной системы. Любое из этих взаимодействий может завершиться неправильно. Примеров этого можно привести очень много, от банального деления на ноль, до открытия несуществующего файла. Обычно для обхода таких ситуаций разработчику приходится вводить многочисленные проверки. Но любой разработчик просто не в состоянии рассмотреть все ситуации, которые могут возникнуть у его программы при взаимодействии с операционной системой и другими приложениями. Именно для таких непредвиденных событий была придумана структурированная обработка исключительных ситуаций. Первоначально она была создана для разработчиков под Windows NT, впоследствии структурированная обработка получила поддержку и в операционных системах Windows 9x. Большинство современных программных сред для создания приложений под Windows поддерживают обработку исключительных ситуаций. Не осталась в стороне и среда Delphi. Обработка исключений была введена уже в Delphi 1.0, но, только начиная с Delphi 2.0, исключения стали частью Win32 API.
В этой главе мы узнаем, что такое исключительная ситуация и как средствами Delphi можно ее обработать. Рассмотрим, что такое RTL-исключения, как можно использовать в обработке исключительных ситуаций метод TAppllcationHandleException. В данной главе мы познакомимся с интерфейсами. Подробно рассмотрим интерфейс IUnknown. А также рассмотрим, как можно использовать интерфейсы в распределенных приложениях.
Понятие исключительной ситуации, ее обработка средствами Delphi
Под исключительной ситуацией мы будем понимать некое непредвиденное событие, способное повлиять на дальнейшее выполнение программы.
При обработке такой ситуации Delphi, как обычно, работает с объектами. С точки зрения компилятора Delphi исключительная ситуация — это объект. Для работы с этим специфичным объектом в Delphi (точнее, в Object Pascal) были введены следующие языковые конструкции: try .. except и try .. finally .
Рассмотрим эти языковые конструкции более подробно.
Итак, конструкция try .. except имеет следующий синтаксис (листинг 1.6):

Если при выполнении кода, размещенного в разделе try, генерируется исключение, то выполнение этого раздела прекращается и управление передается коду, размещенному в разделе except. Раздел except может использоваться двумя способами. Во-первых, в нем могут располагаться любые операторы, кроме обработчиков исключений, начинающихся с приставки on. Это и операторы сообщения об ошибке, и команды, позволяющие освобождать системные ресурсы, а также другие операторы и команды. Во-вторых, раздел except используется для обработки исключений. В этом случае в него могут включаться только операторы обработки исключений. Если среди обработчиков встретился обработчик, соответствующий сгенерированному исключению, то выполняется оператор этого обработчика, исключение разрушается и управление передается коду, расположенному после оператора on Exception do. Раздел, расположенный после ключевого слова else, служит для обработки любых исключений, не описанных в разделе except. Этот раздел не является обязательным. Если при обработке исключительной ситуации не будет найден подходящий обработчик, то произойдет обработка системным обработчиком исключений.
Рассмотрим простой пример обработки исключительной ситуации деления на ноль (листинг 1.7).

Листинг 1.7
try
а:=10;
b:=0;
c:=a/b;
except
on EZeroDivide do MessageBox(‘Делить на ноль нельзя!’);
end;

Итак, как можно видеть из приведенного выше примера, для обработки разных исключений служат разные операторы. Рассмотрим более подробно оператор обработки on .. do . Данный оператор находится внутри раздела except и может иметь две формы (листинг 1.8).

Листинг 1.8
on do ;
или
on :
do

Этот оператор обрабатывает только тот класс исключений, который в нем указан. При указании родительского (базового) класса, все классы исключений — потомки данного класса — также будут обработаны. Для обработки всех исключений можно обратиться к базовому классу всех исключений: Exception. После обработки исключения оно разрушается. Вторая форма оператора on .. do отличается от первой тем, что данному исключению можно временно присвоить имя и обращаться к свойствам исключения. Обращаться к свойствам исключения можно с помощью конструкции . . Посмотрим листинг 1.9.

Листинг 1.9
try
ScrollBarl.Max := ScrollBarl.Min — 1;
except
on E: EInvalidOperation do
MessageDlg( ‘Игнорируем исключение: ‘- + E.Message, mtlnformation, [mbOK], O)
end;

В приведенном примере мы присваиваем исключению EInvalidOperation временное имя Е. Затем в окне сообщения выводим текст ошибки E.Message, выдаваемый Delphi по умолчанию (если бы не было нашего обработчика ошибки).

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

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

После выполнения операторов обработки исключения, написанных программистом, выполняется команда raise, которая снова принудительно вызывает это исключение, после чего управление передается стандартному обработчику исключений.
В случае, когда исключение успешно проходит через все блоки try в коде приложения, вызывается метод HandleException . Он показывает диалоговое окно ошибки. Вы можете вызвать этот метод так, как в листинге 1.11.

Листинг 1.11
try
< операторы >except
Application.HandieException(Self);
end;

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

Итак, операторы, которые размещены после ключевого слова finally , будут выполняться в любом случае, была сгенерирована исключительная ситуация или нет. Если в разделе try была сгенерирована исключительная ситуация, то управление немедленно передается разделу finally . Также, если исключительной ситуации в разделе, try не было, блок finally будет выполняться. Даже если в разделе finally произойдет ошибка, выполнение операторов этого раздела будет продолжено до конца. В конструкции try .. finally не происходит обработка исключений, она используется в основмом для освобождения ресурсов памяти, закрытия ненужных файлов и других операций освобождения ресурсов. Таким образом, в данной конструкции нуждаются операции с файлами, памятью, ресурсами Windows и объектами.
Код обработки исключения можно разбить на блоки try .. except .. end и try .. finally .. end. Эти блоки могут быть вложенными (рис. 1.24, а и б).
При разработке приложений на Delphi часто возникают ситуации, когда программисту не требуется обрабатывать исключения, а необходимо лишь прервать нежелательное действие, вызывающее ошибку. Для этого применяются так называемые молчаливые исключения (silent exceptions). Молчаливые исключения являются потомками стандартного исключения EAbort . По умолчанию обработчик ошибок VCL Delphi отображает на экране диалоговое окно ошибки для всех исключений, кроме наследников EAbort.

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

Для того чтобы сгенерировать молчаливое исключение, можно вызвать процедуру Abort. Она автоматически сгенерирует исключение EAbort, которое прервет текущую операцию без вывода сведения об ошибке на экран. Рассмотрим пример. Пусть форма содержит пустой список (ListBoxi) и кнопку (Button1). Запишем в обработчик события кнопки onclick следующий код:

Листинг 1.13
procedure TForml.ButtonlClick(Sender: TObject);
var
I: Integer;
begin
for I := 1 to 10 do (цикл 10 раз>
begin
ListBoxl.Items.Add(IntToStr(I)); <добавляем номер в список>
if I = 7 then Abort; <прерываем добавление номеров в список после добавления седьмого номера>
end;
end;

В результате работы программы, после нажатия кнопки Button1, в список будет добавлено семь строк с номерами от 1 до 7.

Рис. 1.24. Вложенные блоки в обработчике исключений (а) и в конструкции защиты кода (б)
Обработка RTL-исключений. Иерархия исключений
RTL (real time library)-исключения определены в модуле Delphi sysutils и являются наследниками базового класса исключений Exception.
Имеется несколько типов исключений-наследников RTL (табл. 1.1).
Таблица 1.1 . Типы исключений из RTL

Ошибка доступа к файлу или устройству ввода/вывода

Большинство исключений ввода/вывода связано с кодом ошибки, возвращаемом Windows при обращении к файлу

Ошибка использования динамической памяти

Ошибки кучи возникают при недостатке памяти или когда в приложении присутствует указатель на область памяти вне кучи

Целочисленные математические операции

Неправильное действие с выражением целого типа

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

Математические операции с плавающей точкой

Неправильное действие с выражением вещественного типа

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

Неправильная работа с классами при помощи операции as

Объекты могут работать только с совместимыми объектами

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

Функции преобразования типов (IntToStr, StrToInt И др.) генерируют эту ошибку в случае невозможности преобразования

Аппаратные ошибки указывают, что или процессор, или пользователь сгенерировал ошибку: доступа, переполнения стека или другую

Неправильное использование типа

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


Рассмотрим теперь иерархию классов исключений .более подробно:

— базовый класс исключений

— исключение для намеренного прерывания вычислений

— попытка вызова абстрактного метода

— ошибка доступа к памяти

— ошибка при работе с массивами

— ошибка при проверке истинности

— ошибка доступа к массиву булевых величин TBits

— ошибка построения кэша

— ошибка регистрации или переименования компонента

— нажатие пользователем клавиш + при выполнении консольного приложения

— ошибка преобразования строк (объектов)

— ошибка работы с базами данных

— ошибка в наборе данных клиента

— ошибка обновления данных компонента

— генерируется компонентом TQuery при попытке открыть запрос без select

— ошибка при обновлении В TProvider

— ошибка ввода даты или времени

— ошибка формата данных в кубе решений

— ошибочный индекс в задании размерности в кубе решений

— ошибка ввода/вывода в файл

— базовый класс исключений целочисленных математических операций

— ошибка деления на ноль

— значение или индекс вне допустимого диапазона

— ошибочное преобразование типов as к интерфейсу

— нераспознаваемый графический файл

— ошибка при операциях с графикой

— ошибка при работе с сеткой (Grid)

— ошибочная операция с компонентом

— ошибка при операциях с указателем

— ошибка при работе со списком

— ошибка выделения памяти для куба решений

— базовый класс исключений операций с плавающей запятой

— недопустимое значение параметра при обращении к математической функции

— потеря значащих разрядов

— ошибка деления на ноль

— ошибка доступа к устройствам через драйвер MCI (Media Control Interface)

— ошибка при работе с элементами меню

— ошибка при связывании приложения с элементом ActiveX

— низкоуровневая ошибка OLE

— ошибка интерфейса OLE IDispatch

— ошибка OLE, связанная со свойством или методом

— ошибка при работе с Tout line

— ошибка распределения памяти

— ошибка создания обработчика Windows

— исключение, генерируемое при загрузке или использовании пакета

— ошибка преобразования текста описания формы в двоичный формат

— ошибка выполнения инструкции процессора из-за нехватки привилегий

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

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

— ошибка при задании значения свойства

— ошибка при работе с реестром Windows

— ошибка задания типа сервера (компонент TReport не может соединиться с базой данных)

— ошибка загрузки файла ресурсов (*.DFM или *.RES) во время создания приложения

— переполнение стека — базовый класс исключений ошибок потоков

— ошибка создания файла

— ошибка открытия файла

— базовый класс исключений файловых потоков

— ошибка чтения заданного числа байт

— ошибка записи заданного числа байт

— ошибка связи компонента с приложением

— ошибка чтения файла ресурсов

— не найден метод

— ошибка доступа к окну списка

— ошибка многопоточного приложения

— ошибка индекса при работе с TTreeview

— ошибка при работе с типом данных variant

— внутренняя ошибка Windows


Итак, класс Exception является базовым классом всех исключений в Delphi. Все вышеописанные классы являются прямыми или косвенными наследниками класса Exception. При создании собственных новых классов исключений необходимо использовать класс Exception как родительский. Только в этом случае Delphi гарантированно распознает и обработает новый класс как исключение. В свою очередь, класс Exception является прямым наследником базового класса TObject, и наследует все его функции. В отличие от
других классов, классы исключений начинаются не с буквы т, а с буквы Е. При создании собственных классов исключений можно называть их по своему усмотрению, не обязательно начиная с буквы Е (но это считается плохим стилем программирования).
Создание собственных исключений
Для создания собственных типов исключений необходимо знать, как определить тип объекта исключения, а также как вызвать исключение.
Так как исключение является объектом Delphi, то определение нового типа исключения так же nporfro, как определение объекта нового типа. Теоретически возможно вызывать любой объект как объект исключения, но стандартные обработчики исключений работают только с теми объектами, предками которых являются Exception или потомки Exception.
В качестве примера создания собственного типа исключения рассмотрим следующее определение:
type
EMyException = class(Exception);
Теперь, если вы вызовете исключение EMyException, но не напишете обработчик для него, то произойдет вызов стандартного обработчика для Exception. Так как стандартный обработчик для Exception показывает имя вызванного исключения, вы можете увидеть имя вашего нового исключения.
Для вызова созданного исключения используйте команду raise. Для примера рассмотрим типичную задачу проверки введенного пользователем пароля:
type
EPasswordlnval >
После определения нового типа исключения EpasswordInwalid вы можете вызвать это исключение в любом месте программы:
if Password <> CorrectPassword then raise EPasswordlnvalidCreate(‘Введен неправильный пароль’);
Вызов созданного исключения производится по его имени.
Интерфейсы и их совместное использование классами
Ключевое слово Delphi interface позволяет создавать и использовать интерфейсы в ваших приложениях. Интерфейсы служат для расширения модели наследования в VCL, позволяя одному классу принадлежать нескольким
интерфейсам, а также нескольким классам — наследникам различных базовых классов использовать один интерфейс. Интерфейсы полезны в тех случаях, когда наборы операций, такие как потоки, используются большим количеством объектов.
Таким образом, интерфейсы — это средства для обеспечения взаимодействия между разными объектами.
Интерфейсы являются фундаментом для технологий компонентной объектной модели (СОМ) и CORBA.
Интерфейсы похожи на классы, которые содержат в себе только абстрактные методы и четкие определения их функциональности. Определение метода интерфейса включает в себя параметры и типы параметров, возвращаемый тип, а также ожидаемое поведение. Методы интерфейса семантически или логически связаны с отражением цели интерфейса. Существует соглашение об интерфейсах, гласящее, что каждый интерфейс должен быть назван в соответствии с задачей, которую он будет выполнять. Например, интерфейс iMalloc предназначен для распределения, освобождения и управления памятью. Аналогично, интерфейс IPersist может использоваться как базовый интерфейс для потомков, каждый из которых определяет специфичные прототипы методов для загрузки и сохранения состояния объектов в память, поток или в файл. Приведем простой пример объявления интерфейса (листинг 1.14):

Листинг 1.14
type
IEdit = interface
procedure Copy; stdcall;
procedure Cut; stdcall;
procedure Paste; stdcall;
function Undo: Boolean; stdcall;
end;

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

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

Листинг 1.15
TEditor = class(TInterfacedObject, lEdit)
procedure Copy; stdcall;
procedure Cut; stdcall;
procedure Paste; stdcall;
function Undo: Boolean; stdcall;
end;

Как уже было отмечено выше, использование интерфейсов позволяет нескольким классам использовать один интерфейс, игнорируя требование наличия одного базового класса-предка. Следует запомнить, что интерфейс — это тип с управляемым временем жизни, т. е., он автоматически, при инициализации, принимает значение nil, обладает счетчиком ссылок и автоматически уничтожается, при выходе за пределы своей области видимости.
Интерфейс IUnknown
По аналогии с наследованием классов, предком которых является базовый класс TObject, все интерфейсы — это прямые или косвенные наследники интерфейса IUnknown. Этот базовый интерфейс описан в модуле System следующим образом (листинг 1.16):

Листинг 1.16
type
IUnknown = interface
[‘< 00000000-0000-0000-С000-000000000046>‘]
function Querylnterfасе(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;7
end;

Синтаксис описания интерфейса похож на описание класса. Главное отличие заключается в том, что интерфейс может быть связан с глобальным уникальным идентификатором (Global Unique Identifier, GUID).
GUID — это 128-разрядное целое число, которое используется для уникальной идентификации интерфейсов. Так как в 128-ми разрядах можно закодировать большое количество чисел, GUID практически гарантирует глобальную уникальность идентификатора интерфейса. То есть, практически невозможно, чтобы на двух компьютерах GUID совпал. Алгоритм генерации GUID основан на аппаратной части компьютера (частота процессора, номер сетевой карты и т. д.). В результате работы алгоритма, который может быть реализован с помощью функции API CocreateGUID (), получается запись типа TGUID. Эту запись можно определить в виде строки следующего формата:
‘<хххххххх-хххх-хххх-хххх-хххххххххххх>‘
В дальнейшем, при рассмотрении СОМ, мы увидим, что каждый интерфейс или класс СОМ имеет собственный GUID. Для интерфейсов — это идентификатор интерфейса (Interface ID, IID), а для класса — идентификатор класса (Class ID, CLSID).
Для создания нового GUID в среде Delphi достаточно нажать комбинацию клавиш + + в окне редактора кода.
Итак, интерфейс lunknown поддерживает три метода, которые наследуются всеми интерфейсами:
— QueryInterface() — используется для создания запроса, поддерживается ли данный интерфейс и если ответ положителен, то метод возвращает указатель на него. Для примера, предположим, что имеется некоторый объект object, который поддерживает несколько интерфейсов interface1, interface2 и др. Для получения указателя на интерфейс interface2, объекта Object, вам нужно вызвать метод Interface2.Query Interface О;
— _AddRef () — используется, когда получен указатель на данный интерфейс и вы хотите работать с этим указателем. Метод _AddRef() обязательно должен заканчиваться вызовом метода _Release ();
— _Release () — данный метод применяется для завершения работы с интерфейсом.
Интерфейсы являются фундаментальными элементами таких распределенных объектных моделей, как СОМ и CORBA.
Более подробно интерфейс lunknown мы рассмотрим в третьей части книги, посвященной использованию технологий СОМ и ActiveX.
Класс TlnterfacedObject
В VCL Delphi определен класс TlnterfacedObject, который служит базовым классом для объектов интерфейса. Данный класс определен в модуле Delphi system следующим образом (листинг 1.17):

Листинг 1.17.
type
TlnterfacedObject = class(TObject, IUnknown) private
FRefCount: Integer;
protected
function Querylnterface(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall; public
property RefCount: Integer read FRefCount;
end;

Как мы видим, данный класс в качестве родителей имеет класс TObject и интерфейс lunknown. Класс Tinterfacedobject позволяет достаточно легко создавать классы, поддерживающие интерфейсы. Например,
type
TMyObjInterfaced = class(TInterfacedObject, IPaint)
end;
На вышеприведенном примере мы определяем новый класс TMyObj interfaced, который является прямым потомком класса Tinterfacedobject и поддерживает некий интерфейс IPaint.
Использование оператора as
Объекты, поддерживающие интерфейсы, могут использовать оператор as для динамического присоединения интерфейса. Например,
procedurePaintObjecta(P: TInterfacedObject) var
X: IPaint; begin
X := P as IPaint;
<операторы>
end;
В этом примере переменная Р имеет тип Tinterfacedobject. Данная переменная может быть назначена переменной х, как ссылка на интерфейс IPaint.. Для такого назначения компилятор генерирует код для вызова метода Querylnterface, относяшегося к Интерфейсу IUnknown переменной Р. Подобное назначение возможно, даже если Р не поддерживает данный интерфейс. То есть, компилятор не выдаст ошибку при таком назначении.
Во время выполнения вышеприведенного примера либо успешно происходит присваивание
Х:= Р as IPaint;
либо генерируется исключительная ситуация.
При использовании оператора as вы должны выполнять следующие требования:
— при объявлении интерфейса, явно объявляйте в качестве предка интерфейс lunknown. Так как только в этом случае вы сможете воспользоваться оператором аs;
— если вы используете оператор as для интерфейса, данный интерфейс должен иметь свой IID. Напомним, что для создания нового IID достаточно, находясь в редакторе кода, использовать комбинацию клавиш + + .
Использование ключевого слова implements
Многие классы VCL Delphi имеют в качестве некоторых своих свойств объекты. Кроме того, вы можете использовать в качестве свойств класса интерфейсы. В том случае, когда свойство имеет тип интерфейса, то вы можете использовать ключевое слово implements для определения методов, которые данный интерфейс передает объекту. По умолчанию, ключевое слово implements передает все методы интерфейса. Тем не менее, вы можете самостоятельно определить список тех методов интерфейса, которые передаются объекту.
На приведенном ниже листинге 1.18 представлен пример использования ключевого слова implements при создании объекта адаптера цвета, предназначенного для преобразования восьмибитного значения цвета RGB.

Листинг 1.18
unit cadapt;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
IRGBSbit = interface
[‘‘]
function Red: Byte;
function Green: Byte;
function Blue: Byte;
end;
IColorRef = interface
[41d76360b-f4f5-lldl-87d4-00c04fbl7199>’] function Color: Integer; end;
TRGBSColorRefAdapter = class(TInterfacedObject, IRGBSbit, IColorRef) private
FRGBSbit: IRGBSbit;
FPalRelative: Boolean; public
constructor Create(rgb: IRGBSbit);
property RGBSIntf: IRGBSbit read FRGBSbit implements IRGBSbit;
property PalRelative: Boolean read FPalRelative write-FPalRelative;
function Color: Integer; end;
implementation
constructor TRGBSColorRefAdapter.Create(rgb: IRGBSbit);
begin
FRGBSbit := rgb; end;
function TRGBSColorRefAdapter.Color: Integer;
begin
if FPalRelative then
Result := PaletteRGB(RGBSIntf.Red, RGBSIntf.Green, RGBSIntf.Blue) else
Result := RGB(RGBSIntf.Red, RGBSIntf.Green, RGBSIntf.Blue);
end;
end.

Использование интерфейсов в распределенных приложениях
Интерфейсы являются фундаментальным элементом распределенных объектных моделей СОМ и CORBA (более подробно о моделях читайте в третьей части книги). Delphi обеспечивает базовые классы для этих технологий, которые расширяют возможности объекта TInterfacedObject.
Классы СОМ добавляют возможности использования фабрик классов и идентификаторов классов (CLSID). Фабрики классов отвечают за создание экземпляров классов посредством CLSID. В свою очередь, CLSID используются для регистрации и манипуляции классами СОМ. Классы СОМ, которые обладают и фабрикой класса, и идентификатором класса, называются CoClasses. CoClasses имеют преимущество перед Querylnterface по части поддержки новых версий интерфейсов. Новые версии старых интерфейсов автоматически становятся доступными для программ-клиентов. В приложе ниях СОМ разработчик может вносить правку в код интерфейса для улучшения работы приложения, не изменяя клиентской части кода.
Другая распределенная технология называется CORBA (Архитектура Брокера Общих Объектных Запросов, Common Object Request Broker Architecture). Описание данной технологии не входит в эту книгу, заметим только, что она позволяет создавать приложения для взаимодействия с различными аппаратными или программными платформами. Так, клиентское приложение CORBA, работающее в операционной системе Windows 98, также легко будет работать с сервером приложений операционной системы UNIX.

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

Понять абсурдность этого понятия легче всего обратившись к истокам его возникновения. Рождено оно было Полем Дираком в 1930-х, когда стало ясно, что отрицание эфира в чистом виде, как это делал великий математик, но посредственный физик Анри Пуанкаре, уже нельзя. Слишком много фактов противоречит этому.

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

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

On — Ключевое слово Delphi

С целью поддержки структурной обработки исключительных ситуаций (exception) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в Delphi.

Структурная обработка исключительных ситуаций

Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

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

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

Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

Если в A нет подходящего обработчика, то поиск продолжается в более верхнем уровне, и так может идти, пока поиск не достигнет подходящего обработчика ошибок среди используемых по умолчанию обработчиков в RTL. Обработчики ошибок из RTL только показывают сообщение об ошибке и форсированно прекращают выполнение программы. Любая исключительная ситуация, которая осталась необработанной, приведет к прекращению выполнения приложения.

Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

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

Модель исключительных ситуаций в Delphi

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

Синтаксис обработки исключительных ситуаций

Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

  • try..except
  • try..finally

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

Для уверенности в том, что ресурсы, занятые вашим приложением, освободятся в любом случае, Вы можете использовать конструкцию второго типа. Код, расположенный в части finally, выполняется в любом случае, даже если возникает исключительная ситуация. Соответствующий синтаксис:

Примеры обработки исключительных ситуаций

Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

При ErrorCondition = True программа выдаст:

Возможно вас удивила декларация типа ‘ESampleError = . Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели.

Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError.

Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)).

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

Рассмотрим модифицированную процедуру B:

Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

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

Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

Вызов исключительной ситуации

В процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию:

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

Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

Доступ к экземпляру объекта exception

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

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

Рассмотрим модифицированную процедуру A в нашем примере:

Здесь все изменения внесены в строку

Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

Предопределенные обработчики исключительных ситуаций

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

  • Exception — базовый класс-предок всех обработчиков исключительных ситуаций.
  • EAbort — «скрытое» исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort, определенная, как:
  • EComponentError — вызывается в двух ситуациях:
    1. при попытке регистрации компоненты за пределами процедуры Register;
    2. когда имя компоненты не уникально или не допустимо.
  • EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat, когда конвертация строки в соответствующий числовой тип невозможна.
  • EInOutError — происходит при ошибках ввода/вывода при включенной директиве <$I+>.
  • EIntError — предок исключений, случающихся при выполнении целочисленных операций.
    • EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.
    • EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве <$Q+>.
    • ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве <$R+>.
  • EInval — General Protection Fault. Соответствует RunTime Error 216.
  • EInval >Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе >Особенно важны два свойства класса EDBEngineError : Errors — список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки 0;
    ErrorCount — количество ошибок в стеке.
    Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса TDBError:
    ErrorCode — код ошибки, возвращаемый Borland Database Engine;
    Category — категория ошибки, описанной в ErrorCode;
    SubCode — ‘субкод’ ошибки из ErrorCode;
    NativeError — ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка в ErrorCode не от сервера;
    Message — сообщение, переданное сервером, если NativeError не равно 0; сообщение BDE — в противном случае.
  • EDBEditError — наследник Exception ; вызывается, когда данные не совместимы с маской ввода, наложенной на поле. Объявлено в модуле Mask.
  • Заключение

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

    On — Ключевое слово Delphi

    Структурная обработка исключительных ситуаций

    Модель исключительных ситуаций в Delphi

    Синтаксис обработки исключительных ситуаций

    Примеры обработки исключительных ситуаций

    Вызов исключительной ситуации

    Доступ к экземпляру объекта exception

    Предопределенные обработчики исключительных ситуаций

    Исключения, возникающие при работе с базами данных

      1. Обзор
      2. С целью поддержки структурной обработки исключительных ситуаций ( exception ) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в D elphi.
      3. Структурная обработка исключительных ситуаций
      4. Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы “охраняют” фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в “охраняемом” участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния “железа” для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от “железа”, не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

      При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

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

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

      Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы “охрану” со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает ( raise ) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске “защищенного” участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

      Если в A нет подходящего обработчика, то поиск продолжается в более верхнем уровне, и так может идти, пока поиск не достигнет подходящего обработчика ошибок среди используемых по умолчанию обработчиков в RTL. Обработчики ошибок из RTL только показывают сообщение об ошибке и форсированно прекращают выполнение программы. Любая исключительная ситуация, которая осталась необработанной, приведет к прекращению выполнения приложения.

      Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

      Данная система называется структурной, поскольку обработка ошибок определяется областью “защищенного” кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы.

    1. Модель исключительных ситуаций в Delphi
    2. Модель исключительных ситуаций в Object Pascal является невозобновляемой( non-resumable ). При возникновении исключительной ситуации Вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая( resumable ) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.
    3. Синтаксис обработки исключительных ситуаций

    Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try . Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

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

    on Exception1 do Statement;

    on Exception2 do Statement;

    Для уверенности в том, что ресурсы, занятые вашим приложением, освободятся в любом случае, Вы можете использовать конструкцию второго типа. Код, расположенный в части finally, выполняется в любом случае, даже если возникает исключительная ситуация. Соответствующий синтаксис:

        1. Примеры обработки исключительных ситуаций
        2. Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

        if (ErrorCondition) then

        writeln(‘Raising exception in C’);

        writeln(‘Enter A»s try block’);

        writeln(‘After B call’);

        on ESampleError do

        writeln(‘Inside A»s ESampleError handler’);

        on ESomethingElse do

        writeln(‘Inside A»s ESomethingElse handler’);

        При ErrorCondition = True программа выдаст:

        Enter A’s try block

        Raising exception in C

        Inside A’s ESampleError handler

        Возможно вас удивила декларация типа ‘ESampleError = . Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели.

        Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError.

        Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end . Эта часть кода завершается ключевым словом except , далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy , далее может быть включен необязательный блок else, вся конструкция заканчивается end; . В конструкции, назначающей определенную обработку для конкретной исключительной ситуации ( on xxxx do yyyy ), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on , то выполнение программы переходит сюда (на код после do ). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

        Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

        После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)).

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

        Рассмотрим модифицированную процедуру B:

        Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

        writeln(‘enter NewB»s try block’);

        writeln(‘end of NewB»s try block’);

        writeln(‘inside NewB»s finally block’);

        Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

        Enter A’s try block

        enter NewB’s try block

        Raising exception in C

        inside NewB’s finally block

        Inside A’s ESampleError handler

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

        Почему вызов GetMem не помещен внутрь блока try ? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

        А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

        writeln(‘enter B»s try block’);

        writeln(‘end of B»s try block’);

        writeln(‘inside B»s finally block’);

        if P <> nil then FreeMem(P, 1000);

        if Q <> nil then FreeMem(Q, 1000);

        if R <> nil then FreeMem(R, 1000);

        if S <> nil then FreeMem(S, 1000);

        Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

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

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

        Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

      3. Доступ к экземпляру объекта exception
      4. До сих пор мы рассматривали механизмы защиты кода и ресурсов, логику работы программы в исключительной ситуации. Теперь нужно немного разобраться с тем, как же обрабатывать возникшую ошибку. А точнее, как получить дополнительную информацию о коде ошибки, текст сообщения и т.п.

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

        Рассмотрим модифицированную процедуру A в нашем примере:

        writeln(‘Enter A»s try block’);

        writeln(‘After B call’);

        on E: ESampleError do writeln(E.Message);

        on ESomethingElse do

        writeln(‘Inside A»s ESomethingElse handler’);

        Здесь все изменения внесены в строку

        on ESE: ESampleError do writeln(ESE.Message);

        Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

        Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

        on ESampleError do

        writeln( ESample Error(ExceptionObject).Message);

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

        • Exception — базовый класс-предок всех обработчиков исключительных ситуаций.
        • EAbort — “ скрытое” исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort , определенная, как:

        raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;

        • EComponentError — вызывается в двух ситуациях:
      5. 1) при попытке регистрации компоненты за пределами процедуры Register;

        2) когда имя компоненты не уникально или не допустимо.

        • EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat , когда конвертация строки в соответствующий числовой тип невозможна.
        • EInOutError — происходит при ошибках ввода/вывода при включенной директиве <$I+>.
        • EIntError — предок исключений, случающихся при выполнении целочисленных операций.
        • EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.
        • EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве <$Q+>.
        • ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве <$R+>.
        • EInvalidCast — происходит при попытке приведения переменных одного класса к другому классу, несовместимому с первым (например, приведение переменной типа TListBox к TMemo).
        • EInvalidGraphic — вызывается при попытке передачи в LoadFromFile файла, несовместимого графического формата.
        • EInvalidGraphicOperation — вызывается при попытке выполнения операций, неприменимых для данного графического формата (например, Resize для TIcon).
        • EInvalidObject — реально нигде не используется, объявлен в Controls.pas.
        • EInvalidOperation — вызывается при попытке отображения или обращения по Windows-обработчику (handle) контрольного элемента, не имеющего владельца (например, сразу после вызова MyControl:=TListBox.Create(. ) происходит обращение к методу Refre sh).
        • EInvalidPointer — происходит при попытке освобождения уже освобожденного или еще неинициализированного указателя, при вызове Dispose(), FreeMem() или деструктора класса.
        • EListError — вызывается при обращении к элементу наследника TList по индексу, выходящему за пределы допустимых значений (например, объект TStringList содержит только 10 строк, а происходит обращение к одиннадцатому).
        • EMathError — предок исключений, случающихся при выполнении операций с плавающей точкой.
        • EInvalidOp — происходит, когда математическому сопроцессору передается ошибочная инструкция. Такое исключение не будет до конца обработано, пока Вы контролируете сопроцессор напрямую из ассемблерного кода.
        • EOverflow — происходит как результат переполнения операций с плавающей точкой при слишком больших величинах. Соответствует RunTime Error 205.
        • Underflow — происходит как результат переполнения операций с плавающей точкой при слишком малых величинах. Соответствует RunTime Error 206.
        • EZeroDivide — вызывается в результате деления на ноль.
        • EMenuError — вызывается в случае любых ошибок при работе с пунктами меню для компонент TMenu, TMenuItem, TPopupMenu и их наследников.
        • EOutlineError — вызывается в случае любых ошибок при работе с TOutLine и любыми его наследниками.
        • EOutOfMemory — происходит в случае вызовов New(), GetMem() или конструкторов классов при невозможности распределения памяти. Соответствует RunTime Error 203.
        • EOutOfResources — происходит в том случае, когда невозможно выполнение запроса на выделение или заполнение тех или иных Windows ресурсов (например таких, как обработчики — handles).
        • EParserError — вызывается когда Delphi не может произвести разбор и перевод текста описания формы в двоичный вид (часто происходит в случае исправления текста описания формы вручную в IDE Delphi).
        • EPrinter — вызывается в случае любых ошибок при работе с принтером.
        • EProcessorException — предок исключений, вызываемых в случае прерывания процессора- hardware breakpoint. Никогда не включается в DLL, может обрабатываться только в “цельном” приложении.
        • EBreakpoint — вызывается в случае останова на точке прерывания при отладке в IDE Delphi. Среда Delphi обрабатывает это исключение самостоятельно.
        • EFault — предок исключений, вызываемых в случае невозможности обработки процессором тех или иных операций.
          • EGPFault — вызывается, когда происходит “общее нарушение защиты” — General Protection Fault. Соответствует RunTime Error 216.
          • EInvalidOpCode — вызывается, когда процессор пытается выполнить недопустимые инструкции.
          • EPageFault — обычно происходит как результат ошибки менеджера памяти Windows, вследствие некоторых ошибок в коде Вашего приложения. После такого исключения рекомендуется перезапустить Windows.
          • EStackFault — происходит при ошибках работы со стеком, часто вследствие некорректных попыток доступа к стеку из фрагментов кода на ассемблере. Компиляция Ваших программ со включенной проверкой работы со стеком <$S+>помогает отследить такого рода ошибки.
        • ESingleStep — аналогично EBreakpoint, это исключение происходит при пошаговом выполнении приложения в IDE Delphi, которая сама его и обрабатывает.
        • EPropertyError — вызывается в случае ошибок в редакторах свойств, встраиваемых в IDE Delphi. Имеет большое значение для написания надежных property editors. Определен в модуле DsgnIntf.pas.
        • EResNotFound — происходит в случае тех или иных проблем, имеющих место при попытке загрузки ресурсов форм — файлов .DFM в режиме дизайнера. Часто причиной таких исключений бывает нарушение соответствия между определением класса формы и ее описанием на уровне ресурса (например,вследствие изменения порядка следования полей-ссылок на компоненты, вставленные в форму в режиме дизайнера).
        • EStreamError — предок исключений, вызываемых при работе с потоками.
        • EFCreateError — происходит в случае ошибок создания потока (например, при некорректном задании файла потока).
        • EFilerError — вызывается при попытке вторичной регистрации уже зарегистрированного класса (компоненты). Является, также, предком специализированных обработчиков исключений, возникающих при работе с классами компонент.
          • EClassNotFound — обычно происходит, когда в описании класса формы удалено поле-ссылка на компоненту, вставленную в форму в режиме дизайнера. Вызывается, в отличие от EResNotFound, в RunTime.
          • EInvalidImage — вызывается при попытке чтения файла, не являющегося ресурсом, или разрушенного файла ресурса, специализированными функциями чтения ресурсов (например, функцией ReadComponent).
          • EMethodNotFound — аналогично EClassNotFound, только при несоответствии методов, связанных с теми или иными обработчиками событий.
          • EReadError — происходит в том случае, когда невозможно прочитать значение свойства или другого набора байт из потока (в том числе ресурса).
          • EFOpenError — вызывается когда тот или иной специфированный поток не может быть открыт (например, когда поток не существует).
        • EStringListError — происходит при ошибках работы с объектом TStringList (кроме ошибок, обрабатываемых TListError).

        Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:

        • EDatabaseError — наследник Exception ; происходит при ошибках доступа к данным в компонентах-наследниках TDataSet. Объявлено в модуле DB . Ниже приведен пример из Delphi On-line Help, посвященный этому исключению:

        Программирование на языке Delphi. Глава 2. Основы языка Delphi. Часть 1

        Оглавление

        В основе среды Borland Delphi лежит одноименный язык программирования — Delphi, ранее известный как Object Pascal. При разработке программы среда Delphi выполняет свою часть работы — создает пользовательский интерфейс согласно вашему дизайну, а вы выполняете свою часть — пишите обработчики событий на языке Delphi. Объем вашей работы зависит от программы: чем сложнее алгоритм, тем тяжелее ваш труд. Необходимо заранее усвоить, что невозможно заставить средство разработки делать всю работу за вас. Некоторые задачи среда Delphi действительно полностью берет на себя, например создание простейшей программы для просмотра базы данных. Однако большинство задач не вписываются в стандартные схемы — вам могут понадобиться специализированные компоненты, которых нет в палитре компонентов, или для задачи может не оказаться готового решения, и вы вынуждены будете решать ее старым дедовским способом — с помощью операторов языка Delphi. Поэтому мы настоятельно рекомендуем вам не игнорировать эту главу, поскольку на практике вы не избежите программирования. Мы решили изложить язык в одной главе, не размазывая его по всей книге, чтобы дать вам фундаментальные знания и обеспечить быстрый доступ к нужной информации при использовании книги в качестве справочника.

        Алфавит


        Буквы

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

        Текст программы на языке Delphi формируется с помощью букв, цифр и специальных символов.

        Буквы — это прописные и строчные символы латинского алфавита и символ подчеркивания:

        Цифры представлены стандартной арабской формой записи:

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

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

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

        Числа

        Одно и то же число можно записать самыми разными способами, например:

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

        Целые числа состоят только из цифр и знака + или -. Если знак опущен и число не равно 0, то оно рассматривается как положительное, например:

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

        Вещественные числа могут быть представлены в двух формах: с фиксированной и плавающей точкой.

        Форма с фиксированной точкой совпадает с обычной записью чисел, например:

        Форма с плавающей точкой используется при работе с очень большими или очень малыми числами. В этой форме число, стоящее перед буквой E, умножается на 10 в степени, указанной после буквы E:

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

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

        Слова-идентификаторы

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

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

        Правильно


        Неправильно


        RightName Wrong Name E_mail E-mail _5inches 5inches

        Все идентификаторы подразделяются на зарезервированные слова, стандартные директивы, стандартные идентификаторы и идентификаторы программиста.

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

        Стандартные директивы интерпретируются либо как зарезервированные слова, либо как идентификаторы программиста в зависимости от контекста, в котором используются. Вот они:

        Стандартные идентификаторы — это имена стандартных подпрограмм, типов данных языка Delphi, т.д. В качестве примера приведем имена подпрограмм ввода и вывода данных и нескольких математических функций. Вы, без сомнения, сами угадаете их назначение:

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

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

        Комментарии

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

        Данные


        Понятие типа данных

        Программа в процессе выполнения всегда обрабатывает какие-либо данные. Данные могут представлять собой целые и дробные числа, символы, строки, массивы, множества и др. Так как компьютер всего лишь машина, для которой данные — это последовательность нулей и единиц, он должен абсолютно точно «знать», как их интерпретировать. По этой причине все данные в языке Delphi подразделены на типы. Для описания каждого типа данных существует свой стандартный идентификатор: для целых — Integer, для дробных — Real, для строк — string и т.д. Программист может образовывать собственные типы данных и давать им произвольные имена (о том, как это делается, мы поговорим чуть позже).

        Тип данных показывает, какие значения принимают данные и какие операции можно с ними выполнять. Каждому типу данных соответствует определенный объем памяти, который требуется для размещения данных. Например, в языке Delphi существует тип данных Byte. Данные этого типа принимают значения в целочисленном диапазоне от 0 до 255, могут участвовать в операциях сложения, вычитания, умножения, деления, и занимают 1 байт памяти.

        Все типы данных в языке Delphi можно расклассифицировать следующим образом:

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

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

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

        Константы

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

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

        Значение константы можно задавать и выражением. Эту возможность удобно использовать для комплексного представления какого-либо понятия. Например, временной промежуток, равный одному месяцу, можно задать так:

        Очевидно, что, изменив базовую константу SecondsInMinute, можно изменить значение константы SecondsInDay.

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

        Такие константы называются типизированными; их основное назначение — объявление константных значений составных типов данных.

        Переменные

        Переменные в отличие от констант могут неограниченное число раз менять свое значение в процессе работы программы. Если в начале программы некоторая переменная X имела значение 0, то в конце программы X может принять значение 10000. Так бывает, например, при суммировании введенных с клавиатуры чисел.

        Переменные описываются с помощью зарезервированного слова var . За ним перечисляются идентификаторы переменных, и через двоеточие указывается их тип. Каждая группа переменных отделяется от другой группы точкой с запятой. Например:

        В теле программы переменной можно присвоить значение. Для этого используется составной символ :=, например:

        Вы можете присвоить значение переменной непосредственно при объявлении:

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

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

        Простые типы данных


        Целочисленные типы данных

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

        Расставляем точки над i в Delphi RAII

        Тема RAII в Delphi обычно замалчивается или же информация по этому вопросу ограничивается обсуждением полезности интерфейсов. Но интерфейсы поодиночке не дают многих желаемых возможностей. Когда в Delphi 2006 появилась перегрузка операций, приватные поля записей, собственные конструкторы и методы в записях и, казалось, было бы логично увидеть и автоматически вызываемый деструктор. И run-time позволяет, и в разделе запроса новых фич Delphi на протяжении нескольких лет в ТОП–10 висит запрос №21729 «Record Operator Overloading: Please implement «Initialize» and «Finalize» operators». Наверное, не судьба. Ничего, я покажу, как обойтись без несостоявшихся фич. Так как Delphi 7 живее всех живых, будут рассмотрены решения, совместимые с Delphi 7 в том числе

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

        Зачем хочется RAII в Delphi?

        • Автоматическое управление памятью. Лестница из try… finally — это не серьёзно. TList, TComponentList и прочие требуют дисциплины от того, кто будет их применять. Особенно сложно, не используя автоматику, сделать корректное освобождение памяти для переменных, используемых из восходящего замыкания
        • Copy-on-write и счётчики ссылок
        • Другое особое поведение при копировании
        • Copy-on-write и счётчики ссылок для объектов, созданных в сторонних библиотеках (например, CFString)

        Для каких типов Delphi действует автоматическое управление?

        • AnsiString, WideString, UnicodeString — строки
        • array of… — динамические массивы
        • reference to… — замыкания в Delphi 2009+
        • интерфейсы
        • Variant, OleVariant — варианты

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

        Чем плохи интерфейсы?
        • Инициализируются nil, у которого нельзя вызывать методы.
          Не подходит, если вы хотите реализовать собственный тип строки или собственную длинную арифметику. Неинициализированная переменная должна вести себя как пустая строка или 0, соответственно
        • Методы не могут изменить содержимое переменной–указателя, у которого они были вызваны
        • Нет контроля за тем, что происходит при копировании объекта. Только AddRef, который не может изменить содержимое переменной–указателя
        • Нет встроенной возможности сделать copy-on-write
        • Нет перегрузки операций
        Чем плохи варианты?
        • Инициализируются Unassigned, у которого также нельзя вызвать методы
        • Вызовы нетипизированы. Реализация IDispatch или диспетчеризации у вариантов — нетривиальная и слабо документированная область знаний
        • Необходимость реализации муторных конверсий между другими типами вариантов, всяческих вспомогательных методов, которые могут быть вызваны

        Как решить большинство этих проблем?

        Решение, которое я предлагаю — заворачивать интерфейсы или варианты внутрь приватной части записей (record). Объявляем тип записи. Объявляем тип интерфейса. Дублируем все методы и в интерфейсе, и в записи. Методы записи перенаправляют все вызовы внутреннему объекту, при этом можно сделать то, что сама по себе переменная интерфейсного типа сделать не способна

        В реализации каждого метода записи предусматриваем случай, когда в приватном поле nil — может потребоваться автоматически инициализировать объект перед тем, как что–либо вызывать у него. Если нужно реализовать Copy-on-write, в интерфейсе объявляется метод

        Этот метод определяет по счётчику ссылок свою уникальность. Если объект не уникален, объект должен создать свою копию и записать этот указатель в Obj вместо себя. Каждый метод записи, который может что–либо изменить, перед передачей управления методу интерфейса должен убедиться в уникальности указателя. Для внутренних нужд можно и у других методов интерфейса предусмотреть var Obj: IOurInterface. Например, по аналогии со встроенными строками может возникнуть желание сделать так, чтобы, когда в строке собственного типа не остаётся символов, динамически размещённый объект удалялся, а внутренний указатель становился nil

        В целях оптимизации при реализации собственных строк или длинной арифметики может потребоваться предусмотреть специальный случай a := a + b. Не гарантирую, что это сработает, но можно попробовать при реализации операции + сравнивать указатели @ Self и @ Result

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

        Вариант в записи — это как зефир в глазури, но вариант в записи

        Собственный тип варианта даёт более полное управление по сравнению с интерфейсом. Так как вариантное поле приватно и наружу этот вариант не должен утекать, можно реализовать лишь минимальный набор методов собственного (custom) типа варианта. Если не считать отладчика, пытающегося привести (CastTo) вариант к строке при наведении курсора, потребуется реализовать копирование (Copy) и уничтожение (Clear) варианта. В оперативной памяти собственные типы варианта, как правило, состоят из маркера типа варианта и указателя (например, наследник TObject). Как это делается, предлагаю посмотреть на примере реализации комплексных чисел (VarCmplx.pas), который присутствует, по крайней мере, начиная с Delphi 7

        Использование вариантов пригодилось бы для однозвенной обёртки CFString. Если делать обёртку для интерфейсов, Delphi будет вызывать AddRef и Release у интерфейса, но CFString — не интерфейс, и потребуется либо обернуть CFString в дополнительный слой косвенности из интерфейса, либо использовать собственный тип варианта, который вызывает CFRetain и CFRelease, требуемые для нормального управления памятью CFString. Это работало бы гораздо лучше, чем та обёртка CFString, которую предлагает Embarcadero в Delphi XE2

        Эй, а как же Delphi 7?

        Delphi — язык с длинной историей, и до того, как появилась объектная система Delphi, в Borland Pascal with Objects была другая объектная система. В Delphi 7 и Delphi 2005 она по–прежнему функционирует. Вместо record пишется ключевое слово object, и получившийся тип во многом похож на record в Delphi 2006: у него могут быть приватные поля, у него могут быть методы. object’ы одного типа можно присваивать друг другу, в этом смысле они тоже аналогичны record. Как раз то, что нам нужно. Компилятор будет ругаться на небезопасный тип, нет перегрузки операций, но это единственные неудобства. Сходство object и record настолько велико, что можно, используя условные директивы компилятора, на старых версиях Delphi объявлять тип как object, а на новых — как record. Именно так я поступил в своей небольшой библиотеке коллекций Delphi-CVariants

        Проблемы могут возникнуть, если пытаться объявить несколько таких типов, использующих друг друга. Цикличные зависимости в исходном коде предусмотрены для классов, интерфейсов и указателей, но не для object’ов as is. Предпочтительнее объявлять object’ы так, чтобы каждый следующий знал про предыдущие, но не наоборот. Поэтому, например, в моей библиотечке CMapIterator знает про CVariant, но CVariant не знает про CMapIterator

        Программирование на языке Delphi

        Глава 2. Основы языка Delphi


        Авторы: А.Н. Вальвачев
        К.А. Сурков
        Д.А. Сурков
        Ю.М. Четырько

        Опубликовано: 12.11.2005
        Исправлено: 10.12.2020
        Версия текста: 1.0

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

        2.1. Алфавит


        2.1.1. Буквы

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

        Текст программы на языке Delphi формируется с помощью букв, цифр и специальных символов.

        Буквы — это прописные и строчные символы латинского алфавита и символ подчеркивания:

        Цифры представлены стандартной арабской формой записи:

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

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

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

        2.1.2. Числа

        Одно и то же число можно записать самыми разными способами, например:

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

        Целые числа состоят только из цифр и знака + или – . Если знак опущен и число не равно 0, то оно рассматривается как положительное, например:

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

        Вещественные числа могут быть представлены в двух формах: с фиксированной и плавающей точкой.

        Форма с фиксированной точкой совпадает с обычной записью чисел, например:

        Форма с плавающей точкой используется при работе с очень большими или очень малыми числами. В этой форме число, стоящее перед буквой E, умножается на 10 в степени, указанной после буквы E:

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

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

        2.1.3. Слова-идентификаторы

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

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

        Правильно Неправильно
        RightName Wrong Name
        E_mail E–mail
        _5inches 5inches

        Все идентификаторы подразделяются на зарезервированные слова, стандартные директивы, стандартные идентификаторы и идентификаторы программиста.

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

        Стандартные директивы интерпретируются либо как зарезервированные слова, либо как идентификаторы программиста в зависимости от контекста, в котором используются. Вот они:

        Стандартные идентификаторы — это имена стандартных подпрограмм, типов данных языка Delphi, т.д. В качестве примера приведем имена подпрограмм ввода и вывода данных и нескольких математических функций. Вы, без сомнения, сами угадаете их назначение:

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

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

        2.1.4. Комментарии

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

        2.2. Данные


        2.2.1. Понятие типа данных

        Программа в процессе выполнения всегда обрабатывает какие-либо данные. Данные могут представлять собой целые и дробные числа, символы, строки, массивы, множества и др. Так как компьютер всего лишь машина, для которой данные — это последовательность нулей и единиц, он должен абсолютно точно «знать», как их интерпретировать. По этой причине все данные в языке Delphi подразделены на типы. Для описания каждого типа данных существует свой стандартный идентификатор: для целых — Integer, для дробных — Real, для строк — string и т.д. Программист может образовывать собственные типы данных и давать им произвольные имена (о том, как это делается, мы поговорим чуть позже).

        Тип данных показывает, какие значения принимают данные и какие операции можно с ними выполнять. Каждому типу данных соответствует определенный объем памяти, который требуется для размещения данных. Например, в языке Delphi существует тип данных Byte. Данные этого типа принимают значения в целочисленном диапазоне от 0 до 255, могут участвовать в операциях сложения, вычитания, умножения, деления, и занимают 1 байт памяти.

        Все типы данных в языке Delphi можно расклассифицировать следующим образом:

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

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

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

        2.2.2. Константы

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

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

        Значение константы можно задавать и выражением. Эту возможность удобно использовать для комплексного представления какого-либо понятия. Например, временной промежуток, равный одному месяцу, можно задать так:

        Очевидно, что, изменив базовую константу SecondsInMinute, можно изменить значение константы SecondsInDay.

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

        Такие константы называются типизированными; их основное назначение — объявление константных значений составных типов данных.

        2.2.3. Переменные

        Переменные в отличие от констант могут неограниченное число раз менять свое значение в процессе работы программы. Если в начале программы некоторая переменная X имела значение 0, то в конце программы X может принять значение 10000. Так бывает, например, при суммировании введенных с клавиатуры чисел.

        Переменные описываются с помощью зарезервированного слова var . За ним перечисляются идентификаторы переменных, и через двоеточие указывается их тип. Каждая группа переменных отделяется от другой группы точкой с запятой. Например:

        В теле программы переменной можно присвоить значение. Для этого используется составной символ := , например:

        Вы можете присвоить значение переменной непосредственно при объявлении:

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

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

        2.3. Простые типы данных


        2.3.1. Целочисленные типы данных

        Целочисленные типы данных применяются для описания целочисленных данных. Для решения различных задач могут потребоваться различные целые числа. В одних задачах счет идет на десятки, в других — на миллионы. Соответственно в языке Delphi имеется несколько целочисленных типов данных, среди которых вы можете выбрать наиболее подходящий для своей задачи (таблица 2.1).

        Фундаментальные типы данных:

        Тип данных Диапазон значений Объем памяти (байт)
        Byte 0..255 1
        Word 0..65535 2
        Shortint –128..127 1
        Smallint –32768..32767 2
        Longint –2147483648..2147483647 4
        Longword 0.. 4294967295 4
        Int64 –2^63..2^63–1 8

        Обобщенные типы данных:

        Тип данных Диапазон значений Формат (байт)
        Cardinal 0.. 4294967295 4*
        Integer –2147483648..2147483647 4*
        Таблица 2.1. Целочисленные типы данных
        ПРИМЕЧАНИЕ

        * — количество байт памяти, требуемых для хранения переменных обобщенных типов данных, приведено для 32-разрядных процессоров семейства x86.

        Пример описания целочисленных данных:

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

        2.3.2. Вещественные типы данных

        Вещественные типы данных применяются для описания вещественных данных с плавающей или с фиксированной точкой (таблица 2.2).

        Тип данных Диапазон значений Мантисса Объем памяти (байт)
        Real 5.0*10 –324 ..1.7*10 308 15–16 8*
        Real48 2.9*10 –39 ..1.7*10 38 11–12 6
        Single 1.5*10 –45 ..3.4*10 38 7–8 4
        Double 5.0*10 –324 ..1.7*10 308 15–16 8
        Extended 3.4*10 –4932 ..1.1*10 4932 19–20 10
        Comp –9223372036854775808 .. 9223372036854775807 19–20 8
        Currency –922337203685477.5808 .. 922337203685477.5807 19–20 8
        Таблица 2.2. Вещественные типы данных
        ПРИМЕЧАНИЕ

        * -количество байт памяти, требуемых для хранения переменных обобщенных типов данных, приведено для 32-разрядных процессоров семейства x86.

        Пример описания вещественных данных:

        Необходимо отметить, что тип Real является обобщенным типом данных и по отношению к нему справедливо все то, что было сказано о типах Integer и Cardinal.

        2.3.3. Символьные типы данных

        Символьные типы применяются для описания данных, значением которых является буква, цифра, знак препинания и другие символы. Существуют два фундаментальных символьных типа данных: AnsiChar и WideChar (таблица 2.3). Они соответствуют двум различным системам кодировки символов. Данные типа AnsiChar занимают один байт памяти и кодируют один из 256 возможных символов расширенной кодовой таблицы ANSI, в то время как данные типа WideChar занимают два байта памяти и кодируют один из 65536 символов кодовой таблицы Unicode. Кодовая таблица Unicode — это стандарт двухбайтовой кодировки символов. Первые 256 символов таблицы Unicode соответствуют таблице ANSI, поэтому тип данных AnsiChar можно рассматривать как подмножество WideChar.

        Фундаментальные типы данных:

        Тип данных Диапазон значений Объем памяти (байт)
        AnsiChar Extended ANSI character set 1
        WideChar Unicode character set 2

        Обобщенный тип данных:

        Тип данных Диапазон значений Формат (байт)
        Char Same as AnsiChar’s range 1*
        Таблица 2.3. Символьные типы данных
        ПРИМЕЧАНИЕ

        * — Тип данных Char является обобщенным и соответствует типу AnsiChar. Однако следует помнить, что в будущем тип данных Char может стать эквивалентным типу данных WideChar, поэтому не следует полагаться на то, что символ занимает в памяти один байт.

        Пример описания переменной символьного типа:

        В программе значения переменных и констант символьных типов заключаются в апострофы (не путать с кавычками!), например:

        2.3.4. Булевские типы данных

        Булевские типы данных названы так в честь Георга Буля (George Boole), одного из авторов формальной логики. Диапазон значений данных булевских типов представлен двумя предопределенными константами: True — истина и False — ложь (таблица 2.4).

        Тип данных Диапазон значений Объем памяти (байт)
        Boolean False (0), True (1) 1
        ByteBool False (0), True (не равно 0) 1
        WordBool False (0), True (не равно 0) 2
        LongBool False (0), True (не равно 0) 4
        Таблица 2.4. Булевские типы данных

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

        Булевские типы данных широко применяются в логических выражениях и в выражениях отношения. Переменные типа Boolean используются для хранения результатов логических выражений и могут принимать только два значения: False и True (стандартные идентификаторы). Булевские типы данных ByteBool, WordBool и LongBool введены в язык Delphi специально для совместимости с другими языками, в частности с языками C и C++. Все булевские типы данных совместимы друг с другом и могут одновременно использоваться в одном выражении.

        2.3.5. Определение новых типов данных

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

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

        Синтаксическая конструкция type позволяет создавать новые порядковые типы: перечисляемые типы и интервальные типы .

        2.3.6. Перечисляемые типы данных

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

        На базе типа TDirection можно объявить переменную Direction и присвоить ей значение:

        На самом деле за идентификаторами значений перечисляемого типа стоят целочисленные константы. По умолчанию, первая константа равна 0, вторая — 1 и т.д. Существует возможность явно назначить значения идентификаторам:

        2.3.7. Интервальные типы данных

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

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

        Данные перечисляемых и интервальных типов занимают в памяти 1, 2 или 4 байта в зависимости от диапазона значений типа. Например, если диапазон значений не превышает 256, то элемент данных занимает один байт памяти.

        2.3.8. Временной тип данных

        Для представления значений даты и времени в среде Delphi существует тип TDateTime. Он объявлен тождественным типу Double. Целая часть элемента данных типа TDateTime соответствует количеству дней, прошедших с полночи 30 декабря 1899 года. Дробная часть элемента данных типа TDateTime соответствует времени дня. Следующие примеры поясняют сказанное:

        Значение Дата Время
        30.12.1899 00:00:00
        0.5 30.12.1899 12:00:00
        1.5 31.12.1899 12:00:00
        –1.25 29.12.1899 06:00:00
        35431.0 1.1.1997 00:00:00

        2.3.9. Типы данных со словом type

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

        В приведенном выше примере тип данных TFileName является псевдонимом для стандартного типа данных string.

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

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

        В примере переменные A и B оказываются несовместимы друг с другом из-за слова type в описании типа TType2. Если же переменные A и B принадлежат простым типам данных, то оператор присваивания будет работать:

        2.4. Операции


        2.4.1. Выражения

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

        X, Y, 2 — операнды; ‘+’, ‘/’ — знаки операций; скобки говорят о том, что сначала выполняется операция сложения, потом — деления.

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

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

        2.4.2. Арифметические операции

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

        Операция Действие Тип операндов Тип результата
        + Сложение Целый, вещественный Целый, вещественный
        Вычитание Целый, вещественный Целый, вещественный
        * Умножение Целый, вещественный Целый, вещественный
        / Деление Целый, вещественный Вещественный
        div Целочисленное деление Целый Целый
        mod Остаток от деления Целый Целый
        Таблица 2.5. Арифметические операции

        Операции сложения, вычитания и умножения соответствуют аналогичным операциям в математике. В отличие от них операция деления имеет три формы: обычное деление (/), целочисленное деление ( div ), остаток от деления ( mod ). Назначение каждой из операций станет понятным после изучения следующих примеров:

        Выражение Результат
        6.8 – 2 4.8
        7.3 * 17 124.1
        –(5 + 9) –14
        –13.5 / 5 –2.7
        –10 div 4 –2
        27 div 5 5
        5 div 10
        5 mod 2 1
        11 mod 4 3
        –20 mod 7 –6
        –20 mod 7 –6

        2.4.3. Операции отношения

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

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

        Операция Действие Выражение Результат
        = Равно A = B True, если A = B
        <> Не равно A <> B True, если A B
        Больше A > B True, если A > B
        = Больше или равно A >= B True, если A > B или A = B
        Таблица 2.6. Операции отношения

        Типичные примеры операций отношения:

        Выражение Результат
        123 = 132 False
        123 <> 132 False
        17 19 False
        7 >= 7 True

        2.4.4. Булевские операции

        Результатом выполнения логических (булевских) операций является логическое значение True или False (таблица 2.7). Операндами в логическом выражении служат данные типа Boolean.

        Операция Действие Выражение A B Результат
        not Логическое отрицание not A TrueFalse FalseTrue
        and Логическое И A and B TrueTrueFalseFalse TrueFalseTrueFalse TrueFalseFalseFalse
        or Логическое ИЛИ A or B TrueTrue FalseFalse TrueFalseTrueFalse TrueTrueTrueFalse
        xor Исключающее ИЛИ A xor B TrueTrue FalseFalse TrueFalseTrueFalse FalseTrueTrueFalse
        Таблица 2.7. Логические операции

        Результаты выполнения типичных логических операций:

        Выражение Результат
        not (17 > 19) True
        (7 shl ) и вправо ( shr ).

        Операция Действие Тип операндов Тип результата
        not Побитовое отрицание Целый Целый
        and Побитовое И Целый Целый
        or Побитовое ИЛИ Целый Целый
        xor Побитовое исключающее ИЛИ Целый Целый
        shl Сдвиг влево Целый Целый
        shr Сдвиг вправо Целый Целый
        Таблица 2.8. Побитовые операции

        Примеры побитовых операций:

        Выражение Результат
        not $FF00 $00FF
        $FF00 or $0FF0 $FFF0
        $FF00 and $0FF0 $0F00
        $FF00 xor $0FF0 $F0F0
        $FF00 shl 4 $F000
        $FF00 shr 4 $0FF0

        2.4.6. Очередность выполнения операций

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

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

        Операция Приоритет Описание
        –, not Первый Унарный минус, отрицаиие
        *, /, div, mod, and Второй Операции типа умножение
        +, –, or, xor Третий Операции типа сложение
        =, <>, , = Четвертый Операции отношения
        Таблица 2.9. Приоритет операций

        Чем выше приоритет (первый — высший), тем раньше операция будет выполнена.

        2.5. Консольный ввод-вывод


        2.5.1. Консольное приложение

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

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

        Итак, давайте последовательно создадим консольное приложение:

        1. Запустите среду Delphi, выберите в главном меню команду File | Close All , а затем — команду File | New .

        2. Выберите “Console Application” и нажмите “OK” (рисунок 2.1).

        Рисунок 2.1. Окно среды Delphi для создания нового проекта

        3. В появившемся окне между ключевыми словами BEGIN и END введите следующие строчки (рисунок 2.2):

        Рисунок 2.2. Текст простейшей консольной программы в окне редактора кода

        4. Скомпилируйте и выполните эту программу, щелкнув на пункте Run | Run главного меню среды Delphi. На экране появится черное окно (рисунок 2.3), в левом верхнем углу которого будет содержаться текст «Press ENTER to exit. » («Нажмите клавишу Enter . «).

        Рисунок 2.3. Окно работающей консольной программы

        5. Нажмите в этом окне клавишу Enter — консольное приложение завершится.

        Теперь, когда есть основа для проверки изучаемого материала, рассмотрим операторы консольного ввода-вывода. К ним относятся Write, Writeln, Read, Readln.

        2.5.2. Консольный вывод

        Инструкции Write и Writeln служат для вывода чисел, символов, строк и булевских значений на экран. Они имеют следующий формат:

        где Y1, Y2. Yn — константы, переменные и результаты выражений. Инструкция Writeln аналогична Write, но после своего выполнения переводит курсор в начало следующей строки.

        Если инструкции Write и Writeln записаны без параметров:

        то это вызывает пропуск на экране соответственно одной позиции и одной строки.

        2.5.3. Консольный ввод

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

        где X1, X2, . Xn — переменные, для которых осуществляется ввод значений. Пример:

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

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

        Если вводится одно значение:

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

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

        В простейшем случае в инструкциях Read и Readln параметры можно вообще не указывать:

        Оба этих оператора останавливают выполнение программы до нажатия клавиши Enter.

        2.6. Структура программы

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

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

        2.6.1. Заголовок программы

        Заголовок программы должен совпадать с именем программного файла. Он формируется автоматически при сохранении файла на диске и его не следует изменять вручную. Например, заголовок программы в файле Console.dpr выглядит так:

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

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

        2.6.2. Подключение модулей

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

        С момента подключения все ресурсы модуля (типы данных, константы, переменные, процедуры и функции) становятся доступны программисту.

        2.6.3. Программный блок

        Важнейшим понятием в языке Delphi является так называемый блок. По своей сути блок — это программа в целом или логически обособленная часть программы, содержащая описательную и исполнительную части. В первом случае блок называется глобальным , во втором — локальным . Глобальный блок — это основная программа, он присутствует всегда; локальные блоки — это необязательные подпрограммы (они рассмотрены ниже). Локальные блоки могут содержать в себе другие локальные блоки (т.е. одни подпрограммы могут включать в себя другие подпрограммы). Объекты программы (типы, переменные и константы) называют глобальными или локальными в зависимости от того, в каком блоке они объявлены.

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

        Тело программы является исполнительной частью глобального блока. Именно из него вызываются для выполнения описанные выше процедуры и функции. Тело программы начинается зарезервированным словом begin (начало), далее следуют операторы языка, отделенные друг от друга точкой с запятой. Завершает тело программы зарезервированное слово end (конец) с точкой. Тело простейшей консольной программы выглядит так:

        На этом мы заканчиваем рассмотрение структуры программы и переходим к содержимому тела программы — операторам.

        2.7. Операторы


        2.7.1. Общие положения

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

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

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

        2.7.2. Оператор присваивания

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

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

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

        Исключение составляет случай, когда выражение принадлежит 32-разрядному целочисленному типу данных (например, Integer), а переменная — 64-разрядному целочисленному типу данных Int64. Для того, чтобы на 32-разрядных процессорах семейства x86 вычисление выражения происходило правильно, необходимо выполнить явное преобразование одного из операндов выражения к типу данных Int64. Следующий пример поясняет сказанное:

        2.7.3. Оператор вызова процедуры

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

        2.7.4. Составной оператор

        Составной оператор представляет собой группу из произвольного числа операторов, отделенных друг от друга точкой с запятой и заключенную в так называемые операторные скобки — begin и end :

        Частным случаем составного оператора является тело следующей программы:

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

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

        2.7.5. Оператор ветвления if

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

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

        Логика работы оператора if очевидна: выполнить оператор 1, если условие истинно, и оператор 2, если условие ложно. Поясним сказанное на примере:

        В данном случае значение выражения А > В ложно, следовательно на экране появится сообщение C=8.

        У оператора if существует и другая форма, в которой else отсутствует:

        Логика работы этого оператора if еще проще: выполнить оператор, если условие истинно, и пропустить оператор, если оно ложно. Поясним сказанное на примере:

        В результате на экране появится сообщение С=0, поскольку выражение А > В ложно и присваивание С := А + В пропускается.

        Один оператор if может входить в состав другого оператора if . В таком случае говорят о вложенности операторов. При вложенности операторов каждое else соответствует тому then , которое непосредственно ему предшествует. Например:

        Конструкций со степенью вложенности более 2–3 лучше избегать из-за сложности их анализа при отладке программ.

        2.7.6. Оператор ветвления case

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

        Оператор case вычисляет значение переключателя (который может быть задан выражением), затем последовательно просматривает списки его допустимых значений в поисках вычисленного значения и, если это значение найдено, выполняет соответствующий ему оператор. Если переключатель не попадает ни в один из списков, выполняется оператор, стоящий за словом else . Если часть else отсутствует, управление передается следующему за словом end оператору.

        Переключатель должен принадлежать порядковому типу данных. Использовать вещественные и строковые типы в качестве переключателя не допускается.

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

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

        2.7.7. Операторы повтора — циклы

        Алгоритм решения многих задач требует многократного повторения одних и тех же действий. При этом суть действий остается прежней, но меняются данные. С помощью рассмотренных выше операторов трудно представить в компактном виде подобные действия в программе. Для многократного (циклического) выполнения одних и тех же действий предназначены операторы повтора ( циклы ). К ним относятся операторы for , while и repeat . Все они используются для организации циклов разного вида.

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

        2.7.8. Оператор повтора for

        Оператор повтора for используется в том случае, если заранее известно количество повторений цикла. Приведем наиболее распространенную его форму:

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

        Оператор for обеспечивает выполнение тела цикла до тех пор, пока не будут перебраны все значения параметра цикла от начального до конечного. После каждого повтора значение параметра цикла увеличивается на единицу. Например, в результате выполнения следующей программы на экран будут выведены все значения параметра цикла (от 1 до 10), причем каждое значение — в отдельной строке:

        Заметим, что если начальное значение параметра цикла больше конечного значения, цикл не выполнится ни разу.

        В качестве начального и конечного значений параметра цикла могут использоваться выражения. Они вычисляются только один раз перед началом выполнения оператора for . В этом состоит важная особенность цикла for в языке Delphi, которую следует учитывать тем, кто имеет опыт программирования на языках C/C++.

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

        Вторая форма записи оператора for обеспечивает перебор значений параметра цикла не по возрастанию, а по убыванию:

        Например, в результате выполнения следующей программы на экран будут выведены значения параметра цикла в порядке убывания (от 10 до 1):

        Если в такой записи оператора for начальное значение параметра цикла меньше конечного значения, цикл не выполнится ни разу.

        2.7.9. Оператор повтора repeat

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

        Тело цикла выполняется до тех пор, пока условие завершения цикла (выражение булевского типа) не станет истинным. Оператор repeat имеет две характерные особенности, о которых нужно всегда помнить:

        • между словами repeat и until может находиться произвольное число операторов без операторных скобок begin и end ;
        • так как условие завершения цикла проверяется после выполнения операторов, цикл выполняется, по крайней мере, один раз.

        В следующем примере показано, как оператор repeat применяется для суммирования вводимых с клавиатуры чисел. Суммирование прекращается, когда пользователь вводит число 0:

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

        2.7.10. Оператор повтора while

        Оператор повтора while имеет следующий формат:

        Перед каждым выполнением тела цикла происходит проверка условия. Если оно истинно, цикл выполняется и условие вычисляется заново; если оно ложно, происходит выход из цикла, т.е. переход к следующему за циклом оператору. Если первоначально условие ложно, то тело цикла не выполняется ни разу. Следующий пример показывает использование оператора while для вычисления суммы S = 1 + 2 + .. + N, где число N задается пользователем с клавиатуры:

        2.7.11. Прямая передача управления в операторах повтора

        Для управления работой операторов повтора используются специальные процедуры-операторы Continue и Break, которые можно вызывать только в теле цикла.

        Процедура-оператор Continue немедленно передает управление оператору проверки условия, пропуская оставшуюся часть цикла (рисунок 2.4):

        Рисунок 2.4. Схема работы процедуры-оператора Continue

        Процедура-оператор Break прерывает выполнение цикла и передает управление первому оператору, расположенному за блоком цикла (рисунок 2.5):

        Рисунок 2.5. Схема работы процедуры-оператора Break

        2.7.12. Оператор безусловного перехода

        Среди операторов языка Delphi существует один редкий оператор, о котором авторы сперва хотели умолчать, но так и не решились. Это оператор безусловного перехода goto («перейти к»). Он задумывался для того случая, когда после выполнения некоторого оператора надо выполнить не следующий по порядку, а какой-либо другой, отмеченный меткой, оператор.

        Метка — это именованная точка в программе, в которую можно передать управление. Перед употреблением метка должна быть описана. Раздел описания меток начинается зарезервированным словом label , за которым следуют имена меток, разделенные запятыми. За последним именем ставится точка с запятой. Типичный пример описания меток:

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

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

        Внимание! В соответствии с правилами структурного программирования следует избегать применения оператора goto , поскольку он усложняет понимание логики программы. Оператор goto использовался на заре программирования, когда выразительные возможности языков были скудными. В языке Delphi без него можно успешно обойтись, применяя условные операторы, операторы повтора, процедуры Break и Continue, операторы обработки исключений (последние описаны в главе 4).

        2.8. Подпрограммы


        2.8.1. Общие положения

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

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

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

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

        Все процедуры и функции языка Delphi подразделяются на две группы: встроенные и определенные программистом.

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

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

        2.8.2. Стандартные подпрограммы

        Abs(X) Возвращает абсолютное значение аргумента X.
        Exp(X) Возвращает значение e x .
        Ln(X) Возвращает натуральный логарифм аргумента X.
        Pi Возвращает значение числа ?.
        Sqr(X) Возвращает квадрат аргумента X.
        Sqrt(X) Возвращает квадратный корень аргумента X.

        Выражение Результат
        Abs(–4) 4
        Exp(1) 2.17828182845905
        Ln(Exp(1)) 1
        Pi 3.14159265358979
        Sqr(5) 25
        Sqrt(25) 5

        ArcTan(X) Возвращает угол, тангенс которого равен X.
        Cos(X) Возвращает косинус аргумента X (X задается в радианах).
        Sin(X) Возвращает синус аргумента X (X задается в радианах).

        Выражение Результат
        ArcTan(Sqrt(3)) 1.04719755119660
        Cos(Pi/3) 0.5
        Sin(Pi/6) 0.5

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

        Функции выделения целой или дробной части

        Frac(X) Возвращает дробную часть аргумента X.
        Int(X) Возвращает целую часть вещественного числа X. Результат принадлежит вещественному типу.
        Round(X) Округляет вещественное число X до целого.
        Trunc(X) Возвращает целую часть вещественного числа X. Результат принадлежит целому типу.

        Выражение Результат
        Frac(2.5) 0.5
        Int(2.5) 2.0
        Round(2.5) 3
        Trunc(2.5) 2

        Функции генерации случайных чисел

        Random Возвращает случайное вещественное число в диапазоне 0 ? X Подпрограммы для работы с порядковыми величинами

        Chr(X) Возвращает символ, порядковый номер которого равен X.
        Dec(X, [N]) Уменьшает целую переменную X на 1 или на заданное число N.
        Inc(X, [N]) Увеличивает целую переменную X на 1 или на заданное число N.
        Odd(X) Возвращает True, если аргумент X является нечетным числом.
        Ord(X) Возвращает порядковый номер аргумента X в своем диапазоне значений.
        Pred(X) Возвращает значение, предшествующее значению аргумента X в своем диапазоне.
        Succ(X) Возвращает значение, следующее за значением аргумента X в своем диапазоне.

        Выражение Результат
        Chr(65) ‘A’
        Odd(3) True
        Ord(‘A’) 65
        Pred(‘B’) ‘A’
        Succ(‘A’) ‘B’

        Подпрограммы для работы с датой и временем

        Date Возвращает текущую дату в формате TDateTime.
        Time Возвращает текущее время в формате TDateTime.
        Now Возвращает текущие дату и время в формате TDateTime.
        DayOfWeek(D) Возвращает день недели по дате в формате TDateTime.
        DecodeDate(. ) Разбивает значение даты на год, месяц и день.
        DecodeTime(. ) Разбивает значение времени на час, минуты, секунды и милисекунды.
        EncodeDate(. ) Формирует значение даты по году, месяцу и дню.
        EncodeTime(. ) Формирует значение времени по часу, минутам, секундам и милисекундам.

        Процедуры передачи управления

        Break Прерывает выполнение цикла.
        Continue Начинает новое повторение цикла.
        Exit Прерывает выполнение текущего блока.
        Halt Останавливает выполнение программы и возвращает управление операционной системе.
        RunError Останавливает выполнение программы, генерируя ошибку времени выполнения.

        Разные процедуры и функции

        FillChar(. ) Заполняет непрерывную область символьным или байтовым значением.
        Hi(X) Возвращает старший байт аргумента X.
        High(X) Возвращает самое старшее значение в диапазоне аргумента X.
        Lo(X) Возвращает младший байт аргумента X.
        Low(X) Возвращает самое младшее значение в диапазоне аргумента X.
        Move(. ) Копирует заданное количество байт из одной переменной в другую.
        ParamCount Возвращает количество параметров, переданных программе в командной строке.
        ParamStr(X) Возвращает параметр командной строки по его номеру.
        SizeOf(X) Возвращает количество байт, занимаемое аргументом X в памяти. Функция SizeOf особенно нужна для определения размеров переменных обощенных типов данных, поскольку представление обощенных типов данных в памяти может изменяться от одной версии среды Delphi к другой. Рекомендуем всегда использовать эту функцию для определения размера переменных любых типов данных; это считается хорошим стилем программирования.
        Swap(X) Меняет местами значения старшего и младшего байтов аргумента.
        UpCase(C) Возвращает символ C, преобразованный к верхнему регистру.

        Выражение Результат
        Hi($F00F) $F0
        Lo($F00F) $0F
        High(Integer) 32767
        Low(Integer) –32768
        SizeOf(Integer) 2
        Swap($F00F) $0FF0
        UpCase(‘a’) ‘A’

        2.8.3. Процедуры программиста

        Очевидно, что встроенных процедур и функций для решения большинства прикладных задач недостаточно, поэтому приходиться придумывать собственные процедуры и функции. По своей структуре они очень напоминают программу и состоят из заголовка и блока. Заголовок процедуры состоит из зарезервированного слова procedure , имени процедуры и необязательного заключенного в круглые скобки списка формальных параметров. Имя процедуры — это идентификатор, уникальный в пределах программы. Формальные параметры — это данные, которые вы передаете в процедуру для обработки, и данные, которые процедура возвращает (подробно параметры описаны ниже). Если процедура не получает данных извне и ничего не возвращает, формальные параметры (в том числе круглые скобки) не записываются. Тело процедуры представляет собой локальный блок, по структуре аналогичный программе:

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

        Вызов процедуры для выполнения осуществляется по ее имени, за которым в круглых скобках следует список фактических параметров , т.е. передаваемых в процедуру данных:

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

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

        Приведем пример небольшой программы, использующей процедуру Power для вычисления числа X в степени Y. Результат вычисления процедура Power заносит в глобальную переменную Z.

        2.8.4. Функции программиста

        Функции программиста применяются в тех случаях, когда надо создать подпрограмму, участвующую в выражении как операнд. Как и процедура, функция состоит из заголовка и блока. Заголовок функции состоит из зарезервированного слова function , имени функции, необязательного заключенного в круглые скобки списка формальных параметров и типа возвращаемого функцией значения. Функции возвращают значения любых типов данных кроме Text и file of (см. файлы). Тело функции представляет собой локальный блок, по структуре аналогичный программе.

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

        В качестве примера заменим явно неуклюжую процедуру Power (см. выше) на функцию с таким же именем:

        2.8.5. Параметры процедур и функций

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

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

        Входные параметры объявляются с помощью ключевого слова const ; их значения не могут быть изменены внутри подпрограммы:

        Для объявления выходных параметров служит ключевое слово out :

        Установка значений выходных параметров внутри подпрограммы приводит к установке значений переменных, переданных в качестве аргументов:

        После вызова процедуры GetScreenResolution переменные W и H будут содержать значения, которые были присвоены формальным параметрам Width и Height соответственно.

        Если параметр является одновременно и входным, и выходным , то он описывается с ключевым словом var :

        Изменение значений var -параметров внутри подпрограммы приводит к изменению значений переменных, переданных в качестве аргументов:

        При вызове подпрограмм на место out — и var -параметров можно подставлять только переменные, но не константы и не выражения.

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

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

        Разные способы передачи параметров ( const , out , var и без них) можно совмещать в одной подпрограмме. В следующем законченном примере процедура Average принимает четыре параметра. Первые два (X и Y) являются входными и служат для передачи исходных данных. Вторые два параметра являются выходными и служат для приема в вызывающей программе результатов вычисления среднего арифметического (M) и среднего геометрического (P) от значений X и Y:

        Существует разновидность параметров без типа. Они называются нетипизированными и предназначены для передачи и для приема данных любого типа. Нетипизированные параметры описываются с помощью ключевых слов const и var , при этом тип данных опускается:

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

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

        Ключевое слово Назначение Способ передачи
        Входной Передается копия значения
        const Входной Передается копия значения либо ссылка на значение в зависимости от типа данных
        out Выходной Передается ссылка на значение
        var Входной и выходной Передается ссылка на значение
        Таблица 2.10. Способы передачи параметров

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

        2.8.6. Опущенные параметры процедур и функций

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

        Для параметра InitValue задано стандартное значение, поэтому его можно опустить при вызове процедуры Initialize:

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

        2.8.7. Перегрузка процедур и функций

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

        В языке Delphi существует возможность дать двум и более процедурам (функциям) одинаковые идентификаторы при условии, что все такие процедуры (функции) отличаются списком параметров. Такая возможность называется перегрузкой . Для указания того, что процедура (функция) перегружена, служит стандартная директива overload . С ее помощью вышеприведенный пример можно переписать следующим образом:

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

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

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

        Очевидно, что одно и то же число может интерпретироваться и как Longint, и как Shortint (например, числа 5 и –1). Логика компилятора в таких случаях такова: если значение фактического параметра попадает в диапазон значений нескольких типов, по которым происходит перегрузка, то компилятор выбирает процеудуру (функцию), у которой тип параметра имеет меньший диапазон значений. Например, вызов Print(5) будет означать вызов того варианта процедуры, который имеет тип параметра Shortint. А вот вызов Print(150) будет означать вызов того варианта процедуры, который имеет тип параметра Longint, т.к. число 150 не вмещается в диапазон значений типа данных Shortint.

        Поскольку в нынешней версии среды Delphi обощенный тип данных Integer совпадает с фундаментальным типом данных Longint, следующий вариант перегрузки является ошибочным:

        Такая же ошибка возникает при использовании пользовательских типов данных, определенных через общий базовый тип.

        Что делать в тех случаях, когда такая перегрузка просто необходима? Для этого пользовательский тип данных необходимо создавать с использованием ключевого слова type :

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

        Вызов процедуры Increment с одним параметром вызовет неоднозначность:

        Запрещается также перегружать функции, которые отличаются лишь типом возвращаемого значения.

        2.8.8. Соглашения о вызове подпрограмм

        В различных языках программирования используются различные правила вызова подпрограмм. Для того чтобы из программ, написанных на языке Delphi, возможно было вызывать подпрограммы, написанные на других языках (и наоборот), в языке Delphi существуют директивы, соответствующие четырем известным соглашениям о вызове подпрограмм: register , stdcall , pascal , cdecl .

        Директива, определяющая правила вызова, помещается в заголовок подпрограммы, например:

        Директива register задействует регистры процессора для передачи параметров и поэтому обеспечивает наиболее эффективный способ вызова подпрограмм. Эта директива применяется по умолчанию. Директива stdcall используется для вызова стандартных подпрограмм операционной системы. Директивы pascal и cdecl используются для вызова подпрограмм, написанных на языках Delphi и C/C++ соответственно.

        2.8.9. Рекурсивные подпрограммы

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

        Приведенная ниже программа содержит функцию Factorial для вычисления факториала. Напомним, что факториал числа определяется через произведение всех натуральных чисел, меньших либо равных данному (факториал числа 0 принимается равным 1):

        X! = 1 * 2 * . * (X – 2) * (X – 1) * X

        Из определения следует, что факториал числа X равен факториалу числа (X – 1), умноженному на X. Математическая запись этого утверждения выглядит так:

        X! = (X – 1)! * X, где 0! = 1

        Последняя формула используется в функции Factorial для вычисления факториала:

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

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

        2.8.10. Упреждающее объявление процедур и функций

        Для реализации алгоритмов с косвенной рекурсией в языке Delphi предусмотрена специальная директива предварительного описания подпрограмм forward . Предварительное описание состоит из заголовка подпрограммы и следующего за ним зарезервированного слова forward , например:

        Заметим, что после такого первичного описания в полном описании процедуры или функции можно не указывать список формальных параметров и тип возвращаемого значения (для функции). Например:

        2.8.11. Процедурные типы данных

        Наряду с уже известными типами данных в языке Delphi введен так называемый процедурный тип, с помощью которого обычные процедуры и функции можно интерпретировать как некоторую разновидность переменных. Определение процедурного типа состоит из зарезервированного слова procedure или function , за которым следует полное описание параметров. Для функции дополнительно указывается тип результата. Символические имена параметров никакой роли не играют, поскольку нигде не используются.

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

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

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

        2.9. Программные модули


        2.9.1. Структура модуля

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

        После слова unit записывается имя модуля . Оно должно совпадать с именем файла, в котором находится исходный текст модуля. Например, если файл называется MathLib.pas, то модуль должен иметь имя MathLib. Заголовок модуля формируется автоматически при сохранении файла на диске, поэтому его не следует изменять вручную. Чтобы дать модулю другой заголовок, просто сохраните его на диске под другим именем.

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

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

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

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

        Если модуль не нуждается в инициализации и завершении, блоки initialization и finalization можно опустить.

        В качестве упражнения давайте создадим модуль и подключим его к основной программе (для этого сначала запустите среду Delphi):

        1. Выберите в главном меню команду File | New. , в появившемся диалоговом окне активизируйте значок с подписью Unit и щелкните на кнопке OK (рисунок 2.6).

        Рисунок 2.6. Окно среды Delphi для создания нового модуля

        2. Вы увидите, что среда Delphi создаст в редакторе кода новую страницу с текстом нового модуля Unit1 (рисунок 2.7):

        Рисунок 2.7. Текст нового модуля в редакторе кода

        3. Сохраните модуль под именем MathLib, выбрав в меню команду File | Save (рисунок 2.8):

        Рисунок 2.8. Окно сохранения модуля

        4. Заметьте, что основная программа Console изменилась: в списке подключаемых модулей появилось имя модуля MathLib (рисунок 2.9). После слова in среда Delphi автоматически помещает имя файла, в котором находится модуль. Для стандартных модулей, таких как SysUtils, это не нужно, поскольку их местонахождение хорошо известно.

        Рисунок 2.9. Текст программы Console в окне редактора

        Теперь перейдем к содержимому модуля. Давайте объявим в нем константу Pi и две функции: Power — вычисление степени числа, и Average — вычисление среднего арифметического двух чисел:

        Вот как могла бы выглядеть программа, использующая модуль Math:

        После компиляции и запуска программы вы увидите на экране три числа (рисунок 2.10):

        Рисунок 2.10. Результат работы программы Console

        2.9.2. Стандартные модули языка Delphi

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

        К системным модулям относятся System, SysUtils, ShareMem, Math. В них содержатся наиболее часто используемые в программах типы данных, константы, переменные, процедуры и функции. Модуль System — это сердце среды Delphi; содержащиеся в нем подпрограммы обеспечивают работу всех остальных модулей системы. Модуль System подсоединяется автоматически к каждой программе и его не надо указывать в операторе uses .

        Модули визуальных компонентов (VCL — Visual Component Library) используются для визуальной разработки полнофункциональных GUI-приложений — приложений с графическим пользовательским интерфейсом (Graphical User Interface). Эти модули в совокупности представляют собой высокоуровневую объектно-ориентированную библиотеку со всевозможными элементами пользовательского интерфейса: кнопками, надписями, меню, панелями и т.д. Кроме того, модули этой библиотеки содержат простые и эффективные средства доступа к базам данных. Данные модули подключаются автоматически при помещении компонентов на форму, поэтому вам об этом заботиться не надо. Их список слишком велик, поэтому мы его не приводим.

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

        Исходные тексты стандартных модулей среды Delphi находятся в каталоге Delphi/Source.

        2.9.3. Область действия идентификаторов

        При программировании необходимо соблюдать ряд правил, регламентирующих использование идентификаторов:

        • каждый идентификатор должен быть описан перед тем, как он будет использован;
        • областью действия идентификатора является блок, в котором он описан;
        • все идентификаторы в блоке должны быть уникальными, т.е. не повторяться;
        • один и тот же идентификатор может быть по-разному определен в каждом отдельном блоке, при этом блоки могут быть вложенными;
        • если один и тот же идентификатор определен в нескольких вложенных блоках, то в пределах вложенного блока действует вложенное описание;
        • все глобальные описания подключенного модуля видны программе (подключающему модулю), как если бы они были сделаны в точке подключения;
        • если подключаются несколько модулей, в которых по-разному определен один и тот же идентификатор, то определение, сделанное в последнем подключенном модуле перекрывает все остальные;
        • если один и тот же идентификатор определен и в подключенном модуле, и в программе (подключающем модуле), то первый игнорируется, а используется идентификатор, определенный в программе (подключающем модуле). Доступ к идентификатору подключенного модуля возможен с помощью уточненного имени. Уточненное имя формируется из имени модуля и записанного через точку идентификатора. Например, чтобы в предыдущем примере получить доступ к стандартному значению числа ?, нужно записать System.Pi.

        2.10. Строки


        2.10.1. Строковые значения

        Строка — это последовательность символов. При программировании строковые значения заключаются в апострофы, например:

        Так как апостроф является служебным символом, для его записи в строке как значащего символа применяются два апострофа, следующих непосредственно друг за другом:

        Для записи отсутствующих на клавиатуре символов используется символ # , за которым следует десятичный номер символа в кодовой таблице ASCII, например:

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

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

        2.10.2. Строковые переменные

        Строковая переменная объявляется с помощью зарезервированного слова string или с помощью идентификатора типа данных AnsiString, например:

        Строку можно считать бесконечной, хотя на самом деле ее длина ограничена 2 ГБ. В зависимости от присваиваемого значения строка увеличивается и сокращается динамически. Это удобство обеспечивается тем, что физически строковая переменная хранит не сами символы, а адрес символов строки в области динамически распределяемой памяти (о динамически распределяемой памяти мы расскажем ниже). При создании строки всегда инициализируются пустым значением (»). Управление динамической памятью при операциях со строками выполняется автоматически с помощью стандартных библиотек языка Delphi.

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

        Символы строки индексируются от 1 до N+1, где N — реальная длина строки. Символ с индексом N+1 всегда равен нулю (#0). Для получения длины следует использовать функцию Length , а для изменения длины — процедуру SetLength (см. ниже).

        Для того чтобы в программе обратиться к отдельному символу строки, нужно сразу за идентификатором строковой переменной или константы в квадратных скобках записать его номер. Например, FriendName[1] возвращает значение ‘A’, а FriendName[4] — ‘x’. Символы, получаемые в результате индексирования строки, принадлежат типу Char.

        Достоинство строки языка Delphi состоит в том, что она объединяет в себе свойства строки самого языка Delphi и строки языка C. Оперируя строкой, вы оперируете значением строки, а не адресом в оперативной памяти. В то же время строка не ограничена по длине и может передаваться вместо C-строки (как адрес первого символа строки) в параметрах процедур и функций. Чтобы компилятор позволил это сделать, нужно, записывая строку в качестве параметра, преобразовать ее к типу PChar (тип данных, используемый в языке Delphi для описания нуль-терминированных строк языка C). Такое приведение типа допустимо по той причине, что строка всегда завершается нулевым символом (#0), который хоть и не является ее частью, тем не менее всегда дописывается сразу за последним символом строки. В результате формат строки удовлетворяет формату C-строки. О работе с нуль-терминированными строками мы поговорим чуть позже.

        2.10.3. Строки в формате Unicode

        Для поддержки работы со строками формата Unicode в язык Delphi имеется строковый тип данных WideString. Работа со строками типа WideString почти не отличается от работы со строками типа AnsiString; существуют лишь два отличия.

        Первое отличие состоит в представлении символов. В строках типа WideString каждый символ кодируется не одним байтом, а двумя. Соответственно элементы строки WideString — это символы типа WideChar, тогда как элементы строки AnsiString — это символы типа AnsiChar.

        Второе отличие состоит в том, что происходит при присваивании строковых переменных. Об этом вы узнаете чуть позже, прочитав параграф «Представление строк в памяти».

        2.10.4. Короткие строки

        Короткая строка объявляется с помощью идентификатора типа ShortString или зарезервированного слова string , за которым следует заключенное в квадратные скобки значение максимально допустимой длины, например:

        Короткая строка может иметь длину от 1 до 255 символов. Предопределенный тип данных ShortString эквивалентен объявлению string [255].

        Реальная длина строки может быть меньше или равна той, что указана при ее объявлении. Например, максимальная длина строки Friend в примере выше составляет 30 символов, а ее реальная длина — 9 символов. Реальную длину строки можно узнать с помощью встроенной функции Length . Например, значение Length(Friend) будет равно 9 (количество букв в слове Alexander).

        Все символы в строке типа ShortString пронумерованы от 0 до N, где N — максимальная длина, указанная при объявлении. Символ с номером 0 — это служебный байт, в нем содержится реальная длина короткой строки. Значащие символы нумеруются от 1. Очевидно, что в памяти строка занимает на 1 байт больше, чем ее максимальная длина. Поэтому значение SizeOf(Friend) будет равно 31.

        Обратиться к отдельному символу можно так же, как и к символу обычной строки. Например, выражения FriendName[1] и FriendName[9] возвращают соответственно символы ‘A’ и ‘r’. Значения FriendName[10] .. FriendName[30] будут случайными, так как при объявлении типизированной константы FriendName символы с номерами от 10 до 30 не были инициализированы. Символы, получаемые в результате индексирования короткой строки, принадлежат типу Char.

        Поскольку существует два типа строк: обычные (длинные) строки и короткие строки, возникает закономерный вопрос, можно ли их совмещать. Да, можно! Короткие и длинные строки могут одновременно использоваться в одном выражении, поскольку компилятор языка Delphi автоматически генерирует код, преобразующий их тип. Более того, можно выполнять явные преобразования строк с помощью конструкций вида ShortString(S) и AnsiString(S).

        2.10.5. Операции над строками

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

        Операция сцепления (+) применяется для сцепления нескольких строк в одну строку.

        Выражение Результат
        ‘Object’ + ‘ Pascal’ ‘Object Pascal’

        Операции отношения (=, <>, >, =, Выражение

        Результат
        ‘USA’ ‘ABCDE’ True
        ‘Office’ = ‘Office’ True
        ‘USIS’ > ‘US’ True

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

        Объявление строки Выражение Значение строки
        Name: string[6]; Name := ‘Mark Twain’; ‘Mark T’

        Допускается смешение в одном выражении операндов строкового и символьного типа, например при сцеплении строки и символа.

        2.10.6. Строковые ресурсы

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

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

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

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

        2.10.7. Форматы кодирования символов

        Существуют различные форматы кодирования символов. Отдельный символ строки может быть представлен в памяти одним байтом (стандарт Ansi), двумя байтам (стандарт Unicode) и даже четырьмя байтами (стандарт UCS-4 — Unicode). Строка “Wirth” (фамилия автора языка Pascal — прародителя языка Delphi) будет представлена в указанных форматах следующим образом (рисунок 2.11):

        Рисунок 2.11. Форматы кодирования символов

        Существует также формат кодирования MBCS (Multibyte Character Set), согласно которому символы одной строки кодируются разным количеством байт (одним или двумя байтами в зависимости от алфавита). Например, буквы латинского алфавита кодируются одним байтом, а иероглифы японского алфавита — двумя. При этом латинские буквы и японские иероглифы могут встречаться в одной и той же строке.

        2.10.8. Стандартные процедуры и функции для работы со строками

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

        • Concat (S1, S2, . , Sn): string — возвращает строку, полученную в результате сцепления строк S1, S2, . Sn. По своей работе функция Concat аналогична операции сцепления (+).
        • Copy (S: string, Index, Count: Integer): string — выделяет из строки S подстроку длиной Count символов, начиная с позиции Index.
        • Delete (var S: string, Index, Count: Integer) — удаляет Count символов из строки S, начиная с позиции Index.
        • Insert (Source: string; var S: string, Index: Integer) — вставляет строку Source в строку S, начиная с позиции Index.
        • Length (S: string): Integer — возвращает реальную длину строки S в символах.
        • SetLength (var S: string; NewLength: Integer) — устанавливает для строки S новую длину NewLength.

        ВыражениеЗначение S
        S := Concat(‘Object ‘, ‘Pascal’);‘Object Pascal’
        S:= Copy(‘Debugger’, 3, 3);‘bug’
        S := ‘Compile’; Delete(S, 1, 3);‘pile’
        S := ‘Faction’; Insert(‘r’, S, 2)‘Fraction’

        • Pos (Substr, S: string): Byte — обнаруживает первое появление подстроки Substr в строке S. Возвращает номер той позиции, где находится первый символ подстроки Substr. Если в S подстроки Substr не найдено, результат равен 0.

        ВыражениеРезультат
        Pos(‘rat’, ‘grated’)2
        Pos(‘sh’, ‘champagne’)

        • Str (X [: W >
        ВыражениеЗначение S
        Str(–200, S);‘–200’
        Str(200 : 4, S);‘ 200’
        Str(1.5E+02 : 4, S);‘ 150’
        • Val (S: string, var V; var Code: Integer) — преобразует строку S в величину целого или вещественного типа и помещает результат в переменную V. Если во время операции преобразования ошибки не обнаружено, значение переменной Code равно нулю; если ошибка обнаружена (строка содержит недопустимые символы), Code содержит номер позиции первого ошибочного символа, а значение V не определено.

        ВыражениеЗначение VЗначение Code
        Val(‘100’, V, Code);100
        Val(‘2.5E+01’, V, Code);25.0
        Val(‘2.5A+01’, V, Code);4

        Описанные процедуры и функции являются базовыми для всех остальных подпрограмм обработки строк из модуля SysUtils.

        • AdjustLineBreaks (const S: string): string — возвращает копию строки S, в которой все мягкие переносы строк (одиночные символы #13 или #10) заменены жесткими переносами строк (последовательность символов #13#10).
        • AnsiCompareStr (const S1, S2: string): Integer — сравнивает две строки, делая различие между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
        • AnsiCompareText (const S1, S2: string): Integer — сравнивает две строки, не делая различий между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
        • AnsiDequotedStr (const S: string; Quote: Char): string — удаляет специальный символ, заданный параметром Quote, из начала и конца строки и заменяет парные спецсимволы на одиночные; если специальный символ отсутствует в начале или конце строки, то функция возвращает исходную строку без изменений.
        • AnsiExtractQuotedStr (var Src: PChar; Quote: Char): string — делает то же, что и функция AnsiDequotedStr, но результат возвращается вместо исходной строки, которая имеет тип PChar.
        • AnsiLowerCase (const S: string): string — преобразует заглавные буквы строки S к строчным буквам с учетом местного языка.
        • AnsiPos (const Substr, S: string): Integer — выполняет те же действия, что и функция Pos, но в отличие от нее поддерживает работу с многобайтовой MBCS-кодировкой.
        • AnsiQuotedStr (const S: string; Quote: Char): string — преобразует строку, заменяя все вхождения специального символа, заданного параметром Quote, на парные спецсимволы, а также помещает специальный символ в начало и конец строки. Поддерживает работу с MBCS-кодировкой.
        • AnsiSameCaption (const Text1, Text2: string): Boolean — сравнивает две строки, не делая различие между заглавными и строчными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
        • AnsiSameStr (const S1, S2: string): Boolean — сравнивает строки, делая различие между строчными и заглавными буквами; учитывает местный язык.
        • AnsiSameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
        • AnsiUpperCase (const S: string): string — преобразует все строчные буквы в заглавные; учитывает местный язык.
        • CompareStr (const S1, S2: string): Integer — выполняет сравнение двух строк, делая различие между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
        • CompareText (const S1, S2: string): Integer — выполняет сравнение двух строк, не делая различий между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
        • DateTimeToStr (const DateTime: TDateTime): string — преобразует значение даты и времени в строку.
        • DateTimeToString (var Result: string; const Format: string; DateTime: TDateTime) — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
        • DateToStr (const DateTime: TDateTime): string — преобразует числовое значение даты в строку.
        • Format (const Format: string; const Args: array of const): string — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы шаблона на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по среде Delphi.
        • FormatDateTime (const Format: string; DateTime: TDateTime): string — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
        • BoolToStr (B: Boolean; UseBoolStrs: Boolean = False): string — преобразует булевское значение в строку. Если параметр UseBoolStrs имеет значение False, то результатом работы функции является одно из значений ‘0’ или ‘–1’. Если же параметр UseBoolStrs имеет значение True, то результатом работы является одно из значений ‘FALSE’ или ‘TRUE’ (программист может задать другие значения; о том, как это сделать, читайте в справочнике по системе Delphi).
        • IntToHex (Value: Integer; Digits: Integer): string — возвращает шестнадцатиричное представление целого числа Value. Параметр Digits задает количество цифр результирующей строки.
        • IntToStr (Value: Integer): string — преобразует целое число Value в строку.
        • IsDelimiter (const Delimiters, S: string; Index: Integer): Boolean — проверяет, является ли символ S[Index] одним из символов строки Delimiters. Функция поддерживает работу с многобайтовой MBCS-кодировкой.
        • IsValidIdent (const Ident: string): Boolean — возвращает True, если строка Ident является правильным идентификатором языка Delphi.
        • LastDelimiter (const Delimiters, S: string): Integer — возвращает индекс последнего вхождения одного из символов строки Delimiters в строку S.
        • LowerCase (const S: string): string — преобразует все заглавные буквы строки S к строчным; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘A’ до ‘Z’).
        • QuotedStr (const S: string): string — преобразует исходную строку в строку, взятую в одиночные кавычки; внутри строки символы кавычки дублируются.
        • SameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
        • SetString (var S: string; Buffer: PChar; Len: Integer) — копирует строку с типом PChar в строку с типом string. Длина копируемой строки задается параметром Len.
        • StringOfChar (Ch: Char; Count: Integer): string — возвращает строку, в которой повторяется один и тот же символ. Количество повторений задается параметром Count.
        • StringToGUID (const S: string): TGUID — преобразует строковое представление глобального уникального идентификатора в стандартный тип TGUID.
        • StrToBool (const S: string): Boolean — преобразует строку в булевское значение.
        • StrToBoolDef (const S: string; const Default: Boolean): Boolean — преобразует строку в булевское значение. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
        • StrToDate (const S: string): TDateTime — преобразует строку со значением даты в числовой формат даты и времени.
        • StrToDateDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку со значением даты в числовой формат даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
        • StrToDateTime (const S: string): TDateTime — преобразует строку в числовое значение даты и времени.
        • StrToDateTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовое значение даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
        • StrToInt (const S: string): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
        • StrToIntDef (const S: string; Default: Integer): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция возвращает значение, заданное параметром Default.
        • StrToInt64 (const S: string): Int64 — 64-битный аналог функции StrToInt — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
        • StrToInt64Def (const S: string; const Default: Int64): Int64 — 64-битный аналог функции StrToIntDef — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция возвращает значение, заданное параметром Default.
        • StrToTime (const S: string): TDateTime — преобразует строку в числовой формат времени. Если строка не может быть преобразована в числовой формат времени, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
        • StrToTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовой формат времени. В случае ошибки преобразования, функция возвращает значение, заданное параметром Default.
        • TimeToStr (Time: TDateTime): string — преобразует числовое значение времени в строку.
        • Trim (const S: string): string — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
        • Trim (const S: WideString): WideString — Unicode-аналог функции Trim — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
        • TrimLeft (const S: string): string — возвращает часть строки S без лидирующих пробелов и управляющих символов.
        • TrimLeft (const S: WideString): WideString — Unicode-аналог функции TrimLeft — возвращает часть строки S без лидирующих пробелов и управляющих символов.
        • TrimRight (const S: string): string — возвращает часть строки S без завершающих пробелов и управляющих символов.
        • TrimRight (const S: WideString): WideString — Unicode-аналог функции TrimRight — возвращает часть строки S без завершающих пробелов и управляющих символов.
        • UpperCase (const S: string): string — преобразует все строчные буквы строки S в заглавные; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘a’ до ‘z’).
        • WideFormat (const Format: WideString; const Args: array of const): WideString — Unicode-аналог функции Format, учитывающий символы местного языка, — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы в шаблоне на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по системе Delphi.
        • WideFmtStr (var Result: WideString; const Format: WideString; const Args: array of const) — аналог функции WideFormat. Отличие в том, что WideFmtStr возвращает результат через параметр Result, а не как значение функции.
        • WideLowerCase (const S: WideString): WideString — Unicode-аналог функции LowerCase (учитывает местный язык) — преобразует все заглавные буквы строки S к строчным буквам.
        • WideSameCaption (const Text1, Text2: WideString): Boolean — Unicode-аналог функции AnsiSameCaption — сравнивает две строки, не делая различие между строчными и заглавными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
        • WideSameStr (const S1, S2: WideString): Boolean — Unicode-аналог стандартной операции сравнения строк — сравнивает две строки, делая различие между строчными и заглавными буквами.
        • WideSameText (const S1, S2: WideString): Boolean — Unicode-аналог функции SameText (учитывает местный язык) — сравнивает строки, не делая различие между строчными и заглавными буквами.
        • WideUpperCase (const S: WideString): WideString — Unicode-аналог функции UpperCase (учитывает местный язык) — преобразует все строчные буквы строки S в заглавные.
        • WrapText (const Line: string; MaxCol: Integer = 45): string — разбивает текст Line на строки, вставляя символы переноса строки. Максимальная длина отдельной строки задается параметром MaxCol.
        • WrapText (const Line, BreakStr: string; const BreakChars: TSysCharSet; MaxCol: Integer): string — более мощный аналог предыдущей функции — разбивает текст Line на строки, вставляя символы переноса строки.
        • AnsiToUtf8 (const S: string): UTF8String — перекодирует строку в формат UTF8.
        • PUCS4Chars (const S: UCS4String): PUCS4Char — возвращает указатель на первый символ строки формата UCS-4 для работы со строкой, как с последовательностью символов, заканчивающейся символом с кодом нуль.
        • StringToWideChar (const Source: string; Dest: PWideChar; DestSize: Integer): PWideChar — преобразует стандартную строку к последовательности Unicode-символов, завершающейся символом с кодом нуль.
        • UCS4StringToWideString (const S: UCS4String): WideString — преобразует строку формата UCS-4 к строке формата Unicode.
        • Utf8Decode (const S: UTF8String): WideString — преобразует строку формата UTF-8 к строке формата Unicode.
        • Utf8Encode (const WS: WideString): UTF8String — преобразует строку формата Unicode к строке формата UTF-8.
        • Utf8ToAnsi (const S: UTF8String): string — преобразует строку формата UTF-8 к стандратной строке.
        • WideCharLenToString (Source: PWideChar; SourceLen: Integer): string — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen.
        • WideCharLenToStrVar (Source: PWideChar; SourceLen: Integer; var Dest: string) — аналог предыдущей функции — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen, а результат возвращается через параметр Dest.
        • WideCharToString (Source: PWideChar): string — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке.
        • WideCharToStrVar (Source: PWideChar; var Dest: string) — аналог предыдущей функции — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке. Результат возвращается через параметр Dest.
        • WideStringToUCS4String (const S: WideString): UCS4String — преобразует строку формата Unicode к строке формата UCS-4.

        2.11. Массивы


        2.11.1. Объявление массива

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

        После описания типа можно переходить к определению переменных и типизированных констант:

        Используйте заголовок/текст управления вместо ключевого слова или оператора в Delphi

        Является ли это возможным?

        Пример: ComboBox1 имеет два элемента (или, и). ComboBox2 имеет два элемента ( ).

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

        Любой из инструментов сценариев Pascal, qaru.site/questions/404744/. может работать для этого.

        Но для вашего очень простого примера вам это не нужно, вы можете просто проверить, что ComboBoxx.Text или лучше ComboBox.ItemIndex вернется, а затем напишите код Delphi во время разработки.

        Первое, что вам нужно понять, — это то, как выражения Delphi обрабатываются компилятором. Они указывают на то, что выражения анализируются во время компиляции. Под парсинг мы подразумеваем действие разложения выражения на его составные части: операции и связанные операнды.

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

        Например, рассмотрим это выражение:

        Здесь операция является дополнением. Оба операнда представляют собой переменную x и константу 1 . Все известно во время компиляции, кроме значения x .

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

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

        Вместо этого вам нужно выполнить разбор выражений. Вы можете написать собственный анализатор/оценщик. Вы можете использовать анализатор выражения Delphi LiveBindings. Вы можете использовать сторонний оценщик выражений, например библиотеку JCL.

        Илон Маск рекомендует:  Что такое код pdf_set_border_dash
        Понравилась статья? Поделиться с друзьями:
        Кодинг, CSS и SQL