跳转到内容

Ada 编程/泛型

来自维基教科书,开放的书籍,开放的世界

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

参数多态(泛型单元)

[编辑 | 编辑源代码]

代码重用理念源于构建大型软件系统的必要性,这些系统将成熟的构建块组合在一起。代码的可重用性提高了软件的生产力和质量。泛型单元是 Ada 语言支持这种特性的方式之一。泛型单元是子程序或包,它们根据在用户实例化它们之前未定义的类型和操作来定义算法。

注意 C++ 程序员:泛型单元类似于 C++ 模板。

例如,要定义一个用于交换任何(非受限)类型的变量的过程

generic
  type Element_T is private;  -- Generic formal type parameter
procedure Swap (X, Y : in out Element_T);
procedure Swap (X, Y : in out Element_T) is
  Temporary : constant Element_T := X;
begin
  X := Y;
  Y := Temporary;
end Swap;

Swap 子程序被称为泛型。子程序规范之前是泛型形式部分,由保留字generic 后面跟着可能为空的泛型形式参数列表。声明为泛型的实体不能直接使用,必须先实例化它们。

为了能够使用 Swap,有必要为想要的类型创建一个实例。例如

procedure Swap_Integers is new Swap (Integer);

现在,Swap_Integers 过程可以用于 Integer 类型的变量。

泛型过程可以为所有需要的类型实例化。它可以用不同的名称实例化,或者,如果在实例化中使用相同的标识符,则每个声明都将重载该过程

procedure Instance_Swap is new Swap (Float);
procedure Instance_Swap is new Swap (Day_T);
procedure Instance_Swap is new Swap (Element_T => Stack_T);

类似地,泛型包可用于实现任何类型的堆栈

generic
  Max: Positive; 
  type Element_T is private;
package Generic_Stack is
  procedure Push (E: Element_T);
  function Pop return Element_T;
end Generic_Stack;
package body Generic_Stack is
  Stack: array (1 .. Max) of Element_T;
  Top  : Integer range 0 .. Max := 0;  -- initialise to empty
  -- ...
end Generic_Stack;

可以通过这种方式定义给定大小和类型的堆栈

declare
  package Float_100_Stack is new Generic_Stack (100, Float);
  use Float_100_Stack;
begin
  Push (45.8);
  -- ...
end;

泛型参数

[编辑 | 编辑源代码]

泛型单元声明了 *泛型形式参数*,它们可以是

  • 对象(模式为 *in* 或 *in out*,但绝不是 *out*)
  • 类型
  • 子程序
  • 另一个指定泛型单元的实例。

在实例化泛型时,程序员为每个形式参数传递一个 *实际参数*。形式值和子程序可以有默认值,因此传递实际值是可选的。

泛型形式对象

[编辑 | 编辑源代码]

模式为 *in* 的形式参数接受指定类型的所有值、常量或变量。实际参数被复制到泛型实例中,并在泛型内部充当常量;这意味着指定类型不能受限。可以指定一个默认值,例如

generic
   Object : in Natural := 0;

对于模式为 *in out*,实际参数必须是变量。

泛型形式对象的一个限制是它们从不被视为静态,即使实际参数恰好是静态的。如果该对象是一个数字,则不能使用它来创建新的类型。但是,它可以用于创建新的派生类型或子类型

generic
   Size : in Natural := 0;
package P is
   type T1 is mod Size; -- illegal!
   type T2 is range 1 .. Size; -- illegal!
   type T3 is new Integer range 1 .. Size; -- OK
   subtype T4 is Integer range 1 .. Size; -- OK
end P;

形式对象是非静态的原因是允许编译器只为泛型生成一次目标代码,并让所有实例共享它,并将其实际对象的地址作为参数传递给它。这种编译器技术被称为 *共享泛型*。如果形式对象是静态的,编译器将不得不为每个实例生成目标代码的一个副本,其中包含该对象,这可能会导致目标代码大小急剧膨胀(*代码膨胀*)。

