Rationale for Ada 2005: 6a Containers
RUSTOPBACKNEXT
ENG |
2. Lists and vectors
@ We will first consider the list container since in some ways it is the simplest. Here is its specification interspersed with some explanation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale for Ada 2005: 6a Containers
ENGRUSTOPBACKNEXT2. Списки и вектора
@ Сначала мы рассмотрим контейнерные списки т.к. из всех контейнеров они является наиболее простым. Рассмотрим спецификацию:
|
@ Два настраиваемых параметра здесь тип элемента списка и определение функции равенства для сравнения элементов. Это равенство должно быть таким, что X = Y эквивалентно Y = X.
@ Контейнерный список это объект типа List. Он теговый, так как он будет неизбежно осуществлён как управляемый (controlled) тип. Факт того, что он теговый даёт все преимущества объектно-ориентированного программирования. С одной стороны это позволяет нам использовать префиксную нотацию:
|
@ вместо:
|
@ Тип Cursor - важное понятие. Он обеспечивает средства доступа к индивидуальным элементам в контейнере. Помимо того, что он содержит ссылку на элемент он также идентифицирует и сам контейнер. Это позволяет выполнить соответствующие проверки для гарантии что мы случайно не влезаем в элемент в неправильном контейнере.
@ Константы Empty_List и No_Element, как и ожидается, обеспечивают значения по умолчанию для объектов типа List и Cursor соответственно.
|
@ Функция "=" сравнивает два списка. Она возвращает True только если оба списка имеют одинаковый ряд элементов, и у соответствующих элементов одинаковые значения в соответствии с настраиваемой функцией "=" для сравнения элементов. Назначение подпрограмм Length, Is_Empty и Clear понятно из названия.
@ Заметим, что выражения A_List = Empty_List, Is_Empty (A_List) и Length (A_List) = 0 эквивалентны.
|
@ Это первые операции где мы встречаем использование курсора. Функция Element берет курсор и возвращает значение соответствующего элемента (напомним, что курсор идентифицирует как список так и элемент непосредственно). Процедура Replace_Element заменяет значение элемента, идентифицированного курсором данным значением; это делается копированием, конечно.
@ Обратите внимание, что Replace_Element имеет и список и курсор в качестве параметров. Есть две причины для этого. Первая, это то что нужно обеспечить проверку того, что курсор действительно идентифицирует элемент в данном списке. Другая, состоит в том, что нужно гарантировать, что у нас действительно есть доступ для записи в контейнер (у параметра есть режим in out). Иначе было бы возможно изменить контейнер даже при том, что он был бы представлен константой. Общий принцип такой, у любой операции, которая изменяет контейнер должен быть контейнер в качестве параметра, тогда как операция, которая только читает значение такого параметра нет (например, функция Element).
|
@ Эти процедуры обеспечивают непосредственный доступ к элементу. Один из параметров - курсор, идентифицирующий элемент, а другой - ссылка на процедуру, которая вызывается с этим элементом в качестве параметра. В случае Query_Element мы можем только читать элемент, тогда как в случае Update_Element мы можем изменить его также, так как режим параметра процедуры in out. Отметим, что Update_Element также принимает контейнер в качестве параметра, по причинам только упомянутым при обсуждении Replace_Element.
@ Читатель мог бы задаться вопросом, есть ли какое-нибудь различие между вызовом функции Element для получения текущего значени элемента и использования более сложного механизма Query_Element. Ответ состоит в том, что функция Element делает копию значения, тогда как Query_Element предоставляет ссылку на значение, не делая копию. (аналогично для Replace_Element и Update_Element). Это не имело бы значения для простого списка целых чисел, но имело бы значение, если бы элементы были большими или управляемого (controllrd) типа (возможно даже списки непосредственно).
|
@ Эта процедура перемещает список из Source в Target после первоначальной очистки Target. После выполнения операции источник не очищается, и Length (Source) не становится равным нулю.
|
@ Эти три процедуры позволяют добавлять в список один или более идентичных элементов. Позиция в списке обозначается параметром Before, если указать No_Element, то новые элементы будут добавляються в конец списка. Вторая процедура подобна первой но ещё и возвращает курсор первого из добавленных элементов. Третья походит на вторую, но новые элементы принимают свои значения по умолчанию. Отметим значение по умолчанию одного для ряда элементов.
|
@ Эти процедуры добавляют один или более новых элементов в начало или конец списка соответственно. Ясно что эти операции могут быть сделаны использованием процедуры Insert, но это достаточно часто используемые операции для которых предусмотрены особые процедуры.
|
@ Эти процедуры удаляют один или более элементов в соответствующей позиции. В случае Delete, параметр Position устанавливается в No_Element по возвращению. Если число элементов в списке справа от Position меньше чем Count тогда удаляются столько элементов сколько возможно (ясно, что это приводит к полной очистке контейнера в случае Delete_First и Delete_Last).
|
@ Эта процедура делает очевидную из названия вещь. Было бы правильно назвать её Reverse, но к сожалению, это является зарезервированным словом.
|
@ Эта удобная процедура обменивает значения между двумя элементами, обозначенных этими двумя курсорами. Элементы должны быть в данном контейнере иначе возбуждается исключение Program_Error. Отметим, что курсоры не изменяются.
|
@ Эта процедура выполняет операцию низкого уровня по взаимной перестановке ссылок, а не значений, которая может быть намного быстрее, если элементы являются большими. В векторном пакете нет ничего подобного.
|
@ Эти три процедуры позволяют перемещать элементы (без копирования). Позиция для перемещения обозначается параметром Before, если его значение - No_Element, то элементы добавляются в конец. Первая процедура перемещает все элементы из списка Source в список Target в позицию Before; как и в процедуре Move после операции источник опустошается и Length (Source) становится нулем. Вторая процедура перемещает единственный элемент из Position списка Source в список Target длина которого увеличивается, тогда как длина источника уменьшается на еденицу; Position обновляется к её новому местоположению в Target.
@ Третья процедура перемещает единственный элемент в пределах списка и длина при этом не изменяется (обратите внимание, что формальный параметр называется Container, а не Target в этом случае). Соответствующих операций в векторном пакете нет, потому что, подобно Swap_Links, мы только перемещаем ссылки и не копируем элементы.
|
@ Назначение большинства этих процедур очевидна из названия. Функция Find ищет элемент с указанным значением начиная с данной позиции курсора (или сначала списка, если позиция - No_Element); если никакой элемент не найден, тогда возвращается значение No_Element. Reverse_Find делает то же самое, но обратном порядке. Отметим, что равенство, используемое для сравнения в Find и Reverse_Find, состоит в том что определено универсальным параметром "=".
|
@ Здесь возвращается False, если курсор не идентифицирует элемент; например, если это - No_Element.
|
@ Эти процедуры применяют процедуру, определяемую параметром Process для каждого элемента контейнера в соответствующем порядке.
|
@ Этот настраиваемый пакет выполняет сортировку и операцию слияния используя порядок определяемый настраиваемым формальным параметром. Отметим, что мы используем настраиваемое средство, а не ссылку на подпрограмму передаваемую параметром, когда формальный процесс задаётся оператором. Причина этого состоит в том, что у предопределенных операций есть соответствующие Intrinsic средства, и нельзя передать встроенную операцию как ссылку на подпрограмму передаваемую как параметр. Функция Is_Sorted возвращает True, если контейнер уже отсортирован. Процедура Sort упорядочивает элементы в необходимом порядке, при этом не происходит никакого копирования, так как перемещаются только ссылки. Процедура Merge берет элементы из Source и добавляет их к Target. После этого Length (Source) обнуляется. Если оба списка были отсортированы перед слиянием тогда также сортируется и результат.
@ В конце мы имеем:
|
@ Если читатель забрался так далеко, он вероятно понял, как использовать этот пакет, и таким образом, приведение обширных примеров является излишним. Всё же рассмотрим простой стек чисел с плавающей запятой:
|
@ Здесь мы нуждаемся в некотором пояснении. Пакет списков иллюстрируется в пакете Stack, и объект The_Stack - конечно же списковый контейнер. Остальные являются действительно прямыми. Мы конечно можем использовать префиксную нотацию повсюду как показано в Push.
@ Важный момент должен быть упомянут относительно списков (и контейнеров также). Это то, что приводит обычно к исключению Constraint_Error или Program_Error. Это особенно относится к процедурам Process в Query_Element, Update_Element, Iterate и Reverse_Iterate. Понятия вмешательства в курсороы и элементы введены чтобы обеспечить правило "Вы не должны нарушать ваш контейнер".
@ Вмешательство в курсоры происходит, когда элементы добавляются к или удаляются из контейнера (например, вызовом процедуры Insert), тогда как вмешательство в элементы означает заменять элемент (вызовом Replace_Element, например). Вмешательство в элементы является большим грехом и включает вмешательство в курсоры. Процедура Process в Query_Element и Update_Element не должна вмешиваться в элементы, и процедура Process в других случаях не должна вмешиваться в курсоры. Читатель мог бы посчитать это довольно странным, что Update_Element не позволяется вмешиваться в элементы, так как её цель состоит в том, чтобы обновить элемент; это возвращает нас к сути упомянутого ранее, что элемент обновления предоставляет ссылку к существующему элементу на месте через параметр Process, и это позволено вызовом Replace_Element в пределах Process, вмешался бы. Вмешательство приведит к возбуждению исключения Program_Error.
@ Теперь рассмотрим векторный пакет. Начало его спецификации:
|
@ Он подобен пакету списков за исключением дополнительного настраиваемого параметра Index_Type (отметим что это - целочисленный, а не дискретный тип). Этот дополнительный параметр отражает идею, что вектор - по существу массив, и мы можем индексировать непосредственно массив.
@ Фактически векторный пакет позволяет нам обратиться к элементам используя индекс или используя курсор. Таким образом, много операций таких же как и в пакете списков:
|
@ Если мы используем индекс, тогда всегда есть другой параметр, идентифицирующий также вектор. Если мы используем курсор тогда, векторный параметр отсутствует, если вектор неизменен, как имеет место с функцией Element. Помните, что мы заявили ранее, что курсор идентифицирует и элемент и контейнер, но если контейнер изменяется как в случае Replace_Element тогда, контейнер нужно передать также, чтобы гарантировать ссылку на запись и выполнить проверку что курсор действительно идентифицирует элемент в правильном контейнере.
@ Есть также функции First_Index и Last_Index:
|
@ Они возвращают значения индекса первого и последнего элементов соответственно. Функция First_Index всегда возвращает Index_Type'First, тогда как Last_Index возвратит No_Index, если вектор будет пуст. Функция Length возвращается Last_Index-First_Index+1, который будет нулем, если вектор пуст.
@ Отметим, что раздражающий подтип Extended_Index должен быть введен, чтобы справиться с конечными значениями. У постоянного No_Index есть значение Extended_Index'First, который равен Index_Type'First-1.
@ Есть следующие операции для преобразований между индексом и курсором:
|
@ Это возможно немного более грязно, чтобы использовать индекс и векторные параметры из-за вопросов относительно диапазона значений индекса, но вероятно немного быстрее и возможно более знакомо. Иногда конечно использование индекса является целой сущностью проблемы. В статье посвящённой ссылочным типам мы рассматривали использование процедуры Update_Element, чтобы удвоить значения тех элементов вектора, индекс которых был в диапазоне 5 - 10. Это было бы утомительно с курсорами.
@ Но преимущество использования курсоров состоит в том, что (если определенных операций избегают) легко заменить использование векторов списками.
@ Например вот пакет стека, перезаписанный, чтобы использовать векторы
|
@ Таким образом, изменения действительно небольшие и могут быть быстро сделаны простым редактированием.
@ Отметим, что индексный параметр был задан как Natural, а не Integer. Использование Integer не будет работать, так как попытка иллюстрировать подтип Extended_Index вызовет исключение Constraint_Error при выполнении Integer'First-1. Но в любом случае для индексного диапазона контейнера начинаться с 0 (или 1) более естественно чем с наименьшего отрицательного значения Integer'First.
@ Есть другие важные свойства векторов, которые должны быть упомянуты. Первое - это понятие ёмкости. Границы векторов корректируемы и могут расширяться в случае добавления новых элементов.
@ Однако, это могло бы привести к большому количеству расширений и копирований и поэтому мы можем установить ёмкость контейнера вызовом:
|
@ Имеется также функция:
|
@ которая возвращает текущую ёмкость. Отметим, что Length (V) не может превысить Capacity (V), но может быть намного меньше.
@ Если мы добавляем элементы к вектору, длина которого равна ёмкости, в этом случае не возникнет никаких проблем. Ёмкость будет расширена автоматическим вызовом Reserve_Capacity. Таким образом, пользователь вовсе не обязан вегда устанавливать ёмкость, однако если это не сделать, это может привести к потере производительности.
@ Есть также понятие "пустых элементов". Это - элементы, значения которых не были установлены.
@ Нет такого понятия связанного со списками. Это ошибка границы пытаться читать пустой элемент. Пустые элементы возникают, если мы объявляем вектор вызывая функцию:
|
@ как здесь:
|
@ Есть также намного более безопасный способ:
|
@ который инициализирует все элементы вектора значением New_Item.
@ Имеется также процедура:
|
@ Она изменяет длину вектора. Это может потребовать, чтобы элементы были удалены (с конца) или были добавлены новые (пустые).
@ Последний способ получить пустой элемент, вызывая одну из функций:
|
@ Они вставляют заданное число пустых элементов, определяемое параметром Count в указанном месте. Существующие элементы сдвигаются вправо по мере необходимости. Они не должны быть спутаны с версиями Insert, которые не обеспечивают явное значение для элементов - в тех случаях, новые элементы берут свои значения по умолчанию.
@ Мы должны быть внимательны при использовании пустых элементов. Например мы не должны сравнивать два вектора используя "=", если у них есть пустые элементы, потому что это подразумевает их чтение. Но большое преимущество пустых элементов состоит в том, что они обеспечивают быстрый способ выделить пространство в векторе, который может тогда быть заполнен в соответствующими значениями. Одна большая операция намного быстрее чем много небольших.
@ Для законченности мы кратко упоминаем наличие некоторых подпрограмм, которые уникальны для векторного пакета.
@ Есть ещё несколько версий процедуры Insert:
|
@ Они вставляют копию вектора в другой вектор (а не один элемент).
@ Имеются также соответствующие версии Prepend и Append:
|
@ Наконец, есть четыре функции "&" которые связывают вектора и элементы аналогичные как для типа String. Их спецификация:
|
@ Таким образом, следующие две строки эквивалентны:
|
@ Результат уних одинаков, но использование "&" менее эффективено из-за вовлеченного дополнительного пространства. Но "&" знакомая операция и так предоставлена для удобства.
2010-10-24 00:26:57
. .