perl语言编程 第二章 集腋成裘 上
上一篇 / 下一篇 2008-04-18 11:39:04 / 个人分类:关于脚本语言
因为我们准备从小处开始,所以我们在随后几章里将逐步从小到大。也就是说,我们将发起一次从零开始的长征,从 Perl 程序里最小的构件开始,逐步把它们更精细的组件,就象分子是由原子组成的那样。这样做的缺点是你在卷入细节的洪流之前没有获得必要的全景蓝图。这样做的好处是你能随着我们的进展理解我们的例子。(当然,如果你是那种从宏观到微观的人,你完全可以把书反过来,从后面开始向前看。)
每一章都是建筑在前面章节的基础之上的(如果你从后向前看,就是后面几章),所以如果你喜欢这看看那看看,那你最好看得仔细一点。
我们还建议你在看书的过程中也抽空看看本书末尾的参考资料。(这可不算随处翻看。)尤其是任何以打字机字体(黑体)隔离的文字可能都会在第二十九章,函数,里面找到。同时,尽管我们力图做到与操作系统无关,但如果你对一些 Unix 术语不熟悉并且碰到一些看起来没有意义的词语,你就应该检查一下这些词语是否出现在术语表里。如果术语表里没有,那可能在索引里。
2.1 原子
尽管在我们现在解说的事物背后还有许多看不见的事物在发生作用,通常你在Perl里面用到的最小的东西是字符。这里我们确实说的是字符,在历史上,Perl 很自由地混淆字节和字符这两个概念,不过在如今的全球网络化的时代,我们必须仔细地区分这两个概念。
Perl 当然可以完全用7位(bit)的 ASCII 字符集组成。Perl 同样还允许你用任何 8 位或 16 位字符集书写程序,不管这些字符集是国家字符集还是其他什么传统的字符集。不过,如果你准备用这些古老的非 ASCII 字符集书写程序,可能在你的字符串的文字里只能使用非 ASCII 字符集。你必须负责保证你的程序的语意和你选择的国家字符集是一致的。比如说,如果你正在将 16 位的编码用于亚洲国家字符集,那么你要记住 Perl 会认为你的每个字符是两个字节,而不是一个字符。
象我们在第十五章,Unicode,里面描述的那样,我们最近为 Perl 增加了Unicode的支持(注:尽管我们对能支持 Unicode 感到非常激动,我们的大部分例子仍然是 ASCII 编码的,因为不是每个人都有一个好的 Unicode 编辑器。)。这个支持是遍及这门语言全身的:你可以用 Unicode 字符做标识符(比如说变量名),就象在文本串里使用那样。当你使用 Unicode 的时候,用不着担心一个字符是由几个位或者几个字节组成的。Perl 只是假装所有 Unicode 字符都是相同大小(也就是说,尺寸为 1),甚至任意字符在内部都是由多个字节表示的。Perl 通常在内部把 Unicode 表示为 UTF-8 —— 一种变长编码方式。(比如,一个 Unicode 的笑脸字符,U-263A,在内部会表现为一个三字符序列。)
如果让我们把与物理元素的类比进行得更深入一些,字符是基本粒子,就象不同元素里面独立的原子一样。的确,字符是由位和字节这些更小的粒子组成的,但是如果你把一个字符分割开(当然是在一个字符加速器里),这些独立的位和字节就会完全失去那些赖以区分字符的化学属性。就象中子是铀-238原子的实现细节一样,字节也是 U-236A 字符的实现细节。
所以当我们提及字符时,我们会小心地用“字符”这个字眼,而提及字节时,我们会用“字节”。不过我们不是想吓唬你——你仍然可以很容易的做那些老风格的字节处理。你要干的事只是告诉 Perl 你依然想把字节当作字符处理。你可以用 use bytes 用法来实现这个目的(参阅第三十一章,实用模块)。不过即使你不这样做,Perl 还是能很不错地在你需要的时候把小字符保存在 8 个位里面。
所以不要担心这些小的方面。让我们向更大和更好的东西前进。
2.2 分子
Perl 是自由格式语言,不过也不意味着 Perl 就完全是自由格式。象计算机工作者常说的那样,自由格式的语言就是说你可以在你喜欢的任何地方放空白,制表符和新行等字符(除了不能放的地方以外)。
有一个显然不能放空白的地方就是在记号里。一个记号就是有独立含义的一个字符序列,非常象自然语言中的单字。不过和典型的字不一样的地方是记号可能含有除了字符以外的其他字符——只要它们连在一起形成独立的含义。(从这个意义来看,记号更象分子,因为分子不一定要由一种特定的原子组成。)例如,数字和数学操作符都是记号。一个标识符是由字母或下划线开头的,只包括字母,数字,和下划线。记号里面不能有空白,因为那样会把它分成两个记号,就象在英文里面空白把单词分成两个单词一样。(注:聪明的读者可能会说在文本串里可以包含空白字符。但是字符串只有在两边都用双引号括起来才能保证空白不会漏出去。)
尽管允许空白出现在任何两个记号中间,只有在两个记号放在一起会被误认为是一个记号的时候才要求一定要在中间放空白。用于这个目的的时候所有空白都是一样的。新行只有在引号括起的字串,格式(串)和一些面向行的格式的引用中才和空白或(水平)制表符(tab)不一样。具体来说,换行符不象某些语言一样(比如 FORTRAN 或 Python)是语句的结束符。Perl 里的语句是用分号结束的,就象在 C 里面和 C 的许多其他变种一样。
Unicode 的空白允许出现在 Unicode 的 Perl 程序里面,不过你要小心处理。如果你应用了特殊的 Unicode 段落和分行符,请注意Perl可能会和你的文本编辑器计算出不同的行数来,因此错误信息可能会变得更难处理。最好是依然使用老风格的新行符。
记号的识别是贪多为胜的;如果在某一点让 Perl 的分析器在一长一短两个记号之间做选择,她会选择长的那个。如果你的意思是两个记号,那就在它们中间插入一些空白。(为了增加可读性我们总是在大多数操作符周围放上额外的空白。)
注释用 # 字符标记,从这个字符开始到行尾。一个注释会被当作分隔记号的空白。Perl 语言对你放到注释里面的东西不附加任何特殊含义。(注:实际上,这里撒了个小谎,不过无伤大雅。Perl 的分析器的确在 #! 开头的行里查找命令行开关(参阅第十九章,命令行接口)。它还可以分析各种预处理器产生的各种行数标识(参阅第二十四章,普通实践,的“在其他语言里生成 Perl”节)。
另外一个例外是,语句里任何地方如果存在以 = 开头的行都是合法的,Perl 将忽略从这一行开始直到下一个由 =cut 开头的行。被忽略的文本将被认为是 pod,或“plain old documentation(简单的旧文档)”。Perl 的发布版本里面有一个程序可以从 Perl 模块里抽取 pod 注释输出到一个平面文本文件,手册页,LATEX,HTML,或者(将来某一天)XML文档里。Perl 分析器会从 Perl 模块里面抽取 Perl 代码并且忽略 pod。因此你可以把这个方法当作一种可用的多行注释方法。你也完全可以认为它是一种让人头疼的东西,不过这样做可以使 Perl 模块不会丢失它的文档。参阅第二十六章,简单旧文档,那里有关于 pod 的细节,包括关于如何有效的在 Perl 里面使用多行注释的描述。
不过可不要小看普通的注释字符。用一列整齐的 # 注释的多行文本可以有舒服的视觉效果。它马上就告诉你的眼睛: “这些不是代码。”你可能注意到即使象 C 这样有多行注释机制的语言里,人们都总是在他们注释的左边放上一行 * 字符。通常外观要比仅仅出现更重要。
在 Perl 里,就象在化学和语言里一样,你可以从小的东西开始建造越来越大的结构。我们已经提到过语句了;它只是组成一条命令,就是说,一个祈使句。你可以用花括弧把一系列语句组成块。小块可以组装成大块。若干块组成子函数,然后子函数可以组成模块,模块可以合并到程序里面去。不过我们这里已经走得太远了 ——那些是未来若干章的内容。先让我们从字符里面组建更多的记号开始吧。
2.3 内置的数据类型
在我们开始讲述各种各样的用字符组建的记号之前,我们先要做一些抽象。具体说来,就是我们需要三种数据类型。
计算机语言因它们支持的数据类型的多寡和类别而不同。一些常用的语言为类似的数值提供了许多让人易混的数据类型, Perl 不一样,它只提供了少数几种内建的数据类型。让我们看看 C,在 C 里你可能会碰到char,short,int,long, long long,bool,wchar_t,size_t,off_t,regex_t,uid_t,u_longlong_t,pthread_key_t, fp_exception_field_type 等等类型。这些都是某种类型的整型!然后还有浮点数,指针和字符串等。
所有的这些复杂的类型都只对应 Perl 里面的一种类型:标量(你通常需要的只是 Perl 的简单数据类型,如果不是的话,你可以利用 Perl 的面向对象的特性自由地定义动态类型——参阅第十二章,对象。) Perl 的三种基本数据类型是:标量,标量数组和标量散列(hash)(也被称做联合数组)。有些人喜欢把这些称做数据结构,而不是类型。那也行。
标量是建造更复杂类型的基本类型。一个标量存储单一的简单值——通常是一个字串或者一个数字。这种简单类型的元素可以组成两种聚集类型的任何一种。一个数组是是一个标量的有序排列,你可以通过一个整型脚标(或者索引)访问。Perl 里的所有索引都从 0 开始。不过,和许多编程语言不一样的是,Perl 认为负数脚标也是合法的:负数脚标是从后向前记数你的数组。(这一点对许多子字串和子数组操作符以及正则表达式都适用。)另一方面,一个散列(hash)数组是一个无序的键字/数值对,你可以用字串(就是键字)当作脚标来访问对应一个键字的标量(就是数值)。变量总是这三种类型之一。(除变量外,还有一些其他的 Perl 抽象你也可以认为是数据类型,比如文件句柄,目录句柄,格式串,子过程(子函数),符号表和符号表入口等。)
抽象是好东西,我们一边学习一边会收集到更多的抽象,不过从某个角度来看,抽象也是没什么用的东西。你直接用抽象不能做任何事情。因此计算机语言就要有语法。我们要告诉你各种各样的语法术语,这样你就可以把抽象数据组成表达式。在我们谈到这些语法单元的时候,我们喜欢使用技术术语“项”这个词。(哦,这里的措辞可能有点模糊。不过只要记住数学老师在讲数学等式里用到的“项”这个词,你就不会犯大错。)
就象在数学等式里的项一样,Perl 里的大多数项的目的也是为加号或乘号等操作符生成数值。不过,和数学等式不一样的是,Perl 对它计算的数值要做些处理,而不仅仅是拿着一支笔在手里思考等式两边是否相等。对一个数值最常做的事情就是把它存放在某个地方:
$x = $y;
上面是赋值操作符(不是数字相等操作符,这个操作符在 Perl 里叫 ==)的例子。这条语句把 $y 的值赋予 $x。请注意我们不是用项 $x 做为其数值,而是作为其位置($x 的原先的值被赋值语句删除。)我们把 $x 称为一个 lvalue(左值),意思是我们可以用在赋值语句左边的存储位置。我们把 $y 称为一个 rvalue(右值),因为它是用在右边的。
还有第三种数值,叫临时值,如果你想知道 Perl 是如何处理你的左值和右值的,你就得理解这个临时值。如果我们做一些实际的数学运算并说:
$x = $y + 1;
Perl 拿出右值 $y 并且给它加上右值 1,生成一个临时变量,最后临时变量赋予左值 $x。如果我们告诉你 Perl 把这些临时变量存储在一个叫堆栈的内部结构里面,(注:堆栈就象餐馆小卖部里用发条上紧的自动售货机,你可以在栈顶压入盘子,或者你也可以把他们弹出来。)你可能就能想象内部发生什么事。一个表达式的项(我们在这一章里要谈的内容)会向堆栈里压入数据,当表达式里的操作符(我们在下一章讨论)试图把它们从堆栈里面弹出时,可能会在堆栈里面保留另外一个临时结果给下一个操作符处理。当表达式处理完成时,压栈和弹出相互平衡,堆栈完全清空(或者和开始的时候一样)。后面还有更多关于临时变量的介绍。
有些项只能做右值,比如上面的 1,而其他的既可以做左值也可以做右值。尤其是象上面的赋值语句演示的那样,一个变量就可以同时做左值和右值。那是我们下一章的内容。
2.4 变量
不用说,有三种变量类型和我们前面提到的三种抽象数据类型对应.每种类型都由我们称之为趣味字符( funny character)(注:这是计算机科学的另一个技术术语。(如果以前它不是,那现在它就是了)做前缀.标量变量的开头总是 $,甚至连引用一个数组或者散列中的一个标量也如此.它有点象英文里的单词"the".所以,我们有下表:
| 构造 | 含义 |
| $days | 简单标量值 $days |
| $days[28] | 数组 @days 的第二十九个元素 |
| $days{'Feb'} | 散列 %days 的“Feb”值 |
请注意我们可以对 $days,@days,和 %days 使用相同的名字而不用担心 Perl 会混淆它们。
还有其他一些爱好者使用的标量术语,在一些我们一时半会还接触不到的地方非常有用。他们看起来象:
| 构造 | 含义 |
| ${days} | 和 $days 一样,不过在字母数字前面不易混淆 |
| $Dog::days | 在 Dog 包里面的不同的 $days 变量 |
| $#days | 数组 @days 的最后一个索引 |
| $days->[28] | $days一个引用指向的数组的第二十九个元素 |
| $days[0][2] | 多维数组 |
| $days{200}{'Feb'} | 多维散列 |
| $days{2000,'Feb'} | 多维散列枚举 |
整个数组(或者数组和散列的片段)带趣味字符 @ 命名,很象单词“这些”或“那些”的作用:
| 构造 | 含义 |
| @days | 包含($days[0],$days[1],...$days[n])的数组 |
| @days[3,4,5] | 包含($days[3],$days[4],$days[5])数组片段的数组 |
| @days[3..5] | 包含($days[3],$days[4],$days[5])数组片段的数组 |
| @days{'Jan','Feb'} | 包含($days{'Jan'},$days{'Feb'})片段的散列 |
每个散列都用 % 命名:
| 构造 | 含义 |
| %days | (Jan=>31,Feb=>$leap?29:28,...) |
任何这些构造都可以作为左值使用,声明一个你可以赋值的位置。对于数组,散列或者数组和散列的片段,这个左值提供了可以赋值的多个位置,因此你可以一次给所有这些位置赋值:
@days = 1..7;
2.5 名字
我们已经谈过在变量里面保存数值了,但是变量本身(它们的名字和它们相关的定义)也需要存储在某个位置。在抽象(的范畴里),这些地方被称为名字空间。Perl 提供两种类型的名字空间,通常被称为符号表和词法范围(注:当我们谈论 Perl 详细实现时,我们还把它们称做包(packages)和垫(pads),不过那些更长的词是纯正工业术语,所以我们只能用它们,抱歉)。你可以拥有任意数量的符号表和词法范围,但是你定义的任何一个名字都将存储在其中的某一个里面。我们将随着介绍的深入探讨这两种名字空间。目前我们只能说符号表是全局的散列,用于存储存放全局变量的符号表的入口(包括用于其他符号表的散列)。相比之下,词法范围只是未命名的中间结果暂存器,不会存在于任何符号表,只是附着在你的程序的一块代码后面。它们(词法范围)包含只能被该块所见的变量。(那也是我们说“范围”的含义)。“词法”两个字只是说:“它们必须以文本方式处理”,可不是通常字典赋予它们的含义。可别批评我们。
在任何名字空间里(不管是全局还是局部),每个变量类型都有自己的由趣味字符确定的子名字空间。你可以把同一个名字用于一个标量变量,一个数组或者一个散列(或者,说到这份上,可以是一个文件句柄,一个子过程名,一个标签,甚至你的宠物骆驼)也不用担心会混淆。这就意味着 $foo 和 @foo 是两个不同的变量。加上以前的规则,这还意味着 $foo 是 @foo 的一个元素,它和标量变量 $foot 完全没有关系。这些看起来有点怪异,不过也没啥,因为它就是怪异(译注:我是流氓我怕谁?)。
子过程可以用一个 & 开头命名,不过调用子过程的时候这个趣味字符是可选的。子过程通常不认为是左值,不过最近版本的 Perl 允许你从一个子过程返回一个左值并且赋予该子过程,这样看起来可能就象你在给那个子过程赋值。
有时候你想命名一个“所有叫 foo 的东西”,而不管它的趣味字符是什么。因此符号表入口可以用一个前缀的 * 命名,这里的星号(*)代表所有其他趣味字符。我们把这些东西称为类型团(typeglobs),而且它们有好几种用途。它们也可以用做左值。给一个类型团(typeglobs)赋值就是 Perl 从一个符号表向另外一个输入符号的实现。我们后面还会有更多内容讲这些。
和大多数计算机语言类似,Perl 有一个保留字列表,它把这个表里的字看作特殊关键字。不过,由于变量名总是以趣味字符开头,实际上保留字并不和变量名冲突。不过,有些其他类型的名字不带趣味字符,比如标签和文件句柄。即使这样,你也用不着担心与保留字冲突。因为绝大多数保留字都是完全小写,我们推荐你使用带大写字符的名字做标签和文件句柄。例如,如果你说 open(LOG,logfile),而不是 open(log,"logfile"),你就不会让 Perl 误以为你正在与内建的 log 操作符(它处理对数运算,不是树干(译注:英文 "log" 有树干的含义。))交谈。使用大写的文件句柄也改善了可读性(注:Perl的一个设计原则是:不同的东西看起来应该不同。和那些试图强制把不同的东西变得看起来一样的语言比较一下,看看可读性的好坏。)并且防止你和我们今后可能会增加的保留字的冲突。处于同样的考虑,用户定义的模块通常都是用首字母大写的名字命名的,这样就不会和内建的模块(叫用法(pragmas))冲突,因为内建模块都是以小写字母命名的。到了面向对象命名的时候,你就会发现类的名称同样都是首字母大写的。
你也许能从前面的段落中推导出这样的结论了,就是标识符是大小写敏感的——FOO,Foo,和 foo 在 Perl 里面都是不同的名字。标识符以字母或下划线开头,可以包含任意长度(这个“任意”值的范围是 1 到 251 之间)个字母,数字和下划线。这里包括 Unicode 字母和数字。Unicode 象形文字也包括在内,不过我们可不建议你使用它们,除非你能够阅读它们。参阅第十五章。
严格说来,跟在趣味字符后面的名字一定是标识符。他们可以以数字开头,这时候后面只能跟着更多数字,比如 $123。如果一个名字开头不是字母,数字或下划线,这样的名字(通常)限于一个字符(比如 $? 或 $$),而且通常对 Perl 有预定的意义,比如,就象在 Bourne shell 里一样,$$ 是当前进程 ID 而 $? 是你的上一个子进程的退出状态。
到了版本 5.6,Perl 还有一套用于内部变量的可扩展语法。任何形如 ${^NAME} 这样的变量都是保留为 Perl 使用的。所有这些非标识符名字都被强制存放于主符号表。参阅第二十八章,特殊名字,那里有一些例子。
我们容易认为名字和标识符是一样的东西,不过,当我们说名字的时候,通常是指其全称,也就是说,表明自己位于哪个符号表的名字。这样的名字可能是一个由标记 :: 分隔的标识符的序列:
$Santa::Helper::Reindeer::Rudolph::nose
就象一个路径名里面的目录和文件一样:
/Santa/Helper/Reindeer/Rudolph/nose
在 Perl 的那个表示法里面,所有前导的标识符都是嵌套的符号表名字,并且最后一个标识符就是变量所在的最里层的符号表的名字。比如,上面的变量里,符号表的名字是 Santa::Helper::Reindeer::Rudolph::,而位于此符号表的实际变量是 $nose。(当然,该变量的值是“red”。)
Perl 里的符号表也被称为包(package),因此这些变量常被称为包变量。包变量名义上是其所属包的私有成员,但实际上是全局的,因为包本身就是全局的。也就是说,任何人都可以命名包以获取该变量;但就是不容易碰巧做到这些。比如,任何提到 $Dog::bert 的程序都是获取位于 Dog:: 包的变量 $bert。但它是与 $Cat::bert 完全不同的变量。参阅第十章,包。
附着在词法范围上的变量不属于任何包,因此词法范围变量名字可能不包含 :: 序列。(词法范围变量都是用 my 定义式定义的。)
2.5.1 名字查找
那问题就来了,名字里有什么?如果你光是说 $bert,Perl 是怎样了解你的意思的?问得好。下面是在一定环境里 Perl 分析器为了理解一个非全称的名字时用到的规则:
1。首先,Perl 预先在最先结束的块里面查找,看看该变量是否有用 my(或则 our)定义在该代码块里(参考那些第二十九章的内容和第四章,语句和声明和,里面的“范围声明”节)。如果存在 my 定义,那么该变量是词法范围内的而不存在于任何包里——它只存在于那个词法范围(也就是在该代码块的暂时缓存器里)。因为词法范围是非命名的,在那块程序之外的任何人甚至都看不到你的变量。(注:如果你用的是 our 定义而非 my,这样只是给一个包变量定义了一个词法范围的别名(外号),而不象 my 定义里面真正地定义了一个词法范围变量。代码外部仍然可以通过变量的包获取其值,但除此以外,our 定义和 my 定义的特点是一样的。如果你想在和 use strict 一起使用(参阅第三十一章里的 strict progma ),限制自己的全局变量的使用时很有用。不过如果你不需要全局变量时你应该优先使用 my。)
2。如果上面过程失败,Perl 尝试在包围该代码段的块里查找,仍然是在这个更大的代码块里查找词法范围的变量。同样,如果 Perl 找到一个,那么就象我们刚刚在第一步里说到的变量一样,该变量只属于从其定义开始到其定义块结束为止的词法范围——包括任何嵌套的代码块。如果 Perl 没有发现定义,那么它将重复第二步直到用光所有上层闭合块。
3。当 Perl 用光上层闭合块后,它检查整个编辑单元,把它当作代码块寻找声明(一个编辑单元就是整个当前文件,或者一个被 eval STRING 操作符编译过的当前字串。)如果编辑单元是一个文件,那就是最大的词法范围,这样 Perl 将不再查找词法范围变量,于是进入第四步。不过,如果编辑单元是一个字串,那事情就有趣了。一个当作运行时的 Perl 代码编译的字串会假装它是在一个 eval STRING 运行着的词法范围里面的一个块,即使其实际词法范围只是包含代码的字串而并非任何真实的花括弧也如此。所以如果 Perl 没有在字串的词法范围找到变量,那么我们假装 eval STRING 是一个块并且回到第 2 步,这回我们才检查 eval STRING 的词法范围而不是其内部字串的词法范围。
4。如果我们走到这一步,说明 Perl 没有找到你的变量的任何声明(my 或 our)。Perl 现在放弃词法范围并假设你的变量是一个包变量。如果 strict pragma 用法有效,你现在会收到一个错误,除非该变量是一个 Perl 预定义的变量或者已经输入到当前包里面。这是因为 strict 用法不允许使用非全称的全局名字。不过,我们还是没有完成词法范围的处理。Perl 再次搜索词法范围,就象在第 1 步到第 3 步里一样,不过这次它找的是 package(包)声明而不是变量声明。如果它找到这样的包声明,那它就知道找到的这些代码是为有问题的包编译的于是就在变量前面追加这个声明包的名字。
5。如果在任何词法范围内都没有包声明,Perl 就在未命名的顶层包里面查找,这个包正好就是 main——只要它没有不带名字到处乱跑。因此相对于任何缺失的声明,$bert 和 $::bert 的含义相同,也和 $main:bert 相同。(不过,因为 main 只是在顶层未命名包中的另一个包,该变量也是$::main::bert,和$main::main::bert, $::main::main::bert 等等。这些可以看作没用的特性。参考第10章里的“符号表”。)
这些搜索规则里面还有几个不太明显的暗示,我们这里明确一下。
1。因为文件是可能的最大词法范围,所以一个词法范围变量不可能在定义其的文件之外可见。文件范围不嵌套。
2。不管多大的 Perl 都编译成至少一个词法范围和一个包范围。这个必须的词法范围当然就是文件本身。附加的词法范围由每个封闭的块提供。所有 Perl 代码同样编译进一个包里面,并且尽管声明在哪个包里属于词法范围,包本身并不受词法范围约束。也就是说,它们是全局的。
3。因此可能而在许多词法范围内查找一个非全称变量,但只是在一个包范围内,不管是哪个当前有效的包(这是词汇定义的)。
4。一个变量值只能附着在一个范围上。尽管在你的程序的任何地方都至少有两个不同的范围(词法和包),一个变量仍然只能存在于这些范围之一。
5。因此一个非全长变量名可以被分析为唯一的一个存储位置,要么在定义它的第一个封闭的词法范围里,要么在当前包里——但不是同时在两个范围里。只要解析了存储位置,那么搜索就马上停止,并且如果搜索继续进行,它找到的任何存储位置都被有效地隐藏起来。
6。典型变量名的位置在编译时完全可以决定。
尽管你已经知道关于 Perl 编译器如何处理名字的所有内容,有时候你还是有这样的问题:在编译时你并不知道你想要的名字是什么。有时候你希望间接地命名一些东西;我们把这个问题叫间接(indirection)。因此 Perl 提供了一个机制:你总是可以用一个表达式块代替字母数字的变量名,这个表达式返回一个真正数据的引用。比如,你不说:
$bert
而可能说:
${some_expression()}
如果 some_expression() 函数返回一个变量 $bert 的引用(甚至是字串,“bert”),它都会象第一行里的 $bert 一样。另一方面,如果这个函数返回 $ernie 的引用,那你就会得到这个变量。这里显示的是间接的最常用(至少是最清晰)的形式,不过我们会在第8章,引用,里介绍几个变体。
2.6 标量值
不管是直接命名还是间接命名,不管它是在变量里还是一个数组元素里或者只是一个临时值,一个变量总是包含单一值。这个值可以是一个数字,一个字串或者是另一片数据的引用。或着它里面可以完全没有值,这时我们称其为未定义(undefined)。尽管我们会说标量“包含”着一个数字或者字串,标量本身是无类型的:你用不着把标量定义为整型或浮点型或字符串或者其他的什么东西。(注:将来的 Perl 版本将允许你插入 int,num,和 str 类型声明,这样做不是为了加强类型,而是给优化器一些它自己发现不了的暗示。通常,这个特性用于那些必须运行得非常快的代码,所以我们不准备告诉你如何使用。可选的类型同样还可以用于伪散列的机制,在这种情况下,它们可以表现得象大多数强类型语言里的类型一样。参阅第八章获取更多信息。)
Perl 把字串当作一个字符序列保存,对长度和内容没有任何限制。用我们人类的话来说,你用不着事先判断你的字串会有多长,而且字串里可以有任何字符,包括空(null)字节。Perl 在可能地情况下把数字保存为符号整数,或者是本机格式的双精度浮点数。浮点数值是有限精度的。你一定要记住这条,因为象 (10/3==1/3*10) 这样的比较会莫名其妙的失败。
Perl 根据需要在各种子类型之间做转换,所以你可以把一个数字当作字串或者反过来,Perl 会正确处理这些的。为了把字串转换成数字,Perl 内部使用类似 C 函数库的 atof(3) 函数。在把数字转换成字串的时候,它在大多数机器上做相当于带有格式“%.14g”的 sprintf(3) 处理。象把 foo 转换成数字这样的不当转换会把数字当成 0;如果你打开警告,上面这样做会触发警告,否则没有任何信息。参阅第五章,模式匹配,看看如何判断一个字串里面有什么东西的例子。
尽管字串和数字在几乎所有场合都可以互换,引用却有些不同。引用是强类型的,不可转换的指针;内建了引用计数和析构调用。也就是说,你可以用它们创建复杂的数据类型,包括用户定义对象。但尽管如此,它们仍然是标量,因为不管一个数据结构有多复杂,你通常还是希望把它当作一个值看待。
我们这里说的不可转换,意思是说你不能把一个引用转换成一个数组或者散列。引用不能转换成其他指针类型。不过,如果你拿一个引用当作一个数字或者字串来用,你会收到一个数字或者字串值,我们保证这个值保持引用的唯一性,即使你从真正的引用中拷贝过来而丢失了该“引用”值也如此(唯一)。你可以比较这样的数值或者抽取它们的类型。不过除此之外你对这种类型干不了什么,因为你没法把数字或字串转换回引用。通常,着不是个问题,因为 Perl 不强迫你使用指针运算——甚至都不允许。参阅第八章读取更多引用的信息。
2.6.1 数字文本
数字文本是用任意常用浮点或整数格式声明的:(注:在 Unix 文化中的习惯格式。如果你来自不同文化,欢迎来到我们中间!)
$x = 12345; # 整数 $x = 12345.67; # 浮点 $x = 6.02e23; # 科学记数 $x = 4_294_967_296; # 提高可读性的下划线 $x = 03777; # 八进制 $x = 0xffff; # 十六进制 $x = 0b1100_0000; # 二进制
因为 Perl 使用逗号作为数组分隔符,所以你不能用它分隔千位或者更大位。不过 Perl 允许你用下划线代替。这样的下划线只能用于你程序里面声明的数字文本,而不能用于从其他地方读取的用做数字的字串。类似地,十六进制前面的 0x,二进制前面的 0b,八进制的 0 也都只适用于数字文本。从字串到数字的自动转换并不识别这些前缀,你必须用 oct 函数做显式转换(注:有时候人们认为 Perl 应该对所有输入数据做这个转换。不过我们这个世界里面有太多带有前导零的十进制数会让 Perl 做这种自动转换。比如,O'Reilly & Associates 在麻省剑桥的办公室的邮政编码是 02140。如果你的邮件标签程序把 02140 变成十进制 1120 会让邮递员不知所措的。) oct 也可以转换十六进制或二进制数据,前提是你要在字串前面加 0x 或 0b。
2.6.2 字串文本
字串文本通常被单引号或者双引号包围。这些引号的作用很象 Unix shell 里的引号:双引号包围的字串文本会做反斜杠和变量替换,而单引号包围的字串文本不会(除了\'和\\以外,因此你可以在单引号包围的字串里使用单引号和反斜杠)。如果你想嵌入其他反斜杠序列,比如 \n(换行符),你就必须用双引号的形式。(反斜杠序列也被称为逃逸序列,因为你暂时“逃离”了通常的字符替换。)
一个单引号包围的字串必须和前面的单词之间有一个空白分隔,因为单引号在标识符里是个有效的字符(尽管有些老旧)。它的用法已经被更清晰的 :: 序列取代了。这就意味着 $main'val 和 $main::var 是一样的,只是我们认为后者更为易读。
双引号字串要遭受各种字符替换,其他语言的程序员对很多这样的替换非常熟悉。我们把它们列出在表2-1
表2-1 反斜杠的字符逃逸
| 代码 | 含义 |
| \n | 换行符(常作LF) |
| \r | 回车(常作CR) |
| \t | 水平制表符 |
| \f | 进纸 |
| \b | 退格 |
| \a | 警报(响铃) |
| \e | ESC字符 |
| \033 | 八进制的ESC |
| \x7f | 十六进制DEL |
| \cC | Control-C |
| \x{263a} | Unicode(笑脸) |
| \N{NAME} | 命名字符 |
上面的 \N{NAME} 符号只有在与第三十一章描述的 user charnames 用法一起使用时才有效。这样允许你象征性地声明字符,象在 \N{GREEK SMALL LETTER SIGMA},\n{greek:Sigma},或 \N{sigma} 里的一样——取决于你如何调用这个用法。参阅第十五章。
还有用来改变大小写或者对随后的字符“以下皆同”的操作的逃逸序列。见表2-2
表2-2。引起逃逸
| 代码 | 含义 |
| \u | 强迫下一个字符为大写(Unicode里的“标题”) |
| \l | 强制下一个字符小写 |
| \U | 强制后面所有字符大写 |
| \L | 强制后面所有字符小写 |
| \Q | 所有后面的非字母数字字符加反斜杠 |
| \E | 结束\U,\L,或\Q。 |
你也可以直接在你的字串里面嵌入换行符;也就是说字串可以在另一行里。这样通常很有用,不过也意味着如果你忘了最后的引号字符,Perl 会直到找到另外一个包含引号字符的行时才报错,这是可能已经远在脚本的其他位置了。好在这样使用通常会在同一行立即产生一个语法错,而且如果 Perl 认为有一个字串开了头,它就会很聪明地警告你你可能有字串没有封闭。
除了上面列出的反斜杠逃逸,双引号字串还要经受标量或数组值的变量代换。这就意味着你可以把某个变量的值直接插入字串文本里。这也是一个字串连接的好办法。(注:如果打开了警告,在使用连接或联合操作时,Perl 可能会报告说有未定义的数值插入到字串中,即使你实际上没有在那里使用那些操作也如此。那是编译器给你创建的。)可以做的变量代换的有标量变量,整个数组(不过散列不行),数组或散列的单个元素,或者片段。其它的东西都不转换。换而言之,你只能代换以 $ 或 @ 开头的表达式,因为它们是字串分析器要找的两个字符(还有反斜杠)。在字串里,如果一个 @ 后面跟着一个字母数字字符,而它又不是数组或片段的标识符,那就必须用反斜杠逃逸(\@),否则会导致一个编译错误。尽管带 % 的完整的散列可能不会代换进入字串,单个散列值或散列片段却是会的,因为它们分别以 $ 和 @ 开头。
下面的代码段打印 "The Price is $100.":
$Price = '$100'; # 不替换 print "The price is $Price.\n"; # 替换
和一些 shell 相似,你可以在标识符周围放花括弧,使之与后面的字母数字区分开来:“How ${verb}able!”。一个位于这样的花括弧里面的标识符强制为字串,就象任何散列脚标里面的单引号标识符一样。比如:
$days{'Feb'}
可以写做:
$days{Feb}
并且假设有引号。脚标里任何更复杂的东西都被认为是一个表达式,因此你用不着放在引号里:
$days{'February 29th'} # 正确
$days{"February 29th"} # 也正确""不必代换
$days{February 29th} # 错,产生一个分析错误尤其是你应该总是在类似下面的片段里面使用引号:
@days{'Jan','Feb'} # Ok.
@days{"Jan","Feb"} # Also ok.
@days{ Jan, Feb } # Kinda wrong (breaks under use strict)除了被代换的数组和散列变量的脚标以外,没有其它的多层代换。与 shell 程序员预期地相反,在双引号里面的反勾号不做代换,在双引号里面的单引号也不会阻止变量计算。Perl 里面的代换非常强大,同时也得到严格的控制。它只发生在双引号里,以及我们下一章要描述的一些“类双引号”的操作里:
print "\n"; # 正确,打印一个新行 print \n; # 错了,不是可替换的环境。
2.6.3 选择自己的引号
尽管我们认为引起是文本值,但在 Perl 里他们的作用更象操作符,提供了多种多样的代换和模式匹配功能。Perl 为这些操作提供了常用的引起字符,还提供了更通用的客户化方法,让你可以为上面任意操作选择你自己的引起字符。在表2-3里,任意非字母数字,非空白分隔符都可以放在 / 的位置。(新行和空格字符不再允许做分隔符了,尽管老版本的 Perl 曾经一度允许这么做。)
表2-3。引起构造
| 常用 | 通用 | 含义 | 替换 |
| ' ' | q// | 文本字串 | 否 |
| " " | qq// | 文本字串 | 是 |
| ` ` | qx// | 执行命令 | 是 |
| () | qw// | 单词数组 | 否 |
| // | m// | 模式匹配 | 是 |
| s/// | s/// | 模式替换 | 是 |
| y/// | tr/// | 字符转换 | 否 |
| " " | qr// | 正则表达式 | 是 |
这里的有些东西只是构成“语法调味剂”,以避免你在引起字串里输入太多的反斜杠,尤其是在模式匹配里,在那里,普通斜杠和反斜杠很容易混在一起。
如果你选用单引号做分隔符,那么不会出现变量代换,甚至那些正常状态需要代换的构造也不发生代换。如果起始分隔符是一个起始圆括弧,花括弧,方括弧,那么终止分隔符就是对应终止字符。(嵌入的分隔符必须成对出现。)比如:
$single = q!I said, "You said, 'she sad it.'"!;
$double =qq(can't we get some "good" $variable?);
$chunk_of_code = q {
if ($condition) {
print "Gotcha!";
}
};最后一个例子表明,你可以在引起声明字符和其起始包围字符之间使用空白。对于象s///和tr///这样的两元素构造而言,如果第一对引起是括弧对,那第二部分获取自己的引起字符。实际上,第二部分不必与第一对一样。所以你可以用象s
tr (a-f) [A-F];不过,如果用 # 做为引起字符,就不允许出现空白。q#foo# 被分析为字串 'foo',而 q #foo# 引起操作符 q 后面跟着一个注释。其分隔符将从下一行获取。在两个元素的构造中间也可以出现注释,允许你这样写:
s{foo} # 把 foo
{bar} # 换为 bar。
tr [a-f] # 把小写十六进制
[A-F]; # 换为大写2.6.4 要么就完全不管引起
一个语法里没有其他解释的名字会被当作一个引起字串看待。我们叫它们光字。(注:我们认为变量名,文件句柄,标签等等不是光字,因为它们有被前面的或后面的(或两边的)语句强制的含义。预定义的名字,比如子过程,也不是光字。只有分析器丝毫不知的东西才是光字。)和文件句柄和标签一样,完全由小写字符组成的光字在将来也可能有和保留字冲突的危险。如果你打开了警告,Perl 会就光字对你发出警告。比如:
@days = (Mon,Tue,Wed,Thu,Fri); print STDOUT hello, ' ', world, "\n";
给数组 @days 设置了周日的短缩写以及在 STDOUT 上打印一个 "hello world" 和一个换行。如果你不写文件句柄, Perl 就试图把 hello 解释成一个文件句柄,结果是语法错。因为这样非常容易出错,有些人就可能希望完全避免光字。前面列出的引用操作符提供了许多方便的构形,包括 qw// “单词引用”这样的可以很好地引用一个空白分隔的数组的构造:
@days = qw(Mon Tue Wed Thu Fri); print STDOUT "hello world\n";
你可以一直用到完全废止光字。如果你说:
use strict 'subs';
那么任何光字都会产生一个编译时错误。此约束维持到此闭合范围结束。一个内部范围可以用下面命令反制:
no strict 'subs';
请注意在类似:
"${verb}able"
$days{Feb}这样的构造里面的空标识符不会被认为是光字,因为它们被明确规则批准,而不是说“在语法里没有其他解释”。
一个不带引号的以双冒号结尾的名字,比如 main::或 Dog::,总是被当作包名字看待。Perl 在编译时把可能的光字 Camel:: 转换成字串 "Camel",这样,这个用法就不会被 use strict 指责。
2.6.5 代换数组数值
数组变量通过使用在 $" 变量(缺省时包含一个空格)(注:如果你使用和 Perl 捆绑的 English 模块,那么就是 $LIST_SEPARATOR)里声明的分隔符将所有数组元素替换为双引号包围的字串。下面的东西是一样的:
$temp = join( $", @ARGV ); print $temp;
print "@ARGV";
在搜索模式里(也要进行双引号类似的代换)有一个不巧的歧义:/$foo[bar]/ 是被替换为 /${foo}[bar]/(这时候 [bar] 是用于正则表达式的字符表)还是 /${foo[bar]}/(这里 [bar] 是数组 @foo 的脚标)?如果 @foot 不存在,它很显然是个字符表。如果 @foo 存在,Perl 则猜测 [bar] 的用途,并且几乎总是正确的(注:全面描述猜测机制太乏味了,基本上就是对所有看来象字符表(a-z,\w,开头的^)和看来象表达式(变量或者保留字)的东西进行加权平均)。如果它猜错了,或者是你变态,那你可以用上面描述的花括弧强制正确的代换。就算你只是为了谨慎,那不算是个坏主意。
2.6.6“此处”文档
有一种面向行的引起是以 Unix shell 的“此处文档”语法为基础的。说它面向行是因为它的分隔符是行而不是字符。起始分隔符是当前行,结束分隔符是一个包含你声明的字串的行。你所声明的用以结束引起材料的字串跟在一个 << 后面,所有当前行到结束行(不包括)之间的行都是字串的内容。结束字串可以是一个标识符(一个单词)或者某些引起的文本。如果它也被引起,引起的类型决定文本的变换,就象普通的引起一样。没有引起的标识符当作用双引号引起对待。反斜杠转意的标识符当作用单引号引起(为与 shell 语法兼容)。在 << 和未引起的标识符之间不能有空白,不过如果你用一个带引号的字串做标识符,则可以有空白。(如果你插入了空白,它会被当作一个空标识符,这样做是允许的但我们不赞成这么用,它会和第一个空白行匹配——参阅下面第一个 Hurrah! 例子。)结束字串必须在终止行独立出现——不带引号以及两边没有多余的空白。(译注:常见的错误是为了美观在结束字串前面加 \t 之类的空白,结果却导致错误.)
print <<EOF; # 和前面的例子一样 The price is $Price. EOF print <<"EOF"; # 和上面一样,显式的引起 The price is $Price. EOF print <<'EOF'; # 单引号引起 (略) EOF print << x 10; # 打印下面行10次 The Camels are coming! Hurrah! Hurrah! print <<"" x 10; # 实现上面内容的比较好的方法 The Camels are coming! Hurrah! Hurrah! print <<`EOC`; # 执行命令 echo hi there echo lo there EOC print <<"dromedary", <<"camelid"; # 你可以堆叠 I said bactrian. dromedary She said llama. camelid funkshun(<<"THIS",23,<<'THAT'); # 在不在圆括弧里无所谓 Here's a line ro two. THIS And here's another. THAT
不过别忘记在最后放分号以结束语句,因为 Perl 不知道你不是做这样的试验:
print <<'odd'
- odd +10000; #打印12345
如果你的此处文档在你的其他代码里是缩进的,你就得手工从每行删除开头的空白:
($quote = <<'QUOTE') =~ s/^\s+//gm; The Road goes ever on and on, down from the door where it began. QUOTE
你甚至还可以用类似下面的方法用一个此处文档的行填充一个数组:
@sauces = <<End_Lines =~ m/(\S.*\S)/g; normal tomato spicy tomato green chile pesto white wine End_Lines
2.6.7 V-字串文本
一个以 v 开头,后面跟着一个或多个用句点分隔的整数的文本,会被当作一个字串文本;该字串的字符的自然数对应 v 文本里的数值:
$crlf = v13.10; # ASCII 码回车,换行
这些就是所谓 v-字串,“向量字串”(vector strings)或“版本字串”(version strings)或者任何你能想象得出来的以“v”开头而且处理整数数组的东西的缩写。当你想为每个字符直接声明其数字值时,v-字串给你一种可用的而且更清晰的构造这类字串的方法。因此,v1.20.300.4000是比用下面的方法构造同一个字串的更迷人的手段:
"\x{1}\x{14}\x{12c}\x{fa0}"
pack("U*", 1, 20, 300, 4000)
chr(1) . chr(20) . chr(300) . chr(4000)如果这样的文本有两个或更多句点(三组或者更多整数),开头的v就可以忽略。
print v9786; # 打印UTF-8编码的笑脸“\x{263a}"
print v120.111.111; # 打印"foo"
use 5.6.0; # 要求特定Perl版本(或更新)
$ipaddr = 204.148.40.9; # oreilly.com 的 IPV4地址v-字串在表示 IP 地址和版本号的时候很有用。尤其是在字符可以拥有大于 255 的数值现代,v-字串提供了一个可以表示任意大小的版本并且用简单字符串比较可以得到正确结果的方法。
存储在 v-字串里的版本号和 IP 地址是人类不可读的,因为每个字符都是以任意字符保存的。要获取可读的东西,可以在 printf 的掩码里使用 v 标志,比如 "%vd",这些在第二十九章的 sprintf 部分有描述。有关 Unicode 字串的信息,请参阅第十五章和第三十一章的 use bytes 用法;关于利用字串比较操作符比较版本字串的内容,参阅第二十八章的 $^V;有关 IPV4 地址的表示方面的内容,见第二十九章 gethostbyaddr。
2.6.8 其他文本记号
你应该把任何以双下划线开头和结束的标识符看作由 Perl 保留做特殊语法处理的记号。其中有两个这类特殊文本是LINE和 __FILE__,分别意味着在你的程序某点的当前行号和文件名。它们只能用做独立的记号;它们不能被代换为字串。与之类似,__PACKAGE__ 是当前代码所编译进入的包的名字。如果没有当前包(因为有一个空的 package; 指示),__PACKAGE__ 就是未定义值。记号END(或者是一个 Control-D 或 Control-Z 字符)可以用于在真正的文件结束符之前表示脚本的逻辑结束。任何后面的文本都被忽略,不过可以通过 DATA 文件句柄读取。
DATA记号的作用类似END记号,不过它是在当前包的名字空间打开 DATA 文件句柄,因此你所 require 的所有文件可以同时打开,每个文件都拥有自己的 DATA 文件句柄。更多信息请看第二十八章里的 DATA。
导入论坛 引用链接 收藏 分享给好友 推荐到圈子 管理 举报
TAG:
标题搜索
日历
|
|||||||||
| 日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
| 1 | 2 | 3 | 4 | 5 | |||||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 | |||
| 13 | 14 | 15 | 16 | 17 | 18 | 19 | |||
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | |||
| 27 | 28 | 29 | 30 | 31 | |||||
数据统计
- 访问量: 2844
- 日志数: 555
- 建立时间: 2008-01-07
- 更新时间: 2008-06-24

