强曰为道

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

第 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 中使用 elsifelse,代码会难以理解。

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. 编写一个程序,遍历 1-100,打印所有 3 的倍数但不是 5 的倍数的数
  2. 使用语句修饰符重写上面的程序
  3. 编写一个程序,使用循环标签跳出双重循环
  4. 实现 FizzBuzz:1-100,3 的倍数打印 Fizz,5 的倍数打印 Buzz,15 的倍数打印 FizzBuzz
  5. 编写一个 CSV 解析器,统计每列的数值总和

扩展阅读