perl语言编程 第三章 单目和双目操作符 下

上一篇 / 下一篇  2008-04-18 11:39:37 / 个人分类:关于脚本语言

3.12 相等操作符

相等操作符在表 3-6 里面列出,它们和关系操作符很象。

表3-6 相等操作符

数字字串含义
==eq等于
=ne不等于
<=>cmp比较,带符号结果

等于和不等于操作符为真时返回 1,为假时返回""(和关系操作符一样)。<=> 和 cmp 操作符在左操作数小于右操作数时返回 -1,相等时返回 0,而大于时返回 1。尽管相等操作符和关系操作符很象,但是它们的优先级比较低,因此 $a < $b <=> $c <$d 语法上是合法的。

因为很多人看过“星球大战”,<=>操作符也被称为“飞船”操作符。

3.13 位操作符

和 C 类似,Perl 也有位操作符 AND,OR,和 XOR(异或):&,| 和 ^。在本章开始的时候,你辛辛苦苦地检查表格,发现按位 AND (与)操作符比其他的优先级高,但我们那时候是骗你的,在这里我们会一并讨论一下。

这些操作符对数字值和对字串值的处理不同。(这是少数几个 Perl 关心的区别。)如果两个操作数都是数字(或者被当作数字使用),那么两个操作数都被转换成整数然后在两个整数之间进行位操作。我们保证这些整数是至少 32 位长,不过在某些机器上可以是 64 位长。主要是要知道有一个由机器的体系所施加的限制。

如果两个操作数都是字串(而且自从它们被设置以来还没有当作数字使用过),那么该操作符用两个字串里面来的位做位操作。这种情况下,没有任何字长限制,因为字串本身没有尺寸限制。如果一个字串比另一个长,Perl 就认为短的那个在尾部有足够的 0 以弥补区别。

比如,如果你 AND 两个字串:

"123.45" & "234.56"

你得到另外一个字串:

"020.44"

不过,如果你拿一个字串和一个数字 AND:

"123.45" & 234.56

那字串先转换成数字,得到:

  1. 45 & 234.56

然后数字转换成整数:

  1. & 234

最后得到值为 106。请注意所有位字串都是真(除非它们结果是字串“0”)。这意味着如果你想看看任意字节是否为非零,你不能这么干:

if ( "fred" & "\1\2\3\4" ) { ... }

你得这么干:

if( )"fred" & "\1\2\3\4") =~ /[^\0]/ ) { ... }

3.14 C 风格的逻辑(短路)操作符

和 C 类似,Perl 提供 &&(逻辑 AND)和 ||(逻辑 OR)操作符。它们从左向右计算( && 比 || 的优先级稍稍高一点点),测试语句的真假。这些操作符被认为是短路操作符,因为它们是通过计算尽可能少的操作数来判断语句的真假。例如,如果一个 && 操作符的左操作数是假,那么它永远不会计算右操作数,因为操作符的结果就是假,不管右操作数的值是什么。

例子名称结果
$a && $bAnd如果$a为假则为$a,否则$b
$a || $bOr如果$a为真则为$a,否则$b

这样的短路不仅节约时间,而且还常常用于控制计算的流向。比如,一个经常出现的 Perl 程序的俗语是:

open(FILE, "somefile") || die "Can't open somefile: $!\n";

在这个例子里,Perl 首先计算 open 函数,如果值是真(somefile 被成功打开),die 函数的执行就不必要了,因此忽略。你可以这么读这句文本“打开文件,要不然就去死!”。

&& 和 || 操作符和 C 不同的是,它们不返回 0 或 1,而是返回最后计算的值。如果是 ||,这个特性好就好在你可以从一系列标量数值中选出第一个为真的值。所以,一个移植性相当好的寻找用户的家目录的方法可能是:

$home = $ENV{HOME}
   || $ENV{LOGDIR}
   || (getpwuid($<)) [7]
   || die "You're homeless!\n";
另一方面,因为左参数总是在标量环境里计算,所以你不能把 || 用于在两个集群之间选择其一进行赋值:
@a = @b || @c;      # 这样可不对
   @a = scalar(@b) || @c;   # 上面那句实际上是这个意思,@a 里只有 @b 最后的元素
   @a = @b ? @b : @c;   # 这个是对的
