Приведенная на рис. 2.2 графическая структура является хорошим руководством для приводимых здесь частей программы, а также для программ, приведенных в приложениях В-Д. Мы полагаем, что читатель по-прежнему не знаком с языком Ада и будем поэтому разбирать программу снизу вверх, т. е. в том порядке, в каком их просматривал бы компилятор. В соответствии с этим на рис. 2.6 приводится текст программы для пакета Stock_Types_And_Constants.
package Stock_Types_And_Constants is -- Этот пакет не имеет тела. subtype long_string is string (30); type dollars is delta 0.01 range 0.00..l000000.00; -- Максимально допустимое значение -- миллион долларов. -- Точность - до ближайшего цента. type stock _code_pair is record code: string (1..4); -- Сокращение имени пакета акций. exch: string (1..4); -- Сокращение для фондовой биржи. end record; type stock_name_info is record print_name: long_string; stock_code: stock_code_pair; end record; type date is record day: integer range 1..31; month: integer range 1..12; year: integer range 1900..4000; end record; type buy_sell_type is (buy, sell); type buy_sell_record(buy_sell: buy_sell_type) is record stock_name: stock_name_info; buy_date: date; num_shares: integer; per_share_price: dollars; commission: dollars; case buy_sell is -- Определитель записи. when sell -> -- Дополнительное поле для даты. of_buy_date: date; when others -> -- Без дополнительного поля. null; end case; end record; subtype purchase_record is buy_sell_record(buy); subtype sale_record is buy_sell_record(sell); end Stock_Туpes_And_Constants; Рис. 2.6. Программа пакета Stock_Types_And_Constants.
Наименее очевидными типами и подтипами данных в данном пакете являются:
Тип "dollars" - это неотрицательный тип с фиксированной запятой, принимающий значения до ближайшего целого цента. [Отметим, что в скомпилированной версии, приводимой в приложении В, тип dollars определен как целое число.]
Тип записи "stock_nanne_info" идентифицирует индивидуальный пакет акций по двум его компонентам: "внешнему имени", определенному нами ранее как имя корпорации, и по "внутреннему имени", к которому мы ранее ссылались как к коду корпорации на фондовой бирже. Имя организации имеет тип "long_string" и представляет собой строку символов длиной до 30 символов. Код корпорации на фондовой бирже представлен типом "stock_code_pair".
Тип "buy_sell_type" может принимать два значения: "buy" и "sell". Применение этого типа облегчает определения последних двух типов в данном пакете.
Тип записи "buy_sell_record" используется для определения структуры записей о закупках, являющихся элементами истории закупок, содержащейся в портфеле. Элемент buy_sell_record используется для определения входных аргументов операций по обновлению портфеля. Как достоинство языка Ада, отметим, что запись buy_sell_record может быть определена в двух вариантах: вариант закупки и вариант продажи. Вариант для закупки включает в себя дополнительное поле, в котором указывается дата закупки и число акций. При объявлении элемента типа "buy_sell_record" необходимо указать также дополнительное значение - "buy" или "sell".
С тем чтобы избежать необходимости указывать это значение каждый раз при распределении очередного элемента типа "buy_sell_record", в пакете определены подтипы "purchase_record" и "sale_record". Подтипы являются одними из нескольких весьма удобных конструкций типов данных, разрешенных в языке Ада. Используемые здесь объявления подтипа представляют собой всего лишь удобный механизм переопределения имен. [Подробности даны в третьей главе справочного руководства по языку Ада.]
generic -- Родовой пакет Queue_Mgr. type item is private -- item является параметром. (а) Родовой оператор package Queue_Mgr is -- Этот пакет является пакетом-преобразователем. -- Объявление очереди приватного типа. -- Спецификации для: -- функций с именами: Create и Is_empty -- процедур с именами: Add и Remove -- исключения с именем: underflow private -- Здесь приводятся объявления типа, определяющие структуру очереди. -- Очередь - это однонаправленный список элементов с заданными хвостом и -- заголовком. -- Здесь приводится структура экземпляра очереди: end Queue_Mgr; -- Конец части спецификации. (б) Часть спецификации package body Queue_Mgr is -- В данной части приводятся спецификации и тела для: -- функций с именами: Create и Is_empty и -- процедур с именами: Add и Remove. -- Для данного пакета локальных процедур не требуется. -- Если бы они были необходимы, то их описание приводилось бы здесь и -- начиналось бы со слова begin. end Queue_Mgr; (в) Часть тела Рис. 2.7. Скелетная структура родового пакета Queue_Mgr.
На рис. 2.7 приведена скелетная структура пакета Queue_Mgr. Полный текст программы пакета Queue_Mgr приведен в приложении В. Части (б) и (в) образуют обычные две части пакета - спецификацию и тело. Пакет превращается в родовой, если он начинается с оператора generic, как это видно в части (а). Видимая часть в части (б) спецификации определяет тип данных "queue" ("очередь"), содержащую ни одного, один или несколько элементов, четыре операции с очередью и exeption (исключение), которое регистрирует потерю значимости (попытка удалить элемент из пустой очереди). Экземпляр типа queue создается посредством вызова операции Create (создать). Операция Is_empty (пуст) определяет, пуста или не пуста очередь. Операции Add (добавить) и Remove (удалить) соответственно добавляют или удаляют элементы из очереди.
Подробное представление элемента типа "очередь" дается в приватном (private) разделе части спецификации, поскольку это представление не имеет отношения к пользователям пакета Queue_Mgr. Тело пакета Queue_Mgr содержит элементы, реализующие видимые операции Create, Add, Remove и Is_Empty. Мы рассмотрим их позднее.
В данном пакете представляет интерес то обстоятельство, что ни в одной из частей (б) и (в) мы не уточняем значение "item". Значение этого идентификатора в пакете не приводится. Использование родового оператора делает идентификатор item родовым параметром данного пакета.
Если нам необходимо создать некоторый элемент для Queue_Mgr, то мы должны указать для этого параметра соответствующий аргумент. Это показано в примере на рис. 2.8, в котором пакет Purchase_Queue_Mgr представлен как экземпляр родового пакета Queue_Mgr.
--------------------------- Purchase_Queue_Mgr ---------------------------- with Queue_Mgr, Stock_Types_And_Constants; package Purchase_Queue_Mgr is new Queue_Mgr ( item -> Stock_Types_And_Constants.purchase_record); -- Создание экземпляра родового пакета Queue_Mgr для управления -- очередями закупок, формируемых при помощи Portfolio_Mgr в экземплярах -- портфеля. Рис. 2.8. Пакет Purchase_Queue_Mgr как экземпляр пакета Queue_Mgr с типом, связанным с типом purchase_record.
Читатель может заметить, что созданию экземпляра пакета предшествует список with. Список with дает указание компилятору обеспечить доступ к объектам, объявленным в перечисленных пакетах.
Полные возможности родового пакета, в частности пакета Queue_Mgr, реализуются в том случае, если в одной и той же программе создается несколько экземпляров пакета Queue_Mgr с различными аргументами, соответствующими родовому параметру item. В рассматриваемом нами примере экземпляр Queue_Mgr создается только один раз, поскольку нам необходимо управлять записями о закупках только для общих пакетов акций.
Однако более полный портфель акций может включать в себя записи о закупках ценных бумаг, казначейских билетов, коммерческих товарах и т. п. Для каждой из таких закупок может потребоваться отдельная структура записи. В таких случаях в нашу программу необходимо будет включить несколько экземпляров Queue_Mgr. Таким образом, использование одного "шаблонного" пакета позволяет получить несколько различных пакетов.
Теперь мы в состоянии подробно рассмотреть состав пакета Queue_Mgr. На рис. 2.9 показана часть спецификации пакета Queue_Mgr. (Этот рисунок представляет собой более подробную версию рис. 2.7, б.)
package Queue_Mgr is -- Данный пакет есть пакет-преобразователь. type queue is private; null_queue: constant queue; -- Пример "отложенной константы". function Create return queue; -- Функция: -- Возвращает ссылку к пустому экземпляру очереди. procedure Add ( Е: in item; Q: in queue; to_front: in boolean :=false); -- Необязательный третий параметр. -- Процедура -- Добавляет входной элемент Е в логический "конец" структуры очереди, -- адресуемой как Q, -- если только в to_front не задан входной -- аргумент "истина". procedure Remove ( U: out item; Q: in queue); -- Процедура -- Удаляет элемент U из логического "начала" структуры очереди -- адресуемой как Q. U есть выходной параметр. function Is_empty ( Q: in queue) return boolean; -- Функция -- Возвращает значение "ложь" для экземпляра непустой очереди. underflow: exception; -- Исключение возникает в том случае, если -- в пустую очередь передается Remove. -- Передается вызывающей программе. private type queue_element; -- Ссылка вперед. type queue_element_ptr is access queue_element; type queue_element is record info: item; -- Тип item является параметром. next: queue_element_ptr; -- Связь со следующим элементом очереди. end record; type queue_rep is -- Представление структуры record -- очереди на верхнем уровне, head: queue_element_ptr; tail: queue_element_ptr; end record; type queue is access queue_rep; null_queue: constant queue:=null; end Queue_Mgr; Рис. 2.9. Часть спецификации пакета Queue_Mgr.
На рис. 2.9 приведена спецификация параметров для трех операций: Add, Remove и Is_empty. В языке Ада (формальный) параметр может соответствовать своему аргументу (фактическому параметру) одним из трех следующих способов: как только входной параметр, in; как только выходной параметр, out; как и входной и выходной параметр, in out.
Параметру, который является только входным, может быть присвоено начальное значение. (См., например, параметр to_front в операции Add на рис. 2.9.) Для каждого параметра мы устанавливаем его тип и режим. Так, в спецификации для Remove параметр Q определяется как:
Q: in queue
Наиболее употребительный режим - это режим "только ввода", обозначаемый зарезервированным словом in. Этот режим означает, что данный параметр предназначен "только для чтения" и в него не может помещаться информация. Компилятор языка Ада обычно гарантирует, что аргумент с параметром in в процедуре Р не может быть модифицирован этой процедурой. Этот параметр устанавливается в языке Ада по умолчанию, но в наших программах мы обычно указываем его явно.
Поскольку пакет Purchase_Queue_Mgr является пакетом-преобразователем, то в вызове операции Remove для удаления элемента необходимо указывать очередь, из которой данный элемент удаляется. Параметр Q определяет ссылку к очереди. Поскольку в процессе операции Remove этот параметр не модифицируется, то режим для Q есть in.
Второй режим - это режим "только вывод", обозначаемый зарезервированным словом out. Операция Remove возвращает удаленный элемент, сохраняя его в параметре U. По этой причине, а также потому, что U не содержит никакой входной информации, для него устанавливается режим out:
procedure Remove( U: out item; Q: in queue);
Отметим, что мы описываем операцию Remove как процедуру, но с тем же успехом она может быть описана как функция. Например:
function Remove(Q: in queue) return item;
Третий режим для параметра указывается при помощи двух зарезервированных слов - in out. Эта форма используется в тех случаях, когда параметру Р ставится в соответствие переменная V, значение которой используется в качестве входного параметра при активизации вызываемой подпрограммы, и в процессе активизации значение Р может быть обновлено (т.е. ему может быть присвоено новое значение). Если происходило обновление Р, то при возврате из подпрограммы переменная V будет содержать последнее назначенное для Р значение. (В нашей книге режим in out используется довольно редко.)
[Принцип, по которому устанавливается необходимость обновления фактического параметра V, соответствующего формальному параметру Р, имеющему режим out или in out, определяется требованиями к производительности. Если V - скаляр или переменная ссылочного типа, то обновление V происходит только один раз после возврата из вызова, который рассматривает V как аргумент. Однако если V является массивом, записью или приватным типом, то в зависимости от используемого компилятора каждое новое присваивание Р может привести к немедленному обновлению V (механизм "ссылочного параметра").]
Тип данных для очереди установлен приватным (private) для указания того, что пользователям Queue_Mgr запрещено осуществлять непосредственные операции над компонентами экземпляра очереди. Единственными операциями, разрешенными над элементами типа "очередь", являются отношения равенства (неравенства) и присваивания переменной, включая передачу параметров.
Представление экземпляра очереди показано в операторе private, следующим за видимыми спецификациями. Несмотря на то что любой читатель, прочтя текст программы, может увидеть то, каким образом описана очередь, компилятор языка Ада гарантирует, что подробности этого описания, приводимые в приватной части пакета, видимы только внутри пакета. Вне данного пакета другая программа может знать только лишь факт существования очереди такого типа. Само представление этой очереди определить невозможно.
Как видно из приватной части пакета, экземпляр типа queue_rep представляет собой запись, состоящую из указателей хвоста и заголовка, каждый из которых указывает на элементы очереди. Элемент очереди представляет собой запись, содержащую элемент информации (info) и указатель на следующий элемент очереди (next). Этот указатель имеет тип queue_element_ptr. Неполное объявление имеет вид
type queue_element;
и называется "ссылкой вперед", которая используется для разрешения перекрестных ссылок во взаимно зависимых объявлениях, использующих access, т. е. типы указателей.
package body Queue-Mgr is function Create return queue; is return new queue_rep(head -> null, tail -> null); procedure Add( E: in item; Q: in queue; to_front: in boolean:=false); -- Необязательный третий параметр. is x: queue_element_ptr:=new queue_elennent(info->E, next null); begin case to_front is when false -> -- Нормальный случай. Добавление к хвосту if Q.tail /= null then -- очереди. Q.tail.next:=x; -- Если очередь не пуста, то добавить данный -- элемент в конец очереди. else -- Если очередь пуста, то данный элемент Q.head:=x; -- помещается в ее начало. end if; Q.tail:=x; when others -> -- Cм. подробную программу в приложении B. end Add; procedure Remove( U: out item; Q: in queue); is begin if Q.head = null then raise underflow; else U:=Q.head.info; if Q.head.next = null then -- Q.head последний элемент? Q.head:=null; Q.tail:=null; else Q.head:=Q.head.next; end if; end if; end Remove; function Is_empty( Q: in queue) return boolean is begin return Q.head=null; -- Возвращается истинное значение -- выражения end Is_empty; end Queue_Mgr; Рис. 2.10. Подробное описание тела пакета Queue_Mgr.
Тело Queue_Mgr приведено на рис. 2.10. При написании подпрограммы на языке Ада можно руководствоваться определениями операций Add, Remove и Is_ernpty, приведенными на рис. 2.10. Например, процедура в языке Ада начинается (обязательно) с повторения ее спецификации (приведенной на рис. 2.9), за которой следует ключевое слово is. За ним обычно следует последовательность локальных объявлений, возможно пустых, а за ней - последовательность операторов. В данном случае имеется только один локально определенный объект: переменная х. В любом случае последовательность операторов начинается с ключевого слова begin и заканчивается ключевым словом end.
Операция Create, которая не имеет параметров, возвращает ссылку к вновь распределенной очереди с нулевыми элементами. Указателям начала и конца этой очереди присваиваются нулевые. значения, отражая этим тот факт, что очередь пуста. Операции Add, Remove и Is_empty используют указатели хвоста и заголовка с нулевыми значениями для обнаружения специальных состояний очереди. Минутное размышление убедит читателя в том, что указатель заголовка имеет нулевое значение в том и только в том случае, когда указатель хвоста также нулевой.
В операции Add локальной переменной х типа queue_element_ptr присваивается ссылка на новую распределенную запись типа queue_element. Эта новая распределенная запись есть новый элемент queue_element, который помещается в очередь Q. Его полю info присваивается значение входного аргумента Е. Отметим, что для третьего параметра Add по умолчанию устанавливается значение "ложь" (false). Смысл такой конструкции заключается в том, что при отсутствии третьего аргумента значение "ложь" присваивается параметру to_front. Таким образом, если подпрограмму, вызывающую операцию Add, удовлетворяет установленное по умолчанию для параметра to_front значение "ложь", то указывать третий аргумент нет необходимости. С другой стороны, явно заданное значение для третьего аргумента всегда заменит значение, установленное по умолчанию. На рис. 2.10 показана только та часть операции Add, которая использует значение, устанавливаемое по умолчанию. Полный текст программы для операции Add приведен в приложении В.
Операция Remove аналогична операции Add, но дополнительно дает нам возможность рассмотреть две интересных ситуации: условие исключительных ситуаций и запрос на восстановление динамической памяти. Если в момент вызова операции Remove входной параметр Q очереди не пуст, то выходному параметру U присваивается Q.head.info - информационная часть элемента заголовка очереди. Если в момент вызова Q содержит только один элемент, то индикаторам начала и конца очереди Q присваивается нулевое значение. Если Q содержит более чем один элемент, то значение Q.head.next копируется в Q.head. В любом случае все ссылки к удаленному элементу уничтожаются. Главный эффект от такой ситуации заключается в том, что пространство, отведенное под удаляемый элемент очереди (queue_element), является недоступным и, следовательно, восстанавливаемым. В системе i432 такое восстановление автоматически производится аппаратно-реализованным системным "сборщиком мусора". Если входной параметр очереди Q пуст в момент вызова операции Remove, то возникает исключительная ситуация.
procedure Remove( U: out item; Q: in queue); is begin if Q.head = null then raise underflow; else U:=Q.head.info; if Q.head.next = null then -- Q.head последний элемент? Q.head:=null; Q.tail:=null; else Q.head:=Q.head.next; end if; end if; exception when underflow -> -- Здесь содержатся коды местной обслуживающей подпрограммы end Remove; Рис. 2.11. Местная обслуживающая программа, вставленная в подпрограмму Remove.
Возникшая в программе исключительная ситуация может быть разрешена двумя способами: а) обработкой этой ситуации внутри данной программы или б) передачей ее к вызывающей программе. Программист может сам создать местную обслуживающую программу, в нашем случае - внутри тела подпрограммы Remove, как это показано на рис. 2.11, или, решив обойтись без такой программы, позволить исключительной ситуации передаться назад к программе, вызвавшей Remove, в которой она, возможно, будет обработана. Передача может продолжаться дальше по динамической цепи подпрограммных вызовов до тех пор, пока некоторая программа (возможно, на уровне операционной системы) не возьмет на себя ответственность за обработку данной исключительной ситуации. (При отсутствии локальных программ обслуживания передача в языке Ада исключительной ситуации на обработку происходит по строго определенным правилам. (Подробности см. в гл. II справочного руководства по языку Ада [2].)
Если имеются программы местной обработки, то по завершению работы такой программы управление немедленно передается вызывающей программе. Таким образом, при возникновении исключительной ситуации семантика языка Ада требует прерывания работы текущей программы вне зависимости от наличия программы местной обработки.
Функция Is_empty возвращает булевское значение true, если указатель начала нулевой. В противном случае возвращается значение false. Мы видим, что в языке Ада подпрограмма - функция аналогично подпрограмме процедурного типа определяется повторением своей части спецификации, за которой следует ее тело.
Внимательное чтение программы и комментариев на рис. 2.9 - 2.11 вполне достаточно для того, чтобы убедить впервые изучающего язык Ада читателя в относительной легкости понимания написанных на нем программ.
Часть спецификации пакета Portfolio_Mgr приведена в приложении В. На рис. 2.12 перечислены операции, определенные в этом пакете.
Название операции | |
Create | - Создать |
Record_buy | - Запись о покупке |
Record_sell | - Запись о продаже |
Number_of_stocks | - Число пакетов акций |
Stock_list | - Список пакетов акций |
Shares_and_avg_cost | - Цена каждой доли и средняя цена |
Num_buys | - Число закупок |
History_of_purchases | - История закупок |
Get_portfolio_name | - Запрос имени портфеля |
Рис. 2.12. Видимые операции для Portfolio_Mgr.
Функции каждой из этих операций объясняются в комментариях, следующих за спецификацией каждой операции, поэтому здесь мы их не приводим. В этом разделе мы предпочтем остановить свое внимание на двух определенных в данном пакете приватных типах - stock_summary и portfolio. Из соображений удобства (приватная) спецификация этих типов повторена на рис. 2.13.
private type stock_summary; -- Ссылка вперед. type stock_summary_ptr is access stock_summary; type stock_summary is record stock_name: stock_name_info := (print_name -> "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", stock_code -> (code -> "^^^^", exch -> "^^^^")) num_shares: integer := 0; avg_cost_per_share: dollars :=0.00; next: stock_summary_ptr :=null; purchase_history: Purchase_Queue_Mgr.queue :=Purchase_Queue_Mgr.Create(); end record; type portfolio_ptr is access portfolio; type portfolio is record portfolio_name: long_string :="not yet named ^^^^^^^^^^^^^^^^"; num_diff_stocks_field: integer :=0; stock_list: stock_summary_ptr :=null; end record; Рис. 2.13. Приватная часть спецификации для Portfolio_Mgr.
Эффект вызова операции Portfolio_Mgr_Create заключается в создании начального "портфеля" (извлекаемого из неупорядоченного хранилища) и разрешения "доступа" к нему. Приводимая на рис. 2.13 структура портфеля остается невидимой для вызвавшего команду Create, а также для любого, вызывающего одну из общедоступных операций. Поскольку возвращаемая ссылка не содержит прав доступа, то пользователь вынужден вести работу с портфелем через Portfolio_Mgr. Разумеется, пользователю не запрещается размножить копии portfolio_ptr, полученные как значение выходного параметра. (Более подробное обсуждение прав доступа дается в гл. 4 и 6.)
Однако, рассматривая пакет Portfolio_Mgr, мы можем видеть, что созданный портфель представляет собой запись, содержащую элемент с именем портфеля, индикатор наличия в нем пакетов акций и пустой список содержимого пакета акций. Поскольку тип записи портфеля специфицирует начальные значения для компонент полей, каждая переменная типа "портфель" и каждый динамически созданный экземпляр типа "портфель" инициализируется согласно вышеуказанным значениям. Такая "инициализация по умолчанию" происходит в том случае, если при объявлении переменной типа "портфель" или динамическом создании экземпляра типа "портфель" эти значения не указываются явно.
Главным элементом портфеля является связный список содержимого пакетов акций. К портфелю добавляется новая информация о содержимом при размещении новой записи о закупке пакета акций в результате вызова Portfolio_Mgr.Record.buy. В результате закупки нового пакета происходит создание истории закупки путем создания нового экземпляра типа stock_summary. Эта новая purchase_history (история закупки) получает в качестве начального значения ссылку к очереди закупок, созданную с помощью Purchase_Queue_Mgr.Create. (Наличие инициализации по умолчанию, производимой для поля purchase_history в stock_summary, обусловливает вызов операции Create для каждого нового экземпляра stock_summary.) Впоследствии каждый новый вызов Portfolio_Mgr.Record_buy добавляет новый элемент к каждому последующему экземпляру соответствующей очереди закупок.
Разработчик программы должен обязательно проанализировать возможные альтернативные варианты создания портфеля. Мы рассмотрим два примера.
Сделанный нами выбор структуры данных для представления истории закупок в виде очереди, а не связного списка может оказаться слишком ограниченным. Структура в виде очереди удовлетворительна в том случае, если нас устраивает продажа пакетов акций по принципу "первым пришел - первым обслужен", а также при гарантии того, что "конечный пользователь" системы будет весьма редко интересоваться историей закупок или же пытаться ее изменить. Если эти предположения не являются удовлетворительными, то, без сомнения, наиболее удобным представлением для записей с историями закупок будет связный список. Помимо этого, для Portfolio_Mgr может потребоваться дополнительный набор операций, позволяющий пользователю просматривать и изменять записи для данной истории закупки. (Читателям предлагается подумать над упражнением, в котором необходимо изменить соответствующим образом Portfolio_Mgr и пакеты, от структуры которых зависит возможность реализации вышеописанных возможностей.)
Нам может потребоваться запись в портфеле, состоящая из массива с записями о содержимом пакетов акций (а не связный список), в каждой из которых содержится purchase_queue. При использовании массивов необходимо учитывать возникающие в связи с этим проблемы управления внешней памятью, а при использовании списков - ограничения, связанные с последовательным доступом. Для нашего примера связный список записей содержимого пакетов акций выглядит наиболее привлекательным. До тех пор пока число записей в портфеле с точки зрения управления ими незначительно, предпочтителен последовательный их просмотр. Использование массива в этом случае не дает существенных преимуществ. С другой стороны, использование связных списков дает нам возможность воспользоваться встроенными в i432 механизмами распределения ресурсов, добавляющими при необходимости новые элементы списка. Для удаления вычеркнутых из списка элементов мы воспользуемся имеющимся в i432 сборщиком мусора. Использование связных списков освобождает нас от предварительного задания "граничных условий", таких, как максимальный размер массива.
[Если для представления портфеля мы собираемся использовать массив, то нам необходимо объявить, что портфель содержит "таблицу" размера (size) stock_summary, где размер задается константой в Stock_Types_And_Constants. Изначально stock_summary будет представлять собой запись, содержащую имя пустого пакета акций, нулевое значение в поле стоимости и нулевой указатель на историю закупки.
В языке Ада разрешается объявление динамических массивов (См. гл. 3, Справочное руководство по языку Ада [2].) Это позволяет нам объявлять максимальное число записей о содержимом пакетов акций в таблице как переменную, значение которой вычисляется в момент распределения нового экземпляра портфеля. На рис. 2.14 приведен пример определения такой записи.
type portfolio(newsize: integer range 1..600) is record portfolio_name: long_string := "not yet named ^^^^^^^^^^^^^^^^"; num_diff_stocks_held: integer range 0..newsize := 0; tableau: array 1..newsize of stock_summary; end record; Рис. 2.14. Тип портфеля с переменной таблицей объемом до 600 различных пакетов акций.
Идентификатор newsize является определителем, т.е. параметром типа записи. Его фактическое значение может изменяться от 1 до 600. Для создания экземпляра типа портфель необходимо указать соответствующее значение аргумента в объявлении, например:
my_folio: portfolio(newsize -> 150);
Это объявление не оказывает эффекта на резервирование памяти для переменной типа "портфель" my_folio, которая содержит портфель с таблицей емкостью в 150 записей о содержимом пакета акций. Максимальное число записей не может превышать 150. Изменение этой "верхней границы" в течение жизни программы управления капиталовложениями нецелесообразно.]
При написании тела пакета на языке Ада нет необходимости физического наличия в теле пакета тела любой операции, объявленной внутри пакета, как это было в случае с операциями, объявленными в теле пакета Queue_Mgr. Вместо этого можно воспользоваться временным представлением в виде нулевого (null) оператора или спецификации фиктивного модуля программы (заглушки), указав ключевую фазу is separate, которая говорит о том, что тело физически располагается в отдельно компилирующейся единице.
Наиболее распространенный способ создания отложенной реализации заключается в использовании оператора null. При этом, однако, программист должен в дальнейшем создать необходимую реализацию без поддержки компилятора. Отсрочка реализации с использованием фиктивного модуля, задаваемого ключевым словом is separate, может привести к значительному увеличению текста программы, однако, имеет преимущество в том, что отсутствие такого отдельно определяемого тела будет зарегистрировано компилятором или редактором связей. (Выполнение программы будет невозможно до тех пор, пока в ней не будут присутствовать все ее отдельные части.) На рис. 2.15 на примере частей тела пакета Portfolio_Mgr иллюстрируется только метод, использующий оператор is separate. В данном тексте мы выбрали этот метод откладывания реализации главным образом из соображений простоты организации рисунков.
package body Portfolio_Mgr is --Начало тела пакета. function Create( folio_name: in long_string) return portfolio_ptr; is separate; procedure Record_buy ( folio_ptr: in portfolio_ptr; buy_info: in purchase-record) is separate; -- Другие фиктивные модули процедур начинаются здесь. (Полный текст -- программы приведен в приложении В.) procedure Number_of_stocks( folio_ptr: in portfolio_ptr; num_stocks: out integer) is separate; -- Другие фиктивные модули процедур начинаются здесь (Полный текст -- программы приведен в приложении В.) -- Здесь начинаются локальные процедуры и функции: function Search_for_stock_code( folio_ptr: in portfolio_ptr; buy_record: in purchase_record; create_if_not_found: in boolean) return stock_summary_ptr; is separate; -- Функция: -- Просматривает портфель, адресованный portfolio_ptr, проверяя наличие -- в нем stock_code, совпадающего с заданным в buy_record. Если такой -- пакет акций обнаруживается, возвращается ссылка данного пакета акций -- к записи о его содержимом. Если требуемый пакет акций не обнаруживается, -- то последующее действие зависит от значения входного параметра -- create_if_not_found. Если его значение есть true, то происходит -- создание, инициализация и добавление к портфелю новой записи о -- содержимом пакета акций, а ссылка не возвращается. Если значение -- create_if_not_found есть false, то возвращается null. begin -- Здесь, если это необходимо, располагаются операторы для -- инициализации данного пакета. (Удалить begin, если эти операторы -- не требуются.) end Portfolio_Mgr; -- Конец тела пакета. Рис. 2.15. Отдельные части тела пакета Portfolio_Mgr.
Функция Search_for_stock_code на рис. 2.15 является приватной (локальной) для Portfolio_Mgr. Она вызывается только процедурами, определенными в Portfolio_Mgr. На рис. 2.16 приведена отдельно компилируемая программа для функции Create.
separate(Portfolio_Mgr) -- Префикс для индикации компилятору о том, что -- эта функция должна компилироваться в контексте -- Portfolio_Mgr. function Create( folio_ name: in long_string) return portfolio-ptr; is folio_ptr: portfolio-ptr; -- Локальная ссылочная переменная. begin folio-ptr:=new portfolio; -- Распределяет новый экземпляр портфеля и -- устанавливает ссылку для folio_ptr. folio_ptr.portfolio_name:=folio_name; -- Имя, назначенное данному портфелю. return folio_ptr; end Create; Рис. 2.16. Отдельно компилирующаяся единица для функции Create. Отметьте специальный префикс, служащий указанием компилятору о том, в каком контексте должна компилироваться данная программная единица.
Префикс "separate(Portfolio_Mgr)" сообщает компилятору о том, что контекстом для данной программной единицы служит тело Portfolio_Mgr. Иначе можно также сказать, что префикс сообщает компилятору о том, что приводимое здесь тело программы в действительности логически располагается в теле пакета Portfolio_Mgr в момент появления объявления об отдельной (separate) процедуре. Аналогичные отдельно компилирующиеся программные единицы для функций Record_buy, Record_sell и Search_for_stock_code приведены в приложении Г. Они также иллюстрируют некоторые дополнительные характеристики языка Ада, включая свойство renames, нелокальную программу обслуживания исключительных ситуаций, оператор case и цикл while.
[Например, частично завершенное тело подпрограммы для функции Record_sell в приложении Г включает в себя программу обслуживания исключительной ситуации потери значимости. Эта программа может быть инициирована при возникновении соответствующей исключительной ситуации в Purchase_Queue_Mgr и передаче ее в точку вызова Record_sell. После завершения работы обслуживающей программы
exception when underflow -> history_Underflow:=true;
работа Record_sell будет прекращена. Управление будет передано в точку программы (в Club_Portfolio), следующую за вызовом Record_sell.]
Предполагая, что читатель приобрел некоторые знания о языке Ада, а также хорошо разобрался в пакете Portfolio_Mgr, мы перенесем наше внимание на пакет Club_Portfolio. Часть спецификации пакета Club_Portfolio, приводимая в приложении В, достаточно очевидна. Пакет определяет двенадцать общедоступных операций, перечисленных на рис. 2.17.
Название операции |
Print_club_valuation |
Print_club_holdings |
Find_stock_code |
Print_individual_stock_summary |
Print_shares_and_value_of_stock |
Print_average_cost |
Print_winners |
Print_losers |
Print_non_movers |
Enter_buy |
Enter_sell |
Рис. 2.17. 0бщедоступные операции для Club.Portfolio.
Пакет Club_Portfolio выступает в качестве интерфейса между пользователями пaкeтa Portfolio_Mgr и самим пакетом Portfolio_Mgr. Его основная задача заключается в преобразовании машинно-ориентированной информации, закодированной в структуре данных портфеля, в распознаваемую человеком печатную информацию. Прямой доступ к структуре данных портфеля разрешается только операциям Portfolio_Mgr по причинам, обсуждавшимся в разд. 2.3.
Из всех операций имеются только две функции, которые возвращают назад результат - Finвstock_code и Print_club_valuation. Функция Find_stock_code вызывается для подтверждения официального имени корпорации и для получения соответствующего уникального внутреннего идентификатора для имени корпорации: stock_code_pair (определенного в Stock_Types_And_Constants). Функция Find_stock_code устроена таким образом, что при указании пользователем некоторого примерного имени корпорации ответом является печать всех "сходных" с ним имен. В этом случае в качестве результата своей работы Find_stock_code возвращает нулевое значение. Если пользователь указывает имя, в точности совпадающее с именем корпорации в списке, то это совпадение подтверждается выводом имени на печать, а возвращаемое значение для данного пакета акций есть stock_code_pair.
[Функция Find_stock_code аналогична операции по открытию файла для ввода-вывода. Обычно перед осуществлением над файлом каких-либо операций он должен быть открыт. Операции по открытию файла сообщается внешнее распознаваемое человеком имя этого файла, а результатом ее работы служит распознаваемое машиной имя в ее внутреннем представлении. С этого момента пользователь использует в любой из операций с данным файлом его внутреннее имя.]
Тело Find_stock_code содержит вызовы операций в пакете Stock_Mkt_Info. Этот пакет поддерживает несколько функций просмотра, включая функцию, выдающую stock_code_pair по правильно заданному имени корпорации. Обращающийся к Club_Portfolio может использовать эту часть информации для последующих вызовов функций, в список параметров которых stock_code_pair входит в качестве аргумента. Примером такого вызова может служить:
Find_stock_price(Find_stock_code ("General Motors^^^^^^^^^^^^^^^^"));
где Find_stock_price считается доступной операцией пакета Stock_Mkt_Info. (Oперацию Find_stock_price пакета Club_Portfolio также можно было сделать общедоступной. В нашей версии, однако, этого не сделано.)
Для обеспечения возможности выполнения численного анализа содержимого портфеля и информации о пакетах акций в Club_Portfolio добавлены три дополнительные операции: Print_winners, Print_loosers и Print_non_movers.
Производимые этими операциями вычисления достаточно просты. Вызывающий функцию указывает стандартное отклонение в процентах, например 10%. Ответом является список пакетов акций, прирост по которым превысил 10%, или акций, средние потери на которых превысили 10%, или чьи приращения или потери не вышли за пределы 10% соответственно.
Позднее будут введены операции, осуществляющие более детальный анализ. Небольшие улучшения могут быть произведены путем незначительных изменений в части спецификации. Например, операция Print_winners_since может быть определена аналогично операции Print_winners добавлением параметра number_of_months (число месяцев) в качестве входного параметра. Функция
Print_winners_since(number_of_months -> 5, spread -> 10);
будет выдавать список имеющихся пакетов акций, для которых прирост за последние 5 месяцев превысил 10%.
Привилегии доступа, необходимые для выполнения операций из Club_Portfolio, зависят от самих операций. Большинство операций требуют только привилегии на чтение экземпляра портфеля. Однако операции Enter_buy и Enter_sell, осуществляющие обновление портфеля, требуют тем самым привилегии на запись.
Создание экземпляра портфеля является частью последовательности инициализации пакета Club_Portfolio. Эта последовательность располагается в конце тела пакета и повторяется здесь.
begin our-portfolio:=Portfolio_Mgr.Create("Twenty_cousins_club^^^^^^^^^^^"); end Club-Portfolio;
Результатом вызова операции Create для Portfolio_Mgr является ссылка на новый созданный экземпляр портфеля с именем "Twenty_cousins_club", которая присваивается локальной переменной our_portfolio. После этого Club_Portfolio становится владельцем данного экземпляра портфеля (связан с ним). Эта связь сохраняется на все время жизни пакета Club_Portiolio. С пакетом Club_Portfolio может быть связан только один портфель. [В третьей главе по совершенно другим причинам мы рассмотрим модифицированную версию Club_Portfolio. В этой версии портфель организации не создается в процессе инициализации портфеля. Вместо этого добавляются две общедоступные операции - Create_folio и Delete_folio. Эти операции позволяют пользователям пакета внешним образом производить создание и удаление их портфеля.]
Запрограммированный рассмотренным выше образом пакет Club_Portfolio владеет только одним портфелем Twenty_cousins_club. [Мы предполагаем наличие доступных системных команд, которые делают всю описанную в данном разделе структуру пакета постоянным библиотечным объектом, время жизни которого сходно с временем жизни защищенного файла. Времена жизни объектных файлов мы снова рассмотрим в гл. 4 и 10.]
Довольно легко запрограммировать пакет Club_Portfolio таким образом, что для Club_Portfolio может создаваться несколько экземпляров пакетов, каждый из которых владеет отдельным портфелем. Это может быть реализовано посредством объявления пакета Club_Portfolio родовым пакетом. В этом случае мы должны будем произвести в заголовке пакета следующие изменения:
generic folio_name: long_string; -- Параметр родового пакета. package Club_Portfolio is
Использование родовой версии пакета Club_Portfolio позволяет создавать новые экземпляры пакетов по мере необходимости. При этом каждый из них будет иметь свое отдельное имя и владеть (и иметь на это соответствующие права) отдельным портфелем. Например:
package Му_Estate is new Club_portfolio("Account_45^^^^^^^^^^^^^^^^^^^^");
или
package Her_Estate is new Club_portfolio("Estate_12^^^^^^^^^^^^^^^^^^^^");
Важно отметить, что приведенный набор объявлений не позволяет на этапе выполнения отличить My_Estate от Her_Estate. С другой стороны, концепция "возможности различения пакета на этапе выполнения" в стандартном языке Ада не существует, поскольку этот язык не позволяет пользователям формировать любой вид "переменной" пакета. В стандартном языке Ада пакеты строго статичны, т. е. строго различимы на этапе выполнения.
Расширение языка Ада-432 позволяет пользователям определять "значения" пакетов. Возможность различения экземпляров пакета на этапе выполнения может оказаться весьма важной для пользователей системы Ада-432. Значения пакетов и динамические пакеты обсуждаются в гл. 6.
В этой главе мы закончили обсуждение программы на языке Ада с точки зрения базовой структуры пакета (рис. 2.2). В последнем разделе мы рассмотрим изменения в тексте программ на языке Ада, необходимые для реализации измененной структуры, приведенной на рис. 2.4. Эти изменения должны быть минимальными. В следующей главе рассматриваются более существенные изменения, необходимые для реализации изменений в структуре, приведенной на рис. 2.5.