perl教程(3)
六、子程序的引用
perl中子程序的引用与C中函数的指针类似,构造方法如下:
$pointer_to_sub = sub {... declaration of sub ...};
通过所构造的引用调用子程序的方法为:
&$pointer_to_sub(parameters);
子程序模板
子程序的返回值不仅限于数据,还可以返回子程序的引用。返回的子程序在调用处执行,但却是在最初被创建的调用处被设置,这是由Perl对Closure处理的方式决定的。Closure意即如果你定义了一个函数,它就以最初定义的内容运行。(Closure详见OOP的参考书)下面的例子中,设置了多个错误信息显示子程序,这样的子程序定义方法可用于创建模板。
#!/usr/bin/perl
sub errorMsg {
my $lvl = shift;
#
# define the subroutine to run when called.
#
return sub {
my $msg = shift; # Define the error type now.
print "Err Level $lvl:$msgn"; }; # print later.
}
$severe = errorMsg("Severe");
$fatal = errorMsg("Fatal");
$annoy = errorMsg("Annoying");
&$severe("Divide by zero");
&$fatal("Did you forget to use a semi-colon?");
&$annoy("Uninitialized variable in use");
结果输出如下:
Err Level Severe:Divide by zero
Err Level Fatal:Did you forget to use a semi-colon?
Err Level Annoying:Uninitialized variable in use
上例中,子程序errorMsg使用了局域变量$lvl,用于返回给调用者。当errorMsg被调用时,$lvl的值设置到返回的子程序内容中,虽然是用的my函数。三次调用设置了三个不同的$lvl变量值。当errorMsg返回时,$lvl的值保存到每次被声明时所产生的子程序代码中。最后三句对产生的子程序引用进行调用时$msg的值被替换,但$lvl的值仍是相应子程序代码创建时的值。
很混淆是吗?是的,所以这样的代码在Perl程序中很少见。
七、数组与子程序
数组利于管理相关数据,本节讨论如何向子程序传递多个数组。前面我们讲过用@_传递子程序的参数,但是@_是一个单维数组,不管你传递的参数是多少个数组,都按序存贮在@_中,故用形如my(@a,@b)=@_; 的语句来获取参数值时,全部值都赋给了@a,而@b为空。那么怎么把一个以上的数组传递给子程序呢?方法是用引用。见下例:
#!/usr/bin/perl
@names = (mickey, goofy, daffy );
@phones = (5551234, 5554321, 666 );
$i = 0;
sub listem {
my ($a,$b) = @_;
foreach (@$a) {
print "a[$i] = " . @$a[$i] . " " . "tb[$i] = " . @$b[$i] ."n";
$i++;
}
}
&listem(@names, @phones);
结果输出如下:
a[0] = mickey b[0] = 5551234
a[1] = goofy b[1] = 5554321
a[2] = daffy b[2] = 666
注意:
1、当想传递给子程序的参数是多于一个的数组时一定要使用引用。
2、一定不要在子程序中使用形如 (@variable)=@_; 的语句处理参数,除非你想把所有参数集中到一个长的数组中。
八、文件句柄的引用
有时,必须将同一信息输出到不同的文件,例如,某程序可能在一个实例中输出到屏幕,另一个输出到打印机,再一个输出到记录文件,甚至同时输出到这三个文件。相比较于每种处理写一个单独的语句,可以有更好的实现方式如下:
spitOut(*STDIN);
spitOut(*LPHANDLE);
spitOut(*LOGHANDLE);
其中子程序spitOut的代码如下:
sub spitOut {
my $fh = shift;
print $fh "Gee Wilbur, I like this lettucen";
}
注意其中文件句柄引用的语法为*FILEHANDLE。
Perl中的模块
注: 如果你的e文好, 那就看一看PERL的MAN手册吧 那里面什么都有PERL 中的模块 结构很简单 用起来也不难 但至少我不是很喜欢它
注意 PERL中的模块和类是同意词
perlobj.1 中对模块有三条简单的定义
1 一个模块是一个引用 但系统知道它属于那一个类
2 一个类是一个包 它提供了一些方法
3 一个方法是一个SUB 它的第一个参数为一个类的引用
在PERL中并没有明确的定义类的构造函数
我们所使用的类的引用一般是一个HASH的引用 通过调用函数bless
将它同一个类绑在一起
例如:
Critter.pm
package Critter
sub new {
bless {};
}
本例中返回了一个空的HASH表(PERL中也叫做匿名表)
复杂一点的例子
try.pm
package try;
sub new {
$para = @_;
%hash = {};
$hash{'a'} = $para{'a'};
bless %hash, try;
return %hash;
}
sub pp {
print shift;
print ("\n", @_);
}
a.pl // 它与try.pm放在同一个目录下
use try;
$m_try = try::new(a => "hello");
$m_try->pp("hello world");
一般的文章上都说 应该这样用:
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
bless $self, $class
$self->initialize();
return $self;
}
而上面的例子却不是这样 Why?
这个问题涉及到如何new一个类.
1 try::new(a => "hello");
2 try->new(a => "hello");
3 new try(a => "hello");
第二个和第三个是一样的
2,3 中函数new的参数是 ('try', 'a', 'hello')
1 的参数却是('a', 'hello');
#对于2 3 你可以理解为将class->new(arg); 转换为 new::(class,arg)
#这与class是什么东西无关 如同 $m_class->method(arg)被转换为
#class::method($m_class,arg)一样
PERL的类常用 2 3 两种方法创建
当调用一个类中的方法时 第一个参数为HASH表的引用
try.pm
sub pp {
$class = shift;
print (@_, $class->{'a'});
}
$m_try = new try('a' => 'hello');
$m_try->pp("ha ha ha");
下面看一看 PERL中类的继承
PERL的类继承很奇怪 子类的目录比父类低一层
如
Tk.pm
Tk/Button.pm
Button.pm
package Tk::Button.pm
...
...
另一种方法是使用@ISA
class UNIVERSAL
这是一个特殊的类 所有的类都是它的子类 它定义了如下的方法
isa 判断是不是某个类的引用
can 是否存在某个方法
VERSION 判断版本号
SUPER:: 用于调用父类中的方法
如:
$self->SUPER::get();
先看一看如何使用@ISA 完成类的继承
#try.pm 父类
package try;
$VERSION = "1.00";
sub new {
bless {} ,try;
}
sub pp {
print "this is in father class\n";
}
#tt.pm 子类
package tt;
use trk;
@ISA = qw(try); #这样在tt中找不到的方法 会在try中查找
#如同tt是try的子类
sub new {
bless {} ,tt;
}
sub pp {
print "this is in son class\n";
}
#c.pl
#!/usr/bin/perl
use tt;
$m_tt = new tt;
$m_tt->pp;
这中方法会显示 "this is in son class"
注: 你应该定义new在类tt.pm中, 不然会显示
"this is in father class"
原因是调用了try中的new, 引用被bless成了try的.
修改tt.pm
sub pp {
$self = shift;
$self->SUPER::pp; #调用父类中的方法
}
会显示"this is in father"
9: Perl 中的正则表达式
正则表达式的三种形式
正则表达式中的常用模式
正则表达式的 8 大原则
正则表达式是 Perl 语言的一大特色,也是 Perl 程序中的一点难点,不过如果大家能够很好的掌握他,就可以轻易地用正则表达式来完成字符串处理的任务,当然在 CGI 程序设计中就更能得心应手了。下面我们列出一些正则表达式书写时的一些基本语法规则。
--------------------------------------------------------------------------------
9.1 正则表达式的三种形式
首先我们应该知道 Perl 程序中,正则表达式有三种存在形式,他们分别是:
匹配:m/<regexp>/ (还可以简写为 /<regexp>/ ,略去 m)
替换:s/<pattern>/<replacement>/
转化:tr/<pattern>/<replacemnt>/
这三种形式一般都和 =~ 或 !~ 搭配使用(其中 "=~" 表示相匹配,在整条语句中读作 does,"!~" 表示不匹配,在整条语句中读作 doesn't),并在左侧有待处理的标量变量。如果没有该变量和 =~ !~ 操作符,则默认为处理 $_ 变量中的内容。举例如下:
$str = "I love Perl";
$str =~ m/Perl/; # 表示如果在 $str 中发现 "Perl" 字符串,则返回 "1" 否则返回 "0"。
$str =~ s/Perl/BASH/; # 表示将变量 $str 中的 "Perl" 字符串替换为 "BASH",如果发生此替换则返回 "1",否则返回 "0"。
$str !~ tr/A-Z/a-z/; # 表示将变量 $str 中的所有大写字母转化为小写字母,如果转化发生了则返回 "0",否则返回 "1"。
另外还有:
foreach (@array) { s/a/b/; } # 此处每次循环将从 @array 数组中取出一个元素存放在 $_ 变量中,并对 $_ 进行替换处理。
while (<FILE>) { print if (m/error/); } # 这一句稍微复杂一些,他将打印 FILE 文件中所有包含 error 字符串的行。
Perl 的正则表达式中如果出现 () ,则发生匹配或替换后 () 内的模式被 Perl 解释器自动依次赋给系统 $1, $2 ...... 请看下面的例子:
$string = "I love perl";
$string =~ s/(love)/<$1>/; # 此时 $1 = "love",并且该替换的结果是将 $string 变为 "I <love> perl"
$string = "i love perl";
$string =~ s/(i)(.*)(perl)/<$3>$2<$1>/; # 这里 $1 = "i",$2 = " love ",$3 = "perl",并且替换后 $string 变为 "<perl> love <i>"
替换操作 s/<pattern>/<replacement>/ 还可以在末尾加上 e 或 g 参数,他们的含义分别为:
s/<pattern>/<replacement>/g 表示把待处理字符串中所有符合 <pattern> 的模式全部替换为 <replacement> 字符串,而不是只替换第一个出现的模式。
s/<pattern>/<replacement>/e 表示将把 <replacemnet> 部分当作一个运算符,这个参数用的不多。
比如下面的例子:
$string = "i:love:perl";
$string =~ s/:/*/; #此时 $string="i*love:perl";
$string = "i:love:perl";
$string =~ s/:/*/g; #此时 $string="i*love*perl";
$string =~ tr/*/ /; #此时 $string="i love perl";
$string = "www22cgi44";
$string =~ s/(\d+)/$1*2/e; # (/d+)代表 $string 中的一个或多个数字字符,将这些数字字符执行 *2 的操作,因此最后 $string 变成了 "www44cgi88"。
下面给出一个完整的例子:
#!/usr/bin/perl
print"请输入一个字符串!\n";
$string = <STDIN>; # <STIDN>代表标准输入,会让使用者输入一字符串
chop($string); # 将$string最后一个换行的字符\n删除掉
if($string =~ /perl/){
print("输入的字符串中有 perl 这个字符串!\n";
}
如果输入的字符串含有 perl 这个字符串的话,就会显示后面的提示信息。
9.2 正则表达式中的常用模式
下面是正则表达式中的一些常用模式。
/pattern/ 结果
. 匹配除换行符以外的所有字符
x? 匹配 0 次或一次 x 字符串
x* 匹配 0 次或多次 x 字符串,但匹配可能的最少次数
x+ 匹配 1 次或多次 x 字符串,但匹配可能的最少次数
.* 匹配 0 次或一次的任何字符
.+ 匹配 1 次或多次的任何字符
{m} 匹配刚好是 m 个 的指定字符串
{m,n} 匹配在 m个 以上 n个 以下 的指定字符串
{m,} 匹配 m个 以上 的指定字符串
[] 匹配符合 [] 内的字符
[^] 匹配不符合 [] 内的字符
[0-9] 匹配所有数字字符
[a-z] 匹配所有小写字母字符
[^0-9] 匹配所有非数字字符
[^a-z] 匹配所有非小写字母字符
^ 匹配字符开头的字符
$ 匹配字符结尾的字符
\d 匹配一个数字的字符,和 [0-9] 语法一样
\d+ 匹配多个数字字符串,和 [0-9]+ 语法一样
\D 非数字,其他同 \d
\D+ 非数字,其他同 \d+
\w 英文字母或数字的字符串,和 [a-zA-Z_0-9] 语法一样
\w+ 和 [a-zA-Z0-9]+ 语法一样
\W 非英文字母或数字的字符串,和 [^a-zA-Z_0-9] 语法一样
\W+ 和 [^a-zA-Z_0-9]+ 语法一样
\s 空格,和 [\n\t\r\f] 语法一样
\s+ 和 [\n\t\r\f]+ 一样
\S 非空格,和 [^\n\t\r\f] 语法一样
\S+ 和 [^\n\t\r\f]+ 语法一样
\b 匹配以英文字母,数字为边界的字符串
\B 匹配不以英文字母,数值为边界的字符串
a|b|c 匹配符合a字符 或是b字符 或是c字符 的字符串
abc 匹配含有 abc 的字符串
(pattern) () 这个符号会记住所找寻到的字符串,是一个很实用的语法。第一个 () 内所找到的字符串变成 $1 这个变量或是 \1 变量,第二个 () 内所找到的字符串变成 $2 这个变量或是 \2 变量,以此类推下去。
/pattern/i i 这个参数表示忽略英文大小写,也就是在匹配字符串的时候,不考虑英文的大小写问题。
\ 如果要在 pattern 模式中找寻一个特殊字符,如 "*",则要在这个字符前加上 \ 符号,这样才会让特殊字符失效
下面给出一些例子:
范例 说明
/perl/ 找到含有 perl 的字符串
/^perl/ 找到开头是 perl 的字符串
/perl$/ 找到结尾是 perl 的字符串
/c|g|i/ 找到含有 c 或 g 或 i 的字符串
/cg{2,4}i/ 找到 c 后面跟着 2个到 4个 g ,再跟着 i 的字符串
/cg{2,}i/ 找到 c 后面跟着 2个以上 g ,再跟着 i 的字符串
/cg{2}i/ 找到 c 后面跟着 2个 g,再跟着 i 的字符串
/cg*i/ 找到 c 后面跟着 0个或多个 g ,再跟着 i 的字符串,如同/cg{0,}i/
/cg+i/ 找到 c 后面跟着一个以上 g,再跟着 i 的字符串,如同/cg{1,}i/
/cg?i/ 找到 c 后面跟着 0个或是 1个 g ,再跟着 i 的字符串,如同/cg{0,1}i/
/c.i/ 找到 c 后面跟着一个任意字符,再跟着 i 的字符串
/c..i/ 找到 c 后面跟着二个任意字符,再跟着 i 的字符串
/[cgi]/ 找到符合有这三个字符任意一个的字符串
/[^cgi]/ 找到没有这三个字符中任意一个的字符串
/\d/ 找寻符合数字的字符,可以使用/\d+/来表示一个或是多个数字组成的字符串
/\D/ 找寻符合不是数字的字符,可以使用/\D+/来表示一个或是更多个非数字组成的字符串
/\*/ 找寻符合 * 这个字符,因为 * 在常规表达式中有它的特殊意思,所以要在这个特殊符号前加上 \ 符号,这样才会让这个特殊字符失效
/abc/i 找寻符合 abc 的字符串而且不考虑这些字符串的大小写
9.3 正则表达式的八大原则
如果在 Unix 中曾经使用过 sed、awk、grep 这些命令的话,相信对于 Perl 语言中的正则表达式(Regular Expression)不会感到陌生。Perl 语言由于有这个功能,所以对字符串的处理能力非常强。在Perl语言的程序中,经常可以看到正则表达式的运用,在 CGI 程序设计中也不例外。
正则表达式是初学 Perl 的难点所在,不过只要一旦掌握其语法,你就可以拥有几乎无限的模式匹配能力,而且 Perl 编程的大部分工作都是掌握常规表达式。下面给大家介绍几条正则表达式使用过程中的 8 大原则。
正则表达式在对付数据的战斗中可形成庞大的联盟——这常常是一场战争。我们要记住下面八条原则:
· 原则1:正则表达式有三种不同形式(匹配(m/ /),替换(s/ / /eg)和转换(tr/ / /))。
· 原则2:正则表达式仅对标量进行匹配( $scalar =~ m/a/; 可以工作; @array =~ m/a/ 将把@array作为标量对待,因此可能不会成功)。
· 原则3:正则表达式匹配一个给定模式的最早的可能匹配。缺省时,仅匹配或替换正则表达式一次( $a = 'string string2'; $a =~ s/string/ /; 导致 $a = 'string 2')。
· 原则4:正则表达式能够处理双引号所能处理的任意和全部字符( $a =~ m/$varb/ 在匹配前把varb扩展为变量;如果 $varb = 'a' $a = 'as',$a =~ s/$varb/ /; 等价于 $a =~ s/a/ /; ,执行结果使 $a = " s" )。
· 原则5:正则表达式在求值过程中产生两种情况:结果状态和反向引用: $a=~ m/pattern/ 表示 $a 中是否有子串 pattern 出现,$a =~ s/(word1)(word2)/$2$1/ 则“调换”这两个单词。
· 原则6:正则表达式的核心能力在于通配符和多重匹配运算符以及它们如何操作。$a =~ m/\w+/ 匹配一个或多个单词字符;$a =~ m/\d/" 匹配零个或多个数字。
· 原则7:如果欲匹配不止一个字符集合,Perl使用 "|" 来增加灵活性。如果输入 m/(cat|dog)/ 则相当于“匹配字符串 cat 或者 dog。
· 原则8:Perl用 (?..) 语法给正则表达式提供扩展功能。(这一点请同学们课后看相关资料)
想要学习所有这些原则?我建议大家先从简单的开始,并且不断的尝试和实验。实际上如果学会了 $a =~ m/ERROR/ 是在 $a 中查找子串ERROR,那么你就已经比在 C 这样的低层语言中得到了更大的处理能力。
Perl 数据结构
变量名字
Perl 有三种数据结构: 数值, 数值的数组, 还有数值的关联数组, 即``哈希表''. 普通的数组以数字为索引, 从 0 开始(负值的下标从数组尾部开始计算). 哈希表中的元素是以字符串索引.
数值变量以 '$' 打头, 当引用数组中的一个元素时也一样. 意思是"这". 举例:
$days # 数值变量 &quot;days&quot; $days[28] # 数组 @days 的第29个元素 $days{'Feb'} # 哈希表 %days 中 'Feb' 代表的数值 $#days # 数组 @days 的最大下标
当表示整个数组或数组的一部分时, 用 '@' 打头, 意思是 "这些" 或 "那些"
@days # ($days[0], $days[1],... $days[n]) @days[3,4,5] # 即 @days[3..5] @days{'a','c'} # 即 ($days{'a'},$days{'c'})
当表示整个哈希表时用 '%' 打头:
%days # (key1, val1, key2, val2 ...)
此外, 子过程用 '&' 打头, 当不致引起混淆的时候可以省略. 符号表表项用 '*' 打头, 手册后面部分有详细说明.
不同的变量类型有自己的名字空间. 为一个数值变量, 一个数组和一个哈希表(一个文件句柄, 一个子过程, 一个标号)取相同的名字并不会引起冲突. 也就是说, $foo 和 @foo 是两个不同的变量. $foo[1] 是 @foo 的一部分, 而不是 $foo 的一部分. 看起来也许有点怪, 但要习惯它.
既然变量和数组名都是以 '$', '@', 或 '%' 打头, 那些 ``保留字'' 实际上并非对变量而言. (它们实际上是对标号和文件句柄而言, 标号和文件句柄是没有特殊的打头字母. 给一个文件句柄取名 ``log'' 是错误的. 应该用 open(LOG,'logfile') 而非 open(log,'logfile') . 用大写字母来表示文件句柄也增加了可读性, 避免和将来出现的保留字冲突.) 大小写是区分的 -- ``FOO'', ``Foo'' 和 ``foo'' 是完全不同的名字. 以字母或下划线开头的名字可以包含数字和下划线.
一个以字母数字组成的名字代表的变量可以用一个返回同类型的引用的表达式代替, 详见 perlref .
数字开头的名字只能包含更多的数字. 不是以字母, 数字, 下划线开头的名字只能有一个字符. 例如: $% 或 $$ . (大部分这些单字符名字是 Perl 的预定义变量, 例如, $$ 是当前进程号.)
变量名字
Perl 对操作和变量值的解释有时依赖于上下文. 主要的上下文有两种: 数值和列表. 某些操作, 如果上下文期待的是列表, 就返回列表结果, 对期待数值的上下文就返回数值(如果某种操作有这种上下文依赖性, 会在有关文档中说明). 换句话说, Perl 会根据期待的结果是单个或多个重载一些操作.
与此相对的, 一个操作会为它的每一个参数确定上下文. 例如
int( <stdin>)
取整数操作为 <STDIN> 提供了一个数值上下文, <STDIN> 在 STDIN 读入一行并传送给取整数操作, 后者求出这一行代表的整数值并返回. 而下面的例子
sort( <stdin>)
排序操作为 <STDIN> 提供了一个列表上下文, 从 STDIN 中读入所有的行直到文件结束, 这些行组成的列表被传送到排序操作, 后者对它们排序并返回排序后的行列表.
赋值操作是用等号左边的参数来确定右边参数的上下文. 为数值变量赋值的操作给右边参数确定了数值上下文, 给数组或数组的一部分赋值的时候右边参数是处于列表上下文.
自定义的子过程可以自行确定被调用时的上下文, 但很多场合下不需要这么做, 因为数值可以自动转化成列表. 参看 wantarray .
数值变量
Perl 里的数据要么是数值型, 要么是数值型的数组, 要么是数值型的哈希表. 数值变量可以存放不同类型的单个数据, 比如数字, 字符串, 和引用. 一般情况下, 不同类型之间的转换是透明的. (一个数值型不能存放多个值, 但可以存放对数组或哈希表的引用.) 由于自动类型转换, 返回数值型的操作和函数不需要担心(实际上是不能担心)上下文是等待一个数字还是一个字符串.
数值型不必确定自己的类型. 其实也没有地方去把一个数值型变量声明为``string'', 或 ``number'', 或``filehandle'', 或是其它什么类型. 在 Perl 中, 数值型变量的类型可以是数字或字符串或引用, 根据上下文确定. 字符串和数字实际上没什么两样, 但引用是不可转换的指针, 有内建的引用计数和析构过程.
非空的字符串或非 0 (字符串``0'')的数字可以表示布尔类型中的真值. 布尔上下文是一种特殊的数值型上下文.
空数值型实际上有两种情况: 已定义和未定义的. 当没有任何实际的值存在时, 未定义的空数值型被返回, 比如发生错误的时候, 或者读到文件结束的地方, 或者引用了一个未初始化的变量. 当一个未定义的空数值型首次被使用时, 就变成已定义的, 在此之前可以用 defined() 去检查一个值是否被定义了.
要知道一个字符串是否一个有效的非 0 数字, 一般是确定它不是数值 0 或 字符串 ``0''
if ($str == 0 &amp;&amp; $str ne &quot;0&quot;) { warn &quot;That doesn't look like a number&quot;; }
别的方法是用正则表达式进行检查, 参看 perlre 中对正则表达式的详细介绍.
warn &quot;has nondigits&quot; if /\D/; warn &quot;not a whole number&quot; unless /^\d+$/; warn &quot;not an integer&quot; unless /^[+-]?\d+$/ warn &quot;not a decimal number&quot; unless /^[+-]?\d+\.?\d*$/ warn &quot;not a C float&quot; unless /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;
数组的长度是一个数值型. 数组 @days 的长度是 $# days, 的值, 和 csh 里一样. (实际上这不是数组的长度, 是最大的下标, 因为有数组里第 0 个元素.) 给 $# days 赋值可以改变数组的长度. 用同样的方法缩短数组的长度会破坏被截去的数值. 重新增加被缩短的数组不再能恢复这些数值(在 Perl 4 里是可以的, 但为了保证正确的调用析构做了这样的改变). 给超出最大下标的数组元素赋值可以扩展数组的长度, 给数组赋上空的列表 () 就把数组的长度减为 0 .
@whatever = (); $#whatever = $[ - 1;
如果在数值型上下文里计算数组的值, 会得到数组的长度. (并不适用于列表, 会返回最后一个值, 象 C 的逗号操作符) 以下的等式是成立的:
scalar(@whatever) == $#whatever - $[ + 1;
Perl 第 5 版改变了 $[ 的语意: 不设置 $[ 的文件不用担心别的文件会否改变自己的 $[ 的值. (换句话说, 不赞成使用 $[) 所以一般可以认为
scalar(@whatever) == $#whatever + 1;
有些程序员选择显式的转换:
$element_count = scalar(@whatever);
如果在数值型上下文里计算哈希表的值, 如果哈希表里有key/value对, 会得到一字符串, 其中包含了该哈希表占据的空间和已经分配的空间, 用 '/' 分开. 这可以检查一下 Perl 的散列算法是否有效. 比如, 在哈希表里存放了 10,000 个元素, 而在数值型上下文里计算 %HASH 得到 ``1/16'', 那情况就太浪费空间了.
数值型的值的表示
数字用习惯的浮点数或整数表示法:
12345 12345.67 .23E-10 0xffff # 16 进制 0377 # 8 进制 4_294_967_296 # 用下划线容易阅读
字符串一般用单引号或双引号括住. 引号的作用和 shell 里类似: 双引号字符串中可以有反斜杠和变量替换; 单引号字符串不行(除了 ``\''' 和 ``\\''). 一般的 Unix 反斜杠替换规则同样用来表示换行符, 制表符...等等. 见列表qq.
换行符可以直接嵌入在字符串中. 但如果忘记了结尾的引号, 直到 Perl 找到另外一行有引号的行前不会报出任何错误. 字符串中的变量替换限制于数值型, 数组和数组的部分. (即以$ 或 @ 打头的标识符, 后跟一个可选的括起来的下标) 下面的代码打印出 "The price is $100."
$Price = '$100'; # not interpreted print &quot;The price is $Price.\n&quot;; # interpreted
和在某些 shell 下一样, 可以用花括号把标识符括住, 以区别于其他后随的字母. 实际上, 花括号中的标识符一定是个字符串, 就象一个哈希表的下标. 早先的例子
$days{'Feb'}
可以写成
$days{Feb}
引号是会被自动加上, 而下标中复杂的部分会被解释为表达式.
要注意, 单引号字符串要和前面的词用空白隔开, 因为单引号本身是可以组成标识符. (参看Packages).
有两个特别的字符串是 __LINE__ 和 __FILE__, 分别代表程序执行点的当前行号和文件名. 它们只能用做分隔记号; 不能被转换成字符串. 此外, __END__ 可以用于在脚本的真正结束位置前标记逻辑结束位置, 在此后的所有文字都被忽略, 但可以通过 DATA 文件句柄读出. (DATA 文件句柄只能从主脚本读取, 不能从required包含的文件或计算的字符串读取) 控制字符 ^D 和 ^Z 是 __END__ 的同义字. (在模块里是 __DATA__ 的同义字; 关于 __DATA__ 的详细说明, 参看SelfLoader)
在语法上没有其他解释的词都被看作一个引起来的字符串. 称为``净词''. 和文件句柄和标号一样, 全是小写字母的净词可能会和将来的保留字冲突, 如果使用 -w 选项, Perl 会对这些词发出警告. 有些人会完全不使用净词. 如果用
use strict 'subs';
那么所有不能被解释为子过程调用的净词都会产生一条编译时刻错误. 严格检查一直到闭合块的结尾为止. 内部块用以下方法可以撤消严格检查 no strict 'subs' .
如果把数组的所有元素连接到一起形成一个双引号字符串, 以变量 $" ( $LIST_SEPARATOR )指定的字符做为分隔符(默认为空格).
$temp = join($&quot;,@ARGV); system &quot;echo $temp&quot;; system &quot;echo @ARGV&quot;;
在搜索模式(也要做替换)中模糊是很糟糕的: 到底 /$foo[bar]/ 是解释为 /${foo}[bar]/ ([bar]/ 是正则表达式字符类) 还是解释成 /${foo[bar]}/ ([bar]/ 是数组下标)? 如果 @foo 不存在, 那么明显是个字符类. 如果 @foo 存在, Perl 要猜测 [bar] 是什么, 一般结果是正确的. 但如果猜错了, 或者你可以用花括号来指示正确的解释方法.
面向行的引用是建立在 shell 的 ``here-doc'' 语法上. 在 << 后面指定一个结束引用的字符串, 当前行的所有后随行直到结束串都是引用的内容. 结束字符串可以是一个标识符(一个词), 或引起来的一段文字. 如果是引起来的文字, 引的方式决定了对文字的处理, 象普通的引用. << 和标识符之间不能够有空白. 如果有空白, 引用到第一个空白行为止. 结束字符串必须单独出现在一行, 前后不能有空白.
print <<EOF; # same as above
The price is $Price.
EOF
print <<``EOF''; # same as above
The price is $Price.
EOF
print <<`EOC`; # execute commands
echo hi there
echo lo there
EOC
print <<``foo'', <<``bar''; # you can stack them
I said foo.
foo
I said bar.
bar
myfunc(<<``THIS'', 23, <<'THAT'');
Here's a line
or two.
THIS
and here another.
THAT
要记得结束的分号, 下面的代码是错误的
print <<ABC
179231
ABC
+ 20;
列表值的表示
列表值用多个用逗号分隔的单独值表示(并用括号括起来):
(LIST)
在非列表的上下文里, 列表的值是最后一个元素的值. 例如,
@foo = ('cc', '-E', $bar);
把整个列表的值赋给数组foo, 而
$foo = ('cc', '-E', $bar);
把变量bar的值赋给变量foo. 注意在数值型的上下文里数组的值是数组的长度; 下面的赋值把 $foo 的值设为 3:
@foo = ('cc', '-E', $bar); $foo = @foo; # $foo gets 3
列表的结束括号前可以有一个逗号:
@foo = ( 1, 2, 3, );
列表会把子列表自动展开. 当列表被求值时, 其中的每个元素都在列表上下文里被展开, 最后的结果形成一张大的列表. 数组在列表里不再独立存在. 下面的列表
(@foo,@bar,&amp;SomeSub) 包含了 @foo 中的所有元素, 后面是 @bar 的所有元素, 再后面是子过程 SomeSub 在列表上下文里被调用时返回的所有元素. 要使用不展开列表的引用, 参看 perlref
空列表用 () 表示. 在列表中展开一个空列表是不产生任何变化. 因此 ((),(),()) 等同于 (). 类似的, 在列表中展开一个空数组也不产生任何变化.
可以把列表当作数组, 用下标去访问它的值. 但必须用括号括起列表以避免产生混乱. 例如:
# Stat 返回列表值 $time = (stat($file))[8]; # 语法错误 $time = stat($file)[8]; # 漏了括号 # Find a hex digit. $hexdigit = ('a','b','c','d','e','f')[$digit-10]; # A &quot;reverse comma operator&quot;. return (pop(@foo),pop(@foo))[0];
Lists may be assigned to if and only if each element of the list is legal to assign to: 只有当可以对列表中的每个元素赋值, 才可以对列表赋值.
($a, $b, $c) = (1, 2, 3); ($map{'red'}, $map{'blue'}, $map{'green'}) = (0x00f, 0x0f0, 0xf00);
在数值型上下文里, 列表的赋值操作的返回值是等号右边的表达式的值.
$x = (($foo,$bar) = (3,2,1)); # $x = 3, 不是 2 $x = (($foo,$bar) = f()); # $x 等于 f() 的返回值
当在布尔型的上下文里进行列表赋值操作时, 检查返回值是有意义的. 大部分列表函数结束的时候返回空列表, 即数值 0, 布尔值 FALSE.
待赋值列表的最后一个元素可以是哈希表:
($a, $b, @rest) = split; local($a, $b, %rest) = @_;
实际上, 赋值的时候可以把哈希表放在列表的任何位置, 但列表中第一个出现的哈希表会取去所有的值, 后面的元素都得到一个空值. 这个特点在 local() 或 my() 里可能会有用.
哈希表的元素是一对对的数值, 即键和值的对应.
# same as map assignment above %map = ('red',0x00f,'blue',0x0f0,'green',0xf00);
列表和数组之间通常是可以互相转换的, 但不适用于哈希表. 列表中的元素可以象数组一样用下标去访问, 但不能用键去访问列表元素.
在哈希表的键和值之间使用 => 操作符会更加明白. => 操作符是逗号的同意词, 但兼有引起左操作数的功能, 特别适用于哈希表的初始化.
%map = ( red =&gt; 0x00f, blue =&gt; 0x0f0, green =&gt; 0xf00, );
或者是初始化哈希表的引用, 当作记录使用.
$rec = { witch =&gt; 'Mable the Merciless', cat =&gt; 'Fluffy the Ferocious', date =&gt; '10/31/1776', };
或是用于按名调用的复杂函数:
$field = $query-&gt;radio_group( name =&gt; 'group_name', values =&gt; ['eenie','meenie','minie'], default =&gt; 'meenie', linebreak =&gt; 'true', labels =&gt; \%labels );
要记住哈希表的初始化顺序不等于数据的存放顺序. 参看 sort 中有关对输出进行排序的例子
Typeglobs 和 文件句柄
Perl 用一个叫 typeglob 的内部类型来保存一条整份符号表表项. typeglob 的类型前缀是 *, 代表任意的类型. 它原来是给函数传递数组和哈希表引用的方法, 不过现在有了真正的引用, 就很少用到了.
有一个地方还会用到 typeglobs, 就是传递或存放文件句柄的时候. 要保存一个文件句柄, 可以这样做:
$fh = *STDOUT;
或者用真正的引用:
$fh = \*STDOUT;
这也是建立局部文件句柄的方法. 例如:
sub newopen { my $path = shift; local *FH; # 不用 my! open (FH, $path) || return undef; return \*FH; } $fh = newopen('/etc/passwd');
欲知更多有关 typeglobs 的信息, 参看 perlref , perlsub , 和 ``Symbols Tables'' . 欲知其他生成文件句柄的方法, 参看 open .