Perl 还提供优先级比较低的 and 和 or 操作符,这样程序的可读性更好而且不会强迫你在列表操作符上使用圆括弧。它们也是短路的。参阅表 1-1 获取完整列表。

3.15 范围操作符

范围操作符 .. 根据环境的不同实际上是两种不同的操作符。

在标量环境里,.. 返回一个布尔值。该操作符是双稳定的,类似一个电子开关,并且它仿真 sed,awk,和各种编辑器的行范围(逗号)操作符。每个 .. 操作符都维护自身的状态。只要它的左操作数为假就一直为假。一旦左操作数为真,该范围操作符就保持真的状态直到右操作数为真,右操作数为真之后该范围操作符再次为假。该操作符在下次计算之前不会变成假。它可以测试右操作数并且在右操作数变真后在同一次计算中变成假(awk 的范围操作符的特性),不过它还是会返回一次真。如果你不想拖到下一次计算中才测试右操作数(也是 sed 的范围操作符的工作方式),只需要用三个点(...)代替两个点(..)。对于 .. 和 ...,当操作符处于假状态后就不再测试右操作数,而当操作符处于真状态后就不再测试左操作数。

返回的值要么是代表假的空字串或者是代表真的一个序列数(从 1 开始)。该序列数每次碰到新范围时重置。在一个范围里的最后序列数后面附加了字串“E0”,这个字串不影响它的数字值,只是给你一些东西让你可以搜索,这样你可以把终点排除在外。你也可以通过等待 1 的序列数的方法把启始点排除在外。如果标量 .. 的某个操作数是数字文本,那么该操作数隐含地与 $.变量对比,$. 里包含你的输入文件的当前行号。比如:

if(101 .. 200) {print;}    # 打印第二个一百行
   next line if( 1.. /^$/);   # 忽略开头行
   s/^/> / if (/^$/ .. eof());   # 引起体
