跳转到内容

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 质量和风格指南

[编辑 | 编辑源代码]
华夏公益教科书