本课目标
完成本课后,你将能够:
- 掌握记录类型的声明与使用
- 熟练运用记录聚集进行初始化
- 理解记录变体(判别式)实现类型安全联合体
- 学习记录的表示子句控制内存布局
- 应用记录进行复杂数据建模
一、记录类型基础
1.1 什么是记录
记录是异构数据的聚合体,类似其他语言中的结构体(struct),可将不同类型的数据组合成一个逻辑单元。
-- 基本记录声明
type Point is record
X : Float;
Y : Float;
end record;
-- 使用记录
P : Point;
P.X := 10.5;
P.Y := 20.3;
1.2 记录组件
记录可以包含任意类型的组件,包括标量、数组,甚至其他记录:
type Date is record
Year : Integer range 1900 .. 2100;
Month : Integer range 1 .. 12;
Day : Integer range 1 .. 31;
end record;
type Person is record
Name : String (1 .. 50);
Age : Integer range 0 .. 150;
Birth : Date; -- 嵌套记录
Salary : Float;
end record;
-- 访问嵌套组件
John : Person;
John.Birth.Year := 1990;
1.3 记录初始化
记录变量声明时可以同时初始化:
-- 位置关联(按声明顺序)
P : Point := (10.5, 20.3);
-- 命名关联(推荐,更清晰)
P : Point := (X => 10.5, Y => 20.3);
-- 部分初始化(其余用 others,但必须覆盖所有组件)
P : Point := (X => 10.5, others => 0.0);
-- 嵌套记录初始化
John : Person := (
Name => "John Doe ",
Age => 30,
Birth => (Year => 1990, Month => 5, Day => 15),
Salary => 5000.0
);
二、记录属性与操作
2.1 记录属性
记录类型提供了有限但有用的属性:
| 属性 | 含义 | 示例 |
|---|---|---|
'Size | 记录占用的总位数 | Person'Size |
'Alignment | 记录的对齐要求 | Person'Alignment |
'Bit_Order | 位顺序(与表示子句相关) | - |
2.2 记录的赋值与比较
记录支持整体赋值和相等比较:
P1, P2 : Point;
P1 := (10.5, 20.3);
P2 := P1; -- 整体赋值,复制所有组件
if P1 = P2 then -- 相等比较,要求所有组件相等
Ada.Text_IO.Put_Line ("相同");
end if;
注意:如果记录包含无约束数组(如 String 类型未指定长度),则赋值和比较可能受限,但通常可以使用有约束组件。
2.3 记录作为子程序参数
记录可以作为子程序的参数,可以按 in、in out、out 传递:
procedure Move (P : in out Point; Delta_X, Delta_Y : Float) is
begin
P.X := P.X + Delta_X;
P.Y := P.Y + Delta_Y;
end Move;
function Distance (P1, P2 : Point) return Float is
begin
return (P2.X - P1.X) ** 2 + (P2.Y - P1.Y) ** 2;
end Distance;
三、记录变体(判别式)
3.1 变体记录的概念
变体记录允许记录的结构根据判别式动态改变,是一种类型安全的联合体(union)。
type Shape_Kind is (Circle, Rectangle, Triangle);
type Shape (Kind : Shape_Kind) is record
Color : String (1 .. 10); -- 所有变体共享的组件
case Kind is
when Circle =>
Radius : Float;
when Rectangle =>
Width, Height : Float;
when Triangle =>
Side_A, Side_B, Side_C : Float;
end case;
end record;
3.2 变体记录的使用
声明变体记录变量时必须指定判别式:
C : Shape (Kind => Circle); -- 判别式为 Circle
C.Radius := 5.0;
C.Color := "Red ";
R : Shape (Kind => Rectangle);
R.Width := 10.0;
R.Height := 20.0;
-- 访问不存在的组件会编译错误
-- C.Width := 1.0; -- 错误!Circle 变体没有 Width
3.3 判别式的约束与默认值
可以为判别式提供默认值:
type Shape (Kind : Shape_Kind := Circle) is record -- 默认 Circle
Color : String (1 .. 10);
case Kind is
when Circle => Radius : Float;
when Rectangle => Width, Height : Float;
when Triangle => Side_A, Side_B, Side_C : Float;
end case;
end record;
-- 可以省略判别式,使用默认值
C : Shape; -- 默认 Kind = Circle
C := (Kind => Rectangle, -- 可以改变判别式,但会导致组件变化
Color => "Blue ",
Width => 10.0, Height => 20.0);
3.4 判别式改变与约束
Ada 规定,改变判别式时,旧的变体组件会被丢弃,新的变体组件被初始化。需要整体赋值:
C : Shape (Kind => Circle);
C.Radius := 5.0;
-- 要改成矩形,必须整体赋值
C := (Kind => Rectangle, Color => "Yellow ", Width => 8.0, Height => 12.0);
-- 此时 C.Width 可用,C.Radius 不再存在
四、记录的表示子句
4.1 内存布局控制
在嵌入式或系统编程中,可能需要精确控制记录的内存布局,以匹配硬件寄存器或协议数据包。Ada 提供表示子句来实现:
-- 位字段定义
type Status_Register is record
Enable : Boolean; -- 位 0
Ready : Boolean; -- 位 1
Error : Boolean; -- 位 2
Reserved : Integer range 0 .. 2#11111#; -- 位 3..7
end record;
-- 指定存储布局
for Status_Register use record
Enable at 0 range 0 .. 0;
Ready at 0 range 1 .. 1;
Error at 0 range 2 .. 2;
Reserved at 0 range 3 .. 7;
end record;
for Status_Register'Size use 8; -- 总大小 8 位
4.2 使用 at 子句指定地址
可以使用 at 子句将变量固定在特定内存地址,常用于硬件寄存器映射:
Hardware_Status : Status_Register;
for Hardware_Status'Address use System'To_Address (16#4000_1000#);
pragma Volatile (Hardware_Status); -- 禁止优化,因硬件可能异步改变
4.3 紧凑存储
使用 pragma Pack 可以压缩记录,取消对齐填充:
type Packed_Record is record
A : Boolean;
B : Integer range 0 .. 255;
C : Boolean;
end record;
pragma Pack (Packed_Record);
-- 现在大小可能从 12 字节压缩到 1+1+1=3 字节(取决于编译器)
五、记录与数组的配合
5.1 记录数组
可以声明记录类型的数组:
type Person_Array is array (1 .. 100) of Person;
Employees : Person_Array;
-- 初始化
Employees (1) := (Name => "Alice ", Age => 25,
Birth => (1998, 3, 12), Salary => 4500.0);
5.2 数组作为记录组件
type Matrix is array (1 .. 3, 1 .. 3) of Float;
type Data_Record is record
Name : String (1 .. 20);
M : Matrix;
end record;
六、记录的继承(标记记录简介)
Ada 支持面向对象编程,标记记录(tagged record)是基类的基础。本课简要介绍,详细内容将在后续面向对象章节展开。
-- 标记记录(tagged record)
type Shape is tagged record
Color : String (1 .. 10);
end record;
-- 从 Shape 派生
type Circle is new Shape with record
Radius : Float;
end record;
-- 可重载操作
function Area (S : Shape) return Float is (0.0);
function Area (C : Circle) return Float is (3.14159 * C.Radius ** 2);
七、完整示例:学生信息管理系统
-- 文件名: student_record.adb
-- 功能:使用记录管理学生信息
with Ada.Text_IO;
with Ada.Integer_Text_IO;
with Ada.Float_Text_IO;
procedure Student_Record is
-- 学生类型枚举
type Student_Type is (Undergrad, Graduate);
-- 学生记录(带变体)
type Student (Category : Student_Type := Undergrad) is record
ID : Integer range 10000 .. 99999;
Name : String (1 .. 20);
Age : Integer range 18 .. 60;
GPA : Float range 0.0 .. 4.0;
case Category is
when Undergrad =>
Year : Integer range 1 .. 4; -- 年级
when Graduate =>
Advisor : String (1 .. 20); -- 导师姓名
Thesis_Title : String (1 .. 50);
end case;
end record;
-- 数据库:最多100名学生
type Student_Array is array (1 .. 100) of Student;
Students : Student_Array;
Student_Count : Integer := 0;
-- 打印学生信息
procedure Print_Student (S : Student) is
begin
Ada.Text_IO.Put ("ID: " & Integer'Image (S.ID));
Ada.Text_IO.Put (", Name: " & S.Name);
Ada.Text_IO.Put (", Age: " & Integer'Image (S.Age));
Ada.Text_IO.Put (", GPA: ");
Ada.Float_Text_IO.Put (S.GPA, Fore => 1, Aft => 2, Exp => 0);
case S.Category is
when Undergrad =>
Ada.Text_IO.Put_Line (", Year: " & Integer'Image (S.Year));
when Graduate =>
Ada.Text_IO.Put (", Advisor: " & S.Advisor);
Ada.Text_IO.Put_Line (", Thesis: " & S.Thesis_Title);
end case;
end Print_Student;
-- 添加本科生
procedure Add_Undergrad (ID : Integer; Name : String; Age : Integer;
GPA : Float; Year : Integer) is
begin
Student_Count := Student_Count + 1;
Students (Student_Count) := (Category => Undergrad,
ID => ID,
Name => Name,
Age => Age,
GPA => GPA,
Year => Year);
end Add_Undergrad;
-- 添加研究生
procedure Add_Graduate (ID : Integer; Name : String; Age : Integer;
GPA : Float; Advisor : String;
Thesis_Title : String) is
begin
Student_Count := Student_Count + 1;
Students (Student_Count) := (Category => Graduate,
ID => ID,
Name => Name,
Age => Age,
GPA => GPA,
Advisor => Advisor,
Thesis_Title => Thesis_Title);
end Add_Graduate;
-- 查找最高 GPA 的学生
function Find_Top_Student return Integer is
Top_Index : Integer := 1;
begin
for I in 2 .. Student_Count loop
if Students (I).GPA > Students (Top_Index).GPA then
Top_Index := I;
end if;
end loop;
return Top_Index;
end Find_Top_Student;
begin
-- 添加一些示例数据
Add_Undergrad (10001, "Alice ", 20, 3.8, 3);
Add_Undergrad (10002, "Bob ", 21, 3.5, 4);
Add_Graduate (20001, "Charlie ", 25, 3.9,
"Dr. Smith ", "Deep Learning in Ada");
Add_Graduate (20002, "Diana ", 24, 3.7,
"Dr. Johnson ", "Real-Time Systems");
-- 打印所有学生
Ada.Text_IO.Put_Line ("=== 学生列表 ===");
for I in 1 .. Student_Count loop
Ada.Text_IO.Put (Integer'Image (I) & ". ");
Print_Student (Students (I));
end loop;
-- 输出最高 GPA 学生
declare
Top := Find_Top_Student;
begin
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== 最高 GPA 学生 ===");
Print_Student (Students (Top));
end;
end Student_Record;
编译运行:
gnatmake student_record.adb
./student_record
八、本课总结
- 记录是异构数据的聚合,通过
record ... end record定义 - 记录聚集支持位置关联和命名关联初始化
- 变体记录使用判别式实现类型安全的联合体,结构可根据判别式动态变化
- 表示子句(
at子句、for ... use record)可精确控制内存布局,用于硬件映射和协议数据 pragma Pack压缩记录以节省空间- 记录可作为数组元素,也可包含数组
- 标记记录(tagged record)是 Ada 面向对象的基础
九、课后练习
-
基本记录:定义一个
Complex记录(实部、虚部),编写函数实现复数加法、减法和乘法。 -
变体记录:定义一个
Vehicle记录,根据Vehicle_Type(Car、Truck、Motorcycle)包含不同的字段(如 Car 有 num_doors,Truck 有 cargo_capacity 等),并编写程序输出不同车辆的信息。 -
表示子句:定义一个
Control_Register记录,包含 3 个布尔字段和 1 个 5 位整数字段,使用表示子句精确控制位布局,并尝试输出其大小。 -
记录数组:创建一个包含 10 个
Point记录的数组,计算所有点的中心点(X、Y 的平均值)。 -
学生记录扩展:在学生管理系统中增加一个函数,按 GPA 排序并输出前三名学生。
十、下节预告
第14课|访问类型(指针)
我们将:
- 理解 Ada 访问类型与普通指针的区别
- 掌握访问类型的声明与使用
- 学习动态内存分配与释放
- 理解访问类型的安全性机制(作用域、null 排除)
- 应用访问类型构建链表、树等动态数据结构
关键术语表
记录:异构数据的聚合体,类似其他语言的
struct组件:记录中的单个数据字段
记录聚集:一次性初始化记录所有组件的语法
判别式:变体记录中决定当前变体结构的字段
变体记录:根据判别式改变组件集合的记录类型
表示子句:指定记录或组件存储布局的语法(
at、for ... use)
pragma Pack:编译器指令,要求紧凑存储记录,取消对齐填充标记记录:支持继承和多态的记录类型,Ada 面向对象的基础
位字段:在单个存储单元中打包多个小范围数据的布局技术