本课目标
完成本课后,你将能够:
- 理解标记记录(tagged record)作为 Ada 面向对象的基础原理及其内存布局
- 掌握单继承与扩展记录的声明方式,区分重写与隐藏
- 理解 primitive operations 的判定规则与动态分派(多态)的实现机制
- 学会使用类宽类型(class-wide type)实现运行时多态,并掌握其使用限制
- 区分抽象类型与接口的适用场景,理解多重继承的实现方式
- 应用面向对象设计模式编写可扩展、易维护的程序
- 分析动态分派的性能开销及在实时系统中的适用性
一、为什么 Ada 需要面向对象
Ada 最初于 1983 年设计时,面向对象尚未成为主流。然而,随着软件工程的发展,封装、继承、多态等特性被证明能极大提升代码复用性和可维护性。因此,Ada 95 引入了完整的面向对象特性,包括标记记录、类宽类型、抽象类型、接口等。这些特性建立在已有的强类型和包机制之上,既保留了 Ada 的安全性,又提供了与现代语言(C++、Java)相当的建模能力。
1.1 面向对象的核心诉求
在传统的结构化编程中,数据和操作是分离的。例如,我们有一个 Shape 记录和一组子程序 Area、Draw、Move。当添加新形状(如三角形)时,需要修改所有相关子程序,在它们的 case 语句中添加新的分支。这违反了开闭原则(对扩展开放,对修改关闭),导致代码脆弱且难以维护。
面向对象通过将操作与类型绑定(称为 primitive operations),并支持动态分派,使得添加新形状时只需定义新类型及其操作,无需修改现有代码。这大大降低了模块间的耦合度,提高了系统的可扩展性。
1.2 Ada 面向对象设计的独特之处
与 C++ 相比,Ada 的类(标记记录)与包紧密结合。一个包通常定义一个类型及其 primitive operations,这种设计强化了封装和信息隐藏:类型的内部表示放在包的私有部分,外部只能通过公开的子程序访问。与 Java 相比,Ada 的对象默认是值语义(复制语义),而不是引用语义。这意味着赋值操作会复制整个对象,避免了别名带来的意外副作用。如果需要引用语义,可以显式使用访问类型(指针)。
此外,Ada 的面向对象是可选的,并不要求一切皆对象。开发者可以根据需要混合使用过程式、模块化、泛型和面向对象范式,这在嵌入式实时系统中尤为重要,因为动态分派可能带来轻微的性能开销(两次间接寻址),但 Ada 允许开发者精确控制是否使用多态。
1.3 典型应用场景
- 图形系统:不同形状(圆、矩形、多边形)共享相同的绘制接口,由运行时根据实际类型决定调用哪个
Draw函数。 - 设备驱动框架:不同硬件(串口、USB、网络)使用统一的
Open、Read、Write、Close接口,通过多态管理设备列表。 - 仿真系统:不同实体(坦克、士兵、建筑)具有相同的
Update和Render行为,通过类宽容器统一调度。 - 策略模式:算法可以运行时替换,如排序策略(快速排序、归并排序)作为对象传递,实现算法的灵活切换。
- 用户界面组件:按钮、文本框、复选框等控件共享相同的
Draw、Handle_Event接口,通过多态事件分发。
二、标记记录基础
2.1 什么是标记记录
普通记录(record)只是数据的聚合,没有类型层次。标记记录(tagged record)在记录中添加了一个隐藏的“标签”(tag),用于标识该记录的实际类型,从而支持继承和动态分派。这个标签通常是一个指向分派表(dispatch table)的指针或索引,分派表中存储该类型所有 primitive operations 的地址。
内存布局对比(假设 32 位系统,Float 4 字节,指针 4 字节,对齐按自然边界):
- 普通记录
Point(X, Y: Float):内存布局为 X(4 字节)、Y(4 字节),共 8 字节。 - 标记记录
Shape(tag + Center + Color):tag(4 字节)、Center(8 字节)、Color(10 字节)。由于对齐,Color 后可能填充 2 字节,总大小约 24 字节。
这意味着标记记录比普通记录多占用一个指针大小(通常是 4 或 8 字节),因此大量小对象的场景需评估内存开销。但通常这种开销在可接受范围内,尤其是当对象本身较大时。
2.2 声明标记记录
使用 tagged 关键字声明标记记录。为了保持封装性,通常将标记记录定义为私有类型(private),只通过子程序访问其内部数据。包规范中列出公开操作,包的私有部分给出记录的具体定义。
package Geometry is
type Shape is tagged private;
function Area (S : Shape) return Float;
procedure Draw (S : Shape);
procedure Move (S : in out Shape; Dx, Dy : Float);
private
type Point is record X, Y : Float; end record;
type Shape is tagged record
Center : Point;
Color : String (1 .. 10);
end record;
end Geometry;
这种设计将内部表示隐藏,只暴露必要的操作,符合 Ada 的哲学。外部代码只能通过 Area、Draw、Move 来操作 Shape 对象,无法直接访问 Center 或 Color,保证了数据的完整性。
2.3 为什么需要标记
标记记录的核心作用是运行时类型识别(RTTI)。当你有一个类宽引用(Shape'Class)时,编译器无法在编译时知道具体是 Circle 还是 Rectangle,但通过对象头部的 tag,可以在运行时找到正确的分派表,从而调用正确的 Area 函数。没有 tag,动态分派无法实现。
三、继承与扩展
3.1 派生新类型
使用 new 关键字从标记记录派生,并通过 with 添加新组件。派生类型继承父类型的所有组件和 primitive operations(即那些以父类型为参数的操作)。
type Circle is new Shape with record
Radius : Float;
end record;
type Rectangle is new Shape with record
Width, Height : Float;
end record;
派生类型可以添加任意数量的新组件,这些新组件在内存中位于父组件之后。这种单继承机制简单、高效,避免了多重继承带来的复杂性(如菱形继承问题)。如果确实需要多重契约,可以使用接口(见第五节)。
3.2 重写(overriding)操作
子类型可以重新定义父类型的 primitive operation,使用 overriding 关键字(Ada 2005+)明确标记。这有两个好处:编译器会检查父类确实有该操作(防止拼写错误),并且代码可读性增强,明确表达了“这是一个重写操作”。
overriding function Area (C : Circle) return Float is
begin
return 3.14159 * C.Radius ** 2;
end Area;
overriding function Area (R : Rectangle) return Float is
begin
return R.Width * R.Height;
end Area;
如果不写 overriding,代码依然有效,但可能无意中创建了一个新的 primitive operation(如果父类没有该操作),容易出错。因此工业编码规范(如 JPL、High Integrity Ada)通常要求显式使用 overriding。
3.3 调用父类操作
在重写的操作中,有时需要调用父类的实现。例如,Circle 的 Draw 可能先画一个通用的形状边框,再画圆的特定部分。可以通过类型转换将派生对象视为父类对象,从而调用父类的操作:
overriding procedure Draw (C : Circle) is
begin
Shape(C).Draw; -- 先调用父类的 Draw
-- 然后画圆的特定部分
Ada.Text_IO.Put_Line ("... with radius" & Float'Image (C.Radius));
end Draw;
注意:Shape(C) 将 Circle 视图转换为 Shape,这不会改变对象的内存布局,只是改变了编译器的类型视角。调用 Draw 时,由于 C 被当作 Shape,将调用 Shape 的 Draw(静态分派)。如果需要动态分派,应使用类宽类型。
3.4 扩展记录聚集
创建派生对象时,可以使用扩展记录聚集语法,先初始化父类部分,再用 with 初始化新组件。这种语法保证了父类部分被正确初始化,且可读性较好。
C : Circle := (Shape'(Center => (0.0, 0.0), Color => "Red ") with Radius => 5.0);
如果不使用扩展聚集,也可以先赋值父类部分再单独赋值新组件,但扩展聚集更简洁。
四、Primitive Operations 与动态分派
4.1 什么是 primitive operation
Primitive operation 是在同一包中声明的、以该类型作为第一个参数(或返回类型)的子程序。这些操作会被派生类型继承,并且可以被重写,从而实现动态分派。判断规则(简化版):
- 子程序的一个参数或返回值的类型是
T或access T(其中T是标记记录类型)。 - 子程序在声明
T的包内(与T在同一包规范中)。 - 子程序不是类宽类型参数(
T'Class)——类宽类型用于多态接收,但本身不定义 primitive operation。
package Shapes is
type Shape is tagged null record;
-- 以下都是 Shape 的 primitive operations
function Area (S : Shape) return Float; -- 参数为 Shape
procedure Draw (S : Shape); -- 参数为 Shape
function Create return Shape; -- 返回类型为 Shape
procedure Reset (S : out Shape); -- 参数为 out Shape
procedure Transform (S : in out Shape); -- 参数为 in out Shape
end Shapes;
如果一个子程序只接受 T'Class 参数,它不是 primitive operation,而是一个类宽操作,通常用于实现多态算法(如遍历容器)。类宽操作不会被继承,也不参与动态分派。
4.2 动态分派(多态)
当通过类宽类型('Class)的引用调用 primitive operation 时,Ada 会在运行时根据对象实际标签选择正确的实现。这是面向对象多态的核心机制。
procedure Process (S : in out Shape'Class) is
begin
-- 运行时根据 S 的实际类型(Circle 或 Rectangle)调用对应的 Area
Ada.Text_IO.Put_Line ("Area = " & Float'Image (S.Area));
end Process;
动态分派的实现步骤:
- 从对象的 tag 字段获取分派表的地址。
- 在分派表中查找
Area函数的地址(分派表是一个包含所有 primitive operation 指针的数组,每个操作有固定偏移)。 - 调用该函数,并传递对象的地址作为隐含参数(类似
this)。
这个过程通常需要两次间接寻址(一次取分派表,一次取函数地址),比直接调用稍慢(可能多几十个时钟周期),但在大多数应用中可接受。对于硬实时系统,需要评估最坏情况执行时间,但通常仍能满足要求。
4.3 类宽类型(class-wide type)
T'Class 表示类型 T 及其所有派生类型的集合。它是不确定的,因为派生类型可以添加新组件,导致大小不同。因此,类宽类型只能用于以下场景:
- 子程序参数(
in,in out,out)——通过引用传递(实际是传递对象的地址)。 - 访问类型的目标(
access T'Class)——通过指针间接访问。 - 作为记录组件时,必须使用访问类型。
-- 合法:子程序参数
procedure Handle (S : Shape'Class);
-- 合法:访问类型
type Shape_Ptr is access all Shape'Class;
-- 合法:记录组件(使用访问类型)
type Holder is record
S : access Shape'Class;
end record;
-- 非法:变量声明
-- S : Shape'Class; -- 编译错误,类宽类型不能用于对象声明
类宽类型是实现多态的关键:它保存对象的完整信息,调用 primitive operation 时动态分派。注意,类宽类型的变量本身不是对象,而是一个引用或视图,通常通过 'Class 属性获得,例如将具体对象赋值给类宽变量时,会发生视图转换(view conversion),不复制对象,只改变类型视角。
五、抽象类型与接口
5.1 抽象类型
使用 abstract 关键字定义抽象类型,不能直接实例化,只能作为基类。抽象类型通常包含抽象操作(没有实现),必须在派生类型中重写。
type Shape is abstract tagged null record;
function Area (S : Shape) return Float is abstract; -- 抽象操作
procedure Draw (S : Shape) is abstract;
抽象类型可以有非抽象操作(有默认实现)。例如,Move 和 To_String 可以有一个通用的实现,不需要每个派生类重写。派生类型必须重写所有抽象操作,否则自身也是抽象的,不能实例化。
5.2 接口(Ada 2005+)
接口是只有抽象操作的抽象类型,用于实现多重继承(多接口)。一个类型可以继承一个父类并实现多个接口。接口本身没有数据组件(null record),所有操作都是抽象的。
type Drawable is interface;
procedure Draw (D : Drawable) is abstract;
type Serializable is interface;
procedure Serialize (S : Serializable; Stream : access Root_Stream_Type'Class) is abstract;
-- 实现多个接口
type Circle is new Shape and Drawable and Serializable with record
Radius : Float;
end record;
overriding procedure Draw (C : Circle) is ...;
overriding procedure Serialize (C : Circle; Stream : ...) is ...;
接口与抽象类型的区别:
- 接口没有数据组件(
null record),抽象类型可以有数据。 - 一个类型只能继承一个父类(单继承),但可以实现多个接口。
- 接口的所有操作都是抽象的(默认),抽象类型可以有非抽象操作。
- 接口可以相互继承(
type A is interface; type B is interface and A;)。
接口为 Ada 提供了类似 Java 的多重继承能力,同时避免了菱形继承问题(因为接口没有数据,不会产生冲突)。
5.3 何时使用抽象类型 vs 接口
- 如果需要共享数据成员(如
Center、Color),使用抽象类型。 - 如果只需要定义行为契约(如
Drawable、Comparable),使用接口。 - 如果一个类型需要从多个来源继承行为,使用一个父类(抽象类型)加多个接口。
六、构造与销毁
6.1 构造函数
Ada 没有专门的构造函数,但可以使用函数返回初始化的对象,或使用记录聚集。这符合 Ada 的风格:对象的初始化通过显式赋值或聚集完成,不需要隐式调用构造函数。
function Make_Circle (X, Y, R : Float; Color : String) return Circle is
begin
return Circle'(Center => (X, Y), Color => Color, Radius => R);
end Make_Circle;
使用工厂函数的好处是可以进行参数验证和复杂初始化。对于派生类型,工厂函数通常返回基类的类宽类型,以便隐藏具体类型。
6.2 控制初始化与终结
对于需要资源管理(如文件、锁、动态内存)的对象,可以使用 Ada.Finalization.Controlled 类型,它提供 Initialize、Adjust(用于复制)和 Finalize(用于销毁)过程。这类似于 C++ 的构造函数、拷贝赋值和析构函数,或 Python 的 __init__ 和 __del__。
with Ada.Finalization;
type Resource is new Ada.Finalization.Controlled with record
Handle : Integer;
end record;
procedure Initialize (R : in out Resource);
procedure Adjust (R : in out Resource);
procedure Finalize (R : in out Resource);
当 Resource 对象创建时(通过声明或 new),Initialize 被自动调用。当对象被赋值时,先调用 Adjust 处理复制。当对象离开作用域或被释放时,Finalize 被调用。这为资源管理提供了可靠的基础设施。该特性将在后续“资源管理”课程中深入讲解。
七、完整示例:图形系统
-- 文件名:shapes.ads
-- 目的:定义图形系统的抽象基类和具体形状
with Ada.Text_IO;
package Shapes is
-- 辅助类型:点
type Point is record
X, Y : Float;
end record;
-- 抽象基类:Shape
-- 所有形状共享位置(Center)和颜色(简化,这里省略颜色)
-- 使用 abstract 标记,表示不能直接实例化
type Shape is abstract tagged record
Center : Point;
end record;
-- 抽象操作:面积(必须由派生类实现)
function Area (S : Shape) return Float is abstract;
-- 抽象操作:绘制(必须由派生类实现)
procedure Draw (S : Shape) is abstract;
-- 非抽象操作:移动(所有形状通用,无需重写)
-- 实现见包体
procedure Move (S : in out Shape; Dx, Dy : Float);
-- 非抽象操作:转换为字符串(通用,但可被重写)
-- 这里不是 abstract,所以派生类可以选择重写或不重写
function To_String (S : Shape) return String;
-- 具体派生类型:圆
type Circle is new Shape with record
Radius : Float;
end record;
-- 重写抽象操作,使用 overriding 关键字
overriding function Area (C : Circle) return Float;
overriding procedure Draw (C : Circle);
-- 可选:重写 To_String
overriding function To_String (C : Circle) return String;
-- 具体派生类型:矩形
type Rectangle is new Shape with record
Width, Height : Float;
end record;
overriding function Area (R : Rectangle) return Float;
overriding procedure Draw (R : Rectangle);
-- 多态处理过程:接受任何 Shape 派生类型
-- 此处使用类宽类型参数实现动态分派
procedure Display (S : Shape'Class);
-- 接口示例:可序列化(Ada 2005+)
type Serializable is interface;
procedure Serialize (S : Serializable; Stream : access Ada.Text_IO.File_Type'Class) is abstract;
-- 让 Circle 实现 Serializable 接口
-- 注意:需要定义一个新类型,因为接口实现不能直接在原类型上添加
type Serializable_Circle is new Circle and Serializable with null record;
overriding procedure Serialize (SC : Serializable_Circle; Stream : access Ada.Text_IO.File_Type'Class);
end Shapes;
-- 文件名:shapes.adb
-- 实现 Shapes 包
package body Shapes is
-- 实现通用移动
procedure Move (S : in out Shape; Dx, Dy : Float) is
begin
S.Center.X := S.Center.X + Dx;
S.Center.Y := S.Center.Y + Dy;
end Move;
-- 默认的 To_String 实现
function To_String (S : Shape) return String is
begin
return "Shape at (" & Float'Image (S.Center.X) & ", " &
Float'Image (S.Center.Y) & ")";
end To_String;
-- Circle 实现
function Area (C : Circle) return Float is
begin
return 3.14159 * C.Radius * C.Radius;
end Area;
procedure Draw (C : Circle) is
begin
Ada.Text_IO.Put_Line ("Circle at (" & Float'Image (C.Center.X) & ", " &
Float'Image (C.Center.Y) & ") radius=" &
Float'Image (C.Radius));
end Draw;
-- Circle 重写 To_String
function To_String (C : Circle) return String is
begin
return "Circle at (" & Float'Image (C.Center.X) & ", " &
Float'Image (C.Center.Y) & ") radius=" & Float'Image (C.Radius);
end To_String;
-- Rectangle 实现
function Area (R : Rectangle) return Float is
begin
return R.Width * R.Height;
end Area;
procedure Draw (R : Rectangle) is
begin
Ada.Text_IO.Put_Line ("Rectangle at (" & Float'Image (R.Center.X) & ", " &
Float'Image (R.Center.Y) & ") " &
Float'Image (R.Width) & "x" & Float'Image (R.Height));
end Draw;
-- 多态过程:通过类宽类型参数实现动态分派
procedure Display (S : Shape'Class) is
begin
Draw (S); -- 动态分派:根据 S 的实际类型调用对应的 Draw
Ada.Text_IO.Put_Line ("Area = " & Float'Image (Area (S)));
end Display;
-- 实现 Serializable 接口
overriding procedure Serialize (SC : Serializable_Circle; Stream : access Ada.Text_IO.File_Type'Class) is
begin
Ada.Text_IO.Put (Stream.all, "Circle,");
Ada.Text_IO.Put (Stream.all, Float'Image (SC.Center.X) & ",");
Ada.Text_IO.Put (Stream.all, Float'Image (SC.Center.Y) & ",");
Ada.Text_IO.Put_Line (Stream.all, Float'Image (SC.Radius));
end Serialize;
end Shapes;
-- 文件名:main.adb
-- 测试面向对象特性
with Shapes; use Shapes;
with Ada.Text_IO;
procedure Main is
C : Circle := (Center => (0.0, 0.0), Radius => 5.0);
R : Rectangle := (Center => (10.0, 10.0), Width => 4.0, Height => 3.0);
-- 类宽类型数组:存储不同形状的访问值
type Shape_Ptr is access all Shape'Class;
Shapes_Array : array (1 .. 2) of Shape_Ptr := (new Circle'(C), new Rectangle'(R));
-- 接口测试
SC : Serializable_Circle := (Center => (1.0, 2.0), Radius => 3.0);
File : Ada.Text_IO.File_Type;
begin
-- 直接调用(静态分派)
Ada.Text_IO.Put_Line("=== Direct calls ===");
Draw (C);
Draw (R);
-- 多态调用(动态分派)
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line("=== Polymorphic calls ===");
Display (C);
Display (R);
-- 通过类宽引用数组统一处理
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line("=== Array of class-wide ===");
for I in Shapes_Array'Range loop
Display (Shapes_Array (I).all);
end loop;
-- 接口使用:序列化到文件
Ada.Text_IO.Create (File, Ada.Text_IO.Out_File, "circle.csv");
Serialize (SC, File'Access);
Ada.Text_IO.Close (File);
Ada.Text_IO.Put_Line ("Serialized circle to circle.csv");
end Main;
编译运行:
gnatmake main.adb
./main
输出示例:
=== Direct calls ===
Circle at ( 0.00000E+00, 0.00000E+00) radius= 5.00000E+00
Rectangle at ( 1.00000E+01, 1.00000E+01) 4.00000E+00x 3.00000E+00
=== Polymorphic calls ===
Circle at ( 0.00000E+00, 0.00000E+00) radius= 5.00000E+00
Area = 7.85398E+01
Rectangle at ( 1.00000E+01, 1.00000E+01) 4.00000E+00x 3.00000E+00
Area = 1.20000E+01
=== Array of class-wide ===
Circle at ( 0.00000E+00, 0.00000E+00) radius= 5.00000E+00
Area = 7.85398E+01
Rectangle at ( 1.00000E+01, 1.00000E+01) 4.00000E+00x 3.00000E+00
Area = 1.20000E+01
Serialized circle to circle.csv
八、本课总结
- 标记记录(
tagged)是 Ada 面向对象的基础,通过隐藏标签支持运行时类型识别和动态分派。 - 使用
new ... with实现单继承,可以添加新组件和重写 primitive operations。 - Primitive operations 是与类型绑定的子程序,支持重写和动态分派。判断规则:在同一包中,参数或返回值为该类型(非类宽)。
- 动态分派通过类宽类型(
T'Class)实现,在运行时根据对象实际标签选择操作。类宽类型只能用于参数、访问类型,不能声明变量。 - 抽象类型(
abstract)定义接口契约,不能实例化;接口(interface)允许多重继承,没有数据组件。 - Ada 的面向对象与包机制紧密结合,强调封装和强类型安全。动态分派性能开销可控(两次间接寻址),适合实时系统。
- 通过
Ada.Finalization.Controlled可以实现资源管理的构造/析构机制。
九、课后练习
-
基本继承:定义
Square类型继承自Rectangle,重写Area(确保面积计算正确)和Draw(输出“正方形”)。测试其多态行为,将Square对象传递给Display过程,观察输出。 -
多态容器:实现一个
Shape_List包,内部使用access Shape'Class的动态数组(或链表)。提供Add_Shape(接受任何形状)、Display_All(遍历并调用Draw)和Total_Area(返回所有形状总面积)函数。测试添加不同形状。 -
抽象工厂:定义一个抽象类型
Button,包含抽象操作Click。派生WindowsButton和LinuxButton,实现不同的Click行为(输出不同消息)。编写多态测试,通过一个数组(Button'Class访问类型)统一调用Click。分析如果添加MacButton需要修改哪些代码。 -
接口实践:定义
Comparable接口,包含Less抽象函数(返回 Boolean)。让Circle实现按面积比较,Rectangle也实现按面积比较。编写函数Max_Area,接受两个Shape'Class,返回面积较大的那个(使用Less接口)。注意:需要将Shape'Class转换为Comparable'Class才能调用Less。 -
动态分派深入:分析如果没有类宽类型而使用具体类型,为何无法实现多态?编写代码对比:一个过程接受
Shape而不是Shape'Class,传递Circle会发生什么(静态分派)。写出你的分析,包括编译行为和运行时输出。 -
内存布局:使用
'Size和'Address属性,打印Shape、Circle、Rectangle对象的大小和组件偏移(提示:可使用'Position、'First_Bit、'Last_Bit对于记录组件)。解释为什么标记记录比普通记录大,并计算理论大小与实际大小的差异(考虑对齐)。 -
重写与隐藏:在
Circle中定义一个与父类Shape同名的函数To_String(参数为Circle,是重写)。测试调用Display(接受Shape'Class)时,To_String是否被动态分派?如果To_String不是 primitive operation(例如参数是Circle'Class),会发生什么?解释原因。 -
访问类型与多态:使用
access Shape'Class创建单向链表,每个节点可以指向任何形状。实现链表插入(头插法)、遍历并调用Draw。注意内存释放:实现一个Free_List过程,遍历释放所有节点和形状对象。 -
异常与多态:在
Circle的Area函数中,如果半径为负数,抛出自定义异常Negative_Radius(带错误消息)。在多态调用(通过Display)中捕获该异常,并输出“无效圆半径”。确保异常不影响其他形状的处理。 -
设计模式:策略模式:实现“策略模式”。定义一个接口
Sort_Strategy,包含Sort抽象操作(接受in out Integer_Array)。实现两个策略:Bubble_Sort_Strategy和Quick_Sort_Strategy。编写一个Context类(标记记录),包含一个策略访问值(access Sort_Strategy'Class),提供Set_Strategy和Execute_Sort方法。在主程序中,动态切换策略并排序同一数组,验证结果正确性。
十、下节预告
第22课|访问类型的高级特性
我们将:
- 深入理解访问类型与动态分派的结合(
access T'Class的用途) - 掌握通用访问类型(
access all)与匿名访问(access参数)的区别和适用场景 - 学习访问类型在容器(链表、树)和回调(函数指针)中的应用
- 分析内存管理策略:手动释放(
Unchecked_Deallocation)、存储池(Storage_Pool)、引用计数(Ada.Containers.Reference_Counted) - 理解访问类型的安全性限制(
aliased、not null等)
关键术语表
标记记录(tagged record):包含隐藏标签的记录,支持继承和动态分派。标签指向分派表。
扩展记录(extension):在父记录基础上添加新组件的派生类型,语法
new T with ...。Primitive operation:与类型绑定的子程序,可被继承和重写,是实现多态的基础。判定规则:参数或返回值为该类型(非类宽),且在同一包中。
动态分派(dynamic dispatch):运行时根据对象实际类型选择 primitive operation 的机制,通过分派表实现。
类宽类型(class-wide type):
T'Class,表示 T 及其派生类型的集合,用于多态参数。不能声明变量,只能用于参数和访问类型。分派表(dispatch table):每个标记记录类型关联的表,存储所有 primitive operations 的地址。对象通过 tag 找到分派表。
抽象类型(abstract type):不能实例化的基类型,通常包含抽象操作。
抽象操作(abstract operation):没有实现的子程序,必须在派生类型中重写。
接口(interface):纯抽象类型,没有数据组件,允许多重继承(
type T is interface)。视图转换(view conversion):将派生对象视为父类对象或类宽对象的类型转换,不改变内存布局。