日常编程工作很少有什么魅力可言 ― 您必须使循环简单明了,语句分支正确并使代码非常整洁。幸运的是,有了 Perl,我们可以轻松地用简化的循环构造、后缀 if/unless 分支语句和更多的方式编写优秀的代码。本文将带您尝试 Perl 辅助您进行日常编码工作的几种特定方式。本文不打算教您 Perl,但是将提高您现有的 Perl 知识 ― 从初学者到中级水平。
Perl 循环
Perl 具有标准的过程性循环构造:while() 循环、do-until() 循环和 for() 循环。对于大多数程序员来说,这些构造几乎是第二天性(second nature)。另外,Perl 还允许程序员轻松地针对某一列值或某一范围的测试条件进行迭代处理。
自 Perl 5.004 起,所有上述循环和 if/unless 构造都允许以 EXPRESSION 循环
或
EXPRESSION if/unless 的形式使用反转表示法(语法上而不是语义上)。可以在 perldoc perlsyn 页面中找到 Perl 语法的详细描述。建议您在没有很好编写文档的情况下避免使用这种表示法。某些人,特别是具有 C/C++/Java 背景的人,看到某一行中只出现 print $_ foreach @array 时可能会感到困惑。
while() 循环在执行之前检查它的条件。下例是一种寻求输入的常见方式:
清单 1. 最简单的 while() 循环while (
)
{
# do something with the current input here
}
如果没有输入,则上面的代码将执行 0 次,如果有 N 次输入,则执行 N 次。除了在代码内部使用 next/last 语句之外,没有其它方法来控制迭代次数。
do-until() 循环类似于 while() 循环,但是它的内容将至少被执行一次。对于那些需要事先完成某件事的条件来说,它更合适,例如:
清单 2. 简单的 do-until 循环my $i;
do
{
# put values in @list here...
...
$i = grep(/pattern/, @list);
}
until($i > 0);
在上例中,$i 在执行 grep 之前没有值,因此,我们可以使用一个 do-until 循环并采用一种更自然的流程,而不是给它赋一个特殊的假值(fake value)然后再使用 while。当然,不使用“until”而使用“while”也是可以的。
最后,与 do-until() 和 while() 循环采用一个参数不同的是,for() 循环采用三个参数。这就是 for() 循环最受世界各地的 C 程序员青睐的原因所在 ― 可以很轻易地编写一个在一行中做所有事的 for() 循环。甚至有时程序员还编写没有循环体的 for() 循环,因为所有工作都在循环参数自身内部完成。这种压缩的代码只有在代码模糊大赛(obfuscation contest)中才受欢迎,在生产中却不受欢迎。
for() 有一个初始化区(令人惊讶的是,初始化区常用于初始化变量)、一个迭代区(通常在这里递增循环计数器)和一个测试区(用于测试是否应该继续执行循环)。因此,等价的 while() 和 for() 循环将是:
清单 3. 等价的 for() 和 while() 循环for ($i=0; $i++; $i < 10)
{
# do something 10 times
}
$i=0;
while ($i < 10)
{
# do something 10 times
$i++;
}
For 与 foreach
因为 for() 循环的文化传统(啊嗨)很丰富,所以建议您使用 foreach()。这是因为以下几个原因:
- foreach() 在英语中听起来更好。
- foreach() 与 C 的传统截然不同,而 for() 可能会困惑程序员新手。
- 在解释器级别上二者没有不同。
如果您觉得必须使用 for() 循环,建议您详细记录它出现的地方和使用它的原因。虽然 for() 有其用处,特别是在三个参数的形式中更有用,但程序员新手和中级程序员最好避免使用这个工具。
在上例中,$i 从 0 递增到 9(有 10 个值)。for() 循环看起来当然整洁些,确实如此。然而,Perl 不愿意停留在 C 现有的成就之上(因为一提起 for() 循环,人们最容易直接联想到 C 语言)。Perl 定义了一种 for() 循环的改进语法,并为它起了个别名“foreach”。这意味着,无论在 Perl 中的什么地方,只要可以使用“for”作为独立的语言构造,就可以使用“foreach”来替代“for”,反之亦然。
for 和 foreach:如何获得乐趣并获得成功
上面提到的改进之处是:foreach() 循环以一个列表作为参数。这非常有用,因为列表是 Perl 中的基本数据类型,可以用列表做很多奇妙的事。例如,在 Perl 中,将 2 到 40 的列表写成“2..40”。对于列表中的每一个元素,foreach() 循环的主体都执行一次。与 while() 和 do-until() 循环一样,“next”和“last”关键字可以更改程序的循环流程。“Next”和“last”分别跳至下一元素和全部退出循环。下面是一些示例:
清单 4. foreach() 循环foreach (1..100)
{
# do something 100 times, $_ will be set to the current number
print "Now on iteration $_\n";
}
foreach (1..20,101..120)
{
# do something 40 times, $_ will be set to the current number
}
foreach my $counter (0..1)
{
# do something twice, $counter will be 0, then 1
}
foreach my $i (0..1000)
{
next unless $i%5; # next if this number is a multiple of 5
print "$i is not a multiple of 5...\n";
next unless $i%7; # next if this number is a multiple of 7
print "$i is not a multiple of 7...\n";
next unless $i%12; # next is this number is a multiple of 12
# $i is now not a multiple of 12, 5, or 7
print "$i is a lucky, lucky number to have met you...\n";
}
# here we use the Perl map operator to create a list of 100 even numbers
# see chapter 5 for details on the map and grep operators
foreach (map { $_ *= 2 } 0..99)
{
print "Even number: $_\n";
If、else、elsif,unless,或者,如何逗一只猫
一般对程序员而言,很少有事情象理解和使用逻辑条件和布尔代数(Boolean algebra)那样重要。编写一个不带逻辑分支的程序是完全不可能的(如果您不相信我,可以去问任何一位计算机科学教授)。Perl 的逻辑运算符与 C 十分类似。有关运算符语法的详细信息,请参考 perldoc perlop 页面。
然而,有几点是不同的。对于以前使用 C/C++/Java 的人来说,首先一点是::Perl 通常不允许使用表达式作为 if
或
else 块 ― 它们必须是块,而不是表达式。换句话说:
清单 5. if 或
else 块if (something) dothis(); # does NOT work
if (something) # usually works great
{
dothis();
}
dothis() if (something); # my favorite, but see 3.1
# (it should be documented)
unless() 语句极其有用。请使用它。如果在编写一个 if() 语句的过程中发现必须在检查其状态之前反转条件,那么您差不多肯定要使用 unless() 来进行替代。常见的例外情况是:需要一个 elseif() 分支,控制循环的逻辑语句过于复杂,或者您不善于使用经常困扰您的反转 Boolean 语句。
与 Monty Python 的“如何逗一只猫(How to confuse a cat)”短剧十分类似,Perl 的 unless() 分支逻辑一开始也令人迷惑。unless() 的概念并非来自 Perl,但是大多数 Perl 新手以前没有使用过具有 unless() 的语言。新手可能会被 unless() 的强大所吓倒。毕竟,大学的计算机科学课程没有象教 if() 那样教 unless() ― 因此,有时人们把 unless() 看成是流浪汉(在最好的情况下),有时看成是管闲事的人(在最坏的情况下)。其实不是那样。unless() 语句是一种不同的思考方式。它将基础控制结构从笨拙的反转形式更改成一种自然的形式。请比较以下示例:
清单 6. if 和 unless,但是请学习基本的布尔代数if (!eof()) ...
unless(eof()) ...
if (!clear && !ready) ...
unless(clear || ready) ...
唯一的窍门是从内到外反转逻辑语句 ― 在逻辑上否定每一个条件,还要反转 Boolean 运算符。很容易学会基本的布尔代数(请参阅本文稍后的 参考资料),而修复逻辑错误既不简单也不有趣。请花一些时间学习布尔代数的基本知识,您的所有代码都将从中获益。
整洁之路(无需浴帽)
如果怀念 gcc 中 lint 工具和“-Wall -pedantic”编译开关的光辉日子,那么还有希望。
使用“-W”标志告诉 Perl 解释器打开警告开关(类似于 C 编译器中的“-Wall”选项,但是在运行时也被应用,而不只是在编译阶段被应用)。要那样做,您可以将“-w”开关添加到解释器调用行(在 UNIX 环境中通常是第一行脚本),而在其它环境中则添加一个命令行开关。一些程序员觉得只有开发人员才需要警告,而最终产品不应该再有警告。
首先,在编程领域中没有最终产品。正如 Jack Cohen 的一句名言所说:稳定即意味着死亡(a special word for stable is dead)。
第二,警告将通知用户可能有问题发生,应该预先采取行动。
第三,仅在开发周期使用警告就象使用真正的灭火器来进行防火演习而使用纸杯来扑灭真正的火一样。在产品开发周期中的任意时刻去除安全保障简直毫无意义。
“use strict”编译参数(请参阅“perldoc strict”和“perldoc perlmodlib”帮助页面)与 C 编译器的“-pedantic”开关有些类似。“perldoc strict”命令将告诉您有关该编译参数的更多信息。关于“use strict”编译参数最重要的事情是:它要求在使用变量之前定义所有变量。下例演示了“use strict”希望避免的情况:
清单 7.“use strict”要避免的错误$this_variable_name_is_too_long = 1;
while (
)
{
$this_variabel_name_is_too_long++;
}
在它成为“臭虫(bug)”被发现之前,人们永远不会注意到这个错误。而通过使用“use strict”,将永远不会编译上面的代码。相反,下面这段代码将必须写入:
清单 8. 清单 7 的修复版my $this_variable_name_is_too_long = 1;
while (
)
{
$this_variable_name_is_too_long++;
}
使用长变量名的至理名言其实就是 ... 算了 ... 我们要说:假设您要输入一个长度超过 15 个字符的变量名,那么您可能正在做错误的事。
不要使用函数来定义常量。虽然那曾经在 Perl 中很有必要,但是现在通过“use constant”编译参数来完成。在下例中,请注意注释的风格和箭头的对齐。这段代码看起来很舒服,这说明作者细心编排了每一行代码。一些编辑器(例如 Emacs)可以自动做这种对齐工作。
清单 9. 要定义约束的“use constant”use constant CHILDREN => 3; # 3 children per process
use constant PI => 3.14; # 2 digits of precision
use constant MESSAGE => "Hello"; # a message
在调用函数之前先制订函数原型。制订函数原型很容易。“perldoc perlsub”帮助页面将帮助您更好地理解原型。制订原型对于任何程序员来说都是个好习惯,因为它可以显示出预想(forethought)并在特定情况下帮助您避免编译器错误。在函数更改时更改原型可能令人乏味,但是这里的问题在于在一开始就没有定义好函数。在编码之先制订计划,在制订计划之前先思考。每隔 10 分钟思考一下代码将采取的途径将为您节省 1 小时的编码和调试时间。
Perl 自动在字符串和数字之间转换。不能也没有必要关闭这项特性,但是 Perl 程序员新手可能在几小时内就会产生出新的让人感到意外的错误。请阅读 Perl FAQ(请参阅 参考资料)、 Programming Perl
或
Learning Perl 书籍,当然还有“perldoc perldata”帮助页面(“Scalar values”部分)。
练习
- 编写一个从 1 数到 100 的循环,并且
- 打印所有偶数
- 打印所有奇数
- 打印所有以 1、2
或
7 结尾的数字
编写一个从 100 向后数到 1 的循环
编写一个程序,该程序读取标准输入并打印以“hello”(大写、小写或大小写混合均可)开始的所有行。提示:请查看“perldoc perlrun”帮助页面,尝试使用 Perl 的内置开关,而不要编写自己的循环。
编写一个逻辑条件,该条件将检查一个标量是否已定义、是否是非零、回文(向前读和向后读都一样)或者是数字 234.98。分别使用和不使用 Perl 解释器的“-w”标志和“use strict”编译器编译参数各测试一次。如果生成了警告信息,您是否理解这些警告信息?
以通常(不带后缀)的表示法编写以下代码,如果可以,则简化它们:
- print if $debug;
- $i++ unless $i > 10;
- unless ($j && !$i) { $j += $i while <> };
- next while <>;