本课目标
完成本课后,你将能够:
- 理解异常的概念与 Ada 预定义异常
- 掌握异常处理器的编写与异常传播
- 学习自定义异常的声明与触发
- 运用异常编写健壮、容错的代码
一、异常的基本概念
1.1 什么是异常
异常是程序运行时发生的错误或特殊情况,如除零、数组越界、内存不足等。Ada 提供异常处理机制,允许程序捕获异常并采取适当措施,避免崩溃。
procedure Divider is
A, B : Integer;
begin
A := 10;
B := 0;
A := A / B; -- 这里会触发 Constraint_Error
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("除以零错误!");
end Divider;
1.2 异常的优势
- 将错误处理代码与正常逻辑分离
- 可以在调用栈中向上传播,由适当的层级处理
- 强制处理或至少意识到潜在错误
二、预定义异常
Ada 定义了一些标准异常,在特定错误发生时自动触发。
| 异常 | 触发场景 |
|---|---|
Constraint_Error | 范围检查失败、数组索引越界、除零、空指针访问等 |
Program_Error | 程序逻辑错误(如未初始化的子程序调用) |
Storage_Error | 内存不足或栈溢出 |
Tasking_Error | 任务相关的错误(并发编程) |
Numeric_Error | 数值运算错误(已被 Constraint_Error 取代,保留用于兼容) |
2.1 Constraint_Error 示例
procedure Demo_Constraint is
subtype Small is Integer range 1 .. 10;
X : Small := 5;
begin
X := X + 10; -- 超出范围,触发 Constraint_Error
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("范围溢出");
end Demo_Constraint;
2.2 Program_Error 示例
procedure Demo_Program_Error is
type Func_Ptr is access function (X : Integer) return Integer;
P : Func_Ptr; -- 未初始化
begin
-- P.all (5); -- 调用空访问值,触发 Program_Error
null;
exception
when Program_Error =>
Ada.Text_IO.Put_Line ("程序逻辑错误");
end Demo_Program_Error;
2.3 Storage_Error 示例
procedure Demo_Storage is
type Big_Array is array (1 .. Integer'Last) of Integer;
A : Big_Array; -- 可能触发 Storage_Error
begin
null;
exception
when Storage_Error =>
Ada.Text_IO.Put_Line ("内存不足");
end Demo_Storage;
三、异常处理器的编写
3.1 基本语法
exception 部分放在子程序或包的 end 之前,包含一个或多个 when 分支:
procedure Example is
begin
-- 可能触发异常的代码
...
exception
when Constraint_Error =>
-- 处理约束错误
Ada.Text_IO.Put_Line ("约束错误");
when Program_Error | Storage_Error =>
-- 处理多个异常
Ada.Text_IO.Put_Line ("系统错误");
when others =>
-- 捕获所有未列出的异常
Ada.Text_IO.Put_Line ("未知错误");
end Example;
3.2 when others 分支
when others 必须放在最后,作为默认处理器。推荐至少记录错误信息,避免异常被静默忽略。
exception
when Constraint_Error =>
...
when others =>
Ada.Text_IO.Put_Line ("未预期的异常");
raise; -- 重新抛出当前异常,让上层处理
3.3 异常处理器中的 raise
可以在处理器中重新抛出异常,让上层继续处理:
procedure Sub_Proc is
begin
...
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("Sub_Proc 中捕获,重新抛出");
raise; -- 重新抛出同一异常
end Sub_Proc;
四、自定义异常
4.1 声明异常
可以在任何声明区域定义自己的异常:
package Stack is
Stack_Empty : exception;
Stack_Full : exception;
procedure Push (Item : Integer);
procedure Pop (Item : out Integer);
end Stack;
4.2 触发异常
使用 raise 语句触发异常,可以附带错误消息(Ada 2005+):
package body Stack is
Max_Size : constant := 100;
type Stack_Array is array (1 .. Max_Size) of Integer;
Data : Stack_Array;
Top : Integer := 0;
procedure Push (Item : Integer) is
begin
if Top = Max_Size then
raise Stack_Full;
end if;
Top := Top + 1;
Data (Top) := Item;
end Push;
procedure Pop (Item : out Integer) is
begin
if Top = 0 then
raise Stack_Empty;
end if;
Item := Data (Top);
Top := Top - 1;
end Pop;
end Stack;
4.3 带消息的 raise
raise Stack_Full with "Stack capacity exceeded";
捕获时可通过 Exception_Message 获取消息:
with Ada.Exceptions; use Ada.Exceptions;
exception
when E : Stack_Full =>
Ada.Text_IO.Put_Line (Exception_Message (E));
五、异常的传播
5.1 传播机制
如果异常在当前子程序中没有被处理,它会向调用者传播,直到被某个处理器捕获。如果传播到主程序仍未处理,程序终止。
procedure Level3 is
begin
raise Constraint_Error;
end Level3;
procedure Level2 is
begin
Level3;
exception
when Program_Error =>
Ada.Text_IO.Put_Line ("Level2 捕获 Program_Error");
end Level2;
procedure Level1 is
begin
Level2;
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("Level1 捕获 Constraint_Error");
end Level1;
调用 Level1 输出:
Level1 捕获 Constraint_Error
因为 Level3 抛出的 Constraint_Error 未被 Level2 捕获(Level2 只处理 Program_Error),所以传播到 Level1。
5.2 未处理异常
未捕获的异常会导致程序终止,并输出错误信息(取决于运行时环境)。
procedure Unhandled is
begin
raise Constraint_Error;
end Unhandled;
-- 运行时输出类似:
-- raised CONSTRAINT_ERROR : unhandled exception
六、异常与子程序参数
6.1 子程序内部异常
子程序内部发生的异常,如果未被处理,会传播到调用者。参数的值可能不确定(对于 out 和 in out 参数,如果异常发生在赋值之后,其值可能已改变)。Ada 不保证参数的原子性。
procedure Set_Value (X : out Integer) is
begin
X := 10;
raise Constraint_Error; -- 传播前 X 已被赋值
end Set_Value;
6.2 避免部分更新
如果需要在异常时回滚状态,可以先用局部变量计算,确认无异常后再赋值:
procedure Safe_Update (Target : out Integer; Source : Integer) is
Temp : Integer;
begin
Temp := Source * 2;
if Temp > 100 then
raise Constraint_Error;
end if;
Target := Temp; -- 只有成功时才修改输出参数
end Safe_Update;
七、异常与包初始化
包体初始化部分(begin ... end)抛出的异常会导致程序终止,除非在初始化代码中捕获。
package Init_Error is
procedure P;
end Init_Error;
package body Init_Error is
procedure P is
begin
null;
end P;
begin
Ada.Text_IO.Put_Line ("Initializing...");
raise Constraint_Error; -- 程序会终止
end Init_Error;
要优雅处理,可以在初始化代码内部使用异常处理器:
begin
begin
-- 可能失败的初始化
...
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("Init failed, using defaults");
end;
end Init_Error;
八、完整示例:文件读取与异常处理
-- 文件名: file_reader.adb
-- 功能:读取文件并处理各种异常
with Ada.Text_IO;
with Ada.Exceptions; use Ada.Exceptions;
procedure File_Reader is
File_Name : constant String := "data.txt";
File : Ada.Text_IO.File_Type;
Line : String (1 .. 100);
Last : Integer;
-- 自定义异常
File_Empty : exception;
-- 读取并处理文件
procedure Process_File (Name : String) is
Line_Count : Integer := 0;
begin
Ada.Text_IO.Open (File, Ada.Text_IO.In_File, Name);
Ada.Text_IO.Put_Line ("Opened file: " & Name);
loop
Ada.Text_IO.Get_Line (File, Line, Last);
Line_Count := Line_Count + 1;
Ada.Text_IO.Put_Line (Integer'Image (Line_Count) & ": " & Line (1 .. Last));
end loop;
exception
when Ada.Text_IO.End_Error =>
Ada.Text_IO.Put_Line ("End of file reached. Total lines: " &
Integer'Image (Line_Count));
if Line_Count = 0 then
raise File_Empty;
end if;
Ada.Text_IO.Close (File);
when Ada.Text_IO.Name_Error =>
Ada.Text_IO.Put_Line ("File not found: " & Name);
raise;
when Ada.Text_IO.Status_Error =>
Ada.Text_IO.Put_Line ("File already open or invalid");
when others =>
Ada.Text_IO.Put_Line ("Unexpected error: " & Exception_Information (Exception_Information'First));
end Process_File;
begin
Process_File (File_Name);
exception
when File_Empty =>
Ada.Text_IO.Put_Line ("File is empty");
when E : others =>
Ada.Text_IO.Put_Line ("Program failed: " & Exception_Message (E));
end File_Reader;
九、本课总结
- Ada 预定义异常(
Constraint_Error、Program_Error、Storage_Error等)覆盖常见运行时错误 - 使用
exception部分捕获异常,when分支区分不同异常 raise触发异常,可附带消息(Ada 2005+)- 未处理的异常沿调用链向上传播,直到被捕获或程序终止
- 自定义异常扩展错误类型,提高代码可读性
- 在子程序和包初始化中要小心处理异常,避免数据不一致
十、课后练习
-
基本练习:编写一个除法函数,捕获除零异常并返回
0.0。 -
自定义异常:实现一个银行账户包,提款时若余额不足则触发
Insufficient_Funds异常,并带消息显示当前余额。 -
异常传播:编写三个嵌套子程序,最深层抛出
Program_Error,中间层捕获并记录后重新抛出,最外层捕获并输出消息。 -
包初始化异常:创建一个包,在初始化时检查某个配置变量的值,若无效则触发异常。在主程序中捕获并处理。
-
文件处理:编写程序打开不存在的文件,捕获
Name_Error并提示用户输入正确文件名。
十一、下节预告
第18课|输入输出详解
我们将:
- 掌握
Ada.Text_IO与Ada.Integer_Text_IO的使用 - 学习文件输入输出的各种模式
- 理解格式化输出与控制
- 应用输入输出构建交互式程序
关键术语表
异常:程序运行时发生的错误或特殊情况
预定义异常:Ada 标准库中定义的常见异常(
Constraint_Error等)
exception:定义异常处理器或声明异常的关键字
raise:显式触发异常的关键字
when:在处理器中匹配特定异常的分支关键字
others:捕获所有未列出的异常的通配分支
Exception_Message:获取异常附带消息的函数(Ada.Exceptions)异常传播:异常未被处理时向调用者传递的过程
自定义异常:用户声明的异常,用于特定业务逻辑错误