Copyright (C) А.Гавва V-0.4w май 2004

2. Скалярные типы данных языка Ада.

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

Предопределенный пакет Standard содержит описания стандартных типов, таких как Integer, Float, Boolean, Character и Wide_Character, а также определяет операции, которые допускается производить над этими типами.

Следующие знаки операций допустимы для всех скалярных типов:


=, /=             проверка на равенство/не равенство
<, <=, >, >=      меньше, меньше или равно, больше, больше или равно
in, not in        проверка принадлежности к диапазону

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

2.1 Введение в систему типов языка Ада

Данные - это то, что обрабатывает программа. Практически, любой современный язык программирования определяет свои соглашения и механизмы для разделения данных на разные типы. Известно, что Ада - это язык со строгой типизацией. И хотя это не единственное свойство Ады, но, пожалуй, это свойство наиболее широко известно.

Понятие типа данных Ады подразумевает, что:

  1. каждый тип данных имеет свое имя
  2. каждый тип данных имеет свое множество допустимых значений
  3. для каждого типа данных определено свое множество допустимых операций и знаков операций
  4. строгое разделение объектов одного типа данных от объектов любого другого типа данных

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

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

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

Приводимая ниже диаграмма демонстрирует общую организацию системы типов Ады.


Все типы
 |
 |- Простые типы
 |    |
 |    |- Скалярные типы
 |    |    |
 |    |    |- Вещественные (Real)
 |    |    |    |
 |    |    |    |- Универсальный Вещественный (Universal_Real)    -- все вещественные
 |    |    |    |                                                 -- литералы
 |    |    |    |
 |    |    |    |- Корневой Вещественный      (Root_Real)         -- только Ada95
 |    |    |         |
 |    |    |         |- с плавающей точкой     (Floating Point)
 |    |    |         |- с фиксированной точкой (Fixed Point)
 |    |    |              |
 |    |    |              |- с обычной фиксированной точкой
 |    |    |              |      (Ordinary Fixed Point)
 |    |    |              |
 |    |    |              |- с десятичной фиксированной точкой    -- только Ada95
 |    |    |                     (Decimal Fixed Point)
 |    |    |
 |    |    |- Дискретные типы
 |    |         |
 |    |         |- Целые типы
 |    |         |     |
 |    |         |     |- Универсальный Целый (Universal_Integer)  -- все
 |    |         |     |                                           -- целочисленные
 |    |         |     |                                           -- литералы
 |    |         |     |
 |    |         |     |- Корневой Целый (Root_Integer)            -- только Ada95
 |    |         |         |
 |    |         |         |- Знаковые Целые
 |    |         |         |- Модульные Целые (Modular Integer)    -- только Ada95
 |    |         |
 |    |         |- Перечислимые
 |    |              |
 |    |              |- Символьные   (Character, Wide_Character)
 |    |              |- Логический   (Boolean)
 |    |              |- Определяемые пользователем
 |    |
 |    |-- Ссылочные типы / указатели   (Access)
 |          |
 |          |- Ссылки на объекты
 |          |- Ссылки на подпрограммы                         -- только Ada95
 |
 |- Составные типы
      |
      |-- Массивы (Array)
      |     |
      |     |- Строки  (String)
      |     |- Другие, определяемые пользователем массивы
      |
      |-- Записи  (Record)
      |
      |-- Тэговые Записи (Tagged Record)                      -- только Ada95
      |
      |-- Задачи  (Task)
      |
      |-- Защищенные типы  (Protected)                        -- только Ada95

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

2.2 Целочисленные типы

2.2.1 Предопределенный тип Integer

Предопределенный целочисленный тип Integer описан в пакете Standard (пакет Standard не нужно указывать в инструкциях спецификации контекста with и use). Точный диапазон целочисленных значений, предоставляемых этим типом, зависит от конкретной реализации компилятора и/или оборудования. Однако, стандарт определяет минимально допустимый диапазон значений для этого типа от -(2 ** 15) до +(2 ** 15 - 1) (например, в случае 32-битных систем, таких как Windows или Linux, для реализации компилятора GNAT диапазон значений типа Integer будет от -(2 ** 31) до +(2 ** 31 - 1)).

