Тип-посилання#
Огляд#
Росилання є потенційно небезпечною конструкцією, яка суперечить основній філософії Ади.
Існує два шляхи, якими 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 є взаємозалежними.