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


Содержание

Зарезервированные слова Delphi

var A, B : Integer; begin A:=3; B:=4; A:=A*A+B*B; end;

if (условие) then (действие) else (альтернатива) ;

Слова if (если), then (тогда), else (иначе) — зарезервированные. Действие и else альтернатива — это любые операторы Delphi, или несколько операторов, заключённых в логические скобки begin/end, или вызов подпрограммы. Если условие истинно, то выполняется действие, если ложно, то выполняется альтернатива.

(применяется, когда известно количество повторений цикла)

for счётчик := выражение-1 to выражение-2 do действие ;

Возможна работа оператора цикла, при котором переменная-счётчик будет не увеличиваться, а уменьшаться. В этом случае ключевое слово to заменяется на downto:

for счётчик := выражение-1 downto выражение-2 do действие ;

Цикл с предусловием (применяется, когда неизвестно количество повторений цикла)

while условие do тело цикла ;

Этот цикл будет выполняться до тех пор, пока истинно условие (логическое выражение, возвращающее значение типа Boolean). При этом если это выражение сразу равно false, тело цикла не будет выполнено ни разу. Нужно очень внимательно следить за написанием условия и контролем завершения цикла, так как в результате ошибки цикл while будет повторяться бесконечное количество раз, что приведёт к «зацикливанию» и «зависанию» программы.

Цикл с постусловием

(применяется, когда неизвестно количество повторений цикла)

repeat тело цикла until условие ;

Повторения сначала выполняет тело цикла, а затем уже проверяет выполнение условия: Таким образом, этот вариант цикла гарантирует, что тело цикла будет выполнен по крайней мере один раз. И будет выполняться до тех пор, пока условие не станет истинным (т.е. true). Стоит отметить, что это единственный оператор Delphi, в котором тело цикла не требуется заключать в логические скобки begin/end. Начало и конец тела цикла определяются по ключевым словам repeat и until.

Позволяет изменить последовательность выполнения программы. В качестве метки может использоваться любой допустимый идентификатор или число в диапазоне от 0 до 9999. Метку предварительно необходимо объявить в разделе описания переменных, но с помощью не ключевого слова var, а ключевого слова label:

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

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

Алфавит и словарь языка

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

Прописные и строчные латинские буквы, а так же знак подчеркивания:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z _

0 1 2 3 4 5 6 7 8 9

Пробельные символы (разделители):

Пробел, табуляция, перевод строки, возврат каретки

Составные символы, образуемые сочетанием двух специальных:

ПРИМЕЧАНИЕ
Все прочие символы, включая символы кириллицы, также могут использоваться в Object Pascal, но только внутри строковых переменных и комментариев.

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

Что касается комментариев, то никакого смысла в программе они не несут, и могут использоваться для того, чтобы разработчик мог вставить пояснительный текст в код программы. Комментарии бывают двух видов — однострочные и многострочные. Для однострочных комментариев обычно используют составной символ «//», помещая его перед текстом самого комментария:

// эта строка полностью закомментирована x=5; // эта часть строки — комментарий

Для многострочных комментариев применяют символы < и >, либо (* и *):

Комментарии разных типов можно вкладывать друг в друга:

С помощью комментариев удобно исключать отдельные инструкции или целые блоки программы в процессе ее отладки.

Ключевые слова

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

Таблица 3.1. Зарезервированные и ключевые слова в Delphi

Слово Слово Слово Слово
absolute export nil requires
abstract exports nodefault resident
and external not resourcestring
array far object safecall
as file of set
asm finalization on shl
assembler finally or shr
at for out stdcall
automated forward overload stored
begin function override string
case goto package then
cdecl if packed threadvar
class implementation pascal to
const implements private try
constructor in procedure type
contains index program unit
default inherited property until
destructor initialization protected uses
dispid inline public var
dispinterface interface published virtual
div is raise while
do label read with
downto library readonly write
dynamic message record writeonly
else mod register xor
end name reintroduce
except near repeat

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

Переменные и константы

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

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

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

ПРИМЕЧАНИЕ
Более точно выражение можно определить следующим образом: выражение — это набор данных, переменных, операторов и других выражений, которые приводятся к общему значению.

Другой вариант — это использование знака равенства для сравнения 2 операндов. В таком случае он используется самостоятельно:

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

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

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

Определяются константы при помощи ключевого слова const (от англ. constant):

Теперь во всех выражениях, где требуется значение НДС, просто используется эта константа:

VATsumm := price * VAT;

В этом выражении задействована константа VAT и 2 переменных — VATsumm, которой присваивается значение, и price, которая используется для его вычисления. Впрочем, price в данном случае тоже может быть константой, в отличие от VATsumm. Дело в том, что константы определяются в момент написания программы, а при компиляции в код автоматически подставляются их действительные значения. Соответственно, для вычисляемых значений, равно как и для значений, которые может вводить пользователь, нужны не константы, а переменные. Так, если бы константа VAT была переменной, то можно было бы предусмотреть в программе опцию изменения значения НДС.

Переменные определяются при помощи ключевого слова var (от англ. variable):

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

ПРИМЕЧАНИЕ
Начиная с Delphi 4, в Object Pascal поддерживаются типизированные константы, значения которых можно изменять по ходу выполнения программы. Объявление констант такого типа производится следующим образом: «const : тип = значение». От обычных переменных они отличаются различиями в обработке компилятором, а так же тем, что для них всегда имеется какое-либо предопределенное значение.

Типы данных

Прежде, чем приступить к рассмотрению типов данных, постараемся понять, для чего это вообще надо. Прежде всего, Object Pascal, равно как и его предшественник — язык Pascal, а так же C и C++ относятся к строго типизированным языкам программирования. Типизированные языки, в отличие от нетипизированных (начиная с BASIC и заканчивая различными языками сценариев вроде JavaScript), имеют то преимущество, что уже на этапе синтаксической проверки кода компилятор не допустит заведомо неверных операций. К примеру, вы не сможете сложить строку с числом. Кроме того, типизация положительно сказывается на быстродействии программ: благодаря тому, что компилятор заранее «знает», что за данные ему следует обработать в каждом конкретном случае, он может подбирать оптимальные инструкции при генерации машинного кода.

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

  • Целочисленные (Integer);
  • Вещественные (Real);
  • Булевы (Boolean);
  • Символьные (Character);
  • Строковые (String).

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

Всего в современных версиях Delphi предусмотрено 7 различных типов данных для целых чисел, все они приведены в таблице 3.2.

Таблица 3.2. Типы целочисленных данных

Тип Диапазон Байт (бит) памяти Отрицательные значения
Byte от 0 до 255 1 (8) Нет
ShortInt от -128 до 127 1 (8) Да
Word от 0 до 65535 2 (16) Нет
SmallInt от -32768 до 32767 2 (16) Да
LongWord от 0 до 4294967295 4 (32) Нет
LongInt от -2147483648 до 21474483647 4 (32) Да
Int64 от -9223372036854775808 до 9223372036854775807 8 (64) Да

ПРИМЕЧАНИЕ
Здесь следует сразу оговориться про понимание памяти в программировании. Так, память считают в байтах. Каждый байт состоит из 8 бит. Бит — это минимальная единица информации, бит может принимать только 2 значения, 0 или 1. Каждая переменная, в зависимости от типа, занимает то или иное количество байт в памяти. Отметим так же, что 2 байта образуют слово (word), а 4 байта — двойное слово.

Помимо перечисленных основных типов, в Delphi имеется еще 2 автоматических целочисленных типа — Integer и Cardinal. Первое, в типичном случае, является синонимом для LingInt, хотя может быть приведено и к типу Int64. Например, если объявит переменную типа Integer и попробовать записать в нее значение, превышающее максимально допустимый размер для типа LongInt, то она автоматически преобразуется в Int64:

var x: integer; . x: = 21474483647; // здесь x имеет тип LongInt x: = x + 1;

Что касается Cardinal, то это — устаревшее определение для LongWord, вы сможете встретить его, если будете просматривать исходные коды, написанные во времена первых версий Delphi. Самым распространенным на практике целочисленным типом данных является Integer.

Перейдем к вещественным типам. Для них так же предусмотрены фиксированные типы (правда, в отличие от целочисленных, их не 7, а 6), и один автоматический. Рассмотрим основные типы в таблице 3.3.

Таблица 3.3. Типы вещественных данных

Тип Диапазон Байт памяти Точность
Real48 от ±2.9*10^-39 до 1.7*10^38 6 11.дек
Single от ±1.5*10^-45 до 3.4*10^38 4 07.авг
Double от ±5.0*10^-324 до 1.7*10^308 8 15-16
Extended от ±3.4*10^-4951 до 1.1*10^4932 10 19-20
Comp от -2^63+1 до 2^63 -1 8 19-20
Currency от -922337203685477.5808 до 922337203685477.5807 8 19-20

Имеется так же и автоматический тип — Real, введенный для совместимости с программами, написанными в Delphi 2 или 3. Сейчас тот тип, что был в ранних версиях Delphi, называется Real48 и практически не используется. Вместо него рекомендуется использовать такие типы, как Single или Double. Если же задать тип Real в программе, то он будет автоматически приведен к типу Double.

Нечто подобное можно сказать и про тип Comp — этот 64-разрядный вещественный тип данных изжил себя с момента появления целочисленного типа Int64, и присутствует лишь в целях совместимости со старым программным кодом.

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

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

var x: integer; y: double; . x := 5; y := 5.25; // обратите внимание, что дробная часть отделяется точкой y := x + y; // так делать можно x := x + y; // а так — нельзя, поскольку результатом должно быть целое

Булевы, или логические типы данных представлены в Delphi типами Boolean, ByteBool, WordBool и LongBool. Все они отличаются только размером памяти, выделяемым для хранения значения, причем значений может быть только 2 — false (ложь) и true (истина):

var x, y: Boolean; . x := true; y := false;

Основным типом является 1-байтовый Boolean (он же ByteBool), 2-байтовый WordBool и 4-байтовый LongBool предусмотрены лишь для совместимости в целях взаимодействия с другими языками и платформами.

