Ada 程式設計/型別/陣列
一個陣列是元素的集合,可以透過一個或多個索引值訪問。在 Ada 中,任何確定型別都允許作為元素,並且任何離散型別,即範圍、模數或列舉,都可以用作索引。
Ada 的陣列功能非常強大,因此有相當多的語法變體,如下所示。
Ada 陣列的基本形式為
array(Index_Range)ofElement_Type
其中 Index_Range 是離散索引型別內的值範圍,Element_Type 是確定子型別。陣列包含給定範圍中每個可能值的“Element_Type”的一個元素。例如,如果您想計算特定字母在文字中出現的次數,您可以使用
typeCharacter_Counterisarray(Character)ofNatural;
很多情況下,索引本身沒有語義內容,它只被用作識別元素的一種手段,例如在列表中。因此,作為一個通用的建議,在這些情況下不要使用負索引。當使用數字索引時,從 1 開始而不是從 0 開始定義它們也是一種好的風格,因為它對人類來說更直觀,並且避免了越界錯誤。
但是,在某些情況下,負索引是有意義的。因此,請使用適合手頭問題的索引。假設您是一名化學家,正在進行一些取決於溫度的實驗
typeTemperatureisrange-10 .. +40; -- CelsiustypeExperimentisarray(Temperature )ofSomething;
通常您不需要索引型別的所有可能值的陣列。在這種情況下,您可以將您的索引型別子型別化為實際需要的範圍。
subtypeIndex_Sub_TypeisIndex_TyperangeFirst .. Lastarray(Index_Sub_Type)ofElement_Type
由於這可能涉及大量輸入,並且您也可能用完用於新子型別的有用名稱,因此陣列宣告允許使用快捷方式
array(Index_TyperangeFirst .. Last)ofElement_Type
自從第一和最後是Index_Type的表示式,上述內容的更簡單形式為
array(First .. Last)ofElement_Type
請注意,如果第一和最後是數字文字,這意味著索引型別整數.
如果在上面的示例中字元計數器應該只計算大寫字元並丟棄所有其他字元,則可以使用以下陣列型別
typeCharacter_Counterisarray(Characterrange'A' .. 'Z')ofNatural;
有時實際需要的範圍在執行時才知道,或者您需要不同長度的物件。在某些語言中,您將求助於指向元素型別的指標。Ada 不是這樣。這裡我們有框“<>” ,它允許我們宣告不確定陣列
array(Index_Typerange<>)ofElement_Type;
當您宣告此類型別的物件時,當然必須給出邊界,並且該物件受其約束。
預定義型別字串就是這樣的型別。它被定義為
typeStringisarray(Positiverange<>)ofCharacter;
您可以透過多種方式定義此類無約束型別的物件(對除 String 之外的其他陣列的推斷應該很明顯)
Text : String (10 .. 20); Input: String := Read_from_some_file;
(這些宣告還定義了 String 的匿名子型別。)在第一個示例中,顯式給出了索引範圍。在第二個示例中,從初始表示式隱式定義範圍,此處可能是透過從某個檔案讀取資料的函式。這兩個物件都受其範圍約束,即它們不能增長或縮小。
如果您來自C/C++,您可能習慣於陣列的每個元素都有一個地址。實際C/C++標準要求這樣做。
在 Ada 中,情況並非如此。考慮以下陣列
typeDay_Of_Monthisrange1 .. 31;typeDay_Has_Appointmentisarray(Day_Of_Month)ofBoolean;pragmaPack (Day_Has_Appointment);
由於我們已打包陣列,編譯器將使用盡可能少的儲存空間。在大多數情況下,這意味著 8 個布林值將適合一個位元組。
因此,Ada 知道多個元素共享一個地址的陣列。那麼,如果您需要定址每個單個元素怎麼辦?僅僅不使用編譯指示 Pack是不夠的。如果CPU具有非常快的位訪問速度,則編譯器可能會在未被告知的情況下打包陣列。您需要告訴編譯器您需要透過訪問來定址每個元素。
typeDay_Of_Monthisrange1 .. 31;typeDay_Has_Appointmentisarray(Day_Of_Month)ofaliasedBoolean;
陣列可以有多個索引。考慮以下二維陣列
typeCharacter_Displayisarray(Positiverange<>, Positiverange<>)ofCharacter;
此型別允許宣告矩形字元陣列。示例
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”)。
如上所示,在切片賦值中,索引範圍會滑動。子型別轉換也會使索引範圍滑動
subtypeStr_1_8isString (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 :constantString := "Hello World!"; World :constantString := Hello_World (7 .. 11); Empty_String :constantString := "";
那麼這四個屬性將具有以下值
| 陣列 | '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迴圈中。當遍歷陣列的所有元素時,您無需知道實際的索引範圍;透過使用該屬性,最常見的錯誤之一,即訪問索引範圍之外的元素,將永遠不會發生。
forIinWorld'Rangeloop... World (I)...endloop;
'Range也可用於宣告索引子型別的名稱。
subtypeHello_World_IndexisIntegerrangeHello_World'Range;
Range屬性在程式設計索引檢查時可能很方便。
ifKinWorld'RangethenreturnWorld(K);elsereturnSubstitute;endif;
正如您在上節中看到的,Ada允許使用空陣列。任何最後一個索引值低於第一個索引值的陣列都是空的。當然,您可以擁有各種各樣的空陣列,而不僅僅是String。
typeSome_Arrayisarray(Positive range <>)ofBoolean; Empty_Some_Array :constantSome_Array (1 .. 0) := (others=> False); Also_Empty: Some_Array (42 .. 10);
注意:如果為空陣列提供初始表示式(對於常量來說是必須的),則聚合中的表示式當然不會被評估,因為實際上沒有儲存任何元素。