(注意 C++ 程序员:在 C++ 中,由于形式对象可以是静态的,因此编译器在一般情况下不能实现共享泛型;它必须检查泛型的整个主体,才能决定是否共享其目标代码。相反,Ada 泛型被设计成这样,即编译器可以在 *不查看其主体* 的情况下实例化泛型。)

泛型形式类型

[编辑 | 编辑源代码]

语法允许程序员指定哪些类型类别可以作为实际参数。根据经验:语法表达了泛型如何看待类型,即它假设最坏的情况,而不是实例创建者如何看待类型。

这是 RM 12.5 [注释] 的语法

 formal_type_declaration ::=
   type defining_identifier[discriminant_part] is formal_type_definition;
 
 formal_type_definition ::= formal_private_type_definition
                          | formal_derived_type_definition
                          | formal_discrete_type_definition
                          | formal_signed_integer_type_definition
                          | formal_modular_type_definition
                          | formal_floating_point_definition
                          | formal_ordinary_fixed_point_definition
                          | formal_decimal_fixed_point_definition
                          | formal_array_type_definition
                          | formal_access_type_definition
                          | formal_interface_type_definition

这很复杂,因此下面给出一些示例。使用语法 type T (<>) 声明的类型表示具有 *未知辨别符* 的类型。这是 Ada 语言中不定类型的术语,即不能在没有给出初始表达式的类型,例如具有没有默认值的辨别符的类型,另一个示例是非约束数组类型。

泛型形式类型 可接受的实际类型
type T (<>) 有限 私有; 任何类型。实际类型可以是 有限的 或不是,不定或确定,但 *泛型* 将其视为有限的和不定的,即不假定该类型可以使用赋值。
type T (<>) 私有; 任何非受限类型:泛型知道可以为这种类型的变量赋值,但不能在没有初始值的情况下声明这种类型的对象。
type T 私有; 任何非受限确定类型:泛型知道可以为这种类型的变量赋值,并且可以在没有初始值的情况下声明对象。
type T (<>) 抽象的 标记的 有限 私有; 任何 标记类型,抽象或具体,有限或不是。
type T (<>) 标记的 有限 私有; 任何具体标记类型,有限或不是。
type T (<>) 抽象的 标记的 私有; 任何非受限标记类型,抽象或具体。
type T (<>) 标记的 私有; 任何非受限、具体标记类型。
type T (<>) new Parent; Parent 派生的任何类型。泛型知道 Parent 的操作,因此可以调用它们。TParent 都不能是抽象的。
type T (<>) 抽象的 new Parent 私有; Parent 派生的任何类型,抽象或具体,其中 Parent 是标记类型,因此对 T 的操作的调用可以动态调度。
type T (<>) new Parent 私有; 从标记类型 Parent 派生的任何具体类型。
type T (<>); 任何离散类型:整数枚举
type T 范围 <>; 任何带符号整数类型
type T <>; 任何模类型
type T 增量 <>; 任何(非十进制)定点类型
type T 增量 <> 位数 <>; 任何十进制定点类型
type T 位数 <>; 任何 浮点类型
type T array (I)of E; 任何 数组类型,其索引类型为 I,元素类型为 EIE 也可以是形式参数)
type T access O; 任何指向 O 类型对象的 访问类型O 也可以是形式参数)

在主体中,我们只能使用为形式参数的类型类别预定义的操作。也就是说,泛型规范是泛型实现者与实例化泛型单元的客户端之间的契约。这与其他语言(如 C++)的参数特性不同。

可以通过以下方式进一步限制可接受的实际类型集

泛型形式类型 可接受的实际类型
type T (<>)... 确定或不定类型(简单来说:有或没有辨别符的类型,但存在其他形式的不确定性)
type T (D : DT)... 具有 DT 类型辨别符的类型(也可以指定多个辨别符)
type T... 确定类型(简单来说,没有辨别符或具有具有默认值的辨别符的类型)