Что касается символьных типов данных, то в Delphi предусмотрено 2 их типа — ANSIChar и WideChar. Первый является однобайтовым и может хранить в себе 1 символ из множества символов ANSI, коих насчитывается 256. Второй же тип является 2-байтовым и предназначен для хранения 2-байтовых же символов Unicode. Как и в других случаях, Delphi имеет синоним для символьных типов — Char, который на сегодня является аналогом ANSIChar. Что касается присвоения значений, то обычные символы (буквы и цифры) присваивают переменным символьного типа как есть, лишь заключая их в одиночные кавычки. А специальные символы, например, возврат каретки (Enter) назначают при помощи их номера в таблице ANSI, и выделяют знаком решетки:

var x, y: Char; // x и y получают тип ANSIChar . x := a; // обычные символы y := #13; // возврат каретки в таблице ANSI имеет номер 13

Наконец, еще одним, причем, в общем-то, уже не совсем простым типом данных являются строки. Строковые типы данных отличаются от символьных тем, что могут хранить не единичный символ, а множество символов. В Delphi имеется 3 типа строк: ShortString, AnsiString и WideString. Первый тип строк — ShortString — достался в наследство от языка Pascal и 16-битной Delphi 1.0. Такие строки могут иметь не более 255 символов, и занимают от 2 до 256 байт памяти, в зависимости от размера: Что касается современных строковых типов — AnsiString и WideString, то они могут иметь практически неограниченную длину (AnsiString — до 2 млрд. символов, WideString — до 1 млрд.) и занимать, соответственно, от 4 байт до 2 Гигабайт памяти. При этом по аналогии с символьными типами, тип AnsiString предназначен для хранения обычных строк, а WideString — для строк в формате Unicode. Ну и еще один тип строк — String является синонимом для типа AnsiString:

var str1: ShortString; // короткая строка var str2: AnsiString; // длинная строка var str3: String; // тоже длинная строка . str1 := Начало.; // Строковые значения заключаются в одинарные кавычки str2 := Конец.; str3 := str1 + str2; // получим длинную строку, содержащую Начало.Конец.

В целом, несмотря на кажущееся разнообразие типов данных, на практике чаще всего ограничиваются всего лишь 5-6 основными типами. Это: Integer, Double, Boolean, Char, String, и иногда — еще и Currency.

Данные и значения

Очевидно, что сами по себе типы данных ничего не означают. Главное их предназначение — хранить те или иные значения, или данные. Так, для хранения числовых данных применяют целочисленные или вещественные типы, в зависимости от того, какого типа числа следует использовать. На практике это означает, что будут применяться типы Integer и Double.

ПРИМЕЧАНИЕ
Хотя вещественные типы и являются более универсальными, использовать их надо только при реальной необходимости, поскольку они гораздо менее удобны компьютеру для вычислений. Говоря конкретнее, математические операции над вещественными числами выполняются гораздо медленнее, чем над целыми, а ряд иных операций (например, побитовые, инкремент или декремент) вообще недопустимы.

Строковые данные требуют для своего хранения строковых же типов, т.е. String. Исключение может составить тот случай, когда следует сохранить один и только один символ — в таком случае предпочтительно (а иногда — необходимо) использовать тип Char. Ну а в том случае, если требуется привести какое-то значение к однозначному выводу, используют логический тип Boolean. Например, если сравнивать одно число с другим на предмет того, является ли оно больше, то результатом сравнения будет либо «да», либо «нет», т.е. true или false:

var x: boolean; . x := 5 > 6; // получаем false, т.к. 5 не больше 6

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

Операции и их типы

В любом языке программирования имеются знаки операций. Кроме того, некоторые ключевые слова, например такие, как div или mod также обозначают операции. Все операции в Object Pascal можно разделить на следующие типы: логические, арифметические, логические, операции присвоения и отношения, а так же специальные операции. Для их обозначения используются математические символы или ключевые слова. Участвующие в операциях значения (переменные) называются операндами. При этом та или иная операция может работать с операндами определенного типа. А результатом может быть данные как такого же типа, та и другого (например, для того же сравнения).

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

Таблица 3.4. Арифметические операции

Операция Название, тип Описание Операнды Результат
+ Сложение, бинарная Возвращает сумму левого и правого операндов integer, real integer, real
Вычитание, бинарная Возвращает разницу левого и правого операндов integer, real integer, real
* Умножение, бинарная Возвращает произведение левого операнда на правый операнд integer, real integer, real
/ Деление, бинарная Возвращает результат деления левого операнда на правый операнд. Результат может быть дробным integer, real real
mod Остаток от деления, бинарная Возвращает остаток от деления левого операнда на правый операнд integer integer
div Деление нацело, бинарная Возвращает целую часть числа, получившуюся в результате деления integer integer
Унарный минус Возвращает число, противоположное операнду integer, real integer, real
+ Унарный плюс Явно указывает знак числа integer, real integer, real

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

Другой распространенный тип операций — логические. В Object Pascal имеются все 4 типа логических операций: не, и, или, исключающее или (таблица 3.5).

Таблица 3.5. Логические операции

Операция Название Описание
not Логическое отрицание (НЕ) Возвращает false, если выражение может быть приведено к истине, в противном случае возвращает true
and Логическое (И) Возвращает true, когда оба выражения истинны. В противном случае возвращает false
or Логическое (ИЛИ) Возвращает true, когда хотя бы одно из выражений истинно. В противном случае возвращает false
xor Логическое (исключающее ИЛИ) Возвращает true, когда только одно из выражений истинно. В противном случае возвращает false

Варианты возвращаемых значений для логических операций приводятся ниже:

not true // возвращает false not false // возвращает true true and true // возвращает true true and false // возвращает false false and false // возвращает false true or true // возвращает true true or false // возвращает true false or false // возвращает false true xor true // возвращает false true xor false // возвращает true false xor false // возвращает false

Логическому сравнению подлежат не только булевские значения, но и любые другие выражения, которые могут быть к ним приведены. Например, выражение «3=4» может быть использовано в качестве логически сравниваемой единицы, поскольку результатом его оценки будет булево значение ложь (false).

Те же самые знаки операций, что используются в логических операциях, задействованы и в другом типе операций — побитовых. Побитовые операции выполняются над числами, представленными в двоичном виде (т.е. только нули и единицы). Однако сами операнды могут быть десятичными, шестнадцатеричными, или восьмеричными целыми числами. Например, десятичное число 5 представляется как двоичное 101, десятичное 6 — как 110, а шестнадцатеричное F3 — как двоичное 11110011.

Хотя побитовые операции выполняются над двоичными данными, возвращаемые значения являются стандартными числами. Список всех побитовых операций приводится в таблице 3.6.

Таблица 3.6. Побитовые операции

Операция Название Описание
and Побитовое И Возвращает число, являющееся результатом побитового сравнения «И»
or Побитовое ИЛИ Возвращает число, являющееся результатом побитового сравнения «включающее ИЛИ»
xor Побитовое исключающее ИЛИ Возвращает число, являющееся результатом побитового сравнения «исключающее ИЛИ»
not Побитовое НЕ Возвращает число, с битами, расположенными в обратном порядке
shl Сдвиг влево Сдвигает первый операнд влево на число разрядов, заданных вторым операндом. Освобождающиеся правые биты заполняются нулями
shr Сдвиг вправо Сдвигает первый операнд вправо на число разрядов, заданных вторым операндом. Освобождающиеся левые биты отбрасываются

Чтобы явно представить себе, как работают побитовые операции, обратимся к следующему примеру. Допустим, имеется 2 переменных — x и y:

var x, y: integer; . x := 3; y := 5;

В двоичном представлении число 3 будет выглядеть как 0011, а 5 — как 0101. Теперь посмотрим, какие результаты даст каждая из побитовых операций сравнения и операции отрицания над этими числами:

x or y // Получим 7: 0011 | 0101 = 0111 x and y // Получим 1: 0011 & 0101 = 0001 x xor y // Получим 6: 0011 ^ 0101 = 0110 not x // Получим 12:

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

y shl 2 // Получим 20: 0101 shl 2 = 10100 y shr 2 // Получим 1: 0101 shr 2 = 01 -y shr 2 // Получим -2: -0101 shr 2 = -10

Теперь нам остается исследовать еще один тип операций — операции сравнения. Эти операции всегда требуют наличия двух операндов: они сравнивают значения левого и правого операндов, и возвращают результат сравнения в виде логического значения, которое может принимать значение false или true (ложь или истина). Все имеющиеся в Object Pascal операции сравнения приведены в таблице 3.7.

Таблица 3.7. Операции сравнения

Операция Название Описание Пример, дающий true
= Равно Возвращает истину (true), когда левый и правый операнды равны. 1=1
<> Не равно Возвращает истину, когда левый и правый операнды не равны. 1<>2
> Больше Возвращает истину, когда левый операнд больше правого. 2>1
= Больше или равно Возвращает истину, когда левый операнд больше правого или равен ему. 1>=0; 1>>1
var x: Char; z: Boolean; . x := ‘b’; z := x in [a..d];

В данном случае в качестве результата (z) мы получим истину, поскольку символ b является членом указанного множества [a..d], в которое входят символы a, b, c и d. Наконец, в Object Pascal имеется еще 2 операции — as и is. Они служат для приведения типа и проверки типа, соответственно. Например, если мы хотим проверить, является ли некая переменная «x» целым, то можно написать такое выражение:

b := x is Integer; // b получит значение true, если x — целое

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

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

Выражения и приоритет операций

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

a := b + c; d := e * f; g := a — d div 2;

Так же, как и в обычной математике, при составлении выражений в Object Pascal, следует учитывать приоритет выполнения операций. Например, операции умножения или деления должны выполняться раньше, чем сложение и вычитание. Так, в 3-й строке из приведенных выше примеров выражений, согласно математическим правилам, сначала выполняется операция деления нацело (d div 2), затем результат этой операции вычитается из a, и итоговое значение присваивается переменной g. Все те же правила действуют и в программировании, но поскольку перечень операций не ограничивается арифметическими, а в рамках одного выражения могут быть использованы различные типы операций, то было бы неплохо внести полную ясность в этот вопрос.