Кроме типа Integer, в пакете Standard предопределены два его подтипа (понятие подтипа будет рассмотрено позже) Natural и Positive, которые, соответственно, описывают множества не отрицательных (натуральных) и положительных целых чисел.

2.2.2 Тип Universal_Integer

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

2.2.3 Описание целочисленных констант

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

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


Max_Width : constant := 10_000; -- 10_000 - имеет тип Universal_Integer
                                -- это не тип Integer

2.2.4 Тип Root_Integer

Модель целочисленной арифметики Ады базируется на понятии неявного типа Root_Integer. Этот тип используется как базовый тип для всех целочисленных типов Ады. Другими словами - все целочисленные типы являются производными от типа Root_Integer (см. Производные типы). Диапазон значений типа Root_Integer определяется как System.Min_Int..System.Max_Int. Все знаки арифметических операций описаны так, чтобы они могли выполняться над этим типом.

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


type X is new Integer range 0 .. 100;
type Y is range 0 .. 100;

Здесь, тип X описывается как производный от типа Integer с допустимым диапазоном значений от 0 до 100. Исходя из этого, для типа X базовым типом будет тип Integer.

Тип Y описывается как тип с допустимым диапазоном значений от 0 до 100, и при его описании не указан тип-предок. В таком случае, он будет производным от типа Root_Integer, но его базовый диапазон не обязательно должен быть таким же как у Root_Integer. В результате, некоторые системы могут размещать экземпляры объектов такого типа и его базового типа в одном байте. Другими словами, определение размера распределяемого места под объекты такого типа возлагается на компилятор.

2.2.5 Примеры целочисленных описаний

Ниже приводятся примеры различных целочисленных описаний Ады.


-- описания целочисленных статических переменных

Count           : Integer;
X, Y, Z         : Integer;
Amount          : Integer := 0;


-- описания целочисленных констант (иначе - именованных чисел)

Unity           : constant Integer := 1;
Speed_Of_Light  : constant := 300_000; -- тип Universal_Integer
A_Month         : Integer range 1..12;


-- описания целочисленных типов и подтипов
-- ( см. разделы "Подтипы" и "Производные типы" )

subtype Months is Integer range 1..12;        -- огранниченный тип Integer
-- подтипы - совместимы с их базовым типом (здесь - Integer)
-- например, переменная типа Month может быть "смешана" с переменными
-- типа Integer

type File_Id is new Integer; -- новый целочисленный тип, производный
                             -- от типа Integer

type Result_Range is new Integer range 1..20_000;
-- производный тип с объявлением ограничения

type Other_Result_Range is range 1..100_000;
-- тип производный от Root_Integer
-- при этом, компилятор будет выбирать подходящий размер целочисленного значения
-- для удовлетворения требований задаваемого диапазона

2.2.6 Предопределенные знаки операций для целочисленных типов

Следующие знаки операций предопределены для каждого целочисленного типа:


+, -               унарные плюс и минус
+, -, *, /         сложить, вычесть, умножить и разделить
**                 возведение в степень (только целые значения степени)
mod                модуль
rem                остаток
abs                абсолютное значение

2.2.7 Модульные типы

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

Стандарт Ada95 разделяет целочисленные типы на целые числа со знаком и модульные типы. По существу, модульные типы являются целыми числами без знака. Характерной особенностью таких типов является свойство цикличности арифметических операций. Таким образом, модульные типы соответствуют целочисленным беззнаковым типам в других языках программирования (например: Byte, Word... - в реализациях Паскаля; unsigned_short, unsigned... - в C/C++).

В качестве простого примера рассмотрим следующий фрагмент кода:


    . . .
type  Byte  is mod 2 ** 8;    -- (2 ** 8) = 256
    Count : Byte := 255;
begin
    Count := Count + 1;
    . . .

Здесь не производится генерация ошибки в результате выполнения сложения. Вместо этого, переменная Count, после выполнения сложения, будет содержать 0.

Кроме этого, с модульными типами удобно использовать знаки битовых операций "and", "or", "xor" и "not". Такие операции трактуют значения модульного типа как битовый шаблон. Например:


type  Byte  is mod 2 ** 8;    -- (2 ** 8) = 256
    Some_Var_1  : Byte;
    Some_Var_2  : Byte;
    Mask        : constant := 16#0F#
