在我们开始享受这本书之前,我想介绍一些中级层次的Perl习惯用语,这些用语也将会贯穿整本书。而且它们是Perl程序员初学和进阶的分水岭。
列表操作符
你可能已经知道Perl中的几个列表操作符,但是你可能还没考虑过它们在列表中所发挥的作用。最常见的列表操作符或许就是print。我们提供给一个或多个参数,它将会把它们结合在一起.
print 'Two castaways are', 'Gilligan', 'and', 'Skipper', "/n";
你已经从Learning Perl中学到其他几个列表操作符。sort操作符会把输入的列表进行排序。在它们的主旋律中,castaways不会是字母顺序,但是sort可以做到.
my @castaways = sort qw{Gilligan Skipper Ginger Professor Mary-Ann};
reverse操作符则会以反序返回列表
my @castaways = reverse qw{Gilligan Skipper Ginger Professor Mary-Ann};
Perl有很多其他的列表操作符,而且一旦使用它们,你会觉得打较少的字却能表达你较深的意图。
使用grep进行列表过滤
grep操作符使用列表值和一个测试表达式.它把列表中的值一个个放入$_变量中.然后在标量上下文中计算测试表达式。如果表达式为真,grep会把$_放入输出列表中.
my @lunch_choices = grep &is_edible($_), @gilligans_posessions.
在列表上下文中,grep操作符会返回所有选择的项目列表.而在标量上下文,grep返回所选项目的数目.
my @results = grep EXPR, @input_list;
my $count = grep EXPR, @input_list;
在这儿,EXPR代表了引用$_(显式或隐式)的标量表达式.例如,为了寻找所有大于10的数,在grep的表达式中我们将测试是否$_大于10.
my @input_numbers = (1, 2, 4, 8, 16, 32, 64);
my @bigger_than_10 = grep $_ > 10,@input_numbers;
结果是16,32和64.这是显式引用了$_.还有下面是隐式使用$_的例子:
my @end_in_4 = grep /4$/, @input_numbers;
结果是4,64.
当grep运行时,它会隐藏任何$_中存在的值,也就是说grep是借用这个变量的用处,完成时会把原始值放回.尽管如此,$_并不是简单的一个数据项目的复制;实际上它是数据元素的别名,和foreach中控制变量有些相似.
my @odd_digit_sum = grep digit_sum_is_odd($_), @input_numbers;
sub digit_sum_is_odd{
my $input = shift;
my @digits = split //, $input; #设想没有非数字的字母
my $sum;
$sum += $_ for @digits;
return $sum % 2;
}
现在我们得到1, 16, 和 32.
语法有两种形式:刚刚显示的表达式的形式,现在介绍块形式.这里我们并不是定义一个子程序来进行单一的测试,而且把子程序体放入grep操作符,使用块形式:
my @results = grep{
block;
of;
code;
}@input_numbers;
my $count = grep{
block;
of;
code;
}@input_numbers;
和表达式形式相似,grep将会把输入列表的元素放入$_中.然后,它会执行整个块的代码.块中最后的表达式是测试表达式.(就如所有测试表达式,它在标量上下文中)因为是整个块,我们可以在其中引入一些变量.用块形式重写上面的程序是:
my @odd_digit_sum = grep{
my $input = $_;
my @digits = split //,$input;
my $sum;
$sum += $_ for @digits;
$sum % 2;
}@input_numbers;
注意两个变化: 输入值通过$_传入而不是通过参数列表,而且我们删去了关键字return. 事实上,如果我们保存着return将会是错误的,因为已不在一个单一的子程序中:仅仅一个块代码.当然,我们可以优化使其省去一些中间变量:
my @odd_digit_sum = grep{
my $sum;
$sum += $_ for split //;
$sum % 2;
}@input_numbers;
使用map进行列表转换
map操作符和grep有非常相似的语法,而且它们共享很多操作步骤. 例如,它会临时把列表中的项放入$_,在语法中都允许表达式和块形式.
然而,测试表达式变为映射表达式.map操作符将在列表上下文中计算表达式.每一次计算将给出很多结果的一部分. 整个结果将是所有单一结果的集合.在标量上下文中,map返回元素的数目.但是map更多的还是用在列表上下文.
让我们从一个简单的例子开始:
my @input_numbers = (1, 2, 4, 8, 16, 32, 64);
my @result = map $_ + 100, @input_numbers;
map把七项的每一个都放入$_,我们得到一个单一的输出结果,值为:101,102,104,108,116,132,164
但是我们不能限于对每一个输入仅仅有一个输出.下面看看对于一个输入有两个输出所发生的事情:
my @results = map {$_, 3*$_} @input_numbers;
现在对于一个输入项有两个输出项:1,3,2,6,4,12,8,24,16,48,32,96,64,192.我们可以成对的存入散列中:
my %hash = @results;
或者,不需要中间的数组
my %hash = map {$_, 3*$_} @input_numbers;
你可以看到map是相当通用;对于一个输入项我们可以产生任意多项的输出.而且数量也没有限制. 下面我们看看分离数字:
my @results = map{split //, $_} @input_numbers;
嵌入的块代码将会把每一个数分离成单一的数字.对于1,2,4,8会得到一个结果.对于16,32,和64,我们能得到两个结果.把结果列表集合在一起,最后是:1,2,4,8,1,6,3,2,6,4.
如果一个特殊的调用导致一个空列表,map会返回空结果的列表. 我们可以利用这种特点来选择和丢弃一些项.例如,设想我们仅仅想分离以4为结尾的项:
my @results = map{
my @digits = split //, $_;
if ($digits[-1] == 4){
@digits;
}else{
( );
}
}@input_numbers;
如果最后数字为4,我们将通过@digits返回.如果不是4,返回空列表.并为这种特殊项移除了结果.因此,我们常常使用map来代替grep.
当然,我们可以使用map和grep做任何事情,而且还可以显式的使用foreach,但关键是合适的应用map和grep能帮组我们减少程序的复杂性,使我们能集中在高层次的关键点而不是具体的细节.
使用eval来捕获错误
如果出现错误,具有许多行的代码就潜在的过早的结束程序.
my $averge = $total / $count; # 被0除?
print "okay/n" unless /$match/; #非法模式?
open MINNOW, '>ship.txt'
or die "Can't create 'ship.txt': $!"; #用户自定义的die?
&implement($_) foreach @rescue_scheme; #在子程序中die?
但是仅仅是因为我们代码的一部分产生错误并不就是意味着一切都崩溃. Perl就使用eval操作符来实现错误捕获机制.
eval { $averge = $total / $count};
如果运行在eval块中的代码而产生错误,这个块会执行完成. 但是尽管在块中的代码完成了,Perl将继续执行eval之后的代码.eval后面最常见是检查$@,它将会是空的(意味着没有错误)或者由于失败而产生的信息,像"divide by zero"或更长.
eval{ $averge = $total / $count};
print "Continuing after error: $@" if $@;
eval{ &rescue_scheme_42 };
print "Continuing after error: $@" if $@;
eval块后的分号是必须的,因为eval是一个函数(不是一个控制结构,像if或while).但是这个块又是一个真正的块,里面可能包含词法变量和其他专属的声明.作为函数,很其他子例程一样,eval也有一个返回值.当然如果在块中的代码执行失败,就没有返回值了.这时在标量上下文会给出undef,在列表上下文是一个空列表.因此,另外一种安全计算平均值的方法是:
my $averge = eval{ $total / $count };
现在$averge是一个商或者undef,这就主要依赖于操作成功完成与否.
Perl还支持嵌套的块.只要eval块在执行,它捕获错误的能力就能延伸,因此它能捕获掉嵌套子程序的错误.但是eval不能捕获使它自己停止运行的错误,这包括uncaught signal,越界,和致命错误.eval也不能捕获语法错误,因为Perl使用其他代码来编译eval块,语法错误在编译阶段,而不是在运行阶段.它也不能捕获警告.
带有eval的动态代码
还有另外一种形式的eval,其参数是一个字符串表达式而不是一个块.它编译和执行代码都是在执行阶段.尽管这样很有用处,但是如果有非法数据插入字符串中也会产生危险.因为存在一些显著的异常,我们建议你在字符串中避免使用eval.随后我们会用到它,而且在别人写的代码中尼可能也会见到它:
eval '$sum = 2 + 2';
print "The sum is $sum/n";
Perl会在词法上下文执行这些代码,这就意味着那将是虚的,好像我们刚刚在那儿敲上的代码.eval的结果是最后要执行的表达式,因此在eval中我们不需要整个语句.
#!/usr/bin/perl
foreach my $operator( qw(+ - * /) ){
my $result = eval "2 $operator 2";
print "2 $operator 2 is $result/n";
}
我们遍历operators + - * / 并且在代码中使用了每一个.在字符串中给出了eval,我们把$operator的值插入到eval代码中.eval会执行字符串代表的代码,并且返回最后的表达式.
如果eval不能编译和执行Perl的代码,它也会像块形式那样设置 $@.在这个例子中,我们想捕获任何除0的错误,但不能除任何其他的:
print 'The quotient is ', eval 'eval 5', "/n";
warn $@ if $@;
eval会捕获语法错误并且把信息放到$@,这些我们会在调用eval之后检查到.
The quotient is
syntax error at (eval 1) line 2, at EOF
随后,在Chapters 10,17,和18,我们将在加载模块时使用它.如果没有能够加载模块,Perl正常情况下会停止程序的执行.我们会捕获掉这种错误然后重新执行.
<待续>