本课目标
完成本课后,你将能够:
- 掌握过程与函数的声明与使用
- 深入理解参数模式(
in、out、in out)的本质 - 熟练使用默认参数与命名参数调用
- 理解子程序的重载与递归
- 应用子程序进行模块化设计
一、子程序概述
1.1 过程与函数
Ada 的子程序分为两类:
- 过程(procedure):执行一系列操作,不返回值
- 函数(function):计算并返回一个值
-- 过程:无返回值
procedure Greet (Name : in String) is
begin
Ada.Text_IO.Put_Line ("Hello, " & Name);
end Greet;
-- 函数:返回一个值
function Square (X : Float) return Float is
begin
return X * X;
end Square;
1.2 子程序的结构
-- 完整结构
procedure Name (参数列表) is
-- 声明部分(局部常量、变量、类型等)
begin
-- 执行部分
-- 可以使用 return; (过程)或 return 表达式; (函数)
end Name;
1.3 子程序的基本调用
-- 过程调用(语句)
Greet ("Ada");
-- 函数调用(表达式)
Area : Float := Square (5.0);
二、参数模式
Ada 使用参数模式定义数据流向,在编译时保证正确性。
2.1 三种参数模式
| 模式 | 含义 | 数据流向 | 可读 | 可写 |
|---|---|---|---|---|
in | 输入参数 | 调用者 → 子程序 | 是 | 否(但可初始化) |
out | 输出参数 | 子程序 → 调用者 | 否(未初始化) | 是 |
in out | 输入/输出参数 | 双向 | 是 | 是 |
2.2 in 模式
默认模式,可省略 in 关键字。参数在子程序内是只读的:
procedure Show (Value : in Integer) is
begin
Ada.Text_IO.Put_Line (Integer'Image (Value));
-- Value := 10; -- 错误!不能修改 in 参数
end Show;
2.3 out 模式
输出参数,子程序内部必须对其进行赋值,调用时实参可以是未初始化的变量:
procedure Get_Value (Result : out Integer) is
begin
Result := 42; -- 必须赋值
end Get_Value;
-- 调用
X : Integer; -- 未初始化
Get_Value (X); -- X 现在为 42
注意:out 参数在子程序开始时是未定义的(即使实参已有值),因此不能读取它(除非先赋值)。
2.4 in out 模式
可读可写,实参必须是变量:
procedure Increment (Value : in out Integer) is
begin
Value := Value + 1;
end Increment;
-- 调用
X : Integer := 5;
Increment (X); -- X 变为 6
2.5 参数传递机制
Ada 的参数传递由编译器决定(通常按引用或按值),但程序员不直接控制。对于标量类型,通常按值传递;对于大对象(如数组、记录),通常按引用传递。这不会影响程序语义,因为参数模式保证了正确的数据流向。
三、参数定义与调用
3.1 位置关联与命名关联
调用子程序时,可以按位置传递参数,也可以按名称传递:
procedure Format (Value : Float; Width : Integer; Precision : Integer) is
begin
Ada.Float_Text_IO.Put (Value, Fore => Width, Aft => Precision, Exp => 0);
end Format;
-- 位置关联
Format (3.14159, 5, 2);
-- 命名关联(可任意顺序)
Format (Precision => 2, Value => 3.14159, Width => 5);
命名关联使代码更清晰,尤其当参数较多或有默认值时。
3.2 默认参数
可以为参数指定默认值,调用时可省略:
procedure Log (Message : String; Level : String := "INFO") is
begin
Ada.Text_IO.Put_Line (Level & ": " & Message);
end Log;
-- 调用
Log ("System started"); -- Level 默认为 "INFO"
Log ("Error detected", "ERROR"); -- 覆盖默认值
默认参数只能在末尾省略(位置关联时),但使用命名关联可省略任何有默认值的参数。
3.3 参数的类型与约束
参数可以是任何类型,包括有约束类型:
type Day is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
subtype Weekday is Day range Mon .. Fri;
procedure Work_Day (D : Weekday) is ... -- 只接受工作日
3.4 无约束数组作为参数
无约束数组参数可以接受任意大小的实参:
function Sum (A : array (Integer range <>) of Float) return Float is
S : Float := 0.0;
begin
for I in A'Range loop
S := S + A (I);
end loop;
return S;
end Sum;
-- 调用
V1 : array (1 .. 5) of Float := (1.0, 2.0, 3.0, 4.0, 5.0);
V2 : array (0 .. 9) of Float := (others => 1.0);
Total1 := Sum (V1); -- OK
Total2 := Sum (V2); -- OK
四、函数
4.1 函数的定义与返回
函数必须指定返回类型,且至少有一条 return 语句返回该类型的值:
function Max (A, B : Integer) return Integer is
begin
if A > B then
return A;
else
return B;
end if;
end Max;
4.2 函数返回值的使用
X := Max (10, 20); -- 赋值给变量
if Max (A, B) > 100 then ... -- 用在条件中
Ada.Text_IO.Put_Line (Integer'Image (Max (5, 7))); -- 直接输出
4.3 函数与过程的区别
- 函数不能有
out或in out参数(Ada 2012 之后允许in out参数,但通常不推荐) - 函数必须返回一个值,过程不能返回
- 函数调用是表达式,过程调用是语句
五、子程序的重载
重载允许在同一作用域中使用相同的子程序名,但参数类型或个数不同。
5.1 重载规则
-- 重载过程
procedure Print (Value : Integer) is
begin
Ada.Integer_Text_IO.Put (Value);
end Print;
procedure Print (Value : Float) is
begin
Ada.Float_Text_IO.Put (Value);
end Print;
-- 重载函数
function Max (A, B : Integer) return Integer is ...
function Max (A, B : Float) return Float is ...
5.2 重载解析
编译器根据调用时实参的类型和数量选择正确的子程序。如果无法确定(如字面量可匹配多种类型),可以使用类型转换或命名关联来区分。
Print (10); -- 调用 Integer 版本
Print (3.14); -- 调用 Float 版本
-- 歧义情况
procedure Test (X : Integer) is ...
procedure Test (X : Float) is ...
Test (10); -- OK,字面量 10 是 Integer 类型
Test (10.0); -- OK,字面量 10.0 是 Float 类型
Test (1); -- 字面量 1 可能被解释为 Integer,没有歧义
六、递归
Ada 支持递归,函数或过程可以调用自身。
6.1 递归示例:阶乘
function Factorial (N : Natural) return Natural is
begin
if N = 0 then
return 1;
else
return N * Factorial (N - 1);
end if;
end Factorial;
6.2 递归示例:斐波那契数列
function Fib (N : Natural) return Natural is
begin
if N <= 1 then
return N;
else
return Fib (N - 1) + Fib (N - 2);
end if;
end Fib;
注意:递归深度受系统栈限制,深层递归可能导致栈溢出。对于性能敏感场景,考虑迭代实现。
七、子程序的声明与体分离
7.1 前向声明
子程序的体可以放在声明部分之后,使用前向声明(forward declaration)让子程序互相调用:
procedure A (X : in Integer); -- 前向声明
procedure B (Y : in Integer) is
begin
A (Y); -- 现在可以调用 A
end B;
procedure A (X : in Integer) is
begin
B (X);
end A;
7.2 包规范与包体
在实际项目中,子程序通常声明在包规范(.ads)中,实现在包体(.adb)中。这将在后续“包”课程中详细讲解。
八、完整示例:数学工具库
-- 文件名: math_utils.adb
-- 功能:演示子程序的各种特性
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Math_Utils is
-- 过程:输出一个整数的二进制表示
procedure Print_Binary (N : in Natural; Width : in Positive := 8) is
Temp : Natural := N;
Bits : array (1 .. Width) of Character := (others => '0');
begin
for I in reverse 1 .. Width loop
if Temp mod 2 = 1 then
Bits (I) := '1';
end if;
Temp := Temp / 2;
exit when Temp = 0;
end loop;
Ada.Text_IO.Put (Bits);
end Print_Binary;
-- 函数:幂运算(递归)
function Power (Base : Float; Exponent : Natural) return Float is
begin
if Exponent = 0 then
return 1.0;
else
return Base * Power (Base, Exponent - 1);
end if;
end Power;
-- 重载:幂运算(整数底数)
function Power (Base : Integer; Exponent : Natural) return Integer is
begin
if Exponent = 0 then
return 1;
else
return Base * Power (Base, Exponent - 1);
end if;
end Power;
-- 过程:交换两个整数(in out 模式)
procedure Swap (A, B : in out Integer) is
Temp : Integer := A;
begin
A := B;
B := Temp;
end Swap;
-- 函数:计算最大值(重载)
function Max (A, B : Integer) return Integer is
begin
if A > B then return A; else return B; end if;
end Max;
function Max (A, B : Float) return Float is
begin
if A > B then return A; else return B; end if;
end Max;
-- 测试变量
X, Y : Integer := 10;
Z : Float := 5.5;
begin
Ada.Text_IO.Put_Line ("=== 二进制打印 ===");
Print_Binary (42);
Ada.Text_IO.New_Line;
Print_Binary (255, Width => 16);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== 幂运算 ===");
Ada.Float_Text_IO.Put (Power (2.0, 10), Fore => 1, Aft => 0, Exp => 0);
Ada.Text_IO.Put (" (2^10)");
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line (Integer'Image (Power (2, 10)) & " (2^10)");
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== 交换 ===");
X := 5; Y := 9;
Ada.Text_IO.Put_Line ("交换前: X = " & Integer'Image (X) & ", Y = " & Integer'Image (Y));
Swap (X, Y);
Ada.Text_IO.Put_Line ("交换后: X = " & Integer'Image (X) & ", Y = " & Integer'Image (Y));
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== 最大值 ===");
Ada.Text_IO.Put_Line ("Max(10,20) = " & Integer'Image (Max (10, 20)));
Ada.Text_IO.Put_Line ("Max(3.14,2.71) = " & Float'Image (Max (3.14, 2.71)));
end Math_Utils;
九、本课总结
- Ada 子程序分为过程(无返回值)和函数(有返回值)
- 参数模式
in、out、in out明确数据流向,编译器保证正确性 - 支持默认参数和命名参数调用,提高可读性
- 重载允许同名子程序不同参数类型
- 递归是重要编程技术,但需注意栈深度
- 子程序可以前向声明,支持互相调用
十、课后练习
-
基本练习:编写一个过程
Print_Calendar,接受月份和年份,输出该月的日历(需考虑闰年)。 -
默认参数:编写一个函数
Format_Time,接受小时、分钟、秒,默认秒为0,分钟和小时也为0,返回格式化的时间字符串(如 "12:30:00")。测试不同参数组合。 -
重载:定义一个过程
Put,重载三种版本:Integer、Float、String,输出时分别添加前缀(如 "Int: "、"Float: "、"Str: ")。 -
递归:实现一个函数
GCD(最大公约数),使用欧几里得算法(递归版本)。测试多组数据。 -
参数模式:分析以下代码的错误,并解释原因:
procedure Bad (A : in Integer; B : out Integer) is begin B := A + B; -- 哪行错误?为什么? end Bad;
十一、下节预告
第16课|包与模块化设计
我们将:
- 掌握包的规范与体的分离
- 学习信息隐藏与私有类型
- 理解包的初始化与子程序可见性
- 应用包进行大型程序模块化
关键术语表
过程(procedure):执行操作而不返回值的子程序
函数(function):计算并返回一个值的子程序
参数模式:
in(只读)、out(只写)、in out(读写)默认参数:为参数提供默认值,调用时可省略
命名关联:在调用时显式指定参数名
重载(overloading):同一作用域内同名子程序,参数类型或数量不同
递归(recursion):子程序直接或间接调用自身
前向声明:子程序的提前声明,允许相互调用