begin
    . . .
    Some_Var_2 := Some_Var_1 and Mask;
    . . .

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

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

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


type  Unsigned_Byte  is mod 2 ** 8;    -- (2 ** 8) = 256
type  Signed_Byte    is range -128 .. +127;

U : Unsigned_Byte := 150;
S : Signed_Byte   := Signed_Byte(U);  -- здесь будет сгенерировано исключение
                                      --   Constraint_Error

Этот код будет вызывать генерацию исключения Constraint_Error.

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

2.2.8 Дополнительные целочисленные типы системы компилятора GNAT

Стандарт языка Ада допускает определять в реализации Ада-системы собственные дополнительные целочисленные типы. Таким образом, в пакете Standard системы компилятора GNAT определены дополнительные целочисленные типы:


type   Short_Short_Integer is range -(2 ** 7) .. +(2 ** 7 - 1);
type   Short_Integer       is range -(2 ** 15) .. +(2 ** 15 - 1);
type   Long_Integer        is range -(2 ** 31) .. +(2 ** 31 - 1);
type   Long_Long_Integer   is range -(2 ** 63) .. +(2 ** 63 - 1);

Кроме этого, стандарт требует наличия определения дополнительных 8-, 16-, 32- и 64-битных целочисленных типов в пакете Interfaces:


type   Integer_8     is range   -2 **  7 .. 2 **  7 - 1;
type   Integer_16    is range   -2 ** 15 .. 2 ** 15 - 1;
type   Integer_32    is range   -2 ** 31 .. 2 ** 31 - 1;
type   Integer_64    is range   -2 ** 63 .. 2 ** 63 - 1;

type   Unsigned_8    is mod   2 **  8;
type   Unsigned_16   is mod   2 ** 16;
type   Unsigned_32   is mod   2 ** 32;
type   Unsigned_64   is mod   2 ** 64;

2.3 Вещественные типы

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

2.3.1 Вещественные типы с плавающей точкой, тип Float

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

Пакет Standard предоставляет предопределенный вещественный тип с плавающей точкой Float, который обеспечивает точность в шесть десятичных цифр после запятой:


type  Float  is digits 6 range -16#0.FFFF_FF#E+32 .. 16#0.FFFF_FF#E+32;
                         --    -3.40282E+38 ..  3.40282E+38

В пакете Standard компилятора GNAT, для 32-битных систем Linux и Windows, дополнительно представлены еще несколько вещественных типов с плавающей точкой (фактические значения констант для различных платформ отличаются):


type  Short_Float     is digits 6
  range -16#0.FFFF_FF#E+32 .. 16#0.FFFF_FF#E+32;
  --    -3.40282E+38 ..  3.40282E+38

type  Long_Float      is digits 15
  range -16#0.FFFF_FFFF_FFFF_F8#E+256 .. 16#0.FFFF_FFFF_FFFF_F8#E+256;
  --    -1.79769313486232E+308 ..  1.79769313486232E+308

type  Long_Long_Float is digits 18
  range -16#0.FFFF_FFFF_FFFF_FFFF#E+4096 .. 16#0.FFFF_FFFF_FFFF_FFFF#E+4096;
  --    -1.18973149535723177E+4932 ..  1.18973149535723177E+4932

Ниже следуют примеры описаний вещественных величин с плавающей точкой.


X         : Float;
A, B, C   : Float;
Pi        : constant Float := 3.14_2;
Avogadro  : constant := 6.027E23;        -- тип Universal_Float

subtype Temperatures is Float range 0.0..100.0;
type Result is new Float range 0.0..20_000.0;

type Velocity is new Float;
type Height is new Float;
-- нельзя случайно смешивать Velocity и Height
-- без явного преобразования типов.

type Time is digits 6 range 0.0..10_000.0;
-- в этом диапазоне требуемая точность - шесть десятичных цифр
-- после точки

type Degrees is digits 2 range -20.00..100.00;
-- требуемая точность - две десятичных цифры после точки

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


+, -, *, /
**                 возведение в степень (только целые значения степени)
abs                абсолютное значение

2.3.2 Вещественные типы с фиксированной точкой, тип Duration

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

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


