Иногда бывает полезно обрабатывать выражение так, как оно обрабатывалось бы, если бы имело другой тип. Операция приведения типа позволяет нам сделать это. Синтаксис операции приведения типа следующий:
<идентификатор типа>(<выражение>)
Если выражение является переменной, то операция называется приведением типа переменной, в противном случае операция называется приведением типа значения. Несмотря на то, что операции имеют одинаковый синтаксис, правила их использования различны.
При приведении типа значения идентификатор типа и тип выражения должны быть порядковыми типами или указателями. Результат получается путём преобразования значения в скобках, при этом может производиться расширение или усечение значения, если размеры исходного и нового типа отличаются друг от друга. Знак выражения всегда сохраняется.
Приведём примеры использования операции приведения типа значения.
var
i: integer;
b: byte;
c: char;
f: boolean;
...
i := integer('r'); // Переменная i получает значение 114
c := char(48); // Переменная c получает значение '0'
f := boolean(0); // Переменная f получает значение false
i := integer(c); // Переменная i получает значение 48
c := char(i + 2); // Переменная c получает значение '2'
i := integer(@i); // В переменную i заносится числовое представление адреса этой переменной
Операция приведения типа значения не может использоваться слева от знака присваивания, и за ней не может следовать квалификатор.
Используя операцию приведения типа переменной можно привести переменную к любому типу при условии, что размеры типов совпадают (в противном случае операция рассматривается как приведение типа значения). Также нельзя смешивать целые и вещественные типы – для преобразования вещественных значений в целые используются стандартные функции Int, Trunc, Round, Floor и Ceil. Операция приведения типа переменной может использоваться как справа, так и слева от знака присваивания.
var
c: char = '?';
b: byte;
...
b := byte(c); // Размеры типов совпадают, переменная b получает значение 63
byte(c) := 38; // Переменная c получает значение '&'. При этом переменная c временно рассматривается
// как переменная, имеющая тип byte для того, чтобы переменной можно было присвоить значение другого типа.
c := char(38); // Этот оператор делает то же, что и предыдущий
После операции приведения типа переменной может использоваться квалификатор, как это показано в следующем примере.
type
TByteRec = record
Lo, Hi: byte;
end;
var
b: byte;
w: word;
...
w := $1234; // Присваиваем переменной w значение, записанное в 16-ричной системе счисления
b := TByteRec(w).Lo; // Переменной b присваиваем значение младшего байта переменной w
TByteRec(w).Hi := 0; // В старший байт переменной w записываем значение 0
В принципе, старший и младший байт переменной размером 2 байта можно получить с помощью стандартных функций Hi и Lo. Но операция приведения типа переменной имеет то преимущество, что она может быть использована слева от знака присваивания, т.е. для присваивания нового значения в один из байт переменной.
Информация в компьютере представляется в двоичной системе (наличие и отсутствие напряжения). Минимальной единицей информации является бит – ноль или единица, ложь или истина, «нет» или «да». Каждый байт состоит из 8 бит. Если число знаковое, то самый левый его бит обозначает знак числа – 0 для положительных чисел и 1 для отрицательных чисел, остальные биты формируют модуль числа (это относится только к целым числам, вещественные числа всегда со знаком). Если число беззнаковое, то все биты участвуют в формировании значения, но число может быть только положительным.
Положительные целые числа в компьютере представляются в нормальном коде – это обычное представление числа в двоичной системе, а отрицательные – в дополнительном коде. Для получения дополнительного кода берется двоичное представление равного по модулю целого числа, затем все цифры двоичного представления инвертируются (0 переходит в 1, 1 – в 0), при этом получается так называемый обратный код, к которому прибавляется 1 для получения дополнительного кода. Например, нормальный код числа 207 при использовании 2 байт – 0000000011001111, а дополнительный код числа 207 – 1111111100110001 (количество цифр в числе существенно!). Если сложить два этих числа, получается 0 (с переносом 1 за старший разряд числа). При сложении различных по модулю положительного и отрицательного чисел получается число в нормальном коде, если результат больше 0, и число в дополнительном коде, если результат меньше 0.
Существуют операции, которые работают с битами – можно взять отрицание, применить операции «и» или «или». Поразрядные операции применяются к переменным целых типов и типа-диапазона. Однако нельзя применить эти операции к одному биту, а можно лишь применить одну и ту же операцию ко всем битам переменной.
К поразрядным операциям относятся унарная операция отрицания not и пять бинарных операций: and – поразрядное «и», or – поразрядное «или», xor – поразрядное «исключающее или», shl – сдвиг влево, shr – сдвиг вправо.
Несколько поразрядных операций совпадают с логическими операциями. Различение логических и поразрядных операций осуществляется по типу операндов.
Операции not, and, or и xor применяют соответствующую логическую операцию ко всем битам своих операндов.
Система счисления | А | В | not А | А and В | А or В | А xor В |
---|---|---|---|---|---|---|
Десятичная | 15 | 85 | 240/-16 | 5 | 95 | 90 |
Двоичная | 00001111 | 01010101 | 11110000 | 00000101 | 01011111 | 01011010 |
Шестнадцатеричная | 0f | 55 | f0 | 05 | 5f | 5a |
var
a, b: byte;
x, y: integer;
...
a := a and b;
a := a or b;
x := x xor y;
x := x or y;
Операции сдвига вправо и сдвига влево сдвигают биты в переменной на заданное количество позиций. Существует три разновидности сдвига:
В языке Ассемблер существуют все три разновидности сдвига, в языке Паскаль, однако, существует только одна операция сдвига влево и одна операция сдвига вправо. При сдвиге влево разницы между арифметическим и логическим сдвигом нет. При сдвиге вправо, если переменная имеет беззнаковый тип, выполняется логический сдвиг, если же переменная имеет знаковый тип, выполняется арифметический сдвиг.
Система счисления | Число | byte | shortint | ||
---|---|---|---|---|---|
shl | shr | shl | shr | ||
Десятичная | 189/-67 | 122 | 94 | 122 | -34 |
Двоичная | 10111101 | 01111010 | 01011110 | 01111010 | 11011110 |
Шестнадцатеричная | bd | 7a | 5e | 7a | de |
var
b: byte;
i: integer;
...
b := b shl 2; // Сдвиг влево на 2 позиции, эквивалентен умножению на 4
b := b shr 3; // Логический сдвиг вправо, эквивалентен делению на 8
i := i shr 7; // Арифметический сдвиг вправо, эквивалентен делению на 128
Функция Format позволяет преобразовывать в строку значения различных типов, задавая при этом некоторые характеристики строкового представления преобразуемого значения, например, количество цифр после десятичной точки, выравнивание по правому или левому краю и т.п.
function Format(const Format: string; var Args: array of TVarRec): string;
Первый параметр функции Format представляет собой строку описаний форматов преобразуемых значений, а второй – массив преобразуемых значений.
Строка описаний форматов может содержать обычные символы, которые без изменений копируются в строку, являющуюся результатом, а также спецификации формата, которые задают способ преобразования элементов массива преобразуемых значений.
Спецификация формата имеет следующий вид:
%[индекс:][-][ширина][.точность]тип
Спецификация формата всегда начинается с символа %. Далее следуют:
Следующая таблица описывает возможные значения поля тип и соответствующий результат преобразования.
Значение поля тип может задаваться как строчными, так и прописными буквами – результат будет одним и тем же.
Поле ширина задаёт минимальное количество символов для преобразованного значения. Если для представления исходного значения требуется меньшее количество символов, результат расширяется пробелами. По умолчанию используется выравнивание по правому краю, соответственно, пробелы добавляются слева. Однако если используется индикатор выравнивания по левому краю, пробелы добавляются справа.
var
s: string;
b: byte = 247;
i: integer = -29;
r: real = 1234567.8905;
c: char = '!';
t: string = 'Sono russa';
begin
s := Format('%d', [b]); // Переменная s получит значение '247'
s := Format('%u', [b]); // Переменная s получит значение '247'
s := Format('%x', [b]); // Переменная s получит значение 'F7'
s := Format('%5d', [b]); // Переменная s получит значение ' 247'
s := Format('%.5d', [b]); // Переменная s получит значение '00247'
s := Format('%7.5d', [b]); // Переменная s получит значение ' 00247'
s := Format('%-7.5d', [b]); // Переменная s получит значение '00247 '
s := Format('%d', [i]); // Переменная s получит значение '-29'
s := Format('%u', [i]); // Переменная s получит значение '4294967267'
s := Format('%x', [i]); // Переменная s получит значение 'FFFFFFE3'
s := Format('%e', [r]); // Переменная s получит значение '1.23456789050000E+006'
s := Format('%f', [r]); // Переменная s получит значение '1234567.89'
s := Format('%g', [r]); // Переменная s получит значение '1234567.8905'
s := Format('%n', [r]); // Переменная s получит значение '1 234 567.89'
s := Format('%m', [r]); // Переменная s получит значение '1 234 567.89р.'
s := Format('%.5n', [r]); // Переменная s получит значение '1 234 567.89050'
s := Format('%p', [@i]); // Переменная s получит значение '0040A984'
s := Format('%s', [c]); // Переменная s получит значение '!'
s := Format('%-3s', [c]); // Переменная s получит значение '! '
s := Format('%s', [t]); // Переменная s получит значение 'Sono russa'
s := Format('%.4s', [t]); // Переменная s получит значение 'Sono'
end.
Поля индекс, ширина и точность могут быть заданы непосредственно с помощью десятичной константы, например, '%10d', или косвенно с помощью символа *. При использовании звёздочки соответствующий элемент массива преобразуемых значений, который должен быть целым числом, подставляется в качестве значения поля, заданного символом *. Таким образом, вызов Format('%*.*f', [8, 3, r]) эквивалентен вызову Format('%8.3f', [r]).
Поле индекс устанавливает индекс элемента массива преобразуемых значений в заданное значение. Индексы массива начинаются с 0. Использование поля индекс позволяет вывести одно и то же значение несколько раз. Например, вызов Format('%d %0:x', [12]) приведёт к формированию строки '12 С'. Без использования поля индекс вызов функции Format приведёт к возникновению ошибки времени выполнения программы из-за несоответствия количества спецификаций формата и количества элементов массива преобразуемых значений.
Изменение индекса преобразуемого значения влияет на всю последовательность преобразования. Т.е. вызов Format('%d %d %0:d %d', [1, 2, 3]) приведёт к формированию строки '1 2 1 2', а не строки '1 2 1 3'. Для получения такого результата надо снова использовать индекс для изменения последовательности преобразования: Format('%d %d %0:d %2:d', [1, 2, 3]).
Директива forward заменяет блок, являющийся телом процедуры или функции, включая объявления и операторы. Где-то после forward-объявления подпрограмма должна быть дано определение подпрограммы, включающее блок.
Обычно определение подпрограммы может не включать список параметров и тип возвращаемого значения, однако, если определение повторяет список параметров и тип возвращаемого значения, то они должны точно соответствовать forward-объявлению – могут быть опущены только значения параметров по умолчанию. Если же forward-объявление объявляет совместно используемую процедуру или функцию, определение должно включать список параметров и тип возвращаемого значения.
Целью forward-объявления является расширение области видимости идентификатора процедуры или функции таким образом, чтобы процедура или функции была доступна ранее в коде программы. Это позволит другим подпрограммам вызывать процедуру или функцию, имеющую forward-объявление, до того, как она будет определена. Forward-объявления позволяют более гибко организовывать код, и, кроме того, они бывают необходимы при использовании взаимно-рекурсивных процедур и функций.
Forward-объявления не имеют эффекта в разделе описания интерфейса модуля. Заголовки процедур и функций, включаемые в раздел описания интерфейса и так, по сути, являются forward-объявлениями, и соответствующие процедуры и функции должны быть определены в разделе реализации.
procedure p1; forward;
procedure p2;
begin
...
p1; // Без forward-объявления в данной точке невозможно было бы вызвать процедуру p1
...
end;
procedure p1;
begin
...
p2;
...
end;
program Lesson15_2v3;
{$APPTYPE CONSOLE}
{$I-}
uses
SysUtils;
const
nmax = 100;
type
DynArray = array of integer;
IntArray = array [1..nmax] of integer;
func = function(x: integer; p: DynArray = nil): boolean;
var
a: IntArray;
n, m, k, s: integer;
v: integer = 5;
p: DynArray;
f: TextFile;
{ Процедура ввода одномерного массива из файла }
procedure Get(var x: mas; var n: integer; var f: TextFile);
var
i: integer;
begin
readln(f, n);
for i := 1 to n do
read(f, x[i]);
readln(f);
end;
{ Процедура вывода одномерного массива в файл }
procedure Put(const x: mas; n: integer; name: string; var f: TextFile);
var
i: integer;
begin
writeln(f, 'The array ', name, ' of ', n:2, ' elements');
for i := 1 to n do
write(f, x[i]:8);
writeln(f); writeln(f);
end;
function Negative(x: integer; p: DynArray = nil): boolean;
begin
result := x < 0;
end;
function Multiple(x: integer; p: DynArray = nil): boolean;
begin
if p = nil then
result := false
else
result := x mod p[0] = 0;
end;
function InRange(x: integer; p: DynArray = nil): boolean;
begin
if (p = nil) or (Length(p) < 3) then
result := false
else
result := (p[0] <= x) and (x <= p[1]) and (x <> p[2]);
end;
{ Функция поиска номера элемента, удовлетворяющего условию, в одномерном массиве }
function Search(const x: IntArray; n: integer; f: func; p: DynArray = nil): integer;
var
i: integer;
begin
result := 0;
for i := 1 to n do
if f(x[i], p) then
begin
result := i;
break;
end;
end;
begin
SetLength(p, 3);
p[0] := 0; p[1] := 10; p[2] := 7;
{ Ввод исходных данных }
if ParamCount < 2 then
begin
writeln('There are no enough parameters');
readln;
exit;
end;
AssignFile(f, ParamStr(1));
Reset(f);
if IOResult <> 0 then
begin
writeln('It is not possible to open file ''', ParamStr(1), ''' for reading');
readln;
exit;
end;
Get(a, n, f);
CloseFile(f);
{ Поиск элементов }
m := Search(a, n, Negative);
k := Search(a, n, Multiple, @v);
s := Search(a, n, InRange, p);
{ Вывод полученных результатов }
AssignFile(f, ParamStr(2));
Rewrite(f);
if IOResult <> 0 then
begin
writeln('It is not possible to open file ''', ParamStr(2), ''' for writing');
writeln('Output is made to the screen. Press ENTER');
readln;
AssignFile(f, '');
Rewrite(f);
end;
Put(a, n, 'A', f);
if m = 0 then
writeln(f, 'There is no negative element in the array A')
else
writeln(f, 'The first negative element in the array A has the number ', m:2);
if k = 0 then
writeln(f, 'There is no element multiple of ', v, ' in the array A')
else
writeln(f, 'The first element multiple of ', v, ' in the array A has the number ', k:2);
if s = 0 then
writeln(f, 'There is no element that lies in the range [', p[0], ', ', p[1], ']'#13#10' but not equal ', p[2], ' in the array A')
else
writeln(f, 'The first element that lies in the range [', p[0], ', ', p[1], ']'#13#10' but not equal ', p[2], ' in the array A has the number ', s:2);
CloseFile(f);
end.
При объявлении процедур и функций можно задать тип соглашения о вызовах, используя одну из директив register, pascal, cdecl, stdcall и safecall. Например:
function MyFunction(x: real): real; cdecl;
Соглашения о вызовах определяют порядок передачи параметров в подпрограмму. Они также влияют на удаление параметров из стека*, использование регистров при передаче параметров, обработку ошибок и исключений. По умолчанию подпрограммы в языке Паскаль компилируются с использованием соглашения о вызовах register.
Директива | Порядок передачи параметров | Очистка стека | Использование регистров |
---|---|---|---|
register | Слева направо | Подпрограммой | Да |
pascal | Слева направо | Подпрограммой | Нет |
cdecl | Справа налево | Вызывающей программой | Нет |
stdcall | Справа налево | Подпрограммой | Нет |
safecall | Справа налево | Подпрограммой | Нет |
Используемое по умолчанию соглашение о вызовах register является наиболее эффективным, т.к. во многих случаях позволяет избежать создания стекового фрейма для подпрограммы. Соглашение о вызовах cdecl бывает полезно при импорте внешних подпрограмм из динамических библиотек, написанных на языке C++. Соглашения о вызовах stdcall и safecall обычно используются для внешних подпрограмм. Соглашение о вызовах pascal поддерживается для обратной совместимости.
Директивы near, far и export описывают соглашения о вызовах, использовавшихся в 16-битных приложениях. Они не имеют эффекта на платформе Win32 и поддерживаются только для обратной совместимости.
В оперативной памяти множество хранится как массив элементов типа byte, при этом каждому элементу множества сопоставляется один бит. Если элемент не входит в множество, соответствующий бит равен 0. Когда элемент добавляется в множество, соответствующий бит устанавливается в 1.
Число байтов, выделяемых для хранения множества, вычисляется по формуле (max div 8) – (min div 8) + 1, где max и min – верхняя и нижняя границы базового типа множества.
Номер байта для конкретного элемента e вычисляется по формуле (e div 8) – (min div 8), а номер бита внутри этого байта по формуле e mod 8.
Такая организация множества определяет его свойства – порядок элементов не существенен (т.к. бит, соответствующий конкретному элементу, определяется значением этого элемента, а не порядком добавления элементов в множество), и нельзя дважды добавить в множество один и тот же элемент (поскольку каждому элементу соответствует только один бит, и в этот бит нельзя записать значения 2, 3 и т.п.).
Операции над множествами реализуются через поразрядные операции (см. «Поразрядные операции»). Для проверки вхождения элемента в множество необходимо число 1 (которое содержит только один бит, равный 1) сдвинуть влево на e mod 8 позиций и применить операцию and к получившемуся значению и байту с номером (e div 8) – (min div 8). Если результатом будет 0, значит, элемент не входит в множество, в противном случае – входит.
Для реализации операции пересечения множеств для всех элементов массивов, используемых для представления множеств, вычисляется выражение a[i] and b[i], где a[i] – элемент массива, хранящего первое множество, а b[i] – элемент массива, хранящего второе множество. Для реализации операции объединения множеств вычисляется выражение a[i] or b[i], а для реализации операции разности множеств – выражение a[i] and not b[i].
Для проверки того, что множество a является подмножеством множества b, для всех элементов массивов, используемых для представления множеств, вычисляется выражение a[i] and not b[i]. Если для всех i выражение равно 0, то множество a действительно является подмножеством множества b. Для проверки того, что множество a является надмножеством множества b, вычисляется выражение not a[i] and b[i].
* Параметры в подпрограммы обычно передаются через специальную область в оперативной памяти, называемую стеком.