强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 返回典型场景
列表1my @x = func()
标量"" (false)my $x = func()
undeffunc();

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编译时/结束时执行的特殊块

练习

  1. 编写 sum(@numbers) 子程序,支持任意数量的参数
  2. 编写 fibonacci($n) 子程序,使用 memoization 优化
  3. 编写一个闭包工厂 make_greeting($lang),返回对应语言的问候函数
  4. 实现一个简单的函数缓存(memoization)包装器
  5. 使用命名参数模式实现 send_email(to => ..., subject => ..., body => ...)

扩展阅读