前幾天寫一個perl的指令碼 在:?運算子上遇到了一個很詭異的問題
$data->{$id}->{'total'} ?
$data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) :
$data->{$id}->{'ratio'} = 'N/A';
我的本意是 如果 $data->{$id}->{'total'} 未定義則不計算ratio,把ratio賦值為N/A. 這條語句等同於
if ( $data->{$id}->{'total'} ) {
$data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'});
} else {
$data->{$id}->{'ratio'} = 'N/A';
可奇怪的是,當無論total是否有定義 ratio的結果居然都是N/A. 可後面if……else……的語句是沒有問題的,真的是讓我百思不得其解.
跑去查Perl的文檔, 其中對於?:的運算子號的解釋是
Ternary ``?:'' is the conditional operator, just as in C. It works much like an if-then-else. If the argument before the ? is true, the argument before the : is returned, otherwise the argument after the : is returned.
貌似是return the argument, 於是乎 突然有了一個想法, 在前後都加上了括弧……
$data->{$id}->{'total'} ?
( $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) ) :
( $data->{$id}->{'ratio'} = 'N/A' );
……居然就對了. 既然是return the argument, 我就又換了一種方式:
$data->{$id}->{'ratio'} = $data->{$id}->{'total'} ?
sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) :
'N/A';
雖然後面兩種方式都可以理解, 那確實是一種正確的做法. 但為什麼第一種方式的結果不對呢? 難道return the argument的意思就是不要在那裡做賦值運算嗎?
為了測試, 我又寫了一個簡單的小程式
1 #!/usr/bin/perl
2
3 use strict;
4
5 my $total = 1;
6 my $rval;
7
8 $total ?
9 $rval = $total :
10 $rval = 'N/A';
11
12 print $rval, "/n";
13
14 if ($total) {
15 $rval = $total;
16 } else {
17 $rval = 'N/A';
18 }
19
20 print $rval;
啟動並執行結果顯示, 無論第5行給$total賦什麼值……包括1, "abc", "true", undef 等,執行的結果第一個print列印出來的都是N/A. 難道 $total? 不等價於 if ($total) 嗎?
很偶然的情況下,發現了一個方法來驗證perl在內部是怎麼解析這些語句的方法:
perl -MO=Deparse,-p -e '$a ? $b=1 : $b=2;'
輸出的結果很是出乎我的意料:
(($a ? ($b = 1) : $b) = 2);
現在情況很明顯了,原來是運算子優先順序的問題!Perl語言中,三元操作符:?優先順序比賦值=要高,C語言裡面=的優先順序比?:高,就不會出現這樣的問題。這幾乎是一個超出常識和習慣和特性,也是一個很容易陷入的陷阱。
解決的辦法也很簡單,寫成 $a ? ($b=1) : ($b=2); 就好了。
這也進一步印證了那條編程法則: 在複雜的操作符運算的時候,用括弧來標識出各個運算的優先順序,即使你認為你瞭解運算的順序。