在列表环境里, .. 返回一列从左值到右值计数(以一)的数值。这样对书写 (1 .. 10) 循环和数组片段的操作很有帮助:
for (101 .. 200) {print;}      # 打印 101102 。。。 199200
   @foo = @foo[0 .. $#foo];      # 一个昂贵的空操作
   @foo = @foo[ -5 .. -1];         # 最后5个元素的片段
如果左边的值大于右边的值,则返回一个空列表。(要产生一列反序的列表,参阅 reverse 操作符。)

如果操作数是字串,范围操作符利用早先讨论过的自增处理。(注:如果在所声明的终值不是自增处理中产生的序列中的数,那么该序列将继续增加直到下一个值比声明的终值长为止。)因此你可以说:

@alphabet = ('A' .. 'Z');
以获得所有英文字母,或者:
$hexdigit = (0 .. 9, 'a' .. 'f')[$num & 15];
获得一个十六进制位,或者:
@z2 = ('01' .. '31'); print $z2[$mday];
获得带有前导零的日期。你还可以说:
@combos = ('aa' .. 'zz');
获取所有两个小写字符的组合。不过,用下面的语句要小心:
@bigcombos = ('aaaaaa' .. 'zzzzzz');
因为这条语句要消耗很多内存。准确地说,它需要存储 308,915,776 个标量的空间。希望你分配了一个非常大的交换分区。可能你会考虑用循环代替它。

3.16 条件操作符

和 C 里一样,?: 是唯一的一个三目操作符。它通常被成为条件操作符,因为它运转起来非常象一个 if-then-else,而且,因为它是一个表达式而不是一个语句,所以它可以非常容易地嵌入其他表达式和函数调用中。作为三目操作符,它的两个部分分隔了三个表达式:

COND ? THEN : ELSE

如果条件 COND 为真,那么只计算 THEN 表达式,并且其值成为整个表达式的值。否则,只计算 ELSE 表达式的值,并且其值成为整个表达式的值。

不管选择了哪个参数,标量或者列表环境都传播到该参数。(第一个参数总是处于标量环境,因为它是一个条件。)

$a = $ok ? $b :$c;   # 得到一个标量
   @a = $ok ? @b : @c;   # 得到一个数组
   $a = $ok ? @b :@C;   # 得到一个数组元素的计数
你会经常看到条件操作符嵌入在一列数值中以格式化 printf,因为没人愿意只是为了在两个相关的值之间切换而复制整条语句。
printf "I have $d camel$.\n",
      $n,   $n == 1 ? "" : "s";
?: 的优先级比逗号高,但是比你可能用到的其他大多数操作符(比如本例中的 ==)都低,因此通常你用不着用圆括弧括任何东西。不过如果你愿意,你可以用圆括弧让语句更清晰。对于嵌套在其他 THEN 部分的其他条件操作符,我们建议你在它们中间放入断行和缩进,就好象它们是普通 if 语句一样:
$leapyear = 
      $year % 4 == 0
         ? $year % 100 == 0
            ? $year % 400 == 0
               ? 1
               :0
            :1
      :0;
类似地,对于嵌套在 ELSE 部分的更早的条件,你也可以这样处理:
$leapyear = 
      $year % 4
         ? 0
         : $year %100
            ? 1
            : $year % 400
            ? 0
            :1;
不过通常最好把所有 COND 和 THEN 部分垂直排列:
$leapyear =
      $year % 4?0:
      $year % 100 ? 1:
      $year % 400 ? 0:1
把问号和冒号对齐可以让你在非常混乱的结构中找到感觉:
printf "Yes, I like my %s book!\n",
      $i18n eq "french"   ? "chameau"   :
      $i18n eq "german"   ? "Kamel"   :
      $i18n eq "japanese"   ? "\x{99F1}\x{99DD}"   :
                  "camel"
如果第二个和第三个参数都是合法的左值(也就是说你可以给他们赋值),而且同时为标量或者列表(否则,Perl 就不知道应该给赋值的右边什么环境),那你就可以给它赋值(注:这样无法保证你的程序有很好的可读性。但是这种格式可以用于创建一些很酷的东西。):
($a_or_b ? $a : $b) = $c;      # 把 $a 或 $b 置为 $c 的值
请注意,条件操作符比各种各样赋值操作符绑定得更紧。通常这是你所希望的(比如说上面 $leapyear 赋值),但如果你不用圆括弧的话,你就没法让它反过来。不带圆括弧(在条件操作符)中使用嵌入的赋值会给你带来麻烦,并且这时还不会有分析错误,因为条件操作符左值分析。比如说,你可能写下面这些:
$a %2 ? $a += 10 : $a += 2      # 错
上面这个会分析为:
(($a % 2) ? ($a += 10) : $a) += 2

3.16 赋值操作符

Perl 可以识别 C 的赋值操作符,还提供了几个自己的。这里是一些:

=    **=    +=    *=    &=    <<=    &&=
            -=    /=    |=    >>=    ||=
            .=    %=    ^=
                  x=
每个操作符需要一个目标左值(典型值是一个变量或数组元素)在左边以及一个表达式在右边。对于简单赋值操作符:
TARGET = EXPR
EXPR 的值被存储到 TARGET 指明的变量或者位置里面。对其他操作符而言, Perl 计算表达式:
TARGET P= EXPR
就好象它是这么写的一样:
TARGET = TARGET OP EXPR
这是有利于简便的规则,不过它在两个方面会误导我们。首先,赋值操作符总是以普通赋值的优先级进行分析的,不管 OP 本身的优先级是什么。第二,TARGET 只计算一次。通常这样做没有什么问题,除非存在副作用,比如一个自增:
$var[$a++] += $value;         # $a增加了一次
   $var[$a++] = $var[$a++] + $value;   # $a增加了两次
不象 C 里,赋值操作符生成一个有效的左值。更改一个赋值等效于先赋值然后把变量修改为赋的值。这招对修改一些东西的拷贝很管用,象这样:
($tmp = $global) += $constant;

等效于:

   $tmp = $global + $constant;

类似:

   ($a += 2) *= 3;

等效于:
   
   $a += 2;
   $a *= 3;
本招并非绝招,不过下面是你经常看到的习惯用语:
($new = $old) =~ s/foo/bar/g;
无论什么情况,赋值的值是该变量的新值。因为赋值操作符从右向左结合,所以这一点可以用于给多个变量赋同一个值,比如:
$a = $b = $c =0;
把 0 赋予 $c,其结果(还是 0)给 $b,其结果(依然为 0)再给 $a。

列表赋值可能只能在列表环境里用简单赋值操作符,=.,赋值。列表赋值返回该列新值,就象标量赋值一样。在标量环境里,列表赋值返回赋值右边可获得的值的数目,就象我们在第二章,集腋成裘,里谈到的一样。我们可以利用这个特性测试一个失败(或者不再成功)时返回空列表的函数,比如:

while (($key, $value) = each %gloss) { ... }
   
   next unless ($dev, $ino, $mode) = stat $file;

3.18 逗号操作符

双目“,”是逗号操作符。在标量环境里它先在空环境里计算它的左参数,把那个值抛弃,然后在标量环境里计算它的右参数并且返回之。就象 C 的逗号操作符一样。比如:

$a = (1,3);

把 3 赋予 $a。不要混淆标量环境用法和列表环境用法。在列表环境里,逗号只是列表参数的分隔符,而且把它的两个参数都插入到列表中。并不抛弃任何数值。

比如,如果你把前面例子改为:

@a = (1,3);

你是在构造一个两元素的列表,而:

atan2(1,3);

是用两个参数调用函数atan2。

连字符 => 大多数时候就是逗号操作符的同义词。对那些成对出现的文档参数很有用。它还强制把它紧左边的标识符解释为一个字串。

3.19 列表操作符(右向)

列表操作符的右边是所有列表操作符的参数(用逗号分隔的),所以如果你往右看的话,列表操作符的优先级比逗号低,一旦列表操作符开始啃那些逗号分隔的参数,你能让它停下来的唯一办法就是停止整个表达式的记号(比如分号或语句修改器),或者停止当前子表达式的记号(比如右圆括弧或花括弧),或者是我们下面要讲的低优先级的逻辑操作符。

3.20 逻辑与,或,非和异或

作为 &&,|| 和 ! 的低优先级候补,Perl 提供了 and,or,和 not 操作符。这些操作符的性质和它们对应的短路操作符是一样的,尤其是 and 和 or,这样它们不仅可以用于逻辑表达式也可以用于流控制。

因为这些操作符的优先级远远比从 C 借来的那些低,所以你可以很放心地把它们用在一个列表操作符后面而不用加圆括弧:

unlink "alpha", "beta", "gamma"
      or gripe(), next LINE;
而对 C 风格的操作符你就得这么写:
unlink("alpha", "beat", "gamma") || (girpe(), next LINE);
不过你不能简单地把所有 || 替换为 or。假设你把:
$xyz = $x || $y || $z;

改成:

   $xyz = $x or $y or $z;   # 错
这两句是完全不同的!赋值的优先级比 or 高但是比 || 低,所以它总是会给 $xyz 赋值 $x,然后再进行 or。要获得和 || 一样的效果,你就得写:
$xyz = ( $x or $y or $z );
问题的实质是你仍然必须学习优先级(或使用圆括弧),不管你用的是哪种逻辑操作符。

还有一个逻辑 xor 操作符在 C 或 Perl 里面没有很准确的对应,因为另外一个异或操作符(^)按位操作。xor 操作符不能短路,因为两边都必须计算。$a xor $b 的最好的等效可能是 !$a = !$b。当然你也可以写 !$a ^ !$b,甚至可以是 $a ? !$b : !!$b。要点是 $a 和 $b 必须在布尔环境里计算出真或假,而现有的位操作符在没有帮助的前提下并不提供布尔环境。

3.21 Perl 里没有的 C 操作符

下面是 Perl 里没有的 C 操作符:

单目 & 取址操作符。不过,Perl 的 \ 操作符(用于使用引用)填补了这个生态空白:

$ref_to_var = \$var;

不过 Perl 的引用要远比 C 的指针更安全。

单目 * 解取址操作符。因为 Perl 没有地址,所以它不需要解取址。它有引用,因此 Perl 的变量前缀字符用做截取址操作符,并且还标明类型:$,@,%,和 &。不过, 有趣的是实际上有一个 * 解引用操作符,但因为 * 是表示一个类型团的趣味字符, 所以你无法将其用于同一目的。 (类型) 类型转换操作符。没人喜欢类型转换。


TAG:

 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

日历

« 2008-07-06  
  12345
6789101112
13141516171819
20212223242526
2728293031  

数据统计

  • 访问量: 2769
  • 日志数: 555
  • 建立时间: 2008-01-07
  • 更新时间: 2008-06-24

RSS订阅

Open Toolbar