Perl 完全指南 / 第 7 章:函数与子程序
第 7 章:函数与子程序
“好的函数是代码复用的基石”
Perl 中函数称为子程序(Subroutine),使用 sub 关键字定义。本章涵盖子程序的所有核心知识。
7.1 子程序基础
定义与调用
use strict;
use warnings;
# 定义子程序(可以放在调用之后)
sub greet {
print "Hello, World!\n";
}
# 调用
greet(); # 带括号
greet; # 不带括号也可以(但不推荐)
带参数的子程序
sub greet_name {
my ($name) = @_; # 从 @_ 获取参数
print "Hello, $name!\n";
}
greet_name("Perl"); # Hello, Perl!
greet_name("World"); # Hello, World!
7.2 参数传递机制
Perl 的参数通过特殊数组 @_ 传递,所有参数都是按引用传递。
基本参数获取
sub add {
my ($a, $b) = @_; # 解构参数
return $a + $b;
}
my $result = add(3, 5); # 8
# 一行写法(Perl 惯用法)
sub multiply { $_[0] * $_[1] } # 直接访问 @_ 元素
按引用传递的陷阱
sub bad_modify {
$_[0] = "MODIFIED"; # 直接修改了原始变量!
}
my $x = "original";
bad_modify($x);
print "$x\n"; # MODIFIED — 原变量被修改了!
# 推荐做法:使用 my 复制
sub good_modify {
my ($val) = @_;
$val = "MODIFIED"; # 只修改副本
return $val;
}
传递数组/哈希的注意事项
sub process {
my ($scalar, @array, %hash) = @_;
# 注意:这种写法会把 @_ 全部分给 @array 和 %hash
# 不推荐混合传递!
}
# 推荐方式:传递引用
sub process_ref {
my ($scalar, $array_ref, $hash_ref) = @_;
for my $item (@$array_ref) {
print "$item\n";
}
for my $key (keys %$hash_ref) {
print "$key => $hash_ref->{$key}\n";
}
}
my @data = (1, 2, 3);
my %info = (name => "Perl");
process_ref("test", \@data, \%info);
7.3 返回值
基本返回
sub get_greeting {
my ($name) = @_;
return "Hello, $name!"; # 显式返回
}
# 最后一个表达式的值也会被隐式返回(不推荐)
sub implicit_return {
my ($x) = @_;
$x * 2; # 隐式返回(不推荐)
}
返回多个值
sub min_max {
my @nums = @_;
my ($min, $max) = ($nums[0], $nums[0]);
for my $n (@nums) {
$min = $n if $n < $min;
$max = $n if $n > $max;
}
return ($min, $max); # 返回列表
}
my ($lo, $hi) = min_max(3, 1, 4, 1, 5, 9);
print "最小: $lo, 最大: $hi\n";
上下文感知返回
sub get_data {
my @results = (1, 2, 3, 4, 5);
if (wantarray) { # 列表上下文
return @results;
} elsif (defined wantarray) { # 标量上下文
return scalar @results; # 返回个数
} else {
warn "返回值被忽略"; # 空上下文
}
}
my @list = get_data(); # (1, 2, 3, 4, 5)
my $count = get_data(); # 5
| 上下文 | wantarray 返回 | 典型场景 |
|---|---|---|
| 列表 | 1 | my @x = func() |
| 标量 | "" (false) | my $x = func() |
| 空 | undef | func(); |
7.4 作用域与 my
词法作用域
my $global = "全局";
sub outer {
my $outer_val = "外层";
sub inner {
my $inner_val = "内层";
print "$global\n"; # 可以访问
print "$outer_val\n"; # 可以访问(闭包)
print "$inner_val\n"; # 可以访问
}
inner();
}
变量遮蔽
my $x = "outer";
{
my $x = "inner"; # 遮蔽外层 $x
print "$x\n"; # inner
}
print "$x\n"; # outer(外层 $x 不受影响)
7.5 命名参数模拟
Perl 没有内置的命名参数语法,但可以使用哈希模拟:
sub create_user {
my (%args) = @_;
# 设置默认值
my $name = $args{name} or die "必须提供 name";
my $email = $args{email} or die "必须提供 email";
my $age = $args{age} // 0;
my $active = $args{active} // 1;
return {
name => $name,
email => $email,
age => $age,
active => $active,
};
}
my $user = create_user(
name => "张三",
email => "[email protected]",
age => 30,
);
7.6 闭包(Closure)
sub make_counter {
my $count = 0; # 被闭包捕获的变量
return sub {
$count++;
return $count;
};
}
my $counter = make_counter();
print $counter->(), "\n"; # 1
print $counter->(), "\n"; # 2
print $counter->(), "\n"; # 3
# 每个闭包有独立的 $count
my $counter2 = make_counter();
print $counter2->(), "\n"; # 1
闭包的实际应用
# 创建乘法器
sub make_multiplier {
my ($factor) = @_;
return sub { $_[0] * $factor };
}
my $double = make_multiplier(2);
my $triple = make_multiplier(3);
print $double->(5), "\n"; # 10
print $triple->(5), "\n"; # 15
# 事件回调
sub on_event {
my ($callback) = @_;
# ... 某些操作后
$callback->("event_data");
}
on_event(sub {
my ($data) = @_;
print "收到事件: $data\n";
});
7.7 递归
# 阶乘
sub factorial {
my ($n) = @_;
return 1 if $n <= 1;
return $n * factorial($n - 1);
}
print factorial(5), "\n"; # 120
# 斐波那契(带 memoization)
{
my %memo;
sub fibonacci {
my ($n) = @_;
return $memo{$n} if exists $memo{$n};
return $memo{$n} = ($n <= 2) ? 1 : fibonacci($n-1) + fibonacci($n-2);
}
}
print fibonacci(10), "\n"; # 55
7.8 BEGIN / END 块
# BEGIN:编译阶段执行
BEGIN {
print "这在编译时执行\n";
}
# END:程序结束时执行
END {
print "这在程序结束时执行\n";
}
# CHECK / INIT 等其他特殊块
CHECK { print "编译结束后\n"; }
INIT { print "运行前\n"; }
UNITCHECK { print "编译单元结束后\n"; }
| 块类型 | 执行时机 | 典型用途 |
|---|---|---|
BEGIN | 编译时立即执行 | 初始化、加载配置 |
CHECK | 编译结束后 | 代码检查 |
INIT | 运行前 | 运行时初始化 |
UNITCHECK | 编译单元结束后 | 模块级别初始化 |
END | 程序结束时 | 清理资源 |
7.9 子程序引用
# 获取子程序引用
my $func_ref = \&greet;
# 调用
$func_ref->("World");
# 匿名子程序
my $anon = sub { print "匿名子程序\n" };
$anon->();
# 子程序作为参数
sub apply {
my ($func, $value) = @_;
return $func->($value);
}
print apply(sub { $_[0] ** 2 }, 5); # 25
7.10 业务场景:中间件管道
#!/usr/bin/env perl
use strict;
use warnings;
# 构建中间件管道(类似 Web 框架的中间件)
sub build_pipeline {
my (@middlewares) = @_;
return sub {
my ($request) = @_;
my $current = $request;
for my $mw (@middlewares) {
$current = $mw->($current);
}
return $current;
};
}
# 定义中间件
my $add_header = sub {
my ($req) = @_;
$req->{headers}{'X-Powered-By'} = 'Perl';
return $req;
};
my $add_auth = sub {
my ($req) = @_;
$req->{user} //= 'anonymous';
return $req;
};
my $log_request = sub {
my ($req) = @_;
print "[LOG] $req->{method} $req->{path}\n";
return $req;
};
# 构建并执行
my $pipeline = build_pipeline($log_request, $add_header, $add_auth);
my $request = { method => 'GET', path => '/api/users' };
my $result = $pipeline->($request);
use Data::Dumper;
print Dumper($result);
本章小结
| 要点 | 内容 |
|---|---|
sub | 定义子程序 |
@_ | 参数数组,按引用传递 |
my ($a, $b) = @_ | 推荐的参数获取方式 |
return | 显式返回值 |
wantarray | 感知调用上下文 |
| 闭包 | 捕获外部变量的匿名子程序 |
BEGIN/END | 编译时/结束时执行的特殊块 |
练习
- 编写
sum(@numbers)子程序,支持任意数量的参数 - 编写
fibonacci($n)子程序,使用 memoization 优化 - 编写一个闭包工厂
make_greeting($lang),返回对应语言的问候函数 - 实现一个简单的函数缓存(memoization)包装器
- 使用命名参数模式实现
send_email(to => ..., subject => ..., body => ...)