13.07.2015 Views

Perl 语言编程 - Linux教程

Perl 语言编程 - Linux教程

Perl 语言编程 - Linux教程

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>Perl</strong> 语 言 编 程


1.0 语 法 ................................................................................................................................1792.0 语 意 ................................................................................................................................1812.1 参 数 列 表 的 技 巧 ....................................................................................................1822.2 错 误 指 示 ................................................................................................................1842.3 范 围 问 题 ................................................................................................................1853.0 传 入 引 用 ........................................................................................................................1874.0 函 数 原 型 ........................................................................................................................1894.1 内 联 常 量 函 数 ........................................................................................................1934.2 谨 慎 使 用 函 数 原 型 ................................................................................................1945.0 子 过 程 属 性 ....................................................................................................................1965.1 Locked 和 method 属 性 .......................................................................................1965.3 左 值 属 性 ................................................................................................................197第 七 章 格 式 .................................................................................................................................1997.1 格 式 变 量 .........................................................................................................................2037.2 页 脚 ................................................................................................................................2067.2.1 访 问 格 式 的 内 部 .................................................................................................206第 八 章 引 用 .................................................................................................................................2088.1 什 么 是 引 用 ?................................................................................................................2088.2 创 建 引 用 ........................................................................................................................2108.2.1 反 斜 杠 操 作 符 .....................................................................................................2108.2.2 匿 名 数 据 .............................................................................................................2108.2.2.1 匿 名 数 组 组 合 器 ......................................................................................2118.2.2.2 匿 名 散 列 组 合 器 ......................................................................................2118.2.2.3 匿 名 子 过 程 组 合 器 ..................................................................................2138.2.3 对 象 构 造 器 .........................................................................................................2138.2.4 句 柄 引 用 .............................................................................................................2148.2.5 符 号 表 引 用 .........................................................................................................2158.2.6 引 用 的 隐 含 创 建 .................................................................................................2178.3 使 用 硬 引 用 ....................................................................................................................2178.3.1 把 一 个 变 量 当 作 变 量 名 使 用 .............................................................................2178.3.2 把 一 个 BLOCK 块 当 作 变 量 名 用 ...................................................................2198.3.3 使 用 箭 头 操 作 符 .................................................................................................2198.3.4 使 用 对 象 方 法 .....................................................................................................2228.3.5 伪 散 列 .................................................................................................................2228.3.6 硬 引 用 可 以 用 的 其 他 技 巧 .................................................................................2248.3.7 闭 合 ( 闭 包 ).....................................................................................................2268.3.7.1 用 闭 合 做 函 数 模 板 ..................................................................................2298.3.7.2 嵌 套 的 子 过 程 ..........................................................................................2308.4 符 号 引 用 ........................................................................................................................2318.5 花 括 弧 , 方 括 弧 和 引 号 ................................................................................................2328.5.1 引 用 不 能 当 作 散 列 键 字 用 .................................................................................2348.5.2 垃 圾 收 集 , 循 环 引 用 和 弱 引 用 .........................................................................235第 九 章 数 据 结 构 .........................................................................................................................2369.1 数 组 的 数 组 ....................................................................................................................2375


12.4.1 可 继 承 构 造 器 ...................................................................................................29412.4.2 初 始 器 ...............................................................................................................29612.5 类 继 承 ..........................................................................................................................298第 十 二 章 对 象 ( 下 ).................................................................................................................30012.5.1 通 过 @ISA 继 承 .............................................................................................30012.5.2 访 问 被 覆 盖 的 方 法 ...........................................................................................30112.5.3 UNIVERSAL: 最 终 的 祖 先 类 .........................................................................30312.5.4 方 法 自 动 装 载 ...................................................................................................30612.5.5 私 有 方 法 ...........................................................................................................30812.6 实 例 析 构 器 ..................................................................................................................30912.6.1 用 DESTROY 方 法 进 行 垃 圾 收 集 .................................................................31012.7 管 理 实 例 数 据 ..............................................................................................................31012.7.1 用 use fields 定 义 的 域 ....................................................................................31312.7.2 用 Class::Struct 生 成 类 ..................................................................................31612.7.3 使 用 Autoloading( 自 动 装 载 ) 生 成 指 示 器 ..................................................31812.7.4 用 闭 合 域 生 成 指 示 器 .......................................................................................32012.7.5 将 闭 合 域 用 于 私 有 对 象 ...................................................................................32112.7.6 新 技 巧 ...............................................................................................................32412.8 管 理 类 数 据 ..................................................................................................................32612.9 总 结 ..............................................................................................................................329第 十 三 章 重 载 .............................................................................................................................32913.1 overload 用 法 ...............................................................................................................33013.3 可 重 载 操 作 符 ..............................................................................................................33313.4 拷 贝 构 造 器 (=) .......................................................................................................34113.5 当 缺 失 重 载 句 柄 的 时 候 (nomethod 和 fallback) .................................................34213.6 重 载 常 量 ......................................................................................................................34313.7 公 共 重 载 函 数 ..............................................................................................................34513.8 继 承 和 重 载 ..................................................................................................................34613.9 运 行 时 重 载 ..................................................................................................................34613.10 重 载 诊 断 ....................................................................................................................346第 十 三 章 捆 绑 (tie) 变 量 上 .....................................................................................................34714.1 捆 绑 标 量 ......................................................................................................................34914.1.1 标 量 捆 绑 方 法 ...................................................................................................35014.1.2 魔 术 计 数 变 量 ...................................................................................................35514.1.3 神 奇 地 消 除 $_.................................................................................................35614.2 捆 绑 数 组 ......................................................................................................................35814.2.1 数 组 捆 绑 方 法 ....................................................................................................36014.2.2 大 家 方 便 ...........................................................................................................365第 十 四 章 捆 绑 (tie) 变 量 下 .....................................................................................................36714.3 捆 绑 散 列 ......................................................................................................................36714.3.1 散 列 捆 绑 方 法 ....................................................................................................36814.4 捆 绑 文 件 句 柄 ...............................................................................................................37614.4.1 文 件 句 柄 捆 绑 方 法 ...........................................................................................37814.4.2 创 建 文 件 句 柄 ....................................................................................................3887


14.5 一 个 精 细 的 松 绑 陷 阱 ..................................................................................................39514.6 CPAN 里 的 模 块 ..........................................................................................................399第 十 五 章 UNICODE.................................................................................................................40015.1 制 作 字 符 ......................................................................................................................40115.2 字 符 语 意 的 效 果 ..........................................................................................................403第 十 六 章 , 进 程 间 通 讯 ...............................................................................................................40916.1 信 号 ..............................................................................................................................40916.1.1 给 进 程 组 发 信 号 ...............................................................................................41216.1.2 收 割 僵 死 进 程 ...................................................................................................41316.1.3 给 慢 速 操 作 调 速 ...............................................................................................41516.1.4 阻 塞 信 号 ...........................................................................................................41616.2 文 件 ..............................................................................................................................41616.2.1 文 件 锁 定 ...........................................................................................................41716.2.2 传 递 文 件 句 柄 ...................................................................................................42116.3 管 道 ..............................................................................................................................42516.3.1 匿 名 管 道 ...........................................................................................................42516.3.2 自 言 自 语 ...........................................................................................................42816.3.3 双 向 通 讯 ...........................................................................................................43116.3.4 命 名 管 道 ...........................................................................................................43416.4. System V IPC...............................................................................................................43616.5. 套 接 字 .........................................................................................................................44216.5.1 网 络 客 户 端 程 序 ...............................................................................................44316.5.2 网 络 服 务 器 .......................................................................................................44616.5.3 消 息 传 递 ...........................................................................................................450第 十 七 章 线 程 .............................................................................................................................45317.1 进 程 模 型 ......................................................................................................................45317.2 线 程 模 型 ......................................................................................................................45417.2.1 线 程 模 块 ...........................................................................................................45617.2.1.1 创 建 线 程 ................................................................................................45617.2.1.2 线 程 删 除 ................................................................................................45717.2.1.3 捕 获 来 自 join 的 例 外 ..........................................................................45917.2.1.4 detach 方 法 ............................................................................................45917.2.1.5 标 识 线 程 ................................................................................................46017.2.1.6 列 出 当 前 线 程 ........................................................................................46017.2.1.7 交 出 处 理 器 ............................................................................................46017.2.2 数 据 访 问 ...........................................................................................................46117.2.2.1 用 lock 进 行 访 问 同 步 .........................................................................46117.2.2.2 死 锁 ........................................................................................................46417.2.2.3 锁 定 子 过 程 ............................................................................................46417.2.2.4 locked 属 性 ............................................................................................46617.2.2.5. 锁 定 方 法 ...............................................................................................46717.2.2.6 条 件 变 量 ................................................................................................46717.2.3 其 他 线 程 模 块 ...................................................................................................46917.2.3.1 队 列 (queue) ......................................................................................4698


17.2.3.2. 信 号 灯 ...................................................................................................47117.2.3.3 其 他 标 准 线 程 模 块 ................................................................................471第 十 八 章 编 译 .............................................................................................................................47218.1. <strong>Perl</strong> 程 序 的 生 命 周 期 .................................................................................................47218.2 编 译 你 的 代 码 ..............................................................................................................47418.3 执 行 你 的 代 码 ..............................................................................................................47918.4 编 译 器 后 端 ...................................................................................................................48218.5 代 码 生 成 器 ..................................................................................................................48318.5.1 字 节 码 生 成 器 ...................................................................................................48318.5.2. C 代 码 生 成 器 ..................................................................................................48418.6 提 前 编 译 , 回 头 解 释 ...................................................................................................487第 十 九 章 命 令 行 接 口 .................................................................................................................49119.1 命 令 行 处 理 ..................................................................................................................4919


第 一 章 <strong>Perl</strong> 概 述1.1 从 头 开 始我 们 认 为 <strong>Perl</strong> 是 一 种 容 易 学 习 和 使 用 的 语 言 , 而 且 我 们 希 望 能 证 明 我 们 是 对 的 .<strong>Perl</strong> 比较 简 单 的 一 个 方 面 是 你 用 不 着 在 说 想 说 的 东 西 之 前 先 说 很 多 其 他 东 西 。 在 很 多 其 他 编 程 语 言里 , 你 必 须 首 先 定 义 类 型 , 变 量 , 以 及 你 需 要 用 到 的 子 过 程 , 然 后 才 能 开 始 写 你 要 执 行 的 第一 行 程 序 。 虽 然 对 于 那 些 需 要 复 杂 数 据 结 构 的 复 杂 问 题 而 言 , 声 明 变 量 是 一 个 好 主 意 . 但 是对 于 很 多 简 单 的 日 常 问 题 , 你 肯 定 喜 欢 这 样 的 一 种 编 程 语 言 , 你 只 需 简 单 说 :print "Howdy, World!\n";程 序 就 能 得 到 你 所 需 的 结 果 .<strong>Perl</strong> 就 是 这 样 的 一 种 语 言 。 实 际 上 , 上 面 这 个 例 子 是 一 个 完 整 的 程 序 , 如 果 你 将 它 输 入 到<strong>Perl</strong> 解 释 器 里 , 它 就 会 在 你 的 屏 幕 上 打 印 出 "Howdy, world!"( 例 子 中 的 \n 在 输 出 中产 生 一 个 新 行 。)同 样 , 你 也 用 不 着 在 说 完 之 后 说 很 多 其 他 东 西 。 和 其 他 语 言 不 同 的 是 ,<strong>Perl</strong> 认 为 程 序 的 结尾 就 是 一 种 退 出 程 序 的 正 常 途 径 , 如 果 你 愿 意 的 话 , 你 当 然 可 以 明 确 地 调 用 exit 函 数 来 退出 程 序 。 就 象 你 可 以 声 明 一 些 你 所 用 的 变 量 , 或 者 甚 至 可 以 强 迫 自 己 声 明 所 用 的 所 有 变 量 ,但 这 只 是 你 的 决 定 。 用 <strong>Perl</strong> 你 可 以 自 由 的 做 那 些 正 确 的 事 , 不 过 你 要 仔 细 的 定 义 它 们 。关 于 <strong>Perl</strong> 的 容 易 使 用 还 有 很 多 其 他 理 由 , 但 是 不 可 能 全 在 这 里 列 出 来 , 因 为 这 是 这 本 书 余下 部 分 说 要 讨 论 的 内 容 。 语 言 的 细 节 是 很 难 理 解 的 , 但 是 <strong>Perl</strong> 试 图 能 把 你 从 这 些 细 节 中 解放 出 来 。 在 每 一 个 层 次 ,<strong>Perl</strong> 都 能 够 帮 助 你 以 最 小 的 忙 乱 获 得 最 大 的 享 受 和 进 步 , 这 就 是为 什 么 这 么 多 <strong>Perl</strong> 程 序 员 能 够 如 此 悠 闲 的 原 因 吧 。本 章 是 <strong>Perl</strong> 的 一 个 概 述 , 所 以 我 们 不 准 备 介 绍 得 过 于 深 入 , 同 时 我 们 也 不 追 求 描 述 的 完 整性 和 逻 辑 性 。 那 些 是 下 面 章 节 所 要 做 的 事 情 。 如 果 你 等 不 及 了 , 或 者 你 是 比 较 死 板 的 人 , 你可 以 直 接 进 入 到 第 二 章 , 集 腋 成 裘 , 获 取 最 大 限 度 的 信 息 密 度 。 另 外 如 果 你 需 要 一 个 更 详 细的 教 程 , 你 可 以 去 找 Randal 的 Learning <strong>Perl</strong> ( 由 O'Reilly&Associates 出 版 )。10


不 管 你 喜 欢 把 <strong>Perl</strong> 称 做 想 象 力 丰 富 的 , 艺 术 色 彩 浓 厚 的 , 富 有 激 情 的 还 是 仅 仅 是 具 有 很 好的 灵 活 性 的 东 西 , 我 们 都 会 在 本 章 中 给 你 展 现 <strong>Perl</strong> 的 另 一 个 方 面 。 到 本 章 结 束 时 , 我 们 将给 你 展 现 <strong>Perl</strong> 的 不 同 方 面 , 并 帮 助 你 建 立 起 一 个 <strong>Perl</strong> 的 清 晰 完 整 的 印 象 。1.2 自 然 语 言 与 人 工 语 言语 言 最 早 是 人 类 发 明 出 来 方 便 自 身 的 东 西 。 但 在 计 算 机 科 学 的 历 史 中 , 这 个 事 实 偶 尔 会 ( 注 :更 准 确 地 说 , 人 们 会 偶 尔 记 起 这 个 事 实 ) 被 人 们 忘 记 。 因 为 <strong>Perl</strong> 碰 巧 是 由 一 个 语 言 学 家 设计 的 ( 可 以 这 么 说 吧 ), 因 此 它 被 设 计 成 一 个 可 以 象 自 然 语 言 那 样 使 用 的 编 程 语 言 。 通 常 ,做 到 这 一 点 要 处 理 很 多 方 面 的 事 情 , 因 为 自 然 语 言 可 以 同 时 在 几 个 不 同 的 层 次 做 得 非 常 好 。我 们 可 以 列 举 出 很 多 语 言 设 计 上 的 原 则 , 但 是 我 们 认 为 语 言 设 计 最 重 要 的 原 则 就 是 : 处 理 简单 的 事 情 必 须 容 易 , 并 且 能 够 处 理 困 难 的 事 情 ( 其 实 这 是 两 个 原 则 )。 这 对 你 来 说 也 许 显 而易 见 , 但 是 有 很 多 计 算 机 语 言 在 其 中 的 某 个 方 面 做 得 不 好 。自 然 语 言 在 上 述 两 个 方 面 都 做 得 很 好 , 因 为 人 们 总 是 需 要 表 达 简 单 的 事 情 和 复 杂 的 事 情 , 所以 语 言 进 化 成 能 够 同 时 处 理 这 两 种 情 况 。<strong>Perl</strong> 首 先 被 设 计 成 可 以 进 化 , 并 且 实 际 上 也 已 经进 化 了 。 在 这 个 进 化 过 程 中 , 很 多 人 做 出 了 很 多 贡 献 。 我 们 经 常 开 玩 笑 说 : 骆 驼 (<strong>Perl</strong>) 是一 匹 委 员 会 设 计 的 马 , 但 是 如 果 你 想 一 想 , 骆 驼 非 常 适 应 沙 漠 中 的 生 活 。 骆 驼 已 经 进 化 成 为相 当 能 自 给 自 足 ( 另 一 方 面 , 骆 驼 闻 起 来 不 怎 么 样 ,<strong>Perl</strong> 也 一 样 ), 这 也 是 我 们 选 择 骆 驼 作为 <strong>Perl</strong> 的 吉 祥 物 众 多 原 因 中 的 一 个 , 而 和 语 言 学 没 有 什 么 关 系 。现 在 , 当 有 人 提 起 “ 语 言 学 ” 的 时 候 , 一 些 人 关 注 于 字 , 另 一 些 人 则 关 注 句 子 。 但 是 词 和 句 子只 是 拆 分 一 大 段 话 的 两 个 简 单 方 法 。 它 们 要 么 可 以 拆 分 成 可 以 更 小 的 表 意 部 分 , 要 么 可 以 并成 更 大 的 表 意 部 分 。 任 何 部 分 所 表 达 的 意 思 很 大 程 度 上 依 赖 于 语 法 , 语 义 以 及 所 处 的 环 境 。自 然 语 言 由 不 同 词 性 的 词 : 名 词 , 动 词 等 等 组 成 。 在 一 个 隔 离 的 环 境 中 说 " 狗 " 的 时 候 , 我 们认 为 它 是 一 个 名 词 , 但 是 你 也 可 以 以 不 同 的 方 式 使 用 同 一 个 词 。 在 "If you dog a dogduring the dog days of summer, you will be a dog tired dogcather"( 如 果 你 在 三伏 天 追 赶 一 只 狗 , 你 就 会 成 为 疲 劳 的 捕 狗 人 。) 这 个 句 子 中 ,dog 这 个 名 词 在 这 个 环 境 里可 以 作 为 动 词 , 形 容 词 , 和 副 词 。( 注 : 你 看 了 这 句 话 可 能 都 对 这 些 贫 嘴 的 狗 词 汇 都 烦 了 。不 过 我 们 只 是 想 让 你 理 解 为 什 么 <strong>Perl</strong> 和 其 他 典 型 的 计 算 机 语 言 不 同 , TMD!)<strong>Perl</strong> 也 根 据 不 同 的 环 境 来 处 理 词 , 在 下 面 的 章 节 中 我 们 将 会 了 解 到 <strong>Perl</strong> 是 如 何 进 行 处 理的 。 现 在 我 们 只 需 要 记 住 <strong>Perl</strong> 象 一 个 好 听 众 那 样 努 力 理 解 你 说 的 话 . 你 只 需 要 说 你 的 意思 ,<strong>Perl</strong> 就 能 理 解 你 的 意 思 ( 除 非 你 在 胡 说 , 当 然 <strong>Perl</strong> 解 释 器 更 容 易 听 懂 <strong>Perl</strong>, 而 不 是英 语 或 斯 瓦 希 里 语 。)回 到 名 词 , 一 个 名 词 可 以 命 名 一 个 特 定 的 对 象 , 或 者 它 可 以 命 名 非 特 指 的 某 类 对 象 . 绝 大 多数 计 算 机 语 言 将 上 述 两 个 方 面 区 别 开 来 。 只 有 我 们 把 特 定 对 象 当 作 值 而 把 泛 指 的 对 象 当 做 变11


量 。 值 保 存 在 一 个 地 方 , 而 变 量 和 一 个 或 多 个 值 关 联 。 因 此 无 论 是 谁 解 释 变 量 都 必 须 保 持 跟踪 这 个 关 联 。 这 个 解 释 器 也 许 是 你 的 大 脑 或 者 是 你 的 计 算 机 .1.2.1 变 量 语 法一 个 变 量 就 是 用 来 保 存 一 些 东 西 的 地 方 , 这 个 地 方 有 一 个 名 字 , 这 样 当 你 需 要 使 用 这 些 东 西的 时 候 就 知 道 从 哪 里 找 到 它 。 在 日 常 生 活 中 有 很 多 不 同 地 方 用 来 储 存 东 西 , 有 些 是 很 秘 密 的 ,有 些 则 是 公 开 的 。 有 些 是 暂 时 性 的 , 有 些 则 更 为 永 久 。 计 算 机 学 家 很 喜 欢 讨 论 变 量 范 围 。<strong>Perl</strong>有 不 同 于 其 他 语 言 的 简 便 方 法 来 处 理 范 围 问 题 。 你 可 以 在 本 书 后 面 适 当 的 时 候 学 习 到 相 关 的知 识 ( 如 果 你 很 急 迫 地 知 道 这 些 知 识 , 你 可 以 参 阅 二 十 九 章 , 函 数 , 或 第 四 章 , 语 句 和 声 明 ,里 “ 范 围 声 明 ”。)一 个 区 分 变 量 类 型 最 直 接 的 方 法 是 看 变 量 里 面 保 存 了 何 种 数 据 。 象 英 语 一 样 ,<strong>Perl</strong> 变 量 类型 之 间 区 别 主 要 是 单 数 和 复 数 , 字 符 串 和 数 字 是 单 个 数 据 , 而 一 组 数 字 和 字 符 串 是 复 数 ( 当我 们 接 触 到 面 对 对 象 编 程 时 , 对 象 就 象 一 个 班 的 学 生 一 样 , 从 外 部 看 , 它 是 一 个 单 数 , 而 从内 部 看 则 是 一 个 复 数 ) 我 们 叫 把 单 数 变 量 称 为 标 量 , 而 把 复 数 变 量 称 为 数 组 。 我 们 可 以 将 第一 个 例 子 程 序 改 写 成 一 个 稍 微 长 一 些 的 版 本 :$phrase = "Howdy, world!\n";print $phrase;请 注 意 , 在 <strong>Perl</strong> 中 我 们 不 必 事 先 定 义 $phrase 是 什 么 类 型 的 变 量 ,$ 符 号 告 诉 <strong>Perl</strong>,phrase 是 一 个 标 量 , 也 就 是 包 含 单 个 数 值 的 变 量 。 与 此 对 应 的 数 组 变 量 使 用 @ 开 头 。( 可以 将 $ 理 解 成 代 表 "s" 或 "scalar"( 标 量 ), 而 @ 表 示 "a" 或 "array" ( 数 组 ) 来帮 助 你 记 忆 。)<strong>Perl</strong> 还 有 象 “ 散 列 ”,“ 句 柄 ”,“ 类 型 团 ” 等 其 他 一 些 变 量 类 型 , 与 标 量 和 数 组 一 样 , 这 些 变量 类 型 也 是 前 导 趣 味 字 符 , 下 面 是 你 将 会 碰 到 的 所 有 趣 味 字 符 :类 型 字 符 例 子 用 于 哪 种 名 字标 量 $ $cents 一 个 独 立 的 数 值 ( 数 字 或 字 串 )数 组 @ @large 一 列 数 值 , 用 编 号 做 键 字散 列 % %interest 一 组 数 值 , 用 字 串 做 键 字子 过 程 & &how 一 段 可 以 调 用 的 <strong>Perl</strong> 代 码类 型 团 * *struck 所 有 叫 struck 的 东 西一 些 纯 粹 语 言 主 义 者 将 这 些 古 怪 的 字 符 作 为 一 个 理 由 来 指 责 <strong>Perl</strong>。 这 只 是 表 面 现 象 。 这 些字 符 有 很 多 好 处 , 不 用 额 外 的 语 法 变 量 就 可 以 代 换 成 字 串 只 是 最 简 单 的 一 个 。<strong>Perl</strong> 脚 本 也很 容 易 阅 读 ( 当 然 是 对 那 些 花 时 间 学 习 了 <strong>Perl</strong> 的 人 !) 因 为 名 词 和 动 词 分 离 , 这 样 我 们 就12


可 以 向 语 言 中 增 加 新 的 动 词 而 不 会 破 坏 旧 脚 本 。( 我 们 告 诉 过 你 <strong>Perl</strong> 被 设 计 成 是 可 以 进 化的 语 言 。) 名 词 也 一 样 , 在 英 语 和 其 他 语 言 中 为 了 满 足 语 法 需 要 有 很 多 前 缀 。 这 就 是 我 们 的想 法 。1.2.2 单 数 变 量从 我 们 前 面 的 例 子 中 可 以 看 到 ,<strong>Perl</strong> 中 的 标 量 象 其 他 语 言 一 样 , 可 以 用 = 对 它 进 行 赋 值 ,在 <strong>Perl</strong> 中 标 量 可 以 赋 予 : 整 数 , 浮 点 数 , 字 符 串 , 甚 至 指 向 其 他 变 量 或 对 象 的 引 用 这 样 深奥 的 东 西 。 有 很 多 方 法 可 以 对 标 量 进 行 赋 值 。与 Unix ( 注 : 我 们 在 这 里 和 其 他 所 有 地 方 提 到 Unix 的 时 候 , 我 们 指 的 是 所 有 类 Unix 系统 , 包 括 BSD,Linux, 当 然 还 包 括 Unix) 中 的 shell 编 程 相 似 , 你 可 以 使 用 不 同 的 引号 来 获 得 不 同 的 值 , 双 引 号 进 行 变 量 代 换 ( 注 : 有 时 候 shell 程 序 员 叫 “ 替 换 ”, 不 过 , 我们 宁 愿 在 <strong>Perl</strong> 里 把 这 个 词 保 留 给 其 他 用 途 。 所 以 请 把 它 称 之 为 “ 代 换 ”。 我 们 使 用 的 是 它 的字 面 意 思 (“ 这 段 话 是 某 种 宗 教 代 换 ”), 而 不 是 其 数 学 含 义 (“ 图 上 这 点 是 另 外 两 点 的 插 值 ”))和 反 斜 扛 代 换 ( 比 如 把 \n 转 换 成 新 行 ). 而 反 勾 号 ( 那 个 向 左 歪 的 引 号 ) 将 执 行 外 部 程序 并 且 返 回 程 序 的 输 出 , 因 此 你 可 以 把 它 当 做 包 含 所 有 输 出 行 的 单 个 字 串 .• $answer = 42; # 一 个 整 数• $pi = 3.14159265 # 一 个 " 实 " 数• $pet = "Camel"; # 字 串• $sign = "I ove my $pet"; # 带 代 换 的 字 串• $cose = 'It cose $100'; # 不 带 代 换 的 字 串• $thence = $whence; # 另 一 个 变 量 的 数 值• $salsa = $moles * $avocados; # 一 个 胃 化 学 表 达 式• $exit = system("vi $file"); # 一 条 命 令 的 数 字 状 态• $cwd = `pwd`; # 从 一 个 命 令 输 出 的 字 串上 面 我 们 没 有 涉 及 到 其 他 一 些 有 趣 的 值 , 我 们 应 该 先 指 出 知 道 标 量 也 可 以 保 存 对 其 他 数 据 结构 的 引 用 , 包 括 子 程 序 和 对 象 .如 果 你 使 用 了 一 个 尚 未 赋 值 的 变 量 , 这 个 未 初 始 化 的 变 量 会 在 需 要 的 时 候 自 动 存 在 . 遵 循 最小 意 外 的 原 则 , 该 变 量 按 照 常 规 初 始 化 为 空 值 ,"" 或 0. 根 据 使 用 的 地 方 的 不 同 , 变 量 会被 自 动 解 释 成 字 符 串 , 数 字 或 " 真 " 和 " 假 "( 通 常 称 布 尔 值 ). 我 们 知 道 在 人 类 语 言 中 语 言 环境 是 十 分 重 要 的 . 在 <strong>Perl</strong> 中 不 同 的 操 作 符 会 要 求 特 定 类 型 的 单 数 值 作 为 参 数 . 我 们 就 称 之为 这 个 操 作 符 给 这 些 参 数 提 供 了 一 个 标 量 的 环 境 . 有 时 还 会 更 明 确 , 比 如 说 操 作 符 会 给 这 些13


参 数 提 供 一 个 数 字 环 境 , 字 符 串 环 境 或 布 尔 环 境 .( 稍 后 我 们 会 讲 到 与 标 量 环 境 相 对 的 数 组环 境 )<strong>Perl</strong> 会 根 据 环 境 自 动 将 数 据 转 换 成 正 确 的 形 式 . 例 如 :$camels = '123';print $camels +1, "\n";$camels 最 初 的 值 是 字 符 串 , 但 是 它 被 转 换 成 数 字 然 后 加 一 , 最 后 有 被 转 换 回 字 符 串 , 打印 出 124."\n" 表 示 的 新 行 同 样 也 在 字 符 串 环 境 中 , 但 是 由 于 它 本 来 就 是 一 个 字 符 串 , 因此 就 没 有 必 要 做 转 换 了 . 但 是 要 注 意 我 们 在 这 里 必 须 使 用 双 引 号 , 如 果 使 用 单 引 号 '\n',这 就 表 示 这 是 由 反 斜 扛 和 n 两 个 字 符 组 成 的 字 符 串 , 而 不 表 示 一 个 新 行 .所 以 , 从 某 种 意 义 上 来 说 , 使 用 单 引 号 和 双 引 号 也 是 另 外 一 种 提 供 不 同 环 境 方 法 . 不 同 引 号里 面 的 内 容 根 据 所 用 的 引 号 有 不 同 的 解 释 .( 稍 后 , 我 们 会 看 到 一 些 和 引 号 类 似 的 操 作 符 ,但 是 这 些 操 作 符 以 一 些 特 殊 的 方 法 使 用 字 符 串 , 例 如 模 式 匹 配 , 替 换 . 这 些 都 象 双 引 号 字 符串 一 样 工 作 . 双 引 号 环 境 在 <strong>Perl</strong> 中 称 为 代 换 环 境 . 并 且 很 多 其 他 的 操 作 符 也 提 供 代 换 环境 .)同 样 的 , 一 个 引 用 在 " 解 引 用 " 环 境 表 现 为 一 个 引 用 , 否 则 就 象 一 个 普 通 标 量 一 样 工 作 , 比 如 ,我 们 可 以 说 :$fido= new Camel "Amelia";if (not $fido) { die "dead camel"; }$fido->saddle();在 这 里 , 我 们 首 先 创 建 了 一 个 指 向 Camel 对 象 的 引 用 , 并 将 它 赋 给 变 量 $fido, 在 第 二行 中 , 我 们 将 $fido 当 成 一 个 布 尔 量 来 判 断 它 是 否 为 真 , 如 果 它 不 为 真 , 程 序 将 抛 出 一 个例 外 . 在 这 个 例 子 中 , 这 将 意 味 着 new Camel 构 造 函 数 创 建 Camel 对 象 失 败 . 最 后 一行 , 我 们 将 $fido 作 为 一 个 引 用 , 并 调 用 Camel 对 象 的 saddle() 方 法 . 今 后 我 们 还 将讲 述 更 多 关 于 环 境 的 内 容 . 现 在 你 只 需 记 住 环 境 在 <strong>Perl</strong> 中 是 十 分 重 要 的 ,<strong>Perl</strong> 将 根 据 环境 来 判 断 你 想 要 什 么 , 而 不 用 象 其 他 编 程 语 言 一 样 必 须 明 确 地 告 诉 它 .1.2.3 复 数 变 量一 些 变 量 类 型 保 存 多 个 逻 辑 上 联 系 在 一 起 的 值 .<strong>Perl</strong> 有 两 种 类 型 的 多 值 变 量 : 数 组 和 散 列 ,在 很 多 方 面 , 它 们 和 标 量 很 相 似 , 比 如 它 们 也 会 在 需 要 时 自 动 存 在 . 但 是 , 当 你 给 它 们 赋 值时 , 它 们 就 和 标 量 就 不 一 样 了 . 它 们 提 供 一 个 列 表 环 境 而 不 是 标 量 环 境 .14


数 组 和 散 列 也 互 不 相 同 . 当 你 想 通 过 编 号 来 查 找 东 西 的 时 候 , 你 要 用 数 组 . 而 如 果 你 想 通 过名 称 来 查 找 东 西 , 那 么 你 应 该 用 散 列 . 这 两 种 概 念 是 互 补 的 . 你 经 常 会 看 到 人 们 用 数 组 实 现月 份 数 到 月 份 名 的 翻 译 , 而 用 对 应 的 散 列 实 现 将 月 份 名 翻 译 成 月 份 数 .( 然 而 散 列 不 仅 仅 局限 于 保 存 数 字 , 比 如 , 你 可 以 有 一 个 散 列 用 于 将 月 名 翻 译 成 诞 生 石 的 名 字 .)数 组 . 一 个 数 组 是 多 个 标 量 的 有 序 列 表 , 可 以 用 标 量 在 列 表 中 的 位 置 来 访 问 ( 注 : 也 可 以 说是 索 引 , 脚 标 定 位 , 查 找 , 你 喜 欢 用 哪 个 就 用 哪 个 ) 其 中 的 标 量 , 列 表 中 可 以 包 含 数 字 , 字符 串 或 同 时 包 含 这 两 者 .( 同 时 也 可 以 包 括 对 数 组 和 散 列 的 引 用 ), 要 对 一 个 数 组 赋 值 , 你只 需 简 单 的 将 这 些 值 排 列 在 一 起 , 并 用 大 括 弧 括 起 来 .@home = ("couch", "chair", "table", "stove");相 反 , 如 果 你 在 列 表 环 境 中 使 用 @home, 例 如 在 一 个 列 表 赋 值 的 右 边 , 你 将 得 到 与 你 放进 数 组 时 同 样 的 列 表 . 所 以 可 以 你 象 下 面 那 样 从 数 组 给 四 个 标 量 赋 值 :($potato, $lift, $tennis, $pipe) = @home;他 们 被 称 为 列 表 赋 值 , 他 们 逻 辑 上 平 行 发 生 , 因 此 你 可 以 象 下 面 一 样 交 换 两 个 变 量 :($alpha, $omega) = ( $omega, $alpha);和 C 里 一 样 , 数 组 是 以 0 为 基 的 , 你 可 以 用 下 标 0 到 3 来 表 示 数 组 的 第 一 到 第 四 个 元素 .( 注 : 如 果 你 觉 得 不 好 记 , 那 么 就 把 脚 标 当 做 偏 移 量 , 也 就 是 它 前 面 的 元 素 个 数 . 显 然 ,第 一 个 元 素 前 面 没 有 任 何 元 素 , 因 此 偏 移 量 是 0. 计 算 机 就 是 这 么 想 的 .) 数 组 下 标 使 用中 括 弧 包 围 [ 象 这 样 ], 因 此 如 果 你 想 选 用 独 立 的 数 组 元 素 , 你 可 以 表 示 为 $home[n], 这里 n 是 下 标 ( 元 素 编 码 减 一 ), 参 考 下 面 的 这 个 例 子 . 因 为 我 们 处 理 的 这 个 数 组 元 素 是 标量 , 因 此 在 他 前 面 总 是 前 缀 $.如 果 你 想 一 次 对 一 个 数 组 元 素 赋 值 , 你 可 以 使 用 下 面 的 方 法 :$home[0] = "couch";$home[1] = "chair";$home[2] = "table";$home[3] = "stove";因 为 数 组 是 有 序 的 , 所 以 你 可 以 在 它 上 面 做 很 多 很 有 用 操 作 . 例 如 堆 栈 操 作 push 和 pop,堆 栈 就 是 一 个 有 序 的 列 表 , 有 一 个 开 始 和 一 个 结 尾 . 特 别 是 有 一 个 结 尾 .<strong>Perl</strong> 将 你 数 组 的结 尾 当 成 堆 栈 的 顶 端 ( 也 有 很 多 的 <strong>Perl</strong> 程 序 员 认 为 数 组 是 水 平 的 , 因 此 堆 栈 的 顶 端 在 数 组的 右 侧 .)15


散 列 , 散 列 是 一 组 无 序 标 量 , 可 以 通 过 和 每 个 标 量 关 联 的 字 符 串 进 行 访 问 . 因 为 这 个 原 因 ,散 列 经 常 被 称 为 关 联 数 组 . 但 是 这 个 名 字 太 长 了 , 因 为 会 经 常 提 到 它 , 我 们 决 定 给 它 起 一 个简 短 的 名 字 . 我 们 称 之 为 散 列 的 另 外 一 个 原 因 是 为 了 强 调 它 们 是 无 序 的 .( 在 <strong>Perl</strong> 的 内 部实 现 中 , 散 列 的 操 作 是 通 过 对 一 个 散 列 表 查 找 完 成 的 , 这 就 是 散 列 为 什 么 这 么 快 的 原 因 , 而且 无 论 你 在 散 列 中 存 储 多 少 数 据 , 它 总 是 很 快 ). 然 而 你 不 能 push 或 pop 一 个 散 列 ,因 为 这 样 做 没 有 意 义 . 一 个 散 列 没 有 开 始 也 没 有 结 束 . 不 管 怎 么 样 , 散 列 的 确 非 常 有 用 而 且强 大 . 如 果 你 不 能 理 解 散 列 的 概 念 , 那 你 还 不 能 算 真 正 的 了 解 <strong>Perl</strong>. 图 1-1 显 示 了 一 个数 组 中 有 序 的 元 素 和 一 个 散 列 中 无 序 但 是 有 名 字 的 元 素 .因 为 散 列 不 是 根 据 位 置 来 访 问 的 , 因 此 你 在 构 建 散 列 时 必 须 同 时 指 定 数 值 和 键 字 , 你 仍 然 可以 象 一 个 普 通 的 数 组 那 样 给 散 列 赋 值 , 但 是 在 列 表 中 的 每 一 对 元 素 都 会 被 解 释 为 一 个 键 字 和一 个 数 值 . 因 为 我 们 是 在 处 理 一 对 元 素 , 因 此 散 列 使 用 % 这 个 趣 味 字 符 来 标 志 散 列 名 字( 如 果 你 仔 细 观 察 %, 你 会 发 现 斜 扛 两 边 的 键 字 和 数 值 . 这 样 理 解 可 能 会 帮 助 记 忆 .)假 设 你 想 把 简 写 的 星 期 名 称 转 换 成 全 称 , 你 可 以 使 用 下 面 的 赋 值 语 句 :%longday = ("Sun", "Sunday", "Mon", "Monday", "Tue", "Tuesday","Wed", "Wednesday", "Thu", "Thursday", "Fri","Friday", "Sat", "Saturday");不 过 上 面 地 写 法 非 常 难 读 懂 , 因 此 <strong>Perl</strong> 提 供 了 =>( 等 号 和 大 于 号 地 组 合 ) 来 做 逗 号 的 替代 操 作 符 . 使 用 这 种 表 示 方 法 , 可 以 非 常 容 易 地 看 出 哪 个 字 符 串 是 关 键 字 , 哪 个 是 关 联 的 值 .%longday = ("Sun" => "Sunday","Mon" => "Monday","Tue" => "Tuesday","Wed" => "Wednesday","Thu" => "Thursday","Fri" => "Friday","Sat" => "Saturday",);16


象 我 们 在 上 边 做 的 , 你 可 以 将 一 个 列 表 赋 值 给 一 个 散 列 , 同 样 如 果 你 在 一 个 列 表 环 境 中 使 用散 列 ,<strong>Perl</strong> 能 将 散 列 以 一 种 奇 怪 的 顺 序 转 换 回 的 键 字 / 数 值 列 表 . 通 常 人 们 使 用 keys 函数 来 抽 取 散 列 的 关 键 字 . 但 抽 取 出 来 的 键 字 也 是 无 序 的 . 但 是 你 用 sort 函 数 可 以 很 容 易 地对 它 进 行 排 序 . 然 后 你 可 以 使 用 排 过 序 的 键 字 以 你 想 要 的 顺 序 获 取 数 值 .因 为 散 列 是 一 种 特 殊 的 数 组 , 你 可 以 通 过 {} 来 获 取 单 个 的 散 列 元 素 . 比 如 , 如 果 你 想 找出 与 关 键 字 Wed 对 应 的 值 , 你 应 该 使 用 $longday{"Wed"}. 注 意 因 为 你 在 处 理 标 量 ,因 此 你 在 longday 前 面 使 用 $, 而 不 是 %,% 代 表 整 个 散 列 .通 俗 的 讲 , 散 列 包 含 的 关 系 是 所 有 格 的 , 象 英 文 里 面 的 of 或 者 's. 例 如 Adam 的 妻 子是 Eve, 所 以 我 们 用 下 面 的 表 达 式 :$wife{"Adam"} = "Eve";1.2.4 复 杂 数 据 结 构数 组 和 散 列 是 易 用 、 简 单 平 面 的 数 据 结 构 , 很 不 幸 , 现 实 总 不 能 如 人 所 愿 。 很 多 时 候 你 需要 使 用 很 难 、 复 杂 而 且 非 平 面 的 数 据 结 构 。<strong>Perl</strong> 能 使 复 杂 的 事 情 变 得 简 单 。 方 法 是 让 你 假装 那 些 复 杂 的 数 值 实 际 上 是 简 单 的 东 西 . 换 句 话 说 ,<strong>Perl</strong> 让 你 可 以 操 作 简 单 的 标 量 , 而 这些 标 量 碰 巧 是 指 向 复 杂 数 组 和 散 列 的 引 用 . 在 自 然 语 言 中 , 我 们 总 是 用 简 单 的 单 个 名 词 来 表示 复 杂 的 难 以 理 解 的 实 体 , 比 如 , 用 " 政 府 " 来 代 表 一 个 关 系 复 杂 的 硬 壳 等 等 .继 续 讨 论 上 个 例 子 , 假 设 我 们 想 讨 论 Jacob 的 妻 子 而 不 是 Adam 的 , 而 Jacob 有 四 个妻 子 ( 自 己 可 别 这 么 干 )。 为 了 在 <strong>Perl</strong> 中 表 示 这 个 数 据 结 构 , 我 们 会 希 望 能 将 Jocob 的四 个 妻 子 当 成 一 个 来 处 理 , 但 是 我 们 会 遇 到 一 些 问 题 。 你 可 能 认 为 我 们 可 以 用 下 面 的 语 句 来表 示 :$wife{"Jacob"} = ("Leah", "Rachel", 'Bilhah", "Zilpah");# 错但 是 这 并 不 能 象 你 希 望 的 那 样 运 转 , 因 为 在 <strong>Perl</strong> 中 括 弧 和 逗 号 还 不 够 强 大 , 还 不 能 将 一 个列 表 转 换 成 为 标 量 ( 在 语 法 中 , 圆 括 弧 用 于 分 组 , 逗 号 用 于 分 隔 )。 你 需 要 明 确 地 告 诉 <strong>Perl</strong>你 想 将 一 个 列 表 当 成 一 个 标 量 。[] 中 括 弧 能 够 实 现 这 个 转 换 :$wife{"Jacob"} = ["Leah", "Rachel", "Bilhah", "Zilpah"];# 正 确这 个 语 句 创 建 了 一 个 未 命 名 的 数 组 , 并 将 这 个 数 组 的 引 用 放 入 散 列 的 元 素 $wife{“Jacob”}中 . 因 此 我 们 有 了 一 个 命 名 的 散 列 , 其 中 包 含 了 一 个 未 命 名 的 数 组 . 这 就 是 <strong>Perl</strong> 处 理 多 维数 组 和 嵌 套 数 据 类 型 的 方 法 . 同 普 通 数 组 和 散 列 的 赋 值 方 法 一 样 , 你 可 以 单 独 对 其 进 行 赋 值 :$wife{"Jacob"}[0] = "Leah";17


$wife{"Jacob"}[1] = "Rachel";$wife{"Jacob"}[2] = "Bilhah";$wife{"Jacob"}[3] = "Zilpah";你 可 以 从 上 边 看 出 , 这 看 起 来 象 一 个 具 有 一 个 字 符 串 下 标 和 一 个 数 字 下 标 的 多 维 数 组 . 为 了更 多 了 解 树 状 结 构 , 如 嵌 套 数 据 结 构 , 假 设 我 们 希 望 不 仅 能 列 出 Jocob 的 妻 子 , 而 且 同 时能 列 出 每 个 妻 子 的 所 生 的 儿 子 。 这 种 情 况 下 , 我 们 希 望 将 散 列 结 构 也 当 成 一 个 标 量 , 我 们 可以 使 用 花 括 弧 来 完 成 ( 在 每 个 散 列 值 中 , 象 上 个 例 子 一 样 用 中 括 弧 表 示 数 组 , 现 在 我 们 有 了一 个 在 散 列 中 的 散 列 中 的 数 组 )。$kids_of_wife{"Jacob"} = {"Zebulun"],"Leah"=> ["Reuben", "Simeon", "Levi", "Judah", "Issachar","Rachel" => ["Joseph", "Benjamin"],"Bilhah" => ["Dan", "Naphtali"],"Zilpah" => ["Gad", "Asher"],};同 样 , 我 们 也 可 以 象 下 面 这 样 表 示 :$kids_of_wife{"Jacob"}{"Leah"}[0]$kids_of_wife{"Jacob"}{"Leah"}[1]$kids_of_wife{"Jacob"}{"Leah"}[2]$kids_of_wife{"Jacob"}{"Leah"}[3]$kids_of_wife{"Jacob"}{"Leah"}[4]$kids_of_wife{"Jacob"}{"Leah"}[5]= "Reuben";= "Simeon";= "Levi";= "Judah";= "Issachar";= "Zebulun";$kids_of_wife{"Jacob"}{"Rachel"}[0] = "Joseph";$kids_of_wife{"Jacob"}{"Rachel"}[1] = "Benjamin";$kids_of_wife{"Jacob"}{"Bilhah"}[0] = "Dan";$kids_of_wife{"Jacob"}{"Bilhah"}[1] = "Naphtali";$kids_of_wife{"Jacob"}{"Zilpah"}[0] = "Gad";18


$kids_of_wife{"Jacob"}{"Zilpah"}[1] = "Asher";可 以 从 上 面 看 出 , 在 嵌 套 数 据 结 构 中 增 加 一 层 , 就 像 在 多 维 数 组 中 增 加 了 一 维 。 在 <strong>Perl</strong> 内部 表 示 是 一 样 的 , 但 是 你 可 以 用 任 何 一 种 方 法 来 理 解 。这 里 最 重 要 的 一 点 就 是 ,<strong>Perl</strong> 可 以 用 简 单 的 标 量 来 代 表 复 杂 数 据 结 构 。<strong>Perl</strong> 利 用 这 种 简 单的 封 装 方 法 构 建 了 基 于 对 象 的 结 构 。 当 我 们 用 下 面 的 方 法 调 用 Camel 对 象 的 构 造 函 数 的时 候 :$fido = new Camel "Amelia";我 们 创 建 了 一 个 Camel 对 象 , 并 用 标 量 $fido 来 代 表 。 但 是 在 Camel 对 象 里 面 是 很 复杂 的 。 作 为 优 秀 的 面 向 对 象 的 程 序 员 , 我 们 不 想 关 心 Camel 对 象 里 面 的 细 节 ( 除 非 我 们是 实 现 Camel 类 方 法 的 人 )。 但 是 一 般 说 来 , 一 个 对 象 的 组 成 中 会 有 一 个 包 含 对 象 属 性的 散 列 。 例 如 它 的 名 字 ( 本 例 子 中 , 是 “Amelia” 而 不 是 “fido”), 还 有 驼 峰 的 数 量 ( 在 这里 我 们 没 有 明 确 定 义 , 因 此 使 用 缺 省 值 1, 和 封 面 一 样 )。1.2.5 简 单 数 据 结 构阅 读 完 上 一 节 , 你 一 定 会 感 到 有 点 头 晕 , 否 则 你 一 定 不 简 单 。 通 常 人 们 不 喜 欢 处 理 复 杂 数 据结 构 。 因 此 在 自 然 语 言 中 , 我 们 有 很 多 方 法 来 消 除 复 杂 性 。 很 多 其 中 的 方 法 都 归 结 到 “ 主 题化 ” 这 个 范 畴 , 主 题 化 是 一 个 语 言 学 概 念 , 指 在 谈 论 某 方 面 事 情 时 , 谈 论 双 方 保 持 一 致 。 主题 化 可 以 在 语 言 的 各 个 级 别 出 现 , 在 较 高 的 级 别 中 , 我 们 可 以 根 据 不 同 的 感 兴 趣 的 子 话 题 将自 己 分 成 不 同 的 文 化 类 型 , 同 时 建 立 一 些 专 有 语 言 来 讨 论 这 些 特 定 的 话 题 。 就 象 在 医 生 办 公室 中 的 语 言 (“ 不 可 溶 解 窒 息 物 ”) 和 在 巧 克 力 厂 中 的 语 言 ( “ 永 久 块 止 动 器 ”) 肯 定 是 有 差异 的 一 样 。 所 幸 , 我 们 能 够 在 语 言 环 境 发 生 转 换 时 能 够 自 动 适 应 新 的 语 言 环 境 。在 对 话 级 别 中 , 环 境 转 换 必 须 更 加 明 确 , 因 此 语 言 让 我 们 能 用 很 多 的 方 式 来 表 达 同 一 个 意 思 。我 们 在 书 和 章 节 的 开 头 加 上 题 目 。 在 我 们 的 句 子 中 , 我 们 会 用 “ 根 据 你 最 近 的 查 询 ” 或 “ 对 于所 有 的 X” 来 表 示 后 面 的 讨 论 主 题 。<strong>Perl</strong> 也 有 一 些 主 题 化 的 方 法 , 最 主 要 的 就 是 使 用 package 声 明 。 例 如 你 想 在 <strong>Perl</strong> 中 讨论 Camels, 你 会 在 Camel 模 块 中 以 下 面 的 方 法 开 头 :package Camel;这 个 开 头 有 几 个 值 得 注 意 的 效 果 , 其 中 之 一 就 是 从 这 里 开 始 ,<strong>Perl</strong> 认 为 所 有 没 有 特 别 指 出的 动 词 和 名 词 都 是 关 于 Camels 的 ,<strong>Perl</strong> 通 过 在 全 局 名 字 前 添 加 模 块 名 字 “Camel::” 来实 现 , 因 此 当 你 使 用 下 面 的 方 法 :package Camel;19


这 里 ,$fido 的 真 实 名 字 是 $Camel::fido(&fetch 的 真 实 名 字 是 &Camel::fetch)。这 就 意 味 着 如 果 别 人 在 其 他 模 块 中 使 用 :package Dog;$fido = &fetch();<strong>Perl</strong> 不 会 被 迷 惑 , 因 为 这 里 $fido 的 真 实 名 字 是 $Dog::fido, 而 不 是 $Camel::fido。计 算 机 科 学 家 称 之 为 一 个 package 建 立 了 一 个 名 字 空 间 。 你 可 以 建 立 很 多 的 名 字 空 间 ,但 是 在 同 一 时 间 你 只 能 在 一 个 名 字 空 间 中 , 这 样 你 就 可 以 假 装 其 他 名 字 空 间 不 存 在 。 这 就 是名 字 空 间 如 何 为 你 简 化 实 际 工 作 的 方 法 。 简 化 是 基 于 假 设 的 ( 当 然 , 这 是 否 会 过 于 简 化 , 这正 是 我 们 写 这 一 章 的 原 因 )保 持 动 词 的 简 洁 和 上 面 讨 论 保 持 名 词 的 简 洁 同 样 重 要 。 在 Camel 和 Dog 名 字 空 间 中 ,&Camel::fetch 不 会 与 &Dog::fetch 混 淆 , 但 包 的 真 正 好 处 在 于 它 们 能 够 将 你 的 动 词 分类 , 这 样 你 就 可 以 在 其 他 包 中 使 用 它 们 。 当 我 们 使 用 :$fido = new Camel "Amelia";我 们 实 际 上 调 用 了 Camel 包 中 的 &new, 它 的 全 名 是 &Camel::new。 并 且 当 我 们 使 用 :$fido->saddle();的 时 候 , 我 们 调 用 了 &Camel::saddle 过 程 , 因 为 $fido 记 得 它 是 指 向 一 个 Camel 对象 的 。 这 就 是 一 个 面 向 对 象 程 序 的 工 作 方 法 。当 你 说 package Camel 的 时 候 , 你 实 际 上 是 开 始 了 一 个 新 包 。 但 是 有 时 候 你 只 是 想 借 用其 他 已 有 包 的 名 词 和 动 词 。<strong>Perl</strong> 中 你 可 以 用 use 声 明 来 实 现 ,use 声 明 不 仅 可 以 让 你 使用 其 他 包 的 动 词 , 同 时 也 检 查 磁 盘 上 载 入 的 模 块 名 称 。 实 际 上 , 你 必 须 先 使 用 :use Camel;然 后 才 能 使 用 :$fido = new Camel "Amelia";不 然 的 话 ,<strong>Perl</strong> 将 不 知 道 Camel 是 什 么 东 西 。有 趣 的 是 , 你 自 己 并 不 需 要 真 正 知 道 Camel 是 什 么 , 你 可 以 让 另 外 一 个 人 去 写 Camel 模块 。 当 然 最 好 是 已 经 有 人 为 你 编 写 了 Camel 模 块 。 可 能 <strong>Perl</strong> 最 强 大 的 东 西 并 不 在 <strong>Perl</strong>本 身 , 而 在 于 CPAN(Comprehensive <strong>Perl</strong> Archive Network),CPAN 包 含 无 数 的 用 于实 现 不 同 任 务 模 块 。 你 不 需 要 知 道 如 何 实 现 这 些 任 务 , 只 需 要 下 载 这 些 模 块 , 并 简 单 用 下 面的 方 法 来 使 用 它 们 :20


use Some::Cool::Module;然 后 你 就 可 以 使 用 模 块 中 的 动 词 。因 此 , 象 自 然 语 言 中 的 主 题 化 一 样 ,<strong>Perl</strong> 中 的 主 题 化 能 够 ” 歪 曲 ” 使 用 处 到 程 序 结 束 中 的<strong>Perl</strong> 语 言 。 实 际 上 , 一 些 内 部 模 块 并 没 有 动 词 , 只 是 简 单 地 以 不 同 的 有 用 方 法 来 封 装 <strong>Perl</strong>语 言 。 我 们 称 这 些 模 块 为 用 法 。 比 如 , 你 经 常 看 到 很 多 人 使 用 strict:use strict;strict 模 块 干 的 事 是 更 加 严 格 地 约 束 <strong>Perl</strong> 中 的 一 些 规 则 , 这 样 你 在 很 多 方 面 必 须 更 明 确 ,而 不 是 让 <strong>Perl</strong> 去 猜 , 例 如 如 何 确 定 变 量 的 作 用 范 围 。 使 事 情 更 加 明 确 有 助 于 使 大 工 程 更 容易 操 作 。 缺 省 的 <strong>Perl</strong> 是 为 小 程 序 优 化 的 , 有 了 strict,<strong>Perl</strong> 对 于 那 些 需 要 更 多 维 护 的 大型 工 程 也 是 相 当 好 的 。 由 于 你 可 以 在 任 何 时 候 加 入 strict 用 法 , 所 以 你 可 以 容 易 地 将 小 型工 程 发 展 成 大 型 工 程 。 即 使 你 并 不 想 这 么 做 , 但 是 现 实 生 活 中 你 经 常 能 碰 到 这 种 情 况 。1.2.6 动 词和 典 型 的 祈 使 性 计 算 机 语 言 中 常 用 一 样 ,<strong>Perl</strong> 中 的 很 多 动 词 就 是 命 令 : 它 们 告 诉 <strong>Perl</strong> 解释 器 执 行 某 个 动 作 。 另 一 方 面 , 类 似 于 自 然 语 言 ,<strong>Perl</strong> 的 动 词 能 试 图 根 据 不 同 的 环 境 以 不同 方 向 执 行 。 一 个 以 动 词 开 头 的 语 句 通 常 是 纯 祈 使 句 , 并 完 全 为 其 副 作 用 进 行 计 算 。( 我 们有 时 候 称 这 些 动 词 过 程 , 尤 其 当 它 们 是 用 户 定 义 的 时 候 。) 一 个 常 用 的 内 建 命 令 ( 实 际 上 ,你 在 前 面 已 经 看 到 ) 是 print:print "Adam's wife is $wife{'Adam'}.\n"它 的 副 作 用 就 是 生 成 下 面 的 输 出 :Adam's wife is Eve。但 是 除 了 祈 使 语 气 以 外 , 动 词 还 有 其 他 一 些 语 气 。 有 些 动 词 是 询 问 问 题 并 在 条 件 下 十 分 有 用 ,例 如 if 语 句 。 其 他 的 一 些 动 词 只 是 接 受 输 入 参 数 并 且 返 回 返 回 值 , 就 象 一 个 处 方 告 诉 你 如何 将 原 材 料 做 成 可 以 吃 的 东 西 。 我 们 习 惯 叫 这 些 动 词 为 函 数 , 为 了 顺 从 那 些 不 知 道 英 语 中“functional” 意 思 的 数 学 家 们 的 习 惯 .下 面 是 内 建 函 数 的 一 个 例 子 , 这 就 是 指 数 函 数 :$e = exp(1); # 2.718281828459 或 者 类 似 的 数 值在 <strong>Perl</strong> 中 过 程 和 函 数 并 没 有 硬 性 的 区 别 。 你 将 发 现 这 两 个 概 念 经 常 能 够 互 换 。 我 们 经 常 称动 词 为 操 作 符 ( 内 建 ) 或 者 是 子 过 程 ( 用 户 自 定 义 )( 注 : 历 史 上 ,<strong>Perl</strong> 要 求 你 在 调 用 的21


任 何 用 户 定 义 子 过 程 的 前 面 加 一 个 与 号 (&)( 参 阅 早 先 时 候 的 $fido = &fetch();)。 但是 到 了 <strong>Perl</strong> 版 本 5, 这 个 与 号 是 可 选 的 了 , 所 以 用 户 定 义 动 词 现 在 可 以 和 内 建 动 词 相 同 的方 法 进 行 调 用 了 ($fido = fetch();)。 在 讲 到 关 于 过 程 的 名 字 的 时 候 , 我 们 仍 然 使 用 与 号 ,比 如 当 我 们 用 一 个 引 用 指 向 过 程 名 字 的 时 候 ($fetcher = \&fetch;)。 从 语 言 学 上 来 讲 ,你 可 以 把 与 号 形 式 的 &fetch 当 作 不 定 词 “to fetch”, 或 者 类 似 的 形 式 “to fetch”。 但 是 如果 我 们 可 以 只 说 “fetch” 的 时 候 , 我 们 很 少 说 “do fetch”。 这 才 是 我 们 在 <strong>Perl</strong> 5 里 去 掉 那个 命 令 性 的 与 号 的 原 因 。), 但 是 你 把 它 们 称 做 任 何 你 喜 欢 的 东 西 , 它 们 都 返 回 一 个 值 , 这个 值 可 能 有 意 义 , 也 可 能 没 有 什 么 意 义 。 你 可 以 简 单 的 省 略 掉 。当 我 们 继 续 学 习 的 时 候 , 你 可 以 看 到 很 多 其 他 的 例 子 说 明 <strong>Perl</strong> 和 自 然 语 言 一 样 工 作 。 但 还可 以 用 其 他 方 面 来 看 <strong>Perl</strong>。 我 们 已 经 从 数 学 语 言 中 借 用 了 一 些 概 念 , 例 如 下 标 , 加 法 和 指数 函 数 。 而 且 <strong>Perl</strong> 也 是 一 种 控 制 语 言 , 一 种 连 接 语 言 , 原 型 语 言 , 文 本 处 理 语 言 , 列 表 处理 语 言 以 及 面 向 对 象 的 语 言 。但 是 <strong>Perl</strong> 同 样 也 是 一 种 平 常 古 老 的 计 算 机 语 言 , 这 就 是 我 们 下 面 要 观 察 的 角 度 。1.3 一 个 平 均 值 例 子假 如 你 在 一 个 班 中 教 授 <strong>Perl</strong> 语 言 , 并 且 你 正 在 想 如 何 给 你 的 学 生 评 分 的 方 法 。 你 有 全 班 所有 人 每 次 考 试 的 成 绩 , 它 们 是 随 机 的 顺 序 , 你 可 能 需 要 一 个 所 有 同 学 的 等 级 列 表 , 加 上 他 们的 平 均 分 。 你 有 一 份 象 下 面 一 样 的 文 本 文 件 ( 假 设 名 字 为 grades):No&#235;l 25Ben 76Clementine 49Norm 66Chris 92Doug 42Carol 25Ben 12Clementine 0Norm 66...22


你 可 以 用 下 面 所 示 的 脚 本 将 所 有 的 成 绩 收 集 在 一 起 , 同 时 计 算 出 每 个 学 生 的 平 均 分 数 , 并 将它 们 按 照 字 母 顺 序 打 印 出 来 。 这 个 程 序 天 真 地 假 设 在 你 的 班 级 中 没 有 重 名 的 学 生 , 比 如 没 有两 个 名 为 Carol 的 学 生 。 如 果 班 级 中 有 两 个 Carol, 文 件 中 所 有 以 Carol 开 头 的 条 目 ,程 序 都 会 认 为 这 是 第 一 个 Carol 的 成 绩 ( 但 是 不 会 跟 Noel 的 成 绩 混 淆 )。顺 便 说 一 句 , 下 面 程 序 里 的 行 号 并 不 是 程 序 的 一 部 分 , 任 何 与 BASIC 类 似 的 东 西 都 是 站不 住 脚 的 。1 #!/usr/bin/perl23 open(GRADES, "grades") or die "Can't open grades: $!\n";4 while ($line = ) {5 ($student, $grade) = split(" ", $line);6 $grades{$student} .= $grade . " ";7 }89 foreach $student (sort keys %grades) {10 $scores = 0;11 $total = 0;12 @grades = split(" ", $grades{$student});13 foreach $grade (@grades) {14 $total += $grade;15 $scores++;16 }17 $average = $total / $scores;18 print "$student: $grades{$student}\tAverage: $average\n";19 }23


在 你 离 开 本 例 子 程 序 之 前 , 我 们 需 要 指 出 这 个 程 序 演 示 了 我 们 前 面 涉 及 到 的 许 多 内 容 , 还 要加 上 我 们 马 上 要 解 释 的 一 些 内 容 。 你 可 以 猜 测 下 面 将 要 讲 述 的 内 容 , 我 们 会 告 诉 你 你 的 猜 测是 否 正 确 。可 能 你 还 不 知 道 如 何 运 行 这 个 <strong>Perl</strong> 程 序 , 在 下 一 小 节 我 们 会 告 诉 你 。1.3.1 如 何 运 行刚 才 你 肯 定 想 知 道 如 何 运 行 一 个 <strong>Perl</strong> 程 序 。 最 简 单 的 回 答 就 是 你 可 以 将 程 序 送 进 <strong>Perl</strong> 语言 解 释 器 程 序 , 通 常 它 的 名 字 就 是 perl。 另 一 个 稍 微 长 一 些 的 答 案 就 是 : 回 字 有 四 种 写 法 。( 注 :There's More Than One Way To Do It. TMTOWTDI, 这 是 <strong>Perl</strong> 的 口 号 , 你 可能 都 听 烦 了 , 除 非 你 是 当 地 的 专 家 , 那 样 的 话 你 就 是 说 烦 了 。 有 时 候 我 们 缩 写 成TMOTOWTDI, 念 做 “tim-today”。 不 过 你 可 以 用 你 喜 欢 的 方 式 发 音 , 别 忘 了 ,TMTOWTDI 。 译 注 : 这 里 借 用 鲁 迅 先 生 的 名 言 “ 回 字 有 四 种 写 法 ” 好 象 不 算 过 分 吧 ? 不 管怎 样 , 回 字 是 有 四 种 写 法 。)第 一 种 运 行 perl 的 方 法 ( 也 是 大 多 数 操 作 系 统 都 能 用 的 法 子 ) 就 是 在 命 令 行 中 明 确 地 调 用perl 解 释 器 ( 注 : 假 设 你 的 操 作 系 统 提 供 一 个 命 令 行 接 口 。 如 果 你 运 行 的 是 老 的 Mac, 那么 你 可 能 需 要 升 级 到 一 个 BSD 的 版 本 , 比 如 Mac OS X)。 如 果 你 正 在 做 一 些 非 常 简 单的 事 情 , 你 可 以 直 接 使 用 -e 选 项 开 关 ( 下 面 例 子 中 的 % 表 示 标 准 的 shell 提 示 符 , 所以 在 你 运 行 的 时 候 , 不 需 要 输 入 它 们 )。 在 Unix 中 , 你 可 以 输 入 下 面 的 内 容 :%perl -e 'print "Hello, world!\n";'在 其 它 的 操 作 系 统 中 , 你 可 能 需 要 修 改 一 下 这 几 个 引 号 才 能 使 用 。 但 是 基 本 规 则 都 是 一 样 的 :你 必 须 将 <strong>Perl</strong> 需 要 知 道 的 东 西 塞 进 80 列 当 中 。( 注 : 这 种 类 型 的 脚 本 通 常 称 做 “ 单 行 程序 ”。 如 果 你 曾 经 和 其 他 <strong>Perl</strong> 程 序 员 交 流 过 , 那 么 你 就 会 发 现 我 们 中 有 些 人 非 常 喜 欢 写 这样 的 单 行 程 序 。 因 为 这 个 , 有 时 候 <strong>Perl</strong> 还 被 诽 谤 成 只 写 语 言 。)对 于 长 一 些 的 脚 本 , 你 可 以 使 用 你 熟 悉 的 文 本 编 辑 器 ( 可 以 是 任 何 文 本 编 辑 器 ), 将 你 所 有的 命 令 放 进 一 个 文 件 当 中 , 假 设 你 把 这 个 脚 本 命 名 为 gradation ( 不 要 和 graduation( 毕 业 ) 混 淆 ), 你 可 以 这 样 用 :%perl gradation你 现 在 仍 然 在 明 确 地 调 用 <strong>Perl</strong> 解 释 器 , 但 至 少 你 不 需 要 每 次 都 在 命 令 行 中 输 入 所 有 东 西 。而 且 你 也 不 需 要 为 各 个 shell 之 间 引 号 的 使 用 方 法 不 同 而 大 伤 脑 筋 。执 行 脚 本 最 方 便 的 方 法 就 是 只 需 要 直 接 输 入 脚 本 的 名 字 ( 或 者 点 击 它 ), 然 后 操 作 系 统 帮 你找 到 正 确 的 解 释 器 。 在 一 些 系 统 中 , 可 能 有 方 法 将 文 件 后 缀 或 目 录 和 特 定 的 应 用 程 序 关 联 起24


来 。 在 这 些 系 统 中 , 你 可 以 将 <strong>Perl</strong> 脚 本 与 perl 解 释 器 关 联 起 来 。 在 Unix 系 统 中 支 持#!”shebang” 标 志 ( 现 在 , 大 多 数 Unix 都 支 持 ), 你 可 以 使 你 的 脚 本 第 一 行 变 得 具 有 特殊 功 能 , 因 此 操 作 系 统 知 道 会 需 要 运 行 哪 个 程 序 。 在 我 们 例 子 中 , 用 下 面 的 语 句 作 为 第 一 行 :#! /usr/bin/perl( 如 果 <strong>Perl</strong> 不 在 /usr/bin 目 录 下 , 你 需 要 根 据 实 际 情 况 修 改 #! 行 ), 然 后 你 只 需 要简 单 地 输 入 :%gradation当 然 , 这 样 不 能 运 转 , 因 为 你 忘 了 确 定 脚 本 是 否 是 可 执 行 的 ( 参 看 chmod (1) 手 册 页 )以 及 程 序 是 否 在 你 的 运 行 路 径 下 ( 通 常 用 环 境 变 量 PATH 定 义 )。 如 果 不 在 你 的 环 境 变 量PATH 下 , 你 需 要 提 供 带 路 径 的 完 整 文 件 名 , 只 有 这 样 操 作 系 统 才 知 道 到 什 么 地 方 找 你 的脚 本 。 就 象 下 面 这 样 :%/home/sharon/bin/gradation最 后 , 如 果 你 在 古 老 的 Unix 上 工 作 , 它 不 支 持 #!, 或 者 你 的 解 释 器 的 路 径 超 过 32 个字 符 ( 在 很 多 系 统 上 的 一 个 内 置 限 制 ), 你 也 许 可 以 使 用 下 面 的 方 法 使 你 的 脚 本 工 作 :#! /bin/sh -- # perl, 用 于 停 止 循 环eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'if 0;不 同 的 操 作 系 统 和 不 同 的 命 令 行 解 释 器 , 如 /bin/csh,DCL,COMMAND.COM, 需 要 不同 的 方 法 来 符 合 不 同 需 要 。 或 者 你 还 有 一 些 其 它 的 缺 省 命 令 行 解 释 器 , 你 可 以 咨 询 你 身 边 的专 家 。本 书 中 , 我 们 只 使 用 #!/usr/bin/perl 来 代 表 所 有 的 其 它 的 标 志 , 但 是 你 要 知 道 我 们 真 正的 意 思 。另 外 , 当 你 写 测 试 脚 本 时 , 不 要 将 你 的 脚 本 命 名 为 test, 因 为 Unix 系 统 有 一 个 内 建 的 命令 叫 test 会 优 先 运 行 , 而 不 是 运 行 你 的 脚 本 。 用 try 做 名 字 。还 有 , 当 你 在 学 习 <strong>Perl</strong> 的 时 候 , 甚 至 在 你 认 为 已 经 掌 握 了 <strong>Perl</strong> 后 , 我 们 建 议 你 使 用 -w开 关 , 尤 其 在 你 开 发 的 过 程 中 。 这 个 选 项 会 打 开 所 有 有 用 的 和 有 趣 的 警 告 信 息 , 你 可 以 象 下面 例 子 中 一 样 , 将 -w 开 关 加 入 到 shenbang 行 中 :#! /usr/bin/perl -w25


现 在 你 已 经 知 道 如 何 运 行 你 自 己 的 <strong>Perl</strong> 程 序 ( 不 要 和 perl 解 释 器 混 淆 ), 让 我 们 回 到 例子 上 来 。1.4 文 件 句 柄除 非 你 在 用 人 工 智 能 来 制 作 唯 我 主 义 哲 学 家 的 模 型 , 否 则 你 的 程 序 肯 定 需 要 和 外 边 的 世 界 进行 通 讯 的 途 径 。 在 计 算 平 均 分 的 例 子 第 3, 4 行 , 你 可 以 看 到 GRADES 这 个 词 , 它 是 <strong>Perl</strong>另 一 个 数 据 : 类 型 文 件 句 柄 的 例 子 。 文 件 句 柄 只 是 你 给 文 件 , 设 备 , 网 络 套 接 字 或 管 道 起 的一 个 名 字 , 这 样 可 以 帮 助 你 分 清 你 正 在 和 那 个 文 件 或 设 备 通 讯 , 同 时 掩 藏 了 如 缓 冲 等 复 杂 性 。( 在 内 部 实 现 中 , 文 件 句 柄 近 似 于 C++ 中 的 流 , 或 者 BASIC 中 的 I/O 通 道 )。文 件 句 柄 能 帮 助 你 容 易 地 从 不 同 的 地 方 接 收 输 入 , 并 输 出 到 不 同 的 地 方 。<strong>Perl</strong> 能 成 为 一 种好 的 连 接 语 言 部 分 也 归 功 于 它 能 很 容 易 地 与 很 多 文 件 和 进 程 通 讯 。 有 很 好 的 符 号 名 字 来 表 示各 种 不 同 的 外 部 对 象 是 好 的 连 接 语 言 的 一 个 要 求 。( 注 : 其 他 一 些 令 <strong>Perl</strong> 成 为 优 秀 连 接 语言 的 方 面 包 括 :8 位 无 关 , 可 嵌 入 , 以 及 你 可 以 通 过 扩 展 模 块 嵌 入 其 他 语 言 。 它 的 一 致 性 ,以 及 它 的 “ 网 络 ” 易 用 性 。 它 与 环 境 相 关 性 。 你 可 以 用 许 多 不 同 的 方 法 调 用 它 ( 正 如 我 们 前 面看 到 的 )。 但 最 重 要 的 是 , 这 门 语 言 本 身 没 有 僵 化 的 结 构 要 求 , 搞 得 你 无 法 让 它 “ 绕 开 ” 你的 问 题 。 我 们 又 回 到 “ 回 字 有 四 种 写 法 ” 的 话 题 上 来 了 。)你 可 以 使 用 open 创 建 并 关 联 一 个 文 件 。open 函 数 需 要 至 少 两 个 参 数 : 文 件 句 柄 和 你 希望 与 文 件 句 柄 关 联 的 文 件 名 。<strong>Perl</strong> 也 给 你 一 些 预 定 义 ( 并 且 预 先 打 开 ) 的 文 件 句 柄 。STDIN是 我 们 程 序 的 标 准 输 入 ,STDOUT 是 标 准 输 出 。STDERR 是 一 个 额 外 的 输 出 途 径 , 这 样就 允 许 你 在 将 输 入 转 换 到 你 的 输 出 上 的 时 候 进 行 旁 路 。( 注 : 通 常 这 些 文 件 句 柄 附 着 在 你 的终 端 上 , 这 样 你 就 可 以 向 你 的 程 序 输 入 并 且 观 察 结 果 , 但 是 它 们 也 可 以 附 着 在 文 件 ( 之 类 )上 。 <strong>Perl</strong> 能 给 你 这 些 预 定 义 的 句 柄 是 因 为 你 的 操 作 系 统 已 经 通 过 某 种 方 式 提 供 它 们 了 。 在Unix 里 , 进 程 从 它 们 的 父 进 程 那 里 继 承 标 准 输 入 , 标 准 输 出 和 标 准 错 误 , 通 常 父 进 程 是shell。 shell 的 一 个 职 责 就 是 设 置 这 些 I/O 流 , 好 让 这 些 子 进 程 不 用 担 心 它 们 。)因 为 你 可 以 用 open 函 数 创 建 用 于 不 同 用 途 ( 输 入 , 输 出 , 管 道 ) 的 文 件 句 柄 , 因 此 你 必须 指 定 你 需 要 哪 种 类 型 。 象 在 命 令 行 中 一 样 , 你 只 需 简 单 地 在 文 件 名 中 加 入 特 定 的 字 符 。open(SESAME, "filename")# 从 现 存 文 件 中 读 取open(SESAME, "filename")open(SESAME, ">>filename")# 创 建 文 件 并 写 入# 附 加 在 现 存 文 件 后 面open(SESAME, "| output-pipe-command")# 设 置 一 个 输 出 过 滤 器26


open(SESAME, "input-pipe-command |")# 设 置 一 个 输 入 过 滤 器象 你 看 到 的 一 样 , 文 件 句 柄 可 以 使 用 任 何 名 字 。 一 旦 打 开 , 文 件 句 柄 SESAME 可 以 被 用来 访 问 相 应 的 文 件 或 管 道 , 直 到 它 被 明 确 地 关 闭 ( 也 许 你 可 以 猜 到 使 用 close(SESAME)来 关 闭 文 件 句 柄 ) 或 者 另 外 一 个 open 语 句 将 文 件 句 柄 同 别 的 文 件 关 联 起 来 。( 注 : 打 开一 个 已 经 打 开 的 文 件 句 柄 隐 含 地 关 闭 第 一 个 文 件 , 让 它 不 能 用 该 文 件 句 柄 访 问 , 然 后 再 打 开另 外 一 个 文 件 。 你 必 须 小 心 地 检 查 这 个 是 否 你 要 的 动 作 , 有 时 候 这 种 情 况 会 偶 然 发 生 , 比 如当 你 说 open($handle, $file) 的 时 候 ,$handle 碰 巧 包 含 着 一 个 常 量 字 串 。 确 保 设 置$handle 为 某 些 唯 一 的 东 西 , 否 则 你 就 是 在 同 样 的 文 件 句 柄 上 打 开 了 一 个 新 文 件 。 或 者 你可 以 让 $handle 是 未 定 义 ,<strong>Perl</strong> 自 然 会 给 你 填 充 它 。)一 旦 你 打 开 一 个 用 于 接 受 输 入 的 文 件 句 柄 , 你 可 以 使 用 读 行 操 作 符 来 读 入 一 行 , 因 为它 是 由 尖 括 弧 组 成 , 所 以 也 称 为 尖 角 操 作 符 。 读 行 操 作 符 用 于 括 住 与 你 需 要 读 入 文 件 相 关 联的 文 件 句 柄 ()。 当 使 用 空 的 读 行 操 作 符 时 , 将 读 入 命 令 行 上 指 定 的 所 有 文 件 , 当 命 令 行 未指 定 时 , 读 入 标 准 输 入 STDIN。( 这 是 很 多 过 滤 程 序 标 准 的 动 作 )。 一 个 使 用 STDIN 文件 句 柄 获 取 用 户 输 入 答 案 的 例 子 如 下 :print STDOUT "Enter a number: ";# 请 求 一 个 数 字$number = ;# 输 入 数 字print STDOUT "The number is $number.\n";# 打 印 该 数 字在 这 些 print 语 句 中 的 STDOUT 起 什 么 作 用 ? 实 际 上 这 只 是 你 使 用 输 出 文 件 句 柄 的 一种 方 法 。 文 件 句 柄 可 以 作 为 print 语 句 的 第 一 个 参 数 , 这 样 , 程 序 就 知 道 将 输 出 送 到 哪 里 ,在 这 个 例 子 中 , 文 件 句 柄 是 多 余 的 , 因 为 输 出 无 论 如 何 都 能 输 出 到 STDOUT。 就 象 STDIN是 缺 省 输 入 一 样 ,STDOUT 是 缺 省 的 输 出 途 径 。( 在 我 们 求 平 均 成 绩 的 例 子 中 , 第 18 行我 们 没 有 使 用 STDOUT 免 得 使 你 糊 涂 )。如 果 你 测 试 上 一 个 例 子 , 你 会 注 意 到 输 出 中 有 一 个 多 余 的 空 行 。 这 是 因 为 读 行 操 作 符 不 能 自动 将 新 行 符 从 你 的 输 入 中 删 除 。( 比 如 , 你 的 输 入 是 ”9\n”)。 为 了 方 便 你 去 除 新 行 符 ,<strong>Perl</strong> 提 供 了 chop 和 chomp 函 数 ,chop 不 加 区 别 地 去 处 字 符 串 地 最 后 一 个 字 符 , 并 将结 果 返 回 , 而 chomp 仅 删 除 结 束 标 记 ( 通 常 是 “\n”) 同 时 返 回 被 删 除 的 字 符 数 。 你 经 常能 看 见 这 样 来 处 理 单 行 输 入 :chop($number = );# 输 入 数 字 并 删 除 新 行还 有 另 外 一 种 写 法 :$number = ;chop($number);# 输 入 数 字# 删 除 新 行27


1.5 操 作 符正 象 我 们 以 前 提 过 的 一 样 ,<strong>Perl</strong> 也 是 一 种 数 学 语 言 。 这 可 以 从 几 个 层 次 上 来 说 明 , 从 基 于位 的 逻 辑 操 作 符 , 到 数 字 运 算 , 以 至 各 种 抽 象 。 我 们 都 学 过 数 学 , 也 都 知 道 数 学 家 们 喜 欢 使用 各 种 奇 怪 的 符 号 。 而 且 更 糟 的 是 , 计 算 机 学 家 建 立 了 一 套 他 们 自 己 的 奇 怪 符 号 。<strong>Perl</strong> 也有 很 多 这 些 奇 怪 符 号 , 幸 好 大 多 数 符 号 都 是 直 接 取 自 C,FORTRAN,sed (1) 和 awk (1),至 少 使 用 这 些 语 言 的 用 户 对 它 们 应 该 比 较 熟 悉 。 另 外 , 值 得 庆 幸 的 也 许 是 , 在 <strong>Perl</strong> 中 学 习这 些 奇 怪 符 号 , 可 以 为 你 学 习 其 它 奇 怪 语 言 开 一 个 好 头 。<strong>Perl</strong> 内 置 的 操 作 符 可 以 根 据 操 作数 的 数 目 分 为 单 目 , 双 目 和 三 目 操 作 符 。 也 可 以 根 据 操 作 符 的 位 置 分 为 前 置 ( 放 在 操 作 符 前面 ) 和 嵌 入 操 作 符 ( 在 操 作 符 中 间 )。 也 可 以 根 据 对 操 作 对 象 不 同 分 类 , 如 数 字 , 字 符 串 ,或 者 文 件 。 稍 后 我 们 会 提 供 一 个 列 出 所 有 操 作 符 的 表 格 , 但 现 在 我 们 需 要 先 学 习 一 些 简 单 常用 的 操 作 符 。1.5.1 双 目 算 术 操 作 符算 术 操 作 符 和 我 们 在 学 校 中 学 到 的 没 有 什 么 区 别 。 它 们 对 数 字 执 行 一 些 数 学 运 算 。 比 如 :例 子名 字 结 果$a + $b 加 法 将 $a 和 $b 相 加$a * $b 乘 法 $a 和 $b 的 积$a % $b 模 $a 被 $b 除 的 余 数$a ** $b 幂 取 $a 的 $b 次 幂在 这 里 我 们 没 有 提 及 减 法 和 除 法 , 我 们 认 为 你 能 够 知 道 它 们 是 怎 样 工 作 的 。 自 己 试 一 下 并 看看 是 不 是 和 你 想 象 的 一 样 ( 或 者 直 接 阅 读 第 三 章 , 单 目 和 双 目 操 作 符 ), 算 术 操 作 符 按 照 数学 老 师 教 我 们 的 顺 序 执 行 ( 幂 先 于 乘 法 , 乘 法 先 于 加 法 )。 同 样 你 可 以 用 括 弧 来 是 顺 序 更 加明 确 。1.5.2 字 符 串 操 作 符<strong>Perl</strong> 中 有 一 个 “ 加 号 ” 来 串 联 ( 将 字 符 串 连 接 起 来 ) 字 符 串 。 与 其 它 语 言 不 一 样 ,<strong>Perl</strong> 定 义了 一 个 分 隔 操 作 符 (.) 来 完 成 字 符 串 的 串 联 , 这 样 就 不 会 跟 数 字 的 加 号 相 混 淆 。$a = 123;$b = 456;print $a + $b; # 打 印 57928


print $a . $b; # 打 印 123456同 样 , 字 符 串 中 也 有 “ 乘 号 ”, 叫 做 “ 重 复 ” 操 作 符 。 类 似 的 , 采 用 分 隔 操 作 符 (x) 同 数 字 乘 法相 区 别 :$a = 123;$b = 3;print $a * $b; # 打 印 369print $a x $b; # 打 印 123123123这 些 字 符 串 操 作 符 和 它 们 对 应 的 算 术 操 作 符 关 系 紧 密 。 重 复 操 作 符 一 般 不 会 用 一 个 字 符 串 作为 左 边 参 数 , 一 个 数 字 作 为 右 边 参 数 。 同 样 需 要 注 意 的 是 <strong>Perl</strong> 是 怎 样 将 一 个 数 字 自 动 转 换成 字 符 串 的 。 你 可 以 将 上 边 所 有 的 数 字 都 用 引 号 括 起 来 , 但 是 它 们 仍 然 能 够 产 生 同 样 的 输 出 。在 内 部 , 它 们 已 经 以 正 确 的 方 向 转 换 了 ( 从 字 符 串 到 数 字 )。另 外 一 些 需 要 说 明 的 是 , 字 符 串 连 接 符 同 我 们 前 面 提 到 过 的 双 引 号 一 样 , 里 面 的 表 达 式 中 的变 量 将 使 用 它 所 包 含 的 内 容 。 并 且 当 你 打 印 一 串 值 时 , 你 同 样 得 到 已 经 连 接 过 的 字 符 串 , 下面 三 个 语 句 产 生 同 样 的 输 出 :print $a . ' is equal to ' . $b . ".\n";print $a, ' is equal to ', $b, ".\n";# 点 操 作 符# 列 表print "$a is equal to $b.\n";# 代 换什 么 时 候 使 用 哪 种 写 法 完 全 取 决 于 你 ( 但 是 代 换 的 写 法 是 最 容 易 读 懂 的 )。x 操 作 符 初 看 起 来 没 什 么 用 处 , 但 是 在 有 些 时 候 它 确 实 非 常 有 用 处 , 例 如 下 边 的 例 子 :print "-" x $scrwid, "\n";如 果 $scrwid 是 你 屏 幕 的 宽 度 , 那 么 程 序 就 在 你 的 屏 幕 上 画 一 条 线 。1.5.3 赋 值 操 作 符我 们 已 经 多 次 使 用 了 简 单 的 赋 值 操 作 符 =, 虽 然 准 确 地 说 它 不 是 一 个 数 学 操 作 符 。 你 可 以将 = 理 解 为 “ 设 为 ” 而 不 是 “ 等 于 ”( 数 学 等 于 操 作 符 == 才 表 示 等 于 , 如 果 你 现 在 就 开 始理 解 它 们 的 不 同 之 处 , 你 将 会 省 掉 日 后 的 许 多 烦 恼 。== 操 作 符 相 当 于 一 个 返 回 布 尔 值 的函 数 , 而 = 则 相 当 与 一 个 用 于 修 改 变 量 值 的 过 程 )。29


根 据 我 们 在 前 面 已 经 提 到 过 操 作 符 的 分 类 , 赋 值 操 作 符 属 于 双 目 中 缀 操 作 符 , 这 就 是 意 味 它们 在 操 作 符 的 两 边 都 有 一 个 操 作 数 。 操 作 符 的 右 边 可 以 是 任 何 表 达 式 , 而 左 边 的 操 作 数 则 必须 是 一 个 有 效 的 存 储 空 间 ( 也 就 是 一 个 有 效 的 存 储 空 间 , 比 如 一 个 变 量 或 者 是 数 组 中 的 一 个位 置 )。 最 普 通 的 赋 值 操 作 符 就 是 简 单 赋 值 , 它 计 算 右 边 表 达 式 的 值 , 然 后 将 左 边 的 变 量 设置 成 这 个 值 :$a = $b;$a = $b + 5;$a = $a * 3;注 意 在 最 后 的 这 个 例 子 中 , 赋 值 操 作 符 使 用 了 同 一 个 变 量 两 次 : 一 次 为 了 计 算 , 一 次 为 了 赋值 。 还 有 一 种 方 法 ( 从 c 语 言 中 借 鉴 过 来 ) 能 起 到 同 样 的 作 用 , 而 且 写 法 更 简 洁 :lvalue operator= expression和 下 面 的 这 种 写 法 是 一 样 的 :lvalue = lvalue operator expression区 别 只 是 lvaule 没 有 处 理 两 次 ( 只 有 当 给 lvalue 赋 值 时 有 副 作 用 , 这 两 种 方 法 才 会 有 差异 。 但 是 如 果 它 们 的 确 有 差 异 , 通 常 也 是 得 到 你 所 希 望 得 到 的 结 果 。 所 以 你 不 需 要 为 这 个 担心 )因 此 , 例 如 你 可 以 将 上 面 的 例 子 写 成 :$a *= 3;你 可 读 成 “ 用 3 乘 $a”。<strong>Perl</strong> 中 大 多 数 的 双 目 操 作 符 都 可 以 这 么 使 用 , 甚 至 有 些 你 在 c 语言 中 不 能 使 用 的 也 可 以 在 <strong>Perl</strong> 使 用 :$line .= "\n";$fill x=80;# 给 $line 附 加 一 个 新 行# 把 字 串 变 成 自 填 充 80 遍$val ||= "2"; # 如 果 $val 不 为 真 则 把 它 设 置 为 2在 我 们 求 平 均 成 绩 的 例 子 中 ( 注 : 我 们 还 没 忘 呢 , 你 呢 ?), 第 6 行 包 含 两 个 字 符 串 连 接 ,其 中 一 个 是 赋 值 操 作 符 。 同 时 第 14 行 有 一 个 +=。不 管 你 使 用 哪 种 赋 值 操 作 符 , 最 左 边 变 量 的 值 被 返 回 作 为 整 个 赋 值 表 达 式 的 值 。( 注 : 这 一点 和 象 Pascal 这 样 的 语 言 不 同 , 在 那 样 的 语 言 里 赋 值 是 一 个 语 句 , 不 返 回 值 。 我 们 前 面30


说 过 赋 值 类 似 一 个 过 程 , 但 是 请 记 住 在 <strong>Perl</strong> 里 , 每 个 过 程 都 返 回 值 。)C 语 言 的 程 序 员不 会 感 到 奇 怪 , 因 为 他 们 已 经 知 道 用 下 面 的 方 法 来 使 变 量 清 零 :$a = $b = $c =0;你 也 会 经 常 看 到 赋 值 语 句 在 while 循 环 中 作 为 条 件 , 例 如 求 平 均 成 绩 的 例 子 中 第 4 行 。真 正 能 使 c 程 序 员 惊 讶 的 是 在 <strong>Perl</strong> 中 , 赋 值 语 句 返 回 实 际 的 变 量 作 为 lvalue。 因 此 你 可以 在 同 一 个 语 句 中 多 次 改 变 同 一 个 变 量 的 值 。 例 如 可 以 使 用 下 面 的 语 句 :($temp -= 32) *= 5/9将 华 氏 温 度 转 换 成 摄 氏 温 度 。 这 也 是 为 什 么 在 本 章 的 前 面 我 们 能 使 用 下 面 的 语 句 :chop ($number = );上 面 的 语 句 能 将 $number 最 后 的 值 进 行 chop 操 作 。 通 常 , 当 你 想 在 拷 贝 的 同 时 进 行 一些 其 它 操 作 。 你 就 可 以 利 用 这 个 特 性 。1.5.4 单 目 算 术 操 作 符如 果 你 觉 得 $variable += 1 还 是 不 够 精 简 , 同 c 一 样 ,<strong>Perl</strong> 有 一 个 更 短 的 方 法 自 增 变量 。 使 用 自 增 ( 自 减 ) 操 作 符 可 以 将 变 量 的 值 简 单 地 加 上 ( 减 去 ) 一 。 你 可 以 将 操 作 符 放 在 变量 的 任 何 一 边 , 这 取 决 于 你 希 望 操 作 符 什 么 时 候 被 执 行 :例 子名 字 结 果++$a, $a++ 自 增 向 $a 加 一--$a, $a--自 减 从 $a 中 减 一如 果 你 将 自 增 ( 减 ) 操 作 符 放 在 变 量 的 前 边 , 变 量 就 成 为 “ 预 增 ” 变 量 。 变 量 的 值 在 它 被 引 用前 改 变 。 如 果 放 在 变 量 的 后 边 , 被 称 为 “ 后 增 变 量 ”, 它 在 被 引 用 后 改 变 。 如 :$a = 5; # 给 $a 赋 予 5$b = ++$a; # $b 被 赋 予 $a 自 增 之 后 的 值 ,6$c = $a--; # $c 被 赋 予 6, 然 后 $a 自 减 为 5平 均 成 绩 例 子 中 第 15 行 增 加 成 绩 个 数 , 这 样 我 们 就 知 道 我 们 统 计 了 多 少 个 成 绩 。 这 里 使用 了 一 个 后 增 操 作 符 ($scores++), 但 是 在 这 个 例 子 中 , 实 际 上 无 所 谓 使 用 哪 一 种 , 因为 表 达 式 在 一 个 空 环 境 里 , 在 这 种 环 境 中 , 表 达 式 只 是 为 了 得 到 增 加 变 量 的 值 这 个 副 作 用 ,31


而 把 返 回 的 值 丢 弃 了 。( 注 : 优 化 器 会 注 意 到 这 些 并 且 把 后 增 操 作 符 优 化 成 预 增 操 作 符 , 因为 它 执 行 起 来 略 微 快 一 些 。( 你 不 必 知 道 这 些 , 我 们 只 是 希 望 你 听 到 这 个 后 会 开 心 些 。))1.5.5 逻 辑 操 作 符逻 辑 操 作 符 , 也 称 为 “ 短 路 ” 操 作 符 , 允 许 程 序 不 使 用 嵌 套 if 语 句 , 而 根 据 多 个 条 件 来 决 定执 行 流 程 。 他 们 之 所 以 被 称 为 “ 短 路 ” 操 作 符 , 是 因 为 当 认 为 左 边 的 参 数 能 够 提 供 足 够 的 信 息来 决 定 整 个 值 的 时 候 , 它 们 跳 过 ( 短 路 ) 执 行 右 边 的 参 数 。 这 不 仅 仅 是 为 了 效 率 。 你 可 以 依靠 这 种 “ 短 路 ” 的 特 性 来 避 免 执 行 为 左 边 代 码 做 防 护 的 右 边 的 代 码 。 比 如 你 可 以 说“California or bust!”, 在 <strong>Perl</strong> 中 将 不 会 有 bust( 假 设 你 已 经 到 达 California)。实 际 上 <strong>Perl</strong> 有 两 组 逻 辑 操 作 符 , 一 组 传 统 操 作 符 是 借 鉴 了 C 中 的 操 作 符 , 另 外 一 组 比 较新 ( 或 者 更 旧 的 ) 低 级 操 作 符 借 鉴 了 BASIC 中 的 操 作 符 。 两 组 操 作 符 在 使 用 适 当 的 情 况下 都 很 易 读 。 如 果 你 希 望 逻 辑 操 作 符 比 逗 号 优 先 级 更 高 , 那 么 c 的 符 号 操 作 符 运 转 得 很 好 ,而 如 果 你 希 望 逻 辑 操 作 符 比 逗 号 优 先 级 更 低 时 ,BASIC 的 基 于 词 的 操 作 符 比 较 适 合 。 在 很多 情 况 下 , 它 们 得 到 相 同 的 结 果 。 使 用 哪 一 组 你 可 以 根 据 你 自 己 的 喜 好 来 选 择 。( 你 可 以 在第 三 章 中 的 “ 逻 辑 与 , 或 , 非 和 异 或 ” 小 节 找 到 对 比 例 子 )。 虽 然 由 于 优 先 级 不 同 , 这 两 组 操作 符 不 能 互 换 , 但 是 一 旦 它 们 已 经 被 解 析 , 操 作 符 的 效 果 是 一 样 的 , 优 先 级 只 能 在 它 们 参 数的 范 围 内 起 作 用 。 表 1-1 列 出 了 逻 辑 操 作 符 。表 1-1 逻 辑 操 作 符例 子名字结 果$a && $b 与 如 果 $a 为 假 则 为 $a, 否 则 为 $b$a $b 或 如 果 $a 为 真 则 为 $a, 否 则 为 $b! $a | 非 | 如 果 $a 为 假 则 为 真 |$a and $b 与 如 果 $a 为 假 则 为 $a, 否 则 为 $b$a or $b 或 如 果 $a 为 真 则 为 $a, 否 则 为 $bnot $a 非 如 果 $a 为 假 则 为 真$a xor $b 异 或 如 果 $a 或 $b 为 真 , 但 不 能 同 时 为 真因 为 逻 辑 操 作 符 有 ” 短 路 ” 的 特 性 , 因 此 它 们 经 常 被 使 用 在 条 件 执 行 代 码 里 面 . 下 面 的 代 码 ( 平均 分 数 例 子 中 的 第 4 行 ) 试 图 打 开 文 件 grades:open(GRADES, "grades") or die "Can't open file grades: $!\en";32


如 果 成 功 打 开 了 文 件 , 将 跳 到 下 一 行 继 续 执 行 , 如 果 不 能 打 开 文 件 , 程 序 将 打 印 一 个 错 误 信息 并 停 止 执 行 .从 字 面 意 义 上 看 , 这 行 代 码 表 示 ”Open gradues or bust”, 短 路 操 作 符 保 留 了 虚 拟 流 程 . 重要 的 动 作 在 操 作 符 的 左 边 , 右 边 藏 着 第 二 个 动 作 ($! 变 量 包 含 了 操 作 系 统 返 回 的 错 误 信 息 ,参 看 28 章 , 特 殊 名 字 ), 当 然 , 这 些 逻 辑 操 作 符 也 可 以 用 在 传 统 的 条 件 判 断 中 , 例 如 if 和while 语 句 中 . 赋 值1.5.6 比 较 操 作 符比 较 操 作 符 告 诉 我 们 两 个 标 量 值 ( 数 字 或 字 符 串 ) 之 间 的 比 较 关 系 . 这 里 也 有 两 组 关 系 比 较操 作 符 , 一 组 用 于 数 字 比 较 , 另 一 组 用 于 字 符 串 比 较 .( 两 组 操 作 符 都 要 求 所 有 的 参 数 都 必须 先 转 换 成 合 适 的 类 型 ), 假 设 两 个 参 数 是 $a 和 $b, 我 们 可 以 :比 较 数 字 字 串 返 回 值等 于 == eq 如 果 $a 等 于 $b 返 回 真不 等 于 = ne 如 果 $a 不 等 于 $b 返 回 真小 于 < lt 如 果 $a 小 于 $b 返 回 真大 于 > gt 如 果 $a 大 于 $b 返 回 真小 于 或 等 于


-d $a 目 录 如 果 在 $a 中 命 名 的 文 件 是 目 录 则 为 真-f $a 文 件 如 果 在 $a 中 命 名 的 文 件 是 普 通 文 件 则 为 真-T $a 文 本 文 件 如 果 在 $a 中 命 名 的 文 件 是 文 本 文 件 则 为 真你 可 以 象 下 面 例 子 中 一 样 使 用 它 们 :-e "/usr/bin/perl" or warn "<strong>Perl</strong> is improperly installed\n"; -f "/vmlinuz" andprint "I see you are a friend of Linus\n";注 意 普 通 文 件 并 不 等 同 于 文 本 文 件 , 二 进 制 文 件 /vmlinuz 是 普 通 文 件 , 而 不 是 文 本 文件 . 文 本 文 件 与 二 进 制 文 件 对 应 . 而 普 通 文 件 则 是 和 目 录 及 设 备 等 非 普 通 文 件 相 对 应 .在 <strong>Perl</strong> 中 有 很 多 文 件 测 试 操 作 符 , 很 多 没 有 在 这 里 被 列 出 来 . 这 些 操 作 符 大 多 数 都 是 单 目的 布 尔 操 作 符 , 它 们 只 有 一 个 操 作 数 ( 一 个 指 向 文 件 名 或 文 件 句 柄 的 标 量 ), 并 返 回 一 个 真或 假 的 布 尔 值 . 其 中 一 小 部 分 操 作 符 返 回 一 些 其 它 有 趣 的 东 西 , 例 如 文 件 的 大 小 和 存 在 的 时间 , 但 是 你 可 以 在 你 需 要 用 的 时 候 查 阅 第 三 章 的 ” 命 名 单 目 操 作 符 和 文 件 测 试 操 作 符 ” 小 节 .1.6 流 程 控 制到 目 前 为 止 , 除 了 一 个 大 的 例 子 外 , 所 有 其 它 的 例 子 都 是 只 有 线 性 代 码 ; 我 们 按 顺 序 执 行 代码 . 我 们 也 已 经 接 触 到 了 一 些 例 子 中 , 利 用 ” 短 路 ” 操 作 符 来 使 得 单 个 命 令 被 ( 或 不 被 ) 执 行 . 虽然 你 可 以 写 出 很 多 有 用 的 线 性 代 码 ( 很 多 的 CGI 脚 本 就 是 这 个 类 型 的 ), 但 是 使 用 条 件 表达 式 和 循 环 机 制 可 以 写 出 很 多 更 加 强 大 的 程 序 . 它 们 在 一 起 被 称 为 控 制 结 构 . 因 此 你 也 可 以认 为 <strong>Perl</strong> 是 一 种 控 制 语 言 .为 了 能 够 控 制 , 你 必 须 能 够 做 决 定 , 为 了 做 决 定 , 你 必 须 知 道 假 和 真 之 间 的 差 别 .1.6.1 什 么 是 真我 们 前 面 已 经 涉 及 到 真 的 概 念 ,( 注 : 严 格 说 , 我 们 这 么 说 并 不 正 确 ). 同 时 我 们 也 提 到 了一 些 返 回 真 或 假 的 操 作 符 . 在 进 行 更 深 入 的 讨 论 之 前 , 我 们 应 该 给 所 提 到 真 和 假 准 确 的 定义 .<strong>Perl</strong> 中 真 的 判 断 与 大 多 数 的 计 算 机 语 言 中 的 稍 微 有 些 不 同 , 但 是 通 过 一 段 时 间 的 使 用 ,你 会 对 它 有 更 多 的 认 识 ( 实 际 上 , 我 们 希 望 你 能 通 过 阅 读 下 面 的 内 容 获 得 很 多 的 认 识 ).基 本 上 ,<strong>Perl</strong> 以 自 明 的 方 式 处 理 真 . 这 是 一 种 灵 活 的 方 法 , 你 可 以 确 定 出 几 乎 所 有 事 物 的真 值 .<strong>Perl</strong> 使 用 非 常 实 用 的 方 法 来 定 义 真 , 真 的 定 义 依 赖 于 你 所 处 理 的 事 物 的 类 型 . 事 实上 , 真 值 的 种 类 要 比 不 真 的 种 类 多 得 多 .34


在 <strong>Perl</strong> 中 , 真 总 是 在 标 量 环 境 中 处 理 . 除 此 外 没 有 任 何 的 类 型 强 制 要 求 . 下 面 是 标 量 可 以表 示 的 不 同 种 类 的 真 值 :• 除 了 ”” 和 ”0”, 所 有 字 符 串 为 真• 除 了 0, 所 有 数 字 为 真• 所 有 引 用 为 真• 所 有 未 定 义 的 值 为 假 .1.6.2 If 和 unless 语 句早 些 时 候 , 我 们 已 经 看 到 一 个 逻 辑 操 作 符 如 何 起 一 个 条 件 的 作 用 . 一 个 比 逻 辑 操 作 符 稍 微 复杂 一 些 的 形 式 是 if 语 句 。if 语 句 计 算 一 个 真 假 条 件 ( 布 尔 表 达 式 ) 并 且 在 条 件 为 真 时 执 行一 个 代 码 段 。if ($debug_level > 0) {# Something has gone wrong. Tell the user.print "Debug: Danger, Will Robinson, danger!\en";print "Debug: Answer was '54', expected '42'.\en";}一 个 代 码 段 是 由 一 对 花 括 弧 括 在 一 起 的 一 些 语 句 。 因 为 if 语 句 执 行 代 码 段 , 因 此 花 括 弧 是必 须 的 。 如 果 你 对 一 些 其 它 语 言 比 较 熟 悉 , 例 如 C, 你 会 发 现 它 们 的 不 同 之 处 , 在 C 中 ,如 果 你 只 有 一 条 语 句 , 那 么 你 可 以 省 略 花 括 弧 。 但 是 在 <strong>Perl</strong> 中 , 花 括 弧 是 必 须 的 。有 些 时 候 , 当 一 个 条 件 满 足 后 执 行 一 个 代 码 段 并 不 能 满 足 要 求 , 你 可 能 要 求 在 条 件 不 满 足 的情 况 下 执 行 另 外 一 个 代 码 段 。 当 然 你 可 以 用 两 个 if 语 句 来 完 成 , 其 中 一 个 正 好 和 另 一 个 相反 , 为 此 <strong>Perl</strong> 提 供 了 更 好 的 方 法 , 在 第 一 个 代 码 段 后 边 ,if 有 一 个 可 选 的 条 件 叫 else,当 条 件 为 假 时 运 行 其 后 的 代 码 段 。( 经 验 丰 富 的 程 序 员 对 此 不 会 感 到 惊 讶 。)当 你 有 多 于 两 个 选 择 的 时 候 , 你 可 以 使 用 elsif 条 件 表 达 式 来 表 示 另 一 个 可 能 选 择 ,( 经 验丰 富 的 程 序 员 可 能 对 “elsif” 的 拼 写 感 到 惊 讶 , 不 过 我 们 这 里 没 有 人 准 备 道 歉 , 抱 歉 。)if ($city eq "New York") {print "New York is northeast of Washington, D.C.\en";35


}elsif ($city eq "Chicago") {print "Chicago is northwest of Washington, D.C.\en";}elsif ($city eq "Miami") {print "Miami is south of Washington, D.C. And much warmer!\en";}else {print "I don't know where $city is, sorry.\en";}if 和 elsif 子 句 按 顺 序 执 行 , 直 到 其 中 的 一 个 条 件 被 发 现 是 真 的 或 到 了 else 条 件 为 止 ,当 发 现 其 中 的 一 个 条 件 是 真 的 , 就 执 行 它 的 代 码 段 , 然 后 跳 过 所 有 其 余 的 分 支 。 有 时 候 , 你可 能 希 望 在 条 件 为 假 的 时 候 执 行 代 码 , 而 不 想 在 条 件 为 真 时 执 行 任 何 代 码 。 使 用 一 个 带 else的 空 if 语 句 看 起 来 会 比 较 零 乱 。 而 用 否 定 的 if 会 难 以 理 解 , 这 就 象 在 英 语 中 说 “ 如 果 这 不是 真 的 , 就 做 某 事 ” 一 样 古 怪 。 在 这 种 情 况 下 , 你 可 以 使 用 unless 语 句 :unless ($destination eq $home) { print "I'm not going home.\en"; }但 是 , 没 有 elsunless。 这 通 常 解 释 为 if 的 一 个 特 性 。1.6.3 循 环<strong>Perl</strong> 有 四 种 循 环 语 句 的 类 型 :while,until,for 和 foreach。 这 些 语 句 可 以 允 许 一 个 <strong>Perl</strong>程 序 重 复 执 行 同 一 些 代 码 。1.6.3.1 while 和 until 语 句除 了 是 重 复 执 行 代 码 段 以 外 ,While 和 until 语 句 之 间 的 关 系 就 象 if 和 unless 的 关 系一 样 。 首 先 , 检 查 条 件 部 分 , 如 果 条 件 满 足 (while 语 句 是 真 ,until 是 假 ), 执 行 下 面 代码 段 , 例 如 :while ($tickets_sold < 10000) {36


$available = 10000 - $tickets_sold;print "$available tickets are available. How many would you like: ";$purchase = ;chomp($purchase);$tickets_sold += $purchase;}注 意 如 果 没 满 足 最 初 的 条 件 , 就 根 本 不 会 进 入 循 环 。 例 如 , 假 设 我 们 已 经 卖 出 10000 张票 , 我 们 可 能 希 望 执 行 下 面 的 代 码 :print "This show is sold out, please come back later.\en";在 我 们 前 面 的 “ 平 均 分 ” 例 子 中 , 第 4 行 :while ($line = ) {这 句 代 码 将 文 件 的 下 一 行 内 容 赋 值 给 变 量 $line 并 且 返 回 $line 的 值 , 因 此 while 语 句的 条 件 表 达 式 为 真 , 你 也 许 想 知 道 , 当 遇 到 空 白 行 时 ,<strong>Perl</strong> 是 不 是 返 回 假 并 且 过 早 地 退 出循 环 。 答 案 是 否 定 的 , 如 果 你 还 记 得 我 们 先 前 学 过 的 内 容 , 那 么 理 由 是 非 常 明 显 的 。 读 行 操作 符 在 字 符 串 最 后 并 不 去 掉 新 行 符 , 因 此 空 白 行 的 值 是 “\n”。 并 且 我 们 知 道 “\n” 不 是 假 值 。因 此 条 件 表 达 式 为 真 , 并 且 循 环 将 继 续 。另 一 方 面 , 当 我 们 最 后 达 到 文 件 结 束 的 时 候 , 读 行 操 作 符 将 返 回 未 定 义 值 , 未 定 义 值 总 是 被解 释 为 假 。 并 且 循 环 结 束 , 正 好 是 我 们 希 望 结 束 的 时 候 。 在 <strong>Perl</strong> 中 不 需 要 明 确 地 测 试 eof的 返 回 值 , 因 为 输 入 操 作 符 被 设 计 成 可 以 在 条 件 环 境 下 很 好 的 工 作 。实 际 上 , 几 乎 所 有 东 西 都 设 计 成 在 条 件 环 境 下 能 很 好 工 作 , 如 果 在 一 个 标 量 环 境 中 使 用 一 个数 组 , 就 会 返 回 数 组 的 长 度 。 因 此 你 使 用 下 面 的 代 码 处 理 命 令 行 参 数 :while (@ARGV) {process(shift @ARGV);}每 次 循 环 ,shift 操 作 符 都 从 参 数 数 组 中 删 除 一 个 元 素 ( 同 时 返 回 这 个 元 素 ), 当 数 组@ARGV 用 完 时 循 环 自 动 退 出 , 这 时 候 数 组 长 度 变 为 0, 而 在 <strong>Perl</strong> 中 认 为 0 为 假 。 所 以数 组 本 身 已 经 变 为 “ 假 ”。( 注 : 这 是 <strong>Perl</strong> 程 序 员 的 看 法 , 因 此 我 们 没 有 比 较 拿 0 和 0 比较 以 证 实 它 是 否 为 假 。 但 是 其 他 语 言 却 强 迫 你 这 么 做 , 如 果 你 不 写 while(@ARGV = 0) 就37


不 退 出 。 这 样 做 不 论 对 你 还 是 对 计 算 机 还 是 以 后 维 护 你 的 代 码 的 人 来 说 , 都 是 效 率 低 下 的 做法 。)1.6.3.2 for 语 句另 外 一 个 循 环 语 句 就 是 for 循 环 。for 循 环 和 while 循 环 非 常 相 似 , 但 是 看 起 来 有 很 多 不同 之 处 。(C 语 言 程 序 员 会 觉 得 和 C 中 的 for 循 环 非 常 相 似 。)for ($sold = 0; $sold < 10000; $sold += $purchase) {$available = 10000 - $sold;print "$available tickets are available. How many would you like: ";$purchase = ;chomp($purchase);}for 循 环 在 园 括 弧 中 有 三 个 表 达 式 : 一 个 表 达 式 初 始 化 循 环 变 量 , 一 个 对 循 环 变 量 进 行 条 件判 断 , 还 有 一 个 表 达 式 修 改 条 件 变 量 。 当 一 个 for 循 环 开 始 时 , 设 置 初 始 状 态 并 且 检 查 条件 判 断 , 如 果 条 件 判 断 为 真 , 就 执 行 循 环 体 。 当 循 环 体 中 的 语 句 , 修 改 表 达 式 执 行 。 并 且 再次 检 查 条 件 判 断 , 如 果 为 真 , 循 环 体 返 回 下 一 个 值 , 如 果 条 件 判 断 值 为 真 , 循 环 体 和 修 改 表达 式 将 一 直 执 行 。( 注 意 只 有 中 间 的 条 件 判 断 才 求 值 , 第 一 个 和 第 三 个 表 达 式 只 是 修 改 了 变量 的 值 , 并 将 结 果 直 接 丢 弃 !)1.6.3.3 foreach 语 句<strong>Perl</strong> 中 最 后 一 种 循 环 语 句 就 是 foreach 语 句 , 它 是 用 来 针 对 一 组 标 量 中 的 每 一 个 标 量 运行 同 一 段 程 序 , 例 如 一 个 数 组 :foreach $user (@users) {if (-f "$home{$user}/.nexrc") {print "$user is cool\&\.\.\. they use a perl-aware vi!\en";}}38


不 同 于 if 和 while 语 句 ,foreach 的 条 件 表 达 式 时 在 列 表 环 境 中 , 而 不 是 标 量 环 境 。 因此 表 达 式 用 于 生 成 一 个 列 表 ( 即 使 列 表 中 只 有 一 个 标 量 )。 然 后 列 表 中 的 每 个 元 素 按 顺 序 作为 循 环 变 量 , 同 时 循 环 体 代 码 针 对 每 个 元 素 执 行 一 次 。 注 意 循 环 变 量 直 接 指 向 元 素 本 身 , 而不 是 它 的 一 个 拷 贝 , 因 此 , 修 改 循 环 变 量 , 就 是 修 改 原 始 数 组 。你 将 发 现 在 <strong>Perl</strong> 程 序 中 ,foreach 循 环 比 for 循 环 要 多 得 多 , 这 是 因 为 <strong>Perl</strong> 经 常 使 用一 些 需 要 使 用 foreach 遍 历 的 各 种 列 表 。 你 经 常 会 看 到 使 用 下 面 的 代 码 来 遍 历 散 列 的 关 键字 :foreach $key (sort keys %hash) {实 际 上 “ 平 均 分 ” 例 子 中 的 第 9 行 也 用 到 了 它 。1.6.3.4 跳 出 控 制 结 构 : next 和 lastnext 和 last 操 作 符 允 许 你 在 循 环 中 改 变 程 序 执 行 的 方 向 。 你 可 能 会 经 常 遇 到 一 些 的 特 殊情 况 , 碰 到 这 种 情 况 时 你 希 望 跳 过 它 , 或 者 想 退 出 循 环 。 比 如 当 你 处 理 Unix 账 号 时 , 你也 许 希 望 跳 过 系 统 账 号 ( 比 如 root 或 lp),next 操 作 符 允 许 你 将 跳 至 本 次 循 环 的 结 束 ,开 始 下 一 个 循 环 。 而 last 操 作 符 允 许 你 跳 至 整 个 循 环 的 结 束 , 如 同 循 环 条 件 表 达 式 为 假 时发 生 的 情 况 一 样 。 例 如 在 下 面 例 子 中 , 你 正 在 查 找 某 个 特 殊 账 号 , 并 且 希 望 找 到 后 立 即 退 出循 环 , 这 时 候 ,last 就 非 常 有 用 了 :foreach $user (@users) {if ($user eq "root" or $user eq "lp") {next;}if ($user eq "special") {print "Found the special account.\en";# 做 些 处 理last;}}39


当 在 循 环 中 做 上 标 记 , 并 且 指 定 了 希 望 退 出 的 循 环 ,next 和 last 就 能 退 出 多 重 循 环 。 结合 语 句 修 饰 词 ( 我 们 稍 后 会 谈 到 的 条 件 表 达 式 的 另 外 一 种 形 式 ), 能 写 出 非 常 具 有 可 读 性 的退 出 循 环 代 码 ( 如 果 你 认 为 英 语 是 非 常 容 易 读 懂 的 ):LINE: while ($line = ) {last LINE if $line eq "\en"; # 在 第 一 个 空 白 行 处 停 止next LINE if $line =~ /^#/; # 忽 略 注 释 行# 你 的 东 西 放 在 这 里}你 也 许 会 说 : 稍 等 , 在 双 斜 杠 内 的 ^# 看 起 来 并 不 象 英 语 。 没 错 , 这 就 是 包 含 了 一 个 正 则表 达 式 的 模 式 匹 配 ( 虽 然 这 是 一 个 很 简 单 的 正 则 表 达 式 )。 在 下 一 节 中 , 我 们 将 讲 述 正 则 表达 式 。<strong>Perl</strong> 是 最 好 的 文 本 处 理 语 言 , 而 正 则 表 达 式 是 <strong>Perl</strong> 文 本 处 理 的 核 心 。1.7 正 则 表 达 式正 则 表 达 式 ( 也 可 以 表 示 为 regexes,regexps 或 Res) 广 泛 使 用 在 很 多 搜 索 程 序 里 ,比 如 :grep 和 findstr, 文 本 处 理 程 序 如 :sed 和 awk, 和 编 辑 器 程 序 , 如 :vi 和 emacs。一 个 正 则 表 达 式 就 是 一 种 方 法 , 这 种 方 法 能 够 描 述 一 组 字 符 串 , 但 不 用 列 出 所 有 的 字 符 串 。( 注 : 一 本 关 于 正 则 表 达 式 的 概 念 的 好 书 是 Jeffrey Friedl 的 “Mastering RegularExpressions”(O'Reilly & Associates)其 它 的 一 些 计 算 机 语 言 也 提 供 正 则 表 达 式 ( 其 中 的 一 些 甚 至 宣 扬 “ 支 持 <strong>Perl</strong>5 正 则 表 达 式 ”)但 是 没 有 一 种 能 象 <strong>Perl</strong> 一 样 将 正 则 表 达 式 和 语 言 结 合 成 一 体 。 正 则 表 达 式 有 几 种 使 用 方法 , 第 一 种 , 也 是 最 常 用 的 一 种 , 就 是 确 定 一 个 字 符 串 中 是 否 匹 配 某 个 模 式 , 因 为 在 一 个 布尔 环 境 中 它 们 返 回 真 或 假 。 因 此 当 看 见 /foo/ 这 样 的 语 句 出 现 在 一 个 条 件 表 达 式 中 , 我 们就 知 道 这 是 一 个 普 通 的 模 式 匹 配 操 作 符 :if (/Windows 95/) { print "Time to upgrade?\n" }第 二 种 方 法 , 如 果 你 能 将 一 个 模 式 在 字 符 串 中 定 位 , 你 就 可 以 用 别 的 东 西 来 替 换 它 。 因 此 当看 见 s/foo/bar/ 这 样 的 语 句 , 我 们 就 知 道 这 表 示 将 foo 替 换 成 bar。 我 们 叫 这 是 替 换 操作 符 。 同 样 , 它 根 据 是 否 替 换 成 功 返 回 真 或 假 。 但 是 一 般 我 们 需 要 的 就 是 它 的 副 作 用 :s/Windows/Linux/;最 后 , 模 式 不 仅 可 以 声 明 某 地 方 是 什 么 , 同 样 也 可 以 声 明 某 地 方 不 是 什 么 。 因 此 split 操作 符 使 用 了 一 个 正 则 表 达 式 来 声 明 哪 些 地 方 不 能 匹 配 。 在 split 中 , 正 则 表 达 式 定 义 了 各40


个 数 据 域 之 间 定 界 的 分 隔 符 。 在 我 们 的 “ 平 均 分 ” 例 子 中 , 我 们 在 第 5 和 12 行 使 用 了 两 次split, 将 字 符 串 用 空 格 分 界 以 返 回 一 列 词 。 当 然 你 可 以 用 正 则 表 达 式 给 split 指 定 任 何 分界 符 :($good, $bad, $ugly) = split(/,/, "vi,emacs,teco");(<strong>Perl</strong> 中 有 很 多 修 饰 符 可 以 让 我 们 能 轻 松 完 成 一 些 古 怪 的 任 务 , 例 如 在 字 符 匹 配 中 忽 略 大小 写 。 我 们 将 在 下 面 的 章 节 中 讲 述 这 些 复 杂 的 细 节 )正 则 表 达 式 最 简 单 的 应 用 就 是 匹 配 一 个 文 字 表 达 式 。 象 上 面 的 例 子 中 , 我 们 匹 配 单 个 的 逗 号 。但 如 果 你 在 一 行 中 匹 配 多 个 字 符 , 它 们 必 须 按 顺 序 匹 配 。 也 就 是 模 式 将 寻 找 你 希 望 要 的 子 串 。下 面 例 子 要 完 成 的 任 务 是 , 我 们 想 显 示 一 个 html 文 件 中 所 有 包 含 HTTP 连 接 的 行 。 我 们假 设 我 们 是 第 一 次 接 触 html, 而 且 我 们 知 道 所 有 的 这 些 连 接 都 是 有 “http:”, 因 此 我 们 写出 下 面 的 循 环 :while ($line = ) {if ($line =~ /http:/) {print $line;}}在 这 里 ,=~ 符 号 ( 模 式 绑 定 操 作 符 ) 告 诉 <strong>Perl</strong> 在 $line 中 寻 找 匹 配 正 则 表 达 式 “http:”,如 果 发 现 了 该 表 达 式 , 操 作 符 返 回 真 并 且 执 行 代 码 段 ( 一 个 打 印 语 句 )。( 注 : 非 常 类 似 于Unix 命 令 grep 'http:' file 做 的 事 情 , 在 MS-DOS 里 你 可 以 用 find 命 令 , 但 是 它 不知 道 如 何 做 更 复 杂 的 正 则 表 达 式 。( 不 过 ,Windows NT 里 名 字 错 误 的 findstr 程 序 知 道正 则 表 达 式 。)) 另 外 , 如 果 你 不 是 用 =~ 操 作 符 ,<strong>Perl</strong> 会 对 缺 省 字 符 串 进 行 操 作 。 这 就是 你 说 “Eek, 帮 我 找 一 下 我 的 联 系 镜 头 !”, 别 人 就 会 自 动 在 你 周 围 寻 找 , 而 不 用 你 明 确 告诉 他 们 。 同 样 ,<strong>Perl</strong> 也 知 道 当 你 没 有 告 诉 它 在 那 里 寻 找 的 时 候 , 它 会 在 一 个 缺 省 的 地 方 寻找 。 这 个 缺 省 的 字 符 串 就 是 $_ 这 个 特 殊 标 量 。 实 际 上 ,$_ 并 不 是 仅 仅 是 模 式 匹 配 的 缺 省字 符 串 。 其 它 的 一 些 操 作 符 缺 省 也 使 用 $_ 变 量 。 因 此 一 个 有 经 验 的 <strong>Perl</strong> 程 序 员 会 将 上个 例 子 写 成 :while () {print if /http:/;}( 这 里 我 们 又 提 到 另 外 一 个 语 句 修 饰 词 。 阴 险 的 小 动 物 。)41


上 边 的 例 子 十 分 简 洁 , 但 是 如 果 我 们 想 找 出 所 有 连 接 类 型 而 不 是 只 是 http 连 接 时 怎 么 办 ?我 们 可 以 给 出 很 多 连 接 类 型 : 象 “http:”,“ftp:”,“mailto:” 等 等 。 我 们 可 以 使 用 下 面 的 代码 来 完 成 , 但 是 当 我 们 需 要 加 进 一 种 新 的 连 接 类 型 时 怎 么 办 ?while () {print if /http:/;print if /ftp:/;print if /mailto:/;# 下 一 个 是 什 么 ?}因 为 正 则 表 达 式 是 一 组 字 符 串 的 抽 象 , 我 们 可 以 只 描 述 我 们 要 找 的 东 西 : 后 面 跟 着 一 个 冒 号的 一 些 字 符 。 用 正 则 表 达 式 表 示 为 /[a-zA-Z]+:/, 这 里 方 括 弧 定 义 了 一 个 字 符 表 ,a-z 和A-Z 代 表 所 有 的 字 母 字 符 ( 划 线 表 示 从 开 头 的 字 符 到 结 尾 字 符 中 间 的 所 有 字 母 字 符 )。+ 是一 个 特 殊 字 符 , 表 示 匹 配 “+ 前 边 内 容 一 次 或 多 次 ”。 我 们 称 之 为 量 词 , 这 是 表 示 允 许 重 复多 少 次 的 符 号 。( 这 里 反 斜 杠 不 是 正 则 表 达 式 的 一 部 分 , 但 是 它 是 模 式 匹 配 操 作 符 的 一 部 分 。在 这 里 , 反 斜 杠 与 包 含 正 则 表 达 式 的 双 引 号 起 同 样 的 作 用 )。因 为 字 母 字 符 这 种 类 型 经 常 要 用 到 , 因 此 <strong>Perl</strong> 定 义 了 下 面 一 些 简 写 的 方 式 :名 字 ASCII 定 义代 码空 白 [\t\n\r\f] \s词 [a-zA-Z_0-9] \w数 字 [0-9] \d注 意 这 些 简 写 都 只 匹 配 单 个 字 符 , 比 如 一 个 \w 匹 配 任 何 单 个 字 符 , 而 不 是 整 个 词 。( 还记 得 量 词 + 吗 ? 你 可 以 使 用 \w+ 来 匹 配 一 个 词 )。 在 <strong>Perl</strong> 中 , 这 些 通 配 符 的 大 写 方 式代 表 的 意 思 和 小 写 方 式 刚 好 相 反 。 例 如 你 可 以 使 用 \D 表 示 左 右 非 数 字 字 符 。我 们 需 要 额 外 注 意 的 是 ,\w 并 不 是 总 等 于 [a-zA-Z0-9] ( 而 且 \d 也 不 总 等 于 [0-9]),这 是 因 为 有 些 系 统 自 定 义 了 一 些 ASCII 外 的 额 外 的 字 符 ,\w 就 代 表 所 有 的 这 些 字 符 。 较新 版 本 的 <strong>Perl</strong> 也 支 持 Unicode 字 符 和 数 字 特 性 , 并 且 根 据 这 些 特 性 来 处 理 Unicode 字符 。(<strong>Perl</strong> 认 为 \w 代 表 表 意 文 字 )。还 有 一 种 非 常 特 别 的 字 符 类 型 , 用 “.” 来 表 示 , 这 将 匹 配 所 有 的 字 符 。( 注 : 除 了 通 常 它 不会 匹 配 一 个 新 行 之 外 。 如 果 你 有 怀 疑 , 点 “.” 在 grep (1) 里 通 常 也 不 匹 配 新 行 。) 例 如 ,/a./将 会 匹 配 所 有 含 有 一 个 “a” 并 且 “a” 不 是 最 后 一 个 字 符 的 字 符 串 。 因 而 它 将 匹 配 “at” 或 “am”42


甚 至 “a!”, 但 不 匹 配 “a”, 因 为 没 有 别 的 字 母 在 “a” 的 后 面 。 同 时 因 为 它 在 字 符 串 的 任 何 地 方进 行 匹 配 , 所 以 它 将 匹 配 “oasis” 和 “camel”, 但 不 匹 配 “sheba”。 它 将 匹 配 “caravan” 中的 第 一 个 ”a”。 它 能 和 第 二 个 “a” 匹 配 , 但 它 在 找 到 第 一 个 合 适 的 匹 配 后 就 停 止 了 。 查 找 方 向是 由 左 向 右 。1.7.1 量 词刚 才 我 们 讨 论 的 字 符 和 字 符 类 型 都 只 能 匹 配 单 个 字 符 , 我 们 提 到 过 你 可 以 用 \w+ 来 匹 配多 个 “ 文 本 ” 字 符 。 这 里 + 就 是 量 词 , 当 然 还 有 其 它 一 些 。 所 有 的 量 词 都 放 在 需 要 多 重 匹配 的 东 西 后 边 。最 普 通 的 量 词 就 是 指 定 最 少 和 最 多 的 匹 配 次 数 。 你 可 以 将 两 个 数 字 用 花 括 弧 括 起 来 , 并 用 逗号 分 开 。 例 如 , 你 想 匹 配 北 美 地 区 的 电 话 号 码 , 使 用 \d{7,11} 将 匹 配 最 少 7 位 数 字 ,但 不 会 多 于 11 位 数 字 。 如 果 在 括 弧 中 只 有 一 个 数 字 , 这 个 数 字 就 指 定 了 最 少 和 最 多 匹 配次 数 , 也 就 是 指 定 了 准 确 的 匹 配 次 数 ( 其 它 没 有 使 用 量 词 的 项 我 们 可 以 认 为 使 用 了 {1})。如 果 你 的 花 括 弧 中 有 最 少 次 数 和 逗 号 但 省 略 了 最 大 次 数 , 那 么 最 大 次 数 将 被 当 作 无 限 次 数 。也 就 是 说 , 该 正 则 表 达 式 将 最 少 匹 配 指 定 的 最 少 次 数 , 并 尽 可 能 多 地 匹 配 后 面 的 字 符 串 。 例如 \d{7} 将 匹 配 开 始 的 七 位 号 码 ( 一 个 本 地 北 美 电 话 号 码 , 或 者 一 个 较 长 电 话 号 码 的 前 七位 ), 但 是 当 你 使 用 \d{7,} 将 会 匹 配 任 何 电 话 号 码 , 甚 至 一 个 国 际 长 途 号 码 ( 除 非 它 少于 七 位 数 字 )。 你 也 可 以 使 用 这 种 表 达 式 来 表 示 “ 最 多 ” 这 个 含 义 , 例 如 。{0,5} 表 示 至 多五 个 任 意 字 符 。一 些 特 殊 的 最 少 和 最 多 地 经 常 会 出 现 , 因 此 <strong>Perl</strong> 定 义 了 一 些 特 殊 的 运 算 符 来 表 示 他 们 。 象我 们 看 到 过 的 +, 代 表 {1,}, 意 思 为 “ 最 少 一 次 ”。 还 有 *, 表 示 {0,}, 表 示 “ 零 次 或 多次 ”. ? 表 示 {0,1}, 表 示 “ 零 或 一 次 ”。对 于 量 词 而 言 , 你 需 要 注 意 以 下 一 些 问 题 。 首 先 , 在 缺 省 状 态 下 ,<strong>Perl</strong> 量 词 都 是 贪 婪 的 ,也 就 是 他 们 将 尽 可 能 多 地 匹 配 一 个 字 符 串 中 最 大 数 量 的 字 符 , 例 如 , 如 果 你 使 用 /\d+/ 来匹 配 字 符 串 “1234567890”, 那 么 正 则 表 达 式 将 匹 配 整 个 字 符 串 。 当 你 使 用 “.” 时 特 别 需 要注 意 , 例 如 有 下 边 一 个 字 符 串 :larry:JYHtPh0./NJTU:100:10:Larry Wall:/home/larry:/bin/tcsh并 且 想 用 /.+:/ 来 匹 配 “larry:”, 但 是 因 为 + 是 贪 婪 的 , 这 个 模 式 将 匹 配 一 直 到/home/larry: 为 止 。 因 为 它 尽 可 能 多 地 匹 配 直 到 最 后 出 现 的 一 个 冒 号 。 有 时 候 你 可 以 使 用反 向 的 字 符 类 来 避 免 上 边 的 情 况 , 比 如 使 用 /[^:]+:/, 表 示 匹 配 一 个 或 多 个 不 是 冒 号 的 的字 符 ( 也 是 尽 可 能 多 ), 这 样 正 则 表 达 式 匹 配 至 第 一 个 冒 号 。 这 里 的 ^ 表 示 后 边 的 字 符 表的 反 集 。( 注 : 抱 歉 , 我 们 不 是 有 意 选 用 这 个 名 词 的 , 所 以 别 骂 我 们 。 这 也 是 Unix 里 写43


反 字 符 表 的 习 惯 方 式 。) 另 外 需 要 仔 细 观 察 的 就 是 , 正 则 表 达 式 将 尽 早 进 行 匹 配 。 甚 至 在 它变 得 贪 婪 以 前 。 因 为 字 符 串 扫 描 是 从 左 向 右 的 , 这 就 意 味 着 , 模 式 将 尽 可 能 在 左 边 得 到 匹 配 。尽 管 也 许 在 后 边 也 能 得 到 匹 配 。( 正 则 表 达 式 也 许 贪 婪 , 但 不 会 错 过 满 足 条 件 的 机 会 )。 例如 , 假 设 你 在 使 用 替 换 命 令 (s///) 处 理 缺 省 字 符 串 ( 变 量 $_), 并 且 你 希 望 删 除 中 间 的所 有 的 x。 如 果 你 说 :$_ = "fred xxxxxxx barney";s/x*//;但 是 上 面 的 代 码 并 没 有 达 到 预 想 的 目 的 , 这 是 因 为 x*( 表 示 零 次 或 多 次 “x”) 在 字 符 串 的开 始 匹 配 了 空 字 符 串 , 因 为 空 字 符 串 具 有 零 字 符 宽 度 , 并 且 在 fred 的 f 字 符 前 正 好 有 一个 空 字 串 。( 注 : 千 万 不 要 感 觉 不 爽 , 即 使 是 作 者 也 经 常 惨 遭 毒 手 。)还 有 一 件 你 必 须 知 道 的 事 情 , 缺 省 时 量 词 作 用 在 它 前 面 的 单 个 字 符 上 , 因 此 /bam{2}/ 将匹 配 “bamm” 而 不 是 “bambam”。 如 果 你 要 对 多 于 一 个 字 符 使 用 量 词 , 你 需 要 使 用 圆 括 弧 ,因 此 为 了 匹 配 “bambam” 需 要 使 用 /(bam){2}/。1.7.2 最 小 匹 配如 果 你 在 使 用 老 版 本 的 <strong>Perl</strong> 并 且 你 不 想 使 用 贪 婪 匹 配 , 你 必 须 使 用 相 反 的 字 符 表 ( 实 际 上 ,你 还 是 在 使 用 不 同 形 式 的 贪 婪 匹 配 )。在 新 版 本 <strong>Perl</strong> 中 , 你 可 以 强 制 进 行 非 贪 婪 匹 配 。 在 量 词 后 面 加 上 一 个 问 号 来 表 示 最 小 匹 配 。我 们 同 样 的 用 户 名 匹 配 就 可 以 写 成 /.*?:/。 这 里 的 .*? 现 在 尽 可 能 少 地 匹 配 字 符 , 而 不 是尽 可 能 多 的 匹 配 字 符 。 所 以 它 将 停 止 在 第 一 个 冒 号 而 不 是 最 后 一 个 。1.7.3 把 钉 子 敲 牢你 无 论 什 么 时 候 匹 配 一 个 模 式 , 正 则 表 达 式 都 尝 试 在 每 个 地 方 进 行 匹 配 直 到 找 到 一 个 匹 配 为止 。 一 个 锚 点 允 许 你 限 制 模 式 能 在 什 么 地 方 匹 配 。 基 本 来 说 , 锚 点 匹 配 一 些 “ 无 形 ” 的 东 西 ,这 些 东 西 依 赖 于 周 边 的 特 殊 环 境 。 你 可 以 称 他 们 为 规 则 , 约 束 或 断 言 。 不 管 你 怎 么 称 呼 它 ,它 都 试 图 匹 配 一 些 零 宽 度 的 东 西 , 或 成 功 或 失 败 ( 失 败 仅 仅 意 味 着 这 个 模 式 用 这 种 特 殊 的 方法 不 能 匹 配 。 如 果 还 有 其 它 方 法 可 以 试 的 话 , 该 模 式 会 继 续 用 其 它 方 法 进 行 匹 配 。)特 殊 符 号 \b 匹 配 单 词 边 界 , 就 是 位 于 单 词 字 符 (\w) 和 非 单 词 字 符 (\W) 之 间 的 零 宽度 的 地 方 。( 字 符 串 的 开 始 和 结 尾 也 被 认 为 是 非 单 词 字 符 )。 例 如 : /\bFred\b/ 将 会 匹 配“The Great Fred” 和 “Fred the Great” 中 的 Fred, 但 不 能 匹 配 “Frederick the Great” ,因 为 在 “Frederick” 中 的 “d” 后 面 没 有 跟 着 非 单 词 字 符 。44


同 理 , 也 有 表 示 字 符 串 开 始 和 结 尾 的 锚 点 ,^ 如 果 放 在 模 式 中 的 第 一 个 字 符 , 将 匹 配 字 符串 的 开 始 。 因 此 , 模 式 /^Fred/ 将 匹 配 “Frederick the Great” 中 的 “Fred”, 但 不 配 “TheGreat Fred” 中 的 ”Fred”。 相 反 ,/Fred^/ 两 者 都 不 匹 配 。( 实 际 上 , 它 也 没 有 什 么 意 义 。)美 元 符 号 ($) 类 似 于 ^, 但 是 $ 匹 配 字 符 串 的 结 尾 而 不 是 开 头 。( 注 : 这 么 说 有 点 过 于简 单 了 , 因 为 我 们 在 这 里 假 设 你 的 字 串 不 包 含 新 行 ;^ 和 $ 实 际 上 是 用 于 行 的 开 头 和 结 尾 ,而 不 是 用 于 字 串 的 。 我 们 将 在 第 五 章 , 模 式 匹 配 里 通 篇 强 调 这 一 点 ( 做 我 们 做 得 到 的 强 调 )。)现 在 你 肯 定 已 经 理 解 下 面 的 代 码 :next LINE if $line =~ /^#/;这 里 我 们 想 做 的 是 “ 当 遇 到 以 # 开 头 的 行 , 则 跳 至 LINE 循 环 的 下 一 次 循 环 。”早 些 时 候 , 我 们 提 到 过 \d{7,11} 将 匹 配 一 个 长 度 为 7 到 11 位 的 数 字 。 但 是 严 格 来 讲 ,这 个 语 句 并 不 十 分 正 确 : 当 你 在 一 个 真 正 的 模 式 匹 配 操 作 符 中 使 用 它 的 时 候 , 如/\d{7,11}/, 它 并 不 排 除 在 11 位 匹 配 的 数 字 外 的 数 字 。 因 此 你 通 常 需 要 在 量 词 两 头 使 用锚 点 来 获 取 你 所 要 的 东 西 。1.7.4 反 引 用我 们 曾 经 提 到 过 可 以 用 圆 括 弧 来 为 量 词 包 围 一 些 字 符 。 同 样 , 你 也 可 以 使 用 圆 括 弧 来 记 住 匹配 到 的 东 西 。 正 则 表 达 式 中 的 一 对 圆 括 弧 使 得 这 部 分 匹 配 到 的 东 西 将 被 记 住 以 供 以 后 使 用 。它 不 会 改 变 匹 配 的 方 式 , 因 此 /\d+/ 和 /(\d+)/ 仍 然 会 尽 可 能 多 地 匹 配 数 字 。 但 后 边 的写 法 能 够 把 匹 配 到 的 数 字 保 存 到 一 个 特 殊 变 量 中 , 以 供 以 后 反 向 引 用 。如 何 反 向 引 用 保 存 下 来 的 匹 配 部 分 决 定 于 你 在 什 么 地 方 使 用 它 , 如 果 在 同 一 个 正 则 表 达 式中 , 你 可 以 使 用 反 斜 杠 加 上 一 个 整 数 。 数 字 代 表 从 左 边 开 始 计 数 左 圆 括 弧 的 个 数 ( 从 一 开 始计 数 )。 例 如 , 为 了 匹 配 HTML 中 的 标 记 如 “Bold”, 你 可 以 使 用 /.*?/。这 样 强 制 模 式 的 两 个 部 分 都 匹 配 同 样 的 字 符 串 。 在 此 , 这 个 字 符 串 为 “B”.如 果 不 在 同 一 个 正 则 表 达 式 , 例 如 在 替 换 的 置 换 部 分 中 使 用 反 引 用 , 你 可 以 使 用 $ 后 边 跟一 个 整 数 。 看 起 来 是 一 个 以 数 字 命 名 的 普 通 标 量 变 量 。 因 此 , 如 果 你 想 将 一 个 字 符 串 的 前 两个 词 互 相 调 换 , 你 可 以 使 用 下 面 的 代 码 :/(\S+)\s+(\S+)/$2 $1/上 边 代 码 的 右 边 部 分 ( 第 二 第 三 个 反 斜 杠 之 间 ) 几 乎 就 是 一 个 双 引 号 字 符 串 , 在 这 里 可 以 代换 变 量 。 包 括 反 向 引 用 。 这 是 一 个 强 大 的 概 念 : 代 换 ( 在 有 所 控 制 的 环 境 中 ) 是 <strong>Perl</strong> 成 为一 种 优 秀 的 文 本 处 理 语 言 的 重 要 原 因 之 一 。 另 外 一 个 原 因 就 是 模 式 匹 配 , 当 然 , 正 则 表 达 式方 便 将 所 需 要 的 东 西 分 离 出 来 , 而 代 换 可 以 方 便 将 这 些 东 西 放 回 字 符 串 。45


1.8 列 表 处 理本 章 早 些 时 候 , 我 们 提 过 <strong>Perl</strong> 有 两 种 主 要 的 环 境 : 标 量 环 境 ( 处 理 单 个 的 事 物 ) 和 列 表 环境 ( 处 理 复 数 个 事 物 )。 我 们 描 述 过 的 很 多 传 统 操 作 符 都 是 严 格 在 标 量 环 境 下 执 行 。 他 们 总是 有 单 数 的 参 数 ( 或 者 象 双 目 操 作 符 一 样 有 一 对 单 数 的 参 数 ) 并 且 产 生 一 个 单 数 的 返 回 值 。甚 至 在 列 表 环 境 中 亦 如 此 。 当 你 使 用 下 面 的 代 码 :@array = (1 + 2, 3 - 4, 5 * 6, 7 / 8);你 知 道 右 边 的 的 列 表 中 包 含 四 个 值 , 因 为 普 通 数 学 操 作 符 总 是 产 生 标 量 , 即 使 是 在 给 一 个 数组 赋 值 这 样 的 列 表 环 境 中 。但 是 , 有 一 些 <strong>Perl</strong> 操 作 符 能 根 据 不 同 的 环 境 产 生 一 个 标 量 或 列 表 环 境 。 他 们 知 道 程 序 需 要标 量 环 境 还 是 列 表 环 境 。 但 是 你 如 何 才 能 知 道 ? 下 面 是 一 些 关 键 的 概 念 , 当 你 理 解 这 些 概 念之 后 , 你 就 能 很 容 易 地 知 道 需 要 标 量 还 是 列 表 了 。首 先 , 列 表 环 境 必 须 是 周 围 的 事 物 提 供 的 , 在 上 个 例 子 中 , 列 表 赋 值 提 供 了 列 表 环 境 。 早 些时 候 , 我 们 看 到 过 foreach 循 环 也 能 提 供 列 表 环 境 。 还 有 print 操 作 符 也 能 提 供 。 但 是你 不 必 逐 个 学 习 他 们 。如 果 你 通 读 本 书 其 余 部 分 种 不 同 的 语 法 说 明 , 你 会 看 到 一 些 操 作 符 定 义 为 使 用 LIST 作 为参 数 。 这 就 是 提 供 列 表 环 境 的 操 作 符 。 在 本 书 中 ,LIST 作 为 一 种 特 殊 的 技 术 概 念 表 示 ” 提供 列 表 环 境 的 句 法 ”。 例 如 , 你 观 察 sort, 你 可 以 总 结 为 :sort LIST这 表 示 ,sort 给 它 的 参 数 提 供 了 一 个 列 表 环 境 。其 次 , 在 编 译 的 时 候 ( 当 <strong>Perl</strong> 分 析 你 的 程 序 , 并 翻 译 成 内 部 操 作 码 的 时 候 ), 任 何 使 用 LIST的 操 作 符 给 LIST 的 每 个 语 法 元 素 提 供 了 列 表 环 境 。 因 此 , 在 编 译 的 时 候 , 每 个 顶 层 操 作符 和 LIST 中 的 每 个 元 素 都 知 道 <strong>Perl</strong> 假 设 它 们 使 用 自 己 知 道 的 方 法 生 成 最 好 的 列 表 。 例如 当 你 使 用 下 面 的 代 码 :sort @dudes, @chicks, other();那 么 @dudes,@chicks, 和 other() 都 知 道 在 编 译 的 时 候 <strong>Perl</strong> 假 设 它 们 都 产 生 一 个 列表 值 而 不 是 一 个 标 量 值 。 因 此 编 译 器 产 生 反 映 上 述 内 容 的 内 部 操 作 码 。其 后 , 在 运 行 时 候 ( 当 内 部 执 行 码 被 实 际 解 释 的 时 候 ), 每 个 LIST 成 员 按 顺 序 产 生 列 表 ,然 后 将 所 有 单 独 的 列 表 连 接 在 一 起 ( 这 很 重 要 ), 形 成 一 个 单 独 的 列 表 。 并 且 这 个 平 面 的 一维 列 表 最 后 由 那 些 需 要 LIST 的 函 数 使 用 。 因 此 如 果 @dudes 包 含 (Fred, Barney),46


@chicks 包 含 (Wilma, Betty), 而 other() 函 数 返 回 只 有 一 个 元 素 的 列 表 (Dino), 那么 LIST 看 起 来 就 象 下 面 一 样 :(Fred,Barney,Wilma,Betty,Dino)sort 返 回 的 LIST:(Barney,Betty,Dino,Fred,Wilma)一 些 操 作 符 产 生 列 表 ( 如 keys), 而 一 些 操 作 符 使 用 列 表 ( 如 print), 还 有 其 它 一 些 操作 符 将 列 表 串 进 其 它 的 列 表 ( 如 sort)。 最 后 的 这 类 操 作 符 可 以 认 为 是 筛 选 器 。 同 shell 不一 样 , 数 据 流 是 从 右 到 左 , 因 为 列 表 操 作 符 从 右 开 始 操 作 参 数 , 你 可 以 在 一 行 中 堆 叠 几 个 列表 操 作 符 :print reverse sort map {lc} keys %hash;这 行 代 码 获 取 %hash 的 关 键 字 并 将 它 们 返 回 给 map 函 数 ,map 函 数 使 用 lc 将 所 有的 关 键 字 转 换 成 小 写 , 并 将 处 理 后 的 结 果 传 给 sort 函 数 进 行 排 序 , 然 后 再 传 给 reverse函 数 , reverse 函 数 将 列 表 元 素 颠 倒 顺 序 后 , 传 给 print 函 数 打 印 出 来 。正 如 你 看 到 的 一 样 , 使 用 <strong>Perl</strong> 描 述 比 使 用 英 语 要 简 单 的 多 。在 列 表 处 理 方 面 还 有 很 多 方 法 可 以 写 出 很 多 更 自 然 的 代 码 。 在 这 里 我 们 无 法 列 举 所 有 方 法 。但 是 作 为 一 个 例 子 , 让 我 们 回 到 正 则 表 达 式 , 我 们 曾 经 谈 到 在 标 量 中 使 用 一 个 模 式 来 看 是 否匹 配 。 但 是 如 果 你 在 一 个 列 表 环 境 中 使 用 模 式 , 它 将 做 一 些 其 它 的 事 情 : 它 将 获 得 所 有 的 反引 用 作 为 一 个 列 表 。 假 设 你 在 一 个 日 志 文 件 或 邮 箱 中 搜 索 。 并 且 希 望 分 析 一 些 包 含 象“12:59:59 am” 这 样 形 式 时 间 的 字 符 串 , 你 可 以 使 用 下 面 的 写 法 :($hour, $min, $sec, $ampm) = /(\ed+):(\ed+):(\ed+) *(\ew+)/;这 是 一 种 同 时 设 置 多 个 变 量 的 简 便 方 法 , 但 是 你 也 可 以 简 单 的 写 :@hmsa = /(\ed+):(\ed+):(\ed+) *(\ew+)/;这 里 将 所 有 四 个 值 放 进 了 一 个 数 组 。 奇 秒 的 , 通 过 从 <strong>Perl</strong> 表 达 式 能 力 中 分 离 正 则 表 达 式 的能 力 , 列 表 环 境 增 加 了 语 言 的 能 力 。 有 些 人 可 能 不 同 意 , 但 是 <strong>Perl</strong> 除 了 是 一 种 斜 交 语 言 外 ,它 还 的 确 是 一 种 正 交 语 言 .1.9 你 不 知 道 但 不 伤 害 你 的 东 西 ( 很 多 )最 后 , 请 允 许 我 们 再 次 回 顾 <strong>Perl</strong> 是 一 种 自 然 语 言 的 概 念 。 自 然 语 言 允 许 使 用 者 有 不 同 的 技巧 级 别 , 使 用 语 言 不 同 的 子 集 , 并 且 边 学 边 用 。 通 常 在 知 道 语 言 的 全 部 内 容 之 前 , 他 们 就 可47


使 用 非 ASCII 字 符 集 。 你 必 须 负 责 保 证 你 的 程 序 的 语 意 和 你 选 择 的 国 家 字 符 集 是 一 致 的 。比 如 说 , 如 果 你 正 在 将 16 位 的 编 码 用 于 亚 洲 国 家 字 符 集 , 那 么 你 要 记 住 <strong>Perl</strong> 会 认 为 你的 每 个 字 符 是 两 个 字 节 , 而 不 是 一 个 字 符 。象 我 们 在 第 十 五 章 ,Unicode, 里 面 描 述 的 那 样 , 我 们 最 近 为 <strong>Perl</strong> 增 加 了 Unicode 的 支持 ( 注 : 尽 管 我 们 对 能 支 持 Unicode 感 到 非 常 激 动 , 我 们 的 大 部 分 例 子 仍 然 是 ASCII 编码 的 , 因 为 不 是 每 个 人 都 有 一 个 好 的 Unicode 编 辑 器 。)。 这 个 支 持 是 遍 及 这 门 语 言 全 身的 : 你 可 以 用 Unicode 字 符 做 标 识 符 ( 比 如 说 变 量 名 ), 就 象 在 文 本 串 里 使 用 那 样 。 当 你使 用 Unicode 的 时 候 , 用 不 着 担 心 一 个 字 符 是 由 几 个 位 或 者 几 个 字 节 组 成 的 。<strong>Perl</strong> 只 是假 装 所 有 Unicode 字 符 都 是 相 同 大 小 ( 也 就 是 说 , 尺 寸 为 1), 甚 至 任 意 字 符 在 内 部 都是 由 多 个 字 节 表 示 的 。<strong>Perl</strong> 通 常 在 内 部 把 Unicode 表 示 为 UTF-8 —— 一 种 变 长 编 码 方式 。( 比 如 , 一 个 Unicode 的 笑 脸 字 符 ,U-263A, 在 内 部 会 表 现 为 一 个 三 字 符 序 列 。)如 果 让 我 们 把 与 物 理 元 素 的 类 比 进 行 得 更 深 入 一 些 , 字 符 是 基 本 粒 子 , 就 象 不 同 元 素 里 面 独立 的 原 子 一 样 。 的 确 , 字 符 是 由 位 和 字 节 这 些 更 小 的 粒 子 组 成 的 , 但 是 如 果 你 把 一 个 字 符 分割 开 ( 当 然 是 在 一 个 字 符 加 速 器 里 ), 这 些 独 立 的 位 和 字 节 就 会 完 全 失 去 那 些 赖 以 区 分 字 符的 化 学 属 性 。 就 象 中 子 是 铀 -238 原 子 的 实 现 细 节 一 样 , 字 节 也 是 U-236A 字 符 的 实 现 细节 。所 以 当 我 们 提 及 字 符 时 , 我 们 会 小 心 地 用 “ 字 符 ” 这 个 字 眼 , 而 提 及 字 节 时 , 我 们 会 用 “ 字 节 ”。不 过 我 们 不 是 想 吓 唬 你 —— 你 仍 然 可 以 很 容 易 的 做 那 些 老 风 格 的 字 节 处 理 。 你 要 干 的 事 只 是告 诉 <strong>Perl</strong> 你 依 然 想 把 字 节 当 作 字 符 处 理 。 你 可 以 用 use bytes 用 法 来 实 现 这 个 目 的 ( 参阅 第 三 十 一 章 , 实 用 模 块 )。 不 过 即 使 你 不 这 样 做 ,<strong>Perl</strong> 还 是 能 很 不 错 地 在 你 需 要 的 时 候把 小 字 符 保 存 在 8 个 位 里 面 。所 以 不 要 担 心 这 些 小 的 方 面 。 让 我 们 向 更 大 和 更 好 的 东 西 前 进 。2.2 分 子<strong>Perl</strong> 是 自 由 格 式 语 言 , 不 过 也 不 意 味 着 <strong>Perl</strong> 就 完 全 是 自 由 格 式 。 象 计 算 机 工 作 者 常 说 的那 样 , 自 由 格 式 的 语 言 就 是 说 你 可 以 在 你 喜 欢 的 任 何 地 方 放 空 白 , 制 表 符 和 新 行 等 字 符 ( 除了 不 能 放 的 地 方 以 外 )。有 一 个 显 然 不 能 放 空 白 的 地 方 就 是 在 记 号 里 。 一 个 记 号 就 是 有 独 立 含 义 的 一 个 字 符 序 列 , 非常 象 自 然 语 言 中 的 单 字 。 不 过 和 典 型 的 字 不 一 样 的 地 方 是 记 号 可 能 含 有 除 了 字 符 以 外 的 其 他字 符 —— 只 要 它 们 连 在 一 起 形 成 独 立 的 含 义 。( 从 这 个 意 义 来 看 , 记 号 更 象 分 子 , 因 为 分 子不 一 定 要 由 一 种 特 定 的 原 子 组 成 。) 例 如 , 数 字 和 数 学 操 作 符 都 是 记 号 。 一 个 标 识 符 是 由 字母 或 下 划 线 开 头 的 , 只 包 括 字 母 , 数 字 , 和 下 划 线 。 记 号 里 面 不 能 有 空 白 , 因 为 那 样 会 把 它分 成 两 个 记 号 , 就 象 在 英 文 里 面 空 白 把 单 词 分 成 两 个 单 词 一 样 。( 注 : 聪 明 的 读 者 可 能 会 说49


在 文 本 串 里 可 以 包 含 空 白 字 符 。 但 是 字 符 串 只 有 在 两 边 都 用 双 引 号 括 起 来 才 能 保 证 空 白 不 会漏 出 去 。)尽 管 允 许 空 白 出 现 在 任 何 两 个 记 号 中 间 , 只 有 在 两 个 记 号 放 在 一 起 会 被 误 认 为 是 一 个 记 号 的时 候 才 要 求 一 定 要 在 中 间 放 空 白 。 用 于 这 个 目 的 的 时 候 所 有 空 白 都 是 一 样 的 。 新 行 只 有 在 引号 括 起 的 字 串 , 格 式 ( 串 ) 和 一 些 面 向 行 的 格 式 的 引 用 中 才 和 空 白 或 ( 水 平 ) 制 表 符 (tab)不 一 样 。 具 体 来 说 , 换 行 符 不 象 某 些 语 言 一 样 ( 比 如 FORTRAN 或 Python) 是 语 句 的 结束 符 。<strong>Perl</strong> 里 的 语 句 是 用 分 号 结 束 的 , 就 象 在 C 里 面 和 C 的 许 多 其 他 变 种 一 样 。Unicode 的 空 白 允 许 出 现 在 Unicode 的 <strong>Perl</strong> 程 序 里 面 , 不 过 你 要 小 心 处 理 。 如 果 你 应用 了 特 殊 的 Unicode 段 落 和 分 行 符 , 请 注 意 <strong>Perl</strong> 可 能 会 和 你 的 文 本 编 辑 器 计 算 出 不 同 的行 数 来 , 因 此 错 误 信 息 可 能 会 变 得 更 难 处 理 。 最 好 是 依 然 使 用 老 风 格 的 新 行 符 。记 号 的 识 别 是 贪 多 为 胜 的 ; 如 果 在 某 一 点 让 <strong>Perl</strong> 的 分 析 器 在 一 长 一 短 两 个 记 号 之 间 做 选择 , 她 会 选 择 长 的 那 个 。 如 果 你 的 意 思 是 两 个 记 号 , 那 就 在 它 们 中 间 插 入 一 些 空 白 。( 为 了增 加 可 读 性 我 们 总 是 在 大 多 数 操 作 符 周 围 放 上 额 外 的 空 白 。)注 释 用 # 字 符 标 记 , 从 这 个 字 符 开 始 到 行 尾 。 一 个 注 释 会 被 当 作 分 隔 记 号 的 空 白 。<strong>Perl</strong> 语言 对 你 放 到 注 释 里 面 的 东 西 不 附 加 任 何 特 殊 含 义 。( 注 : 实 际 上 , 这 里 撒 了 个 小 谎 , 不 过 无伤 大 雅 。<strong>Perl</strong> 的 分 析 器 的 确 在 #! 开 头 的 行 里 查 找 命 令 行 开 关 ( 参 阅 第 十 九 章 , 命 令 行 接口 )。 它 还 可 以 分 析 各 种 预 处 理 器 产 生 的 各 种 行 数 标 识 ( 参 阅 第 二 十 四 章 , 普 通 实 践 , 的 “ 在其 他 语 言 里 生 成 <strong>Perl</strong>” 节 )。另 外 一 个 例 外 是 , 语 句 里 任 何 地 方 如 果 存 在 以 = 开 头 的 行 都 是 合 法 的 ,<strong>Perl</strong> 将 忽 略 从 这一 行 开 始 直 到 下 一 个 由 =cut 开 头 的 行 。 被 忽 略 的 文 本 将 被 认 为 是 pod, 或 “plain olddocumentation( 简 单 的 旧 文 档 )”。<strong>Perl</strong> 的 发 布 版 本 里 面 有 一 个 程 序 可 以 从 <strong>Perl</strong> 模 块里 抽 取 pod 注 释 输 出 到 一 个 平 面 文 本 文 件 , 手 册 页 ,LATEX,HTML, 或 者 ( 将 来 某 一 天 )XML 文 档 里 。<strong>Perl</strong> 分 析 器 会 从 <strong>Perl</strong> 模 块 里 面 抽 取 <strong>Perl</strong> 代 码 并 且 忽 略 pod。 因 此 你 可 以把 这 个 方 法 当 作 一 种 可 用 的 多 行 注 释 方 法 。 你 也 完 全 可 以 认 为 它 是 一 种 让 人 头 疼 的 东 西 , 不过 这 样 做 可 以 使 <strong>Perl</strong> 模 块 不 会 丢 失 它 的 文 档 。 参 阅 第 二 十 六 章 , 简 单 旧 文 档 , 那 里 有 关 于pod 的 细 节 , 包 括 关 于 如 何 有 效 的 在 <strong>Perl</strong> 里 面 使 用 多 行 注 释 的 描 述 。不 过 可 不 要 小 看 普 通 的 注 释 字 符 。 用 一 列 整 齐 的 # 注 释 的 多 行 文 本 可 以 有 舒 服 的 视 觉 效果 。 它 马 上 就 告 诉 你 的 眼 睛 : “ 这 些 不 是 代 码 。” 你 可 能 注 意 到 即 使 象 C 这 样 有 多 行 注 释机 制 的 语 言 里 , 人 们 都 总 是 在 他 们 注 释 的 左 边 放 上 一 行 * 字 符 。 通 常 外 观 要 比 仅 仅 出 现 更重 要 。在 <strong>Perl</strong> 里 , 就 象 在 化 学 和 语 言 里 一 样 , 你 可 以 从 小 的 东 西 开 始 建 造 越 来 越 大 的 结 构 。 我 们已 经 提 到 过 语 句 了 ; 它 只 是 组 成 一 条 命 令 , 就 是 说 , 一 个 祈 使 句 。 你 可 以 用 花 括 弧 把 一 系 列语 句 组 成 块 。 小 块 可 以 组 装 成 大 块 。 若 干 块 组 成 子 函 数 , 然 后 子 函 数 可 以 组 成 模 块 , 模 块 可50


以 合 并 到 程 序 里 面 去 。 不 过 我 们 这 里 已 经 走 得 太 远 了 —— 那 些 是 未 来 若 干 章 的 内 容 。 先 让我 们 从 字 符 里 面 组 建 更 多 的 记 号 开 始 吧 。2.3 内 置 的 数 据 类 型在 我 们 开 始 讲 述 各 种 各 样 的 用 字 符 组 建 的 记 号 之 前 , 我 们 先 要 做 一 些 抽 象 。 具 体 说 来 , 就 是我 们 需 要 三 种 数 据 类 型 。计 算 机 语 言 因 它 们 支 持 的 数 据 类 型 的 多 寡 和 类 别 而 不 同 。 一 些 常 用 的 语 言 为 类 似 的 数 值 提 供了 许 多 让 人 易 混 的 数 据 类 型 , <strong>Perl</strong> 不 一 样 , 它 只 提 供 了 少 数 几 种 内 建 的 数 据 类 型 。 让 我 们看 看 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 等 等 类 型 。 这 些 都 是 某 种 类 型 的 整 型 ! 然 后 还 有 浮 点 数 , 指 针和 字 符 串 等 。所 有 的 这 些 复 杂 的 类 型 都 只 对 应 <strong>Perl</strong> 里 面 的 一 种 类 型 : 标 量 ( 你 通 常 需 要 的 只 是 <strong>Perl</strong> 的简 单 数 据 类 型 , 如 果 不 是 的 话 , 你 可 以 利 用 <strong>Perl</strong> 的 面 向 对 象 的 特 性 自 由 地 定 义 动 态 类 型—— 参 阅 第 十 二 章 , 对 象 。) <strong>Perl</strong> 的 三 种 基 本 数 据 类 型 是 : 标 量 , 标 量 数 组 和 标 量 散 列(hash)( 也 被 称 做 联 合 数 组 )。 有 些 人 喜 欢 把 这 些 称 做 数 据 结 构 , 而 不 是 类 型 。 那 也 行 。标 量 是 建 造 更 复 杂 类 型 的 基 本 类 型 。 一 个 标 量 存 储 单 一 的 简 单 值 —— 通 常 是 一 个 字 串 或 者 一个 数 字 。 这 种 简 单 类 型 的 元 素 可 以 组 成 两 种 聚 集 类 型 的 任 何 一 种 。 一 个 数 组 是 是 一 个 标 量 的有 序 排 列 , 你 可 以 通 过 一 个 整 型 脚 标 ( 或 者 索 引 ) 访 问 。<strong>Perl</strong> 里 的 所 有 索 引 都 从 0 开 始 。不 过 , 和 许 多 编 程 语 言 不 一 样 的 是 ,<strong>Perl</strong> 认 为 负 数 脚 标 也 是 合 法 的 : 负 数 脚 标 是 从 后 向 前记 数 你 的 数 组 。( 这 一 点 对 许 多 子 字 串 和 子 数 组 操 作 符 以 及 正 则 表 达 式 都 适 用 。) 另 一 方 面 ,一 个 散 列 (hash) 数 组 是 一 个 无 序 的 键 字 / 数 值 对 , 你 可 以 用 字 串 ( 就 是 键 字 ) 当 作 脚 标 来访 问 对 应 一 个 键 字 的 标 量 ( 就 是 数 值 )。 变 量 总 是 这 三 种 类 型 之 一 。( 除 变 量 外 , 还 有 一 些其 他 的 <strong>Perl</strong> 抽 象 你 也 可 以 认 为 是 数 据 类 型 , 比 如 文 件 句 柄 , 目 录 句 柄 , 格 式 串 , 子 过 程 ( 子函 数 ), 符 号 表 和 符 号 表 入 口 等 。)抽 象 是 好 东 西 , 我 们 一 边 学 习 一 边 会 收 集 到 更 多 的 抽 象 , 不 过 从 某 个 角 度 来 看 , 抽 象 也 是 没什 么 用 的 东 西 。 你 直 接 用 抽 象 不 能 做 任 何 事 情 。 因 此 计 算 机 语 言 就 要 有 语 法 。 我 们 要 告 诉 你各 种 各 样 的 语 法 术 语 , 这 样 你 就 可 以 把 抽 象 数 据 组 成 表 达 式 。 在 我 们 谈 到 这 些 语 法 单 元 的 时候 , 我 们 喜 欢 使 用 技 术 术 语 “ 项 ” 这 个 词 。( 哦 , 这 里 的 措 辞 可 能 有 点 模 糊 。 不 过 只 要 记 住 数学 老 师 在 讲 数 学 等 式 里 用 到 的 “ 项 ” 这 个 词 , 你 就 不 会 犯 大 错 。)就 象 在 数 学 等 式 里 的 项 一 样 ,<strong>Perl</strong> 里 的 大 多 数 项 的 目 的 也 是 为 加 号 或 乘 号 等 操 作 符 生 成 数值 。 不 过 , 和 数 学 等 式 不 一 样 的 是 ,<strong>Perl</strong> 对 它 计 算 的 数 值 要 做 些 处 理 , 而 不 仅 仅 是 拿 着 一支 笔 在 手 里 思 考 等 式 两 边 是 否 相 等 。 对 一 个 数 值 最 常 做 的 事 情 就 是 把 它 存 放 在 某 个 地 方 :51


$x = $y;上 面 是 赋 值 操 作 符 ( 不 是 数 字 相 等 操 作 符 , 这 个 操 作 符 在 <strong>Perl</strong> 里 叫 ==) 的 例 子 。 这 条 语句 把 $y 的 值 赋 予 $x。 请 注 意 我 们 不 是 用 项 $x 做 为 其 数 值 , 而 是 作 为 其 位 置 ($x 的 原先 的 值 被 赋 值 语 句 删 除 。) 我 们 把 $x 称 为 一 个 lvalue( 左 值 ), 意 思 是 我 们 可 以 用 在 赋值 语 句 左 边 的 存 储 位 置 。 我 们 把 $y 称 为 一 个 rvalue( 右 值 ), 因 为 它 是 用 在 右 边 的 。还 有 第 三 种 数 值 , 叫 临 时 值 , 如 果 你 想 知 道 <strong>Perl</strong> 是 如 何 处 理 你 的 左 值 和 右 值 的 , 你 就 得 理解 这 个 临 时 值 。 如 果 我 们 做 一 些 实 际 的 数 学 运 算 并 说 :$x = $y + 1;<strong>Perl</strong> 拿 出 右 值 $y 并 且 给 它 加 上 右 值 1, 生 成 一 个 临 时 变 量 , 最 后 临 时 变 量 赋 予 左 值 $x。如 果 我 们 告 诉 你 <strong>Perl</strong> 把 这 些 临 时 变 量 存 储 在 一 个 叫 堆 栈 的 内 部 结 构 里 面 ,( 注 : 堆 栈 就 象餐 馆 小 卖 部 里 用 发 条 上 紧 的 自 动 售 货 机 , 你 可 以 在 栈 顶 压 入 盘 子 , 或 者 你 也 可 以 把 他 们 弹 出来 。) 你 可 能 就 能 想 象 内 部 发 生 什 么 事 。 一 个 表 达 式 的 项 ( 我 们 在 这 一 章 里 要 谈 的 内 容 ) 会向 堆 栈 里 压 入 数 据 , 当 表 达 式 里 的 操 作 符 ( 我 们 在 下 一 章 讨 论 ) 试 图 把 它 们 从 堆 栈 里 面 弹 出时 , 可 能 会 在 堆 栈 里 面 保 留 另 外 一 个 临 时 结 果 给 下 一 个 操 作 符 处 理 。 当 表 达 式 处 理 完 成 时 ,压 栈 和 弹 出 相 互 平 衡 , 堆 栈 完 全 清 空 ( 或 者 和 开 始 的 时 候 一 样 )。 后 面 还 有 更 多 关 于 临 时 变量 的 介 绍 。有 些 项 只 能 做 右 值 , 比 如 上 面 的 1, 而 其 他 的 既 可 以 做 左 值 也 可 以 做 右 值 。 尤 其 是 象 上 面的 赋 值 语 句 演 示 的 那 样 , 一 个 变 量 就 可 以 同 时 做 左 值 和 右 值 。 那 是 我 们 下 一 章 的 内 容 。2.4 变 量不 用 说 , 有 三 种 变 量 类 型 和 我 们 前 面 提 到 的 三 种 抽 象 数 据 类 型 对 应 . 每 种 类 型 都 由 我 们 称 之为 趣 味 字 符 ( funny character)( 注 : 这 是 计 算 机 科 学 的 另 一 个 技 术 术 语 。( 如 果 以 前它 不 是 , 那 现 在 它 就 是 了 ) 做 前 缀 . 标 量 变 量 的 开 头 总 是 $, 甚 至 连 引 用 一 个 数 组 或 者 散列 中 的 一 个 标 量 也 如 此 . 它 有 点 象 英 文 里 的 单 词 "the". 所 以 , 我 们 有 下 表 :构 造$days$days[28]含 义简 单 标 量 值 $days数 组 @days 的 第 二 十 九 个 元 素$days{'Feb'} 散 列 %days 的 “Feb” 值请 注 意 我 们 可 以 对 $days,@days, 和 %days 使 用 相 同 的 名 字 而 不 用 担 心 <strong>Perl</strong> 会 混淆 它 们 。52


还 有 其 他 一 些 爱 好 者 使 用 的 标 量 术 语 , 在 一 些 我 们 一 时 半 会 还 接 触 不 到 的 地 方 非 常 有 用 。 他们 看 起 来 象 :构 造${days}$Dog::days$#days$days->[28]$days[0][2]$days{200}{'Feb'}$days{2000,'Feb'}含 义和 $days 一 样 , 不 过 在 字 母 数 字 前 面 不 易 混 淆在 Dog 包 里 面 的 不 同 的 $days 变 量数 组 @days 的 最 后 一 个 索 引$days 一 个 引 用 指 向 的 数 组 的 第 二 十 九 个 元 素多 维 数 组多 维 散 列多 维 散 列 枚 举整 个 数 组 ( 或 者 数 组 和 散 列 的 片 段 ) 带 趣 味 字 符 @ 命 名 , 很 象 单 词 “ 这 些 ” 或 “ 那 些 ” 的 作 用 :构 造@days@days[3,4,5]@days[3..5]@days{'Jan','Feb'}含 义包 含 ($days[0],$days[1],...$days[n]) 的 数 组包 含 ($days[3],$days[4],$days[5]) 数 组 片 段 的 数 组包 含 ($days[3],$days[4],$days[5]) 数 组 片 段 的 数 组包 含 ($days{'Jan'},$days{'Feb'}) 片 段 的 散 列每 个 散 列 都 用 % 命 名 :构 造含 义%days (Jan=>31,Feb=>$leap?29:28,...)任 何 这 些 构 造 都 可 以 作 为 左 值 使 用 , 声 明 一 个 你 可 以 赋 值 的 位 置 。 对 于 数 组 , 散 列 或 者 数 组和 散 列 的 片 段 , 这 个 左 值 提 供 了 可 以 赋 值 的 多 个 位 置 , 因 此 你 可 以 一 次 给 所 有 这 些 位 置 赋 值 :@days = 1..7;2.5 名 字我 们 已 经 谈 过 在 变 量 里 面 保 存 数 值 了 , 但 是 变 量 本 身 ( 它 们 的 名 字 和 它 们 相 关 的 定 义 ) 也 需要 存 储 在 某 个 位 置 。 在 抽 象 ( 的 范 畴 里 ), 这 些 地 方 被 称 为 名 字 空 间 。<strong>Perl</strong> 提 供 两 种 类 型的 名 字 空 间 , 通 常 被 称 为 符 号 表 和 词 法 范 围 ( 注 : 当 我 们 谈 论 <strong>Perl</strong> 详 细 实 现 时 , 我 们 还 把它 们 称 做 包 (packages) 和 垫 (pads), 不 过 那 些 更 长 的 词 是 纯 正 工 业 术 语 , 所 以 我 们 只能 用 它 们 , 抱 歉 )。 你 可 以 拥 有 任 意 数 量 的 符 号 表 和 词 法 范 围 , 但 是 你 定 义 的 任 何 一 个 名 字都 将 存 储 在 其 中 的 某 一 个 里 面 。 我 们 将 随 着 介 绍 的 深 入 探 讨 这 两 种 名 字 空 间 。 目 前 我 们 只 能说 符 号 表 是 全 局 的 散 列 , 用 于 存 储 存 放 全 局 变 量 的 符 号 表 的 入 口 ( 包 括 用 于 其 他 符 号 表 的 散53


列 )。 相 比 之 下 , 词 法 范 围 只 是 未 命 名 的 中 间 结 果 暂 存 器 , 不 会 存 在 于 任 何 符 号 表 , 只 是 附着 在 你 的 程 序 的 一 块 代 码 后 面 。 它 们 ( 词 法 范 围 ) 包 含 只 能 被 该 块 所 见 的 变 量 。( 那 也 是 我们 说 “ 范 围 ” 的 含 义 )。“ 词 法 ” 两 个 字 只 是 说 :“ 它 们 必 须 以 文 本 方 式 处 理 ”, 可 不 是 通 常 字 典赋 予 它 们 的 含 义 。 可 别 批 评 我 们 。在 任 何 名 字 空 间 里 ( 不 管 是 全 局 还 是 局 部 ), 每 个 变 量 类 型 都 有 自 己 的 由 趣 味 字 符 确 定 的 子名 字 空 间 。 你 可 以 把 同 一 个 名 字 用 于 一 个 标 量 变 量 , 一 个 数 组 或 者 一 个 散 列 ( 或 者 , 说 到 这份 上 , 可 以 是 一 个 文 件 句 柄 , 一 个 子 过 程 名 , 一 个 标 签 , 甚 至 你 的 宠 物 骆 驼 ) 也 不 用 担 心 会混 淆 。 这 就 意 味 着 $foo 和 @foo 是 两 个 不 同 的 变 量 。 加 上 以 前 的 规 则 , 这 还 意 味 着 $foo是 @foo 的 一 个 元 素 , 它 和 标 量 变 量 $foot 完 全 没 有 关 系 。 这 些 看 起 来 有 点 怪 异 , 不 过也 没 啥 , 因 为 它 就 是 怪 异 ( 译 注 : 我 是 流 氓 我 怕 谁 ?)。子 过 程 可 以 用 一 个 & 开 头 命 名 , 不 过 调 用 子 过 程 的 时 候 这 个 趣 味 字 符 是 可 选 的 。 子 过 程 通常 不 认 为 是 左 值 , 不 过 最 近 版 本 的 <strong>Perl</strong> 允 许 你 从 一 个 子 过 程 返 回 一 个 左 值 并 且 赋 予 该 子 过程 , 这 样 看 起 来 可 能 就 象 你 在 给 那 个 子 过 程 赋 值 。有 时 候 你 想 命 名 一 个 “ 所 有 叫 foo 的 东 西 ”, 而 不 管 它 的 趣 味 字 符 是 什 么 。 因 此 符 号 表 入 口可 以 用 一 个 前 缀 的 * 命 名 , 这 里 的 星 号 (*) 代 表 所 有 其 他 趣 味 字 符 。 我 们 把 这 些 东 西 称为 类 型 团 (typeglobs), 而 且 它 们 有 好 几 种 用 途 。 它 们 也 可 以 用 做 左 值 。 给 一 个 类 型 团(typeglobs) 赋 值 就 是 <strong>Perl</strong> 从 一 个 符 号 表 向 另 外 一 个 输 入 符 号 的 实 现 。 我 们 后 面 还 会 有更 多 内 容 讲 这 些 。和 大 多 数 计 算 机 语 言 类 似 ,<strong>Perl</strong> 有 一 个 保 留 字 列 表 , 它 把 这 个 表 里 的 字 看 作 特 殊 关 键 字 。不 过 , 由 于 变 量 名 总 是 以 趣 味 字 符 开 头 , 实 际 上 保 留 字 并 不 和 变 量 名 冲 突 。 不 过 , 有 些 其 他类 型 的 名 字 不 带 趣 味 字 符 , 比 如 标 签 和 文 件 句 柄 。 即 使 这 样 , 你 也 用 不 着 担 心 与 保 留 字 冲 突 。因 为 绝 大 多 数 保 留 字 都 是 完 全 小 写 , 我 们 推 荐 你 使 用 带 大 写 字 符 的 名 字 做 标 签 和 文 件 句 柄 。例 如 , 如 果 你 说 open(LOG,logfile), 而 不 是 open(log,"logfile"), 你 就 不 会 让 <strong>Perl</strong> 误以 为 你 正 在 与 内 建 的 log 操 作 符 ( 它 处 理 对 数 运 算 , 不 是 树 干 ( 译 注 : 英 文 "log" 有 树 干的 含 义 。)) 交 谈 。 使 用 大 写 的 文 件 句 柄 也 改 善 了 可 读 性 ( 注 :<strong>Perl</strong> 的 一 个 设 计 原 则 是 :不 同 的 东 西 看 起 来 应 该 不 同 。 和 那 些 试 图 强 制 把 不 同 的 东 西 变 得 看 起 来 一 样 的 语 言 比 较 一下 , 看 看 可 读 性 的 好 坏 。) 并 且 防 止 你 和 我 们 今 后 可 能 会 增 加 的 保 留 字 的 冲 突 。 处 于 同 样 的考 虑 , 用 户 定 义 的 模 块 通 常 都 是 用 首 字 母 大 写 的 名 字 命 名 的 , 这 样 就 不 会 和 内 建 的 模 块 ( 叫用 法 (pragmas)) 冲 突 , 因 为 内 建 模 块 都 是 以 小 写 字 母 命 名 的 。 到 了 面 向 对 象 命 名 的 时候 , 你 就 会 发 现 类 的 名 称 同 样 都 是 首 字 母 大 写 的 。你 也 许 能 从 前 面 的 段 落 中 推 导 出 这 样 的 结 论 了 , 就 是 标 识 符 是 大 小 写 敏 感 的 ——FOO,Foo,和 foo 在 <strong>Perl</strong> 里 面 都 是 不 同 的 名 字 。 标 识 符 以 字 母 或 下 划 线 开 头 , 可 以 包 含 任 意 长 度 ( 这个 “ 任 意 ” 值 的 范 围 是 1 到 251 之 间 ) 个 字 母 , 数 字 和 下 划 线 。 这 里 包 括 Unicode 字 母54


和 数 字 。Unicode 象 形 文 字 也 包 括 在 内 , 不 过 我 们 可 不 建 议 你 使 用 它 们 , 除 非 你 能 够 阅 读它 们 。 参 阅 第 十 五 章 。严 格 说 来 , 跟 在 趣 味 字 符 后 面 的 名 字 一 定 是 标 识 符 。 他 们 可 以 以 数 字 开 头 , 这 时 候 后 面 只 能跟 着 更 多 数 字 , 比 如 $123。 如 果 一 个 名 字 开 头 不 是 字 母 , 数 字 或 下 划 线 , 这 样 的 名 字 ( 通常 ) 限 于 一 个 字 符 ( 比 如 $? 或 $$), 而 且 通 常 对 <strong>Perl</strong> 有 预 定 的 意 义 , 比 如 , 就 象 在Bourne shell 里 一 样 ,$$ 是 当 前 进 程 ID 而 $? 是 你 的 上 一 个 子 进 程 的 退 出 状 态 。到 了 版 本 5.6,<strong>Perl</strong> 还 有 一 套 用 于 内 部 变 量 的 可 扩 展 语 法 。 任 何 形 如 ${^NAME} 这 样的 变 量 都 是 保 留 为 <strong>Perl</strong> 使 用 的 。 所 有 这 些 非 标 识 符 名 字 都 被 强 制 存 放 于 主 符 号 表 。 参 阅 第二 十 八 章 , 特 殊 名 字 , 那 里 有 一 些 例 子 。我 们 容 易 认 为 名 字 和 标 识 符 是 一 样 的 东 西 , 不 过 , 当 我 们 说 名 字 的 时 候 , 通 常 是 指 其 全 称 ,也 就 是 说 , 表 明 自 己 位 于 哪 个 符 号 表 的 名 字 。 这 样 的 名 字 可 能 是 一 个 由 标 记 :: 分 隔 的 标 识符 的 序 列 :$Santa::Helper::Reindeer::Rudolph::nose就 象 一 个 路 径 名 里 面 的 目 录 和 文 件 一 样 :/Santa/Helper/Reindeer/Rudolph/nose在 <strong>Perl</strong> 的 那 个 表 示 法 里 面 , 所 有 前 导 的 标 识 符 都 是 嵌 套 的 符 号 表 名 字 , 并 且 最 后 一 个 标 识符 就 是 变 量 所 在 的 最 里 层 的 符 号 表 的 名 字 。 比 如 , 上 面 的 变 量 里 , 符 号 表 的 名 字 是Santa::Helper::Reindeer::Rudolph::, 而 位 于 此 符 号 表 的 实 际 变 量 是 $nose。( 当 然 ,该 变 量 的 值 是 “red”。)<strong>Perl</strong> 里 的 符 号 表 也 被 称 为 包 (package), 因 此 这 些 变 量 常 被 称 为 包 变 量 。 包 变 量 名 义 上是 其 所 属 包 的 私 有 成 员 , 但 实 际 上 是 全 局 的 , 因 为 包 本 身 就 是 全 局 的 。 也 就 是 说 , 任 何 人 都可 以 命 名 包 以 获 取 该 变 量 ; 但 就 是 不 容 易 碰 巧 做 到 这 些 。 比 如 , 任 何 提 到 $Dog::bert 的程 序 都 是 获 取 位 于 Dog:: 包 的 变 量 $bert。 但 它 是 与 $Cat::bert 完 全 不 同 的 变 量 。 参阅 第 十 章 , 包 。附 着 在 词 法 范 围 上 的 变 量 不 属 于 任 何 包 , 因 此 词 法 范 围 变 量 名 字 可 能 不 包 含 :: 序 列 。( 词法 范 围 变 量 都 是 用 my 定 义 式 定 义 的 。)2.5.1 名 字 查 找那 问 题 就 来 了 , 名 字 里 有 什 么 ? 如 果 你 光 是 说 $bert,<strong>Perl</strong> 是 怎 样 了 解 你 的 意 思 的 ? 问 得好 。 下 面 是 在 一 定 环 境 里 <strong>Perl</strong> 分 析 器 为 了 理 解 一 个 非 全 称 的 名 字 时 用 到 的 规 则 :55


1。 首 先 ,<strong>Perl</strong> 预 先 在 最 先 结 束 的 块 里 面 查 找 , 看 看 该 变 量 是 否 有 用 my( 或 则 our) 定义 在 该 代 码 块 里 ( 参 考 那 些 第 二 十 九 章 的 内 容 和 第 四 章 , 语 句 和 声 明 和 , 里 面 的 “ 范 围 声 明 ”节 )。 如 果 存 在 my 定 义 , 那 么 该 变 量 是 词 法 范 围 内 的 而 不 存 在 于 任 何 包 里 —— 它 只 存 在于 那 个 词 法 范 围 ( 也 就 是 在 该 代 码 块 的 暂 时 缓 存 器 里 )。 因 为 词 法 范 围 是 非 命 名 的 , 在 那 块程 序 之 外 的 任 何 人 甚 至 都 看 不 到 你 的 变 量 。( 注 : 如 果 你 用 的 是 our 定 义 而 非 my, 这 样只 是 给 一 个 包 变 量 定 义 了 一 个 词 法 范 围 的 别 名 ( 外 号 ), 而 不 象 my 定 义 里 面 真 正 地 定 义了 一 个 词 法 范 围 变 量 。 代 码 外 部 仍 然 可 以 通 过 变 量 的 包 获 取 其 值 , 但 除 此 以 外 ,our 定 义和 my 定 义 的 特 点 是 一 样 的 。 如 果 你 想 在 和 use strict 一 起 使 用 ( 参 阅 第 三 十 一 章 里 的strict progma ), 限 制 自 己 的 全 局 变 量 的 使 用 时 很 有 用 。 不 过 如 果 你 不 需 要 全 局 变 量 时你 应 该 优 先 使 用 my。)2。 如 果 上 面 过 程 失 败 ,<strong>Perl</strong> 尝 试 在 包 围 该 代 码 段 的 块 里 查 找 , 仍 然 是 在 这 个 更 大 的 代 码 块里 查 找 词 法 范 围 的 变 量 。 同 样 , 如 果 <strong>Perl</strong> 找 到 一 个 , 那 么 就 象 我 们 刚 刚 在 第 一 步 里 说 到 的变 量 一 样 , 该 变 量 只 属 于 从 其 定 义 开 始 到 其 定 义 块 结 束 为 止 的 词 法 范 围 —— 包 括 任 何 嵌 套 的代 码 块 。 如 果 <strong>Perl</strong> 没 有 发 现 定 义 , 那 么 它 将 重 复 第 二 步 直 到 用 光 所 有 上 层 闭 合 块 。3。 当 <strong>Perl</strong> 用 光 上 层 闭 合 块 后 , 它 检 查 整 个 编 辑 单 元 , 把 它 当 作 代 码 块 寻 找 声 明 ( 一 个 编辑 单 元 就 是 整 个 当 前 文 件 , 或 者 一 个 被 eval STRING 操 作 符 编 译 过 的 当 前 字 串 。) 如 果编 辑 单 元 是 一 个 文 件 , 那 就 是 最 大 的 词 法 范 围 , 这 样 <strong>Perl</strong> 将 不 再 查 找 词 法 范 围 变 量 , 于 是进 入 第 四 步 。 不 过 , 如 果 编 辑 单 元 是 一 个 字 串 , 那 事 情 就 有 趣 了 。 一 个 当 作 运 行 时 的 <strong>Perl</strong> 代码 编 译 的 字 串 会 假 装 它 是 在 一 个 eval STRING 运 行 着 的 词 法 范 围 里 面 的 一 个 块 , 即 使 其实 际 词 法 范 围 只 是 包 含 代 码 的 字 串 而 并 非 任 何 真 实 的 花 括 弧 也 如 此 。 所 以 如 果 <strong>Perl</strong> 没 有 在字 串 的 词 法 范 围 找 到 变 量 , 那 么 我 们 假 装 eval STRING 是 一 个 块 并 且 回 到 第 2 步 , 这 回我 们 才 检 查 eval STRING 的 词 法 范 围 而 不 是 其 内 部 字 串 的 词 法 范 围 。4。 如 果 我 们 走 到 这 一 步 , 说 明 <strong>Perl</strong> 没 有 找 到 你 的 变 量 的 任 何 声 明 (my 或 our)。<strong>Perl</strong>现 在 放 弃 词 法 范 围 并 假 设 你 的 变 量 是 一 个 包 变 量 。 如 果 strict pragma 用 法 有 效 , 你 现 在会 收 到 一 个 错 误 , 除 非 该 变 量 是 一 个 <strong>Perl</strong> 预 定 义 的 变 量 或 者 已 经 输 入 到 当 前 包 里 面 。 这 是因 为 strict 用 法 不 允 许 使 用 非 全 称 的 全 局 名 字 。 不 过 , 我 们 还 是 没 有 完 成 词 法 范 围 的 处 理 。<strong>Perl</strong> 再 次 搜 索 词 法 范 围 , 就 象 在 第 1 步 到 第 3 步 里 一 样 , 不 过 这 次 它 找 的 是 package( 包 ) 声 明 而 不 是 变 量 声 明 。 如 果 它 找 到 这 样 的 包 声 明 , 那 它 就 知 道 找 到 的 这 些 代 码 是 为 有问 题 的 包 编 译 的 于 是 就 在 变 量 前 面 追 加 这 个 声 明 包 的 名 字 。5。 如 果 在 任 何 词 法 范 围 内 都 没 有 包 声 明 ,<strong>Perl</strong> 就 在 未 命 名 的 顶 层 包 里 面 查 找 , 这 个 包 正 好就 是 main—— 只 要 它 没 有 不 带 名 字 到 处 乱 跑 。 因 此 相 对 于 任 何 缺 失 的 声 明 ,$bert 和$::bert 的 含 义 相 同 , 也 和 $main:bert 相 同 。( 不 过 , 因 为 main 只 是 在 顶 层 未 命 名包 中 的 另 一 个 包 , 该 变 量 也 是 $::main::bert, 和 $main::main::bert,$::main::main::bert 等 等 。 这 些 可 以 看 作 没 用 的 特 性 。 参 考 第 10 章 里 的 “ 符 号 表 ”。)56


这 些 搜 索 规 则 里 面 还 有 几 个 不 太 明 显 的 暗 示 , 我 们 这 里 明 确 一 下 。1。 因 为 文 件 是 可 能 的 最 大 词 法 范 围 , 所 以 一 个 词 法 范 围 变 量 不 可 能 在 定 义 其 的 文 件 之 外 可见 。 文 件 范 围 不 嵌 套 。2。 不 管 多 大 的 <strong>Perl</strong> 都 编 译 成 至 少 一 个 词 法 范 围 和 一 个 包 范 围 。 这 个 必 须 的 词 法 范 围 当 然就 是 文 件 本 身 。 附 加 的 词 法 范 围 由 每 个 封 闭 的 块 提 供 。 所 有 <strong>Perl</strong> 代 码 同 样 编 译 进 一 个 包 里面 , 并 且 尽 管 声 明 在 哪 个 包 里 属 于 词 法 范 围 , 包 本 身 并 不 受 词 法 范 围 约 束 。 也 就 是 说 , 它 们是 全 局 的 。3。 因 此 可 能 而 在 许 多 词 法 范 围 内 查 找 一 个 非 全 称 变 量 , 但 只 是 在 一 个 包 范 围 内 , 不 管 是 哪个 当 前 有 效 的 包 ( 这 是 词 汇 定 义 的 )。4。 一 个 变 量 值 只 能 附 着 在 一 个 范 围 上 。 尽 管 在 你 的 程 序 的 任 何 地 方 都 至 少 有 两 个 不 同 的 范围 ( 词 法 和 包 ), 一 个 变 量 仍 然 只 能 存 在 于 这 些 范 围 之 一 。5。 因 此 一 个 非 全 长 变 量 名 可 以 被 分 析 为 唯 一 的 一 个 存 储 位 置 , 要 么 在 定 义 它 的 第 一 个 封 闭的 词 法 范 围 里 , 要 么 在 当 前 包 里 —— 但 不 是 同 时 在 两 个 范 围 里 。 只 要 解 析 了 存 储 位 置 , 那 么搜 索 就 马 上 停 止 , 并 且 如 果 搜 索 继 续 进 行 , 它 找 到 的 任 何 存 储 位 置 都 被 有 效 地 隐 藏 起 来 。6。 典 型 变 量 名 的 位 置 在 编 译 时 完 全 可 以 决 定 。尽 管 你 已 经 知 道 关 于 <strong>Perl</strong> 编 译 器 如 何 处 理 名 字 的 所 有 内 容 , 有 时 候 你 还 是 有 这 样 的 问 题 :在 编 译 时 你 并 不 知 道 你 想 要 的 名 字 是 什 么 。 有 时 候 你 希 望 间 接 地 命 名 一 些 东 西 ; 我 们 把 这 个问 题 叫 间 接 (indirection)。 因 此 <strong>Perl</strong> 提 供 了 一 个 机 制 : 你 总 是 可 以 用 一 个 表 达 式 块 代替 字 母 数 字 的 变 量 名 , 这 个 表 达 式 返 回 一 个 真 正 数 据 的 引 用 。 比 如 , 你 不 说 :$bert而 可 能 说 :${some_expression()}如 果 some_expression() 函 数 返 回 一 个 变 量 $bert 的 引 用 ( 甚 至 是 字 串 ,“bert”),它 都 会 象 第 一 行 里 的 $bert 一 样 。 另 一 方 面 , 如 果 这 个 函 数 返 回 $ernie 的 引 用 , 那 你 就会 得 到 这 个 变 量 。 这 里 显 示 的 是 间 接 的 最 常 用 ( 至 少 是 最 清 晰 ) 的 形 式 , 不 过 我 们 会 在 第 8章 , 引 用 , 里 介 绍 几 个 变 体 。2.6 标 量 值57


不 管 是 直 接 命 名 还 是 间 接 命 名 , 不 管 它 是 在 变 量 里 还 是 一 个 数 组 元 素 里 或 者 只 是 一 个 临 时值 , 一 个 变 量 总 是 包 含 单 一 值 。 这 个 值 可 以 是 一 个 数 字 , 一 个 字 串 或 者 是 另 一 片 数 据 的 引 用 。或 着 它 里 面 可 以 完 全 没 有 值 , 这 时 我 们 称 其 为 未 定 义 (undefined)。 尽 管 我 们 会 说 标 量 “ 包含 ” 着 一 个 数 字 或 者 字 串 , 标 量 本 身 是 无 类 型 的 : 你 用 不 着 把 标 量 定 义 为 整 型 或 浮 点 型 或 字符 串 或 者 其 他 的 什 么 东 西 。( 注 : 将 来 的 <strong>Perl</strong> 版 本 将 允 许 你 插 入 int,num, 和 str 类型 声 明 , 这 样 做 不 是 为 了 加 强 类 型 , 而 是 给 优 化 器 一 些 它 自 己 发 现 不 了 的 暗 示 。 通 常 , 这 个特 性 用 于 那 些 必 须 运 行 得 非 常 快 的 代 码 , 所 以 我 们 不 准 备 告 诉 你 如 何 使 用 。 可 选 的 类 型 同 样还 可 以 用 于 伪 散 列 的 机 制 , 在 这 种 情 况 下 , 它 们 可 以 表 现 得 象 大 多 数 强 类 型 语 言 里 的 类 型 一样 。 参 阅 第 八 章 获 取 更 多 信 息 。)<strong>Perl</strong> 把 字 串 当 作 一 个 字 符 序 列 保 存 , 对 长 度 和 内 容 没 有 任 何 限 制 。 用 我 们 人 类 的 话 来 说 ,你 用 不 着 事 先 判 断 你 的 字 串 会 有 多 长 , 而 且 字 串 里 可 以 有 任 何 字 符 , 包 括 空 (null) 字 节 。<strong>Perl</strong> 在 可 能 地 情 况 下 把 数 字 保 存 为 符 号 整 数 , 或 者 是 本 机 格 式 的 双 精 度 浮 点 数 。 浮 点 数 值是 有 限 精 度 的 。 你 一 定 要 记 住 这 条 , 因 为 象 (10/3==1/3*10) 这 样 的 比 较 会 莫 名 其 妙 的失 败 。<strong>Perl</strong> 根 据 需 要 在 各 种 子 类 型 之 间 做 转 换 , 所 以 你 可 以 把 一 个 数 字 当 作 字 串 或 者 反 过 来 ,<strong>Perl</strong>会 正 确 处 理 这 些 的 。 为 了 把 字 串 转 换 成 数 字 ,<strong>Perl</strong> 内 部 使 用 类 似 C 函 数 库 的 atof(3) 函数 。 在 把 数 字 转 换 成 字 串 的 时 候 , 它 在 大 多 数 机 器 上 做 相 当 于 带 有 格 式 “%.14g” 的sprintf(3) 处 理 。 象 把 foo 转 换 成 数 字 这 样 的 不 当 转 换 会 把 数 字 当 成 0; 如 果 你 打 开 警 告 ,上 面 这 样 做 会 触 发 警 告 , 否 则 没 有 任 何 信 息 。 参 阅 第 五 章 , 模 式 匹 配 , 看 看 如 何 判 断 一 个 字串 里 面 有 什 么 东 西 的 例 子 。尽 管 字 串 和 数 字 在 几 乎 所 有 场 合 都 可 以 互 换 , 引 用 却 有 些 不 同 。 引 用 是 强 类 型 的 , 不 可 转 换的 指 针 ; 内 建 了 引 用 计 数 和 析 构 调 用 。 也 就 是 说 , 你 可 以 用 它 们 创 建 复 杂 的 数 据 类 型 , 包 括用 户 定 义 对 象 。 但 尽 管 如 此 , 它 们 仍 然 是 标 量 , 因 为 不 管 一 个 数 据 结 构 有 多 复 杂 , 你 通 常 还是 希 望 把 它 当 作 一 个 值 看 待 。我 们 这 里 说 的 不 可 转 换 , 意 思 是 说 你 不 能 把 一 个 引 用 转 换 成 一 个 数 组 或 者 散 列 。 引 用 不 能 转换 成 其 他 指 针 类 型 。 不 过 , 如 果 你 拿 一 个 引 用 当 作 一 个 数 字 或 者 字 串 来 用 , 你 会 收 到 一 个 数字 或 者 字 串 值 , 我 们 保 证 这 个 值 保 持 引 用 的 唯 一 性 , 即 使 你 从 真 正 的 引 用 中 拷 贝 过 来 而 丢 失了 该 “ 引 用 ” 值 也 如 此 ( 唯 一 )。 你 可 以 比 较 这 样 的 数 值 或 者 抽 取 它 们 的 类 型 。 不 过 除 此 之 外你 对 这 种 类 型 干 不 了 什 么 , 因 为 你 没 法 把 数 字 或 字 串 转 换 回 引 用 。 通 常 , 着 不 是 个 问 题 , 因为 <strong>Perl</strong> 不 强 迫 你 使 用 指 针 运 算 —— 甚 至 都 不 允 许 。 参 阅 第 八 章 读 取 更 多 引 用 的 信 息 。2.6.1 数 字 文 本58


数 字 文 本 是 用 任 意 常 用 浮 点 或 整 数 格 式 声 明 的 :( 注 : 在 Unix 文 化 中 的 习 惯 格 式 。 如 果你 来 自 不 同 文 化 , 欢 迎 来 到 我 们 中 间 !)$x = 12345; # 整 数$x = 12345.67; # 浮 点$x = 6.02e23; # 科 学 记 数$x = 4_294_967_296; # 提 高 可 读 性 的 下 划 线$x = 03777; # 八 进 制$x = 0xffff; # 十 六 进 制$x = 0b1100_0000; # 二 进 制因 为 <strong>Perl</strong> 使 用 逗 号 作 为 数 组 分 隔 符 , 所 以 你 不 能 用 它 分 隔 千 位 或 者 更 大 位 。 不 过 <strong>Perl</strong> 允许 你 用 下 划 线 代 替 。 这 样 的 下 划 线 只 能 用 于 你 程 序 里 面 声 明 的 数 字 文 本 , 而 不 能 用 于 从 其 他地 方 读 取 的 用 做 数 字 的 字 串 。 类 似 地 , 十 六 进 制 前 面 的 0x, 二 进 制 前 面 的 0b, 八 进 制 的 0也 都 只 适 用 于 数 字 文 本 。 从 字 串 到 数 字 的 自 动 转 换 并 不 识 别 这 些 前 缀 , 你 必 须 用 oct 函 数做 显 式 转 换 ( 注 : 有 时 候 人 们 认 为 <strong>Perl</strong> 应 该 对 所 有 输 入 数 据 做 这 个 转 换 。 不 过 我 们 这 个 世界 里 面 有 太 多 带 有 前 导 零 的 十 进 制 数 会 让 <strong>Perl</strong> 做 这 种 自 动 转 换 。 比 如 ,O'Reilly &Associates 在 麻 省 剑 桥 的 办 公 室 的 邮 政 编 码 是 02140。 如 果 你 的 邮 件 标 签 程 序 把02140 变 成 十 进 制 1120 会 让 邮 递 员 不 知 所 措 的 。) oct 也 可 以 转 换 十 六 进 制 或 二 进 制数 据 , 前 提 是 你 要 在 字 串 前 面 加 0x 或 0b。2.6.2 字 串 文 本字 串 文 本 通 常 被 单 引 号 或 者 双 引 号 包 围 。 这 些 引 号 的 作 用 很 象 Unix shell 里 的 引 号 : 双 引号 包 围 的 字 串 文 本 会 做 反 斜 杠 和 变 量 替 换 , 而 单 引 号 包 围 的 字 串 文 本 不 会 ( 除 了 \' 和 \\ 以 外 ,因 此 你 可 以 在 单 引 号 包 围 的 字 串 里 使 用 单 引 号 和 反 斜 杠 )。 如 果 你 想 嵌 入 其 他 反 斜 杠 序 列 ,比 如 \n( 换 行 符 ), 你 就 必 须 用 双 引 号 的 形 式 。( 反 斜 杠 序 列 也 被 称 为 逃 逸 序 列 , 因 为 你暂 时 “ 逃 离 ” 了 通 常 的 字 符 替 换 。)一 个 单 引 号 包 围 的 字 串 必 须 和 前 面 的 单 词 之 间 有 一 个 空 白 分 隔 , 因 为 单 引 号 在 标 识 符 里 是 个有 效 的 字 符 ( 尽 管 有 些 老 旧 )。 它 的 用 法 已 经 被 更 清 晰 的 :: 序 列 取 代 了 。 这 就 意 味 着$main'val 和 $main::var 是 一 样 的 , 只 是 我 们 认 为 后 者 更 为 易 读 。双 引 号 字 串 要 遭 受 各 种 字 符 替 换 , 其 他 语 言 的 程 序 员 对 很 多 这 样 的 替 换 非 常 熟 悉 。 我 们 把 它们 列 出 在 表 2-159


表 2-1 反 斜 杠 的 字 符 逃 逸代 码含 义\n 换 行 符 ( 常 作 LF)\r 回 车 ( 常 作 CR)\t 水 平 制 表 符\f 进 纸\b 退 格\a 警 报 ( 响 铃 )\e ESC 字 符\033 八 进 制 的 ESC\x7f十 六 进 制 DEL\cCControl-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。你 也 可 以 直 接 在 你 的 字 串 里 面 嵌 入 换 行 符 ; 也 就 是 说 字 串 可 以 在 另 一 行 里 。 这 样 通 常 很 有 用 ,不 过 也 意 味 着 如 果 你 忘 了 最 后 的 引 号 字 符 ,<strong>Perl</strong> 会 直 到 找 到 另 外 一 个 包 含 引 号 字 符 的 行 时才 报 错 , 这 是 可 能 已 经 远 在 脚 本 的 其 他 位 置 了 。 好 在 这 样 使 用 通 常 会 在 同 一 行 立 即 产 生 一 个语 法 错 , 而 且 如 果 <strong>Perl</strong> 认 为 有 一 个 字 串 开 了 头 , 它 就 会 很 聪 明 地 警 告 你 你 可 能 有 字 串 没 有封 闭 。60


除 了 上 面 列 出 的 反 斜 杠 逃 逸 , 双 引 号 字 串 还 要 经 受 标 量 或 数 组 值 的 变 量 代 换 。 这 就 意 味 着 你可 以 把 某 个 变 量 的 值 直 接 插 入 字 串 文 本 里 。 这 也 是 一 个 字 串 连 接 的 好 办 法 。( 注 : 如 果 打 开了 警 告 , 在 使 用 连 接 或 联 合 操 作 时 ,<strong>Perl</strong> 可 能 会 报 告 说 有 未 定 义 的 数 值 插 入 到 字 串 中 , 即使 你 实 际 上 没 有 在 那 里 使 用 那 些 操 作 也 如 此 。 那 是 编 译 器 给 你 创 建 的 。) 可 以 做 的 变 量 代 换的 有 标 量 变 量 , 整 个 数 组 ( 不 过 散 列 不 行 ), 数 组 或 散 列 的 单 个 元 素 , 或 者 片 段 。 其 它 的 东西 都 不 转 换 。 换 而 言 之 , 你 只 能 代 换 以 $ 或 @ 开 头 的 表 达 式 , 因 为 它 们 是 字 串 分 析 器 要找 的 两 个 字 符 ( 还 有 反 斜 杠 )。 在 字 串 里 , 如 果 一 个 @ 后 面 跟 着 一 个 字 母 数 字 字 符 , 而 它又 不 是 数 组 或 片 段 的 标 识 符 , 那 就 必 须 用 反 斜 杠 逃 逸 (\@), 否 则 会 导 致 一 个 编 译 错 误 。尽 管 带 % 的 完 整 的 散 列 可 能 不 会 代 换 进 入 字 串 , 单 个 散 列 值 或 散 列 片 段 却 是 会 的 , 因 为它 们 分 别 以 $ 和 @ 开 头 。下 面 的 代 码 段 打 印 "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'}@days{"Jan","Feb"}@days{ Jan, Feb }# Ok.# Also ok.# Kinda wrong (breaks under use strict)61


除 了 被 代 换 的 数 组 和 散 列 变 量 的 脚 标 以 外 , 没 有 其 它 的 多 层 代 换 。 与 shell 程 序 员 预 期 地相 反 , 在 双 引 号 里 面 的 反 勾 号 不 做 代 换 , 在 双 引 号 里 面 的 单 引 号 也 不 会 阻 止 变 量 计 算 。<strong>Perl</strong>里 面 的 代 换 非 常 强 大 , 同 时 也 得 到 严 格 的 控 制 。 它 只 发 生 在 双 引 号 里 , 以 及 我 们 下 一 章 要 描述 的 一 些 “ 类 双 引 号 ” 的 操 作 里 :print "\n";# 正 确 , 打 印 一 个 新 行print \n; # 错 了 , 不 是 可 替 换 的 环 境 。2.6.3 选 择 自 己 的 引 号尽 管 我 们 认 为 引 起 是 文 本 值 , 但 在 <strong>Perl</strong> 里 他 们 的 作 用 更 象 操 作 符 , 提 供 了 多 种 多 样 的 代 换和 模 式 匹 配 功 能 。<strong>Perl</strong> 为 这 些 操 作 提 供 了 常 用 的 引 起 字 符 , 还 提 供 了 更 通 用 的 客 户 化 方 法 ,让 你 可 以 为 上 面 任 意 操 作 选 择 你 自 己 的 引 起 字 符 。 在 表 2-3 里 , 任 意 非 字 母 数 字 , 非 空 白分 隔 符 都 可 以 放 在 / 的 位 置 。( 新 行 和 空 格 字 符 不 再 允 许 做 分 隔 符 了 , 尽 管 老 版 本 的 <strong>Perl</strong>曾 经 一 度 允 许 这 么 做 。)表 2-3。 引 起 构 造常 用 通 用 含 义 替 换' ' q// 文 本 字 串 否" " qq// 文 本 字 串 是` ` qx// 执 行 命 令是() qw// 单 词 数 组 否// m// 模 式 匹 配 是s/// s/// 模 式 替 换y/// tr/// 字 符 转 换是否" " qr// 正 则 表 达 式 是这 里 的 有 些 东 西 只 是 构 成 “ 语 法 调 味 剂 ”, 以 避 免 你 在 引 起 字 串 里 输 入 太 多 的 反 斜 杠 , 尤 其 是在 模 式 匹 配 里 , 在 那 里 , 普 通 斜 杠 和 反 斜 杠 很 容 易 混 在 一 起 。如 果 你 选 用 单 引 号 做 分 隔 符 , 那 么 不 会 出 现 变 量 代 换 , 甚 至 那 些 正 常 状 态 需 要 代 换 的 构 造 也不 发 生 代 换 。 如 果 起 始 分 隔 符 是 一 个 起 始 圆 括 弧 , 花 括 弧 , 方 括 弧 , 那 么 终 止 分 隔 符 就 是 对应 终 止 字 符 。( 嵌 入 的 分 隔 符 必 须 成 对 出 现 。) 比 如 :$single = q!I said, "You said, 'she sad it.'"!;62


$double =qq(can't we get some "good" $variable?);$chunk_of_code = q {if ($condition) {print "Gotcha!";}};最 后 一 个 例 子 表 明 , 你 可 以 在 引 起 声 明 字 符 和 其 起 始 包 围 字 符 之 间 使 用 空 白 。 对 于 象 s///和 tr/// 这 样 的 两 元 素 构 造 而 言 , 如 果 第 一 对 引 起 是 括 弧 对 , 那 第 二 部 分 获 取 自 己 的 引 起 字符 。 实 际 上 , 第 二 部 分 不 必 与 第 一 对 一 样 。 所 以 你 可 以 用 象 s(bar) 或 者 tr(a-f)[A-f] 这 样的 东 西 。 因 为 在 两 个 内 部 的 引 起 字 符 之 间 允 许 使 用 空 白 , 所 以 你 可 以 把 上 面 最 后 一 个 例 子 写做 :tr (a-f)[A-F];不 过 , 如 果 用 # 做 为 引 起 字 符 , 就 不 允 许 出 现 空 白 。q#foo# 被 分 析 为 字 串 'foo', 而 q#foo# 引 起 操 作 符 q 后 面 跟 着 一 个 注 释 。 其 分 隔 符 将 从 下 一 行 获 取 。 在 两 个 元 素 的 构 造中 间 也 可 以 出 现 注 释 , 允 许 你 这 样 写 :s{foo}{bar}# 把 foo# 换 为 bar。tr [a-f][A-F];# 把 小 写 十 六 进 制# 换 为 大 写2.6.4 要 么 就 完 全 不 管 引 起一 个 语 法 里 没 有 其 他 解 释 的 名 字 会 被 当 作 一 个 引 起 字 串 看 待 。 我 们 叫 它 们 光 字 。( 注 : 我 们认 为 变 量 名 , 文 件 句 柄 , 标 签 等 等 不 是 光 字 , 因 为 它 们 有 被 前 面 的 或 后 面 的 ( 或 两 边 的 ) 语句 强 制 的 含 义 。 预 定 义 的 名 字 , 比 如 子 过 程 , 也 不 是 光 字 。 只 有 分 析 器 丝 毫 不 知 的 东 西 才 是63


光 字 。) 和 文 件 句 柄 和 标 签 一 样 , 完 全 由 小 写 字 符 组 成 的 光 字 在 将 来 也 可 能 有 和 保 留 字 冲 突的 危 险 。 如 果 你 打 开 了 警 告 ,<strong>Perl</strong> 会 就 光 字 对 你 发 出 警 告 。 比 如 :@days = (Mon,Tue,Wed,Thu,Fri);print STDOUT hello, ' ', world, "\n";给 数 组 @days 设 置 了 周 日 的 短 缩 写 以 及 在 STDOUT 上 打 印 一 个 "hello world" 和 一个 换 行 。 如 果 你 不 写 文 件 句 柄 , <strong>Perl</strong> 就 试 图 把 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::, 总 是 被 当 作 包 名 字 看 待 。<strong>Perl</strong> 在 编 译 时 把 可 能 的 光 字 Camel:: 转 换 成 字 串 "Camel", 这 样 , 这 个 用 法 就 不 会 被use strict 指 责 。2.6.5 代 换 数 组 数 值64


数 组 变 量 通 过 使 用 在 $" 变 量 ( 缺 省 时 包 含 一 个 空 格 )( 注 : 如 果 你 使 用 和 <strong>Perl</strong> 捆 绑 的English 模 块 , 那 么 就 是 $LIST_SEPARATOR) 里 声 明 的 分 隔 符 将 所 有 数 组 元 素 替 换 为双 引 号 包 围 的 字 串 。 下 面 的 东 西 是 一 样 的 :$temp = join( $", @ARGV ); print $temp;print "@ARGV";在 搜 索 模 式 里 ( 也 要 进 行 双 引 号 类 似 的 代 换 ) 有 一 个 不 巧 的 歧 义 :/$foo[bar]/ 是 被 替 换为 /${foo}[bar]/( 这 时 候 [bar] 是 用 于 正 则 表 达 式 的 字 符 表 ) 还 是 /${foo[bar]}/( 这里 [bar] 是 数 组 @foo 的 脚 标 )? 如 果 @foot 不 存 在 , 它 很 显 然 是 个 字 符 表 。 如 果@foo 存 在 ,<strong>Perl</strong> 则 猜 测 [bar] 的 用 途 , 并 且 几 乎 总 是 正 确 的 ( 注 : 全 面 描 述 猜 测 机 制 太乏 味 了 , 基 本 上 就 是 对 所 有 看 来 象 字 符 表 (a-z,\w, 开 头 的 ^) 和 看 来 象 表 达 式 ( 变 量 或者 保 留 字 ) 的 东 西 进 行 加 权 平 均 )。 如 果 它 猜 错 了 , 或 者 是 你 变 态 , 那 你 可 以 用 上 面 描 述 的花 括 弧 强 制 正 确 的 代 换 。 就 算 你 只 是 为 了 谨 慎 , 那 不 算 是 个 坏 主 意 。2.6.6“ 此 处 ” 文 档有 一 种 面 向 行 的 引 起 是 以 Unix shell 的 “ 此 处 文 档 ” 语 法 为 基 础 的 。 说 它 面 向 行 是 因 为 它 的分 隔 符 是 行 而 不 是 字 符 。 起 始 分 隔 符 是 当 前 行 , 结 束 分 隔 符 是 一 个 包 含 你 声 明 的 字 串 的 行 。你 所 声 明 的 用 以 结 束 引 起 材 料 的 字 串 跟 在 一 个


EOFprint


Here's a linero two.THISAnd here's another.THAT不 过 别 忘 记 在 最 后 放 分 号 以 结 束 语 句 , 因 为 <strong>Perl</strong> 不 知 道 你 不 是 做 这 样 的 试 验 :print


这 些 就 是 所 谓 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; # 要 求 特 定 <strong>Perl</strong> 版 本 ( 或 更 新 )$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 其 他 文 本 记 号你 应 该 把 任 何 以 双 下 划 线 开 头 和 结 束 的 标 识 符 看 作 由 <strong>Perl</strong> 保 留 做 特 殊 语 法 处 理 的 记 号 。 其中 有 两 个 这 类 特 殊 文 本 是 LINE 和 __FILE__, 分 别 意 味 着 在 你 的 程 序 某 点 的 当 前 行 号 和文 件 名 。 它 们 只 能 用 做 独 立 的 记 号 ; 它 们 不 能 被 代 换 为 字 串 。 与 之 类 似 ,__PACKAGE__ 是当 前 代 码 所 编 译 进 入 的 包 的 名 字 。 如 果 没 有 当 前 包 ( 因 为 有 一 个 空 的 package; 指 示 ),__PACKAGE__ 就 是 未 定 义 值 。 记 号 END ( 或 者 是 一 个 Control-D 或 Control-Z 字68


符 ) 可 以 用 于 在 真 正 的 文 件 结 束 符 之 前 表 示 脚 本 的 逻 辑 结 束 。 任 何 后 面 的 文 本 都 被 忽 略 , 不过 可 以 通 过 DATA 文 件 句 柄 读 取 。DATA 记 号 的 作 用 类 似 END 记 号 , 不 过 它 是 在 当 前 包 的 名 字 空 间 打 开 DATA 文 件 句柄 , 因 此 你 所 require 的 所 有 文 件 可 以 同 时 打 开 , 每 个 文 件 都 拥 有 自 己 的 DATA 文 件 句柄 。 更 多 信 息 请 看 第 二 十 八 章 里 的 DATA。2.7 环 境到 现 在 为 止 , 我 们 已 经 看 到 了 一 些 会 产 生 标 量 值 的 项 。 在 我 们 进 一 步 讨 论 项 之 前 , 我 们 要 先讨 论 带 环 境 (context) 的 术 语 。2.7.1 标 量 和 列 表 环 境你 在 <strong>Perl</strong> 脚 本 里 激 活 的 每 个 操 作 ( 注 : 这 里 我 们 用 “ 操 作 ” 统 称 操 作 符 或 项 。 当 你 开 始 讨 论那 些 分 析 起 来 类 似 项 而 看 起 来 象 操 作 符 的 函 数 时 , 这 两 个 概 念 间 的 界 限 就 模 糊 了 。) 都 是 在特 定 的 环 境 里 进 行 的 , 并 且 该 操 作 的 运 转 可 能 依 赖 于 那 个 环 境 的 要 求 。 存 在 两 种 主 要 的 环 境 :标 量 和 列 表 。 比 如 , 给 一 个 标 量 变 量 , 一 个 数 组 或 散 列 的 标 量 元 素 赋 值 , 在 右 手 边 就 会 以 标量 环 境 计 算 :$x = funkshun(); # scalar context$x[1]= funkshun(); # scalar context$x{"ray"} = funkshun(); # scalar context但 是 , 如 果 给 一 个 数 组 或 者 散 列 , 或 者 它 们 的 片 段 赋 值 , 在 右 手 边 就 会 以 列 表 环 境 进 行 计 算 ,即 便 是 该 片 段 只 选 出 了 的 一 个 元 素 :@x@x[1]= funkshun(); # list context= funkshun(); # list context@x{"ray"} = funkshun(); # list context%x = funkshun(); # list context即 使 你 用 my 或 our 修 改 项 的 定 义 , 这 些 规 则 也 不 会 改 变 :my $xmy @x= funkshun(); # scalar context= funkshun(); # list context69


my %xmy ($x)= funkshun(); # list context= funkshun(); # list context在 你 正 确 理 解 标 量 和 列 表 环 境 的 区 别 之 前 你 都 会 觉 得 很 痛 苦 , 因 为 有 些 操 作 符 ( 比 如 我 们 上面 虚 构 的 funkshun() 函 数 ) 知 道 它 们 处 于 什 么 环 境 中 , 所 以 就 能 在 列 表 环 境 中 返 回 列 表 ,在 标 量 环 境 中 返 回 标 量 。( 如 果 这 里 提 到 的 东 西 对 于 某 操 作 成 立 , 那 么 在 那 个 操 作 的 文 档 里面 应 该 提 到 这 一 点 。) 用 计 算 机 行 话 来 说 , 这 些 操 作 重 载 了 它 们 的 返 回 类 型 。 不 过 这 是 一 种非 常 简 单 的 重 载 , 只 是 以 单 数 和 复 数 之 间 的 区 别 为 基 础 , 别 的 就 没 有 了 。如 果 某 些 操 作 符 对 环 境 敏 感 , 那 么 很 显 然 必 须 有 什 么 东 西 给 它 们 提 供 环 境 。 我 们 已 经 显 示 了赋 值 可 以 给 它 的 右 操 作 数 提 供 环 境 , 不 过 这 个 例 子 不 难 理 解 , 因 为 所 有 操 作 符 都 给 它 的 每 个操 作 数 提 供 环 境 。 你 真 正 感 兴 趣 的 应 该 是 一 个 操 作 符 会 给 它 的 操 作 数 提 供 哪 个 环 境 。 这 样 ,你 可 以 很 容 易 地 找 出 哪 个 提 供 了 列 表 环 境 , 因 为 在 它 们 的 语 法 描 述 部 分 都 有 LIST。 其 他 的都 提 供 标 量 环 境 。 通 常 , 这 是 很 直 观 的 。( 注 : 不 过 请 注 意 , 列 表 环 境 可 以 通 过 子 过 程 调 用传 播 , 因 此 , 观 察 某 个 语 句 会 在 标 量 还 是 列 表 环 境 里 面 计 算 并 不 总 是 很 直 观 。 程 序 可 以 在 子过 程 里 面 用 wantarray 函 数 找 出 它 的 环 境 。) 如 果 必 要 , 你 可 以 用 伪 函 数 scalar 给 一 个LIST 中 间 的 参 数 强 制 一 个 标 量 环 境 。<strong>Perl</strong> 没 有 提 供 强 制 列 表 环 境 成 标 量 环 境 的 方 法 , 因为 在 任 何 一 个 你 需 要 列 表 环 境 的 地 方 , 都 会 已 经 通 过 一 些 控 制 函 数 提 供 了 LIST。标 量 环 境 可 以 进 一 步 分 类 成 字 串 环 境 , 数 字 环 境 和 无 所 谓 环 境 。 和 我 们 刚 刚 说 的 标 量 与 列 表环 境 的 区 别 不 同 , 操 作 从 来 不 关 心 它 们 处 于 那 种 标 量 环 境 。 它 们 只 是 想 返 回 的 标 量 值 , 然 后让 <strong>Perl</strong> 在 字 串 环 境 中 把 数 字 转 换 成 字 串 , 以 及 在 数 字 环 境 中 把 字 串 转 换 成 数 字 。 有 些 标 量环 境 不 关 心 返 回 的 是 字 串 还 是 数 字 还 是 引 用 , 因 此 就 不 会 发 生 转 换 。 这 个 现 象 会 发 生 在 你 给另 外 一 个 变 量 赋 值 的 时 候 。 新 的 变 量 只 能 接 受 和 旧 值 一 样 的 子 类 型 。2.7.2 布 尔 环 境另 外 一 个 特 殊 的 无 所 谓 标 量 环 境 是 布 尔 环 境 。 布 尔 环 境 就 是 那 些 要 对 一 个 表 达 式 进 行 计 算 ,看 看 它 是 真 还 是 假 的 地 方 。 当 我 们 在 本 书 中 说 到 “ 真 ” 或 “ 假 ” 的 时 候 , 我 们 指 的 是 <strong>Perl</strong> 用 的技 术 定 义 : 如 果 一 个 标 量 不 是 空 字 串 "" 或 者 数 字 0 ( 或 者 它 的 等 效 字 串 ,"0" ) 那 么 就是 真 。 一 个 引 用 总 是 真 , 因 为 它 代 表 一 个 地 址 , 而 地 址 从 不 可 能 是 0。 一 个 未 定 义 值 ( 常称 做 undef) 总 是 假 , 因 为 它 看 起 来 象 "" 或 者 0—— 取 决 于 你 把 它 当 作 字 串 还 是 数 字 。( 列 表 值 没 有 布 尔 值 , 因 为 列 表 值 从 来 不 会 产 生 标 量 环 境 !)因 为 布 尔 环 境 是 一 个 无 所 谓 环 境 , 它 从 不 会 导 致 任 何 标 量 转 换 的 发 生 , 当 然 , 标 量 环 境 本 身施 加 在 任 何 参 与 的 操 作 数 上 。 并 且 对 于 许 多 相 关 的 操 作 数 , 它 们 在 标 量 环 境 里 产 生 的 标 量 代表 一 个 合 理 的 布 尔 值 。 也 就 是 说 , 许 多 在 列 表 环 境 里 会 产 生 一 个 列 表 的 操 作 符 可 以 在 布 尔 环70


境 里 用 于 真 / 假 测 试 。 比 如 , 在 一 个 由 unlink 操 作 符 提 供 的 列 表 环 境 里 , 一 个 数 组 名 产 生一 列 值 :unlink @files; # 删 除 所 有 文 件 , 忽 略 错 误 。但 是 , 如 果 你 在 一 个 条 件 里 ( 也 就 是 说 , 在 布 尔 环 境 里 ) 使 用 数 组 , 数 组 就 会 知 道 它 正 处 于一 个 标 量 环 境 并 且 返 回 数 组 里 的 元 素 个 数 , 只 要 数 组 里 面 还 有 元 素 , 通 常 就 是 真 。 因 此 , 如果 你 想 获 取 每 个 没 有 正 确 删 除 的 文 件 的 警 告 , 你 可 能 就 会 这 样 写 一 个 循 环 :while (@files) {my $file = shift @files;unlink $file or warn "Can't delete $file: $!\n";}这 里 的 @files 是 在 由 while 语 句 提 供 的 布 尔 环 境 里 计 算 的 , 因 此 <strong>Perl</strong> 就 计 算 数 组 本 身 ,看 看 它 是 “ 真 数 组 ” 还 是 “ 假 数 组 ”。 只 要 里 面 还 有 文 件 名 , 它 就 是 真 数 组 , 不 过 一 旦 最 后 一个 文 件 被 移 出 , 它 就 变 成 假 数 组 。 请 注 意 我 们 早 先 说 过 的 依 然 有 效 。 虽 然 数 组 包 含 ( 和 可 以产 生 ) 一 列 数 值 , 我 们 在 标 量 环 境 里 并 不 计 算 列 表 值 。 我 们 只 是 告 诉 数 组 这 里 是 标 量 , 然 后问 它 觉 得 自 己 是 什 么 。不 要 试 图 在 这 里 用 defined @files。 那 样 没 用 , 因 为 defined 函 数 是 询 问 一 个 标 量 是 否为 undef, 而 一 个 数 组 不 是 标 量 。 简 单 的 布 尔 测 试 就 够 用 了 。2.7.3 空 (void) 环 境另 外 一 个 特 殊 的 标 量 环 境 是 空 环 境 (void context)。 这 个 环 境 不 仅 不 在 乎 返 回 值 的 类 型 ,它 甚 至 连 返 回 值 都 不 想 要 。 从 函 数 如 何 运 行 的 角 度 看 , 这 和 普 通 标 量 环 境 没 有 区 别 。 但 是 如果 你 打 开 了 警 告 , 如 果 你 在 一 个 不 需 要 值 的 地 方 , 比 如 说 在 一 个 不 返 回 值 的 语 句 里 , 使 用 了一 个 没 有 副 作 用 的 表 达 式 ,<strong>Perl</strong> 的 编 译 器 就 会 警 告 你 , 比 如 , 如 果 你 用 一 个 字 串 当 作 语 句 :"Camel Lot";你 会 收 到 这 样 的 警 告 :Useless use of a constant in void context in myprog line 123;2.7.4 代 换 环 境71


我 们 早 先 说 过 双 引 号 文 本 做 反 斜 杠 代 换 和 变 量 代 换 , 不 过 代 换 文 本 ( 通 常 称 做 “ 双 引 号 文 本 ”)不 仅 仅 适 用 于 双 引 号 字 串 。 其 他 的 一 些 双 引 号 类 构 造 是 : 通 用 的 反 勾 号 操 作 符 qx, 模 式 匹配 操 作 符 m//, 替 换 操 作 符 s///, 和 引 起 正 则 表 达 式 操 作 符 ,qr//。 替 换 操 作 符 在 处 理 模式 匹 配 之 前 在 它 的 左 边 做 代 换 动 作 , 然 后 每 次 匹 配 左 边 时 做 右 边 的 代 换 工 作 。代 换 环 境 只 发 生 在 引 起 里 , 或 者 象 引 起 那 样 的 地 方 , 也 许 我 们 把 它 当 作 与 标 量 及 列 表 环 境 一样 的 概 念 来 讲 并 不 恰 当 。( 不 过 也 许 是 对 的 。)2.8 列 表 值 和 数 组既 然 我 们 谈 到 环 境 , 那 我 们 可 以 谈 谈 列 表 文 本 和 它 们 在 环 境 里 的 性 质 。 你 已 经 看 到 过 一 些 列表 文 本 。 列 表 文 本 是 用 逗 号 分 隔 的 独 立 数 值 表 示 的 ( 当 有 优 先 级 要 求 的 时 候 用 圆 括 弧 包 围 )。因 为 使 用 圆 括 弧 几 乎 从 不 会 造 成 损 失 , 所 以 列 表 值 的 语 法 图 通 常 象 下 面 这 样 说 明 :(LIST)我 们 早 先 说 过 在 语 法 描 述 里 的 LIST 表 示 某 个 东 西 给 它 的 参 数 提 供 了 列 表 环 境 , 不 过 只 有列 表 文 本 自 身 会 部 分 违 反 这 条 规 则 , 就 是 说 只 有 在 列 表 和 操 作 符 全 部 处 于 列 表 环 境 里 才 会 提供 真 正 的 列 表 环 境 。 列 表 文 本 在 列 表 环 境 里 的 内 容 只 是 顺 序 声 明 的 参 数 值 。 作 为 一 种 表 达 式里 的 特 殊 的 项 , 一 个 列 表 文 本 只 是 把 一 系 列 临 时 值 压 到 <strong>Perl</strong> 的 堆 栈 里 , 当 操 作 符 需 要 的 时候 再 从 堆 栈 里 弹 出 来 。不 过 , 在 标 量 环 境 里 , 列 表 文 本 并 不 真 正 表 现 得 象 一 个 列 表 (LIST), 因 为 它 并 没 有 给 它的 值 提 供 列 表 环 境 。 相 反 , 它 只 是 在 标 量 环 境 里 计 算 它 的 每 个 参 数 , 并 且 返 回 最 后 一 个 元 素的 值 。 这 是 因 为 它 实 际 上 就 是 伪 装 的 C 逗 号 操 作 符 , 逗 号 操 作 符 是 一 个 两 目 操 作 符 , 它 会丢 弃 左 边 的 值 并 且 返 回 右 边 的 值 。 用 我 们 前 面 讨 论 过 的 术 语 来 说 , 逗 号 操 作 符 的 左 边 实 际 上提 供 了 一 个 空 环 境 。 因 为 逗 号 操 作 符 是 左 关 联 的 , 如 果 你 有 一 系 列 逗 号 分 隔 的 数 值 , 那 你 总是 得 到 最 后 一 个 数 值 , 因 为 最 后 一 个 逗 号 会 丢 弃 任 何 前 面 逗 号 生 成 的 东 西 。 因 此 , 要 比 较 这两 种 环 境 , 列 表 赋 值 :@stuff = ( "one", "two", "three");给 数 组 @stuff, 赋 予 了 整 个 列 表 的 值 。 但 是 标 量 赋 值 :$stuff = ( "one", "two", "three");只 是 把 值 "three" 赋 予 了 变 量 $stuff。 和 我 们 早 先 提 到 的 @files 数 组 一 样 , 逗 号 操 作 符知 道 它 是 处 于 标 量 还 是 列 表 环 境 , 并 且 根 据 相 应 环 境 选 择 其 动 作 。72


值 得 说 明 的 一 点 是 列 表 值 和 数 组 是 不 一 样 的 。 一 个 真 正 的 数 组 变 量 还 知 道 它 的 环 境 , 处 于 列表 环 境 时 , 它 会 象 一 个 列 表 文 本 那 样 返 回 其 内 部 列 表 。 但 是 当 处 于 标 量 环 境 时 , 它 只 返 回 数组 长 度 。 下 面 的 东 西 给 $stuff 赋 值 3:@stuff = ("one", "two", "three"); $stuff = @stuff;如 果 你 希 望 它 获 取 值 "three", 那 你 可 能 是 认 为 <strong>Perl</strong> 使 用 逗 号 操 作 符 的 规 则 , 把 @stuff放 在 堆 栈 里 的 临 时 值 都 丢 掉 , 只 留 下 一 个 交 给 $stuff。 不 过 实 际 上 不 是 这 样 。@stuff 数组 从 来 不 把 它 的 所 有 值 都 放 在 堆 栈 里 。 实 际 上 , 它 从 来 不 在 堆 栈 上 放 任 何 值 。 它 只 是 在 堆 栈里 放 一 个 数 值 —— 数 组 长 度 , 因 为 它 知 道 自 己 处 于 标 量 环 境 。 没 有 任 何 项 或 者 操 作 符 会 在 标量 环 境 里 把 列 表 放 入 堆 栈 。 相 反 , 它 会 在 堆 栈 里 放 一 个 标 量 , 一 个 它 喜 欢 的 值 , 而 这 个 值 不太 可 能 是 列 表 的 最 后 一 个 值 ( 就 是 那 个 在 列 表 环 境 里 返 回 的 值 ), 因 为 最 后 一 个 值 看 起 来 不象 时 在 标 量 环 境 里 最 有 用 的 值 。 你 的 明 白 ?( 如 果 还 不 明 白 , 你 最 好 重 新 阅 读 本 自 然 段 , 因为 它 很 重 要 。)现 在 回 到 真 正 的 LIST( 列 表 环 境 )。 直 到 现 在 我 们 都 假 设 列 表 文 本 只 是 一 个 文 本 列 表 。 不过 , 正 如 字 串 文 本 可 能 代 换 其 他 子 字 串 一 样 , 一 个 列 表 文 本 也 可 以 代 换 其 他 子 列 表 。 任 何 返回 值 的 表 达 式 都 可 以 在 一 个 列 表 中 使 用 。 所 使 用 的 值 可 以 是 标 量 值 或 列 表 值 , 但 它 们 都 成 为新 列 表 值 的 一 部 分 , 因 为 LIST 会 做 子 列 表 的 自 动 代 换 。 也 就 是 说 , 在 计 算 一 个 LIST 时 ,列 表 中 的 每 个 元 素 都 在 一 个 列 表 环 境 中 计 算 , 并 且 生 成 的 列 表 值 都 被 代 换 进 LIST, 就 好 象每 个 独 立 的 元 素 都 是 LIST 的 成 员 一 样 。 因 此 数 组 在 一 个 LIST 中 失 去 它 们 的 标 识 ( 注 :有 些 人 会 觉 得 这 是 个 问 题 , 但 实 际 上 不 是 。 如 果 你 不 想 失 去 数 组 的 标 识 , 那 么 你 总 是 可 以 用一 个 引 用 代 换 数 组 。 参 阅 第 八 章 。)。 列 表 :(@stuff,@nonsense,funkshun())包 含 元 素 @stuff, 跟 着 是 元 素 @nonsense, 最 后 是 在 列 表 环 境 里 调 用 子 过 程&funkshun 时 它 的 返 回 值 。 请 注 意 她 们 的 任 意 一 个 或 者 全 部 都 可 能 被 代 换 为 一 个 空 列 表 ,这 时 就 象 在 该 点 没 有 代 换 过 数 组 或 者 函 数 调 用 一 样 。 空 列 表 本 身 由 文 本 () 代 表 。 对 于 空 数组 , 它 会 被 代 换 为 一 个 空 列 表 因 而 可 以 忽 略 , 把 空 列 表 代 换 为 另 一 个 列 表 没 有 什 么 作 用 。 所以 ,((),(),()) 等 于 ()。这 条 规 则 的 一 个 推 论 就 是 你 可 以 在 任 意 列 表 值 结 尾 放 一 个 可 选 的 逗 号 。 这 样 , 以 后 你 回 过 头来 在 最 后 一 个 元 素 后 面 增 加 更 多 元 素 会 简 单 些 :@releases = ("alpha","beta",73


"gamma",);或 者 你 可 以 完 全 不 用 逗 号 : 另 一 个 声 明 文 本 列 表 的 方 法 是 用 我 们 早 先 提 到 过 的 qw( 引 起 字 )语 法 。 这 样 的 构 造 等 效 于 在 空 白 的 地 方 用 单 引 号 分 隔 。 例 如 :@froots = qw(apple banana carambolacoconut guava kumquatmandarin nectarine peachpear persimmon plum);( 请 注 意 那 些 圆 括 弧 的 作 用 和 引 起 字 符 一 样 , 不 是 普 通 的 圆 括 弧 。 我 们 也 可 以 很 容 易 地 使 用尖 括 弧 或 者 花 括 弧 或 者 斜 杠 。 但 是 圆 括 弧 看 起 来 比 较 漂 亮 。)一 个 列 表 值 也 可 以 象 一 个 普 通 数 组 那 样 使 用 脚 标 。 你 必 须 把 列 表 放 到 一 个 圆 括 弧 ( 真 的 ) 里面 以 避 免 混 淆 。 我 们 经 常 用 到 从 一 个 列 表 里 抓 取 一 个 值 , 但 这 时 候 实 际 上 是 抓 了 列 表 的 一 个片 段 , 所 以 语 法 是 :(LIST)[LIST]例 子 :# Stat 返 回 列 表 值$modification_time = (stat($file))[9];# 语 法 错 误$modification_time = stat($file)[9]; # 忘 记 括 弧 了 。# 找 一 个 十 六 进 制 位$hexdigit = ('a','b','c','d','e','f')[$digit-10];74


# 一 个 “ 反 转 的 逗 号 操 作 符 ”。return (pop(@foo),pop(@foo))[0];# 把 多 个 值 当 作 一 个 片 段($day, $month, $year) = (localtime)[3,4,5];2.8.1 列 表 赋 值只 有 给 列 表 赋 值 的 每 一 个 元 素 都 合 法 时 , 才 能 给 整 个 列 表 赋 值 :($a, $b, $c) = (1, 2, 3); ($map{red}, ${map{green}, $map{blue}) =(0xff0000, 0x00ff00, 0x0000ff);你 可 以 给 一 个 列 表 里 的 undef 赋 值 。 这 一 招 可 以 很 有 效 地 把 一 个 函 数 的 某 些 返 回 值 抛 弃 :($dev, $ino, undef, undef, $uid, $gid) = stat($file);最 后 一 个 列 表 元 素 可 以 是 一 个 数 组 或 散 列 :($a, $b, @rest) = split;my ($a, $b, %rest) = @arg_list;实 际 上 你 可 以 在 赋 值 的 列 表 里 的 任 何 地 方 放 一 个 数 组 或 散 列 , 只 是 第 一 个 数 组 或 散 列 会 吸 收所 有 剩 余 的 数 值 , 而 且 任 何 在 它 们 后 面 的 东 西 都 会 被 设 置 为 未 定 义 值 。 这 样 可 能 在 local 或my 里 面 比 较 有 用 , 因 为 这 些 地 方 你 可 能 希 望 数 组 初 始 化 为 空 。你 甚 至 可 以 给 空 列 表 赋 值 :() = funkshun();这 样 会 导 致 在 列 表 环 境 里 调 用 你 的 函 数 , 但 是 把 返 回 值 丢 弃 。 如 果 你 在 没 有 赋 值 ( 语 句 ) 的情 况 下 调 用 了 此 函 数 , 那 它 就 会 在 一 个 空 环 境 里 被 调 用 , 而 空 环 境 是 标 量 环 境 , 因 此 可 能 令此 函 数 的 行 为 完 全 不 同 。在 标 量 环 境 里 的 列 表 赋 值 返 回 赋 值 表 达 式 右 边 生 成 的 元 素 的 个 数 :$x = (($a,$b)=(7,7,7)); # 把 $x 设 为 3, 不 是 2$x = ( ($a, $b) = funk()); # 把 $x 设 为 funk() 的 返 回 数75


$x = ( () = funk() ); # 同 样 把 $x 设 为 funk() 的 返 回 数这 样 你 在 一 个 布 尔 环 境 里 做 列 表 赋 值 就 很 有 用 了 , 因 为 大 多 数 列 表 函 数 在 结 束 的 时 候 返 回 一个 空 (null) 列 表 , 空 列 表 在 赋 值 时 生 成 一 个 0, 也 就 是 假 。 下 面 就 是 你 可 能 在 一 个 while语 句 里 使 用 的 情 景 :while (($login, $password) = getpwent) {if (crypt($login, $password) eq $password) {print "$login has an insecure password!\n";}}2.8.2 数 组 长 度你 可 以 通 过 在 标 量 环 境 里 计 算 数 组 @days 而 获 取 数 组 @days 里 面 的 元 素 的 个 数 , 比如 :@days + 0; # 隐 含 地 把 @days 处 于 标 量 环 境 scalar(@days) # 明 确 强 制 @days处 于 标 量 环 境请 注 意 此 招 只 对 数 组 有 效 。 并 不 是 一 般 性 地 对 列 表 值 都 有 效 。 正 如 我 们 早 先 提 到 的 , 一 个 逗号 分 隔 的 列 表 在 标 量 环 境 里 返 回 最 后 一 个 值 , 就 象 C 的 逗 号 操 作 符 一 样 。 但 是 因 为 你 几 乎从 来 都 不 需 要 知 道 <strong>Perl</strong> 里 列 表 的 长 度 , 所 以 这 不 是 个 问 题 。和 @days 的 标 量 计 算 有 紧 密 联 系 的 是 $#days。 这 样 会 返 回 数 组 里 最 后 一 个 元 素 的 脚标 , 或 者 说 长 度 减 一 , 因 为 ( 通 常 ) 存 在 第 零 个 元 素 。 给 $#days 赋 值 则 修 改 数 组 长 度 。用 这 个 方 法 缩 短 数 组 的 长 度 会 删 除 插 入 的 数 值 。 你 在 一 个 数 组 扩 大 之 前 预 先 伸 展 可 以 获 得 一定 的 效 能 提 升 。( 你 还 可 以 通 过 给 超 出 数 组 长 度 之 外 的 元 素 赋 值 的 方 法 来 扩 展 一 个 数 组 。)你 还 可 以 通 过 给 数 组 赋 空 列 表 () 把 它 裁 断 为 什 么 都 没 有 。 下 面 的 两 个 语 句 是 等 效 的 :@whatever = (); $#whatever = -1;而 且 下 面 的 表 达 式 总 是 真 :scalar(@whatever) == $#whatever + 1;76


截 断 一 个 数 组 并 不 回 收 其 内 存 。 你 必 须 undef(@whatever) 来 把 它 的 内 存 释 放 回 你 的 进程 的 内 存 池 里 。 你 可 能 无 法 把 它 释 放 回 你 的 系 统 的 内 存 池 , 因 为 几 乎 没 有 那 种 操 作 系 统 支 持这 样 做 。2.9 散 列如 前 所 述 , 散 列 只 是 一 种 有 趣 的 数 组 类 型 , 在 散 列 里 你 是 用 字 串 而 不 是 数 字 来 取 出 数 值 。 散列 定 义 键 字 和 值 之 间 的 关 联 , 因 此 散 列 通 常 被 那 些 打 字 不 偷 懒 的 人 称 做 关 联 数 组 。在 <strong>Perl</strong> 里 实 际 上 是 没 有 叫 什 么 散 列 文 本 的 东 西 的 , 但 是 如 果 你 给 一 个 散 列 赋 一 个 普 通 列 表的 值 , 列 表 里 的 每 一 对 值 将 被 当 作 一 对 键 字 / 数 值 关 联 :%map = ('red', 0xff0000,'green', 0x00ff00,'blue',0x0000ff);上 面 形 式 和 下 面 的 形 式 作 用 相 同 :%map = ();# 先 清 除 散 列$map{red} = 0xfff0000;$map{green} = 0x00ff00;$map{blue} = 0x0000ff;通 常 在 键 字 / 数 值 之 间 使 用 => 操 作 符 会 有 更 好 的 可 读 性 。=> 操 作 符 只 是 逗 号 的 同 义 词 ,不 过 却 有 更 好 的 视 觉 区 分 效 果 , 并 且 还 把 任 何 空 标 识 符 引 起 在 其 左 边 ( 就 象 上 面 的 花 括 弧 里面 的 标 识 符 ), 这 样 , 它 在 若 干 种 操 作 中 就 显 得 非 常 方 便 , 包 括 初 始 化 散 列 变 量 :%map = (red => 0xff0000,green => 0x00ff00,blue => 0x0000ff,);或 者 初 始 化 任 何 当 作 记 录 使 用 的 匿 名 散 列 引 用 :$rec = {NAME => 'John Simth',77


RANK => 'Captain',SERNO => '951413',};或 者 用 命 名 的 参 数 激 活 复 杂 的 函 数 :$fiels = radio_group(NAME => 'animals'VALUESDEFAULTLINEBREADLABELS=>['camel','llama','ram','wolf'],=>'camel',=> 'true',=>\%animal_names,);不 过 这 里 我 们 又 走 的 太 远 了 。 先 回 到 散 列 。你 可 以 在 一 个 列 表 环 境 里 使 用 散 列 变 量 (%hash), 这 种 情 况 下 它 把 它 的 键 字 / 数 值 对 转 换成 列 表 。 但 是 , 并 不 意 味 着 以 某 种 顺 序 初 始 化 的 散 列 就 应 该 同 样 的 顺 序 恢 复 出 来 。 散 列 在 系统 内 部 实 现 上 是 使 用 散 列 表 来 达 到 高 速 查 找 , 这 意 味 着 记 录 存 储 的 顺 序 和 内 部 用 于 计 算 记 录在 散 列 表 的 里 的 位 置 的 散 列 函 数 有 关 , 而 与 任 何 其 它 事 情 无 关 。 因 此 , 记 录 恢 复 出 来 的 时 候看 起 来 是 随 机 的 顺 序 。( 当 然 , 每 一 对 键 字 / 数 值 是 以 正 确 的 顺 序 取 出 来 的 。) 关 于 如 何 获得 排 序 输 出 的 例 子 , 可 以 参 考 第 二 十 九 章 的 keys 函 数 。当 你 在 标 量 环 境 里 计 算 散 列 变 量 的 数 值 的 时 候 , 它 只 有 在 散 列 包 含 任 意 键 字 / 数 值 对 时 才 返回 真 。 如 果 散 列 里 存 在 键 字 / 数 值 对 , 返 回 的 值 是 一 个 用 斜 线 分 隔 的 已 用 空 间 和 分 配 的 总 空间 的 值 组 成 的 字 串 。 这 个 特 点 可 以 用 于 检 查 <strong>Perl</strong> 的 ( 编 译 好 的 ) 散 列 算 法 在 你 的 数 据 集 里面 性 能 是 否 太 差 。 比 如 , 你 把 10,000 个 东 西 放 到 一 个 散 列 里 面 , 但 是 在 标 量 环 境 里 面 计算 %HASH 得 出 “1/8”, 意 味 着 总 共 八 个 桶 里 只 用 了 一 个 桶 。 大 概 是 一 个 桶 里 存 放 了10,000 个 条 目 。 这 可 是 不 应 该 发 生 的 事 情 。要 算 出 一 个 散 列 里 面 的 键 字 的 数 量 , 在 标 量 环 境 里 使 用 keys 函 数 :scalar(keys(%HASH)).78


你 可 以 通 过 在 花 括 弧 里 面 声 明 用 逗 号 分 隔 的 , 超 过 一 个 键 字 的 方 法 仿 真 多 维 数 组 。 列 出 的 键字 连 接 到 一 起 , 由 $;($SUBSCRIPT_SEPARATOR)( 缺 省 值 是 chr(28)) 的 内 容 分 隔 。结 果 字 串 用 做 散 列 的 真 实 键 字 。 下 面 两 行 效 果 相 同 :$people{ $state, $country } = $census_results;$people{ join $; =>$state, $county} = $census_results;这 个 特 性 最 初 是 为 了 支 持 a2p(awk 到 perl 转 换 器 ) 而 实 现 的 。 现 在 , 你 通 常 会 只 使 用第 九 章 , 数 据 结 构 , 里 写 的 一 个 真 的 ( 或 者 , 更 真 实 一 些 ) 的 多 维 数 组 。 旧 风 格 依 然 有 用 的一 个 地 方 是 与 DBM 文 件 捆 绑 在 一 起 的 散 列 ( 参 阅 第 三 十 二 章 , 标 准 模 块 , 里 的 DB_File),因 为 它 不 支 持 多 维 键 字 。请 不 要 把 多 维 散 列 仿 真 和 片 段 混 淆 起 来 。 前 者 表 示 一 个 标 量 数 值 , 后 者 则 是 一 个 列 表 数 值 :$hash{ $x, $y, $z}@hash{ $x, $y, $z}# 单 个 数 值# 一 个 三 个 值 的 片 段2.10 型 团 (typeglob) 和 文 件 句 柄<strong>Perl</strong> 里 面 有 种 特 殊 的 类 型 叫 类 型 团 (typeglob) 用 以 保 留 整 个 符 号 表 记 录 。( 符 号 表 记 录*foo 包 括 $foo, @foo, %foo,&foo 和 其 他 几 个 foo 的 简 单 解 释 值 。) 类 型 团(typeglob) 的 类 型 前 缀 上 一 个 *, 因 为 它 代 表 所 有 类 型 。类 型 团 (typeglob)( 或 由 此 的 引 用 ) 的 一 个 用 途 是 是 用 于 传 递 或 者 存 储 文 件 句 柄 。 如 果你 想 保 存 一 个 文 件 句 柄 , 你 可 以 这 么 干 :$fh = *STDOUT;或 者 作 为 一 个 真 的 引 用 , 象 这 样 :$fh = \*STDOUT;这 也 是 创 建 一 个 本 地 文 件 句 柄 的 方 法 , 比 如 :sub newopen {my $path = shift;local *FH; # 不 是 my() 或 our ()open(FH,$path ) or return undef;79


eturn *FH:# 不 是 \*FH!}$fh = newopen('/etc/passwd');参 阅 open 函 数 获 取 另 外 一 个 生 成 新 文 件 句 柄 的 方 法 。类 型 团 如 今 的 主 要 用 途 是 把 一 个 符 号 表 取 另 一 个 符 号 表 名 字 做 别 名 。 别 名 就 是 外 号 , 如 果 你说 :*foo = *bar;那 所 有 叫 “foo” 的 东 西 都 是 每 个 对 应 的 叫 “bar” 的 同 意 词 。 你 也 可 以 通 过 给 类 型 团 赋 予 引 用 实现 只 给 某 一 个 变 量 取 别 名 :*foo = \$bar;这 样 $foo 就 是 $bar 的 一 个 别 名 , 而 没 有 把 @foo 做 成 @bar 的 别 名 , 或 者 把 %foo做 成 %bar 的 别 名 。 所 有 这 些 都 只 影 响 全 局 ( 包 ) 变 量 ; 词 法 不 能 通 过 符 号 表 记 录 访 问 。象 这 样 给 全 局 变 量 别 名 看 起 来 可 能 有 点 愚 蠢 , 不 过 事 实 是 整 个 模 块 的 输 入 / 输 出 机 制 都 是 建筑 在 这 个 特 性 上 的 , 因 为 没 有 人 要 求 你 正 在 当 别 名 用 的 符 号 必 须 在 你 的 名 字 空 间 里 。 因 此 :local *Here::blue = \$There::green;临 时 为 $There::green 做 了 一 个 叫 $Here::blue 的 别 名 , 但 是 不 要 给 @There:green做 一 个 叫 @Here::blue 的 别 名 , 或 者 给 %There::green 做 一 个 %Here::blue 的 别名 。 幸 运 的 是 , 所 有 这 些 复 杂 的 类 型 团 操 作 都 隐 藏 在 你 不 必 关 心 的 地 方 。 参 阅 第 八 章 的 “ 句柄 参 考 ” 和 “ 符 号 表 参 考 ”, 第 十 章 的 “ 符 号 表 ”, 和 第 十 一 章 , 模 块 , 看 看 更 多 的 关 于 类 型 团的 讨 论 和 重 点 。2.11 输 入 操 作 符这 里 我 们 要 讨 论 几 个 操 作 符 , 因 为 他 们 被 当 作 项 分 析 。 有 时 候 我 们 称 它 们 为 伪 文 本 , 因 为 它们 在 很 多 方 面 象 引 起 的 字 串 。( 象 print 这 样 的 输 出 操 作 符 被 当 作 列 表 操 作 符 分 析 并 将 在第 二 十 九 章 讨 论 。)2.11.1 命 令 输 入 ( 反 勾 号 ) 操 作 符首 先 , 我 们 有 命 令 输 入 操 作 符 , 也 叫 反 勾 号 操 作 符 , 因 为 它 看 起 来 象 这 样 :80


$info = `finger $user`;一 个 用 反 勾 号 ( 技 术 上 叫 重 音 号 ) 引 起 的 字 串 首 先 进 行 变 量 替 换 , 就 象 一 个 双 引 号 引 起 的 字串 一 样 。 得 到 的 结 果 然 后 被 系 统 当 作 一 个 命 令 行 , 而 且 那 个 命 令 的 输 出 成 为 伪 文 本 的 值 。( 这是 一 个 类 似 Unix shell 的 模 块 。) 在 标 量 环 境 里 , 返 回 一 个 包 含 所 有 输 出 的 字 串 。 在 列 表环 境 里 , 返 回 一 列 值 , 每 行 输 出 一 个 值 。( 你 可 以 通 过 设 置 $/ 来 使 用 不 同 的 行 结 束 符 。)每 次 计 算 伪 文 本 的 时 候 , 该 命 令 都 得 以 执 行 。 该 命 令 的 数 字 状 态 值 保 存 在 $?( 参 阅 第 二 十八 章 获 取 $? 的 解 释 , 也 被 称 为 $CHILD_ERROR )。 和 这 条 命 令 的 csh 版 本 不 同 的 是 ,对 返 回 数 据 不 做 任 何 转 换 —— 换 行 符 仍 然 是 换 行 符 。 和 所 有 shell 不 同 的 是 ,<strong>Perl</strong> 里 的 单引 号 不 会 隐 藏 命 令 行 上 的 变 量 , 使 之 避 免 代 换 。 要 给 shell 传 递 一 个 $, 你 必 须 用 反 斜 杠把 它 隐 藏 起 来 。 我 们 上 面 的 finger 例 子 里 的 $user 被 <strong>Perl</strong> 代 换 , 而 不 是 被 shell。( 因为 该 命 令 shell 处 理 , 参 阅 第 二 十 三 章 , 安 全 , 看 看 与 安 全 有 关 的 内 容 。)反 勾 号 的 一 般 形 式 是 qx//( 意 思 是 “ 引 起 的 执 行 ”), 但 这 个 操 作 符 的 作 用 完 全 和 普 通 的 反勾 号 一 样 。 你 只 要 选 择 你 的 引 起 字 符 就 行 了 。 有 一 点 和 引 起 的 伪 函 数 类 似 : 如 果 你 碰 巧 选 择了 单 引 号 做 你 的 分 隔 符 , 那 命 令 行 就 不 会 进 行 双 引 号 代 换 ;$perl_info = qx(ps $$); # 这 里 $$ 是 <strong>Perl</strong> 的 处 理 对 象 $perl_info = qx'ps $$'; #这 里 $$ 是 shell 的 处 理 对 象2.11.2 行 输 入 ( 尖 角 ) 操 作 符最 频 繁 使 用 的 是 行 输 入 操 作 符 , 也 叫 尖 角 操 作 符 或 者 readline 函 数 ( 因 为 那 是 我 们 内 部 的叫 法 )。 计 算 一 个 放 在 尖 括 弧 里 面 的 文 件 句 柄 ( 比 如 STDIN) 将 导 致 从 相 关 的 文 件 句 柄 读取 下 一 行 。( 包 括 新 行 , 所 以 根 据 <strong>Perl</strong> 的 真 值 标 准 , 一 个 新 输 入 的 行 总 是 真 , 直 到 文 件 结束 , 这 时 返 回 一 个 未 定 义 值 , 而 未 定 义 值 习 惯 是 为 假 。) 通 常 , 你 会 把 输 入 值 赋 予 一 个 变 量 ,但 是 有 一 种 情 况 会 发 生 自 动 赋 值 的 现 象 。 当 且 仅 当 行 输 入 操 作 符 是 一 个 while 循 环 的 唯 一一 个 条 件 的 时 候 , 其 值 自 动 赋 予 特 殊 变 量 $_。 然 后 就 对 这 个 赋 值 进 行 测 试 , 看 看 它 是 否 定义 了 。( 这 些 东 西 看 起 来 可 能 有 点 奇 怪 , 但 是 你 会 非 常 频 繁 地 使 用 到 这 个 构 造 , 所 以 值 得 花些 时 间 学 习 。) 因 此 , 下 面 行 是 一 样 的 :while (defined($_ = )) {print $_; }# 最 长 的 方 法while ($_ =


print while $_ = ;print while ;# 明 确 $_# 短 的 语 句 修 改请 记 住 这 样 的 特 殊 技 巧 要 求 一 个 while 循 环 。 如 果 你 在 其 他 的 什 么 地 方 使 用 这 样 的 输 入 操作 符 , 你 必 须 明 确 地 把 结 果 赋 给 变 量 以 保 留 其 值 :while(&& ) { ... }# 错 误 : 两 个 输 入 都 丢 弃if () { print; } # 错 误 : 打 印 $_ 原 先 的 值if ($_=) {PRINT; } # 有 小 问 题 : 没 有 测 试 是 否 定 义if (defined($_=)) { print;}# 最 好当 你 在 一 个 $_ 循 环 里 隐 含 的 给 $_ 赋 值 的 时 候 , 你 赋 值 的 对 象 是 同 名 全 局 变 量 , 而 不 是while 循 环 里 的 那 只 局 部 的 。 你 可 以 用 下 面 方 法 保 护 一 个 现 存 的 $_ 的 值 :while(local $_=) { print; } # 使 用 局 部 $_当 循 环 完 成 后 , 恢 复 到 原 来 的 值 。 不 过 ,$_ 仍 然 是 一 个 全 局 变 量 , 所 以 , 不 管 有 意 无 意 ,从 那 个 循 环 里 调 用 的 函 数 仍 然 能 够 访 问 它 。 当 然 你 也 可 以 避 免 这 些 事 情 的 发 生 , 只 要 定 义 一个 文 本 变 量 就 行 了 :while (my $line = ) { print $line;} # 现 在 是 私 有 的 了( 这 里 的 两 个 while 循 环 仍 然 隐 含 地 进 行 测 试 , 看 赋 值 结 果 是 否 已 定 义 , 因 为 my 和local 并 不 改 变 分 析 器 看 到 的 赋 值 。) 文 件 句 柄 STDIN,STDOUT, 和 STDERR 都 是 预定 义 和 预 先 打 开 的 。 额 外 的 文 件 句 柄 可 以 用 open 或 sysopen 函 数 创 建 。 参 阅 第 二 十 九章 里 面 那 些 函 数 的 文 档 获 取 详 细 信 息 。在 上 面 的 while 循 环 里 , 我 们 是 在 一 个 标 量 环 境 里 计 算 行 输 入 操 作 符 , 所 以 该 操 作 符 分 别返 回 每 一 行 。 不 过 , 如 果 你 在 一 个 列 表 环 境 里 使 用 这 个 操 作 符 , 则 返 回 一 个 包 括 所 有 其 余 输入 行 的 列 表 , 每 个 列 表 元 素 一 行 。 用 这 个 方 法 你 会 很 容 易 就 使 用 一 个 很 大 的 数 据 空 间 , 所 以一 定 要 小 心 使 用 这 个 特 性 :$one_line =; # 获 取 第 一 行 $all_lines =; # 获 取 文 件 其 余 部 分 。没 有 哪 种 while 处 理 和 输 入 操 作 符 的 列 表 形 式 相 关 联 , 因 为 while 循 环 的 条 件 总 是 提 供一 个 标 量 环 境 ( 就 象 在 任 何 其 他 条 件 语 句 里 一 样 )。在 一 个 尖 角 操 作 符 里 面 使 用 空 (null) 文 件 句 柄 是 一 种 特 殊 用 法 ; 它 仿 真 典 型 的 Unix 的 命令 行 过 滤 程 序 ( 象 sed 和 awk) 的 特 性 。 当 你 从 一 个 读 取 数 据 行 的 时 候 , 它 会 魔 术82


般 的 把 所 有 你 在 命 令 行 上 提 到 的 所 有 文 件 的 所 有 数 据 行 都 交 给 你 。 如 果 你 没 有 ( 在 命 令 行 )上 声 明 文 件 , 它 就 把 标 准 输 入 交 给 你 , 这 样 你 的 程 序 就 可 以 很 容 易 地 插 入 到 一 个 管 道 或 者 一个 进 程 中 。下 面 是 其 工 作 原 理 的 说 明 : 当 第 一 次 计 算 时 , 先 检 查 @ARGV 数 组 , 如 果 它 是 空(null), 则 $ARGV[0] 设 置 为 “-”, 这 样 当 你 打 开 它 的 时 候 就 是 标 准 输 入 。 然 后 @ARGV数 组 被 当 作 一 个 文 件 名 列 表 处 理 。 更 明 确 地 说 , 下 面 循 环 :while () { ... # 处 理 每 行 的 代 码 }等 效 于 下 面 的 类 <strong>Perl</strong> 的 伪 代 码 :@ARGV = ('-') unless @ARGV; # 若 为 空 则 假 设 为 STDINwhile( @ARGV) {$ARGV = shift @ARGV;# 每 次 缩 短 @ARGVif( !open(ARGV, $ARGV)) {warn "Can't open $ARGV: $!\n";next;}while () {... # 处 理 每 行 的 代 码}}第 一 段 代 码 除 了 没 有 那 么 唠 叨 以 外 , 实 际 上 是 一 样 的 。 它 实 际 上 也 移 动 @ARGV, 然 后 把当 前 文 件 名 放 到 全 局 变 量 $ARGV 里 面 。 它 也 在 内 部 使 用 了 特 殊 的 文 件 句 柄ARGV—— 只 是 更 明 确 的 写 法 ( 也 是 一 个 特 殊 文 件 句 柄 ) 的 一 个 同 义 词 ,( 上面 的 伪 代 码 不 能 运 行 , 因 为 它 把 当 作 一 个 普 通 句 柄 使 用 。)你 可 以 在 第 一 个 语 句 之 前 修 改 @ARGV, 直 到 数 组 最 后 包 含 你 真 正 需 要 的 文 件 名 列 表为 止 。 因 为 <strong>Perl</strong> 在 这 里 使 用 普 通 的 open 函 数 , 所 以 如 果 碰 到 一 个 “-” 的 文 件 名 , 就 会 把它 当 作 标 准 输 入 , 而 其 他 更 深 奥 的 open 特 性 是 是 <strong>Perl</strong> 自 动 提 供 给 你 的 ( 比 如 打 开 一 个名 字 是 “gzip -dc


大 文 件 一 样 。( 不 过 你 可 以 重 置 行 号 , 参 阅 第 二 十 九 章 看 看 当 到 了 eof 时 怎 样 为 每 个 文 件重 置 行 号 。)如 果 你 想 把 @ARGV 设 置 为 你 自 己 的 文 件 列 表 , 直 接 用 :# 如 果 没 有 给 出 args 则 缺 省 为 README @ARGV = ("README") unless @ARGV;如 果 你 想 给 你 的 脚 本 传 递 开 关 , 你 可 以 用 Getopt::* 模 块 或 者 在 开 头 放 一 个 下 面 这 样 的 循环 :while( @ARGV and $ARGV[0] =~ /^-/) {$_ = shift;last if /^--$/;if (/^-D(.*)/) {$debug = $1 }if (/^-v/){ $verbose++}... # 其 他 开 关}while(){... # 处 理 每 行 的 代 码}符 号 将 只 会 返 回 一 次 假 。 如 果 从 这 ( 返 回 假 ) 以 后 你 再 次 调 用 它 , 它 就 假 设 你 正 在 处理 另 外 一 个 @ARGV 列 表 , 如 果 你 没 有 设 置 @ARGV, 它 会 从 STDIN 里 输 入 。如 果 尖 括 弧 里 面 的 字 串 是 一 个 标 量 变 量 ( 比 如 ,), 那 么 该 变 量 包 含 一 个 间 接 文 件句 柄 , 不 是 你 准 备 从 中 获 取 输 入 的 文 件 句 柄 的 名 字 就 是 一 个 这 样 的 文 件 句 柄 的 引 用 。 比 如 :$fh = \*STDIN; $line = ;或 :open($fh, "


你 可 能 会 问 : 如 果 我 们 在 尖 角 操 作 符 里 放 上 一 些 更 有 趣 的 东 西 , 行 输 入 操 作 符 会 变 成 什 么呢 ? 答 案 是 它 会 变 异 成 不 同 的 操 作 符 。 如 果 在 尖 角 操 作 符 里 面 的 字 串 不 是 文 件 句 柄 名 或 标 量变 量 ( 甚 至 只 是 多 了 一 个 空 格 ), 它 就 会 被 解 释 成 一 个 要 “ 聚 集 ”( 注 : 文 件 团 和 前 面 提 到的 类 型 团 毫 无 关 系 , 除 了 它 们 都 把 * 字 符 用 于 通 配 符 模 式 以 外 。 当 用 做 通 配 符 用 途 时 , 字符 * 有 “ 聚 集 ”(glob) 的 别 名 。 对 于 类 型 团 而 言 , 它 是 聚 集 符 号 表 里 相 同 名 字 的 符 号 。 对于 文 件 团 而 言 , 它 在 一 个 目 录 里 做 通 配 符 匹 配 , 就 象 各 种 shell 做 的 一 样 。) 的 文 件 名 模式 。 这 里 的 文 件 名 模 式 与 当 前 目 录 里 的 ( 或 者 作 为 文 件 团 模 式 的 一 部 分 直 接 声 明 的 目 录 ) 文件 名 进 行 匹 配 , 并 且 匹 配 的 文 件 名 被 该 操 作 符 返 回 。 对 于 行 输 入 而 言 , 在 标 量 环 境 里 每 次 返回 一 个 名 字 , 而 在 列 表 环 境 里 则 是 一 起 返 回 。 后 面 一 种 用 法 更 常 见 ; 你 常 看 到 这 样 的 东 西 :@files = ;和 其 他 伪 文 本 一 样 , 首 先 进 行 一 层 的 变 量 代 换 , 不 过 你 不 能 说 , 因 为 我 们 前 面 已经 解 释 过 , 那 是 一 种 间 接 文 件 句 柄 。 在 老 版 本 的 <strong>Perl</strong> 里 , 程 序 员 可 以 用 插 入 花 括 弧 的 方 法来 强 制 它 解 释 成 文 件 团 :。 现 在 , 我 们 认 为 把 它 当 作 内 部 函 数 glob($foo) 调用 更 为 清 晰 , 这 么 做 也 可 能 是 在 第 一 时 间 进 行 干 预 的 正 确 方 法 。 所 以 , 如 果 你 不 想 重 载 尖 角操 作 符 ( 你 可 以 这 么 干 。) 你 可 以 这 么 写 :@files = glob("*.xml");不 管 你 用 glob 函 数 还 是 老 式 的 尖 括 弧 形 式 , 文 件 团 操 作 符 还 是 会 象 行 输 入 操 作 符 那 样 做while 特 殊 处 理 , 把 结 果 赋 予 $_。( 也 是 在 第 一 时 间 重 载 尖 角 操 作 符 的 基 本 原 理 。) 比 如 ,如 果 你 想 修 改 你 的 所 有 C 源 代 码 文 件 的 权 限 , 你 可 以 说 :while (glob "*.c") { chmod 0644, $_; }等 效 于 :while () { chmod 0644,$_; }最 初 glob 函 数 在 老 的 <strong>Perl</strong> 版 本 里 是 作 为 一 个 shell 命 令 实 现 的 ( 甚 至 在 旧 版 的 Unix里 也 一 样 ), 这 意 味 着 运 行 它 开 销 相 当 大 , 而 且 , 更 糟 的 是 它 不 是 在 所 有 地 方 运 行 得 都 一 样 。现 在 它 是 一 个 内 建 的 函 数 , 因 此 更 可 靠 并 且 快 多 了 。 参 阅 第 三 十 二 章 里 的 File:Glob 模 块的 描 述 获 取 如 何 修 改 这 个 操 作 符 的 缺 省 特 性 的 信 息 , 比 如 如 何 让 它 把 操 作 数 ( 参 数 ) 里 面 的空 白 当 作 路 径 名 分 隔 符 , 是 否 扩 展 发 音 符 或 花 括 弧 , 是 否 大 小 写 敏 感 和 是 否 对 返 回 值 排 序 等等 。当 然 , 处 理 上 面 chmod 命 令 的 最 短 的 和 可 能 最 易 读 的 方 法 是 把 文 件 团 当 作 一 个 列 表 操 作符 处 理 :chmod 0644, ;85


文 件 团 只 有 在 开 始 ( 处 理 ) 一 个 新 列 表 的 时 候 才 计 算 它 ( 内 嵌 ) 的 操 作 数 。 所 有 数 值 必 须 在该 操 作 符 开 始 处 理 之 前 读 取 。 这 在 列 表 环 境 里 不 算 什 么 问 题 , 因 为 你 自 动 获 取 全 部 数 值 。 不过 , 在 标 量 环 境 里 时 , 每 次 调 用 操 作 符 都 返 回 下 一 个 值 , 或 者 当 你 的 数 值 用 光 后 返 回 一 个 假值 。 同 样 , 假 值 只 会 返 回 一 次 。 所 以 如 果 你 预 期 从 文 件 团 里 获 取 单 个 数 值 , 好 些 的 方 法 是 :($file) = ; # 列 表 环 境上 面 的 方 法 要 比 :$fiole = ; # 标 量 环 境好 , 因 为 前 者 返 回 所 有 匹 配 的 文 件 名 并 重 置 该 操 作 符 , 而 后 者 要 么 返 回 文 件 名 , 要 么 返 回 假 。如 果 你 准 备 使 用 变 量 代 换 功 能 , 那 么 使 用 glob 操 作 符 绝 对 比 使 用 老 式 表 示 法 要 好 , 因 为老 方 法 会 导 致 与 间 接 文 件 句 柄 的 混 淆 。 这 也 是 为 什 么 说 项 和 操 作 符 之 间 的 边 界 线 有 些 模 糊 的原 因 :@files = ; # 能 用 , 不 过 应 该 避 免 这 么 用 。 @files = glob("dir/*.[ch]");# 把 glob 当 函 数 用 。 @files = glob $some_pattern; # 把 glob 当 操 作 符 用 。我 们 在 最 后 一 个 例 子 里 把 圆 括 弧 去 掉 是 为 了 表 明 glob 可 以 作 为 函 数 ( 一 个 项 ) 使 用 或 者是 一 个 单 目 操 作 符 用 ; 也 就 是 说 , 一 个 接 受 一 个 参 数 的 前 缀 操 作 符 。glob 操 作 符 是 一 个 命名 的 单 目 操 作 符 的 例 子 ; 是 我 们 下 一 章 将 要 谈 到 的 操 作 符 。 稍 后 , 我 们 将 谈 谈 模 式 匹 配 操 作符 , 它 也 是 分 析 起 来 类 似 项 , 而 作 用 象 操 作 符 。第 三 章 单 目 和 双 目 操 作 符在 上 面 一 章 里 , 我 们 讲 了 各 种 你 可 能 在 表 达 式 里 用 到 的 项 , 不 过 老 实 说 , 把 项 隔 离 出 来 让 人觉 得 有 点 无 聊 。 因 为 许 多 项 都 是 群 居 动 物 。 它 们 相 互 之 间 有 某 种 关 系 。 年 轻 的 项 急 于 以 各 种方 式 表 现 自 己 并 影 响 其 它 项 , 而 且 还 存 在 不 同 类 型 的 社 会 关 系 和 许 多 不 同 层 次 的 义 务 。 在<strong>Perl</strong> 里 , 这 种 关 系 是 用 操 作 符 来 表 现 的 。社 会 学 必 须 对 某 些 事 物 有 利 。从 数 学 的 角 度 来 看 , 操 作 符 只 是 带 着 特 殊 语 法 的 普 通 函 数 。 从 语 言 学 的 角 度 来 说 , 操 作 符 只是 不 规 则 动 词 。 不 过 , 几 乎 任 何 语 言 都 会 告 诉 你 , 在 一 种 语 言 里 的 不 规 则 动 词 很 可 能 是 你 最86


常 用 的 语 素 。 而 从 信 息 理 论 的 角 度 来 看 , 这 一 点 非 常 重 要 , 因 为 不 规 则 动 词 不 管 是 在 使 用 中还 是 识 别 上 都 比 较 短 而 且 更 有 效 。从 实 用 角 度 出 发 , 操 作 符 非 常 易 用 。根 据 操 作 符 的 元 数 ( 它 们 操 作 数 的 个 数 ) 的 不 同 , 它 们 的 优 先 级 ( 它 们 从 周 围 的 操 作 符 手 中夺 取 操 作 数 的 难 易 ) 的 不 同 , 它 们 的 结 合 性 ( 当 与 同 优 先 级 的 操 作 符 相 联 时 , 它 们 是 从 左 到右 处 理 还 是 从 右 到 左 处 理 。) 的 不 同 , 操 作 符 可 以 分 成 各 种 各 样 类 型 。<strong>Perl</strong> 的 操 作 符 有 三 种 元 数 : 单 目 , 双 目 和 三 目 。 单 目 操 作 符 总 是 前 缀 操 作 符 ( 除 自 增 和 自减 操 作 符 以 外 )。( 注 : 你 当 然 可 以 认 为 各 种 各 样 的 引 号 和 括 弧 是 项 与 项 之 间 分 隔 的 环 缀 操作 符 。) 其 他 的 都 是 中 缀 操 作 符 —— 除 非 你 把 列 表 操 作 符 也 算 进 来 , 它 可 以 做 任 意 数 量 参 数的 前 缀 。 不 过 大 多 数 人 认 为 列 表 操 作 符 只 是 一 种 普 通 的 函 数 , 只 不 过 你 可 以 不 为 它 写 括 弧 而已 。 下 面 是 一 些 例 子 :! $x # 一 个 单 目 操 作 符$x * $y # 一 个 双 目 操 作 符$x ? $y : $z # 一 个 三 目 操 作 符print $x, $y, $z# 一 个 列 表 操 作 符操 作 符 的 优 先 级 控 制 它 绑 定 的 松 紧 度 。 高 优 先 级 的 操 作 符 先 于 低 优 先 级 的 操 作 符 攫 取 它 们 周围 的 参 数 。 优 先 级 的 原 理 可 以 直 接 在 基 本 数 学 里 面 找 到 , 在 数 学 里 , 乘 法 比 加 法 优 先 级 高 :1. + 3 * 4 # 生 成 14 而 不 是 20两 个 同 等 优 先 级 的 操 作 符 在 一 起 的 时 候 , 它 们 的 执 行 顺 序 取 决 于 它 们 的 结 合 性 。 这 些 规 则 在某 种 程 度 上 仍 然 遵 循 数 学 习 惯 :2 * 3 * 4 # 意 味 着 (2*3)*4, 左 结 合2 ** 3 ** 4 # 意 味 着 2**(3**4), 右 结 合2 != 3 != 4 # 非 法 , 不 能 结 合表 3-1 列 出 了 从 高 优 先 级 到 低 优 先 级 的 <strong>Perl</strong> 操 作 符 , 以 及 它 们 的 结 合 性 和 元 数 。表 3-1。 操 作 符 优 先 级结 合 性 元 数优 先 级 表无 0 项 , 和 列 表 操 作 符 ( 左 侧 )87


左 2 ->无 1 ++ --右 2 **右 1 !~&gt 和 单 目 + 和 -左 2 =~ !~左2 * / % x左 2 + - .左 2 >右无无左左左左0,1 命 名 单 目 操 作 符2 < > = lt gt le ge2 = eq ne cmp2 &2 | ……2 &&2 ||无 2 .. ...右 3 ?:右2 + += -+ *= 等 等左 2 , =>右 0+ 列 表 操 作 符 ( 右 侧 )右左左1 not2 and2 or xor看 起 来 好 象 要 记 很 多 的 优 先 级 级 别 。 不 错 , 的 确 很 多 。 幸 运 的 是 , 有 两 件 事 情 可 以 帮 助 你 。首 先 , 这 里 定 义 的 优 先 级 级 别 通 常 遵 循 你 的 直 觉 ( 前 提 是 你 没 得 精 神 病 )。 第 二 , 如 果 你 得了 精 神 病 , 那 你 总 还 是 可 以 放 上 额 外 的 圆 括 弧 以 减 轻 你 的 疑 虑 。另 外 一 个 可 以 帮 助 你 的 线 索 是 , 任 何 从 C 里 借 来 的 操 作 符 相 互 之 间 仍 然 保 留 相 同 的 优 先 级关 系 , 尽 管 C 的 优 先 级 有 点 怪 。( 这 就 让 那 些 C 和 C++ 的 爱 好 者 , 甚 至 还 包 括 JAVA的 爱 好 者 学 习 起 <strong>Perl</strong> 来 会 更 容 易 些 。)随 后 的 各 节 按 照 优 先 级 顺 序 讲 述 这 些 操 作 符 。 只 有 极 少 数 例 外 , 所 有 这 样 的 操 作 符 都 只 处 理标 量 值 , 而 不 处 理 列 表 值 。 我 们 会 在 轮 到 它 们 出 现 的 时 候 提 到 这 一 点 。88


尽 管 引 用 是 标 量 值 , 但 是 在 引 用 上 使 用 大 多 数 操 作 符 没 有 什 么 意 义 , 因 为 一 个 数 字 值 的 引 用只 是 在 <strong>Perl</strong> 内 部 才 有 意 义 。 当 然 , 如 果 一 个 引 用 指 向 一 个 允 许 重 载 的 类 里 的 一 个 对 象 , 你就 可 以 在 这 样 的 对 象 上 调 用 这 些 操 作 符 , 并 且 如 果 该 类 为 那 种 特 定 操 作 符 定 义 了 重 载 , 那 它也 会 定 义 那 个 操 作 符 应 该 如 何 处 理 该 对 象 。 比 如 , 复 数 在 <strong>Perl</strong> 里 就 是 这 么 实 现 的 。 有 关 重载 的 更 多 内 容 , 请 参 考 第 十 三 章 , 重 载 。3.1 项 和 列 表 操 作 符 ( 左 向 )在 <strong>Perl</strong> 里 , 项 的 优 先 级 最 高 。 项 包 括 变 量 , 引 起 和 类 似 引 起 的 操 作 符 、 大 多 数 圆 括 弧 ( 或者 方 括 弧 或 大 括 弧 ) 内 的 表 达 式 , 以 及 所 有 其 参 数 被 圆 括 弧 包 围 的 函 数 。 实 际 上 ,<strong>Perl</strong> 里没 有 这 种 意 义 上 的 函 数 , 只 有 列 表 操 作 符 和 单 目 操 作 符 会 表 现 得 象 函 数 —— 当 你 在 它 们 的 参数 周 围 放 上 圆 括 弧 的 时 候 。 不 管 怎 样 , 第 二 十 九 章 的 名 称 是 函 数 。现 在 请 注 意 听 了 。 下 面 有 几 条 非 常 重 要 的 规 则 , 它 们 能 大 大 简 化 事 情 的 处 理 , 但 是 如 果 你 粗心 地 话 , 偶 尔 会 产 生 不 那 么 直 观 的 结 果 。 如 果 有 哪 个 列 表 操 作 符 ( 如 print) 或 者 哪 个 命 名单 目 操 作 符 ( 比 如 chdir) 后 面 跟 着 左 圆 括 弧 做 为 下 一 个 记 号 ( 忽 略 空 白 ), 那 么 该 操 作 符和 它 的 用 圆 括 弧 包 围 的 参 数 就 获 得 最 高 优 先 级 , 就 好 象 它 是 一 个 普 通 的 函 数 调 用 一 样 。 规 则是 : 如 果 看 上 去 象 函 数 调 用 , 它 就 是 函 数 调 用 。 你 可 以 把 它 做 得 不 象 函 数 —— 在 圆 括 弧 前 面加 一 个 单 目 加 号 操 作 符 即 可 ,( 从 语 意 上 来 说 , 这 个 加 号 什 么 都 没 干 , 它 甚 至 连 参 数 是 否 数字 都 不 关 心 )。例 如 , 因 为 || 比 chdir 的 优 先 级 低 , 我 们 有 :chdir $foo || die;chdir ($foo) || die;chdir ($foo) || die;# (chdir $foo) || die# (chdir $foo) || die# (chdir $foo) || diechdir +($foo) || die;# (chdir $foo) || die不 过 , 因 为 * 的 优 先 级 比 chdir 高 , 我 们 有 :chdir $foo * 20; # chdir ($foo * 20)chdir ($foo) * 20; # (chdir $foo) * 20chdir ($foo) * 20; # (chidir $foo) * 20chdir +($foo) * 20 # chdir ($foo * 20 )这 种 情 况 对 任 何 命 名 单 目 操 作 符 的 数 字 操 作 符 同 样 有 效 , 比 如 rand:89


and 10 * 20; # rand (10 * 20)rand(10) * 20; # (rand 10) * 20rand (10) * 20; # (rand 10) * 20rand +(10) * 20; # rand (10 * 20)当 缺 少 圆 括 弧 时 , 象 print,sort 或 chmod 这 样 的 列 表 操 作 符 的 优 先 级 要 么 非 常 高 , 要么 非 常 低 —— 取 决 于 你 是 向 操 作 符 左 边 还 是 右 边 看 。( 这 也 是 为 什 么 我 们 在 本 节 标 题 上 有“ 左 向 ” 字 样 的 原 因 。) 比 如 , 在 :@ary = (1, 3, sort 4, 2);print @ary; # 打 印 1324在 sort 右 边 的 逗 号 先 于 sort 计 算 , 而 在 其 左 边 的 后 其 计 算 。 换 句 话 说 , 一 个 列 表 操 作 符试 图 收 集 它 后 面 所 有 的 参 数 , 然 后 当 做 一 个 简 单 的 项 和 它 前 面 的 表 达 式 放 在 一 起 。 但 你 还 是要 注 意 圆 括 弧 的 使 用 :# 这 些 在 进 行 print 前 退 出print($foo, exit); # 显 然 不 是 你 想 要 的 。print $foo, exit;# 也 不 是 这 个# 这 些 在 退 出 前 打 印 :(print $foo), exit; # 这 个 是 你 要 的 。print ($foo), exit; # 或 者 这 个 。print ($foo), exit; # 这 个 也 行 。最 容 易 出 错 的 地 方 是 你 用 圆 括 弧 把 数 学 参 数 组 合 起 来 的 时 候 , 但 是 你 却 忘 记 了 圆 括 弧 同 时 用于 组 合 函 数 参 数 :print ($foo & 255) + 1, "\n"; # 打 印 ($foo & 255)( 译 注 : 这 里 print ($foo & 255) 构 成 函 数 , 函 数 是 一 个 项 , 项 的 优 先 级 最 高 , 因 而 先执 行 .)90


这 句 话 可 能 和 你 一 开 始 想 的 结 果 不 同 。 好 在 这 样 的 错 误 通 常 会 生 成 类 似 "Useless use ofaddition in a void context" 这 样 的 警 告 —— 如 果 你 打 开 了 警 告 。同 样 当 作 项 分 析 的 构 造 还 有 do {} 和 eval {} 以 及 子 过 程 和 方 法 调 用 , 匿 名 数 组 和 散 列构 造 符 [] 和 {}, 还 有 匿 名 子 过 程 构 造 符 {}。3.2 箭 头 操 作 符和 C 和 C++ 类 似 , 双 目 操 作 符 -> 是 一 个 中 缀 解 引 用 操 作 符 。 如 果 右 边 是 一 个 [...] 数组 下 标 、 一 个 {...} 散 列 下 标 、 或 者 一 个 (...) 子 过 程 参 数 列 表 , 那 么 左 边 必 须 是 一 个 对应 的 数 组 、 散 列 、 或 者 子 过 程 的 应 用 ( 硬 引 用 或 符 号 引 用 都 行 )。 在 一 个 左 值 ( 可 赋 值 ) 环境 里 , 如 果 左 边 不 是 一 个 引 用 , 那 它 必 须 是 一 个 能 够 保 存 硬 引 用 的 位 置 , 这 种 情 况 下 这 种 引用 会 为 你 自 动 激 活 。 有 关 这 方 面 的 更 多 的 信 息 ( 以 及 关 于 故 意 自 激 活 的 一 些 警 告 信 息 ), 请参 阅 第 八 章 , 引 用 。$aref-&gt;[42]# 一 个 数 组 解 引 用$href-&gt;{"corned beff"}# 一 个 散 列 解 引 用$sref-&gt;(1,2,3)# 一 个 子 过 程 解 引 用要 不 然 , 它 就 是 某 种 类 型 的 方 法 调 用 。 右 边 必 须 是 一 个 方 法 名 ( 或 者 一 个 包 含 该 方 法 名 的 简单 标 量 变 量 ), 而 且 左 边 必 须 得 出 一 个 对 象 名 ( 一 个 已 赐 福 引 用 ) 或 者 一 个 类 的 名 字 ( 也 就是 说 , 一 个 包 名 字 ):$yogi = Bear->new("Yogi"); # 一 个 类 方 法 调 用 $yogi->swipe($picnic); # 一 个 对象 方 法 调 用方 法 名 可 以 用 一 个 包 名 修 饰 以 标 明 在 哪 个 包 里 开 始 搜 索 该 方 法 , 或 者 带 着 特 殊 包 名 字 ,SUPER::, 以 表 示 搜 索 应 该 从 父 类 开 始 。 参 阅 第 十 二 章 , 对 象 。3.3 自 增 和 自 减 操 作 符++ 和 -- 操 作 符 的 功 能 和 C 里 面 一 样 。 就 是 说 , 当 把 它 们 放 在 一 个 变 量 前 面 时 , 它 们 在返 回 变 量 值 之 前 增 加 或 者 减 少 变 量 值 , 当 放 在 变 量 后 面 时 , 它 们 在 返 回 变 量 值 后 再 对 其 加 一或 减 一 。 比 如 ,$a++ 把 标 量 变 量 $a 的 值 加 一 , 在 它 执 行 增 加 之 前 返 回 它 的 值 。 类 似 地 ,--$b{(/(\w+)/)[0]} 把 散 列 %b 里 用 缺 省 的 搜 索 变 量 ($_) 里 的 第 一 个 “ 单 词 ” 索 引 的元 素 先 减 一 , 然 后 返 回 。( 注 : 哦 , 这 儿 可 能 有 点 不 公 平 , 因 为 好 多 东 西 你 还 不 知 道 。 我 们只 是 想 让 你 专 心 。 该 表 达 式 的 工 作 过 程 是 这 样 的 : 首 先 , 模 式 匹 配 用 表 达 式 \w+ 在 $_ 里找 第 一 个 单 词 。 它 周 围 的 圆 括 弧 确 保 此 单 词 作 为 单 元 素 列 表 值 返 回 , 因 为 该 模 式 匹 配 是 在 列91


表 环 境 里 进 行 的 。 这 个 列 表 环 境 是 由 列 表 片 段 操 作 符 ,(...)[0] 提 供 的 , 它 返 回 列 表 的 第 一个 ( 也 是 唯 一 一 个 ) 元 素 。 该 值 用 做 散 列 的 键 字 , 然 后 散 列 记 录 ( 值 ) 被 判 断 并 返 回 。 通 常 ,如 果 碰 到 一 个 复 杂 的 表 达 式 , 你 可 以 从 内 向 外 地 分 析 它 并 找 出 事 情 发 生 的 顺 序 。)自 增 操 作 符 有 一 点 额 外 的 内 建 处 理 。 如 果 你 增 加 的 变 量 是 一 个 数 字 , 或 者 该 变 量 在 一 个 数 字环 境 里 使 用 , 你 得 到 正 常 自 增 的 功 能 。 不 过 , 如 果 该 变 量 从 来 都 是 在 字 串 环 境 里 使 用 , 而 且值 为 非 空 , 还 匹 配 模 式 /^[a-zA-z]*[0-9]*$/, 这 时 自 增 是 以 字 串 方 式 进 行 的 , 每 个 字 符都 保 留 在 其 范 围 之 内 , 同 时 还 会 进 位 :print ++($foo = '99');print ++($foo = 'a0');print ++($foo = 'Az');print ++($foo = 'zz');# 打 印 '100'# 打 印 'a1'# 打 印 'Ba'# 打 印 'aaa'在 我 们 写 这 些 的 时 候 , 自 增 的 额 外 处 理 还 没 有 扩 展 到 Unicode 字 符 和 数 字 , 不 过 将 来 也 许会 的 。不 过 自 减 操 作 符 没 有 额 外 处 理 , 我 们 也 没 有 准 备 给 它 增 加 这 个 处 理 。3.4 指 数 运 算双 目 ** 是 指 数 操 作 符 。 请 注 意 它 甚 至 比 单 目 操 作 符 的 绑 定 更 严 格 , 所 以 -2**4 是-(2**4), 不 是 (-2)**4。 这 个 操 作 符 是 用 C 的 pow(3) 函 数 实 现 的 , 该 函 数 在 内 部 以浮 点 数 模 式 运 转 。 它 用 对 数 运 算 进 行 计 算 , 这 就 意 味 着 它 可 以 处 理 小 数 指 数 , 不 过 有 时 候 你得 到 的 结 果 不 如 直 接 用 乘 法 得 出 的 准 确 。3.5 表 意 单 目 操 作 符大 多 数 单 目 操 作 符 只 有 名 字 ( 参 阅 本 章 稍 后 的 “ 命 名 的 单 目 和 文 件 测 试 操 作 符 ”), 不 过 , 有些 操 作 符 被 认 为 比 较 重 要 , 所 以 赋 予 它 们 自 己 的 特 殊 符 号 。 所 有 这 类 操 作 符 好 象 都 和 否 定 操作 有 关 。 骂 数 学 家 去 。单 目 ! 执 行 逻 辑 否 , 就 是 说 ,“not”。 参 阅 not 看 看 一 个 在 优 先 级 中 级 别 较 低 的 逻 辑 否 。如 果 操 作 数 为 假 ( 数 字 零 , 字 串 "0", 空 字 串 或 未 定 义 ), 则 对 操 作 数 取 否 , 值 为 真 (1),若 操 作 数 为 真 , 则 值 为 假 (“”)。如 果 操 作 数 是 数 字 , 单 目 - 执 行 数 学 取 负 。 如 果 操 作 数 是 一 个 标 识 , 则 返 回 一 个 由 负 号 和标 识 符 连 接 在 一 起 的 字 串 。 否 则 , 如 果 字 串 以 正 号 或 负 号 开 头 , 则 返 回 以 相 反 符 号 开 头 的 字92


串 。 这 些 规 则 的 一 个 效 果 是 -bareword 等 于 "-bareword"。 这 个 东 西 对 Tk 程 序 员 很 有用 。单 目 ~ 操 作 符 进 行 按 位 求 反 , 也 就 是 1 的 补 数 。 从 定 义 上 来 看 , 这 个 是 有 点 不 可 移 植 的东 西 , 因 为 它 受 限 于 你 的 机 器 。 比 如 , 在 一 台 32 位 机 器 上 ,~123 是 4294967172,而 在 一 台 64 位 的 机 器 上 , 它 是 18446744073709551493。 不 过 你 早 就 知 道 这 个 了 。你 可 能 还 不 知 道 的 是 , 如 果 ~ 的 参 数 是 字 串 而 不 是 数 字 , 则 返 回 等 长 字 串 , 但 是 字 串 的 所有 位 都 是 互 补 的 。 这 是 同 时 翻 转 所 有 位 的 最 快 的 方 法 , 而 且 它 还 是 可 移 植 的 翻 转 位 的 方 法 ,因 为 它 不 依 靠 你 的 机 器 的 字 大 小 。 稍 后 我 们 将 谈 到 按 位 逻 辑 操 作 符 , 它 也 有 一 个 面 向 字 串 的变 体 。单 目 + 没 有 任 何 语 义 效 果 , 即 使 对 字 串 也 一 样 。 它 在 语 法 上 用 于 把 函 数 名 和 一 个 圆 括 弧 表达 式 分 隔 开 , 否 则 它 们 会 被 解 释 成 一 个 一 体 的 函 数 参 数 。( 参 阅 “ 项 和 列 表 操 作 符 ” 的 例 子 。)如 果 你 向 它 的 一 边 进 行 考 虑 ,+ 取 消 了 圆 括 弧 把 前 缀 操 作 符 变 成 函 数 的 作 用 。单 目 操 作 符 \ 给 它 后 面 的 东 西 创 建 一 个 引 用 。 在 一 个 列 表 上 使 用 时 , 它 创 建 一 列 引 用 。 参阅 第 八 章 中 的 “ 反 斜 杠 操 作 符 ” 获 取 详 细 信 息 。 不 要 把 这 个 性 质 和 字 串 里 的 反 斜 杠 的 作 用 混 淆了 , 虽 然 两 者 都 有 防 止 下 一 个 东 西 被 转 换 的 模 糊 的 含 义 。 当 然 这 个 相 似 也 并 不 是 完 全 偶 然 的 。3.6 绑 定 操 作 符双 目 =~ 把 一 个 字 串 和 一 个 模 式 匹 配 、 替 换 或 者 转 换 绑 定 在 一 起 。 要 不 然 这 些 操 作 会 搜 索或 修 改 包 含 在 $_( 缺 省 变 量 ) 里 面 的 字 串 。 你 想 绑 定 的 字 串 放 在 左 边 , 而 操 作 符 本 身 放 在右 边 。 返 回 值 标 识 右 边 的 操 作 符 的 成 功 或 者 失 败 , 因 为 绑 定 操 作 符 本 身 实 际 上 不 做 任 何 事 情 。如 果 右 边 的 参 数 是 一 个 表 达 式 而 不 是 模 式 匹 配 、 子 过 程 或 者 转 换 , 那 运 行 时 该 表 达 式 会 被 解释 成 一 个 搜 索 模 式 。 也 就 是 说 ,$_ =~ $pat 等 效 于 $_ =~ /$pat/。 这 样 做 要 比 明 确 搜索 效 率 低 , 因 为 每 次 计 算 完 表 达 式 后 都 必 须 检 查 模 式 以 及 可 能 还 要 重 新 编 译 模 式 。 你 可 以 通过 使 用 qr//( 引 起 正 则 表 达 式 ) 操 作 符 预 编 译 最 初 的 模 式 的 方 法 来 避 免 重 新 编 译 。双 目 !~ 类 似 =~ 操 作 符 , 只 是 返 回 值 是 =~ 的 对 应 返 回 值 的 逻 辑 非 。 下 面 的 表 达 式 功能 上 是 完 全 一 样 的 :$string !~ /pattern/not $string =~ /pattern/我 们 说 返 回 值 标 识 成 功 , 但 是 有 许 多 种 成 功 。 替 换 返 回 成 功 替 换 的 数 量 , 转 换 也 一 样 。( 实际 上 , 转 换 操 作 符 常 用 于 字 符 计 数 。) 因 为 任 何 非 零 值 都 是 真 , 所 以 所 有 的 都 对 。 最 吸 引 人93


的 真 值 类 型 是 模 式 的 列 表 赋 值 : 在 列 表 环 境 下 , 模 式 匹 配 可 以 返 回 和 模 式 里 圆 括 弧 相 匹 配 的子 字 串 。 不 过 , 根 据 列 表 赋 值 的 规 则 , 如 果 有 任 何 东 西 匹 配 并 且 赋 了 值 , 列 表 赋 值 本 身 将 返回 真 , 否 则 返 回 假 。 因 此 , 有 时 候 你 会 看 到 这 样 的 东 西 :if( ($k, $v) = $string =~ m/(\w+)=(\w*)/) {print "KEY $k VALUE $v\n";}让 我 们 分 解 这 个 例 子 。 =~ 的 优 先 级 比 = 高 , 因 此 首 先 计 算 =~。 =~ 把 字 串 $string绑 定 与 右 边 的 模 式 进 行 匹 配 , 右 边 是 扫 描 你 的 字 串 里 看 起 来 象 KEY=VALUES 这 样 的 东西 。 这 是 在 列 表 环 境 里 , 因 为 它 是 在 一 个 列 表 赋 值 的 右 边 。 如 果 匹 配 了 模 式 , 它 返 回 一 个 列表 并 赋 值 给 $k 和 $v。 列 表 赋 值 本 身 是 在 标 量 环 境 , 所 以 它 返 回 2-- 赋 值 语 句 右 边 的数 值 的 个 数 。 而 2 正 好 又 是 真 —— 因 为 标 量 环 境 也 是 一 个 布 尔 环 境 。 当 匹 配 失 败 , 没 有 赋值 发 生 , 则 返 回 零 , 是 假 。关 于 模 式 规 则 的 更 多 内 容 , 参 阅 第 五 章 , 模 式 匹 配 。3.7 乘 号 操 作 符<strong>Perl</strong> 提 供 类 似 C 的 操 作 符 ( 乘 )、/( 除 )、 和 %( 模 除 )。 和 / 的 运 行 和 你 预 料的 一 样 , 对 其 两 个 操 作 数 进 行 乘 或 除 。 除 法 是 以 浮 点 数 进 行 的 , 除 非 你 用 了 integer 用 法模 块 。% 操 作 符 在 用 整 数 除 法 计 算 余 数 前 , 把 它 的 操 作 数 转 换 为 整 数 。( 不 过 , 如 果 必 要 , 它 会以 浮 点 进 行 除 法 , 这 样 你 的 操 作 数 在 大 多 数 32 位 机 器 上 最 多 可 以 有 ( 以 浮 点 )15 位 。)假 设 你 的 两 个 操 作 数 叫 $b 和 $a。 如 果 $b 是 正 数 , 那 么 $a % $b 的 结 果 是 $a 减 去$b 不 大 于 $a 的 最 大 倍 数 ( 也 就 意 味 着 结 果 总 是 在 范 围 0 .. $b-1 之 间 )。 如 果 $b 是负 数 , 那 么 $a % $b 的 结 果 是 $a 减 去 $b 不 小 于 $a 的 最 小 倍 数 ( 意 味 着 结 果 介 于$b+1 .. 0 之 间 )。当 use integer 在 范 围 里 时 ,% 直 接 给 你 由 你 的 C 编 译 器 实 现 的 模 除 操 作 符 。 这 个 操 作符 对 负 数 定 义 得 不 是 很 好 , 但 是 执 行 得 更 快 。双 目 x 是 复 制 操 作 符 。 实 际 上 , 它 是 两 个 操 作 符 , 在 标 量 环 境 里 , 它 返 回 一 个 由 左 操 作 数重 复 右 操 作 数 的 次 数 连 接 起 来 的 字 串 。( 为 了 向 下 兼 容 , 如 果 左 操 作 数 没 有 位 于 圆 括 弧 中 ,那 么 它 在 列 表 环 境 里 也 这 样 处 理 。)print '-' x 80;# 打 印 一 行 划 线94


print "\t" x ($tab/8), ' ' x ($tab%8);# 跳 过在 列 表 环 境 里 , 如 果 左 操 作 数 是 在 圆 括 弧 中 的 列 表 ,x 的 作 用 是 一 个 列 表 复 制 器 , 而 不 是字 串 复 制 器 。 这 个 功 能 对 初 始 化 一 个 长 度 不 定 的 数 组 的 所 有 值 为 同 一 值 时 很 有 用 :@ones = (1) x80;# 一 个 80 个 1 的 列 表@ones = (5) x @ones; # 把 所 有 元 素 设 置 为 5类 似 , 你 还 可 以 用 x 初 始 化 数 组 和 散 列 片 段 :@keys = qw(perls before swine);@hash{@keys} = (" ") x @keys;如 果 这 些 让 你 迷 惑 , 注 意 @keys 被 同 时 当 做 一 个 列 表 在 赋 值 左 边 使 用 和 当 做 一 个 标 量 值( 返 回 数 组 长 度 ) 在 赋 值 语 句 右 边 。 前 面 的 例 子 在 %hash 上 有 相 同 的 作 用 :$hash{perls} = "";$hash{before} = "";$hash{swine} = "";3.8 附 加 操 作 符很 奇 怪 的 是 ,<strong>Perl</strong> 还 有 惯 用 的 +( 加 法 ) 和 -( 减 法 ) 操 作 符 。 两 种 操 作 符 都 在 必 要 的 时候 把 它 们 的 参 数 从 字 串 转 换 为 数 字 值 并 且 返 回 一 个 数 字 值 。另 外 ,<strong>Perl</strong> 提 供 . 操 作 符 , 它 做 字 串 连 接 处 理 。 比 如 :$almost = "Fred" . "Flitstone"; # 返 回 FredFlitstone ?请 注 意 <strong>Perl</strong> 并 不 在 连 接 的 字 串 中 间 放 置 空 白 。 如 果 你 需 要 空 白 , 或 者 你 要 连 接 的 字 串 多 于两 个 , 你 可 以 使 用 join 操 作 符 , 在 第 二 十 九 章 , 函 数 , 中 介 绍 。 更 常 用 的 是 人 们 在 一 个 双引 号 引 起 的 字 串 里 做 隐 含 的 字 串 连 接 :$fullname = "$firstname $lastname";3.9 移 位 操 作 符按 位 移 位 操 作 符 (>) 返 回 左 参 数 向 左 () 移 动 由 右 参 数 声 明位 ( 是 bit) 数 的 值 。 参 数 应 该 是 整 数 。 比 如 :95


1. > 4; # 返 回 23.10 命 名 单 目 操 作 符 和 文 件 测 试 操 作 符在 第 二 十 九 章 里 描 述 的 一 些 “ 函 数 ” 实 际 上 都 是 单 目 操 作 符 。 表 3-2 列 出 所 有 命 名 的 单 目 操作 符 。表 3-2 命 名 单 目 操 作 符-X (file tests) gethostbyname localtime returnalarm getnetbyname lock rmdircaller getpgrp log scalarchdir getprotobyname lstat sinchroot glob my sleepcos gmtime oct sqrtdefined goto ord sranddelete hex quotemeta statdo int rand uceval lc readlink ucfirstexists lcfirst ref umaskexit length require undef单 目 操 作 符 比 某 些 双 目 操 作 符 的 优 先 级 高 。 比 如 :sleep 4 | 3;并 不 是 睡 7 秒 钟 ; 它 先 睡 4 秒 钟 然 后 把 sleep 的 返 回 值 ( 典 型 的 是 零 ) 与 3 进 行 按 位或 操 作 , 就 好 象 该 操 作 符 带 这 样 的 圆 括 弧 :(sleep 4) | 3;与 下 面 相 比 :print 4 | 3;上 面 这 句 先 拿 4 和 3 进 行 或 操 作 , 然 后 再 打 印 之 ( 本 例 中 是 7), 就 好 象 是 下 面 这 样 写的 一 样 :print (4 | 3);96


这 是 因 为 print 是 一 个 列 表 操 作 符 , 而 不 是 一 个 简 单 的 单 目 操 作 符 。 一 旦 你 知 道 了 哪 个 操作 符 是 列 表 操 作 符 , 你 再 把 单 目 操 作 符 和 列 表 操 作 符 区 分 开 就 不 再 困 难 了 。 当 你 觉 得 有 问 题时 , 你 总 是 可 以 用 圆 括 弧 把 一 个 命 名 的 单 目 操 作 符 转 换 成 函 数 。 记 住 : 如 果 看 起 来 象 函 数 ,那 它 就 是 函 数 。有 关 命 名 单 目 操 作 符 的 另 一 个 趣 事 是 , 它 们 中 的 许 多 在 你 没 有 提 供 参 数 时 , 缺 省 使 用 $_。不 过 , 如 果 你 省 略 了 参 数 , 而 跟 在 命 名 单 目 操 作 符 后 面 的 记 号 看 起 来 又 象 一 个 参 数 开 头 的 话 ,那 <strong>Perl</strong> 就 傻 了 , 因 为 它 期 望 的 是 一 个 项 。 如 果 <strong>Perl</strong> 的 记 号 是 列 在 表 3-3 中 的 一 个 字 符 ,那 么 该 记 号 会 根 据 自 己 是 期 待 一 个 项 还 是 操 作 符 转 成 不 同 的 记 号 类 型 。表 3-3 模 糊 字 符字 符 操 作 符 项+ 加 法 单 目 正 号- 减 法 单 目 负 号* 乘 法 * 类 型 团/ 除 法 / 模 式 /< 小 于 号 , 左 移 ,


的 文 件 , 看 看 某 些 东 西 是 否 为 真 。 如 果 忽 略 参 数 , 它 测 试 $_, 但 除 了 -t 之 外 ,-t 是 测 试STDIN。 除 非 另 有 文 档 , 它 测 试 为 真 时 返 回 1, 为 假 时 返 回 "", 或 者 如 果 文 件 不 存 在 或 无法 访 问 时 返 回 未 定 义 。 目 前 已 实 现 的 文 件 测 试 操 作 符 列 在 表 3-4。表 3-4 文 件 测 试 操 作 符操 作 符含 义-r 文 件 可 以 被 有 效 的 UID/GID 读 取 。-w 文 件 可 以 被 有 效 的 UID/GID 写 入 。-x 文 件 可 以 被 有 效 的 UID/GID 执 行 。-o 文 件 被 有 效 UID 所 有-R 文 件 可 以 被 真 实 的 UID/GID 读 取 。-W 文 件 可 以 被 真 实 的 UID/GID 写 入 。-X 文 件 可 以 被 真 实 的 UID/GID 执 行 。-O 文 件 被 真 实 的 UID 所 有-e 文 件 存 在-z 文 件 大 小 为 零-s 文 件 大 小 不 为 零 ( 返 回 大 小 )-f 文 件 是 简 单 文 件-d 文 件 是 目 录-l 文 件 是 符 号 连 接-p 文 件 是 命 名 管 道 (FIFO)。-S 文 件 是 套 接 字-b 文 件 是 特 殊 块 文 件-c 文 件 是 特 殊 字 符 文 件-t 文 件 句 柄 为 一 个 tty 打 开 了-u 文 件 设 置 了 setuid 位-g 文 件 设 置 了 setgid 位-k 文 件 设 置 了 sticky 位-T 文 件 是 文 本 文 件-B 文 件 是 一 个 二 进 制 文 件 ( 与 -T 对 应 )-M 自 从 修 改 以 来 的 文 件 以 天 记 的 年 龄 ( 从 开 始 起 )-A 自 从 上 次 访 问 以 来 的 文 件 以 天 记 的 年 龄 ( 从 开 始 起 )-C 自 从 inode 修 改 以 来 的 文 件 以 天 记 的 年 龄 ( 从 开 始 起 )98


请 注 意 -s/a/b/ 并 不 是 做 一 次 反 向 替 换 。 不 过 , 说 -exp($foo) 仍 然 会 和 你 预 期 的 那 样 运行 , 因 为 只 有 跟 在 负 号 后 面 的 单 个 字 符 才 解 释 成 文 件 测 试 。文 件 权 限 操 作 符 -r ,-R,-w,-W,-x 和 -X 的 解 释 各 自 基 于 文 件 和 用 户 的 用 户 ID 和组 ID。 可 能 还 有 其 他 原 因 让 你 无 法 真 正 读 , 写 或 执 行 该 文 件 , 比 如 Andrew FileSystem(AFS) 的 的 访 问 控 制 列 表 ( 注 : 不 过 , 你 可 以 用 use filetest 用 法 覆 盖 内 建 的 语义 。 参 阅 第 三 十 一 章 , 用 法 模 块 )。 还 要 注 意 的 是 , 对 于 超 级 用 户 而 言 ,-r,-R,-w 和 -W总 总 是 返 回 1, 并 且 如 果 文 件 模 式 里 设 置 了 执 行 位 ,-x 和 -X 也 返 回 1。 因 此 , 由 超 级 用 户执 行 的 脚 本 可 能 需 要 做 一 次 stat 来 检 测 文 件 的 真 实 模 式 , 或 者 暂 时 把 UID 设 置 为 其 他 的什 么 东 西 。其 他 文 件 测 试 操 作 符 不 关 心 你 是 谁 。 任 何 人 都 可 以 用 这 些 操 作 符 来 测 试 " 普 通 " 文 件 :while () {chomp;next unless -f $_;# 忽 略 “ 特 殊 ” 文 件...}-T 和 -B 开 关 按 照 下 面 描 述 的 方 法 运 转 。 检 查 文 件 的 第 一 块 的 内 容 , 查 找 是 否 有 类 似 控 制字 符 或 者 设 置 了 第 八 位 的 字 符 ( 这 样 看 起 来 就 不 象 UTF-8)。 如 果 有 超 过 三 分 之 一 的 字 符看 起 来 比 较 特 殊 , 它 就 是 二 进 制 文 件 ; 否 则 , 就 是 文 本 文 件 。 而 且 , 任 何 在 第 一 块 里 包 含ASCII NUL(\0 ) 的 文 件 都 会 被 认 为 是 二 进 制 文 件 。 如 果 对 文 件 句 柄 使 用 -T 或 -B, 则检 测 当 前 输 入 ( 标 准 I/O 或 者 “stdio”) 缓 冲 区 , 而 不 是 文 件 的 第 一 块 。-T 和 -B 对 空 文件 都 返 回 真 , 或 者 测 试 一 个 文 件 句 柄 时 读 到 EOF( 文 件 结 束 ) 时 也 返 回 真 。 因 为 <strong>Perl</strong> 需要 读 文 件 才 能 进 行 -T 测 试 , 所 以 你 大 概 不 想 在 某 些 特 殊 文 件 上 用 -T 把 系 统 搞 得 挂 起 来 ,或 者 是 发 生 其 他 让 你 痛 苦 的 事 情 吧 。 所 以 , 大 多 数 情 况 下 , 你 会 希 望 先 用 -f 测 试 , 比 如 :next unless -f $file && -T $file;如 果 给 任 何 文 件 测 试 ( 操 作 符 )( 或 者 是 stat 或 lstat 操 作 符 ) 的 特 殊 文 件 句 柄 只 包 含单 独 一 个 下 划 线 , 则 使 用 前 一 个 文 件 测 试 的 stat 结 构 , 这 样 就 省 了 一 次 系 统 调 用 。( 对 -t无 效 , 而 且 你 还 要 记 住 lstat 和 -l 会 在 stat 结 构 里 保 存 符 号 连 接 而 不 是 真 实 文 件 的 数值 。 类 似 地 , 在 一 个 正 常 的 stat 的 后 面 的 -l _ 总 是 会 为 假 。)下 面 是 几 个 例 子 :99


print "Can do.\n" if -r $a || -w _ || -x _;stat($filename);print "Readable\n" if -r _;print "Writable\n" if -w _;print "Executable\n" if -x _;print "Setuid\n" if -u _;print "Setgid\n" if -g _;print "Sticky\n" if -k _;print "Text\n" if -T _;print "Binary\n" if -B _;-M,-A 和 -C 返 回 脚 本 开 始 运 行 以 来 一 天 ( 包 括 分 数 日 子 ) 计 的 文 件 年 龄 。 这 个 时 间 是保 存 在 特 殊 变 量 $^T ($BASETIME) 里 面 的 。 因 此 , 如 果 文 件 在 脚 本 启 动 后 做 了 改 变 ,你 就 会 得 到 一 个 负 数 时 间 。 请 注 意 , 大 多 数 时 间 值 ( 概 率 为 86400 分 之 86399) 都 是 分数 , 所 以 如 果 不 用 int 函 数 就 拿 它 和 一 个 整 数 进 行 相 等 性 测 试 , 通 常 都 会 失 败 。 比 如 :next unless -M $file > .5;&newfile if -M $file < 0;# 文 件 长 于 12 小 时# 文 件 比 进 程 新&mailwarning if int(-A) == 90;# 文 件 ($_) 是 90 十 天 前 访 问 的要 把 脚 本 的 开 始 时 间 重 新 设 置 为 当 前 时 间 , 这 样 :$^T = time;3.11 关 系 操 作 符<strong>Perl</strong> 有 两 类 关 系 操 作 符 。 一 类 操 作 符 操 作 数 字 值 , 另 一 类 操 作 字 串 值 , 在 表 3-5 中 列 出 。100


表 3-5 关 系 操 作 符数 字 字 串 含 义> gt 大 于>= ge 大 于 或 等 于< lt 小 于


这 些 操 作 符 对 数 字 值 和 对 字 串 值 的 处 理 不 同 。( 这 是 少 数 几 个 <strong>Perl</strong> 关 心 的 区 别 。) 如 果 两个 操 作 数 都 是 数 字 ( 或 者 被 当 作 数 字 使 用 ), 那 么 两 个 操 作 数 都 被 转 换 成 整 数 然 后 在 两 个 整数 之 间 进 行 位 操 作 。 我 们 保 证 这 些 整 数 是 至 少 32 位 长 , 不 过 在 某 些 机 器 上 可 以 是 64 位长 。 主 要 是 要 知 道 有 一 个 由 机 器 的 体 系 所 施 加 的 限 制 。如 果 两 个 操 作 数 都 是 字 串 ( 而 且 自 从 它 们 被 设 置 以 来 还 没 有 当 作 数 字 使 用 过 ), 那 么 该 操 作符 用 两 个 字 串 里 面 来 的 位 做 位 操 作 。 这 种 情 况 下 , 没 有 任 何 字 长 限 制 , 因 为 字 串 本 身 没 有 尺寸 限 制 。 如 果 一 个 字 串 比 另 一 个 长 ,<strong>Perl</strong> 就 认 为 短 的 那 个 在 尾 部 有 足 够 的 0 以 弥 补 区 别 。比 如 , 如 果 你 AND 两 个 字 串 :"123.45" & "234.56"你 得 到 另 外 一 个 字 串 :"020.44"不 过 , 如 果 你 拿 一 个 字 串 和 一 个 数 字 AND:"123.45" & 234.56那 字 串 先 转 换 成 数 字 , 得 到 :1. 45 & 234.56然 后 数 字 转 换 成 整 数 :1. & 234最 后 得 到 值 为 106。 请 注 意 所 有 位 字 串 都 是 真 ( 除 非 它 们 结 果 是 字 串 “0”)。 这 意 味 着 如 果你 想 看 看 任 意 字 节 是 否 为 非 零 , 你 不 能 这 么 干 :if ( "fred" & "\1\2\3\4" ) { ... }你 得 这 么 干 :if( )"fred" & "\1\2\3\4") =~ /[^\0]/ ) { ... }3.14 C 风 格 的 逻 辑 ( 短 路 ) 操 作 符和 C 类 似 ,<strong>Perl</strong> 提 供 &&( 逻 辑 AND) 和 ||( 逻 辑 OR) 操 作 符 。 它 们 从 左 向 右 计 算( && 比 || 的 优 先 级 稍 稍 高 一 点 点 ), 测 试 语 句 的 真 假 。 这 些 操 作 符 被 认 为 是 短 路 操 作102


符 , 因 为 它 们 是 通 过 计 算 尽 可 能 少 的 操 作 数 来 判 断 语 句 的 真 假 。 例 如 , 如 果 一 个 && 操 作符 的 左 操 作 数 是 假 , 那 么 它 永 远 不 会 计 算 右 操 作 数 , 因 为 操 作 符 的 结 果 就 是 假 , 不 管 右 操 作数 的 值 是 什 么 。例 子 名 称 结 果$a && $b And 如 果 $a 为 假 则 为 $a, 否 则 $b$a || $b Or 如 果 $a 为 真 则 为 $a, 否 则 $b这 样 的 短 路 不 仅 节 约 时 间 , 而 且 还 常 常 用 于 控 制 计 算 的 流 向 。 比 如 , 一 个 经 常 出 现 的 <strong>Perl</strong> 程序 的 俗 语 是 :open(FILE, "somefile") || die "Can't open somefile: $!\n";在 这 个 例 子 里 ,<strong>Perl</strong> 首 先 计 算 open 函 数 , 如 果 值 是 真 (somefile 被 成 功 打 开 ),die 函数 的 执 行 就 不 必 要 了 , 因 此 忽 略 。 你 可 以 这 么 读 这 句 文 本 “ 打 开 文 件 , 要 不 然 就 去 死 !”。&& 和 || 操 作 符 和 C 不 同 的 是 , 它 们 不 返 回 0 或 1, 而 是 返 回 最 后 计 算 的 值 。 如 果 是||, 这 个 特 性 好 就 好 在 你 可 以 从 一 系 列 标 量 数 值 中 选 出 第 一 个 为 真 的 值 。 所 以 , 一 个 移 植 性相 当 好 的 寻 找 用 户 的 家 目 录 的 方 法 可 能 是 :$home = $ENV{HOME}|| $ENV{LOGDIR}|| (getpwuid($


在 标 量 环 境 里 ,.. 返 回 一 个 布 尔 值 。 该 操 作 符 是 双 稳 定 的 , 类 似 一 个 电 子 开 关 , 并 且 它 仿真 sed,awk, 和 各 种 编 辑 器 的 行 范 围 ( 逗 号 ) 操 作 符 。 每 个 .. 操 作 符 都 维 护 自 身 的 状 态 。只 要 它 的 左 操 作 数 为 假 就 一 直 为 假 。 一 旦 左 操 作 数 为 真 , 该 范 围 操 作 符 就 保 持 真 的 状 态 直 到右 操 作 数 为 真 , 右 操 作 数 为 真 之 后 该 范 围 操 作 符 再 次 为 假 。 该 操 作 符 在 下 次 计 算 之 前 不 会 变成 假 。 它 可 以 测 试 右 操 作 数 并 且 在 右 操 作 数 变 真 后 在 同 一 次 计 算 中 变 成 假 (awk 的 范 围 操作 符 的 特 性 ), 不 过 它 还 是 会 返 回 一 次 真 。 如 果 你 不 想 拖 到 下 一 次 计 算 中 才 测 试 右 操 作 数 ( 也是 sed 的 范 围 操 作 符 的 工 作 方 式 ), 只 需 要 用 三 个 点 (...) 代 替 两 个 点 (..)。 对 于 .. 和 ...,当 操 作 符 处 于 假 状 态 后 就 不 再 测 试 右 操 作 数 , 而 当 操 作 符 处 于 真 状 态 后 就 不 再 测 试 左 操 作数 。返 回 的 值 要 么 是 代 表 假 的 空 字 串 或 者 是 代 表 真 的 一 个 序 列 数 ( 从 1 开 始 )。 该 序 列 数 每 次碰 到 新 范 围 时 重 置 。 在 一 个 范 围 里 的 最 后 序 列 数 后 面 附 加 了 字 串 “E0”, 这 个 字 串 不 影 响 它的 数 字 值 , 只 是 给 你 一 些 东 西 让 你 可 以 搜 索 , 这 样 你 可 以 把 终 点 排 除 在 外 。 你 也 可 以 通 过 等待 1 的 序 列 数 的 方 法 把 启 始 点 排 除 在 外 。 如 果 标 量 .. 的 某 个 操 作 数 是 数 字 文 本 , 那 么 该操 作 数 隐 含 地 与 $. 变 量 对 比 ,$. 里 包 含 你 的 输 入 文 件 的 当 前 行 号 。 比 如 :if(101 .. 200) {print;}next line if( 1.. /^$/);# 打 印 第 二 个 一 百 行# 忽 略 开 头 行s/^/> / if (/^$/ .. eof());# 引 起 体在 列 表 环 境 里 , .. 返 回 一 列 从 左 值 到 右 值 计 数 ( 以 一 ) 的 数 值 。 这 样 对 书 写 (1 .. 10) 循环 和 数 组 片 段 的 操 作 很 有 帮 助 :for (101 .. 200) {print;} # 打 印 101102 。。。 199200@foo = @foo[0 .. $#foo];@foo = @foo[ -5 .. -1];# 一 个 昂 贵 的 空 操 作# 最 后 5 个 元 素 的 片 段如 果 左 边 的 值 大 于 右 边 的 值 , 则 返 回 一 个 空 列 表 。( 要 产 生 一 列 反 序 的 列 表 , 参 阅 reverse操 作 符 。)如 果 操 作 数 是 字 串 , 范 围 操 作 符 利 用 早 先 讨 论 过 的 自 增 处 理 。( 注 : 如 果 在 所 声 明 的 终 值 不是 自 增 处 理 中 产 生 的 序 列 中 的 数 , 那 么 该 序 列 将 继 续 增 加 直 到 下 一 个 值 比 声 明 的 终 值 长 为止 。) 因 此 你 可 以 说 :@alphabet = ('A' .. 'Z');以 获 得 所 有 英 文 字 母 , 或 者 :104


$hexdigit = (0 .. 9, 'a' .. 'f')[$num & 15];获 得 一 个 十 六 进 制 位 , 或 者 :@z2 = ('01' .. '31'); print $z2[$mday];获 得 带 有 前 导 零 的 日 期 。 你 还 可 以 说 :@combos = ('aa' .. 'zz');获 取 所 有 两 个 小 写 字 符 的 组 合 。 不 过 , 用 下 面 的 语 句 要 小 心 :@bigcombos = ('aaaaaa' .. 'zzzzzz');因 为 这 条 语 句 要 消 耗 很 多 内 存 。 准 确 地 说 , 它 需 要 存 储 308,915,776 个 标 量 的 空 间 。 希望 你 分 配 了 一 个 非 常 大 的 交 换 分 区 。 可 能 你 会 考 虑 用 循 环 代 替 它 。3.16 条 件 操 作 符和 C 里 一 样 ,?: 是 唯 一 的 一 个 三 目 操 作 符 。 它 通 常 被 成 为 条 件 操 作 符 , 因 为 它 运 转 起 来非 常 象 一 个 if-then-else, 而 且 , 因 为 它 是 一 个 表 达 式 而 不 是 一 个 语 句 , 所 以 它 可 以 非 常容 易 地 嵌 入 其 他 表 达 式 和 函 数 调 用 中 。 作 为 三 目 操 作 符 , 它 的 两 个 部 分 分 隔 了 三 个 表 达 式 :COND ? THEN : ELSE如 果 条 件 COND 为 真 , 那 么 只 计 算 THEN 表 达 式 , 并 且 其 值 成 为 整 个 表 达 式 的 值 。 否 则 ,只 计 算 ELSE 表 达 式 的 值 , 并 且 其 值 成 为 整 个 表 达 式 的 值 。不 管 选 择 了 哪 个 参 数 , 标 量 或 者 列 表 环 境 都 传 播 到 该 参 数 。( 第 一 个 参 数 总 是 处 于 标 量 环 境 ,因 为 它 是 一 个 条 件 。)$a = $ok ? $b :$c; # 得 到 一 个 标 量@a = $ok ? @b : @c;# 得 到 一 个 数 组$a = $ok ? @b :@C; # 得 到 一 个 数 组 元 素 的 计 数你 会 经 常 看 到 条 件 操 作 符 嵌 入 在 一 列 数 值 中 以 格 式 化 printf, 因 为 没 人 愿 意 只 是 为 了 在 两个 相 关 的 值 之 间 切 换 而 复 制 整 条 语 句 。printf "I have $d camel$.\n",$n,$n == 1 ? "" : "s";105


?: 的 优 先 级 比 逗 号 高 , 但 是 比 你 可 能 用 到 的 其 他 大 多 数 操 作 符 ( 比 如 本 例 中 的 ==) 都 低 ,因 此 通 常 你 用 不 着 用 圆 括 弧 括 任 何 东 西 。 不 过 如 果 你 愿 意 , 你 可 以 用 圆 括 弧 让 语 句 更 清 晰 。对 于 嵌 套 在 其 他 THEN 部 分 的 其 他 条 件 操 作 符 , 我 们 建 议 你 在 它 们 中 间 放 入 断 行 和 缩 进 ,就 好 象 它 们 是 普 通 if 语 句 一 样 :$leapyear =$year % 4 == 0? $year % 100 == 0? $year % 400 == 0? 1:0:1:0;类 似 地 , 对 于 嵌 套 在 ELSE 部 分 的 更 早 的 条 件 , 你 也 可 以 这 样 处 理 :$leapyear =$year % 4? 0: $year %100? 1: $year % 400? 0:1;不 过 通 常 最 好 把 所 有 COND 和 THEN 部 分 垂 直 排 列 :$leapyear =$year % 4?0:$year % 100 ? 1:106


$year % 400 ? 0:1把 问 号 和 冒 号 对 齐 可 以 让 你 在 非 常 混 乱 的 结 构 中 找 到 感 觉 :printf "Yes, I like my %s book!\n",$i18n eq "french" ? "chameau" :$i18n eq "german" ? "Kamel" :$i18n eq "japanese" ? "\x{99F1}\x{99DD}" :"camel"如 果 第 二 个 和 第 三 个 参 数 都 是 合 法 的 左 值 ( 也 就 是 说 你 可 以 给 他 们 赋 值 ), 而 且 同 时 为 标 量或 者 列 表 ( 否 则 ,<strong>Perl</strong> 就 不 知 道 应 该 给 赋 值 的 右 边 什 么 环 境 ), 那 你 就 可 以 给 它 赋 值 ( 注 :这 样 无 法 保 证 你 的 程 序 有 很 好 的 可 读 性 。 但 是 这 种 格 式 可 以 用 于 创 建 一 些 很 酷 的 东 西 。):($a_or_b ? $a : $b) = $c;# 把 $a 或 $b 置 为 $c 的 值请 注 意 , 条 件 操 作 符 比 各 种 各 样 赋 值 操 作 符 绑 定 得 更 紧 。 通 常 这 是 你 所 希 望 的 ( 比 如 说 上 面$leapyear 赋 值 ), 但 如 果 你 不 用 圆 括 弧 的 话 , 你 就 没 法 让 它 反 过 来 。 不 带 圆 括 弧 ( 在 条件 操 作 符 ) 中 使 用 嵌 入 的 赋 值 会 给 你 带 来 麻 烦 , 并 且 这 时 还 不 会 有 分 析 错 误 , 因 为 条 件 操 作符 左 值 分 析 。 比 如 说 , 你 可 能 写 下 面 这 些 :$a %2 ? $a += 10 : $a += 2 # 错上 面 这 个 会 分 析 为 :(($a % 2) ? ($a += 10) : $a) += 23.16 赋 值 操 作 符<strong>Perl</strong> 可 以 识 别 C 的 赋 值 操 作 符 , 还 提 供 了 几 个 自 己 的 。 这 里 是 一 些 := **= += *= &= = ||=.= %= ^=x=107


每 个 操 作 符 需 要 一 个 目 标 左 值 ( 典 型 值 是 一 个 变 量 或 数 组 元 素 ) 在 左 边 以 及 一 个 表 达 式 在 右边 。 对 于 简 单 赋 值 操 作 符 :TARGET = EXPREXPR 的 值 被 存 储 到 TARGET 指 明 的 变 量 或 者 位 置 里 面 。 对 其 他 操 作 符 而 言 , <strong>Perl</strong> 计 算表 达 式 :TARGET OP= EXPR就 好 象 它 是 这 么 写 的 一 样 :TARGET = TARGET OP EXPR这 是 有 利 于 简 便 的 规 则 , 不 过 它 在 两 个 方 面 会 误 导 我 们 。 首 先 , 赋 值 操 作 符 总 是 以 普 通 赋 值的 优 先 级 进 行 分 析 的 , 不 管 OP 本 身 的 优 先 级 是 什 么 。 第 二 ,TARGET 只 计 算 一 次 。 通 常这 样 做 没 有 什 么 问 题 , 除 非 存 在 副 作 用 , 比 如 一 个 自 增 :$var[$a++] += $value;# $a 增 加 了 一 次$var[$a++] = $var[$a++] + $value;# $a 增 加 了 两 次不 象 C 里 , 赋 值 操 作 符 生 成 一 个 有 效 的 左 值 。 更 改 一 个 赋 值 等 效 于 先 赋 值 然 后 把 变 量 修 改为 赋 的 值 。 这 招 对 修 改 一 些 东 西 的 拷 贝 很 管 用 , 象 这 样 :($tmp = $global) += $constant;等 效 于 :$tmp = $global + $constant;类 似 :($a += 2) *= 3;等 效 于 :108


$a += 2;$a *= 3;本 招 并 非 绝 招 , 不 过 下 面 是 你 经 常 看 到 的 习 惯 用 语 :($new = $old) =~ s/foo/bar/g;无 论 什 么 情 况 , 赋 值 的 值 是 该 变 量 的 新 值 。 因 为 赋 值 操 作 符 从 右 向 左 结 合 , 所 以 这 一 点 可 以用 于 给 多 个 变 量 赋 同 一 个 值 , 比 如 :$a = $b = $c =0;把 0 赋 予 $c, 其 结 果 ( 还 是 0) 给 $b, 其 结 果 ( 依 然 为 0) 再 给 $a。列 表 赋 值 可 能 只 能 在 列 表 环 境 里 用 简 单 赋 值 操 作 符 ,=., 赋 值 。 列 表 赋 值 返 回 该 列 新 值 ,就 象 标 量 赋 值 一 样 。 在 标 量 环 境 里 , 列 表 赋 值 返 回 赋 值 右 边 可 获 得 的 值 的 数 目 , 就 象 我 们 在第 二 章 , 集 腋 成 裘 , 里 谈 到 的 一 样 。 我 们 可 以 利 用 这 个 特 性 测 试 一 个 失 败 ( 或 者 不 再 成 功 )时 返 回 空 列 表 的 函 数 , 比 如 :while (($key, $value) = each %gloss) { ... }next unless ($dev, $ino, $mode) = stat $file;3.18 逗 号 操 作 符双 目 “,” 是 逗 号 操 作 符 。 在 标 量 环 境 里 它 先 在 空 环 境 里 计 算 它 的 左 参 数 , 把 那 个 值 抛 弃 , 然后 在 标 量 环 境 里 计 算 它 的 右 参 数 并 且 返 回 之 。 就 象 C 的 逗 号 操 作 符 一 样 。 比 如 :$a = (1,3);把 3 赋 予 $a。 不 要 混 淆 标 量 环 境 用 法 和 列 表 环 境 用 法 。 在 列 表 环 境 里 , 逗 号 只 是 列 表 参数 的 分 隔 符 , 而 且 把 它 的 两 个 参 数 都 插 入 到 列 表 中 。 并 不 抛 弃 任 何 数 值 。比 如 , 如 果 你 把 前 面 例 子 改 为 :@a = (1,3);你 是 在 构 造 一 个 两 元 素 的 列 表 , 而 :109


atan2(1,3);是 用 两 个 参 数 调 用 函 数 atan2。连 字 符 => 大 多 数 时 候 就 是 逗 号 操 作 符 的 同 义 词 。 对 那 些 成 对 出 现 的 文 档 参 数 很 有 用 。 它还 强 制 把 它 紧 左 边 的 标 识 符 解 释 为 一 个 字 串 。3.19 列 表 操 作 符 ( 右 向 )列 表 操 作 符 的 右 边 是 所 有 列 表 操 作 符 的 参 数 ( 用 逗 号 分 隔 的 ), 所 以 如 果 你 往 右 看 的 话 , 列表 操 作 符 的 优 先 级 比 逗 号 低 , 一 旦 列 表 操 作 符 开 始 啃 那 些 逗 号 分 隔 的 参 数 , 你 能 让 它 停 下 来的 唯 一 办 法 就 是 停 止 整 个 表 达 式 的 记 号 ( 比 如 分 号 或 语 句 修 改 器 ), 或 者 停 止 当 前 子 表 达 式的 记 号 ( 比 如 右 圆 括 弧 或 花 括 弧 ), 或 者 是 我 们 下 面 要 讲 的 低 优 先 级 的 逻 辑 操 作 符 。3.20 逻 辑 与 , 或 , 非 和 异 或作 为 &&,|| 和 ! 的 低 优 先 级 候 补 ,<strong>Perl</strong> 提 供 了 and,or, 和 not 操 作 符 。 这 些 操 作符 的 性 质 和 它 们 对 应 的 短 路 操 作 符 是 一 样 的 , 尤 其 是 and 和 or, 这 样 它 们 不 仅 可 以 用 于逻 辑 表 达 式 也 可 以 用 于 流 控 制 。因 为 这 些 操 作 符 的 优 先 级 远 远 比 从 C 借 来 的 那 些 低 , 所 以 你 可 以 很 放 心 地 把 它 们 用 在 一 个列 表 操 作 符 后 面 而 不 用 加 圆 括 弧 :unlink "alpha", "beta", "gamma"or gripe(), next LINE;而 对 C 风 格 的 操 作 符 你 就 得 这 么 写 :unlink("alpha", "beat", "gamma") || (girpe(), next LINE);不 过 你 不 能 简 单 地 把 所 有 || 替 换 为 or。 假 设 你 把 :$xyz = $x || $y || $z;改 成 :$xyz = $x or $y or $z;# 错110


这 两 句 是 完 全 不 同 的 ! 赋 值 的 优 先 级 比 or 高 但 是 比 || 低 , 所 以 它 总 是 会 给 $xyz 赋 值$x, 然 后 再 进 行 or。 要 获 得 和 || 一 样 的 效 果 , 你 就 得 写 :$xyz = ( $x or $y or $z );问 题 的 实 质 是 你 仍 然 必 须 学 习 优 先 级 ( 或 使 用 圆 括 弧 ), 不 管 你 用 的 是 哪 种 逻 辑 操 作 符 。还 有 一 个 逻 辑 xor 操 作 符 在 C 或 <strong>Perl</strong> 里 面 没 有 很 准 确 的 对 应 , 因 为 另 外 一 个 异 或 操 作符 (^) 按 位 操 作 。xor 操 作 符 不 能 短 路 , 因 为 两 边 都 必 须 计 算 。$a xor $b 的 最 好 的 等效 可 能 是 !$a = !$b。 当 然 你 也 可 以 写 !$a ^ !$b, 甚 至 可 以 是 $a ? !$b : !!$b。 要 点是 $a 和 $b 必 须 在 布 尔 环 境 里 计 算 出 真 或 假 , 而 现 有 的 位 操 作 符 在 没 有 帮 助 的 前 提 下 并不 提 供 布 尔 环 境 。3.21 <strong>Perl</strong> 里 没 有 的 C 操 作 符下 面 是 <strong>Perl</strong> 里 没 有 的 C 操 作 符 :单 目 & 取 址 操 作 符 。 不 过 ,<strong>Perl</strong> 的 \ 操 作 符 ( 用 于 使 用 引 用 ) 填 补 了 这 个 生 态 空 白 :$ref_to_var = \$var;不 过 <strong>Perl</strong> 的 引 用 要 远 比 C 的 指 针 更 安 全 。单 目 * 解 取 址 操 作 符 。 因 为 <strong>Perl</strong> 没 有 地 址 , 所 以 它 不 需 要 解 取 址 。 它 有 引 用 , 因 此 <strong>Perl</strong>的 变 量 前 缀 字 符 用 做 截 取 址 操 作 符 , 并 且 还 标 明 类 型 :$,@,%, 和 &。 不 过 , 有 趣 的是 实 际 上 有 一 个 * 解 引 用 操 作 符 , 但 因 为 * 是 表 示 一 个 类 型 团 的 趣 味 字 符 , 所 以 你 无 法将 其 用 于 同 一 目 的 。 ( 类 型 ) 类 型 转 换 操 作 符 。 没 人 喜 欢 类 型 转 换 。to top第 四 章 语 句 和 声 明一 个 <strong>Perl</strong> 程 序 由 一 系 列 声 明 和 语 句 组 成 。 一 个 声 明 可 以 放 在 任 何 可 以 放 语 句 的 地 方 ,但 是 它 的 主 要 作 用 发 生 在 编 译 时 。 有 几 个 声 明 类 似 语 句 , 有 双 重 身 份 , 但 是 大 多 数 在 运 行 时是 完 全 透 明 的 。 编 译 完 之 后 , 语 句 的 主 序 列 只 执 行 一 次 。111


和 许 多 编 程 语 言 不 同 ,<strong>Perl</strong> 并 不 要 求 明 确 的 变 量 声 明 ; 变 量 只 在 第 一 次 使 用 的 时 候 才 存 在 ,不 管 你 是 否 曾 声 明 它 们 。 如 果 你 试 图 从 一 个 从 未 赋 值 的 变 量 里 面 获 取 一 个 值 , 当 你 把 它 当 作数 字 时 <strong>Perl</strong> 会 被 悄 悄 地 把 它 当 0 看 待 , 而 当 作 字 串 时 会 把 它 当 作 ""( 空 字 串 ), 或 者 做逻 辑 值 用 的 时 候 就 是 假 。 如 果 你 喜 欢 在 误 把 未 定 义 的 值 用 做 真 字 串 或 数 字 时 收 到 警 告 , 或 者你 更 愿 意 把 这 样 用 当 作 错 误 , 那 么 use warning 声 明 会 处 理 这 一 切 ; 参 阅 本 章 末 尾 的 “ 用法 ” 节 。如 果 你 喜 欢 的 话 , 你 可 以 在 变 量 名 前 用 my 或 our 声 明 你 的 变 量 。 你 甚 至 可 以 把 使 用 未 声明 变 量 处 理 为 错 误 。 这 样 的 限 制 是 好 的 , 但 你 必 须 声 明 你 需 要 这 样 的 限 制 。 通 常 , 对 你 的 编程 习 惯 ,<strong>Perl</strong> 只 管 自 己 的 事 , 但 是 如 果 使 用 use strict 声 明 , 未 定 义 的 变 量 就 会 在 编 译 时被 了 解 。 同 样 , 参 阅 “ 用 法 ” 节 。4.1 简 单 语 句一 个 简 单 语 句 是 一 个 表 达 式 , 因 为 副 作 用 而 计 算 。 每 条 简 单 语 句 都 必 须 以 分 号 结 尾 , 除 非 它是 一 个 块 中 的 最 后 语 句 。 这 种 情 况 下 , 分 号 是 可 选 的 ——<strong>Perl</strong> 知 道 你 肯 定 已 经 完 成 语 句 了 ,因 为 你 已 经 结 束 块 了 。 但 是 如 果 是 在 多 个 块 的 结 尾 , 那 你 最 好 还 是 把 分 号 加 上 , 因 为 你 最 后可 能 还 是 要 另 加 一 行 。虽 然 象 eval{},do{}, 和 sub{} 这 样 的 操 作 符 看 起 来 象 组 合 语 句 , 其 实 它 们 不 是 。 的确 , 它 们 允 许 在 它 们 内 部 放 多 个 语 句 , 但 是 那 不 算 数 。 从 外 部 看 , 这 些 操 作 符 只 是 一 个 表 达式 里 的 项 , 因 此 如 果 把 它 们 用 做 语 句 中 的 最 后 一 个 项 , 则 需 要 一 个 明 确 的 分 号 结 束 。任 何 简 单 语 句 的 后 面 都 允 许 跟 着 一 条 简 单 的 修 饰 词 , 紧 接 着 是 结 束 的 分 号 ( 或 块 结 束 )。 可能 的 修 饰 词 有 :if EXPRunless EXPRwhile EXPRuntil EXPRforeach LISTif 和 unless 修 饰 词 和 他 们 在 英 语 里 的 作 用 类 似 :$trash->take('out') if $you_love_me;shutup() unless $you_want_me_to_leave;112


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"参 考 第 二 十 九 章 , 函 数 , 里 的 三 种 不 同 的 do 入 口 , 还 请 注 意 我 们 稍 后 讲 的 循 环 控 制 操 作符 在 这 个 构 造 中 无 法 使 用 , 因 为 修 饰 词 不 接 受 循 环 标 记 。 你 总 是 可 以 在 它 周 围 放 一 些 额 外 的( 花 括 弧 ) 块 提 前 结 束 它 , 或 者 在 其 内 部 放 先 行 运 行 —— 就 象 我 们 稍 后 将 在 “ 光 块 ” 里 描 述 的那 样 。 或 者 你 可 以 写 一 个 内 部 带 有 多 重 循 环 控 制 的 真 正 的 循 环 。 说 到 真 正 的 循 环 , 我 们 下 面要 谈 谈 混 合 语 句 。4.2 混 合 语 句在 一 个 范 围 ( 注 : 范 围 和 名 字 空 间 在 第 二 章 , 集 腋 成 裘 , 里 描 述 , 在 “ 名 字 ” 节 ) 里 的 一 个 语句 序 列 称 之 为 一 个 块 。 有 时 候 , 范 围 是 整 个 文 件 , 比 如 一 个 required 文 件 或 者 包 含 你 的主 程 序 的 那 个 文 件 。 有 时 候 , 范 围 是 一 个 用 eval 计 算 的 字 串 。 但 是 , 通 常 来 说 , 一 个 块是 一 个 用 花 括 弧 ({}) 包 围 的 语 句 体 。 当 我 们 说 到 范 围 的 时 候 , 我 们 的 意 思 就 是 上 面 三 种之 一 。 当 我 们 说 一 个 带 花 括 弧 的 块 时 , 我 们 会 用 术 语 BLOCK。113


混 合 语 句 是 用 表 达 式 和 BLOCK 一 起 构 造 的 。 表 达 式 是 由 项 和 操 作 符 组 成 的 。 我 们 将 在 语法 描 述 里 用 EXPR 表 示 那 些 你 可 以 使 用 任 意 标 量 表 达 式 的 地 方 。 要 表 示 一 个 表 达 式 是 在 列表 环 境 里 计 算 的 , 我 们 就 用 LIST。下 面 的 语 句 可 以 用 于 控 制 BLOCK 的 条 件 和 重 复 执 行 。(LABEL 部 分 是 可 选 的 。)if (EXPR) BLOCKif (EXPR) BLOCK else BLOCKif (EXPR) BLOCK elsif (EXPR) BLOCK ...if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCKunless (EXPR) BLOCKunless (EXPR) BLOCK else BLOCKunless (EXPR) BLOCK elsif (EXPR) BLOCK ...unless (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCKLABEL while (EXPR) BLOCKLABEL while (EXPR) BLOCK continue BLOCKLABEL until (EXPR) BLOCKLABEL until (EXPR) BLOCK continue BLOCKLABEL for (EXPR; EXPR; EXPR) BLOCKLABEL foreach (LIST) BLOCKLABEL foreach VAR (LIST) BLOCKLABEL foreach VAR (LIST) BLOCK continue BLOCK114


LABEL BLOCKLABEL BLOCK continue BLOCK请 注 意 和 C 及 Java 不 同 的 是 , 这 些 语 句 是 根 据 BLOCK 而 不 是 根 据 语 句 定 义 的 。 这 就意 味 着 花 括 弧 是 必 须 的 —— 不 允 许 有 虚 悬 的 语 句 。 如 果 你 想 不 带 圆 括 弧 写 条 件 , 可 以 有 若干 种 处 理 方 法 。 下 面 的 语 句 作 用 相 同 :unless (open(FOO, $foo)) {die "Can't open $foo: $!" }if(!open(FOO, $foo)) {die "Can't open $foo: $!" }die "Can't open $foo: $!"die "Can't open $foo: $!"unless open(FOO, $foo);if !open(FOO, $foo);open(FOO, $foo) || die "Can't open $foo: $!";open(FOO, $foo) or die "Can't open $foo: $!";在 大 多 数 情 况 下 , 我 们 都 建 议 使 用 最 后 一 对 儿 。 这 种 形 式 看 着 整 齐 一 点 , 尤 其 是 "or die" 版本 。 而 用 || 形 式 时 你 必 须 习 惯 虔 诚 地 使 用 圆 括 弧 , 而 如 果 用 or 版 本 , 即 使 你 忘 了 也 用 不着 担 心 。不 过 我 们 喜 欢 最 后 一 种 形 式 的 原 因 是 它 把 语 句 里 重 要 的 部 分 放 到 行 的 前 面 , 这 样 你 会 先 看 到它 们 。 错 误 控 制 部 分 挪 到 了 边 上 , 这 样 除 非 必 要 的 时 候 , 你 用 不 着 注 意 它 们 。 如 果 你 每 次 都把 所 有 "or die" 检 查 放 到 右 边 同 一 行 , 那 就 很 容 易 读 了 :chdir $dir or die "chdir $dir: $!";open FOO, $file or die "open $file: $!";@lines = or die "$file is empty?";close FOO or die "close $file: $!";4.2.1 if 和 else 语 句115


if 语 句 比 较 直 接 了 当 。 因 为 BLOCK 们 总 是 用 花 括 弧 包 围 , 所 以 从 不 会 出 现 混 淆 哪 一 个 if和 else 或 elsif 有 效 的 情 况 。 在 给 出 的 任 意 if/elsif/else BLOCK 里 , 只 有 第 一 个 条 件BLOCK 才 执 行 。 如 果 没 有 一 个 为 真 , 而 且 存 在 else BLOCK, 则 执 行 之 。 一 个 好 习 惯 是在 一 个 elsif 链 的 最 后 放 上 一 个 else 以 捕 捉 漏 掉 的 情 况 。如 果 你 用 unless 取 代 if, 那 么 它 的 测 试 是 相 反 的 , 也 就 是 说 :unless ($x == 1) ...等 效 于if($x != 1) ...或 者 是 难 看 的 :if(!($x == 1)) ...在 控 制 条 件 里 定 义 的 变 量 , 其 范 围 只 扩 展 到 其 余 条 件 的 范 围 , 包 括 任 何 随 后 可 能 存 在 的 elsif和 else 子 句 , 但 是 不 会 超 过 这 个 范 围 :if (( my $color = ) =~ /red/i ) {$value = 0xff0000;}else ($color =~ /green/i) {$value = 0x00ff00;}else if ($color =~ /blue /i ){$value = 0x0000ff;}else {warn "unknown RGB component `$color', using black instead\n";$value = 0x000000;}116


在 else 以 后 ,$color 变 量 将 不 再 位 于 范 围 之 内 。 如 果 你 想 把 范 围 扩 大 一 些 , 那 么 请 在 条件 之 前 定 义 该 变 量 。4.3 循 环 语 句所 有 循 环 语 句 在 它 们 正 式 语 法 里 有 可 选 的 LABEL( 标 记 )。( 你 可 以 在 任 何 语 句 里 放 上 标记 , 但 是 它 们 对 循 环 有 特 殊 含 义 。) 如 果 有 标 记 , 它 由 一 个 标 识 后 面 跟 着 一 个 冒 号 组 成 。 通常 标 记 都 用 大 写 以 避 免 与 保 留 字 冲 突 , 并 且 这 样 它 也 比 较 醒 目 。 同 时 , 尽 管 <strong>Perl</strong> 不 会 因 为你 使 用 一 个 已 经 有 含 义 的 词 做 标 记 ( 比 如 if 和 open) 而 晕 菜 , 但 你 的 读 者 却 是 会 的 , 所以 ...。4.3.1 while 和 until 语 句while 语 句 在 EXPR( 表 达 式 ) 为 真 的 时 候 不 断 执 行 语 句 块 。 如 果 while 被 until 取 代 ,那 么 条 件 测 试 的 取 向 就 变 反 了 ; 也 就 是 说 , 它 只 有 在 EXPR 为 假 的 时 候 才 执 行 语 句 块 。 不过 , 在 第 一 个 执 行 文 本 之 前 先 要 测 试 条 件 。while 和 until 语 句 可 以 有 一 个 额 外 的 块 :continue 块 。 这 个 块 在 整 个 块 每 继 续 一 次 都执 行 一 次 , 不 管 是 退 出 第 一 个 块 还 是 用 一 个 明 确 的 next( 一 个 循 环 控 制 操 作 符 , 它 控 制 进入 下 一 句 文 本 )。 在 实 际 中 continue 块 用 得 并 不 多 , 但 是 有 它 可 以 让 我 们 严 格 地 定 义 下一 节 里 的 for 循 环 。和 我 们 稍 后 将 看 到 的 foreach 循 环 不 同 的 是 , 一 个 while 循 环 从 不 在 它 的 测 试 条 件 里 隐含 地 局 部 化 任 何 变 量 。 这 种 特 性 在 while 循 环 使 用 全 局 量 做 循 环 变 量 时 可 能 会 有 “ 很 有 趣 ”的 结 果 。 尤 其 是 你 可 以 看 看 第 二 章 “ 行 输 入 ( 尖 角 ) 操 作 符 ” 里 关 于 在 某 些 while 循 环 里 是如 何 隐 含 地 给 全 局 的 $_ 赋 值 的 例 子 , 以 及 如 何 如 何 明 确 地 局 部 化 $_ 的 例 子 。 不 过 , 对于 其 他 循 环 变 量 来 说 , 你 最 好 象 我 们 下 一 个 例 子 那 样 用 my 定 义 它 们 .在 一 个 while 或 者 until 语 句 的 测 试 条 件 里 定 义 的 变 量 只 是 在 该 语 句 块 或 由 该 测 试 条 件控 制 的 语 句 块 中 可 见 。 它 可 不 属 于 任 何 周 围 的 语 句 块 的 范 围 。 比 如 :while (my $line = ) {$line = lc $line;}continue {print $line;# 仍 然 可 见117


}# $line 现 在 超 出 范 围 外 了这 里 的 $line 的 范 围 从 它 在 控 制 表 达 式 里 定 义 开 始 一 直 延 伸 到 循 环 构 造 的 其 他 部 分 , 包 括continue 语 句 块 , 但 是 不 再 超 出 该 范 围 。 如 果 你 想 把 范 围 扩 得 更 大 , 请 在 循 环 之 前 定 义 该变 量 。4.3.2 for 循 环分 成 三 部 分 的 for 循 环 在 其 圆 括 弧 里 有 三 个 用 分 号 隔 离 的 表 达 式 。 这 些 表 达 式 分 别 代 表 循环 的 初 始 化 , 条 件 和 再 初 始 化 表 达 式 。 所 有 三 个 表 达 式 都 是 可 选 的 ( 不 过 分 号 不 是 ); 如 果省 略 了 它 们 , 则 条 件 总 是 为 真 。 因 此 三 部 分 的 for 表 达 式 可 以 用 对 应 的 while 循 环 来 代 替 。下 面 这 样 的 :LABEL:for( my $i = i; $i


}}是 一 样 的 , 只 不 过 实 际 上 没 有 外 层 的 块 。( 我 们 在 这 里 放 一 个 只 是 为 了 显 示 my 的 范 围 是如 何 被 限 制 的 。)如 果 你 想 同 时 使 用 两 个 变 量 , 只 需 要 用 逗 号 分 隔 平 行 的 表 达 式 即 可 :for ( $i = 0, $bit = 0; $i < 32; $i++, $bit


...}上 面 这 样 等 效 于 写 :whie (1) {...}如 果 你 因 为 无 限 循 环 的 表 示 法 而 烦 恼 , 那 我 们 要 指 出 的 是 你 总 是 可 以 在 任 何 一 点 退 出 循 环 ,只 需 要 明 确 的 使 用 一 个 象 last 这 样 的 循 环 控 制 操 作 符 即 可 , 当 然 , 如 果 你 为 巡 航 导 弹 写 代码 , 你 就 可 能 不 用 什 么 明 确 的 循 环 退 出 了 。 因 为 循 环 会 在 合 适 的 时 间 自 动 退 出 。( 译 注 : 导弹 掉 下 来 就 ...)4.3.3 foreach 循 环foreach 循 环 通 过 把 控 制 变 量 (VAR) 设 置 为 每 个 后 继 的 列 表 元 素 来 循 环 通 过 一 列 数 值 :foreach VAR (LIST) {...}foreach 键 字 只 是 for 键 字 的 一 个 同 义 词 , 所 以 , 只 要 你 觉 得 哪 个 可 读 性 更 好 , 你 就 可 以互 换 地 使 用 for 和 foreach。 如 果 省 略 VAR, 则 使 用 全 局 $_ 变 量 。( 别 担 心 ,<strong>Perl</strong> 可以 很 容 易 地 区 分 for(@ARGV) 和 for( $i=0; $i


print "$_\en"; sleep(1);}for $field (split /:/, $data) {# 任 何 LIST 表 达 式print "Field contains: `$field'\en";}foreach $key (sort keys %hash) {print "$key => $hash{$key}\en";}最 后 一 句 是 打 印 一 个 排 了 序 的 散 列 数 组 的 规 范 的 方 法 。 参 阅 第 二 十 九 章 里 的 键 字 和 排 序 记 录获 取 更 详 细 的 例 子 。在 foreach 里 , 你 没 有 办 法 获 知 你 位 于 列 表 的 何 处 。 你 可 以 通 过 把 前 面 的 元 素 保 留 在 一 个变 量 里 , 然 后 用 相 邻 的 元 素 与 之 进 行 比 较 来 获 取 当 前 位 置 , 但 是 有 时 候 你 就 是 得 用 一 个 带 脚标 的 三 部 分 for 循 环 来 获 取 当 前 位 置 。 毕 竟 ,for 循 环 还 是 有 其 特 点 的 。如 果 LIST 完 全 包 含 可 赋 值 元 素 ( 通 常 也 就 是 说 变 量 , 而 不 是 枚 举 常 量 ), 你 可 以 通 过 修改 循 环 内 的 VAR 来 修 改 每 个 变 量 。 这 是 因 为 foreache 循 环 的 索 引 变 量 隐 含 地 是 你 正 在逐 一 取 出 的 列 表 的 每 个 元 素 的 别 名 。 你 不 仅 可 以 现 场 修 改 单 个 列 表 , 你 还 可 以 修 改 在 一 个 列表 里 的 多 个 数 组 和 散 列 :foreach $pay (@salaries) {# 赋 予 8% 的 提 升$pay *= 1.08;}for (@christmas, @easter) {s/ham/turkey/; # 修 改 菜 单 ( 译 注 : 这 可 真 的 是 菜 单 )}121


s/ham/turkey/ for @christmas, @easter;# 和 上 面 一 样 的 东 西for ($scalar, @array, values %hash) {s/^\s+//;s/\s+$//;# 删 除 开 头 的 空 白# 删 除 结 尾 的 空 白}循 环 变 量 只 在 循 环 的 动 态 或 者 词 法 范 围 内 有 效 。 如 果 该 变 量 事 先 用 my 定 义 , 那 么 它 隐 含地 是 在 词 法 范 围 里 。 这 样 , 对 于 任 何 在 词 法 范 围 之 外 定 义 的 函 数 它 都 是 不 可 见 的 , 即 使 是 在循 环 里 调 用 的 函 数 也 看 不 到 这 个 变 量 。 不 过 , 如 果 在 范 围 里 没 有 词 法 定 义 , 那 么 循 环 变 量 就是 局 部 化 的 ( 动 态 范 围 ) 全 局 变 量 ; 这 样 就 允 许 循 环 内 调 用 的 函 数 访 问 它 。 另 外 , 循 环 变 量在 循 环 前 拥 有 的 值 将 在 循 环 退 出 之 后 自 动 恢 复 。如 果 你 愿 意 , 你 可 以 明 确 地 声 明 使 用 哪 种 变 量 ( 词 法 或 全 局 )。 这 样 让 你 的 代 码 的 维 护 人 员能 够 比 较 容 易 理 解 你 的 代 码 ; 否 则 , 他 们 就 得 在 一 个 封 闭 的 范 围 里 往 回 找 , 看 看 该 变 量 在 前面 声 明 为 什 么 :for my $i (1 .. 10) { ... }for our $Tick ( 1.. 10) { ...}# $i 总 是 在 词 法 范 围# $Tick 总 是 全 局当 定 义 伴 随 着 循 环 变 量 时 , 短 些 的 for 比 foreach 要 好 , 因 为 它 更 符 合 英 文 的 阅 读 习 惯 。下 面 是 一 个 C 或 者 JAVA 程 序 员 在 使 用 <strong>Perl</strong> 表 示 某 种 算 法 时 首 先 会 想 到 的 代 码 :for ( $i = 0; $i < @ary1; $i++) {for ( $j=0; $j $ary2[$j]) {last; # 没 法 到 外 层 循 环 :-(}$ary1[$i] += $ary2[$j];}122


# 这 里 是 last 把 我 带 到 的 地 方}而 这 里 是 老 练 的 <strong>Perl</strong> 程 序 员 干 的 :WID: foreach $this (@ary1) {JET: foreach $that (@ary2) {next WID if $this > $that;$this += $that;}}看 看 , 用 合 乎 <strong>Perl</strong> 习 惯 的 语 法 是 多 简 单 ? 这 样 更 干 净 , 更 安 全 , 更 快 。 更 干 净 是 因 为 它 较少 代 码 。 更 安 全 是 因 为 代 码 在 内 层 和 后 面 的 外 层 循 环 之 间 做 加 法 ; 第 二 段 代 码 不 会 被 意 外 地执 行 , 因 为 next( 下 面 解 释 ) 明 确 地 执 行 外 层 循 环 而 不 只 是 简 单 地 退 出 内 层 。 更 快 是 因 为<strong>Perl</strong> 在 执 行 foreach 语 句 时 比 等 效 的 for 循 环 快 , 因 为 元 素 是 直 接 访 问 的 , 而 不 是 通 过下 标 来 访 问 的 。当 然 , 你 熟 悉 哪 个 就 用 哪 个 。 要 知 道 " 回 字 有 四 种 写 法 "。和 while 语 句 相 似 ,foreach 语 句 也 可 以 有 一 个 continue 块 。 这 样 就 允 许 你 在 每 个 循环 之 后 执 行 一 小 段 代 码 , 不 管 你 是 用 普 通 的 方 法 到 达 那 里 还 是 用 一 个 next 到 达 的 。现 在 我 们 可 以 说 next 就 是 “ 下 一 个 ”。4.3.4 循 环 控 制我 们 说 过 , 你 可 以 在 一 个 循 环 上 放 一 个 LABEL( 标 记 ), 这 样 就 给 它 一 个 名 字 。 循 环 的 LABEL为 循 环 的 循 环 控 制 操 作 符 next,last, 和 redo 标 识 循 环 。LABEL 是 给 整 个 循 环 取 名 ,而 不 仅 仅 是 循 环 的 顶 端 。 因 此 , 一 个 引 用 了 循 环 ( 标 记 ) 的 循 环 控 制 操 作 符 实 际 上 并 不 是 “goto”( 跑 到 ) 循 环 标 记 本 身 。 就 计 算 机 而 言 , 该 标 记 完 全 可 以 放 在 循 环 的 尾 部 。 但 是 不 知 什么 原 因 人 们 喜 欢 把 标 记 放 在 东 西 的 顶 部 。循 环 的 典 型 命 名 是 命 名 为 每 次 循 环 所 处 理 的 项 目 。 这 样 和 循 环 控 制 操 作 符 交 互 地 很 好 , 因 为循 环 控 制 操 作 符 的 设 计 原 则 是 : 当 合 适 的 标 记 和 语 句 修 饰 词 一 起 使 用 时 , 它 读 起 来 应 该 象 英123


文 。 如 果 循 环 原 型 是 处 理 行 的 , 那 循 环 标 记 原 型 就 是 LINE:, 因 此 循 环 控 制 操 作 符 就 是 类似 这 样 的 东 西 :next LINE if / ^#/;# 丢 弃 注 释循 环 控 制 操 作 符 的 语 法 是 :last LABELnext LABELredo LABELLABEL 是 可 选 的 ; 如 果 忽 略 了 , 该 操 作 符 就 选 用 最 内 层 的 封 闭 循 环 。 但 是 如 果 你 想 跳 过 比一 层 多 的 ( 循 环 ), 那 你 就 必 须 用 一 个 LABEL 以 命 名 你 想 影 响 的 循 环 。LABEL 不 一 定 非在 你 的 词 法 范 围 内 不 可 ( 尽 管 它 应 该 在 词 法 范 围 里 )。 实 际 上 ,LABEL 可 以 在 你 的 动 态 范围 的 任 何 地 方 。 如 果 它 的 位 置 强 制 你 的 跳 转 超 出 了 一 个 eval 或 者 子 过 程 的 范 围 ,<strong>Perl</strong> ( 会根 据 需 要 ) 发 出 一 个 警 告 。就 象 在 一 个 函 数 里 你 可 以 想 要 几 个 return 就 要 几 个 return 一 样 , 你 也 可 以 在 循 环 里 想要 几 个 循 环 控 制 语 句 就 要 几 个 。 在 早 期 的 结 构 化 编 程 的 日 子 里 , 有 些 人 坚 持 认 为 循 环 和 子 过程 只 能 有 一 个 入 口 和 一 个 出 口 。 单 入 口 的 表 示 法 是 个 好 主 意 , 可 单 出 口 的 想 法 却 让 我 们 写 了许 多 非 自 然 的 代 码 。 许 多 程 序 都 包 括 在 决 策 树 里 漫 游 的 问 题 。 一 个 决 策 树 通 常 从 一 个 树 干 开始 , 但 通 常 以 许 多 叶 子 结 束 。 把 你 的 循 环 退 出 代 码 ( 还 有 函 数 返 回 代 码 ) 写 成 具 有 多 个 出 口是 解 决 你 的 问 题 的 很 自 然 的 做 法 。 如 果 你 在 合 适 的 范 围 里 定 义 你 的 变 量 , 那 么 所 有 东 西 在 合适 的 时 候 都 会 被 清 理 干 净 , 不 管 你 是 如 何 退 出 该 语 句 块 的 。last 操 作 符 立 即 退 出 当 事 循 环 。 如 果 有 continue 块 也 不 会 执 行 。 下 面 的 例 子 在 第 一 个 空白 行 撤 出 循 环 :LINE: while () {last LINE if /^$/;# 当 处 理 完 邮 件 头 后 退 出}next 操 作 符 忽 略 当 前 循 环 的 余 下 的 语 句 然 后 开 始 一 次 新 的 循 环 。 如 果 循 环 里 有 continue子 句 , 那 它 将 就 在 重 新 计 算 条 件 之 前 执 行 , 就 象 三 部 分 的 for 循 环 的 第 三 个 部 件 一 样 。 因此 continue 语 句 可 以 用 于 增 加 循 环 变 量 —— 即 使 是 在 循 环 的 部 分 语 句 被 next 终 止 了 的情 况 下 :LINE: while () {124


next LINE if /^#/;next LINE if /^$/;# 忽 略 注 释# 忽 略 空 白 行...} continue {$count++;}redo 操 作 符 在 不 重 新 计 算 循 环 条 件 的 情 况 下 重 新 开 始 循 环 语 句 块 。 如 果 存 在 continue 块也 不 会 执 行 。 这 个 操 作 符 通 常 用 于 那 些 希 望 在 刚 输 入 的 东 西 上 耍 点 小 伎 俩 的 程 序 。 假 设 你 正在 处 理 这 样 的 一 个 文 件 , 这 个 文 件 里 你 时 不 时 要 处 理 带 有 反 斜 杠 续 行 符 的 行 。 下 面 就 是 你 如何 利 用 rddo 来 干 这 件 事 :while () {chomp;if (s/\\$//) {$_ .= ;redo unless eof;# 不 超 过 每 个 文 件 的 eof}# 现 在 处 理 $_}上 面 的 代 码 是 下 面 这 样 更 明 确 ( 也 更 冗 长 ) 的 <strong>Perl</strong> 代 码 的 缩 写 版 本 :LINE: while (defined($line = )) {chomp($line);if ($line =~ s/\\$//) {$line .= ;redo LINE unless eof(ARGV);}125


# 现 在 处 理 $line}下 面 是 一 段 真 实 的 代 码 , 它 使 用 了 所 有 三 种 循 环 控 制 操 作 符 。 因 为 有 了 Getopts::* 模 块后 , 现 在 我 们 不 常 用 下 面 这 样 的 方 法 分 析 命 令 行 参 数 , 但 是 它 仍 然 是 一 个 在 命 名 的 嵌 套 循 环上 使 用 循 环 控 制 操 作 符 的 好 例 子 :ARG: while (@ARGV && $ARGV[0] =~ s/^-(?=.)//) {OPT: for (shift @ARGV) {m/^$/ && do { next ARG; };m/^-$/ && do { last ARG; };s/^d// && do { $Debug_Level++; redo OPT; };s/^l// && do { $Generate_Listing++; redo OPT; };s/^i(.*)// && do { $In_Place = $1 || ".bak"; next ARG; };say_usage("Unknown option: $_");}}还 有 一 个 关 于 循 环 控 制 操 作 符 的 要 点 。 你 可 能 已 经 注 意 到 我 们 没 有 把 它 们 称 为 “ 语 句 ”。 那 是因 为 它 们 不 是 语 句 —— 尽 管 和 其 他 表 达 式 一 样 , 它 们 可 以 当 语 句 用 。 你 甚 至 可 以 把 它 们 看 作那 种 只 是 导 致 控 制 流 改 变 的 单 目 操 作 符 。 因 此 , 你 可 以 在 表 达 式 里 任 何 有 意 义 的 地 方 使 用 它们 。 实 际 上 , 你 甚 至 可 以 在 它 们 没 有 意 义 的 地 方 使 用 它 们 。 我 们 有 时 候 看 到 这 样 的 错 误 代 码 :open FILE, $fileor warn "Can't open $file: $!\n", next FILE; # 错这 样 做 的 目 的 是 好 的 , 但 是 next FILE 会 被 分 析 为 warn 的 一 个 参 数 。 所 以 是 在 warn获 得 发 出 警 告 的 机 会 之 前 先 执 行 next。 这 种 情 况 , 我 们 很 容 易 用 一 些 圆 括 弧 通 过 把 warn列 表 操 作 符 转 换 成 warn 函 数 来 修 补 这 个 错 误 :open FILE, $fileor warn( "Can't open $file: $! \n"), next FILE; # 对 了126


不 过 , 你 会 觉 得 下 面 的 更 好 读 :unless(open FILE, $file) {warn "Can't open $file: $!\n";next FILE;}4.4 光 块一 个 BLOCK 本 身 ( 带 标 记 或 者 不 带 标 记 ) 等 效 于 一 个 执 行 一 次 的 循 环 。 所 以 你 可 以 用 last退 出 一 个 块 或 者 用 redo 重 新 运 行 块 ( 注 : 相 比 之 下 ,next 也 退 出 这 种 一 次 性 的 块 。 不 过有 点 小 区 别 :next 会 执 行 一 个 continue 块 , 而 last 不 会 。)。 不 过 请 注 意 , 对 于 eval{},sub{} 或 者 更 让 人 吃 惊 的 是 do{} 这 些 构 造 而 言 , 情 况 就 不 一 样 了 。 这 哥 仨 不 是 循 环 块 ,因 为 它 们 自 己 就 不 是 BLOCK; 它 们 前 面 的 键 字 把 它 们 变 成 了 表 达 式 中 的 项 , 只 不 过 碰 巧 包含 一 个 语 句 块 罢 了 。 因 为 它 们 不 是 循 环 块 , 所 以 不 能 给 它 们 打 上 标 记 用 以 循 环 控 制 等 用 途 。循 环 控 制 只 能 用 于 真 正 的 循 环 , 就 象 return 只 能 用 于 子 过 程 ( 或 者 eval ) 一 样 。循 环 控 制 也 不 能 在 一 个 if 或 unless 里 运 行 , 因 为 它 们 也 不 是 循 环 。 但 是 你 总 是 可 以 引 入一 付 额 外 的 花 括 弧 , 这 样 就 有 了 一 个 光 块 , 而 光 块 的 确 是 一 个 循 环 :if ( /pattern/) {{last if /alpha/;last if /beta/;last if /gamma/;# 在 这 里 处 理 一 些 只 在 if() 里 处 理 的 事 情}}下 面 是 如 何 利 用 一 个 光 块 实 现 在 do{} 构 造 里 面 使 用 循 环 控 制 操 作 符 的 例 子 。 要 next 或redo 一 个 do, 在 它 里 面 放 一 个 光 块 :do {{next if $x == $y;# 在 这 处 理 一 些 事 务127


}} until $x++ > $z;对 于 last 而 言 , 你 得 更 仔 细 :{do {last if $x = $y ** 2;# 在 这 里 处 理 一 些 事 务}while $x++


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


$def = 1;last SWITCH;};/^xyz/ && do {$xyz = 1;last SWITCH;};}甚 至 可 以 是 更 恐 怖 的 :if (/^ac/) {$abc = 1}elseif (/^def/) { $def = 1 }elseif (/^xyz/) { $xyz = 1 }else {$nothing = 1}在 下 面 的 例 子 里 , 请 注 意 last 操 作 符 是 如 何 忽 略 并 非 循 环 的 do{} 块 , 并 且 直 接 退 出 for循 环 的 :for ($very_nasty_long_names[$i++][$j++]->method()) {/this pattern/ and do { push @flags, '-e'; last; };/that one/ and do { push @flags, '-h'; last; };/something else/ and do {last;};die "unknown value: `$_'";}只 对 单 个 值 进 行 循 环 , 可 能 你 看 起 来 有 点 奇 怪 , 因 为 你 只 是 走 过 循 环 一 次 。 但 是 这 里 利 用for/foreach 的 别 名 能 力 做 一 个 临 时 的 , 局 部 的 $_ 赋 值 非 常 方 便 。 在 与 同 一 个 很 长 的 数 值进 行 重 复 地 比 较 的 时 候 , 这 样 做 更 容 易 敲 键 而 且 更 不 容 易 敲 错 。 这 样 做 避 免 了 再 次 计 算 表 达130


式 的 时 候 可 能 出 现 的 副 作 用 。 并 且 和 本 章 相 关 的 是 , 这 样 的 结 构 也 是 实 现 switch 或 case结 构 最 常 见 最 标 准 的 习 惯 用 法 。?: 操 作 符 的 级 联 使 用 也 可 以 起 到 简 单 的 分 支 作 用 。 这 里 我 们 再 次 使 用 for 的 别 名 功 能 , 把重 复 比 较 变 得 更 清 晰 :for ($user_color_perference) {$value = /red/? 0xff0000:/green//blue/? 0xff0000:? 0x0000ff:0x000000; # 全 不 是 用 黑 色}对 于 最 后 一 种 情 况 , 有 时 候 更 好 的 方 法 是 给 自 己 建 一 个 散 列 数 组 , 然 后 通 过 索 引 快 速 取 出 结果 。 和 我 们 刚 刚 看 到 的 级 联 条 件 不 同 的 是 , 散 列 可 以 扩 展 为 无 限 数 量 的 记 录 , 而 且 查 找 第 一个 比 查 找 最 后 一 个 的 时 间 不 会 差 到 哪 儿 去 , 缺 点 是 只 能 做 精 确 匹 配 , 不 能 做 模 式 匹 配 。 如 果你 有 这 样 的 散 列 数 组 :%color_map = (azure=> 0xF0FFFF,chartreuse => 0x7FFF00,lavendermagentaturquoise=> 0xE6E6FA,=> 0xFF00FF,=> 0x40E0D0,);那 么 精 确 的 字 串 查 找 跑 得 飞 快 :$value = $color_map{ lc $user_color_preference } || 0x000000;甚 至 连 复 杂 的 多 路 分 支 语 句 ( 每 个 分 支 都 涉 及 多 个 不 同 语 句 的 执 行 ) 都 可 以 转 化 成 快 速 的 查找 。 你 只 需 要 用 一 个 函 数 引 用 的 散 列 表 就 可 以 实 现 这 些 。 参 阅 第 九 章 , 数 据 结 构 , 里 的 “ 函数 散 列 ” 获 取 如 何 操 作 这 些 的 信 息 。131


4.5 goto尽 管 不 是 想 吓 唬 你 ( 当 然 也 不 是 想 安 慰 你 ),<strong>Perl</strong> 的 确 支 持 goto 操 作 符 。 有 三 种 goto 形式 :got LABLE,goto EXPR, 和 goto &NAME。goto LABEL 形 式 找 出 标 记 为 LABEL 的 语 句 并 且 从 那 里 重 新 执 行 。 它 不 能 用 于 跳 进 任 何需 要 初 始 化 的 构 造 , 比 如 子 过 程 或 者 foreach 循 环 。 它 也 不 能 跳 进 一 个 已 经 优 化 了 的 构 造( 参 阅 第 十 八 章 , 编 译 )。 除 了 这 两 个 地 方 之 外 ,goto 几 乎 可 以 用 于 跳 转 到 当 前 块 的 任 何地 方 或 者 你 的 动 态 范 围 ( 就 是 说 , 一 个 调 用 你 的 块 ) 的 任 何 地 方 。 你 甚 至 可 以 goto 到 子过 程 外 边 , 不 过 通 常 还 有 其 他 更 好 的 构 造 可 用 。<strong>Perl</strong> 的 作 者 从 来 不 觉 得 需 要 用 这 种 形 式 的goto( 在 <strong>Perl</strong> 里 就 是 这 样 ——C 就 单 说 了 )。goto EXPR 形 式 只 是 goto LABEL 的 一 般 形 式 。 它 期 待 表 达 式 生 成 一 个 标 记 名 称 , 这 个标 记 名 称 显 然 可 以 由 分 析 器 动 态 地 解 释 。 这 样 允 许 象 FORTRAN 那 样 计 算 goto, 但 是 如果 你 为 了 保 持 可 维 护 性 , 我 们 建 议 你 还 是 不 要 这 么 做 :goto(("FOO", "BAR", "GLARCH")[$i]); # 希 望 0


全 局 声 明 可 以 放 在 任 何 可 以 出 现 语 句 的 地 方 , 不 过 它 们 对 语 句 的 主 要 执 行 顺 序 没 有 影 响 --声 明 只 在 编 译 时 起 作 用 。这 意 味 着 你 不 能 做 条 件 的 子 过 程 和 / 或 格 式 声 明 。 你 可 能 会 想 用 一 个 运 行 时 的 if 来 屏 蔽 你的 声 明 , 使 之 不 为 编 译 器 所 见 , 但 这 是 不 可 能 的 , 因 为 只 有 解 释 器 关 心 那 些 条 件 。 不 管 出 现在 什 么 地 方 , 子 过 程 和 格 式 声 明 ( 还 有 use 和 no 声 明 ) 都 只 有 编 译 器 能 够 看 到 。全 局 声 明 通 常 出 现 在 你 的 程 序 的 开 头 或 者 结 尾 , 或 者 是 放 在 其 它 的 文 件 里 。 不 过 , 如 果 你 声明 的 是 词 法 范 围 的 变 量 ( 见 下 节 ), 而 且 你 还 希 望 你 的 格 式 或 者 子 过 程 能 够 访 问 某 些 私 有 变量 , 那 你 就 得 保 证 你 的 声 明 落 在 这 些 变 量 声 明 的 范 围 之 内 。请 注 意 我 们 偷 偷 地 从 讲 声 明 转 移 到 了 讲 定 义 。 有 时 候 , 把 子 过 程 的 声 明 和 定 义 分 开 能 帮 我 们忙 。 这 两 个 概 念 语 义 上 的 唯 一 的 区 别 是 定 义 包 含 一 个 要 执 行 的 代 码 块 BLOCK, 而 声 明 没 有 。( 如 果 一 个 子 过 程 没 有 声 明 部 分 , 那 么 它 的 定 义 就 是 它 的 声 明 。) 把 定 义 从 声 明 里 面 剥 离 ,就 允 许 你 把 子 过 程 的 声 明 放 在 开 头 而 把 其 定 义 放 在 后 面 ( 而 你 的 词 法 范 围 的 变 量 声 明 放 在 中间 ):sub count (@);my $x;# 现 在 编 译 器 知 道 如 何 调 用 count()。# 现 在 编 译 器 知 道 词 法 变 量$x = count(3,2,1); # 编 译 器 可 以 核 实 函 数 调 用sub count (@) { @_ }# 现 在 编 译 器 知 道 count() 是 什 么 意 思 了正 如 上 面 例 子 显 示 的 那 样 , 子 过 程 在 调 用 它 们 之 前 不 用 定 义 也 能 编 译 ,( 实 际 上 , 如 果 你 使用 自 动 装 载 技 术 , 定 义 甚 至 可 以 推 迟 到 首 次 调 用 ), 但 是 声 明 子 过 程 可 以 以 各 种 方 式 协 助 编译 器 , 并 且 给 你 更 多 调 用 它 们 的 选 择 。声 明 一 个 子 过 程 后 允 许 你 不 带 圆 括 弧 使 用 它 , 好 象 它 是 一 个 内 建 的 操 作 符 一 样 。( 我 们 在 上面 的 例 子 里 用 了 圆 括 弧 , 实 际 上 我 们 可 以 不 用 。) 你 可 以 只 声 明 而 不 定 义 子 过 程 , 只 需 :sub myname;$me = myname $0or die "can't get myname";这 样 的 空 定 义 把 函 数 定 义 成 一 个 列 表 操 作 符 , 但 不 是 单 目 操 作 符 , 所 以 上 面 用 了 or 而 不 是。 操 作 符和 列 表 操 作 符 之 间 的 联 系 太 紧 密 了 , 以 至 于 基 本 上 只 能 用 于 列 表 操 作 符 后 面 , 当 然 , 你 还 是可 以 在 列 表 操 作 符 参 数 周 围 放 上 圆 括 弧 , 让 列 表 操 作 符 表 现 得 更 象 函 数 调 用 来 解 决 这 个 问题 。 另 外 , 你 可 以 用 原 型 ($) 把 子 过 程 转 化 成 单 目 操 作 符 :133


sub myname ($);$me = myname $0|| die "can't get myname";这 样 就 会 按 照 你 想 象 的 那 样 分 析 了 , 不 过 你 还 是 应 该 养 成 在 这 种 情 况 下 用 or 的 习 惯 。 有 关原 型 的 更 多 内 容 , 参 阅 第 六 章 , 子 过 程 。有 时 候 你 的 确 需 要 定 义 子 过 程 , 否 则 你 在 运 行 时 会 收 到 一 个 错 误 , 说 你 调 用 了 一 个 没 有 定 义的 子 过 程 。 除 了 自 己 定 义 子 过 程 外 , 还 有 几 个 方 法 从 其 它 地 方 引 入 定 义 。你 可 以 用 简 单 的 require 语 句 从 其 它 文 件 装 载 定 义 , 在 <strong>Perl</strong> 4 里 , 这 是 装 载 文 件 的 最 好方 法 , 但 是 这 种 方 法 有 两 个 问 题 。 首 先 , 其 他 文 件 通 常 会 向 一 个 它 们 自 己 选 定 的 包 ( 一 个 符号 表 ) 里 插 入 子 过 程 名 , 而 不 是 向 你 的 包 里 插 。 其 次 ,require 在 运 行 时 起 作 用 , 这 对 调用 它 起 声 明 作 用 的 文 件 来 说 有 点 太 晚 了 。 不 过 , 有 时 候 你 要 的 就 是 推 迟 的 装 载 。引 入 声 明 和 定 义 的 更 好 的 办 法 是 使 用 use 声 明 , 它 可 以 在 编 译 时 就 require 各 模 块 ( 因为 use 算 做 BEGIN 块 ), 然 后 你 就 可 以 把 一 些 模 块 的 声 明 引 入 到 你 的 程 序 里 面 来 了 。 所以 可 以 把 use 看 成 某 种 类 型 的 全 局 声 明 , 因 为 它 在 编 译 时 把 名 字 输 入 到 你 自 己 的 ( 全 局 )包 里 面 , 就 好 象 你 是 自 己 声 明 的 一 样 。 参 阅 第 十 章 , 包 , 的 " 符 号 表 " 一 节 , 看 看 包 之 间 的 传输 运 做 的 低 层 机 制 ; 第 十 一 章 , 模 块 , 看 看 如 何 设 置 一 个 模 块 的 输 入 和 输 出 ; 以 及 第 十 八 章 ,看 看 BEGIN 和 它 的 表 兄 弟 CHECK,INIT, 和 END 的 解 释 。 它 们 在 某 种 程 度 上 也 是 全局 声 明 , 因 为 它 们 在 编 译 是 做 处 理 , 而 且 具 有 全 局 影 响 。4.7 范 围 声 明和 全 局 声 明 类 似 , 词 法 范 围 声 明 也 是 在 编 译 时 起 作 用 的 。 和 全 局 声 明 不 同 的 是 , 词 法 范 围 声明 的 作 用 范 围 是 从 声 明 开 始 到 闭 合 范 围 的 最 里 层 ( 块 , 文 件 , 或 者 eval-- 以 先 到 者 为 准 )。这 也 是 为 什 么 我 们 称 它 为 词 法 范 围 , 尽 管 " 文 本 范 围 " 可 能 更 准 确 些 , 因 为 词 法 范 围 这 个 词 实在 和 词 法 没 什 么 关 系 。 但 是 全 世 界 的 计 算 机 科 学 家 都 知 道 " 词 法 范 围 " 是 什 么 意 思 , 所 以 在 这里 我 们 还 是 用 这 个 词 。<strong>Perl</strong> 还 支 持 动 态 范 围 声 明 。 动 态 范 围 同 样 也 伸 展 到 最 里 层 的 闭 合 块 , 但 是 这 里 的 " 闭 合 " 是运 行 时 动 态 定 义 的 , 而 不 是 象 文 本 那 样 在 编 译 时 定 义 。 用 另 外 一 种 方 式 来 说 , 语 句 块 通 过 调用 其 他 语 句 块 实 现 动 态 地 嵌 套 , 而 不 是 通 过 包 含 其 他 语 句 块 来 实 现 嵌 套 。 这 样 的 动 态 嵌 套 可能 在 某 种 程 度 上 和 嵌 套 的 文 本 范 围 相 关 , 但 是 这 两 者 通 常 是 不 一 样 的 , 尤 其 是 在 调 用 子 过 程的 时 候 。我 们 曾 经 说 过 use 的 一 些 方 面 可 以 认 为 是 全 局 声 明 , 但 是 use 的 其 他 方 面 却 是 词 法 范 围的 。 特 别 是 ,use 不 仅 输 入 包 的 符 号 , 而 且 还 实 现 了 许 多 让 人 不 可 思 议 的 编 译 器 暗 示 , 也134


就 是 我 们 说 的 用 法 (pragmas)。 大 多 数 用 法 是 词 法 范 围 的 , 包 括 use strict 'vars' 用 法 ,这 个 用 法 强 制 你 在 使 用 前 先 声 明 变 量 。 参 阅 后 面 的 “ 用 法 ” 节 。很 有 意 思 的 是 , 尽 管 包 是 一 个 全 局 入 口 , 包 声 明 本 身 是 词 法 范 围 的 。 但 是 一 个 package 声明 只 是 为 闭 合 块 的 余 下 部 分 声 明 此 缺 省 包 的 身 份 。<strong>Perl</strong> 会 到 这 个 包 中 查 找 未 声 明 的 , 未 修饰 的 变 量 名 ( 注 : 还 有 未 定 义 的 子 过 程 , 文 件 句 柄 , 目 录 句 柄 和 格 式 )。 换 句 话 说 , 实 际 上从 来 没 有 声 明 什 么 包 , 只 是 当 你 引 用 了 某 些 属 于 那 些 包 的 东 西 的 时 候 才 突 然 出 现 。 当 然 这 就是 <strong>Perl</strong> 的 风 格 。4.7.1 范 围 变 量 声 明本 章 剩 下 的 大 部 分 内 容 是 关 于 使 用 全 局 变 量 的 。 或 者 换 句 话 说 , 是 关 于 ‘ 不 ’ 使 用 全 局 变 量 的 。有 各 种 各 样 的 声 明 可 以 帮 助 你 不 使 用 全 局 变 量 —— 或 者 至 少 不 会 愚 蠢 地 使 用 它 们 。我 们 已 经 提 到 过 package 定 义 , 它 在 很 早 以 前 就 引 入 <strong>Perl</strong> 了 , 这 样 就 允 许 全 局 量 可 以分 别 放 到 独 立 的 包 里 。 对 于 某 些 变 量 来 说 , 这 个 方 法 非 常 不 错 。 库 , 模 块 和 类 都 用 包 来 存 储它 们 的 接 口 数 据 ( 以 及 一 些 它 们 的 半 私 有 数 据 ) 以 避 免 和 你 的 主 程 序 或 者 其 他 模 块 的 变 量 或者 函 数 冲 突 。 如 果 你 看 到 某 人 写 到 $Some::stuff( 注 : 或 者 $Some'stuff, 不 过 我 们 不鼓 励 这 么 写 ), 他 们 是 在 使 用 来 自 包 Some 的 标 量 变 量 $stuff。 参 阅 第 十 章 。如 果 这 么 干 活 的 话 ,<strong>Perl</strong> 程 序 随 着 变 量 的 增 长 会 很 快 变 得 不 好 用 。 好 在 <strong>Perl</strong> 的 三 种 范 围声 明 让 它 很 容 易 做 下 面 这 些 事 : 创 建 私 有 变 量 ( 用 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 把 名 字 和 值 都 限 于 一 个 范 围 。135


这 些 构 造 都 是 可 以 赋 值 的 , 当 然 它 们 对 值 的 实 际 处 理 是 不 同 的 , 因 为 它 们 有 不 同 的 存 储 值 的机 制 。 如 果 你 不 给 它 们 赋 任 何 值 ( 象 我 们 上 面 那 样 ), 它 们 也 有 一 些 区 别 :my 和 local 把涉 及 的 变 量 初 始 化 为 undef 或 (), 另 一 方 面 ,our 不 修 改 与 之 相 联 的 全 局 变 量 的 当 前 值 。从 语 义 上 来 讲 ,my,our 和 local 都 只 是 简 单 的 左 值 表 达 式 的 修 饰 词 ( 类 似 形 容 词 )。 当你 给 一 个 被 修 饰 的 左 值 赋 值 时 , 修 饰 词 并 不 改 变 左 值 是 标 量 状 态 还 是 列 表 状 态 。 想 判 断 赋 值将 按 照 什 么 样 的 方 式 运 行 , 你 只 要 假 设 修 饰 词 不 存 在 就 行 了 。 所 以 :my ($foo) = ;my @array = ;给 右 手 边 提 供 了 一 个 列 表 环 境 , 而 :my $foo = ;提 供 了 一 个 标 量 环 境 。修 饰 词 比 逗 号 绑 定 得 更 紧 密 ( 也 有 更 高 优 先 级 )。 下 面 的 例 子 错 误 地 声 明 了 一 个 变 量 , 而 不是 两 个 , 因 为 跟 在 列 表 后 面 的 修 饰 词 没 有 用 圆 括 弧 包 围 。my $foo, $bar = 1; # 错上 面 和 下 面 的 东 西 效 果 一 样 :my $foo; $bar = 1;如 果 打 开 警 告 的 话 , 你 会 收 到 一 个 关 于 这 个 错 误 的 警 告 。 你 可 以 用 -w 或 -W 命 令 行 开 关打 开 警 告 , 或 者 用 后 面 在 “ 用 法 ” 里 解 释 的 use warning 声 明 。通 常 , 应 尽 可 能 在 变 量 所 适 合 的 最 小 范 围 内 定 义 它 们 。 因 为 在 流 控 制 语 句 里 面 定 义 的 变 量 只能 在 该 语 句 控 制 的 块 里 面 可 见 , 因 此 , 它 们 的 可 视 性 就 降 低 了 。 同 样 , 这 样 的 英 文 读 起 来 也更 通 顺 。sub check_warehouse {for my $widget (our @Current_Inventory) {print "I have a $widget in stock today.\n";}}136


最 常 见 的 声 明 形 式 是 my, 它 定 义 词 法 范 围 的 变 量 ; 这 些 变 量 的 名 字 和 值 都 存 储 在 当 前 范围 的 临 时 中 间 变 量 暂 存 器 里 , 而 且 不 能 全 局 访 问 。 与 之 相 近 的 是 our 声 明 , 它 在 当 前 范 围输 入 一 个 词 法 范 围 的 名 字 , 就 象 my 一 样 , 但 是 实 际 上 却 引 用 一 个 全 局 变 量 , 任 何 人 如 果想 看 地 话 都 可 以 访 问 。 换 句 话 说 , 就 是 伪 装 成 词 汇 的 全 局 变 量 。另 外 一 种 形 式 的 范 围 , 动 态 范 围 , 应 用 于 local 变 量 , 这 种 变 量 实 际 上 是 全 局 变 量 , 除 了“local( 局 部 )” 的 字 眼 以 外 和 局 部 的 中 间 变 量 暂 存 器 没 有 任 何 关 系 。4.7.2 词 法 范 围 的 变 量 :my为 帮 助 你 摆 脱 维 护 全 局 变 量 的 痛 苦 ,<strong>Perl</strong> 提 供 了 词 法 范 围 的 变 量 , 通 常 简 称 为 词 汇 。 和 全局 变 量 不 同 , 词 汇 保 证 你 的 隐 私 。 假 如 你 没 有 分 发 这 些 私 有 变 量 的 引 用 ( 引 用 可 以 间 接 地 处理 这 些 私 有 变 量 ), 你 就 可 以 确 信 对 这 些 私 有 变 量 的 访 问 仅 限 于 你 的 程 序 里 面 的 一 个 分 离 的 ,容 易 标 识 的 代 码 段 。 这 也 是 为 什 么 我 们 使 用 关 键 字 my 的 原 因 。一 个 语 句 序 列 可 以 包 含 词 法 范 围 变 量 的 声 明 。 习 惯 上 这 样 的 声 明 放 在 语 句 序 列 的 开 头 , 但 我们 并 不 要 求 这 样 做 。 除 了 在 编 译 时 声 明 变 量 名 字 以 外 , 声 明 所 起 的 作 用 就 象 普 通 的 运 行 时 语句 : 它 们 每 一 句 都 被 仔 细 地 放 在 语 句 序 列 中 , 就 好 象 它 们 是 没 有 修 饰 词 的 普 通 语 句 一 样 :my $name = "fred";my @stuff = ("car", "house", "club");my ($vehicle, $home, $tool) = @stuff;这 些 词 法 变 量 对 它 们 所 处 的 最 近 的 闭 合 范 围 以 外 的 世 界 而 言 是 完 全 不 可 见 的 。 和 local 的动 态 范 围 效 果 ( 参 阅 下 一 节 ) 不 同 的 是 , 词 汇 对 任 何 在 它 的 范 围 内 调 用 的 子 过 程 都 是 不 可 见的 。 甚 至 相 同 的 子 过 程 调 用 自 身 或 者 从 别 处 调 用 也 如 此 —— 每 个 子 过 程 的 实 例 都 得 到 自 己 的词 法 变 量 “ 中 间 变 量 暂 存 器 ”。和 块 范 围 不 同 的 是 , 文 件 范 围 不 能 嵌 套 ; 也 没 有 “ 闭 合 ” 的 东 西 —— 至 少 没 有 文 本 上 的 闭 合 。如 果 你 用 do,require 或 者 use 从 一 个 独 立 的 文 件 装 载 代 码 , 那 么 在 那 个 文 件 里 的 代 码无 法 访 问 你 的 词 汇 , 同 样 你 也 不 能 访 问 那 个 文 件 的 词 汇 。但 是 , 任 何 一 个 文 件 内 部 的 范 围 ( 甚 至 文 件 本 身 ) 都 是 平 等 的 。 通 常 , 一 个 比 子 过 程 定 义 大一 些 的 范 围 对 你 很 有 用 , 因 为 这 样 你 就 可 以 在 有 限 的 一 个 子 过 程 集 合 里 共 享 私 有 变 量 。 这 就是 你 创 建 在 C 里 被 认 为 是 “static”( 静 态 ) 的 变 量 的 方 法 :{my $state = 0;137


sub on { $state = 1}sub off { $state = 0}sub toggle { $state =!$state }}eval STRING 操 作 符 同 样 也 作 为 嵌 套 范 围 运 行 , 因 为 eval 里 的 代 码 可 以 看 到 其 调 用 者 的词 汇 ( 只 要 其 名 字 不 被 eval 自 己 范 围 里 的 相 同 声 明 隐 藏 )。 匿 名 子 过 程 也 可 以 在 它 们 的闭 合 范 围 内 访 问 任 意 词 汇 ; 如 果 是 这 样 , 那 么 这 些 匿 名 子 过 程 就 是 所 谓 的 闭 包 ( 注 : 一 个 记忆 用 词 , 表 示 在 “ 闭 合 范 围 ” 和 “ 闭 包 ” 之 间 的 普 通 元 素 。( 闭 包 的 真 实 定 义 源 自 一 个 数 学 概 念 ,该 概 念 考 虑 数 值 集 合 和 对 那 些 数 值 的 操 作 的 完 整 性 。)) 结 合 这 两 种 概 念 , 如 果 一 个 块 eval了 一 个 创 建 一 个 匿 名 子 过 程 的 字 串 , 该 子 过 程 就 成 为 可 以 同 时 访 问 eval 和 该 块 的 闭 包 ,甚 至 在 eval 和 该 块 退 出 后 也 是 如 此 。 参 阅 第 八 章 , 引 用 , 里 的 “ 闭 包 ” 节 。新 声 明 的 变 量 ( 或 者 是 值 -- 如 果 你 使 用 的 是 local) 在 声 明 语 句 之 后 才 可 见 。 因 此 你 可 以 用下 面 的 方 法 给 一 个 变 量 做 镜 像 :my $x = $x;这 句 话 把 新 的 内 部 $x 初 始 化 为 当 前 值 $x, 不 管 $x 的 当 前 含 义 是 全 局 还 是 词 汇 。( 如果 你 没 有 初 始 化 新 变 量 , 那 么 它 从 一 个 未 定 义 或 者 空 值 开 始 。)定 义 一 个 任 意 名 字 的 词 汇 变 量 隐 藏 了 任 何 以 前 定 义 的 同 名 词 汇 。 它 也 同 时 隐 藏 任 何 同 名 无 修饰 全 局 变 量 , 不 过 你 总 是 可 以 通 过 明 确 声 明 全 局 变 量 所 处 的 包 的 方 法 来 访 问 全 局 变 量 , 比 如 ,$PackageName::varname。4.7.3 词 法 范 围 全 局 声 明 :our有 一 个 访 问 全 局 变 量 的 更 好 的 方 法 就 是 our 声 明 , 尤 其 那 些 在 use strice 声 明 下 运 行 的程 序 和 模 块 。 这 个 声 明 也 是 词 法 范 围 内 的 , 因 为 它 的 应 用 范 围 只 扩 展 到 当 前 范 围 的 结 尾 。 但与 词 法 范 围 的 my 或 动 态 范 围 的 local 不 同 的 是 :our 并 不 隔 离 当 前 词 法 或 者 动 态 范 围 里的 任 何 东 西 。 相 反 , 它 在 当 前 环 境 里 提 供 一 个 访 问 全 局 变 量 的 途 径 , 它 把 所 有 同 名 词 汇 隐 藏起 来 ( 否 则 这 些 词 汇 会 为 你 隐 藏 全 局 变 量 )。 在 这 个 方 面 ,our 变 量 和 my 变 量 作 用 相 同 。如 果 你 把 our 声 明 放 在 任 何 花 括 弧 分 隔 的 块 的 外 面 , 它 的 范 围 就 延 续 到 当 前 编 译 单 元 的 结尾 。 通 常 , 人 们 只 是 把 它 放 在 一 个 子 过 程 定 义 的 顶 端 以 表 明 他 们 在 访 问 全 局 变 量 :sub check_warehouse {138


our @Current_Inventory;my $widget;foreach $widget (@Current_Inventory) {print "I have a $widget in stock today.\n";}}因 为 全 局 变 量 比 私 有 变 量 有 更 长 的 生 命 期 和 更 广 的 可 见 范 围 , 所 以 与 临 时 变 量 相 比 我 们 喜 欢为 它 们 使 用 更 长 和 更 鲜 明 的 名 字 。 如 果 你 有 意 遵 循 这 个 习 惯 , 它 可 以 象 use strict 一 样 起到 制 约 全 局 量 使 用 的 效 果 , 尤 其 是 对 那 些 不 愿 意 敲 字 的 人 。重 复 的 our 声 明 并 不 意 味 着 嵌 套 。 每 个 嵌 套 的 my 会 生 成 一 个 新 变 量 , 每 个 嵌 套 的 local也 生 成 一 个 新 变 量 。 但 是 每 次 你 使 用 our 的 时 候 , 你 实 际 上 是 说 同 一 个 变 量 , 不 管 你 有 没有 嵌 套 。 当 你 给 一 个 our 变 量 赋 值 时 , 其 作 用 在 整 个 声 明 范 围 都 起 作 用 。 这 是 因 为 our 从不 创 建 数 值 ; 它 只 是 提 供 一 种 有 限 制 地 访 问 全 局 量 的 形 式 , 该 形 式 永 远 存 活 :our $PROGRAM_NAME = "waiter";{our $PROGRAM_NAME = "server";# 这 里 调 用 的 代 码 看 到 的 是 "server"}# 这 里 执 行 的 代 码 看 到 的 仍 然 是 "server".而 对 于 my 和 local 来 说 , 在 块 之 后 , 外 层 变 量 或 值 再 次 可 见 :my $i = 10;{my $i = 99;...}139


# 这 里 编 译 的 代 码 看 到 外 层 变 量 。local $PROGRAM_NAME = "waiter";{local $PROGRAM_NAME = "server";# 这 里 的 代 码 看 到 "server"....}# 这 里 执 行 的 代 码 再 次 看 到 "watier"通 常 只 给 our 赋 值 一 次 , 可 能 是 在 程 序 或 者 模 块 的 非 常 顶 端 的 位 置 , 或 者 是 很 少 见 地 用local 前 缀 our, 获 取 一 个 local 自 己 的 变 量 :{local our @Current_Inventory = qw(bananas);check_warehouse();# 我 们 有 香 蕉 (bananas)}4.7.4 动 态 范 围 变 量 :local在 一 个 全 局 变 量 上 使 用 local 操 作 符 的 时 候 , 在 每 次 执 行 local 的 时 候 都 给 该 全 局 量 一 个临 时 值 , 但 是 这 并 不 影 响 该 变 量 的 全 局 可 视 性 。 当 程 序 抵 达 动 态 范 围 的 末 尾 时 , 临 时 值 被 抛弃 然 后 恢 复 原 来 的 值 。 但 它 仍 然 是 一 个 全 局 变 量 , 只 是 在 执 行 那 个 块 的 时 候 碰 巧 保 存 了 一 个临 时 值 而 已 。 如 果 你 在 该 全 局 变 量 包 含 临 时 值 时 调 用 其 他 函 数 , 而 且 该 函 数 访 问 了 该 全 局 变量 , 那 么 它 看 到 的 将 是 临 时 值 , 而 不 是 初 始 值 。 换 句 话 说 , 该 函 数 处 于 你 的 动 态 范 围 , 即 使它 并 不 处 在 你 的 词 法 范 围 也 如 此 ( 注 : 这 就 是 为 什 么 有 时 候 把 词 法 范 围 叫 做 静 态 范 围 : 这 样可 以 与 动 态 范 围 相 比 并 且 突 显 它 们 的 编 译 时 决 定 性 。 不 要 把 这 个 术 语 的 用 法 和 C 或 C++里 的 static 的 用 法 混 淆 。 这 个 术 语 用 得 太 广 泛 了 , 也 是 我 们 避 免 使 用 它 的 原 因 。)如 果 你 有 个 看 起 来 象 这 样 的 local:{140


local $var = $newvalue;some_func();...}你 完 全 可 以 认 为 它 是 运 行 时 的 赋 值 :{$oldvalue = $var;$var = $newvalue;some_func();...}continue {$var = $oldvalue;}区 别 是 如 果 用 local, 那 么 不 管 你 是 如 何 退 出 该 块 的 , 变 量 值 都 会 恢 复 到 原 来 的 , 即 使 你 提前 从 那 个 范 围 return( 返 回 )。 变 量 仍 然 是 同 样 的 全 局 变 量 , 其 值 则 取 决 于 函 数 是 从 从 哪个 范 围 调 用 的 。 这 也 是 为 什 么 我 们 称 之 为 动 态 范 围 —— 因 为 它 是 在 运 行 时 修 改 。和 my 一 样 , 你 也 可 以 用 一 份 同 样 的 全 局 变 量 的 拷 贝 来 初 始 化 一 个 local。 在 子 过 程 执 行过 程 中 ( 以 及 任 何 在 该 过 程 中 的 调 用 , 因 为 显 然 仍 将 看 到 的 是 动 态 范 围 的 全 局 变 量 ) 对 那 个变 量 的 任 何 改 变 都 会 在 子 过 程 返 回 的 时 候 被 丢 弃 。 当 然 , 你 最 好 还 是 对 你 干 的 事 加 注 释 :# WARNING: Changes are temporary to this dynamic scope.local $Some_Global = $Some_Global;不 管 一 个 全 局 变 量 是 用 our 明 确 声 明 的 , 还 是 突 然 出 现 的 , 还 是 它 保 存 一 个 注 定 要 在 范 围退 出 后 被 丢 弃 掉 的 local 变 量 , 它 对 你 的 整 个 程 序 而 言 仍 然 是 完 全 可 见 的 。 对 小 程 序 来 说 ,141


这 样 挺 好 ; 但 是 对 大 程 序 来 说 , 你 很 快 就 会 忘 记 代 码 中 在 那 里 使 用 了 全 局 变 量 。 如 果 你 愿 意 ,你 可 以 禁 止 随 机 地 使 用 全 局 变 量 , 你 可 以 用 下 一 节 描 述 的 use strict 'vars' 用 法 来 达 到 这个 目 的 。尽 管 my 和 local 都 提 供 了 某 种 程 度 的 保 护 , 总 的 来 说 你 还 是 应 该 优 先 使 用 my。 当 然 ,有 时 候 你 不 得 不 用 local 来 临 时 改 变 一 个 现 有 全 局 变 量 的 值 , 就 象 我 们 在 第 二 十 八 章 , 特殊 名 字 , 里 列 出 来 的 那 样 。 只 有 字 母 数 字 标 识 符 才 能 处 于 词 法 范 围 , 而 那 些 特 殊 变 量 有 许 多并 不 是 严 格 的 字 母 数 字 。 你 也 需 要 用 local 来 对 一 个 包 的 符 号 表 做 临 时 的 修 改 —— 象 我 们在 第 十 章 “ 符 号 表 ” 里 显 示 的 那 样 。 最 后 , 你 可 以 把 local 用 在 数 组 或 散 列 的 单 个 元 素 或 者整 个 片 段 上 。 甚 至 当 数 组 或 散 列 是 词 法 变 量 的 时 候 也 能 这 么 干 , 这 时 候 是 把 local 的 动 态范 围 建 筑 在 那 些 词 法 ( 变 量 ) 的 上 层 。 我 们 不 会 在 这 里 就 local 的 语 义 讲 得 太 多 。 参 阅 第 二十 九 章 的 local 获 取 更 多 知 识 。4.8 用 法 (pragmas)许 多 编 程 语 言 允 许 你 给 编 译 器 一 些 提 示 或 暗 示 。 在 <strong>Perl</strong> 里 , 这 些 暗 示 是 用 use 声 明 交 给编 译 器 的 。 一 些 用 法 是 :use warning;use strict;use integer;use bytes;use constant pi => ( 4* atan2(1,1) );<strong>Perl</strong> 的 用 法 都 在 第 三 十 一 章 , 用 法 模 块 , 里 描 述 。 这 里 我 们 只 是 讲 几 个 和 本 章 的 内 容 关 系非 常 密 切 的 用 法 。虽 然 有 几 个 用 法 是 全 局 声 明 , 它 们 对 全 局 变 量 或 者 对 当 前 包 有 影 响 , 但 其 他 大 部 分 用 法 都 是词 法 范 围 里 声 明 的 , 其 影 响 范 围 只 是 伸 展 到 闭 合 块 的 结 尾 , 文 件 或 者 eval( 先 到 为 准 )。一 个 词 法 范 围 的 用 法 可 以 在 内 层 范 围 里 用 no 声 明 取 消 ,no 的 用 途 和 use 一 样 , 只 是 作用 正 相 反 。4.8.1 控 制 警 告为 了 显 示 这 些 东 西 如 何 运 行 , 我 们 将 操 纵 warnings 用 法 , 告 诉 <strong>Perl</strong> 是 否 就 有 问 题 的 东西 发 出 警 告 :142


use warnings;# 在 这 里 打 开 警 告 , 作 用 到 文 件 结 束...{no warnings;# 关 闭 警 告 , 作 用 到 块 结 束...}# 警 告 在 这 里 自 动 恢 复 打 开 状 态一 旦 打 开 警 告 ,<strong>Perl</strong> 就 会 抱 怨 你 的 变 量 只 用 了 一 次 , 变 量 的 声 明 屏 蔽 了 同 范 围 内 的 其 他 声明 , 字 串 到 数 字 的 非 法 转 换 , 把 未 定 义 的 值 用 做 合 法 字 串 或 数 字 , 试 图 写 入 一 个 你 以 只 读 方式 ( 或 者 根 本 没 有 打 开 ) 打 开 的 文 件 和 许 多 其 他 的 问 题 。 这 些 问 题 在 第 三 十 三 章 , 诊 断 信 息 ,里 有 描 述 。use warning 用 法 是 优 选 的 控 制 警 告 的 方 法 。 老 的 程 序 可 能 只 用 -w 命 令 行 开 关 或 者 修 改$^W 全 局 变 量 :{local $^W = 0;...}最 好 还 是 用 use warnings 和 no warnings 用 法 。 用 法 更 好 些 的 原 因 是 , 首 先 它 是 在 编译 时 作 用 ; 其 次 它 是 词 法 声 明 , 所 以 不 会 影 响 它 不 该 影 响 的 代 码 ; 最 后 , 它 提 供 了 对 离 散 的警 告 范 畴 精 细 的 控 制 ( 尽 管 我 们 没 有 在 这 些 简 单 的 例 子 中 向 你 演 示 这 些 )。 更 多 关 于wranings 用 法 的 知 识 , 包 括 如 何 把 一 般 警 告 转 换 成 致 命 错 误 , 如 何 把 警 告 全 局 地 打 开 , 覆盖 所 有 说 no 的 模 块 的 设 置 等 , 请 参 阅 第 三 十 一 章 ,use warnings。4.8.2 控 制 全 局 变 量 的 使 用另 外 一 个 常 见 的 声 明 是 use strict 用 法 , 它 有 几 个 功 能 , 其 中 之 一 是 控 制 全 局 量 的 使 用 。通 常 ,<strong>Perl</strong> 允 许 你 在 需 要 时 随 时 随 地 创 建 全 局 变 量 ( 或 者 是 覆 盖 旧 变 量 )。 就 是 说 缺 省 时不 需 要 变 量 声 明 。 因 为 不 加 限 制 地 使 用 全 局 变 量 会 导 致 程 序 或 模 块 维 护 的 困 难 , 所 以 有 时 候你 可 能 想 限 制 全 局 变 量 的 随 机 使 用 。 为 了 避 免 全 局 变 量 的 随 机 使 用 , 你 可 以 说 :143


use strict 'vars';这 意 味 着 从 这 里 开 始 到 闭 合 范 围 结 尾 的 这 个 区 间 里 , 任 何 变 量 要 么 是 一 个 词 法 范 围 变 量 , 要么 是 一 个 明 确 声 明 允 许 使 用 的 全 局 变 量 。 如 果 两 者 都 不 是 , 将 导 致 编 译 错 误 。 如 果 下 列 之 一为 真 , 则 一 个 全 局 变 量 是 明 确 允 许 使 用 的 :• 它 是 <strong>Perl</strong> 的 程 序 范 围 内 的 特 殊 变 量 之 一 ( 参 阅 第 二 十 八 章 )。• 它 带 有 包 括 其 包 名 字 的 全 称 ( 参 阅 第 十 章 )。• 它 被 输 入 到 当 前 包 ( 参 阅 第 十 一 章 )。• 它 通 过 一 个 our 声 明 伪 装 成 了 一 个 词 法 范 围 的 变 量 。( 这 是 我 们 向 <strong>Perl</strong> 中 增 加our 声 明 的 主 要 原 因 。)当 然 , 还 有 第 五 种 可 能 —— 如 果 该 用 法 让 人 觉 得 烦 , 只 需 要 在 内 层 块 里 用 下 面 语 句 取 消 掉 它 :no strict 'vars';你 还 可 以 利 用 这 个 用 法 打 开 对 符 号 解 引 用 和 光 字 的 随 机 使 用 的 严 格 检 查 。 通 常 人 们 只 是 说 :use strict;这 样 就 把 三 个 检 查 都 打 开 了 。 参 阅 第 三 十 一 章 的 use strict 部 分 获 取 更 多 信 息 。第 五 章 模 式 匹 配<strong>Perl</strong> 内 置 的 模 式 匹 配 让 你 能 够 简 便 高 效 地 搜 索 大 量 的 数 据 。 不 管 你 是 在 一 个 巨 型 的 商 业 门户 站 点 上 用 于 扫 描 每 日 感 兴 趣 的 珍 闻 报 道 , 还 是 在 一 个 政 府 组 织 里 用 于 精 确 地 描 述 人 口 统 计( 或 者 人 类 基 因 组 图 ), 或 是 在 一 个 教 育 组 织 里 用 于 在 你 的 web 站 点 上 生 成 一 些 动 态 信 息 ,<strong>Perl</strong> 都 是 你 可 选 的 工 具 。 这 里 的 一 部 分 原 因 是 <strong>Perl</strong> 的 数 据 库 联 接 能 力 , 但 是 更 重 要 的 原因 是 <strong>Perl</strong> 的 模 式 匹 配 能 力 。 如 果 你 把 “ 文 本 ” 的 含 义 尽 可 能 地 扩 展 , 那 么 可 能 你 做 的 工 作 中有 90% 是 在 处 理 文 本 。 这 个 领 域 实 在 就 是 <strong>Perl</strong> 的 最 初 专 业 , 而 且 一 直 是 <strong>Perl</strong> 的 目 的—— 实 际 上 , 它 甚 至 是 <strong>Perl</strong> 的 名 字 :Practical Extraction and Report Language ( 实用 抽 取 和 报 表 语 言 )。<strong>Perl</strong> 的 模 式 提 供 了 在 堆 积 成 山 的 数 据 中 扫 描 数 据 和 抽 取 有 用 信 息 的强 大 工 具 。144


你 可 以 通 过 创 建 一 个 正 则 表 达 式 ( 或 者 叫 regex) 来 声 明 一 个 模 式 , 然 后 <strong>Perl</strong> 的 正 则 表达 式 引 擎 ( 我 们 在 本 章 余 下 的 部 分 称 之 为 “ 引 擎 ”) 把 这 个 正 则 表 达 式 拿 过 并 判 断 模 式 是 否 ( 以及 如 何 ) 和 你 的 数 据 相 匹 配 。 因 为 你 的 数 据 可 能 大 部 分 由 文 本 字 串 组 成 , 所 以 你 没 有 理 由 不用 正 则 表 达 式 搜 索 和 替 换 任 意 字 节 序 列 , 甚 至 有 些 你 认 为 是 “ 二 进 制 ” 的 数 据 也 可 以 用 正 则 处理 。 对 于 <strong>Perl</strong> 而 言 , 字 节 只 不 过 碰 巧 是 那 些 数 值 小 于 256 的 自 然 数 而 已 。( 更 多 相 关 内容 见 第 十 五 章 ,Unicode。)如 果 你 通 过 别 的 途 径 已 经 知 道 正 则 表 达 式 了 , 那 么 我 们 必 须 先 警 告 你 在 <strong>Perl</strong> 里 的 正 则 表 达式 是 有 些 不 同 的 。 首 先 , 理 论 上 来 讲 , 它 们 并 不 是 完 全 “ 正 则 ” 的 , 这 意 味 着 <strong>Perl</strong> 里 的 正 则可 以 处 理 比 计 算 机 科 学 课 程 里 教 的 正 则 表 达 式 更 多 的 事 情 。 第 二 , 因 为 它 们 在 <strong>Perl</strong> 里 用 得实 在 是 太 广 泛 了 , 所 以 在 这 门 语 言 里 , 它 们 有 属 于 自 己 的 特 殊 的 变 量 , 操 作 符 , 和 引 用 习 惯 ;这 些 东 西 都 和 语 言 本 身 紧 密 地 结 合 在 一 起 。 而 不 象 其 他 语 言 那 样 通 过 库 松 散 地 组 合 在 一 起 。<strong>Perl</strong> 的 程 序 员 新 手 常 常 徒 劳 地 寻 找 地 这 样 的 函 数 :match( $string, $pattern );subst( $string, $pattern, $replacement );要 知 道 在 <strong>Perl</strong> 里 , 匹 配 和 子 串 都 是 非 常 基 本 的 任 务 , 所 以 它 们 只 是 单 字 符 操 作 符 :m/PATTERN/ 和 s/PATTERN/REPLACEMENT/( 缩 写 为 m// 和 s///)。 它 们 不 仅 语法 简 单 , 而 且 还 象 双 引 号 字 串 那 样 分 析 , 而 不 只 是 象 普 通 操 作 符 那 样 处 理 ; 当 然 它 们 的 操 作还 是 象 操 作 符 的 , 所 以 我 们 才 叫 它 们 操 作 符 。 你 在 本 章 通 篇 都 会 看 到 这 些 操 作 符 用 于 匹 配 字串 。 如 果 字 串 的 一 部 分 匹 配 了 模 式 , 我 们 就 说 是 一 次 成 功 的 模 式 匹 配 。 特 别 是 在 你 用 s/// 的时 候 , 成 功 匹 配 的 部 分 将 被 你 在 REPLACEMENT 里 声 明 的 内 容 代 替 。本 章 所 有 的 内 容 都 是 有 关 如 何 制 作 和 使 用 模 式 的 。<strong>Perl</strong> 的 正 则 表 达 式 非 常 有 效 , 把 许 多 含义 包 含 到 了 一 个 很 小 的 表 达 式 里 。 所 以 如 果 你 想 直 接 理 解 一 个 很 长 的 模 式 , 那 很 有 可 能 被 吓着 。 不 过 如 果 你 能 把 长 句 分 解 成 短 句 , 并 且 还 知 道 引 擎 如 何 解 释 这 些 短 句 , 那 你 就 能 理 解 任何 正 则 表 达 式 。 一 行 正 则 表 达 式 相 当 于 好 几 百 行 C 或 者 JAVA 程 序 并 不 罕 见 。 正 则 表 达式 可 能 比 一 个 长 程 序 的 单 一 一 行 要 难 理 解 , 但 是 如 果 从 整 体 来 看 , 正 则 表 达 式 通 常 要 比 很 长的 程 序 要 好 理 解 。 你 只 需 要 提 前 知 道 这 些 事 情 就 可 以 了 。5.1 正 则 表 达 式 箴 言在 我 们 开 始 讲 述 正 则 表 达 式 之 前 , 让 我 们 先 看 看 一 些 模 式 的 样 子 是 什 么 。 正 则 表 达 式 里 的 大多 数 字 符 只 是 代 表 自 身 。 如 果 你 把 几 个 字 符 排 在 一 行 , 它 们 必 须 按 顺 序 匹 配 , 就 象 你 希 望 的那 样 。 因 此 如 果 你 写 出 一 个 模 式 匹 配 :/Frodo/ ( 译 注 : 记 得 电 影 “ 魔 戒 ” 吗 ?;))145


你 可 以 确 信 除 非 该 字 串 在 什 么 地 方 包 含 子 字 串 “Frodo”, 否 则 该 模 式 不 会 匹 配 上 。( 一 个 子字 串 只 是 字 串 的 一 部 分 。) 这 样 的 匹 配 可 以 发 生 在 字 串 里 的 任 何 位 置 , 只 要 这 五 个 字 符 以 上面 的 顺 序 在 什 么 地 方 出 现 就 行 。其 他 字 符 并 不 匹 配 自 身 , 而 是 从 某 种 角 度 来 说 表 现 得 有 些 “ 怪 异 ”。 我 们 把 这 些 字 符 称 做 元 字符 。( 大 多 数 元 字 符 都 是 自 己 淘 气 , 但 是 有 一 些 坏 得 把 旁 边 的 字 符 也 带 “ 坏 ” 了 。)下 面 的 就 是 这 些 字 符 :\ | ( ) [ { ^ $ * + ? .实 际 上 元 字 符 非 常 有 用 , 而 且 在 模 式 里 有 特 殊 的 含 义 ; 我 们 会 一 边 讲 述 , 一 边 告 诉 你 所 有 的那 些 含 义 。 不 过 我 们 还 是 要 告 诉 你 , 你 仍 然 可 以 在 任 意 时 刻 使 用 前 置 反 斜 杠 的 方 法 来 匹 配 这十 二 个 字 符 本 身 。 比 如 , 反 斜 杠 本 身 是 一 个 元 字 符 , 因 此 如 果 你 要 匹 配 一 个 文 本 的 反 斜 杠 ,你 要 在 反 斜 杠 前 面 放 一 个 反 斜 杠 :\\。要 知 道 , 反 斜 杠 就 是 那 种 让 其 他 字 符 “ 怪 异 ” 的 字 符 。 事 实 是 如 果 你 让 一 个 怪 异 元 字 符 再 次 怪异 , 它 就 会 正 常 —— 双 重 否 定 等 于 肯 定 。 因 此 反 斜 杠 一 个 字 符 能 够 让 它 正 确 反 映 文 本 值 , 但是 这 条 只 对 标 点 符 号 字 符 有 用 ; 反 斜 杠 ( 平 时 正 常 ) 的 字 母 数 字 字 符 作 用 相 反 : 它 把 该 文 本字 符 变 成 某 些 特 殊 的 东 西 。 不 论 什 么 时 候 你 看 到 下 面 的 双 字 符 序 列 :\b \D \t \3 \s你 就 应 该 知 道 这 些 序 列 是 一 个 元 符 号 , 它 们 匹 配 某 些 特 殊 的 东 西 。 比 如 ,\b 匹 配 一 个 字 边界 , 而 \t 匹 配 一 个 普 通 水 平 制 表 字 符 。 请 注 意 一 个 水 平 制 表 符 是 一 个 字 符 宽 , 而 一 个 字 边界 是 零 字 符 宽 , 因 为 它 是 两 个 字 符 之 间 的 位 置 。 所 以 我 们 管 \b 叫 一 个 零 宽 度 断 言 。 当 然 ,\t 和 \b 还 是 相 似 的 , 因 为 他 们 都 断 言 某 些 和 字 串 里 的 某 个 特 殊 位 置 相 关 的 东 西 。 当 你 在正 则 表 达 式 里 断 言 某 些 东 西 的 时 候 , 你 的 意 思 只 是 说 , 如 果 要 匹 配 模 式 , 那 些 东 西 必 须 是 真的 。一 个 正 则 表 达 式 里 的 大 多 数 部 分 都 是 某 种 断 言 , 包 括 那 些 普 通 字 符 , 只 不 过 它 们 是 断 言 它 们必 须 匹 配 自 身 。 准 确 来 说 , 它 们 还 断 言 下 一 个 东 西 将 匹 配 字 串 里 下 一 个 字 符 , 这 也 是 为 什 么我 们 说 水 平 制 表 符 是 “ 单 字 符 宽 ”。 有 些 断 言 ( 比 如 \t) 当 匹 配 的 时 候 就 吞 掉 字 串 的 一 部 分 ,而 其 他 的 ( 比 如 \b) 不 会 这 样 。 但 是 通 常 我 们 把 “ 断 言 ” 这 个 词 保 留 给 零 宽 度 断 言 用 。 为 避免 混 淆 , 我 们 把 这 些 东 西 称 做 宽 度 为 一 个 原 子 的 东 西 。( 如 果 你 是 一 个 物 理 学 家 , 你 可 以 把非 零 宽 的 原 子 当 作 物 质 , 相 比 之 下 零 宽 断 言 类 似 无 质 量 的 光 子 。)你 还 会 看 到 有 些 元 字 符 不 是 断 言 ; 而 是 结 构 元 素 ( 就 好 象 花 括 弧 和 分 号 定 义 普 通 <strong>Perl</strong> 代 码的 结 构 , 但 是 实 际 上 什 么 也 不 干 )。 这 些 结 构 元 字 符 在 某 种 程 度 上 来 说 是 最 重 要 的 元 字 符 ,因 为 学 习 阅 读 正 则 表 达 式 关 键 的 第 一 步 就 是 让 你 的 眼 睛 学 会 挑 出 结 构 元 字 符 。 一 旦 你 学 会 了146


挑 出 结 构 元 字 符 , 阅 读 正 则 表 达 式 就 是 如 坐 春 风 ( 注 : 当 然 , 有 时 候 风 力 强 劲 , 但 绝 对 不 会把 你 刮 跑 。)有 一 个 结 构 元 字 符 是 竖 直 条 , 表 示 侯 选 项 :/Frodo|Pippin|Merry|Sam/ ( 译 注 : 电 影 “ 魔 戒 ” 里 Shire 的 四 个 小 矮 人 )这 意 味 着 这 些 字 串 的 任 何 一 个 都 会 触 发 匹 配 ; 这 个 内 容 在 本 章 稍 后 的 “ 侯 选 项 ” 节 描 述 。 并 且我 们 还 会 在 那 节 后 面 的 “ 捕 获 和 集 群 ” 节 里 告 诉 你 如 何 使 用 圆 括 弧 , 把 你 的 模 式 各 个 部 分 括 起来 分 组 :/(Frodo|Drogo|Bilbo) Baggins/ ( 译 注 :Bilbo Baggins 是 Frodo 的 叔 叔 , 老 一 辈 魔戒 承 载 者 。)或 者 甚 至 :/(Frod|Drog|Bilb)o Baggins/你 看 到 的 其 他 的 东 西 是 我 们 称 之 为 量 词 的 东 西 , 它 表 示 在 一 行 里 面 前 面 匹 配 的 东 西 应 该 匹 配几 个 。 量 词 是 这 些 东 西 :* + ? *? {3} {2,5}不 过 你 永 远 不 会 看 到 它 们 这 样 独 立 地 存 在 。 量 词 只 有 附 着 在 原 子 后 面 才 有 意 义 —— 也 就 是说 , 断 言 那 些 有 宽 度 的 ( 注 : 量 词 有 点 象 第 四 章 , 语 句 和 声 明 , 里 的 语 句 修 饰 词 , 也 是 只 能附 着 在 单 个 语 句 后 面 。 给 一 个 零 宽 度 的 断 言 附 着 量 词 就 象 试 图 给 一 个 声 明 语 句 附 着 一 个while 修 饰 词 一 样 —— 两 种 做 法 都 和 你 跟 药 剂 师 要 一 斤 光 子 一 样 无 聊 。 药 剂 师 只 卖 原 子什 么 的 。) 量 词 只 附 着 在 前 一 个 原 子 上 , 从 我 们 人 类 的 眼 光 来 看 , 这 通 常 量 化 为 只 有 一 个 字符 。 如 果 你 想 匹 配 一 行 里 的 三 个 “bar” 的 副 本 , 你 得 用 圆 括 弧 把 “bar” 的 三 个 独 立 的 字 符 组合 成 一 个 “ 分 子 ”, 象 这 样 :/(bar){3}/这 样 将 和 “barbarbar” 匹 配 。 如 果 你 用 的 是 /bar{3}/, 那 么 匹 配 的 是 “barrr“ —— 这 东西 表 明 你 是 苏 格 兰 人 ( 译 注 : 爱 尔 兰 人 说 英 文 的 时 候 尾 音 比 较 长 ), 而 不 是 barbarbar 人 。( 话 又 说 回 来 , 也 可 能 不 是 。 我 们 有 些 很 喜 欢 的 元 字 符 就 是 爱 尔 兰 人 。) 有 关 量 词 的 更 多 东西 , 参 阅 后 面 的 “ 量 词 ”。你 已 经 看 到 了 一 些 继 承 了 正 则 表 达 式 的 野 兽 , 现 在 一 定 迫 不 及 待 地 想 驯 服 它 们 。 不 过 , 在 我们 开 始 认 真 地 讨 论 正 则 表 达 式 之 前 , 我 们 需 要 先 向 回 追 溯 一 些 然 后 再 谈 谈 使 用 正 则 表 达 式 的模 式 匹 配 操 作 符 。( 并 且 如 果 你 在 学 习 过 程 中 碰 巧 多 遇 到 几 只 “ 野 兽 ”, 那 么 不 妨 给 我 们 的 学习 向 导 留 一 条 不 错 的 技 巧 。)147


5.2 模 式 匹 配 操 作 符从 动 物 学 角 度 来 说 ,<strong>Perl</strong> 的 模 式 匹 配 操 作 符 函 数 是 某 种 用 来 关 正 则 表 达 式 的 笼 子 。 我 们 是有 意 这 么 设 计 的 ; 如 果 我 们 任 由 正 则 怪 兽 在 语 言 里 四 处 乱 逛 ,<strong>Perl</strong> 就 完 全 是 一 个 原 始 丛 林了 。 当 然 , 世 界 需 要 丛 林 —— 它 们 是 生 物 种 类 多 样 性 的 引 擎 , 但 是 , 丛 林 毕 竟 应 该 放 在 它 们应 该 在 的 位 置 。 一 样 , 尽 管 也 是 组 合 多 样 化 的 引 擎 , 正 则 表 达 式 也 应 该 放 在 它 们 应 该 在 的 模式 匹 配 操 作 符 里 面 。 那 里 是 另 外 一 个 丛 林 。因 为 正 则 表 达 式 还 不 够 强 大 ,m// 和 s/// 操 作 符 还 提 供 了 ( 同 样 也 是 限 制 ) 双 引 号 代 换的 能 力 。 因 为 模 式 是 按 照 类 似 双 引 号 字 串 那 样 分 析 的 , 所 以 所 有 的 双 引 号 代 换 都 有 效 , 包 括变 量 代 换 ( 除 非 你 用 单 引 号 做 分 隔 符 ) 和 用 反 斜 杠 逃 逸 标 识 的 特 殊 字 符 。( 参 阅 本 章 后 面 的 "特 殊 字 符 "。) 在 字 串 被 解 释 成 正 则 表 达 式 之 前 首 先 应 用 这 些 代 换 。( 也 是 <strong>Perl</strong> 语 言 里 极少 数 的 几 个 地 方 之 一 , 在 这 些 地 方 一 个 字 串 要 经 过 多 于 一 次 处 理 。) 第 一 次 处 理 是 不 那 么 正常 的 双 引 号 代 换 , 不 正 常 是 因 为 它 知 道 它 应 该 转 换 什 么 和 它 应 该 给 正 则 表 达 式 分 析 器 传 递 什么 。 因 此 , 任 何 后 面 紧 跟 竖 直 条 , 闭 圆 括 弧 或 者 字 符 串 结 尾 的 $ 都 不 会 被 当 作 变 量 代 换 ,而 是 当 作 典 型 的 正 则 表 达 式 的 行 尾 断 言 。 所 以 , 如 果 你 说 :$foo = "bar";/$foo$/;双 引 号 代 换 过 程 是 知 道 那 两 个 $ 符 作 用 是 不 同 的 。 它 先 做 $foo 的 变 量 代 换 , 然 后 把 剩 下的 交 给 正 则 表 达 式 分 析 器 :/bar$/;这 种 两 回 合 分 析 的 另 一 个 结 果 是 普 通 的 <strong>Perl</strong> 记 号 分 析 器 首 先 查 找 正 则 表 达 式 的 结 尾 , 就 好象 它 在 查 找 一 个 普 通 字 串 的 结 尾 分 隔 符 一 样 。 只 有 在 它 找 到 字 串 的 结 尾 后 ( 并 且 完 成 任 意 变量 代 换 ), 该 模 式 才 被 当 作 正 则 表 达 式 对 待 。 这 意 味 着 你 无 法 在 一 个 正 则 构 造 里 面 “ 隐 藏 ”模 式 的 结 尾 分 隔 符 ( 比 如 一 个 字 符 表 或 者 一 个 正 则 注 释 , 我 们 还 没 有 提 到 这 些 东 西 )。<strong>Perl</strong>总 是 会 在 任 何 地 方 识 别 该 分 隔 符 并 且 在 该 处 结 束 该 模 式 。你 还 应 该 知 道 在 模 式 里 面 代 换 变 量 会 降 低 模 式 匹 配 的 速 度 , 因 为 它 会 觉 得 需 要 检 查 变 量 是 否曾 经 变 化 过 , 如 果 变 化 过 , 那 么 它 必 须 重 新 编 译 模 式 ( 这 样 更 会 降 低 速 度 )。 参 阅 本 章 后 面的 “ 变 量 代 换 ”。tr/// 转 换 操 作 符 不 做 变 量 代 换 ; 它 甚 至 连 正 则 表 达 式 都 不 用 !( 实 际 上 , 它 可 能 并 不 属 于本 章 , 但 我 们 实 在 想 不 出 更 好 的 地 方 放 它 。) 不 过 , 它 在 一 个 方 面 还 是 和 m// 和 s/// 一样 的 : 它 用 =~ 和 !~ 操 作 符 与 变 量 绑 定 。148


第 三 章 , 单 目 和 双 目 操 作 符 , 里 描 述 的 =~ 和 !~ 操 作 符 把 它 们 左 边 的 标 量 表 达 式 和 在 右边 的 三 个 引 起 类 操 作 符 之 一 绑 定 在 一 起 : m// 用 于 匹 配 一 个 模 式 ,s/// 用 于 将 某 个 符 合模 式 的 子 字 串 代 换 为 某 个 字 串 , 而 tr/// ( 或 者 其 同 义 词 ,y///) 用 于 将 一 套 字 符 转 换 成另 一 套 。( 如 果 把 斜 杠 用 做 分 隔 符 , 你 可 以 把 m// 写 成 //, 不 用 写 m。) 如 果 =~ 或 !~的 右 手 边 不 是 上 面 三 个 , 它 仍 然 当 作 是 m// 匹 配 操 作 , 不 过 此 时 你 已 经 没 有 地 方 放 跟 在 后面 的 修 饰 词 了 ( 参 阅 后 面 的 “ 模 式 修 饰 词 ”), 并 且 你 必 须 操 作 自 己 的 引 号 包 围 :print "matches" if $somestring =~ $somepattern;不 过 , 我 们 实 在 没 道 理 不 明 确 地 说 出 来 :print "matches" if$somestring =~ m/$somepattern/;当 用 于 匹 配 操 作 时 , 有 时 候 =~ 和 !~ 分 别 读 做 “ 匹 配 ” 和 “ 不 匹 配 ”( 因 为 “ 包 含 ” 和 “ 不 包含 ” 会 让 人 觉 得 有 点 模 糊 )。除 了 在 m// 和 s/// 操 作 符 里 使 用 外 , 在 <strong>Perl</strong> 的 另 外 两 个 地 方 也 使 用 正 则 表 达 式 。split函 数 的 第 一 个 参 数 是 一 个 特 殊 的 匹 配 操 作 符 , 它 声 明 的 是 当 把 字 串 分 解 成 多 个 子 字 串 后 不 返回 什 么 东 西 。 参 阅 第 二 十 九 章 , 函 数 , 里 的 关 于 split 的 描 述 和 例 子 。qr// (“ 引 起 正 则 表达 ”) 操 作 符 同 样 也 通 过 正 则 表 达 式 声 明 一 个 模 式 , 但 是 它 不 是 为 了 匹 配 匹 配 任 何 东 西 ( 和m// 做 的 不 一 样 )。 相 反 , 编 译 好 的 正 则 表 达 的 形 式 返 回 后 用 于 将 来 的 处 理 。 参 阅 “ 变 量 代换 ” 获 取 更 多 信 息 。你 用 m//,s/// 或 者 tr/// 操 作 符 和 =~ ( 从 语 言 学 上 来 说 , 它 不 是 真 正 的 操 作 符 , 只是 某 种 形 式 的 标 题 符 ) 绑 定 操 作 符 把 某 一 字 串 和 这 些 操 作 符 之 一 绑 定 起 来 。 下 面 是 一 些 例 子 :$haystack =~ m/meedle/$haystack =~ /needle/# 匹 配 一 个 简 单 模 式# 一 样 的 东 西$italiano =~ s/butter/olive oil/# 一 个 健 康 的 替 换$rotate13 =~ tr/a-zA-Z/n-za-mN-ZA-M/# 简 单 的 加 密如 果 没 有 绑 定 操 作 符 , 隐 含 地 用 $_ 做 “ 标 题 ”:/new life/ and # 搜 索 $_ 和 ( 如 果 找 到 )/new civilizations/# 再 次 更 宽 范 围 地 搜 索 $_149


s/sugar/aspartame/# 把 一 个 替 换 物 替 换 到 $_ 里tr/ATCG/TAGC# 修 改 在 $_ 里 表 示 的 DNA因 为 s/// 和 tr/// 修 改 它 们 所 处 理 的 标 量 , 因 此 你 只 能 把 它 们 用 于 有 效 的 左 值 :"onshore" =~ s/on/off/;# 错 ; 编 译 时 错 误不 过 ,m// 可 以 应 用 于 任 何 标 量 表 达 式 的 结 果 :if (( lc $magic_hat->fetch_contents->as_string) =! /rabbit/) {print "Nyaa, what's up doc?\n";}else {print "That trick never works!\n";}但 是 , 在 这 里 你 得 更 小 心 一 些 , 因 为 =~ 和 !~ 的 优 先 级 相 当 高 —— 在 前 一 个 例 子 里 , 左边 的 项 的 圆 括 弧 是 必 须 的 ( 注 : 如 果 没 有 圆 括 弧 , 低 优 先 级 的 lc 将 会 应 用 于 整 个 模 式 匹 配而 不 只 是 对 magic hat 对 象 的 方 法 调 用 。)。!~ 绑 定 操 作 符 作 用 和 =~ 类 似 , 只 是 把逻 辑 结 果 取 反 :if ($song !~ /words/) {print qq/"$song" appears to be a song without words. \n/;}因 为 m//,s///, 和 tr/// 都 是 引 号 包 围 操 作 符 , 所 以 你 可 以 选 择 自 己 的 分 隔 符 。 这 时 其运 行 方 式 和 引 起 操 作 符 q//,qq//,qr//, 和 qw// 一 样 ( 参 阅 第 二 章 , 集 腋 成 裘 , 中 的 “ 选择 自 己 的 引 号 ”)。$path =~ s#/tmp#/var/tmp/scratch#;if ($dir =~ m[/bin]) {150


print "No binary directories please.\n";}当 你 把 成 对 的 分 隔 符 和 s/// 或 者 tr/// 用 在 一 起 的 时 候 , 如 果 第 一 部 分 是 四 种 客 户 化 的括 弧 对 之 一 ( 尖 括 弧 , 圆 括 弧 , 方 括 弧 或 者 花 括 弧 ), 那 么 你 可 以 为 第 二 部 分 选 用 不 同 于 第一 部 分 的 分 隔 符 :s(egg);s{larva}{pupa};s[pupa]/imago/;也 可 以 在 实 际 使 用 的 分 隔 符 前 面 加 空 白 字 符 :s (egg) ;s {larva}s [pupa]{pupa};/imago/;每 次 成 功 匹 配 了 一 个 模 式 ( 包 括 替 换 中 的 模 式 ), 操 作 符 都 会 把 变 量 $`,$&, 和 $' 分 别设 置 为 匹 配 内 容 左 边 内 容 , 匹 配 的 内 容 和 匹 配 内 容 的 右 边 的 文 本 。 这 个 功 能 对 于 把 字 串 分 解为 组 件 很 有 用 :"hot cross buns" =~ /cross/;print "Matched: $& \n"; # Right: < buns>为 了 有 更 好 的 颗 粒 度 和 提 高 效 率 , 你 可 以 用 圆 括 弧 捕 捉 你 特 别 想 分 离 出 来 的 部 分 。 每 对 圆 括弧 捕 捉 与 圆 括 弧 内 的 模 式 相 匹 配 的 子 模 式 。 圆 括 弧 由 左 圆 括 弧 的 位 置 从 左 到 右 依 次 排 序 ; 对应 那 些 子 模 式 的 子 字 串 在 匹 配 之 后 可 以 通 过 顺 序 的 变 量 $1,$2,$3 等 等 获 得 :$_ = "Bilbo Baggins's birthday is September 22";/(.*)'s birthday is (.*)/;print "Person: $1\n";151


print "Date: $2\n";$`, $&, $' 和 排 序 的 变 量 都 是 全 局 变 量 , 它 们 隐 含 地 局 部 化 为 属 于 此 闭 合 的 动 态 范 围 。它 们 的 存 在 直 到 下 一 次 成 功 的 匹 配 或 者 当 前 范 围 的 结 尾 , 以 先 到 者 为 准 。 我 们 稍 后 在 其 它 课题 里 有 关 于 这 方 面 内 容 里 更 多 介 绍 。一 旦 <strong>Perl</strong> 认 为 你 的 程 序 的 任 意 部 分 需 要 $`, $&, 或 $', 它 就 会 为 每 次 模 式 匹 配 提 供这 些 东 西 。 这 样 做 会 微 微 减 慢 你 的 程 序 的 速 度 。<strong>Perl</strong> 同 样 还 利 用 类 似 的 机 制 生 成 $1,$2 等等 , 因 此 你 也 会 为 每 个 包 含 捕 捉 圆 括 弧 的 模 式 付 出 一 些 代 价 。( 参 阅 “ 集 群 ” 获 取 在 保 留 分 组的 特 征 的 同 时 避 免 捕 获 的 开 销 的 方 法 。) 但 如 果 你 从 不 使 用 $`,$& 或 者 $', 那 么 不 带 捕获 圆 括 弧 的 模 式 不 会 有 性 能 损 失 。 因 此 , 如 果 可 能 地 话 , 通 常 你 应 该 避 免 使 用 $`,$& 和$', 尤 其 是 在 库 模 块 里 。 但 是 如 果 你 必 须 至 少 使 用 它 们 一 次 ( 而 且 有 些 算 法 的 确 因 此 获 益 非浅 ), 那 么 你 就 随 便 用 它 们 吧 , 因 为 你 已 经 为 之 付 出 代 价 了 。 在 最 近 的 <strong>Perl</strong> 版 本 里 ,$& 比另 外 两 个 开 销 少 。5.2.1 模 式 修 饰 词我 们 稍 后 将 逐 个 讨 论 模 式 匹 配 操 作 符 , 但 首 先 我 们 先 谈 谈 另 一 个 这 些 模 式 操 作 符 都 有 的 共性 : 修 饰 词 。你 可 以 在 一 个 m//,s///,qr//, 或 者 tr/// 操 作 符 的 最 后 一 个 分 隔 符 后 面 , 以 任 意 顺 序放 一 个 或 多 个 单 字 母 修 饰 词 。 为 了 保 持 清 晰 , 修 饰 词 通 常 写 成 “/o 修 饰 词 ” 并 且 读 做 “ 斜 杠 o修 饰 词 ”), 即 使 最 后 的 分 隔 符 可 能 不 是 一 个 斜 杠 也 这 么 叫 。( 有 时 候 人 们 把 “ 修 饰 词 ” 叫 做 “ 标志 ” 或 者 “ 选 项 ” 也 可 以 。)有 些 修 饰 词 改 变 单 个 操 作 符 的 特 性 , 因 此 我 们 将 在 后 面 仔 细 讨 论 它 们 。 其 他 的 修 改 正 则 表 达式 的 解 释 方 式 , 所 以 我 们 在 这 里 讨 论 它 们 。m//,s/// 和 qr// 操 作 符 (tr/// 操 作 符 并 不接 受 正 则 表 达 式 , 所 以 这 些 修 饰 词 并 不 适 用 。) 的 最 后 一 个 分 隔 符 后 面 都 接 受 下 列 修 饰 词 :修 饰 词含 义/i 忽 略 字 母 的 大 小 写 ( 大 小 写 无 关 )/s 令 . 匹 配 换 行 符 并 且 忽 略 不 建 议 使 用 的 $* 变 量/m 令 ^ 和 $ 匹 配 下 一 个 嵌 入 的 \n。/x 忽 略 ( 大 多 数 ) 空 白 并 且 允 许 模 式 中 的 注 释/o 只 编 译 模 式 一 次/i 修 饰 词 是 说 同 时 匹 配 大 写 或 者 小 写 ( 以 及 在 Unicode 里 的 标 题 )。 也 是 为 什 么 /perl/i 将匹 配 字 串 "PROPERLY" 或 "perlaceous"( 几 乎 是 完 全 不 同 的 东 西 )。use locale 用 法可 能 也 会 对 被 当 作 相 同 的 东 西 有 影 响 。( 这 可 能 对 包 含 Unicode 的 字 串 有 负 面 影 响 。)152


s 和 /m 修 饰 词 并 不 涉 及 任 何 古 怪 的 东 西 。 它 们 只 是 影 响 <strong>Perl</strong> 对 待 那 些 包 含 换 行 符 的 匹配 的 态 度 。 不 过 它 们 和 你 的 字 串 是 否 包 含 换 行 符 无 关 ; 它 们 关 心 的 是 <strong>Perl</strong> 是 否 应 该 假 设 你的 字 串 包 含 单 个 行 (/s) 还 是 多 个 行 (/m), 因 为 有 些 元 字 符 根 据 你 是 否 需 要 让 它 们 工 作于 面 向 行 的 模 式 而 有 不 同 的 行 为 。通 常 , 元 字 符 "." 匹 配 除 了 换 行 符 以 外 的 任 何 单 个 字 符 , 因 为 它 的 传 统 含 义 是 匹 配 一 行 内的 某 个 字 符 。 不 过 , 带 有 /s 时 ,"." 元 字 符 也 可 以 匹 配 一 个 换 行 符 , 因 为 你 已 经 告 诉 <strong>Perl</strong>忽 略 该 字 串 可 能 包 含 多 个 换 行 符 的 情 况 。 (/s 修 饰 词 同 样 还 令 <strong>Perl</strong> 忽 略 我 们 已 经 不 鼓 励使 用 的 $* 变 量 , 我 们 也 希 望 你 也 忽 略 。) 另 一 方 面 ,/m 修 饰 词 还 修 改 元 字 符 ^ 和 $ 的解 释 —— 通 过 令 它 们 匹 配 字 串 里 的 换 行 符 后 面 的 东 西 , 而 不 仅 仅 是 字 串 的 结 尾 。 参 阅 本 章的 ” 位 置 “ 节 的 例 子 。/o 操 作 符 控 制 模 式 的 重 新 编 译 。 除 非 你 选 用 的 分 隔 符 是 单 引 号 (m'PATTERN',s'PATTERN'REPLACEMENT', 或 者 qr'PATTERN'), 否 则 每 次 计 算 模 式 操 作 符 的 时 候 ,任 何 模 式 里 的 变 量 都 会 被 代 换 ( 并 且 可 能 会 导 致 模 式 的 重 新 编 译 )。 如 果 你 希 望 这 样 的 模 式被 且 只 被 编 译 一 次 ; 那 么 就 该 使 用 /o 修 饰 词 。 这 么 做 可 以 避 免 开 销 巨 大 的 运 行 时 重 新 编译 ; 这 么 做 非 常 有 用 , 尤 其 是 你 在 转 换 的 值 在 执 行 中 不 会 改 变 的 情 况 下 。 不 过 ,/o 实 际 上是 让 你 做 出 了 不 会 改 变 模 式 中 的 变 量 的 承 诺 。 如 果 你 改 变 了 这 些 变 量 ,<strong>Perl</strong> 设 置 都 不 会 注意 到 。 为 了 更 好 地 控 制 重 编 译 , 你 可 以 使 用 qr// 正 则 表 达 式 引 起 操 作 符 。 详 情 请 参 阅 本 章后 面 的 “ 变 量 代 换 ” 节 。/x 是 表 达 修 饰 词 : 它 允 许 你 利 用 空 白 和 解 释 性 注 释 扩 展 你 的 模 式 的 易 读 性 , 你 甚 至 还 可 以把 模 式 扩 展 得 超 过 一 行 的 范 围 。也 就 是 说 ,/x 修 改 空 白 字 符 ( 还 有 # 字 符 ) 的 含 义 : 它 们 不 再 是 普 通 字 符 那 样 的 自 匹 配字 符 , 而 是 转 换 成 元 字 符 , 这 些 元 字 符 的 特 征 类 似 空 白 ( 和 注 释 字 符 )。 因 此 ,/x 允 许 ( 在模 式 里 面 ) 将 空 白 , 水 平 制 表 符 和 换 行 符 用 于 格 式 化 , 就 象 普 通 <strong>Perl</strong> 代 码 一 样 。 它 还 允 许用 通 常 在 模 式 里 没 有 特 殊 含 义 的 # 字 符 引 入 延 伸 到 当 前 模 式 行 行 尾 的 注 释 。( 注 : 请 注 意不 要 在 注 释 里 包 含 模 式 分 隔 符 —— 因 为 “ 先 找 结 尾 ” 的 规 则 ,<strong>Perl</strong> 没 办 法 知 道 你 在 该 点 上 并不 想 结 束 。) 如 果 你 想 匹 配 一 个 真 正 的 空 白 字 符 ( 或 者 # 字 符 ), 那 你 就 要 把 它 们 放 到 字符 表 里 , 或 者 用 反 斜 杠 逃 逸 , 或 者 用 八 进 制 或 者 十 六 进 制 逃 逸 的 编 码 。( 但 是 空 白 通 常 用 一个 \s* 或 \s+ 序 列 匹 配 , 因 此 实 际 中 这 种 情 况 出 现 得 并 不 多 。)总 结 而 言 , 这 些 特 性 朝 着 把 传 统 的 正 则 表 达 式 变 成 更 可 读 的 语 言 迈 进 了 一 大 步 。 从 " 回 字 有四 种 写 法 " 精 神 出 发 , 现 在 写 一 个 正 则 表 达 式 的 方 法 是 不 止 一 种 了 。 实 际 上 , 我 们 有 不 止 两种 的 方 法 :( 译 注 :TMTOWTDI:"There's More Then One Way To Do It", " 做 事 的方 法 不 止 一 种 ". <strong>Perl</strong> 文 化 口 号 , 见 本 书 尾 部 的 词 汇 表 .)m/\w+:(\s+\w+)\s*\d+/; # 一 个 词 , 冒 号 , 空 白 , 词 , 空 白 , 数 字 。153


m/\w+: (\s+ \w+) \s* \d+/x; # 一 个 词 , 冒 号 , 空 白 , 词 , 空 白 , 数 字 。m{\w+: # 匹 配 一 个 词 和 一 个 冒 号 。( # 分 组 开 始 。\s+ # 匹 配 一 个 或 多 个 空 白 。\w+ # 匹 配 另 外 一 个 词 。) # 分 组 结 束 。\s* # 匹 配 零 或 更 多 空 白 。\d+# 匹 配 一 些 数 字}x;我 们 会 在 本 章 稍 后 描 述 这 些 元 符 号 。( 本 节 本 来 是 讲 模 式 修 饰 词 的 , 但 是 我 们 却 因 为 对 /x 过于 兴 奋 而 超 出 了 我 们 的 控 制 。) 下 面 是 一 个 正 则 表 达 式 , 它 找 出 一 个 段 落 里 面 的 重 复 的 词 ,我 们 从 <strong>Perl</strong> Cookbook 里 直 接 把 这 个 例 子 偷 了 出 来 。 它 使 用 /x 和 /i 修 饰 词 , 以 及 后 面描 述 的 /g 修 饰 词 。# 找 出 段 落 里 面 的 重 复 的 单 词 , 可 能 会 跨 越 行 界 限 。# 将 /x 用 于 空 白 和 注 释 ,/i 以 匹 配 在 "Is is this ok?" 里 的 两 个 `is'# 用 /g 找 出 所 有 重 复 。$/ = ""; # "paragrep" 模 式while( ) {while ( m{\b # 从 字 边 界 开 始(\w\S+)# 找 出 一 个 字 块(154


\s+# 由 一 些 空 白 分 隔\l # 然 后 再 次 分 块)+ # 重 复 动 作\b # 直 到 另 外 一 个 字 边 界}xig){print "dup word '$1' at paragraph $.\n";}}当 对 本 章 运 行 这 个 程 序 时 , 它 的 输 出 象 下 面 这 样 :dup word 'that' at paragraph 100 ( 译 注 : 只 对 英 文 原 版 有 效 :))看 到 这 些 , 我 们 就 知 道 这 个 重 复 是 我 们 有 意 做 的 。5.2.2 m// 操 作 符 ( 匹 配 )EXPR =~ m/PATTERN/cgimosxEXPR =~ /PATTERN/cgimosxEXPR =~ ?PATERN?cgimosxm/PATTERN/cgimosx/PATTERN/cgimosx?PATTERN?cgimosxm// 操 作 符 搜 索 标 量 EXPR 里 面 的 字 串 , 查 找 PATTERN。 如 果 使 用 / 或 ? 做 分 隔 符 ,那 么 开 头 的 m 是 可 选 的 。? 和 ' 做 分 隔 符 时 都 有 特 殊 含 义 : 前 者 表 示 只 匹 配 一 次 ; 后 者禁 止 进 行 变 量 代 换 和 六 种 转 换 逃 逸 (\U 等 , 后 面 描 述 )。155


如 果 PATTERN 计 算 出 的 结 果 是 空 字 串 , 则 要 么 是 你 用 // 把 它 声 明 成 空 字 串 或 者 是 因 为一 个 代 换 过 来 的 变 量 就 是 空 字 串 , 这 时 就 用 没 有 隐 藏 在 内 层 块 ( 或 者 一 个 split,grep, 或者 map) 里 的 最 后 执 行 成 功 的 正 则 表 达 式 替 代 。在 标 量 环 境 里 , 该 操 作 符 在 成 功 时 返 回 真 (1), 失 败 时 返 回 假 ("")。 这 种 形 式 常 见 于 布尔 环 境 :if($shire =~ m/Baggins/) { ...}哪 里 么 ?;)# 在 $shire 里 找 Baggins, 译 注 :shire 知 道if($shire =~ /Baggins/) { ...} # 在 $shire 里 找 Bagginsif(m#Baggins#) {...} # 在 $_ 里 找if( /Baggins/ ) {...} # 在 $_ 里 找在 列 表 环 境 里 使 用 ,m// 返 回 一 个 子 字 串 的 列 表 , 这 些 子 字 串 匹 配 模 式 里 的 捕 获 圆 括 弧 ( 也就 是 $1,$2,$3 等 等 ), 这 些 捕 获 圆 括 弧 将 在 稍 后 的 “ 捕 获 和 集 群 ” 里 描 述 。 当 列 表 返 回的 时 候 , 这 些 序 列 数 变 量 仍 然 是 平 滑 的 。 如 果 在 列 表 环 境 里 匹 配 失 败 , 则 返 回 一 个 空 列 表 。如 果 在 列 表 环 境 中 匹 配 成 功 , 但 是 没 有 使 用 捕 获 圆 括 弧 ( 也 没 有 /g), 则 返 回 则 返 回 一 列(1)。 因 此 它 在 失 败 时 返 回 一 列 空 列 表 , 所 以 这 种 形 式 的 m// 仍 然 能 用 于 布 尔 环 境 , 但 是仅 限 于 通 过 列 表 赋 值 间 接 参 与 的 情 况 :if( ($key, $values) = /(\w+): (.*)/) { ... }用 于 m//( 不 管 是 什 么 形 式 ) 的 合 法 修 饰 词 见 表 5-1。表 5-1。 m// 修 饰 词修 饰 词含 义/i 或 略 字 母 大 小 写/m 令 ^ 和 $ 匹 配 随 后 嵌 入 的 \n。/s 令 . 匹 配 换 行 符 并 且 忽 略 废 弃 了 的 $*。/x 或 略 ( 大 多 数 ) 空 白 并 且 允 许 在 模 式 里 的 注 释/o 只 编 译 模 式 一 次/g 全 局 地 查 找 所 有 匹 配/cg在 /g 匹 配 失 败 后 允 许 继 续 查 找头 五 个 用 于 正 则 表 达 式 的 修 饰 词 我 们 前 面 描 述 过 了 。 后 面 两 个 修 改 匹 配 操 作 本 身 的 特 性 。/g修 饰 词 声 明 一 个 全 局 匹 配 —— 也 就 是 说 , 在 该 字 串 里 匹 配 尽 可 能 多 的 次 数 。 它 的 具 体 特 性 取156


决 于 环 境 。 在 列 表 环 境 里 ,m//g 返 回 所 有 找 到 的 东 西 的 列 表 。 下 面 的 语 句 找 出 所 有 我 们 提到 的 "perl", "<strong>Perl</strong>","PERL" 的 地 方 :if( @perls = $paragrapth =~ /perl/gi) {printf "<strong>Perl</strong> mentioned %d times.\n", scalar @perls;}如 果 在 /g 模 式 里 没 有 捕 获 圆 括 弧 , 那 么 返 回 完 整 的 匹 配 。 如 果 有 捕 获 圆 括 弧 , 那 么 返 回捕 获 到 的 字 串 。 想 象 一 下 这 样 的 字 串 :$string = "password=xyzzy verbose=9 score=0";并 且 假 设 你 想 用 这 个 字 串 初 始 化 下 面 这 样 的 散 列 :%hash = (password => "xyzzy", verbose => 9, socre => 0);当 然 , 你 有 字 串 但 还 没 有 列 表 。 要 获 取 对 应 的 列 表 , 你 可 以 在 列 表 环 境 里 用 m//g 操 作 符 ,从 字 串 里 捕 获 所 有 的 键 / 值 对 :%hash = $string =~ /(\w+)=(\w+)/g;(\w+) 序 列 捕 获 一 个 字 母 数 字 单 词 。 参 阅 “ 捕 获 和 集 群 ” 节 。在 标 量 环 境 里 使 用 时 ,/g 修 饰 词 表 明 一 次 渐 进 地 匹 配 , 它 令 <strong>Perl</strong> 从 上 一 次 匹 配 停 下 来 的位 置 开 始 一 次 对 同 一 个 变 量 的 新 的 匹 配 。\G 断 言 表 示 字 符 串 中 的 那 个 位 置 ;\G 的 描 述 请参 阅 本 章 后 面 的 “ 位 置 ” 一 节 。 如 果 除 了 用 /g, 你 还 用 了 /c( 表 示 “ 连 续 ”) 修 饰 词 , 那 么 当/g 运 行 结 束 后 , 失 败 的 匹 配 不 会 重 置 位 置 指 针 。如 果 分 隔 符 是 ?, 就 象 ?PATTERN?, 那 么 运 行 起 来 和 /PATTERN/ 搜 索 一 样 , 区 别 是 它在 两 次 reset 操 作 符 调 用 之 间 只 匹 配 一 次 。 如 果 你 只 想 匹 配 程 序 运 行 中 模 式 出 现 的 第 一 次 ,而 不 是 所 有 的 出 现 , 那 么 这 是 一 个 很 方 便 的 优 化 方 法 。 你 每 次 调 用 此 操 作 符 时 都 会 运 行 搜 索 ,直 到 它 最 终 匹 配 了 什 么 东 西 , 然 后 它 就 关 闭 自 身 , 在 你 明 确 地 用 reset 把 它 重 置 之 前 它 一直 返 回 假 。<strong>Perl</strong> 替 你 跟 踪 这 个 匹 配 状 态 。当 一 个 普 通 模 式 匹 配 想 找 出 最 后 一 个 匹 配 而 不 是 第 一 个 , 那 么 ?? 操 作 符 很 好 用 :open DICT, "/usr/dict/words" or die "Can't open words: $!\n";while () {$first = $1 if ?(^neur.*)?;157


$last = $1 if /(^neur.*)/;}print $first, "\n";print $last, "\n";# 打 印 "neurad"# 打 印 "neurypnology"在 调 用 reset 操 作 符 时 ,reset 只 重 置 那 些 编 译 进 同 一 个 包 的 ?? 记 录 。 你 说 m?? 的 时候 等 效 于 说 ??。5.2.3 s/// 操 作 符 ( 替 换 )LVALUE =~ s/PATTERN/REPLACEMENT/egimosxs/PATTERN/REPLACEMENT/egimosx这 个 操 作 符 在 字 串 里 搜 索 PATTERN, 如 果 找 到 , 则 用 REPLACEMENT 文 本 替 换 匹 配 的子 字 符 串 。( 修 饰 词 在 本 节 稍 后 描 述 。)$lotr = $hobbit;Shire :)# 只 是 拷 贝 Hobbit 译 注 : 影 片 魔 戒 里 ,Hobbit 人 住 在$lotr =~ s/Bilbo/Frodo/g;成 了 魔 戒 的 看 护 人 , 又 是 魔 戒# 然 后 用 最 简 单 的 方 法 写 结 局 , 译 注 :Frodo 代 替 Bilbo一 个 s/// 操 作 符 的 返 回 值 ( 在 标 量 和 列 表 环 境 里 都 差 不 多 ) 是 它 成 功 的 次 数 ( 如 果 与 /g 修饰 词 一 起 使 用 , 返 回 值 可 能 大 于 一 )。 如 果 失 败 , 因 为 它 替 换 了 零 次 , 所 以 它 返 回 假 (""),它 等 效 于 数 字 0。if( $lotr =~ s/Bilbo/Frodo/) { print "Successfully wrote sequel." }$change_count = $lotr =~ s/Bilbo/Frodo/g;替 换 部 分 被 当 作 双 引 号 包 围 的 字 串 看 待 。 你 可 以 在 替 换 字 串 里 使 用 我 们 前 面 描 述 过 的 任 何 动态 范 围 的 模 式 变 量 ($`,$&,$',$1,$2, 等 等 ), 以 及 任 何 其 他 你 准 备 使 用 的 双 引 号包 围 的 小 发 明 。 比 如 下 面 是 一 个 小 例 子 , 用 于 找 出 所 有 字 串 "revision","version", 或 者"release", 并 且 用 对 应 的 大 写 字 串 替 换 , 我 们 可 以 用 \u 逃 逸 处 理 替 换 的 目 标 部 分 :s/revision|version|release/\u$&/g; # | 用 于 表 示 模 式 中 的 “ 或 ”158


所 有 的 标 量 变 量 都 在 双 引 号 包 围 的 环 境 中 扩 展 开 , 而 不 仅 仅 是 这 些 特 殊 的 变 量 。 假 设 你 有 一个 散 列 %Names, 把 版 本 号 映 射 为 内 部 的 项 目 名 ; 比 如 ,$Name{"3.0"} 可 能 是 名 为"Isengard" 的 代 码 名 。 你 可 以 用 s/// 找 出 版 本 号 并 且 用 它 们 对 应 的 项 目 名 替 换 掉 :s/version ([0-9.]+)/the $Names{$1} release/g;在 在 替 换 字 串 里 ,$1 返 回 第 一 对 ( 也 是 唯 一 的 一 对 ) 捕 获 圆 括 弧 。( 愿 意 的 话 你 还 可 以 在模 式 里 用 \1, 但 是 这 个 用 法 在 替 换 中 已 经 废 弃 了 , 在 一 个 普 通 的 双 引 号 包 围 的 字 串 里 ,\1的 意 思 是 Control-A。)如 果 PATTERN 是 一 个 空 字 串 , 则 使 用 上 一 次 成 功 执 行 的 正 则 表 达 式 取 代 。PATTERN 和RELPACEMENT 都 需 要 经 受 变 量 代 换 , 不 过 每 次 计 算 s/// 操 作 符 的 时 候 都 进 行 代 换 , 而REPLACEMENT 只 是 在 有 匹 配 的 时 候 才 做 变 量 代 换 。( 如 果 你 使 用 了 /g 修 饰 词 , 那 么PATTERN 在 一 次 计 算 中 可 能 匹 配 多 次 。)和 前 面 一 样 , 表 5-2 中 的 头 五 个 修 饰 词 修 改 正 则 表 达 式 的 性 质 ; 他 们 与 m// 和 qr// 中的 一 样 。 后 面 两 个 修 改 替 换 操 作 符 本 身 。表 5-2 s/// 修 饰 词修 饰 词含 义/i 或 略 字 母 大 小 写/m 令 ^ 和 $ 匹 配 随 后 嵌 入 的 \n。/s 令 . 匹 配 换 行 符 并 且 忽 略 废 弃 了 的 $*。/x 或 略 ( 大 多 数 ) 空 白 并 且 允 许 在 模 式 里 的 注 释/o 只 编 译 模 式 一 次/g 全 局 地 查 找 所 有 匹 配/e 把 右 边 当 作 一 个 表 达 式 计 算/g 修 饰 词 用 于 s/// 的 时 候 就 会 把 每 个 匹 配 PATTERN 的 东 西 用 REPLACEMENT 值 替换 , 而 不 仅 仅 是 所 找 到 的 第 一 个 。 一 个 s///g 操 作 符 的 作 用 象 一 次 全 局 的 搜 索 和 替 换 , 令所 有 修 改 同 时 发 生 , 很 象 m//g, 只 不 过 m//g 不 改 变 任 何 东 西 。( 而 且 s///g 也 和 标 量m//g 不 一 样 , 它 不 是 递 增 匹 配 。)/e 修 饰 词 把 REPLACEMENT 当 作 一 个 <strong>Perl</strong> 代 码 块 , 而 不 仅 仅 是 一 个 替 换 的 字 串 。 执 行这 段 代 码 后 得 出 的 结 果 当 作 替 换 字 串 使 用 。 比 如 ,s/(0-9)+)/sprintf("%#x", $1)/ge 将把 所 有 数 字 转 换 成 十 六 进 制 , 比 如 , 把 2581 变 成 0xb23。 或 者 , 假 设 在 我 们 前 一 个 例子 里 , 你 不 知 道 是 否 所 有 版 本 都 有 名 称 , 因 此 , 你 希 望 把 这 些 没 有 名 称 的 保 留 不 动 。 可 以 利用 稍 微 有 点 创 造 力 的 /x 格 式 , 你 可 以 说 :159


s{version\s+([0-9.]+)}{$Names{$1}? "the $Names[$1} release": $&}xge;你 的 s///e 的 右 边 ( 或 者 本 例 中 的 下 半 部 分 ) 在 编 译 时 与 你 的 程 序 的 其 他 部 分 一 起 做 语 法检 查 和 编 译 。 在 编 译 过 程 中 , 任 何 语 法 错 误 都 会 被 检 测 到 , 而 运 行 时 例 外 则 被 忽 略 。 在 第 一个 /e 后 面 每 多 一 个 e( 象 /ee,/eee 等 等 ) 都 等 效 于 对 生 成 的 代 码 调 用 eval STRING,每 个 /e 相 当 于 一 次 调 用 。 这 么 做 等 于 计 算 了 代 码 表 达 式 的 结 果 并 且 把 例 外 俘 获 在 特 殊 变 量$@ 里 。 参 阅 本 章 后 面 的 “ 编 程 化 模 式 ” 获 取 更 多 信 息 。5.2.3.1 顺 便 修 改 一 下 字 串有 时 候 你 想 要 一 个 新 的 , 修 改 过 的 字 串 , 而 不 是 在 旧 字 串 上 一 阵 乱 改 , 新 字 串 以 旧 字 串 为 基础 。 你 不 用 写 :$lotr = $hobbit;$lotr =~ s/Bilbo/Frodo/g;你 可 以 把 这 些 组 合 成 一 个 语 句 。 因 为 优 先 级 关 系 , 必 须 在 赋 值 周 围 使 用 圆 括 弧 , 因 为 它 们 大多 和 使 用 了 =~ 的 表 达 式 结 合 在 一 起 。($lotr = $hobbit ) =~ s/Bilbo/Frodo/g;如 果 没 有 赋 值 语 句 周 围 的 圆 括 弧 , 你 只 修 改 了 $hobbit 并 且 把 替 换 的 个 数 存 储 在 $lotr里 , 那 样 会 得 到 很 傻 的 结 局 。160


你 不 能 对 数 组 直 接 使 用 s/// 操 作 符 。 这 时 , 你 需 要 一 个 循 环 。 幸 运 的 是 ,for/foreach 的别 名 特 性 加 上 它 把 $_ 当 作 缺 省 循 环 变 量 , 这 样 就 产 生 了 <strong>Perl</strong> 标 准 的 用 于 搜 索 和 替 换 一个 数 组 里 每 个 元 素 的 俗 语 :for (@chapters) { s/Bilbo/Frodo/g }s/bilbo/Frodo/g for @chapters;# 一 章 一 章 的 替 换# 一 样 的 东 西就 象 一 个 简 单 的 标 量 变 量 一 样 , 如 果 你 想 把 初 始 的 值 保 留 在 其 他 地 方 , 你 也 可 以 把 替 换 和 赋值 结 合 在 一 起 :@oldhues = ('bluebird', 'bluegrass', 'bluefish', 'the blues');for (@newhues = @oldhues) { s/blue/red/}print "@newhues\n";# 打 印 :redbird redgrass redfish the reds对 同 一 个 变 量 执 行 重 复 替 换 的 最 经 典 的 方 法 是 用 一 个 单 程 循 环 。 比 如 , 下 面 是 规 范 变 量 里 的空 白 的 方 法 :for ($string) {s/^\s+//;s/\s+$//;s/\s+/ /g;# 丢 弃 开 头 的 空 白# 丢 弃 结 尾 的 空 白# 压 缩 内 部 的 空 白}这 个 方 法 正 好 和 下 面 的 是 一 样 的 :$string = join(" ", split " ", $string);你 还 可 以 把 这 样 的 循 环 和 赋 值 用 在 一 起 , 就 象 我 们 在 数 组 的 例 子 里 做 的 那 样 :for( $newshow = $oldshow ) {s/Fred/Homer/g;s/Wilma/Marge/g;s/Pebbles/Lisa/g;s/Dino/Bart/g;161


}5.2.3.2 当 全 局 替 换 不 够 “ 全 局 ” 地 时 候有 时 候 , 你 用 /g 不 能 实 现 全 部 修 改 的 发 生 , 这 时 要 么 是 因 为 替 换 是 从 右 向 左 发 生 的 , 要么 是 因 为 你 要 求 $` 的 长 度 在 不 同 的 匹 配 之 间 改 变 。 通 常 你 可 以 通 过 反 复 调 用 s/// 做 你想 做 的 事 情 。 不 过 , 通 常 你 希 望 当 s/// 失 败 的 时 候 循 环 停 下 来 , 因 此 你 必 须 把 它 放 进 条 件里 , 这 样 又 让 循 环 的 主 体 无 所 事 事 。 因 此 我 们 只 写 一 个 1, 这 也 是 一 件 无 聊 的 事 情 , 不 过有 时 候 无 聊 比 没 希 望 好 。 下 面 是 一 些 例 子 , 它 们 又 用 了 一 些 正 则 表 达 式 怪 兽 :# 把 逗 号 放 在 一 个 整 数 的 合 理 的 位 置1 while s/(\d)(\d\d\d)(?!\d)/$1,$2/;# 把 水 平 制 表 符 扩 展 为 八 列 空 间1 while s/\t+/' ' x (length($&)*8 - length($`)%8)/e;# 删 除 ( 嵌 套 ( 甚 至 深 层 嵌 套 ( 象 这 样 ))) 的 括 弧1 while s/\([^()]*\)//g;# 删 除 重 复 的 单 词 ( 以 及 三 重 的 ( 和 四 重 的 。。。))1 while s/\b(\w+) \1\b/$1/gi;最 后 一 个 需 要 一 个 循 环 是 因 为 如 果 没 有 循 环 , 它 会 把 :Paris in THE THE THE THE spring.转 换 成 :Paris in THE THE spring。这 看 起 来 会 让 那 些 懂 点 法 文 的 人 觉 得 巴 黎 位 于 一 个 喷 冰 茶 的 喷 泉 中 间 , 因 为 "the"( 法 文 )是 法 文 “tea” 的 单 词 。 当 然 , 巴 黎 人 从 来 不 会 上 当 。5.2.4 tr/// 操 作 符 ( 转 换 )162


LVALUE =~ tr/SEARCHLIST/REPLACEMENTLIST/cdstr/SEARCHLIST/REPLACEMENTLIST/cds对 于 sed 的 爱 好 者 而 言 ,y/// 就 是 tr/// 的 同 义 词 。 这 就 是 为 什 么 你 不 能 调 用 名 为 y 的函 数 , 同 样 也 不 能 调 用 名 为 q 或 m 的 函 数 。 在 所 有 的 其 他 方 面 ,y/// 都 等 效 于 tr///,并 且 我 们 不 会 再 提 及 它 。把 这 个 操 作 符 放 进 关 于 模 式 匹 配 的 章 节 看 起 来 其 实 有 点 不 合 适 , 因 为 它 不 使 用 模 式 。 这 个 操作 符 逐 字 符 地 扫 描 一 个 字 串 , 然 后 把 每 个 在 SEARCHLIST ( 不 是 正 则 表 达 式 ) 里 出 现 的字 符 替 换 成 对 应 的 来 自 REPLACEMENTLIST( 也 不 是 替 换 字 串 ) 的 字 符 。 但 是 它 看 上 去象 m// 和 s///, 你 甚 至 还 可 以 和 它 一 起 使 用 =~ 和 !~ 绑 定 操 作 符 , 因 此 我 们 在 这 里描 述 它 。(qr// 和 split 都 是 模 式 匹 配 操 作 符 , 但 是 它 们 不 能 和 绑 定 操 作 符 一 起 使 用 , 因此 因 此 在 我 们 本 书 的 别 处 描 述 它 们 。 自 己 找 找 看 。)转 换 返 回 替 换 或 者 删 除 了 的 字 符 个 数 。 如 果 没 有 通 过 =~ 或 者 !~ 操 作 符 声 明 的 字 串 , 那么 使 用 $_ 字 串 。SEARCHLIST 和 REPLACEMENTLIST 可 以 用 一 个 划 线 定 义 一 个 顺 序字 符 的 范 围 :$message =~ tr/A-Za-z/N-ZA-Mn-za-m?;# 旋 转 13 加 密请 注 意 想 A-Z 这 样 的 范 围 假 设 你 用 的 是 线 性 字 符 集 , 比 如 ASCII。 但 是 不 同 的 字 符 集 的字 符 排 列 顺 序 是 不 一 样 的 。 一 个 合 理 的 原 则 是 , 只 使 用 起 始 点 都 是 相 同 大 小 写 的 字 母 序 列 ,如 (a-e,A-E), 或 者 数 字 (0-4)。 任 何 其 他 的 范 围 都 有 问 题 。 如 果 觉 得 有 问 题 , 他 们 写 成你 用 的 整 个 字 符 集 :ABCDE。SEARCHLIST 和 REPLACEMENTLIST 都 不 会 象 双 引 号 字 串 那 样 进 行 变 量 代 换 ; 不 过 ,你 可 以 使 用 那 些 映 射 为 特 殊 字 符 的 反 斜 杠 序 列 , 比 如 \n 或 \015。表 5-3 是 可 用 于 tr/// 操 作 符 的 修 饰 词 。 它 们 和 用 于 m//,s///, 或 qr// 上 的 完 全 不同 , 即 使 有 些 看 起 来 有 点 象 。表 5-3。tr/// 修 饰 词修 饰 词含 义/c 与 SEARCHLIST 为 补/d 删 除 找 到 的 但 是 没 有 替 换 的 字 符/s 消 除 重 复 的 字 符 。163


如 果 声 明 了 /c 修 饰 词 , 那 么 SEARCHLIST 里 的 字 符 会 被 求 补 ; 也 就 是 说 , 实 际 的 搜 索列 表 包 含 所 有 不 在 SEARCHLIST 里 的 字 符 。 如 果 是 Unicode, 这 样 可 能 会 代 表 许 多 字 符 ,不 过 因 为 它 们 是 逻 辑 存 储 的 , 而 不 是 物 理 存 储 , 所 以 你 不 用 害 怕 会 用 光 内 存 。/d 修 饰 词 把 tr/// 转 换 成 所 谓 的 “ 过 滤 吸 收 ” 操 作 符 : 任 何 由 SEARCHLIST 声 明 的 但 是没 有 在 RELPACEMENTLIST 里 给 出 替 换 的 字 符 将 被 删 除 。( 这 样 比 一 些 tr(1) 程 序 的 性质 显 得 更 加 灵 活 , 那 些 程 序 删 除 它 们 在 SERACHLIST 里 找 到 的 任 何 东 西 。)如 果 声 明 了 /s 修 饰 词 , 被 转 换 成 相 同 字 符 的 顺 序 字 符 将 被 压 缩 成 单 个 字 符 。如 果 使 用 了 /d 修 饰 词 , 那 么 RELPACEMENTLIST 总 是 严 格 地 解 释 成 声 明 的 样 子 。 否 则 ,如 果 REPLACEMENTLIST 比 SEARCHLIST 短 , 则 复 制 REPLACEMENTLIST 的 最 后一 个 字 符 , 直 到 足 够 长 为 止 。 如 果 RELPACEMENTLIST 为 空 , 则 复 制 SEARCHLIST,这 一 点 虽 然 奇 怪 , 但 很 有 用 , 尤 其 是 当 你 只 是 想 计 算 字 符 数 , 而 不 是 改 变 它 们 的 时 候 。 也 有利 于 用 /s 压 缩 字 符 。tr/aeiou/!/; # 把 所 有 元 音 字 母 转 换 成 !tr{/\\\r\n\b\f. }{_};# 把 怪 字 符 转 成 下 划 线tr/A-Z/a-z/ for @ARGV;# 把 字 符 规 则 化 为 小 写 ASCII$count = ($para =~ tr/\n//);# 计 算 $para 里 的 换 行 符$count = tr/0-9//;# 计 算 $_ 里 的 位$word =~ tr/a-zA-Z//s;# bookkeeper -> bokepertr/@$%*//d;tr#A-Za-z0-9+/##cd;# 删 除 这 里 几 个 字 符# 删 除 非 base64 字 符# 顺 便 修 改($HOST = $host) =~ tr/a-z/A-Z/;164


$pathname =~ tr/a-zA-Z/_/cs;# 把 非 ASCII 字 母 换 成 下 划 线tr [\200-\377]{\000-\177]; # 剥 除 第 八 位 , 字 节 操 作如 果 在 SEARCHLIST 里 同 一 个 字 符 出 现 的 次 数 多 于 一 次 , 那 么 只 有 第 一 个 有 效 。 因 此 :tr/AAA/XYZ/将 只 会 把 ($_ 里 的 ) 任 何 单 个 字 符 A 转 换 成 X。尽 管 变 量 不 会 代 换 进 入 tr///, 但 是 你 还 是 可 以 用 eval EXPR 实 现 同 样 效 果 :$count = eval "tr/$oldlist/$newlist/";die if $@;# 传 播 非 法 eval 内 容 的 例 外最 后 一 条 信 息 : 如 果 你 想 把 你 的 文 本 转 换 为 大 写 或 者 小 写 , 不 要 用 tr///。 用 双 引 号 里 的 \U或 者 \L 序 列 ( 或 者 等 效 的 uc 和 lc 函 数 ), 因 为 它 们 会 关 心 区 域 设 置 或 Unicode 信 息 ,而 tr/a-z/A-Z/ 不 关 心 这 些 。 另 外 在 Unicode 字 串 里 ,\u 序 列 和 它 的 对 应 ucfirst 函数 能 够 识 别 标 题 格 式 , 对 某 些 语 言 来 说 , 比 简 单 地 转 换 成 大 写 更 突 出 。5.3 元 字 符 和 元 符 号既 然 我 们 尊 重 这 些 神 奇 的 笼 子 , 那 么 我 们 就 可 以 回 过 头 来 看 看 笼 子 里 的 动 物 了 , 也 就 是 那 些你 放 在 模 式 里 好 看 的 符 号 。 到 现 在 你 应 该 已 经 看 到 这 样 的 事 实 , 就 是 这 些 符 号 并 不 是 普 通 的函 数 调 用 或 者 算 术 操 作 符 那 样 的 <strong>Perl</strong> 代 码 。 正 则 表 达 式 本 身 就 是 嵌 入 <strong>Perl</strong> 的 小 型 语 言 。( 在 现 实 社 会 里 总 是 有 小 丛 林 。)<strong>Perl</strong> 里 的 模 式 识 别 所 有 的 12 个 传 统 的 元 字 符 ( 所 谓 十 二 烂 人 ), 以 及 它 们 的 所 有 潜 能 和表 现 力 。 许 多 其 他 正 则 表 达 式 包 里 也 能 看 到 它 们 :\ | () [ { ^ $ * + ? .它 们 中 有 些 曲 解 规 则 , 令 跟 在 它 们 后 面 本 来 正 常 的 字 符 变 成 特 殊 的 。 我 们 不 喜 欢 把 长 序 列 叫做 “ 字 符 ”, 因 此 , 如 果 它 们 组 成 长 序 列 后 , 我 们 叫 它 们 元 符 号 ( 有 时 候 干 脆 就 叫 “ 符 号 ”)。但 是 在 顶 级 , 这 十 二 个 元 字 符 就 是 你 ( 和 <strong>Perl</strong>) 需 要 考 虑 的 所 有 内 容 。 任 何 东 西 都 是 从 这里 开 始 的 。165


有 些 简 单 的 元 字 符 就 代 表 它 们 自 己 , 象 . 和 ^ 和 $。 它 们 并 不 直 接 影 响 它 们 周 围 的 任 何东 西 。 有 些 元 字 符 运 行 起 来 象 前 缀 操 作 符 , 控 制 任 何 跟 在 后 面 的 东 西 , 象 反 斜 杠 “\” 。 其他 的 则 像 后 缀 操 作 符 , 控 制 紧 排 在 它 们 前 面 的 东 西 , 像 *,+, 和 ?。 有 一 个 元 字 符 :|,其 作 用 象 中 缀 操 作 符 , 站 在 它 控 制 的 操 作 数 中 间 。 甚 至 还 有 括 弧 操 作 符 , 作 用 类 似 包 围 操 作符 , 控 制 一 些 被 它 包 围 的 东 西 , 像 (...) 和 [...]。 圆 括 弧 尤 其 重 要 , 因 为 它 们 在 内 部 声 明 |的 范 围 , 而 在 外 部 声 明 *,+ 和 ? 的 范 围 。如 果 你 只 学 习 十 二 个 元 字 符 中 的 一 个 , 那 么 选 反 斜 杠 。( 恩 。。。 还 有 圆 括 弧 ) 这 是 因 为 反斜 杠 令 其 他 元 字 符 失 效 。 如 果 在 一 个 <strong>Perl</strong> 模 式 里 , 一 个 反 斜 杠 放 在 一 个 非 字 母 数 字 字 符 前 ,这 样 就 让 下 一 个 字 符 成 为 一 个 字 面 的 文 本 。 如 果 你 象 在 一 个 模 式 文 本 里 匹 配 十 二 个 元 字 符 中的 任 何 一 个 , 你 可 以 在 它 们 前 面 写 一 个 反 斜 杠 。 因 此 ,\. 匹 配 一 个 真 正 的 点 ,\$ 匹 配 真 正的 美 圆 符 ,\\ 是 一 个 真 正 的 反 斜 杠 等 等 。 这 样 做 被 称 做 “ 逃 逸 ” 元 字 符 , 或 曰 “ 引 号 包 围 之 ”,或 者 有 时 候 就 叫 做 “ 反 斜 杠 某 ”。( 当 然 , 你 已 经 知 道 反 斜 杠 可 以 用 于 禁 止 在 双 引 号 字 串 里 进行 变 量 代 换 。)虽 然 一 个 反 斜 杠 把 一 个 元 字 符 转 换 成 一 个 文 本 字 符 , 它 对 后 继 的 字 母 数 字 字 符 的 作 用 却 是 完全 另 一 码 事 。 它 把 本 来 普 通 的 东 西 变 特 别 。 也 就 是 说 , 它 们 在 一 起 形 成 元 字 符 。 我 们 在 表 5-7里 给 出 了 一 个 按 字 母 排 序 的 元 字 符 表 。5.3.1 元 字 符 表符 号 原 子 性含 义\... 变 化 反 逃 逸 下 一 个 非 字 母 数 字 字 符 , 转 意 下 一 个 字 母 数 字 ( 可 能 )...|... 否 可 选 ( 匹 配 前 者 或 者 后 者 )。(...) 是 分 组 ( 当 作 单 元 对 待 )。[...]是 字 符 表 ( 匹 配 一 个 集 合 中 的 一 个 字 符 )。否 如 果 在 字 串 开 头 则 为 真 ( 或 者 可 能 是 在 任 何 换 行 符 后 面 。). 是 匹 配 一 个 字 符 ( 通 常 除 了 换 行 符 以 外 )。$ 否 在 字 串 尾 部 时 为 真 ( 或 者 可 能 是 在 任 何 换 行 符 前 面 )。至 于 量 词 , 我 们 会 在 它 们 自 己 的 节 里 详 细 描 述 。 量 词 表 示 前 导 的 原 子 ( 也 就 是 说 , 单 字 符 或者 分 组 ) 应 该 匹 配 的 次 数 。 它 们 列 在 表 5-5 中 。表 5-5。 正 则 量 词量 词 原 子 性 含 义* 否 匹 配 0 或 者 更 多 次 数 ( 最 大 )。+ 否 匹 配 或 者 更 多 次 数 ( 最 大 )。166


? 否 匹 配 1 或 者 0 次 ( 最 大 )。{COUNT} 否 匹 配 COUNT 次{MIN,} 否 匹 配 至 少 MIN 次 ( 最 大 )。{MIN,MAX} 否 匹 配 至 少 MIN 次 但 不 超 过 MAX 次 ( 最 大 )*? 否 匹 配 0 或 者 更 多 次 ( 最 小 )+? 否 匹 配 1 或 者 更 多 次 ( 最 小 )?? 否 匹 配 0 或 者 1 次 ( 最 小 ){MIN,}? 否 匹 配 最 多 MIN 次 ( 最 小 ){MIN,MAX}? 否 匹 配 至 少 MIN 次 但 不 超 过 MAX 次 ( 最 小 )最 小 量 词 会 试 图 匹 配 在 它 的 许 可 范 围 内 的 尽 可 能 少 的 次 数 。 最 大 量 词 会 试 图 匹 配 在 它 的 许 可范 围 内 的 尽 可 能 多 的 次 数 。 比 如 , .+ 保 证 至 少 匹 配 字 串 的 一 个 字 符 , 但 是 如 果 有 机 会 ,它 会 匹 配 所 有 机 会 。 这 里 的 机 会 将 在 稍 后 的 “ 小 引 擎 的 / 能 与 不 能 /” 节 里 讲 。你 还 会 注 意 量 词 决 不 能 量 化 。我 们 想 给 新 类 型 的 元 符 号 一 个 可 以 扩 展 的 语 法 。 因 为 我 们 只 需 要 使 用 十 二 个 元 字 符 , 所 以 我们 选 用 原 先 被 认 为 是 非 法 正 则 的 序 列 做 任 意 语 法 扩 展 。 这 些 元 符 号 的 形 式 都 是 (?KEY...);也 就 是 , 一 个 开 圆 括 弧 后 面 跟 着 一 个 问 号 , 然 后 是 KEY 和 模 式 其 余 部 分 。KEY 字 符 表 明它 是 哪 种 正 则 扩 展 。 参 阅 表 5-6 看 看 正 则 扩 展 的 一 个 列 表 。 它 们 中 大 多 数 性 质 象 列 表 , 因为 它 们 基 于 圆 括 弧 , 不 过 它 们 还 是 有 附 加 含 义 。 同 样 , 只 有 原 子 可 以 量 化 , 因 为 它 们 代 表 真正 存 在 ( 潜 在 地 ) 的 东 西 。表 5-6 扩 展 的 正 则 序 列扩 展 原 子 性 含 义(?#...) 否 注 释 , 抛 弃(?:...) 是 只 集 群 , 不 捕 获 的 圆 括 弧(?imsx-imsx) 否 打 开 / 关 闭 模 式 修 饰 词(?imsx-imsx:...) 是集 群 圆 括 弧 加 修 饰 词(?=...) 否 如 果 前 向 查 找 断 言 成 功 , 返 回 真(?!...) 否 如 果 前 向 查 找 断 言 失 败 , 返 回 真(?...) 是 匹 配 未 反 向 跟 踪 的 子 模 式(?{...}) 否 执 行 嵌 入 的 <strong>Perl</strong> 代 码(??{...}) 是 匹 配 来 自 嵌 入 <strong>Perl</strong> 代 码 。167


(?(...)... ...) 是 匹 配 if-then-elase 模 式(?(...)...) 是 匹 配 if-then 模 式最 后 , 表 5-7 显 示 了 所 有 你 常 用 的 字 母 数 字 元 符 号 。( 那 些 在 变 量 代 换 回 合 处 理 过 的 符 号在 原 子 性 列 里 用 一 个 划 线 标 记 , 因 为 引 擎 永 远 不 会 看 到 它 们 。)表 5-7。 字 母 数 字 正 则 元 符 号符 号 原 子 性 含 义\0 是 匹 配 空 字 符 (ASCII NUL)。\NNN 是 匹 配 给 出 八 进 制 的 字 符 , 最 大 值 为 \377。\n 是 匹 配 前 面 第 n 个 捕 获 字 串 ( 十 进 制 )。\a 是 匹 配 警 钟 字 符 (BEL)。\A 否 如 果 在 字 串 的 开 头 为 真\b 是 匹 配 退 各 字 符 (BS)。\b 否 在 字 边 界 为 真\B 否 不 在 字 边 界 时 为 真\cX 是 匹 配 控 制 字 符 Control-x(\cZ,\c[, 等 )。\C 是 匹 配 一 个 字 节 (C 字 符 ), 甚 至 在 utf8 中 也 如 此 ( 危 险 )\d 是 匹 配 任 何 数 字 字 符\D 是 匹 配 任 何 非 数 字 字 符\e 是 匹 配 逃 逸 字 符 (ASCII ESC, 不 是 反 斜 杠 )。\E —— 结 束 大 小 写 (\L,\U) 或 者 掩 码 (\Q) 转 换\f 是 匹 配 进 页 字 符 (FF)。\G 否 如 果 在 前 一 个 m//g 的 匹 配 结 尾 位 置 时 为 真\l —— 只 把 下 一 个 字 符 变 成 小 写\L —— 把 \E 以 前 的 字 母 都 变 成 小 写\n 是 匹 配 换 行 符 字 符 ( 通 常 是 NL, 但 是 在 Mac 上 是 CR)。\N{NAME} 是\p{PROP} 是\P{PROP} 是匹 配 命 名 字 符 (\N{greek:Sigma})。匹 配 任 何 有 命 名 属 性 的 字 符匹 配 任 何 没 有 命 名 属 性 的 字 符\Q —— 引 起 ( 消 元 ) 直 到 \E 前 面 的 字 符\r 是 匹 配 返 回 字 符 ( 通 常 是 CR, 但 是 在 Mac 上 是 NL)。\s 是 匹 配 任 何 空 白 字 符 。\S 是 匹 配 任 何 非 空 白 字 符 。168


\t 是 匹 配 水 平 制 表 符 (HT)。\u —— 只 把 下 一 个 字 符 变 成 标 题 首 字 符\U —— 大 写 ( 不 是 标 题 首 字 符 )\E 以 前 的 字 符 。\w 是 匹 配 任 何 “ 字 ” 字 符 ( 字 母 数 字 加 "_" )。\W 是 匹 配 任 何 “ 非 字 ” 字 符 。\x{abcd} 是 匹 配 在 十 六 进 制 中 给 出 的 字 符 。\X 是 匹 配 Unicode 里 的 ” 组 合 字 符 序 列 “ 字 串 。\z 否 只 有 在 字 串 结 尾 时 为 真\Z 否 在 字 串 结 尾 或 者 在 可 选 的 换 行 符 之 前 为 真 。如 果 在 \p 和 \P 里 的 属 性 名 字 是 一 个 字 符 , 那 么 花 括 弧 是 可 选 的 。 如 果 \x 里 的 十 六 进制 数 为 两 位 或 者 更 少 , 那 么 花 括 弧 也 是 可 选 的 。 在 \N 里 的 花 括 弧 决 不 能 省 略 。只 有 在 含 义 中 带 “ 匹 配 。。。” 或 者 “ 匹 配 任 何 。。。” 字 样 的 元 符 号 才 能 够 在 字 符 表 ( 方 括 弧 )里 面 使 用 。 也 就 是 说 , 字 符 表 仅 限 于 包 含 特 定 的 字 符 集 , 因 此 在 字 符 表 里 面 , 你 只 能 使 用 那些 描 述 其 他 特 定 字 符 集 的 元 符 号 , 或 者 那 些 描 述 特 定 独 立 字 符 的 元 符 号 。 当 然 , 这 些 元 符 号也 可 以 和 其 他 非 分 类 元 符 号 一 起 在 字 符 表 外 面 用 , 不 过 , 这 里 请 注 意 =b 是 两 只 完 全 不 同的 怪 兽 : 它 在 字 符 表 内 是 退 格 字 符 , 而 在 外 边 是 一 个 字 边 界 断 言 。一 个 模 式 可 以 匹 配 的 字 符 的 数 量 和 一 个 双 引 号 字 串 可 以 替 换 的 字 符 的 数 量 有 一 些 重 叠 。 因 为正 则 要 经 历 两 个 回 合 , 所 以 有 时 候 应 该 由 哪 个 回 合 处 理 一 个 给 定 的 字 符 会 显 得 有 些 混 乱 。 如果 出 现 混 乱 , 这 种 字 符 的 变 量 代 换 回 合 就 推 迟 给 正 则 表 达 式 分 析 器 。但 是 只 有 当 变 量 代 换 回 合 知 道 它 正 在 分 析 一 个 正 则 的 时 候 , 它 才 能 把 变 量 代 换 推 迟 给 正 则 分析 器 。 你 可 以 把 正 则 表 达 式 声 明 为 普 通 的 双 引 号 包 围 字 串 , 但 这 样 你 就 必 须 遵 循 普 通 的 双 引号 包 围 规 则 。 任 何 前 面 碰 巧 映 射 为 时 间 字 符 的 元 符 号 仍 然 生 效 , 即 使 它 们 没 有 被 推 迟 给 正 则分 析 器 也 如 此 。 但 是 在 普 通 的 双 引 号 里 你 不 能 使 用 任 何 其 它 的 元 符 号 ( 或 者 任 何 类 似 的 构 造 ,比 如 `...`,qq(...),qx(...), 或 者 等 效 的 “ 此 处 ” 文 档 )。 如 果 你 想 你 的 字 串 分 析 成 一 个正 则 表 达 式 而 不 做 任 何 匹 配 , 你 应 该 使 用 qr// ( 引 号 构 造 正 则 ) 操 作 符 。请 注 意 大 小 写 和 元 引 号 包 围 转 换 逃 逸 (\U 和 它 的 伙 伴 ) 必 须 在 变 量 代 换 回 合 处 理 , 因 为 这些 元 符 号 的 用 途 就 是 影 响 变 量 代 换 。 如 果 你 用 单 引 号 禁 止 变 量 代 换 , 那 么 你 也 不 能 获 得 转 换逃 逸 。 在 任 何 单 引 号 字 串 里 , 都 不 会 进 行 变 量 或 者 转 换 逃 逸 (\U 等 ) 的 扩 展 , 在 单 号 包 围的 的 m'...' 或 者 qr'...' 操 作 符 里 也 不 会 。 甚 至 在 你 做 代 换 的 时 候 , 如 果 这 些 转 换 逃 逸 是一 个 变 量 代 换 的 结 果 , 那 么 它 们 也 会 被 忽 略 , 因 为 这 个 时 候 他 们 想 要 影 响 变 量 代 换 已 经 太 晚了 。169


尽 管 字 符 替 换 操 作 符 不 处 理 正 则 表 达 式 , 但 是 我 们 讨 论 过 的 任 何 匹 配 单 个 特 定 字 符 的 元 符 号在 tr/// 操 作 中 仍 然 可 用 。 而 其 他 的 用 不 了 ( 除 了 反 斜 杠 以 外 , 它 继 续 按 照 它 原 来 的 样 子运 转 。)5.3.2 特 定 的 字 符如 前 所 述 , 非 特 殊 的 东 西 在 模 式 里 匹 配 自 身 。 这 意 味 着 一 个 /a/ 匹 配 一 个 "a", 一 个 /=/匹 配 一 个 "=" 等 等 。 不 过 , 有 些 字 符 可 不 是 那 么 容 易 在 键 盘 上 敲 进 去 , 或 者 即 使 你 敲 进 去了 , 也 不 会 在 打 印 输 出 中 显 示 出 来 ; 最 臭 名 昭 著 的 例 子 就 是 控 制 字 符 。 在 正 则 表 达 式 里 ,<strong>Perl</strong> 识 别 下 列 双 引 号 包 围 的 字 符 别 名 :逃 逸含 义\0 空 字 符 (ASCII NUL)\a 警 铃 (BEL)\e 逃 逸 (ESC)\f 进 纸 (FF)\n 换 行 符 (NL,Mac 里 的 CR)\r 回 车 (CR,Mac 里 的 NL)\t 水 平 制 表 符 (HT)就 象 在 双 引 号 包 围 字 串 里 一 样 ,<strong>Perl</strong> 还 识 别 模 式 里 的 下 面 四 种 元 符 号 :\cX\NNN\x{LONGHEX}一 个 命 名 的 控 制 字 符 , 象 \cC 指 Control-C,\cZ 指 Control-Z,\c[ 指 ESC, 而 \c? 表 示DEL。用 两 位 或 者 三 位 八 进 制 码 声 明 的 字 符 。 除 了 小 于010 的 数 值 ( 十 进 制 8) 外 , 前 导 的 0 是 可 选的 , 因 为 ( 和 在 双 引 起 字 串 里 不 同 ) 一 位 数 字 的东 西 总 认 为 是 用 于 在 模 式 里 捕 获 字 串 的 引 用 。 如果 你 在 模 式 里 先 捕 获 了 至 少 n 个 子 字 串 , 那 多 位数 字 解 释 成 第 n 个 引 用 ( 这 里 n 被 认 为 是 一 个十 进 制 数 )。 否 则 , 他 们 被 解 释 成 一 个 用 八 进 制声 明 的 字 符 。一 个 用 一 到 两 个 十 六 进 制 数 位 ([0-9a-fA-F]) 声明 的 字 符 , 比 如 \x1B。 一 位 数 字 的 形 式 只 有 在170


\N{NAME}后 面 跟 随 的 字 符 不 是 一 个 十 六 进 制 数 字 才 可 用 。如 果 使 用 了 花 括 弧 , 你 想 用 多 少 位 数 字 都 可 以 ,这 时 候 结 果 可 能 是 一 个 Unicode 字 符 。 比 如 ,\x{262} 匹 配 一 个 Unicode YIN YANG。一 个 命 名 字 符 , 如 \N{GREEK SMALL LETTEREPSILON},\N{Greek:epsilon}, 或 者\N{epsilon}。 它 要 求 使 用 第 三 十 一 章 , 用 法 模块 , 里 描 述 的 use charnames 用 法 , 它 同 时 还判 断 你 可 能 使 用 那 些 名 字 中 的 哪 一 个 ( 分 别 是":long",":full",":short", 对 应 上 面 三 个 风 格 。)你 可 以 在 离 你 最 近 的 Unicode 标 准 文 档 里 找 到 所 有 Unicode 字 符 名 字 的 列 表 , 或 者 在PATH_TO_PERLLIB/unicode/Names.txt 里 也 有 。5.3.3 通 配 元 符 号三 个 特 殊 的 元 符 号 可 以 用 做 通 用 通 配 符 , 它 们 的 每 一 个 都 可 以 匹 配 " 任 何 " 字 符 ( 是 " 任 何 "中 的 某 些 字 符 )。 它 们 是 句 点 ("."),\c 和 \x。 它 们 都 不 能 在 字 符 表 里 使 用 。 你 不 能 在字 符 表 里 用 句 点 是 因 为 它 会 匹 配 ( 几 乎 ) 任 何 存 在 的 字 符 , 因 此 它 本 身 就 是 某 种 万 能 字 符 。如 果 你 想 包 括 或 者 排 除 所 有 东 西 , 也 没 有 什 么 必 要 使 用 一 个 字 符 表 。 特 殊 通 配 符 \C 和 \X有 着 特 殊 的 结 构 化 含 义 , 而 这 些 特 殊 含 义 和 选 择 单 个 Unicode 字 符 的 表 示 法 关 联 得 并 不好 , 而 该 表 示 法 才 是 字 符 表 运 行 的 层 次 .句 点 元 字 符 匹 配 除 了 换 行 符 以 外 的 任 何 单 字 符 。( 如 果 带 着 /s 修 饰 词 , 也 能 匹 配 换 行 符 。)和 十 二 个 特 殊 字 符 里 的 其 它 字 符 一 样 , 如 果 你 想 匹 配 一 个 文 本 句 点 , 你 就 必 须 用 一 个 反 斜 扛逃 逸 它 。 比 如 , 下 面 的 代 码 检 查 一 个 文 件 名 是 否 以 一 个 句 点 后 面 跟 着 一 个 单 字 符 扩 展 名 结 尾的 :if ($pathname =~ /\.(.)\z/s) {print "Ends in $1\n";}第 一 个 句 点 是 逃 逸 了 的 , 是 文 本 句 点 , 而 第 二 个 句 点 说 " 匹 配 任 何 字 符 "。\z 说 只 匹 配 字 串末 尾 的 东 西 , 而 \s 修 饰 词 令 点 也 可 以 匹 配 换 行 符 。( 的 确 , 用 换 行 符 做 文 件 扩 展 名 不 怎 么漂 亮 , 但 并 不 是 说 就 不 能 做 。)171


点 元 字 符 经 常 和 量 词 一 起 使 用 。.* 匹 配 尽 可 能 多 的 字 符 , 而 .*? 匹 配 尽 可 能 少 的 字 符 。 不过 有 时 候 它 不 用 量 词 而 是 自 己 解 决 长 度 问 题 : /(..):(..):(..)/ 匹 配 三 个 用 冒 号 分 隔 的 域 ,每 个 域 两 个 字 符 长 。如 果 你 在 一 个 use utf8 用 法 的 词 法 范 围 里 编 译 的 模 式 里 使 用 一 个 点 , 那 么 它 就 匹 配 任 何Unicode 字 符 。( 你 可 能 不 需 要 用 use utf8, 不 过 偶 然 还 是 会 发 生 的 , 在 你 阅 读 到 这 里的 时 候 你 可 能 不 需 要 这 个 用 法 。)use utf8;use charnames qw/:full/;%BWV[887] = "G\N{MUSIC SHARP SIGN} minor";($note, $black, $mode) = $BWV[886] =~ /^([A-G])(.)\s+(\S+)/;print "That's lookin' sharp!\n" if $black eq chr(9839);元 符 号 \X 在 更 广 的 概 念 上 匹 配 字 符 。 它 实 际 上 是 匹 配 一 个 由 一 个 或 多 个 Unicode 字 符组 成 的 字 串 , 这 个 字 串 就 是 所 谓 的 " 组 合 字 符 序 列 "。 这 样 的 序 列 包 括 一 个 基 本 字 符 和 后 面 跟着 任 意 个 " 标 志 " 字 符 ( 象 变 音 符 和 分 音 符 那 样 的 区 分 标 志 ) 一 起 组 成 一 个 逻 辑 单 元 。\X 实际 上 等 效 于 (?:\PM\pM*)。 这 样 做 允 许 匹 配 一 个 逻 辑 字 符 , 即 使 这 几 个 字 符 实 际 上 是 由 几个 独 立 的 字 符 组 成 的 也 行 。 如 果 匹 配 任 意 组 合 字 符 , 那 么 在 /\X/ 里 匹 配 的 长 度 将 超 过 一 个字 符 长 。( 而 且 这 里 的 字 符 长 度 和 字 节 长 度 没 有 什 么 关 系 )。如 果 你 在 使 用 Unicode 并 且 真 的 想 获 取 单 字 节 而 不 是 单 字 符 , 那 么 你 可 以 使 用 \C 元 字符 。 它 将 总 是 匹 配 一 个 字 节 ( 具 体 说 , 就 是 一 个 C 语 言 的 char 类 型 ), 而 不 管 是 否 会 与你 的 Unicode 字 符 流 步 调 失 调 。 参 阅 第 十 五 章 里 关 于 做 这 些 事 情 时 合 适 的 警 告 。5.4 字 符 表在 模 式 匹 配 里 , 你 可 以 匹 配 任 意 字 符 , 不 管 它 们 有 没 有 特 殊 性 质 。 有 四 种 声 明 字 符 表 的 方 法( 译 注 : 孔 乙 己 ?:)。 你 可 以 按 照 传 统 的 方 法 声 明 字 符 集 —— 用 方 括 弧 和 枚 举 可 能 的 字 符 ,或 者 或 者 你 可 以 使 用 三 种 记 忆 法 中 的 任 意 一 种 : 经 典 <strong>Perl</strong> 表 , 新 <strong>Perl</strong>Unicode 属 性 , 或者 标 准 POSIX 表 。 这 些 缩 写 均 只 匹 配 其 字 符 集 中 的 一 个 字 符 。 你 可 以 量 化 它 们 , 使 它 们可 以 匹 配 更 多 的 字 符 , 比 如 \d+ 匹 配 一 个 或 者 多 个 数 字 。( 一 个 比 较 容 易 犯 的 错 误 是 认 为\w 匹 配 一 个 字 。 用 \w+ 匹 配 一 个 字 。)5.4.1 客 户 化 字 符 表172


一 个 方 括 弧 中 的 一 个 枚 举 字 符 列 表 被 称 为 字 符 表 , 它 匹 配 列 表 中 的 任 何 一 个 字 符 。 比 如 ,[aeiouy] 匹 配 一 个 英 文 中 的 元 音 字 母 。( 对 于 威 尔 士 要 加 "w", 对 于 苏 格 兰 加 个 "r"。)要 匹 配 一 个 右 方 括 弧 , 你 可 以 用 反 斜 杠 逃 逸 之 或 者 把 它 放 在 列 表 开 头 。字 符 范 围 可 以 用 一 个 连 字 符 和 a-z 表 示 法 表 示 。 你 可 以 合 并 多 个 范 围 ; 比 如 [0-9a-fA-F]匹 配 一 个 十 六 进 制 “ 位 ”。 你 可 以 用 反 斜 杠 避 免 连 字 符 被 解 释 为 一 个 范 围 分 隔 符 , 或 者 把 它 放在 表 的 开 头 或 者 结 尾 ( 后 面 的 方 法 虽 然 不 易 读 , 但 是 比 较 常 用 )。在 字 符 表 开 头 的 脱 字 符 ( 或 者 说 是 抑 扬 符 号 , 或 者 帽 子 , 或 者 向 上 箭 头 "^" ) 反 转 该 字 符表 , 结 果 是 匹 配 任 何 不 在 此 列 表 中 的 字 符 。( 要 匹 配 脱 字 符 , 要 么 不 要 放 在 开 头 , 或 者 是 用反 斜 杠 逃 逸 )。 比 如 ,[^aeiouy] 匹 配 任 何 不 是 元 音 的 字 母 。 不 过 , 对 待 字 符 表 反 意 要 小心 些 , 因 为 字 符 的 领 域 在 不 断 扩 展 。 比 如 , 那 个 字 符 表 匹 配 辅 音 —— 而 在 西 利 尔 语 , 希 腊 语和 几 乎 任 何 语 言 里 还 匹 配 空 白 , 换 行 符 和 其 他 任 何 东 西 ( 包 括 元 音 ), 更 不 用 说 中 日 韩 文 里的 标 记 了 。 而 且 以 后 还 可 能 有 Cirth,Tengwar, 和 Klingon。( 当 然 , 还 可 能 有 LinearB 和 Etruscan) 所 以 你 最 好 还 是 明 确 声 明 你 的 辅 音 , 比 如 [cbdfghjklmnpqrstvwxyz],或 者 简 写 为 [b-df-hj-p-tv-z]。( 这 样 还 解 决 了 “y” 需 要 在 两 个 地 方 同 时 出 现 的 问 题 , 排 除了 一 个 补 集 。)字 符 表 里 支 持 普 通 字 符 元 符 号 ,( 参 阅 “ 声 明 字 符 ”), 比 如 \n,\t,\cX,\NNN, 和\N{NAME}。 另 外 , 你 可 以 在 一 个 字 符 表 里 使 用 \b 表 示 一 个 退 格 , 就 象 它 在 双 引 号 字 串里 显 示 的 那 样 。 通 常 , 在 一 个 模 式 匹 配 里 , 它 意 味 着 一 个 字 边 界 。 但 是 零 宽 度 的 断 言 在 字 符表 里 没 有 任 何 意 义 , 因 此 这 里 的 \b 返 回 到 它 在 字 串 里 的 普 通 含 义 。 你 还 可 以 使 用 我 们 本章 稍 后 预 定 义 的 任 何 字 符 表 ( 经 典 ,Unicode 或 POSIX), 但 是 不 要 把 它 们 用 在 一 个 范围 的 结 尾 —— 那 样 没 有 意 义 , 所 以 "-" 会 被 解 释 成 文 本 。所 有 其 他 的 元 符 号 在 方 括 弧 中 都 失 去 特 殊 意 义 。 实 际 上 , 你 不 能 在 这 里 面 使 用 三 个 普 通 通 配符 中 的 任 何 一 个 :".",\X 或 \C。 不 允 许 第 一 个 字 符 通 常 令 人 奇 怪 , 不 过 把 普 遍 意 义 的 字符 表 用 做 有 限 制 的 形 式 的 确 没 有 什 么 意 义 , 而 且 你 常 会 在 一 个 字 符 表 中 要 用 到 文 本 句 点 ——比 如 , 当 你 要 匹 配 文 件 名 的 时 候 。 而 且 在 字 符 表 里 声 明 量 词 , 断 言 或 者 候 选 都 是 没 有 意 义 的 ,因 为 这 些 字 符 都 是 独 立 解 释 的 。 比 如 ,[fee|fie|foe|foo] 和 [feio|] 是 一 样 的 。5.4.2 典 型 <strong>Perl</strong> 字 符 表 缩 写从 一 开 始 ,<strong>Perl</strong> 就 已 经 提 供 了 一 些 字 符 表 缩 写 。 它 们 在 表 5-8 中 列 出 。 它 们 都 是 反 斜 杠字 母 元 字 符 , 而 且 把 它 们 的 字 母 换 成 大 写 后 , 它 们 的 含 义 就 是 小 写 版 本 的 反 义 。 这 些 元 字 符的 含 义 并 不 像 你 想 象 的 那 么 固 定 , 其 含 义 可 能 在 新 的 Unicode 标 准 出 台 后 改 变 , 因 为 新 标准 会 增 加 新 的 数 字 和 字 母 。( 为 了 保 持 旧 的 字 节 含 义 , 你 总 是 可 以 使 用 use bytes。 要 解173


释 utf8 的 含 义 , 参 阅 本 章 后 面 的 "Unicode 属 性 "。 不 管 怎 样 ,utf8 含 义 都 是 字 节 含 义的 超 集 。)表 5-8 典 型 字 符 表符 号 含 义 做 字 节 做 utf8\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}( 好 好 好 , 我 们 知 道 大 多 数 字 里 面 没 有 数 字 和 下 划 线 ,\w 的 用 意 是 用 于 匹 配 典 型 的 编 程 语言 里 的 记 号 。 就 目 前 而 言 , 是 匹 配 <strong>Perl</strong> 里 的 记 号 。)这 些 元 符 号 在 方 括 弧 外 面 或 者 里 面 都 可 以 使 用 , 也 就 是 说 不 管 作 为 独 立 存 在 的 符 号 还 是 作 为一 个 构 造 成 的 字 符 表 的 一 部 分 存 在 都 行 :if ($var =~ /\D/) { warn "contains non-digit" }if ($var =~ /[^\w\s.]/) { warn "contains non-(word, space, dot)"}5.4.3 Unicode 属 性Unicode 属 性 可 以 用 \p{PROP} 及 其 补 集 \P{PROP} 获 取 . 对 于 那 些 比 较 少 见 的 名 字里 只 有 一 个 字 符 的 属 性 , 花 括 弧 是 可 选 的 , 就 象 \pN 表 示 一 个 数 字 字 符 ( 不 一 定 是 十 进 制数 - 罗 马 数 字 也 是 数 字 字 符 )。 这 些 属 性 表 可 以 独 自 使 用 或 者 与 一 个 字 符 表 构 造 一 起 使 用 :if ($var =~ /^\p{IsAlpha}+$/) {print "all alphabetic" }if ($var =~ s/[\p{Zl}\p{Zp}]/\n/g) {print "fixed newline wannabes"}有 些 属 性 是 直 接 由 Unicode 标 准 定 义 的 , 而 有 些 属 性 是 <strong>Perl</strong> 基 于 标 准 属 性 组 合 定 义 的 ,Zl 和 Zp 都 是 标 准 Unicode 属 性 , 分 别 代 表 行 分 隔 符 和 段 分 隔 符 , 而 IsAlpha ? 是 <strong>Perl</strong>而 IsAlpha ? 是 <strong>Perl</strong> 定 义 的 , 是 一 个 组 合 了 标 准 属 性 Ll, Lu, Lt, 和 Lo ( 也 就 是 小 写 ,大 写 , 标 题 或 者 其 它 字 母 ) 的 属 性 表 。 在 <strong>Perl</strong> 5.6.0 里 , 要 想 用 这 些 属 性 , 你 得 用 use utf8。将 来 会 放 松 这 个 限 制 。174


还 有 很 多 其 它 属 性 。 我 们 会 列 出 我 们 知 道 的 那 些 , 但 是 这 个 列 表 肯 定 不 完 善 。 很 可 能 在 新 版本 的 Unicode 里 会 定 义 新 的 属 性 , 而 且 你 甚 至 也 可 以 定 义 自 己 的 属 性 。 稍 后 我 们 将 更 详 细地 介 绍 这 些 。Unicode 委 员 会 制 作 了 在 线 资 源 , 这 些 资 源 成 为 <strong>Perl</strong> 用 于 其 Unicode 实 现 里 的 各 种 文件 。 关 于 更 多 这 些 文 件 的 信 息 , 请 参 阅 第 15 章 。 你 可 以 在 文 档PATH_TO_PERLLIB/unicode/Unicode3.html 里 获 得 很 不 错 的 关 于 Unicode 的 概 述性 介 绍 。 这 里 PATH_TO_PERLLIB 是 下 面 命 令 的 打 印 输 出 :perl -MConfig -le 'print $Config{privlib}'大 多 数 Unicode 的 属 性 形 如 \p{IsPROP}。Is 是 可 选 的 , 因 为 它 太 常 见 了 , 不 过 你 还 是会 愿 意 把 它 们 写 出 来 的 , 因 为 可 读 性 好 。5.4.3.1 <strong>Perl</strong> 的 Unicode 属 性首 先 , 表 5-9 列 出 了 <strong>Perl</strong> 的 组 合 属 性 。 它 们 定 义 得 合 理 地 接 近 于 标 准 POSIX 定 义 的 字符 表 。表 5-9. 组 合 Unicode 属 性属 性等 效IsASCII ? [\x00-\x7f]IsAlnum ? [\p{IsLl}\p{IsLu}\p{IsLt}\p{IsLo}\p{IsNd}]IsAlpha ?IsCntrl ?IsDigit ?[\p{IsLl}\p{IsLu}\p{IsLt}\p{IsLo}]\p{IsC}\p{Nd}IsGraph ? [^\pC\p{IsSpace}]IsLower ? \p{IsLl}IsPrint ?IsPunct ?\P{IsC}\p{IsP}IsSpace ? [\t\n\f\r\p{IsZ}]IsUpper ? [\p{IsLu}\p{IsLt}]IsWord ?[_\p{IsLl}\p{IsLu}\p{IsLt}\p{IsLo}\p{IsNd}]IsXDigit ? [0-9a-fA-F]<strong>Perl</strong> 还 为 标 准 Unicode 属 性 ( 见 下 节 ) 的 每 个 主 要 范 畴 提 供 了 下 列 组 合 :属 性 含 义 是 否 规 范 的175


IsC ? 错 误 控 制 代 码 等 是IsL ? 字 母IsM ? 标 志IsN ? 数 字IsP ? 标 点IsS ? 符 号IsZ ? 分 隔 符部 分是是否否是5.4.3.2 标 准 的 Unicode 属 性表 5-10 列 出 了 大 多 数 基 本 的 标 准 Unicode 属 性 , 源 自 每 个 字 符 的 类 别 。 没 有 哪 个 字 符是 多 于 一 个 类 别 的 成 员 。 有 些 属 性 是 规 范 的 , 而 有 些 只 是 提 示 性 的 。 请 参 阅 Unicode 标 准获 取 那 些 标 准 的 说 辞 , 看 看 什 么 是 规 范 信 息 , 而 什 么 又 是 提 示 信 息 。表 5-10 标 准 Unicode 属 性属 性 含 义 规 范 化IsCc ? 其 他 , 控 制 是IsCf ? 其 他 , 格 式 是IsCn ? 其 他 , 未 赋 值 是IsCo ? 其 他 , 私 有 使 用 是IsCs ? 其 他 , 代 理 是IsLl ? 字 母 , 小 写 是IsLm ? 字 母 , 修 饰 词否IsLo ? 字 母 , 其 他 否IsLt ? 字 母 , 抬 头 是IsLu ? 字 母 , 大 写 是IsMc ? 标 记 , 组 合 是IsMe ? 标 记 , 闭 合IsMn ? 标 记 , 非 空 白IsNd ? 数 字 , 十 进 制 数是是是IsNl ? 数 字 , 字 母 是IsNo ? 数 字 , 其 他 是IsPc ? 标 点 , 联 接 符 否IsPd ? 标 点 , 破 折 号 否176


IsPe ? 标 点 , 关 闭 否IsPf ? 标 点 , 结 束 引 用 否IsPi ? 标 点 , 初 始 引 用 否IsPo ? 标 点 , 其 他 否IsPs ? 标 点 , 打 开 否IsSc ? 符 号 , 货 币 否IsSk ? 符 号 , 修 饰 词 否IsSm ? 符 号 , 数 学否IsSo ? 符 号 , 其 他 否IsZl ? 分 隔 符 , 行 是IsZp ? 分 隔 符 , 段 落 是IsZs ? 分 隔 符 , 空 白 是另 外 一 个 有 用 的 属 性 集 是 关 于 一 个 字 符 是 否 可 以 分 解 为 更 简 单 的 字 符 。( 规 范 分 解 或 者 兼 容分 解 )。 规 范 分 解 不 会 丢 失 任 何 格 式 化 信 息 。 兼 容 分 解 可 能 会 丢 失 格 式 信 息 , 比 如 一 个 字 符是 否 上 标 等 。属 性信 息 丢 失IsDecoCanon ?无IsDecoCompat ? 有 一 些 ( 下 列 之 一 )IsDCcircle ?字 符 周 围 的 圆IsDCfinal ? 最 终 的 位 置 ( 阿 拉 伯 文 )IsDCfont ?IsDCfraction ?字 体 变 体 的 选 择俚 语 字 符 片 段IsDCinitial ? 起 始 位 置 选 择 ( 阿 拉 伯 文 )IsDCisolated ? 隔 离 位 置 选 择 ( 阿 拉 伯 文 )IsDCmedial ? 中 间 位 置 选 择 ( 阿 拉 伯 文 )IsDCnarrow ?窄 字 符IsDCnoBreadk ? 空 白 或 连 字 符 的 非 中 断 选IsDCsmall ?IsDCsquare ?IsDCsub ?IsDCsuper ?小 字 符CJK 字 符 周 围 的 方 块脚 标上 标IsDCvertical ? 旋 转 ( 水 平 或 垂 直 )IsDCwide ?宽 字 符177


IsDCcompat ? 标 识 ( 杂 项 )下 面 是 那 些 对 双 向 书 写 的 人 感 兴 趣 的 属 性 :属 性含 义IsBidiL ? 从 左 向 右 ( 阿 拉 伯 语 , 希 伯 来 语 )IsBidiLRE ?IsBidiLRO ?IsBidiR ?IsBidiAL ?IsBidiRLE ?IsBidiRLO ?IsBidiPDF ?IsBidiEN ?IsBidlES ?IsBidkET ?IsBidiAN ?IsBidiCS ?从 左 向 右 插 入从 左 向 右 覆 盖从 右 向 左阿 拉 伯 语 从 右 向 左从 右 向 左 插 入从 右 向 左 覆 盖流 行 方 向 的 格 式欧 洲 数 字欧 洲 数 字 分 隔 符欧 洲 数 字 结 束 符阿 拉 伯 数 字普 通 数 字 分 隔 符IsBidiNSM ? 非 空 白 标 记IsBidiBN ?IsBidiB ?IsBidiS ?IsBidiWS ?IsBidiON ?与 边 界 无 色段 落 分 隔 符段 分 隔 符空 白其 他 无 色 字 符IsMirrored ? 当 使 用 从 右 向 左 模 式 时 反 向下 面 的 属 性 根 据 元 音 的 发 音 把 它 们 分 类 为 各 种 音 节 :IsSylA IsSylE IsSylO IsSylWAA IsSylWIIIsSylAA IsSylEE IsSylOO IsSylWC IsSylWOIsSylAAI IsSylI IsSylU IsSylWE IsSylWOOIsSylAI IsSylII IsSylV IsSylWEE IsSylWUIsSylC IsSylN IsSylWA IsSylWI IsSylWV178


比 如 ,\p{IsSyLA} 将 匹 配 \N{KATAKANA LETTER KA}, 但 不 匹 配 \N{KATAKANALETTER KU}。既 然 你 现 在 基 本 上 已 经 知 道 了 所 有 的 这 些 Unicode 3.0 属 性 , 那 我 们 还 要 说 的 是 在 版 本5.6 的 <strong>Perl</strong> 里 有 几 个 比 较 秘 传 的 属 性 还 没 有 实 现 , 因 为 它 们 的 实 现 有 一 部 分 是 基 于Unicode 2.0 的 , 而 且 , 象 那 些 双 向 的 算 法 还 在 我 们 的 制 作 之 中 。 不 过 , 等 到 你 读 到 这 些的 时 候 , 那 些 缺 失 的 属 性 可 能 早 就 实 现 了 , 所 以 我 们 还 是 把 它 们 列 出 来 了 。第 六 章 子 过 程象 其 他 的 语 言 一 样 ,<strong>Perl</strong> 也 支 持 自 定 义 的 子 过 程 .( 注 : 我 们 也 把 它 们 叫 做 函 数 , 不 过 函数 和 子 过 程 在 <strong>Perl</strong> 里 是 一 样 的 东 西 . 有 时 候 我 们 甚 至 叫 它 们 方 法 , 方 法 和 函 数 或 子 过 程 是同 样 的 方 式 定 义 的 , 只 是 调 用 方 式 不 同 .) 这 些 子 过 程 可 以 在 主 程 序 中 的 任 何 地 方 定 义 , 也可 以 用 do,require 或 use 关 键 字 从 其 他 文 件 中 加 载 . 或 者 直 接 使 用 eval 在 运 行 的 时候 产 生 . 你 甚 至 可 以 使 用 第 十 章 " 包 " 中 " 自 动 装 载 " 一 节 描 述 的 机 制 在 运 行 时 加 载 它 们 . 你 可以 间 接 调 用 子 过 程 , 使 用 一 个 包 含 该 子 过 程 名 字 或 包 含 指 向 该 子 过 程 引 用 的 变 量 来 调 用 , 或者 通 过 对 象 , 让 对 象 决 定 调 用 哪 个 子 过 程 . 你 可 以 产 生 只 能 通 过 引 用 使 用 的 匿 名 子 过 程 , 如果 必 要 , 你 还 可 以 通 过 闭 合 , 用 匿 名 子 过 程 克 隆 几 乎 相 同 的 函 数 . 我 们 将 在 第 八 章 " 引 用 "中 的 相 关 小 节 中 讲 述 .1.0 语 法声 明 一 个 命 名 子 过 程 , 但 不 定 义 它 , 使 用 下 面 的 形 式 :sub NAMEsub NAME PROTOsub NAMEATTRSsub NAME PROTO ATTRS声 明 并 且 定 义 一 个 命 名 子 过 程 , 加 上 一 个 BLOCK:sub NAMEBLOCK179


sub NAME PROTOsub NAMEBLOCKATTRS BLOCKsub NAME PROTO ATTRS BLOCK创 建 一 个 匿 名 子 过 程 或 子 句 , 把 NAME 去 掉 就 可 以 :subBLOCKsub PROTO BLOCKsubsubATTRS BLOCKPROTO ATTRS BLOCKPROTO 和 ATTRS 表 示 原 型 和 属 性 , 分 别 将 在 本 章 下 面 的 章 节 中 讨 论 . 相 对 于 NAME 和BLOCK 它 们 并 不 很 重 要 .NAME 和 BLOCK 是 基 本 部 分 , 甚 至 有 时 候 它 们 也 可 以 省 略 .对 于 没 有 NAME 的 形 式 , 你 还 必 须 提 供 调 用 子 过 程 的 方 法 . 因 此 你 必 须 保 存 返 回 值 , 因 为这 种 形 式 的 sub 声 明 方 法 不 但 在 编 译 的 时 候 编 译 , 同 时 也 产 生 一 个 运 行 时 的 返 回 值 , 所 以我 们 就 可 以 保 证 保 存 它 :$subref = sub BLOCK;可 以 用 下 面 的 方 法 引 入 在 另 一 个 模 块 中 定 义 的 子 过 程 :use MODULE qw(NAME1 NAME2 NAME2...)直 接 调 用 子 过 程 可 以 用 下 面 的 方 法 :NAME(LIST)NAME LIST&NAME# 有 圆 括 弧 时 & 是 可 选 的# 如 果 预 声 明 / 输 入 了 子 过 程 , 那 么 圆 括 弧 是 选 的# 把 当 前 的 @_ 输 出 到 该 子 过 程#( 并 且 绕 开 原 型 ).间 接 调 用 子 过 程 ( 通 过 名 字 或 引 用 ), 可 以 使 用 下 面 的 任 何 一 种 方 法 :180


1. &$subref(LIST) # 在 间 接 调 用 的 时 候 ,& 不 能忽 略2. $subref->(LIST) # ( 除 非 使 用 中 缀 表 示 法 )3. &$subref # 把 当 前 的 @_ 输 出 到 该 子 过 程正 式 情 况 下 , 一 个 子 过 程 的 名 字 包 括 & 前 缀 , 一 个 子 过 程 可 以 使 用 & 前 缀 调 用 , 但 通 常情 况 下 & 是 可 选 的 , 如 果 预 先 定 义 了 子 过 程 , 那 么 圆 括 弧 也 是 可 选 的 . 但 是 , 在 只 使 用 子过 程 名 字 的 时 候 ,& 不 能 省 略 , 例 如 当 子 过 程 名 字 被 用 做 一 个 参 数 来 判 断 是 否 它 已 经 定 义过 的 时 候 , 或 者 当 你 使 用 $subref = \&name 来 获 取 一 个 命 名 子 过 程 的 引 用 的 时 候 . 同样 , 当 你 使 用 &$subref() 或 &{$subref()} 进 行 一 个 间 接 子 过 程 调 用 的 时 候 也 不 能 省略 &. 不 过 , 如 果 使 用 一 种 更 方 便 的 形 式 $subref->(), 则 不 需 要 &. 参 看 第 八 章 , 那 里有 更 多 有 关 子 过 程 引 用 的 内 容 .<strong>Perl</strong> 并 不 强 制 子 过 程 名 字 使 用 大 写 风 格 . 但 是 按 惯 例 由 perl 的 运 行 时 系 统 间 接 调 用 的 函数 都 是 大 写 的 (BEGIN, CHECK, INIT, END, AUTOLOAD, DESTORY, 和 所 有第 十 四 章 " 捆 绑 变 量 " 涉 及 到 的 函 数 ). 因 此 你 应 该 避 免 使 用 这 种 大 写 风 格 .( 但 是 操 作 常 量值 的 子 过 程 通 常 也 写 成 大 写 的 ).2.0 语 意在 你 记 住 所 有 语 法 前 , 你 只 需 要 记 住 下 边 这 种 定 义 子 过 程 的 普 通 方 法 :sub razzle {print "Ok, you've been razzled.\n";}和 调 用 子 过 程 的 正 常 方 法 就 是 :razzle();在 上 边 的 写 法 中 , 我 们 省 略 了 输 入 ( 参 数 ) 和 输 出 ( 返 回 值 ). 但 是 <strong>Perl</strong> 向 子 过 程 中 传 入 数 据和 子 过 程 传 出 数 据 的 方 法 非 常 简 单 : 所 有 传 入 的 参 数 被 当 成 单 个 平 面 标 量 列 表 , 类 似 的 多 个返 回 值 也 被 当 成 单 个 平 面 标 量 列 表 返 回 给 调 用 者 . 当 使 用 任 意 LIST 时 也 一 样 , 任 何 传 入的 数 组 或 散 列 的 值 都 代 换 到 一 个 平 面 的 列 表 里 面 , 同 时 也 失 去 了 它 们 的 标 识 , 不 过 有 几 种 方法 可 以 绕 开 这 个 问 题 , 这 种 自 动 的 列 表 代 换 在 很 多 场 合 非 常 有 用 . 参 数 列 表 和 返 回 值 列 表 都可 以 根 据 你 的 需 要 包 含 任 意 多 个 标 量 成 员 ( 当 然 你 可 以 使 用 原 型 定 义 来 约 束 参 数 的 类 型 ). 实际 上 ,<strong>Perl</strong> 是 按 照 支 持 可 变 参 函 数 ( 可 以 支 持 任 何 数 量 的 参 数 ) 概 念 来 设 计 的 .C 则 不 同 ,虽 然 C 也 勉 强 支 持 一 些 变 参 的 函 数 , 例 如 printf (3).181


现 在 , 如 果 你 将 设 计 一 种 可 以 支 持 不 定 数 量 的 任 意 参 数 的 语 言 , 你 最 好 让 你 的 语 言 在 处 理 这些 任 意 长 的 参 数 列 表 上 容 易 些 . 所 有 传 入 <strong>Perl</strong> 过 程 的 参 数 都 是 以 @_ 身 份 传 入 的 . 如 果你 调 用 一 个 有 两 个 参 数 的 函 数 , 它 们 在 函 数 内 部 可 以 作 为 @_ 数 组 的 前 两 个 成 员 访 问 :$_[0] 和 $_[1]. 因 为 @_ 只 是 一 个 有 着 奇 怪 名 字 的 普 通 数 组 , 所 以 你 可 以 象 处 理 普 通数 组 一 样 随 意 处 理 它 .( 注 : 这 个 领 域 是 <strong>Perl</strong> 和 传 统 的 编 程 语 言 冲 突 得 最 厉 害 的 地 方 .)数 组 @_ 是 一 个 本 地 数 组 , 但 是 它 的 值 是 实 际 标 量 参 数 的 别 名 ( 通 常 称 为 引 用 传 参 ) 因 而 如果 修 改 了 @_ 中 的 成 员 那 么 同 时 也 修 改 了 对 应 的 实 际 参 数 的 值 .( 通 常 的 语 言 中 很 少 这 么做 , 但 是 采 用 这 种 方 法 在 <strong>Perl</strong> 中 可 以 很 容 易 的 返 回 所 需 要 的 值 ).子 过 程 ( 其 他 的 程 序 块 也 一 样 ) 的 返 回 值 是 过 程 最 后 一 个 表 达 式 的 值 . 或 者 你 可 以 在 子 过 程 的任 何 一 个 地 方 明 确 使 用 一 个 return 语 句 来 返 回 值 并 且 退 出 子 过 程 . 不 管 是 那 种 方 法 , 当在 一 个 标 量 或 列 表 环 境 中 调 用 子 过 程 时 , 最 后 一 个 表 达 也 将 在 同 样 的 标 量 或 列 表 环 境 中 求值 .2.1 参 数 列 表 的 技 巧<strong>Perl</strong> 没 有 命 名 的 正 式 参 数 , 但 是 在 实 际 中 你 可 以 将 @_ 的 值 拷 贝 到 一 个 my 列 表 , 这 样就 可 以 方 便 使 用 这 些 正 式 参 数 ( 不 一 样 的 是 , 这 样 拷 贝 就 将 引 用 传 参 的 语 义 变 为 了 传 值 传 参 ,也 许 传 值 传 参 正 是 很 多 用 户 通 常 希 望 参 数 被 处 理 的 方 法 , 即 使 他 们 不 知 道 这 些 计 算 机 术 语 ),下 面 是 一 个 典 型 的 例 子 :sub aysetenv {my ($key, $value) = @_;$ENV{$key} = $value unless $ENV{$key};}但 是 没 人 要 你 一 定 要 给 你 的 参 数 命 名 , 这 就 是 @_ 数 组 的 全 部 观 点 . 例 如 , 计 算 一 个 最 大值 , 你 可 以 简 单 直 接 遍 历 @_ 数 组 :sub max {$max = shift(@_);for my $item (@_) {$max = $item if $max < $item;}182


eturn $max;}$bestday = max($mon, $tue, $wed, $thu, $fri);或 者 你 可 以 一 次 将 @_ 填 入 一 个 散 列 :sub configuration {my %options = @_;print "Maximum verbosity.\n" if $options{VERBOSE} == 9;}configuration(PASSWORD => 'xyzzy', VERBOSE => 9, SOCRE => 0);下 面 是 一 个 例 子 , 这 里 不 命 名 正 式 参 数 , 这 样 你 可 以 修 改 实 际 参 数 的 值 :upcase_in($v1, $v2); # 这 里 改 变 $v1 和 $v2sub upcase_in {for (@_) { tr/a-z/A-Z/ }}但 是 你 不 允 许 用 这 种 方 法 修 改 常 量 , 如 果 一 个 参 数 是 一 个 象 "hobbit" 这 样 的 实 际 标 量 值或 象 $1 这 样 只 读 标 量 , 当 你 试 图 修 改 它 时 ,<strong>Perl</strong> 会 抛 出 一 个 例 外 ( 可 能 的 致 命 错 误 或 者可 能 的 威 胁 ). 例 如 , 下 面 的 例 子 将 不 能 工 作 :upcase_in("fredrick");如 果 将 upcase_in 函 数 写 成 返 回 它 的 参 数 的 一 个 拷 贝 会 比 直 接 改 变 参 数 安 全 得 多 :($v3, $v4) = upcase($v1, $v2);sub upcase {my @parms = @_;for (@parms) { tr/a-z/A-Z/ }183


# 检 查 我 们 是 否 在 列 表 环 境 中 被 调 用 的return wantarray ? @parms : $parms[0];}注 意 这 个 函 数 ( 没 有 原 型 ) 并 不 在 意 传 进 来 的 参 数 是 真 的 标 量 还 是 数 组 .<strong>Perl</strong> 将 所 有 的 参 数粉 碎 成 一 个 又 大 又 长 的 平 面 @_ 数 组 列 表 . 这 是 <strong>Perl</strong> 简 单 传 参 方 式 闪 光 的 地 方 之 一 . 甚至 我 们 可 以 给 它 象 下 面 这 样 的 参 数 的 时 候 都 不 需 要 修 改 upcase 的 定 义 ,upcase 将 照 样工 作 得 呗 棒 :@newlist = upcase(@list1, @list2);@newlist = upcase( split /:/, $var);但 是 , 如 果 象 下 边 这 样 用 , 就 不 会 得 到 你 想 要 的 的 结 果 :(@a, @b) = upcase( @list1, @list3);# 错因 为 , 和 @_ 一 样 , 返 回 列 表 同 样 是 一 个 平 面 列 表 . 因 此 所 有 的 返 回 值 将 存 储 在 @a 中 ,@b 是 空 的 . 可 以 在 下 面 " 传 递 引 用 " 部 分 看 到 替 代 的 办 法 .2.2 错 误 指 示如 果 你 希 望 你 的 函 数 能 够 以 特 定 的 方 式 返 回 , 使 调 用 者 能 够 得 知 发 生 了 一 个 错 误 . 在 <strong>Perl</strong>中 实 现 这 个 目 的 最 自 然 的 一 种 方 法 就 是 用 一 个 不 带 参 数 的 return 语 句 . 这 样 当 函 数 在 标量 环 境 中 使 用 时 , 调 用 者 得 到 一 个 undef, 如 果 在 列 表 环 境 中 使 用 , 调 用 者 得 到 一 个 空 列表 .特 殊 情 况 下 , 你 可 以 选 者 产 生 一 个 例 外 来 指 示 错 误 , 但 是 必 须 谨 慎 使 用 这 种 方 法 . 因 为 你 的程 序 将 被 例 外 处 理 程 序 终 结 . 例 如 , 在 一 个 文 件 操 作 函 数 中 , 打 开 文 件 失 败 几 乎 不 是 例 外 的事 件 . 因 此 , 最 好 能 够 忽 略 这 种 失 败 . 当 你 在 无 效 的 环 境 下 调 用 函 数 时 ,wantarray 内 建函 数 将 返 回 undef. 因 此 如 果 你 想 忽 略 它 , 你 可 以 使 用 下 面 的 方 法 :if($something_went_awry) {return if defined wantarray;# 很 好 , 不 是 空 环 境die "Pay attention to my error, you danglesocket!!!\n";}184


2.3 范 围 问 题因 为 每 次 调 用 都 有 自 己 的 参 数 数 组 , 因 此 子 过 程 可 以 递 归 调 用 , 甚 至 可 以 调 用 它 自 己 . 如 果使 用 & 的 形 式 调 用 子 过 程 , 那 么 参 数 列 表 是 可 选 的 . 如 果 使 用 了 & 并 且 省 略 了 参 数 列 表 ,那 么 有 一 些 特 殊 的 规 则 : 调 用 过 程 中 的 @_ 数 组 将 做 为 被 调 用 子 过 程 的 参 数 . 新 用 户 可 能不 想 使 用 这 种 有 效 的 机 制 .&foo(1,2,3)foo(1,2,3)# 传 递 三 个 参 数# 和 上 面 一 样foo();&foo();# 传 递 一 个 空 列 表# 和 上 面 一 样&foo; # foo() 获 取 当 前 的 参 数 , 和 foo(@_) 一 样 , 但 更 快 !foo;# 如 果 预 定 义 了 子 过 程 foo, 那 么 和 foo() 一 样 , 否 则# 就 是 光 字 "foo"使 用 & 形 式 调 用 子 过 程 不 仅 可 以 省 略 掉 参 数 列 表 , 同 时 对 你 提 供 的 参 数 也 不 进 行 任 何 原 型检 查 . 这 种 做 法 一 部 分 是 因 为 历 史 原 因 形 成 , 另 一 部 分 原 因 是 为 了 在 用 户 清 楚 自 己 在 干 什 么的 情 况 下 提 供 一 个 方 便 的 办 法 . 你 可 以 参 看 本 章 后 面 的 " 原 型 " 小 节 .在 函 数 中 访 问 一 个 并 没 有 定 义 成 该 函 数 私 有 的 变 量 不 一 定 是 全 局 变 量 ; 它 们 遵 循 第 二 章 "集 腋 成 裘 " 中 " 名 字 " 一 节 中 提 到 的 块 作 用 范 围 规 则 , 这 意 味 着 他 们 首 先 在 词 法 作 用 范 围 里 面决 定 该 变 量 , 然 后 才 扩 展 到 单 个 包 作 用 范 围 . 从 子 过 程 的 角 度 看 来 , 任 何 在 一 个 闭 合 的 词 法作 用 域 中 的 my 变 量 仍 然 优 先 使 用 .例 如 , 下 面 例 子 中 的 bumpx 函 数 使 用 了 文 件 作 用 范 围 中 的 $x 变 量 , 这 是 因 为 my 变量 被 定 义 的 作 用 范 围 --- 也 就 是 文 件 本 身 --- 并 没 有 在 定 义 子 过 程 之 前 结 束 .# 文 件 顶 部my $x = 10;sub bumpx { $x++ }# 声 明 和 初 始 化 变 量# 函 数 可 以 看 到 外 层 词 法 变 量185


C 和 C++ 程 序 员 很 可 能 认 为 $x 是 一 个 " 文 件 静 态 " 变 量 . 它 对 其 他 文 件 中 的 函 数 是 私 有的 , 但 是 在 上 例 中 在 my 后 面 定 义 的 函 数 可 以 透 视 到 这 个 变 量 . 那 些 由 C 程 序 员 转 变 而来 的 <strong>Perl</strong> 程 序 员 在 <strong>Perl</strong> 中 找 不 到 他 们 熟 悉 的 文 件 或 函 数 的 " 静 态 变 量 ".<strong>Perl</strong> 程 序 员 避免 使 用 "static" 这 个 词 . 因 为 静 态 系 统 过 时 而 且 乏 味 , 并 且 因 为 在 历 史 使 用 中 这 个 词 被 搞得 一 团 糟 .虽 然 <strong>Perl</strong> 语 法 中 没 有 包 括 "static" 这 个 词 , 但 是 <strong>Perl</strong> 程 序 员 同 样 能 够 创 建 函 数 的 私 有 变量 , 并 且 保 持 跨 函 数 访 问 .<strong>Perl</strong> 中 没 有 专 门 的 词 来 表 述 他 们 . 利 用 <strong>Perl</strong> 丰 富 的 作 用 范 围规 则 结 合 自 动 内 存 管 理 , 就 可 以 有 很 多 方 式 实 现 "static" 关 键 字 的 功 能 .词 法 变 量 并 不 会 只 是 因 为 退 出 了 它 们 的 作 用 范 围 后 就 被 自 动 内 存 垃 圾 收 集 回 收 , 它 们 要 等 到不 再 使 用 后 才 被 回 收 , 这 个 概 念 十 分 重 要 . 为 了 创 建 一 个 在 跨 函 数 调 用 中 不 被 重 置 的 私 有 变量 , 你 可 以 将 整 个 函 数 用 花 括 弧 括 起 来 , 并 将 my 定 义 和 函 数 定 义 放 入 该 函 数 块 中 . 你 甚至 可 以 放 入 多 个 函 数 定 义 , 这 样 该 私 有 变 量 就 可 以 被 这 些 函 数 共 享 访 问 .{my $counter = 0;sub next_counter { return ++$counter }sub prev_counter { return --$counter }}通 常 , 对 词 法 变 量 的 访 问 被 限 制 在 同 一 个 词 法 作 用 域 中 . 两 个 函 数 的 名 字 可 以 被 全 局 访 问( 在 同 一 个 包 内 ), 并 且 因 为 它 们 是 在 $counter 的 作 用 域 中 定 义 的 , 它 们 仍 然 可 以 访 问 该变 量 , 即 使 其 他 函 数 访 问 不 到 也 无 妨 .如 果 这 个 函 数 是 通 过 require 或 use 加 载 的 , 那 么 也 可 以 . 如 果 它 全 在 主 程 序 中 , 那 么你 就 要 确 保 使 任 何 运 行 时 的 my 赋 值 要 足 够 地 早 , 你 可 以 将 整 个 程 序 块 放 在 主 程 序 的 最 前边 , 也 可 以 使 用 BEGIN 或 INIT 程 序 块 来 保 证 它 在 你 的 程 序 之 前 运 行 :BEGIN {my @scale = ('A' .. 'G');my $note = -1;sub next_pitch { return $scale[ ($note += 1) %= @scale ] );}186


BEGIN 既 不 会 影 响 子 过 程 的 定 义 , 也 不 会 影 响 子 过 程 里 使 用 的 任 意 词 法 的 一 致 性 . 这 里 它仅 仅 保 证 在 子 程 序 被 调 用 之 前 变 量 就 被 初 始 化 . 想 了 解 定 义 私 有 变 量 和 全 局 变 量 更 多 的 内容 , 请 分 别 参 考 29 章 " 函 数 " 的 my 和 our 的 说 明 ,BEGIN 和 INIT 在 第 十 八 章 " 编 译 "中 解 释 .3.0 传 入 引 用如 果 你 想 在 一 个 函 数 中 传 入 或 传 出 不 止 一 个 的 数 组 或 散 列 结 构 , 同 时 你 希 望 它 们 保 持 它 们 的一 致 性 , 那 么 你 就 需 要 使 用 一 个 更 明 确 的 传 递 引 用 的 机 制 . 在 你 使 用 传 递 引 用 之 前 , 你 需 要懂 得 第 八 章 里 有 关 引 用 的 细 节 . 本 小 节 不 着 重 讲 述 引 用 的 内 容 .这 里 有 几 个 简 单 的 例 子 , 首 先 , 让 我 们 定 义 一 个 函 数 , 这 个 函 数 使 用 数 组 的 引 用 作 为 参 数 . 当这 个 数 组 非 常 大 时 , 作 为 一 个 引 用 传 递 要 比 传 入 一 长 列 值 要 快 得 多 :$total = sum (\@a );sub sum {my ($aref) = @_;my ($total) = 0;foreach (@$aref) { $total += $_ }return $total;}下 面 让 我 们 将 几 个 数 组 传 入 一 个 函 数 , 并 且 使 用 使 用 pop 得 到 每 个 数 组 的 最 后 一 个 元 素 ,并 返 回 每 个 数 组 最 后 一 个 元 素 组 成 的 一 个 新 的 数 组 :@tailings = popmany (\@a, \@b, \@c, \@d );sub popmany {my @retlist = ();for my $aref (@_) {push @retlist, pop @$aref;187


}return @retlist;}下 面 是 一 个 函 数 , 能 够 返 回 一 个 列 表 , 这 个 列 表 包 含 在 每 个 传 入 的 散 列 结 构 中 都 出 现 的 键 字 .@common = inter (\%foo, \%bar, \%joe );sub inter {my %seen;for my $href (@_) {while (my $k = each %$href ) {$seen{$k}++;}}return grep { $seen{$_} == @_ } keys %seen;}这 里 我 们 只 用 了 普 通 的 列 表 返 回 机 制 . 当 你 想 传 送 或 返 回 一 个 散 列 结 构 时 会 发 生 什 么 ? 如 果你 仅 用 其 中 的 一 个 , 或 者 你 不 在 意 它 们 连 在 一 起 , 那 么 使 用 普 通 的 调 用 方 法 就 行 了 , 如 果 不是 , 那 么 就 会 稍 微 复 杂 一 些 .我 们 已 经 在 前 面 提 到 过 , 人 们 常 会 在 下 面 的 写 法 中 遇 到 麻 烦 :(@a, @b) = func(@c, @d);或 这 里 :(%a, %b) = func(%c, %d);这 些 表 达 式 将 不 会 正 确 工 作 , 它 只 会 设 置 @a 或 %a, 而 @b 或 %b 则 是 空 的 . 另 外 函 数不 会 得 到 两 个 分 离 的 数 组 和 散 列 结 构 作 为 参 数 : 它 和 往 常 一 样 从 @_ 中 得 到 一 个 长 列 表 .你 也 许 想 在 函 数 的 输 入 和 输 出 中 都 使 用 引 用 . 下 面 是 一 个 使 用 两 个 数 组 引 用 作 为 参 数 的 函数 , 并 且 根 据 数 组 中 包 含 元 数 的 多 少 为 顺 序 返 回 两 个 数 组 的 引 用 :188


($aref, $bref) = func(\@c, \@d);print "@$aref has more than @$bref\n";sub func {my ($cref, $dref) = @_;if (@$cref > @$dref) {return ($cref, $dref);} else {return ($dref, $cref);}}如 何 向 函 数 传 入 或 传 出 文 件 句 柄 或 目 录 句 柄 , 请 参 阅 第 八 章 的 " 文 件 句 柄 引 用 " 和 " 符 号 表 句柄 " 小 节 .4.0 函 数 原 型<strong>Perl</strong> 可 以 让 你 定 义 你 自 己 的 函 数 , 这 些 函 数 可 以 象 <strong>Perl</strong> 的 内 建 函 数 一 样 调 用 . 例 如push(@array, $item), 它 必 须 接 收 一 个 @array 的 引 用 , 而 不 仅 仅 是 @array 中 的值 , 这 样 这 个 数 组 才 能 够 被 函 数 改 变 . 函 数 原 型 能 够 让 你 声 明 的 子 过 程 能 够 象 很 多 内 建 函 数一 样 获 得 参 数 , 就 是 获 得 一 定 数 目 和 类 型 的 参 数 . 我 们 虽 然 称 之 为 函 数 原 型 , 但 是 它 们 的 运转 更 像 调 用 环 境 中 的 自 动 模 板 , 而 不 仅 仅 是 C 或 java 程 序 员 认 为 的 函 数 原 型 . 使 用 这 些模 板 ,<strong>Perl</strong> 能 够 自 动 添 加 隐 含 的 反 斜 扛 或 者 调 用 scalar, 或 能 够 使 事 情 能 变 成 符 合 模 板 的其 他 一 些 操 作 . 比 如 , 如 果 你 定 义 :sub mypush (\@@);那 么 mypush 就 会 象 push 一 样 接 受 参 数 . 为 了 使 其 运 转 , 函 数 的 定 义 和 调 用 在 编 译 的时 候 必 须 是 可 见 的 . 函 数 原 型 只 影 响 那 些 不 带 & 方 式 调 用 的 函 数 . 换 句 话 说 , 如 果 你 象 内建 函 数 一 样 调 用 它 , 它 就 像 内 建 函 数 一 样 工 作 . 如 果 你 使 用 老 式 的 方 法 调 用 子 过 程 , 那 么 它就 象 老 式 子 过 程 那 样 工 作 . 调 用 中 的 & 消 除 所 有 的 原 型 检 查 和 相 关 的 环 境 影 响 .因 为 函 数 原 型 仅 仅 在 编 译 的 时 候 起 作 用 , 自 然 它 对 象 \&foo 这 样 的 子 过 程 引 用 和 象&{$subref} 和 $subref->() 这 样 的 间 接 子 过 程 调 用 的 情 况 不 起 作 用 . 同 样 函 数 原 型 在189


方 法 调 用 中 也 不 起 作 用 . 这 是 因 为 被 调 用 的 实 际 函 数 不 是 在 编 译 的 时 候 决 定 的 , 而 是 依 赖 于它 的 继 承 , 而 继 承 在 <strong>Perl</strong> 中 是 动 态 判 断 的 .因 为 本 节 的 重 点 主 要 是 让 你 学 会 定 义 象 内 建 函 数 一 样 工 作 的 子 过 程 , 下 面 使 一 些 函 数 原 型 ,你 可 以 用 来 模 仿 对 应 的 内 建 函 数 :声 明 为sub mylink ($$)调 用mylink $old, $newsub myreverse (@) myreverse $a, $b, $csub myjoin ($@) myjoin ":", $a, $b, $csub mypop (\@)sub mysplice(\@$$@)sub mykeys (\%)sub mypipe (**)sub myindex ($$;$)mypop @arraymysplice @array, @array, 0, @pushmemykeys %($hashref)mypipe READHANDLE, WRITEHANDLEmyindex &getstring, "substr"myindex &getstring, "substr", $startsub mysyswrite (*$;$$) mysyswrite OUTF, $bufmysyswrite OUTF, $buf, length($buf)-$off, $offsub myopen (*;$@)myopen HANDLEmyopen HANDLE, $namemyopen HANDLE, "-|", @cmdsub mygrep (&@) mygrep { /foo/ } $a, $b, $csub myrand ($) myrand 42sub mytime ()mytime任 何 带 有 反 斜 扛 的 原 型 字 符 ( 在 上 表 左 列 中 的 圆 括 弧 里 ) 代 表 一 个 实 际 的 参 数 ( 右 列 中 有 示 例 )必 须 以 以 这 个 字 符 开 头 . 例 如 keys 函 数 的 第 一 个 参 数 必 须 以 % 开 始 , 同 样 mykeys 的第 一 个 参 数 也 必 须 以 % 开 头 .分 号 将 命 令 性 参 数 和 可 选 参 数 分 开 .( 在 @ 或 % 前 是 多 余 的 , 因 为 列 表 本 身 就 可 以 是 空的 ) . 非 反 斜 扛 函 数 原 型 字 符 有 特 殊 的 含 义 . 任 何 不 带 反 斜 扛 的 @ 或 % 会 将 实 际 参 数 所有 剩 下 的 参 数 都 吃 光 并 强 制 进 入 列 表 环 境 .( 等 同 于 语 法 描 述 中 的 LIST).$ 代 表 的 参 数 强迫 进 入 标 量 环 境 .& 要 求 一 个 命 名 或 匿 名 子 过 程 的 引 用 .函 数 原 型 中 的 * 允 许 子 过 程 在 该 位 置 接 受 任 何 参 数 , 就 像 内 建 的 文 件 句 柄 那 样 : 可 以 是 一个 名 字 , 一 个 常 量 , 标 量 表 达 式 , 类 型 团 或 者 类 型 团 的 引 用 . 值 将 可 以 当 成 一 个 简 单 的 标 量190


或 者 类 型 团 ( 用 小 写 字 母 的 ) 的 引 用 由 子 过 程 使 用 . 如 果 你 总 是 希 望 这 样 的 参 数 转 换 成 一 个类 型 团 的 引 用 , 可 以 使 用 Symbol::qualify_to_ref, 象 下 面 这 样 :use Symblo 'qualify_to_ref';sub foo (*) {my $fh = qualify_to_ref(shift, caller);...}注 意 上 面 表 中 的 最 后 三 个 例 子 会 被 分 析 器 特 殊 对 待 ,mygrep 被 分 析 成 一 个 真 的 列 表 操 作符 ,myrand 被 分 析 成 一 个 真 的 单 目 操 作 符 就 象 rand 一 样 , 同 样 mytime 被 分 析 成 没有 参 数 , 就 象 time 一 样 .也 就 是 说 , 如 果 你 使 用 下 面 的 表 达 式 :mytime +2;你 将 会 得 到 mytime()+2, 而 不 是 mytime(2), 这 就 是 在 没 有 函 数 原 型 时 和 使 用 了 单 目函 数 原 型 时 分 析 的 得 到 的 不 同 结 果 .mygrep 例 子 同 样 显 示 了 当 & 是 第 一 个 参 数 的 时 候 是 如 果 处 理 的 . 通 常 一 个 & 函 数 原 型要 求 一 个 象 \&foo 或 sub{} 这 样 参 数 . 当 它 是 第 一 个 参 数 时 , 你 可 以 在 你 的 匿 名 子 过 程中 省 略 掉 sub, 只 在 " 非 直 接 对 象 " 的 位 置 上 传 送 一 个 简 单 的 程 序 块 ( 不 带 冒 号 ). 所 以 & 函数 原 型 的 一 个 重 要 功 能 就 是 你 可 以 用 它 生 成 一 个 新 语 法 , 只 要 & 是 在 初 始 位 置 :sub try (&$) {my ($try, $catch) = @_;eval { &$try };if ($@) {local $_ = $@;&$catch;191


}}sub catch (&) { $_[0] }try {die "phooey";} # 不 是 函 数 调 用 的 结 尾 !catch {/phooey/ and print "unphooey\n";};它 打 印 出 "unphooey". 这 里 发 生 的 事 情 是 这 样 的 ,<strong>Perl</strong> 带 两 个 参 数 调 用 了 try, 匿 名 函数 {die "phooey";} 和 catch 函 数 的 返 回 值 , 在 本 例 中 这 个 返 回 值 什 么 都 不 是 , 只 不 过是 它 自 己 的 参 数 , 而 整 个 块 则 是 另 外 一 个 匿 名 函 数 . 在 try 里 , 第 一 个 函 数 参 数 是 在 eval里 调 用 的 , 这 样 就 可 以 捕 获 任 何 错 误 . 如 果 真 的 出 了 错 误 , 那 么 调 用 第 二 个 函 数 , 并 且 设 置$_ 变 量 以 抛 出 例 外 .( 注 : 没 错 , 这 里 仍 然 有 涉 及 @_ 的 可 视 性 的 问 题 没 有 解 决 . 目 前我 们 忽 略 那 些 问 题 . 但 是 如 果 我 们 将 来 把 @_ 做 成 词 法 范 围 的 东 西 , 就 象 现 在 试 验 的 线 程化 <strong>Perl</strong> 版 本 里 已 经 做 的 那 样 , 那 么 那 些 匿 名 子 过 程 就 可 以 象 闭 合 的 行 为 一 样 .) 如 果 你 觉得 这 些 东 西 听 起 来 象 胡 说 八 道 , 那 么 你 最 好 看 看 第 二 十 九 章 里 的 die 和 eval, 然 后 回 到第 八 章 里 看 看 匿 名 函 数 和 闭 合 . 另 外 , 如 果 你 觉 得 麻 烦 , 你 还 可 以 看 看 CPAN 上 的 Error模 块 , 这 个 模 块 就 是 实 现 了 一 个 用 try,catch,except,otherwise, 和 finally 子 句 的灵 活 的 结 构 化 例 外 操 作 机 制 .下 面 是 一 个 grep 操 作 符 的 重 新 实 现 ( 当 然 内 建 的 实 现 更 为 有 效 ):sub mygrep (&@) {my $coderef = shift;my @result;foreach $_ (@_) {push(@result, $_) if &$coderef;}192


eturn @result;}一 些 读 者 希 望 能 够 看 到 完 整 的 字 母 数 字 函 数 原 型 . 我 们 有 意 把 字 母 数 字 放 在 了 原 型 之 外 , 为的 是 将 来 我 们 能 够 很 快 地 增 加 命 名 的 , 正 式 的 参 数 .( 可 能 ) 现 在 函 数 原 型 的 主 要 目 的 就 是让 模 块 作 者 能 够 对 模 块 用 户 作 一 些 编 译 时 的 强 制 参 数 检 查 .4.1 内 联 常 量 函 数带 有 () 的 函 数 原 型 表 示 这 个 函 数 没 有 任 何 参 数 , 就 象 内 建 函 数 time 一 样 . 更 有 趣 的 是 ,编 译 器 将 这 种 函 数 当 作 潜 在 的 内 联 函 数 的 候 选 函 数 . 当 <strong>Perl</strong> 优 化 和 常 量 值 替 换 回 合 后 , 得到 结 果 如 果 是 一 个 固 定 值 或 者 是 一 个 没 有 其 他 引 用 的 语 法 作 用 域 标 量 时 , 那 么 这 个 值 就 将 替换 对 这 个 函 数 的 调 用 . 但 是 使 用 &NAME 方 式 调 用 的 函 数 不 被 " 内 联 化 ", 然 而 , 只 是 因 为它 们 不 受 其 他 函 数 原 型 影 响 .( 参 看 第 三 十 一 章 " 用 法 模 块 " 中 的 use constant, 这 是 一 种定 义 这 种 固 定 值 的 更 简 单 的 方 法 ).下 面 的 两 种 计 算 ∏ 的 函 数 写 法 都 会 被 编 译 器 " 内 联 化 ":sub pi () { 3.14159 }sub PI () { 4 * atan2(1, 1) }# 不 准 确 , 但 接 近# 和 它 的 一 样 好实 际 上 , 下 面 所 有 的 函 数 都 能 被 <strong>Perl</strong> " 内 联 化 ", 因 为 <strong>Perl</strong> 能 够 在 编 译 的 时 候 就 能 确 定 所有 的 值 :sub FLAG_FOO () { 1


sub N () { int(GLARCH_VAL) / 3 }BEGIN {# compiler runs this block at compile timemy $prod = 1;# persistent, private variablefor (1 .. N) { $prod *= $_ }sub NFACT () { $prod }}最 后 一 个 例 子 中 ,NFACT 函 数 也 将 内 联 化 , 因 为 它 有 一 个 空 的 函 数 原 型 并 且 函 数 返 回 的 变量 并 没 有 被 函 数 修 改 , 而 且 不 能 被 其 他 东 西 改 变 , 因 为 它 在 一 个 语 法 作 用 范 围 里 面 . 因 此 编译 器 在 编 译 的 时 候 预 先 计 算 它 的 值 , 并 用 这 个 值 替 换 所 有 使 用 NFACT 的 地 方 .如 果 你 重 新 定 义 已 经 被 内 联 化 的 子 过 程 , 那 么 你 会 收 到 一 个 命 令 性 警 告 ( 你 可 以 使 用 这 个 警告 来 确 认 一 个 子 过 程 是 不 是 已 经 被 内 联 化 了 ) 因 为 重 新 定 义 的 子 过 程 会 用 先 前 编 译 产 生 的 值代 替 , 因 此 这 个 警 告 足 够 的 确 定 这 个 子 过 程 是 否 被 内 联 化 . 如 果 你 需 要 重 新 定 义 子 过 程 , 你可 以 通 过 删 除 () 函 数 原 型 ( 这 个 更 改 调 用 方 法 ) 或 者 重 新 修 改 函 数 的 写 法 来 阻 挠 内 联 化 机制 来 避 免 子 过 程 被 内 联 化 . 例 如 :sub not_inlined () {return 23 if $$;}参 看 第 十 八 章 学 习 更 多 有 关 程 序 编 译 和 执 行 阶 段 的 知 识 .4.2 谨 慎 使 用 函 数 原 型最 好 在 新 函 数 中 使 用 函 数 原 型 , 而 不 在 旧 函 数 中 使 用 函 数 原 型 .<strong>Perl</strong> 中 函 数 原 型 是 环 境 模板 , 而 不 象 ANSI C 中 的 函 数 原 型 , 因 此 你 必 须 十 分 注 意 函 数 原 型 是 否 将 你 的 子 过 程 带 入了 一 个 新 的 环 境 . 例 如 , 你 想 写 一 个 只 有 一 个 参 数 的 函 数 , 象 下 面 这 个 函 数 :sub func ($) {my $n = shift;print "you gave my $n\n";194


}这 将 得 到 一 个 单 目 操 作 符 ( 象 rand 内 建 函 数 ) 并 且 改 变 了 编 译 器 确 定 函 数 参 数 的 方 法 . 使用 了 新 的 函 数 原 型 , 该 函 数 就 只 使 用 一 个 标 量 环 境 下 的 参 数 , 而 不 是 在 列 表 环 境 下 的 多 个 参数 . 如 果 你 在 以 数 组 或 者 列 表 表 达 式 中 调 用 这 个 函 数 , 即 使 这 个 数 组 或 列 表 只 包 含 一 个 元 素 ,你 可 能 会 得 到 完 全 不 同 的 结 果 :func @foo;func split /:/;# 计 算 @foo 元 素 个 数# 计 算 返 回 的 域 的 个 数func "a", "b", "c";# 只 传 递 "a", 抛 弃 "b" 和 "c"func("a", "b", "c"); # 马 上 生 成 一 个 编 译 器 错 误 !你 已 经 隐 含 地 在 参 数 列 表 前 面 提 供 了 一 个 scalar, 这 的 确 令 人 有 点 吃 惊 . 如 果 @foo 只 包含 一 个 元 素 , 那 么 传 递 给 函 数 不 是 这 个 元 素 , 而 是 1(@foo 的 元 素 个 数 ). 并 且 在 第 二 个例 中 ,split 在 标 量 环 境 中 被 调 用 , 吞 没 你 的 整 个 @_ 参 数 列 表 . 在 第 三 个 例 子 中 , 因 为func 已 经 用 函 数 原 型 定 义 为 一 个 单 目 操 作 符 , 因 此 只 有 "a" 传 递 给 了 func; 然 后 func返 回 值 被 丢 弃 , 因 为 逗 号 操 作 符 的 存 在 因 此 继 续 处 理 下 两 个 元 素 并 返 回 "c". 最 后 一 个 例 子 ,在 编 译 的 时 候 用 户 将 得 到 一 个 语 法 错 误 .如 果 你 想 写 一 个 新 的 代 码 得 到 一 个 只 使 用 一 个 标 量 参 数 的 单 目 操 作 符 , 而 不 是 任 何 旧 的 标 量表 达 式 , 你 可 以 使 用 下 面 的 函 数 原 型 使 它 使 用 标 量 引 用 :sub func (\$) {my $nref = shift;print "you gave me $$nref\n";}现 在 , 编 译 器 可 以 让 下 面 的 例 子 中 , 参 数 以 $ 开 头 的 通 过 :func @foo; # 编 译 器 错 误 , 看 见 了 @, 但 要 的 是 $func split/:/; # 编 译 器 错 误 , 看 见 了 函 数 , 但 要 的 是 $func $s;func $a[3];# 这 个 是 对 的 -- 获 取 了 真 的 $ 符 号# 这 个 也 对195


func $h{stuff}[-1];# 这 个 也 对func 2+5;# 标 量 表 达 式 也 会 导 致 编 译 器 错 误func ${\(2+5) }; # 对 , 不 过 它 是 不 是 比 病 毒 还 糟 糕 ?如 果 你 不 小 心 , 你 可 能 因 为 使 用 函 数 原 型 遇 到 很 多 麻 烦 . 但 如 果 你 非 常 注 意 , 你 可 以 使 用 函数 原 型 来 作 很 多 漂 亮 的 工 作 . 函 数 原 型 是 非 常 强 大 的 , 当 然 需 要 谨 慎 使 用 才 能 得 到 好 的 结 果 .5.0 子 过 程 属 性子 过 程 的 定 义 和 声 明 能 够 附 带 一 些 属 性 . 如 果 属 性 列 表 存 在 , 它 使 用 空 格 或 者 冒 号 分 割 , 并等 同 于 通 过 use attributes 定 义 的 一 样 . 请 阅 读 三 十 一 章 的 use attributes 获 得 内 部 细节 . 有 三 个 标 准 的 子 过 程 属 性 :locked, method 和 左 值 .5.1 Locked 和 method 属 性# 在 这 个 函 数 里 只 允 许 一 个 线 程sub afunc : locked { ... }# 在 一 个 特 定 的 对 象 上 之 允 许 一 个 线 程 进 入 这 个 函 数sub afunc : locked method { ... }只 有 在 子 过 程 或 者 方 法 要 被 多 个 线 程 调 用 的 时 候 , 设 置 locked 属 性 才 有 意 义 . 当 设 置 一个 不 是 方 法 的 子 过 程 的 时 候 ,<strong>Perl</strong> 确 保 在 进 入 子 过 程 之 前 获 得 一 个 锁 . 当 设 置 一 个 方 法 子过 程 时 ( 具 有 method 属 性 的 子 过 程 ),<strong>Perl</strong> 确 保 在 执 行 之 前 锁 住 它 的 第 一 个 参 数 ( 所 属 的对 象 ).method 属 性 能 够 被 它 自 己 使 用 :sub afunc : method { ... }现 在 它 只 是 用 来 标 记 子 过 程 , 使 之 不 产 生 "Ambiguous call resolved as CORE::%s"警 告 .( 我 们 以 后 可 以 给 它 更 多 的 含 义 ).属 性 系 统 是 用 户 可 扩 展 的 ,<strong>Perl</strong> 可 以 让 你 创 建 自 己 的 属 性 名 . 这 些 新 的 属 性 必 须 是 简 单 的标 记 名 字 ( 除 了 "_" 字 符 之 外 没 有 任 何 标 点 符 号 ). 它 们 后 边 可 以 有 一 个 参 数 列 表 用 来 检 查它 的 花 括 弧 是 否 匹 配 正 确 .196


下 面 是 一 些 正 确 的 语 法 的 例 子 ( 即 使 这 些 属 性 是 未 知 的 ):sub fnord (&\%) : switch(10, foo(7,3)) : expensive;sub plugh () : Ugly('\(") :Bad;sub xyzzy : _5x5 { ... }下 面 是 一 些 不 正 确 语 法 的 例 子 :sub fnord : Switch(10, foo());# ()- 字 串 不 平 衡sub snoid : Ugly ('(');# ()- 字 串 不 平 衡sub xyzzy : 5x5;sub plugh : Y2::north;sub snurt : foo + bar;# "5x5" 不 是 合 法 的 标 识 符# "Y2::north" 不 是 简 单 标 识 符# "+" 不 是 一 个 冒 号 或 空 格属 性 列 表 作 为 一 个 常 量 字 符 串 列 表 传 递 进 子 过 程 相 关 的 代 码 . 它 的 正 确 工 作 方 法 是 高 度 试 验性 的 . 查 阅 attributes(3) 获 得 属 性 列 表 的 详 细 信 息 和 操 作 方 法 .5.3 左 值 属 性除 非 你 定 义 子 过 程 返 回 一 个 左 值 , 否 则 你 你 不 能 从 子 过 程 中 返 回 一 个 可 以 修 改 的 标 量 值 :my $val;sub canmod : 左 值 {$val;}sub nomod {$val;}canmod() = 5; # 给 $val 赋 值 为 5nomod() = 5;# 错 误197


如 果 你 正 传 递 参 数 到 一 个 有 左 值 属 性 的 子 过 程 , 你 一 般 会 使 用 圆 括 弧 来 防 止 歧 义 :canmod $x = 5; # 先 给 $x 赋 值 5!canmod 42 = 5;canmod($x)= 5;canmod(42)= 5;# 无 法 改 变 常 量 , 编 译 时 错 误# 这 个 是 对 的# 这 个 也 对如 果 你 想 使 用 省 略 的 写 法 , 你 可 以 在 子 过 程 只 使 用 一 个 参 数 的 情 况 下 省 略 圆 括 弧 . 使 用 ($)函 数 原 型 定 义 一 个 函 数 可 以 使 该 函 数 被 解 释 为 一 个 具 有 命 名 的 单 目 操 作 符 优 先 级 的 操 作符 . 因 为 命 名 单 目 操 作 符 优 先 级 高 于 赋 值 , 所 以 你 不 再 需 要 圆 括 弧 ( 需 不 需 要 圆 括 弧 只 是 一个 代 码 风 格 的 问 题 ).当 一 个 子 过 程 允 许 空 参 数 时 ( 使 用 () 函 数 原 型 ), 你 可 以 使 用 下 面 的 方 法 而 不 会 引 起 歧 义 :canmod = 5;因 为 没 有 哪 个 合 法 项 以 = 开 头 , 因 此 它 能 正 确 工 作 . 同 样 , 具 有 左 值 属 性 的 方 法 调 用 在 不传 送 任 何 参 数 时 也 能 省 略 圆 括 弧 :$obj->canmod = 5;我 们 保 证 在 未 来 的 <strong>Perl</strong> 版 本 中 不 改 变 上 面 的 两 种 方 法 . 当 你 希 望 在 方 法 调 用 中 封 装 对象 属 性 时 , 它 们 是 非 常 简 便 的 方 法 ( 因 此 它 们 可 以 象 方 法 调 用 一 样 被 继 承 但 又 象 变 量 一 样 访问 ).左 值 子 过 程 和 子 过 程 的 赋 值 表 达 式 右 边 部 分 可 以 通 过 使 用 标 量 替 换 子 过 程 的 方 法 , 来 确 定 是标 量 环 境 还 是 列 表 环 境 . 例 如 :data(2,3) = get_data(3,4);上 边 两 个 子 过 程 都 在 标 量 环 境 中 调 用 , 而 在 :(data(2,3)) = get_data(3,4);和 :198


(dat(3), data(3) = get_data(3,4);中 , 所 有 的 子 过 程 在 列 表 环 境 中 被 调 用 .在 当 前 的 实 现 中 不 允 许 从 左 值 子 过 程 直 接 返 回 数 组 和 散 列 结 构 . 不 过 你 总 是 可 以 返 回 一 个 引用 来 解 决 这 个 问 题 .第 七 章 格 式<strong>Perl</strong> 有 一 个 机 制 帮 助 你 产 生 简 单 的 报 告 和 图 表 . 为 了 实 现 这 个 机 制 ,<strong>Perl</strong> 帮 助 你 格 式化 你 的 输 出 , 使 它 打 印 出 来 的 时 候 看 起 来 比 较 接 近 于 你 想 要 的 结 果 . 它 能 保 持 跟 踪 象 一 页 里面 有 多 少 行 , 当 前 的 页 码 , 以 及 什 么 时 候 打 印 页 头 等 等 的 东 西 . 使 用 的 关 键 字 是 从FORTRAN 里 面 借 来 的 :format 用 来 声 明 而 write 用 来 执 行 ; 参 看 第 二 十 九 章 , 函 数 ,获 取 相 关 内 容 . 所 幸 , 布 局 时 非 常 易 读 的 , 很 象 BASIC 中 的 PRINT USING 语 句 . 也 可以 将 它 想 象 成 nroff( 如 果 你 知 道 nroff, 这 也 许 不 象 是 一 个 比 较 ).格 式 输 出 , 和 包 和 子 过 程 一 样 , 是 声 明 而 不 是 执 行 , 因 此 它 们 可 以 在 你 的 程 序 中 任 何 地 方 出现 .( 通 常 最 好 将 所 有 的 格 式 输 出 放 在 一 起 ). 它 们 有 他 们 自 己 的 名 字 空 间 , 与 <strong>Perl</strong> 中 其 它类 型 的 名 字 空 间 是 区 分 开 来 的 . 这 就 是 说 如 果 你 有 一 个 函 数 "Foo", 但 它 不 同 于 一 个 名 字为 "Foo" 的 格 式 输 出 . 然 而 和 一 个 文 件 句 柄 相 关 联 的 格 式 输 出 的 缺 省 名 字 和 该 文 件 句 柄 的名 字 相 同 . 因 而 ,STDOUT 的 缺 省 格 式 输 出 的 名 字 是 "STDOUT", 文 件 句 柄 TEM P 的缺 省 格 式 输 出 名 字 为 "TEMP", 它 们 看 起 来 是 一 样 的 , 实 际 上 是 不 一 样 的 .输 出 纪 录 格 式 输 出 象 下 边 一 样 定 义 :format NAME =FORMLIST.如 果 省 略 NAME, 将 定 义 格 式 输 出 STDOUT.FORMLIST 由 一 些 有 序 的 行 组 成 , 每 一 行都 是 下 面 三 种 类 型 中 的 一 种 :199


1. 注 释 , 以 第 一 列 为 # 来 表 示 .2. 一 个 格 式 行 , 用 来 定 义 一 个 输 出 行 的 格 式3. 参 数 行 , 用 来 向 前 面 的 格 式 行 中 插 入 值格 式 行 除 了 那 些 需 要 被 替 换 的 部 分 外 , 严 格 按 照 它 们 的 声 明 被 输 出 .( 注 : 而 且 , 甚 至 那 些你 放 进 去 维 护 列 完 整 性 的 域 也 如 此 . 在 一 个 格 式 行 中 没 有 任 何 东 西 可 以 导 致 域 的 伸 缩 或 者 移位 . 你 看 到 的 列 是 按 照 WYSIWYG 的 概 念 分 布 的 --- 假 设 你 用 的 是 定 宽 字 体 . 就 连 控 制 字符 都 假 设 是 宽 度 为 一 的 字 符 .) 格 式 行 中 每 个 被 替 换 的 部 分 分 别 以 @ 或 者 ^ 开 头 . 这 些行 不 作 任 何 形 式 的 变 量 代 换 .@ 域 ( 不 要 同 数 组 符 号 @ 相 混 淆 ) 是 普 通 的 域 . 另 一 种 域 ,^ 域 用 来 进 行 多 行 文 本 块 填 充 . 域 的 长 度 通 过 在 格 式 符 号 @,^ 后 跟 随 特 定 长 度 的 ,| 来 定 义 , 同 时 ,,| 还 分 别 表 示 , 左 对 齐 , 右 对 齐 , 居 中 对 齐 . 如 果 变 量 超 出定 义 的 长 度 , 那 么 它 将 被 截 断 .作 为 右 对 齐 的 另 外 一 种 方 式 , 你 可 以 使 用 #( 在 @ 或 ^ 后 边 ) 来 指 定 一 个 数 字 域 . 你 可 以在 这 种 区 域 中 插 入 一 个 . 来 制 定 小 数 点 的 位 置 . 如 果 这 些 区 域 的 值 包 含 一 个 换 行 符 , 那 么只 输 出 换 行 符 前 面 的 文 本 . 最 后 , 特 殊 的 域 @* 可 以 被 用 来 打 印 多 行 不 截 断 的 值 ; 这 种 区域 通 常 在 一 个 格 式 行 中 出 现 .参 数 行 指 定 参 数 的 顺 序 必 须 跟 相 应 的 格 式 行 的 域 顺 序 一 致 . 不 同 参 数 的 表 达 式 需 要 使 用 逗 号分 隔 . 参 数 行 被 处 理 之 前 所 有 的 参 数 表 达 式 都 在 列 表 环 境 中 求 值 , 因 此 单 个 列 表 表 达 式 会 产生 多 个 列 表 元 素 . 通 过 使 用 圆 括 弧 将 表 达 式 括 起 来 , 可 以 使 表 达 式 扩 展 到 多 行 ( 因 此 , 圆 括弧 必 须 是 第 一 行 的 第 一 个 标 志 ). 这 样 就 可 以 将 值 同 相 应 的 格 式 域 对 应 起 来 方 便 阅 读 .如 果 一 个 表 达 式 求 出 的 值 是 一 个 有 小 数 部 分 的 数 字 , 并 且 如 果 对 应 的 格 式 域 指 定 了 输 出 的 小数 部 分 的 格 式 ( 除 了 没 有 内 嵌 . 的 多 个 # 字 符 的 格 式 ), 用 来 表 示 小 数 点 的 字 符 总 是 由LC_NUMERIC 区 域 参 数 确 定 . 这 就 是 , 如 果 运 行 时 环 境 恰 好 是 德 国 本 地 化 参 数 , 一 个 逗号 总 是 用 来 替 代 句 点 . 参 看 perllocale 手 册 页 获 取 更 多 的 内 容 .在 一 个 表 达 式 中 , 空 白 字 符 \n,\t, 和 \f 总 是 被 解 释 成 单 个 空 格 . 因 而 , 你 可 以 认 为 这样 的 过 滤 表 达 式 作 用 于 每 个 格 式 中 的 表 达 式 :$value =~ tr/\n\t\f/ /;余 下 的 空 白 字 符 ,\r, 如 果 格 式 行 允 许 的 话 , 将 强 制 输 出 一 个 换 行 符 .以 ^ 开 头 的 格 式 域 不 同 于 @ 格 式 域 , 它 会 被 特 殊 对 待 . 例 如 一 个 # 区 域 , 如 果 值 没 有定 义 , 那 么 这 个 区 域 将 变 为 空 白 . 对 于 其 他 的 区 域 类 型 ,^ 会 使 用 一 种 特 殊 的 填 充 模 式 . 提供 的 值 必 须 是 一 个 包 含 字 符 串 的 标 量 变 量 名 , 而 不 是 一 个 强 制 表 达 式 .<strong>Perl</strong> 在 这 个 区 域 中放 入 尽 可 能 多 的 文 本 , 并 且 将 已 经 打 印 过 的 字 符 截 去 , 这 样 当 下 次 引 用 该 变 量 的 时 候 , 就 可以 打 印 更 多 的 文 本 .( 这 就 是 说 在 write 调 用 过 程 中 , 变 量 本 身 将 发 生 变 化 , 原 来 的 值 将 不200


被 保 留 . 因 此 如 果 你 想 保 持 最 初 的 值 , 你 需 要 使 用 一 个 临 时 变 量 来 代 替 原 来 的 变 量 ). 通 常你 应 该 使 用 一 组 垂 直 对 齐 的 格 式 区 域 打 印 一 块 文 本 . 你 也 许 会 想 用 文 本 "..." 来 结 束 最 后 的区 域 , 这 样 当 文 本 太 长 不 能 完 整 地 打 印 的 时 候 , 指 定 的 文 本 将 会 被 打 印 . 你 也 可 以 改 变 变 量$:( 当 你 使 用 English 模 块 , 那 么 就 是 $FORMAT_LINE_BREAK_CHARACTERS) 来改 变 用 来 表 示 中 断 的 合 法 字 符 .使 用 ^ 区 域 能 够 产 生 变 长 的 纪 录 . 如 果 格 式 区 域 太 短 , 你 可 以 重 复 几 次 带 有 ^ 区 域 的 格式 行 . 如 果 你 用 这 个 方 法 处 理 一 块 较 短 的 数 据 , 那 么 你 将 会 得 到 几 个 空 白 输 出 行 . 为 了 避 免空 白 行 , 你 可 以 在 格 式 行 中 的 任 意 地 方 放 置 一 个 ~ ( 波 浪 号 ).( 在 输 出 中 波 浪 号 本 身 会 被 转换 成 一 个 空 格 ). 如 果 你 使 用 第 二 个 ~ 波 浪 号 , 该 格 式 行 会 被 重 复 直 到 在 该 格 式 行 中 所 有域 中 的 文 本 被 用 尽 为 止 .( 因 为 ^ 区 域 会 吃 掉 所 要 打 印 的 字 符 串 , 因 此 前 面 的 格 式 行 能 够运 行 , 但 是 如 果 你 使 用 和 两 个 波 浪 号 结 合 的 一 个 @ 域 , 你 最 好 每 次 给 这 表 达 式 不 同 的 值 !使 用 shift 或 者 其 他 带 有 副 作 用 的 操 作 符 , 来 用 尽 所 有 的 值 .)标 头 的 处 理 缺 省 使 用 当 前 文 件 句 柄 名 加 上 _TOP 后 缀 的 格 式 来 处 理 . 它 在 每 一 页 的 开 头 被触 发 . 参 看 二 十 九 章 的 write.# a report on the /etc/passwd fileformat STDOUT_TOP =Passwd FileName Login Office Uid Gid Home------------------------------------------------------------------.format STDOUT =@


@>$system, $%, $date------------------------------------------------------------------.format STDOUT =Subject: @


$description.除 非 一 个 格 式 是 在 词 法 变 量 的 作 用 范 围 内 定 义 , 否 则 该 词 法 变 量 在 格 式 中 是 不 可 见 的 .在 同 一 个 输 出 通 道 中 将 print 和 write 混 合 起 来 是 可 以 的 , 但 是 你 必 须 自 己 操 作 特 殊 变 量$- ( 在 English 模 块 中 是 $FORMAT_LINES_LEFT).7.1 格 式 变 量当 前 的 格 式 名 字 存 储 在 $~ 中 ($FORMAT_NAME), 当 前 的 表 头 格 式 名 字 存 储 在 $^($FORMAT_TOP_NAME). 当 前 输 出 的 页 号 在 $% ($FORMAT_PAGE_NUMBER), 每页 中 的 行 数 在 $= ($FORMAT_LINES_PER_PAGE). 是 否 自 动 刷 新 输 出 缓 冲 区 存 储 在 $|($FORMAT_AUTOFLUSH). 在 每 一 页 ( 除 了 第 一 页 ) 表 头 之 前 需 要 输 出 的 字 符 串 存 储 在$^L ($FORMAT_FORMFEED). 这 些 变 量 以 文 件 句 柄 为 基 础 设 定 , 因 此 你 需 要 select 与特 定 格 式 关 联 的 文 件 句 柄 来 影 响 这 些 格 式 变 量 :select((select(OUTF),$~ = "My_Other_Format",$^ = "My_Top_Format")[0]);是 不 是 很 难 看 ? 可 是 这 是 一 个 习 惯 用 法 , 因 此 当 你 看 见 它 时 不 要 感 到 惊 讶 . 你 至 少 可 以 使 用一 个 临 时 变 量 来 保 持 前 一 个 文 件 句 柄 :$ofh = select(OUTF);$~ = "My_Other_Format";$^ = "My_Top_Format";select($ofh);通 常 这 是 一 个 更 好 的 方 式 , 因 为 这 不 仅 仅 是 增 加 了 可 读 性 , 但 是 你 现 在 在 代 码 有 了 一 个 中间 语 句 , 这 样 你 可 以 在 单 步 调 试 的 时 候 可 以 在 这 里 停 下 来 , 如 果 你 使 用 English 模 块 , 你甚 至 可 以 这 样 读 取 变 量 名 字 :use English;$ofh = select(OUTF);203


$FORMAT_NAME= "My_Other_Format";$FORMAT_TOP_name = "My_Top_Format";select($ofh);但 是 你 仍 然 要 调 用 这 些 select, 如 果 你 想 避 免 使 用 他 们 , 使 用 <strong>Perl</strong> 集 成 的 FileHandle ?模 块 . 现 在 你 就 可 以 使 用 小 写 的 方 法 名 来 访 问 这 些 特 殊 变 量 :use FileHandle;OUTF->format_name("My_Other_Format");OUTF->format_top_name("My_Top_Format");这 样 看 起 来 更 好 !因 为 跟 在 格 式 行 后 面 的 数 值 行 可 以 包 含 任 意 的 表 达 式 ( 提 供 给 @ 域 , 而 不 是 ^ 域 ), 所 以你 可 以 使 用 一 些 高 级 的 处 理 , 象 sprintf 或 者 一 个 你 自 己 的 函 数 . 例 如 为 了 在 一 个 数 字 里面 插 入 一 些 逗 号 , 你 可 以 使 用 下 面 的 方 法 :format Ident =@


"Some text line".> 域 长 度 标 识 保 证 在 域 中 的 文 本 将 是 右 对 齐 , 但 只 是 在 你 定 义 的 域 内 精 确 地 对 齐 .<strong>Perl</strong> 中没 有 一 种 内 置 的 方 法 可 以 获 得 " 将 这 个 区 域 在 页 右 侧 对 齐 , 而 不 管 它 的 宽 度 " 这 样 的 效 果 ,你 必 须 指 定 用 于 对 齐 的 左 侧 位 置 . 你 可 以 基 于 当 前 列 号 ( 不 提 供 ) 来 产 生 它 们 自 己 的 格 式 , 来达 到 上 面 的 目 的 , 然 后 eval 它 :$format = "format STDOUT = \n". '^' . '


^


ENDprint "Wow, I just stored `$^A' in the accumulator!\n";或 者 创 建 一 个 swrite 子 过 程 , 它 对 于 write 的 作 用 就 像 sprintf 对 printf 的 作 用 , 可以 象 下 边 的 代 码 一 样 使 用 :use Carp;sub swrite {croak "usage: swrite PICTURE ARGS" unless @_;my $format = shift;$^A = "";formline($format, @_);return $^A;}$string = swrite(formline("^" . ("


第 八 章 引 用不 管 是 从 理 论 还 是 实 践 的 角 度 出 发 ,<strong>Perl</strong> 都 是 偏 爱 平 面 线 性 的 数 据 结 构 的 。 并 且 对 许 多 问题 来 说 , 这 些 也 就 是 你 所 要 的 东 西 。假 设 你 想 制 作 一 个 简 单 的 表 ( 二 维 数 组 ), 为 一 组 人 员 显 示 生 命 数 据 用 —— 包 括 年 龄 , 眼 睛颜 色 , 和 重 量 等 。 你 可 以 通 过 先 给 每 个 独 立 的 成 员 创 建 一 个 数 组 来 实 现 这 个 目 的 。@john = (47, "brown", 186);@mary = (23, "hazel", 128);@bill = (35, "blue", 157);然 后 你 就 可 以 构 造 一 个 附 加 的 数 组 , 该 数 组 由 其 他 数 组 的 名 字 组 成 :@vitals = ('john', 'mary', 'bill');在 小 镇 上 过 了 一 夜 之 后 , 为 了 把 John 的 眼 睛 变 成 “ 红 色 ”(“red”), 我 们 需 要 一 个 仅 仅 通过 使 用 字 串 “john” 就 可 以 改 变 数 组 @john 的 内 容 的 方 法 。 这 就 是 间 接 的 基 本 问 题 , 而 不同 的 语 言 是 用 不 同 的 方 法 来 解 决 这 个 问 题 的 。 在 C 里 , 间 接 的 最 常 见 的 形 式 就 是 指 针 , 它可 以 让 一 个 变 量 保 存 另 外 一 个 变 量 的 内 存 地 址 。 在 <strong>Perl</strong> 里 , 间 接 的 最 常 见 的 形 式 是 引 用 。8.1 什 么 是 引 用 ?在 我 们 的 例 子 里 ,$vitals[0] 的 值 是 “john”。 也 就 是 说 它 正 好 包 含 另 外 一 个 ( 全 局 ) 变 量的 名 字 。 我 们 说 第 一 个 变 量 提 到 了 第 二 个 变 量 , 并 且 这 种 参 考 叫 符 号 引 用 , 因 为 <strong>Perl</strong> 必 须在 一 个 符 号 表 里 找 出 @john 来 才 能 找 到 它 。( 你 可 以 把 符 号 引 用 看 作 类 似 文 件 系 统 中 的符 号 联 接 的 东 西 )。 我 们 将 在 本 章 晚 些 时 候 讨 论 符 号 引 用 。另 外 一 种 引 用 是 硬 引 用 , 这 种 引 用 是 大 多 数 <strong>Perl</strong> 程 序 员 用 来 实 现 它 们 的 间 接 访 问 方 法 ( 只要 不 是 他 们 的 草 率 行 为 )。 我 们 叫 它 们 硬 引 用 并 不 是 因 为 它 们 用 起 来 很 难 ( 译 注 :"hardreference" 硬 引 用 , 在 英 文 中 “hard” 有 “ 困 难 , 硬 ” 的 意 思 ), 而 是 因 为 它 们 是 现 实 并 且 存在 的 。 如 果 你 愿 意 , 那 么 你 可 以 把 硬 引 用 当 作 真 正 的 引 用 而 把 符 号 引 用 当 作 虚 假 的 引 用 。 它们 的 区 别 就 好 象 真 正 的 友 谊 和 见 面 打 个 招 呼 一 样 。 如 果 我 们 没 有 声 明 我 们 指 的 是 哪 种 引 用 ,208


那 么 我 们 说 的 就 是 硬 引 用 。 图 8-1 描 述 了 一 个 叫 $bar 的 变 量 引 用 了 一 个 叫 $foo 的 变 量的 内 容 , 而 $foo 的 值 是 “bot”。和 符 号 引 用 不 同 的 是 , 真 实 引 用 所 引 用 的 不 是 另 外 一 个 变 量 的 名 字 ( 名 字 只 是 一 个 数 值 的 容器 ), 而 是 实 际 的 数 值 本 身 , 是 一 些 内 部 的 数 据 团 。 我 们 没 有 什 么 好 字 眼 来 描 述 这 样 的 东 西 ,可 是 我 们 又 不 得 不 描 述 , 于 是 我 们 就 叫 它 引 用 。 举 例 来 说 , 假 如 你 创 建 了 一 个 指 向 某 个 词 法范 围 数 组 @array 的 硬 引 用 。 那 么 , 即 使 在 @array 超 出 了 范 围 之 后 , 该 引 用 以 及 它 所引 用 的 参 考 物 也 仍 然 继 续 存 在 。 一 个 引 用 只 有 在 对 它 的 所 有 引 用 都 消 失 之 后 才 会 被 摧 毁 。除 了 指 向 它 的 引 用 之 外 , 引 用 实 际 上 并 没 有 自 己 的 名 字 。 换 句 话 说 , 每 个 <strong>Perl</strong> 变 量 都 存 储在 某 个 符 号 表 里 , 保 存 着 一 个 指 向 所 引 用 的 东 西 的 硬 引 用 ( 否 则 就 没 有 名 字 )。 引 用 物 可 以很 简 单 , 比 如 一 个 数 字 或 者 字 串 , 也 可 以 很 复 杂 , 比 如 一 个 数 组 或 散 列 。 不 管 哪 种 情 况 , 从变 量 到 数 值 之 间 都 只 有 一 个 引 用 。 你 可 以 创 建 指 向 相 同 引 用 物 的 额 外 的 引 用 , 但 该 变 量 并 不知 道 ( 或 在 乎 ) 这 些 引 用 。( 注 : 如 果 你 觉 得 奇 怪 , 那 么 你 可 以 用 Devel::Peek 模 块 计算 引 用 计 数 , 这 个 包 是 和 <strong>Perl</strong> 捆 绑 发 布 的 。)符 号 引 用 只 是 一 个 字 串 , 它 的 值 碰 巧 和 包 的 符 号 表 里 什 么 东 西 的 名 字 相 同 。 它 和 你 平 时 处 理的 字 串 没 有 什 么 太 大 的 区 别 。 但 是 硬 引 用 却 是 完 全 不 同 的 家 伙 。 它 是 三 种 基 本 的 标 量 类 型 中的 第 三 种 , 其 他 两 种 是 字 串 和 数 字 。 硬 引 用 除 了 指 向 某 些 事 物 之 外 并 不 知 道 它 们 的 名 字 , 并且 这 些 引 用 物 在 一 开 始 的 时 候 并 没 有 名 字 也 是 非 常 正 常 的 事 情 。 这 样 的 未 名 引 用 叫 做 匿 名 ,我 们 将 在 下 面 的 “ 匿 名 数 据 ” 里 讨 论 它 们 。在 本 章 的 术 语 里 , 引 用 一 个 数 值 就 是 创 建 一 个 指 向 它 的 硬 引 用 。( 我 们 有 一 个 操 作 符 用 于 这种 创 建 动 作 )。 这 样 创 建 的 引 用 只 是 一 个 简 单 的 标 量 , 它 和 所 有 其 他 标 量 一 样 在 我 们 熟 悉 的环 境 里 有 着 一 样 的 行 为 。 给 这 个 标 量 解 引 用 ( 析 引 用 ) 意 味 着 我 们 使 用 这 个 引 用 访 问 引 用 物 。引 用 和 解 引 用 都 是 只 发 生 在 某 些 明 确 的 机 制 里 , 在 <strong>Perl</strong> 里 从 来 不 会 出 现 隐 含 地 引 用 或 解 引用 的 现 象 。 哦 , 是 几 乎 从 来 不 发 生 。一 次 函 数 调 用 可 以 使 用 明 确 的 引 用 传 参 语 意 —— 只 要 它 有 这 样 声 明 的 原 型 。 如 果 是 这 样 , 那么 函 数 的 调 用 者 并 不 明 确 地 传 递 一 个 引 用 , 但 是 你 在 函 数 里 面 还 是 要 明 确 的 对 它 ( 参 数 ) 进行 解 引 用 。 参 阅 第 六 章 , 子 过 程 , 里 的 “ 原 型 ” 节 。 如 果 从 绝 对 诚 实 的 角 度 出 发 , 那 么 你 在 使用 某 些 类 型 的 文 件 句 柄 的 时 候 仍 然 是 有 一 些 隐 藏 在 幕 后 的 解 引 用 发 生 , 但 是 这 么 做 是 为 了 保持 向 下 兼 容 , 并 且 对 于 那 些 偶 然 使 用 到 的 用 户 来 说 是 透 明 的 。 最 后 , 有 两 个 内 建 的 函 数 ,bless 和 block, 它 们 都 接 受 一 个 引 用 作 为 自 己 的 参 数 , 但 是 都 会 隐 含 地 对 这 些 引 用 进 行解 引 用 以 实 现 各 自 的 功 能 。 不 过 除 了 我 们 这 里 招 供 的 这 些 以 外 , 基 本 的 概 念 还 是 一 致 的 , 那就 是 <strong>Perl</strong> 并 不 想 介 入 你 自 己 的 间 接 层 次 中 。209


一 个 引 用 可 以 指 向 任 何 数 据 结 构 。 因 为 引 用 是 标 量 , 所 以 你 可 以 把 它 们 保 存 在 数 组 和 散 列 里 ,因 此 我 们 就 可 以 做 出 数 组 的 数 组 , 散 列 的 数 组 , 数 组 的 散 列 , 散 列 和 函 数 的 数 组 等 。 在 第 九章 , 数 据 结 构 , 里 有 这 些 东 西 的 例 子 。不 过 , 你 要 记 住 的 是 ,<strong>Perl</strong> 里 的 数 组 和 散 列 都 是 故 意 作 成 一 维 的 。 也 就 是 说 , 它 们 的 元 素只 能 保 存 标 量 值 ( 字 串 , 数 字 , 和 引 用 )。 当 我 们 使 用 “ 数 组 的 数 组 ” 这 样 的 习 语 的 时 候 , 我们 的 意 思 实 际 上 是 “ 一 个 保 存 指 向 一 些 数 组 的 引 用 的 数 组 ”, 就 好 象 我 们 说 “ 函 数 散 列 ” 的 时候 , 我 们 实 际 上 是 说 “ 一 个 保 存 着 一 些 指 向 子 过 程 的 引 用 的 散 列 ”。 但 因 为 引 用 是 在 <strong>Perl</strong> 里实 现 这 些 构 造 的 唯 一 方 法 , 所 以 我 们 那 些 稍 微 短 的 并 非 准 确 的 习 语 也 就 并 不 是 完 全 不 对 , 因此 也 不 应 该 完 全 忽 视 , 除 非 你 碰 到 准 确 性 问 题 。8.2 创 建 引 用创 建 引 用 的 方 法 有 好 多 种 , 我 们 在 讲 述 它 们 的 时 候 大 多 会 先 描 述 它 们 , 然 后 才 解 释 如 何 使 用( 解 引 用 ) 所 生 成 的 引 用 。8.2.1 反 斜 杠 操 作 符你 可 以 用 一 个 反 斜 杠 创 建 一 个 指 向 任 何 命 名 变 量 或 者 子 过 程 的 引 用 。( 你 还 可 以 把 它 用 于 一个 匿 名 标 量 值 , 比 如 7 或 "camel", 尽 管 你 通 常 并 不 需 要 这 些 东 西 。) 乍 一 看 , 这 个 操 作符 的 作 用 类 似 C 里 的 &( 取 址 ) 操 作 符 。下 面 是 一 些 例 子 :$scalarref = \$foo;$constref = \186_282.42;$arrayref = \@ARGV;$hashref$coderef$globref= \%ENV;= \&handler;= \*STDOUT;反 斜 杠 操 作 符 可 以 做 的 事 情 远 远 不 止 生 成 一 个 引 用 。 如 果 你 对 一 个 列 表 使 用 反 斜 杠 , 那 么 它会 生 成 一 整 列 引 用 。 参 阅 “ 你 用 硬 引 用 可 以 实 现 的 其 他 技 巧 ” 一 节 。8.2.2 匿 名 数 据210


在 我 们 刚 刚 显 示 的 例 子 里 , 反 斜 杠 操 作 符 只 是 简 单 地 复 制 了 一 个 已 经 存 在 于 一 个 命 名 变 量 上的 引 用 —— 但 有 一 个 例 外 。186_282.42 不 是 一 个 命 名 变 量 的 引 用 —— 它 只 是 一 个 数 值 。它 是 那 种 我 们 早 先 提 到 过 的 匿 名 引 用 。 匿 名 引 用 物 只 能 通 过 引 用 来 访 问 。 我 们 这 个 例 子 碰 巧是 一 个 数 字 , 但 是 你 也 可 以 创 建 匿 名 数 组 , 散 列 , 和 子 过 程 。8.2.2.1 匿 名 数 组 组 合 器你 可 以 用 方 括 弧 创 建 一 个 创 建 一 个 指 向 匿 名 数 组 的 引 用 :$arrayref = [1, 2, ['a', 'b', 'c', 'd']];在 这 里 我 们 组 合 成 了 一 个 三 个 元 素 的 匿 名 数 组 , 该 数 组 最 后 一 个 元 素 是 一 个 指 向 有 着 四 个 元素 的 匿 名 数 组 ( 在 图 8-2 里 演 示 )。( 我 们 稍 后 描 述 的 多 维 语 法 可 以 用 于 访 问 这 些 东 西 。比 如 , $arrayref->[2][1] 将 具 有 数 值 “b”。)现 在 我 们 有 一 个 方 法 来 表 示 我 们 本 章 开 头 的 表 :$table = [ [ "john", 47, "brown", 186],[ "mary", 23, ""hazel", 128],[ "bill", 35, "blue", 157] ];只 是 当 <strong>Perl</strong> 分 析 器 在 一 个 表 达 式 里 需 要 项 的 时 候 , 方 括 弧 才 可 以 这 样 运 做 。 你 可 不 要 把 它们 和 表 达 式 里 的 方 括 弧 混 淆 起 来 —— 比 如 $array[6]—— 尽 管 与 数 组 的 记 忆 性 关 联 是 有 意为 之 的 。 在 一 个 引 起 的 字 串 里 , 方 括 弧 并 不 组 成 匿 名 数 组 ; 相 反 , 它 们 成 为 字 串 里 的 文 本 字符 。( 在 字 串 里 方 括 弧 的 确 仍 然 可 以 当 作 脚 标 使 用 , 否 则 你 就 不 能 打 印 象"VAL=$array[6]\n" 这 样 的 字 串 。 如 果 要 我 们 绝 对 诚 实 , 你 实 际 上 是 可 以 偷 偷 地 把 匿 名数 组 组 合 器 放 到 字 串 里 , 但 只 能 是 在 它 被 潜 入 到 一 个 更 大 的 表 达 式 中 , 并 且 该 表 达 式 被 代 换的 情 况 下 才 可 以 实 现 。 我 们 将 在 本 章 稍 后 讲 述 这 个 酷 酷 的 特 性 , 因 为 它 既 包 括 引 用 也 包 括 解引 用 。)8.2.2.2 匿 名 散 列 组 合 器你 可 以 用 花 括 弧 创 建 一 个 指 向 匿 名 散 列 的 引 用 :$hashref = {'Adam' => 'Eve','Clyde' => $bonnie,211


'Antony' => 'Cleo' . 'patra',};对 于 散 列 的 数 值 ( 但 不 是 键 字 ), 你 可 以 自 由 地 混 合 其 他 匿 名 数 组 , 散 列 , 和 子 过 程 , 组 合成 你 需 要 的 复 杂 数 据 结 构 。现 在 我 们 有 了 表 示 本 章 开 头 的 表 的 另 外 一 种 方 法 :$table = {"john" => [47, "brown", 186],"mary" => [23, "hazel", 128],"bill" => [35, "blue", 157],};这 是 一 个 数 组 散 列 。 选 择 最 好 的 数 据 结 构 是 难 度 很 高 的 工 种 , 下 一 章 专 门 讲 这 个 。 但 是 为 了恶 作 剧 , 我 们 甚 至 可 以 将 散 列 的 散 列 用 于 我 们 的 表 :$table = {"john" => { age => 47,eyes=> "brown",weight => 186,},"mary" => { age => 23,eyes=> "hazel",weight => 128,},"bill" => { age => 35,eyes=> "blue",weight => 157,},212


};和 方 括 弧 一 样 , 只 有 在 <strong>Perl</strong> 分 析 器 在 表 达 式 里 需 要 一 个 项 的 时 候 , 花 括 弧 才 运 做 。 你 也 不要 把 它 和 在 表 达 式 里 的 花 括 弧 混 淆 了 , 比 如 $hash{key}—— 尽 管 与 散 列 的 记 忆 性 关 联( 也 ) 是 有 意 为 之 的 。 同 样 的 注 意 事 项 也 适 用 于 在 字 串 里 的 花 括 弧 。但 是 有 另 外 一 个 注 意 事 项 并 不 适 用 于 方 括 弧 。 因 为 花 括 弧 还 可 以 用 于 几 种 其 他 的 东 西 ( 包 括块 ), 有 时 候 你 可 能 不 得 不 在 语 句 的 开 头 放 上 一 个 + 或 者 一 个 return 来 消 除 这 个 位 置 的花 括 弧 的 歧 义 , 这 样 <strong>Perl</strong> 就 会 意 识 到 这 个 开 花 括 弧 不 是 引 出 一 个 块 。 比 如 , 如 果 你 需 要 一个 函 数 生 成 一 个 新 散 列 然 后 返 回 一 个 指 向 它 的 引 用 , 那 么 你 有 下 列 选 择 :sub hashem { { @_ } } # 不 声 不 响 地 错 误 -- returns @_sub hashem { +{ @_ } } # 对sub hashem { return {@_}}# 对8.2.2.3 匿 名 子 过 程 组 合 器你 可 以 通 过 用 不 带 子 过 程 名 字 的 sub 创 建 一 个 匿 名 子 过 程 :$coderef = sub { print "Boink!\n" };# 现 在 &$coderef 打 印 "Boink!"请 注 意 分 号 的 位 置 , 在 这 里 要 求 用 分 号 是 为 了 终 止 该 表 达 式 。( 在 更 常 见 的 声 明 和 定 义 命 名子 过 程 的 用 法 sub NAME {} 里 面 不 需 要 这 个 分 号 。) 一 个 没 有 名 字 的 sub {} 并 不 象是 个 声 明 而 更 象 一 个 操 作 符 —— 象 do {} 或 eval {} 那 样 —— 只 不 过 在 里 面 的 代 码 并 不是 立 即 执 行 的 。 它 只 是 生 成 一 个 指 向 那 些 代 码 的 引 用 , 在 我 们 的 例 子 里 是 存 储 在 $coderef里 。 不 过 , 不 管 你 执 行 上 面 的 行 多 少 次 ,$coderef 都 仍 然 将 指 向 同 样 的 匿 名 子 过 程 。( 注 :不 过 就 算 只 有 一 个 匿 名 子 过 程 , 也 有 可 能 有 好 几 份 词 法 变 量 的 拷 贝 被 该 子 过 程 使 用 , 具 体 情况 取 决 于 子 过 程 生 成 的 时 候 。 这 些 东 西 在 稍 后 的 “ 闭 合 ”( 闭 包 ) 里 讨 论 )。8.2.3 对 象 构 造 器子 过 程 也 可 以 返 回 引 用 。 这 句 话 听 起 来 有 些 陈 腐 , 但 是 有 时 候 别 人 要 求 你 用 一 个 子 过 程 来 创建 引 用 而 不 是 由 你 自 己 创 建 引 用 。 特 别 是 那 些 叫 构 造 器 的 特 殊 子 过 程 创 建 并 返 回 指 向 对 象 的引 用 。 对 象 只 是 一 种 特 殊 的 引 用 , 它 知 道 自 己 是 和 哪 个 类 关 联 在 一 起 的 , 而 构 造 器 知 道 如 何创 建 那 种 关 联 关 系 。 这 些 构 造 器 是 通 过 使 用 bless 操 作 符 , 将 一 个 普 通 的 引 用 物 转 换 成 一个 对 象 实 现 的 , 所 以 我 们 可 以 认 为 对 象 是 一 个 赐 过 福 的 引 用 。( 译 注 :bless 在 英 文 中 原 意是 “ 赐 福 ”,<strong>Perl</strong> 中 使 用 这 样 的 词 作 为 操 作 符 名 称 , 想 必 和 Larry 先 生 是 学 习 语 言 出 身 , 并且 是 虔 诚 的 教 徒 的 背 景 有 关 系 吧 , 不 过 这 个 词 的 确 非 常 贴 切 的 形 容 了 该 操 作 符 的 作 用 , 所 以 ,213


我 就 直 译 为 “ 赐 福 ”, 希 望 不 会 破 坏 原 味 。) 这 儿 可 和 宗 教 没 什 么 关 系 ; 因 为 一 个 类 起 的 作 用是 用 户 定 义 类 型 , 给 一 个 引 用 赐 福 只 是 简 简 单 单 地 把 它 变 成 除 了 内 建 类 型 之 外 的 用 户 定 义 类型 。 构 造 器 通 常 叫 做 new—— 特 别 是 C++ 程 序 员 更 是 如 此 看 待 —— 但 在 <strong>Perl</strong> 里 它 们 可以 命 名 为 任 何 其 他 名 字 。构 造 器 可 以 用 下 列 任 何 方 法 调 用 :$objref = Doggie::->new(Tail => 'short', Ears => 'long'); #1$objref = new Doggie:: Tail => 'short', Ears => 'long'; #2$objref = Doggie->new(Tail => 'short', Ears => 'long'); #3$objref = new Doggie Tail => 'short', Ears => 'long'; #4第 一 个 和 第 二 个 调 用 方 法 是 一 样 的 。 它 们 都 调 用 了 Doggie 模 块 提 供 的 一 个 叫 new 的 函数 。 第 三 个 和 第 四 个 调 用 和 头 两 个 是 一 样 的 , 只 不 过 稍 微 更 加 模 糊 一 些 : 如 果 你 定 义 了 自 己的 叫 Doggie 的 子 过 程 , 那 么 分 析 器 将 被 你 弄 糊 涂 。( 这 就 是 为 什 么 人 们 坚 持 把 小 写 的 名字 用 于 子 过 程 , 而 把 大 写 的 名 字 用 于 模 块 。) 如 果 你 定 义 了 自 己 的 new 子 过 程 , 并 且 偏 偏有 没 有 用 require 或 者 use 使 用 Doggie 模 块 ( 这 两 个 都 有 声 明 该 模 块 的 作 用 。), 那么 第 四 个 方 法 也 会 引 起 歧 义 。 如 果 你 想 用 方 法 4, 那 么 最 好 声 明 你 的 模 块 。( 并 且 还 要 注意 看 看 有 没 有 Doggie 子 过 程 。) 参 阅 第 十 二 章 , 对 象 , 看 看 有 关 <strong>Perl</strong> 对 象 的 讨 论 。8.2.4 句 柄 引 用你 可 以 通 过 引 用 同 名 的 类 型 团 来 创 建 指 向 文 件 句 柄 或 者 目 录 句 柄 :splutter(\*STDOUT);sub splutter {my $fh = shift;print $fh = "her um well a hmmm\n";}$rec = get_rec(\*STDIN);sub get_rec {214


my $fh = shift;return scalar ;}如 果 你 是 在 传 递 文 件 句 柄 , 你 还 可 以 使 用 光 文 件 句 柄 来 实 现 : 在 上 面 的 例 子 中 , 你 可 以 使 用*STDOUT 或 者 *STDIN, 而 不 是 \*STDOUT 和 \*STDIN。尽 管 通 常 你 可 以 互 换 地 使 用 类 型 团 和 指 向 类 型 团 的 引 用 , 但 还 是 有 少 数 几 个 地 方 是 不 可 以 这么 用 的 , 简 单 的 类 型 团 不 能 bless 成 对 象 , 并 且 类 型 团 引 用 无 法 传 递 出 一 个 局 部 化 了 的 类型 团 的 范 围 。当 生 成 新 的 文 件 句 柄 的 时 候 , 老 的 代 码 通 常 做 类 似 下 面 这 样 的 东 西 来 打 开 一 个 文 件 列 表 :for $file (@name) {local *FH;open(*FH, $file) || next;$handle($file) = *FH;}这 么 做 仍 然 可 行 , 但 是 现 在 我 们 有 更 简 单 的 方 法 : 就 是 让 一 个 未 定 义 的 变 量 自 动 激 活 一 个 匿名 类 型 团 :for $file (@name) {my $fh;open($fh, $file) || next;$handle($file) = $fh;}使 用 间 接 文 件 句 柄 的 时 候 ,<strong>Perl</strong> 并 不 在 意 你 使 用 的 是 类 型 团 , 还 是 指 向 类 型 团 的 引 用 , 或者 是 更 奇 异 的 I/O 对 象 中 的 一 个 。 就 大 多 数 目 的 来 说 , 你 几 乎 可 以 没 有 任 何 区 别 的 使 用 类型 团 或 者 类 型 团 引 用 之 一 。 但 是 我 们 前 面 也 承 认 过 , 这 两 种 做 法 仍 然 有 一 些 隐 含 的 细 小 区 别 。8.2.5 符 号 表 引 用215


在 特 别 的 环 境 里 , 你 在 开 始 写 程 序 的 时 候 可 能 不 知 道 你 需 要 什 么 样 的 引 用 。 你 可 以 用 一 种 特殊 的 语 法 创 建 引 用 , 人 们 常 说 是 *foo{THING} 语 法 。*foo{THING} 返 回 一 个 指 向*foo 里 面 THING 槽 位 的 引 用 , 这 个 引 用 就 是 在 符 号 表 里 保 存 $foo,@foo,%foo, 和友 元 的 记 录 。$scalarref = *foo{SCALAR};$arrayref = *ARGV{ARRAY};# 和 \$foo 一 样# 和 \@ARGV 一 样$hashref = *ENV{HASH}; # 和 \%ENV 一 样$coderef = *handler{CODE}; # 和 \&handler 一 样$globref = *foo{GLOB}; # 和 \*foo 一 样$ioref = *STDIN{IO}; # ?...所 有 这 些 语 句 都 具 有 自 释 性 , 除 了 *STDIN{IO} 之 外 。 它 生 成 该 类 型 团 包 含 的 实 际 的 内部 IO::Handle 对 象 , 也 就 是 各 种 I/O 函 数 实 际 上 感 兴 趣 的 类 型 团 的 部 分 。 为 了 和 早 期版 本 的 <strong>Perl</strong> 兼 容 ,*foo{FILEHANDLE} 是 上 面 的 *foo{IO} 说 法 的 一 个 同 义 词 。理 论 上 来 说 , 你 可 以 在 任 何 你 能 用 *HANDLE 或 者 \*HANDLE 的 地 方 使 用*HANDLE{IO}, 比 如 将 文 件 句 柄 传 入 或 者 传 出 子 过 程 , 或 者 在 一 个 更 大 的 数 据 结 构 里 存储 它 们 。( 实 际 上 , 仍 然 有 一 些 地 方 不 能 这 么 互 换 着 用 。) 它 们 的 优 点 是 它 们 只 访 问 你 需 要的 真 实 的 I/O, 而 不 是 整 个 类 型 团 , 因 此 如 果 你 通 过 一 个 类 型 团 赋 值 剪 除 了 比 你 预 计 的 要多 的 东 西 也 不 会 有 什 么 风 险 ( 但 如 果 你 总 是 给 一 个 标 量 变 量 赋 值 而 不 是 给 类 型 团 赋 值 , 那 么你 就 OK 了 )。 缺 点 是 目 前 没 有 办 法 自 动 激 活 这 么 一 个 。( 注 : 目 前 ,open my $fh 自动 激 活 一 个 类 型 团 而 不 是 一 个 IO::Handle 对 象 , 不 过 有 朝 一 日 我 们 总 会 修 正 这 个 问 题的 , 所 以 你 不 应 该 依 赖 open 目 前 自 动 激 活 的 类 型 团 的 特 征 )。splutter(*STDOUT);splutter(*STDOUT{IO});sub splutter {my $fh = shift;print $fh "her um well a hmmm\n";}216


上 面 对 splutter 的 两 个 调 用 都 打 印 "her um well a hmm"。如 果 编 译 器 还 没 有 看 到 特 定 的 THING, 那 么 *foo{THING} 这 样 的 构 造 返 回 undef。 而且 *foo{SCALAR} 返 回 一 个 指 向 一 个 匿 名 标 量 的 引 用 , 即 使 编 译 器 还 没 有 看 到 $foo。(<strong>Perl</strong> 总 是 给 任 何 类 型 团 加 一 个 标 量 , 它 把 这 个 动 作 当 作 一 个 优 化 , 以 便 节 省 其 他 的 什 么地 方 的 一 些 代 码 。 但 是 在 未 来 的 版 本 中 不 要 指 望 <strong>Perl</strong> 保 持 这 种 做 法 。)8.2.6 引 用 的 隐 含 创 建创 建 引 用 的 最 后 一 种 做 法 就 根 本 算 不 上 什 么 方 法 。 如 果 你 在 一 个 认 为 存 在 引 用 的 左 值 环 境 里做 解 引 用 的 动 作 , 那 么 引 用 就 会 自 动 出 现 。 这 样 做 是 非 常 有 用 的 , 并 且 它 也 是 你 希 望 的 。 这个 论 题 我 们 在 本 章 稍 后 探 讨 , 那 时 侯 我 们 将 讨 论 如 何 把 我 们 到 此 为 止 创 建 的 引 用 给 解 引 用掉 。8.3 使 用 硬 引 用就 象 我 们 有 无 数 的 方 法 创 建 引 用 一 样 , 我 们 也 有 好 几 种 方 法 使 用 引 用 ( 或 者 称 之 为 解 引 用 )。使 用 过 程 中 只 有 一 个 最 高 级 的 原 则 :<strong>Perl</strong> 不 会 做 任 何 隐 含 的 引 用 或 者 解 引 用 动 作 。( 注 :我 们 已 经 承 认 这 句 话 是 撒 了 一 个 小 谎 。 我 们 不 想 再 说 了 。) 如 果 一 个 标 量 挂 在 了 一 个 引 用 上 ,那 么 它 总 是 表 现 出 简 单 标 量 的 行 为 。 它 不 会 突 然 就 成 为 一 个 数 组 或 者 散 列 或 是 子 过 程 , 你 必须 明 确 地 告 诉 它 进 行 转 变 , 方 法 就 是 对 它 解 引 用 。8.3.1 把 一 个 变 量 当 作 变 量 名 使 用如 果 你 看 到 一 个 标 量 , 比 如 $foo, 你 应 该 把 它 看 成 “foo 的 标 量 值 。” 也 就 是 说 , 在 符 号 表里 有 一 条 foo 记 录 , 而 趣 味 字 符 $ 是 一 个 查 看 其 内 部 的 标 量 值 的 方 法 。 如 果 在 里 面 的 是一 个 引 用 , 那 么 你 可 以 通 过 在 前 面 再 增 加 一 个 趣 味 字 符 来 查 看 引 用 的 内 容 ( 解 引 用 )。 或 者用 其 他 方 法 查 看 它 , 你 可 以 把 $foo 里 的 文 本 字 串 foo 替 换 成 一 个 指 向 实 际 引 用 物 的 标 量变 量 。 这 样 做 对 任 何 变 量 类 型 都 是 正 确 的 , 因 此 不 仅 仅 $$foo 是 指 $foo 指 向 的 标 量 值 ,@$bar 是 $bar 指 向 的 数 组 值 , %$glarch 是 $glarch 指 向 的 散 列 数 值 , 等 等 。 结 果是 你 可 以 在 任 何 简 单 标 量 前 面 放 上 一 个 额 外 的 趣 味 字 符 将 它 解 引 用 :$foo= "three humps";$scalarref = \$foo; # $scalarref 现 在 是 一 个 指 向 $foo 的 引 用$camel_model = $$scalarref; # $camel_model 现 在 是 "three humps"下 面 是 其 他 的 一 些 解 引 用 方 法 :217


$bar =$$scalarref;push(@$arrayref, $filename);素$$arrarref[0] = "January"; # 设 置 @$arrayref 的 第 一 个 元@$arrayref[4..6]=qw/May June July/;# 设 置 若 干 个 @$arrayref 的 元 素%$hashref = (KEY => "RING", BIRD => "SING");$$hashref{KEY} = "VALUE";@$hashref{"KEY1", "KEY2"} = {"VAL1", "VAL2"};# 初 始 化 整 个 散 列# 设 置 一 个 键 字 / 数 值 对# 再 设 置 两 对&$coderef(1,2,3);print $handleref "output\n";这 种 类 型 的 解 引 用 只 能 使 用 一 个 简 单 的 标 量 变 量 ( 没 有 脚 标 的 那 种 )。 也 就 是 说 , 解 引 用 在任 何 数 组 或 者 散 列 查 找 之 前 发 生 ( 或 者 说 是 比 数 组 和 散 列 查 找 绑 定 得 更 紧 )。 还 是 让 我 们 用一 些 花 括 弧 来 把 我 们 的 意 思 表 示 得 明 确 一 些 : 一 个 象 $$arrayref[0] 这 样 的 表 达 式 等 于${$arrayref}[0] 并 且 意 思 是 数 组 的 第 一 个 元 素 由 $arrayref 指 向 。 类 似 的 ,$$hashref{KEY} 和 ${$hashref}{KEY} 一 样 , 并 且 和 ${$hashref{KEY}} 没 什 么关 系 , 后 者 将 对 一 个 叫 做 %hashref 的 散 列 里 的 记 录 进 行 解 引 用 的 操 作 。 你 在 意 识 到 这 一点 之 前 可 能 会 非 常 悲 惨 。你 可 以 实 现 多 层 引 用 和 解 引 用 , 方 法 是 连 接 合 适 的 趣 味 字 符 。 下 面 的 程 序 打 印 "howdy":$refrefref = \\\"howdy";print $$$$refrefref;你 可 以 认 为 美 圆 符 号 是 从 右 向 左 操 作 的 。 但 是 整 个 链 条 的 开 头 必 须 是 一 个 简 单 的 , 没 有 脚 标的 标 量 变 量 。 不 过 , 还 有 一 种 方 法 变 得 更 神 奇 , 这 个 方 法 我 们 前 面 已 经 偷 偷 用 过 了 , 我 们 会在 下 一 节 解 释 这 种 方 法 。218


8.3.2 把 一 个 BLOCK 块 当 作 变 量 名 用你 不 仅 可 以 对 一 个 简 单 的 变 量 名 字 进 行 解 引 用 , 而 且 你 还 可 以 对 一 个 BLOCK 的 内 容 进 行解 引 用 。 在 任 何 你 可 以 放 一 个 字 母 数 字 标 识 符 当 变 量 或 者 子 过 程 名 字 一 部 分 的 地 方 , 你 都 可以 用 一 个 返 回 指 向 正 确 类 型 的 BLOCK 代 替 该 标 识 符 。 换 句 话 说 , 早 先 的 例 子 都 可 以 用 下面 这 样 的 方 法 明 确 化 :$bar = ${$scalarref};push(@{$arrayref}, $filename);${$arrayref}[0] = "January";@{$arrayref}[4..6] = qw/May June July/;${$hashref}{"KEY"} = "VALUE";@{$hashref}{"KEY", "KEY2"} = ("VAL1", "VAL2");&{$coderef}(1,2,3);更 不 用 说 :$refrefref = \\\"howdy";print ${${${$refrefref}}};当 然 , 在 这 么 简 单 的 情 况 下 使 用 花 括 弧 的 确 非 常 愚 蠢 , 但 是 BLOCK 可 以 包 含 任 意 地 表 达式 。 特 别 是 , 它 可 以 包 含 带 脚 标 的 表 达 式 。在 下 面 的 例 子 里 , 我 们 假 设 $dispatch{$index} 包 含 一 个 指 向 某 个 子 过 程 的 引 用 ( 有 时候 我 们 称 之 为 "coderef")。 这 个 例 子 带 着 三 个 参 数 调 用 该 子 过 程 :&{ $dispatch{$index} } (1, 2, 3);在 这 里 ,BLOCK 是 必 要 的 。 没 有 这 个 外 层 的 花 括 弧 对 ,<strong>Perl</strong> 将 把 $dispatch 当 作coderef 而 不 是 $dispatch{$index}。8.3.3 使 用 箭 头 操 作 符对 于 指 向 数 组 , 散 列 , 或 者 子 过 程 的 引 用 , 第 三 种 解 引 用 的 方 法 涉 及 到 使 用 -> 中 缀 操 作符 。 这 样 做 就 形 成 了 一 种 语 法 糖 , 这 样 就 让 我 们 可 以 更 容 易 访 问 独 立 的 数 组 或 者 散 列 元 素 ,或 者 间 接 地 调 用 一 个 子 过 程 。219


解 引 用 的 类 型 是 由 右 操 作 数 决 定 的 , 也 就 是 , 由 直 接 跟 在 箭 头 后 面 的 东 西 决 定 。 如 果 箭 头 后面 的 东 西 是 一 个 方 括 弧 或 者 花 括 弧 , 那 么 左 操 作 数 就 分 别 当 作 一 个 指 向 一 个 数 组 或 者 散 列 的引 用 , 由 右 边 的 操 作 数 做 下 标 定 位 。 如 果 箭 头 后 面 的 东 西 是 一 个 左 圆 括 弧 , 那 么 左 操 作 数 就当 作 一 个 指 向 一 个 子 过 程 的 引 用 看 待 , 然 后 用 你 在 圆 括 弧 右 边 提 供 的 参 数 进 行 调 用 。下 面 的 东 西 每 三 行 都 是 一 样 的 , 分 别 对 应 我 们 已 经 介 绍 过 的 三 种 表 示 法 。( 我 们 插 入 了 一 些空 白 , 以 便 将 等 效 的 元 素 对 齐 。)$ $arrayref [2] = "Dorian"; #1${ $arrayref }[2] = "Dorian"; #2$arrayref->[2] = "Dorian"; #3$ $hashref {KEY} = "F#major"; #1${ $hashref }{KEY} = "F#major"; #2$hashref->{KEY} = "F#major"; #3& $coderef (Presto => 192); #1&{ $coderef }(Presto => 192); #2$coderef->(Presto => 192); #3你 可 以 注 意 到 , 在 每 个 三 行 组 里 , 第 三 种 表 示 法 的 趣 味 字 符 都 不 见 了 。 这 个 趣 味 字 符 是 由<strong>Perl</strong> 猜 测 的 , 这 就 是 为 什 么 你 不 能 用 它 对 整 个 数 组 , 整 个 散 列 , 或 者 是 它 们 的 某 个 片 段 进行 解 引 用 。 不 过 , 只 要 你 坚 持 使 用 标 量 数 值 , 那 么 你 就 可 以 在 -> 左 边 使 用 任 意 表 达 式 ,包 括 另 外 一 个 解 引 用 , 因 为 多 个 箭 头 操 作 符 是 从 左 向 右 关 联 的 :print $array[3]->{"English"}->[0];你 可 以 从 这 个 表 达 式 里 推 论 出 @array 的 第 四 个 元 素 是 一 个 散 列 引 用 , 并 且 该 散 列 里 的"English" 记 录 的 数 值 是 一 个 数 组 的 引 用 。请 注 意 $array[3] 和 $array->[3] 是 不 一 样 的 。 第 一 个 东 西 讲 的 是 @array 里 的 第 四个 元 素 , 而 第 二 个 东 西 讲 的 是 一 个 保 存 在 $array 里 的 数 组 ( 可 能 是 匿 名 的 数 组 ) 引 用 的第 四 个 元 素 。220


假 设 现 在 $array[3] 是 未 定 义 。 那 么 下 面 的 语 句 仍 然 合 法 :$array[3]->{"English"}->[0] = "January";这 个 东 西 就 是 我 们 在 前 面 提 到 过 的 引 用 突 然 出 现 的 例 子 , 这 时 候 , 当 引 用 用 做 左 值 的 时 候 是自 动 激 活 的 ( 也 就 是 说 , 当 给 它 赋 予 数 值 的 时 候 )。 如 果 $array[3] 是 未 定 义 , 那 么 它 会被 自 动 定 义 成 一 个 散 列 引 用 , 这 样 我 们 就 可 以 在 它 里 面 给 $array[3]->{"English"} 设 置一 个 数 值 。 一 旦 这 个 动 作 完 成 , 那 么 $array[3]->{"English"} 自 动 定 义 成 一 个 数 组 引 用 ,这 样 我 们 就 可 以 给 那 个 数 组 的 第 一 个 元 素 赋 一 些 东 西 。 请 注 意 右 值 稍 微 有 些 不 同 :print$array[3]->{"English"}->[0] 只 定 义 了 $array[3] 和 $array[3]->{"English"},没 有 定 义 $array[3]->{"English"}->[0], 因 为 最 后 一 个 元 素 不 是 左 值 。( 你 可 以 认 为前 面 两 个 在 右 值 环 境 里 有 定 义 是 一 只 臭 虫 。 我 们 将 来 可 能 除 掉 这 只 虫 子 。)在 方 括 弧 或 花 括 弧 之 间 , 或 者 在 一 个 闭 方 括 弧 或 花 括 弧 与 圆 括 弧 之 间 的 箭 头 是 可 选 的 。 后 者表 示 间 接 的 函 数 调 用 。 因 此 你 可 以 把 前 面 的 代 码 缩 减 成 :$dispatch{$index}(1, 2, 3);$array[3]{"English"}[0] = "January";在 普 通 的 数 组 的 情 况 下 , 这 些 东 西 给 你 一 个 多 维 的 数 组 , 就 好 象 C 的 数 组 :$answer[$x][$y][$z] += 42;当 然 , 并 不 完 全 象 C 的 数 组 。 其 中 之 一 就 是 C 的 数 组 不 会 按 照 需 要 增 长 , 而 <strong>Perl</strong> 的 却会 。 而 且 , 一 些 在 两 种 语 言 里 相 似 的 构 造 是 用 不 同 的 方 法 分 析 的 。 在 <strong>Perl</strong> 里 , 下 面 的 两 个语 句 做 的 事 情 是 一 样 的 :$listref->[2][2] = "hello";$$listref[2][2] = "hello";# 相 当 干 净# 有 点 混 乱上 面 第 二 句 话 可 能 会 让 C 程 序 员 觉 得 诧 异 , 因 为 C 程 序 员 习 惯 于 使 用 *a[i] 表 示 “a 的第 i 个 元 素 所 指 向 的 内 容 ”。 但 是 在 <strong>Perl</strong> 里 , 五 个 元 素 ($ @ * % &) 实 际 上 比 花 括 弧或 者 方 括 弧 绑 定 得 都 更 紧 密 。( 注 : 但 不 是 因 为 操 作 符 优 先 级 。 在 <strong>Perl</strong> 里 的 趣 味 字 符 不 是操 作 符 。<strong>Perl</strong> 的 语 法 只 是 简 单 地 禁 止 任 何 比 一 个 简 单 变 量 或 者 块 更 复 杂 的 东 西 跟 在 趣 味 字符 后 面 , 个 中 缘 由 也 是 有 很 多 有 趣 的 原 因 的 。) 因 此 , 被 当 作 指 向 一 个 数 组 引 用 的 是$$listref 而 不 是 $listref[2]。 如 果 你 想 要 C 的 行 为 , 你 要 么 是 写 成 ${$listref[2]} 以强 迫 $listref[2] 先 于 前 面 的 $ 解 引 用 操 作 符 计 算 , 要 么 你 就 要 使 用 -> 表 示 法 :$listref[2]->{$greeting} = "hello";221


8.3.4 使 用 对 象 方 法如 果 一 个 引 用 碰 巧 是 一 个 指 向 一 个 对 象 的 引 用 , 那 么 定 义 该 对 象 的 类 可 能 提 供 了 访 问 该 对 象内 部 的 方 法 , 并 且 如 果 你 只 是 使 用 这 些 类 , 那 么 通 常 应 该 坚 持 使 用 那 些 方 法 ( 与 实 现 这 些 方法 相 对 )。 换 句 话 说 就 是 要 友 善 , 并 且 不 要 把 一 个 对 象 当 作 一 个 普 通 引 用 看 待 , 虽 然 在 你 必须 这 么 做 的 时 候 <strong>Perl</strong> 也 允 许 你 这 么 看 。 我 们 不 想 在 这 个 问 题 上 搞 极 权 。 但 是 我 们 的 确 希 望有 一 些 礼 貌 。有 了 这 种 礼 貌 , 你 就 在 对 象 和 数 据 结 构 之 间 获 得 了 正 交 。 在 你 需 要 的 时 候 , 任 何 数 据 结 构 都可 以 认 为 是 一 个 对 象 。 或 者 是 在 你 不 需 要 的 时 候 都 认 为 不 是 对 象 。8.3.5 伪 散 列一 个 伪 散 列 是 一 个 指 向 数 组 的 任 意 引 用 , 它 的 第 一 个 元 素 是 一 个 指 向 散 列 的 引 用 。 你 可 以 把伪 散 列 引 用 当 作 一 个 数 组 引 用 ( 如 你 所 料 ), 也 可 以 把 它 当 作 一 个 散 列 引 用 ( 出 乎 你 的 意 料 )。下 面 是 一 个 伪 散 列 的 例 子 。$john = [ {age => 1, eyes => 2, weight => 3}, 47, "brown", 186 ];在 $john->[0] 下 面 的 散 列 定 义 了 随 后 的 数 组 元 素 (47, "brown", 186) 的 名 字 ("age","eyes" , "weight")。 现 在 你 可 以 用 散 列 和 数 组 的 表 示 法 来 访 问 一 个 元 素 :$john->{weight}$john->[3]# 把 $john 当 作 一 个 hashref 对 待# 把 $john 当 作 一 个 arrayref 对 待伪 散 列 的 魔 术 并 不 那 么 神 奇 ; 它 只 知 道 一 个 “ 技 巧 ”: 如 何 把 一 个 散 列 解 引 用 转 变 成 一 个 数 组解 引 用 。 在 向 伪 散 列 里 增 加 其 他 元 素 的 时 候 , 在 你 使 用 散 列 表 示 法 之 前 , 必 须 明 确 告 诉 下 层的 散 列 那 些 元 素 将 放 在 哪 里 :$john->[0]{height} = 4;$john->{height} = "tall";# 高 度 是 元 素 4 的 数 值# 或 者 $john->[4] = "tall"如 果 你 试 图 从 一 个 伪 散 列 中 删 除 一 个 键 字 , 那 么 <strong>Perl</strong> 将 抛 出 一 个 例 外 , 尽 管 你 总 是 可 以 从映 射 散 列 中 删 除 键 字 。 如 果 你 试 图 访 问 一 个 不 存 在 的 键 字 , 那 么 <strong>Perl</strong> 也 会 抛 出 一 个 例 外 ,这 里 “ 存 在 ” 的 意 思 是 在 映 射 散 列 里 出 现 :delete $john->[9]{height};# 只 从 下 层 散 列 中 删 除$john->{height};# 现 在 抛 出 一 个 例 外222


$john->[4];# 仍 然 打 印 "tall"除 非 你 很 清 楚 自 己 在 干 什 么 , 否 则 不 要 把 数 组 分 成 片 段 。 如 果 数 组 元 素 的 位 置 移 动 了 , 那 么映 射 散 列 仍 然 指 向 原 来 的 元 素 位 置 , 除 非 你 同 时 也 明 确 改 变 它 们 。 伪 散 列 的 道 行 并 不 深 。要 避 免 不 一 致 性 , 你 可 以 使 用 use fields 用 法 提 供 的 fields::phash 函 数 创 建 伪 散 列 :use fields;$ph = fields::phash(age => 47, eyes => "brown", weight => 186);print $ph->(age);有 两 个 方 法 可 以 检 查 一 个 键 字 在 伪 散 列 里 面 是 否 存 在 。 第 一 个 方 法 是 使 用 exists, 它 检 查给 出 的 字 段 是 否 已 经 设 置 过 了 。 它 用 这 种 办 法 来 对 应 一 个 真 正 的 散 列 的 行 为 。 比 如 :use fields;$ph = fields::phash([qw(age eyes brown)], [47]);$ph->{eyes} = undef;print exists $ph->{age};print exists $ph->{weight};print exists $ph->{eyes};# 对 ,'age' 在 声 明 中 就 设 置 了# 错 ,'weight' 还 没 有 用 呢# 对 , 你 的 'eyes' 已 经 修 改 过 了第 二 种 方 法 是 在 第 一 个 数 组 元 素 里 放 着 的 影 射 散 列 上 使 用 exists。 这 样 就 检 查 了 给 出 的 键字 对 该 伪 散 列 是 否 是 一 个 有 效 的 字 段 :print exists $ph->[0]{age};print exists $ph->[0]{name};# 对 ,'age' 是 一 个 有 效 的 字 段# 错 , 不 能 使 用 'name'和 真 正 的 散 列 里 发 生 的 事 情 有 些 不 同 , 在 伪 散 列 元 素 上 调 用 delete 的 时 候 只 删 除 对 应 该键 字 的 数 组 值 , 而 不 是 映 射 散 列 的 真 正 的 键 字 。 要 删 除 该 键 字 , 你 必 须 明 确 地 从 映 射 散 列 中删 除 之 。 一 旦 你 删 除 了 该 键 字 , 那 么 你 就 不 再 能 用 该 名 字 作 为 伪 散 列 的 脚 标 :print delete $ph->{age}; # 删 除 并 返 回 $ph->[1], 47print exists $ph->{age};print exists $ph->[0]{age};# 现 在 是 错 的# 对 ,'age' 键 字 仍 然 可 用223


print delete $ph->[0]{age};# 现 在 'age' 键 字 没 了print $ph->{age};# 运 行 时 例 外你 可 能 会 想 知 道 是 因 为 什 么 原 因 促 使 人 们 想 出 这 种 伪 装 在 散 列 的 外 衣 下 面 的 数 组 的 。 数 组 的查 找 速 度 比 较 快 并 且 存 储 效 率 也 高 些 , 而 散 列 提 供 了 对 你 的 数 据 命 名 的 方 便 ( 而 不 是 编 号 );伪 散 列 提 供 了 两 种 数 据 结 构 的 优 点 。 但 是 这 些 巨 大 的 优 点 只 有 在 你 开 始 考 虑 <strong>Perl</strong> 的 编 译 阶段 的 时 候 才 会 出 现 。 在 一 两 个 用 法 的 帮 助 下 , 编 译 器 可 以 核 实 对 有 效 的 数 据 域 的 访 问 , 这 样你 就 可 以 在 程 序 开 始 运 行 之 前 发 现 不 存 在 的 脚 标 ( 可 以 查 找 拼 写 错 误 )。伪 散 列 的 速 度 , 效 率 , 和 编 译 时 访 问 检 查 ( 你 甚 至 可 以 把 这 个 特 性 看 作 一 种 安 全 性 ) 的 属 性令 它 成 为 创 建 高 效 健 壮 的 类 方 法 的 非 常 便 利 的 工 具 。 参 阅 第 十 二 章 里 的 use fields 以 及 第三 十 一 章 , 实 用 模 块 里 的 讨 论 。伪 散 列 是 一 种 比 较 新 的 并 且 相 对 试 验 性 的 特 性 ; 因 此 , 其 下 的 实 现 在 将 来 很 有 可 能 被 修 改 。要 保 护 自 己 免 于 受 到 这 样 的 修 改 的 影 响 , 你 应 该 总 是 使 用 fields 里 面 有 文 档 记 录 的phash 和 new 函 数 。8.3.6 硬 引 用 可 以 用 的 其 他 技 巧我 们 前 面 提 到 过 , 反 斜 杠 操 作 符 通 常 用 于 一 个 引 用 中 生 成 一 个 引 用 , 但 是 并 非 必 须 如 此 。 如果 和 一 列 引 用 物 一 起 使 用 , 那 么 它 生 成 一 列 对 应 的 引 用 。 下 面 的 例 子 的 第 二 行 和 第 一 行 做 的事 情 是 一 样 的 , 因 为 反 斜 杠 是 自 动 在 整 个 列 表 中 分 布 的 。@reflist = (\$s, \@a, \%h, \&f);@reflist = \($s, @a, %h, &f);# 一 列 四 个 元 素 的 表# 同 样 的 东 西如 果 一 个 圆 括 弧 列 表 只 包 含 一 个 数 组 或 者 散 列 , 那 么 所 有 它 的 数 值 都 被 代 换 , 并 且 返 回 每 个引 用 :@reflist = \(@x);@reflist = map (\$_) @x;# 代 换 数 组 , 然 后 获 得 引 用# 一 样 的 东 西如 果 你 在 散 列 上 试 验 这 些 代 码 , 那 么 结 果 将 包 含 指 向 数 值 的 引 用 ( 如 你 所 料 ), 而 且 还 包 含那 些 键 字 的 拷 贝 的 引 用 ( 这 可 是 你 始 料 未 及 的 )。因 为 数 组 和 散 列 片 段 实 际 上 都 是 列 表 , 因 此 你 可 以 用 反 斜 杠 逃 逸 两 者 中 的 任 意 一 个 获 取 一 个引 用 的 列 表 。 下 面 三 行 中 的 任 何 一 个 实 际 上 做 的 都 是 完 全 一 样 的 事 情 :@envrefs = \@ENV{'HOME', 'TERM'};# 反 斜 杠 处 理 一 个 片 段224


@envrefs = \($ENV{HOME}, $ENV{TERM} );@envrefs = ( \$ENV{HOME}, \$ENV{TERM} );# 反 斜 杠 处 理 一 个 列 表# 一 个 两 个 引 用 的 列 表因 为 函 数 可 以 返 回 列 表 , 所 以 你 可 以 给 它 们 加 上 反 斜 杠 。 如 果 你 有 超 过 一 个 的 函 数 需 要 调 用 ,那 么 首 先 把 每 个 函 数 的 返 回 值 代 换 到 一 个 大 的 列 表 中 , 然 后 在 给 整 个 列 表 加 上 反 斜 杠 :@reflist = \fx();@reflist = map { \$_ } fx();# 一 样 的 东 西@reflist = \( fx(), fy(), fz() );@reflist = ( \fx(), \fy(), fz() );@reflist = map { \$_ } fx(), fy(), fz();# 一 样 的 东 西# 一 样 的 东 西反 斜 杠 操 作 符 总 是 给 它 的 操 作 数 提 供 一 个 列 表 环 境 , 因 此 那 些 函 数 都 是 在 列 表 环 境 中 调 用 。如 果 反 斜 杠 本 身 是 处 于 标 量 环 境 , 那 么 你 最 终 会 得 到 一 个 指 向 该 函 数 返 回 的 列 表 中 的 最 后 一个 数 值 的 引 用 :@reflist = \localtime();@lastref = \localtime();# 引 用 九 个 时 间 元 素 中 的 每 一 个# 引 用 的 是 它 是 否 为 夏 时 制从 这 个 方 面 来 看 , 反 斜 杠 的 行 为 类 似 命 名 的 <strong>Perl</strong> 列 表 操 作 符 , 比 如 print,reverse 和sort, 它 们 总 是 在 它 们 的 右 边 提 供 一 个 列 表 环 境 , 而 不 管 它 们 左 边 是 什 么 东 西 。 和 命 名 的 列表 操 作 符 一 样 , 使 用 明 确 的 scalar 强 迫 跟 在 后 面 的 进 入 标 量 环 境 :$dateref = \scalar localtime(); # \"Thu Apr 19 22:02:18 2001"你 可 以 使 用 ref 操 作 符 来 判 断 一 个 引 用 指 向 的 是 什 么 东 西 。 把 ref 想 象 成 一 个 “typeof”( 类 型 为 ) 操 作 符 , 如 果 它 的 参 数 是 一 个 引 用 那 么 返 回 真 , 否 则 返 回 假 。 返 回 的 数 值 取 决 于所 引 用 的 东 西 的 类 型 。 内 建 的 类 型 包 括 SCALAR,ARRAY,HASH,CODE,GLOB,REF,LVALUE,IO,IO::Handle, 和 Regexp。 在 下 面 , 我 们 用 它 检 查 子 过 程 参 数 :sub sum {my $arrayref = shift;warn "Not an array reference" if ref($arrayref) ne "ARRAY";return eval join("+", @$arrayref);225


}如 果 你 在 一 个 字 串 环 境 中 使 用 硬 引 用 , 那 么 它 将 被 转 换 成 一 个 包 含 类 型 和 地 址 的 字 串 :SCALAR(0x12fcde)。( 反 向 的 转 换 是 不 能 实 现 的 , 因 为 在 字 串 化 过 程 中 , 引 用 计 数 信 息将 被 丢 失 —— 而 且 让 程 序 可 以 访 问 一 个 由 一 个 随 机 字 串 命 名 的 地 址 也 太 危 险 了 。)你 可 以 用 bless 操 作 符 把 一 个 引 用 和 一 个 包 函 数 关 联 起 来 作 为 一 个 对 象 类 。 在 你 做 这 些 的时 候 ,ref 返 回 类 名 字 而 不 是 内 部 的 类 型 。 在 字 串 环 境 里 使 用 的 对 象 引 用 返 回 带 着 内 部 和 外部 类 型 的 字 串 , 以 及 在 内 存 中 的 地 址 :MyType=HASH(0x21cda) 或 者IO::Handle=IO(0x186904)。 参 阅 第 十 二 章 获 取 有 关 对 象 的 更 多 的 细 节 。因 为 你 对 某 些 东 西 解 引 用 的 方 法 总 是 表 示 着 你 在 寻 找 哪 种 引 用 物 , 一 个 类 型 团 可 以 用 与 引 用一 样 的 方 法 来 使 用 , 尽 管 一 个 类 型 团 包 含 各 种 类 型 的 多 个 引 用 。 因 此 ${*main::foo} 和${\$main::foo} 访 问 的 都 是 同 一 个 标 量 变 量 , 不 过 后 者 更 高 效 一 些 。下 面 是 一 个 把 子 过 程 调 用 的 返 回 值 代 换 成 一 个 字 串 的 技 巧 :print "My sub returned @{[ mysub(1, 2, 3) ]} that time.\n";它 的 运 转 过 程 如 下 。 在 编 译 时 , 当 编 译 器 看 到 双 引 号 字 串 里 的 @{...} 的 时 候 , 它 就 会 被当 作 一 个 返 回 一 个 引 用 的 块 分 析 。 在 块 里 面 , 方 括 弧 创 建 一 个 指 向 一 个 匿 名 数 组 的 引 用 , 该数 组 来 自 方 括 弧 里 面 的 东 西 。 因 此 在 运 行 时 ,mysub(1,2,3) 会 在 列 表 环 境 中 调 用 , 然 后其 结 果 装 载 到 一 个 匿 名 数 组 里 , 然 后 在 块 里 面 返 回 一 个 指 向 该 匿 名 数 组 的 引 用 。 然 后 该 数 组引 用 马 上 就 被 周 围 的 @{...} 解 引 用 , 而 其 数 组 的 值 就 被 代 换 到 双 引 号 字 串 中 , 就 好 象 一个 普 通 的 数 组 做 的 那 样 。 这 样 的 强 词 夺 理 式 的 解 释 也 适 用 于 任 意 表 达 式 , 比 如 :print "We need @{ [$n + 5] } widgets!\n";不 过 要 小 心 的 是 : 方 括 弧 给 它 们 的 表 达 式 提 供 了 一 个 列 表 环 境 。 在 本 例 中 它 并 不 在 意 是 否 为列 表 环 境 , 不 过 前 面 对 mysub 的 调 用 可 能 会 介 意 。 如 果 列 表 环 境 有 影 响 的 时 候 , 你 可 以使 用 一 个 明 确 的 scalar 以 强 迫 环 境 为 标 量 :print "Mysub return @{ [scalar mysub(1,2,3)] } now.\n";8.3.7 闭 合 ( 闭 包 )我 们 早 些 时 候 谈 到 过 用 一 个 没 有 名 字 的 sub {} 创 建 匿 名 子 过 程 。 你 可 以 把 那 些 子 过 程 看作 是 在 运 行 时 定 义 , 这 就 意 味 着 它 们 有 一 个 生 成 的 时 间 和 一 个 定 义 的 地 点 。 在 创 建 子 过 程 的时 候 , 有 些 变 量 可 能 在 范 围 里 , 而 调 用 子 过 程 的 时 候 , 可 能 有 不 同 的 变 量 在 范 围 里 。先 让 我 们 暂 时 忘 掉 子 过 程 , 先 看 看 一 个 指 向 一 个 词 法 变 量 的 引 用 :226


{my $critter = "camel";$critterref = \$critter;}$$critterref 的 数 值 仍 将 是 “camel”, 即 使 在 离 开 闭 合 的 花 括 弧 之 后 $critter 消 失 了 也 如此 。 但 是 $critterref 也 可 以 指 向 一 个 指 向 了 $critter 的 子 过 程 :{my $critter = "camel";$critterref = sub { return $critter };}这 是 一 个 闭 合 ( 闭 包 ), 这 个 词 是 来 自 LISP 和 Scheme 那 些 机 能 性 (functional) 编程 世 界 的 。( 注 : 在 这 样 的 语 言 环 境 里 ,“functional”( 机 能 性 ) 应 该 看 作 是 “dysfunctional”( 机 能 紊 乱 ) 的 反 义 词 )。 这 就 意 味 着 如 果 你 某 一 时 刻 在 特 定 的 词 法 范 围 定 义 了 一 个 匿 名 函数 , 那 么 它 就 假 装 自 己 是 在 那 个 范 围 里 运 行 的 , 即 使 后 面 它 又 从 该 范 围 之 外 调 用 。( 一 个 力求 正 统 的 人 会 说 你 用 不 着 使 用 “ 假 装 ” 这 个 词 —— 它 实 际 上 就 是 运 行 在 该 范 围 里 。)换 句 话 说 ,<strong>Perl</strong> 保 证 你 每 次 都 获 得 同 一 套 词 法 范 围 变 量 的 拷 贝 , 即 使 该 词 法 变 量 的 其 他 实例 在 该 闭 合 的 实 例 之 前 或 者 自 该 闭 合 存 在 开 始 又 创 建 其 他 实 例 也 如 此 。 这 样 就 给 你 一 个 方 法让 你 可 以 在 定 义 子 过 程 的 时 候 设 置 子 过 程 里 的 数 值 , 而 不 仅 仅 是 在 调 用 它 们 的 时 候 。你 还 可 以 把 闭 合 看 作 是 一 个 不 用 eval 书 写 子 过 程 模 板 的 方 法 。 这 时 候 词 法 变 量 用 做 填 充模 板 的 参 数 , 作 用 主 要 是 设 置 很 少 的 一 些 代 码 用 于 稍 后 运 行 。 在 基 于 事 件 的 编 程 里 面 , 这 种做 法 常 常 叫 做 回 调 (callback), 比 如 你 把 一 些 代 码 和 一 次 键 盘 敲 击 , 鼠 标 点 击 , 窗 口 露 出等 等 关 联 起 来 。 如 果 当 作 回 调 使 用 , 闭 合 做 的 就 是 你 所 预 期 的 , 即 使 你 不 知 道 机 能 性 编 程 的第 一 件 事 情 也 无 妨 。( 请 注 意 这 些 与 闭 合 相 关 的 事 情 只 适 用 于 my 变 量 。 全 局 量 还 是 和 往常 一 样 运 转 , 因 为 它 们 不 是 按 照 词 法 变 量 的 方 式 创 建 和 删 除 的 。)闭 合 的 另 外 一 个 用 途 就 是 函 数 生 成 器 , 也 就 是 说 , 创 建 和 返 回 全 新 函 数 的 函 数 。 下 面 是 一 个用 闭 合 实 现 的 函 数 生 成 器 的 例 子 :sub make_saying {my $salute = shift;227


my $newfunc = sub {my $target = shift;print "$salute, $target!\n";};return $newfunc;# 返 回 一 个 闭 合}$f = make_saying("Howdy"); # 创 建 一 个 闭 合$g = make_saying("Greetings"); # 创 建 另 外 一 个 闭 合# 到 时 ...$f->("world");$g->("earthings");它 打 印 出 :Howdy, world!Greetings earthlings!特 别 要 注 意 $salute 是 如 何 继 续 指 向 实 际 传 递 到 make_saying 里 的 数 值 的 , 尽 管 到 该匿 名 子 过 程 运 行 的 时 候 my $salute 已 经 超 出 了 范 围 。 这 就 是 用 闭 合 来 干 的 事 情 。 因 为 $f和 $g 保 存 着 指 向 函 数 的 引 用 , 当 调 用 它 们 的 时 候 , 这 些 函 数 仍 然 需 要 访 问 独 立 的 $salute版 本 , 因 此 那 些 变 量 版 本 自 动 附 着 在 四 周 。 如 果 你 现 在 覆 盖 $f, 那 么 它 的 版 本 的 $salute将 自 动 消 失 。(<strong>Perl</strong> 只 是 在 你 不 再 查 看 的 时 候 才 做 清 理 。)<strong>Perl</strong> 并 不 给 对 象 方 法 ( 在 第 十 二 章 描 述 ) 提 供 引 用 , 但 是 你 可 以 使 用 闭 合 获 取 类 似 的 效 果 。假 设 你 需 要 这 么 一 个 引 用 : 它 不 仅 仅 指 向 他 代 表 的 方 法 的 子 过 程 , 而 且 它 在 调 用 的 时 候 , 还会 在 特 定 的 对 象 上 调 用 该 方 法 。 你 可 以 很 方 便 地 把 对 象 和 方 法 都 看 成 封 装 在 闭 合 中 的 词 法 变量 :228


sub get_method {my ($self, $methodname) = @_;my $methref = sub {# 下 面 的 @_ 和 上 面 的 那 个 不 一 样 !return $self->$methodname(@_);};return $methref;}my $dog = new Doggie::Name => "Lucky",Legs => 3,Tail => "clipped";our $wagger = get_method_ref($dog, 'wag');$wagger->("tail");# 调 用 $dog->wag('tail').现 在 , 你 不 仅 可 以 让 Lucky 摇 动 尾 巴 上 的 任 何 东 西 , 就 算 词 法 $dog 变 量 已 经 跑 出 了 范围 并 且 现 在 你 看 不 到 Lucky 了 , 那 么 全 局 的 $wagger 变 量 仍 然 会 让 它 摇 尾 巴 —— 不 管它 在 哪 里 。8.3.7.1 用 闭 合 做 函 数 模 板拿 闭 合 做 函 数 模 板 可 以 让 你 生 成 许 多 动 作 类 似 的 函 数 。 比 如 你 需 要 一 套 函 数 来 生 成 各 种 不 同颜 色 的 HTML 字 体 变 化 :print "Be", red("careful"), "with that ", green("light), "!!!";red 和 green 函 数 会 非 常 类 似 。 我 们 已 经 习 惯 给 我 们 的 函 数 名 字 , 但 是 闭 合 没 有 名 字 , 因为 它 们 只 是 带 倾 向 性 的 匿 名 子 过 程 。 为 了 绕 开 这 个 问 题 , 我 们 将 使 用 给 我 们 的 匿 名 子 过 程 命229


名 的 非 常 技 巧 性 的 方 法 。 你 可 以 把 一 个 coderef 绑 定 到 一 个 现 存 的 名 字 上 , 方 法 是 把 它 赋予 一 个 类 型 团 , 该 类 型 团 的 名 字 就 是 你 想 要 的 函 数 。( 参 阅 第 十 章 , 包 , 里 的 “ 符 号 表 ” 一 节 。在 本 例 中 , 我 们 将 把 它 绑 定 到 两 个 不 同 的 名 字 上 , 一 个 是 大 写 , 一 个 是 小 写 。@colors = qw(red blue green yellow orange purple wiolet);for my $name (@colors) {no strict 'refs';# 允 许 符 号 引 用*$name = *(uc $name) = sub { "


local *inner = sub { return $x * 19};return $x + inner();}因 为 闭 合 的 临 时 赋 值 , 现 在 inner 只 能 从 outer 里 面 调 用 。 但 只 要 你 是 在 outer 里 调 用它 的 , 那 么 它 就 能 从 outer 的 范 围 里 对 词 法 变 量 $x 的 正 常 访 问 。这 么 做 对 于 创 建 一 个 相 对 另 外 一 个 函 数 是 局 部 函 数 的 时 候 有 一 些 有 趣 的 效 果 , 这 些 效 果 不 是<strong>Perl</strong> 正 常 时 支 持 的 。 因 为 local 是 动 态 范 围 , 并 且 函 数 名 字 对 于 它 们 的 包 来 说 是 全 局 的 ,那 么 任 何 其 他 outer 调 用 的 函 数 也 可 以 调 用 inner 的 临 时 版 本 。 要 避 免 这 些 , 你 应 该 需要 额 外 的 间 接 的 层 次 :sub outer {my $x = $_[0] + 35;my $inner = sub {return $x * 19 };return $x + $inner->();}8.4 符 号 引 用如 果 你 试 图 给 一 个 不 是 硬 引 用 的 数 值 进 行 解 引 用 会 发 生 什 么 事 情 ? 那 么 该 值 就 会 被 当 作 一个 符 号 引 用 。 也 就 是 说 , 该 引 用 被 解 释 成 一 个 代 表 某 个 全 局 量 的 名 字 的 字 串 。下 面 是 其 运 转 样 例 :$name = "bam";$$name = 1;$name->[0] = 4;$name->{X} = "Y";@$name = ();keys %$name;# 设 置 $bam# 设 置 @bam 的 第 一 个 元 素# 设 置 %bam 的 X 元 素 为 Y# 清 除 @bam# 生 成 %bam 的 键 字&$name;# 调 用 &bam231


这 么 用 的 功 能 是 非 常 强 大 的 , 并 且 有 点 危 险 , 因 为 我 们 有 可 能 原 来 想 用 的 是 ( 有 着 最 好 的 安全 性 ) 硬 引 用 , 但 是 却 不 小 心 用 了 一 个 符 号 引 用 。 为 了 防 止 出 现 这 个 问 题 , 你 可 以 说 :use strict 'refs';然 后 在 闭 合 块 的 剩 余 部 分 , 你 就 只 被 允 许 使 用 硬 引 用 。 更 内 层 的 块 可 以 用 下 面 的 语 句 撤 消 这样 的 命 令 :no strict 'refs';另 外 , 我 们 还 必 须 理 解 下 面 两 行 程 序 的 区 别 :${identifier};# 和 $identifier 一 样${"identifier"}; # 也 是 $identifier, 不 过 却 是 一 个 符 号 引 用 。因 为 第 二 种 形 式 是 引 号 引 起 的 , 所 以 它 被 当 作 一 个 符 号 引 用 , 如 果 这 时 候 use strict 'refs'起 作 用 的 话 它 将 会 生 成 一 个 错 误 。 就 算 strict 'refs' 没 有 起 作 用 , 它 也 只 能 指 向 一 个 包 变量 。 但 是 第 一 种 形 式 是 和 没 有 花 括 弧 的 形 式 相 等 的 , 它 甚 至 可 以 指 向 一 个 词 法 范 围 的 变 量 ,只 要 你 定 义 了 这 么 一 个 。 下 面 的 例 子 显 示 了 这 个 用 法 ( 而 且 下 一 节 就 是 讨 论 这 个 问 题 的 )。只 有 包 变 量 可 以 通 过 符 号 引 用 访 问 , 因 为 符 号 引 用 总 是 查 找 包 的 符 号 表 。 由 于 词 法 变 量 不 是在 包 的 符 号 表 里 面 , 因 此 它 们 对 这 种 机 制 是 隐 形 的 。 比 如 :our $value = "global";{my $value = "private";print "Inside, mine is ${value},";print "but ours is ${'value'}.\n";}print "Outside, ${value} is again ${'value'}.\n";它 打 印 出 :Inside, mine is private, but ours is global.Outside, global is again global.8.5 花 括 弧 , 方 括 弧 和 引 号232


在 前 面 一 节 里 , 我 们 指 出 了 ${identifier} 是 不 被 当 作 符 号 引 用 看 待 的 。 你 可 能 会 想 知 道这 个 机 制 是 怎 么 和 保 留 字 相 交 互 的 , 简 单 的 回 答 是 : 它 们 没 有 相 互 交 互 。 尽 管 push 是 一个 保 留 字 , 下 面 这 两 句 还 是 打 印 "pop on over":$push = "pop on ";print "${push}over";这 里 的 原 因 是 这 样 的 , 历 史 上 ,Unix shell 是 用 花 括 弧 来 隔 离 变 量 名 字 和 后 继 的 字 母 数 字文 本 的 ( 要 不 然 这 些 字 母 数 字 就 成 了 变 量 名 字 的 一 部 分 。) 这 也 是 许 多 人 预 料 的 变 量 代 换 的运 转 方 式 , 因 此 我 们 在 <strong>Perl</strong> 令 之 以 同 样 的 方 法 运 转 。 但 是 就 <strong>Perl</strong> 而 言 , 这 种 表 示 法 的 使用 得 到 了 扩 展 , 它 可 以 用 于 任 意 用 来 生 成 引 用 的 花 括 弧 , 不 管 它 们 是 否 位 于 引 号 里 面 。 这 就意 味 着 :print ${push} . 'over';或 者 甚 至 是 下 面 ( 因 为 空 格 无 所 谓 ):print ${ push } . 'over';都 打 印 出 "pop on over", 尽 管 花 括 弧 在 双 引 号 外 边 。 同 样 的 规 则 适 用 于 任 意 给 散 列 做 脚标 的 标 识 符 。 因 此 , 我 们 可 以 不 用 写 下 面 这 样 的 句 子 :$hash{ "aaa" }{ "bbb" }{ "ccc" }你 可 以 只 写 :$hash{ aaa}{ bbb }{ ccc }或 者$hash{aaa}{bbb}{ccc}而 不 用 担 心 这 些 脚 标 是 否 为 保 留 字 。 因 此 :$hash{ shift }被 代 换 成 $hash{"shift"}。 你 可 以 迫 使 代 换 当 作 保 留 字 来 用 , 方 法 是 增 加 任 何 可 以 令 这 个名 字 不 仅 仅 是 一 个 标 识 符 的 东 西 :$hash{ shift() }$hash{ +shift }233


$hash{ shift @_ }8.5.1 引 用 不 能 当 作 散 列 键 字 用散 列 键 字 在 内 部 都 存 储 成 字 串 。( 注 : 它 们 在 外 部 也 存 储 成 字 串 , 比 如 在 你 把 它 们 放 到 DBM文 件 中 的 时 候 。 实 际 上 ,DBM 文 件 要 求 它 们 的 键 字 ( 和 数 值 ) 是 字 串 。) 如 果 你 试 图 把 一个 引 用 当 作 一 个 散 列 的 键 字 存 储 , 那 么 该 键 字 值 将 被 转 换 成 一 个 字 串 :$x{ \$a } = $a;($key, $value) = each %x;print $$key;# 错 误我 们 前 面 已 经 说 过 你 不 能 把 一 个 字 串 转 换 回 硬 引 用 。 因 此 如 果 你 试 图 解 引 用 $key,( 它 里面 只 保 存 着 一 个 字 串 ), 那 么 它 不 会 返 回 一 个 硬 引 用 , 而 是 一 个 符 号 引 用 —— 并 且 因 为 你 可能 没 有 叫 SCALAR(0x1fc0e) 的 变 量 , 所 以 你 就 无 法 实 现 你 的 目 的 。 你 可 能 要 做 一 些 更 象 :$r = \@a;$x{ $r } = $r;这 样 的 东 西 。 这 样 你 至 少 能 使 用 散 列 值 , 这 个 值 是 一 个 硬 引 用 , 但 你 不 能 用 键 字 , 它 不 是 硬引 用 。尽 管 你 无 法 把 一 个 引 用 存 储 为 键 字 , 但 是 如 果 你 拿 一 个 硬 引 用 在 一 个 字 串 的 环 境 中 使 用 ,( 象我 们 前 面 的 例 子 ) 那 么 <strong>Perl</strong> 保 证 它 生 成 一 个 唯 一 的 字 串 , 因 为 该 引 用 的 地 址 被 当 作 字 串 的一 部 分 包 含 。 这 样 你 实 际 上 就 可 以 把 引 用 当 作 一 个 唯 一 的 键 字 使 用 。 只 是 你 后 面 就 没 有 办 法对 它 解 引 用 了 。有 一 种 特 殊 类 型 的 散 列 , 在 这 种 散 列 里 , 你 可 以 拿 引 用 当 作 键 字 。 通 过 与 <strong>Perl</strong> 捆 绑 在 一 起的 Tie::RefHasn 模 块 的 神 奇 ( 这 个 词 是 技 术 术 语 , 如 果 你 翻 翻 <strong>Perl</strong> 源 程 序 目 录 里 的mg.c 文 件 就 会 明 白 。), 你 就 可 以 做 我 们 刚 才 还 说 不 能 做 的 事 情 :use Tie::RefHandle;tie my %h, 'Tie::RefHash';%h = (["this", "here"]["that", "there"]=> "at home",=> "elsewhere",234


);while ( my($keyref, $value) = each %h ) {print "@$keyref is $value\n";}实 际 上 , 通 过 将 不 同 的 实 现 与 内 建 类 型 捆 绑 , 你 可 以 把 标 量 , 散 列 , 和 数 组 制 作 成 可 以 拥 有我 们 上 面 说 过 不 行 的 行 为 。 你 看 , 这 些 作 者 就 那 么 傻 ...有 关 捆 绑 的 更 多 细 节 , 参 阅 第 十 四 章 , 捆 绑 变 量 。8.5.2 垃 圾 收 集 , 循 环 引 用 和 弱 引 用高 级 语 言 通 常 都 允 许 程 序 员 不 用 担 心 在 用 过 内 存 之 后 的 释 放 问 题 。 这 种 自 动 化 收 割 处 理 就 叫做 垃 圾 收 集 。 就 大 多 数 情 况 而 言 ,<strong>Perl</strong> 使 用 一 种 快 速 并 且 简 单 的 以 引 用 为 基 础 的 垃 圾 收 集器 。如 果 一 个 块 退 出 了 , 那 么 它 的 局 部 范 围 的 变 量 通 常 都 释 放 掉 , 但 是 你 也 有 可 能 把 你 的 垃 圾 藏起 来 , 因 此 <strong>Perl</strong> 的 垃 圾 收 集 器 就 找 不 到 它 们 了 。 一 个 严 重 的 问 题 是 如 果 一 片 不 可 访 问 的 内存 的 引 用 记 数 为 零 的 话 , 那 么 它 将 得 不 到 释 放 。 因 此 , 循 环 引 用 是 一 个 非 常 糟 糕 的 主 意 :{ # 令 $a 和 $b 指 向 对 方my ($a, $b);$a = \$b;$b = \$a;}或 者 更 简 单 的 就 是 :{ # 令 $a 指 向 它 自 己my $a;$a = \$a;}235


即 使 $a 应 该 在 块 的 结 尾 释 放 , 但 它 实 际 上 却 没 有 。 在 制 作 递 归 数 据 结 构 的 时 候 , 如 果 你想 在 程 序 退 出 之 前 回 收 内 存 , 那 么 你 不 得 不 自 己 打 破 ( 或 者 弱 化 , 见 下 文 ) 这 样 的 自 引 用 。( 退 出 后 , 这 些 内 存 将 通 过 一 个 开 销 比 较 大 但 是 完 整 的 标 记 和 打 扫 垃 圾 收 集 。) 如 果 数 据 结构 是 一 个 对 象 , 你 可 以 可 以 用 DESTROY 方 法 自 动 打 破 引 用 ; 参 阅 第 十 二 章 的 “ 用DESTROY 方 法 进 行 垃 圾 收 集 ”。一 个 类 似 的 情 况 可 能 出 现 在 缓 冲 中 —— 缓 冲 是 存 放 数 据 的 仓 库 , 用 于 以 更 快 的 速 度 进 行 检 索采 用 的 方 法 。 在 缓 冲 之 外 , 有 指 向 缓 冲 内 部 的 引 用 。 问 题 发 生 在 所 有 这 些 引 用 都 被 删 除 的 时候 , 但 是 缓 冲 数 据 和 它 的 内 部 引 用 仍 然 存 在 。 任 何 引 用 的 存 在 都 会 阻 止 <strong>Perl</strong> 回 收 引 用 物 ,即 使 我 们 希 望 缓 冲 数 据 在 我 们 不 再 需 要 的 时 候 尽 快 消 失 也 是 如 此 。 对 于 循 环 引 用 , 我 们 需 要一 个 不 影 响 引 用 计 数 的 引 用 , 因 而 就 不 会 推 迟 垃 圾 收 集 了 。弱 引 用 解 决 了 循 环 引 用 和 缓 冲 数 据 造 成 的 问 题 , 它 允 许 你 “ 弱 化 ” 任 意 引 用 ; 也 就 是 说 , 让 它不 受 引 用 计 数 的 影 响 。 当 最 后 一 个 指 向 对 象 的 未 弱 化 引 用 被 删 除 之 后 , 该 对 象 就 被 删 除 并 且所 有 指 向 改 对 象 的 弱 引 用 都 被 释 放 。要 使 用 这 个 特 性 , 你 需 要 来 自 CPAN 的 WeakRef ?验 性 特 性 。 不 过 , 有 些 人 就 是 特 别 能 钻 。包 , 它 包 含 附 加 的 文 档 。 弱 引 用 是 试第 九 章 数 据 结 构<strong>Perl</strong> 免 费 提 供 许 多 数 据 结 构 , 这 些 数 据 结 构 在 其 他 编 程 语 言 里 是 需 要 你 自 己 制 作 的 。 比 如那 些 计 算 机 科 学 的 新 芽 们 都 需 要 学 习 的 堆 栈 和 队 列 在 <strong>Perl</strong> 里 都 只 是 数 组 。 在 你 push 和pop( 或 者 shift 和 unshift) 一 个 数 组 的 时 候 , 它 就 是 一 个 堆 栈 ; 在 你 push 和 shift( 或 者 unshift 和 pop) 一 个 数 组 的 时 候 , 它 就 是 一 个 队 列 。 并 且 世 界 上 有 许 多 树 结 构 的作 用 只 是 为 了 给 一 些 概 念 上 是 平 面 的 搜 索 表 文 件 提 供 快 速 动 态 的 访 问 。 当 然 , 散 列 是 内 建 于<strong>Perl</strong> 的 , 可 以 给 概 念 上 是 平 面 的 搜 索 表 提 供 快 速 动 态 的 访 问 , 只 有 对 编 号 不 敏 感 的 递 归 数据 结 构 才 会 被 那 些 脑 袋 已 经 相 当 程 度 编 了 号 的 人 称 为 美 人 。但 是 有 时 候 你 需 要 嵌 套 的 数 据 结 构 , 因 为 这 样 的 数 据 结 构 可 以 更 自 然 地 给 你 要 解 决 的 问 题 建模 。 因 为 <strong>Perl</strong> 允 许 你 组 合 和 嵌 套 数 组 和 散 列 以 创 建 任 意 复 杂 的 数 据 结 构 。 经 过 合 理 地 组 合 ,它 们 可 以 用 来 创 建 链 表 , 二 叉 树 , 堆 ,B-tree( 平 衡 树 ), 集 , 图 和 任 何 你 设 计 的 东 西 。参 阅 Mastering Algorithms with <strong>Perl</strong>(O'Reilly, 1999),<strong>Perl</strong> Cookbook(O'Reilly236


1998), 或 者 CPAN—— 所 有 这 些 模 块 的 中 心 仓 库 。 不 过 , 你 需 要 的 所 有 东 西 可 能 就 是 简单 地 组 合 数 组 和 散 列 , 所 以 我 们 就 在 本 章 介 绍 这 些 内 容 。9.1 数 组 的 数 组有 许 多 种 类 型 的 嵌 套 数 据 结 构 。 最 容 易 做 的 是 制 作 一 个 数 组 的 数 组 , 也 叫 做 两 维 数 组 或 者 矩阵 。( 明 显 的 总 结 是 : 一 个 数 组 的 数 组 的 数 组 就 是 一 个 三 维 数 组 , 对 于 更 高 的 维 数 以 此 类 推 。)多 维 数 组 比 较 容 易 理 解 , 并 且 几 乎 所 有 它 适 用 的 东 西 都 适 用 于 我 们 在 随 后 各 节 里 将 要 讲 述 的其 他 更 奇 特 的 数 据 结 构 。9.1.1 创 建 和 访 问 一 个 两 维 数 组下 面 是 如 何 把 一 个 两 维 数 组 放 在 一 起 的 方 法 :# 给 一 个 数 组 赋 予 一 个 数 组 引 用 列 表 。@Aoa = (["fred", "barney" ],["george", "jane", "elroy" ],["homer", "marge", "bart" ],);print $AoA[2][1];# 打 印 "marge"整 个 列 表 都 封 装 在 圆 括 弧 里 , 而 不 是 花 括 弧 里 , 因 为 你 是 在 给 一 个 列 表 赋 值 而 不 是 给 引 用 赋值 。 如 果 你 想 要 一 个 指 向 数 组 的 引 用 , 那 么 你 要 使 用 方 括 弧 :# 创 建 一 个 指 向 一 个 数 组 的 数 组 的 引 用 。$ref_to_AoA = [[ "fred", "barney", "pebbles", "bamm bamm", "dino", ],[ "homer", "bart", "marge", "maggie", ],[ "george", "jane", "elroy", "judy", ],];237


print $ref_to_AoA->[2][3];# 打 印 "judy"请 记 住 在 每 一 对 相 邻 的 花 括 弧 或 方 括 弧 之 间 有 一 个 隐 含 的 ->。 因 此 下 面 两 行 :$AoA[2][3]$ref_to_AoA->[2][3]等 效 于 下 面 两 行 :$AoA[2]->[3]$ref_to_AoA->[2][3]不 过 , 在 第 一 对 方 括 弧 前 面 没 有 隐 含 的 ->, 这 也 是 为 什 么 $ref_to_AoA 的 解 引 用 要 求 开头 的 ->。 还 有 就 是 要 记 住 你 可 以 用 负 数 索 引 从 一 个 数 组 后 面 向 前 面 计 数 , 因 此 :$AoA[0][-2]是 第 一 行 的 倒 数 第 二 个 元 素 。9.1.2 自 行 生 长大 的 列 表 赋 值 是 创 建 大 的 固 定 数 据 结 构 的 好 方 法 , 但 是 如 果 你 想 运 行 时 计 算 每 个 元 素 , 或 者是 一 块 一 块 地 制 作 这 些 结 构 的 时 候 该 怎 么 办 呢 ?让 我 们 从 一 个 文 件 里 读 入 一 个 数 据 结 构 。 我 们 将 假 设 它 是 一 个 简 单 平 面 文 本 文 件 , 它 的 每 一行 是 结 构 的 一 个 行 , 并 且 每 行 包 含 由 空 白 分 隔 的 元 素 。 下 面 是 处 理 的 方 法 :( 注 : 在 这 里 和其 他 章 节 一 样 , 我 们 忽 略 那 些 通 常 你 要 放 进 去 的 my 声 明 。 在 这 个 例 子 里 , 你 通 常 写 my@tmp = split。)while () {@tmp = split;push @AoA, [ @tmp ];# 把 元 素 分 裂 成 一 个 数 组# 向 @AoA 中 增 加 一 个 匿 名 数 组 引 用}238


当 然 , 你 不 必 命 名 那 个 临 时 的 数 组 , 因 此 你 还 可 以 说 :while(){push @AoA, [ split ];}如 果 你 想 要 一 个 指 向 一 个 数 组 的 数 组 的 引 用 , 你 可 以 这 么 做 :while (){push @ref_to_AoA, [ split ];}这 些 例 子 都 向 数 组 的 数 组 增 加 新 的 行 。 那 么 如 何 增 加 新 的 列 呢 ? 如 果 你 只 是 对 付 两 维 数 组 ,通 常 更 简 单 的 方 法 是 使 用 简 单 的 赋 值 :( 注 : 和 前 面 的 临 时 赋 值 一 样 , 我 们 在 这 里 已 经 简 化了 ; 在 这 一 章 的 循 环 在 实 际 的 代 码 中 应 该 写 做 my $x。)for $x (0..9){ # 对 每 一 行 ...for $y (0..9) { # 对 每 一 列 ...$AoA[$x][$y] = func($x, $y);# ... 设 置 调 用}}for $x (0..9) { # 对 每 一 行 ...$ref_to_AoA->[$x][3] = func2($x);# ... 设 置 第 四 行}至 于 你 给 元 素 赋 值 的 顺 序 如 何 则 没 有 什 么 关 系 , 而 且 @AoA 的 脚 标 元 素 是 否 存 在 也 没 有 什么 关 系 ;<strong>Perl</strong> 会 很 开 心 地 为 你 创 建 它 们 , 并 且 把 中 间 的 元 素 根 据 需 要 设 置 为 未 定 义 值 。( 如果 有 必 要 ,<strong>Perl</strong> 甚 至 会 为 你 在 $ref_to_AoA 中 创 建 最 初 的 引 用 。) 如 果 你 只 是 想 附 加 一行 , 你 就 得 做 得 更 奇 妙 一 些 :# 向 一 个 已 经 存 在 的 行 中 附 加 一 个 新 列push @{ $AoA[0] }, "wilma", "betty";239


请 注 意 下 面 这 些 无 法 运 转 :push $AoA[0], "wilma", "betty"; # 错 误 !上 面 的 东 西 甚 至 连 编 译 都 过 不 了 , 因 为 给 push 的 参 数 必 须 是 一 个 真 正 的 数 组 , 而 不 只 是一 个 指 向 一 个 数 组 的 引 用 。 因 此 , 第 一 个 参 数 绝 对 必 须 以 @ 字 符 开 头 。 而 跟 在 @ 后 面 的东 西 则 可 以 忽 略 一 些 。9.1.3 访 问 和 打 印现 在 把 数 据 结 构 打 印 出 来 。 如 果 你 只 想 要 一 个 元 素 , 下 面 的 就 足 够 了 :print $AoA[3][2];但 是 如 果 你 想 打 印 所 有 的 东 西 , 那 你 不 能 这 么 写 :print @AoA; # 错 误 !这 么 做 是 错 误 的 , 因 为 它 会 打 印 出 字 串 化 的 引 用 , 而 不 是 你 的 数 据 。<strong>Perl</strong> 从 来 不 会 自 动 给你 解 引 用 。 你 必 须 自 己 用 一 两 个 循 环 遍 历 你 的 数 据 。 下 面 的 代 码 打 印 整 个 结 构 , 循 环 遍 历@AoA 的 元 素 并 且 在 print 语 句 里 对 每 个 元 素 进 行 解 引 用 :for $row (@AoA) {print "@$row\n";}如 果 你 想 追 踪 脚 标 , 你 可 以 这 么 做 :for $i (0..$#AoA) {print "row $i is: @{$AoA[$i]}\n";}或 者 甚 至 可 以 是 下 面 这 样 :for $i (0..$#AoA){for $j (0..$#{$AoA[$i]}){print "element $i $j is $AoA[$i][$j]\n";}240


}就 象 你 看 到 的 那 样 , 这 里 的 程 序 有 点 复 杂 。 这 就 是 为 什 么 很 多 时 候 你 用 一 个 临 时 变 量 事 情 会变 得 更 简 单 :for $i (0..$#AoA){$row = $AoA[$i];for $j (0..$@{$row}){print "element $i $j is $row->[$j]\n";}}9.1.4 片 段如 果 你 想 访 问 一 个 多 维 数 组 的 某 个 片 段 ( 一 行 的 一 部 分 ), 你 就 是 在 准 备 做 一 些 奇 特 的 脚 标处 理 。 指 针 箭 头 赋 予 我 们 一 种 访 问 单 一 变 量 的 极 好 的 方 法 , 但 是 对 于 片 段 而 言 却 没 有 这 么 好的 便 利 方 法 。 当 然 , 你 总 是 可 以 用 一 个 循 环 把 你 的 片 段 一 个 一 个 地 取 出 来 :@part = ();for ($y = 7; $y < 13; $y++) {push @part, $AoA[4][$y];}这 个 循 环 可 以 用 一 个 数 组 片 段 代 替 :@part = @{ $AoA[4] } [7..12];如 果 你 想 要 一 个 两 维 的 片 段 , 比 如 $x 在 4..8 而 $y 是 7..12, 下 面 是 实 现 的 一 些 方 法 :@newAoA = ();for ($startx = $x = 4; $x


}}在 这 个 例 子 里 , 我 们 的 两 维 数 组 @newAoA 里 的 每 个 独 立 的 数 值 都 是 一 个 一 个 地 从 一 个 两维 数 组 @AoA 中 取 出 来 赋 值 的 。 另 外 一 个 方 法 是 创 建 一 个 匿 名 数 组 , 每 个 由 一 个 @AoA中 我 们 要 的 子 数 组 组 成 , 然 后 然 后 把 指 向 这 些 匿 名 数 组 的 引 用 放 到 @newAoA 中 。 然 后 我们 就 可 以 把 引 用 写 到 @newAoA ( 也 是 脚 标 , 只 是 这 么 说 而 已 ), 而 不 用 把 一 个 子 数 组 值写 到 两 维 数 组 @newAoA 中 。 这 个 这 个 方 法 消 除 了 内 层 的 循 环 :for ($x = 4; $x


for $i (1..10) {@array = somefunc($i);$AoA = @array; # 错 误 !}在 这 里 @array 是 在 一 个 标 量 环 境 里 访 问 的 , 因 此 生 成 它 的 元 素 的 计 数 , 然 后 这 个 计 数 被忠 实 地 赋 予 $AoA[$i]。 赋 予 引 用 的 正 确 方 法 我 们 将 在 稍 后 介 绍 。在 产 生 前 面 的 错 误 之 后 , 人 们 认 识 到 他 们 需 要 赋 一 个 引 用 值 , 因 此 人 们 随 后 很 自 然 会 犯 的 的错 误 包 括 把 引 用 不 停 地 放 到 同 一 片 内 存 位 置 :for $i (1..10) {@array = somefunc($i);$AoA[$i] = \@array; # 又 错 了 !}每 个 for 循 环 的 第 二 行 生 成 的 引 用 都 是 一 样 的 , 也 就 是 说 , 一 个 指 向 同 一 个 数 组 @array的 引 用 。 的 确 , 这 个 数 组 在 循 环 的 每 个 回 合 中 都 会 变 化 , 但 是 当 所 有 的 话 都 说 完 了 , 所 有 的事 都 做 完 了 之 后 , $AoA 就 包 含 10 个 指 向 同 一 数 组 的 引 用 , 这 个 时 候 它 保 存 给 它 的 最 后一 次 赋 值 的 数 值 。 print @{$AoA[1]} 将 检 索 和 print @{$AoA[2]} 一 样 的 数 值 。下 面 是 更 成 功 的 方 法 :for $i (1..10) {@array = somefunc($i);$AoA[$i] = [ @array ]; # 正 确 !}在 @array 周 围 的 方 括 弧 创 建 一 个 新 的 匿 名 数 组 ,@array 里 的 元 素 都 将 拷 贝 到 这 里 。 然后 我 们 就 把 一 个 指 向 它 的 引 用 放 到 这 个 新 的 数 组 里 。一 个 类 似 的 结 果 —— 不 过 更 难 读 一 些 —— 可 以 是 :for $i (1..10) { @array = somefunc($i); @{$AoA[$i]} = @array; }243


因 为 $AoA 必 须 是 一 个 新 引 用 , 所 以 该 引 用 自 动 生 成 。 然 后 前 导 的 @ 把 这 个 新 引 用 解 引用 , 结 果 是 @array 的 数 值 赋 予 了 ( 在 列 表 环 境 中 )$AoA[$i] 引 用 的 数 组 。 出 于 程 序 清晰 性 的 考 虑 , 你 可 以 避 免 这 种 写 法 。但 是 有 一 种 情 况 下 你 可 能 要 用 这 种 构 造 。 假 设 $AoA 已 经 是 一 个 指 向 数 组 的 引 用 的 数 组 。也 就 是 说 你 要 做 类 似 下 面 这 样 的 赋 值 := $AoA[3] = \@original_array;=然 后 我 们 再 假 设 你 要 修 改 @original_array( 也 就 是 要 修 改 $AoA 的 第 四 行 ) 这 样 它 就指 向 @array 的 元 素 。 那 么 下 面 的 代 码 可 以 用 := @{$AoA[3]} = @array;=在 这 个 例 子 里 , 引 用 本 身 并 不 变 化 , 但 是 被 引 用 数 组 的 元 素 会 变 化 。 这 样 就 覆 盖 了@original_array 的 数 值 。最 后 , 下 面 的 这 些 看 起 来 很 危 险 的 代 码 将 跑 得 很 好 :for $i (1..10) {my @array = somefunc($i);$AoA[$i] = \@array;}这 是 因 为 在 循 环 的 每 个 回 合 中 , 词 法 范 围 的 my @array 都 会 重 新 创 建 。 因 此 即 使 看 起 来好 象 你 每 次 存 储 的 都 是 相 同 的 变 量 的 引 用 , 但 实 际 上 却 不 是 。 这 里 的 区 别 是 非 常 微 小 的 , 但是 这 样 的 技 巧 却 可 以 生 成 更 高 效 的 代 码 , 付 出 的 代 价 是 可 能 有 误 导 稍 微 差 一 些 的 程 序 员 。( 更高 效 是 因 为 它 没 有 最 后 赋 值 中 的 拷 贝 。) 另 一 方 面 , 如 果 你 必 须 拷 贝 这 些 数 值 ( 也 就 是 循 环中 第 一 个 赋 值 干 的 事 情 ), 那 么 你 也 可 以 使 用 方 括 号 造 成 的 隐 含 拷 贝 , 因 而 省 略 临 时 变 量 :for $i (1..10) {$AoA[$i] = [ somefunc($i)];}概 括 来 说 :$AoA[$i] = [ @array ];$AoA[$i] = \@array;# 最 安 全 , 有 时 候 最 快# 快 速 但 危 险 , 取 决 于 数 组 的 自 有 性244


@{ $AoA[$i] } = @array;# 有 点 危 险一 旦 你 掌 握 了 数 组 的 数 组 , 你 就 可 以 处 理 更 复 杂 的 数 据 结 构 。 如 果 你 需 要 C 结 构 或 者Pascal 记 录 , 那 你 在 <strong>Perl</strong> 里 找 不 到 任 何 特 殊 的 保 留 字 为 你 设 置 这 些 东 西 。<strong>Perl</strong> 给 你 的 是更 灵 活 的 系 统 。 如 果 你 对 记 录 结 构 的 观 念 比 这 样 的 系 统 的 灵 活 性 差 , 或 者 你 宁 愿 给 你 的 用 户一 些 更 不 透 明 的 , 更 僵 化 的 东 西 , 那 么 你 可 以 使 用 在 第 十 二 章 , 对 象 , 里 详 细 描 述 的 面 向 对象 的 特 性 。<strong>Perl</strong> 只 有 两 种 组 织 数 据 的 方 式 : 以 排 序 的 列 表 存 储 在 数 组 里 按 位 置 访 问 , 或 者 以 未 排 序 的键 字 / 数 值 对 存 储 在 散 列 里 按 照 名 字 访 问 。 在 <strong>Perl</strong> 里 代 表 一 条 记 录 的 最 好 的 方 法 是 用 一 个散 列 引 用 , 但 是 你 选 择 的 组 织 这 样 的 记 录 的 方 法 是 可 以 变 化 的 。 你 可 能 想 要 保 存 一 个 有 序 的记 录 列 表 以 便 按 照 编 号 来 访 问 , 这 种 情 况 下 你 不 得 不 用 一 个 散 列 数 组 来 保 存 记 录 。 或 者 , 你可 能 希 望 按 照 名 字 来 寻 找 记 录 , 这 种 情 况 下 你 就 要 维 护 一 个 散 列 的 散 列 。 你 甚 至 可 以 两 个 同时 用 , 这 时 候 就 是 伪 散 列 。在 随 后 的 各 节 里 , 你 会 看 到 详 细 地 讲 述 如 何 构 造 ( 从 零 开 始 ), 生 成 ( 从 其 他 数 据 源 ), 访问 , 和 显 示 各 种 不 同 的 数 据 结 构 的 代 码 。 我 们 首 先 演 示 三 种 直 截 了 当 的 数 组 和 散 列 的 组 合 ,然 后 跟 着 一 个 散 列 函 数 和 更 不 规 则 的 数 据 结 构 。 最 后 我 们 以 一 个 如 何 保 存 这 些 数 据 结 构 的 例子 结 束 。 我 们 在 讲 这 些 例 子 之 前 假 设 你 已 经 熟 悉 了 我 们 在 本 章 中 前 面 已 经 设 置 了 的 解 释 集 。9.2 数 组 的 散 列如 果 你 想 用 一 个 特 定 的 字 串 找 出 每 个 数 组 , 而 不 是 用 一 个 索 引 数 字 找 出 它 们 来 , 那 么 你 就 要用 数 组 的 散 列 。 在 我 们 电 视 角 色 的 例 子 里 , 我 们 不 是 用 第 零 个 , 第 一 个 等 等 这 样 的 方 法 查 找该 名 字 列 表 , 而 是 设 置 成 我 们 可 以 通 过 给 出 角 名 字 找 出 演 员 列 表 的 方 法 。因 为 我 们 外 层 的 数 据 结 构 是 一 个 散 列 , 因 此 我 们 无 法 对 其 内 容 进 行 排 序 , 但 是 我 们 可 以 使 用sort 函 数 声 明 一 个 特 定 的 输 出 顺 序 。9.2.1 数 组 的 散 列 的 组 成你 可 以 用 下 面 的 方 法 创 建 一 个 匿 名 数 组 的 散 列 :# 如 果 键 字 是 标 识 符 , 我 们 通 常 省 略 引 号%HoA = (flintstones => [ "fred", "barney" ],245


jetsons => [ "george", "jane", "elroy" ],simpsons => [ "homer", "marge", "bart" ],);要 向 散 列 增 加 另 外 一 个 数 组 , 你 可 以 简 单 地 说 :$HoA{teletubbies} = [ "tinky winky", "dipsy", "laa-laa", "po" ];9.2.2 生 成 数 组 的 散 列下 面 是 填 充 一 个 数 组 的 散 列 的 技 巧 。 从 下 面 格 式 的 文 件 中 读 取 出 来 :flintsotnes:fred barney wilma dinojetsons:simpsons:george jane elroyhomer marge bart你 可 以 用 下 列 两 个 循 环 之 一 :while( ) {next unless s/^(.*?):\S*//;$HoA{$1} = [ split ];}while ( $line = ) {($who, $rest) = split /:\S*/, $line, 2;@fields = spilt ' ', $rest;$HoA{$who} = [ @fields ];}如 果 你 有 一 个 子 过 程 叫 get_family, 它 返 回 一 个 数 组 , 那 么 你 可 以 用 下 面 两 个 循 环 之 一 填充 %HoA:for $group ( "simpsons", "jetsons", "flintstones" ) {246


$HoA{$group} = [ get_family($group) ];}for $group ( "simpsons", "jetsons", "flintstones" ) {@members = get_family($group);$HoA{$group} = [ @members ];}你 可 以 用 下 面 的 方 法 向 一 个 已 存 在 的 数 组 追 加 新 成 员 :push @{ $HoA{flintstones}}, "wilma", "pebbles";9.2.3 访 问 和 打 印 数 组 的 散 列你 可 以 用 下 面 的 方 法 设 置 某 一 数 组 的 第 一 个 元 素 :$HoA{flintstones}[0] = "Fred";要 让 第 二 个 Simpson 变 成 大 写 , 那 么 对 合 适 的 数 组 元 素 进 行 一 次 替 换 :$HoA{simpsons}[1] =~ s/(\w)/\u$1/;你 可 以 打 印 所 有 这 些 家 族 , 方 法 是 遍 历 该 散 列 的 所 有 键 字 :for $family ( keys %HoA ){print "$family: @{ $HoA{$family} }\n";}我 们 稍 微 多 做 一 些 努 力 , 你 就 可 以 一 样 追 加 数 组 索 引 :for $family ( keys %HoA ) {print "$family: ";for $i ( 0 .. $#{ $HoA{$family} }) {print " $i = $HoA{$family}[$i]";247


}print "\n";}或 者 通 过 以 数 组 拥 有 的 元 素 个 数 对 它 们 排 序 :for $family ( sort { @{$HoA{$b}} @{$HoA{$a}} } keys %HoA ){print "$family: @{ $HoA{$family}}\n";}或 者 甚 至 可 以 是 以 元 素 的 个 数 对 数 组 排 序 然 后 以 元 素 的 ASCII 码 顺 序 进 行 排 序 ( 准 确 地 说是 utf8 的 顺 序 ):# 打 印 以 成 员 个 数 和 名 字 排 序 的 所 有 内 容for $family ( sort { @{$HoA{$b}} @{$HoA{$a}}} keys %HoA) {print "$family: ", join(", ", sort @{$HoA{$family}}), "\n";}9.3 散 列 的 数 组如 果 你 有 一 堆 记 录 , 你 想 顺 序 访 问 它 们 , 并 且 每 条 记 录 本 身 包 含 一 个 键 字 / 数 值 对 , 那 么 散列 的 数 组 就 很 有 用 。 在 本 章 中 , 散 列 的 数 组 比 其 他 结 构 用 得 少 一 些 。9.3.1 组 成 一 个 散 列 的 数 组你 可 以 用 下 面 方 法 创 建 一 个 匿 名 散 列 的 数 组 :@AoH = ({husband => "barney",wifeson=> "betty",=> "bamm bamm",},248


{husband => "george",wifeson=> "jane",=> "elroy",},{husband => "homer",wifeson=> "marge",=> "bart",},);要 向 数 组 中 增 加 另 外 一 个 散 列 , 你 可 以 简 单 地 说 :push @AoH, { husband => "fred", wife => "wilma", daughter => "pebbles" };9.3.2 生 成 散 列 的 数 组下 面 是 一 些 填 充 散 列 数 组 的 技 巧 。 要 从 一 个 文 件 中 读 取 下 面 的 格 式 :husband=fred friend=barney你 可 以 使 用 下 面 两 个 循 环 之 一 :while () {$rec = {};for $field ( split ) {($key, $value) = split /=/, $field;$rec->{$key} = $value;}249


push @AoH, $rec;}while () {push @AoH, { split /[\s=]+/ };}如 果 你 有 一 个 子 过 程 get_next_pair 返 回 一 个 键 字 / 数 值 对 , 那 么 你 可 以 用 它 利 用 下 面 两个 循 环 之 一 来 填 充 @AoH:while ( @fields = get_next_pari()) {push @AoH, {@fields};}while () {push @AoH, { get_next_pair($_) };}你 可 以 象 下 面 这 样 向 一 个 现 存 的 散 列 附 加 新 的 成 员 :$AoH[0]{pet} = "dino";$AoH[2]{pet} = "santa's little helper";9.3.3 访 问 和 打 印 散 列 的 数 组你 可 以 用 下 面 的 方 法 设 置 一 个 特 定 散 列 的 数 值 / 键 字 对 :$AoH[0]{husband} = "fred";要 把 第 二 个 数 组 的 丈 夫 (husband) 变 成 大 写 , 用 一 个 替 换 :$AoH[1]{husband} =~ s/(\w)/\u$1/;你 可 以 用 下 面 的 方 法 打 印 所 有 的 数 据 :250


for $href ( @AoH ) {print "{ ";for $role ( keys %$href ) {print "$role=$href->{$role} ";}print "}\n";}以 及 带 着 引 用 打 印 :for $i ( 0 .. $#AoH ) {print "$i is { ";for $role ( keys %{ $AoH[$i] } ) {print "$role=$AoH[$i]{$role} ";}print "}\n";}9.4 散 列 的 散 列多 维 的 散 列 是 <strong>Perl</strong> 里 面 最 灵 活 的 嵌 套 结 构 。 它 就 好 象 绑 定 一 个 记 录 , 该 记 录 本 身 包 含 其 他记 录 。 在 每 个 层 次 上 , 你 都 用 一 个 字 串 ( 必 要 时 引 起 ) 做 该 散 列 的 索 引 。 不 过 , 你 要 记 住 散列 里 的 键 字 / 数 值 对 不 会 以 任 何 特 定 的 顺 序 出 现 ; 你 可 以 使 用 sort 函 数 以 你 喜 欢 的 任 何 顺序 检 索 这 些 配 对 。9.4.1 构 成 一 个 散 列 的 散 列你 可 以 用 下 面 方 法 创 建 一 个 匿 名 散 列 的 散 列 :%HoH = (flintstones => {251


husband => "fred",pal=> "barney",},jetsons => {husband => "george",wife=> "jane","his boy" => "elroy",# 键 字 需 要 引 号},simpsons => {husband => "homer",wifekid=> "marge",=> "bart",},);要 向 %HoH 中 增 加 另 外 一 个 匿 名 散 列 , 你 可 以 简 单 地 说 :$HoH{ mash } = {captain => "pierce",major=> "burns",corporal=> "radar",}9.4.2 生 成 散 列 的 散 列下 面 是 一 些 填 充 一 个 散 列 的 散 列 的 技 巧 。 要 从 一 个 下 面 格 式 的 文 件 里 读 取 数 据 :flintstones252


husband=fred pal=barney wife=wilmapet=dino你 可 以 使 用 下 面 两 个 循 环 之 一 :while( ){next unless s/^(.*?):\S*//;$who = $1;for $field ( split ) {($key, $value) = split /=/, $field;$HoH{$who}{$key} = $value;}}while( ){next unless s/^(.*?):\S*//;$who = $1;$rec = {};$HoH{$who} = $rec;for $field ( split ) {($key, $value) = split /=/, $field;$rec->{$key} = $value;}}如 果 你 有 一 个 子 过 程 get_family 返 回 一 个 键 字 / 数 值 列 表 对 , 那 么 你 可 以 拿 下 面 三 种 方 法的 任 何 一 种 , 用 它 填 充 %HoH:for $group ("simpsons", "jetsons", "flintstones" ) {253


$HoH{$group} = {get_family($group)};}for $group ( "simpsons", "jetsons", "flintstones" ) {@members = get_family($group);$HoH{$group} = {@menbers};}sub hash_families {my @ret;for $group (@_) {push @ret, $group, {get_family($group)};}return @ret;}%HoH = hash_families( "simpsons", "jetsons", "flintstones" );你 可 以 用 下 面 的 方 法 向 一 个 现 有 的 散 列 附 加 新 的 成 员 :%new_floks = (wife => "wilma",pet => "dino",);for $what (keys %new_floks) {254


$HoH{flintstones}{$what} = $new_floks{$what};}9.4.3 访 问 和 打 印 散 列 的 散 列你 可 以 用 下 面 的 方 法 设 置 键 字 / 数 值 对 :$HoH{flintstones}{wife} = "wilma";要 把 某 个 键 字 / 数 值 对 变 成 大 写 , 对 该 元 素 应 用 一 个 替 换 :$HoH{jetsons}{'his boy'} =~ s/(\w)/\u$1/;你 可 以 用 先 后 遍 历 内 外 层 散 列 键 字 的 方 法 打 印 所 有 家 族 :for $family ( keys %HoH ) {print "$family: ";for $role ( keys %{ $HoH{$family} } ){print "$role=$person ";}print "\n";}在 非 常 大 的 散 列 里 , 可 能 用 each 同 时 把 键 字 和 数 值 都 检 索 出 来 会 略 微 快 一 些 ( 这 样 做 可以 避 免 排 序 ):while ( ($family, $roles) = each %HoH ) {print "$family: ";while ( ($role, $person) = each %$roles ) {print "$role=$person";}print "\n";}255


( 糟 糕 的 是 , 需 要 存 储 的 是 那 个 大 的 散 列 , 否 则 你 在 打 印 输 出 里 就 永 远 找 不 到 你 要 的 东 西 .)你 可 以 用 下 面 的 方 法 先 对 家 族 排 序 然 后 再 对 脚 色 排 序 :for $family ( sort keys %HoH ) {print "$family: ";for $role ( sort keys %{ $HoH{$family} } ) {print "$role=$HoH{$family}{$role} ";}print "\n";}要 按 照 家 族 的 编 号 排 序 ( 而 不 是 ASCII 码 ( 或 者 utf8 码 )), 你 可 以 在 一 个 标 量 环 境 里使 用 keys:for $family ( sort { keys %{$HoH{$a} } keys %{$HoH{$b}}} keys %HoH ) {print "$family: ";for $role ( sort keys %{ $HoH{$family} } ) {print "$role=$HoH{$family}{$role};}print "\n";}要 以 某 种 固 定 的 顺 序 对 一 个 家 族 进 行 排 序 , 你 可 以 给 每 个 成 员 赋 予 一 个 等 级 来 实 现 :$i = 0;for ( qw(husband wife son daughter pal pet) ) { $rank{$_} = ++$i }for $family ( sort { keys %{$HoH{$a} } keys %{$HoH{$b}}} keys %HoH ) {print "$family: ";for $role ( sort { $rank{$a} $rank{$b} } keys %{ $HoH{$family} }) {256


print "$role=$HoH{$family}{$role} ";}print "\n";}9.5 函 数 的 散 列在 使 用 <strong>Perl</strong> 书 写 一 个 复 杂 的 应 用 或 者 网 络 服 务 的 时 候 , 你 可 能 需 要 给 你 的 用 户 制 作 一 大 堆命 令 供 他 们 使 用 。 这 样 的 程 序 可 能 有 象 下 面 这 样 的 代 码 来 检 查 用 户 的 选 择 , 然 后 采 取 相 应 的动 作 :if ($cmd =~ /^exit$/i) { exit }elsif ($cmd =~ /^help$/i) { show_help() }elsif ($cmd =~ /^watch$/i) { $watch = 1 }elsif ($cmd =~ /^mail$/i) { mail_msg($msg) }elsif ($cmd =~ /^edit$/i) { $edited++; editmsg($msg); }elsif ($cmd =~ /^delete$/i) { confirm_kill() }else {warn "Unknown command: `$cmd'; Try `help' next time\n";}你 还 可 以 在 你 的 数 据 结 构 里 保 存 指 向 函 数 的 引 用 , 就 象 你 可 以 存 储 指 向 数 组 或 者 散 列 的 引 用一 样 :%HoF = (# Compose a hash of functionsexit => sub { exit },help=> \&show_help,watch => sub { $watch = 1 },mail => sub { mail_msg($msg) },257


edit => sub { $edited++; editmsg($msg); },delete => \&confirm_kill,);if ($HoF{lc $cmd}) { $HoF{lc $cmd}->() } # Call functionelse { warn "Unknown command: `$cmd'; Try `help' next time\n" }在 倒 数 第 二 行 里 , 我 们 检 查 了 声 明 的 命 令 名 字 ( 小 写 ) 是 否 在 我 们 的 “ 遣 送 表 ”%HoF 里 存在 。 如 果 是 , 我 们 调 用 响 应 的 命 令 , 方 法 是 把 散 列 值 当 作 一 个 函 数 进 行 解 引 用 并 且 给 该 函 数传 递 一 个 空 的 参 数 列 表 。 我 们 也 可 以 用 &{ $HoF{lc $cmd} }( ) 对 散 列 值 进 行 解 引 用 ,或 者 , 在 <strong>Perl</strong> 5.6 里 , 可 以 简 单 地 是 $HoF{lc $cmd} ()。9.6 更 灵 活 的 记 录到 目 前 为 止 , 我 们 在 本 章 看 到 的 都 是 简 单 的 , 两 层 的 , 同 质 的 数 据 结 构 : 每 个 元 素 包 含 同 样类 型 的 引 用 , 同 时 所 有 其 他 元 素 都 在 该 层 。 数 据 结 构 当 然 可 以 不 是 这 样 的 。 任 何 元 素 都 可 以保 存 任 意 类 型 的 标 量 , 这 就 意 味 着 它 可 以 是 一 个 字 串 , 一 个 数 字 , 或 者 指 向 任 何 东 西 的 引 用 。这 个 引 用 可 以 是 一 个 数 组 或 者 散 列 引 用 , 或 者 一 个 伪 散 列 , 或 者 是 一 个 指 向 命 名 或 者 匿 名 函数 的 引 用 , 或 者 一 个 对 象 。 你 唯 一 不 能 干 的 事 情 就 是 向 一 个 标 量 里 填 充 多 个 引 用 物 。 如 果 你发 现 自 己 在 做 这 种 尝 试 , 那 就 表 示 着 你 需 要 一 个 数 组 或 者 散 列 引 用 把 多 个 数 值 压 缩 成 一 个 。在 随 后 的 节 里 , 你 将 看 到 一 些 代 码 的 例 子 , 这 些 代 码 设 计 成 可 以 演 示 许 多 你 想 存 储 在 一 个 记录 里 的 许 多 可 能 类 型 的 数 据 , 我 们 将 用 散 列 引 用 来 实 现 它 们 。 这 些 键 字 都 是 大 写 字 串 , 这 是我 们 时 常 使 用 的 一 个 习 惯 ( 有 时 候 也 不 用 这 个 习 惯 , 但 只 是 偶 然 不 用 )—— 如 果 该 散 列 被 用做 一 个 特 定 的 记 录 类 型 。9.6.1 更 灵 活 的 记 录 的 组 合 , 访 问 和 打 印下 面 是 一 个 带 有 六 种 完 全 不 同 的 域 的 记 录 :$rec = {TEXT=> $string,SEQUENCE => [ @old_values ],LOOKUP => { %some_table },258


THATCODE => sub { $_[0] ** $_[1] },HANDLE=> \*STDOUT,};TEXT 域 是 一 个 简 单 的 字 串 。 因 此 你 可 以 简 单 的 打 印 它 :print $rec->{TEXT};SEQUENCE 和 LOOKUP 都 是 普 通 的 数 组 和 散 列 引 用 :print $rec->{SEQUENCE }[0];$last = pop @{ $rec->{SEQUENCE} };print $rec->{LOOKUP}{"key"};($first_k, $first_v) = each %{ $rec->{LOOKUP} };THATCODE 是 一 个 命 名 子 过 程 而 THISCODE 是 一 个 匿 名 子 过 程 , 但 是 它 们 的 调 用 是 一样 的 :$that_answer = $rec->{THATCODE}->($arg1, $arg2);$this_answer = $rec->{THISCODE}->($arg1, $arg2);再 加 上 一 对 花 括 弧 , 你 可 以 把 $rec->{HANDLE} 看 作 一 个 间 接 的 对 象 :print { $rec->{HANDLE} } "a string \n";如 果 你 在 使 用 FileHandle ?模 块 , 你 甚 至 可 以 把 该 句 柄 看 作 一 个 普 通 的 对 象 :use FileHandle;$rec->{HANDLE}->autoflush(1);$rec->{HANDLE}->print("a string\n");9.6.2 甚 至 更 灵 活 的 记 录 的 组 合 , 访 问 和 打 印自 然 , 你 的 数 据 结 构 的 域 本 身 也 可 以 是 任 意 复 杂 的 数 据 结 构 :%TV = (259


flintstones => {series=> "flintstones",nights => [ "monday", "thursday", "friday" ],members => [{ name => "fred", role => "husband", age => 36, },{ name => "wilma", role => "wife", age => 31, },{ name => "pebbles", role => "kid", age => 4, },],},jetsons => {series=> "jetsons",nights => [ "wednesday", "saturday" ],members => [{ name => "george", role => "husband", age => 41, },{ name => "jane", role => "wife", age => 39, },{ name => "elroy", role => "kid", age => 9, },],},simpsons => {series=> "simpsons",nights => [ "monday" ],260


members => [{ name => "homer", role => "husband", age => 34, },{ name => "marge", role => "wife", age => 37, },{ name => "bart", role => "kid", age => 11, },],},);9.6.3 复 杂 记 录 散 列 的 生 成因 为 <strong>Perl</strong> 分 析 复 杂 数 据 结 构 相 当 不 错 , 因 此 你 可 以 把 你 的 数 据 声 明 作 为 <strong>Perl</strong> 代 码 放 到 一个 独 立 的 文 件 里 , 然 后 用 do 或 者 require 等 内 建 的 函 数 把 它 们 装 载 进 来 。 另 外 一 种 流 行的 方 法 是 使 用 CPAN 模 块 ( 比 如 XML::Parser) 装 载 那 些 用 其 他 语 言 ( 比 如 XML) 表 示的 任 意 数 据 结 构 。你 可 以 分 片 地 制 作 数 据 结 构 :$rec = {};$rec->{series} = "flintstones";$rec->{nights} = [ find_days()];或 者 从 文 件 里 把 它 们 读 取 进 来 ( 在 这 里 , 我 们 假 设 文 件 的 格 式 是 field=value 语 法 ):@members = ();while () {%fields = split /[\s=]+/;push @members, {%fields};}$rec->{members} = [ @members ];然 后 以 一 个 子 域 为 键 字 , 把 它 们 堆 积 到 更 大 的 数 据 结 构 里 :261


$TV{ $rec->{series} } = $rec;你 可 以 使 用 额 外 的 指 针 域 来 避 免 数 据 的 复 制 。 比 如 , 你 可 能 需 要 在 一 个 人 的 记 录 里 包 含 一 个“kids” ( 孩 子 ) 数 据 域 , 这 个 域 可 能 是 一 个 数 组 , 该 数 组 包 含 着 指 向 这 个 孩 子 自 己 记 录 的引 用 。 通 过 把 你 的 数 据 结 构 的 一 部 分 指 向 其 他 的 部 分 , 你 可 以 避 免 因 为 在 一 个 地 方 更 新 数 据而 没 有 在 其 他 地 方 更 新 数 据 造 成 的 数 据 倾 斜 :for $family (keys %TV) {my $rec = $TV{$family};# 临 时 指 针@kids = ();for $person ( @{$rec->{members}}) {if ($person->{role} =~ /kid|son|daughter/) {push @kids, $person;}}# $rec 和 $TV{$family} 指 向 相 同 的 数 据 !$rec->{kids} = [@kids];}这 里 的 $rec->{kids} = [@kids] 赋 值 拷 贝 数 组 内 容 —— 但 它 们 只 是 简 单 的 引 用 , 而 没有 拷 贝 数 据 。 这 就 意 味 着 如 果 你 给 Bart 赋 予 下 面 这 样 的 年 龄 :$TV{simpsons}{kids}[0]{age}++; # 增 加 到 12那 么 你 就 会 看 到 下 面 的 结 果 , 因 为 $TV{simpsons}{kids}[0] 和$TV{simpsons}{members}[2] 都 指 向 相 同 的 下 层 匿 名 散 列 表 :print $TV{simpsons}{members}[2]{age}; # 也 打 印 12现 在 你 打 印 整 个 %TV 结 构 :for $family ( keys %TV ) {print "the $family";262


print " is on ", join (" and ", @{ $TV{$family}{nights} }), "\n";print "its members are:\n";for $who ( @{ $TV{$family}{members} } ) {print " $who->{name} ($who->{role}), age $who->{age}\n";}print "children: ";print join (", ", map { $_->{name} } @{ $TV{$family}{kids} } );print "\n\n";}9.7 保 存 数 据 结 构如 果 你 想 保 存 你 的 数 据 结 构 以 便 以 后 用 于 其 他 程 序 , 那 么 你 有 很 多 方 法 可 以 用 。 最 简 单 的 方法 就 是 使 用 <strong>Perl</strong> 的 Data::Dumper 模 块 , 它 把 一 个 ( 可 能 是 自 参 考 的 ) 数 据 结 构 变 成一 个 字 串 , 你 可 以 把 这 个 字 串 保 存 在 程 序 外 部 , 以 后 用 eval 或 者 do 重 新 组 成 :use Data::Dumper;$Data::Dumper::Purity = 1;# 因 为 %TV 是 自 参 考 的open (FILE, "> tvinfo.perldata") or die "can't open tvinfo: $!";print FILE Data::Dumper->Dump([\%TV], ['*TV']);close FILE or die "can't close tvinfo: $!";其 他 的 程 序 ( 或 者 同 一 个 程 序 ) 可 以 稍 后 从 文 件 里 把 它 读 回 来 :open (FILE, "< tvinfo.perldata") or die "can't open tvinfo: $!";undef $/;eval ;# 一 次 把 整 个 文 件 读 取 进 来# 重 新 创 建 %TVdie "can't recreate tv data from tvinfo.perldata: $@" if $@;close FILE or die "can't close tvinfo: $!";263


print $TV{simpsons}{members}[2]{age};或 者 简 单 的 是 :do "tvinfo.perldata"or die "can't recreate tvinfo: $! $@";print $TV{simpsons}{members}[2]{age};还 有 许 多 其 他 的 解 决 方 法 可 以 用 , 它 们 的 存 储 格 式 的 范 围 从 打 包 的 二 进 制 ( 非 常 快 ) 到 XML( 互 换 性 非 常 好 )。 检 查 一 下 靠 近 你 的 CPAN 镜 象 !第 十 章 包在 本 章 里 , 我 们 开 始 有 好 玩 的 东 西 了 , 因 为 我 们 要 开 始 讲 有 关 软 件 设 计 的 东 西 。 如 果 我 们 要聊 一 些 好 的 软 件 设 计 , 那 么 我 们 就 必 须 先 侃 侃 懒 惰 , 急 燥 , 和 傲 慢 , 这 几 样 好 的 软 件 设 计 需要 的 基 本 要 素 。我 们 经 常 落 到 使 用 拷 贝 和 粘 贴 (ICP-I Copy & Paste) 的 陷 阱 里 , 而 如 果 一 个 循 环 或 者 一个 子 过 程 就 足 够 了 ,( 注 : 这 是 伪 懒 惰 的 一 种 形 式 ) 那 么 这 时 候 我 们 实 际 上 应 该 定 义 一 个 更高 层 次 的 抽 象 。 但 是 , 有 些 家 伙 却 走 向 另 外 一 个 极 端 , 定 义 了 一 层 又 一 层 的 高 层 抽 象 , 而 这个 时 候 他 们 应 该 用 拷 贝 和 粘 贴 。( 注 : 这 是 伪 傲 慢 的 一 种 形 式 。) 不 过 , 通 常 来 讲 , 我 们 大多 数 人 都 应 该 考 虑 使 用 更 多 的 抽 象 。落 在 中 间 的 是 那 些 对 抽 象 深 度 有 平 衡 观 念 的 人 , 不 过 他 们 马 上 就 开 始 写 它 们 自 己 的 抽 象 层 ,而 这 个 时 候 它 们 应 该 重 用 现 有 的 代 码 。( 注 : 你 也 许 已 经 猜 到 了 —— 这 是 为 急 燥 。 不 过 , 如果 你 准 备 推 倒 重 来 , 那 么 你 至 少 应 该 发 明 一 种 更 好 的 东 西 。)如 果 你 准 备 做 任 何 这 样 的 事 情 , 那 么 你 都 应 该 坐 下 来 想 想 , 怎 样 做 才 能 从 长 远 来 看 对 你 和 你的 邻 居 最 有 好 处 。 如 果 你 准 备 把 你 的 创 造 力 引 擎 作 用 到 一 小 块 代 码 里 , 那 么 为 什 么 不 把 这 个你 还 要 居 住 的 这 个 世 界 变 得 更 美 好 一 些 呢 ?( 即 使 你 的 目 的 只 是 为 了 程 序 的 成 功 , 那 你 就 要确 信 你 的 程 序 能 够 符 合 社 会 生 态 学 的 要 求 。)朝 着 生 态 编 程 的 第 一 步 是 : 不 要 在 公 园 里 乱 丢 垃 圾 ( 译 注 : 否 则 砸 到 小 朋 友 ... 或 者 花 花 草草 ...:)。 当 你 写 一 段 代 码 的 时 候 , 考 虑 一 下 给 这 些 代 码 自 己 的 名 字 空 间 , 这 样 你 的 变 量和 函 数 就 不 会 把 别 人 的 变 量 和 函 数 搞 砸 了 , 反 之 亦 然 。 名 字 空 间 有 点 象 你 的 家 , 你 的 家 里 想264


怎 么 乱 都 行 , 只 要 你 保 持 你 的 外 部 界 面 对 其 他 公 民 来 说 是 适 度 文 明 的 就 可 以 了 。 在 <strong>Perl</strong> 里 ,一 个 名 字 空 间 叫 一 个 包 。 包 提 供 了 基 本 的 制 作 块 , 在 它 上 面 构 造 更 高 级 的 概 念 , 比 如 模 块 和类 等 。和 “ 家 ” 的 说 法 相 似 ,“ 包 ” 的 说 法 也 有 一 些 模 糊 。 包 独 立 于 文 件 。 你 可 以 在 一 个 文 件 里 有 许 多包 , 或 者 是 一 个 包 跨 越 多 个 文 件 , 就 好 象 你 的 家 可 以 是 在 一 座 大 楼 里 面 的 小 小 的 顶 楼 ( 如 果你 是 一 个 穷 困 潦 倒 的 艺 术 家 ), 或 者 你 的 家 也 可 以 由 好 多 建 筑 构 成 ( 比 如 你 的 名 字 叫 伊 丽 沙白 女 王 )。 但 家 的 常 见 大 小 就 是 一 座 建 筑 , 而 包 通 常 也 是 一 个 文 件 大 ,<strong>Perl</strong> 给 那 些 想 把 一个 包 放 到 一 个 文 件 里 的 人 们 提 供 了 一 些 特 殊 的 帮 助 , 条 件 只 是 你 愿 意 给 文 件 和 包 相 同 的 名 字并 且 使 用 一 个 .pm 的 扩 展 名 ,pm 是 “perl module” 的 缩 写 。 模 块 (module) 是 <strong>Perl</strong>里 重 复 使 用 的 最 基 本 的 模 块 。 实 际 上 , 你 使 用 模 块 的 方 法 是 use 命 令 , 它 是 一 个 编 译 器 指示 命 令 , 可 以 控 制 从 一 个 模 块 里 输 入 子 过 程 和 变 量 。 到 目 前 为 止 你 看 到 的 每 一 个 use 的 例子 都 是 模 块 复 用 的 例 子 。如 果 其 他 人 认 为 你 的 模 块 有 用 , 那 么 你 应 该 把 它 们 放 到 CPAN。<strong>Perl</strong> 的 繁 荣 是 和 程 序 员 愿意 和 整 个 社 区 分 享 他 们 劳 动 的 果 实 分 不 开 的 。 自 然 ,CPAN 也 是 你 可 以 找 到 那 些 其 他 人 已经 非 常 仔 细 地 上 载 上 去 给 别 人 用 的 模 块 的 地 方 。 参 阅 第 二 十 二 章 , CPAN, 以 及www.cpan.org 获 取 详 细 信 息 。过 去 25 年 左 右 的 时 间 里 , 设 计 计 算 机 语 言 的 趋 势 是 强 调 某 种 偏 执 。 你 必 须 编 制 每 一 个 模块 , 就 好 象 它 是 一 个 围 城 的 阶 段 一 样 。 显 然 有 些 封 建 领 地 式 的 文 化 可 以 使 用 这 样 的 方 法 , 但并 不 是 所 有 文 化 都 喜 欢 这 样 。 比 如 , 在 <strong>Perl</strong> 文 化 里 , 人 们 让 你 离 它 们 的 房 子 远 一 点 是 因 为他 们 没 有 邀 请 你 , 而 不 是 因 为 窗 户 上 有 窗 栅 。( 注 : 不 过 , 如 果 需 要 , <strong>Perl</strong> 提 供 了 一 些 窗栅 。 参 阅 第 二 十 三 章 , 安 全 , 里 的 “ 处 理 不 安 全 数 据 ”。)这 本 书 不 是 讲 面 向 对 象 的 方 法 论 的 , 并 且 我 们 在 这 里 也 不 想 把 你 推 到 面 向 对 象 的 狂 热 中 去 ,就 算 你 想 进 去 我 们 的 态 度 也 这 样 。 关 于 这 方 面 的 东 西 已 经 有 大 量 书 籍 了 。<strong>Perl</strong> 对 面 向 对 象设 计 的 原 则 和 <strong>Perl</strong> 对 其 他 东 西 的 原 则 是 一 样 的 : 在 面 向 对 象 的 设 计 方 法 有 意 义 的 地 方 就 用它 , 而 在 没 有 意 义 的 地 方 就 绕 开 它 。 你 的 选 择 。在 OO 的 说 法 中 , 每 个 对 象 都 属 于 一 个 叫 做 类 的 组 。 在 <strong>Perl</strong> 里 , 类 和 包 以 及 模 块 之 间 的关 系 是 如 此 地 密 切 , 以 至 于 许 多 新 手 经 常 认 为 它 们 是 可 以 互 换 的 。 典 型 的 类 是 用 一 个 定 义 了与 该 类 同 名 的 包 名 字 的 模 块 实 现 的 。 我 们 将 在 随 后 的 几 章 里 解 释 这 些 东 西 。当 你 use 一 个 模 块 的 时 候 , 你 是 从 软 件 复 用 中 直 接 受 益 。 如 果 你 用 了 类 , 那 么 如 果 一 个 类通 过 继 承 使 用 了 另 外 一 个 类 , 那 么 你 是 间 接 地 从 软 件 复 用 中 受 益 。 而 且 用 了 类 , 你 就 获 得 了更 多 的 一 些 东 西 : 一 个 通 往 另 外 一 个 名 字 空 间 的 干 净 的 接 口 。 在 类 里 面 , 所 有 东 西 都 是 间 接地 访 问 的 , 把 这 个 类 和 外 部 的 世 界 隔 离 开 。265


就 象 我 们 在 第 八 章 , 引 用 , 里 提 到 的 一 样 , 在 <strong>Perl</strong> 里 的 面 向 对 象 的 编 程 是 通 过 引 用 来 实 现的 , 这 些 引 用 的 引 用 物 知 道 它 们 属 于 哪 些 类 。 实 际 上 , 如 果 你 知 道 引 用 , 那 么 你 就 知 道 几 乎所 有 有 关 对 象 的 困 难 。 剩 下 的 就 是 “ 放 在 你 的 手 指 下 面 ”, 就 象 画 家 会 说 的 那 样 。 当 然 , 你 需要 做 一 些 练 习 。你 的 基 本 练 习 之 一 就 学 习 如 何 保 护 不 同 的 代 码 片 段 , 避 免 被 其 他 人 的 变 量 不 小 心 篡 改 。 每 段代 码 都 属 于 一 个 特 定 的 包 , 这 个 包 决 定 它 里 面 有 哪 些 变 量 和 代 码 可 以 使 用 。 当 <strong>Perl</strong> 碰 到 一段 代 码 的 时 候 , 这 段 代 码 就 被 编 译 成 我 们 叫 做 当 前 包 的 东 西 。 最 初 的 当 前 包 叫 做 “main”,不 过 你 可 以 用 package 声 明 在 任 何 时 候 把 当 前 的 包 切 换 成 另 外 一 个 。 当 前 包 决 定 使 用 哪个 符 号 表 查 找 你 的 变 量 , 子 过 程 ,I/O 句 柄 和 格 式 等 。任 何 没 有 和 my 关 联 在 一 起 的 变 量 声 明 都 是 和 一 个 包 相 关 联 的 —— 甚 至 是 一 些 看 起 来 无所 不 在 的 变 量 , 比 如 $_ 和 %SIG。 实 际 上 , 在 <strong>Perl</strong> 里 实 际 上 没 有 全 局 变 量 这 样 的 东 西 。( 特 殊 的 标 识 符 , 比 如 _ 和 SIG, 只 是 看 上 去 象 全 局 变 量 , 因 为 它 们 缺 省 时 属 于 main 包 ,而 不 是 当 前 包 。)package 声 明 的 范 围 从 声 明 本 身 开 始 直 到 闭 合 范 围 的 结 束 ( 块 , 文 件 , 或 者 eval—— 以先 到 为 准 ) 或 者 直 到 其 他 同 层 次 的 package 声 明 , 它 会 取 代 前 面 的 那 个 。( 这 是 个 常 见的 实 践 。)所 有 随 后 的 标 识 符 ( 包 括 那 些 用 our 声 明 的 , 但 是 不 包 括 那 些 用 my 或 者 那 些 用 其 他 包名 字 修 饰 的 的 变 量 。) 都 将 放 到 属 于 当 前 包 的 符 号 表 中 。( 用 my 声 明 的 变 量 独 立 于 包 ;它 们 总 是 属 于 包 围 它 们 的 闭 合 范 围 , 而 且 也 只 属 于 这 个 范 围 , 不 管 有 什 么 包 声 明 。)通 常 , 一 个 package 声 明 如 果 是 一 个 文 件 的 第 一 个 语 句 的 话 就 意 味 着 它 将 被 require 或者 use 包 含 。 但 这 只 是 习 惯 , 你 可 以 在 任 何 可 以 放 一 条 语 句 的 地 方 放 一 个 package 声 明 。你 甚 至 可 以 把 它 放 在 一 个 块 的 结 尾 , 这 个 时 候 它 将 没 有 任 何 作 用 。 你 可 以 在 多 于 一 个 的 地 方切 换 到 一 个 包 里 面 ; 包 声 明 只 是 为 该 块 剩 余 的 部 分 选 择 将 要 使 用 的 符 号 表 。( 这 也 是 一 个 包实 现 跨 越 多 个 文 件 的 方 法 。)你 可 以 引 用 其 他 包 里 的 标 识 符 ( 注 : 我 们 说 的 标 识 符 的 意 思 是 用 做 符 号 表 键 字 的 东 西 , 可 以用 来 访 问 标 量 变 量 , 数 组 变 量 , 子 过 程 , 文 件 或 者 目 录 句 柄 , 以 及 格 式 等 。 从 语 法 上 来 说 ,标 签 (Label) 也 是 标 识 符 , 但 是 它 们 不 会 放 到 特 定 的 符 号 表 里 ; 相 反 , 它 们 直 接 附 着 在 你的 程 序 里 的 语 句 上 面 。 标 签 不 能 用 包 名 字 修 饰 。), 方 法 是 用 包 名 字 和 双 冒 号 做 前 缀 (“ 修饰 ”):$Package::Variable。 如 果 包 名 字 是 空 , 那 么 就 假 设 为 main 包 。 也 就 是 说 ,$::sail等 于 $main::sail。( 注 : 为 了 把 另 外 一 点 容 易 混 淆 的 概 念 理 清 楚 , 在 变 量 名 $main::sail里 , 我 们 对 main 和 sail 使 用 术 语 “ 标 识 符 ”, 但 不 把 main::sail 称 做 标 识 符 。 我 们 叫它 一 个 变 量 名 。 因 为 标 识 符 不 能 包 含 冒 号 。)266


老 的 包 分 隔 符 还 是 一 个 单 引 号 , 因 此 在 老 的 <strong>Perl</strong> 程 序 里 你 会 看 到 象 $main'sail 和$somepack'horse 这 样 的 变 量 。 不 过 , 双 冒 号 是 现 在 的 优 选 的 分 隔 符 , 部 分 原 因 是 因 为它 更 具 有 可 读 性 , 另 一 部 分 原 因 是 它 更 容 易 被 emacs 的 宏 读 取 。 而 且 这 样 表 示 也 令 C++程 序 员 觉 得 明 白 自 己 在 做 什 么 —— 相 比 之 下 , 用 单 引 号 的 时 候 就 能 让 Ada 的 程 序 员 知 道 自己 在 做 什 么 。 因 为 出 于 向 下 兼 容 的 考 虑 ,<strong>Perl</strong> 仍 然 支 持 老 风 格 的 语 法 , 所 以 如 果 你 试 图 使用 象 "This is $owner's house" 这 样 的 字 串 , 那 么 你 实 际 上 就 是 在 访 问 $owner::s; 也就 是 说 , 在 包 owner 里 的 $s 变 量 , 这 可 能 并 不 是 你 想 要 的 。 你 可 以 用 花 括 弧 来 消 除 歧义 , 就 象 "This is ${owner}'s house"。双 冒 号 可 以 用 于 把 包 名 字 里 的 标 识 符 链 接 起 来 :$Red::Blue::Var。 这 就 意 味 着 $var 属于 Red::Blue 包 。Red::Blue 包 和 任 何 可 能 存 在 的 Red 或 者 Blue 包 都 没 有 关 系 。 也就 是 说 , 在 Red::Blue 和 Red 或 者 Blue 之 间 的 关 系 可 能 对 那 些 书 写 或 使 用 这 个 程 序的 人 有 意 义 , 但 是 它 对 <strong>Perl</strong> 来 说 没 有 任 何 意 义 。( 当 然 , 在 当 前 的 实 现 里 , 符 号 表Red::Blue 碰 巧 存 储 在 Red 符 号 表 里 。 但 是 <strong>Perl</strong> 语 言 对 此 没 有 做 任 何 直 接 的 利 用 。)由 于 这 个 原 因 , 每 个 package 声 明 都 必 须 声 明 完 整 的 包 名 字 。 任 何 包 名 字 都 没 有 做 任 何隐 含 的 “ 前 缀 ” 的 假 设 , 甚 至 ( 看 起 来 象 ) 在 一 些 其 他 包 声 明 的 范 围 里 声 明 的 那 样 也 如 此 。只 有 标 识 符 ( 以 字 母 或 者 一 个 下 划 线 开 头 的 名 字 ) 才 存 储 在 包 的 符 号 表 里 。 所 有 其 他 符 号 都保 存 在 main 包 里 , 包 括 所 有 非 字 母 变 量 , 比 如 $!,$?, 和 $_。 另 外 , 在 没 有 加 以 修 饰的 时 候 , 标 识 符 STDIN,STDOUT,STDERR,ARGV,ARGVOUT,ENV,INC, 和 SIG都 强 制 在 包 main 里 , 即 使 你 是 用 做 其 他 目 的 , 而 不 是 用 做 它 们 的 内 建 功 能 也 如 此 。 不 要把 你 的 包 命 名 为 m,s,tr,q,qq,qr,qw, 或 者 qx, 除 非 你 想 自 找 一 大 堆 麻 烦 。 比 如 ,你 不 能 拿 修 饰 过 的 标 识 符 形 式 做 文 件 句 柄 , 因 为 它 将 被 解 释 成 一 个 模 式 匹 配 , 一 个 替 换 , 或者 一 个 转 换 。很 久 以 前 , 用 下 划 线 开 头 的 变 量 被 强 制 到 main 包 里 , 但 是 我 们 发 现 让 包 作 者 使 用 前 导 的下 划 线 作 为 半 私 有 的 标 识 符 标 记 更 有 用 , 这 样 它 们 就 可 以 表 示 为 只 被 该 包 内 部 使 用 。( 真 正私 有 的 变 量 可 以 声 明 为 文 件 范 围 的 词 汇 , 但 是 只 有 在 包 和 模 块 之 间 有 一 对 一 的 关 系 的 时 候 ,这 样 的 做 法 才 比 较 有 效 , 虽 然 这 样 的 一 对 一 比 较 普 遍 , 但 并 不 是 必 须 的 。)%SIG 散 列 ( 用 于 捕 获 信 号 ; 参 阅 第 十 六 章 , 进 程 间 通 讯 ) 也 是 特 殊 的 。 如 果 你 把 一 个 信号 句 柄 定 义 为 字 串 , 那 么 <strong>Perl</strong> 就 假 设 它 引 用 一 个 main 包 里 的 子 过 程 , 除 非 明 确 地 使 用了 其 他 包 名 字 。 如 果 你 想 声 明 一 个 特 定 的 包 , 那 么 你 要 使 用 一 个 信 号 句 柄 的 全 称 , 或 者 完 全避 免 字 串 的 使 用 : 方 法 是 改 为 赋 予 一 个 类 型 团 或 者 函 数 引 用 :$SIG{QUIT} = "Pkg::quit_chatcher";# 句 柄 全 称$SIG{QUIT} = "quit_catcher";# 隐 含 的 "main::quit_catcher"267


$SIG{QUIT} = *quit_catcher;$SIG{QUIT} = \&quit_catcher;# 强 制 为 当 前 包 的 子 过 程# 强 制 为 当 前 包 的 子 过 程$SIG{QUIT} = sub { print "Caught SIGQUIT\n" };# 匿 名 子 过 程“ 当 前 包 ” 的 概 念 既 是 编 译 时 的 概 念 也 是 运 行 时 的 概 念 。 大 多 数 变 量 名 查 找 发 生 在 编 译 时 , 但是 运 行 时 查 找 发 生 在 符 号 引 用 解 引 用 的 时 候 , 以 及 eval 分 析 新 的 代 码 的 时 候 。 实 际 上 ,在 你 eval 一 个 字 串 的 时 候 ,<strong>Perl</strong> 知 道 该 eval 是 在 哪 个 包 里 调 用 的 并 且 在 计 算 该 字 串 的时 候 把 那 个 包 名 字 传 播 到 eval 里 面 。( 当 然 , 你 总 是 可 以 在 eval 里 面 切 换 到 另 外 一 个包 , 因 为 一 个 eval 字 串 是 当 作 一 个 块 对 待 的 , 就 象 一 个 用 do,require, 或 者 use 装载 的 块 一 样 。)另 外 , 如 果 一 个 eval 想 找 出 它 在 哪 个 包 里 , 那 么 特 殊 的 符 号 PACKAGE 包 含 当 前 包 名字 。 因 为 你 可 以 把 它 当 作 一 个 字 串 看 待 , 所 以 你 可 以 把 它 用 做 一 个 符 号 引 用 来 访 问 一 个 包 变量 。 但 如 果 你 在 这 么 做 , 那 么 你 很 有 机 会 把 该 变 量 用 our 声 明 , 作 为 一 个 词 法 变 量 来 访 问 。10.1 符 号 表一 个 包 的 内 容 总 体 在 一 起 称 做 符 号 表 。 符 号 表 都 存 储 在 一 个 散 列 里 , 这 个 散 列 的 名 字 和 该 包的 名 字 相 同 , 但 是 后 面 附 加 了 两 个 冒 号 。 因 此 main 符 号 表 的 名 字 是 %main::。 因 为main 碰 巧 也 是 缺 省 的 包 ,<strong>Perl</strong> 把 %:: 当 作 %main:: 的 缩 写 。类 似 ,Red::Blue 包 的 符 号 表 名 字 是 %Red::Blue::。 同 时 main 符 号 表 还 包 含 所 有 其他 顶 层 的 符 号 表 , 包 括 它 本 身 。 因 此 %Red::Blue:: 同 时 也 是 %main::Red::Blue::。当 我 们 说 到 一 个 符 号 表 “ 包 含 ” 其 他 的 符 号 表 的 时 候 , 我 们 的 意 思 是 它 包 含 一 个 指 向 其 他 符 号表 的 引 用 。 因 为 main 是 顶 层 包 , 它 包 含 一 个 指 向 自 己 的 引 用 , 结 果 是 %main:: 和%main::main::, 和 %main::main::main::, 等 等 是 一 样 的 , 直 到 无 穷 。 如 果 你 写 的代 码 包 括 便 历 所 有 的 符 号 表 , 那 么 一 定 要 注 意 检 查 这 个 特 殊 的 情 况 。在 符 号 表 的 散 列 里 , 每 一 对 键 字 / 数 值 对 都 把 一 个 变 量 名 字 和 它 的 数 值 匹 配 起 来 。 键 字 是 符号 标 识 符 , 而 数 值 则 是 对 应 的 类 型 团 。 因 此 如 果 你 使 用 *NAME 表 示 法 , 那 么 你 实 际 上 只在 访 问 散 列 里 的 一 个 数 值 , 该 数 值 保 存 当 前 包 的 符 号 表 。 实 际 上 , 下 面 的 东 西 有 ( 几 乎 ) 一样 的 效 果 :*sym = *main::variable;*sym = $main::{"variable"};268


第 一 种 形 式 更 高 效 是 因 为 main 符 号 表 是 在 编 译 时 被 访 问 的 。 而 且 它 还 会 在 该 名 字 的 类 型团 不 存 在 的 时 候 创 建 一 个 新 的 , 但 是 第 二 种 则 不 会 。因 为 包 是 散 列 , 因 此 你 可 以 找 出 该 包 的 键 字 然 后 获 取 所 有 包 中 的 变 量 。 因 此 该 散 列 的 数 值 都是 类 型 团 , 你 可 以 用 好 几 种 方 法 解 引 用 。 比 如 :foreach $symname (sort keys %main::) {local *sym = $main::{$symname};print "\$$symname is defined\n" if defined $sym;print "\@$symname is nonnull\n" ifprint "\%$symname is nonnull\n" if@sym;%sym;}因 为 所 有 包 都 可 以 ( 直 接 或 间 接 地 ) 通 过 main 包 访 问 , 因 此 你 可 以 在 你 的 程 序 里 写 出 访问 每 一 个 包 变 量 的 <strong>Perl</strong> 代 码 。 当 你 用 v 命 令 要 求 <strong>Perl</strong> 调 试 器 倾 倒 所 有 你 的 变 量 的 时 候 ,它 干 的 事 情 就 是 这 个 。 请 注 意 , 如 果 你 做 这 些 事 情 , 那 么 你 将 看 不 到 用 my 声 明 的 变 量 ,因 为 它 们 都 是 独 立 于 包 的 , 不 过 你 看 得 到 用 our 声 明 的 变 量 。 参 阅 第 二 十 章 ,<strong>Perl</strong> 调 试器 。早 些 时 候 我 们 说 过 除 了 在 main 里 , 其 他 的 包 里 只 能 存 储 标 识 符 。 我 们 在 这 里 撒 了 个 小 慌 :你 可 以 在 一 个 符 号 表 散 列 里 使 用 任 何 你 需 要 的 字 串 作 为 键 字 —— 只 不 过 如 果 你 企 图 直 接 使用 一 个 非 标 识 符 的 时 候 它 就 不 是 有 效 的 <strong>Perl</strong>:$!@#$% = 0; # 错 , 语 法 错#{'!@#$%'} = 1; # 正 确 , 用 的 是 未 修 饰 的${'main::!@#$%'} = 2; # 可 以 在 字 串 里 修 饰 。print ${ $main::{'!@#$%'}} # 正 确 , 打 印 2给 一 个 匿 名 类 型 团 赋 值 执 行 一 个 别 名 操 作 ; 也 就 是 ,*dick= *richard;导 致 所 有 可 以 通 过 标 识 符 richard 访 问 的 变 量 , 子 过 程 , 格 式 , 文 件 和 目 录 句 柄 也 可 以 通过 符 号 dick 访 问 。 如 果 你 只 需 要 给 一 个 特 定 的 变 量 或 者 子 过 程 取 别 名 , 那 么 使 用 一 个 引 用 :269


*dick = \$richard;这 样 就 令 $richard 和 $dick 成 为 同 样 的 变 量 , 但 是 @richard 和 @dick 则 剩 下 来 是独 立 的 数 组 。 很 高 明 , 是 吗 ?这 也 是 Exporter 在 从 一 个 包 向 另 外 一 个 包 输 入 符 号 的 时 候 采 用 的 方 法 。 比 如 :*SomePack::dick = \&OtherPack::richard;从 包 OtherPack ? 输 入 &richard 函 数 到 SomePack ? , 让 它 可 以 当 作 &dick 函 数 用 。( Exporter 模 块 在 下 一 章 描 述 。) 如 果 你 用 一 个 local 放 在 赋 值 前 面 , 那 么 , 该 别 名 将只 持 续 到 当 前 动 态 范 围 结 束 。这 种 机 制 可 以 用 于 从 一 个 子 过 程 中 检 索 一 个 引 用 , 令 该 引 用 可 以 用 做 一 个 合 适 的 数 据 类 型 :*units = populate();# 把 \%newhash 赋 予 类 型 团print $units{kg}; # 打 印 70; 而 不 用 解 引 用 !sub populate {my %newhash = (km => 10, kg => 70);return \%newhash;}类 似 , 您 还 可 以 把 一 个 引 用 传 递 到 一 个 引 用 传 递 到 一 个 子 过 程 里 并 且 不 加 解 引 用 地 使 用 它 :%units = (miles => 6, stones => 11);fillerup( \%units );# 传 递 进 一 个 引 用print $units{quarts}; # 打 印 4sub fillerup {local *hashsym = shift;# 把 \%units 赋 予 该 类 型 团$hashsym{quarts} = 4; # 影 响 \%units; 不 需 要 解 引 用 !}270


上 面 这 些 都 是 廉 价 传 递 引 用 的 巧 妙 方 法 , 用 在 你 不 想 明 确 地 对 它 们 进 行 解 引 用 的 时 候 。 请 注意 上 面 两 种 技 巧 都 是 只 能 对 包 变 量 管 用 ; 如 果 我 们 用 my 声 明 了 %units 那 么 它 们 不 能运 行 。另 外 一 个 符 号 表 的 用 法 是 制 作 “ 常 量 ” 标 量 :*PI = \3.14159265358979;现 在 你 不 能 更 改 $PI, 不 管 怎 么 说 这 样 做 可 能 是 好 事 情 。 它 和 常 量 子 过 程 不 一 样 , 常 量 子过 程 在 编 译 时 优 化 。 常 量 子 过 程 是 一 个 原 型 定 义 为 不 接 受 参 数 并 且 返 回 一 个 常 量 表 达 式 的 子过 程 ; 参 阅 第 六 章 , 子 过 程 , 的 “ 内 联 常 量 函 数 ” 获 取 细 节 。use constant 用 法 ( 参 阅 第 三十 一 章 , 用 法 模 块 ) 是 一 个 方 便 的 缩 写 :use constant PI => 3.14159;在 这 个 钩 子 下 面 , 它 使 用 *PI 的 子 过 程 插 槽 , 而 不 是 前 面 用 的 标 量 插 槽 。 它 等 效 于 更 紧 凑( 不 过 易 读 性 稍 微 差 些 ):*PI = sub () {3.14159};不 过 , 这 是 一 个 很 值 得 知 道 的 俗 语 —— 把 一 个 sub {} 赋 予 一 个 类 型 团 是 在 运 行 时 给 匿 名子 过 程 赋 予 一 个 名 字 的 方 法 。把 一 个 类 型 团 引 用 赋 予 另 外 一 个 类 型 团 (*sym = \*oldvar) 和 赋 予 整 个 类 型 团 是 一 样 的 。并 且 如 果 你 把 类 型 团 设 置 为 一 个 简 单 的 字 串 , 那 么 你 就 获 得 了 该 字 串 命 名 的 整 个 类 型 团 , 因为 <strong>Perl</strong> 在 当 前 符 号 表 中 寻 找 该 字 串 。 下 面 的 东 西 互 相 都 是 一 样 的 , 不 过 头 两 个 在 编 译 时 计算 符 号 表 记 录 , 而 后 面 两 个 是 在 运 行 时 :*sym =*oldvar;*sym = \*oldvar;*sym = *{"oldvar"};*sym = "oldvar";# 自 动 解 引 用# 明 确 的 符 号 表 查 找# 隐 含 地 符 号 表 查 找当 你 执 行 任 意 下 列 的 赋 值 的 时 候 , 你 实 际 上 只 是 替 换 了 类 型 团 里 的 一 个 引 用 :*sym = \$frodo;*sym = \@sam;*sym = \%merry;271


*sym = \&pippin;如 果 你 从 另 外 一 个 角 度 来 考 虑 问 题 , 类 型 团 本 身 可 以 看 作 一 种 散 列 , 它 里 面 有 不 同 类 型 的 变量 记 录 。 在 这 种 情 况 下 , 键 字 是 固 定 的 , 因 为 一 个 类 型 团 正 好 可 以 包 含 一 个 标 量 , 一 个 散 列 ,等 等 。 但 是 你 可 以 取 出 独 立 的 引 用 , 象 这 样 :*pkg::sym{SCALAR}*pkg::sym{ARRAY}*pkg::sym{HASH}*pkg::sym{CODE}*pkg::sym{GLOB}*pkg::sym{IO}# 和 \$pkg::sym 一 样# 和 \@pkg::sym 一 样# 和 \%pkg::sym 一 样# 和 \&pkg::sym 一 样# 和 \*pkg::sym 一 样# 内 部 的 文 件 / 目 录 句 柄 , 没 有 直 接 的 等 价 物*pkg::sym{NAME} # “sym”( 不 是 引 用 )*pkg::sym{PACKAGE} # “pkg”( 不 是 引 用 )你 可 以 通 过 说 *foo{PACKAGE} 和 *foo{NAME} 找 出 *foo 符 号 表 记 录 来 自 哪 个 名字 和 包 。 这 个 功 能 对 那 些 传 递 类 型 团 做 参 数 的 子 过 程 里 很 有 用 :sub identify_typeglob {my $glob = shift;print 'You gave me ', *{glob}{PACKAGE}, '::', *{$globa}{NAME}, "\n";}identify_typeglob(*foo);identify_typeglob(*bar::glarch);它 打 印 出 :You gave me main::fooYou gave me bar::glarch272


*foo{THING} 表 示 法 可 以 用 于 获 取 指 向 *foo 的 独 立 元 素 的 引 用 。 参 阅 第 八 章 的 “ 符 号 表引 用 ” 一 节 获 取 细 节 。这 种 语 法 主 要 用 于 获 取 内 部 文 件 句 柄 或 者 目 录 句 柄 引 用 , 因 为 其 他 内 部 引 用 已 经 都 可 以 用 其他 方 法 访 问 。( 老 的 *foo{FILEHANDLE} 形 式 仍 然 受 支 持 , 表 示 *foo{IO}, 但 是 不 要让 这 个 名 字 把 你 涮 了 , 它 可 不 能 把 文 件 句 柄 和 目 录 句 柄 区 别 开 。) 但 是 我 们 认 为 应 该 概 括 它 ,因 为 它 看 起 来 相 当 漂 亮 。 当 然 , 你 可 能 根 本 不 用 记 住 这 些 东 西 , 除 非 你 想 再 写 一 个 <strong>Perl</strong> 调试 器 。10.2 自 动 装 载通 常 , 你 不 能 调 用 一 个 没 有 定 义 的 子 过 程 。 不 过 , 如 果 在 未 定 义 的 子 过 程 的 包 ( 如 果 是 在 对象 方 法 的 情 况 下 , 在 任 何 该 对 象 的 基 类 的 包 里 ) 里 有 一 个 子 过 程 叫 做 AUTOLOAD, 那 么就 会 调 用 AUTOLOAD 子 过 程 , 同 时 还 传 递 给 它 原 本 传 递 给 最 初 子 过 程 的 同 样 参 数 。 你 可以 定 义 AUTOLOAD 子 过 程 返 回 象 普 通 子 过 程 那 样 的 数 值 , 或 者 你 可 以 让 它 定 义 还 不 存 在的 子 过 程 然 后 再 调 用 它 , 就 好 象 该 子 过 程 一 直 存 在 一 样 。最 初 的 子 过 程 的 全 称 名 会 神 奇 地 出 现 在 包 全 局 变 量 $AUTOLOAD 里 , 该 包 和AUTOLOAD 所 在 的 包 是 同 一 个 包 。 下 面 是 一 个 简 单 的 例 子 , 它 会 礼 貌 地 警 告 你 关 于 未 定义 的 子 过 程 调 用 , 而 不 是 退 出 :sub AUTOLOAD {our $AUTOLOAD;warn "Attempt to call $AUTOLOAD failed.\n";}blarg(10);# 我 们 的 $AUTOLOAD 将 会 设 置 为 main::blargprint "Still alive!\n";或 者 你 可 以 代 表 该 未 定 义 的 子 过 程 返 回 一 个 数 值 :sub AUTOLOAD {our $AUTOLOAD;return "I see $AUTOLOAD(@_)\n":273


}print blarg(20);# 打 印 :I see main::blarg(20)你 的 AUTOLOAD 子 过 程 可 以 用 eval 或 者 require 为 该 未 定 义 的 子 过 程 装 载 一 个 定义 , 或 者 是 用 我 们 前 面 讨 论 过 的 类 型 团 赋 值 的 技 巧 , 然 后 使 用 特 殊 形 式 的 goto 执 行 该 子过 程 , 这 种 goto 可 以 不 留 痕 迹 地 抹 去 AUTOLOAD 过 程 的 堆 栈 桢 。 下 面 我 们 通 过 给 类 型团 赋 予 一 个 闭 合 来 定 义 该 子 过 程 :sub AUTOLOAD {my $name = our $AUTOLOAD;*$AUTOLOAD = sub { print "I see $name(@_)\n"};goto &$AUTOLOAD; # 重 起 这 个 新 过 程 。}blarg(30);blarg(40);blarg(50);# 打 印 :I see main::blarg(30)# 打 印 :I see main::blarg(40)# 打 印 :I see main::blarg(50)标 准 的 AutoSplit ? 模 块 被 模 块 作 者 用 于 把 他 们 的 模 块 分 裂 成 独 立 的 文 件 ( 用 以 .al 结 尾的 文 件 ), 每 个 保 存 一 个 过 程 。 该 文 件 放 在 你 的 系 统 的 <strong>Perl</strong> 库 的 auto/ 目 录 里 , 在 那 之后 该 文 件 可 以 根 据 标 准 的 AutoLoader ? 模 块 的 需 要 自 动 装 载 。一 种 类 似 的 方 法 被 SelfLoader ? 模 块 使 用 , 只 不 过 它 从 该 文 件 自 己 的 DATA 区 自 动 装 载函 数 , 从 某 种 角 度 来 看 , 它 的 效 率 要 差 一 些 , 但 是 从 其 他 角 度 来 看 , 它 的 效 率 又 比 较 高 。 用AutoLoader ? 和 SelfLoader ? 自 动 装 载 <strong>Perl</strong> 函 数 是 对 通 过 DynaLoader ? 动 态 装 载 编译 好 的 C 函 数 的 模 拟 , 只 不 过 自 动 装 载 是 以 函 数 调 用 的 粒 度 进 行 实 现 的 , 而 动 态 装 载 是 以整 个 模 块 的 粒 度 进 行 装 载 的 , 并 且 通 常 会 一 次 链 接 进 入 若 干 个 C 或 C++ 函 数 。( 请 注 意许 多 <strong>Perl</strong> 程 序 员 不 用 AutoSplit ? ,AutoLoader,SelfLoader, 或 者 DynaLoader ? 模块 过 得 也 很 好 。 你 只 需 要 知 道 它 们 的 存 在 , 以 防 哪 天 你 不 用 它 还 真 解 决 不 了 问 题 。)我 们 可 以 在 把 AUTOLOAD 过 程 当 作 其 他 接 口 的 封 装 器 中 获 取 许 多 乐 趣 。 比 如 , 让 我 们 假设 任 何 没 有 定 义 的 函 数 应 该 就 是 哪 它 的 参 数 调 用 system。 你 要 做 的 就 是 :274


sub AUTOLOAD {my $program = our $AUTOLOAD;$program =~ s/.*:://;# 截 去 包 名 字system($program, @_);}( 恭 喜 , 你 刚 刚 实 现 了 和 <strong>Perl</strong> 一 起 发 布 的 Shell 模 块 的 一 种 冗 余 的 形 式 。) 你 可 以 象 下面 这 样 调 用 你 的 自 动 装 载 器 ( 在 Unix 里 ):date();who('am', 'i');is('-l');echo("Abadugabuadaredd...");实 际 上 , 如 果 你 预 先 按 照 这 种 方 法 声 明 你 想 要 调 用 的 函 数 , 那 么 你 就 可 以 认 为 它 们 是 内 建 的函 数 并 且 在 调 用 的 时 候 忽 略 圆 括 弧 :sub date (;$$); # 允 许 零 到 两 个 参 数 。sub who (;$$$$);sub ls;sub echo ($@);# 允 许 零 到 四 个 参 数# 允 许 任 意 数 量 的 参 数# 允 许 至 少 一 个 参 数date;who "am", "i";ls "-l";echo "That's all, folks!";275


第 十 一 章 模 块模 块 是 <strong>Perl</strong> 里 重 复 使 用 的 基 本 单 元 。 在 它 的 外 皮 下 面 , 它 只 不 过 是 定 义 在 一 个 同 名 文 件( 以 .pm 结 尾 ) 里 面 的 包 。 本 章 里 , 我 们 将 探 究 如 何 使 用 别 人 的 模 块 以 及 创 建 你 自 己 的 模块 。<strong>Perl</strong> 是 和 一 大 堆 模 块 捆 绑 在 一 起 安 装 的 , 你 可 以 在 你 用 的 <strong>Perl</strong> 版 本 的 lib 目 录 里 找 到 它们 。 那 里 面 的 许 多 模 块 将 在 第 三 十 二 章 , 标 准 模 块 , 和 第 三 十 一 章 , 用 法 模 块 里 描 述 。 所 有标 准 模 块 都 还 有 大 量 的 在 线 文 档 , 很 可 能 比 这 本 书 更 新 。 如 果 你 的 man 命 令 里 没 有 更 丰富 的 东 西 , 那 么 请 试 着 使 用 perldoc 命 令 。综 合 <strong>Perl</strong> 库 网 络 (CPAN) 是 包 含 全 世 界 的 <strong>Perl</strong> 社 区 所 贡 献 的 <strong>Perl</strong> 模 块 的 仓 库 , 我 们将 在 第 二 十 二 章 ,CPAN 里 介 绍 它 。 同 样 也 请 参 阅 http://www.cpan.org。11.1 使 用 模 块模 块 有 两 种 风 格 : 传 统 的 和 面 向 对 象 的 。 传 统 模 块 为 调 用 者 的 输 入 和 使 用 定 义 了 子 过 程 和 变量 。 面 向 对 象 的 模 块 的 运 转 类 似 类 声 明 并 且 是 通 过 方 法 调 用 来 访 问 的 , 在 第 十 二 章 , 对 象 ,里 描 述 。 有 些 模 块 有 上 面 两 种 类 型 的 东 西 。<strong>Perl</strong> 模 块 通 常 用 下 面 的 语 句 包 含 入 你 的 程 序 :use MODULE LIST;或 者 只 是 :use MODULE;MODULE 必 须 是 一 个 命 名 模 块 的 包 和 文 件 的 标 识 符 。( 这 里 描 述 的 语 法 只 是 建 议 性 的 ;use语 句 的 详 细 描 述 在 第 二 十 九 章 , 函 数 , 里 。)use 语 句 在 编 译 的 时 候 对 MODULE 进 行 一 次 预 装 载 , 然 后 把 你 需 要 的 符 号 输 入 进 来 , 这样 剩 下 的 编 译 过 程 就 可 以 使 用 这 些 符 号 了 。 如 果 你 没 提 供 你 想 要 的 符 号 的 LIST ( 列 表 ),那 么 就 使 用 在 模 块 的 内 部 @EXPORT 数 组 里 命 名 的 符 号 —— 假 设 你 在 用 Exporter 模块 , 有 关 Exporter 的 内 容 在 本 章 稍 后 的 “ 模 块 私 有 和 输 出 器 ” 里 介 绍 。( 如 果 你 没 有 提 供276


LIST, 那 么 所 有 你 的 符 号 都 必 须 在 模 块 的 @EXPORT 或 者 @EXPORT_OK 数 组 里 提 及 ,否 则 否 则 就 会 发 生 一 个 错 误 。)因 为 模 块 使 用 Exporter 把 符 号 输 入 到 当 前 包 里 , 所 以 你 可 以 不 加 包 限 制 词 地 使 用 来 自 该模 块 的 符 号 :use Fred;flintstone();# 如 果 Fred.pm 有 @EXPORT = qw(flintstone)# ... 这 里 调 用 Fred::flintstone()。所 有 <strong>Perl</strong> 的 模 块 文 件 都 有 .pm 的 扩 展 名 。use 和 require 都 做 这 种 假 定 ( 和 引 起 ),因 此 你 不 用 说 "MODULE.pm"。 只 使 用 描 述 符 可 以 帮 助 我 们 把 新 模 块 和 老 版 本 的 <strong>Perl</strong> 中使 用 用 的 .pl 和 .ph 库 区 别 开 。 它 还 把 MODULE 当 作 一 个 正 式 模 块 名 , 这 样 可 以 在 某些 有 歧 义 的 场 合 帮 助 分 析 器 。 在 模 块 名 字 中 的 任 何 双 冒 号 都 被 解 释 成 你 的 系 统 的 目 录 分 隔符 , 因 此 如 果 你 的 模 块 的 名 字 是 Red::Blue::Green,<strong>Perl</strong> 就 会 把 它 看 作Red/Blue/Green.pm。<strong>Perl</strong> 将 在 @INC 数 组 里 面 列 出 的 每 一 个 目 录 里 面 查 找 模 块 。 因 为 use 在 编 译 的 时 候 装 载模 块 , 所 以 任 何 对 @INC 的 修 改 都 需 要 在 编 译 时 发 生 。 你 可 以 使 用 第 三 十 一 章 里 描 述 的 lib用 法 或 者 一 个 BEGIN 块 来 实 现 这 个 目 的 。 一 旦 包 含 了 一 个 模 块 , 那 么 就 会 向 %INC 哈希 表 里 增 加 一 个 键 字 / 数 值 对 。 这 里 的 键 字 将 是 模 块 的 文 件 名 ( 在 我 们 的 例 子 中 是Red/Blue/Green.pm) 而 数 值 将 是 全 路 径 名 。 如 果 是 在 一 个 windows 系 统 上 合 理 安 装的 模 块 , 这 个 路 径 可 能 是 C:/perl/site/lib/Red/Blue/Green.pm。除 非 模 块 起 用 法 的 作 用 , 否 则 它 们 的 名 字 应 该 首 字 母 大 写 。 用 法 是 有 效 的 编 译 器 指 示 器 ( 给编 译 器 的 提 示 ), 因 此 我 们 把 小 写 的 用 法 名 字 留 给 将 来 使 用 。当 你 use 一 个 模 块 的 时 候 , 在 模 块 里 的 所 有 代 码 都 得 到 执 行 , 就 好 象 require 里 的 通 常情 况 一 样 。 如 果 你 不 在 乎 模 块 是 在 编 译 的 时 候 还 是 在 运 行 的 时 候 引 入 的 , 你 可 以 只 说 :require MODULE;不 过 , 通 常 我 们 更 愿 意 用 use 而 不 是 require , 因 为 它 在 编 译 的 时 候 就 查 找 模 块 , 因 此你 可 以 更 早 知 道 有 没 有 问 题 。下 面 的 两 个 语 句 做 几 乎 完 全 一 样 的 事 情 :require MODULE;require "MODULE.pm";277


不 过 , 它 们 在 两 个 方 面 不 太 一 样 。 在 第 一 个 语 句 里 ,require 把 模 块 名 字 里 的 任 何 双 冒 号转 换 成 你 的 系 统 的 目 录 分 隔 符 , 就 象 use 那 样 。 第 二 种 情 况 不 做 转 换 , 强 制 你 在 文 本 上 声明 你 的 模 块 的 路 径 名 , 这 样 移 植 性 比 较 差 。 另 外 一 个 区 别 是 第 一 个 require 告 诉 编 译 器 说 ,带 有 关 于 "MODULE" 的 间 接 对 象 符 号 的 表 达 式 ( 比 如 $ob = purgre MODULE) 都 是模 块 调 用 , 而 不 是 函 数 调 用 。( 如 果 在 你 自 己 的 模 块 里 有 冲 突 的 purge 定 义 , 那 么 这 里 就有 区 别 了 。)因 为 use 声 明 和 相 关 的 no 声 明 都 隐 含 有 一 个 BEGIN 块 , 编 译 器 就 会 一 看 到 这 个 声 明就 装 载 这 个 模 块 ( 并 且 运 行 里 面 的 任 何 可 执 行 初 始 化 代 码 ), 然 后 才 编 译 剩 下 的 文 件 。 这 就是 用 法 如 何 改 变 编 译 器 的 性 质 的 方 法 , 以 及 为 什 么 模 块 可 以 声 明 一 些 子 过 程 , 这 些 子 过 程 可以 作 为 列 表 操 作 符 用 于 剩 下 的 编 译 过 程 。 如 果 你 用 require 代 替 use, 这 些 事 情 就 不 会 发生 。 使 用 require 的 唯 一 原 因 就 是 你 有 两 个 模 块 , 这 两 个 模 块 都 需 要 来 自 对 方 的 函 数 。( 我们 不 知 道 这 是 不 是 个 好 理 由 。)<strong>Perl</strong> 模 块 总 是 装 载 一 个 .pm 文 件 , 但 是 这 个 文 件 随 后 可 以 装 载 相 关 的 文 件 , 比 如 动 态 链接 的 C 或 C++ 库 或 者 自 动 装 载 的 <strong>Perl</strong> 子 过 程 定 义 。 如 果 是 这 样 , 那 么 附 加 的 东 西 对 模块 用 户 而 言 是 完 全 透 明 的 。 装 载 ( 或 者 安 排 自 动 ) 任 何 附 加 的 函 数 或 功 能 的 责 任 在 .pm 文件 。 正 巧 是 POSIX 模 块 动 态 装 载 和 自 动 装 载 两 种 方 法 都 要 用 , 不 过 用 户 可 以 只 说 :use POSIX;就 可 以 获 取 所 有 输 出 了 的 函 数 和 变 量 。11.2 创 建 模 块我 们 前 面 说 过 , 一 个 模 块 可 以 有 两 个 方 法 把 它 的 接 口 提 供 给 你 的 程 序 使 用 : 把 符 号 输 出 或 者允 许 方 法 调 用 。 我 们 在 这 里 先 给 你 演 示 一 个 第 一 种 风 格 的 例 子 ; 第 二 种 风 格 用 于 面 向 对 象 的模 块 , 我 们 将 在 下 一 章 里 描 述 。( 面 向 对 象 的 模 块 应 该 不 输 出 任 何 东 西 , 因 为 方 法 最 重 要 的概 念 就 是 <strong>Perl</strong> 以 该 对 象 的 类 型 为 基 础 自 动 帮 你 找 到 方 法 自 身 。)构 造 一 个 叫 Bestiary 的 模 块 , 创 建 一 个 看 着 象 下 面 这 样 的 叫 Bestiary.pm 的 文 件 :packagerequireBestiary;Exporter;our @ISA=qw(Exporter);our @EXPORT =qw(camel); # 缺 省 输 出 的 符 号278


our @EXPORT_OK =qw($weight); # 按 要 求 输 出 的 符 号our $VERSION = 1.00; # 版 本 号### 在 这 里 包 含 你 的 变 量 和 函 数sub camel { print "One-hump dromedary" }$weight = 1024;1;一 个 程 序 现 在 可 以 说 use Bestiary 就 能 访 问 camel 函 数 ( 但 是 不 能 访 问 $weight 变量 ), 或 者 use Bestiary qw(camel, $weight) 可 以 访 问 函 数 和 变 量 。你 还 可 以 创 建 动 态 装 载 C 写 的 代 码 的 模 块 。 参 阅 第 二 十 一 章 , 内 部 和 外 部 , 获 取 细 节 。11.2.1 模 块 私 有 和 输 出 器<strong>Perl</strong> 不 会 自 动 在 它 的 模 块 的 私 有 / 公 有 边 界 上 进 行 检 查 —— 和 C++,JAVA 和 Ada 这 样的 语 言 不 同 ,<strong>Perl</strong> 不 会 被 强 加 的 私 有 性 质 搞 糊 涂 。<strong>Perl</strong> 希 望 你 呆 在 她 的 起 居 室 外 面 是 因 为你 没 有 收 到 邀 请 , 而 不 是 因 为 你 拿 着 一 把 手 枪 。<strong>Perl</strong> 模 块 和 其 用 户 之 间 有 一 种 约 定 , 有 一 部 分 是 常 见 规 则 而 另 外 一 部 分 是 单 写 的 。 常 见 规则 部 分 约 定 是 说 禁 止 一 个 模 块 修 改 任 何 没 有 允 许 它 修 改 的 名 字 空 间 。 为 模 块 单 写 的 约 定 ( 也就 是 文 档 ) 可 以 有 其 他 约 束 。 不 过 , 如 果 你 读 过 约 定 以 后 , 我 们 就 假 设 你 知 道 自 己 说 useReadfineTheWorld ? 的 时 候 就 是 在 重 定 义 世 界 , 并 且 你 愿 意 面 对 其 后 果 。 重 定 义 世 界 的 最常 用 的 方 法 是 使 用 Exporter 模 块 。 我 们 稍 后 在 本 章 中 就 能 看 到 , 你 甚 至 可 以 用 这 个 模 块重 定 义 内 建 的 东 西 。当 你 use 一 个 模 块 , 通 常 是 这 个 模 块 提 供 了 你 的 程 序 可 以 使 用 的 几 个 函 数 或 者 变 量 , 或 者更 准 确 地 说 , 为 你 的 程 序 的 当 前 包 提 供 了 函 数 和 变 量 。 这 种 从 模 块 输 出 符 号 ( 并 且 把 它 们 输入 到 你 的 程 序 里 ) 的 动 作 有 时 候 被 称 为 污 染 你 的 名 字 空 间 。 大 多 数 模 块 使 用 Exporter 来做 这 些 事 情 ; 这 就 是 为 什 么 在 接 近 顶 端 的 地 方 说 这 些 东 西 :279


equire Exporter;our @ISA = ("Exporter");这 两 行 令 该 模 块 从 Exporter 类 中 继 承 下 来 。 我 们 在 下 一 章 讲 继 承 , 但 在 这 里 你 要 知 道 的所 有 东 西 就 是 我 们 的 Bestiary 模 块 现 在 可 以 用 类 似 下 面 的 行 把 符 号 输 出 到 其 他 包 里 :our @EXPORT =qw($camel %wolf ram); # 缺 省 输 出our @EXPORT =qw(leopard @llama $emu); # 请 求 时 输 出our %EXPORT_TAGS = (camelids => [qw($camel @llama)],critters => [qw(ram $camel %wolf)],);从 输 出 模 块 的 角 度 出 发 ,@EXPORT 数 组 包 含 缺 省 时 要 输 出 的 变 量 和 函 数 的 名 字 : 当 你 的程 序 说 use Bestary 的 时 候 得 到 的 东 西 。 在 @EXPORT_OK 里 的 变 量 和 函 数 只 有 当 程 序在 use 语 句 里 面 特 别 要 求 它 们 的 时 候 才 输 出 。 最 后 , %EXPORT_TAGS 里 的 键 字 / 数 值对 允 许 程 序 包 含 那 些 在 @EXPORT 和 @EXPORT_OK 里 面 列 出 的 特 定 的 符 号 组 。从 输 入 包 的 角 度 出 发 ,use 语 句 声 明 了 一 列 可 以 输 入 的 符 号 , 一 组 在 %EXPORT_TAGS里 面 的 名 字 , 一 个 符 号 的 模 式 或 者 什 么 也 没 有 , 这 时 在 @EXPORT 里 的 符 号 将 从 模 块 里 输入 到 你 的 程 序 里 。你 可 以 包 含 任 意 的 这 些 语 句 , 从 Bestiary 模 块 里 输 入 符 号 :use Bestiary;use Bestiary();# 输 入 @EXPORT 符 号# 什 么 也 不 输 入use Bestiary qw(ram @llama);use Bestiary qw(:camelids);use Bestiary qw(:DEFAULT);use Bestiary qw(/am/);use Bestiary qw(/^\$/);# 输 入 ram 函 数 和 @llama 数 组# 输 入 $camel 和 @llama# 输 入 @EXPORT 符 号# 输 入 $camle,@llama, 和 ram# 输 入 所 有 标 量use Bestiary wq(:critters !ram);# 输 入 citters 但 是 把 ram 排 除280


use Bestiary wq(:critters !:camelids);# 输 入 critters, 但 是 不 包 括 camelids把 一 个 符 号 排 除 在 输 出 列 表 之 外 ( 或 者 用 感 叹 号 明 确 地 从 输 入 列 表 里 删 除 ) 并 不 会 让 使 用 模块 的 程 序 无 法 访 问 它 。 该 程 序 总 是 可 以 通 过 带 完 整 修 饰 词 的 包 名 来 访 问 模 块 的 包 的 内 容 , 比如 %Bestiary::gecko。( 因 为 词 法 变 量 不 属 于 包 , 所 以 私 有 属 性 仍 然 可 实 现 : 参 阅 下 一章 的 “ 私 有 方 法 ”。)你 可 以 说 BEGIN {$Exporter::Verbose=1 }, 这 样 就 可 以 看 到 声 明 是 如 何 处 理 的 , 以及 实 际 上 有 什 么 东 西 输 入 到 你 的 包 里 。Exporter 本 身 是 一 个 <strong>Perl</strong> 模 块 , 如 果 你 觉 得 奇 怪 , 你 可 以 看 看 类 型 团 巧 妙 地 使 用 它 把 符号 从 一 个 包 输 出 的 另 一 个 包 , 在 Export 模 块 里 , 起 关 键 作 用 的 函 数 叫 import, 它 做 一些 必 要 的 别 名 工 作 , 把 一 个 包 里 的 符 号 体 现 在 另 外 一 个 包 里 。 实 际 上 , 一 个 use BestiaryLIST 语 句 和 下 面 的 语 句 完 全 一 样 :BEGIN {require Bestiary;import Bestiary LIST;}这 意 味 着 你 的 模 块 并 不 一 定 要 使 用 Exporter。 当 你 使 用 一 个 模 块 的 时 候 , 它 可 以 做 任 何 它喜 欢 干 的 事 情 , 因 为 use 只 是 为 那 个 模 块 调 用 普 通 的 import 方 法 , 而 你 可 以 把 这 个 方 法定 义 为 处 理 任 何 你 想 干 的 事 情 。11.2.1.1 不 用 Exporter 的 输 入 方 法 进 行 输 出Export 定 义 一 个 叫 export_to_level 的 方 法 , 用 于 你 ( 因 为 某 些 原 因 ) 不 能 直 接 调 用Exporter 的 import 方 法 的 情 况 下 。export_to_level 方 法 用 下 面 的 方 法 调 用 :MODULE->export_to_level($where_to_export, @what_to_export);这 里 的 $where_to_export 是 一 个 整 数 , 标 识 调 用 模 块 的 堆 栈 把 你 的 符 号 输 出 了 多 远 ,而 @what_to_export 是 是 一 个 数 组 , 里 面 列 出 要 输 出 的 所 有 符 号 ( 通 常 是 @_)。比 如 , 假 设 我 们 的 Bestiary 有 一 个 自 己 的 import 函 数 :package Bestiary; @ISA = qw(Exporter); @EXPORT_OK = qw($zoo);281


sub import { $Bestiary::zoo = "menagerie"; }这 个 import 函 数 的 存 在 抑 制 了 对 Exporter 的 import 函 数 的 继 承 。 如 果 你 希 望Bestiary 的 import 函 数 在 设 置 了 $Bestiary::zoo 之 后 的 的 性 质 和 Exporter 的import 函 数 一 样 , 那 么 你 应 该 象 下 面 那 样 定 义 它 :sub import { $Bestiary::zoo = "menagerie";Bestiary->export_to_level(1,@_); }这 样 就 把 符 号 符 号 从 当 前 包 输 出 到 “ 上 面 ” 一 层 的 包 里 。 也 就 是 说 , 输 出 到 使 用 Bestiary 的程 序 或 者 模 块 里 。11.2.1.2 版 本 检 查如 果 你 的 模 块 定 义 了 一 个 $VERSION 变 量 , 使 用 你 的 模 块 的 程 序 可 以 识 别 该 模 块 足 够 新 。比 如 :use Bestiary 3.14;use Bestiary v1.0.4;# Bestiary 必 须 是 版 本 3.14 或 者 更 新# Bestiary 必 须 是 版 本 1.0.4 或 者 更 新这 些 东 西 都 转 换 成 对 Bestiary->require_version 的 调 用 , 然 后 你 的 模 块 就 继 承 了 它 们 。11.2.1.3 管 理 未 知 符 号有 时 候 , 你 可 能 希 望 避 免 某 些 符 号 的 输 出 。 通 常 这 样 的 情 况 出 现 在 你 的 模 块 里 有 一 些 函 数 或者 约 束 对 某 些 系 统 而 言 没 有 什 么 用 的 时 候 。 你 可 以 通 过 把 它 们 放 在 @EXPORT_FAIL 数 组里 面 避 免 把 这 些 符 号 输 出 。如 果 一 个 程 序 想 输 入 这 些 符 号 中 的 任 何 一 个 , Exporter 在 生 成 一 个 错 误 之 前 给 模 块 一 个处 理 这 种 情 况 的 机 会 。 它 通 过 带 着 一 个 失 败 符 号 列 表 调 用 export_fail 的 方 法 来 实 现 这 个目 的 , 你 可 以 这 样 定 义 export_fail( 假 设 你 的 模 块 使 用 Carp 模 块 ):sub export_fail {my $class = shift;carp "Sorry, these symblos are unavailable: @_";return @_;}282


Exporter 提 供 缺 省 的 export_fail 方 法 , 它 只 是 简 单 地 不 加 改 变 地 返 回 该 列 表 并 且 令use 失 败 , 同 时 给 每 个 符 号 产 生 一 个 例 外 。 如 果 export_fail 返 回 一 个 空 列 表 , 那 么 就 不会 记 录 任 何 错 误 并 且 输 出 所 有 请 求 的 符 号 。11.2.1.4 标 签 绑 定 工 具 函 数因 为 在 %EXPORT_TAGS 里 列 出 的 符 号 必 须 同 时 在 @EXPORT 或 者 @EXPORT_OK里 面 出 现 , 所 以 Exporter 提 供 了 两 个 函 数 让 你 可 以 增 加 这 些 标 签 或 者 符 号 :%EXPORTER_TAGS = (foo => [qw(aa bb cc)], bar => [qw(aa cc dd)]);Exporter::export_tags('foo');Exporter::export_ok_tags('bar');# 把 aa,bb 和 cc 加 到 @EXPORT# 把 aa,cc 和 dd 加 到 @EXPORT_OK声 明 非 标 签 名 字 是 错 误 的 。11.3 覆 盖 内 建 的 函 数许 多 内 建 的 函 数 都 可 以 覆 盖 , 尽 管 ( 就 象 在 你 的 墙 里 面 打 洞 一 样 ) 你 应 该 只 是 偶 然 才 做 这 些事 情 并 且 只 有 必 要 时 才 这 么 做 。 通 常 , 那 些 试 图 在 一 个 非 Unix 系 统 上 仿 真 一 些 Unix 系统 的 功 能 的 包 要 这 种 用 法 。( 不 要 把 覆 盖 和 重 载 两 个 概 念 混 淆 了 , 重 载 给 内 建 的 操 作 符 增 加了 面 向 对 象 的 含 义 , 但 并 不 覆 盖 什 么 东 西 。 参 阅 第 十 三 章 里 的 重 载 模 块 的 讨 论 , 重 载 , 获 取更 多 信 息 。)我 们 可 以 通 过 从 一 个 模 块 里 输 入 名 字 来 实 现 重 载 —— 预 定 义 的 不 够 好 。 更 准 确 地 说 , 触 发 覆盖 的 是 对 一 个 指 向 类 型 团 的 代 码 引 用 的 赋 值 动 作 , 就 象 *open = \&myopen 里 一 样 。 另外 , 赋 值 必 须 出 现 在 其 他 的 包 里 ; 这 样 就 不 大 可 能 通 过 故 意 的 类 型 团 别 名 导 致 偶 然 的 覆 盖 。不 过 , 如 果 你 真 的 是 希 望 做 你 自 己 的 覆 盖 , 那 也 别 失 望 , 因 为 subs 用 法 令 你 通 过 输 入 语法 预 定 义 子 过 程 , 这 样 , 这 些 名 字 就 覆 盖 了 内 建 的 名 字 :use subs qw(chdir chroot chmod chown);chdir $somewhere;sub chdir {...}通 常 , 模 块 不 应 该 把 open 或 chdir 这 样 的 内 建 的 名 字 放 在 缺 省 的 @EXPORT 列 表 里 输出 , 因 为 这 些 名 字 可 能 会 不 知 不 觉 地 跑 到 别 人 的 名 字 空 间 里 , 并 且 在 人 们 不 知 情 的 情 况 下 把283


语 意 改 变 了 。 如 果 模 块 包 含 的 是 在 @EXPORT_OK 列 表 里 的 名 字 , 那 么 输 入 者 就 需 要 明确 地 请 求 那 些 要 覆 盖 的 内 建 的 名 字 , 这 样 才 能 保 证 每 个 人 都 是 可 信 的 。内 建 的 函 数 的 最 早 的 版 本 总 是 可 以 通 过 伪 包 CORE 来 访 问 。 因 此 ,CORE::chdir 将 总 是最 初 编 译 进 <strong>Perl</strong> 里 的 版 本 , 即 使 chdir 关 键 字 已 经 被 覆 盖 了 。不 过 , 覆 盖 内 建 函 数 的 机 制 总 是 被 有 意 地 限 制 在 那 些 要 求 这 样 输 入 的 包 中 。 不 过 有 一 个 更 有覆 盖 性 的 机 制 可 以 让 你 在 任 何 地 方 覆 盖 一 个 内 建 的 函 数 , 而 不 用 考 虑 名 字 空 间 的 限 制 。 这 是通 过 在 CORE:GLOBAL 伪 包 里 定 义 该 函 数 来 实 现 的 。 下 面 是 是 用 一 个 可 以 理 解 正 则 表 达式 的 东 西 替 换 glob 操 作 符 的 例 子 。( 请 注 意 , 这 个 例 子 没 有 实 现 干 净 地 覆 盖 <strong>Perl</strong> 的 内 建glob 的 所 有 的 东 西 ,glob 在 不 同 的 标 量 或 者 列 表 环 境 里 的 行 为 是 不 一 致 的 . 实 际 上 , 许多 <strong>Perl</strong> 内 建 都 有 这 种 环 境 敏 感 的 行 为 , 而 一 个 写 得 好 的 覆 盖 应 该 充 分 支 持 这 些 行 为 。 有 关全 功 能 的 glob 覆 盖 的 例 子 , 你 可 以 学 习 和 <strong>Perl</strong> 绑 定 在 一 起 的 File::Glob 模 块 。) 总 之 ,下 面 的 是 一 个 不 全 面 的 例 子 :*CORE::GLOBAL::glob = sub {my $pat = shift;my @got;local *D;if (opendir D, '.') {$got = grep /$pat/, readdir D;closedir D;}return @got;}package Whatever;print ;# 显 示 当 前 目 录 里 的 所 有 用 法284


通 过 全 局 覆 盖 glob, 这 样 的 抢 占 强 制 在 任 何 名 字 空 间 里 使 用 一 个 新 的 ( 并 且 是 破 坏 性 的 )glob 操 作 符 , 而 不 需 要 拥 有 该 名 字 空 间 模 块 的 认 可 和 协 助 。 自 然 , 这 么 做 必 须 非 常 小 心—— 如 果 必 须 这 么 做 的 话 。 但 很 可 能 不 是 必 须 的 。我 们 对 覆 盖 的 态 度 是 : 变 得 比 较 重 要 很 好 , 但 是 更 重 要 的 是 变 得 更 好 。第 十 二 章 对 象 ( 上 )12.1 简 单 复 习 一 下 面 向 对 象 的 语 言对 象 是 一 个 数 据 结 构 , 带 有 一 些 行 为 。 我 们 通 常 把 这 些 行 为 称 为 对 象 的 直 接 动 作 , 有 时 候 可以 把 这 些 对 象 拟 人 化 。 比 如 , 我 们 可 能 会 说 一 个 长 方 形 “ 知 道 ” 如 何 在 屏 幕 上 显 示 自 己 , 或 者说 它 “ 知 道 ” 如 何 计 算 它 自 己 的 区 域 。作 为 一 个 类 的 实 例 , 对 象 从 中 获 益 , 取 得 其 行 为 。 类 定 义 方 法 : 就 是 那 些 应 用 于 类 和 它 的 事例 的 性 质 。 如 果 需 要 区 分 上 面 两 种 情 况 , 那 么 我 们 就 把 适 用 于 某 一 特 定 对 象 的 方 法 叫 做 实 例方 法 , 而 把 那 些 适 用 于 整 个 类 的 方 法 叫 做 类 方 法 。 不 过 这 样 做 只 是 为 了 方 便 —— 对 于 <strong>Perl</strong>而 言 , 方 法 就 是 方 法 , 只 是 由 其 第 一 个 参 数 的 类 型 来 区 分 。你 可 以 把 实 例 方 法 看 作 一 个 由 特 定 对 象 执 行 的 某 种 动 作 , 比 如 说 打 印 自 己 啦 , 拷 贝 自 己 啦 ,或 者 是 更 改 自 己 的 一 个 或 者 多 个 属 性 (“ 把 剑 命 名 为 Anduril”)。 类 方 法 可 以 在 许 多 共 同 的对 象 上 执 行 操 作 (“ 显 示 所 有 的 剑 ”) 或 者 提 供 其 他 不 依 赖 于 任 何 特 定 对 象 的 操 作 (“ 从 现 在开 始 , 一 旦 新 铸 了 一 把 剑 , 就 在 数 据 库 里 注 册 它 的 主 人 ”)。 类 里 面 那 些 生 成 对 象 实 例 的 方法 叫 构 造 方 法 (“ 铸 一 把 镶 满 宝 石 并 且 带 有 秘 密 题 字 的 剑 ”)。 这 些 通 常 是 类 方 法 (“ 给 我 造把 新 剑 ”), 但 也 有 可 能 是 实 例 方 法 (“ 造 一 把 和 我 的 剑 一 样 的 剑 ”)。一 个 类 可 以 从 父 类 中 继 承 方 法 , 父 类 也 叫 基 类 或 者 超 级 类 。 如 果 类 是 这 样 生 成 的 , 那 么 它 叫派 生 类 或 者 子 类 。( 让 我 们 把 稀 泥 和 得 更 糊 一 些 : 有 些 文 章 里 把 “ 基 类 ” 定 义 为 “ 最 上 层 ” 的超 级 类 。 我 们 这 里 不 是 这 个 意 思 。) 继 承 令 新 类 的 行 为 和 现 存 的 类 很 象 , 但 是 又 允 许 它 修 改或 者 增 加 它 的 父 类 没 有 的 性 质 。 如 果 你 调 用 了 一 个 方 法 , 而 在 当 前 的 类 中 没 有 找 到 这 个 方 法 ,<strong>Perl</strong> 会 自 动 询 问 父 表 , 以 找 出 定 义 。 比 如 , 剑 类 可 能 从 一 个 通 用 的 刀 锋 类 中 继 承 attack( 攻 击 ) 方 法 。 父 类 自 己 也 可 以 有 父 类 , 而 <strong>Perl</strong> 会 根 据 需 要 也 在 那 些 类 中 进 行 搜 索 。 刀 锋类 可 能 自 己 又 是 从 更 通 用 的 武 器 类 中 继 承 了 它 的 attack( 攻 击 ) 方 法 。285


当 一 个 对 象 调 用 attack( 攻 击 ) 方 法 时 , 产 生 的 效 果 可 能 取 决 于 该 对 象 是 一 把 剑 还 是 一 支箭 。 可 能 这 两 者 之 间 并 没 有 什 么 区 别 —— 如 果 剑 和 箭 都 是 从 通 用 的 武 器 类 中 继 承 他 们 的 攻 击(attack) 性 质 的 话 。 但 是 如 果 两 者 的 行 为 不 同 , 那 么 方 法 分 选 机 制 总 是 会 选 择 最 适 合 该对 象 需 要 的 那 个 attack 方 法 。 总 是 为 某 一 类 型 的 特 定 对 象 选 择 最 合 适 的 行 为 的 特 性 叫 做多 样 性 。 它 是 " 不 在 意 " 的 一 个 重 要 形 式 。当 你 在 实 现 一 个 类 的 时 候 , 你 必 须 关 注 那 些 对 象 的 “ 内 脏 ”, 但 是 当 你 使 用 一 个 类 的 时 候 , 你应 该 把 这 个 对 象 当 作 一 个 黑 盒 子 。 你 不 用 知 道 内 部 有 什 么 , 你 也 不 需 要 知 道 它 是 怎 么 工 作 的 ,而 且 你 和 这 个 黑 盒 子 的 交 互 只 用 它 的 方 式 进 行 : 通 过 类 提 供 的 方 法 。 即 使 你 知 道 这 些 方 法 对对 象 做 些 什 么 处 理 , 你 也 应 该 抑 制 住 自 己 干 的 冲 动 。 就 好 象 遥 控 电 视 一 样 , 即 使 即 使 你 知 道电 视 里 面 是 怎 样 运 转 的 , 如 果 没 有 特 别 充 分 的 理 由 , 也 不 应 该 在 电 视 里 上 窜 下 跳 地 摆 弄 它 。<strong>Perl</strong> 可 以 让 你 在 需 要 的 时 候 从 类 的 外 部 观 察 对 象 的 内 部 。 但 是 这 样 做 就 打 破 了 它 的 封 装 的原 则 —— 所 有 对 对 象 的 访 问 都 应 该 只 通 过 方 法 。 封 装 断 开 了 接 口 的 公 开 ( 对 象 应 该 如 何 使 用 )和 实 现 ( 它 实 际 上 是 如 何 运 转 的 ) 之 间 的 联 系 。<strong>Perl</strong> 除 了 一 个 未 写 明 的 设 计 者 和 用 户 之 间的 契 约 之 外 没 有 任 何 其 他 明 确 的 接 口 。 双 方 都 依 靠 常 识 和 公 德 来 运 做 : 用 户 依 靠 归 档 的 接 口 ,而 设 计 者 不 会 随 便 打 破 该 接 口 。<strong>Perl</strong> 并 不 强 制 你 使 用 某 种 编 程 风 格 , 并 且 它 也 不 会 有 一 些 其 他 的 面 向 对 象 的 语 言 里 的 私 有性 的 困 惑 。 不 过 ,<strong>Perl</strong> 的 确 会 在 自 由 上 令 人 迷 惑 , 并 且 , 作 为 一 个 <strong>Perl</strong> 程 序 员 , 你 所 拥有 的 一 个 自 由 就 是 根 据 自 己 的 喜 好 选 择 或 多 或 少 的 私 有 性 。 实 际 上 ,<strong>Perl</strong> 可 以 在 它 的 类 里面 有 比 C++ 更 强 的 私 有 性 。 也 就 是 说 ,<strong>Perl</strong> 不 限 制 你 做 任 何 事 情 , 而 且 实 际 上 它 也 不 限制 你 自 己 约 束 自 己 —— 假 如 你 必 须 这 么 做 的 话 。 本 章 稍 后 的 “ 私 有 方 法 ” 和 “ 用 做 对 象 的 闭 合 ”节 演 示 了 你 如 何 才 能 增 大 自 律 的 剂 量 。不 过 我 们 承 认 , 对 象 的 内 涵 比 我 们 这 里 说 的 多 得 多 , 而 且 有 很 多 方 法 找 出 面 向 对 象 的 设 计 的更 多 内 容 。 但 是 那 些 不 是 我 们 这 里 的 目 标 。 所 以 , 我 们 接 着 走 。12.2 <strong>Perl</strong> 的 对 象 系 统<strong>Perl</strong> 没 有 为 定 义 对 象 , 类 或 者 方 法 提 供 任 何 特 殊 的 语 法 。 相 反 , 它 使 用 现 有 的 构 造 来 实 现这 三 种 概 念 。( 注 : 现 在 有 了 一 个 软 件 重 用 的 例 子 了 !)下 面 是 一 些 简 单 的 定 义 , 可 以 让 你 安 心 些 :一 个 对 象 只 不 过 是 一 个 引 用 ... 恩 , 就 是 引 用 。因 为 引 用 令 独 立 的 标 量 代 表 大 量 的 数 据 , 所 以 我 们 不 应 该 对 把 引 用 用 于 所 有 对 象 感 到 奇 怪 。从 技 术 上 来 讲 , 对 象 不 太 适 合 用 引 用 表 示 , 实 际 上 引 用 指 向 的 是 引 用 物 。 不 过 这 个 区 别 很快 就 被 <strong>Perl</strong> 的 程 序 员 淡 化 了 , 而 且 因 为 我 们 觉 得 这 是 一 个 很 好 的 转 喻 , 如 果 这 么 用 合 适286


的 话 , 我 们 将 永 远 用 下 去 ( 注 : 我 们 更 喜 欢 语 言 的 活 力 , 而 不 是 数 学 的 严 密 。 不 管 你 同 意与 否 。) ( 译 注 :Larry Wall 是 学 语 言 的 , 不 是 学 数 学 的 , 当 然 这 么 说 .:))一 个 类 只 是 一 个 包一 个 包 当 作 一 个 类 —— 通 过 使 用 包 的 子 过 程 来 执 行 类 的 方 法 , 以 及 通 过 使 用 包 的 变 量 来 保存 类 的 全 局 数 据 。 通 常 , 使 用 一 个 模 块 来 保 存 一 个 或 者 更 多 个 类 。一 个 方 法 只 是 一 个 子 过 程你 只 需 要 在 你 当 作 类 来 使 用 的 包 中 声 明 子 过 程 ; 它 们 就 会 被 当 作 该 类 的 方 法 来 使 用 。 方 法调 用 是 调 用 子 过 程 的 一 种 新 的 方 法 , 它 传 递 额 外 的 参 数 : 用 于 调 用 方 法 所 使 用 的 对 象 或 包 。12.3 方 法 调 用如 果 你 把 面 向 对 象 的 编 程 方 法 凝 缩 成 一 个 精 华 的 词 , 那 就 是 抽 象 。 你 会 发 现 这 个 词 是 所 有 那些 OO 的 鼓 吹 者 所 传 播 的 言 辞 背 后 的 真 正 主 题 , 那 些 言 辞 包 括 多 型 性 啦 , 继 承 啦 还 有 封 装啦 。 我 们 相 信 这 些 有 趣 的 字 眼 , 但 是 我 们 还 是 会 从 实 际 的 角 度 出 发 来 理 解 它 们 —— 它 们 在 方法 调 用 中 的 作 用 是 什 么 。 方 法 是 对 象 系 统 的 核 心 , 因 为 它 们 为 实 现 所 有 魔 术 提 供 了 抽 象 层 。你 不 是 直 接 访 问 对 象 里 的 一 块 数 据 区 , 而 是 调 用 一 个 实 例 方 法 , 你 不 是 直 接 调 用 某 个 包 里 面的 子 过 程 , 而 是 调 用 一 个 类 方 法 。 通 过 在 类 的 使 用 和 实 现 之 间 放 置 这 个 间 接 层 , 程 序 设 计 人员 仍 然 可 以 自 由 修 改 复 杂 的 类 的 内 部 机 制 , 因 而 要 冒 一 点 儿 破 坏 使 用 它 的 程 序 的 风 险 。<strong>Perl</strong> 支 持 调 用 方 法 的 两 种 不 同 的 语 意 。 一 种 是 你 已 经 在 <strong>Perl</strong> 别 的 地 方 看 惯 了 的 风 格 , 而第 二 种 是 你 可 以 在 其 他 编 程 语 言 中 看 到 的 。 不 管 使 用 哪 种 方 法 调 用 的 形 式 ,<strong>Perl</strong> 总 是 会 给构 成 这 个 方 法 的 子 过 程 传 递 一 个 额 外 的 初 始 化 参 数 。 如 果 用 一 个 类 调 用 该 方 法 , 那 个 参 数 将会 是 类 的 名 字 。 如 果 用 一 个 对 象 调 用 方 法 , 那 个 参 数 就 是 对 象 的 引 用 。 不 管 是 什 么 , 我 们 都叫 它 方 法 调 用 者 。 对 于 类 方 法 而 言 , 调 用 者 是 包 的 名 字 。 对 于 一 个 实 例 方 法 , 调 用 者 是 调 用者 是 一 个 声 明 对 象 的 引 用 。换 句 话 说 , 调 用 者 就 是 调 用 方 法 的 那 个 东 西 。 有 些 OO 文 章 把 它 叫 做 代 理 或 演 员 。 从 文 法上 看 , 调 用 者 既 不 是 动 作 的 对 象 也 不 是 动 作 的 承 担 者 。 它 更 象 一 个 间 接 的 对 象 , 是 代 表 动 作执 行 后 受 益 人 的 东 西 , 就 向 在 命 令 “ 给 我 铸 把 剑 !” 里 的 “ 我 ” 一 样 。 从 语 意 上 来 看 , 你 既 可 以把 调 用 者 看 作 动 作 的 施 动 者 , 也 可 以 把 它 看 作 动 作 的 受 动 者 —— 更 象 哪 个 取 决 于 你 的 智 力 取向 。 我 们 可 不 打 算 告 诉 你 怎 样 看 待 它 们 。大 多 数 方 法 是 明 确 调 用 的 , 但 是 也 可 以 隐 含 调 用 —— 由 对 象 析 构 器 , 重 载 的 操 作 符 或 者 捆 绑的 变 量 触 发 的 时 候 。 准 确 地 说 , 这 些 都 不 是 正 常 的 子 过 程 调 用 , 而 是 代 表 对 象 的 <strong>Perl</strong> 自 动触 发 的 方 法 调 用 。 析 构 器 在 本 章 后 面 描 述 , 重 载 在 第 十 三 章 , 重 载 , 描 述 ; 而 捆 绑 在 第 十 四章 , 捆 绑 变 量 。287


方 法 和 普 通 子 过 程 之 间 的 一 个 区 别 是 , 它 们 的 包 在 什 么 时 候 被 解 析 —— 也 就 是 说 ,<strong>Perl</strong> 什么 时 候 决 定 应 该 执 行 该 方 法 或 者 子 过 程 的 哪 些 代 码 。 子 过 程 的 包 是 在 你 的 程 序 开 始 运 行 之前 , 在 编 译 的 时 候 解 析 的 。( 注 : 更 准 确 地 说 , 子 过 程 调 用 解 析 成 一 个 特 定 的 类 型 团 , 它 是一 个 填 充 到 编 译 好 的 操 作 码 树 上 的 引 用 。 这 个 类 型 团 的 含 义 甚 至 在 运 行 时 也 是 可 以 协 商 的—— 这 就 是 为 什 么 AUTOLOAD 可 以 为 你 自 动 装 载 一 个 子 过 程 。 不 过 , 类 型 团 的 含 义 通 常在 编 译 的 时 候 也 被 解 析 —— 由 一 个 命 名 恰 当 的 子 过 程 定 义 解 析 。) 相 比 之 下 , 一 个 方 法 包 直到 实 际 调 用 的 时 候 才 解 析 。( 编 译 的 时 候 检 查 原 型 , 这 也 是 为 什 么 编 译 时 可 以 使 用 普 通 子 过程 而 却 不 能 使 用 方 法 的 原 因 。)方 法 包 不 能 早 些 解 析 的 原 因 是 相 当 简 单 的 : 包 是 由 调 用 的 类 决 定 的 , 而 在 方 法 实 际 被 调 用 之前 , 调 用 者 是 谁 并 不 清 楚 。OO 的 核 心 是 下 面 这 样 简 单 的 逻 辑 : 一 旦 得 知 调 用 者 , 则 可 以知 道 调 用 者 的 类 , 而 一 旦 知 道 了 类 , 就 知 道 了 类 的 继 承 关 系 , 一 旦 知 道 了 类 的 继 承 关 系 , 那么 就 知 道 实 际 调 用 的 子 过 程 了 。抽 象 的 逻 辑 是 要 花 代 价 的 。 因 为 方 法 比 较 迟 才 解 析 , 所 以 <strong>Perl</strong> 里 面 向 对 象 的 解 决 方 法 要 比相 应 的 非 OO 解 决 方 法 慢 。 而 对 我 们 稍 后 要 介 绍 的 几 种 更 加 神 奇 的 技 巧 而 言 , 它 可 能 慢 很多 。 不 过 , 解 决 许 多 常 见 的 问 题 的 原 因 并 不 是 做 得 快 , 而 是 做 得 聪 明 。 那 就 是 OO 的 闪 光点 。12.3.1 使 用 箭 头 操 作 符 的 方 法 调 用我 们 说 过 有 两 种 风 格 的 方 法 调 用 。 第 一 种 调 用 方 法 的 风 格 看 起 来 象 下 面 这 样 :INVOCANT->METHOD(LIST)INVOCANT->METHOD这 种 方 法 通 常 被 称 做 箭 头 调 用 ( 原 因 显 而 易 见 )。( 请 不 要 把 -> 和 => 混 淆 ,“ 双 管 ” 箭 头 起神 奇 逗 号 的 作 用 。) 如 果 有 任 何 参 数 , 那 么 就 需 要 使 用 圆 括 弧 , 而 当 INVOCANT 是 一 个包 的 名 字 的 时 候 , 我 们 把 那 个 被 调 用 的 METHOD 看 作 类 方 法 。 实 际 上 两 者 之 间 并 没 有 区别 , 只 不 过 和 类 的 对 象 相 比 , 包 名 字 与 类 本 身 有 着 更 明 显 的 关 联 。 还 有 一 条 你 必 须 记 住 : 就是 对 象 同 样 也 知 道 它 们 的 类 。 我 们 会 告 诉 你 一 些 如 何 把 对 象 和 类 名 字 关 联 起 来 的 信 息 , 但 是你 可 以 在 不 知 道 这 些 信 息 的 情 况 下 使 用 对 象 。比 如 , 使 用 类 方 法 summon 的 构 造 一 个 类 , 然 后 在 生 成 的 对 象 上 调 用 实 例 方 法 speak,你 可 以 这 么 说 :$mage = Wizard->summon("Gandalf");# 类 方 法$mage->speak("friend");# 实 例 方 法288


summon 和 speak 方 法 都 是 由 Wizard 类 定 义 的 —— 或 者 是 从 一 个 它 继 承 来 的 类 定 义的 。 不 过 你 用 不 着 担 心 这 个 。 用 不 着 管 Wizard 的 闲 事 。因 为 箭 头 操 作 符 是 左 关 联 的 ( 参 阅 第 三 章 , 单 目 和 双 目 操 作 符 ), 你 甚 至 可 以 把 这 两 个 语 句合 并 成 一 条 :Wizard->summon("Gandalf")->speak("friend");有 时 候 你 想 调 用 一 个 方 法 而 不 想 先 知 道 它 的 名 字 。 你 可 以 使 用 方 法 调 用 的 箭 头 形 式 , 并 且 把方 法 名 用 一 个 简 单 的 标 量 变 量 代 替 :$method = "summon";$mage = Wizard->$method("Gandalf");# 调 用 Wizard->summon$travel = $companion eq "Shadowfax" ? "ride" : "walk";$mage->$travel("seven leagues");# 调 用 $mage->ride 或 者 $mage->walk虽 然 你 间 接 地 使 用 方 法 名 调 用 了 方 法 , 这 个 用 法 并 不 会 被 use strict 'refs' 禁 止 , 因 为 所有 方 法 调 用 实 际 上 都 是 在 它 们 被 解 析 的 时 候 以 符 号 查 找 的 形 式 进 行 的 。在 我 们 的 例 子 里 , 我 们 把 一 个 子 过 程 的 名 字 存 储 在 $travel 里 , 不 过 你 也 可 以 存 储 一 个 子过 程 引 用 。 但 这 样 就 忽 略 了 方 法 查 找 算 法 , 不 过 有 时 候 你 就 是 想 这 样 处 理 。 参 阅 “ 私 有 方 法 ”节 和 在 “UNIVERSAL: 最 终 的 祖 先 类 ” 节 里 面 的 can 方 法 的 讨 论 。 要 创 建 一 个 指 向 某 方 法在 特 定 实 例 上 的 调 用 的 引 用 , 参 阅 第 八 章 的 “ 闭 合 ” 节 。12.3.2 使 用 间 接 对 象 的 方 法 调 用第 二 种 风 格 的 方 法 调 用 看 起 来 象 这 样 :METHOD INVOCANT (LIST)METHOD INVOCANT LISTMETHOD INVOCANTLIST 周 围 的 圆 括 弧 是 可 选 的 ; 如 果 忽 略 了 圆 括 弧 , 就 把 方 法 当 作 一 个 列 表 操 作 符 。 因 此 你可 以 有 下 面 这 样 的 语 句 , 它 们 用 的 都 是 这 种 风 格 的 方 法 调 用 :$mage = summon Wizard "gandalf";289


$nemesis = summon Balrog home => "Moria", weapon => "whip";move $namesis "bridge";speak $mage "You cannot pass";break $staff;# 更 安 全 的 用 法 : break $staff();你 应 该 很 熟 悉 列 表 操 作 符 的 语 法 ; 它 是 用 于 给 print 或 者 printf 传 递 文 件 句 柄 的 相 同 的风 格 :print STDERR "help!!!\n";它 还 和 "Give Gollum the Preciousss" 这 样 的 英 语 句 子 类 似 , 所 以 我 们 称 他 为 间 接 对 象形 式 。<strong>Perl</strong> 认 为 调 用 者 位 于 间 接 对 象 槽 位 中 。 当 你 看 到 传 递 一 个 内 建 的 函 数 , 象 systemsystem 或 exec 什 么 的 到 它 的 “ 间 接 对 象 槽 位 中 ” 时 , 你 的 实 际 意 思 是 在 同 一 个 位 置 提 供这 个 额 外 的 , 没 有 逗 号 的 参 数 ( 列 表 ), 这 个 位 置 和 你 用 间 接 对 象 语 法 调 用 方 法 时 的 位 置 一样 。间 接 对 象 形 式 甚 至 允 许 你 把 INVOCANT 声 明 为 一 个 BLOCK, 该 块 计 算 出 一 个 对 象 ( 引用 ) 或 者 类 ( 包 )。 这 样 你 就 可 以 用 下 面 的 方 法 把 那 两 种 调 用 组 合 成 一 条 语 句 :speak {summon Wizard "Gandalf" } "friend";12.3.3 间 接 对 象 的 句 法 障 碍一 种 语 法 总 是 比 另 外 一 种 更 易 读 。 间 接 对 象 语 法 比 较 少 混 乱 , 但 是 容 易 导 致 几 种 语 法 含 糊 的情 况 。 首 先 就 是 间 接 对 象 调 用 的 LIST 部 分 和 其 他 列 表 操 作 符 一 样 分 析 。 因 此 , 下 面 的 圆括 弧 :enchant $sword ($pips + 2) * $cost;是 假 设 括 在 所 有 参 数 的 周 围 的 , 而 不 管 先 看 到 的 是 什 么 。 那 么 , 它 就 等 效 于 下 面 这 样 的 :($sword->enchant($pips + 2)) * $cost;这 样 可 不 象 你 想 要 的 : 调 用 enchant 时 只 给 了 $pips + 2, 然 后 方 法 返 回 的 值 被 $cost乘 。 和 其 他 列 表 操 作 符 一 样 , 你 还 必 须 仔 细 对 待 && 和 || 与 and 和 or 之 间 的 优 先 级 。比 如 :name $sword $oldname || "Glamdring";# 在 这 不 能 用 "or"290


变 成 :$sword->name($oldname || "Glamdring");而 :speak $mage "friend" && enter();# 这 儿 应 该 用 "and"变 成 奇 怪 的 :$mage->speak("friend" && enter());这 些 可 以 通 过 把 它 们 写 成 下 面 的 等 效 形 式 消 除 错 误 :enter() if $mage->speak("friend");$mage->speak("friend") && enter();speak $mage "friend" and enter();第 二 种 语 法 不 适 用 于 间 接 对 象 形 式 , 因 为 它 的 INVOCANT 局 限 于 一 个 名 字 , 一 个 未 代 换的 标 量 值 或 者 一 个 块 。( 注 : 仔 细 的 读 者 应 该 还 记 得 , 这 些 语 法 项 是 和 允 许 出 现 在 趣 味 字 符后 面 的 列 表 是 一 样 的 , 那 些 语 法 项 标 识 一 个 变 量 的 解 引 用 —— 比 如 @ary,@$aryref, 或者 {$aryref}。 当 分 析 器 看 到 这 些 内 容 之 一 时 , 她 就 有 自 己 的 INVOCANT 了 , 因 此 她 开始 查 找 她 的 LIST。 所 以 下 面 这 些 调 用 :move $party->{LEADER}; # 可 能 错 了 !move $riders[$i]; # 可 能 错 了 !实 际 分 析 成 这 样 :$party->move->{LEADER};$riders->move([i]);但 是 你 想 要 的 可 能 是 :$party->{LEADER}->move;$riders[$i]->move;分 析 器 只 是 为 一 个 间 接 对 象 查 找 一 个 调 用 时 稍 稍 向 前 看 了 一 点 点 , 甚 至 看 的 深 度 都 不 如 为 单目 操 作 符 那 样 深 远 。 如 果 你 使 用 第 一 种 表 示 法 是 就 不 会 发 生 这 件 怪 事 , 因 此 你 可 能 会 选 择 箭头 作 为 你 的 “ 武 器 ”。291


甚 至 英 语 在 这 方 面 也 有 类 似 的 问 题 。 看 看 下 面 的 句 子 : “Throw your cat out the windowa toy mouse to play with.” 如 果 你 分 析 这 句 话 速 度 太 快 , 你 最 后 就 会 把 猫 仍 出 去 , 而 不是 耗 子 ( 除 非 你 意 识 到 猫 已 经 在 窗 户 外 边 了 )。 类 似 <strong>Perl</strong>, 英 语 也 有 两 种 不 同 的 方 法 来 表达 这 个 代 理 :“Throw your cat the mouse” 和 “ Throw the mouse to your cat.” 有 时候 长 一 点 的 形 式 比 较 清 晰 并 且 更 自 然 , 但 是 有 时 侯 短 的 好 。 至 少 在 <strong>Perl</strong> 里 , 我 们 要 求 你 在任 何 编 译 为 间 接 对 象 的 周 围 放 上 花 括 弧 。12.3.4 引 用 包 的 类间 接 对 象 风 格 的 方 法 调 用 最 后 还 有 一 种 可 能 的 混 淆 , 那 就 是 它 可 能 完 全 不 会 被 当 作 一 个 方 法调 用 而 分 析 , 因 为 当 前 包 可 能 有 一 个 和 方 法 同 名 的 子 过 程 。 当 用 一 个 类 方 法 和 一 个 文 本 包 名字 一 起 做 调 用 者 用 的 时 候 , 有 一 个 方 法 可 以 解 析 这 样 的 混 淆 , 而 同 时 仍 然 保 持 间 接 对 象 的 语法 : 通 过 在 包 后 面 附 加 两 个 冒 号 引 用 类 名 。$obj = method CLASS::;# 强 制 为 "CLASS"->method这 个 方 法 很 重 要 , 因 为 经 常 看 到 下 面 的 表 示 法 :$obj = new CLASS;# 不 会 分 析 为 方 法如 果 当 前 包 有 一 个 子 过 程 叫 new 或 者 CLASS 时 , 将 不 能 保 证 总 是 表 现 得 正 确 。 即 使 你很 仔 细 地 使 用 箭 头 形 式 而 不 是 间 接 对 象 形 式 调 用 方 法 , 也 有 极 小 可 能 会 有 问 题 。 虽 然 引 入 了额 外 标 点 的 杂 音 , 但 CLASS:: 表 示 法 却 能 保 证 <strong>Perl</strong> 正 确 分 析 你 的 方 法 调 用 。 下 面 例 子中 前 面 两 个 不 总 是 分 析 成 一 样 的 东 西 , 但 后 面 两 个 可 以 :$obj = new ElvenRing;# 可 以 是 new("ElvenRing")# 甚 至 是 new(ElvenRing())$obj = ElvenRing->new;# 可 以 是 ElvenRing()->new()$obj = new ElvenRing::;$obj = ElvenRing::->new;# 总 是 "ElvenRing"->new()# 总 是 "ElvenRing"->new()包 引 用 表 示 法 可 以 用 一 些 富 有 创 造 性 的 对 齐 写 得 更 好 看 :$obj = new ElvenRing::name => "Narya",292


owner => "Gandalf",domain => "fire",stone => "ruby";当 然 , 当 你 看 到 双 冒 号 的 时 候 可 能 还 是 会 说 ,“ 真 难 看 !”, 所 以 我 们 还 要 告 诉 你 , 你 几 乎 总是 可 以 只 使 用 光 光 的 类 名 字 , 只 要 两 件 事 为 真 。 首 先 , 没 有 和 类 同 名 的 子 过 程 名 。( 如 果 你遵 循 命 名 传 统 : 过 程 名 , 比 如 new 以 小 写 开 头 , 而 类 名 字 , 比 如 ElvenRing ? 以 大 写 开头 , 那 么 就 永 远 不 会 有 这 个 问 题 。) 第 二 , 类 是 用 下 面 的 语 句 之 一 装 载 的 :use ElvenRing;require ElvenRing;这 两 种 方 法 都 令 <strong>Perl</strong> 意 识 到 ElvenRing ? 是 一 个 模 块 名 字 , 它 强 制 任 何 在 类 名ElvenRing ? 前 面 的 光 板 名 字 , 比 如 new, 解 释 为 一 个 方 法 调 用 , 即 使 你 碰 巧 在 你 的 当 前包 里 定 义 了 一 个 自 己 的 new 子 过 程 , 也 不 会 错 误 解 释 成 子 过 程 。 我 们 通 常 不 会 在 使 用 间 接对 象 中 碰 到 问 题 , 除 非 你 在 一 个 文 件 里 填 满 多 个 类 , 这 个 时 候 ,<strong>Perl</strong> 就 可 能 不 知 道 一 个 特定 的 包 名 字 就 是 一 个 类 名 字 。 而 且 那 些 把 子 过 程 的 名 字 命 名 为 类 似 ModuleNames ? 这 样的 人 最 终 也 会 陷 入 痛 苦 。12.4 构 造 对 象所 有 对 象 都 是 引 用 , 但 不 是 所 有 引 用 都 是 对 象 。 一 个 引 用 不 会 作 为 对 象 运 转 , 除 非 引 用 它 的东 西 有 特 殊 标 记 告 诉 <strong>Perl</strong> 它 属 于 哪 个 包 。 把 一 个 引 用 和 一 个 包 名 字 标 记 起 来 ( 因 此 也 和 包中 的 类 标 记 起 来 了 , 因 为 一 个 类 就 是 一 个 包 ) 的 动 作 被 称 作 赐 福 (blessing), 你 可 以 把赐 福 (bless) 看 作 把 一 个 引 用 转 换 成 一 个 对 象 , 尽 管 更 准 确 地 说 是 它 把 该 引 用 转 换 成 一 个对 象 引 用 。bless 函 数 接 收 一 个 或 者 两 个 参 数 。 第 一 个 参 数 是 一 个 引 用 , 而 第 二 个 是 要 把 引 用 赐 福(bless) 成 的 包 。 如 果 忽 略 第 二 个 参 数 , 则 使 用 当 前 包 。$obj = { };bless($obj);# 把 引 用 放 到 一 个 匿 名 散 列# Bless 散 列 到 当 前 包bless($obj, "Critter");# Bless 散 列 到 类 Critter。这 里 我 们 使 用 了 一 个 指 向 匿 名 散 列 的 引 用 , 也 是 人 们 通 常 拿 来 做 他 们 的 对 象 的 数 据 结 构 的 东西 。 毕 竟 , 散 列 极 为 灵 活 。 不 过 请 允 许 我 们 提 醒 你 的 是 , 你 可 以 赐 福 (bless) 一 个 引 用 为任 何 你 在 <strong>Perl</strong> 里 可 以 用 作 引 用 的 东 西 , 包 括 标 量 , 数 组 , 子 过 程 和 类 型 团 。 你 甚 至 可 以 把293


一 个 引 用 赐 福 (bless) 成 一 个 包 的 符 号 表 散 列 —— 只 要 你 有 充 分 的 理 由 。( 甚 至 没 理 由 都行 。)<strong>Perl</strong> 里 的 面 向 对 象 的 特 性 与 数 据 结 构 完 全 不 同 。一 旦 赐 福 (bless) 了 指 示 物 , 对 它 的 引 用 调 用 内 建 的 ref 函 数 会 返 回 赐 福 了 的 类 名 字 , 而不 是 内 建 的 类 型 , 比 如 HASH。 如 果 你 需 要 内 建 的 类 型 , 使 用 来 自 attributes 模 块 的reftype。 参 阅 第 三 十 一 章 , 实 用 模 块 , 里 的 use attributes。这 就 是 如 何 制 作 对 象 。 只 需 要 使 用 某 事 的 引 用 , 通 过 把 他 赐 福 (bless) 到 一 个 包 里 给 他 赋一 个 类 , 仅 此 而 已 。 如 果 你 在 设 计 一 个 最 小 的 类 , 所 有 要 做 的 事 情 就 是 这 个 。 如 果 你 在 使 用一 个 类 , 你 要 做 的 甚 至 更 少 , 因 为 类 的 作 者 会 把 bless 隐 藏 在 一 个 叫 构 造 器 的 方 法 里 , 它创 建 和 返 回 类 的 实 例 。 因 为 bless 返 回 其 第 一 个 参 数 , 一 个 典 型 的 构 造 器 可 以 就 是 :package Critter;sub spawn { bless {}; }或 者 略 微 更 明 确 地 拼 写 :package Critter;sub spawn {my $self = {};# 指 向 一 个 空 的 匿 名 散 列bless $self, "Critter";# 把 那 个 散 列 作 成 一 个 Critter 对 象return $self;# 返 回 新 生 成 的 Critter}有 了 那 个 定 义 , 下 面 就 是 我 们 如 何 创 建 一 个 Critter 对 象 了 :$pet = Critter->spawn;12.4.1 可 继 承 构 造 器和 所 有 方 法 一 样 , 构 造 器 只 是 一 个 子 过 程 , 但 是 我 们 不 把 它 看 作 一 个 子 过 程 。 在 这 个 例 子 里 ,我 们 总 是 把 它 当 作 一 个 方 法 来 调 用 —— 一 个 类 方 法 , 因 为 调 用 者 是 一 个 包 名 字 。 方 法 调 用 和普 通 的 子 过 程 调 用 有 两 个 区 别 。 首 先 , 它 们 获 取 我 们 前 面 讨 论 过 的 额 外 的 参 数 。 其 次 , 他 们遵 守 继 承 的 规 则 , 允 许 一 个 类 使 用 另 外 一 个 类 的 方 法 。我 们 将 在 下 一 章 更 严 格 地 描 述 继 承 下 层 的 机 制 , 而 现 在 , 通 过 几 个 简 单 的 例 子 , 你 就 应 该 可以 理 解 他 们 的 效 果 , 因 此 可 以 帮 助 你 设 计 构 造 器 。 比 如 , 假 设 我 们 有 一 个 Sppider 类 从294


Spider 类 继 承 了 方 法 。 特 别 是 , 假 设 Spider 类 没 有 自 己 的 spawn 方 法 。 则 有 下 面 对应 的 现 象 :方 法 调 用结 果 子 过 程 调 用Critter->spawn() Citter::spawn("Critter")Spider->spawn() Critter::spawn("Spider")两 种 情 况 里 调 用 的 子 过 程 都 是 一 样 的 , 但 是 参 数 不 一 样 。 请 注 意 我 们 上 面 的 spawn 构 造器 完 全 忽 略 了 它 的 参 数 , 这 就 意 味 着 我 们 的 Spider 对 象 被 错 误 地 赐 福 (bless) 成 了Critter 类 。 一 个 更 好 的 构 造 器 将 提 供 包 名 字 ( 以 第 一 个 参 数 传 递 进 来 ) 给 bless:sub spawn {my $class = shift;# 存 储 包 名 字my $self = { };bless( $self, $class);# 把 赐 福 该 包 为 引 用return $self;}现 在 你 可 以 为 两 种 情 况 都 使 用 同 一 个 子 过 程 :$vermin = Critter->spawn;$shelob = Spider->spawn;并 且 每 个 对 象 都 将 是 正 确 的 类 。 甚 至 是 间 接 运 转 的 , 就 象 :$type = "Spider";$shelob = $type->spawn;# 和 "Spider"->spawn 一 样这 些 仍 然 是 类 方 法 , 不 是 实 例 方 法 , 因 为 它 的 调 用 者 持 有 的 是 字 串 而 不 是 一 个 引 用 。如 果 $type 是 一 个 对 象 而 不 是 一 个 类 名 字 , 前 一 个 构 造 器 的 定 义 将 不 会 运 行 , 因 为 bless需 要 一 个 类 名 字 。 但 是 对 许 多 类 而 言 , 只 有 拿 一 个 现 有 的 对 象 当 模 板 去 创 建 另 外 一 个 对 象 的时 候 它 才 有 意 义 。 在 这 些 情 况 下 , 你 可 以 设 计 你 的 构 造 器 , 这 样 他 们 就 可 以 与 对 象 或 者 类 名字 一 起 运 转 了 :sub spawn {my $invocant = shift;295


my $class = ref($invocant) || $invocant;# 对 象 或 者 类 名 字my $self = { };bless ($self, $class);return $self;}12.4.2 初 始 器大 多 数 对 象 维 护 的 信 息 是 由 对 象 的 方 法 间 接 操 作 的 。 到 目 前 为 止 我 们 的 所 有 构 造 器 都 创 建 了空 散 列 , 但 是 我 们 没 有 理 由 让 它 们 这 么 空 着 。 比 如 , 我 们 可 以 让 构 造 器 接 受 额 外 的 参 数 , 并且 把 它 们 当 作 键 字 / 数 值 对 。 有 关 OO 的 文 章 常 把 这 样 的 数 据 称 为 " 所 有 "," 属 性 "," 访 问者 "," 成 员 数 据 "," 实 例 数 据 " 或 者 " 实 例 变 量 " 等 。 本 章 稍 后 的 “ 实 例 变 量 ” 节 详 细 地 讨 论 这些 属 性 。假 设 一 个 Horse 类 有 一 些 实 例 属 性 , 比 如 "name" 和 "color":$steed = Horse->new(name => "shadowfax", color => "white");如 果 该 对 象 是 用 散 列 引 用 实 现 的 , 那 么 一 旦 调 用 者 被 从 参 数 列 表 里 删 除 , 那 么 键 字 / 数 值 对就 可 以 直 接 代 换 进 散 列 :sub new {my $invocant = shift;my $class = ref($invocant) || $invocant;my $self = { @_ };bless($self, $class);# 剩 下 的 参 数 变 成 属 性# 给 予 对 象 性 质return $self;}这 回 我 们 用 一 个 名 字 叫 new 的 方 法 做 该 类 的 构 造 器 , 这 样 就 可 以 把 那 些 C++ 程 序 员 哄得 相 信 这 些 都 是 正 常 的 。 不 过 <strong>Perl</strong> 可 不 认 为 “new” 有 任 何 特 殊 的 地 方 ; 你 可 以 把 你 的 构 造器 命 名 为 任 意 的 东 西 。 任 何 碰 巧 创 建 和 返 回 一 个 对 象 的 方 法 都 是 实 际 上 的 构 造 器 。 通 常 , 我们 建 议 你 把 你 的 构 造 器 命 名 为 任 何 在 你 解 决 的 问 题 的 环 境 中 有 意 义 的 东 西 。 比 如 , 在 Tk 模块 中 的 构 造 器 命 名 为 它 们 创 建 的 窗 口 构 件 。 在 DBI 模 块 里 , 一 个 叫 connect 的 构 造 器 返296


回 一 个 数 据 库 句 柄 对 象 , 而 另 外 一 个 叫 prepare 的 构 造 器 是 当 作 一 个 实 例 方 法 调 用 的 , 并且 返 回 一 个 语 句 句 柄 对 象 。 不 过 如 果 没 有 很 好 的 适 合 环 境 的 构 造 器 名 字 , 那 么 new 也 不 算是 一 个 太 坏 的 选 择 。 而 且 , 随 便 挑 一 个 名 字 , 这 样 强 制 人 们 在 使 用 构 造 器 之 前 去 读 接 口 文 档( 也 就 是 类 的 文 档 ) 也 不 是 太 坏 的 事 情 。更 灵 活 一 些 , 你 可 以 用 缺 省 键 字 / 数 值 对 设 置 你 的 构 造 器 , 这 些 参 数 可 以 由 用 户 在 使 用 的 时候 通 过 提 供 参 数 而 覆 盖 掉 :sub new {my $invocant = shift;my $class = ref($invocant) || $invocant;my $self = {color => "bay",legs => 4,owner => undef,@_,# 覆 盖 以 前 的 属 性};return bless $self, $class;}$ed = Horse->new; # 四 腿 湾 马$stallion = Horse->new(color => "black"); # 四 腿 黑 马当 把 这 个 Horse 构 造 器 当 作 实 例 方 法 使 用 的 时 候 , 它 忽 略 它 的 调 用 者 现 有 的 属 性 。 你 可 以设 计 第 二 个 构 造 器 , 把 它 当 作 实 例 方 法 来 调 用 , 如 果 你 设 计 得 合 理 , 那 你 就 可 以 使 用 来 自 调用 对 象 的 数 值 作 为 新 生 成 的 对 象 的 缺 省 值 :$steed = Horse->new(color => "dun");$foal = $steed->clone(owner => "EquuGen Guild, Ltd.");297


sub clone {my $model = shift;my $self = $model->new(%$model, @_);return $self;# 前 面 被 ->new 赐 福 过 了}( 你 可 以 把 这 个 功 能 直 接 放 进 new 里 , 但 是 这 样 的 话 名 字 就 不 是 那 么 适 合 这 个 函 数 了 。)请 注 意 我 们 即 使 是 在 clone 构 造 器 里 , 我 们 也 没 有 硬 编 码 Horse 类 的 名 字 。 我 们 让 最 初的 那 个 对 象 调 用 它 自 己 的 new 方 法 , 不 管 是 什 么 。 如 果 我 们 把 它 写 成 Horse->new 而不 是 $model->new, 那 么 该 类 不 能 帮 助 实 现 Zebra( 斑 马 ) 或 Unicorn ( 独 角 兽 )类 。 你 应 该 不 会 想 克 隆 一 匹 飞 马 但 是 却 突 然 发 现 你 得 到 是 一 匹 颜 色 不 同 的 马 。不 过 , 有 时 候 你 碰 到 的 是 相 反 的 问 题 : 你 不 是 想 在 不 同 的 类 里 共 享 一 个 构 造 器 , 而 是 想 多 个构 造 器 共 享 一 个 类 对 象 。 当 一 个 构 造 器 想 调 用 一 个 基 类 的 构 造 器 作 为 构 造 工 作 的 一 部 分 的 时候 就 会 出 现 这 种 问 题 。<strong>Perl</strong> 不 会 帮 你 做 继 承 构 造 。 也 就 是 说 ,<strong>Perl</strong> 不 会 为 任 何 基 类 或 者 任何 其 他 所 需 要 的 类 自 动 调 用 构 造 器 ( 或 者 析 构 器 ), 所 以 你 的 构 造 器 将 不 得 不 自 己 做 这 些 事情 然 后 增 加 衍 生 的 类 所 需 要 的 附 加 的 任 何 属 性 。 因 此 情 况 不 象 clone 过 程 那 样 , 你 不 能 把一 个 现 有 的 对 象 拷 贝 到 新 对 象 里 , 而 是 先 调 用 你 的 基 类 的 构 造 器 , 然 后 把 新 的 基 类 对 象 变 形为 新 的 衍 生 对 象 。12.5 类 继 承对 <strong>Perl</strong> 的 对 象 系 统 剩 下 的 内 容 而 言 , 从 一 个 类 继 承 另 外 一 个 类 并 不 需 要 给 这 门 语 言 增 加 特殊 的 语 法 。 当 你 调 用 一 个 方 法 的 时 候 , 如 果 <strong>Perl</strong> 在 调 用 者 的 包 里 找 不 到 这 个 子 过 程 , 那 么它 就 检 查 @ISA 数 组 ( 注 : 发 音 为 "is a", 象 "A horse is a critter。" 里 哪 样 )。 <strong>Perl</strong>是 这 样 实 现 继 承 的 : 一 个 包 的 @ISA 数 组 里 的 每 个 元 素 都 保 存 另 外 一 个 包 的 名 字 , 当 缺 失方 法 的 时 候 就 搜 索 这 些 包 。 比 如 , 下 面 的 代 码 把 Horse 类 变 成 Critter 类 的 字 类 。( 我们 用 our 声 明 @ISA, 因 为 它 必 须 是 一 个 打 包 的 变 量 , 而 不 是 用 my 声 明 的 词 。)package Horse;our @ISA = "Critter";298


你 现 在 应 该 可 以 在 原 先 Critter 使 用 的 任 何 地 方 使 用 Horse 类 或 者 对 象 了 。 如 果 你 的 新类 通 过 了 这 样 的 空 字 类 测 试 , 那 么 你 就 可 以 认 为 Critter 是 一 个 正 确 的 基 类 , 可 以 用 于 继承 。假 设 你 在 $steed 里 有 一 个 Horse 对 象 , 并 且 在 他 上 面 调 用 了 一 个 move:$steed->move(10);因 为 $steed 是 一 个 Horse,<strong>Perl</strong> 对 该 方 法 的 第 一 个 选 择 是 Horse::move 子 过 程 。 如果 没 有 ,<strong>Perl</strong> 先 询 问 @Horse::ISA 的 第 一 个 元 素 , 而 不 是 生 成 一 个 运 行 时 错 误 , 这 样 将导 致 查 询 到 Critter 包 里 , 并 找 到 Critter::move。 如 果 也 没 有 找 到 这 个 子 过 程 , 而 且Critter 有 自 己 的 @Critter::ISA 数 组 , 那 么 继 续 查 询 那 里 面 的 父 类 , 看 看 有 没 有 一 个move 方 法 , 如 此 类 推 直 到 上 升 到 继 承 级 别 里 面 一 个 没 有 @ISA 的 包 。我 们 刚 刚 描 述 的 情 况 是 单 继 承 的 情 况 , 这 时 每 个 类 只 有 一 个 父 类 。 这 样 的 继 承 类 似 一 个 相 关包 的 链 表 。<strong>Perl</strong> 还 支 持 多 继 承 ; 只 不 过 是 向 该 类 的 @ISA 里 增 加 更 多 的 包 。 这 种 继 承 的运 做 更 象 一 个 树 状 结 构 , 因 为 每 个 包 可 以 有 多 于 一 个 的 直 接 父 类 。 很 多 人 认 为 这 样 更 带 劲 。当 你 调 用 了 调 用 者 的 一 个 类 型 为 classname 的 方 法 methname,<strong>Perl</strong> 将 尝 试 六 种 不 同的 方 法 来 找 出 所 用 的 子 过 程 ( 译 注 : 又 是 孔 乙 己 ?:):1. 首 先 ,<strong>Perl</strong> 在 调 用 者 自 己 的 包 里 查 找 一 个 叫 classname::methname 的 子 过 程 。如 果 失 败 , 则 进 入 继 承 , 并 且 进 入 步 骤 2。2. 第 二 步 ,<strong>Perl</strong> 通 过 检 查 @classname::ISA 里 列 出 的 所 有 父 包 , 检 查 从 基 类 继 承过 来 的 方 法 , 看 看 有 没 有 parent::methname 子 过 程 。 这 种 搜 索 是 从 左 向 右 , 递 归 的 ,由 浅 入 深 进 行 的 。 递 归 保 证 祖 父 类 , 曾 祖 父 类 , 太 祖 父 类 等 等 类 都 进 入 搜 索 。3. 如 果 仍 然 失 败 ,<strong>Perl</strong> 就 搜 索 一 个 叫 UNIVERSAL::methname 的 子 过 程 。4. 这 时 ,<strong>Perl</strong> 放 弃 methname 然 后 开 始 查 找 AUTOLOAD。 首 先 , 它 检 查 叫 做classmane::AUTOLOAD 的 子 过 程 。5. 如 果 上 面 的 失 败 ,<strong>Perl</strong> 则 搜 索 所 有 在 @classname::ISA 里 列 出 的 parent 包 ,寻 找 任 何 parent::AUTOLOAD 子 过 程 。 这 样 的 搜 索 仍 然 是 从 左 向 右 , 递 归 的 , 由 浅入 深 进 行 的 。6. 最 后 ,<strong>Perl</strong> 寻 找 一 个 叫 UNIVERSAL::AUTOLOAD 的 子 过 程 。<strong>Perl</strong> 会 在 找 到 的 第 一 个 子 过 程 处 停 止 并 调 用 该 子 过 程 。 如 果 没 有 找 到 子 过 程 , 则 产 生 一 个例 外 , 也 是 你 经 常 看 到 的 :Can't locate object method "methname" via package "classnaem"299


如 果 你 给 你 的 C 编 译 器 提 供 了 -DDEBUGGING 选 项 , 做 了 一 个 调 试 版 本 的 <strong>Perl</strong> , 那么 如 果 你 给 <strong>Perl</strong> 一 个 -Do 开 关 , 你 就 能 看 到 它 一 边 解 析 方 法 调 用 一 边 走 过 这 些 步 骤 。我 们 将 随 着 我 们 的 继 续 介 绍 更 详 细 地 讨 论 继 承 机 制 。第 十 二 章 对 象 ( 下 )12.5.1 通 过 @ISA 继 承如 果 @ISA 包 含 多 于 一 个 包 的 名 字 , 包 的 搜 索 都 是 从 左 向 右 的 顺 序 进 行 的 。 这 些 搜 索 是 由浅 入 深 的 , 因 此 , 如 果 你 有 一 个 Mule 类 有 象 下 面 这 样 的 继 承 关 系 :package Mule;our @ISA= ("Horse", "Donkey");<strong>Perl</strong> 将 首 先 在 Horse 里 ( 和 他 的 任 何 前 辈 类 里 , 比 如 Critter) 查 找 任 何 在 Mule 里 找不 到 的 方 法 , 找 不 到 以 后 才 继 续 在 Donkey 和 其 父 类 里 进 行 查 找 。如 果 缺 失 的 方 法 在 一 个 基 类 里 发 现 ,<strong>Perl</strong> 内 部 把 该 位 置 缓 存 在 当 前 类 里 , 依 次 提 高 效 率 ,这 样 要 查 找 该 方 法 的 时 候 , 它 不 用 再 跑 老 远 。 对 @ISA 的 修 改 或 者 定 义 新 的 方 法 就 会 令 该缓 存 失 效 , 因 此 导 致 <strong>Perl</strong> 再 次 执 行 查 找 。当 <strong>Perl</strong> 搜 索 一 个 方 法 的 时 候 , 它 确 信 你 没 有 创 建 一 个 闭 环 的 继 承 级 别 。 如 果 两 个 类 互 相 继承 则 可 能 出 现 这 个 问 题 , 甚 至 是 间 接 地 通 过 其 他 类 这 样 继 承 也 如 此 。 试 图 做 你 自 己 的 祖 父 即使 对 <strong>Perl</strong> 而 言 也 是 荒 谬 的 , 因 此 这 样 的 企 图 导 致 抛 出 一 个 例 外 。 不 过 , 如 果 从 多 于 一 个 类继 承 下 来 , 而 且 这 些 类 共 享 同 样 的 祖 宗 ,<strong>Perl</strong> 不 认 为 是 错 误 , 这 种 情 况 类 似 近 亲 结 婚 。 你的 继 承 级 别 看 起 来 不 再 象 一 棵 树 , 而 是 象 一 个 油 脂 分 子 图 。 不 过 这 样 不 会 为 难 <strong>Perl</strong>—— 只要 这 个 图 形 是 真 的 油 脂 分 子 。当 你 设 置 @ISA 的 时 候 , 赋 值 通 常 发 生 在 运 行 时 , 因 此 除 非 你 加 以 预 防 , 否 则 在 BEGIN,CHECK, 或 者 INIT 块 里 的 代 码 不 能 在 继 承 级 别 里 使 用 。 预 防 之 一 是 use base 用 法 ,它 令 你 require 类 并 且 在 编 译 时 把 它 加 入 @ISA 里 。 下 面 是 你 使 用 它 们 的 方 法 :packge Mule;300


use base ("Horse", "donkey");# 声 明 一 个 超 类它 是 下 面 东 西 的 缩 写 :package Mule;BEGIN {our @ISA = ("Horse", "Donkey");require Horse;require Donkey;}只 是 use base 还 算 入 所 有 use fields 声 明 中 。有 时 候 人 们 很 奇 怪 的 是 , 在 @ISA 中 包 含 一 个 类 并 没 有 为 你 require ( 请 求 ) 合 适 的 模块 。 那 是 因 为 <strong>Perl</strong> 的 类 系 统 很 大 程 度 上 是 与 它 的 模 块 相 冲 突 的 。 一 个 文 件 可 以 保 存 许 多 类( 因 为 他 们 只 是 包 ), 而 一 个 包 可 以 在 许 多 文 件 中 提 及 。 但 是 在 最 常 见 的 情 况 下 , 一 个 包 、一 个 类 、 一 个 模 块 、 一 个 文 件 这 样 总 是 相 当 具 有 可 换 性 —— 只 要 你 足 够 倾 斜 ,use base 用法 提 供 了 一 个 声 明 性 的 语 法 , 用 这 个 语 法 可 以 建 立 继 承 , 装 载 模 块 文 件 和 并 且 提 供 任 意 声 明了 的 基 类 域 。 它 也 是 我 们 不 停 提 到 的 最 便 利 的 对 角 线 。 参 阅 第 三 十 一 章 的 use base 和use fields 获 取 细 节 。12.5.2 访 问 被 覆 盖 的 方 法当 一 个 类 定 义 一 个 方 法 , 那 么 该 子 过 程 覆 盖 任 意 基 类 中 同 名 的 方 法 。 想 象 一 下 你 有 一 个Mule( 骡 ) 对 象 ( 它 是 从 Horse( 马 ) 类 和 Donkey( 驴 ) 类 衍 生 而 来 的 ), 而 且 你 想 调用 你 的 对 象 的 breed ( 种 ) 方 法 。 尽 管 父 类 有 它 们 自 己 的 breed( 种 ) 方 法 ,Mule ( 骡 )类 的 设 计 者 通 过 给 Mule( 骡 ) 类 写 自 己 的 breed 方 法 覆 盖 了 那 些 父 类 的 breed 方 法 。这 这 意 味 着 下 面 的 交 叉 引 用 可 能 不 能 正 确 工 作 :$stallion = Horse->new(gender => "male");$molly = Mule->new(gender => "female");$colt = $molly->breed($stallion);现 在 假 设 基 因 工 程 的 魔 术 治 好 了 骡 子 臭 名 昭 著 的 不 育 症 , 因 此 你 想 忽 略 无 法 生 活 的Mule::breed 方 法 。 你 可 以 象 平 常 那 样 调 用 你 的 子 过 程 , 但 是 一 定 要 确 保 明 确 传 递 调 用 者 :301


$colt = Horse::breed($molly, $stallion);不 过 , 这 是 回 避 继 承 关 系 , 实 际 上 总 是 错 误 的 处 理 方 法 。 我 们 很 容 易 想 象 实 际 上 没 有 这 么 个Horse::breed 子 过 程 , 因 为 Horse 和 Donkeys 都 是 从 一 个 公 共 的 叫 Equine ( 马 )的 父 类 继 承 来 的 那 个 秉 性 。 从 另 一 方 面 来 讲 , 如 果 你 希 望 表 明 <strong>Perl</strong> 应 该 从 一 个 特 定 类 开 始搜 索 某 方 法 , 那 么 只 要 使 用 普 通 的 方 法 调 用 方 式 , 只 不 过 用 方 法 名 修 饰 类 名 字 就 可 以 了 :$colt = $molly->Horse::breed($stallion);有 时 候 , 你 希 望 一 个 衍 生 类 的 方 法 表 现 得 象 基 类 中 的 某 些 方 法 的 封 装 器 。 实 际 上 衍 生 类 的 方法 本 身 就 可 以 调 用 基 类 的 方 法 , 在 调 用 前 或 者 调 用 后 增 加 自 己 的 动 作 。 你 可 以 把 这 种 表 示 法只 用 于 演 示 声 明 从 哪 个 类 开 始 搜 索 。 但 是 在 使 用 被 覆 盖 方 法 的 大 多 数 情 况 下 , 你 并 不 希 望 自己 要 知 道 或 者 声 明 该 执 行 哪 个 父 类 被 覆 盖 了 的 方 法 。这 就 是 SUPER 伪 类 提 供 便 利 的 地 方 。 它 令 你 能 够 调 用 一 个 覆 盖 了 的 基 类 方 法 , 而 不 用 声明 是 哪 个 类 定 义 了 该 方 法 。( 注 : 不 要 把 这 个 和 第 十 一 章 的 覆 盖 <strong>Perl</strong> 的 内 建 函 数 的 机 制 混淆 了 , 那 个 不 是 对 象 方 法 并 且 不 会 被 继 承 覆 盖 。 你 调 用 内 建 函 数 的 覆 盖 是 通 过 CORE 伪 包 ,而 不 是 SUPER 伪 包 。) 下 面 的 子 过 程 在 当 前 包 的 @ISA 里 查 找 , 而 不 会 要 求 你 声 明 特定 类 :package Mule;our @ISA = qw(Horse Donkey);sub kick {my $self = shift;print "The mule kicks!\n";$self->SUPER::kick(@_);}SUPER 伪 包 只 有 在 一 个 方 法 里 使 用 才 有 意 义 。 尽 管 一 个 类 的 实 现 器 可 以 在 它 们 自 己 的 代 码里 面 使 用 SUPER, 但 那 些 使 用 一 个 类 的 对 象 的 却 不 能 。当 出 现 多 重 继 承 的 时 候 ,SUPER 不 总 是 按 照 你 想 象 的 那 样 运 行 。 你 大 概 也 可 以 猜 到 , 它 也象 @ISA 那 样 遵 循 普 通 的 继 承 机 制 的 规 则 : 从 左 向 右 , 递 归 , 由 浅 入 深 。 如 果 Horse 和Donkey 都 有 一 个 speak 方 法 , 而 你 需 要 使 用 Donkey 的 方 法 , 你 将 不 得 不 明 确 命 名 该父 类 :sub speak {302


my $self = shift;print "The mule speaks!\n";$self->Donkey::speak(@_);}用 于 多 重 继 承 的 情 况 的 更 灵 活 的 方 法 可 以 用 UNIVERSAL::can 方 法 进 行 雕 琢 , 该 方 法 下节 介 绍 。 或 者 你 可 以 从 CPAN 抓 Class::Multimethods 方 法 下 来 , 它 提 供 了 更 灵 活 的 解决 方 法 , 包 括 搜 索 最 接 近 的 , 而 不 是 最 左 端 的 。<strong>Perl</strong> 里 的 每 一 段 代 码 都 知 道 自 己 现 在 在 哪 个 包 里 , 就 象 最 后 的 package 语 句 说 的 那 样 。只 有 在 调 用 SUPER 的 包 编 译 过 以 后 ,SUPER 才 询 问 @ISA。 它 不 关 心 调 用 者 的 类 , 也 不关 心 调 用 的 子 过 程 所 属 的 包 。 不 过 , 如 果 你 想 在 另 外 一 个 类 中 定 义 方 法 , 而 且 只 是 修 改 方 法名 , 那 么 就 有 可 能 出 问 题 :package Bird;use Dragonfly;sub Dragonfly::divebomb { shift->SUPER::divebomb(@_) }不 幸 的 是 , 这 样 会 调 用 Bird 的 超 类 , 而 不 是 Dragonfly 的 。 要 想 按 照 你 的 意 愿 做 事 , 你还 得 为 SUPER 的 编 译 明 确 地 切 换 到 合 适 的 包 :package Bird;use Dragonfly;{package Dragonfly;sub divebomb { shift->SUPER::divebomb(@_) }}如 上 例 所 示 , 你 用 不 着 只 是 为 了 给 某 个 现 有 类 增 加 一 个 方 法 去 编 辑 一 个 模 块 。 因 为 类 就 是 一个 包 , 而 方 法 就 是 一 个 子 过 程 , 你 所 要 做 的 就 是 在 那 个 包 里 定 义 一 个 函 数 , 就 象 我 们 上 面 做的 那 样 , 然 后 该 类 就 一 下 子 有 了 一 个 新 方 法 。 没 有 要 求 继 承 。 只 需 要 考 虑 包 , 而 因 为 包 是 全局 的 , 程 序 的 任 意 位 置 都 可 以 访 问 任 意 包 。( 小 意 思 ! 湿 湿 碎 !)12.5.3 UNIVERSAL: 最 终 的 祖 先 类303


如 果 对 调 用 者 的 类 和 所 有 他 的 祖 先 类 递 归 搜 索 后 , 还 没 有 发 现 有 正 确 名 字 的 方 法 定 义 , 那 么会 在 一 个 预 定 义 的 叫 UNIVERSAL 的 类 中 最 后 再 搜 索 该 方 法 一 次 。 这 个 包 从 来 不 会 在@ISA 中 出 现 , 但 如 果 查 找 @ISA 失 败 总 是 要 查 找 它 。 你 可 以 把 UNIVERSAL 看 作 最 终的 祖 先 , 所 有 类 都 隐 含 地 从 它 衍 生 而 来 。在 UNIVERSAL 类 里 面 有 下 面 的 预 定 义 的 方 法 可 以 使 用 , 因 此 所 有 类 中 都 可 以 用 它 们 。 而且 不 管 它 们 是 被 当 作 类 方 法 还 是 对 象 方 法 调 用 的 都 能 运 行 。INVOCANT->isa(CLASS)如 果 INVOCANT 的 类 是 CLASS 或 者 任 何 从 CLASS 继 承 来 的 ,isa 方 法 返 回 真 。 除了 包 名 字 以 外 ,CLASS 还 可 以 是 一 个 内 建 的 类 型 , 比 如 "HASH" 或 者 "ARRAY"。 ( 准确 地 检 查 某 种 类 型 在 封 装 和 多 态 性 机 制 中 并 不 能 很 好 地 工 作 。 你 应 该 依 赖 重 载 分 检 给 你 正确 的 方 法 。)use FileHandle;if (FileHandle->isa("Exporter")) {print "FileHandle is an Exporter.\n";}$fh = FileHandle->new();if ($fh->isa("IO::Handle")) {print "\$fh is some sort of IOish object.\n"}if ($fh->isa("GLOB")) {print "\$fh is really a GLOB reference.\n";}INVOCANT->can(METHOD)如 果 INVOCANT 中 有 METHOD, 那 么 can 方 法 就 返 回 一 个 可 以 调 用 的 该 子 过 程 的 引用 。 如 果 没 有 定 义 这 样 的 子 过 程 ,can 返 回 undef。304


if ($invocant->can("copy")) {print "Our invocant can copy.\n";}我 们 可 以 用 这 个 方 法 实 现 条 件 调 用 —— 只 有 方 法 存 在 才 调 用 :$obj->snarl if $obj->can("snarl");在 多 重 继 承 情 况 下 , 这 个 方 法 允 许 调 用 所 有 覆 盖 掉 的 基 类 的 方 法 , 而 不 仅 仅 是 最 左 边 的 那个 :sub snarl {my $self = shift;print "Snarling: @_\n";my %seen;for my $parend (@ISA) {if (my $code = $parent->can("snarl")) {$self->$code(@_) unless $seen{$code}++;}}}我 们 用 %seen 散 列 跟 踪 那 些 我 们 已 经 调 用 的 子 过 程 , 这 样 我 们 才 能 避 免 多 次 调 用 同 一 个子 过 程 。 这 种 情 况 在 多 个 父 类 共 享 一 个 共 同 的 祖 先 的 时 候 可 能 发 生 。会 触 发 一 个 AUTOLOAD( 在 下 一 节 描 述 ) 的 方 法 将 不 会 被 准 确 地 汇 报 , 除 非 该 包 已 经 声明 ( 但 没 有 定 义 ) 它 需 要 自 动 装 载 的 子 过 程 了 。INVOCANT-VERSION(NEED)VERSION 方 法 返 回 INVOCANT 的 类 的 版 本 号 , 就 是 存 贮 在 包 的 $VERSION 变 量 里 的那 只 。 如 果 提 供 了 NEED 参 数 , 它 表 示 当 前 版 本 至 少 不 能 小 于 NEED, 而 如 果 真 的 小 于就 会 抛 出 一 个 例 外 。 这 是 use 用 以 检 查 一 个 模 块 是 否 足 够 新 所 调 用 的 方 法 。use Thread 1.0;# 调 用 Thread->VERSION(1.0)305


print "Running versino ", Thread->VERSION, " of Thread.\n";你 可 以 提 供 自 己 的 VERSION 方 法 覆 盖 掉 UNIVERSAL 的 。 不 过 那 样 会 令 任 何 从 你 的 类衍 生 的 类 也 使 用 哪 个 覆 盖 类 。 如 果 你 不 想 发 生 这 样 的 事 情 , 你 应 该 把 你 的 方 法 设 计 成 把 其他 类 的 版 本 请 求 返 回 给 UNIVERSAL。在 UNIVERSAL 里 的 方 法 是 内 建 的 <strong>Perl</strong> 子 过 程 , 如 果 你 使 用 全 称 并 且 传 递 两 个 参 数 , 你就 可 以 调 用 它 们 , 比 如 UNIVERSAL::isa($formobj, "HASH")。( 但 是 我 们 不 推 荐 这 么用 , 因 为 通 常 而 言 can 包 含 你 真 正 在 找 的 答 案 。)你 可 以 自 由 地 给 UNIVERSAL 增 加 你 自 己 的 方 法 。( 当 然 , 你 必 须 小 心 ; 否 则 你 可 能 真 的把 事 情 搞 砸 , 比 如 有 些 东 西 是 假 设 找 不 到 你 正 在 定 义 的 方 法 名 的 , 这 样 它 们 就 可 以 从 其 他 地方 自 动 装 载 进 来 。) 下 面 我 们 创 建 了 一 个 copy 方 法 , 所 有 类 的 对 象 都 可 以 使 用 —— 只 要这 些 对 象 没 有 定 义 自 己 的 。( 我 们 忘 了 给 调 用 一 个 对 象 做 解 析 。)use Data::Dumper;use Carp;sub UNIVERSAL::copy {my $self = shift;if (ref $self) {return eval Dumper($self);# 没 有 CORE 引 用}else{confess "UNIVERSAL::copy can't copy class $self";}}如 果 该 对 象 包 含 任 意 到 子 过 程 的 引 用 , 这 个 Data::Dumper 的 策 略 就 无 法 运 转 , 因 为 它们 不 能 正 确 地 复 现 。 即 使 能 够 拿 到 源 程 序 , 词 法 绑 定 仍 然 会 丢 失 。12.5.4 方 法 自 动 装 载通 常 , 当 你 调 用 某 个 包 里 面 未 定 义 子 过 程 , 而 该 包 定 义 了 一 个 AUTOLOAD 子 过 程 , 则 调用 该 AUTOLOAD 子 过 程 并 且 抛 出 一 个 例 外 ( 参 阅 第 十 章 ,“ 自 动 装 载 Autoloading”)。方 法 的 运 做 略 有 不 同 。 如 果 普 通 的 方 法 查 找 ( 通 过 类 , 它 的 祖 先 以 及 最 终 的 UNIVERSAL)306


没 能 找 到 匹 配 , 则 再 按 同 样 的 顺 序 运 行 一 便 , 这 次 是 查 找 一 个 AUTOLOAD 子 过 程 。 如 果找 到 , 则 把 这 个 子 过 程 当 作 一 个 方 法 来 调 用 , 同 时 把 包 的 $AUTOLOAD 变 量 设 置 为 该 子过 程 的 全 名 ( 就 是 代 表 AUTOLOAD 调 用 的 那 个 子 过 程 。)当 自 动 装 载 方 法 的 时 候 , 你 得 小 心 一 些 。 首 先 , 如 果 AUTOLOAD 的 子 过 程 代 表 一 个 叫DESTROY 的 方 法 调 用 , 那 么 它 应 该 立 即 返 回 , 除 非 你 的 目 的 是 仿 真 DESTROY, 那 样 的话 对 <strong>Perl</strong> 有 特 殊 含 义 , 我 们 将 在 本 章 后 面 的 “ 实 例 析 构 器 ” 里 描 述 。sub AUTOLOAD {return if our $AUTOLOAD =~ /::DESTROY$/;...}第 二 , 如 果 该 类 提 供 一 个 AUTOLOAD 安 全 网 , 那 么 你 就 不 能 对 一 个 方 法 名 使 用UNIVERSAL::can 来 检 查 调 用 该 方 法 是 否 安 全 。 你 必 须 独 立 地 检 查 AUTOLOAD:if ($obj->can("methname") || $obj->can("AUTOLAOD")) {$obj->methname();}最 后 , 在 多 重 继 承 的 情 况 下 , 如 果 一 个 类 从 两 个 或 者 更 多 类 继 承 过 来 , 而 每 个 类 都 有 一 个AUTOLOAD, 那 么 只 有 最 左 边 的 会 被 触 发 , 因 为 <strong>Perl</strong> 在 找 到 第 一 个 AUTOLOAD 以 后就 停 下 来 了 。后 两 个 要 求 可 以 很 容 易 地 通 过 声 明 包 里 的 子 过 程 来 绕 开 , 该 包 的 AUTOLOAD 就 是 准 备 管理 这 些 方 法 的 。 你 可 以 用 独 立 的 声 明 实 现 这 些 :package Goblin;sub kick;sub bite;sub scratch;或 者 用 use subs 用 法 , 如 果 你 有 许 多 方 法 要 声 明 , 这 样 会 更 方 便 :package Goblin;use subs qw(kick bite scratch);307


甚 至 你 只 是 声 明 了 这 些 子 过 程 而 并 没 有 定 义 它 们 , 系 统 也 会 认 为 它 是 真 实 的 。 它 们 在 一 个UNIVERSAL::can 检 查 里 出 现 , 而 且 更 重 要 的 是 , 它 们 在 搜 索 方 法 的 第 二 步 出 现 , 这 样 就永 远 不 会 进 行 第 三 步 , 更 不 用 说 第 四 步 了 。“ 不 过 ,” 你 可 能 会 说 ,“ 它 们 调 用 了 AUTOLOAD, 不 是 吗 ?” 的 确 , 它 们 最 终 调 用 了AUTOLOAD, 但 是 机 制 是 不 一 样 的 。 一 旦 通 过 第 二 步 找 到 了 方 法 存 根 (stub),<strong>Perl</strong> 就 会试 图 调 用 它 。 当 最 后 发 现 该 方 法 不 是 想 要 的 方 法 时 , 则 再 次 进 行 AUTOLOAD 搜 索 , 不 过这 回 它 从 包 含 存 根 的 类 开 始 搜 索 , 这 样 就 把 方 法 的 搜 索 限 制 在 该 类 和 该 类 的 祖 先 ( 以 及UNIVSRSAL) 中 。 这 就 是 <strong>Perl</strong> 如 何 查 找 正 确 的 AUTOLOAD 来 运 行 和 如 何 忽 略 来 自 最初 的 继 承 树 中 错 误 的 AUTOLOAD 部 分 的 方 法 。12.5.5 私 有 方 法有 一 个 调 用 方 法 的 手 段 可 以 完 全 令 <strong>Perl</strong> 忽 略 继 承 。 如 果 用 的 不 是 一 个 文 本 方 法 名 , 而 是 一个 简 单 的 标 量 变 量 , 该 变 量 包 含 一 个 指 向 一 个 子 过 程 的 引 用 , 则 立 即 调 用 该 子 过 程 。 在 前 一节 的 UNIVERSAL->can 的 描 述 中 , 最 后 一 个 例 子 使 用 子 过 程 的 引 用 而 不 是 其 名 字 调 用 所有 被 覆 盖 了 的 方 法 。这 个 特 性 的 一 个 非 常 诱 人 的 方 面 是 他 可 以 用 于 实 现 私 有 方 法 调 用 。 如 果 把 你 的 类 放 在 一 个 模块 里 , 你 可 以 利 用 文 件 的 词 法 范 围 为 私 有 性 服 务 。 首 先 , 把 一 个 匿 名 子 过 程 存 放 在 一 个 文 件范 围 的 词 法 里 :# 声 明 私 有 方 法my $secret_door = sub {my $self = shift;...};然 后 在 这 个 文 件 里 , 你 可 以 把 那 个 变 量 当 作 保 存 有 一 个 方 法 名 这 样 来 使 用 。 这 个 闭 合 将 会 被直 接 调 用 , 而 不 用 考 虑 继 承 。 和 任 何 其 他 方 法 一 样 , 调 用 者 作 为 一 个 额 外 的 参 数 传 递 进 去 。sub knock {my $self = shift;if ($self->{knocked}++ > 5) {$self->$secret_door();308


}}这 样 就 可 以 让 该 文 件 自 己 的 子 过 程 ( 类 方 法 ) 调 用 一 个 代 码 超 出 该 词 法 范 围 ( 因 而 无 法 访 问 )的 方 法 。12.6 实 例 析 构 器和 <strong>Perl</strong> 里 任 何 其 他 引 用 一 样 , 当 一 个 对 象 的 最 后 一 个 引 用 消 失 以 后 , 该 对 象 的 存 储 器 隐 含地 循 环 使 用 。 对 于 一 个 对 象 而 言 , 你 还 有 机 会 在 这 些 事 情 发 生 的 时 候 ( 对 象 内 存 循 环 使 用 )捕 获 控 制 , 方 法 是 在 类 的 包 里 定 义 DESTROY 子 过 程 。 这 个 方 法 在 合 适 的 时 候 自 动 被 触 发 ,而 将 要 循 环 使 用 的 对 象 是 它 的 唯 一 的 参 数 。在 <strong>Perl</strong> 里 很 少 需 要 析 构 器 , 因 为 存 贮 器 管 理 是 自 动 进 行 的 。 不 过 有 些 对 象 可 能 有 一 个 位 于存 储 器 系 统 之 外 的 状 态 ( 比 如 文 件 句 柄 或 数 据 库 联 接 ), 而 且 你 还 想 控 制 它 们 , 所 以 析 构 器还 是 有 用 的 。package MailNotify;sub DESTROY {my $self = shift;my $fh = $self->{mailhandle};my $id = $self->{name};print $fh "\n$id is signing off at " . localtime( ) . "\n";close $fh;# 关 闭 mailer 的 管 道}因 为 <strong>Perl</strong> 只 使 用 一 个 方 法 来 构 造 一 个 对 象 , 即 使 该 构 造 器 的 类 是 从 一 个 或 者 多 个 其 他 类 继承 过 来 的 也 这 样 ,<strong>Perl</strong> 也 只 是 每 个 对 象 使 用 一 个 DESTROY 方 法 来 删 除 对 象 , 也 不 管 继承 关 系 。 换 而 言 之 ,<strong>Perl</strong> 并 不 为 你 做 分 级 析 构 。 如 果 你 的 类 覆 盖 了 一 个 父 类 的 析 构 器 , 那么 你 的 DESTROY 方 法 可 能 需 要 调 用 任 意 适 用 的 基 类 的 DESTROY 方 法 :sub DESTROY {my $self = shift;# 检 查 看 看 有 没 有 覆 盖 了 的 析 构 器309


$self->SUPER::DESTROY if $self->can("SUPER::DESTROY");# 现 在 干 你 自 己 的 事 情}这 个 方 法 只 适 用 于 继 承 的 类 ; 一 个 对 象 只 是 简 单 地 包 含 在 当 前 对 象 里 —— 比 如 , 一 个 大 的 散列 表 里 的 一 个 数 值 —— 会 被 自 动 释 放 和 删 除 。 这 也 是 为 什 么 一 个 简 单 地 通 过 聚 集 ( 有 时 候 叫“ 有 xx” 关 系 ) 实 现 的 包 含 器 要 更 干 净 , 并 且 比 继 承 ( 一 个 “ 是 xx” 关 系 ) 更 干 净 。 换 句 话 说 ,通 常 你 实 际 上 只 需 要 把 一 个 对 象 直 接 保 存 在 另 外 一 个 对 象 里 面 而 不 用 通 过 继 承 , 因 为 继 承 会增 加 不 必 要 的 复 杂 性 。 有 时 候 当 你 诉 诸 多 重 继 承 的 时 候 , 实 际 上 单 继 承 就 足 够 用 了 。你 有 可 能 明 确 地 调 用 DESTROY, 但 实 际 上 很 少 需 要 这 么 做 。 这 么 做 甚 至 是 有 害 的 , 因 为对 同 一 个 对 象 多 次 运 行 析 构 器 可 能 会 有 让 你 不 快 的 后 果 。12.6.1 用 DESTROY 方 法 进 行 垃 圾 收 集正 如 第 八 章 的 “ 垃 圾 收 集 , 循 环 引 用 和 弱 引 用 ” 节 里 介 绍 的 那 样 , 一 个 引 用 自 身 的 变 量 ( 或 者多 个 变 量 间 接 的 相 互 引 用 ) 会 一 直 到 程 序 ( 或 者 嵌 入 的 解 释 器 ) 快 要 退 出 的 时 候 才 释 放 。 如果 你 想 早 一 些 重 新 利 用 这 些 存 储 器 , 你 通 常 是 不 得 不 使 用 CPAN 上 的 WeakRef ? 方 法 来明 确 地 打 破 或 者 弱 化 该 引 用 。对 于 对 象 , 一 个 候 补 的 解 决 方 法 是 创 建 一 个 容 器 类 , 该 容 器 类 保 存 一 个 指 向 这 个 自 引 用 数 据结 构 的 指 针 。 为 该 被 包 含 对 象 的 类 定 义 一 个 DESTROY 方 法 , 该 方 法 手 工 打 破 自 引 用 结 构的 循 环 性 。 你 可 以 在 <strong>Perl</strong> Cookbook 这 本 书 的 第 十 三 章 里 找 到 关 于 这 些 的 例 子 , 该 章 的名 称 是 ,“Coping with Circular Data Structures"( 对 付 循 环 数 据 结 构 )。当 一 个 解 释 器 退 出 的 时 候 , 它 的 所 有 对 象 都 删 除 掉 , 这 一 点 对 多 线 程 或 者 嵌 入 式 的 <strong>Perl</strong> 应用 非 常 重 要 。 对 象 总 是 在 普 通 引 用 被 删 除 之 前 在 一 个 独 立 的 回 合 里 被 删 除 。 这 样 就 避 免 了DESTROY 方 法 处 理 那 些 本 身 已 经 被 删 除 的 引 用 。( 也 是 因 为 简 单 引 用 只 有 在 嵌 入 的 解 释器 中 才 会 被 当 作 垃 圾 收 集 , 因 为 退 出 一 个 进 程 是 回 收 引 用 的 非 常 迅 速 的 方 法 。 但 是 退 出 并 不运 行 对 象 的 析 构 器 , 因 此 <strong>Perl</strong> 先 做 那 件 事 。)12.7 管 理 实 例 数 据大 多 数 类 创 建 的 对 象 实 际 上 都 是 有 几 个 内 部 数 据 域 ( 实 例 数 据 ) 和 几 个 操 作 数 据 域 的 方 法 的数 据 结 构 。<strong>Perl</strong> 类 继 承 方 法 , 而 不 是 数 据 , 不 过 由 于 所 有 对 对 象 的 访 问 都 是 通 过 方 法 调 用 进 行 的 , 所以 这 样 运 行 得 很 好 。 如 果 你 想 继 承 数 据 , 那 么 你 必 须 通 过 方 法 继 承 来 实 现 。 不 过 ,<strong>Perl</strong> 在310


多 数 情 况 下 是 不 需 要 这 么 做 的 , 因 为 大 多 数 类 都 把 它 们 的 对 象 的 属 性 保 存 在 一 个 匿 名 散 列 表里 。 对 象 的 实 例 数 据 保 存 在 这 个 散 列 表 里 , 这 个 散 列 表 也 是 该 对 象 自 己 的 小 名 字 空 间 , 用 以划 分 哪 个 类 对 该 对 象 进 行 了 哪 些 操 作 。 比 如 , 如 果 你 希 望 一 个 叫 $city 的 对 象 有 一 个 数 据域 名 字 叫 elevation, 你 可 以 简 单 地 $city->{elevation} 这 样 访 问 它 。 可 以 不 用 声 明 。方 法 的 封 装 会 为 你 做 这 些 。假 设 你 想 实 现 一 个 Person 对 象 。 你 决 定 它 有 一 个 叫 “name” 的 数 据 域 , 因 为 某 种 奇 怪 的一 致 性 原 因 , 你 将 把 它 按 照 键 字 name 保 存 在 该 匿 名 散 列 表 里 , 该 散 列 表 就 是 为 这 个 对 象服 务 的 。 不 过 你 不 希 望 用 户 直 接 修 改 数 据 。 要 想 获 得 封 装 的 优 点 , 用 户 需 要 一 些 方 法 来 访 问该 实 例 变 量 , 而 又 不 用 揭 开 抽 象 的 面 纱 。比 如 , 你 可 能 写 这 样 的 一 对 访 问 方 法 :sub get_name {my $self = shift;return $self ->{name};}sub set_name {my $self = shift;$self->{name} = shift;}它 们 会 导 致 下 面 的 代 码 的 形 成 :$him = Person->new();$him->set_name("Laser");$him->set_name( ucfirst($him->get_name) );你 甚 至 可 以 把 两 个 方 法 组 合 成 一 个 :sub name {my $self = shift;311


if (@_) { $self->{name} = shift }return $self->{name};}这 样 会 形 成 下 面 的 代 码 :$him = Person->new();$him->name("BitBIRD");$him->name( ucfirst($him->name) );给 每 个 实 例 变 量 ( 对 于 我 们 的 Person 类 而 言 可 能 是 name,age,height 等 等 ) 写 一个 独 立 的 函 数 的 优 点 是 直 接 , 明 显 和 灵 活 。 缺 点 是 每 当 你 需 要 一 个 新 的 类 , 你 最 终 都 要 对 每个 实 例 变 量 定 义 一 个 或 两 个 几 乎 相 同 的 方 法 。 对 于 开 头 的 少 数 几 个 类 而 言 , 这 么 做 不 算 太 坏 ,而 且 如 果 你 喜 欢 这 么 干 的 话 我 们 也 欢 迎 你 这 么 干 。 但 是 如 果 便 利 比 灵 活 更 重 要 , 那 么 你 可 能就 会 采 用 后 面 描 述 的 那 种 技 巧 。请 注 意 我 们 会 变 化 实 现 , 而 不 是 接 口 。 如 果 你 的 类 的 用 户 尊 重 封 装 , 那 么 你 就 可 以 透 明 地 从一 种 实 现 切 换 到 另 外 一 种 实 现 , 而 不 会 让 你 的 用 户 发 现 。( 如 果 你 的 继 承 树 里 的 家 庭 成 员 把你 的 类 用 于 子 类 或 者 父 类 , 那 可 能 不 能 这 么 宽 容 了 , 毕 竟 它 们 对 年 你 的 认 识 比 陌 生 人 要 深 刻得 多 。) 如 果 你 的 用 户 曾 经 深 入 地 刺 探 过 你 的 类 中 的 私 有 部 分 , 那 么 所 导 致 的 不 可 避 免 的 损害 就 是 他 们 自 己 的 问 题 而 不 是 你 的 。 你 所 能 做 的 一 切 就 是 通 过 维 护 好 你 的 接 口 来 快 乐 地 过 日子 。 试 图 避 免 这 个 世 界 里 的 每 一 个 人 做 出 一 些 有 些 恶 意 的 事 情 会 消 耗 掉 你 的 所 有 时 间 和 精力 , 并 且 最 终 你 会 发 现 还 是 徒 劳 的 。对 付 家 成 员 更 富 挑 战 性 。 如 果 一 个 子 类 覆 盖 了 一 个 父 类 的 属 性 的 指 示 器 , 那 么 它 是 应 该 访 问散 列 表 中 的 同 一 个 域 呢 还 是 不 应 该 ? 根 据 该 属 性 的 性 质 , 不 管 那 种 做 法 都 会 产 生 一 些 争 论 。从 通 常 的 安 全 性 角 度 出 发 , 每 个 指 示 器 都 可 以 用 它 自 己 的 类 名 字 作 为 散 列 域 名 字 的 前 缀 , 这样 子 类 和 父 类 就 都 可 以 有 自 己 的 版 本 。 下 面 有 几 个 使 用 这 种 子 类 安 全 策 略 的 例 子 , 其 中 包 括标 准 的 Struct::Class 模 块 。 你 会 看 到 指 示 器 是 这 样 组 成 的 :sub name {my $self =shift;my $field = __PACKAGE__ . "::name";if (@_) { $self->{$field} = shift }return $self->{field};312


}在 随 后 的 每 个 例 子 里 , 我 们 都 创 建 一 个 简 单 的 Person 类 , 它 有 name,race, 和 aliases域 , 每 种 类 都 有 一 个 相 同 的 接 口 , 但 是 有 完 全 不 同 的 实 现 。 我 们 不 准 备 告 诉 你 我 们 最 喜 欢 哪种 实 现 , 因 为 根 据 实 际 使 用 的 环 境 , 我 们 几 乎 都 喜 欢 。 有 些 人 喜 欢 弯 曲 的 实 现 , 有 些 人 喜 欢直 接 的 实 现 。12.7.1 用 use fields 定 义 的 域对 象 不 一 定 要 用 匿 名 散 列 来 实 现 。 任 何 引 用 都 可 以 。 比 如 , 如 果 你 使 用 一 个 匿 名 数 组 , 你 可以 这 样 设 置 一 个 构 造 器 :sub new {my $invocant = shift;my $class = ref($invocant) || $invocant;return bless [], $class;}以 及 象 下 面 这 样 的 指 示 器 :sub name {my $self = shift;if (@_) { $self->[0] = shift }return $self->[0];}sub race {my $self = shift;if (@_) { $self->[1] = shift }return $self->[1];}313


sub aliases {my $self = shift;if (@_) { $self->[2] = shift }return $self->[2];}数 组 访 问 比 散 列 快 一 些 , 而 且 占 的 内 存 少 一 些 , 不 过 用 起 来 不 象 散 列 那 样 方 便 。 你 不 得 不 跟踪 所 有 下 标 数 字 ( 不 仅 仅 在 你 自 己 的 类 里 面 , 而 且 还 得 在 你 的 父 类 等 等 里 面 ), 这 些 下 标 用于 指 示 你 的 类 正 在 使 用 的 数 组 的 部 分 。 这 样 你 才 能 重 复 使 用 这 些 空 间 。use fields 用 法 可 以 对 付 所 有 这 些 问 题 :package Person;use fields qw(name race aliases);这 个 用 法 不 会 为 你 创 建 指 示 器 方 法 , 但 是 它 的 确 是 基 于 一 些 内 建 的 方 法 之 上 ( 我 们 叫 它 伪 散列 ) 做 一 些 类 似 的 事 情 的 。( 不 过 你 可 能 会 希 望 对 这 些 域 用 指 示 器 进 行 封 装 , 就 象 我 们 处 理下 面 的 例 子 一 样 。) 伪 散 列 是 数 组 引 用 , 你 可 以 把 它 们 当 作 散 列 那 样 来 用 , 因 为 他 们 有 一 个相 关 联 的 键 字 映 射 表 。use fields 用 法 为 你 设 置 这 些 键 字 映 射 , 等 效 于 声 明 了 哪 些 域 对Person 对 象 是 有 效 的 ; 以 此 令 <strong>Perl</strong> 的 编 译 器 意 识 到 它 们 的 存 在 。 如 果 你 声 明 了 你 的 对 象变 量 的 类 型 ( 就 象 在 下 个 例 子 里 的 my Person $self 一 样 ), 编 译 器 也 会 聪 明 得 把 对 该 域的 访 问 优 化 成 直 接 的 数 组 访 问 。 这 样 做 更 重 要 的 原 因 可 能 是 它 令 域 名 字 在 编 译 时 是 类 型 安 全的 ( 实 际 上 是 敲 键 安 全 )。( 参 阅 第 八 章 里 的 “ 伪 散 列 ”。)一 个 构 造 器 和 例 子 指 示 器 看 起 来 可 能 是 这 个 样 子 的 :package Person;use fields qw(naem race aliases);sub new {my $type = shift;my Person $self = fields::new(ref $type || $type);$self->{name} = "unnamed";314


$self->{race} = "unknown";$self->{aliases} = [];return $self;}sub name {my Person $self = shift;$self->{name} = shift if @_;return $self->{name};}sub race {my Person $self = shift;$self->{race} = shift if @_;return $self->{race};}sub aliases {my Person $self = shift;$self->{aliases} = shift if @_;return $self->{aliases};}1;如 果 你 不 小 心 拼 错 了 一 个 用 于 访 问 伪 散 列 的 文 本 键 字 , 你 用 不 着 等 到 运 行 时 才 发 现 这 些 问题 。 编 译 器 知 道 对 象 $self 想 要 引 用 的 数 据 类 型 ( 因 为 你 告 诉 它 了 ), 因 此 它 就 可 以 那 些只 访 问 Person 对 象 实 际 拥 有 的 数 据 域 的 代 码 。 如 果 你 走 神 了 , 并 且 想 访 问 一 个 不 存 在 的数 据 域 ( 比 如 $self->{mane}), 那 么 编 译 器 可 以 马 上 标 出 这 个 错 误 并 且 绝 对 不 会 让 有错 误 的 程 序 跑 到 解 释 器 那 里 运 行 。315


这 种 方 法 在 声 明 获 取 实 例 变 量 的 方 法 的 时 候 仍 然 有 些 重 复 , 所 以 你 可 能 仍 然 喜 欢 使 用 下 面 介绍 的 技 巧 之 一 , 这 些 技 巧 实 现 了 简 单 指 示 器 方 法 的 自 动 创 建 。 不 过 , 因 为 所 有 的 这 些 技 巧 都使 用 某 种 类 型 的 间 接 引 用 , 所 以 如 果 你 使 用 了 这 些 技 巧 , 那 么 你 就 会 失 去 上 面 的 编 译 时 词 法类 型 散 列 访 问 的 拼 写 检 查 好 处 。 当 然 , 你 还 是 能 保 留 一 点 点 的 时 间 和 空 间 的 优 势 。如 果 你 决 定 使 用 一 个 伪 散 列 来 实 现 你 的 类 , 那 么 任 何 从 这 个 类 继 承 的 类 都 必 须 知 晓 下 面 的 类的 伪 散 列 实 现 。 如 果 一 个 对 象 是 用 伪 散 列 实 现 的 , 那 么 所 有 继 承 分 级 中 的 成 员 都 必 须 使 用use base 和 use fields 声 明 。 比 如 :package Wizard;use base "Person";user fields qw(staff color sphere);这 么 干 就 把 Wizard 模 块 标 为 Person 的 子 类 , 并 且 装 载 Person.pm 文 件 。 而 且 除 了来 自 Person 的 数 据 域 外 , 还 在 这 个 类 中 注 册 了 三 个 新 的 数 据 域 。 这 样 , 当 你 写 :my Wizard $mage = fields::new("Wizard");的 时 候 , 你 就 能 得 到 一 个 可 以 访 问 两 个 类 的 数 据 域 的 伪 散 列 :$mage->name("Gandalf");$mage->color("Grey");因 为 所 有 子 类 都 必 须 知 道 他 们 用 的 是 一 种 伪 散 列 的 实 现 , 所 以 , 从 效 率 和 拼 写 安 全 角 度 出 发 ,它 们 应 该 使 用 直 接 伪 散 列 句 法 :$mage->{name} = "Gandalf";$mage->{color} = "Grey";不 过 , 如 果 你 希 望 保 持 你 的 实 现 的 可 互 换 性 , 那 么 你 的 类 以 外 的 用 户 必 须 使 用 指 示 器 方 法 。尽 管 use base 只 支 持 单 继 承 , 但 也 算 不 上 非 常 严 重 的 限 制 。 参 阅 第 三 十 一 章 的 use base和 use fields 的 描 述 。12.7.2 用 Class::Struct 生 成 类标 准 的 Class::Struct 模 块 输 出 一 个 叫 struct 的 函 数 。 它 创 建 了 你 开 始 构 造 一 个 完 整 的类 所 需 要 的 所 有 机 关 。 它 生 成 一 个 叫 new 的 构 造 器 , 为 每 个 该 结 构 里 命 名 的 数 据 域 增 加 一个 指 示 器 方 法 ( 实 例 变 量 )。316


比 如 , 如 果 你 把 下 面 结 构 放 在 一 个 Person.pm 文 件 里 :package Person;use Class::Struct;struct Person => {name => '$',race => '$',# 创 建 一 个 "Person" 的 定 义# name 域 是 一 个 标 量# race 域 也 是 一 个 标 量aliases => '@',# 但 aliases 域 是 一 个 数 组 引 用};1;然 后 你 就 可 以 用 下 面 的 方 法 使 用 这 个 模 块 :use Person;my $mage = Person->new();$mage->name("Gandalf");$mage->race("Istar");$mage->aliases( ["Mithrandir","Olorin", "Incanus"] );Class::Struct 模 块 为 你 创 建 上 面 的 所 有 四 种 方 法 。 因 为 它 遵 守 子 类 安 全 原 则 , 总 是 在 域名 字 前 面 前 缀 类 名 字 , 所 以 它 还 允 许 一 个 继 承 类 可 以 拥 有 它 自 己 独 立 的 与 基 类 同 名 的 域 , 而又 不 用 担 心 会 发 生 冲 突 。 这 就 意 味 着 你 在 用 于 这 个 实 例 变 量 的 时 候 , 必 须 用"Person::name" 而 不 能 用 "name" 当 作 散 列 键 字 来 访 问 散 列 表 。在 结 构 声 明 里 的 数 据 域 可 以 不 是 <strong>Perl</strong> 的 基 本 类 型 。 它 们 也 可 以 声 明 其 他 的 类 , 但 是 和struct 一 起 创 建 的 类 并 非 运 行 得 最 好 , 因 为 那 些 对 类 的 特 性 做 出 假 设 的 函 数 并 不 是 对 所 有的 类 都 能 够 明 察 秋 毫 。 比 如 , 对 于 合 适 的 类 而 言 , 会 调 用 new 方 法 来 初 始 化 它 们 , 但 是 很多 类 有 其 他 名 字 的 构 造 器 。参 阅 第 三 十 二 章 , 标 准 模 块 , 以 及 它 的 联 机 文 档 里 关 于 Class::Struct 的 描 述 。 许 多 标 准模 块 使 用 Class::Struct 来 实 现 它 们 的 类 , 包 括 User::pwent 和 Net::hostent。 阅 读它 们 的 代 码 会 很 有 收 获 。317


12.7.3 使 用 Autoloading( 自 动 装 载 ) 生 成 指 示 器正 如 我 们 早 先 提 到 过 的 , 当 你 调 用 一 个 不 存 在 的 方 法 的 时 候 ,<strong>Perl</strong> 有 两 种 不 同 的 手 段 搜 索一 个 AUTOLOAD 方 法 , 使 用 哪 种 方 法 取 决 于 你 是 否 声 明 了 一 个 存 根 方 法 。 你 可 以 利 用 这个 特 性 提 供 访 问 对 象 的 实 例 数 据 的 方 法 , 而 又 不 用 为 每 个 实 例 书 写 独 立 的 函 数 。 在AUTOLOAD 过 程 内 部 , 实 际 被 调 用 的 方 法 的 名 字 可 以 从 $AUTOLOAD 变 量 中 检 索 出 来 。让 我 们 看 看 下 面 下 面 的 代 码 :user Person;$him = Person->new;$him->name("Weiping");$him->race("Man");$him->aliases( ["Laser", "BitBIRD", "chemi"] );printf "%s is of the race of %s. \n", $him->name, $him->race;printf "His aliases are: ", join(", ", @{$him->aliases}), ".\n";和 以 前 一 样 , 这 个 版 本 的 Person 类 实 现 了 一 个 有 三 个 域 的 数 据 结 构 :name,race, 和aliases:package Person;use Carp;my %Fields = ("Person::name" => "unnamed","Person::race" => "unknown","Person::aliases" => [],);# 下 一 个 声 明 保 证 我 们 拿 到 自 己 的 autoloader( 自 动 装 载 器 )。use subs qw(name race aliases);318


sub new {my $invocant = shift;my $class = ref($invocant) || $invocant;my $self = { %Fields, @_};# 类 似 Class::Struct 的 克 隆bless $self, $class;return $self;}sub AUTOLOAD {my $self = shift;# 只 处 理 实 例 方 法 , 而 不 处 理 类 方 法croak "$self not an object" unless ref($invocant);my $name = our $AUTOLOAD;return if $name =~ /::DESTROY$/;unless (exist $self->{name}) {croak "Can't access `$name' field in $self";}if (@_) {return $self->{$name} = shift }else { return $self->{$name} }}如 你 所 见 , 这 里 你 可 找 不 到 叫 name,race, 或 者 aliases 的 方 法 。AUTOLOAD 过 程 为你 照 看 那 些 事 情 。 当 某 人 使 用 $him->name("Aragorn") 的 时 候 , 那 么 <strong>Perl</strong> 就 调 用AUTOLOAD, 同 时 把 $AUTOLOAD 设 置 为 "Person::name"。 出 于 方 便 考 虑 , 我 们 用了 全 名 , 这 是 访 问 保 存 在 对 象 散 列 里 的 数 据 域 的 最 正 确 的 方 法 。 这 样 你 可 以 把 这 个 类 用 做 一个 更 大 的 继 承 级 中 的 一 部 分 , 而 又 不 用 担 心 会 和 其 他 类 中 使 用 的 同 名 数 据 域 冲 突 。319


12.7.4 用 闭 合 域 生 成 指 示 器大 多 数 指 示 器 方 法 实 际 上 干 的 是 一 样 的 事 情 : 它 们 只 是 简 简 单 单 地 从 实 例 变 量 中 把 数 值 抓 过来 并 保 存 起 来 . 在 <strong>Perl</strong> 里 , 创 建 一 个 近 乎 相 同 的 函 数 族 的 最 自 然 的 方 法 就 是 在 一 个 闭 合 区域 里 循 环 . 但 是 闭 合 域 是 匿 名 函 数 , 它 们 缺 少 名 字 , 而 方 法 必 须 是 类 所 在 包 的 符 号 表 的 命 名子 过 程 , 这 样 它 们 才 能 通 过 名 字 来 调 用 . 不 过 这 不 算 什 么 问 题 -- 只 要 把 那 个 闭 合 域 赋 值 给一 个 名 字 合 适 的 类 型 团 就 可 以 了 .package Person;sub new {my $invocant = shift;my $self = bless( {}, ref $invocant || $invocant);$self->init();return $self;}sub init {my $self = shift;$self->name("unnamed");$self->race("unknown");$self->aliases([]);}for my $field (qw(name race aliases)) {my $slot = __PACKAGE__ . "::$field";no strict "refs";# 这 样 指 向 类 型 团 的 符 号 引 用 就 可 以 用 了320


*$field = sub {my $self = shift;$self->{$slot} = shift if @_;return $self->{$slot};};}闭 合 域 是 为 你 的 实 例 数 据 创 建 一 个 多 用 途 指 示 器 的 最 干 净 的 操 作 方 法 . 而 且 不 论 对 计 算 机 还是 你 而 言 , 它 都 很 有 效 . 不 仅 所 有 指 示 器 都 共 享 同 一 段 代 码 ( 它 们 只 需 要 它 们 自 己 的 词 法 本 ),而 且 以 后 你 要 增 加 其 他 属 性 也 方 便 , 你 要 做 的 修 改 是 最 少 的 : 只 需 要 给 for 循 环 增 加 一 条或 更 多 条 单 词 , 以 及 在 init 方 法 里 加 上 几 句 就 可 以 了 .12.7.5 将 闭 合 域 用 于 私 有 对 象到 目 前 为 止 , 这 些 管 理 实 例 数 据 的 技 巧 还 没 有 提 供 " 避 免 " 外 部 对 数 据 的 访 问 的 机 制 . 任 何 类以 外 的 对 象 都 可 以 打 开 对 象 的 黑 盒 子 然 后 查 看 内 部 -- 只 要 它 们 不 怕 质 保 书 失 效 . 增 强 私 有 性又 好 象 阻 碍 了 人 们 完 成 任 务 .<strong>Perl</strong> 的 哲 学 是 最 好 把 一 个 人 的 数 据 用 下 面 的 标 记 封 装 起 来 :IN CASE OF FIREBREAK GLASS如 果 可 能 , 你 应 该 尊 重 这 样 的 封 装 , 但 你 在 紧 急 情 况 ( 比 如 调 试 ) 下 仍 然 可 以 很 容 易 地 访 问其 内 容 .但 是 如 果 你 确 实 想 强 调 私 有 性 , <strong>Perl</strong> 不 会 给 你 设 置 障 碍 .<strong>Perl</strong> 提 供 低 层 次 的 制 作 块 , 你可 以 用 这 些 制 作 块 围 绕 在 你 的 类 和 其 对 象 周 围 形 成 无 法 进 入 的 私 有 保 护 网 -- 实 际 上 , 它 甚 至比 许 多 流 行 的 面 向 对 象 的 语 言 的 保 护 还 要 强 . 它 们 内 部 的 词 法 范 围 和 词 法 变 量 是 这 个 东 西 的关 键 组 件 , 而 闭 合 域 起 到 了 关 键 的 作 用 .在 " 私 有 方 法 " 节 里 , 我 们 看 到 了 一 个 类 如 何 才 能 使 用 闭 合 域 来 实 现 那 种 模 块 文 件 外 部 不 可 见的 方 法 . 稍 后 我 们 将 看 看 指 示 器 方 法 , 它 们 把 类 数 据 归 执 得 连 类 的 其 他 部 分 都 无 法 进 行 不 受限 制 的 访 问 。 那 些 仍 然 是 闭 合 域 相 当 传 统 的 用 法 。 真 正 让 我 们 感 兴 趣 的 东 西 是 把 闭 合 域 用 做一 个 对 象 。 该 对 象 的 实 例 变 量 被 锁 在 该 对 象 内 部 -- 也 就 是 说 , 闭 合 域 , 也 只 有 闭 合 域 才 能 自由 访 问 。 这 是 非 常 强 的 封 装 形 式 ; 这 种 方 法 不 仅 可 以 防 止 外 部 对 对 象 内 部 的 操 作 , 甚 至 连 同一 个 类 里 面 的 其 他 方 法 也 必 须 使 用 恰 当 的 访 问 方 法 来 获 取 对 象 的 实 例 数 据 。321


下 面 是 一 个 解 释 如 何 实 现 这 些 的 例 子 。 我 们 将 把 闭 合 域 同 时 用 于 生 成 对 象 本 身 和 生 成 指 示器 :package Person;sub new {my $invocant = shift;my $class = ref($invocant) || $invocant;my $data = {NAME => "unnamed",RACE => "unknown",ALIASES =>[],};my $self = sub {my $field = shift;################################## 在 这 里 进 行 访 问 检 查 ##################################if (@_) { $data->{$field} = shift }return $data->{$field};};bless ($self, $class);return $self;};322


# 生 成 方 法 名for my $field (qw(name race aliases)) {no strict "refs";# 为 了 访 问 符 号 表*$field = sub {my $self = shift;return $self->(uc $field, @_);};}new 方 法 创 建 和 返 回 的 对 象 不 再 是 一 个 散 列 , 因 为 它 在 我 们 刚 才 看 到 的 其 他 的 构 造 器 里 。实 际 上 是 一 个 闭 合 域 访 问 存 储 在 散 列 里 的 属 性 数 据 , 该 闭 合 域 是 唯 一 可 以 访 问 该 属 性 数 据 的类 成 员 , 而 存 储 数 据 的 散 列 是 用 $data 引 用 的 。 一 旦 构 造 器 调 用 完 成 , 访 问 $data ( 里面 的 属 性 ) 的 唯 一 方 法 就 是 通 过 闭 合 域 。在 一 个 类 似 $him->name("Bombadil") 这 样 的 调 用 中 , 在 $self 里 存 储 的 调 用 对 象 是那 个 闭 合 域 , 这 个 闭 合 域 已 经 由 构 造 器 赐 福 (bless) 并 返 回 了 。 对 于 一 个 闭 合 而 言 , 我 们除 了 能 调 用 它 以 外 , 干 不 了 太 多 什 么 事 , 因 此 我 们 只 是 做 $self->(uc $field, @_)。 请 不要 被 箭 头 糊 弄 了 , 这 条 语 句 只 是 一 个 正 规 的 间 接 函 数 调 用 , 而 不 是 一 个 方 法 调 用 。 初 始 参 数是 字 串 "name", 而 其 他 的 参 数 就 是 那 些 传 进 来 的 。( 注 : 当 然 , 双 函 数 调 用 比 较 慢 , 但是 如 果 你 想 快 些 , 你 还 会 首 先 选 用 对 象 吗 ?) 一 旦 我 们 在 闭 合 域 内 部 执 行 , 那 么 在 $data 里的 散 列 就 又 可 以 访 问 得 到 了 。 这 样 闭 合 域 就 可 以 自 由 地 给 任 何 它 愿 意 的 对 象 访 问 权 限 , 而 封杀 任 何 它 讨 厌 的 对 象 的 访 问 。在 闭 合 域 外 面 没 有 任 何 对 象 可 以 不 经 中 介 地 访 问 这 些 非 常 私 有 的 实 例 数 据 , 甚 至 该 类 里 面 的其 他 方 法 都 不 能 。 它 们 可 以 按 照 for 循 环 生 成 的 方 法 来 调 用 闭 合 域 , 可 能 是 设 置 一 个 该 类从 来 没 有 听 说 过 的 实 例 变 量 。 但 是 我 们 很 容 易 通 过 在 构 造 器 里 放 上 几 段 代 码 来 阻 止 这 样 的 方法 使 用 , 放 那 些 代 码 的 地 方 就 是 你 看 到 的 上 面 的 访 问 检 查 注 释 的 地 方 。 首 先 , 我 们 需 要 一 个通 用 的 导 言 :use Carp;local $Carp::CarpLevel = 1;# 保 持 牢 骚 消 息 短 小my ($cpack, $cfile) = caller();323


然 后 我 们 进 行 各 个 检 查 。 第 一 个 要 确 保 声 明 的 属 性 名 存 在 :croak "No valid field '$field' in object"unless exists $data->{$field};下 面 这 条 语 句 只 允 许 来 自 同 一 个 文 件 的 调 用 :carp "Unmediated access denied to foreign file"unless $cfiled eq __FILE__;下 面 这 条 语 句 只 允 许 来 自 同 一 个 包 的 调 用 :carp "Unmediated access denied to foreign package ${cpack}::"unless $cpack eq __PACKAGE__;所 有 这 些 代 码 都 只 检 查 未 经 中 介 的 访 问 。 那 些 有 礼 貌 地 使 用 该 类 指 定 的 方 法 访 问 的 用 户 不 会受 到 这 些 约 束 。<strong>Perl</strong> 会 给 你 一 些 工 具 , 让 你 想 多 挑 剔 就 有 多 挑 剔 。 幸 运 的 是 , 不 是 所 有 人都 这 样 。不 过 有 些 人 应 该 挑 剔 。 当 你 写 飞 行 控 制 软 件 的 时 候 , 严 格 些 就 是 正 确 的 了 。 如 果 你 想 成 为 或者 要 成 为 这 些 人 员 , 而 且 你 喜 欢 使 用 能 干 活 的 代 码 而 不 是 自 己 重 新 发 明 所 有 东 西 , 那 么 请 看看 CPAN 上 Damian Conway 的 Tie::SecureHash 模 块 。 它 实 现 了 严 格 的 散 列 , 支持 公 有 , 保 护 和 私 有 约 束 。 它 还 对 付 我 们 前 面 的 例 子 中 忽 略 掉 的 继 承 性 问 题 。Damian 甚至 还 写 了 一 个 更 雄 心 勃 勃 的 模 块 ,Class::Contract, 在 <strong>Perl</strong> 灵 活 的 对 象 系 统 上 强 加 了 一层 正 式 的 软 件 工 程 层 。 这 个 模 块 的 特 性 列 表 看 起 来 就 象 一 本 计 算 机 科 学 教 授 的 软 件 工 程 课 本的 目 录 ,( 注 : 你 知 道 Damian 是 干 什 么 的 吗 ? 顺 便 说 一 句 , 我 们 非 常 建 议 你 看 看 他 的 书 ,Object Oriented <strong>Perl</strong>( 面 向 对 象 的 <strong>Perl</strong>)(Manning Publications, 1999))。 包 括 强制 封 装 , 静 态 继 承 和 用 于 面 向 对 象 的 <strong>Perl</strong> 的 按 需 设 计 条 件 检 查 , 以 及 一 些 用 于 对 象 和 类 层次 的 属 性 , 方 法 , 构 造 器 和 析 构 器 定 义 的 的 可 声 明 的 语 法 , 以 及 前 提 , 后 记 和 类 固 定 。 天 !12.7.6 新 技 巧到 了 <strong>Perl</strong> 5.6, 你 还 可 以 声 明 一 个 方 法 并 指 出 它 是 返 回 左 值 的 。 这 些 是 通 过 做 值 子 过 程 属性 实 现 的 ( 不 要 和 对 象 方 法 混 淆 了 )。 这 个 实 验 性 的 特 性 允 许 你 把 该 方 法 当 作 某 些 可 以 在 一个 等 号 左 边 出 现 的 东 西 :package Critter;324


sub new {my $class = shift;my $self = { pups => 0, @_ }; # 覆 盖 缺 省 。bless $self, $class;}sub pups : lvalue {# 我 们 稍 后 给 pups() 赋 值my $self = shift;$self->{pups};}package main;$varmint = Critter->new(pups => 4);$varmint->pups *= 2;$varmint->pups =~ s/(.)/$1$1/;print $varmint->pups;# 赋 给 $varmint->pups!# 现 场 修 改 $varmint->pups!# 现 在 我 们 有 88 个 pups。这 么 做 让 你 以 为 $varminit->pups 仍 然 是 一 个 遵 守 封 装 的 变 量 。 参 阅 第 六 章 , 子 过 程 ,的 “ 左 值 属 性 ”。如 果 你 运 行 的 是 一 个 线 程 化 的 <strong>Perl</strong>, 并 且 你 想 确 保 只 有 一 个 线 程 可 以 调 用 一 个 对 象 的 某 个方 法 , 你 可 以 使 用 locked 和 method 属 性 实 现 这 些 功 能 :sub pups : locked method {...}当 任 意 线 程 调 用 一 个 对 象 上 的 pups 方 法 的 时 候 ,<strong>Perl</strong> 在 执 行 前 锁 住 对 象 , 阻 止 其 他 线 程做 同 样 的 事 情 。 参 阅 第 六 章 里 的 “locked 和 method 属 性 ”。325


12.8 管 理 类 数 据我 们 已 经 看 到 了 按 对 象 访 问 对 象 数 据 的 几 种 不 同 方 法 。 不 过 , 有 时 候 你 希 望 有 些 通 用 的 状 态在 一 个 类 里 的 所 有 对 象 之 间 共 享 。 不 管 你 使 用 哪 个 类 实 例 ( 对 象 ) 来 访 问 他 们 , 这 些 变 量 是整 个 类 的 全 局 量 , 而 不 只 是 该 类 的 一 个 实 例 ,(C++ 程 序 员 会 认 为 这 些 是 静 态 成 员 数 据 。)下 面 是 一 些 类 变 量 能 帮 助 你 的 情 况 :• 保 存 一 个 曾 经 创 建 的 所 有 对 象 的 计 数 , 或 者 是 仍 然 存 活 的 数 量 。• 保 存 一 个 你 可 以 叙 述 的 所 有 对 象 的 列 表• 保 存 一 个 全 类 范 围 调 试 方 法 使 用 的 日 志 文 件 的 文 件 名 或 者 文 件 描 述 符 。• 保 存 收 集 性 数 据 , 比 如 想 一 个 网 段 里 的 所 有 ATM 某 一 天 支 取 的 现 金 的 总 额 。• 跟 踪 类 创 建 的 最 后 一 个 或 者 最 近 访 问 过 的 对 象 。• 保 存 一 个 内 存 里 的 对 象 的 缓 存 , 这 些 对 象 是 从 永 久 内 存 中 重 新 构 建 的 。• 提 供 一 个 反 转 的 查 找 表 , 这 样 你 就 可 以 找 到 一 个 基 于 其 属 性 之 一 的 数 值 的 对 象 。然 后 问 题 就 到 了 哪 里 去 存 储 这 些 共 享 属 性 上 面 。<strong>Perl</strong> 没 有 特 殊 的 语 法 机 制 用 于 声 明 类 属 性 ,用 于 实 例 属 性 的 也 多 不 了 什 么 。<strong>Perl</strong> 给 开 发 者 提 供 了 一 套 广 泛 强 大 而 且 灵 活 的 特 性 , 这 些特 性 可 以 根 据 不 同 情 况 分 别 雕 琢 成 适 合 特 定 的 需 要 。 然 后 你 就 可 以 根 据 某 种 情 况 选 择 最 有 效的 机 制 , 而 不 是 被 迫 屈 就 于 别 人 的 设 计 决 定 。 另 外 , 你 也 可 以 选 择 别 人 的 设 计 决 定 —— 那些 已 经 打 包 并 且 放 到 CPAN 去 的 东 西 。 同 样 ," 回 字 有 四 种 写 法 "。和 任 何 与 类 相 关 的 东 西 一 样 , 类 数 据 不 能 被 直 接 访 问 , 尤 其 是 从 类 实 现 的 外 部 。 封 装 的 理 论没 有 太 多 关 于 为 实 例 变 量 设 置 严 格 受 控 的 指 示 器 方 法 的 内 容 , 但 是 却 发 明 了 public 来 直 接欺 骗 你 的 类 变 量 , 就 好 象 设 置 $SomeClass::Debug = 1。 要 建 立 接 口 和 实 现 之 间 干 净 的防 火 墙 , 你 可 以 创 建 类 似 你 用 于 实 例 数 据 的 指 示 器 方 法 来 操 作 类 数 据 。假 设 我 们 想 跟 踪 Critter 对 象 的 全 部 数 量 。 我 们 将 把 这 个 数 量 存 储 在 一 个 包 变 量 里 , 但 是提 供 一 个 方 法 调 用 population, 这 样 此 类 的 用 户 就 不 用 知 道 这 个 实 现 :Critter->population()$gollum->population()# 通 过 类 名 字 访 问# 通 过 实 例 访 问因 为 在 <strong>Perl</strong> 里 , 类 只 是 一 个 包 , 存 储 一 个 类 的 最 自 然 的 位 置 是 在 一 个 包 变 量 里 。 下 面 就 是这 样 的 一 个 类 的 简 单 实 现 。population 方 法 忽 略 它 的 调 用 者 并 且 只 返 回 该 包 变 量 的 当 前 值$Population。( 有 些 程 序 喜 欢 给 它 们 的 全 局 量 大 写 开 头 。)package Critter;326


our $population = 0;sub pupulation { return $Population; }sub DESTROY {$Population --}sub spawn {my $invocant = shift;my $class = ref($invocant) || $invocant;$Population++;return bless { name => shift || "anon" }, $class;}sub name {my $self = shift;$self->{name} = shift if @_;return $self->{name};}如 果 你 想 把 类 数 据 方 法 做 得 想 实 例 数 据 的 指 示 器 那 样 , 这 么 做 :our $Debugging = 0;# 类 数 据sub debug {shift;# 有 意 忽 略 调 用 者$Debugging = shift if @_;return $Debugging;}现 在 你 可 以 为 给 该 类 或 者 它 的 任 何 实 例 设 置 全 局 调 试 级 别 。因 为 它 是 一 个 包 变 量 , 所 以 $Debugging 是 可 以 全 局 访 问 的 。 但 是 如 果 你 把 our 变 量 改成 my, 那 么 就 只 有 该 文 件 里 后 面 的 代 码 可 以 看 到 它 。 你 还 可 以 走 得 再 远 一 些 —— 你 可 以把 对 类 属 性 的 访 问 限 制 在 该 类 本 身 其 余 部 分 里 。 把 该 变 量 声 明 裹 在 一 个 块 范 围 里 :327


{my $Debugging = 0;# 词 法 范 围 的 类 数 据sub debug {shift;# 有 意 忽 略 调 用 者$Debugging = shift if @_;return $Debugging;}}现 在 没 有 人 可 以 不 通 过 使 用 指 示 器 方 法 来 读 写 该 类 属 性 , 因 为 只 有 那 个 子 过 程 和 变 量 在 同 一个 范 围 因 而 可 以 访 问 它 。如 果 一 个 生 成 的 类 继 承 了 这 些 类 指 示 器 , 那 么 它 们 仍 然 访 问 最 初 的 数 据 , 不 管 变 量 是 用 our还 是 用 my 定 义 的 。 数 据 是 包 无 关 的 。 当 方 法 在 它 们 最 初 定 义 的 地 方 执 行 的 时 候 , 你 可 以看 到 它 们 , 但 是 在 调 用 它 的 类 里 面 可 不 一 定 看 得 到 。对 于 某 些 类 数 据 , 这 个 方 法 运 行 得 很 好 , 但 对 于 其 他 的 而 言 , 就 不 一 定 了 。 假 设 我 们 创 建 了一 个 Critter 的 Warg 子 类 。 如 果 我 们 想 分 离 我 们 的 两 个 数 量 , Warg 就 不 能 继 承Critter 的 population 方 法 , 因 为 那 个 方 法 总 是 返 回 $Critter::Poplation 的 值 。你 可 能 会 不 得 不 根 据 实 际 情 况 决 定 类 属 性 与 包 相 关 是 否 有 用 。 如 果 你 想 要 包 相 关 的 属 性 , 可以 使 用 调 用 者 的 类 来 定 位 保 存 着 类 数 据 的 包 :sub debug {my $invocant = shift;my $class = ref($invocant) || $invocant;my $varname = $class . "::Debugging";no strict "refs";# 符 号 访 问 包 数 据$$varname = shift if @_;return $$varname;}328


我 们 暂 时 废 除 严 格 的 引 用 , 因 为 不 这 样 的 话 我 们 就 不 能 把 符 号 名 全 名 用 于 包 的 全 局 量 。 这 是绝 对 有 道 理 的 : 因 为 所 有 定 义 的 包 变 量 都 存 活 在 一 个 包 里 , 通 过 该 包 的 符 号 表 访 问 它 们 是 没什 么 错 的 。另 外 一 个 方 法 是 令 对 象 需 要 的 所 有 东 西 —— 甚 至 它 的 全 局 类 数 据 —— 都 可 以 由 该 对 象 访 问( 或 者 可 以 当 作 参 数 传 递 )。 要 实 现 这 些 功 能 , 你 通 常 不 得 不 为 每 个 类 都 做 一 个 精 制 的 构 造器 , 或 者 至 少 要 做 一 个 构 造 器 可 以 调 用 的 精 制 的 初 始 化 过 程 。 在 构 造 器 或 者 初 始 化 器 里 , 你把 对 任 何 类 数 据 的 引 用 直 接 保 存 在 该 对 象 本 身 里 面 , 这 样 就 没 有 什 么 东 西 需 要 查 看 它 们 了 。访 问 器 方 法 使 用 该 对 象 来 查 找 到 数 据 的 引 用 。不 要 把 定 位 类 数 据 的 复 杂 性 放 到 每 个 方 法 里 , 只 要 让 对 象 告 诉 方 法 数 据 在 哪 里 就 可 以 了 。 这个 办 法 只 有 在 类 数 据 指 示 器 方 法 被 当 作 实 例 方 法 调 用 的 时 候 才 好 用 , 因 为 类 数 据 可 能 在 一 个你 用 包 名 字 无 法 访 问 到 的 词 法 范 围 里 。不 管 你 是 怎 么 看 待 它 , 与 包 相 关 的 类 数 据 总 是 有 点 难 用 。 继 承 一 个 类 数 据 的 指 示 器 方 法 的 确更 清 晰 一 些 , 你 同 样 有 效 地 继 承 了 它 能 够 访 问 的 状 态 数 据 。 参 阅 perltootc 手 册 页 获 取 管理 类 数 据 的 更 多 更 灵 活 的 方 法 。12.9 总 结除 了 其 他 东 西 以 外 , 大 概 就 这 么 多 东 西 了 。 现 在 你 只 需 要 走 出 去 买 本 关 于 面 向 对 象 的 设 计 方法 学 的 书 , 然 后 再 花 N 个 月 的 时 间 来 学 习 它 就 行 了 。第 十 三 章 重 载对 象 非 常 酷 , 但 有 时 候 它 有 点 太 酷 了 。 有 时 候 你 会 希 望 它 表 现 得 少 象 一 点 对 象 而 更 象 普通 的 数 据 类 型 一 点 。 但 是 实 现 这 个 却 有 问 题 : 对 象 是 用 引 用 代 表 的 引 用 , 而 引 用 除 了 当 引 用以 外 没 什 么 别 的 用 途 。 你 不 能 在 引 用 上 做 加 法 , 也 不 能 打 印 它 们 , 甚 至 也 不 能 给 它 们 使 用 许多 <strong>Perl</strong> 的 内 建 操 作 符 。 你 能 做 的 唯 一 一 件 事 就 是 对 它 们 解 引 用 。 因 此 你 会 发 现 自 己 在 写 许多 明 确 的 方 法 调 用 , 象 :print $object->as_string;$new_object = $subject->add($object);329


象 这 样 的 明 确 的 解 引 用 通 常 都 是 好 事 ; 你 决 不 能 把 你 的 引 用 和 指 示 物 混 淆 , 除 非 你 想 混 淆 它们 。 下 面 可 能 就 是 你 想 混 淆 的 情 况 之 一 。 如 果 你 把 你 的 类 设 计 成 使 用 重 载 , 你 可 以 装 做 看 不到 引 用 而 只 是 说 :print $object;$new_object = $subject + $object;当 你 重 载 某 个 <strong>Perl</strong> 的 内 建 操 作 符 的 时 候 , 你 实 际 上 定 义 了 把 它 应 用 于 某 特 定 类 型 的 对 象 时的 特 性 。 有 很 多 <strong>Perl</strong> 的 模 块 利 用 了 重 载 , 比 如 Math::BigInt, 它 让 你 可 以 创 建Math::BigInt 对 象 , 这 些 对 象 的 性 质 和 普 通 整 数 一 样 , 但 是 没 有 尺 寸 限 制 。 你 可 以 用 + 把它 们 相 加 , 用 / 把 它 们 相 除 , 用 比 较 它 们 , 以 及 用 print 打 印 它 们 。请 注 意 重 载 和 自 动 装 载 (autoload) 是 不 一 样 的 , 自 动 装 载 是 根 据 需 要 装 载 一 个 缺 失 的 函数 或 方 法 。 重 载 和 覆 盖 (overriding) 也 是 不 一 样 的 , 覆 盖 是 一 个 函 数 或 方 法 覆 盖 了 另 外一 个 。 重 载 什 么 东 西 也 不 隐 藏 ; 它 给 一 个 操 作 添 加 了 新 含 义 , 否 则 在 区 区 引 用 上 进 行 该 操 作就 是 无 聊 的 举 动 。13.1 overload 用 法use overload 用 法 实 现 操 作 符 重 载 。 你 给 它 提 供 一 个 操 作 符 和 对 应 的 性 质 的 键 字 / 数 值 列表 :package MyClass;use overload '+' => \&myadd, # 代 码 引 用'


对 于 单 目 操 作 符 ( 那 些 只 有 一 个 操 作 数 的 东 西 , 比 如 abs), 当 该 操 作 符 应 用 于 该 类 的 一个 对 象 的 时 候 则 调 用 为 该 类 声 明 的 句 柄 。对 于 双 目 操 作 符 而 言 , 比 如 + 或


里 用 + 表 示 如 何 相 加 ), 那 么 第 三 个 参 数 就 不 仅 仅 是 假 , 而 是 undef。 这 个 区 别 可 以 应用 一 些 优 化 。举 个 例 子 , 这 里 是 一 个 类 , 它 让 你 操 作 一 个 有 范 围 限 制 的 数 字 。 它 重 载 了 + 和 -, 这 样对 象 相 加 或 相 减 的 范 围 局 限 在 0 和 255 之 间 :package ClipByte;use overload '+' => \&clip_add,'-' => \&clip_sub;sub new {my $class = shift;my $value = shift;return bless \$value => $class;}sub clip_add {my ($x, $y) = @_;my ($value) = ref($x) ? $$x : $x;$value += ref($y) ? $$y :$y;$value = 255 if $value > 255;$value = 0 if $value < 0;return bless \$value => ref($x);}sub clip_sub {332


my ($x, $y, $swap) = @_;my ($value) = (ref $x) ? $$x : $x;$value-= (ref $y) ? $$y : $y;if ($swap) { $value = -$value }$value = 255 if $value > 255;$value = 0 if $value < 0;return bless \$value => ref($x);}package main;$byte1 = ClipByte->new(200);$byte2 = ClipByte->new(100);$byte3 = $byte1 + $byte2; # 255$byte4 = $byte1 - $byte2; # 100$byte5 = 150 - $byte2; # 50你 可 以 注 意 到 这 里 的 每 一 个 函 数 实 际 上 都 是 一 个 构 造 器 , 所 以 每 一 个 都 使 用 bless 把 它 的新 的 对 象 赐 福 回 给 当 前 类 -- 不 管 是 什 么 ; 我 们 假 设 我 们 的 类 可 以 被 继 承 。 我 们 还 假 设 如 果$y 是 一 个 引 用 , 它 是 指 向 一 个 我 们 自 己 类 型 的 对 象 的 引 用 。 除 了 测 试 ref($y) 以 外 , 如果 我 们 想 更 彻 底 一 些 ( 也 慢 一 些 ) 我 们 也 可 以 调 用 $y->isa("ClipByte")。13.3 可 重 载 操 作 符你 只 能 重 载 一 部 分 操 作 符 , 它 们 在 表 13-1 列 出 。 当 你 用 use overload 时 , 操 作 符 也 在%overload::ops 散 列 列 出 供 你 使 用 , 不 过 其 内 容 和 这 里 的 有 一 点 区 别 。表 13-1。 重 载 操 作 符333


范 畴转 换算 术"" 0+ bool操 作 符+ - * / % ** x . neg逻 辑 !位 操 作 & | ~ ^ ! >赋 值 += -= *= /= %= **= x= .= = ++ --比 较数 学文 本= < < > >= = lt le gt ge eq ne cmpatan2 cos sin exp abs log sqrt 解 引 用 ${} @{} %{} &{} *{}伪 nomethod fallback =>请 注 意 neg,bool,nomethod, 和 fallback 实 际 上 不 是 <strong>Perl</strong> 的 操 作 符 。 五 种 解 引 用 ,"", 和 0+ 可 能 看 起 来 也 不 象 操 作 符 。 不 过 , 它 们 都 是 你 给 use overload 提 供 的 参 数 列表 的 有 效 键 字 。 这 不 是 什 么 问 题 。 我 们 会 告 诉 你 一 个 小 秘 密 : 说 overload 用 法 重 载 了 操作 符 是 一 个 小 花 招 。 它 重 载 了 下 层 的 操 作 符 , 不 管 是 通 过 他 们 的 “ 正 式 ” 操 作 符 明 确 调 用 的 ,还 是 通 过 一 些 相 关 的 操 作 符 隐 含 调 用 的 。( 我 们 提 到 的 伪 操 作 符 只 能 隐 含 地 调 用 。) 换 句 话说 , 重 载 不 是 在 语 句 级 发 生 的 , 而 是 在 语 义 级 。 原 因 是 我 们 不 是 为 了 好 看 而 是 为 了 正 确 。 请随 意 进 行 概 括 。请 注 意 = 并 不 象 你 预 料 的 那 样 重 载 <strong>Perl</strong> 的 赋 值 操 作 符 。 那 样 做 是 错 的 , 稍 后 详 细 介 绍 这个 。我 们 将 从 转 换 操 作 符 开 始 讨 论 , 但 并 不 是 因 为 它 们 最 显 眼 ( 它 们 可 不 抢 眼 ), 而 是 因 为 它 们是 最 有 用 的 。 许 多 类 除 了 重 载 用 "" 键 字 ( 没 错 , 就 是 一 行 上 的 两 个 双 引 号 。) 声 明 的 字 串化 以 外 不 会 重 载 任 何 东 西 。转 换 操 作 符 : "",0+,bool 这 三 个 键 字 让 你 给 <strong>Perl</strong> 提 供 分 别 自 动 转 换 成 字 串 , 数 字 和布 尔 值 的 性 质 。当 一 个 非 字 串 变 量 当 作 字 串 使 用 的 时 候 , 我 们 就 说 是 发 生 了 字 串 化 。 当 你 通 过 打 印 , 替 换 ,连 接 , 或 者 是 把 它 用 做 一 个 散 列 键 字 等 方 法 把 一 个 变 量 转 换 成 字 串 时 就 发 生 这 个 动 作 。 字串 化 也 是 当 你 试 图 print 一 个 对 象 时 看 到 象 SCALAR(0xba5fe0) 这 样 的 东 西 的 原 因 。我 们 说 当 一 个 非 数 字 值 在 任 意 数 字 环 境 下 转 换 成 一 个 数 字 时 发 生 的 事 情 叫 数 字 化 , 这 些 数字 环 境 可 以 是 任 意 数 学 表 达 式 , 数 组 下 标 , 或 者 是 ... 范 围 操 作 符 的 操 作 数 。334


最 后 , 尽 管 咱 们 这 里 没 有 谁 急 于 把 它 称 做 布 尔 化 , 你 还 是 可 以 通 过 创 建 一 个 bool 句 柄 来定 义 一 个 对 象 在 布 尔 环 境 里 应 该 如 何 解 释 ( 比 如 if, unless,while, for,and,or,&&,||,?:, 或 者 grep 表 达 式 的 语 句 块 )。如 果 你 已 经 有 了 它 们 中 的 任 意 一 个 , 你 就 可 以 自 动 生 成 这 三 个 转 换 操 作 符 的 任 何 一 个 ( 我们 稍 后 解 释 自 动 生 成 )。 你 的 句 柄 可 以 返 回 你 喜 欢 的 任 何 值 。 请 注 意 如 果 触 发 转 换 的 操 作也 被 重 载 , 则 该 重 载 将 在 后 面 立 即 发 生 。这 里 是 一 个 "" 的 例 子 , 它 在 字 串 化 时 调 用 一 个 对 象 的 as_string 句 柄 。 别 忘 记 引 起 引 号 :package Person;use overload q("") => \&as_string;sub new { my $class = shift; return bless {@_} => $class; }sub as_string {my $self = shift; my ($key, $value, $result); while (( $key, $value) = each%$self) { $result .= "$key => $value\n"; } return $result; }$obj = Person->new(height => 72, weight => 165, eyes => "vrown"); print$obj;这 里 会 打 印 下 面 的 内 容 ( 一 散 列 顺 序 ), 而 不 是 什 么 Person=HASH(0xba1350) 之 类 的东 西 :weight => 165 ...( 我 们 真 诚 的 希 望 此 人 不 是 用 公 斤 和 厘 米 做 单 位 的 。)算 术 操 作 符 :+,-,*,/,%,**,x,.,neg除 了 neg 以 外 这 些 应 该 都 很 熟 悉 ,neg 是 一 个 用 于 单 目 负 号 (-123 里 的 -) 的 特 殊 重 载键 字 。neg 和 - 键 字 之 间 的 区 别 允 许 你 给 单 目 负 号 和 双 目 负 号 ( 更 常 见 的 叫 法 是 减 号 )声 明 不 同 的 性 质 。如 果 你 重 载 了 - 而 没 有 重 载 neg, 然 后 试 图 使 用 一 个 单 目 负 号 ,<strong>Perl</strong> 会 为 你 模 拟 一 个neg 句 柄 。 这 就 是 所 谓 的 自 动 生 成 , 就 是 说 某 些 操 作 符 可 以 合 理 地 从 其 他 操 作 符 中 归 纳 出来 ( 以 “ 该 重 载 操 作 符 将 和 该 普 通 操 作 符 有 一 样 的 关 系 ” 为 假 设 ) 。 因 为 单 目 符 号 可 以 表 示成 双 目 符 号 的 一 个 函 数 ( 也 就 是 ,-123 等 于 0-123), 在 - 够 用 的 时 候 ,<strong>Perl</strong> 并 不 强335


制 你 重 载 neg。( 当 然 , 如 果 你 已 经 独 裁 地 定 义 了 双 目 负 号 用 来 把 第 二 个 参 数 和 第 一 个 参数 分 开 , 那 么 单 目 操 作 符 会 是 一 个 很 好 的 抛 出 被 零 除 例 外 的 好 方 法 。)由 . 操 作 符 进 行 的 连 接 可 以 通 过 字 串 化 句 柄 ( 见 上 面 的 "" ) 自 动 生 成 。逻 辑 操 作 符 :!如 果 没 有 声 明 用 于 ! 的 句 柄 , 那 么 它 可 以 用 bool,"", 或 者 0+ 句 柄 自 动 生 成 。 如 果 你重 载 了 ! 操 作 符 , 那 么 当 表 现 你 的 请 求 的 时 候 , 同 样 还 会 触 发 not 操 作 符 。( 还 记 得 我们 的 小 秘 密 吗 ?) 你 可 能 觉 得 奇 怪 : 其 他 逻 辑 操 作 符 哪 里 去 了 ? 但 是 大 多 数 逻 辑 操 作 符 不能 重 载 , 因 为 它 们 是 短 路 的 。 他 们 实 际 上 是 控 制 流 操 作 符 , 这 样 它 们 就 可 以 推 迟 对 它 们 的一 些 参 数 的 计 算 。 那 也 是 ?: 操 作 符 不 能 重 载 的 原 因 。位 运 算 操 作 符 :&,|,~,^, ~ 操 作 符 是 一 个 单 目 操 作 符 ; 所 有 其 他 的 都 是 双目 。 下 面 是 我 们 如 何 重 载 >>, 使 之 能 做 类 似 chop 的 工 作 :package ShiftString ? ;use overload '>>' => \&right_shift, '""' => sub { ${ $_[0] } };sub new { my $class = shift; my $value = shift; return bless \$value =>$class; }sub right_shift { my ($x, $y) = @_; my $value = $$x; substr($value, -$y) = "";return bless \$value => ref($x); }$camel = ShiftString ? ->new("Camel"); $ram = $camel >>2; print $ram; #Cam赋 值 操 作 符 :+=,-=,*=,/=,%=,**=,x=,.=,=,++,-- 这 些赋 值 操 作 符 可 以 改 变 它 们 的 参 数 的 值 或 者 就 那 样 把 它 的 参 数 放 着 。 结 果 是 只 有 新 值 和 旧 值不 同 时 才 把 结 果 赋 值 给 左 手 边 的 操 作 数 。 这 样 就 允 许 同 一 个 句 柄 同 时 用 于 重 载 += 和 +。尽 管 这 么 做 是 可 以 的 , 不 过 我 们 并 不 建 议 你 这 么 做 , 因 为 根 据 我 们 稍 后 将 在 “ 当 缺 失 重 载 句柄 的 时 候 (nomethod 和 fallback)” 里 描 述 的 语 义 ,<strong>Perl</strong> 将 为 + 激 活 该 句 柄 , 同 时 假设 += 并 没 有 直 接 重 载 。连 接 (.=) 可 以 用 字 串 化 后 面 跟 着 普 通 的 字 串 连 接 自 动 生 成 。++ 和 -- 操 作 符 可 以 从 +和 -( 或 者 += 和 -=) 自 动 生 成 。实 现 ++ 和 -- 的 句 柄 可 能 会 变 更 ( 更 改 ) 他 们 的 参 数 。 如 果 你 希 望 自 增 也 能 对 字 母 有 效 ,你 可 以 用 类 似 下 面 的 句 柄 实 现 :336


package MagicDec;use overloadq(--) => \&decrement,q("") => sub { ${ $_[0] } };sub new {my $class = shift;my $value = shift;bless \$value => $class;}sub decrement {my @string = reverse split (//, ${ $_[0] } );my $i;for ( $i = 0; $i < @string; $i++ ) {last unless $string[$i] =~ /a/i;$string[$i] = chr( ord($string[$i]) + 25 );}$string[$i] = chr( ord($string[$i]) - 1);my $result = join(' ', reverse @string);$_[0] = bless \$result => ref($_[0]);}337


package main;for $normal (qw/perl NZ pa/) {$magic = MagicDec->new($normal);$magic --;print "$normal goes to $magic\n";}打 印 出 :perl goes to perkNZ goes to NYPa goes to Oz很 准 确 地 对 <strong>Perl</strong> 神 奇 的 字 串 自 增 操 作 符 做 了 反 向 工 程 。++$a 操 作 符 可 以 用 $a += 1 或 $a = $a +1 自 动 生 成 , 而 $a-- 使 用 $a -= 1 或 $a= $a - 1。 不 过 , 这 样 并 不 会 触 发 真 正 的 ++ 操 作 符 会 触 发 的 拷 贝 性 质 。 参 阅 本 章 稍 后 的“ 拷 贝 构 造 器 ”。比 较 操 作 符 :==,=,!=,,lt,le,gt,ge,eq,ne,cmp 如 果重 载 了 , 那 么 它 可 以 用 于 自 动 生 成 =, = 和 的 性 质 。 类 似 地 , 如 果重 载 了 cmp, 那 么 它 可 以 用 于 自 动 生 成 lt, le, gt, ge, eq 和 ne 的 性 质 。请 注 意 重 载 cmp 不 会 让 你 的 排 序 变 成 你 想 象 的 那 么 简 单 , 因 为 将 要 比 较 的 是 字 串 化 的 版本 的 对 象 , 而 不 是 对 象 本 身 。 如 果 你 的 目 的 是 对 象 本 身 那 么 你 还 会 希 望 重 载 ""。数 学 函 数 :atan2,cos,sin,exp,abs,log,sqrt 如 果 没 有 abs , 那 它 可 以 从 < 或 与 单 目 负 号 或 者 减 号 组 合 中 自 动 生 成 。重 载 可 以 用 于 为 单 目 负 号 或 者 为 abs 函 数 自 动 生 成 缺 失 的 句 柄 , 而 它 们 本 身 也 可 以 独 立的 被 重 载 。( 没 错 , 我 们 知 道 abs 看 起 来 象 个 函 数 , 而 单 目 负 号 象 个 操 作 符 , 但 是 从 <strong>Perl</strong>的 角 度 而 言 它 们 的 差 别 没 这 么 大 。)反 复 ( 迭 代 ) 操 作 符 : 使 用 readline ( 在 它 从 一 个 文 件 句 柄 读 入 数 据 的 时 候 , 象 while() 里 的 ) 或 者 glob ( 当 它 用 于 文 件 聚 团 的 时 候 , 比 如 在 @files = 里 )。338


package LuckyDraw;use overload'' => sub {my $self = shift;return splice @$self, rand @$self, 1;};sub new {my $class = shift;return bless [@_] => $class;}package main;$lotto = new LuckyDraw 1 .. 51;for (qw(1st 2nd 3rd 4th 5th 6th)) {$lucky_number = ;print "The $_ lucky number is: $lucky_number.\n";}$lucky_number = ;print "\nAnd the bonus number is: $lucky_number.\n";339


在 加 州 , 这 些 打 印 出 :The 1st lucky number is: 18The 2nd lucky number is: 11The 3rd lucky number is: 40The 4th lucky number is: 7The 5th lucky number is: 51The 6th lucky number is: 33And the bonus number is: 5解 引 用 操 作 符 :${},@{},%{},&{},*{} 对 标 量 , 数 组 , 散 列 , 子 过 程 , 和 团 的 解引 用 可 以 通 过 重 载 这 五 个 符 号 来 截 获 。<strong>Perl</strong> 的 overload 的 在 线 文 档 演 示 了 你 如 何 才 能 使 用 这 些 操 作 符 来 仿 真 你 自 己 的 伪 散列 。 下 面 是 一 个 更 简 单 的 例 子 , 它 实 现 了 一 个 有 匿 名 数 组 的 对 象 , 但 是 使 用 散 列 引 用 。 别试 图 把 他 当 作 真 的 散 列 用 ; 你 不 能 从 该 对 象 删 除 键 字 / 数 值 对 。 如 果 你 想 合 并 数 组 和 散 列 符号 , 使 用 一 个 真 的 伪 散 列 。package PsychoHash;use overload '%{}' => \&as_hash;sub as_hash {my ($x) = shift;return { @$x };}340


sub new {my $class = shift;return bless [ @_ ] => $class;}$critter = new PsychoHash( height => 72, weight => 365, type => "camel" );print $critter->{weight}; # 打 印 365又 见 第 十 四 章 , 捆 绑 变 量 , 那 里 有 一 种 机 制 你 可 以 用 于 重 新 定 义 在 散 列 , 数 组 , 和 标 量 上的 操 作 。当 重 载 一 个 操 作 符 的 时 候 , 千 万 不 要 试 图 创 建 一 个 带 有 指 向 自 己 的 引 用 的 操 作 符 。 比 如 ,use overload '+' => sub { bless [ \$_[0], \$_[1] ] };这 么 干 是 自 找 苦 吃 , 因 为 如 果 你 说 $animal += $vegetable, 结 果 将 令 $animal 是 一个 引 用 一 个 赐 福 了 的 数 组 引 用 , 而 该 数 组 引 用 的 第 一 个 元 素 是 $animal。 这 是 循 环 引 用 ,意 味 着 即 使 你 删 除 了 $animal, 它 的 内 存 仍 然 不 会 释 放 , 直 到 你 的 进 程 ( 或 者 解 释 器 ) 终止 。 参 阅 第 八 章 , 引 用 , 里 面 的 “ 垃 圾 收 集 , 循 环 引 用 , 和 弱 引 用 ”。13.4 拷 贝 构 造 器 (=)尽 管 = 看 起 来 想 一 个 普 通 的 操 作 符 , 它 做 为 一 个 重 载 的 键 字 有 点 特 殊 的 含 义 。 它 并 不 重 载<strong>Perl</strong> 的 赋 值 操 作 符 。 它 不 能 那 样 重 载 , 因 为 赋 值 操 作 符 必 须 保 留 起 来 用 于 赋 引 用 的 值 , 否则 所 有 的 东 西 就 都 完 了 。= 句 柄 在 下 面 的 情 况 下 使 用 : 一 个 修 改 器 ( 比 如 ++,--, 或 者 任 何 赋 值 操 作 符 ) 施 用 于一 个 引 用 , 而 该 引 用 与 其 他 引 用 共 享 其 对 象 。= 句 柄 令 你 截 获 这 些 修 改 器 并 且 让 你 自 己 拷贝 该 对 象 , 这 样 拷 贝 本 身 也 经 过 修 改 了 。 否 则 , 你 会 把 原 来 的 对 象 给 改 了 (*)。$copy = $original;++$copy;# 只 拷 贝 引 用# 修 改 下 边 的 共 享 对 象341


现 在 , 从 这 开 始 。 假 设 $original 是 一 个 对 象 的 引 用 。 要 让 ++$copy 只 修 改 $copy 而不 是 $original, 先 复 制 一 份 $copy 的 拷 贝 , 然 后 把 $copy 赋 给 一 个 指 向 这 个 新 对 象 的引 用 。 直 到 ++$copy 执 行 之 后 才 执 行 这 个 动 作 , 所 以 在 自 增 之 前 $copy 和 $original是 一 致 的 —— 但 是 自 增 后 就 不 一 样 了 。 换 句 话 说 , 是 ++ 识 别 出 拷 贝 的 需 要 , 并 且 调 用 你的 拷 贝 构 造 器 。只 有 象 ++ 或 +=, 或 者 nomethod 这 样 的 修 改 器 才 能 知 晓 是 否 需 要 拷 贝 , 我 们 稍 后 描述 nomethod。 如 果 此 操 作 是 通 过 + 自 动 生 成 的 , 象 :$copy = $original;$copy = $copy +1;这 样 , 那 么 不 会 发 生 拷 贝 , 因 为 + 不 知 道 它 正 被 当 作 修 改 器 使 用 。如 果 在 某 些 修 改 器 的 运 行 中 需 要 拷 贝 构 造 器 , 但 是 没 有 给 = 声 明 句 柄 , 那 么 只 要 该 对 象 是一 个 纯 标 量 而 不 是 什 么 更 神 奇 的 东 西 , 就 可 以 自 动 生 成 = 的 句 柄 。比 如 , 下 面 的 实 际 代 码 序 列 :$copy = $original;...++$copy;可 能 最 终 会 变 成 象 下 面 这 样 的 东 西 ;$copy = $original;...$copy = $copy->clone(undef, "");$copy->incr(undef, "");这 里 假 设 $original 指 向 一 个 重 载 的 对 象 ,++ 是 用 \&incr 重 载 的 , 而 = 是 用\&clone 重 载 的 。类 似 的 行 为 也 会 在 $copy = $original++ 里 触 发 , 它 解 释 成 $copy = $original;++$original.13.5 当 缺 失 重 载 句 柄 的 时 候 (nomethod 和 fallback)342


如 果 你 在 一 个 对 象 上 使 用 一 个 未 重 载 的 操 作 符 ,<strong>Perl</strong> 首 先 用 我 们 早 先 描 述 过 的 规 则 , 尝 试从 其 他 重 载 的 操 作 符 里 自 动 生 成 一 个 行 为 。 如 果 这 样 失 败 了 , 那 么 <strong>Perl</strong> 找 一 个 重 载nomethod 得 到 的 行 为 , 如 果 可 行 , 则 用 之 。 这 个 句 柄 之 于 操 作 符 就 好 象 AUTOLOAD 子过 程 之 于 子 过 程 : 它 是 你 走 投 无 路 的 时 候 干 的 事 情 。如 果 用 了 nomethod, 那 么 nomethod 键 字 应 该 后 面 跟 着 一 个 引 用 , 该 引 用 指 向 一 个 接受 四 个 参 数 的 句 柄 。 前 面 三 个 参 数 和 任 何 其 他 句 柄 里 的 没 有 区 别 ; 第 四 个 是 一 个 字 串 , 对 应缺 失 句 柄 的 那 个 操 作 符 。 它 起 的 作 用 和 AUTOLOAD 子 过 程 里 的 $AUTOLOAD 变 量 一样 。如 果 <strong>Perl</strong> 不 得 不 找 一 个 nomethod 句 柄 , 但 是 却 找 不 到 , 则 抛 出 一 个 例 外 。如 果 你 想 避 免 发 生 自 动 生 成 , 或 者 你 想 要 一 个 失 效 的 自 动 生 成 , 这 样 你 就 可 以 得 到 一 个 完 全没 有 重 载 的 结 果 , 那 么 你 可 以 定 义 特 殊 的 fallback 重 载 键 字 。 它 有 三 个 有 用 的 状 态 :• undef :如 果 没 有 设 置 fallback , 或 者 你 把 它 明 确 地 设 为 undef, 那 么 不 会 影 响 重 载 事 件 序列 : 先 找 一 个 句 柄 , 然 后 尝 试 自 动 生 成 , 最 后 调 用 nomethod。 如 果 都 失 败 则 抛 出 例 外 。• false:如 果 把 fallback 设 置 为 一 个 预 定 义 的 , 但 是 为 假 的 值 ( 比 如 0), 那 么 永 远 不 会 进 行自 动 生 成 。 如 果 存 在 nomethod 句 柄 , 那 么 <strong>Perl</strong> 将 调 用 之 , 否 则 则 抛 出 例 外 。• true:和 undef 有 几 乎 一 样 的 性 质 , 但 是 如 果 不 能 通 过 自 动 生 成 合 成 一 个 合 适 的 句 柄 , 那么 也 不 会 抛 出 例 外 。 相 反 ,<strong>Perl</strong> 回 复 到 该 操 作 符 没 有 重 载 的 性 质 , 就 好 象 根 本 没 有 在 那 个类 里 面 使 用 use overload 一 样 。13.6 重 载 常 量你 可 以 用 overload::constant 改 变 <strong>Perl</strong> 解 释 常 量 的 方 法 , 这 个 用 法 放 在 一 个 包 的import 方 法 里 很 有 用 。( 如 果 这 么 做 了 , 你 应 该 在 该 包 的 unimport 方 法 里 正 确 地 调 用overload::remove_constant, 这 样 当 你 需 要 的 时 候 , 包 可 以 自 动 清 理 自 身 。)overload::constant 和 overload::remove_constant 都 需 要 一 个 键 字 / 数 值 对 的 列表 。 这 些 键 字 应 该 是 integer,float,binary,q 和 qr 中 的 任 何 东 西 , 而 且 每 个 数 值 都应 该 是 一 个 子 过 程 的 名 字 , 一 个 匿 名 子 过 程 , 或 者 一 个 将 操 作 这 些 常 量 的 代 码 引 用 。343


sub import { orverload::constant ( integer => \&integer_handler,float => \&float_handler,binary => \&base_handler,q => \&string_handler,qr => \&regex_handler)}当 <strong>Perl</strong> 记 号 查 找 器 碰 到 一 个 常 量 数 字 的 时 候 , 就 会 调 用 你 提 供 的 任 何 用 于 integer 和float 的 句 柄 。 这 个 功 能 是 和 use constant 用 法 独 立 的 ; 象 下 面 这 样 的 简 单 语 句 :$year = cube(12) + 1;$pi = 3.14159265358979;# 整 数# 浮 点会 触 发 你 要 求 的 任 何 句 柄 。binary 键 字 令 你 截 获 二 进 制 , 八 进 制 和 十 六 进 制 常 量 。q 处 理 单 引 号 字 串 ( 包 括 用 q 引入 的 字 串 ) 和 qq- 里 面 的 子 字 串 , 还 有 qx 引 起 的 字 串 和 “ 此 处 ” 文 档 。 最 后 ,qr 处 理 在正 则 表 达 式 里 的 常 量 片 段 , 就 好 象 在 第 五 章 , 模 式 匹 配 , 结 尾 处 描 述 的 那 样 。<strong>Perl</strong> 会 给 句 柄 传 递 三 个 参 数 。 第 一 个 参 数 是 原 来 的 常 量 , 形 式 是 它 原 来 提 供 给 <strong>Perl</strong> 的 形式 。 第 二 个 参 数 是 <strong>Perl</strong> 实 际 上 如 何 解 释 该 常 量 ; 比 如 ,123_456 将 显 示 为 123456。第 三 个 参 数 只 为 那 些 由 q 和 qr 处 理 字 串 的 句 柄 定 义 , 而 且 是 qq,q,s, 或 者 tr 之 一 ,具 体 是 哪 个 取 决 于 字 串 的 使 用 方 式 。qq 意 味 着 该 字 串 是 从 一 个 代 换 过 的 环 境 来 的 , 比 如 双引 号 , 反 斜 杠 ,m// 匹 配 或 者 一 个 s/// 模 式 的 替 换 。q 意 味 着 该 字 串 来 自 一 个 未 代 换 的 s意 味 着 此 常 量 是 一 个 在 s/// 替 换 中 来 的 替 换 字 串 , 而 tr 意 味 着 它 是 一 个 tr/// 或 者y/// 表 达 式 的 元 件 。该 句 柄 应 该 返 回 一 个 标 量 , 该 标 量 将 用 于 该 常 量 所 处 的 位 置 。 通 常 , 该 标 量 会 是 一 个 重 载 了的 对 象 的 引 用 , 不 过 没 什 么 东 西 可 以 让 你 做 些 更 卑 鄙 的 事 情 :package DigitDoubler;# 一 个 将 放 在 DigitDoubler.pm 里 的 模 块use overload;sub import { overload::constant ( integer => \&handler,float => \&handler ) }344


sub handler {my ($orig, $intergp, $context) = @_;return $interp * 2;# 所 有 常 量 翻 番}1;请 注 意 该 handler ( 句 柄 ) 由 两 个 键 字 共 享 , 在 这 个 例 子 里 运 行 得 不 错 。 现 在 当 你 说 :use DigitDoubler;$trouble = 123; # trouble 现 在 是 246$jeopardy = 3.21; # jeopardy 现 在 是 6.42你 实 际 上 把 所 有 东 西 都 改 变 了 。如 果 你 截 获 字 串 常 量 , 我 们 建 议 你 同 时 提 供 一 个 连 接 操 作 符 ("."), 因 为 所 有 代 换 过 的 表达 式 , 象 "ab$cd!!", 仅 仅 只 是 一 个 更 长 的 'ab'.$cd.'!!' 的 缩 写 。 类 似 的 , 负 数 被 类 似 的 ,负 数 被 认 为 是 正 数 常 量 的 负 值 , 因 此 , 如 果 你 截 获 整 数 或 者 浮 点 数 , 你 应 该 给 neg 提 供 一个 句 柄 。( 我 们 不 需 要 更 早 做 这 些 , 因 为 我 们 是 返 回 真 实 数 字 , 而 不 是 重 载 的 对 象 引 用 。)请 注 意 overload::constant 并 不 传 播 进 eval 的 运 行 时 编 译 , 这 一 点 可 以 说 是 臭 虫 也 可以 说 是 特 性 —— 看 你 怎 么 看 了 。13.7 公 共 重 载 函 数对 <strong>Perl</strong> 版 本 5.6 而 言 ,use overload 用 法 为 公 共 使 用 提 供 了 下 面 的 函 数 :1. overload::StrVal(OBJ):这 个 函 数 返 回 缺 少 字 串 化 重 载 ("") 时 OBJ 本 应 该 有 的 字 串 值 。2. overload::Overloaded(OBJ):如 果 OBJ 经 受 了 任 何 操 作 符 重 载 , 此 函 数 返 回 真 , 否 则 返 回 假 。345


3. overload::Method(OBJ,OPERATOR):这 个 函 数 返 回 OPERATOR 操 作 OBJ 的 时 候 重 载 OPERATOR 的 代 码 , 或 者 在 不存 在 重 载 时 返 回 undef。13.8 继 承 和 重 载继 承 和 重 载 以 两 种 方 式 相 互 交 互 。 第 一 种 发 生 在 当 一 个 句 柄 命 名 为 一 个 字 串 而 不 是 以 一 个 代码 引 用 或 者 匿 名 子 过 程 的 方 式 提 供 的 时 候 。 如 果 命 名 为 字 串 , 那 么 该 句 柄 被 解 释 成 为 一 种 方法 , 并 且 因 此 可 以 从 父 类 中 继 承 。第 二 种 重 载 和 继 承 的 交 互 是 任 何 从 一 个 重 载 的 类 中 衍 生 出 来 的 类 本 身 也 经 受 该 重 载 。 换 句 话说 , 重 载 本 身 是 继 承 的 。 一 个 类 里 面 的 句 柄 集 是 该 类 的 所 有 父 类 的 句 柄 的 联 合 ( 递 归 地 )。如 果 在 几 个 不 同 的 祖 先 里 都 有 某 个 句 柄 , 该 句 柄 的 使 用 是 由 方 法 继 承 的 通 用 规 则 控 制 的 。 比如 , 如 果 类 Alpha 按 照 类 Beta, 类 Gamma 的 顺 序 继 承 , 并 且 类 Beta 用\&Beta::plus_sub 重 载 +, 而 Gamma 用 字 串 "plus_meth" 重 载 +, 那 么 如 果 你对 一 个 Alpha 对 象 用 + 的 时 候 , 将 调 用 Beta::plus。因 为 键 字 fallback 的 值 不 是 一 个 句 柄 , 它 的 继 承 不 是 由 上 面 给 出 的 规 则 控 制 的 。 在 当 前 的实 现 里 , 使 用 来 自 第 一 个 重 载 祖 先 的 fallback 值 , 不 过 这 么 做 是 无 意 的 , 并 且 我 们 可 能 会在 不 加 说 明 的 情 况 下 改 变 ( 当 然 , 是 不 加 太 多 说 明 )。13.9 运 行 时 重 载因 为 use 语 句 是 在 编 译 时 重 载 的 , 在 运 行 时 改 变 重 载 的 唯 一 方 法 是 :eval "use overload '+' =>\&my_add";你 还 可 以 说 :eval "no voerload '+', '--', '


如 果 你 现 在 觉 得 “ 重 载 ” 了 , 下 一 章 可 能 会 把 这 些 东 西 给 你 约 束 回 来 。第 十 三 章 捆 绑 (tie) 变 量 上有 些 人 类 的 工 作 需 要 伪 装 起 来 。 有 时 候 伪 装 的 目 的 是 欺 骗 , 但 更 多 的 时 候 , 伪 装 的 目 的是 为 了 在 更 深 层 次 做 一 些 真 实 的 通 讯 。 比 如 , 许 多 面 试 官 希 望 你 能 穿 西 服 打 领 带 以 表 示 你 对工 作 是 认 真 的 , 即 使 你 们 俩 都 知 道 你 可 能 在 工 作 的 时 候 永 远 不 会 打 领 带 。 你 思 考 这 件 事 的 时候 可 能 会 觉 得 很 奇 怪 : 在 你 脖 子 上 系 一 块 布 会 神 奇 地 帮 你 找 到 工 作 。 在 <strong>Perl</strong> 文 化 里 , tie操 作 符 起 到 类 似 的 作 用 的 角 色 : 它 让 你 创 建 一 个 看 起 来 象 普 通 变 量 的 变 量 , 但 是 在 变 量 的 伪装 后 面 , 它 实 际 上 是 一 个 羽 翼 丰 满 的 <strong>Perl</strong> 对 象 , 而 且 此 对 象 有 着 自 己 有 趣 的 个 性 。 它 只 是一 个 让 人 有 点 奇 怪 的 小 魔 术 , 就 好 象 从 一 个 帽 子 里 弹 出 一 个 邦 尼 兔 那 样 。( 译 注 : 英 文 tie做 动 词 有 " 捆 绑 " 之 意 , 而 做 名 词 有 " 领 带 " 之 意 .) 用 另 外 一 个 方 法 来 看 , 在 变 量 名 前 面 的 趣味 字 符 $,@,%, 或 者 * 告 诉 <strong>Perl</strong> 和 它 的 程 序 许 多 事 情 —— 他 们 每 个 都 暗 示 了 一 个 特殊 范 畴 的 原 形 特 性 。 你 可 以 利 用 tie 用 各 种 方 法 封 装 那 些 特 性 , 方 法 是 用 一 个 实 现 一 套 新性 质 的 类 与 该 变 量 关 联 起 来 。 比 如 , 你 可 以 创 建 一 个 普 通 的 <strong>Perl</strong> 散 列 , 然 后 把 它 tie ( 绑 )到 到 一 个 类 上 , 这 个 类 把 这 个 散 列 放 到 一 个 数 据 库 里 , 所 以 当 你 从 散 列 读 取 数 据 的 时 候 ,<strong>Perl</strong> 魔 术 般 地 从 一 个 外 部 数 据 库 文 件 抓 取 数 据 , 而 当 你 设 置 散 列 中 的 数 值 的 时 候 ,<strong>Perl</strong> 又神 奇 地 把 数 据 存 储 到 外 部 数 据 库 文 件 里 。 这 里 的 " 魔 术 "," 神 奇 " 指 的 是 “ 透 明 地 处 理 一 些 非常 复 杂 的 任 务 ”。 你 应 该 听 过 那 些 老 话 : 再 先 进 的 技 术 也 和 <strong>Perl</strong> 脚 本 没 什 么 区 别 。( 严 肃地 说 , 那 些 在 <strong>Perl</strong> 内 部 工 作 的 人 们 把 魔 术 (magic) 一 词 当 作 一 个 技 术 术 语 , 特 指 任 何 附加 在 变 量 上 的 额 外 的 语 义 , 比 如 %ENV 或 者 %SIG。 捆 绑 变 量 只 是 其 中 一 种 扩 展 。)<strong>Perl</strong> 已 经 有 内 建 的 dbmopen 和 dbmclose 函 数 , 它 们 可 以 完 成 把 散 列 变 量 和 数 据 库 系在 一 起 的 魔 术 , 不 过 那 些 函 数 的 实 现 是 早 在 <strong>Perl</strong> 没 有 tie 的 时 候 。 现 在 tie 提 供 了 更 通用 的 机 制 。 实 际 上 , <strong>Perl</strong> 本 身 就 是 以 tie 的 机 制 来 实 现 dbmopen 和 dbmclose 的 。你 可 以 把 一 个 标 量 , 数 组 , 散 列 或 者 文 件 句 柄 ( 通 过 它 的 类 型 团 ) 系 到 任 意 一 个 类 上 , 这 个类 提 供 合 适 的 命 名 方 法 以 截 获 和 模 拟 对 这 些 对 象 的 正 常 访 问 。 那 些 方 法 的 第 一 个 是 在 进 行tie 动 作 本 身 时 调 用 的 : 使 用 一 个 变 量 总 是 调 用 一 个 构 造 器 , 如 果 这 个 构 造 器 成 功 运 行 , 则返 回 一 个 对 象 , 而 <strong>Perl</strong> 把 这 个 对 象 藏 在 一 个 你 看 不 见 的 地 方 —— 在 “ 普 通 ” 变 量 的 深 层 内部 。 你 总 是 可 以 稍 后 用 tied 函 数 在 该 普 通 变 量 上 检 索 该 对 象 :tie VARIABLE, CLASSNAME, LIST;# 把 VARIABLE 绑 定 到 CLASSNAME347


$object = tied VARIABLE;上 面 两 行 等 效 于 :$object = tie VARIABLE, CLASSNAME, LIST;一 旦 该 变 量 被 捆 绑 , 你 就 可 以 按 照 平 时 那 样 对 待 该 普 通 变 量 , 不 过 每 次 访 问 都 自 动 调 用 下 层对 象 的 方 法 ; 所 有 该 类 的 复 杂 性 都 隐 藏 在 那 些 方 法 调 用 的 背 后 。 如 果 稍 后 你 想 打 破 变 量 和 类之 间 的 关 联 , 你 可 以 untie ( 松 绑 ) 那 个 变 量 :untie VARIABLE;你 几 乎 完 全 可 以 把 tie 看 作 一 种 有 趣 的 bless 类 型 , 只 不 过 它 是 给 一 个 光 秃 秃 的 变 量 赐福 , 而 不 是 给 一 个 对 象 引 用 赐 福 。 它 同 样 还 可 以 接 收 额 外 的 参 数 , 就 象 构 造 器 那 样 —— 这 个恐 怕 也 不 新 鲜 了 , 因 为 它 实 际 上 就 是 在 内 部 调 用 一 个 构 造 器 , 该 构 造 器 的 名 字 取 决 于 你 尝 试的 变 量 类 型 : 是 TIESCALAR,TIEARRAY,TIEHASH, 或 者 TIEHANDLE。( 注 : 因 为这 些 构 造 器 是 独 立 的 名 字 , 你 甚 至 可 以 提 供 一 个 独 立 的 类 来 实 现 它 们 。 那 样 , 你 就 可 以 把 标量 , 数 组 , 散 列 , 和 文 件 句 柄 统 统 绑 定 到 同 一 个 类 上 , 不 过 通 常 不 是 这 么 干 的 , 因 为 它 会 令其 他 的 魔 术 方 法 比 较 难 写 。) 调 用 这 些 构 造 器 的 时 候 , 它 们 用 所 声 明 的 CLASSNAME 为它 们 的 调 用 者 作 为 类 方 法 调 用 , 另 外 把 你 放 在 LIST 里 的 任 何 东 西 作 为 附 加 的 参 数 。(VARIABLE 并 不 传 递 给 构 造 器 。)这 四 种 构 造 器 每 种 都 返 回 一 个 普 通 风 格 的 对 象 。 它 们 并 不 在 乎 它 们 是 否 从 tie 里 调 用 的 ,类 里 的 其 他 方 法 也 不 在 意 , 因 为 如 果 你 喜 欢 的 话 你 总 是 可 以 直 接 调 用 它 们 。 从 某 种 意 义 来 说 ,所 有 魔 术 都 是 在 tie 里 , 而 不 是 在 实 现 tie 的 类 里 。 该 类 只 是 一 个 有 着 有 趣 的 方 法 名 的 普通 类 。( 实 际 上 , 有 些 捆 绑 的 模 块 提 供 了 额 外 的 一 些 方 法 , 这 些 方 法 是 不 能 通 过 捆 绑 的 变 量看 到 的 ; 你 必 须 明 确 调 用 这 些 方 法 , 就 象 你 对 待 其 他 对 象 方 法 一 样 。 这 样 的 额 外 方 法 可 以 提供 类 似 文 件 锁 , 事 务 保 护 , 或 者 任 何 其 他 实 例 方 法 可 以 做 的 东 西 。)因 此 这 些 构 造 器 就 象 其 他 构 造 器 那 样 bless( 赐 福 ) 并 且 返 回 一 个 对 象 引 用 。 该 引 用 不 需 要指 向 和 被 捆 绑 的 变 量 相 同 类 型 的 变 量 ; 它 只 是 必 须 被 赐 福 , 所 以 该 绑 定 的 变 量 可 以 很 容 易 在你 的 类 中 找 到 支 持 。 比 如 , 我 们 的 长 例 子 TIEARRAY 就 会 用 一 个 基 于 散 列 的 对 象 , 这 样 它就 可 以 比 较 容 易 地 保 存 它 在 模 拟 的 数 组 的 附 加 信 息 。tie 函 数 不 会 为 你 use 或 者 require 一 个 模 块 —— 如 果 必 要 的 话 , 在 调 用 tie 前 你 必 须 自己 明 确 地 做 那 件 事 。( 另 外 , 为 了 保 持 向 下 兼 容 ,dbmopen 函 数 会 尝 试 use 一 个 或 者 某个 DBM 实 现 。 但 你 可 以 用 一 个 明 确 的 use 修 改 它 的 选 择 优 先 级 —— 只 要 你 use 的 模 块是 在 dbmopen 的 模 块 列 表 中 的 一 个 。 参 阅 AnyDBM ? _File 模 块 的 在 线 文 档 获 取 更 完 善的 解 释 。)348


一 个 捆 绑 了 的 变 量 调 用 的 方 法 有 一 个 类 似 FETCH 和 STORE 这 样 的 预 定 义 好 了 的 名 字 ,因 为 它 们 是 在 <strong>Perl</strong> 内 部 隐 含 调 用 的 ( 也 就 是 说 , 由 特 定 事 件 触 发 )。 这 些 名 字 都 在ALLCAPS, 内 部 隐 含 调 用 的 ( 也 就 是 说 , 有 特 定 事 件 触 发 )。 这 些 名 字 都 是 全 部 大 写 , 这是 我 们 遵 循 的 一 个 称 呼 这 些 隐 含 调 用 过 程 的 习 惯 。( 其 他 遵 循 这 种 传 统 的 习 惯 有 BEGIN,CHECK,INIT, END,DESTROY, 和 AUTOLOAD, 更 不 用 说UNIVERSAL->VERSION。 实 际 上 , 几 乎 所 有 <strong>Perl</strong> <strong>Perl</strong> 预 定 义 的 变 量 和 文 件 句 柄 都 是 大写 的 :STDIN,SUUPER,CORE,CORE::GLOBAL,DATA, @EXPORT,@INC,@ISA,@ARGV 和 %ENV。 当 然 , 内 建 操 作 符 和 用 法 是 另 外 一 个 极 端 , 它 们 没 有 用 大 写的 。)我 们 首 先 要 介 绍 的 内 容 很 简 单 : 如 何 捆 绑 一 个 标 量 变 量 。14.1 捆 绑 标 量要 实 现 一 个 捆 绑 的 标 量 , 一 个 类 必 须 定 义 下 面 的 方 法 :TIESCALAR,FETCH, 和 STORE( 以 及 可 能 还 有 DESTROY)。 当 你 tie 一 个 标 量 变 量 的 时 候 ,<strong>Perl</strong> 调 用 TIESCALAR。如 果 你 读 取 这 个 捆 绑 的 变 量 , 它 调 用 FETCH, 并 且 当 你 给 一 个 变 量 赋 值 的 时 候 , 它 调 用STORE。 如 果 你 保 存 了 最 初 的 tie ( 或 者 你 稍 后 用 tied 检 索 它 们 ), 你 就 可 以 自 己 访 问下 层 的 对 象 —— 这 样 并 不 触 发 它 的 FETCH 或 者 STORE 方 法 。 作 为 一 个 对 象 , 这 一 点毫 不 神 奇 , 而 是 相 当 客 观 的 。如 果 存 在 一 个 DESTROY 方 法 , 那 么 当 指 向 被 捆 绑 对 象 的 最 后 一 个 引 用 消 失 时 ,<strong>Perl</strong> 就会 调 用 这 个 DESTROY 方 法 , 就 好 象 对 其 他 对 象 那 样 。 当 你 的 程 序 结 束 或 者 你 调 用 untie的 时 候 就 会 发 生 这 些 事 情 , 这 样 就 删 除 了 捆 绑 使 用 的 引 用 。 不 过 ,untie 并 不 删 除 任 何 你 放在 其 他 地 方 的 引 用 ;DESTROY 也 会 推 迟 到 那 些 引 用 都 删 除 以 后 。在 标 准 的 Tie::Scalar 模 块 里 有 Tie::Scalar 和 Tie::StdScalar 包 , 如 果 你 不 想 自 己定 义 所 有 这 些 方 法 , 那 么 它 们 定 义 了 一 些 简 单 的 基 类 。Tie::Scalar 提 供 了 只 能 做 很 有 限 的工 作 的 基 本 方 法 , 而 Tie::StdScalar 提 供 了 一 些 方 法 令 一 个 捆 绑 了 的 标 量 表 现 得 象 一 个普 通 的 <strong>Perl</strong> 标 量 。( 好 象 没 什 么 用 , 不 过 有 时 候 你 只 是 想 简 单 地 给 普 通 标 量 语 义 上 加 一 些封 装 , 比 如 , 计 算 某 个 变 量 设 置 的 次 数 。)在 我 们 给 你 显 示 我 们 精 心 设 计 的 例 子 和 对 所 有 机 制 进 行 完 整 地 描 述 之 前 , 我 们 先 给 你 来 点 开胃 的 东 西 —— 并 且 给 你 显 示 一 下 这 些 东 西 是 多 简 单 。 下 面 是 一 个 完 整 的 程 序 :#! /usr/bin/perlpackage Centsible;sub TIESCALAR { bless \my $self, shift }349


sub STORE { ${ $_[0] } = $_[1] }# 做 缺 省 的 事 情sub FETCH { sprintf "%0.2f", ${ my $self = shift } }# 圆 整 值package main;tie $bucks, "Centsible";$bucks = 45.00;$bucks *= 1.0715;# 税$bucks *= 1.0715; # 和 双 倍 的 税 !print "That will be $bucks, please.\n";运 行 的 时 候 , 这 个 程 序 生 成 :That will be 51.67, please.把 tie 调 用 注 释 掉 以 后 , 你 可 以 看 到 区 别 :That will be 51.66505125, please.当 然 , 这 样 要 比 你 平 时 做 圆 整 所 做 的 工 作 要 多 。14.1.1 标 量 捆 绑 方 法既 然 你 已 经 看 到 我 们 将 要 讲 的 东 西 , 那 就 让 我 们 开 发 一 个 更 灵 活 的 标 量 捆 绑 类 吧 。 我 们 不 会使 用 任 何 封 装 好 了 的 包 做 基 类 ( 特 别 是 因 为 标 量 实 在 是 简 单 ), 相 反 我 们 会 轮 流 照 看 一 下 这四 种 方 法 , 构 造 一 个 名 字 为 ScalarFile ? 的 例 子 类 。 捆 绑 到 这 个 类 上 的 标 量 包 含 普 通 字 串 ,并 且 每 个 这 样 的 变 量 都 隐 含 地 和 一 个 文 件 关 联 , 此 文 件 就 是 字 串 存 贮 的 地 方 。( 你 可 以 通 过给 变 量 命 名 的 方 法 来 记 忆 你 引 用 的 是 哪 个 文 件 。) 变 量 用 下 面 的 方 法 绑 到 类 上 :use ScalarFile;# 装 载 ScalarFile.pmtie $camel, "ScalarFiel", "/tmp/camel.lot";变 量 一 旦 捆 绑 , 它 以 前 的 内 容 就 被 取 代 , 并 且 变 量 和 其 对 象 内 部 的 联 系 覆 盖 了 此 变 量 平 常 的语 义 。 当 你 请 求 $camel 的 值 时 , 它 现 在 读 取 /tmp/camel.lot 的 内 容 , 而 当 你 给$camel 赋 值 的 时 候 , 它 把 新 的 内 容 写 到 /tmp/camel.lot 里 , 删 除 任 何 原 来 的 东 西 。350


捆 绑 是 对 变 量 进 行 的 , 而 不 是 数 值 , 因 此 一 个 变 量 的 捆 绑 属 性 不 会 随 着 赋 值 一 起 传 递 。 比 如 ,假 设 你 拷 贝 一 个 已 经 捆 绑 了 的 变 量 :$dromedary = $camel;<strong>Perl</strong> 不 是 象 平 常 那 样 从 $camel 标 量 里 读 取 变 量 , 而 是 在 相 关 的 下 层 对 象 上 调 用 FETCH方 法 。 就 好 象 你 写 的 是 这 样 的 东 西 :$dromedary = (tied $camel)->FETCH();或 者 如 果 你 还 记 得 tie 返 回 的 对 象 , 你 可 以 直 接 使 用 那 个 引 用 , 就 象 在 下 面 的 例 子 代 码 里一 样 :$clot = tie $camel, "ScalarFile", "/tmp/camle.lot";$dromedary = $camle;# 通 过 隐 含 的 接 口$dromedary = $clot->FETCH();# 一 样 的 东 西 , 不 过 是 明 确 的 方 法 而 已如 果 除 了 TIESCALAR,FETCH,STORE, 和 DESTROY 以 外 , 该 类 还 提 供 其 他 方 法 ,你 也 可 以 使 用 $clot 手 工 调 用 它 们 。 不 过 , 大 家 应 该 做 好 自 己 的 事 情 而 不 要 去 管 下 层 对 象 ,这 也 是 为 什 么 你 看 到 来 自 tie 的 返 回 值 常 被 忽 略 。 如 果 稍 后 你 又 需 要 该 对 象 ( 比 如 , 如 果该 类 碰 巧 记 载 了 任 何 你 需 要 的 额 外 方 法 的 文 档 ), 那 么 你 仍 然 可 以 通 过 tie 获 取 该 对 象 。忽 略 所 返 回 的 对 象 同 样 也 消 除 了 某 些 类 型 的 错 误 , 这 一 点 我 们 稍 后 介 绍 。下 面 是 我 们 的 类 所 需 要 的 东 西 , 我 们 将 把 它 们 放 到 ScalarFile ? .pm:package ScalarFile;use Carp; # 很 好 地 传 播 错 误 消 息 。use strict; # 给 我 们 自 己 制 定 一 些 纪 律 。use warnings; # 打 开 词 法 范 围 警 告 。use warnings::register;# 允 许 拥 护 说 "use warnings 'ScalarFile'"。my $count = 0; # 捆 绑 了 的 ScalarFile ?的 内 部 计 数 。这 个 标 准 的 Carp 模 块 输 出 carp,croak, 和 confess 子 过 程 , 我 们 将 在 本 节 稍 后 的 代码 中 使 用 它 们 。 和 往 常 一 样 , 参 阅 第 32 章 , 标 准 模 块 , 或 者 在 线 文 档 获 取 Carp 的 更 多介 绍 。下 面 的 方 法 是 该 类 定 义 的 。351


CLASSNAME->TIESCALAR(LIST)每 当 你 tie 一 个 标 量 变 量 , 都 会 触 发 该 类 的 TIESCALAR 方 法 。 可 选 的 LIST 包 含 任 意正 确 初 始 化 该 对 象 所 需 要 的 参 数 。( 在 我 们 的 例 子 里 , 只 有 一 个 参 数 : 该 文 件 的 名 字 。)这 个 方 法 应 该 返 回 一 个 对 象 , 不 过 这 个 对 象 不 必 是 一 个 标 量 的 引 用 。 不 过 在 我 们 的 例 子 里是 标 量 的 引 用 。sub TIESCALAR {# 在 ScalarFile.pmmy $class = shift;my $filename = shift;$count++;# 一 个 文 件 范 围 的 词 法 , 是 类 的 私 有 部 分return bless \$filename, $class;}因 为 匿 名 数 组 和 散 列 构 造 器 ([] 和 {}) 没 有 标 量 等 价 物 , 我 们 只 是 赐 福 一 个 词 法 范 围 的 引用 物 , 这 样 , 只 要 这 个 名 字 超 出 范 围 , 它 就 变 成 一 个 匿 名 。 这 样 做 运 转 得 很 好 ( 你 可 以 对数 组 和 散 列 干 一 样 的 事 情 )—— 只 要 这 个 变 量 真 的 是 词 法 范 围 。 如 果 你 在 一 个 全 局 量 上 使用 这 个 技 巧 , 你 可 能 会 以 为 你 成 功 处 理 了 这 个 全 局 量 , 但 直 到 你 创 建 另 外 一 个 camel.lot的 时 候 才 能 意 识 到 这 是 错 的 。 不 要 试 图 写 下 面 这 样 的 东 西 :sub TIESCALAR {bless \$_[1], $_[0] } # 错 , 可 以 引 用 全 局 量 。一 个 写 得 更 强 壮 一 些 的 构 造 器 可 能 会 检 查 该 文 件 名 是 否 可 联 合 。 我 们 首 先 检 查 , 看 看 这 个文 件 是 否 可 读 , 因 为 我 们 不 想 毁 坏 现 有 的 数 值 。( 换 句 话 说 , 我 们 不 应 该 假 设 用 户 准 备 先写 。 他 们 可 能 很 珍 惜 此 程 序 以 前 运 行 留 下 来 的 旧 的 Camel Lot 文 件 。) 如 果 我 们 不 能 打 开或 者 创 建 所 声 明 的 文 件 名 , 我 们 将 通 过 返 回 一 个 undef 礼 貌 地 指 明 该 错 误 , 并 且 还 可 以 通过 carp 打 印 一 个 警 告 。( 我 们 还 可 以 只 用 croak —— 这 是 口 味 的 问 题 , 取 决 于 你 喜 欢鱼 还 是 牛 蛙 。) 我 们 将 使 用 warnings 用 法 来 判 断 这 个 用 户 是 否 对 我 们 的 警 告 感 兴 趣 :sub TIESCALAR {# 在 ScalarFile.pmmy $class = shift;my $filename = shift;my $fh;if (open $fh, "


open $fh, ">", $filename){close $fh;$count++;return bless \$filename, $class;}carp "Can't tie $filename: $!" if warnings::enabled();return;}有 了 这 样 的 构 造 器 , 我 们 现 在 就 可 以 把 标 量 $string 和 文 件 camel.lot 关 联 在 一 起 了 :tie ($string, "ScalarFile", "camel.lot") or die;( 我 们 仍 然 做 了 一 些 不 应 该 做 的 假 设 。 在 一 个 正 式 版 本 里 , 我 们 可 能 打 开 该 文 件 句 柄 一 次并 且 在 捆 绑 的 过 程 中 记 住 该 文 件 句 柄 和 文 件 名 , 保 证 此 句 柄 在 所 有 时 间 里 都 是 用 flock 排他 锁 住 的 。 否 则 我 们 就 会 面 对 冲 突 条 件 —— 参 阅 第 二 十 三 章 , 安 全 , 里 的 “ 处 理 计 时 缝 隙 ”。)• SELF->FETCH当 你 访 问 这 个 捆 绑 变 量 的 时 候 就 会 调 用 这 个 方 法 ( 也 就 是 说 , 读 取 其 值 ). 除 了 与 变 量 捆绑 的 对 象 以 外 , 它 没 有 其 他 的 参 数 . 在 我 们 的 例 子 里 , 那 个 对 象 包 含 文 件 名 .sub FETCH {my $self = shift;confess "I am not a class method" unless ref $self;return unless open my $fh, $$self;read($fh, my $value, -s $fh); # NB: 不 要 在 管 道 上 使 用 -sreturn $value;}353


这 回 我 们 决 定 : 如 果 FETCH 拿 到 了 不 是 引 用 的 东 西 , 那 么 就 摧 毁 ( 抛 出 一 个 例 外 ).( 它要 么 是 被 当 做 类 方 法 调 用 , 要 么 是 什 么 东 西 不 小 心 把 它 当 成 一 个 子 过 程 调 用 了 .) 我 们 没有 其 它 返 回 错 误 的 方 法 , 所 以 这 么 做 可 能 是 对 的 . 实 际 上 , 只 要 我 们 试 图 析 引 用 $self,<strong>Perl</strong> 都 会 立 刻 抛 出 一 个 例 外 ; 我 们 只 是 做 得 礼 貌 一 些 并 且 用 confess 把 完 整 的 堆 栈 追 踪输 出 到 用 户 的 屏 幕 上 .( 如 果 这 个 动 作 可 以 认 为 是 礼 貌 的 话 .)如 果 我 们 说 下 面 这 些 话 就 会 看 到 camel.lot 的 内 容 :tie($string, "ScalarFile", "camel.lot");print $string;• SELF->STORE(VALUE)当 设 置 ( 赋 值 ) 捆 绑 的 变 量 的 时 候 会 运 行 这 个 方 法 . 第 一 个 参 数 SELF 与 往 常 一 样 是 与 变量 关 联 的 对 象 ;VALUE 是 给 变 量 赋 的 值 .( 我 们 这 里 的 " 赋 值 " 的 含 义 比 较 宽 松 -- 任 何 修改 变 量 的 动 作 都 可 以 叫 做 STORE.)sub STORE {my($self, $value) = @_;ref $selfor confess "not a class method";open my $fh, ">", $$self or croak "can't clobber $$self:$!";syswrite($fh, $value) == length $valueor croak "can't write to $$self: $!";close $fhor croak "can't close $$self:$!";return $value;}在 给 它 " 赋 值 " 以 后 , 我 们 返 回 新 值 -- 因 为 这 也 是 赋 值 做 的 事 情 . 如 果 赋 值 失 败 , 我 们 把 错误 croak 出 来 . 可 能 的 原 因 是 我 们 没 有 写 该 关 联 文 件 的 权 限 , 或 者 磁 盘 满 , 或 者 磁 盘 控制 器 坏 了 . 有 时 候 是 你 控 制 这 些 局 势 , 有 时 候 是 局 势 控 制 你 .现 在 如 果 我 们 说 下 面 的 话 , 我 们 就 可 以 写 入 camel.lot 了 .tie($string, "ScalarFile", "camel.lot");354


$string = "Here is the first line of camel.lot\n";$string .= "And here is another line, automatically appended.\n";• SELF->DESTORY当 与 捆 绑 变 量 相 关 联 的 对 象 即 将 收 集 为 垃 圾 时 会 触 发 这 个 方 法 , 尤 其 是 在 做 一 些 特 殊 处 理以 清 理 自 身 的 情 况 下 。 和 其 他 类 一 样 , 这 样 的 方 法 很 少 是 必 须 的 , 因 为 <strong>Perl</strong> 自 动 为 你 清 除垂 死 的 对 象 的 内 存 。 在 这 里 , 我 们 会 定 义 一 个 DESTROY 方 法 用 来 递 减 我 们 的 捆 绑 文 件的 计 数 :sub DESTROY {my $self = shift;confess "wrong type" unless ref $self;$count--;}我 们 还 可 以 提 供 一 个 额 外 的 类 方 法 用 于 检 索 当 前 的 计 数 。 实 际 上 , 把 它 称 做 类 方 法 还 是 对象 方 法 并 不 要 紧 , 但 是 你 在 DESTROY 之 后 就 不 再 拥 有 一 个 对 象 了 , 对 吧 ?sub count {# my $invocant = shift;$count;}你 可 以 在 任 何 时 候 把 下 面 这 个 称 做 一 个 类 方 法 :if (ScalarFile->count) {warn "Still some tied ScalarFiles sitting around somewhere...\n";}这 就 是 所 要 的 东 西 。 实 际 上 , 比 所 要 的 东 西 还 要 多 , 因 为 我 们 在 这 里 为 完 整 性 , 坚 固 性 和普 遍 美 学 做 了 几 件 相 当 漂 亮 的 事 情 。 当 然 , 更 简 单 的 TIESCALAR 类 也 是 可 能 的 。14.1.2 魔 术 计 数 变 量355


这 里 是 一 个 简 单 的 Tie::Counter 类 , 灵 感 来 自 CPAN 中 同 名 的 模 块 。 捆 绑 到 这 个 类 上的 变 量 每 次 自 增 1。 比 如 :tie my $counter, "Tie::Counter", 100;@array = qw /Red Green Blue/;for my $color (@array) { # 打 印 :print " $counter $color\n";# 100 Red} # 101 Green# 102 Blue构 造 器 把 一 个 可 选 的 额 外 参 数 当 作 计 数 器 的 初 始 值 , 缺 省 时 为 零 。 给 这 个 计 数 器 赋 值 将 设 置一 个 新 值 。 下 面 是 类 :package Tie::Counter;sub FETCH { ++ ${ $_[0] }}sub STORE { ${ $_[0] } = $_[1] }sub TIESCALAR {my ($class, $value) = @_;$value = 0 unless defined $value;bless \$value => $class;}1; # 如 果 在 模 块 里 需 要 这 个多 小 ! 看 到 了 吗 ? 要 写 这 么 一 个 类 并 不 需 要 多 少 代 码 。14.1.3 神 奇 地 消 除 $_这 个 让 人 好 奇 的 外 部 的 捆 绑 类 用 于 防 止 非 局 部 的 $_ 的 使 用 。 它 不 是 用 use 把 方 法 拉 进来 , 而 是 调 用 该 类 的 import 方 法 , 装 载 这 个 模 块 应 该 用 no, 以 便 调 用 很 少 用 的unimport 方 法 。 用 户 说 :no Underscore;356


然 后 所 有 把 $_ 当 作 一 个 非 局 部 的 全 局 变 量 使 用 就 都 会 产 生 一 个 例 外 。下 面 是 一 个 用 这 个 模 块 的 小 测 试 程 序 :#!/usr/bin/perlno Underscore;@tests = ("Assignment" => sub { $_ = "Bad" },"Reading" => sub { print },"Matching" => sub { $x = /badness/ },"Chop" => sub { chop },"Filetest" => sub { -x },"Nesting" => sub { for (1..3) { print } },);while ( ($name, $code) = splice(@tests, 0, 2) ) {print "Testing $name: ";eval { &$code };print $@ ? "detected" : " missed!";print "\n";}这 个 程 序 打 印 出 下 面 的 东 西 :Testing Assignment: detectedTesting Reading: detectedTesting Matching: detectedTesting Chop: detected357


Testing Filetest: detectedTesting Nesting: 123 missed!“ 丢 失 ” 了 最 后 一 个 是 因 为 它 由 for 循 环 正 确 地 局 部 化 了 , 并 且 因 此 可 以 安 全 地 访 问 。下 面 是 让 人 感 兴 趣 的 外 部 Underscore 模 块 本 身 。( 我 们 说 过 它 是 让 人 感 兴 趣 的 外 部 吗 ?)它 能 运 转 是 因 为 捆 绑 的 神 奇 变 量 被 一 个 local 有 效 地 隐 藏 起 来 了 。 该 模 块 在 它 自 己 的 初 始化 代 码 里 做 了 一 个 tie , 所 以 require 也 能 运 转 。package Underscore;use Carp;sub TIESCALAR { bless \my $dummy => shift }sub FETCH { croak 'Read access to $_ forbidden' }sub STORE { croak 'Write access to $_ forbidden' }sub unimport { tie($_, __PACKAGE__) }sub import { untie $_ }tie($_, __PACKAGE__) unless tied $_;1;在 你 的 程 序 里 对 这 个 类 混 合 调 用 use 和 no 几 乎 没 有 任 何 用 处 , 因 为 它 们 都 在 编 译 时 发生 , 而 不 是 运 行 时 。 你 可 以 直 接 调 用 Underscore->import 和Underscore->unimport, 就 象 use 和 no 那 样 。 通 常 , 如 果 你 想 反 悔 并 且 让 自 己 可 以使 用 $_, 你 就 要 对 它 使 用 local, 这 也 是 所 有 要 点 所 在 。14.2 捆 绑 数 组一 个 实 现 捆 绑 数 组 的 类 至 少 要 定 义 方 法 TIEARRAY,FETCH, 和 STORE。 此 外 还 有 许 多可 选 方 法 : 普 遍 存 在 的 DESTROY 方 法 , 还 有 用 于 提 供 $#array 和 scalar(@array) 访问 的 STORESIZE 和 FETCHSIZE 方 法 。 另 外 , 当 <strong>Perl</strong> 需 要 清 空 该 数 组 时 会 触 发CLEAR , 而 当 <strong>Perl</strong> 在 一 个 真 正 的 数 组 上 需 要 预 先 扩 充 ( 空 间 ) 分 配 的 时 候 需 要 EXTEND。如 果 你 想 让 相 应 的 函 数 在 捆 绑 的 数 组 上 也 能 够 运 行 , 你 还 可 以 定 义 POP,PUSH,SHIFT,UNSHIFT,SPLICE,DELETE, 和 EXISTS 方 法 。Tie::Array 类 可 以 作 为 一 个 基 类 ,用 于 利 用 FETCH 和 STORE 实 现 前 五 个 函 数 。(Tie::Array 的 DELETE 和 EXISTS358


的 缺 省 实 现 只 是 简 单 地 调 用 croak。) 只 要 你 定 义 了 FETCH 和 STORE, 那 么 你 的 对 象包 含 什 么 样 的 数 据 结 构 就 无 所 谓 了 。另 外 , Tie::StdArray 类 ( 在 标 准 的 Tie::Array 模 块 中 定 义 ) 提 供 了 一 个 基 类 , 这 个 基类 的 缺 省 方 法 假 设 此 对 象 包 含 一 个 正 常 的 数 组 。 下 面 是 一 个 利 用 这 个 类 的 简 单 的 数 组 捆 绑类 。 因 为 它 使 用 了 Tie::StdArray 做 它 的 基 类 , 所 以 它 只 需 要 定 义 那 些 应 该 以 非 标 准 方 法对 待 的 方 法 。#! /usr/bin/perlpackage ClockArray;use Tie::Array;our @ISA = 'Tie::StdArray';sub FETCH {my ($self, $place) = @_;$self->[$place % 12 ];}sub STORE {my($self, $place, $value ) = @_;$self->[$place % 12 ] = $value;}package main;tie my @array, 'ClockArray';@array = ( "a" ... "z" );print "@array\n";359


运 行 的 时 候 , 这 个 程 序 打 印 出 "y z o p q r s t u v w x"。 这 个 类 提 供 一 个 只 有 一 打 位 置的 数 组 , 类 似 一 个 时 钟 的 小 时 数 , 编 号 为 0 到 11。 如 果 你 请 求 第 十 五 个 元 素 , 你 实 际 上获 得 第 三 个 。 把 它 想 象 成 一 个 旅 游 助 手 , 用 于 帮 助 那 些 还 没 有 学 会 如 何 读 24 小 时 时 钟 的 人 。14.2.1 数 组 捆 绑 方 法前 面 的 是 简 单 的 方 法 。 现 在 让 我 们 看 看 真 正 的 细 节 。 为 了 做 演 示 , 我 们 将 实 现 一 个 数 组 , 这个 数 组 的 范 围 在 创 建 的 时 候 是 固 定 的 。 如 果 你 试 图 访 问 任 何 超 出 该 界 限 的 东 西 , 则 抛 出 一 个例 外 。 比 如 :use BoundedArray;tie @array, "BoundedArray", 2;$array[0] = "fine";$array[1] = "good";$array[2] = "great";$array[3] = "whoa"; # 禁 止 , 显 示 一 个 错 误 信 息 。这 个 类 的 预 定 义 的 代 码 如 下 :package BoundedArray;use Carp;use strict;为 了 避 免 稍 后 定 义 SPLICE, 我 们 将 从 Tie::Array 类 中 继 承 :use Tie::Array;our @ISA = ("Tie::Array");CLASSNAME->TIEARRAY(LIST)是 该 类 的 构 造 器 ,TIEARRAY 应 该 返 回 一 个 赐 福 了 的 引 用 , 通 过 该 引 用 模 拟 这 个 捆 绑 了 的数 组 。360


在 下 一 个 例 子 里 , 为 了 告 诉 你 并 非 一 定 要 返 回 一 个 数 组 的 引 用 , 我 们 选 择 了 一 个 散 列 引 用来 代 表 我 们 的 对 象 。 散 列 很 适 合 做 通 用 记 录 类 型 : 散 列 的 “BOUND” 键 字 将 存 储 最 大 允 许的 范 围 , 而 其 “DATA” 值 将 保 存 实 际 的 数 据 。 如 果 有 个 类 以 外 的 解 引 用 返 回 的 对 象 ( 就 是一 个 数 组 引 用 , 不 用 怀 疑 ), 则 抛 出 一 个 例 外 。sub TIEARRAY {my $class = shift;my $bound = shift;confess "usage: tie( \@ary, 'BoundedArray', max_subscript)"if @_ || $bound =~ /\D/;return bless { BOUND => $bouind, DATA => [] }, $class;}现 在 我 们 可 以 说 :tie( @array, "BoundedArray", 3); # 允 许 的 最 大 索 引 是 3以 确 保 该 数 组 永 远 不 会 有 多 于 四 个 元 素 。 当 对 数 组 的 一 个 独 立 的 元 素 进 行 访 问 或 者 存 储 的时 候 , 将 调 用 FETCH 和 STORE, 就 好 象 是 处 理 标 量 一 样 , 不 过 有 一 个 额 外 的 索 引 参 数 。SELF->FETCH(INDEX)当 访 问 捆 绑 的 数 组 里 的 一 个 独 立 的 元 素 的 时 候 调 用 这 个 方 法 。 它 接 受 对 象 后 面 的 一 个 参 数 :我 们 试 图 抓 取 的 数 值 的 索 引 。sub FETCH {my ($self, $index ) = @_;if ($index > $self->{BOUND} ) {confess "Array OOB: $index > $self->{BOUND}";}return $self->{DATA}[$index];}SELF->STORE(INDEX, VALUE)361


当 设 置 捆 绑 了 的 数 组 里 的 一 个 元 素 的 时 候 调 用 这 个 方 法 。 它 接 受 对 象 后 面 的 两 个 参 数 : 我们 试 图 存 储 的 东 西 的 索 引 和 我 们 准 备 放 在 那 里 的 数 值 。 比 如 :sub STORE {my($self, $index, $value) = @_;if ($index > $self->{BOUND} ) {confess "Array OOB: $index > $self->{BOUND}";}return $self->{DATA}[$index] = $value;}SELF->DESTROY当 需 要 删 除 捆 绑 变 量 和 回 收 它 的 内 存 的 时 候 调 用 这 个 方 法 。 对 于 一 门 有 垃 圾 回 收 功 能 的 语言 来 说 , 这 个 东 西 几 乎 用 不 上 , 所 以 本 例 中 我 们 忽 略 它 。SELF->FETCHSIZEFETCHSIZE 方 法 应 该 返 回 与 SELF 关 联 的 捆 绑 数 组 的 条 目 的 总 数 。 它 等 效 于scalar(@array), 通 常 等 于 $#array + 1。sub FETCHSIZE {my $self = shift;return scalar @{$self->{DATA}};}SELF->STORESIZE(COUNT)这 个 方 法 把 与 SELF 关 联 的 捆 绑 数 组 的 条 目 总 数 设 置 为 COUNT。 如 果 此 数 组 收 缩 , 那 么你 应 该 删 除 超 出 COUNT 范 围 的 记 录 。 如 果 数 组 增 长 , 你 应 该 确 保 新 的 空 位 置 是 未 定 义的 。 对 于 我 们 的 BoundedArray ? 类 , 我 们 还 要 确 保 该 数 组 不 会 增 长 得 超 出 初 始 化 时 设 置的 限 制 。sub STORESIZE {362


my ($self, $count ) = @_;if ($count > $self->{BOUND} ) {confess "Array OOB: $count > $self->{BOUND}";}$#{$self->{DATA}} = $count;}SELF->EXTEND(COUNT)<strong>Perl</strong> 使 用 EXTEND 方 法 来 表 示 一 个 数 组 将 要 扩 展 成 保 存 COUNT 条 记 录 。 这 样 你 就 可以 一 次 分 配 足 够 大 的 内 存 , 而 不 是 以 后 的 多 次 调 用 。 因 为 我 们 的 BoundedArrays ? 已 经 固定 了 上 限 , 所 以 我 们 不 用 定 义 这 个 方 法 。SELF->EXISTS(INDEX)这 个 方 法 验 证 在 INDEX 位 置 的 元 素 存 在 于 捆 绑 数 组 中 。 对 于 我 们 的 BoundedArray ? ,我 们 只 需 要 在 核 实 了 查 找 企 图 没 有 超 过 我 们 的 固 定 上 限 , 然 后 就 可 以 使 用 <strong>Perl</strong> 内 建 的exists 来 核 实 。sub EXISTS {my ($self, $index) = @_;if ( $index > $self->{BOUND}) {confess "array OOB: $index > $self->{BOUND}";}exists $self->{DATA}[$index];}SELF->DELETE(INDEX)DELETE 方 法 从 捆 绑 数 组 SELF 中 删 除 在 INDEX 位 置 的 元 素 。 对 于 我 们 的BoundedArray ? 类 , 这 个 方 法 看 起 来 几 乎 和 EXISTS 完 全 一 样 , 不 过 这 可 不 是 标 准 。sub DELETE {my ($self, $index) = @_;363


print STDERR "deleting!\n";if ($index > $self->{BOUND} ) {confess "Array OOB: $index > $self->{BOUND}";}delete $self->{DATA}[$index];}SELF->CLEAR当 这 个 数 组 需 要 清 空 的 时 候 调 用 这 个 方 法 。 当 该 数 组 设 置 为 一 列 新 值 ( 或 者 一 列 空 值 ) 的时 候 发 生 这 个 动 作 , 不 过 不 会 在 提 供 给 undef 函 数 的 时 候 发 生 这 个 动 作 。 因 为 一 个 清 空了 的 boundedArray 总 是 满 足 上 限 , 所 以 我 们 在 这 里 不 需 要 检 查 任 何 东 西 :sub CLEAR {my $self = shift;$self->{DATA} = [];}如 果 你 把 数 组 设 置 为 一 个 列 表 , 这 个 动 作 会 触 发 CLEAR, 但 是 看 不 到 列 表 数 值 。 因 此 如果 你 象 下 面 这 样 违 反 上 界 :tie(@array, "BoundedArray", 2);@array = (1,2,3,4);CLEAR 方 法 仍 将 返 回 成 功 。 而 只 有 在 随 后 的 STORE 中 才 会 产 生 例 外 。 这 样 的 赋 值 触 发一 个 CLEAR 和 四 个 STORES。SELF->PUSH(LIST)这 个 方 法 把 LIST 的 元 素 附 加 到 数 组 上 。 下 面 是 它 在 我 们 BoundedArray ?方 法 :类 里 的 运 行sub PUSH {364


my $self = shift;if (@_ + $#{$self->{DATA}} > $self->{BOUND} ) {confess "Attempt to push too many elements";}push @{$self->{DATA}}, @_;}SELF->UNSHIFT(LIST)这 个 方 法 预 先 把 LIST 的 元 素 放 到 数 组 中 。 对 于 我 们 的 BoundedArray ?子 过 程 类 似 PUSH。类 而 言 , 这 个SELF->POPPOP 方 法 从 数 组 中 删 除 最 后 一 个 元 素 并 返 回 之 。 对 于 boundedArray, 它 只 有 一 行 :subPOP { my $self = shift; pop @{$self->{DATA}}}SELF->SHIFTSHIFT 方 法 删 除 数 组 的 第 一 个 元 素 并 返 回 之 。 对 于 boundedArray, 它 类 似 POP。SELF->SPLICE(OFFSET, LENGTH, LIST)这 个 方 法 让 你 接 合 SELF 数 组 。 为 了 模 拟 <strong>Perl</strong> 内 建 的 splice,OFFSET 应 该 是 可 选 项并 且 缺 省 为 零 , 而 负 值 是 从 数 组 后 面 向 前 数 。LENGTH 也 应 该 是 可 选 项 , 缺 省 为 数 组 剩下 的 长 度 。LIST 可 以 为 空 。 如 果 正 确 模 拟 内 建 函 数 , 那 么 它 应 该 返 回 原 数 组 从 OFFSET开 始 的 LENGTH 长 个 元 素 ( 要 被 LIST 代 替 的 元 素 列 表 )。因 为 接 合 是 有 些 复 杂 的 操 作 , 我 们 就 不 定 义 它 了 ; 我 们 只 需 要 使 用 来 自 Tie::Array 模 块的 SPLICE 子 过 程 , 这 个 子 过 程 是 继 承 Tie::Array 时 免 费 时 免 费 拿 到 的 。 这 样 我 们 用其 他 BoundedArray ? 方 法 定 义 了 SPLICE, 因 此 范 围 检 查 仍 然 进 行 。上 面 就 是 BoundedArray ? 类 的 全 部 。 它 只 是 对 数 组 的 语 义 做 了 一 点 点 封 装 。 不 过 我 们 可以 干 得 更 好 , 而 且 用 的 空 间 更 少 。14.2.2 大 家 方 便365


变 量 的 一 个 好 特 性 是 它 可 以 代 换 。 函 数 的 一 样 不 太 好 的 特 点 是 它 不 能 代 换 。 你 可 以 使 用 捆 绑的 数 组 把 一 个 函 数 做 成 可 以 代 换 。 假 设 你 想 代 换 一 个 字 串 里 的 任 意 整 数 。 你 可 以 就 说 :#! /usr/bin/perlpackage RandInterp;sub TIEARRAY { bless\my $self};sub FETCH { int rand $_[1] };package main;tie @rand, "RandInterp";for (1,10,100,1000) {print "A random integer less then $_ would be $rand[$_]\n";}$rand[32] = 5; # 这 么 干 会 重 新 格 式 化 我 们 的 系 统 表 了 么 ?当 运 行 的 时 候 , 它 打 印 出 下 面 的 内 容 :A random integer less than 1 would be 0A random integer less than 10 would be 3A random integer less than 100 would be 46A random integer less than 1000 would be 755Can't locate object method "STORE" via package "RandInterp" at foo line 10.如 你 所 见 , 我 们 甚 至 还 没 能 实 现 STORE, 不 过 这 不 算 什 么 。 我 们 只 是 和 往 常 一 样 把 它 去 掉了 。366


第 十 四 章 捆 绑 (tie) 变 量 下14.3 捆 绑 散 列一 个 实 现 捆 绑 散 列 的 类 应 该 定 义 八 个 方 法 。TIEHASH 构 造 一 个 新 对 象 。FETCH 和STORE 访 问 键 字 / 数 值 对 。EXISTS 报 告 某 键 字 是 否 存 在 于 散 列 中 , 而 DELETE 删 除 一个 键 字 和 它 关 联 的 数 值 ( 注 : 请 注 意 在 <strong>Perl</strong> 里 , 散 列 里 不 存 在 一 个 键 字 与 存 在 一 个 键 字 但是 其 对 应 数 值 为 undef 是 不 同 的 两 种 情 况 。 这 两 种 情 况 可 以 分 别 用 exists 和 defined测 试 。) CLEAR 通 过 删 除 所 有 键 字 / 数 值 对 清 空 散 列 。FIRSTKEY 和 NEXTKEY 在 你 调用 keys, values, 或 each 的 时 候 逐 一 遍 历 键 字 / 数 值 对 。 还 有 就 是 和 往 常 一 样 , 如 果你 想 在 对 象 删 除 的 时 候 执 行 某 种 特 定 的 动 作 , 那 么 你 可 能 还 要 定 义 DESTORY 方 法 。( 如果 你 觉 得 方 法 太 多 , 那 么 你 肯 定 没 有 认 真 阅 读 上 一 节 关 于 数 组 的 内 容 。 不 管 怎 样 , 你 都 可 以自 由 地 从 标 准 的 Tie::Hash 模 块 继 承 缺 省 的 方 法 , 只 用 重 新 定 义 你 感 兴 趣 的 方 法 。 同 样 ,Tie::StdHash 假 设 此 实 现 同 样 也 是 一 个 散 列 。)比 如 , 假 定 你 想 创 建 这 么 一 个 散 列 : 每 次 你 给 一 个 键 字 赋 值 的 时 候 , 它 不 是 覆 盖 掉 原 来 的 内容 , 而 是 把 新 数 值 附 加 到 一 个 数 值 数 组 上 。 这 样 当 你 说 :$h{$k} = "one";$h{$k} = "two";它 实 际 上 干 的 是 :push @{ $h{$k} }, "one";push @{ $h{$k} }, "two";这 个 玩 叶 儿 不 算 什 么 复 杂 的 主 意 , 所 以 你 应 该 能 用 一 个 挺 简 单 的 模 块 实 现 。 把Tie::StdHash 用 做 基 类 , 下 面 就 是 干 这 事 的 Tie::AppendHash:package Tie::AppendHash;use Tie::Hash;our @ISA = ("Tie::StdHash");sub STORE {367


my ($self, $key, $value) = @_;push @{$self->{key}}, $value;}1;14.3.1 散 列 捆 绑 方 法这 儿 是 一 个 很 有 趣 的 捆 绑 散 列 类 的 例 子 : 它 给 你 一 个 散 列 , 这 个 散 列 代 表 用 户 特 定 的 点 文 件( 也 就 是 说 , 文 件 名 开 头 是 一 个 句 点 的 文 件 , 这 样 的 文 件 是 Unix 初 始 化 文 件 的 命 名 传 统 。)你 用 文 件 名 做 索 引 ( 除 去 开 头 的 句 点 ) 把 文 件 名 放 进 散 列 , 而 拿 出 来 的 时 候 是 点 文 件 的 内 容 。比 如 :use DotFiles;tie %dot, "DotFiles";if ( $dot{profile} =~ /MANPATH/ or$dot{login}=~ /MANPATH/ or$dot{cshrc} =~ /MANPATH/ ) {print "you seem to set your MANPATH\n";}下 面 是 使 用 我 们 捆 绑 类 的 另 外 一 个 方 法 :# 第 三 个 参 数 是 用 户 名 , 我 们 准 备 把 他 的 点 文 件 捆 绑 上 去 。tie %him, "DotFiels", "daemon";foreach $f (keys %him) {printf "daemon dot file %s is size %d\n", $f, length $him{$f};}在 我 们 的 DotFiles ? 例 子 里 , 我 们 把 这 个 对 象 当 作 一 个 包 含 几 个 重 要 数 据 域 的 普 通 散 列 来实 现 , 这 几 个 数 据 域 里 只 有 {CONTENTS} 域 会 保 存 一 般 用 户 当 作 散 列 的 东 西 。 下 面 是 此对 象 的 实 际 数 据 域 :368


数 据 域内 容USERHOME这 个 对 象 代 表 谁 的 点 文 件那 些 点 文 件 在 哪 里CLOBBER 我 们 是 否 允 许 修 改 或 者 删 除 这 些 点 文 件CONTENTS 点 文 件 名 字 和 内 容 映 射 的 散 列下 面 是 DotFiles ? .pm 的 开 头 :package DotFiles;use Carp;sub whowasi { (caller(1))[3]. "()" }my $DEBUG = 0;sub debug { $DEBUGA = @_ ? shift: 1}对 于 我 们 的 例 子 而 言 , 我 们 希 望 打 开 调 试 输 出 以 便 于 在 开 发 中 的 跟 踪 , 因 此 我 们 为 此 设 置 了$DEBUG。 我 们 还 写 了 一 个 便 利 函 数 放 在 内 部 以 便 于 打 印 警 告 :whowasi 返 回 调 用 了 当 前函 数 的 那 个 函 数 的 名 字 (whowasi 是 “ 祖 父 辈 ” 的 函 数 )。下 面 就 是 DotFiles ?捆 绑 散 列 的 方 法 :CLASSNAME->TIEHASH(LIST)这 里 是 DotFiles ?构 造 器 :sub TIEHASH {my $self = shift;my $user = shift || $>;my $dotdir = shift || "";croak "usage: @{[ &whowasi ] } [ USER [DOTDIR]]" if @_;$user = getpwuid($user) if $user =~ /^\d+$/;369


my $dir = (getpwname($user))[7]or corak "@{ [&whowasi] }: no user $user";$dir .= "/$dotdir" if $dotdir;my $node = {USER => $user,HOME => $dir,CONTENTS => {},CLOBBER => 0,};opendir DIR, $diror croak "@{[&whowasi]}: can't opendir $dir: $!";for my $dot ( grep /^\./ && -f "$dir/$_", readdir(DIR)) {$dot =~ s/^\.//;$node->{CONTENTS}{$dot} = undef;}closedir DIR;return bless $node, $self;}值 得 一 提 的 是 , 如 果 你 准 备 用 文 件 来 测 试 上 面 的 readdir 的 返 回 值 , 你 最 好 预 先 准 备 好有 问 题 的 目 录 ( 象 我 们 一 样 )。 否 则 , 因 为 我 们 没 有 用 chdir, 所 以 你 很 有 可 能 测 试 的 是错 误 的 文 件 。SELF->FETCH(KEY)370


这 个 方 法 实 现 的 是 从 这 个 捆 绑 的 散 列 里 读 取 元 素 。 它 在 对 象 后 面 还 有 一 个 参 数 : 你 想 抓 取的 散 列 元 素 的 键 字 。 这 个 键 字 是 一 个 字 串 , 因 而 你 可 以 对 它 做 你 想 做 的 任 何 处 理 ( 和 字 串一 致 )。 下 面 是 我 们 DotFiles ? 例 子 的 抓 取 :sub FETCH {carp &whowasi if $DEBUG;my $self = shift;my $dot = shift;my $dir = $self->{HOME};my $file = "$dir/.$dot";unless (exists $self->{CONTENTS}->{$dot} || -f $file ) {carp "@{[&whowasi]}: no $dot file" if $DEBUG;return undef;}# 实 现 一 个 缓 冲if (defined $self->{CONTENTS}->{$dot} ) {return $self->{CONTENTS}->{$dot};} else {return $self->{CONTENTS}->{$dot} = `cat $dir/.$dot`;}}我 们 在 这 里 做 了 一 些 手 脚 : 我 们 用 的 是 Unix 的 cat (1) 命 令 , 不 过 这 样 打 开 文 件 的 移植 性 更 好 ( 而 且 更 高 效 )。 而 且 , 因 为 点 文 件 是 一 个 Unix 式 的 概 念 , 所 以 我 们 不 用 太 担心 。 或 者 是 不 应 该 太 担 心 。 或 者 ...371


SELF->STORE(KEY, VALUE)当 捆 绑 散 列 里 的 元 素 被 设 置 ( 或 者 写 入 ) 的 时 候 , 这 个 方 法 做 那 些 脏 活 累 活 。 它 在 对 象 后面 还 有 两 个 参 数 : 我 们 存 贮 新 值 的 键 字 , 以 及 新 值 本 身 。就 我 们 的 DotFiles ? 例 子 而 言 , 我 们 首 先 要 在 tie 返 回 的 最 初 的 对 象 上 调 用 方 法 clobber以 后 才 能 允 许 拥 护 覆 盖 一 个 文 件 :sub STORE {carp &whowasi if $DEBUG;my $self = shift;my $dot = shift;my $value = shift;my $file = $self->{HOME} . "/.$dot";croak "@{[&whowasi]}: $file not clobberable"unless $self->{CLOBBER};open(F, "> $file") or croak "cna't open $file: $!";print F $vale;close(F);}如 果 有 谁 想 删 除 什 么 东 西 , 他 们 可 以 说 :$ob = tie %daemon_dots, "daemon";$ob->clobber(1);$daemon_dot{signature} = "A true daemon\n";不 过 , 它 们 可 以 用 tied 设 置 {CLOBBER}:372


tie %daemon_dots, "Dotfiles", "daemon";tied(%daemon_dots)->clobber(1);或 者 用 一 条 语 句 :(tie %daemon_dots, "DotFiles", "daemon")->clobber(1);clobber 方 法 只 是 简 单 的 几 行 :sub clobber{my $self = shift;$self->{CLOBBER} = @_ ? shift : 1;}SELF->DELETE(KEY)这 个 方 法 处 理 从 散 列 中 删 除 一 个 元 素 的 请 求 。 如 果 你 模 拟 的 散 列 在 某 些 地 方 用 了 真 的 散列 , 那 么 你 可 以 只 调 用 真 的 delete。 同 样 , 我 们 将 仔 细 检 查 用 户 是 否 真 的 想 删 除 文 件 :sub DELETE {carp &whowasi if $DEBUG;my $self = shift;my $dot = shift;my $file = $self->{HOME} . "./$dot";croak "@{[&whowasi]}: won't remove file $file"unless $self->{CLOBBER};373


delete $self->{CONTENTS}->{$dot};unlink $file or carp "@{[&whowasi]}: can't unlink $file: $!";}SELF->CLEAR当 需 要 清 理 整 个 散 列 的 时 候 运 行 这 个 方 法 , 通 常 是 给 散 列 赋 一 个 空 列 表 。 在 我 们 的 例 子 里 ,这 可 以 要 删 除 用 户 的 所 有 点 文 件 的 ! 这 可 真 是 一 个 危 险 的 方 法 , 所 以 我 们 要 求 在 进 行 清 理之 前 要 把 CLOBBER 设 置 为 大 于 1:sub CLEAR {carp &whowasi if $DEBUG;my $self = shift;croak "@{[&whowasi]}: won't remove all dotfiles for $self->{USER}"unless $self->{CLOBBER} > 1;for my $dot (key % {$self->{COTENTS}}) {$self->DELETE($dot);}}SELF->EXISTS(KEY)当 用 户 在 某 个 散 列 上 调 用 exists 函 数 的 时 候 运 行 这 个 方 法 。 在 我 们 的 例 子 里 , 我 们 会 查找 {CONTENTS} 散 列 元 素 来 找 出 结 果 :sub EXISTS {carp &whowasi if $DEBUG;my $self = shift;my $dot = sfhit;return exists $self->{CONTENTS}->{$dot};}374


SELF->FIRSTKEY当 用 户 开 始 遍 历 散 列 , 比 如 说 用 一 个 keys, 或 者 values, 或 者 一 个 each 调 用 的 时 候需 要 这 个 方 法 。 我 们 通 过 在 标 量 环 境 中 调 用 keys, 重 置 其 (keys 的 ) 内 部 状 态 以 确 保后 面 retrun 语 句 里 的 each 将 拿 到 第 一 个 键 字 。sub FIRSTKEY {carp &whowasi if $DEBUG;my $self = shift;my $temp = keys %{$self->{CONTENTS}};return scalar each %{$self->{CONTENTS}};}SELF->NEXTKEY(PREVKEY)这 个 方 法 是 keys,values 或 者 each 函 数 的 叙 述 器 。PREVKEY 是 上 次 访 问 的 键 字 ,<strong>Perl</strong> 知 道 该 提 供 什 么 。 如 果 NEXTKEY 方 法 需 要 知 道 它 的 前 面 的 状 态 来 计 算 下 一 个 状 态时 这 个 变 量 很 有 用 。就 我 们 的 例 子 , 我 们 正 在 使 用 一 个 真 正 的 散 列 来 代 表 捆 绑 了 的 散 列 的 数 据 , 不 同 的 只 是 这个 散 列 保 存 在 散 列 的 CONTENTS 数 据 域 而 不 是 在 散 列 本 身 。 因 此 我 们 只 需 要 依 赖 <strong>Perl</strong>的 each 叙 述 器 就 行 了 :sub NEXTKEY {carp &whowasi if $DEBUG;my $self = shift;return scalar each %{ $self->{CONTENTS}}}SELF->DESTORY当 你 准 备 删 除 这 个 捆 绑 的 散 列 对 象 的 时 候 触 发 这 个 方 法 。 你 实 际 上 并 不 需 要 这 个 东 西 , 除非 是 用 做 调 试 和 额 外 的 清 理 。 下 面 是 一 个 非 常 简 单 的 版 本 :375


sub DESTROY{carp &whowasi if $DEBUG;}请 注 意 我 们 已 经 给 出 了 所 有 的 方 法 , 你 的 作 业 就 是 退 回 去 把 我 们 代 换 @{[&whowasi]}的 地 方 找 出 来 然 后 把 他 们 替 换 成 名 字 为 $whowasi 的 简 单 捆 绑 标 量 , 并 且 要 实 现 相 同 的功 能 。14.4 捆 绑 文 件 句 柄一 个 实 现 捆 绑 文 件 句 柄 的 类 应 该 定 义 下 面 的 方 法 :TIEHANDLE 和 至 少 PRINT,PRINTF,WRITE,READLINE,GETC 和 READ 之 一 。 该 类 还 可 以 提 供 一 个 DESTROY 方 法 ,以 及 BINMODE,OPEN,CLOSE,EOF,FILENO,SEEK,TELL,READ 和 WRITE 方法 以 便 于 相 应 的 <strong>Perl</strong> 内 建 函 数 用 这 个 捆 绑 的 文 件 句 柄 。( 当 然 , 这 也 不 是 绝 对 正 确 :WRITE对 应 syswrite 而 与 <strong>Perl</strong> 内 建 的 write 函 数 没 有 任 何 关 系 ,write 是 和 format 声 明 一起 用 于 打 印 的 。)当 <strong>Perl</strong> 内 嵌 于 其 他 程 序 中 ( 比 如 Apache 和 vi ) 时 以 及 当 向 STDOUT 和 STDERR的 输 出 需 要 以 某 种 特 殊 的 方 式 重 新 定 向 的 时 候 捆 绑 的 文 件 句 柄 就 特 别 有 用 了 。不 过 捆 绑 的 文 件 句 柄 实 际 上 完 全 不 必 与 文 件 捆 绑 。 你 可 以 用 输 出 语 句 制 作 一 个 存 在 于 内 存 的数 据 结 构 以 及 用 输 入 语 句 把 它 们 读 取 回 来 。 下 面 是 一 个 反 转 print 和 printf 语 句 的 打 印顺 序 但 却 不 用 颠 倒 相 关 行 顺 序 的 简 单 方 法 :package ReversePrint;use strict;sub TIEHANDLE {my $class = shift;bless [], $class;}sub PRINT {my $self = shift;push @$self, join ' ', @_376


}sub PRINTF {my $self = shift;my $fmt = shift;push @$self, sprintf $fmt, @_;}sub READLINE {my $self = shift;pop @$self;}package main;my $m = "--MORE--\n";tie *REV, "ReversePrint";# 做 一 些 print 和 printf。print REV "The fox is now dead. $m";printf REV


over the lazy dog.END# 现 在 从 同 一 个 句 柄 中 读 回 来print while ;打 印 出 :The quick brown fox jumpsover the lazy dog.The quick brown fox jumps overover the lazy dog 3179357 times!The fox is now dead.--MORE--14.4.1 文 件 句 柄 捆 绑 方 法对 于 我 们 扩 展 的 例 子 而 言 , 我 们 将 创 建 一 个 文 件 句 柄 , 并 且 向 之 打 印 大 写 字 串 。 为 了 明 确 ,我 们 会 在 把 这 个 文 件 句 柄 打 开 的 时 候 向 它 打 印 , 而 当 结 束 关 闭 的 时 候 打 印 。 这 个 方 法 是 我们 从 格 式 优 良 的 XML 中 借 来 的 。下 面 是 我 们 将 实 现 这 个 类 的 shout.pm 文 件 的 开 头 :package Shout;use Carp;# 这 样 我 们 就 可 以 把 我 们 的 错 误 汇 报 出 来然 后 我 们 把 在 shout.pm 里 定 义 的 方 法 列 出 来 :CLASSNAME->TIEHANDLE(LIST)这 是 该 类 的 构 造 器 , 和 往 常 一 样 , 应 该 返 回 一 个 赐 福 了 的 引 用 。sub TIEHANDLE {my $class = shift;my $form = shift;378


open my $self, $form, @_ or croak "can't open $form@_: $!";if ($form =~ />/) {print $self "\n";$$self->{WRITING} = 1;# 记 得 写 结 束 标 记}return bless $self, $class;# $self 是 一 个 全 局 引 用}在 这 里 , 我 们 根 据 传 递 给 tie 操 作 符 的 模 式 和 文 件 名 打 开 一 个 新 的 文 件 句 柄 , 向 文 件 中 写入 , 然 后 返 回 一 个 指 向 它 的 赐 福 了 的 引 用 。 在 open 语 句 里 有 一 大 堆 东 西 , 不 过 我 们 将只 会 指 出 一 点 , 除 了 通 常 的 "open or die" 惯 用 法 以 外 ,my $self 给 open 提 供 了 一个 未 定 义 的 标 量 ,open 知 道 自 动 把 那 个 标 量 转 成 一 个 类 型 团 。 这 个 变 量 是 类 型 团 这 一 点非 常 重 要 , 因 为 这 个 类 型 团 不 仅 包 含 文 件 真 实 的 I/O 对 象 , 而 且 还 包 含 各 种 各 样 其 他 可以 自 由 获 取 的 数 据 结 构 , 比 如 一 个 标 量 ($$$self), 一 个 数 组 (@$$self), 和 一 个 散列 (%$$self)。( 我 们 不 会 提 到 子 过 程 ,&$$self。)$form 是 文 件 名 或 者 模 式 参 数 。 如 果 它 是 一 个 文 件 名 ,@_ 就 是 空 的 , 所 以 它 的 性 质 就象 一 个 两 个 参 数 的 open。 否 则 ,$form 就 是 剩 余 参 数 的 模 式 。open 之 后 , 我 们 检 测 一 下 看 看 我 们 是 否 应 该 写 入 表 示 开 始 的 标 记 。 如 果 是 , 我 们 就 写 。然 后 我 们 马 上 使 用 那 些 我 们 谈 到 的 团 数 据 结 构 。 那 个 @@self->{WRITING} 是 一 个 使 用团 存 储 有 趣 信 息 的 一 个 例 子 。 在 这 个 例 子 里 , 我 们 记 住 是 否 写 过 起 始 标 记 , 这 样 我 们 才 知道 我 们 是 否 应 该 做 相 应 的 结 束 标 记 。 我 们 正 在 使 用 %$$self 散 列 , 所 以 我 们 可 以 给 那 个数 据 域 一 个 象 样 的 名 字 。 我 们 本 可 以 用 象 $$$self 这 样 的 标 量 , 但 是 那 样 不 能 自 说 明 。( 或者 它 只 能 自 说 明 —— 取 决 于 你 如 何 看 它 。)SELF->PRINT(LIST)这 个 方 法 实 现 了 一 个 向 捆 绑 的 句 柄 print。LIST 是 传 递 给 print 的 东 西 。 我 们 下 面 的 方法 把 LIST 的 每 个 元 素 都 转 换 成 大 写 :sub PRINT {my $self = shift;print $self map {uc} @_;379


}SELF->READLINE当 用 尖 角 操 作 符 () 或 者 readline 读 句 柄 的 时 候 , 用 这 个 方 法 提 供 数 据 。 当 没 有 更 多 数据 可 读 的 时 候 , 这 个 方 法 应 该 返 回 undef。sub READLINE {my $self = shift;return ;}在 这 里 , 我 们 只 是 简 单 地 return , 这 样 , 根 据 标 量 环 境 还 是 列 表 环 境 , 这 个 方法 就 能 做 出 正 确 的 反 映 。SELF->GETC当 在 捆 绑 的 文 件 句 柄 上 使 用 getc 的 时 候 就 会 运 行 这 个 方 法 。sub GETC {my $self = shift;return getc($self);}和 我 们 Shout 类 的 几 个 方 法 类 似 ,GETC 只 是 简 单 地 调 用 相 应 的 <strong>Perl</strong> 内 建 的 函 数 然 后返 回 结 果 。SELF->OPEN(LIST)我 们 的 TIEHANDLE 方 法 本 身 就 打 开 一 个 文 件 , 但 是 一 个 使 用 Shout 类 的 程 序 在 那 之后 调 用 open 将 触 发 这 个 方 法 。sub OPEN {my $self = shift;my $form = shift;my $name = "$form@_";380


$self->CLOSE;open($self, $form, @_) or croak "can't reopen $name: $!";if ($form =~ />/) {print $self "\n" or croak "can't start print: $!";$$self->{WRITING} = 1;# 记 得 写 结 束 标 记}else {$$self->{WRITING} = 0;# 记 得 不 要 写 结 束 标 记}return 1;}我 们 激 活 了 我 们 自 己 的 CLOSE 方 法 明 确 地 关 闭 文 件 , 以 免 用 户 不 愿 意 自 己 做 。 然 后 我 们打 开 一 个 新 文 件 , 文 件 名 是 在 open 里 声 明 的 , 然 后 再 向 里 面 写 东 西 。SELF->CLOSE这 个 方 法 处 理 关 闭 句 柄 的 请 求 。 在 这 里 , 我 们 搜 索 到 文 件 的 结 尾 , 如 果 成 功 , 则 打 印 , 然后 调 用 <strong>Perl</strong> 内 建 的 close。sub CLOSE {my $self = shift;if ($$self->{WRITING}) {$self->SEEK(0,2)or return;$self->PRINT("\n")or return;}return close $self;}SELF->SEEK(LIST)381


当 你 对 一 个 捆 绑 的 文 件 句 柄 进 行 seek 的 时 候 调 用 SEEK 方 法 。sub SEEK {my $self = shift;my ($offset, $whence) = @_;return seek($self, $offset, $whence);}SELF->TELL当 你 对 一 个 捆 绑 的 文 件 句 柄 调 用 tell 的 时 候 调 用 这 个 方 法 。sub TELL {my $self = shift;return tell $self;}SELF->PRINTF(LIST)当 在 捆 绑 的 句 柄 上 面 使 用 printf 的 时 候 运 行 这 个 方 法 。LIST 将 包 含 格 式 和 需 要 打 印 的 条目 。sub PRINTF {my $self = shift;my $template = shift;return $self->PRINT(sprintf $template, @_);}在 这 里 , 我 们 用 sprintf 生 成 格 式 化 字 串 然 后 把 它 传 递 给 PRINT 转 成 大 写 。 不 过 这 里 也没 有 让 你 一 定 要 用 内 建 的 sprintf 函 数 的 原 因 。 你 可 以 截 获 百 分 号 逃 逸 以 满 足 你 自 己 的 目的 。SELF->READ(LIST)382


当 用 read 或 者 sysread 对 句 柄 做 读 操 作 时 , 该 这 个 方 法 会 做 出 响 应 。 请 注 意 我 们 “ 现场 ” 修 改 LIST 的 第 一 个 参 数 , 模 拟 read 的 能 力 : 它 填 充 作 为 它 的 第 二 个 参 数 传 递 进 来的 标 量 。sub READ {my ($self, undef, $length, $offset) = @_;my $bufref = \$_[1];return read($self, $$bufref, $length, $offset);}SELF->WRITE(LIST)当 用 syswrite 对 该 句 柄 写 入 的 时 候 调 用 这 个 方 法 。 在 这 里 , 我 们 把 待 写 字 串 变 成 大 写 。sub WRITE {my $self = shift;my $string = uc(shift);my $length = shift || length $string;my $offset = shift || 0;return syswrite $self, $string, $length, $offset;}SELF->EOF如 果 用 eof 对 一 个 与 Shout 类 捆 绑 的 文 件 句 柄 进 行 测 试 的 时 候 , 这 个 方 法 返 回 一 个 布 尔值 。sub EOF {my $self = shift;return eof $self;}SELF->BINMODE(DISC)383


这 个 方 法 声 明 将 要 用 于 这 个 文 件 句 柄 的 I/O 规 则 。 如 果 没 有 声 明 , 它 把 这 个 捆 绑 了 的 文件 句 柄 置 于 二 进 制 模 式 (:raw 规 则 ), 用 于 那 些 区 分 文 本 和 二 进 制 文 件 的 文 件 系 统 。sub BINMODE {my $self = shift;my $disc = shift || ":raw";return binmode $self, $disc;}就 这 么 写 , 但 实 际 上 这 个 方 法 在 我 们 的 类 中 没 有 什 么 用 , 因 为 open 已 经 向 句 柄 中 写 入 数据 了 。 所 以 在 我 们 的 例 子 里 我 们 可 能 可 以 做 的 更 简 单 些 :sub BINMODE { croak("Too late to use binmode") }SELF->FILENO这 个 方 法 应 该 返 回 与 捆 绑 的 文 件 句 柄 相 关 联 的 操 作 系 统 文 件 描 述 符 (fileno)。sub FILENO {my $self = shift;return fileno $self;}SELF->DESTROY和 其 他 类 型 的 捆 绑 一 样 , 当 对 象 将 要 删 除 的 时 候 触 发 这 个 方 法 。 对 象 清 理 自 己 的 时 候 很 有用 。 在 这 里 , 我 们 确 保 文 件 关 闭 了 , 以 免 程 序 忘 记 调 用 close。 我 们 可 以 只 说 close $self,不 过 更 好 的 方 法 是 调 用 该 类 的 CLOSE 方 法 。 这 样 的 话 , 如 果 类 的 设 计 者 决 定 修 改 文 件 关闭 的 方 法 的 话 , 这 个 DESTROY 方 法 就 不 用 改 了 。sub DESTROY {my $self = shift;$self->CLOSE;# 用 Shout 的 CLOSE 方 法 关 闭 文 件}384


下 面 是 我 们 的 Shout 类 的 一 个 演 示 :#! /usr/bin/perluse Shout;tie(*FOO, Shout::, ">filename");print FOO "hello\n";seek FOO, 0, 0;# 打 印 HELLO。# 退 回 到 开 头@lines = ; # 调 用 READLINE 方 法 。close FOO; # 明 确 关 闭 文 件 。open(FOO, "+


# 这 里 是 放 你 想 跟 踪 的 函 数 的 存 根 。sub trace {my $self = shift;local $Carp::CarpLevel = 1;Carp::cluck("\ntrace magical method") if $self->debug;}# 重 载 句 柄 以 打 印 出 我 们 的 路 径sub pathname {my $self = shift;confess "i am not a class method" unless ref $self;$$self->{PATHNAME} = shift if @_;return $$self->{PATHNAME};}# 双 重 模 式sub debug {my $self = shift;my $var = ref $self ? \$$self->{DEBUG} : \our $Debug;$$var = shift if @_;return ref $self ? $$self->{DEBUG} || $Debug : $Debug;}然 后 象 下 面 这 样 在 所 有 你 的 普 通 方 法 的 入 口 处 调 用 trace:sub GETC { $_[0]->trace;# 新 的386


my($self) = @_;getc($self);}并 且 在 TIEHANDLE 和 OPEN 里 设 置 路 径 名 :sub TIEHANDLE {my $class= shift;my $form = shift;my $name = "$form@_";# NEWopen my $self, $form, @_ or croak "can't open $name: $!";if ($form =~ />/) {print $self "\n";$$self->{WRITING} = 1;# Remember to do end tag}bless $self, $class;$self->pathname($name);# $fh is a glob ref# NEWreturn $self;}sub OPEN { $_[0]->trace;# NEWmy $self = shift;my $form = shift;my $name = "$form@_";$self->CLOSE;open($self, $form, @_) or croak "can't reopen $name: $!";387


$self->pathname($name);# NEWif ($form =~ />/) {print $self "\n" or croak "can't start print: $!";$$self->{WRITING} = 1;# Remember to do end tag}else {$$self->{WRITING} = 0;# Remember not to do end tag}return 1;}有 些 地 方 你 还 需 要 调 用 $self->debug(1) 打 开 调 试 。 如 果 你 这 么 做 , 那 么 你 的 所 有Carp::cluck 调 用 都 将 会 生 成 有 意 义 的 信 息 。 下 面 是 我 们 做 上 面 的 reopen 的 时 候 得 到 的信 息 。 它 给 我 们 显 示 了 三 个 深 藏 的 方 法 , 当 时 我 们 正 在 关 闭 旧 文 件 并 且 准 备 打 开 新 文 件 :trace magical method at foo line 87Shout::SEEK('>filename', '>filename', 0, 2) called at foo line 81Shout::CLOSE('>filename') called at foo line 65Shout::OPEN('>filename', '+


print CALC "$sum * $sum\n";$sum = ;print "$_: $sum";chomp $sum;}close CALC;我 们 可 以 看 到 打 印 出 下 面 的 东 西 :1: 42: 163: 2564: 655365: 42949672966: 184467440737095516167: 340282366920938463463374607431768211456如 果 你 的 机 器 里 有 bc 而 且 还 有 象 下 面 这 样 定 义 的 Tie::Open2, 那 么 你 就 能 看 到 上 面 预期 的 输 出 。 这 次 我 们 给 我 们 的 内 部 对 象 用 了 一 个 赐 福 了 的 数 组 。 它 包 含 我 们 的 两 个 真 正 的 文件 句 柄 用 于 读 和 写 。( 打 开 一 个 双 头 管 道 的 脏 活 由 IPC::Open2 干 ; 我 们 只 做 有 意 思 的 部分 。)package Tie::Open2;use strict;use Carp;use Tie::Handle;# 不 要 从 这 里 继 承use IPC::Open2;sub TIEHANDLE {389


my ($class, @cmd) = @_;no warnings 'once';my @fhpair = \do { local(*RDR, *WTR) };bless $_, 'Tie::StdHandle' for @fhpair;bless(\@fhpair => $class)->OPEN(@cmd) || die;return \@fhpair;}sub OPEN {my ($self, @cmd) = @_;$self->CLOSE if grep {defined} @{ $self->FILENO };open2(@$self, @cmd);}sub FILENO {my $self = shift;[ map { fileno $self->[$_] } 0, 1];}for my $outmeth (qw(PRINT PRINTF WRITE) ) {no strict 'refs';*$outmeth = sub {my $self = shift;$self->[1]->$outmeth(@_);390


};}for my $inmeth (qw(READ READLINE GETC) ) {no strict 'refs';*$inmeth = sub {my $self = shift;$self->[0]->$inmeth(@_);};}for my $doppelmeth (qw(BINMODE CLOSE EOF)) {no strict 'refs';*$doppelmeth = sub {my $self = shift;$self->[0]->$doppelmeth(@_) && $self->[1]->$doppelmeth(@_);};}for my $deadmeth (qw(SEEK TELL)) {no strict 'refs';*$deadmeth = sub {craok("can't $deadmeth a pipe");};391


}1;最 后 四 行 以 我 们 的 观 点 来 说 是 非 常 时 髦 的 。 为 了 解 释 这 里 在 做 什 么 , 请 回 过 头 看 一 眼 第 八 章 ,引 用 , 里 面 的 “ 作 为 函 数 模 板 的 闭 合 ”。下 面 是 一 套 更 怪 异 的 类 。 它 的 包 名 字 应 该 给 你 一 些 关 于 它 是 干 什 么 的 线 索 。use strict;package Tie::DevNullsub TIEHANDLE {my $class = shift;my $fh = local *FH;bless \$fh, $class;}for (qw(READ READLINE GETC PRINT PRINTF WRITE)) {no strict 'refs';*$_ = sub { return };}package Tie::DevRandom;sub READLINE { rand() . "\n"; }sub TIEHANDLE {my $class = shift;my $fh = local *FH;392


less \$fh, $class;}sub FETCH { rand() }sub TIESCALAR {my $class = shift;bless \my $self, $class;}package Tie::Tee;sub TIEHANDLE {my $class = shift;my @handles;for my $path (@_) {open(my $fh, ">$path") || die "can't write $path";push @handles, $fh;}bless \@handles, $class;}sub PRINT {my $self = shift;my $ok = 0;for my $fh (@$self) {393


$ok += print $fh @_;}return $ok = @$self;}Tie::Tee 类 模 拟 标 准 的 Unix tee (1) 程 序 , 它 把 一 个 输 出 流 发 送 到 多 个 不 同 的 目 的 。Tie::DevNull 类 模 拟 空 设 备 ,Unix 系 统 里 的 /dev/null 。 而 Tie::DevRandom 类 生成 可 以 用 做 句 柄 或 标 量 的 随 机 数 , 具 体 做 什 么 取 决 于 你 调 用 的 是 TIEHANDLE 还 是TIESCALAR! 下 面 是 你 调 用 它 们 的 方 法 :package main;tie *SCATTER,tie *RANDOM,"Tie::Tee", qw(tmp1 - tmp2 >tmp3 tmp4);"Tie::DevRandom";tie *NULL,"Tie::DevNull";tie my $randy, "Tie::DevRandom";for my $i (1..10) {my $line = ;chomp $line;for my $fh ( *NULL, *SCATTER) {print $fh "$i: $line $randy\n";}}这 个 程 序 在 你 的 屏 幕 上 输 出 类 似 下 面 的 东 西 :1: 0.124115571686165 0.208728194740742: 0.156618299751194 0.678171662366353394


3: 0.799749050426126 0.3001849639607924: 0.599474551447884 0.2139352860299165: 0.700232143543861 0.8007737512966716: 0.201203608274334 0.06543032906395757: 0.605381294683365 0.7181623040904878: 0.452976481105495 0.5740262691216679: 0.736819876983848 0.39173761066204410: 0.518606540417331 0.381805078272308不 过 事 还 没 完 ! 它 向 你 的 屏 幕 输 出 是 因 为 上 面 的 *SCATTER tie 里 的 -。 而 且 那 一 行 还 命令 它 创 建 文 件 tmp1,tmp2, 和 tmp4, 同 时 还 附 加 到 文 件 tmp3 上 。( 我 们 在 循 环 里 还向 *NULL 输 出 了 , 当 然 那 不 会 在 任 何 有 趣 的 地 方 显 示 任 何 东 西 , 除 非 你 对 黑 洞 感 兴 趣 。)14.5 一 个 精 细 的 松 绑 陷 阱如 果 你 试 图 使 用 从 tie 或 者 tied 返 回 的 对 象 , 而 且 该 类 定 义 了 一 个 析 构 器 , 那 么 你 就 得小 心 一 个 精 细 的 陷 阱 。 看 看 下 面 这 个 ( 故 意 设 计 的 ) 例 子 类 , 它 使 用 一 个 文 件 来 记 录 赋 予 一个 标 量 的 所 有 值 :package Remember;sub TIESCALAR {my $class = shift;my $filename = shift;open (my $handle, ">", $filename)or die "Cannot open $filename: $!\n";print $handle "The Start\n";bless {FH => $handle, VALUE => 0}, $class;}395


sub FETCH {my $self = shift;return $self->{VALUE};}sub STORE{my $self = shift;my $value = shift;my $handle = $self->{FH};print $handle "$value\n";$self->{VALUE} = $value;}sub DESTROY {my $self = shift;my $handle = $self->{FH};print $handle "The End\n";close $handle;}1;然 后 是 一 个 利 用 我 们 Remember 类 的 例 子 :use strict;396


use Remember;my $fred;$x = tie $fred, "Remember", "camel.log";$fred = 1;$fred = 4;$fred = 5;untie $fred;system "cat camel.log";执 行 的 输 出 是 :The Start145The End到 目 前 为 止 还 不 错 。 现 在 让 我 们 增 加 一 个 额 外 的 方 法 到 Remember 类 , 这 样 允 许 在 文 件中 有 注 释 , 也 就 是 象 下 面 这 样 的 东 西 :sub comment {my $self = shift;my $message = shift;print {$self->{FH}} $handle $message, "\n";}下 面 是 前 面 那 个 例 子 , 修 改 以 后 利 用 comment 方 法 :use strict;397


use Remember;my ($fred, $x);$x = tie $fred, "Remember", "camel.log";$fred = 1;$fred = 4;comment $x "changing...";$fred = 5;untie $fred;system "cat camel.log";现 在 这 个 文 件 会 是 空 的 , 而 这 样 的 结 果 可 能 不 是 你 想 要 的 。 让 我 们 解 释 一 下 为 什 么 。 捆 绑 一个 变 量 实 际 上 是 把 它 和 构 造 器 返 回 的 对 象 关 联 起 来 。 这 个 对 象 通 常 只 有 一 个 引 用 : 那 个 藏 在捆 绑 变 量 身 后 的 。 调 用 “untie” 打 破 了 这 个 关 联 并 且 消 除 了 该 引 用 。 因 为 没 有 余 下 什 么 指 向该 对 象 的 引 用 , 那 么 就 会 出 发 DESTROY 方 法 。不 过 , 在 上 面 的 例 子 中 , 我 们 存 贮 了 第 二 个 指 向 捆 绑 到 $x 上 的 对 象 。 那 就 意 味 着 在 untie之 后 , 我 们 还 有 一 个 有 效 的 指 向 该 对 象 的 引 用 。 因 此 DESTROY 就 不 会 触 发 , 于 是 该 文 件就 得 不 到 输 出 冲 刷 并 且 关 闭 。 这 就 是 没 有 输 出 的 原 因 : 文 件 句 柄 的 缓 冲 区 仍 然 在 内 存 里 。 它在 程 序 退 出 之 前 不 会 存 储 到 磁 盘 上 。要 想 侦 测 到 这 些 东 西 , 你 可 以 用 -w 命 令 行 标 志 , 或 者 在 当 前 的 词 法 范 围 里 包 含 usewarnings "untie" 用 法 。 这 两 种 技 巧 都 等 效 于 在 仍 然 存 在 有 捆 绑 的 对 象 的 一 个 untie 调用 。 如 果 这 么 处 理 ,<strong>Perl</strong> 打 印 下 面 的 警 告 :untie attempted while 1 inner references still exist要 想 让 程 序 能 够 运 行 而 且 看 不 见 这 些 警 告 , 那 么 就 要 在 调 用 untie 之 前 删 除 任 何 多 余 的 指向 捆 绑 对 象 的 引 用 。 你 可 以 用 下 面 的 方 法 明 确 地 处 理 :undef $x;untie $fred;不 过 , 通 常 你 可 以 通 过 让 变 量 在 合 适 的 时 刻 跑 出 范 围 来 解 决 问 题 。398


14.6 CPAN 里 的 模 块在 你 开 始 鼓 足 干 劲 写 你 自 己 的 捆 绑 模 块 之 前 , 你 应 该 检 查 一 下 是 不 是 已 经 有 人 做 出 来 了 。 在CPAN 里 面 有 许 多 捆 绑 模 块 , 而 且 每 天 都 在 增 加 。( 哦 , 应 该 是 每 个 月 。) 表 14-1 列 出了 其 中 的 一 部 分 。表 14-1。 CPAN 的 捆 绑 模 块模 块描 述GnuPG ? ::Tie::Encrypt 把 一 个 文 件 句 柄 和 GNU Privacy Guard 加 密 捆 绑 在 一 起IO::WrapTie 把 捆 绑 对 象 和 一 个 IO:Handle 接 口 封 装 在 一 起 。MLDBMNet::NISplusTiedTie::Cache::LRUTie::ConstTie::CounterTie::CPHashTie::DB_FileLockTie::DBITie::DB_LockTie::DictTie::DirTie::DirHandleTie::FileLURCacheTie::FlipFlopTie::HashDefaultsTie::HashHistoryTie::IxHashTie::LDAPTie::PersistentTie::PickTie::RDBMTie::SecureHashTie::STDERR在 一 个 DBM 文 件 里 透 明 地 存 储 复 杂 的 数 据 值 , 而 不 仅 仅 是 简 单 的字 串 。把 散 列 和 NIS+ 表 捆 绑 在 一 起实 现 一 个 最 近 最 少 使 用 缓 冲提 供 常 数 标 量 和 散 列让 一 个 标 量 变 量 每 接 受 一 次 访 问 就 加 一实 现 一 个 保 留 大 小 写 但 是 又 大 小 写 无 关 的 散 列提 供 对 Berkeley DB 1.x 的 锁 定 访 问把 散 列 和 DBI 关 系 数 据 库 捆 绑 在 一 起用 共 享 和 排 它 锁 把 散 列 和 数 据 库 捆 绑 在 一 起把 一 个 散 列 和 一 个 RPC 字 典 服 务 器 捆 绑 在 一 起把 一 个 散 列 捆 绑 为 读 取 目 录捆 绑 目 录 句 柄实 现 一 个 轻 量 级 的 , 基 于 文 件 系 统 的 永 久 的 LRU 缓 冲实 现 一 个 在 两 个 值 之 间 接 换 的 捆 绑令 散 列 有 缺 省 值跟 踪 对 散 列 修 改 的 所 有 历 史为 <strong>Perl</strong> 提 供 有 序 的 关 联 数 组实 现 一 个 LDAP 数 据 库 的 接 口通 过 tie 实 现 一 个 永 久 数 据 结 构从 一 个 集 合 中 随 机 选 取 ( 和 删 除 ) 一 个 元 素把 散 列 和 关 系 数 据 库 捆 绑支 持 基 于 名 字 空 间 的 封 装把 你 的 STDERR 的 输 出 发 给 另 外 一 个 进 程 , 比 如 一 个 邮 件 服 务399


器Tie::SyslogTie::TextDirTie::TransactHashTie::VecArrayTie::Watch把 一 个 文 件 句 柄 自 动 捆 绑 到 syslog 作 为 其 输 出捆 绑 一 个 文 件 目 录在 事 务 中 编 辑 一 个 散 列 , 而 又 不 改 变 事 务 的 顺 序给 一 个 位 矢 量 提 供 一 个 数 组 接 口在 <strong>Perl</strong> 变 量 中 提 供 观 察 点Win32::TieRegistry 提 供 有 效 且 简 单 的 操 作 Microsoft Windows 注 册 表 的 方 法 。第 十 五 章 UNICODE如 果 你 还 不 知 道 什 么 是 Unicode, 那 么 你 很 快 就 会 知 道 了 —— 即 使 你 略 过 本 章 , 你也 会 知 道 —— 因 为 利 用 Unicode 工 作 日 益 成 为 必 须 。( 有 些 人 认 为 它 是 一 个 必 须 有 的 恶 魔 ,但 实 际 上 它 更 象 是 必 须 存 在 的 好 人 。 不 管 怎 样 , 它 都 是 必 须 有 的 痛 苦 。)从 历 史 来 看 , 人 们 制 定 字 符 集 来 反 映 在 他 们 自 己 的 文 化 环 境 里 所 需 要 处 理 的 事 物 。 因 为 所 有不 同 文 化 的 人 群 都 是 懒 惰 的 , 他 们 只 想 包 含 那 些 他 们 需 要 的 符 号 , 而 排 除 他 们 不 需 要 的 符 号 。如 果 我 们 只 和 自 己 文 化 内 部 的 其 他 人 进 行 交 流 , 那 么 这 些 符 号 是 够 用 的 , 但 是 既 然 我 们 开 始使 用 互 联 网 进 行 跨 文 化 的 交 流 , 那 么 我 们 就 会 因 为 原 先 排 除 的 符 号 而 头 疼 。 在 一 个 美 国 键 盘上 键 入 音 调 字 符 已 经 够 难 的 了 。 那 么 应 该 怎 样 才 能 写 一 个 多 语 言 的 网 页 能 ?Unicode 就 是 答 案 , 或 者 至 少 是 一 部 分 答 案 ( 又 见 XML)。Unicode 是 一 种 包 容 性 的 字符 集 , 而 不 是 排 斥 性 的 字 符 集 。 尽 管 人 们 仍 然 会 就 Unicode 的 各 种 细 节 争 论 不 休 ( 而 且 的确 还 有 许 多 可 以 争 吵 的 细 节 ), 但 是 Unicode 的 总 体 目 标 是 让 每 个 人 在 使 用 Unicode 的时 候 都 足 够 高 兴 ( 注 : 或 者 在 一 些 场 合 , 并 非 彻 底 失 望 。), 这 样 大 家 就 都 愿 意 把 Unicode当 作 交 换 文 本 数 据 的 国 际 媒 介 。 没 有 人 强 迫 你 使 用 Unicode, 就 好 象 没 有 人 强 迫 你 阅 读 本章 一 样 ( 我 们 希 望 如 此 )。 我 们 仍 将 允 许 人 们 在 他 们 自 己 的 文 化 里 使 用 他 们 原 来 的 排 它 的 字符 集 。 但 是 那 样 的 话 ( 如 我 们 所 说 ), 移 植 性 就 会 有 问 题 。痛 苦 守 恒 定 律 告 诉 我 们 , 如 果 我 们 在 一 个 地 方 减 少 痛 苦 , 那 么 在 另 外 的 地 方 肯 定 会 增 加 痛 苦 。在 Unicode 的 例 子 里 , 我 们 必 须 经 历 从 字 节 语 义 到 字 符 语 义 的 迁 移 的 痛 苦 。 因 为 由 于 历 史的 偶 然 性 ,<strong>Perl</strong> 是 美 国 人 发 明 的 , 而 <strong>Perl</strong> 在 历 史 上 就 混 淆 了 字 节 和 字 符 的 概 念 。 为 了 向Unicode 迁 移 ,<strong>Perl</strong> 必 须 在 某 种 程 度 上 分 清 这 两 个 概 念 。400


荒 谬 的 是 , 我 们 让 <strong>Perl</strong> 分 清 楚 了 字 符 和 字 节 的 关 系 , 而 却 又 允 许 <strong>Perl</strong> 程 序 员 混 淆 它 们 的概 念 , 而 依 靠 <strong>Perl</strong> 来 保 持 界 限 , 就 好 象 我 们 允 许 程 序 员 混 淆 数 字 和 字 串 而 是 依 靠 <strong>Perl</strong> 做必 要 的 转 换 。 要 扩 展 可 能 性 ,<strong>Perl</strong> 处 理 Unicode 的 方 法 和 处 理 其 他 东 西 的 方 法 是 一 样 的 :做 正 确 的 事 情 。 通 常 , 我 们 要 实 现 下 面 四 个 目 标 :目 标 1:旧 的 面 向 字 节 的 程 序 不 能 同 时 破 坏 它 们 原 来 用 来 运 行 的 旧 式 面 向 字 节 的 数 据 。目 标 2:旧 的 面 向 字 节 的 程 序 在 合 适 的 条 件 下 应 该 能 神 奇 地 开 始 在 新 的 面 向 字 符 的 数 据 上 运 行 。目 标 3:程 序 在 新 的 面 向 字 符 模 式 下 应 该 和 原 来 的 面 向 字 节 的 模 式 运 行 得 一 样 快 。目 标 4:<strong>Perl</strong> 应 该 仍 然 是 一 种 语 言 , 而 不 是 分 裂 成 一 种 面 向 字 节 的 <strong>Perl</strong> 和 一 种 面 向 字 符 的 <strong>Perl</strong>。把 它 们 合 在 一 起 , 这 些 目 标 实 际 上 是 不 可 能 实 现 的 。 不 过 我 们 已 经 非 常 接 近 了 。 或 者 , 换 句话 说 , 我 们 仍 然 在 向 非 常 接 近 目 标 努 力 , 因 为 这 是 一 项 正 在 进 行 的 工 作 。 随 着 Unicode 的不 断 发 展 ,<strong>Perl</strong> 也 在 不 断 发 展 。 但 是 我 们 的 主 要 目 标 是 提 供 一 条 安 全 的 迁 移 路 径 , 这 样 我们 在 迁 移 的 过 程 中 只 需 要 最 少 的 注 意 就 可 以 了 。 我 们 是 如 何 做 的 是 下 一 节 的 内 容 。15.1 制 作 字 符在 <strong>Perl</strong> 5.6 以 前 的 版 本 , 所 有 字 串 都 被 看 成 一 个 字 节 序 列 ( 注 : 你 可 能 愿 意 把 它 们 称 做 “ 八位 字 节 ”; 那 样 挺 好 , 不 过 我 们 认 为 如 此 这 两 个 词 几 乎 同 义 , 所 以 我 们 仍 然 使 用 那 个 蓝 领 阶层 的 叫 法 ) 不 过 ,<strong>Perl</strong> 5.6 以 后 的 版 本 里 , 一 个 字 串 可 能 包 含 一 些 比 一 个 字 节 宽 的 字 符 。我 们 现 在 不 把 字 串 看 作 一 个 字 节 序 列 , 而 是 一 个 数 字 序 列 , 这 些 数 字 是 处 于 0 .. 2**32-1( 或 者 在 64 位 机 上 是 0 .. 2**64-1) 之 间 。 这 些 数 字 代 表 抽 象 的 字 符 , 而 且 在 某 种 意义 上 数 字 越 大 , 字 符 越 宽 ; 不 过 和 许 多 语 言 不 同 的 是 ,<strong>Perl</strong> 没 有 捆 绑 在 任 何 特 定 宽 度 的 字符 形 式 上 。<strong>Perl</strong> 使 用 一 种 变 长 编 码 ( 基 于 UTF-8), 所 以 这 些 抽 象 的 数 据 可 能 能 够 , 也 可能 不 能 够 每 字 节 封 装 一 个 数 字 。 显 然 , 字 符 数 字 18,446,744,073,709,551,615 ( 也 就是 "\x{ffff_ffff_ffff_ffff}") 肯 定 不 能 放 在 一 个 字 节 里 ( 实 际 上 , 它 占 了 十 三 个 字 节 ), 但是 如 果 你 的 字 串 里 的 所 有 字 符 都 在 十 进 制 0..127 的 范 围 里 , 那 么 它 们 肯 定 可 以 包 在 一 个字 节 的 范 围 里 , 因 为 UTF-8 在 低 七 位 空 间 里 和 ASCII 是 一 样 的 。401


<strong>Perl</strong> 只 有 在 它 认 为 有 益 的 时 候 才 使 用 UTF-8, 所 以 如 果 你 的 字 串 里 的 所 有 字 符 都 落 在0..255 的 范 围 里 , 那 么 很 有 可 能 这 些 字 符 都 封 装 在 一 个 字 节 里 —— 但 是 如 果 缺 乏 其 他 知识 , 你 也 无 法 确 定 这 一 点 , 因 为 <strong>Perl</strong> 在 内 部 根 据 需 要 在 定 长 的 8 位 字 符 和 变 长 UTF-8 字符 之 间 进 行 转 换 。 关 键 是 你 大 多 数 时 间 不 需 要 关 心 这 些 内 容 , 因 为 这 里 的 字 符 语 意 是 一 种 不考 虑 表 现 的 抽 象 含 义 。不 论 什 么 情 况 下 , 如 果 你 的 字 串 包 含 任 意 大 于 十 进 制 255 的 数 字 , 那 么 该 字 串 肯 定 是 以UTF-8 的 形 式 存 储 的 。 更 准 确 地 说 , 它 是 以 <strong>Perl</strong> 扩 展 了 的 UTF-8 版 本 存 储 的 , 我 们 管它 叫 utf8, 一 方 面 是 尊 重 那 个 名 称 的 用 法 , 另 一 方 面 也 是 为 了 敲 击 简 单 。( 并 且 因 为 “ 真 正 ”的 UTF-8 只 允 许 包 含 Unicode 联 盟 确 认 的 字 符 数 字 。<strong>Perl</strong> 的 utf8 允 许 你 包 含 任 何 你需 要 的 字 符 数 字 。<strong>Perl</strong> 并 不 在 乎 你 的 字 符 数 字 是 官 方 认 可 的 还 是 你 认 可 的 。)我 们 说 过 你 在 大 部 分 时 候 都 不 用 关 心 这 些 , 但 是 人 们 还 是 愿 意 关 心 。 假 设 你 使 用 一 个 v 字串 代 表 一 个 IPv4 地 址 :$locaddr = v127.0.0.1;$oreilly = v204.148.40.9;$badaddr = v2004.148.40.9# 当 然 是 按 照 字 节 存 储# 可 能 以 字 节 或 者 以 utf8 方 式 存 储# 当 然 是 以 utf8 方 式 存 储每 个 人 都 明 白 $badaddr 不 是 一 个 IP 地 址 。 所 以 我 们 很 容 易 认 为 如 果 O'Reilly 的 网 络地 址 被 强 制 表 示 成 UTF-8 方 式 , 那 么 它 就 无 法 运 行 了 。 但 是 在 字 串 里 的 字 符 都 是 抽 象 数 字 ,而 不 是 字 节 。 任 何 使 用 IPv4 地 址 的 东 西 , 比 如 gethostbyaddr 函 数 , 都 自 动 把 抽 象 字符 数 字 转 换 成 一 个 字 节 形 式 ( 并 且 对 $badaddr 会 失 效 )。<strong>Perl</strong> 和 真 实 世 界 之 间 的 接 口 需 要 处 理 表 现 形 式 的 细 节 。 现 有 的 接 口 在 最 大 的 可 能 下 都 可 以不 用 你 告 诉 它 们 如 何 处 理 而 做 出 正 确 的 动 作 。 但 是 的 确 有 偶 尔 的 机 会 需 要 你 给 一 些 接 口 以 某种 指 导 ( 比 如 open 函 数 ), 而 且 如 果 你 写 你 自 己 的 与 现 实 世 界 的 接 口 , 那 么 这 个 接 口 要么 是 聪 明 得 能 够 自 己 分 辨 事 物 , 要 么 至 少 是 能 够 在 非 缺 省 特 性 的 时 候 能 够 遵 循 指 导 。( 注 :在 一 些 系 统 上 可 能 存 在 一 次 性 切 换 你 的 所 有 接 口 的 方 法 。 如 果 使 用 了 -C 命 令 行 开 关 ,( 或者 全 局 的 ${@WIDE_SYSTEM_CALLS} 变 量 设 置 为 1, 那 么 所 有 系 统 调 用 都 会 使 用 对应 的 宽 字 符 API。( 这 个 特 性 目 前 只 在 Microsoft Windows 上 实 现 。) Linux 社 区 目前 的 计 划 是 如 果 $ENV{LC_CTYPE} 设 置 为 “UTF-8”, 那 么 所 有 接 口 都 切 换 到 UTF-8 模式 。 其 他 的 社 区 可 能 采 纳 其 他 方 法 。 我 们 的 进 展 也 可 能 变 化 。)因 为 <strong>Perl</strong> 要 关 心 在 它 自 己 内 部 维 持 透 明 的 字 符 语 意 , 所 以 你 需 要 关 心 字 节 与 字 符 语 意 的 区别 的 唯 一 的 地 方 就 是 你 的 接 口 。 缺 省 时 , 你 所 有 旧 的 连 接 外 部 世 界 的 <strong>Perl</strong> 接 口 都 是 面 向 字节 的 , 因 此 它 们 生 成 和 处 理 面 向 字 节 的 数 据 。 也 就 是 说 , 在 这 个 抽 象 层 , 你 的 所 有 字 串 都 是范 围 0..255 的 数 字 序 列 , 所 以 如 果 在 程 序 里 没 有 什 么 东 西 强 迫 它 们 表 示 成 utf8, 你 的 旧402


的 程 序 就 继 续 以 面 向 字 节 的 数 据 为 处 理 对 象 , 就 象 它 们 原 来 的 样 子 。 因 此 可 以 在 上 面 的 目 标1 上 画 个 勾 。如 果 你 希 望 你 的 旧 程 序 可 以 处 理 新 的 面 向 字 符 的 数 据 , 那 么 你 必 须 设 法 标 记 你 的 面 向 字 符 的接 口 , 这 样 <strong>Perl</strong> 就 明 白 在 那 些 接 口 上 准 备 处 理 面 向 字 符 的 数 据 。 一 旦 你 完 成 这 些 工 作 ,<strong>Perl</strong> 就 会 自 动 做 任 何 必 须 的 转 换 工 作 以 保 持 字 符 的 抽 象 性 。 唯 一 的 区 别 是 你 已 经 引 入 了 一些 字 串 到 你 的 程 序 里 面 , 这 些 字 串 标 记 为 可 能 包 含 超 过 255 的 字 符 , 因 此 , 如 果 你 在 字 串和 utf8 字 串 之 间 进 行 操 作 , 那 么 <strong>Perl</strong> 将 在 内 部 先 把 字 节 字 串 转 换 成 utf8 字 串 , 然 后 再执 行 操 作 。 通 常 , 只 有 你 又 把 它 们 发 送 回 给 一 个 字 节 接 口 的 时 候 ,utf8 字 串 才 转 换 回 字 节字 串 , 这 个 时 候 , 如 果 字 串 包 含 大 于 255 的 字 符 , 那 么 你 就 会 有 一 个 问 题 , 这 个 问 题 可 以用 多 种 不 同 的 方 法 来 处 理 —— 取 决 于 那 个 有 问 题 的 接 口 。 因 此 你 可 以 在 目 标 2 上 也 画 一 个勾 。有 时 候 你 想 把 理 解 字 符 语 意 的 编 码 和 那 些 必 须 以 字 节 语 意 运 行 的 编 码 , 比 如 读 取 或 者 写 出 固定 大 小 的 块 的 I/O 编 码 , 混 合 起 来 使 用 。 这 种 情 况 下 , 你 可 以 在 面 向 字 节 的 编 码 周 围 放 一个 use bytes 声 明 以 强 制 它 使 用 字 节 语 意 —— 甚 至 是 在 那 些 标 记 为 utf8 的 字 串 上 。 然 后你 就 要 对 任 何 必 须 的 转 换 负 责 。 不 过 这 是 一 个 强 化 目 标 1 的 更 严 格 的 本 地 读 取 , 代 价 放 松目 标 2 的 全 局 读 取 。目 标 3 大 部 分 都 实 现 了 , 原 因 部 分 是 通 过 做 字 节 和 utf8 表 示 形 式 之 间 的 懒 惰 的 转 换 另 一部 分 是 因 为 我 们 在 实 现 Unicode 里 的 那 些 比 较 慢 的 特 性 ( 比 如 大 表 里 的 属 性 查 找 ) 的 时 候做 得 比 较 隐 蔽 。我 们 通 过 牺 牲 一 小 部 分 实 现 其 他 目 标 的 接 口 的 兼 容 性 实 现 了 目 标 4。 从 某 个 角 度 来 看 , 我们 没 有 把 <strong>Perl</strong> 分 裂 成 不 同 的 两 种 <strong>Perl</strong>; 但 是 以 另 外 一 种 方 法 来 看 , 版 本 5.6 的 <strong>Perl</strong> 是一 种 分 裂 了 的 版 本 , 只 是 它 仍 然 尊 重 以 前 的 版 本 , 而 且 我 们 认 为 人 们 在 确 信 新 版 本 能 够 处 理他 们 的 事 物 之 前 不 会 从 更 早 的 版 本 切 换 到 新 的 版 本 。 不 过 新 版 本 总 是 这 个 样 子 的 , 所 以 我 们允 许 自 己 在 目 标 4 上 也 打 个 勾 。15.2 字 符 语 意 的 效 果字 符 语 意 的 结 果 是 一 个 典 型 的 内 建 操 作 符 将 以 字 符 为 操 作 对 象 , 除 非 它 位 于 use bytes 用法 里 。 不 过 , 即 使 在 use bytes 用 法 外 面 , 如 果 该 操 作 符 的 所 有 操 作 数 都 以 8 位 字 符 的方 式 存 储 ( 也 就 是 说 , 没 有 操 作 符 以 utf8 方 式 存 储 ), 那 么 字 符 语 意 就 和 字 节 语 意 没 有 区别 , 并 且 操 作 符 的 结 果 将 以 8 位 的 方 式 存 储 在 内 部 。 这 样 , 只 要 你 的 程 序 不 使 用 比 Latin-1更 宽 的 字 符 , 那 么 这 个 程 序 就 保 留 的 向 后 的 兼 容 性 。403


utf8 用 法 主 要 是 一 个 兼 容 性 设 备 , 它 打 开 分 析 器 对 UTF-8 文 本 和 标 识 的 识 别 。 它 还 可 以用 于 打 开 一 些 更 是 处 于 实 验 阶 段 的 Unicode 支 持 特 性 。 我 们 的 远 期 目 标 是 把 utf8 用 法 变成 透 明 层 (no-op)。use bytes 用 法 可 能 永 远 不 会 成 为 透 明 层 。 它 不 仅 对 面 向 字 节 的 编 码 是 必 要 的 , 而 且 在 一个 函 数 上 定 义 面 向 字 节 的 封 装 在 use bytes 范 围 外 面 使 用 还 会 有 副 作 用 。 在 我 们 写 这 些 的时 候 , 唯 一 定 义 了 的 封 装 是 length, 不 过 随 着 时 间 的 推 移 , 会 有 更 多 封 装 出 现 的 。 要 使 用这 样 的 封 装 , 你 可 以 说 :use bytes(); # 不 加 输 入 byte 语 意 地 装 载 封 装 器 ... $charlen =length("\x{ffff_ffff}"); # 返 回 1 $bytelen = byte::length("\x{ffff_ffff}"); # 返回 7在 use bytes 声 明 外 边 ,<strong>Perl</strong> 版 本 5.6 的 运 行 特 性 ( 或 者 至 少 其 期 望 的 运 行 特 性 ) 是 这样 的 :• 字 串 和 模 式 现 在 可 以 包 含 值 大 于 255 的 字 符 ;use utf8;$convergence = " ";假 设 你 有 一 个 可 以 编 辑 Unicode 的 编 辑 器 编 辑 你 的 程 序 , 这 样 的 字 符 通 常 会 在 文 本 字 串中 直 接 以 UTF-8 字 符 的 方 式 出 现 。 从 现 在 开 始 , 你 必 须 在 你 的 程 序 开 头 开 头 声 明 一 个use utf8 以 便 允 许 文 本 中 使 用 UTF-8。如 果 你 没 有 Unicode 编 辑 器 , 那 么 你 还 是 可 以 用 \x 表 示 法 声 明 特 定 的 ASCII 码 扩 展 。Latin-1 范 围 的 字 符 可 以 以 \x{ab} 的 形 式 或 者 \xab 的 形 式 写 , 但 是 如 果 数 字 超 过 两位 十 六 进 制 数 字 , 那 你 就 必 须 使 用 花 括 号 。 你 可 以 用 \x 后 面 跟 着 花 括 号 括 起 来 的 十 六 进制 编 码 来 表 示 Unicode。 比 如 一 个 Unicode 笑 脸 符 号 是 \x{263A}。 在 <strong>Perl</strong> 里 没 有 什么 语 法 构 造 假 设 Unicode 字 符 正 好 就 是 16 位 , 所 以 你 不 能 用 其 他 语 言 那 样 的 \u263A来 表 示 ;\x{263A} 是 最 相 近 的 等 价 物 。• 在 <strong>Perl</strong> 脚 本 里 的 标 识 符 可 以 包 含 Unicode 字 母 数 字 字 符 , 包 括 象 形 文 字 :use utf8;$ 人 ++ # 又 生 了 一 个 孩 子同 样 , 你 需 要 use utf8 ( 至 少 目 前 ) 才 能 识 别 你 的 脚 本 中 的 UTF-8。 目 前 , 如 果 需 要使 用 规 范 化 的 字 符 形 式 , 你 得 自 己 决 定 ——<strong>Perl</strong> ( 还 ) 不 会 试 图 帮 助 你 规 范 化 变 量 名 。 我404


们 建 议 你 把 你 的 程 序 规 范 化 为 正 常 C 模 式 (Normalization Form C), 因 为 这 种 形 式 是有 朝 一 日 可 能 是 <strong>Perl</strong> 的 缺 省 规 范 化 形 式 。 参 阅 www.unicode.org 获 取 最 新 的 有 关 规 范化 的 技 术 报 告 。• 正 则 表 达 式 现 在 匹 配 字 符 , 而 不 是 字 节 。 比 如 , 点 匹 配 一 个 字 符 而 不 是 一 个 字 节 。 如果 Unicode 协 会 准 备 批 准 Tengwar 语 言 , 那 么 ( 尽 管 这 样 的 字 符 在 UTF-8 里 用四 个 字 节 表 示 ), 但 下 面 的 东 西 是 匹 配 的 :"\N{TENGWAR LETTER SILME NUQUERNA}" =~ /^.$/\C 模 式 用 于 强 制 一 次 匹 配 是 对 一 个 字 节 的 (C 里 的 “char”, 因 此 是 \C)。 用\C 的 时 候 要 小 心 , 因 为 它 会 令 你 和 你 的 字 串 的 字 符 边 界 不 同 步 , 而 且 你 可 能 会收 到 “Malformed UTF-8 character” 错 误 。 你 不 能 在 方 括 号 里 使 用 \C, 因 为它 不 代 表 任 何 特 定 的 字 符 或 者 字 符 集 。• 在 正 则 表 达 式 里 的 字 符 表 匹 配 字 符 而 不 是 字 节 , 并 且 匹 配 那 些 在 Unicode 属 性数 据 库 里 声 明 的 字 符 属 性 。 因 此 可 以 把 \w 用 于 匹 配 一 个 象 形 文 字 :" 人 " =~ /\w/• 可 以 用 新 的 \p ( 匹 配 属 性 ) 和 \P( 不 匹 配 属 性 ) 构 造 , 把 命 名 Unicode 属 性 和块 范 围 用 做 字 符 表 。 比 如 ,\p{Lu} 匹 配 任 何 有 Unicode 大 写 字 符 属 性 的 字 符 , 而\p{M} 匹 配 任 何 标 记 字 符 。 但 字 母 属 性 可 以 忽 略 花 括 号 , 因 此 标 记 字 符 也 可 以 用 \pM匹 配 。 还 有 许 多 预 定 义 的 字 符 表 可 以 用 , 比 如 \p{IsMirrored} 和 \p{InTibetan}:"\N{greek:Iota}" =~ /\p{Lu}/你 还 可 以 在 放 括 号 字 符 表 里 使 用 \p 和 \P。( 在 版 本 5.6 的 <strong>Perl</strong> 里 , 你 需 要 使 用 useutf8 才 能 令 字 符 属 性 正 确 工 作 。 不 过 这 个 限 制 在 将 来 会 消 失 。) 参 阅 第 五 章 , 模 式 匹 配 ,获 取 匹 配 Unicode 属 性 的 细 节 。• 特 殊 的 模 式 \X 匹 配 任 何 扩 展 的 Unicode 序 列 (Unicode 标 准 中 的 “ 组 合 字 符 序列 ”), 这 时 候 , 第 一 个 字 符 是 基 础 字 符 , 而 随 后 的 字 符 是 标 记 字 符 , 这 些 标 记 字 符 附加 在 基 础 字 符 上 。 它 等 效 于 (?:\PM\pM*):405


"o\N{COMBING TILDE BELOW}" =~ /\X/你 不 能 在 方 括 号 里 使 用 \X, 因 为 它 可 以 匹 配 多 个 字 符 , 而 且 它 不 匹 配 任 何 特 定 的 字 符 或者 字 符 集 。• r/// 操 作 符 转 换 字 符 , 而 不 是 转 换 字 节 。 要 把 所 有 Latin-1 范 围 以 外 的 字 符 变 成一 个 问 号 , 你 可 以 说 :tr/\0-\x{10ffff}/\0-\xff?/; # utf8 到 latin1 字 符• 如 果 有 字 符 输 入 , 那 么 大 小 写 转 换 操 作 使 用 Unicode 的 大 小 写 转 换 表 。 请 注 意 uc转 换 成 大 写 , 而 ucfirst 转 换 成 抬 头 体 ( 对 那 些 区 分 这 些 的 语 言 而 言 )。 通 常 对 应 的 反斜 杠 序 列 有 着 相 同 的 语 意 :$x = "\u$word"; # 把 $word 的 第 一 个 字 母 改 成 抬 头 体$x = "\U$word"; # 大 写 $word$x = "\l$word"; # 小 写 $word 的 第 一 个 字 母$x = "L$word"; # 小 写 $word需 要 小 心 的 是 ,Unicode 的 大 小 写 转 换 表 并 不 准 备 给 每 种 实 例 都 提 供 循 环 映 射 , 尤 其 是 那些 大 写 或 者 抬 头 体 的 字 符 数 和 对 应 小 写 的 字 符 数 不 同 的 语 言 。 正 如 Unicode 协 会 的 人 所说 , 尽 管 大 小 写 属 性 本 身 是 标 准 的 , 大 小 写 映 射 却 只 是 报 告 性 的 。• 大 多 数 处 理 字 串 内 的 位 置 或 者 长 度 的 操 作 符 将 自 动 切 换 成 使 用 字 符 位 置 , 包 括chop,substr,pos,index,rindex,sprintf,write, 和 length。 有 意 不 做 切 换的 操 作 符 包 括 vec,pack, 和 unpack。 不 在 意 这 些 东 西 的 操 作 符 包 括 chomp, 以 及任 何 其 他 的 把 字 串 当 作 一 堆 二 进 制 位 的 操 作 , 比 如 缺 省 的 sort 和 处 理 文 件 名 的 操 作符 。use bytes;$bytelen = length("I do 合 气 道 .");# 15 字 节no bytes;$charlen = length("I do 合 气 道 .");# 只 有 9 字 符406


• pack/unpack 字 符 “c” 和 “C” 不 改 变 , 因 为 它 们 常 用 于 面 向 字 节 的 格 式 。( 同 样 ,类 似 C 语 言 里 的 “char”。) 不 过 , 现 在 有 了 一 个 新 的 “U” 修 饰 词 可 以 在 UTF-8 字 符 和整 数 之 间 做 转 换 :pack("U*", 1, 20, 300, 4000) eq v1.20.300.4000• chr 和 ord 函 数 处 理 字 符 :chr(1).chr(20).chr(300).chr(400) eq v1.20.300.4000换 句 话 说 ,chr 和 ord 类 似 pack("U") 和 unpack("U"), 而 不 是 pack("C") 和unpack("C")。 实 际 上 , 后 面 两 个 语 句 就 是 你 懒 得 不 想 详 use bytes 的 时 候 模 拟 字 节 的chr 和 ord 的 方 法 。• 最 后 ,scalar reverse 倒 转 的 是 字 符 , 而 不 是 字 节 : 。。。( 略 )如 果 你 看 看 目 录 PATH——TOPERLLIB/unicode, 你 就 会 找 到 许 多 定 义 上 面 语 意 需 要 的 文件 。 Unicode 协 会 规 定 的 Unicode 属 性 数 据 库 放 在 文 件 Unicode.300( 用 于 Unicode3.0)。 这 个 文 件 已 经 用 mktables.PL 处 理 成 同 目 录 下 的 许 多 小 .pl 文 件 了 ( 以 及 子 目 录Is/, In/, 和 To/), 这 些 文 件 或 目 录 中 的 一 部 分 会 被 <strong>Perl</strong> 自 动 装 载 用 以 实 现 诸 如 \p( 参阅 Is/ 和 In/ 目 录 ) 和 uc ( 参 阅 To/ 目 录 ) 这 样 的 东 西 。 其 他 的 文 件 由 模 块 装 载 , 比如 use charname 用 法 ( 参 阅 Name.pl)。 不 过 到 我 们 写 这 些 为 止 , 还 有 一 些 文 件 只 是放 在 那 里 , 等 着 你 给 它 们 写 一 个 访 问 模 块 :ArabLink.plArabLnkGrp.plBidirectional.plBlock.plCategory.plCombiningClass.plDecomposition.plJamoShort.plNumber.plTo/Digit.pl407


一 个 可 读 性 更 好 的 Unicode 的 概 述 以 及 许 多 超 级 链 接 都 放 在PATH_TO_PERLLIB/unicode/Unicode3.html。请 注 意 , 如 果 Unicode 协 会 制 定 了 新 的 版 本 , 那 么 这 些 文 件 中 的 一 部 分 的 文 件 名 可 能 会变 化 , 因 此 你 就 必 须 四 处 刺 探 。 你 可 以 用 下 面 的 “ 咒 语 ” 找 出 PATH_TO_PERLLIB:%perl -MConfig -le 'print $config{Privlib}'如 果 想 找 到 现 有 所 有 的 关 于 Unicode 东 西 , 你 应 该 看 看 Unicode 标 准 , 版 本 3.0(ISBN 0-201-61633-5)。• 请 注 意 ,“ 人 (Unicode)” 可 以 用 了 在 我 们 写 到 这 些 的 时 候 ( 也 就 是 说 , 对 于 版 本 5.6的 <strong>Perl</strong>), 使 用 Unicode 上 仍 然 有 一 些 注 意 事 项 。( 请 检 查 你 的 在 线 文 档 获 取 最 新信 息 。)• 目 前 的 正 则 表 达 式 编 译 器 不 生 成 多 形 的 操 作 码 。 这 就 意 味 着 在 编 译 模 式 的 时 候 就 要判 断 某 个 模 式 是 否 匹 配 Unicode 字 符 ( 基 于 该 模 式 是 否 包 含 Unicode 字 符 ) 而 不 是在 匹 配 该 模 式 的 运 行 的 时 候 。 这 方 面 需 要 改 进 成 只 有 待 匹 配 的 字 串 是 Unicode 才 相 应需 要 匹 配 Unicode。• 目 前 没 有 很 简 单 的 方 法 标 记 从 一 个 文 件 或 者 其 他 外 部 数 据 源 读 取 的 数 据 是 utf8。 这方 面 将 是 近 期 注 意 的 主 要 方 面 , 并 且 在 你 读 取 这 些 的 时 候 可 能 已 经 搞 定 了 。• 我 们 没 有 办 法 把 输 入 和 输 出 转 换 成 除 UTF-8 以 外 的 编 码 方 式 。 不 过 我 们 准 备 在 最近 做 这 些 事 情 , 请 检 查 你 的 在 线 文 档 。• 把 本 地 化 设 置 和 utf8 一 起 使 用 会 导 致 奇 怪 的 结 果 。 目 前 , 我 们 准 备 把 8 位 的 区域 信 息 用 于 范 围 在 0..255 的 字 符 , 不 过 我 们 完 全 可 以 证 明 这 样 做 对 那 些 使 用 超 过 上 面范 围 的 本 地 化 设 置 是 不 正 确 的 ( 当 映 射 成 Unicode 的 时 候 )。 而 且 这 样 做 还 会 运 行 得慢 一 些 。 我 们 强 烈 建 议 避 免 区 域 设 置 。Unicode 很 好 玩 —— 但 是 你 得 正 确 地 定 义 好 玩 的 东 西 。408


第 十 六 章 , 进 程 间 通 讯计 算 机 进 程 之 间 几 乎 有 和 人 与 人 之 间 一 样 多 的 交 流 。 我 们 不 应 低 估 进 程 间 通 讯 的 难 度 。如 果 你 的 朋 友 只 使 用 形 体 语 言 , 那 么 你 光 注 意 语 言 暗 示 对 你 是 一 点 用 都 没 有 。 同 样 , 两 个 进程 之 间 只 有 达 成 了 通 讯 的 方 法 以 及 建 筑 在 该 方 法 之 上 的 习 惯 的 共 识 以 后 才 能 通 讯 。 和 任 何 通讯 一 样 , 这 些 需 要 达 成 共 识 的 习 惯 的 范 围 从 词 法 到 实 际 用 法 : 几 乎 是 从 用 什 么 方 言 到 说 话 的顺 序 的 一 切 东 西 。 这 些 习 惯 是 非 常 重 要 的 , 因 为 我 们 都 知 道 如 果 光 有 语 义 而 没 有 环 境 ( 上 下文 ), 通 讯 起 来 是 非 常 困 难 的 。在 我 们 的 方 言 里 , 进 程 间 通 讯 通 常 念 做 IPC。<strong>Perl</strong> 的 IPC 设 施 的 范 围 从 极 为 简 单 到 极 为复 杂 。 你 需 要 用 哪 种 设 施 取 决 于 你 要 交 流 的 信 息 的 复 杂 度 。 最 简 单 的 信 息 几 乎 就 是 没 有 信 息 :只 是 对 某 个 时 间 点 发 生 了 某 个 事 件 的 知 晓 。 在 <strong>Perl</strong> 里 , 这 样 的 事 件 是 通 过 模 拟 Unix 信号 系 统 的 信 号 机 制 实 现 的 。在 另 外 一 个 极 端 ,<strong>Perl</strong> 的 套 接 字 设 施 允 许 你 与 在 互 联 网 上 的 另 外 一 个 进 程 以 任 何 你 们 同 时都 支 持 的 协 议 进 行 通 讯 。 自 然 , 自 由 是 有 代 价 的 : 你 必 须 通 过 许 多 步 骤 来 设 置 连 接 并 且 还 要确 保 你 和 那 头 的 进 程 用 的 是 同 样 的 语 言 。 这 样 做 的 结 果 就 是 要 求 你 需 要 坚 持 许 多 其 他 奇 怪 的习 惯 。 更 准 确 地 说 , 甚 至 还 要 求 你 用 象 XML,Java, 或 <strong>Perl</strong> 这 样 的 语 言 讲 话 。 很 恐 怖 。上 面 两 个 极 端 的 中 间 的 东 西 是 一 些 主 要 用 于 在 同 一 台 机 器 上 的 进 程 之 间 进 行 通 讯 的 设 施 。 包括 老 派 的 文 件 , 管 道 ,FIFO, 和 各 种 Sys V IPC 系 统 调 用 。 对 这 些 设 施 的 支 持 因 平 台 的不 同 而 有 所 差 异 ; 现 代 的 Unix 系 统 ( 包 括 苹 果 的 Mac OS X) 支 持 上 面 的 所 有 设 施 , 但是 , 除 信 号 和 Sys V IPC 以 外 ,Microsoft 操 作 系 统 支 持 剩 下 的 所 有 的 , 包 括 管 道 , 进 程分 裂 , 文 件 锁 和 套 接 字 。( 注 : 除 了 AF_UNIX 套 接 字 )。关 于 移 植 的 更 多 的 一 般 性 信 息 可 以 在 标 准 的 <strong>Perl</strong> 文 档 集 中 找 到 ( 不 管 你 的 系 统 里 是 什 么格 式 ), 他 们 在 perlport 里 。 与 Microsoft 相 关 的 信 息 可 以 在 perlwin32 和 perlfork里 找 到 , 即 使 在 非 Microsoft 的 系 统 里 都 安 装 了 它 们 。 相 关 的 书 籍 , 我 们 介 绍 下 面 的 :1. The <strong>Perl</strong> Cookbook, Tom Christiansen 和 Nathan Torkington (O'Reillyand Associates,1998), 第 十 六 到 十 八 章 。2. Advanced Programming in the UNIX Environment, W. Richard Stevens(Addison-Wesley,1992)3. TCP/IP Illustrated, W. Richard Stevens, 卷 I-III (Addison-Wesley,1992-1996)16.1 信 号409


<strong>Perl</strong> 使 用 一 种 简 单 的 信 号 处 理 模 型 : 在 %SIG 散 列 里 包 含 指 向 用 户 定 义 信 号 句 柄 的 引 用( 符 号 或 者 硬 引 用 )。 某 些 事 件 促 使 操 作 系 统 发 送 一 个 信 号 给 相 关 的 进 程 。 这 时 候 对 应 该 事件 的 句 柄 就 会 被 调 用 , 给 该 句 柄 的 参 数 中 就 有 一 个 包 含 触 发 它 的 信 号 名 字 。 要 想 给 另 外 一 个进 程 发 送 一 个 信 号 , 你 可 以 用 kill 函 数 。 把 这 个 过 程 想 象 成 是 一 个 给 其 他 进 程 发 送 一 个 二进 制 位 信 息 的 动 作 。( 注 : 实 际 上 , 更 有 可 能 是 五 或 者 六 个 二 进 制 位 , 取 决 于 你 的 OS 定义 的 信 号 数 目 以 及 其 他 进 程 是 否 利 用 了 你 不 发 送 别 的 信 号 的 这 个 情 况 。)。 如 果 另 外 一 个 进程 安 装 了 一 个 处 理 该 信 号 的 信 号 句 柄 , 那 么 如 果 收 到 该 信 号 , 它 就 能 够 执 行 代 码 。 不 过 发 送进 程 没 有 任 何 办 法 获 取 任 何 形 式 的 返 回 , 它 只 能 知 道 该 信 号 已 经 合 法 发 送 出 去 了 。 发 送 者 也接 收 不 到 任 何 接 收 进 程 对 该 信 号 做 的 处 理 的 信 息 。我 们 把 这 个 设 施 归 类 为 IPC 的 一 种 , 但 实 际 上 信 号 可 以 来 自 许 多 源 头 , 而 不 仅 仅 是 其 他 进程 。 一 个 信 号 也 可 能 来 自 你 自 己 的 进 程 , 或 者 是 用 户 在 键 盘 上 敲 入 了 某 种 特 定 键 盘 序 列 , 比如 Control-C 或 者 Control-Z 造 成 的 , 也 可 能 是 内 核 在 处 理 某 些 特 殊 事 件 的 时 候 产 生的 , 比 如 子 进 程 退 出 或 者 你 的 进 程 用 光 堆 栈 或 者 达 到 了 文 件 尺 寸 或 内 存 的 极 限 等 。 不 过 你 自己 的 进 程 可 以 很 容 易 区 别 这 些 场 合 。 信 号 就 好 象 一 个 送 到 你 家 门 口 的 没 有 返 回 地 址 的 神 秘 包裹 。 你 打 开 的 时 候 最 好 小 心 一 点 。因 为 在 %SIG 里 的 记 录 可 能 是 硬 链 接 , 所 以 通 常 把 匿 名 函 数 用 做 信 号 句 柄 :$SIG{INT} = sub {die "\nOutta here1\n"};$SIG{ALRM} = sub { die "Your alarm clock went off" };或 者 你 可 以 创 建 一 个 命 名 函 数 , 并 且 把 它 的 名 字 或 者 引 用 放 在 散 列 里 的 合 适 的 槽 位 里 。 比 如 ,要 截 获 中 断 和 退 出 信 号 ( 通 常 和 你 的 键 盘 的 Control-C 和 Control-\ 绑 在 一 起 ), 你 可以 这 样 设 置 句 柄 :sub catch_zap {my $signame = shift;our $shucks++;die "Somebody sent me a SIG$signame!";}$shucks = 0;$SIG{INT} = 'catch_zap';$SIG{INT} = \&catch_zap;# 意 思 总 是 &main::catch_zap# 最 好 的 方 法410


$SIG{QUIT} = \&catch_zap;# 把 另 外 一 个 信 号 也 捕 获 上注 意 , 我 们 在 信 号 句 柄 里 做 的 所 有 事 情 就 是 设 置 一 个 全 局 变 量 然 后 用 die 抛 出 一 个 例 外 。如 果 可 能 , 请 力 争 避 免 处 理 比 这 更 复 杂 的 东 西 , 因 为 在 大 多 数 系 统 上 ,C 库 都 是 不 可 再 入的 。 信 号 是 异 步 传 送 的 ( 注 : 与 <strong>Perl</strong> 层 操 作 码 同 步 的 信 号 传 递 安 排 在 以 后 的 版 本 发 布 , 那样 应 该 能 解 决 信 号 和 核 心 转 储 的 问 题 。), 所 以 , 如 果 信 号 传 递 后 你 已 经 在 一 个 相 关 的 C 库过 程 里 面 了 , 那 么 调 用 任 何 print 函 数 ( 或 者 只 是 任 何 需 要 malloc(3) 分 配 更 多 内 存 的函 数 ) 在 理 论 上 都 可 能 触 发 内 存 错 误 并 导 致 内 核 转 储 。( 甚 至 可 能 die 过 程 也 有 点 有 点 不安 全 —— 除 非 该 进 程 是 在 一 个 eval 里 执 行 的 , 因 为 那 样 会 消 除 来 自 die 的 I/O, 于 是 就让 它 无 法 调 用 C 库 。)一 个 更 简 单 的 捕 获 信 号 的 方 法 是 使 用 sigtrap 用 法 安 装 简 单 的 缺 省 信 号 句 柄 :use sigtrap qw(die INT QUIT);use sigtrap qw(die untrapped normal-signals stack-trace any error-signals);如 果 你 嫌 写 自 己 的 句 柄 麻 烦 , 那 就 可 以 用 这 个 用 法 , 不 过 你 仍 然 会 希 望 捕 获 危 险 的 信 号 并 且执 行 一 个 正 常 的 关 闭 动 作 。 缺 省 时 , 这 些 信 号 中 的 一 部 分 对 你 的 进 程 是 致 命 的 , 当 的 程 序 收到 这 样 的 信 号 时 只 能 停 止 。 糟 糕 的 是 , 这 也 意 味 着 不 会 调 用 任 何 用 做 退 出 控 制 的 END 函数 和 用 于 对 象 终 止 的 DESTROY 方 法 。 但 是 它 们 在 正 常 的 <strong>Perl</strong> 例 外 中 的 确 是 被 调 用 的( 比 如 你 调 用 die 的 时 候 ), 所 以 , 你 可 以 用 这 个 用 法 无 痛 地 把 信 号 转 换 成 例 外 。 甚 至 在你 没 有 自 己 处 理 这 些 信 号 的 情 况 下 , 你 的 程 序 仍 然 能 够 表 现 正 确 。 参 阅 第 三 十 一 章 , 用 法 模块 , 里 use sigtrap 的 描 述 获 取 这 个 用 法 的 更 详 细 的 特 性 。你 还 可 以 把 %SIG 句 柄 设 置 为 字 串 “IGNORE” 或 者 “DEFAULT”, 这 样 ,<strong>Perl</strong> 就 会 试 图 丢弃 该 信 号 或 者 允 许 用 缺 省 动 作 处 理 该 信 号 ( 不 过 有 些 信 号 既 不 能 捕 获 , 也 不 能 忽 略 , 比 如KILL 和 STOP 信 号 ; 如 果 手 边 有 资 料 , 你 可 以 参 阅 signal(3), 看 看 你 的 系 统 可 以 用 的信 号 列 表 和 它 们 的 缺 省 行 为 。)操 作 系 统 认 为 信 号 是 一 个 数 字 , 而 不 是 一 个 名 字 , 但 是 <strong>Perl</strong> 和 大 多 数 人 一 样 , 喜 好 符 号 名字 , 而 讨 厌 神 秘 的 数 字 。 如 果 想 找 出 信 号 的 名 字 , 你 可 以 把 %SIG 散 列 里 的 键 字 都 列 出 来 ,或 者 如 果 你 的 系 统 里 有 kill 命 令 , 你 可 以 用 kill -l 把 它 们 列 出 来 。 你 还 可 以 使 用 <strong>Perl</strong> 标准 的 Config 模 块 来 检 查 你 的 操 作 系 统 的 信 号 名 字 和 信 号 数 字 之 间 的 映 射 。 参 阅 Config(3)获 取 例 子 。因 为 %SIG 是 一 个 全 局 的 散 列 , 所 以 给 它 赋 值 将 影 响 你 的 整 个 程 序 。 如 果 你 把 信 号 捕 获 局限 于 某 个 范 围 , 可 能 对 你 的 程 序 的 其 他 部 分 更 有 好 处 。 实 现 这 个 目 的 的 方 法 是 用 一 个 local信 号 句 柄 赋 值 , 这 样 , 一 旦 退 出 了 闭 合 的 语 句 块 , 那 么 该 句 柄 就 失 去 作 用 了 。( 但 是 要 记 住 ,local 变 量 对 那 些 语 句 块 中 调 用 的 函 数 是 可 见 的 。)411


{local $SIG{INT} = 'IGNORE';... # 处 理 你 自 己 的 业 务 , 忽 略 所 有 的 信 号fn(); # 在 fn() 里 也 忽 略 信 号 !... # 这 里 也 忽 略 。} # 语 句 块 退 出 后 恢 复 原 来 的 $SIG{INT} 值 。fn();# 在 ( 假 设 的 ) fn() 里 没 有 忽 略 SIGINT16.1.1 给 进 程 组 发 信 号( 至 少 在 Unix 里 ,) 进 程 是 组 织 成 进 程 组 的 , 一 起 对 应 一 个 完 整 的 任 务 。 比 如 , 如 果 你运 行 了 单 个 shell 命 令 , 这 条 命 令 是 有 一 系 列 过 滤 器 命 令 组 成 , 相 互 之 间 用 管 道 传 递 数 据 ,这 些 进 程 ( 以 及 它 们 的 子 进 程 ) 都 属 于 同 一 个 进 程 组 。 该 进 程 组 有 一 个 数 字 对 应 这 个 进 程 组的 领 头 进 程 的 进 程 号 。 如 果 你 给 一 个 正 数 的 进 程 号 发 送 信 号 , 该 信 号 只 发 送 给 该 进 程 , 而 如果 你 给 一 个 负 数 进 程 号 发 送 信 号 , 那 么 该 信 号 将 发 送 给 对 应 的 进 程 组 的 所 有 进 程 , 该 进 程 组的 进 程 组 号 就 是 这 个 负 数 的 绝 对 值 , 也 就 是 该 进 程 组 领 头 进 程 的 进 程 号 。( 为 了 方 便 进 程 组领 头 进 程 , 进 程 组 ID 就 是 $$。)假 设 你 的 程 序 想 给 由 它 直 接 启 动 的 所 有 子 进 程 ( 以 及 由 那 些 子 进 程 启 动 的 孙 子 进 程 和 曾 孙 进程 等 ) 发 送 一 个 挂 起 信 号 。 实 现 这 个 目 的 的 方 法 是 : 你 的 程 序 首 先 调 用 setpgrp(0,0), 使自 己 成 为 新 的 进 程 组 的 领 头 进 程 , 这 样 任 何 它 创 建 的 进 程 都 将 成 为 新 进 程 组 的 一 部 分 。 不 管那 些 进 程 是 通 过 fork 手 工 启 动 的 还 是 通 过 管 道 open 打 开 的 或 是 用 system("cmd &")启 动 的 后 台 进 程 。 即 使 那 些 进 程 有 自 己 的 子 进 程 也 无 所 谓 , 只 要 你 给 你 的 整 个 进 程 组 发 送 挂起 信 号 , 那 么 就 会 把 它 们 都 找 出 来 ( 除 了 那 些 设 置 了 自 己 的 进 程 组 或 者 改 变 了 自 己 的 UID的 进 程 —— 它 们 对 你 的 信 号 有 外 交 豁 免 权 。){local $SIG{HUP} = 'IGNORE';# 排 除 自 己kill(HUP, -$$);# 通 知 自 己 的 进 程 组}412


另 外 一 个 有 趣 的 信 号 是 信 号 数 0。 它 实 际 上 不 影 响 目 标 进 程 , 只 是 检 查 一 下 , 看 看 那 个 进程 是 否 还 活 着 或 者 是 是 否 改 变 了 UID。 也 就 是 说 , 它 判 断 给 目 标 进 程 发 送 信 号 是 否 合 法 ,而 实 际 上 并 不 真 正 发 送 信 号 。unless ( kill 0 => $kid_pid ) {warn "something wicked happened to $kid_pid";}信 号 0 是 唯 一 的 一 个 在 Unix 上 和 Windows 上 的 <strong>Perl</strong> 移 植 作 用 一 样 的 信 号 。 在Microsoft 系 统 里 ,kill 实 际 上 并 不 发 送 信 号 。 相 反 , 它 强 迫 目 标 进 程 退 出 , 而 退 出 状 态 由信 号 数 标 明 。 这 些 东 西 以 后 都 会 修 改 。 但 是 , 神 奇 的 0 信 号 将 依 然 如 故 , 表 现 出 非 破 坏 性的 特 性 。16.1.2 收 割 僵 死 进 程当 一 个 进 程 退 出 的 时 候 , 内 核 向 它 的 父 进 程 发 送 一 个 CHLD 信 号 然 后 该 进 程 就 成 为 一 个 僵死 进 程 (zombie, 注 : 这 是 一 个 技 术 术 语 ), 直 到 父 进 程 调 用 wait 或 者 waitpid。 如 果你 在 <strong>Perl</strong> 里 面 启 动 新 进 程 用 的 不 是 fork, 那 么 <strong>Perl</strong> 就 会 替 你 收 割 这 些 僵 死 进 程 , 但 是如 果 你 用 的 是 一 个 fork, 那 么 就 得 自 己 做 清 理 工 作 。 在 许 多 ( 但 不 是 全 部 ) 内 核 上 , 自 动收 割 的 最 简 单 办 法 就 是 把 $SIG{CHLD} 设 置 为 'IGNORE'。 另 一 个 更 简 单 ( 但 也 更 乏 味 )的 方 法 是 你 自 己 收 割 它 们 。 因 为 在 你 开 始 处 理 的 时 候 , 可 能 有 不 止 一 个 子 进 程 已 经 完 蛋 了 ,所 以 , 你 必 须 在 一 个 循 环 里 收 割 你 的 子 进 程 直 到 没 有 更 多 为 止 :use POSIX ":sys_wait_h";sub REAPER { 1 until waitpid(-1, WNOHANG) == -1) }想 根 据 需 要 运 行 这 些 代 码 , 你 要 么 可 以 给 它 设 置 CHLD 信 号 :$SIG{CHLD} =\&REAPER;或 者 如 果 你 的 程 序 是 在 一 个 循 环 里 运 行 , 那 你 只 需 要 循 环 调 用 收 割 器 就 行 了 。 这 个 方 法 最 好 ,因 为 它 避 免 了 那 些 信 号 可 能 触 发 的 在 C 库 里 偶 然 的 核 心 转 储 。 但 是 , 如 果 在 一 个 很 快 速 的循 环 里 调 用 , 这 样 做 的 开 销 是 巨 大 的 , 所 以 一 种 合 理 的 折 衷 是 用 一 种 混 合 的 方 法 : 你 在 句 柄里 尽 可 能 少 做 处 理 , 把 风 险 降 到 最 低 , 同 时 在 外 部 循 环 中 等 待 收 割 僵 死 进 程 :our $zombies = 0;$SIG{CHLD} = sub { $zombies++};413


sub reaper {my $zombie;our %Kid_Status;# 存 储 每 个 退 出 状 态$zombies = 0;while (($zombie = waitpid( -1, WNOHANG)) != -1) {$Kid_Status{$zombie} = $?;}}while(1) {reaper() if $zombies;...}这 段 代 码 假 设 你 的 内 核 支 持 可 靠 信 号 。 老 的 Sys V 风 格 的 信 号 是 不 可 靠 的 , 那 样 的 话 , 想写 正 确 的 信 号 句 柄 几 乎 是 不 可 能 的 。 甚 至 早 在 <strong>Perl</strong> 版 本 5.003, 只 要 可 能 , 我 们 就 开 始使 用 sigaction(2) 系 统 调 用 了 , 因 为 它 更 可 靠 些 。 这 意 味 着 除 非 你 在 一 个 古 老 的 操 作 系 统上 运 行 或 者 跑 的 是 一 个 古 老 的 <strong>Perl</strong> , 你 用 不 着 重 新 安 装 你 的 句 柄 , 也 不 会 冒 丢 失 信 号 的 危险 。 幸 运 的 是 , 所 有 带 BSD 风 格 的 系 统 ( 包 括 Linux,Solaris, 和 Mac OS X) 以 及 所有 POSIX 兼 容 的 系 统 都 提 供 可 靠 的 信 号 , 所 以 那 些 老 旧 的 Sys V 问 题 更 多 是 历 史 遗 留 问题 , 而 不 是 目 前 我 们 要 关 心 的 问 题 。在 新 内 核 上 , 还 有 许 多 其 他 东 西 也 会 运 行 得 更 好 些 。 比 如 ,“ 慢 的 ” 系 统 调 用 ( 那 种 可 以 阻 塞的 , 就 象 read,wait, 和 accept) 如 果 被 一 个 信 号 中 断 后 将 自 动 重 新 启 动 。 在 那 些 灰 暗的 旧 社 会 里 , 用 户 代 码 必 须 记 得 明 确 地 检 查 每 个 慢 的 系 统 调 用 是 否 带 着 $! ($ERRNO) 为EINTR 失 败 的 , 而 且 如 果 是 这 样 , 那 么 重 起 。 而 且 这 样 的 情 况 不 光 对 INT 信 号 , 而 且 对有 些 无 辜 的 信 号 , 比 如 TSTP( 来 自 Control-Z) 或 者 CONT ( 来 自 把 任 务 放 到 前 台 )也 会 退 出 系 统 调 用 。 如 果 操 作 系 统 允 许 , 现 在 <strong>Perl</strong> 自 动 为 你 重 新 启 动 系 统 调 用 。 我 们 通 常认 为 这 是 一 个 特 性 。你 可 以 检 查 一 下 , 看 看 你 的 系 统 是 否 有 更 严 格 的 POSIX 风 格 的 信 号 , 方 法 是 装 载 Config模 块 然 后 检 查 $Config{d_sigaction} 是 否 为 真 。 要 检 查 慢 的 系 统 调 用 是 否 可 以 可 以 重起 , 检 查 你 的 系 统 的 文 档 :sigaction(2) 或 者 sigvec(3), 或 者 在 你 的 C sys/signal.h 里414


查 找 SV_INTERRUPT 或 者 SA_RESTART。 如 果 找 到 两 个 或 者 其 中 之 一 , 你 可 能 就 拥 有可 重 起 的 系 统 调 用 。16.1.3 给 慢 速 操 作 调 速信 号 的 一 个 用 途 就 是 给 长 时 间 运 行 的 操 作 设 置 一 个 时 间 限 制 。 如 果 你 用 的 是 一 种 Unix 系统 ( 或 者 任 何 POSIX 兼 容 的 支 持 ALRM 信 号 的 系 统 ), 你 就 可 以 让 内 核 在 未 来 的 某 时 刻给 你 进 程 发 送 一 个 ALRM 信 号 :use Fcntl ':flock';eval {local $SIG{ALRM} = sub { die "alarm clock restart" };alarm 10;# 安 排 10 秒 后 报 警eval {flock(FH, LOCK_EX)# 一 个 阻 塞 的 , 排 它 锁or die "can't flock:$!";};alarm 0;# 取 消 报 警};alarm 0;# 避 免 冲 突 条 件die if $@ && $@ !~ /alarm clock restart/;# 重 新 启 动如 果 你 在 等 待 锁 的 时 候 报 警 , 你 只 是 把 信 号 缓 冲 起 来 然 后 返 回 , 你 会 直 接 回 到 flock, 因 为<strong>Perl</strong> 在 可 能 的 情 况 下 会 自 动 重 起 系 统 调 用 。 跳 出 去 的 唯 一 方 法 是 用 die 抛 出 一 个 例 外 并 且让 eval 捕 获 之 。( 这 样 做 是 可 行 的 , 因 为 例 外 会 退 回 到 调 用 库 的 longjmp(3) 函 数 , 而longjmp(3) 是 真 正 把 你 带 出 重 起 系 统 调 用 的 东 西 。)我 们 使 用 了 嵌 套 的 例 外 陷 阱 是 因 为 如 果 flock 在 你 的 平 台 上 没 有 实 现 的 话 , 那 么 调 用flock 会 抛 出 一 个 例 外 , 因 此 你 必 须 确 保 清 理 了 警 告 信 号 。 第 二 个 alarm 0 用 于 处 理 这 样的 情 况 : 信 号 到 达 时 是 在 调 用 flock 之 后 但 是 在 到 达 第 一 个 alarm 0 之 前 。 没 有 第 二 个alarm, 你 可 能 会 面 对 一 个 很 小 的 冲 突 条 件 —— 不 过 冲 突 条 件 可 不 会 管 你 的 冲 突 条 件 是 大 是小 ; 它 们 是 黑 白 分 明 的 : 要 么 有 , 要 么 无 。 而 我 们 更 希 望 没 有 。415


16.1.4 阻 塞 信 号有 时 候 , 你 可 能 想 在 一 些 关 键 的 代 码 段 里 推 迟 接 收 信 号 。 你 并 不 想 简 单 地 忽 略 这 些 信 号 , 只是 你 做 的 事 情 太 关 键 了 , 因 而 不 能 中 断 。<strong>Perl</strong> 的 %SIG 散 列 并 不 实 现 信 号 阻 塞 , 但 是POSIX 模 块 通 过 它 的 调 用 sigprocmask(2) 系 统 调 用 的 接 口 实 现 了 信 号 阻 塞 :use POSIX qw(:signal_h);$sigset = POSXI::SigSet->new;$blockset = POSIX::SigSet->new(SIGINT, SIGQUIT, SIGCHLD);sigprocmask(SIG_BLOCK, $blockset, $sigset)or die "Could not block INT, QUIT, CHLD signals: $! \n";一 旦 上 面 三 个 信 号 都 被 阻 塞 了 , 你 就 可 以 毫 不 担 心 地 执 行 你 的 任 务 了 。 在 你 处 理 完 你 的 关 键业 务 以 后 , 用 恢 复 旧 的 信 号 掩 码 的 方 法 取 消 信 号 的 阻 塞 :sigprocmask( SIG_SETMASK, $sigset)or die "Could not restore INT, QUIT, CHLD signals: $!\n";如 果 阻 塞 的 时 候 有 三 个 信 号 中 的 任 何 信 号 到 达 , 那 么 这 时 它 们 会 被 立 即 发 送 。 如 果 有 两 种 或者 更 多 的 不 同 信 号 在 等 待 , 那 么 它 们 的 发 送 顺 序 并 没 有 定 义 。 另 外 , 在 阻 塞 过 程 中 , 收 到 某个 信 号 一 次 和 收 到 多 次 是 没 有 区 别 的 。( 注 : 通 常 是 这 样 。 根 据 最 新 的 规 范 , 可 计 数 信 号 可能 在 一 些 实 时 系 统 上 有 实 现 , 但 是 我 们 还 没 有 看 到 那 些 系 统 。) 比 如 , 如 果 你 在 阻 塞 CHLD信 号 期 间 有 九 个 子 进 程 退 出 , 那 么 你 的 信 号 句 柄 ( 如 果 存 在 ) 在 退 出 阻 塞 后 仍 然 只 会 被 调 用一 次 。 这 就 是 为 什 么 当 你 在 收 割 僵 死 进 程 的 时 候 , 你 应 该 循 环 到 所 有 的 僵 死 进 程 都 消 失 。16.2 文 件你 以 前 可 能 从 未 把 文 件 当 作 一 种 IPC 机 制 , 但 是 它 们 却 占 据 了 进 程 间 通 讯 的 很 大 一 部 分 份额 —— 远 比 其 他 方 法 的 总 和 份 额 要 大 。 当 一 个 进 程 把 它 的 关 键 数 据 存 放 在 文 件 里 , 而 且 以 后另 外 一 个 进 程 又 检 索 那 些 数 据 , 那 么 这 就 是 两 个 进 程 在 通 讯 。 文 件 提 供 了 一 些 这 里 提 到 的 其他 的 IPC 机 制 所 没 有 的 特 点 : 就 象 纸 张 埋 在 地 下 几 千 年 一 样 , 文 件 可 以 比 它 的 作 者 有 更 长的 保 存 期 。( 注 : 假 设 我 们 有 人 的 保 存 期 )。 再 加 上 相 对 而 言 使 用 的 简 单 , 文 件 至 今 仍 然 流行 就 一 点 都 不 奇 怪 了 。使 用 文 件 在 已 经 消 亡 的 过 去 和 不 知 何 时 的 未 来 之 间 传 递 信 息 并 不 让 人 奇 怪 。 你 在 一 些 永 久 介质 上 ( 比 如 磁 盘 ) 写 文 件 就 行 了 。 仅 此 而 已 。( 如 果 它 包 含 HTML, 你 可 能 还 要 告 诉 一 台 web416


服 务 器 在 那 里 能 找 的 到 。) 有 趣 的 问 题 是 如 果 所 有 当 事 人 都 健 在 并 且 试 图 相 互 通 讯 时 该 怎 么办 。 如 果 对 各 自 说 话 的 顺 序 没 有 一 些 规 定 的 话 , 就 根 本 不 可 能 有 可 靠 的 交 流 ; 这 样 的 规 定 可以 通 过 文 件 锁 来 实 现 , 我 们 将 在 下 一 节 介 绍 。 在 其 后 一 节 里 , 我 们 将 讨 论 父 进 程 和 其 子 进 程之 间 存 在 的 特 殊 关 系 , 这 些 关 系 可 以 让 相 关 的 当 事 人 通 过 对 相 同 文 件 继 承 的 访 问 交 换 信 息 。文 件 当 然 有 其 缺 点 , 比 如 远 程 访 问 , 同 步 , 可 靠 性 和 会 话 管 理 等 。 本 章 其 他 节 介 绍 那 些 着 眼于 解 决 这 些 问 题 的 不 同 IPC 机 制 。16.2.1 文 件 锁 定在 一 个 多 任 务 环 境 里 , 你 需 要 很 小 心 地 避 免 与 其 他 试 图 使 用 你 正 在 用 的 文 件 的 进 程 冲 突 。 如果 所 有 进 程 都 只 读 取 文 件 内 容 , 那 么 大 家 相 安 无 事 , 但 是 如 果 有 哪 怕 只 有 一 个 进 程 需 要 写 该文 件 , 那 么 随 后 就 会 发 生 混 乱 —— 除 非 使 用 某 种 排 序 机 制 充 当 交 通 警 察 的 角 色 。绝 对 不 要 只 是 使 用 文 件 是 否 存 在 ( 也 就 是 -e $file) 当 作 文 件 锁 的 标 志 , 因 为 在 测 试 文 件 名是 否 存 在 和 你 计 划 的 处 理 ( 比 如 创 建 , 打 开 , 或 者 删 除 它 ) 之 间 存 在 冲 突 条 件 。 参 阅 第 二 十三 章 , 安 全 , 中 的 “ 处 理 冲 突 条 件 ”, 获 取 更 多 相 关 信 息 。<strong>Perl</strong> 的 可 移 植 锁 定 接 口 是 flock(HANDLE,FLAGS) 函 数 , 在 第 二 十 九 章 , 函 数 , 里 描 述 。<strong>Perl</strong> 只 采 用 那 些 在 最 广 范 围 的 平 台 上 都 能 找 到 的 最 简 单 的 锁 定 机 制 , 因 此 获 得 了 最 大 的 可移 植 性 。 这 些 语 意 简 单 得 可 以 在 绝 大 部 分 系 统 上 使 用 , 包 括 那 些 不 支 持 这 些 传 统 系 统 调 用 的平 台 , 比 如 System V 或 Windows NT。( 如 果 你 运 行 的 Microsoft 的 系 统 是 早 于 NT的 平 台 , 那 么 你 很 可 能 没 有 这 些 系 统 调 用 支 持 , 就 好 象 你 运 行 Mac OS X 以 前 的 苹 果 系 统一 样 。)锁 有 两 种 变 体 , 共 享 (LOCK_SH 标 志 ) 和 排 它 (LOCK_EX 标 志 )。 尽 管 听 着 有 “ 排 它 ”的 意 思 , 但 是 进 程 并 不 需 要 服 从 对 文 件 的 锁 。 也 就 是 说 ,flock 只 是 实 现 了 劝 告 性 的 锁 定 ,劝 告 性 的 锁 定 , 也 就 意 味 着 锁 定 一 个 文 件 并 不 阻 止 其 他 的 进 程 读 取 甚 至 是 写 入 该 文 件 。 进 程请 求 一 个 排 它 锁 只 是 让 操 作 系 统 推 迟 它 对 文 件 的 处 理 , 直 到 所 有 当 前 的 锁 持 有 者 , 不 管 是 共享 锁 还 是 排 它 锁 , 都 完 成 操 作 以 后 才 进 行 。 类 似 地 , 如 果 一 个 进 程 请 求 一 个 共 享 锁 , 它 只 是推 迟 处 理 直 到 没 有 排 它 锁 存 在 。 只 有 所 有 当 事 人 都 使 用 文 件 锁 机 制 的 时 候 , 你 才 能 安 全 地 访问 一 个 有 内 容 的 文 件 。因 此 ,flock 缺 省 时 是 一 个 阻 塞 操 作 。 也 就 是 说 , 如 果 你 不 能 立 即 获 取 你 需 要 的 锁 , 操 作 系统 会 推 迟 你 的 处 理 , 直 到 你 能 够 获 得 锁 为 止 。 下 面 是 如 何 获 取 阻 塞 的 共 享 锁 的 方 法 , 通 常 用于 读 取 文 件 :use Fcntl qw(:DEFAULT :flock);open(FH, "< filename") or die "can't open filename: $!";417


flock(FH, LOCK_SH) or die "can't lock filename: $!";# 现 在 从 FH 里 读 取你 可 以 试 图 请 求 一 个 非 阻 塞 的 锁 , 只 需 要 在 flock 请 求 里 加 入 LOCK_NB 标 志 就 可 以 了 。如 果 你 不 能 马 上 获 得 锁 , 那 么 该 函 数 失 败 并 且 马 上 返 回 假 。 下 面 是 例 子 :flock(FH, LOCK_FH | LOCK_NB)or die "can't lock filename: $!";你 除 了 象 我 们 这 样 抛 出 一 个 例 外 之 外 可 能 还 想 做 点 别 的 事 情 , 但 是 你 肯 定 不 敢 对 该 文 件 进 行任 何 I/O 操 作 。 如 果 你 的 锁 申 请 被 拒 绝 , 你 就 不 应 该 访 问 该 文 件 直 到 你 能 够 拿 到 锁 。 谁 知道 那 个 文 件 处 于 什 么 样 的 混 乱 状 态 ? 非 阻 塞 模 式 的 主 要 目 的 是 让 你 离 开 并 且 在 等 待 期 间 做些 其 他 的 事 情 。 而 且 它 也 可 以 用 于 生 成 更 友 好 的 交 互 , 比 如 警 告 用 户 说 他 可 能 要 一 段 时 间 才能 获 取 锁 , 这 样 用 户 就 不 会 觉 得 被 抛 弃 :use Fcntl qw(:DEFAULT :flock);open(FH, "< filename") or die "can't open filename: $!";unless (flock(FH, LOCK_SH | LOCK_NB)) {local $| = 1;print "Waiting for lock on filename...";flock(FH, LOCK_SH) or die "can't lock filename: $!";print "got it.\n";}# 现 在 从 FH 读 数有 些 人 会 试 图 把 非 阻 塞 锁 放 到 一 个 循 环 里 去 。 非 阻 塞 锁 的 主 要 问 题 是 , 当 你 回 过 头 来 再 次 检查 的 时 候 , 可 能 其 他 人 已 经 把 锁 拿 走 了 , 因 为 你 放 弃 了 在 队 伍 里 的 位 置 。 有 时 候 你 不 得 不 排队 并 且 等 待 。 如 果 你 走 运 的 话 , 可 能 可 以 先 看 看 杂 志 什 么 的 。锁 是 针 对 文 件 句 柄 的 , 而 不 是 文 件 名 。( 注 : 实 际 上 , 锁 不 是 针 对 文 件 句 柄 的 —— 他 们 是 针对 与 文 件 句 柄 关 联 的 文 件 描 述 符 的 , 因 为 操 作 系 统 并 不 知 道 文 件 句 柄 。 这 就 意 味 着 我 们 的 所有 关 于 对 某 文 件 名 没 能 拿 到 锁 的 die 消 息 从 技 术 上 来 讲 都 是 不 准 确 的 。 不 过 下 面 这 样 的 错误 信 息 :”I can't get a lock on the file represented by the file descriptor associatedwith the filehandle originally opened to the path filename, although by now418


filename may represend a different file entrely than our handle does" 只 能 让 用户 糊 涂 ( 更 不 用 说 读 者 了 )。 当 你 关 闭 一 个 文 件 , 锁 自 动 消 除 , 不 管 你 是 通 过 调 用 close 明确 关 闭 该 文 件 还 是 通 过 重 新 打 开 该 句 柄 隐 含 的 关 闭 还 是 退 出 你 的 进 程 。如 果 需 要 获 取 排 它 锁 ( 通 常 用 于 写 ), 你 就 得 更 小 心 。 你 不 能 用 普 通 的 open 来 实 现 这 些 ;如 果 你 用 < 的 打 开 模 式 , 如 果 文 件 不 存 在 那 么 它 会 失 败 , 如 果 你 用 >, 那 么 它 会 先 删 除它 处 理 的 任 何 文 件 。 你 应 该 使 用 sysopen 打 开 文 件 , 这 样 该 文 件 就 可 以 在 被 覆 盖 之 前 先被 锁 住 。 一 旦 你 安 全 地 打 开 了 用 于 写 入 的 文 件 ( 但 还 没 有 写 ), 那 么 先 成 功 获 取 排 它 锁 , 只有 这 时 候 文 件 才 被 截 去 。 现 在 你 可 以 用 新 数 据 覆 盖 它 。use Fcntl qw(:DEFALUT :flock);sysopen(FH, "filename", O_WRONLY | O_CREAT)or die "can't open filename:$!";flock(FH, LOCK_EX)or die "can't lock filename:$!";truncate(FH,0)or die "can't truncate filename:$!";# 现 在 写 FH如 果 想 现 场 修 改 文 件 的 内 容 , 那 么 再 次 使 用 sysopen。 这 次 你 请 求 读 写 权 限 , 如 果 必 要 就创 建 文 件 。 一 旦 打 开 了 文 件 , 但 是 还 没 有 开 始 读 写 的 时 候 , 先 获 取 排 它 锁 , 然 后 在 你 的 整 个事 务 过 程 中 都 使 用 它 。 释 放 锁 的 最 好 方 法 是 关 闭 文 件 , 因 为 那 样 保 证 了 在 释 放 锁 之 前 所 有 缓冲 区 都 写 入 文 件 。一 次 更 新 包 括 读 进 旧 值 和 写 出 新 值 。 你 必 须 在 单 个 排 它 锁 里 面 做 两 个 操 作 , 减 少 其 他 进 程 在你 处 理 之 后 ( 甚 至 之 前 ) 读 取 ( 马 上 就 不 正 确 了 的 ) 数 值 。( 我 们 将 在 本 章 稍 后 的 共 享 内 存的 部 分 再 次 介 绍 这 个 情 况 。)use Fcntl qw(:DEFAULT :flock);sysopen(FH, "counterfile", O_RDWR | O_CREAT)or die "can't open counterfile: $!";flock(FH, LOCK_EX);419


or die "can't write-lock counterfile: $!";$counter = || 0;# 首 先 应 该 undefseek(FH, 0, 0)or die "can't rewind counterfile :$!";print FH $counter+1, "\n"or die "can't write counterfile: $!";# 下 一 行 在 这 个 程 序 里 从 技 术 上 是 肤 浅 的 , 但 是 一 个 一 般 情 况 下 的 好 主 意truncate(FH, tell(FH))or die "can't truncate counterfile: $!";close(FH)or die "can't close counterfile: $!";你 不 能 锁 住 一 个 你 还 没 打 开 的 文 件 , 而 且 你 无 法 拥 有 一 个 施 加 于 多 个 文 件 的 锁 。 你 能 做 的 是用 一 个 完 全 独 立 的 文 件 充 当 某 种 信 号 灯 ( 象 交 通 灯 ), 通 过 在 这 个 信 号 灯 文 件 上 使 用 普 通 的共 享 和 排 它 锁 来 提 供 可 控 制 的 对 其 他 东 西 ( 文 件 ) 的 访 问 。 这 个 方 法 有 几 个 优 点 。 你 可 以 用一 个 文 件 来 控 制 对 多 个 文 件 的 访 问 , 从 而 避 免 那 种 一 个 进 程 试 图 以 一 种 顺 序 锁 住 那 些 文 件 而另 外 一 个 进 程 试 图 以 其 他 顺 序 锁 住 那 些 文 件 导 致 的 死 锁 。 你 可 以 用 信 号 灯 文 件 锁 住 整 个 目 录里 的 文 件 。 你 甚 至 可 以 控 制 对 那 些 就 不 在 文 件 系 统 上 的 东 西 的 访 问 , 比 如 一 个 共 享 内 存 对 象或 者 是 一 个 若 干 个 预 先 分 裂 出 来 的 服 务 器 准 备 调 用 accept 的 套 接 字 。如 果 你 有 一 个 DBM 文 件 , 而 且 这 个 DBM 文 件 没 有 明 确 的 锁 定 机 制 , 那 么 用 一 个 附 属 的锁 文 件 就 是 控 制 多 个 客 户 并 发 访 问 的 最 好 的 方 法 。 否 则 , 你 的 DBM 库 的 内 部 缓 冲 就 可 能与 磁 盘 上 的 文 件 之 间 丢 失 同 步 。 在 调 用 dbmopen 或 者 tie 之 前 , 先 打 开 并 锁 住 信 号 灯 文件 。 如 果 你 用 O_RDONLY 打 开 数 据 库 , 那 你 会 愿 意 使 用 LOCK_SH 处 理 锁 定 。 否 则 ,使 用 LOCK_EX 用 于 更 新 数 据 库 的 排 它 访 问 。( 同 样 , 只 有 所 有 当 事 人 都 同 意 关 注 信 号 灯才 有 效 。)use Fcntl qw(:DEFAULT :flock);use DB_File;# 只 是 演 示 用 途 , 任 何 db 都 可 以420


$DBNAME = "/path/to/database";$LCK = $DBNAME. ".lockfile";# 如 果 你 想 把 数 据 写 到 锁 文 件 里 , 使 用 O_RDWRsysopen(DBLOCK, $LCK, O_RDONLY| O_CREAT)or die "can't open $LCK:$!";# 在 打 开 数 据 库 之 前 必 须 锁 住 文 件flock(DBLOCK, LOCK_SH)or die "can't LOCK_SH $LCK: $!";tie(%hash, "DB_File", $DBNAME, O_RDWR | O_CREAT)or die "can't tie $DBNAME: $!";现 在 你 可 以 安 全 地 对 捆 绑 了 的 %hash 做 任 何 你 想 做 的 处 理 了 。 如 果 你 完 成 了 对 你 的 数 据库 的 处 理 , 那 么 确 保 你 明 确 地 释 放 了 那 些 资 源 , 并 且 是 以 你 请 求 它 们 的 相 反 的 顺 序 :untie %hash;close DBLOCK;# 必 须 在 锁 定 文 件 之 前 关 闭 数 据 库# 现 在 可 以 安 全 地 释 放 锁 了如 果 你 安 装 了 GNU DBM 库 , 你 可 以 使 用 标 准 的 GDBM_File 模 块 的 隐 含 锁 定 。 除 非 最初 的 tie 包 含 GDBM_NOLOCK 标 志 , 否 则 该 库 将 保 证 任 意 时 刻 只 有 一 个 用 户 可 以 写 入GDBM 文 件 , 而 且 读 进 程 和 写 进 程 不 能 让 数 据 库 同 时 处 于 打 开 状 态 。16.2.2 传 递 文 件 句 柄每 当 你 用 fork 创 建 一 个 子 进 程 , 那 个 新 的 进 程 就 从 它 的 父 进 程 继 承 所 有 打 开 了 的 文 件 句柄 。 用 文 件 句 柄 做 进 程 间 通 讯 可 以 很 容 易 先 通 过 使 用 平 面 文 件 来 演 示 。 理 解 文 件 机 制 的 原 理对 于 理 解 本 章 后 面 的 管 道 和 套 接 字 等 更 奇 妙 的 机 制 有 很 大 帮 助 。下 面 这 个 最 简 单 的 例 子 打 开 一 个 文 件 然 后 开 始 一 个 子 进 程 。 然 后 子 进 程 则 使 用 已 经 为 它 打 开了 的 文 件 句 柄 :421


open(INPUT, "< /etc/motd") or die "/etc/motd: $!";if ($pid = fork) { waitpid($pid, 0);}else {defined($pid) or die "fork: $!";while () { print "$.: $_" }exit;# 不 让 子 进 程 回 到 主 代 码}# INPUT 句 柄 现 在 在 父 进 程 里 位 于 EOF一 旦 用 open 获 取 了 文 件 的 访 问 权 , 那 么 该 文 件 就 保 持 授 权 状 态 直 到 该 文 件 句 柄 关 闭 ; 对文 件 的 权 限 或 者 所 有 人 的 访 问 权 限 对 文 件 的 访 问 没 有 什 么 影 响 。 即 使 该 进 程 后 面 修 改 它 自 己的 用 户 或 者 组 ID, 或 者 该 文 件 已 经 把 自 己 的 所 有 权 赋 予 了 一 个 不 同 的 用 户 或 者 组 , 也 不 会影 响 已 经 打 开 的 文 件 句 柄 。 那 些 运 行 在 提 升 权 限 级 别 的 程 序 ( 比 如 set-id (SID) 程 序 或者 系 统 守 护 进 程 ) 通 常 在 它 们 提 升 以 后 的 权 限 下 打 开 一 个 文 件 , 然 后 把 文 件 句 柄 传 递 给 一 个不 能 自 己 打 开 文 件 的 子 进 程 。尽 管 这 个 机 制 在 有 意 识 地 使 用 的 时 候 非 常 便 利 , 但 如 果 文 件 句 柄 碰 巧 从 一 个 程 序 漏 到 了 另 外一 个 程 序 , 那 么 它 就 有 可 能 导 致 安 全 问 题 。 为 避 免 给 所 有 可 能 的 文 件 句 柄 赋 予 隐 含 的 访 问 权限 , 当 你 明 确 地 用 exec 生 成 一 个 新 的 程 序 或 者 通 过 调 用 一 个 透 过 管 道 的 open,system, 或 者 qx//( 反 钩 号 ) 隐 含 地 执 行 一 个 新 程 序 的 时 候 ,<strong>Perl</strong> 都 会 自 动 关 闭 任 何 他已 经 打 开 的 文 件 句 柄 ( 包 括 管 道 和 套 接 字 ) 。STDIN,STDOUT, 和 STDERR 被 排 除 在外 , 因 为 他 们 的 主 要 目 的 是 提 供 程 序 之 间 的 联 系 。 所 以 将 文 件 句 柄 传 递 给 一 个 新 程 序 的 方 法之 一 是 把 该 文 件 句 柄 拷 贝 到 一 个 标 准 文 件 句 柄 里 :open(INPUT, "< /etc/motd") or die "/etc/motd: $!";if ($pid = fork) { wait }else {defined($pid)or die "fork:$!";open(STDIN, "


如 果 你 真 的 希 望 新 程 序 能 够 获 取 除 了 上 面 三 个 之 外 的 文 件 句 柄 的 访 问 权 限 , 你 也 能 做 到 , 不过 你 必 须 做 两 件 事 之 一 。 当 <strong>Perl</strong> 打 开 一 个 新 文 件 ( 或 者 管 道 和 套 接 字 ) 的 时 候 , 它 检 查 变量 $^F($SYSTEM_FD_MAX) 的 当 前 设 置 。 如 果 新 文 件 句 柄 用 的 数 字 文 件 描 述 符 大 于 那个 $^F, 该 描 述 符 就 标 记 为 一 个 要 关 闭 的 。 否 则 ,<strong>Perl</strong> 就 把 它 放 着 , 并 且 你 exec 出 来的 新 的 程 序 就 会 继 承 访 问 。通 常 很 难 预 料 你 新 创 建 的 文 件 句 柄 是 什 么 , 但 是 你 可 以 在 open 期 间 暂 时 把 你 的 最 大 系 统文 件 描 述 符 数 设 置 的 非 常 大 :# 打 开 文 件 并 且 把 INPUT 标 记 为 在 exec 之 间 可 以 使 用{local $^F = 10_000;open(INPUT, "< /etc/motd") or die "/etc/motd: $!";} # 在 范 围 退 出 后 , 恢 复 旧 的 $^F 值现 在 你 所 要 干 的 就 是 让 新 程 序 关 照 你 刚 刚 打 开 的 文 件 句 柄 的 文 件 描 述 符 。 最 干 净 的 解 决 方 法( 在 那 些 支 持 这 些 的 系 统 上 ) 就 是 传 递 一 个 文 件 名 是 刚 创 建 的 文 件 描 述 符 的 特 殊 文 件 。 如 果你 的 系 统 有 一 个 目 录 叫 /dev/fd 或 者 /proc/$$/fd, 里 面 包 含 从 了 0 到 你 的 系 统 支 持 的最 大 文 件 描 述 符 数 字 , 那 么 你 就 可 能 可 以 使 用 这 个 方 法 。( 许 多 Linux 系 统 两 个 都 有 , 但是 好 象 只 有 /proc 的 版 本 是 正 确 填 充 的 。 BSD 和 Solaris 喜 欢 用 /dev/fd。 你 最 好 自己 看 看 你 的 系 统 , 检 查 一 下 你 的 系 统 是 哪 种 情 况 。) 首 先 , 用 我 们 前 面 显 示 的 代 码 打 开 一 个文 件 句 柄 并 且 把 它 标 记 成 一 个 可 以 在 exec 之 间 传 递 的 句 柄 , 然 后 用 下 面 的 方 法 分 裂 进 程 :if ($pid = fork) { wait }else {defined($pid) or die "fork: $!";$fdfile = "/dev/fd/" . fileno(INPUT);exec("cat", "-n", $fdfile) or die "exec cat: $!";}如 果 你 的 系 统 支 持 fcntl 系 统 调 用 , 你 就 可 以 手 工 骗 过 文 件 句 柄 在 exec 时 候 的 关 闭 标 志了 。 如 果 你 创 建 了 文 件 句 柄 而 且 还 想 与 你 的 子 进 程 共 享 , 但 是 早 些 时 候 你 还 没 有 意 识 到 想 共享 句 柄 的 场 合 下 , 这 个 方 法 特 别 方 便 。use Fcntl qw/F_SETFD/;423


fcntl( INPUT, F_SETFD, 0)or die "Can't clear close-on-exec flag on INPUT: $!\n";你 还 可 以 强 制 一 个 文 件 句 柄 关 闭 :fcntl(INPUT, F_SETFD, 1)or die "Can't set close-on-exec flag on INPUT: $!\n";你 还 可 以 查 询 当 前 状 态 :use Fcntl qw/F_SETFD F_GETFD/;printf("INPUT will be %s across exec\n",fcntl(INPUT, F_GETFD, 1) ? "closed" : "left open");如 果 你 的 系 统 不 支 持 文 件 系 统 中 的 文 件 描 述 符 名 字 , 而 你 又 不 想 通 过 STDIN,STDOUT,或 者 STDERR 传 递 文 件 句 柄 , 你 还 是 可 以 实 现 , 但 是 你 必 须 给 那 些 程 序 做 特 殊 的 安 排 。常 用 的 策 略 是 用 一 个 环 境 变 量 或 者 一 个 命 令 行 选 项 传 递 这 些 描 述 符 号 。如 果 被 执 行 的 程 序 是 用 <strong>Perl</strong> 写 的 , 你 可 以 用 open 把 一 个 文 件 描 述 符 转 成 一 个 文 件 句 柄 。这 次 不 是 声 明 文 件 名 , 而 是 用 "&=" 后 面 跟 着 描 述 符 号 。if (defined($ENV{input_fdno}) && $ENV{input_fdno}) =~ /^\d$/) {open(INPUT, "


再 假 设 你 已 经 安 排 好 让 INPUT 句 柄 在 exec 之 间 保 持 打 开 状 态 , 你 可 以 这 样 调 用 这 个 程序 :$fdspec = '


or die "can't fork: $!";local $SIG{PIPE} = sub {die "spooler pipe broke" };print SPOOLER "stuff\n";close SPOOLER or die "bad spool: $! $?";这 个 例 子 实 际 上 启 动 了 两 个 进 程 , 我 们 可 以 直 接 向 第 一 个 ( 运 行 cat) 打 印 。 第 二 个 进 程 ( 运行 lpr) 则 接 收 第 一 个 进 程 的 输 出 。 在 shell 编 程 里 , 这 样 的 技 巧 通 常 称 为 流 水 线 。 一 个流 水 线 在 一 行 里 可 以 有 任 意 个 进 程 , 只 要 中 间 的 那 个 明 白 如 何 表 现 得 象 过 滤 器 ; 也 就 是 说 ,它 们 从 标 准 输 入 读 取 而 写 到 标 准 输 出 。如 果 一 条 管 道 命 令 包 含 shell 照 看 的 特 殊 字 符 , 那 么 <strong>Perl</strong> 使 用 你 的 缺 省 系 统 shell( 在Unix 上 /bin/sh)。 如 果 你 只 启 动 一 条 命 令 , 而 且 你 不 需 要 -- 或 者 是 不 想 -- 使 用 shell,那 么 你 就 可 以 用 打 开 管 道 的 一 个 多 参 数 的 形 式 替 代 :open SPOOLER, "|-", "lpr", "-h" # 要 求 5.6.1or die "can't run lpr: $!";如 果 你 重 新 打 开 你 的 程 序 的 标 准 输 出 作 为 到 另 外 一 个 程 序 的 管 道 , 那 么 你 随 后 向 STDOUTprint 的 任 何 东 西 都 将 成 为 那 个 新 程 序 的 标 准 输 入 。 因 此 如 果 你 想 给 你 的 程 序 做 成 分 页 输 出( 注 : 也 就 是 每 次 显 示 一 频 , 而 不 是 希 里 哗 啦 一 堆 ), 你 可 以 :if (-t STDOUT) {# 只 有 标 准 输 出 是 终 端 时my $pager = $ENV(PAGER}|| 'more';open( STDOUT, "| $pager") or die "can't fork a pager: $!";}END {close(STDOUT) or die "can't close STDOUT: $!"}如 果 你 向 一 个 与 管 道 连 接 的 文 件 句 柄 写 入 数 据 , 那 么 在 你 完 成 处 理 之 后 , 你 需 要 明 确 地close 它 。 那 样 你 的 主 程 序 才 不 会 在 它 的 后 代 之 前 退 出 。下 面 就 是 如 何 启 动 一 个 你 想 读 取 数 据 的 子 进 程 的 方 法 :open STATUS, "netstat -an 2>/dev/null |"426


or die "can't fork: $!";while () {next if /^(tcp|udp)/;print;}close STATUS or die "bad netstat: $! $?";你 同 样 也 可 以 象 用 在 输 出 里 那 样 , 打 开 一 个 多 命 令 的 输 入 管 道 。 而 且 和 以 前 一 样 , 你 可 以 通过 使 用 一 个 可 选 的 open 形 式 避 免 shell:open STATUS, "-|", "netstat", "-an" # 需 要 5.6.1or die "can't runnetstat: $!";不 过 那 样 你 就 得 不 到 I/O 重 定 向 , 通 配 符 扩 展 或 者 多 命 令 管 道 , 因 为 <strong>Perl</strong> 得 靠 你 的 shell做 这 些 事 情 。你 可 能 已 经 注 意 到 你 可 以 使 用 反 钩 号 实 现 与 打 开 一 个 管 道 读 取 数 据 一 样 的 功 能 :print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;die "bad netstat" if $?;尽 管 反 钩 号 很 方 便 , 但 是 它 们 必 须 把 所 有 东 西 都 一 次 读 进 内 存 , 所 以 , 通 常 你 打 开 自 己 的 管道 文 件 句 柄 然 后 每 次 一 行 或 者 一 条 记 录 地 处 理 文 件 会 更 有 效 些 。 这 样 你 就 能 对 整 个 操 作 有 更好 的 控 制 , 让 你 可 以 提 前 杀 死 进 程 。 你 甚 至 还 可 以 一 边 接 收 输 入 一 边 处 理 , 这 样 效 率 更 高 ,因 为 当 有 两 个 或 者 更 多 进 程 同 时 运 行 的 时 候 , 计 算 机 可 以 插 入 许 多 操 作 。( 即 使 在 一 台 单CPU 的 机 器 上 , 输 入 和 输 出 也 可 能 在 CPU 处 理 其 他 什 么 事 的 时 候 发 生 。)因 为 你 正 在 并 行 运 行 两 个 或 者 更 多 进 程 , 那 么 在 open 和 close 之 间 的 任 何 时 刻 , 子 进 程都 有 可 能 遭 受 灾 难 。 这 意 味 着 父 进 程 必 须 检 查 open 和 close 两 个 的 返 回 值 。 只 检 查open 是 不 够 安 全 的 , 因 为 它 只 告 诉 你 进 程 分 裂 是 否 成 功 , 以 及 ( 可 能 还 有 ) 随 后 的 命 令 是否 成 功 启 动 ( 只 有 在 最 近 的 版 本 的 <strong>Perl</strong> 中 才 能 做 到 这 一 点 , 而 且 该 命 令 还 必 须 是 通 过 直 接分 裂 的 子 进 程 执 行 的 , 而 不 是 通 过 shell 执 行 的 )。 任 何 在 那 以 后 的 灾 难 都 是 从 子 进 程 向 父进 程 以 非 零 退 出 状 态 返 回 的 。 当 close 函 数 看 到 这 个 返 回 值 , 那 么 它 就 知 道 要 返 回 一 个 假值 。 表 明 实 际 的 状 态 应 该 从 $?($CHILD_ERROR) 变 量 里 读 取 。 因 此 检 查 close 的 返 回值 和 检 查 open 的 返 回 值 一 样 重 要 , 如 果 你 往 一 个 管 道 里 写 数 据 , 那 么 你 还 应 该 准 备 处 理427


PIPE 信 号 , 如 果 你 还 没 有 完 成 数 据 的 发 送 , 而 另 外 一 端 的 进 程 完 蛋 掉 , 那 么 系 统 就 会 给 你发 送 这 个 信 号 。16.3.2 自 言 自 语IPC 的 另 外 一 个 用 途 就 是 让 你 的 程 序 和 自 己 讲 话 , 就 象 自 言 自 语 一 样 。 实 际 上 , 你 的 进 程通 过 管 道 和 一 个 它 自 己 分 裂 的 拷 贝 讲 话 时 , 它 的 工 作 方 式 和 我 们 上 一 节 里 讲 的 用 管 道 打 开 很类 似 , 只 不 过 是 子 进 程 继 续 执 行 你 的 脚 本 而 不 是 其 他 命 令 。要 想 把 这 个 东 西 提 交 给 open 函 数 , 你 要 使 用 一 个 包 含 负 号 的 伪 命 令 。 所 以 open 的 第 二个 参 数 看 起 来 就 象 "-|" 或 者 "|-", 取 决 于 你 是 想 从 自 己 发 出 数 据 还 是 从 自 己 接 收 数 据 。和 一 个 普 通 的 fork 命 令 一 样 ,open 函 数 在 父 进 程 里 返 回 子 进 程 的 进 程 ID, 而 在 子 进 程里 返 回 0。 另 外 一 个 不 对 称 的 方 面 是 open 命 名 的 文 件 句 柄 名 字 只 在 父 进 程 里 使 用 。 管 道的 子 进 程 端 根 据 实 际 情 况 要 么 是 挂 在 STDIN 上 要 么 是 STDOUT 上 。 也 就 是 说 , 如 果 你用 |- 打 开 一 个 “ 输 出 到 ” 管 道 , 那 么 你 就 可 以 向 你 打 开 的 这 个 文 件 句 柄 写 数 据 , 而 你 的 子 进程 将 在 它 的 STDIN 里 找 到 这 些 数 据 :if (open(TO, "|-")) {print TO $fromparent;}else {$tochild = ;exit;}如 果 你 用 -| 打 开 一 个 “ 来 自 ” 管 道 , 那 么 你 可 以 从 这 个 文 件 句 柄 读 取 数 据 , 而 那 些 数 据 就 是你 的 子 进 程 往 STDOUT 写 的 :if (open(FROM, "-|" )) {$toparent = ;}else {print STDOUT $fromchild;428


exit}这 个 方 法 的 一 个 常 见 的 应 用 就 是 当 你 想 从 一 个 命 令 打 开 一 个 管 道 的 时 候 绕 开 shell。 你 想 这么 干 的 原 因 可 能 是 你 不 希 望 shell 代 换 任 何 你 准 备 传 递 命 令 过 去 的 文 件 名 里 的 元 字 符 吧 。如 果 你 运 行 <strong>Perl</strong> 5.6.1 或 者 更 新 的 版 本 , 你 可 以 利 用 open 的 多 参 数 形 式 获 取 同 样 的 结果 。使 用 分 裂 的 文 件 打 开 的 原 因 是 为 了 在 一 个 假 想 的 UID 或 GID 下 也 能 打 开 一 个 文 件 或 者命 令 。 你 fork 出 来 的 子 进 程 会 抛 弃 任 何 特 殊 的 访 问 权 限 , 然 后 安 全 地 打 开 文 件 或 者 命 令 ,然 后 充 当 一 个 中 介 者 , 在 它 的 更 强 大 的 父 进 程 和 它 打 开 的 文 件 或 命 令 之 间 传 递 数 据 。 这 样 的例 子 可 以 在 第 二 十 三 章 的 “ 在 有 限 制 的 权 限 下 访 问 命 令 和 文 件 ” 节 找 到 。分 裂 的 文 件 打 开 的 一 个 创 造 性 的 用 法 是 过 滤 你 自 己 的 输 出 。 有 些 算 法 用 两 个 独 立 的 回 合 来 实现 要 远 比 用 一 个 回 合 实 现 来 得 简 单 。 下 面 是 一 个 简 单 的 例 子 , 我 们 通 过 把 自 己 的 正 常 输 出 放到 一 个 管 道 里 模 拟 Unix 的 tee(1) 程 序 。 在 管 道 的 另 外 一 端 的 代 理 进 程 ( 我 们 自 己 的 子过 程 之 一 ) 把 我 们 的 输 出 分 发 到 所 声 明 的 所 有 文 件 中 去 。tee("/tmp/foo", "/tmp/bar", "/tmp/glarch");while() {print "$ARGV at line $. => $_";}close(STDOUT)or die "can't close STDOUT:$!";sub tee {my @output = @_;my @handles = ();for my $path (@output) {my $fh;# open 会 填 充 这 些429


unless (open ($fh, ">", $path)) {warn "cannot write to $path: $!";next;}push @handles, $fh;}# 在 父 进 程 的 STDOUT 里 重 新 打 开 并 且 返 回return if my $pid = open(STDOUT, "|-");die "cannot fork: $!" unless defined $pid;# 在 子 进 程 里 处 理 STDINwhile() {for my $fh (@handles) {print $fh $_ or die "tee output failed:$!";}}for my $fh (@handles) {close($fh) or die "tee closing failed: $!";}exit; # 不 让 子 进 程 返 回 到 主 循 环 !}430


你 可 以 不 停 地 重 复 使 用 这 个 技 巧 , 而 且 在 你 的 输 出 流 上 放 你 希 望 的 任 意 多 的 过 滤 器 。 只 要 不停 调 用 那 个 分 裂 打 开 STDOUT 的 函 数 , 并 且 让 子 进 程 从 它 的 父 进 程 ( 它 认 为 是 STDIN)里 读 取 数 据 , 然 后 把 消 息 输 出 给 流 里 面 的 下 一 个 函 数 。这 种 利 用 分 裂 后 打 开 文 件 的 自 言 自 语 的 另 外 一 个 有 趣 的 应 用 是 从 一 个 糟 糕 的 函 数 中 捕 获 输出 , 那 些 函 数 总 是 把 它 们 的 结 果 输 出 到 STDOUT。 假 设 <strong>Perl</strong> 只 有 printf 但 是 没 有sprintf。 那 么 你 需 要 的 就 是 类 似 反 钩 号 那 样 的 东 西 , 但 却 是 <strong>Perl</strong> 的 函 数 , 而 不 是 外 部 命令 :badfunc("arg"); # TMD, 跑 !$string = forksub(\&badfunc, "arg");@lines = forksub(\&badfunc, "arg");# 把 它 当 作 字 串 捕 获# 当 作 独 立 的 行sub forksub {my $kidpid = open my $self, "-|";defined $kidpid or die "cannot fork: $!";shift->(@_), exitunless $kidpid;local $/unless wantarray;return ;# 当 退 出 范 围 的 时 候 关 闭}我 们 不 能 说 这 么 做 最 好 , 一 个 捆 绑 的 文 件 句 柄 可 能 更 快 一 点 。 但 是 如 果 你 比 你 的 计 算 机 更 着急 , 那 么 这 个 方 法 更 容 易 写 代 码 。16.3.3 双 向 通 讯尽 管 在 单 向 通 讯 中 , 用 open 与 另 外 一 条 命 令 通 过 管 道 运 转 得 很 好 , 但 是 双 向 通 讯 该 怎 么办 ? 下 面 这 种 方 法 实 际 上 行 不 通 :open(PROG_TO_READ_AND_WRITE, "| some program |") # 错 !而 且 如 果 你 忘 记 打 开 警 告 , 那 么 你 就 会 完 全 错 过 诊 断 信 息 :Can't do bidirectional pipe at myprog line 3.431


open 函 数 不 允 许 你 这 么 干 , 因 为 这 种 做 法 非 常 容 易 导 致 死 锁 , 除 非 你 非 常 小 心 。 但 是 如 果你 决 定 了 , 那 么 你 可 以 使 用 标 准 的 IPC::Open2 库 模 块 , 用 这 个 模 块 给 一 个 子 过 程 的STDIN 和 STDOUT 附 着 两 个 管 道 。 还 有 一 个 IPC::Open3 模 块 用 于 三 通 I/O( 还 允 许你 捕 获 子 进 程 的 STDERR), 但 这 个 模 块 需 要 一 个 笨 拙 的 select 循 环 或 者 更 方 便 一 些 的IO:Select 模 块 。 不 过 那 样 你 就 得 放 弃 <strong>Perl</strong> 的 缓 冲 的 输 入 操 作 ( 比 如 , 读 一 行 )。下 面 是 一 个 使 用 open2 的 例 子 :use IPC::Open2;local (*Reader, *Writer);$pid = open2(\*Reader, \*Writer, "bc -l");$sum = 2;for (1 .. 5) {print Writer "$sum * $sum\n";chomp($sum = );}close Writer;close Reader;waitpid($pid, 0);print "sum is $sum\n";你 还 可 以 自 动 激 活 词 法 句 柄 :my ($fhread, $fhwrite);$pid = open2($fhread, $fhwrite, "cat -u -n");这 个 方 法 的 普 遍 的 问 题 就 是 标 准 I/O 缓 冲 实 在 是 会 破 坏 你 的 好 事 。 即 使 你 的 输 出 文 件 句 柄是 自 动 刷 新 的 ( 库 为 你 做 这 些 事 情 ), 这 样 另 一 端 的 进 程 将 能 及 时 地 收 到 你 的 数 据 , 但 是 通常 你 没 法 强 迫 它 也 返 回 这 样 的 风 格 。 在 一 些 特 殊 的 情 况 下 我 们 是 很 幸 运 的 ,bc 可 以 在 管 道的 模 式 下 运 行 , 而 且 还 会 刷 新 每 个 输 出 行 。 但 是 只 有 很 少 的 几 条 命 令 是 这 样 设 计 的 , 因 此 这个 方 法 很 少 管 用 , 除 非 你 自 己 就 是 双 向 管 道 的 另 外 一 端 的 程 序 的 作 者 。 甚 至 是 简 单 而 且 明 确432


的 ftp 这 样 的 交 互 式 程 序 也 会 在 这 里 失 败 , 因 为 它 们 不 会 在 管 道 上 做 行 缓 冲 。 它 们 只 会 在一 个 tty 设 备 上 这 么 干 。CPAN 上 的 IO:Pty 和 Expect 模 块 可 以 在 这 方 面 做 协 助 , 因 为 它 们 提 供 真 正 的 tty( 实际 上 是 一 个 真 正 的 伪 tty, 不 过 看 起 来 和 真 的 一 样 )。 它 们 让 你 可 以 获 取 其 他 进 程 的 缓 冲 行而 不 用 修 改 那 些 程 序 。如 果 你 把 你 的 程 序 分 裂 成 几 个 进 程 并 且 想 让 它 们 都 能 进 行 双 向 交 流 , 那 么 你 不 能 使 用 <strong>Perl</strong>的 高 端 管 道 接 口 , 因 为 那 些 接 口 都 是 单 向 通 讯 的 。 你 需 要 使 用 两 个 低 层 的 pipe 函 数 调 用 ,每 个 处 理 一 个 方 向 的 交 谈 :pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!";pipe(FROM_CHILD, TO_PARENT)or die "pipe:$!";select((select(TO_CHILD), $| = 1))[0]);select((select(TO_PARENT), $| = 1))[0]);# 自 动 刷 新# 自 动 刷 新if ($pid = fork) {close FROM_PARENT; close TO_PARENT;print TO_CHILD "Parent Pid $$ is sending this\n";chomp($line = );print "Parent Pid $$ just read this: `$line'\n";close FROM_CHILD; close TO_CHILD;waitpid($pid, 0);} else {die "cannot fork: $!" unless defined $pid;close FROM_CHILD; close TO_CHILD;chomp($line = );print "Child Pid $$ just read this: `$line'\n";print TO_PARENT "Child Pid $$ is sending this\n";433


close FROM_PARENT; close TO_PARENT;exit;}在 许 多 Unix 系 统 上 , 你 实 际 上 不 必 用 两 次 独 立 的 pipe 调 用 来 实 现 父 子 进 程 之 间 的 全 双工 的 通 讯 。socketpair 系 统 调 用 给 在 同 一 台 机 器 上 的 相 关 进 程 提 供 了 双 向 的 联 接 。 所 以 ,除 了 用 两 个 pipe 以 外 , 你 还 可 以 只 用 一 个 socketpair。use Socket;socketpair(Child, Parent,AF_UNIX, SOCK_STREAM, PF_UNSPEC)or die "socketpair: $!";# 或 者 让 perl 给 你 选 择 文 件 句 柄my ($kidfh, $dadfh);socketpair($kidfh, $dadfh, AF_UNIX, SOCK_STREAM, PF_UNSPEC)or die "socketpair: $!";在 fork 之 后 , 父 进 程 关 闭 Parent 句 柄 , 然 后 通 过 Child 句 柄 读 写 。 同 时 子 进 程 关 闭Child 句 柄 , 然 后 通 过 Parent 句 柄 读 写 。如 果 你 正 在 寻 找 双 向 通 讯 的 方 法 , 而 且 是 因 为 你 准 备 交 流 的 对 方 实 现 了 标 准 的 互 联 网 服 务 ,那 么 你 通 常 应 该 先 忽 略 这 些 中 间 人 并 且 使 用 专 为 那 些 目 的 设 计 的 CPAN 模 块 。( 参 阅 本 章稍 后 “ 套 接 字 ” 节 获 取 它 们 的 列 表 。)16.3.4 命 名 管 道一 个 命 名 管 道 ( 通 常 称 做 FIFO) 是 为 同 一 台 机 器 上 不 相 关 的 进 程 之 间 建 立 交 流 的 机 制 。“ 命名 ” 管 道 的 名 字 存 在 于 文 件 系 统 中 , 实 际 上 就 是 在 文 件 系 统 的 名 字 空 间 中 放 一 个 特 殊 的 文 件 ,而 在 文 件 背 后 不 是 磁 盘 , 而 是 另 外 一 个 进 程 ( 注 : 你 可 以 对 Unix 域 套 接 字 干 一 样 的 事 情 ,不 过 你 不 能 对 它 们 用 open)。 命 名 管 道 只 不 过 是 一 个 有 趣 的 叫 法 而 已 。434


如 果 你 想 把 一 个 进 程 和 一 个 不 相 干 的 进 程 联 接 起 来 , 那 么 FIFO 就 很 方 便 。 如 果 你 打 开 一 个FIFO, 你 的 进 程 会 阻 塞 住 直 到 对 端 也 有 进 程 打 开 它 为 止 。 因 此 如 果 一 个 读 进 程 先 打 开FIFO, 那 么 它 会 一 直 阻 塞 到 写 进 程 出 现 -- 反 之 亦 然 。要 创 建 一 个 命 名 管 道 , 使 用 一 个 POSIX mkfifo 函 数 -- 也 就 是 说 你 用 的 必 须 是 POSIX 系统 。 在 Microsoft 系 统 上 , 你 就 要 看 看 Win32::Pipe 模 块 了 , 尽 管 看 名 字 好 象 意 思 正 相反 , 实 际 上 它 创 建 的 是 命 名 管 道 。(Win32 用 户 和 我 们 其 他 人 一 样 用 pipe 创 建 匿 名 管 道 。)比 如 , 假 设 你 想 把 你 的 .signature 文 件 在 每 次 读 取 的 时 候 都 有 不 同 的 内 容 。 那 么 只 要 把它 作 成 一 个 命 名 管 道 , 然 后 在 另 外 一 端 应 一 个 <strong>Perl</strong> 程 序 守 着 , 每 次 读 取 的 时 候 都 生 成 一 个不 同 的 数 据 就 可 以 了 。 然 后 每 当 有 任 何 程 序 ( 比 如 邮 件 程 序 , 新 闻 阅 读 器 ,finger 程 序 等等 ) 试 图 从 那 个 文 件 中 读 取 数 据 的 时 候 , 该 程 序 都 会 与 你 的 程 序 相 联 并 且 读 取 一 个 动 态 的 签名 。在 下 面 的 例 子 里 , 我 们 使 用 很 少 见 的 -p 文 件 测 试 器 来 判 断 某 人 ( 或 某 物 ) 是 否 曾 不 小 心删 除 了 我 们 的 FIFO。( 注 : 另 外 一 个 用 途 是 看 看 一 个 文 件 句 柄 是 否 与 一 个 命 名 的 或 者 匿 名的 管 道 相 联 接 , 就 象 -p STDIN。) 如 果 FIFO 被 删 除 了 , 那 么 就 没 有 理 由 做 打 开 尝 试 ,因 此 我 们 把 这 个 看 作 退 出 请 求 。 如 果 我 们 使 用 简 单 的 用 ">$fpath" 模 式 的 open 函 数 ,那 么 就 存 在 一 个 微 小 的 冲 突 条 件 : 如 果 在 -p 测 试 和 打 开 文 件 之 间 文 件 消 失 了 , 那 么 它 会不 小 心 创 建 成 为 一 个 普 通 平 面 文 件 。 我 们 也 不 能 使 用 "+


POSIX::mkfifo($fpath, 0666) or die "can't mknod $fpath: $!";warn "$0: created $fpath as a named pipe\n";}}while (1) {# 如 果 签 名 文 件 被 手 工 删 除 则 退 出die "Pipe file disappeared" unless -p $fpath;# 下 一 行 阻 塞 住 直 到 有 读 者 出 现sysopen(FIFO, $fpath, O_WRONLY)or die "can't write $fpath: $!";print FIFO "John Smith (smith\@host.org)\n", `fortune -s`;close FIFO;select(undef, undef, undef, 0.2);# 睡 眠 1/5 秒}关 闭 之 后 的 短 暂 的 睡 眠 是 为 了 给 读 者 一 个 机 会 读 取 已 经 写 了 的 数 据 。 如 果 我 们 只 是 马 上 循 环出 去 然 后 再 次 打 开 FIFO, 而 读 者 还 没 有 完 成 刚 刚 发 送 的 数 据 读 取 , 那 么 就 不 会 发 送end-of-file, 因 为 写 入 进 程 已 经 成 为 历 史 了 。 我 们 既 要 一 圈 一 圈 循 环 , 也 要 在 每 次 叙 述 过程 中 让 写 入 者 比 读 者 略 微 慢 一 些 , 这 样 才 能 让 读 者 最 后 看 到 难 得 的 end-of-file。( 而 且 我们 还 担 心 冲 突 条 件 ?)16.4. System V IPC每 个 人 都 讨 厌 System V IPC。 它 比 打 孔 纸 带 还 慢 , 使 用 与 文 件 系 统 完 全 无 关 少 得 可 怜 的名 字 空 间 , 使 用 人 类 讨 厌 的 数 字 给 它 的 对 象 命 名 , 并 且 还 常 常 自 己 忘 记 自 己 的 对 象 , 你 的 系统 管 理 员 经 常 需 要 用 ipcs(1) 查 找 那 些 丢 失 了 的 对 象 并 且 用 ipcrm(1) 删 除 它 们 , 还 得 求老 天 保 佑 不 要 在 用 光 内 存 以 后 才 发 现 问 题 。436


尽 管 有 这 些 痛 苦 , 古 老 的 Sys IPC 仍 然 有 好 几 种 有 效 的 用 途 。 三 种 IPC 对 象 是 共 享 内 存 ,信 号 灯 和 消 息 。 对 于 传 送 消 息 , 目 前 套 接 字 是 更 好 的 机 制 , 而 且 移 植 性 也 更 好 。 对 于 简 单 的信 号 灯 用 途 , 用 文 件 系 统 更 好 。 对 于 共 享 内 存 -- 现 在 还 有 点 问 题 。 如 果 你 的 系 统 支 持 , 用 更现 代 的 mmap(2) 更 好 ,( 注 : 在 CPAN 上 甚 至 有 一 个 Mmap 模 块 。) 但 是 这 些 实 现的 质 量 因 系 统 而 异 。 而 且 它 还 需 要 更 小 心 一 些 , 以 免 让 <strong>Perl</strong> 从 mmap(2) 放 你 的 字 串 的位 置 再 次 分 配 它 们 。 但 当 程 序 员 准 备 使 用 mmap(2) 的 时 候 , 他 们 会 听 到 那 些 常 用 这 个 的老 鸟 嘀 咕 , 说 自 己 是 如 何 如 何 地 绕 开 那 些 没 有 “ 统 一 缓 冲 缓 存 ”( 或 者 叫 “ 统 一 总 线 捕 蝇 器 ”)的 系 统 上 的 缓 冲 一 致 性 问 题 , 还 有 , 他 们 讲 他 们 知 道 的 问 题 要 比 描 述 他 们 不 懂 的 东 西 说 得 还要 好 , 然 后 新 手 就 赶 快 退 回 Sys V IPC 并 且 憎 恨 所 有 他 们 必 须 使 用 的 共 享 内 存 。下 面 是 一 个 小 程 序 , 它 演 示 了 一 窝 兄 妹 进 程 对 一 个 共 享 内 存 缓 冲 的 有 控 制 的 访 问 。Sys VIPC 对 象 也 可 以 在 同 一 台 计 算 机 的 不 相 关 的 进 程 之 间 共 享 , 不 过 那 样 的 话 你 就 得 想 出 它 们相 互 之 间 找 到 对 方 的 方 法 。 为 了 保 证 安 全 的 访 问 , 我 们 将 为 每 块 内 存 创 建 一 个 信 号 灯 。( 注 :可 能 给 每 块 内 存 创 建 一 对 信 号 灯 更 现 实 : 一 个 用 于 读 而 另 外 一 个 用 于 写 , 而 且 实 际 上 , 这 就是 在 CPAN 上 的 IPC::Shareable 模 块 所 用 的 方 法 。 但 是 我 们 想 在 这 里 保 持 简 单 些 。 不过 我 们 要 承 认 , 如 果 使 用 一 对 信 号 灯 , 你 就 可 以 利 用 好 SysV ? IPC 唯 一 的 优 点 : 你 可 以 在整 个 信 号 灯 集 上 执 行 原 子 操 作 , 就 好 象 它 们 是 一 个 单 元 一 样 , 这 一 点 有 时 候 很 有 用 。)每 当 你 想 获 取 或 者 写 入 新 值 到 共 享 内 存 里 面 , 你 就 必 须 先 通 过 信 号 灯 这 一 关 。 这 个 过 程 可 能非 常 乏 味 , 所 以 我 们 将 把 访 问 封 装 在 一 个 对 象 类 里 面 。 IPC::Shareable 更 进 一 步 , 把 它的 对 象 封 装 在 一 个 tie 接 口 里 。下 面 的 程 序 会 一 直 运 行 , 直 到 你 用 一 个 Control-C 或 者 相 当 的 东 西 终 止 它 :#! /usr/bin/perl -wuse v5.6.0;# 或 者 更 新use strict;use sigtrap wq(die INT TERM HUP QUIT);my $PROGENY= shift(@ARGV) || 3;eval { main() };# 参 阅 下 面 的 DESTROY 找 原 因die if $@ && $@ !~ /^Caught a SIG/;print "\nDone.\n";exit;437


sub main{my $mem = ShMem->alloc("Original Creation at " . localtime);my (@kids, $child);$SIG{CHLD} = 'IGNORE';for (my $unborn = $PROGENY; $unborn > 0; $unborn--) {if ($child = fork) {print "$$ begat $child\n";next;}die "cannot fork: $!" unless defined $child;eval {while (1) {$mem->lock();$mem->poke("$$ " . localtime)unless $mem->peek =~ /^$$\b/o;$mem->unlock();}};die if $@ && $@ !~ /^CAught a SIG/;exit;# 子 进 程 退 出}while (1) {print "Buffer is ", $mem->get, "\n";sleep 1;438


}}下 面 是 ShMem ? 包 , 就 是 上 面 程 序 用 的 东 西 。 你 可 以 把 它 直 接 贴 到 程 序 的 末 尾 , 或 者 把它 放 到 自 己 的 文 件 里 ,( 在 结 尾 放 一 个 "1;") 然 后 在 主 程 序 里 require 它 。( 用 到 的 这 两个 IPC 模 块 以 后 会 在 标 准 的 <strong>Perl</strong> 版 本 里 找 到 。)package ShMem;use IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRWXU);use IPC::Semaphore;sub MAXBUF() { 2000 }sub alloc {# 构 造 方 法my $class = shift;my $value = @_ ? shift : ' ';my $key = shmget(IPC_PRIVATE, MAXBUF, S_IRWXU) or die "shmget: $!";my $sem = IPC::Semaphore->new(IPC_PRIVATE, 1, S_IRWXU| IPC_CREAT)or die "IPC::Semaphore->new: $!";$sem->setval(0,1) or die "sem setval: $!";my $self = bless {OWNER => $$,SHMKEY => $key,SEMA => $sem,} => $class;439


$self->put($value);return $self;}下 面 是 抓 取 和 存 储 方 法 。get 和 put 方 法 锁 住 缓 冲 区 , 但 是 peek 和 poke 不 会 , 因 此后 面 两 个 只 有 在 对 象 被 手 工 锁 住 的 时 候 才 能 用 —— 当 你 想 检 索 一 个 旧 的 数 值 并 且 存 回 一 个修 改 过 的 数 值 , 并 且 所 有 都 处 于 同 一 把 锁 的 时 候 你 就 必 须 手 工 上 锁 。 演 示 程 序 在 它 的 while(1) 循 环 里 做 这 些 工 作 。 整 个 事 务 必 须 在 同 一 把 锁 里 面 发 生 , 否 则 测 试 和 设 置 就 不 可 能 是 原子 化 的 , 并 且 可 能 爆 炸 。sub get {my $self = shift;$self->lock;my $value = $self->peek(@_);$self->unlock;return $value;}sub peek {my $self = shift;shmread($self->{SHMKEY}, my $buff=' ', 0, MAXBUF) or die "shmread: $!";substr($buff, index($buff, "\0")) = ' ';return $buff;}sub put {my $self = shift;$self->lock;440


$self->poke(@_);$self->unlock;}sub poke {my($self, $msg) = @_;shmwrite($self->{SHMKEY}, $msg, 0, MAXBUF) or die "shmwrite: $!";}sub lock {my $self = shift;$self->{SEMA}->op(0,-1,0) or die "semop: $!";}sub unlock {my $self = shift;$self->{SEMA}->op(0,1,0) or die "semop: $!";}最 后 , 此 类 需 要 一 个 析 构 器 , 这 样 当 对 象 消 失 的 时 候 , 我 们 可 以 手 工 释 放 那 些 存 放 在 对 象 内部 的 共 享 内 存 和 信 号 灯 。 否 则 , 它 们 会 活 得 比 它 们 的 创 造 者 长 , 因 而 你 不 得 不 用 ipcs 和ipcrm ( 或 者 一 个 系 统 管 理 员 ) 来 删 除 它 们 。 这 也 是 为 什 么 我 们 在 主 程 序 里 精 心 设 计 了 把信 号 转 换 成 例 外 的 封 装 的 原 因 : 在 那 里 才 能 运 行 析 构 器 ,SysV IPC 对 象 才 能 被 释 放 , 并且 我 们 才 不 需 要 系 统 管 理 员 。sub DESTROY {my $self = shift;return unless $self->{OWNER} == $$;# 避 免 复 制 释 放441


shmctl ($self->{SHMKEY}, IPC_RMID, 0) or warn "shmctl RMID: $!";$self->{SEMA}->remove() or warn "sema->remove: $!";}16.5. 套 接 字我 们 早 先 讨 论 的 IPC 机 制 都 有 一 个 非 常 严 重 的 局 限 : 它 们 是 设 计 用 来 在 运 行 在 同 一 台 计 算机 上 的 进 程 之 间 通 讯 用 的 。( 即 使 有 时 候 文 件 可 以 在 机 器 之 间 通 过 象 NFS 这 样 的 机 制 共 享 ,但 是 在 许 多 NFS 实 现 中 锁 都 会 奇 怪 地 失 败 , 这 样 实 际 上 就 不 可 能 对 文 件 进 行 并 发 访 问 了 。)对 于 通 用 目 的 的 网 络 通 讯 , 套 接 字 是 最 好 的 办 法 。 尽 管 套 接 字 是 在 BSD 里 发 明 的 , 但 它们 很 快 就 传 播 到 其 他 类 型 的 Unix 里 去 了 , 并 且 现 在 你 几 乎 可 以 在 可 以 在 任 何 能 用 的 操 作系 统 里 找 到 它 。 如 果 你 的 机 器 上 没 有 套 接 字 , 那 么 你 想 使 用 互 联 网 的 话 就 会 碰 到 无 数 的 麻 烦 。利 用 套 接 字 , 你 既 可 以 使 用 虚 电 路 ( 象 TCP 流 ) 也 可 以 使 用 数 据 报 ( 象 UDP 包 )。 你 甚至 可 以 做 得 更 多 , 取 决 于 你 的 系 统 。 不 过 最 常 见 的 套 接 字 编 程 用 的 是 基 于 互 联 网 际 的 套 接 字TCP, 因 此 我 们 在 这 里 介 绍 这 种 类 型 的 套 接 字 。 这 样 的 套 接 字 提 供 可 靠 的 联 接 , 运 行 起 来 有点 象 双 向 管 道 , 但 是 并 不 局 限 于 本 地 机 器 。 互 联 网 的 两 个 杀 手 级 的 应 用 ,email 和 web 浏览 , 都 几 乎 是 完 全 依 赖 于 TCP 套 接 字 的 。你 还 在 不 知 情 的 情 况 下 很 频 繁 地 使 用 了 UDP。 每 次 你 的 机 器 试 图 访 问 互 联 网 上 的 一 台 主机 , 它 都 向 你 的 DNS 服 务 器 发 送 一 个 UDP 包 请 求 其 真 实 的 IP 地 址 。 如 果 你 想 发 送 和接 收 数 据 报 , 那 么 你 也 可 以 自 己 使 用 UDP。 数 据 报 比 TCP 更 经 济 是 因 为 它 们 不 是 面 向 联接 的 ; 也 就 是 说 , 它 们 不 太 象 打 电 话 倒 是 象 发 信 件 。 但 是 UDP 同 样 也 缺 乏 TCP 提 供 的 可靠 性 , 这 样 它 就 更 适 合 那 些 你 不 在 乎 是 否 有 一 两 个 包 丢 掉 , 多 出 来 , 或 者 坏 掉 , 或 者 是 你 知道 更 高 层 的 协 议 将 强 制 某 种 程 度 的 冗 余 (DNS 就 是 这 样 ) 的 场 合 。还 有 其 他 选 择 , 但 是 非 常 少 见 。 你 可 以 使 用 Unix 域 套 接 字 , 但 是 只 能 用 于 本 地 通 讯 。 有许 多 其 他 系 统 支 持 各 种 其 他 的 并 非 基 于 IP 的 协 议 。 毫 无 疑 问 一 些 地 方 的 一 些 人 会 对 它 们 感兴 趣 , 但 是 我 们 将 只 会 略 微 提 到 它 们 。<strong>Perl</strong> 里 面 处 理 套 接 字 的 函 数 和 C 里 面 的 对 应 系 统 调 用 同 名 , 不 过 参 数 有 些 区 别 , 原 因 有二 : 首 先 ,<strong>Perl</strong> 的 文 件 句 柄 和 C 的 文 件 描 述 符 的 工 作 机 制 不 同 ; 第 二 ,<strong>Perl</strong> 已 经 知 道 它的 字 串 的 长 度 , 所 以 你 不 需 要 传 递 这 个 信 息 。 参 阅 第 二 十 九 章 获 取 关 于 与 套 接 字 相 关 的 系 统调 用 的 详 细 信 息 。老 的 <strong>Perl</strong> 的 套 接 字 代 码 有 一 个 问 题 是 人 们 会 使 用 硬 代 码 数 值 做 常 量 传 递 到 套 接 字 函 数 里 ,那 样 就 会 破 坏 移 植 性 。 和 大 多 数 系 统 调 用 一 样 , 与 套 接 字 相 关 的 系 统 调 用 在 失 败 的 时 候 会 礼貌 而 安 静 地 返 回 undef, 而 不 是 抛 出 一 个 例 外 。 因 此 检 查 这 些 函 数 的 返 回 值 是 很 重 要 的 ,442


因 为 如 果 你 传 一 些 垃 圾 给 它 们 , 它 们 也 不 会 大 声 叫 嚷 的 。 如 果 你 曾 经 看 到 任 何 明 确 地 设 置$AF_INET = 2 的 代 码 , 你 就 知 道 你 要 面 对 大 麻 烦 了 。 一 个 更 好 的 方 法 ( 好 得 没 法 比 ) 是使 用 Socket 模 块 或 者 是 更 友 善 一 些 的 IO::Socket 模 块 , 它 们 俩 都 是 标 准 模 块 。 这 些 模块 给 你 提 供 了 设 置 服 务 器 和 客 户 端 所 需 要 的 各 种 常 量 和 协 助 函 数 。 为 了 最 大 可 能 地 成 功 , 你的 套 接 字 程 序 应 该 象 下 面 这 样 开 头 ( 不 要 忘 记 给 服 务 器 的 程 序 里 加 -T 开 关 打 开 错 误 检查 ):#! /usr/bin/perl -wuse strict;use sigtrap;use Socket;# 或 者 IO:Socket正 如 我 们 在 其 他 地 方 说 明 的 一 样 ,<strong>Perl</strong> 依 靠 你 的 C 库 实 现 它 的 大 部 分 系 统 特 性 , 而 且 不是 所 有 系 统 都 支 持 所 有 的 套 接 字 类 型 。 所 以 最 安 全 的 可 能 办 法 就 是 只 做 普 通 的 TCP 和UDP 套 接 字 操 作 。 比 如 , 如 果 你 希 望 你 的 代 码 有 机 会 能 够 移 植 到 那 些 你 还 没 有 想 过 的 系 统上 , 千 万 别 假 设 那 台 系 统 支 持 可 靠 的 顺 序 报 文 协 议 。 也 别 想 在 不 相 关 的 进 程 之 间 通 过 本 地Unix 域 套 接 字 传 递 打 开 的 文 件 描 述 符 。( 的 确 , 你 可 以 在 许 多 Unix 机 器 上 做 那 些 事 情 --参 阅 你 本 机 的 recvmsg(2) 手 册 页 。)如 果 你 只 是 想 使 用 一 个 标 准 的 互 联 网 服 务 , 比 如 邮 件 , 新 闻 组 , 域 名 服 务 ,FTP,Telnet,Web 等 等 , 那 么 你 不 用 从 零 开 始 , 先 试 者 使 用 现 有 的 CPAN 模 块 。 为 这 些 目 的 设 计 的 打了 包 的 模 块 包 括 Net::SMTP( 或 者 Mail::Mailer),Net::NNTP,Net::DNS,Net::FTP,Net::Telnet, 和 各 种 各 样 的 HTTP 相 关 的 模 块 。 有 关 CPAN 上 的 模 块 , 你 可 能 要 查 看一 下 第 五 节 的 网 络 和 IPC, 第 十 五 节 的 WWW 相 关 模 块 , 以 及 第 十 六 节 的 服 务 器 和 守 护进 程 应 用 。在 随 后 的 一 节 里 , 我 们 将 讲 述 几 个 简 单 的 客 户 和 服 务 器 的 例 子 , 但 是 我 们 不 会 对 所 使 用 的 函数 做 过 多 的 解 释 , 因 为 那 样 会 和 我 们 将 在 第 二 十 九 章 里 的 描 述 重 合 很 大 部 分 。16.5.1 网 络 客 户 端 程 序如 果 你 需 要 在 可 能 是 不 同 机 器 之 间 的 可 靠 的 客 户 端 - 服 务 器 通 讯 , 那 么 请 用 互 联 网 域 套 接 字 。要 在 什 么 地 方 创 建 一 个 TCP 与 服 务 器 联 接 的 TCP 客 户 端 , 最 简 单 的 方 法 通 常 是 使 用 标 准的 IO::Socket::INET 模 块 :use IO::Socket::INET;443


$socket = IO::Socket::INET->new(PeerAddr => $remote_host,PeerPort => $remote_port,Proto => "tcp",Type => SOCK_STREAM)or die "Couldn't connect to $remote_host:$remote_port: $!\n";# 通 过 套 接 字 发 送 某 些 东 西 ,print $soket "Why don't you call me anymore?\n";# 读 取 远 端 响 应$answer = ;# 然 后 在 结 束 之 后 终 止 联 接 。close($socket);如 果 你 只 有 主 机 名 和 端 口 号 准 备 联 接 , 并 且 愿 意 在 其 他 域 里 使 用 缺 省 的 字 段 , 那 么 调 用 这 个函 数 的 缩 写 形 式 也 是 很 好 的 :$socket = IO::Socket::INET->new("www.yahoo.com:80")or die "Couldn't connect to port 80 of yahoo: $!";如 果 使 用 基 本 的 Socket 模 块 联 接 :use Socket;# 创 建 一 个 套 接 字socket(Server, PF_INET, SOCK_STREAM, getprotobyname('tcp'));444


# 制 作 远 端 机 器 的 地 址$internet_addr = inet_aton($remote_host)or die "Couldn't convert $remote_host into an Internet address: $!\n";$paddr = sockaddr_in($remote_port, $internet_addr);# 联 接connect(Server, $paddr)or die "Couldn't connect to $remote_host:$remote_port: $!\n";select((select(Server), $| = 1)[0]);# 打 开 命 令 缓 冲# 通 过 套 接 字 发 送 一 些 东 西print Server "Why don't you call me anymore?\n";# 读 取 远 端 的 响 应$answer = ;# 处 理 完 之 后 终 止 联 接close(Server);如 果 你 想 只 关 闭 你 方 的 联 接 , 这 样 远 端 就 可 以 得 到 一 个 end-of-file, 不 过 你 还 是 能 够 读 取来 自 该 服 务 器 的 数 据 , 使 用 shutdown 系 统 调 用 进 行 半 关 闭 :# 不 再 向 服 务 器 写 数 据shutdown(Server, 1);# 在 v5.6 里 的 Socket::SHUT_WR 常 量445


16.5.2 网 络 服 务 器下 面 是 和 前 面 的 客 户 端 对 应 的 服 务 器 。 如 果 用 标 准 的 IO::Socket::INET 类 来 实 现 是 相 当简 单 的 活 :use IO::Socket::INET;$server = IO::Socket::INET->new(LocalPort => $server_port,Type=> SOCK_STREAM,Reuse => 1,Listen => 10) # 或 者 SOMAXCONNor die "Couldn't be a tcp server on port $server_port: $!\n";while ($client = $server->accept()) {# $client 是 新 联 接}close($server);你 还 可 以 用 低 层 Socket 模 块 来 写 这 些 东 西 :use Socket;# 创 建 套 接 字socket( Server, PF_INET, SOCK_STREAM, getprotobyname('tcp'));# 为 了 我 们 可 以 快 速 重 起 服 务 器setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, 1);446


# 制 作 自 己 的 套 接 字 地 址$my_addr = sockaddr_in($server_port, INADDR_ANY);bind(Server, $my_addr)or die "Couldn't bind to port $server_port: $!\n";# 为 来 访 联 接 建 立 一 个 队 列listen(Server, SOMAXCONN)or die "Couldn't listen on port $server_port: $!\n";# 接 受 并 处 理 任 何 联 接while(accept(Client, Server)) {# 在 新 建 的 客 户 端 联 接 上 做 些 处 理}close(Server);客 户 端 不 需 要 和 任 何 地 址 bind ( 绑 定 ), 但 是 服 务 器 需 要 这 么 做 。 我 们 把 它 的 地 址 声 明为 INADDR_ANY, 意 味 着 客 户 端 可 以 从 任 意 可 用 的 网 络 接 口 上 来 , 如 果 你 想 固 定 使 用 某个 接 口 ( 比 如 一 个 网 关 或 者 防 火 墙 机 器 ), 那 么 使 用 该 机 器 的 真 实 地 址 。( 客 户 端 也 可 以 这么 干 , 不 过 很 少 必 须 这 么 干 。)如 果 你 想 知 道 是 哪 个 机 器 和 你 相 联 , 你 可 以 在 客 户 联 接 上 调 用 getpeername。 这 样 返 回一 个 IP 地 址 , 你 可 以 自 己 把 它 转 换 成 一 个 名 字 ( 如 果 你 能 ):use Socket;$other_end = getpeername(Client)or die "Couldn't identify other end: $!\n";447


($port, $iaddr) = unpack_sockaddr_in($other_end);$actual_ip = inet_ntoa($iaddr);$claimed_hostname = gethostbyaddr($iaddr, AF_INET);这 个 方 法 一 般 都 可 以 欺 骗 过 去 , 因 为 该 IP 地 址 的 所 有 者 可 以 把 它 们 的 反 向 域 名 表 设 置 为 任何 它 们 想 要 的 东 西 。 为 了 略 微 增 强 信 心 , 从 另 外 一 个 方 向 再 转 换 一 次 :@name_lookup = gethostbyname($claimed_hostname)or die "Could not reverse $claimed_hostname: $!\n";@resolved_ips = map { inet_ntoa($_) } @name_lookup[ 4 .. $#name_lookup ];$might_spoof = !grep { $actual_ip eq $_ } @resolved_ips;一 旦 一 个 客 户 端 与 你 的 服 务 器 相 联 , 你 的 服 务 器 就 可 以 对 那 个 客 户 端 句 柄 进 行 I/O。 不 过因 为 你 的 服 务 器 忙 着 做 这 件 事 , 所 以 它 不 能 在 给 来 自 其 他 客 户 端 的 请 求 提 供 服 务 了 。 为 了 避免 每 次 只 能 给 一 个 客 户 端 服 务 , 许 多 服 务 器 都 是 马 上 fork 一 个 自 己 的 克 隆 来 处 理 每 次 到 来的 联 接 。( 其 他 的 服 务 器 预 先 fork, 或 者 使 用 select 系 统 调 用 在 多 个 客 户 端 之 间 复 用I/O。)REQUEST:while (accept(Client, Server)) {if ($kidpid = fork) {close Client;# 父 进 程 关 闭 不 用 的 句 柄next REQUEST;}defined($kidpid) or die "cannot fork: $!";close Server;# 子 进 程 关 闭 无 用 的 句 柄select(Client);# 打 印 用 的 新 的 缺 省 句 柄448


$| = 1; # 自 动 刷 新# 预 联 接 的 子 进 程 代 码 与 客 户 端 句 柄 进 行 I/O$inpu t= ;print Client "output\n";# 或 者 STDOUT, 一 样 的 东 西open(STDIN, "&Client") or die "can't dup client: $!";# 运 行 计 算 器 , 就 当 一 个 例 子system("bc -l");# 或 者 任 何 你 喜 欢 的 东 西 , 只 要 它 不 会 逃 逸 出 shellprint "done\n";# 仍 然 是 给 客 户 端clase Client;exit;# 不 让 子 进 程 回 到 accept!}这 个 服 务 器 为 每 个 到 来 的 请 求 用 fork 克 隆 一 个 子 进 程 。 那 样 它 就 可 以 一 次 处 理 许 多 请 求—— 只 要 你 创 建 更 多 的 进 程 。( 你 可 能 想 限 制 这 些 数 目 。) 即 使 你 不 fork,listen 也 会 允许 最 多 有 SOMAXCONN ( 通 常 是 五 个 或 更 多 个 ) 挂 起 的 联 接 。 每 个 联 接 使 用 一 些 资 源 ,不 过 不 象 一 个 进 程 用 的 那 么 多 。 分 裂 出 的 服 务 器 进 程 必 须 仔 细 清 理 它 们 运 行 结 束 了 的 子 进 程( 在 Unix 语 言 里 叫 "zombies" 僵 死 进 程 ), 否 则 它 们 很 快 就 会 填 满 你 的 进 程 表 。 在 “ 信号 ” 一 节 里 的 讨 论 的 REAPER 代 码 会 为 你 做 这 些 事 情 , 或 者 你 可 以 赋 值 $SIG{CHLD} ='IGNORE'。在 运 行 其 他 命 令 之 前 , 我 们 把 标 准 输 入 和 输 出 ( 以 及 错 误 ) 联 接 到 客 户 端 联 接 中 。 这 样 任 何从 STDIN 读 取 输 入 并 且 写 出 到 STDOUT 的 命 令 都 可 以 与 远 端 的 机 器 交 谈 —— 如 果 没 有449


重 新 赋 值 , 这 条 命 令 就 无 法 找 到 客 户 端 句 柄 —— 因 为 客 户 端 句 柄 缺 省 的 时 候 在 exec 范 围外 自 动 关 闭 。如 果 你 写 一 个 网 络 服 务 器 , 我 们 强 烈 建 议 你 使 用 -T 开 关 以 打 开 污 染 检 查 , 即 使 它 们 没 有运 行 setuid 或 者 setgid 也 这 样 干 。 这 个 主 意 对 那 些 服 务 器 和 任 何 代 表 其 他 进 程 运 行 的程 序 ( 比 如 所 有 CGI 脚 本 ) 都 是 好 主 意 , 因 为 它 减 少 了 来 自 外 部 的 人 威 胁 你 的 系 统 的 机 会 。参 阅 第 二 十 三 章 里 “ 操 作 不 安 全 的 数 据 ” 节 获 取 更 多 相 关 信 息 。当 写 互 联 网 程 序 的 时 候 有 一 个 额 外 的 考 虑 : 许 多 协 议 声 明 行 结 束 符 应 该 是 CRLF, 它 可 以 用好 多 种 方 法 来 声 明 :"\015\12", 或 者 "\xd\xa", 或 者 还 可 以 是 chr(13).chr(10)。 对于 版 本 5.6 的 <strong>Perl</strong>, 说 v13.10 也 生 成 一 样 的 字 串 。( 在 许 多 机 器 上 , 你 还 可 以 用 "\r\n"表 示 CRLF, 不 过 如 果 你 想 向 Mac 上 移 植 , 不 要 用 "\r\n", 因 为 在 Mac 上 "\r\n" 的 含义 正 相 反 !) 许 多 互 联 网 程 序 实 际 上 会 把 只 有 "\012" 的 字 串 当 行 结 束 符 , 但 那 是 因 为 互联 网 程 序 总 是 对 它 们 接 收 的 东 西 很 宽 容 而 对 它 们 发 出 的 东 西 很 严 格 。( 现 在 只 有 我 们 能 让 人们 这 么 做 了 ...)16.5.3 消 息 传 递我 们 早 先 提 到 过 ,UDP 通 讯 更 少 过 热 但 是 可 靠 性 也 更 低 , 因 为 它 不 保 证 消 息 会 按 照 恰 当 的顺 序 到 达 —— 或 者 甚 至 不 能 保 证 消 息 能 够 到 达 。 人 们 常 把 UDP 称 做 不 可 靠 的 数 据 报 协 议(Unreliable Datagram Protocol)。不 过 ,UDP 提 供 了 一 些 TCP 所 没 有 的 优 点 , 包 括 同 时 向 一 堆 目 的 主 机 广 播 或 者 多 点 广 播的 能 力 ( 通 常 在 你 的 本 地 子 网 )。 如 果 你 觉 得 自 己 太 关 心 可 靠 性 并 且 开 始 给 你 的 消 息 系 统 做检 查 子 系 统 , 那 么 你 可 能 应 该 使 用 TCP。 的 确 , 建 立 或 者 解 除 一 个 TCP 联 接 开 销 更 大 ,但 是 如 果 你 可 以 用 许 多 消 息 补 偿 ( 或 者 一 条 长 消 息 ) 这 些 , 那 么 也 是 值 得 的 。不 管 怎 么 说 , 下 面 是 一 个 UDP 程 序 的 例 子 。 它 与 在 命 令 行 声 明 的 机 器 的 UDP 时 间 端 口联 接 , 如 果 命 令 行 上 没 有 声 明 机 器 名 , 它 与 它 能 找 到 的 任 何 使 用 统 一 广 播 地 址 的 机 器 联 接 。( 注 : 如 果 还 不 行 , 那 么 运 行 ifconfig -a 找 出 合 适 的 本 地 广 播 地 址 。) 不 是 所 有 机 器 都 打开 了 时 间 服 务 , 尤 其 是 跨 防 火 墙 边 界 的 时 候 , 但 是 那 些 给 你 发 包 的 机 器 会 给 你 按 照 网 络 字 节序 打 包 发 送 一 个 4 字 节 的 整 数 , 表 明 它 认 为 的 时 间 值 。 返 回 的 这 个 时 间 值 是 自 1900 年以 来 的 秒 数 。 你 必 须 减 去 1970 年 和 1900 年 之 间 的 描 述 然 后 才 能 传 给 localtime 或者 gmtime 转 换 函 数 。#! /usr/bin/perl# clockdrift - 与 其 他 系 统 的 时 钟 进 行 比 较# 如 果 没 有 参 数 , 广 播 给 其 他 人 听450


# 等 待 1.5 秒 听 取 回 答use v5.6.0;# 或 者 更 高 版 本use warnings;use strict;use Socket;unshift(@ARGV, inet_ntoa(INADDR_BROCAST))unless @ARGV;socket(my $msgsock, PF_INET, SOCK_DGRAM, getprotobyname("udp"))or die "socket: $!";# 有 些 有 毛 病 的 机 器 需 要 这 个 。 不 会 伤 害 任 何 其 他 的 机 器 。setsockopt($msgsock, SOL_SOCKET, SO_BROADCAST, 1)or die "setsockopt: $!";my $portno = getservbyname("time", "udp")or die "no udp time port";for my $target (@ARGV) {print "Sending to $target: $portno\n";my $destpaddr = sockaddr_in($portno, inet_aton($target));send($msgsock, "x", 0, $destpaddr)451


or die "send: $!";}# 时 间 服 务 返 回 自 1900 年 以 来 以 秒 计 的 32 位 时 间my $FROM_1900_TO_EPOCH = 2_208_988_800;my $time_fmt = "N"; # 并 且 它 以 这 种 二 进 制 格 式 处 理 。my $time_len = length(pack($time_fmt, 1));# 任 何 数 字 都 可 以my $inmask = ' '; # 存 储 用 于 select 文 件 号 位 的 字 串 ,vec($inmask, fileno($msgsock), 1) = 1;# 等 待 半 秒 钟 等 待 输 入 出 现while (select (my $outmask = $inmask, undef, undef, 0.5)) {defined(my $srcpaddr = recv($msgsock, my $bintime, $time_len, 0))or die "recv: $!";my($port, $ipaddr) = sockaddr_in($srcpaddr);my $sendhost = sprintf "%s [%s]",gethostbyaddr($ipaddr, AF_INET) || 'UNKNOWN',inet_ntoa($ipaddr);my $delta = unpack($time_fmt, $bintime) -$FROM_1900_TO_EPOCH - time();print "Clock on $sendhost is $delta seconds ahead of this one.\n";}452


第 十 七 章 线 程并 行 编 程 要 比 看 上 去 要 难 得 多 。 假 设 我 们 从 一 个 烹 饪 书 拿 出 一 条 菜 谱 , 然 后 把 它 转 换成 某 种 几 十 个 厨 师 可 以 同 时 工 作 的 东 西 。 那 么 你 有 两 个 实 现 方 法 。一 个 方 法 是 给 每 个 厨 师 一 个 专 用 的 厨 房 , 给 它 装 备 原 料 和 器 具 。 对 于 那 些 可 以 很 容 易 分 解 的菜 谱 , 以 及 那 些 可 以 很 容 易 从 一 个 厨 房 转 到 另 外 一 个 厨 房 的 食 物 而 言 , 这 个 方 法 很 好 用 , 因为 它 把 不 同 厨 师 分 隔 开 , 互 不 影 响 。另 外 , 你 也 可 以 把 所 有 厨 师 都 放 在 一 个 厨 房 里 , 然 后 让 他 们 把 菜 烧 出 来 , 让 他 们 混 合 使 用 那些 东 西 。 这 样 可 能 会 很 乱 , 尤 其 是 切 肉 机 开 始 飞 转 的 时 候 。这 两 个 方 法 对 应 计 算 机 的 两 种 并 行 编 程 方 法 。 第 一 种 是 Unix 系 统 里 典 型 的 多 进 程 模 型 ,这 种 模 型 里 每 个 控 制 线 索 都 有 自 己 的 一 套 资 源 , 我 们 把 它 们 放 在 一 起 叫 进 程 。 第 二 种 模 型 是多 线 程 模 型 , 这 种 模 型 里 每 个 控 制 线 索 和 其 他 控 制 线 索 共 享 资 源 。 或 者 有 些 场 合 可 能 ( 或 者必 须 ) 不 共 享 ( 资 源 )。我 们 都 知 道 厨 师 喜 欢 掌 勺 ; 这 一 点 我 们 明 白 , 因 为 只 有 让 厨 师 掌 好 勺 才 能 实 现 我 们 想 让 他 们干 的 事 。 但 是 厨 师 也 需 要 有 组 织 , 不 管 用 什 么 方 法 。<strong>Perl</strong> 支 持 上 面 两 种 模 式 的 组 织 形 式 。 本 章 我 们 将 把 它 们 称 为 进 程 模 型 和 线 程 模 型 。17.1 进 程 模 型我 们 不 会 在 这 里 太 多 地 讨 论 进 程 模 型 , 原 因 很 简 单 : 它 遍 及 本 书 的 所 有 其 他 部 分 。<strong>Perl</strong> 起源 于 Unix 系 统 , 所 以 它 浸 满 了 每 个 进 程 处 理 自 己 的 事 情 的 概 念 。 如 果 一 个 进 程 想 并 行 处理 某 些 事 情 , 那 么 逻 辑 上 它 必 须 启 动 一 个 并 行 的 进 程 ; 也 就 是 说 , 它 必 须 分 裂 一 个 重 量 级 的新 进 程 , 它 和 父 进 程 共 享 很 少 东 西 , 除 了 一 些 文 件 描 述 符 以 外 。( 有 时 候 看 起 来 父 进 程 和 子进 程 共 享 很 多 东 西 , 但 大 多 数 都 只 是 在 子 进 程 中 复 制 父 进 程 并 且 在 逻 辑 概 念 上 并 没 有 真 正 共享 什 么 东 西 。 操 作 系 统 为 了 偷 懒 也 会 强 制 那 种 逻 辑 分 离 的 , 这 种 情 况 下 我 们 叫 它 写 时 拷 贝(copy-on-write) 语 意 , 但 是 如 果 我 们 不 首 先 逻 辑 上 分 开 , 我 们 实 际 上 就 根 本 不 能 做 拷 贝 。)由 于 历 史 原 因 , 这 种 工 业 级 的 多 进 程 观 点 在 Microsoft 系 统 上 引 起 一 些 问 题 , 因 为Windows 没 有 完 善 的 多 进 程 模 型 ( 并 且 老 实 说 , 它 并 不 常 依 靠 并 发 编 程 技 术 )。 而 且 它 通常 采 用 一 种 多 线 程 的 方 法 。453


不 过 , 通 过 不 懈 的 努 力 ,<strong>Perl</strong> 5.6 现 在 在 Windows 里 实 现 了 fork 操 作 , 方 法 是 在 同 一个 进 程 里 克 隆 一 个 新 的 解 释 器 对 象 。 这 意 味 着 本 书 其 余 部 分 使 用 fork 的 例 子 现 在 在Windows 里 都 可 以 使 用 了 。 克 隆 出 来 的 解 释 器 与 其 他 解 释 器 共 享 不 可 改 变 的 代 码 段 但 是 有自 己 的 数 据 段 。( 当 然 , 对 那 些 可 能 不 理 解 线 程 的 C 库 可 能 仍 然 有 问 题 。)这 种 多 进 程 的 方 法 被 命 名 为 ithread, 是 “interpreter threads”( 解 释 器 线 程 ) 的 缩 写 。实 现 ithread 的 最 初 的 动 力 就 是 在 Microsoft 系 统 上 模 拟 fork。 不 过 , 我 们 很 快 就 意 识到 , 尽 管 其 他 解 释 器 是 作 为 独 立 的 线 程 运 行 的 , 它 们 仍 然 在 同 一 个 进 程 里 运 行 , 因 此 , 要 让这 些 独 立 的 解 释 器 共 享 数 据 是 相 当 容 易 的 , 尽 管 缺 省 时 它 们 并 不 共 享 数 据 。这 种 做 法 和 典 型 的 线 程 模 型 是 相 对 的 , 那 种 模 型 里 所 有 东 西 都 是 共 享 的 , 而 且 你 还 得 花 点 力气 才 能 不 共 享 一 些 东 西 。 但 是 你 不 能 把 这 两 种 模 型 看 作 是 完 全 分 离 的 两 个 模 式 , 因 为 它 们 都试 图 在 同 一 条 河 上 架 桥 通 路 ; 只 不 过 它 们 是 从 相 对 的 河 两 岸 分 别 开 工 的 。 任 何 解 决 并 发 进 程问 题 的 方 法 实 际 上 最 后 都 是 某 种 程 度 的 共 享 和 某 种 程 度 的 自 私 。所 以 从 长 远 来 看 ,ithread 的 目 的 是 允 许 你 需 要 或 者 想 要 的 尽 可 能 多 的 共 享 。 不 过 , 到 我 们写 这 些 为 止 ,ithread 唯 一 的 用 户 可 见 的 接 口 是 在 <strong>Perl</strong> 的 Microsoft 版 本 里 面 的 fork调 用 。 我 们 最 终 认 为 , 这 个 方 法 能 比 标 准 的 线 程 方 法 提 供 更 干 净 的 程 序 。 原 则 上 来 说 , 如 果你 假 设 每 个 人 都 拥 有 他 该 拥 有 的 东 西 , 那 么 这 样 的 系 统 更 容 易 运 转 , 相 比 之 下 每 个 人 都 拥 有所 有 的 东 西 的 系 统 要 难 于 运 转 得 多 。 不 象 资 本 主 义 经 济 那 样 没 有 共 享 的 东 西 , 也 不 是 共 产 主义 经 济 那 样 所 有 东 西 都 共 享 。 这 些 东 西 有 点 象 中 庸 主 义 。 类 似 社 会 主 义 。 不 过 对 于 一 大 堆 人而 言 , 只 有 在 拥 有 一 个 很 大 的 绞 肉 机 , 而 且 “ 厨 师 长 ” 认 为 它 拥 有 所 有 东 西 的 时 候 共 享 全 部东 西 才 可 行 。当 然 , 任 何 计 算 机 的 实 际 控 制 都 是 由 那 个 被 称 做 操 作 系 统 的 独 裁 者 控 制 的 。 不 过 是 聪 明 的 独裁 者 知 道 什 么 时 候 让 人 们 认 为 它 们 是 资 本 主 义 -- 以 及 什 么 时 候 让 他 们 觉 得 是 共 产 主 义 。17.2 线 程 模 型并 发 处 理 的 线 程 模 模 型 是 在 版 本 5.005 里 第 一 次 以 一 种 试 验 特 性 介 绍 到 <strong>Perl</strong> 里 来 的 。( 这 里 “ 线 程 模 型 ” 的 意 思 是 那 些 缺 省 时 共 享 数 据 资 源 的 线 程 , 不 是 版 本 5.6 里 的ithreads。) 从 某 种 角 度 来 说 , 这 个 线 程 模 型 即 使 在 5.6 里 也 仍 然 是 一 个 实 验 的 模 型 , 因为 <strong>Perl</strong> 是 一 门 复 杂 的 语 言 , 而 多 线 程 即 使 在 最 简 单 的 语 言 里 也 可 能 搞 得 一 团 糟 。 在 <strong>Perl</strong>的 语 意 里 仍 然 有 隐 蔽 的 地 方 和 小 漏 洞 , 这 些 地 方 不 能 和 共 享 所 有 东 西 的 概 念 完 全 融 合 在 一起 。 新 的 ithread 模 型 是 一 个 绕 开 这 些 问 题 的 尝 试 , 并 且 在 将 来 的 某 个 时 间 , 现 在 的 线 程模 型 可 能 会 被 包 容 在 ithread 模 型 里 ( 那 时 我 们 就 可 以 有 一 个 “ 缺 省 时 共 享 所 有 你 能 共 享的 东 西 ” 的 ithread 接 口 )。 不 过 , 尽 管 有 瑕 疵 , 目 前 的 “ 试 验 性 ” 的 线 程 模 型 仍 然 在 现 实世 界 中 的 许 多 方 面 用 得 上 , 那 种 情 况 下 你 只 能 换 一 种 你 不 想 要 的 更 笨 拙 的 方 法 。 你 可 以 用 线454


程 化 的 <strong>Perl</strong> 写 出 足 够 坚 固 的 应 用 , 不 过 你 必 须 非 常 小 心 。 如 果 你 能 想 出 一 种 用 管 道 而 不 是用 共 享 数 据 结 构 的 方 法 解 决 你 的 问 题 的 话 , 那 么 你 至 少 应 该 考 虑 使 用 fork。不 过 如 果 多 个 任 务 能 够 容 易 且 有 效 地 访 问 同 一 组 数 据 池 ( 注 : 上 一 章 讨 论 的 System V 里的 共 享 内 存 的 模 型 并 不 算 “ 容 易 且 有 效 ”), 那 么 有 些 算 法 可 以 更 容 易 地 表 达 。 这 样 就 可 以 把代 码 写 的 更 少 和 更 简 单 。 并 且 因 为 在 创 建 线 程 的 时 候 内 核 并 不 必 为 数 据 拷 贝 内 存 页 表 ( 甚 至写 时 拷 贝 (copy-on-write) 都 不 用 ), 那 么 用 这 种 方 法 启 动 一 个 任 务 就 应 该 更 快 些 。 类 似 ,因 为 内 核 不 必 交 换 内 存 页 表 , 环 境 切 换 也 应 该 更 快 一 些 。( 实 际 上 , 对 于 用 户 级 线 程 而 言 ,内 核 根 本 不 用 参 与 —— 当 然 , 用 户 级 的 线 程 有 一 些 内 核 级 的 线 程 没 有 的 问 题 。)这 些 可 是 好 消 息 。 那 么 现 在 我 们 要 做 些 弃 权 声 明 。 我 们 已 经 说 过 线 程 在 <strong>Perl</strong> 里 是 某 种 试 验特 性 , 而 且 即 使 它 们 不 再 是 试 验 特 性 了 , 那 么 线 程 编 程 也 是 非 常 危 险 的 。 一 个 执 行 流 能 够 把另 外 一 个 执 行 流 的 数 据 区 捅 得 乱 七 八 糟 的 能 力 可 以 暴 露 出 比 你 能 想 象 得 更 多 的 导 致 灾 难 的机 会 。 你 可 能 会 对 自 己 说 ,“ 那 很 容 易 修 理 , 我 只 需 要 在 任 何 共 享 的 数 据 上 加 锁 就 可 以 了 。”不 错 , 共 享 数 据 的 锁 是 不 可 缺 少 的 , 不 过 设 计 正 确 的 锁 协 议 是 臭 名 昭 著 地 难 , 协 议 的 错 误 会导 致 死 锁 或 者 不 可 预 料 的 结 果 。 如 果 你 在 程 序 里 有 定 时 问 题 , 那 么 使 用 线 程 不 仅 会 恶 化 它 们 ,而 且 还 让 他 们 难 于 跟 踪 。你 不 仅 要 确 保 你 自 己 的 共 享 数 据 的 安 全 , 而 且 你 还 要 保 证 这 些 数 据 在 所 有 你 调 用 的 <strong>Perl</strong> 模块 和 C 库 里 安 全 。 你 的 <strong>Perl</strong> 代 码 可 以 是 100% 线 程 安 全 的 , 但 是 如 果 你 调 用 了 一 个 线程 不 安 全 的 模 块 或 者 C 的 子 过 程 , 而 又 没 有 提 供 你 自 己 的 信 号 灯 保 护 , 那 么 你 完 了 。 在 你能 证 明 之 前 , 你 应 该 假 设 任 何 模 块 都 是 现 成 不 安 全 的 。 那 些 甚 至 包 括 一 些 标 准 的 模 块 。 甚 至是 它 们 的 大 多 数 。我 们 有 没 有 让 你 泄 气 ? 没 有 ? 然 后 我 们 还 要 指 出 , 如 果 事 情 到 了 调 度 和 优 先 级 策 略 的 份 上 ,你 还 很 大 程 度 上 依 赖 你 的 操 作 系 统 的 线 程 库 的 慈 悲 。 有 些 线 程 库 在 阻 塞 的 系 统 调 用 的 时 候 只做 线 程 切 换 。 有 些 线 程 库 在 某 个 线 程 做 阻 塞 的 系 统 调 用 的 时 候 阻 塞 住 整 个 进 程 。 有 些 库 只 在时 间 量 超 时 (quantum expiration) 的 时 候 才 切 换 线 程 ( 线 程 或 者 进 程 )。 有 些 库 只 能 明确 地 切 换 线 程 。哦 , 对 了 , 如 果 你 的 进 程 接 收 到 一 个 信 号 , 那 么 信 号 发 送 给 哪 个 线 程 完 全 是 由 系 统 决 定 的 。如 果 想 在 <strong>Perl</strong> 里 写 线 程 程 序 , 你 必 须 制 作 一 个 特 殊 的 <strong>Perl</strong> 的 版 本 , 遵 照 <strong>Perl</strong> 源 程 序 目录 里 的 README.threads 文 件 的 指 示 就 可 以 了 。 这 个 特 殊 的 <strong>Perl</strong> 版 本 几 乎 是 可 以 肯 定要 比 标 准 的 版 本 慢 一 些 的 。请 不 要 觉 得 你 只 要 知 道 了 其 他 线 程 模 型 (POSIX,DEC,Microsoft, 等 等 ) 的 编 程 特 点 就认 为 自 己 认 识 了 <strong>Perl</strong> 的 线 程 的 运 转 模 式 。 就 象 <strong>Perl</strong> 里 的 其 他 东 西 一 样 , <strong>Perl</strong> 就 是<strong>Perl</strong>, 它 不 是 C++ 或 者 Java 或 者 其 他 什 么 东 西 。 比 如 ,<strong>Perl</strong> 里 没 有 实 时 线 程 优 先 级 ( 也455


没 有 实 现 它 们 的 方 法 )。 也 没 有 互 斥 线 程 。 你 只 能 用 锁 定 或 者 是 Thread::Semaphore 模块 或 者 cond_wait 设 施 。还 没 泄 气 ? 好 , 因 为 线 程 实 在 是 酷 。 你 准 备 迎 接 一 些 乐 趣 吧 。17.2.1 线 程 模 块目 前 的 <strong>Perl</strong> 的 线 程 接 口 是 由 Thread 模 块 定 义 的 。 另 外 还 增 加 了 一 个 新 的 <strong>Perl</strong> 关 键 字 ,lock 操 作 符 。 我 们 将 在 本 章 稍 后 描 述 lock 操 作 符 。 其 他 标 准 的 线 程 模 块 都 是 在 这 个 基 本接 口 的 基 础 上 制 作 的 。Thread 模 块 提 供 了 下 列 类 模 块 :模 块 用 途new 构 造 一 个 新 的 Thread。self 返 回 我 的 当 前 Thread 对 象 。list 返 回 一 个 Thread 列 表 。并 且 , 对 于 Thread 对 象 , 它 还 提 供 了 这 些 对 象 方 法 :模 块用 途join 结 束 一 个 线 程 ( 传 播 错 误 )eval 结 束 一 个 线 程 ( 捕 获 错 误 )equal 比 较 两 个 线 程 是 否 相 同tid返 回 内 部 线 程 ID另 外 ,Thread 模 块 还 提 供 了 这 些 重 要 的 函 数 :函 数用 途yieldasynccond_signal告 诉 调 度 器 返 回 一 个 不 同 的 线 程通 过 闭 合 域 构 造 一 个 Thread只 唤 醒 一 个 正 在 cond_wait() 一 个 变 量 的 线 程cond_braodcast 唤 醒 所 有 可 能 在 cond_wait() 一 个 变 量 的 线 程cond_wait等 待 一 个 变 量 , 直 到 被 一 个 cond_signal() 打 断 或 变 量 上 有cond_broadcast()。17.2.1.1 创 建 线 程456


你 可 以 用 两 种 方 法 之 一 派 生 线 程 , 要 么 是 用 Thread->new 类 方 法 或 者 使 用 async 函数 。 不 管 那 种 方 法 , 返 回 值 都 是 一 个 Thread 对 象 。Thread->new 接 收 一 个 要 运 行 的 表示 某 函 数 的 代 码 引 用 以 及 传 给 那 个 函 数 的 参 数 :use Thread;...$t = Thread->new(\&func, $arg1, $arg2);你 通 常 会 想 传 递 一 个 闭 合 域 做 第 一 个 参 数 而 省 略 其 他 的 参 数 :my $something;$t = Thread->new( sub {say{$something} } );对 这 种 特 殊 的 例 子 ,async 函 数 提 供 了 一 些 符 号 上 的 解 放 ( 就 是 语 法 糖 ):use Thread qw(async);...my $something;$t = async {say($something);};你 会 注 意 到 我 们 明 确 地 输 入 了 async 函 数 。 你 当 然 可 以 用 全 称 的 Thread::async 代 换 ,不 过 那 样 你 的 语 法 糖 就 不 够 甜 了 。 因 为 async 只 包 含 一 个 闭 合 域 , 所 以 你 想 放 进 去 的 任 何东 西 都 必 须 是 一 个 在 传 入 是 的 范 围 的 一 个 词 法 变 量 。17.2.1.2 线 程 删 除一 旦 开 始 —— 并 且 开 始 遭 受 你 的 线 程 库 的 反 复 无 常 —— 该 线 程 将 保 持 运 行 直 到 它 的 顶 层 函数 ( 就 是 你 传 给 构 造 器 的 函 数 ) 返 回 。 如 果 你 想 提 前 终 止 一 个 线 程 , 只 需 要 从 那 个 顶 层 函 数中 return 就 行 了 。( 注 : 不 要 调 用 exit! 那 样 就 试 图 停 止 你 的 整 个 进 程 , 并 且 可 能 会 成功 。 但 实 际 上 该 进 程 直 到 所 有 线 程 都 退 出 之 后 才 能 退 出 , 而 且 有 些 线 程 在 exit 的 时 候 可 能拒 绝 退 出 。 我 们 稍 后 有 更 多 内 容 。)457


现 在 是 你 的 顶 层 子 过 程 返 回 的 好 时 机 , 但 是 它 返 回 给 谁 呢 ? 派 生 这 个 线 程 的 那 个 线 程 可 能 已经 转 去 做 别 的 事 情 , 并 且 不 再 等 待 一 个 方 法 调 用 的 响 应 了 。 答 案 很 简 单 : 该 线 程 等 待 某 个 过程 发 出 一 个 方 法 调 用 并 真 正 等 待 一 个 返 回 值 。 那 个 调 用 的 方 法 叫 join, 因 为 它 概 念 上 是 把两 个 线 程 连 接 回 一 个 :$retval = $t->join(); # 结 束 线 程 $tjoin 的 操 作 是 对 子 进 程 的 waitpid 的 回 忆 。 如 果 线 程 已 经 停 止 ,join 方 法 马 上 返 回 该 线程 的 顶 层 子 过 程 的 返 回 值 。 如 果 该 线 程 还 没 有 完 蛋 ,join 的 动 作 就 象 一 个 阻 塞 调 用 , 把 调用 线 程 不 确 定 地 挂 起 。( 这 儿 没 有 超 时 机 制 。) 当 该 线 程 最 终 结 束 时 ,join 返 回 。不 过 , 和 waitpid 不 一 样 ,waitpid 只 能 终 止 该 进 程 自 己 的 子 过 程 , 一 个 线 程 可 以 join 任何 同 一 个 进 程 内 的 其 他 线 程 。 也 就 是 说 , 与 主 线 程 或 者 父 线 程 连 接 并 不 是 必 要 的 。 唯 一 的 限制 是 线 程 不 能 join 它 自 己 ( 那 样 的 话 就 好 象 安 排 你 自 己 的 葬 礼 。), 而 且 一 个 线 程 不 能 join一 个 已 经 连 接 过 的 线 程 ( 那 样 就 好 象 两 个 葬 礼 主 持 在 尸 体 上 扭 打 )。 如 果 你 试 图 做 这 两 件 事之 一 那 么 就 会 抛 出 一 个 例 外 。join 的 返 回 值 不 一 定 是 标 量 值 —— 它 可 以 是 一 个 列 表 :use Thread 'async';$t1 = async {my Sstuff = getpwuid($>);return @stuff;};$t2 = async {my $motd = `cat /etc/modt`;return $motd;};@retlist = $t1->join();458


$retval = $t2->join();print "1st kid returned @retlist\n";print "2nd kid returned $retval\n";实 际 上 , 一 个 线 程 的 返 回 表 达 式 总 是 在 列 表 环 境 里 计 算 的 , 即 使 jion 是 在 一 个 标 量 环 境 里调 用 的 也 如 此 , 那 种 情 况 下 返 回 列 表 的 最 后 一 个 值 。17.2.1.3 捕 获 来 自 join 的 例 外如 果 一 个 线 程 带 着 一 个 未 捕 获 的 例 外 终 止 , 不 会 立 即 杀 死 整 个 程 序 。 这 就 有 点 头 疼 了 。 相 比之 下 , 如 果 一 个 join 在 那 个 线 程 上 运 行 , 那 么 join 本 身 会 抛 出 例 外 。 在 一 个 线 程 上 使 用了 join 意 味 着 愿 意 传 播 该 线 程 抛 出 的 例 外 。 如 果 你 宁 可 到 处 捕 获 这 些 例 外 , 那 么 使 用 eval方 法 , 它 就 象 一 个 内 建 的 配 对 儿 , 导 致 例 外 被 放 进 $@:$retval = $t->eval(); # 捕 获 join 错 误 if ($@) { warn "thread failed: $@"; }else { print "thread returned $retval\n"; }你 在 实 际 中 可 能 还 是 只 想 在 创 建 被 连 接 的 线 程 的 那 个 线 程 里 连 接 该 线 程 —— 尽 管 我 们 没 有这 个 影 响 的 规 则 。 也 就 是 说 , 你 只 从 父 线 程 里 终 止 子 线 程 。 这 样 可 以 比 较 方 便 地 跟 踪 你 应 该在 那 里 操 作 哪 个 例 外 。17.2.1.4 detach 方 法这 是 另 外 一 个 终 止 线 程 的 方 法 , 如 果 你 不 准 备 稍 后 join 一 个 线 程 以 获 取 它 的 返 回 值 , 那 么你 可 以 对 它 调 用 detach 方 法 , 这 样 <strong>Perl</strong> 会 为 你 清 理 干 净 。 然 后 该 线 程 就 不 能 再 被 连 接了 。 它 有 点 象 Unix 里 的 一 个 进 程 继 承 给 init, 不 过 在 Unix 里 这 么 做 唯 一 的 方 法 是 父 进程 退 出 。detach 方 法 并 不 把 该 线 程 “ 放 到 后 台 ”; 如 果 你 试 图 退 出 主 程 序 并 且 一 个 已 发 配 的 线 程 仍 在运 行 , 那 么 退 出 过 程 将 挂 起 , 直 到 该 线 程 自 己 退 出 。 更 准 确 一 点 说 ,detach 只 是 替 你 做 清理 工 作 。 它 只 是 告 诉 <strong>Perl</strong> 在 该 线 程 退 出 之 后 不 必 再 保 留 它 的 返 回 值 和 退 出 状 态 。 从 某 种 意义 上 来 说 ,detach 告 诉 <strong>Perl</strong> 当 该 线 程 结 束 后 做 一 个 隐 含 的 join 并 且 丢 掉 结 果 。 这 一 点很 重 要 , 如 果 你 既 不 join 也 不 detach 一 个 返 回 巨 大 列 表 的 线 程 , 那 么 那 部 分 存 储 空 间将 直 到 结 束 时 都 不 能 使 用 , 因 为 <strong>Perl</strong> 将 不 得 不 为 以 后 ( 在 我 们 的 例 子 里 是 非 常 以 后 ) 可 能会 出 现 的 某 个 家 伙 想 join 该 线 程 的 机 会 挂 起 。459


在 一 个 发 配 了 的 子 线 程 里 抛 出 的 例 外 也 不 再 通 过 join 传 播 , 因 为 它 们 将 不 再 被 使 用 。 在 顶层 函 数 里 合 理 使 用 eval {}, 你 可 能 会 找 到 其 他 汇 报 错 误 的 方 法 。17.2.1.5 标 识 线 程每 个 <strong>Perl</strong> 线 程 都 有 一 个 唯 一 的 线 程 标 识 , 由 tid 对 象 方 法 返 回 :$his_tidno = $t1->tid();一 个 线 程 可 以 通 过 Thread->self 调 用 访 问 它 自 己 的 线 程 对 象 。 不 要 把 这 个 和 线 程 ID 混淆 : 要 获 得 自 身 的 线 程 ID, 一 个 线 程 可 以 这 样 :$mytid = Thread->self->tid();#$$ 是 线 程 , 和 以 前 一 样要 拿 一 个 线 程 对 象 和 另 外 一 个 做 比 较 , 用 下 列 之 一 :Thread::equal($t1, $t2)$t1->equal($t2)$t1->tid() == $td->tid()17.2.1.6 列 出 当 前 线 程你 可 以 用 Thread->list 类 方 法 调 用 在 当 前 进 程 获 取 一 个 当 前 线 程 对 象 的 列 表 。 该 列 表 包括 运 行 着 的 现 成 和 已 经 退 出 但 还 未 连 接 的 线 程 。 你 可 以 在 任 何 线 程 里 做 这 个 工 作 :for my $t (Thread->list()) { printf "$t has tid = %d\n", $t->tid(); }17.2.1.7 交 出 处 理 器Thread 模 块 支 持 一 个 重 要 的 函 数 , 叫 yield。 它 的 工 作 是 令 调 用 它 的 线 程 放 弃 处 理 器 。 不幸 的 是 , 这 个 函 数 具 体 干 的 事 情 完 全 依 赖 于 你 所 的 线 程 的 实 现 方 式 。 不 管 怎 样 , 我 们 还 是 认为 它 是 一 个 偶 然 放 弃 CPU 的 控 制 的 很 好 的 手 势 。use Thread 'yield';yield();你 不 必 使 用 圆 括 号 。 从 语 法 上 来 讲 , 这 样 可 能 更 安 全 , 因 为 这 样 能 捕 获 看 起 来 无 法 避 免 的“yeild” 的 错 误 输 入 :460


use strict;use Thread 'yield';yeild;yield;# 编 译 器 出 错 , 然 后 才 过 去# 正 确17.2.2 数 据 访 问到 目 前 为 止 我 们 看 到 的 东 西 都 不 算 太 难 , 不 过 很 快 就 不 是 那 样 了 。 我 们 到 目 前 为 止 所 做 的 任何 事 情 实 际 上 都 没 有 面 对 线 程 的 并 行 本 性 。 访 问 共 享 数 据 就 会 结 束 上 面 的 状 况 了 。<strong>Perl</strong> 里 的 线 程 代 码 在 面 对 数 据 可 视 性 问 题 的 时 候 和 任 何 其 他 的 <strong>Perl</strong> 代 码 都 要 经 受 一 样 的约 束 。 全 局 量 仍 然 是 通 过 全 局 符 号 表 来 访 问 的 , 而 词 汇 变 量 仍 然 是 通 过 某 些 包 含 它 的 词 法 范围 ( 便 签 本 ) 访 问 的 。不 过 , 因 为 程 序 里 存 在 多 个 线 程 的 控 制 , 所 以 带 来 一 些 新 的 问 题 。<strong>Perl</strong> 不 允 许 两 个 线 程 同时 访 问 同 一 个 全 局 变 量 , 否 则 它 们 就 会 踩 着 对 方 的 脚 。( 踩 得 严 重 与 否 取 决 于 访 问 的 性 质 )。相 似 的 情 况 还 有 两 个 线 程 不 能 同 时 访 问 同 一 个 词 法 变 量 , 因 为 如 果 词 法 范 围 在 线 程 使 用 的 闭合 域 的 外 面 声 明 , 那 么 它 的 性 质 和 全 局 量 类 似 。 通 过 用 子 过 程 引 用 来 启 动 线 程 ( 用Thread->new) 而 不 是 用 闭 合 域 启 动 ( 用 async), 可 以 限 制 对 词 法 变 量 的 访 问 , 这 样 也许 能 满 足 你 的 要 求 。( 不 过 有 时 候 也 不 行 。)<strong>Perl</strong> 给 一 些 内 建 特 殊 变 量 解 决 了 这 个 问 题 , 比 如 $! 和 $_ 和 @_ 等 等 , 方 法 是 把 它 们标 记 为 线 程 相 关 的 数 据 。 糟 糕 的 是 所 有 你 日 常 使 用 的 基 础 包 变 量 都 没 有 受 到 保 护 。好 消 息 是 通 常 你 完 全 不 必 为 你 的 词 法 变 量 担 心 —— 只 要 它 们 是 在 当 前 线 程 内 部 声 明 的 ; 因 为每 个 线 程 在 启 动 的 时 候 都 会 生 成 自 己 的 词 法 范 围 的 实 例 , 这 是 与 任 何 其 他 的 线 程 隔 离 的 。 只有 在 词 法 变 量 在 线 程 之 间 共 享 的 时 候 你 才 需 要 担 心 ,( 比 如 , 四 处 传 递 引 用 , 或 者 在 多 线 程里 运 行 的 闭 合 域 里 引 用 词 法 变 量 )。17.2.2.1 用 lock 进 行 访 问 同 步如 果 同 一 时 间 有 多 个 用 户 能 够 访 问 同 一 条 目 , 那 么 就 会 发 生 冲 突 , 就 象 十 字 路 口 一 样 。 你 唯一 的 武 器 就 是 仔 细 地 锁 定 。内 建 的 lock 函 数 是 <strong>Perl</strong> 用 于 访 问 控 制 的 的 红 绿 灯 机 制 。 尽 管 lock 是 各 种 关 键 字 之 一 ,但 它 是 那 种 层 次 比 较 低 的 , 因 为 如 果 编 译 器 已 经 发 现 用 户 代 码 中 有 一 个 sub lock {} 定 义存 在 , 那 么 就 不 会 使 用 内 建 的 lock。 这 是 为 了 向 下 兼 容 , 不 过 , CORE::LOCK 总 是 内 建461


的 函 数 。( 在 不 是 为 线 程 使 用 制 作 的 perl 版 本 里 调 用 lock 不 是 个 错 误 , 只 是 一 个 无 害 的“ 无 动 作 ”, 至 少 在 最 近 的 版 本 里 如 此 。)就 好 象 flock 操 作 符 只 是 阻 塞 其 它 的 flock 的 实 例 , 而 不 是 实 际 I/O 一 样 ,lock 也 只 是阻 塞 其 它 lock 的 实 例 , 而 不 是 普 通 的 数 据 访 问 。 实 际 上 , 它 们 也 是 劝 告 性 锁 定 。 就 象 交 通灯 一 样 。( 注 : 有 些 铁 路 十 字 路 口 是 强 制 性 锁 ( 那 些 有 门 的 ), 有 些 家 伙 认 为 lock 也 应 该是 强 制 性 的 。 不 过 想 象 一 下 , 如 果 现 实 世 界 中 的 每 个 十 字 路 口 都 有 升 降 杆 是 多 么 可 怕 。)你 可 以 锁 住 独 立 的 标 量 变 量 , 整 个 数 组 和 整 个 哈 希 。lock $var;lock @values;lock %table;不 过 , 在 一 个 聚 集 上 使 用 lock 并 非 隐 含 的 对 该 聚 集 的 每 一 个 标 量 元 素 都 锁 定 :lock @values; # 在 线 程 1...lock $values[23]; # 在 线 程 2 -- 不 会 阻 塞 !如 果 你 锁 定 一 个 引 用 , 那 么 也 自 动 锁 住 了 对 引 用 的 访 问 。 也 就 是 说 , 你 获 得 一 个 可 以 释 放 的析 引 用 。 这 个 技 巧 很 有 用 , 因 为 对 象 总 是 隐 藏 在 一 个 引 用 后 面 , 并 且 你 经 常 想 锁 住 对 象 。( 并且 你 几 乎 从 来 不 会 想 锁 住 引 用 。)当 然 , 交 通 灯 的 问 题 是 它 们 有 一 半 时 间 是 红 灯 , 这 时 候 你 只 能 等 待 。 同 样 ,lock 也 是 阻 塞性 调 用 —— 你 的 线 程 会 挂 起 , 直 到 获 得 了 锁 。 这 个 过 程 中 没 有 超 时 机 制 。 也 没 有 解 锁 设 施 ,因 为 锁 是 动 态 范 围 对 象 。 它 们 持 续 到 它 们 的 语 句 块 , 文 件 或 者 eval 的 结 束 。 如 果 它 们 超出 了 范 围 , 那 么 它 们 被 自 动 释 放 。锁 还 是 递 归 的 。 这 意 味 着 如 果 你 在 一 个 函 数 里 锁 住 了 一 个 变 量 , 而 且 该 函 数 在 持 有 锁 的 时 候递 归 , 那 么 同 一 个 线 程 可 以 再 次 成 功 地 锁 住 该 变 量 。 当 所 有 拥 有 锁 的 框 架 都 退 出 以 后 , 锁 才最 终 被 删 除 。下 面 是 一 个 简 单 的 演 示 程 序 , 看 看 如 果 没 有 锁 , 世 界 将 会 怎 样 。 我 们 将 用 yield 强 制 一 次环 境 切 换 以 显 示 在 优 先 级 调 度 的 时 候 也 可 能 偶 然 发 生 的 这 类 问 题 :use Thread qw/async yield/;my $var = 0;462


sub abump {if ($var == 0) {yield;$var++;}}my $t1 = new Thread \&abump;my $t2 = new Thread \&abump;for my $t ($t1, $t2) { $t->join}print "var is $var\n";这 段 代 码 总 是 打 印 2( 某 种 意 义 上 的 “ 总 是 ”), 因 为 我 们 在 看 到 数 值 为 0 后 决 定 增 加 数 值 ,但 在 我 们 增 加 之 前 , 另 外 一 个 线 程 也 在 做 一 样 的 事 情 。我 们 可 以 在 检 查 $var 之 前 用 一 个 微 乎 其 微 的 锁 来 修 补 这 个 冲 突 。 下 面 的 代 码 总 是 打 印 1:sub bump {lock $var;if ($var == 0) {yield;$var++;}}请 记 住 我 们 没 有 明 确 的 unlock 函 数 。 要 控 制 解 锁 , 只 需 要 增 加 另 外 一 个 嵌 套 的 范 围 层 次就 行 了 , 这 样 锁 就 会 在 范 围 结 束 后 释 放 。sub abump {463


{lock $var;if ($var == 0) {yield;$var++;}} # 锁 在 这 里 释 放# 其 他 不 用 锁 定 $var 的 代 码}17.2.2.2 死 锁死 锁 是 线 程 程 序 员 的 毒 药 , 因 为 很 容 易 偶 然 地 就 死 锁 了 , 但 即 使 你 努 力 做 好 却 很 难 避 免 。 下面 是 一 个 死 锁 的 简 单 的 例 子 :my $t1 = async { lock $a; yield; lock $b; $a++; $b++ };my $t2 = async { lock $b; yield; lock $a; $b++; $a++ };解 决 方 法 是 对 于 所 有 需 要 某 个 锁 集 合 的 当 事 方 , 都 必 须 按 照 相 同 的 顺 序 获 取 锁 。把 你 持 有 锁 的 时 间 最 小 化 也 是 很 好 的 做 法 。( 至 少 出 于 性 能 的 考 虑 也 是 好 的 。 但 是 如 果 你 只是 为 了 减 少 死 锁 的 风 险 , 那 么 你 所 做 的 只 是 让 复 现 问 题 和 诊 断 问 题 变 得 更 难 。)17.2.2.3 锁 定 子 过 程你 可 以 在 一 个 子 过 程 上 加 一 把 锁 :lock &func;和 数 据 锁 不 一 样 , 数 据 锁 只 有 劝 告 性 锁 , 而 子 过 程 锁 是 强 制 性 的 。 除 了 拥 有 锁 的 线 程 以 外 其它 线 程 都 不 能 进 入 子 过 程 。考 虑 一 下 下 面 的 代 码 , 它 包 含 一 个 涉 及 $done 变 量 的 冲 突 条 件 。(yield 只 是 用 于 演 示 )。use Thread qw/async yield/;464


my $done = 0;sub frob {my $arg = shift;my $tid = Thread->self->tid;print "thread $tid: frob $arg\n";yield;unless ($done) {yield;$done++;frob($arg + 10);}}如 果 你 这 样 运 行 :my @t;for my $i (1..3) {push @t, Thread->new(\&frob, $i);}for (@t) { $_->join}print "done is $done\n";下 面 是 输 出 ( 哦 , 有 时 候 是 这 样 的 —— 输 出 是 不 可 预 料 的 ):thread 1: frob 1thread 2: frob 2thread 3: frob 3thread 1: frob 11465


thread 2: frob 12thread 3: frob 13done is 3不 过 如 果 你 这 么 运 行 :for my $i (1..3) {push @t, async {lock &frob;frob($i);};}for (@t) { $_->join }print "done is $done\n";输 出 是 下 面 的 东 西 :thread 1: frob 1thread 1: frob 11thread 2: frob 2thread 3: frob 3done is 117.2.2.4 locked 属 性尽 管 你 必 须 遵 守 子 过 程 锁 , 但 是 没 有 什 么 东 西 让 你 一 开 始 就 锁 住 他 们 。 你 可 以 说 锁 的 位 置 是劝 告 性 的 。 不 过 有 些 子 过 程 确 实 需 要 在 调 用 之 前 把 它 们 锁 住 。子 过 程 的 locked 属 性 就 是 干 这 个 的 。 它 比 调 用 lock &sub 快 , 因 为 它 在 编 译 时 就 知 道了 , 而 不 只 是 在 运 行 时 。 但 是 其 性 质 和 我 们 提 前 明 确 地 锁 住 它 是 一 样 的 。 语 法 如 下 :sub frob : locked {466


# 和 以 前 一 样}如 果 你 有 函 数 原 形 , 它 放 在 名 字 和 任 意 属 性 之 间 :sub frob ($) : locked {# 和 以 前 一 样}17.2.2.5. 锁 定 方 法在 子 过 程 上 自 动 加 锁 的 特 性 是 非 常 棒 的 , 但 有 时 候 杀 伤 力 太 大 。 通 常 来 说 , 当 你 调 用 一 个 对象 方 法 时 , 是 否 有 多 个 方 法 同 时 运 行 并 没 有 什 么 关 系 , 因 为 它 们 都 代 表 不 同 的 对 象 运 行 。 因此 你 真 正 想 锁 住 的 是 其 方 法 正 在 被 调 用 的 那 个 对 象 。 向 该 子 过 程 里 增 加 一 个 method 属 性可 以 实 现 这 个 目 的 :sub frob : locked method {# 和 以 前 一 样}如 果 它 被 当 作 一 个 方 法 调 用 , 那 么 正 在 调 用 的 对 象 被 锁 住 , 这 样 就 可 以 对 该 对 象 进 行 串 行 访问 , 但 是 允 许 在 其 他 对 象 上 调 用 该 方 法 。 如 果 该 方 法 不 是 在 对 象 上 调 用 的 , 该 属 性 仍 然 力 图做 正 确 的 事 情 : 如 果 你 把 一 个 锁 住 的 方 法 当 作 一 个 类 方 法 调 用 (Package->new 而 不 是$obj->new), 那 么 包 的 符 号 表 被 锁 住 。 如 果 你 把 一 个 锁 住 的 方 法 当 作 普 通 子 过 程 调 用 ,<strong>Perl</strong> 会 抛 出 一 个 错 误 。17.2.2.6 条 件 变 量条 件 变 量 允 许 一 个 线 程 放 弃 处 理 器 , 直 到 某 些 条 件 得 到 满 足 。 当 你 需 要 比 锁 能 提 供 的 更 多 控制 机 制 的 时 候 , 条 件 变 量 是 在 线 程 之 间 提 供 协 调 的 点 。 另 一 方 面 , 你 并 不 需 要 比 锁 有 更 多 过荷 的 东 西 , 而 条 件 变 量 就 是 带 着 这 些 思 想 设 计 的 。 你 只 是 用 普 通 锁 加 上 普 通 条 件 。 如 果 条 件失 败 , 那 么 你 必 须 通 过 cond_wait 函 数 采 取 特 殊 的 措 施 ; 但 是 我 们 很 有 可 能 能 成 功 , 因为 在 一 个 设 计 良 好 的 应 用 里 , 我 们 不 应 该 在 当 前 的 条 件 上 设 置 瓶 颈 。除 了 锁 和 测 试 , 对 条 件 变 量 的 基 本 操 作 是 由 发 送 或 者 接 收 一 个 “ 信 号 ” 事 件 ( 不 是 %SIG 意义 上 的 真 正 的 信 号 ) 组 成 的 。 你 要 么 推 迟 你 自 己 的 执 行 以 等 待 一 个 事 件 的 到 来 , 要 么 发 送 一467


条 事 件 以 唤 醒 其 他 正 在 等 待 事 件 到 来 的 线 程 。Thread 模 块 提 供 了 三 个 不 可 移 植 的 函 数 做 这些 事 情 : cond_wait,cond_signal, 和 cond_broadcast。 这 些 都 是 比 较 原 始 的 机 制 ,在 它 们 的 基 础 上 构 造 了 更 抽 象 的 模 块 , 比 如 Thread::Queue 和 Thread::Semaphore。如 果 可 能 的 话 , 使 用 那 些 抽 象 可 能 更 方 便 些 。cond_wait 函 数 接 受 一 个 已 经 被 当 前 的 线 程 锁 住 的 变 量 , 给 那 个 变 量 解 锁 , 然 后 阻 塞 住 直到 另 外 一 个 线 程 对 同 一 个 锁 住 了 的 变 量 做 了 一 次 cond_signal 或 者 cond_broadcast。被 cond_wait 阻 塞 住 的 变 量 在 cond_wait 返 回 以 后 重 新 锁 住 。 如 果 有 多 个 线 程 在cond_wait 这 个 变 量 , 那 么 只 有 一 个 线 程 重 新 阻 塞 , 因 为 它 们 无 法 重 新 获 得 变 量 的 锁 。 因此 , 如 果 你 只 使 用 cond_wait 进 行 同 步 工 作 , 那 么 应 该 尽 快 放 弃 变 量 锁 。cond_signal 函 数 接 受 一 个 已 经 被 当 前 线 程 锁 住 的 变 量 , 然 后 解 除 一 个 当 前 正 在cond_wait 该 变 量 的 线 程 的 阻 塞 。 如 果 不 止 一 个 线 程 阻 塞 在 对 该 变 量 的 cond_wait 上 ,只 有 解 除 一 个 的 阻 塞 , 而 且 你 无 法 预 料 是 哪 个 。 如 果 没 有 线 程 阻 塞 在 对 该 变 量 的cond_wait 上 , 该 事 件 被 丢 弃 。cond_broadcast 函 数 运 行 得 象 cond_signal, 但 是 解 除 所 有 在 锁 住 的 变 量 的cond_wait 的 线 程 的 阻 塞 , 而 不 只 是 一 个 。( 当 然 , 仍 然 是 某 一 时 刻 只 有 一 个 线 程 可 以 拥有 锁 住 的 变 量 。)cond_wait 应 该 是 一 个 线 程 在 条 件 没 有 得 到 满 足 后 的 最 后 的 手 段 。cond_signal 和cond_broadcast 表 明 条 件 已 经 改 变 了 。 我 们 假 设 各 个 事 件 的 安 排 是 这 样 的 : 锁 定 , 然 后检 查 一 下 看 看 是 否 满 足 你 需 要 的 条 件 ; 如 果 满 足 , 很 好 , 如 果 不 满 足 , cond_wait 直 到满 足 。 这 里 的 重 点 是 放 在 尽 可 能 避 免 阻 塞 这 方 面 的 。( 在 对 付 线 程 的 时 候 通 常 是 个 好 建 议 。)下 面 是 一 个 在 两 个 线 程 之 间 来 回 传 递 控 制 的 一 个 例 子 。 千 万 不 要 因 为 看 到 实 际 条 件 都 在 语 句修 饰 词 的 右 边 而 被 欺 骗 ; 除 非 我 们 等 待 的 条 件 是 假 的 , 否 则 决 不 会 调 用 cond_wait。use Thread qw(async cond_wait cond_signal);my $wait_var = 0;async {lock $wait_var;$wait_var = 1;cond_wait $wait_var until $wait_var == 2;cond_signal($wait_var);468


$wait_var = 1;cond_wait $wait_var until $wait_var == 2;cond_signal($wait_var);};async {lock $wait_var;cond_wait $wait_var until $wait_var == 1;$wait_var = 2;cond_signal($wait_var);cond_wait $wait_var until $wait_var == 1;$wait_var = 2;cond_signal($wait_var);cond_wait $wait_var until $wait_var == 1;};17.2.3 其 他 线 程 模 块有 几 个 模 块 是 在 基 本 的 cond_wait 上 构 造 的 。17.2.3.1 队 列 (queue)标 准 的 Thread::Queue 模 块 提 供 了 一 个 在 线 程 之 间 传 递 对 象 而 又 不 用 担 心 锁 定 和 同 步问 题 的 方 法 。 它 的 接 口 更 简 单 :方 法用 途new构 造 一 个 Thread::Queueequeue 向 队 列 结 尾 压 入 一 个 或 更 多 标 量dequeue 把 队 列 头 的 第 一 个 标 量 移 出 。 如 果 队 列 里 没 有 内 容 了 , 那 么 dequeue 阻 塞 。469


请 注 意 , 队 列 和 普 通 管 道 非 常 相 似 , 只 不 过 不 是 发 送 字 节 而 是 传 递 整 个 标 量 , 包 括 引 用 和 赐福 过 的 对 象 而 已 !下 面 是 一 个 从 perlthrtut 手 册 页 来 的 一 个 例 子 :use Thread qw/async/;use Thread::Queue;my $Q = Thread::Queue->new();async {while (defined($datum = $Q->dequeue)) {print "Pulled $datum from queue\n";}};$Q->enqueue(12);$Q->enqueue("A", "B", "C");$Q->enqueue($thr);sleep 3;$Q->enqueue(\%ENV);$Q->enqueue(undef);下 面 是 你 获 得 的 输 出 :Pulled 12 from queuePulled A from queuePulled B from queuePulled C from queue470


Pulled Thread=SCALAR(0x8117200) from queuePulled HASH(0x80dfd8c) from queue请 注 意 当 我 们 通 过 一 个 async 闭 合 域 启 动 一 个 异 步 线 程 的 时 候 $Q 在 范 围 里 是 怎 样 的 。线 程 和 <strong>Perl</strong> 里 的 其 他 东 西 一 样 遵 守 同 样 的 范 围 规 则 。 如 果 $Q 在 async 调 用 之 后 才 声明 , 那 么 上 面 的 例 子 就 不 能 运 行 了 。17.2.3.2. 信 号 灯Thread::Semaphre 给 你 提 供 了 线 程 安 全 的 计 数 信 号 灯 对 象 , 你 可 以 用 它 来 实 现 你 自 己 的p() 和 v() 操 作 。 因 为 我 们 大 部 分 人 都 不 把 这 些 操 作 和 荷 兰 语 的 passeer (“ 回 合 ”) 和verlaat(“ 树 叶 ”) 联 系 在 一 起 , 所 以 此 模 块 把 这 些 操 作 相 应 称 做 “ 向 下 ” 和 “ 向 上 ”。( 在 有些 文 化 里 , 它 们 叫 “ 等 ” 和 “ 信 号 ”。) 此 模 块 支 持 下 面 的 方 法 :方 法 用 途new 构 造 一 个 新 的 Thread::Semaphore。down 分 配 一 个 或 更 多 项 目 。up 析 构 一 个 或 者 更 多 项 目 。new 方 法 创 建 一 个 新 的 信 号 灯 并 且 把 它 初 始 化 为 声 明 的 初 始 计 数 。 如 果 没 有 声 明 初 始 数 值 ,则 该 信 号 灯 的 初 始 值 设 置 为 1。( 数 字 代 表 某 些 条 目 的 “ 池 ”, 如 果 所 有 数 字 都 分 配 完 了 则它 们 会 “ 用 光 ”。)use Thread::Semaphore;$mutex = Thread::Semaphore->new($MAX);down 方 法 把 信 号 灯 的 计 数 值 减 去 所 声 明 的 数 值 , 如 果 没 有 给 出 此 数 值 则 为 1。 你 可 以 认为 它 是 一 个 分 配 某 些 或 者 所 有 资 源 的 动 作 。 如 果 信 号 灯 计 数 减 到 零 以 下 , 这 个 方 法 会 阻 塞 住直 到 信 号 灯 计 数 等 于 或 者 大 于 你 要 求 的 数 量 。 用 下 面 的 方 法 调 用 它 :$mutex->down();up 方 法 给 该 信 号 灯 的 计 数 值 加 指 定 的 数 值 , 如 果 没 有 给 出 此 数 值 则 为 1。 你 可 以 认 为 这 是一 个 释 放 原 先 分 配 的 资 源 的 动 作 。 这 样 的 操 作 至 少 要 解 除 一 个 因 为 试 图 down 这 个 信 号 等而 阻 塞 住 的 线 程 。 用 下 面 这 样 的 方 法 调 用 :$mutex->up();17.2.3.3 其 他 标 准 线 程 模 块471


Thread::Signal 允 许 你 启 动 一 个 线 程 用 于 接 收 你 的 进 程 的 %SIG 信 号 。 这 就 解 决 了 目 前仍 然 让 人 头 疼 的 问 题 : 目 前 的 <strong>Perl</strong> 实 现 里 信 号 是 不 可 靠 的 , 如 果 轻 率 使 用 可 能 会 偶 而 导 致内 核 倾 倒 。这 些 模 块 仍 然 在 开 发 中 , 并 且 也 可 能 无 法 在 你 的 系 统 上 提 供 你 需 要 的 结 果 。 但 , 它 们 也 可 能可 以 用 。 如 果 不 能 , 那 么 就 是 因 为 某 些 象 你 一 样 的 人 还 没 有 修 补 它 们 。 可 能 你 或 者 某 个 人 就可 以 参 与 进 来 帮 助 解 决 问 题 。第 十 八 章 编 译如 果 你 到 这 里 来 是 为 了 找 一 个 <strong>Perl</strong> 的 编 译 器 , 你 可 能 很 奇 怪 地 发 现 你 已 经 有 一 个 了—— 你 的 perl 程 序 ( 通 常 是 /usr/bin/perl) 已 经 包 含 一 个 <strong>Perl</strong> 编 译 器 。 这 个 东 西 可 能不 是 你 想 要 的 , 如 果 不 是 你 想 象 的 东 西 , 你 可 能 会 很 开 心 地 得 知 我 们 还 提 供 代 码 生 成 器 ( 也就 是 那 些 要 求 意 义 严 格 的 人 所 谓 的 “ 编 译 器 ”), 我 们 将 在 本 章 讨 论 那 些 东 西 。 但 是 首 先 我 们想 讲 讲 我 们 眼 中 的 编 译 器 是 什 么 。 本 章 不 可 避 免 地 要 讲 一 些 底 层 的 细 节 , 而 有 些 人 会 喜 欢 这些 内 容 , 有 些 人 则 不 。 如 果 你 发 现 自 己 并 不 喜 欢 这 些 内 容 , 那 就 把 它 当 作 一 个 提 高 你 阅 读 速度 的 练 习 吧 。( 呵 呵 , 不 能 不 看 , 但 是 可 以 不 用 全 明 白 。)假 设 你 是 一 个 指 挥 家 , 任 务 是 给 一 个 大 管 弦 乐 队 排 练 乐 谱 。 当 乐 谱 到 货 的 时 候 , 你 会 发 现 有一 些 小 册 子 , 管 弦 乐 队 成 员 人 手 一 册 , 每 人 只 有 自 己 需 要 演 奏 的 部 分 乐 章 。 但 奇 怪 的 是 , 你的 主 乐 谱 里 面 什 么 东 西 也 没 有 。 而 更 奇 怪 的 是 , 那 些 有 部 分 乐 章 的 乐 谱 都 是 用 纯 英 语 写 的 ,而 不 是 用 音 乐 符 号 写 的 。 在 你 开 始 准 备 一 个 演 奏 的 节 目 之 前 , 或 者 甚 至 把 乐 谱 给 你 的 乐 队 演奏 之 前 , 你 首 先 得 把 这 样 的 散 文 翻 译 成 普 通 的 音 符 和 小 节 线 。 然 后 你 要 把 所 有 独 立 的 部 分 编辑 成 一 个 完 整 的 乐 谱 , 这 样 你 才 能 对 整 个 节 目 有 一 个 完 整 的 印 象 。与 之 类 似 , 当 你 把 你 的 <strong>Perl</strong> 脚 本 的 源 程 序 交 给 perl 执 行 的 时 候 , 对 计 算 机 而 言 , 它 就 象用 英 语 写 的 交 响 乐 对 音 乐 家 一 样 毫 无 用 处 。 在 你 的 程 序 开 始 运 行 之 前 ,<strong>Perl</strong> 需 要 把 这 些 看着 象 英 文 似 的 东 西 编 译 ( 注 : 或 曰 翻 译 , 或 转 换 , 或 改 变 或 变 形 ) 为 一 种 特 殊 符 号 表 现 形 式 。不 过 你 的 程 序 仍 然 不 会 运 行 , 因 为 编 译 器 只 是 编 译 。 就 象 指 挥 的 乐 谱 一 样 , 就 算 你 的 程 序 已经 转 换 成 一 种 容 易 解 释 的 指 令 格 式 , 它 仍 然 需 要 一 个 活 跃 的 对 象 来 解 释 那 些 指 令 。18.1. <strong>Perl</strong> 程 序 的 生 命 周 期472


你 可 以 把 一 个 <strong>Perl</strong> 程 序 分 裂 为 四 个 独 立 的 阶 段 , 每 个 阶 段 都 有 自 己 的 独 立 的 子 阶 段 。 第 一个 和 最 后 一 个 是 最 让 人 感 兴 趣 的 两 个 , 而 中 间 的 两 个 是 可 选 的 。 这 些 阶 段 在 图 18-1 里 描绘 。1. 编 译 阶 段在 第 一 阶 段 : 编 译 阶 段 里 ,<strong>Perl</strong> 编 译 器 把 你 的 程 序 转 换 成 一 个 叫 分 析 树 的 数 据 结 构 。除 了 使 用 标 准 的 分 析 技 术 以 外 ,<strong>Perl</strong> 还 使 用 了 一 种 更 强 大 的 分 析 技 术 : 它 使 用 BEGIN块 引 导 编 译 进 行 得 更 深 入 。BEGIN 块 一 完 成 分 析 就 转 交 给 解 释 器 运 行 , 结 果 是 它 们 以FIFO ( 先 进 先 出 ) 顺 序 运 行 。 这 样 处 理 的 包 括 use 和 no 声 明 ; 它 们 实 际 上 是 伪 装的 BEGIN 块 。 任 何 CHECK,INIT, 和 END 块 都 由 编 译 器 安 排 推 迟 的 执 行 。词 法 声 明 也 做 上 记 号 , 但 是 还 不 执 行 给 它 们 的 赋 值 。 所 有 eval BLOCKS,s///e 构 造 ,和 非 代 换 的 规 则 表 达 式 都 在 这 里 编 译 , 并 且 常 量 表 达 式 都 预 先 计 算 。 现 在 编 译 器 完 成 工作 , 除 非 稍 后 它 又 被 再 次 调 用 进 行 服 务 。 在 这 个 阶 段 的 结 尾 , 再 次 调 用 解 释 器 , 以 LIFO顺 序 ( 后 进 先 出 ) 执 行 任 何 安 排 好 了 的 CHECK 块 。 是 否 有 CHECK 块 出 现 决 定 了 我们 下 一 步 是 进 入 第 二 阶 段 还 是 直 接 进 入 第 四 阶 段 。2. 代 码 生 成 阶 段 ( 可 选 )CHECK 块 是 由 代 码 生 成 器 装 配 的 , 因 此 这 个 阶 段 是 在 你 明 确 地 使 用 一 个 代 码 生 成 器( 稍 后 在 “ 代 码 生 成 器 ” 里 描 述 ) 的 时 候 发 生 的 。 这 时 候 把 编 译 完 成 的 ( 但 还 没 运 行 的 )程 序 转 换 成 C 源 程 序 或 者 串 行 的 <strong>Perl</strong> 字 节 码 —— 一 个 代 表 内 部 <strong>Perl</strong> 指 令 的 数 值 序列 。 如 果 你 选 择 生 成 C 源 程 序 , 它 最 后 可 以 生 成 一 个 称 做 可 执 行 影 象 的 本 机 机 器 语 言 。( 注 : 你 最 初 的 脚 本 也 是 一 个 可 执 行 文 件 , 但 它 不 是 机 器 语 言 , 因 此 我 们 不 把 它 称 做影 象 。 之 所 以 称 其 为 影 象 文 件 是 因 为 它 是 一 个 你 的 CPU 用 于 直 接 执 行 的 机 器 编 码 的 逐字 拷 贝 。)这 时 候 , 你 的 程 序 暂 时 休 眠 。 如 果 你 制 作 了 一 个 可 执 行 影 象 , 那 么 你 可 以 直 接 进 入 阶 段4; 否 则 , 你 需 要 在 阶 段 三 里 重 新 组 成 冻 干 的 字 节 码 。3. 分 析 树 重 构 阶 段 ( 可 选 )要 复 活 你 的 程 序 , 它 的 分 析 树 必 须 重 新 构 造 。 这 个 阶 段 只 有 在 发 生 了 代 码 生 成 并 且 你 选择 生 成 字 节 码 的 情 况 下 存 在 。<strong>Perl</strong> 必 须 首 先 从 字 节 码 序 列 中 重 新 构 造 它 的 分 析 树 , 然后 才 能 运 行 程 序 。<strong>Perl</strong> 并 不 直 接 从 字 节 码 运 行 程 序 , 因 为 那 样 会 比 较 慢 。473


4. 执 行 阶 段最 后 , 就 是 你 等 待 的 时 刻 : 运 行 你 的 程 序 。 因 此 , 这 个 阶 段 也 称 做 运 行 阶 段 。 解 释 器 拿来 分 析 树 ( 可 能 是 直 接 从 编 译 器 来 的 或 者 间 接 从 代 码 生 成 阶 段 以 及 随 后 的 分 析 树 重 构 阶段 过 来 的 ) 并 且 运 行 之 。( 或 者 , 如 果 你 生 成 了 一 个 可 执 行 影 象 文 件 , 它 可 以 当 作 一 个独 立 的 程 序 运 行 , 因 为 它 包 含 一 个 内 嵌 的 <strong>Perl</strong> 解 释 器 。)这 个 阶 段 的 开 始 , 在 你 的 主 程 序 运 行 之 前 , 所 有 安 排 好 了 的 INIT 块 以 FIFO顺 序 执 行 。 然 后 你 的 主 程 序 运 行 。 解 释 器 在 碰 到 一 个 eval STRING, 一 个 do FILE或 者 require 语 句 , 一 个 s///ee 构 造 , 或 者 一 个 代 换 变 量 里 含 有 合 法 代 码 断 言 的 模式 匹 配 的 时 候 会 根 据 需 要 回 过 头 调 用 编 译 器 。当 你 的 主 程 序 结 束 之 后 , 最 后 执 行 任 何 推 迟 的 END 块 , 这 回 是 以 LIFO 顺 序 。 最 早的 一 个 最 后 执 行 , 然 后 你 的 程 序 结 束 。(END 块 只 有 在 你 的 exec 或 者 你 的 进 程 被 一个 未 捕 获 的 灾 难 性 错 误 摧 毁 后 才 能 忽 略 。 普 通 的 例 外 都 不 认 为 是 灾 难 性 的 。)下 面 我 们 将 以 非 常 详 细 的 方 式 讨 论 这 些 阶 段 , 而 且 是 以 不 同 的 顺 序 。18.2 编 译 你 的 代 码<strong>Perl</strong> 总 是 处 于 两 种 操 作 模 式 之 一 : 要 么 它 在 编 译 你 的 程 序 , 要 么 是 在 执 行 它 —— 从 来 不 会同 时 处 于 两 种 模 式 。 在 我 们 这 本 书 里 , 我 们 把 某 些 事 件 称 做 在 编 译 时 发 生 , 或 者 我 们 说 “<strong>Perl</strong>编 译 器 做 这 做 那 ”。 在 其 他 地 方 , 我 们 说 某 些 东 西 在 运 行 时 发 生 , 或 者 说 “<strong>Perl</strong> 的 解 释 器 做这 做 那 ”。 尽 管 你 可 以 认 为 “<strong>Perl</strong>” 就 是 编 译 器 和 解 释 器 的 合 体 , 但 是 把 这 <strong>Perl</strong> 在 不 同 场 合所 扮 演 的 两 个 角 色 之 一 区 分 清 楚 还 是 非 常 重 要 的 , 这 样 你 才 能 理 解 许 多 事 情 发 生 的 原 因 。( 也可 能 有 其 他 角 色 ;perl 还 是 一 个 优 化 器 和 代 码 生 成 器 。 有 时 候 , 它 还 是 一 个 骗 子 —— 不 过都 是 善 意 的 玩 笑 。)同 时 , 区 分 编 译 阶 段 和 编 译 时 , 以 及 运 行 阶 段 和 运 行 时 之 间 的 区 别 也 非 常 重 要 。 一 个 典 型 的<strong>Perl</strong> 程 序 只 有 一 个 编 译 阶 段 , 然 后 只 有 一 个 运 行 阶 段 。“ 阶 段 ” 是 一 个 大 范 围 的 概 念 。 但 是编 译 时 和 运 行 时 是 小 范 围 的 概 念 。 一 个 给 定 的 编 译 阶 段 大 多 数 时 间 做 的 是 编 译 时 工 作 , 但 是也 通 过 BEGIN 块 做 一 些 运 行 时 工 作 。 一 个 给 定 的 运 行 阶 段 大 多 数 时 间 做 的 是 运 行 时 工 作 ,但 是 它 也 可 能 通 过 类 似 eval STRING 这 样 的 操 作 符 做 编 译 时 任 务 。在 典 型 的 事 件 过 程 中 ,<strong>Perl</strong> 先 阅 读 你 的 整 个 程 序 然 后 才 开 始 执 行 。 这 就 是 <strong>Perl</strong> 分 析 声 明 ,语 句 和 表 达 式 , 确 保 它 们 语 法 上 是 正 确 的 时 候 。( 注 : 不 , 这 个 过 程 中 没 有 正 式 的 象 BNF 那样 的 语 法 图 表 , 不 过 我 们 欢 迎 你 细 读 <strong>Perl</strong> 源 程 序 树 里 的 perly.y 文 件 , 里 面 包 含 <strong>Perl</strong> 用的 yacc(1) 语 法 。 我 们 建 议 你 离 词 法 远 一 些 , 因 为 它 让 小 白 鼠 产 生 进 食 紊 乱 的 症 状 了 。 译474


注 : 呵 呵 , 象 大 话 里 的 唐 僧 。) 如 果 它 发 现 语 法 错 误 , 编 译 器 会 试 图 从 错 误 中 恢 复 过 来 , 这样 它 才 能 汇 报 任 何 后 面 的 源 程 序 的 错 误 。 有 时 候 可 以 这 样 恢 复 , 但 是 有 时 候 不 行 ; 语 法 错 误有 一 个 很 讨 厌 的 问 题 是 会 出 发 一 系 列 错 误 警 告 。<strong>Perl</strong> 在 汇 报 近 10 个 错 误 后 暂 时 退 出 。除 了 处 理 BEGIN 块 的 解 释 器 以 外 , 编 译 器 默 许 三 个 概 念 上 的 过 程 处 理 你 的 程 序 。 词 法 分析 器 (lexer) 扫 描 你 的 程 序 里 的 每 一 个 最 小 的 单 元 。 这 些 东 西 有 时 候 称 为 “ 词 位 ”(lexemes), 但 你 在 讲 述 编 程 语 言 的 文 章 里 看 到 的 可 能 更 多 的 是 “ 记 号 ” (token)。 词法 分 析 器 有 时 候 被 称 做 标 记 器 或 扫 描 器 , 而 它 干 的 工 作 有 时 候 被 称 做 是 词 法 分 析 或 记 号 分析 。 然 后 分 析 器 (parser) 以 <strong>Perl</strong> 语 言 的 语 法 为 基 础 , 试 图 通 过 把 这 些 记 号 组 合 成 更 大的 构 造 , 比 如 表 达 式 和 语 句 , 来 获 取 合 适 的 意 义 , 优 化 器 (optimizer) 对 这 些 词 法 的 组 合进 行 重 新 排 列 并 且 把 它 们 归 减 成 更 有 效 的 序 列 。 优 化 器 仔 细 地 选 择 最 优 的 方 法 , 它 不 会 在 边缘 优 化 上 花 费 时 间 , 因 为 <strong>Perl</strong> 编 译 器 用 做 即 时 编 译 器 时 必 须 运 行 得 极 快 。这 些 过 程 并 不 是 在 相 互 独 立 的 阶 段 进 行 的 , 而 是 同 时 发 生 , 并 且 相 互 之 间 有 大 量 交 互 。 词 法分 析 器 偶 尔 需 要 来 自 parser 的 提 示 , 这 样 它 才 能 够 知 道 它 需 要 注 意 哪 几 种 记 号 类 型 。( 很奇 怪 的 是 , 词 法 范 围 就 是 词 法 分 析 器 不 能 理 解 的 事 物 之 一 , 因 为 那 是 “ 词 法 ” 的 其 他 含 义 。)优 化 器 同 时 还 需 要 跟 踪 分 析 器 的 处 理 , 因 为 有 些 优 化 在 分 析 器 到 达 某 一 点 之 前 是 无 法 进 行的 , 比 如 完 成 一 个 表 达 式 , 语 句 , 块 , 或 者 子 过 程 。你 可 能 会 奇 怪 , 为 什 么 <strong>Perl</strong> 编 译 器 同 时 做 这 些 事 情 , 而 不 是 一 件 一 件 做 呢 ? 因 为 这 个 混 乱的 过 程 就 是 当 你 在 听 取 或 者 读 取 自 然 语 言 的 时 候 , 你 即 时 地 理 解 它 们 的 过 程 。 你 用 不 着 直 到读 到 本 章 的 结 束 才 理 解 第 一 句 话 的 含 义 。 你 可 以 想 象 下 面 的 对 应 关 系 :计 算 机 语 言 自 然 语 言字 符记 号术 语表 达 式语 句块文 件程 序字 母词 素词短 语句 子段 落章 节故 事如 果 分 析 过 程 进 展 顺 利 , 编 译 器 就 认 为 你 输 入 了 一 则 合 法 的 故 事 , 哦 , 是 程 序 。 如 果 你 运 行程 序 的 时 候 使 用 了 -c 开 关 , 那 么 编 译 器 会 打 印 一 条 “syntax OK” 消 息 然 后 退 出 。 否 则 ,编 译 器 会 把 它 自 己 的 成 果 转 交 给 其 他 过 程 。 这 些 “ 成 果 ” 是 以 分 析 树 的 形 式 表 示 的 。 在 分 析 树上 的 每 个 “ 果 实 ”—— 或 者 称 做 节 点 —— 代 表 一 个 <strong>Perl</strong> 内 部 的 操 作 码 , 而 树 上 的 分 支 代 表 树475


的 历 史 增 长 模 式 。 最 后 , 这 些 节 点 都 会 线 性 地 一 个 接 一 个 地 串 在 一 起 , 以 表 示 运 行 时 系 统 的访 问 这 些 节 点 的 执 行 顺 序 。每 个 操 作 码 都 是 <strong>Perl</strong> 认 为 的 最 小 的 可 执 行 单 元 。 你 可 能 见 过 一 条 象 $a = -($b + $c) 这样 的 语 句 , 但 是 <strong>Perl</strong> 认 为 它 是 六 个 独 立 的 操 作 码 。 如 果 让 我 们 用 一 种 简 单 的 格 式 来 表 示 ,上 面 那 个 表 达 式 的 分 析 树 看 起 来 象 图 18-2。 黑 圆 点 里 的 数 字 代 表 <strong>Perl</strong> 运 行 时 系 统 将 遵 循的 访 问 顺 序 。<strong>Perl</strong> 不 是 有 些 人 想 象 的 那 种 单 回 合 编 译 器 。( 单 回 合 编 译 器 是 那 种 把 事 情 做 得 对 计 算 机 简单 而 对 程 序 员 复 杂 的 东 西 。)<strong>Perl</strong> 编 译 器 是 那 种 多 回 合 的 , 优 化 的 编 译 器 , 它 是 由 至 少 三种 不 同 的 , 相 互 交 错 的 逻 辑 回 合 组 成 的 。 回 合 1 和 2 在 编 译 器 在 分 析 树 上 上 窜 下 跳 构 造执 行 流 的 时 候 轮 流 运 行 , 而 回 合 3 在 一 个 子 过 程 或 者 文 件 完 全 分 析 完 的 时 候 运 行 。 下 面 是那 些 回 合 :• 回 合 1: 自 底 向 上 分 析在 这 个 回 合 里 , 分 析 树 是 由 yacc(1) 分 析 器 建 造 的 , 用 的 记 号 是 从 下 层 的 词 法 分 析 器处 理 出 来 的 ( 那 个 过 程 也 可 以 认 为 是 另 外 一 个 逻 辑 回 合 )。 自 底 向 上 只 是 意 味 着 该 分 析器 先 知 道 树 叶 后 知 道 树 支 和 树 根 。 它 并 不 是 象 在 图 18-2 里 那 样 从 底 向 上 处 理 各 种 元素 , 因 为 我 们 是 在 顶 部 画 树 根 的 , 这 是计 算 机 科 学 家 ( 和 语 言 学 家 ) 的 特 殊 的 传 统 。在 构 造 每 个 操 作 码 节 点 的 时 候 同 时 对 每 个 操 作 码 进 行 完 成 性 检 查 , 以 校 验 语 意 是 否 正确 , 比 如 是 否 使 用 了 正 确 数 量 和 类 型 的 参 数 来 调 用 内 建 函 数 。 在 完 成 一 条 分 析 树 的 分 支以 后 , 优 化 器 就 参 与 进 来 看 看 现 在 它 能 否 对 整 个 子 树 做 某 些 转 换 。 比 如 , 一 旦 它 知 道 我们 给 某 个 接 收 一 定 数 量 参 数 的 函 数 一 列 数 值 , 那 么 它 就 可 以 把 记 录 可 变 长 参 数 函 数 的 参数 个 数 的 操 作 码 仍 掉 。 还 有 一 个 更 重 要 的 优 化 , 我 们 称 之 为 常 量 消 除 , 将 在 本 节 稍 后 讲述 。这 个 回 合 同 时 还 构 造 稍 后 执 行 的 时 候 要 用 的 节 点 访 问 顺 序 , 这 个 处 理 也 几 乎 完 全 是 戏法 , 因 为 第 一 个 要 访 问 的 地 方 几 乎 从 来 都 不 是 顶 端 节 点 。 编 译 器 把 操 作 数 作 成 一 个 临时 的 循 环 , 把 顶 端 节 点 指 向 第 一 个 要 访 问 的 操 作 码 。 如 果 顶 端 操 作 码 无 法 和 更 大 的 操 作码 匹 配 , 那 么 这 个 操 作 码 环 就 破 裂 了 , 于 是 更 大 的 操 作 码 就 成 为 新 的 顶 端 节 点 。 最 后 是这 个 环 因 为 进 入 其 他 结 构 里 ( 比 如 子 过 程 描 述 符 ) 而 合 理 破 裂 。 尽 管 其 分 析 树 会 一 直 延476


伸 到 树 的 底 部 ( 象 图 18-2 里 那 样 ) 子 过 程 调 用 者 仍 然 可 以 找 到 第 一 个 操 作 码 。 而 且 这里 不 需 要 解 释 器 回 朔 到 分 析 树 里 寻 找 从 哪 里 开 始 。• 回 合 2: 自 顶 向 下 优 化如 果 你 在 阅 读 一 小 段 <strong>Perl</strong> 代 码 ( 或 者 英 文 文 章 ), 那 么 如 果 你 不 检 查 上 下 文 的 词 法 元素 的 话 , 你 就 无 法 判 断 环 境 。 有 时 候 你 在 获 取 更 多 的 消 息 之 前 无 法 判 断 真 正 将 要 发 生 什么 事 情 。 不 过 , 不 必 害 怕 , 因 为 你 并 不 孤 独 : 编 译 器 也一 样 。 在 这 个 回 合 里 , 在 它 刚 创 建 的 子 树 上 向 回 退 , 以 便 进 行 局 部 优 化 , 最 需 要 注意 的 事 情 是 环 境 传 播 。 编 译 器 用 当 前 节 点 产 生 的 恰 当 的 环 境 ( 空 , 标 量 , 列 表 , 引 用 或者 左 值 等 ) 标 记 到 相 邻 的 下 层 节 点 上 。 不 需 要 的 操 作 码 被 清 空 但 并 不 删 除 , 因 为 现 在 重新 构 造 执 行 顺 序 已 经 太 晚 了 。 我 们 将 依 赖 第 三 回 合 把 他 们 从 第 一 回 合 决 定 了 的 临 时 执 行顺 序 中 删 除 。• 回 合 3: 窥 探 孔 优 化 器有 些 代 码 单 元 有 自 己 的 存 储 空 间 , 它 们 在 里 面 保 存 词 法 范 围 的 变 量 。( 在 <strong>Perl</strong> 的 说 法里 , 这 样 的 空 间 称 为 便 条 簿 (scratchpad))。 这 样 的 单 元 包 括 eval STRING, 子 过程 和 整 个 文 件 。 从 优 化 器 的 角 度 来 说 , 更 重 要 的 是 它 们 1. 有 自 己 的 进 入 点 , 这 就 意 味着 尽 管 我 们 知 道 从 这 里 开 始 的 执 行 顺 序 , 我 们 也 不 知 道 以 前 发 生 过 什 么 , 因 为 这 个 构 造可 能 是 从 其 他 什 么 地 方 调 用 的 。 因 此 如 果 一 个 这 样 的 单 元 被 分 析 器 分 析 完 成 ,<strong>Perl</strong> 就在 那 段 代 码 上 运 行 一 个 窥 探 孔 优 化 器 。 和 前 面 两 个 回 合 不 同 的 是 , 前 面 两 个 回 合 在 分 析树 的 各 个 分 支 上 运 行 , 而 这 个 回 合 是 以 线 性 执 行 顺 序 横 跨 代 码 , 因 为 这 里 基 本 上 是 我 们采 取 这 个 步 骤 的 最 后 的 机 会 了 , 然 后 我 们 就 要 从 分 析 器 上 砍 断 操 作 码 列 表 了 。 大 多 数 优化 已 经 在 头 两 个 回 合 里 完 成 了 , 但 是 有 些 不 行 。最 后 的 分 类 优 化 在 这 个 阶 段 发 生 , 包 括 把 最 后 的 执 行 顺 序 缝 合 在 一 起 , 忽 略 清 空 了的 操 作 码 , 以 及 识 别 什 么 时 候 可 以 把 各 种 操 作 码 缩 减 成 更 简 单 的 东 西 。 识 别 字 串 的 链 接就 是 一 个 非 常 重 要 的 优 化 , 因 为 你 肯 定 不 想 每 次 向 字 串 结 尾 加 一 点 东 西 的 时 候 就 要 到 处拷 贝 字 串 。 这 个 回 合 不 仅 仅 做 优 化 ; 它 还 做 大 量 “ 实 际 ” 的 工 作 : 捕 获 光 字 , 在 有 问 题 的构 造 上 生 成 警 告 信 息 , 检 查 那 些 可 能 无 法 抵 达 的 代 码 , 分 析 伪 哈 希 键 字 , 以 及 在 子 过 程的 原 型 被 编 译 前 寻 找 它 们 。• 回 合 4: 代 码 生 成477


这 个 回 合 是 可 选 的 ; 在 普 通 的 情 况 下 并 不 出 现 这 个 回 合 。 但 是 , 如 果 调 用 了 任 何 三个 代 码 生 成 器 之 一 ——B::Bytecode,B::C, 和 B::CC, 那 么 就 会 最 后 再 访 问 一 次 分析 树 。 代 码 生 成 器 要 么 发 出 用 于 稍 后 重 新 构 造 分 析 树 的 串 行 的 <strong>Perl</strong> 字 节 码 , 要 么 是 代表 编 译 时 分 析 树 状 态 的 文 本 C 代 码 。C 代 码 的 生 成 源 自 两 种 不 同 的 风 格 。B::C 简 单 地 重 新 构 造 分 析 树 , 然 后 用 <strong>Perl</strong> 在执 行 的 时 候 自 己 用 的 普 通 的 runops() 循 环 运 行 之 。B::CC 生 成 一 个 线 性 化 了 并 且 优化 过 的 运 行 时 代 码 路 径 ( 组 成 一 个 巨 大 的 跳 转 表 ) 的 C 等 效 物 , 并 且 执 行 之 。在 编 译 的 时 候 ,<strong>Perl</strong> 用 许 多 方 法 优 化 你 的 代 码 。 它 重 新 排 列 代 码 好 让 它 在 运 行 时 更 有 效 。它 删 除 那 些 在 执 行 时 可 能 永 远 不 会 到 达 的 代 码 , 比 如 一 个 if(0) 块 , 或 者 在 if (1) 块 里 的elsif 和 else。 如 果 你 使 用 用 my ClassName ? $var 或 our ClassName ? $var 声 明 的词 法 类 型 , 而 且 ClassName ? 包 是 用 use fields 用 法 设 置 的 , 那 么 对 下 层 的 伪 哈 希 的 常量 域 的 访 问 会 在 编 译 时 进 行 拼 写 检 查 并 且 转 换 成 一 个 数 组 访 问 。 如 果 你 给 sort 操 作 符 一 个足 够 简 单 的 比 较 路 径 , 比 如 {$a $b} 或 者 {$b cmp $a}, 那 么 它 会 被 一 个 编 译 好的 C 代 码 代 替 。<strong>Perl</strong> 里 最 富 戏 剧 性 的 优 化 可 能 就 是 它 尽 可 能 快 地 解 析 常 量 表 达 式 的 方 法 。 比 如 , 让 我 们 看看 图 18-2 的 分 析 树 。 如 果 节 点 1 和 2 都 有 文 本 和 常 量 函 数 , 节 点 1 到 4 将 已 经 被 那些 计 算 代 替 了 , 就 象 图 18-3 的 分 析 树 :图 18-3 ( 略 。。。)这 就 叫 常 量 消 除 。 常 量 消 除 并 不 仅 限 于 象 把 2**10 转 成 1024 这 么 简 单 的 场 合 。 它 还 解析 函 数 调 用 —— 包 括 内 建 的 和 用 户 定 义 的 子 过 程 , 只 要 它 们 符 合 第 六 章 , 子 过 程 , 的 “ 内 联常 量 函 数 ” 的 标 准 。 回 想 一 下 FORTRAN 编 译 器 对 它 们 内 在 函 数 臭 名 昭 著 的 知 识 ,<strong>Perl</strong> 在编 译 的 过 程 中 也 知 道 要 调 用 它 的 哪 些 内 建 函 数 。 这 就 是 为 什 么 如 果 你 试 着 做 log(0.0) 或 者sqrt ( 求 平 方 根 ) 一 个 负 数 的 时 候 , 会 导 致 一 个 编 译 错 误 , 而 不 是 一 个 运 行 时 错 误 , 并 且解 释 器 根 本 没 有 运 行 。( 注 : 实 际 上 , 我 们 在 这 里 实 在 是 简 化 的 太 厉 害 了 。 解 释 器 实 际 上 是运 行 了 , 因 为 那 就 是 常 量 消 除 器 实 现 的 方 法 。 不 过 它 是 在 编 译 时 立 即 运 行 的 , 类 似 BEGIN块 执 行 的 方 式 。)甚 至 任 意 复 杂 的 表 达 式 都 是 提 前 解 析 的 , 有 时 候 导 致 整 个 块 的 删 除 , 象 下 面 这 个 :if (2* sin(1)/cos(1) < 3 && somefn() ) { whatever() }那 些 永 不 计 算 的 东 西 不 会 生 成 任 何 代 码 。 因 为 第 一 部 分 总 是 假 , 所 以 somefn 和whatever 都 不 会 调 用 。( 所 以 不 必 期 待 那 个 语 句 块 里 会 有 goto 标 签 , 因 为 它 甚 至 都 不478


会 在 运 行 时 出 现 。) 如 果 somefn 是 一 个 可 以 内 联 的 常 量 函 数 , 那 么 即 使 你 把 上 面 的 顺 序换 成 下 面 这 样 :if ( somefn() && 2*sin(1)/cos(1)


与 <strong>Perl</strong> 的 编 译 器 相 比 ,<strong>Perl</strong> 的 解 释 器 是 非 常 直 接 的 , 直 接 得 几 乎 让 人 厌 倦 的 程 序 。 它 所做 的 一 切 就 是 走 过 那 些 编 译 出 来 的 操 作 码 , 每 次 一 个 , 然 后 把 它 们 发 配 给 <strong>Perl</strong> 运 行 时 环 境 ,也 就 是 <strong>Perl</strong> 虚 拟 机 。 它 只 不 过 是 一 小 堆 C 代 码 , 对 吧 ?实 际 上 , 它 一 点 也 不 乏 味 。<strong>Perl</strong> 的 虚 拟 机 替 你 跟 踪 一 堆 动 态 环 境 , 这 样 你 就 不 用 跟 踪 了 。<strong>Perl</strong> 维 护 不 少 堆 栈 , 你 用 不 着 理 解 它 们 , 但 是 我 们 会 在 这 里 列 出 来 好 加 深 你 的 印 象 :• 操 作 数 堆 栈 (operand stack)这 个 堆 栈 我 们 已 经 讲 过 了 。• 保 存 堆 栈 (save stack)在 这 里 存 放 等 待 恢 复 的 局 部 数 值 。 许 多 内 部 过 程 也 有 许 多 你 不 知 道 的 局 部 值 。• 范 围 堆 栈 (scope stack)轻 量 的 动 态 环 境 , 它 控 制 何 时 保 存 堆 栈 应 该 ” 弹 出 “。• 环 境 堆 栈 (context stack)重 量 级 的 动 态 环 境 ; 是 谁 调 用 了 谁 最 后 把 你 放 到 了 你 现 在 所 处 的 位 置 。caller 函数 遍 历 这 个 堆 栈 。 循 环 控 制 函 数 扫 描 这 个 堆 栈 以 找 出 需 要 控 制 哪 个 循 环 。 如 果 你 从 环 境堆 栈 剥 出 , 那 么 范 围 堆 栈 也 相 应 剥 出 , 这 样 就 从 你 的 保 存 堆 栈 里 恢 复 所 有 局 部 变 量 , 甚至 你 用 一 些 极 端 的 方 法 , 比 如 抛 出 例 外 或 者 longjmp(3) 出 去 等 也 是 如 此 。• 环 境 跳 转 堆 栈 (jumpenv stack)longjmp(3) 环 境 的 堆 栈 , 它 允 许 你 抛 出 错 误 或 者 迅 速 退 出 。• 返 回 堆 栈 (return stack)我 们 进 入 这 个 子 过 程 时 的 来 路 。• 标 记 堆 栈 (mark stack)在 操 作 数 堆 栈 里 的 列 出 的 当 前 的 杂 参 数 的 起 点 。480


• 递 归 词 法 填 充 堆 栈 (recursive lexical pad stacks)当 子 过 程 被 递 归 地 调 用 时 词 法 变 量 和 其 他 “ 原 始 寄 存 器 ” 存 放 的 地 方 。当 然 , 还 有 存 放 所 有 C 变 量 的 C 堆 栈 。<strong>Perl</strong> 实 际 上 力 图 避 免 依 赖 C 的 堆 栈 存 储 保 存 的数 值 , 因 为 longjmp(3) 忽 略 了 这 样 的 数 值 的 合 理 的 恢 复 。所 有 的 这 些 就 是 说 , 我 们 对 解 释 器 的 通 常 的 看 法 : 一 个 解 释 另 外 一 个 程 序 的 程 序 , 是 非 常 不足 以 描 述 其 内 部 的 真 正 情 况 的 。 的 确 , 它 的 内 部 是 一 些 C 的 代 码 实 现 了 一 些 操 作 码 , 但 当我 们 提 到 “ 解 释 器 ” 的 时 候 , 我 们 所 说 的 含 义 要 比 上 面 这 句 话 更 多 些 , 就 好 象 我 们 谈 到 “ 音 乐家 ” 时 , 我 们 说 的 含 义 可 不 仅 仅 是 一 个 可 以 把 符 号 转 换 成 声 音 的 DNA 指 令 集 。 音 乐 家 是 活生 生 的 “ 有 状 态 ” 的 有 机 组 织 。 解 释 器 也 一 样 。具 体 来 说 , 所 有 这 些 动 态 和 词 法 范 围 , 加 上 全 局 符 号 表 , 带 上 分 析 树 , 最 后 加 上 一 个 执 行 的线 索 , 就 是 我 们 所 谓 的 一 个 解 释 器 。 从 执 行 的 环 境 来 看 , 解 释 器 实 际 上 在 编 译 器 开 始 运 行 之前 就 存 在 了 , 并 且 甚 至 是 可 以 在 编 译 器 正 在 制 作 它 的 环 境 的 时 候 解 释 器 就 开 始 进 行 初 步 的 运行 了 。 实 际 上 , 这 就 是 当 编 译 器 调 用 解 释 器 执 行 BEGIN 块 等 的 时 候 发 生 的 事 情 。 而 解 释器 可 以 回 过 头 来 使 用 编 译 器 进 一 步 制 作 自 己 的 运 行 环 境 。 每 次 你 定 义 另 外 一 个 子 过 程 或 者 装载 另 外 一 个 模 块 , 那 么 我 们 称 之 为 解 释 器 的 特 定 的 虚 拟 <strong>Perl</strong> 机 器 实 际 上 就 在 重 新 定 义 自身 。 你 实 际 上 不 能 说 是 编 译 器 还 是 解 释 器 在 控 制 这 一 切 , 因 为 它 们 合 作 控 制 我 们 通 常 称 之 为“ 运 行 <strong>Perl</strong> 脚 本 ” 的 引 导 过 程 。 就 象 启 动 一 个 孩 子 的 大 脑 。 是 DNA 还 是 蛋 白 质 在 做 处 理 ?我 们 认 为 两 个 都 在 起 作 用 , 并 且 还 有 一 些 来 自 外 部 程 序 员 的 输 入 。我 们 可 以 在 一 个 进 程 里 运 行 多 个 解 释 器 ; 它 们 可 以 或 者 不 可 以 共 享 分 析 树 , 取 决 于 它 们 是 通过 克 隆 一 个 现 有 解 释 器 生 成 的 还 是 通 过 从 头 开 始 制 作 一 个 新 的 解 释 器 生 成 的 。 我 们 也 可 能 在一 个 解 释 器 里 运 行 多 个 线 程 , 这 种 情 况 下 我 们 不 仅 共 享 分 析 树 而 且 还 共 享 全 局 符 号 —— 参 阅第 十 七 章 , 线 程 。不 过 大 多 数 <strong>Perl</strong> 程 序 只 使 用 一 个 <strong>Perl</strong> 解 释 器 执 行 它 们 编 译 好 了 的 代 码 。 并 且 尽 管 你 可 以在 一 个 进 程 里 运 行 多 个 独 立 的 <strong>Perl</strong> 解 释 器 , 目 前 可 以 实 现 这 个 目 的 的 API 只 能 从 C 里访 问 。( 注 : 到 目 前 为 止 只 有 一 个 例 外 :<strong>Perl</strong> 5.6.0 可 以 在 Microsoft Windows 的 仿 真fork 的 支 持 下 实 现 克 隆 解 释 器 。 到 你 阅 读 到 此 处 时 , 可 能 也 有 一 个 实 现 "ithread" 的 <strong>Perl</strong>API 。) 每 个 独 立 的 <strong>Perl</strong> 解 释 器 起 到 一 个 完 全 独 立 的 进 程 的 作 用 , 但 是 并 不 象 创 建 一 个完 全 新 的 进 程 那 样 开 销 巨 大 。 这 就 是 为 什 么 Apache 的 mod_perl 扩 展 的 性 能 如 此 突 出的 原 因 : 当 你 在 mod_perl 里 启 动 一 个 CGI 脚 本 时 , 那 个 脚 本 已 经 被 编 译 成 <strong>Perl</strong> 的 操作 码 了 , 消 除 了 重 新 编 译 的 需 要 —— 但 是 更 重 要 的 是 , 消 除 了 启 动 一 个 新 进 程 的 需 要 , 这才 是 真 正 的 瓶 颈 。Apache 在 一 个 现 存 的 进 程 里 初 始 化 一 个 新 的 <strong>Perl</strong> 解 释 器 然 后 把 前 面编 译 完 了 的 代 码 交 给 它 执 行 。 当 然 , 这 里 头 的 东 西 要 远 比 我 们 说 的 多 —— 一 直 是 这 样 的 。 更481


多 关 于 mod_perl 的 东 西 , 请 参 考 Writing Apache Modules with <strong>Perl</strong> and C(O'Reilly,1999)。许 多 其 他 应 用 都 可 以 内 嵌 <strong>Perl</strong> 解 释 器 , 比 如 nvi,vim 和 innd; 我 们 可 不 指 望 在 这 里 把它 们 都 列 出 来 。 而 且 还 有 许 多 甚 至 都 不 敢 宣 传 它 们 有 内 嵌 的 <strong>Perl</strong> 引 擎 的 商 业 产 品 。 它 们 只是 在 内 部 使 用 它 , 因 为 它 能 按 照 他 们 的 风 格 实 现 他 们 的 程 序 。18.4 编 译 器 后 端所 以 , 如 果 Apache 可 以 现 在 编 译 一 个 <strong>Perl</strong> 程 序 而 稍 后 才 执 行 它 , 你 为 什 么 不 行 ?Apache 和 其 他 包 含 内 嵌 <strong>Perl</strong> 解 释 器 的 程 序 做 得 非 常 简 单 —— 它 们 从 来 不 把 分 析 树 存 到一 个 外 部 文 件 中 。 如 果 你 对 这 样 的 做 法 表 示 满 意 , 而 且 不 介 意 使 用 C API 获 得 这 样 的 特 性 ,那 么 你 可 以 做 一 样 的 事 情 。 参 阅 第 二 十 一 章 , 内 部 和 外 部 , 里 的 “ 嵌 入 <strong>Perl</strong>” 一 节 , 获 取 如何 从 一 个 闭 合 的 C 框 架 里 访 问 <strong>Perl</strong> 的 信 息 。如 果 你 不 想 走 这 条 路 或 者 有 其 他 需 要 , 那 么 还 有 几 个 选 择 可 用 。 你 可 以 不 让 来 自 <strong>Perl</strong> 编 译器 的 输 出 立 即 输 入 <strong>Perl</strong> 解 释 器 , 而 是 调 用 任 意 可 用 的 后 端 。 这 些 后 端 可 以 把 编 译 好 的 操 作码 串 行 化 和 存 储 到 任 何 外 部 文 件 中 , 甚 至 可 以 把 它 们 转 换 成 几 种 不 同 风 格 的 C 代 码 。请 注 意 那 些 代 码 生 成 器 都 是 非 常 试 验 性 的 工 具 , 在 生 产 环 境 中 不 可 靠 。 实 际 上 , 你 甚 至 都 不能 指 望 它 们 在 非 生 产 环 境 里 面 能 用 —— 除 了 极 为 稀 有 的 情 况 以 外 。 现 在 我 们 已 经 把 你 的 期 望值 降 得 足 够 低 了 , 这 样 任 何 成 功 都 可 以 比 较 容 易 超 过 它 们 , 这 时 候 我 们 才 能 放 心 地 告 诉 你 后端 是 如 何 运 行 的 。有 些 后 端 模 块 是 代 码 生 成 器 , 比 如 B::Bytecode,B::C, 和 B::CC。 其 他 的 实 际 上 都 是代 码 分 析 和 调 试 工 具 , 比 如 B::Deparse,B::Lint, 和 B::Xref。 除 了 这 些 后 端 以 外 , 标准 版 还 包 括 几 种 其 他 的 底 层 模 块 , 那 些 潜 在 的 <strong>Perl</strong> 代 码 开 发 工 具 的 作 者 可 能 对 它 们 感 兴趣 。 其 他 的 后 端 模 块 可 以 在 CPAN 找 到 , 包 括 ( 到 我 们 写 这 些 为 止 )B::Fathom,B::Graph,B::JVM::Jasmin, 和 B::Size。如 果 你 除 了 给 解 释 器 提 供 输 入 以 外 还 有 其 他 地 方 使 用 <strong>Perl</strong> 编 译 器 , 那 么 O 模 块 ( 也 就 是O.pm 文 件 ) 位 于 编 译 器 和 你 分 配 的 后 端 模 块 之 间 。 你 并 不 直 接 调 用 该 后 端 ; 相 反 , 你 调用 中 间 层 , 然 后 由 它 调 用 你 指 定 的 后 端 。 因 此 如 果 你 有 一 个 模 块 调 用 B::Backend, 你 可以 在 一 个 脚 本 里 这 样 来 调 用 :%perl -MO=Backend SCRIPTNAME有 些 后 端 需 要 选 项 , 用 下 面 的 方 法 声 明 :%perl -MO=Backend, OPTS SCRIPTNAME482


有 些 后 端 已 经 有 调 用 它 们 的 中 间 层 的 前 端 了 , 所 以 你 不 必 费 心 记 忆 它 们 的 M.O。 尤 其 是perlcc(1) 调 用 那 个 代 码 生 成 器 , 而 代 码 生 成 器 启 动 起 来 可 能 比 较 麻 烦 。18.5 代 码 生 成 器目 前 的 三 种 把 <strong>Perl</strong> 操 作 码 转 换 成 其 他 格 式 的 后 端 都 是 处 于 实 验 阶 段 的 。( 没 错 , 我 们 前 面说 过 这 些 , 但 是 我 们 不 想 你 忘 记 这 点 。) 甚 至 就 算 它 门 生 成 的 输 出 碰 巧 能 正 确 运 行 , 生 成 的程 序 也 可 能 比 平 常 需 要 更 多 的 磁 盘 空 间 , 更 多 的 存 储 器 , 和 更 多 的 CPU 时 间 。 这 是 一 个 正在 进 行 的 研 究 可 开 发 领 域 。 不 过 一 切 都 会 慢 慢 好 起 来 的 。18.5.1 字 节 码 生 成 器B::Bytecode 模 块 将 分 析 树 的 操 作 码 以 平 台 无 关 的 编 码 写 出 。 你 可 以 把 一 个 <strong>Perl</strong> 脚 本 编译 成 字 节 码 然 后 把 它 们 拷 贝 到 另 外 一 台 安 装 了 <strong>Perl</strong> 的 机 器 上 跑 。perlcc 命 令 知 道 怎 么 把 一 个 <strong>Perl</strong> 源 程 序 转 换 成 一 个 编 译 成 字 节 码 的 <strong>Perl</strong> 程 序 。 这 个 命令 是 标 准 的 , 不 过 仍 然 处 于 实 验 阶 段 。 你 要 做 的 事 情 只 是 :%perlcc -b -o pbyscript srcscript然 后 你 就 应 该 能 直 接 “ 执 行 ” 所 生 成 的 pbyscript。 该 文 件 的 开 头 看 起 来 象 下 面 这 样 :#!/usr/bin/perluse ByteLoader 0.03;^C^@^E^A^C^@^@^@^A^F^@^C^@^@^@^B^F^@^C^@^@^@^C^F^@^C^@^@^@B^@^@^@^H9^A8M-^?M-^?M-^?M-^?7M-^?M-^?M-^?M-^?6^@^@^@^A6^@^G^D^D^@^@^@^KR^@^@^@^HS^@^@^@^HV^@M-2W


ByteLoader ? 模 块 是 源 码 过 滤 器 , 它 知 道 如 何 把 B::Bytecode 生 成 的 串 行 的 字 节 码 分 解并 重 新 组 合 成 原 来 的 分 析 树 。 这 样 重 新 组 合 的 <strong>Perl</strong> 代 码 被 接 合 进 入 当 前 分 析 树 , 不 需 要 通过 编 译 器 。 当 解 释 器 拿 到 这 些 操 作 码 , 它 就 开 始 执 行 它 们 , 就 好 象 它 们 早 就 在 那 里 一 样 。18.5.2. C 代 码 生 成 器剩 下 的 代 码 生 成 器 ,B::C 和 B::C 都 生 成 C 代 码 , 而 不 是 串 行 化 的 <strong>Perl</strong> 操 作 码 。 它 们生 成 的 代 码 非 常 难 读 , 如 果 你 想 试 着 读 他 们 那 你 就 傻 了 。 它 可 不 是 那 种 转 换 好 了 的 <strong>Perl</strong> 到C 的 代 码 片 段 , 可 以 插 入 到 一 个 更 大 的 C 程 序 里 。 关 于 那 方 面 的 内 容 , 请 参 阅 第 二 十 一 章 。B::C 模 块 只 是 把 创 建 整 个 <strong>Perl</strong> 运 行 时 环 境 所 需 要 的 C 数 据 结 构 写 出 来 。 你 得 到 一 个 专用 的 解 释 器 , 它 的 所 有 由 编 译 器 制 作 的 数 据 结 构 都 已 经 初 始 化 好 了 。 从 某 种 意 义 上 来 说 , 所生 成 的 代 码 类 似 B::Bytecode 生 成 的 东 西 。 它 们 都 是 编 译 器 制 作 的 操 作 码 树 的 直 接 翻 译 ,只 不 过 B::Bytecodes 把 他 们 以 符 号 的 形 式 输 出 , 这 些 符 号 稍 后 可 以 重 建 操 作 码 树 并 且 插入 到 一 个 运 行 着 的 <strong>Perl</strong> 解 释 器 , 而 B::C 把 那 些 操 作 码 输 出 为 C 代 码 。 当 你 用 你 的 C编 译 器 编 译 这 些 C 程 序 并 且 把 它 们 和 <strong>Perl</strong> 库 链 接 , 生 成 的 程 序 就 不 需 要 在 目 标 系 统 安 装<strong>Perl</strong> 解 释 器 就 可 以 运 行 。( 不 过 , 它 可 能 需 要 一 些 共 享 库 —— 如 果 你 没 有 把 所 有 的 东 西 都静 态 链 接 的 话 。) 不 过 , 这 个 程 序 和 那 些 运 行 你 的 脚 本 的 普 通 的 <strong>Perl</strong> 解 释 器 没 有 什 么 根 本的 区 别 。 它 只 不 过 是 预 先 编 译 成 一 个 独 立 的 可 执 行 影 象 而 已 。不 过 ,B::CC 模 块 试 图 做 得 更 多 。 它 生 成 的 C 源 文 件 的 开 头 看 起 来 很 象 B::C 生 成 的 东西 ,( 不 过 , 当 你 犯 傻 的 时 候 当 然 什 么 东 西 看 起 来 都 一 样 。 我 们 难 道 没 有 告 诉 你 别 看 吗 ?)不 过 , 最 终 所 有 相 似 都 会 消 失 。 在 B::C 生 成 的 代 码 里 , 有 一 个 C 程 序 的 很 大 的 操 作 码 表 ,它 的 作 用 就 象 解 释 器 自 己 做 的 处 理 , 而 B::CC 生 成 的 C 代 码 是 以 你 的 程 序 对 应 的 运 行 时顺 序 为 布 局 输 出 的 。 它 甚 至 还 有 C 函 数 对 应 你 的 程 序 的 每 个 函 数 。 它 做 了 一 些 基 于 变 量 类型 的 优 化 ; 有 些 速 度 测 试 可 以 比 在 标 准 的 解 释 器 里 快 一 倍 。 这 是 目 前 的 代 码 生 成 器 中 最 具 野心 的 一 个 , 也 是 对 未 来 做 出 了 最 多 承 诺 的 一 个 。 不 过 同 时 也 是 最 不 稳 定 的 一 个 。那 些 为 毕 业 设 计 找 主 题 的 计 算 机 科 学 系 的 学 生 可 以 在 这 里 仔 细 找 找 。 这 里 有 大 量 还 未 琢 磨 的钻 石 等 待 你 们 发 掘 。18.6 代 码 开 发 工 具O 模 块 里 有 许 多 很 有 趣 的 操 作 数 方 法 (Modi Operandi), 可 以 用 来 给 易 怒 的 实 验 性 代 码生 成 器 做 输 入 用 。 这 个 模 块 给 你 提 供 了 相 对 而 言 痛 苦 少 些 的 访 问 <strong>Perl</strong> 编 译 器 输 出 的 方 法 ,这 样 你 就 可 以 比 较 容 易 地 制 作 那 些 认 识 <strong>Perl</strong> 程 序 所 有 内 涵 所 需 要 的 其 他 工 具 。B::Lint 模 块 是 参 考 lint(1) 命 名 的 ,lint(1) 是 C 程 序 的 校 验 器 。 它 检 查 那 些 常 常 拌 倒初 学 者 但 又 不 会 触 发 警 告 的 有 问 题 的 构 造 。 直 接 调 用 这 个 模 块 :484


%perl -MO=Lint, all myprog目 前 只 定 义 了 几 个 检 查 , 象 在 一 个 隐 含 的 标 量 环 境 里 使 用 数 组 啦 , 依 赖 缺 省 变 量 啦 , 以 及 访问 另 外 一 个 包 ( 通 常 是 私 有 包 ) 中 以 _ 开 头 的 标 识 啦 。 参 阅 B::lint(3) 获 取 细 节 。B::Xref 模 块 生 成 一 个 交 叉 引 用 , 里 面 列 出 一 个 程 序 里 的 声 明 以 及 所 有 变 量 ( 包 括 全 局 和词 法 范 围 ) 的 使 用 , 子 过 程 , 和 格 式 , 用 文 件 和 子 过 程 分 隔 。 用 下 面 方 法 调 用 这 个 模 块 :%perl -MO=Xref myprog > myprof.pxref举 例 来 说 , 下 面 就 是 一 部 分 输 出 ;Subroutine parse_argvPackage (lexical)$on i113, 114$opt i113, 114%getopt_cfg i107, 113@cfg_args i112, 114, 116, 116Package Getopt::Long$ignorecase 101&GetOptions &124Package main$Options 123, 124, 141, 150, 165, 169%$Options 141, 150, 165, 169&check_read &167@ARGV 121, 157, 157, 162, 166, 166这 里 显 示 出 parse_argv 子 过 程 自 己 有 四 个 词 法 变 量 ; 它 还 通 过 main 包 和Getopt::Long 包 访 问 全 局 标 识 符 。 列 表 中 的 数 字 是 使 用 这 些 项 的 行 号 : 前 导 的 i 表 明 该项 在 随 后 的 行 号 首 次 引 入 , 而 一 个 前 导 & 意 味 着 在 这 里 有 一 个 子 过 程 调 用 。 析 引 用 分 别 列出 , 于 是 $Options 和 %$Options 都 会 显 示 出 来 。485


B::Deparse 是 一 个 很 好 的 打 印 机 , 它 可 以 揭 开 <strong>Perl</strong> 代 码 神 秘 的 面 纱 , 帮 助 你 理 解 优 化器 为 你 的 代 码 做 了 那 些 转 换 。 比 如 , 下 面 的 东 西 显 示 了 <strong>Perl</strong> 给 各 种 构 造 使 用 了 什 么 缺 省 :% perl -MO=Deparse -ne 'for (1 .. 10) { print if -t }'LINE: while (defined($_ = )) {foreach $_ (1 .. 10) {print $_ if -t STDIN;}}-p 开 关 给 你 的 程 序 加 圆 括 号 , 这 样 你 就 可 以 看 到 <strong>Perl</strong> 对 优 先 级 的 看 法 :%perl -MO=Deparse, -p -e 'print $a ** 3 + sqrt(2) /10 ** -2 ** $c'print((($a ** 3) + (1.4142135623731 / (10 ** (-(2 ** %c))))));你 可 以 使 用 -p 看 看 哪 个 优 先 代 换 的 字 串 编 译 到 代 码 里 :%perl -MO=Deparse, -q -e '"A $name and some @ARGV\n"''A ' . $name . ' and some ' . join($", @ARGV) . "\n";下 面 的 例 子 显 示 了 <strong>Perl</strong> 是 怎 样 把 一 个 三 部 分 的 for 循 环 变 成 一 个 while 循 环 :%perl -MO=Deparse -e 'for ($i=0;$i


18.6 提 前 编 译 , 回 头 解 释做 事 情 的 时 候 总 有 考 虑 所 有 事 情 的 合 适 时 机 ; 有 时 候 是 在 做 事 之 前 , 有 时 候 是 做 事 之 后 。 有时 候 是 做 事 情 的 过 程 中 间 。<strong>Perl</strong> 并 不 假 定 何 时 是 适 合 考 虑 的 好 时 机 , 所 以 它 给 程 序 员 许 多选 项 , 好 让 程 序 员 告 诉 它 什 么 时 候 思 考 。 其 他 时 间 里 它 知 道 有 些 东 西 是 必 要 的 , 但 它 不 知 道应 该 考 虑 哪 个 方 案 , 因 此 它 需 要 一 些 手 段 来 询 问 你 的 程 序 。 你 的 程 序 通 过 定 义 一 些 子 过 程 来回 答 这 些 问 题 , 这 些 子 过 程 名 字 与 <strong>Perl</strong> 试 图 找 出 来 的 答 案 相 对 应 。不 仅 编 译 器 可 以 在 需 要 提 前 思 考 的 时 候 调 用 解 释 器 , 而 且 解 释 器 也 可 以 在 想 修 改 历 史 的 时 候回 过 头 来 调 用 编 译 器 。 你 的 程 序 可 以 使 用 好 几 个 操 作 符 回 过 头 来 调 用 编 译 器 。 和 编 译 器 类 似 ,解 释 器 也 可 以 在 需 要 的 时 候 调 用 命 名 子 过 程 。 因 为 所 有 这 些 来 来 回 回 都 是 在 编 译 器 , 解 释 器 ,和 你 的 程 序 之 间 进 行 的 , 所 以 你 需 要 清 楚 何 时 发 生 何 事 。 首 先 我 们 谈 谈 这 些 命 名 子 过 程 何 时被 触 发 。在 第 十 章 , 包 , 里 , 我 们 讲 了 如 果 该 包 里 的 一 个 未 定 义 函 数 被 调 用 的 时 候 , 包 的 AUTOLOAD子 过 程 是 如 何 触 发 的 。 在 第 十 二 章 , 对 象 , 里 我 们 提 到 了 DESTROY 方 法 , 它 是 在 对 象 的内 存 要 自 动 被 <strong>Perl</strong> 回 收 的 时 候 调 用 的 。 以 及 在 第 十 四 章 , 捆 绑 变 量 , 里 , 我 们 碰 到 了 许 多访 问 一 个 捆 绑 了 的 变 量 是 要 隐 含 地 调 用 的 函 数 。这 些 子 过 程 都 遵 循 一 个 传 统 : 如 果 一 个 子 过 程 会 被 编 译 器 或 者 解 释 器 自 动 触 发 , 那 么 我 们 用大 写 字 符 为 之 命 名 。 与 你 的 程 序 的 生 命 期 的 不 同 阶 段 相 联 的 是 另 外 四 个 子 过 程 , 分 别 是BEGIN,CHECK,INIT, 和 END。 它 们 前 面 的 sub 关 键 字 是 可 选 的 。 可 能 最 好 叫 它 们 “ 语句 块 ”, 因 为 它 们 从 某 种 程 度 上 来 说 更 象 命 名 语 句 块 而 不 象 真 的 子 过 程 。比 如 , 和 普 通 的 子 过 程 不 同 的 是 , 你 多 次 定 义 这 些 块 不 会 有 任 何 问 题 , 因 为 <strong>Perl</strong> 会 跟 踪 何时 调 用 它 们 , 因 此 你 不 用 通 过 名 字 调 用 它 们 。( 它 们 还 和 普 通 子 过 程 不 同 的 是 shift 和 pop表 现 得 象 在 主 程 序 里 面 , 因 此 它 们 缺 省 时 对 @ARGV 进 行 操 作 , 而 不 是 @_。)这 四 种 块 类 型 以 下 面 顺 序 运 行 :• BEGIN如 果 在 编 译 过 程 中 碰 到 则 在 编 译 其 他 文 件之 前 尽 可 能 快 地 运 行 。• CHECK当 编 译 完 成 之 后 , 但 在 程 序 开 始 之 前 运 行 。487


(CHECK 可 以 理 解 为 “ 检 查 点 ” 或 者 “ 仔 细 检 查 ”或 者 就 是 “ 停 止 ”。)• INIT在 你 的 程 序 的 主 流 程 开 始 执 行 之 前 运 行 。• END在 程 序 执 行 结 束 之 后 运 行 。如 果 你 声 明 了 多 于 一 个 这 样 的 同 名 语 句 块 , 即 使 它 们 在 不 同 的 模 块 里 ,BEGIN 也 都 是 在CHECK 前 面 运 行 的 , 而 CHECK 也 都 是 在 INIT 前 面 运 行 , 以 及 INIT 都 在 END 前 面——END 都 是 在 最 后 , 你 的 主 程 序 退 出 的 时 候 运 行 , 多 个 BEGIN 和 INIT 以 声 明 的 顺序 运 行 (FIFO), 而 CHECK 和 END 以 相 反 的 顺 序 运 行 (LIFO)。下 面 的 可 能 是 最 容 易 演 示 的 例 子 :#! /usr/bin/perl -lprintdiedie"start main running here";"main now dying here\n";"XXX: not reached\n";END { print "1st END: done running" }CHECK { print "1st CHECK : done compiling" }INIT { print "1st INIT: started running" }END { print "2nd END: done running" }BEGIN{ print "1st BEGIN: still compiling"}INIT { print "2nd INIT: started running" }BEGIN{ print "2nd CHECK: done compiling"}END { print "3rd END: done running"}如 果 运 行 它 , 这 个 演 示 程 序 输 出 下 面 的 结 果 :488


1st BEGIN: still compiling2nd BEGIN: still compiling2nd CHECK: done compiling1st CHECK: done compiling1st INIT: started running2nd INIT: started runningstart main running heremain now dying here3rd END: done running2nd END: done running1st END: done running因 为 一 个 BEGIN 块 立 即 就 执 行 了 , 所 以 它 甚 至 可 以 在 其 他 文 件 编 译 前 把 子 过 程 声 明 , 定义 以 及 输 入 等 抓 过 来 。 这 些 动 作 可 能 改 变 编 译 器 对 当 前 文 件 其 他 部 分 分 析 的 结 果 , 特 别 是 在你 输 入 了 子 过 程 定 义 的 情 况 下 。 至 少 , 声 明 一 个 子 过 程 就 把 它 当 作 一 个 列 表 操 作 符 使 用 , 这样 就 令 圆 括 号 是 可 选 的 。 如 果 输 入 的 子 过 程 定 义 了 原 型 , 那 么 调 用 它 的 时 候 就 会 当 作 内 建 函数 分 析 , 甚 至 覆 盖 同 名 的 内 建 函 数 , 这 样 就 可 以 给 它 们 不 同 的 语 意 。use 声 明 就 是 一 个 带有 目 的 的 BEGIN 块 声 明 。相 比 之 下 ,END 块 是 尽 可 能 晚 地 执 行 : 在 你 的 程 序 退 出 <strong>Perl</strong> 解 释 器 的 时 候 , 甚 至 是 因 为一 个 没 有 捕 获 的 die 或 者 其 他 致 命 错 误 。 有 两 种 情 况 下 会 忽 略 END 块 ( 或 者 一 个DESTROY 方 法 )。 如 果 一 个 程 序 不 是 退 出 , 而 是 用 exec 从 一 个 程 序 变 形 到 另 外 一 个 程序 , 那 么 END 就 不 会 运 行 。 一 个 进 程 被 一 个 未 捕 获 的 信 号 杀 死 的 时 候 也 不 会 执 行 END 过程 。( 参 阅 在 第 三 十 一 章 , 用 法 模 块 , 里 描 述 的 use sigtrap 用 法 。 那 里 面 有 将 可 捕 获 信号 转 换 成 例 外 的 一 个 比 较 容 易 的 方 法 。 关 于 信 号 操 作 的 通 用 信 息 , 请 参 考 第 十 六 章 , 进 程 间通 讯 , 里 的 “ 信 号 ”。) 想 要 绕 开 所 有 END 处 理 , 你 可 以 调 用 POSIX::exit, 也 就 是 kill -9,$$, 或 者 就 是 exec 任 何 无 伤 大 雅 的 程 序 , 比 如 Unix 系 统 里 的 /bin/true。在 一 个 END 块 里 面 ,$? 包 含 程 序 exit 时 准 备 的 状 态 。 你 可 以 在 END 块 里 修 改 $? 以修 改 程 序 的 退 出 值 。 要 小 心 不 要 碰 巧 用 system 或 者 反 勾 号 运 行 了 其 它 程 序 而 改 变 了 $?。如 果 你 在 一 个 文 件 里 有 好 几 个 END 块 , 那 么 它 们 以 定 义 它 们 的 相 反 顺 序 执 行 。 也 就 是 说 ,你 的 程 序 结 束 的 时 候 定 义 在 最 后 的 END 块 首 先 执 行 。 如 果 你 把 BEGIN 和 END 成 对 使489


用 的 话 , 这 样 的 反 序 允 许 相 关 的 BEGIN 和 END 块 按 照 你 预 期 的 方 法 嵌 套 , 比 如 , 如 果如 果 主 程 序 和 它 装 载 的 模 块 都 有 自 己 的 成 对 的 BEGIN 和 END 子 过 程 , 象 下 面 这 样 :BEGIN { parint "main begun" }END { print "main ended" }use Module;并 且 在 那 个 模 块 里 , 定 义 了 下 面 的 声 明 :BEGIN { print "module begun" }END { print "module ended" }那 么 主 程 序 就 知 道 它 的 BEGIN 总 是 首 先 发 生 , 而 它 的 END 总 是 最 后 使 用 。( 不 错 ,BEGIN 实 际 上 是 一 个 编 译 时 的 块 , 但 类 似 的 现 象 对 运 行 时 的 INIT 和 END 对 身 上 也 会发 生 。) 如 果 一 个 文 件 包 含 另 外 一 个 文 件 , 而 且 它 们 都 有 类 似 这 样 的 声 明 的 时 候 , 这 个 原 则是 递 归 地 正 确 的 。 这 样 的 嵌 套 属 性 令 这 些 块 可 以 很 好 地 当 作 包 构 造 器 和 析 构 器 来 使 用 。 每 个模 块 都 可 以 有 它 们 自 己 的 安 装 和 删 除 函 数 , 而 <strong>Perl</strong> 可 以 自 动 地 调 用 它 们 。 这 样 , 程 序 员 就不 用 总 是 记 住 是 否 用 了 某 个 库 , 是 否 在 某 时 需 要 调 用 特 殊 的 初 始 化 或 者 清 理 代 码 。 这 些 事 件都 由 模 块 的 声 明 来 保 证 。如 果 你 把 eval STRING 当 作 一 个 从 解 释 器 到 编 译 器 的 回 调 函 数 , 那 么 你 可 以 把 BEGIN看 作 从 编 译 器 到 解 释 器 的 前 进 函 数 。 它 们 两 个 都 是 暂 时 把 当 前 正 在 处 理 的 事 情 挂 起 来 然 后 切换 操 作 的 模 式 。 如 果 我 们 说 一 个 BEGIN 块 是 尽 可 能 早 地 执 行 , 我 们 的 意 思 就 是 说 它 在 完成 定 义 以 后 马 上 就 执 行 , 甚 至 是 在 包 含 它 的 文 件 的 其 他 部 分 分 析 之 前 。 因 此 BEGIN 块 是在 编 译 时 执 行 的 , 而 不 是 在 运 行 时 。 一 旦 一 个 BEGIN 块 开 始 运 行 , 那 么 它 马 上 就 取 消 定义 并 且 它 使 用 的 任 何 代 码 都 返 回 到 <strong>Perl</strong> 的 内 存 池 中 。 你 不 能 把 BEGIN 当 作 一 个 子 过 程调 用 ,( 你 试 了 也 没 有 用 。) 因 为 当 它 存 在 那 里 的 时 候 , 它 就 已 经 运 行 ( 消 失 ) 了 。和 BEGIN 块 类 似 ,INIT 块 都 是 在 <strong>Perl</strong> 运 行 时 开 始 执 行 之 前 运 行 的 , 顺 序 是 “ 先 进 先 出 ”(FIFO)。 比 如 , 在 perlcc 里 讲 到 的 代 码 生 成 器 使 用 INIT 块 初 始 化 并 解 析 指 向 XSUB的 指 针 。 INIT 块 实 际 上 和 BEGIN 块 一 样 , 只 不 过 是 它 们 让 程 序 员 分 开 了 必 须 在 编 译 阶段 发 生 的 构 造 和 必 须 在 运 行 阶 段 发 生 的 构 造 。 如 果 你 直 接 运 行 一 个 脚 本 , 这 两 者 没 什 么 大 区别 , 因 为 每 次 运 行 编 译 器 都 要 运 行 ; 但 是 如 果 编 译 和 执 行 是 分 开 的 , 那 么 这 样 的 区 别 就 可 能是 关 键 的 。 编 译 器 可 能 只 调 用 一 次 , 而 生 成 的 可 执 行 文 件 可 以 运 行 多 次 。和 END 块 类 似 ,CHECK 块 在 <strong>Perl</strong> 编 译 阶 段 完 成 之 后 而 在 开 始 运 行 阶 段 之 前 运 行 。 顺 序是 LIFO。CHECK 块 可 以 用 于 “ 退 出 ” 编 译 器 , 就 好 象 END 块 可 以 用 于 退 出 你 的 程 序 一 样 。特 别 是 后 端 都 把 CHECK 块 当 作 挂 钩 使 用 , 这 样 它 们 可 以 调 用 相 应 的 代 码 生 成 器 。 它 们 需490


要 做 的 只 是 把 一 个 CHECK 块 放 到 它 们 自 己 的 模 块 里 , 而 这 个 CHECK 块 在 合 适 的 时 刻 就会 运 行 , 这 样 你 就 不 用 把 CHECK 写 到 你 的 程 序 里 。 因 此 , 你 很 少 需 要 写 自 己 的 CHECK块 , 除 非 你 正 在 写 这 样 的 模 块 。把 上 面 的 内 容 都 放 到 一 起 , 表 18-1 列 出 了 各 种 构 造 , 列 出 了 它 们 何 时 编 译 或 者 运 行 “...”代 表 的 代 码 。表 18-1 何 时 发 生 何 事Block Compiles Traps Runs Traps Callor During Compile During Run TriggerExpression Phase Errors Phase Errors Policyuse ... C No C No Nowno ... C No C No NowBEGIN {...} C No C No NowCHECK {...} C No C No LateINIT {...} C No R No EarlyEND {...} C No R No Lateeval {...} C No R Yes Inlineeval "..." R Yes R Yes Inlinefoo(...) C No R No Inlinesub foo {...} C No R No Call anytimeeval "sub {...}" R Yes R No Call laters/pat/.../e C No R No Inlines/pat/"..."/ee R Yes R Yes Inline现 在 你 知 道 结 果 了 , 我 们 希 望 你 能 更 有 信 心 的 编 辑 和 使 用 的 <strong>Perl</strong> 程 序 。第 十 九 章 命 令 行 接 口19.1 命 令 行 处 理491


很 幸 运 的 是 <strong>Perl</strong> 是 在 Unix 世 界 里 成 长 起 来 的 , 因 为 那 就 意 味 着 它 的 调 用 语 法 在 其 他 操作 系 统 的 命 令 行 解 释 器 里 也 能 运 行 得 相 当 好 。 大 多 数 命 令 行 解 释 器 知 道 如 何 把 一 列 单 词 当 作参 数 处 理 , 而 用 不 着 关 心 某 个 参 数 是 否 以 一 个 负 号 开 头 。 当 然 , 如 果 你 从 一 个 系 统 转 到 另 外一 个 系 统 , 也 有 一 些 乱 七 八 糟 的 地 方 会 把 事 情 搞 糟 。 比 如 , 你 不 能 在 MS-DOS 里 象 在 Unix里 那 样 使 用 单 引 号 。 而 且 对 于 象 VMS 这 样 的 系 统 来 说 , 有 些 封 装 代 码 必 须 经 过 一 些 处 理才 能 模 拟 Unix I/O 的 重 定 向 。 而 通 配 符 就 解 释 成 通 配 符 。 一 旦 你 搞 定 这 些 问 题 , 那 么 <strong>Perl</strong>就 能 在 任 何 操 作 系 统 上 非 常 一 致 地 处 理 它 的 开 关 和 参 数 。即 使 你 自 己 并 没 有 一 个 命 令 行 解 释 器 , 你 也 很 容 易 从 用 其 它 语 言 写 的 另 外 一 个 程 序 里 执 行<strong>Perl</strong> 程 序 。 调 用 程 序 不 仅 能 够 用 常 用 的 方 法 传 递 参 数 , 而 且 它 还 可 以 通 过 环 境 变 量 传 递 信息 , 还 有 , 如 果 你 的 操 作 系 统 支 持 的 话 , 你 还 可 以 通 过 继 承 文 件 描 述 符 来 传 递 信 息 ( 参 阅 第十 六 章 , 进 程 间 通 讯 , 里 的 “ 传 递 文 件 句 柄 ” 一 节 。) 甚 至 一 些 外 来 的 参 数 传 递 机 制 都 可 以 很容 易 地 在 一 个 模 块 里 封 装 , 然 后 通 过 简 单 的 use 指 示 引 入 的 你 的 <strong>Perl</strong> 程 序 里 来 。<strong>Perl</strong> 以 标 准 的 风 格 传 递 命 令 行 参 数 。( 注 : 假 设 你 认 为 Unix 是 标 准 风 格 。) 也 就 是 说 ,它 预 料 在 命 令 行 上 先 出 现 开 关 ( 以 负 号 开 头 的 字 )。 在 那 之 后 通 常 出 现 脚 本 的 名 字 , 后 面 跟着 任 何 附 加 的 传 递 到 脚 本 里 的 参 数 。 有 些 附 加 的 参 数 本 身 看 起 来 就 象 开 关 , 不 过 如 果 是 这 样的 话 , 它 们 必 须 由 脚 本 本 身 处 理 , 因 为 <strong>Perl</strong> 一 旦 看 到 一 个 非 开 关 参 数 , 或 者 特 殊 的 "--“ 开关 ( 意 思 是 说 :“ 我 是 最 后 一 个 开 关 ”), 它 就 停 止 分 析 开 关 。<strong>Perl</strong> 对 你 放 源 代 码 的 地 方 提 供 了 一 些 灵 活 性 。 对 于 短 小 的 快 速 使 用 的 工 作 , 你 可 以 把 <strong>Perl</strong>程 序 全 部 放 在 命 令 行 上 。 对 于 大 型 的 , 比 较 永 久 的 工 作 , 你 可 以 把 <strong>Perl</strong> 脚 本 当 作 一 个 独 立的 文 件 使 用 。<strong>Perl</strong> 按 照 下 面 三 种 方 式 之 一 寻 找 一 个 脚 本 编 译 和 运 行 :• 通 过 在 命 令 行 上 的 -e 开 关 一 行 一 行 地 声 明 。 比 如 :%perl -e "print 'Hello, World.'"Hello, World.• 包 含 在 命 令 行 声 明 的 第 一 个 文 件 名 的 文 件 里 面 。 系 统 支 持 可 执 行 脚 本 第 一 行 的#! 符 号 为 你 调 用 解 释 器 的 用 法 。1. 通 过 标 准 输 入 隐 含 地 传 递 。 这 个 方 法 只 有 在 没 有 文 件 名 参 数 的 情 况 下 才 能 用 ; 要 给 一 个标 准 输 入 脚 本 传 递 参 数 , 你 必 须 使 用 方 法 2, 为 脚 本 名 字 明 确 地 声 明 一 个 “-”。 比 如 :%echo "print qq(Hello, @ARGV.)"| perl - WorldHello, World.492


对 于 方 法 2 和 3, <strong>Perl</strong> 从 文 件 开 头 开 始 分 析 —— 除 非 你 声 明 了 -x 开 关 , 那 样 它会 寻 找 第 一 个 以 #! 开 头 并 且 包 含 "perl" 的 行 , 然 后 从 那 里 开 始 分 析 。 这 个 开 关对 于 在 一 个 更 大 的 消 息 里 运 行 一 段 嵌 入 的 脚 本 很 有 用 。 如 果 是 这 样 , 你 可 以 用__END__ 记 号 指 明 脚 本 的 结 尾 。不 管 你 是 否 使 用 了 -x, 分 析 #! 行 的 时 候 总 是 要 检 查 是 否 有 开 关 。 这 样 , 如 果 你 所 在的 平 台 只 允 许 在 #! 行 里 有 一 个 参 数 , 或 者 更 惨 , 就 根 本 不 把 #! 行 当 作 一 个 特 殊 的行 , 你 仍 然 能 够 获 得 一 致 的 开 关 特 性 , 而 不 用 管 是 如 何 调 用 <strong>Perl</strong> 的 , 即 使 你 是 用-x 来 寻 找 脚 本 的 开 头 。警 告 : 因 为 老 版 本 的 Unix 不 声 不 响 地 把 内 核 对 #! 行 分 析 时 超 过 32 个 字 符 的 部 分截 去 , 最 后 可 能 是 有 些 开 关 原 封 不 动 地 传 给 你 的 程 序 而 其 他 的 参 数 就 没 了 ; 如 果 你不 小 心 , 你 甚 至 有 可 能 收 到 一 个 "-" 而 没 有 它 的 字 母 。 你 可 能 会 想 确 保 所 有 你 的 开 关要 么 在 32 字 符 之 前 , 要 么 在 其 后 。 大 多 数 开 关 并 不 关 心 它 们 是 否 被 多 次 处 理 , 但如 果 拿 到 一 个 “-” 而 不 是 整 个 开 关 , 就 会 导 致 <strong>Perl</strong> 试 图 从 标 准 输 入 读 取 它 的源 代 码 , 而 不 是 从 你 的 脚 本 里 。 而 且 一 个 -I 开 关 的 片 段 也 会 导 致 很 奇 怪 的 结 果 。不 过 , 有 些 开 关 的 确 关 心 它 们 是 否 被 处 理 了 两 次 , 比 如 -l 和 -0 的 组 合 。 你 要 么 把所 有 开 关 放 到 32 字 符 范 围 之 后 ( 如 果 可 行 ), 要 么 把 -0DIGITS 换 成BEGIN{ $/ = "\0DIGITS"; }。 当 然 , 如 果 你 用 的 不 是 Unix 平 台 , 那 么 我 们 保 证不 会 有 这 种 问 题 发 生 。对 #! 行 的 开 关 的 分 析 从 该 行 中 首 次 出 现 "perl" 的 地 方 开 始 。 为 了 emacs 用 户 的习 惯 ,"-*" 和 "-" 组 成 的 序 列 特 别 被 忽 略 掉 , 因 此 , 如 果 你 有 意 使 用 , 你 可 以 说 :493


#! /bin/sh -- # -*- perl -*- -peval 'exec perl -S $0 ${1+"$@"}'if 0;于 是 <strong>Perl</strong> 就 只 看 见 -p 开 关 。 奇 妙 的 小 发 明 "-*- perl -*-" 告 诉 emacs 以 <strong>Perl</strong>模 式 启 动 ; 如 果 你 不 使 用 emacs, 那 么 你 用 不 着 它 。 我 们 稍 后 在 描 述 -S 的 时 候 解 释它 那 堆 东 西 。如 果 你 有 env(1) 程 序 , 也 可 以 用 类 似 的 技 巧 :#! /usr/bin/env perl前 面 的 例 子 使 用 <strong>Perl</strong> 解 释 器 的 相 对 路 径 , 把 用 户 路 径 里 出 现 的 第 一 个 perl 拿 过 来 。如 果 你 想 要 特 定 版 本 的 <strong>Perl</strong>, 比 如 ,perl5.6.1, 那 么 把 它 直 接 放 进 #! 行 的 路 径里 , 要 么 是 和 env 程 序 一 起 , 要 么 是 -S 那 堆 东 西 在 一 起 , 或 者 在 普 通 的 #! 里 。如 果 #! 行 不 包 含 单 词 “perl”, 那 么 在 #! 后 面 的 程 序 代 替 <strong>Perl</strong> 解 释 器 执 行 。比 如 , 假 设 你 有 一 个 普 通 的 Bourne shell 脚 本 , 内 容 是 :#! /bin/shecho "I am a shell script"如 果 你 把 这 个 文 件 给 <strong>Perl</strong> , 那 么 <strong>Perl</strong> 会 为 你 运 行 /bin/sh。 这 个 举 止 可 能 有 些 怪 异 , 但是 它 可 以 帮 助 那 些 不 识 别 #! 的 机 器 的 用 户 。 因 为 这 些 用 户 可 以 通 过 设 置 SHELL 环 境 变 量494


告 诉 一 个 程 序 ( 比 如 一 个 邮 件 程 序 ) 说 , 它 们 的 shell 是 /usr/bin/perl, 然 后 <strong>Perl</strong> 就 帮他 们 把 该 程 序 发 配 给 正 确 的 解 释 器 , 就 算 他 们 的 内 核 傻 得 不 会 干 这 事 也 没 关 系 。 不 过 还 是 让我 们 回 到 真 正 的 <strong>Perl</strong> 脚 本 里 头 来 。 在 完 成 你 的 脚 本 的 定 位 之 后 ,<strong>Perl</strong> 把 整 个 程 序 编 译 成一 种 内 部 格 式 ( 参 阅 第 十 八 章 , 编 译 )。 如 果 发 生 任 何 编 译 错 误 , 脚 本 的 执 行 甚 至 都 不 能 开始 。( 这 一 点 和 典 型 的 shell 脚 本 或 者 命 令 文 件 不 同 , 它 们 在 发 现 一 个 语 法 错 误 之 前 可 能先 跑 上 一 段 。) 如 果 脚 本 语 法 正 确 , 那 么 就 开 始 执 行 。 如 果 脚 本 运 行 到 最 后 也 没 有 发 现 一 个exit 或 者 die 操 作 符 , 那 么 <strong>Perl</strong> 隐 含 地 提 供 一 个 exit(0), 为 你 的 脚 本 的 调 用 者 标 识 一个 成 功 的 结 束 状 态 。( 这 一 点 和 典 型 的 C 程 序 也 不 一 样 , 在 C 里 面 , 如 果 你 的 程 序 只 是 按照 通 常 的 方 法 结 束 , 那 么 你 的 退 出 状 态 是 随 机 的 。)** 在 非 Unix 系 统 上 的 #! 和 引 号Unix 的 #! 技 巧 可 以 在 其 他 系 统 上 仿 真 :Macintosh在 Macintosh 上 的 <strong>Perl</strong> 程 序 有 合 适 的 创 建 者 和 类 型 , 所 以 双 击 它 们 就 会调 用 <strong>Perl</strong> 应 用 。MS-DOS创 建 一 个 批 处 理 文 件 运 行 你 的 程 序 , 并 且 把 它 在 ALTERNATIVE_SHEBANG 里 成 文 。阅 <strong>Perl</strong> 源 程 序 发 布 的 顶 级 目 录 里 的 dosish.h 文 件 获 取 更 多 这 方 面 的 信 息 。参OS/2把 下 面 这 行 :extproc perl -S -your_siwtches495


放 在 *.cmd 文 件 的 第 一 行 里 (-S 绕 开 了 一 个 在 cmd.exe 里 的 “extproc”处 理 的 臭 虫 。)VMS把 下 面 几 行 :% perl -mysw 'f$env("procedure")' 'p1' 'p2' 'p3' 'p4' 'p5' 'p6' 'p7' 'p8' !$ exit++ + ++$status != 0 and $exit = $status = undef;放 在 你 的 程 序 的 顶 端 , 这 里 的 -mysw 是 任 何 你 想 传 递 给 <strong>Perl</strong> 的 命 令 行 开 关 。 现 在你 可 以 直 接 通 过 键 入 perl program 调 用 你 的 程 序 , 或 者 说 @program 把 它 当 作 一 个DCL 过 程 调 用 , 或 者 使 用 程 序 名 通 过 隐 含 地 DCL$PATH 调 用 。 这 些 方 法 记 起 来 有 点 困 难 , 不过 如 果 你 在 perl 里 键 入 "-V:startperl", 那 么 <strong>Perl</strong> 会 给 你 显 示 出 来 。 如 果 你 记 不 住 这个 用 法 —— 很 好 , 那 就 是 你 买 这 本 书 的 原 因 。Win??如 果 在 一 些 Microsoft Windows 系 列 操 作 系 统 里 ( 也 就 是 Win95,Win98, Win00( 注 :请 原 谅 , 我 们 只 用 两 位 数 表 示 年 代 ),WinNT, 不 过 不 包 括 Win31。) 使 用 <strong>Perl</strong> 的 ActiveState版 本 。<strong>Perl</strong> 的 安 装 过 程 修 改 了 Windows 的 注 册 表 , 把 .pl 扩 展 名 和 <strong>Perl</strong> 解 释 器 关 联 起来 。如 果 你 安 装 了 另 外 一 个 移 植 的 <strong>Perl</strong>, 包 括 那 个 在 <strong>Perl</strong> 版 本 里 Win32 目 录 里 的 那 个 ,那 么 你 就 必 须 自 己 修 改 Windows 注 册 表 。请 注 意 如 果 你 使 用 .pl 扩 展 名 就 意 味 着 你 再 也 不 能 区 分 一 个 可 执 行 <strong>Perl</strong> 程 序 和 一 个“perl 库 ” 文 件 了 。 你 可 以 用 .plx 做 <strong>Perl</strong> 程 序 的 扩 展 名 以 避 免 这 个 问 题 。 现 在 这 个问 题 已 经 不 明 显 了 , 因 为 大 多 数 <strong>Perl</strong> 模 块 在 .pm 文 件 里 。在 非 Unix 系 统 上 的 命 令 行 解 释 器 通 常 和 Unix shell 有 不 同 的 引 号 的 用 法 。 你 必 须 了 解 你的 命 令 行 解 释 器 里 的 特 殊 字 符 (*,\, 和 " 是 比 较 常 见 的 ) 以 及 如 何 保 护 通 过 -e 开 关 运496


行 的 一 行 程 序 里 的 空 白 和 这 些 特 殊 字 符 。 如 果 % 是 你 的 shell 的 特 殊 字 符 , 你 还 可 以 把 单个 % 改 成 %%, 或 者 把 它 逃 逸 。在 一 些 系 统 上 , 你 可 能 还 要 把 单 引 号 改 成 双 引 号 。 但 是 不 要 在 Unix 或 者 Plan9 系 统 , 或者 任 何 运 行 Unix 风 格 的 shell 上 这 么 干 , 比 如 从 MKS 工 具 箱 或 者 来 自 Cygnus 的 哥 几 个( 现 在 在 Redhat) 的 Cygwin 包 。 呃 ,Microsoft 的 叫 Interix 的 新 的 Unix 仿 真 器 也 开始 看 到 了 , 也 要 注 意 。比 如 , 在 Unix 和 Mac OS X 里 , 用 :%perl -e 'print "Hello world\n"'在 Macintosh (Mac OS X 的 前 身 ) 里 , 用 :print "Hello world\n"然 后 运 行 "Myscript" 或 者 Shift-Command-R。在 VMS 上 , 使 用 :$perl -e "print ""Hello world\n"""或 者 再 次 使 用 qq//:$perl -e "print qq(Hello world\n)"在 MS-DOS 等 等 里 , 用 :A:> perl -e "print \"Hello world\n\"或 者 用 qq// 使 用 自 己 的 引 号 :A:> perl -e "print qq(Hello world\n)"问 题 是 这 些 方 法 都 是 不 可 靠 的 : 它 依 赖 于 你 使 用 的 命 令 行 解 释 器 。 如 果 4DOS 是 命 令 行shell, 下 面 这 样 的 命 令 可 能 跑 得 好 些 :perl -e "print "Hello world\n""497


Windows NT 上 的 CMD.EXE 程 序 好 象 在 没 有 人 注 意 的 情 况 下 偷 偷 加 入 了 许 多 Unix shell的 功 能 , 但 你 需 要 看 看 它 的 文 档 , 查 找 它 的 引 号 规 则 。在 Macintosh 上 ,( 注 : 至 少 在 Mac OS X 发 布 之 前 , 我 们 可 以 很 开 心 地 说 它 是 源 于 BSD 的系 统 。) 所 有 这 些 取 决 于 你 用 的 是 哪 个 环 境 。 Mac<strong>Perl</strong> shell, 或 者 MPW, 都 很 象 Unix shell,支 持 几 个 引 号 变 种 , 只 不 过 它 把 Macintosh 的 非 ASCII 字 符 自 由 地 用 做 控 制 字 符 。对 这 些 问 题 我 们 没 有 通 用 的 解 决 方 法 。 它 们 就 这 么 乱 。 如 果 你 用 的 不 是 Unix 系 统 , 但 是 想做 命 令 行 类 的 处 理 , 最 好 的 解 决 方 法 是 找 一 个 比 你 的 供 应 商 提 供 的 更 好 的 命 令 行 解 释 器 , 这个 不 会 太 难 。 或 者 把 所 有 的 东 西 都 在 <strong>Perl</strong> 里 写 , 完 全 忘 掉 那 些 单 行 命 令 。**<strong>Perl</strong> 的 位 置尽 管 这 个 问 题 非 常 明 显 , 但 是 <strong>Perl</strong> 只 有 在 用 户 很 容 易 找 到 它 的 时 候 才 有 用 。 如 果 可 能 , 最好 /usr/bin/perl 和 /usr/local/bin/perl 都 是 指 向 真 正 二 进 制 文 件 的 符 号 链 接 。 如 果 无法 做 到 这 一 点 , 我 们 强 烈 建 议 系 统 管 理 员 把 <strong>Perl</strong> 和 其 相 关 工 具 都 放 到 一 个 用 户 的 标 准PATH 里 面 去 , 或 者 其 他 的 什 么 明 显 而 又 方 便 的 位 置 。在 本 书 中 , 我 们 在 程 序 的 第 一 行 使 用 标 准 的 #! /usr/bin/perl 符 号 来 表 示 在 你 的 系 统 上 能用 的 任 何 相 应 机 制 。 如 果 你 想 运 行 特 定 版 本 的 <strong>Perl</strong>, 那 么 使 用 声 明 的 位 置 :#! /usr/local/bin/perl5.6.0如 果 你 只 是 想 运 行 至 少 某 个 版 本 的 <strong>Perl</strong> , 而 不 在 乎 运 行 更 高 版 本 , 那 么 在 你 的 程 序 顶 端 附近 放 下 面 这 样 的 语 句 :use v5.6.0( 请 注 意 : 更 早 的 <strong>Perl</strong> 版 本 使 用 象 5.005 或 者 5.004_05 这 样 的 数 字 。 现 在 我 们 会 把 它们 看 作 5.5.0 和 5.4.5, 不 过 比 5.6.0 早 的 <strong>Perl</strong> 版 本 不 能 理 解 那 些 符 号 。)498


** 开 关单 字 符 并 且 没 有 自 己 的 参 数 的 命 令 行 开 关 可 以 与 其 他 跟 在 后 面 的 开 关 组 合 ( 捆 绑 ) 在 一 起 。#! /usr/bin/perl -spi.bak # 和 -s -p -i.bak 一 样开 关 , 有 时 候 也 叫 选 项 或 者 标 志 。 不 管 你 叫 他 们 什 么 , 下 面 的 是 <strong>Perl</strong> 识 别 的 一 些 :-- 结 束 开 关 处 理 , 就 算 下 一 个 参 数 以 一 个 负 号 开 头 也 要 结 束 。 它 没 有 其 他 作 用 。-0OCTNUM-0 把 记 录 分 隔 符 ($/) 声 明 为 一 个 八 进 制 数 字 。 如 果 没 有 提 供 OCTNUM, 那 么 NUL 字 符 ( 也就 是 ASCII 字 符 0,<strong>Perl</strong> 的 "\0") 就 是 分 隔 符 。 其 他 开 关 可 以 超 越 或 者 遵 循 这 个 八 进 制数 字 。 比 如 , 如 果 你 有 一 个 可 以 打 印 文 件 名 是 空 字 符 结 尾 的 文 件 的 find(1) 版 本 , 那 么 你可 以 这 么 说 :% find . -name '*.bak' -print0 | perl -n0e unlink特 殊 数 值 00 令 <strong>Perl</strong> 以 段 落 模 式 读 取 文 件 , 等 效 于 把 $/ 变 量 设 置 为 ""。 数 值0777 令 <strong>Perl</strong> 立 即 把 整 个 文 件 都 吃 掉 。 这 么 做 等 效 于 解 除 $/ 变 量 的 定 义 。 我 们 使 用 0777是 因 为 没 有 ASCII 字 符 是 那 个 数 值 。( 不 幸 的 是 , 有 一 个 Unicode 字 符 是 那 个 数 值 ,\N{LATIN SMALL LETTER O WITH STROKE AND ACUTE}, 不 过 有 人 说 你 不 会 用 那 个 字 符 分 隔 你的 记 录 。)-a 打 开 自 动 分 割 模 式 , 不 过 只 有 在 和 -n 或 -p 一 起 使 用 时 才 有 效 。 在 -n 和 -p 开 关 生成 的 while 循 环 里 首 先 对 @F 数 组 进 行 一 次 隐 含 的 split 命 令 调 用 。 因 此 :% perl -ane 'print pop(@F), "\n";'等 效 于 :LINE: while () {@F = split(' ');print pop(@F), "\n";}你 可 以 通 过 给 split 的 -F 开 关 传 递 一 个 正 则 表 达 式 声 明 另 外 一 个 域 分 隔 符 。 比 如 ,下 面 两 个 调 用 是 等 效 的 :499


% awk -F: '$7 && $7 !~ /^\/bin/' /etc/passwd% perl -F: -lane 'print if $F[6] && $F[6] !~ m(^/bin)' /etc/passwd-c 令 <strong>Perl</strong> 检 查 脚 本 的 语 法 然 后 不 执 行 刚 编 译 的 程 序 退 出 。 从 技 术 角 度 来 讲 , 它 比那 做 得 更 多 一 些 : 它 会 执 行 任 何 BEGIN 或 CHECK 块 以 及 任 何 use 指 令 , 因 为这 些 都 是 在 执 行 你 的 程 序 之 前 要 发 生 的 事 情 。 不 过 它 不 会 再 执 行 任 何 INIT 或 者END 块 了 。 你 仍 然 通 过 在 你 的 主 脚 本 的 末 尾 包 括 下 面 的 行 获 得 老 一 些 的 但 很 少用 到 的 性 质 :BEGIN { $^C = 0; exit; }-C 如 果 目 标 系 统 支 持 本 机 宽 字 符 , 则 允 许 <strong>Perl</strong> 在 目 标 系 统 上 使 用 本 机 宽 字 符 API 对 于 版本 5.6.0 而 言 , 它 只 能 用 于 Microsoft 平 台 )。 特 殊 变 量 ${^WIDE_SYSTEM_CALLS} 反 映 这个 开 关 的 状 态 。-d 在 <strong>Perl</strong> 调 试 器 里 运 行 脚 本 。 参 阅 第 二 十 章 ,<strong>Perl</strong> 调 试 器 。-dMODULE在 调 试 和 跟 踪 模 块 的 控 制 下 运 行 该 脚 本 , 该 模 块 以 Devel::MODULE 形 式 安 装 在<strong>Perl</strong> 库 里 。 比 如 , -d:Dprof 使 用 Devel::Dprof 调 节 器 执 行 该 脚 本 。 参 阅 第 二 十 章 的 调试 节 。-DLETTERS-DNUMBER设 置 调 试 标 志 。( 这 个 开 关 只 有 在 你 的 <strong>Perl</strong> 版 本 里 编 译 了 调 试 特 性 ( 下 面 描 述 )之 后 才 能 用 。) 你 可 以 声 明 一 个 NUMBER, 它 是 你 想 要 的 位 的 总 和 , 或 者 一 个LETTER 的 列 表 。 比 如 , 想 看 看 <strong>Perl</strong> 是 如 何 执 行 你 的 脚 本 的 , 用 -D14 或 者-Dslt。 另 外 一 个 有 用 的 值 是 -D1024 或 -Dx, 它 会 列 出 你 编 译 好 的 语 法 树 。 而-D512 或 -Dr 显 示 编 译 好 的 正 则 表 达 式 。 数 字 值 在 内 部 可 以 作 为 特 殊 的 变 量 $^D获 得 。 表 19-1 列 出 了 赋 了 值 的 位 。表 19-1 -D 选 项500


位 | 字 母 | 含 义------------------------------------1 | p | 记 号 分 解 和 分 析2 | s | 堆 栈 快 照4 | l | 标 签 堆 栈 处 理8 | t | 跟 踪 执 行16 | o | 方 法 和 重 载 解 析32 | c | 字 串 / 数 字 转 换64 | P | 为 -P 打 印 预 处 理 器 命 令128 | m | 存 储 器 分 配256 | f | 格 式 化 处 理512 | r | 正 则 分 析 和 执 行1024 | x | 语 法 树 倾 倒2048 | u | 污 染 检 查4096 | L | 内 存 泄 露 ( 需 要 在 编 译 <strong>Perl</strong> 时 使 用 -DLEAKTEST)8192 | H | 哈 希 倾 倒 -- 侵 占 values()16384 | X | 便 签 簿 分 配32768 | D | 清 理65536 | S | 线 程 同 步所 有 这 些 标 志 都 需 要 <strong>Perl</strong> 的 可 执 行 文 件 是 为 调 试 目 的 特 殊 制 作 的 。 不 过 , 因 为 调 试制 作 不 是 缺 省 , 所 以 除 非 你 的 系 统 管 理 员 制 作 了 这 个 特 殊 的 <strong>Perl</strong> 的 调 试 版 本 , 否 则你 根 本 别 想 用 -D 开 关 。 参 阅 <strong>Perl</strong> 源 文 件 目 录 里 面 的 INSTALL 文 件 获 取 细 节 , 短501


一 些 的 说 法 是 你 在 编 译 <strong>Perl</strong> 本 身 的 时 候 需 要 给 你 的 C 编 译 器 传 递 -DDEBUGGING编 译 选 项 。 在 Configure 问 你 优 化 选 项 和 调 试 选 项 的 时 候 , 如 果 你 包 括 了 -g 选 项 ,那 么 这 个 编 译 选 项 会 自 动 加 上 。如 果 你 只 是 想 在 你 的 每 行 <strong>Perl</strong> 代 码 执 行 的 时 候 获 取 一 个 打 印 输 出 ( 象 sh -x 为shell 脚 本 做 的 那 样 ), 那 你 就 不 能 用 -D 开 关 。 你 应 该 用 :# Bourne shell 语 法$PERLDB_OPTS="NonStop=1 AutoTrace=1 frame=2" perl -dS program# csh 语 法% (setenv PERLDB_OPTS "NonStop=1 AutoTrace=1 frame=2"; perl -dS program)见 第 二 十 章 获 取 细 节 和 变 种 。-e PERLCODE可 以 用 于 输 入 一 行 或 多 行 脚 本 。 如 果 使 用 了 -e ,<strong>Perl</strong> 将 不 在 参 数 列 表 中 寻 找 程 序的 文 件 名 。<strong>Perl</strong> 把 PERLCODE 参 数 当 作 有 新 行 结 尾 看 待 , 所 以 可 以 给 出 多 个 -e命 令 形 成 一 个 多 行 的 程 序 。( 一 定 要 使 用 分 号 , 就 象 你 在 文 件 里 的 程 序 一 样 。) -e给 每 个 参 数 提 供 新 行 并 不 意 味 着 你 必 须 使 用 多 个 -e 开 关 ; 如 果 你 的 shell 支 持多 行 的 引 用 , 比 如 sh,ksh, 或 bash, 你 可 以 把 多 行 脚 本 当 作 一 个 -e 参 数 传 递 :$perl -e 'print "Howdy, ";print "@ARGV!\n";' world502


Howdy, world!对 于 csh 而 言 , 可 能 最 好 还 是 使 用 多 个 -e 开 关 :%perl -e 'print "Howdy, ";'-e 'print "@ARGV!\n";' worldHowdy, world!在 行 计 数 的 时 候 , 隐 含 的 和 明 确 给 出 的 新 行 都 算 数 , 所 以 两 个 程 序 中 的 第 二 个print 都 是 在 -e 脚 本 的 第 2 行 。-F PATTERN声 明 当 通 过 -a 开 关 ( 否 则 没 有 作 用 ) 自 动 分 裂 是 要 split 的 模 式 。 该 模 式 可 以 由斜 杠 (//), 双 引 号 (""), 或 者 单 引 号 ('') 包 围 。 否 则 , 他 会 自 动 放 到 单 引 号 里 。请 注 意 如 果 要 通 过 shell 传 递 引 号 , 你 必 须 把 你 的 引 号 引 起 来 , 具 体 怎 么 做 取 决 于你 的 系 统 。-h 打 印 一 个 <strong>Perl</strong> 命 令 行 选 项 的 概 要-i EXTENSION-i 声 明 那 些 由 构 造 处 理 的 文 件 将 被 现 场 处 理 。<strong>Perl</strong> 是 通 过 这 样 的 办 法 实 现 的 :先 重 命 名 输 入 文 件 , 然 后 用 原 文 件 名 打 开 输 出 文 件 , 并 且 把 该 输 出 文 件 选 为 调 用print,printf, 和 write 的 缺 省 。( 注 : 通 常 , 这 并 不 是 真 的 “ 现 场 ”。 它 是相 同 的 文 件 名 , 但 是 不 同 的 物 理 文 件 。)503


EXTENSION 用 于 修 改 旧 文 件 名 然 后 做 一 个 备 份 的 拷 贝 。 如 果 没 有 提 供 EXTENSION,那 么 不 做 备 份 并 且 当 前 文 件 被 覆 盖 。 如 果 EXTENSION 不 包 含 一 个 *, 那 么 该 字 串被 附 加 到 当 前 文 件 名 的 后 面 。 如 果 EXTENSION 包 含 一 个 或 多 个 * 字 符 , 那 么 每 个* 都 被 当 前 正 在 处 理 的 文 件 名 替 换 。 用 <strong>Perl</strong> 的 话 来 说 , 你 可 以 认 为 事 情 是 这 样 的 :($backup = $extension) =~ s/\*/$file_name/g;这 样 就 允 许 你 把 一 个 前 缀 用 于 备 份 文 件 , 而 不 是 -- 或 者 可 以 说 是 除 了 后 缀 以 外 :%perl -pi'orig_*' -e 's/foo/bar/' xyx# 备 份 到 'orig_xyx'你 甚 至 可 以 可 以 把 原 来 文 件 的 备 份 放 到 另 外 一 个 目 录 里 ( 只 要 该 目 录 已 经 存 在 ):%perl -pi'old/*.orig' -e 's/foo/bar/' xyx # 备 份 到 'old/xyx.orig'这 些 一 行 程 序 对 都 是 相 等 的 :%perl -pi -e 's/foo/bar/' xyx%perl -pi'*' -e 's/foo/bar/' xyx# 覆 盖 当 前 文 件# 覆 盖 当 前 文 件%perl -pi'.orig' -e 's/foo/bar/' xyx%perl -pi'*.orig' -e 's/foo/bar/' xyx# 备 份 到 'xyx.orig'# 备 份 到 'xyx.orig'504


从 shell 上 , 你 说 :%perl -p -i.oirg -e "s/foo/bar/;"等 效 于 使 用 下 面 的 程 序 :#! /usr/bin/perl -pi.origs/foo/bar/;而 上 面 的 又 是 下 面 的 程 序 的 便 利 缩 写 :#! /usr/bin/perl$extension = '.orig';LINE: while(){if ($ARGV ne $oldargv) {if ($extension !~ /\*/) {$backup = $ARGV . $extension;}else {($backup = $extension) =~ s/\*/$ARGV/g;}unless (rename($ARGV, $bckup)) {warn "cannot rename $ARGV to $backup: $! \n";close ARGV;505


next;}open(ARGVOUT, ">$ARGV");select(ARGVOUT);$oldargv = $ARGV;}s/foo/bar/;}continue {print;# 这 一 步 打 印 到 原 来 的 文 件 名}select(STDOUT);这 一 段 长 代 码 实 际 上 相 当 于 那 条 简 单 的 单 行 带 -i 开 关 的 命 令 , 只 不 过 -i 的 形 式不 需 要 拿 $ARGV 和 $oldargv 进 行 比 较 以 判 断 文 件 名 是 否 改 变 。 不 过 , 它 的 确 使 用ARGVOUT 作 为 选 出 的 文 件 句 柄 并 且 在 循 环 结 束 以 后 把 原 来 的 STDOUT 恢 复 为 缺 省 文 件句 柄 。 象 上 面 的 代 码 那 样 ,<strong>Perl</strong> 创 建 备 份 文 件 时 并 不 考 虑 任 何 输 出 是 否 真 的 被 修 改了 。 如 果 你 想 附 加 到 每 个 文 件 背 后 , 或 者 重 置 行 号 , 那 么 请 参 阅 eof 函 数 的 描 述 ,获 取 关 于 如 何 使 用 不 带 圆 括 号 的 eof 定 位 每 个 输 入 文 件 的 结 尾 的 例 子 。如 果 对 于 某 个 文 件 来 说 ,<strong>Perl</strong> 不 能 创 建 象 EXTENSION 里 声 明 的 那 样 的 备 份 文 件 ,它 会 为 之 发 出 一 个 警 告 然 后 继 续 处 理 列 出 的 其 他 文 件 。你 不 能 用 -i 创 建 目 录 或 者 剥 离 文 件 的 扩 展 名 。 你 也 不 能 用 一 个 ~ 来 表 示 家 目 录 --506


因 为 有 些 家 伙 喜 欢 使 用 这 个 字 符 来 表 示 他 们 的 备 份 文 件 :%perl -pi~ -e 's/foo/bar' file1 file2 file3...最 后 , 如 果 命 令 行 上 没 有 给 出 文 件 名 , 那 么 -i 开 关 不 会 停 止 <strong>Perl</strong> 的 运 行 。 如 果发 生 这 种 事 情 , 则 不 会 做 任 何 备 份 , 因 为 不 能 判 断 原 始 文 件 , 而 可 能 会 发 生 从 STDIN到 STDOUT 的 处 理 。-IDIRECTORY-I 声 明 的 目 录 比 @INC 优 先 考 虑 , 它 包 含 搜 索 模 块 的 目 录 。-I 还 告 诉 C 预 处 理器 到 那 里 寻 找 包 含 文 件 。C 预 处 理 器 是 用 -P 调 用 的 ; 缺 省 时 它 搜 索/usr/include 和 /usr/lib/perl。 除 非 你 打 算 使 用 C 预 处 理 器 ( 而 实 际 上 几 乎没 人 再 干 这 事 了 ), 否 则 你 最 好 在 你 的 脚 本 里 使 用 use lib 指 示 器 。 不 过 ,-I 和use lib 类 似 , 它 隐 含 地 增 加 平 台 相 关 的 目 录 。 参 阅 第 三 十 一 章 , 实 用 模 块 , 里 的use lib 获 取 细 节 。-lOCTNUM-l 打 开 自 动 行 结 束 处 理 。 它 有 两 个 效 果 : 首 先 , 如 果 它 和 -n 或 者 -p 一 起 使 用 ,它 自 动 chomp 行 终 止 符 , 其 次 , 它 把 $\ 设 置 为 OCTNUM 的 数 值 , 这 样 任 何 打 印语 句 将 有 一 个 值 为 OCTNUM 的 ASCII 字 符 追 加 在 结 尾 代 替 行 终 止 符 。 如 果 省 略 了OCTNUM,-l 把 $\ 设 置 为 $/ 的 当 前 值 , 通 常 是 新 行 。 因 此 要 把 行 截 断 为 80列 , 你 这 么 说 :%perl -lpe 'substr($_, 80) = ""'507


请 注 意 在 处 理 这 个 开 关 的 时 候 完 成 了 $\ = $/ 的 赋 值 , 因 此 如 果 -l 开 关 后 面跟 着 -0 开 关 , 那 么 这 个 输 入 记 录 分 隔 符 可 以 与 输 出 记 录 分 隔 符 不 同 :%gnufind / -print0 | perl -ln0e 'print "found $_" if -p'这 条 命 令 把 $\ 设 置 为 新 行 而 稍 后 把 $/ 设 置 为 空 字 符 。( 请 注 意 如 果 0 直 接 跟在 -l 后 面 , 它 将 被 当 作 -l 开 关 的 一 部 分 。 这 就 是 为 什 么 我 们 在 它 们 之 间 绑 上 了-n 开 关 。)-m 和 -M这 些 开 关 装 载 一 个 MODULE, 就 象 你 执 行 了 一 个 use 那 样 , 如 果 你 声 明 的 是-MODULE, 而 不 是 MODULE, 那 么 它 将 调 用 no。 比 如 ,-Mstrict 类 似use strict, 而 -M-strict 类 似 -no strict。-mMODULE在 执 行 你 的 脚 本 之 前 执 行 use MODULE()。-MMODULE-M'MODULE ...'在 执 行 你 的 脚 本 之 前 执 行 use MODULE。 这 条 命 令 是 通 过 简 单 的 解 析 -M 后 面剩 下 的 参 数 形 成 的 , 因 此 你 可 以 用 引 号 在 该 模 块 名 后 面 加 额 外 的 代 码 , 比 如 ,-M'MODULE qw(foo bar)'。-MMODULE=arg1,arg2...一 块 小 小 的 语 法 糖 , 意 思 是 你 还 可 以 把 -Mmodule=foo,bar 当 作508


-M'module qw(foo bar)' 的 一 个 缩 写 来 用 。 这 样 当 输 入 符 号 的 时 候 就 避 免 了引 号 的 使 用 。-Mmodule=foo,bar 生 成 的 实 际 的 代 码 是 :use module split(/,/, q{foo,bar})请 注 意 = 的 形 式 删 除 了 -m 和 -M 之 间 的 区 别 , 但 是 最 好 还 是 使 用 大 写 的形 式 以 避 免 混 淆 。你 可 能 只 会 在 真 正 的 <strong>Perl</strong> 命 令 行 调 用 的 时 候 使 用 -M 和 -m 开 关 , 而 不 会 在 #!封 装 的 选 项 行 上 用 。( 如 果 你 准 备 把 它 放 在 文 件 里 , 为 什 么 不 用 一 个 等 效 的 use或 者 no 代 替 呢 ?)-n 令 <strong>Perl</strong> 认 为 在 你 的 脚 本 周 围 围 绕 着 下 面 的 循 环 , 这 样 就 让 你 的 脚 本 遍 历 文 件 名参 数 , 就 象 sed -n 或 者 awk 做 的 那 样 :LINE:while() {... # 你 的 脚 本 在 这 里}你 可 以 在 你 的 脚 本 里 把 LINE 当 作 一 个 循 环 标 记 来 用 , 即 使 你 在 你 的 文 件 里 看不 到 实 际 的 标 记 也 如 此 。请 注 意 , 那 些 行 缺 省 的 时 候 并 不 打 印 。 参 阅 -p 选 项 看 看 如 何 打 印 。 下 面 是 一 个删 除 旧 于 一 周 的 文 件 的 有 效 方 法 :509


find . -mtime +7 -print | perl -nle unlink这 样 做 比 用 find(1) 的 -exec 开 关 要 快 , 因 为 你 不 需 要 对 每 个 找 到 的 文 件 启 动一 个 进 程 。 一 个 很 有 趣 的 一 致 性 是 , 你 可 以 用 BEGIN 和 END 块 捕 获 这 个 隐 含 的循 环 之 前 或 之 后 的 控 制 , 就 象 awk 一 样 。-p 令 <strong>Perl</strong> 认 为 在 你 的 脚 本 周 围 围 绕 着 下 面 的 循 环 , 这 样 就 让 你 的 脚 本 遍 历 文 件 名参 数 , 象 sed 那 样 :LINE:while() {... # 你 的 脚 本 在 这 里}continue {print or die "-p destination: $!\n";}你 可 以 在 你 的 脚 本 里 把 LINE 当 作 一 个 循 环 标 记 来 用 , 即 使 你 在 你 的 文 件 里看 不 到 实 际 的 标 记 也 如 此 。如 果 由 于 某 种 原 因 一 个 参 数 命 名 的 文 件 无 法 打 开 ,<strong>Perl</strong> 会 警 告 你 , 然 后 继 续 下一 个 文 件 。 请 注 意 这 些 行 都 自 动 打 印 出 来 。 在 打 印 的 过 程 中 如 果 发 生 错 误 则 认 为 是致 命 错 误 。 同 样 也 是 一 个 很 有 趣 的 一 致 性 是 , 你 可 以 用 BEGIN 和 END 块 捕 获 这 个510


隐 含 的 循 环 之 前 或 之 后 的 控 制 , 就 象 awk 一 样 。-P 令 你 的 脚 本 先 由 C 预 处 理 器 运 行 , 然 后 才 由 <strong>Perl</strong> 编 译 。( 因 为 注 释 和 cpp(1)指 示 都 是 由 # 字 符 开 头 , 所 以 你 应 该 避 免 注 释 任 何 C 预 处 理 器 可 以 识 别 的 词 ,比 如 "if","else", 或 者 "define"。) 不 过 你 用 不 用 -P 开 关 ,<strong>Perl</strong> 都 会 注 意#line 指 示 以 控 制 行 号 和 文 件 名 , 这 样 任 何 预 处 理 器 都 可 以 通 知 <strong>Perl</strong> 这 些 事 情 。参 阅 第 二 十 四 章 , 普 通 实 践 , 里 面 的 “ 在 其 他 语 言 里 生 成 <strong>Perl</strong>”。-s 打 开 命 令 行 上 脚 本 名 之 后 , 但 在 任 何 文 件 名 或 者 一 个 “--” 开 关 处 理 终 止 符 之 前 的基 本 的 开 关 分 析 。 找 到 的 任 何 开 关 都 从 @ARGV 里 删 除 , 并 且 在 <strong>Perl</strong> 里 设 置 一 个同 名 的 开 关 变 量 。 这 里 不 允 许 开 关 捆 绑 , 因 为 这 里 允 许 使 用 多 字 符 开 关 。下 面 的 脚 本 只 有 在 你 带 着 -foo 开 关 调 用 脚 本 的 时 候 才 打 印 “true”。#! /sr/bin/perl -sif ($foo) {print "true\n"}如 果 该 开 关 形 如 -xxx=yyy, 那 么 $xxx 变 量 的 值 设 置 为 跟 在 这 个 参 数 的 等 号 后 面的 值 ( 本 例 中 是 ”yyy“)。 下 面 的 脚 本 只 有 在 你 带 着 -foo=bar 开 关 调 用 的 时 候才 打 印 “true”。#! /usr/bin/perl -sif ($foo eq 'bar') { print "true\n" }511


-S 让 <strong>Perl</strong> 使 用 PATH 环 境 变 量 搜 索 该 脚 本 ( 除 非 脚 本 的 名 字 包 含 目 录 分 隔 符 )。通 常 , 这 个 开 关 用 于 帮 助 在 那 些 不 支 持 #! 的 平 台 上 仿 真 #!。 在 许 多 有 兼 容Bourne 或 者 C shell 的 平 台 上 , 你 可 以 用 下 面 这 些 :#! /usr/bin/perleval "exec /usr/bin/perl -S $0 $*"if $running_under_some_shell;系 统 忽 略 第 一 行 然 后 把 这 个 脚 本 交 给 /bin/sh, 然 后 它 继 续 运 行 然 后 试 图 把 <strong>Perl</strong>脚 本 当 作 一 个 shell 脚 本 运 行 。 该 shelll 把 第 二 行 当 作 一 个 普 通 的 shell命 令 执 行 , 因 此 启 动 <strong>Perl</strong> 解 释 器 。 在 一 些 系 统 上 ,$0 并 不 总 是 包 含 路 径 全 名 ,因 此 -S 告 诉 <strong>Perl</strong> 在 必 要 的 时 候 搜 索 该 脚 本 。 在 <strong>Perl</strong> 找 到 该 脚 本 以 后 , 它分 析 该 行 并 且 忽 略 它 们 , 因 为 变 量 $running_under_some_shell 总 是 为 假 。 一 个更 好 的 构 造 是 $* 应 该 是 ${1+"$@"}, 它 可 以 处 理 文 件 名 中 嵌 入 的 空 白 这 样 的东 西 , 但 如 果 该 脚 本 被 csh 解 释 将 不 能 运 行 , 为 了 用 sh 代 替 csh 启 动 , 有 些系 统 必 须 用 一 个 只 有 一 个 冒 号 的 行 替 代 #! 行 ,<strong>Perl</strong> 会 礼 貌 地 忽 略 那 样 的 行 。其 他 不 能 支 持 这 些 的 系 统 必 须 用 一 种 完 全 迂 回 的 构 造 , 这 种 构 造 可 以 在 任 何csh,sh 或 者 perl 里 运 行 , 这 种 构 造 是 这 样 的 :eval '(exit $?0)' && eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'& eval 'exec /usr/bin/perl -S $0 $argv:q'if -;512


的 确 , 这 个 东 西 很 难 看 , 不 过 那 些 系 统 也 一 样 难 看 ( 注 : 我 们 故 意 用 这 个 词 )。在 一 些 平 台 上 ,-S 开 关 同 样 也 令 <strong>Perl</strong> 在 搜 索 的 时 候 给 文 件 名 附 加 后 缀 。 比 如 ,在 Win32 平 台 上 , 如 果 对 最 初 的 文 件 名 查 找 失 败 而 且 这 个 文 件 名 原 来 没 有 用.bat 或 者 .com, 则 会 给 该 文 件 名 后 缀 上 .bat 或 者 .com 进 行 查 找 。 如 果 你 的<strong>Perl</strong> 是 打 开 调 试 编 译 的 , 那 么 你 可 以 使 用 <strong>Perl</strong> 的 -Dp 开 关 来 观 察 搜 索 过 程 。如 果 你 提 供 的 文 件 名 包 含 目 录 分 隔 符 ( 即 使 只 是 一 个 相 对 路 径 名 , 而 不 是 绝 对 路 径名 ), 而 且 如 果 没 有 找 到 该 文 件 , 那 么 在 那 些 会 隐 含 附 加 文 件 扩 展 名 的 平 台 上 (不 是 Unix) 就 会 做 这 件 事 , 然 后 一 个 接 一 个 的 找 那 些 带 这 些 扩 展 名 的 文 件 。在 类 似 DOS 的 平 台 上 , 如 果 脚 本 不 包 含 目 录 分 隔 符 , 它 首 先 会 在 当 前 目 录 搜 索 ,然 后 再 寻 找 PATH。 在 Unix 平 台 上 , 出 于 安 全 性 考 虑 , 为 了 避 免 未 经 明 确 请 求 ,偶 然 执 行 了 当 前 工 作 目 录 里 面 的 东 西 , 将 严 格 地 在 PATH 里 搜 索 ,-T 强 制 打 开 “ 感 染 ” 检 查 , 这 样 你 就 可 以 检 查 它 们 了 。 通 常 , 这 些 检 查 只 有 在 运 行setuid 或 者 setgid 的 时 候 才 进 行 。 把 它 们 明 确 地 打 开 , 让 程 序 的 作 者 自 己 控 制是 一 个 不 错 的 主 意 , 比 如 在 CGI 程 序 上 。 参 阅 第 二 十 三 章 , 安 全 。-u 令 <strong>Perl</strong> 在 编 译 完 你 的 脚 本 以 后 倾 倒 核 心 。 然 后 从 理 论 上 来 讲 你 可 以 用 undump程 序 ( 未 提 供 ) 把 它 转 成 一 个 可 执 行 文 件 。 这 样 就 以 一 定 的 磁 盘 空 间 为 代 价 ( 你可 以 通 过 删 除 可 执 行 文 件 来 最 小 化 这 个 代 价 ) 换 来 了 速 度 的 提 升 。 如 果 你 想 在 输 出之 前 执 行 一 部 分 你 的 脚 本 , 那 么 使 用 <strong>Perl</strong> 的 dump 操 作 符 。 注 意 :undump 的可 用 性 是 平 台 相 关 的 ; 可 能 在 某 些 <strong>Perl</strong> 的 移 植 版 本 里 不 能 用 。 它 已 经 被 新 的513


<strong>Perl</strong> 到 C 的 代 码 生 成 器 替 换 掉 了 , 由 这 个 代 码 生 成 器 生 成 的 东 西 更 具 移 植 性( 不 过 仍 然 处 于 实 验 期 )。-U 允 许 <strong>Perl</strong> 进 行 不 安 全 的 操 作 。 目 前 , 唯 一 的 “ 不 安 全 ” 的 操 作 是 以 超 级 用 户 身 份运 行 是 删 除 目 录 , 以 及 在 把 致 命 污 染 检 查 转 换 成 警 告 的 情 况 下 运 行 setuid 程 序 。请 注 意 如 果 要 真 的 生 成 污 染 检 查 警 告 , 你 必 须 打 开 警 告 。-v 打 印 你 的 <strong>Perl</strong> 的 版 本 和 补 丁 级 别 , 以 及 另 外 一 些 信 息 。-V 打 印 <strong>Perl</strong> 的 主 要 配 置 值 的 概 要 以 及 @INC 的 当 前 值 。-V:NAME向 STDOUT 打 印 命 名 配 置 变 量 的 值 。NAME 可 以 包 括 正 则 字 符 , 比 如 用 “.” 匹 配任 何 字 符 , 或 者 ".*" 匹 配 任 何 可 选 的 字 符 序 列 。%perl -V:man.dirman1dir='/usr/local/man/man1'man3dir='/usr/local/man/man3'%perl -V:'.*threads'd_oldpathreads='undef'...( 略 )如 果 你 要 求 的 环 境 变 量 不 存 在 , 它 的 值 将 输 出 为 "UNKNOWN"。 在 程 序 里 可 以 使 用514


Config 模 块 获 取 配 置 信 息 , 不 过 在 哈 希 脚 标 里 不 支 持 模 式 :%perl -MConfig -le 'print $Config{man1dir}'/usr/local/man/man1参 阅 第 三 十 二 章 , 标 准 模 块 , 里 的 Config 模 块 。-w 打 印 关 于 只 提 到 一 次 的 变 量 警 告 , 以 及 在 设 置 之 前 就 使 用 了 的 标 量 的 警 告 。 同 时还 警 告 子 过 程 重 定 义 , 以 及 未 定 义 的 文 件 句 柄 的 引 用 或 者 文 件 句 柄 是 只 读 方 式 打 开的 而 你 却 试 图 写 它 们 这 样 的 信 息 。 如 果 你 用 的 数 值 看 上 去 不 象 数 字 , 而 你 却 把 它 们当 作 数 字 来 使 用 ; 如 果 你 把 一 个 数 组 当 作 标 量 使 用 ; 如 果 你 的 子 过 程 递 归 深 度 超 过100 层 ; 以 及 无 数 其 他 的 东 西 时 也 会 警 告 。 参 阅 第 三 十 三 章 , 诊 断 信 息 , 里 的 每 条标 记 着 “(W)” 的 记 录 。这 个 开 关 只 是 设 置 全 局 的 $^W 变 量 。 它 对 词 法 范 围 的 警 告 没 有 作 用 -- 那 方 面 的 请参 阅 -W 和 -X 开 关 。 你 可 以 通 过 使 用 use warning 用 法 打 开 或 者 关 闭 特 定 的警 告 , 这 些 在 第 三 十 一 章 描 述 。-W 无 条 件 地 永 久 打 开 程 序 中 的 所 有 警 告 , 即 使 你 用 no warnings 或 者 $^W = 0局 部 关 闭 了 警 告 也 没 用 。 它 的 作 用 包 括 所 有 通 过 use,require, 或 者 do 包 括进 来 的 文 件 。 把 它 当 作 <strong>Perl</strong> 的 等 效 lint(1) 命 令 看 待 。-XDIRECTORY-x 告 诉 <strong>Perl</strong> 抽 取 一 个 嵌 入 在 一 条 信 息 里 面 的 脚 本 。 前 导 的 垃 圾 会 被 丢 弃 , 直 到 以515


#! 开 头 并 包 括 字 串 “perl” 的 第 一 行 出 现 。 任 何 在 该 行 的 单 词 “perl” 之 后 的有 意 义 的 开 关 都 会 被 <strong>Perl</strong> 使 用 。 如 果 声 明 了 一 个 目 录 名 ,<strong>Perl</strong> 在 运 行 该 脚 本之 前 将 切 换 到 该 目 录 。-x 开 关 只 控 制 前 导 的 垃 圾 , 而 不 关 心 尾 随 的 垃 圾 。 如 果该 脚 本 有 需 要 忽 略 的 尾 随 的 垃 圾 , 那 它 就 必 须 以 __END__ 或 者 __DATA__ 结 束 ,( 如 果 需 要 , 该 脚 本 可 以 通 过 DATA 文 件 句 柄 处 理 任 何 部 分 或 者 全 部 尾 随 的 垃 圾 。它 在 理 论 上 甚 至 可 以 seek 到 文 件 的 开 头 并 且 处 理 前 导 的 垃 圾 。)-X 无 条 件 及 永 久 地 关 闭 所 有 警 告 , 做 的 正 好 和 -W 完 全 相 反 。* 环 境 变 量除 了 各 种 明 确 修 改 <strong>Perl</strong> 行 为 的 开 关 以 外 , 你 还 可 以 设 置 各 种 环 境 变 量 以 影 响 各 种潜 在 的 性 质 。 怎 样 设 置 环 境 变 量 是 系 统 相 关 的 , 不 过 , 如 果 你 用 sh,ksh 或 者bash, 有 一 个 技 巧 就 是 你 可 以 为 单 条 命 令 临 时 地 设 置 一 个 环 境 变 量 , 就 好 象 它 是 一 个有 趣 的 开 关 一 样 。 你 必 须 在 命 令 前 面 设 置 它 :$PATH='/bin:/usr/bin' perl myproggie你 可 以 在 csh 或 者 tcsh 里 用 一 个 子 shell 干 类 似 的 事 :%(setenv PATH "/bin:/usr/bin"; perl myproggie)否 则 , 你 通 常 就 要 在 你 的 家 目 录 里 的 一 些 名 字 象 .chsrc 或 者 .profile 这 样 的 文 件里 设 置 环 境 变 量 。 在 csh 或 者 tcsh 里 , 你 说 :516


%setenv PATH '/bin:/usr/bin'而 在 sh,ksh, 和 bash 里 , 你 说 :$PATH='/bin:/usr/bin'; export PATH其 他 系 统 里 有 其 他 的 半 永 久 的 设 置 方 法 。 下 面 是 <strong>Perl</strong> 会 注 意 的 环 境 变 量 :HOME如 果 不 带 参 数 调 用 chdir 时 要 用 到 。LC_ALL, LC_CTYPE, LC_COLLATE, LC_NUMERIC, PERL_BADLAND控 制 <strong>Perl</strong> 操 作 某 种 自 然 语 言 的 方 法 的 环 境 变 量 。 参 阅 perllocale 的 联 机文 档 。LOGDIR如 果 没 有 给 chdir 参 数 , 而 且 没 有 设 置 HOME。PATH用 于 执 行 子 进 程 , 以 及 使 用 了 -S 开 关 的 时 候 寻 找 程 序 。PERL5LIB一 个 用 冒 号 分 隔 的 目 录 的 列 表 , 用 于 指 明 在 搜 索 标 准 库 和 当 前 目 录 之 前 搜 索517


<strong>Perl</strong> 库 文 件 的 目 录 。 如 果 存 在 有 任 何 声 明 的 路 径 下 的 体 系 相 关 的 目 录 , 则都 回 自 动 包 括 进 来 。 如 果 没 有 定 义 PERL5LIB, 则 测 试 PERLLIB, 以 保 持 与老 版 本 的 向 下 兼 容 。 如 果 运 行 了 污 染 检 查 ( 要 么 是 因 为 该 程 序 正 在 运 行setuid 或 者 setgid, 要 么 是 因 为 使 用 了 -T 开 关 。), 则 两 个 库 变 量 都不 使 用 。 这 样 的 程 序 必 须 用 use lib 用 法 来 实 现 这 些 目 的 。PERL5OPT缺 省 命 令 行 开 关 。 在 这 个 变 量 里 的 开 关 会 赋 予 每 个 <strong>Perl</strong> 命 令 行 。 只 允 许-[DIMUdmw]。 如 果 你 运 行 污 染 检 查 ( 因 为 该 程 序 正 在 运 行 setuid 或 者setgid, 或 者 使 用 了 -T 开 关 ), 则 忽 略 这 个 变 量 。 如 果 PERL5OPT 是以 -T 开 头 , 那 么 将 打 开 污 染 检 查 , 导 致 任 何 后 继 选 项 都 被 忽 略 。PERL5DB用 于 装 载 调 试 器 代 码 的 命 令 。 缺 省 的 是 :BEGIN { require 'perl5db.pl' }参 阅 第 二 十 章 获 取 这 个 变 量 的 更 多 用 法 。PERL5SHELL( 仅 用 于 Microsoft 移 植 )可 以 设 置 一 个 侯 选 shell, 这 个 shell 是 <strong>Perl</strong> 在 通 过 反 勾 号 或 者system 执 行 命 令 的 时 候 必 须 的 。 缺 省 时 在 WinNT 上 是 cmd.exe /x/c以 及 在 Win95 上 是 command.com /c。<strong>Perl</strong> 认 为 该 值 是 空 白 分 隔 的 。 在518


任 何 需 要 保 护 的 字 符 ( 比 如 空 白 和 反 斜 杠 ) 之 前 用 反 斜 杠 保 护 。请 注 意 <strong>Perl</strong> 并 不 将 COMSPEC 用 于 这 个 目 的 , 因 为 COMSPEC 在 用 户 中 有更 高 的 可 变 性 , 容 易 导 致 移 植 问 题 。 另 外 ,<strong>Perl</strong> 可 以 使 用 一 个 不 适 合 交 互使 用 的 shell, 而 把 COMSPEC 设 置 为 这 样 的 shell 可 能 会 干 涉 其 他 程 序 的正 常 功 能 ( 那 些 程 序 通 常 检 查 COMSPEC 以 寻 找 一 个 适 于 交 互 使 用 的 shell)。PERLLIB一 个 冒 号 分 隔 的 目 录 列 表 , 在 到 标 准 库 和 当 前 目 录 查 找 库 文 件 之 前 先 到 这 些目 录 搜 索 。 如 果 定 义 了 PERL5LIB, 那 么 就 不 会 使 用 PERLLIB。PERL_DEBUG_MSTATS只 有 在 编 译 时 带 上 了 与 <strong>Perl</strong> 发 布 带 在 一 起 的 malloc 函 数 时 才 有 效 ( 也就 是 说 , 如 果 perl -V:d_mymalloc 生 成 “define”)。 如 果 设 置 , 这 就 会导 致 在 执 行 之 后 显 示 存 储 器 统 计 信 息 。 如 果 设 置 为 一 个 大 于 一 的 整 数 , 那 么 也会 导 致 在 编 译 以 后 的 存 储 器 统 计 的 显 示 。PERL_DESTRUCTI_LEVEL只 有 在 <strong>Perl</strong> 的 可 执 行 文 件 是 打 开 了 调 试 编 译 的 时 候 才 相 关 , 它 控 制 对 对 象和 其 他 引 用 的 全 局 删 除 的 行 为 。除 了 这 些 以 外 ,<strong>Perl</strong> 本 身 不 再 使 用 其 他 环 境 变 量 , 只 是 让 它 执 行 的 程 序 以 及 任 何 该程 序 运 行 的 子 程 序 可 以 使 用 他 们 。 有 些 模 块 , 标 准 的 或 者 非 标 准 的 , 可 能 会 关 心其 他 的 环 境 变 量 。 比 如 ,use re 用 法 使 用 PERL_RE_TC 和 PERL_RE_COLORS,Cwd519


模 块 使 用 PWD, 而 CGI 模 块 使 用 许 多 你 的 HTTP 守 护 进 程 ( 也 就 是 你 的 web 服 务 器 )设 置 的 环 境 变 量 向 CGI 脚 本 传 递 信 息 。运 行 setuid 的 程 序 在 做 任 何 事 情 之 前 执 行 下 面 几 行 将 会 运 行 得 更 好 , 它 只 是 保 持人 们 的 诚 实 :$ENV{PATH} = '/bin:/usr/bin';# 或 者 任 何 你 需 要 的$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};参 阅 第 二 十 三 章 获 取 细 节 。520

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!