Skip to content

Perl 子例程

在 Perl 中,子程序(通常称为函数)是一个设计用于执行特定任务的代码块。通过将代码划分到子程序中,你可以使程序更具模块化、可重用性,并更易于理解。理想情况下,每个子程序执行一个单一的、定义明确的操作。

虽然在 Perl 中子程序(subroutine)和函数(function)这两个术语可以互换使用,但在面向对象编程中,一个“方法”(method)通常指的是与一个对象或一个类相关联的子程序。

Perl 中子程序定义的通用形式是:

sub subroutine_name {
# Body of the subroutine
my @args = @_;
# ... code ...
return $result; # Optional
}

要调用一个子程序,你通常使用它的名称后跟包含任何参数的括号:

subroutine_name(argument1, argument2);

旧版 Perl 代码(Perl 5 之前)可能会使用符号 & 作为前缀来调用子程序(例如,&subroutine_name;)。虽然这种语法可能仍然有效,但不推荐在现代 Perl 中使用,因为它可能会绕过子程序原型(prototypes),并且通常被认为是过时的。

Perl 会在执行前编译整个程序,所以你可以在脚本中的任何位置定义你的子程序(例如,在文件末尾)。然而,为了可读性,通常会将它们分组或放在首次使用之前。

这是一个简单的例子:

#!/usr/bin/perl
use strict;
use warnings;
# Subroutine definition
sub greet {
print "Hello, Modern Perl!\n";
}
# Subroutine call
greet();

执行该程序会产生:

Hello, Modern Perl!

传递给子程序的参数通过一个名为 @_ 的特殊数组在子程序内部可用。第一个参数是 $_[0],第二个是 $_[1],依此类推。一种良好的实践是在子程序开始处将 @_ 复制到命名的词法变量(lexical variables),以提高清晰度和安全性(因为 @_ 元素是原始参数的别名)。

#!/usr/bin/perl
use strict;
use warnings;
sub display_info {
my ($name, $age) = @_;
print "Name: $name, Age: $age\n";
}
display_info("Alice", 30);

输出:

Name: Alice, Age: 30

当传递多个数组或哈希(hashes)时,它们会被展平(flattened)为 @_ 中的一个单一列表。要将它们作为独立的实体传递,你应该使用引用(references)。这个主题通常会在讨论 Perl 引用时详细介绍。

我们来看一个计算数字列表平均值的例子:

#!/usr/bin/perl
use strict;
use warnings;
sub calculate_average {
my @numbers = @_;
return unless @numbers; # Handle empty list
my $sum = 0;
foreach my $num (@numbers) {
$sum += $num;
}
return $sum / @numbers;
}
my $avg = calculate_average(10, 20, 30, 40);
print "Average: $avg\n"; # Output: Average: 25
my $avg_empty = calculate_average();
if (defined $avg_empty) {
print "Average (empty): $avg_empty\n";
} else {
print "Cannot calculate average for an empty list.\n";
}

输出:

Average: 25
Cannot calculate average for an empty list.

由于 @_ 是一个数组,它自然处理列表。如果你传递混合了标量(scalars)和数组的参数,数组的元素将是展平列表 @_ 的一部分。如果你想轻松捕获其元素,最好将数组作为最后一个参数传递,或者使用引用。

#!/usr/bin/perl
use strict;
use warnings;
sub print_items {
my $prefix = shift @_; # First argument
my @items = @_; # Rest of the arguments
print "$prefix: ", join(", ", @items), "\n";
}
my @fruits = ("Apple", "Banana", "Cherry");
print_items("Fruits", @fruits);
# Output: Fruits: Apple, Banana, Cherry
print_items("Numbers", 1, 2, 3);
# Output: Numbers: 1, 2, 3

当哈希传递给子程序时,它通常会被展平为 @_ 中的键值对列表。你可以在子程序内部重建哈希。

#!/usr/bin/perl
use strict;
use warnings;
sub print_hash_data {
my %data = @_;
foreach my $key (sort keys %data) {
print "$key: $data{$key}\n";
}
}
my %user_info = (
name => "Bob",
id => 101,
city => "New York"
);
print_hash_data(%user_info);

输出(如果没有排序,键的顺序可能不同):

city: New York
id: 101
name: Bob

子程序可以使用 return 关键字返回值。如果省略 return,则子程序中最后评估表达式的值会自动返回。你可以返回标量、列表或引用。

要独立地返回多个数组或哈希,你必须返回它们的引用。

示例(回顾 calculate_average):

#!/usr/bin/perl
use strict;
use warnings;
sub calculate_sum_and_average {
my @numbers = @_;
return (undef, undef) unless @numbers;
my $sum = 0;
foreach my $num (@numbers) {
$sum += $num;
}
my $average = $sum / @numbers;
return ($sum, $average); # Return a list
}
my ($total, $avg_val) = calculate_sum_and_average(10, 20, 30);
if (defined $total) {
print "Sum: $total, Average: $avg_val\n";
} else {
print "Could not calculate.\n";
}
# Output: Sum: 60, Average: 20

