назад

Глава 5. Переменные и типизированные константы


Описания переменных

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

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

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

Приведем пример раздела описания переменной:

     var
       X,Y,Z: real;
       I,J,K: integer;
       Digit: 0..9;
       C: Color;
       Done,Error: boolean;
       Operator: (plus, minus, times);
       Hue1,Hue2: set of Color;
       Today: Date;
       Results: MeasureList;
       P1,P2: Person;
       Matrix: array[1..10,1..10] of Real;

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


Сегмент данных

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

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


Сегмент стека

Размер сегмента стека устанавливается с помощью директивы компилятора $M и лежит в пределах от 1024 до 65520 байт. По умолчанию размер стека равен 16384 байт.

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

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

Директива компилятора $S используется для проверок переполнения стека в программе. В состоянии {$S+}, принятом по умолчанию, генерируется код, осуществляющий проверку переполнения стека в начале каждой процедуры или функции. В состоянии {$S-} такие проверки не проводятся. Переполнение стека может вызвать аварийное завершение работы системы, поэтому не следует отменять проверки стека, если нет абсолютной уверенности в том, что переполнения не произойдет.


Абсолютные переменные

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

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

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

     CrtMode   : byte absolute $0040:$0049;

Первая константа обозначает базу сегмента, а вторая определяет смещение внутри этого сегмента. Обе константы не должны выходить за пределы диапазона от $0000 до $FFFF (от 0 до 65535).

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

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

     var
       Str: string[32];
       StrLen: byte absolute Str;

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

Эту вторую форму оператора absolute можно без опасения использовать при программировании в Windows или в защищенном режиме DOS. Память, к которой вы обращаетесь, находится в области прог- раммы.


Ссылки на переменные

Ссылка на переменную может обозначать следующее:

Синтаксис ссылки на переменную имеет вид:

Отметим, что синтаксис ссылки на переменную допускает использование выражения, вычисляющего значение ссылочного типа. Выражение должно следовать за квалификатором, разыменовывающим ссылочное значение (или индексирующим значением указателя, если с помощью директивы {$X+} разрешен расширенный синтаксис), что дает фактическую ссылку на переменную.


Квалификаторы

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

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

Results

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

Results[Current+1]

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

Results[Current+1].Data

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

Results[Current+1].Data^

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

Results[Current+1].Data^[J]


Массивы, строки и индексы

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

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

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

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

Matrix[I][J]

что тождественно записи:

Matrix[I,J]

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

Первый символ строковой переменной (индекс 0) содержит динамическую длину строки, то есть Length(S) тождественно Ord(S[0]). Если атрибуту длины присваивается значение, то компилятор не проверяет, является ли это значение меньшим описанного размера строки. Вы можете указать индекс строки и вне ее текущей динамической длины. В этом случае считываемые символы будут случайными, а присваивания вне текущей длины не повлияют на действительное значение строковой переменной.

Когда с помощью директивы компилятора {$X+} разрешен расширенный синтаксис, значение PChar может индексироваться одиночным индексным выражением типа Word. Индексное выражение задает смещение, которое нужно добавить к символу перед его разыменованием для получения ссылки на переменную типа Char.


Записи и десигнаторы полей

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

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

     Today.Year
     Results[1].Count
     Result[1].When.Month

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


Десигнаторы компонентов объекта

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

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


Переменные-указатели и динамические переменные

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

Ссылка на динамическую переменную, на которую указывает переменная-указатель, записывается в виде переменной-указателя, после которой ставится символ указателя (^).

Динамические переменные и значения их указателей создаются с помощью стандартных процедур New и GetMem. Вы можете использовать операцию @ и стандартную функцию Ptr для создания значений указателя, которые рассматриваются как указатели динамических переменных.

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

Приведем несколько примеров ссылок (указателей) на динамические переменные:

     P1^
     P1.Sibling^
     Results[1].Data^

Приведение типов переменных

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

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

Примечание: Определять допустимость приведения типа должен программист.

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

     type
       TByteRec = record
                    lo, hi: byte;
                  end;
       TWordRec = record
                    low, high: word;
                  end;
       TPtrRec = record
                   ofs, seg: word;
                 end;
       PByte = ^Byte;
     var
       B: byte;
       W: word;
       L: longint;
       P: pointer;
     begin
       W := $1234;
       B := TByteRec(W).lo;
       TByteRec(W).hi := 0;
       L := $1234567;
       W := TWordRec(L).lo;
       B := PByte(L)^;
       P := Ptr($40,$49);
       W := TPtrRec(P).seg;
       Inc(TPtrRec(P).Ofs,4);
     end.

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

Borland Pascal также полностью поддерживает приведение типов для процедурных типов. Например, имея следующие описания:

     type
        Func = function(X: Integer): Integer;
     var
        F: Func;
        P: Pointer;
        N: Integer;

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

     F := Func(P); { присвоить F значение процедурного типа в P }
     Func(P) := F; { присвоить P значение процедурного типа в F }
     @F := P;      { присвоить F значение-указатель в P }
     P := @F;      { присвоить P значение-указатель в F }
     N := F(N);    { вызвать функцию через F }
     N := Func(P)(N); { вызвать функцию через P }

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


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

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

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

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


Константы простого типа

Описание типизированной константы с простым типом означает указание значения константы:

     const
       Maximum   : integer = 9999;
       Factor    : real = -0.1;
       Breakchar : char = #3;

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

     var
        Buffer: array[0..1023] of Byte;
     const
        BufferOfs: Word = Ofs(Buffer);
        BufferSeg: Word = Seg(Buffer);

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

     const
       Min : integer = 0;
       Max : integer = 99;
     type
       Vector = array[Min..Max] of integer;

