$纯量值 (scalar) (数字,字串或参考值 [reference]) @阵列 %杂凑阵列 (关连阵列) *代表同一个变数名的所有类形。在第四版中它们常用来达到指标 (pointers)的功能,但现在在新版的 perl中这个角色已被参 考值 (reference)取代了。
虽然这些符号在某些场合下可省略,但建议你随处都用。
有些其他的符号你可能会碰到但却不是指定形态用的有:
<>这是用来从一个档案把手 (filehandle)里输入一份记录 \取某样东西的参考值 (reference)
注意 <
FILE> 不是用来指定档案的形态,亦非此把手的名字。它只是
将<>
这个运算子用在
FILE这个把手上。在纯量的情境 (scalar context)
下,它自
FILE
把手一次读入一行
(嗯,该说一笔记录,参看
$/),在序列情境 (list context)下,则一次将 全部的内容读
入。当对档案使用开、关或其它 <>
之外的动作、或甚至只是提到把
手时,切记不要使用 <>
。下面的用法是正确的:eof(FH)
,
seek(FH, 0,2)
以及 ``copying from
STDIN to
FILE''。
use strict
下则是必须的)。但由一个简单的字(不
能是一个已定义的副函数之名称)所构成的索引值,和 =>
左端的运算子,都会被视为已纳入引号了:
这些是和这些一样的 ------------ --------------- $foo{line} $foo{"line"} bar => stuff "bar" => stuff
一个区块末端的分号可有可无,一个序列的最後一个逗号亦同。良好的写作风格 (参看perlstyle)中建议除了在单行程式 (one-liners)的情况外都将他们加上去:
if ($whoops) { exit 1 } @nums = (1, 2, 3);
if ($whoops) { exit 1; } @lines = ( "There Beren came from mountains cold", "And lost he wandered under leaves", );
$dir = (getpwnam($user))[7];
另一种方法就是在等号左端用 undef 作元素:
($dev, $ino, undef, undef, $uid, $gid) = stat($file);
$^W
变数 (在 perlvar中有说明)控制一个区块在执行期 (runtime)的警告讯息:
{ local $^W = 0; #暂时关掉警告讯息 $a = $b + $c; #我知道这些变数可能未定义 }
注意,像所有的标点符号变数一样,目前不能对 $^W
用 my,只能用 local()。
一个发展中的新 use warnings
编译器指挥模组 (pragma)
提供了更精细的控制。好奇宝宝们应该翻翻 perl5-porters
邮件论坛的档案库以获得更详细的说明。
一个常犯的错误像是:
unlink $file || die "snafu";
这会被解译器看成是:
unlink ($file || die "snafu");
要避免此问题,须加上括号或是用超低优先的 or
运算子:
(unlink $file) || die "snafu"; unlink $file or die "snafu";
这些“英文的”运算子 (and
, or
, xor
,及 not
)是刻意设计成较一般序列运算子低的优先顺序,这就是为了解决前述的状况。
另一个拥有出人意料的优先顺序者为指数。它甚至高於负号,这使得 -2**2
变成负四而非正四。他同时也会“向右靠”(right-associate),意思是说
2**3**2
代表二的九次方,而不是八的平方。
$person = {}; #新的不具名杂凑阵列 $person->{AGE} = 24; #把 AGE栏的值设成 24 $person->{NAME} = "Nat"; #将 NAME栏设成 "Nat"
如果你要的是更严谨的写法,看看 perltoot 。
下面是个方便的样板,你也许希望在撰写第一个模组时将他派上用场。记得要改名 字。
package Some::Module; #假设是 Some/Module.pm
use strict;
BEGIN { use Exporter (); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
##设定版本以备版本检查之用;去掉 "#"号即可使用。 ## $VERSION = 1.00;
#如果有使用 RCS/CVS,那应该考虑将下一行保留, #但是小心两位数版本编号可能造成的影响。 $VERSION = do{my@r=q$Revision: 1.1 $=~/\d+/g;sprintf '%d.'.'%02d'x$#r,@r};
@ISA = qw(Exporter); @EXPORT = qw(&func1 &func2 &func3); %EXPORT_TAGS = ( ); #例如: TAG => [ qw!name1 name2! ],
#整个包裹要输出的全域变数(exported package globals)在此, #还有其他选择要输出的函数。 @EXPORT_OK = qw($Var1 %Hashit); } use vars @EXPORT_OK;
#没有输出的全域变数在此。 use vars qw( @more $stuff );
#起始包裹内的全域变数,首先是要输出的那几个。 $Var1 = ''; %Hashit = ();
#接下来是其他的 (还是能以 $Some::Module::stuff的方式撷取他们的值) $stuff = ''; @more = ();
#所有以档案为范围的变数名都 #必须在让後面的函数使用前先创造出来。
#以档案为范围的变数名在此。 my $priv_var = ''; my %secret_hash = ();
#下面是一个以档案为限的函数,当作一个闭包 #可以用 &$priv_func的方式呼叫它;但不能使用原型定义 my $priv_func = sub { #程式码放在这里 };
#不论是否要输出,都记得要将你的函数造出来。 #别忘了在 {}括号间放些有趣的内容。
sub func1 {} #没有定义原型 sub func2() {} #定原型为 void sub func3($$) {} #定原型为两个纯量值
#这个函数虽然未被输出,但是可以被呼叫 sub func4(\%) {} #定原型为一个杂凑阵列的参考值
END { } #模组清洁大队在此 (global destructor)
1; #模组必须传回真值
kill()
没有将任何程序交给讯号):
sub is_tainted { return ! eval { join('',@_), kill 0; 1; }; }
然而,此方法会触发 -w
参数的警告讯息。目前并无任何不会触发 -w
的方法可下侦测变数污染 --
就把警告讯息当成是在提醒你,该把所有可能被污染的资料给 ``漂白''
(untaint)。
【译注:这里所提的 ``被污染'' (tainted),指的是当使用 -T这个参数时,或当 perl程式做 setuid或 setgid模式执行时 (在 UNIX 下), perl会自动将有安 全顾虑的变数列为受污染, 也就是 tainted。除非先做解除污染 (untaint)处理,否则 perl不会容许受污染的变数做出可能危害系统的举动。因此 CGI程式应尽可能地使用 -T这个参数以策安全。】
闭包 (closure)是个精确但又很难解释的电脑名词。在 Perl 里面,闭包是以 匿名函数的形式来实现,具有持续参照位於该函数范围之外的文字式变数值的能力。 这些外部的文字变数会神奇地保留它们在闭包函数最初定义时的值 (深连结)。
如果一个程式语言容许函数递回另一个函数的话 (像 Perl 就是),闭包便具有意 义。要注意的是,有些语言虽提供匿名函数的功能,但却无法正确处理闭包; Python 这个语言便是一例。如果要想多了解闭包的话,建议你去找本功能性程式 设计的教科书来看。Scheme这个语言不仅支援闭包,更鼓励多加使用。
以下是个典型的产生函数的函数:
sub add_function_generator { return sub { shift + shift }; }
$add_sub = add_function_generator(); $sum = &$add_sub(4,5); # $sum现在是 9了
闭包用起来就像是个 函数样板,其中保留了一些可以在稍後再填入的空格。
add_function_generator()
所递回的匿名函数在技术上来讲并不能算是一个闭包,
因为它没有用到任何位在这个函数范围之外的文字变数。
把上面这个例子和下面这个 make_adder()
函数对照一下,下面这个函数所递回的匿名函数中使用了一个外部的文字变数。这种指名外部函数的作法需要由 Perl递回一个适当的闭包,因此那个文字变数在匿名函数产生之时的值便永久地被锁进闭
包里。
sub make_adder { my $addpiece = shift; return sub { shift + $addpiece }; }
$f1 = make_adder(20); $f2 = make_adder(555);
这样一来 &$f1($n)
永远会是 20加上你传进去的值 $n
,而
&$f2($n)
将
永远会是 555加上你传进去的值 $n
。$addpiece的值会在闭包中保留下来。
闭包在比较实际的场合中也常用得到,譬如当你想把一些程式码传入一个函数时:
my $line; timeout( 30, sub { $line = <STDIN> } );
如果要执行的程式码当初是以字串的形式传入的话,即 '$line =
<STDIN>'
,那麽 timeout()
这个假想的函数在回到该函数被呼叫时所在的范围後便无法再撷取
$list
这个文字变数的值了。
my()
和 local()和闭包或
foreach()
回圈变数及函数参数相互影响
所致。
从前【在旧版 perl的时代】大家写程式的时候很容易因为这样而不小心把变数值 给弄丢。但现在 perl提供了一些保护措施,因此犯这种错的机率要小多了。
my $f = "foo"; sub T { while ($i++ < 3) { my $f = $f; $f .= "bar"; print $f, "\n" } } T; print "Finally $f\n";
有叁个 ``bar''加进去的 $f
变数应该是一个新的 $f
(因为 my $f
在每个回圈都应该创造一个新的区域变数)。然而,实际上并非如此。这个臭虫在未来的 perl
版本中将会被修正。
func( \$some_scalar );
func( \$some_array ); func( [ 1 .. 10 ] );
func( \%some_hash ); func( { this => 10, that => 20 } );
func( \&some_func ); func( sub { $_[0] ** $_[1] } );
*FH
或 \*FH
(这叫 ``typeglobs'' --请参看 perldata
),或是使用旧名 FileHandle的 IO::File模组以动态方式来产生档案把手亦可,这两个模组都附在标准 Perl
版本内。
use Fcntl; use IO::File; my $fh = new IO::File $filename, O_WRONLY|O_APPEND; or die "Can't append to $filename: $!"; func($fh);
sub compare($$) { my ($val1, $regexp) = @_; my $retval = eval { $val =~ /$regexp/ }; die if $@; return $retval; }
$match = compare("old McDonald", q/d.*D/);
确定绝对不要用以下的写法:
return eval "\$val =~ /$regexp/"; #错误
不然某人可以靠双引号括起来的字串以及 eval 双重解译的本质而偷偷插入 shell指令来作坏事。例如:
$pattern_of_evil = 'danger ${ system("rm -rf * &") } danger';
eval "\$string =~ /$pattern_of_evil/";
想学非常非常聪明的方法的读者可以参考 O'Reilly
出的 Mastering Regular
Expressions这本书,作者是 Jeffrey Friedl。其中第 273页的
Build_MatchMany_Function()
特别的有趣。在 perlfaq2中可以找到有关本书
的资料。
call_a_lot(10, $some_obj, "methname") sub call_a_lot { my ($count, $widget, $trick) = @_; for (my $i = 0; $i < $count; $i++) { $widget->$trick(); } }
不然你就用个闭包 (closure) 把物件和它的方法以及其参数都包在一起:
my $whatnot = sub { $some_obj->obfuscate(@args) }; func($whatnot); sub func { my $code = shift; &$code(); }
你也可以研究
UNIVERSAL
类别中的 can()
方法 (附於标准 Perl
版本中)。
以下就是实作函数私有变数的程式:
BEGIN { my $counter = 42; sub prev_counter { return --$counter } sub next_counter { return $counter++ } }
prev_counter()
和 next_counter()
将会共用一个於编译时起始的私有变数 $counter。
要宣告一个档案私有(file-private)变数,你仍然得使用
my(),将它放在档案开
头处最外围。假设现在是在 Pax.pm
这个档案里:
package Pax; my $started = scalar(localtime(time()));
sub begun { return $started }
当用 use Pax
或 require Pax
载入此模组时,这个变数就会被起始。不过它不会被资源回收,像其他出了有效范围的变数那样,因为 begun()
函数要用到它,但是没有其他函数能撷取它。这个变数不能以 $Pax::started
的形式来撷取,因为它所存在的范围与此包裹无关。它存在的范围是这个档案。可想见地,一个档案里可以放好几个包裹,而所有的包裹都撷取同一个私有变数,但从另一个档案中,即使是属於同一个包裹(package),也不能取得它的值。
local($x)
将全域变数 $x
的原值存起来,并在此函数执行期间赋予一个新
值,此值可以从此函数所呼叫的其他函数里看见。这整个步骤是在执行期间完成的,所以才叫做动态范围选取 (dynamic
scoping)。local()影响的是全域变数,或者称作包裹变数或动态变数。
my($x)
会创造一个只能在目前这个函数里看得见的新变数。这个步骤是在编译
期完成(compile-time),所以称作文字式或是静态范围选取。my()总是作用在私
有变数,也称作文字式变数或(不当地)称作静态(范围选取)变数。
例如:
sub visible { print "var has value $var\n"; }
sub dynamic { local $var = 'local'; #授予 $var这个全域变数 visible(); #一个暂时的新值 }
sub lexical { my $var = 'private'; #新的私有变数,$var visible(); # (无法从此函数外看到) }
$var = 'global';
visible(); #会印出 global dynamic(); #会印出 local lexical(); #会印出 global
你可以发现在整个过程中 ``private''这个值都印不出来。那是因为
$var
的值只存在於lexical()
函数的区块里面,对它所呼叫的函数来说是看不到的。
总结来说,local()不会产生你想像中的私有、区域变数。它只是将一个暂时的值
授予一个全域变数。如果你要的是私有的变数,那麽 my()
才是你要找的。
参看 perlsub ,里面有更详尽的解说。
use strict "refs"
设定取掉。然後使用 ${'var'}
,而非 $var。
local $var = "global"; my $var = "lexical";
print "lexical is $var\n";
no strict 'refs'; print "global is ${'var'}\n";
如果你知道你所在的是哪一个包裹(package)的话,你可以直接指名,就像写
$Some_Pack::var这样。注意 $::var这个写法 并非表示目前此包裹 (package)
内的动态变数 $var,而是指在 main
包裹(package)
里的那个,就等价於 $main::var。直接指定包裹(package)的名称虽然需要你把名字敲进程式码
中,但是它执行起来比较快,也避免和
use strict "refs"
起冲突。
my()
创造的)式的深连结。然而,动态变数(也称作全域(global),区域(local),或包裹(package)变数)在功效上是浅连结。就把这当作是少用它们的另一个理由好
了。请参考
闭包 (closure)是啥?
一节。
local()
会把 =
号右边以序列情境来对待。而 <
FH>这个阅读的
动作,就像 Perl里许多的函数以及运算子一样,会自动分辨出自己被呼叫时所在的情境并且采取适当的作法。一般来说,scalar()函数可以帮点忙。这个函数实际上对资料本身不会有任何作用(与一般所认为的相反),但是会告诉它所作用的函数要以对待纯量值的方法来运算。如果那个函数没有预先定义好碰到纯量情境的行为,那麽它当然也帮不了你(例如 sort()
函数)。
然而,在以上这个例子 (local...)中,只要省略括号便可强制使用纯量情境:
local($foo) = <FILE>; #错误 local($foo) = scalar(<FILE>); #可以 local $foo = <FILE>; #正确
其实在这个例子中,或许你该改用文字式变数 (lexical variables),不过会碰到 的问题跟上面一样:
my($foo) = <FILE>; #错误 my $foo = <FILE>; #正确
如果你要覆盖掉某个内建函数,例如说 open(),那你得将其定义从另一个模组载
入。参考
Overriding Builtin Functions。在
Class/Template里面也有个范例。
如果你要覆盖掉一个 Perl运算子,像是 +
或 **
,那你该使用 use
overload
这个编译器指挥模组(pragma),其文件在 overload
。
如果你要覆盖父类别 (parent class)里的方法呼叫 (method calls),请看 Overridden Methods 。
&foo
的方式呼叫一个函数时,你等於让这个函数撷取你目前 @_
里面的值,同时也跳过原型定义 (prototypes)不用。这表式此函数抓到的是你当时的
@_,
而非一个空的
@_!虽然严格讲起来它也不能算是个 bug (但是在
perlsub里面是这麽说的)但在大部份情况下,这也算不上是个特别功能。
当你用 &foo()
的方式呼叫你的函数时,你会得到一个新的 @_,但是原型定义
仍然会被避开不用。
在一般情况下,你该用 foo()
的方式去呼叫函数。只有在编译器已事先知道这
个函数的定义时,括号才能省略,譬如当这个函数所在的模组或包裹被 use
(但如果是被 require
则不行)时,或是透过先前提及或 use subs
宣告等
方法,让编译器先接触到这个函数的定义。用这种呼叫方式,即使是当括号省掉时,
你都会得到一个乾净的 @_,不会有任何不该出现的旧值残留在上面。
下面这个简单的 switch范例以模式对应为基础。我们将要做的是对储存在
$whatchamacallit
里面的参考值 (reference)的类型进行多重条件的判断。【译注:$whatchamacallit
函意为 $what_you_might_call_it
】
SWITCH: for (ref $whatchamacallit) {
/^$/ && die '不是个参考值';
/SCALAR/ && do { print_scalar($$ref); last SWITCH; };
/ARRAY/ && do { print_array(@$ref); last SWITCH; };
/HASH/ && do { print_hash(%$ref); last SWITCH; };
/CODE/ && do { warn '无法印出函数的 ref'; last SWITCH; };
# DEFAULT
warn '跳过使用者自定的类型';
}
如果是要处理一些在 -w
之下触发警告讯息的未定义变数,你可以使用一个处理元 (handler)来捕捉 __WARN__
这个虚拟信号 (pseudo-signal),范例如下:
$SIG{__WARN__} = sub {
for ( $_[0] ) {
/Use of uninitialized value/ && do { #将警讯提升为致命行动 die $_; };
#其它要捕捉的状况可以写在此。
warn $_; }
};
print
ref($object)
来找出 $object
这个物件是被归到哪个类别底下。
另一个可能的原因是你在 Perl还不知道这个包裹 (package)存在之前便将某个
类别名称在间接式物件语法中使用 (例如 find Guru "Samy"
)。最好是在开始使用你的包裹前,先确定都已经先把它们定义好了,如果你用的是 use
而非
require
的话,这件事便会自动处理好。不然的话,确定你使用箭头式语法 (例如,Guru-
find(``Samy'')>)。在
perlobj
里面对於物件的记号有详尽解释。
my $packname = ref bless [];
但如果是一个方法的话,而且印出的错误讯息中要包含呼叫此方法的物件 (不见得 就是把这个方法编译进去的那个物件)则:
sub amethod { my $self = shift; my $class = ref($self) || $self; warn "我是被 $class这个物件所召唤"; }
#这是程式
=for nobody 这段就变成了注解
#程式继续下去
=begin comment text
接下来此处所有
的文字都会被 所有人忽略
=end comment text
=cut
译者:陈彦铭、萧百龄
中译版着作权所有:陈彦铭、萧百龄及两只老虎工作室。 本中译版遵守并使用与原文版相同的使用条款发行。