子程序中的私有变量 (词法作用域)

Section titled “子程序中的私有变量 (词法作用域)”

默认情况下,如果不使用 use strict;,未用 my 声明的变量(例如 $foo = 1;)具有包(全局)作用域。然而,强烈建议始终使用 use strict; 和 use warnings;。这要求你使用 my、our 或 state 声明变量。

my 关键字声明词法变量(lexical variables)。这些变量是封闭块(例如,子程序、循环或任何一对花括号 {})的私有变量。它们在其作用域之外不可见。

sub example_scope {
my $lexical_var = "I'm local to this sub";
# $lexical_var is only accessible here
my ($another_var, @an_array, %a_hash); # Declaring multiple lexicals
}

区分类全局 (包) 变量和词法变量的示例:

#!/usr/bin/perl
use strict;
use warnings;
our $package_string = "Hello, Package!"; # Declared with 'our' for package scope
sub print_scoped_hello {
my $lexical_string = "Hello, Lexical!";
# $package_string is accessible here
print "Inside subroutine (lexical): $lexical_string\n";
print "Inside subroutine (package): $package_string\n";
$package_string = "Changed by subroutine"; # Modifies package variable
}
print_scoped_hello();
print "Outside subroutine (package): $package_string\n";
# print $lexical_string; # This would be a compile-time error

输出:

Inside subroutine (lexical): Hello, Lexical!
Inside subroutine (package): Hello, Package!
Outside subroutine (package): Changed by subroutine

local() 为包(全局)变量赋予临时值,包括特殊变量如 $_ 或 $/。原始值会在当前块退出时恢复。这称为动态作用域(dynamic scoping)。与 my(词法作用域)相比,local() 在现代 Perl 中很少需要,通常更受青睐,因为它具有更可预测的行为。

谨慎使用 local(),主要用于局部化对 Perl 特殊全局变量或现有包变量的修改,在词法作用域不是选项或不适合的情况下使用。

#!/usr/bin/perl
use strict;
use warnings;
our $global_var = "Initial Global Value";
sub show_local_effect {
print "Inside show_local_effect (before local): $global_var\n";
local $global_var = "Temporary Localized Value";
print "Inside show_local_effect (after local): $global_var\n";
call_another_sub();
print "Inside show_local_effect (after call_another_sub): $global_var\n";
}
sub call_another_sub {
print "Inside call_another_sub: $global_var\n"; # Sees the localized value
}
print "Outside (before call): $global_var\n";
show_local_effect();
print "Outside (after call): $global_var\n";

输出:

Outside (before call): Initial Global Value
Inside show_local_effect (before local): Initial Global Value
Inside show_local_effect (after local): Temporary Localized Value
Inside call_another_sub: Temporary Localized Value
Inside show_local_effect (after call_another_sub): Temporary Localized Value
Outside (after call): Initial Global Value

state 变量(自 Perl 5.9.4 起可用,通常通过 use v5.10; 或 use feature 'state'; 启用)是具有词法作用域的变量,它们在子程序的调用之间保持其值。它们只初始化一次,即第一次调用子程序时。

#!/usr/bin/perl
use strict;
use warnings;
use feature 'state'; # or use v5.10; or higher
sub persistent_counter {
state $count = 0; # Initialized only once
$count++;
print "Counter is now: $count\n";
}
persistent_counter(); # Output: Counter is now: 1
persistent_counter(); # Output: Counter is now: 2
persistent_counter(); # Output: Counter is now: 3

这是一种更简洁的方式来实现以前需要闭包(closures)或包变量的功能。

调用子程序的上下文(context)决定了它预期返回哪种类型的值。Perl 子程序在标量上下文(scalar context,期望一个单一值)与列表上下文(list context,期望一个值列表)中行为可能不同。可以在子程序内部使用 wantarray 函数来检查其调用上下文。

例如,内置的 localtime() 函数:

#!/usr/bin/perl
use strict;
use warnings;
# Scalar context: expects a single string value
my $datestring = localtime(time);
print "Scalar context: $datestring\n";
# List context: expects a list of time components
my @time_parts = localtime(time);
# ($sec, $min, $hour, $mday, $mon, $year_offset, $wday, $yday, $isdst) = localtime(time);
# $mon is 0-11, $year_offset is years since 1900
print "List context (hour): $time_parts[2]\n";
sub contextual_return {
if (wantarray) { # list context
return ("alpha", "beta", "gamma");
} else { # scalar context
return "delta";
}
}
my @list_result = contextual_return();
print "List result: @list_result\n"; # Output: List result: alpha beta gamma
my $scalar_result = contextual_return();
print "Scalar result: $scalar_result\n"; # Output: Scalar result: delta

理解上下文对于编写灵活且正确的 Perl 子程序至关重要,尤其是在与内置函数交互或设计复杂的 API 时。