Описание Vector является недопустимым, поскольку Min и Max являются типизированными константами.


Константы строкового типа

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

     const
       Heading  : string[7] = 'Section';
       NewLine  : string[2] = #13#10;
       TrueStr  : string[5] = 'Yes';
       FalseStr : string[5] = 'No';

Константы структурного типа

Описание константы структурного типа определяет значение каждого компонента структуры. Borland Pascal поддерживает описания констант типа массив, запись, множество и указатель. Константы файлового типа и константы типа массив или запись, содержащие компоненты файлового типа, не допускаются.


Константы типа массив

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

Приведем пример константы типа массив:

     type
       Status = (Active,Passive,Waiting);
       StatusMap = array[Status] of string[7];
     const
       StatStr: StatusMap = ('Active','Passive','Waiting');

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

     StatStr[Active]  =  'Active'
     StatStr[Passive] =  'Passive'
     StatStr[Waiting] =  'Waiting'

Тип элемента константы-массива может быть любым, кроме файлового типа. Упакованные константы строкового типа (символьные массивы) могут быть определены и как одиночные символы, и как строки. Определение:

     const
     Digits:array[0..9] of
          char=('0','1','2','3','4','5','6','7','8','9');

можно представить в более удобном виде:

     const
       Digits: array[0..9] of char = '0123456789';

При разрешении расширенного синтаксиса (с помощью директивы компилятора {$X+}) массивы с нулевой базой могут инициализироваться строкой, которая короче, чем описанная длина массива, например:

     const
        FileName = array[0..79] of Char = 'TEXT.PAS';

В таких случаях оставшиеся символы устанавливаются в NULL (#0), и массив содержит строку с завершающим нулем.

Примечание: Подробнее о строках с завершающим нулем рассказывается в Главе 18.

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

     type
       Cube = array[0..1,0..1,0..1] of integer;
     const
       Maze: Cube = (((0,1),(2,3)),((4,5),(6,7)));

задает следующие начальные значения массива Maze:

     Maze[0, 0, 0] = 0
     Maze[0, 0, 1] = 1
     Maze[0, 1, 0] = 2
     Maze[0, 1, 1] = 3
     Maze[1, 0, 0] = 4
     Maze[1, 0, 1] = 5
     Maze[1, 1, 0] = 6
     Maze[1, 1, 1] = 7

Константы типа запись

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

Приведем несколько примеров констант-записей:

     type
       Point  = record
                  x,y: real;
                end;
       Vector = array[0..1] of Point;
       Month  =
           (Jan,Feb,Mar,Apr,May,Jun,Jly,Aug,Sep,Oct,Nov,Dec);
       Date   = record
                  d: 1..31; m: Month; y: 1900..1999;
                end;
     const
       Origin  : Point = (x: 0.0; y: 0.0);
       Line    : Vector = ((x: -3.1; y: 1.5),(x: 5.8; y: 3.0));
       SomeDay : Date = (d: 2; m: Dec; y: 1960);

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


Константы объектного типа

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

     const
        ZeroPoint: Point = (X: 0; Y: 0)
        ScreenRect: Rect = (A: (X: 0; Y: 0); B: (X: 80; Y: 25);
        CountField: NumField = (X: 5; Y: 20; Len: 4; Name: nil;
          Value: 0; Min: -999; Max: 999);

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


Константы множественного типа

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

Приведем несколько примеров констант-множеств:

     type
       Digits  = set of 0..9;
       Letters = set of 'A'..'Z';
     const
       EvenDigits: Digits = [0,2,4,6,8];
       Vowels    : Letters = ['A','E','I','O','U','Y'];
       HexDigits : set of '0'..'z' =
      ['0'..'9','A'..'F','a'..'f'];

Константы ссылочного типа

Описание константы ссылочного типа может содержать только значение nil (пусто). Приведем несколько примеров:

     type
        TDirection = (Left, Right, Up, Down);
        TStringPtr = ^String;
        TNodePtr = ^Node;
        TNode = record
                  Next: NodePtr;
                  Symbol: StringPtr;
                  Value: Direction;
                end;
     const
          S1: string[4] = 'DOWN';
          S2: string[2] = 'UP';
          S3: string[5] = 'RIGHT';
          S4: string[4] = 'LEFT';
          N1: Node = (Next: nil; Symbol: @S1; Value: Down);
          N2: Node = (Next: @N1; Symbol: @S2; Value: Up);
          N3: Node = (Next: @N2; Symbol: @S3; Value: Right);
          N2: Node = (Next: @N3; Symbol: @S4; Value: Left);
          DirectionTable: NodePtr = @N4;

Если разрешен расширенный синтаксис (указана директива компилятора {$X+}), типизированная константа типа PChar может инициализироваться строковой константой, например:

     const
        Message: PChar = 'Программа завершена';
        Prompt: PChar = 'Введите значения: ';
        Digits: array[0..9] of PChar = (
            'Ноль', 'Один', 'Два', 'Три', 'Четыре',
            'Пять', 'Шесть', 'Семь', 'Восемь', 'Девять');

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


Константы процедурного типа

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

Приведем следующий пример:

     type
        ErrorProc = procedure(ErrorCode: Integer);
 
     procedure DefaultError(ErrorCode: Integer); far;
     begin
          WriteLn('Error ', ErrorCode, '.');
     end;
 
     const
          ErrorHandler: ErrorProc = DefaultError;

 

Hosted by uCoz