Ada 程式設計/子程式
在 Ada 中,子程式分為兩類:過程 和 函式。過程呼叫是一個語句,不返回值,而函式返回值,因此必須是表示式的一部分。
子程式引數可以有三種模式。
in- 實際引數的值進入呼叫,並且不會在其中更改;形式引數是一個常量,只允許讀取 - 有一個警告,請參閱 Ada 程式設計/常量。如果沒有給出模式,這是預設值。實際引數可以是表示式。
inout- 實際引數進入呼叫,可以重新定義。形式引數是一個變數,可以讀取和寫入。
out- 呼叫前的實際引數值無關緊要,它將在呼叫中獲得值。形式引數可以讀取和寫入。(在 Ada 83 中
out引數是隻寫。)
任何模式的引數也可以顯式地aliased.
access- 形式引數是對某個變數的訪問(指標)。(從參考手冊的角度來看,這不是引數模式。)
請注意,引數模式不指定引數傳遞方法。它們的目的是記錄資料流。
引數傳遞方法取決於引數的型別。經驗法則是,適合放入暫存器的引數按值傳遞,其他引數按引用傳遞。對於某些型別,有特殊的規則,對於其他型別,引數傳遞模式留給編譯器(你可以假設它會做最合理的做法)。帶標籤的型別總是按引用傳遞。
顯式地aliased 引數和access 引數指定按引用傳遞。
與 C 類程式語言不同,Ada 子程式呼叫不能在沒有引數時具有空引數括號 ( )。
過程
[edit | edit source]Ada 中的過程呼叫本身構成一個語句。
例如
procedureA_Test (A, B:inInteger; C:outInteger)isbeginC := A + B;endA_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 語句退出過程並將控制權返回給呼叫者。
例如,要解形如 的方程
withAda.Numerics.Elementary_Functions;useAda.Numerics.Elementary_Functions;procedureQuadratic_Equation (A, B, C : Float; -- By default it is "in". R1, R2 :outFloat; Valid :outBoolean)isZ : Float;beginZ := B**2 - 4.0 * A * C;ifZ < 0.0orA = 0.0thenValid := False; -- Being out parameter, it should be modified at least once. R1 := 0.0; R2 := 0.0;elseValid := True; R1 := (-B + Sqrt (Z)) / (2.0 * A); R2 := (-B - Sqrt (Z)) / (2.0 * A);endif;endQuadratic_Equation;
函式 SQRT 計算非負值的平方根。如果根是實數,則它們在 R1 和 R2 中返回,但如果它們是複數或方程退化(A = 0),則過程的執行將在將 Valid 變數的值賦值為 False 後完成,以便在呼叫過程後對其進行控制。請注意,out 引數應該至少修改一次,並且如果未指定模式,則隱式地in.
函式
[edit | edit source]函式是一個可以作為表示式的一部分呼叫的子程式。在 Ada 2005 之前,函式只能接受in(預設)或access 引數;後者可用作函式不能具有out 引數的限制的變通方法。Ada 2012 已刪除此限制。
這是一個函式體的示例
functionMinimum (A, B: Integer)returnIntegerisbeginifA <= BthenreturnA;elsereturnB;endif;endMinimum;
(順便說一下,還有屬性 Integer'Min,請參閱 RM 3.5。)或在 Ada2012 中
functionMinimum (A, B: Integer)returnIntegerisbeginreturn(ifA <= BthenAelseB);endMinimum;
或者更短的,作為 *表示式函式*
functionMinimum (A, B: Integer)returnIntegeris(ifA <= BthenAelseB);
模式為in 的形式引數表現為本地常量,其值由相應的實際引數提供。語句return 用於指示函式呼叫返回的值,並將控制權返回給呼叫函式的表示式。該return 語句的表示式可以具有任意複雜性,並且必須與規範中宣告的型別相同。如果使用不相容的型別,編譯器會給出錯誤。如果子型別的限制沒有滿足,例如範圍,則會引發 Constraint_Error 異常。
函式體可以包含多個return 語句,其中任何一個的執行都將結束函式,並將控制權返回給呼叫者。如果函式內的控制流以多種方式分支,則必須確保每種方式都以 a 結束return 語句。如果在執行時到達函式的末尾而沒有遇到return 語句,則會引發 Program_Error 異常。因此,函式體必須至少包含一個這樣的return 語句。
對函式的每次呼叫都會在函式內部生成任何物件的全新副本。當函式完成時,其物件會消失。因此,可以遞迴地呼叫函式。例如,考慮階乘函式的這種實現
functionFactorial (N : Positive)returnPositiveisbeginifN = 1thenreturn1;elsereturn(N * Factorial (N - 1));endif;endFactorial;
在評估表示式 Factorial (4); 時,函式將使用引數 4 被呼叫,並且在函式內部,它將嘗試評估表示式 Factorial (3),將其自身作為函式呼叫,但在此情況下,引數 N 將為 3(每次呼叫都會複製引數)等等,直到 N = 1 被評估,這將結束遞迴,然後表示式將開始以相反的順序完成。
函式的形式引數可以是任何型別,包括向量或記錄。但是,它不能是匿名型別,即它的型別必須提前宣告,例如
typeFloat_Vectorisarray(Positiverange<>)ofFloat;functionAdd_Components (V: Float_Vector)returnFloatisResult : Float := 0.0;beginforIinV'RangeloopResult := Result + V(I);endloop;returnResult;endAdd_Components;
在此示例中,該函式可以在任意維度的向量上使用。因此,傳遞給函式的引數中沒有靜態邊界。例如,可以按以下方式使用它
V4 : Float_Vector (1 .. 4) := (1.2, 3.4, 5.6, 7.8); Sum : Float; Sum := Add_Components (V4);
同樣,函式還可以返回一個型別,其邊界在先驗情況下未知。例如
functionInvert_Components (V : Float_Vector)returnFloat_VectorisResult : Float_Vector(V'Range); -- Fix the bounds of the vector to be returned.beginforIinV'RangeloopResult(I) := V (V'First + V'Last - I);endloop;returnResult;endInvert_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
另一方面,形式引數可以有預設值。因此,它們可以在子程式呼叫中省略。例如
procedureBy_Default_Example (A, B:inInteger := 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 可能具有不同的值;在其他情況下,它不會被評估。
子程式可以重新命名。重新命名宣告的引數和結果配置檔案必須是模式一致的。
procedureSolve (A, B, C:inFloat; R1, R2 :outFloat; Valid :outBoolean)renamesQuadratic_Equation;
這對帶標籤型別可能特別方便。
packageSome_PackageistypeMessage_Typeistaggednullrecord;procedurePrint (Message:inMessage_Type);endSome_Package;
withSome_Package;procedureMainisMessage: Some_Package.Message_Type;procedurePrintrenamesMessage.Print; -- this has convention intrinsic, see RM 6.3.1(10.1/2)Method_Ref:-- thus taking 'Access should be illegal; GNAT GPL 2012 allows thisaccessprocedure:= Print'Access;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 suchMethod_Ref.-- 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)all;endMain;
但請注意,Message.Print' 是非法的,您必須使用上面的重新命名宣告。Access;
由於只需要模式一致性(而不是規範和主體之間的完全一致性),因此引數名稱和預設值可以在重新命名中更改
procedureP (X:inInteger := 0);procedureR (A:inInteger := -1)renamesP;
