Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3056267
  • 博文数量: 535
  • 博客积分: 15788
  • 博客等级: 上将
  • 技术积分: 6507
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-07 09:11
文章分类

全部博文(535)

文章存档

2016年(1)

2015年(1)

2014年(10)

2013年(26)

2012年(43)

2011年(86)

2010年(76)

2009年(136)

2008年(97)

2007年(59)

分类:

2009-03-10 16:33:03

20090907:

任何没有和 my 关联在一起的变量声明都是和一个包相关联的——甚至是一些看起来无所不在的变量,比如 $_ 和 %SIG。实际上,在 Perl 里实际上没有全局变量这样的东西。(特殊的标识符,比如 _ 和 SIG,只是看上去象全局变量,因为它们缺省时属于 main 包,而不是当前包。)

一个包的内容总体在一起称做符号表。符号表都存储在一个散列里,这个散列的名字和该包的名字相同,但是后面附加了两个冒号。因此 main 符号表的名字是 %main::。因为 main 碰巧也是缺省的包,Perl 把 %:: 当作 %main:: 的缩写。

类似,Red::Blue 包的符号表名字是 %Red::Blue::。同时 main 符号表还包含所有其他顶层的符号表,包括它本身。因此 %Red::Blue:: 同时也是 %main::Red::Blue::。


当我们说到一个符号表“包含”其他的符号表的时候,我们的意思是它包含一个指向其他符号表的引用。


类型团(typeglob)用以保留整个符号表记录。(符号表记录 *foo 包括 $foo, @foo, %foo,&foo 和其他几个 foo 的简单解释值。)


一个对象只不过是一个引用...恩,就是引用。

一个类只是一个包

一个包当作一个类——通过使用包的子过程来执行类的方法,以及通过使用包的 变量来保存类的全局数据。通常,使用一个模块来保存一个或者更多个类。

一个方法只是一个子过程 

方法和普通子过程之间的一个区别是,它们的包在什么时候被解析——也就是说,Perl 什么时候决定应该执行该方法或者子过程的哪些代码。子过程的包是在你的程序开始运行之前,在编译的时候解析的。相比之下,一个方法包直到实际调用的时候才解析。


Perl 支持调用方法的两种不同的语意。不管使用哪种方法调用的形式,Perl 总是会给构成这个方法的子过程传递一个额外的初始化参数。如果用一个类调用该方法,那个参数将会是类的名字。如果用一个对象调用方法,那个参数就是对象的引用。不管是什么,我们都叫它方法调用者。对于类方法而言,调用者是包的名字。对于一个实例方法,调用者是调用者是一个声明对象的引用。

换句话说,调用者就是调用方法的那个东西。从文法上看,调用者既不是动作的对象也不是动作的承担者。它更象一个间接的对象,是代表动作执行后受益人的东西,就向在命令“给我铸把剑!”里的“我”一样。从语意上来看,你既可以把调用者看作动作的施动者,也可以把它看作动作的受动者。

第一种调用方法的风格看起来象下面这样:

   INVOCANT->METHOD(LIST)
   INVOCANT->METHOD

第二种风格的方法调用看起来象这样:

   METHOD INVOCANT (LIST)
   METHOD INVOCANT LIST
   METHOD INVOCANT

所有对象都是引用,但不是所有引用都是对象。一个引用不会作为对象运转,除非引用它的东西有特殊标记告诉 Perl 它属于哪个包。把一个引用和一个包名字标记起来(因此也和包中的类标记起来了,因为一个类就是一个包)的动作被称作赐福(blessing),你可以把赐福(bless)看作把一个引用转换成一个对象,尽管更准确地说是它把该引用转换成一个对象引用。

bless 函数接收一个或者两个参数。第一个参数是一个引用,而第二个是要把引用赐福(bless)成的包。如果忽略第二个参数,则使用当前包。

   $obj = { };            # 把引用放到一个匿名散列
   bless($obj);         # Bless 散列到当前包
   bless($obj, "Critter");     # Bless 散列到类 Critter。


