第20课|泛型编程入门

1 阅读6分钟

本课目标

完成本课后,你将能够:

  • 理解泛型的概念与代码复用的优势
  • 掌握泛型包的声明与实例化
  • 学会泛型子程序的编写
  • 应用泛型实现通用容器(如栈、队列)
  • 理解泛型参数的不同种类(类型、值、子程序)

一、泛型的基本概念

1.1 为什么需要泛型

在强类型语言中,为每种数据类型编写相同的算法是重复劳动。泛型(generic)允许我们编写与类型无关的代码,在编译时实例化为具体类型。

-- 没有泛型:为每种类型写一个栈
package Int_Stack is ...     -- 整数栈
package Float_Stack is ...   -- 浮点栈
-- 代码重复!

-- 使用泛型:一次编写,多种使用
generic
   type Element_Type is private;
package Generic_Stack is ... -- 通用栈

1.2 泛型的形式

Ada 的泛型通过 generic 关键字声明,可以参数化:

  • 类型参数:如 type T is private
  • 值参数:如 Size : Positive
  • 子程序参数:如 with function "+" (A, B : T) return T
-- 泛型包声明
generic
   type Item is private;
   Max_Size : Positive := 100;   -- 默认值
package Stack is
   procedure Push (X : Item);
   procedure Pop (X : out Item);
   function Is_Empty return Boolean;
end Stack;

二、泛型包

2.1 声明泛型包

-- 文件名:generic_stack.ads
generic
   type Element is private;
   Capacity : Positive := 10;
package Generic_Stack is
   procedure Push (E : Element);
   procedure Pop (E : out Element);
   function Is_Empty return Boolean;
   function Is_Full return Boolean;
   Stack_Empty : exception;
   Stack_Full  : exception;
end Generic_Stack;

2.2 实现泛型包体

-- 文件名:generic_stack.adb
package body Generic_Stack is
   type Array_Type is array (1 .. Capacity) of Element;
   Data : Array_Type;
   Top  : Natural := 0;
   
   procedure Push (E : Element) is
   begin
      if Is_Full then
         raise Stack_Full;
      end if;
      Top := Top + 1;
      Data (Top) := E;
   end Push;
   
   procedure Pop (E : out Element) is
   begin
      if Is_Empty then
         raise Stack_Empty;
      end if;
      E := Data (Top);
      Top := Top - 1;
   end Pop;
   
   function Is_Empty return Boolean is (Top = 0);
   function Is_Full return Boolean is (Top = Capacity);
end Generic_Stack;

2.3 实例化泛型包

使用 new 关键字创建具体包:

with Generic_Stack;

procedure Use_Stack is
   -- 实例化为整数栈,容量 20
   package Int_Stack is new Generic_Stack (Element => Integer, Capacity => 20);
   -- 实例化为浮点栈,使用默认容量 10
   package Float_Stack is new Generic_Stack (Element => Float);
   
   X : Integer;
