2009年5月7日星期四

笔记:Programming Perl (Perl语言编程)

见:第二章 集腋成裘 译者:何伟平

Perl预先在最先结束的块里面查找,看看该变量是否有用my(或者our)定义在该代码块里(参考那些第二十九章的内容和第四章,语句和声明,里面的范围声明节)。如果存在my定义,那么该变量是词法范围内的而不存在于任何包里--它只存在于那个词法范围(也就是在该代码块的暂时缓存器里)。因为词法范围是非命名的,在那块程序之外的任何人甚至都看不到你的变量。(注:如果你用的是our定义而非my,这样只是给一个包变量定义了一个词法范围的别名(外号),而不象my定义里面真正地定义了一个词法范围变量。代码外部仍然可以通过变量的包获取其值,但除此以外,our定义和my定义的特点是一样的。如果你想在和 use strict一起用(参阅第三十一章里的strict progma),限制自己的全局变量,那时很有用。不过如果你不需要全局变量时你应该优先使用my。

不要试图在这里用 defined @files。那样没用,因为 defined 函数是询问一个标量是否为 undef,而一个数组不是标量。简单的布尔测试 if(@file) 就够用了。

在标量环境里,列表文本并不真正表现得象一个列表(LIST),因为它并没有给它的值提供列表环境。相反,它只是在标量环境里计算它的每个参数,并且返回最后一个元素的值。这是因为它实际上就是伪装的 C 逗号操作符,逗号操作符是一个双目操作符,它会丢弃左边的值并且返回右边的值。用我们前面讨论过的术语来说,逗号操作符的左边实际上提供了一个空环境。因为逗号操作符是左关联的,如果你有一系列逗号分隔的数值,那你总是得到最后一个数值,因为最后一个逗号会丢弃任何前面逗号生成的东西。因此,要比较这两种环境,列表赋值:
  @stuff = ( "one", "two", "three");
给数组@stuff,赋予了整个列表的值。但是标量赋值:
  $stuff = ( "one", "two", "three");
只是把值 "three" 赋予了变量 $stuff。和我们早先提到的 @files 数组一样,逗号操作符知道它是处于标量还是列表环境,并且根据相应环境选择其动作。

你甚至可以给空列表赋值:
  () = funkshun();
这样会导致在列表环境里调用你的函数,但是把返回值丢弃。如果你在没有赋值(语句)的情况下调用了此函数,那它就会在一个空环境里被调用,而空环境是标量环境,因此可能令此函数的行为完全不同。

截断一个数组并不回收其内存。你必须 undef(@whatever) 来把它的内存释放回你的进程的内存池里。你可能无法把它释放回你的系统的内存池,因为几乎没有那种操作系统支持这样做。

请不要把多维散列仿真和片段混淆起来。前者表示一个标量数值,后者则是一个列表数值:
  $hash{ $x, $y, $z}      # 单个数值
  @hash{ $x, $y, $z}      # 一个三个值的片段

typeglob 所有这些都只影响全局(包)变量;词法不能通过符号表记录访问。象这样给全局变量别名看起来可能有点愚蠢,不过事实是整个模块的输入/输出机制都是建筑在这个特性上的,因为没有人要求你正在当别名用的符号必须在你的名字空间里。因此:
   local *Here::blue = \$There::green;
   
临时为 $There::green 做了一个叫 $Here::blue 的别名,但是不要给 @There:green 做一个叫 @Here::blue 的别名,或者给 %There::green 做一个 %Here::blue 的别名。

$info = `finger $user`; 的数字状态值保存在 $?(参阅第二十八章获取 $? 的解释,也被称为 $CHILD_ERROR )。
命令输入操作符``相当于qx//,如果你碰巧选择了单引号做你的分隔符,那命令行就不会进行双引号代换;
  $perl_info   = qx(ps $$);      # 这里 $$ 是 Perl 的处理对象
  $perl_info   = qx'ps $$';      # 这里 $$ 是 shell 的处理对象
 
 
 第三章 单目和双目操作符 译者:何伟平 
如 果有哪个列表操作符(如 print)或者哪个命名单目操作符(比如 chdir)后面跟着左圆括弧做为下一个记号(忽略空白),那么该操作符和它的用圆括弧包围的参数就获得最高优先级,就好象它是一个普通的函数调用一样。 规则是:如果看上去象函数调用,它就是函数调用。你可以把它做得不象函数--在圆括弧前面加一个单目加号操作符即可,(从语意上来说,这个加号什么都没 干,它甚至连参数是否数字都不关心)。
因为 * 的优先级比 chdir 高,我们有:
?
1
2
3
4
chdir $foo * 20;   # chdir ($foo * 20)
chdir($foo) * 20;   # (chdir $foo) * 20
chdir ($foo) * 20;   # (chidir $foo) * 20
chdir +($foo) * 20    # chdir ($foo * 20 )
最容易出错的地方是你用圆括弧把数学参数组合起来的时候,但是你却忘记了圆括弧同时用于组合函数参数:
?
1
print ($foo & 255) + 1, "\n";   # 打印($foo & 255)
这种情况对任何命名单目操作符的数字操作符同样有效。

一个列表操作符试图收集它后面所有的参数,然后当做一个简单的项和它前面的表达式放在一起。
但你还是要注意圆括弧的使用:
?
1
2
3
4
5
6
7
8
# 这些在进行print前退出
print($foo, exit);   # 显然不是你想要的。
print $foo, exit;   # 也不是这个
 
# 这些在退出前打印:
(print $foo), exit;   # 这个是你要的。
print($foo), exit;   # 或者这个。
print ($foo), exit;    # 这个也行。

双目 ** 是指数操作符。请注意它甚至比单目操作符的绑定更严格,所以 -2**4 是-(2**4),不是 (-2)**4。

在列表环境里,如果左操作数是在圆括弧中的列表,x 的作用是一个列表复制器,而不是字串复制器。这个功能对初始化一个长度不定的数组的所有值为同一值时很有用:
?
1
2
@ones = (1) x80;      # 一个80个1的列表
@ones = (5) x @ones;      # 把所有元素设置为5

单目操作符比某些双目操作符的优先级高。比如:
?
1
sleep 4 | 3;
并不是睡 7 秒钟;它先睡 4 秒钟然后把 sleep 的返回值(典型的是零)与 3 进行按位或操作,就好象该操作符带这样的圆括弧:
?
1
(sleep 4) | 3;
与下面相比:
?
1
print 4 | 3;
上面这句先拿 4 和 3 进行或操作,然后再打印之(本例中是 7),就好象是下面这样写的一样:
?
1
print (4 | 3);
这是因为 print 是一个列表操作符,而不是一个简单的单目操作符。一旦你知道了哪个操作符是列表操作符,你再把单目操作符和列表操作符区分开就不再困难了。当你觉得有问题 时,你总是可以用圆括弧把一个命名的单目操作符转换成函数。记住:如果看起来象函数,那它就是函数。

表3-4 文件测试操作符


第四章 语句和声明 译者:何伟平

?
1
2
3
4
5
6
WID: foreach $this (@ary1) {
   JET: foreach $that (@ary2) {
      next WID if $this > $that;
      $this += $that;
   }
}

一个 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;

你可以用原型 ($) 把子过程转化成单目操作符:
?
1
2
sub myname ($);  
$me = myname $0      || die "can't get myname";
这样就会按照你想象的那样分析了,不过你还是应该养成在这种情况下用 or 的习惯。

就 my 和 our 而言,元素只能是简单的标量,数组或者散列变量。就 local 而言,其构造可以更宽松:你还可以局部化整个类型团和独立的变量或者数组和散列的片段:
?
1
2
3
my($nose, @eyes, %teeth);
our ($House, @Autos, %Kids);
local (*Spouse, $phone{HOME});


第五章 模式匹配 译者:何伟平

/s 令 . 匹配换行符并且忽略不建议使用的 $* 变量
/m 令 ^ 和 $ 匹配下一个嵌入的 \n。
/s 和 /m 修饰词并不涉及任何古怪的东西。它们只是影响 Perl 对待那些包含换行符的匹配的态度。不过它们和你的字串是否包含换行符无关;它们关心的是 Perl 是否应该假设你的字串包含单个行(/s)还是多个行(/m),因为有些元字符根据你是否需要让它们工作于面向行的模式而有不同的行为。

/cg 在 /g 匹配失败后允许继续查找,
如果除了用 /g,你还用了 /c(表示"连续")修饰词,那么当 /g 运行结束后,失败的匹配不会重置位置指针。

如 果分隔符是 ?,就象 ?PATTERN?,那么运行起来和 /PATTERN/ 搜索一样,区别是它在两次 reset 操作符调用之间只匹配一次。如果你只想匹配程序运行中模式出现的第一次,而不是所有的出现,那么这是一个很方便的优化方法。你每次调用此操作符时都会运行 搜索,直到它最终匹配了什么东西,然后它就关闭自身,在你明确地用 reset 把它重置之前它一直返回假。Perl 替你跟踪这个匹配状态。
当一个普通模式匹配想找出最后一个匹配而不是第一个,那么 ?? 操作符很好用:
?
1
2
3
4
5
6
7
open DICT, "/usr/dict/words" or die "Can't open words: $!\n";
while (<DICT>) {
   $first = $1 if ?(^neur.*)?;
   $last = $1 if /(^neur.*)/;
}
print $first, "\n";      # 打印"neurad"
print $last, "\n";      # 打印 "neurypnology"
在调用 reset 操作符时,reset 只重置那些编译进同一个包的 ?? 记录。你说 m?? 的时候等效于说 ??。

?
1
2
3
tr/a-z//s;  # bookkeeper -> bokeper
tr/@%//d;   # x%y@ -> xy
tr/@%//cd;  # x%y@ -> %@

表 5-6 扩展的正则序列 (?=...)之类
表 5-7 字母数字正则元符号 \d之类
表5-9 组合 Unicode 属性 \p{IsDigit}之类

 

没有评论:

发表评论