Тип-посилання#
Огляд#
Росилання є потенційно небезпечною конструкцією, яка суперечить основній філософії Ади.
Існує два шляхи, якими Ada допомагає захистити програмістів від небезпек посилань:
Одним із підходів, який ми вже бачили, є надання альтернативних функцій, щоб програміст не потребував використання посилань. Режими параметрів, масиви та типи змінних розмірів — це конструкції, які можуть замінити типове використання покажчиків у C.
По-друге, Ada зробила посилання максимально безпечними та обмеженими, але дозволяє «аварійний режим», коли програміст явно їх вимагає, і, ймовірно, використовуватиме такі функції з належною обережністю.
Приклад декрарації простого типу посилання в Ada:
package Dates is type Months is (January, February, March, April, May, June, July, August, September, October, November, December); type Date is record Day : Integer range 1 .. 31; Month : Months; Year : Integer; end record; end Dates;with Dates; use Dates; package Access_Types is -- Декларація типу посилання type Date_Acc is access Date; -- ^ Date_Acc значення -- посилається на -- обєкт Date D : Date_Acc := null; -- ^ Літерал для -- "немає посилання" -- ^ Посилання на дату end Access_Types;
Це ілюструє, як:
Декларує тип-посилання, значення якого вказують на об'єкти певного типу
Декларує змінну цього типу
Ініціалізує значенням
null
Відповідно до філософії суворої типізації Ada, якщо ви оголосите ще один тип-посилання та тип Date, ці типи будуть несумісні один з одним:
with Dates; use Dates; package Access_Types is -- Декларація типів-посилань type Date_Acc is access Date; type Date_Acc_2 is access Date; D : Date_Acc := null; D2 : Date_Acc_2 := D; -- ^ Невірно! Різні типи end Access_Types;
В інших мовах
У більшості інших мов типи-посилання є структурно, а не номінально типізованими, як це є в Ada, що означає, що два типи посилань будуть однаковими, доки вони мають однаковий цільовий тип і правила доступності.
В Аді це не так. Потрібен час, щоб звикнути до цього. Здавалося б, проста проблема: якщо ви хочете мати канонічний доступ до типу, де його слід оголосити? Зазвичай використовуваний шаблон полягає в тому, що якщо вам потрібен тип-посилання на певний тип, яким ви «володієте», ви повинні оголосити його разом із типом:
package Access_Types is
type Point is record
X, Y : Natural;
end record;
type Point_Access is access Point;
end Access_Types;
Виділення пам'яті#
Після того як ми декларували тип-посилання, нам потрібен спосіб надати
змінним значення. Ви можете призначити значення тиакм змінним за допомогою
ключового слова new
.
with Dates; use Dates; package Access_Types is type Date_Acc is access Date; D : Date_Acc := new Date; -- ^ Виділення пам'яті для Date end Access_Types;
Якщо тип, для якого хочете виділити пам'ять, потребує обмежень, ви можете вказати їх при виділенні, так само, як ви зробили б у декларації змінної:
with Dates; use Dates; package Access_Types is type String_Acc is access String; -- ^ -- Посилання на необмеженний тип Msg : String_Acc; -- ^ За замовчуванням значення null Buffer : String_Acc := new String (1 .. 10); -- ^ Необхідно вказати обмеження end Access_Types;
У деяких випадках, однак, створення з вказанням обмежень не є ідеальним, тому Ada також дозволяє ініціалізувати при виділенні. Наприклад:
with Dates; use Dates; package Access_Types is type Date_Acc is access Date; type String_Acc is access String; D : Date_Acc := new Date'(30, November, 2011); Msg : String_Acc := new String'("Hello"); end Access_Types;
Доступ до значення#
Останньою важливою частиною функції типу-посилання в Ada є те, як отримати
доступ до значення на якє посилається такий тип, тобто як розіменувати посилання.
Розіменування посилання використовує синтаксис .all
в Ada, але зазвичай
це не потрібне — у багатьох випадках воно буде виконано неявно для вас:
with Dates; use Dates; package Access_Types is type Date_Acc is access Date; D : Date_Acc := new Date'(30, November, 2011); Today : Date := D.all; -- ^ Доступ до значення J : Integer := D.Day; -- ^ Неявне розіменування -- для записів і массивів -- Еквівалентно до D.all.Day end Access_Types;
Інші можливості#
Як ви могли знати, якщо ви використовували посилання в C або C++, нам все ще бракує функцій, які вважаються фундаментальними для використання посилань, наприклад:
Арифметичні дії (здатність збільшувати або зменшувати посилання, щоб вказати на наступний або попередній об’єкт)
Звільнення пам'яті вручну — те, що в C називається
free
абоdelete
. Це потенційно небезпечна операція.
Ці функції існують в Ada, але доступні лише через спеціальні API стандартної бібліотеки.
Увага
Правило Ada полягає в тому, що в більшості випадків ви можете уникнути ручного розподілу, і Ви повинні.
Існує багато способів уникнути розподілу вручну, деякі з яких розглянуто (наприклад, режими параметрів). Мова також надає бібліотечні абстракції, щоб уникнути посилань:
Одним з них є використання containers. Контейнери допомагають користувачам уникати посилань, оскільки пам’ять контейнера керується автоматично.
Контейнер, на який слід звернути увагу в цьому контексті, це Indefinite holder. Цей контейнер дозволяє зберігати значення невизначеного розміру, наприклад String.
Бібліотека GNATCOLL має інтелектуальні вказівники під назвою Refcount Пам’ять в них керується автоматично, тому, коли виділений об’єкт більше не має посилань на нього, пам’ять автоматично звільняється.
Взаємно рекурсивні типи#
Зв'язаний список є загальною ідіомою в структурах даних; в Ada це було б найбільш природно визначити через два типи, тип запису та тип-посилання, які є взаємозалежними. Щоб оголосити взаємозалежні типи, ви можете використовувати неповне оголошення типу:
package Simple_List is type Node; -- Це не повна декларація типу, -- яка має бути повністю декларована -- в цьому ж декларативному блоці. type Node_Acc is access Node; type Node is record Content : Natural; Prev, Next : Node_Acc; end record; end Simple_List;
У цьому прикладі типи Node
і Node_Acc
є взаємозалежними.