跳轉到內容

Ada 程式設計/子程式

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

Ada. Time-tested, safe and secure.
Ada. 經久考驗,安全可靠。

在 Ada 中,子程式分為兩類:過程函式。過程呼叫是一個語句,不返回值,而函式返回值,因此必須是表示式的一部分。

子程式引數可以有三種模式。

in
實際引數的值進入呼叫,並且不會在其中更改;形式引數是一個常量,只允許讀取 - 有一個警告,請參閱 Ada 程式設計/常量。如果沒有給出模式,這是預設值。實際引數可以是表示式。
in out
實際引數進入呼叫,可以重新定義。形式引數是一個變數,可以讀取和寫入。
out
呼叫前的實際引數值無關緊要,它將在呼叫中獲得值。形式引數可以讀取和寫入。(在 Ada 83 中out 引數是隻寫。)

任何模式的引數也可以顯式地aliased.

access
形式引數是對某個變數的訪問(指標)。(從參考手冊的角度來看,這不是引數模式。)

請注意,引數模式不指定引數傳遞方法。它們的目的是記錄資料流。

引數傳遞方法取決於引數的型別。經驗法則是,適合放入暫存器的引數按值傳遞,其他引數按引用傳遞。對於某些型別,有特殊的規則,對於其他型別,引數傳遞模式留給編譯器(你可以假設它會做最合理的做法)。帶標籤的型別總是按引用傳遞。

顯式地aliased 引數和access 引數指定按引用傳遞。

與 C 類程式語言不同,Ada 子程式呼叫不能在沒有引數時具有空引數括號 ( )

過程

[edit | edit source]

Ada 中的過程呼叫本身構成一個語句。

例如

procedure A_Test (A, B: in Integer; C: out Integer) is
begin
   C := A + B;
end A_Test;

當用語句呼叫過程時

A_Test (5 + P, 48, Q);

表示式 5 + P 和 48 被計算(表示式只允許用於 in 引數),然後分配給形式引數 A 和 B,它們的行為類似於常量。然後,值 A + B 被分配給形式變數 C,其值將在過程完成後分配給實際引數 Q。

C 作為一個out 引數,是在第一次賦值之前未初始化的變數。(因此在 Ada 83 中,存在這樣一個限制,即out 引數是隻寫的。如果你想讀取寫入的值,你必須宣告一個區域性變數,用它進行所有計算,最後在返回之前將其賦值給 C。這很笨拙並且容易出錯,因此該限制在 Ada 95 中被移除。)

在過程內部,可以使用不帶引數的 return 語句退出過程並將控制權返回給呼叫者。

例如,要解形如 的方程

with Ada.Numerics.Elementary_Functions;
use  Ada.Numerics.Elementary_Functions;

procedure Quadratic_Equation
   (A, B, C :     Float;   -- By default it is "in".
    R1, R2  : out Float;
    Valid   : out Boolean)
is
   Z : Float;
begin
   Z := B**2 - 4.0 * A * C;
   if Z < 0.0 or A = 0.0 then
      Valid := False;  -- Being out parameter, it should be modified at least once.
      R1    := 0.0;
      R2    := 0.0;
   else
      Valid := True;
      R1    := (-B + Sqrt (Z)) / (2.0 * A);
      R2    := (-B - Sqrt (Z)) / (2.0 * A);
   end if;
end Quadratic_Equation;

函式 SQRT 計算非負值的平方根。如果根是實數,則它們在 R1 和 R2 中返回,但如果它們是複數或方程退化(A = 0),則過程的執行將在將 Valid 變數的值賦值為 False 後完成,以便在呼叫過程後對其進行控制。請注意,out 引數應該至少修改一次,並且如果未指定模式,則隱式地in.

函式

[edit | edit source]

函式是一個可以作為表示式的一部分呼叫的子程式。在 Ada 2005 之前,函式只能接受in(預設)或access 引數;後者可用作函式不能具有out 引數的限制的變通方法。Ada 2012 已刪除此限制。

這是一個函式體的示例

function Minimum (A, B: Integer) return Integer is
begin
   if A <= B then
      return A;
   else
      return B;
   end if;
end Minimum;

