perl语言编程 第四章 语句和声明 上
上一篇 / 下一篇 2008-04-18 11:48:59 / 个人分类:关于脚本语言
一个 Perl 程序由一系列声明和语句组成。一个声明可以放在任何可以放语句的地方,但是它的主要作用发生在编译时。有几个声明类似语句,有双重身份,但是大多数在运行时是完全透明的。编译完之后,语句的主序列只执行一次。
和许多编程语言不同,Perl 并不要求明确的变量声明;变量只在第一次使用的时候才存在,不管你是否曾声明它们。如果你试图从一个从未赋值的变量里面获取一个值,当你把它当作数字时 Perl 会被悄悄地把它当 0 看待,而当作字串时会把它当作""(空字串),或者做逻辑值用的时候就是假。如果你喜欢在误把未定义的值用做真字串或数字时收到警告,或者你更愿意把这样用当作错误,那么 use warning 声明会处理这一切;参阅本章末尾的“用法”节。
如果你喜欢的话,你可以在变量名前用 my或 our 声明你的变量。你甚至可以把使用未声明变量处理为错误。这样的限制是好的,但你必须声明你需要这样的限制。通常,对你的编程习惯,Perl 只管自己的事,但是如果使用 use strict 声明,未定义的变量就会在编译时被了解。同样,参阅“用法”节。
4.1简单语句
一个简单语句是一个表达式,因为副作用而计算。每条简单语句都必须以分号结尾,除非它是一个块中的最后语句。这种情况下,分号是可选的——Perl 知道你肯定已经完成语句了,因为你已经结束块了。但是如果是在多个块的结尾,那你最好还是把分号加上,因为你最后可能还是要另加一行。
虽然象 eval{},do{},和 sub{} 这样的操作符看起来象组合语句,其实它们不是。的确,它们允许在它们内部放多个语句,但是那不算数。从外部看,这些操作符只是一个表达式里的项,因此如果把它们用做语句中的最后一个项,则需要一个明确的分号结束。
任何简单语句的后面都允许跟着一条简单的修饰词,紧接着是结束的分号(或块结束)。可能的修饰词有:
if EXPR unless EXPR while EXPR until EXPR foreach LIST
if 和 unless 修饰词和他们在英语里的作用类似:
$trash->take('out') if $you_love_me;
shutup() unless $you_want_me_to_leave;while 和 until 修饰词重复计算。如你所想,while 修饰词将不停地执行表达式,只要表达式的值为真,或者 until 里只要表达式为假则不断执行表达式。
$expresion++ while -e "$file$expression";
kiss('me') until $I_die;foreach 修饰词(也拼为 for)为在其 LIST 里的每个元素计算一次,而 $_ 是当前元素的别名:
s/java/perl/ for @resumes; print "field: $_ \n" foreach split /:/, $dataline;
while 和 until 修饰词有普通的 while 循环的语意(首先计算条件),只有用于do BLOCK(或者现在已经过时的 do SUBROUTINE 语句)里是个例外,在此情况下,在计算条件之前,先执行一次语句块。这样你就可以写下面这样的循环:
do {
$line = <STDIN>
...
} until $line eq ".\n"参考第二十九章,函数,里的三种不同的 do 入口,还请注意我们稍后讲的循环控制操作符在这个构造中无法使用,因为修饰词不接受循环标记。你总是可以在它周围放一些额外的(花括弧)块提前结束它,或者在其内部放先行运行——就象我们稍后将在“光块”里描述的那样。或者你可以写一个内部带有多重循环控制的真正的循环。说到真正的循环,我们下面要谈谈混合语句。
4.2混合语句
在一个范围(注:范围和名字空间在第二章,集腋成裘,里描述,在“名字”节)里的一个语句序列称之为一个块。有时候,范围是整个文件,比如一个 required 文件或者包含你的主程序的那个文件。有时候,范围是一个用 eval 计算的字串。但是,通常来说,一个块是一个用花括弧({})包围的语句体。当我们说到范围的时候,我们的意思就是上面三种之一。当我们说一个带花括弧的块时,我们会用术语 BLOCK。
混合语句是用表达式和 BLOCK 一起构造的。表达式是由项和操作符组成的。我们将在语法描述里用 EXPR 表示那些你可以使用任意标量表达式的地方。要表示一个表达式是在列表环境里计算的,我们就用 LIST。
下面的语句可以用于控制 BLOCK 的条件和重复执行。(LABEL 部分是可选的。)
if (EXPR) BLOCK if (EXPR) BLOCK else BLOCK if (EXPR) BLOCK elsif (EXPR) BLOCK ... if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK unless (EXPR) BLOCK unless (EXPR) BLOCK else BLOCK unless (EXPR) BLOCK elsif (EXPR) BLOCK ... unless (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK LABEL while (EXPR) BLOCK LABEL while (EXPR) BLOCK continue BLOCK LABEL until (EXPR) BLOCK LABEL until (EXPR) BLOCK continue BLOCK LABEL for (EXPR; EXPR; EXPR) BLOCK LABEL foreach (LIST) BLOCK LABEL foreach VAR (LIST) BLOCK LABEL foreach VAR (LIST) BLOCK continue BLOCK LABEL BLOCK LABEL BLOCK continue BLOCK
请注意和 C 及 Java 不同的是,这些语句是根据 BLOCK 而不是根据语句定义的。这就意味着花括弧是必须的 —— 不允许有虚悬的语句。如果你想不带圆括弧写条件,可以有若干种处理方法。下面的语句作用相同:
unless (open(FOO, $foo)) {die "Can't open $foo: $!" }
if(!open(FOO, $foo)) {die "Can't open $foo: $!" }
die "Can't open $foo: $!" unless open(FOO, $foo);
die "Can't open $foo: $!" if !open(FOO, $foo);
open(FOO, $foo) || die "Can't open $foo: $!";
open(FOO, $foo) or die "Can't open $foo: $!";在大多数情况下,我们都建议使用最后一对儿。这种形式看着整齐一点,尤其是 "or die" 版本。而用 || 形式时你必须习惯虔诚地使用圆括弧,而如果用 or 版本,即使你忘了也用不着担心。
不过我们喜欢最后一种形式的原因是它把语句里重要的部分放到行的前面,这样你会先看到它们。错误控制部分挪到了边上,这样除非必要的时候,你用不着注意它们。如果你每次都把所有 "or die" 检查放到右边同一行,那就很容易读了:
chdir $dir or die "chdir $dir: $!"; open FOO, $file or die "open $file: $!"; @lines = <FOO> or die "$file is empty?"; close FOO or die "close $file: $!";
4.2.1 if 和 else 语句
if 语句比较直接了当。因为 BLOCK 们总是用花括弧包围,所以从不会出现混淆哪一个 if 和 else 或 elsif 有效的情况。在给出的任意 if/elsif/else BLOCK 里,只有第一个条件BLOCK 才执行。如果没有一个为真,而且存在 else BLOCK,则执行之。一个好习惯是在一个 elsif 链的最后放上一个 else 以捕捉漏掉的情况。
如果你用 unless 取代 if,那么它的测试是相反的,也就是说:
unless ($x == 1) ...
等效于
if($x != 1) ...
或者是难看的:
if(!($x == 1)) ...
在控制条件里定义的变量,其范围只扩展到其余条件的范围,包括任何随后可能存在的 elsif 和 else 子句,但是不会超过这个范围:
if (( my $color = <STDIN> ) =~ /red/i ) {
$value = 0xff0000;
}
else ($color =~ /green/i) {
$value = 0x00ff00;
}
else if ($color =~ /blue /i ){
$value = 0x0000ff;
}
else {
warn "unknown RGB component `$color', using black instead\n";
$value = 0x000000;
}在 else 以后,$color 变量将不再位于范围之内。如果你想把范围扩大一些,那么请在条件之前定义该变量。
4.3 循环语句
所有循环语句在它们正式语法里有可选的 LABEL(标记)。(你可以在任何语句里放上标记,但是它们对循环有特殊含义。)如果有标记,它由一个标识后面跟着一个冒号组成。通常标记都用大写以避免与保留字冲突,并且这样它也比较醒目。同时,尽管 Perl不会因为你使用一个已经有含义的词做标记(比如 if 和 open)而晕菜,但你的读者却是会的,所以 ...。
4.3.1 while 和 until 语句
while 语句在 EXPR(表达式)为真的时候不断执行语句块。如果 while 被 until 取代,那么条件测试的取向就变反了;也就是说,它只有在 EXPR 为假的时候才执行语句块。不过,在第一个执行文本之前先要测试条件。
while 和 until 语句可以有一个额外的块:continue 块。这个块在整个块每继续一次都执行一次,不管是退出第一个块还是用一个明确的 next(一个循环控制操作符,它控制进入下一句文本)。在实际中 continue 块用得并不多,但是有它可以让我们严格地定义下一节里的 for 循环。
和我们稍后将看到的 foreach 循环不同的是,一个 while 循环从不在它的测试条件里隐含地局部化任何变量。这种特性在 while 循环使用全局量做循环变量时可能会有“很有趣”的结果。尤其是你可以看看第二章“行输入(尖角)操作符”里关于在某些 while 循环里是如何隐含地给全局的 $_ 赋值的例子,以及如何如何明确地局部化 $_ 的例子。不过,对于其他循环变量来说,你最好象我们下一个例子那样用 my 定义它们.
在一个 while 或者 until 语句的测试条件里定义的变量只是在该语句块或由该测试条件控制的语句块中可见。它可不属于任何周围的语句块的范围。比如:
while (my $line = <STDIN>) {
$line = lc $line;
}
continue {
print $line; # 仍然可见
}
# $line现在超出范围外了这里的 $line 的范围从它在控制表达式里定义开始一直延伸到循环构造的其他部分,包括 continue 语句块,但是不再超出该范围。如果你想把范围扩得更大,请在循环之前定义该变量。
4.3.2 for循环
分成三部分的 for 循环在其圆括弧里有三个用分号隔离的表达式。这些表达式分别代表循环的初始化,条件和再初始化表达式。所有三个表达式都是可选的(不过分号不是);如果省略了它们,则条件总是为真。因此三部分的 for 表达式可以用对应的 while 循环来代替。下面这样的:
LABEL:
for( my $i = i; $i <= 10; $i++ ) {
...
}
和
{
my $i =1;
LABEL:
while ($i <= 10 ){
...
}
continue {
$i++;
}
}是一样的,只不过实际上没有外层的块。(我们在这里放一个只是为了显示 my 的范围是如何被限制的。)
如果你想同时使用两个变量,只需要用逗号分隔平行的表达式即可:
for ( $i = 0, $bit = 0; $i < 32; $i++, $bit <<=1) {
print "Bit $i is set\n" if $ mask & $bit;
}
# $i 和 $bit里的值超越循环继续存在或者把变量定义在只有 for 循环里可见:
for (my ($i, $bit) = (0, 1); @i < 32; $i ++, $bit <<=1 ) {
print "Bit $i is set \n" if $mask & $bit;
}
# 循环版本的$i和$bit现在超出范围了除了通常用于数组索引循环外,for 还可以把自身借给许多其他感兴趣的应用。它甚至不需要明确的循环变量。下面是一个这样的例子,这个例子可以避免你在明确地测试一个交互的文件描述符的文件结束符(EOF)的时候导致的程序的挂起。
$on_a_tty = -t STDIN && -t STDOUT;
sub prompt {print "yes?" if $on_a_tty }
for ( prompt(); <STDIN>; prompt() ) {
# 处理一些事情
}三部分 for 的另外一个传统应用源自其所有三个表达式都是可选的,而且缺省为真这样的一个特点。如果你省略的所有三个表达式,那么你实际上写了一个无限循环:
for(;;) {
...
}上面这样等效于写:
whie (1) {
...
}如果你因为无限循环的表示法而烦恼,那我们要指出的是你总是可以在任何一点退出循环,只需要明确的使用一个象 last 这样的循环控制操作符即可,当然,如果你为巡航导弹写代码,你就可能不用什么明确的循环退出了。因为循环会在合适的时间自动退出。(译注:导弹掉下来就...)
4.3.3 foreach 循环
foreach 循环通过把控制变量(VAR)设置为每个后继的列表元素来循环通过一列数值:
foreach VAR (LIST) {
...
}foreach 键字只是 for 键字的一个同义词,所以,只要你觉得哪个可读性更好,你就可以互换地使用 for 和 foreach。如果省略 VAR,则使用全局 $_ 变量。(别担心,Perl 可以很容易地区分 for(@ARGV) 和 for( $i=0; $i<$#ARGV;$i++),因为后者包含分号。)下面是一些例子:
$sum = 0; foreach $value (@array) { $sum += $value }
for $count (10,9,8,7,6,5,4,3,2,1,'BOOM') { # 倒计时
print "$count\en"; sleep(1);
}
for (reverse 'BOOM', 1 .. 10) { # 一样的东西
print "$_\en"; sleep(1);
}
for $field (split /:/, $data) { # 任何 LIST 表达式
print "Field contains: `$field'\en";
}
foreach $key (sort keys %hash) {
print "$key => $hash{$key}\en";
}最后一句是打印一个排了序的散列数组的规范的方法。参阅第二十九章里的键字和排序记录获取更详细的例子。
在 foreach 里,你没有办法获知你位于列表的何处。你可以通过把前面的元素保留在一个变量里,然后用相邻的元素与之进行比较来获取当前位置,但是有时候你就是得用一个带脚标的三部分 for 循环来获取当前位置。毕竟,for 循环还是有其特点的。
如果 LIST 完全包含可赋值元素(通常也就是说变量,而不是枚举常量),你可以通过修改循环内的 VAR 来修改每个变量。这是因为 foreache 循环的索引变量隐含地是你正在逐一取出的列表的每个元素的别名。你不仅可以现场修改单个列表,你还可以修改在一个列表里的多个数组和散列:
foreach $pay (@salaries) { # 赋予 8%的提升
$pay *= 1.08;
}
for (@christmas, @easter) {
s/ham/turkey/; # 修改菜单(译注:这可真的是菜单)
}
s/ham/turkey/ for @christmas, @easter; # 和上面一样的东西
for ($scalar, @array, values %hash) {
s/^\s+//; #删除开头的空白
s/\s+$//; #删除结尾的空白
}循环变量只在循环的动态或者词法范围内有效。如果该变量事先用 my 定义,那么它隐含地是在词法范围里。这样,对于任何在词法范围之外定义的函数它都是不可见的,即使是在循环里调用的函数也看不到这个变量。不过,如果在范围里没有词法定义,那么循环变量就是局部化的(动态范围)全局变量;这样就允许循环内调用的函数访问它。另外,循环变量在循环前拥有的值将在循环退出之后自动恢复。
如果你愿意,你可以明确地声明使用哪种变量(词法或全局)。这样让你的代码的维护人员能够比较容易理解你的代码;否则,他们就得在一个封闭的范围里往回找,看看该变量在前面声明为什么:
for my $i (1 .. 10) { ... } # $i总是在词法范围
for our $Tick ( 1.. 10) { ...} # $Tick 总是全局当定义伴随着循环变量时,短些的 for 比 foreach 要好,因为它更符合英文的阅读习惯。
下面是一个 C 或者 JAVA 程序员在使用 Perl 表示某种算法时首先会想到的代码:
for ( $i = 0; $i < @ary1; $i++) {
for ( $j=0; $j <@ary2; $j++) {
if($ary1[$i] > $ary2[$j]) {
last; # 没法到外层循环 :-(
}
$ary1[$i] += $ary2[$j];
}
# 这里是last把我带到的地方
}而这里是老练的 Perl 程序员干的:
WID: foreach $this (@ary1) {
JET: foreach $that (@ary2) {
next WID if $this > $that;
$this += $that;
}
}看看,用合乎 Perl 习惯的语法是多简单?这样更干净,更安全,更快。更干净是因为它较少代码。更安全是因为代码在内层和后面的外层循环之间做加法;第二段代码不会被意外地执行,因为 next(下面解释)明确地执行外层循环而不只是简单地退出内层。更快是因为 Perl 在执行 foreach 语句时比等效的 for 循环快,因为元素是直接访问的,而不是通过下标来访问的。
当然,你熟悉哪个就用哪个。要知道"回字有四种写法"。
和 while 语句相似,foreach 语句也可以有一个 continue 块。这样就允许你在每个循环之后执行一小段代码,不管你是用普通的方法到达那里还是用一个 next 到达的。
现在我们可以说 next 就是“下一个”。
4.3.4 循环控制
我们说过,你可以在一个循环上放一个 LABEL(标记),这样就给它一个名字。循环的LABEL 为循环的循环控制操作符 next,last,和 redo 标识循环。LABEL 是给整个循环取名,而不仅仅是循环的顶端。因此,一个引用了循环(标记)的循环控制操作符实际上并不是“go to”(跑到)循环标记本身。就计算机而言,该标记完全可以放在循环的尾部。但是不知什么原因人们喜欢把标记放在东西的顶部。
循环的典型命名是命名为每次循环所处理的项目。这样和循环控制操作符交互地很好,因为循环控制操作符的设计原则是:当合适的标记和语句修饰词一起使用时,它读起来应该象英文。如果循环原型是处理行的,那循环标记原型就是LINE:,因此循环控制操作符就是类似这样的东西:
next LINE if / ^#/; # 丢弃注释
循环控制操作符的语法是:
last LABEL next LABEL redo LABEL
LABEL 是可选的;如果忽略了,该操作符就选用最内层的封闭循环。但是如果你想跳过比一层多的(循环),那你就必须用一个 LABEL 以命名你想影响的循环。LABEL不一定非在你的词法范围内不可(尽管它应该在词法范围里)。实际上,LABEL 可以在你的动态范围的任何地方。如果它的位置强制你的跳转超出了一个 eval 或者子过程的范围,Perl (会根据需要)发出一个警告。
就象在一个函数里你可以想要几个 return 就要几个 return 一样,你也可以在循环里想要几个循环控制语句就要几个。在早期的结构化编程的日子里,有些人坚持认为循环和子过程只能有一个入口和一个出口。单入口的表示法是个好主意,可单出口的想法却让我们写了许多非自然的代码。许多程序都包括在决策树里漫游的问题。一个决策树通常从一个树干开始,但通常以许多叶子结束。把你的循环退出代码(还有函数返回代码)写成具有多个出口是解决你的问题的很自然的做法。如果你在合适的范围里定义你的变量,那么所有东西在合适的时候都会被清理干净,不管你是如何退出该语句块的。
last 操作符立即退出当事循环。如果有 continue 块也不会执行。下面的例子在第一个空白行撤出循环:
LINE: while (<STDIN>) {
last LINE if /^$/; # 当处理完邮件头后退出
}next 操作符忽略当前循环的余下的语句然后开始一次新的循环。如果循环里有 continue 子句,那它将就在重新计算条件之前执行,就象三部分的 for 循环的第三个部件一样。因此 continue 语句可以用于增加循环变量——即使是在循环的部分语句被 next 终止了的情况下:
LINE: while (<STDIN>) {
next LINE if /^#/; # 忽略注释
next LINE if /^$/; # 忽略空白行
...
} continue {
$count++;
}redo 操作符在不重新计算循环条件的情况下重新开始循环语句块。如果存在 continue 块也不会执行。这个操作符通常用于那些希望在刚输入的东西上耍点小伎俩的程序。假设你正在处理这样的一个文件,这个文件里你时不时要处理带有反斜杠续行符的行。下面就是你如何利用 rddo 来干这件事:
while (<>) {
chomp;
if (s/\\$//) {
$_ .= <>;
redo unless eof; # 不超过每个文件的eof
}
# 现在处理 $_
}上面的代码是下面这样更明确(也更冗长)的 Perl 代码的缩写版本:
LINE: while (defined($line = <ARGV>)) {
chomp($line);
if ($line =~ s/\\$//) {
$line .= <ARGV>;
redo LINE unless eof(ARGV);
}
# 现在处理$line
}下面是一段真实的代码,它使用了所有三种循环控制操作符。因为有了 Getopts::* 模块后,现在我们不常用下面这样的方法分析命令行参数,但是它仍然是一个在命名的嵌套循环上使用循环控制操作符的好例子:
ARG: while (@ARGV && $ARGV[0] =~ s/^-(?=.)//) {
OPT: for (shift @ARGV) {
m/^$/ && do { next ARG; };
m/^-$/ && do { last ARG; };
s/^d// && do { $Debug_Level++; redo OPT; };
s/^l// && do { $Generate_Listing++; redo OPT; };
s/^i(.*)// && do { $In_Place = $1 || ".bak"; next ARG; };
say_usage("Unknown option: $_");
}
}还有一个关于循环控制操作符的要点。你可能已经注意到我们没有把它们称为“语句”。那是因为它们不是语句——尽管和其他表达式一样,它们可以当语句用。你甚至可以把它们看作那种只是导致控制流改变的单目操作符。因此,你可以在表达式里任何有意义的地方使用它们。实际上,你甚至可以在它们没有意义的地方使用它们。我们有时候看到这样的错误代码:
open FILE, $file
or warn "Can't open $file: $!\n", next FILE; # 错这样做的目的是好的,但是 next FILE 会被分析为 warn 的一个参数。所以是在 warn 获得发出警告的机会之前先执行 next。这种情况,我们很容易用一些圆括弧通过把 warn 列表操作符转换成 warn 函数来修补这个错误:
open FILE, $file
or warn( "Can't open $file: $! \n"), next FILE; # 对了不过,你会觉得下面的更好读:
unless(open FILE, $file) {
warn "Can't open $file: $!\n";
next FILE;
}4.4 光块
一个 BLOCK本身(带标记或者不带标记)等效于一个执行一次的循环。所以你可以用 last 退出一个块或者用 redo 重新运行块(注:相比之下,next 也退出这种一次性的块。不过有点小区别:next 会执行一个 continue 块,而 last 不会。)。不过请注意,对于eval{}, sub{} 或者更让人吃惊的是 do{} 这些构造而言,情况就不一样了。这哥仨不是循环块,因为它们自己就不是 BLOCK;它们前面的键字把它们变成了表达式中的项,只不过碰巧包含一个语句块罢了。因为它们不是循环块,所以不能给它们打上标记用以循环控制等用途。循环控制只能用于真正的循环,就象 return 只能用于子过程(或者 eval )一样。
循环控制也不能在一个 if 或 unless 里运行,因为它们也不是循环。但是你总是可以引入一付额外的花括弧,这样就有了一个光块,而光块的确是一个循环:
if ( /pattern/) {{
last if /alpha/;
last if /beta/;
last if /gamma/;
# 在这里处理一些只在if()里处理的事情
}}下面是如何利用一个光块实现在 do{} 构造里面使用循环控制操作符的例子。要 next 或 redo 一个 do,在它里面放一个光块:
do {{
next if $x == $y;
# 在这处理一些事务
}} until $x++ > $z;对于 last 而言,你得更仔细:{
do {
last if $x = $y ** 2;
# 在这里处理一些事务
}while $x++ <= $z;
}如果你想同时使用两种循环控制,那你就得在那些块上放上标记,以便区分它们:DO_LAST:{
do {
DO_NEXT: {
next DO_NEXT if $x == $y;
last DO_LAST if $x = $y ** 2;
# 在这里处理一些事务
}
}while $x++ <= $z;
}不过就这个例子而言,你还是用一个在末尾有 last 的普通的无限循环比较好:
for (;;) {
next if $x == $y;
last if $x = $y ** 2;
# 在这里处理一些事务
last unless $x++ <= $z;
}4.4.1 分支(case)结构
和其他一些编程语言不同的是,Perl 没有正式的 switch 或者 case 语句。这是因为 Perl 不需要这样的东西,它有很多方法可以做同样的事情。一个光块就是做条件结构(多路分支)的一个很方便的方法。下面是一个例子:
SWITCH: {
if (/^abc/) { $abc = 1; last SWITCH; }
if (/^def/) { $def = 1; last SWITCH; }
if (/^xyz/) { $xyz = 1; last SWITCH; }
$nothing = 1;
}这里是另外一个:
SWITCH: {
/^abc/ && do { $abc = 1; last SWITCH; };
/^def/ && do { $def = 1; last SWITCH; };
/^xyz/ && do { $xyz = 1; last SWITCH; };
}或者是把每个分支都格式化得更明显:
SWITCH: {
/^abc/ && do {
$abc = 1;
last SWITCH;
};
/^def/ && do {
$def = 1;
last SWITCH;
};
/^xyz/ && do {
$xyz = 1;
last SWITCH;
};
}甚至可以是更恐怖的:
if (/^ac/) {$abc = 1}
elseif (/^def/) { $def = 1 }
elseif (/^xyz/) { $xyz = 1 }
else {$nothing = 1}在下面的例子里,请注意 last 操作符是如何忽略并非循环的 do{} 块,并且直接退出 for 循环的:
for ($very_nasty_long_names[$i++][$j++]->method()) {
/this pattern/ and do { push @flags, '-e'; last; };
/that one/ and do { push @flags, '-h'; last; };
/something else/ and do { last;};
die "unknown value: `$_'";
}只对单个值进行循环,可能你看起来有点奇怪,因为你只是走过循环一次。但是这里利用 for/foreach 的别名能力做一个临时的,局部的$_赋值非常方便。在与同一个很长的数值进行重复地比较的时候,这样做更容易敲键而且更不容易敲错。这样做避免了再次计算表达式的时候可能出现的副作用。并且和本章相关的是,这样的结构也是实现 switch 或 case 结构最常见最标准的习惯用法。
?: 操作符的级联使用也可以起到简单的分支作用。这里我们再次使用 for 的别名功能,把重复比较变得更清晰:
for ($user_color_perference) {
$value = /red/ ? 0xff0000:
/green/ ? 0xff0000:
/blue/ ? 0x0000ff:
0x000000; # 全不是用黑色
}对于最后一种情况,有时候更好的方法是给自己建一个散列数组,然后通过索引快速取出结果。和我们刚刚看到的级联条件不同的是,散列可以扩展为无限数量的记录,而且查找第一个比查找最后一个的时间不会差到哪儿去,缺点是只能做精确匹配,不能做模式匹配。如果你有这样的散列数组:
%color_map = (
azure => 0xF0FFFF,
chartreuse => 0x7FFF00,
lavender => 0xE6E6FA,
magenta => 0xFF00FF,
turquoise => 0x40E0D0,
);那么精确的字串查找跑得飞快:
$value = $color_map{ lc $user_color_preference } || 0x000000;甚至连复杂的多路分支语句(每个分支都涉及多个不同语句的执行)都可以转化成快速的查找。你只需要用一个函数引用的散列表就可以实现这些。参阅第九章,数据结构,里的 “函数散列”获取如何操作这些的信息。
导入论坛 引用链接 收藏 分享给好友 推荐到圈子 管理 举报
TAG:
标题搜索
日历
|
|||||||||
| 日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
| 1 | 2 | 3 | 4 | 5 | |||||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 | |||
| 13 | 14 | 15 | 16 | 17 | 18 | 19 | |||
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | |||
| 27 | 28 | 29 | 30 | 31 | |||||
数据统计
- 访问量: 3240
- 日志数: 555
- 建立时间: 2008-01-07
- 更新时间: 2008-06-24