begin
   Int_Stack.Push (42);
   Int_Stack.Pop (X);
   Ada.Text_IO.Put_Line (Integer'Image (X));
end Use_Stack;

三、泛型子程序

3.1 泛型函数

generic
   type T is private;
   with function ">" (A, B : T) return Boolean is <>;  -- 默认使用内置比较
function Generic_Max (A, B : T) return T;

-- 实现
function Generic_Max (A, B : T) return T is
begin
   if A > B then
      return A;
   else
      return B;
   end if;
end Generic_Max;

3.2 实例化泛型函数

function Max_Int is new Generic_Max (Integer);
function Max_Float is new Generic_Max (Float);

-- 自定义类型需要提供比较函数
type Person is record ...;
function ">" (P1, P2 : Person) return Boolean is ...;
function Max_Person is new Generic_Max (Person);

四、泛型参数详解

4.1 类型参数的种类

形式含义允许的操作
type T is private任意非受限类型赋值、相等比较
type T is limited private受限类型无赋值和相等
type T (D : Discrete) is limited private带判别式的类型-
type T is range <>整数类型算术运算、比较
type T is digits <>浮点类型浮点运算
type T is delta <>定点类型定点运算
type T is (<>);离散类型(枚举或整数)枚举属性、比较

4.2 值参数

generic
   Size : Positive;                     -- 编译时常量
   Initial : Integer := 0;              -- 默认值
package Array_Utils is
   type My_Array is array (1 .. Size) of Integer;
   procedure Fill (A : out My_Array; Value : Integer := Initial);
end Array_Utils;

4.3 子程序参数

generic
   type T is private;
   with function Add (A, B : T) return T;
   with function Zero return T;
function Generic_Sum (Items : array (Positive range <>) of T) return T;

-- 实现
function Generic_Sum (Items : array (Positive range <>) of T) return T is
   Result : T := Zero;
begin
   for I in Items'Range loop
      Result := Add (Result, Items (I));
   end loop;
   return Result;
end Generic_Sum;

实例化:

function Sum_Int is new Generic_Sum (Integer, Add => "+", Zero => 0);
function Sum_String is new Generic_Sum (String, Add => "&", Zero => "");

五、完整示例:通用动态数组

-- 文件名:generic_vector.ads
-- 泛型动态数组(类似 C++ vector)

generic
   type Element is private;
   Initial_Capacity : Positive := 16;
package Generic_Vector is
   
   type Vector is private;
   
   procedure Append (V : in out Vector; E : Element);
   procedure Pop (V : in out Vector; E : out Element);
   function Length (V : Vector) return Natural;
   function Is_Empty (V : Vector) return Boolean;
   function Get (V : Vector; Index : Positive) return Element;
   procedure Set (V : in out Vector; Index : Positive; E : Element);
   procedure Clear (V : in out Vector);
   
   Index_Error : exception;
   
private
   type Element_Array is array (Positive range <>) of Element;
   type Vector is record
      Data : Element_Array (1 .. Initial_Capacity);
      Len  : Natural := 0;
   end record;
end Generic_Vector;
-- 文件名:generic_vector.adb
with Ada.Unchecked_Deallocation;

package body Generic_Vector is
   
   procedure Grow (V : in out Vector) is
      New_Cap : Positive := V.Data'Length * 2;
      New_Data : Element_Array (1 .. New_Cap);
   begin
      New_Data (1 .. V.Len) := V.Data (1 .. V.Len);
      V.Data := New_Data;
   end Grow;
   
   procedure Append (V : in out Vector; E : Element) is
   begin
      if V.Len = V.Data'Length then
         Grow (V);
      end if;
      V.Len := V.Len + 1;
      V.Data (V.Len) := E;
   end Append;
   
   procedure Pop (V : in out Vector; E : out Element) is
   begin
      if V.Len = 0 then
         raise Index_Error;
      end if;
      E := V.Data (V.Len);
      V.Len := V.Len - 1;
   end Pop;
   
   function Length (V : Vector) return Natural is (V.Len);
   
   function Is_Empty (V : Vector) return Boolean is (V.Len = 0);
   
   function Get (V : Vector; Index : Positive) return Element is
   begin
      if Index > V.Len then
         raise Index_Error;
      end if;
      return V.Data (Index);
   end Get;
   
   procedure Set (V : in out Vector; Index : Positive; E : Element) is
   begin
      if Index > V.Len then
         raise Index_Error;
      end if;
      V.Data (Index) := E;
   end Set;
   
   procedure Clear (V : in out Vector) is
   begin
      V.Len := 0;
   end Clear;
   
end Generic_Vector;
-- 文件名:main.adb
with Generic_Vector;
with Ada.Text_IO;

procedure Main is
   package Int_Vector is new Generic_Vector (Element => Integer);
   use Int_Vector;
   
   V : Vector;
   X : Integer;
begin
   -- 添加元素
   for I in 1 .. 10 loop
      Append (V, I * 10);
   end loop;
   
   Ada.Text_IO.Put_Line ("Length: " & Natural'Image (Length (V)));
   
   -- 访问元素
   for I in 1 .. Length (V) loop
      Ada.Integer_Text_IO.Put (Get (V, I), Width => 3);
   end loop;
   Ada.Text_IO.New_Line;
   
   -- 弹出元素
   while not Is_Empty (V) loop
      Pop (V, X);
      Ada.Integer_Text_IO.Put (X, Width => 3);
   end loop;
   Ada.Text_IO.New_Line;
end Main;

六、本课总结

  • 泛型允许编写与类型无关的代码,通过实例化生成具体版本
  • 泛型包使用 generic 声明,通过 new 实例化
  • 泛型参数可以是类型、值或子程序
  • 类型参数可用 privatelimited privaterange <>digits <>delta <>(<>) 等约束
  • 子程序参数可指定默认操作(如 is <> 使用内置运算符)
  • 泛型是实现容器库(栈、向量、列表)的基础

七、课后练习

  1. 泛型交换:编写泛型过程 Swap,交换两个任意类型的变量。

  2. 泛型栈扩展:为 Generic_Stack 添加 Peek 函数(查看栈顶但不弹出)。

  3. 泛型队列:实现一个泛型循环队列(FIFO),支持 EnqueueDequeueIs_EmptyIs_Full

  4. 泛型查找:编写泛型函数 Find,在数组中查找给定元素,返回索引或 0。

  5. 泛型排序:实现泛型冒泡排序,接受比较函数参数,对任意数组排序。


八、下节预告

第21课|面向对象编程(标记记录)

我们将:

  • 理解标记记录(tagged record)作为类的基础
  • 掌握继承与多态的实现
  • 学习动态分派与类宽类型
  • 应用面向对象设计模式

关键术语表

泛型(generic):参数化的代码模板,在实例化时生成具体代码

实例化(instantiation):使用 new 关键字将泛型转换为具体包或子程序

类型参数:泛型中传递类型的形式参数

值参数:泛型中传递编译时常量的形式参数

子程序参数:泛型中传递子程序的形式参数

private:表示类型支持赋值和相等比较

limited private:表示类型不支持赋值和相等比较

<>:表示未指定约束,常用于离散类型或形式化子程序默认

is <>:表示使用预定义的运算符作为默认子程序参数