第12课|数组类型详解

10 阅读11分钟

本课目标

完成本课后,你将能够:

  • 理解 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 .. 'LastA'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) 简化遍历
  • 数组在科学计算、数据统计等领域应用广泛

十、课后练习

  1. 基本练习:定义一个 Vector 类型(1..10的整数),求所有元素的平均值、最大值和最小值。
  2. 非约束数组:编写一个函数,接受任意长度的整数数组,返回逆序后的新数组(使用连接或循环)。
  3. 多维数组:实现一个 3x3 矩阵转置程序,输入矩阵输出转置结果。
  4. 切片应用:编写程序,将一个 10 元素数组分为两个 5 元素子数组,分别求和,再比较大小。
  5. 字符串数组:定义 type Word_List is array (1 .. 10) of String (1 .. 20);,读入最多10个单词,按字母顺序排序输出(可引入 Ada.Strings.Unbounded 简化)。
  6. 成绩扩展:修改成绩统计示例,增加一个“总分”列,并找出最高分学生。

十一、下节预告

第13课|记录类型详解

我们将:

  • 掌握记录的声明与使用
  • 学习记录变体(判别式)实现类型安全联合体
  • 理解记录的表示子句与内存布局控制
  • 应用记录进行复杂数据建模

关键术语表

约束数组:索引范围在类型定义时固定的数组类型

非约束数组:索引范围在对象声明时才指定的数组类型,使用 <> 表示

数组属性'First, 'Last, 'Range, 'Length 等,用于查询数组边界和大小

数组聚集:一次性给整个数组赋值的语法结构,支持位置关联、命名关联和 others

切片:数组的一段连续子序列,用 A (L .. R) 表示

元素迭代:Ada 2012 引入的 for Element of A 语法,直接遍历数组元素

多维数组:具有两个或以上维度的数组,通过多个索引访问

字符串String 类型,本质是 Character 的一维约束数组