1. What does this do?
$foo = $foo[1]
将数组 @foo 的第2个元素赋给标量 $foo
2. What does this print?
@a = ();
$h{'a'} = 'b';
push @a, %h;
print "@a";
push 的参数除了第 1 个以外都处于列表环境当中,而散列在列表环境中会将其中每个键值对拆开,组合成一个大列表。因此
push @a, %h;
就相当于
push @a, 'a', 'b';
最后经过内插以后的输出结果就是
a b
需要注意的是散列转化为列表时,每个键值对的位置是不固定的,这里只有一个键值对看不出来。如果散列变成
%h = ('a' => 'b', 'c' => 'd');
那么“@a = %h”的结果可能是以下两种中的某一个
@a = ('a', 'b', 'c', 'd');
@a = ('c', 'd', 'a', 'b');
3. In this code
@x = ('a', 'b', 'c');
$y = scalar ('a', 'b', 'c');
$z = scalar @x;
what are $y and $z set to?
“('a', 'b', 'c')”是列表,“@x”是数组,二者在标量环境下求值的结果是不同的:列表在标量环境下求值得到的是最后一个元素,数组在标量环境下求值则得到数组本身的长度。因此上面 $y 赋值得到 'c',而 $z 赋值得到 3。在赋值操作中,赋值号右边内容所处的环境完全由赋值号左边的内容决定,如果赋值号左边是数组、散列或列表,则右边就处于列表环境中,若左边是标量则右边也处于标量环境中。这里两个赋值操作的左边都是标量,因此右边的 scalar 操作实际上没有必要,去掉该操作以后结果不会改变。
4. What does this do?
$c = 'foo';
$a = "$c=/'///'/n";
$b = '$c=/'///'/n';
双引号中的所有转义序列都有效,且变量会进行内插;单引号中的有效转义序列仅有“/'”和“//”,不进行内插操作。如此结论就很明显了。
5. What does this do?
while (<STDIN>) {
chomp;
unless ($_) {
print "empty line/n";
}
}
Perl 中在布尔环境下求值为假的量只有未定义值 undef、空字符串 ''、数值 0 和字符串 '0' 这几个,其余的量在布尔环境中都是真值,因此这里的 unless 语句不只挑出了所有空行,还把只含有一个字符 0 的行也挑出来了,达不到原来的目的。
6. Characterize the possible outcomes of this code
@s = sort foo (1, 4, 3, 2);
assuming that foo is a user-defined subroutine.
题目原来想表现的问题是自定义比较函数返回结果不一致的时候 (比如头一次结果 1 大于 2,下一次结果变成 1 小于 2,这就是比较结果不一致),sort 可能会出错乃至崩溃。所幸的是这个问题只在 Perl 5.004之前的版本有,后来版本的 sort 对这个情况已经不敏感了 (不敏感的意思就是在这种情况下排序的结果不一定对,但是肯定不会出错)。其实函数 foo 的定义方式也值得说明一下,当自定义比较函数没有函数原型时,sort 向比较函数传递参数是通过和函数处于同一名字空间的全局变量 $a 和 $b 进行的;而当函数具有 $$ 这样的原型时,sort 通过比较函数的参数数组 @_ 传递参数,需要注意的是原型只要不是 $$,就会按照没有函数原型的情况传递参数。用有原型的比较函数排序一般会比没有原型的时候慢一些。此外,解释器对 sort 的语法解析太要命:
sort func (1,2,3,4); # 对列表 1,2,3,4 使用比较函数 func 进行排序
sort func 1,2,3,4; # 和上面一样,很正常
sort func(1,2,3,4); # 和上面一样!!
sort (func 1,2,3,4); # 和上面一样!!
sort (func (1,2,3,4)); # 和上面一样!!
sort (func(1,2,3,4)); # 对 func(1,2,3,4) 返回的列表进行默认排序!!
sort ((func(1,2,3,4))); # 和上面一样,很正常
sort ((func (1,2,3,4)));# 语法错误!!
func 有没有原型似乎不会影响对 sort 参数的解释,也就是说就算你事先定义过 sub func($$$$) 或者 sub func(@),也绝对不会改变上面列出来的结果。因此为了尽量少给自己找麻烦,大家最好还是用尽简单的 sort 形式,千万别在没把握的情况下随意把多行代码合并到 sort 参数里去。
7. What does this do?
$[ = 42;
唔,没什么好说的,查 perldoc perlvar
8. What does this do?
$x = (sort => (4,8,6));
$y = sort => (4,8,6) ;
“=>”和逗号等价,只是编译时会把它左边的参数当作字符串处理。换个马甲改变不了本质,它的优先级和逗号一样低。因此题目中的代码相当于以下形式:
$x = ("sort", 4, 8, 6);
($y = "sort"), 4, 8, 6;
前一个语句把列表赋给标量,标量得到的是列表的最后一个元素,因此 $x = 6;后一个语句把字符串赋给标量,然后赋值结果也就是字符串“sort”和剩余的值组成一个列表,在空环境中被丢弃,最后 $y = "sort"。
9. What does this do?
print (1+2)+3;
print +(1+2)+3;
经典问题,在 Perl 中看起来“像”函数调用的语句就被处理成函数调用,给上面两行加上括号:
(print (1+2))+3;
print (+(1+2)+3);
结果就很清楚了,最后输出“36”。
10. What does this do?
$mt = stat($file)[8];
assuming that $file is a valid file handle?
$file 再有效也改变不了语法错误的命运,列表环境的括号不能省,应该改成 (stat($file))[8]。
11. What will this Perl regular expression match?
/$foo[bar]/
Characterize all of the possibilities.
这是典型的变量内插产生二义性的例子,题目中的表达式可以被解释为 ${foo}[bar] 或者 ${foo[bar]},即解释为标量 $foo 的内插或者数组元素 $foo[bar] 的内插,但明显后一种在这里解释不通,因为字符串“bar”并不能作为数组的下标,所以 Perl 只有使用前一种解释。但当“[]”中的字符串看起来像常数、变量或者内置关键字的时候就是另一回事了,如:
/$foo[length]/ 解释为/${foo[length($_)]}/
/$foo[01]/ 解释为/${foo[1]}/
/$foo[$x]/ 解释为/${foo[$x]}/
而且即使被解释为下标的变量不存在,正则表达式引擎仍然会尝试访问,这样在打开 strict 的时候会产生变量未声明的错误。为了避免不必要的歧义,使用正则表达式的时候最好不要把内插变量放在字符集合之前。
12. Given this subroutine
sub somefunc {
keys %somehash, 0;
}
and assuming that %somehash is defined, what is the value of somefunc()?
Perl 里的函数返回值所处的环境是和调用函数时的环境一样的,因此如果调用形式是
@array = somefunc;
那么“keys %somehash, 0”就处于列表环境之中,会把 %somehash 的所有键和 0 组成一个列表返回。然而调用形式如果是
$var = somefunc;
它们所处的环境就相应地变成标量环境,而将生成的列表强制转变为标量,也就是返回列表的最后一个值“0”。
13. What does this do?
$SIG{PIPE} = handler;
别忘了函数引用是 /&func 而不是直接用 func,题目中的那个语句是先调用函数 handler,再将其返回值赋给 $SIG{PIPE},这和预期的修改自定义信号处理函数这一功能可是差了十万八千里 (当然,如果你说 handler 本来就要返回一个函数引用,这个语句大概就没什么问题了)。
14. How might this expression be evaluated?
x +2
Enumerate all of the possibilities.
该表达式如何求值主要看 x 是否定义为函数以及函数是否存在原型。求值的结果可能是:
"x"+2 没有任何关于 x 的定义,因此将其视为裸字,转换为字符串 (前提是没有打开 strict) 参与计算,最终得到数值 2。
x(+2) x 定义为没有原型的函数,产生函数调用。
x(+2) x 定义为带有 $、@、% 或者其他接受列表参数原型的函数,产生函数调用。
x()+2 x 定义为带有空原型的函数,产生无参数函数调用并将结果同 2 进行计算。
x 定义为具有其他原型的函数时,将由于参数和原型不匹配而产生语法错误。
15. What does this do?
@foo[1] = <STDIN>;
只要变量前面的符号是 @,那就意味着产生了一个数组,即使下标只有一个也一样。给数组赋值时会形成列表环境,而文件读取操作符 <> 在列表环境中的行为是按行读入所有文件内容,每行形成一个列表元素。这一列表赋给数组切片 @foo[1] 时,只有列表的第一个元素赋给了 $foo[1],列表的其他内容都被丢弃。因此整个语句的作用就是读入文件所有内容,将第一行赋给数组 @foo 的第二个元素。
16. What does this do?
$i = 0;
do {
$done = foo();
if ($done) { last; }
$i++;
print "x";
} while ($i < 10);
“do {}”只是一条语句,而不是块,因此不能在其中使用循环控制语句 redo、next、last 等,但只有在确实执行到这些语句时出问题。比如题目中的程序在 $done 为真的情况下才会出错。要想在“do {}”结构中使用循环控制,必须用大括号人为制造块,比如如果想使用 last 控制语句,就需要写成:
{
do {
...
last;
} while (...);
}
而要使用 next 时,要写成:
do {{
...
next;
}} while (...);
这些都是由循环控制语句的特性决定的,可以根据具体情况和需求自行创造符合要求的格式。
17. Explain when each of ``exists'' or ``defined'' will be printed.
print "exists" if exists $foo{$bar};
print "defined" if defined $foo{$bar};
exists 在给定参数的存储空间已分配时返回真,它不在意这块空间中究竟存放了什么值;defined 只有在给定参数求值不为 undef 时才返回真,对未分配空间 defined 返回假,因为对它求值时得到的将是 undef。因此要打印“exists”,必须保证 $foo{$bar} 已经被赋过值;要打印“defined”,必须保证 $foo{$bar} 被赋过非 undef 的值。还要注意一点就是,exists 只能对数组或散列的元素使用,而任意表达式都可以作为 defined 的参数。下面一个例子简单地说明了一下二者的区别:
@a=();
print "1. /$a[0] exists/n" if exists($a[0]); # nothing
print "2. /$a[0] defined/n" if defined($a[0]); # nothing
$a[0]=undef;
print "3. /$a[0] exists/n" if exists($a[0]); # something
print "4. /$a[0] defined/n" if defined($a[0]); # nothing
$a[0]=0;
print "5. /$a[0] exists/n" if exists($a[0]); # something
print "6. /$a[0] defined/n" if defined($a[0]); # something
undef($a[0]);
print "7. /$a[0] exists/n" if exists($a[0]); # something
print "8. /$a[0] defined/n" if defined($a[0]); # nothing
delete($a[0]);
print "9. /$a[0] exists/n" if exists($a[0]); # nothing
print "10. /$a[0] defined/n" if defined($a[0]); # nothing
18. What does this do?
for ($i=0; $i<10; $i++) {
if (<STDIN>) { print; }
}
if 并没有 while 那样隐含对 $_ 赋值的功能,因此这里的 if 语句从标准输入读入一行后 print 不会输出任何内容。
19. What does this do?
$f = <foo[bar]>;
这是一个 fileglob,由于是在标量环境下赋值,只会返回首先找到的那个文件名,而字符集合“[bar]”在匹配时是按顺序调整成“[abr]”进行的,因此查找顺序是“fooa”、“foob”、“foor”,如果这些文件都不存在则返回未定义值 undef。
20. What does this print?
$_ = "foo bar baz";
--$b{(/(/w+)/)[0]};
--$b{/(/w+)/};
print %b;
“m//”操作符简写为“//”,在没有用“=~”或“!~”将其绑定到字符串上时默认对“$_”进行操作。没有加“g”修饰符时,在列表环境中它返回捕获内容组成的列表,标量环境中它返回匹配成功与否 (分别用1和空字符串表示)。题目里第一个自减语句中的正则表达式处于“(...)[0]”构造的列表环境当中,因此返回捕获内容列表;第二个自减语句处于标量环境中,返回表示匹配成功的 1。故最终结果相当于:
--$b{foo};
--$b{1};
print %b;
最后输出是“foo-11-1”或“1-1foo-1”。
21. What does this print?
print -0.5 ** -0.5;
Perl 中幂运算“**”的优先级高于单目取反运算符“-”,因此实际解释为:
print -(0.5**(-0.5)); # -1.4142135623731
22. What does this print?
@a = (0 => 0);
print ~$a[0], " ", ~$a[1];
由于使用了“=>”操作符,按照 perldoc perlop 文档中的定义,@a 的第一个元素本来应该解释为字符串“0”。然而在 5.8.x 的 Perl 中,后面对该元素的取反操作仍然是按照数值方式进行的,说明“=>”的实际行为和文档中的描述有所差别 (应该属于 bug),因此要非常小心。不仅仅是 0,所有合法的数值放在“=>”左边时都不会被解释为字符串,为了以防万一,在你确实需要字符串的地方最好使用引号强迫解释器将其作为字符串看待。
23. What does this print?
$a[0] = 7;
$a[1] = 8;
@b = (5 x @a);
@c = ((5) x @a);
print "@b @c";
“x”算符为右操作数提供了标量环境,而数组 @a 在标量环境中求值就是其长度 2。当左操作数为标量时“x”的结果是字符串,为列表时结果也是列表。故最终:
@b = ("55");
@c = (5, 5);
显示结果为“55 5 5”。
24. What does this do?
$print_blanks = 1;
while (<>) {
next unless length & $print_blanks;
print;
}
典型问题,length 同单目运算符类似,对参数“获取”能力非常强,未给出参数时它会尝试将后续表达式结合求值,因此解释为 length(&$print_blanks) ,产生运行时错误。让 length 使用默认参数 $_ 最好用 length() 的形式。
25. What does this do?
$n = 5;
while (<>) {
if (1 .. $n) { print; }
}
根据 perldoc perlop 中描述的特性,在标量环境中“..”操作符在左操作数为真时变为真,而在右操作数变为真之后变为假且一直维持该状态。由于题目中 1 和 $n 的值都是真,因此只有在首次求值时“..”返回真值,后面一直维持假,故只打印出来读入的第一行内容。
26. What does this do?
$sos = 1_1_1 x x x 1_1_1 ... 1_1_1 x x x 1_1_1;
首先数字之间的下划线可以忽略,其次“x”算符是左结合的,它的右操作数始终被转换为一个整数,若右操作数小于等于 0 则返回空字符串,因此该语句变成:
$sos = (111 x x) x 111 ... (111 x x) x 111;
裸字“x”被当作字符串看待,随后转换为整数 0,因此“111 x x”就是“111 x 0”,结果是空字符串。故 ... 算符的两个操作数都是空字符串,因此 $sos 就被赋成空字符串。