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.0or
A = 0.0then
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
Integeris
begin
if
A <= Bthen
return
A;else
return
B;end
if
;end
Minimum;
(顺便说一下,还有属性 Integer'Min
,请参阅 RM 3.5。)或在 Ada2012 中
function
Minimum (A, B: Integer)return
Integeris
begin
return
(if
A <= Bthen
Aelse
B);end
Minimum;
或者更短的,作为 *表达式函数*
function
Minimum (A, B: Integer)return
Integeris
(if
A <= Bthen
Aelse
B);
模式为in
的形式参数表现为本地常量,其值由相应的实际参数提供。语句return
用于指示函数调用返回的值,并将控制权返回给调用函数的表达式。该return
语句的表达式可以具有任意复杂性,并且必须与规范中声明的类型相同。如果使用不兼容的类型,编译器会给出错误。如果子类型的限制没有满足,例如范围,则会引发 Constraint_Error 异常。
函数体可以包含多个return
语句,其中任何一个的执行都将结束函数,并将控制权返回给调用者。如果函数内的控制流以多种方式分支,则必须确保每种方式都以 a 结束return
语句。如果在运行时到达函数的末尾而没有遇到return
语句,则会引发 Program_Error 异常。因此,函数体必须至少包含一个这样的return
语句。
对函数的每次调用都会在函数内部生成任何对象的全新副本。当函数完成时,其对象会消失。因此,可以递归地调用函数。例如,考虑阶乘函数的这种实现
function
Factorial (N : Positive)return
Positiveis
begin
if
N = 1then
return
1;else
return
(N * Factorial (N - 1));end
if
;end
Factorial;
在评估表达式 Factorial (4);
时,函数将使用参数 4 被调用,并且在函数内部,它将尝试评估表达式 Factorial (3)
,将其自身作为函数调用,但在此情况下,参数 N 将为 3(每次调用都会复制参数)等等,直到 N = 1 被评估,这将结束递归,然后表达式将开始以相反的顺序完成。
函数的形式参数可以是任何类型,包括向量或记录。但是,它不能是匿名类型,即它的类型必须提前声明,例如
type
Float_Vectoris
array
(Positiverange
<>)of
Float;function
Add_Components (V: Float_Vector)return
Floatis
Result : Float := 0.0;begin
for
Iin
V'Rangeloop
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_Vectoris
Result : Float_Vector(V'Range); -- Fix the bounds of the vector to be returned.begin
for
Iin
V'Rangeloop
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_Packageis
type
Message_Typeis
tagged
null
record
;procedure
Print (Message:in
Message_Type);end
Some_Package;
with
Some_Package;procedure
Mainis
Message: Some_Package.Message_Type;procedure
Printrenames
Message.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 thisaccess
procedure
:= 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
;end
Main;
但请注意,Message.Print'
是非法的,您必须使用上面的重命名声明。Access
;
由于只需要模式一致性(而不是规范和主体之间的完全一致性),因此参数名称和默认值可以在重命名中更改
procedure
P (X:in
Integer := 0);procedure
R (A:in
Integer := -1)renames
P;