a simple rule: whenever you need a variable in Perl, you can use an assignment instead. Perl does the assignment and then it uses the variable in whatever way you requested. The most common use of chomp looks like this:

    chomp($text = ); # Read the text, without the newline character

1、perl 也可以使用``执行shell命令
$info = `finger $user`;
反勾号的一般形式是 qx//(意思是“引起的执行”),但这个操作符的作用完全和普通的反勾号一样。
$perl_info = qx(ps $$); # 这里 $$ 是 Perl 的处理对象 $perl_info = qx'ps $$'; # 这里 $$ 是 shell 的处理对象

2、&& 和 || 操作符和 C 不同的是,它们不返回 0 或 1,而是返回最后计算的值。

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

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

这样的短路不仅节约时间,而且还常常用于控制计算的流向。比如,一个经常出现的 Perl 程序的俗语是:
open(FILE, "somefile") || die "Can't open somefile: $!\n"; 
Perl 还提供优先级比较低的 and 和 or 操作符,这样程序的可读性更好而且不会强迫你在列表操作符上使用圆括弧。它们也是短路的
open(FILE, "somefile") or die "Can't open somefile: $!\n"; 

你不能简单地把所有 || 替换为 or。假设你把:
   $xyz = $x || $y || $z;

改成:

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



3、Perl 并不要求明确的变量声明;变量只在第一次使用的时候才存在,不管你是否曾声明它们。如果你试图从一个从未赋值的变量里面获取一个值,当你把它当作数字时 Perl 会被悄悄地把它当 0 看待,而当作字串时会把它当作""(空字串),或者做逻辑值用的时候就是假。

4.1一个简单语句是一个表达式,因为副作用而计算。每条简单语句都必须以分号结尾,除非它是一个块中的最后语句。这种情况下,分号是可选的
任何简单语句的后面都允许跟着一条简单的修饰词,紧接着是结束的分号(或块结束)。可能的修饰词有:
   if EXPR
   unless EXPR
   while EXPR
   until EXPR
   foreach LIST
if 和 unless 修饰词和他们在英语里的作用类似:
   $trash->take('out') if $you_love_me;
   shutup() unless $you_want_me_to_leave;

while 和 until 修饰词重复计算。如你所想,while 修饰词将不停地执行表达式,只要表达式的值为真,或者 until 里只要表达式为假则不断执行表达式。

   $expresion++ while -e "$file$expression";
   kiss('me') until $I_die;

foreach 修饰词(也拼为 for)为在其 LIST 里的每个元素计算一次,而 $_ 是当前元素的别名:

   s/java/perl/ for @resumes;
   print "field: $_ \n" foreach split /:/, $dataline;

while 和 until 修饰词有普通的 while 循环的语意(首先计算条件),只有用于do BLOCK(或者现在已经过时的 do SUBROUTINE 语句)里是个例外,在此情况下,在计算条件之前,先执行一次语句块。这样你就可以写下面这样的循环:

   do {
      $line = 
      ...
   } until $line eq ".\n"

4.3 循环语句

所有循环语句在它们正式语法里有可选的 LABEL(标记)。(你可以在任何语句里放上标记,但是它们对循环有特殊含义。)如果有标记,它由一个标识后面跟着一个冒号组成。通常标记都用大写以避免与保留字冲突,并且这样它也比较醒目。

下面的语句可以用于控制 BLOCK 的条件和重复执行。(LABEL 部分是可选的。)

if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif (EXPR) BLOCK ...
if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK

