本课目标
完成本课后,你将能够:
- 理解 Ada 数组的类型体系与设计哲学
- 掌握约束数组与非约束数组的定义与使用
- 熟练操作多维数组与数组切片
- 运用数组属性进行高效编程
- 理解数组运算与数组字面量的规则
- 应用数组解决实际数据处理问题
一、数组类型基础
1.1 数组的定义
数组是同类型元素的有序集合,每个元素通过一个或多个索引访问。Ada 的数组是类型化的,索引可以是任何离散类型。
-- 一维数组
type Vector is array (1 .. 10) of Float; -- 索引范围 1..10
type Int_Array is array (Integer range <>) of Integer; -- 无约束数组
-- 多维数组
type Matrix is array (1 .. 3, 1 .. 3) of Float; -- 3x3 矩阵
type Cube is array (1 .. 5, 1 .. 5, 1 .. 5) of Float;
-- 使用数组变量
V : Vector; -- V 有10个元素
M : Matrix; -- M 有9个元素
1.2 数组索引的类型
索引可以是任何离散类型(整数、枚举、字符等):
type Day is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
type Daily_Temp is array (Day) of Float; -- 以星期为索引
type Char_Count is array (Character) of Natural; -- 以字符为索引
-- 使用
Temps : Daily_Temp;
Temps (Mon) := 25.5;
Temps (Tue) := 26.0;
1.3 数组的静态与动态性质
Ada 数组在编译时要求索引类型和元素类型已知,但范围可以动态确定:
procedure Array_Bounds (N : Integer) is
type Dynamic_Vector is array (1 .. N) of Float; -- N 必须在进入时已知
A : Dynamic_Vector; -- OK,因为 N 是过程的参数
begin
...
end;
但数组类型的索引范围必须在对象声明时确定(约束数组)或在类型中留待以后约束(非约束数组)。
二、约束数组与非约束数组
2.1 约束数组
约束数组在类型定义时就固定了索引范围:
type Vector_10 is array (1 .. 10) of Float; -- 固定长度10
type Week_Temp is array (Day) of Float; -- 固定7个元素
-- 声明变量
V : Vector_10; -- 自动包含10个元素
W : Week_Temp; -- 自动包含7个元素
特点:
- 编译时大小已知
- 可直接分配栈上空间
- 索引检查在编译和运行期进行
2.2 非约束数组
非约束数组只定义索引的类型,不指定范围,使用 <> 表示“范围待定”:
type Int_Vector is array (Integer range <>) of Integer;
type Matrix is array (Integer range <>, Integer range <>) of Float;
声明变量时必须指定范围:
V1 : Int_Vector (1 .. 100); -- 1..100
V2 : Int_Vector (0 .. 99); -- 0..99
V3 : Int_Vector (5 .. 15); -- 5..15
M1 : Matrix (1 .. 10, 1 .. 20); -- 10行20列
M2 : Matrix (-5 .. 5, 0 .. 10); -- 负索引也合法
特点:
- 灵活性高,同一类型可对应不同大小的数组
- 大小在运行时确定
- 常用于子程序参数(可接受任意大小的数组)
2.3 无约束数组作为参数
子程序可以接受任意大小的非约束数组作为参数:
function Sum (A : Int_Vector) return Integer is
Result : Integer := 0;
begin
for I in A'Range loop -- 使用属性获取实际范围
Result := Result + A (I);
end loop;
return Result;
end Sum;
-- 调用
Total := Sum (V1); -- V1 是 Int_Vector (1..100)
Total := Sum (V2); -- V2 是 Int_Vector (0..99)
三、数组属性
数组类型和对象提供丰富的属性,用于查询数组的边界和大小:
| 属性 | 含义 | 示例 |
|---|---|---|
'First (N) | 第 N 维的下界(N 默认为 1) | A'First, A'First (2) |
'Last (N) | 第 N 维的上界 | A'Last, A'Last (2) |
'Range (N) | 第 N 维的范围('First .. 'Last) | A'Range, A'Range (2) |
'Length (N) | 第 N 维的长度 | A'Length, A'Length (2) |
'Component_Size | 每个元素占用的位数 | A'Component_Size |
with Ada.Text_IO;
procedure Array_Attributes is
type Matrix is array (1 .. 5, 2 .. 8) of Float;
M : Matrix;
begin
Ada.Text_IO.Put_Line ("第一维下界: " & Integer'Image (M'First (1)));
Ada.Text_IO.Put_Line ("第一维上界: " & Integer'Image (M'Last (1)));
Ada.Text_IO.Put_Line ("第一维长度: " & Integer'Image (M'Length (1)));
Ada.Text_IO.Put_Line ("第二维下界: " & Integer'Image (M'First (2)));
Ada.Text_IO.Put_Line ("第二维上界: " & Integer'Image (M'Last (2)));
Ada.Text_IO.Put_Line ("第二维长度: " & Integer'Image (M'Length (2)));
-- 使用 Range 遍历
for I in M'Range (1) loop
for J in M'Range (2) loop
M (I, J) := 0.0;
end loop;
end loop;
end Array_Attributes;
四、数组字面量与聚集
4.1 数组聚集(Aggregates)
使用聚集可以一次性给整个数组赋值:
type Vector is array (1 .. 5) of Integer;
-- 位置关联
V : Vector := (1, 2, 3, 4, 5);
-- 命名关联(指定索引)
W : Vector := (1 => 10, 3 => 30, 2 => 20, 5 => 50, 4 => 40);
-- 混合使用
X : Vector := (1, 2, others => 0); -- 其他元素为 0
-- 使用 `others` 初始化所有未指定元素
Y : Vector := (others => 1); -- 全部为 1
4.2 多维数组聚集
type Matrix is array (1 .. 2, 1 .. 3) of Integer;
-- 按行嵌套
M : Matrix := ((1, 2, 3), (4, 5, 6));
-- 命名关联
N : Matrix := (1 => (1, 2, 3), 2 => (4, 5, 6));
-- 混合
P : Matrix := (1 => (1, 2, 3), 2 => (others => 0));
4.3 字符串字面量
字符串是 String 类型的数组字面量:
Message : String := "Hello";
-- 等价于: Message : String (1 .. 5) := ('H', 'e', 'l', 'l', 'o');
-- 空字符串
Empty : String := "";
五、数组操作
5.1 元素访问与赋值
V (3) := 100; -- 修改元素
X := V (1) + V (2); -- 读取元素
-- 边界检查:如果索引越界,引发 Constraint_Error
5.2 数组切片(Slices)
切片是数组的一段连续子序列:
type Vector is array (1 .. 10) of Integer;
V : Vector := (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
-- 切片 V (L .. R)
Slice_1 : Vector (3 .. 7) := V (3 .. 7); -- [3,4,5,6,7]
-- 切片可赋值
V (2 .. 4) := (20, 30, 40); -- 将 V(2)=20, V(3)=30, V(4)=40
-- 切片可作为参数
procedure Process (A : Vector (2 .. 5)) is ... -- 必须匹配边界
多维数组切片:
type Matrix is array (1 .. 5, 1 .. 5) of Float;
M : Matrix;
-- 取一行
Row_3 : Matrix (3, 1 .. 5) := M (3, 1 .. 5); -- 第三行
-- 取一列(需用循环,Ada 不支持直接列切片)
5.3 数组比较
两个同类型的数组可以直接用 = 和 /= 比较,要求所有元素相等:
type Vector is array (1 .. 3) of Integer;
A : Vector := (1, 2, 3);
B : Vector := (1, 2, 3);
C : Vector := (1, 0, 3);
Result : Boolean;
begin
Result := (A = B); -- True
Result := (A = C); -- False
Result := (A /= C); -- True
end;
5.4 数组连接(仅一维数组)
使用 & 运算符可以连接两个一维数组,或数组与单个元素:
type Int_Vector is array (Integer range <>) of Integer;
A : Int_Vector (1 .. 3) := (1, 2, 3);
B : Int_Vector (1 .. 2) := (4, 5);
C : Int_Vector := A & B; -- (1,2,3,4,5) 长度5
D : Int_Vector := A & 6; -- (1,2,3,6)
E : Int_Vector := 0 & A; -- (0,1,2,3)
-- 连接返回无约束数组,需指定变量类型
5.5 数组赋值与复制
相同类型的数组可以直接赋值,会复制所有元素:
type Vector is array (1 .. 10) of Integer;
A, B : Vector;
begin
A := (others => 1);
B := A; -- B 获得 A 的副本
end;
如果类型相同但范围不同,不能直接赋值:
type Vector_5 is array (1 .. 5) of Integer;
type Vector_10 is array (1 .. 10) of Integer;
V5 : Vector_5;
V10 : Vector_10;
-- V10 := V5; -- 错误!类型不匹配
六、多维数组深入
6.1 矩阵运算示例
with Ada.Text_IO;
procedure Matrix_Ops is
type Matrix is array (1 .. 3, 1 .. 3) of Float;
A, B, C : Matrix;
Sum : Float;
begin
-- 初始化 A 和 B
A := ((1.0, 2.0, 3.0),
(4.0, 5.0, 6.0),
(7.0, 8.0, 9.0));
B := ((9.0, 8.0, 7.0),
(6.0, 5.0, 4.0),
(3.0, 2.0, 1.0));
-- 矩阵加法
for I in A'Range (1) loop
for J in A'Range (2) loop
C (I, J) := A (I, J) + B (I, J);
end loop;
end loop;
-- 矩阵乘法
for I in A'Range (1) loop
for J in B'Range (2) loop
Sum := 0.0;
for K in A'Range (2) loop -- 或 B'Range (1)
Sum := Sum + A (I, K) * B (K, J);
end loop;
C (I, J) := Sum;
end loop;
end loop;
end Matrix_Ops;
6.2 动态多维数组
使用无约束数组类型创建动态多维数组:
type Matrix is array (Integer range <>, Integer range <>) of Float;
procedure Process_Matrix (M : in out Matrix) is
begin
for I in M'Range (1) loop
for J in M'Range (2) loop
M (I, J) := M (I, J) * 2.0;
end loop;
end loop;
end Process_Matrix;
-- 调用
M1 : Matrix (1 .. 10, 1 .. 20); -- 10x20
M2 : Matrix (-5 .. 5, 0 .. 10); -- 11x11
七、数组与循环
7.1 遍历数组
推荐使用数组属性 'Range 遍历,确保边界正确:
procedure Traverse (A : Int_Vector) is
begin
for I in A'Range loop
Process (A (I));
end loop;
end Traverse;
7.2 使用 for ... in 直接枚举元素(Ada 2012+)
Ada 2012 引入了元素迭代语法:
procedure Iterate_Elements (A : Int_Vector) is
begin
for Element of A loop
Process (Element);
end loop;
end Iterate_Elements;
-- 如果需要修改元素,使用 `in out`
for Element in out A loop
Element := Element * 2;
end loop;
7.3 多维数组遍历
for I in M'Range (1) loop
for J in M'Range (2) loop
M (I, J) := ...
end loop;
end loop;
八、完整示例:学生成绩统计
-- 文件名: student_grades.adb
-- 功能:使用数组统计学生成绩
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Student_Grades is
-- 定义学生人数和科目数常量
Num_Students : constant := 5;
Num_Subjects : constant := 3;
-- 定义科目枚举
type Subject is (Math, Physics, Chemistry);
-- 定义成绩表:学生 x 科目
type Grade_Table is array (1 .. Num_Students, Subject) of Float;
-- 学生姓名数组
type Name_Array is array (1 .. Num_Students) of String (1 .. 10);
Student_Names : constant Name_Array :=
("Alice ", "Bob ", "Charlie ", "Diana ", "Eve ");
-- 成绩数据
Grades : Grade_Table :=
(1 => (Math => 85.5, Physics => 92.0, Chemistry => 78.0),
2 => (Math => 76.0, Physics => 81.5, Chemistry => 88.5),
3 => (Math => 93.0, Physics => 89.0, Chemistry => 91.0),
4 => (Math => 67.5, Physics => 74.0, Chemistry => 70.0),
5 => (Math => 88.0, Physics => 84.0, Chemistry => 82.5));
-- 计算每个学生的平均分
type Student_Avg is array (1 .. Num_Students) of Float;
Student_Averages : Student_Avg;
-- 计算每门科目的平均分
type Subject_Avg is array (Subject) of Float;
Subject_Averages : Subject_Avg;
begin
-- 计算学生平均分
for S in 1 .. Num_Students loop
declare
Sum : Float := 0.0;
begin
for Sub in Subject loop
Sum := Sum + Grades (S, Sub);
end loop;
Student_Averages (S) := Sum / Float (Num_Subjects);
end;
end loop;
-- 计算科目平均分
for Sub in Subject loop
declare
Sum : Float := 0.0;
begin
for S in 1 .. Num_Students loop
Sum := Sum + Grades (S, Sub);
end loop;
Subject_Averages (Sub) := Sum / Float (Num_Students);
end;
end loop;
-- 输出学生成绩表
Ada.Text_IO.Put_Line ("学生成绩统计");
Ada.Text_IO.Put_Line ("=" * 50);
Ada.Text_IO.Put ("姓名 ");
for Sub in Subject loop
Ada.Text_IO.Put (Sub'Image & " ");
end loop;
Ada.Text_IO.Put_Line ("平均分");
Ada.Text_IO.Put_Line ("-" * 50);
for S in 1 .. Num_Students loop
Ada.Text_IO.Put (Student_Names (S));
for Sub in Subject loop
Ada.Float_Text_IO.Put (Grades (S, Sub), Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.Put (" ");
end loop;
Ada.Float_Text_IO.Put (Student_Averages (S), Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.New_Line;
end loop;
-- 输出科目平均分
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("各科目平均分");
Ada.Text_IO.Put_Line ("-" * 20);
for Sub in Subject loop
Ada.Text_IO.Put (Sub'Image & ": ");
Ada.Float_Text_IO.Put (Subject_Averages (Sub), Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.New_Line;
end loop;
end Student_Grades;
编译运行:
gnatmake student_grades.adb
./student_grades
九、本课总结
- 数组是同类型元素的集合,索引可以是任何离散类型
- 约束数组在类型定义时固定范围,非约束数组使用
<>留待声明时指定 - 数组提供丰富的属性 (
'First,'Last,'Range,'Length) 用于安全遍历 - 数组聚集可一次性初始化数组,支持位置关联、命名关联和
others - 切片操作允许引用数组的一段连续子序列,方便子数组操作
- 数组可作为子程序参数传递,无约束数组类型可接受不同大小的实参
- Ada 2012 支持元素迭代 (
for Element of A) 简化遍历 - 数组在科学计算、数据统计等领域应用广泛
十、课后练习
- 基本练习:定义一个
Vector类型(1..10的整数),求所有元素的平均值、最大值和最小值。 - 非约束数组:编写一个函数,接受任意长度的整数数组,返回逆序后的新数组(使用连接或循环)。
- 多维数组:实现一个 3x3 矩阵转置程序,输入矩阵输出转置结果。
- 切片应用:编写程序,将一个 10 元素数组分为两个 5 元素子数组,分别求和,再比较大小。
- 字符串数组:定义
type Word_List is array (1 .. 10) of String (1 .. 20);,读入最多10个单词,按字母顺序排序输出(可引入Ada.Strings.Unbounded简化)。 - 成绩扩展:修改成绩统计示例,增加一个“总分”列,并找出最高分学生。
十一、下节预告
第13课|记录类型详解
我们将:
- 掌握记录的声明与使用
- 学习记录变体(判别式)实现类型安全联合体
- 理解记录的表示子句与内存布局控制
- 应用记录进行复杂数据建模
关键术语表
约束数组:索引范围在类型定义时固定的数组类型
非约束数组:索引范围在对象声明时才指定的数组类型,使用
<>表示数组属性:
'First,'Last,'Range,'Length等,用于查询数组边界和大小数组聚集:一次性给整个数组赋值的语法结构,支持位置关联、命名关联和
others切片:数组的一段连续子序列,用
A (L .. R)表示元素迭代:Ada 2012 引入的
for Element of A语法,直接遍历数组元素多维数组:具有两个或以上维度的数组,通过多个索引访问
字符串:
String类型,本质是Character的一维约束数组