第 6 章:控制流
第 6 章:控制流
“程序的灵魂在于控制流”
本章介绍 Perl 的条件判断和循环结构,以及 Perl 独特的语句修饰符(Statement Modifier)。
6.1 if / elsif / else
use strict;
use warnings;
my $score = 85;
if ($score >= 90) {
print "优秀\n";
} elsif ($score >= 80) {
print "良好\n";
} elsif ($score >= 60) {
print "及格\n";
} else {
print "不及格\n";
}
unless — 反向 if
# unless 等价于 if (!condition)
my $password = "";
unless ($password) {
die "密码不能为空!\n";
}
# 等价于
if (!$password) {
die "密码不能为空!\n";
}
注意:不推荐在
unless中使用elsif和else,代码会难以理解。
if 作为表达式
# Perl 没有三元运算符以外的"条件表达式"语法
# 但可以这样写:
my $max = ($a > $b) ? $a : $b;
# 或者使用 do 块
my $greeting = do {
if ($hour < 12) { "早上好" }
elsif ($hour < 18) { "下午好" }
else { "晚上好" }
};
6.2 for 循环(C 风格)
# C 风格 for 循环
for (my $i = 0; $i < 10; $i++) {
print "$i ";
}
print "\n"; # 0 1 2 3 4 5 6 7 8 9
# 省略部分
for (my $i = 0; ; $i++) { # 无限循环
last if $i >= 5; # last 相当于 break
print "$i ";
}
# 多变量
for (my ($i, $j) = (0, 10); $i < $j; $i++, $j--) {
print "i=$i j=$j\n";
}
6.3 foreach 循环
# 遍历列表
my @fruits = qw(apple banana cherry);
foreach my $fruit (@fruits) {
print "水果: $fruit\n";
}
# foreach 可以简写为 for
for my $fruit (@fruits) {
print "水果: $fruit\n";
}
# 使用默认变量 $_
for (@fruits) {
print "水果: $_\n"; # $_ 是默认变量
}
# 遍历哈希
my %info = (name => "Perl", year => 1987);
for my $key (sort keys %info) {
printf "%-10s: %s\n", $key, $info{$key};
}
# 遍历范围
for my $i (1..10) {
print "$i ";
}
# 遍历空列表不会执行循环体
for my $x () {
print "这不会执行\n";
}
6.4 while 与 until
# while 循环
my $count = 0;
while ($count < 5) {
print "$count ";
$count++;
}
print "\n";
# until 等价于 while (!condition)
my $attempts = 0;
until ($attempts >= 3) {
print "尝试第 $attempts 次\n";
$attempts++;
}
# 读取文件(最常见的 while 用法)
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
print "> $line\n";
}
close $fh;
6.5 语句修饰符(Statement Modifier)
Perl 的独特特性——将条件/循环放在语句后面:
# 条件修饰符
print "是成年人\n" if $age >= 18;
print "未成年\n" if $age < 18;
die "密码为空\n" unless $password;
warn "数值为零\n" if $count == 0;
# 循环修饰符
print "$_\n" for @array; # 遍历打印
print "$_\n" while <$fh>; # 逐行打印
push @results, $_ for @input; # 收集结果
do_something() until $done; # 直到完成
语句修饰符 vs 块语法
# 语句修饰符(适合单行逻辑)
print "OK\n" if $success;
# 块语法(适合多行逻辑)
if ($success) {
log("操作成功");
send_notification();
print "OK\n";
}
| 场景 | 推荐风格 |
|---|---|
| 单行语句 | 语句修饰符 |
| 多行逻辑 | 块语法 |
| 后置否定 | unless 修饰符 |
| 复杂条件 | 块语法 + if/elsif/else |
6.6 循环控制
last — 跳出循环(相当于 break)
for my $i (1..100) {
last if $i > 10; # 当 $i > 10 时跳出循环
print "$i ";
}
# 输出: 1 2 3 4 5 6 7 8 9 10
next — 跳到下一次迭代(相当于 continue)
for my $i (1..10) {
next if $i % 2 == 0; # 跳过偶数
print "$i ";
}
# 输出: 1 3 5 7 9
redo — 重新执行当前迭代
my $count = 0;
for my $i (1..5) {
$count++;
redo if $count < 3; # 前两次 redo,count 继续增加
print "i=$i count=$count\n";
}
continue 块
my $i = 0;
while ($i < 5) {
print "循环体: $i\n";
} continue {
$i++; # continue 块在每次迭代后执行
}
循环标签
OUTER: for my $i (1..5) {
INNER: for my $j (1..5) {
next OUTER if $j == 3; # 跳到外层循环的下一次
print "i=$i j=$j\n";
}
}
# last 也可以用标签
OUTER: for my $i (1..5) {
for my $j (1..5) {
last OUTER if $i * $j > 10; # 跳出外层循环
print "$i*$j=", $i * $j, " ";
}
}
6.7 循环的返回值
Perl 的循环有返回值(最后一次迭代的结果):
# for/foreach 返回修改后的列表
my @doubled = map { $_ * 2 } 1..5; # (2, 4, 6, 8, 10)
# while 循环的返回值
my @lines;
{
open my $fh, '<', 'data.txt' or die $!;
@lines = <$fh>; # 列表上下文读取所有行
close $fh;
}
6.8 given/when(实验性)
Perl 5.10 引入了类似 switch/case 的结构,但在 Perl 5.18+ 被标记为实验性:
use feature 'switch'; # Perl 5.10-5.34
# use experimental 'switch'; # Perl 5.38+
my $fruit = "apple";
given ($fruit) {
when ("apple") { print "苹果\n" }
when ("banana") { print "香蕉\n" }
default { print "未知水果\n" }
}
注意:
given/when在 Perl 5.38+ 中已被 class 语法取代。建议使用if/elsif/else或哈希分发表代替。
替代方案:哈希分发
my %handler = (
apple => sub { print "苹果\n" },
banana => sub { print "香蕉\n" },
);
my $fruit = "apple";
if (exists $handler{$fruit}) {
$handler{$fruit}->();
} else {
print "未知水果\n";
}
6.9 业务场景:CSV 数据过滤器
#!/usr/bin/env perl
use strict;
use warnings;
# 过滤 CSV 数据:只保留金额 > 1000 的记录
sub filter_csv {
my ($input_file, $output_file, $min_amount) = @_;
open my $in, '<', $input_file or die "读取失败: $!\n";
open my $out, '>', $output_file or die "写入失败: $!\n";
my $header = <$in>;
print $out $header; # 写入表头
my ($total, $filtered) = (0, 0);
while (my $line = <$in>) {
chomp $line;
my @fields = split /,/, $line;
$total++;
if (@fields >= 3 && $fields[2] > $min_amount) {
print $out "$line\n";
$filtered++;
}
}
close $in;
close $out;
return ($total, $filtered);
}
# 使用
my ($total, $filtered) = filter_csv("sales.csv", "filtered.csv", 1000);
print "共 $total 条记录,筛选出 $filtered 条\n";
本章小结
| 要点 | 内容 |
|---|---|
| if/elsif/else | 标准条件判断 |
| unless | 反向 if,适合"除非"场景 |
| for (C 风格) | 传统循环语法 |
| for/foreach | 遍历列表 |
| while/until | 条件循环 |
| 语句修饰符 | statement if condition — Perl 特色 |
| last/next/redo | 循环控制(break/continue/重做) |
| 循环标签 | OUTER: for ... 实现多层循环控制 |
练习
- 编写一个程序,遍历 1-100,打印所有 3 的倍数但不是 5 的倍数的数
- 使用语句修饰符重写上面的程序
- 编写一个程序,使用循环标签跳出双重循环
- 实现 FizzBuzz:1-100,3 的倍数打印 Fizz,5 的倍数打印 Buzz,15 的倍数打印 FizzBuzz
- 编写一个 CSV 解析器,统计每列的数值总和