type Duration is delta 0.000000001
     range -((2 ** 63 - 1) * 0.000000001) ..
           +((2 ** 63 - 1) * 0.000000001);

Ниже следуют примеры описаний вещественных типов с фиксированной точкой.


type Volt is delta 0.125 range 0.0 .. 255.0;

type Fraction is delta System.Fine_Delta range -1.0..1.0;        -- Ada95
        -- Fraction'Last = 1.0 - System.Fine_Delta

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

2.3.3 Вещественные типы с десятичной фиксированной точкой

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

Примером описания вещественного типа с десятичной фиксированной точкой может служить следующее:


type Money is delta 0.01 digits 15; -- десятичная фиксированная точка,
                                    -- здесь величина, задаваемая в delta,
                                    -- должна быть степенью 10
subtype Salary is Money digits 10;  

2.3.4 Типы Universal_Float и Root_Real

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

Модель вещественной арифметики Ады основывается на анонимном типе Root_Real. Этот анонимный тип используется как базовый тип для всех вещественных типов. Тип Root_Real имеет точность, которая определяется значением константы Max_Base_Digits пакета System (System.Max_Base_Digits). Такой подход использован для облегчения переносимости программ.

2.3.5 Пакеты для численной обработки

Полное обсуждение поддержки численной обработки в Аде - весьма обширная тема. Поэтому здесь, чтобы указать "куда бежать дальше", мы только приведем список пакетов для численной обработки, которые предоставляются поставкой компиляора GNAT:


Ada.Numerics
Ada.Numerics.Aux
Ada.Numerics.Float_Random
Ada.Numerics.Discrete_Random

Ada.Numerics.Complex_Types
Ada.Numerics.Complex_Elementary_Functions
Ada.Numerics.Elementary_Functions

Ada.Numerics.Generic_Complex_Types
Ada.Numerics.Generic_Complex_Elementary_Functions
Ada.Numerics.Generic_Elementary_Functions

Ada.Numerics.Long_Complex_Types
Ada.Numerics.Long_Complex_Elementary_Functions
Ada.Numerics.Long_Elementary_Functions

Ada.Numerics.Long_Long_Complex_Types
Ada.Numerics.Long_Long_Complex_Elementary_Functions
Ada.Numerics.Long_Long_Elementary_Functions

Ada.Numerics.Short_Complex_Types
Ada.Numerics.Short_Complex_Elementary_Functions
Ada.Numerics.Short_Elementary_Functions

Следует заметить, что пакет Ada.Numerics.Aux, который указан выше, не предназначен для непосредственного использования в программах пользователя, и упоминается только с целью полноты показанного выше списка.

2.4 Преобразование численных типов

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

Ада позволяет явно указывать необходимость преобразования значения типа Float в значение типа Integer, и наоборот. Для выполнения такого преобразования используется синтаксис подобный синтаксису вызова функции:


X : Integer := 4;
Y : Float;

Y := Float(X);

  . . .

X := Integer(Y);

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

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

Значение Float Округленное значение Integer
1.5 2
1.3 1
-1.5 -2
-1.3 -1

2.5 Перечислимые типы

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

2.5.1 Описание перечислимого типа

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


type Computer_Language is (Assembler, Cobol, Lisp, Pascal, Ada);
type C_Letter_Languages is (Cobol, C);

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


A_Language      : Computer_Language;
Early_Language  : Computer_Language := Cobol;
First_Language  : constant Computer_Language := Assembler;
Example         : C_Letter_Language := Cobol;

Необходимо заметить, что порядок перечисления значений типа, при описании перечислимого типа, имеет самостоятельное значение - он устанавливает отношение порядка следования значений перечислимого типа, которое используется атрибутами 'First, 'Last, 'Pos, 'Val, 'Pred, 'Succ (см "Атрибуты типов"), а также при сравнении величин перечислимого типа. Так, для типа Computer_Language, описанного в примере выше, значение Assembler будет меньше чем значение Cobol.

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

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


type Primary is (Red, Green, Blue);
type Rainbow is (Red, Yellow, Green, Blue, Violet);
...
for I in Red..Blue loop ...      -- это двусмысленно

