范围

在Perl中,可以使用两点运算符..或三点运算符...表示一个范围。在列表上下文中两者等价,在标量上下文中两者不等价。

列表上下文中的范围

在列表上下文中,对于范围A..B来说,它返回从A到B中间所有的值,且包含边界的A和B,每一个值都是前一个值自增(即++运算符)之后的结果。如果左边的A值大于右边的B值,将表示空范围。

例如3..6表示3、4、5、6共四个数,'a'..'d'表示a、b、c、d共四个字母。

范围常用来为列表提供数据。例如:

my @arr1 = 1..3;   # 1 2 3
my @arr2 = ('A'..'Z'); # 所有大写字母
my @arr3 = ('a'..'z'); # 所有小写字母
my @arr4 = ('a'..'z','A'..'Z'); # 所有大小写字母
my @arr5 = (0,3..5,7,10..20); # 离散数据:0 3 4 5 7 10到20
my @arr6 = ('01'..'31');  # 两位数日期
my @arr7 = ('01'..'12');  # 两位数月份

通过范围来指定循环执行次数变得非常简单:

# 循环10次
for(1..10){
  say $_;
}

范围也常用于标量上下文。在标量上下文中,范围表示一个布尔值,Perl将这种情况下的操作符称为flip..flop,flip就像合上开关,flop就像打开开关。

标量上下文中的范围,表示的含义是:

  • A..B:从表达式A返回布尔真开始,到表达式B返回布尔真结束,评估表达式A之后会立即评估表达式B
  • A...B:从表达式A返回布尔真开始,到表达式B返回布尔真结束,评估表达式A之后不会立即评估表达式B,而是下一次再评估B

无论是哪种方式,在左表达式开始为真之前,不进入范围,此时不会评估表达式B;在左表达式开始为真后,进入范围,在右表达式为真结束范围之前,不会再继续评估左表达式。简单来说,对于A..B,在A为真之前,不会执行B,在A为真之后、B为真之前,不会再执行A。

例如:

my $i = 0;

# 从0开始,左表达式为真,开始进入范围
# 到5结束,此时右表达式为真,结束范围
# 范围不要放在for、foreach里,它们是列表上下文
while(($i==0)..($i==5)){
  say $i;   # 输出:0 1 2 3 4 5
  $i++;
}

注意,最好不要让右表达式处在左表达式所表示的范围中。例如,下面将是一个无限循环:

my $i = 0;
# 无限循环,从0到5是正常的范围,
# 从6开始,左表达式再次为真,但右表达式一直为假
while(($i>=0)..($i==5)){
  say $i;
  $i++;
}

如果进入范围时,左表达式和右表达式都为真,对于A..B,将立即结束范围。如果不想在进入范围时评估B,使用A...B

例如:

my $i = 0;
# i为0时,左右表达式都为真,立即终止范围
# 因此只输出0
while(($i==0)..($i>=0)){
  say $i;
  $i++;
}

$i = 0;
# 变量i为0时,左表达式为真,开始进入范围,
# 此时不评估右表达式,第二轮循环才评估右表达式
# 因此输出0和1
while(($i==0)...($i>=0)){
  say $i;
  $i++;
}

很多时候,标量上下文中的范围flip..flop的左右表达式都是字面量而不是完整的表达式。如果flip或flop为数值,则该数值将和所读取内容的行号进行比较,即取行号范围,此时和sed、awk的..效果一致,如果flip或flop为正则表达式,则该正则表达式将与所读取含的内容$_做匹配。这种用法在Perl一行式命令中非常方便。

if(100..200){print;}   # 输出第100行到第200行
next if (1.../^$/);    # 跳过文件开头的所有空行
next if(/^$/..eof());  # 忽略从空行开始的所有行