(順便說一下,還有屬性 Integer'Min,請參閱 RM 3.5。)或在 Ada2012 中

function Minimum (A, B: Integer) return Integer is
begin
   return (if A <= B then A else B);
end Minimum;

或者更短的,作為 *表示式函式*

function Minimum (A, B: Integer) return Integer is (if A <= B then A else B);

模式為in 的形式引數表現為本地常量,其值由相應的實際引數提供。語句return 用於指示函式呼叫返回的值,並將控制權返回給呼叫函式的表示式。該return 語句的表示式可以具有任意複雜性,並且必須與規範中宣告的型別相同。如果使用不相容的型別,編譯器會給出錯誤。如果子型別的限制沒有滿足,例如範圍,則會引發 Constraint_Error 異常。

函式體可以包含多個return 語句,其中任何一個的執行都將結束函式,並將控制權返回給呼叫者。如果函式內的控制流以多種方式分支,則必須確保每種方式都以 a 結束return 語句。如果在執行時到達函式的末尾而沒有遇到return 語句,則會引發 Program_Error 異常。因此,函式體必須至少包含一個這樣的return 語句。

對函式的每次呼叫都會在函式內部生成任何物件的全新副本。當函式完成時,其物件會消失。因此,可以遞迴地呼叫函式。例如,考慮階乘函式的這種實現

function Factorial (N : Positive) return Positive is
begin
   if N = 1 then
      return 1;
   else
      return (N * Factorial (N - 1));
   end if;
end Factorial;

在評估表示式 Factorial (4); 時,函式將使用引數 4 被呼叫,並且在函式內部,它將嘗試評估表示式 Factorial (3),將其自身作為函式呼叫,但在此情況下,引數 N 將為 3(每次呼叫都會複製引數)等等,直到 N = 1 被評估,這將結束遞迴,然後表示式將開始以相反的順序完成。

函式的形式引數可以是任何型別,包括向量或記錄。但是,它不能是匿名型別,即它的型別必須提前宣告,例如

type Float_Vector is array (Positive range <>) of Float;

function Add_Components (V: Float_Vector) return Float is
   Result : Float := 0.0;
begin
   for I in V'Range loop
      Result := Result + V(I);
   end loop;
   return Result;
end Add_Components;

在此示例中,該函式可以在任意維度的向量上使用。因此,傳遞給函式的引數中沒有靜態邊界。例如,可以按以下方式使用它

V4  : Float_Vector (1 .. 4) := (1.2, 3.4, 5.6, 7.8);
Sum : Float;

Sum := Add_Components (V4);

同樣,函式還可以返回一個型別,其邊界在先驗情況下未知。例如

function Invert_Components (V : Float_Vector) return Float_Vector is
   Result : Float_Vector(V'Range);   -- Fix the bounds of the vector to be returned.
begin
   for I in V'Range loop
      Result(I) := V (V'First + V'Last - I);
   end loop;
   return Result;
end Invert_Components; 

變數 Result 與 V 具有相同的邊界,因此返回的向量始終與作為引數傳遞的向量具有相同的維數。

函式返回的值無需分配給變數即可使用,它可以作為表示式引用。例如,Invert_Components (V4) (1),其中將獲得函式返回的向量的第一個元素(在本例中,是 V4 的最後一個元素,即 7.8)。

命名引數

[編輯 | 編輯原始碼]

在子程式呼叫中,命名引數記法(即形式引數名稱後跟符號 =>,然後是實際引數)允許在呼叫中重新排列引數。例如

Quadratic_Equation (Valid => OK, A => 1.0, B => 2.0, C => 3.0, R1 => P, R2 => Q);
F := Factorial (N => (3 + I));

這在明確引數含義方面特別有用。

Phi := Arctan (A, B);
Phi := Arctan (Y => A, X => B);

第一個呼叫(來自 Ada.Numerics.Elementary_Functions)不是很清楚。人們可能會很容易混淆引數。第二個呼叫使含義清晰,沒有任何歧義。

另一個用途是用於帶有數字字面量的呼叫

Ada.Float_Text_IO.Put_Line (X, 3, 2, 0);  -- ?
Ada.Float_Text_IO.Put_Line (X, Fore => 3, Aft => 2, Exp => 0);  -- OK

預設引數

[編輯 | 編輯原始碼]

另一方面,形式引數可以有預設值。因此,它們可以在子程式呼叫中省略。例如

procedure By_Default_Example (A, B: in Integer := 0);

可以按以下方式呼叫

By_Default_Example (5, 7);      -- A = 5, B = 7
By_Default_Example (5);         -- A = 5, B = 0
By_Default_Example;             -- A = 0, B = 0
By_Default_Example (B => 3);    -- A = 0, B = 3
By_Default_Example (1, B => 2); -- A = 1, B = 2

在第一個語句中,使用“常規呼叫”(帶有位置關聯);第二個也使用位置關聯,但省略了第二個引數以使用預設值;在第三個語句中,所有引數都是預設的;第四個語句使用命名關聯省略第一個引數;最後,第五個語句使用混合關聯,此處位置引數必須位於命名引數之前。

請注意,對於每個沒有實際引數的形式引數,預設表示式只評估一次。因此,如果在上面的例子中,函式被用作 A 和 B 的預設值,則函式會在情況 2 和 4 中評估一次;在情況 3 中評估兩次,因此 A 和 B 可能具有不同的值;在其他情況下,它不會被評估。

重新命名

[編輯 | 編輯原始碼]

子程式可以重新命名。重新命名宣告的引數和結果配置檔案必須是模式一致的。

procedure Solve
  (A, B, C: in  Float;
   R1, R2 : out Float;
   Valid  : out Boolean) renames Quadratic_Equation;

這對帶標籤型別可能特別方便。

package Some_Package is
  type Message_Type is tagged null record;
  procedure Print (Message: in Message_Type);
end Some_Package; 
with Some_Package;
procedure Main is
  Message: Some_Package.Message_Type;
  procedure Print renames Message.Print;  -- this has convention intrinsic, see RM 6.3.1(10.1/2)
  Method_Ref: access procedure := Print'Access;  -- thus taking 'Access should be illegal; GNAT GPL 2012 allows this
begin  -- All these calls are equivalent:
  Some_Package.Print (Message);  -- traditional call without use clause
  Message.Print;                 -- Ada 2005 method.object call - note: no use clause necessary
  Print;                         -- Message.Print is a parameterless procedure and can be renamed as such
  Method_Ref.all;                -- GNAT GPL 2012 allows illegal call via an access to the renamed procedure Print
                                 -- This has been corrected in the current version (as of Nov 22, 2012)
end Main;

但請注意,Message.Print'Access; 是非法的,您必須使用上面的重新命名宣告。

由於只需要模式一致性(而不是規範和主體之間的完全一致性),因此引數名稱和預設值可以在重新命名中更改

procedure P (X: in Integer :=  0);
procedure R (A: in Integer := -1) renames P;

另請參閱

[編輯 | 編輯原始碼]

華夏公益教科書

[編輯 | 編輯原始碼]

Ada 95 參考手冊

[編輯 | 編輯原始碼]

Ada 2005 參考手冊

[編輯 | 編輯原始碼]

Ada 質量和風格指南

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