Прежде всего, приоритет выполнения определяется принадлежностью конкретной операции к тому или иному типу. Так, первыми выполняются унарные операции, затем — операции умножения (включая деление и побитовые), далее обрабатываются сложение и вычитание (опять, таки включая побитовые or и xor), и, наконец, в последнюю очередь — операции отношения. Для удобства представим все операции, классифицированные по уровню приоритета, в виде таблицы (табл. 3.8).

Таблица 3.8. Приоритет выполнения операций

Операторы Уровень приоритета Категория
@, not Высший Унарные
*, /, div, mod, and, shl, shr, as Высокий Умножение
+, -, or, xor Средний Сложение
=, <>, >, =, in, is Низкий Отношение

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

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

Таким образом, сначала здесь из a вычитается d, и лишь затем производится операция деления нацело.

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

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

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

program ; uses ; label ; const ; type ; var ; ; begin ; end.

В структуре той или иной программы часть этих разделов может быть опущена за ненадобностью. Обязательными являются всего 3 ключевых слова — program (вместе с названием), а так же begin и end. Разумеется, между begin и end должны находиться какие-либо инструкции. Так, возвращаясь к примеру «Hello, World», в нем можно обнаружить лишь название программы и блок инструкций:

program hello; // название <$APPTYPE CONSOLE>//это указание компилятору к коду программы не относится begin // начало исполняемого кода write(Hello, World!); // инструкции readln; end. // конец программы

ПРИМЕЧАНИЕ
Список модулей в данном случае не нужен, поскольку мы не используем никаких дополнительных функций или процедур из библиотеки Object Pascal. Но, разумеется, в более сложных программах они могут понадобиться.

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

Рассмотренная нами структура характерна для программ на Pascal в целом, в том числе и для Windows-приложений, разрабатываемых визуальными средствами. Структура же отдельных модулей, представляющих собой описание того или иного окна, имеет ряд отличий. В общих чертах ее можно представить следующим образом: начинается такой модуль с ключевого слова unit, после которого следуют секции interface и implementation. Иногда могут быть также использованы еще 2 секции — initialization и finalization. Ну а завершается все это, разумеется, ключевым словом end с точкой:

unit ; interface implementation ; initialization ; finalization ; end.

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

Завершая эту тему, затронем еще один аспект структуры программ — подпрограммы. В качестве подпрограммы понимается блок инструкций, предназначенный для решения какой-либо частной задачи в рамках самой программы. Такие блоки в Object Pascal могут просто выполняться — в таком случае они называются процедурами, либо выполняться и возвращать какой-либо результат — в таком случае они называются функциями. И в том, и в другом случае, они могут принимать какие-либо значения для обработки. Эти значения называются аргументами. В целом же синтаксис обращения к процедуре или функции выглядит следующим образом:

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

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

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

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

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

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

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

Обратите внимание , что этот ответ показывает только новые языковые особенности

Вот ссылки на RAD Studio docwiki:

Полный список от Embarcadero: Что нового
Смотри также: список Дэвида I в

  • Поддержка OSX 64-битный
  • Обнуляемые типы
  • Поддержка Android 5.1.1 и прошивка 8.4
  • Улучшенная обработка исключений OSX
  • Поддержка 64-битная прошивка;
  • Новые целочисленные типы: FixedInt , FixedUInt 32-битных целочисленных типов на всех платформах;
  • Зависимые новая платформа целых типы: LONGINT , LongWord (64 бит на 64-прошивке, 32 бита на все другие платформах);

function IsManagedType(T: TypeIdentifier): Boolean; function HasWeakRef(T: TypeIdentifier): Boolean; function GetTypeKind(T: TypeIdentifier): TTypeKind; function IsConstValue(Value): boolean;

Следующее новые условные введены / включены в xe4:
AUTOREFCOUNT
CPUARM
EXTERNAL_LINKER
IOS
NEXTGEN
UNDERSCOREIMPORTNAME
WEAKREF
WEAKINSTREF
WEAKINTREF

В xe4, это изменилось, так что $ ENDIF стала общепринятым терминатор за $ IF, $ IFDEF, $ IFNDEF и $ IFOPT.

  • Поддержка кросс платформа для Mac OSX (32-бит) и прошивкой;
  • Поддержка Win64;
  • Модифицированный RTL для поддержки кросс-платформенный;
  • Packed Теперь силы Byte Выравнивание записей (Pre XE2 это не обязательно делать это)

Восемь новых DEFINES были добавлены:

ALIGN_STACK
CPUX86
CPUX64
MACOS (операционная система Mac)
MACOS32
PC_MAPPED_EXCEPTIONS
PIC
Win64

  • <$STRINGCHECKS>Директива компилятора игнорируется в ХЕ;
  • Новое значение 16 байт для <$ALIGN>директивы: Допустимые значения для <$ALIGN>директивы теперь включают в себя 1, 2, 4, 8 и 16.
  • Новая <$CODEALIGN>директива, это устанавливает начальный адрес процедуры или функции.
  • <$STRONGLINKTYPES ON>директива
  • Поддержка регулярных выражений.
  • Enhanced Delphi RTTI (Run Time Type Information).
  • Атрибуты
  • as Оператор может быть использован , чтобы бросить ссылку на интерфейс обратно на объект , из которого он был извлечен.
  • is Оператор может быть использован для проверки , была ли ссылка на интерфейс , извлеченные из определенного класса.
  • Нормальное небезопасным литье может выполняться на интерфейсе: TObject(SomeInterface) .
  • Новая delayed директива указывает , что внешняя библиотека , такие как DLL не будет загружен во время декларации , но не ждать до первого вызова метода
  • Класс Конструктор / деструктор
  • Характеристическая типа в string настоящее время сопоставляется UnicodeString ;
  • <$HighCharUnicode on|off>директива компилятора
  • Дженерики ;
  • function Default(T): T внутренняя функция (Незарегистрированный)
  • Интеллектуальные указатели ;
  • Анонимные методы ;
  • Поддержка вложенных исключений и отслеживание исключений ;
  • поддержка POINTERMATH и новая директива компилятора: <$PointerMath on|off>;
  • Четыре новых предупреждений компилятора:
    • W1057 Implicit string cast from ‘%s’ to ‘%s’ ,
    • W1058 Implicit string cast with potential data loss from ‘%s’ to ‘%s’ ,
    • W1059 Explicit string cast from ‘%s’ to ‘%s’ ,
    • W1060 Explicit string cast with potential data loss from ‘%s’ to ‘%s’ ;
  • Exit Функция может принимать параметр , указывающий на результат;
  • resourcestrings а Widestrings;
  • TObject имеет дополнительный скрытый указатель TMonitor в дополнение к указателю ВМТ;
  • deprecated Теперь ключевое слово может иметь дополнительный текст
  • Нет языка изменения , которые я знаю;
    Обратите внимание , что Delphi 2007 представляет собой неразрывный релиз, DCU от D2006 будет работать без изменений в D2007 ;
  • (В .NET «личность» в 2007 году введены дженериков )
  • for . in петли,
  • inline ключевое слово
  • Джокер в изез заявлении разрешено
  • вложенные типы
  • вложенные константы
  • <$REGION>/ <$ENDREGION>директивы
  • [TCustomVariantType][68] обеспечивает перегрузку операторов для пользовательских типов вариантов
  • Новые директивы компилятора:
    • Поддержка <$IF><$ELSE>директив компилятора
    • Компилятор намекая директивы: experimental , deprecated , library , platform (но без дополнительного текста для устаревшей)
    • не вариант больше не основан на COM, но изменяется на CLX совместим, COM на основе варианта переименован OLEVariant
    • Введенные константы не могут быть назначены (Override с <$J+>)
    • Перечисляемые типы могут быть назначены явное значение (ср C ++);
    • свойства интерфейса
    • Поддержка для вызова varargs внешних функций (но только для cdecl вызывающей конвенции)
    • пользовательские варианты

    Нет новых возможностей языка, но:

    • Динамические массивы
    • LongWord и Int64 ; Cardinal это UInt32 (прежде , чем это было беззнаковое 31-битное значение)
    • Real занимает 8 байт и так же, как double (ранее было 6 байт);
      • Перекрыть с новой <$REALCOMPATIBILITY ON>директивой компилятора;
      • REAL48 заменяет старый 6-байт real ;
    • Поддержка для resourcestrings
    • перегрузка методов
    • параметры по умолчанию
    • <$EXTERNALSYM>и <$NODEFINE>директивы
    • implements Ключевое слово для свойств
    • WordBool, LongBool и bytebool магазин , false как -1 вместо 1 (Boolean не изменяется)
    • Компоненты должны быть установлены с помощью packages .
    • Утверждениях.
    • out параметры.
    • Widestring
    • interface и dispinterface ключевое слово и COM ( dispid поддержка).
    • Поддержка 32-битной;
    • Ansistring заменяет в shortstring качестве строкового типа по умолчанию
    • Currency
    • Variant (Для Interop с автоматизацией OLE).
    • Многопоточность поддержки и ThreadVar ключевое слово.
    • 4 байт данные 4 байта выровнены новому packed переопределяет ключевое слово , такое поведение;
    • TDateTime начинается 1899/12/30 под D1 это началось в 0000/00/00
    • новое finalization ключевое слово
    • register и stdcall соглашение о вызовах добавлено.
    • packed ключевое слово.

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

    > GrayFace © (07.01.05 09:52)

    Упакованный массив и упакованная запись.

    Ой. Сорри. В справку не заглянул. Бывает же такое!
    Но все-таки уточню: packed array может быть нужен только при хранении структур с размером, не кратным 4 байтам?

    > [2] GrayFace © (07.01.05 09:57)

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

    а если record не packed — как её в файл писать читать?

    > [4] uny © (07.01.05 10:32)
    > а если record не packed — как её в файл писать читать?

    Точно так же
    Только надо быть уверенным, что за время между write/read правила выравнивания не изменились (смена компилятора/платформы/настройки компилятора)

    >Только надо быть уверенным
    не радует как то.. пусть тогда packed будет

    только маленькое но — где-0то видел, что не советуют использовать packed — это может снизить производительность, ибо 32 всётаки сейчас разрядность процессора и если пытаться использовать что-то в районе 16-24 то это нехзорошо скажется. вообщем некоторые проци вообще оптимизированы на работу с 32 разрядами и оочень тормозят с другими типами.

    uny © (07.01.05 11:07) [7]
    Этого недостаточно, надо еще пользоваться фундаментальными типами, поскольку производные (общии) имеют точно такую же тенденцию менять размер.

    Poirot © (07.01.05 13:24) [8]
    Это хорошо, пока не потребуется мобильность.

    Касательно packed array: у меня всегда было такое чувство, что массивы в Delphi являются packed всегда. Вот такой кусочек, прогнанный на D3 и D6, это подтвердил: выдал две пятерки. Хотя как в Delphi.Net — не знаю.

    <$R *.dfm>
    var
    a:array[1..5] of byte;
    b:packed array[1..5] of byte;
    initialization
    ShowMessage(IntToStr(sizeof(a))+» «+IntToStr(sizeof(b)));
    end.

    При включенной опции проекта Aligned record fields размеры записей, у которых отсутствует packed, будут выравниваться на 32-битную границу (кратно 4). Если packed присутствует, тогда размеры останутся неизменными.

    A: packed record
    ID: integer;
    Count: word;
    end;

    Размер равен 6 байтам независимо от установленной опции. Если убрать packed, то размер будет равен 8 байт.

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

    Тоже самое относится к массивам.

    dimaxx © (07.01.05 16:18) [11]

    AFAIK, это опция не вкл/выкл, а 1-2-4-8.

    >Тоже самое относится к массивам.
    Большая просьба показать это на примере.

    PVOzerski © (07.01.05 15:53) [10]
    Нехороший пример, поскольку байт. Попробуй array [..] of 5 byte, но вероятнее всего будет тоже.

    Я уже попробовал — то же самое. Правда у меня был array [] of record, а в record были word и byte.

    palva © (07.01.05 17:10) [15]
    А рекорд был упакованный или нет?

    implementation
    <$align on>
    type
    t5=packed record
    b0,b1,b2,b3,b4:byte;
    end;
    a=array[1..5]of t5;
    b=packed array[1..5]of t5;
    initialization
    ShowMessage(IntToStr(Sizeof(a))+» «+IntToStr(Sizeof(b)));

    2 GuAV: Что ты хочешь этим сказать? Выравнивание как раз зависит от этой опции проекта ($A+ или $ALIGN ON в тексте).

    The $A directive controls alignment of fields in Delphi record types and class structures.
    In the <$A1>or <$A->state, fields are never aligned. All record and class structures are packed.
    In the <$A2>state, fields in record types that are declared without the packed modifier and fields in class structures are aligned on word boundaries.
    In the <$A4>state, fields in record types that are declared without the packed modifier and fields in class structures are aligned on double-word boundaries.

    In the <$A8>or <$A+>state, fields in record types that are declared without the packed modifier and fields in class structures are aligned on quad word boundaries.

    Anatoly Podgoretsky © (07.01.05 13:36) [9]
    Этого недостаточно, надо еще пользоваться фундаментальными типами, поскольку производные (общии) имеют точно такую же тенденцию менять размер.

    А какие типы являются производными?

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

    GrayFace © (14.01.05 18:38) [23]
    Индетично по строковым и символным типа. Char/String это generic, с приставками в основном фундаментальные.

    Блог GunSmoker-а (переводы)

    . when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

    вторник, 1 сентября 2009 г.

    Работа с указателями

    Pointers are like jumps, leading wildly from one part of the data structure to another. Their introduction into high-level languages has been a step backwards from which we may never recover. — Charles Hoare

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

    Но указатели очень важны. Даже в языках, которые явно не поддерживают указатели, или в которых использование указателей затруднено, указатели являются важными факторами «под капотом» языка. Я считаю, что для программиста понимание указателей является весьма важной вещью. Существует несколько подходов к пониманию указателей.

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

    Содержание

    Память (Memory)

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

    Указатели — это переменные, которые указывают на другие переменные. Чтобы объяснить это, необходимо понять концепцию адреса памяти и концепцию переменной. Для этого я сначала грубо опишу компьютерную память (*).

    Кратко говоря, компьютерная память может рассматриваться как один очень-очень длинный ряд байтов. Байт — это единица измерения количества информации, в стандартном виде байт считается равным восьми битам и может хранить одно из 256 различных значений (от 0 до 255 ). В текущей 32 -х битной версии Delphi на память можно смотреть (за редкими исключениями) как на массив байт максимальным размером в 2 Гб ( 2^31 байт). Что именно содержат эти байты — зависит от того, как интерпретировать их содержимое, т.е. от того, как их используют. Значение 97 может означать число 97 , или же букву ‘a’ . Если вы рассматриваете вместе несколько байт, то вы можете хранить и большие значения. Например, в 2 -х байтах вы можете хранить одно из 256*256 = 65536 различных значений и т.д.

    Чтобы обратиться к конкретному байту в памяти (адресовать его), можно присвоить каждому байту номер, пронумеровав их числами от 0 и до 2147483647 (в предположении, что у вас есть 2 Гб — а даже если у вас их нет, то Windows попытается сделать так, чтобы вам казалось, что они у вас есть). Индекс байта в этом огромном массиве и называется его адресом.

    Кто-то может сказать: байт — это наименьший кусок памяти, который можно адресовать.

    В действительности, память устроена сложнее. Например, существуют компьютеры, байт в которых не равен 8-ми битам, что означает, что они могут содержать больше или меньше 256 значений. Впрочем, для тех машин, на которых работает Delphi для Win32, байт всегда равен 8-ми битам. Память управляется и железом и программами, так что не вся видимая вам память может существовать (менеджеры памяти скрывают это от вас, выгружая память частями на жёсткий диск), но для целей этой статьи мы можем смотреть на память как на один большой блок памяти, разделённый для использования несколькими программами (прим.пер.: для более подробного ознакомления с архитектурой памяти в Windows — рекомендую эту короткую серию статей).

    Переменные (Variables)

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

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

    Тип переменной определяет, как будет использоваться место в памяти. Тип определяет размер, т.е. сколько байт занимает место в памяти, а также структуру памяти. Например, на следующей диаграмме показан кусок памяти в 4 байта, начинающихся по адресу $00012344 . Байты содержат значения $4D , $65 , $6D и $00 , соответственно.

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

    Тип определяет, как используются эти байты. Например, это может быть число типа Integer со значением 7169357 (что есть $006D654D ), или же массив символов array[0..3] of AnsiChar , формирующий C-строку (т.е. PChar ) ‘Mem’ , или что-то совершенно иное, как множество, массив из отдельных байт, небольшая запись, Single , часть Double и т.д. Другими словами, смысл куска памяти переменной не известен, если только вы не знаете, какого типа (или типов) эта переменная (или переменные).

    Адрес переменной — это адрес первого байта места хранения. Например, в диаграмме выше, в предположении, что у нас переменная типа Integer , её адрес будет $00012344 .

    Неинициализированные переменные

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

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

    Первое отображаемое значение (значение неинициализированной переменной A ) зависит от уже существующего содержания памяти, зарезервированной под A . В моём случае каждый раз значение было 2147319808 ( $7FFD8000 ), но это число может быть совершенно другим на вашей машине. Значение не определено, потому что переменная не была инициализирована. В более сложных программах, особенно (но не только) с участием указателей, это частая причина для вылета программы или вывода неверных результатов. Присваивание инициализирует переменную A значением 12345 ( $00003039 ), что и будет вторым выведенным значением.

    Указатели (Pointers)

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

    Например, пусть у нас есть следующие объявления и инициализация:

    Предположим, что это дало нам такую компоновку памяти:

    Теперь, после выполнения этого кода (предполагая что P — это указатель):

    Эта диаграмма более не показывает настоящих размеров (C выглядит так же, как и I или J), но этого достаточно, чтобы продемонстрировать, что происходит с указателями.

    Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end. — Henry Spencer

    nil — это специальное значение указателя. Оно может быть присвоено любому указателю. nil означает пустой указатель (nil — это сокращение от Лат. nihil, что означает ничего или ноль; кое-кто расшифровывает NIL как аббревиатуру от Not In Listне в списке). Это означает, что указатель был определён (инициализирован, присвоен), но вы не должны пытаться получить значение, на которое он указывает (в языке C, nil называется NULL — см. цитату в начале секции).

    Nil никогда не указывает на допустимую память, но т.к. это вполне конкретное значение, то подпрограммы могут сравнивать указатели с этим значением (например, используя функцию Assigned() ). Нельзя проверить, является ли любое другое (не- nil ) значение указателя допустимым. Мусорный или неинициализированный указатель ничем не отличается от допустимого указателя (см. ниже). Не существует способа их отличать друг от друга. Программная логика всегда должна гарантировать, что указатель либо допустим, либо равен nil (**).

    В Delphi, nil имеет значение 0 (прим. пер.: т.е. nil = Pointer(0) или 0 = Integer(nil)) — т.е. он указывает на самый первый байт в памяти. Очевидно, что этот байт никогда не будет доступен для Delphi кода. Но обычно вы не должны рассчитывать на то, что указатель nil будет равен 0 , если только вы точно не знаете, что вы делаете. Числовое значение nil может быть изменено в следующих версиях Delphi по какой-то причине (***).

    Типизированные указатели

    В простом примере выше P имеет тип Pointer . Это означает, что P содержит адрес, но вы не знаете, переменная какого типа лежит по этому адресу. Вот почему обычно используются типизированные указатели, т.е. указатель интерпретируется как указывающий на переменную (область памяти) определённого типа.

    Предположим, что у нас есть ещё один указатель, Q :

    Q типа ^Integer , что читается как «указатель на Integer» (мне сказали, что ^Integer расшифровывается как ↑Integer ). Это означает, что Q — это не Integer , но вместо этого указывает на память, которая может быть использована как Integer . Если вы присвоите адрес J в Q , используя оператор взятия адреса @ или эквивалентную функциональность псевдофункции Addr ,

    то тогда Q будет указывать на место по адресу $00012348 ( Q ссылается (references) на место памяти, занимаемое J ). Но поскольку Q является типизированным указателем, то компилятор будет трактовать память, на которую указывает Q , как число типа Integer . Integer является базовым типом Q .

    Хотя вы навряд ли увидите псевдофункцию Addr в реальном коде, она полностью эквивалентна @ . Однако у @ есть недостаток: если его применять к сложному выражению, то не всегда ясно, указатель чего берётся. Addr же, используя синтаксис функции, получается намного более читабельным, поскольку целевое выражение заключается в скобки:

    Прим. пер.: поэтому неплохо использовать скобки вместе с @, хотя это и не обязательно:

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

    Это записывает число 98765 ( $000181CD ) в место памяти, занимаемое переменной J . Но чтобы получить доступ к этой памяти через указатель Q , вы должны работать косвенно, используя оператор ^ :

    Это называется разыменованием указателя. Вы должны следовать по воображаемой «стрелочке» до места, на которое указывает Q (другими словами, до Integer по адресу $00012348 ) и сохранить там значение.

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

    Обычно полезно определять типы для используемых в программе указателей. Например, вы не можете использовать ^Integer при указании типа параметра подпрограммы, так что вам придётся объявить новый тип:

    Фактически, тип PInteger и некоторые другие часто используемые типы уже определены в библиотеке Delphi (модули System , SysUtils и Types ). Начинать имя типов указателей с заглавной буквы P и следующим за ней именем типа, на переменную которого указывает указатель, является традицией, рекомендованной к выполнению. Если базовый тип указателя начинается с заглавной T, то T обычно опускается. Например:

    Анонимные переменные

    (прим. пер.: не путать с захваченными переменными в анонимных методах)

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

    New() — это псевдофункция компилятора. Она резервирует память для базового типа PI и записывает адрес на эту память в указатель PI . У самой переменной здесь нет имени (т.е. она анонимная) — имя есть только у указателя на переменную. Получить доступ к такой переменной можно только используя указатель. Теперь вы можете присваивать ей значения, передавать её в подпрограммы, избавиться от неё, когда она станет вам не нужна, используя вызов Dispose ( PI ):

    Вместо использования New и Dispose вы можете спуститься на уровень пониже и использовать GetMem и FreeMem . Но подпрограммы New и Dispose имеют несколько преимуществ: они осведомлены о типе указателя (прим. пер.: поэтому автоматически определяют размер памяти), а также инициализируют и освобождают содержимое участка памяти, если это необходимо. Так что рекомендуется всегда использовать New и Dispose , вместо GetMem и FreeMem , там, где это возможно.

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

    Сейчас может быть не очевидно, чем же это лучше, чем объявлять переменную явно, но бывают ситуации, когда это полезно, обычно, если вы не знаете, как много переменных вам понадобится. Подумайте об узлах в связанном списке (см. ниже) или о TList -е. TList хранит указатели, и если вы хотите иметь список значений Double , то вы просто вызываете New() для каждого значения и храните его в TList :

    Конечно же, вам нужно будет потом вызывать Dispose() для каждого значения, когда список не будет больше нужен.

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

    PI заполняет память значением $006D654D ( 7169357 ). На диаграмме (напомню, что все адреса были придуманы мной):

    PC указывает на ту же самую память (поскольку базовые типы указателей несовместимы, вы не можете просто так присвоить один указатель другому — вам нужно использовать преобразование типов). Но PC является указателем на AnsiChar , так что если вы берёте PC^ , то вы получаете AnsiChar — один символ с ASCII значением $4D или ‘M’ .

    Вообще-то, PC — это особый случай, поскольку тип PAnsiChar , хотя он формально указывает на символ AnsiChar , трактуется специальным образом, немного иначе, чем остальные типы указателей. Я объясняю это в другой статье. PC , если он не разыменовывается, обычно трактуется как указатель на текст, заканчивающийся нулевым символом #0 , поэтому Writeln(PC) покажет текст, сформированный байтами $4D $65 $6D $00 , т.е. ‘Mem’ .

    Когда мне нужно подумать об указателях, и особенно о сложных ситуациях с ними, я обычно беру бумажку и ручку и рисую диаграммки типа тех, что вы видели выше. Я также даю переменным выдуманные адреса, если это нужно (не обязательно использовать все 32 бита, адреса типа 30000 , 40000 , 40004 , 40008 и 50000 тоже вполне подойдут).

    Плохие указатели

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

    Неинициализированные указатели

    Указатели являются переменными, и как любые другие переменные они должны быть инициализированы перед использованием, либо присваиванием им другого указателя, либо используя подпрограммы типа New или GetMem , например:

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

    Если вы попробуете получить доступ к памяти по такому указателю, будут происходить плохие вещи. Если эта память не будет зарезервирована вашим приложением, то вы получите исключение Access Violation — вылет программы (program crash). Но если эта память будет частью вашей программы, то вы можете переписать данные, которые не должны меняться. Если эти данные используются в другой части вашей программы, то чуть позже, получаемые результаты выполнения вашей программы будут ошибочны. Такие ошибки чрезвычайно тяжело искать.

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

    Мусорные указатели

    Мусорные указатели (stale pointers) — это указатели, которые когда-то были допустимыми, но теперь уже нет. Это может произойти, если память, на которую указывает указатель, была освобождена и/или повторно использована.

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

    Другая частая ошибка: иметь более чем один указатель на один и тот же кусок памяти. Вы можете освободить память по одному указателю и даже об- nil -ить его, но другой указатель всё также будет содержать старое значение, указывающее на уже освобождённую память. Если вам повезёт, вы получите ошибку типа «Access Violation» или «Invalid pointer», но реальное поведение часто неопределено.

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

    V будет помещена в процессорном стеке. Это специальная часть памяти, которая используется для хранения локальных переменных и параметров вызываемых процедур, а также содержит важные данные типа адресов возврата для каждой вызванной подпрограммы. Результат функции указывает на V ( PChar может указывать прямо на массив — см. статью, что я упоминал). Как только VersionData завершает выполнение, стек изменяется следующей вызванной подпрограммой, так что данные, вычисленные в CalculateVersion оказываются перезаписанными, а указатель указывает на это новое содержимое по всё тому же старому адресу.

    Похожая проблема: указывание PChar на строку String , но это также обсуждалось в статье про PChar . Или использование указателя на элемент динамического массива (динамические массивы могут перемещаться по памяти, если их размер меняется вызовами SetLength ) — вместо этого надо использовать просто индекс.

    Использование неверного базового типа

    Тот факт, что указатели могут указывать на любое место в памяти и что два указателя разных типов могут указывать на одно и то же место, фактически означает, что вы можете обращаться к одной памяти разными способами. Используя указатель на Byte ( ^Byte ), вы можете изменять индивидуальные байты Integer -а или любого другого типа.

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

    J будет иметь правильное значение, потому что компилятор добавит код расширения одного байта до ( 4 -х байтового) Integer с помощью обнуления старшей части Integer -а. Но I не будет иметь верного значения. Она будет содержать наш байт и ещё 3 каких-то байта, которые следуют за B , формируя этим некоторое неопределённое (мусорное) значение.

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

    Владельцы и забытая память (Owners and orphans)

    Указатели не только могут иметь различные базовые типы, но и различную семантику владения. Если вы выделяете память, используя New или GetMem или любую другую более специализированную подпрограмму, вы являетесь владельцем этой памяти. Лучше всего, если вы будете держаться за эту память, заныкав указатель на неё в надёжное место. Указатель — это ваш единственный способ получить доступ к этой памяти, и если он будет утерян — вы не сможете ни прочитать данные, ни освободить их. Одно из общих правил: тот, кто выделяет память, обязан её и освободить, так что это ваша обязанность. Хорошо спроектированные программы всегда следуют этому правилу.

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

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

    Вот простой пример, взятый (с разрешения автора) из групп обсуждений Borland:

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

    Фактически тут есть и другие ошибки. bitdata — это динамический массив, и взятие адреса bitdata берёт адрес указателя на данные массива, вместо адреса самих данных (см. ниже, динамические массивы). Также, поскольку pbBitmap уже является указателем, неправильно использовать на него оператор @ .

    Более правильный код выглядел бы так:

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

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

    Арифметика указателей и массивы

    You can either have software quality or you can have pointer arithmetic, but you cannot have both at the same time. — Bertrand Meyer

    Delphi позволяет производить над указателями несколько простых действий. Во-первых, конечно же, вы можете присваивать им значения и сравнивать их на равенство ( if P1 = P2 then ) или неравенство. Но вы также можете увеличивать или уменьшать их, используя псевдофункции Inc и Dec . Приятным моментом при этом является то, что эти функции учитывают размер базового типа указателя. Пример (заметьте, что я руками присвоил указателю фальшивый адрес. Пока я не попытаюсь получить по нему доступ, всё будет в порядке):

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

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

    В реальности же, разница между обоими способами незначительна, если вообще заметна. Во-первых, у современных процессоров есть специальные способы для адресования при типичных случаях использования индекса. Во-вторых, компилятор всё равно обычно оптимизирует использование индекса в использование указателя, если это возможно. А в примере выше, незначительная разница и вовсе съедается выполнением такой сложной функции как Write().

    Как вы можете видеть в примере выше, вы можете легко забыть увеличить указатель в цикле. И вы всё равно должны использовать либо цикл for-to-do , либо какой-то другой способ для организации цикла и условия выхода (с ручным сдвигом и сравнением). Код с использованием указателей обычно труднее поддерживать. И поскольку подход с указателями ничуть не быстрее (ну за исключением очень маленьких циклов), я бы избегал подобного кода в Delphi. Делайте так только если вы прогнали программу под профайлером и считаете, что доступ с указателем действительно будет выигрышным.

    Указатели на массивы

    Но иногда у вас нет массива, а есть только указатель для доступа к памяти. Функции Windows API часто возвращают данные в буферах, которые содержат массивы записей определённого размера. Но даже в этом случае, вероятно, будет проще преобразованием типа сделать буфер указателем на массив, чем использовать Inc или Dec. Пример:

    Delphi 2009

    В Delphi 2009 и выше арифметика указателей, помимо типа PChar (и PAnsiChar и PWideChar ), также применима и для других типов указателей. Где и когда это становится возможным — контролируется директивой компилятора $POINTERMATH .

    По умолчанию, арифметика указателей выключена, но она может быть включена для участка кода, используя директиву <$POINTERMATH ON>, и выключена после него с помощью <$POINTERMATH OFF>. Директива также может применяться при объявлении типа указателя. При этом арифметика с указателями будет доступна для этого типа в любом коде без предварительной обёртки в директивы $POINTERMATH . Помимо операций инкремента/декремента, новая арифметика указателей в Delphi 2009 также допускает индексацию.

    Сейчас, кроме типов PChar , PAnsiChar и PWideChar , единственным типом с поддержкой арифметики указателей по умолчанию является PByte . Но вы можете включить её для любого типа, как например, PInteger . Это значительно упростит код из примера выше:

    Так что теперь у нас нет необходимости объявлять специальный тип PIntegerArray . Также вместо PInt[I] можно использовать синтаксис (PInt + I)^ , который приводит к тому же результату.

    Кажется, в Delphi 2009 новая арифметика указателей не работает, как ожидается, для указателей на обобщённые типы (generics). С каким бы параметрическим типом вы не инстанциировали тип, индексы не масштабируются на SizeOf(T) , как это ожидается.

    Ссылки (References)

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

    Что отличает ссылки (references) от указателей (pointers):

    • Ссылки неизменяемы. Вы не можете увеличить или уменьшить ссылку. Ссылки указывают на определённые структуры, но никогда не указывают в середину них, как, например, указатели на данные массива в примерах выше.
    • Ссылки не используют синтаксис указателей. Это скрывает тот факт, что они являются указателями, и делает их сложнее для понимания для тех, кто не знаком с этой темой (и поэтому люди делают с ними вещи, которых делать нельзя).

    Не путайте такие ссылки со ссылочными типами (reference types) в C++. Они во многом отличаются.

    Динамические массивы (dynamic arrays)

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

    Динамические массивы (например, array of Integer ) в Delphi работают точно так же. Но библиотека runtime добавляет специальный код, который управляет доступом и присваиваниями. В участке памяти ниже адреса, на который указывает ссылка массива, располагаются служебные данные массива: два поля — число выделенных элементов и счётчик ссылок (reference count).

    Если, как на диаграмме выше, N — это адрес в переменной динамического массива, то счётчик ссылок массива лежит по адресу N — 8 , а число выделенных элементов (указатель длины) лежит по адресу N — 4 . Первый элемент массива (сами данные) лежит по адресу N .

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

    Доступ к данным динамических массивов с помощью низкоуровневых процедур типа Move или FillChar , или любых других подпрограмм, получающих доступ сразу ко всему массиву, наподобие TStream.Write , часто выполняется неправильно. Для обычного массива (его часто называют также статическим массивом — в противоположность динамическому массиву) переменная массива тождественна его данным. Для динамического массива это не так (см. диаграмму выше). Так что если вы хотите получить доступ к данным массива — вы не должны использовать саму переменную массива, а использовать вместо неё первый элемент массива.

    Заметьте, что в примере выше Stream.Write использует нетипизированный var параметр, который также является ссылочным типом. Мы ещё обсудим их ниже.

    Многомерные динамические массивы

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

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

    Ну, это выглядит как многомерный динамический массив и, действительно, мы можем обращаться к нему как MyIntegers[0, 3] . Но на самом деле это объявление следует скорее читать как (тут я позволяю себе некоторые вольности с синтаксисом):

    Или, чтобы сделать совсем явным, это фактически означает следующее:

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

    (что создаст 10 TSingleIntegerArrays по 20 Integer в каждом — т.е. прямоугольный массив), вы можете получить доступ к любому из подмассивов и установить его длину индивидуально:

    Строки (Strings)

    Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. — Stan Kelly-Bootle

    Строки во многом похожи на динамические массивы. Они также имеют счётчик ссылок, у них похожая внутренняя структура (со счётчиком ссылок и указателем длины ниже самих данных, по тем же смещениям).

    Но есть и различия между ними в синтаксисе и семантике. Вы не можете присвоить строке nil — вместо этого вы присваиваете ей » (пустую строку). Строки могут быть константами (со счётчиком ссылок равным -1 , что является специальным указанием для библиотеки runtime: она не будет пытаться увеличивать или уменьшать его или удалять строку). Первый элемент строки имеет индекс 1 , в то время как в массиве это 0 .

    Больше информации о строках вы можете узнать из моей статьи о PChar и строках.

    Объекты (Objects)

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

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

    После указателей на VMT и все таблицы интерфейсов, идут обычные поля объекта, которые хранятся ровно как в записях (record).

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

    Ниже следует пример. Предположим, что у нас есть такое объявление:

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

    Интерфейсы (Interfaces)

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

    Тогда отношения между интерфейсом, реализующим его объектом и классом, а также методами будет выглядеть вот так:

    Переменная MyEditable указывает на указатель IEditable в объекте, созданном TMyClass.Create . Заметим, что MyEditable не указывает на начало объекта, а куда-то в его середину. Далее, указатель MyEditable в объекте указывает на таблицу указателей — по одному указателю на каждый метод в интерфейсе. Каждая из таких записей указывает на кусочек-кода: заглушку (stub). Этот служебный код корректирует указатель Self (который к моменту вызова фактически равен MyEditable ), чтобы он указывал на начало объекта, с помощью отнимания смещения указателя IEditable в объекте от переданного в код указателя, а затем вызывает настоящий метод. Эта заглушка вводится для каждой реализации метода каждого интерфейса, реализуемого классом.

    Пример: предположим у нас есть экземпляр по адресу 50000 , а указатель на реализацию IEditable классом TWhatsit лежит по смещению 16 в каждом экземпляре. Тогда переменная MyEditable будет содержать 50016 . Указатель IEditable по адресу 50016 будет указывать на таблицу интерфейса для нашего класса (которая лежит, скажем, по адресу 30000 ), элементы которой указывают на заглушку (скажем, по адресу 60000 ). Заглушка увидит значение 50016 (которое передаётся ей как параметр Self ), вычтет из него смещение 16 и получит 50000 . Это и будет настоящий адрес реализующего интерфейс объекта. Затем заглушка вызывает настоящий метод, передавая ему 50000 в качестве параметра Self .

    В диаграмме я, для ясности, опустил заглушки для методов QueryInterface, _AddRef и _Release

    Ну, теперь вы видите, почему иногда я люблю использовать бумагу и ручку? ;-)

    Ссылочные параметры

    Ссылочные параметры часто называются var -параметрами, а также out -параметрами или параметрами, передаваемыми по ссылке.

    Ссылочные параметры — это такие параметры подпрограммы, для которых не само значение параметра передаётся и/или возвращается из подпрограммы, а только указатель на него. Пример:

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

    Хотя, тут есть несколько различий:

    • Вы не используете синтаксис указателей. Когда вы пишете имя параметра, вы автоматически разыменовываете указатель, т.е. использование имени параметра означает работу со значением, а не с указателем.
    • Ссылочные параметры не могут быть изменены (имеется ввиду сам параметр, а не его значение). Использование имени параметра даёт вам значение параметра — вы не можете изменить указатель или инкрементировать/декрементировать его.
    • Вы должны передать что-то, что имеет адрес — т.е. реальную память, если только вы не используете трюк с приведением типов. Так что, имея ссылочный параметр типа Integer, вы не можете передать, например, 17 , 98765 или Abs(MyInteger) . Передаваемый параметр должен быть переменной (это включает в себя также элементы массива, поля записей и объектов и т.д.).
    • Фактические параметры обязаны быть того же типа, как и параметры в объявлении подпрограммы, т.е. вы не можете передать TEdit , если вы объявили параметр как TObject . Чтобы избежать этого, вы можете использовать только нетипизированные ссылочные параметры (см. ниже). Прим. пер.: как это сделано в, например, FreeAndNil.

    Синтаксически, наверное, кажется, что проще использовать ссылочные параметры, чем явные указатели. Но вы должны быть в курсе некоторых особенностей. Чтобы передавать указатели, вы должны увеличить уровень косвенности на единичку. Другими словами, если у вас есть указатель P на Integer , то чтобы передать его, вы должны синтаксически передавать P^ (хотя на деле будет передаваться сам P), например:

    Прим. пер.: на самом деле по ссылке также передаются почти любые типы, размер которых больше SizeOf(Pointer), даже если вы не указали var или out. Например, запись из 8 байт будет передаваться по ссылке. Хотя с точки зрения синтаксиса никаких указателей вы опять не увидите (хотя без наличия модификатора const будет сделана локальная копия). Аналогично: если функция возвращает тип, размер которого больше SizeOf(Pointer), то на самом деле в функцию будет передан указатель на память, куда надо записать результат. Т.е. функция

    На самом деле трактуется как:

    Нетипизированные параметры

    Нетипизированные параметры также являются ссылочными параметрами, но они могут быть либо var , либо const либо out . Вы можете передавать в этот параметр любой тип данных, что делает эти виды параметров пригодными для написания подпрограмм, которые принимают почти что угодно, любого размера и типа, но это также означает, что вы должны как-то указать подпрограмме на тип передаваемого аргумента — либо отдельным параметром, либо просто по соглашению, либо же тип параметра значения не имеет.

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

    Второй пример — это метод TIntegerList — дочернего класса для TTypedList :

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

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

    Я уже упоминал уровень косвенности (level of indirection). Вы уже видели это в действии. Например, если вы хотите инициализировать динамический массив с помощью FillBytes, то вы не передаёте в FillBytes саму переменную массива, а только первый элемент массива. Фактически, вы также можете передать первый элемент статического массива для достижения такого же эффекта. Таким образом, если вы передаёте любой массив в подпрограмму с нетипизированным ссылочным параметром, то, по моему мнению, ваш лучший выбор — всегда передавать первый элемент массива, чтобы не зависеть от типа массива (динамический или статический) (тем более, что вы потом можете изменить объявление — и попробуйте тогда найти ошибку в коде).

    Структуры данных

    Указатели активно используются в структурах данных типа связанных списков, любых видов деревьев или иных иерархий. Я не буду подробно разбирать их здесь. Достаточно сказать, что такие продвинутые структуры данных не могут существовать без указателей или подобных видов ссылок (например, индекс массива в элементе массива), даже в языках, где формально нет указателей, типа Java (ну, по крайней мере я так думаю). Если вы хотите узнать больше об этих структурах данных — просто возьмите любую книжку по этой теме.

    Я только дам простой пример диаграммы структуры данных, которая основательно зависит от указателей — связанный список:

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

    Заключение

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

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

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

    Примечания переводчика:

    (*) Подробное описание компьютерной памяти вы можете прочитать у Рихтера и цикле статей Марка Руссиновича «Pushing the Limits of Windows» (часть 1, часть 2, часть 3).

    (***) Хотя это и чертовски маловероятно. Писать код без такого предположения довольно тяжело, т.к. получается, что (к примеру) после ZeroMemory мы не получаем nil .

    (****) На самом деле, формально такой массив должен быть объявлен с ключевым словом packed.

    Программирование на языке 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. Глава 2. Основы языка Delphi. Часть 3

    Оглавление


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


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

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

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

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

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

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

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

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

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


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

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

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

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

  • Сохраните модуль под именем MathLib, выбрав в меню команду File / Save (рисунок 8):
  • Рисунок 8. Окно сохранения модуля

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Строки


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

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

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

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

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

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

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

    Строковая переменная объявляется с помощью зарезервированного слова 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-строки. О работе с нуль-терминированными строками мы поговорим чуть позже.

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

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

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

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

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

    Короткая строка объявляется с помощью идентификатора типа 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).

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

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

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

    Выражение


    Результат


    ‘Object’ + ‘ Pascal’ ‘Object Pascal’

    Операции отношения (=, <>, >, =, ‘ABCDE’

    True
    ‘Office’ = ‘Office’ True
    ‘USIS’ > ‘US’ True

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

    Объявление строки


    Выражение


    Значение строки


    Name: string[6]; Name := ‘Mark Twain’; ‘Mark T’

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

    Строковые ресурсы

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

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

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

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

    Форматы кодирования символов

    Существуют различные форматы кодирования символов. Отдельный символ строки может быть представлен в памяти одним байтом (стандарт Ansi), двумя байтам (стандарт Unicode) и даже четырьмя байтами (стандарт UCS-4 — Unicode). Строка «Wirth» (фамилия автора языка Pascal — прародителя языка Delphi) будет представлена в указанных форматах следующим образом (рисунок 11):

    Рисунок 11. Форматы кодирования символов

    Существует также формат кодирования MBCS (Multibyte Character Set), согласно которому символы одной строки кодируются разным количеством байт (одним или двумя байтами в зависимости от алфавита). Например, буквы латинского алфавита кодируются одним байтом, а иероглифы японского алфавита — двумя. При этом латинские буквы и японские иероглифы могут встречаться в одной и той же строке.

    Стандартные процедуры и функции для работы со строками

    Так как обработка строк выполняется практически в каждой серьезной программе, стандартно подключаемый модуль 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’)2Pos(‘sh’, ‘champagne’)

  • Str (X [: Width [: Decimals] ], var S: string) — преобразует числовое значение величины X в строку S. Необязательные параметры Width и Decimals являются целочисленными выражениями. Значение Width задает ширину поля результирующей строки. Значение Decimals используется с вещественными числами и задает количество символов в дробной части.

