Ссылочные типы для динамической памяти
Ссылочные типы для динамической памяти
Ссылочные типы для динамической памяти известны со времен стандарта Ada-83 и благополучно поддерживаются в стандарте Ada-9Они могут быть использованы для ссылок на объекты размещаемые в области динамической памяти, которую также часто называют пулом динамической памяти или кучей.
Ссылочные типы для подпрограмм
Ссылочные типы для подпрограмм
Также как и обобщенные ссылочные типы, ссылочные типы для подпрограмм не доступны в стандарте Ada8Они являются нововведением, которое определил выход стандарта Ada9
Ссылочные типы для подпрограмм позволяют использовать подпрограммы как параметры передаваемые другим подпрограммам (например, так как это делается в математических библиотеках Фортрана), а также осуществлять позднее связывание.
При описании ссылочных типов для подпрограмм следует придерживаться следующих соглашений:
описание типа должно определять такой список параметров,
который соответствует списку параметров подпрограммы на которую будут ссылаться
значения этого типа
при описании ссылочного типа для функции, тип возвращаемого значения должен соответствовать типу возвращаемого значения функции на которую будут ссылаться значения этого типа
переменные ссылочного типа для подпрограмм могут быть использованы для доступа к любой подпрограмме профиль которой соответствует профилю определеному при описании этого ссылочного типа
Простейшим примером использования функционального ссылочного типа как параметра подпрограммы может служить следующий фрагмент кода:
type Access_Function is access function(Item: in Float) return Float;type Vector is array (Integer range < >) of Float;procedure For_Each( F : in Access_Function; To: in out Vector ) is begin for I in To'Range loop To(I) := F( To(I) ); end loop; end For_Each; |
Здесь, процедура For_Each принимает в качестве параметра F значение ссылочного типа Access_Function указывющее на функцию, которую необходимо вызывать при обработке каждого элемента массива типа Vector, передаваемого ей как параметр To.
Примечательно, что при вызове функции F расшифровка ссылки производится автоматически. Следует также заметить, что вместо "F( To(I) )" можно было бы написать "F.all( To(I) )", что в подобных случаях - не обязательно.Использование .all требуется когда вызываемая подпрограмма (процедура или функция) не имеет параметров.
Описание переменной массива чисел, передаваемой как параметр To для процедуры For_Each, и описание функции, ссылка на которую могла бы быть использовна как параметр F процедуры For_Each, могут иметь следующий вид:
Data_Vector : Vector () := (, , );function Square(Val: in Float) return Float is begin return Val * Val; end Square; |
Таким образом, вызов процедуры For_Each (с учетом приведенных ранее описаний) будет иметь вид:
For_Each( F => Square'Access, To => Data_Vector ); |
Примечательно, что для получения ссылочного значения которое указывает на функцию Square используется атрибут 'Access.
Ссылочные типы для подпрограмм могут быть использованы для построения таблиц вызова подпрограмм. Рассмотрим следующий схематический пример:
type Action_Operation is access procedure;procedure Add; procedure List; procedure Delete;Action : constant array () of Action_Operation := ( Add'Access, List'Access, Delete'Access );type Math_Function is access function (I : in Float) return Float;function Sin (F : in Float) return Float; function Cos (F : in Float) return Float; function Tan (F : in Float) return Float;Math_Operation : constant array () of Math_Function := ( Sin'Access, Cos'Access, Tan'Access ); |
Здесь формируются две таблицы вызовов подпрограмм: первая таблица вызовов представлена массивом Action, который содержит значения ссылочного типа Action_Operation, а вторая таблица вызовов представлена массивом Math_Operation, который содержит значения ссылочного типа Math_Function (заметим, что таблицами вызовов, как правило, являются массивы).
Примером вызова I-той подпрограммы в таблице (где I - это индекс в таблице) может служить следующее:
F: Float; . . .Action(I).all; -- вызов I-той процедуры из таблицы -- Action F := Math_Operation(I)(F); -- вызов I-той функции из таблицы -- Math_Operation с параметром F |
Напомним, что для ссылочных значений, которые указывают на подпрограммы без параметров, при вызове подпрограммы требуется использование .all.
Ссылочные типы (указатели)
Ссылочные типы (указатели)
До настоящего момента мы предполагали, что структуры данных и их размещение в пространстве памяти строго фиксировано и не изменно, то есть статично.Поэтому, такие данные, как правило, называют статическими.При этом, следует заметить, что доступ к статическим данным всегда осуществляется непосредственно к месту их физического размещения в памяти, то есть, используется механизм непосредственного доступа к данным.
Для повышения эффективности в современных системах сбора и обработки информации часто требуется использование данных структура которых может изменяться в процессе работы программы (списки, деревья, графы...).Такие данные, как правило, называют динамическими данными или данными с динамической структурой.Для того, чтобы обеспечить эффективную работу с динамическими данными механизм непосредственного доступа как правило не приемлем.Для этого необходим механизм при котором доступ к реальным данным осуществляется через адресные значения, определяющие расположение реальных данных в пространстве памяти, и предоставляется возможность манипулирования такими адресными значениями.Такой механизм доступа к данным называют механизмом косвенного доступа.
Многие современные языки программирования, в качестве механизма косвенного доступа к данным, используют указатели, которые позволяют манипулировать адресами размещаемых в пространстве памяти объектов.Не смотря на то, что указатели являются достаточно эффективным средством работы с динамическими данными, непосредственная работа с адресами и адресной арифметикой часто является источником ошибок которые трудно обнаружить.Известным обладателем подобной проблемы является семейство языков C/C++.
В качестве механизма косвенного доступа к данным, Ада предлагает концепцию ссылочных типов, использование которых во многом подобно традиционному использованию указателей.При этом ссылочные типы Ады обладают следующими характерными особенностями:
Ссылочные типы не могут быть использованы для доступа к произвольному адресу памяти.
Значения ссылочного типа не рассматриваются как целочисленные зачения, а значит, они не поддерживают адресную арифметику.
Значения ссылочного типа могут ссылаться только на значения того типа, который был указан при описании ссылочного типа.
Из этого следует, что использование ссылочных типов Ады более безопасно и повышает общую надежность создаваемого программного обеспечения.
Примечательно, что в литературе не редко встречаются случаи когда официальная терминология нарушается и ссылочные типы Ады называют указателями.Как правило, это делается с целью обеспечить более традиционное изложение материала.
Все описания ссылочных типов Ады можно условно разделить на три вида: ссылочные типы для динамической памяти обобщенные ссылочные типы ссылочные типы для подпрограмм
При этом, следует заметить, что последние два вида введены стандартом Ada-9
Стандартные низкоуровневые средства, пакет Ada.Tags
Стандартные низкоуровневые средства, пакет Ada.Tags
Стандартным низкоуровневым средством работы с тэговыми типами является пакет Ada.Tags.Спецификация этого пакета, согласно стандарта, имеет следующий вид:
package Ada.Tags is type Tag is private; function Expanded_Name(T : Tag) return String; function External_Tag(T : Tag) return String; function Internal_Tag(External : String) return Tag; Tag_Error : exception;private . . . -- стандартом не определеноend Ada.Tags; |
Функция Expanded_Name возвращает полное расширенное имя типа, индефицируемого значением тэга, в виде строки (в верхнем регистре).Результат будет зависеть от реализации компилятора, если тип описан внутри неименованного блока инструкций.
Функция External_Tag возвращает строку, которая может быть использована для внешнего представления указанного тэга.Вызов External_Tag(S'Tag) эквивалентен обращению к атрибуту S'External_Tag.
Функция Internal_Tag возвращает тэг который соответствует указанному
внешнему представлению тэга, или возбуждает исключение Tag_Error
если ни для одного из типов, в пределах раздела программы,
указанная строка не является внешним представлением тэга.
Строки языка C
Строки языка C
Описание массива символов языка C имеет следующий вид:
type Char_Array is array (Size_T range <>) of aliased Char; |
Для представления строк, в языке C используются массивы символов заканчивающиеся нулевым символом.Нулевой символ используется как индикатор конца строки.Таким образом, описание строки Name, содержащей текст "Vasya", которая может быть передана как параметр для функции языка C может иметь следующий вид:
Name : constant Char_Array := "Vasya" & nul; |
Примечательно, что nul используется для представления нулевого символа (индикатор конца строки) и не является зарезервированным словом Ады null.
Для выполнения символьной конверсии можно использовать функции:
function To_C (Item: in Character) return Char; function To_Ada (Item: in Char) return Character; |
Для выполнения строковой конверсии можно использовать функции:
function To_C (Item : in String; Append_Nul : in Boolean := True) return Char_Array; function To_Ada (Item : in Char_Array; Trim_Nul : in Boolean := True) return String; |
Не сложно догадаться, что функции с именами To_C должны использоваться для преобразования переменных в форму совместимую с языком C, а функии с именами To_Ada - обратно.
Структуры данных со ссылками на себя
Структуры данных со ссылками на себя
В предыдущих примерах мы рассмотрели то как размещать обычные типы данных в динамической области памяти и выполнять их инициализацию.Однако, для того чтобы строить сложные динамические структуры данных (списки, деревья, графы...) необходима возможность описания структур данных которые могут ссылаться сами на себя, то есть требуется создание ссылки на структуру данных, которая еще не существует (не описана).
Ада позволяет решить подобную проблему осуществляя неполное описание типа.При неполном описании просто указывается имя типа, который еще не описан.После этого компилятор ожидает, что полное описание типа будет дано до завершения файла с исходным текстом:
type Element; -- неполное описание типаtype Element_Ptr is access Element;type Element is -- полное описание типа record Value : Integer; Next : Element_Ptr; end record;Head_Element : Element_Ptr; -- переменная которая будет началом списка |
Теперь мы можем построить простой связанный список, началом которого будет переменная Head_Element, следующим образом:
Head_Element := new Element'( Value => 1, Next => (new Element'( Value => 2, Next => (new Element'( Value => 3, Next => null))))); |
Данный список содержит всего три элемента (узла). Следует обратить внимание на то, что в последнем элементе списка ссылка Next имеет значение null.
которая позволяет вызывать подпрограммы написанные
Связь с другими языками в Ada83
Стандартным средством взаимодействия с другими языками в Ada83 является директива компилятора Interface, которая позволяет вызывать подпрограммы написанные на других языках программирования.
Предположим, что при работе в системе Unix, необходимо использовать команду kill.Для осуществления этого, необходимо выполнить следующее:
function kill( pid : in Integer; sig : in Integer) return Integer;pragma Interface(C, kill); |
В данном случае, первый параметр директивы компилятора Interface - это язык вызываемой подпрограммы, а второй - имя подпрограммы под которым она (подпрограмма) известна в программе на Аде.
Пример пакета который импортирует функции написанные на Фортране может иметь следующий вид.
package MATHS is function sqrt(X : Float) return Float; function exp (X : Float) return Float; private pragma Interface(Fortran, sqrt); pragma Interface(Fortran, exp); end MATHS; |
Необходимо заметить, что директива компилятора Interface не может быть использована
с настраиваемыми подпрограммами.
некоторые изменения, которые облегчают использование
Связь с другими языками в Ada95
Стандарт Ada95 внес в средства взаимодействия Ады с другими языками программирования
некоторые изменения, которые облегчают использование Ады совместно с
программным обеспечением написанным на других языках.Согласно стандарта Ada95, для организации взаимодействия с программным обеспечением,
написанным на других языках программирования,
можно использовать стандартные директивы компилятора
Import, Export, Convention и Linker_Options.Кроме того, для описания трансляции типов данных между Адой и другими языками программирования,
можно использовать предопределенную библиотеку - стандартный пакет Interfaces
и его дочерние модули.
"Сюрпризы" переводной терминологии
"Сюрпризы" переводной терминологии
Прежде чем приступить к непосредственному знакомству с Адой, есть необходимость заметить, что в англоязычной литературе, посвященной вычислительной технике в целом и программированию в частности, достаточно часто встречаются такие английские термины как operator, operation и statement. При сложившейся практике перевода такой документации на русский язык, эти термины, как правило, переводят следующим образом:
operation | операция | |
operator | операция, оператор | |
statement | оператор |
На первый взгляд, проблема перевода данных терминов достаточно безобидна. Однако, при таком традиционном подходе, добившись приемлемой благозвучности изложения материала, достаточно сложно избежать неоднозначностей и даже путаницы при обсуждении некоторых основополагающих вопросов. Также стоит особо отметить, что стандарт языка Ада строго различает такие понятия.
В действительности, смысловое значение данных терминов, при их употреблении в англоязычной документации, как правило, имеет следующее русскоязычное толкование:
operation | непосредственно обозначает понятие операции, функции или какого-либо действия, в совокупности составляющего выполнение команды (или набора команд) процессора | |
operator | обозначает понятие знака или обозначения операции, функции или какого-либо действия, что подразумевает не столько само действие для выполнения операции, сколько обозначение операции в тексте программы | |
statement | элемент текста программы, выражающий целостное законченное действие (или набор действий) |
Таким образом, исходя из всего выше сказанного, в данной работе, для достижения однозначности, принято следующее соответствие терминов:
operation | операция | |
operator | знак операции | |
statement | инструкция |
Хотя такое решение выглядит несколько не привычно, оно не должно вызвать трудности при рассмотрении материала представленного в этой работе.
Тэговые типы (tagged types)
Тэговые типы (tagged types)
Тэговые типы являются нововведением стандарта Ada9Они дополняют традиционную систему типов языка Ада, и позволяют обеспечить полную поддержку объектно-ориентированного программирования.Концептуально новыми особенностями тэговых типов являются возможность расширения структуры данных типа при наследовании, способствующая программированию посредством расширения, и динамическая диспетчеризация вызовов примитивных операций, являющаяся основой полиморфизма.
Чтобы в последствии не породить терминологической путаницы, необходимо сразу сделать одно важное замечание которое специально предназначено для знатоков ООП, активно использующих другие языки программирования (например, C++ или какой-либо современный диалект Паскаля, поддерживающий объектно-ориентированное расширение).В традиционном понимании, слово "класс" трактуется как спецификация типа данных и множество методов (операций) этого типа данных.В отличие от этого, Ада трактует понятие "класс" как набор типов которые объединены иерархией наследования.
Текстовый ввод/вывод
Текстовый ввод/вывод
Заметим, что хотя средства поддержки текстового ввода/вывода Ады не рассматривались до настоящего момента, некоторые ранее рассмотренные примеры уже применяли эти средства, осуществляя ввод/вывод с использованием стандартного устройства ввода/вывода.Теперь, необходимо рассмотреть эти механизмы более детально.
Как известно, текстовыми файлами являются файлы которые содержат символы. Как мы уже знаем, согласно стандарта Ada95, Ада обеспечивает поддержку двух символьных типов: Character и Wide_Character.Поэтому, средства поддержки текстового ввода/вывода Ады подразделяются на средства поддержки ввода/вывода для символов типа Character и средства поддержки ввода/вывода для символов типа Wide_Character.
Следует заметить, что стандартные пакеты поддержки текстового ввода/вывода для символов типа Character содержат в своем названии строку Text_IO, а стандартные пакеты поддержки текстового ввода/вывода для символов типа Wide_Character содержат в своем названии строку Wide_Text_IO.
Поскольку логика обработки текстового ввода/вывода для символов типа Character соответствует логике обработки текстового ввода/вывода для символов типа Wide_Character, то для понимания организации поддержки текстового ввода/вывода Ады достаточно рассмотреть эти механизмы в контексте символов типа Character.
Тело пакета
Тело пакета
Тело пакета содержит все детали реализации сервисов, указаных в спецификации пакета.Схематическим примером тела пакета, для показанной выше спецификации, может служить:
package body Odd_Demo is type List is array (0) of Integer; Storage_List : List; Upto : Integer; procedure Insert(Item : in Integer; Success : out Boolean) is begin . . . end Insert; function Is_Present(Item : in Integer) return Boolean is begin . . . end Is_Present;begin -- действия по инициализации пакета -- это выполняется до запуска основной программы! for I in Storage_List'Range loop Storage_List(I) := 0; end loop; Upto := 0; end Odd_Demo; |
Все ресурсы, указанные в спецификации пакета, будут непосредственно доступны в теле пакета без использования дополнительных инструкций спецификации контекста with и/или use.
Необходимо заметить, что тело пакета, также как и спецификация пакета, может содержать описания типов, переменных, подпрограмм и т.д.При этом, ресурсы, описанные в теле пакета, не доступны для использования в другом самостоятельном модуле (пакете или подпрограмме).Любая попытка обращения к ним из другого модуля будет приводить к ошибке компиляции.
Переменные, описанные в теле пакета, сохраняют свои значения между успешными вызовами публично доступных подпрограмм пакета.Таким образом, мы можем создавать пакеты, которые сохраняют информацию для более позднего использования (другими словами: сохранять информацию о состоянии).
Раздел "begin ... end", в конце тела пакета, содержит перечень инструкций инициализации для этого пакета.Инициализация пакета выполняется до запуска на выполнение главной подпрограммы.Это справедливо для всех пакетов.Следует заметить, что стандарт не определяет порядок выполнения инициализации различных пакетов.
Тип Root_Integer
Тип 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 до 10 Исходя из этого, для типа X базовым типом будет тип Integer.
Тип Y описывается как тип с допустимым диапазоном значений от 0 до 100, и при его описании не указан тип-предок. В таком случае, он будет производным от типа Root_Integer, но его базовый диапазон не обязательно должен быть таким же как у Root_Integer. В результате, некоторые системы могут размещать экземпляры объектов такого типа и его базового типа в одном байте. Другими словами, определение размера распределяемого места под объекты такого типа возлагается на компилятор.
Тип Universal_Integer
Тип Universal_Integer
Для предотвращения необходимости явного преобразования типов при описании целочисленных констант, Ада предлагает понятие универсального целого типа - Universal_Integer. Все целочисленные литералы принадлежат типу Universal_Integer. Многие атрибуты языка (обсуждаемые позже) также возвращают значения универсального целого типа. Установлено, что тип Universal_Integer совместим с любым другим целочисленным типом, поэтому, в арифметических выражениях, компилятор будет автоматически преобразовывать значения универсального целого типа в значения соответствующего целочисленного типа.
Типы и объекты задач
Типы и объекты задач
Конструкция задачи обладает свойствами, которые характерны для пакетов, процедур и структур данных: Подобно пакетам, задача имеет спецификацию и тело, однако она не может быть самостоятельно компилируемой единицей, помещенной в свой собственный файл с исходным текстом.Вместо этого, задача должна быть помещена в другую структуру (например, пакет или процедуру) Подобно процедуре, задача содержит раздел описаний и исполнительную часть, однако она не вызывается как процедура.Вместо этого, она начинает выполняться автоматически, как часть структуры, в которой она описана. Подобно структуре данных, задача имеет тип и может существовать как переменная этого типа. Кроме того, подобно записи, задача может иметь дискриминанты.
Спецификация задачи, начинающаяся зарезервированными словами task type, определяет тип задачи (или задачный тип). Значение объекта (переменная) типа задачи представляет собой задачу.Спецификация задачи, не содержащаяя зарезервированного слова type, определяет одиночную задачу, а описание задачи с такой спецификацией равносильно описанию анонимного типа задачи с одновременным описанием объекта этого типа.
Простым примером многозадачной программы может служить следующая программа:
with Ada.Text_IO;procedure Multitasking_Demo is -- спецификация анонимной задачи task Anonimous_Task; -- тело анонимной задачи task body Anonimous_Task is begin -- для Anonimous_Task for Count in loop Ada.Text_IO.Put_Line("Hello from Anonimous_Task"); end loop; end Anonimous_Task; -- спецификация типа задачи task type Simple_Task (Message: Character); -- тип задачи имеет тело task body Simple_Task is begin -- для Simple_Task for Count in loop Ada.Text_IO.Put_Line("Hello from Simple_Task " & Message); end loop; end Simple_Task; -- переменная задачного типа Simple_Task_Variable: Simple_Task(Message => 'A');begin -- для Multitasking_Demo -- в отличие от процедур, задачи не вызываются, -- а активируются автоматически -- выполнение обоих задач начинается как только -- управление достигнет этой точки, то есть, сразу -- после "begin", но перед выполнением первой инструкции -- головной процедуры Multitasking_Demo null;end Multitasking_Demo; |
В данном примере описана одиночная задача анонимного типа Anonimous_Task, тип задачи Simple_Task и переменная задачи Simple_Task_Variable, имеющая тип Simple_Task.Примечательно, что описание типа задачи Simple_Task содержит дискриминант, значение которого используется как параметр задачи и указывается при описании переменной задачи Simple_Task_Variable.Следует также обратить внимание на то, что любой тип задачи является лимитированным и, таким образом, для него не существует предопределенных операций присваивания или сравнения.Алгоритмы работы обоих задач просты и подобны - каждая задача выводит пять приветственных сообщений и завершает свою работу.
Рассмотрим еще один простой пример, который демонстрирует использование задачного типа для описания более одного экземпляра объекта задачи.
with Ada.Text_IO;procedure Multitasking_Demo_2 is -- спецификация типа задачи task type Simple_Task (Message: Character; How_Many: Positive); -- тело типа задачи task body Simple_Task is begin -- для Simple_Task for Count in .How_Many loop Ada.Text_IO.Put_Line("Hello from Simple_Task " & Message); delay ; end loop; end Simple_Task; -- переменные задачного типа Simple_Task_A: Simple_Task(Message => 'A', How_Many => 5); Simple_Task_B: Simple_Task(Message => 'B', How_Many => 10);begin -- для Multitasking_Demo_2 null;end Multitasking_Demo_2; |
В этом примере, тип задачи Simple_Task содержит два дискриминанта: дискриминант Message типа Character используется при выдаче приветственного сообщения, как и ранее, а дискриминант How_Many используется для указания количества выдаваемых сообщений.Таким образом, переменная задачи Simple_Task_A выдаст пять приветствий, а переменная Simple_Task_B - десять.
Типы и подтипы
Типы и подтипы
Как уже говорилось, концепция типа является в Аде основопологающим фактором создания надежного программного обеспечения. Предположим, что у нас есть два целочисленных типа, которые как-то характеризуют "звоночки" и "свисточки", и мы никак не хотим смешивать эти понятия. Нам необходимо, чтобы компилятор имел возможность предупредить нас: "Ба, да вы пытаетесь смешать выши "звоночки" и "свисточки"! Что Вы в действительности подразумеваете?". Это выглядит излишними осложнениями для людей, которые используют другие языки программирования со слабой типизацией данных. Размышления над тем какие данные необходимо представить, описание различных типов данных и правил конвертирования типов требуют некоторых усилий. Однако, такие усилия оправдываются тем, что как только это сделано, компилятор сможет помочь отыскать разные глупые ошибки.
Бывают случаи, когда нам необходимо указать, что какие-то переменные могут хранить только какое-то ограниченное число значений предоставляемых определенным типом данных. При этом, мы не хотим описывать самостоятельный новый тип данных, поскольку это потребует явного указания правила преобразования типа при взаимодействии со значениями, которые имеют оригинальный тип данных, то есть нам необходима возможность смешивать различные типы, которые, по своей сути, являются одинаковыми сущностями, но при этом имеют некоторые различия в своих характеристиках. К тому же, мы хотим сохранить преимущества проверки корректности наших действий, которые предоставляет компилятор. В таких случаях, Ада позволяет нам описывать подтипы (subtype) существующих типов данных (это соответствует типам определяемым пользователем в Паскале).
Общий синтаксис объявления подтипа имеет вид:
subtype Name_1 is Base_Type; -- в данном случае, Name_1 является -- синонимом типа Base_Typesubtype Name_2 is Base_Type range Lowerbound..Upperbound; |
Примеры объявления подтипов приводятся ниже:
type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC); subtype Old_Processors is Processors range M6800.i8086; subtype New_Processors is Processors range Pentium..PowerPC;subtype Data is Integer; subtype Age is Data range 40; subtype Temperatures is Float range -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 (40), -- но при этом остается совметимым |
Чтобы избежать генерацию исключительной ситуации, можно использовать проверки принадлежности диапазону ("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" может выходить за пределы значений типа не приводя к генерации исключительной ситуации если общий результат вычисленного значения всего выражения будет находиться в допустимом диапазоне значений для данного типа.
Таким образом, необходимо заметить, что проверка допустимости диапазона
производится только для значений подтипов, а
для значений базовых анонимных типов такая проверка не производится.
При этом, чтобы результат вычислений был математически корректен,
всегда производится проверка на переполнение.
Типы неограниченных массивов (unconstrained array), предопределенный тип String
Типы неограниченных массивов (unconstrained array),
предопределенный тип String
До настоящего момента мы рассматривали только такие массивы у которых диапазон значений индекса был заведомо известен. Такие массивы называют ограниченными массивами, и они могут быть использованы для создания экземпляров объектов с четко определенными во время описания типа границами.
В дополнение к таким типам, Ада позволяет описывать типы массивов, которые не имеют четко определенного диапазона для индексных значений, когда описание типа не определяет границ массива. Поэтому такие массивы называют неограниченными массивами (unconstrained array).
Типы неограниченных массивов позволяют описывать массивы, которые во всех отношениях идентичны обычным массивам, за исключением того, что их размер не указан. Ограничение массива (указание диапазона для индексных значений) производится при создании экземпляра объекта такого типа.
Таким образом, описание неограниченого массива предоставляет целый класс массивов, которые содержат элементы одинакового типа, имеют одинаковый тип индекса и одинаковое количество индексов, но при этом разные экземпляры объеков такого типа будут иметь разные размеры.
Этот механизм удобно использовать в случаях когда массив произвольного размера необходимо передать в подпрограмму как параметр.
Примером описания неограниченного массива целых чисел может служить следующее:
type Numbers_Array is array (Positive range <>) of Integer; |
Символы "<>" указывают, что диапазон значений индекса должен быть указан при описании объектов типа Numbers_Array. Переменная такого типа может быть описана следующим образом:
Numbers : Numbers_Array () := (1, 2, 3, 4, 5); |
Здесь, при описании переменной Numbers предусматривается ограничение (constraint) размеров массива - указывается диапазон значений индекса - ().
Пакет Standard предоставляет предопределенный тип String, который описывается как неограниченный массив символов:
type String is array (Positive range <>) of Character; |
Таким образом, тип String может быть использован для описания обширного класса символьных массивов, которые идентичны во всем, кроме количества элементов в массиве.
Также как и в предыдущем примере описания переменной Numbers, для создания фактического массива типа String, мы должны предусмотреть ограничение диапазона возможных значений индекса:
My_Name : String (0); |
Здесь, ограничение диапазона индексных значений находится в диапазоне 0. Преимущество такого подхода в том, что все описанные строки имеют один и тот же тип, и могут, таким образом, использоваться как параметры подпрограмм. Такой дополнительный уровень абстракции позволяет более общее использование подпрограмм обработки строк.
Необходимо заметить, что для инициализации объектов типа String, можно использовать агрегаты, поскольку тип String, по сути, является массивом символов. Однако, более цивилизованным способом будет использование строковых литералов. Так, вышеописанную переменную My_Name, можно инициализировать следующим образом:
My_Name := "Alex "; |
Следует учесть, что в приведенном примере, строковый литерал, указывающий имя, необходимо дополнить пробелами, чтобы его размер совпадал с размером описанной переменной. В противном случае, компилятор может выдать предупреждение о возбуждении исключения Constraint_Error во время выполнения программы.
При описании строк, которым присваиваются начальные значения, границы диапазона можно не указывать:
Some_Name : String := "Vasya Pupkin"; Some_Saying : constant String := "Beer without vodka is money to wind!"; |
Для обработки каждого элемента переменной, которая порождается при использовании типа неограниченного массива, требуется использование таких атрибутов типа массив, как A'Range, A'First и т.д., поскольку не известно какие индексные значения будет иметь обрабатываемый массив.
My_Name : String (0); My_Surname : String (20); |
Обычно, неограниченные массивы реализуются с объектом который хранит значения границ диапазона индекса и указатель на массив.
Типы Universal_Float и Root_Real
Типы Universal_Float и Root_Real
Подобно тому как все целочисленные литералы принадлежат универсальному классу Universal_Integer, все вещественные численные литералы принадлежат универсальному классу Universal_Float. Этот универсальный тип совместим с любым вещественным типом с плавающей точкой и любым вещественным типом с фиксированной точкой. Такой подход избавляет от необходимости выполнения различных преобразований типов при описании вещественных констант и переменных.
Модель вещественной арифметики Ады основывается на анонимном типе Root_Real. Этот анонимный тип используется как базовый тип для всех вещественных типов. Тип Root_Real имеет точность, которая определяется значением константы Max_Base_Digits пакета System (System.Max_Base_Digits). Такой подход использован для облегчения переносимости программ.
Указание значения параметра по-умолчанию
Указание значения параметра по-умолчанию
Для любых "in"-параметров ("in" или "in out"), в спецификации подпрограммы можно указать значение параметра по-умолчанию.Синтаксис установки значения параметра по-умолчанию подобен синтаксису определения инициализированных переменных и имеет следующий вид:
with Ada.Text_IO; use Ada.Text_IO;procedure Print_Lines(No_Of_Lines: Integer := 1) isbegin for Count in 1 .. No_Of_Lines loop New_Line; end loop; end Print_Lines; |
Такое описание устанавливает значение параметра No_Of_Lines для случаев когда процедура Print_Lines вызывается без указания значения этого параметра (позиционного или именованного).
Таким образом, вызов этой процедуры может иметь вид:
Print_Lines; -- это печатает одну строку Print_Lines(6); -- переопределяет значение параметра -- установленное по-умолчанию |
Подобно этому, если процедура Write_Lines была описана как:
with Ada.Text_IO; use Ada.Text_IO;procedure Write_Lines(Letter : in Char := '*'; No_Of_Lines : in Integer := 1) isbegin for I in 1 .. No_Of_Lines loop for I in 1 .. 80 loop Put(Letter); end loop; New_Line; end loop; end Write_Lines; |
то она может быть вызвана следующими способами:
Write_Lines; -- для параметров Letter и No_Of_Lines -- используются значения устанавливаемые -- по-умолчанию Write_Lines('-'); -- значение по-умолчанию - для No_Of_Lines Write_Lines(no_of_lines => 5); -- значение по-умолчанию - для Letter Write_Lines('-', 5) -- оба параметра определены |
Уменьшение длин имен
Уменьшение длин имен
Переименование может быть полезно в случае наличия длинных имен пакетов:
with Ada.Text_IO; with Ada.Integer_Text_IO;procedure Gun_Aydin is package TIO renames Ada.Text_IO; package IIO renames Ada.Integer_Text_IO; . . . |
В этом случае, для внутреннего использования, длинное имя Ada.Text_IO переименовано в короткое имя TIO, а длинное имя Ada.Integer_Text_IO переименовано в короткое имя IIO.
Управление динамическими объектами
Управление динамическими объектами
Одним из широко распространенных случев использования объектов контролируемых типов являестя решение проблемы "утечки" памяти при работе с динамическими данными.
Предположим, что у нас есть пакет Lists который содержит описание связанного списка List, и спецификация этого пакета выглядит следующим образом:
package Lists is type List is private; Underflow : exception; procedure Insert_At_Head(Item : in out List; Value : in Integer); procedure Remove_From_Head(Item : in out List; Value : out Integer); procedure Insert_At_Tail(Item : in out List; Value : in Integer); procedure Remove_From_Tail(Item : in out List; Value : out Integer); function Full(Item : List) return Boolean; function Empty(Item : List) return Boolean;private type List is ... -- полное описание типа . . .end Lists; |
Заметим, что тип связанного списка List, который описан в этом пакете, не является контролируемым типом.Теперь, рассмотрим следующий пример, использующий описание пакета Lists:
with Lists; use Lists;procedure Memory_Leaks_Demo is A, B : List; Result : Integer; procedure Loose_Memory is C : List; begin Insert_At_Head(C, 1); Insert_At_Head(C, 2); end Loose_Memory; -- при попадании в эту точку, -- C выходит за пределы области видимости -- и теряется память ассоциируемая с этим узломbegin Insert_At_Head(A, 1); Insert_At_Head(A, 2); Insert_At_Head(B, 3); Insert_At_Head(B, 4); B := A; -- B и A указывают на один и тот же список -- все узлы "старого" списка B - теряются Remove_From_Tail(A, Result); -- изменяет как список A, так и список Bend Memory_Leaks_Demo; |
В данном примере наглядно демонстрируются проблемы "утечки" памяти при работе с объектами связанного списка List: когда объект связанного списка выходит за пределы области видимости, пространство динамической памяти, выделенное для размещения этого объекта, не восстанавливается в случае выполнения присваивания, осуществляется копирование только указателя на "голову" списка (начало списка), а копирование остальной части списка не выполняется
Рассмотрим вариант модификации пакета Lists в котором тип связанного списка List является контролируемым. Спецификация пакета будет иметь следующий вид:
with Ada.Finalization;package Lists is type List is private; Underflow : exception; procedure Insert_At_Head(Item : in out List; Value : in Integer); procedure Remove_From_Head(Item : in out List; Value : out Integer); procedure Insert_At_Tail(Item : in out List; Value : in Integer); procedure Remove_From_Tail(Item : in out List; Value : out Integer); function Full(Item :List) return Boolean; function Empty(Item:List) return Boolean;private -- обычные описания для списка type Node; type Ptr is access Node; type Node is record Value : Integer; Next : Ptr; end record; -- только "голова" списка - "специальная" type List is new Ada.Finalization.Controlled with record Head : Ptr; end record; -- Initialize не нужна (указатели автоматически устанавливаются в null) -- procedure Initialize(Item : in out List); procedure Adjust(Item : in out List); procedure Finalize(Item : in out List);end Lists; |
Примечательным фактом является описание подпрограмм Adjust и Finalize в приватной части спецификации пакета.Это предотвращает от непосредственной возможности их неуместного вызова клиентами.Тело пакета Lists (с подробными комментариями) будет иметь вид:
with Unchecked_Deallocation;package body Lists is -- подпрограмма освобождения памяти занимаемой узлом procedure Free is new Unchecked_Deallocation(Node, Ptr); ------------------------------------------------------------ -- реализация остальных внутренностей (Insert_At_Head, Remove_From_Head...) . . . -------------------------------------------------------------- -- дан указатель на список, подпрограмма будет размещать -- в памяти новый, идентичный первому, список function Copy_List(Item : Ptr) return Ptr is begin if Item = null then return null; else return new Node'(Item.Value, Copy_List(Item.Next)); end if; end Copy_List; ------------------------------------------------------------ -- при присваивании B := A, B будет только переписано содержимым A. -- для связанного списка это подразумевает, что оба, A и B, -- указывают на один и тот же объект. -- теперь, необходимо сделать физическую копию узлов, на которые указывает B, -- и переставить указатель начала списка на начало копии списка, который мы -- только что сделали procedure Adjust(Item : in out List) is begin Item.Head := Copy_List(Item.Head); end Adjust; ------------------------------------------------------------ -- освободить всю память, занимаемую узлами списка, -- при разрушении списка procedure Finalize(Item : in out List) is Upto : Ptr := Item.Head; Temp : Ptr; begin while Upto /= null loop Temp := Upto; Upto := Upto.Next; Free(Temp); end loop; Item.Head := null; end Finalize;end Lists; |
Ниже представлен пример программы которая использует модифицированную версию пакета Lists (где тип List является контролируемым).
with Lists; use Lists;procedure Controlled_Demo is A : List; -- автоматический вызов Initialize(A); B : List; -- автоматический вызов Initialize(B);begin Insert_At_Head(A, 1); Insert_At_Head(A, 2); Insert_At_Head(B, 3); Insert_At_Head(B, 4); ------------------------------------------ -- -- A --> | 2 |-->| 1 |--> null -- B --> | 4 |-->| 3 |--> null -- ------------------------------------------ B := A; ------------------------------------------ -- -- Finalize(B); -- освобождение узлов B, до перезаписи -- -- A --> | 2 |-->| 1 |--> null -- B --> null -- -- копирование A в B -- теперь они _оба_ указывают на один и тот же список -- -- A --> | 2 |-->| 1 |--> null -- B ----^ -- -- Adjust(B); -- B копирует список, на который он в текущий момент -- указывает, и после этого указывает на новый список -- -- A --> | 2 |-->| 1 |--> null -- B --> | 2 |-->| 1 |--> null -- ------------------------------------------end Controlled_Demo; ------------------------------------------ -- -- Finalize(A), Finalize(B). -- освобождение памяти ассоциируемой с A и B -- -- A --> null -- B --> null -- ------------------------------------------ |
Таким образом, использование контролируемых типов предоставляет удобное средство управления жизненным циклом динамических объектов, которое позволяет избавить клиентов типа от необходимости непосредственного управления динамическим распределением памяти.
Управляющие структуры
Управляющие структуры
Управляющие структуры любого языка программирования предназначены для описания алгоритмов программ, или, другими словами, для описания последовательности тех действий которые выполняет программа во время своей работы.
Управляющие структуры языка Ада по своему стилю и назначению подобны тем конструкциям, которые встречаются в большинстве других современных языках программирования. Однако, при этом присутствуют и некоторые отличия.
Как и язык в целом, управляющие структуры языка Ада, были разработаны так, чтобы обеспечить максимальную читабельность. Все управляющие структуры языка Ада можно разделить на простые и составные инструкции. Все инструкции языка Ада должны завершаться символом точки с запятой. Все составные инструкции завершаются конструкцией вида "end что-нибудь ;".
Условные инструкции if
Условные инструкции if
Для организации условного выполнения последовательностей алгоритмических действий (то есть, построения разветвляющихся алгоритмов), в Аде могут использоваться условные инструкции if.
Каждая инструкция if заканчивается конструкцией "end if".
if <логическое_выражение> then -- последовательность инструкцийend if;if <логическое_выражение> then -- последовательность инструкций 1else -- другая последовательность инструкций 2end if; |
В первом примере, приведенном выше, последовательность инструкций, описывающая алгоритмические действия, будет выполнена только в случае когда результат вычисления логического выражения будет иметь значение True. Во втором примере, в случае когда результат вычисления логического выражения - True будет выполняться "последовательность инструкций 1", в противном случае - "последовательность инструкций 2".
Для сокращения инструкций вида "else if ... ", и в целях улучшения читабельности, введена конструкция elsif, которая может быть использована столько раз, сколько это будет необходимо.
if <логическое_выражение> then -- последовательность инструкций 1elsif <логическое_выражение> then -- последовательность инструкций 2elsif <логическое_выражение> then -- последовательность инструкций 3else -- последовательность инструкцийend if; |
В этой форме инструкции if, заключительная конструкция else - опциональна.
Необходимо также заметить, что результат вычисления логического выражения всегда должен иметь предопределенный тип Standard.Boolean.
Вариантные записи
Вариантные записи
Использование дискриминантов позволяет конструировать вариантные записи (или, называемые иначе, записи с вариантами). В этом случае, значение дискриминанта определяет наличие или отсутствие некоторых полей записи.
Рассмотрим следующий пример:
type Vehicle is (Bicycle, Car, Truck, Scooter);type Transport (Vehicle_Type : Vehicle) is record Owner : String(0); -- владелец Description : String(0); -- описание case Vehicle_Type is when Car => Petrol_Consumption : Float; -- расход бензина when Truck => Diesel_Consumption : Float; -- расход солярки Tare : Real; -- вес тары Net : Real; -- вес нетто when others => null; end case; end record; |
В представленном описании типа записи Transport, поле Vehicle_Type (vehicle - транспортное средство) является дискриминантом.
В данном случае, значение дискриминанта должно быть указано при описании экземпляра записи. Таким образом, можно создавать различные объекты у которых значения дискриминантов будут отличаться. Заметим также, что при описании таких объектов допускается указывать только значение дискриминанта, а присваивание значений остальным полям записи можно выполнять позже. Агрегаты для записей с дискриминантами должны включать значение дискриминанта как первое значение. Рассмотрим следующие описания:
My_Car : Transport (Car); My_Bicycle : Transport (Vehicle_Type => Bicycle); His_Car : Transport := (Car, "dale ", "escort ", 3); |
Здесь, переменные My_Car и His_Car, у которых дискриминант имеет значение Car, содержат поля: Owner, Description и Petrol_Consumption. При этом, попытка обращения к таким полям этих переменных как Tare и/или Net будет не допустима. В результате, это приведет к генерации ошибки ограничения (constraint error), что может быть обнаружено и обработано при использовании механизма исключений (exceptions) язака Ада.
Следует особо отметить, что несмотря на то, что дискриминант является полем записи, непосредственное присваивание значения дискриминанту запрещено.
Также отметим, что в приведенных примерах тип записи, для простоты, имеет только один дискриминант. Реально, запись может содержать столько дискриминантов, сколько необходимо для решения конкретной задачи.
И в заключение укажем несколько общих особенностей описания записей с вариантами: вариантная часть всегда записывается последней запись может иметь только одну вариантную часть, однако внутри вариантной части разрешается указывать другой раздел вариантов для каждого значения дискриминанта должен быть указан свой список компонентов альтернатива others допустима только для последнего варианта, она задает все оставшиеся значения дискриминанта (возможно, и ни одного), которые не были упомянуты в предыдущих вариантах если список компонентов варианта задан как null, то такой вариант не содержит никаких компонентов
Вещественные типы
Вещественные типы
Ада предусматривает два способа представления вещественных чисел: представление вещественных величин с плавающей точкой и представление вещественных величин с фиксированной точкой. Кроме этого вы можете использовать типы вещественных величин с десятичной фиксированной точкой.
Вещественные типы с десятичной фиксированной точкой
Вещественные типы с десятичной фиксированной точкой
Следует учитывать, что поскольку от реализации компилятора Ады не требуется обязательное обеспечение поддержки вещественных величин с десятичной фиксированной точкой, то это может вызвать трудности при переносе программного обеспечения на другую систему или при использовании разных компиляторов.
Примером описания вещественного типа с десятичной фиксированной точкой может служить следующее:
type Money is delta 1 digits 15; -- десятичная фиксированная точка, -- здесь величина, задаваемая в delta, -- должна быть степенью 10 subtype Salary is Money digits 10; |
Вещественные типы с фиксированной точкой, тип Duration
Вещественные типы с фиксированной точкой, тип Duration
Представление чисел с фиксированной точкой имеет более ограниченный диапазон значений и указанную абсолютную погрешность, которая задается как delta этого типа.
В пакете Standard предоставлен предопределенный вещественный тип с фиксированной точкой Duration, который используется для представления времени и обеспечивает точность измерения времени в 50 микросекунд:
type Duration is delta 00000001 range -((2 ** 63 - 1) * 00000001) .. +((2 ** 63 - 1) * 00000001); |
Ниже следуют примеры описаний вещественных типов с фиксированной точкой.
type Volt is delta 25 range .. 25;type Fraction is delta System.Fine_Delta range -..; -- Ada95 -- Fraction'Last = - System.Fine_Delta |
Последний пример показывает полезную способность вещественных типов с фиксированной точкой - четкое определение насколько тип должен быть точным. Например, это позволяет контролировать ошибки, возникающие при округлении.
Вещественные типы с плавающей точкой, тип Float
Вещественные типы с плавающей точкой, тип Float
Вещественные типы с плавающей точкой имеют неограниченный диапазон значений и точность, определяемую количеством десятичных цифр после запятой. Представление чисел с плавающей точкой имеет фиксированную относительную погрешность.
Пакет Standard предоставляет предопределенный вещественный тип с плавающей точкой Float, который обеспечивает точность в шесть десятичных цифр после запятой:
type Float is digits 6 range -16#FFFF_FF#E+32 .. 16#FFFF_FF#E+32; -- -0282E+38 .. 0282E+38 |
В пакете Standard компилятора GNAT, для 32-битных систем Linux и Windows, дополнительно представлены еще несколько вещественных типов с плавающей точкой (фактические значения констант для различных платформ отличаются):
type Short_Float is digits 6 range -16#FFFF_FF#E+32 .. 16#FFFF_FF#E+32; -- -0282E+38 .. 0282E+38type Long_Float is digits 15 range -16#FFFF_FFFF_FFFF_F8#E+256 .. 16#FFFF_FFFF_FFFF_F8#E+256; -- -9769313486232E+308 .. 9769313486232E+308type Long_Long_Float is digits 18 range -16#FFFF_FFFF_FFFF_FFFF#E+4096 .. 16#FFFF_FFFF_FFFF_FFFF#E+4096; -- -8973149535723177E+4932 .. 8973149535723177E+4932 |
Ниже следуют примеры описаний вещественных величин с плавающей точкой.
X : Float; A, B, C : Float; Pi : constant Float := 4_2; Avogadro : constant := 27E23; -- тип Universal_Floatsubtype Temperatures is Float range .0; type Result is new Float range .0_00;type Velocity is new Float; type Height is new Float; -- нельзя случайно смешивать Velocity и Height -- без явного преобразования типов.type Time is digits 6 range .0_00; -- в этом диапазоне требуемая точность - шесть десятичных цифр -- после точкиtype Degrees is digits 2 range -200; -- требуемая точность - две десятичных цифры после точки |
Следующие знаки операций предопределены для каждого вещественного типа с плавающей точкой.
+, -, *, / ** возведение в степень (только целые значения степени) abs абсолютное значение |
Вложенные структуры
Вложенные структуры
В качестве компонентов записи можно использовать составные типы. Например, полем записи может быть массив или какая-либо другая запись. Таким образом, Ада предоставляет возможность построения описаний сложных структур данных. Однако, описания таких структур данных обладают некоторыми характерными особенностями на которые необходимо обратить внимание.
Вложенные записи
Вложенные записи
Бывают случаи, когда компонент записи сам является записью, или, говоря иначе, требуется использование вложенных записей. Например:
type Point is record X : Integer; Y : Integer; end recordtype Rect is record Left_Hight_Corner : Point; Right_Low_Corner : Point; end recordP : Point := (100, 100); R : Rect; |
В этом случае, доступ к полям переменной R типа Rect может быть выполнен следующим образом:
R.Left_Hight_Corner.X := 0; R.Left_Hight_Corner.Y := 0;R.Right_Low_Corner := P; |
Для указания всех значений можно использовать агрегаты.
R_1 : Rect := ( (0, 0), (100, 100) ); R_2 : Rect := ( Left_Hight_Corner => (Y => 0, X => 0), Right_Low_Corner => (100, 100) ); |
Как видно из приведенных примеров, здесь используются вложенные агрегаты.
Возбуждение исключений
Возбуждение исключений
Указание возбуждения исключения достаточно простое.Для этого используется инструкция raise. Например:
raise Numeric_Error; |
Сгенерированное таким образом исключение не будет ничем отличаться от "истинного" исключения Numeric_Error.
Введение в систему типов языка Ада
Введение в систему типов языка Ада
Данные - это то, что обрабатывает программа. Практически, любой современный язык программирования определяет свои соглашения и механизмы для разделения данных на разные типы.Известно, что Ада - это язык со строгой типизацией. И хотя это не единственное свойство Ады, но, пожалуй, это свойство наиболее широко известно.
Понятие типа данных Ады подразумевает, что: каждый тип данных имеет свое имя каждый тип данных имеет свое множество допустимых значений для каждого типа данных определено свое множество допустимых операций и знаков операций строгое разделение объектов одного типа данных от объектов любого другого типа данных
Исходя из сказанного, в отличие от других языков программирования, Ада не допускает прямого присваивания значений одного типа данных другому типу данных и/или любого неявного преобразования значений одного типа в значения другого типа. В случае необходимости преобразования значений одного типа в значения другого типа можно указать (или описать) требуемое правило преобразования, но такое правило преобразования должно быть указано явно.
На первый взгляд, строгое требование явного указания или описания всех правил преобразования выглядит раздражающе (особенно для тех кто привык к С) - без явного указания таких правил компилятор просто отказывается компилировать вашу программу. Однако, в последствии это предохраняет от долгих и мучительных поисков, в процессе отладки, тех "сюрпризов" неявного преобразования и/или некорректного присваивания значений различных типов, которые допускаются в других языках программирования. Таким образом, подобный подход является одним из основополагающих факторов обеспечения надежности программного обеспечения.
Ада обладает достаточным набором предопределенных встроенных типов, а также предоставляет богатые возможности для описания новых типов данных и их подтипов (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 |
Примечательно, что, в отличие от языка Паскаль, Ада не предоставляет предопределенного механизма поддержки множественных типов. Однако, богатые средства описания типов Ады, при необходимости, позволяют программисту конструировать подобные типы без значительных усилий.
Ввод/вывод двоичных данных
Ввод/вывод двоичных данных
Как правило, современные производительные системы обработки больших объемов данных не используют текст для сохранения информации в своих файлах.Это значит, что вместо текста, в целях повышения производительности, файлы данных таких систем обычно используют двоичное представление.В этом случае файлы состоят из композитных объектов, которыми чаще всего являются записи.
В качестве основы для организации ввода/вывода двоичных данных Ада предусматривает настраиваемые пакеты Ada.Sequential_IO и Ada.Direct_IO.С помощью этих пакетов можно построить работу с двоичными файлами, состоящими из однотипных объектов одинаковой длины (записи, массивы, числа с плавающей точкой ...).
Выполнение подавления исключений
Выполнение подавления исключений
Мы можем подавить проверку исключений для индивидуального объекта:
pragma Suppress (Idex_Check, on => table); |
Подавление проверки исключений также может относиться к какому-то определенному типу:
type Employee_Id is new Integer; pragma Suppress (Range_Check, Employee_Id); |
Более полным примером использования директивы Supress может служить код показанный ниже. В этом случае область действия директивы распространяется до конца блока.
declare pragma Suppress(Range_Check); subtype Small_Integer is Integer range 0; A : Small_Integer; X : Integer := 50;begin A := X; end; |
Этот код не будет генерировать ошибок ограничения (Constraint_Error).
Вызов переопределенной операции предка
Вызов переопределенной операции предка
Достаточно часто, реализация примитивной операции производного типа нуждается в вызове переопределенной примитивной операции предка.Предположим, что для типов Root и Child_1 существуют описания операции Display имеющие следующий вид:
. . . procedure Display (Self: in Root); procedure Display (Self: in Child_1); . . . |
Поскольку такие операции совмещены, то можно сказать, что реализация операции Display типа Root (предка для типа Child_1) является "затененной", в виду переопределения реализации в производном типе Child_1.
В таком случае, для вызова "затененной" операции предка можно использовать следующее:
procedure Display (Self: in Child_1) is begin Display ( Root(Self) ); -- вызов "затененной" операции предка . . . end Display; |
Здесь, для вызова "затененной" реализации операции предка, используется явное преобразование представления параметра Self к типу Root.За счет этого, в подобных случаях, всегда осуществляется статическое связывание, а также отсутствует опасность получения бесконечного рекурсивного вызова.
Взаимодействие с программами написанными на C
Взаимодействие с программами написанными на C
Обсуждение взаимодействия с программами написанными на C полагается на описания которые содержатся в стандартном пакете Interfaces.C.В частности, этот пакет предусматривает средства преобразования объектов с типами языка C в объектыс типами Ады, и обратно.
Взаимодействие задач
Взаимодействие задач
Показанные ранее примеры многозадачных программ демонстрировали только способность задач выполняться одновременно.При этом организация взаимодействия различных задач между собой не рассматривалась.В реальности обеспечение такого взаимодействия между одновременно выполняющимися задачами с целью обмена данными или взаимной синхронизации работы имеет важное значение.
Записи-константы
Записи-константы
Записи-константы могут быть созданы также как и обычные переменные. В этом случае значения всех полей записи должны быть инициализированы с помощью агрегата или значений определенных по-умолчанию.
My_Bicycle : constant Bicycle := ( Hi_Tensile_Steel, Unknown, Front_Brake => Side_Pull, Rear_Brake => Side_Pull ); |
Присваивать новые значения полям записи-константы или самой записи-константе нельзя. Необходимо также заметить, что отдельные поля записи не могут быть описаны как константы.
Записи (record)
Записи (record)
Понятие записи, также как и понятие массива, является механизмом структурирования данных. Однако, в отличие от массива, запись позволяет сгруппировать в одном объекте набор объектов которые могут принадлежать различным типам. При этом объекты из которых состоит запись часто называют компонентами или полями записи.
Для работы с записями, Ада предлагает средства подобные тем, которые предоставляют другие современные языки программирования, а также дополняет их некоторыми своими особенностями. Также как и для массивов, для записей предусматривается использование агрегатов. Использование дискриминантов позволяет создавать вариантные записи, указывать размер для записи переменного размера и выполнять инициализацию компонентов записи.
Зарезервированные слова
Зарезервированные слова
Некоторые слова, такие как with, procedure, is, begin, end и т.д., являются частью самого языка программирования. Такие слова называют зарезервированными (или ключевыми) и они не могут быть использованы в программе в качестве имен идентификаторов. Полный список зарезервированных слов Ады приводится ниже:
abort else new return abs elsif not reverse * abstract end null accept entry select access exception separate * aliased exit of subtype all or and for others * tagged array function out task at terminate generic package then begin goto pragma type body private if procedure case in * protected * until constant is use raise declare range when delay limited record while delta loop record while digits renames do mod * requeue xor |
Примечание:
Зарезервированные слова помеченные звездочкой введены стандартом Ada9
Защищенные модули (protected units)
Защищенные модули (protected units)
Защищенные процедуры обработки прерываний
Защищенные процедуры обработки прерываний
Ада предусматривает два стиля установки обработчиков прерываний: вложенный (nested) и не-вложенный (non-nested).При вложенном стиле, обработчик прерываний в защищенном объекте устанавливается неявно в момент появления данного защищенного объекта, и после того как защищенный объект прекращает существовать, происходит неявное восстановление старого обработчика прерывания.При не-вложенном стиле, обработчик прерываний устанавливается явным вызовом процедуры, и старый обработчик прерывания может быть восстановлен только явно.
Существует два способа определения устанавливаемого обработчика прерывания вложенного стиля, в зависимости от используемой в защищенном описании директивы компилятора.
В первом случае, устанавливаемый с использованием вложенного стиля обработчик прерывания, идентифицируется последующей директивой компилятора Attach_Handler, которая появляется в защищенном описании и имеет следующий вид:
pragma Attach_Handler (Handler, Interrupt); |
Здесь, Handler указывает имя защищенной процедуры без параметров, которая описана в защищенном описании, а Interrupt является выражением типа Interrupt_ID.Защищенное описание должно выполняться на уровне библиотеки, то есть, оно не может быть вложено в тело подпрограммы, тело задачи или инструкцию блока.Однако, если защищенное описание описывает защищенный тип, то индивидуальный защищенный объект этого типа может быть размещен в этих местах.Динамическое размещение с помощью аллокатора new обладает большей гибкостью: аллокация защищенного объекта с обработчиком прерывания устанавливает обработчик прерывания, ассоциированный с этим защищенным объектом, а деаллокация защищенного объекта восстанавливает ранее установленный обработчик.Выражение типа Interrupt_ID не обязано быть статическим.В частности, его значение может зависить от дискриминанта защищенного типа. Например:
package Nested_Handler_Example is protected type Device_Interface (Int_ID : Ada.Interrupts.Interrupt_ID) is procedure Handler; pragma Attach_Handler (Handler, Int_ID);end Nested_Handler_Example; |
Во втором случае, устанавливаемый с использованием вложенного стиля обработчик прерывания, идентифицируется последующей директивой компилятора Interrupt_Handler, которая появляется в защищенном описании и имеет следующий вид:
pragma Interrupt_Handler (Handler, Interrupt); |
Здесь, также как и в первом случае, Handler указывает имя защищенной процедуры без параметров, которая описана в защищенном описании, а Interrupt является выражением типа Interrupt_ID.Как и в случае использования директивы Attach_Handler, защищенное описание не может быть вложено в тело подпрограммы, тело задачи или инструкцию блока.Кроме того, эта директива компилятора имеет одно дополнительное ограничение: если защищенная процедура описана для защищенного типа, то объекты этого типа также не могут быть расположены в этих местах.Следовательно, они должны создаваться динамически, с помощью new.
Защищенные типы и объекты Защищенные подпрограммы
Защищенные типы и объекты
Защищенные подпрограммы
Защищенные модули (типы и объекты) Ады инкапсулируют данные и позволяют осуществлять доступ к ним с помощью защищенных подпрограмм или защищенных входов.Стандарт языка гарантирует, что в результате выполнения кода таких подпрограмм и входов изменение содержимого данных будет производиться в режиме взаимного исключения без необходимости создания дополнительной задачи.
Защищенный модуль может быть описан как защищенный тип или как одиночный защищенный объект, что аналогично одиночной задаче.В последнем случае предполагается, что защищенный объект имеет соответствующий анонимный тип.Следует учитывать, что защищенный тип является лимитированным и, тем самым, не обладает предопределенными операциями присваивания или сравнения.
Подобно задаче или пакету, защищенный модуль имеет спецификацию и тело.Спецификация описывает протокол доступа к защищенному модулю (интерфейс), и может содержать спецификации процедур, функций и входов защищенного модуля.Тело описывает детали реализации протокола доступа к данным защищенного модуля и, как правило, содержит тела защищенных подпрограмм и входов.Подобно задачам и записям, защищенный модуль может иметь дискриминанты дискретного или ссылочного типа.
В качестве простой иллюстрации рассмотрим пример следующго одиночного защищенного объекта:
-- спецификация защищенного объекта protected Variable is function Read return Item; procedure Write(New_Value : Item);private Data : Item;end Variable;-- тело защищенного объекта protected body Variable is function Read return Item is begin return Data; end; procedure Write (New_Value : Item) is begin Data := New_Value; end;end Variable; |
Защищенный объект Variable предоставляет управляемый доступ к приватной переменной Data типа Item.Функция Read позволяет читать, а процедура Write - обновлять текущее значение переменной Data.
Защищаемые данные и данные о состоянии объекта должны быть помещены в приватную часть спецификации.Смысл этого заключается в том, что приватная часть не доступна клиенту непосредственно, а наличие всей информации в интерфейсе защищенного объекта необходимо компилятору для эффективного распределения памяти.
Защищенные процедуры предусматривают взаимно исключающий доступ к данным защищенного модуля по чтению и/или записи.Защищенные функции предоставляют одновременный доступ к данным защищенного модуля только по чтению, что подразумевает одновременное выполнение множества вызовов функций.Однако вызовы защищенных процедур и защищенных функций остаются взаимно исключающими.Порядок, в котором разные задачи ожидают выполнения защищенных процедур и защищенных функций, стандартом не определяется.Однако поддержка требований приложения D (Annex D) стандарта Ada95, в котором указаны требования для систем реального времени, позволяет сделать некоторые предположения о возможном порядке выполнения подпрограмм.
Для обращения к защищенным подпрограммам используется традиционная точечная нотация:
X := Variable.Read; . . . Variable.Write (New_Value => Y); |
Внутри тела защищенного объекта допускается несколько подпрограмм, при этом реализация Ада-системы будет гарантированно осуществлять вызовы подпрограмм по принципу взаимного исключения (подобно монитору).
Защищенные входы и барьеры
Защищенные входы и барьеры
По аналогии со входами задач, защищенный модуль может иметь защищенные входы.Действия, выполняемые при вызове защищенного входа, предусматриваются в его теле.Защищенные входы подобны защищенным процедурам в том, что они гарантируют взаимно исключающий доступ к данным защищенного модуля по чтению и/или записи.Однако внутри тела защищенного модуля защищенные входы предохраняются логическим выражением, которое называют барьером, а результат вычисления этого логического выражения должен иметь предопределенный логический тип Standard.Boolean.
Если при вызове защищенного входа значение барьера есть False, то выполнение вызывающей задачи приостанавливается до тех пор, пока значение барьера не станет равным True и внутри защищенного модуля будут отсутствовать активные задачи (задачи, которые выполняют тело какого-либо защищенного входа или какой-либо защищенной подпрограммы).Следовательно, вызов защищенного входа может быть использован для реализации условной синхронизации.
Можно заметить, что существует строгая параллель между инструкцией принятия с охранным условием из тела задачи и телом защищенного входа с барьерным условием для защищенного модуля.При этом затраты времени на проверку барьерного условия для защищенного входа значительно меньше, чем затраты времени на проверку охранного условия инструкции принятия.
Хорошим примером решения упоминавшейся ранее проблемы "поставщик-потребитель" служит реализации циклического буфера с помощью защищенного типа:
-- спецификация защищенного типа protected type Bounded_Buffer is entry Put(X: in Item); entry Get(X: out Item);private A: Item_Array(1 .. Max); I, J: Integer range 1 .. Max := 1; Count: Integer range 0 .. Max := 0;end Bounded_Buffer;-- тело защищенного типа protected body Bounded_Buffer is entry Put(X: in Item) when Count < Max is begin A(I) := X; I := I mod Max + 1; Count := Count + 1; end Put; entry Get(X: out Item) when Count > 0 is begin X := A(J); J := J mod Max + 1; Count := Count - 1; end Get;end Bounded_Buffer; |
Здесь предусмотрен циклический буфер, который позволяет сохранить до Max значений типа Item.Доступ обеспечивается с помощью входов Put и Get.Описание объекта (переменной) защищенного типа осуществляется традиционным образом, а для обращения к защищенным входам, как и для обращения к защищенным подпрограммам, используется точечная нотация:
declare . . . My_Buffer: Bounded_Buffer; . . . begin . . . My_Buffer.Put(X); . . . end; |
Заметим, что так же как и в случае вызова входа задачи, вызывающая задача может использовать инструкции отбора для временного или условного вызова защищенного входа. Например:
. . . select My_Buffer.Get(X); . . . -- список инструкций else . . . -- список инструкций end select; |
Поведение защищенного типа контролируется барьерами.При вызове входа защищенного объекта выполняется проверка соответствующего барьера.Если значение барьера False, то вызов помещается в очередь, подобно тому, как это происходит при вызове входа задачи.При описании переменной My_Buffer буфер - пуст, и, таким образом, барьер для входа Put имеет значение True, а для входа Get - False.Следовательно, будет выполняться только вызов Put, а вызов Get будет отправлен в очередь.
В конце выполнения тела входа (или тела процедуры) защищенного объекта производится вычисление значений всех барьеров, у которых есть задачи, ожидающие в очереди, разрешая, таким образом, обработку обращений к входам, которые ранее были помещены в очередь в результате того, что значение барьера было вычислено как False.Таким образом, после завершения обработки первого же вызова Put, если вызов Get уже находится в очереди, то значение барьера для Get будет вычислено заново, и это позволит обслужить ожидающий в очереди вызов Get.
Важно понять, что здесь нет задачи, которая непосредственно ассоциирована с самим буфером.Вычисление барьеров эффективно выполняется системой времени выполнения.Барьеры вычисляются когда вход вызывается впервые и когда происходит что-либо, что может повлиять на состояние барьера ожидающей задачи.
Таким образом, барьеры вычисляются заново только в конце выполнения тела защищенного входа или тела защищенной процедуры, но не в конце выполнения тела защищенной функции, поскольку вызов функции не может повлиять на внутреннее состояние защищенного объекта и, как следствие, не может изменить значения барьеров.Такие правила гарантируют эффективность реализации защищенного объекта.
Следует обратить особое внимание на то, что барьер может ссылаться на значение глобальной переменной.Значение такой переменной может быть изменено независимо от вызова защищенной процедуры или защищенного входа объекта (например, она может быть изменена какой-либо другой задачей или даже в результате вызова защищенной функции), и такие изменения не могут быть обработаны достаточно точно.Поскольку подобные случаи требуют дополнительной внимательности, то рекомендуется воздерживаться от использования глобальных переменных в барьерах.
Необходимо понимать, что защитный механизм барьеров налагается на обычное взаимное исключение защищенной конструкции, предоставляя, таким образом, два различных уровня защиты.В конце защищенного вызова уже находящиеся в очереди обращения к входам (чей барьер теперь имеет значение True) более приоритетны по сравнению с другими защищенными вызовами.С другой стороны, пока защищенный объект занят обработкой текущего вызова (а также любых уже готовых к обработке, но находящихся в очереди вызовов), проверка значения барьера, для вновь поступившего вызова входа, не может быть даже произведена.
Это имеет одно важное следствие: если состояние защищенного объекта изменяется, и существует задача, которая ожидает новое состояние защищенного объекта, то такая задача получает доступ к ресурсу.При этом гарантируется, что состояние ресурса, когда такая задача получает к нему доступ, будет таким же самым, как и в момент принятия решения о предоставлении этой задаче доступа к ресурсу.Таким образом, полностью предотвращаются неудовлетворенные опросы и состязание задач за ресурс.
Основопологающая концепция защищенных объектов подобна мониторам.Они являются пассивными конструкциями с синхронизацией, предусматриваемой системой времени выполнения языка.Однако защищенные объекты, по сравнению с мониторами, обладают большим преимуществом: протокол взаимодействия с защищенными объектами описывается барьерными условиями (в правильности которых достаточно легко убедиться), а не низкоуровневыми и неструктурируемыми сигналами, используемыми в мониторах (как в языке Modula).
Другими словами, защищенные объекты обладают существенными преимуществами высокоуровневых охранных условий модели рандеву, но без дополнительных издержек по производительности, обусловленных наличием дополнительной задачи.
Защищенные модули позволяют осуществлять очень эффективную реализацию различных сигнальных объектов, семафоров и подобных им парадигм:
-- спецификация защищенного типа protected type Counting_Semaphore (Start_Count : Integer := 1) is entry Secure; procedure Release; function Count return Integer;private Current_Count : Integer := Start_Count;end;-- тело защищенного типа protected body Counting_Semaphore is entry Secure when Current_Count > 0 is begin Current_Count := Current_Count - 1; end; procedure Release is begin Current_Count := Current_Count + 1; end; function Count return Integer is begin return Current_Count; end;end Counting_Semaphore; |
Особенностью этого примера является то, что Start_Count является дискриминантом.При вызове входа Secure опрашивается барьер этого входа.Если результат опроса барьера есть False, то задача ставится в очередь, а ожидание обслуживания становится True.
Следует заметить, что этот пример демонстрирует реализацию общего семафора Дейкстры (Dijkstra), где вход Secure и процедура Release соответствуют операциям P и V (Dutch Passeren и Vrijmaken), а функция Count возвращает текущее значение семафора.
Очевидно, что в очереди обращения к защищенному входу может одновременно находиться несколько задач.Так же как и в случае с очередями к входам задач, очереди к входам защищенных объектов обслуживаются в порядке поступления вызовов (FIFO - First-In-First-Out).Однако в случае поддержки требований приложения D (Annex D) стандарта Ada95, в котором указываются требования для систем реального времени, допускается использование другой дисциплины обслуживания очереди.
Для защищенного типа можно описать семейство защищенных входов; это делается заданием дискретного типа в спецификации входа, подобно описанию семейств входов для задач.Однако, в отличие от задач, нет необходимости предусматривать самостоятельно выделенное тело для входа, принадлежащего такому семейству.Индекс семейства может использовать барьер, ассоциируемый с таким входом (обычно такой индекс используется в качестве индекса массива значений логического типа Standard.Boolean).
Завершение задачи
Завершение задачи
Перед выходом, владелец (master) осуществляет выполнение конструкций, которые включают очистку (finalization) локальных объектов после их использования (и после ожидания всех локальных задач).Каждая задача зависит от одного владельца, или более: Если задача была создана в результате обработки аллокатора ссылочного типа, то она зависит от каждого владельца, который включает элаборацию описаний самого отдаленного предка указанного ссылочного типа. Если задача была создана в результате элаборации описания объекта, то она зависит от каждого владельца, который включает эту элаборацию.
Кроме того, если задача зависит от указанного владельца, то считается, что она также зависит от задачи, которая выполняет этого владельца, и (рекурсивно) от любого владельца такой задачи.
В первую очередь, зависимая задача дожидается очистки (finalization) владельца.Затем, осуществляется очистка каждого объекта с таким же уровнем вложенности как и у очищаемого владельца, если этот объект был успешно проинициализирован и до сих пор существует.Примечательно, что любой объект, уровень вложенности которого глубже чем у владельца, уже не должен существовать, поскольку он должен был очиститься внутренним владельцем.Таким образом, после выхода из владельца, не очищенными остаются только те объекты, уровень вложенности которых меньше чем у владельца.
Значения полей записи по-умолчанию
Значения полей записи по-умолчанию
При описании записи, полям записи могут быть назначены значения по-умолчанию. Эти значения будут использоваться всякий раз при создании экземпляра записи данного типа (до тех пор пока они не будут инициализированы другими значениями).
type Bicycle is record Frame : Construction := CromeMolyebdenum; Maker : Manufacturer; Front_Brake : Brake_Type := Cantilever; Rear_Brake : Brake_Type := Cantilever; end record; |