Дополнительные материалы

Содержание

  1. Приведение типа
  2. Поразрядные операции
  3. Функция Format
  4. Forward-объявления
  5. Пример поиска элемента, удовлетворяющего условию, с функцией сравнения с параметром – динамическим массивом со значениями по умолчанию
  6. Соглашения о вызовах
  7. Реализация типа set в Паскале

1. Приведение типа

Иногда бывает полезно обрабатывать выражение так, как оно обрабатывалось бы, если бы имело другой тип. Операция приведения типа позволяет нам сделать это. Синтаксис операции приведения типа следующий:
<идентификатор типа>(<выражение>)

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

1.1. Приведение типа значения

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

Приведём примеры использования операции приведения типа значения. 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 заносится числовое представление адреса этой переменной

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

1.2. Приведение типа переменной

Используя операцию приведения типа переменной можно привести переменную к любому типу при условии, что размеры типов совпадают (в противном случае операция рассматривается как приведение типа значения). Также нельзя смешивать целые и вещественные типы – для преобразования вещественных значений в целые используются стандартные функции 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. Но операция приведения типа переменной имеет то преимущество, что она может быть использована слева от знака присваивания, т.е. для присваивания нового значения в один из байт переменной.

2. Поразрядные операции

2.1. Внутреннее представление целых чисел

Информация в компьютере представляется в двоичной системе (наличие и отсутствие напряжения). Минимальной единицей информации является бит – ноль или единица, ложь или истина, «нет» или «да». Каждый байт состоит из 8 бит. Если число знаковое, то самый левый его бит обозначает знак числа – 0 для положительных чисел и 1 для отрицательных чисел, остальные биты формируют модуль числа (это относится только к целым числам, вещественные числа всегда со знаком). Если число беззнаковое, то все биты участвуют в формировании значения, но число может быть только положительным.

Положительные целые числа в компьютере представляются в нормальном коде – это обычное представление числа в двоичной системе, а отрицательные – в дополнительном коде. Для получения дополнительного кода берется двоичное представление равного по модулю целого числа, затем все цифры двоичного представления инвертируются (0 переходит в 1, 1 – в 0), при этом получается так называемый обратный код, к которому прибавляется 1 для получения дополнительного кода. Например, нормальный код числа 207 при использовании 2 байт – 0000000011001111, а дополнительный код числа 207 – 1111111100110001 (количество цифр в числе существенно!). Если сложить два этих числа, получается 0 (с переносом 1 за старший разряд числа). При сложении различных по модулю положительного и отрицательного чисел получается число в нормальном коде, если результат больше 0, и число в дополнительном коде, если результат меньше 0.

Существуют операции, которые работают с битами – можно взять отрицание, применить операции «и» или «или». Поразрядные операции применяются к переменным целых типов и типа-диапазона. Однако нельзя применить эти операции к одному биту, а можно лишь применить одну и ту же операцию ко всем битам переменной.

К поразрядным операциям относятся унарная операция отрицания not и пять бинарных операций: and – поразрядное «и», or – поразрядное «или», xor – поразрядное «исключающее или», shl – сдвиг влево, shr – сдвиг вправо.

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

2.2. Операции «отрицание», «и», «или», «исключающее или»

Операции 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;

2.3. Операции сдвига

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

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

Система счисления Число 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

3. Функция Format

Функция Format позволяет преобразовывать в строку значения различных типов, задавая при этом некоторые характеристики строкового представления преобразуемого значения, например, количество цифр после десятичной точки, выравнивание по правому или левому краю и т.п.
function Format(const Format: string; var Args: array of TVarRec): string;

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

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

Спецификация формата имеет следующий вид:
%[индекс:][-][ширина][.точность]тип

Спецификация формата всегда начинается с символа %. Далее следуют:

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

