既然我们有 qr// 操作符,那么上面的编译完的运行时 eval 看上去没有必要。下面的做相同的事情:
@pats = ();
foreach $word (@words) {
push @pats, qr/\b${word}\b/i;
}
@ARGV = @files;
undef $/; # 吃进每个完整的文件
while (<>) {
for $pat (@pats) {
$seen{$ARGV}++ if /$pat/;
}
}
$/ = "\n"; # 恢复正常的输入终止符
foreach $file (sort keys(%seen)) {
print "$file\n";
}
命名声明
sub NAME PROTO ATTRS
sub NAME ATTRS
sub NAME PROTO
sub NAME
命名定义*sub NAME PROTO ATTRS BLOCK
sub NAME ATTRS BLOCK
sub NAME PROTO BLOCK
sub NAME BLOCK
未命名定义
sub PROTO ATTRS BLOCK
sub ATTRS BLOCK
sub PROTO BLOCK
sub BLOCK
子过程声明和定义的语法看起来挺复杂的,但是在实践上实际相当简单。所有东西都是基于下面语法的:
sub NAME PROTO ATTRS BLOCK
所有的四个域都是可选的;唯一的限制就是如果这些域的确存在的话那么它们必须以这些顺序出现,并且你必须至少使用 NAME 或者 BLOCK 之一。目前,我们会忽略 PROTO 和 ATTRS;它们只是基本语法的修饰词。NAME 和 BLOCK 都是保证正确重要部分:
如果你只有 NAME 而没有 BLOCK,它就是一个该名字的声明(并且如果你想调用该子过程,那么你就必须稍后用 NAME 和 BLOCK 提供一个定义。)命名的声明是非常有用的,因为如果编译器知道它是一个用户定义子过程,那么它会对该名字另眼相看。你可以把这样的子过程当作一个函数或者当作一个操作符来调用,就象内建函数一样。有时候我们把这样的东西叫做提前声明。
如果你同时提供了 NAME 和 BLOCK,那么它就是一个标准的命名子过程定义(如果你在前面没有声明,那么它还是声明)。命名定义也很重要,因为 BLOCK 把一个实际的含义(子过程体)和声明关联起来。这就是我们所谓的定义和声明的区别。不过,定义和声明也有类似的地方,那就是子过程代码看不到它,并且它不返回你可以用之来引用子过程的内联的值。
如果你只有 BLOCK 而没有 NAME,那么它就是一个匿名的定义,也就是一个匿名子过程。因为它没有名字,所以它根本就不是声明,而是一个真正的操作符,在运行时返回一个指向匿名子过程体的引用。这个东西在把代码当作数据对待的时候极为有用。它允许你传递一段奇怪的代码用做回调函数,并且甚至还可以当作闭合块用——如果该 sub 定义操作符提到了任何在其自身以被摧毁了也如此。
在上面三种情况中的任何一种里,PROTO 和 ATTRS 之一或者全部都可以在 NAME 之后和/或 BLOCK 之前出现。原型是一个放在圆括弧里的字符列表,它告诉分析器如何对待该函数的参数。属性是用一个冒号引入的,它告诉分析器有关这个函数的额外的信息。下面是一个包含四个域的典型的定义:
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-28372-15.html
完全隔绝空气