Переменные — это явно именованные области памяти с определенным типом. При присвоении значений переменным компилятор Free Pascal генерирует машинный код для перемещения значения в область памяти, зарезервированную для этой переменной. Место хранения этой переменной зависит от того, где она объявлена:
• Глобальные переменные — это переменные, объявленные в модуле или программе, но не внутри процедуры или функции. Они хранятся в фиксированных областях памяти и доступны в течение всего времени выполнения программы.
• Локальные переменные объявляются внутри процедуры или функции. Их значение хранится в стеке программы, т. е. не в фиксированных областях.
Компилятор Free Pascal прозрачно обрабатывает выделение этих областей памяти, хотя на это расположение можно повлиять в объявлении.
Компилятор Free Pascal также прозрачно обрабатывает чтение значений из переменных или запись значений в них. Но даже это может быть явно обработано программистом при использовании свойств.
Переменные должны быть явно объявлены, когда они необходимы. Память не выделяется, если переменная не объявлена. Использование идентификатора переменной (например, переменной цикла), которая не объявлена первой, является ошибкой, о которой сообщит компилятор.
Это означает, что следующие объявления переменных являются допустимыми:
Var
curterm1 : integer;
curterm2 : integer; cvar;
curterm3 : integer; cvar; external;
curterm4 : integer; external name 'curterm3';
curterm5 : integer; external 'libc' name 'curterm9';
curterm6 : integer absolute curterm1;
curterm7 : integer; cvar; export;
curterm8 : integer; cvar; public;
curterm9 : integer; export name 'me';
curterm10 : integer; public name 'ma';
curterm11 : integer = 1 ;
Разница между этими объявлениями следующая:
1. Первая форма (curterm1) определяет обычную переменную. Компилятор управляет всем сам.
2. Вторая форма (curterm2) также объявляет обычную переменную, но указывает, что ассемблерное имя для этой переменной равно имени переменной, как записано в исходном коде.
3. Третья форма (curterm3) объявляет переменную, которая находится снаружи: компилятор будет
предполагать, что память находится в другом месте, и что ассемблерное имя этого расположения указано именем переменной, как записано в исходном коде. Имя можно не указывать.
4. Четвертая форма полностью эквивалентна третьей, она объявляет переменную, которая хранится
внешне, и явно указывает ассемблерное имя расположения. Если cvar не используется, имя должно быть указано.
5. Пятая форма является вариантом четвертой формы, только имя библиотеки, в которой зарезервирована память, также указано.
6. Шестая форма объявляет переменную (curterm6) и сообщает компилятору, что она хранится в том же месте, что и другая переменная (curterm1).
7. Седьмая форма объявляет переменную (curterm7) и сообщает компилятору, что ассемблерная метка этой переменной должна быть именем переменной (с учетом регистра) и должна быть общедоступной. т. е. на нее можно ссылаться из других объектных файлов.
8. Восьмая форма (curterm8) эквивалентна седьмой: «public» — это псевдоним для «export».
9. Девятая и десятая формы эквивалентны: они указывают имя ассемблера переменной.
10. одиннадцатая форма объявляет переменную (curterm11) и инициализирует ее значением (1 в приведенном выше случае).
Обратите внимание, что имена ассемблера должны быть уникальными. Невозможно объявить или экспортировать две переменные с одним и тем же именем ассемблера. В частности, не пытайтесь экспортировать переменные с публичным именем, которое начинается с FPC_; компилятор использует некоторые внутренние системные процедуры с этим именем.
Переменные, как и любой идентификатор, подчиняются общим правилам области действия. Кроме того, инициализированные переменные инициализируются, когда они попадают в область действия:
• Глобальные инициализированные переменные инициализируются один раз при запуске программы.
• Локальные инициализированные переменные инициализируются каждый раз при входе в процедуру.
Обратите внимание, что поведение локальных инициализированных переменных отличается от поведения локальной типизированной константы.
Локальная типизированная константа ведет себя как глобальная инициализированная переменная.
По умолчанию простые переменные в Pascal не инициализируются после их объявления. Любое предположение, что они содержат 0 или любое другое значение по умолчанию, ошибочно: они могут содержать мусор. Чтобы исправить это, существует концепция инициализированных переменных. Отличие от обычных переменных в том, что их объявление включает начальное значение, как можно увидеть на диаграмме в предыдущем разделе.
Примечание. Существуют два исключения из этого правила:
1. Управляемые типы являются исключением из этого правила: Управляемые типы всегда инициализируются значением по умолчанию: в общем случае это означает установку счетчика ссылок в ноль или установку значения указателя типа в Nil.
2. Глобальные переменные инициализируются эквивалентом нуля.
Обратите внимание, что обнуление некоторых переменных может привести к недопустимому содержимому переменных:
Type
TWeekDays =
(monday,tuesday,wednesday,thursday,friday,saturday,sunday);
TWeekend = saturday..sunday;
var
W : TWeekend;
begin
Writeln(W);
end.
При запуске вышеприведенного кода возникнет ошибка:
Runtime error 107 at $000000000040024A
$000000000040024A
$000000000042BF70
$00000000004001D2
Поэтому настоятельно рекомендуется всегда инициализировать переменные перед их использованием. Это можно легко сделать в объявлении переменных. Учитывая объявление:
Var
S : String = 'This is an initialized string';
Значение переменной following будет инициализировано предоставленным значением. Следующий способ — это еще лучший способ сделать это:
Const
SDefault = 'This is an initialized string';
Var
S : String = SDefault;
Инициализация часто используется для инициализации массивов и записей. Для массивов инициализируемые элементы должны быть указаны, заключены в круглые скобки и разделены запятыми. Количество инициализированных элементов должно быть точно таким же, как количество элементов в объявлении типа. Например:
Var
tt : array [1..3] of string[20] = ('ikke', 'gij', 'hij');
ti : array [1..3] of Longint = (1,2,3);
Для постоянных записей каждый элемент записи, который вы хотите инициализировать, должен быть указан в форме Поле: Значение, разделенный точкой с запятой и заключенный в круглые скобки. Вы можете опустить поля, которые вы не хотите инициализировать, фактически вы можете пропустить все поля. Если вы пропустите поля, компилятор выдаст предупреждение.
В качестве примера:
Type
Point = record
X,Y : Real
end;
Var
Origin : Point = (X:0.0; Y:0.0);
Partial : Point = (X:0.0);
Empty : Point = ();
Вышеуказанные заявления приведут к следующим предупреждениям:
iv.pp(7,27) Warning: Some fields coming after "X" were not initialized
iv.pp(8,20) Warning: Some fields coming after "" were not initialized
Порядок полей в константной записи должен быть таким же, как в объявлении типа, в противном случае возникнет ошибка компиляции.
Примечание: Следует подчеркнуть, что инициализированные переменные инициализируются, когда они попадают в область видимости, в отличие от типизированных констант, которые инициализируются при запуске программы. Это также справедливо для локальных инициализированных переменных. Локальные инициализированные переменные инициализируются всякий раз, когда вызывается процедура. Любые изменения, произошедшие при предыдущем вызове процедуры, будут отменены, поскольку они снова инициализируются.
Примечание: Следует проявлять осторожность при использовании инициализированных типов указателей, таких как PChar. В следующих примерах S — это указатель, указывающий на блок постоянных (только для чтения) данных программы. Поэтому назначение символа в строке не сработает. Назначение самого S, конечно, сработает. Первая процедура выдаст ошибку, вторая — нет:
procedure foo1;
var
s: PChar = 'PChar';
begin
s[0] := 'a';
end;
procedure foo2;
var
s: PChar;
begin
s := 'PChar';
s[0] := 'a';
end;
Некоторые переменные должны быть инициализированы, поскольку они содержат управляемые типы. Для переменных, которые объявлены в разделе var функции или в основной программе, это происходит автоматически. Для переменных, которые размещены в куче, это не обязательно так.
Для этого компилятор содержит встроенную функцию Default. Эта функция принимает дентификатор типа в качестве аргумента и вернет правильно инициализированную переменную этого типа. По сути, она обнуляет всю переменную.
Ниже приведен пример ее использования:
type
TRecord = record
i: LongInt;
s: AnsiString;
end;
var
i: LongInt;
o: TObject;
r: TRecord;
begin
i := Default(LongInt); // 0
o := Default(TObject); // Nil
r := Default(TRecord); // ( i: 0; s: '')
end.
Случай, когда переменная размещается в куче, более интересен:
type
TRecord = record
i: LongInt;
s: AnsiString;
end;
var
i: ^LongInt;
o: ^TObject;
r: ^TRecord;
begin
i:=GetMem(SizeOf(Longint));
i^ := Default(LongInt); // 0
o:=GetMem(SizeOf(TObject));
o^ := Default(TObject); // Nil
r:=GetMem(SizeOf(TRecord));
r^ := Default(TRecord); // ( i: 0; s: '')
end.
Он работает для всех типов, за исключением различных типов файлов (или сложных типов, содержащих тип файла).
Замечание:
• Для универсальных типов использование Default особенно полезно, поскольку тип переменной может быть неизвестен во время объявления универсального типа.
• Результаты функции доступны как идентификатор Result и, как таковые, напоминают переменные. Они не являются переменными, но рассматриваются как параметры, передаваемые по ссылке. Поэтому они не инициализируются.
Для программы, которая использует потоки, переменные могут быть действительно глобальными, т. е. одинаковыми для всех потоков, или локальными для потока: это означает, что каждый поток получает копию переменной. Локальные переменные (определенные внутри процедуры) всегда являются локальными для потока. Глобальные переменные обычно одинаковы для всех потоков. Глобальную переменную можно объявить локальной для потока, заменив ключевое слово var в начале блока объявления переменной на Threadvar:
Threadvar
IOResult : Integer;
Если потоки не используются, переменная ведет себя как обычная переменная. Если потоки используются, то копия создается для каждого потока (включая основной поток). Обратите внимание, что копия создается с исходным значением переменной, а не со значением переменной на момент запуска потока.
Переменные потоков следует использовать экономно: существуют накладные расходы на извлечение или установку значения переменной. Если это вообще возможно, рассмотрите возможность использования локальных переменных; они всегда быстрее, чем переменные потоков. Потоки не включены по умолчанию. Для получения дополнительной информации о потоках программирования см. главу о потоках в Programmer’s Guide.
Глобальный блок может объявлять свойства, так же как они могут быть определены в классе. Разница в том, что глобальному свойству не нужен экземпляр класса: есть только один экземпляр этого свойства. В остальном глобальное свойство ведет себя как свойство класса. Спецификаторы чтения/записи для глобального свойства также должны быть обычными процедурами, а не методами.
Концепция глобального свойства специфична для Free Pascal и не существует в Delphi. Для работы со свойствами требуется режим ObjFPC.
Концепция глобального свойства может использоваться для «скрытия» местоположения значения, для вычисления значения на лету или для проверки значений, которые записываются в свойство.
{$mode objfpc}
unit testprop;
Interface
Function GetMyInt : Integer;
Procedure SetMyInt(Value : Integer);
Property
MyProp : Integer Read GetMyInt Write SetMyInt;
Implementation
Uses sysutils;
Var
FMyInt : Integer;
Function GetMyInt : Integer;
begin
Result:=FMyInt;
end;
Procedure SetMyInt(Value : Integer);
begin
If ((Value mod 2)=1) then
Raise Exception.Create('MyProp can only contain even value');
FMyInt:=Value;
end;
end.
Спецификаторы чтения/записи можно скрыть, объявив их в другом модуле, который должен быть в разделе uses модуля. Это можно использовать для скрытия спецификаторов доступа чтения/записи для программистов, как если бы они находились в закрытом разделе класса (обсуждается ниже). Для предыдущего примера это могло бы выглядеть следующим образом:
{$mode objfpc}
unit testrw;
Interface
Function GetMyInt : Integer;
Procedure SetMyInt(Value : Integer);
Implementation
Uses sysutils;
Var
FMyInt : Integer;
Function GetMyInt : Integer;
begin
Result:=FMyInt;
end;
Procedure SetMyInt(Value : Integer);
begin
If ((Value mod 2)=1) then
Raise Exception.Create('Only even values are allowed');
FMyInt:=Value;
end;
end.
Тогда unit testprop будет выглядеть так:
{$mode objfpc}
unit testprop;
Interface
uses testrw;
Property
MyProp : Integer Read GetMyInt Write SetMyInt;
Implementation
end.
|