Выражение


Значение 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.

Массивы


Объявление массива

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

После описания типа можно переходить к определению переменных и типизированных констант:

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

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

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

Объявленные выше массивы являются одномерными, так как имеют только один индекс. Одномерные массивы обычно используются для представления линейной последовательности элементов. Если при описании массива задано два индекса, массив называется двумерным , если n индексов — n-мерным . Двумерные массивы используются для представления таблицы, а n-мерные — для представления пространств. Вот пример объявления таблицы, состоящей из 5 колонок и 20 строк:

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

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

или в более компактной записи

Эти два способа индексации эквивалентны.

Работа с массивами

Массивы в целом участвуют только в операциях присваивания. При этом все элементы одного массива копируются в другой. Например, если объявлены два массива A и B,

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

Оба массива-операнда в левой и правой части оператора присваивания должны быть не просто идентичны по структуре, а описаны с одним и тем же типом, иначе компилятор сообщит об ошибке. Именно поэтому все массивы рекомендуется описывать в секции type .

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

Для массивов определены две встроенные функции — Low и High. Они получают в качестве своего аргумента имя массива. Функция Low возвращает нижнюю, а High — верхнюю границу этого массива. Например, Low(A) вернет значение 1, а High(A) — 5. Функции Low и High чаще всего используются для указания начального и конечного значений в операторе цикла for . Поэтому вычисление суммы элементов массива A лучше переписать так:

В операциях с многомерными массивами циклы for вкладываются друг в друга. Например, для инициализации элементов таблицы, объявленной как

требуются два вложенных цикла for и две целые переменные Col и Row для параметров этих циклов:

Массивы в параметрах процедур и функций

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

Функция Average принимает в качестве параметра массив известной размерности. Требование фиксированного размера для массива-параметра часто является чрезмерно сдерживающим фактором. Процедура для нахождения среднего значения должна быть способна работать с массивами произвольной длины. Для этой цели в язык Delphi введены открытые массивы-параметры. Такие массивы были заимствованы разработчиками языка Delphi из языка Modula-2. Открытый массив-параметр описывается с помощью словосочетания array of , при этом границы массива опускаются:

Внутри подпрограммы Average нижняя граница открытого массива A равна нулю (Low(A) = 0), а вот значение верхней границы (High(A)) неизвестно и выясняется только на этапе выполнения программы.

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

Вот пример использования функции Average:

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

И еще одно важное замечание по поводу открытых массивов. Некоторые библиотечные подпрограммы языка Delphi принимают параметры типа array of const — открытые массивы констант . Массив, передаваемый в качестве такого параметра, обязательно конструируется в момент вызова подпрограммы и может состоять из элементов различных типов (!). Физически он состоит из записей типа TVarRec , кодирующих тип и значение элементов массива (записи рассматриваются ниже). Открытый массив констант позволяет эмулировать подпрограммы с переменным количеством разнотипных параметров и используется, например, в функции Format для форматирования строки (см. выше).

