Ada 编程/类型/数组
一个数组是元素的集合,可以通过一个或多个索引值访问。在 Ada 中,任何确定类型都允许作为元素,并且任何离散类型,即范围、模数或枚举,都可以用作索引。
Ada 的数组功能非常强大,因此有相当多的语法变体,如下所示。
Ada 数组的基本形式为
array
(Index_Range)of
Element_Type
其中 Index_Range 是离散索引类型内的值范围,Element_Type 是确定子类型。数组包含给定范围中每个可能值的“Element_Type”的一个元素。例如,如果您想计算特定字母在文本中出现的次数,您可以使用
type
Character_Counteris
array
(Character)of
Natural;
很多情况下,索引本身没有语义内容,它只被用作识别元素的一种手段,例如在列表中。因此,作为一个通用的建议,在这些情况下不要使用负索引。当使用数字索引时,从 1 开始而不是从 0 开始定义它们也是一种好的风格,因为它对人类来说更直观,并且避免了越界错误。
但是,在某些情况下,负索引是有意义的。因此,请使用适合手头问题的索引。假设您是一名化学家,正在进行一些取决于温度的实验
type
Temperatureis
range
-10 .. +40; -- Celsiustype
Experimentis
array
(Temperature )of
Something;
通常您不需要索引类型的所有可能值的数组。在这种情况下,您可以将您的索引类型子类型
化为实际需要的范围。
subtype
Index_Sub_Typeis
Index_Typerange
First .. Lastarray
(Index_Sub_Type)of
Element_Type
由于这可能涉及大量输入,并且您也可能用完用于新子类型的有用名称,因此数组声明允许使用快捷方式
array
(Index_Typerange
First .. Last)of
Element_Type
自从第一和最后是Index_Type的表达式,上述内容的更简单形式为
array
(First .. Last)of
Element_Type
请注意,如果第一和最后是数字文本,这意味着索引类型整数.
如果在上面的示例中字符计数器应该只计算大写字符并丢弃所有其他字符,则可以使用以下数组类型
type
Character_Counteris
array
(Characterrange
'A' .. 'Z')of
Natural;
有时实际需要的范围在运行时才知道,或者您需要不同长度的对象。在某些语言中,您将求助于指向元素类型的指针。Ada 不是这样。这里我们有框“<>” ,它允许我们声明不确定数组
array
(Index_Typerange
<>)of
Element_Type;
当您声明此类类型的对象时,当然必须给出边界,并且该对象受其约束。
预定义类型字符串就是这样的类型。它被定义为
type
Stringis
array
(Positiverange
<>)of
Character;
您可以通过多种方式定义此类无约束类型的对象(对除 String 之外的其他数组的推断应该很明显)
Text : String (10 .. 20); Input: String := Read_from_some_file;
(这些声明还定义了 String 的匿名子类型。)在第一个示例中,显式给出了索引范围。在第二个示例中,从初始表达式隐式定义范围,此处可能是通过从某个文件读取数据的函数。这两个对象都受其范围约束,即它们不能增长或缩小。
如果您来自C/C++,您可能习惯于数组的每个元素都有一个地址。实际C/C++标准要求这样做。
在 Ada 中,情况并非如此。考虑以下数组
type
Day_Of_Monthis
range
1 .. 31;type
Day_Has_Appointmentis
array
(Day_Of_Month)of
Boolean;pragma
Pack (Day_Has_Appointment);
由于我们已打包数组,编译器将使用尽可能少的存储空间。在大多数情况下,这意味着 8 个布尔值将适合一个字节。
因此,Ada 知道多个元素共享一个地址的数组。那么,如果您需要寻址每个单个元素怎么办?仅仅不使用编译指示 Pack是不够的。如果CPU具有非常快的位访问速度,则编译器可能会在未被告知的情况下打包数组。您需要告诉编译器您需要通过访问来寻址每个元素。
type
Day_Of_Monthis
range
1 .. 31;type
Day_Has_Appointmentis
array
(Day_Of_Month)of
aliased
Boolean;
数组可以有多个索引。考虑以下二维数组
type
Character_Displayis
array
(Positiverange
<>, Positiverange
<>)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_8is
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开始的假设是错误的(参见第二行上的World'First = 7)。
- X'Length = X'Last 的假设(从第一个假设推导而来)是错误的。
- X'Last >= X'First 的假设;对于空字符串,这不是真的。
预定义类型String的索引子类型是Positive,因此通过子类型约束(Positive)将0或-17等排除在可能的索引值集合之外。此外,'A'或2.17e+4也被排除在外,因为它们不是Positive类型。
属性'Range有点特殊,因为它不返回离散值,而是数组的抽象描述。人们可能想知道它有什么用。最常见的用途是在数组上的for循环中。当遍历数组的所有元素时,您无需知道实际的索引范围;通过使用该属性,最常见的错误之一,即访问索引范围之外的元素,将永远不会发生。
for
Iin
World'Rangeloop
... World (I)...end
loop
;
'Range也可用于声明索引子类型的名称。
subtype
Hello_World_Indexis
Integerrange
Hello_World'Range;
Range属性在编程索引检查时可能很方便。
if
Kin
World'Rangethen
return
World(K);else
return
Substitute;end
if
;
正如您在上节中看到的,Ada允许使用空数组。任何最后一个索引值低于第一个索引值的数组都是空的。当然,您可以拥有各种各样的空数组,而不仅仅是String。
type
Some_Arrayis
array
(Positive range <>)of
Boolean; Empty_Some_Array :constant
Some_Array (1 .. 0) := (others
=> False); Also_Empty: Some_Array (42 .. 10);
注意:如果为空数组提供初始表达式(对于常量来说是必须的),则聚合中的表达式当然不会被评估,因为实际上没有存储任何元素。