跳转到内容

Ada 编程/类型/数组

来自 Wikibooks,开放世界中的开放书籍

Ada. Time-tested, safe and secure.
Ada。经久耐用、安全可靠。


一个数组是元素的集合,可以通过一个或多个索引值访问。在 Ada 中,任何确定类型都允许作为元素,并且任何离散类型,即范围模数枚举,都可以用作索引。

声明数组

[编辑 | 编辑源代码]

Ada 的数组功能非常强大,因此有相当多的语法变体,如下所示。

基本语法

[编辑 | 编辑源代码]

Ada 数组的基本形式为

array (Index_Range) of Element_Type

其中 Index_Range 是离散索引类型内的值范围,Element_Type 是确定子类型。数组包含给定范围中每个可能值的“Element_Type”的一个元素。例如,如果您想计算特定字母在文本中出现的次数,您可以使用

type Character_Counter is array (Character) of Natural;

很多情况下,索引本身没有语义内容,它只被用作识别元素的一种手段,例如在列表中。因此,作为一个通用的建议,在这些情况下不要使用负索引。当使用数字索引时,从 1 开始而不是从 0 开始定义它们也是一种好的风格,因为它对人类来说更直观,并且避免了越界错误

但是,在某些情况下,负索引是有意义的。因此,请使用适合手头问题的索引。假设您是一名化学家,正在进行一些取决于温度的实验

type Temperature is range -10 .. +40;  -- Celsius

type Experiment is array (Temperature ) of Something;

已知子范围

[编辑 | 编辑源代码]

通常您不需要索引类型的所有可能值的数组。在这种情况下,您可以将您的索引类型子类型化为实际需要的范围。

subtype Index_Sub_Type is Index_Type range First .. Last

array (Index_Sub_Type) of Element_Type

由于这可能涉及大量输入,并且您也可能用完用于新子类型的有用名称,因此数组声明允许使用快捷方式

array (Index_Type range First .. Last) of Element_Type

自从第一最后Index_Type的表达式,上述内容的更简单形式为

array (First .. Last) of Element_Type

请注意,如果第一最后是数字文本,这意味着索引类型整数.

如果在上面的示例中字符计数器应该只计算大写字符并丢弃所有其他字符,则可以使用以下数组类型

type Character_Counter is array (Character range 'A' .. 'Z') of Natural;

未知子范围

[编辑 | 编辑源代码]

有时实际需要的范围在运行时才知道,或者您需要不同长度的对象。在某些语言中,您将求助于指向元素类型的指针。Ada 不是这样。这里我们有框“<>” ,它允许我们声明不确定数组

array (Index_Type range <>) of Element_Type;

当您声明此类类型的对象时,当然必须给出边界,并且该对象受其约束。

预定义类型字符串就是这样的类型。它被定义为

 type String is array (Positive range <>) of Character;

您可以通过多种方式定义此类无约束类型的对象(对除 String 之外的其他数组的推断应该很明显)

 Text : String (10 .. 20);
 Input: String := Read_from_some_file;

(这些声明还定义了 String 的匿名子类型。)在第一个示例中,显式给出了索引范围。在第二个示例中,从初始表达式隐式定义范围,此处可能是通过从某个文件读取数据的函数。这两个对象都受其范围约束,即它们不能增长或缩小。

带别名元素的数组

[编辑 | 编辑源代码]

如果您来自C/C++,您可能习惯于数组的每个元素都有一个地址。实际C/C++标准要求这样做。

在 Ada 中,情况并非如此。考虑以下数组

 type Day_Of_Month is range 1 .. 31;
 type Day_Has_Appointment is array (Day_Of_Month) of Boolean;
 pragma Pack (Day_Has_Appointment); 

由于我们已打包数组,编译器将使用尽可能少的存储空间。在大多数情况下,这意味着 8 个布尔值将适合一个字节。

因此,Ada 知道多个元素共享一个地址的数组。那么,如果您需要寻址每个单个元素怎么办?仅仅不使用编译指示 Pack是不够的。如果CPU具有非常快的位访问速度,则编译器可能会在未被告知的情况下打包数组。您需要告诉编译器您需要通过访问来寻址每个元素。

 type Day_Of_Month is range 1 .. 31;
 type Day_Has_Appointment is array (Day_Of_Month) of aliased Boolean;

多维数组

[编辑 | 编辑源代码]

数组可以有多个索引。考虑以下二维数组

  type Character_Display is
     array (Positive range <>, Positive range <>) of Character;

此类型允许声明矩形字符数组。示例

  Magic_Square: constant Character_Display :=
     (('S', 'A', 'T', 'O', 'R'),
      ('A', 'R', 'E', 'P', 'O'),
      ('T', 'E', 'N', 'E', 'T'),
      ('O', 'P', 'E', 'R', 'A'),
      ('R', 'O', 'T', 'A', 'S'));