Здесь, компилятор не может самостоятельно определить к какому из двух типов (Primary или Rainbow) принадлежит диапазон значений Red..Blue переменной цикла I (литералы Red и Blue - совмещены). Поэтому, в подобных случаях, нам необходимо точно указывать требуемый тип, используя квалификацию типа:


for I in Rainbow'(Red)..Rainbow'(Blue) loop ...
for I in Rainbow'(Red)..Blue loop ...     -- необходима только одна квалификация
for I in Primary'(Red)..Blue loop ...

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

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

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

2.5.2 Предопределенный логический тип Boolean

В Аде, предопределенный логический тип Boolean описывается как перечислимый тип в пакете Standard:


type  Boolean  is  (False, True);

Таким образом, переменные логического типа Boolean могут принимать только одно из двух значений: True (истина) или False (ложь).

Примечательно, что предопределеннный логический тип Boolean имеет специальное предназначение. Значения этого типа используются в условных инструкциях языка Ада ("if ... ", "exit when ... ", ...). Это подразумевает, что если вы пытаетесь описать свой собственный логический тип Boolean, и описываете его точно также как и предопределенный тип Boolean (полное имя предопределенного логического типа - Standard.Boolean), то вы получаете абсолютно самостоятельный тип(!). В результате, вы не можете использовать значения, описанного вами типа Boolean, в условных инструкциях языка Ада, которые ожидают только тип Standard.Boolean.

Значения предопределенного логического типа Standard.Boolean возвращают знаки операций сравнения:


=     -- равно
/=    -- не равно
<     -- меньше
<=    -- меньше или равно
>     -- больше
>=    -- больше или равно

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


and         -- логическое И:    вычисляются и левая, и правая часть выражения
and then    -- логическое И:    правая часть выражения вычисляется, если
            --                  результат вычисления левой части - True

or          -- логическое ИЛИ:  вычисляются и левая, и правая часть выражения
or else     -- логическое ИЛИ:  правая часть выражения вычисляется, если
            --                  результат вычисления левой части - False

xor         -- исключающее ИЛИ
not         -- отрицание (инверсия); унарная операция

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


(B /= 0) and (A/B > 0)

в случае, когда значение B равно нулю будет возникать ошибка деления на ноль. Причина в том, что значение части выражения, расположенной справа от "and", вычисляется всегда, не зависимо от значения результата полученного при вычислении (B /= 0).

Чтобы избежать подобных ошибок, а также в целях увеличения производительности, необходимо производить вычисление значений логических переменных в определенной последовательности и прекращать ее как только результат всего выражения уже определен. Для этого можно использовать "and then" вместо "and", и "or else" вместо "else", указывая порядок обработки логического выражения явно:


(B /= 0) and then (A/B > 0)

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

Можно переписать предыдущий пример с использованием "or else". Тогда, обработка логического выражения будет завершена в случае если значение слева от "or else" вычислено как True:


(B = 0) or else (A/B <= 0)

В заключение обсуждения логического типа отметим, что Ада не позволяет одновременное использование "and" ("and" или "and then"), "or" ("or" или "or else") и "xor" в одном выражении не разделенном скобками. Это уменьшает вероятность разночтения содержимого сложного логического выражения. Например:


(A < B) and (B > C) or (D < E)    -- запрещено
((A < B) and (B > C)) or (D < E)  -- разрешено

2.5.3 Символьные типы Ады (Character, Wide_Character)

Ада имеет два предопределенных перечислимый типа, Character и Wide_Character, для поддержки обработки символьных значений. Согласно стандарта, любой перичислимый тип который содержит хотя бы один символьный литерал является символьным типом.

Оригинальный стандарт Ada83 описывал 7-битный тип Character. Еще до появления стандарта Ada95, это ограничение было ослаблено, но оставалось принудительным для старых компиляторов (например таких как компилятор Meridian Ada). Это создавало трудности при попытках отобразить графические символы на PC, поскольку для отображения символов с кодами большими чем ASCII-127 приходилось использовать целые числа. Такая поддержка обеспечивалась за счет специальных подпрограмм предоставляемых разработчиками соответствующего компилятора.

В настоящее время, предопределенный символьный тип Character предусматривает 256 различных символьных значений (то есть, является 8-битным), и основывается на стандарте ISO-8859-1 (Latin-1).