泛型形式子程序

[编辑 | 编辑源代码]

可以将子程序作为参数传递给泛型。泛型指定一个泛型形式子程序,包含参数列表和返回类型(如果子程序是函数)。实际参数必须与该参数配置文件匹配,但参数的名称不必匹配。

这是一个将另一个子程序作为参数的泛型子程序的规范

generic
  type Element_T is private;
  with function "*" (X, Y: Element_T) return Element_T;
function Square (X : Element_T) return Element_T;

这是泛型子程序的主体;它像调用任何其他子程序一样调用参数。

function Square (X: Element_T) return Element_T is
begin
  return X * X;   -- The formal operator "*".
end Square;

例如,这个泛型函数可以与矩阵一起使用,假设已经定义了矩阵乘积。

with Square;
with Matrices;
procedure Matrix_Example is
  function Square_Matrix is new Square
    (Element_T => Matrices.Matrix_T, "*" => Matrices.Product);
  A : Matrices.Matrix_T := Matrices.Identity;
begin
  A := Square_Matrix (A);
end Matrix_Example;

可以使用“方框”(is <>)来指定默认值,例如

generic
  type Element_T is private;
  with function "*" (X, Y: Element_T) return Element_T is <>;

这意味着,如果在实例化时,实际类型存在一个函数“*”,并且该函数是直接可见的,那么它将被默认用作实际子程序。

主要用途之一是传递需要的运算符。以下示例显示了这一点(请点击下载链接查看完整示例)

文件:算法/二分查找.adb (查看, 纯文本, 下载页面, 浏览所有)
  generic
     type Element_Type is private;
     ...
     with function "<"
       (Left  : in Element_Type;
        Right : in Element_Type)
        return  Boolean
     is <>;
  procedure Search
    (Elements : in Array_Type;
     Search   : in Element_Type;
     Found    : out Boolean;
     Index    : out Index_Type'Base)
     ...

其他泛型包的泛型实例

[edit | edit source]

泛型形式可以是包;它必须是泛型包的实例,以便泛型知道包导出的接口

generic
   with package P is new Q (<>);

这意味着实际参数必须是泛型包 Q 的实例。Q 后的方框表示我们不关心用于创建 P 的实际参数。可以指定确切的参数,也可以指定必须使用默认值,如下所示

generic
   -- P1 must be an instance of Q with the specified actual parameters:
   with package P1 is new Q (Param1 => X, Param2 => Y);

   -- P2 must be an instance of Q where the actuals are the defaults:
   with package P2 is new Q;

可以指定一个默认参数、无参数或只指定一些参数。默认值用方框 (“ => <> ”) 表示,可以使用 “ others => <> ”) 表示 “ 对所有未提及的参数使用默认值”。当然,实际包必须与这些约束匹配。

泛型可以看到实际包的公共部分和泛型参数(以上示例中的 Param1 和 Param2)。

此功能允许程序员将任意复杂的类型作为参数传递给泛型单元,同时保持完整的类型安全性和封装。(需要示例)

包不能将自身列为泛型形式,因此无法进行泛型递归。以下操作是非法的

with A;
generic
   with package P is new A (<>);
package A; -- illegal: A references itself

事实上,这只是

with A; -- illegal: A does not exist yet at this point!
package A;

的一个特例,这也是非法的,尽管 A 已经不再是泛型。

实例化泛型

[edit | edit source]

要实例化泛型单元,请使用关键字 new

function Square_Matrix is new Square
   (Element_T => Matrices.Matrix_T, "*" => Matrices.Product);