或者,明确说明一些索引值,

  Magic_Square: constant Character_Display(1 .. 5, 1 .. 5) :=
     (1 => ('S', 'A', 'T', 'O', 'R'),
      2 => ('A', 'R', 'E', 'P', 'O'),
      3 => ('T', 'E', 'N', 'E', 'T'),
      4 => ('O', 'P', 'E', 'R', 'A'),
      5 => ('R', 'O', 'T', 'A', 'S'));

第二维的索引值,即索引每一行中的字符,在这里为 1 .. 5。通过选择不同的第二个范围,我们可以将其更改为 11 .. 15

  Magic_Square: constant Character_Display(1 .. 5, 11 .. 15) :=
     (1 => ('S', 'A', 'T', 'O', 'R'),
       ...

通过向数组类型添加更多维度,我们可以拥有同类数据项的正方形、立方体(或“砖块”)等。

最后,字符数组是一个字符串(参见Ada 编程/字符串)。所以,Magic_Square可以简单地这样声明

  Magic_Square: constant Character_Display :=
     ("SATOR",
      "AREPO",
      "TENET",
      "OPERA",
      "ROTAS");

使用数组

[编辑 | 编辑源代码]

访问元素时,索引在括号中指定。也可以通过这种方式访问切片

Vector_A (1 .. 3) := Vector_B (3 .. 5);

请注意,在此示例中索引范围会滑动:赋值后,Vector_A (1) = Vector_B (3),其他索引也类似。

另请注意,切片赋值是一次完成的,而不是逐个字符循环完成的,因此具有重叠范围的赋值按预期工作

Name: String (1 .. 13) := "Lady Ada     ";

Name (6 .. 13) := Name (1 .. 8);

结果是“Lady Lady Ada”(而不是“Lady Lady Lad”)。

如上所示,在切片赋值中,索引范围会滑动。子类型转换也会使索引范围滑动

subtype Str_1_8 is String (1 .. 8);

Str_1_8(Name (6 .. 13))的结果具有新的边界1和8,内容为“Lady Ada”,并且不是副本。这是更改数组或其部分边界的最佳方法。

运算符“&”可以用于连接数组。

Name := First_Name & ' ' & Last_Name;

在这两种情况下,如果结果数组不适合目标数组,则会引发Constraint_Error。

如果尝试通过索引超出数组边界来访问现有元素,则会引发Constraint_Error(除非抑制检查)。

数组属性

[编辑 | 编辑源代码]

有四个属性对数组很重要:'First、'Last、'Length 和 'Range。让我们用一个例子来看一下它们。假设我们有以下三个字符串

Hello_World  : constant String := "Hello World!";
World        : constant String := Hello_World (7 .. 11);
Empty_String : constant String := "";

那么这四个属性将具有以下值

数组 'First 'Last 'Length 'Range
Hello_World 1 12 12 1 .. 12
World 7 11 5 7 .. 11
Empty_String 1 0 0 1 .. 0

这个例子是为了展示一些常见的初学者错误。

  1. 字符串从索引值1开始的假设是错误的(参见第二行上的World'First = 7)。
  2. X'Length = X'Last 的假设(从第一个假设推导而来)是错误的。
  3. X'Last >= X'First 的假设;对于空字符串,这不是真的。

预定义类型String的索引子类型是Positive,因此通过子类型约束(Positive)将0或-17等排除在可能的索引值集合之外。此外,'A'或2.17e+4也被排除在外,因为它们不是Positive类型。

属性'Range有点特殊,因为它不返回离散值,而是数组的抽象描述。人们可能想知道它有什么用。最常见的用途是在数组上的for循环中。当遍历数组的所有元素时,您无需知道实际的索引范围;通过使用该属性,最常见的错误之一,即访问索引范围之外的元素,将永远不会发生。

 for I in World'Range loop
   ... World (I)...
 end loop;

'Range也可用于声明索引子类型的名称。

subtype Hello_World_Index is Integer range Hello_World'Range;

Range属性在编程索引检查时可能很方便。

if K in World'Range then
   return World(K);
else
   return Substitute;
end if;

空数组或空数组

[编辑 | 编辑源代码]

正如您在上节中看到的,Ada允许使用空数组。任何最后一个索引值低于第一个索引值的数组都是空的。当然,您可以拥有各种各样的空数组,而不仅仅是String。

type Some_Array is array (Positive range <>) of Boolean;

Empty_Some_Array : constant Some_Array (1 .. 0) := (others => False);

Also_Empty: Some_Array (42 .. 10);

注意:如果为空数组提供初始表达式(对于常量来说是必须的),则聚合中的表达式当然不会被评估,因为实际上没有存储任何元素。

另请参阅

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]

Ada 95 参考手册

[编辑 | 编辑源代码]

Ada 2005 参考手册

[编辑 | 编辑源代码]

Ada 质量和风格指南

[编辑 | 编辑源代码]
华夏公益教科书