高效的 Perl 程序依赖于对值的精确的表示方式及操作。
计算机程序包含 变量:持有 值 的容器。值是程序实际操作的数据。虽然很容易就能 描述某项数据────你姑妈的名字和地址、你的办公室和月球上一堂高尔夫课程的距离,或者 去年你吃的曲奇的重量────但是这些和数据格式相关的规则往往很严格。编写高效的程序 通常意味着理解能表示该数据的最佳(最简单、最快、最紧凑或者最容易的)方式。
虽然程序的结构很大程度上依赖你用适合的变量为数据建立模型的方式,但如果不能准确地 容下数据本身(即:值)这些变量将会是毫无意义的。
字符串 是无特定格式、无特定内容的一小片数据,对程序来说也没有其他特别的含义。它 可能是你的名字,可能是从你的硬盘上读取的图像文件的内容,还可能是 Perl 程序本身。一 个字符串在你赋予它意义之前对程序而言毫无意义。字符串是一块由某种形式的括号括起的固 定长度的数据,Perl 字符串还能随着你的添加和抽减而变化。
最普通的字符串分隔符是单双引号:
单引号字符串 中的字符代表它们自身的字面含义,但有两处例外。通过反斜杠转义,你可 以在一个单引号字符串中内嵌另一个单引号字符串:
如果反斜杠在字符串末尾,你可以用另一个反斜杠将其转义,否则 Perl 语法分析器将认为你 是在转义结尾的单引号:
双引号字符串 有着更为复杂(通常也更有用)的行为。举例来说,你可以将非打印字符编 码进一个字符串:
字符串定义可以横跨多个逻辑行,如下列两个字符串是等价的:
你 可以 将这些字符直接输入字符串,但在视觉上通常很难将一个制表符和四个(或者两 个再或者八个)空格区分开来。
在双引号字符串内,你也可以将标量或数组的值 内插 入字符串,使得变量的值成为该字符串 的一部分,就好像你直接对其进行拼接操作:
在双引号字符串内,你可以通过 转义 插入一个字面双引号(即,在它前面加上一个反斜杠):
如果你觉得这简直丑陋到令人发指,那你可以使用另外的 引号操作符。q
操作符进行单引号 操作,而 qq
操作符是双引号。每种情况下,你都可以自行选择字符串分隔符。紧随操作符后的 字符决定了该字符串的开头和结尾。如果该字符是诸如大括号之类标点对中的开标点,则对应的闭 标点为字符串结尾的分隔符。除上述之外,字符本身将作为开头和结尾的分隔符。
即使你可以通过一系列内嵌的转义字符来声明一个复杂的字符串,有些时候跨行声明一个多行字符 串更为方便。heredoc 的语法让你可以以另一种方式进行多行字符串赋值:
<<'END_BLURB'
语法有三个部分。两重尖括号引入了 heredoc。引号决定此 heredoc 在处理 变量内插和转义字符时遵循单引号还是双引号的规则。它们是可选的,默认按双引号规则处理。END_BLURB
自身可以是任意标识符,由 Perl 5 语法分析器用作标示结束的分隔符。
注意,不管 heredoc 声明部分缩进多少,结束分隔符 必须 位于一行的开头:
你也可以在其他上下文中使用字符串,如布尔上下文和数值上下文,它的内容将决定结果的值(coercion)。
Unicode 一套用于表示世界手写语言字符的系统。虽然绝大部分英语文本使用一个仅含 127 个字符的 字符集(该字符集要求7位的存储空间,并且对于8位的字节来说正合适),但举例来说,不相信 需要变音的人是幼稚(“naïve”,注意第三个字符)的。
Perl 5 可以按两种相关但不同的方式表示一个字符串:
- Unicode 字符序列
-
Unicode 字符集包含了绝大多数语言的书写字符及其他一些符号。每一个字符有一个对应的 代码点, 一个在 Unicode 字符集中标识该字符的唯一数字。
- 八进制序列
-
二进制数据是一个 八进制 的序列────8位的数字,每一个都可以表示一个 0 到 255 直接的数字。
Unicode 字符串和二进制字符串看上去很像。它们都有 length()
函数,你可以对它们进行标准 字符串操作,如:拼接、分片和正则表达式处理。任何非纯二进制的字符串都是文本数据,它应是一 个 Unicode 字符序列。
虽都是八进制序列,数据还是会因操作系统对磁盘数据的表示方式,以及其他用户、网络等原因而不同, 因此 Perl 无从得知读取的某块数据究竟是图像文件、文本文档还是其他东西。默认地,Perl 将所有 读入的数据按八进制序列处理。给字符串内容赋予额外的含义是你的责任。
Unicode 字符串是一个表示一系列字符的八进制序列。Unicode 编码 将八进制序列映射到字符上。 一些编码方式,如 UTF-8,可以编码 Unicode 字符集中的所有字符。其他编码方式只能表示 Unicode 字符的一个子集。例如,ASCII 编码纯英语文本且不含重音字符,Latin-1 可以表示使用拉丁字母表的 大部分语言的文本。
如果你总是以正确的方式编解码程序输入输出,你将避开许多麻烦。
Unicode 输入的一个来源就是文件句柄(files)。如果你告知 Perl 某特定的文件句柄对已编码的文本进行操作,Perl 能够将数据自动转换为 Unicde 字符串。为实现此功能,请向内置 open
操作符的模式添加一个 IO 层。 IO 层 包装了输入输出并对数据进行某种形式的转换。在此种情况下,:utf8
层将对 UTF-8 数据进行解码:
你也可以用 binmode
对已经打开的文件句柄进行修改,无论是输入还是输出:
不启用 utf8
模式时,向某文件句柄打印 Unicode 字符串会得到一个警告(Wide character in %s
),因为 文件包含的是八进制数据而非 Unicode 字符。
Encode
核心模块提供了一个名为 decode()
的函数来将已知格式标量数据转换成一个 Unicode 字符串。例如, 如果你有的是 UTF-8 数据:
对应的 encode()
函数将 Perl 内部编码转换为所需输出编码:
在你的程序中包含 Unicode 字符有三种方式。最简易的方法是利用 utf8
编译命令(pragmas),它告诉 Perl 语法分析器将后续源代码编码解释为 UTF-8。这允许你在字符串和标识符中使用 Unicode 字符:
要 编写 这些代码,你的文本编辑器必须理解 UTF-8 并且你必须按正确的编码保存该文件。
你也可以使用 Unicode 转义序列来表示字符编码。\x{}
语法代表一个单独的字符。将该字符的 Unicode 数字的十六进制表示放入大括号内:
一些 Unicode 字符有自己的名称。虽然这相当详细,但是比起 Unicode 数字来说更加易读。你必须使用 charnames
编译命令来启用它。用 \N{}
转义语法来指代它:
你可以在正则表达式内使用 \x{}
和 \N{}
形式,就像在他处合理地使用一个字符或字符串。
Perl 中的大多数 Unicode 问题因字符串既可以是八进制数据序列也可以是字符序列而起。Perl 允许你 通过隐式转换结合使用这些类型。当这些转换出错时,它们不会错得那么 明显。
当 Perl 拼接一个八进制序列和一个 Unicode 字符序列时,它隐式地将八进制序列按 Latin-1 编码方式 解码。结果字符串包含 Unicode 字符。当你打印 Unicode 字符时,Perl 用 UTF-8 编码该字符串,因为 Latin-1 无法表示完整的 Unicode 字符集。
这个不对称性可导致 Unicode 字符串在输出时编码为 UTF-8 以及在输入时候解码为 Latin-1。
更糟糕的是,当文本只包含除重音外的英文字符时,这个 bug 会隐藏起来────因为这两种编码对每一个 字符的表示方式一致。
如果 $name
包含例如 Alice
之类的英文名字,你决不会察觉任何问题,因为 Latin-1 表示和 UTF-8 表示是一样的。
又如果,$name
包含类似 José
的名字,则 $name
可能包含下列几种可能的值:
$name
四个 Unicode 字符;$name
四个代表它们各自 Unicode 字符的八进制 Latin-1;$name
五个代表它们各自 Unicode 字符的八进制 UTF-8。
字符串字面值有如下可能的情形:
- ASCII 字符串字面值
-
此字符串字面值包含八进制数据。
- Latin-1 字符串字面值,无明确编码,例如:
-
此字符串字面值包含八进制数据。
- 非 ASCII 字符串字面值,通过
utf8
或encoding
编译命令指定: -
此字符串字面值包含 Unicode 字符。
如果 $hello
和 $name
都是 Unicode 字符串,拼接操作会产生另一个 Unicode 字符串。
如果两个字符串都是八进制流,Perl 会将它们拼接为一个新的八进制字符串。如果两者的都为 编码方式相同的八进制值────例如,全为 Lation-1,则拼接操作将正常执行。如果两者并不共用 编码方式,拼接操作将把 UTF-8 数据追加到 Latin-1 数据之后,生成两种编码方式 都无法 识别的八进制序列。这种情况可能在用户以 UTF-8 编码输入姓名而问候语是 Latin-1 字符串字 面值、程序又不能按任一编码解码时发生。
如果仅有一值为 UTF-8 字符串,Perl 将其他部分按 Latin-1 解码。如果这不是正确的编码方式, 作为结果的 UTF-8 字符串也会是错误的。例如,用户输入的是 UTF-8 数据并且此字符串字面值是 一个 Unicode 字符串,那么这个姓名将被错误地解码成五个 Unicode 字符,José
而非 José
,因为 UTF-8 数据在作为 Latin-1 解码时有着不同的含义。
参见 perldoc perluniintro
以获取一份有关如下话题更为详细的解释:Unicode、编码、以及如 何在 Unicode 世界中正确处理输入输出数据。
Perl 同样提供对数字的支持,无论是整数还是浮点数。它们可以按科学计数法、二进制、 八进制和十六进制形式给出:
加粗的字符是二进制、八进制、十六进制对应的数值前缀。注意开头的零总是意味着八进制 模式,这偶尔会引发意想不到的迷惑。
你无法用逗号按千分隔数值字面值,因为语法分析器会将逗号解释为逗号操作符。为此你 可以 在数字内使用下划线。语法分析器会将其作为不可见的字符对待,但你程序的读者不会。下面这些 是等价的:
请考虑使用最易读的方式。
由于强制转换(coercion),Perl 程序员几乎不用担心将外部读取的文本转换为数字一事。在 数值上下文中 Perl 会将所有看上去像数字的东西统一作为数字对待。虽然它几乎每每都能正确处理 此类事项,有时知道一下某物看上去是不是像数字也不赖。核心模块 Scalar::Util
包含一个名 叫 looks_like_number
的函数,如果 Perl 将某物做数值考虑,它就会返回一个为真的值。
来自 CPAN 的 Regexp::Common
模块同样提供了若干经过良好测试的正则表达式来识别数值的有 效 类型 (全数字、整数、浮点数值)。
Perl 5 中由 undef
代表所有未赋值、未定义和未知的值。已声明但未定义的标量包含 undef
:
在布尔上下文中对 undef
求值得到假。将 undef
内插入一个字符串────或者对其在 字符串上下文中求值────将产生一个 uninitialized value
的警告:
……产生:
当用于一个赋值操作的右手边时,()
结构代表一个空列表。当在标量上下文中求值时得到 undef
。 在列表上下文中,它在效果上就是一个空列表。
当用于一个赋值操作的左手边时,()
规定了列表上下文。不用临时变量,计算一个表达式在列表上下 文中返回结果的个数,你可以使用如下惯用语(idioms):
由于赋值操作符的右结合性(associativity),Perl 先对第二个赋值操作通过在列表上下文中调用 get_all_clown_hats()
求值,这会产生一个列表。
对空列表的赋值丢弃了该列表全部的值,但这个赋值在标量上下文中发生,将求得赋值操作右手边的元素 的个数。结果就是 $count
包含了从 get_all_clown_hats()
返回的列表中元素的个数。
目前你不用理解这段代码中所有的隐含内容,但是它确确实实地展示了 Perl 设计中区区几个基础功能的 组合就能产生如此有趣且有用的行为。
列表是一个由逗号分隔、包含一个或多个表达式的组。
列表可能在源代码中逐字出现:
……或作为赋值的目标:
……或作为一系列表达式:
值得一提的是 创建 列表并不需要括号,这些例子中(括号)出现的地方是为了让表达式成组出现,以 改变这些表达式的 优先级(precedence)。
你可以用范围操作符以一种紧凑的方式创建一个字面值列表:
……同时,你也可以用 qw()
操作符以空白符分隔一字符串字面值,并创建一个字符串列表:
列表可以(通常也)作为一个表达式的结果,但是这些列表不以字面形式在源代码中出现。
在 Perl 中,列表和数组概念之间不可以交换。列表是值而数组是容器。你可以在一个数组中存放一个列表, 也可以将一个数组强转为列表,但它们是不同的实体。举例来说,按下标访问列表通常在列表上下文中出现。 按下标访问数组通常在标量上下文(为获取单个元素)或列表上下文中出现(为数组分片):
Hey! The above document had some coding errors, which are explained below:
- Around line 5:
-
A non-empty Z<>
- Around line 199:
-
A non-empty Z<>