对 C++ 程序员特别感兴趣的注意事项

  • 泛型形式类型完全定义了哪些类型可以作为实际参数;因此,编译器可以在不查看泛型主体的情况下实例化泛型。
  • 每个实例都有一个名称,与所有其他实例不同。特别是,如果一个泛型包声明了一个类型,并且创建了该包的两个实例,那么即使实际参数相同,也将获得两个不同的、不兼容的类型。
  • Ada 要求所有实例化必须是显式的。
  • 无法创建泛型的特殊情况实例(在 C++ 中称为“模板特化”。

作为上述内容的结果,Ada 不允许模板元编程。但是,这种设计具有显著的优势

  • 除非程序员要求内联子程序,否则所有泛型实例都可以共享目标代码;不存在代码膨胀的风险。
  • 在阅读其他人编写的程序时,没有隐藏的实例化,也没有特殊情况需要担心。Ada 遵循最小惊奇原则。

高级泛型

[edit | edit source]

泛型和嵌套

[edit | edit source]

泛型单元可以嵌套在另一个单元中,该单元本身可能是泛型的。即使没有特殊的规则适用(只有关于泛型的正常规则和关于嵌套单元的规则),新手也可能感到困惑。理解泛型单元和泛型单元的实例之间的区别很重要。

示例 1. 嵌套在非泛型包中的泛型子程序。

package Bag_Of_Strings is
   type Bag is private;
   generic
      with procedure Operator (S : in out String);
   procedure Apply_To_All (B : in out Bag);
private
   -- omitted
end Bag_Of_Strings;

要使用 Apply_To_All,首先定义要应用于 Bag 中每个 String 的过程。然后,实例化 Apply_To_All,最后调用实例。

with Bag_Of_Strings;
procedure Example_1 is
   procedure Capitalize (S : in out String) is separate; -- omitted
   procedure Capitalize_All is
      new Bag_Of_Strings.Apply_To_All (Operator => Capitalize);
   B : Bag_Of_Strings.Bag;
begin
   Capitalize_All (B);
end Example_1;

示例 2. 嵌套在泛型包中的泛型子程序

这与上面的示例相同,只是现在 Bag 本身是泛型的

generic
   type Element_Type (<>) is private;
package Generic_Bag is
   type Bag is private;
   generic
      with procedure Operator (S : in out Element_Type);
   procedure Apply_To_All (B : in out Bag);
private
   -- omitted
end Generic_Bag;

如您所见,泛型形式子程序 Operator 接受泛型形式类型 Element_Type 的参数。这是可以的:嵌套泛型可以看到其封闭单元中的所有内容。

您不能直接实例化 Generic_Bag.Apply_To_All,因此必须首先创建 Generic_Bag 的实例,例如 Bag_Of_Strings,然后实例化 Bag_Of_Strings.Apply_To_All

with Generic_Bag;
procedure Example_2 is
   procedure Capitalize (S : in out String) is separate; -- omitted
   package Bag_Of_Strings is
      new Generic_Bag (Element_Type => String);
   procedure Capitalize_All is
      new Bag_Of_Strings.Apply_To_All (Operator => Capitalize);
   B : Bag_Of_Strings.Bag;
begin
   Capitalize_All (B);
end Example_2;

泛型和子单元

[edit | edit source]

示例 3. 作为非泛型单元的子单元的泛型单元。

泛型子单元的每个实例都是父单元的子单元,因此它可以看到父单元的公共部分和私有部分。

package Bag_Of_Strings is
   type Bag is private;
private
   -- omitted
end Bag_Of_Strings; 

generic
   with procedure Operator (S : in out String);
procedure Bag_Of_Strings.Apply_To_All (B : in out Bag);

与示例 1 的区别在于

  • Bag_Of_Strings.Apply_To_All 是单独编译的。特别是,Bag_Of_Strings.Apply_To_All 可能由没有访问 Bag_Of_Strings 源文本的人员编写。
  • 在使用 Bag_Of_Strings.Apply_To_All 之前,必须显式地 with 它;with 父单元 Bag_Of_Strings 不足以。
  • 如果不使用 Bag_Of_Strings.Apply_To_All,程序中将不包含其目标代码。
  • 由于 Bag_Of_Strings.Apply_To_All 位于库级别,因此它可以声明受控类型;嵌套包在 Ada 95 中无法做到这一点。在 Ada 2005 中,可以在任何级别声明受控类型。
with Bag_Of_Strings.Apply_To_All; -- implicitly withs Bag_Of_Strings, too
procedure Example_3 is
   procedure Capitalize (S : in out String) is separate; -- omitted
   procedure Capitalize_All is
      new Bag_Of_Strings.Apply_To_All (Operator => Capitalize);
   B : Bag_Of_Strings.Bag;
begin
   Capitalize_All (B);
end Example_3;

示例 4. 作为泛型单元的子单元的泛型单元

这与示例 3 相同,只是现在 Bag 也是泛型的。

generic
   type Element_Type (<>) is private;
package Generic_Bag is
   type Bag is private;
private
   -- omitted
end Generic_Bag;

generic
   with procedure Operator (S : in out Element_Type);
procedure Generic_Bag.Apply_To_All (B : in out Bag);

with Generic_Bag.Apply_To_All;
procedure Example_4 is
   procedure Capitalize (S : in out String) is separate; -- omitted
   package Bag_Of_Strings is
      new Generic_Bag (Element_Type => String);
   procedure Capitalize_All is
      new Bag_Of_Strings.Apply_To_All (Operator => Capitalize);
   B : Bag_Of_Strings.Bag;
begin
   Capitalize_All (B);
end Example_4;

示例 5. 无参数泛型子单元

泛型单元的子单元 必须 是泛型的,无论如何。仔细想想,这是非常合乎逻辑的:子单元可以看到其父单元的公共部分和私有部分,包括在父单元中声明的变量。如果父单元是泛型的,那么子单元应该看到哪个实例?答案是,子单元必须是父单元的单个实例的子单元,因此子单元也必须是泛型的。

generic
   type Element_Type (<>) is private;
   type Hash_Type is (<>);
   with function Hash_Function (E : Element_Type) return Hash_Type;
package Generic_Hash_Map is
   type Map is private;
private
   -- omitted
end Generic_Hash_Map;

假设我们想要 Generic_Hash_Map 的一个子单元,它可以将映射序列化到磁盘;为此,它需要按哈希值对映射进行排序。这很容易做到,因为我们知道 Hash_Type 是离散类型,因此它具有小于运算符。执行序列化的子单元不需要任何额外的泛型参数,但它必须是泛型的,因此它可以看到其父单元的泛型参数、公共部分和私有部分。

generic
package Generic_Hash_Map.Serializer is
    procedure Dump (Item : in Map; To_File : in String);
    procedure Restore (Item : out Map; From_File : in String);
end Generic_Hash_Map.Serializer;

要将映射读写到磁盘,首先要创建 Generic_Hash_Map 的实例,例如 Map_Of_Unbounded_Strings,然后创建 Map_Of_Unbounded_Strings.Serializer 的实例

with Ada.Strings.Unbounded;
with Generic_Hash_Map.Serializer;
procedure Example_5 is
   use Ada.Strings.Unbounded;
   function Hash (S : in Unbounded_String) return Integer is separate; -- omitted
   package Map_Of_Unbounded_Strings is
      new Generic_Hash_Map (Element_Type => Unbounded_String,
                            Hash_Type => Integer,
                            Hash_Function => Hash);
   package Serializer is
      new Map_Of_Unbounded_Strings.Serializer;
   M : Map_Of_Unbounded_Strings.Map;
begin
   Serializer.Restore (Item => M, From_File => "map.dat");
end Example_5;

另请参阅

[edit | edit source]

维基教科书

[edit | edit source]

维基百科

[edit | edit source]

Ada 参考手册

[edit | edit source]
华夏公益教科书