Тип
Результат
d
Десятичное целое число. Преобразуемое значение должно быть целым числом. Значение преобразуется в строку десятичных цифр. Если спецификация формата содержит поле точность, это поле задаёт минимальное количество цифр для представления числа. Если для числа требуется меньше количество цифр, число дополняется впереди нулями.
u
Тот же что и при использовании d, но число рассматривается как беззнаковое.
x
Шестнадцатеричное целое число. Преобразуемое значение должно быть целым числом. Значение преобразуется в строку шестнадцатеричных цифр. Если спецификация формата содержит поле точность, это поле задаёт минимальное количество цифр для представления числа. Если для числа требуется меньше количество цифр, число дополняется впереди нулями.
e
Научный формат. Преобразуемое значение должно быть вещественным числом. Значение преобразуется в строку вида –d.ddd…E+ddd, где d – одна десятичная цифра. Строка начинается со знака «минус», если преобразуемое значение отрицательно. Как минимум одна цифра всегда предшествует десятичной точке. Общее количество цифр в полученной строке, включая цифры до и после десятичной точки, задаётся полем точность. Если это поле отсутствует, используется значение по умолчанию – 15 цифр. За символом Е, отделяющим мантиссу от порядка, всегда следует знак «плюс» или «минус» и как минимум три цифры.
f
Фиксированный формат. Преобразуемое значение должно быть вещественным числом. Значение преобразуется в строку вида –ddd.ddd…, где d – одна десятичная цифра. Строка начинается со знака «минус», если преобразуемое значение отрицательно. Количество цифр после десятичной точки задаётся полем точность. Если это поле отсутствует, используется значение по умолчанию – 2 цифры.
g
Обобщённый формат. Преобразуемое значение должно быть вещественным числом. Используется либо научный, либо фиксированный формат, в зависимости от того, какое представление числа требует меньшего количества знаков. Лидирующие нули удаляются, десятичная точка добавляется только при необходимости.
n
Числовой формат. Преобразуемое значение должно быть вещественным числом. Значение преобразуется в строку вида –d,ddd,ddd,ddd.ddd…, т.е. числовой формат эквивалентен фиксированному формату, но полученная строка содержит разделители групп разрядов.
m
Денежный формат. Преобразуемое значение должно быть вещественным числом. Значение преобразуется в строку, представляющую собой денежную величину. Преобразование управляется глобальными переменными CurrencyString, CurrencyFormat, NegCurrFormat, ThousandSeparator, DecimalSeparator и CurrencyDecimals.
p
Указатель. Преобразуемое значение должно быть указателем. Значение преобразуется в строку из 8 символов, представляющую собой значение указателя в шестнадцатеричной системе счисления.
s
Строка. Преобразуемое значение должно быть строкой или символом. Поле точность задаёт максимальное количество символов. Если преобразуемая строка содержит больше символов, строка укорачивается до заданного количества символов.

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

Поле ширина задаёт минимальное количество символов для преобразованного значения. Если для представления исходного значения требуется меньшее количество символов, результат расширяется пробелами. По умолчанию используется выравнивание по правому краю, соответственно, пробелы добавляются слева. Однако если используется индикатор выравнивания по левому краю, пробелы добавляются справа. 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]).

4. Forward-объявления

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

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

Целью forward-объявления является расширение области видимости идентификатора процедуры или функции таким образом, чтобы процедура или функции была доступна ранее в коде программы. Это позволит другим подпрограммам вызывать процедуру или функцию, имеющую forward-объявление, до того, как она будет определена. Forward-объявления позволяют более гибко организовывать код, и, кроме того, они бывают необходимы при использовании взаимно-рекурсивных процедур и функций.

Forward-объявления не имеют эффекта в разделе описания интерфейса модуля. Заголовки процедур и функций, включаемые в раздел описания интерфейса и так, по сути, являются forward-объявлениями, и соответствующие процедуры и функции должны быть определены в разделе реализации. procedure p1; forward; procedure p2; begin ... p1; // Без forward-объявления в данной точке невозможно было бы вызвать процедуру p1 ... end; procedure p1; begin ... p2; ... end;

5. Пример поиска элемента, удовлетворяющего условию, с функцией сравнения с параметром – динамическим массивом со значениями по умолчанию

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.

6. Соглашения о вызовах

При объявлении процедур и функций можно задать тип соглашения о вызовах, используя одну из директив 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 и поддерживаются только для обратной совместимости.

7. Реализация типа set в Паскале

В оперативной памяти множество хранится как массив элементов типа 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].


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