Одна из полезных операций - создание нового объекта типа List_Node и возврат значения, ссылающегося на только что созданный объект. Новые объекты создаются в области основной памяти и они не будут удалены автоматически при выходе из подпрограммы. Процесс создания нового объекта в некоторой области памяти называется выделением памяти. В языке C операция для выделения памяти называется "malloc", тогда как в языке Pascal и C++ похожая операция называется "new". В Ada эта операция тоже называется new, а синтаксис ее такой же как в C++ и Pascal. За ключевым словом "new" следует имя типа создаваемого объекта. Например, следующим образом можно создать два новых объекта типа List_Node, при этом сделав переменную "Current" ссылкой на один объект, а "Root" - на другой:
Current := new List_Node; Root := new List_Node;
Теперь можно сказать, что Current ссылается на элемент List_Node, а Root - на другой List_Node.
Как только ссылочное значение начинает ссылаться на реальный объект (а не на null), можно использовать операцию ``точка'' для обращения к значениям внутри объекта. Синтаксис точно такой же, как и при обращении к полям записи. Например, для созданных нами объектов List_Node, давайте установим значение поля Data внутри объекта, на который ссылается "Current", равным 1, а поля внутри объекта, на который ссылается "Root" - 2:
Current.Data := 1; Root.Data := 2;
Очень полезное свойство ссылочных значений состоит в том, что их можно использовать для "связывания" компонентов в сложные структуры. "Связывание" осуществляется при помощи присаивания определенных значений ссылочным переменным. Например, давайте свяжем созданные нами объекты таким образом, чтобы следующим после объекта, на который ссылается Root, был объект, на который ссылается Current. Сделать это можно, изменив значение "Root.Next". Но каким же должно быть это новое значение? Ну конечно, новое значение должно совпадать со значением "Current", чтобы оба значения ссылались на один и тот же объект. В Ada это можно сделать так:
Root.Next := Current;
Если Вы не слишком хорошо знакомы со ссылочными значениями, то это может показаться головоломкой. Одним из способов прочтения преведенного выше выражения будет: "изменить Root.Next так, чтобы это значение ссылалось на тот же объект, на который ссылается Current".
Существует часто используемый способ изображения такого рода структур. Каждая преременная изображается в виде прямоугольника, а записи изображаются в виде прямоугольников, разделенных на поля. Для переменных, не являющихся ссылками, их значения записываются внутри прямоугольников. Для ссылочных переменных внутри прямоугольника записывается либо "null" (если значение переменной null), либо рисуется стрелка от этого прямоугольника, к прямоугольнику той переменной, на которую ссылается данная. Каждый раз, когда выполняется операция "new", нужно дорисовать новый прямоугольник. Изменение значения ссылочной переменной изменяет направление стрелки, при этом название начальной точки стрелки находится слева от знака операции присваивания, а новая конечная точка, т.е. новое значение, на которое ссылается переменная, - справа. Следующий рисунок иллюстрирует сделанное нами:
В некоторых случаях необходимо работать со всем объектом, на который мы ссылаемся, а не с какой-либо его частью. В этих случаях используется псевдо-поле ".all". Ссылочная переменная с ".all" представляет собой значение, на которое ссылается переменная, а не значение самой ссылки. Например, пусть имеется некоторая процедура (My_Procedure) требующая в качестве входного параметра значение типа Tree_Node. Описать такую процедуру просто:
procedure My_Procedure(Input : in Tree_Node);
Заметим, что такой процедуре нельзя передать ссылку на Tree_Node в качестве входного параметра, из-за различия типов (значение и ссылка на значение - это не одно и то же). Выход из положения - использовать слово "all" с точкой для получения доступа ко всему объекту:
My_Procedure(Current.all);
Многих может смутить операции присваивания со словом ".all" и без него. Давайте сравним их. Ниже приведены две инструкции, которые внешне выглядят очень похоже, но по значению совершенно различны:
Root.all := Current.all; -- Инструкция (1). Root := Current; -- Инструкция (2).
При выполнении инструкции (1) все содержимое области памяти, на которую ссылается Current, копируется в область памяти, на которую ссылается Root. Если выполнить инструкцию (2) вместо (1), содержимое области памяти, на которую ссылается Root не будет изменено, но вместо этого изменится значение Root и эта переменная станет ссылкой на другой объект - на тот же, что и Current.
Следующие рисунки иллюстрируют различия между инструкциями (1) и (2).
Если Вы знакомы с ANSI C, следующие "анологии" помогут понять, как пользоваться ссылочными типами языка Ada:
Ada Statement or Declaration ANSI C Equivalent -------------------------------- ------------------------------- type Node_Access is access Node; typedef node *node_access; Start : Node_Access; node_access start = 0; Current := new Node; current = malloc(sizeof(node)); Current := Start; current = start; Current.all := Start.all; *current = *start; Current.Data := 5; current->data = 5;
Допустим, выполняется последовательность инструкций, приведенная ниже, а переменные Current и Root имеют тип List_Node_Access:
Current := new List_Node; Root := new List_Node; Root.Data := 7; Current.Data := 12; Current := Root; Root.Data := 6;
Каким будет значение Current.Data после выполнения этих инструкций? Намек: Это не слишком простой вопрос. Возможно, Вам будет проще ответить на него, воспользовавшись рисунками, похожими на те, о которых мы говорили.
Перейти к предыдущему разделу | Перейти к следующему разделу | Вернуться к содержанию Урока 12 |
---|
David A. Wheeler (dwheeler@ida.org)
Исходная копия этого документа находится по адресу
"http://www.adahome.com/Tutorials/Lovelace/s12s3.htm".
Исходная копия перевода размещена на сайте http://www.ada-ru.org
Перевод: Юрий Королев
Общая редакция перевода: Г.Ю. Сисюк