Skip to content

Perl 子程序(函数)

定义子程序

基本 sub 定义

perl
sub greet {
    print "Hello, World!\n";
}

# 调用子程序
greet();

带参数的子程序

perl
sub greet_person {
    my ($name) = @_;
    print "Hello, $name!\n";
}

greet_person("Alice");
greet_person("Bob");

多个参数

perl
sub add_numbers {
    my ($a, $b) = @_;
    return $a + $b;
}

my $result = add_numbers(10, 20);
print "Result: $result\n";  # Result: 30

参数传递

使用 @_

@_ 是包含所有参数的特殊数组:

perl
sub print_params {
    print "Number of parameters: " . scalar(@_) . "\n";
    foreach my $param (@_) {
        print "Parameter: $param\n";
    }
}

print_params("apple", "banana", "orange");

直接访问参数

perl
sub add {
    my $sum = $_[0] + $_[1];
    return $sum;
}

print add(5, 3);  # 8

使用 my 获取参数副本

perl
sub multiply {
    my ($a, $b) = @_;  # 将参数赋值给局部变量
    return $a * $b;
}

print multiply(4, 5);  # 20

修改原始参数

perl
sub modify_array {
    my ($arr_ref) = @_;  # 接收数组引用
    foreach my $elem (@$arr_ref) {
        $elem *= 2;
    }
}

my @numbers = (1, 2, 3);
modify_array(\@numbers);
print "@numbers\n";  # 2 4 6

返回值

使用 return

perl
sub divide {
    my ($a, $b) = @_;
    return $a / $b if $b != 0;
    return undef;  # 除以零时返回 undef
}

my $result = divide(10, 2);
print "Result: $result\n";  # Result: 5

隐式返回

子程序最后执行的值会自动返回:

perl
sub implicit_return {
    my $x = 10;
    $x * 2;  # 返回 20
}

print implicit_return();  # 20

返回多个值

perl
sub get_stats {
    my @numbers = @_;
    my $sum = 0;
    my $min = $numbers[0];
    my $max = $numbers[0];
    
    foreach my $num (@numbers) {
        $sum += $num;
        $min = $num if $num < $min;
        $max = $num if $num > $max;
    }
    
    return ($sum, $min, $max);
}

my ($total, $minimum, $maximum) = get_stats(1, 5, 3, 9, 2);
print "Sum: $total, Min: $minimum, Max: $maximum\n";

返回列表和标量

perl
sub get_data {
    return (1, 2, 3, 4, 5);
}

# 列表上下文
my @data = get_data();
print "@data\n";  # 1 2 3 4 5

# 标量上下文
my $count = get_data();
print "Count: $count\n";  # Count: 5

默认参数

使用 // 定义默认值

perl
sub greet_with_default {
    my ($name, $greeting) = @_;
    $name //= "World";
    $greeting //= "Hello";
    print "$greeting, $name!\n";
}

greet_with_default();              # Hello, World!
greet_with_default("Alice");        # Hello, Alice!
greet_with_default("Bob", "Hi");    # Hi, Bob!

使用 || 定义默认值(注意陷阱)

perl
sub multiply {
    my ($a, $b) = @_;
    $a ||= 1;
    $b ||= 1;
    return $a * $b;
}

print multiply();        # 1
print multiply(5);      # 5
print multiply(5, 0);   # 5(0 被替换为 1)

命名参数

使用哈希实现命名参数

perl
sub create_user {
    my %params = @_;
    my $name = $params{name} // "Anonymous";
    my $age = $params{age} // 0;
    my $email = $params{email} // "";
    
    return {
        name => $name,
        age => $age,
        email => $email
    };
}

my $user1 = create_user(name => "Alice", age => 25);
my $user2 = create_user(email => "bob@example.com");

print "$user1->{name}, $user1->{age}\n";
print "$user2->{email}\n";

使用引用实现命名参数

perl
sub process_data {
    my ($data_ref, $options_ref) = @_;
    my $verbose = $options_ref->{verbose} // 0;
    
    if ($verbose) {
        print "Processing " . scalar(@$data_ref) . " items\n";
    }
    
    return [map { $_ * 2 } @$data_ref];
}

my @numbers = (1, 2, 3);
my $result = process_data(\@numbers, {verbose => 1});
print "@$result\n";

递归

基本递归示例:阶乘

perl
sub factorial {
    my ($n) = @_;
    return 1 if $n <= 1;
    return $n * factorial($n - 1);
}

print factorial(5);  # 120

递归示例:斐波那契数列

perl
sub fibonacci {
    my ($n) = @_;
    return $n if $n <= 1;
    return fibonacci($n - 1) + fibonacci($n - 2);
}

for (my $i = 0; $i < 10; $i++) {
    print fibonacci($i) . " ";
}
# 0 1 1 2 3 5 8 13 21 34

递归示例:汉诺塔