Некоторые символы не имеют непосредственно печатаемого значения (первые 32 символа). Такие символы используются в качестве управляющих (примером может служить символ CR - возврат каретки). Для обращения к таким символам можно использовать пакет ASCII, который является дочерним пакетом пакета Standard (благодаря этому, нет необходимости указывать пакет ASCII в спецификаторах контекста with и/или use). Например, для обращения к символу возврат каретки можно использовать: ASCII.CR. Однако, пакет ASCII содержит только первые 128 символов и считается устаревшим, и возможно, что в будущем он будет удален. Поэтому, вместо старого пакета ASCII рекомендуется использовать пакет Ada.Characters.Latin_1, который предоставляет 256 символов. Следовательно, используя пакет Ada.Characters.Latin_1, к символу возврата каретки можно обратиться следующим образом: Ada.Characters.Latin_1.CR.

Предопределенный символьный тип Wide_Character основывается на стандарте ISO-10646 Basic Multilingual Plane (BMP) и предусматривает 65336 различных символьных значений (использует 16 бит).

Также, Ада предоставляет пакет Ada.Characters.Handling, предлагающий набор полезных подпрограмм символьной обработки.

Система компилятора GNAT предоставляет дополнительный пакет Ada.Characters.Wide_Latin_1, который описывает символьные значения типа Wide_Character соответствующие кодировке Latin_1.

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


Standard.ASCII                -- предоставляет только первые 128 символов
                              -- (считается устаревшим)

Ada.Characters                -- 
Ada.Characters.Latin_1        -- предоставляет 256 символов ISO-8859-1 (Latin-1)
Ada.Characters.Handling       -- предоставляет подпрограммы символьной обработки

Ada.Characters.Wide_Latin_1   -- дополнительный пакет из поставки
                              -- системы компилятора GNAT

Следует заметить, что пакет ASCII считается устаревшим и его не рекомендуется использовать при написании новых программ. Вместо него необходимо использовать стандартный пакет Ada.Characters.Latin_1.

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

2.6 Типы и подтипы

Как уже говорилось, концепция типа является в Аде основопологающим фактором создания надежного программного обеспечения. Предположим, что у нас есть два целочисленных типа, которые как-то характеризуют "звоночки" и "свисточки", и мы никак не хотим смешивать эти понятия. Нам необходимо, чтобы компилятор имел возможность предупредить нас: "Ба, да вы пытаетесь смешать выши "звоночки" и "свисточки"! Что Вы в действительности подразумеваете?". Это выглядит излишними осложнениями для людей, которые используют другие языки программирования со слабой типизацией данных. Размышления над тем какие данные необходимо представить, описание различных типов данных и правил конвертирования типов требуют некоторых усилий. Однако, такие усилия оправдываются тем, что как только это сделано, компилятор сможет помочь отыскать разные глупые ошибки.

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

Общий синтаксис объявления подтипа имеет вид:


subtype Name_1 is Base_Type;  -- в данном случае, Name_1 является
                              -- синонимом типа Base_Type

subtype Name_2 is Base_Type range Lowerbound..Upperbound;

Примеры объявления подтипов приводятся ниже:


type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC);
subtype Old_Processors is Processors range M68000..i8086;
subtype New_Processors is Processors range Pentium..PowerPC;

subtype Data is Integer;
subtype Age is Data range 0..140;
subtype Temperatures is Float range -50.0..200.0;
subtype Upper_Chars is Character range 'A'..'Z';

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


My_Age  : Age;
Height  : Integer;

Height := My_Age;   -- глупо, но никогда не вызывает проблем

My_Age := Height;   -- может вызвать проблемы, когда значение типа Height
                    -- будет за пределами диапазона значений My_Age (0..140),
                    -- но при этом остается совметимым

Чтобы избежать генерацию исключительной ситуации, можно использовать проверки принадлежности диапазону ("in" и/или "not in"). Например:


I : Integer;
N : Natural;

    . . .

if  I in Natural  then
    N := I
else
    Put_Line ("I can't be assigned to N!");
end if;

    . . .

