本课目标
完成本课后,你将能够:
- 理解包(package)作为模块化单元的核心作用
- 掌握包规范与包体的分离设计
- 熟练使用私有类型实现信息隐藏
- 理解包的初始化与子程序可见性规则
- 应用包构建可重用、可维护的大型程序
一、包的基本概念
1.1 什么是包
包是 Ada 中最重要的模块化构造,它将相关的类型、常量、变量、子程序组织在一起,形成一个逻辑单元。包通常分为两部分:
- 包规范(package specification):公开接口,声明对外可见的类型、子程序等
- 包体(package body):私有实现,定义子程序的具体实现和内部细节
-- 包规范(文件:math_utils.ads)
package Math_Utils is
function Square (X : Float) return Float;
function Cube (X : Float) return Float;
end Math_Utils;
-- 包体(文件:math_utils.adb)
package body Math_Utils is
function Square (X : Float) return Float is
begin
return X * X;
end Square;
function Cube (X : Float) return Float is
begin
return X * X * X;
end Cube;
end Math_Utils;
1.2 包的使用
通过 with 子句引入包,然后使用点号访问包中的声明:
with Ada.Text_IO;
with Math_Utils;
procedure Main is
Result : Float;
begin
Result := Math_Utils.Square (5.0);
Ada.Text_IO.Put_Line (Float'Image (Result));
end Main;
1.3 use 子句
使用 use 可以免去包名前缀,但可能引起命名冲突:
with Math_Utils;
use Math_Utils;
procedure Main is
begin
-- 可以直接调用 Square,无需 Math_Utils. 前缀
Ada.Text_IO.Put_Line (Float'Image (Square (5.0)));
end Main;
工业建议:在包规范中避免 use,在包体中可以酌情使用。
二、包规范与包体
2.1 包规范(可见部分)
包规范定义了包的公共接口,包括:
- 公开类型声明
- 常量
- 子程序声明(无实现)
- 异常声明
package Stack is
type Stack_Type is private; -- 私有类型,外部无法访问内部结构
procedure Push (S : in out Stack_Type; Item : Integer);
procedure Pop (S : in out Stack_Type; Item : out Integer);
function Is_Empty (S : Stack_Type) return Boolean;
function Is_Full (S : Stack_Type) return Boolean;
Stack_Error : exception;
private
type Stack_Array is array (1 .. 100) of Integer;
type Stack_Type is record
Data : Stack_Array;
Top : Integer range 0 .. 100 := 0;
end record;
end Stack;
2.2 包体(私有实现)
包体实现包规范中声明的子程序,也可以包含私有的类型、子程序等:
package body Stack is
procedure Push (S : in out Stack_Type; Item : Integer) is
begin
if Is_Full (S) then
raise Stack_Error;
end if;
S.Top := S.Top + 1;
S.Data (S.Top) := Item;
end Push;
procedure Pop (S : in out Stack_Type; Item : out Integer) is
begin
if Is_Empty (S) then
raise Stack_Error;
end if;
Item := S.Data (S.Top);
S.Top := S.Top - 1;
end Pop;
function Is_Empty (S : Stack_Type) return Boolean is
begin
return S.Top = 0;
end Is_Empty;
function Is_Full (S : Stack_Type) return Boolean is
begin
return S.Top = S.Data'Last;
end Is_Full;
end Stack;
2.3 分离编译
包规范和包体通常放在不同文件中:
- 规范文件扩展名:
.ads(Ada Specification) - 体文件扩展名:
.adb(Ada Body)
编译时,gnatmake 自动处理依赖关系:
gnatmake main.adb # 自动编译所需的包
三、私有类型与信息隐藏
3.1 私有类型
私有类型(private)在包规范中声明,但完整定义放在 private 部分之后。外部代码只能使用该类型对象和公开子程序,无法访问内部结构。
package Bank_Account is
type Account is private;
procedure Deposit (A : in out Account; Amount : Float);
procedure Withdraw (A : in out Account; Amount : Float);
function Balance (A : Account) return Float;
private
type Account is record
Balance : Float := 0.0;
Number : Integer;
end record;
end Bank_Account;
优点:
- 隐藏实现细节,降低耦合
- 可以更改内部结构而不影响外部代码
- 强制通过子程序访问数据,确保一致性
3.2 受限私有类型
limited private 更进一步,禁止赋值和相等比较操作:
package File_System is
type File_Handle is limited private;
procedure Open (F : out File_Handle; Name : String);
procedure Close (F : in out File_Handle);
procedure Read (F : in File_Handle; Data : out String);
private
type File_Handle is record
FD : Integer;
Path : String (1 .. 256);
end record;
end File_System;
3.3 子类型的可见性
包体中声明的类型、常量、子程序对外部不可见,实现真正的封装。
四、包的初始化
4.1 包的执行部分
包体可以包含初始化代码,放在 begin 和 end 之间。这些代码在程序启动时、使用该包之前自动执行。
package Database is
procedure Connect;
procedure Disconnect;
private
type Connection is ...
end Database;
package body Database is
Conn : Connection;
procedure Connect is ...;
procedure Disconnect is ...;
begin
-- 初始化代码
Connect;
Ada.Text_IO.Put_Line ("Database initialized");
end Database;
4.2 多个包的初始化顺序
Ada 保证包的初始化按依赖关系有序进行(被依赖的包先初始化)。复杂情况下可使用 pragma Elaborate 或 pragma Elaborate_All 控制。
with Logging;
package Application is
...
end Application;
pragma Elaborate_Body (Application); -- 强制包体立即 elaborate
五、子程序可见性
5.1 私有子程序
在包规范中声明的子程序是公开的。如果仅在包体内声明的子程序,则只在包内部可见:
package body Math_Utils is
-- 私有函数,仅包内可用
function Internal_Helper (X : Float) return Float is
begin
return X * 2.0;
end Internal_Helper;
-- 公开函数
function Square (X : Float) return Float is
begin
return Internal_Helper (X) * X; -- 调用私有函数
end Square;
end Math_Utils;
5.2 子程序的可见性规则
- 包规范中声明的子程序:外部可见,需要
with包 - 包体中声明的子程序:仅包内部可见
- 包体中的前向声明:允许子程序互相调用
六、包的重命名
6.1 renames 子句
可以为包或子程序提供本地别名,简化使用或解决命名冲突:
with Ada.Text_IO;
procedure Main is
package IO renames Ada.Text_IO;
procedure Put_Line (S : String) renames IO.Put_Line;
begin
Put_Line ("Hello"); -- 调用 Ada.Text_IO.Put_Line
end Main;
6.2 重命名子程序
function Max (A, B : Integer) return Integer renames Math_Utils.Maximum;
七、子包与嵌套包
7.1 嵌套包
包可以嵌套在其他包内部,实现更细粒度的组织:
package Outer is
procedure P;
package Inner is
procedure Q;
end Inner;
end Outer;
package body Outer is
procedure P is
begin
Inner.Q;
end P;
package body Inner is
procedure Q is
begin
Ada.Text_IO.Put_Line ("Inner");
end Q;
end Inner;
end Outer;
7.2 子包与可见性
子包可以访问父包中声明的内容,父包不能访问子包的私有内容。
八、完整示例:几何库
-- 文件名: geometry.ads
-- 几何形状库的包规范
package Geometry is
type Point is record
X, Y : Float;
end record;
type Shape_Kind is (Circle, Rectangle);
type Shape (Kind : Shape_Kind) is record
Center : Point;
case Kind is
when Circle =>
Radius : Float;
when Rectangle =>
Width, Height : Float;
end case;
end record;
-- 面积计算
function Area (S : Shape) return Float;
-- 周长计算
function Perimeter (S : Shape) return Float;
-- 移动形状
procedure Move (S : in out Shape; Delta_X, Delta_Y : Float);
-- 判断点是否在形状内部
function Contains (S : Shape; P : Point) return Boolean;
private
-- 辅助常量
Pi : constant Float := 3.141592653589793;
end Geometry;
-- 文件名: geometry.adb
-- 几何库的包体
with Ada.Numerics.Generic_Elementary_Functions;
package body Geometry is
-- 私有函数:计算两点间距离
function Distance (P1, P2 : Point) return Float is
begin
return (P2.X - P1.X) ** 2 + (P2.Y - P1.Y) ** 2;
end Distance;
-- 面积实现
function Area (S : Shape) return Float is
begin
case S.Kind is
when Circle =>
return Pi * S.Radius ** 2;
when Rectangle =>
return S.Width * S.Height;
end case;
end Area;
-- 周长实现
function Perimeter (S : Shape) return Float is
begin
case S.Kind is
when Circle =>
return 2.0 * Pi * S.Radius;
when Rectangle =>
return 2.0 * (S.Width + S.Height);
end case;
end Perimeter;
-- 移动
procedure Move (S : in out Shape; Delta_X, Delta_Y : Float) is
begin
S.Center.X := S.Center.X + Delta_X;
S.Center.Y := S.Center.Y + Delta_Y;
end Move;
-- 点是否在形状内部
function Contains (S : Shape; P : Point) return Boolean is
Dx, Dy : Float;
begin
case S.Kind is
when Circle =>
return Distance (S.Center, P) <= S.Radius ** 2;
when Rectangle =>
Dx := abs (P.X - S.Center.X);
Dy := abs (P.Y - S.Center.Y);
return Dx <= S.Width / 2.0 and Dy <= S.Height / 2.0;
end case;
end Contains;
begin
-- 包初始化代码
Ada.Text_IO.Put_Line ("Geometry package initialized");
end Geometry;
-- 文件名: main.adb
-- 使用几何库的主程序
with Ada.Text_IO;
with Geometry; use Geometry;
procedure Main is
C : Shape (Kind => Circle) := (Center => (0.0, 0.0), Radius => 5.0);
R : Shape (Kind => Rectangle) := (Center => (10.0, 10.0), Width => 4.0, Height => 3.0);
P : Point := (3.0, 4.0);
begin
Ada.Text_IO.Put_Line ("Circle area: " & Float'Image (Area (C)));
Ada.Text_IO.Put_Line ("Rectangle area: " & Float'Image (Area (R)));
if Contains (C, P) then
Ada.Text_IO.Put_Line ("Point is inside circle");
end if;
Move (C, 1.0, 1.0);
Ada.Text_IO.Put_Line ("Circle moved to: " &
Float'Image (C.Center.X) & ", " &
Float'Image (C.Center.Y));
end Main;
九、本课总结
- 包是 Ada 模块化的核心,分为规范(
.ads)和体(.adb) - 包规范定义公开接口,包体实现私有细节
- 私有类型(
private)实现信息隐藏,limited private禁止赋值和比较 - 包体可以包含初始化代码(
begin部分),程序启动时自动执行 - 子程序可见性:规范中声明为公开,体中声明为私有
renames可为包或子程序创建别名- 包可以嵌套,子包可访问父包声明
十、课后练习
-
基础包:创建
String_Utils包,提供函数To_Upper、To_Lower、Reverse,并编写测试。 -
私有类型:实现一个
Counter包,私有类型Counter存储整数,提供Increment、Decrement、Value函数,测试其封装性。 -
初始化:创建一个
Config包,在初始化时从文件读取配置参数(可先使用硬编码),提供函数获取配置值。 -
子包:实现一个
Math_Extended包,包含子包Trigonometry(提供Sin、Cos)和Algebra(提供Solve_Quadratic)。 -
重命名:编写程序使用
renames将Ada.Text_IO.Put_Line重命名为Print,并测试。
十一、下节预告
第17课|异常处理
我们将:
- 理解异常的概念与预定义异常
- 掌握异常处理器的编写
- 学习异常的传播与捕获
- 应用异常编写健壮的代码
关键术语表
包规范(package specification):包的公开接口,包含类型、子程序等声明
包体(package body):包的私有实现,包含子程序定义和初始化代码
私有类型(private type):隐藏内部结构的类型,外部无法直接访问组件
受限私有类型(limited private):禁止赋值和相等比较的私有类型
renames:为包、子程序或对象创建别名的关键字初始化部分:包体中的
begin ... end代码块,在程序启动时执行子包:嵌套在其他包内部的包,用于逻辑分组
信息隐藏:封装实现细节,仅暴露必要接口的设计原则