perl
sub hanoi {
    my ($n, $from, $to, $aux) = @_;
    return if $n == 0;
    
    hanoi($n - 1, $from, $aux, $to);
    print "Move disk $n from $from to $to\n";
    hanoi($n - 1, $aux, $to, $from);
}

hanoi(3, "A", "C", "B");

匿名子程序

基本匿名子程序

perl
my $greet = sub {
    my ($name) = @_;
    print "Hello, $name!\n";
};

$greet->("World");

作为回调函数

perl
sub process_list {
    my ($list, $callback) = @_;
    foreach my $item (@$list) {
        $callback->($item);
    }
}

my @fruits = ("apple", "banana", "orange");
process_list(\@fruits, sub {
    my ($fruit) = @_;
    print "Fruit: $fruit\n";
});

闭包(Closure)

perl
sub make_counter {
    my $count = 0;
    return sub {
        return ++$count;
    };
}

my $counter1 = make_counter();
my $counter2 = make_counter();

print $counter1->();  # 1
print $counter1->();  # 2
print $counter2->();  # 1
print $counter1->();  # 3

原型(Prototype)

基本原型

perl
sub add($$) {  # 接受两个标量
    my ($a, $b) = @_;
    return $a + $b;
}

print add(10, 20);  # 30

数组原型

perl
sub sum_array(\@) {  # 接受一个数组
    my ($arr_ref) = @_;
    my $total = 0;
    $total += $_ for @$arr_ref;
    return $total;
}

my @numbers = (1, 2, 3, 4, 5);
print sum_array(@numbers);  # 15

哈希原型

perl
sub print_hash(\%) {
    my ($hash_ref) = @_;
    while (my ($k, $v) = each %$hash_ref) {
        print "$k: $v\n";
    }
}

my %data = (a => 1, b => 2);
print_hash(%data);

实践示例

示例 1:简单的计算器

perl
#!/usr/bin/perl
use strict;
use warnings;

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

sub subtract {
    my ($a, $b) = @_;
    return $a - $b;
}

sub multiply {
    my ($a, $b) = @_;
    return $a * $b;
}

sub divide {
    my ($a, $b) = @_;
    die "Cannot divide by zero" if $b == 0;
    return $a / $b;
}

print "计算器\n";
print "请输入第一个数字: ";
chomp(my $num1 = <STDIN>);

print "请输入第二个数字: ";
chomp(my $num2 = <STDIN>);

printf "加法: %.2f\n", add($num1, $num2);
printf "减法: %.2f\n", subtract($num1, $num2);
printf "乘法: %.2f\n", multiply($num1, $num2);
printf "除法: %.2f\n", divide($num1, $num2);

示例 2:数据验证

perl
#!/usr/bin/perl
use strict;
use warnings;

sub is_email {
    my ($email) = @_;
    return $email =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
}

sub is_phone {
    my ($phone) = @_;
    return $phone =~ /^\d{3}-\d{3}-\d{4}$/;
}

sub validate_user {
    my ($name, $email, $phone) = @_;
    
    my @errors = ();
    
    push @errors, "Name is required" unless $name;
    push @errors, "Invalid email format" unless is_email($email);
    push @errors, "Invalid phone format" unless is_phone($phone);
    
    return @errors;
}

my $name = "Alice";
my $email = "alice@example.com";
my $phone = "123-456-7890";

my @errors = validate_user($name, $email, $phone);

if (@errors) {
    print "Validation errors:\n";
    print "- $_\n" for @errors;
} else {
    print "Validation passed!\n";
}

示例 3:文本处理工具

perl
#!/usr/bin/perl
use strict;
use warnings;

sub count_words {
    my ($text) = @_;
    my @words = split /\s+/, $text;
    return scalar @words;
}

sub count_chars {
    my ($text) = @_;
    return length($text);
}

sub count_lines {
    my ($text) = @_;
    my @lines = split /\n/, $text;
    return scalar @lines;
}

sub find_longest_word {
    my ($text) = @_;
    my @words = split /\s+/, $text;
    my $longest = "";
    
    foreach my $word (@words) {
        $longest = $word if length($word) > length($longest);
    }
    
    return $longest;
}

my $sample_text = "This is a sample text for demonstration purposes.
It contains multiple lines and words.";

printf "Words: %d\n", count_words($sample_text);
printf "Characters: %d\n", count_chars($sample_text);
printf "Lines: %d\n", count_lines($sample_text);
printf "Longest word: %s\n", find_longest_word($sample_text);

小结

本章节学习了 Perl 的子程序:

  1. ✅ 定义和调用子程序
  2. ✅ 参数传递
  3. ✅ 返回值
  4. ✅ 默认参数
  5. ✅ 命名参数
  6. ✅ 递归
  7. ✅ 匿名子程序
  8. ✅ 原型

接下来,我们将学习 Perl 引用