Реально, все типы Ады являются подтипами анонимных типов, рассматриваемых как их базовые типы. Поскольку базовые типы анонимны, то на них нельзя ссылаться по имени. При этом, для получения базового типа можно использовать атрибут 'Base. Например, Integer'Base - это базовый тип для Integer. Базовые типы могут иметь или могут не иметь диапазон значений больший чем их подтипы. Это имеет значение только в выражениях вида "A * B / C" которые, при вычислении промежуточных значений, используют базовый тип. То есть, результат "A * B" может выходить за пределы значений типа не приводя к генерации исключительной ситуации если общий результат вычисленного значения всего выражения будет находиться в допустимом диапазоне значений для данного типа.

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

2.7 Производные типы

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

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


type Child_Type is new Parent_Type;

Такое описание создает новый тип данных - Child_Type, при этом Parent_Type - это тип-предок для типа Child_Type, а тип Child_Type - это производный тип от типа Parent_Type.

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

Однако, в этом случае, производный тип Child_Type - это абсолютно самостоятельный и независимый тип данных. Он не совместим ни с каким другим типом, включая тип Parent_Type, от которого он был произведен, и другими типами, производными от типа Parent_Type. Это подразумевает, что при необходимости комбинирования значений типа Child_Type со значениями типа Parent_Type требуется выполнять преобразование типов. В Аде, разрешается выполнять преобразование значений производного типа в значения типа-предка и наоборот.

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


Parent  : Parent_Type;
Child   : Child_Type := Child_Type (Parent);  -- конвертирует значение Parent,
                                              -- имеющее тип Parent_Type
                                              -- в значение типа Child_Type

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


type Child_Type is new Parent_Type range Lowerbound..Upperbound;

В этом случае диапазон значений производного типа Child_Type будет ограничен значениями нижней границы диапазона (Lowerbound) и верхней границы диапазона (Upperbound).

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

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

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

Например, класс дискретных типов предусматривает атрибут 'First, который наследуется всеми дискретными типами. Класс целочисленных типов добавляет к унаследованным от класса дискретных типов операциям знак операции арифметического сложения "+". Эти механизмы более полно рассматриваются при обсуждении тэговых типов.

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


type Employee_No is new Integer;
type Account_No  is new Integer range 0..999_999;

Здесь Employee_No и Account_No различные и не смешиваемые типы, которые нельзя комбинировать между собой без явного использования преобразования типов. Производные типы наследуют все операции объявленные для базового типа. Например, если была объявлена запись которая имела процедуры Push и Pop, то производный тип автоматически унаследует эти процедуры.

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

Это определяется использованием производных типов, без типа-предка.


type Name is range <некоторый_диапазон_значений>;

Например,


type Data is range 0..2_000_000;

В этом случае ответственность, за выбор подходящего размера для целого, ложится на компилятор. Таким образом, для PC, этот размер может быть 32 бита, что эквивалентно типу Long_Integer, а для рабочей станции Unix, этот размер можт остаться равным 32-битному целому, но это будет эквивалентно типу Integer. Такое предоставление компилятору возможности выбирать освобождает программиста от обязанности выбирать. Поэтому перекомпиляция программы на другой машине не требует изменения исходного текста.

2.8 Атрибуты

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

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


имя_типа'имя_атрибута
имя_экземпляра_объекта'имя_атрибута

Некоторыми из атрибутов для дискретных типов являются:


type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC);

Integer'First             -- наименьшее целое Integer
Integer'Last              -- наибольшее целое Integer
Processors'Succ(M68000)   -- последующее за M68000 в типе
Upper_Chars'Pred('C')     -- предшествующее перед 'C' в типе ('B')
Integer'Image(67)         -- строка " 67"
                          -- пробел для '-'
Integer'Value("67")       -- целое значение 67
Processors'Pos(M68030)    -- позиция M68030 в типе
                          -- (3, первая позиция - 0)

Простым примером использования атрибутов, - для улучшения переносимости программы, - может служить следующий пример описания подтипа Positive, предоставляющего положительные целые числа:


subtype Positive is Integer range 1..Integer'Last;

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

В Ada83 для не дискретных типов, - таких как Float, Fixed, всех их подтипов и производных от них типов, - концепции 'Pred и 'Succ - не имеют смысла. В Ada95 - они присутствуют. Все другие атрибуты скалярных типов также справедливы и для вещественных типов.


Copyright (C) А.Гавва V-0.4w май 2004