unless (EXPR) BLOCK
unless (EXPR) BLOCK else BLOCK
unless (EXPR) BLOCK elsif (EXPR) BLOCK ...
unless (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK

LABEL while (EXPR) BLOCK
LABEL while (EXPR) BLOCK continue BLOCK

LABEL until (EXPR) BLOCK
LABEL until (EXPR) BLOCK continue BLOCK

LABEL for (EXPR; EXPR; EXPR) BLOCK

LABEL foreach (LIST) BLOCK
LABEL foreach VAR (LIST) BLOCK
LABEL foreach VAR (LIST) BLOCK continue BLOCK

LABEL BLOCK
LABEL BLOCK continue BLOCK
4.3.1 while 和 until 语句
while 和 until 语句可以有一个额外的块:continue 块。这个块在整个块每继续一次都执行一次,不管是退出第一个块还是用一个明确的 next。

4.3.2 for循环

分成三部分的 for 循环在其圆括弧里有三个用分号隔离的表达式。这些表达式分别代表循环的初始化,条件和再初始化表达式。所有三个表达式都是可选的(不过分号不是);如果省略了它们,则条件总是为真。因此三部分的 for 表达式可以用对应的 while 循环来代替。

LABEL: for( my $i = i; $i <= 10; $i++ ) { ... }

如果你想同时使用两个变量,只需要用逗号分隔平行的表达式即可:

   for ( $i = 0, $bit = 0; $i < 32; $i++, $bit <<=1) {
      print "Bit $i is set\n" if $ mask & $bit;
   }
   # $i 和 $bit里的值超越循环继续存在

4.3.3 foreach 循环

foreach 循环通过把控制变量(VAR)设置为每个后继的列表元素来循环通过一列数值:

   foreach VAR (LIST) {
      ...
   }
foreach 键字只是 for 键字的一个同义词,所以,只要你觉得哪个可读性更好,你就可以互换地使用 for 和 foreach。如果省略 VAR,则使用全局 $_ 变量。
$sum = 0; foreach $value (@array) { $sum += $value }

foreach $key (sort keys %hash) {
    print "$key => $hash{$key}\en";
}
上面一句是打印一个排了序的散列数组的规范的方法。
循环变量在循环前拥有的值将在循环退出之后自动恢复。

4.3.4 循环控制

我们说过,你可以在一个循环上放一个 LABEL(标记),这样就给它一个名字。循环的LABEL 为循环的循环控制操作符 next,last,和 redo 标识循环。LABEL 是给整个循环取名,而不仅仅是循环的顶端。因此,一个引用了循环(标记)的循环控制操作符实际上并不是“go to”(跑到)循环标记本身。就计算机而言,该标记完全可以放在循环的尾部。但是不知什么原因人们喜欢把标记放在东西的顶部。

循环的典型命名是命名为每次循环所处理的项目。这样和循环控制操作符交互地很好,因为循环控制操作符的设计原则是:当合适的标记和语句修饰词一起使用时,它读起来应该象英文。如果循环原型是处理行的,那循环标记原型就是LINE:,因此循环控制操作符就是类似这样的东西:

   next LINE if / ^#/;   # 丢弃注释

循环控制操作符的语法是:

   last LABEL
   next LABEL
   redo LABEL
LABEL 是可选的;如果忽略了,该操作符就选用最内层的封闭循环。但是如果你想跳过比一层多的(循环),那你就必须用一个 LABEL 以命名你想影响的循环。

last 操作符立即退出当事循环。如果有 continue 块也不会执行。下面的例子在第一个空白行撤出循环:

   LINE: while () {
      last LINE if /^$/;      # 当处理完邮件头后退出
   }

next 操作符忽略当前循环的余下的语句然后开始一次新的循环。如果循环里有 continue 子句,那它将就在重新计算条件之前执行,就象三部分的 for 循环的第三个部件一样。因此 continue 语句可以用于增加循环变量——即使是在循环的部分语句被 next 终止了的情况下:

   LINE: while () {
      next LINE if /^#/;      # 忽略注释
      next LINE if /^$/;      # 忽略空白行
      ...
   } continue {
      $count++;
   }

redo 操作符在不重新计算循环条件的情况下重新开始循环语句块。如果存在 continue 块也不会执行。这个操作符通常用于那些希望在刚输入的东西上耍点小伎俩的程序。假设你正在处理这样的一个文件,这个文件里你时不时要处理带有反斜杠续行符的行。下面就是你如何利用 redo 来干这件事:

    while (<>) {
      chomp;
      if (s/\\$//) {
         $_ .= <>;
         redo unless eof;      # 不超过每个文件的eof
      }
      # 现在处理 $_ 
   }

4.4 光块

一个 BLOCK本身(带标记或者不带标记)等效于一个执行一次的循环。所以你可以用 last 退出一个块或者用 redo 重新运行块(注:相比之下,next 也退出这种一次性的块。不过有点小区别:next 会执行一个 continue 块,而 last 不会。)


4.4.1 分支(case)结构

和其他一些编程语言不同的是,Perl 没有正式的 switch 或者 case 语句。这是因为 Perl 不需要这样的东西,它有很多方法可以做同样的事情。一个光块就是做条件结构(多路分支)的一个很方便的方法。下面是一个例子:

   SWITCH: {
      if (/^abc/)   { $abc = 1; last SWITCH; }
      if (/^def/)   { $def = 1; last SWITCH; }
      if (/^xyz/)   { $xyz = 1; last SWITCH; }
      $nothing = 1;
   }

这里是另外一个:

   SWITCH: {
      /^abc/   && do { $abc = 1;   last SWITCH; };
      /^def/   && do { $def = 1;   last SWITCH; };
      /^xyz/   && do { $xyz = 1;   last SWITCH; };
   }

或者是把每个分支都格式化得更明显:

   SWITCH: {
      /^abc/   && do {
            $abc = 1;
            last SWITCH;
         };
      /^def/   && do {
            $def = 1;
            last SWITCH;
         };
      /^xyz/   && do {
            $xyz = 1;
            last SWITCH;
         };

}。
4.7.1 范围变量声明

Perl 的三种范围声明让它很容易做下面这些事:创建私有变量(用 my),进行有选择地访问全局变量(用 our),和给全局变量提供临时的值(用 local):

   my $nose;
   our $House;
   local $TV_channel;
如果列出多于一个变量,那么列表必须放在圆括弧里。就 my 和 our 而言,元素只能是简单的标量,数组或者散列变量。就 local 而言,其构造可以更宽松:你还可以局部化整个类型团和独立的变量或者数组和散列的片段:
   my($nose, @eyes, %teeth);
   our ($House, @Autos, %Kids);
   local (*Spouse, $phone{HOME});
上面每种修饰词都给它们修饰的变量做出某种不同类型的“限制”。简单说:our 把名字限于一个范围,local 把值限于一个范围以及 my 把名字和值都限于一个范围。
最常见的声明形式是 my,它定义词法范围的变量;这些变量的名字和值都存储在当前范围的临时中间变量暂存器里,而且不能全局访问。与之相近的是 our 声明,它在当前范围输入一个词法范围的名字,就象 my 一样,但是实际上却引用一个全局变量,任何人如果想看地话都可以访问。换句话说,就是伪装成词汇的全局变量。

5.2 模式匹配操作符

从动物学角度来说,Perl 的模式匹配操作符函数是某种用来关正则表达式的笼子。我们是有意这么设计的;如果我们任由正则怪兽在语言里四处乱逛,Perl 就完全是一个原始丛林了。当然,世界需要丛林——它们是生物种类多样性的引擎,但是,丛林毕竟应该放在它们应该在的位置。一样,尽管也是组合多样化的引擎,正则表达式也应该放在它们应该在的模式匹配操作符里面。那里是另外一个丛林。

m//,s///,和 tr/// 都是引号包围操作符,所以所有的双引号代换都有效,包括变量代换(除非你用单引号做分隔符)和用反斜杠逃逸标识的特殊字符。

=~ 和 !~ 操作符把它们左边的标量表达式和在右边的三个引起类操作符之一绑定在一起: m// 用于匹配一个模式,s/// 用于将某个符合模式的子字串代换为某个字串,而 tr/// (或者其同义词,y///)用于将一套字符转换成另一套。(如果把斜杠用做分隔符,你可以把 m// 写成 //,不用写 m。)如果 =~ 或 !~ 的右手边不是上面三个,它仍然当作是 m// 匹配操作,不过此时你已经没有地方放跟在后面的修饰词了(参阅后面的“模式修饰词”),并且你必须操作自己的引号包围:

   print "matches"   if   $somestring =~ $somepattern;

不过,我们实在没道理不明确地说出来:

   print "matches" if   $somestring =~ m/$somepattern/;
当用于匹配操作时,有时候 =~ 和 !~ 分别读做“匹配”和“不匹配”(因为“包含”和“不包含”会让人觉得有点模糊)。

除了在 m// 和 s/// 操作符里使用外,在 Perl 的另外两个地方也使用正则表达式:
split 函数的第一个参数是一个特殊的匹配操作符,它声明的是当把字串分解成多个子字串后不返回什么东西
qr// (“引起正则表达”)操作符同样也通过正则表达式声明一个模式,但是它不是为了匹配匹配任何东西(和 m// 做的不一样)。相反,编译好的正则表达的形式返回后用于将来的处理。

因为 s/// 和 tr/// 修改它们所处理的标量,因此你只能把它们用于有效的左值: 
"onshore" =~ s/on/off/; # 错;编译时错误
不过,m// 可以应用于任何标量表达式的结果

但是,在这里你得更小心一些,因为 =~ 和 !~ 的优先级相当高——在一些情况下,左边的项的圆括弧是必须的。!~ 绑定操作符作用和 =~ 类似,只是把逻辑结果取反

每次成功匹配了一个模式(包括替换中的模式),操作符都会把变量 $`,$&,和 $' 分别设置为匹配内容左边内容,匹配的内容和匹配内容的右边的文本。这个功能对于把字串分解为组件很有用:

$`, $&, $' 和排序的变量都是全局变量,它们隐含地局部化为属于此闭合的动态范围。它们的存在直到下一次成功的匹配或者当前范围的结尾,以先到者为准。

"hot cross buns" =~ /cross/;
print "Matched: <$`> $& <$'>\n";    # Matched:  cross < buns>
print "Left:    <$`>\n";            # Left:    
print "Match:   <$&>\n";            # Match:   
print "Right:   <$'>\n";            # Right:   < buns>

为了有更好的颗粒度和提高效率,你可以用圆括弧捕捉你特别想分离出来的部分。每对圆括弧捕捉与圆括弧内的模式相匹配的子模式。圆括弧由左圆括弧的位置从左到右依次排序;对应那些子模式的子字串在匹配之后可以通过顺序的变量 $1,$2,$3 等等获得:

   $_ = "Bilbo Baggins's birthday is September 22";
   /(.*)'s birthday is (.*)/;
   print "Person: $1\n";
   print "Date: $2\n";


5.2.1 模式修饰词
你可以在一个 m//,s///,qr//,或者 tr/// 操作符的最后一个分隔符后面,以任意顺序放一个或多个单字母修饰词。

m//,s/// 和 qr// 操作符(tr/// 操作符并不接受正则表达式,所以这些修饰词并不适用。)的最后一个分隔符后面都接受下列修饰词:

/i 忽略字母的大小写(大小写无关)
/s 令 . 匹配换行符并且忽略不建议使用的 $* 变量
/m 令 ^ 和 $ 匹配下一个嵌入的 \n。
/x 忽略(大多数)空白并且允许模式中的注释
/o 只编译模式一次

,/x 修改空白字符(还有 # 字符)的含义:它们不再是普通字符那样的自匹配字符,而是转换成元字符,这些元字符的特征类似空白(和注释字符)。因此,/x 允许(在模式里面)将空白,水平制表符和换行符用于格式化,就象普通 Perl 代码一样。它还允许用通常在模式里没有特殊含义的 # 字符引入延伸到当前模式行行尾的注释。

5.2.2 m// 操作符(匹配)
m// 操作符搜索标量 EXPR 里面的字串,查找 PATTERN。如果使用 / 或 ? 做分隔符,那么开头的 m 是可选的。? 和 ' 做分隔符时都有特殊含义:前者表示只匹配一次;后者禁止进行变量代换和六种转换逃逸

/i 或略字母大小写
/m 令 ^ 和 $ 匹配随后嵌入的 \n。
/s 令 . 匹配换行符并且忽略废弃了的 $*。
/x 或略(大多数)空白并且允许在模式里的注释
/o 只编译模式一次
/g 全局地查找所有匹配
/cg 在 /g 匹配失败后允许继续查找

5.2.3 s/// 操作符(替换)


   LVALUE =~ s/PATTERN/REPLACEMENT/egimosx
   s/PATTERN/REPLACEMENT/egimosx

这个操作符在字串里搜索 PATTERN,如果找到,则用 REPLACEMENT 文本替换匹配的子字符串。

一个 s/// 操作符的返回值(在标量和列表环境里都差不多)是它成功的次数(如果与 /g 修饰词一起使用,返回值可能大于一)。如果失败,因为它替换了零次,所以它返回假(""),它等效于数字 0。

替换部分被当作双引号包围的字串看待。你可以在替换字串里使用我们前面描述过的任何动态范围的模式变量($`,$&,$',$1,$2,等等),以及任何其他你准备使用的双引号包围的小发明。比如下面是一个小例子,用于找出所有字串 "revision","version",或者 "release",并且用对应的大写字串替换,我们可以用 \u 逃逸处理替换的目标部分:

   s/revision|version|release/\u$&/g;   # | 用于表示模式中的“或”


表 5-2 s/// 修饰词

/i 或略字母大小写
/m 令 ^ 和 $ 匹配随后嵌入的 \n。
/s 令 . 匹配换行符并且忽略废弃了的 $*。
/x 或略(大多数)空白并且允许在模式里的注释
/o 只编译模式一次
/g 全局地查找所有匹配
/e 把右边当作一个表达式计算

/e 修饰词把 REPLACEMENT 当作一个 Perl 代码块,而不仅仅是一个替换的字串。执行这段代码后得出的结果当作替换字串使用。比如,s/(0-9)+)/sprintf("%#x", $1)/ge 将把所有数字转换成十六进制
5.2.3.1 顺便修改一下字串

有时候你想要一个新的,修改过的字串,而不是在旧字串上一阵乱改,新字串以旧字串为基础。你不用写:

   $lotr = $hobbit;
   $lotr =~ s/Bilbo/Frodo/g;

你可以把这些组合成一个语句。因为优先级关系,必须在赋值周围使用圆括弧,因为它们大多和使用了 =~ 的表达式结合在一起。

   ($lotr = $hobbit ) =~ s/Bilbo/Frodo/g;

你不能对数组直接使用 s/// 操作符。这时,你需要一个循环。幸运的是,for/foreach 的别名特性加上它把 $_ 当作缺省循环变量,这样就产生了Perl 标准的用于搜索和替换一个数组里每个元素的俗语:

   for (@chapters) { s/Bilbo/Frodo/g }      # 一章一章的替换
   s/bilbo/Frodo/g for @chapters;         # 一样的东西

5.3.3 通配元符号

三个特殊的元符号可以用做通用通配符,它们的每一个都可以匹配"任何"字符(是"任何"中的某些字符)。它们是句点("."),\c 和 \x。

句点元字符匹配除了换行符以外的任何单字符。(如果带着 /s 修饰词,也能匹配换行符。)

点元字符经常和量词一起使用。.* 匹配尽可能多的字符,而 .*? 匹配尽可能少的字符。


5.4 字符表

5.4.2 典型 Perl 字符表缩写

从一开始,Perl 就已经提供了一些字符表缩写。它们在表 5-8 中列出。它们都是反斜杠字母元字符,而且把它们的字母换成大写后,它们的含义就是小写版本的反义。

表 5-8 典型字符表

\d 数字 [0-9] \p{IsDigit}
\D 非数字 [^0-9} \P{IsDigit}
\s 空白 [ \t\n\r\f] \p{IsSpace}
\S 非空白 [^ \t\n\r\f] \P{IsSpace}
\w [a-zA-Z0-9_] \p{IsWord}
\W 非字 [^a-zA-Z0-9_] \P{IsWord}

第六章 子过程

2.0 语意

在你记住所有语法前,你只需要记住下边这种定义子过程的普通方法:

sub razzle {
        print "Ok, you've been razzled.\n";
}

和调用子过程的正常方法就是:

razzle();

在上边的写法中,我们省略了输入(参数)和输出(返回值).但是 Perl 向子过程中传入数据和子过程传出数据的方法非常简单:所有传入的参数被当成单个平面标量列表,类似的多个返回值也被当成单个平面标量列表返回给调用者.

所有传入 Perl 过程的参数都是以 @_ 身份传入的.如果你调用一个有两个参数的函数,它们在函数内部可以作为 @_ 数组的前两个成员访问: $_[0] 和 $_[1].因为 @_ 只是一个有着奇怪名字的普通数组,所以你可以象处理普通数组一样随意处理它.数组 @_ 是一个本地数组,但是它的值是实际标量参数的别名(通常称为引用传参)因而如果修改了 @_ 中的成员那么同时也修改了对应的实际参数的值.

子过程(其他的程序块也一样)的返回值是过程最后一个表达式的值.或者你可以在子过程的任何一个地方明确使用一个 return 语句来返回值并且退出子过程.不管是那种方法,当在一个标量或列表环境中调用子过程时,最后一个表达也将在同样的标量或列表环境中求值.


2.1 参数列表的技巧

Perl 没有命名的正式参数,但是在实际中你可以将 @_ 的值拷贝到一个 my 列表,这样就可以方便使用这些正式参数(不一样的是,这样拷贝就将引用传参的语义变为了传值传参,也许传值传参正是很多用户通常希望参数被处理的方法,即使他们不知道这些计算机术语),下面是一个典型的例子:

sub aysetenv {
        my ($key, $value) = @_;
        $ENV{$key} = $value unless $ENV{$key};
}

但是没人要你一定要给你的参数命名,这就是 @_ 数组的全部观点

或者你可以一次将 @_ 填入一个散列:

sub configuration {

        my %options = @_;
        print "Maximum verbosity.\n" if $options{VERBOSE} == 9;
}


引用

符号引用只是一个字串,它的值碰巧和包的符号表里什么东西的名字相同。它和你平时处理的字串没有什么太大的区别。但是硬引用却是完全不同的家伙。它是三种基本的标量类型中的第三种,其他两种是字串和数字。硬引用除了指向某些事物之外并不知道它们的名字,并且这些引用物在一开始的时候并没有名字也是非常正常的事情。这样的未名引用叫做匿名


在本章的术语里,引用一个数值就是创建一个指向它的硬引用。(我们有一个操作符用于这种创建动作)。这样创建的引用只是一个简单的标量,它和所有其他标量一样在我们熟悉的环境里有着一样的行为。给这个标量解引用(析引用)意味着我们使用这个引用访问引用物。引用和解引用都是只发生在某些明确的机制里,在 Perl 里从来不会出现隐含地引用或解引用的现象。哦,是几乎从来不发生。


注意 $array[3] 和 $array->[3] 是不一样的。第一个东西讲的是 @array 里的第四个元素,而第二个东西讲的是一个保存在 $array 里的数组(可能是匿名的数组)引用的第四个元素。


将一个列表转化成一个标量

$wife{"Jacob"} = ("Leah", "Rachel", 'Bilhah", "Zilpah");   # 错

但是这并不能象你希望的那样运转,因为在 Perl 中括弧和逗号还不够强大,还不能将一个列表转换成为标量(在语法中,圆括弧用于分组,逗号用于分隔)。你需要明确地告诉 Perl 你想将一个列表当成一个标量。[] 中括弧能够实现这个转换:

   $wife{"Jacob"} = ["Leah", "Rachel", "Bilhah", "Zilpah"];   # 正确 

引用

你可以用方括弧创建一个创建一个指向匿名数组的引用: 

$arrayref = [1, 2, ['a', 'b', 'c', 'd']];


你可以用花括弧创建一个指向匿名散列的引用:

$hashref = {
        'Adam' => 'Eve',
        'Clyde' => $bonnie,
        'Antony' => 'Cleo' . 'patra',
};

你可以通过用不带子过程名字的 sub 创建一个匿名子过程:

   $coderef = sub { print "Boink!\n" };

你可以通过引用同名的类型团来创建指向文件句柄或者目录句柄:

splutter(\*STDOUT);




阅读(1389) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~