Уплотнение структурных данных в памяти

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

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

Заметим, что ключевое слово packed применимо к любому структурному типу данных, т.е. массиву, множеству, записи, файлу, классу, ссылке на класс.

Множества


Объявление множества

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

Для описания множественного типа используется словосочетание set of , после которого записывается базовый тип множества:

Теперь можно объявить переменную множественного типа:

Можно объявить множество и без предварительного описания типа:

В выражениях значения элементов множества указываются в квадратных скобках: [2, 3, 5, 7], [1..9], [‘A’, ‘B’, ‘C’]. Если множество не имеет элементов, оно называется пустым и обозначается как [ ]. Пример инициализации множеств:

Количество элементов множества называется мощностью . Мощность множества в языке Delphi не может превышать 256.

Операции над множествами

При работе с множествами допускается использование операций отношения (=, <>, >=, in .

Операции сравнения (=, <>). Два множества считаются равными, если они состоят из одних и тех же элементов. Порядок следования элементов в сравниваемых множествах значения не имеет. Два множества A и B считаются не равными, если они отличаются по мощности или по значению хотя бы одного элемента.

Выражение


Результат


[1, 2] <> [1, 2, 3] True [1, 2] = [1, 2, 2] True [1, 2, 3] = [3, 2, 1] True [1, 2, 3] = [1..3] True

Операции принадлежности (>=, = B равно True, если все элементы множества B содержатся в множестве A. Выражение A = [1, 2] True [1, 2] in . Используется для проверки принадлежности элемента указанному множеству. Обычно применяется в условных операторах.

Выражение


Результат


5 in [1..9] True 5 in [1..4, 6..9] False

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

можно заменить более коротким:

Операцию in иногда пытаются записать с отрицанием: X not in S. Такая запись является ошибочной, так как две операции следуют подряд. Правильная запись имеет вид: not (X in S).

Объединение множеств (+) . Объединением двух множеств является третье множество, содержащее элементы обоих множеств.

Выражение


Результат


[ ] + [1, 2] [1, 2] [1, 2] + [2, 3, 4] [1, 2, 3, 4]

Пересечение множеств (*) . Пересечение двух множеств — это третье множество, которое содержит элементы, входящие одновременно в оба множества.

Выражение


Результат


[ ] * [1, 2] [ ] [1, 2] * [2, 3, 4] [2]

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

Выражение


Результат


[1, 2, 3] — [2, 3] [1] [1, 2, 3] — [ ] [1, 2, 3]

В язык Delphi введены две стандартные процедуры Include и Exclude, которые предназначены для работы с множествами.

Процедура Include (S, I) включает в множество S элемент I. Она дублирует операцию + (плюс) с той лишь разницей, что при каждом обращении включает только один элемент и делает это более эффективно.

Процедура Exclude (S, I) исключает из множества S элемент I. Она дублирует операцию — (минус) с той лишь разницей, что при каждом обращении исключает только один элемент и делает это более эффективно.

Выражение


Результат


S := [1, 3]; [1, 3] Include(S, 2); [1, 2, 3] Exclude(S, 3) [1, 2]

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

Записи


Объявление записи

Запись — это составной тип данных, состоящий из фиксированного числа элементов одного или нескольких типов. Описание типа записи начинается словом record и заканчивается словом end . Между ними заключен список элементов, называемых полями , с указанием идентификаторов полей и типа каждого поля:

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

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

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

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

Обращение к полям записи имеет несколько громоздкий вид, что особенно неудобно при использовании мнемонических идентификаторов длиной более 5 символов. Для решения этой проблемы в языке Delphi предназначен оператор with, который имеет формат:

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

Допускается применение оператора присваивания и к записям в целом, если они имеют один и тот же тип. Например,

После выполнения этого оператора значения полей записи Friend станут равными значениям соответствующих полей записи BestFriend.

Записи с вариантами

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

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

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

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

Зарезервированные слова Delphi

var A, B : Integer; begin A:=3; B:=4; A:=A*A+B*B; end;

if (условие) then (действие) else (альтернатива) ;

Слова if (если), then (тогда), else (иначе) — зарезервированные. Действие и else альтернатива — это любые операторы Delphi, или несколько операторов, заключённых в логические скобки begin/end, или вызов подпрограммы. Если условие истинно, то выполняется действие, если ложно, то выполняется альтернатива.

(применяется, когда известно количество повторений цикла)

for счётчик := выражение-1 to выражение-2 do действие ;

Возможна работа оператора цикла, при котором переменная-счётчик будет не увеличиваться, а уменьшаться. В этом случае ключевое слово to заменяется на downto:

for счётчик := выражение-1 downto выражение-2 do действие ;

Цикл с предусловием (применяется, когда неизвестно количество повторений цикла)

while условие do тело цикла ;

Этот цикл будет выполняться до тех пор, пока истинно условие (логическое выражение, возвращающее значение типа Boolean). При этом если это выражение сразу равно false, тело цикла не будет выполнено ни разу. Нужно очень внимательно следить за написанием условия и контролем завершения цикла, так как в результате ошибки цикл while будет повторяться бесконечное количество раз, что приведёт к «зацикливанию» и «зависанию» программы.

Цикл с постусловием

(применяется, когда неизвестно количество повторений цикла)

repeat тело цикла until условие ;

Повторения сначала выполняет тело цикла, а затем уже проверяет выполнение условия: Таким образом, этот вариант цикла гарантирует, что тело цикла будет выполнен по крайней мере один раз. И будет выполняться до тех пор, пока условие не станет истинным (т.е. true). Стоит отметить, что это единственный оператор Delphi, в котором тело цикла не требуется заключать в логические скобки begin/end. Начало и конец тела цикла определяются по ключевым словам repeat и until.

Позволяет изменить последовательность выполнения программы. В качестве метки может использоваться любой допустимый идентификатор или число в диапазоне от 0 до 9999. Метку предварительно необходимо объявить в разделе описания переменных, но с помощью не ключевого слова var, а ключевого слова label:

Что означает «Packed Now Force Byte Alignment of Records»?

Что нового для Delphi XE2 содержит ниже.

Packed Now Принудительное выравнивание байтов записей

Если у вас есть устаревший код, который использует тип упакованной записи, и вы хотите для связи с внешней DLL или с С++, вам нужно удалить слово «упакован» из вашего кода. Упакованное ключевое слово теперь приводит к выравниванию байтов, тогда как в прошлом это не обязательно делало это. Поведение изменение связано с изменениями совместимости выравнивания С++ в Delphi 2009.

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

Почему документы говорят «если вы хотите установить связь с внешней DLL или с С++, вам нужно удалить слово, упакованное из вашего кода»? Если внешний код использует #pragma pack(1) , то что нам делать, если он упакован — это пределы?

Как насчет директивы $ALIGN ? Являются ли <$A1>and <$A->эквивалентными packed или есть какой-то дополнительный смысл с packed ?

Кажется, что я что-то упускаю и буду признателен, если кто-нибудь сможет это объяснить. Или документация очень бедна?

Обновление

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

Это дает тот же результат на Delphi 6, 2010, XE и XE2 32 бит и XE 64 бит.

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

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

Если вы объявляете переменную записи, она выровнена с некоторым адресом в памяти, который, скорее всего, выровнен по 4-байтовой границе. Это произошло (как было протестировано в D2007) для упакованных и распакованных записей.

Теперь в XE2 упакованная запись помещается в 1-байтовую границу, в то время как распакованные записи помещаются на некоторую четную границу, которую можно контролировать с помощью ключевого слова align. Вот так:

Упакованная запись по-прежнему выравнивается по 1-байтовой границе, а распакованная запись выровнена с 16-байтовой границей.

Как я уже сказал, это всего лишь предположение. Формулировка цитаты Эмбаркадеро не так уж ясна в этом вопросе.

Насколько я помню, record использовался для упаковки с версии компилятора вокруг Delphi 5-6.

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

Текст, который вы цитируете, связан не с XE2, а с изменением Delphi 2009. См. эту запись в блоге для исторической цели.

Я предполагаю, что «Packed» Now Bull Alignment of Records «относится к функции Delphi 2009 <$OLDTYPELAYOUT ON>, которая может иметь некоторые проблемы с реализацией перед XE2. Я согласен с вами: это звучит как проблема с документацией.

pascal всегда поддерживает упакованные структуры, которые, вероятно, проще всего объяснить с помощью примера

Допустим, у вас есть два массива x и y, и мы используем 16-битный процессор. То есть размер «слова» процессора составляет 16 бит.

Pascal только сообщает вам, что у вас есть 10 элементов для работы, каждый из которых содержит 3 байта информации (позволяет предположить, что integer является старым 16-битным разнообразием). Он фактически ничего не говорит о том, как хранится эта информация. Вы можете предположить, что массив хранится в 30 последовательных байтах, однако это не обязательно true и полностью зависит от компилятора (действительно хорошая причина избежать математики указателя).

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

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

Снова я говорю «может», потому что он все еще зависит от компилятора. «Упакованное» ключевое слово просто говорит, чтобы собрать данные ближе друг к другу, но на самом деле не говорит, как это сделать. Поэтому некоторые компиляторы просто игнорируют ключевое слово, тогда как другие используют упакованные для отключения выравнивания слов.

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

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

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

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

UPDATE, продолжение потока комментариев ниже.

DavidH > Я не могу себе представить, что вы имеете в виду, что компилятор способен производить разные выходные данные при идентичном вводе

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

TonyH > Я думаю, что большинство из нас всегда считалось, что упакованный = байт выровнен, так что теперь мы царапаем головы для сценария, где это не так. @Дэвид Хеффернан спрашивает, знает ли кто-нибудь

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

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

Если вы затем напечатаете SizeOf (myarray), что бы вы ожидали? Ответ заключается в том, что delphi не гарантирует, что упакованный реализован каким-либо конкретным способом. Поэтому ответ может быть 8 байтов (если вы байт выравниваете каждый элемент). Для Delphi так же корректно упаковывать их как биты, и поэтому весь массив может вписываться в 1 байт. Это может быть 16 байт, если компилятор решает не оптимизировать границы байтов и работает на 16-битной архитектуре. Да, это было бы менее эффективно с точки зрения скорости, но весь смысл упаковки — оптимизировать размер по скорости. Независимо от того, что он делает, невидимо для разработчика, поскольку они просто обращаются к элементам массива и не делают никаких предположений и не пытаются использовать свою собственную математику указателей для доступа к базовым данным.

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

Эти типы архитектурных проблем именно поэтому разработаны различные методы, такие как файл с разделителями-запятыми, xml и т.д. Для обеспечения надежной общей платформы.

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

Элементы языка программирования Delphi

Алфавит

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

Цифры – арабские цифры от 0 до 9.

Шестнадцатеричные цифры имеют значения от 0 до 15. Первые 10 обозначаются арабскими цифрами от 0 до 9, остальные – это A, B, C, D, E, F.

Специальные символы Delphi – это символы:

К специальным символам также относятся составные символы, которые воспринимаются компилятором как единое целое:

Зарезервированные слова и идентификаторы

В Delphi имеются следующие зарезервированные слова:

And Except Library Set

Array Exports Mod Shl

As File Nil Shr

Asm Finalization Not String

Begin For Object Then

Case Function Of Threadvar

Class Goto Or To

Const If Out Try

Constructor Implementation Packed Type

Destructor In Procedure Unit

Dispinterface Inherited Program Until

Div Initialization Property Uses

Do Inline Raise Var

Downto Interface Record While

Else Is Repeat With

End Label Resourcestring Xor

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

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

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

Например,Vara: integer задает переменную а целого типа.

Типы данных

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

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

По мере изучения Delphi ознакомимся со всеми этими типами данных, но начнем с простых типов.

К простым типам данных относятся порядковые, вещественные типы и тип дата-время.

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

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

Таблица 3. Целые типы данных

Целые типы
Название типа Длина, байт Диапазон допустимых значений
Byte 0 . +255
ShortInt -128 . +127
SmallInt -32 768 . +32 767
Word 0 . +65 535
Integer -2 147 486 648 . +2 147 487 647
LongInt -2 147 483 648 . +2 147 483 647
LongWord 0 . + 4 294 967 295
Int64 -9∙10 18 . +9∙10 18
Cardinal 0 . 2 147 483 647

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

Таблица 4. Вещественные типы данных

Длина, байт Название типа Количество значащих цифр Диапазон допустимых значений
Real 15 … 16 5,0∙10 -324 … 1,7∙10 308
Single 7 … 8 1,5∙10 -45 … 3,4∙10 38
Double 15 … 16 5,0∙10 -324 … 1,7∙10 308
Extended 19 … 20 3,4∙10 -4951 … 1,1∙10 4932
Comp 19 … 20 -2 63 … +2 63 -1
Currency 19 … 20 ±922 337 203 685 477, 5807

3. Тип дата-время предназначен для хранения даты и времени. Фактически для этой цели он использует вещественный формат.

Константы

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

Константы описываются после ключевого слова const.Тип у константы можно указы­вать, а можно и не указывать. Значение константы задается после знака равенства.

Например, constPI = 3.14;

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

Помимо числовых констант, в Delphi существуют строковые и символьные константы.Символы имеют тип Charи записываются в одиночных кавычках: ‘Язык программирования Delphi’.

Например, const Prog = ‘Язык программирования Delphi’;

Илон Маск рекомендует:  Использование syslog для логирования работы программскриптов
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL