跳轉到內容

Ada 程式設計/技巧

來自 Wikibooks,開放世界中的開放書籍

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

型別的完整宣告可以延遲到單元體中

[編輯 | 編輯原始碼]

通常,您可能希望更改私有型別的內部結構。這反過來又需要修改作用於它的演算法。如果型別在單元規範中完成,則即使使用IDE,編輯和重新編譯這兩個檔案也很麻煩,但這卻是某些程式設計師學會忍受的事情。

事實證明,您不必這樣做。在ARM中漫不經心地提到,並且通常在教程中跳過,事實上私有型別可以在單元體本身中完成,這使得它們更接近相關的程式碼,並節省了規範的重新編譯,以及每個依賴它的單元。這看起來可能是一件小事,對於小型專案來說確實如此。但是,如果您有一個需要數十次調整的難以合作的型別,或者您的依賴關係圖深度很大,那麼節省的時間和煩惱就會迅速累積。

此外,這種結構在編寫共享庫時非常有用,因為它允許更改型別的實現,同時仍然提供相容的ABI

程式碼示例

package Private_And_Body is

  type Private_Type is limited private;

  -- Operations...

private
  type Body_Type;   -- incomplete type declaration completed in the body
  type Private_Type is access Body_Type;
end Private_And_Body;

公共部分中的型別是隱藏型別的訪問。這有一個缺點,即記憶體管理必須由包實現提供。這就是為什麼Private_Type是一個受限型別,客戶端將不允許複製訪問值,以防止懸空引用。

通常,必須在規範中給出完整的型別定義,因為編譯器必須知道要為物件分配多少空間才能生成使用此型別的程式碼。而敏銳的讀者會注意到,在這種情況下也為 Private_Type 給出了完整的型別定義:它是對某些其他(儘管不完整)型別的訪問,並且訪問值的尺寸是已知的。這就是為什麼 Body_Type 的完整型別定義可以移動到主體中的原因。

圍繞Private_Type構建的結構有時被稱為不透明指標

透過泛型實現 Lambda 演算

[編輯 | 編輯原始碼]

假設您已決定自己編寫集合型別。您可以向其中新增內容,從中刪除內容,並且您希望允許使用者將其中的所有成員應用於某些任意函式。但是作用域規則似乎與您作對,迫使幾乎所有內容都成為全域性的。

心理障礙是,給出的泛型大多數示例都是包,而 Set 包已經是泛型的。在這種情況下,解決方案是使 Apply_To_All 過程也成為泛型的;也就是說,巢狀泛型。包內的泛型過程存在於一個奇怪的作用域模糊狀態中,其中例項化時作用域內的任何內容都可以由例項化使用,並且正式時通常作用域內的任何內容都可以由正式訪問。最終結果是相關的範圍障礙不再適用。它不是完整的 Lambda 演算,只是其中最有用的一部分。

generic
  type Element is private;
package Sets is
  type Set is private;
   [..]
  generic
    with procedure Apply_To_One (The_Element : in out Element);
  procedure Apply_To_All (The_Set : in out Set);
end Sets;

有關 Ada 中函數語言程式設計的檢視,請參閱。[1]

編譯器訊息

[編輯 | 編輯原始碼]

不同的編譯器可以以不同的方式診斷不同的內容,或者使用不同的訊息診斷相同的內容,等等。擁有兩個編譯器在手可能很有用。

選定的元件
當源程式包含諸如 Foo.Bar 之類的構造時,您可能會看到類似於“選定的元件“Bar””或可能類似於“選定的元件“Foo””的訊息。這些短語可能令人困惑,因為一個指的是 Foo,而另一個指的是 Bar。但它們都是正確的。原因是selected_component是 Ada 語法中的一個專案(4.1.3 選定的元件 (帶註釋的))。它表示所有內容:字首、點和選擇器名稱。在 Foo.Bar 示例中,它們分別對應於 Foo、'.' 和 Bar。在編譯器訊息中查詢更多語法單詞,例如“字首”,並將它們與訊息中引用的識別符號關聯起來。
例如,如果您將以下程式碼提交給編譯器,
with Pak;
package Foo is
   type T is new Pak.Bar;  --  Oops, Pak is generic!
end Foo;
編譯器可能會列印有關字首元件的診斷訊息:Foo 的作者認為 Pak 表示一個包,但實際上它是一個泛型包的名稱。(它需要首先例項化;然後例項名稱是一個合適的字首。)

通用整數

[編輯 | 編輯原始碼]

所有整數文字以及一些屬性(如 'Length)都屬於匿名型別universal_integer,它包含無限的數學整數集。命名數字屬於此型別,並精確計算(除了機器儲存限制之外沒有溢位),例如

 Very_Big: constant := 10**1_000_000 - 1;

由於universal_integer沒有運算子,因此在此示例中,其值將轉換為root_integer(另一種匿名型別),執行計算,並將結果再次轉換回universal_integer

通常,當在某些表示式中使用universal_integer的值時,會將其隱式轉換為適當的型別。因此,表示式 not A'Length 很好;A'Length 的值被解釋為模整數,因為not 只能應用於模整數(當然需要上下文來確定是指哪種模整數型別)。此功能可能導致陷阱。考慮

   type Ran_6 is range 1 .. 6;
   type Mod_6 is mod 6;

然後

   --  1
   if A'Length in Ran_6 then  --  OK--  2
   if not A'Length in Ran_6 then  --  not OK--  this is the same as
   if (not A'Length) in Ran_6 then  --  not OK--  3
   if A'Length in 1 .. 6 then  --  OK--  4
   if not A'Length in 1 .. 6 then  --  not OK--  5
   if A'Length in Mod_6 then  --  OK?--  6
   if not A'Length in Mod_6 then  --  OK?
第二個條件無法編譯,因為 in 左側的表示式與右側的型別不相容。請注意,not 的優先順序高於 in。它不會否定整個成員資格測試,而只是 A'Length
第四個條件以各種方式失敗。
第六個條件可能沒問題,因為 notA'Length 轉換為模值,如果該值被模型別 Mod_6 覆蓋,則可以。
GNAT GPL 2009 分別給出了這些診斷
error: incompatible types
error: operand of not must be enclosed in parentheses
warning: not expression should be parenthesized here
避免這些問題的一種方法是為成員資格測試使用 not in(順便說一句,這是語言預期形式),
   if A'Length not in Ran_6 then  --  OK
參見

Text_IO 問題

[編輯 | 編輯原始碼]

從文字檔案讀取一系列行的規範方法是使用標準過程Ada.Text_IOGet_Line。當到達輸入結束時,Get_Line 將失敗,並引發異常 End_Error。一些程式將使用來自Ada.Text_IO 的另一個函式來防止這種情況並測試 End_of_File。但是,這並不總是最佳選擇,例如在 comp.lang.ada 上的 Get_Line 新聞組討論中所解釋的那樣。

一個可行的解決方案是使用異常處理程式

  declare
     The_Line: String(1..100);
     Last: Natural;
  begin
     loop
        Text_IO.Get_Line(The_Line, Last);
        --  do something with The_Line ...
     end loop;
  exception
     when Text_IO.End_Error =>
        null;
  end;

函式 End_of_File RM A.10.1(34) 只要檔案遵循 Ada 假設的文字檔案的規範形式,就可以正常工作,當檔案由 Ada.Text_IO 寫入時,情況總是如此。這種規範形式要求在檔案末尾之前,緊跟著一個 End_of_Line 標記,然後是一個 End_of_Page 標記。

如果檔案是由任何其他方式生成的,它通常不會具有這種規範形式,因此對 End_of_File 的測試將失敗。這就是為什麼在這些情況下必須使用異常 End_Error(它始終有效)的原因。

在 Windows 上使用 GNAT 時,對來自 Ada.Real_Time 的子程式的呼叫可能需要特別注意。(例如,當肯定已經過了一些時間時,Real_Time.Clock 函式似乎返回的值表明兩次呼叫之間沒有經過時間。)其原因據報道是在程式中沒有其他即時特性時,執行時支援的初始化缺失。[2] 作為臨時解決方案,建議插入

delay 0.0;

在任何使用 Real_Time 服務之前。

棧大小

[編輯 | 編輯原始碼]

對於某些實現,特別是 GNAT,瞭解棧大小操作將對您有利。使用 GNAT 工具和標準設定生成的的可執行檔案可能會達到棧大小限制。如果是這樣,作業系統可能會允許設定更高的限制。使用 GNU/Linux 和 Bash 命令列 shell,嘗試

$ ulimit -s [some number]

當僅將 -s 傳遞給 ulimit 時,將列印當前值。

參考文獻

[編輯 | 編輯原始碼]
  1. Ada 中的功能程式設計?,作者:Chris Okasaki
  2. Vincent Celier (2010-03-08)。“計時程式碼塊”。comp.lang.ada(網路連結)。於 2010 年 3 月 11 日檢索。“現在已經瞭解並修正了 GNAT 開發版本中的問題。” Usenet 文章轉發了來自 AdaCore 的此資訊。

另請參閱

[編輯 | 編輯原始碼]

華夏公益教科書

[編輯 | 編輯原始碼]

Ada 參考手冊

[編輯 | 編輯原始碼]
華夏公益教科書