26.07.2014 Views

Visual Prolog V7.1 初学指南 - PDC Download site

Visual Prolog V7.1 初学指南 - PDC Download site

Visual Prolog V7.1 初学指南 - PDC Download site

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>Visual</strong> <strong>Prolog</strong> <strong>V7.1</strong><br />

初 学 指 南<br />

托 马 斯 ·W· 德 · 玻 尔 编 著<br />

乙 丁 译<br />

V 1.0


1.0 版 说 明<br />

这 里 的 译 文 , 是 按 原 书 1.0 版 在 前 一 个 译 文 的 基 础 上 修 改 的 。<br />

原 书 1.0 版 较 0.95 版 , 作 者 新 增 了 7.6 节 , 重 写 了 8.6 节 , 对 后 三 章 的 内 容 及 程 序 例<br />

子 做 了 较 多 的 修 改 和 调 整 , 还 在 最 后 增 加 了 一 个 索 引 ( 这 个 索 引 译 文 中 没 有 使 用 )。<br />

译 文 修 改 的 过 程 中 , 自 己 发 现 了 一 些 译 得 不 对 的 地 方 , 有 些 错 得 很 离 谱 , 订 正 过 来 了 。<br />

一 定 还 有 , 只 能 等 着 挨 砖 了 !<br />

乙 丁<br />

2009 年 3 月<br />

II


译 序<br />

译 者 对 人 工 智 能 编 程 语 言 有 兴 趣 , 翻 译 了 这 本 书 。<br />

以 前 学 习 过 <strong>Visual</strong> <strong>Prolog</strong> V.4, 后 来 发 现 第 六 版 本 后 的 <strong>Visual</strong> <strong>Prolog</strong> 成 了 面 向 对 像 的 语 言<br />

了 , 而 这 本 书 中 有 相 关 的 内 容 介 绍 , 于 是 开 始 读 , 为 学 着 方 便 , 又 开 始 译 , 先 译 了 第 8 章 的<br />

内 容 , 接 着 索 性 译 下 去 , 就 有 了 这 个 译 本 。<br />

我 自 己 的 感 觉 , 这 本 书 确 实 编 排 繁 简 得 当 , 讲 解 通 俗 易 懂 , 对 初 学 者 会 有 很 好 的 帮 助 。<br />

译 完 校 对 时 , 有 些 问 题 请 教 了 原 书 作 者 Thomas W de Boer 先 生 , 得 到 了 他 的 很 多 帮 助 。<br />

Thomas W de Boer 先 生 和 <strong>PDC</strong> <strong>Visual</strong> <strong>Prolog</strong> 官 方 网 站 的 Elizabeth Safro 女 士 建 议 我 在 该 网 站<br />

上 发 表 这 个 译 本 , 我 觉 得 这 可 能 会 对 有 兴 趣 学 习 <strong>Visual</strong> <strong>Prolog</strong> 的 使 用 汉 语 的 朋 友 有 些 帮 助 ,<br />

就 把 这 个 译 本 提 交 给 了 他 们 。<br />

我 的 英 语 水 平 不 高 , 译 得 不 对 不 妥 的 地 方 一 定 不 少 。 好 在 原 书 也 很 容 易 得 到 , 有 不 明 白<br />

的 地 方 可 以 对 照 一 下 原 文 。 当 然 , 译 文 中 的 错 误 都 是 我 的 问 题 , 非 常 欢 迎 指 出 来 , 便 于 以 后<br />

修 改 。 我 的 邮 箱 :its2u@qq.com。<br />

译 文 是 按 原 书 0.95 版 译 的 , 与 现 在 的 1.0 版 可 能 有 些 不 同 , 我 将 尽 快 按 新 版 修 改 。<br />

借 此 机 会 , 衷 心 感 谢 Thomas W de Boer 先 生 和 Elizabeth Safro 女 士 的 热 心 帮 助 !<br />

乙 丁<br />

2009 年 2 月<br />

III


前 言<br />

有 一 种 奇 妙 的 编 程 语 言 , 叫 <strong>Prolog</strong>。<br />

最 初 的 <strong>Prolog</strong> 语 言 是 由 Calmeraur 创 建 的 , 又 由 于 Clocksin & Mellish 和 Ivan Bratko 等 人<br />

的 著 名 的 书 流 行 开 来 , 并 且 由 软 件 厂 商 Borland 提 供 给 了 大 众 , 它 销 售 MsDOS 系 统 下 的 Turbo<br />

<strong>Prolog</strong>。 不 过 没 有 多 少 人 购 买 ,Borland 停 止 了 Turbo <strong>Prolog</strong> 的 销 售 。<br />

那 都 是 很 早 以 前 的 事 了 。 一 些 年 后 ,Turbo <strong>Prolog</strong> 的 制 作 者 回 到 丹 麦 建 立 了 <strong>PDC</strong> 公 司 :<br />

<strong>Prolog</strong> 开 发 公 司 。 经 过 多 年 努 力 , 如 今 有 了 <strong>Visual</strong> <strong>Prolog</strong>。 它 是 面 向 对 象 的 , 有 图 形 用 户 界<br />

面 , 有 集 成 开 发 环 境 。<br />

而 且 , 它 仍 有 着 <strong>Prolog</strong> 语 言 的 魔 力 与 优 雅 。<br />

以 前 学 习 <strong>Prolog</strong> 的 最 大 问 题 , 是 你 必 须 掌 握 陈 述 式 编 程 语 言 独 特 的 思 维 方 式 。 而 一 旦<br />

掌 握 了 它 , 就 掌 握 了 一 种 很 好 的 计 算 机 编 程 方 法 , 这 种 努 力 是 值 得 的 。<br />

而 如 今 , 就 不 仅 是 要 掌 握 一 种 陈 述 式 语 言 , 现 代 编 程 语 言 的 特 点 尽 在 其 中 : 面 向 对 象 、<br />

图 形 用 户 界 面 , 等 等 。 这 本 书 就 是 要 告 诉 你 这 一 切 。<br />

本 书 是 一 本 介 绍 读 物 , 读 者 应 有 些 编 程 知 识 , 而 不 适 用 于 完 全 没 有 这 方 面 基 础 的 人 。 读<br />

者 应 该 有 些 计 算 机 基 础 知 识 , 知 道 计 算 机 是 可 编 程 的 , 知 道 编 写 程 序 要 用 某 种 编 程 语 言 。 现<br />

在 这 些 东 西 差 不 多 是 尽 人 皆 知 的 了 。 对 于 已 经 入 门 的 读 者 , 建 议 看 看 这 些 内 容 :<br />

• 如 果 你 是 程 序 员 , 知 道 些 如 <strong>Visual</strong> Basic 或 C++ 之 类 的 语 言 , 请 看 看 Eduardo Costa<br />

写 的 《<strong>Prolog</strong> for Tyros》( 初 学 者 的 <strong>Prolog</strong>)。<br />

• 如 果 你 了 解 其 它 语 言 又 对 <strong>Prolog</strong> 感 兴 趣 , 可 以 到 <strong>Visual</strong> <strong>Prolog</strong> 的 网 站 www.pdc.dk 看 看<br />

Thomas Linder Puls 和 Sabu Francis 写 的 专 题 文 章 。<br />

• 如 果 你 编 写 过 <strong>Prolog</strong> 程 序 , 可 以 在 上 述 网 站 上 找 到 <strong>Visual</strong> <strong>Prolog</strong> 的 详 细 内 容 及 面 向<br />

对 象 编 程 的 高 级 指 南 。<br />

在 www.pdc.dk ‐> Solutions ‐> <strong>Visual</strong> <strong>Prolog</strong> ‐> Tutorials 中 , 可 以 找 到 各 类 相 关 出 版 物 。 在<br />

这 个 网 站 里 , 还 有 关 于 用 <strong>Visual</strong> <strong>Prolog</strong> 编 程 的 Wiki 网 页 。<br />

关 于 <strong>Visual</strong> <strong>Prolog</strong> 有 很 多 读 物 , 但 对 初 学 者 来 说 都 很 难 做 入 门 教 材 。 本 书 想 为 入 门 者 在<br />

一 团 乱 麻 中 整 出 些 头 绪 , 借 用 别 的 作 者 ( 经 过 他 们 的 同 意 ) 所 写 的 内 容 , 重 新 编 排 或 改 写 ,<br />

也 填 充 了 些 笔 者 以 前 所 写 的 内 容 。 所 有 内 容 集 中 到 这 里 , 尽 可 能 通 俗 易 懂 。<br />

如 果 有 什 么 不 好 理 解 的 , 请 与 笔 者 联 系 。t.w.de.boer@rug.nl<br />

托 马 斯 ·W· 德 · 玻 尔<br />

2008 年 秋 于 格 劳 宁 根<br />

IV


目 录<br />

简 介 ...................................................................................................................................................1<br />

第 1 章 集 成 开 发 环 境 IDE .............................................................................................................2<br />

1.1 集 成 开 发 环 境 IDE .....................................................................................................2<br />

1.2 在 VIP 中 创 建 一 个 工 程 .............................................................................................1<br />

第 2 章 表 格 ...................................................................................................................................5<br />

2.1 创 建 表 格 ...................................................................................................................5<br />

2.2 使 能 任 务 菜 单 选 项 ...................................................................................................9<br />

2.3 在 CodeExpert 中 添 加 代 码 ......................................................................................10<br />

2.4 这 背 后 是 怎 么 回 事 ?.............................................................................................12<br />

2.5 鼠 标 事 件 .................................................................................................................13<br />

第 3 章 简 单 的 用 户 界 面 .............................................................................................................15<br />

3.1 过 程 .........................................................................................................................15<br />

3.2 写 消 息 .....................................................................................................................16<br />

3.3 获 取 用 户 响 应 .........................................................................................................21<br />

第 4 章 细 观 IDE ...........................................................................................................................24<br />

4.1 IDE 概 述 ...................................................................................................................24<br />

4.2 工 程 树 中 的 任 务 窗 口 .............................................................................................26<br />

4.3 在 工 程 树 中 创 建 一 个 新 条 目 .................................................................................28<br />

4.4 CodeExpert 和 Dialog and WindowExpert................................................................30<br />

4.5 访 问 事 件 相 应 的 代 码 .............................................................................................32<br />

第 5 章 <strong>Prolog</strong> 基 础 ......................................................................................................................35<br />

5.1 Horn 子 句 逻 辑 .........................................................................................................35<br />

5.2 PIE:<strong>Prolog</strong> 的 推 理 机 .............................................................................................38<br />

5.3 扩 展 家 庭 知 识 系 统 .................................................................................................40<br />

5.4 <strong>Prolog</strong> 是 一 种 编 程 语 言 ..........................................................................................41<br />

5.5 程 序 控 制 .................................................................................................................42<br />

5.5.1 寻 找 匹 配 .............................................................................................................42<br />

5.5.2 完 成 目 标 .............................................................................................................42<br />

5.5.3 失 败 .....................................................................................................................43<br />

5.5.4 回 溯 .....................................................................................................................43<br />

5.5.5 防 止 回 溯 :cut( 截 断 )....................................................................................46<br />

5.6 递 归 .........................................................................................................................48<br />

5.7 辅 助 功 能 (side effect 副 作 用 ) ............................................................................49<br />

5.8 小 结 .........................................................................................................................50<br />

第 6 章 <strong>Prolog</strong> 的 数 据 模 型 ..........................................................................................................51<br />

6.1 Domains( 域 ) ......................................................................................................51<br />

6.2 改 进 家 庭 知 识 系 统 .................................................................................................51<br />

6.3 复 合 域 和 函 子 .........................................................................................................52<br />

6.4 使 用 函 子 .................................................................................................................54<br />

6.5 函 子 与 谓 词 .............................................................................................................55<br />

6.6 做 参 数 的 函 子 .........................................................................................................56<br />

6.7 递 归 中 使 用 函 子 .....................................................................................................57<br />

6.8 使 用 函 子 的 策 略 .....................................................................................................57<br />

6.9 小 结 .........................................................................................................................58<br />

第 7 章 使 用 表 格 或 对 话 框 及 控 件 : 一 个 小 数 据 库 ...............................................................59<br />

7.1 小 数 据 库 .................................................................................................................59<br />

7.2 VIP 中 的 数 据 库 .......................................................................................................60<br />

7.3 操 控 数 据 : 增 加 一 个 记 录 .....................................................................................65<br />

V


7.4 操 控 数 据 : 删 除 一 个 记 录 .....................................................................................68<br />

7.5 操 控 数 据 : 更 改 记 录 的 内 容 .................................................................................73<br />

7.6 保 存 和 恢 复 数 据 库 .................................................................................................76<br />

7.7 小 结 .........................................................................................................................77<br />

第 8 章 面 向 对 象 的 编 程 – 类 与 对 象 .......................................................................................79<br />

8.1 面 向 对 象 在 现 实 中 的 例 子 .....................................................................................79<br />

8.2 进 一 步 了 解 类 .........................................................................................................80<br />

8.3 <strong>Visual</strong> <strong>Prolog</strong> 中 的 类 与 对 象 ....................................................................................80<br />

8.4 类 与 对 象 是 不 同 的 .................................................................................................85<br />

8.5 类 和 模 块 .................................................................................................................87<br />

8.6 跟 踪 对 象 : 一 个 简 单 的 面 向 对 象 的 数 据 库 .........................................................89<br />

第 9 章 <strong>Visual</strong> <strong>Prolog</strong> 中 的 声 明 ...................................................................................................95<br />

9.1 声 明 和 编 译 .............................................................................................................95<br />

9.2 基 本 概 念 和 关 键 字 概 述 .........................................................................................96<br />

9.3 段 的 关 键 字 概 述 .....................................................................................................98<br />

9.4 域 段 .........................................................................................................................99<br />

9.5 常 数 段 ...................................................................................................................103<br />

9.6 事 实 段 ...................................................................................................................103<br />

9.7 谓 词 段 ...................................................................................................................105<br />

9.8 子 句 段 ...................................................................................................................107<br />

9.9 目 标 段 ...................................................................................................................108<br />

9.10 open 段 和 访 问 范 围 ..............................................................................................109<br />

9.11 类 谓 词 、 类 事 实 以 及 在 哪 儿 声 明 它 们 ...............................................................110<br />

第 10 章 递 归 、 表 和 排 序 .........................................................................................................112<br />

10.1 递 归 .......................................................................................................................112<br />

10.2 表 ...........................................................................................................................117<br />

10.3 表 和 递 归 ...............................................................................................................119<br />

10.4 特 殊 的 表 谓 词 .......................................................................................................126<br />

10.5 排 序 .......................................................................................................................130<br />

10.6 小 结 .......................................................................................................................135<br />

第 11 章 读 、 写 、 流 和 文 件 .....................................................................................................136<br />

11.1 控 制 台 ...................................................................................................................136<br />

11.2 VIP 中 的 消 息 窗 口 和 错 误 窗 口 ..............................................................................137<br />

11.3 流 ...........................................................................................................................138<br />

11.4 标 准 输 入 输 出 :stdIO 类 ......................................................................................139<br />

11.5 谓 词 stdIO::read.....................................................................................................145<br />

11.6 谓 词 writef() 和 format string( 格 式 串 )..............................................................147<br />

11.7 通 用 输 入 输 出 :Stream 类 ...................................................................................148<br />

11.8 文 件 和 目 录 ...........................................................................................................155<br />

第 12 章 数 据 结 构 : 堆 栈 、 队 列 和 树 .....................................................................................156<br />

12.1 数 据 结 构 ...............................................................................................................156<br />

12.2 再 说 表 ...................................................................................................................156<br />

12.3 堆 栈 ....................................................................................................................... 157<br />

12.4 队 列 .......................................................................................................................160<br />

12.5 树 ...........................................................................................................................163<br />

12.6 作 为 数 据 结 构 的 树 ...............................................................................................165<br />

12.7 在 树 中 穿 行 ...........................................................................................................166<br />

12.8 创 建 树 ...................................................................................................................168<br />

12.9 二 进 制 树 ...............................................................................................................169<br />

12.10 遍 历 树 的 其 它 方 法 ...............................................................................................171<br />

12.11 遍 历 树 的 程 序 .......................................................................................................172<br />

VI


附 录 1 对 话 框 与 表 格 的 详 细 说 明 ............................................................................................175<br />

A1.1 创 建 对 话 框 或 表 格 ...................................................................................................175<br />

A1.2 编 辑 对 话 框 ...............................................................................................................177<br />

A1.3 控 件 属 性 表 ...............................................................................................................184<br />

A1.3.1 大 多 数 GUI 控 件 的 共 同 属 性 ..........................................................................185<br />

A1.3.2 不 同 GUI 控 件 类 型 的 特 殊 属 性 ......................................................................187<br />

附 录 2 表 操 作 谓 词 ....................................................................................................................194<br />

VII


简 介<br />

这 是 一 本 有 关 如 何 用 <strong>Visual</strong> <strong>Prolog</strong> 编 写 计 算 机 程 序 的 书 。<strong>Visual</strong> <strong>Prolog</strong>( 缩 写 为 VIP) 是<br />

一 种 面 向 对 象 的 编 程 语 言 (OOPL), 编 写 出 的 程 序 可 以 在 MsWindows 环 境 下 运 行 , 也 就 是<br />

说 它 支 持 图 形 用 户 界 面 (GUI)。<br />

写 这 本 书 上 来 就 碰 到 个 有 趣 的 教 学 问 题 : 你 要 同 时 学 习 三 个 专 题 ( 用 VIP 编 程 , 用 GUI<br />

编 程 和 用 OOPL 编 程 )。 但 好 习 惯 是 一 次 只 写 一 件 事 , 所 以 我 用 了 逐 渐 增 加 的 办 法 来 解 决 问<br />

题 , 每 次 介 绍 一 个 专 题 中 的 一 点 点 内 容 , 以 能 理 解 基 本 内 容 为 度 。 然 后 再 转 向 另 一 个 专 题 ,<br />

介 绍 足 以 理 解 下 面 章 节 基 本 内 容 所 需 要 的 东 西 。<br />

听 起 来 有 些 含 糊 是 吧 ? 再 细 些 说 我 打 算 怎 么 做 :<br />

1. 先 介 绍 打 开 <strong>Visual</strong> <strong>Prolog</strong> 时 所 看 到 的 界 面 , 这 是 集 成 开 发 环 境 , 缩 写 为 IDE。 这 是<br />

用 户 在 <strong>Visual</strong> <strong>Prolog</strong> 中 编 写 程 序 的 地 方 。 头 几 章 让 你 掌 握 IDE 的 基 础 。 需 要 你 做 很<br />

多 事 情 , 感 觉 会 像 哈 里 波 特 , 写 入 一 些 魔 法 公 式 , 一 下 子 它 呈 现 出 来 , 好 像 是 写 了<br />

一 个 能 干 活 儿 的 程 序 一 样 。<br />

2. 接 下 来 , 帮 助 你 扩 展 用 户 图 形 界 面 (GUI) 的 知 识 , 这 在 MsWindows 下 编 程 是 非 常<br />

重 要 的 。 当 然 , 在 用 GUI 编 程 时 又 不 可 避 免 地 需 要 一 些 <strong>Prolog</strong> 的 知 识 , 所 以 也 还<br />

要 对 <strong>Prolog</strong> 编 程 做 一 些 基 本 的 说 明 。<br />

3. 然 后 我 们 会 回 到 作 为 OOPL 的 <strong>Visual</strong> <strong>Prolog</strong> 上 来 。 下 面 的 章 节 要 介 绍 OO 编 程 的 基<br />

础 以 及 在 <strong>Visual</strong> <strong>Prolog</strong> 中 是 如 何 实 现 的 。 在 这 里 可 以 看 到 类 声 明 、 类 界 面 、 类 实 现<br />

等 内 容 。<br />

4. 不 同 于 <strong>Prolog</strong> 方 言 ,<strong>Visual</strong> <strong>Prolog</strong> 是 强 类 型 语 言 。 在 第 9 章 中 我 们 要 了 解 一 下 VIP<br />

的 一 些 说 明 。<br />

5. 最 后 几 章 的 专 题 介 绍 对 <strong>Prolog</strong> 是 非 常 重 要 的 , 如 递 归 、 表 等 。 我 们 还 要 了 解 一 些<br />

非 常 著 名 的 数 据 结 构 , 如 堆 栈 、 队 列 和 树 。 最 后 还 有 一 章 专 门 介 绍 如 何 正 确 地 处 理<br />

输 入 和 输 出 。<br />

这 时 , 你 就 可 以 自 己 来 探 索 <strong>Prolog</strong> 和 VIP 了 。 在 <strong>PDC</strong> 网 站 中 , 有 几 本 很 好 的 书 , 还 有<br />

各 种 专 题 的 不 少 文 章 。 此 外 , 本 书 是 非 常 初 级 的 读 物 , 而 你 那 时 已 经 不 再 是 初 学 者 , 可 以 合<br />

上 这 本 书 啦 !<br />

但 愿 在 使 用 这 本 书 时 能 有 很 多 乐 趣 。 如 果 遇 到 什 么 麻 烦 , 请 与 我 联 系 , 我 愿 尽 力 改 进 这<br />

本 书 。 联 系 时 请 使 用 t.w.de.boer@rug.nl。 请 在 主 题 栏 中 标 注 <strong>Visual</strong> <strong>Prolog</strong> 字 样 。<br />

致 谢<br />

我 要 感 谢 Eduardo Costa,Sabu Francis 和 Thomas Linder Puls, 他 们 慷 慨 地 允 许 我 自 由 地<br />

使 用 他 们 写 作 的 课 本 , 我 大 量 地 使 用 了 ( 而 且 可 能 还 滥 用 了 ) 他 们 的 著 作 。 我 试 图 尽 可 能 准<br />

确 , 并 在 各 章 中 指 出 哪 些 是 原 样 引 用 他 们 的 著 作 , 如 果 有 什 么 疏 忽 , 请 通 知 我 。 不 言 而 喻 ,<br />

所 有 错 误 完 全 是 我 的 责 任 。<br />

1


第 1 章 集 成 开 发 环 境 IDE 1<br />

本 章 要 介 绍 <strong>Visual</strong> <strong>Prolog</strong> 的 集 成 开 发 环 境 。 现 在 的 编 程 已 不 再 仅 是 在 编 辑 器 中 输 入 代 码<br />

了 , 编 程 变 得 非 常 复 杂 。 这 也 正 是 为 什 么 编 程 语 言 的 开 发 者 不 仅 仅 只 提 供 编 程 语 言 , 还 提 供<br />

编 程 工 具 的 原 因 。 集 成 开 发 环 境 (IDE) 就 是 这 样 一 种 工 具 。 当 创 建 一 个 <strong>Visual</strong> <strong>Prolog</strong> 计 算<br />

机 程 序 时 , 就 要 使 用 IDE。 本 章 的 目 的 是 要 使 读 者 熟 悉 VIP 的 IDE。<br />

1.1 集 成 开 发 环 境 IDE<br />

当 启 动 <strong>Visual</strong> <strong>Prolog</strong> 时 , 其 实 打 开 的 是 集 成 开 发 环 境 ,IDE。 如 图 1.1 所 示<br />

2 :<br />

图 1.1<br />

<strong>Visual</strong> <strong>Prolog</strong> 的 集 成 开 发 环 境<br />

IDE 呈 现 出 的 各 部 分 与 一 般 Windows 程 序 一 样 。 本 书 中 说 到 的 IDE 组 成 部 分 以 及 在 <strong>Visual</strong><br />

<strong>Prolog</strong> 中 使 用 的 名 称 , 如 图 1.1 示 。 其 中 :<br />

• 任 务 窗 口 IDE 的 任 务 窗 口 是 启 动 一 个 程 序 时 呈 现 出 的 窗 口 , 也 可 以 叫 它 “ 主 窗<br />

口 ”。 窗 口 上 方 有 菜 单 选 项 “File,Edit,View,…,Windows,Help”。<br />

• 任 务 菜 单 这 个 菜 单 含 有 人 们 非 常 熟 悉 的 “File”,“Edit” 等 Windows 常 用 的 菜<br />

单 选 项 , 它 也 被 称 为 “ 主 菜 单 ”。<br />

• 任 务 菜 单 工 具 条 含 有 最 常 用 的 任 务 菜 单 项 的 图 标 。<br />

• 最 近 工 程 它 列 出 了 IDE 处 理 过 的 工 程 名 称 。 当 首 次 运 行 <strong>Visual</strong> <strong>Prolog</strong> 时 , 不 会<br />

有 最 近 处 理 的 工 程 名 。<br />

• 消 息 窗 口 这 个 窗 口 是 <strong>Visual</strong> <strong>Prolog</strong> 和 IDE 特 有 的 。 这 里 有 IDE 发 出 的 消 息 , 后<br />

面 还 会 看 到 , 你 可 以 经 由 这 里 写 出 一 些 简 短 的 信 息 。<br />

IDE 还 有 很 多 内 容 可 说 , 不 过 已 经 说 的 这 会 儿 够 用 了 , 我 们 来 用 IDE 做 点 儿 什 么 , 先 创<br />

建 个 简 单 程 序 吧 。 由 于 编 程 是 一 项 工 程 , 所 以 “ 工 程 ” 和 “ 程 序 ” 这 两 个 词 会 等 价 地 使 用 。<br />

1<br />

这 一 章 是 Eduardo Costa 所 写 的 《<strong>Prolog</strong> for Tyros》 第 一 章 的 缩 编 。<br />

2<br />

本 书 中 的 图 是 由 <strong>Visual</strong> <strong>Prolog</strong> v7.1 商 业 版 复 制 的 , 不 同 软 件 版 本 的 图 可 能 略 有 差 别 。<br />

2


1.2 在 VIP 中 创 建 一 个 工 程<br />

我 们 先 创 建 一 个 空 的 工 程 , 以 后 再 来 增 加 功 能 。<br />

第 一 步 : 创 建 一 个 新 工 程 。<br />

有 两 种 办 法 创 建 工 程 , 一 是 当 工 程 概 览 ( 最 近 工 程 的 列 表 窗 口 ) 打 开 时 , 可 以 点 击 其 底<br />

部 的 New Project 按 钮 来 创 建 新 工 程 。 二 是 当 工 程 概 览 关 闭 时 在 任 务 菜 单 上 点 击 选 项<br />

Project/New 创 建 新 工 程 , 如 图 1.2 所 示 。<br />

图 1.2<br />

任 务 菜 单 :Project/New<br />

点 击 该 选 项 时 ,IDE 会 打 开 一 个 新 的 窗 口 供 输 入 工 程 设 置 。 由 于 这 样 的 窗 口 是 用 来 与 用<br />

户 对 话 的 , 所 以 也 称 它 们 为 对 话 窗 口 或 对 话 框 。 还 因 为 这 样 的 对 话 框 像 一 个 表 格 , 让 用 户 填<br />

写 各 项 , 所 以 它 们 又 被 称 为 表 。 表 和 对 话 框 是 一 个 意 思 , 它 们 多 少 有 些 相 同 , 后 面 还 会 学 习<br />

到 更 多 表 和 对 话 框 的 内 容 。<br />

我 们 按 图 1.3 填 好 Project Settings 对 话 框 。<br />

图 1.3<br />

工 程 设 置<br />

这 里 只 需 要 填 写 Project Name, 大 多 数 项 IDE 已 经 填 写 好 了 , 我 们 来 看 看 这 些 内 容 :<br />

• 第 一 项 是 , 工 程 名 称 , 我 们 这 里 填 的 是 ch01p01。 你 也 可 以 选 其 它<br />

的 名 称 , 不 过 建 议 还 是 先 用 这 个 名 称 。<br />

• 下 一 项 是 UI Strategy, 它 表 明 打 算 使 用 的 用 户 界 面 类 型 。IDE 建 议 使 用 面 向 对 象 的<br />

图 形 用 户 界 面 , 也 就 是 标 准 的 窗 口 界 面 。 就 这 样 。<br />

• Target Type 是 目 标 类 型 , 指 IDE 要 产 生 的 程 序 类 型 。Exe 就 好 , 它 表 示 IDE 应 产 生<br />

标 准 的 独 立 运 行 的 Windows 程 序 。 另 一 个 选 项 是 DLL, 如 果 不 知 道 它 是 什 么 , 暂 且<br />

不 去 管 它 好 了 。<br />

• Base Directory 是 基 本 目 录 , 是 程 序 归 属 的 文 件 夹 , 但 它 未 必 就 是 程 序 ch01p01 所<br />

在 的 文 件 夹 , 指 定 它 是 下 一 项 的 内 容 。 要 创 建 的 程 序 , 它 所 有 的 文 件 夹 都 在 这 个 基<br />

1


本 目 录 中 。<br />

• Sub‐Directory 是 子 目 录 , 它 是 要 存 储 程 序 ch01p01 的 文 件 夹 。 如 果 需 要 , 这 个 子 目<br />

录 就 会 在 Base Directory 中 创 建 。 当 输 入 工 程 名 时 ,IDE 就 将 这 个 子 目 录 名 取 与 工 程<br />

名 一 样 的 名 字 , 如 果 愿 意 也 可 以 改 变 它 。 现 在 建 议 还 是 让 它 们 一 样 好 了 。 还 有 , 我<br />

们 这 里 “ 目 录 ” 与 “ 文 件 夹 ” 混 用 着 , 它 们 是 一 个 意 思 。<br />

点 击 , 等 标 签 还 可 以 看 到 更 多 的 工 程 设 置 , 不 过 目 前 我 们 只<br />

需 要 使 用 标 签 内 的 设 置 。 设 置 好 了 之 后 , 点 击 , 就 会 出 现 工 程 树 。 所 谓 “ 工<br />

程 树 ”, 是 当 前 编 程 工 程 文 件 的 一 个 总 览 , 样 子 与 Windows 的 资 源 管 理 器 相 似 , 实 际 上 它 显<br />

示 的 就 是 硬 盘 目 录 结 构 的 一 部 分 。<br />

图 1.4<br />

工 程 树<br />

工 程 树 也 叫 工 程 目 录 , 它 显 示 了 IDE 维 护 的 当 前 工 程 中 的 文 件 夹 与 模 块 。 当 新 的 工 程<br />

ch01p01 创 建 时 , 在 硬 盘 中 也 创 建 了 一 个 与 工 程 树 相 像 的 目 录 。 这 里 的 工 程 树 根 名 称 为<br />

ch01p01, 也 就 是 在 工 程 设 置 中 输 入 的 工 程 名 。 它 不 是 工 程 设 置 中 输 入 的 子 目 录 的 名 字 , 实<br />

际 上 这 里 看 不 到 程 序 在 硬 盘 上 存 储 位 置 的 目 录 名 。IDE 只 对 付 工 程 名 , 这 里 是 ch01p01。 工<br />

程 树 的 根 目 录 下 有 两 个 文 件 夹 ,$(ProDir)\Lib 和 TaskWindow, 它 们 是 由 IDE 创 建 的 , 现 在 我<br />

们 先 不 去 管 它 , 以 后 再 来 介 绍 它 们 。 工 程 目 录 中 还 有 一 个 名 为 main.pack 的 文 件 , 它 包 含 了<br />

工 程 的 基 本 信 息 。<br />

要 知 道 现 在 还 没 有 程 序 , 要 创 建 该 工 程 的 程 序 , 就 要 应 用 IDE 的 生 成 功 能 。 根 据 对 工 程<br />

所 作 的 声 明 ,IDE 会 产 生 必 要 的 代 码 。 尽 管 前 面 我 们 只 声 明 了 工 程 的 名 字 和 它 应 存 放 的 目 录 ,<br />

但 IDE 明 白 这 是 一 个 Windows 程 序 , 它 会 生 成 一 个 带 有 主 窗 口 ( 也 就 是 任 务 窗 口 ) 及 File、<br />

Edit 等 菜 单 项 的 windows 程 序 代 码 。<br />

现 在 可 以 进 行 第 二 步 : 构 造 和 编 译 可 执 行 程 序 了 。<br />

要 生 成 <strong>Prolog</strong> 代 码 , 在 任 务 菜 单 中 选 Build/build 项 , 如 图 1.5 示 。 点 击 该 选 项 后 ,IDE<br />

生 成 相 应 的 <strong>Prolog</strong> 代 码 , 同 时 还 会 在 消 息 窗 口 中 发 出 消 息 。<br />

图 1.5<br />

构 造 工 程<br />

2


构 造 工 程 的 过 程 要 几 秒 钟 的 时 间 。 看 看 消 息 窗 口 中 的 内 容 可 以 了 解 创 建 一 个 简 单 的<br />

Windows 程 序 需 要 多 少 代 码 。IDE 生 成 代 码 , 编 译 并 链 接 程 序 的 各 部 分 , 都 做 完 了 之 后 , 就<br />

会 显 示 出 消 息 “Project has been build”( 工 程 构 建 完 成 )。<br />

构 建 完 了 工 程 ( 也 就 是 让 IDE 生 成 了 源 代 码 ), 再 看 工 程 树 , 它 “ 长 大 ” 了 。 构 建 工 程<br />

意 味 着 生 成 存 放 在 几 个 文 件 中 的 代 码 。 参 见 图 1.6,IDE 增 加 的 文 件 中 有 一 个 文 件 是<br />

ch01p01.pro 它 里 面 有 程 序 的 <strong>Prolog</strong> 核 心 代 码 。 还 有 一 些 文 件 也 有 <strong>Prolog</strong> 代 码 。 要 看 看 里 面<br />

有 什 么 , 只 要 双 击 相 应 的 名 字 就 可 以 , 不 过 千 万 别 更 改 这 些 代 码 。<br />

图 1.6<br />

工 程 构 建 后 的 工 程 树<br />

构 建 完 成 后 , 可 以 运 行 这 个 程 序 了 。 在 任 务 菜 单 中 选 Build/Execute, 就 出 现 了 如 图 1.7<br />

示 的 窗 口 。<br />

图 1.7<br />

执 行 ch01p01 程 序<br />

看 ,IDE 产 生 了 标 准 的 带 有 通 用 菜 单 选 项 File、Edit、Window 和 Help 的 窗 口 程 序 。 点 击<br />

这 些 选 项 不 会 有 什 么 作 用 , 因 为 还 没 有 指 定 它 们 要 做 什 么 。 这 里 只 有 很 少 的 选 项 真 起 作 用 :<br />

右 上 角 的 最 大 化 / 最 小 化 和 菜 单 选 项 File/Exit, 因 为 它 们 在 所 有 Windows 程 序 中 作 用 都 是 相<br />

同 的 , 对 IDE 来 说 不 难 产 生 这 些 代 码 。 最 后 要 提 一 下 的 是 消 息 窗 口 , 这 是 <strong>Visual</strong> <strong>Prolog</strong> 特 有<br />

的 , 不 过 现 在 先 不 管 它 。 还 有 , 程 序 ch01p01 与 IDE 是 独 立 的 , 关 闭 ch01p01 时 ,IDE 还 是<br />

打 开 的 。<br />

庆 祝 一 下 ! 你 搞 定 了 你 的 第 一 个 <strong>Visual</strong> <strong>Prolog</strong> 7 程 序 。 或 是 准 确 地 说 , 你 做 了 些 安 排 ,<br />

<strong>Visual</strong> <strong>Prolog</strong> 的 IDE 生 成 了 程 序 的 代 码 以 便 可 以 执 行 。 这 就 是 要 学 习 的 使 用 IDE 的 方 法 : 你<br />

说 明 你 想 要 什 么 , 然 后 生 成 代 码 , 有 时 再 对 生 成 的 代 码 做 些 修 补 。 这 就 是 用 先 进 的 工 具 如<br />

3


<strong>Visual</strong> <strong>Prolog</strong> 和 它 的 集 成 开 发 环 境 创 建 计 算 机 程 序 的 方 法 。 这 样 的 编 程 方 法 看 起 来 很 费 劲 儿 ,<br />

这 是 因 为 面 向 对 象 编 程 语 言 环 境 使 用 图 形 用 户 界 面 是 相 当 复 杂 的 , 要 打 理 好 每 个 细 节 是 很 困<br />

难 的 。 多 亏 IDE 为 我 们 做 了 很 多 书 记 工 作 , 才 使 得 事 情 比 较 容 易 了 。<br />

现 在 用 Windows 的 资 源 管 理 器 来 看 看 硬 盘 上 的 文 件 吧 。 当 查 看 ch01p01 的 目 录 时 , 会<br />

发 现 IDE 创 建 的 内 容 不 光 是 工 程 树 中 看 到 的 , 还 有 很 多 其 它 的 。 注 意 Exe 和 Bin 这 两 个 文 件<br />

夹 , 在 前 者 中 可 以 发 现 可 执 行 文 件 ( 可 独 立 于 VIP 启 动 的 文 件 ), 而 在 后 者 中 可 以 发 现 IDE<br />

生 成 程 序 时 所 需 要 的 其 它 的 文 件 。 通 过 这 些 可 以 大 概 了 解 创 建 一 个 Windows 程 序 所 做 的 工<br />

作 内 容 。 还 可 以 看 到 , 我 们 的 程 序 工 程 是 由 几 个 文 件 构 成 的 , 像 <strong>Visual</strong> <strong>Prolog</strong> 这 样 的 面 向 对<br />

象 的 语 言 通 常 都 是 这 样 的 。<br />

4


第 2 章 表 格<br />

3<br />

前 一 章 中 创 建 的 是 个 简 单 程 序 , 可 以 启 动 , 最 大 化 或 最 小 化 任 务 窗 口 及 退 出 , 就 这 些 。<br />

实 际 的 程 序 起 码 应 能 与 用 户 进 行 些 通 信 。 在 Windows 环 境 下 的 通 信 , 有 些 是 通 过 特 殊 窗 口<br />

进 行 的 。 通 信 窗 口 有 好 几 类 , 其 中 最 常 用 的 被 称 为 表 格 和 对 话 框 。 由 于 VIP 中 的 表 格 和 对 话<br />

框 非 常 相 似 , 这 里 就 不 再 区 分 它 们 。 本 章 中 , 我 们 要 在 前 一 章 创 建 的 空 的 工 程 中 加 一 个 表 格 。<br />

在 任 务 菜 单 中 选 Project/Open 打 开 工 程 ch01p01, 或 是 启 动 <strong>Visual</strong> <strong>Prolog</strong> 时 在 最 近 工 程 列 表<br />

中 选 择 ch01p01 打 开 它 。<br />

2.1 创 建 表 格<br />

一 个 工 程 可 能 很 复 杂 , 会 由 很 多 文 件 构 成 。 因 此 , 要 事 先 考 虑 好 在 哪 些 工 程 树 文 件 夹 中<br />

存 放 程 序 各 个 部 分 或 模 块 。 现 在 的 例 子 中 还 不 用 有 太 多 顾 虑 , 可 以 放 在 以 后 再 考 虑 , 我 们 就<br />

把 新 表 格 放 在 工 程 的 根 目 录 下 。 在 工 程 树 中 点 击 ch01p01 使 其 高 亮 显 示 , 这 时 就 可 以 创 建 放<br />

在 根 目 录 下 的 表 格 了 , 在 任 务 菜 单 中 选 择 File/New in Existing Packag, 如 图 2.1 示 。<br />

图 2.1<br />

在 已 有 包 中 创 建 新 文 件<br />

IDE 打 开 一 个 窗 口 , 提 供 了 多 种 创 建 工 程 的 选 项 , 如 图 2.2。 该 窗 口 的 名 称 是 “Create<br />

Project Item”( 创 建 工 程 项 , 这 正 是 我 们 要 做 的 ), 它 是 一 个 分 为 两 部 分 的 表 格 。 左 半 部 分 显<br />

示 了 可 以 创 建 的 项 , 可 以 创 建 包 (package)、 类 (class)、 对 话 框 (dialog)、 表 格 (form),<br />

等 等 , 在 这 里 选 择 想 要 创 建 的 项 。<br />

右 边 提 供 了 创 建 项 的 一 些 可 选 项 。 要 创 建 一 个 表 格 , 在 左 边 选 择 Form 。 在 右 边 有 IDE<br />

需 要 知 道 的 这 个 表 格 的 选 项 。 如 图 2.2 名 为 “Create Project Item” 这 样 的 窗 口 叫 做 对 话 框 ,<br />

现 在 这 个 就 是 Create Project Item 对 话 框 ,IDE 通 过 它 来 了 解 要 做 什 么 以 及 把 它 放 在 哪 儿 。<br />

这 个 新 表 格 我 们 给 它 起 名 为 “form1”。 这 可 能 是 个 很 不 好 的 名 字 , 因 为 它 对 这 个 表 格 什<br />

么 也 没 有 说 明 , 通 常 应 该 起 个 有 意 义 的 名 字 , 不 过 这 里 也 够 用 了 。 按 图 2.2 那 样 输 入 名 字 。<br />

一 定 要 在 工 程 树 (Project Tree) 中 高 亮 ch01p01 以 便 将 表 格 放 在 工 程 根 目 录 下 。 需 要 的 话 ,<br />

点 一 下 Existing package, 接 受 IDE 缺 省 给 出 的 main.pack 名 。 做 完 这 些 , 对 话 框 与 图 2.2 一<br />

样 了 , 点 击 让 IDE 创 建 新 表 格 。<br />

3<br />

本 章 是 Eduardo Costar 的 《<strong>Prolog</strong> for Tyros》 第 二 章 的 简 写 版 。<br />

5


图 2.2<br />

创 建 工 程 项 – 表 格<br />

这 时 IDE 会 打 开 一 道 工 作 的 四 个 小 窗 口 来 支 持 新 表 格 的 创 建 。 这 些 窗 口 名 为 form1( 我 们<br />

给 新 表 格 起 的 名 字 )、Layout( 布 局 )、Controls( 控 件 ) 和 Properties( 属 性 )。 要 了 解 这 些 ,<br />

需 要 知 道 些 Windows 程 序 背 后 的 东 西 。 一 个 Windows 程 序 主 要 由 窗 口 ( 这 个 不 奇 怪 ) 构 成 ,<br />

用 户 可 以 用 它 来 做 点 什 么 。 例 如 , 在 一 个 表 格 中 , 有 可 以 填 写 名 字 的 地 方 , 有 可 以 点 选 的 单<br />

选 按 钮 , 有 下 拉 式 菜 单 , 有 OK 按 钮 等 等 。 这 些 用 户 可 使 用 的 东 西 叫 “ 控 件 ”。 像 我 们 要 创<br />

建 的 这 样 的 表 格 , 可 以 加 上 很 多 控 件 。 可 用 的 控 件 , 就 显 示 在 名 为 Controls 的 窗 口 中 , 可 以<br />

随 意 将 控 件 放 在 表 格 中 。 要 这 样 , 先 得 有 一 个 新 表 格 的 原 型 , 这 就 是 form1 窗 口 。 我 们 就 是<br />

用 这 个 窗 口 来 设 计 和 编 辑 新 表 格 的 , 所 以 也 叫 它 编 辑 窗 口 。 创 建 表 格 时 , 可 以 使 用 Layout<br />

窗 口 的 选 项 改 变 布 局 , 例 如 调 整 一 组 按 钮 的 对 齐 方 式 。 表 格 中 的 每 个 控 件 都 有 一 系 列 的 属 性 ,<br />

比 如 按 键 在 表 格 中 的 位 置 、 大 小 等 等 。 控 件 的 属 性 显 示 在 Properties 窗 口 中 。 试 试 看 , 点 击<br />

编 辑 窗 口 中 的 按 钮 , 看 一 下 Properties 窗 口 中 内 容 的 变 化 。 点 击 编 辑 窗 口 中 任 意 一 点 ( 但<br />

不 能 是 按 钮 上 ) 就 可 以 看 到 新 窗 口 的 一 般 属 性 。 拉 动 编 辑 窗 口 边 沿 以 改 变 窗 口 , 会 看 到<br />

Properties 窗 口 中 dimensions 在 变 化 。 这 四 个 窗 口 是 一 道 工 作 的 , 不 过 可 以 任 意 排 放 它 们 在<br />

屏 幕 中 的 位 置 。<br />

我 们 来 仔 细 看 一 下 这 四 个 窗 口 。<br />

标 题 为 “Controls” 的 窗 口 ( 如 图 2.3) 其 实 是 个 工 具 条 , 它 显 示 了 可 以 在 表 格 中 使 用 的<br />

所 有 控 件 的 图 标 。 在 第 一 行 从 左 到 右 , 有 按 钮 、 复 选 框 、 单 选 钮 、 静 态 文 本 和 编 辑 框 。 把 光<br />

标 放 在 图 标 上 , 就 会 有 相 应 的 说 明 出 现 。 要 将 控 件 加 到 表 格 中 , 先 点 击 Controls 中 的 控 件 图<br />

标 , 再 点 击 表 格 编 辑 器 中 要 放 置 该 控 件 的 位 置 就 可 以 了 。<br />

图 2.3<br />

Controls 工 具 条<br />

6


标 题 为 “Layout” 的 窗 口 也 是 一 个 工 具 条 。 这 些 图 标 表 示 了 调 整 表 格 中 控 件 布 局 的 各 种<br />

方 法 。 第 一 行 中 有 左 对 齐 、 居 中 对 齐 、 右 对 齐 等 。 在 表 格 中 选 取 了 一 组 控 件 后 再 点 击 这 些 图<br />

标 , 控 件 的 位 置 就 会 按 要 求 调 整 。 光 标 移 到 这 些 图 标 上 时 , 也 会 有 说 明 出 现 。 最 下 面 一 行 中<br />

那 些 图 标 , 是 用 来 等 间 隔 各 控 件 和 使 控 件 大 小 相 同 的 。<br />

图 2.4 Layout 工 具 条<br />

“form1” 窗 口 是 表 格 的 编 辑 器 。 它 是 一 个 我 们 要 做 的 表 格 的 原 型 ,IDE 已 经 放 置 了 三 个<br />

控 件 在 里 边 ( 如 图 2.5)。<br />

图 2.5<br />

表 格 编 辑 器<br />

第 四 个 标 题 为 “Properties” 的 窗 口 显 示 出 表 格 中 选 定 控 件 的 属 性 。 如 果 没 有 选 定 控 件 ,<br />

则 显 示 的 是 表 格 的 属 性 。 如 果 没 有 显 示 表 格 的 属 性 , 可 以 在 表 格 中 点 击 任 意 一 点 ( 但 不 能 点<br />

在 按 键 上 ), 表 格 的 属 性 就 会 如 图 2.6 那 样 显 示 出 来 。 属 性 中 表 明 了 要 显 示 在 标 题 行 上 的 标<br />

题 (Title)、 表 格 在 屏 幕 上 显 示 的 坐 标 位 置 X 和 Y、 表 格 的 高 和 宽 等 等 许 多 有 关 这 个 表 格 的<br />

属 性 特 征 。 可 以 通 过 将 值 True 改 为 False 来 改 变 表 格 的 属 性 , 如 显 示 或 不 显 示 标 题 行 或 关 闭<br />

按 钮 等 等 。 随 便 试 试 控 件 和 属 性 , 不 会 有 多 大 事 儿 。 要 注 意 ,Title 框 中 的 名 字 是 要 显 示 在<br />

表 格 标 题 行 上 的 内 容 , 它 不 是 表 格 的 名 字 , 表 格 的 名 字 是 form1。 可 以 试 试 改 变 表 格 的 title。<br />

IDE 已 经 加 入 了 三 个 按 钮 (OK、Cancel 和 Help), 因 为 在 Windows 的 窗 口 中 这 些 都 是 非<br />

常 通 用 的 。 要 改 变 表 格 的 大 小 , 可 以 像 改 变 任 何 Windows 的 窗 口 一 样 拖 动 窗 口 的 右 下 角 。<br />

我 们 之 所 以 把 它 称 为 原 型 , 是 因 为 它 有 了 表 格 的 一 般 属 性 。 在 上 部 有 名 字 , 有 工 作 空 间 , 有<br />

三 个 常 用 按 钮 。IDE 就 是 这 样 的 , 只 要 用 IDE 创 建 什 么 东 西 ,IDE 都 会 提 供 一 个 可 以 修 改 的 原<br />

型 。 现 在 这 个 表 格 还 只 含 有 三 个 按 钮 , 通 常 都 会 再 添 加 些 控 件 , 不 过 我 们 暂 时 就 这 样 了 , 点<br />

击 任 务 菜 单 条 上 的 磁 盘 图 标 保 存 这 个 表 格 , 再 点 击 原 型 右 上 角 的 关 闭 按 钮 关 闭 原 型 。 也 可 以<br />

直 接 关 闭 原 型 ,IDE 会 询 问 是 否 要 保 存 , 再 保 存 这 个 表 格 。<br />

7


图 2.6<br />

Properties 窗 口<br />

再 来 看 工 程 树 , 如 下 图 , 又 增 加 了 一 个 名 为 form1.frm 的 文 件 , 它 是 IDE 存 放 刚 才 创 建<br />

的 表 格 属 性 的 地 方 。 双 击 form1.frm, 又 会 打 开 那 个 原 型 。<br />

图 2.7 工 程 树 中 增 加 了 文 件<br />

我 们 只 有 原 型 表 格 , 还 没 有 任 何 <strong>Prolog</strong> 代 码 。 在 文 件 form1.frm 中 , 包 含 了 在 表 格 编 辑<br />

窗 口 中 编 辑 表 格 时 赋 予 它 的 那 些 信 息 。 要 生 成 <strong>Prolog</strong> 的 代 码 , 在 菜 单 Build 中 选 择 Build。 这<br />

可 与 我 们 在 第 1 章 中 所 作 的 做 个 比 较 , 那 里 我 们 规 定 了 程 序 的 一 些 属 性 ( 名 字 和 根 目 录 ),<br />

然 后 让 IDE 产 生 代 码 。 在 这 里 也 一 样 , 我 们 设 计 了 一 个 表 格 , 然 后 让 IDE 来 产 生 <strong>Prolog</strong> 代 码 。<br />

点 击 Build/Build 后 , 可 以 看 到 在 工 程 树 中 又 增 加 了 一 些 文 件 , 有 form1.cl、form1.i、form1.pro。<br />

这 些 文 件 中 包 含 了 必 需 的 <strong>Prolog</strong> 代 码 , 也 是 存 放 我 们 想 要 添 加 代 码 的 地 方 。 要 看 一 下<br />

form1.pro 中 IDE 生 成 了 些 什 么 , 双 击 文 件 名 就 是 了 。 这 时 会 打 开 一 个 编 辑 窗 口 , 展 现 出 表<br />

格 的 <strong>Prolog</strong> 代 码 。 可 以 上 下 滚 动 来 查 看 , 但 请 不 要 做 改 动 。IDE 在 四 个 文 件 中 保 存 了 表 格 form1<br />

8


的 全 部 信 息 。Form1.frm 中 有 表 格 的 设 计 原 型 而 其 它 三 个 含 有 <strong>Prolog</strong> 代 码 。 如 果 想 要 改 变<br />

form1, 就 打 开 设 计 文 件 做 修 改 , 保 存 之 后 再 生 成 相 应 代 码 。 这 个 过 程 会 来 回 地 使 用 。<br />

2.2 使 能 任 务 菜 单 选 项<br />

目 前 为 止 , 我 们 有 一 个 程 序 , 这 个 程 序 中 有 一 个 表 格 , 但 是 它 还 没 成 事 儿 。 现 在 编 译 和<br />

执 行 该 程 序 , 不 会 有 表 格 出 来 , 因 为 我 们 还 没 有 规 定 要 它 什 么 时 候 出 来 。 在 Windows 程 序<br />

中 , 是 由 称 为 “ 事 件 ” 的 东 西 来 引 发 事 情 的 。 事 件 有 很 多 , 我 们 最 熟 悉 的 可 能 要 算 点 击 鼠 标<br />

左 键 了 。 一 点 那 个 键 , 可 能 会 打 开 或 关 闭 窗 口 , 或 是 开 始 菜 单 选 项 等 。 程 序 需 要 知 道 什 么 时<br />

候 让 我 们 创 建 的 表 格 出 现 , 可 以 任 选 一 个 事 件 , 但 现 在 我 们 选 用 在 程 序 菜 单 选 项 File/New<br />

上 点 左 键 这 个 事 件 来 做 例 子 。<br />

图 2.8<br />

任 务 窗 口 的 文 件 夹 内 容<br />

在 第 1 章 中 运 行 空 程 序 时 我 们 已 经 注 意 到 ,File/New 选 项 是 未 使 能 的 , 它 是 灰 色 的 , 也<br />

就 是 说 在 窗 口 中 无 法 使 用 它 。 要 用 它 , 先 要 使 能 它 。 使 能 File/New, 先 打 开 工 程 树 , 这 中 间<br />

有 一 个 名 为 TaskWindow 的 文 件 夹 图 标 , 该 文 件 夹 中 含 有 启 动 程 序 时 创 建 任 务 窗 口 ( 主 窗 口 )<br />

所 需 要 的 所 有 信 息 。 展 开 文 件 夹 , 可 以 看 到 程 序 的 任 务 窗 口 中 的 典 型 部 件 , 如 图 2.8。 这 里<br />

有 一 些 文 件 , 可 以 看 出 一 点 规 律 , 比 如 有 四 个 文 件 名 字 都 是 AboutDialog, 只 不 过 扩 展 名 不<br />

一 样 。 当 运 行 程 序 时 , 点 击 Help/About 选 项 , 会 弹 出 一 个 窗 口 ,AboutDialog.dlg 这 个 文 件 就<br />

包 含 有 对 这 个 窗 口 的 设 计 信 息 。 双 击 AboutDialog.dlg,IDE 会 打 开 对 话 框 编 辑 器 , 它 与 我 们<br />

前 面 创 建 form1 时 的 编 辑 器 很 相 似 。 其 它 文 件 包 含 有 AboutDialog 窗 口 的 <strong>Prolog</strong> 代 码 。<br />

现 在 我 们 来 看 程 序 的 菜 单 条 , 因 为 选 项 File/New 是 在 菜 单 条 中 。 点 击 TaskMenu.mnu,<br />

这 是 个 任 务 菜 单 的 原 型 , 与 form1.frm 是 表 格 原 型 一 样 。 要 想 改 变 主 菜 单 中 的 什 么 , 就 用 这<br />

个 文 件 。<br />

我 们 要 在 菜 单 中 使 能 一 个 选 项 , 所 以 双 击 TaskMenu.mnu,IDE 会 打 开 任 务 菜 单 编 辑 器 ,<br />

如 图 2.9 所 示 。 在 这 里 可 以 使 能 、 禁 止 、 添 加 和 删 除 菜 单 选 项 , 可 以 定 义 快 捷 键 等 。 我 们 要<br />

使 能 File/New, 点 击 展 开 在 表 格 下 半 部 里 的 &File 选 项 , 再 点 &New/tF7。IDE 会 将 其 属 性 显<br />

示 在 表 格 的 上 半 部 中 。 可 以 看 到 当 前 Disabled 是 选 中 的 , 去 掉 选 中 状 态 就 使 能 了 这 个 菜 单 选<br />

项 。 然 后 可 以 保 存 新 的 菜 单 选 项 并 关 闭 任 务 菜 单 对 话 框 。<br />

9


图 2.9<br />

任 务 菜 单 的 编 辑 器<br />

2.3 在 CodeExpert 中 添 加 代 码<br />

菜 单 项 File/New 使 能 了 , 运 行 程 序 时 可 以 看 到 它 不 再 是 灰 色 的 , 不 过 点 击 它 时 还 没 有<br />

什 么 反 应 。 这 是 因 为 我 们 还 没 有 给 这 个 菜 单 项 添 加 代 码 。 现 在 可 以 来 添 加 它 的 代 码 了 。 回 到<br />

工 程 树 , 找 到 文 件 TaskWindow.win。 这 个 文 件 用 于 管 理 和 操 控 任 务 窗 口 中 各 部 件 的 <strong>Prolog</strong><br />

代 码 。 代 码 最 终 是 放 在 文 件 TaskWindow.pro 中 , 可 以 直 接 打 开 它 输 入 代 码 , 不 过 现 在 最 好<br />

不 那 样 做 , 不 直 接 输 入 代 码 。 比 较 好 的 方 法 是 使 用 IDE 中 的 CodeExpert( 代 码 专 家 ) 来 添 加<br />

代 码 , 它 会 知 道 代 码 要 放 在 什 么 地 方 。 当 程 序 大 起 来 时 , 它 能 导 引 我 们 , 所 以 还 是 相 信 它 吧 。<br />

图 2.10 打 开 代 码 专 家 系 统<br />

10


右 键 点 击 工 程 树 中 的 TaskWindow.win, 会 出 现 如 图 2.10 示 的 悬 浮 菜 单 , 选 CodeExpert。<br />

IDE 此 时 会 在 Dialog and Window Expert 中 打 开 文 件 TaskWindow.win, 可 以 看 到 程 序 的 任<br />

务 窗 口 中 各 项 菜 单 选 项 的 列 表 , 在 这 里 可 以 规 定 很 多 事 。Dialog and Window Expert 如 图 2.11<br />

示 。<br />

图 2.11<br />

对 话 框 和 窗 口 专 家 系 统 (TaskWindow.win)<br />

给 菜 单 选 项 File/New 添 加 代 码 , 先 得 找 到 它 。 它 在 文 件 夹 Menu 中 。 打 开 它 可 以 看 到<br />

子 文 件 夹 TaskMenu, 再 打 开 它 , 就 可 以 看 到 IDE 预 先 放 置 在 程 序 中 的 标 准 菜 单 。 选 项 File、<br />

Edit 和 Help 在 这 里 分 别 由 id_file、id_edit 和 id_help 表 示 。 打 开 id_file, 单 击 id‐file_new。<br />

在 对 话 框 的 底 部 有 一 个 Add 按 钮 , 点 击 它 时 CodeExpert 就 会 给 程 序 添 加 <strong>Prolog</strong> 代 码 ,<br />

也 就 是 程 序 用 户 点 击 该 选 项 时 要 执 行 的 代 码 。 现 在 还 看 不 到 代 码 , 不 过 可 以 看 到 CodeExpert<br />

给 id_file_new 添 了 点 东 西 ,onFileNew, 可 以 认 为 它 就 是 点 击 菜 单 选 项 File/New 时 要 执 行 的<br />

程 序 的 名 字 。 用 专 业 点 儿 的 术 语 来 说 : 当 点 击 选 项 File/New 时 产 生 的 事 件 将 执 行 onFileNew。<br />

作 为 已 经 在 程 序 里 增 加 了 些 东 西 的 信 号 , 此 时 Add 按 钮 变 成 了 Delete 按 钮 了 。 需 要 的<br />

话 , 也 可 以 删 除 增 加 的 代 码 , 不 过 现 在 不 要 这 样 做 。 现 在 程 序 的 File/New 菜 单 选 项 已 经 和<br />

名 为 onFileNew 的 子 程 序 联 系 在 一 起 了 。 要 看 看 增 加 的 代 码 , 可 以 在 id_file_new‐>onFileNew<br />

上 双 击 ,CodeExpert 会 在 编 辑 器 中 打 开 它 在 TaskWindow.pro 文 件 中 添 加 代 码 的 那 一 部 分 ,<br />

光 标 会 落 在 下 面 这 段 代 码 上 :<br />

predicates<br />

onFileNew : window::menuItemListener.<br />

clauses<br />

onFileNew(_Source, _MenuTag).<br />

现 在 可 以 构 建 应 用 程 序 了 。 关 闭 编 辑 器 中 的 TaskWindow.pro 文 件 , 在 任 务 菜 单 中 点 击<br />

Build/Build。IDE 会 询 问 是 否 要 插 入 一 个 包 (pack), 回 答 “ 是 ”。 消 息 窗 口 中 出 现 Project has<br />

been built 后 , 回 到 工 程 树 , 右 键 点 击 TaskWindow.win 再 用 CodeExpert 为 onFileNew 添 加 代<br />

码 。 好 像 很 麻 烦 ( 老 实 说 , 还 真 是 !), 不 过 这 样 很 快 就 会 熟 悉 了 IDE, 而 经 常 构 建 工 程 也 是<br />

11


明 智 的 。<br />

在 编 辑 器 里 做 如 下 的 修 改 :<br />

clauses<br />

onFileNew(Source, _MenuTag) :‐ NewForm= form1::new(Source), NewForm:show().<br />

一 定 要 小 心 按 上 面 的 内 容 修 改 。 注 意 大 小 写 、 下 划 线 、 逗 点 和 句 点 。 发 现 没 有 : 要 把 原<br />

来 Source 前 的 下 划 线 删 去 。<br />

保 存 所 做 的 修 改 , 关 闭 编 辑 器 , 再 在 任 务 菜 单 中 选 Build/build 构 建 程 序 。 完 了 之 后 , 再<br />

来 执 行 程 序 , 可 以 看 到 , 不 管 什 么 时 候 点 选 File/New 选 项 后 , 都 会 创 建 一 个 新 的 表 格 。 如<br />

果 多 次 点 选 File/New, 就 会 出 现 多 个 重 叠 在 一 起 的 表 格 , 拖 开 上 面 的 表 格 可 以 看 到 下 面 的 。<br />

2.4 这 背 后 是 怎 么 回 事 ?<br />

可 能 现 在 对 背 后 是 怎 么 回 事 没 兴 趣 , 不 过 还 是 要 稍 微 多 说 几 句 。 需 要 有 一 点 了 解 , 因 为<br />

IDE 做 事 有 时 似 乎 很 古 怪 。 这 背 后 的 事 情 分 为 两 部 分 。 当 点 击 Add 按 钮 时 ,IDE 会 在 文 件<br />

taskwindow.pro 中 增 加 两 段 代 码 。 可 以 打 开 文 件 找 出 这 两 段 代 码 , 但 千 万 小 心 不 要 改 变 任 何<br />

内 容 。 有 一 段 已 经 知 道 了 , 我 们 前 面 改 过 的 那 段 , 现 在 也 已 经 知 道 怎 样 借 助 CodeExpert 找<br />

到 它 。<br />

开 始 时 它 是 这 样 的 :<br />

clauses<br />

onFileNew(_Source, _MenuTag).<br />

我 们 把 它 改 了 。 另 一 段 所 在 的 位 置 在 文 件 的 尾 部 , 这 部 分 只 能 看 , 不 要 编 辑 它 。 起 始 处<br />

有 一 段 蓝 色 的 警 告 语 :<br />

% This code is maintained automatically, do not update it manually. 13:14:32‐10.7.2007<br />

百 分 号 (%) 向 编 译 器 表 示 这 里 是 注 释 。 注 释 是 阅 读 者 用 的 , 不 执 行 , 编 译 器 会 跳 过 它 。<br />

它 之 后 就 是 IDE 管 理 的 代 码 。 这 里 放 入 了 一 行 代 码 ( 可 能 需 要 滚 动 到 文 件 最 末 才 能 看 到 ),<br />

是 与 修 改 的 点 击 菜 单 项 File/New 相 关 的 :<br />

addMenuItemListener(resourceIdentifiers::id_file_new, onFileNew).<br />

在 VIP 中 , 这 叫 加 个 监 听 器 , 这 行 代 码 为 在 菜 单 项 上 点 击 设 置 了 一 个 监 听 器 。 可 以 把 监<br />

听 器 看 成 一 个 小 精 灵 , 它 在 背 后 等 着 用 户 点 击 File/New。 一 旦 点 击 了 , 它 就 引 导 程 序 转 向 我<br />

们 前 面 改 过 的 那 个 子 句 (onFileNew 开 头 的 子 句 ), 执 行 那 段 代 码 。<br />

前 面 我 们 改 动 了 IDE 插 入 的 代 码 。 如 果 不 改 呢 ? 不 改 , 它 就 是 段 什 么 也 不 做 的 代 码 , 可<br />

以 随 它 去 。<br />

就 是 这 样 ,IDE 控 制 着 哪 些 菜 单 项 是 可 用 的 ( 取 消 disabled 状 态 并 加 上 监 听 器 ) 而 哪 些<br />

是 不 可 用 的 , 哪 些 要 做 点 什 么 事 ( 插 入 我 们 前 面 输 入 的 代 码 ) 而 哪 些 什 么 也 不 做 。 更 重 要 的<br />

是 想 要 改 动 程 序 时 ,IDE 可 以 帮 我 们 找 到 相 关 的 代 码 。 可 以 想 像 一 下 , 在 一 个 很 大 的 程 序 中<br />

做 这 类 书 记 工 作 可 是 个 活 儿 呢 !<br />

不 过 也 有 代 价 。 要 改 动 任 务 窗 口 的 程 序 代 码 , 决 不 要 自 己 输 入 , 一 定 要 用 CodeExpert,<br />

否 则 事 情 就 会 一 团 糟 。 尤 其 在 删 除 IDE 插 入 的 代 码 时 要 当 心 。 例 如 , 要 在 Dialog and Window<br />

Expert 中 删 除 TaskWindow.win 文 件 中 的 onFileNew 代 码 , 先 找 到 id_file_new‐>onFileNew,<br />

选 中 它 后 点 击 delete。IDE 会 删 除 监 听 器 那 行 代 码 , 但 不 会 删 除 我 们 改 动 的 程 序 代 码 。 因 为<br />

我 们 改 动 过 , 它 很 难 确 定 要 删 除 什 么 , 说 不 定 会 删 掉 我 们 想 要 留 下 以 后 用 或 在 别 的 地 方 用 的<br />

代 码 。 所 以 不 得 不 人 工 地 删 除 那 些 代 码 。 这 时 可 能 就 会 出 错 , 有 两 种 可 能 :<br />

12


一 是 删 除 了 监 听 器 但 没 有 删 除 我 们 的 代 码 。 此 时 再 构 建 工 程 时 会 有 一 个 警 告 , 说 在 程 序<br />

中 有 未 使 用 的 谓 词 , 指 向 我 们 那 段 代 码 。 它 没 有 用 , 因 为 没 有 监 听 器 来 激 活 它 。 只 警 告 是 因<br />

为 留 着 它 对 程 序 无 妨 , 它 仅 仅 是 无 用 而 已 。<br />

二 是 删 除 了 我 们 的 代 码 但 没 有 删 除 监 听 器 。 当 构 建 工 程 时 就 会 出 现 错 误 , 说 程 序 中 有 未<br />

使 用 的 标 识 ( 比 如 一 个 变 量 )。 监 听 器 是 指 向 点 击 File/New 时 做 出 反 应 的 变 量 标 识 的 。 但 标<br />

识 我 们 删 了 , 监 听 器 找 不 到 相 应 要 转 向 的 程 序 段 。 这 是 个 错 误 , 一 定 要 加 上 相 应 代 码 或 用 IDE<br />

删 除 监 听 器 来 修 改 。<br />

2.5 鼠 标 事 件<br />

点 击 File/New 是 一 个 事 件 , 但 事 件 并 不 仅 限 于 点 击 菜 单 项 。 本 节 中 我 们 再 在 程 序 中 加<br />

个 鼠 标 功 能 : 特 别 点 儿 , 将 程 序 扩 展 为 在 表 格 任 意 地 方 击 鼠 标 左 键 时 做 出 反 应 。 点 击 鼠 标 就<br />

会 产 生 所 谓 鼠 标 事 件 。<br />

点 击 鼠 标 时 会 发 生 两 个 事 件 , 按 下 时 一 个 , 松 开 时 一 个 。 按 下 时 事 件 名 是 MouseDown,<br />

松 开 时 是 MouseUp。Windows 可 以 分 别 检 测 到 这 两 个 事 件 。 我 们 现 在 只 用 MouseDown, 另<br />

一 个 不 管 它 。 我 们 想 在 表 格 form1.frm 中 让 MouseDown 引 起 程 序 的 反 应 , 这 意 味 着 我 们 应<br />

该 在 表 格 中 插 入 所 需 要 的 程 序 代 码 。<br />

工 程 树 中 在 form1.frm 上 点 击 鼠 标 右 键 ,IDE 会 打 开 一 个 悬 浮 窗 口 , 选 Edit。 打 开 的 表 格<br />

编 辑 窗 口 我 们 前 面 见 过 。 打 开 这 个 窗 口 的 另 一 方 法 是 双 击 form1.frm。<br />

图 2.12<br />

form1 的 事 件<br />

我 们 想 让 表 格 在 事 件 MouseDown 时 有 反 应 , 所 以 我 们 要 找 到 这 个 事 件 并 把 它 和 相 应 的<br />

程 序 代 码 关 联 起 来 。 在 表 格 上 点 击 一 下 ( 不 要 点 在 按 钮 上 ) 以 显 示 表 格 的 属 性 。 在 属 性 窗 口<br />

13


底 部 有 两 个 标 签 , 一 个 是 Properties, 另 一 个 是 Events。 点 击 后 一 个 , 在 列 表 的 中 部 可 以 找<br />

到 MouseDown 事 件 。 在 右 边 一 栏 点 击 , 会 出 现 列 表 框 , 点 击 箭 头 以 展 示 各 选 项 。 这 里 只 有<br />

一 项 ,onMouseDown。 选 择 该 项 , 在 其 上 双 击 ,IDE 会 打 开 代 码 编 辑 器 , 代 码 中 有 MouseDown<br />

事 件 相 关 的 代 码 行 , 光 标 会 落 在 这 段 代 码 边 上 :<br />

predicates<br />

onMouseDown : drawWindow::mouseDownListener.<br />

clauses<br />

onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).<br />

IDE 知 道 我 们 想 在 MouseDown 时 引 发 点 儿 什 么 事 , 所 以 插 入 了 这 个 事 件 的 代 码 。 这 和<br />

我 们 前 面 为 File/New 插 入 代 码 类 似 。 可 以 认 为 这 段 代 码 就 是 名 为 onMouseDown 的 子 程 序 的<br />

头 儿 。 很 好 ! 可 是 IDE 并 不 知 道 在 MouseDown 时 准 确 地 要 去 做 什 么 , 所 以 我 们 要 自 己 来 添<br />

加 代 码 。 把 代 码 :<br />

clauses<br />

onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).<br />

换 成 :<br />

clauses<br />

onMouseDown(Source, Point, _ShiftControlAlt, _Button) :‐<br />

Window= Source:getVPIWindow(),<br />

Point= pnt(X, Y),<br />

vpi::drawText(Window, X, Y, "Hello, World!").<br />

要 注 意 每 个 点 、 逗 点 、 下 划 线 、 大 小 写 ,<strong>Prolog</strong> 与 其 它 编 程 语 言 一 样 , 对 这 些 都 很 敏 感 。<br />

尤 其 要 注 意 删 除 Source 和 Point 前 的 下 划 线 。<br />

插 入 完 代 码 后 保 存 。 再 构 建 程 序 , 执 行 。 程 序 中 选 File/New 时 会 创 建 新 表 格 , 只 要 点<br />

击 表 格 中 任 意 地 方 , 程 序 就 会 写 出 那 段 我 们 熟 知 的 问 候 语 。OnMouseDown 中 的 代 码 会 响 应<br />

MouseDown 事 件 , 我 们 把 它 称 为 事 件 的 句 柄 。Windows 编 程 中 , 最 多 的 就 是 通 过 定 义 和 编<br />

码 适 当 的 事 件 句 柄 来 管 理 事 件 了 。<br />

来 想 想 我 们 做 了 些 什 么 , 看 到 些 什 么 。 前 面 我 们 加 了 File/New 的 代 码 , 这 里 又 加 了<br />

MouesDown 的 代 码 。 地 方 和 名 字 不 一 样 , 办 法 一 样 :<br />

• 打 开 一 个 原 型 (TaskMenu.mnu 或 form1.frm)<br />

• 选 择 一 个 事 件 (File/New 或 MouseDown)<br />

• 为 事 件 添 加 代 码<br />

• 改 动 相 应 的 代 码 以 符 合 我 们 的 需 要<br />

小 结 一 下 。 编 程 时 有 章 可 循 , 想 要 程 序 有 所 表 现 , 先 要 确 定 某 种 合 适 的 事 件 ; 再 确 定 引<br />

发 这 个 事 件 的 用 户 动 作 , 这 可 以 是 点 击 一 个 按 钮 或 是 一 个 菜 单 项 或 是 随 便 什 么 地 方 。 然 后 创<br />

建 和 使 能 需 要 的 部 件 ( 比 如 在 表 格 中 的 一 个 按 钮 或 是 菜 单 中 的 一 个 选 项 )。 接 着 , 用 IDE 插<br />

入 监 听 器 ( 或 者 叫 响 应 器 ) 和 一 些 标 准 的 代 码 , 最 后 改 动 或 插 入 所 需 要 的 代 码 。 目 前 为 止 ,<br />

这 些 代 码 可 能 还 如 同 天 书 一 般 , 不 过 整 个 过 程 是 清 楚 了 。<br />

14


第 3 章 简 单 的 用 户 界 面<br />

本 章 要 稍 微 细 致 些 介 绍 用 户 界 面 。 与 用 户 通 信 有 若 干 种 方 法 , 需 要 了 解 一 下 。 同 时 , 还<br />

会 增 加 些 使 用 IDE 的 体 验 。<br />

Windows 程 序 很 大 一 部 分 由 与 外 界 通 信 的 代 码 构 成 , 用 户 是 它 的 重 要 组 成 部 分 。 所 以 ,<br />

了 解 如 何 与 用 户 通 信 是 很 有 用 的 。 而 且 , 通 过 它 也 很 容 易 理 解 VIP 和 IDE。 本 节 中 我 们 要 讲<br />

解 怎 么 向 用 户 发 消 息 和 请 求 输 入 。<br />

写 计 算 机 程 序 很 有 趣 ( 常 常 也 很 受 打 击 ☺)。 但 它 本 身 不 是 目 的 , 还 有 用 户 在 那 里 , 我<br />

们 需 要 与 用 户 对 话 。 本 章 要 介 绍 一 些 与 用 户 对 话 的 简 单 方 法 。 我 们 会 学 到 如 何 向 用 户 发 送 消<br />

息 , 如 何 取 得 用 户 的 输 入 信 息 。 同 时 , 对 IDE 会 更 加 熟 悉 并 了 解 编 程 的 一 般 方 式 。<br />

我 们 慢 慢 来 , 因 为 是 对 初 学 者 , 所 以 这 里 经 常 会 有 些 重 复 性 的 介 绍 。 如 果 觉 得 太 慢 了 ,<br />

也 可 以 跳 过 本 章 的 一 些 内 容 。<br />

我 们 看 到 一 个 Windows 程 序 时 , 会 非 常 熟 悉 那 些 告 诉 我 们 消 息 的 弹 出 窗 口 。 这 一 节 我<br />

们 介 绍 标 准 的 与 用 户 对 话 方 法 : 短 信 、 错 误 消 息 和 消 息 框 。 在 VIP 中 它 们 都 可 以 使 用 , 而 且<br />

很 方 便 。“VIP 中 都 可 以 使 用 ”, 就 是 说 在 IDE 中 有 它 们 的 代 码 , 只 需 告 诉 IDE 在 程 序 中 我 们<br />

要 用 什 么 , 构 建 程 序 时 IDE 就 会 把 相 应 代 码 插 入 到 程 序 中 。 需 要 的 代 码 被 称 作 “ 类 ”, 为 什<br />

么 这 么 叫 先 不 管 它 , 后 面 还 有 专 门 章 节 介 绍 。 现 在 , 我 们 可 以 把 “ 类 ” 看 作 为 写 好 的 一 组 过<br />

程 , 需 要 的 时 候 就 可 以 用 。 过 程 就 是 在 程 序 中 可 以 使 用 的 一 小 段 程 序 , 我 们 后 面 还 要 讨 论 它 。<br />

我 们 这 章 中 要 用 的 类 是 vpiCommonDialogs, 它 包 括 三 十 多 个 与 用 户 通 信 的 过 程 。 可 以 在 帮<br />

助 文 件 中 了 解 vpiCommonDialogs 的 内 容 。<br />

3.1 过 程<br />

开 始 编 程 后 , 就 会 感 到 我 们 正 写 的 代 码 可 能 别 人 已 经 写 过 好 多 次 了 。 可 能 不 完 全 一 样 ,<br />

但 肯 定 多 次 有 人 写 过 诸 如 打 开 一 个 表 格 、 输 入 一 些 内 容 、 说 “ 喂 , 你 好 !” 等 代 码 。 要 是 在<br />

我 们 的 程 序 中 能 用 这 些 现 成 的 片 断 多 好 ! 代 码 重 用 是 一 种 有 效 编 写 程 序 的 办 法 。<br />

有 多 种 方 法 实 现 代 码 重 用 。 一 种 是 手 工 的 办 法 , 用 的 时 候 把 需 要 的 代 码 复 制 - 粘 贴 到 程<br />

序 中 来 。 还 有 一 种 办 法 , 把 执 行 所 需 任 务 的 代 码 段 做 成 一 个 库 包 含 在 我 们 的 程 序 中 。 本 章 要<br />

介 绍 后 一 种 方 法 。<br />

在 程 序 中 加 入 一 个 包 含 有 若 干 代 码 段 的 库 , 需 要 的 时 候 让 程 序 使 用 相 应 的 代 码 段 。<br />

这 样 的 代 码 段 称 为 “ 过 程 ”, 它 是 完 成 某 项 任 务 的 一 小 段 程 序 , 什 么 时 候 需 要 都 可 以 用 。<br />

过 程 有 自 己 的 名 字 , 程 序 中 叫 它 的 名 字 来 使 用 它 , 这 就 是 所 谓 “ 过 程 调 用 ”。 举 个 例 子 , 有<br />

这 样 一 个 程 序 ( 不 是 <strong>Prolog</strong> 也 不 是 其 它 什 么 程 序 代 码 , 只 是 举 例 ):<br />

Number1 = 2<br />

Number2 = 3<br />

Answer = Number1 + Number2<br />

这 段 代 码 中 , 我 们 先 对 变 量 Number1 和 Number2 赋 值 , 再 将 两 个 变 量 相 加 , 其 结 果 值<br />

赋 给 Answer, 是 5。 现 在 假 设 有 个 叫 sum 的 过 程 , 它 能 完 成 两 个 数 的 加 法 。 我 们 的 程 序 就<br />

可 以 是 这 样 :<br />

Number1 = 2<br />

Number2 = 3<br />

sum(Number1, Number2, Answer)<br />

这 段 代 码 , 先 给 变 量 Number1 和 Number2 赋 值 , 然 后 调 用 过 程 sum。Sum 名 字 的 后 面<br />

是 括 号 包 围 着 的 变 量 表 , 它 们 也 叫 过 程 的 参 数 。 现 在 的 例 子 中 有 三 个 参 数 , 前 两 个 (Number1<br />

和 Number2) 用 来 告 诉 过 程 哪 两 个 数 要 相 加 ; 第 三 个 参 数 用 于 过 程 返 回 结 果 。 我 们 这 个 例<br />

子 , 调 用 过 程 后 的 结 果 是 5。 这 个 例 子 当 然 很 蠢 , 因 为 有 很 好 的 办 法 做 加 法 , 不 过 希 望 读 者<br />

可 以 明 白 要 说 的 意 思 。<br />

过 程 一 组 组 地 合 在 一 起 , 尤 其 是 按 它 们 的 相 关 性 。 成 组 的 过 程 合 在 一 起 就 是 “ 类 ”。 可<br />

以 把 “ 类 ” 看 成 在 程 序 内 可 以 调 用 的 一 组 过 程 。 本 章 中 要 遇 到 的 vpiCommonDialogs 类 , 正<br />

15


如 名 称 表 明 的 那 样 , 包 含 与 用 户 通 信 的 普 通 对 话 框 的 过 程 。<strong>Visual</strong> <strong>Prolog</strong> 提 供 了 很 多 类 ,VIP<br />

安 装 后 就 可 以 使 用 , 需 要 时 IDE 会 编 译 和 链 接 它 们 。 不 用 去 管 这 些 类 在 什 么 地 方 ,IDE 知 道<br />

怎 么 找 。<br />

最 后 还 要 说 一 下 , 在 <strong>Prolog</strong> 中 对 过 程 还 有 一 种 简 化 写 法 。 比 如 前 面 的 过 程 sum, 可 以 写<br />

为 sum/3, 表 示 这 个 sum 过 程 有 三 个 参 数 。 名 称 和 参 数 个 数 唯 一 地 确 定 一 个 过 程 , 所 以 这 里<br />

指 的 是 哪 个 过 程 很 明 白 。<strong>Prolog</strong> 库 中 有 一 些 相 同 名 字 的 过 程 , 但 它 们 参 数 个 数 是 不 一 样 的 。<br />

3.2 写 消 息<br />

程 序 中 的 任 何 地 方 都 可 以 产 生 消 息 。 我 们 先 用 任 务 菜 单 来 产 生 消 息 。 当 然 , 我 们 以 后 并<br />

不 这 样 用 它 , 不 过 现 在 它 是 一 个 很 方 便 的 方 法 。 先 开 始 一 个 新 工 程 , 起 好 名 字 , 比 如 ch03p01。<br />

通 知 用 户 的 简 单 方 法 是 写 个 短 信 。 这 样 做 时 , 用 户 会 看 到 一 个 如 图 3.1 所 示 的 带 有 一 段<br />

短 文 的 小 窗 口 。<br />

图 3.1<br />

短 信<br />

要 产 生 这 样 一 个 消 息 , 先 要 创 建 一 个 事 件 。 这 里 , 我 们 用 在 任 务 菜 单 的 一 个 选 项 上 点 击<br />

鼠 标 这 个 事 件 。 先 给 任 务 菜 单 建 一 个 新 的 选 项 , 然 后 加 入 标 准 代 码 , 再 修 改 标 准 代 码 来 产 生<br />

消 息 。 本 章 中 一 直 会 重 复 使 用 这 一 方 式 。<br />

图 3.2<br />

任 务 菜 单 编 辑 器 中 的 图 标 工 具 条<br />

在 工 程 树 TaskWindow 文 件 夹 中 , 选 择 TaskMenu.mnu, 在 菜 单 编 辑 器 中 打 开 它 。 紧 挨<br />

着 标 题 行 下 可 以 看 到 几 个 图 标 , 表 示 这 个 编 辑 器 中 的 可 选 项 ( 见 图 3.2)。 当 光 标 移 到 这 些 图<br />

标 上 时 , 会 显 示 出 相 应 的 名 字 。 从 左 到 右 是 :<br />

• New first Item 在 菜 单 的 第 一 项 前 插 入 一 个 新 项 , 程 序 中 这 个 菜 单 项 会 出 现 在 原 来 第 一 项 的<br />

左 边 或 顶 上<br />

• New item 在 所 选 的 项 后 面 插 入 一 个 新 项<br />

• New sub item 在 所 选 子 菜 单 项 后 插 入 一 个 新 项<br />

• New separator 在 所 选 项 后 插 入 一 条 点 划 线<br />

• Shift Up 所 选 项 向 上 移 动 一 位<br />

• Shift Down 所 选 项 向 下 移 动 一 位<br />

• Test 在 任 务 菜 单 中 显 示 菜 单 。<br />

要 插 入 一 个 菜 单 项 , 先 要 高 亮 一 个 可 选 项 , 点 击 上 述 图 标 之 一 , 这 与 我 们 打 算 插 入 的 内<br />

容 有 关 。 如 果 是 要 插 入 一 个 新 项 , 就 点 击 New Item 图 标 。 这 时 会 在 高 亮 的 项 下 面 出 现 一 个<br />

小 编 辑 框 , 我 们 可 以 在 这 里 输 入 新 项 的 名 称 。 有 两 个 特 殊 符 号 要 说 一 下 , 名 称 中 的 “&”,<br />

16


如 “&File”, 表 示 用 “&” 后 的 字 母 “F” 作 为 该 项 的 快 捷 键 。 符 号 可 以 放 在 名 称 的 任 意 位 置 ,<br />

它 后 面 的 字 母 就 是 快 捷 键 。 当 心 不 要 用 重 了 已 有 的 快 捷 键 。 在 名 称 中 加 上 \tF5, 就 表 示 用 功<br />

能 键 F5 当 快 捷 键 。<br />

图 3.3<br />

加 了 新 项 的 任 务 菜 单<br />

现 在 我 们 打 算 加 一 些 菜 单 项 来 产 生 消 息 。 把 这 些 新 选 项 放 在 单 独 的 菜 单 中 要 较 为 合 适 。<br />

按 Windows 的 传 统 ,File 和 Edit 应 该 在 菜 单 条 的 左 边 而 Help 应 该 在 右 侧 , 所 以 我 们 把 新 项<br />

放 在 Edit 和 Help 之 间 。 高 亮 “&Edit” 并 点 击 工 具 条 中 的 New Item,IDE 会 打 开 一 个 小 编 辑<br />

窗 , 输 入 新 项 的 名 称 , 我 们 叫 它 Messages 好 了 。 编 辑 器 开 始 时 看 起 来 应 该 和 图 3.3 差 不 多 。<br />

还 可 以 在 名 字 里 加 上 符 号 “&”, 其 后 的 字 母 就 可 以 当 做 这 个 选 项 的 快 捷 键 用 了 。 可 以 参 考<br />

一 下 &File 或 &Edit。 回 车 或 在 别 的 地 方 点 击 一 下 鼠 标 , 就 可 以 关 闭 编 辑 栏 。<br />

Messages 之 下 我 们 要 创 建 一 个 子 菜 单 。 高 亮 Messages 并 点 击 工 具 条 中 的 New Subitem,<br />

这 时 Messages 下 会 出 现 一 个 编 辑 框 , 输 入 新 选 项 的 名 字 , 这 里 我 们 用 WriteNote1。 参 见 图<br />

3.3。 可 以 点 击 工 具 条 上 的 “T” 选 项 来 验 证 一 下 新 的 菜 单 , 任 务 菜 单 会 变 成 为 我 们 的 新 菜 单<br />

的 样 子 。 点 击 工 程 窗 口 回 到 原 来 的 任 务 菜 单 。 点 击 右 上 角 的 X 可 以 关 闭 菜 单 编 辑 器 , 不 过 别<br />

忘 了 保 存 所 做 的 修 改 。<br />

现 在 我 们 有 了 一 个 新 的 菜 单 项 , 接 着 来 产 生 相 应 的 代 码 。 构 造 工 程 , 完 成 之 后 选<br />

TaskWindow.win, 击 左 键 选 CodeExpert, 打 开 Menu 文 件 夹 中 的 TaskMenu, 打 开 id_messages,<br />

点 Add 给 id_messages_writenote1 添 加 代 码 。 参 见 图 3.4。<br />

17


图 3.4<br />

给 菜 单 项 添 加 代 码<br />

然 后 , 在 id_messages_writenote1‐>onMessagesWriteNote1 上 双 击 ,IDE 会 打 开 一 个 窗 口 ,<br />

在 这 里 可 以 输 入 向 用 户 发 消 息 的 代 码 。 编 辑 器 中 看 到 的 代 码 是 IDE 插 入 的 , 会 是 这 样 :<br />

predicates<br />

onMessagesWritenote1 : window::menuItemListener.<br />

clauses<br />

onMessagesWritenote1(_Source, _MenuTag).<br />

把 它 改 成 :<br />

predicates<br />

onMessagesWritenote1 : window::menuItemListener.<br />

clauses<br />

onMessagesWritenote1(_Source, _MenuTag) :‐<br />

vpiCommonDialogs::note("This is a message").<br />

上 面 的 最 后 一 行 , 告 诉 <strong>Prolog</strong> 使 用 vpiCommonDialogs 类 中 名 为 note 的 过 程 , 括 号 中 的<br />

字 符 串 是 要 显 示 在 屏 幕 上 的 内 容 。 关 闭 编 辑 器 , 保 存 修 改 , 构 造 和 执 行 程 序 。 在 任 务 菜 单 中<br />

会 出 现 新 的 菜 单 项 , 点 它 再 点 击 WriteNote1, 消 息 就 会 显 示 出 来 。 点 OK 可 以 关 闭 这 个 消 息 。<br />

可 以 看 到 , 出 现 的 消 息 窗 口 标 题 行 显 示 的 是 note。 需 要 的 话 也 可 以 显 示 别 的 。 我 们 在<br />

Messages 下 再 加 一 项 WriteNote2, 给 它 添 加 代 码 时 把 子 句 改 成 如 下 内 容 :<br />

clauses<br />

onMessagesWritenote2(_Source, _MenuTag) :‐<br />

vpiCommonDialogs::note("This is a title", "This is a message").<br />

18


保 存 修 改 , 构 建 工 程 , 执 行 程 序 。 当 点 选 WriteNote2 时 , 会 看 到 标 题 中 出 现 的 是 第 一<br />

个 参 数 而 第 二 个 参 数 在 消 息 体 里 。note/1 可 以 用 一 个 预 设 的 标 题 窗 口 送 出 一 个 短 消 息 给 用<br />

户 , 而 note/2 不 仅 可 以 送 出 消 息 , 还 允 许 用 户 选 择 窗 口 的 标 题 。<br />

再 回 过 头 来 看 看 代 码 , 每 次 要 用 vpiCommonDialogs 类 中 的 过 程 时 输 入 那 么 长 串 的 名 字 太 啰<br />

嗦 , 有 办 法 简 单 些 。 在 工 程 树 中 双 击 TaskWindow 文 件 夹 中 的 TaskWindow.pro, 这 个 文 件 中 <strong>Prolog</strong> 保<br />

存 有 TaskWindow 的 代 码 。 通 常 我 们 是 用 CodeExpert 来 操 作 代 码 的 , 但 也 可 以 这 样 打 开 文 件 直 接 访 问<br />

代 码 , 不 过 千 万 要 小 心 。<br />

在 这 个 文 件 的 开 始 处 , 可 以 看 到 这 样 的 代 码 :<br />

implement taskWindow<br />

inherits applicationWindow<br />

open core, vpiDomains<br />

注 意 第 三 行 , 这 是 告 诉 编 译 器 对 在 程 序 中 找 不 到 的 过 程 名 ( 如 “note”), 要 在 类 “core”<br />

和 “vpiDomains” 中 找 。 我 们 已 经 知 道 要 用 过 程 note, 还 知 道 它 在 类 “vpiCommonDialogs”<br />

中 , 所 以 我 们 可 以 加 上 类 名 告 诉 编 译 器 也 要 查 找 vpiCommonDialogs 类 。 把 这 行 改 成 :<br />

open core, vpiDomains, vpiCommonDialogs<br />

加 了 这 个 名 字 , 其 它 地 方 就 可 以 不 写 它 了 , 用 到 它 的 地 方 可 以 写 成 :<br />

clauses<br />

onMessagesWritenote1(_Source, _MenuTag) :‐<br />

note("This is a message").<br />

和<br />

clauses<br />

onMessagesWritenote2(_Source, _MenuTag) :‐<br />

note("This is a title", "This is a message").<br />

向 下 滚 动 TaskWindow.pro 到 尾 端 , 可 以 找 到 这 些 代 码 , 而 之 上 的 那 些 代 码 是 由 IDE 维<br />

护 的 , 不 要 改 变 它 们 。 当 然 , 也 还 是 可 以 借 由 Code Expert 的 帮 助 找 到 这 些 代 码 。<br />

改 好 后 , 关 闭 编 辑 器 , 保 存 修 改 , 构 建 并 执 行 工 程 , 应 该 与 以 前 的 显 示 一 样 。<br />

我 们 看 到 ,note 过 程 可 以 用 一 个 参 数 , 也 可 以 用 两 个 参 数 。 看 起 来 有 点 儿 乱 , 不 过 <strong>Prolog</strong><br />

自 己 很 清 楚 : 过 程 note/1 和 note/2 是 不 同 的 , 因 为 参 数 个 数 有 差 别 。 在 声 明 中 和 调 用 时 ,<br />

参 数 个 数 是 很 清 楚 的 。<br />

另 一 个 向 用 户 发 消 息 的 方 法 是 使 用 过 程 error, 它 也 在 vpiCommonDialogs 里 。 使 用 方 法<br />

与 note 一 样 。Error 过 程 有 两 个 版 本 ,error/1 只 有 一 个 消 息 变 量 ,error/2 还 可 以 给 消 息 窗 口<br />

加 上 标 题 。 自 己 试 试 在 前 面 的 菜 单 中 再 加 两 个 选 项 ,WriteError1 和 WriteError2, 用 CodeExpert<br />

在 适 当 的 地 方 添 加 下 面 的 代 码 :<br />

predicates<br />

onMessagesWriteerror1 : window::menuItemListener.<br />

clauses<br />

onMessagesWriteerror1(_Source, _MenuTag) :‐<br />

error("This is an error message").<br />

predicates<br />

onMessagesWriteerror2 : window::menuItemListener.<br />

clauses<br />

onMessagesWriteerror2(_Source, _MenuTag) :‐<br />

19


error("The title", "The error message").<br />

保 存 修 改 并 执 行 工 程 , 可 以 看 到 弹 出 的 错 误 (error) 消 息 。 注 意 , 这 里 与 note/1 和 note/2<br />

有 一 点 点 小 差 别 ,note 产 生 的 消 息 窗 有 一 个 “i” 图 标 , 而 error 消 息 窗 里 的 是 表 示 “ 错 误 ”<br />

的 红 圈 白 叉 图 标 。<br />

Note 和 error 过 程 用 于 发 出 简 短 的 消 息 。 还 有 一 个 messagebox/6 过 程 , 它 允 许 控 制 消 息<br />

窗 口 的 外 观 , 还 可 以 跟 踪 用 户 的 反 应 。 我 们 再 加 一 个 选 项 WriteMessageBox 看 看 它 是 什 么 样 。<br />

添 加 下 面 的 代 码 :<br />

predicates<br />

onMessagesWritemessagebox : window::menuItemListener.<br />

clauses<br />

onMessagesWritemessagebox(_Source, _MenuTag) :‐<br />

Answer = messageBox("TitleString", "Message line",<br />

mesbox_iconExclamation,<br />

mesbox_buttonsYesNo,<br />

mesbox_defaultSecond,<br />

mesbox_suspendApplication),<br />

note("You pressed button number ...", tostring(Answer)).<br />

小 心 按 原 样 输 入 代 码 , 很 麻 烦 但 值 得 。Messagebox 过 程 有 六 个 参 数 , 可 以 控 制 很 多 东<br />

西 。 第 一 个 参 数 “Titlestring” 是 消 息 的 标 题 条 字 符 串 , 第 二 个 “Message line” 是 消 息 字 符<br />

串 。 其 它 的 用 于 控 制 图 标 、 按 钮 、 缺 省 按 钮 和 搁 置 。<br />

图 标 : 有 四 种 图 标 可 用 : 信 息 、 问 题 、 错 误 和 惊 叹 。 我 们 已 经 在 前 面 看 到 了 note 中<br />

用 的 “ 消 息 ” 图 标 和 error 中 用 的 “ 错 误 ” 图 标 。 可 以 选 用 的 有 下 面 的 四 个 名 字 :<br />

• mesbox_iconinformation 显 示 “ 消 息 ” 图 标 。 还 可 以 使 用 数 字 0 做 参 数 , 效 果 相 同 。<br />

• mesbox_iconquestion 显 示 “ 问 号 ” 图 标 。 也 可 以 用 数 字 1。<br />

• mesbox_iconerror 显 示 “ 错 误 ” 图 标 。 也 可 以 用 数 字 2。<br />

• mesbox_iconexclamation 显 示 “ 惊 叹 号 ” 图 标 。 也 可 以 用 数 字 3。<br />

四 个 名 字 也 被 称 为 “ 常 数 ”。 它 们 已 在 <strong>Visual</strong> <strong>Prolog</strong> 中 预 先 定 义 , 在 <strong>Visual</strong> <strong>Prolog</strong> 环 境<br />

中 代 表 着 某 个 常 数 。 不 使 用 名 字 而 直 接 使 用 ( 它 们 所 代 表 的 ) 数 值 也 是 可 以 的 , 但 显 然 用 名<br />

字 更 易 于 理 解 程 序 。 图 标 名 是 第 三 个 参 数 。<br />

按 钮 : 第 四 个 位 置 上 要 输 入 消 息 窗 口 中 出 现 按 钮 的 识 别 符 。 可 用 的 ( 等 效 值 ) 有 :<br />

• mesbox_buttonsok (= 0) 只 出 现 一 个 OK 钮 , 它 看 起 来 与 note 窗 口 差 不 多 。<br />

• mesbox_buttonsokcancel (= 1) 出 现 OK 和 Cancel 两 个 按 钮 。<br />

• mesbox_buttonsyesno (= 2) 出 现 Yes 和 No 两 个 按 钮 。<br />

• mesbox_buttonsyesnocancel (= 3) 出 现 Yes、No 和 Cancel 三 个 按 钮<br />

• mesbox_buttonsretrycancel (= 4) 出 现 Retry 和 Cancel 两 个 按 钮 。。<br />

• mesbox_buttonsabortretryignore (= 5) 出 现 Abort、Retry 和 Ignore 三 个 按 钮 。<br />

缺 省 按 钮 : 第 五 个 参 数 用 于 表 示 哪 个 按 钮 是 缺 省 预 选 的 。 当 消 息 窗 口 打 开 后 , 假 设 用<br />

户 是 会 点 击 一 个 按 钮 关 闭 它 , 但 用 户 也 可 能 是 敲 回 车 键 , 这 相 当 于 点 击 了 缺 省 按 钮 。<br />

• mesbox_defaultfirst (= 0) 第 一 个 按 钮 是 缺 省 预 选 的 。<br />

• mesbox_defaultsecond (= 1) 第 二 个 按 钮 是 缺 省 预 选 的 。<br />

• mesbox_defaultthird (= 2) 第 三 个 按 钮 是 缺 省 预 选 的 。<br />

当 然 了 , 程 序 中 只 有 两 个 按 钮 但 却 指 定 第 三 个 按 钮 是 缺 省 的 , 这 肯 定 不 行 。<br />

搁 置 : 最 后 一 个 位 置 上 要 指 明 是 否 允 许 用 户 在 消 息 窗 口 打 开 时 使 用 另 外 的 应 用 程 序 。<br />

• mesbox_suspendapplication (= 0) 表 示 用 户 必 须 对 消 息 作 出 反 应 后 才 可 以 继 续 这 个 程 序 , 但<br />

可 以 在 消 息 显 示 时 使 用 别 的 应 用 程 序 。<br />

• mesbox_suspendsystem (= 1) 表 示 消 息 显 示 着 时 不 能 使 用 其 它 任 何 应 用 程 序 。<br />

最 后 一 个 要 说 的 是 变 量 Answer。 调 用 messagebox 过 程 时 , 它 不 仅 把 消 息 显 示 在 屏 幕 上 ,<br />

它 还 检 测 用 户 点 击 的 按 钮 并 返 回 相 应 的 消 息 。 在 其 它 的 编 程 语 言 中 , 这 被 称 为 函 数 。 点 击 的<br />

按 钮 值 会 放 在 Answer 变 量 中 返 回 。 上 面 代 码 的 最 后 一 行 , 这 个 变 量 被 用 在 note 中 提 供 给 用<br />

20


户 。 因 为 Answer 是 一 个 数 而 过 程 note 需 要 的 是 串 , 所 以 必 须 用 tostring 函 数 把 Answer 转<br />

换 成 串 。 按 钮 从 左 到 右 被 编 了 号 , 从 1 开 始 。 建 议 读 者 用 各 种 可 能 来 试 试 messagebox 过 程 ,<br />

改 变 图 标 和 按 钮 , 体 会 一 下 这 些 过 程 的 使 用 。 它 们 是 很 有 用 的 工 具 。<br />

3.3 获 取 用 户 响 应<br />

用 messagebox 过 程 可 以 得 到 用 户 的 输 入 , 不 过 它 仅 只 是 一 种 方 法 , 还 有 很 多 其 它 方 法 。<br />

这 些 过 程 有 ask/2、ask/3、 getString/3、listSelect/5 和 getFileName/6。 要 看 看 它 们 是 怎 么 样<br />

的 , 我 们 还 是 使 用 任 务 菜 单 选 项 来 触 发 它 们 , 它 们 会 接 收 来 自 用 户 的 输 入 , 我 们 要 加 一 些 代<br />

码 来 显 示 这 些 输 入 。 和 前 面 使 用 messagebox/6 一 样 , 看 用 户 点 击 了 什 么 。<br />

先 来 看 ask/2 和 ask/3。 它 们 的 用 法 与 note 和 error 相 似 。 用 于 它 们 的 触 发 事 件 , 在 任 务<br />

菜 单 中 Messages 的 子 菜 单 里 增 加 两 个 选 项 ask2 和 ask3。 生 成 代 码 并 把 它 们 改 成 :<br />

predicates<br />

onMessagesAsk2 : window::menuItemListener.<br />

clauses<br />

onMessagesAsk2(_Source, _MenuTag) :‐<br />

ButtonPressed = ask("This is the question", ["Button0", "Button1", "Button2"]),<br />

note("You pressed button number ...", tostring(ButtonPressed)).<br />

构 造 并 运 行 程 序 。 会 弹 出 一 个 含 有 问 题 和 三 个 按 钮 的 窗 口 , 如 下 图 。 用 户 可 以 点 击 按 钮 ,<br />

点 击 的 按 钮 值 会 返 回 给 程 序 。<br />

注 意 一 下 代 码 中 的 :<br />

["Button0", "Button1", "Button2"]<br />

这 是 过 程 调 用 中 的 第 二 个 参 数 。<strong>Prolog</strong> 中 把 它 称 为 表 。 一 个 表 由 方 括 号 和 其 中 的 若 干 用<br />

逗 号 分 隔 开 的 元 素 构 成 。 在 我 们 这 个 例 子 中 表 是 由 按 钮 的 标 签 构 成 的 。 按 钮 的 标 签 是 字 符 串 ,<br />

所 以 它 们 要 用 引 号 包 围 起 来 。 例 子 中 我 们 指 定 了 三 个 按 钮 , 但 也 可 以 只 用 一 个 或 两 个 按 钮 。<br />

至 少 要 指 定 一 个 按 钮 的 名 字 。 指 定 四 个 或 更 多 的 按 钮 无 效 , 三 个 是 最 多 的 。 按 钮 的 标 签 可 以<br />

是 任 意 字 符 串 。 在 内 部 按 钮 是 按 表 中 出 现 的 顺 序 编 号 的 , 第 一 个 编 号 是 0( 零 ), 要 注 意 这<br />

点 与 messagebox 过 程 中 的 情 况 不 一 样 。<br />

ask/3 过 程 与 ask/2 几 乎 一 样 , 不 同 的 是 可 以 规 定 消 息 窗 的 标 题 。 插 入 必 要 的 代 码 对 读 者<br />

来 说 应 该 已 经 不 成 问 题 了 , 这 里 给 出 代 码 :<br />

predicates<br />

onMessagesAsk3 : window::menuItemListener.<br />

clauses<br />

onMessagesAsk3(_Source, _MenuTag) :‐<br />

ButtonPressed = ask("A convenient Title", "This is the question", ["Button0", "Button1"]),<br />

note("You pressed button number ...", tostring(ButtonPressed)).<br />

关 于 使 用 ask/2 和 ask/3 获 取 用 户 输 入 再 多 说 两 句 。 我 们 通 常 假 设 用 户 会 点 击 我 们 提 供<br />

21


的 那 些 按 钮 , 但 实 际 上 未 必 如 此 , 用 户 可 能 直 接 回 车 , 也 可 能 点 击 窗 口 右 上 角 的 关 闭 图 标 ,<br />

还 可 能 按 ESC 键 。ask/2 和 ask/3 对 此 这 是 样 处 理 的 : 敲 回 车 键 等 同 于 点 击 第 一 个 按 钮 , 因 为<br />

缺 省 按 钮 是 第 一 个 ; 点 击 关 闭 按 钮 等 同 于 回 车 ; 按 ESC 键 等 同 于 按 最 后 一 个 按 钮 。 我 们 这 里<br />

没 管 这 么 多 , 但 在 写 实 际 的 应 用 程 序 时 , 应 该 把 对 用 户 这 些 动 作 的 响 应 考 虑 周 全 。<br />

当 可 选 答 案 不 多 时 ,ask/2 和 ask/3 是 很 有 用 的 。 但 当 选 项 一 多 , 比 如 询 问 某 人 的 名 字 时 ,<br />

困 难 就 来 了 , 它 是 一 个 字 符 串 , 但 这 字 符 串 有 太 多 的 可 能 , 无 法 用 几 个 选 项 来 完 成 。 要 获 取<br />

字 符 串 输 入 , 可 以 使 用 getString/3 过 程 。 它 的 调 用 与 ask/2 和 ask/3 相 似 , 下 面 是 相 关 的 代<br />

码 , 是 用 任 务 菜 单 中 Messages 菜 单 下 的 GetString 触 发 的 :<br />

predicates<br />

onMessagesGetstring : window::menuItemListener.<br />

clauses<br />

onMessagesGetstring(_Source, _MenuTag) :‐<br />

AnswerString = getString("Title", "Question", "Preset Answer"), !,<br />

note("Your answer is ...", AnswerString).<br />

onMessagesGetstring(_Source, _MenuTag) :‐<br />

note("You clicked the button").<br />

getString/3 有 三 个 输 入 字 符 串 : 一 个 标 题 Title、 一 个 问 题 Question 和 一 个 缺 省 回 答 Preset<br />

Answer。 它 用 标 题 、 问 题 和 填 有 缺 省 回 答 的 编 辑 框 及 两 个 分 别 标 为 OK 和 Cancel 的 按 钮 构 成<br />

一 个 窗 口 。 用 户 可 以 接 受 缺 省 的 回 答 , 也 可 以 改 变 回 答 , 点 击 OK 或 Cancel 就 关 闭 窗 口 。 接<br />

下 来 有 两 种 可 能 , 点 击 OK 时 返 回 用 户 输 入 的 字 符 串 , 点 击 Cancel 时 则 没 有 字 符 串 返 回 。 正<br />

因 为 这 样 , 所 以 对 回 答 需 要 有 两 行 处 理 代 码 。 现 在 来 解 释 在 <strong>Prolog</strong> 中 这 些 是 怎 么 回 事 儿 还<br />

太 早 了 , 我 们 会 在 以 后 再 说 。 还 要 说 一 下 , 如 果 没 有 缺 省 回 答 , 只 需 要 用 空 字 符 串 就 行 了 。<br />

用 户 用 其 它 方 法 关 闭 窗 口 时 : 回 车 与 点 击 OK 效 果 相 同 , 按 ESC 键 与 点 击 Cancel 效 果 相 同 。<br />

这 是 与 ask/2、ask/3 的 处 理 不 同 的 。<br />

输 入 字 符 串 的 另 一 个 方 法 是 listSelect/5。 当 回 答 是 数 量 有 限 的 若 干 选 择 ( 比 如 用 户 必 须<br />

要 输 入 其 所 在 国 ) 时 , 可 以 使 用 这 个 过 程 。listSelect/5 会 向 用 户 显 示 一 个 可 选 择 回 答 的 表 ,<br />

用 户 在 其 中 选 择 一 项 。 使 用 这 个 过 程 , 可 以 输 入 标 题 、 供 选 择 的 表 和 当 前 选 项 的 索 引 值 。 结<br />

果 放 在 第 四 个 和 第 五 个 参 数 中 返 回 , 这 些 都 是 变 量 。 看 一 下 这 些 代 码 , 把 它 在 程 序 中 与 菜 单<br />

选 项 Listselect 相 关 联 :<br />

predicates<br />

onMessagesListselect : window::menuItemListener.<br />

clauses<br />

onMessagesListselect(_Source, _MenuTag) :‐<br />

b_true = listSelect("Title", ["Choice1", "Choice2", "Choice3"],0,SelectedString, SelectedIndex),<br />

!,<br />

note("Your selection is ...", SelectedString),<br />

note(" ... with index ...",tostring(SelectedIndex)).<br />

onMessagesListselect(_Source, _MenuTag).<br />

listSelect/5 的 输 入 输 出 模 式 是 (i,i,i,o,o), 头 三 个 是 输 入 , 后 两 个 是 输 出 。 第 一 个 参 数 是<br />

输 入 的 窗 口 标 题 。 第 二 个 参 数 是 输 入 的 可 选 择 项 列 表 , 它 是 一 个 字 符 串 的 表 , 第 一 项 Choice1<br />

的 索 引 值 是 0, 后 面 依 次 是 1,2,3,…… 等 等 。 第 三 个 变 量 表 示 当 前 要 显 示 的 列 表 选 项 ,0<br />

表 示 在 窗 口 中 显 示 第 一 项 , 如 果 要 显 示 列 表 中 的 其 它 项 , 只 需 要 指 定 相 应 的 索 引 号 就 行 了 。<br />

如 果 不 需 要 提 供 当 前 选 项 , 索 引 号 用 -1。 变 量 SelectedString 和 SelectedIndex 用 于 接 收 用 户 输<br />

入 , 当 然 这 是 指 用 OK 键 结 束 时 。 如 果 是 Cancel 或 关 闭 钮 结 束 的 , 没 有 返 回 的 字 符 串 而 返 回<br />

的 索 引 号 为 -1。<br />

最 后 一 个 与 用 户 通 信 的 方 法 , 是 都 很 熟 悉 的 让 用 户 选 择 一 个 文 件 。 在 VIP 中 有 一 个 很 有<br />

22


用 的 getFileName/6 过 程 可 以 做 这 个 事 情 。 看 起 来 挺 复 杂 , 所 以 现 在 跳 过 去 也 可 以 。 下 面 给<br />

出 的 代 码 是 用 GetFileName 菜 单 项 触 发 的 :<br />

predicates<br />

onMessagesGetfilename : window::menuItemListener.<br />

clauses<br />

onMessagesGetfilename(_Source, _MenuTag) :‐<br />

SelectedFile = getFileName("*.txt",<br />

["Text Files", "*.txt", "<strong>Prolog</strong> Files","*.pro", "All Files", "*.*"],<br />

"Title: Please select a file",<br />

[],<br />

" ",<br />

_SelecFiles), !,<br />

note("You selected File ...", SelectedFile).<br />

onMessagesGetfilename(_Source, _MenuTag) :‐<br />

note("You pressed the Cancel button").<br />

getFileName/6 需 要 如 下 输 入 参 数 ( 为 了 便 于 阅 读 , 上 面 的 代 码 中 把 每 个 参 数 都 分 列 一<br />

行 )。 要 选 择 一 个 文 件 时 ,GetFileName 要 有 一 个 过 滤 器 来 确 定 要 显 示 哪 些 文 件 。 在<br />

MsWindows 中 大 多 数 时 间 是 用 文 件 的 扩 展 名 来 过 滤 的 。 第 一 个 变 量 中 , 我 们 指 定 了 打 开 窗<br />

口 时 要 显 示 以 txt 为 扩 展 名 的 那 些 文 件 。 第 二 个 参 数 是 成 对 字 符 串 的 一 个 表 , 每 对 字 符 串 由<br />

文 件 类 型 和 选 择 该 文 件 类 型 的 过 滤 器 构 成 。 如 : 文 本 文 件 一 般 使 用 的 扩 展 名 是 .txt, 所 以 第<br />

一 对 字 符 串 就 是 “Text Files”,“*.txt”。 在 这 个 表 中 要 规 定 用 户 可 以 选 择 的 各 种 文 件 类 型 , 当<br />

然 最 好 也 还 是 要 提 供 “All Files”,“*.*”。 这 个 表 之 后 , 可 以 指 定 窗 口 的 标 题 。 下 一 个 ( 第 四<br />

个 ) 参 数 是 指 定 窗 口 的 一 些 特 殊 属 性 的 表 , 上 例 中 是 个 空 表 [ ], 也 就 是 没 有 规 定 什 么 特 殊<br />

的 属 性 。 第 五 个 参 数 是 字 符 串 , 是 窗 口 打 开 时 要 使 用 的 文 件 夹 名 。 例 子 中 是 空 串 , 它 意 味 着<br />

窗 口 打 开 时 由 Windows 决 定 要 显 示 的 文 件 夹 , 很 多 情 况 下 会 是 “ 我 的 文 档 ” 这 个 文 件 夹 。<br />

最 后 一 个 参 数 是 _SelecFiles , 它 是 getFileName/6 返 回 的 所 选 文 件 的 表 , 前 面 下 划 线 的 意 思 是<br />

我 们 不 打 算 使 用 这 个 参 数 。getFileName/6 作 为 一 个 函 数 还 会 返 回 所 选 文 件 名 , 我 们 把 这 个 文<br />

件 名 赋 给 了 变 量 SelectedFiles。 通 常 用 户 会 点 击 OK 做 结 束 。 有 时 用 户 会 用 Cancel, 第 二 个 子<br />

句 就 是 用 于 这 种 情 况 的 。<br />

小 结<br />

本 章 学 习 了 与 用 户 通 信 的 一 些 简 单 方 法 。 我 们 改 变 了 任 务 菜 单 并 使 用 了 弹 出 式 窗 口 。 同<br />

时 , 也 进 一 步 熟 悉 了 CodeExpert 及 其 使 用 。 与 用 户 通 信 的 方 法 很 多 , 后 续 章 节 中 还 要 介 绍 ,<br />

比 如 第 7 章 中 的 定 制 表 格 和 其 它 的 对 话 框 等 。<br />

目 前 为 止 , 想 必 读 者 已 经 可 以 顺 利 地 完 成 以 下 工 作 :<br />

• 创 建 一 个 新 的 工 程<br />

• 在 任 务 菜 单 中 创 建 一 个 新 项<br />

• 在 任 务 菜 单 中 使 能 某 个 项<br />

• 创 建 一 个 表 格 或 对 话 框 并 在 其 中 添 加 控 件<br />

• 借 助 CodeExpert 输 入 代 码<br />

下 一 章 我 们 要 更 细 些 看 看 集 成 开 发 环 境 IDE, 更 进 一 步 了 解 它 的 功 用 。<br />

23


第 4 章 细 观 IDE 4<br />

集 成 开 发 环 境 IDE 是 创 建 <strong>Prolog</strong> 程 序 很 好 很 有 用 的 一 个 工 具 。 本 章 我 们 要 浏 览 一 下 IDE<br />

的 方 方 面 面 , 以 有 一 个 概 貌 上 的 了 解 。<br />

4.1 IDE 概 述<br />

IDE 用 于 创 建 、 开 发 和 维 护 我 们 的 <strong>Visual</strong> <strong>Prolog</strong> 工 程 。 简 要 地 说 , 在 工 程 的 生 命 周 期 内<br />

如 下 任 务 需 要 使 用 IDE:<br />

• 创 建 工 程 是 在 IDE 中 创 建 的 。 创 建 时 可 以 选 择 重 要 的 工 程 特 性 , 如 工 程 是 可 执 行<br />

文 件 还 是 一 个 DLL, 是 使 用 GUI 还 是 基 于 文 本 模 式 等 等 。<br />

• 构 造 工 程 是 在 IDE 中 构 造 的 , 也 就 是 在 IDE 中 编 译 和 链 接 的 。 编 译 是 把 <strong>Prolog</strong> 代<br />

码 转 换 成 机 器 代 码 以 便 处 理 器 能 够 执 行 , 链 接 则 是 把 程 序 的 各 个 部 分 结 合 在 一 起 。<br />

• 浏 览 IDE 和 编 译 器 收 集 工 程 相 关 信 息 , 这 些 信 息 以 多 种 方 式 提 供 了 快 速 定 位 实 体<br />

等 一 系 列 功 能 。<br />

• 开 发 在 开 发 维 护 过 程 中 ,IDE 用 于 工 程 中 增 减 源 文 件 和 GUI 实 体 以 及 编 辑 等 项 工<br />

作<br />

• 调 试 IDE 还 用 于 工 程 的 调 试 。 调 试 用 来 跟 踪 程 序 的 执 行 , 采 集 程 序 运 行 状 态 , 它<br />

能 帮 助 我 们 发 现 和 修 改 错 误 , 保 证 程 序 的 正 确 运 行 。<br />

我 们 会 依 次 来 了 解 上 述 内 容 。 在 开 始 还 要 时 不 时 重 复 地 说 一 下 以 前 已 经 介 绍 过 的 内 容 ,<br />

以 保 持 上 下 文 的 完 整 和 可 读 性 。 我 们 要 用 一 个 工 程 来 做 例 子 加 以 说 明 , 读 者 应 该 创 建 这 个 工<br />

程 并 随 时 使 用 它 , 没 有 比 在 干 中 学 更 好 的 方 法 了 。<br />

创 建<br />

在 菜 单 中 选 择 Project‐>New 创 建 一 个 工 程 。 其 响 应 是 一 个 对 话 窗 口 , 其 中 有 工 程 的 各 种<br />

属 性 , 我 们 应 该 已 经 熟 悉 了 。 我 们 的 例 子 这 样 选 :<br />

• 工 程 名 是 ch04p01<br />

• 工 程 名 同 时 也 用 于 生 成 的 目 标 名<br />

• 目 标 是 一 个 exe 文 件 , 这 样 目 标 名 就 是 ch04p01.exe<br />

• 目 标 是 GUI 程 序 , 也 就 是 说 程 序 使 用 图 形 界 面 接 口 。<br />

Base Directory 是 工 程 的 根 目 录 , 选 一 个 用 着 方 便 的 地 方 就 行 。<br />

新 的 工 程 会 创 建 在 根 目 录 下 的 一 个 子 目 录 里 。 缺 省 时 这 个 子 目 录 的 名 字 与 工 程 名 相 同 。<br />

还 和 以 前 一 样 , 现 在 可 以 不 去 管 其 它 那 些 标 签 和 选 项 。<br />

构 造<br />

在 做 修 改 之 前 , 需 要 构 造 ( 也 就 是 编 译 链 接 ) 工 程 。 在 Build 菜 单 中 , 可 以 看 到 对 工 程<br />

的 构 造 、 编 译 和 执 行 命 令 。 如 果 选 Execute, 则 首 先 会 构 造 工 程 , 这 样 才 可 以 保 证 执 行 的 是<br />

最 新 版 本 。 我 们 就 选 Execute。 如 果 使 用 的 是 未 注 册 版 本 , 会 出 现 一 个 窗 口 提 示 说 还 未 注 册 。<br />

建 议 注 册 , 但 也 可 以 选 择 “ 继 续 试 用 ”。 消 息 窗 口 中 IDE 会 显 示 编 译 了 哪 些 文 件 等 , 底 部 还<br />

有 一 个 状 态 条 显 示 进 程 。 构 造 成 功 后 , 创 建 的 程 序 就 会 执 行 。 结 果 , 我 们 可 以 看 到 一 个 什 么<br />

也 做 不 了 的 图 形 界 面 程 序 的 小 窗 口 。 它 看 起 来 与 IDE 本 身 相 似 , 这 当 然 不 是 偶 然 的 事 儿 , 因<br />

为 IDE 本 身 就 是 一 个 <strong>Visual</strong> <strong>Prolog</strong> 程 序 。 后 面 我 们 还 会 看 到 , 如 果 在 构 造 过 程 中 发 现 错 误 了<br />

会 怎 么 样 。<br />

浏 览<br />

现 在 , 稍 微 仔 细 地 来 探 究 一 下 工 程 窗 口 中 的 工 程 树 。 工 程 树 是 按 Windows 中 资 源 管 理<br />

器 的 形 式 显 示 的 。 它 是 一 个 标 准 的 Windows 树 控 件 , 所 以 我 们 已 经 熟 悉 它 的 使 用 方 法 。 可<br />

以 按 资 源 管 理 器 中 浏 览 文 件 夹 的 方 式 来 浏 览 工 程 树 。 我 们 来 看 看 工 程 树 中 的 内 容 。<br />

工 程 树 的 外 观 如 图 4.1 示 。<br />

4<br />

本 章 许 多 内 容 取 材 于 Thomas Linder Puls 的 the tutorial Environment Overview。<br />

24


图 4.1<br />

工 程 树<br />

树 结 构 是 由 各 种 文 件 夹 构 成 的 , 我 们 主 要 看 一 下 做 为 节 点 的 各 文 件 夹 。 最 上 层 的 名 为<br />

ch04p01 的 节 点 , 表 示 工 程 本 身 和 工 程 目 录 , 它 的 名 字 是 我 们 在 创 建 工 程 对 话 框 中 给 起 的 。<br />

下 面 紧 接 着 的 是 逻 辑 节 点 ( 文 件 夹 )$(ProDir), 这 个 目 录 中 含 有 来 自 <strong>Visual</strong> <strong>Prolog</strong> 系 统 的 库<br />

和 库 代 码 , 编 译 链 接 时 需 要 用 到 它 们 。 在 工 程 树 中 这 个 文 件 夹 显 示 为 工 程 的 一 个 子 目 录 , 但<br />

实 际 上 它 在 磁 盘 另 外 某 个 地 方 , 所 以 我 们 称 它 为 逻 辑 节 点 。<br />

$(ProDir) 之 下 是 名 为 TaskWindow 的 目 录 , 这 是 工 程 目 录 的 一 个 真 实 的 子 目 录 。 它 包 含<br />

了 生 成 程 序 的 任 务 窗 口 及 其 中 的 菜 单 、 工 具 条 和 about 对 话 框 所 需 的 全 部 代 码 。 任 务 窗 口 是<br />

程 序 的 主 窗 口 , 程 序 一 启 动 它 就 会 出 现 。 由 于 这 个 窗 口 在 MsWindows 下 的 显 示 有 一 些 传 统<br />

规 则 , 所 以 IDE 不 用 用 户 指 明 就 可 以 为 它 生 成 部 分 代 码 。<br />

最 后 , 还 有 一 些 名 为 main… 和 resourceidentifiers.i 的 文 件 , 后 者 这 里 先 不 说 。 其 它 那 些<br />

文 件 将 含 有 我 们 的 程 序 。 当 然 , 可 能 加 入 的 文 件 会 越 来 越 多 , 但 这 些 文 件 开 始 时 就 有 。 应 该<br />

知 道 , 我 们 的 程 序 是 分 散 在 若 干 文 件 中 的 , 每 个 文 件 含 有 程 序 的 一 部 分 。 这 是 由 于 <strong>Visual</strong><br />

<strong>Prolog</strong> 是 一 个 面 向 对 象 编 程 语 言 的 缘 故 。 我 们 在 第 8 章 中 会 要 说 明 , 这 里 简 单 说 一 下 。 程 序<br />

的 声 明 ( 定 义 ) 在 文 件 main.cl 中 , 扩 展 名 .cl 表 示 class。 目 前 我 们 可 以 把 class 看 成 为 模 块 。<br />

程 序 的 <strong>Prolog</strong> 代 码 在 文 件 main.pro 中 。 有 两 种 方 法 访 问 main.pro 中 的 代 码 , 一 种 是 直 接 双<br />

击 工 程 树 中 这 个 文 件 名 , 会 在 编 辑 窗 口 中 打 开 显 示 代 码 , 试 试 找 到 下 面 的 代 码 :<br />

clauses<br />

run():‐<br />

TaskWindow = taskWindow::new(),<br />

TaskWindow:show().<br />

子 句 (clauses) 是 可 以 执 行 的 代 码 段 。 上 面 这 个 子 句 说 的 是 : 如 果 你 运 行 这 个 程 序 , 先 创<br />

建 一 个 新 的 任 务 窗 口 , 接 着 显 示 它 。 执 行 ch04p01 出 现 的 情 况 就 是 这 样 。<br />

访 问 特 定 代 码 的 另 一 个 办 法 是 在 这 个 文 件 名 上 单 击 , 窗 口 的 右 半 部 会 显 示 出 该 文 件 中 现<br />

有 的 部 件 , 如 图 4.2 示 。<br />

在 右 边 会 看 到 predicates、constants 和 goal 等 字 样 , 先 不 用 去 管 它 们 是 什 么 意 思 。Main.pro<br />

文 件 的 内 容 如 一 树 状 结 构 显 示 出 来 , 它 表 明 该 文 件 中 含 有 一 个 名 为 main 的 类 , 这 个 类 中 又<br />

包 含 有 两 个 名 为 classinfo/2 和 run/0 的 谓 词 。 这 些 谓 词 可 以 看 成 为 过 程 。 文 件 中 还 有 两 个 常<br />

数 className 和 classVersion, 还 有 一 个 叫 goal 的 东 西 。 在 某 个 条 目 上 双 击 时 ,IDE 就 会 打 开<br />

编 辑 器 , 光 标 移 到 与 其 相 关 的 代 码 段 上 。<br />

25


图 4.2<br />

工 程 树 和 main.pro 的 内 容<br />

窗 口 左 边 可 以 看 到 带 有 扩 展 名 的 各 个 文 件 。 扩 展 名 表 示 了 文 件 的 类 型 。 要 了 解 这 些 类 型 ,<br />

必 须 了 解 面 向 对 象 的 编 程 , 我 们 在 第 8 章 中 要 介 绍 , 这 里 先 简 单 说 一 下 。 面 向 对 象 编 程 (OOP)<br />

的 中 心 思 想 , 是 说 程 序 是 由 一 些 称 为 对 象 的 部 件 构 成 的 。 对 象 由 所 谓 的 “ 类 ” 来 创 建 , 而 类<br />

则 是 对 某 种 类 型 对 象 的 一 般 描 述 。 同 时 , 类 还 是 对 象 可 以 执 行 的 动 作 的 一 个 集 合 。 当 程 序 运<br />

行 时 , 对 象 被 创 建 并 执 行 动 作 。 这 些 动 作 产 生 编 程 者 期 望 的 结 果 。 拿 学 生 数 据 库 做 为 一 个 例<br />

子 , 程 序 可 以 有 一 个 main 类 , 用 于 启 动 程 序 来 创 建 带 有 主 菜 单 的 任 务 窗 口 。 菜 单 中 , 要 有<br />

创 建 学 生 记 录 项 及 搜 索 记 录 项 的 选 项 。 要 创 建 学 生 记 录 , 可 以 是 一 个 名 为 student 的 类 , 用<br />

来 输 入 和 存 储 学 生 的 数 据 。 点 选 菜 单 项 创 建 一 个 新 的 学 生 记 录 时 ,student 类 就 会 被 调 用 来<br />

创 建 一 个 新 的 对 象 , 这 个 新 对 象 会 接 收 来 自 用 户 输 入 的 数 据 并 把 它 保 存 在 数 据 库 中 。<br />

对 这 些 程 序 结 构 , 需 要 以 下 几 种 类 型 的 文 件 :<br />

• 类 : 过 程 与 数 据 的 集 合<br />

• 界 面 : 说 明 类 ( 或 对 象 ) 如 何 调 用 其 它 的 对 象 执 行 某 个 动 作<br />

• <strong>Prolog</strong> 文 件 : 存 放 程 序 的 <strong>Prolog</strong> 代 码<br />

• 包 : 所 属 文 件 的 集 合 。 可 以 把 它 看 作 是 MsWindows 中 的 一 个 文 件 夹 。<br />

在 VIP 中 , 文 件 类 型 是 由 扩 展 名 说 明 的 ,<strong>Visual</strong> <strong>Prolog</strong> 中 的 约 定 如 下 :<br />

• *.cl 文 件 包 含 有 类 的 声 明<br />

• *.i 文 件 包 含 有 接 口<br />

• *.pro 文 件 包 含 有 类 的 <strong>Prolog</strong> 代 码 , 也 称 为 类 的 实 现<br />

• *.pack 文 件 含 有 #include 指 令 , 指 出 了 哪 些 文 件 应 该 包 含 在 这 个 包 中<br />

• *.pack 文 件 中 的 #include 指 令 指 向 *.pro 及 *.ph 文 件<br />

• *.ph 文 件 是 包 头 文 件 , 也 含 有 #include 指 令<br />

• *.ph 文 件 中 的 #include 指 令 指 向 其 它 的 *.ph 文 件 及 *.i 和 *.cl 文 件<br />

• 只 有 在 工 程 被 编 译 / 构 建 后 , 包 含 的 文 件 才 会 显 示 在 工 程 树 中 。<br />

在 左 边 看 到 的 扩 展 名 就 是 这 些 。 我 们 在 第 8 章 及 以 后 的 章 节 中 还 要 介 绍 这 些 概 念 , 我 们<br />

需 要 了 解 面 向 对 象 来 理 解 它 们 。<br />

4.2 工 程 树 中 的 任 务 窗 口<br />

现 在 来 展 开 TaskWindow 节 点 / 文 件 夹 。 我 们 会 看 到 有 个 名 字 叫 Toolbars 的 文 件 夹 和<br />

AboutDialog 及 TaskWindow 等 几 个 文 件 , 如 图 4.3 示 。 我 们 前 面 说 过 , 这 些 文 件 有 一 种 共 同<br />

的 模 式 。 先 来 看 看 AboutDialog 文 件 。 我 们 知 道 , 对 话 框 是 表 格 的 一 种 形 式 , 用 来 与 用 户 通<br />

信 。About 对 话 框 是 一 个 小 窗 口 , 在 程 序 中 点 击 主 菜 单 的 Help‐>About 选 项 时 就 会 弹 出 。IDE<br />

知 道 这 个 窗 口 , 它 的 内 容 则 包 含 在 四 个 文 件 中 。<br />

在 Aboutdialog.dlg 文 件 双 击 , 我 们 可 以 编 辑 About 对 话 框 。 双 击 这 个 文 件 , 会 打 开 一 个<br />

与 我 们 在 第 2 章 中 看 到 过 的 表 格 编 辑 器 相 似 的 图 形 编 辑 器 。 我 们 可 以 修 改 IDE 在 其 中 的 缺 省<br />

设 置 的 内 容 。AboutDialog.cl 文 件 中 包 含 有 声 明 。 程 序 中 的 所 有 东 西 都 必 须 先 声 明 , 而 About<br />

对 话 框 是 一 个 类 ( 可 以 视 为 一 个 模 块 ), 类 的 声 明 都 是 在 .cl 文 件 中 的 。AboutDialog.i 是 接 口<br />

26


文 件 , 我 们 在 第 8 章 中 细 说 。 最 后 是 About 对 话 框 的 代 码 文 件 AboutDialog.pro。 这 个 文 件 有<br />

两 部 分 , 一 部 分 我 们 可 以 随 意 输 入 、 删 除 和 改 变 代 码 , 另 一 部 分 则 是 由 IDE 来 维 护 的 。 每 次<br />

修 改 后 构 建 工 程 时 , 就 会 依 照 对 AboutDialog.dlg 的 编 辑 来 产 生 代 码 。 这 种 方 式 在 IDE 中 反 复<br />

使 用 。 看 看 名 为 TaskWindow 的 文 件 , 可 以 猜 到 :<br />

• TaskWindow.cl 文 件 中 有 声 明<br />

• TaskWindow.pro 文 件 中 有 prolog 代 码<br />

• TaskWindow.win 文 件 是 一 个 可 编 辑 文 件 , 双 击 这 个 文 件 名 会 出 现 一 个 对 话 框 , 可 以<br />

用 来 改 变 窗 口 的 属 性 。 这 里 并 不 能 像 在 About 对 话 框 里 所 做 的 那 样 去 编 辑 它 , 因 为<br />

任 务 窗 口 是 由 若 干 项 目 如 任 务 菜 单 、About 对 话 框 、 工 程 图 标 等 等 组 成 的 。<br />

TaskMenu.mnu 文 件 中 包 含 有 程 序 的 主 菜 单 , 我 们 在 前 面 遇 见 过 了 。 工 程 图 标 是 显 示 在<br />

程 序 窗 口 左 上 角 的 一 个 小 图 片 。<br />

图 4.3<br />

TaskWindow 文 件 夹 的 内 容<br />

文 件 的 扩 展 名 对 IDE 来 说 很 重 要 。 它 表 明 了 文 件 的 类 型 , 当 我 们 双 击 一 个 文 件 名 时 能 帮<br />

助 IDE 在 合 适 的 编 辑 器 中 打 开 文 件 。 扩 展 名 有 若 干 种 :<br />

• *.dlg 文 件 含 有 对 话 框<br />

• *.frm 文 件 含 有 表 格<br />

• *.win 文 件 含 有 窗 口 ( 任 务 窗 口 或 是 常 规 的 PFC GUI 窗 口 )<br />

• *.mnu 文 件 含 有 菜 单<br />

• *.ico 文 件 含 有 图 标<br />

以 后 还 会 看 到 :<br />

• *.ctl 文 件 含 有 控 件<br />

• *.tb 文 件 含 有 工 具 条<br />

• *.cur 文 件 含 有 光 标<br />

• *.bmp 文 件 含 有 位 图<br />

• *.lib 文 件 是 库 文 件 。<br />

27


所 有 这 些 类 型 的 文 件 都 是 由 不 同 的 编 辑 器 处 理 的 ,IDE 能 识 别 文 件 类 型 并 选 择 合 适 的 编<br />

辑 器 。 这 就 是 选 择 正 确 的 扩 展 名 很 重 要 的 原 因 , 千 万 不 要 去 改 变 已 有 的 扩 展 名 。<br />

如 果 在 文 件 名 上 点 右 键 , 会 出 现 一 个 菜 单 , 其 中 有 适 合 当 前 节 点 的 相 关 命 令 。 如 果 在 节<br />

点 在 双 击 , 相 关 的 实 体 就 会 打 开 适 当 的 编 辑 器 。 所 有 代 码 都 是 在 文 本 编 辑 器 中 编 辑 的 , 但 窗<br />

口 资 源 如 对 话 框 和 菜 单 等 是 在 图 形 编 辑 器 中 编 辑 的 。 看 一 个 例 子 , 打 开 toolbars 文 件 夹 , 看<br />

看 前 三 项 :ProjectToolbar.cl、ProjectToolbar.pro 和 ProjectToolbar.tb。 按 前 面 说 的 , 我 们 可 以<br />

知 道 .tb 文 件 是 一 个 工 具 条 。 在 ProjectToolbar.tb 文 件 名 上 点 右 键 , 会 出 现 有 四 个 选 项 的 窗 口 :<br />

• Edit: 这 会 打 开 图 形 编 辑 器 来 编 辑 工 具 条 , 它 相 当 于 在 文 件 名 上 双 击<br />

• Attributes: 打 开 一 个 对 话 框 , 可 以 改 变 工 具 条 的 属 性 , 例 如 , 可 以 改 变 工 具 条 的<br />

位 置<br />

• Delete: 哇 噢 ! 要 当 心 , 这 是 删 除 工 具 条<br />

• Properties: 显 示 一 些 工 具 条 的 属 性 。<br />

此 时 ,CodeExpert 不 可 用 , 因 为 它 是 与 任 务 窗 口 配 合 使 用 的 。<br />

4.3 在 工 程 树 中 创 建 一 个 新 条 目<br />

5<br />

工 程 树 展 现 了 工 程 的 目 录 和 它 们 里 面 的 条 目 。 要 在 工 程 树 中 加 一 个 新 条 目 , 可 以 在 任 务<br />

菜 单 中 选 File/New。 但 一 定 要 记 住 , 每 个 条 目 都 是 存 放 在 一 个 文 件 中 的 , 所 以 实 际 上 创 建 的<br />

是 一 个 文 件 。 在 如 <strong>Visual</strong> <strong>Prolog</strong> 这 样 的 面 向 对 象 环 境 中 , 会 创 建 很 多 文 件 。 由 此 我 们 应 该 明<br />

白 , 工 程 树 是 文 件 夹 和 文 件 的 逻 辑 层 次 的 显 示 , 而 属 于 一 组 的 文 件 最 好 是 放 在 相 同 的 文 件 夹<br />

中 。 可 以 把 包 看 成 为 MsWindows 中 的 一 个 文 件 夹 。<br />

我 们 经 常 会 需 要 创 建 文 件 夹 来 存 放 工 程 中 的 某 个 部 分 。 这 里 我 们 在 ch04p01 工 程 中 创 建<br />

一 个 包 , 放 一 个 表 格 在 里 面 。 这 个 包 的 名 字 我 们 叫 它 pack01, 里 面 的 表 格 名 是 query。<br />

第 一 步 : 创 建 一 个 包 (package)<br />

在 工 程 树 中 添 加 一 个 包 ch04p01/pack01, 采 用 以 下 步 骤 :<br />

• 在 工 程 树 中 点 击 根 目 录 ch04p01 使 其 高 亮<br />

• 在 任 务 菜 单 中 点 击 File/New, 这 时 会 打 开 创 建 工 程 项 的 对 话 框 ( 参 见 图 4.4)<br />

• 在 左 边 点 击 Package<br />

• 名 称 中 输 入 pack01, 父 目 录 那 一 栏 空 着 它 , 如 图 4.4 示<br />

• 点 击 Create 按 钮 创 建 一 个 新 的 包<br />

注 意 一 下 包 存 放 的 位 置 。 如 果 想 把 包 放 在 工 程 的 根 目 录 ( 工 程 的 主 文 件 夹 ) 里 , 应 该 让<br />

其 父 目 录 为 空 , 如 图 4.4 示 。 开 始 时 就 让 父 目 录 为 空 的 办 法 , 是 在 点 击 File/New in existing<br />

package 之 前 先 选 择 工 程 树 的 根 目 录 , 如 图 2.1 示 。 点 击 File/New 之 后 也 可 以 选 择 合 适 的 目<br />

录 , 这 时 可 以 点 击 创 建 工 程 项 对 话 框 右 边 的 Browse 按 钮 , 显 示 计 算 机 中 的 目 录 结 构 , 再 选<br />

择 需 要 的 目 录 。 但 是 一 定 要 小 心 , 最 好 是 选 择 工 程 中 子 目 录 的 一 个 目 录 , 除 非 有 其 它 特 别 的<br />

理 由 不 这 样 做 。<br />

第 二 步 : 创 建 一 个 包 并 将 其 存 放 在 pack01 中 。<br />

现 在 我 们 已 经 创 建 了 包 pack01, 我 们 再 来 把 名 为 Query 的 表 格 放 到 里 面 去 。<br />

在 2.1 章 节 中 我 们 已 经 做 过 这 个 , 步 骤 应 该 是 熟 悉 的 。 为 了 完 整 起 见 , 这 里 重 复 一 下 :<br />

• 点 击 工 程 树 中 新 创 建 的 pack01 包 ( 不 是 pack01.pack!)<br />

• 在 任 务 菜 单 上 点 击 File/New in existing package, 打 开 创 建 工 程 项 的 对 话 框<br />

• 点 选 左 边 的 Form<br />

• 在 名 字 一 栏 中 输 入 query<br />

• 此 时 , 必 须 在 两 个 单 选 按 钮 NewPackage 和 ExistingPackage 做 选 择 。 如 果 选 前 者 ,<br />

表 格 的 文 件 会 放 在 目 录 pack01 中 的 一 个 子 目 录 里 ; 如 果 选 后 者 , 文 件 就 会 直 接 放<br />

在 目 录 pack01 中 。 这 里 我 们 选 已 有 包 (ExistingPackage)<br />

• 还 需 要 确 定 表 格 是 Public( 公 用 ) 还 是 Private( 私 有 )。 如 果 还 不 明 白 这 是 怎 么 回<br />

事 , 选 Public 好 了 。<br />

• 点 击 Create 按 钮 ,IDE 会 在 表 格 编 辑 器 中 显 示 出 一 个 新 的 表 格 。<br />

5<br />

下 面 的 章 节 部 分 取 材 于 Eduardo Costa 的 <strong>Prolog</strong> for Tyros 中 的 第 4 章 。<br />

28


图 4.4<br />

创 建 一 个 新 包<br />

IDE 打 开 表 格 编 辑 器 显 示 一 个 新 表 格 ( 如 图 4.5), 我 们 在 里 边 加 上 一 个 编 辑 栏 和 一 个 按<br />

钮 。 编 辑 栏 可 以 让 程 序 的 用 户 输 入 文 本 、 数 字 或 其 它 输 入 值 或 改 变 一 个 值 ; 按 钮 则 可 以 在 用<br />

户 点 击 它 时 产 生 事 件 , 引 发 程 序 执 行 某 个 动 作 。<br />

图 4.5<br />

表 格 编 辑 器 , 带 有 控 件 和 布 局 工 具 条 和 属 性 窗 口<br />

按 以 下 步 骤 加 编 辑 栏 : 在 控 件 工 具 条 里 , 点 选 “ 编 辑 栏 ” 图 标 , 第 一 行 最 右 边 带 有 “edit”<br />

字 样 的 那 个 。 然 后 在 表 格 模 板 中 要 放 置 编 辑 栏 的 地 方 点 左 键 ,IDE 就 会 安 放 一 个 编 辑 栏 在 那<br />

里 。 可 以 按 需 要 调 整 它 的 大 小 。 再 加 一 个 按 钮 , 在 控 件 条 点 选 “ 按 钮 ” 图 标 , 第 一 行 最 左 边<br />

带 有 OK 字 样 的 那 个 , 把 它 放 在 表 格 需 要 的 位 置 上 。<br />

点 击 表 格 模 板 中 的 项 时 , 属 性 列 表 中 就 会 显 示 出 它 的 属 性 及 当 前 值 。 左 栏 显 示 的 属 性 名<br />

称 , 右 栏 是 值 。 例 如 , 在 刚 才 加 入 的 按 钮 上 点 一 下 , 就 会 列 出 它 的 所 有 属 性 , 见 图 4.6。 第<br />

29


三 项 属 性 是 Text, 其 缺 省 值 是 “PushButton”。 可 以 改 变 它 , 只 要 在 相 应 的 属 性 栏 目 上 点 击<br />

再 输 入 期 望 的 内 容 就 可 以 了 。 还 有 一 项 需 要 注 意 一 下 , 第 一 项 ,Name( 名 字 )。 在 这 一 栏 可<br />

以 看 到 程 序 中 涉 及 这 个 按 钮 时 所 用 的 名 字 。 这 个 按 钮 的 缺 省 名 字 是 pushbutton_ctl, 因 为 它<br />

是 一 个 名 为 PushButton 的 控 件 。 当 然 也 可 以 更 改 这 个 名 字 , 以 使 在 程 序 中 更 易 理 解 。 可 以<br />

把 它 改 成 与 Text 的 一 样 。 不 过 不 要 忘 了 后 面 加 上 ctl, 以 表 示 这 是 个 控 件 。 还 可 以 再 看 看 表<br />

中 的 其 它 项 。 后 面 , 等 程 序 做 完 了 还 要 再 回 到 query.frm 更 改 某 些 属 性 , 看 看 会 出 现 什 么 情<br />

况 , 比 如 把 Visible 属 性 改 为 false。<br />

要 把 表 格 放 到 一 个 包 中 , 一 定 要 在 点 击 File/New 之 前 先 选 好 这 个 包 文 件 夹 。 包 和 表 格<br />

的 名 字 , 要 起 得 有 意 义 。 比 如 , 包 中 要 放 图 表 , 给 包 起 名 为 plots, 而 要 放 计 算 机 图 形 时 给<br />

包 起 名 为 CanvasFolder, 要 放 查 询 表 给 包 起 名 queryForms, 要 放 文 档 表 包 名 叫 portfolio 等 等 。<br />

除 非 是 做 试 验 或 真 是 要 排 序 , 不 要 起 诸 如 test 或 folder01 这 样 的 名 字 。<br />

可 以 随 时 到 表 格 窗 口 中 对 表 格 进 行 编 辑 。 在 工 程 树 中 右 键 点 击 < 文 件 名 >.frm 并 在 出 现 的<br />

弹 出 菜 单 中 选 Edit 就 可 以 打 开 编 辑 窗 口 。 也 可 以 直 接 在 表 格 文 件 名 如 query.frm 上 双 击 。 要<br />

记 住 , 带 扩 展 名 .frm 的 文 件 是 可 以 更 改 表 格 的 地 方 。 而 带 扩 展 名 .pro 的 文 件 含 有 代 码 , 访 问<br />

这 类 文 件 中 的 代 码 有 两 种 方 法 。 对 任 务 窗 口 是 用 Dialog and WindowExpert, 其 它 项 如 表 格 的<br />

代 码 的 访 问 是 通 过 属 性 窗 口 中 的 事 件 表 ( 在 4.5 节 中 还 要 介 绍 这 些 内 容 )。 还 可 以 直 接 在 .pro<br />

文 件 名 上 双 击 或 击 右 键 后 选 edit。 不 管 用 什 么 方 法 , 一 定 要 清 楚 自 己 在 做 什 么 。<br />

图 4.6<br />

PushButton_ctl 的 属 性<br />

4.4 CodeExpert 和 Dialog and WindowExpert<br />

正 如 名 字 说 的 那 样 ,CodeExpert 用 于 插 入 任 务 窗 口 的 代 码 。 要 访 问 CodeExpert, 需 要 在<br />

TaskWindow.win 上 点 右 键 , 在 出 现 的 浮 动 菜 单 中 选 CodeExpert。CodeExpert 会 打 开 Dialog and<br />

WindowExpert 窗 口 , 它 显 示 一 个 树 形 结 构 , 如 图 4.7 示 。 这 个 专 家 系 统 在 标 题 行 上 括 号 中<br />

标 示 出 正 在 显 示 哪 个 窗 口 , 下 面 的 内 容 是 该 窗 口 中 能 够 发 生 的 事 件 。 图 4.7 中 显 示 的 是 任 务<br />

窗 口 的 事 件 。 我 们 可 以 自 由 选 择 要 插 入 或 更 改 代 码 的 事 件 。<br />

Dialog and WindowExpert 其 实 是 个 树 结 构 。 树 是 由 分 支 与 叶 构 成 的 , 而 叶 是 分 支 的 末 端 。<br />

在 一 个 叶 上 可 以 操 控 程 序 代 码 。 要 浏 览 树 , 到 达 需 要 插 入 代 码 的 点 , 可 以 在 树 的 合 适 分 支 上<br />

30


击 左 键 , 直 到 需 要 的 叶 。Dialog and WindowExpert 使 用 方 式 很 简 单 , 找 到 树 中 需 要 的 叶 , 点<br />

击 它 , 然 后 有 三 种 可 能 :<br />

• 这 个 叶 上 还 没 有 代 码 。 点 击 按 钮 , 专 家 系 统 插 入 一 些 标 准 代 码 , 也 就 是 原 型<br />

代 码 。<br />

• 已 经 插 入 过 代 码 。 点 击 可 以 删 除 代 码 。<br />

• 对 已 经 有 的 代 码 , 在 叶 上 双 击 可 以 访 问 代 码 。<br />

要 想 让 Dialog and WindowExpert 在 叶 上 添 加 标 准 代 码 , 点 击 叶 , 再 点 击 出 现 的 对 话 框<br />

底 部 的 Add, 然 后 再 在 叶 上 双 击 , 就 可 以 看 到 代 码 了 。<br />

图 4.7<br />

Dialog and Window Expert<br />

如 果 想 在 CodeExpert 中 给 TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new 添<br />

加 下 面 的 内 容 ( 我 们 在 2.3 章 节 中 做 过 ):<br />

clauses<br />

onFileNew(W, _MenuTag) :‐<br />

S= query::new(W), S:show().<br />

可 以 这 样 做 :<br />

• 进 入 工 程 树<br />

• 打 开 TaskWindow 文 件 夹<br />

• 在 Taskwindow.win 上 击 右 键<br />

• 在 出 现 的 菜 单 中 选 CodeExpert, 打 开 Dialog and Windows Expert 窗 口<br />

• 进 入 Dialog and Windows Expert 窗 口<br />

• 点 Menu<br />

• 点 TaskMenu<br />

• 点 id_file<br />

• 点 id_file_new<br />

31


• 点 击 Add 按 钮 生 成 原 型 代 码<br />

• 最 后 , 双 击 id_file_new‐>onFileNew。 编 辑 器 会 打 开 文 件 taskwindow.pro 并 显 示 插<br />

入 代 码 的 地 方<br />

• 加 入 需 要 的 代 码 :<br />

clauses<br />

onFileNew(Window, _MenuTag) :‐<br />

NewWindow= query::new(Window), NewWindow:show().<br />

在 图 4.7 中 可 以 注 意 到 , 标 题 条 下 面 有 三 个 选 项 , 用 于 规 定 Dialog and Window Expert<br />

要 显 示 哪 些 内 容 。 可 以 显 示 所 有 事 件 ( 这 是 缺 省 状 态 ), 也 可 以 选 Handled 或 Unhandled。<br />

若 选 Handled, 只 会 显 示 那 些 已 经 添 加 了 代 码 的 事 件 , 而 选 Unhandled 则 正 相 反 。 一 个 窗 口<br />

要 是 包 含 有 很 多 事 件 时 , 这 样 的 选 择 会 比 较 方 便 。 现 在 只 需 要 选 All 就 行 了 。<br />

第 2 章 中 我 们 说 过 ,CodeExpert 是 个 便 利 的 工 具 , 但 是 仍 然 需 要 小 心 。 这 里 把 前 面 说 的<br />

再 重 复 一 遍 , 因 为 它 很 重 要 。 当 要 删 除 由 Dialog and Window Expert 插 入 的 代 码 时 , 一 定 要<br />

做 好 所 有 保 存 工 作 。 例 如 , 要 删 除 onFileNew 的 代 码 , 我 们 在 Dialog and Window Expert 中<br />

打 开 文 件 TaskWindow.win, 找 到 id_file_new‐>onFileNew, 高 亮 显 示 它 并 点 击 delete 按 钮 。<br />

此 时 IDE 删 除 的 是 监 听 器 的 代 码 , 并 没 有 删 除 我 们 改 动 过 的 代 码 。 因 为 改 动 过 , 所 以 IDE 很<br />

难 精 准 地 确 定 要 删 除 什 么 。 还 有 一 个 原 因 , 是 IDE 可 能 会 删 除 我 们 打 算 以 后 再 用 或 在 其 它 的<br />

地 方 使 用 的 代 码 。 所 以 我 们 必 须 自 己 手 工 地 删 除 那 些 代 码 。 这 时 可 能 会 出 错 , 会 有 两 种 可 能 :<br />

1. 我 们 删 除 了 监 听 器 但 没 有 删 除 我 们 自 己 的 代 码 。 在 构 造 工 程 时 , 就 会 出 现 一 个 警 告 ,<br />

说 程 序 中 有 末 使 用 的 谓 词 , 并 指 向 我 们 的 代 码 。 它 没 有 被 使 用 , 因 为 没 有 监 听 器 来<br />

激 活 它 。 只 是 警 告 是 因 为 这 些 代 码 对 程 序 无 害 , 它 们 只 是 没 有 用 到 罢 了 。<br />

2. 我 们 删 除 了 自 己 的 代 码 但 是 没 有 删 除 监 听 器 。 构 造 工 程 时 , 会 出 现 错 误 , 说 程 序 中<br />

有 一 个 没 有 使 用 的 标 识 符 ( 如 变 量 )。 监 听 器 是 指 向 变 量 标 识 (onFileNew) 的 , 变<br />

量 标 识 需 要 对 点 击 File/New 做 出 反 应 。 但 是 我 们 已 经 删 除 了 这 个 标 识 , 这 样 一 来<br />

找 不 到 程 序 中 的 代 码 , 监 听 器 无 法 引 导 程 序 做 出 反 应 。 这 是 一 个 错 误 , 必 须 通 过 添<br />

加 需 要 的 代 码 或 借 助 IDE 删 除 监 听 器 来 修 正 它 。<br />

4.5 访 问 事 件 相 应 的 代 码<br />

我 们 可 以 用 CodeExpert 访 问 TaskWindow 的 代 码 。 其 它 代 码 的 访 问 可 以 经 由 编 辑 器 的 属<br />

性 窗 口 。 我 们 再 进 一 步 。 打 开 taskwindow 文 件 夹 并 选 中 aboutdialog.dlg 文 件 , 击 右 键 并 选<br />

Edit,IDE 会 打 开 编 辑 器 以 便 编 辑 About 对 话 框 。 属 性 窗 口 展 现 了 About 对 话 框 的 各 种 属 性 ,<br />

在 窗 口 的 底 部 有 两 个 标 签 , 一 个 是 Properties( 属 性 ) 一 个 是 Events( 事 件 )。 点 击 Events,<br />

窗 口 中 的 内 容 改 变 了 , 这 时 可 以 看 到 这 个 对 话 框 可 以 处 理 的 事 件 列 表 , 如 图 4.8 示 。<br />

在 这 个 表 中 , 我 们 可 以 看 到 打 开 About 对 话 框 时 所 能 发 生 的 事 件 。 来 看 CloseResponder。<br />

它 是 与 右 上 角 关 闭 按 钮 相 关 的 事 件 , 只 要 点 击 那 个 按 钮 , 程 序 就 会 知 道 CloseResponder 事 件<br />

发 生 了 并 以 关 闭 该 窗 口 做 为 响 应 。 但 是 , 还 可 以 为 这 个 事 件 增 加 其 它 的 动 作 。 我 们 来 加 一 个<br />

只 是 说 发 生 了 什 么 事 儿 的 短 信 。 为 了 添 加 代 码 , 先 点 选 CloseResponder, 值 域 框 中 有 一 个 小<br />

箭 头 , 点 击 它 就 会 列 出 CloseResponder 可 选 的 值 。 这 里 只 有 onClose。 选 中 它 并 双 击 ,IDE<br />

会 插 入 一 行 激 活 监 听 器 的 代 码 , 并 为 onClose 过 程 插 入 一 些 标 准 代 码 。IDE 会 打 开 代 码 编 辑<br />

器 并 把 光 标 放 在 插 入 代 码 的 地 方 , 其 代 码 如 下 :<br />

predicates<br />

onClose : frameDecoration::closeResponder.<br />

clauses<br />

onClose(_Source) = frameDecoration::defaultCloseHandling.<br />

把 子 句 部 分 改 为 :<br />

clauses<br />

onClose(_Source) = frameDecoration::defaultCloseHandling :‐<br />

vpiCommonDialogs::note("You hit the button. \n Closing the About window now").<br />

32


图 4.8<br />

About 对 话 框 属 性 窗 口 中 的 事 件 列 表<br />

这 样 一 来 , 点 击 关 闭 按 钮 时 程 序 在 关 闭 之 前 会 先 显 示 出 一 个 短 信 窗 口 , 说 刚 才 我 们 做 了<br />

什 么 , 接 下 来 会 怎 样 。 注 意 到 文 本 中 的 “\n” 了 吗 ? 它 的 作 用 是 使 其 后 字 符 串 另 起 一 行 。<br />

用 这 个 方 法 可 以 给 出 所 有 其 它 控 件 的 <strong>Prolog</strong> 代 码 。 我 们 再 来 给 OK 按 钮 加 点 儿 代 码 。 在<br />

表 格 中 选 OK 按 钮 , 然 后 在 属 性 窗 口 中 得 到 它 的 事 件 , 如 图 4.9 示 。<br />

图 4.9<br />

OK 按 钮 的 事 件 , 已 经 激 活 了 ClickResponder<br />

选 ClickResponder, 它 是 点 击 该 按 钮 时 的 响 应 。 选 onButtonOKClick( 也 是 唯 一 的 选 择 ),<br />

然 后 看 代 码 , 应 该 是 这 样 的 :<br />

33


predicates<br />

onButtonOkClick : button::clickResponder.<br />

clauses<br />

onButtonOkClick(_Source) = button::defaultAction.<br />

clauses<br />

把 子 句 改 成 :<br />

onButtonOkClick(_Source) = button::defaultAction :‐<br />

vpiCommonDialogs::note("OK, I'll close the AboutDialog").<br />

现 在 保 存 代 码 , 关 闭 其 它 内 容 , 构 造 并 执 行 程 序 。 点 Help 选 About, 打 开 的 差 不 多 是<br />

一 个 标 准 的 About 对 话 框 。 关 闭 它 时 , 会 看 到 一 个 短 信 。 可 以 观 察 到 , 用 关 闭 按 钮 和 OK 按<br />

钮 关 闭 About 窗 口 是 不 同 的 事 件 。<br />

实 际 当 中 可 能 不 会 给 About 对 话 框 添 加 什 么 代 码 。 不 过 我 们 前 面 这 个 About 对 话 框 的 例<br />

子 说 明 程 序 中 的 所 有 项 确 实 是 可 以 改 变 的 。 给 其 它 部 件 加 代 码 的 过 程 是 一 样 的 。 在 编 辑 器 中<br />

打 开 项 ( 对 话 框 、 表 格 、 工 具 条 等 ), 选 定 一 个 要 加 代 码 的 项 ( 按 钮 、 单 选 钮 什 么 的 ), 再 在<br />

属 性 窗 口 打 开 事 件 列 表 。 选 择 事 件 , 双 击 它 进 入 代 码 编 辑 器 , 改 变 插 入 的 标 准 代 码 , 搞 定 !<br />

听 起 来 很 容 易 啊 ! 确 实 不 难 。 但 插 入 些 什 么 代 码 呢 ? 那 是 另 一 回 事 情 了 。 下 一 章 我 们 来<br />

说 说 用 <strong>Prolog</strong> 编 程 序 。<br />

34


第 5 章 <strong>Prolog</strong> 基 础<br />

6<br />

本 章 中 我 们 要 学 习 <strong>Prolog</strong> 编 程 的 基 本 思 想 。 先 介 绍 一 点 理 论 , 后 面 再 创 建 一 个 实 际 的<br />

<strong>Prolog</strong> 程 序 并 运 行 它 。<br />

<strong>Visual</strong> <strong>Prolog</strong> 是 面 向 对 象 的 、 严 格 类 型 化 的 和 模 式 校 验 的 编 程 语 言 。 需 要 掌 握 所 有 这 些<br />

内 容 才 能 编 好 <strong>Visual</strong> <strong>Prolog</strong> 程 序 。 不 过 , 现 在 我 们 先 集 中 精 力 于 代 码 的 核 心 上 , 也 就 是 先 不<br />

考 虑 类 、 类 型 和 模 式 , 也 不 管 用 户 图 形 界 面 的 那 样 的 代 码 。<br />

可 以 只 用 纸 和 笔 来 学 习 <strong>Prolog</strong>, 不 过 尽 快 开 始 用 计 算 机 更 好 。 所 以 我 们 一 开 始 就 介 绍 了<br />

IDE。 在 接 下 来 的 这 一 部 分 , 我 们 在 IDE 中 用 PIE 来 做 工 程 。PIE 的 工 程 使 用 传 统 方 法 编 写 <strong>Prolog</strong><br />

程 序 , 差 不 多 和 <strong>Prolog</strong> 刚 开 始 时 使 用 的 方 法 ( 可 能 在 一 些 人 工 智 能 课 程 中 还 在 用 ) 一 样 。<br />

PIE 是 <strong>Prolog</strong> Interpretation Engine 的 缩 写 。 这 个 程 序 是 与 <strong>Visual</strong> <strong>Prolog</strong> 一 道 的 , 可 以 在 附 带<br />

的 程 序 例 子 中 找 到 它 。PIE 是 经 典 的 <strong>Prolog</strong> 解 释 程 序 , 使 用 它 可 以 学 习 和 理 解 <strong>Prolog</strong> 而 又 不<br />

用 去 考 虑 类 、 类 型 以 及 图 形 界 面 等 问 题 。<br />

5.1 Horn 子 句 逻 辑<br />

<strong>Visual</strong> <strong>Prolog</strong> 和 其 它 的 <strong>Prolog</strong> 方 言 都 是 基 于 Horn 子 句 逻 辑 的 。 所 谓 Horn 子 句 逻 辑 , 是<br />

一 种 事 物 及 其 相 互 关 系 的 推 理 的 形 式 系 统 。 例 如 , 用 自 然 语 言 我 们 可 以 陈 述 说 :<br />

John is the father of Bill.<br />

这 中 间 有 两 件 “ 事 物 ”:John 和 Bill, 他 们 之 间 的 关 系 , 就 是 一 个 是 另 一 个 的 父 亲 (father)。<br />

我 们 可 以 用 Horn 子 句 逻 辑 形 式 化 地 表 述 这 句 话 为 :<br />

father("Bill", "John").<br />

这 种 形 式 中 , 把 关 系 的 名 称 放 在 前 面 , 两 个 “ 事 物 ” 放 在 括 号 里 , 并 用 逗 号 隔 开 。 两 个<br />

事 物 的 名 称 用 计 算 机 行 话 讲 是 “ 字 符 串 ” 或 简 称 “ 串 ”。 串 要 用 引 号 界 定 。 再 说 一 个 概 念 :<br />

我 们 说 father 是 一 个 谓 词 或 是 一 个 关 系 , 它 有 ”Bill” 和 ”John” 两 个 参 数 。 谓 词 描 述 两 个 对 象<br />

( 在 这 里 也 就 是 Bill 和 John) 之 间 的 关 系 。 我 们 解 释 说 , 第 二 个 参 数 John, 是 第 一 个 参 数<br />

Bill 的 father。<br />

注 意 , 这 里 我 们 选 定 第 二 个 对 象 是 第 一 个 对 象 的 父 亲 。 我 们 也 可 以 反 过 来 规 定 。 参 数 的<br />

次 序 是 由 形 式 化 的 设 计 者 选 择 的 。 不 过 , 一 经 选 定 , 就 需 要 保 持 一 贯 。 因 而 在 我 们 的 形 式 化<br />

过 程 中 ,father 总 是 第 二 个 。 可 以 这 样 说 : 在 计 算 机 程 序 中 我 们 创 建 了 一 个 世 界 , 这 个 世 界<br />

中 谓 词 father 的 第 二 个 参 数 在 这 个 关 系 中 是 父 亲 。<br />

前 面 我 们 选 用 了 人 名 表 示 一 个 人 , 并 用 串 来 表 示 人 名 。 要 复 杂 些 想 , 这 可 能 不 行 , 因 为<br />

现 实 世 界 中 有 很 多 同 名 的 人 。 不 过 , 在 我 们 的 例 子 中 这 就 够 用 了 。<br />

用 上 述 这 样 的 形 式 化 方 法 , 我 们 可 以 陈 述 任 何 人 的 任 意 家 庭 关 系 。 或 者 更 通 用 些 , 用 谓<br />

词 和 参 数 , 我 们 可 以 说 明 对 象 间 的 任 意 关 系 。 看 一 下 例 子 :<br />

lives_in("Thomas", "Groningen").<br />

is_married_to("John","Claire").<br />

这 些 是 两 个 对 象 间 关 系 的 例 子 。 其 实 参 数 个 数 可 以 是 任 意 的 , 例 如 只 有 一 个 参 数 :<br />

is_dumb(“Jone”).<br />

三 个 对 象 也 可 以 :<br />

has_degree("Jeannet", "shorthand", "Spanish").<br />

参 数 个 数 根 据 需 要 而 定 。 而 这 样 的 叙 述 称 为 fact( 事 实 )。 一 个 事 实 是 由 一 个 谓 词 , 一<br />

些 放 在 括 号 中 并 用 逗 号 分 隔 开 的 参 数 和 结 尾 的 句 点 构 成 的 。 事 实 描 述 对 象 间 的 静 态 关 系 。<br />

知 道 事 实 很 有 用 。 但 还 需 要 更 进 一 步 , 我 们 需 要 掌 握 事 物 间 普 遍 的 关 系 。 普 遍 的 关 系 称<br />

为 rule( 规 则 )。 我 们 可 以 应 用 规 则 把 father 的 例 子 扩 展 到 grandfather:<br />

6<br />

本 章 是 对 Thomas Linder Puls (<strong>PDC</strong>) 的 the tutorial Fundamental <strong>Prolog</strong> (Part 1) 的 改 写 。<br />

35


Person3 is the grandfather of Person1, if Person2 is the father of Person1 and Person 3 is the<br />

father of Person 2( 第 三 个 人 是 第 一 个 人 的 祖 父 , 如 果 第 二 个 人 是 第 一 个 人 的 父 亲 而 第 三 个 人 是 第 二<br />

个 人 的 父 亲 )<br />

Person1、Person2 和 Person3 表 示 单 个 的 人 ( 对 象 )。 用 Horn 子 句 逻 辑 可 以 这 样 形 式 化<br />

地 表 述 上 面 的 规 则 :<br />

grandFather(Person1, Person3) :‐<br />

father(Person1, Person2), father(Person2, Person3).<br />

在 规 则 中 , 除 了 谓 词 , 我 们 还 使 用 了 两 个 逻 辑 算 子 :if 和 and。 在 形 式 化 过 程 中 ,if 我<br />

们 用 符 号 :‐ 表 示 , 而 and 用 逗 号 来 替 代 。 这 些 符 号 在 <strong>Prolog</strong> 中 代 表 逻 辑 算 子 。 很 清 楚 , 我 们<br />

在 grandFather(Person1,Person3) 中 是 要 说 Person3 是 Person1 的 祖 父 。 这 样 的 形 式 称 为 rule( 规<br />

则 )。 我 们 仔 细 看 一 个 规 则 的 语 法 。 第 一 部 分 grandFather(Person1,Person3) 称 为 结 论 , 结 论 后<br />

面 是 if 的 符 号 :‐。 在 这 个 符 号 之 后 有 一 个 或 多 个 事 实 谓 词 , 用 逗 号 分 隔 开 , 最 后 用 句 点 结 尾 。<br />

规 则 中 的 这 些 事 实 谓 词 称 为 规 则 的 前 提 。 还 要 注 意 ,grandFather 是 以 小 写 开 头 , 这 是 <strong>Prolog</strong><br />

的 语 法 要 求 , 谓 词 必 须 以 小 写 字 母 开 头 。 参 数 如 Person1 是 大 写 开 头 的 。 在 <strong>Prolog</strong> 中 这 表 示<br />

它 是 一 个 变 量 , 也 就 是 说 在 这 个 位 置 上 可 以 是 任 何 表 示 某 人 的 对 象 。 当 然 , 这 个 规 则 中 不 能<br />

适 用 人 的 组 合 , 那 是 另 外 的 事 了 。 注 意 一 下 , 这 个 规 则 中 的 前 提 是 事 实 , 但 我 们 也 可 以 用 其<br />

它 规 则 的 结 论 来 当 前 提 。<br />

熟 悉 逻 辑 的 读 者 可 能 会 发 现 这 与 逻 辑 推 理 差 不 多 , 不 同 的 是 在 逻 辑 推 理 中 我 们 是 这 样 写<br />

的 :<br />

IF p AND q THEN r<br />

而 在 <strong>Prolog</strong> 中 是 这 样 写 :<br />

r :‐ p, q.<br />

当 前 提 是 真 , 则 结 论 也 就 是 真 。 这 和 <strong>Prolog</strong> 中 是 一 样 的 。 规 则 更 通 常 的 形 式 可 以 写 成 :<br />

conclusion :‐ premiss1, premiss2, ...,premissN.<br />

我 们 回 来 看 grandfather 的 规 则 。 由 于 是 形 式 化 了 的 , 有 些 难 理 解 。 比 如 , 这 样 写 看 不<br />

出 谁 好 像 应 该 是 谁 的 祖 父 。 用 好 理 解 的 名 字 来 替 代 Person1、Person2 和 Person3 可 能 更 好 。<br />

在 Horn 子 句 逻 辑 中 我 们 可 以 这 样 做 , 我 们 可 以 任 意 选 择 变 量 名 。 因 此 , 规 则 可 以 改 写 成 :<br />

grandFather(GrandChild, GrandFather) :‐<br />

father(GrandChild, Father), father(Father, GrandFather).<br />

要 知 道 这 只 是 为 了 我 们 读 程 序 时 的 方 便 。 计 算 机 并 不 认 为 father 就 会 有 个 child, 对 计<br />

算 机 来 讲 下 面 的 规 则 与 上 面 的 一 样 清 楚 :<br />

g(X,Y) :‐ f(X,Z), f(Z,Y).<br />

而 在 这 里 我 们 用 变 量 替 代 了 对 象 名 称 。 规 则 是 任 意 对 象 间 的 普 遍 关 系 。 上 式 说 : 只 要 对<br />

象 Z 是 对 象 X 的 父 亲 而 同 时 对 象 Y 是 Z 的 父 亲 , 在 我 们 程 序 的 世 界 里 对 象 Y 就 是 对 象 X 的 祖<br />

父 , 句 点 。<br />

小 结 : 读 规 则 时 要 把 符 号 :‐ 当 作 if, 把 分 隔 子 句 的 逗 号 当 作 and。 变 量 名 首 字 母 要 大 写 ,<br />

如 GrandChild、Father、Grandfather 等 。 在 <strong>Prolog</strong> 中 变 量 名 总 是 以 大 写 字 母 开 头 的 。 在 上 面<br />

的 规 则 中 ,“grandFather(GrandChild, GrandFather)” 是 结 论 , 而 “father(GrandChild, Father),<br />

father(Father, GrandFather)” 是 前 提 。 规 则 以 句 点 结 尾 。<br />

选 择 合 适 的 变 量 名 会 比 只 用 X、Y、Z 更 易 理 解 。 我 们 前 面 的 修 改 就 要 好 理 解 些 。 想 想 看 ,<br />

要 从 grandfather(X,Y) 中 来 确 定 X 和 Y 谁 是 祖 父 , 很 容 易 引 起 混 乱 。 把 规 则 分 成 多 行 写 也 能 够<br />

提 高 易 读 性 。 首 行 是 结 论 , 下 一 行 是 前 提 , 如 果 前 提 很 长 , 可 以 每 个 前 提 占 一 行 。<br />

通 过 一 个 规 则 , 我 们 为 grandfather 关 系 引 出 了 一 个 谓 词 。 它 很 有 效 率 。 我 们 不 需 要 写<br />

出 每 个 祖 父 与 孙 儿 的 事 实 , 只 说 明 了 一 条 规 则 并 应 用 父 子 关 系 就 可 以 了 。 我 们 仍 选 择 了 祖 父<br />

是 第 二 个 参 数 。 应 该 像 这 样 一 致 下 去 , 也 就 是 不 同 的 谓 词 要 遵 行 某 些 共 同 的 原 则 。<br />

在 <strong>Prolog</strong> 中 事 实 与 规 则 称 为 子 句 。 有 了 事 实 和 规 则 , 形 式 化 的 知 识 系 统 就 准 备 好 了 。<br />

一 般 地 说 , 知 识 系 统 就 是 事 实 与 规 则 的 集 合 。 在 <strong>Prolog</strong> 中 , 我 们 会 说 知 识 系 统 就 是 一 组 子<br />

句 。 我 们 用 下 面 三 个 事 实 和 一 个 规 则 来 给 出 一 个 小 的 知 识 系 统 :<br />

36


father("Bill", "John").<br />

father("Pam", "Bill").<br />

father("Sue", "Jim").<br />

grandFather(GrandChild, GrandFather) :‐<br />

father(GrandChild, Father),<br />

father(Father, GrandFather).<br />

用 这 个 知 识 系 统 可 以 回 答 如 下 的 问 题 :<br />

Jim 是 Sue 的 父 亲 吗 ?<br />

这 个 问 题 的 回 答 , 可 以 查 找 事 实 。 第 三 个 事 实 确 认 了 这 一 点 , 所 以 回 答 是 Yes。<br />

John 是 Sue 的 父 亲 吗 ?<br />

没 有 事 实 说 这 个 情 况 , 所 以 回 答 是 No。 不 过 我 们 可 以 注 意 到 有 事 实 说 Jim 是 Sue<br />

的 父 亲 , 所 以 John 不 可 能 是 。 但 这 是 我 们 可 以 注 意 到 的 , 而 不 是 <strong>Prolog</strong> 推 论 的 方<br />

法 。<strong>Prolog</strong> 回 答 No 是 因 为 没 有 事 实 来 让 它 说 Yes。<br />

谁 是 Pam 的 父 亲 ?<br />

在 事 实 中 寻 找 , 第 二 个 事 实 说 Bill 是 Pam 的 父 亲 。<br />

John 是 Ann 的 父 亲 吗 ?<br />

由 于 没 有 关 于 Ann 的 事 实 , 我 们 无 法 回 答 这 个 问 题 。 在 <strong>Prolog</strong> 中 会 回 答 No, 因 为<br />

我 们 无 法 说 是 。<br />

John 是 Pam 的 祖 父 吗 ?<br />

要 回 答 这 个 问 题 需 要 使 用 事 实 与 规 则 。 规 则 说 要 找 到 两 个 事 实 , 第 一 个 说 某 人 是<br />

Pam 的 父 亲 而 第 二 个 要 说 John 是 这 个 “ 某 人 ” 的 父 亲 。 在 事 实 里 寻 找 , 可 以 发 现<br />

当 “ 某 人 ” 是 Bill 时 , 事 实 就 是 这 么 说 的 。 所 以 回 答 是 Yes,John 是 Pam 的 祖 父 。<br />

在 <strong>Prolog</strong> 中 可 以 对 推 理 机 提 这 些 问 题 , 推 理 机 是 内 建 在 <strong>Prolog</strong> 中 的 。 对 <strong>Prolog</strong> 来 说 ,<br />

这 些 问 题 被 称 作 goal( 目 标 , 可 能 是 因 为 推 理 机 去 追 寻 它 们 , 所 以 这 么 叫 ), 目 标 可 以 按 以<br />

下 的 方 式 形 式 化 :<br />

“Jim 是 Sue 的 父 亲 吗 ?” 写 成 :<br />

?‐father(“Sue”,”Jim”).<br />

“John 是 Sue 的 父 亲 吗 ?” 写 成 :<br />

?‐father(“Sue”,”John”).<br />

“Pan 的 父 亲 叫 什 么 名 字 ?” 写 成 :<br />

?‐father(“Pam”,X).<br />

“John 是 Ann 的 父 亲 吗 ?” 写 成 :<br />

?‐father(“Ann”,”John”).<br />

“John 是 Pam 的 祖 父 吗 ?” 写 成 :<br />

?‐grandFather(“Pam”,”John”).<br />

上 面 这 样 的 问 题 叫 作 目 标 子 句 或 简 称 目 标 。 事 实 、 规 则 加 上 目 标 称 为 Horn 子 句 , 所 以<br />

叫 Horn 子 句 逻 辑 。<br />

上 面 第 一 、 第 二 和 最 后 一 个 目 标 , 回 答 就 是 简 单 的 Yes 或 No, 因 为 问 的 就 是 某 个 事 实<br />

是 否 为 真 。 有 的 目 标 如 第 三 个 , 我 们 要 找 的 是 一 个 解 , 如 X=”Bill”。 有 的 目 标 甚 至 有 多 个 解 ,<br />

如 :<br />

?‐father(X,Y).<br />

有 三 个 解 ( 因 为 有 三 个 事 实 各 给 出 一 个 解 ):<br />

X = "Bill", Y = "John".<br />

X = "Pam", Y = "Bill".<br />

X= "Sue", Y= "Jim".<br />

一 个 <strong>Prolog</strong> 程 序 由 一 个 知 识 系 统 与 一 个 目 标 构 成 。 程 序 启 动 后 , 它 就 会 在 知 识 系 统 中<br />

37


为 目 标 找 到 一 个 解 。 搜 寻 解 是 由 <strong>Prolog</strong> 中 叫 作 推 理 机 的 部 件 完 成 的 。 在 知 识 系 统 中 搜 索 解<br />

要 用 到 规 则 与 事 实 , 用 我 们 人 类 会 使 用 的 差 不 多 的 办 法 。 当 然 推 理 机 对 解 的 搜 索 非 常 公 式 化 ,<br />

不 过 类 比 对 我 们 理 解 <strong>Prolog</strong> 及 它 的 推 理 机 如 何 工 作 还 是 很 有 用 的 。 下 一 节 我 们 要 说 明 这 个<br />

问 题 。<br />

5.2 PIE:<strong>Prolog</strong> 的 推 理 机<br />

现 在 我 们 把 上 面 的 例 子 在 PIE 中 试 试 看 。PIE 是 <strong>Visual</strong> <strong>Prolog</strong> 中 的 一 个 例 子 , 在 使 用 之<br />

前 需 要 安 装 它 。 如 果 还 没 有 安 装 , 请 按 下 面 的 步 骤 进 行 安 装 :<br />

• 在 Windows 的 开 始 菜 单 中 选 择 安 装 (Start‐><strong>Visual</strong> <strong>Prolog</strong>‐>Install Examples)<br />

• 启 动 <strong>Visual</strong> <strong>Prolog</strong><br />

• 可 以 在 安 装 的 Examples 文 件 夹 中 找 到 PIE 工 程 , 在 IDE 中 打 开 这 个 工 程 并 执 行 程 序 。<br />

可 以 如 同 前 面 做 过 的 一 样 把 PIE 工 程 当 成 通 常 的 工 程 来 执 行 。 结 果 如 图 5.1 示 。<br />

图 5.1<br />

PIE 对 话 窗<br />

对 话 框 中 显 示 用 一 些 缺 省 值 创 建 了 名 为 pie32.ini 的 文 件 。 还 告 诉 我 们 使 用 在 线 帮 助 查 看<br />

详 细 描 述 及 其 它 一 些 事 。 我 们 打 算 执 行 前 节 中 的 程 序 。 要 创 建 这 个 程 序 , 在 菜 单 条 中 选 择<br />

File‐>New,PIE 会 打 开 第 二 个 窗 口 - 程 序 窗 口 , 它 用 于 输 入 程 序 。 现 在 打 开 了 两 个 窗 口 , 程<br />

序 窗 口 和 对 话 窗 口 。 程 序 窗 口 是 程 序 使 用 的 , 它 的 标 题 条 上 显 示 着 PIE 缺 省 给 出 的 名 字<br />

File0.pro。 在 程 序 窗 口 的 编 辑 窗 中 , 要 写 入 我 们 的 知 识 系 统 的 事 实 和 规 则 。 我 们 有 时 把 它 们<br />

也 叫 程 序 , 不 过 要 记 住 没 有 目 标 的 程 序 是 不 完 整 的 。 对 话 窗 口 是 与 推 理 机 对 话 用 的 , 我 们 在<br />

这 里 输 入 程 序 的 目 标 , 也 就 是 我 们 要 寻 求 答 案 的 问 题 。PIE 也 用 这 个 窗 口 显 示 消 息 。 搞 清 楚<br />

哪 个 是 活 动 窗 口 很 重 要 。 要 让 一 个 窗 口 获 得 控 制 权 , 可 以 点 击 它 的 标 题 行 。<br />

按 前 一 节 的 内 容 在 程 序 窗 口 中 输 入 father 和 grandFather 的 子 句 ( 也 就 是 那 些 事 实 与 规<br />

则 ), 结 果 应 该 如 图 5.2 示 。<br />

输 入 的 程 序 在 程 序 窗 口 里 了 。 要 想 让 推 理 机 使 用 这 些 子 句 与 规 则 , 先 要 加 载 到 推 理 机 中<br />

去 。 在 程 序 窗 口 中 选 择 Engine‐>Reconsult。 这 时 就 会 把 编 辑 窗 口 中 的 内 容 加 载 到 推 理 机 去 ,<br />

以 便 推 理 机 使 用 它 们 。 在 对 话 窗 口 , 可 以 看 到 下 面 的 消 息 :<br />

Reconsulted from: ....\pie\Exe\FILE0.PRO<br />

38


图 5.2<br />

程 序 窗 口<br />

编 辑 器 中 的 内 容 加 载 了 , 但 没 有 保 存 在 任 何 文 件 中 。 要 保 存 这 些 内 容 , 可 以 在 编 辑 窗 口<br />

中 使 用 File‐>Save 命 令 。 而 要 从 文 件 中 取 回 内 容 到 编 辑 窗 口 可 以 用 File‐>Consult 命 令 , 这 时<br />

不 管 文 件 是 不 是 已 经 打 开 的 都 会 把 文 件 的 内 容 加 载 到 编 辑 窗 口 中 。<br />

要 使 推 理 机 搜 寻 一 个 解 , 必 须 要 给 它 一 个 追 寻 的 目 标 。 这 是 在 对 话 窗 口 中 进 行 的 , 其 过<br />

程 总 是 如 下 :<br />

• 在 程 序 窗 口 用 File‐>New 创 建 一 个 文 件 或 用 File‐>consult 加 载 一 个 文 件<br />

• 需 要 的 话 用 File‐>Save 保 存 文 件<br />

• 用 Engine/Reconsult 给 推 理 机 加 载<br />

• 在 对 话 窗 口 输 入 一 个 目 标 并 回 车<br />

一 旦 加 载 了 知 识 系 统 , 就 可 以 用 它 回 答 问 题 了 。<br />

在 对 话 窗 口 中 一 个 空 行 上 输 入 目 标 ( 不 用 输 入 前 面 的 ?‐)。 我 们 来 问 问 谁 是 谁 的 祖 父 。<br />

在 <strong>Prolog</strong> 中 这 个 目 标 有 两 个 变 量 , 我 们 想 让 推 理 机 找 出 这 两 个 变 量 的 值 ( 名 字 )。 在 对 话 窗<br />

口 中 输 入 ( 在 空 行 上 ):<br />

grandfather(X,Y).<br />

注 意 grandFather 用 小 写 开 头 ,X 和 Y 要 大 写 , 也 别 忘 了 结 尾 的 句 点 。 如 图 5.3 示 。<br />

图 5.3<br />

光 标 移 到 这 行 的 最 后 , 敲 回 车 。PIE 会 把 这 一 行 从 开 始 到 光 标 处 作 为 一 个 目 标 来 执 行 。<br />

推 理 机 开 始 工 作 , 并 得 到 如 图 5.4 示 的 结 果 。<br />

若 结 果 是 下 面 这 样 :<br />

grandFather(X,Y).<br />

图 5.4<br />

39


Unknown clause found grandfather(X$0,Y$1)<br />

Execution terminated<br />

No solutions<br />

可 能 是 忘 了 加 载 了 。 目 标 一 定 要 如 图 5.3 示 的 那 样 写 准 确 。 把 grandFather 写 成 了<br />

GrandFather 会 出 现 错 误 消 息 , 因 为 谓 词 必 须 以 小 写 开 头 。 也 不 要 忘 记 结 尾 的 句 点 ! 光 回 车<br />

是 不 行 的 。<br />

前 一 节 中 其 它 一 些 目 标 也 是 很 好 的 练 习 , 花 一 些 时 间 做 做 它 们 是 有 益 的 。 不 过 , 还 需 要<br />

知 道 一 些 有 关 指 定 目 标 的 事 , 下 面 是 一 些 规 则 :<br />

• 可 以 使 用 程 序 中 的 任 何 谓 词 来 形 式 化 一 个 目 标 , 在 当 前 的 例 子 中 可 以 使 用 father<br />

和 grandFather。<br />

• 使 用 谓 词 时 , 一 定 要 给 出 合 适 的 变 量 , 也 就 是 说 参 数 的 数 量 和 类 型 要 正 确 。 在 当 前<br />

的 例 子 中 类 型 总 是 串 , 不 大 可 能 出 错 。<br />

• 参 数 可 以 使 用 特 定 对 象 的 名 字 或 是 一 个 变 量 。 我 们 的 例 子 中 特 定 对 象 的 名 字 是<br />

Sue、Jim、Bill 等 , 不 过 其 实 任 何 串 都 是 可 以 接 受 的 。 如 :<br />

father("Sue","Jim").<br />

father("Sammy","Simson").<br />

grandfather("Sue","Jim").<br />

输 入 上 面 这 样 的 目 标 时 , 推 理 机 会 根 据 程 序 中 的 事 实 与 规 则 回 答 Yes 或 No。<br />

• 可 以 用 变 量 当 参 数 , 如 :<br />

father(X,Y).<br />

father(Child,Father).<br />

grandfather(GrandChild, GrandFather).<br />

注 意 变 量 要 以 大 写 开 头 。 当 用 变 量 输 入 目 标 时 , 推 理 机 就 会 为 变 量 找 一 个 值 以 使 目<br />

标 与 某 个 事 实 匹 配 。 成 功 了 , 它 就 会 返 回 这 个 值 。 如 果 我 们 使 用 的 变 量 名 有 意 义 ,<br />

就 会 使 输 出 易 于 理 解 。<br />

• 目 标 中 可 以 同 时 使 用 变 量 和 对 象 名 , 如 father(“Sue”,Father)。 这 时 推 理 机 会 为 变 量<br />

寻 找 能 够 匹 配 某 个 事 实 的 值 。<br />

• 目 标 还 可 以 由 一 个 以 上 的 谓 词 构 成 。 这 时 就 要 使 用 and 的 逻 辑 操 作 符 “,” 和 or 的<br />

“;”。 如 :<br />

father("Sue","Jim"),father("Bill","John").<br />

好 了 , 再 多 做 些 练 习 , 找 找 推 理 机 产 生 答 案 的 感 觉 。<br />

5.3 扩 展 家 庭 知 识 系 统<br />

为 增 强 程 序 的 知 识 能 力 , 增 加 事 实 与 规 则 似 乎 是 个 很 好 的 办 法 。 不 过 要 当 心 ! 在 <strong>Prolog</strong><br />

中 我 们 是 与 逻 辑 和 推 理 机 在 打 交 道 , 这 两 者 的 结 合 有 时 会 出 现 没 有 预 计 到 的 结 果 。 这 一 节 我<br />

们 来 看 看 有 需 要 注 意 哪 些 一 般 性 的 问 题 。<br />

增 加 如 mother 和 grandMother 这 样 的 谓 词 是 直 接 扩 展 上 述 家 庭 知 识 系 统 的 办 法 。 可 以<br />

自 己 试 一 下 , 还 要 加 上 更 多 的 人 。 建 议 用 自 己 的 家 庭 成 员 为 例 子 , 这 样 验 证 起 来 很 方 便 , 谁<br />

是 谁 的 奶 奶 一 目 了 然 。<br />

加 入 了 mother 和 grandMother 我 们 还 可 以 定 义 一 个 “ 父 母 ”(parent) 谓 词 , 如 果 是 mother<br />

那 就 是 parent, 如 果 是 father 那 也 是 parent。 所 以 定 义 parent 可 以 使 用 如 下 的 子 句 :<br />

parent(Person, Parent) :‐ mother(Person, Parent).<br />

parent(Person, Parent) :‐ father(Person, Parent).<br />

在 上 面 的 规 则 中 , 我 们 选 择 了 第 二 个 参 数 是 第 一 个 参 数 的 mother/father/parent。 第 一<br />

个 规 则 读 为 :Parent 是 Person 的 父 母 , 如 果 Parent 是 Person 的 母 亲 。 第 二 个 规 则 则 是 说 :<br />

Parent 是 Person 的 父 母 , 如 果 Parent 是 Person 的 父 亲 。<br />

两 条 规 则 在 一 起 , 说 明 了 两 种 可 能 : 某 人 可 能 是 父 亲 , 可 能 是 母 亲 , 在 这 两 种 情 况 下 都<br />

是 parent。 事 实 提 供 了 两 种 选 择 , 不 是 母 亲 就 再 试 试 看 是 不 是 父 亲 。<br />

40


我 们 还 可 以 使 用 表 示 or 的 分 号 “;” 来 定 义 parent:<br />

parent(Person, Parent) :‐<br />

mother(Person, Parent); father(Person, Parent).<br />

这 个 规 则 说 :Parent 是 Person 的 父 母 , 如 果 Parent 是 Person 的 母 亲 or( 或 者 )Parent<br />

是 Person 的 父 亲 。<br />

但 是 , 建 议 尽 可 能 少 用 ( 最 好 干 脆 不 用 ) 分 号 “;”。 有 这 样 几 个 理 由 :<br />

• 逗 点 和 分 号 在 书 写 印 刷 上 差 别 很 小 但 语 义 上 差 别 却 很 大 , 分 号 常 常 是 混 乱 的 根 源 ,<br />

它 太 容 易 被 当 成 逗 点 了 , 尤 其 是 在 长 长 一 行 的 结 尾 处 更 易 弄 错<br />

• <strong>Visual</strong> <strong>Prolog</strong> 中 只 允 许 在 最 外 层 使 用 分 号 (PIE 允 许 任 意 层 使 用 ), 所 以 它 没 有 多 大<br />

用 处<br />

• 混 合 使 用 逗 点 与 分 号 时 , 马 上 就 会 造 成 评 估 规 则 时 哪 个 前 提 优 先 的 问 题 。 当 然 这 是<br />

有 规 定 的 , 但 对 一 般 人 类 理 解 上 容 易 产 生 错 误 。<br />

• 有 很 简 单 的 方 法 来 表 示 or 的 关 系 。 如 同 上 面 我 们 开 始 定 义 parent 的 办 法 , 对 一 个<br />

or 关 系 只 要 分 开 成 两 个 规 则 写 就 行 了 。<br />

试 试 创 建 一 个 sibling( 兄 弟 姐 妹 ) 谓 词 。 然 后 让 推 理 机 找 出 兄 弟 姐 妹 来 ! 遇 到 什 么 问 题 ?<br />

找 兄 弟 姐 妹 时 可 能 会 找 出 两 次 ! 至 少 要 是 说 : 两 人 是 sibling 如 果 他 们 同 母 , 两 人 是 sibling<br />

如 果 他 们 同 父 。 也 就 是 说 , 如 果 规 则 是 :<br />

sibling(Person, Sibling) :‐ mother(Person, Mother), mother(Sibling, Mother).<br />

sibling(Person, Sibling) :‐ father(Person, Father), father(Sibling, Father).<br />

第 一 条 规 则 说 :Sibling 是 Person 的 兄 弟 姐 妹 如 果 Mother 是 Person 的 母 亲 and( 而 且 )<br />

Mother 是 Sibling 的 母 亲 。<br />

第 二 条 规 则 说 :Sibling 是 Person 的 兄 弟 姐 妹 如 果 Father 是 Person 的 父 亲 and( 而 且 )<br />

Father 是 Sibling 的 父 亲 。<br />

这 样 的 规 则 , 就 会 出 现 回 答 重 复 。 其 原 因 是 : 大 多 数 兄 弟 姐 妹 既 同 父 又 同 母 , 所 以 同 时<br />

完 全 满 足 上 述 两 个 条 件 , 因 而 推 理 机 就 会 两 次 找 到 同 一 答 案 。 现 在 我 们 还 不 打 算 处 理 这 个 问<br />

题 , 但 是 我 们 知 道 了 , 有 些 规 则 会 给 出 过 多 的 结 果 。<br />

纯 血 统 意 义 上 的 兄 弟 姐 妹 的 谓 词 , 不 应 该 有 这 样 的 问 题 , 因 为 这 需 要 两 人 的 母 亲 是 同 一<br />

人 , 而 两 人 的 父 亲 也 是 同 一 人 。 其 规 则 为 :<br />

fullBloodedSibling(Person, Sibling) :‐<br />

mother(Person, Mother),<br />

mother(Sibling, Mother),<br />

father(Person, Father),<br />

father(Sibling, Father).<br />

分 散 在 两 个 规 则 中 的 四 个 前 提 现 在 集 中 在 一 个 规 则 里 了 , 它 们 是 用 and 联 结 起 来 的 。<br />

5.4 <strong>Prolog</strong> 是 一 种 编 程 语 言<br />

目 前 为 止 的 描 述 , 可 能 会 使 我 们 认 为 与 其 说 <strong>Prolog</strong> 是 一 种 编 程 语 言 , 还 不 如 说 它 是 一<br />

个 专 家 系 统 。 我 们 提 问 , 它 回 答 。 的 确 ,<strong>Visual</strong> <strong>Prolog</strong> 可 以 用 作 一 个 专 家 系 统 , 但 它 是 按 编<br />

程 语 言 设 计 的 。<br />

要 使 它 成 为 一 个 可 用 的 编 程 语 言 , 需 要 两 个 重 要 因 素 将 Horn 子 句 逻 辑 转 变 成 为 编 程 语<br />

言 :<br />

• 刚 性 的 搜 索 顺 序 及 程 序 控 制 。 当 一 个 程 序 运 行 时 , 它 的 行 为 应 该 是 可 预 知 的 , 也 就<br />

是 说 程 序 的 反 应 不 是 随 机 的 。 想 想 看 , 一 个 计 算 机 程 序 星 期 一 的 运 行 情 况 和 星 期 三<br />

的 运 行 情 况 不 一 样 , 那 会 是 个 什 么 样 ? 因 此 , 除 了 其 它 一 些 要 求 外 , 我 们 要 求 程 序<br />

按 一 种 严 格 的 方 法 处 理 目 标 与 规 则 。 例 如 , 程 序 应 该 按 照 严 格 的 顺 序 来 搜 索 规 则 。<br />

• 辅 助 功 效 。Horn 子 句 逻 辑 对 回 答 问 题 和 检 测 一 个 命 题 是 否 为 真 是 很 好 的 。 但 计 算<br />

41


机 程 序 还 要 与 用 户 和 环 境 通 信 。 因 此 , 我 们 需 要 <strong>Prolog</strong> 还 有 某 些 辅 助 功 效 , 例 如<br />

我 们 前 章 中 做 的 屏 幕 显 示 。<br />

下 面 两 节 , 我 们 就 来 看 看 这 些 问 题 。<br />

5.5 程 序 控 制<br />

<strong>Prolog</strong> 是 一 种 编 程 语 言 , 所 以 它 有 严 格 的 搜 索 和 程 序 控 制 。 本 节 中 我 们 来 仔 细 地 看 一 看<br />

<strong>Prolog</strong> 试 图 解 答 问 题 的 方 法 , 还 要 介 绍 两 个 重 要 的 概 念 fail( 失 败 ) 和 backtracking( 回 溯 )。<br />

5.5.1 寻 找 匹 配<br />

前 面 说 过 , 推 理 机 会 尝 试 找 到 我 们 输 入 的 目 标 的 匹 配 结 果 。 如 果 输 入 的 是 一 个 简 单 的 事<br />

实 , 如 father(“Sue”,”Jim”), 那 搜 索 就 很 直 接 。 推 理 机 查 找 程 序 中 事 实 里 的 father 谓 词 , 然 后<br />

回 答 Yes 或 No。 但 如 果 输 入 的 不 是 事 实 而 是 规 则 的 结 论 , 如 grandFather(X,Y), 那 会 怎 么 样<br />

呢 ? 这 时 没 有 事 实 与 之 相 匹 配 。 对 这 样 的 目 标 , 推 理 机 会 应 用 规 则 把 复 合 目 标 grandFather<br />

用 前 提 father(X,Y) 和 father(Y,Z) 替 换 成 两 个 子 目 标 。 现 在 推 理 机 要 完 成 两 个 目 标 , 但 这 些 子 目<br />

标 不 是 复 合 的 , 在 程 序 事 实 中 可 以 找 到 , 因 而 推 理 机 可 以 完 成 目 标 。<br />

存 在 着 若 干 可 能 性 。 我 们 应 该 知 道 推 理 机 是 如 何 处 理 这 些 可 能 性 的 :<br />

• 如 果 目 标 中 使 用 的 是 对 象 名 , 推 理 机 就 把 复 合 目 标 中 的 名 字 传 递 到 子 目 标 中 。 如 ,<br />

目 标 是 grandFather(“Sue”,”Jim”), 那 子 目 标 就 会 是 father(“Sue”,Y),father(Y,”Jim”)。<br />

• 如 果 目 标 中 是 变 量 , 那 它 也 会 传 递 到 子 目 标 中 。 如 ,grandFather(“Sue”,Grandfather)<br />

会 变 为 father(“Sue”,Y),father(Y, Grandfather)。<br />

下 一 节 我 们 要 讨 论 推 理 机 是 如 何 用 匹 配 机 制 来 完 成 目 标 的 。 这 个 策 略 , 就 是 把 目 标 分 解<br />

成 希 望 能 在 知 识 系 统 的 事 实 中 找 到 的 简 单 子 目 标 。<br />

5.5.2 完 成 目 标<br />

当 人 们 要 解 决 目 标 如 :?‐father(X,Y). 的 时 候 , 可 以 有 很 多 种 方 法 。 比 如 , 可 以 只 考 虑 知<br />

识 系 统 中 的 第 二 条 事 实 , 得 到 一 个 解 ,X=”Pam”,Y=”Bill”。 或 者 用 不 同 的 顺 序 搜 索 规 则 , 比 如<br />

自 底 向 上 或 自 顶 向 下 。<br />

但 <strong>Prolog</strong> 不 使 用 随 机 搜 索 策 略 , 而 总 是 使 用 相 同 的 策 略 。 它 从 给 它 提 出 的 问 题 开 始 ,<br />

把 这 个 问 题 叫 做 “ 当 前 目 标 ”, 在 搜 索 过 程 中 系 统 保 存 着 这 个 当 前 目 标 , 并 且 “ 从 左 至 右 ”<br />

地 完 成 目 标 。<br />

假 设 输 入 的 目 标 是 :<br />

?‐ grandFather(X, Y), mother(Y, Z).<br />

这 是 要 找 出 X 的 祖 父 , 把 名 字 放 在 Y 里 , 再 找 出 Y 的 母 亲 。 在 这 种 情 况 下 , 系 统 总 是 首<br />

先 完 成 子 目 标 grandFather(X,Y), 然 后 才 是 mother(Y,Z)。 如 果 第 一 个 ( 最 左 边 的 那 个 ) 子 目 标<br />

完 成 不 了 , 整 个 问 题 就 没 有 答 案 , 而 第 二 个 子 目 标 根 本 就 不 会 去 试 。<br />

当 解 决 一 个 特 定 的 子 目 标 时 , 对 事 实 和 规 则 的 试 探 总 是 自 顶 向 下 的 。 简 单 说 :<strong>Prolog</strong> 中<br />

的 推 理 机 总 是 从 左 到 右 从 上 到 下 地 工 作 的 , 就 如 同 我 们 看 书 一 样 。<br />

当 用 规 则 解 决 了 一 个 子 目 标 后 , 右 边 的 子 目 标 就 变 成 了 当 前 目 标 。<br />

还 是 刚 才 的 例 子 :?‐ grandFather(X, Y), mother(Y, Z). 推 理 机 从 最 左 边 部 分 开 始 , 也 就 是<br />

grandFather(X,Y)。 解 决 它 , 推 理 机 在 知 识 系 统 的 事 实 与 规 则 查 找 , 发 现 :<br />

grandFather(GrandChild, GrandFather) :‐ father(GrandChild, Father), father(Father, GrandFather).<br />

由 于 规 则 的 结 论 部 分 与 第 一 个 子 目 标 匹 配 , 所 以 就 用 这 个 规 则 解 它 。 因 此 , 用 规 则 中 的<br />

前 提 替 代 到 第 一 个 子 目 标 中 去 , 得 到 当 前 目 标 为 :<br />

?‐ father(X, Father), father(Father, Y), mother(Y, Z).<br />

看 起 来 很 蠢 , 因 为 二 个 子 目 标 成 了 三 个 子 目 标 了 ! 但 这 背 后 的 意 义 在 于 前 提 比 结 论 减 少<br />

了 复 杂 性 , 因 而 易 于 确 定 是 否 为 真 。 现 在 不 要 耽 心 太 多 。 还 要 注 意 , 替 代 前 提 中 的 一 些 变 量<br />

被 子 目 标 中 的 变 量 置 换 了 , 如 同 我 们 在 前 面 做 的 那 样 。<br />

42


用 新 的 形 式 化 了 的 目 标 , 再 来 查 找 可 以 完 成 目 标 的 事 实 与 规 则 。 当 以 事 实 核 实 子 目 标 并<br />

发 现 为 真 时 , 就 可 以 把 它 从 目 标 中 拿 掉 。 当 所 有 的 子 目 标 都 是 真 时 , 原 来 那 个 目 标 就 是 真 的<br />

( 记 住 , 子 目 标 是 用 逗 点 分 隔 的 , 意 思 是 and),<strong>Prolog</strong> 会 给 出 一 个 消 息 。<br />

按 照 上 述 策 略 , 我 们 可 以 更 程 序 化 地 解 释 子 句 。 对 于 规 则 :<br />

grandFather(Person, GrandFather) :‐ father(Person, Father), father(Father, GrandFather).<br />

按 严 格 的 策 略 来 解 读 , 就 是 : 要 解 grandFather(Person, GrandFather), 首 先 解 father(Person,<br />

Father), 然 后 再 解 father(Father, GrandFather)。 或 是 : 当 调 用 grandFather(Person, GrandFather)<br />

时 , 首 先 调 用 father(Person, Father), 然 后 再 调 用 father(Father, GrandFather)。<br />

用 这 样 程 序 化 的 解 读 方 式 , 可 以 看 出 谓 词 相 当 于 其 它 语 言 中 的 过 程 或 子 程 序 或 函 数 。 过<br />

程 、 子 程 序 和 函 数 间 是 有 严 格 区 分 的 。 一 个 函 数 只 返 回 一 个 值 而 一 个 过 程 对 每 个 输 出 变 量 返<br />

回 一 个 值 。 但 在 <strong>Prolog</strong> 中 一 个 谓 词 在 一 次 调 用 时 可 以 返 回 若 干 个 解 ( 给 出 多 个 解 的 目 标 )<br />

甚 或 是 失 败 。 失 败 意 味 着 什 么 解 也 没 有 。 而 有 趣 的 是 在 <strong>Prolog</strong> 中 失 败 又 是 很 有 用 的 。 下 节<br />

中 我 们 要 详 细 讨 论 它 。<br />

5.5.3 失 败<br />

在 程 序 中 , 一 个 谓 词 的 调 用 可 能 会 没 有 任 何 解 。 如 :<br />

parent("Hans", X)<br />

要 是 事 实 与 规 则 中 没 有 关 于 Hans 的 条 目 时 就 没 有 解 。 我 们 称 这 时 调 用 该 谓 词 失 败 。 目<br />

标 , 某 种 意 义 上 来 说 也 是 谓 词 , 也 会 失 败 。 如 果 目 标 失 败 , 也 就 等 于 知 识 系 统 中 没 有 目 标 的<br />

解 。<br />

谓 词 的 失 败 有 两 种 。 一 种 是 没 有 事 实 与 目 标 匹 配 或 与 目 标 的 某 个 前 提 匹 配 。 还 有 一 种 ,<br />

是 在 一 个 谓 词 的 前 提 中 遇 到 了 单 词 fail。 听 起 来 很 蠢 , 但 与 所 谓 “ 回 溯 ” 结 合 在 一 起 , 这 就<br />

成 为 一 种 非 常 有 用 的 技 术 了 。<br />

下 一 节 介 绍 通 常 情 况 下 失 败 是 如 何 处 理 的 。<br />

5.5.4 回 溯<br />

我 们 知 道 , 推 理 机 通 过 匹 配 事 实 来 解 目 标 。 它 从 左 到 右 从 上 到 下 地 搜 索 规 则 与 事 实 。 在<br />

它 不 懈 搜 索 解 的 过 程 中 , 推 理 机 使 用 了 一 个 叫 作 回 溯 的 机 制 。 我 们 需 要 了 解 它 是 什 么 意 思 ,<br />

是 如 何 进 行 的 。 回 溯 , 是 本 节 的 主 题 。<br />

假 设 我 们 有 下 面 一 个 程 序 :<br />

father("Bill", "John").<br />

father("Pam", "Bill").<br />

father("Sue", "Jim").<br />

grandFather(GrandChild, GrandFather) :‐<br />

father(GrandChild, Father),<br />

father(Father, GrandFather).<br />

很 熟 悉 的 , 是 吧 ? 我 们 提 这 样 的 目 标 :<br />

?‐father("Sue","Jim").<br />

回 车 。 推 理 机 拿 到 目 标 , 看 到 在 程 序 中 有 三 个 事 实 与 谓 词 father 匹 配 , 因 而 有 三 次 机 会<br />

匹 配 目 标 。 因 为 它 是 自 顶 向 下 工 作 的 , 所 以 首 先 用 第 一 个 事 实 father(“Bill”,”John”), 看 是 否<br />

与 目 标 匹 配 。 但 在 做 之 前 , 推 理 机 会 把 一 个 指 针 指 向 第 二 个 事 实 father(“Pam”,”Bill”), 这 个<br />

指 针 叫 回 溯 点 。 这 是 推 理 机 在 处 理 完 第 一 个 事 实 后 要 返 回 的 地 方 。 创 建 好 了 回 溯 点 后 , 推 理<br />

机 取 第 一 个 事 实 father(“Bill”,”John”) 并 检 查 是 否 与 目 标 匹 配 。 显 然 不 行 , 因 为 Bill 和 Sue 是 不<br />

同 的 ( 当 然 John 与 Jim 也 不 一 样 , 但 那 已 经 不 重 要 了 )。 这 时 推 理 机 就 回 到 回 溯 点 检 查 第 二<br />

个 事 实 , 但 在 此 之 前 它 会 把 回 溯 指 针 指 到 第 三 个 事 实 father(“Sue”,”Jim”)。 接 着 , 检 查 第 二 个<br />

事 实 , 也 与 目 标 不 匹 配 。 推 理 机 又 回 到 回 溯 点 , 由 于 已 经 是 谓 词 father 的 最 后 一 个 事 实 , 没<br />

43


有 回 溯 点 可 放 了 , 它 就 直 接 检 查 事 实 是 否 与 目 标 匹 配 。 是 的 , 事 实 与 目 标 匹 配 , 谓 词 与 对 象<br />

匹 配 , 推 理 机 回 答 Yes。<br />

如 果 我 们 提 出 的 目 标 是 :<br />

?‐father("Sue",X).<br />

X 是 大 写 的 , 它 是 一 个 还 没 有 值 的 变 量 。 可 能 这 里 用 Father 比 用 X 要 好 , 不 过 在 一 个 目<br />

标 里 有 两 个 一 样 的 标 识 ( 一 个 谓 词 father 和 一 个 变 量 Father) 也 可 能 容 易 引 起 混 乱 。<br />

回 车 。 事 情 差 不 多 , 推 理 机 拿 到 目 标 , 在 程 序 事 实 中 查 看 谓 词 father。 把 回 溯 点 放 在 第<br />

二 个 事 实 上 , 检 查 第 一 个 事 实 是 否 匹 配 目 标 。 不 行 , 推 理 机 来 到 回 溯 点 检 查 下 一 个 事 实 , 在<br />

此 之 前 会 先 把 回 溯 点 放 在 再 下 一 个 事 实 上 , 检 查 第 二 个 事 实 , 也 与 目 标 不 匹 配 。 推 理 机 又 来<br />

到 回 溯 点 , 检 查 目 标 与 father(“Sue”,”Jim”) 是 否 匹 配 。 谓 词 和 对 象 Sue 是 匹 配 的 , 但 变 量 呢 ?<br />

因 为 现 在 变 量 X 还 没 有 值 , 所 以 推 理 机 可 以 给 X 赋 值 为 Jim 从 而 取 得 匹 配 。 在 <strong>Prolog</strong> 中 这 叫<br />

绑 定 变 量 。 现 在 X 被 绑 定 为 ”Jim”, 目 标 与 事 实 匹 配 。 推 理 机 这 时 报 告 说 X=”Jim”, 我 们 把 它<br />

叫 目 标 的 解 。<br />

推 理 机 得 到 一 个 解 后 , 并 不 会 停 下 来 。 当 有 更 多 的 事 实 需 要 核 实 时 , 它 就 会 进 行 下 去 ,<br />

直 到 穷 尽 事 实 。 有 时 这 也 称 为 解 的 不 懈 搜 索 。<br />

到 目 前 为 止 , 我 们 都 还 是 只 用 了 简 单 的 目 标 与 事 实 。 现 在 来 看 一 下 复 合 目 标 与 事 实 和 规<br />

则 。 假 设 程 序 中 有 如 下 的 子 句 :<br />

mother("Bill", "Lisa").<br />

father("Bill", "John").<br />

father("Pam", "Bill").<br />

father("Jack", "Bill").<br />

parent(Child, Parent) :‐<br />

mother(Child, Parent); % 注 意 这 里 的 分 号 !<br />

father(Child, Parent).<br />

目 标 是 :<br />

?‐ father(AA, BB), parent(BB, CC).<br />

这 个 目 标 是 说 : 我 们 要 找 这 样 的 AA、BB 和 CC 的 名 字 ,BB 是 AA 的 父 亲 而 CC 是 BB 的<br />

父 母 。 所 有 三 个 标 识 都 是 大 写 开 头 的 , 所 以 它 们 是 变 量 并 且 开 始 时 都 没 有 被 绑 定 。<br />

前 面 我 们 说 过 , 推 理 机 总 是 从 左 到 右 地 解 目 标 , 所 以 它 先 调 用 father 的 谓 词 , 执 行 father<br />

谓 词 时 它 又 按 从 上 到 下 的 顺 序 。 第 一 个 谓 词 是 mother 谓 词 , 它 会 跳 过 去 , 因 为 推 理 机 在 找<br />

father 谓 词 。 下 一 个 谓 词 在 程 序 中 是 第 一 个 father 谓 词 , 谓 词 名 与 推 理 机 查 找 的 相 匹 配 , 所<br />

以 它 要 核 对 。 但 首 先 它 要 在 第 二 个 father 谓 词 子 句 上 创 建 一 个 回 溯 点 , 然 后 再 核 查 第 一 个 子<br />

句 。<br />

用 第 一 个 子 句 , 如 果 AA 是 Bill 而 BB 是 John 我 们 就 得 到 一 个 解 。 因 为 AA 和 BB 是 还 没<br />

有 值 的 变 量 , 不 会 妨 碍 这 么 做 , 所 以 推 理 机 绑 定 AA=”Bill”,BB=”John”。 它 对 第 一 个 子 目 标<br />

有 了 一 个 解 , 接 着 转 到 第 二 个 子 目 标 :<br />

?‐ parent(BB, CC).<br />

不 过 这 时 BB 已 经 有 了 一 个 值 , 所 以 现 在 的 实 际 目 标 是 :<br />

?‐ parent("John", CC).<br />

推 理 机 调 用 parent 谓 词 , 也 就 是 在 程 序 中 查 找 谓 词 parent, 这 样 就 到 了 最 后 一 个 子 句 ,<br />

它 说 :<br />

Parent 是 Child 的 父 母 , 如 果 Parent 是 Child 的 母 亲 或 Parent 是 Child 的 父 亲<br />

现 在 的 问 题 是 , 推 理 机 可 以 使 用 这 个 规 则 吗 ? 答 案 与 子 目 标 与 规 则 的 结 论 是 否 匹 配 有<br />

关 。 如 果 匹 配 , 就 可 以 用 , 反 之 则 不 能 用 。 在 现 在 的 情 况 下 , 子 目 标 是 parent(“John”,CC),<br />

与 结 论 parent(Child,Parent) 在 Child 绑 定 为 John 的 情 况 下 是 匹 配 的 。 使 用 规 则 意 味 着 结 论 被<br />

前 提 所 替 代 。 但 做 之 前 , 推 理 机 会 注 意 刚 才 使 子 目 标 与 规 则 匹 配 的 绑 定 。 结 论 中 的 Child 已<br />

经 绑 定 为 John, 所 以 推 理 机 使 用 前 提 时 也 会 把 Child 绑 定 为 John。 这 样 , 实 际 的 规 则 就 成 了 :<br />

parent("John",Parent) :‐ mother("John",Parent); father("John",Parent).<br />

44


现 在 推 理 机 可 以 在 当 前 目 标 中 做 替 代 了 , 当 前 目 标 是 :<br />

parent("John",CC).<br />

替 代 结 果 是 :<br />

?‐ mother("John", CC); father("John", CC). % 注 意 分 号<br />

子 句 中 的 变 量 已 经 由 调 用 的 实 际 参 数 替 代 了 , 这 和 其 它 语 言 中 使 用 子 程 序 时 情 况 一 样 。<br />

现 在 的 目 标 是 一 个 or 目 标 。 也 就 是 说 , 如 果 第 一 个 前 提 没 有 匹 配 , 还 有 第 二 个 机 会 。<br />

因 此 , 推 理 机 在 第 二 个 上 放 置 一 个 回 溯 点 , 再 核 验 第 一 个 。 要 明 白 , 现 在 已 经 有 两 个 有 效 的<br />

回 溯 点 了 , 一 个 在 子 目 标 第 二 个 选 择 (father(“John,CC)) 上 , 一 个 在 程 序 father 谓 词 的 第 二<br />

个 子 句 上 。<br />

建 立 了 这 个 回 溯 点 后 , 推 理 机 就 开 始 处 理 子 目 标<br />

?‐mother(“John”,CC).<br />

调 用 mother 谓 词 , 结 果 失 败 , 因 为 mother 子 句 没 能 与 第 一 个 参 数 John 匹 配 。 失 败 后<br />

推 理 机 会 回 到 最 后 一 个 建 立 的 回 溯 点 , 它 就 在 目 标 中 , 所 以 现 在 开 始 来 追 寻 目 标 :<br />

?‐father(“John”,CC).<br />

在 这 次 调 用 father 谓 词 时 , 还 是 首 先 在 第 二 个 father 子 句 上 建 立 一 个 回 溯 点 。 记 得 前 面<br />

在 原 来 目 标 中 第 一 次 调 用 father 谓 词 时 , 我 们 已 经 在 这 个 子 句 上 建 立 过 一 个 回 溯 点 。 我 们 来<br />

勾 画 一 下 现 在 的 情 形 : 我 们 要 解 的 目 标 是 :<br />

?‐ father(AA, BB), parent(BB, CC).<br />

推 理 机 找 到 father 谓 词 的 一 个 解 , 还 找 到 了 parent 谓 词 的 一 个 规 则 , 所 以 目 标 变 成 了 :<br />

father("Bill","John"), mother("John",CC); father("John",CC).<br />

第 一 个 目 标 解 决 了 , 它 放 了 一 个 回 溯 点 在 程 序 的 谓 词 father(“Pam”,”Bill”) 上 。 解 第 二 个<br />

子 目 标 失 败 了 , 因 为 程 序 中 的 mother 谓 词 不 匹 配 。 第 三 个 目 标 现 在 正 要 解 , 是 要 核 查 程 序<br />

中 的 第 一 个 father 谓 词 , 所 以 要 先 放 一 个 回 溯 点 在 第 二 个 father 谓 词 上 。 程 序 片 断 如 下 所 示 :<br />

mother("Bill", "Lisa").<br />

father("Bill", "John").<br />

第 一 个 子 目 标 的 回 溯 点 ‐> father("Pam", "Bill").


AA = "Jack", BB = "Bill", CC = "John".<br />

AA = "Jack", BB = "Bill", CC = "Lisa".<br />

此 后 推 理 机 试 遍 了 所 有 可 能 , 再 也 没 有 留 下 回 溯 点 。 所 以 , 目 标 全 部 的 解 就 是 四 个 。<br />

哇 ! 太 棒 了 ! 歇 会 儿 吧 。 如 果 理 解 了 推 理 机 到 目 前 为 止 的 工 作 , 把 <strong>Prolog</strong> 当 作 一 种 编<br />

程 语 言 使 用 就 没 有 问 题 了 。 当 然 一 个 程 序 会 更 复 杂 ( 下 一 节 中 就 是 这 样 ), 但 理 解 了 现 在 这<br />

样 的 基 础 , 也 就 能 够 处 理 好 更 复 杂 的 。<br />

5.5.5 防 止 回 溯 :cut( 截 断 ) 7<br />

回 溯 会 使 推 理 机 不 懈 地 搜 索 , 试 图 找 到 所 有 可 能 的 解 。 有 时 这 很 好 , 就 时 就 未 必 。 比 如 ,<br />

有 时 对 目 标 有 一 个 解 就 满 意 了 。 要 找 一 个 钢 琴 调 音 师 , 在 黄 页 里 找 到 第 一 个 可 能 就 行 了 , 不<br />

需 要 翻 遍 电 话 簿 找 出 全 城 所 有 的 钢 琴 调 音 师 。 对 这 种 情 况 , 我 们 会 希 望 程 序 找 到 了 第 一 个 解<br />

就 停 下 来 。<br />

<strong>Visual</strong> <strong>Prolog</strong> 的 cut( 截 断 ) 就 是 做 这 事 儿 的 。 截 断 可 以 防 止 回 溯 , 因 而 可 以 防 止 去 搜<br />

索 更 多 的 解 。 截 断 用 惊 叹 号 “!” 表 示 。 截 断 的 作 用 很 简 单 : 一 旦 通 过 cut 后 就 不 可 能 回 溯<br />

到 它 之 前 的 谓 词 上 。 截 断 就 是 过 河 拆 桥 , 过 去 了 就 没 有 回 头 路 。<br />

在 程 序 中 截 断 的 使 用 就 如 同 在 规 则 的 体 ( 前 提 部 分 也 称 “ 体 ”) 里 写 子 目 标 一 样 。 当 处<br />

理 到 截 断 时 , 对 它 的 调 用 是 立 即 成 功 的 , 所 以 接 着 就 会 调 用 下 一 个 子 目 标 ( 如 果 有 的 话 )。<br />

一 旦 通 过 了 一 个 截 断 , 就 不 可 能 再 回 溯 到 这 个 子 句 中 在 截 断 前 面 已 经 处 理 过 的 子 目 标 上 , 也<br />

不 可 能 回 溯 到 定 义 当 前 正 在 处 理 的 谓 词 的 其 它 子 句 上 ( 因 为 当 前 谓 词 含 有 截 断 )。<br />

截 断 主 要 有 两 个 用 途 :<br />

• 当 预 先 知 道 某 些 子 句 用 来 查 找 替 代 解 时 , 不 会 进 一 步 给 出 有 意 义 的 解 , 而 只 能 消 耗<br />

时 间 和 存 储 空 间 , 这 时 使 用 截 断 , 程 序 运 行 能 更 快 , 更 省 内 存 。 这 样 的 截 断 叫 绿 色<br />

截 断 。<br />

• 程 序 逻 辑 上 需 要 截 断 , 用 于 禁 止 考 虑 替 代 解 。 这 样 的 截 断 叫 红 色 截 断 。<br />

怎 样 使 用 截 断 呢 ?<br />

下 面 给 出 程 序 中 可 以 如 何 使 用 截 断 的 例 子 。 例 子 中 的 规 则 是 示 意 性 的 , 都 表 述 相 同 的 谓<br />

词 r, 还 有 几 个 子 目 标 (a,b,c 等 )。 规 则 的 内 容 无 关 紧 要 , 重 点 是 程 序 的 流 程 。<br />

1. 防 止 回 溯 到 规 则 中 前 面 的 某 个 子 目 标<br />

我 们 先 来 看 以 下 的 规 则 :<br />

r1:‐a,b,c.<br />

规 则 r1 有 三 个 子 目 标 ,a、b、c。 通 常 推 理 机 会 先 求 解 子 目 标 a, 然 后 是 b, 最 后 是 c。<br />

当 c 失 败 了 , 推 理 机 就 回 溯 到 b( 如 果 b 这 里 有 回 溯 点 的 话 ) 或 是 a。 现 在 我 们 在 这 个 子 句<br />

中 放 一 个 截 断 :<br />

r1:‐a,b,!,c.<br />

截 断 是 一 个 惊 叹 号 , 可 以 像 其 它 子 目 标 一 样 地 放 在 程 序 中 。 现 在 在 子 目 标 b 和 c 之 间 有<br />

一 个 截 断 。 这 个 截 断 作 为 一 个 子 目 标 会 成 功 , 所 以 推 理 机 可 以 通 过 它 。 而 带 来 的 作 用 是 : 当<br />

推 理 机 通 过 截 断 后 , 在 截 断 前 的 所 有 子 目 标 ( 在 这 里 是 a 和 b) 就 再 不 会 被 当 作 潜 在 的 回 溯<br />

点 考 虑 。 这 是 告 诉 <strong>Visual</strong> <strong>Prolog</strong>, 我 们 对 推 理 机 找 到 的 子 目 标 a 和 b 的 第 一 个 解 满 意 了 。 尽<br />

管 <strong>Visual</strong> <strong>Prolog</strong> 可 以 通 过 回 溯 对 调 用 c 找 到 多 个 解 , 但 是 不 允 许 跨 过 截 断 对 调 用 a 或 b 查 找<br />

新 的 解 。 而 且 , 也 不 允 许 回 溯 到 定 义 谓 词 r1 的 其 它 子 句 。 所 以 最 终 我 们 没 有 了 这 个 子 句 中<br />

这 个 子 目 标 的 回 溯 , 也 没 有 了 相 同 谓 词 r1 的 替 代 子 句 的 回 溯 。<br />

我 们 来 看 个 具 体 点 儿 的 例 子 :<br />

person("Pete", "haircutter", 20).<br />

person("John", "piano_tuner", 50).<br />

person("Anna", "piano_tuner", 40).<br />

person("Carl", "piano_tuner", 44).<br />

数 据 库 中 有 四 个 人 , 每 个 人 有 名 字 , 有 他 们 的 职 业 和 他 们 服 务 的 索 价 。 假 设 我 们 要 找 一<br />

个 钢 琴 调 音 师 , 付 费 不 想 超 过 44 欧 元 。 可 以 让 推 理 机 在 数 据 库 中 查 找 如 下 目 标 :<br />

7<br />

本 节 主 要 取 材 于 <strong>Visual</strong> <strong>Prolog</strong> 4.0 的 语 言 指 南 。<br />

46


un :‐<br />

person(Name,"piano_tuner", Amount),<br />

Amount < 45,<br />

write(Name).<br />

查 找 后 推 理 机 会 给 出 两 个 名 字 :Anna 和 Carl, 他 们 是 符 合 要 求 的 。 不 过 用 不 着 , 满 足<br />

我 们 要 求 的 ,Anna 就 够 了 。 为 了 让 推 理 机 在 得 到 第 一 个 解 后 就 停 , 我 们 加 一 个 截 断 :<br />

run :‐<br />

person(Name,"piano_tuner", Amount),<br />

Amount < 45, !,<br />

write(Name).<br />

截 断 放 在 必 须 满 足 的 最 后 一 个 子 目 标 之 后 , 当 Amount


girl("mary").<br />

girl("jane").<br />

girl("sue").<br />

likes("jim","baseball").<br />

likes("bill","jane").<br />

程 序 中 没 有 截 断 时 ,<strong>Visual</strong> <strong>Prolog</strong> 会 拿 出 三 个 解 :Bill 的 朋 友 有 Jane、Jim 和 Sue。 但 是 ,<br />

当 定 义 friend 的 第 一 个 子 句 有 截 断 时 , 就 告 诉 了 <strong>Visual</strong> <strong>Prolog</strong> 如 果 这 个 子 句 满 足 条 件 了 , 也<br />

就 找 到 了 Bill 的 一 个 朋 友 , 不 必 再 找 了 。 这 种 类 型 的 截 断 实 际 上 是 说 : 我 们 对 找 到 的 结 果 满<br />

意 了 , 不 要 再 找 其 他 的 了 。<br />

为 了 满 足 调 用 的 条 件 , 回 溯 可 以 在 子 句 内 进 行 。 不 过 一 旦 找 到 一 个 解 ,<strong>Visual</strong> <strong>Prolog</strong> 就<br />

会 通 过 截 断 。 像 这 样 写 的 friend 子 句 , 会 返 回 一 个 也 只 返 回 一 个 Bill 的 朋 友 ( 如 果 有 的 话 )。<br />

确 定 性 和 截 断<br />

如 果 前 面 的 程 序 中 friend 的 谓 词 里 没 有 截 断 , 它 就 是 一 种 非 确 定 性 谓 词 。 它 的 意 思 是 该<br />

谓 词 有 多 重 子 句 , 通 过 回 溯 可 以 产 生 多 重 解 。 在 许 多 实 际 的 <strong>Prolog</strong> 程 序 中 , 编 程 人 员 必 须<br />

特 别 小 心 非 确 定 性 子 句 , 因 为 运 行 时 可 能 会 对 内 存 资 源 提 出 大 量 的 需 求 。 不 过 <strong>Visual</strong> <strong>Prolog</strong><br />

内 建 有 非 确 定 性 子 句 检 查 机 制 , 用 来 减 轻 编 程 人 员 的 负 担 。<br />

我 们 可 以 通 过 在 定 义 谓 词 的 规 则 的 体 中 加 入 截 断 , 使 非 确 定 性 的 谓 词 变 为 确 定 性 的 谓<br />

词 。 例 如 , 在 定 义 friend 谓 词 的 子 句 中 放 置 截 断 , 就 使 得 这 个 谓 词 变 成 了 确 定 性 的 , 因 为 它<br />

能 返 回 一 个 、 也 只 返 回 一 个 解 。 更 多 相 关 参 见 第 9 章 。<br />

5.6 递 归<br />

大 多 数 家 庭 关 系 很 容 易 用 上 面 的 办 法 来 构 造 。 但 是 当 涉 及 到 无 法 穷 尽 的 关 系 时 , 比 如 “ 祖<br />

先 ”, 上 面 的 办 法 就 不 够 用 了 。 按 前 面 的 办 法 , 我 们 需 要 这 样 来 定 义 祖 先 ancestor:<br />

ancestor(Person, Ancestor) :‐ parent(Person, Ancestor).<br />

ancestor(Person, Ancestor) :‐ parent(Person, P1), parent(P1, Ancestor).<br />

ancestor(Person, Ancestor) :‐ parent(Person, P1), parent(P1, P2), parent(P2, Ancestor).<br />

...<br />

问 题 是 这 样 定 义 下 去 子 句 没 个 完 。 要 解 决 这 个 问 题 , 就 需 要 使 用 一 种 叫 作 “ 递 归 ” 的 概<br />

念 。 递 归 是 由 递 推 的 定 义 建 立 的 , 也 就 是 用 定 义 本 身 来 作 定 义 , 比 如 :<br />

ancestor(Person, Ancestor) :‐ parent(Person, Ancestor).<br />

ancestor(Person, Ancestor) :‐ parent(Person, P1), ancestor(P1, Ancestor).<br />

第 一 个 子 句 说 : 甲 是 Person 的 祖 先 若 甲 是 Person 的 父 母 。 第 二 个 子 句 说 : 乙 也 是 Person<br />

的 祖 先 如 果 乙 是 Person 父 母 的 祖 先 。 如 果 对 递 归 还 不 熟 悉 , 可 能 会 觉 得 它 太 弯 弯 绕 了 。 但<br />

递 归 对 <strong>Prolog</strong> 来 说 是 很 基 本 的 东 西 , 会 经 常 用 到 它 。 熟 悉 了 以 后 就 会 觉 得 它 很 自 然 了 。<br />

我 们 在 祖 先 的 程 序 中 加 两 个 子 句 :<br />

parent("Bill", "John").<br />

parent("Pam", "Bill").<br />

ancestor(Person, Ancestor) :‐ parent(Person, Ancestor).<br />

ancestor(Person, Ancestor) :‐ parent(Person, P1), ancestor(P1, Ancestor).<br />

然 后 来 试 试 目 标 :<br />

?‐ ancestor("Pam", AA).<br />

我 们 自 己 来 当 推 理 机 , 在 第 二 个 ancestor 子 句 上 放 一 个 回 溯 点 , 再 来 套 用 第 一 个 子 句 ,<br />

48


结 果 新 的 目 标 是 :<br />

?‐ parent("Pam", AA).<br />

这 个 目 标 可 以 成 功 , 解 是 :<br />

AA = "Bill".<br />

接 着 我 们 用 第 二 个 ancestor 子 句 上 的 回 溯 点 来 获 取 另 外 的 解 。 得 到 新 的 目 标 是 :<br />

?‐ parent("Pam", P1), ancestor(P1, AA).<br />

还 一 样 ,Bill 是 Pam 的 父 母 , 所 以 P1=”Bill”, 接 下 来 要 看 目 标 :<br />

?‐ ancestor("Bill", AA).<br />

要 解 这 个 目 标 , 我 们 先 在 第 二 个 ancestor 子 句 上 放 一 个 回 溯 点 , 再 套 用 第 一 个 子 句 , 得<br />

到 的 目 标 是 :<br />

?‐ parent("Bill", AA).<br />

这 个 目 标 的 解 是 :<br />

AA = "John".<br />

这 样 , 我 们 查 到 了 Pam 的 两 个 祖 先 :Bill 和 John。 如 果 我 们 再 用 第 二 个 ancestor 子 句 的<br />

回 溯 点 时 , 会 得 到 如 下 的 目 标 :<br />

?‐ parent("Bill", P1), ancestor(P1, AA).<br />

我 们 会 发 现 John 是 Bill 的 父 母 , 所 以 P1 是 John, 这 又 得 出 下 面 的 目 标 :<br />

?‐ ancestor("John", AA).<br />

再 追 寻 这 个 目 标 , 得 不 到 解 了 。 所 以 我 们 总 共 只 能 找 到 Pam 的 两 位 祖 先 。<br />

递 归 很 有 用 , 但 也 有 点 难 掌 握 。 记 住 两 个 要 点 :<br />

• 递 归 要 向 前 进 。 如 果 递 归 总 在 一 个 子 句 上 转 圈 子 , 那 是 没 有 用 的 。 那 只 能 一 遍 遍 地<br />

产 生 相 同 的 答 案 。<br />

• 递 归 要 能 结 束 。 如 果 递 归 没 有 尽 头 , 程 序 就 会 不 断 地 建 立 回 溯 点 , 总 也 不 会 停 下 来 ,<br />

就 算 是 追 到 了 亚 当 和 夏 娃 也 不 会 停 , 这 当 然 不 是 所 希 望 的 。<br />

上 面 程 序 中 递 归 的 第 一 条 子 句 保 证 了 它 能 有 尽 头 , 因 为 这 个 子 句 不 是 递 归 的 ( 也 就 是 它<br />

不 会 调 用 谓 词 本 身 )。 而 第 二 个 子 句 ( 递 归 的 那 个 ), 我 们 使 得 递 归 调 用 前 祖 先 都 上 升 了 一 代 ,<br />

也 就 是 说 对 问 题 前 进 了 一 步 。<br />

5.7 辅 助 功 能 (side effect 副 作 用 )<br />

除 了 严 格 的 取 值 顺 序 ,<strong>Prolog</strong> 谓 词 还 有 一 些 辅 助 功 能 。 例 如 ,<strong>Prolog</strong> 有 一 些 预 定 义 的 读<br />

写 谓 词 。 这 些 谓 词 比 较 特 殊 , 因 为 它 们 有 以 下 特 点 :<br />

• 它 们 永 远 会 成 功<br />

• 它 们 比 简 单 地 求 值 “ 真 ” 或 “ 假 ” 能 做 更 多 的 事 。<br />

<strong>Prolog</strong> 中 每 个 谓 词 都 要 被 求 值 。 求 值 时 谓 词 取 “ 真 ” 我 们 就 说 它 成 功 了 , 反 之 取 “ 假 ”<br />

我 们 就 说 它 失 败 了 。 理 论 上 任 何 一 个 谓 词 都 可 以 取 成 功 或 失 败 , 但 某 些 谓 词 只 能 成 功 或 只 能<br />

失 败 。 我 们 已 经 碰 见 过 谓 词 fail, 它 总 是 失 败 。<br />

<strong>Prolog</strong> 中 有 一 些 谓 词 我 们 主 要 是 使 用 它 们 的 辅 助 功 能 。 这 些 谓 词 总 是 成 功 , 所 以 对 这 一<br />

点 我 们 没 有 兴 趣 , 我 们 有 兴 趣 的 是 它 除 了 成 功 还 能 做 些 什 么 。 这 就 是 它 们 的 辅 助 功 能 。 总 是<br />

成 功 而 且 主 要 是 使 用 它 们 辅 助 功 能 的 谓 词 , 比 较 重 要 的 有 读 谓 词 和 写 谓 词 。 下 面 的 目 标 会 将<br />

找 到 的 Pam 的 祖 先 写 出 来 :<br />

?‐ ancestor("Pam", AA), write("Ancestor of Pam : ", AA), nl().<br />

它 是 怎 么 工 作 的 呢 ?ancestor 调 用 会 把 Pam 的 祖 先 绑 定 给 AA,write 调 用 成 功 并 由 其 辅<br />

助 功 能 写 出 字 符 串 “Ancestor of Pam:”, 接 着 再 写 出 AA 的 值 。 因 为 write 成 功 , 所 以 会 执 行<br />

下 一 个 子 目 标 nl(), 它 的 辅 助 功 能 是 换 行 。<br />

在 PIE 中 运 行 程 序 时 , 它 会 自 己 写 出 解 , 所 以 总 起 来 看 我 们 的 输 出 和 PIE 的 会 混 在 一 起 ,<br />

这 当 然 不 是 我 们 期 望 的 。 避 免 PIE 写 输 出 的 一 个 简 单 办 法 就 是 让 目 标 没 有 解 , 看 下 面 的 目 标 :<br />

?‐ ancestor("Pam", AA), write("Ancestor of Pam : ", AA), nl(), fail.<br />

最 后 一 个 谓 词 fail 总 是 失 败 , 也 就 是 没 有 解 。 前 三 个 谓 词 调 用 的 效 果 与 前 面 说 过 的 完 全<br />

一 样 , 找 到 一 个 祖 先 , 写 出 来 了 , 遇 到 fail, 失 败 了 。 所 以 就 又 会 回 到 回 溯 点 ( 如 果 有 的 话 ),<br />

49


找 出 另 一 个 祖 先 , 写 出 来 , 又 失 败 , 又 找 , 又 写 , 直 到 找 出 所 有 的 , 再 也 没 有 回 溯 点 了 , 整<br />

个 目 标 也 失 败 了 。 看 看 , 我 们 找 到 了 所 有 的 解 , 但 目 标 失 败 了 。<br />

有 几 个 要 点 , 需 要 注 意 :<br />

• 目 标 本 身 没 有 一 个 解 , 但 我 们 需 要 的 解 由 于 某 些 谓 词 的 辅 助 功 能 ( 副 作 用 ) 而 得 到<br />

了 。<br />

• 失 败 的 副 作 用 并 不 是 什 么 都 不 做 。<br />

这 些 是 一 个 事 物 的 两 面 , 表 示 了 不 同 的 倾 向 。 乐 观 地 说 , 有 某 些 可 能 性 可 以 利 用 , 而 悲<br />

观 地 说 , 要 当 心 这 些 副 作 用 , 它 们 并 非 不 作 为 , 即 使 在 当 前 目 标 没 有 解 的 情 况 下 也 是 这 样 。<br />

任 何 一 个 学 习 <strong>Prolog</strong> 的 人 , 迟 早 都 会 遇 到 程 序 中 失 败 部 分 给 出 的 不 期 望 的 输 出 。 一 个<br />

小 建 议 或 许 有 帮 助 : 把 “ 计 算 ” 用 的 代 码 和 输 入 输 出 的 代 码 分 开 来 。<br />

在 我 们 上 面 的 例 子 中 , 所 有 谓 词 都 是 “ 计 算 ” 谓 词 , 都 是 计 算 某 种 家 庭 关 系 的 。 如 果 要<br />

写 出 什 么 ( 比 如 要 写 出 祖 先 ) 时 , 建 立 一 个 分 开 的 写 祖 先 的 谓 词 , 然 后 用 这 个 谓 词 调 用 “ 计<br />

算 ” 祖 先 的 谓 词 。<br />

5.8 小 结<br />

这 一 章 中 我 们 了 解 了 <strong>Prolog</strong> 的 一 些 基 本 特 点 。 我 们 看 到 了 事 实 、 规 则 和 目 标 , 学 习 了<br />

<strong>Prolog</strong> 的 执 行 策 略 , 包 括 失 败 和 回 溯 。 回 溯 对 一 个 问 题 可 能 会 给 出 多 个 解 。 还 了 解 了 谓 词 的<br />

辅 助 功 能 ( 副 作 用 )。<br />

50


第 6 章 <strong>Prolog</strong> 的 数 据 模 型<br />

8<br />

一 般 地 说 , 计 算 机 程 序 ( 或 更 广 义 地 , 一 个 信 息 系 统 ) 是 由 数 据 、 操 控 数 据 的 方 法 及 用<br />

户 接 口 构 成 的 。 前 一 章 我 们 关 注 的 重 点 是 <strong>Prolog</strong> 程 序 怎 样 操 控 数 据 为 用 户 找 到 答 案 。 这 一<br />

章 里 , 我 们 要 深 入 地 看 一 下 <strong>Prolog</strong> 程 序 的 数 据 是 如 何 构 造 的 。 我 们 会 看 到 多 变 而 又 典 雅 的<br />

创 建 数 据 结 构 的 方 法 。 我 们 主 要 集 中 精 力 来 了 解 , 数 据 在 <strong>Prolog</strong> 中 可 以 操 作 前 是 怎 样 模 型<br />

化 的 , 因 此 , 不 会 有 太 多 的 可 执 行 代 码 的 例 子 。 前 面 我 们 通 过 PIE 学 习 和 开 发 <strong>Prolog</strong>, 本 章<br />

还 要 继 续 , 只 是 在 以 后 的 章 节 中 才 会 使 用 <strong>Visual</strong> <strong>Prolog</strong> IDE。<br />

6.1 Domains( 域 )<br />

第 5 章 里 所 有 的 人 都 是 用 “Bill”、“John” 和 “Pam” 等 等 来 表 示 的 , 这 些 名 字 每 个 都 代<br />

表 了 一 个 人 。 在 <strong>Prolog</strong> 中 , 我 们 对 数 据 的 看 法 是 不 一 样 的 。 如 <strong>Prolog</strong> 这 样 的 编 程 语 言 ,“Bill”<br />

是 一 个 由 双 引 号 包 围 着 的 字 符 序 列 , 这 样 的 字 符 序 列 称 为 串 。 一 个 串 可 以 表 示 一 个 值 。 在 这<br />

种 情 况 下 ,“Bill” 这 个 值 可 以 赋 给 变 量 “Name”。 这 样 的 表 示 方 法 称 为 数 据 类 型 。 在 <strong>Prolog</strong><br />

中 有 好 些 数 据 类 型 , 下 面 是 其 中 的 一 些 :<br />

• char: 这 种 类 型 的 值 是 由 在 单 引 号 间 的 单 个 字 符 构 成 的 。 如 ’a’、’b’。<br />

• string: 串 是 由 双 引 号 包 围 的 一 系 列 字 符 组 成 的 。 如 ”this is a long string”、”This is a<br />

string with @#$%^&*() strange characters”。<br />

• integer: 这 个 类 型 是 有 正 负 号 的 整 数 。 省 略 符 号 时 是 正 数 。<br />

• unsigned: 无 符 号 整 数 。<br />

• real: 这 个 类 型 是 有 符 号 数 , 可 以 有 小 数 部 分 。<br />

数 据 类 型 描 述 了 数 据 出 现 的 状 态 。 可 以 把 数 据 类 型 看 作 为 一 组 可 能 值 。 在 <strong>Prolog</strong> 中 这<br />

一 套 值 称 为 domain( 域 )。 我 们 这 里 说 的 域 被 称 为 简 单 数 据 类 型 或 simple domain。 人 的 名 字 ,<br />

在 简 单 数 据 类 型 中 是 串 。 域 为 变 量 提 供 了 一 组 值 , 域 numbers 提 供 了 一 个 数 值 范 围 。 大 多<br />

数 的 域 我 们 都 是 熟 悉 的 , 它 们 提 供 我 们 常 遇 到 的 传 统 的 值 。<br />

简 单 域 提 供 的 值 用 于 描 述 单 一 特 性 。 但 是 , 个 体 的 描 述 需 要 更 多 的 特 征 而 不 仅 只 是 名 字 。<br />

如 果 需 要 描 述 所 有 这 些 特 征 而 不 是 单 一 的 , 怎 么 办 呢 ? 我 们 需 要 加 在 一 起 描 述 一 组 特 性 的 方<br />

法 , 也 就 是 说 , 我 们 需 要 一 种 机 制 来 描 述 compound domain( 复 合 域 ), 它 把 简 单 域 组 合 在<br />

一 起 形 成 一 个 集 合 。<br />

6.2 改 进 家 庭 知 识 系 统<br />

如 果 我 们 接 着 研 究 上 一 章 中 的 家 庭 关 系 , 可 能 会 发 现 一 个 问 题 , 就 是 我 们 很 难 确 定 一 个<br />

人 (person) 的 性 别 , 除 非 这 个 人 是 父 亲 或 母 亲 。<br />

这 个 问 题 出 在 开 始 组 织 知 识 系 统 时 方 法 不 好 。 我 们 是 从 考 虑 个 体 间 的 长 幼 关 系 和 父 母 关<br />

系 入 手 , 形 成 知 识 系 统 的 。 如 果 我 们 一 开 始 就 把 着 重 点 放 在 个 体 上 , 结 果 会 大 不 相 同 。 我 们<br />

的 个 体 是 人 , 一 个 人 有 名 字 , 还 有 性 别 , 还 有 其 它 许 多 特 性 , 不 过 这 其 它 的 暂 时 不 在 我 们 关<br />

心 的 范 围 内 。 因 此 , 我 们 可 以 这 样 来 定 义 person 谓 词 :<br />

person("Bill", "male").<br />

person("John", "male").<br />

person("Pam", "female").<br />

person 谓 词 的 第 一 个 参 数 是 名 字 而 第 二 个 参 数 是 性 别 。 若 要 把 谓 词 看 成 一 种 关 系 , 可 以<br />

认 为 person 谓 词 是 一 个 名 字 与 性 别 之 间 的 关 系 。 或 者 说 是 在 一 个 个 体 内 的 关 系 而 非 个 体 之<br />

间 的 关 系 。 这 样 的 谓 词 称 为 函 子 ,6.3 节 中 还 要 介 绍 很 多 函 子 的 内 容 。<br />

下 面 , 我 们 不 用 父 亲 和 母 亲 做 事 实 , 而 用 双 亲 做 事 实 , 用 父 亲 和 母 亲 做 规 则 :<br />

8<br />

本 章 是 Sabu Francis (Tangential Solutions) 和 Thomas Linder Puls (<strong>PDC</strong>) 的 the tutorial Fundamental <strong>Prolog</strong>(Part2)<br />

的 改 写 。<br />

51


parent("Bill", "John").<br />

parent("Pam", "Bill").<br />

father(Person, Father) :‐ parent(Person, Father), person(Father, "male").<br />

mother(Person, Mother) :‐ parent(Person, Mother), person(Mother, "female").<br />

注 意 : 用 上 面 这 样 的 关 系 “ 推 得 ” 的 father, 不 可 能 是 女 性 , 在 这 一 点 上 这 个 知 识 系 统<br />

有 其 内 在 的 一 致 性 , 这 是 和 用 其 它 方 式 表 达 时 不 一 样 的 。<br />

通 过 上 面 的 例 子 可 以 看 出 , 知 识 系 统 可 以 用 不 同 的 方 法 构 成 。 可 能 有 点 儿 乱 , 而 且 某 种<br />

程 度 上 说 确 实 是 乱 , 但 它 说 明 一 个 事 实 : 任 何 知 识 系 统 都 可 以 用 不 同 的 方 法 构 成 , 要 靠 我 们<br />

自 己 来 确 定 最 好 的 方 法 。 但 需 要 提 醒 一 下 , 在 科 学 上 ( 生 活 中 也 一 样 ) 很 少 有 唯 一 最 好 的 方<br />

法 。<br />

上 面 , 我 们 把 某 个 人 的 数 据 表 示 成 一 个 参 数 表 。 不 过 , 还 有 更 进 一 步 的 办 法 来 表 示 个 体 。<br />

我 们 可 以 把 名 字 和 性 别 用 所 谓 的 复 合 域 打 包 在 一 起 。 可 以 把 它 看 成 一 个 人 的 特 征 的 一 组 值 。<br />

这 是 很 有 用 处 的 , 因 为 在 <strong>Prolog</strong> 中 可 以 用 一 个 变 量 来 表 示 整 个 一 大 包 数 据 , 而 又 可 以 分 别<br />

取 得 每 个 数 据 。 比 如 , 在 <strong>Prolog</strong> 中 可 以 这 样 :<br />

P = person("Bill","male").<br />

这 时 , 变 量 P 绑 定 了 整 个 人 Bill, 但 是 也 可 以 这 样 :<br />

person(Name,Gender) = person("Bill","male").<br />

这 里 的 Name 绑 定 值 是 Bill 而 Gender 的 绑 定 值 是 male。 下 一 节 就 会 看 到 它 们 的 用 处 。<br />

6.3 复 合 域 和 函 子<br />

前 面 关 于 person 的 事 实 , 可 以 按 下 面 的 一 般 表 示 法 声 明 为 一 个 复 合 域 :<br />

person = person(string Name, string Gender)<br />

这 个 声 明 说 :person 可 以 有 两 个 参 数 , 一 个 是 Name( 名 字 ), 另 一 个 是 Gender( 性 别 ),<br />

它 们 都 是 串 类 型 。 要 注 意 , 作 为 类 型 声 明 ”string” 对 编 译 器 来 说 非 常 重 要 ,Name 和 Gender<br />

写 在 那 里 是 人 读 程 序 时 用 的 。 还 要 注 意 , 这 既 不 是 一 个 事 实 , 也 不 是 一 个 谓 词 。 事 实 只 含 有<br />

一 个 值 , 而 person 有 两 个 。 它 也 不 是 谓 词 , 因 为 谓 词 是 用 来 操 作 数 据 的 而 person 我 们 只 是<br />

用 来 存 放 某 个 人 的 数 据 的 。 声 明 表 示 系 统 中 有 个 叫 作 person 的 某 种 复 合 域 , 而 该 域 中 的 每<br />

个 实 体 有 两 个 特 征 , 用 逻 辑 变 量 Name 和 Gender 表 示 。 注 意 , 我 们 在 简 单 域 中 说 值 而 对 复<br />

合 域 则 说 实 体 , 这 是 因 为 复 合 域 中 的 实 体 可 以 包 含 不 止 一 个 值 , 而 每 个 值 又 来 自 于 简 单 域 。<br />

单 词 person 称 为 函 子 , 而 变 量 则 是 它 的 参 数 。 现 在 我 们 来 用 函 子 打 包 事 实 。 因 为 复 合<br />

域 总 是 有 函 子 的 , 所 以 本 章 中 我 们 就 用 函 子 来 表 示 复 合 域 。<br />

现 在 , 我 们 用 新 函 子 person 的 定 义 来 修 改 前 一 章 中 的 例 子 。 我 们 把 一 个 人 看 作 为 一 组<br />

双 值 , 也 就 是 说 , 我 们 定 义 Bill 是 :<br />

person("Bill","male").<br />

而 Pam 则 是 :<br />

person("Pam","female").<br />

要 表 示 Bill 是 Pam 的 父 亲 , 可 以 这 样 写 :<br />

father(person("Pam","female"),person("Bill","male")).<br />

这 与 我 们 在 第 5 章 中 用 的 father("Pam","Bill"). 形 式 是 一 样 的 , 差 别 在 于 现 在 参 数 是 复 合<br />

域 的 。 家 庭 程 序 现 在 变 成 了 :<br />

father(person("Bill","male"),person("John","male")).<br />

father(person("Pam","female"),person("Bill","male")).<br />

father(person("Sue","female"),person("Jim","male")).<br />

grandfather(Person,Grandfather) :‐<br />

father(Father,Grandfather),<br />

father(Person,Father).<br />

52


在 PIE 编 辑 器 中 输 入 上 面 的 程 序 , 如 图 6.1 示 。<br />

图 6.1<br />

带 有 复 合 域 的 程 序<br />

请 注 意 , 对 我 们 正 在 使 用 的 PIE 来 说 , 我 们 可 以 直 接 使 用 复 合 域 而 不 需 要 对 <strong>Prolog</strong> 推 理<br />

机 做 任 何 事 先 的 声 明 ( 例 如 , 对 上 面 的 程 序 , 我 们 不 需 要 声 明 person 谓 词 或 事 实 )。PIE 中 ,<br />

可 以 直 接 在 谓 词 中 使 用 复 合 域 。 这 与 在 <strong>Visual</strong> <strong>Prolog</strong> 中 必 须 先 声 明 后 使 用 不 一 样 。 第 9 章 还<br />

要 讨 论 这 个 问 题 。<br />

看 看 有 关 father 关 系 的 事 实 , 可 以 发 现 对 人 的 描 述 比 以 前 更 丰 富 了 。 现 在 每 个 人 是 用<br />

person 函 子 以 其 名 字 和 性 别 表 示 的 , 而 在 第 5 章 中 我 们 只 用 了 名 字 。<br />

输 入 完 了 新 代 码 , 用 Engine/Reset 使 PIE 推 理 机 复 位 。 然 后 用 Engine/Reconsult 使 代 码<br />

加 载 到 推 理 机 。<br />

与 前 面 一 样 , 在 对 话 窗 口 一 个 新 的 空 行 中 输 入 目 标 , 如 :<br />

grandfather(X,Y).<br />

击 回 车 键 。 推 理 机 会 回 答 :<br />

X=person(Pam,female), Y=person(Bill,male).<br />

1 solution<br />

如 图 6.2 示 。<br />

图 6.2<br />

家 庭 程 序 对 话 框<br />

在 变 量 X 里 包 含 了 Pam 的 所 有 情 况 : 她 的 名 字 和 她 的 性 别 。 想 要 分 别 取 得 名 字 和 性 别<br />

也 是 可 以 的 , 在 对 话 框 中 输 入 下 面 的 目 标 :<br />

grandfather(person(ChildName, ChildGender),person(GrandName, GrandGender)).<br />

注 意 大 写 字 母 和 句 点 。 击 回 车 键 , 推 理 机 回 答 :<br />

CHILDNAME = Pam, CHILDGENDER = female, GRANDNAME = John,<br />

GRANDGENDER = male<br />

1 Solution<br />

这 一 回 给 出 的 答 案 更 详 细 。 下 面 还 要 讨 论 。<br />

53


6.4 使 用 函 子<br />

如 果 仔 细 看 看 grandfather 谓 词 , 就 会 发 现 这 里 有 个 微 妙 的 问 题 : 每 个 人 通 常 有 两 个<br />

grandfather, 一 个 来 自 母 亲 这 边 , 另 一 个 来 自 父 亲 这 边 , 但 是 前 面 定 义 的 这 个 grandfather<br />

谓 词 只 能 得 出 父 亲 这 边 的 grandfather。 因 此 grandfather 谓 词 应 该 重 新 定 义 为 :<br />

grandFather(Person,TheGrandFather):‐<br />

parent(Person,ParentOfPerson),<br />

father(ParentOfPerson,TheGrandFather).<br />

这 个 谓 词 表 示 出 某 人 父 母 任 何 一 方 的 父 亲 都 应 该 被 视 为 这 个 人 的 grandfather。<br />

这 个 谓 词 要 成 立 , 需 要 我 们 用 函 子 定 义 一 个 father 谓 词 。father 谓 词 又 可 以 通 过 数 据 库<br />

中 的 事 实 来 解 释 系 统 中 的 parent。 与 前 面 我 们 用 事 实 表 示 的 方 法 相 比 , 这 是 一 个 较 为 正 规 的<br />

查 找 出 father 的 方 法 。 我 们 后 面 还 要 扩 展 这 个 概 念 用 相 同 的 方 式 查 找 出 母 亲 。 谓 词 father<br />

可 以 用 下 面 的 方 法 来 定 义 :<br />

第 一 种 版 本 :<br />

我 们 先 假 设 变 量 Person 是 由 Name 和 Gender 绑 定 的 , 看 一 下 谓 词 :<br />

father(Person, Father) :‐<br />

parent(Person, Father),<br />

% 第 一 行<br />

Father = person(_, "male"). % 第 二 行<br />

我 们 用 这 个 谓 词 来 查 找 father。 调 用 它 时 ,Person 绑 定 为 一 特 定 值 并 返 回 father 的 名 字 。<br />

首 先 , 推 理 机 对 第 一 行 在 数 据 库 中 查 找 parent 子 句 中 与 Person 绑 定 的 名 字 与 性 别 相 匹 配 的<br />

项 , 并 将 其 绑 定 给 第 二 个 参 数 Father, 注 意 此 时 Father 是 绑 定 在 整 个 person 上 的 。 第 二 行 ,<br />

推 理 机 再 来 验 证 新 绑 定 的 第 二 个 参 数 Father 是 否 为 男 性 。 如 果 是 , 谓 词 就 成 功 了 , 找 到 了<br />

一 个 father。 这 一 版 本 的 谓 词 代 码 ,<strong>Prolog</strong> 系 统 地 检 查 插 在 程 序 中 的 每 个 parent 事 实 , 看 是<br />

否 与 从 谓 词 头 部 传 下 来 的 第 一 个 变 量 (Person) 相 匹 配 , 如 果 匹 配 了 , 就 再 检 查 由 person 函 子<br />

构 成 的 的 第 二 个 参 数 , 看 该 函 子 第 二 个 参 数 是 否 为 “male”。<br />

这 个 例 子 表 明 了 函 子 的 一 个 重 要 特 点 : 对 函 子 的 参 数 , 我 们 可 以 用 普 通 的 <strong>Prolog</strong> 变 量<br />

分 别 提 取 、 检 查 和 绑 定 。 在 第 二 行 中 , 我 们 还 使 用 了 一 个 下 划 线 , 在 <strong>Prolog</strong> 中 这 表 示 匿 名<br />

变 量 , 当 对 某 个 变 量 我 们 不 关 心 它 取 什 么 值 时 就 可 以 这 样 用 。 这 里 我 们 对 函 子 person 的 第<br />

一 个 参 数 使 用 匿 名 变 量 , 是 因 为 这 时 我 们 不 关 心 father 的 名 字 是 什 么 , 我 们 的 关 注 点 在 于 是<br />

否 有 一 位 father。 我 们 还 使 用 了 百 分 号 “%”, 它 表 示 本 行 其 后 的 内 容 是 注 释 , 推 理 机 可 以 忽<br />

略 。<br />

第 二 个 版 本 :<br />

father(Person, person(Name, "male")) :‐<br />

parent(Person, person(Name, "male")).<br />

调 用 这 个 谓 词 时 ,Person 也 是 绑 定 于 一 个 变 量 , 也 会 返 回 father 的 名 字 。 这 两 个 版 本 背<br />

后 的 逻 辑 是 一 样 的 , 但 是 说 给 <strong>Prolog</strong> 推 理 机 的 逻 辑 不 一 样 。 第 二 个 版 本 中 , 也 检 查 parent<br />

事 实 组 , 当 对 Person 得 到 了 正 确 的 值 时 , 如 果 函 子 第 二 个 参 数 是 “male”, 程 序 就 停 止 并 返<br />

回 正 确 的 函 子 数 据 给 谓 词 头 部 。 可 以 看 出 , 函 子 与 第 一 个 版 本 中 的 不 同 , 是 它 没 有 绑 定 给 任<br />

何 中 间 变 量 。 这 个 版 本 比 第 一 个 要 简 洁 , 当 然 代 码 的 这 种 写 法 对 初 学 者 来 说 易 读 性 要 差 一 些 。<br />

所 以 我 们 可 以 仍 用 第 一 种 方 法 , 因 为 它 也 是 正 确 的 。<br />

我 们 现 在 用 这 段 代 码 来 做 一 个 复 杂 些 的 例 子 , 这 里 先 给 出 一 些 person 的 事 实 , 把 它 们<br />

输 入 到 PIE 中 :<br />

parent(person("Bill","male"),person("John","male")).<br />

parent(person("Pam","female"),person("Bill","male")).<br />

parent(person("Pam","female"),person("Jane","female")).<br />

parent(person("Jane","female"),person("Joe","male")).<br />

54


grandfather(Person,Grandfather) :‐<br />

parent(Father,ParentOfPerson),<br />

father(ParentOfPerson,GrandFather).<br />

father(Person, person(Name, "male")) :‐<br />

parent(Person, person(Name,"male")).<br />

参 看 图 6.3。<br />

图 6.3<br />

另 一 个 家 庭 数 据 库<br />

复 位 推 理 机 并 在 PIE 中 加 载 上 述 代 码 后 , 给 出 以 下 目 标 :<br />

grandFather(person("Pam","female"),W)<br />

其 结 果 将 是 :<br />

grandFather(person("Pam","female"),W)<br />

W = person(John,male)<br />

W = person(Joe,male)<br />

2 Solutions<br />

我 们 得 到 两 个 grandfather 的 名 字 。 随 便 对 这 个 程 序 再 做 些 修 改 , 增 加 一 些 子 句 并 提 出<br />

不 同 的 目 标 再 试 试 。 函 子 在 <strong>Prolog</strong> 中 是 很 重 要 的 , 我 们 需 要 对 它 有 很 好 的 了 解 。<br />

6.5 函 子 与 谓 词<br />

函 子 与 谓 词 看 起 来 差 不 多 , 但 其 实 它 们 很 是 不 同 。 从 技 术 上 说 , 函 子 表 示 的 是 一 种 逻 辑<br />

功 能 , 它 把 若 干 域 捆 在 一 起 。 简 单 说 , 函 子 是 一 种 机 制 , 它 告 诉 <strong>Prolog</strong> 推 理 机 如 何 把 数 据<br />

部 件 组 合 在 一 起 。 它 能 有 效 地 把 数 据 的 多 个 部 分 组 合 在 一 个 通 用 的 框 架 中 。 这 也 就 是 为 什 么<br />

我 们 在 前 面 说 , 函 子 是 用 来 存 放 和 取 回 数 据 值 的 , 可 以 在 需 要 的 时 候 取 回 数 据 。 它 挺 像 是<br />

<strong>Prolog</strong> 的 事 实 或 谓 词 调 用 , 但 其 实 不 是 。 它 只 是 一 个 数 据 片 断 , 可 以 用 与 处 理 串 和 数 字 差 不<br />

多 的 方 法 来 处 理 它 。<br />

要 注 意 , 函 子 与 其 它 编 程 语 言 中 的 函 数 概 念 没 有 什 么 关 系 , 它 不 是 用 来 进 行 某 种 运 算 的 。<br />

它 只 是 表 示 复 合 域 , 把 参 数 放 在 一 起 。 如 果 读 者 知 道 编 程 语 言 Pascal, 可 以 把 复 合 域 与 Pascal<br />

中 的 record 类 型 比 较 一 下 。<br />

仔 细 研 究 上 面 的 例 子 可 以 看 出 , 如 果 用 逻 辑 变 量 替 代 函 子 表 示 的 数 据 时 , 不 需 要 做 什 么<br />

特 别 的 事 。 用 逻 辑 变 量 替 代 这 样 的 数 据 时 , 写 法 与 其 它 任 何 逻 辑 变 量 没 有 什 么 不 同 : 都 是 大<br />

写 开 头 的 一 个 字 。 因 此 , 看 看 这 里 的 grandFather 谓 词 , 与 第 5 章 中 定 义 的 这 个 谓 词 , 没 有<br />

55


什 么 改 变 , 谓 词 的 逻 辑 上 没 有 改 变 。 因 而 , 也 看 不 到 谓 词 中 使 用 的 变 量 有 什 么 变 化 。<br />

使 用 函 子 的 最 大 好 处 , 是 在 维 护 代 码 时 , 我 们 可 以 随 意 修 改 函 子 中 的 参 数 而 不 用 修 改 很<br />

多 用 到 这 个 函 子 的 谓 词 。 也 就 是 说 , 当 我 们 打 算 让 函 子 person 再 增 加 一 个 参 数 时 , 只 要 还<br />

把 函 子 当 成 一 个 整 体 , 我 们 就 不 需 要 改 变 谓 词 grandfather 什 么 东 西 。 如 果 是 要 从 函 子 中 取<br />

回 某 个 特 定 的 值 , 那 就 不 得 不 改 变 调 用 代 码 了 。<br />

6.6 做 参 数 的 函 子<br />

前 面 的 person 函 子 有 两 个 参 数 :Name 和 Gender。 它 们 都 是 简 单 域 的 , 是 串 。 它 们 的<br />

值 如 “Bill” 和 “male” 通 常 称 为 常 数 , 因 为 在 程 序 中 不 会 发 生 变 化 。 此 外 , 使 用 函 子 做 为<br />

另 一 个 函 子 的 参 数 也 是 可 以 的 。<br />

比 如 , 我 们 想 要 为 “ 配 偶 ” 定 义 一 个 函 子 , 来 看 看 怎 样 用 这 个 函 子 。 配 偶 由 一 个 男 性 和<br />

一 个 女 性 组 成 , 起 码 先 让 我 们 这 样 规 定 。 这 样 一 来 , 一 对 配 偶 就 是 一 个 男 性 和 一 个 女 性 以 及<br />

他 们 名 字 的 一 个 封 装 。 配 偶 函 子 的 一 个 子 句 可 以 是 这 样 的 :<br />

couple(person("John","male"), person ("Anna","female"))<br />

在 程 序 中 可 以 有 一 个 谓 词 , 比 如 说 myPredicate(), 来 对 couple 进 行 某 种 操 作 :<br />

myPredicate(ACouple):‐<br />

ACouple=couple(person(Husband,"male"), person(Wife,"female") ),<br />

...<br />

这 个 例 子 中 ,couple 函 子 的 定 义 中 使 用 了 两 个 函 子 , 它 们 都 是 复 合 域 的 person, 而 且 每<br />

个 函 子 都 带 有 变 量 和 常 量 混 合 的 参 数 。 它 们 反 映 了 所 代 表 数 据 的 逻 辑 。 这 个 逻 辑 就 是 : 丈 夫<br />

总 是 male 而 妻 子 总 是 female, 一 对 配 偶 由 一 个 丈 夫 和 一 个 妻 子 组 成 。 所 有 这 些 与 我 们 通 常<br />

认 为 的 配 偶 意 义 是 完 全 一 致 的 。 当 然 , 它 只 是 描 述 了 我 们 周 围 大 千 世 界 的 一 部 分 。<br />

虽 然 在 PIE 中 不 能 预 先 定 义 函 子 实 际 具 有 的 语 法 类 型 , 但 是 在 <strong>Visual</strong> <strong>Prolog</strong> 中 却 可 以 做<br />

这 样 的 定 义 。 用 这 样 的 方 法 定 义 函 子 的 好 处 , 最 起 码 是 在 用 函 子 创 建 实 际 数 据 时 ,<strong>Prolog</strong> 会<br />

检 查 创 建 的 数 据 是 否 与 语 法 规 定 的 相 一 致 。<br />

函 子 还 有 另 外 一 个 特 点 :couple 函 子 的 定 义 有 两 个 参 数 , 而 这 些 参 数 的 位 置 反 映 了 它 们<br />

之 间 的 逻 辑 关 联 。 第 5 章 中 已 经 说 过 , 谓 词 参 数 的 位 置 是 由 设 计 代 码 的 编 程 者 规 定 的 。 一 经<br />

确 定 , 就 需 要 保 持 一 贯 。 同 样 , 这 个 要 求 也 适 用 于 函 子 的 创 建 。 在 couple 函 子 中 , 我 们 决<br />

定 表 示 丈 夫 的 参 数 是 第 一 个 参 数 , 而 表 示 妻 子 的 参 数 是 第 二 个 参 数 。 一 旦 这 样 确 定 了 , 无 论<br />

什 么 时 候 用 这 个 函 子 创 建 数 据 时 , 都 应 该 保 证 丈 夫 的 参 数 在 第 一 个 位 置 而 妻 子 的 参 数 在 第 二<br />

个 位 置 。<br />

回 头 来 看 一 下 couple 函 子 , 有 人 可 能 会 很 有 道 理 地 说 : 既 然 肯 定 第 一 个 参 数 就 是 丈 夫<br />

( 他 总 是 male) 而 第 二 个 参 数 就 是 妻 子 ( 她 总 是 female), 那 还 有 什 么 必 要 调 用 这 个 谓 词 时<br />

使 用 male 和 female 这 些 串 呢 ? 如 果 肯 定 第 一 个 参 数 就 是 丈 夫 而 第 二 个 参 数 就 是 妻 子 , 那 这<br />

个 调 用 就 可 以 简 化 成 :<br />

myPredicate(ACouple):‐<br />

ACouple=couple(Husband,Wife),<br />

...<br />

不 过 , 要 是 并 不 能 确 定 哪 个 是 在 第 一 个 参 数 位 置 上 时 , 可 能 就 需 要 搞 清 楚 谁 是 谁 了 。 下<br />

面 这 个 写 法 可 以 做 到 这 一 点 :<br />

myPredicate(Couple):‐<br />

Couple=couple(person(PersonsName,PersonsGender),person(SpouseName,SpouseGender) ),<br />

...<br />

用 上 面 这 样 的 函 子 , 下 面 两 个 例 子 逻 辑 上 是 一 样 的 :<br />

myPredicate(C1):‐<br />

C1=couple(person("Bill", "male"),person("Pam", "female")),...<br />

或 是 :<br />

myPredicate(C2):‐<br />

C2=couple(person("Pam", "female"),person("Bill", "male")),...<br />

56


调 用 时 , 不 同 变 量 对 应 的 绑 定 , 就 可 以 使 我 们 搞 清 楚 哪 一 个 是 哪 一 个 了 。<br />

需 要 指 出 , 在 PIE 中 ( 在 许 多 其 它 的 <strong>Prolog</strong> 推 理 机 也 一 样 ) 只 看 定 义 函 子 的 变 量 , 无 法<br />

确 定 该 函 子 使 用 的 是 简 单 域 还 是 复 合 域 变 量 。 原 因 是 , 在 PIE 中 复 合 域 是 直 接 使 用 的 , 除 了<br />

在 编 程 者 的 头 脑 里 , 并 没 有 什 么 地 方 做 声 明 。 例 如 , 我 们 要 使 用 下 面 的 复 合 域 :<br />

person(Name,Gender)<br />

对 PIE 来 说 它 是 可 以 接 受 下 面 这 样 的 逻 辑 变 量 的 :<br />

regardingAPerson(Somebody):‐<br />

Somebody=person("Pam",<br />

person("Pam",<br />

person("Pam",<br />

)<br />

person("Pam","female")<br />

)<br />

), ...<br />

但 上 面 这 段 代 码 逻 辑 上 没 有 任 何 意 义 。 当 然 ,<strong>Visual</strong> <strong>Prolog</strong> 在 对 简 单 域 和 复 合 域 的 区 别<br />

上 要 做 得 好 得 多 , 所 以 下 一 章 中 我 们 不 会 遇 到 这 样 的 问 题 。<br />

6.7 递 归 中 使 用 函 子<br />

使 用 函 子 描 述 的 数 据 , 可 以 与 其 它 任 何 数 据 一 样 来 处 理 。 例 如 , 可 以 写 一 个 使 用 函 子 的<br />

复 合 域 数 据 进 行 递 归 的 谓 词 。<br />

让 我 们 暂 时 回 到 第 5 章 中 的 ancestor 谓 词 上 来 看 看 。<br />

为 了 要 确 定 某 人 是 否 是 另 一 人 的 祖 先 , 这 个 谓 词 我 们 使 用 了 递 归 定 义 , 也 就 是 用 自 身 的<br />

定 义 来 定 义 :<br />

ancestor(Person, Ancestor) :‐ parent(Person, Ancestor).<br />

ancestor(Person, Ancestor) :‐ parent(Person, P1), ancestor(P1, Ancestor).<br />

这 个 定 义 说 : 父 母 是 祖 先 , 而 且 父 母 的 祖 先 也 是 祖 先 。 如 果 看 一 下 定 义 中 Person 和<br />

Ancestor 变 量 , 可 以 发 现 对 简 单 域 和 复 合 域 都 适 用 。<br />

我 们 先 来 定 义 这 些 数 据 :<br />

parent(person("Bill","male"),person("John","male")).<br />

parent(person("Pam","female"),person("Bill","male")).<br />

如 果 让 PIE 解 下 面 的 目 标 :<br />

P=person("pam","female"),ancestor(P,Who).<br />

我 们 可 以 得 到 :<br />

P=person("Pam","female"), WHO = person("Bill","male")<br />

P=person("Pam","female"), WHO = person("John","male")<br />

PIE 递 归 地 检 查 了 parent 事 实 , 找 出 了 存 在 的 两 个 解 。 顺 便 说 一 下 , 上 面 查 询 时 我 们 把<br />

一 个 变 量 P 绑 定 为 person 复 合 域 , 在 目 标 中 提 交 给 PIE。 它 使 代 码 可 读 性 好 了 , 但 也 再 次 说<br />

明 , 我 们 可 以 把 用 复 合 域 定 义 的 一 段 数 据 绑 定 给 任 意 的 <strong>Prolog</strong> 变 量 。<br />

6.8 使 用 函 子 的 策 略<br />

软 件 好 不 好 , 取 决 于 数 据 模 型 化 得 好 不 好 。 模 型 化 , 就 是 建 立 我 们 要 处 理 的 这 一 部 分 现<br />

实 世 界 与 软 件 内 的 数 据 结 构 之 间 的 关 联 。 如 果 数 据 模 型 化 得 不 好 , 很 可 能 软 件 也 不 会 好 , 最<br />

少 也 是 缺 乏 效 率 。 这 对 用 任 何 编 程 语 言 写 的 软 件 都 是 一 样 的 。 这 就 是 为 什 么 我 们 上 一 章 后 部<br />

要 集 中 精 力 于 实 体 并 且 要 插 入 更 丰 富 的 事 实 来 做 改 进 的 原 因 。 同 样 , 在 这 一 章 中 介 绍 的 函 子<br />

概 念 , 也 就 是 要 使 涉 及 实 体 的 模 型 化 数 据 更 加 清 晰 。<br />

<strong>Prolog</strong> 的 优 点 是 , 它 可 以 方 便 地 以 代 码 能 有 效 利 用 的 方 式 来 描 述 现 实 世 界 的 数 据 , 同 时 ,<br />

对 一 道 工 作 的 同 一 工 程 编 程 人 员 来 说 ,<strong>Prolog</strong> 也 使 代 码 有 很 好 的 可 读 性 。<br />

函 子 可 用 于 创 建 任 意 类 型 的 复 合 域 帮 助 这 种 模 型 化 过 程 。 对 打 算 用 函 子 ( 以 及 后 面 章 节<br />

57


中 将 看 到 的 其 它 数 据 类 型 ) 处 理 和 转 换 的 现 实 世 界 数 据 的 每 个 部 分 , 必 须 进 行 仔 细 检 查 , 要<br />

记 住 , 它 们 使 用 在 软 件 各 个 关 键 部 件 中 。 有 的 地 方 很 有 用 的 数 据 结 构 , 可 能 换 个 地 方 就 有 问<br />

题 。<br />

如 果 瞄 准 了 函 子 ( 或 其 它 数 据 结 构 ), 打 算 在 软 件 中 使 用 它 , 应 该 要 做 一 个 整 体 的 分 析 。<br />

看 看 软 件 的 各 个 部 分 , 再 使 用 必 需 的 函 子 。<br />

需 要 的 还 不 只 是 仔 细 考 虑 数 据 结 构 。 还 必 须 同 时 编 写 或 改 写 各 种 目 标 和 子 目 标 ( 谓 词 ),<br />

并 在 这 些 谓 词 中 使 用 已 有 的 数 据 。 目 标 和 子 目 标 包 含 的 辅 助 功 能 可 以 使 软 件 运 行 起 来 , 可 以<br />

得 到 有 价 值 的 反 馈 , 这 些 反 馈 又 可 以 帮 助 数 据 结 构 的 进 一 步 完 善 。<br />

在 这 一 章 中 , 我 们 并 没 有 整 体 地 充 实 软 件 设 计 , 我 们 只 是 动 了 动 数 据 , 这 些 数 据 建 立 了<br />

对 有 关 人 的 亲 属 关 系 ( 父 母 、 祖 父 母 、 家 庭 、 祖 先 等 ) 的 现 实 世 界 数 据 的 感 觉 。 下 一 章 , 我<br />

们 要 详 细 说 明 这 些 专 题 , 最 后 以 需 要 这 样 数 据 结 构 的 有 用 的 软 件 来 做 结 束 。<br />

6.9 小 结<br />

本 章 中 我 们 知 道 了 数 据 可 以 由 简 单 域 构 成 , 也 可 以 由 用 函 子 表 示 的 复 合 域 构 成 。 我 们 看<br />

到 了 , 函 子 参 数 的 位 置 必 须 与 它 们 所 表 示 的 逻 辑 意 义 保 持 一 致 。 我 们 还 了 解 到 , 函 子 的 参 数<br />

并 非 一 定 要 是 简 单 域 的 , 它 也 可 以 用 另 一 个 函 子 做 参 数 。 我 们 还 了 解 了 用 函 子 表 示 的 数 据 可<br />

以 如 同 任 何 普 通 的 <strong>Prolog</strong> 变 量 一 样 来 使 用 , 对 它 们 可 以 做 所 有 的 操 作 , 包 括 递 归 。<br />

我 们 也 知 道 了 必 须 要 花 时 间 对 用 来 开 发 软 件 的 现 实 世 界 的 子 集 进 行 模 型 化 , 要 熟 悉 这 些<br />

数 据 。 同 时 , 还 应 该 实 验 要 使 用 这 些 模 型 化 的 数 据 的 谓 词 , 以 便 进 一 步 改 进 它 们 。<br />

58


第 7 章 使 用 表 格 或 对 话 框 及 控 件 :<br />

一 个 小 数 据 库<br />

本 章 我 们 要 仔 细 探 究 一 下 表 格 和 表 格 中 的 控 件 。 我 们 要 开 发 一 个 简 单 功 能 的 数 据 库 , 它<br />

带 有 由 若 干 表 格 构 成 的 简 单 的 用 户 界 面 。 在 表 格 中 , 我 们 要 加 上 些 需 要 的 功 能 , 通 过 这 来 理<br />

解 表 格 中 的 控 件 是 如 何 触 发 程 序 的 。 在 后 面 附 录 A1 中 , 有 对 话 框 和 表 格 的 较 全 面 的 介 绍 ,<br />

内 容 取 自 于 <strong>Visual</strong> <strong>Prolog</strong> 帮 助 文 件 。<br />

第 一 节 我 们 要 重 复 以 前 已 经 说 过 的 东 西 , 如 果 烦 了 , 可 以 跳 过 去 。<br />

7.1 小 数 据 库<br />

数 据 库 是 由 结 构 化 了 的 数 据 集 和 配 合 这 些 数 据 工 作 的 程 序 构 成 的 。 多 年 来 已 经 提 出 过 若<br />

干 种 结 构 , 而 关 系 数 据 库 结 构 则 是 实 践 中 最 常 用 的 一 种 。 关 系 数 据 库 由 若 干 二 维 表 构 成 , 表<br />

间 又 有 着 某 种 关 联 。 表 的 结 构 是 矩 阵 式 的 , 表 7.1 显 示 了 一 些 人 和 相 应 数 据 的 表 。<br />

PersonID Name Weight Length IQ ...<br />

1 John 85 185 110 ...<br />

2 Thomas 78 190 150 ...<br />

3 Anna 65 175 130 ...<br />

4 Lidy 55 160 123 ...<br />

... ... ... ... ... ...<br />

... ... ... ... ... ...<br />

... ... ... ... ... ...<br />

表 7.1 数 据 库 的 一 个 表<br />

表 中 的 每 一 行 描 述 了 一 个 人 的 特 征 。 我 们 可 以 看 到 ,John 是 85 千 克 重 ,185 公 分 高 ,<br />

IQ 是 110。 数 据 库 中 的 一 行 称 为 一 个 记 录 。 每 一 列 用 于 一 个 特 征 ( 体 重 、 身 高 等 等 )。 在 数<br />

据 库 中 这 样 一 个 特 征 又 称 为 一 个 属 性 。 如 果 读 者 熟 悉 统 计 程 序 , 就 会 看 出 这 样 的 表 与 数 据 矩<br />

阵 相 类 似 。 这 里 可 能 没 有 什 么 难 理 解 的 东 西 , 但 还 是 有 两 点 要 指 出 来 。 第 一 个 , 属 性 PersonID<br />

是 个 特 殊 的 东 西 , 在 数 据 库 中 它 是 用 来 标 示 记 录 的 。 有 人 可 能 会 想 , 人 的 名 字 用 于 这 个 目 的<br />

也 行 。 但 是 , 偶 尔 人 有 重 名 的 , 出 现 这 种 情 况 标 识 就 不 是 唯 一 的 了 。 所 以 数 据 库 中 通 常 会 用<br />

社 会 保 险 号 作 标 识 号 。 第 二 个 , 关 于 第 一 行 的 内 容 问 题 。 这 是 一 个 标 题 行 , 用 于 人 的 阅 读 ,<br />

在 数 据 库 的 表 里 是 没 有 的 。 也 就 是 说 在 数 据 库 里 我 们 只 记 录 了 “1”、“John”、“85” 等 等 数<br />

据 。 呈 现 给 用 户 时 清 楚 地 表 示 数 据 代 表 的 意 义 , 这 是 数 据 库 设 计 和 编 程 人 员 的 责 任 。<br />

一 个 数 据 库 会 由 很 多 个 表 构 成 , 但 这 一 节 里 我 们 建 个 小 的 , 只 有 一 个 表 。 表 里 我 们 放 些<br />

有 关 人 的 数 据 。 为 了 节 省 输 入 工 作 , 定 义 的 表 只 有 五 列 , 分 别 是 PersonID、Name、Weight、<br />

Length 和 IQ, 与 表 7.1 中 的 一 样 。 行 数 多 少 也 不 重 要 , 我 们 就 用 表 7.1 中 四 个 人 的 数 据 好 了 。<br />

为 了 在 <strong>Prolog</strong> 中 表 示 一 个 表 , 我 们 要 用 一 个 函 子 , 函 子 的 结 构 是 :<br />

person(id, name, weight, length, iq).<br />

要 注 意 , 不 要 用 大 写 开 头 , 以 防 <strong>Prolog</strong> 把 它 们 当 成 变 量 了 。<br />

在 <strong>Prolog</strong> 程 序 中 存 放 数 据 有 一 个 很 方 便 的 方 法 , 就 是 借 助 于 事 实 。 事 实 段 是 <strong>Prolog</strong> 程<br />

序 特 殊 的 一 部 分 , 可 以 用 以 存 储 事 实 , 这 并 不 奇 怪 。 但 什 么 是 事 实 呢 ? 最 简 单 的 事 实 就 是 对<br />

象 的 一 个 特 征 , 一 个 属 性 , 一 个 值 。 比 如 , 我 是 Thomas, 有 身 高 , 可 以 测 得 是 190, 也 就<br />

是 1.9 米 。 看 看 表 7.1, 里 面 都 是 事 实 ! 其 实 表 里 的 每 一 项 都 是 一 个 事 实 。 可 以 这 样 表 示 它<br />

们 :<br />

fact("Thomas","Length",190).<br />

fact("John","Length",185).<br />

fact("Anna","IQ",130).<br />

59


还 记 得 第 6 章 中 的 函 子 吧 。 我 们 可 以 不 用 分 别 写 每 个 事 实 , 而 可 以 把 一 个 对 象 的 所 有 事<br />

实 用 一 个 函 子 集 中 起 来 。 我 们 可 以 用 函 子 person() 及 适 当 的 参 数 来 这 样 做 。 在 PIE 中 可 以 直<br />

接 使 用 函 子 , 但 在 <strong>Visual</strong> <strong>Prolog</strong> 中 则 必 须 通 知 VIP 我 们 要 用 函 子 了 。 需 要 声 明 我 们 要 用 函 子<br />

的 形 式 来 存 放 事 实 。 在 合 适 的 地 方 要 声 明 说 :<br />

facts<br />

person : (string ID, string Name, integer Weight, integer Length, integer IQ).<br />

这 个 声 明 表 示 , 有 一 个 person 函 子 , 带 有 五 个 参 数 :<br />

• 第 一 个 是 串 类 型 的 , 用 于 表 示 标 识 ,<br />

• 第 二 个 也 是 串 类 型 的 , 是 名 字 ,<br />

• 第 三 个 是 整 数 类 型 的 , 表 示 体 重 ,<br />

• 第 四 个 是 整 数 类 型 的 , 表 示 身 高 ,<br />

• 第 五 个 还 整 数 类 型 的 , 表 示 智 商 。<br />

ID、Name 等 等 , 是 给 人 看 的 , 编 译 器 不 管 这 些 。<br />

声 明 之 后 , 在 程 序 中 就 可 以 使 用 下 面 这 样 的 子 句 了 :<br />

person("1", "John", 85, 185, 110).<br />

person("2", "Thomas", 75, 190, 150).<br />

标 识 号 ID 用 串 来 表 示 有 点 儿 怪 , 它 明 明 是 个 数 啊 ! 但 这 里 还 是 有 原 因 的 。 主 要 因 为 ID<br />

虽 然 是 个 数 , 但 它 是 代 码 , 所 以 用 串 来 表 示 更 恰 当 。<br />

7.2 VIP 中 的 数 据 库<br />

我 们 为 这 个 小 数 据 库 来 创 建 一 个 新 的 工 程 , 这 里 叫 它 chap07db 好 了 , 当 然 也 可 以 用 别<br />

的 名 字 。 创 建 和 构 造 工 程 , 接 下 来 添 加 数 据 库 部 分 。 因 为 它 是 个 有 关 人 员 的 数 据 库 , 我 们 来<br />

创 建 一 个 模 块 保 存 这 个 数 据 库 。 为 使 工 程 的 结 构 清 晰 , 把 这 个 模 块 放 在 一 个 独 立 的 包 中 , 包<br />

名 用 person。<br />

图 7.1<br />

创 建 person 包<br />

60


先 创 建 包 person, 在 工 程 树 中 点 击 根 目 录 创 建 包 : 点 File/New, 在 创 建 工 程 项 对 话 框 左<br />

半 部 中 选 Package, 输 入 包 的 名 字 person。 如 图 7.1 示 。<br />

点 , 这 个 包 就 会 出 现 在 工 程 树 中 。 再 次 构 造 工 程 ( 最 好 是 经 常 对 工 程 作 构 造 ),<br />

现 在 就 必 须 来 创 建 放 数 据 库 的 模 块 了 。<br />

VIP 是 种 面 向 对 象 的 语 言 , 我 们 下 一 章 中 要 介 绍 。 不 过 这 样 一 来 , 本 章 中 遇 到 的 事 情 就<br />

有 点 儿 像 变 戏 法 。 下 一 章 中 事 情 会 越 来 越 清 楚 的 , 现 在 只 需 要 知 道 在 面 向 对 象 环 境 中 , 计 算<br />

机 的 程 序 里 有 一 个 叫 “ 类 ” 的 东 西 就 可 以 了 。 可 以 把 类 看 成 是 一 个 模 块 , 不 过 要 记 住 这 只 是<br />

一 个 片 面 的 描 述 , 类 还 有 更 多 的 含 义 。 现 在 , 类 就 是 个 放 数 据 库 的 好 地 方 。<br />

我 们 来 创 建 一 个 类 , 把 这 个 类 放 在 person 包 里 以 使 工 程 结 构 清 晰 。 点 选 person 包 , 再<br />

点 File/NewInExistingPackage。 在 打 开 的 创 建 工 程 项 对 话 框 中 , 选 左 半 部 中 的 Class, 输 入 类<br />

名 person。 把 包 名 和 类 名 弄 得 一 样 好 像 有 些 怪 , 不 过 也 没 什 么 关 系 , 包 和 类 是 完 全 不 同 的 项 。<br />

点 选 Existing Package 选 钮 , 要 保 证 后 面 框 里 的 是 “person.pack”。 然 后 去 掉 Creates Objects<br />

选 项 并 点 击 , 如 图 7.2。<br />

图 7.2<br />

创 建 person 类<br />

点 击 后 ,IDE 会 打 开 两 个 编 辑 窗 口 , 一 个 是 person.cl, 另 一 个 是 person.pro。 这<br />

会 儿 可 以 先 关 了 person.cl, 后 面 再 说 。Person.pro 是 放 数 据 库 需 要 的 东 西 的 文 件 。 我 们 先 要<br />

声 明 有 一 个 数 据 库 , 在 VIP 中 是 用 单 词 facts 来 做 这 件 事 的 。 在 person.pro 中 加 上 类 事 实 子<br />

句 的 代 码 。 常 数 (constants) 和 子 句 (clauses) 部 分 已 经 有 了 :<br />

constants<br />

className = "person/person".<br />

classVersion = "".<br />

class facts<br />

person : (string ID, string Name, integer Weight, integer Length, integer IQ).<br />

clauses<br />

61


classInfo(className, classVersion).<br />

用 这 个 声 明 , 我 们 表 示 在 这 个 类 中 有 一 个 由 事 实 构 成 的 数 据 库 。 每 个 事 实 是 一 个 带 有 五<br />

个 参 数 的 函 子 , 五 个 参 数 中 两 个 是 串 三 个 是 整 数 。 保 存 文 件 , 关 闭 编 辑 器 。 这 些 步 骤 是 例 行<br />

公 事 , 本 章 中 后 面 就 不 再 说 “ 保 存 、 关 闭 ” 什 么 的 了 。<br />

现 在 来 执 行 这 个 程 序 时 , 数 据 库 是 空 的 。 实 际 上 , 编 译 器 会 警 告 说 person 谓 词 尚 未 使<br />

用 。 要 用 数 据 库 , 还 要 给 程 序 增 加 功 能 。 最 好 是 先 给 数 据 库 加 上 几 条 记 录 。 我 们 在 任 务 菜 单<br />

中 创 建 一 个 菜 单 项 用 于 数 据 库 的 功 能 。 在 TaskMenu 中 添 加 一 个 Database 菜 单 项 , 其 中 还 要<br />

带 有 FillDatabase 子 项 。 如 图 7.3。 以 后 我 们 还 会 再 增 加 一 些 子 项 。<br />

图 7.3<br />

创 建 菜 单 项<br />

现 在 我 们 有 了 一 个 填 充 数 据 库 的 菜 单 项 , 不 过 要 能 起 作 用 还 得 在 工 程 中 适 当 的 地 方 加 入<br />

需 要 的 代 码 。 我 们 决 定 把 与 数 据 库 有 关 的 代 码 都 放 在 类 ( 模 块 )person 中 , 双 击 工 程 树 中 的<br />

person.pro 文 件 名 , 在 编 辑 器 中 打 开 它 , 在 下 面 这 段 代 码 之 后 :<br />

clauses<br />

classInfo(className, classVersion).<br />

加 入 以 下 的 代 码 :<br />

clauses<br />

fillDatabase() :‐<br />

assert(person("1", "John", 85, 185, 110)),<br />

assert(person("2", "Thomas", 75, 190, 150)),<br />

assert(person("3","Anna", 65, 175, 123)),<br />

assert(person("4", "Lidy", 55, 160, 135)),<br />

stdIO::write("Database has been filled"), stdIO::nl.<br />

这 段 代 码 让 计 算 机 给 数 据 库 添 加 了 四 条 记 录 。assert 是 一 个 标 准 谓 词 , 用 于 数 据 库 事 实<br />

的 操 作 。<br />

代 码 加 上 了 , 但 还 有 点 小 问 题 。 在 类 person 内 部 知 道 了 这 段 代 码 , 但 外 部 并 不 知 道 。<br />

62


这 就 是 个 问 题 了 , 因 为 我 们 想 要 把 任 务 窗 口 中 的 菜 单 项 与 person 类 中 的 这 段 代 码 联 系 起 来 。<br />

所 以 , 我 们 需 要 对 工 程 中 其 它 部 分 说 明 这 段 代 码 的 存 在 。 这 就 需 要 用 到 person.cl 文 件 了 ,<br />

可 以 把 这 个 文 件 看 成 一 个 服 务 台 或 一 个 窗 口 , 在 这 里 了 解 模 块 person 里 有 什 么 可 用 的 。 打<br />

开 这 个 文 件 , 在 下 面 代 码 之 后 :<br />

predicates<br />

classInfo : core::classInfo.<br />

% @short Class information predicate.<br />

% @detail This predicate represents information predicate of this class.<br />

% @end<br />

加 上 :<br />

fillDatabase : () procedure.<br />

注 意 行 尾 的 句 点 。Person.cl 中 的 这 一 行 , 通 知 程 序 中 person 类 以 外 的 部 分 , 有 个 谓 词<br />

fillDatabase 的 存 在 , 这 样 就 可 以 用 它 了 。 现 在 , 我 们 把 菜 单 项 FillDatabase 和 谓 词 fillDatabase<br />

联 系 起 来 。 通 常 的 办 法 , 先 选 好 TaskWindow.win 文 件 , 击 右 键 , 选 CodeExpert。 在 代 码 专<br />

家 系 统 中 ( 其 实 就 是 在 Dialog and Window Expert 窗 口 里 ) 打 开 Menu/TaskMenu/id_database,<br />

高 亮 “id_database_filldatabase‐>… ” 为 这 个 选 项 添 加 代 码 ( 参 见 图 7.4 ), 双 击<br />

“id_database_filldatabase‐>…” 进 入 代 码 , 把 代 码 改 成 :<br />

predicates<br />

onDatabaseFilldatabase : window::menuItemListener.<br />

clauses<br />

onDatabaseFilldatabase(_Source, _MenuTag) :‐<br />

person::filldatabase().<br />

图 7.4<br />

为 fillDatabase 加 代 码<br />

63


这 段 代 码 是 说 , 当 用 户 点 击 菜 单 项 FillDatabase 时 , 程 序 就 应 该 调 用 在 person 类 中 的<br />

fillDatabase 谓 词 。 类 和 谓 词 的 名 字 用 了 一 个 双 冒 号 连 在 一 起 , 表 示 person 是 一 个 类 的 名 字<br />

而 fillDatabase 是 谓 词 名 。<br />

现 在 执 行 程 序 并 点 击 菜 单 项 Database/FillDatabase 的 话 ,person 类 中 的 fillDatabase 代 码<br />

就 会 执 行 , 数 据 库 中 会 插 入 四 条 记 录 。 试 试 吧 ! 关 上 打 开 的 文 件 , 执 行 程 序 。 如 果 计 算 机 问<br />

是 否 要 包 含 一 个 或 多 个 文 件 , 回 答 是 。 当 点 击 菜 单 项 Database/FillDatabase 时 , 记 录 就 会 插<br />

入 到 空 的 数 据 库 中 ,Message 窗 口 还 会 显 示 一 条 消 息 。 说 一 下 , 这 一 章 里 所 有 的 对 话 就 用 消<br />

息 窗 口 了 , 这 么 做 输 入 的 工 作 量 最 小 。 当 然 , 要 是 愿 意 的 话 , 也 可 以 按 你 喜 欢 的 办 法 去 做 。<br />

数 据 库 是 填 好 了 , 不 过 我 们 还 看 不 到 任 何 记 录 , 只 得 了 个 没 多 大 价 值 的 消 息 , 谁 信 计 算<br />

机 啊 ? 好 , 我 们 再 加 个 谓 词 , 把 数 据 库 里 的 记 录 列 个 表 出 来 。 需 要 的 步 骤 和 我 们 填 充 数 据 库<br />

的 相 类 似 , 所 以 下 面 只 列 出 动 作 和 代 码 , 不 多 解 释 了 。<br />

先 在 person.pro 文 件 中 加 入 listDatabase 谓 词 的 代 码 。 紧 随 在 fillDatabase 代 码 之 后 插 入<br />

下 面 的 代 码 :<br />

listDatabase() :‐<br />

person(ID, Name, Weight, Length, IQ),<br />

stdIO::write("ID: ", ID, ", Name: ", Name, ", Weight: ", Weight, ", Length: ", Length, ", IQ: ", IQ),<br />

stdIO::nl,<br />

fail.<br />

listDatabase().<br />

这 里 用 了 第 5 章 中 学 到 的 一 个 窍 门 。 调 用 listDatabase 谓 词 时 , 事 实 数 据 库 中 第 一 条 记<br />

录 就 匹 配 了 , 然 后 调 用 写 谓 词 stdIO(), 它 的 辅 助 功 能 把 值 写 到 消 息 窗 口 中 , 接 着 谓 词 失 败 了 。<br />

推 理 机 回 溯 , 数 据 库 中 下 一 条 记 录 匹 配 并 写 出 来 , 谓 词 又 失 败 了 。 如 此 下 去 , 直 到 没 有 匹 配<br />

的 记 录 , 推 理 机 回 溯 到 第 二 个 子 句 , 那 里 什 么 也 没 有 , 谓 词 无 言 地 结 束 , 但 所 有 记 录 都 写 在<br />

消 息 窗 口 里 了 。<br />

VIP 7.1 版 中 , 可 以 用 另 一 个 方 法 替 代 这 个 fail 的 技 巧 。 这 就 是 foreach‐do‐end foreach。<br />

看 看 下 面 的 代 码 :<br />

listDatabase() :‐<br />

foreach<br />

person(ID, Name, Weight, Length, IQ),<br />

do<br />

stdIO::write("ID: ", ID, ", Name: ", Name, ", Weight: ", Weight, ", Length: ", Length, ", IQ: ", IQ),<br />

stdIO::nl,<br />

end foreach.<br />

关 键 字 foreach 查 找 perosn 的 子 句 , 一 次 一 个 , 找 到 之 后 就 把 它 用 于 do 之 后 的 程 序 段 ,<br />

当 foreach 的 搜 索 由 于 再 没 有 person 子 句 而 失 败 时 , 程 序 就 跳 到 end foreach, 谓 词 成 功 。 这<br />

样 , 用 foreach‐do‐end foreach 的 代 码 就 把 数 据 库 中 每 个 人 员 的 数 据 都 打 印 出 来 了 。<br />

再 在 person.cl 文 件 中 加 入 谓 词 listDatabase 的 声 明 , 以 便 工 程 的 其 它 部 件 可 以 使 用 它 。<br />

把 下 面 的 代 码 加 在 fillDatabase 声 明 之 后 :<br />

listDatabase : () procedure.<br />

再 给 任 务 菜 单 增 加 一 个 菜 单 项 。 在 TaskMenu.mnu 中 给 Database 添 加 ListDatabase 子 项 。<br />

再 为 这 个 菜 单 项 添 加 标 准 代 码 , 在 TaskWindow.win 上 , 击 右 键 打 开 代 码 专 家 系 统 , 点 击<br />

Menu, 点 击 TaskMenu, 点 击 id_Database, 高 亮 id_Database_ListDatabase, 点 , 把 代<br />

码 改 成 :<br />

predicates<br />

onDatabaseListdatabase : window::menuItemListener.<br />

clauses<br />

64


onDatabaseListdatabase(_Source, _MenuTag) :‐<br />

person::listDatabase().<br />

这 就 成 了 。 构 造 并 执 行 程 序 , 点 击 其 中 的 Database/ListDatabase 时 , 消 息 窗 口 会 出 现 四<br />

条 记 录 。 别 忘 了 先 填 充 数 据 库 , 不 然 什 么 也 看 不 到 。<br />

7.3 操 控 数 据 : 增 加 一 个 记 录<br />

进 展 不 错 。 不 过 , 数 据 库 好 玩 的 地 方 是 操 作 数 据 , 比 如 增 加 记 录 、 删 除 记 录 、 更 改 记 录<br />

的 内 容 等 等 。 这 一 节 我 们 就 要 添 加 这 些 代 码 。 先 从 增 添 记 录 开 始 吧 。<br />

输 入 一 个 记 录 , 需 要 一 个 表 格 。 在 VIP 中 有 两 个 办 法 , 都 差 不 多 。 可 以 用 对 话 框 (dialog)<br />

也 可 以 用 表 格 (form)。 这 个 工 程 里 我 们 用 对 话 框 , 不 过 随 你 便 , 也 可 以 用 表 格 , 差 别 实 在<br />

不 大 , 说 老 实 话 , 不 知 道 有 什 么 差 别 。 通 过 对 话 框 , 我 们 为 新 添 加 的 人 ( 新 的 记 录 ) 采 集 数<br />

据 , 点 击 时 就 把 记 录 加 入 到 数 据 库 中 。<br />

为 使 工 程 结 构 清 晰 起 见 , 把 对 话 框 也 放 到 一 个 单 独 的 包 中 , 我 们 叫 这 个 包 forms 好 了 。<br />

在 工 程 树 上 点 一 下 根 , 添 加 forms 包 。 再 选 中 这 个 包 , 添 加 一 个 对 话 框 。 点 击<br />

File/NewInExistingPackage, 在 左 窗 口 中 选 Dialog 创 建 名 为 addPerson 的 对 话 框 。 一 定 要 把 这<br />

个 对 话 框 放 在 已 有 的 forms.pack 包 中 。<br />

点 击 ,IDE 打 开 对 话 框 编 辑 器 。 可 以 看 到 很 熟 悉 的 灰 栅 格 底 的 新 对 话 框 , 里 边<br />

还 有 OK、Cancel 和 Help 三 个 按 钮 。 我 们 要 加 上 四 个 文 本 框 和 四 个 编 辑 框 , 如 图 7.5 示 。<br />

图 7.5<br />

addPerson 对 话 框<br />

要 添 加 左 上 角 那 样 一 个 标 有 “Name” 字 样 的 文 本 框 , 先 在 控 件 工 具 箱 中 点 击 带 有 大 写<br />

“T” 字 的 图 标 , 光 标 会 为 一 个 加 号 和 一 个 单 字 “Text”; 再 在 对 话 框 中 要 放 置 这 个 文 本 框 的<br />

地 方 点 击 , 这 时 这 个 文 本 框 就 得 到 焦 点 了 ( 如 果 没 有 就 再 点 击 一 下 它 ), 属 性 列 表 中 会 列 出<br />

这 个 文 本 框 的 各 项 属 性 。 在 左 列 第 三 行 可 以 看 到 Text, 它 的 右 边 是 StaticText。 把 StaticText<br />

改 成 Name, 回 车 , 就 会 看 到 文 本 框 的 标 签 变 了 。 用 同 样 的 方 法 再 把 其 它 三 个 都 改 好 。<br />

加 编 辑 框 工 作 量 稍 大 点 儿 。 控 件 工 具 箱 里 选 好 带 有 Edit 字 样 ( 头 一 行 最 右 边 ) 的 图 标 ,<br />

光 标 变 成 了 一 个 加 号 和 单 词 Edit, 在 对 话 框 要 放 编 辑 框 的 地 方 点 一 下 , 这 个 控 件 得 到 焦 点 了<br />

( 若 没 有 就 选 中 它 ), 属 性 列 表 中 也 会 出 现 这 个 控 件 的 属 性 。 这 里 有 两 个 重 要 的 属 性 : 控 件<br />

的 名 字 (Name) 和 文 本 (Text)。 名 字 在 属 性 列 表 的 第 一 行 , 开 始 时 是 edit_ctl, 我 们 把 这 个<br />

控 件 的 名 字 改 成 name_ctl, 因 为 我 们 打 算 用 它 来 接 收 新 记 录 的 那 个 人 的 名 字 。 要 注 意 名 字 开<br />

头 的 字 母 要 用 小 写 , 结 尾 也 保 留 “_ctl” 字 样 。 把 控 件 的 名 字 改 成 有 意 义 的 内 容 是 良 好 的 习<br />

惯 做 法 。 再 把 另 外 三 个 控 件 加 上 , 名 字 分 别 是 weight_ctl、length_ctl、iq_ctl。 编 辑 框 的 标 准<br />

文 本 ( 缺 省 值 )Edit 也 可 以 改 一 下 , 名 字 编 辑 框 的 可 以 是 “Type a name”, 其 它 三 个 可 以 用<br />

65


各 自 要 求 输 入 的 平 均 值 。 在 属 性 列 表 的 Text 栏 中 输 入 需 要 的 内 容 。<br />

插 入 控 件 时 , 可 能 会 发 现 要 把 它 们 对 齐 不 太 容 易 。 现 在 可 以 来 用 一 下 布 局 (layout) 工<br />

具 箱 里 的 工 具 了 。 选 好 一 组 控 件 , 使 它 们 对 齐 。<br />

可 能 会 有 个 疑 问 : 这 里 怎 么 没 有 ID 条 目 呢 ? 原 因 在 于 ID 必 须 是 唯 一 的 , 如 果 人 工 输 入 ,<br />

可 能 会 出 错 , 把 相 同 的 ID 用 到 不 同 人 的 记 录 上 。 为 了 避 免 这 种 错 误 , 我 们 要 让 程 序 来 “ 算 ”<br />

出 唯 一 的 ID。<br />

回 到 对 话 框 上 来 , 所 有 事 情 都 做 完 了 , 就 可 以 保 存 并 关 闭 对 话 框 编 辑 器 。 构 造 ( 对 , 再<br />

一 次 构 造 ) 工 程 。IDE 又 会 问 是 不 是 要 包 含 什 么 东 西 , 回 答 Yes。 好 , 现 在 有 了 一 个 表 格 了 ,<br />

不 过 还 缺 两 样 东 西 : 一 是 需 要 一 个 菜 单 项 来 打 开 这 个 表 格 , 二 是 需 要 一 些 代 码 , 当 我 们 在 表<br />

格 上 点 击 时 , 加 入 一 个 新 记 录 。<br />

加 菜 单 项 现 在 对 我 们 来 说 已 经 是 小 菜 啦 。 在 TaskMenu.mnu 中 , 在 Database 中 加 一 个 新<br />

子 项 ,AddPerson。 做 完 之 后 , 在 TaskWindow.win 上 打 开 代 码 专 家 系 统 。 选 Menu, 选 TaskMenu,<br />

选 id_database, 给 id_Database_Add_Person 添 加 标 准 代 码 。 再 把 代 码 修 改 成 :<br />

predicates<br />

onDatabaseAddPerson : window::menuItemListener.<br />

clauses<br />

onDatabaseAddPerson(Source, _MenuTag) :‐<br />

NewDialog = addPerson::new(Source),<br />

NewDialog:show().<br />

现 在 点 选 Database/AddPerson 时 , 对 话 框 就 会 打 开 , 可 以 为 新 添 的 人 输 入 数 据 了 。 不 过<br />

现 在 输 入 的 记 录 还 没 有 添 加 到 数 据 库 中 去 , 还 需 要 再 加 一 些 代 码 来 做 这 个 事 。 我 们 要 在 模 块<br />

person 中 加 一 个 谓 词 , 给 数 据 库 增 加 新 记 录 。 先 来 创 建 这 个 谓 词 的 代 码 。<br />

所 有 操 作 数 据 库 的 谓 词 都 集 中 放 在 模 块 person 中 。 打 开 person.pro(<strong>Prolog</strong> 代 码 的 存 放<br />

文 件 ), 紧 接 着 listDatabase 的 代 码 后 面 输 入 下 面 的 代 码 :<br />

addPerson(Name, Weight, Length, IQ) :‐<br />

findAll(ID, person(ID, _, _, _, _), IDList),<br />

NewID = toString(toTerm(maximum(IDList)) + 1),<br />

assertz(person(NewID, Name, Weight, Length, IQ)),<br />

stdIO::write("Person ", Name, " has been added with ID: ", NewID), stdIO::nl.<br />

谓 词 的 关 键 部 分 在 中 间 :<br />

assertz(person(NewID, Name, Weight, Length, IQ)),<br />

这 行 代 码 把 记 录 ( 连 带 新 计 算 得 到 的 唯 一 的 ID) 添 加 到 数 据 库 中 。 标 准 谓 词 assertz 使<br />

计 算 机 把 新 记 录 放 在 事 实 表 的 最 后 。 要 是 用 asserta 的 话 , 就 会 把 新 记 录 放 在 事 实 表 的 最 前<br />

面 。 给 数 据 库 列 表 时 , 就 会 看 出 使 用 这 两 个 不 同 的 谓 词 的 差 别 。 我 们 这 里 就 用 assertz。<br />

添 加 新 记 录 之 前 , 需 要 确 定 新 的 唯 一 的 ID。 这 里 使 用 了 两 个 标 准 谓 词 来 “ 计 算 ”ID, 第<br />

一 个 是 :<br />

findAll(ID, person(ID, _, _, _, _), IDList),<br />

这 个 标 准 谓 词 找 出 数 据 库 中 现 有 的 所 有 记 录 , 采 集 特 定 段 ( 函 子 的 参 数 ) 的 值 , 把 它 们<br />

放 在 一 个 表 里 。 哇 哇 , 要 做 好 多 解 释 啊 !<br />

<strong>Prolog</strong> 中 的 一 个 表 , 是 一 组 值 。 表 的 元 素 放 在 方 括 号 中 , 各 元 素 用 逗 号 分 隔 。 值 可 以 是<br />

任 意 类 型 的 , 不 过 现 在 我 们 只 用 串 和 整 数 。 下 面 的 表 的 例 子 :<br />

[ 1, 2, 3, 4, 5, 6 ]<br />

["John", "Thomas", "Anna", "Lidy"]<br />

元 素 的 数 量 没 有 限 制 , 甚 至 可 以 造 出 没 有 元 素 的 表 , 这 样 的 表 叫 空 表 , 就 是 []。 但 也<br />

还 有 一 个 限 制 , 表 中 的 元 素 必 须 是 相 同 类 型 的 。 我 们 用 下 面 的 形 式<br />

findAll(ID, person(ID, _, _, _, _), IDList),<br />

调 用 谓 词 findall 时 , 第 一 个 参 数 说 : 我 要 找 一 个 变 量 ID; 第 二 个 参 数 说 : 这 个 变 量 可 以 在<br />

66


函 子 person 的 第 一 个 位 置 上 找 到 ; 第 三 个 参 数 说 : 我 要 findall/3 把 找 到 的 值 的 表 绑 定 给 变<br />

量 IDList。 这 样 , 调 用 过 后 我 们 就 知 道 了 : 数 据 库 中 所 有 ID 的 值 都 放 在 表 IDList 中 了 。 接 下<br />

来 一 个 语 句 , 用 这 个 表 找 出 数 据 库 中 最 大 的 ID。 这 个 语 句 要 从 内 向 外 地 看 , 核 心 表 达 式 是 :<br />

(maximum(IDList))<br />

maximum/1 是 一 个 标 准 谓 词 , 用 于 计 算 指 定 表 ( 例 子 中 是 IDList) 中 最 大 的 元 素 。 再 外<br />

一 层 是 :<br />

(toTerm(maximum(IDList)) + 1)<br />

这 里 ,toTerm/1 将 IDList 表 中 最 大 的 值 转 换 成 整 数 , 然 后 加 1。 这 样 , 这 是 一 个 整 数 值 ,<br />

比 数 据 库 里 最 大 的 值 还 多 1, 是 一 个 唯 一 的 数 。 然 后 , 把 它 绑 定 给 变 量 NewID。 由 于 ID 是<br />

串 类 型 的 , 所 以 必 须 用 toString/1 把 这 个 整 数 转 换 成 串 :<br />

NewID = toString(toTerm(maximum(IDList)) + 1),<br />

看 起 来 复 杂 了 些 , 也 确 实 是 , 不 过 挺 管 用 。NewID 用 于 assert 语 句 中 。 谓 词 结 尾 时 向 消<br />

息 窗 口 输 入 了 一 些 内 容 , 说 明 新 记 录 添 加 好 了 , 并 显 示 出 新 记 录 的 ID:<br />

stdIO::write("Person ", Name, " has been added with ID: ", NewID), stdIO::nl.<br />

给 这 个 谓 词 写 好 了 代 码 , 还 要 声 明 这 个 新 谓 词 以 便 程 序 其 它 部 件 可 以 用 它 。 在 person.cl<br />

文 件 中 加 上 :<br />

addPerson : (string Name, integer Weight, integer Length, integer IQ) procedure.<br />

放 在 listDatabase 声 明 之 后 就 可 以 了 。 差 不 多 都 做 完 了 , 只 剩 一 个 细 节 。 标 准 谓 词<br />

maximum() 是 在 名 叫 list 的 一 个 独 立 模 块 ( 其 实 是 类 ) 中 的 , 必 须 告 诉 程 序 打 开 这 个 模 块 。<br />

在 person.pro 文 件 开 头 处 , 把 代 码 改 成 :<br />

implement person<br />

open core, list<br />

这 样 person 的 代 码 就 完 成 了 。 但 程 序 还 缺 少 一 些 代 码 , 在 采 集 数 据 的 表 格 和 把 记 录 添<br />

加 到 数 据 库 中 的 谓 词 间 还 应 该 有 一 种 关 联 。 看 来 , 把 这 些 代 码 与 点 击 表 格 的 按 钮 相 关<br />

起 来 应 该 不 错 。 这 就 需 要 给 点 击 按 钮 添 加 一 些 标 准 代 码 。 在 工 程 树 中 双 击 addPerson.dlg<br />

打 开 对 话 框 编 译 器 , 选 OK 按 钮 , 在 属 性 窗 口 里 点 击 标 签 。 在 事 件 表 里 选 择<br />

Clickresponder, 在 取 值 栏 里 选 择 onOKClick, 并 在 其 上 双 击 打 开 代 码 编 辑 器 。 把 插 入 的 标 准<br />

代 码 改 成 如 下 内 容 :<br />

predicates<br />

onOkClick : button::clickResponder.<br />

clauses<br />

onOkClick(_Source) = button::defaultAction() :‐<br />

Name = name_ctl:gettext(),<br />

Weight = toTerm(weight_ctl:gettext()),<br />

Length = toTerm(length_ctl:gettext()),<br />

IQ = toTerm(iq_ctl:gettext()),<br />

person::addPerson(Name, Weight, Length, IQ).<br />

这 里 发 生 的 事 情 其 实 很 简 单 : 前 面 四 个 语 句 从 表 格 的 四 个 编 辑 框 中 取 出 值 并 放 入 四 个 变<br />

量 里 , 有 了 这 四 个 变 量 , 再 调 用 谓 词 :<br />

person::addPerson(Name, Weight, Length, IQ).<br />

由 于 编 辑 框 中 的 值 是 串 类 型 的 , 而 变 量 Name 的 值 也 是 串 , 所 以 由 gettext() 取 得 的 值 可<br />

以 直 接 绑 定 给 Name。 但 变 量 Weight、Length 和 IQ 都 需 要 整 数 , 故 在 绑 定 这 些 值 之 前 先 要<br />

67


用 toTerm/1 转 换 一 下 。<br />

一 切 就 绪 。 构 造 并 执 行 工 程 , 输 入 一 些 新 的 人 。 不 过 在 输 入 新 人 前 , 先 要 用 fillDatabase<br />

填 充 数 据 库 。 如 果 不 这 样 , 就 会 出 错 。 因 为 当 插 入 新 记 录 时 , 我 们 用 已 经 有 的 记 录 来 计 算 新<br />

记 录 的 ID。 要 是 数 据 库 是 空 的 , 函 数 maximum 就 紧 张 了 , 它 找 不 到 任 何 记 录 ! 所 以 一 定 要<br />

先 填 充 数 据 库 , 再 添 加 记 录 , 用 listDatabase 来 查 看 结 果 。 程 序 中 的 这 个 缺 陷 是 个 麻 烦 , 下<br />

面 改 进 的 addPerson 代 码 可 以 防 止 出 现 这 个 问 题 :<br />

addPerson(Name, Weight, Length, IQ) :‐<br />

findAll(ID, person(ID, _, _, _, _), IDList),<br />

IDList [], !,<br />

NewID = toString(toTerm(maximum(IDList)) + 1),<br />

assertz(person(NewID, Name, Weight, Length, IQ)),<br />

stdIO::write("Person ", Name, " has been added with ID: ", NewID),<br />

stdIO::nl.<br />

addPerson(Name, Weight, Length, IQ) :‐<br />

assertz(person("1", Name, Weight, Length, IQ)),<br />

stdIO::write("Person ", Name, " has been added with ID: ", "1"),<br />

stdIO::nl.<br />

别 忘 了 惊 叹 号 ! 现 在 , 程 序 发 现 还 没 有 事 实 时 , 新 记 录 的 ID 就 是 1。 要 是 一 时 还 消 化<br />

不 了 这 么 多 东 西 , 也 就 那 样 吧 , 认 了 !<br />

7.4 操 控 数 据 : 删 除 一 个 记 录<br />

我 们 现 在 转 到 下 一 个 数 据 库 的 活 动 上 来 : 删 除 一 个 记 录 。 如 何 更 改 记 录 放 在 下 一 节 讨 论 。<br />

要 删 除 记 录 , 首 先 要 找 到 它 。 有 关 人 的 数 据 库 , 用 人 的 名 字 来 搜 索 是 很 方 便 的 , 但 也 可 能 有<br />

问 题 , 比 如 在 数 据 库 中 有 同 名 的 人 。 所 以 , 用 记 录 的 ID 来 搜 索 更 好 些 , 因 为 ID 是 唯 一 的 。<br />

不 过 , 大 多 数 人 并 不 记 得 自 己 的 ID 号 。 我 们 这 里 用 个 折 中 的 办 法 : 要 找 一 个 记 录 时 我 们 先<br />

用 人 名 搜 索 , 程 序 再 把 记 录 的 ID 连 同 名 字 一 道 显 示 出 来 , 通 过 点 选 ID 来 确 定 要 删 除 哪 个 记<br />

录 。<br />

先 来 创 建 一 个 完 成 上 述 工 作 的 对 话 框 , 如 图 7.6 所 示 。<br />

图 7.6<br />

删 除 记 录 的 对 话 框<br />

对 话 框 中 ,Choose a Person 下 面 要 显 示 可 以 删 除 的 人 名 列 表 。 当 用 户 选 择 了 一 个 名 字 时 ,<br />

旁 边 Choose an ID 下 面 , 要 显 示 相 应 的 ID 列 表 。 如 果 名 字 是 唯 一 的 , 就 只 出 现 一 个 ID; 如<br />

果 名 字 不 是 唯 一 的 , 就 要 出 现 所 有 与 这 个 名 字 相 对 应 的 ID。 当 用 户 选 择 一 个 ID 时 , 相 应 的<br />

ID 和 名 字 就 显 示 在 右 边 。 这 时 用 户 可 以 点 击 或 , 点 前 者 就 删 除 这 个 记 录 , 点<br />

68


后 者 不 删 除 。 不 管 点 的 是 哪 一 个 , 最 终 都 要 关 闭 这 个 窗 口 。 我 们 这 里 不 打 算 用 按 钮 ,<br />

读 者 愿 意 的 话 也 可 以 自 己 把 它 和 一 个 消 息 框 联 系 起 来 。<br />

现 在 , 我 们 在 forms 包 中 创 建 一 个 新 的 名 为 deletePerson 的 对 话 框 。 按 图 7.7 所 示 的 样<br />

式 安 排 好 控 件 。 大 多 数 控 件 都 是 我 们 熟 悉 的 , 只 有 Choose a Person 和 Choose an ID 下 面 的 那<br />

两 个 除 外 。 这 两 个 都 是 列 表 框 , 可 以 在 控 件 工 具 箱 第 二 行 最 左 边 看 到 代 表 它 的 图 标 。 在<br />

Choose a Person 下 面 的 列 表 框 名 字 就 是 listbox_ctl, 这 是 IDE 给 它 的 缺 省 名 称 , 我 们 就 这 样<br />

用 它 。 在 Choose an ID 下 面 的 列 表 框 就 不 能 再 是 这 个 名 字 了 , 我 们 叫 它 idBox_ctl 吧 。 还 要 改<br />

一 下 右 边 两 个 编 辑 框 的 名 字 ,ID 后 面 的 那 个 要 接 收 选 定 记 录 的 ID, 所 以 把 它 的 名 字 改 成<br />

id_ctl; 另 一 个 要 放 记 录 中 的 人 名 , 就 叫 它 name_ctl 好 了 。 这 两 个 编 辑 框 都 只 是 复 核 要 删 除<br />

记 录 的 数 据 的 , 不 能 让 用 户 改 变 这 里 的 内 容 , 所 以 要 把 它 们 的 ReadOnly 属 性 由 False 改 成<br />

True。 可 以 在 属 性 列 表 中 找 到 这 个 属 性 。 完 成 对 话 框 后 , 保 存 并 构 造 工 程 。<br />

图 7.7<br />

删 除 一 个 人 员 的 对 话 框<br />

现 在 来 看 触 发 这 个 对 话 框 的 菜 单 项 。 在 任 务 菜 单 中 AddPerson 之 下 添 加 一 个 菜 单 项 , 就<br />

叫 DeletePerson。 点 击 它 就 应 该 显 示 相 应 的 对 话 框 。 到 TaskWindow.win 上 , 右 键 打 开 代 码 专<br />

家 系 统 。 点 Menu, 点 TaskMenu, 点 id_database, 增 加 id_delete_person 新 菜 单 项 的<br />

标 准 代 码 。 然 后 , 找 到 相 应 的 代 码 , 改 成 :<br />

predicates<br />

onDatabaseDeletePerson : window::menuItemListener.<br />

clauses<br />

onDatabaseDeletePerson(Source, _MenuTag) :‐<br />

NewDialog = deletePerson::new(Source),<br />

NewDialog:show().<br />

现 在 要 是 执 行 程 序 的 话 , 点 击 Database/DeletePerson 时 就 会 显 示 出 这 个 表 格 。 不 过 这<br />

个 表 格 还 什 么 都 做 不 了 , 因 为 我 们 还 没 有 添 加 相 应 的 功 能 。<br />

我 们 首 先 要 做 的 是 把 数 据 库 里 的 人 名 放 到 列 表 框 中 去 。 要 记 住 , 我 们 想 把 所 有 与 数 据 库<br />

有 关 的 谓 词 都 放 在 类 ( 模 块 )person 里 。 填 写 可 选 的 人 名 列 表 框 , 需 要 所 有 人 的 名 字 。 我 们<br />

做 一 个 谓 词 :<br />

getAllNames(NameList) :‐<br />

findAll(Name, person(_,Name,_,_,_), NameList).<br />

把 这 个 谓 词 放 在 person.pro 文 件 中 , 就 在 addPerson() 谓 词 之 后 , 但 要 在 end implement<br />

69


person 之 前 。 调 用 这 个 谓 词 , 标 准 谓 词 findall 就 会 创 建 我 们 需 要 的 表 。 对 这 个 标 准 谓 词 的 使<br />

用 与 前 一 节 一 样 , 变 量 Name 用 来 采 集 函 子 person 的 第 二 个 参 数 , 而 所 有 的 名 字 又 都 被 放<br />

在 NameList 表 中 。 下 面 的 子 句 是 这 个 谓 词 的 声 明 :<br />

getAllNames : (string* NameList) procedure (o).<br />

要 把 它 放 在 person.cl 文 件 中 , 其 它 的 谓 词 声 明 也 是 放 在 那 里 的 。<br />

现 在 可 以 用 这 个 谓 词 来 填 写 deletePerson 对 话 框 的 列 表 框 了 。deletePerson 对 话 框 是 由<br />

在 deletePerson.pro 中 的 谓 词 new() 创 建 的 。 打 开 这 个 文 件 ( 可 能 需 要 先 构 造 工 程 ), 找 到 下<br />

面 的 子 句 :<br />

clauses<br />

new(Parent) :‐<br />

dialog::new(Parent),<br />

generatedInitialize().<br />

这 是 创 建 对 话 框 用 的 代 码 。 我 们 要 对 它 进 行 扩 充 , 以 便 创 建 时 把 人 名 填 写 在 列 表 框 中 。<br />

代 码 改 动 后 是 :<br />

clauses<br />

new(Parent) :‐<br />

dialog::new(Parent),<br />

generatedInitialize(),<br />

person::getAllNames(NameList),<br />

SingletonList = removeDuplicates(NameList),<br />

listBox_ctl:addList(SingletonList).<br />

千 万 要 小 心 , 把 generatedInitialize() 后 面 的 句 点 改 成 逗 号 。<br />

添 加 的 三 行 代 码 作 用 是 :<br />

• person::getAllNames(NameList), 调 用 谓 词 产 生 数 据 库 中 所 有 人 名 的 列 表<br />

• SingletonList = removeDuplicates(NameList), removeDuplicates 把 表 中 重 复 的 名 字<br />

去 掉 , 清 好 的 表 绑 定 给 变 量 SingletonList<br />

• listBox_ctl:addList(SingletonList). 把 人 名 表 填 写 到 列 表 框 里 。 它 其 实 是 在 列 表 框 中<br />

添 加 人 名 , 但 由 于 列 表 框 原 来 是 空 的 , 所 以 我 们 看 到 的 就 是 人 名 表 里 的 名 字 。<br />

谓 词 removeDuplicates 是 一 个 标 准 谓 词 , 在 类 list 里 。 所 以 我 们 要 把 这 个 类 加 到 工 程 中 。<br />

在 deletePerson.pro 开 头 的 地 方 加 上 代 码 :<br />

implement deletePerson<br />

inherits dialog<br />

open core, vpiDomains, list<br />

其 实 只 是 在 vipDomanins 之 后 加 上 单 词 list 而 已 。 别 忘 了 逗 号 ! 也 别 忘 了 在 试 程 序 时 先<br />

填 充 数 据 库 , 不 然 就 会 什 么 也 看 不 到 , 看 到 的 是 空 表 。<br />

列 表 框 可 以 用 数 据 库 中 的 人 名 填 好 了 。 可 以 选 择 人 名 , 不 过 没 什 么 作 用 , 因 为 我 们 还 没<br />

有 加 相 应 的 代 码 。 现 在 先 试 一 下 这 个 程 序 好 了 。<br />

我 们 想 要 在 列 表 框 里 选 择 了 一 个 人 名 之 后 , 程 序 用 这 个 名 字 搜 索 数 据 库 , 并 显 示 出 所 有<br />

用 这 个 名 字 的 记 录 的 ID。 选 择 一 个 名 字 , 就 应 该 触 发 这 个 动 作 。 我 们 有 福 啊 ! 这 是 有 标 准<br />

谓 词 可 用 的 。 它 是 列 表 框 带 的 一 个 谓 词 。 在 对 话 框 编 辑 器 中 打 开 deletePerson.dlg, 点 击 显 示<br />

人 名 的 列 表 框 使 其 得 到 焦 点 , 再 在 属 性 窗 口 中 打 开 事 件 列 表 。 在 事 件 列 表 里 选<br />

SelectionChangedListener, 把 它 的 值 设 为 onListboxSelectionChanged, 然 后 双 击 它 , 编 辑 器 中<br />

70


会 显 示 出 插 入 标 准 代 码 的 地 方 , 一 改 变 列 表 框 中 的 选 择 就 会 执 行 这 些 代 码 。 把 标 准 代 码 修 改<br />

成 如 下 内 容 :<br />

predicates<br />

onListboxSelectionChanged : listControl::selectionChangedListener.<br />

clauses<br />

onListboxSelectionChanged(Source) :‐<br />

[Name | _T] = Source:getSelectedItems(),<br />

person::getSomeIDs(Name, IDList),<br />

idBox_ctl:clearAll(),<br />

id_ctl:setText(" "),<br />

name_ctl:setText(" "),<br />

idBox_ctl:addList(IDList), !.<br />

onListBoxSelectionChanged(_Source).<br />

别 忘 了 把 第 一 个 子 句 Source 前 的 下 划 线 去 掉 , 也 别 忘 了 在 第 二 个 子 句 的 Source 前 插 入<br />

一 个 下 划 线 。 我 们 来 做 些 解 释 :<br />

[Name | _T] = Source:getSelectedItems(),<br />

getSelectedItems 是 一 个 标 准 谓 词 , 它 返 回 选 定 的 项 。 由 于 VIP 允 许 在 一 个 表 中 选 择 多<br />

个 项 , 所 以 返 回 的 选 择 项 也 是 一 个 表 , 这 个 表 绑 定 给 了 [Name | _T]。 这 个 表 达 式 是 典 型 的 <strong>Prolog</strong><br />

表 达 式 , 它 表 示 了 一 个 表 , 同 时 又 把 表 分 成 了 两 部 分 。 第 一 个 元 素 约 束 给 了 变 量 Name, 其 余 的<br />

元 素 绑 定 给 了 变 量 _T。 下 划 线 告 诉 编 译 器 我 们 不 需 要 使 用 这 个 变 量 。 由 于 在 这 个 例 子 中 只 能 让 用<br />

户 选 择 一 个 元 素 , 所 以 绑 定 表 中 第 一 个 元 素 给 Name 就 够 用 了 。<br />

person::getSomeIDs(Name, IDList),<br />

把 Name 用 在 谓 词 getSomeIDs() 调 用 中 。 这 个 谓 词 是 我 们 需 要 做 的 , 要 把 它 放 在 类 person<br />

中 , 后 面 我 们 再 考 虑 它 的 代 码 。 现 在 , 我 们 先 关 注 Name 作 为 它 的 输 入 , 调 用 后 它 会 返 回 一<br />

个 ID 的 表 IDList。<br />

idBox_ctl:clearAll(),<br />

id_ctl:setText(" "),<br />

name_ctl:setText(" "),<br />

因 为 选 择 了 一 个 新 的 名 字 , 需 要 把 ID 列 表 框 及 ID 和 Name 编 辑 框 的 内 容 先 清 一 下 。<br />

idBox_ctl:addList(IDList), !.<br />

把 ID 表 的 内 容 填 写 到 idBox_ctl 中 。<br />

onListBoxSelectionChanged(_Source).<br />

对 这 行 代 码 先 不 用 想 得 太 多 , 现 在 它 没 有 多 大 用 处 。<br />

前 面 提 到 了 谓 词 getSomeIDs(), 它 是 类 person 中 的 一 个 谓 词 , 要 找 出 指 定 人 名 的 所 有 ID。<br />

它 的 代 码 很 简 单 :<br />

getSomeIDs(Name,IDList) :‐<br />

findall(ID, person(ID,Name,_,_,_), IDList).<br />

把 它 放 在 person.pro 文 件 中 getAllNames 之 后 。 这 个 谓 词 教 会 我 们 findall/3 的 另 一 种 用<br />

法 。 调 用 findall/3 时 , 我 们 告 诉 它 找 出 ID 的 值 , 也 就 是 它 的 第 一 个 参 数 , 并 把 找 到 的 值 放<br />

在 IDList 表 里 。 而 同 时 我 们 还 告 诉 这 个 谓 词 , 只 找 第 二 个 参 数 规 定 的 记 录 。 变 量 Name 中 有<br />

在 人 名 列 表 中 选 择 的 人 名 , 这 就 限 制 了 它 只 在 含 有 变 量 Name 中 名 字 的 记 录 中 搜 索 。 当 然 ,<br />

我 们 还 得 在 person.cl 中 声 明 getSomeIDs(), 是 这 样 :<br />

71


getSomeIDs : (string Name, string* IDList) procedure (i,o) .<br />

把 它 放 在 person.cl 文 件 中 getAllNames() 声 明 的 后 面 。 现 在 构 造 和 执 行 程 序 , 选 择 一 个<br />

名 字 后 就 会 显 示 相 应 的 ID 了 。 当 然 , 还 是 要 先 填 充 数 据 库 。<br />

不 过 右 边 待 删 除 记 录 的 名 字 和 ID 还 没 有 显 示 , 选 了 一 个 ID 后 应 该 显 示 的 。 代 码 和 选 择<br />

一 个 名 字 时 的 差 不 多 , 不 解 释 了 , 直 接 写 代 码 。<br />

工 程 树 中 选 deletePerson.dlg, 在 表 格 编 辑 器 中 打 开 它 。 点 选 idBox_ctl 打 开 属 性 窗 口 中<br />

的 事 件 列 表 。 选 事 件 SelectionChangedListener, 并 设 其 值 为 onIDBoxSelectionChanged。 加 上<br />

标 准 代 码 , 在 编 辑 器 中 打 开 , 更 改 代 码 成 这 样 :<br />

predicates<br />

onIdBoxSelectionChanged : listControl::selectionChangedListener.<br />

clauses<br />

onIdBoxSelectionChanged(Source) :‐<br />

[ID | _T] = Source:getSelectedItems(),<br />

person::getPerson(ID, Name, _, _, _),<br />

id_ctl:setText(ID),<br />

name_ctl:setText(Name), !.<br />

onIdboxSelectionChanged(_Source).<br />

当 ID 列 表 框 中 的 选 择 变 化 时 , 就 会 得 到 选 择 的 ID, 并 要 求 模 块 person 返 回 这 个 ID 所<br />

代 表 的 人 的 数 据 , 我 们 用 返 回 的 Name 数 据 填 写 编 辑 框 。 要 能 工 作 , 我 们 还 需 要 在 person.pro<br />

中 加 一 个 谓 词 getPerson()。 把 下 面 的 代 码 放 在 getSomeIDs() 之 后 :<br />

getPerson(ID, Name, Weight, Length, IQ) :‐<br />

person(ID, Name, Weight, Length, IQ), !.<br />

getPerson(_ID, "0", 0, 0, 0).<br />

第 二 个 子 句 可 能 永 远 用 不 上 , 不 过 编 译 器 会 喜 欢 这 样 的 。 最 后 , 在 person.cl 文 件 中 声<br />

明 这 个 谓 词 :<br />

getPerson : (string ID, string Name, integer Weight, integer Length, integer IQ) procedure (i,o,o,o,o) .<br />

现 在 差 不 多 了 , 只 剩 下 用 户 点 击 时 删 除 所 选 记 录 的 代 码 了 。 打 开 deletePerson.dlg,<br />

选 择 OK 按 钮 , 打 开 属 性 窗 口 中 的 事 件 列 表 。 选 ClickResponder, 设 其 值 为 onOKClick。 添 加<br />

标 准 代 码 , 在 编 辑 器 中 打 开 并 修 改 代 码 成 下 面 这 样 :<br />

predicates<br />

onOkClick : button::clickResponder.<br />

clauses<br />

onOkClick(_Source) = button::defaultAction() :‐<br />

ID = id_ctl:getText(),<br />

person::deletePerson(ID).<br />

用 户 点 击 后 , 程 序 就 从 ID 编 辑 框 中 取 得 ID, 告 诉 person 执 行 谓 词 deletePerson。<br />

这 个 谓 词 就 会 删 除 相 应 ID 的 记 录 。 看 看 它 是 怎 么 样 做 的 。 在 编 辑 器 中 打 开 person.pro 文 件 ,<br />

在 end implementation 之 前 插 入 下 面 的 代 码 :<br />

deletePerson(ID) :‐<br />

retract(person(ID, Name, _, _, _)), !,<br />

72


stdIO::write("Person ", Name, " with ID: ", ID, " has been deleted"), stdIO::nl.<br />

deletePerson(_).<br />

这 个 谓 词 让 程 序 撤 消 指 定 ID 的 记 录 。 撤 消 一 个 记 录 , 也 就 是 丢 弃 这 个 记 录 。 为 了 表 示<br />

完 成 情 况 , 会 把 丢 弃 记 录 中 的 人 名 显 示 给 用 户 看 。 最 后 , 还 要 在 person.cl 中 声 明 这 个 谓 词 :<br />

deletePerson : (string ID) procedure (i) .<br />

现 在 可 以 保 存 和 构 造 程 序 了 。 执 行 它 , 先 填 充 数 据 库 , 再 加 一 些 人 ( 可 以 用 相 同 的 名 字<br />

或 是 数 据 库 中 已 经 有 的 名 字 ), 再 删 除 。 时 不 时 还 可 以 用 List Database 检 查 一 下 数 据 库 的 结<br />

果 。 不 用 太 担 心 用 户 点 击 , 我 们 没 有 为 它 写 代 码 , 用 户 点 了 它 , 不 会 出 现 什 么 情 况 ,<br />

这 也 正 是 用 户 想 要 的 结 果 , 而 VIP 只 不 过 就 是 关 闭 这 个 窗 口 而 已 。 这 里 还 有 一 个 缺 陷 , 出 现<br />

在 用 户 要 在 空 的 数 据 库 中 删 除 记 录 时 , 程 序 就 是 显 示 一 个 空 的 列 表 。 可 以 在 getAllNames 的<br />

代 码 中 显 示 一 个 消 息 , 这 样 :<br />

getAllNames(NameList) :‐<br />

findAll(Name, person(_,Name,_,_,_), NameList),<br />

NameList [], !.<br />

getAllNames([]) :‐<br />

vpiCommonDialogs::error("database is empty").<br />

还 是 要 注 意 惊 叹 号 !<br />

7.5 操 控 数 据 : 更 改 记 录 的 内 容<br />

要 更 改 记 录 的 内 容 , 需 要 做 一 些 我 们 已 经 熟 悉 了 的 事 情 :<br />

• 先 要 获 取 需 要 的 记 录 的 内 容<br />

• 显 示 这 些 内 容<br />

• 用 户 更 改<br />

• 丢 弃 旧 的 记 录<br />

• 用 新 数 据 插 入 一 个 新 记 录<br />

考 虑 一 下 上 面 最 后 这 两 步 : 如 果 用 户 没 有 修 改 原 来 的 内 容 怎 么 办 ? 好 , 我 们 当 回 懒 人 ,<br />

不 怕 这 样 , 如 果 什 么 都 没 变 , 就 把 旧 的 记 录 丢 了 , 再 用 没 变 的 值 插 入 一 个 新 记 录 。<br />

这 一 节 里 的 步 骤 与 上 一 节 差 不 多 , 所 以 只 给 出 需 要 的 代 码 , 不 多 解 释 。 先 按 图 7.8 的 样<br />

式 建 一 个 对 话 框 , 它 和 删 除 一 个 记 录 的 对 话 框 有 相 同 的 列 表 框 , 我 们 就 用 相 同 的 名 字<br />

listbox_ctl 和 idBox_ctl。 右 边 的 五 个 编 辑 框 用 来 放 记 录 的 内 容 。ID 那 个 是 灰 色 的 , 因 为 不 能<br />

让 用 户 改 它 , 它 是 唯 一 的 , 所 以 把 它 的 只 读 属 性 设 为 True。 另 外 , 也 改 一 下 编 辑 框 的 名 字 ,<br />

分 别 是 id_ctl、name_ctl、length_ctl、weight_ctl 和 iq_ctl。 对 话 框 的 名 字 是 changePerson。<br />

可 以 看 到 , 对 话 框 里 用 数 据 库 里 的 人 名 填 充 了 。 这 里 先 是 用 7.1 节 的 四 个 记 录 填 充 , 再<br />

用 Thomas 的 名 字 加 了 两 个 记 录 。 在 对 话 框 中 选 择 人 名 Thomas, 程 序 相 应 地 显 示 出 三 个 ID。<br />

选 ID5, 程 序 就 显 示 出 ID 为 5 的 记 录 内 容 。<br />

如 果 理 解 了 前 一 节 的 内 容 , 这 个 对 话 框 的 代 码 就 是 很 熟 悉 的 了 。 建 对 话 框 前 , 先 要 用 人<br />

名 填 写 列 表 框 。 构 造 工 程 , 在 changePerson.pro 文 件 中 找 到 下 面 的 代 码 :<br />

clauses<br />

new(Parent) :‐<br />

dialog::new(Parent),<br />

generatedInitialize().<br />

73


图 7.8<br />

更 改 人 员 记 录 的 对 话 框<br />

改 写 成 这 样 :<br />

clauses<br />

new(Parent) :‐<br />

dialog::new(Parent),<br />

generatedInitialize(),<br />

person::getAllNames(NameList),<br />

SingletonList = removeDuplicates(NameList),<br />

listBox_ctl:addList(SingletonList).<br />

在 文 件 开 头 的 地 方 , 把<br />

open core, vpiDomains<br />

改 成 ;<br />

open core, vpiDomains, list<br />

我 们 需 要 人 名 列 表 框 中 改 变 了 名 字 和 ID 列 表 框 中 改 变 了 ID 时 的 触 发 代 码 。 先 插 入 它 们<br />

的 标 准 代 码 , 再 把 它 们 改 成 下 面 的 样 子 :<br />

predicates<br />

onListboxSelectionChanged : listControl::selectionChangedListener.<br />

clauses<br />

onListboxSelectionChanged(Source) :‐<br />

[Name | _T] = Source:getSelectedItems(),<br />

person::getSomeIDs(Name, IDList),<br />

idBox_ctl:clearAll(),<br />

id_ctl:setText(" "),<br />

name_ctl:setText(" "),<br />

weight_ctl:setText(" "),<br />

length_ctl:setText(" "),<br />

iq_ctl:setText(" "),<br />

idBox_ctl:addList(IDList), !.<br />

onListBoxSelectionChanged(_Source).<br />

predicates<br />

74


onIdBoxSelectionChanged : listControl::selectionChangedListener.<br />

clauses<br />

onIdboxSelectionChanged(Source) :‐<br />

[ID | _T] = Source:getSelectedItems(),<br />

person::getPerson(ID, Name,Length,Weight,IQ),<br />

id_ctl:setText(ID),<br />

name_ctl:setText(Name),<br />

length_ctl:setText(toString(Length)),<br />

weight_ctl:setText(toString(Weight)),<br />

iq_ctl:setText(toString(IQ)), !.<br />

onIdboxSelectionChanged(_Source).<br />

然 后 , 给 OK 按 钮 插 入 标 准 代 码 , 再 改 成 :<br />

predicates<br />

onOkClick : button::clickResponder.<br />

clauses<br />

onOkClick(_Source) = button::defaultAction() :‐<br />

ID = id_ctl:getText(),<br />

Name = name_ctl:gettext(),<br />

Weight = toTerm(weight_ctl:gettext()),<br />

Length = toTerm(length_ctl:gettext()),<br />

IQ = toTerm(iq_ctl:gettext()),<br />

person::changePerson(ID, Name, Weight, Length, IQ).<br />

还 需 要 在 person.pro 中 加 一 个 新 谓 词 changePerson(), 代 码 是 :<br />

changePerson(ID, Name, Weight, Length, IQ) :‐<br />

retract(person(ID, _, _, _, _)),<br />

assertz(person(ID, Name, Weight, Length, IQ)), !,<br />

stdIO::write("Person ", Name, " with ID: ", ID, " has been changed"), stdIO::nl.<br />

changePerson(_, _, _, _, _).<br />

这 个 谓 词 删 除 旧 的 记 录 , 并 用 对 话 框 编 辑 框 中 的 内 容 做 一 个 新 记 录 插 入 数 据 库 中 。 因 为<br />

使 用 的 是 assertz(), 所 以 新 记 录 是 在 数 据 库 的 最 后 。 这 个 谓 词 在 person.cl 中 的 声 明 是 :<br />

changePerson : (string ID, string Name, integer Weight, integer Length, integer IQ) procedure (i, i, i, i, i) .<br />

最 后 , 在 任 务 菜 单 中 建 一 个 新 项 ,Change Person, 把 它 放 在 Add Person 和 Delete Person<br />

之 间 。 代 码 专 家 系 统 会 插 入 标 准 代 码 , 改 成 下 面 这 样 :<br />

predicates<br />

onDatabaseChangePerson : window::menuItemListener.<br />

clauses<br />

onDatabaseChangePerson(Source, _MenuTag) :‐<br />

NewDialog = changePerson::new(Source),<br />

NewDialog:show().<br />

别 忘 了 去 掉 Source 前 的 下 划 线 !<br />

75


7.6 保 存 和 恢 复 数 据 库<br />

这 样 , 小 数 据 库 就 完 成 了 。 可 以 增 加 记 录 、 更 改 记 录 和 删 除 记 录 。 不 过 程 序 退 出 后 , 数<br />

据 库 就 丢 失 了 , 所 有 在 程 序 中 保 存 事 实 的 内 部 数 据 库 都 这 样 , 而 之 所 以 要 用 内 部 数 据 库 是 因<br />

为 它 比 磁 盘 数 据 库 要 快 。 在 实 际 使 用 中 丢 失 数 据 当 然 是 不 能 接 受 的 , 所 以 我 们 再 来 加 两 个 谓<br />

词 , 一 个 用 于 把 数 据 库 保 存 到 一 个 磁 盘 文 件 上 , 另 一 个 用 于 恢 复 保 存 的 数 据 。 有 标 准 谓 词 来<br />

完 成 这 些 动 作 , 可 以 在 file 类 中 找 到 它 们 。<br />

把 内 部 事 实 数 据 库 保 存 到 磁 盘 的 标 准 谓 词 是 save/2, 它 有 两 个 参 数 , 一 个 是 要 写 入 数 据<br />

的 文 件 名 , 另 一 个 是 要 保 存 的 内 部 数 据 库 的 名 字 。 目 前 为 止 , 还 没 有 给 内 部 数 据 库 起 名 字 ,<br />

因 为 在 类 person 中 只 有 一 个 数 据 库 , 没 必 要 再 起 名 字 。 但 要 用 谓 词 save/2, 就 必 须 先 给 事<br />

实 数 据 库 一 个 名 字 , 我 们 就 叫 它 personDB 好 了 。 给 事 实 数 据 库 起 名 , 可 以 这 样 , 把 person.pro<br />

中 的<br />

class facts<br />

改 成<br />

Class facts ‐ personDB<br />

不 要 丢 了 名 字 前 面 的 短 横 “‐”, 也 不 需 要 在 行 尾 加 句 点 。<br />

现 在 这 个 事 实 数 据 库 有 名 字 了 , 可 以 来 实 现 用 于 保 存 与 恢 复 的 谓 词 了 。 先 在 任 务 菜 单 里<br />

加 两 个 新 的 菜 单 项 , 一 个 叫 SaveDatabase, 另 一 个 叫 ConsultDatabase, 把 它 们 放 在 Database<br />

项 中 。 然 后 在 TaskWindow.win 中 给 这 两 个 项 加 上 标 准 代 码 , 再 把 标 准 代 码 改 成 :<br />

predicates<br />

onDatabaseSavedatabase : window::menuItemListener.<br />

clauses<br />

onDatabaseSavedatabase(_Source, _MenuTag) :‐<br />

person::saveDatabase().<br />

predicates<br />

onDatabaseConsultdatabase : window::menuItemListener.<br />

clauses<br />

onDatabaseConsultdatabase(_Source, _MenuTag) :‐<br />

person::consultDatabase().<br />

前 面 , 是 把 操 控 事 实 数 据 库 中 的 数 据 的 谓 词 放 在 person 类 中 的 。 所 以 , 要 到 person.pro<br />

文 件 中 加 入 两 个 新 谓 词 的 代 码 :<br />

saveDatabase() :‐<br />

file::existFile("personDB.txt"), !,<br />

file::delete("personDB.txt"),<br />

file::save("personDB.txt", personDB),<br />

stdIO::write("The database has been saved to file personDB.txt"), stdIO::nl.<br />

saveDatabase() :‐<br />

file::save("personDB.txt", personDB),<br />

stdIO::write("The database has been saved to file personDB.txt"), stdIO::nl.<br />

consultDatabase() :‐<br />

file::consult("personDB.txt", personDB),<br />

stdIO::write("Records have been added from file personDB.txt"), stdIO::nl.<br />

两 个 谓 词 中 , 中 心 的 子 句 就 是 从 file 类 中 调 用 标 准 谓 词 并 把 需 要 的 参 数 给 它 们 。 第 一 个<br />

参 数 是 外 部 文 件 名 , 要 用 来 写 入 或 读 出 数 据 的 那 个 文 件 的 名 字 ; 第 二 个 参 数 是 程 序 内 部 的 事<br />

76


实 数 据 库 名 。 同 时 , 还 要 向 消 息 窗 口 写 一 个 消 息 。consultDatabase/0 谓 词 的 子 句 很 容 易 明 白 ,<br />

而 saveDatabase/0 有 点 儿 复 杂 。 因 为 save/2 是 把 记 录 添 加 在 文 件 中 , 如 果 文 件 已 经 存 在 的 情<br />

况 下 , 不 加 防 范 , 多 次 保 存 就 会 导 致 相 同 内 容 多 次 重 复 出 现 。 所 以 ,saveDatabase/0 的 第 一<br />

个 子 句 先 检 查 一 下 文 件 是 否 存 在 , 它 是 通 过 标 准 谓 词 existFile/1 来 完 成 的 。 如 果 有 指 定 名 字<br />

的 文 件 , 这 个 谓 词 就 成 功 , 没 有 就 失 败 。 如 果 文 件 已 经 存 在 , 就 用 delete/1 删 除 这 个 文 件 而<br />

后 save/2 会 创 建 新 文 件 并 添 加 记 录 到 新 文 件 中 。 如 果 文 件 不 存 在 , 谓 词 existFile/1 就 失 败 ,<br />

推 理 机 就 会 转 到 第 二 个 子 句 上 , 这 时 只 需 要 把 数 据 写 到 新 创 建 的 文 件 personDB.txt 中 。<br />

最 后 , 在 person.cl 中 加 两 行 声 明 , 如 下 :<br />

saveDatabase : () procedure .<br />

consultDatabase : () procedure .<br />

好 了 , 这 个 程 序 现 在 可 以 在 磁 盘 上 一 个 外 部 文 件 中 保 存 和 恢 复 数 据 了 。 要 当 心 , 这 种 保 存 和<br />

恢 复 数 据 的 实 现 是 很 容 易 出 错 的 , 要 恢 复 数 据 , 首 先 要 保 存 数 据 , 否 则 程 序 就 会 垮 。 还 有 ,<br />

磁 盘 上 的 文 件 名 是 预 设 的 , 数 据 总 是 写 到 personDB.txt 文 件 中 , 可 以 在 程 序 ch07db.exe 所 在<br />

文 件 夹 中 看 到 这 个 文 件 , 就 是 工 程 文 件 夹 ch07db 中 名 字 为 Exe 的 那 个 文 件 夹 。 另 一 个 麻 烦<br />

事 , 当 从 外 部 文 件 中 恢 复 数 据 时 , 它 是 添 加 在 内 部 文 件 已 有 数 据 后 的 。 所 以 , 恢 复 两 次 就 会<br />

使 记 录 重 复 。 要 避 免 它 , 就 必 须 添 加 子 句 在 恢 复 数 据 之 前 删 除 内 部 事 实 数 据 库 中 所 有 的 记 录 。<br />

最 好 在 从 文 件 恢 复 数 据 之 前 , 询 问 用 户 是 保 留 还 是 删 除 现 有 记 录 。 用 下 面 consultDatabase/0<br />

的 子 句 替 换 前 面 的 , 程 序 表 现 会 好 一 些 。 这 里 同 时 也 修 改 了 如 果 文 件 personDB.txt 不 存 在 会<br />

使 程 序 崩 溃 的 问 题 。<br />

class predicates<br />

getData : () procedure .<br />

clauses<br />

consultDatabase() :‐<br />

file::existFile("personDB.txt"), !,<br />

getData().<br />

consultDatabase() :‐<br />

vpiCommonDialogs::error("Sorry, can't do what you want", "No file called personDB.txt ").<br />

getData() :‐<br />

Answer = vpiCommonDialogs::ask("What do you want me to do?",<br />

"Do you want to delete the existing records \n before consulting?",<br />

["Yes", "No"] ),<br />

Answer = 0, !,<br />

retractFactDB(personDB),<br />

file::consult("personDB.txt", personDB),<br />

stdIO::write("Existing records have been deleted \n<br />

New records have been added from file personDB.txt"), stdIO::nl.<br />

getData() :‐<br />

file::consult("personDB.txt", personDB),<br />

stdIO::write("New records have been added from file personDB.txt"), stdIO::nl.<br />

数 据 库 现 在 多 少 算 是 完 整 了 。 但 还 是 要 小 心 ! 这 里 没 有 什 么 安 全 系 统 , 用 户 可 以 想 做<br />

什 么 就 做 什 么 , 不 需 要 授 权 。 不 过 它 真 的 是 一 个 数 据 库 。<br />

7.7 小 结<br />

这 一 章 , 我 们 创 建 了 一 个 数 据 库 , 或 者 说 是 一 个 数 据 文 件 程 序 。 学 习 了 使 用 列 表 框 和 一<br />

77


些 与 其 相 关 的 谓 词 ; 学 习 了 如 何 使 用 列 表 及 如 何 在 数 据 库 中 插 入 和 获 取 事 实 。 内 容 真 不 少 ,<br />

庆 贺 一 个 !<br />

在 VIP 中 , 有 一 个 谓 词 库 专 门 用 于 操 控 数 据 库 。 可 以 到 http://wiki.visual‐prolog.com 看 一<br />

下 有 关 <strong>Visual</strong> <strong>Prolog</strong> External Database System 的 文 章 。 也 可 以 参 看 Eduardo Costa 写 的 《<strong>Prolog</strong><br />

for Tyros》 第 20 章 的 内 容 。<br />

78


第 8 章 面 向 对 象 的 编 程 – 类 与 对 象<br />

<strong>Visual</strong> <strong>Prolog</strong> 是 一 种 面 向 对 象 的 语 言 。 要 用 它 编 制 程 序 , 就 要 了 解 面 向 对 象 编 程 的 一 些<br />

基 本 原 则 。 面 向 对 象 的 编 程 可 以 缩 写 为 OOP, 本 章 要 说 明 OOP 时 需 要 了 解 的 内 容 。 当 然 ,<br />

不 可 能 在 入 门 课 本 的 一 个 章 节 中 解 释 所 有 OOP 知 识 , 不 过 必 须 要 知 道 的 大 概 都 在 这 里 了 。<br />

8.1 面 向 对 象 在 现 实 中 的 例 子<br />

科 学 界 内 外 , 人 们 都 用 模 型 来 描 述 他 们 周 围 的 世 界 。 模 型 各 式 各 样 , 没 有 什 么 限 制 人 们<br />

来 描 述 这 个 世 界 ,OOP 只 不 过 是 建 立 模 型 的 又 一 种 方 式 。 不 过 , 它 产 生 出 了 一 种 强 有 力 的 建<br />

模 方 法 。 欧 几 里 德 用 5 个 轴 创 建 了 几 何 学 , 这 里 也 按 5 个 基 本 概 念 来 说 明 OOP 的 基 本 原 则 :<br />

1. OO 建 模 和 编 程 的 首 要 思 想 是 : 世 界 是 由 对 象 构 成 的 。 对 象 可 以 是 任 何 事 物 , 唯 一 的 条<br />

件 是 要 能 够 识 别 出 它 来 , 也 就 是 说 要 能 够 把 对 象 与 它 的 环 境 区 分 开 来 。 窗 上 的 一 滴 雨 水<br />

可 以 是 一 个 对 象 , 但 海 里 的 一 滴 水 就 不 是 。 当 然 , 要 知 道 这 不 是 一 个 非 常 严 格 的 约 束 。<br />

2. 第 二 , 对 象 具 有 某 些 特 征 。 特 征 也 叫 属 性 , 对 象 可 以 有 很 多 属 性 。 把 人 当 成 一 个 对 象 ,<br />

想 象 一 下 有 多 少 属 性 , 身 高 、 体 重 、 智 商 …… 等 等 。 一 个 属 性 就 是 一 个 特 征 , 而 且 还 是<br />

一 个 测 度 项 , 可 以 把 一 个 属 性 与 一 个 值 联 系 起 来 。 事 实 就 是 这 样 建 立 的 , 一 个 事 实 就 是<br />

在 特 定 场 合 特 定 时 间 为 真 的 一 个 “ 对 象 — 属 性 — 值 ” 的 元 组 。<br />

3. 第 三 , 对 象 可 以 做 事 情 , 可 以 执 行 动 作 ( 也 被 称 为 任 务 )。 所 以 , 除 了 属 性 , 对 象 还 有<br />

其 可 以 执 行 的 动 作 。 在 OOP 中 称 这 些 动 作 为 操 作 或 方 法 。<br />

4. 第 四 , 对 象 可 以 成 群 地 合 在 一 起 。 合 起 来 有 很 多 办 法 , 但 在 OOP 中 最 重 要 的 办 法 是 把<br />

那 些 有 相 同 属 性 和 相 同 方 法 的 对 象 组 合 在 一 起 。 具 有 相 同 方 法 和 相 同 属 性 的 对 象 集 合 ,<br />

称 为 这 些 对 象 的 类 。<br />

5. 第 五 , 对 象 是 相 互 关 联 的 , 它 们 之 间 有 多 种 联 系 。 这 里 暂 且 不 说 。<br />

这 些 概 念 有 什 么 用 呢 ? 主 要 是 用 来 解 决 计 算 机 复 杂 的 编 程 工 作 。 使 用 一 个 程 序 , 就 是 想<br />

让 它 做 点 什 么 , 同 时 又 想 让 它 与 其 它 的 程 序 打 交 道 , 比 如 希 望 有 帮 助 文 件 , 比 如 希 望 能 检 查<br />

拼 写 , 比 如 还 想 要 访 问 因 特 网 , 等 等 。 而 此 时 又 不 想 劳 神 来 管 程 序 什 么 时 间 做 什 么 , 也 不 喜<br />

欢 只 有 一 种 选 择 的 程 序 。 编 这 样 一 个 程 序 可 得 费 些 劲 儿 。OOP 是 一 种 按 模 块 构 建 程 序 的 方 法 ,<br />

每 个 模 块 只 管 任 务 的 一 部 分 , 比 起 整 个 大 程 序 来 复 杂 性 要 小 。<br />

我 们 来 举 个 例 子 。 你 有 一 个 数 据 库 , 里 面 有 学 生 、 教 师 和 课 程 。 假 设 用 微 软 的 Access<br />

来 编 制 这 样 一 个 信 息 系 统 , 需 要 建 立 必 要 的 表 格 与 用 户 界 面 , 然 后 写 些 操 作 该 系 统 需 要 的 代<br />

码 。 用 户 在 界 面 上 点 击 选 项 时 , 程 序 识 别 选 择 , 从 表 中 取 得 数 据 并 按 用 户 要 求 完 成 作 业 。 菜<br />

单 中 每 个 选 项 都 由 相 同 的 程 序 处 理 。 在 OOP 环 境 下 我 们 会 为 学 生 建 立 一 个 类 , 类 会 为 每 个<br />

学 生 生 成 互 不 相 同 的 对 象 来 容 纳 每 个 特 定 学 生 的 数 据 ( 属 性 和 值 )。 同 样 , 还 有 教 师 的 类 和<br />

课 程 的 类 来 为 每 位 教 师 和 每 一 课 程 生 成 特 定 的 对 象 。 现 在 , 可 以 把 类 做 为 这 个 信 息 系 统 的 模<br />

块 来 考 虑 。 再 加 两 个 模 块 , 用 户 界 面 和 报 告 生 成 器 。 如 果 用 户 想 要 一 份 学 生 名 单 , 通 过 用 户<br />

界 面 告 诉 程 序 。 用 户 界 面 会 找 报 告 生 成 器 。 报 告 生 成 器 产 生 报 告 , 并 开 始 创 建 输 出 , 先 生 成<br />

表 头 等 内 容 , 然 后 要 每 个 学 生 对 象 提 供 学 生 数 据 , 得 到 学 生 数 据 后 完 成 报 告 并 交 给 用 户 界 面 。<br />

这 差 不 多 就 是 OOP 程 序 的 工 作 过 程 。<br />

这 样 编 程 的 好 处 是 把 工 作 分 成 为 一 些 简 单 的 、 相 对 独 立 的 动 作 让 不 同 的 对 象 去 做 。 用 户<br />

界 面 只 需 要 关 心 用 户 输 入 了 什 么 , 并 把 需 要 的 信 息 传 送 给 程 序 的 其 它 部 分 。 学 生 对 象 只 管 某<br />

个 学 生 的 数 据 并 在 需 要 时 提 供 这 些 数 据 。 这 样 的 构 造 似 乎 是 比 必 需 的 复 杂 了 些 , 但 有 好 处 。<br />

想 象 一 下 , 假 使 有 一 天 需 要 改 动 用 户 界 面 , 只 须 改 动 那 一 个 模 块 就 行 了 。 要 是 需 要 增 加 学 生<br />

类 的 属 性 , 也 只 需 要 改 动 学 生 类 就 可 以 了 。 只 有 当 想 让 用 户 使 用 这 些 新 属 性 时 , 才 需 要 改 变<br />

用 户 界 面 和 相 应 的 报 告 产 生 器 。<br />

另 一 个 好 处 是 : 一 个 模 块 只 需 要 知 道 该 怎 样 向 其 它 模 块 发 请 求 , 不 必 了 解 其 它 模 块 是 如<br />

何 工 作 的 。 当 向 一 个 对 象 询 问 年 龄 时 , 不 需 要 知 道 这 个 对 象 是 怎 么 计 算 年 龄 的 。 当 需 要 改 变<br />

年 龄 的 计 算 方 法 时 , 只 需 要 在 这 个 对 象 的 类 里 面 做 变 动 , 用 不 着 改 变 其 它 的 类 。 这 就 是 所 谓<br />

“ 封 装 ”, 也 是 OOP 的 魔 术 之 一 。<br />

79


上 面 的 例 子 中 , 我 们 交 叉 地 使 用 了 “ 类 ” 和 “ 模 块 ” 这 两 个 术 语 。 其 实 这 并 不 准 确 , 不<br />

过 并 无 大 碍 。 要 理 解 OOP, 可 以 把 计 算 机 想 象 为 现 实 世 界 的 一 种 仿 真 模 型 。 我 们 再 进 一 步 地<br />

来 看 看 所 谓 的 “ 类 ”。<br />

8.2 进 一 步 了 解 类<br />

类 是 OOP 的 核 心 概 念 , 它 并 非 仅 只 是 一 组 对 象 。 首 先 , 类 是 声 明 。 创 建 类 的 同 时 , 也<br />

给 类 中 的 对 象 规 定 了 它 们 所 具 有 的 属 性 与 方 法 。<br />

声 明 一 个 类 时 , 其 中 并 没 有 对 象 。 对 象 是 在 程 序 执 行 时 按 需 要 创 建 的 。 类 产 生 它 专 属 的<br />

新 对 象 。 类 的 对 象 称 为 该 类 的 实 例 。 用 前 面 的 例 子 再 详 细 些 说 : 当 输 入 了 一 个 新 的 学 生 数 据<br />

时 ,OO 信 息 系 统 就 实 实 在 在 地 创 建 一 个 代 表 那 个 学 生 的 新 的 对 象 。OO 程 序 中 每 个 类 都 有<br />

一 个 称 作 new() 的 方 法 , 它 是 类 的 构 造 器 , 由 它 创 建 新 的 实 例 。 在 我 们 的 例 子 中 , 新 的 对 象<br />

就 要 容 纳 新 输 入 学 生 的 数 据 。 要 在 对 象 的 属 性 中 存 储 数 据 , 每 个 对 象 又 有 名 字 开 始 为 set....<br />

的 方 法 。 如 果 学 生 对 象 的 属 性 中 有 姓 名 、 地 址 等 , 则 会 有 setName(),setAddress() 等 方 法 。<br />

要 取 得 这 些 数 据 , 又 有 以 get.... 开 头 的 一 些 方 法 , 在 我 们 的 例 子 中 会 有 getName(),getAddress()<br />

等 方 法 。 对 象 可 以 有 很 多 属 性 , 也 就 会 有 很 多 个 get 和 set 方 法 。 这 些 方 法 有 时 也 称 为 对 象<br />

的 getter 和 setter。<br />

对 象 间 的 通 讯 依 靠 调 用 方 法 。 前 面 的 例 子 中 , 用 户 界 面 调 用 报 告 产 生 器 中 创 建 报 告 的 方<br />

法 。 报 告 产 生 器 调 用 学 生 对 象 的 方 法 得 到 需 要 的 数 据 。 在 其 它 类 中 的 对 象 要 与 某 个 对 象 交 互<br />

时 必 须 知 道 有 哪 些 方 法 可 用 , 这 些 其 它 对 象 可 用 的 方 法 ( 在 其 它 对 象 中 可 以 调 用 的 方 法 ) 称<br />

为 public( 公 用 的 )。 为 尽 可 能 地 使 类 独 立 , 不 会 让 所 有 的 方 法 都 是 公 用 的 , 不 然 , 改 变 一<br />

个 方 法 时 就 要 在 每 一 个 调 用 该 方 法 的 类 中 改 变 调 用 。 因 此 , 方 法 应 该 尽 可 能 不 外 露 , 这 就 是<br />

所 谓 “ 松 耦 合 ”, 也 是 OOP 中 一 个 重 要 法 则 。 如 此 一 来 , 就 有 很 多 方 法 在 对 象 外 是 不 能 调<br />

用 的 , 这 些 只 能 在 对 象 自 身 内 部 调 用 的 方 法 称 为 private( 私 有 的 ) 方 法 。<br />

小 结 一 下 ,OO 程 序 是 由 一 些 类 构 成 的 , 类 创 建 对 象 而 对 象 进 行 作 业 , 它 们 从 用 户 界 面<br />

或 是 其 它 对 象 那 里 获 取 消 息 。<br />

<strong>Visual</strong> <strong>Prolog</strong> 是 一 种 OO 编 程 语 言 。 这 意 味 着 , 编 程 必 须 按 类 和 对 象 来 思 考 问 题 , 每 个<br />

操 作 都 是 由 对 象 的 方 法 来 进 行 的 。 使 用 PIE 时 不 同 , 它 与 OO 时 代 前 的 <strong>Prolog</strong> 类 似 , 无 关<br />

OO 和 GUI, 所 以 它 是 学 习 理 解 <strong>Prolog</strong> 推 理 机 的 好 帮 手 。<br />

8.3 <strong>Visual</strong> <strong>Prolog</strong> 中 的 类 与 对 象<br />

为 了 解 VIP 中 类 、 对 象 和 消 息 的 运 作 , 我 们 创 建 一 个 工 程 来 产 生 名 为 person 的 类 的 对<br />

象 , 可 以 创 建 新 的 对 象 和 删 除 对 象 。 创 建 一 个 名 为 ch08class 的 工 程 , 在 Project Tree 中 高 亮<br />

根 目 录 ch08class, 然 后 点 击 File/New 创 建 一 个 新 类 , 在 左 面 板 中 高 亮 “class” 并 在 右 边 输<br />

入 类 的 名 字 person, 选 New Package 并 选 中 Create Objects 选 项 , 如 果 图 8.1, 点 击 。<br />

IDE 创 建 三 个 文 件 并 在 三 个 不 同 的 编 辑 器 中 展 现 出 来 , 文 件 名 分 别 是 “person.i”、<br />

“person.cl” 和 “person.pro”。 这 三 个 文 件 规 定 了 “person” 类 , 它 们 包 括 了 所 有 “person”<br />

类 的 内 容 。Person.pro 文 件 已 经 很 熟 悉 了 , 这 个 文 件 中 包 含 了 谓 词 的 <strong>Prolog</strong> 代 码 , 在 <strong>Prolog</strong><br />

帮 助 文 件 中 称 之 为 谓 词 的 定 义 。 在 “person.cl” 文 件 中 有 类 的 声 明 , 本 章 中 还 要 细 说 。<br />

还 不 熟 悉 的 文 件 是 “person.i”, 它 是 person 类 的 界 面 。 界 面 就 像 是 问 询 台 , 外 界 就 是<br />

通 过 界 面 来 看 已 生 成 对 象 的 。 公 用 的 类 谓 词 在 person.cl 中 。 可 想 而 知 , 在 文 件 person.cl 和<br />

person.i 中 包 含 了 公 用 方 法 的 声 明 。<strong>Prolog</strong> 的 方 法 是 由 谓 词 来 执 行 的 , 在 这 个 文 件 中 声 明 的<br />

谓 词 , 可 由 其 它 对 象 和 类 调 用 。IDE 已 经 在 “person.i” 文 件 中 放 入 了 一 些 代 码 , 如 下 所 示 :<br />

interface person<br />

open core<br />

end interface person<br />

80


图 8.1 创 建 person 类<br />

可 以 看 到 , 这 里 还 没 有 person 的 公 有 谓 词 。 除 了 方 法 外 ,person 类 的 对 象 还 有 属 性 。<br />

我 们 这 里 暂 且 只 定 义 一 个 , 对 象 的 名 字 。 前 面 说 过 , 对 象 的 属 性 通 常 可 以 用 公 用 的 get‐ 和 set‐<br />

方 法 获 取 。 因 而 , 对 person 类 , 我 们 需 要 两 个 谓 词 ,getName 和 setName。 调 用 第 一 个 ,<br />

对 象 会 提 供 出 自 己 的 名 字 , 调 用 第 二 个 则 可 以 输 入 ( 或 是 更 改 ) 对 象 的 名 字 。 按 如 下 所 示 更<br />

改 person.i 文 件 内 容 :<br />

interface person<br />

open core<br />

predicates<br />

getName: () ‐> string Name.<br />

setName : (string Name).<br />

end interface person<br />

这 样 就 声 明 了 凡 是 由 person 类 产 生 的 对 象 都 能 知 道 的 两 个 谓 词 , 而 每 个 谓 词 都 如 同 一<br />

道 过 程 。 过 程 getName 不 需 要 变 量 , 它 能 把 对 象 的 名 字 当 作 一 个 字 串 返 回 。 另 一 过 程 setName<br />

则 需 要 一 个 字 串 类 型 的 变 量 , 可 以 设 置 对 象 的 名 字 。 做 为 一 个 局 外 人 , 只 需 要 了 解 对 象 的 这<br />

些 了 。 询 问 对 象 的 名 字 , 可 以 得 到 它 , 至 于 对 象 内 怎 么 运 作 的 不 是 你 的 事 , 只 要 得 到 名 字 就<br />

行 了 。 当 然 , 要 明 白 这 一 切 的 后 面 有 许 多 的 约 定 。getName 和 setName 在 OO 编 程 中 是 很 常<br />

见 的 过 程 。 当 创 建 其 它 过 程 时 , 要 对 外 明 确 它 们 是 做 什 么 的 。 另 外 , 要 把 过 程 作 为 谓 词 加 以<br />

声 明 , 这 在 <strong>Prolog</strong> 中 是 常 规 , 一 个 过 程 就 是 某 种 谓 词 , 后 面 一 章 中 还 要 对 声 明 做 详 细 解 释 。<br />

还 要 明 白 , 这 里 只 需 要 声 明 谓 词 , 谓 词 做 什 么 、 怎 么 做 , 也 就 是 怎 么 写 谓 词 的 代 码 , 那 是 另<br />

外 的 事 情 。<br />

关 闭 person.i 文 件 , 我 们 再 来 看 看 person.cl 文 件 。 它 是 person 的 类 声 明 , 含 有 以 下 内<br />

容 :<br />

81


class person : person<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

% @short Class information predicate.<br />

% @detail This predicate represents information predicate of this class.<br />

% @end<br />

end class person<br />

以 “%” 开 始 的 那 些 是 注 释 行 , 插 在 程 序 中 对 该 处 作 说 明 。 这 些 内 容 编 译 器 会 忽 略 掉 。<br />

Person.cl 文 件 的 首 行 是 :<br />

class person : person<br />

这 是 类 person 的 声 明 。 它 声 明 : 有 一 个 名 为 person 的 类 , 这 个 类 的 类 型 是 person。 这<br />

是 什 么 意 思 呢 ? 回 想 一 下 person.i 文 件 , 在 那 里 说 明 了 如 何 访 问 person 类 的 对 象 。 在 VIP 中<br />

则 是 经 由 对 该 类 的 类 型 描 述 而 得 知 的 。 作 为 类 型 person, 它 告 诉 说 可 以 用 person 的 界 面 访<br />

问 这 个 类 型 的 类 所 生 成 的 对 象 , 也 就 是 说 在 我 们 的 程 序 中 来 自 person 类 型 的 类 的 对 象 有 两<br />

个 可 调 用 的 过 程 :getName 和 setName。 除 了 这 两 个 过 程 之 外 必 须 要 有 一 个 构 造 器 , 因 为 前<br />

面 我 们 点 选 了 “creates objects” 选 项 , 而 对 象 是 由 构 造 器 创 建 的 。 要 声 明 构 造 器 , 按 下 面<br />

的 内 容 改 写 person.cl 文 件 :<br />

class person : person<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

% @short Class information predicate.<br />

% @detail This predicate represents information predicate of this class.<br />

% @end<br />

constructors<br />

new : (string Name).<br />

end class person<br />

在 这 个 类 中 , 谓 词 new/1 是 构 造 器 , 当 调 用 这 个 谓 词 时 它 就 会 为 person 创 建 一 个 新 的<br />

实 例 。 创 建 新 实 例 时 它 需 要 一 个 名 字 , 我 们 把 名 字 当 作 new/1 的 变 量 , 变 量 的 类 型 是 字 串 。<br />

保 存 并 关 闭 person.cl 文 件 , 我 们 最 后 来 看 看 person.pro 文 件 。 这 个 文 件 中 是 存 放 过 程<br />

代 码 的 , 这 也 是 它 被 称 作 person 类 的 执 行 文 件 的 原 因 。 在 这 里 要 输 入 期 望 对 象 进 行 的 动 作<br />

的 代 码 , 也 就 是 在 文 件 person.i 和 person.cl 中 声 明 的 谓 词 的 代 码 。 按 以 下 内 容 修 改 原 来 的 文<br />

件 :<br />

implement person<br />

open core<br />

facts<br />

name : string.<br />

82


constants<br />

className = "person/person".<br />

classVersion = "".<br />

clauses<br />

classInfo(className, classVersion).<br />

clauses<br />

new(Name) :‐<br />

name := Name.<br />

clauses<br />

getName() = name.<br />

clauses<br />

setName(Name) :‐<br />

name := Name.<br />

end implement person<br />

我 们 来 细 看 一 下 这 些 代 码 。 首 先 是 :<br />

facts<br />

name : string.<br />

这 是 在 说 , 该 类 的 对 象 有 一 个 类 型 为 字 串 的 事 实 。 事 实 是 一 个 变 量 , 这 里 我 们 认 可 了 该<br />

事 实 包 含 有 对 象 名 字 。 注 意 , 这 里 的 变 量 名 是 由 小 写 字 母 开 头 。 像 这 里 的 事 实 是 属 于 某 个 对<br />

象 的 , 对 各 个 对 象 该 事 实 可 以 有 不 同 的 值 。<br />

接 下 来 是 构 造 器 :<br />

clauses<br />

new(Name) :‐<br />

name := Name.<br />

构 造 器 是 在 person.cl 中 声 明 的 , 这 里 看 到 了 这 个 谓 词 的 代 码 。 这 里 说 明 , 当 调 用 谓 词<br />

new/1 时 , 事 实 name 必 须 取 变 量 Name( 注 意 这 里 的 大 小 写 ) 的 值 , 而 变 量 Name 是 暗 含<br />

在 一 个 外 部 调 用 中 的 。 调 用 new 时 , 需 要 给 新 对 象 一 个 名 字 。 注 意 赋 值 符 号 “:=”, 表 达 式<br />

a := B<br />

意 味 着 把 B 的 值 赋 予 a。<br />

接 下 来 是 谓 词 getName 的 子 句 :<br />

clauses<br />

getName() = name.<br />

调 用 这 个 谓 词 时 , 它 应 将 包 含 在 事 实 name 中 的 名 字 返 回 。 最 后 是 谓 词 setName:<br />

clauses<br />

setName(Name) :‐<br />

name := Name.<br />

调 用 这 个 谓 词 时 , 在 对 象 内 部 事 实 name 的 值 被 替 换 成 由 Name 给 出 的 值 。<br />

完 整 的 内 容 就 是 这 些 。 我 们 声 明 了 一 个 类 person, 它 有 一 个 属 性 , 放 在 事 实 name 中 ;<br />

我 们 为 这 个 类 声 明 了 构 造 器 , 并 在 界 面 person.i 中 声 明 了 两 个 谓 词 来 设 置 和 取 得 name。 最<br />

后 , 我 们 在 执 行 文 件 person.pro 中 写 入 了 必 要 的 代 码 。<br />

花 点 儿 时 间 消 化 这 些 内 容 : 我 们 声 明 了 一 个 类 , 但 当 程 序 运 行 时 , 动 作 是 由 对 象 来 完 成<br />

的 , 没 有 产 生 对 象 时 什 么 都 不 会 有 。 还 有 , 事 实 name 对 每 个 对 象 都 是 不 同 的 , 对 于 创 建 的<br />

每 个 对 象 它 都 有 不 同 的 值 。 另 外 , 有 两 个 地 方 来 声 明 公 用 谓 词 , 在 person.cl 中 声 明 的 谓 词<br />

叫 类 谓 词 , 它 们 把 类 当 作 一 个 整 体 来 看 待 。 而 在 person.i 中 声 明 的 谓 词 是 对 象 谓 词 , 它 们 着<br />

83


眼 于 对 象 的 个 体 。 后 面 还 有 介 绍 。<br />

现 在 我 们 来 创 建 对 象 , 这 需 要 一 个 事 件 。 在 Task 菜 单 中 创 建 一 个 名 为 “CreateObjects”<br />

新 选 项 , 进 入 Code expert 选 加 入 新 菜 单 选 项 的 代 码 。 然 后 进 入 代 码 输 入 以 下 内 容 :<br />

predicates<br />

oncreateobjects : window::menuItemListener.<br />

clauses<br />

oncreateobjects(_Source, _MenuTag) :‐<br />

NewPerson = person::new("John"),<br />

NameGiven = NewPerson:getName(),<br />

stdIO::write("New person with name ", Namegiven), stdIO::nl(),<br />

NewPerson:setName("Thomas"),<br />

stdIO::write("Name is changed. New name is ", NewPerson:getName()), stdIO::nl().<br />

点 击 “CreateObjects” 试 试 看 。 这 段 代 码 中 , 先 是 变 量 NewPerson 约 束 给 一 个 新 对 象 ,<br />

它 是 person 类 的 一 个 实 例 , 有 一 个 名 字 “John”。 应 该 记 得 , 构 造 器 new(Name) 是 在 这 个<br />

类 中 声 明 过 的 一 个 谓 词 。 调 用 一 个 类 中 的 谓 词 时 , 先 指 明 类 的 名 称 , 接 着 是 双 冒 号 , 再 接 着<br />

是 谓 词 的 名 字 和 它 的 变 量 。 接 下 来 , 由 调 用 getName() 询 问 被 创 建 的 对 象 的 名 字 , 并 将 其 约<br />

束 给 变 量 NameGiven, 然 后 写 在 消 息 窗 中 , 这 时 写 出 的 名 字 是 John。 再 接 着 ,NewPerson<br />

的 名 字 改 成 了 “Thomas”, 再 用 getName 询 问 名 字 时 便 写 出 Thomas。 请 注 意 , 在 对 象 内 的<br />

谓 词 调 用 是 对 象 名 后 随 单 个 冒 号 再 接 着 谓 词 名 与 其 变 量 。 像 这 样 使 用 构 造 器 及 getName 和<br />

setName 也 算 是 一 种 可 用 的 方 法 , 尽 管 用 相 同 的 名 字 创 建 对 象 又 直 接 来 改 变 这 个 名 字 的 办 法<br />

有 点 儿 傻 , 但 可 以 从 这 个 简 单 的 例 子 中 了 解 如 何 创 建 对 象 及 其 它 谓 词 的 使 用 方 法 。<br />

在 实 际 的 应 用 中 , 可 能 是 要 求 用 户 来 指 定 名 字 , 然 后 创 建 该 对 象 。 按 着 这 个 思 路 , 先 创<br />

建 一 个 如 图 8.2 所 示 的 对 话 框 。<br />

图 8.2<br />

输 入 名 字 并 创 建 对 象 的 表<br />

我 们 给 这 个 表 格 起 名 为 createPerson, 这 里 有 一 个 编 辑 框 和 一 个 标 签 为 “Create Object”<br />

的 按 钮 。 用 户 在 编 辑 框 中 输 入 名 字 , 点 击 CreateObject 按 钮 时 , 程 序 就 用 输 入 的 名 字 创 建 一<br />

个 对 象 。 为 使 这 个 对 话 框 能 如 此 工 作 , 需 要 输 入 CreateObject 按 钮 的 代 码 。 在 属 性 表 里 , 我<br />

们 把 按 钮 的 名 字 改 称 为 createObject_ctl, 同 样 , 把 编 辑 框 的 名 字 改 作 name_ctl, 如 此 变 动 后<br />

关 闭 对 话 框 编 辑 器 。 然 后 在 编 辑 器 中 再 重 新 打 开 这 个 对 话 框 ( 这 好 象 很 无 聊 , 但 不 如 此 代 码<br />

产 生 器 不 会 承 认 新 的 名 字 ), 接 着 生 成 createObject_ctl 按 钮 点 击 响 应 的 标 准 代 码 , 再 将 代<br />

码 改 动 如 下 :<br />

predicates<br />

onCreateObjectClick : button::clickResponder.<br />

clauses<br />

onCreateObjectclick(_Source) = button::defaultAction() :‐<br />

Name = name_ctl:getText(),<br />

84


_NewPerson = person::new(Name).<br />

要 使 用 这 个 对 话 框 需 要 把 它 显 示 出 来 。 因 此 , 需 要 更 改 Task 菜 单 中 CreateObject 选 项<br />

的 代 码 为 :<br />

predicates<br />

oncreateobjects : window::menuItemListener.<br />

clauses<br />

oncreateobjects(Source, _MenuTag) :‐<br />

NewForm = createPerson::new(Source),<br />

NewForm:show().<br />

当 用 户 选 择 CreateObject 选 项 时 , 就 会 打 开 createPerson 对 话 框 。 用 户 点 击 CreateObject<br />

按 钮 , 变 量 Name 就 约 束 为 编 辑 框 中 的 内 容 。 我 们 把 编 辑 框 的 名 字 定 为 “name_ctl”,<br />

name_ctl:getText() 会 返 回 用 户 在 编 辑 框 中 输 入 的 名 字 。 然 后 , 变 量 Name 就 用 在 person 类 的<br />

谓 词 new 的 调 用 中 , 按 用 户 输 入 的 名 字 创 建 一 个 对 象 。 不 过 对 这 个 新 对 象 看 不 出 什 么 来 ,<br />

就 是 被 创 建 了 而 已 。 要 实 实 在 在 地 感 受 它 , 我 们 再 来 改 动 一 下 文 件 person.pro 中 的 构 造 器 谓<br />

词 new/1 的 代 码 :<br />

clauses<br />

new(Name) :‐<br />

name = Name,<br />

stdIO::write("Hello world, I'm a new object. My name is ", name),<br />

stdIO::nl.<br />

这 样 , 每 创 建 一 个 对 象 , 就 会 在 消 息 窗 中 写 出 一 个 问 候 语 和 对 象 的 名 字 。<br />

8.4 类 与 对 象 是 不 同 的<br />

类 与 对 象 是 不 同 的 东 西 。 在 OO 程 序 中 的 分 工 是 这 样 的 : 类 创 建 对 象 而 对 象 完 成 谓 词 规<br />

定 的 动 作 。 因 而 有 两 种 谓 词 , 一 种 是 由 对 象 执 行 的 谓 词 , 另 一 种 是 由 类 执 行 的 谓 词 。 前 者 称<br />

为 对 象 谓 词 , 它 们 聚 焦 于 某 个 特 定 的 对 象 。getName() 谓 词 只 管 提 供 它 所 从 属 的 对 象 的 名 字 ,<br />

它 给 不 出 别 的 对 象 的 名 字 。 而 后 者 则 称 为 类 谓 词 , 它 们 并 不 聚 焦 于 某 个 特 定 对 象 而 是 聚 集 于<br />

类 的 整 体 。 在 *.i 或 *.cl 文 件 中 声 明 的 对 象 谓 词 或 类 谓 词 都 是 公 用 的 , 区 别 是 类 谓 词 把 类 作 为<br />

一 个 整 体 来 看 待 , 聚 焦 于 某 个 类 创 建 的 所 有 对 象 。 类 谓 词 还 可 以 使 用 并 非 对 象 属 性 的 事 实 。<br />

这 样 的 事 实 就 是 所 谓 类 事 实 , 是 类 作 为 一 个 整 体 的 属 性 , 由 该 类 中 所 有 对 象 共 享 。 在 VIP 的<br />

语 法 中 也 可 以 看 出 两 者 的 差 别 , 调 用 类 谓 词 时 是 这 样 的 :<br />

:: ().<br />

在 类 名 后 使 用 双 冒 号 。 而 调 用 对 象 谓 词 则 为 :<br />

: ().<br />

对 象 名 后 使 用 单 冒 号 。 回 想 一 下 第 2 章 中 创 建 代 码 显 示 一 个 表 单 的 内 容 :<br />

clauses<br />

onFileNew(Source, _MenuTag) :‐ NewForm= form1::new(Source), NewForm:show().<br />

当 点 击 菜 单 选 项 File/New 时 就 会 调 用 这 个 谓 词 , 它 由 两 个 子 调 用 构 成 , 第 一 个 是 :<br />

NewForm= form1::new(Source),<br />

它 是 调 用 form 类 的 new() 谓 词 。 现 在 , 我 们 已 经 知 道 这 个 谓 词 是 类 的 构 造 器 , 它 创 建 类 form1<br />

的 一 个 新 对 象 , 也 就 是 一 个 表 单 。 这 个 表 单 ( 该 对 象 ) 被 约 束 给 了 变 量 Newform。 这 以 后<br />

就 可 以 用 该 变 量 作 为 引 用 符 号 来 使 用 该 表 单 了 , 在 第 二 个 子 调 用 中 正 是 这 样 做 的 :<br />

NewForm:show().<br />

我 们 通 过 变 量 NewForm 引 用 该 对 象 让 它 show() 自 己 , 哇 赛 ! 它 就 出 现 在 屏 幕 上 了 ! 请 注 意<br />

85


单 冒 号 和 双 冒 号 的 使 用 差 别 。<br />

为 了 进 一 步 说 明 类 谓 词 与 对 象 谓 词 的 差 别 , 把 上 面 的 例 子 再 扩 充 一 下 , 让 它 对 创 建 的<br />

person 对 象 计 数 , 每 创 建 一 个 对 象 计 数 值 就 加 一 。 先 扩 充 类 的 声 明 , 增 加 一 个 谓 词<br />

getCreatedCount 用 于 返 回 当 前 的 计 数 值 。 按 如 下 内 容 修 改 person.cl 文 件 :<br />

class person : person<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

constructors<br />

new : (string Name).<br />

predicates<br />

getCreatedCount : () ‐> unsigned Count.<br />

end class person<br />

为 什 么 要 在 类 声 明 文 件 person.cl 中 声 明 这 个 谓 词 呢 ? 想 想 看 。 被 创 建 的 每 个 对 象 , 知<br />

道 自 己 的 每 件 事 , 但 却 并 不 知 道 其 它 对 象 的 任 何 东 西 。 在 我 们 这 个 例 子 中 , 一 个 对 象 并 不 知<br />

道 自 己 是 否 是 最 后 被 创 建 的 对 象 , 甚 至 不 知 道 是 否 存 在 其 它 的 对 象 。 但 是 类 知 道 ! 因 为 类 产<br />

生 对 象 , 它 可 以 计 数 对 象 并 保 存 这 个 计 数 。 可 以 将 类 想 像 成 一 把 覆 盖 对 象 的 伞 ( 或 是 一 只 罩<br />

着 小 鸭 的 母 鸭 )。 当 需 要 了 解 创 建 过 的 对 象 数 时 , 应 该 去 问 类 而 不 是 对 象 。 因 此 , 这 应 该 是<br />

一 个 类 谓 词 , 必 须 在 类 声 明 文 件 中 声 明 , 这 是 一 个 严 格 的 规 则 , 毫 无 例 外 。 公 有 谓 词 在 界 面<br />

和 类 声 明 文 件 中 都 要 声 明 。 由 类 执 行 的 类 谓 词 在 类 声 明 文 件 中 声 明 , 而 由 任 何 对 象 执 行 的 对<br />

象 谓 词 在 界 面 中 声 明 。 不 能 在 类 声 明 中 声 明 对 象 谓 词 , 也 不 能 在 界 面 中 声 明 类 谓 词 。<br />

我 们 已 经 将 getCreatedCount() 声 明 为 一 个 公 用 的 类 谓 词 。 接 下 来 需 要 规 定 类 的 执 行 , 还<br />

需 要 一 个 存 储 计 数 值 的 事 实 。 这 个 事 实 是 个 类 事 实 , 因 为 它 不 属 于 某 个 对 象 , 而 属 于 作 为 整<br />

体 的 类 , 它 由 全 体 对 象 共 享 。 事 实 在 执 行 文 件 person.pro 中 声 明 。 在 类 的 执 行 文 件 中 可 以 声<br />

明 和 定 义 私 有 的 对 象 项 目 ( 事 实 和 谓 词 ), 还 可 以 声 明 和 定 义 私 有 的 类 项 目 。 声 明 类 的 项 目<br />

时 , 将 关 键 字 class 放 在 相 应 的 声 明 段 前 。 对 于 我 们 的 person 类 , 可 以 这 样 实 现 :<br />

implement person<br />

open core<br />

class facts<br />

createdCount : unsigned := 0.<br />

clauses<br />

getCreatedCount() = createdCount.<br />

facts<br />

name : string.<br />

constants<br />

className = "person/person".<br />

classVersion = "".<br />

clauses<br />

classInfo(className, classVersion).<br />

clauses<br />

new(Name) :‐<br />

86


name := Name,<br />

createdCount := createdCount + 1,<br />

stdIO::write("Hello world, I'm a new object. My name is ", name),<br />

stdIO::nl,<br />

stdIO::write("I am object number ", createdCount),<br />

stdIO::nl.<br />

clauses<br />

getName() = name.<br />

clauses<br />

setName(Name) :‐<br />

name := Name.<br />

end implement person<br />

这 里 增 加 了 类 事 实 createdCount, 初 始 值 是 0, 这 个 语 法 后 面 的 章 节 中 再 说 明 。 这 里 还<br />

为 谓 词 getCreatedCount 增 加 了 一 个 子 句 , 该 谓 词 会 返 回 createdCount 的 当 前 值 。 最 后 , 在<br />

构 造 器 中 也 增 加 了 代 码 , 来 使 createdCount 递 增 。<br />

注 意 , 构 造 器 中 有 两 个 赋 值 语 句 :<br />

name := Name,<br />

createdCount := createdCount+1.<br />

它 们 形 式 相 同 , 但 前 者 为 对 象 事 实 name 赋 值 , 而 后 者 是 为 类 事 实 createdCount 赋 值 。<br />

在 OO 语 言 中 , 这 被 称 作 : 一 个 更 新 对 象 状 态 而 另 一 个 更 新 类 状 态 。 对 象 的 状 态 是 其 属 性 值<br />

的 集 合 , 当 一 个 或 多 个 值 变 化 时 , 状 态 就 变 了 。 类 状 态 也 是 一 样 。 创 建 一 个 新 对 象 时 ,<br />

createdCount 的 值 就 会 改 变 , 用 OO 语 言 来 说 , 就 是 person 类 的 状 态 改 变 了 。 在 构 造 器 new/1<br />

中 的 其 它 一 些 子 句 , 是 用 于 消 息 窗 口 显 示 的 。<br />

构 建 和 执 行 这 个 程 序 试 试 看 !<br />

8.5 类 和 模 块<br />

可 以 认 为 类 或 其 对 象 是 一 组 紧 密 相 关 的 完 成 任 务 的 谓 词 。 有 时 , 需 要 把 一 些 谓 词 集 合 在<br />

一 起 却 又 不 需 要 一 个 类 来 产 生 对 象 。 在 <strong>Prolog</strong> 中 , 可 以 声 明 一 个 类 是 无 对 象 构 造 的 。 这 样<br />

的 类 , 就 像 一 个 模 块 , 在 File/New 中 , 选 class, 在 创 建 工 程 项 目 对 话 框 中 , 不 要 选 中 creates<br />

objects 选 项 。 不 创 建 对 象 的 类 只 有 两 个 描 述 文 件 , 界 面 文 件 就 不 需 要 了 。 因 为 没 有 对 象 ,<br />

也 就 没 有 要 公 用 的 对 象 谓 词 , 也 就 不 需 要 界 面 文 件 。 因 为 没 有 界 面 文 件 , 这 样 的 类 也 称 为 非<br />

类 型 的 。 在 .cl 文 件 中 可 以 看 到 , 首 行 没 有 冒 号 , 也 没 有 类 型 。 这 样 的 类 也 没 有 构 造 器 。 最<br />

后 , 因 为 没 有 对 象 , 这 样 的 类 也 没 有 对 象 谓 词 和 对 象 事 实 。 它 们 只 能 有 类 谓 词 和 类 事 实 。<br />

我 们 再 来 扩 充 一 下 上 面 的 程 序 , 加 上 一 个 用 于 输 入 的 类 。 先 创 建 一 个 名 为 output 的 类 。<br />

我 们 只 用 这 个 类 来 产 生 输 出 , 它 不 需 要 对 象 , 所 以 请 不 要 选 中 creates object 选 项 。 一 定 要<br />

把 这 个 类 放 在 person 的 包 中 。 然 后 , 类 。 此 时 IDE 仅 只 提 供 出 了 两 个 文 件 :output.cl<br />

和 output.pro。 因 为 已 经 声 明 这 个 类 是 不 产 生 对 象 的 , 因 而 也 就 没 有 对 象 谓 词 要 声 明 , 也 就<br />

不 需 要 output.i 文 件 了 。<br />

IDE 已 经 产 生 了 output.cl 的 标 准 代 码 , 在 文 件 中 再 增 加 些 内 容 :<br />

class output<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

87


% @short Class information predicate.<br />

% @detail This predicate represents information predicate of this class.<br />

% @end<br />

myWrite : (string ToWrite).<br />

myWrite : (unsigned ToWrite).<br />

end class output<br />

保 存 并 关 闭 文 件 。 再 打 开 output.pro 文 件 添 加 代 码 如 下 :<br />

implement output<br />

open core<br />

constants<br />

className = "person/output".<br />

classVersion = "".<br />

clauses<br />

classInfo(className, classVersion).<br />

clauses<br />

myWrite(ThingToWrite) :‐<br />

stdIO::write(ThingToWrite).<br />

end implement output.<br />

在 output 类 中 , 我 们 是 用 标 准 的 写 语 句 write 来 实 现 谓 词 myWrite() 的 , 在 实 际 的 编 程<br />

中 不 会 这 样 做 , 也 就 是 说 不 会 去 创 建 一 个 类 来 做 标 准 语 句 可 以 做 的 事 。 还 应 当 注 意 一 下 , 我<br />

们 在 output.cl 文 件 中 两 次 声 明 myWrite/1 谓 词 , 使 得 这 个 谓 词 既 可 用 于 写 串 , 也 可 以 用 于<br />

写 无 符 号 整 数 。 在 OOP 中 这 叫 多 态 现 象 : 一 个 谓 词 可 以 有 多 种 使 用 方 式 。<br />

再 打 开 person.pro 文 件 , 更 改 new/1 的 代 码 来 使 用 新 谓 词 myWrite/1:<br />

clauses<br />

new(Name) :‐<br />

name = Name,<br />

createCount := createCount + 1,<br />

output::myWrite("Hello world, I am a new object. My name is "),<br />

output::myWrite(getName()),<br />

output::myWrite(". I am person number "),<br />

Number = person::getCreatedCount(),<br />

output::myWrite(Number),<br />

stdIO::nl().<br />

构 建 并 执 行 程 序 , 应 该 与 以 前 执 行 结 果 相 同 。<br />

我 们 已 经 创 建 了 一 个 与 真 实 的 面 向 对 象 系 统 相 似 的 程 序<br />

9 , 有 用 户 界 面<br />

(createPerson.frm), 有 执 行 任 务 的 对 象 ( 告 诉 我 们 对 象 的 名 字 和 数 量 ), 有 服 务 用 户 输<br />

入 的 模 块 。<br />

我 们 刚 才 做 的 是 OO 编 程 的 基 本 规 则 , 名 字 信 息 是 存 储 于 对 象 中 的 , 应 该 从 那 里 寻 求 它 ,<br />

要 写 出 名 字 , 就 应 该 找 对 象 来 提 供 它 。 因 此 , 用 getName() 调 用 来 获 取 名 字 。 对 象 的 计 数 则<br />

9<br />

尽 管 这 个 程 序 没 做 多 少 事 , 只 是 生 成 对 象 。<br />

88


是 存 储 在 类 事 实 中 的 , 所 以 应 该 找 类 来 询 问 创 建 了 多 少 个 对 象 ( 我 们 使 用 了 调 用<br />

person::getCreatedCount(), 注 意 双 冒 号 )。 还 有 , 不 必 非 用 这 样 的 代 码 :<br />

output::myWrite(getName()),<br />

可 以 用 :<br />

output::myWrite(Name),<br />

当 然 , 前 后 一 贯 地 使 用 get‐ 谓 词 获 取 值 更 正 规 些 。<br />

8.6 跟 踪 对 象 : 一 个 简 单 的 面 向 对 象 的 数 据 库<br />

在 前 一 节 , 程 序 创 建 了 对 象 , 没 有 更 多 的 东 西 。 这 一 节 , 我 们 来 扩 充 一 下 , 用 对 象 保 存<br />

数 据 , 需 要 的 时 候 再 让 对 象 给 我 们 恢 复 这 些 数 据 。 这 也 就 是 面 向 对 象 数 据 库 的 简 单 原 理 。<br />

目 前 为 止 , 我 们 知 道 了 如 何 创 建 对 象 以 及 为 它 们 的 属 性 指 定 数 据 。 接 着 问 题 来 了 : 如 果<br />

再 获 取 这 些 数 据 呢 ? 要 重 新 取 得 数 据 , 就 需 要 重 新 得 到 对 象 。 怎 么 重 新 得 到 对 象 呢 ? 有 多 种<br />

方 法 , 如 , 对 象 创 建 时 是 绑 定 给 一 个 变 量 的 , 这 个 变 量 就 可 以 当 成 该 对 象 的 指 针 。 查 找 对 象<br />

的 一 个 办 法 , 是 可 以 保 存 一 个 绑 定 变 量 的 表 , 当 需 要 了 解 某 个 对 象 时 , 只 需 要 在 这 个 变 量 表<br />

里 查 一 下 , 找 到 相 应 的 对 象 , 再 对 它 提 问 相 关 的 数 据 。 这 一 节 里 , 我 们 用 另 一 个 方 法 , 直 接<br />

把 创 建 的 对 象 放 在 一 个 表 里 , 需 要 某 个 对 象 时 , 搜 索 这 个 表 找 到 相 应 的 对 象 。 因 为 要 用 到 表<br />

(list), 可 以 先 看 一 下 第 10 章 的 前 两 节 。<br />

假 设 我 们 需 要 一 个 关 于 人 员 的 数 据 库 , 我 们 要 知 道 每 个 人 的 名 字 和 编 号 。 名 字 可 以 随 便<br />

取 , 而 编 号 是 程 序 决 定 的 。 我 们 来 创 建 一 个 程 序 , 使 用 GUI, 程 序 名 是 ch12classDB。 这 个 程<br />

序 看 起 来 和 ch12class 差 不 多 , 不 过 我 们 还 是 另 外 创 建 一 个 工 程 而 不 要 去 改 ch12class。 在 这<br />

个 工 程 中 创 建 一 个 person 包 , 包 里 创 建 一 个 person 类 , 我 们 用 它 放 数 据 , 也 就 是 说 , 数 据<br />

存 放 在 person 类 中 的 内 部 事 实 数 据 库 中 。 先 创 建 这 个 工 程 、person 包 及 person 类 , 注 意 ,<br />

person 类 不 需 要 创 建 对 象 。<br />

因 为 我 们 想 要 做 个 数 据 库 , 所 以 必 须 要 实 现 一 个 数 据 库 的 基 本 功 能 。 这 就 是 , 必 须 要 能<br />

创 建 对 象 , 显 示 一 个 或 多 个 对 象 以 及 删 除 对 象 。 因 此 , 我 们 先 在 任 务 菜 单 中 加 四 个 选 项 :<br />

• CreatePerson 用 来 创 建 一 个 对 象 ,<br />

• ShowAll 用 来 显 示 所 有 人 员 的 数 据 ,<br />

• ShowSingle 用 来 显 示 某 一 人 员 的 数 据 ,<br />

• DeletePerson 用 来 删 除 一 个 人 员 。<br />

在 任 务 菜 单 中 加 上 这 四 个 选 项 , 再 用 代 码 专 家 为 它 们 生 成 标 准 代 码 。 我 们 过 会 儿 再 来 看<br />

这 些 菜 单 项 。<br />

数 据 要 放 在 类 person 产 生 的 对 象 里 。 在 定 义 文 件 person.pro 中 , 要 加 两 类 事 实 。 一 类<br />

是 对 象 事 实 name 和 number, 存 放 每 个 对 象 的 数 据 , 另 一 类 是 类 事 实 createdCount 和<br />

createdObjects。 类 事 实 createdCount 来 对 已 经 创 建 的 对 象 做 计 数 , 而 createdObjects 是 一 个<br />

含 有 已 经 创 建 对 象 的 表 。 下 面 是 它 们 的 声 明 :<br />

class facts<br />

createdCount : unsigned := 0.<br />

createdObjects : person* :=[] .<br />

facts<br />

name : string.<br />

number : unsigned :=0.<br />

注 意 一 下 类 事 实 createdObjects, 声 明 说 它 的 类 型 是 person 的 表 , 但 编 译 器 怎 么 知 道 这<br />

个 类 型 是 什 么 呢 ? 答 案 在 类 声 明 里 , 文 件 person.cl 第 一 行 说 :<br />

class person : person<br />

这 是 告 诉 编 译 器 , 这 个 person 类 是 person 类 型 的 。 这 就 是 说 , 每 个 该 类 中 创 建 的 对 象 ,<br />

都 能 对 在 接 口 person.i 中 提 到 过 的 对 象 谓 词 的 调 用 起 反 应 。 而 在 这 个 接 口 中 , 我 们 声 明 了 一<br />

些 谓 词 , 这 中 间 就 包 括 有 设 置 和 取 得 某 个 对 象 的 属 性 的 谓 词 。 在 我 们 这 个 例 子 中 , 对 象 有 两<br />

89


个 属 性 ( 事 实 ):name 和 number。 名 字 是 由 用 户 指 定 的 , 所 以 需 要 两 个 谓 词 :setName()<br />

用 于 设 置 该 属 性 ,getName() 用 于 重 新 取 得 该 属 性 值 。 编 号 是 由 程 序 计 算 得 到 的 , 所 以 这 个<br />

属 性 只 需 要 一 个 谓 词 getNumber()。 在 person.i 中 要 声 明 这 三 个 谓 词 , 打 开 这 个 文 件 加 入 下<br />

面 的 代 码 :<br />

predicates<br />

getName : () ‐> string Name.<br />

setName : (string Name) procedure (i).<br />

getNumber : () ‐> unsigned Number .<br />

接 着 需 要 为 这 些 谓 词 在 person.pro 中 添 加 代 码 , 如 下 :<br />

clauses<br />

getName() = name.<br />

setName(Name) :‐<br />

name := Name.<br />

getNumber() = number.<br />

要 创 建 对 象 , 我 们 需 要 一 个 所 谓 构 造 器 的 东 西 , 它 是 一 个 创 建 对 象 的 类 谓 词 , 其 实 它 做<br />

的 就 是 为 新 创 建 对 象 预 留 一 些 内 存 , 生 成 类 似 指 针 那 样 的 东 西 指 向 对 象 。 这 个 指 针 绑 定 给 一<br />

个 变 量 , 我 们 可 以 用 这 个 变 量 来 找 到 相 应 的 对 象 。 为 构 造 对 象 , 声 明 构 造 器 new(), 由 于 它<br />

是 由 类 谓 词 定 义 的 ( 类 创 建 对 象 , 对 象 不 能 直 接 创 建 对 象 ), 所 以 把 这 个 声 明 放 在 person.cl<br />

文 件 中 , 声 明 是 这 样 的 :<br />

constructors<br />

new : (string Name).<br />

构 造 器 new() 有 一 个 参 数 , 就 是 新 创 建 对 象 的 名 字 。 程 序 应 要 求 用 户 提 供 这 个 名 字 , 因<br />

此 我 们 需 要 创 建 一 个 名 为 createPerson.frm 的 表 格 , 如 图 8.3 示 。 它 很 简 单 , 有 一 些 静 态 文<br />

字 , 一 个 编 辑 控 件 name_ctl 和 两 个 按 钮 ; 带 create 字 样 的 按 钮 是 控 件 createButton_ctl, 它<br />

是 新 建 的 , 点 击 它 就 创 建 一 个 对 象 ; 另 一 个 带 有 Close 字 样 的 按 钮 是 控 件 close_ctl, 它 是 建<br />

立 一 个 表 格 时 由 IDE 提 供 的 标 准 的 按 钮 。 我 们 利 用 这 个 以 前 就 有 的 按 钮 , 点 击 它 就 关 闭<br />

表 格 。 这 样 的 用 法 , 我 们 不 需 要 为 这 个 按 钮 再 加 什 么 代 码 。 另 外 还 有 两 个 创 建 表 格 时 IDE 提<br />

供 的 按 钮 : 和 , 我 们 不 用 它 , 删 除 就 行 了 。<br />

图 8.3<br />

创 建 一 个 人 员 的 表 格<br />

剩 下 的 事 是 为 点 击 按 钮 时 产 生 的 事 件 设 计 代 码 。 在 表 格 编 辑 器 中 高 亮 <br />

按 钮 , 在 属 性 窗 口 选 择 Events 标 签 , 到 ClickResponder 选 项 , 选 定 onCreateNuttonClick。 双<br />

90


击 这 个 选 项 , 在 createperson.pro 中 添 加 下 面 的 代 码 :<br />

predicates<br />

clauses<br />

onCreateButtonClick : button::clickResponder.<br />

onCreateButtonClick(_Source) = button::defaultAction :‐<br />

Name = name_ctl:getText(),<br />

_NewPerson = person::new(Name).<br />

当 用 户 点 击 按 钮 时 , 类 person 的 构 造 器 谓 词 new() 就 会 被 调 用 。 这 个 谓 词 应 该<br />

放 在 person.pro 中 , 像 这 样 :<br />

clauses<br />

new(Name) :‐<br />

name := Name,<br />

createdCount := createdCount + 1,<br />

stdIO::write("Hello World, I am a new object person. My name is: ", getName()),<br />

number := getCreatedCount(),<br />

stdIO::write(". My number is: ", number),<br />

addToObjectsList(This),<br />

stdIO::nl.<br />

第 一 行 , 把 用 户 提 供 的 名 字 赋 予 属 性 name, 接 着 计 数 器 createdCount 加 一 , 再 显 示 出<br />

一 条 消 息 。 接 下 来 , 属 性 number 被 设 置 成 计 数 器 的 结 果 值 , 这 样 , 每 个 对 象 都 会 有 一 个 唯<br />

一 的 编 号 。 显 示 编 号 之 后 , 有 一 个 谓 词 调 用 :<br />

addToObjectsList(This)<br />

这 个 谓 词 是 一 个 私 有 的 类 谓 词 , 它 只 能 从 该 类 中 调 用 , 所 以 必 须 在 person.pro 中 声 明 ,<br />

它 的 声 明 和 子 句 是 这 样 的 :<br />

predicates<br />

addToObjectsList : (person NewPerson) procedure (i).<br />

clauses<br />

addToObjectsList(NewPerson) :‐<br />

createdObjects := [NewPerson | createdObjects].<br />

调 用 这 个 谓 词 时 , 我 们 使 用 了 参 数 This, 这 是 一 个 特 殊 的 变 量 。This 可 以 用 在 谓 词 中 表<br />

示 当 前 受 控 的 那 个 对 象 , 当 我 们 问 一 个 对 象 的 它 名 字 时 ,This 就 表 示 我 们 对 其 提 问 要 它 的 名<br />

字 的 那 个 对 象 。 在 我 们 现 在 这 个 例 子 中 ,This 就 表 示 由 构 造 器 刚 刚 创 建 的 那 个 对 象 。 这 段 代<br />

码 是 把 新 创 建 的 对 象 放 在 已 经 创 建 对 象 表 的 表 头 上 。<br />

最 后 要 做 的 是 给 任 务 菜 单 的 选 项 加 代 码 。 用 代 码 专 家 系 统 给 CreatePerson 加 上 标 准 代<br />

码 , 再 把 它 改 成 :<br />

predicates<br />

oncreateperson : window::menuItemListener.<br />

clauses<br />

oncreateperson(Source, _MenuTag) :‐<br />

NewForm = createperson::new(Source),<br />

NewForm:show().<br />

这 样 , 创 建 一 个 人 员 对 象 的 代 码 就 完 整 了 。 试 试 构 建 和 执 行 工 程 , 生 成 一 些 对 象 看 看 。<br />

如 果 想 把 事 情 弄 得 更 有 趣 些 , 可 以 再 增 加 一 些 属 性 。<br />

可 以 创 建 对 象 了 , 还 需 要 显 示 它 们 。 为 此 , 再 创 建 一 个 谓 词 showAllPerson/0 并 把 它 放<br />

91


在 person 类 中 。 因 为 它 对 所 有 对 象 起 作 用 , 所 以 是 个 类 谓 词 , 而 且 它 是 从 person 类 外 面 调<br />

用 的 , 它 还 是 个 公 用 类 谓 词 。 这 就 需 要 在 person.cl 中 声 明 它 , 如 下 :<br />

showAllPersons : () procedure.<br />

接 下 来 在 person.pro 中 加 入 定 义 :<br />

showAllPersons() :‐<br />

stdIO::write("These are the persons in the database \n"),<br />

showAllObjects(createdObjects).<br />

这 个 谓 词 先 发 出 一 个 消 息 , 然 后 调 用 另 一 个 谓 词 showAllObjects/1 来 显 示 所 有 人 员 。 谓<br />

词 showAllObjects/1 从 对 象 表 里 一 个 一 个 地 取 出 表 头 , 提 问 对 象 的 名 字 和 编 号 , 然 后 再 处 理<br />

表 尾 , 一 直 到 表 空 了 为 止 。 这 个 谓 词 是 一 个 私 有 类 谓 词 , 所 以 在 person.pro 文 件 中 声 明 :<br />

class predicates<br />

clauses<br />

showAllObjects : (person* CreatedObjects).<br />

showAllObjects( [Head | Tail]) :‐<br />

stdIO::write("Name: ", Head:getName(), " with number: ", Head:getNumber() ),<br />

stdIO::nl, !,<br />

showAllObjects(Tail).<br />

showAllObjects( [ ] ).<br />

我 们 还 要 把 这 段 代 码 与 任 务 菜 单 中 的 选 项 联 系 起 来 , 到 IDE 生 成 的 标 准 代 码 上 , 把 它 改<br />

成 :<br />

predicates<br />

clauses<br />

onshowall : window::menuItemListener.<br />

onshowall(_Source, _MenuTag) :‐<br />

person::showAllPersons().<br />

程 序 现 在 可 以 在 消 息 窗 口 中 显 示 对 象 列 表 了 。<br />

列 表 当 然 很 好 , 不 过 对 数 据 库 我 们 常 常 要 求 取 出 某 一 个 对 象 。 我 们 接 着 来 设 计 任 务 菜 单<br />

中 ShowSingle 的 代 码 。 这 个 选 项 要 显 示 的 是 一 个 单 个 的 对 象 , 我 们 这 样 做 : 先 显 示 名 字 列<br />

表 以 便 选 择 一 个 对 象 , 然 后 用 名 字 找 到 对 象 和 其 它 的 数 据 , 在 这 里 就 是 编 号 。 把 IDE 给 任 务<br />

菜 单 选 项 ShowSingle 生 成 的 标 准 代 码 改 成 :<br />

predicates<br />

clauses<br />

onshowsingle : window::menuItemListener.<br />

onshowsingle(_Source, _MenuTag) :‐<br />

_Index), !,<br />

person::getAllNames(Nameslist),<br />

b_true = vpiCommonDialogs::listSelect("Select a name", NamesList, 0, Name,<br />

person::showSinglePerson(Name).<br />

onshowsingle(_Source, _MenuTag) :‐ !.<br />

在 person.cl 中 声 明 新 的 谓 词 :<br />

getAllNames : (string* Nameslist) procedure (o).<br />

showSinglePerson : (string Name) procedure (i).<br />

谓 词 getAllNames/1 生 成 一 个 所 有 对 象 名 字 的 表 , 代 码 是 :<br />

clauses<br />

getAllNames(NamesList) :‐<br />

92


class predicates<br />

clauses<br />

getNames(createdObjects, NamesList).<br />

getNames : (person* CreatedObjects, string* NamesList) procedure (i,o).<br />

getNames(ObjectsList, NamesList) :‐<br />

[Head | Tail] = ObjectsList, !,<br />

Name = Head:getName(),<br />

getNames(Tail, TailNamesList),<br />

NamesList = [Name | TailNamesList].<br />

getNames([], []) :‐ !.<br />

谓 词 showSinglePerson/1 取 得 名 字 并 调 用 另 一 个 谓 词 getData/3 来 获 取 数 据 。getData/3<br />

谓 词 有 一 个 我 们 已 经 熟 悉 了 的 模 式 , 它 取 得 对 象 的 表 , 从 表 中 取 出 第 一 个 对 象 的 名 字 , 与 作<br />

为 参 数 给 出 的 那 个 名 字 比 较 。 如 果 两 个 名 字 不 一 样 , 它 就 会 继 续 处 理 剩 余 的 表 的 内 容 。 如 果<br />

名 字 一 样 , 就 会 调 用 第 二 个 子 句 , 取 表 的 第 一 个 对 象 ( 与 我 们 要 找 的 名 字 相 同 的 那 个 对 象 ),<br />

获 取 其 它 的 数 据 , 也 就 是 编 号 。 然 后 ,showSinglePerson/1 把 这 些 数 据 写 到 消 息 窗 口 中 。 构<br />

建 和 执 行 程 序 , 检 查 一 下 看 看 作 得 对 不 对 。<br />

我 们 还 需 要 有 删 除 一 个 人 员 的 代 码 。 现 在 读 者 应 该 是 很 熟 悉 程 序 了 , 这 里 就 只 给 出 代 码 ,<br />

不 再 多 解 释 。 把 任 务 菜 单 选 项 DeletePerson 的 标 准 代 码 改 成 :<br />

predicates<br />

ondeleteperson : window::menuItemListener.<br />

clauses<br />

ondeleteperson(_Source, _MenuTag) :‐<br />

person::getAllNames(Nameslist),<br />

b_true = vpiCommonDialogs::listSelect("Select a name", NamesList, 0, Name,<br />

_Index), !,<br />

person::deletePerson(Name).<br />

ondeleteperson(_,_).<br />

与 编 写 showSingle() 的 代 码 差 不 多 , 先 取 得 所 有 的 名 字 , 显 示 给 用 户 , 用 户 选 了 一 个 名<br />

字 后 就 把 相 应 的 对 象 从 表 里 删 除 。 删 除 是 由 在 类 person 中 的 谓 词 deletePerson/1 执 行 的 。 这<br />

个 谓 词 是 一 个 公 用 类 谓 词 , 所 以 要 在 person.cl 中 声 明 :<br />

deletePerson : (string Name) procedure (i).<br />

谓 词 的 代 码 是 这 样 :<br />

deletePerson(Name) :‐<br />

[Object | Tail] = createdObjects,<br />

Name = Object:getName(), !,<br />

createdObjects := Tail,<br />

stdIO::write("Person ", Name, " has been deleted \n").<br />

deletePerson(Name) :‐<br />

[Object | Tail] = createdObjects,<br />

createdobjects := Tail,<br />

deleteperson(Name), !,<br />

createdObjects := [Object | createdObjects].<br />

deletePerson(_).<br />

与 其 它 谓 词 一 样 , 取 出 对 象 表 的 表 头 , 把 它 的 名 字 与 用 户 选 择 的 名 字 相 比 较 , 如 果 一 样<br />

93


就 删 除 它 。 如 果 名 字 不 一 样 , 在 第 二 个 子 句 中 返 还 这 个 对 象 , 接 着 处 理 表 尾 , 调 用 返 回 时 ,<br />

把 返 还 的 对 象 和 剩 余 的 表 尾 合 起 来 。 仔 细 研 究 一 下 我 们 处 理 表 createdObjects 的 方 法 , 在 其<br />

它 的 谓 词 中 这 个 表 是 当 作 一 个 参 数 的 。 我 们 知 道 , 它 是 一 个 事 实 数 据 库 , 这 意 味 着 在<br />

person.pro 文 件 的 任 何 地 方 都 可 以 使 用 这 个 表 。 来 看 看 它 是 如 何 起 作 用 的 : 我 们 取 了 整 个 表<br />

createdObjects, 分 离 出 表 头 , 如 果 这 个 对 象 的 名 字 就 是 要 找 的 ,createdObjects 中 就 剩 下 表<br />

尾 了 。 把 要 找 的 对 象 从 表 中 删 除 就 行 了 。 如 果 表 头 中 还 不 是 我 们 要 找 的 名 字 , 第 二 个 子 句 中<br />

我 们 再 分 离 出 表 头 , 但 这 一 次 取 出 的 对 象 返 还 给 了 变 量 Object。 接 着 deletePerson/1 递 归 调<br />

用 , 它 再 使 用 表 createdObjects, 不 过 此 时 表 里 已 经 没 有 了 ( 以 前 的 ) 第 一 项 了 。 当 删 除 了<br />

指 定 的 对 象 后 , 表 又 与 以 前 的 项 合 在 一 起 。 如 果 还 不 能 理 解 , 再 看 看 后 面 的 关 于 表 的 章 节 好<br />

了 。<br />

现 在 可 以 构 建 和 执 行 程 序 了 , 它 应 该 工 作 得 不 错 。 但 可 能 有 人 担 心 一 个 细 节 : 删 除 一 个<br />

对 象 , 我 们 是 把 它 从 表 里 删 除 的 , 但 创 建 它 时 是 为 它 保 留 了 一 些 内 存 的 , 我 们 并 没 有 移 除 这<br />

些 内 存 。 可 以 说 对 象 还 在 那 里 , 只 不 过 我 们 没 办 法 访 问 它 了 。 因 此 , 相 对 于 构 造 器 , 有 人 可<br />

能 希 望 有 个 类 似 相 反 的 东 西 , 解 构 器 什 么 的 。 但 是 ,<strong>Visual</strong> <strong>Prolog</strong> 中 没 有 这 样 的 东 西 , 不 过<br />

有 另 一 类 的 一 个 东 西 , 叫 垃 圾 收 集 器 。 这 是 一 个 程 序 , 它 检 查 创 建 的 对 象 , 如 果 一 个 对 象 再<br />

也 不 能 被 引 用 了 , 它 就 会 把 这 个 对 象 从 内 存 中 清 理 掉 。 这 样 , 当 我 们 从 表 里 取 消 了 对 某 个 对<br />

象 的 引 用 关 系 时 , 它 会 被 垃 圾 收 集 器 从 内 存 中 清 除 。<br />

94


第 9 章 <strong>Visual</strong> <strong>Prolog</strong> 中 的 声 明<br />

10<br />

在 <strong>Visual</strong> <strong>Prolog</strong> 中 , 程 序 执 行 前 要 进 行 编 译 。 编 译 就 是 把 <strong>Prolog</strong> 代 码 转 换 成 计 算 机 处 理<br />

器 可 以 执 行 的 代 码 , 它 可 是 件 事 儿 ! 编 译 是 由 编 译 器 来 做 的 。 为 了 让 编 译 器 工 作 得 好 , 必 须<br />

提 供 它 所 需 要 的 信 息 。 本 章 就 来 讨 论 这 个 问 题 。 在 程 序 里 , 我 们 提 供 代 码 。 代 码 中 有 一 部 分<br />

不 是 说 要 做 什 么 事 的 , 而 是 说 代 码 是 什 么 的 : 是 谓 词 , 是 子 句 , 是 事 实 ? 要 告 诉 编 译 器 程 序<br />

代 码 每 个 段 落 的 这 些 内 容 。 这 是 程 序 中 被 称 作 “ 声 明 ” 的 东 西 所 完 成 的 事 情 。 换 个 说 法 , 程<br />

序 是 由 若 干 部 分 构 成 的 , 每 个 部 分 开 头 有 个 关 键 字 , 它 告 诉 编 译 器 这 部 分 是 什 么 。 而 这 一 章<br />

就 是 关 于 关 键 字 的 , 它 们 的 含 意 以 及 程 序 中 各 部 分 的 内 容 。 我 们 要 从 最 基 本 的 开 始 , 程 序 是<br />

由 什 么 组 成 的 ? 然 后 再 来 看 有 哪 些 关 键 字 , 它 们 的 含 意 , 如 何 使 用 。<br />

这 一 章 会 很 正 规 , 计 算 机 就 是 这 么 干 的 。 为 了 让 正 规 的 句 法 好 理 解 , 有 时 会 用 一 种 放 在<br />

尖 括 号 中 的 特 殊 字 词 的 说 明 。 尖 括 号 中 的 字 是 有 所 指 的 , 我 们 尽 力 让 这 些 字 词 好 懂 。 我 们<br />

会 用 表 示 一 个 类 的 名 字 , 这 样 我 们 也 就 可 以 用 .pro 来 描 述 一 个 有 某<br />

个 类 的 代 码 的 文 件 名 。 对 尖 括 号 中 的 字 词 会 有 说 明 , 但 大 多 数 时 候 它 们 的 意 义 是 很 清 楚 的 。<br />

如 果 是 第 一 次 读 , 这 章 可 以 跳 过 去 。 不 过 也 不 好 说 , 这 章 的 内 容 可 以 让 人 很 好 地 感 受 到<br />

<strong>Visual</strong> <strong>Prolog</strong> 的 丰 富 性 。 硬 着 头 皮 啃 啃 也 是 值 得 的 。<br />

9.1 声 明 和 编 译<br />

在 经 典 的 <strong>Prolog</strong> 比 如 PIE 中 , 要 用 一 个 谓 词 时 我 们 直 接 就 用 了 , 不 需 要 告 诉 <strong>Prolog</strong> 我 们<br />

的 打 算 。 前 面 使 用 PIE 的 章 节 中 关 于 家 庭 的 程 序 ,grandFather 的 子 句 就 是 直 接 用 传 统 的 <strong>Prolog</strong><br />

谓 词 头 部 和 体 部 构 造 写 下 来 就 行 了 。 不 需 要 劳 神 用 代 码 明 确 地 告 诉 推 理 机 这 个 谓 词 是 什 么<br />

样 , 没 有 任 何 这 样 的 信 息 。 同 样 , 在 经 典 <strong>Prolog</strong> 中 对 函 子 的 使 用 也 是 直 接 的 , 不 需 要 事 先<br />

说 什 么 。 需 要 的 时 候 , 用 就 是 了 。<br />

但 在 <strong>Visual</strong> <strong>Prolog</strong> 中 , 我 们 需 要 在 写 出 子 句 代 码 之 前 , 显 式 地 向 编 译 器 声 明 所 用 的 谓 词 。<br />

使 用 任 何 函 子 前 , 也 需 要 声 明 它 们 的 存 在 , 告 诉 编 译 器 。<strong>Visual</strong> <strong>Prolog</strong> 中 需 要 这 种 预 告 , 它<br />

对 确 保 运 行 时 异 常 ( 错 误 , 缺 陷 ) 尽 可 以 反 映 成 编 译 时 的 错 误 至 关 重 要 。 运 行 时 异 常 , 意 味<br />

着 程 序 运 行 时 出 现 错 误 。 例 如 , 原 本 打 算 用 整 数 做 函 子 的 参 数 却 错 用 成 了 实 数 , 运 行 这 样 的<br />

程 序 时 就 会 出 错 。 我 们 说 它 是 运 行 时 错 误 , 程 序 会 因 为 这 而 停 在 那 里 。 这 是 许 多 其 它 的 有 所<br />

谓 解 释 程 序 的 编 程 语 言 的 情 况 。 这 些 语 言 的 程 序 , 是 用 文 本 形 式 存 储 着 的 , 只 有 当 运 行 到 需<br />

要 的 程 序 代 码 时 , 才 一 句 一 句 地 由 解 释 程 序 转 换 成 机 器 代 码 加 以 执 行 。 在 VIP 中 是 用 编 译 器<br />

替 代 解 释 程 序 的 , 它 在 程 序 执 行 前 就 把 整 个 程 序 转 换 成 机 器 代 码 了 , 运 行 时 处 理 器 直 接 执 行<br />

机 器 代 码 。 这 样 , 编 译 器 在 程 序 运 行 前 , 就 可 以 对 代 码 进 行 检 查 。<br />

<strong>Visual</strong> <strong>Prolog</strong> 由 于 这 个 特 性 而 改 进 了 程 序 的 整 体 效 率 。 编 程 人 员 不 必 等 到 程 序 真 的 实 际<br />

运 行 时 才 检 测 程 序 的 缺 陷 。 其 实 , 对 有 经 验 的 编 程 人 员 来 说 , 更 能 体 会 到 这 会 节 约 多 少 时 间 !<br />

一 个 引 发 运 行 时 异 常 的 特 定 序 列 事 件 , 运 行 时 可 能 很 难 捕 捉 到 , 程 序 中 的 缺 陷 好 几 年 后 才 被<br />

发 现 , 或 是 在 紧 要 关 头 显 现 出 来 , 这 是 常 有 的 事 。<br />

所 有 这 些 要 求 , 都 意 味 着 必 须 向 编 译 器 提 供 关 于 谓 词 和 代 码 中 其 它 存 在 着 的 事 物 的 明 确<br />

的 指 令 。 这 样 的 指 令 有 两 种 不 同 的 概 念 。 在 声 明 (declaration) 中 , 一 般 性 地 描 述 比 如 说 谓<br />

词 是 什 么 样 子 、 用 什 么 类 型 的 参 数 ; 而 在 定 义 (definition) 或 是 说 实 现 (implemantation)<br />

中 , 则 对 该 谓 词 写 下 特 定 的 子 句 。 不 要 把 它 们 弄 混 了 , 在 本 章 中 及 VIP 文 档 中 都 要 用 到 它 们 。<br />

有 时 , 会 需 要 把 它 们 两 个 结 合 在 一 个 句 子 里 , 如 在 域 (domain) 和 常 数 (constant) 中 ; 有<br />

时 , 又 必 须 在 不 同 的 句 子 里 声 明 和 定 义 , 如 对 谓 词 。<br />

本 章 要 说 的 是 声 明 什 么 、 怎 么 声 明 和 把 声 明 放 在 程 序 的 什 么 地 方 。 当 然 并 不 全 , 我 们 只<br />

介 绍 最 常 用 的 和 对 初 学 者 来 说 很 重 要 的 声 明 。<br />

10<br />

本 章 内 容 部 分 取 材 于 Sabu Francis 写 的 手 册 , 部 分 取 材 于 VIP 的 帮 助 文 件 , 还 有 部 分 取 材 于 <strong>Visual</strong> <strong>Prolog</strong> V4.0 的 <strong>Visual</strong> <strong>Prolog</strong><br />

语 言 指 南 。 这 里 只 描 述 了 部 分 声 明 - 为 初 学 者 起 见 ☺。<br />

95


9.2 基 本 概 念 和 关 键 字 概 述<br />

给 计 算 机 编 程 , 要 使 用 某 种 语 言 。 这 本 书 用 的 是 <strong>Prolog</strong>。 要 能 谈 论 这 个 语 言 , 我 们 就 需<br />

要 一 些 基 本 概 念 。 这 一 节 就 讲 讲 这 些 基 本 概 念 。<br />

Identifier( 标 识 )<br />

标 识 是 一 个 名 字 , 可 以 是 谓 词 的 名 字 , 是 类 的 名 字 , 是 变 量 的 名 字 , 是 某 个 东 西 的 名 字<br />

等 等 。 标 识 必 须 符 合 某 些 规 则 。VIP 中 有 四 类 标 识 , 分 别 是 :<br />

• 小 写 标 识<br />

• 大 写 标 识<br />

• 匿 名 标 识<br />

• 省 略 符<br />

小 写 标 识 是 由 小 写 字 母 开 头 的 一 串 字 母 、 数 字 和 下 划 线 组 合 而 成 的 。 大 写 标 识 是 由 大 字<br />

字 母 或 下 划 线 开 头 的 一 串 字 母 、 数 字 和 下 划 线 组 合 而 成 的 。 匿 名 标 识 就 是 一 个 下 划 线 , 它 用<br />

在 谓 词 中 需 要 一 个 参 数 而 我 们 又 不 关 心 它 是 什 么 的 场 合 下 , 比 如 一 个 函 子 包 含 有 名 字 和 年<br />

龄 , 而 我 们 只 想 要 知 道 名 字 时 , 谓 词 调 用 中 我 们 就 可 以 对 年 龄 使 用 匿 名 标 识 。 我 们 在 第 7<br />

章 的 数 据 库 程 序 中 已 经 用 过 匿 名 标 识 了 。 这 本 书 是 对 初 学 者 的 , 省 略 符 就 先 省 略 了 吧 。<br />

Keyword( 关 键 字 )<br />

关 键 字 是 有 特 定 意 义 的 一 些 单 词 , 编 译 器 要 使 用 它 们 。 我 们 只 能 按 VIP 的 制 造 者 规 定 的<br />

方 式 和 意 义 来 使 用 关 键 字 , 不 能 有 别 的 方 法 , 比 如 不 能 用 关 键 字 来 当 变 量 名 。 下 面 是 VIP 的<br />

关 键 字 , 有 些 关 键 字 的 意 义 本 章 中 要 介 绍 :<br />

align and anyflow as bitsize class clauses constants constructors div delegate domains determ digits do<br />

else erroneous externally end facts from failure foreach goal guards implement inherits interface if<br />

language multi mod monitor nondeterm or open predicates procedure quot reference rem resolve<br />

supports single then to<br />

现 在 先 不 用 操 心 这 些 关 键 字 是 什 么 意 思 , 只 需 要 记 住 不 要 用 它 们 当 类 、 谓 词 和 变 量 的 名<br />

字 。 除 了 as 和 language, 所 有 关 键 字 都 是 保 留 字 。 注 意 ,div、mod、rem、quot 也 是 保 留<br />

字 , 但 它 们 不 是 关 键 字 而 是 算 术 运 算 符 , 下 面 还 要 介 绍 。 关 键 字 guards 和 monitor 当 前 在 语<br />

言 中 没 有 使 用 , 但 是 为 以 后 使 用 所 保 留 。<br />

Comment( 注 释 )<br />

注 释 是 程 序 代 码 的 一 部 分 , 但 编 译 器 会 忽 略 它 。 注 释 是 为 人 工 阅 读 程 序 代 码 时 用 的 。VIP<br />

中 注 释 的 写 法 有 以 下 两 种 :<br />

• 由 /* 开 头 , 以 */ 结 尾 , 中 间 可 以 包 含 任 意 字 符 ( 包 括 回 车 、 换 行 等 )。 这 样 的 注 释<br />

可 以 是 多 行 的 , 也 可 以 嵌 套 。<br />

• 百 分 号 %, 后 面 可 以 是 任 何 字 符 序 列 。 百 分 号 开 头 的 注 释 结 束 于 行 尾 , 所 以 也 称 为<br />

单 行 注 释 。<br />

注 释 的 例 子 :<br />

/*This is a comment ...<br />

... that continues on the next line */<br />

person(Thomas, 61,190).<br />

% the rest of this line is a comment<br />

在 VIP 编 辑 器 中 , 注 释 文 本 是 蓝 色 的 。<br />

Declaration( 声 明 )<br />

声 明 是 一 个 情 报 性 的 规 范 化 的 代 码 说 明 , 表 明 要 在 程 序 中 使 用 的 东 西 。 例 如 , 要 使 用 一<br />

个 谓 词 , 首 先 需 要 声 明 它 。 谓 词 square( 平 方 ), 要 用 一 个 整 数 作 为 输 入 参 数 , 并 且 要 返 回<br />

这 个 数 的 平 方 , 是 这 样 的 声 明 的 :<br />

square : (integer Input) ‐> integer Answer procedure (i).<br />

这 样 的 一 个 句 子 就 是 声 明 。 声 明 向 编 译 器 说 明 了 某 个 东 西 ( 比 如 一 个 谓 词 ) 的 一 般 形 式 。<br />

声 明 只 是 在 编 译 时 用 , 不 是 运 行 时 执 行 的 。 本 章 最 后 还 要 再 讨 论 声 明 。<br />

96


Definition( 定 义 )<br />

定 义 就 是 程 序 中 的 代 码 规 定 。 例 如 , 谓 词 square 的 定 义 可 以 是 这 样 的 :<br />

square(Input) = Answer :‐<br />

Answer = Input * Input<br />

这 个 子 句 就 定 义 了 ( 规 定 了 ) 声 明 过 的 谓 词 。<br />

为 了 区 分 一 个 谓 词 的 声 明 和 定 义 , 我 们 在 声 明 前 使 用 单 字 predicates 而 在 定 义 前 使 用 单<br />

字 clauses。 声 明 必 须 在 定 义 前 。 这 样 , 对 谓 词 square 来 说 就 是 :<br />

predicates<br />

square : (integer Input) ‐> integer Answer procedure (i).<br />

clauses<br />

square(Input) = Answer :‐<br />

Answer = Input * Input<br />

运 行 时 执 行 的 是 定 义 。 编 译 时 , 编 译 器 用 声 明 来 检 查 定 义 中 谓 词 的 使 用 , 这 时 编 译 器 可<br />

以 发 现 程 序 中 调 用 square 时 错 用 了 串 当 输 入 这 样 的 错 误 。<br />

标 点 符 号<br />

标 点 符 号 对 编 译 器 既 有 语 法 意 义 又 有 语 义 意 义 , 它 们 中 大 多 数 本 身 并 不 表 示 一 种 能 生 成<br />

值 的 操 作 。 它 们 用 来 分 隔 子 句 , 结 束 声 明 等 。 有 些 标 点 符 号 ( 单 独 的 或 组 合 的 ) 还 可 以 是<br />

VIP 的 操 作 符 。 编 译 器 可 以 识 别 以 下 的 标 点 符 号 :<br />

; ! , . # [ ] | ( ) :‐ : ::<br />

Operator( 操 作 符 )<br />

操 作 符 规 定 了 相 关 操 作 数 的 运 算 。 例 如 :1+2, 数 字 1 和 2 是 操 作 数 ,“+”( 加 ) 是 操 作<br />

符 。 算 术 操 作 符 “+” 规 定 数 字 1 和 2 要 相 加 。VIP 中 有 以 下 操 作 符 :<br />

+ ‐ / * ^ = div mod quot rem < > >< = :=<br />

所 有 操 作 符 都 是 二 元 的 , 这 就 是 说 需 要 两 个 操 作 数 。 但 ‐ 和 + 也 可 以 是 一 元 的 , 对 编 译 器<br />

来 说 ,‐3 就 是 一 个 负 数 。 尽 管 这 些 操 作 符 是 人 们 熟 知 的 , 但 在 <strong>Prolog</strong> 中 的 使 用 和 其 它 语 言<br />

还 是 有 一 点 儿 差 别 的 :<br />

• 操 作 符 “=” 不 是 赋 值 运 算 符 , 而 是 比 较 运 算 符 , 它 的 基 本 作 用 是 比 较 两 个 操 作 数 ,<br />

而 且 使 用 方 式 与 操 作 数 是 否 已 经 绑 定 有 关 。 如 果 两 个 操 作 数 已 经 绑 定 了 , 当 它 们 相<br />

同 时 比 较 就 成 功 , 否 则 就 失 败 。 如 果 一 个 绑 定 了 而 另 一 个 没 有 , 则 未 绑 定 的 那 个 就<br />

会 绑 定 为 已 绑 定 的 那 个 操 作 数 的 值 。 如 果 两 个 操 作 数 都 没 有 绑 定 , 则 会 相 互 绑 定 ,<br />

以 后 一 个 操 作 数 取 得 了 值 时 , 另 一 个 就 会 自 动 地 取 得 相 同 的 值 。<br />

• 操 作 符 div 和 mod 是 保 留 字 。 操 作 符 div 和 quot 都 是 做 整 数 除 法 , 但 两 者 有 些 差<br />

别 , 具 体 差 别 可 以 参 见 帮 助 文 件 。 操 作 符 mod 和 rem 都 会 得 到 整 数 除 后 的 余 数 ,<br />

它 们 的 差 别 也 可 以 在 帮 助 文 件 中 找 到 。<br />

文 字<br />

文 字 就 是 文 字 。 它 们 不 代 表 变 量 、 谓 词 或 其 它 的 什 么 , 就 是 文 字 本 身 。 数 字 12345, 就<br />

是 个 数 , 不 是 别 的 。 文 字 可 以 细 分 为 下 面 几 类 : 整 数 、 字 符 、 浮 点 数 、 串 、 二 进 制 数 和 表 。<br />

整 数 文 字<br />

整 数 文 字 ( 或 简 称 整 数 ) 是 一 个 没 有 小 数 部 分 的 数 , 是 一 个 整 数 值 , 可 以 带 有 正 负 号 。<br />

程 序 中 也 可 以 使 用 八 进 制 的 整 数 , 用 前 辍 0o( 零 和 一 个 小 写 字 母 o) 表 示 。0o12 就 是 十 进<br />

制 数 的 10。 还 可 以 使 用 十 六 进 制 数 , 用 前 辍 0x( 零 和 一 个 小 写 字 母 x) 表 示 ,0x22 就 是 十<br />

进 制 数 34。 有 两 个 标 准 的 域 (domain) 对 整 数 文 字 适 用 , 一 个 是 integer 域 , 另 一 个 是 unsigned<br />

域 。 要 注 意 , 整 数 不 要 超 过 了 该 域 的 最 大 值 和 最 小 值 。 下 一 节 还 要 介 绍 这 些 域 。<br />

实 数 文 字<br />

实 数 文 字 ( 简 称 实 数 ) 是 含 有 小 数 部 分 ( 或 至 少 含 有 小 数 点 ) 或 指 数 部 分 的 数 。 如 果 一<br />

个 数 没 有 小 数 点 , 没 有 小 数 部 分 , 或 没 有 指 数 部 分 ,VIP 就 认 为 它 是 整 数 。 实 数 有 两 种 写 法 :<br />

97


• 带 小 数 点 , 如 543. 、1234.56<br />

• 带 指 数 部 分 , 如 12345.6789E2。 这 里 的 E2 表 示 数 12345.6789 必 须 乘 以 10 的 平 方 ,<br />

所 以 12345.6789E2 其 实 等 于 1234567.89。<br />

实 数 也 可 以 带 有 正 负 号 , 而 且 指 数 部 分 也 可 以 带 有 正 负 号 。 实 数 又 叫 浮 点 数 。 也 要 注 意<br />

不 能 超 出 实 数 的 最 大 值 和 最 小 值 。<br />

字 符<br />

字 符 是 单 引 号 内 的 单 个 字 符 , 如 ’w’。 字 符 可 以 是 任 何 可 打 印 字 符 域 一 个 ESC 序 列 。<br />

串<br />

串 是 双 引 号 中 的 一 个 字 符 序 列 , 如 ”1234$%^&”,”Hello world”。<br />

表<br />

表 是 若 干 元 素 的 清 单 。 元 素 可 以 是 任 意 类 型 的 , 但 同 一 个 表 中 的 元 素 类 型 必 须 相 同 。 表<br />

有 多 种 写 法 , 但 最 重 要 的 有 两 种 :<br />

• 在 一 个 方 括 号 中 列 出 各 元 素 , 各 元 素 用 逗 号 分 隔 。 如 :[1,6,3,2]( 一 个 整 数 表 ) 和<br />

[‘w’,’d’,’a’,’q’,’w’]( 一 个 字 符 表 )<br />

• 写 成 表 的 头 部 ( 表 元 素 的 第 一 个 ) 和 尾 部 ( 表 中 除 第 一 个 元 素 之 外 的 所 有 元 素 ),<br />

头 和 尾 用 竖 线 | 分 隔 开 。 例 如 [3|4,2,1,3], 第 一 个 3 是 头 ,[4,2,1,3] 是 尾 。 第<br />

10 章 有 更 详 细 的 介 绍 。<br />

Section( 段 )<br />

一 个 <strong>Visual</strong> <strong>Prolog</strong> 程 序 由 若 干 文 件 构 成 , 而 每 个 文 件 又 分 为 不 同 的 段 , 各 段 是 以 关 键 字<br />

区 分 的 。 关 键 字 告 诉 编 译 器 这 个 段 是 什 么 内 容 。 前 面 我 们 有 个 谓 词 和 子 句 的 例 子 , 关 键 字<br />

predicates 告 诉 编 译 器 , 这 个 关 键 字 开 始 的 段 是 谓 词 的 声 明 。 另 一 方 面 , 谓 词 的 定 义 可 以 在<br />

以 clauses 关 键 字 开 始 的 段 中 找 到 。 通 常 , 一 个 段 都 是 由 关 键 字 开 头 的 , 没 有 关 键 字 来 表 示<br />

一 个 段 的 结 束 。 一 个 关 键 字 本 身 , 就 表 示 着 当 前 段 的 开 始 和 前 面 一 个 段 的 结 束 。 仅 有 一 个 例<br />

外 , 关 键 字 implement 和 end implement 之 间 所 包 含 的 代 码 是 用 于 一 个 特 定 类 的 代 码 。<br />

Scope( 范 围 )<br />

程 序 分 成 几 个 部 分 , 可 以 认 为 它 们 就 是 构 成 工 程 的 文 件 。 声 明 的 所 有 东 西 , 在 所 声 明 的<br />

文 件 内 是 可 以 随 意 使 用 的 , 但 是 大 多 数 时 间 它 们 不 能 在 程 序 的 那 部 分 之 外 使 用 。 这 就 是 范 围<br />

的 意 思 , 它 表 明 了 比 如 一 个 谓 词 在 哪 儿 是 可 以 用 的 。 超 出 了 这 个 范 围 要 使 用 谓 词 , 就 必 须 打<br />

招 呼 。 比 如 , 超 出 了 一 个 谓 词 的 范 围 使 用 这 个 谓 词 时 , 就 要 用 类 或 对 象 的 名 字 事 前 说 明 在 哪<br />

儿 可 以 找 到 这 个 谓 词 。<br />

9.3 段 的 关 键 字 概 述<br />

这 一 节 里 我 们 简 要 地 介 绍 几 个 VIP 中 使 用 的 段 的 关 键 字 。 这 里 先 是 简 单 地 介 绍 , 下 一 节<br />

中 还 会 有 更 详 细 的 内 容 。 当 然 这 里 介 绍 的 并 不 是 VIP 中 所 有 的 关 键 字 , 但 它 们 现 在 对 我 们 来<br />

说 是 很 重 要 的 几 个 关 键 字 。 不 管 关 键 字 在 这 里 是 怎 么 写 的 ( 有 时 候 WORD 这 样 的 程 序 总 喜<br />

欢 自 作 聪 明 地 用 大 写 ), 程 序 中 关 键 字 必 须 是 小 写 标 识 。<br />

constants( 常 数 )<br />

这 个 关 键 字 用 于 定 义 程 序 中 常 用 的 数 值 段 。 例 如 , 文 字 串 ”<strong>PDC</strong> <strong>Prolog</strong>” 在 程 序 中 很 多 地<br />

方 用 到 , 我 们 就 可 以 定 义 一 个 助 记 符 号 ( 短 点 儿 的 、 好 记 的 ) 来 表 示 这 个 串 :<br />

constants<br />

pdc=” <strong>PDC</strong> <strong>Prolog</strong>”.<br />

注 意 , 常 数 的 定 义 要 用 句 点 结 尾 。 与 <strong>Prolog</strong> 的 变 量 不 同 , 常 数 的 名 字 应 该 是 小 写 标 识 ,<br />

是 一 个 小 写 字 母 开 头 的 词 。<br />

domains( 域 )<br />

这 个 关 键 字 用 来 标 明 声 明 代 码 适 用 域 的 段 。 域 是 描 述 例 如 变 量 值 的 范 围 或 谓 词 的 类 型<br />

的 。 常 见 的 域 有 integer( 整 数 )、real( 实 数 )、string( 串 )。 域 的 声 明 有 很 多 种 , 用 于 适 应<br />

代 码 应 用 的 各 种 可 能 。 在 这 本 入 门 书 中 , 我 们 只 介 绍 一 部 分 。<br />

facts( 事 实 )<br />

98


关 键 字 facts 标 示 一 个 段 的 开 始 , 这 个 段 声 明 后 面 程 序 代 码 中 要 用 到 的 事 实 。 事 实 可 以<br />

只 有 一 个 变 量 , 也 可 以 是 含 有 很 多 值 的 函 子 。 事 实 用 它 的 名 字 来 声 明 , 如 果 是 函 子 , 还 要 包<br />

含 各 个 事 实 使 用 的 参 数 及 参 数 所 属 的 域 。<br />

predicates( 谓 词 )<br />

以 这 个 关 键 字 开 始 的 段 包 含 有 谓 词 的 声 明 , 而 谓 词 本 身 则 要 在 程 序 后 面 的 子 句 段 定 义 。<br />

和 事 实 段 一 样 , 声 明 也 是 用 谓 词 的 名 字 、 它 的 参 数 及 参 数 所 属 的 域 来 做 的 。<br />

clauses( 子 句 )<br />

在 <strong>Visual</strong> <strong>Prolog</strong> 的 所 有 段 中 , 只 有 这 个 段 和 传 统 的 <strong>Prolog</strong> 程 序 相 差 无 几 。 以 这 个 关 键 字<br />

开 始 的 段 中 含 有 前 面 声 明 过 的 谓 词 的 实 际 定 义 , 而 这 里 的 谓 词 要 遵 从 谓 词 段 声 明 的 语 法 。<br />

class facts( 类 事 实 )<br />

事 实 表 示 某 种 属 性 , 而 多 数 事 实 是 有 关 某 个 对 象 的 属 性 。 在 这 种 情 况 下 , 对 不 同 的 对 象 ,<br />

事 实 可 能 会 有 不 同 的 值 。 但 有 时 , 一 个 事 实 是 做 为 整 体 的 类 的 一 个 属 性 。 比 如 , 我 们 要 跟 踪<br />

某 个 类 创 建 的 对 象 数 , 那 这 个 事 实 就 是 属 于 这 个 类 的 事 实 , 而 不 是 哪 一 个 独 立 对 象 的 事 实 。<br />

这 时 , 声 明 的 事 实 就 要 放 在 class facts 段 里 。<br />

class predicates( 类 谓 词 )<br />

类 谓 词 段 是 用 来 声 明 局 部 谓 词 的 , 这 样 的 谓 词 只 能 在 它 定 义 的 范 围 内 调 用 。 局 部 谓 词 用<br />

于 一 个 类 或 对 象 的 内 部 活 动 , 这 种 活 动 其 它 的 对 象 不 关 心 , 比 如 , 在 打 印 一 个 表 之 前 先 对 表<br />

进 行 排 序 。<br />

open<br />

这 个 关 键 字 扩 展 了 类 的 可 见 范 围 。 在 这 个 段 中 , 标 出 其 它 一 些 类 的 名 字 。 当 编 译 器 编 译<br />

某 个 类 遇 到 了 不 在 这 个 类 中 的 谓 词 时 , 就 会 到 这 个 段 标 出 的 类 里 去 查 找 。 这 个 关 键 字 要 紧 随<br />

implement 关 键 字 之 后 使 用 。<br />

implement 和 end implement<br />

这 里 讨 论 的 所 有 关 键 字 中 , 只 有 它 们 是 成 对 出 现 的 。<strong>Visual</strong> <strong>Prolog</strong> 把 它 们 之 间 的 代 码 作<br />

为 一 个 类 的 代 码 处 理 , 而 类 的 名 字 则 必 须 在 implement 之 后 出 现 。end implement 后 面 也 可<br />

以 有 类 的 名 字 , 但 必 须 与 前 面 在 implement 之 后 出 现 的 名 字 一 样 。<br />

介 绍 了 最 常 用 的 关 键 字 之 后 , 下 一 节 我 们 详 细 说 明 它 们 的 意 义 和 使 用 方 法 。<br />

9.4 域 段<br />

我 们 来 用 一 种 通 俗 的 方 法 说 说 “ 域 ” 这 个 段 。<strong>Prolog</strong> 中 的 每 个 变 量 都 有 特 定 的 类 型 , 这<br />

个 类 型 表 示 了 变 量 可 以 接 受 的 值 。 如 , 一 个 整 数 变 量 , 它 就 只 能 有 整 数 值 ,123,-456, 都<br />

行 。<strong>Prolog</strong> 中 有 几 个 标 准 类 型 , 前 面 已 经 介 绍 过 一 些 , 如 unsigned、integer、real、string。<br />

如 果 在 程 序 中 只 使 用 这 些 标 准 类 型 , 也 可 以 不 要 域 这 个 段 。<br />

用 标 准 域 可 以 创 建 用 户 自 己 的 类 型 。 例 如 , 前 面 章 节 中 我 们 使 用 过 一 个 人 的 身 高 和 体 重<br />

的 函 子 , 身 高 和 体 重 都 不 能 是 负 值 , 我 们 想 把 这 一 点 告 诉 编 译 器 。 可 以 创 建 一 个 正 数 的 域 来<br />

这 样 做 , 假 设 身 高 不 能 超 过 300( 相 当 高 了 ) 体 重 不 能 超 过 250( 也 很 重 了 )。 可 以 声 明 三 个<br />

域 , 一 个 身 高 , 一 个 体 重 , 一 个 名 字 , 像 这 样 :<br />

domains<br />

length = integer [0..300].<br />

weight = integer [0..250].<br />

name = string.<br />

声 明 表 示 身 高 是 一 个 0 到 300 之 间 的 整 数 值 。 对 新 的 域 name, 我 们 只 是 说 它 是 个 串 。<br />

现 在 来 看 person 函 子 , 函 子 的 名 字 是 person, 有 三 个 参 数 , 名 字 、 身 高 和 体 重 。 在 第 7 章<br />

里 我 们 是 这 样 声 明 函 子 person 的 事 实 的 :<br />

facts<br />

person : (string, integer, integer)<br />

现 在 , 我 们 手 上 有 新 的 域 了 , 所 以 可 以 更 易 理 解 地 声 明 为 :<br />

facts<br />

person : (name, length, weight)<br />

99


域 的 使 用 达 到 了 两 个 目 的 : 程 序 的 代 码 更 好 理 解 , 而 编 译 器 可 以 做 更 准 确 的 核 查 工 作 。<br />

前 面 的 一 个 声 明 中 , 第 二 个 和 第 三 个 参 数 类 型 都 是 integer, 容 易 搞 混 。 而 后 面 的 声 明 中 ,<br />

编 译 器 可 以 知 道 第 二 个 参 数 类 型 是 length, 它 肯 定 与 height 类 型 是 有 差 别 的 。<br />

声 明 一 个 域 时 , 还 可 以 用 一 个 域 名 去 创 建 一 个 新 的 域 。 比 如 , 可 以 用 前 面 声 明 的 域 weight<br />

声 明 一 个 新 域 lightWeight, 来 表 示 体 重 比 较 轻 的 人 :<br />

domains<br />

lightWeight = weight [0..50].<br />

现 在 我 们 来 看 看 它 的 正 规 语 法 。 前 面 我 们 看 到 了 一 个 变 量 值 的 范 围 的 域 , 这 里 还 会 看 到<br />

更 多 种 类 的 域 。 域 段 的 开 始 有 单 词<br />

domains<br />

它 后 面 就 是 一 个 或 多 个 域 的 声 明 。 一 个 域 的 声 明 同 时 也 是 这 个 域 的 定 义 。 域 的 定 义 规 定 了 域<br />

是 在 当 前 范 围 的 , 也 就 是 在 当 前 这 个 文 件 ( 类 , 模 块 )、 定 义 该 域 的 文 件 内 的 。 不 能 超 出 当<br />

前 范 围 使 用 域 , 除 非 直 接 说 明 这 个 域 是 在 哪 儿 声 明 和 定 义 的 。 域 声 明 最 简 单 的 语 法 形 式 是 :<br />

domains<br />

= .<br />

是 新 域 的 名 字 , 随 便 起 个 好 记 的 就 行 , 但 必 须 是 小 写 标 识 。 而<br />

则 是 标 准 类 型 的 名 字 , 或 是 前 面 声 明 定 义 过 的 域 的 名 字 。 通 常 把<br />

称 为 子 域 而 把 称 为 父 域 。 不 过 要 小 心 , 也 有 例 外 的 。<br />

11<br />

在 VIP 中 有 几 个 预 定 义 的 基 本 类 型 , 它 们 是 integer、unsigned、real、character 和 string。<br />

这 些 基 本 的 类 型 可 以 用 来 创 建 程 序 中 要 用 的 任 意 类 型 , 要 用 的 类 型 在 域 段 中 声 明 , 并 由 一 个<br />

或 几 个 来 定 义 。 一 个 就 是 一 个 类 型 的 表 达 式 , 它 可 以 是 :<br />

• 整 数 域<br />

• 实 数 域<br />

• 类 型 名<br />

• 复 合 域<br />

• 表 域<br />

• 谓 词 域<br />

• 类 型 变 量<br />

• 类 型 应 用<br />

我 们 这 里 不 讨 论 后 面 三 种 。<br />

整 数 域 (integral domains)<br />

整 数 域 用 于 表 示 整 数 的 范 围 , 有 两 种 预 定 义 的 整 数 域 :integer 和 unsigned, 它 们 分 别 表<br />

示 有 符 号 和 无 符 号 的 数 。 要 声 明 一 个 新 的 整 数 域 , 应 该 这 样 做 :<br />

domains<br />

myDomain = integer.<br />

在 integer 之 后 , 还 可 以 带 两 个 参 数 , 和 。 这 样 , 语<br />

法 就 变 成 了 :<br />

domains<br />

= .<br />

表 示 这 个 值 的 二 进 制 位 数 , 用 bitsize 后 跟 一 个 整 数 说 明 。<br />

表 示 值 的 范 围 , 用 写 在 方 括 号 中 的 最 小 值 和 最 大 值 ( 都 是 整 数 , 两 个 值 用<br />

两 个 点 分 隔 ) 说 明 。 一 个 叫 newDomain 的 、 值 在 0 到 10 之 间 、 在 内 存 中 占 8 位 的 整 数 域 是<br />

这 样 声 明 和 定 义 的 :<br />

domains<br />

newDomain = integer bitsize 8 [0..10].<br />

如 果 省 略 了 , 编 译 器 会 认 为 它 与 父 域 是 一 样 的 ; 如 果 没 有 父 域 , 就 与<br />

11<br />

还 有 类 型 符 号 , 这 里 不 说 了 。<br />

100


处 理 器 的 位 数 一 致 。 省 略 了 时 , 就 用 其 父 域 的 范 围 ; 没 有 父 域 时 , 这 个 范<br />

围 就 依 赖 于 。 例 如 , 占 8 位 的 一 个 无 符 号 数 就 不 能 大 于 255( 十 进 制 )。 当<br />

然 , 也 不 能 让 最 小 值 比 最 大 值 还 大 , 最 小 值 和 最 大 值 也 要 满 足 的 要 求 。<br />

对 和 , 还 可 以 使 用 算 术 表 达 式 , 但 表 达 式 在 编 译 时 应<br />

该 能 得 出 一 个 数 值 的 结 果 。 如 :<br />

constants<br />

three = 3.<br />

domains<br />

subint = integer [0..three].<br />

实 数 域 (real domains)<br />

实 数 域 用 于 表 示 带 小 数 的 数 , 它 可 以 表 示 很 大 的 值 和 很 小 的 值 。 预 定 义 的 real 域 具 有 与<br />

处 理 器 架 构 一 致 的 精 度 ( 或 是 编 译 器 给 定 的 精 度 )。<br />

和 整 数 相 似 , 实 数 域 的 声 明 是 :<br />

domains<br />

= real .<br />

在 real 之 后 , 还 可 以 对 新 的 域 指 定 两 个 属 性 : 一 个 是 , 另 一 个<br />

是 。 前 者 是 域 的 精 度 , 也 就 是 小 数 点 后 的 数 字 位 数 , 用 关 键 字 digits<br />

后 跟 一 个 整 数 表 示 。 如 果 省 略 了 , 就 与 父 域 的 一 样 ; 如 果 没 有 父 域 , 就 是 处 理 器 架 构 的 自 然<br />

精 度 或 是 编 译 器 给 定 的 精 度 (<strong>Visual</strong> <strong>Prolog</strong> v.6 编 译 器 的 精 度 是 15 位 )。 编 译 器 限 定 了 精 度 的<br />

范 围 , 如 果 规 定 的 超 过 了 这 个 范 围 , 就 只 能 得 到 处 理 器 ( 编 译 器 ) 规 定 的 精 度 。<br />

和 整 数 范 围 值 一 样 : 方 括 号 中 两 个 数 用 两 个 点 分 开 。 最 小 值 和 最<br />

大 值 这 两 个 数 是 实 数 , 也 可 以 用 表 达 式 表 示 它 们 , 但 编 译 时 应 能 得 出 浮 点 数 值 结 果 , 也 就 是<br />

说 在 编 译 时 编 译 器 要 能 计 算 出 这 个 实 数 域 的 精 度 和 范 围 来 。 表 明 了 实<br />

数 域 的 最 小 值 和 最 大 值 。 如 果 省 略 了 , 就 与 其 父 域 的 一 样 , 如 果 没 有 父 域 , 就 会 使 用 声 明 的<br />

精 度 能 表 示 的 最 大 范 围 。<br />

类 型 名 (type names)<br />

类 型 名 是 一 个 在 当 前 范 围 或 其 它 什 么 地 方 已 经 声 明 过 的 。 当 已 经 定 义 了<br />

一 个 时 , 就 可 以 在 其 它 的 声 明 中 使 用 这 个 名 字 , 这 就 是 类 型 名 的 使 用 。<br />

声 明 使 用 类 型 名 是 很 直 白 的 , 如 :<br />

domains<br />

= <br />

它 的 后 面 一 样 可 以 有 对 占 用 比 特 位 数 、 精 度 及 范 围 的 参 数 选 项 。 如 果 使 用 了 来 自 另 一 个<br />

范 围 ( 比 如 另 一 个 类 ) 的 取 值 范 围 时 , 必 须 在 前 写 出 该 类 名 并 用<br />

双 冒 号 隔 开 , 如 :<br />

domains<br />

myNewDomain = anotherClass::myOld Domain.<br />

上 例 中 ,myNewDomain 是 myOld Domain 的 一 个 子 类 型 , 必 须 小 心 不 要 让 前 者 的 属 性 ( 比<br />

特 位 、 范 围 、 精 度 ) 与 后 者 的 发 生 冲 突 , 例 如 , 前 者 的 范 围 不 能 超 出 后 者 的 范 围 。 比 特 位 和<br />

精 度 也 一 样 。<br />

复 合 域 (compound domains)<br />

前 面 说 过 的 都 是 所 谓 的 简 单 域 , 还 有 所 谓 的 复 合 域 ( 也 叫 代 数 数 据 类 型 )。 复 合 域 用 于<br />

表 示 表 、 树 及 其 它 树 状 结 构 的 值 。 复 合 域 的 简 单 形 式 是 前 面 已 经 遇 到 过 的 函 子 , 还 有 枚 举 值 。<br />

例 如 :<br />

domains<br />

person = person(string, integer, integer).<br />

尽 管 上 面 的 例 子 写 法 是 正 确 的 , 但 不 够 清 晰 , 我 们 可 以 改 写 成 这 样 :<br />

domains<br />

101


person = person(string Name, integer Length, integer Weight).<br />

编 译 器 会 忽 略 变 量 Name、Length 和 Weight, 但 程 序 变 得 好 懂 了 。<br />

复 合 域 也 可 以 更 复 杂 。 假 设 我 们 要 做 一 个 数 据 库 , 存 放 人 及 其 所 属 物 品 , 比 如 一 个 人 可<br />

以 有 书 ( 带 书 名 和 作 者 ) 或 是 吉 它 ( 带 品 牌 名 )。 我 们 可 以 声 明 :<br />

domains<br />

personName = string.<br />

author = string.<br />

title = string.<br />

brandName = string.<br />

possession = book(author, title); guitar(brandName). % 注 意 分 号 , 是 “ 或 ”<br />

class facts<br />

person : (personName, possession).<br />

下 一 节 说 facts。 这 个 声 明 说 函 子 person 由 两 个 参 数 构 成 , 一 个 是 人 的 名 字 (personName)<br />

而 另 一 个 是 所 有 物 (possession), 人 名 是 串 域 的 , 所 有 物 则 属 于 book 域 或 guitar 域 。 分 号<br />

读 作 逻 辑 或 。 这 样 , 在 程 序 中 就 可 以 按 以 下 方 式 初 始 化 事 实 :<br />

person("John", book( "Eduardo Costa","<strong>Prolog</strong> for Tyros")).<br />

person("John", guitar("Fender")).<br />

然 后 , 我 们 可 以 用 这 样 的 句 子 :<br />

...<br />

person(Name, Possession),<br />

writePossession(Possesion),<br />

...<br />

而 writePossession 的 子 句 可 以 这 样 :<br />

writePossession(book(X,Y)) :‐<br />

write(X,Y).<br />

writePossession(guitar(X)) :‐<br />

write(X).<br />

复 合 域 和 其 它 域 没 有 子 类 关 系 。 如 果 一 个 域 定 义 得 与 某 个 复 合 域 一 样 , 那 它 们 就 是 同 类<br />

型 的 , 而 不 是 子 类 , 也 就 是 说 它 们 是 同 类 型 而 不 同 名 。<br />

复 合 域 可 以 递 归 定 义 , 可 以 是 互 递 归 和 间 接 递 归 的 。<br />

例 一 :<br />

domains<br />

t1 = ff(); gg(integer, t1).<br />

t1 是 一 个 有 两 种 可 能 的 复 合 域 , 第 一 种 可 能 是 空 函 子 ff, 第 二 种 是 二 元 函 子 gg, 它 的<br />

一 个 元 是 整 数 而 另 一 个 元 是 t1 本 身 。 所 以 , 域 t1 是 递 归 定 义 的 , 下 面 的 表 达 式 都 属 于 域 t1:<br />

ff()<br />

gg(77, ff())<br />

gg(33, gg(44, gg(55, ff())))<br />

例 二 :<br />

domains<br />

t1 = ff(); gg(t2).<br />

t2 = hh(t1, t1).<br />

t1 是 一 个 有 两 种 可 能 的 复 合 域 。 第 一 种 可 能 是 空 函 子 ff, 第 二 种 是 一 元 函 子 gg, 而 gg<br />

的 元 域 是 属 于 t2 的 。t2 是 一 个 函 子 hh 的 复 合 域 , 而 hh 的 两 个 元 域 都 是 属 于 t1 的 。 这 样 一<br />

来 ,t1 和 t2 是 互 递 归 的 。 下 面 这 些 项 都 是 属 于 t1 域 的 :<br />

ff()<br />

gg(hh(ff(), ff()))<br />

gg(hh(gg(hh(ff(), ff())), ff()))<br />

ggg(hh(ff(), g(hh(ff(), ff()))))<br />

102


gg(hh(gg(hh(ff(), ff())), gg(hh(ff(), ff()))))<br />

在 第 11 章 中 我 们 还 会 看 到 更 多 的 复 合 域 , 比 如 树 。<br />

表 域 (list domain)<br />

表 域 表 示 某 个 域 中 的 一 组 值 。 通 常 可 以 认 为 表 是 一 个 在 方 括 号 中 用 逗 号 分 开 的 一 组 元<br />

素 , 这 些 元 素 必 须 是 同 类 型 的 。 例 如 :[1,2,3,2,1] 是 一 个 整 数 表 。 表 的 声 明 是 用 元 素 的 域 名<br />

后 跟 一 个 星 号 (*) 表 示 的 :<br />

domains<br />

realList = real*.<br />

9.5 常 数 段<br />

常 数 是 一 个 在 程 序 中 始 终 取 相 同 值 的 变 量 。 它 的 用 途 之 一 是 使 程 序 好 理 解 。 在 代 码 中 使<br />

用 变 量 VATpercentage 比 使 用 值 0.20 更 有 意 义 。 而 且 , 当 使 用 一 个 常 数 而 它 的 值 变 化 时 ( 税<br />

率 确 实 是 变 化 的 ), 只 需 要 改 变 常 数 的 声 明 就 行 了 , 这 可 以 减 少 差 错 。<br />

常 数 段 由 关 键 字 constants 开 头 , 可 以 对 当 前 范 围 声 明 一 个 或 多 个 常 数 。 每 个 常 数 声 明<br />

占 一 行 , 由 常 数 名 开 始 , 然 后 是 一 个 冒 号 , 接 着 是 常 数 的 类 型 , 再 一 个 “=” 和 这 个 常 数 的<br />

值 , 行 尾 要 用 句 点 结 束 。 通 常 的 常 数 声 明 是 这 样 的 :<br />

constants<br />

: = .<br />

常 数 名 是 小 写 标 识 。 常 数 值 要 与 常 数 类 型 相 匹 配 , 如 果 用 表 达 式 来 表 示 值 , 在 编 译 时 应<br />

该 可 以 计 算 得 到 值 。 例 :<br />

constants<br />

vat : real = 0.19.<br />

constVIP : string = "<strong>Visual</strong> <strong>Prolog</strong>".<br />

如 果 值 的 域 是 标 准 域 之 一 , 那 么 域 名 和 后 面 的 冒 号 就 可 以 省 略 , 得 到 下 面 的 简 化 方 式 :<br />

= <br />

如 :<br />

vat = 0.19.<br />

这 样 定 义 的 常 数 可 以 用 于 所 有 能 够 使 用 文 字 符 号 的 场 合 中 。<br />

如 果 常 数 类 型 省 略 了 , 那 么 常 数 域 一 定 要 能 清 楚 地 由 常 数 值 来 确 定 。 要 注 意 VIP 对 整 数<br />

和 实 数 的 区 别 ,123 是 一 个 整 数 , 而 123. 则 是 一 个 实 数 。<br />

9.6 事 实 段<br />

事 实 描 述 对 象 或 类 的 一 个 或 多 个 属 性 的 值 。<strong>Prolog</strong> 中 的 事 实 是 存 放 在 数 据 库 中 的 , 每 个<br />

事 实 有 它 自 己 的 数 据 库 。 事 实 有 两 类 , 当 事 实 表 示 单 个 属 性 的 值 时 , 被 称 为 事 实 变 量 ; 当 事<br />

实 表 示 多 个 属 性 的 值 时 , 称 为 事 实 函 子 。<br />

事 实 段 用 来 声 明 放 在 一 个 事 实 数 据 库 中 的 事 实 , 事 实 数 据 库 和 事 实 属 于 当 前 范 围 。 前 面<br />

已 经 说 过 , 事 实 数 据 库 既 可 以 存 在 于 类 的 级 别 上 也 可 以 存 在 于 对 象 的 级 别 上 。 如 果 是 对 象 级<br />

别 的 事 实 , 对 不 同 的 对 象 事 实 是 不 相 同 的 , 需 要 调 用 对 象 谓 词 来 添 加 、 更 改 或 删 除 事 实 。 如<br />

果 是 类 级 别 的 事 实 , 则 需 要 调 用 类 谓 词 来 添 加 、 更 改 或 删 除 事 实 。 对 后 一 种 情 况 , 事 实 必 须<br />

声 明 为 类 事 实 , 在 这 一 节 的 最 后 要 介 绍 。<br />

事 实 只 能 在 类 的 实 现 里 声 明 , 也 就 是 在 文 件 .pro 中 声 明 。 事 实 数 据 库 可 以<br />

给 一 个 名 字 , 如 果 命 名 了 事 实 数 据 库 , 也 就 隐 含 地 定 义 了 一 个 附 带 的 复 合 域 , 这 个 域 名 与 事<br />

实 数 据 库 同 名 , 并 拥 有 事 实 段 中 相 应 事 实 的 函 子 。<br />

事 实 段 由 facts 关 键 字 开 头 , 后 面 是 一 个 或 多 个 事 实 声 明 。 如 果 想 要 给 事 实 数 据 库 起 一<br />

个 名 字 , 就 应 该 是 这 样 :<br />

facts ‐ 数 据 库 名<br />

数 据 库 名 是 小 写 字 母 开 头 的 , 关 键 字 facts 和 数 据 库 名 之 间 用 “‐” 分 开 。 这 样 的 事 实 数<br />

103


据 库 被 称 为 已 命 名 的 事 实 数 据 库 。<br />

关 键 字 facts 下 面 的 每 个 声 明 都 要 用 句 点 结 尾 。<br />

事 实 变 量 的 声 明 和 事 实 函 子 的 稍 有 不 同 。 最 简 单 的 事 实 声 明 是 事 实 变 量 的 声 明 , 它 声 明<br />

一 个 事 实 的 名 字 、 类 型 和 初 始 值 :<br />

: := <br />

事 实 变 量 名 是 小 写 标 识 , 事 实 类 型 可 以 是 任 意 声 明 过 的 域 , 而 初 始 值 当 然 必 须 符 合 声 明<br />

过 的 域 , 如 果 是 用 表 达 式 的 话 , 它 一 定 要 在 编 译 时 能 算 得 确 定 的 值 。 初 始 值 可 以 省 略 , 但 事<br />

实 变 量 在 构 造 器 中 必 须 被 初 始 化 , 也 就 是 说 , 在 对 象 被 创 建 的 时 候 , 对 象 所 属 的 事 实 必 须 要<br />

被 初 始 化 , 事 实 变 量 不 能 没 有 一 个 值 而 存 在 。<br />

改 变 事 实 的 值 , 一 定 要 使 用 赋 值 符 号 “:=”, 当 声 明 了 事 实 变 量 :<br />

facts<br />

myFact : integer := 0<br />

以 后 ,myFact 的 初 值 是 零 , 要 改 变 这 个 值 , 必 须 用 下 面 这 样 的 句 子 :<br />

...,<br />

myFact := 3,<br />

...<br />

事 实 函 子 的 声 明 包 括 函 子 的 名 字 、 紧 随 其 后 的 一 个 冒 号 、 括 号 中 的 函 子 参 数 和 一 个 函 子<br />

模 式 的 选 项 。 一 般 形 式 如 下 :<br />

: () <br />

函 子 名 是 小 写 标 识 。 参 数 表 包 括 每 个 参 数 的 类 型 及 名 字 ( 可 选 项 ), 各 个 类 型 用 逗 号 分<br />

隔 。 如 果 要 加 变 量 名 , 变 量 名 和 前 面 的 类 型 之 间 不 能 有 逗 号 。 例 如 :<br />

facts<br />

person : (string, integer)<br />

定 义 了 带 有 两 个 参 数 的 一 个 函 子 。 下 面 的 定 义 与 上 面 的 是 一 样 的 :<br />

person : (string Name, integer Length)<br />

编 译 器 编 译 时 会 忽 略 变 量 的 名 字 。<br />

函 子 模 式 是 一 个 可 选 项 , 它 只 能 用 于 事 实 函 子 , 表 示 可 以 存 放 在 数 据 库 中 的 事 实 数 。 显<br />

然 了 , 事 实 变 量 只 会 有 一 个 值 。 模 式 可 以 是 determ、nondeterm 和 single 三 种 。<br />

• single 事 实 有 一 个 也 只 能 有 一 个 值 , 若 要 在 数 据 库 中 插 入 一 个 新 值 , 旧 的 值 就 被<br />

替 换 掉 了 。 这 个 模 式 的 事 实 不 能 被 取 消 , 调 用 它 时 总 能 成 功 。<br />

• determ 事 实 可 以 有 0 个 或 1 个 值 , 如 果 事 实 有 0 个 值 , 对 事 实 的 任 何 读 访 问 都 会<br />

失 败 。 调 用 这 样 的 事 实 可 能 失 败 ( 没 有 事 实 时 ), 也 可 能 成 功 。<br />

• undeterm 事 实 可 以 有 0 个 、1 个 或 任 意 个 值 。 调 用 这 样 的 事 实 可 能 失 败 , 可 能 成<br />

功 , 并 且 可 以 形 成 给 出 更 多 解 的 回 溯 点 。<br />

• 缺 省 时 的 模 式 是 undeterm。<br />

事 实 函 子 是 由 子 句 段 初 始 化 的 。 声 明 了 函 子 :<br />

facts<br />

person : (string Name, integer Length)<br />

以 后 , 可 以 在 子 句 段 ( 下 面 要 介 绍 ) 添 加 这 样 的 子 句 :<br />

person("John", 185).<br />

开 始 时 , 数 据 库 中 就 是 由 这 些 事 实 填 充 的 。 同 样 要 小 心 , 使 用 表 达 式 时 应 能 让 编 译 器 在<br />

编 译 时 得 出 值 。<br />

由 facts 关 键 字 开 始 的 事 实 声 明 , 定 义 的 是 对 象 事 实 。 类 事 实 的 段 是 由 class facts 关 键 字<br />

开 始 的 , 它 的 语 法 与 facts 的 完 全 一 样 。 所 以 , 类 事 实 数 据 库 也 有 事 实 变 量 和 事 实 函 子 , 等<br />

等 。<br />

事 实 只 能 在 类 实 现 (.pro 文 件 ) 中 声 明 , 也 只 能 在 这 个 实 现 文 件 中 引 用 ,<br />

所 以 事 实 的 范 围 是 在 其 声 明 文 件 中 的 实 现 。 但 对 象 事 实 的 生 命 期 是 它 所 归 属 的 对 象 的 生 命<br />

期 , 而 类 事 实 的 生 命 期 是 从 程 序 开 始 到 程 序 结 束 的 整 个 期 间 。<br />

104


9.7 谓 词 段<br />

谓 词 段 在 当 前 范 围 中 声 明 一 组 对 象 或 类 的 谓 词 。 段 的 起 始 要 使 用 关 键 字 predicates, 后<br />

面 是 一 个 或 多 个 谓 词 声 明 。 谓 词 声 明 最 简 单 的 形 式 是 一 个 谓 词 名 加 一 个 冒 号 加 上 括 号 中 的 参<br />

数 表 , 由 句 点 结 束 , 一 般 形 式 如 下 :<br />

: () .<br />

谓 词 名 是 小 写 标 识 。 参 数 表 说 明 了 参 数 的 类 型 , 可 以 是 标 准 类 型 或 已 声 明 过 的 域 , 各 用<br />

逗 号 分 开 。 参 数 后 可 以 跟 变 量 名 , 这 时 参 数 类 型 和 变 量 名 间 不 用 逗 号 。 如 :<br />

myPredicate : (integer, real, string).<br />

用 变 量 名 时 就 成 了 :<br />

myPredicate : (integer FirstNumber, real SecondNumber, string TheString).<br />

编 译 器 会 忽 略 变 量 名 。<br />

声 明 中 有 几 个 选 项 ( 有 时 是 必 须 要 有 的 ):<br />

• 如 果 谓 词 是 一 个 函 数 , 用 于 返 回 值 的 变 量 必 须 要 声 明 。<br />

• 可 以 声 明 谓 词 的 模 式 。<br />

• 可 以 声 明 谓 词 的 流 模 式 。<br />

如 果 省 略 了 模 式 和 流 模 式 , 编 译 器 会 为 它 们 设 定 缺 省 值 。<br />

加 了 选 项 后 的 声 明 其 一 般 形 式 是 :<br />

: () ‐> <br />

各 选 项 的 意 义 如 下 :<br />

‐> <br />

返 回 参 数 选 项 前 用 符 号 “‐>” 标 识 , 它 含 有 返 回 值 的 类 型 ( 值 是 哪 个 域 的 ) 及 变 量 名 。<br />

声 明 中 有 这 个 选 项 时 , 该 谓 词 就 称 为 一 个 函 数 。 一 定 要 小 心 让 子 句 返 回 正 确 的 值 。 我 们 再 细<br />

说 一 下 函 数 。<br />

调 用 一 个 谓 词 时 , 有 些 参 数 可 以 用 作 输 出 参 数 。 依 靠 返 回 功 能 , 我 们 可 以 重 新 取 得 数 据 ,<br />

这 样 我 们 就 可 以 用 谓 词 来 提 供 我 们 所 需 要 的 数 据 。 调 用 谓 词 恢 复 数 据 的 另 一 个 办 法 是 使 用 函<br />

数 , 它 是 一 种 特 殊 的 谓 词 , 调 用 时 返 回 一 个 值 而 不 需 要 指 明 输 出 参 数 。 举 个 例 子 , 来 看 一 下<br />

谓 词 squarePred/2, 计 算 一 个 整 数 的 平 方 , 这 个 谓 词 的 声 明 是 :<br />

predicates<br />

squarePred : (integer InputVar, integer OutputSquare) procedure (i,o).<br />

12<br />

它 的 子 句 是 :<br />

clauses<br />

squarePred(InputNumber, OutputSquare) :‐<br />

OutputSquare = InputNumber * InputNumber.<br />

调 用 这 个 谓 词 时 ,InputNumber 是 要 计 算 平 方 的 数 , 结 果 绑 定 于 OutputSquare。 如 果 按<br />

下 面 的 情 况 调 用 :<br />

squarePred(2, Square),<br />

那 么 Square 就 会 绑 定 为 4。 我 们 换 个 情 况 , 来 看 一 下 这 样 的 谓 词 :<br />

predicates<br />

squareFunc : (integer InputVar) ‐> integer OutputSquare procedure (i).<br />

clauses<br />

SquareFunc(InputNumber) = OutputSquare :‐<br />

OutputSquare = InputNumber * InputNumber.<br />

这 个 谓 词 中 InputNumber 是 要 计 算 平 方 的 数 , 结 果 绑 定 给 OutputSquare。 它 会 把<br />

OutoutSquare 的 值 返 回 给 调 用 程 序 , 调 用 时 必 须 提 供 一 个 变 量 来 接 收 这 个 返 回 值 , 要 这 样 :<br />

...,<br />

Square = squareFunc(3),<br />

...<br />

12<br />

子 句 在 下 一 节 讨 论 。<br />

105


于 是 ,Square 就 会 绑 定 为 函 数 squareFunc/1 所 返 回 的 值 。 声 明 中 的<br />

‐> integer OutputSquare<br />

部 分 表 示 这 个 谓 词 是 一 个 函 数 , 它 会 在 结 束 时 返 回 绑 定 变 量 OutputSquare 的 值 。<br />

<br />

这 个 选 项 表 示 谓 词 的 行 为 方 式 , 它 告 诉 编 译 器 该 谓 词 是 否 可 以 失 败 、 成 功 或 回 溯 ( 或 三<br />

项 中 的 两 项 或 全 部 )。 这 样 , 就 有 六 种 可 能 :<br />

• fail 这 个 模 式 表 示 谓 词 总 是 失 败 。 有 一 个 标 准 谓 词 就 是 这 个 模 式 ,fail/0。<br />

• procedure 这 个 模 式 表 示 谓 词 总 是 成 功 , 并 且 只 以 一 种 解 成 功 , 不 会 有 别 的 可 能 ,<br />

不 会 发 生 回 溯 。<br />

• determ 它 表 示 谓 词 可 以 失 败 也 可 以 成 功 , 但 不 会 有 回 溯 点 。 这 意 味 着 <strong>Prolog</strong> 回<br />

溯 时 会 跳 过 这 个 谓 词 , 因 为 它 不 会 有 回 溯 点 。 成 功 时 , 它 只 有 一 个 解 。<br />

• multi 这 个 模 式 表 示 该 谓 词 永 远 不 会 失 败 , 它 总 会 成 功 并 能 回 溯 , 所 以 调 用 这 样<br />

的 谓 词 会 产 生 多 种 解 。<br />

• nondeterm 表 示 可 以 失 败 , 可 以 成 功 , 也 可 以 回 溯 。<br />

• erroneous 这 个 模 式 比 较 特 殊 。 它 不 是 失 败 也 不 是 成 功 , 但 它 引 起 所 谓 “ 异 常 ”。<br />

异 常 , 就 是 程 序 背 离 了 正 常 执 行 的 路 线 , 比 如 : 找 不 到 文 件 、 发 生 了 内 存 溢 出 , 等<br />

等 。 出 现 了 异 常 , 就 要 执 行 特 定 的 异 常 处 理 谓 词 。 异 常 处 理 程 序 以 某 种 方 式 分 析 所<br />

发 生 异 常 的 类 型 , 采 取 一 定 程 度 的 补 救 措 施 , 比 如 , 让 用 户 输 入 正 确 的 文 件 名 来 重<br />

新 调 用 谓 词 , 或 是 简 单 地 显 示 异 常 的 消 息 。 这 里 我 们 不 再 讨 论 它 。<br />

这 个 选 项 缺 省 是 procedure, 这 还 是 比 较 合 理 的 。 表 9.1 是 一 个 模 式 概 览 。<br />

谓 词 模 式 成 功 或 失 败 解 回 溯 点<br />

fail 总 是 失 败 无 无<br />

procedure 总 是 成 功 一 个 无<br />

determ 两 者 皆 有 可 能 无 或 一 个 无<br />

multi 总 是 成 功 一 个 或 多 个 有<br />

nondeterm 两 者 皆 有 可 能 无 , 一 个 或 多 个 有<br />

表 9.1 谓 词 模 式<br />

谓 词 模 式 告 诉 了 推 理 机 我 们 想 要 如 何 使 用 这 个 谓 词 。<br />

按 顺 序 ,procedure、determ、multi 和 nondeterm 模 式 一 个 比 一 个 强 地 限 制 了 选 择 , 也<br />

就 是 对 更 多 选 择 一 个 比 一 个 弱 。 比 如 procedure 的 限 制 比 determ 强 , 因 为 后 者 不 光 可 以 成<br />

功 , 还 可 以 失 败 。 声 明 谓 词 时 一 定 要 考 虑 它 们 之 间 的 兼 容 性 。 例 如 , 声 明 了 一 个 procedure<br />

的 谓 词 , 而 在 它 的 子 句 中 调 用 了 另 一 个 是 nondeterm 的 谓 词 , 这 其 实 就 改 变 了 后 者 的 模 式 。<br />

不 过 还 好 , 我 们 可 以 用 截 断 “!” 和 fail 来 改 变 谓 词 的 行 为 。 举 个 例 子 , 看 下 面 的 子 句 :<br />

child(Name) :‐<br />

father(Name, Father), write("Father is: ", Father).<br />

child(Name) :‐<br />

mother(Name, Mother), write("Mother is: ", Mother).<br />

这 个 谓 词 表 示 , 名 为 Name 的 某 人 有 父 亲 或 母 亲 时 是 个 孩 子 。 假 设 我 们 有 个 数 据 库 , 它<br />

里 面 有 函 子 father 和 mother 的 一 些 事 实 , 用 某 个 名 字 调 用 这 个 谓 词 , 比 如 :<br />

child("John").<br />

如 果 找 不 到 名 为 John 的 事 实 , 显 然 这 个 调 用 就 会 失 败 。 但 如 果 它 找 到 了 一 个 事 实 , 比<br />

如 father(“John”,Father), 它 就 成 功 并 返 回 第 一 个 解 。 不 过 它 还 会 回 溯 , 因 为 数 据 库 中 还 有 另<br />

一 个 子 句 可 以 试 。 所 以 这 个 谓 词 是 nondeterm 的 , 它 可 以 以 失 败 、 成 功 或 回 溯 三 个 方 式 之 一<br />

再 回 来 。 现 在 , 我 们 在 第 一 个 子 句 上 加 一 个 截 断 :<br />

child(Name) :‐<br />

father(Name, Father), !, write("Father is: ", Father).<br />

child(Name) :‐<br />

106


mother(Name, Mother), write("Mother is: ", Mother).<br />

第 一 个 子 句 成 功 时 , 这 个 截 断 就 会 阻 止 回 溯 。 这 样 , 谓 词 就 只 能 由 失 败 或 成 功 再 回 头 了 ,<br />

模 式 变 成 了 determ 的 。 更 进 一 步 , 还 可 以 把 谓 词 变 成 procedure 的 , 看 下 面 :<br />

child(Name) :‐<br />

father(Name, Father), !, write("Father is: ", Father).<br />

child(Name) :‐<br />

mother(Name, Mother), !, write("Mother is: ", Mother).<br />

child(_) :‐<br />

write("No known parents").<br />

最 后 一 个 子 句 是 个 “ 包 医 百 病 ” 的 角 色 , 前 两 个 失 败 了 , 最 后 一 个 肯 定 会 成 功 , 不 管 用<br />

什 么 名 字 。 这 样 的 定 义 中 , 由 于 截 断 的 作 用 只 有 一 个 子 句 能 成 功 , 又 由 于 那 个 全 兜 的 子 句 ,<br />

总 会 有 一 个 成 功 , 所 以 模 式 成 了 procedure 的 。<br />

<br />

谓 词 的 流 模 式 指 明 了 哪 些 参 数 是 输 入 、 哪 些 参 数 是 输 出 。 调 用 谓 词 时 , 输 入 参 数 必 须 有<br />

一 个 值 , 而 用 输 出 参 数 取 得 需 要 谓 词 提 供 的 值 。 流 模 式 用 括 号 中 由 逗 号 分 开 的 字 母 i 和 o 表<br />

示 , 前 者 代 表 输 入 参 数 , 后 者 代 表 输 出 参 数 , 不 需 要 对 它 们 使 用 双 引 号 ; 它 们 的 顺 序 必 须 与<br />

参 数 表 中 给 出 的 顺 序 相 对 应 。 如 果 一 个 谓 词 有 五 个 参 数 , 前 三 个 是 输 入 后 二 个 是 输 出 , 流 模<br />

式 就 是 :<br />

(i, i, i, o, o)<br />

因 此 , 谓 词 的 声 明 可 以 这 样 :<br />

myPredicate : (string Title, integer NrOfPages) procedure (i,o).<br />

这 个 声 明 说 :myPredicate 有 两 个 参 数 , 一 个 串 一 个 是 整 数 , 模 式 是 procedure( 也 就 是<br />

总 会 成 功 , 没 有 回 溯 点 ), 必 须 要 输 入 一 个 串 当 书 名 , 它 会 用 第 二 个 参 数 返 回 总 的 页 数 。<br />

声 明 多 个 流 模 式 也 是 可 以 的 。 声 明 的 谓 词 模 式 适 用 于 后 面 所 有 的 流 模 式 。 当 不 同 的 流 模<br />

式 有 不 同 的 谓 词 模 式 时 , 必 须 要 声 明 。 这 就 可 能 是 一 个 序 列 :<br />

... ... procedure (i, o) nondeterm (o, o).<br />

这 个 声 明 表 示 , 用 流 模 式 (I,o) 时 谓 词 模 式 是 procedure 的 , 而 用 流 模 式 (o,o) 时 , 谓 词 模<br />

式 是 nondeterm 的 。<br />

声 明 谓 词 时 , 流 模 式 可 以 省 略 。 在 一 个 实 现 内 部 ( 也 就 是 对 私 有 谓 词 ) 所 需 要 的 流 模 式 ,<br />

源 自 谓 词 的 用 法 。 在 一 个 接 口 或 类 声 明 ( 也 就 是 对 公 有 谓 词 ) 中 , 省 略 流 模 式 就 表 示 所 有 参<br />

数 都 是 输 入 。 特 殊 流 模 式 anyflow 只 能 用 于 声 明 局 部 谓 词 ( 也 就 是 在 一 个 类 实 现 的 内 部 ),<br />

这 意 味 着 编 译 时 要 取 得 明 确 的 流 模 式 。 如 果 anyflow 流 模 式 前 的 谓 词 模 式 省 略 了 , 就 用 缺 省<br />

模 式 procedure。<br />

流 模 式 还 适 用 于 函 子 和 表 , 这 里 就 不 说 了 。<br />

9.8 子 句 段<br />

子 句 段 当 然 是 用 关 键 字 clauses 开 头 了 , 它 包 含 一 系 列 的 子 句 。 一 个 文 件 可 以 有 多 个 子<br />

句 段 。 子 句 段 用 于 定 义 谓 词 的 实 现 或 初 始 化 事 实 。 一 个 子 句 段 可 以 含 若 干 谓 词 或 事 实 , 而 一<br />

个 谓 词 或 事 实 的 所 有 子 句 必 须 在 同 一 个 子 句 段 中 集 中 起 来 , 不 能 插 入 别 的 谓 词 或 事 实 的 子<br />

句 。<br />

子 句 用 于 定 义 谓 词 。 一 个 谓 词 由 一 个 或 多 个 子 句 定 义 , 每 个 子 句 顺 序 地 执 行 , 直 到 没 有<br />

其 它 的 子 句 为 止 。 如 果 没 有 子 句 成 功 , 谓 词 就 失 败 了 。<br />

如 果 一 个 子 句 成 功 了 而 谓 词 中 还 剩 有 相 关 子 句 , 程 序 就 可 以 在 后 面 再 回 到 谓 词 的 这 些 子<br />

句 上 以 搜 索 其 它 的 解 。 这 就 是 所 谓 设 置 了 一 个 “ 回 溯 点 ”, 这 样 一 来 , 从 理 论 上 说 , 一 个 谓<br />

词 可 以 失 败 , 可 以 成 功 , 甚 至 还 可 以 成 功 很 多 次 。<br />

子 句 至 少 由 一 个 头 部 和 一 个 可 能 出 现 的 体 部 构 成 。 子 句 的 头 部 由 谓 词 名 和 括 号 中 的 参 数<br />

构 成 , 如 :<br />

person("John", 185).<br />

107


这 个 子 句 说 John 的 身 高 是 185。 可 以 认 为 它 是 无 条 件 地 成 立 的 , 所 以 它 也 叫 做 事 实 。<br />

如 果 要 给 头 部 加 上 体 , 它 们 之 间 必 须 用 符 号 “:‐” 分 开 来 , 例 如 :<br />

grandFather(GrandChild, GrandFather) :‐<br />

parent(GrandChild, Person),<br />

father(person, GrandFather).<br />

符 号 “:‐” 读 作 “ 如 果 ”, 而 体 部 则 可 以 视 为 一 个 或 多 个 条 件 , 这 是 子 句 的 逻 辑 性 解 释 。<br />

当 条 件 都 满 足 了 , 头 部 就 是 真 的 。 构 成 体 部 的 句 子 用 逗 号 分 开 , 逗 号 “,” 读 作 “ 与 ”。 如 果<br />

句 子 用 分 号 “;” 分 开 , 它 就 读 作 “ 或 ”, 不 推 荐 在 子 句 中 使 用 分 号 , 因 为 它 容 易 引 起 混 乱 。<br />

分 号 的 使 用 是 可 以 避 免 的 , 看 下 面 :<br />

child(Name) :‐ father(Name, Father) ; mother(Name, Mother). % 用 了 分 号<br />

我 们 可 以 用 两 个 独 立 的 子 句 来 表 示 完 全 一 样 的 意 思 :<br />

child(Name) :‐ father(Name, Father).<br />

child(Name) :‐ mother(Name, Mother).<br />

相 对 子 句 逻 辑 性 的 解 释 , 还 有 它 的 过 程 性 的 解 释 , 它 是 说 子 句 头 部 是 调 用 的 过 程 头 体 ,<br />

而 符 号 :‐ 后 的 句 子 是 在 谓 词 调 用 时 才 执 行 的 。 需 要 使 用 哪 种 解 释 , 与 程 序 及 句 子 的 副 作 用 ( 辅<br />

助 功 能 ) 有 关 。<br />

调 用 一 个 谓 词 时 , 会 ( 从 上 到 下 ) 依 次 试 它 的 子 句 。 每 个 子 句 的 头 部 用 调 用 参 数 来 合 一 ,<br />

也 就 是 说 , 输 入 参 数 绑 定 于 谓 词 调 用 所 提 供 的 值 。 如 果 这 个 合 一 过 程 成 功 了 , 才 会 执 行 子 句<br />

的 体 ( 如 果 有 的 话 )。 子 句 只 有 在 头 部 匹 配 和 体 部 匹 配 都 成 功 时 , 才 会 成 功 , 否 则 , 就 失 败<br />

了 。<br />

如 果 一 个 谓 词 是 函 数 , 就 必 须 在 “=” 之 后 声 明 一 个 变 量 用 于 绑 定 函 数 必 须 返 回 的 值 。<br />

例 如 , 声 明 了 下 面 这 样 的 函 数 :<br />

square : (integer) ‐> integer Square procedure (i).<br />

这 是 求 一 个 整 数 平 方 的 函 数 , 在 子 句 段 中 就 应 该 包 含 必 要 的 子 句 , 如 :<br />

square(Number) = Square :‐<br />

Square = Number*Number.<br />

表 达 式 “=Square” 告 诉 编 译 器 , 当 谓 词 square/1 的 子 句 结 束 时 , 变 量 Square 的 值 必 须<br />

返 回 给 调 用 谓 词 。 而 调 用 一 个 函 数 , 必 须 要 准 备 好 一 个 变 量 绑 定 于 返 回 值 , 如 :<br />

...,<br />

NewSquare = square(3),<br />

...<br />

Newsquare 绑 定 成 9。<br />

9.9 目 标 段<br />

目 标 段 定 义 了 <strong>Visual</strong> <strong>Prolog</strong> 程 序 的 起 始 入 口 。<br />

传 统 的 <strong>Prolog</strong>, 要 给 推 理 机 一 个 起 动 的 谓 词 , 这 个 谓 词 可 以 是 任 意 的 。 回 想 一 下 我 们 在<br />

PIE 中 的 家 庭 程 序 。 我 们 可 以 输 入 我 们 想 要 的 目 标 , 程 序 用 这 个 谓 词 开 始 动 起 来 。 不 过 , 在<br />

<strong>Visual</strong> <strong>Prolog</strong> 中 不 是 这 样 了 。 它 的 程 序 是 经 过 编 译 的 , 而 编 译 器 需 要 负 责 产 生 我 们 写 的 程 序<br />

的 高 效 执 行 代 码 。 需 要 强 调 指 出 , 对 程 序 进 行 编 译 和 执 行 编 译 过 的 程 序 是 两 码 事 。 首 先 , 程<br />

序 要 经 过 编 译 , 以 后 再 要 执 行 的 是 编 译 过 的 程 序 。 因 此 , 编 译 器 需 要 预 先 知 道 到 底 从 哪 个 谓<br />

词 的 代 码 开 始 执 行 , 以 后 程 序 起 动 时 才 能 按 正 确 的 位 置 开 始 。 我 们 知 道 , 编 译 后 的 程 序 是 可<br />

以 不 需 要 <strong>Visual</strong> <strong>Prolog</strong> 编 译 器 或 IDE 而 独 立 运 行 的 。<br />

为 了 得 到 编 译 过 的 程 序 , 专 门 有 一 个 以 关 键 字 goal 开 始 的 特 殊 的 段 。 这 个 段 中 , 包 含<br />

有 一 个 特 殊 谓 词 run, 使 用 它 时 没 有 参 数 。 这 个 谓 词 是 一 个 程 序 执 行 的 起 点 , 所 以 目 标 段 就<br />

是 一 个 程 序 的 入 口 。 举 个 例 子 , 假 设 我 们 有 个 名 为 procfunc 的 程 序 , 是 要 向 控 制 台 输 出 ,<br />

它 的 目 标 段 是 :<br />

goal<br />

mainExe::run(main::run).<br />

这 个 目 标 说 : 机 器 应 该 在 类 main 中 找 run 谓 词 , 这 个 类 是 在 创 建 新 程 序 ( 工 程 ) 时 由<br />

108


IDE 创 建 的 。main 中 的 run 子 句 会 是 这 样 :<br />

clauses<br />

run():‐<br />

Kwad = func::squareFunc(3),<br />

stdIO::write(Kwad),<br />

_X = stdIO::readchar(),<br />

succeed().<br />

% 我 们 为 程 序 加 的 这 一 行<br />

% 我 们 为 程 序 加 的 这 一 行<br />

% 我 们 为 程 序 加 的 这 一 行<br />

% 可 以 在 这 里 写 需 要 的 代 码<br />

end implement procfunc<br />

可 以 不 去 过 多 关 心 这 些 东 西 ,IDE 会 处 理 好 它 。 不 过 这 里 可 能 有 一 个 问 题 。 通 常 目 标 中<br />

的 run/0 谓 词 必 须 是 procedure 模 式 的 , 而 我 们 有 时 会 插 入 一 些 代 码 ( 如 上 述 的 例 子 中 那 样 ),<br />

当 所 调 用 的 谓 词 是 较 弱 模 式 ( 如 nondeterm) 时 , 编 译 器 会 提 示 说 , 这 样 的 调 用 会 把 run/0<br />

的 模 式 实 际 改 变 成 nondeterm 模 式 了 。 这 是 个 讨 厌 的 事 , 但 是 必 须 想 办 法 去 解 决 。<br />

还 要 说 一 点 , 目 标 段 定 义 了 自 己 的 范 围 , 因 此 对 其 它 谓 词 的 所 有 调 用 都 应 包 含 类 标 识 ,<br />

必 须 要 给 出 谓 词 所 在 类 的 名 称 。<br />

9.10 open 段 和 访 问 范 围<br />

<strong>Visual</strong> <strong>Prolog</strong> 把 整 个 程 序 分 成 若 干 个 部 分 , 每 个 部 分 定 义 一 个 类 。 对 于 面 向 对 象 的 语 言<br />

来 说 , 一 个 类 就 是 把 代 码 及 相 关 数 据 放 在 一 起 的 一 个 包 , 第 8 章 中 介 绍 了 类 的 概 念 。 前 面 也<br />

说 过 , 对 不 熟 悉 面 向 对 象 的 语 言 的 人 来 说 , 也 可 以 近 似 地 把 类 看 成 是 模 块 。 通 常 ,<strong>Visual</strong> <strong>Prolog</strong><br />

在 单 独 的 文 件 中 定 义 各 个 类 。 程 序 执 行 时 常 会 发 生 这 样 的 情 况 : 程 序 需 要 调 用 在 另 一 个 文 件<br />

中 定 义 的 谓 词 , 同 样 , 在 一 个 类 中 定 义 的 数 据 ( 常 数 ) 或 域 有 时 又 需 要 从 别 的 文 件 中 来 访 问 。<br />

<strong>Visual</strong> <strong>Prolog</strong> 允 许 这 样 的 代 码 和 数 据 的 交 叉 访 问 , 这 里 用 到 了 一 个 概 念 : 访 问 范 围 。 为 便 于<br />

理 解 , 举 个 例 子 。 假 设 有 一 个 在 类 class1 中 定 义 的 谓 词 pred1, 我 们 需 要 在 由 class2 中 定 义<br />

的 谓 词 pred2 的 子 句 里 调 用 pred1, 就 应 该 这 样 做 :<br />

/* this is a part of the clauses in "class2" */<br />

pred3:‐<br />

...,<br />

... .<br />

pred2:‐<br />

class1::pred1, %pred1 在 这 个 文 件 中 是 不 知 道 的<br />

% 它 是 在 别 的 文 件 中 定 义 的<br />

% 因 此 需 要 类 标 识 "class1::"<br />

pred3,<br />

...<br />

/* end of the part from "class2" */<br />

在 上 面 的 例 子 中 可 以 看 到 ,pred2 的 子 句 体 里 调 用 了 两 个 谓 词 pred1 和 pred3。 由 于 pred1<br />

是 在 别 的 文 件 ( 定 义 class1 的 文 件 ) 中 定 义 的 , 所 以 在 pred1 前 面 要 加 上 由 class1 和 双 冒 号<br />

构 成 的 类 标 识 ; 但 pred3 就 是 在 与 pred2 同 一 个 文 件 中 定 义 的 , 因 此 就 不 需 要 在 它 的 前 面 使<br />

用 类 标 识 。<br />

之 所 以 这 样 做 , 技 术 上 的 解 释 是 :pred3 的 可 见 范 围 与 pred2 是 相 同 的 , 因 此 不 需 要 说<br />

明 它 是 来 自 pred2 同 一 类 中 的 , 编 译 器 会 自 动 地 在 class2 范 围 中 查 找 pred3 的 定 义 。 某 个 类<br />

定 义 的 范 围 , 仅 限 于 一 个 文 件 中 这 个 类 实 现 ( 也 就 是 写 在 关 键 字 implement 和 end implement<br />

之 间 的 代 码 ) 内 , 定 义 在 这 里 的 谓 词 可 以 相 互 调 用 而 不 需 要 在 谓 词 前 使 用 类 标 识 。<br />

类 的 定 义 范 围 可 以 用 关 键 字 open 来 扩 展 。 这 个 关 键 字 告 诉 编 译 器 , 把 在 其 它 文 件 中 定<br />

义 的 谓 词 、 常 数 或 域 的 名 字 拿 来 。 扩 展 了 范 围 之 后 , 就 不 需 要 使 用 类 标 识 了 。<br />

/* this is a part of the clauses in "class2" */<br />

109


open class1<br />

...<br />

pred3:‐<br />

...<br />

!.<br />

pred2:‐<br />

pred1, % 这 里 不 再 需 要 "class1::" 标 识 了 , 因 为 已 经 用 关 键 字 open 扩 展 了 范 围<br />

pred3,<br />

...<br />

/* end of the part from "class2" */<br />

open 段 看 起 来 不 像 是 个 段 , 它 就 是 由 关 键 字 open 开 头 的 一 行 , 后 面 跟 着 由 逗 号 分 隔 的<br />

类 名 , 如 :<br />

open another_class, yet_another_class<br />

注 意 , 它 不 是 用 句 点 而 是 用 硬 回 车 结 束 的 。<br />

对 类 级 别 实 体 的 引 用 , 由 于 有 了 open 而 更 加 方 便 了 。open 段 把 一 个 范 围 的 名 字 带 到 了<br />

另 一 个 范 围 中 , 这 样 在 引 用 时 就 可 以 不 需 要 使 用 限 定 标 识 了 。<br />

open 对 对 象 成 员 的 名 字 没 有 作 用 , 因 为 它 们 只 能 通 过 对 象 来 访 问 。 但 对 类 成 员 、 域 、<br />

函 子 和 常 数 的 名 字 , 就 可 以 不 用 限 定 标 识 来 访 问 了 。<br />

用 这 个 方 法 把 名 字 带 入 到 一 个 范 围 之 后 , 有 可 能 发 现 有 些 名 字 就 不 是 唯 一 的 了 。<br />

open 段 只 在 它 出 现 的 范 围 内 有 作 用 , 这 尤 其 意 味 着 在 一 个 类 声 明 中 的 open 段 对 这 个 类<br />

的 实 现 是 没 有 作 用 的 。<br />

9.11 类 谓 词 、 类 事 实 以 及 在 哪 儿 声 明 它 们<br />

一 个 可 以 创 建 对 象 的 名 为 的 类 , 它 在 VIP 的 三 个 文 件 中 要 有 代 码 , 它 们 是 :<br />

• .cl 文 件 , 这 是 类 声 明 文 件 ;<br />

• .i 文 件 , 这 是 接 口 文 件 ;<br />

• .pro 文 件 , 这 是 实 现 文 件 。<br />

这 些 文 件 第 8 章 中 介 绍 过 , 这 里 再 简 单 重 复 一 下 : 类 接 口 文 件 包 括 公 有 ( 可 以 从 另 一 个<br />

文 件 调 用 的 ) 声 明 及 由 类 创 建 的 对 象 的 相 关 内 容 。 类 接 口 可 以 认 为 是 从 外 面 看 到 的 对 一 个 类<br />

的 对 象 的 描 述 , 所 以 也 可 以 把 它 看 成 是 这 个 类 生 成 的 对 象 的 类 型 声 明 。 这 也 就 是 为 什 么 我 们<br />

有 时 说 接 口 定 义 了 名 义 上 的 对 象 类 型 。 所 有 在 接 口 中 定 义 的 谓 词 , 都 是 接 口 类 型 对 象 的 成 员 。<br />

如 果 一 个 类 不 用 生 成 对 象 ( 这 可 由 编 程 者 来 确 定 ), 那 就 不 需 要 类 接 口 的 文 件 , 类 的 代<br />

码 就 只 是 在 两 个 文 件 里 。 类 声 明 文 件 包 括 谓 词 的 声 明 , 它 们 也 是 公 用 的 , 是 把 这 个 类 做 为 一<br />

个 整 体 来 考 虑 的 。 类 声 明 文 件 里 可 以 含 有 常 数 及 域 定 义 和 谓 词 声 明 , 程 序 的 其 它 部 分 可 以 看<br />

到 和 使 用 这 些 在 谓 词 声 明 中 涉 及 到 的 实 体 , 我 们 说 类 的 声 明 规 定 了 该 类 的 公 有 部 分 。 最 后 一<br />

个 , 类 实 现 文 件 包 含 了 谓 词 的 定 义 , 也 就 是 谓 词 的 子 句 和 事 实 , 它 们 都 是 在 类 声 明 和 接 口 文<br />

件 中 声 明 过 的 。 类 实 现 提 供 了 类 声 明 文 件 中 声 明 过 的 谓 词 的 定 义 和 构 造 器 , 还 有 构 造 对 象 所<br />

支 持 的 其 它 的 谓 词 定 义 。<br />

每 个 文 件 中 除 了 用 于 谓 词 的 段 以 外 还 可 以 插 入 其 它 的 段 , 不 过 要 特 别 注 意 它 们 的 范 围 。<br />

参 见 9.10 节<br />

对 于 谓 词 和 事 实 来 说 , 事 情 比 较 微 妙 , 因 为 这 里 有 谓 词 和 类 谓 词 , 事 实 和 类 事 实 。 我 们<br />

来 说 一 下 要 注 意 些 什 么 , 说 不 定 会 把 读 者 弄 晕 ( 可 能 已 经 晕 了 ☺)。<br />

事 实 属 于 对 象 时 , 同 一 类 中 的 各 个 对 象 可 以 ( 而 且 常 常 就 是 ) 有 不 同 的 事 实 , 这 种 事 实<br />

必 须 要 在 类 接 口 文 件 中 声 明 和 定 义 。 但 当 事 实 是 属 于 一 个 类 的 全 体 时 , 这 就 是 个 类 事 实 , 必<br />

须 要 在 类 声 明 文 件 中 声 明 和 定 义 。 但 要 小 心 , 在 接 口 文 件 中 声 明 一 个 事 实 时 , 它 是 对 象 事 实 ;<br />

在 类 声 明 文 件 中 声 明 一 个 事 实 时 , 它 是 类 事 实 , 只 有 在 类 实 现 文 件 中 才 会 出 现 混 淆 。 在 实 现<br />

文 件 中 声 明 事 实 , 意 味 着 它 是 私 有 的 , 在 类 实 现 之 外 是 看 不 到 的 , 但 它 可 以 是 对 象 事 实 也 可<br />

110


以 是 类 事 实 。 所 以 , 在 实 现 文 件 中 声 明 事 实 时 , 必 须 指 出 它 到 底 是 对 象 事 实 还 是 类 事 实 。 如<br />

果 是 类 事 实 , 必 须 要 在 以 关 键 字 class facts 开 始 的 一 个 段 中 声 明 。 参 见 表 9.2 的 概 览 。<br />

事 实 声 明 的 指 向 公 用 事 实 私 有 事 实<br />

对 象 成 员 在 类 接 口 的 facts 段 在 实 现 文 件 的 facts 段<br />

类 成 员 在 类 声 明 的 facts 段 在 实 现 文 件 的 class facts 段<br />

表 9.2 事 实 声 明<br />

谓 词 声 明 用 于 在 一 个 范 围 内 声 明 谓 词 , 这 个 范 围 内 谓 词 声 明 是 可 见 的 。 在 接 口 文 件 中 声<br />

明 一 个 谓 词 , 就 表 示 着 这 个 谓 词 是 一 个 对 象 谓 词 并 且 是 可 以 公 用 的 , 相 应 类 的 对 象 必 须 支 持<br />

这 个 谓 词 。 在 类 声 明 中 声 明 的 谓 词 , 表 示 这 个 类 支 持 声 明 的 谓 词 公 用 。 而 在 类 实 现 中 声 明 的<br />

谓 词 , 就 意 味 着 这 个 谓 词 只 是 局 部 可 用 ( 私 有 ) 的 , 此 时 , 谓 词 可 以 是 对 象 成 员 也 可 以 是 类<br />

成 员 。 是 对 象 成 员 , 就 必 须 在 以 关 键 字 predicates 开 始 的 段 中 声 明 ; 而 是 类 谓 词 , 就 必 须 在<br />

以 关 键 字 class predicates 开 始 的 段 中 声 明 。 不 管 是 什 么 情 况 , 必 须 要 有 相 应 的 谓 词 定 义 存 在 ,<br />

就 是 说 一 定 要 在 实 现 文 件 中 有 声 明 谓 词 的 子 句 。 参 见 表 9.3 的 概 览 。<br />

谓 词 声 明 的 指 向 公 用 谓 词 私 有 谓 词<br />

对 象 成 员 在 类 接 口 的 predicates 段 在 实 现 文 件 的 predicates 段<br />

类 成 员 在 类 声 明 的 predicates 段 在 实 现 文 件 的 class<br />

predicates 段<br />

表 9.3 谓 词 声 明<br />

关 键 字 class 只 能 在 类 实 现 文 件 中 使 用 , 实 现 文 件 之 外 它 没 有 用 处 。 因 为 对 于 ( 用 谓 词<br />

举 例 来 说 ) 在 接 口 文 件 中 声 明 的 谓 词 , 它 总 是 对 象 谓 词 , 而 在 类 声 明 文 件 中 声 明 的 谓 词 , 它<br />

又 总 是 类 谓 词 。<br />

因 此 , 类 私 有 地 ( 也 就 是 在 实 现 文 件 内 部 ) 声 明 和 定 义 的 实 体 比 在 接 口 文 件 中 声 明 和 定<br />

义 的 要 多 。 尤 其 是 , 实 现 可 以 声 明 事 实 数 据 库 , 它 可 以 支 持 类 和 对 象 。 实 现 是 一 个 混 合 范 围 ,<br />

因 为 它 包 含 了 类 的 实 现 和 由 类 产 生 的 对 象 的 实 现 。 类 的 类 部 分 由 类 的 所 有 对 象 共 享 , 这 与 对<br />

象 部 分 正 相 反 , 它 只 是 各 个 对 象 的 。 类 部 分 和 对 象 部 分 都 可 以 包 含 事 实 和 谓 词 , 而 域 、 函 子 、<br />

常 数 则 总 是 属 于 类 部 分 的 , 也 就 是 说 它 们 不 属 于 单 个 的 对 象 。<br />

缺 省 时 , 所 有 在 一 个 类 实 现 中 声 明 的 谓 词 和 事 实 成 员 都 是 对 象 成 员 。 要 声 明 类 成 员 , 必<br />

须 要 在 段 关 键 字 (predicates 和 facts) 前 加 class 关 键 字 , 在 这 样 的 段 中 声 明 的 都 是 类 成 员 。<br />

类 成 员 可 以 由 类 的 类 部 分 引 用 , 但 不 能 被 对 象 部 分 引 用 。 另 一 方 面 , 对 象 成 员 则 可 以 访 问 类<br />

的 类 部 分 和 对 象 部 分 。<br />

接 口 声 明 了 外 界 应 如 何 看 它 这 部 分 的 对 象 。 不 过 要 小 心 ! 在 声 明 中 声 明 的 常 数 和 域 , 可<br />

以 按 对 象 部 分 访 问 :<br />

:<br />

就 是 用 对 象 标 识 加 一 个 冒 号 再 加 上 常 数 名 就 可 以 访 问 到 。 但 是 在 接 口 文 件 中 声 明 的 常 数 和<br />

域 , 它 们 不 是 对 象 成 员 。 这 时 , 接 口 也 是 全 局 范 围 的 , 这 里 可 以 定 义 常 数 和 域 , 但 接 口 里 的<br />

常 数 和 域 不 是 接 口 表 示 的 类 型 ( 或 对 象 具 有 的 类 型 )。 对 这 样 的 域 和 常 数 , 在 其 它 的 范 围 里<br />

引 用 时 可 以 用 接 口 名 :<br />

::<br />

还 有 另 一 个 办 法 , 就 是 使 用 open( 参 见 9.10 节 )。<br />

111


第 10 章 递 归 、 表 和 排 序<br />

这 章 中 介 绍 递 归 和 表 的 概 念 , 它 们 在 <strong>Prolog</strong> 程 序 中 都 是 很 常 用 的 。 其 实 在 前 面 的 章 节<br />

中 我 们 已 经 看 到 过 递 归 和 表 , 但 这 里 要 介 绍 的 是 需 要 了 解 的 更 多 细 节 。 第 一 节 中 介 绍 递 归 ,<br />

第 二 节 中 介 绍 表 , 第 三 节 是 它 们 的 结 合 , 可 以 看 到 更 有 用 的 东 西 。 在 10.4 节 中 介 绍 list 类 ,<br />

它 包 含 了 很 多 很 有 用 的 表 处 理 的 谓 词 。 第 五 节 说 排 序 , 可 以 看 到 一 些 递 归 和 表 的 应 用 例 子 。<br />

需 要 事 先 说 明 , 一 定 程 度 上 说 这 一 章 是 多 余 的 。 这 里 的 每 个 例 子 程 序 都 有 标 准 谓 词 , 就<br />

在 list 类 中 , 把 这 个 类 用 open 声 明 后 , 就 可 以 访 问 到 所 有 这 些 谓 词 , 附 录 2 中 有 介 绍 。 但<br />

是 , 通 过 这 章 中 的 例 子 , 读 者 对 <strong>Prolog</strong> 的 理 解 会 得 以 加 深 。 在 自 己 的 程 序 中 使 用 递 归 与 表<br />

的 可 能 性 是 很 大 的 。<br />

10.1 递 归<br />

小 学 时 从 1 数 到 10 还 记 得 吗 ? 从 1 开 始 , 加 1 是 2, 再 加 1,…, 直 到 10。 所 做 的 ,<br />

就 是 从 1 开 始 , 然 后 调 用 同 一 个 函 数 ( 加 1) 得 到 下 个 数 , 这 就 是 递 归 背 后 的 基 本 思 路 。 一<br />

般 地 说 , 当 函 数 调 用 它 本 身 时 , 就 会 出 现 递 归 , 它 会 产 生 一 个 无 限 的 调 用 序 列 。 在 <strong>Prolog</strong><br />

中 函 数 是 一 个 谓 词 , 所 以 在 这 里 当 谓 词 调 用 自 己 的 时 候 , 就 出 现 递 归 。 举 例 来 说 , 一 个 计 数<br />

的 谓 词 :<br />

predicates<br />

clauses<br />

count : (integer Number) procedure.<br />

count (Number) :‐<br />

stdIO::write(Number),<br />

NextNumber = Number + 1,<br />

count(Nextnumber).<br />

用 count(1) 调 用 这 个 谓 词 , 就 像 我 们 从 1 开 始 数 一 样 , 它 会 先 写 出 1, 接 着 加 1 得 到 2,<br />

然 后 它 用 count(2) 调 用 自 己 , 写 出 2, 又 加 1 得 到 3, 又 ……, 没 完 没 了 地 做 下 去 。count 谓<br />

词 是 一 个 递 归 的 谓 词 。<br />

一 调 用 count/1, 它 就 不 会 停 了 , 这 很 讨 厌 。 我 们 要 加 个 子 句 让 递 归 停 下 来 , 假 设 要 在<br />

写 到 10 时 就 停 吧 , 可 以 这 样 做 :<br />

count(11) :‐<br />

stdIO::write("That's all folks!"), !.<br />

把 这 个 子 句 放 在 其 它 子 句 之 前 。 每 次 这 个 谓 词 递 归 调 用 时 , 首 先 都 会 检 查 是 不 是 到 了<br />

11 了 , 如 果 没 有 , 就 会 继 续 ; 如 果 到 了 , 就 会 写 出 消 息 然 后 停 下 来 。 由 于 截 断 的 作 用 , 回<br />

溯 也 会 停 止 。 我 们 来 把 所 有 内 容 放 在 一 个 程 序 里 。 这 一 章 中 我 们 的 策 略 是 使 用 console( 控<br />

制 台 ), 因 为 这 样 输 入 工 作 量 最 小 , 毕 竟 我 们 这 章 主 要 着 眼 点 是 <strong>Prolog</strong> 而 不 是 GUI。<br />

先 来 创 建 一 个 新 工 程 , 叫 ch10count 吧 。 不 要 忘 了 策 略 选 console。 在 工 程 树 中 创 建 一<br />

个 名 为 counter 的 新 类 , 把 它 放 在 已 有 包 中 , 去 掉 creates object 选 项 , 因 为 这 个 类 只 当 一 个<br />

模 块 来 用 。IDE 会 打 开 counter.cl 和 counter.pro 文 件 , 在 前 一 个 文 件 中 输 入 谓 词 count 的 声 明 :<br />

class counter<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

% @short Class information predicate.<br />

112


% @detail This predicate represents information predicate of this class.<br />

% @end<br />

count(integer Number) procedure .<br />

end class counter<br />

在 counter.pro 文 件 中 加 上 谓 词 count/1 的 子 句 :<br />

clauses<br />

classInfo(className, classVersion).<br />

count(11) :‐<br />

stdIO::write("That's all folks!"), !.<br />

count(Number) :‐<br />

stdIO::write(Number),<br />

stdIO::nl,<br />

NextNumber = Number + 1,<br />

count(NextNumber).<br />

end implement count<br />

程 序 中 的 stdIO::nl 使 每 个 数 字 各 占 一 行 。 现 在 差 不 多 了 , 我 们 是 用 console 策 略 的 , 所<br />

以 还 要 在 目 标 中 插 入 正 确 的 代 码 。 先 构 造 工 程 , 然 后 在 main.pro 文 件 中 找 到 下 面 的 子 句 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

succeed().<br />

end implement main<br />

% place your own code here<br />

把 它 改 成 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

counter::count(1),<br />

_X = stdIO::readChar().<br />

end implement main<br />

由 子 句 counter::count(1) 启 动 计 数 , 子 句 _X=stdIO::readChar() 会 使 程 序 计 数 到 10 以 后 强<br />

迫 程 序 等 待 , 直 到 用 户 击 回 车 键 。 如 果 没 有 这 个 句 子 , 还 没 看 清 程 序 写 出 了 些 什 么 MsDOS<br />

控 制 台 就 会 消 失 了 。<br />

运 行 这 个 程 序 , 会 出 现 一 个 MsDOS 窗 口 , 程 序 显 示 计 数 的 情 况 。<br />

我 们 用 了 一 个 单 独 的 子 句 描 述 了 让 程 序 停 止 的 规 则 。 还 可 以 用 别 的 方 法 , 比 如 :<br />

count(Number) :‐<br />

Number < 11,<br />

stdIO::write(Number),<br />

113


stdIO::nl,<br />

NextNumber = Number + 1,<br />

count(NextNumber).<br />

count(_) :‐<br />

stdIO::write("That's all folks!"), !.<br />

递 归 显 然 更 加 便 利 。 但 同 时 又 必 须 十 分 小 心 , 想 想 看 把 两 个 子 句 位 置 交 换 一 下 会 是 什 么<br />

样 的 后 果 :<br />

count(Number) :‐<br />

stdIO::write(Number),<br />

stdIO::nl,<br />

NextNumber = Number + 1,<br />

count(NextNumber).<br />

count(11) :‐<br />

stdIO::write("That's all folks!"), !.<br />

这 样 的 话 , 永 远 也 到 不 了 count(11), 程 序 也 就 永 远 不 会 停 下 来 ! 同 样 , 写 语 句 的 位 置<br />

也 相 当 重 要 , 把 程 序 改 成 下 面 这 样 , 看 看 会 出 现 什 么 结 果 :<br />

count(11) :‐<br />

stdIO::write("That's all folks!"), !.<br />

count(Number) :‐<br />

NextNumber = Number + 1,<br />

count(NextNumber),<br />

stdIO::write(Number),<br />

stdIO::nl.<br />

写 出 的 数 字 顺 序 是 反 的 。 这 是 因 为 改 动 后 程 序 先 做 从 1 到 10 的 递 归 , 然 后 才 从 调 用 中<br />

退 出 开 始 写 数 字 , 所 以 会 先 写 出 最 后 一 个 数 字 10, 然 后 是 9, 等 等 。 注 意 , 当 程 序 调 用 递 归<br />

谓 词 时 , 每 次 调 用 都 会 返 回 本 次 调 用 的 变 量 和 它 的 值 , 每 次 都 有 自 己 所 属 的 变 量 。 也 就 是 说 ,<br />

变 量 是 局 限 于 子 句 使 用 它 们 的 地 方 的 。<br />

在 谓 词<br />

count(Number) :‐<br />

stdIO::write(Number),<br />

stdIO::nl,<br />

NextNumber = Number + 1,<br />

count(NextNumber).<br />

中 可 以 看 到 , 递 归 调 用 是 最 后 一 个 句 子 , 这 隐 含 着 说 明 了 程 序 在 处 理 完 谓 词 调 用 后 不 必 回 到<br />

这 里 来 。 这 被 称 作 尾 递 归 , 写 程 序 时 这 样 做 是 很 有 益 的 。 尾 递 归 意 味 着 程 序 执 行 时 编 译 器 可<br />

以 用 重 复 来 替 代 递 归 , 这 可 以 降 低 对 堆 栈 空 间 的 要 求 。 递 归 会 导 致 巨 多 的 调 用 , 如 果 这 些 调<br />

用 不 需 要 记 录 返 回 地 址 就 可 以 节 省 大 量 存 储 空 间 。<br />

本 节 最 后 , 我 们 来 看 一 个 经 典 的 例 子 , 阶 乘 。 公 式 n!( 阶 乘 ) 的 意 思 是 :<br />

n! = 1 * 2 * 3 * 4 * ...* n<br />

计 算 一 个 数 的 阶 乘 的 程 序 可 以 包 含 以 下 一 些 子 句 :<br />

fac(1,1).<br />

114


fac(Number, Factorial) :‐<br />

NumberMinus1 = Number ‐ 1,<br />

fac(NumberMinus1, FactorialMinus1),<br />

Factorial = Number * FactorialMinus1.<br />

1 的 阶 乘 是 1。 当 数 大 于 1 时 , 先 算 出 较 小 数 的 阶 乘 , 再 算 那 个 数 的 阶 乘 。 我 们 来 写 个<br />

程 序 做 阶 乘 。 先 创 建 一 个 新 工 程 ch10factorial, 策 略 还 是 console。 在 工 程 中 创 建 一 个 模 块 ( 不<br />

创 建 对 象 的 类 )factorial, 在 factorial.cl 中 声 明 fac:<br />

predicates<br />

classInfo : core::classInfo.<br />

% @short Class information predicate.<br />

% @detail This predicate represents information predicate of this class.<br />

% @end<br />

fac : (integer Number, integer Factorial) procedure (i,o).<br />

end class factorial<br />

然 后 在 factorial.pro 文 件 中 添 加 子 句 :<br />

clauses<br />

classInfo(className, classVersion).<br />

fac(1,1).<br />

fac(Number, Factorial) :‐<br />

NumberMinus1 = Number ‐ 1,<br />

fac(NumberMinus1, FactorialMinus1),<br />

Factorial = Number * FactorialMinus1.<br />

end implement factorial<br />

构 造 工 程 后 在 main.pro 文 件 中 把 run 谓 词 改 成 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

stdIO::write("Type a number (0 to end) ‐> "),<br />

hasdomain(integer, Number),<br />

Number = stdIO::read(),<br />

Number 0, !,<br />

factorial::fac(Number, Factorial),<br />

stdIO::write("Factorial of ", Number, " is ", Factorial),<br />

stdIO::nl,<br />

_ = stdIO::readchar(),<br />

run().<br />

run().<br />

运 行 这 个 程 序 , 它 会 让 用 户 输 入 一 个 数 , 然 后 给 出 这 个 数 的 阶 乘 。 当 然 , 它 不 是 个 考 虑<br />

周 全 的 程 序 , 如 果 输 入 的 数 是 负 数 或 是 太 大 , 它 就 会 出 错 。 谓 词 fac 的 参 数 我 们 用 的 是 整 数<br />

115


型 , 它 的 最 大 值 是 有 限 制 的 , 其 允 许 的 范 围 是 ‐2147483648 到 2147483647。 当 输 入 的 数 是<br />

13 时 , 可 能 就 已 经 太 大 了 , 阶 乘 的 结 果 会 超 出 限 度 , 给 出 一 个 错 误 消 息 ( 可 能 这 个 消 息 会<br />

一 闪 而 过 )。<br />

跟 踪 一 下 计 算 的 过 程 事 情 会 有 趣 些 。 我 们 用 一 个 写 语 句 来 显 示 递 归 的 中 间 步 骤 , 在 文 件<br />

factorial.pro 中 把 fac(Number,Factorial) 子 句 改 成 :<br />

fac(Number, Factorial) :‐<br />

NumberMinus1 = Number ‐ 1,<br />

fac(NumberMinus1, FactorialMinus1),<br />

Factorial = Number * FactorialMinus1,<br />

stdIO::write("Factorial of ", Number, " is ", Factorial),<br />

stdIO::nl.<br />

再 运 行 程 序 , 它 会 显 示 出 每 次 计 算 得 到 的 阶 乘 , 直 到 输 入 的 数 的 阶 乘 。 可 以 看 到 阶 乘 增<br />

长 得 很 快 , 比 数 的 平 方 要 快 多 了 。<br />

做 为 一 个 例 子 , 我 们 再 定 义 一 个 函 数 来 计 算 阶 乘 。 函 数 名 叫 facfunc, 在 factorial.cl 中 声<br />

明 它 :<br />

facfunc: (integer Number) ‐> integer Factorial procedure.<br />

在 factorial.pro 中 添 加 facfunc() 的 子 句 :<br />

clauses<br />

facfunc(1) = 1 :‐ !.<br />

facfunc(Number) = Factorial :‐<br />

NumberMinus1 = Number ‐ 1,<br />

FactorialMinus1 = facfunc(NumberMinus1),<br />

Factorial = Number * FactorialMinus1,<br />

stdIO::write("Factorial of ", Number, " is ", Factorial),<br />

stdIO::nl.<br />

再 在 main.pro 中 把 run 谓 词 改 成 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

stdIO::write("Type a number (0 to end) ‐> "),<br />

hasdomain(integer, Number),<br />

Number = stdIO::read(),<br />

Number 0, !,<br />

stdIO::write("Factorial by predicate"), stdIO::nl,<br />

factorial::facpred(Number, Factorial),<br />

stdIO::write("Factorial of ", Number, " is ", Factorial),<br />

stdIO::nl,<br />

stdIO::write("hit "),<br />

_ = stdIO::readchar(),<br />

_ = stdIO::readchar(),<br />

stdIO::write("Factorial by function"), stdIO::nl,<br />

FuncFactorial = factorial::facfunc(Number),<br />

116


stdIO::write("Factorial of ", Number, " is ", FuncFactorial),<br />

stdIO::nl,<br />

run().<br />

run().<br />

代 码 最 后 这 一 段 相 当 恐 怖 啦 , 最 好 是 从 本 书 中 拷 贝 — 粘 贴 吧 。<br />

构 造 运 行 程 序 , 谓 词 和 函 数 的 结 果 是 一 样 的 , 但 它 们 解 决 问 题 的 方 法 是 不 一 样 的 。 怎 么<br />

做 好 , 由 各 人 了 。<br />

10.2 表<br />

13<br />

表 是 元 素 的 序 列 。 表 和 表 处 理 是 <strong>Prolog</strong> 中 很 有 用 的 技 术 。 本 节 要 介 绍 什 么 是 表 , 怎 么<br />

声 明 表 , 还 要 介 绍 一 些 例 子 , 说 明 如 何 在 应 用 程 序 中 处 理 表 。 我 们 会 定 义 几 个 <strong>Prolog</strong> 中 著<br />

名 的 谓 词 , 要 从 递 归 和 过 程 两 种 观 点 来 看 看 表 的 处 理 。 最 后 , 要 了 解 一 下 复 合 表 , 也 就 是 不<br />

同 类 型 元 素 的 表 。<br />

什 么 是 表 ?<br />

在 <strong>Prolog</strong> 里 , 表 是 一 个 对 象 , 它 包 含 了 任 意 数 量 的 其 它 对 象 。 表 大 体 相 当 于 其 它 语 言<br />

中 的 数 组 , 但 与 数 组 不 同 的 是 , 表 不 需 要 在 使 用 前 声 明 它 有 多 大 , 它 也 没 有 元 素 的 索 引 指 针 。<br />

含 有 数 字 1,2 和 3 的 表 写 为 :<br />

[1,2,3]<br />

元 素 的 顺 序 很 要 紧 ,1 是 第 一 个 元 素 ,2 是 第 二 个 ,3 是 第 三 个 。 表 [1,2,3] 和 表 [1,3,2] 是<br />

不 同 的 。<br />

表 中 的 每 一 项 称 作 一 个 元 素 。 要 构 成 一 个 表 的 数 据 结 构 , 需 要 把 表 中 的 每 个 元 素 用 逗 号<br />

分 开 , 并 把 所 有 元 素 放 在 方 括 号 中 。 下 面 是 一 些 表 的 例 子 :<br />

["dog", "cat", "canary"]<br />

["valerie ann", "jennifer caitlin", "benjamin thomas"]<br />

同 样 的 元 素 可 以 多 次 出 现 在 表 里 , 如 :<br />

[ 1, 2, 1, 3, 1 ]<br />

一 个 表 可 以 含 有 任 意 ( 正 数 ) 个 元 素 , 尤 其 是 , 表 还 可 以 没 有 元 素 , 这 样 的 表 叫 空 表 ,<br />

用 [] 表 示 。<br />

表 的 声 明<br />

声 明 一 个 整 数 表 的 域 , 要 用 域 声 明 , 这 样 做 :<br />

domains<br />

integer_list = integer*.<br />

星 号 (*) 的 意 思 是 “ 什 么 什 么 的 表 ”,integer* 的 意 思 是 “ 整 数 的 表 ”。<br />

要 注 意 , 在 <strong>Visual</strong> <strong>Prolog</strong> 中 单 词 list 并 没 有 特 殊 的 意 义 , 可 以 把 表 的 域 叫 成 任 意 的 名 字 ,<br />

是 星 号 而 不 是 名 字 表 示 了 表 域 。 表 里 的 元 素 可 以 任 意 , 甚 至 可 以 是 其 它 的 表 。 但 是 , 一 个 表<br />

里 所 有 的 元 素 必 须 是 同 一 个 域 的 。 如 果 表 里 的 元 素 不 是 标 准 类 型 ( 整 数 、 实 数 、 串 ) 的 , 在<br />

表 域 的 声 明 中 , 就 必 须 对 元 素 做 域 声 明 :<br />

domains<br />

element_list = elements*.<br />

elements = string<br />

13<br />

这 节 主 要 采 用 Thomas Linder Puls(<strong>PDC</strong> <strong>Prolog</strong>) 一 本 手 册 的 内 容 。<br />

117


或 是<br />

domains<br />

integerlist = integer*.<br />

上 面 的 elements 等 号 右 边 必 须 是 单 一 的 标 准 域 类 型 ( 如 integer、real 或 string), 或 是<br />

用 函 子 表 示 的 其 它 的 域 。<strong>Visual</strong> <strong>Prolog</strong> 中 , 不 能 在 表 里 混 杂 使 用 标 准 域 。 下 面 声 明 的 例 子 ,<br />

不 能 表 示 一 个 使 用 了 整 数 、 实 数 和 串 的 表 :<br />

element_list = elements*.<br />

elements = integer; real; string. /* 不 正 确 */<br />

声 明 一 个 表 是 由 整 数 、 实 数 及 串 构 成 的 , 应 该 先 用 函 子 定 义 一 个 单 一 的 域 , 这 个 域 是 由<br />

上 述 三 个 类 型 组 成 的 , 函 子 表 示 了 各 元 素 属 于 哪 个 类 型 , 例 如 :<br />

element_list = elements*.<br />

elements = i(integer); r(real); s(string). /* i, r, 和 s 是 函 子 */<br />

有 关 这 些 , 可 以 参 看 本 章 复 合 表 一 节 的 内 容 。<br />

表 头 和 表 尾<br />

表 其 实 是 一 个 递 归 的 复 合 对 象 , 由 两 部 分 构 成 : 表 的 第 一 个 元 素 构 成 表 头 Head, 剩 下<br />

的 元 素 组 成 一 个 新 的 表 形 成 表 尾 Tail。<br />

一 个 表 的 表 尾 总 是 一 个 表 ; 而 表 头 总 是 一 个 元 素 。<br />

例 如 :<br />

表 [a,b,c] 的 表 头 是 元 素 a, 而 表 尾 是 表 [b,c]。<br />

一 直 这 样 分 解 下 去 , 表 里 只 有 一 个 元 素 时 , 再 分 解 是 什 么 结 果 呢 ? 答 案 是 :<br />

表 [c] 的 表 头 是 元 素 c, 而 表 尾 是 表 []。 空 表 也 是 一 个 表 , 只 不 过 是 空 的 而 已 。<br />

用 表 头 和 表 尾 的 方 式 分 解 表 , 最 终 总 能 得 到 一 个 空 表 。 空 表 无 法 再 分 成 表 头 和 表 尾 。<br />

这 就 是 说 , 从 概 念 上 来 讲 , 表 与 其 它 复 合 对 象 一 样 具 有 树 结 构 。 表 [a,b,c,d] 的 树 结 构 是 :<br />

表<br />

/ \<br />

a 表<br />

/ \<br />

b 表<br />

/ \<br />

c 表<br />

/ \<br />

d []<br />

这 个 结 构 可 以 这 样 来 解 释 , 表 [a,b,c,d] 是 由 元 素 a 和 表 [b,c,d] 构 成 的 。 而 表 [b,c,d] 则 是 由<br />

元 素 b 和 表 [c,d] 构 成 的 , 等 等 。 还 有 , 只 有 一 个 元 素 的 表 如 [a] 与 它 所 含 的 元 素 a 是 不 一 样 的 ,<br />

因 为 [a] 其 实 也 是 复 合 数 据 结 构 :<br />

表<br />

/ \<br />

a []<br />

118


表 处 理<br />

<strong>Prolog</strong> 提 供 了 一 种 方 法 来 明 显 地 区 分 表 头 和 表 尾 。 分 隔 表 中 的 元 素 是 用 逗 号 , 而 分 隔 表<br />

头 与 表 尾 则 是 用 一 个 竖 线 (|), 如 :[a,b,c] 与 [a|[b,c]] 是 等 价 的 。 用 这 样 的 方 法 , 可 以 把 一<br />

个 表 分 解 成 表 头 和 表 尾 , 分 解 过 程 可 以 连 续 进 行 :[a|[b,c]] 与 [a|[b|[c]]] 是 一 样 的 , 后 者 还<br />

可 以 再 分 解 为 [a|[b|[c|[]]]]。<br />

还 可 以 把 逗 号 与 竖 线 两 种 分 隔 符 结 合 起 来 用 , 但 竖 线 必 须 是 最 后 一 个 分 隔 符 。 比 如 , 可<br />

以 把 [a,b,c,d] 写 成 [a,b|[c,d]], 这 里 头 部 有 两 个 元 素 , 但 我 们 并 不 把 它 叫 做 表 头 。 下 面 是 一 些<br />

例 子 :<br />

表 头 尾<br />

['a', 'b', 'c'] 'a' [ 'b', 'c']<br />

[ 'a' ] 'a' [ ]<br />

[ ] 不 确 定 不 确 定<br />

[[1, 2, 3], [2, 3, 4], []] [1, 2, 3] [[2, 3, 4], []]<br />

表 1 表 头 和 表 尾 :<br />

表 2 是 几 个 表 的 合 一 性 :<br />

List1 List2 变 量 绑 定 的 子 句 :<br />

List1=List2<br />

[X,Y,Z] [egbert,eats,icecream] X=egbert, Y=eats, Z=icecream<br />

[7] [X|Y] X=7, Y=[]<br />

[1,2,3,4] [X,Y|Z] X=1, Y=2, Z=[3,4]<br />

[1,2] [3 | X] 失 败<br />

表 2<br />

表 的 合 一<br />

10.3 表 和 递 归<br />

14<br />

因 为 表 就 是 一 个 递 归 的 复 合 数 据 结 构 , 所 以 需 要 用 递 归 的 算 法 来 处 理 它 。 处 理 表 最 基 本<br />

的 方 法 , 就 是 对 各 个 元 素 进 行 需 要 的 处 理 直 到 全 部 处 理 完 毕 。<br />

这 种 算 法 一 般 需 要 两 个 子 句 , 一 个 是 从 表 中 取 出 表 头 并 进 行 相 应 的 处 理 , 这 个 句 子 重 复<br />

进 行 直 到 空 表 ; 另 一 个 对 空 表 进 行 处 理 。 希 望 读 者 能 感 觉 到 递 归 在 这 里 的 便 利 性 。 这 一 节 和<br />

下 一 节 中 有 些 内 容 是 与 10.1 节 中 有 重 复 的 , 可 以 跳 过 去 。<br />

本 节 和 下 一 节 有 一 些 小 程 序 , 这 里 认 为 读 者 已 经 知 道 如 何 在 IDE 中 创 建 程 序 。 有 几 个 步<br />

骤 提 醒 一 下 :<br />

1. 创 建 一 个 新 的 工 程 , 策 略 用 console,<br />

2. 添 加 一 个 类 , 可 以 放 子 句 的 代 码 , 不 要 选 creates objects,<br />

3. 在 .pro 文 件 中 输 入 谓 词 的 子 句 ( 定 义 ),<br />

4. 在 .cl 文 件 中 输 入 谓 词 的 声 明 ,<br />

5. 在 main.pro 文 件 中 改 动 run 谓 词 , 以 便 调 用 .pro 中 的 谓 词 ,<br />

6. 根 据 需 要 , 添 加 写 语 句 。 不 要 忘 了 在 最 后 加 上<br />

_ = stdIO::readChar(), 否 则 会 读 不 到 结 果 。<br />

写 表<br />

如 果 只 是 要 打 印 出 表 的 元 素 , 可 以 这 样 做 :<br />

声 明<br />

predicates<br />

write_a_list : (integer*).<br />

14<br />

这 节 内 容 与 前 节 一 样 取 自 于 Thomas Linder Puls 的 那 本 手 册 。<br />

119


end class<br />

子 句<br />

clauses<br />

write_a_list([]). % 如 果 是 空 表 , 就 到 此 为 止 。<br />

write_a_list([H|T]):‐<br />

stdio::write(H),stdio::nl,<br />

write_a_list(T).<br />

end implement<br />

% 把 表 头 匹 配 给 H, 表 尾 匹 配 给 T, 然 后<br />

% 写 表 头<br />

% 用 表 尾 重 复<br />

run<br />

console::init(),<br />

writelist::write_a_list([1, 2, 3])<br />

_ = stdIO::readChar().<br />

% 假 设 给 的 类 名 是 writelist<br />

程 序 只 是 把 表 的 元 素 写 到 控 制 台 。 这 里 有 两 个 write_a_list 子 句 , 用 自 然 语 言 来 说 是 这<br />

样 的 :<br />

第 一 个 : 写 一 个 空 表 的 话 , 什 么 都 不 用 做 。<br />

第 二 个 : 否 则 , 写 一 个 表 就 是 写 表 头 ( 一 个 元 素 ), 然 后 写 表 尾 ( 一 个 表 )。<br />

它 们 是 这 样 干 活 儿 的 :<br />

第 一 次 时 , 目 标 是 :<br />

writelist::write_a_list([1, 2, 3]).<br />

它 可 以 与 第 二 个 子 句 匹 配 ,H=1,T=[2,3]; 这 样 , 把 “1” 写 出 来 , 再 用 表 尾 递 归 调 用<br />

write_a_list, 就 是 :<br />

writelist::write_a_list([2, 3]).<br />

% 这 是 write_a_list(T).<br />

这 个 递 归 调 用 又 与 第 二 个 子 句 匹 配 ,H=2,T=[3]; 把 “2” 写 出 来 , 再 次 用 表 尾 递 归<br />

调 用 write_a_list:<br />

writelist::write_a_list([3]).<br />

这 次 目 标 与 哪 个 子 句 匹 配 了 呢 ? 要 记 住 , 尽 管 表 [3] 只 有 一 个 元 素 , 但 它 还 是 可 以 分 成<br />

表 头 和 表 尾 的 ; 表 头 是 3, 表 尾 是 一 个 空 表 []。 因 此 , 目 标 还 是 与 第 二 个 子 句 匹 配 ,H=3,T<br />

=[]。 写 出 “3”, 再 递 归 递 归 调 用 write_a_list:<br />

writelist::write_a_list([]).<br />

现 在 , 可 以 看 到 为 什 么 程 序 需 要 第 一 个 子 句 了 。 第 二 个 子 句 与 目 标 不 能 匹 配 了 , 因 为 []<br />

无 法 再 分 解 成 表 头 和 表 尾 。 所 以 , 如 果 这 里 没 有 第 一 个 子 句 , 目 标 就 失 败 了 ; 而 有 了 它 , 目<br />

标 就 匹 配 成 功 , 并 且 也 不 用 再 做 什 么 处 理 。<br />

检 查 一 个 看 是 否 理 解 了 上 面 的 情 况 , 把 第 二 个 子 句 改 成 下 面 的 情 况 , 想 像 一 下 结 果 :<br />

write_a_list([H|T]):‐<br />

write_a_list(T),<br />

stdio::write(H),stdio::nl.<br />

如 果 想 不 明 白 , 把 程 序 改 了 试 试 看 !<br />

表 元 素 的 计 数<br />

现 在 来 考 虑 一 下 如 何 知 道 一 个 表 里 有 多 少 个 元 素 。 表 元 素 的 个 数 称 为 表 的 长 度 。 怎 样 知<br />

道 表 的 长 度 呢 ? 最 简 单 的 办 法 是 数 表 的 元 素 , 简 单 的 算 法 是 这 样 的 :<br />

1. 空 表 [] 的 长 度 是 0,<br />

2. 表 非 空 时 , 可 以 把 表 分 成 表 头 和 表 尾 , 表 的 长 度 就 是 1 加 上 表 尾 的 长 度 。<br />

可 以 做 出 来 吧 ? 在 <strong>Prolog</strong> 中 这 很 简 单 , 只 有 两 个 子 句 :<br />

120


predicates<br />

length_of : (integer* List, integer Length) procedure(i,o).<br />

end class<br />

implement listlength<br />

clauses<br />

length_of([], 0).<br />

length_of( [ _ | Tail], Length):‐<br />

length_of(Tail, TailLength),<br />

Length = TailLength + 1.<br />

end implement<br />

run<br />

console::init(),<br />

listLength::length_of([1, 2, 3], Length),<br />

stdio::write("the length of the list is: ",Length),<br />

_ = StdIO::readchar().<br />

% 给 的 类 名 是 listLength<br />

先 看 一 下 第 二 个 子 句 。 这 里 关 键 的 是 [_ | Tail ], 它 能 与 任 意 非 空 的 表 匹 配 , 把 表 尾 绑 定<br />

给 Tail, 而 表 头 的 值 是 什 么 并 不 重 要 , 只 要 有 , 就 计 一 个 数 。 这 样 , 目 标 :<br />

listlength::length_of([1, 2, 3], Length)<br />

就 会 与 第 二 个 子 句 匹 配 ,Tail=[2,3]。 下 一 步 是 计 算 Tail 的 长 度 , 计 算 完 了 ( 先 别 管 是 怎 么 算<br />

的 ) 后 , 得 到 表 尾 的 长 度 是 2, 加 上 头 的 1,Length 就 绑 定 为 3 了 。<br />

那 么 中 间 是 怎 么 进 行 的 呢 ? 这 一 步 是 计 算 [2,3] 的 长 度 , 就 是 要 满 足 以 下 的 子 目 标 :<br />

listlength::length_of([2, 3], TailLength)<br />

换 句 话 说 , 它 是 length_of 的 递 归 调 用 。 这 个 目 标 与 第 二 个 子 句 匹 配 , 绑 定 子 句 中 的 Tail<br />

为 [3], 同 时 把 目 标 中 的 TailLength 绑 定 给 子 句 中 的 Length。<br />

记 住 , 目 标 中 的 TailLength 不 会 影 响 子 句 中 的 TailLength, 因 为 子 句 的 每 次 递 归 调 用 循 环<br />

都 有 自 己 的 变 量 组 。 到 现 在 , 问 题 变 成 了 计 算 [3] 的 长 度 了 , 它 是 1, 再 加 上 [2,3] 的 长 度 ( 这<br />

个 是 2), 就 是 这 样 的 。<br />

同 样 ,length_of 再 次 递 归 调 用 自 己 , 计 算 [3] 的 长 度 。[3] 的 表 尾 是 [], 所 以 Tail 绑 定 为 [],<br />

然 后 就 是 求 得 [] 的 长 度 , 把 它 加 1, 得 到 [3] 的 长 度 。 这 回 简 单 了 , 目 标 是 :<br />

listLength::length_of([], TailLength)<br />

它 与 第 一 个 子 句 匹 配 , 绑 定 TailLength 为 0。 这 时 , 计 算 机 可 以 把 它 加 上 1, 得 到 [3] 的 长 度 ,<br />

再 回 到 调 用 的 子 句 。 这 时 , 又 加 上 1, 得 到 [2,3] 的 长 度 , 并 回 到 调 用 它 的 子 句 上 去 , 这 个 最<br />

开 始 的 子 句 再 加 上 1, 得 到 表 [1,2,3] 的 长 度 。<br />

乱 ? 希 望 不 是 的 。 下 面 再 总 结 一 下 各 次 调 用 , 我 们 用 下 标 来 区 分 表 示 不 同 子 句 的 同 名 变<br />

量 或 同 一 子 句 各 次 调 用 时 的 变 量 , 这 样 就 可 以 清 楚 些 :<br />

Listlength::length_of([1, 2, 3], Length<br />

1 ).<br />

Listlength::length_of([2, 3], Length<br />

2 ).<br />

Listlength::length_of([3], Length<br />

3 ).<br />

Listlength::length_of([], 0).<br />

Length<br />

3 = 0+1 = 1.<br />

Length<br />

2 = Length 3 +1 = 2.<br />

Length 1 = Length 2 +1 = 3.<br />

尾 递 归<br />

可 以 看 出 上 面 的 length_of 不 是 尾 递 归 的 , 因 为 递 归 调 用 在 子 句 中 不 是 最 后 一 步 。 可 以<br />

写 出 一 个 尾 递 归 的 计 算 表 长 度 的 谓 词 吗 ? 肯 定 行 , 但 要 做 些 工 作 。<br />

上 面 length_of 的 问 题 在 于 , 一 直 到 计 算 完 了 表 尾 的 长 度 , 才 能 计 算 表 的 长 度 。 对 表 尾 ,<br />

121


同 样 又 是 这 个 问 题 。<br />

计 算 表 长 的 新 谓 词 , 需 要 三 个 参 数 。 第 一 个 参 数 是 表 , 每 次 调 用 时 计 算 机 就 会 把 它 削 去<br />

一 点 , 直 到 它 成 了 空 表 ; 第 二 个 参 数 最 后 要 放 结 果 , 开 始 时 它 是 未 绑 定 的 ; 第 三 个 参 数 是 从<br />

0 开 始 的 计 数 器 , 它 在 每 次 调 用 时 加 1。 当 表 空 了 , 就 将 计 数 器 的 内 容 绑 定 给 第 二 个 参 数 ,<br />

得 到 结 果 ( 表 长 )。 谓 词 做 的 是 : 每 次 从 表 里 取 出 一 个 元 素 , 把 它 移 走 并 计 数 。<br />

predicates<br />

length_of : (integer* List, integer Length, integer Counter) procedure(i,o,i).<br />

end class<br />

clauses<br />

length_of([], Result, Result).<br />

length_of([_|T], Result, Counter):‐<br />

NewCounter = Counter + 1,<br />

length_of(T, Result, NewCounter).<br />

end implement<br />

run<br />

console::init(),<br />

listlength::length_of([1, 2, 3], L, 0),<br />

/* start with Counter = 0 */<br />

stdio::write(" L = ", L),<br />

_ = stdIO::readchar().<br />

这 个 版 本 的 length_of 谓 词 比 较 复 杂 了 , 逻 辑 性 也 不 如 前 一 个 。 写 这 样 一 个 谓 词 只 是 要<br />

说 明 : 用 迂 回 的 办 法 , 差 不 多 总 能 找 到 用 尾 递 归 解 决 问 题 的 算 法 。<br />

修 改 表<br />

有 时 需 要 以 一 个 表 为 基 础 建 立 另 一 个 表 。 这 只 要 对 表 中 的 每 个 元 素 进 行 加 工 , 用 新 计 算<br />

的 值 替 换 , 就 可 以 了 。 下 面 的 例 子 , 是 对 表 中 的 每 个 数 加 1 构 成 一 个 新 表 :<br />

predicates<br />

add1 : (integer*, integer*) procedure(i,o).<br />

end class<br />

clauses<br />

add1([], []).<br />

% 边 界 条 件<br />

add1([Head|Tail],[NewHead|NewTail]):‐ % 从 表 中 分 离 出 表 头<br />

NewHead = Head+1, % 第 一 个 元 素 加 1<br />

add1(Tail, NewTail).<br />

% 用 表 尾 再 调 用<br />

end implement<br />

run<br />

console::init(),<br />

listmanager::add1([1,2,3,4], NewList),<br />

stdio::write("Newlist = ", NewList)),<br />

stdIO::readchar().<br />

% 类 名 是 listmanager<br />

用 自 然 语 言 来 解 释 它 , 第 一 个 子 句 是 : 对 空 表 的 所 有 元 素 加 1, 只 是 得 到 另 一 个 空 表 。<br />

122


第 二 个 子 句 是 : 对 非 空 表 的 所 有 元 素 加 1, 先 对 表 头 加 1 并 把 它 做 为 新 的 表 头 , 再 把 表 尾 的<br />

各 个 元 素 加 1 做 为 新 的 表 尾 。 装 入 这 个 程 序 , 用 下 面 的 目 标 试 一 下 :<br />

add1([1,2,3,4], NewList).<br />

返 回 结 果 应 该 是 :<br />

NewList = [2,3,4,5]<br />

再 论 尾 递 归<br />

上 面 的 add1 谓 词 是 尾 递 归 的 吗 ? 如 果 读 者 熟 悉 Lisp 或 Pascal 的 话 , 可 能 会 说 它 不 是 ,<br />

因 为 它 所 做 的 运 算 是 这 样 的 :<br />

• 把 表 分 成 Head 和 Tail<br />

• 把 Head 加 1, 得 到 NewHead<br />

• 递 归 地 对 Tail 的 所 有 元 素 加 1, 得 到 NewTail<br />

• 把 NewHead 和 NewTail 合 起 来 , 得 到 结 果 。<br />

这 不 是 尾 递 归 , 因 为 递 归 调 用 不 是 最 后 一 步 。<br />

但 重 要 的 是 , 对 <strong>Prolog</strong> 来 说 它 不 是 这 么 做 的 。 在 <strong>Visual</strong> <strong>Prolog</strong> 中 ,add1 是 尾 递 归 , 因<br />

为 它 的 步 骤 其 实 是 这 样 的 :<br />

• 把 原 来 表 的 表 头 和 表 尾 绑 定 给 Head 和 Tail<br />

• 把 结 果 的 表 头 和 表 尾 绑 定 给 NewHead 和 NewTail(NewHead 和 NewTail 还 没 有 值 )<br />

• Head 加 1, 得 到 NewHead<br />

• 递 归 地 对 Tail 的 所 有 元 素 加 1, 得 到 NewTail。<br />

当 这 些 都 做 完 了 ,NewHead 和 NewTail 已 经 是 结 果 的 表 头 和 表 尾 了 , 不 需 要 一 个 单 独 的<br />

操 作 把 它 们 合 起 来 。 所 以 , 递 归 调 用 确 实 是 最 后 一 步 。<br />

再 说 表 的 修 改<br />

有 时 也 不 一 定 要 替 换 每 个 元 素 。 下 面 这 个 程 序 对 一 个 表 进 行 扫 瞄 和 拷 贝 , 但 不 拷 贝 负 数 。<br />

它 使 用 的 模 块 名 是 listmanager。<br />

predicates<br />

discard_negatives : (integer*, integer*) procedure(i,o).<br />

end class<br />

clauses<br />

discard_negatives([], []).<br />

discard_negatives([H|T], ProcessedTail):‐<br />

H < 0,<br />

!,<br />

discard_negatives(T, ProcessedTail). % 如 果 H 是 负 数 就 跳 过 去 , 处 理 的 表 尾 不 会 与 H 结 合<br />

discard_negatives([H|T], [H|ProcessedTail]):‐<br />

discard_negatives(T, ProcessedTail). % 这 里 H 将 加 到 处 理 的 表 尾 中<br />

end implement listmanager<br />

run<br />

console::init(),<br />

listmanager::discard_negatives ([2, ‐45, 3, 468], X),<br />

stdio::write(X),<br />

stdIO::readchar().<br />

如 果 目 标 是 :<br />

listmanager::discard_negatives([2, ‐45, 3, 468], X)<br />

结 果 会 是 :<br />

123


[2, 3, 468].<br />

下 面 的 谓 词 把 表 里 各 元 素 拷 贝 两 次 , 构 成 一 个 新 表 :<br />

doubletalk([], []).<br />

doubletalk([H|T], [H, H|DoubledTail]) :‐<br />

doubletalk(T, DoubledTail).<br />

表 的 成 员<br />

假 设 有 一 个 表 , 表 里 有 一 些 名 字 :John、Leonard、Eric、Frank, 我 们 要 用 <strong>Visual</strong> <strong>Prolog</strong><br />

来 研 究 一 下 一 个 给 定 的 名 字 是 否 在 表 中 。 换 句 话 说 , 我 们 需 要 用 “ 一 个 名 字 ” 和 “ 一 个 名 字<br />

的 表 ” 这 两 个 参 数 , 描 述 其 成 员 关 系 。 它 相 应 于 下 面 的 谓 词 :<br />

isMember : (name, name*).<br />

% "name" 是 "name*" 的 成 员<br />

% 这 意 味 着 , 表 name* 是 由 name 类 型 的 成 员 构 成 的<br />

下 面 程 序 中 的 第 一 个 子 句 , 考 查 表 头 。 如 果 表 头 与 要 找 的 名 字 一 样 , 就 可 以 得 出 结 论 说<br />

Name 是 表 的 成 员 。 因 为 表 尾 在 这 里 并 不 关 心 , 所 以 使 用 了 匿 名 变 量 。 由 于 第 一 个 子 句 , 下<br />

面 的 目 标 :<br />

listMan::isMember("john", ["john", "leonard", "eric", "frank"])<br />

可 以 得 到 满 足 。<br />

下 面 是 程 序 :<br />

class listMan<br />

predicates<br />

isMember : (string, string*) determ.<br />

end class<br />

implement listMan<br />

clauses<br />

isMember(Name, [Name|_]) :‐<br />

!.<br />

isMember(Name, [_|Tail]):‐<br />

isMember(Name,Tail).<br />

end implement<br />

clauses<br />

run():‐<br />

console::init(),<br />

listMan::isMember("john", ["john", "leonard", "eric", "frank"]),<br />

!,<br />

stdio::write("Success"), _ = stdIO::readChar()<br />

;<br />

stdio::write("No solution"), _ = stdIO::readChar().<br />

如 果 Name 与 表 头 不 相 同 , 就 需 要 检 查 在 表 尾 里 能 否 找 到 它 , 用 自 然 语 言 说 :<br />

• Name 是 表 的 成 员 , 如 果 Name 是 表 的 第 一 个 元 素 , 或 者<br />

• Name 是 表 的 成 员 , 如 果 Name 是 表 尾 的 成 员 。<br />

第 二 个 子 句 isMember 说 的 就 是 这 种 关 系 , 用 <strong>Visual</strong> <strong>Prolog</strong> 说 就 是 :<br />

isMember(Name, [_|Tail]) :‐<br />

isMember(Name, Tail).<br />

124


表 的 接 续 : 陈 述 式 与 过 程 式 编 程<br />

上 面 的 谓 词 , 工 作 于 两 种 方 式 。 再 考 虑 一 下 它 的 子 句 :<br />

isMember(Name, [Name|_]).<br />

isMember(Name, [_|Tail]) :‐<br />

isMember(Name, Tail).<br />

可 以 从 两 种 不 同 的 观 点 来 看 这 些 子 句 , 一 种 是 陈 述 式 的 , 一 种 是 过 程 式 的 。<br />

从 陈 述 式 的 观 点 看 , 这 些 子 句 说 :<br />

• Name 是 表 的 成 员 , 如 果 表 头 与 Name 相 等<br />

• 如 果 不 等 ,Name 是 表 的 成 员 如 果 它 是 表 尾 的 成 员 。<br />

从 过 程 式 的 观 点 看 , 它 们 是 说 :<br />

• 要 找 一 个 表 的 成 员 , 找 它 的 表 头<br />

• 不 是 , 就 找 它 的 表 尾 的 成 员 。<br />

这 两 种 观 点 , 相 应 于 目 标 :<br />

和<br />

isMember(2, [1, 2, 3, 4]).<br />

isMember(X, [1, 2, 3, 4]).<br />

效 果 上 , 第 一 个 目 标 是 问 <strong>Visual</strong> <strong>Prolog</strong> 某 事 是 否 为 真 , 而 第 二 个 目 标 则 是 让 <strong>Visual</strong> <strong>Prolog</strong><br />

找 出 表 [1,2,3,4] 所 以 的 元 素 。 不 要 把 它 们 搞 混 了 , 两 种 情 况 下 谓 词 是 一 个 , 但 其 作 用 可 以 从<br />

不 同 的 角 度 来 看 。<br />

从 过 程 观 点 看 递 归<br />

<strong>Prolog</strong> 的 迷 人 之 处 在 于 , 从 一 种 观 点 给 一 个 谓 词 构 造 的 子 句 , 它 们 也 可 以 从 其 它 的 观 点<br />

来 工 作 。 为 了 看 清 这 种 二 元 性 , 在 下 面 的 例 子 中 我 们 构 造 一 个 谓 词 , 把 一 个 表 接 在 另 一 个 表<br />

的 后 面 。 谓 词 append 需 要 三 个 参 数 :<br />

append(List1, List2, List3).<br />

它 把 List1 和 List2 结 合 成 List3。 还 是 要 用 递 归 ( 这 次 是 从 过 程 的 观 点 用 ): 如 果 List1 是<br />

空 的 ,List1 和 List2 相 接 的 结 果 与 List2 是 一 回 事 儿 , 用 <strong>Prolog</strong> 说 就 是 :<br />

append([], List2, List2).<br />

如 果 List1 不 是 空 的 , 要 把 List1 和 List2 相 接 形 成 List3, 可 以 这 样 做 : 把 List1 的 表 头 当<br />

成 List3 的 表 头 ( 下 面 的 程 序 中 , 变 量 H 既 用 于 List1 的 表 头 也 用 于 List3 的 表 头 ), 而 List3<br />

的 表 尾 是 Tail3, 这 个 Tail3 又 是 由 List1 的 剩 余 部 分 ( 它 的 表 尾 Tail1) 和 List2 构 成 的 , 用 <strong>Prolog</strong><br />

来 说 就 是 :<br />

append([H|Tail1], List2, [H|Tail3]) :‐<br />

append(Tail1, List2, Tail3).<br />

append 谓 词 的 运 作 是 这 样 的 :List1 非 空 时 , 递 归 规 则 一 次 一 个 地 把 元 素 移 到 list3 中 ;<br />

而 当 list1 是 空 表 时 , 第 一 个 子 句 又 确 保 了 list2 链 在 list3 的 后 部 。<br />

一 个 谓 词 可 以 有 不 同 的 用 法<br />

从 陈 述 的 观 点 看 append 时 , 必 须 要 规 定 好 三 个 表 之 间 的 关 系 。 这 种 关 系 , 在 知 道 了 list1<br />

和 list3 而 不 知 道 list2 时 也 是 成 立 的 。 而 且 , 如 果 只 知 道 list3, 这 种 关 系 也 还 是 成 立 的 。 例<br />

如 , 要 找 到 哪 两 个 表 构 成 了 一 个 已 知 和 表 时 , 可 以 用 这 样 的 目 标 :<br />

125


append(L1, L2, [1, 2, 4]).<br />

对 上 面 的 目 标 ,<strong>Visual</strong> <strong>Prolog</strong> 可 以 得 到 下 面 的 解 :<br />

L1=[], L2=[1,2,4]<br />

L1=[1], L2=[2,4]<br />

L1=[1,2], L2=[4]<br />

L1=[1,2,4], L2=[]<br />

4 Solutions<br />

还 可 以 用 append 找 出 哪 个 表 可 以 与 [3,4] 连 接 后 形 成 [1,2,3,4]。 试 一 下 这 个 目 标 :<br />

append(L1, [3,4], [1,2,3,4]).<br />

推 理 机 找 到 的 解 是 :<br />

L1=[1,2].<br />

append 谓 词 输 入 与 输 出 组 的 关 系 定 义 成 可 以 适 用 于 两 种 要 求 : 按 这 个 关 系 , 既 可 以 问<br />

哪 个 输 出 对 应 于 给 定 的 输 入 , 也 可 以 问 哪 个 输 入 能 符 合 给 定 的 输 出 。<br />

调 用 一 个 谓 词 时 , 该 谓 词 的 参 数 状 态 称 为 流 模 式 。 在 调 用 时 刻 一 个 绑 定 的 或 实 例 化 了 的<br />

参 数 是 输 入 参 数 , 用 符 号 (i) 表 示 ; 一 个 自 由 的 参 数 是 输 出 参 数 , 用 符 号 (o) 表 示 。<br />

append 谓 词 具 有 处 理 任 意 流 模 式 的 能 力 。 当 然 , 并 不 是 所 有 谓 词 都 可 以 用 不 同 流 模 式<br />

进 行 调 用 。 在 <strong>Prolog</strong> 中 , 一 个 可 以 处 理 多 种 流 模 式 的 子 句 , 称 为 可 逆 子 句 。<br />

10.4 特 殊 的 表 谓 词<br />

前 一 节 中 , 我 们 创 建 了 需 要 的 那 些 谓 词 。 其 实 这 是 不 必 要 的 , 我 们 也 说 过 , 有 一 个 叫 list<br />

的 类 , 它 里 面 有 很 多 专 门 用 于 处 理 表 的 很 便 利 的 谓 词 。 在 帮 助 文 件 中 查 找 list, 看 看 list 这<br />

个 类 。<br />

这 一 节 介 绍 经 常 与 表 一 道 使 用 的 三 个 谓 词 , 它 们 在 <strong>Prolog</strong> 中 是 标 准 谓 词 , 不 必 用 list 类<br />

或 其 它 什 么 类 就 可 以 在 程 序 中 调 用 这 些 标 准 谓 词 。<br />

回 溯 和 递 归 是 进 行 重 复 过 程 的 两 种 方 法 。 递 归 与 回 溯 不 同 , 它 可 以 ( 通 过 参 数 ) 把 一 次<br />

递 归 调 用 的 消 息 传 递 给 下 一 次 。 由 于 这 个 原 因 , 递 归 程 序 可 以 在 进 行 时 保 存 特 定 的 结 果 踪 迹<br />

( 如 计 数 器 )。 但 有 一 件 事 回 溯 做 得 到 而 递 归 不 行 : 给 一 个 目 标 找 到 所 有 的 解 。 不 过 , 它 找<br />

解 又 是 一 个 一 个 地 , 有 时 就 让 人 有 些 进 退 两 难 了 : 又 需 要 找 到 所 有 的 解 , 又 需 要 一 次 性 解 决 ,<br />

把 解 组 合 成 一 个 综 合 的 数 据 结 构 。 该 怎 么 做 呢 ?<br />

乐 吧 ,<strong>Visual</strong> <strong>Prolog</strong> 提 供 了 一 种 解 决 这 个 僵 局 的 办 法 。 我 们 已 经 遇 到 过 谓 词 findall/3,<br />

它 是 一 个 标 准 谓 词 , 一 般 调 用 形 式 如 下 :<br />

findall ( ArgumentName, predicateCall, ValuesList )<br />

findall/3 的 流 模 式 为 (i,i,o), 它 的 目 标 是 用 一 个 非 确 定 性 谓 词 , 把 所 有 的 解 组 合 成 一 个 表 。<br />

调 用 者 提 供 前 两 个 参 数 当 作 输 入 ,findall/3 在 第 三 个 参 数 中 返 回 期 望 的 表 。 参 数 说 明 如 下 :<br />

• ArgumentName 指 定 所 列 谓 词 predicateCall 中 的 哪 个 参 数 要 采 集 到 表 ValuesList 中<br />

去 。<br />

• predicateCall 明 确 从 哪 个 谓 词 中 采 集 值 。<br />

• ValuesList 这 是 一 个 输 出 变 量 , 它 是 通 过 回 溯 采 集 到 的 各 个 值 的 表 。<br />

举 个 例 子 , 考 虑 一 下 带 有 下 面 函 子 的 事 实 数 据 库 :<br />

item(string ItemName, string ItemColor).<br />

126


它 可 以 是 一 个 存 货 的 项 目 表 。 每 个 事 实 包 含 了 物 品 的 名 称 及 颜 色 。 如 果 想 要 一 个 所 有 物<br />

品 的 条 目 表 , 可 以 用 下 面 的 调 用 :<br />

findall(ItemName, item(ItemName, _ ), NameList).<br />

这 会 在 变 量 NameList 中 返 回 所 有 物 品 的 一 个 表 。 如 果 只 想 查 看 红 色 的 物 品 , 可 以 这 样 :<br />

findall(ItemName, item(ItemName, "red" ), NameList).<br />

如 果 想 看 看 这 些 存 货 究 竟 有 多 少 种 颜 色 , 可 以 这 样 :<br />

findall(Color, item("coat", Color), ColorList).<br />

findall/3 会 返 回 它 找 到 的 所 有 值 。 这 里 , 相 同 的 颜 色 会 有 重 复 。 在 list 类 中 可 以 找 到 一<br />

个 去 除 重 复 项 的 谓 词 , 需 要 时 可 以 用 它 来 除 去 重 复 的 内 容 。<br />

说 一 下 使 用 findall/3 时 需 要 注 意 的 事 。 这 个 谓 词 通 过 在 谓 词 predicateCall 中 回 溯 直 到 最<br />

终 失 败 而 构 成 一 个 表 ValuesList, 这 个 表 里 是 所 有 的 变 量 ArgumentName 的 实 例 , 而 变 量<br />

ArgumentName 必 须 是 指 定 谓 词 predicateCall 的 一 个 输 出 参 数 。 为 了 让 findall 能 工 作 , 必 须<br />

在 用 户 域 声 明 中 声 明 ValuesList 为 表 域 的 , 也 就 是 说 , 比 如 想 要 得 到 一 张 颜 色 表 , 这 个 表 必<br />

须 要 事 先 声 明 。<br />

findall/3 能 产 生 一 个 表 , 但 还 有 更 一 般 的 方 法 来 生 成 一 个 表 , 这 个 方 法 称 为 表 内 涵 (list<br />

comprehension)。 我 们 先 来 看 一 下 表 内 涵 的 形 式 , 它 是 一 个 用 下 面 方 式 调 用 的 函 数 :<br />

ResultList = [ Exp || Gen ]<br />

注 意 双 竖 线 “||” 在 表 达 式 的 右 边 ! 表 内 涵 是 一 个 表 的 表 达 式 , 这 意 味 着 表 内 涵 调 用 其<br />

实 是 一 个 表 达 式 , 因 而 它 必 须 要 绑 定 给 一 个 变 量 。 我 们 来 细 看 一 下 [ Exp || Gen ]。 在 这 个 表<br />

达 式 中 ,Gen 通 常 是 一 个 非 确 定 性 的 项 , 就 是 说 调 用 它 时 有 可 能 失 败 和 回 溯 。 我 们 可 以 把<br />

Gen 看 成 一 个 函 子 , 这 个 函 子 被 调 用 并 生 成 一 些 值 作 为 解 。 可 以 把 它 与 上 面 的 item 函 子 做<br />

个 比 较 。Exp 是 按 Gen 各 个 解 的 计 算 得 到 的 , 所 有 Exp 的 结 果 被 采 集 到 一 个 表 里 , 在 这 个 表<br />

中 , 第 一 个 元 素 就 是 相 应 于 Gen 的 第 一 个 解 的 Exp, 第 二 个 元 素 是 就 相 应 于 Gen 的 第 二 个 解<br />

的 Exp, 等 等 。 这 个 表 是 表 内 涵 项 的 结 果 , 返 回 时 这 个 表 约 束 给 了 ResultList。Exp 的 模 式 必<br />

须 是 procedure 的 ( 或 erroneous)。Exp 和 Gen 两 者 的 截 断 作 用 域 是 独 立 的 、 互 不 相 干 的 。<br />

可 以 这 样 解 释 表 内 涵 : 给 我 一 个 Gen 那 样 的 Exp 的 表 。<br />

我 们 来 看 一 个 例 子 。 假 设 有 一 个 表 L, 它 含 有 一 些 数 。 如 果 我 们 要 把 这 个 表 中 的 偶 数 取<br />

出 来 , 可 以 这 样 做 :<br />

EvenNumberList = [ X || isMember(X, L), X div 2 = 0 ]<br />

上 面 的 公 式 , 用 自 然 语 言 来 表 达 就 是 : 把 X 的 表 放 到 EvenNumberList 中 , 而 X 是 那 些<br />

在 表 L 中 的 偶 数 。 它 与 findall/3 相 仿 , 但 这 里 我 们 多 加 了 一 个 条 件 。 再 有 一 个 例 子 , 假 设 表<br />

内 涵 是 :<br />

OddNumberList = [ X + 1 || isMember(X, L), X div 2 = 0 ]<br />

这 里 , 采 集 表 达 式 更 复 杂 了 , 用 自 然 语 言 可 以 这 样 说 : 把 X 的 表 放 到 OddNumberList<br />

中 , 而 X 是 那 些 在 表 L 中 的 偶 数 ; 不 过 在 把 X 放 到 采 集 表 中 之 前 , 先 把 它 加 1。 这 样 一 来 ,<br />

我 们 就 得 到 了 一 个 奇 数 的 表 ( 注 意 , 不 是 L 表 中 的 奇 数 )。 相 同 的 结 果 还 可 以 这 样 做 :<br />

OddNumberList = [ Y || isMember(X, L), X div 2 = 0 , Y = X+1 ]<br />

用 自 然 语 言 说 就 是 :Y 的 表 是 这 样 的 : 对 表 L 中 的 偶 数 X 加 1 就 是 Y。<br />

表 内 涵 是 通 用 化 的 findall/3。findall 的 原 理 形 式 是 :<br />

findall(V, Gen, L)<br />

它 可 以 等 效 地 写 成 :<br />

L = [ V || Gen ]<br />

findall 与 表 内 涵 的 主 要 差 别 是 ,findall 是 一 个 谓 词 , 它 把 结 果 约 束 给 一 个 变 量 ; 而 表 内<br />

涵 是 一 个 表 达 式 。<br />

我 们 把 表 内 涵 用 在 一 个 程 序 里 。 重 复 一 下 , 当 我 们 说 :<br />

127


ListParam = [ VarName || mypredicate]<br />

它 的 意 思 是 :<br />

• 第 一 个 参 数 ,VarName, 规 定 了 在 指 定 的 谓 词 中 的 哪 个 参 数 要 采 集 到 一 个 表 里 ,<br />

• 第 二 个 参 数 ,mypredicate, 表 示 要 从 中 采 集 值 的 谓 词 ,<br />

• 输 出 ListParam, 是 一 个 变 量 , 用 它 来 放 置 通 过 回 溯 采 集 到 的 值 的 表 。<br />

下 面 一 个 程 序 是 使 用 表 内 涵 来 打 印 一 些 人 的 平 均 年 龄 。<br />

class listcom<br />

domains<br />

name = string.<br />

address = string.<br />

age = integer.<br />

predicates<br />

person : (name, address, age) multi (o,o,o).<br />

sumlist : (age*, age, integer) procedure(i,o,o).<br />

end class listcom<br />

implement listcom<br />

clauses<br />

sumlist([],0,0).<br />

sumlist([H|T], Sum, N):‐<br />

sumlist(T, S1, N1),<br />

Sum=H+S1, N=1+N1.<br />

person("Sherlock Holmes", "22B Baker Street", 42).<br />

person("Pete Spiers", "Apt. 22, 21st Street", 36).<br />

person("Mary Darrow", "Suite 2, Omega Home", 51).<br />

end implement listcom<br />

in main.pro:<br />

run():‐<br />

console::init(),<br />

L = [ Age || listcom::person(_, _, Age)],<br />

listcom::sumlist(L, Sum, N),<br />

Ave = Sum/N,<br />

stdio::write("Average=", Ave, ". "),<br />

_X = stdIO::readchar().<br />

这 个 程 序 中 的 表 内 涵 子 句 创 建 了 一 个 表 L, 它 是 从 谓 词 person 中 采 集 到 的 所 有 的 年 龄 。<br />

如 果 想 采 集 所 有 42 岁 的 人 , 可 以 使 用 下 面 的 子 目 标 :<br />

List = [ Who || my::person(Who, _, 42) ]<br />

而 下 面 这 个 公 式 可 以 创 建 一 个 正 数 的 表 :<br />

List = [ X || X = list::getMember_nd([2,‐8,‐3,6]), X > 0]<br />

128


式 中 的 getMember_nd 是 list 类 中 的 一 个 谓 词 , 它 能 取 出 一 个 表 中 的 成 员 。<br />

最 后 , 来 看 另 一 个 结 构 。 我 们 以 前 在 打 印 一 些 数 的 表 时 , 用 过 这 样 的 谓 词 形 式 :<br />

write_a_list(List) :‐<br />

member(Element, List),<br />

write(Element), nl()<br />

fail.<br />

write_a_list(_).<br />

它 把 List 中 的 每 个 元 素 都 写 出 来 , 再 明 白 些 说 , 它 把 List 中 的 每 个 元 素 都 写 出 来 , 而 这<br />

“ 每 个 元 素 ” 都 由 是 非 确 定 性 谓 词 member() 产 生 的 一 个 一 个 的 解 。 为 生 成 一 个 完 整 的 表 ,<br />

我 们 让 这 个 谓 词 失 败 了 。 尽 管 这 有 些 不 太 讲 究 , 但 在 这 种 情 况 下 它 写 出 了 我 们 想 要 的 内 容 ,<br />

所 以 失 败 时 写 成 功 了 。 还 需 要 另 一 个 子 句 来 防 止 程 序 失 败 。VIP 的 7 版 本 中 引 入 了 foreach<br />

结 构 , 是 这 样 的 :<br />

…,<br />

foreach nondeterm_predicate(X) do<br />

write(X), nl()<br />

end foreach,<br />

…<br />

foreach 结 构 是 要 成 为 fail 循 环 的 替 代 品 , 在 上 面 的 例 子 中 可 以 这 样 用 :<br />

Write_a_list(List) :‐<br />

foreach member(X,List) do<br />

write(X), nl()<br />

end foreach.<br />

这 个 谓 词 写 了 表 , 并 且 成 功 , 所 以 不 再 需 要 另 外 的 子 句 。 在 这 种 情 况 下 甚 至 不 需 要 特 殊<br />

谓 词 了 ,foreach 结 构 可 以 当 成 一 个 子 目 标 插 入 在 另 一 个 谓 词 中 :<br />

…,<br />

foreach nondeterm_predicate(X) do<br />

Process(X),<br />

end foreach,<br />

…<br />

当 循 环 完 成 时 ,foreach 结 构 就 成 功 了 。 这 样 , 计 算 可 以 紧 接 着 foreach 结 构 之 后 继 续 进<br />

行 。 而 用 fail 子 句 时 , 就 需 要 小 心 , 得 让 程 序 紧 接 着 fail 之 后 进 行 , 这 点 在 foreach 结 构 中 不<br />

必 操 心 , 它 自 己 会 打 理 这 个 问 题 。 来 看 下 面 的 程 序 , 打 印 完 了 之 后 它 会 继 续 :<br />

…,<br />

foreach p_nd(X) do<br />

write(X), nl()<br />

end foreach,<br />

write("I will continue here"),<br />

...<br />

foreach 结 构 还 可 以 嵌 套 。 例 如 :<br />

foreach member(L, LL) do<br />

write(">\n")<br />

end foreach.<br />

129


LL 是 表 的 一 个 表 , 外 层 的 foreach 结 构 从 这 个 表 的 表 中 生 成 各 个 表 , 每 个 L 都 是 一 个 表 。<br />

内 层 foreach 循 环 以 L 中 的 元 素 重 复 地 约 束 X, 这 样 就 写 出 了 各 个 元 素 。<br />

有 几 件 事 要 注 意 :<br />

• 关 键 字 do 与 end foreach 之 前 都 没 有 逗 号 ,<br />

• foreach 结 构 中 体 的 截 断 作 用 域 仅 限 于 体 内 , 所 以 在 体 中 有 截 断 的 话 并 不 影 响 重 复 ,<br />

• foreach 总 是 成 功 ( 或 引 起 异 常 ), 并 且 在 它 完 成 之 后 没 有 约 束 另 外 的 变 量 。 因 此 ,<br />

foreach 结 构 只 能 使 用 它 的 辅 助 功 能 ( 副 作 用 )。<br />

作 者 都 有 点 儿 认 为 后 面 这 几 句 不 好 懂 , 抱 歉 。 只 要 记 住 :foreach 结 构 用 于 处 理 表 元 素<br />

简 单 而 强 悍 , 这 就 可 以 了 !<br />

10.5 排 序<br />

前 节 中 我 们 说 过 , 表 中 元 素 的 顺 序 是 很 要 紧 的 。 这 给 我 们 带 来 了 排 序 这 个 题 目 。 如 果 对<br />

这 个 问 题 没 有 兴 趣 , 可 以 跳 过 这 一 节 。 排 序 是 有 标 准 谓 词 可 用 的 , 不 过 , 对 一 个 表 进 行 排 序<br />

还 是 挺 有 意 思 的 , 因 为 它 可 以 使 我 们 进 一 步 洞 察 表 及 递 归 , 本 书 这 一 节 之 所 以 要 说 它 就 是 这<br />

个 原 因 。<br />

有 很 多 种 方 法 对 一 个 表 进 行 排 序 , 这 节 我 们 讲 四 种 。 其 中 三 种 ,insertSort、selectSort<br />

和 bubbleSort, 只 是 好 玩 , 因 为 它 们 并 非 十 分 有 效 。 第 四 种 ,quickSort, 是 很 有 名 的 , 必 须<br />

了 解 。 还 有 一 个 限 制 , 我 们 只 用 整 数 表 来 进 行 讨 论 , 但 它 应 该 不 是 问 题 。 我 们 要 创 建 一 个 名<br />

为 ch10SortList 的 程 序 , 策 略 是 console, 工 程 中 还 要 创 建 一 个 没 有 对 象 的 类 ( 一 个 模 块 ),<br />

名 为 sort, 用 这 个 模 块 放 所 有 的 谓 词 。 与 通 常 一 样 , 声 明 放 在 sort.cl 中 , 而 谓 词 的 子 句 是 在<br />

sort.pro 中 。<br />

第 一 种 方 法 , 是 把 未 排 序 表 中 的 元 素 一 个 一 个 取 出 来 , 放 在 一 个 新 表 中 适 当 的 位 置 上 。<br />

当 所 有 元 素 都 放 在 新 表 里 了 , 排 序 就 完 成 了 。 在 <strong>Prolog</strong> 中 , 我 们 可 以 这 样 做 : 把 未 排 序 的<br />

表 头 取 出 来 , 放 在 一 个 开 始 是 空 表 的 新 表 中 适 当 位 置 上 , 然 后 递 归 取 下 一 个 元 素 , 也 就 是 未<br />

排 序 表 尾 的 第 一 个 元 素 。 这 个 方 法 叫 插 入 排 序 法 , 我 们 要 用 谓 词 insertSort 来 做 , 显 然 它 有<br />

两 个 参 数 , 一 个 是 未 排 序 的 表 作 输 入 , 另 一 个 是 返 回 作 为 输 出 的 排 序 好 的 表 , 这 样 谓 词 的 声<br />

明 就 是 :<br />

insertsort : (integer*, integer*) procedure (i,o).<br />

这 个 谓 词 有 两 个 子 句 , 第 一 个 是 对 一 个 空 表 排 序 :<br />

insertSort([], []).<br />

这 很 简 单 , 对 一 个 空 表 排 序 , 得 到 的 还 是 空 表 。 另 一 种 替 代 形 式 是 对 只 有 一 个 元 素 的 表<br />

进 行 排 序 :<br />

insertSort([X], [X]).<br />

当 表 里 的 元 素 超 过 一 个 时 , 可 以 用 下 面 的 步 骤 来 排 序 :<br />

1. 从 未 排 序 的 表 中 取 走 表 头<br />

2. 对 表 尾 进 行 排 序<br />

3. 把 取 出 的 表 头 放 在 排 序 过 的 表 尾 中 的 适 当 位 置 上 。<br />

用 <strong>Prolog</strong> 来 说 , 就 是 :<br />

insertSort([], []) :‐ !.<br />

insertSort([Head | Tail], SortedList) :‐<br />

insertSort( Tail, SortedTail),<br />

insert(Head, SortedTail, SortedList), !.<br />

% 取 一 个 未 排 序 表 , 生 成 排 序 过 的 表<br />

% 对 表 尾 进 行 排 序<br />

% 在 排 序 好 的 表 尾 中 适 当 位 置 插 入 表 头<br />

作 者 对 变 量 喜 欢 用 长 名 字 , 这 样 读 程 序 时 知 道 它 代 表 什 么 , 所 以 这 里 用 了 SortedList 和<br />

SortedTail 这 样 的 名 字 。 当 然 也 可 以 用 像 L1、L2 这 样 的 名 字 。<br />

当 然 还 需 要 有 插 入 一 个 元 素 的 谓 词 , 我 们 用 insert 做 这 个 谓 词 名 , 声 明 如 下 :<br />

Insert : (integer, integer*, integer*) procedure (i,i,o).<br />

130


这 个 谓 词 用 三 个 参 数 , 第 一 个 是 要 插 入 的 元 素 , 第 二 个 是 元 素 要 插 入 的 那 个 表 , 而 第 三<br />

个 是 结 果 表 。 它 也 是 个 递 归 谓 词 , 开 始 是 一 个 停 止 规 则 , 插 入 一 个 元 素 到 空 表 里 :<br />

insert(Element, [], [Element]) := !.<br />

不 是 空 表 怎 么 办 呢 ? 这 就 要 看 待 插 入 的 元 素 和 表 中 的 第 一 个 元 素 ( 表 头 ) 的 情 况 了 。 要<br />

记 住 , 我 们 要 插 入 元 素 的 这 个 表 已 经 是 排 序 好 了 的 , 所 以 , 如 果 待 插 入 元 素 小 于 等 于 表 头 ,<br />

则 它 应 该 放 在 表 头 前 面 , 用 <strong>Prolog</strong> 说 就 是 :<br />

insert(Element, [Head | Tail], [Element, Head | Tail]) :‐<br />

Element


selectSort(UnsortedRestOfList, SortedRestOfList).<br />

% 对 剩 余 的 表 排 序<br />

为 找 出 表 中 最 小 的 元 素 , 使 用 一 个 递 归 谓 词 smallest, 声 明 是 这 样 的 :<br />

smallest : (integer*, integer) procedure (i,o).<br />

它 要 一 个 表 , 并 返 回 最 小 元 素 。 我 们 用 三 个 子 句 , 第 一 个 是 表 里 只 有 一 个 元 素 时 :<br />

smallest([Element], Element).<br />

有 多 个 元 素 时 , 我 们 取 出 表 头 , 接 着 找 表 尾 里 的 最 小 元 素 , 把 它 与 表 头 比 , 如 果 表 头 小 ,<br />

那 表 头 就 是 表 里 的 最 小 元 素 , 如 果 不 是 , 那 表 尾 取 出 的 那 个 就 是 表 里 的 最 小 元 素 。 用 <strong>Prolog</strong><br />

说 就 是 :<br />

smallest([Head | Tail], Head) :‐<br />

smallest(Tail, Element),<br />

Head


ubble(List, List1), !,<br />

bubbleSort(List1, SortedList).<br />

bubbleSort(SortedList, SortedList).<br />

所 以 , 关 键 在 谓 词 bubble/2, 声 明 是 这 样 :<br />

bubble : (integer*, integer*) determ (i,o).<br />

子 句 是 :<br />

bubble( [X,Y | Rest], [Y,X | Rest] ) :‐<br />

X > Y ,!.<br />

bubble( [Z|Rest], [Z | Rest1]) :‐<br />

bubble(Rest, Rest1), !.<br />

第 一 个 子 句 交 换 头 两 个 元 素 的 位 置 , 如 果 第 一 个 比 第 二 个 大 的 话 。 如 果 不 大 , 第 二 个 子<br />

句 就 保 持 第 一 个 元 素 的 位 置 , 继 续 对 余 下 的 表 冒 泡 。<br />

最 后 , 加 上 run/0 谓 词 子 句 进 行 冒 泡 排 序 。<br />

sort::bubblesort(UnsortedList, BubSortedList),<br />

stdIO::write("The sorted list (bubblesort) is: ",BubSortedList),<br />

stdIO::nl,<br />

运 行 程 序 看 看 。<br />

这 三 种 方 法 都 不 是 很 有 效 率 , 排 序 过 程 中 表 要 被 搜 索 多 次 。 称 为 快 速 排 序 法 的 方 法 是 个<br />

较 好 的 方 法 , 它 的 步 骤 如 下 :<br />

1. 取 一 个 元 素 , 随 便 一 个 , 所 以 最 简 单 的 就 是 取 未 排 序 表 的 表 头<br />

2. 把 剩 下 的 元 素 放 在 两 个 表 里 , 一 个 放 较 小 的 元 素 , 一 个 放 较 大 的 元 素<br />

3. 对 两 个 表 中 的 每 个 元 素 重 复 上 两 个 步 骤 , 直 到 一 个 表 里 只 有 一 个 元 素<br />

4. 持 续 不 断 地 把 元 素 分 成 小 的 和 大 的 , 这 个 表 就 已 经 排 序 好 了 。<br />

可 以 有 些 难 理 解 。 手 工 来 试 试 表 [4,2,5,3,1,2,6,4]:<br />

1. 第 一 个 元 素 是 4<br />

2. 用 它 把 表 分 成 了 两 个 :[2,3,1,2,4] 和 [5,6]( 这 里 把 相 等 的 4 放 在 小 表 里 了 )<br />

3. 取 小 表 , 第 一 个 元 素 是 2<br />

4. 用 2 又 把 表 分 成 了 两 个 , 结 果 是 [1,2],[3,4]<br />

5. 再 取 小 表 , 第 一 个 元 素 是 1<br />

6. 用 1 又 把 表 分 成 了 两 个 , 结 果 是 [],[2], 因 为 结 果 不 是 空 就 是 只 有 一 个 元 素 的 表 ,<br />

这 一 分 支 就 可 以 结 束 了 , 可 以 进 行 另 一 分 支 , 也 就 是 表 [3,4]。<br />

如 上 所 述 , 再 进 行 下 去 , 得 到 一 个 树 结 构 , 红 色 的 元 素 是 分 支 的 末 端 , 它 们 是 叶 , 把 它<br />

们 合 起 来 就 是 排 序 好 的 表 。<br />

[4,2,5,3,1,2,6,4]<br />

/ | \<br />

[2,3,1,2,4] 4 [5,6]<br />

/ | \ / | \<br />

[1,2] 2 [3,4] [] 5 [6]<br />

/ | \ / | \<br />

[] 1 [1] [] 3 [4]<br />

这 时 , 沿 着 树 叶 ( 红 色 数 字 ) 从 左 到 右 , 忽 略 那 些 空 表 , 就 得 到 了 排 序 好 的 元 素 。<br />

我 们 用 这 个 原 理 来 做 个 谓 词 quickSort, 和 前 面 一 样 , 这 个 谓 词 要 有 一 个 未 排 序 的 表 和<br />

产 生 一 个 排 序 过 的 表 , 声 明 是 熟 悉 的 了 :<br />

quickSort : (integer*, integer*) procedure (i,o).<br />

从 声 明 中 可 以 看 出 这 个 谓 词 是 递 归 的 , 第 一 个 子 句 不 会 太 难 , 就 是 对 空 表 排 序 :<br />

133


quicksort( [], []) :‐ !.<br />

对 非 空 的 表 就 麻 烦 些 了 , 必 须 先 要 取 出 第 一 个 元 素 并 把 剩 下 的 表 分 成 两 个 新 表 , 一 个 是<br />

较 小 ( 及 相 等 ) 元 素 的 , 一 个 是 较 大 元 素 的 , 这 样 :<br />

quicksort( [SplitElem | Tail], SortedList) :‐<br />

split(SplitElem, Tail, LowerList, UpperList),<br />

/* 首 先 把 表 尾 用 SplitElem 分 成 两 个 表 */<br />

quicksort(LowerList, SortedLowerList),<br />

/* 然 后 quickSort 小 表 成 排 序 好 的 表 */<br />

quicksort(UpperList, SortedUpperList),<br />

/* 再 quickSort 大 表 成 排 序 好 的 表 */<br />

append(SortedLowerList, [SplitElem | SortedUpperList], SortedList).<br />

/* 把 排 序 过 的 表 及 各 个 单 独 的 元 素 放 在 一 个 表 中 */<br />

可 以 看 到 , 分 裂 是 这 里 的 关 键 , 我 们 一 直 把 表 分 裂 到 每 个 表 只 有 一 个 元 素 或 成 了 空 表 。<br />

分 裂 时 我 们 使 用 谓 词 split, 下 面 是 它 的 声 明 :<br />

split : (integer, integer*, integer*, integer*) procedure (i,i,o,o).<br />

它 有 一 个 整 数 和 一 个 表 做 输 入 , 生 成 两 个 表 , 这 两 个 表 可 以 在 下 一 轮 中 再 分 裂 。 这 个 谓<br />

词 也 是 递 归 的 , 开 始 时 是 对 空 表 分 裂 , 这 时 用 什 么 元 素 来 分 裂 是 无 所 谓 的 , 子 句 是 :<br />

split( _, [], [], []) :‐ !.<br />

对 非 空 表 , 必 须 用 待 分 裂 表 (Tail) 的 第 一 个 元 素 ( 表 头 ) 与 分 裂 元 素 相 比 较 , 表 头 小<br />

于 等 于 分 裂 元 素 时 , 它 就 被 放 在 小 表 里 , 如 果 不 是 , 我 们 要 用 第 二 个 子 句 把 它 放 在 大 表 里 ,<br />

用 <strong>Prolog</strong> 来 说 就 是 :<br />

split(SplitElem, [Head | Tail], [Head | LowerList], UpperList) :‐<br />

Head


stdIO::write("The sorted list (insert sort) is: ",InsortedList),<br />

stdIO::nl,<br />

sort::selectsort(UnsortedList, SelSortedList),<br />

stdIO::write("The sorted list (select sort) is: ",SelsortedList),<br />

stdIO::nl,<br />

sort::bubblesort(UnsortedList, BubSortedList),<br />

stdIO::write("The sorted list (bubble sort) is: ",BubSortedList),<br />

stdIO::nl,<br />

sort::quicksort(UnsortedList, QuickSortedList),<br />

stdIO::write("The sorted list (quickSort) is: ",QuickSortedList),<br />

_X = stdIO::readchar(),<br />

succeed().<br />

这 样 应 该 可 以 了 。 如 果 感 觉 没 看 到 排 序 的 过 程 很 不 爽 , 那 可 以 在 合 适 的 地 方 插 入 write<br />

语 句 。 在 上 面 的 代 码 中 , 用 stdIO::write() 写 出 表 和 其 它 类 型 的 变 量 , 它 使 程 序 有 趣 多 了 。<br />

10.6 小 结<br />

本 章 的 重 点 :<br />

1. 表 可 以 含 有 任 意 数 量 的 元 素 , 用 已 经 定 义 过 的 域 的 后 面 加 个 星 号 声 明 表<br />

2. 表 是 复 合 对 象 的 递 归 结 构 , 这 个 复 合 对 象 是 由 表 头 和 表 尾 构 成 的 。 表 头 是 表 的 第 一<br />

个 元 素 而 表 尾 是 表 除 去 第 一 个 元 素 后 剩 下 的 部 分 。 表 尾 永 远 是 个 表 , 表 头 是 一 个 元<br />

素 。 表 可 以 没 有 元 素 , 也 可 以 有 多 个 元 素 , 没 有 元 素 的 表 是 空 表 , 记 为 []<br />

3. 表 中 的 元 素 可 以 是 任 意 的 , 包 括 其 它 的 表 ; 一 个 表 中 所 有 的 元 素 必 须 属 于 同 一 个 域 。<br />

元 素 的 域 声 明 必 须 是 如 下 形 式 的 :<br />

domains<br />

element_list = elements*.<br />

elements = ....<br />

这 里 的 elements=…, 等 号 后 是 标 准 域 ( 整 数 、 实 数 等 等 ) 之 一 或 是 一 组 替 代 的 不<br />

同 的 函 子 (int(integer),rl(real),smb(string), 等 等 ), 用 分 号 分 隔 。 在 <strong>Visual</strong> <strong>Prolog</strong> 的 表<br />

中 使 用 混 合 类 型 , 只 能 通 过 把 它 们 放 在 复 合 对 象 / 函 子 中 来 实 现<br />

4. 可 以 用 分 隔 符 ( 逗 号 ,[, 和 |) 把 表 分 成 表 头 与 表 尾 , 如 对 表 [a,b,c,d], 可 以 这 样<br />

来 表 示 :[a|[b, c, d]] 或 [a, b|[c, d]] 或 [a, b, c|[d]] 或 [a|[b|[c, d]]] 或 [a|[b|[c|[d]]]] 甚<br />

至 是 [a|[b|[c|[d|[]]]]]。 注 意 最 后 一 个 写 法 中 的 空 表<br />

5. 表 处 理 由 递 归 地 取 表 头 ( 并 对 其 进 行 某 种 处 理 ) 构 成 , 一 直 进 行 到 表 成 了 空 表<br />

6. 标 准 表 谓 词 在 list 类 中<br />

7. <strong>Visual</strong> <strong>Prolog</strong> 提 供 了 一 种 内 建 的 结 构 , 表 内 涵 , 它 把 一 个 目 标 做 为 它 的 一 个 参 数 并<br />

采 集 这 个 目 标 所 有 的 解 到 一 个 表 中 。 它 的 一 般 形 式 是 :<br />

Result = [ Argument || myPredicate(Argument) ]<br />

8. 因 为 <strong>Visual</strong> <strong>Prolog</strong> 规 定 表 中 所 有 元 素 必 须 是 同 一 域 的 , 所 以 要 创 建 一 个 存 放 不 同 类<br />

型 元 素 的 表 就 要 使 用 函 子<br />

9. 对 表 排 序 可 以 有 多 种 方 法 , 并 不 是 每 一 种 方 法 都 很 有 效 。<br />

135


第 11 章 读 、 写 、 流 和 文 件<br />

15<br />

计 算 机 程 序 要 能 与 用 户 通 信 才 有 用 , 计 算 机 一 启 动 , 用 户 就 需 要 登 录 到 系 统 中 去 。 所 以 ,<br />

一 开 始 计 算 机 就 需 要 知 道 从 哪 儿 读 用 户 的 输 入 及 把 给 用 户 的 消 息 送 到 哪 儿 去 。 在 开 发 计 算 的<br />

同 时 也 在 开 发 与 用 户 的 通 信 。 本 章 我 们 来 看 看 程 序 与 用 户 的 通 信 问 题 。 要 理 解 VIP 中 如 何 处<br />

理 输 入 输 出 , 需 要 了 解 一 些 计 算 机 输 入 输 出 的 历 史 背 景 , 所 以 这 章 不 光 要 说 输 入 输 出 的 谓 词 。<br />

不 过 , 作 者 本 人 也 是 面 向 对 象 编 程 的 新 手 , 这 里 只 是 把 不 同 材 料 中 的 内 容 抓 在 一 起 , 然 后 给<br />

出 解 释 , 所 以 可 能 说 得 不 对 , 或 是 不 全 对 。<br />

11.1 控 制 台<br />

控 制 台 是 计 算 机 的 入 口 和 显 示 设 备 , 它 可 以 显 示 系 统 管 理 消 息 、 系 统 启 动 消 息 , 让 用 户<br />

登 录 进 入 系 统 。 它 是 一 个 物 理 设 备 , 看 得 见 摸 得 着 , 简 单 的 文 字 输 入 和 文 字 显 示 设 备 就 是 一<br />

个 键 盘 和 一 个 显 示 器 。<br />

传 统 计 算 机 中 , 控 制 台 是 一 个 哑 终 端 , 由 一 个 键 盘 和 一 个 字 符 显 示 屏 组 成 , 通 过 串 行 通<br />

信 与 计 算 机 相 连 。 这 样 的 终 端 过 去 一 般 都 是 放 在 一 个 安 全 的 屋 子 里 , 因 为 它 能 用 于 某 些 特 权<br />

功 能 , 如 停 止 系 统 或 是 选 择 从 哪 儿 引 导 系 统 等 。 大 型 计 算 机 , 如 SUN、 惠 普 、IBM 等 , 还 在<br />

使 用 这 样 的 控 制 台 。<br />

在 PC 里 机 , 键 盘 、 鼠 标 和 显 示 器 与 计 算 机 相 连 ( 也 可 以 说 它 们 就 是 计 算 机 的 一 部 分 )<br />

用 作 控 制 台 。 但 这 个 “ 控 制 台 ” 更 复 杂 , 加 了 鼠 标 , 显 示 器 不 仅 可 以 显 示 文 字 而 且 还 可 以 显<br />

示 图 形 。 当 说 到 控 制 台 时 , 要 明 白 我 们 是 指 一 个 简 单 的 能 接 收 键 盘 的 输 入 并 显 示 输 出 字 符 的<br />

非 图 形 设 备 , 在 Windows 里 , 可 以 把 它 看 作 是 MsDOS 的 命 令 行 提 示 窗 口 。Windows 的 这 个<br />

控 制 台 , 是 它 应 用 程 序 接 口 (API) 系 统 中 控 制 台 程 序 的 一 个 纯 文 本 窗 口 , 叫 Win32 控 制 台 。<br />

Win32 控 制 台 有 显 示 缓 冲 区 和 输 入 缓 冲 区 , 这 些 缓 冲 区 作 为 计 算 机 与 键 盘 和 计 算 机 与 屏 幕 间<br />

的 中 介 。 当 在 键 盘 输 入 字 符 时 , 它 们 被 放 到 输 入 缓 冲 区 中 , 击 回 车 后 , 计 算 机 就 从 缓 冲 区 中<br />

读 取 输 入 ; 有 输 出 时 , 计 算 机 向 缓 冲 区 里 写 , 当 有 一 个 行 结 束 符 写 到 缓 冲 区 时 , 缓 冲 区 里 的<br />

内 容 就 写 在 屏 幕 的 一 行 上 。Win32 控 制 台 用 于 不 需 要 显 示 图 像 的 程 序 中 。 在 VIP 里 , 当 把<br />

VIP 工 程 的 目 标 设 为 console 时 , 就 会 创 建 控 制 台 计 算 机 程 序 。<br />

在 VIP 中 , 有 一 个 特 殊 的 类 过 程 用 于 读 写 控 制 台 。 把 VIP 程 序 视 为 一 个 过 程 , 想 像 一 下 ,<br />

在 一 些 程 序 同 时 工 作 时 , 有 许 多 过 程 同 时 也 在 运 行 。 每 个 过 程 都 与 一 个 控 制 台 相 连 , 而 一 个<br />

时 刻 每 个 过 程 也 只 与 一 个 控 制 台 相 连 。 对 输 入 和 输 出 , 控 制 台 支 持 三 种 流 : 一 个 读 字 符 的 输<br />

入 流 、 一 个 写 字 符 的 输 出 流 和 一 个 出 错 时 写 特 殊 消 息 的 错 误 流 。 流 的 概 念 后 面 再 说 , 现 在 就<br />

把 它 看 成 是 一 个 通 道 , 计 算 机 通 过 它 得 到 和 写 出 字 符 ( 的 流 )。<br />

输 入 流 缺 省 时 是 与 键 盘 相 连 的 , 输 出 流 和 错 误 流 缺 省 时 是 与 屏 幕 相 连 的 , 不 过 它 们 都 可<br />

以 重 定 向 。<br />

要 直 接 向 控 制 台 写 , 可 以 使 用 console 类 中 的 谓 词 。 但 强 烈 建 议 不 这 么 做 , 除 非 有 很 好<br />

的 理 由 并 且 完 全 明 白 所 做 的 内 容 。 向 控 制 台 写 , 除 了 其 它 的 , 还 意 味 着 必 须 确 定 有 控 制 台 存<br />

在 。 在 VIP 和 IDE 中 , 把 目 标 设 为 console 16 就 可 以 明 确 。 但 把 程 序 转 移 到 另 一 个 计 算 机 上 会 怎<br />

么 样 呢 ? 可 能 并 不 是 预 期 的 那 样 。<br />

最 好 是 用 我 们 以 前 用 的 标 准 输 入 输 出 stdIO 来 替 代 写 控 制 台 。 这 个 类 中 的 过 程 总 会 正 确<br />

地 写 输 出 设 备 , 在 控 制 台 环 境 中 , 它 使 用 Win32 控 制 台 , 在 GUI 环 境 中 它 会 使 用 消 息 窗 口<br />

和 任 务 窗 口 。<br />

在 帮 助 文 件 中 , 可 以 找 到 有 关 console 类 和 它 的 谓 词 方 面 的 内 容 。<br />

15<br />

本 章 一 般 性 内 容 取 材 于 Wikipedia( 维 基 百 科 ) 及 其 它 一 些 理 论 著 作 , 许 多 细 节 内 容 取 材 于 VIP 的 帮 助 文 件 , 还 有 小 部 分 内 容 来<br />

自 于 和 Thomas Linder Puls 的 私 人 通 信 , 感 谢 Thomas。<br />

16<br />

一 个 以 控 制 台 为 目 标 的 程 序 要 以 子 句 console::init() 开 头 。<br />

136


11.2 VIP 中 的 消 息 窗 口 和 错 误 窗 口<br />

我 们 已 经 遇 到 过 消 息 窗 口 了 , 它 在 VIP 中 是 这 样 一 种 控 制 台 角 色 : 主 要 用 于 显 示 与 来 自<br />

系 统 的 各 种 消 息 , 尤 其 是 表 示 各 种 IDE 重 要 事 件 的 进 程 消 息 , 如 保 存 了 文 件 或 工 程 、 打 开 一<br />

个 新 的 工 程 、 编 译 了 一 个 模 块 、 构 造 了 一 个 工 程 等 等 。 消 息 窗 口 中 可 以 看 到 所 有 IDE 重 要 事<br />

件 的 列 表 , 它 形 成 了 IDE 操 作 的 历 程 , 如 图 11.1 示 。<br />

因 为 消 息 窗 口 是 Windows 窗 口 , 可 以 做 的 事 还 有 很 多 。 比 如 :<br />

• 消 息 窗 口 可 以 随 时 滚 动 和 缩 放 , 以 便 观 察 到 更 多 的 内 容 ,<br />

• 可 以 在 消 息 窗 口 中 选 择 文 本 拷 贝 到 剪 贴 板 里 ,<br />

• 在 消 息 窗 口 中 点 击 右 键 可 以 调 出 一 个 弹 出 式 菜 单 ,<br />

• 消 息 窗 口 中 的 字 体 可 以 用 弹 出 式 菜 单 改 变 ,<br />

• 在 消 息 窗 口 的 选 项 标 签 里 , 可 以 设 置 消 息 窗 口 记 忆 的 行 数 、 可 以 定 位 消 息 窗 口 、 可<br />

以 设 置 消 息 窗 口 显 示 是 否 需 要 回 绕 , 等 等 。<br />

• 如 果 消 息 窗 口 关 闭 了 , 可 以 用 菜 单 选 项 View|Message Window 重 新 打 开 它 。<br />

图 11.1<br />

IDE 的 消 息 窗 口<br />

在 IDE 中 还 有 一 个 单 独 的 窗 口 显 示 错 误 , 显 示 错 误 流 , 大 多 数 时 间 它 是 隐 藏 着 的 ( 当 然<br />

希 望 它 永 远 也 不 出 现 ☺)。 当 编 译 <strong>Prolog</strong> 文 件 编 译 器 发 现 错 误 时 , 或 把 目 标 文 件 、 库 文 件 及<br />

资 源 文 件 合 成 一 个 单 个 的 文 件 链 接 器 发 现 错 误 时 , 它 们 发 出 错 误 或 告 警 消 息 ,IDE 就 会 显 示<br />

出 这 个 窗 口 。 如 果 错 误 窗 口 出 现 , 会 是 如 图 11.2 示 的 样 子 。<br />

图 11.2<br />

错 误 和 告 警 窗 口<br />

窗 口 中 的 每 一 行 有 三 个 部 分 : 类 型 、 描 述 和 文 件 名 。 类 型 这 一 列 显 示 出 错 误 的 类 型 及 消<br />

137


息 代 号 , 出 错 时 , 编 译 会 尽 可 能 地 进 行 下 去 , 但 不 会 做 链 接 和 执 行 。 如 果 是 一 个 警 告 , 编 译<br />

和 链 接 都 可 以 进 行 下 去 , 需 要 的 话 也 会 执 行 。 错 误 的 颜 色 是 红 的 , 而 警 告 的 颜 色 是 洋 红 色 。<br />

例 如 ,“e609” 表 示 错 误 、 代 号 是 609;“w507” 表 示 警 告 、 代 号 是 507。 可 以 在 <strong>Visual</strong> <strong>Prolog</strong><br />

帮 助 文 件 中 找 到 错 误 和 警 告 的 详 细 说 明 。 把 光 标 放 在 窗 口 中 的 错 误 消 息 行 上 , 按 F1 键 就 会<br />

出 现 相 应 的 帮 助 主 题 , 说 明 那 一 行 的 错 误 内 容 。<br />

描 述 这 一 列 是 消 息 说 明 。 文 件 名 栏 中 是 产 生 错 误 或 警 告 的 文 件 名 , 带 有 文 件 的 路 径 。<br />

在 错 误 窗 口 中 双 击 编 译 器 的 错 误 或 警 告 消 息 时 ,IDE 会 打 开 相 应 的 源 文 件 , 光 标 会 移 到<br />

产 生 错 误 / 警 告 的 位 置 上 。<br />

错 误 窗 口 的 下 沿 处 有 错 误 的 完 整 描 述 : 带 路 径 的 文 件 名 及 文 本 位 置 、 消 息 的 全 名 ( 含 代<br />

号 及 说 明 )。 如 果 F1 键 不 好 使 , 可 以 打 开 <strong>Visual</strong> <strong>Prolog</strong> 的 帮 助 文 件 中 , 用 这 里 报 告 的 错 误 代<br />

号 做 索 引 , 查 看 错 误 的 详 细 说 明 。<br />

有 一 个 类 带 有 可 以 直 接 写 消 息 窗 口 的 谓 词 , 就 是 vipMessages 类 , 在 VIP 帮 助 文 件 中 有<br />

详 细 介 绍 。 直 接 写 消 息 窗 口 ( 及 错 误 窗 口 ) 很 诱 人 , 不 过 同 样 建 议 除 非 有 很 好 的 理 由 , 否 则<br />

就 不 要 这 样 做 。 最 好 还 是 使 用 标 准 输 入 输 出 类 stdIO。 与 控 制 台 的 类 一 样 , 消 息 窗 口 是 否 可<br />

用 以 接 收 输 出 是 不 好 确 定 的 , 用 户 可 能 把 消 息 窗 口 关 了 , 这 样 的 话 写 的 东 西 就 丢 失 了 。 而 用<br />

stdIO, 谓 词 总 会 写 向 正 确 的 输 出 通 道 。<br />

11.3 流<br />

过 去 , 没 有 很 多 的 写 设 备 与 读 设 备 。 现 在 情 况 大 不 相 同 了 , 可 以 读 写 U 盘 、 硬 盘 、CD、<br />

DVD、ftp‐socket、 图 形 界 面 , 计 算 机 可 以 接 收 广 播 、 电 视 、 传 真 和 视 频 信 号 , 多 了 去 了 。 作<br />

为 一 个 编 程 人 员 想 一 想 , 必 须 打 理 好 所 有 这 些 不 同 缓 冲 区 里 的 不 同 信 号 、 还 得 用 各 自 的 方 法<br />

( 说 不 定 还 有 各 自 的 语 言 ) 对 这 些 信 号 进 行 编 解 码 , 头 都 大 了 !<br />

这 就 是 为 什 么 现 在 把 输 入 输 出 看 成 来 去 通 道 的 原 因 , 这 样 来 来 去 去 的 通 道 称 为 “ 流 ”。<br />

每 个 输 入 输 出 通 道 都 是 一 个 不 同 的 流 , 有 三 种 标 准 流 : 输 入 流 、 输 出 流 和 错 误 流 。 与 老 的 编<br />

程 人 员 直 接 把 程 序 与 输 入 输 出 设 备 相 连 的 IO 系 统 相 比 , 流 有 两 个 优 点 : 第 一 , 流 是 自 动 连<br />

接 的 , 不 需 要 编 程 人 员 多 做 什 么 事 ; 第 二 , 流 相 对 程 序 来 讲 是 隐 藏 的 。 编 程 人 员 只 要 使 用 诸<br />

如 stdIO::write(…) 这 样 的 命 令 , 系 统 会 把 你 要 输 出 的 写 到 设 备 上 去 。 这 就 是 说 , 编 程 人 员 只<br />

需 要 关 注 于 想 要 用 计 算 机 程 序 解 决 的 问 题 , 而 不 必 操 心 输 入 输 出 的 技 术 细 节 。 此 外 , 这 种 方<br />

法 也 便 于 程 序 移 植 到 不 同 种 类 的 计 算 机 上 。<br />

可 以 把 流 看 成 为 是 一 些 字 节 的 序 列 。 一 个 字 节 是 八 位 二 进 制 数 位 , 一 些 字 节 的 序 列 会 是<br />

这 样 的 :<br />

11001010 01010101 01010100 11010101 01010010 10110101 11010101 10101010 11111100 ...<br />

对 文 本 来 说 , 可 以 认 为 一 个 字 节 代 表 了 一 个 字 符 , 但 也 并 不 总 是 这 样 。 空 格 是 人 们 看 到<br />

的 , 但 在 流 里 就 不 会 有 空 。 上 面 写 出 的 那 样 的 流 人 们 无 法 理 解 , 需 要 解 释 。11001010 是 什<br />

么 意 思 呢 ? 为 了 解 决 这 些 问 题 , 创 建 一 个 统 一 的 解 释 标 准 , 提 出 了 若 干 种 方 案 。 最 常 用 的 是<br />

ASCII 代 码 表 , 这 个 表 里 有 256 个 代 码 , 表 示 了 大 小 写 字 母 、 控 制 字 符 ( 换 行 、 退 格 等 ) 及<br />

标 点 符 号 。 大 写 字 母 A 的 ASCII 代 码 是 65, 二 进 制 是 01000001。<br />

现 在 , 还 使 用 其 它 一 些 代 码 。VIP 中 支 持 的 是 ANSI 代 码 和 Unicode。 前 者 是 对 旧 的 ASCII<br />

代 码 的 扩 展 , 它 提 供 了 诸 如 控 制 屏 幕 颜 色 的 控 制 字 符 , 这 些 控 制 字 符 称 为 ANSI 换 码 序 列 。<br />

ANSI 的 换 码 序 列 是 一 个 ASCII 字 符 序 列 , 它 的 前 两 个 字 符 是 ASCII 的 escape 字 符 27( 二 进 制<br />

00011011, 十 六 进 制 1B) 和 左 方 括 号 “[”( 二 进 制 01011011, 十 六 进 制 5B), 后 面 的 字 符<br />

代 码 指 定 了 控 制 键 盘 或 显 示 的 一 种 功 能 。 我 们 在 写 语 句 里 用 的 “\n” 就 是 这 种 东 西 , 在 VIP<br />

中 , 反 斜 杠 表 示 换 码 序 列 , 后 面 的 n 表 示 换 新 行 。<br />

Unicode 是 一 种 世 界 上 各 种 语 言 都 可 以 使 用 的 代 码 。 这 个 雄 心 很 大 , 它 使 用 了 16 位 的<br />

代 码 替 代 了 8 位 的 。 不 用 太 操 心 这 些 东 西 , 只 要 知 道 文 件 用 的 是 什 么 代 码 就 可 以 了 。 现 在 ,<br />

最 简 单 的 编 辑 程 序 如 Notepad 都 可 以 读 写 这 两 种 代 码 的 。<br />

那 这 些 与 VIP 中 的 流 有 什 么 关 系 呢 ? 流 不 仅 仅 是 一 连 串 的 二 进 制 数 据 , 它 还 有 两 个 属<br />

138


性 , 一 个 是 模 式 , 一 个 是 位 置 。VIP 中 有 二 种 或 是 三 种 模 式 , 取 决 于 怎 么 来 看 。 这 些 模 式 指<br />

明 了 应 该 如 何 解 释 流 中 的 字 节 :<br />

• 二 进 制 模 式 这 个 模 式 下 程 序 从 输 入 流 中 读 取 字 节 是 就 按 原 样 读 , 得 到 的 就 是 二 进<br />

制 比 特 序 列 , 要 怎 么 用 , 全 是 编 程 者 自 己 的 事 ; 输 出 流 也 是 一 样 , 照 二 进 制 数 据 原<br />

样 写 出 去 , 没 有 解 释 。<br />

• 文 本 模 式 这 个 模 式 下 会 有 一 个 解 释 器 , 流 中 的 字 节 读 做 字 母 、 数 字 等 等 。 它 会 把<br />

字 节 翻 译 成 字 符 , 在 文 本 模 式 下 读 一 个 文 本 , 还 会 有 各 种 各 样 的 特 殊 字 符 , 如 “ 换<br />

行 ”。 当 向 打 印 机 输 出 时 , 换 行 字 符 就 会 被 转 换 成 打 印 机 的 两 个 控 制 字 符 : 一 个 换<br />

行 , 一 个 回 车 。 还 有 其 它 的 转 换 方 式 , 例 如 , 在 unicode 中 数 字 十 二 的 代 码 是 0x0C。<br />

当 这 个 代 码 送 到 显 示 屏 上 时 , 会 自 动 地 转 换 成 你 看 到 的 “12”, 一 个 1 和 一 个 2。<br />

有 两 种 文 本 模 式 :ANSI 模 式 和 Unicode 模 式 , 前 者 使 用 ANSI 代 码 而 后 者 使 用 Unicode<br />

代 码 , 这 就 是 两 者 的 差 别 。<br />

第 二 个 属 性 , 位 置 , 要 当 心 在 输 入 或 输 出 流 中 现 在 是 在 哪 儿 , 在 输 入 的 流 中 可 以 回 到 前<br />

面 已 经 读 过 的 字 符 上 去 。 更 多 的 内 容 下 节 来 说 。<br />

11.4 标 准 输 入 输 出 :stdIO 类<br />

在 VIP 中 有 一 个 标 准 的 输 入 输 出 类 , 叫 作 stdIO, 我 们 前 面 已 经 使 用 过 它 。 程 序 开 始 时 ,<br />

stdIO 是 由 一 个 输 入 流 、 一 个 输 出 流 和 一 个 错 误 流 构 成 的 。 输 入 流 通 常 与 键 盘 连 接 , 如 果 工<br />

程 策 略 是 console, 输 出 流 和 错 误 流 就 与 控 制 台 窗 口 (Win32 控 制 台 ) 连 接 ; 如 果 工 程 策 略<br />

是 GUI, 输 出 流 和 错 误 流 就 与 消 息 窗 口 连 接 。<br />

我 们 来 看 个 例 子 。 创 建 一 个 工 程 , 策 略 是 console, 带 一 个 名 为 database 的 类 。 程 序 要<br />

从 键 盘 上 读 入 一 些 数 据 , 把 它 们 插 入 到 数 据 库 中 , 结 束 程 序 时 , 把 数 据 库 的 内 容 写 到 一 个 文<br />

件 里 。 谓 词 放 在 模 块 database 中 , 下 面 是 代 码 :<br />

class database<br />

open core<br />

predicates<br />

classInfo : core::classInfo.<br />

storeNames : (unsigned Counter) ‐> unsigned Number procedure (i).<br />

fill : () procedure ().<br />

end class database<br />

在 实 现 文 件 database.pro 中 , 添 加 下 面 的 代 码 :<br />

class facts ‐ persons<br />

% 事 实 以 person 的 名 字 存 放 在 数 据 库 中<br />

person : (string Name).<br />

clauses<br />

storeNames(N) = N :‐<br />

% 这 个 谓 词 读 一 行 并 添 加 一 个 事 实 到 本 地 数 据 库 中<br />

stdIO::write("\nInput Name ( '0' to stop): "),<br />

'0' = stdIO::readchar(), % 程 序 查 看 第 一 个 字 符 是 不 是 0,<br />

!. % 如 果 是 , 程 序 就 停 止<br />

storeNames(I) = Number :‐<br />

stdIO::ungetchar(),<br />

% 如 果 不 是 , 程 序 放 回 第 一 个 字 符<br />

Term = stdIO::readline(), % 并 读 入 完 整 的 输 入 行<br />

assert(person(Term)), % 在 数 据 库 中 添 加 一 个 新 人<br />

N=I+1, % 计 数 器 加 1<br />

139


Nubmer = storeName(N).<br />

% 计 算 记 录 数<br />

fill() :‐<br />

% 填 写 数 据 库<br />

% 调 用 storeNames/1 开 始 填 写 数 据 库<br />

N = storeNames(0),<br />

stdIO::writef("\n %d terms were stored \n Press any key\n", N),<br />

stdIO::save(persons), % 保 存 数 据 库 到 当 前 输 出 流 , 这 里 是 屏 幕<br />

% 等 待<br />

_Char = stdIO::readchar(),<br />

DBFile = outputStream_file::create8("persons.txt"), % 创 建 一 个 ANSI 文 本 模 式 的 输 出 文 件 ,<br />

stdIO::setoutputstream(DBFile),<br />

stdIO::save(persons),<br />

stdIO::flush(),<br />

_Char2 = stdIO::readchar().<br />

% 用 变 量 DBFile 表 示 这 个 文 件 ,<br />

% 这 个 变 量 是 流 类 型 的<br />

%DBFile 成 为 当 前 输 出 流<br />

% 把 数 据 库 写 到 输 出 缓 冲 区 里<br />

% 把 缓 冲 区 写 到 文 件 里<br />

end implement database<br />

这 里 做 些 解 释 。 数 据 库 有 个 名 字 , 所 以 在 save 命 令 中 就 可 以 用 名 字 引 用 它 。 为 了 写 到<br />

一 个 文 件 里 而 不 是 屏 幕 上 , 需 要 先 创 建 一 个 文 件 。 创 建 之 后 , 用 已 有 的 文 件 名 写 就 会 覆 盖 它 。<br />

接 着 , 把 输 出 流 重 定 向 到 这 个 文 件 , 用 save 命 令 把 数 据 库 内 容 写 到 输 出 文 件 里 。 写 的 时 候 ,<br />

程 序 是 写 一 个 缓 冲 区 , 不 是 直 接 向 硬 盘 里 写 , 那 样 程 序 就 太 慢 了 , 所 以 要 用 缓 冲 区 。 缓 冲 区<br />

是 一 片 存 储 器 , 它 可 以 快 速 地 访 问 , 当 缓 冲 区 满 了 的 时 候 , 一 个 独 立 的 程 序 会 把 缓 冲 区 内 容<br />

写 到 磁 盘 的 文 件 里 , 而 此 时 处 理 器 可 以 同 时 处 理 其 它 的 事 。 所 以 , 当 要 关 闭 输 出 流 时 ( 程 序<br />

结 束 时 这 会 自 动 进 行 ), 首 先 要 flush() 缓 冲 区 , 它 会 把 缓 冲 区 里 剩 下 的 内 容 送 到 文 件 里 。 如<br />

果 不 这 样 做 , 缓 冲 区 里 的 内 容 就 丢 失 了 。<br />

我 们 用 写 命 令 stdIO::setoutputstream(DBFile) 改 变 了 当 前 的 输 出 流 , 也 就 是 说 写 和 保 存 命<br />

令 是 写 一 个 文 件 而 不 是 标 准 输 出 流 ( 可 能 会 在 win32 控 制 台 上 看 到 一 些 回 车 , 这 是 对 输 入 东<br />

西 的 回 应 )。 如 果 想 要 重 新 向 屏 幕 上 写 , 必 须 再 用 setoutputstream() 对 输 出 流 重 定 向 。 可 以 这<br />

样 :<br />

NewOutput = console::getconsoleoutputstream(),<br />

stdIO::setoutputstream(NewOutput),<br />

stdIO::write("Thats all"),<br />

把 这 些 行 放 在 fill/0 子 句 的 最 后 , 上 面 最 后 一 个 写 语 句 的 内 容 就 会 再 次 出 现 在 屏 幕 上 。<br />

剩 下 的 就 是 main.pro 中 的 代 码 了 , 是 这 样 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

database::fill(),<br />

_=stdIO::readchar(),<br />

succeed().<br />

本 节 剩 下 的 部 分 , 概 要 地 介 绍 一 下 stdIO 中 最 重 要 的 一 些 谓 词 。 最 重 要 , 是 指 它 们 对 初<br />

学 者 来 说 是 最 有 用 的 。 这 里 把 它 们 分 成 四 组 : 输 入 谓 词 、 输 出 谓 词 、 错 误 谓 词 和 数 据 库 谓 词 。<br />

对 每 个 谓 词 , 有 它 的 名 字 、 声 明 及 简 要 说 明 。 这 些 内 容 也 就 是 帮 助 文 件 的 剪 辑 版 吧 。<br />

我 们 从 读 输 入 流 的 谓 词 开 始 。<br />

140


‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::getInputStream/0‐><br />

getInputStream : () ‐> inputStream Stream procedure ().<br />

说 明<br />

这 个 谓 词 返 回 当 前 输 入 流 , 就 是 说 把 这 个 流 绑 定 给 一 个 变 量 , 从 此 刻 开 始 , 可 以 用 这 个<br />

变 量 来 引 用 这 个 流 。 变 量 是 Stream 类 型 的 , 不 能 打 印 它 。 帮 助 文 件 里 说 应 该 事 先 用<br />

setInputStream/1 谓 词 进 行 设 置 , 但 作 者 试 过 不 用 自 己 设 置 也 行 。 可 以 用 这 个 谓 词 暂 时 改 变 输<br />

入 流 , 然 后 再 改 回 来 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::read/0‐><br />

read : () ‐> _ Term procedure ().<br />

说 明<br />

这 个 谓 词 从 输 入 流 中 读 取 一 个 指 定 的 项 。 项 的 类 型 可 以 是 任 意 合 法 的 <strong>Prolog</strong> 项 , 也 就<br />

是 说 用 这 个 谓 词 可 以 读 整 数 、 实 数 、 字 符 、 串 、 事 实 , 等 等 。 程 序 可 以 决 定 需 要 什 么 , 读 合<br />

适 的 项 类 型 。 如 果 在 程 序 中 声 明 了 一 个 变 量 是 整 数 类 型 的 , 又 在 程 序 中 读 它 时 , 程 序 会 知 道<br />

预 期 结 果 是 什 么 。 如 果 不 确 定 , 或 是 一 个 变 量 没 有 声 明 过 , 可 以 用 谓 词 hasdomain/2 明 确 地<br />

定 义 该 项 的 域 。 这 好 象 有 点 儿 复 杂 了 , 我 们 下 一 节 来 说 这 个 问 题 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::readBytes/1‐><br />

readBytes : (byteCount Size) ‐> binary Binary procedure (i).<br />

说 明<br />

如 果 输 入 流 模 式 是 二 进 制 的 (binary), 程 序 就 应 该 按 字 节 读 。 这 个 谓 词 从 输 入 流 中 读 入<br />

一 些 字 节 并 把 它 们 按 二 进 制 存 在 一 个 变 量 里 。 读 入 字 节 的 数 量 是 由 调 用 该 谓 词 时 使 用 的 变 量<br />

Size 确 定 的 。 如 果 在 读 完 指 定 字 节 数 之 前 遇 到 了 输 入 流 的 结 尾 , 变 量 中 返 回 的 是 实 际 读 到 的<br />

数 量 的 那 些 二 进 制 字 节 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::readChar/0‐><br />

readChar : () ‐> char Char procedure ().<br />

说 明<br />

这 个 谓 词 从 当 前 的 输 入 流 中 读 并 返 回 一 个 字 符 。 很 奇 怪 , 它 还 需 要 一 个 行 结 束 符 。 不 过<br />

也 可 能 是 我 弄 错 了 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::readLine/0‐><br />

readLine : () ‐> string String procedure ().<br />

说 明<br />

这 个 谓 词 从 输 入 流 中 读 入 一 行 。 行 结 尾 的 符 号 是 ’\n’( 换 行 ) 或 ’0’ 字 符 ( 串 结 尾 标 识 )<br />

或 流 的 末 尾 。<br />

141


‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::readString/1‐><br />

readString : (charCount NumberOfChars) ‐> string String procedure (i).<br />

说 明<br />

这 个 谓 词 要 从 输 入 流 中 读 NumberOFChars 个 字 符 并 把 它 们 放 在 一 个 串 里 。 如 果 读 时 碰<br />

到 流 的 结 尾 , 它 返 回 的 是 实 际 读 到 数 量 的 字 符 串 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::setInputStream/1<br />

setInputStream : (inputStream Stream) procedure (i).<br />

说 明<br />

这 个 谓 词 把 Stream 设 置 为 当 前 的 输 入 流 。 谓 词 的 输 入 参 数 需 要 一 个 流 类 型 的 变 量 , 随<br />

后 就 可 以 使 用 这 个 变 量 , 例 如 , 创 建 了 一 个 新 文 件 时 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::ungetChar/0<br />

ungetChar : () procedure ().<br />

说 明<br />

这 个 谓 词 返 回 最 后 读 的 字 符 给 当 前 的 输 入 流 。<br />

现 在 来 看 看 写 输 出 流 的 谓 词 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::flush/0<br />

flush : () procedure ().<br />

说 明<br />

这 个 谓 词 强 使 当 前 输 出 流 的 内 部 缓 冲 区 内 容 写 到 与 之 相 连 的 资 源 设 备 ( 如 文 件 ) 中 去 。<br />

当 输 入 定 向 到 一 个 串 行 接 口 , 而 在 缓 冲 区 还 没 有 写 满 时 又 需 要 向 这 个 接 口 送 数 据 时 ,flush<br />

是 很 有 用 的 。 对 确 保 另 一 个 进 程 得 到 流 的 最 后 的 数 据 它 也 很 有 用 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::getOutputStream/0‐><br />

getOutputStream : () ‐> outputStream Stream procedure ().<br />

说 明<br />

这 个 谓 词 返 回 当 前 输 出 流 的 名 字 。 这 个 名 字 应 该 事 先 用 setOutputStream/1 谓 词 设 置 过 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::nl/0<br />

nl : () procedure ().<br />

说 明<br />

这 个 谓 词 向 当 前 输 出 流 写 一 个 换 行 符 。<br />

142


‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::setOutputStream/1<br />

setOutputStream : (outputStream Stream) procedure (i).<br />

说 明<br />

这 个 谓 词 把 Stream 设 置 为 当 前 的 输 出 流 , 所 有 的 输 出 定 向 到 这 个 新 的 输 出 流 上 。 变 量<br />

Stream 是 流 类 型 的 , 打 开 一 个 文 件 供 输 出 时 就 会 需 要 这 样 的 类 型 。 可 以 用 下 面 这 样 的 子 句<br />

来 实 现 :<br />

DBFile = outputStream_file::create8("persons.txt"),<br />

我 们 最 后 的 一 个 程 序 就 是 这 么 做 的 。 通 过 定 义 使 变 量 DBFile 是 流 类 型 的 , 随 后 可 以 把<br />

它 用 在 任 何 要 求 这 类 的 变 量 的 地 方 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::write/...<br />

write : (...) procedure (...).<br />

说 明<br />

这 个 谓 词 向 当 前 输 出 流 输 出 任 意 数 量 的 变 量 。 变 量 间 用 逗 号 分 开 , 也 可 以 加 上 串 来 说 明<br />

输 出 的 东 西 。 可 以 使 用 任 意 类 型 的 变 量 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::writeQuoted/...<br />

writeQuoted : (...) procedure (...).<br />

说 明<br />

这 个 谓 词 写 指 定 的 变 量 到 当 前 输 出 流 。 调 用 时 可 以 有 任 意 非 零 数 量 的 参 数 , 所 有 串 变 量<br />

的 前 辍 和 后 辍 双 引 号 也 会 写 到 流 里 去 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::writef/1...<br />

writef : (string Format , ...) procedure (i,...).<br />

说 明<br />

这 个 谓 词 会 产 生 “ 自 动 ” 格 式 化 了 的 输 出 , 它 可 以 形 成 严 格 的 格 式 , 例 如 , 当 需 要 整 齐<br />

的 列 格 式 时 。 其 实 窍 门 在 于 事 先 把 要 输 出 的 变 量 按 串 格 式 规 定 列 好 了 表 。 这 里 只 是 概 要 地 说<br />

说 , 在 11.6 节 再 介 绍 格 式 化 的 细 节 吧 。<br />

下 面 是 写 错 误 流 的 谓 词 。 它 看 起 来 与 写 输 出 流 的 那 些 谓 词 差 不 多 , 就 不 进 一 步 解 释 了 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::flush_error/0<br />

flush_error : () procedure ().<br />

说 明<br />

这 个 谓 词 强 使 当 前 错 误 流 的 内 部 缓 冲 区 内 容 写 向 资 源 设 备 。 当 输 入 定 向 到 一 个 串 行 接<br />

口 , 而 在 缓 冲 区 还 没 有 写 满 时 又 需 要 向 这 个 接 口 送 数 据 时 ,flush 是 很 有 用 的 。 对 确 保 另 一<br />

个 进 程 得 到 流 的 最 后 的 数 据 它 也 很 有 用 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

143


stdio::getErrorStream/0‐><br />

getErrorStream : () ‐> outputStream Stream procedure ().<br />

这 个 谓 词 返 回 当 前 的 错 误 流 。 也 就 是 说 , 它 把 一 个 变 量 与 这 个 流 相 绑 定 , 其 后 就 可 以 用<br />

这 个 变 量 来 引 用 这 个 流 。 错 误 流 应 该 事 先 用 setErrorStream/1 谓 词 设 置 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::nl_error/0<br />

nl_error : () procedure ().<br />

说 明<br />

这 个 谓 词 向 错 误 流 写 一 个 换 行 符 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::setErrorStream/1<br />

setErrorStream : (outputStream Stream)<br />

procedure (i).<br />

说 明<br />

这 个 谓 词 把 Stream 设 置 为 当 前 的 错 误 流 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::writeQuoted_error/...<br />

writeQuoted_error : (...) procedure (...).<br />

说 明<br />

这 个 谓 词 写 指 定 的 变 量 到 当 前 错 误 流 。 调 用 时 可 以 有 任 意 非 零 数 量 的 参 数 , 所 有 串 变 量<br />

的 前 辍 和 后 辍 双 引 号 也 会 写 到 流 里 去 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::write_error/...<br />

write_error : (...) procedure (...).<br />

说 明<br />

这 个 谓 词 写 指 定 的 变 量 到 当 前 错 误 流 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::writef_error/1...<br />

writef_error : (string Format [formatString], ...) procedure (i,...).<br />

说 明<br />

这 个 谓 词 产 生 格 式 化 的 输 出 到 当 前 错 误 流 。<br />

最 后 , 来 看 一 下 需 要 了 解 的 特 殊 用 途 的 谓 词 :<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::consult/1<br />

consult : (factDB FactsSectionName) procedure (i).<br />

这 个 谓 词 从 当 前 输 入 流 中 读 事 实 并 把 它 们 用 FactsSectionName 规 定 的 名 字 插 入 到 内 部<br />

事 实 段 中 。 必 须 事 先 已 经 用 save/1 谓 词 创 建 了 输 入 文 件 , 或 是 用 文 本 编 辑 器 手 工 创 建 了 输<br />

144


入 文 件 。 文 件 中 各 个 事 实 必 须 单 独 占 一 行 , 不 能 有 多 余 的 空 格 , 函 子 必 须 用 小 写 字 母 。 如 果<br />

是 以 前 用 save/1 保 存 的 文 件 , 这 些 都 不 成 问 题 。 文 件 中 可 以 有 注 释 , 单 行 的 和 多 行 的 都 行 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::save/1<br />

save : (factDB FactsSectionName) procedure (i).<br />

说 明<br />

这 个 保 存 谓 词 把 事 实 段 的 内 容 以 FactsSectionName 规 定 的 名 字 保 存 到 当 前 输 出 流 中 去 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::save_error/1<br />

save_error : (factDB FactsSectionName) procedure (i).<br />

说 明<br />

这 个 保 存 谓 词 把 事 实 段 的 内 容 以 FactsSectionName 规 定 的 名 字 保 存 到 当 前 错 误 流 中 去 。<br />

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐<br />

stdio::endOfStream/0<br />

endOfStream : () determ ().<br />

说 明<br />

检 查 当 前 输 入 流 的 位 置 是 否 已 经 到 了 流 的 末 尾 了 。 谓 词 失 败 的 话 , 说 明 流 指 针 不 在 流 的<br />

末 尾 。 对 不 确 定 的 流 , 这 个 谓 词 总 是 失 败 。<br />

11.5 谓 词 stdIO::read<br />

谓 词 read/0 很 普 通 , 但 它 有 一 个 特 性 , 它 能 自 己 确 定 需 要 用 什 么 类 型 的 输 入 来 进 行 读 操<br />

作 。 最 好 还 是 用 程 序 来 说 明 , 建 立 一 个 新 的 工 程 ( 目 标 是 console) 把 下 面 的 子 句 添 加 到<br />

main.pro 文 件 中 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

readExamples::writeSomeTerms(),<br />

Intfile = inputstream_file::openfile("intSource.txt"),<br />

Stringfile = inputstream_file::openfile("stringSource.txt"),<br />

FunctorFile = inputstream_file::openfile("functorSource.txt"),<br />

readExamples::readSomeTerms(Intfile, Stringfile,FunctorFile),<br />

readExamples::writeSomeTerms(),<br />

readExamples::readSomeTerms(Intfile, Stringfile,FunctorFile),<br />

readExamples::writeSomeTerms(),<br />

_ = stdIO::readchar().<br />

end implement main<br />

再 建 立 三 个 文 件 ( 要 用 Unicode), 文 件 intsource.txt 有 两 个 整 数 :<br />

123 456<br />

文 件 stringsource.txt 有 三 行 , 每 行 中 有 一 个 串 :<br />

"this is a string"<br />

"here is another string"<br />

"And again another one"<br />

145


文 件 functorsource.txt 中 有 两 上 函 子 事 实 :<br />

person("AnotherThomas")<br />

person("Anna")<br />

注 意 , 输 入 的 时 候 每 个 人 各 占 一 行 , 行 尾 不 要 用 句 点 !<br />

main.pro 中 创 建 了 三 个 对 象 , 与 上 面 三 个 文 件 相 关 联 。 我 们 来 调 用 类 readexamples, 并<br />

让 它 写 出 三 个 事 实 变 量 的 值 。 类 readexamples 是 这 样 的 :<br />

class readexamples<br />

open core<br />

predicates<br />

readSomeTerms : (inputStream Ints,<br />

writeSomeTerms : () procedure ().<br />

inputStream Strings, inputstream Functors) procedure (i,i,i).<br />

end class readexamples<br />

而 它 的 实 现 是 :<br />

domains<br />

person = person(string Name).<br />

class facts<br />

intfact : integer := 1.<br />

stringFact : string :="This is a string".<br />

functorFact : person := person("Thomas").<br />

clauses<br />

classInfo(className, classVersion).<br />

clauses<br />

writeSomeTerms() :‐<br />

stdIO::write("intfact = ",intfact, "\nstringfact = ", stringfact, "\nfunctorFact = ", functorfact),<br />

stdIO::nl.<br />

readSomeTerms(Intfile, StringFile,FunctorFile) :‐<br />

intfact := Intfile:read(),<br />

stringfact := StringFile:read(),<br />

functorFact := FunctorFile:read().<br />

end implement readexamples<br />

上 面 已 经 对 事 实 做 了 初 始 化 。 当 调 用 readsometerms() 时 , 会 从 三 个 文 件 中 读 入 新 值 。<br />

尽 管 三 个 事 实 是 不 同 类 型 的 , 但 这 个 程 序 可 以 从 类 型 声 明 中 确 定 不 同 情 况 要 如 何 读 。<br />

有 时 无 法 像 这 个 例 子 中 那 样 给 出 明 确 的 类 型 声 明 , 这 时 可 以 借 助 特 殊 谓 词 hasDomain/2<br />

强 迫 一 个 变 量 类 型 化 。 这 个 谓 词 有 两 个 参 数 , 一 个 类 型 参 数 和 一 个 变 量 名 , 使 用 方 法 如 下 :<br />

predicates<br />

readint : (inputStream S) ‐> integer Value.<br />

clauses<br />

readint(S) ‐ Value :‐<br />

146


...,<br />

hasDomain(integer, Var),<br />

Value = S:read().<br />

通 过 使 用 hasDomain/2 可 以 明 确 变 量 Value 具 有 整 数 类 型 , 这 样 一 来 谓 词 read/0 就 可 以<br />

从 输 入 流 S 中 按 整 数 读 入 。 当 然 , 除 了 整 数 , 也 可 以 使 用 其 它 的 预 定 义 的 和 声 明 过 的 类 型 。<br />

11.6 谓 词 writef() 和 format string( 格 式 串 )<br />

谓 词 writef() 产 生 格 式 化 的 输 出 到 输 出 流 。 要 格 式 化 输 出 , 就 需 要 提 供 一 个 称 作 格 式 串<br />

的 东 西 给 谓 词 , 一 般 的 调 用 形 式 是 :<br />

stdIO::writef(, ).<br />

是 含 有 一 个 或 多 个 ( 格 式 区 段 ) 的 串 , 对 <br />

中 的 每 个 变 量 都 应 该 在 这 个 串 中 给 一 个 格 式 区 段 。 格 式 串 可 能 会 变 得 很 复 杂 , 因 为 除 了 变 量<br />

可 能 还 会 有 文 本 交 织 其 中 。 中 的 的 变 量 个 数 必 须 与 格 式 串 中 给 定 的 格 式 区<br />

段 相 一 致 , 否 则 就 会 产 生 运 行 时 错 误 “Wrong number of arguments in the format string”( 格 式 串<br />

参 数 个 数 错 误 )。<br />

( 格 式 区 段 ) 以 百 分 号 “%” 开 始 , 最 多 可 以 含 有 五 项 <br />

( 格 式 说 明 )。 格 式 说 明 明 确 指 定 了 相 应 的 变 量 应 如 何 写 到 输 出 流 中 去 。 如 果 百 分 号 后 跟 了<br />

一 些 不 明 确 的 字 符 ( 不 是 定 义 的 格 式 说 明 符 ), 这 些 字 符 就 会 不 加 修 饰 地 打 印 , 也 就 会 出 现<br />

在 输 出 中 。 格 式 表 的 一 般 形 式 是 :<br />

%<br />

百 分 号 后 跟 了 五 种 可 能 的 格 式 说 明 , 它 们 都 是 可 选 的 。 下 面 依 次 介 绍 :<br />

连 字 号 表 示 这 个 区 段 是 左 对 齐 的 , 而 缺 省 是 右 对 齐 。 如 果 没 有 设 置 值 , 连<br />

字 号 就 没 有 作 用 , 因 为 宽 度 会 自 动 地 设 置 成 所 需 要 的 位 置 数 量 。 如 果 实 际 字 符 数 超 过 了 宽 度<br />

值 , 连 字 符 也 没 有 作 用 。<br />

零 放 在 ( 宽 度 ) 前 面 表 示 需 要 加 0 直 到 达 到 最 小 宽 度 。 如 果 它 与 前 一 项 ( 连<br />

字 号 ) 同 时 出 现 , 它 就 被 忽 略 了 。<br />

宽 度 是 一 个 自 然 数 , 也 就 是 一 个 正 的 十 进 制 整 数 , 它 规 定 了 区 段 的 最 小 宽 度 。<br />

如 果 字 符 数 的 实 际 值 小 于 宽 度 值 , 在 这 个 值 的 前 面 就 会 加 上 需 要 数 量 的 空 格 符 ( 如 果 设 置 了<br />

连 字 符 , 那 就 会 放 在 后 面 )。 如 果 字 符 数 的 实 际 值 比 宽 度 值 大 , 就 不 会 有 什 么 变 化 。<br />

精 度 点 “.” 和 它 后 面 的 十 进 制 无 符 号 整 数 既 可 以 指 定 浮 点 数 的 精 度 又 可<br />

以 指 定 一 个 串 打 印 出 的 最 多 字 符 数 , 是 哪 种 情 况 与 相 应 的 变 量 有 关 。<br />

类 型 指 定 了 不 同 于 变 量 缺 省 使 用 的 其 它 格 式 。 例 如 , 可 以 规 定 一 个 整 数 必 须 按<br />

无 符 号 整 数 格 式 化 。 类 型 可 以 使 用 以 下 的 值 :<br />

f 使 实 数 使 用 定 点 表 示 法 ( 如 123.4 或 0.004321) 的 格 式 。 这 是 实 数 的 缺 省 格 式 。<br />

e 使 实 数 使 用 指 数 表 示 法 ( 如 1.234e+002 或 4.321e‐003) 的 格 式 。<br />

g 使 实 数 使 用 f 和 e 中 最 短 的 格 式 , 如 果 指 数 值 小 于 ‐4 或 大 于 等 于 精 度 值 就 总 会 使 用<br />

e 格 式 。 尾 部 的 0 都 会 被 截 去 。<br />

d 或 D 有 符 号 十 进 制 数 格 式 。<br />

u 或 U 无 符 号 整 数 格 式 。<br />

x 或 X 十 六 进 制 数 格 式 。<br />

o 或 O 八 进 制 数 格 式<br />

c 字 符 格 式 。<br />

B <strong>Visual</strong> <strong>Prolog</strong> 二 进 制 类 型 格 式 。<br />

R 数 据 库 引 用 号 格 式 。<br />

P 过 程 参 数 格 式 。<br />

s 串 格 式 。<br />

注 意 , 把 一 个 实 数 值 转 换 成 文 本 表 示 时 , 它 会 被 截 短 并 四 舍 五 入 , 共 保 留 17 位 数 字 ,<br />

147


除 非 有 其 它 的 格 式 说 明 。<br />

下 面 是 一 个 例 子 :<br />

clauses<br />

run():‐<br />

console::init(),<br />

stdIO::writef("a without variables\n"),<br />

IntegerVar = 12345,<br />

stdIO::writef("Numbers %5\n wider %10 \n left justified %‐10


出 流 。Stream 类 是 一 个 更 通 用 的 类 , 不 知 道 现 在 介 绍 它 是 否 合 适 , 不 过 必 须 这 样 认 识 Stream<br />

类 : 它 给 每 个 打 开 读 或 写 的 流 创 建 一 个 对 象 。 在 标 准 I/O 中 一 个 流 可 以 读 或 写 , 而 在 Stream<br />

类 中 , 一 个 流 可 以 读 和 写 。<br />

Stream 类 是 若 干 类 的 根 , 在 最 顶 层 的 , 是 类 Stream。 它 有 一 些 最 常 用 的 谓 词 , 它 们 可<br />

以 用 于 任 何 流 。 如 : 关 闭 一 个 流 的 谓 词 、 设 置 或 获 取 流 的 模 式 的 谓 词 、 设 置 或 获 取 流 中 的 位<br />

置 的 谓 词 。 下 一 层 有 两 个 类 Inputstream 和 Outputstream, 这 两 个 类 分 别 包 含 了 用 于 输 入 流<br />

和 输 出 流 的 谓 词 。 在 Inputstream 类 中 , 有 read/0、readchar/0、consult/1 等 谓 词 ; 在 Outputstream<br />

类 中 , 有 flush/0、save/1、write()、writeQuoted() 和 writef() 等 谓 词 。 这 些 谓 词 大 家 现 在 都 已<br />

经 熟 悉 , 它 们 在 这 里 出 现 也 没 有 什 么 奇 怪 的 。 不 过 , 这 还 不 是 全 部 , 在 类 Inputstream 和<br />

Outputstream 之 下 还 有 第 三 层 , 包 含 一 些 用 于 特 殊 用 途 输 入 输 出 流 的 谓 词 , 每 个 类 与 一 种 特<br />

殊 的 输 入 或 输 出 媒 介 相 关 连 , 如 :<br />

• Outputstream_console 类 用 于 写 控 制 台<br />

• Outputstream_file 类 用 于 写 文 件<br />

• Inputstream_file 类 用 于 读 文 件<br />

• Inputstream_console 类 用 于 读 控 制 台<br />

在 这 本 介 绍 性 的 读 物 中 , 我 们 只 讲 一 下 类 Inputstream_file 和 Outputstream_file。 前 者 含<br />

有 打 开 一 个 文 件 的 谓 词 , 打 开 的 文 件 可 以 有 三 种 模 式 ( 二 进 制 、ANSI、Unicode); 而 后 者 包<br />

含 有 以 规 定 的 模 式 打 开 一 个 文 件 的 谓 词 、 以 规 定 的 模 式 打 开 一 个 已 有 文 件 及 加 写 一 个 文 件 的<br />

谓 词 。<br />

图 11.3 描 画 了 处 理 输 入 和 输 出 的 谓 词 层 级 关 系 。 要 找 一 个 谓 词 , 就 要 在 更 高 层 级 上 、<br />

在 规 定 该 谓 词 类 型 的 类 或 它 的 接 口 里 去 找 。<br />

图 11.3<br />

Stream 的 类 层 级<br />

在 详 细 介 绍 谓 词 之 前 , 先 看 一 个 例 子 , 我 们 可 以 先 指 出 一 些 特 点 。 这 个 程 序 取 自 VIP 的<br />

帮 助 文 件 , 不 过 做 了 一 点 改 动 。<br />

这 个 程 序 把 一 个 文 件 的 内 容 拷 贝 到 另 一 个 文 件 中 。 工 程 的 目 标 是 console, 包 含 一 个<br />

copystream 类 , 类 中 有 拷 贝 一 个 文 件 内 容 到 另 一 个 文 件 的 谓 词 , 在 文 件 main.pro 中 我 们 从<br />

copystream 中 调 用 这 个 谓 词 。<br />

clauses<br />

run():‐<br />

console::init(),<br />

copystream::copy("source.txt","destination.txt"),<br />

_ = stdIO::readchar(),<br />

succeed().<br />

end implement main<br />

这 里 , 我 们 只 是 调 用 了 类 copystream 中 的 谓 词 copy("source.txt", "destination.txt)。 我 们<br />

打 算 把 名 为 source.txt 的 文 件 中 的 内 容 拷 贝 到 destination.txt 文 件 中 去 。 要 注 意 , 文 件<br />

149


source.txt 必 须 在 工 程 的 exe 文 件 夹 中 。<br />

copystream 类 中 的 代 码 如 下 ( 说 明 在 后 面 ), 声 明 是 :<br />

class copystream<br />

open core<br />

predicates<br />

copy : (string InputFileName, string OutputFileName) procedure (i,i).<br />

end class copystream.<br />

下 面 是 实 现 :<br />

implement copystream<br />

open core<br />

constants<br />

className = "copystream".<br />

classVersion = "".<br />

clauses<br />

classInfo(className, classVersion).<br />

clauses<br />

copy(InFile, OutFile):‐<br />

InputFile = inputStream_file::openFile8(InFile),<br />

OutputFile = outputStream_file::create(OutFile),<br />

copyStream(InputFile,OutputFile),<br />

InputFile:close(),<br />

OutputFile:close().<br />

class predicates<br />

copyStream : (inputStream InputFile, outputStream OutputFile) procedure (i,i).<br />

clauses<br />

copyStream(InputFile, OutputFile):‐<br />

repeat(),<br />

String = InputFile:readLine(),<br />

stdIO::write(String),<br />

OutputFile:write(String, "\n"),<br />

InputFile:endOfStream(),<br />

!.<br />

copyStream(_InputFile, _OutputFile).<br />

class predicates<br />

repeat: () multi ().<br />

clauses<br />

repeat().<br />

repeat() :‐<br />

150


epeat().<br />

end implement copystream<br />

对 上 面 的 子 句 做 些 解 释 :<br />

程 序 开 始 于 mian 中 的 谓 词 copy/2 调 用 ,<br />

copy(InFile,OutFile):‐<br />

这 个 调 用 中 ,InFile 绑 定 于 source.txt 而 OutFile 绑 定 于 destination.txt, 它 们 俩 是 磁 盘 上<br />

的 文 件 。 接 着 继 续 进 行 copy/2 子 句 , 打 开 这 两 个 文 件 :<br />

InputFile = inputStream_file::openFile8(InFile),<br />

OutputFile = outputStream_file::create(OutFile),<br />

source.txt 打 开 为 输 入 流 文 件 , 模 式 是 ANSI,source.txt 绑 定 于 变 量 Inutfile。 从 这 时 起 ,<br />

我 们 要 引 用 source.txt 时 , 只 需 要 直 接 使 用 变 量 Inputfile 就 可 以 了 。 用 这 个 方 法 , 只 需 要 改<br />

变 调 用 中 的 名 字 , 就 可 以 拷 贝 很 多 文 件 了 , 甚 至 还 可 以 让 用 户 输 入 文 件 名 。 对 于 输 出 文 件 ,<br />

是 用 Unicode 模 式 创 建 了 一 个 新 文 件 , 文 件 名 是 destination.txt, 绑 定 于 变 量 Outputfile。<br />

现 在 可 以 拷 贝 了 , 调 用<br />

copyStream(InputFile,OutputFile),<br />

这 个 谓 词 进 行 拷 贝 , 拷 贝 完 了 , 把 输 入 输 出 文 件 都 关 闭<br />

InputFile:close(),<br />

OutputFile:close().<br />

好 好 注 意 一 下 这 两 个 句 子 的 差 别 :<br />

InputFile = inputStream_file::openFile8(InFile),<br />

和<br />

InputFile:close(),<br />

第 一 个 子 句 我 们 从 Inputstream_file 类 中 调 用 一 个 谓 词 , 第 二 个 子 句 我 们 从 对 象 Inputfile<br />

中 调 用 一 个 谓 词 。 打 开 一 个 文 件 时 , 一 个 对 象 被 创 建 了 , 它 会 打 理 那 个 特 定 的 流 的 一 切 。 现<br />

在 我 们 来 看 一 下 拷 贝 过 程 。 关 键 代 码 是 :<br />

copyStream(InputFile, OutputFile):‐<br />

repeat(),<br />

String = InputFile:readLine(),<br />

stdIO::write(String),<br />

stdIO::nl,<br />

OutputFile:write(String, "\n"),<br />

InputFile:endOfStream(),<br />

开 始 是 谓 词 repeat, 它 在 <strong>Prolog</strong> 的 循 环 中 是 很 有 用 的 , 现 在 我 们 先 不 解 释 它 。 接 着 , 把<br />

String 约 束 为 从 Inputfile 中 读 进 来 的 一 行 东 西 , 我 们 看 到 了 如 同 好 的 面 向 对 象 的 做 法 一 样 ,<br />

我 们 是 让 对 象 Inputfile 从 它 控 制 的 流 里 读 一 行 , 再 把 读 到 的 给 String。 然 后 , 把 Sting 写 到 标<br />

准 IO 上 , 我 们 在 屏 幕 消 息 窗 口 中 看 到 了 读 进 来 的 东 西 ; 最 后 , 把 String 写 到 Outputfile 中 。<br />

其 实 , 是 对 象 Outputfile 把 String 写 到 它 当 前 控 制 的 流 中 去 的 。<br />

上 面 这 个 例 子 中 , 我 们 为 copy 谓 词 用 了 一 个 分 开 的 类 。 其 实 不 必 要 , 也 可 以 把 所 有 的<br />

东 西 都 放 在 main.pro 里 。 可 以 这 样 : 建 个 新 工 程 , 目 标 console, 在 main.pro 中 添 加 和 修 改<br />

代 码 如 下 :<br />

class predicates<br />

copystream : (inputStream InputFile, outputStream OutputFile) procedure (i,i).<br />

clauses<br />

copystream(InputFile, OutputFile) :‐<br />

repeat(),<br />

151


String = InputFile:readLine(),<br />

stdIO::write(String), stdIO::nl,<br />

OutputFile:write(String, "\n"),<br />

InputFile:endOfStream(), !.<br />

copystream(_,_).<br />

class predicates<br />

repeat : () multi ().<br />

clauses<br />

repeat().<br />

repeat() :‐<br />

repeat().<br />

clauses<br />

run():‐<br />

console::init(),<br />

InputFile = inputStream_file::openFile8("Source.txt"),<br />

OutputFile = outputStream_File::create("Destination.txt"),<br />

copystream(InputFile, OutputFile),<br />

InputFile:close(),<br />

OutputFile:close(),<br />

_ = stdIO::readChar(),<br />

succeed(). % place your own code here<br />

end implement main<br />

goal<br />

mainExe::run(main::run).<br />

这 就 是 在 VIP 中 使 用 流 的 方 法 : 对 每 个 需 要 用 的 流 创 建 一 个 对 象 , 与 这 个 对 象 通 信 以 得<br />

到 或 交 给 它 所 需 要 的 东 西 。 有 很 多 谓 词 可 以 帮 助 我 们 完 成 这 个 工 作 , 下 面 要 介 绍 一 些 最 常 用<br />

的 谓 词 。 不 过 , 还 应 该 好 好 看 一 下 帮 助 文 件 , 以 了 解 Stream 类 及 其 子 类 的 丰 富 资 源 。 下 面<br />

的 介 绍 只 是 概 貌 , 我 们 认 为 现 在 读 者 已 经 知 道 这 些 谓 词 是 干 什 么 的 。<br />

Stream 类<br />

这 个 类 中 包 含 有 下 面 一 些 ( 当 然 不 仅 只 是 这 些 ) 谓 词 :<br />

close : () procedure ().<br />

关 闭 流 。<br />

getCRLFconversion : () ‐> boolean Enabled procedure ().<br />

返 回 的 值 如 果 为 true, 表 示 流 数 据 被 处 理 时 要 经 过 CRLF 与 LF 间 的 相 互 转 换 ; 为 false<br />

表 示 不 经 过 转 换 而 直 传 。<br />

getMode : () ‐> mode Mode procedure ().<br />

返 回 流 的 当 前 模 式 : 二 进 制 ,ANSI 或 Unicode。<br />

getPosition : () ‐> unsigned64 Position procedure ().<br />

返 回 流 的 当 前 位 置 , 这 个 位 置 就 是 下 一 个 读 或 写 的 位 置 。<br />

setCRLFconversion : (boolean Enabled) procedure (i).<br />

152


规 定 流 数 据 在 处 理 时 要 不 要 进 行 CRLF 和 LF 间 的 转 换 。 需 要 的 话 , 在 写 流 的 时 候 要 先 把<br />

LF 转 换 成 CRLF, 在 读 流 的 时 候 要 把 CRLF 转 换 成 LF。<br />

setMode : (mode Mode) procedure (i).<br />

设 置 读 / 写 模 式 : 二 进 制 ,ANSI 或 Unicode。<br />

setPosition : (unsigned64 Position) procedure (i).<br />

设 置 新 的 流 位 置 , 这 个 位 置 是 下 一 个 读 或 写 的 位 置 。<br />

Inputstream 类<br />

这 个 类 里 含 有 下 面 一 些 谓 词 :<br />

consult : (factDB FactDB) procedure (i).<br />

从 一 个 文 件 中 读 事 实 并 把 它 们 插 入 到 FactDB 中 。<br />

endOfStream : () determ ().<br />

检 查 流 指 针 是 不 是 已 经 到 了 流 的 末 尾 了 。<br />

read : () ‐> _ Value procedure ().<br />

从 输 入 流 中 读 指 定 的 项 。<br />

readBytes : (byteCount Size) ‐> binary Binary procedure (i).<br />

从 输 入 流 中 读 Size 个 字 节 。<br />

readChar : () ‐> char Character procedure ().<br />

从 输 入 流 中 读 字 符 。<br />

readLine : () ‐> string Line procedure ().<br />

从 输 入 流 中 读 行 。<br />

readString : (charCount NumberOfChars) ‐> string String procedure (i).<br />

从 输 入 流 中 读 NumbetOfChars 个 字 符 。<br />

reconsult : (factDB FactDB) procedure (i).<br />

从 一 个 流 中 读 事 实 到 指 定 的 事 实 段 ,reconsult first retracts the fact database。<br />

ungetChar : () procedure ().<br />

返 回 最 后 读 的 字 符 到 输 入 流 。<br />

Outputstream 类<br />

下 面 是 一 些 这 个 类 中 的 谓 词 :<br />

flush : () procedure ().<br />

强 使 流 的 内 部 缓 冲 区 内 容 写 到 资 源 中 去 。<br />

nl : () procedure ().<br />

向 输 出 流 写 一 个 换 行 符 。<br />

save : (factDB FactsSectionName) procedure (i).<br />

保 存 指 定 的 内 部 事 实 段 内 容 到 文 本 流 。<br />

153


write : (...) procedure (...).<br />

写 指 定 的 变 量 到 输 出 流 。<br />

writeBytes : (pointer Value, byteCount Size) procedure (i,i).<br />

写 Size 个 字 节 到 输 出 流 。<br />

writeQuoted : (...) procedure (...).<br />

写 指 定 的 变 量 到 输 出 流 。 如 果 变 量 有 串 ( 字 符 ) 域 的 前 辍 和 后 辍 , 则 它 们 ( 双 引 号 或 单<br />

引 号 ) 也 会 写 到 流 中 去 。<br />

writef : (string FormatString [formatString], ...) procedure (i,...).<br />

产 生 格 式 化 的 输 出 到 输 出 流 。<br />

Inputstream_file 类<br />

下 面 一 些 谓 词 是 InputStream_File 类 中 的 。 要 注 意 , 其 实 这 些 谓 词 是 某 种 构 造 器 : 它 们<br />

创 建 可 以 引 用 的 对 象 。<br />

openFile : (string FileName).<br />

打 开 Unicode 文 件 用 于 输 入 。<br />

openFile : (string FileName, stream::mode Mode).<br />

用 指 定 的 模 式 打 开 输 入 文 件 。<br />

openFile8 : (string FileName).<br />

打 开 ANSI 文 件 用 于 输 入 。<br />

Outputstream_file 类 。<br />

下 面 一 些 谓 词 是 OutputStream_File 类 中 的 。 要 注 意 , 其 实 这 些 谓 词 是 某 种 构 造 器 : 它<br />

们 创 建 可 以 引 用 的 对 象 。<br />

append : (string FileName).<br />

打 开 Unicode 文 件 用 于 输 出 , 把 流 位 置 设 置 到 文 件 的 尾 端 。<br />

append : (string FileName, stream::mode Mode).<br />

打 开 文 件 用 于 输 出 , 把 流 位 置 设 置 到 文 件 的 尾 端 。<br />

append8 : (string FileName).<br />

打 开 ANSI 文 件 用 于 输 出 , 把 流 位 置 设 置 到 文 件 的 尾 端 。<br />

create : (string FileName).<br />

以 Unicode 模 式 创 建 一 个 新 文 件 用 于 输 出 。<br />

create : (string FileName, stream::mode Mode).<br />

创 建 Unicode 模 式 文 件 用 于 输 出 , 数 据 输 出 模 式 设 置 为 Mode。<br />

create8 : (string FileName).<br />

创 建 ANSI 文 件 用 于 输 出 。<br />

openFile : (string FileName).<br />

154


创 建 Unicode 文 件 用 于 输 出 。<br />

openFile : (string FileName, stream::mode Mode).<br />

打 开 文 件 用 于 输 出 , 规 定 初 始 的 流 模 式 。<br />

openFile8 : (string FileName).<br />

打 开 ANSI 文 件 用 于 输 出 。<br />

11.8 文 件 和 目 录<br />

输 入 输 出 谓 词 讲 完 了 , 还 有 一 些 类 , 如 file 类 , directory 类 。 它 们 里 面 有 很 多 有 用 的<br />

谓 词 , 可 以 对 目 录 与 文 件 进 行 操 作 , 可 以 看 看 帮 助 文 件 , 本 书 就 不 讲 了 。<br />

155


第 12 章 数 据 结 构 : 堆 栈 、 队 列 和 树<br />

18<br />

在 编 程 中 我 们 常 常 使 用 数 据 结 构 存 放 数 据 , 这 样 使 用 时 更 有 效 率 。 这 同 时 也 意 味 着 : 数<br />

据 的 不 同 使 用 , 需 要 不 同 的 数 据 结 构 。 前 些 章 中 我 们 已 经 看 到 了 一 些 存 放 ( 或 表 示 ) 数 据 的<br />

结 构 : 事 实 、 函 子 、 数 据 库 和 表 。 在 这 一 章 中 , 我 们 再 介 绍 三 种 在 编 程 时 经 常 使 用 的 数 据 结<br />

构 , 它 们 是 堆 栈 、 队 列 和 树 。 我 们 先 说 在 VIP 中 怎 么 实 现 这 些 结 构 , 再 介 绍 一 些 在 VIP 标 准<br />

类 中 可 以 找 到 的 实 现 , 这 样 就 好 理 解 如 何 使 用 这 些 标 准 类 了 。<br />

12.1 数 据 结 构<br />

数 据 结 构 , 是 在 计 算 机 ( 程 序 ) 中 存 储 数 据 的 方 法 , 这 个 方 法 要 便 于 有 效 地 使 用 数 据 。<br />

各 类 程 序 设 计 中 , 选 择 有 效 的 数 据 结 构 是 一 个 首 要 的 设 计 内 容 , 构 建 大 型 系 统 的 经 验 显 示 ,<br />

实 现 的 难 度 与 最 终 程 序 的 质 量 和 性 能 , 很 大 程 度 上 与 选 择 最 佳 的 数 据 结 构 有 关 。 数 据 结 构 确<br />

定 了 , 要 使 用 的 算 法 也 就 明 确 了 。 有 些 时 候 事 情 总 是 矛 盾 的 , 数 据 结 构 的 选 择 有 可 能 是 因 为<br />

某 种 关 键 任 务 需 要 的 算 法 与 特 定 的 数 据 结 构 最 匹 配 。 不 管 在 哪 种 情 况 下 , 选 择 合 适 的 数 据 结<br />

构 都 是 至 关 重 要 的 。<br />

一 种 编 程 语 言 应 该 可 以 提 供 各 种 选 择 实 现 所 需 要 的 数 据 结 构 , 而 一 个 设 计 良 好 的 数 据 结<br />

构 应 该 允 许 用 户 有 多 种 操 作 使 用 方 式 , 并 要 尽 可 能 减 少 执 行 时 间 与 内 存 空 间 方 面 的 资 源 。 数<br />

据 结 构 是 用 数 据 类 型 以 及 编 程 语 言 提 供 的 对 它 们 的 引 用 与 操 作 来 实 现 的 。 有 许 多 形 式 化 的 设<br />

计 方 法 和 编 程 语 言 , 在 它 们 那 里 是 数 据 结 构 而 不 是 算 法 , 是 关 键 的 构 成 要 素 。 大 多 数 语 言 都<br />

有 某 种 模 块 化 系 统 的 特 征 , 允 许 数 据 结 构 通 过 在 控 制 界 面 后 隐 藏 实 现 细 节 , 以 使 在 不 同 的 应<br />

用 中 可 以 安 全 的 重 用 。 面 向 对 象 的 编 程 语 言 , 如 C++、Java 和 VIP 就 是 用 类 来 达 到 这 个 目 的<br />

的 。<br />

在 实 际 程 序 中 , 我 们 会 通 过 类 的 界 面 使 用 内 建 的 过 程 来 做 这 里 要 做 的 事 , 但 现 在 我 们 要<br />

自 己 来 编 程 数 据 结 构 , 不 考 虑 标 准 类 的 使 用 。 这 样 可 以 使 我 们 更 详 细 地 了 解 不 同 的 结 构 。 这<br />

一 章 中 要 仔 细 看 一 下 表 、 堆 栈 、 队 列 和 树 , 后 面 三 种 是 新 的 数 据 结 构 , 表 在 前 面 我 们 已 经 遇<br />

到 了 。 之 所 以 要 再 把 它 提 出 来 , 是 想 让 读 者 理 解 , 表 是 递 归 结 构 的 。<br />

12.2 再 说 表<br />

我 们 已 经 看 到 了 , 表 是 在 方 括 号 中 用 逗 号 分 隔 开 的 一 系 列 元 素 。 作 为 定 义 , 这 不 错 , 但<br />

也 只 说 对 了 一 部 分 。 为 了 对 表 再 下 一 个 定 义 , 我 们 要 使 用 一 种 叫 作 巴 科 斯 — 诺 尔 范 式 的 方 法 ,<br />

简 写 为 BNF。 在 BNF 中 定 义 一 个 概 念 , 是 用 它 的 名 字 , 加 上 一 个 ::= 符 号 , 后 面 是 所 有 归 入<br />

这 个 定 义 的 可 能 项 目 的 总 括 , 各 个 可 能 项 目 用 一 个 竖 线 分 开 , 读 作 逻 辑 “ 或 ”, 定 义 的 概 念<br />

要 起 个 有 意 义 的 名 字 , 名 字 放 在 尖 括 号 中 。 以 无 符 号 整 数 为 例 , 来 看 一 下 它 的 定 义 。 无 符 号<br />

整 数 是 一 个 数 字 序 列 , 可 以 这 样 定 义 :<br />

::= | | | ...<br />

这 个 定 义 说 , 概 念 可 以 是 一 个 或 一 个 后 面 加 一 个 <br />

或 者 是 三 个 一 组 的 或 …<br />

还 有 , 我 们 可 以 定 义 一 个 是 :<br />

::= 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0<br />

这 个 定 义 说 , 一 个 可 以 是 一 个 自 然 数 ( 包 括 零 )。 这 个 的 定 义 很 完 备 , 很<br />

显 然 , 在 任 何 出 现 的 地 方 都 可 以 使 用 任 意 一 个 上 面 定 义 中 的 十 个 数 字 。 可 能 需 要 限 制<br />

一 下 的 是 出 现 在 前 面 的 0, 不 过 这 也 不 是 必 须 的 。<br />

但 第 一 个 定 义 ,, 出 现 了 一 个 问 题 , 马 上 我 们 就 会 看 到 这 个 定 义 不 太<br />

18<br />

本 章 很 大 部 分 内 容 得 益 于 Wikipedia( 维 基 百 科 ) 关 于 数 据 结 构 、 堆 栈 、 队 列 和 树 的 主 题 。 有 关 树 的 章 节 内 容 部 分 取 材 于 VIP<br />

V5.0 的 用 户 手 册 。<br />

156


好 。 一 个 无 符 号 整 数 可 以 有 任 意 位 数 , 而 这 个 定 义 没 有 表 示 出 所 有 可 能 , 它 需 要 无 限 多 个 表<br />

达 式 才 行 。 解 决 问 题 的 办 法 是 递 归 。 在 BNF 中 可 以 创 建 一 个 递 归 定 义 , 整 数 可 以 这 样 定 义 :<br />

::= | <br />

定 义 现 在 是 这 样 了 : 一 个 无 符 号 整 数 可 以 是 一 个 或 一 个 后 跟 一 个 。 这 第 二 个 选 项 是 说 : 一 个 无 符 号 整 数 可 以 是 一 个 后 跟 一 个 , 也 就 是 说<br />

是 一 组 两 个 , 但 还 有 第 二 个 选 项 说 它 是 一 个 后 跟 两 个 , 如 此 可 以 无 限 地<br />

重 复 下 去 。 这 就 是 递 归 定 义 , 它 其 实 就 是 说 一 个 无 符 号 整 数 可 以 是 任 意 个 组 成 的 序 列 。<br />

现 在 回 到 表 上 来 , 我 们 可 以 给 出 一 个 这 样 的 定 义 :<br />

::= [ ] | [] | [, ]<br />

这 个 定 义 是 说 , 表 可 以 是 个 空 表 [] 或 在 方 括 号 中 有 一 个 元 素 或 是 一 个 元 素 接 一 个 逗 号 再<br />

接 一 个 表 。 由 于 表 可 以 是 空 的 , 后 面 一 项 就 可 以 是 :<br />

[, [ ]]<br />

而 表 可 以 有 一 个 元 素 , 后 面 一 项 也 可 以 是 :<br />

[, []]<br />

而 最 后 , 它 也 可 以 是 :<br />

[, [, ]]<br />

对 上 面 的 情 况 , 我 们 又 可 以 递 归 地 把 展 开 写 成 :<br />

[ , [, [ , [ ] ] ] ]<br />

就 这 样 , 还 可 以 把 表 展 开 到 任 意 我 们 想 要 的 长 度 。 这 正 是 我 们 说 表 是 递 归 结 构 的 意 思 。<br />

我 们 用 递 归 定 义 , 创 建 的 表 可 以 有 任 意 需 要 的 长 度 。<br />

只 是 为 了 方 便 , 不 写 :<br />

[ , [, [ , [ ] ] ] ]<br />

而 写 成 :<br />

[ , , , ]<br />

12.3 堆 栈<br />

堆 栈 在 计 算 机 科 学 中 是 一 个 临 时 的 数 据 结 构 , 这 也 意 味 着 计 算 机 程 序 是 在 运 行 时 才 使 用<br />

堆 栈 , 程 序 结 束 时 它 并 不 像 我 们 存 储 数 据 的 数 据 库 那 样 还 存 在 。 堆 栈 是 基 于 先 进 后 出 (LIFO)<br />

原 则 的 , 可 以 把 它 想 像 成 桌 上 的 一 摞 纸 , 任 何 时 候 把 东 西 放 进 堆 栈 时 , 总 是 把 它 放 在 最 上 面 ,<br />

而 任 何 时 候 要 从 堆 栈 中 取 东 西 的 时 候 , 又 总 是 取 的 最 上 面 的 。 常 常 用 自 助 餐 厅 里 的 弹 出 式 餐<br />

盘 架 来 比 喻 它 , 这 样 的 弹 出 式 餐 盘 架 , 只 能 看 到 和 取 出 最 上 面 的 一 个 餐 盘 , 其 它 的 餐 盘 都 看<br />

不 到 。 当 加 入 新 的 餐 盘 时 , 新 餐 盘 就 成 了 栈 顶 , 下 面 的 餐 盘 看 不 到 并 向 下 压 了 。 当 顶 上 的 餐<br />

盘 取 走 时 , 下 面 的 餐 盘 就 会 顶 上 来 , 原 来 第 二 个 餐 盘 就 最 顶 上 的 了 。 这 个 比 方 说 明 了 两 个 重<br />

要 的 原 则 : 一 是 后 进 先 出 , 二 是 栈 里 的 内 容 被 隐 藏 了 。 只 有 顶 上 的 那 个 盘 子 可 以 看 到 和 取 用 ,<br />

如 果 想 要 看 到 第 三 个 盘 子 , 就 必 须 把 第 一 个 和 第 二 个 盘 子 拿 开 。<br />

在 现 代 计 算 机 系 统 的 各 个 层 级 中 都 广 泛 地 使 用 堆 栈 。 例 如 , 现 代 PC 机 在 体 系 层 就 使 用<br />

堆 栈 , 用 在 了 操 作 系 统 中 断 处 理 和 操 作 系 统 功 能 调 用 的 基 本 设 计 中 。<br />

作 为 抽 象 的 数 据 类 型 , 堆 栈 是 一 个 元 素 的 容 器 , 它 具 有 两 种 基 本 操 作 :push 和 pop。<br />

push, 就 是 在 堆 栈 的 顶 端 放 进 一 个 元 素 , 把 以 前 在 堆 栈 中 的 元 素 向 下 压 。pop, 就 是 取 出 当<br />

前 堆 栈 顶 上 的 元 素 。<br />

其 它 一 些 操 作<br />

现 代 计 算 机 语 言 中 , 堆 栈 实 现 的 操 作 比 仅 仅 push 和 pop 要 多 得 多 。 堆 栈 的 长 度 常 常 可<br />

以 当 做 一 个 参 数 返 回 , 另 一 个 很 有 用 的 操 作 是 top[1]( 也 称 为 peek 或 peak), 它 可 以 返 回 当<br />

前 栈 顶 的 内 容 但 又 不 把 它 从 栈 中 移 走 。<br />

在 VIP 中 , 表 可 以 很 方 便 地 实 现 堆 栈 , 对 pop、peek 和 push 的 实 现 过 程 相 当 简 单 , 我<br />

们 来 看 看 在 VIP 中 如 何 使 用 堆 栈 。 新 建 一 个 名 为 ch12Stack 的 工 程 , 目 标 GUI, 创 建 一 个 ( 不<br />

157


创 建 对 象 的 ) 类 存 放 程 序 ,, 类 的 名 字 是 stackprocs。 我 们 创 建 一 个 事 实 变 量 当 堆 栈 , 名 字 是<br />

stacklist, 声 明 在 stackprocs.pro 中 , 是 这 样 的 :<br />

class facts<br />

stacklist : stringlist :=["ddd", "ccc", "bbb", "aaa"].<br />

这 个 栈 表 里 初 始 化 时 就 带 有 ["ddd", "ccc", "bbb", "aaa"], 所 以 不 用 在 之 前 再 压 入 一 些 元 素<br />

了 。 不 过 , 在 程 序 中 一 般 说 来 堆 栈 开 始 时 都 是 空 的 。<br />

在 stacklist.cl 中 声 明 域 stacklist:<br />

domains<br />

stringlist = string*.<br />

接 着 在 stackprocs.cl 中 声 明 pop 和 push 等 谓 词 为 过 程 。 在 这 种 情 况 下 , 把 谓 词 peek 和<br />

pop 声 明 成 函 数 也 很 好 :<br />

predicates<br />

push : (string Element) procedure (i).<br />

peek : (string Element) determ (o).<br />

pop : (string Element) determ (o).<br />

过 程 push 把 一 个 元 素 放 在 栈 表 的 第 一 个 位 置 上 并 返 回 新 的 栈 表 :<br />

push(Element) :‐<br />

stacklist := [Element | stacklist]. % 把 新 元 素 放 在 栈 的 前 面<br />

peek 看 一 看 第 一 个 元 素 并 返 回 这 个 元 素 , 但 不 改 变 栈 表 :<br />

peek(Element) :‐<br />

[Element | _ ] = stacklist. % 看 一 看 第 一 个 元 素 但 不 改 变 栈 表<br />

pop 过 程 把 栈 表 里 的 第 一 个 元 素 取 走 :<br />

pop(Element) :‐<br />

[Element | Tail] = stacklist, % 取 第 一 个 元 素<br />

stacklist := Tail.<br />

% 并 把 它 从 栈 表 中 拿 掉<br />

还 有 一 些 过 程 可 以 与 堆 栈 一 道 工 作 , 我 们 在 stackprocs.cl 中 添 加 下 面 一 些 声 明 :<br />

dup : () determ.<br />

swap : () determ.<br />

showstack : () .<br />

过 程 dup(duplicate, 复 制 ) 先 peek 后 push, 这 样 栈 顶 是 原 栈 顶 的 复 制 件 , 原 栈 顶 下<br />

压 了 。 在 VIP 中 是 这 样 做 的 :<br />

dup() :‐<br />

[Element | _] = stacklist,<br />

% 取 第 一 个 元 素<br />

stacklist := [Element | stacklist].<br />

% 把 第 一 个 元 素 放 在 栈 顶<br />

swap 是 交 换 , 它 把 栈 顶 最 上 面 两 个 元 素 的 位 置 对 调 , 用 <strong>Prolog</strong> 就 是 :<br />

swap() :‐<br />

[Element1, Element2 | Tail] = stacklist, % 取 最 前 面 两 个 元 素<br />

stacklist := [Element2, Element1 | Tail]. % 交 换 它 们 的 位 置<br />

为 了 方 便 查 看 结 果 , 还 加 了 一 个 过 程 来 显 示 堆 栈 的 内 容 。 这 部 分 正 常 使 用 时 是 不 需 要 的 ,<br />

用 堆 栈 关 心 的 就 是 顶 上 的 元 素 , 否 则 就 不 用 堆 栈 了 :<br />

showstack() :‐<br />

158


stdIO::write("The stack contains: "),<br />

stdIO::nl,<br />

stdIO::write(stacklist).<br />

我 们 利 用 了 stdIO/… 可 以 写 表 的 特 性 。 还 要 当 心 , 这 里 的 谓 词 并 不 完 善 , 在 实 际 程 序 中<br />

对 堆 栈 做 peek 和 pop 操 作 都 要 先 检 查 一 下 , 看 堆 栈 是 不 是 空 的 , 然 后 进 行 适 当 的 操 作 。<br />

为 了 使 用 这 些 谓 词 , 我 们 在 任 务 窗 口 的 任 务 菜 单 中 加 一 个 新 的 菜 单 ,stack, 里 边 再 加<br />

上 Push、Peek、Pop 等 选 项 。 这 些 选 项 的 代 码 ( 用 代 码 专 家 系 统 生 成 标 准 代 码 ) 是 :<br />

predicates<br />

onStackPush : window::menuItemListener.<br />

clauses<br />

onStackPush(_Source, _MenuTag) :‐<br />

Element = vipCommonDialogs::getString("Give a string", "input a string", "DefaultString"), !,<br />

stackprocs::push(Element),<br />

stdIO::write("Element ", Element, " has been pushed"), stdIO::nl.<br />

onStackPush(_Source, _MenuTag).<br />

predicates<br />

onStackPeek : window::menuItemListener.<br />

clauses<br />

onStackPeek(_Source, _MenuTag) :‐<br />

stackprocs::peek(Element), !,<br />

stdIO::write("the peeked element is ", Element), stdIO::nl.<br />

onStackPeek(_Source, _MenuTag).<br />

predicates<br />

onStackPop : window::menuItemListener.<br />

clauses<br />

onStackPop(_Source, _MenuTag) :‐<br />

stackprocs::pop(Element), !,<br />

stdIO::write("the popped element is ", Element), stdIO::nl.<br />

onStackPop(_Source, _MenuTag).<br />

读 者 应 该 可 以 自 己 创 建 其 它 选 项 的 代 码 了 , 它 们 都 差 不 多 。<br />

还 有 更 多 的 可 能 , 比 如 说 旋 转 : 最 顶 上 的 项 以 旋 转 的 方 式 在 栈 里 移 动 。 例 如 , 栈 里 有 三<br />

项 元 素 ,1,2 和 3, 移 动 后 各 元 素 成 了 2,3,1。 旋 转 可 以 有 多 种 方 式 , 最 常 见 的 是 左 旋 和<br />

右 旋 。 右 旋 是 把 第 一 个 元 素 放 到 第 三 个 位 置 上 , 第 二 个 放 在 第 一 , 第 三 个 放 在 第 二 。 左 旋 则<br />

是 另 一 个 方 向 。 下 面 是 这 些 过 程 的 图 示 :<br />

apple<br />

banana<br />

banana ==right rotate cucumber<br />

cucumber<br />

apple<br />

apple<br />

cucumber<br />

banana ==left rotate apple<br />

cucumber<br />

banana<br />

在 <strong>Prolog</strong> 中 , 旋 转 可 以 用 与 交 换 元 素 位 置 相 似 的 方 法 来 实 现 。 请 看 :<br />

rotateRight() :‐<br />

[ E1, E2, E3 | Tail ] = stackList,<br />

stacklist := [ E2, E3, E1 | Tail ].<br />

159


在 这 里 我 们 假 设 stacklist 是 事 实 变 量 。 如 果 它 是 按 一 个 参 数 给 出 来 的 , 要 右 旋 它 时 调 用<br />

谓 词 是 这 样 :<br />

rotateRight(OldStackList, RotatedStacklist)<br />

谓 词 rotateRight/2 的 样 子 是 :<br />

rotateRight( [ E1, E2, E3 | Tail ], [ E2, E3, E1 | Tail ] ) .<br />

我 们 要 说 的 堆 栈 应 用 的 最 后 一 个 例 子 , 计 算 器 用 堆 栈 结 构 来 存 放 数 值 以 实 现 反 向 波 兰 表<br />

示 法 。 表 达 式 可 以 使 用 前 辍 、 后 辍 或 中 辍 符 号 , 从 一 种 表 达 式 转 换 成 另 一 种 表 达 式 , 就 需 要<br />

使 用 堆 栈 。 很 多 编 译 器 在 把 程 序 转 换 成 低 级 代 码 之 前 , 也 使 用 堆 栈 来 分 析 表 达 式 的 语 法 、 程<br />

序 块 等 。<br />

例 如 , 计 算 ((1+2)*4)+3, 可 以 把 它 用 后 辍 法 改 写 , 好 处 是 不 用 优 先 规 则 , 也 不 需 要 用 括<br />

号 。 后 辍 法 表 示 上 面 的 计 算 式 子 是 :<br />

1 2 + 4 * 3 +<br />

表 达 式 的 计 算 可 以 用 一 个 堆 栈 从 左 到 右 地 进 行 。 我 们 把 数 字 叫 作 操 作 数 , 加 号 和 乘 号 叫<br />

运 算 符 。 计 算 的 执 行 规 则 是 :<br />

• 碰 到 一 个 操 作 数 , 就 把 它 压 入 到 堆 栈<br />

• 碰 到 一 个 运 算 符 , 就 弹 出 两 个 操 作 数 , 对 它 们 进 行 运 算 符 指 定 的 运 算 , 而 后 把 结 果<br />

压 回 到 堆 栈<br />

• 最 后 , 输 入 行 空 了 , 就 弹 出 顶 上 的 元 素 , 显 示 。<br />

1 2 + 4 * 3 + 的 处 理 过 程 如 下 ( 每 个 运 算 后 显 示 堆 栈 的 情 况 ):<br />

输 入 操 作 堆 栈<br />

1 压 入 操 作 数 1<br />

2 压 入 操 作 数 2,1<br />

+ 弹 出 两 个 数 相 加 并 压 入 结 果 3<br />

4 压 入 操 作 数 4,3<br />

* 弹 出 两 个 数 相 乘 12<br />

3 压 入 操 作 数 3,12<br />

+ 弹 出 两 个 数 相 加 15<br />

最 后 的 结 果 是 15, 计 算 完 成 之 后 在 栈 顶 。<br />

12.4 队 列<br />

队 列 是 一 个 元 素 序 列 , 队 列 中 的 元 素 保 持 相 互 间 的 次 序 , 可 以 进 行 的 操 作 只 有 在 队 列 的<br />

尾 巴 上 添 加 元 素 和 在 头 上 取 走 元 素 。 这 就 使 得 队 列 成 为 一 种 先 进 先 出 (FIFO) 的 数 据 结 构 。<br />

在 一 个 FIFO 数 据 结 构 中 , 第 一 个 加 到 队 列 中 的 元 素 肯 定 是 第 一 个 移 出 的 元 素 , 这 等 于 说 ,<br />

无 论 什 么 时 候 加 入 一 个 新 的 元 素 , 只 有 在 它 前 面 加 入 的 元 素 都 移 走 了 , 才 可 以 访 问 到 这 个 新<br />

元 素 。<br />

队 列 应 用 于 计 算 机 科 学 、 运 输 及 作 业 服 务 等 方 面 , 在 这 些 地 方 各 种 实 体 如 数 据 、 对 象 、<br />

人 或 事 件 被 存 放 起 来 以 待 逐 个 处 理 。 在 这 种 意 义 下 , 队 列 是 一 个 缓 冲 区 的 功 能 。 规 定 队 列 数<br />

据 结 构 的 特 征 是 基 于 这 样 的 事 实 , 只 能 从 前 头 和 后 头 来 访 问 这 个 结 构 , 更 进 一 步 说 , 是 只 能<br />

从 前 面 移 走 , 只 能 从 后 面 添 加 。 形 象 的 比 喻 队 列 可 以 看 看 检 票 , 还 有 就 是 人 站 在 自 动 扶 梯 上 ,<br />

部 件 在 组 装 线 上 , 或 加 油 站 里 排 队 的 汽 车 。 它 们 有 一 个 共 同 点 : 队 列 的 实 质 是 排 队 等 待 。<br />

上 述 各 种 情 况 中 , 最 前 面 的 顾 客 或 对 象 , 一 定 是 最 先 进 入 队 列 的 , 而 最 后 一 个 也 是 最 后<br />

进 入 队 列 的 。 每 当 一 个 顾 客 为 他 买 的 东 西 付 完 了 账 ( 或 是 一 个 人 离 开 了 自 动 扶 梯 , 或 是 一 个<br />

部 件 被 从 组 装 线 上 取 下 来 ), 这 个 对 象 就 从 前 面 离 开 队 列 。 这 表 示 队 列 的 pop 功 能 。 每 当 一<br />

个 对 象 进 入 队 列 等 待 , 它 就 从 尾 端 加 入 到 队 列 中 , 这 表 示 push 功 能 。 队 列 的 “size” 功 能 ,<br />

可 以 返 回 排 队 的 长 度 , 而 empty 功 能 , 只 有 在 队 列 里 什 么 都 没 有 时 , 才 会 返 回 true。<br />

在 <strong>Prolog</strong> 中 , 用 表 实 现 队 列 并 不 方 便 。 麻 烦 在 push, 要 把 元 素 放 在 最 后 , 程 序 要 把 所<br />

有 队 列 中 的 元 素 动 个 遍 。 所 以 , 我 们 要 用 事 实 数 据 库 来 实 现 队 列 , 但 这 里 也 有 一 个 限 制 : 在<br />

VIP 中 事 实 数 据 库 不 能 为 空 , 但 队 列 可 以 。 如 果 用 事 实 数 据 库 实 现 队 列 , 在 程 序 开 始 时 要 放<br />

一 个 哑 元 素 在 库 中 。 可 以 用 数 据 库 的 标 准 过 程 来 模 拟 ( 实 现 ) 一 个 队 列 。<br />

160


我 们 来 假 设 有 一 个 顾 客 付 款 的 队 列 。 新 建 一 个 名 为 ch12Queue 的 工 程 , 创 建 一 个 类 ( 没<br />

有 对 象 ), 名 为 classQ, 在 classQ.pro 中 , 声 明 带 有 事 实 函 子 q 的 数 据 库 :<br />

class facts<br />

q : (string Name, real Amount) nondeterm.<br />

nondeterm 表 示 事 实 数 据 库 可 以 有 0 或 一 个 或 多 个 项 。 要 操 作 队 列 , 在 classQ.cl 中 声 明<br />

四 个 谓 词 :<br />

predicates<br />

push : (string Name, real Amount) procedure (i,i).<br />

peek : (string Name, real Amount ) procedure (o,o).<br />

pop : (string Name, real Amount) procedure (o,o).<br />

listQ : ().<br />

然 后 在 classQ.pro 中 添 加 这 些 谓 词 的 代 码 。 谓 词 push 是 在 队 列 的 尾 巴 上 加 一 个 元 素 ,<br />

对 此 只 需 要 使 用 assertz 就 可 以 了 ( 注 意 : 是 assertz, 而 不 是 assert):<br />

push(Name, Amount) :‐<br />

assertz(q(Name, Amount)).<br />

要 peek 第 一 个 元 素 ( 但 不 移 走 它 ), 只 需 要 取 它 就 行 了 。 按 这 个 定 义 , 要 求 推 理 机 取 数<br />

据 库 的 第 一 个 项 , 所 以 要 这 样 :<br />

peek(Name, Amount) :‐<br />

q(Name, Amount), !.<br />

peek("0","0").<br />

第 二 个 子 句 是 与 队 列 为 空 时 的 情 况 匹 配 的 。 此 时 返 回 的 值 有 特 殊 含 义 , 我 们 在 菜 单 里 来<br />

说 。<br />

弹 出 第 一 个 元 素 , 只 需 要 取 出 retract 就 好 了 :<br />

pop(Name, Amount) :‐<br />

retract(q(Name, Amount)), !.<br />

pop("0","0").<br />

变 量 Name 和 Amount 将 包 含 数 据 库 中 第 一 个 事 实 的 值 。 如 果 队 列 是 空 的 ,Name 的 返<br />

回 值 就 是 0( 零 )。<br />

最 后 , 还 要 有 一 个 谓 词 来 显 示 队 列 里 的 项 , 如 果 队 列 为 空 , 就 什 么 也 不 显 示 :<br />

listQ() :‐<br />

q(Name, Amount), !,<br />

stdIO::write(Name, “ ”, Amount),<br />

stdIO::nl,<br />

foreach q(Name, Amount)<br />

do stdIO::write(Name, " ", Amount),<br />

stdIO::nl<br />

end foreach.<br />

listQ() :‐<br />

stdIO::write("nothing"), stdIO::nl.<br />

% 看 看 队 列 里 有 没 有 东 西<br />

% 如 果 队 列 是 空 的 , 就 要 这 样 说<br />

要 让 这 些 谓 词 工 作 , 在 任 务 窗 口 的 任 务 菜 单 中 创 建 一 个 菜 单 项 , 名 为 Q, 给 它 加 上 四 个<br />

选 项 :push、peek、pop 和 listQ。 下 面 是 代 码 专 家 系 统 产 生 标 准 代 码 后 加 入 或 修 改 的 代 码 :<br />

predicates<br />

161


onQPush : window::menuItemListener.<br />

clauses<br />

onQPush(Source, _MenuTag) :‐<br />

NewForm = enterdata::new(Source), NewForm:show().<br />

predicates<br />

onQPeek : window::menuItemListener.<br />

clauses<br />

onQPeek(_Source, _MenuTag) :‐<br />

classQ::peek(Name, Amount),<br />

Name "0", !,<br />

stdIO::write(“q(“,Name, " ", Amount, ") is first in the queue"),<br />

stdIO::nl.<br />

onQPeek(_Source, _MenuTag) :‐<br />

stdIO::write("Queue is empty"),<br />

stdIO::nl.<br />

predicates<br />

onQPop : window::menuItemListener.<br />

clauses<br />

onQPop(_Source, _MenuTag) :‐<br />

classQ::pop(Name, Amount),<br />

Name "0", !,<br />

stdIO::write(“q(“,Name, " ", Amount, ") is first in the queue and is retracted"),<br />

stdIO::nl.<br />

onQPop(_Source, _MenuTag) :‐<br />

stdIO::write("Queue is empty"),<br />

stdIO::nl.<br />

predicates<br />

onQListq : window::menuItemListener.<br />

clauses<br />

onQListq(_Source, _MenuTag) :‐<br />

stdIO::write("The queue contains:"), stdIO::nl,<br />

classQ::listQ().<br />

用 一 个 表 格 来 给 队 列 添 加 新 项 。 表 格 有 两 个 编 辑 控 件 , 一 个 是 name_ctl, 另 一 个 是<br />

amount_ctl。 点 击 按 钮 时 , 这 两 个 控 件 的 内 容 就 会 加 到 队 列 中 去 。 下 面 是 加 到 表 格 的 代<br />

码 :<br />

predicates<br />

onOkClick : button::clickResponder.<br />

clauses<br />

onOkClick(_Source) = button::defaultAction :‐<br />

Name = name_ctl:gettext(),<br />

Amount = toterm(address_ctl:gettext()),<br />

classQ::push(Name, Amount),<br />

stdIO::write(“q(“,Name, " ", Amount, ") is pushed at the end of the queue"), stdIO::nl.<br />

运 行 程 序 , 试 试 操 控 队 列 。 为 了 更 有 趣 , 再 加 两 个 谓 词 , 一 个 重 复 队 列 中 的 第 一 个 项 ,<br />

另 一 个 把 队 列 前 两 个 项 交 换 位 置 。 下 面 是 放 在 classQ.pro 中 的 谓 词 子 句 :<br />

162


duplicate(Name, Amount) :‐<br />

q(Name, Amount), !,<br />

asserta(q(Name, Amount) ).<br />

duplicate("0","0").<br />

swap(Name1, Amount1, Name2, Amount2) :‐<br />

retract(q(Name1, Amount1)), % 移 去 队 列 中 的 第 一 项<br />

assert(qout(Name1, Amount1)), % 暂 存 第 一 项<br />

retract(q(Name2, Amount2)), !, % 取 队 列 中 第 二 项<br />

asserta(q(Name1, Amount1)), % 把 它 们 逆 序 放 回 队 列 中<br />

asserta(q(Name2, Amount2)),<br />

retract(qout(Name1, Amount1)). % 移 去 暂 存 的 事 实<br />

swap("0","0","0","0") :‐<br />

retract(qout(Name1, Amount1)), % 移 去 暂 存 的 项<br />

asserta(q(Name1, Amount1)). % 恢 复 它 在 队 列 中 的 位 置<br />

在 classQ.cl 中 的 声 明 是 :<br />

duplicate : (string Name, real Amount) procedure (o,o).<br />

swap : (string Name1, real Amount1, string Name2, real Amount2) determ (o,o,o,o).<br />

为 了 使 用 这 两 个 谓 词 , 在 任 务 菜 单 中 再 加 两 个 选 项 ,Duplicate 和 Swap。 下 面 是 对 代 码<br />

专 家 在 taskmenu.pro 中 生 成 的 代 码 的 修 改 :<br />

predicates<br />

onQDuplicate : window::menuItemListener.<br />

clauses<br />

onQDuplicate(_Source, _MenuTag) :‐<br />

classQ::duplicate(Name, Address),<br />

Name "0", !,<br />

stdIO::write(Name, " ", Address, " has been duplicated"), stdIO::nl.<br />

onQDuplicate(_Source, _MenuTag) :‐<br />

stdIO::write("Queue is empty, nothing to duplicate"), stdIO::nl.<br />

predicates<br />

onQSwap : window::menuItemListener.<br />

clauses<br />

onQSwap(_Source, _MenuTag) :‐<br />

classQ::swap(Name1, Address1, Name2, Address2),<br />

Name1 "0", !,<br />

stdIO::write("(",Name1, " ", Address1, ") and (", Name2, "", Address2, ") have been swapped"),<br />

stdIO::nl.<br />

onQSwap(_Source, _MenuTag) :‐<br />

stdIO::write("Queue contains less than two items. Nothing to swap"), stdIO::nl.<br />

好 好 转 转 这 些 程 序 , 掌 握 好 堆 栈 和 队 列 !<br />

12.5 树<br />

不 仅 子 句 可 以 递 归 , 数 据 结 构 也 可 以 递 归 , 如 同 我 们 在 表 中 看 到 的 那 样 。 事 实 上 , 广 泛<br />

使 用 的 编 程 语 言 中 , 只 有 <strong>Prolog</strong> 可 以 允 许 定 义 递 归 的 数 据 结 构 。 如 果 一 个 数 据 结 构 允 许 包<br />

163


含 像 它 自 己 一 样 的 结 构 , 那 它 就 是 递 归 的 数 据 类 型 。 这 与 递 归 的 子 句 在 它 自 身 中 调 用 它 自 己<br />

是 相 似 的 。<br />

最 基 本 的 递 归 数 据 结 构 是 表 , 尽 管 不 是 一 下 子 就 能 看 出 它 的 递 归 结 构 。 很 多 的 表 处 理 功<br />

能 都 内 建 在 <strong>Prolog</strong> 中 了 , 在 第 10 章 和 本 章 的 第 一 节 已 经 讨 论 过 表 , 这 一 节 和 下 一 节 我 们 要<br />

讨 论 另 外 的 递 归 数 据 类 型 , 实 现 它 , 并 用 它 来 展 现 一 个 很 快 的 排 序 程 序 。 这 种 递 归 的 数 据 类<br />

型 结 构 就 是 树 ( 见 图 12.1), 重 要 的 是 树 里 的 每 一 个 分 支 本 身 就 是 一 个 树 , 正 因 为 如 此 这 个<br />

结 构 才 是 递 归 的 。 图 12.1 是 一 个 家 庭 成 员 关 系 的 树 图 例 子 , 可 以 把 这 个 树 读 作 Cathy 是<br />

Michael 和 Melody 的 孩 子 ,Michael 是 Charles 和 Hazel 的 孩 子 , 等 等 。 可 能 人 们 更 习 惯 图 中<br />

的 父 母 画 在 孩 子 的 上 面 , 不 过 图 12.1 中 也 是 一 样 的 。<br />

图 12.1<br />

家 庭 成 员 关 系 的 树<br />

树 在 分 类 中 是 很 重 要 的 , 而 且 它 还 广 泛 应 用 在 问 题 求 解 中 ( 如 人 工 智 能 ), 在 这 里 使 用<br />

树 状 结 构 来 查 找 特 定 问 题 域 中 的 一 个 解 。 一 个 树 由 节 点 和 链 构 成 , 图 12.1 中 矩 形 表 示 节 点<br />

而 箭 头 表 示 链 。 节 点 有 名 字 做 标 识 , 对 于 节 点 Cathy, 可 以 说 它 链 接 于 节 点 Michael 和 节 点<br />

Melody。 在 讨 论 树 的 时 候 , 我 们 说 Michael 是 Cathy 的 左 链 而 Melody 是 Cathy 的 右 链 ,Cathy<br />

也 称 为 Michael 和 Melody 的 父 节 点 , 而 它 们 也 叫 做 Cathy 的 子 节 点 。 以 此 类 推 ,Michael 是<br />

Charles 和 Hazel 节 点 的 父 节 点 , 它 们 是 Michael 的 子 节 点 。 在 树 中 , 每 个 节 点 都 至 少 有 一 个<br />

父 节 点 和 一 个 子 节 点 , 只 有 树 中 最 顶 上 的 节 点 和 最 下 面 的 节 点 例 外 , 前 者 称 为 树 的 根 , 后 者<br />

称 为 树 的 叶 。 在 图 12.1 中 , 每 个 节 点 有 两 个 子 节 点 , 但 这 并 非 必 须 如 此 , 另 一 方 面 , 树 中<br />

的 节 点 有 且 只 有 一 个 父 节 点 , 当 然 根 节 点 除 外 。<br />

看 一 下 图 12.1, 树 的 递 归 特 性 是 很 明 显 的 。Cathy 是 全 树 的 根 节 点 , 但 Michael 是 一 个<br />

子 树 的 根 节 点 , 这 个 子 树 由 节 点 Michael、Charles 和 Hazel 构 成 。 对 任 何 一 个 树 这 都 是 成 立<br />

的 : 一 个 节 点 下 的 所 有 节 点 也 形 成 一 个 树 。<br />

在 这 一 节 我 们 只 讨 论 一 种 广 泛 使 用 的 特 殊 类 型 的 树 : 二 进 制 树 。 二 进 制 树 是 每 个 节 点 最<br />

多 只 有 两 个 子 节 点 的 树 , 其 中 子 节 点 分 为 左 子 和 右 子 。 我 们 再 定 义 一 些 概 念<br />

19 :<br />

• 某 个 节 点 的 深 度 是 指 由 根 到 这 个 节 点 的 路 径 长 度 , 如 图 12.1 中 Charles 节 点 的 深 度<br />

是 2<br />

• 同 一 深 度 所 有 节 点 的 集 合 有 时 又 称 为 树 的 一 层 , 节 点 Michael 和 Melody 是 同 一 层<br />

的<br />

• 根 节 点 的 深 度 为 0<br />

• 树 的 高 度 , 是 它 最 大 的 叶 深 度 。 图 12.1 中 树 的 高 度 是 2<br />

• 只 有 根 节 点 的 树 , 高 度 是 0<br />

• 兄 弟 节 点 是 具 有 同 一 父 节 点 的 那 些 节 点 。Michael 和 Melody 节 点 是 兄 弟 节 点<br />

• 如 果 节 点 p 到 节 点 q 之 间 有 路 径 , 而 p 节 点 比 q 节 点 更 接 近 根 , 则 p 节 点 是 q 节<br />

19<br />

取 材 于 Wikipedia。<br />

164


点 的 前 辈 节 点 而 q 节 点 是 p 节 点 的 晚 辈 节 点 。 图 12.1 中 ,Cathy 是 所 有 节 点 的 前 辈<br />

节 点 , 而 Charles 节 点 是 Michael 节 点 的 晚 辈 节 点<br />

• 一 个 节 点 的 大 小 是 所 有 它 的 晚 辈 节 点 数 再 加 上 它 自 己<br />

• 入 度 是 从 前 辈 节 点 到 达 一 个 节 点 的 链 数 , 根 节 点 在 树 中 是 唯 一 一 个 入 度 为 0 的 节 点<br />

• 出 度 是 离 开 一 个 节 点 向 晚 辈 节 点 的 链 数 , 在 二 进 制 树 中 , 树 的 出 度 可 以 是 0、1 或<br />

2。<br />

12.6 作 为 数 据 结 构 的 树<br />

Niklaus Wirth 写 的 《 算 法 + 数 据 结 构 = 程 序 》 一 书 , 普 及 了 递 归 类 型 。 他 在 上 世 纪 70<br />

年 代 从 ALGOL60 中 衍 生 出 了 Pascal, 发 表 了 他 的 著 作 。 在 Pascal 中 他 没 有 实 现 递 归 类 型 , 但<br />

他 确 实 讨 论 了 如 果 有 了 递 归 会 是 什 么 样 。<strong>Visual</strong><strong>Prolog</strong> 实 现 了 递 归 类 型 的 定 义 , 例 如 , 可 以<br />

这 样 定 义 一 个 树 :<br />

domains<br />

treetype = tree(string, treetype, treetype)<br />

这 个 声 明 说 , 一 个 类 型 为 treetype 的 变 量 , 写 做 tree 函 子 , 而 这 个 函 子 的 第 一 个 参 数<br />

是 串 类 型 的 ( 可 以 用 来 表 示 节 点 的 名 字 ), 第 二 和 第 三 个 参 数 是 treetype 的 。 我 们 可 以 解 释<br />

成 第 二 个 参 数 是 左 边 的 子 树 而 第 三 个 参 数 是 右 边 的 子 树 。 这 样 说 来 , 构 成 treetype 的 东 西 ( 节<br />

点 ) 有 名 字 , 还 ( 左 和 右 的 ) 连 结 到 treetype 的 其 它 东 西 ( 节 点 ) 上 , 可 以 对 照 图 12.1 看<br />

一 下 。 不 过 这 还 不 够 , 这 个 声 明 没 有 提 供 递 归 的 结 束 , 而 现 实 中 树 也 不 会 无 限 制 地 继 续 下 去 ,<br />

有 些 节 点 , 也 就 是 叶 , 就 没 有 再 链 到 子 树 , 这 一 点 在 声 明 中 要 注 意 。 这 正 是 我 们 定 义 树 的 两<br />

类 节 点 的 原 因 : 普 通 节 点 就 像 我 们 上 面 treetype 定 义 的 那 样 , 有 左 右 子 树 ; 还 有 没 有 左 右 子<br />

树 的 节 点 。 可 以 这 样 实 现 它 , 允 许 treetype 有 两 个 函 子 之 一 : 带 三 个 参 数 的 tree 或 不 带 参<br />

数 的 empty。 一 个 treetype 的 声 明 就 成 了 这 样 :<br />

domains<br />

treetype = tree(string, treetype, treetype) or empty<br />

这 里 的 or 用 来 替 代 分 号 。 注 意 , 名 字 tree( 一 个 有 三 个 参 数 的 函 子 ) 和 empty( 不 带<br />

参 数 的 函 子 ) 是 由 编 程 者 起 的 , 在 <strong>Prolog</strong> 中 它 们 都 没 有 预 定 义 什 么 东 西 , 也 可 以 改 用 其 它<br />

的 如 xxx 和 yyy, 是 一 样 的 , 当 然 后 面 这 样 的 名 字 无 助 于 对 声 明 的 理 解 。<br />

现 在 就 很 好 理 解 了 , 我 们 其 实 定 义 了 一 个 有 两 种 节 点 的 树 : 有 的 节 点 有 子 树 而 有 的 节 点<br />

没 有 子 树 。 也 可 以 换 个 方 式 来 解 释 : 没 有 子 树 的 节 点 也 是 treetype 的 , 只 是 它 们 带 的 子 树 是<br />

empty。 图 12.1 中 Charles 节 点 可 以 这 样 表 示 :<br />

tree("Charles", empty, empty).<br />

它 表 示 这 个 节 点 的 名 字 是 Charles, 它 的 左 右 子 树 都 是 empty。 于 是 我 们 可 以 这 样 来 鉴<br />

别 叶 : 一 个 带 有 两 个 空 子 树 的 节 点 是 叶 。<br />

Michael 节 点 开 始 的 子 树 可 以 这 样 表 示 :<br />

tree("Michael",<br />

% ( 根 ) 节 点 名<br />

tree("Charles", empty, empty), % 左 子 树<br />

tree("Hazel", empty, empty) % 右 子 树<br />

). % 结 束 括 号<br />

我 们 可 以 看 到 , 左 子 树 和 右 子 树 只 是 叶 , 但 其 结 构 与 其 它 节 点 是 一 样 的 。 图 12.1 整 个<br />

的 树 可 以 表 示 为 :<br />

tree("Cathy",<br />

tree("Michael",<br />

% 左 子 树 节 点<br />

tree("Charles", empty, empty), % 左 子 树 的 左 子 树<br />

tree("Hazel", empty, empty)) % 左 子 树 的 右 子 树<br />

tree("Melody",<br />

% 右 子 树 节 点<br />

tree("Jim", empty, empty), % 右 子 树 的 左 子 树<br />

165


tree("Eleanor", empty, empty))) % 右 子 树 的 右 子 树<br />

这 样 的 梯 状 排 列 只 是 为 了 便 于 阅 读 ,<strong>Prolog</strong> 并 不 要 求 非 得 这 样 , 打 印 树 时 也 没 有 这 个 要<br />

求 。 相 同 数 据 结 构 的 另 一 个 写 法 是 :<br />

tree("Cathy"<br />

tree("Michael", tree("Charles", empty, empty), tree("Hazel", empty, empty))<br />

tree("Melody", tree("Jim", empty, empty), tree("Eleanor", empty, empty)))<br />

可 以 看 出 , 这 个 结 构 是 由 函 子 、 逗 号 及 圆 括 号 定 义 的 , 外 观 由 人 的 阅 读 习 惯 而 定 。 要 知<br />

道 , 这 不 是 <strong>Prolog</strong> 子 句 , 而 是 一 个 复 合 的 数 据 结 构 。<br />

再 说 一 下 , 这 看 起 来 效 率 并 不 高 , 这 样 表 示 一 个 树 多 少 有 点 笨 拙 。 在 使 用 树 的 时 候 , 可<br />

能 不 这 么 做 。 可 以 让 计 算 机 创 建 一 个 树 , 从 用 它 到 废 了 它 可 以 根 本 就 看 不 到 这 个 树 。 对 计 算<br />

机 而 言 , 树 就 是 一 个 指 针 结 构 , 它 可 以 很 容 易 地 维 护 和 来 回 穿 过 。<br />

12.7 在 树 中 穿 行<br />

在 讨 论 如 何 创 建 一 个 树 之 前 , 先 来 看 一 下 如 果 有 了 一 个 树 我 们 拿 它 怎 么 办 。 最 常 用 的 树<br />

操 作 , 是 检 视 所 有 的 节 点 并 对 其 进 行 某 种 处 理 。 可 以 查 找 一 个 特 定 的 值 ( 比 如 对 一 个 问 题 的<br />

解 ) 或 是 采 集 树 中 所 有 的 值 。 这 就 是 所 谓 在 树 中 穿 行 。 基 本 的 算 法 有 如 下 三 个 步 骤 :<br />

1. 如 果 树 是 空 的 , 不 用 做 什 么 ( 这 里 事 实 上 是 遇 到 了 叶 的 空 子 树 , 而 对 空 树 是 不 需 要<br />

做 什 么 的 ),<br />

2. 否 则 , 处 理 当 前 节 点 , 然 后 穿 行 左 子 树 ,<br />

3. 左 子 树 处 理 完 了 , 接 着 穿 行 右 子 树 。<br />

与 树 本 身 一 样 , 这 个 算 法 是 递 归 的 : 它 对 待 左 右 子 树 与 对 待 原 来 的 树 是 完 全 相 同 的 。 用<br />

<strong>Prolog</strong> 表 示 有 两 个 子 句 , 一 个 用 于 空 树 , 一 个 用 于 非 空 树 :<br />

traverse(empty). /* 什 么 也 不 做 */<br />

traverse(tree(Node, Left, Right)) :‐<br />

do_something_with_Node,<br />

% 处 理 节 点<br />

traverse(Left),<br />

% 处 理 左 子 树<br />

traverse(Right).<br />

% 处 理 右 子 树<br />

图 12.2<br />

深 度 优 先 的 树 处 理 流 程<br />

这 样 的 处 理 流 程 称 为 深 度 优 先 搜 索 , 因 为 它 在 回 头 处 理 其 它 分 支 前 , 先 尽 可 能 深 地 处 理<br />

完 一 个 分 支 到 底 ( 图 12.2 示 )。 要 了 解 它 的 动 作 过 程 , 看 看 下 面 的 例 子 程 序 , 这 个 程 序 穿 行<br />

一 个 树 并 打 印 出 所 有 单 元 。<br />

为 这 个 程 序 创 建 一 个 新 的 工 程 , 目 标 是 console, 在 main.cl 中 输 入 声 明 :<br />

domains<br />

166


treetype = tree(string, treetype, treetype) ; empty().<br />

predicates<br />

traverse : (treetype).<br />

在 main.pro 中 添 加 下 面 这 些 子 句 :<br />

implement main<br />

open core, stdIO<br />

clauses<br />

traverse(empty).<br />

traverse(tree(Name,Left,Right)):‐<br />

write(Name,'\n'),<br />

traverse(Left),<br />

traverse(Right).<br />

clauses<br />

run():‐<br />

console::init(),<br />

FamilyTree = tree("Cathy",<br />

traverse(FamilyTree),<br />

_ = stdIO::readchar(),<br />

tree("Michael", tree("Charles", empty, empty), tree("Hazel", empty, empty)),<br />

tree("Melody", tree("Jim", empty, empty), tree("Eleanor", empty, empty))),<br />

succeed(). % place your own code here<br />

end implement main<br />

Cathy<br />

Michael<br />

Charles<br />

Hazel<br />

Melody<br />

Jim<br />

Eleanor<br />

对 图 12.1 和 12.2 中 的 树 , 程 序 会 打 印 出 :<br />

读 者 可 以 很 容 易 地 改 编 这 个 程 序 对 各 个 单 元 进 行 一 些 其 它 的 处 理 , 替 代 打 印 。<br />

显 然 , 深 度 优 先 搜 索 与 <strong>Prolog</strong> 对 知 识 库 的 搜 索 很 相 似 , 后 者 的 搜 索 是 通 过 把 子 句 按 树<br />

的 结 构 安 排 好 , 并 追 寻 各 个 分 支 直 到 询 问 失 败 。 只 要 愿 意 , 也 可 以 用 下 面 的 <strong>Prolog</strong> 子 句 形<br />

式 来 描 述 前 面 的 树 :<br />

father_of("Cathy", "Michael").<br />

mother_of("Cathy", "Melody").<br />

father_of("Michael", "Charles").<br />

mother_of("Michael", "Hazel").<br />

...<br />

这 样 , 就 有 了 两 种 不 同 的 方 法 来 描 述 家 庭 成 员 : 用 树 结 构 或 是 用 一 条 一 条 的 子 句 。 后 者<br />

只 有 在 树 结 构 只 是 要 表 示 各 个 个 体 间 的 关 系 时 , 才 更 可 取 。 不 过 这 种 描 述 方 法 无 法 把 树 整 体<br />

作 为 一 个 单 一 的 复 合 数 据 结 构 , 而 正 如 我 们 已 经 看 到 的 那 样 , 复 合 数 据 结 构 是 非 常 有 用 的 ,<br />

167


因 为 它 们 简 化 了 计 算 工 作 的 难 度 。<br />

12.8 创 建 树<br />

创 建 树 的 一 个 办 法 就 是 像 我 们 在 前 面 的 例 子 那 样 , 写 一 个 嵌 套 的 函 子 及 其 参 数 结 构 。 不<br />

过 , 这 不 是 个 好 办 法 , 它 很 容 易 出 错 , 也 无 法 动 态 地 改 变 树 。 所 以 通 常 是 用 计 算 的 方 法 来 创<br />

建 树 , 先 创 建 一 个 只 是 由 根 节 点 构 成 的 树 , 然 后 再 不 断 地 加 上 下 一 个 节 点 。 当 需 要 在 一 个 树<br />

中 加 节 点 时 , 首 先 要 找 到 加 这 个 节 点 的 位 置 , 要 一 直 找 到 分 支 插 入 新 的 节 点 的 叶 , 在 那 里 插<br />

入 。 每 一 步 中 , 通 过 <strong>Prolog</strong> 的 合 一 过 程 , 一 个 空 子 树 被 一 个 非 空 子 树 所 替 代 。<br />

创 建 树 需 要 一 个 谓 词 , 我 们 叫 它 createTree 吧 , 由 起 始 时 的 数 据 创 建 只 有 一 个 单 元 的 树<br />

是 小 菜 一 碟 :<br />

create_tree(N, tree(N, empty, empty)).<br />

这 里 的 意 思 是 : 如 果 要 在 空 树 中 插 入 一 个 节 点 N, 其 结 果 是 一 个 节 点 带 着 两 个 空 子 树 ,<br />

把 带 有 节 点 N 的 一 个 单 元 的 树 写 做 (N,empty,empty)。 创 建 一 个 树 结 构 同 样 地 简 单 , 下 面 的 过<br />

程 带 三 个 参 数 , 第 一 第 二 个 参 数 用 于 输 入 ( 新 节 点 与 已 有 的 树 ) 而 第 三 个 参 数 用 于 输 出 新 的<br />

树 。 每 一 个 参 数 都 是 一 个 树 , 过 程 把 第 一 个 树 当 做 第 二 个 树 的 左 子 树 插 入 , 得 到 结 果 的 第 三<br />

个 树 :<br />

insert_left(X, tree(A, _, B), tree(A, X, B)). /* (i,i,o) */<br />

注 意 , 这 个 规 则 没 有 体 部 , 执 行 它 时 没 有 什 么 步 骤 。 计 算 机 所 要 做 的 , 就 是 在 合 适 的 位<br />

置 上 匹 配 各 个 参 数 , 这 就 完 了 。 比 如 , 要 把 tree("Michael", empty, empty) 做 为 tree("Cathy",<br />

empty, empty) 的 左 子 树 插 入 , 只 需 要 执 行 子 句 :<br />

insert_left(tree("Michael", empty, empty),<br />

tree("Cathy", empty, empty),<br />

NewTree)<br />

这 样 ,NewTree 马 上 就 约 束 为 :<br />

tree("Cathy", tree("Michael", empty, empty), empty).<br />

这 就 是 一 步 一 步 建 造 树 的 一 种 方 法 , 下 面 的 程 序 说 明 了 这 种 技 术 。 程 序 中 要 插 入 的 项 是<br />

由 子 句 run() 里 给 出 的 , 而 在 实 际 应 用 中 , 要 插 入 的 项 会 来 自 于 外 部 输 入 , 比 如 一 个 项 表 或<br />

是 数 据 库 等 。<br />

创 建 一 个 新 的 工 程 ( 目 标 console), 在 main.cl 声 明 域 和 谓 词 :<br />

domains<br />

treetype = tree(string,treetype,treetype) ; empty().<br />

predicates<br />

create_tree: (string,treetype) procedure (i,o).<br />

insert_left : (treetype,treetype,treetype) determ (i,i,o).<br />

insert_right : (treetype, treetype, treetype) determ (i,i,o).<br />

在 main.pro 中 , 输 入 下 面 的 子 句 :<br />

clauses<br />

create_tree(A,tree(A,empty,empty)).<br />

insert_left(X,tree(A,_,B),tree(A,X,B)).<br />

insert_right(X,tree(A,B,_),tree(A,B,X)).<br />

clauses<br />

168


un():‐<br />

console::init(),<br />

/* 先 创 建 些 一 个 单 元 的 树 ... */<br />

/* ... 再 把 它 们 接 在 一 起 ... */<br />

/* ... 打 印 结 果 */<br />

run().<br />

create_tree("Charles",CharlesTree),<br />

create_tree("Hazel",HazelTree),<br />

create_tree("Michael",MichaelTree),<br />

create_tree("Jim",JimTree),<br />

create_tree("Eleanor",EleanorTree),<br />

create_tree("Melody",MelodyTree),<br />

create_tree("Cathy",CathyTree),<br />

insert_left(CharlesTree, MichaelTree, Mi2),<br />

insert_right(HazelTree, Mi2Tree, Mi3),<br />

insert_left(JimTree, MelodyTree, Me2),<br />

insert_right(EleanorTree, Me2, Me3),<br />

insert_left(Mi3, CathyTree, Ca2),<br />

insert_right(Me3, Ca2, Ca3),<br />

stdIO::write(Ca3,'\n'),<br />

_ = stdIO::readChar(), !,<br />

succeed().<br />

end implement main<br />

% place your own code here<br />

程 序 打 印 出 它 所 创 建 的 树 。 读 者 也 可 以 扩 展 这 个 程 序 , 让 打 印 出 来 的 树 更 便 于 阅 读 。<br />

这 个 程 序 的 一 个 问 题 是 , 我 们 建 造 树 时 使 用 了 很 多 变 量 , 这 是 因 为 <strong>Prolog</strong> 的 变 量 一 经<br />

绑 定 , 就 没 有 办 法 改 变 这 个 值 。 这 是 程 序 中 为 什 么 要 使 用 这 么 多 变 量 名 的 原 因 , 每 次 要 建 一<br />

个 新 树 , 就 需 要 一 个 新 变 量 。 这 里 大 量 的 变 量 名 是 很 不 寻 常 的 , 更 常 用 的 办 法 是 , 重 复 过 程<br />

通 过 递 归 调 用 自 己 得 到 新 的 变 量 , 因 为 每 次 调 用 都 会 有 一 组 独 立 的 变 量 。<br />

12.9 二 进 制 树<br />

目 前 为 止 , 我 们 都 是 用 树 来 表 示 它 的 元 素 间 的 关 系 。 对 树 来 说 , 这 不 是 它 的 最 佳 用 途 ,<br />

因 为 <strong>Prolog</strong> 子 句 也 可 以 做 这 样 的 事 。 但 树 还 有 其 它 的 用 途 , 它 可 以 提 供 一 种 能 快 速 找 到 数<br />

据 的 存 储 方 法 。 为 这 个 目 的 建 造 的 树 称 为 搜 索 树 , 以 用 户 的 观 点 看 , 树 的 结 构 不 带 什 么 信 息 ,<br />

它 只 是 一 种 比 表 或 数 组 更 快 的 方 法 。 回 想 一 下 , 穿 行 一 个 普 通 的 树 , 要 检 查 当 前 的 节 点 ( 单<br />

元 ) 而 后 接 着 是 它 的 子 树 。 如 果 树 结 构 组 织 得 好 , 只 需 要 搜 索 一 个 子 树 , 但 并 不 总 能 这 样 ,<br />

有 时 要 找 一 个 特 殊 项 , 就 不 得 不 把 整 个 树 的 每 个 单 元 都 走 一 遍 。<br />

在 一 个 有 N 个 元 素 的 普 通 树 中 搜 索 的 时 间 , 平 均 来 说 , 与 N 成 正 比 。 二 进 制 树 的 构 造 ,<br />

可 以 预 知 要 查 找 的 单 元 在 哪 个 子 树 中 。 这 是 通 过 定 义 数 据 项 的 一 种 顺 序 关 系 , 如 按 字 母 顺 序<br />

或 数 字 顺 序 排 列 来 实 现 的 。 左 子 树 中 的 项 , 在 当 前 单 元 项 之 前 , 也 在 右 子 树 项 之 前 , 参 见 图<br />

12.3 的 示 例 。 要 注 意 , 按 不 同 的 顺 序 添 加 的 相 同 名 字 , 会 产 生 稍 微 不 同 的 树 。 尽 管 这 个 树 中<br />

有 十 个 名 字 , 但 从 最 上 层 开 始 , 确 定 处 理 左 子 树 或 右 子 树 , 最 多 只 要 五 步 就 可 以 找 到 任 何 一<br />

个 名 字 。<br />

169


图 12.3<br />

二 进 制 搜 索 树<br />

在 二 进 制 树 中 查 找 一 个 值 , 可 以 根 据 值 存 放 的 顺 序 用 递 归 的 过 程 来 进 行 。 例 如 , 一 个 树<br />

中 有 我 们 朋 友 的 名 字 与 地 址 , 要 找 一 个 地 址 , 我 们 先 从 根 节 点 的 名 字 开 始 。 如 果 这 个 值 与 根<br />

节 点 的 一 样 , 就 找 到 了 我 们 要 的 东 西 ; 如 果 它 小 于 根 节 点 ( 对 名 字 来 说 就 是 它 排 在 根 节 点 名<br />

字 之 前 ), 那 它 肯 定 就 在 左 子 树 中 , 我 们 可 以 用 相 同 的 方 法 搜 索 左 子 树 。 同 样 , 如 果 它 大 于<br />

根 节 点 , 那 它 必 然 是 在 右 子 树 中 , 我 们 可 以 递 归 地 搜 索 右 子 树 。 如 果 到 了 一 个 叶 , 我 们 还 没<br />

有 找 到 所 需 要 的 , 那 这 个 地 址 一 定 没 有 在 它 应 该 在 的 地 方 ( 如 果 有 它 的 话 ), 也 就 是 说 , 树<br />

中 根 本 没 有 这 个 地 址 。<br />

在 搜 索 过 程 中 , 每 次 对 二 进 制 树 一 个 单 元 的 检 查 , 都 会 消 去 一 半 需 要 考 虑 的 单 元 , 搜 索<br />

过 程 非 常 迅 速 。 如 果 树 的 大 小 ( 也 就 是 节 点 数 ) 增 加 一 倍 , 搜 索 时 一 般 只 需 要 多 增 加 一 步 。<br />

二 进 制 树 搜 索 时 间 , 平 均 来 说 , 与 log 2 N 成 正 比 ( 其 实 , 与 任 意 数 为 底 的 N 的 对 数 成 正 比 )。<br />

建 造 一 个 树 , 由 一 个 空 树 开 始 , 一 项 一 项 地 加 。 添 加 一 项 与 搜 索 一 个 项 相 同 : 查 找 要 添<br />

加 的 项 应 该 在 什 么 位 置 , 然 后 插 入 它 。 算 法 如 下 :<br />

1. 如 果 当 前 项 是 空 树 , 把 项 就 插 入 在 这 里<br />

2. 否 则 , 把 待 插 入 的 项 与 当 前 节 点 存 放 的 项 相 比 较 , 如 果 待 插 入 的 项 小 , 就 把 它 插 入<br />

到 左 子 树 中 , 否 则 就 插 入 到 右 子 树 中 。<br />

在 <strong>Prolog</strong> 里 , 这 需 要 三 个 子 句 , 每 个 用 于 一 种 情 况 。 假 设 已 经 有 了 一 个 可 以 用 下 面 函<br />

子 描 述 的 树 :<br />

tree(Node, LeftTree, RightTree) or empty<br />

要 在 这 个 树 里 插 入 项 , 我 们 来 写 出 插 入 过 程 的 各 个 子 句 。 过 程 名 是 insert, 要 用 三 个 参<br />

数 : 要 插 入 的 项 、 要 找 的 节 点 以 及 插 入 项 后 形 成 的 新 树 。 第 一 个 子 句 是 :<br />

insert(NewItem, empty, tree(NewItem, empty, empty) :‐ !. /* (i,i,o) */<br />

用 自 然 语 言 来 说 , 上 面 代 码 的 意 思 是 : 在 一 个 空 树 节 点 上 插 入 NewItem 的 结 果 是<br />

tree(NewItem,empty,empty)。 截 断 确 保 了 如 果 这 个 子 句 成 功 使 用 后 , 不 会 有 其 它 的 子 句 再 试<br />

着 插 入 NewItem。<br />

第 二 和 第 三 个 子 句 用 于 插 入 非 空 的 树 , 它 们 的 流 模 式 都 是 (i,i,o):<br />

insert(NewItem, tree(Element, Left, Right), tree(Element, NewLeft, Right) :‐<br />

NewItem


insert(NewItem, tree(Element, Left, Right), tree(Element, Left, NewRight) :‐<br />

insert(NewItem, Right, NewRight).<br />

如 果 NewItem


12.11 遍 历 树 的 程 序<br />

现 在 , 我 们 来 写 一 个 程 序 , 建 立 图 12.4 中 的 树 , 用 不 同 的 方 法 访 问 这 个 树 。 先 建 一 个<br />

名 为 ch12Trees 的 新 工 程 , 再 在 任 务 窗 口 中 的 任 务 菜 单 中 加 一 个 菜 单 项 ,Trees, 给 它 添 加 四<br />

个 选 项 :create、traverse PRE‐order、traverse IN‐order、traverse POST‐order。 用 IDE 产 生 标 准<br />

代 码 , 再 把 它 们 改 成 下 面 这 样 :<br />

predicates<br />

onTreesCreate : window::menuItemListener.<br />

clauses<br />

onTreesCreate(_Source, _MenuTag) :‐<br />

classtree::add_from_list(["F", "B", "G", "A", "D", "I", "C", "E", "H"]),<br />

stdIO::write("Tree has been created"), stdIO::nl.<br />

predicates<br />

onTreesTraversePreorder : window::menuItemListener.<br />

clauses<br />

onTreesTraversePreorder(_Source, _MenuTag) :‐<br />

classtree::traverse("preorder").<br />

predicates<br />

onTreesTraverseInorder : window::menuItemListener.<br />

clauses<br />

onTreesTraverseInorder(_Source, _MenuTag) :‐<br />

classtree::traverse("inorder").<br />

predicates<br />

onTreesTraversePostorder : window::menuItemListener.<br />

clauses<br />

onTreesTraversePostorder(_Source, _MenuTag):‐<br />

classtree::traverse("postorder").<br />

把 树 和 对 树 处 理 的 所 有 谓 词 , 放 在 类 classTree 中 , 这 样 , 所 有 菜 单 选 项 都 是 从 这 个 类<br />

中 调 用 谓 词 的 。 为 了 创 建 树 , 用 了 一 个 字 符 表 , 顺 序 随 意 。 谓 词 add_from_list() 会 创 建 树 ,<br />

而 调 用 谓 词 traverse() 会 遍 历 树 , 它 有 一 个 参 数 ( 串 类 型 的 ) 来 表 示 如 何 遍 历 树 。<br />

域 和 操 作 树 的 谓 词 声 明 在 classtree.cl 中 , 像 这 样 :<br />

domains<br />

stringlist = string*.<br />

treetype = tree(string, treetype, treetype) ; empty.<br />

predicates<br />

add_from_list : (stringlist) procedure (i).<br />

treeinsert : (string NewItem, treetype OldTree, treetype Newtree) procedure (i,i,o).<br />

traverse : (string TraverseOrder) procedure (i).<br />

preOrderTraverse : (treetype Tree) procedure (i).<br />

inOrderTraverse : ( treetype Tree) procedure (i).<br />

postOrdertraverse : (treetype Tree) procedure (i).<br />

树 放 在 一 个 类 事 实 变 量 中 , 它 在 classtree.pro 中 的 声 明 方 法 是 :<br />

172


class facts<br />

treefact : treetype := empty.<br />

这 是 一 个 treetype 类 型 的 事 实 变 量 , 初 始 化 为 空 的 。 再 对 classtree.pro 中 的 谓 词 添 加 以<br />

下 代 码 :<br />

clauses<br />

add_from_list([]) :‐ !.<br />

add_from_list([NewItem | Tail]) :‐<br />

treeinsert(NewItem, treefact, NewTree),<br />

treefact := Newtree,<br />

add_from_list(Tail).<br />

treeinsert(NewItem, empty, tree(NewItem, empty, empty)) :‐ !.<br />

treeinsert(NewItem, tree(Element, LEft, Right), tree(Element, Newleft, Right) ) :‐<br />

NewItem < Element, !,<br />

treeinsert(NewItem, Left, NewLeft).<br />

treeinsert(NewItem, tree(Element, LEft, Right), tree(Element, Left, Newright) ) :‐<br />

treeinsert(NewItem, Right, NewRight).<br />

traverse("preorder") :‐<br />

stdIO::nl, stdIO::write("the tree in PRE‐order:"),<br />

preOrderTraverse(treefact), !, stdIO::nl.<br />

traverse("inorder") :‐<br />

stdIO::nl, stdIO::write("the tree in IN‐order:"),<br />

inOrderTraverse(treefact), !, stdIO::nl.<br />

traverse("postorder") :‐<br />

stdIO::nl, stdIO::write("the tree in POST‐order:"),<br />

postOrderTraverse(treefact), !, stdIO::nl.<br />

traverse(_).<br />

preOrderTraverse(empty) :‐ !.<br />

preOrderTraverse(tree(Element, Left, Right)) :‐<br />

stdIO::write(Element, " "),<br />

preOrderTraverse(Left),<br />

preOrderTraverse(Right).<br />

inOrderTraverse(empty) :‐ !.<br />

inOrderTraverse(tree(Element, Left, Right)) :‐<br />

inOrderTraverse(Left),<br />

stdIO::write(Element, " "),<br />

inOrderTraverse(Right).<br />

postOrderTraverse(empty) :‐ !.<br />

postOrderTraverse(tree(Element, Left, Right)) :‐<br />

postOrderTraverse(Left),<br />

postOrderTraverse(Right),<br />

stdIO::write(Element, " ").<br />

173


这 是 一 个 类 型 为 treetype 的 事 实 变 量 , 初 始 化 为 empty。<br />

好 好 玩 玩 这 个 程 序 吧 。 还 可 以 做 些 实 验 , 比 如 , 在 程 序 中 , 一 个 新 元 素 如 果 比 当 前 节 点<br />

小 或 是 相 等 , 则 会 把 它 放 在 左 子 树 中 。 按 以 下 方 法 改 一 下 程 序 , 看 看 会 怎 么 样 , 把<br />

treeinsert(NewItem, tree(Element, Left, Right), tree(Element, Newleft, Right) ) :‐<br />

NewItem


附 录 1 对 话 框 与 表 格 的 详 细 说 明<br />

这 个 附 录 详 细 介 绍 对 话 框 与 表 格 的 编 辑 。 头 一 节 是 创 建 对 话 框 与 编 辑 它 的 属 性 , 第 二 节<br />

是 对 话 框 的 设 计 与 控 件 , 再 下 一 节 主 要 讲 控 件 的 属 性 。 主 要 内 容 都 摘 自 VIP 帮 助 文 件 , 这 里<br />

所 说 的 对 话 框 (dialog)、 表 格 (form) 和 窗 口 (window), 都 是 指 的 一 个 东 西 , 是 一 个 用 户<br />

可 以 点 击 与 编 辑 控 件 的 窗 口 。 有 时 , 这 样 的 窗 口 也 叫 容 器 , 因 为 它 可 以 容 纳 控 件 。<br />

在 工 程 树 中 , 可 以 通 过 文 件 扩 展 名 来 辨 别 文 件 。 对 话 框 的 扩 展 名 是 .dlg, 而 表 格 的 扩 展<br />

名 是 .frm。 对 话 框 与 表 格 它 们 之 间 稍 有 不 同 , 对 话 框 在 运 行 时 不 能 改 变 大 小 , 而 表 格 可 以 指<br />

定 边 框 , 边 框 可 以 规 定 是 否 能 改 变 大 小 。 在 这 个 附 录 中 , 提 到 对 话 框 与 表 格 时 , 认 为 都 是 一<br />

样 的 , 不 考 虑 它 们 的 差 别 。<br />

A1.1 创 建 对 话 框 或 表 格<br />

创 建 表 格<br />

表 格 可 以 在 任 何 目 标 为 GUI 图 形 用 户 界 面 的 工 程 中 创 建 , 在 工 程 中 用 常 规 GUI(pfc/vpi)<br />

和 对 象 GUI(pfc/gui) 类 型 的 UI 策 略 都 可 以 创 建 表 格 。 要 注 意 , 窗 口 只 能 在 图 形 用 户 界 面 的<br />

工 程 中 创 建 , 使 用 常 规 GUI(pfc/vpi) 类 型 的 UI 策 略 ( 参 看 PFC 帮 助 中 关 于 GUI 包 中 的 表 格<br />

介 绍 )。<br />

在 工 程 中 创 建 和 注 册 一 个 新 表 格 , 只 需 要 点 击 File/New 菜 单 命 令 。IDE 会 打 开 创 建 工 程<br />

项 对 话 框 ( 图 A1.1), 左 边 有 项 表 , 是 这 个 对 话 框 可 以 创 建 的 项 , 选 择 Form 就 会 创 建 表 格<br />

而 选 择 Dialog 就 会 创 建 对 话 框 。 当 选 择 Form 时 , 创 建 工 程 项 对 话 框 如 图 A1.1 示 。<br />

包<br />

在 包 列 表 按 钮 中 , 需 要 选 择 一 个 工 程 知 道 的 包 ( 在 图 A1.1 中 是 main.pack) 或 是 创 建 一<br />

个 新 的 包 。 用 于 处 理 创 建 的 表 格 而 产 生 的 文 件 就 放 在 这 个 包 里 , 在 图 A1.1 中 选 择 的 是 工 程<br />

已 有 的 包 , 因 而 文 件 就 都 会 放 在 main.pack 中 。<br />

图 A1.1<br />

创 建 一 个 表 格<br />

175


公 用 和 私 有<br />

表 格 是 当 类 来 对 待 的 , 也 就 是 说 除 了 别 的 而 外 还 会 产 生 两 个 文 件 , 一 个 是 .i, 还<br />

有 一 个 是 .cl, 这 里 的 是 表 格 的 名 字 。.i 文 件 是 接 口 ,.cl 则 是 声<br />

明 。 这 两 个 文 件 中 包 含 着 工 程 其 它 部 分 可 见 谓 词 的 声 明 , 创 建 表 格 时 , 需 要 点 选 Public 或<br />

Private 按 钮 以 确 定 产 生 的 表 格 和 处 理 表 格 的 类 对 存 放 表 格 的 包 来 说 , 是 公 用 的 还 是 私 有 的 。<br />

如 果 选 Public, 产 生 接 口 与 类 的 包 括 指 令 会 加 在 包 文 件 的 头 文 件 ( 图 A1.1 中 的 main.ph)<br />

里 , 插 入 的 内 容 如 下 :<br />

% exported interfaces<br />

#include @"myForm.i"<br />

% exported classes<br />

#include @"myForm.cl"<br />

由 于 在 所 有 使 用 包 的 地 方 , 都 会 用 #include @"main.ph" 指 令 插 入 包 的 头 文 件 , 所 以 上 面<br />

的 结 果 也 就 会 使 这 些 地 方 都 可 以 看 到 #include @"myForm.i" 和 #include @"myForm.cl" 指 令 , 也<br />

就 是 说 , 在 处 理 myForm 表 格 的 接 口 和 类 文 件 中 声 明 的 谓 词 , 在 工 程 中 的 任 何 地 方 都 可 以 调<br />

用 , 所 以 是 公 用 的 。<br />

如 果 选 Private, 则 包 括 指 令 会 插 入 在 包 的 实 现 文 件 ( 图 A1.1 中 的 main.pack) 里 , 插 入<br />

的 内 容 如 下 :<br />

% private interfaces<br />

#include @"myForm.i"<br />

% private classes<br />

#include @"myForm.cl"<br />

这 些 包 括 指 令 只 在 包 内 可 见 , 因 而 接 口 和 类 声 明 在 包 实 现 之 外 是 看 不 到 的 , 这 样 一 来 ,<br />

处 理 myForm 表 格 的 接 口 和 类 对 包 实 现 来 讲 就 是 私 有 的 , 它 们 中 的 谓 词 也 是 私 有 的 。<br />

不 用 多 说 , 读 者 明 白 上 面 的 名 字 main 只 是 个 偶 选 , 基 本 的 差 别 体 现 在 扩 展 名 上 ,.PH<br />

是 包 的 头 文 件 ,.PACK 是 包 。<br />

设 置 好 了 所 有 选 项 , 点 击 Create 按 钮 , 会 出 现 表 格 编 辑 器 , 给 出 一 个 标 准 的 表 格 原 型 ,<br />

如 图 A1.2 示 。<br />

图 A1.2<br />

表 格 编 辑 器<br />

176


A1.2 编 辑 对 话 框<br />

要 编 辑 一 个 对 话 框 , 在 工 程 树 中 双 击 带 有 对 话 框 文 件 扩 展 名 .dlg 的 文 件 名 ; 要 编 辑 一 个<br />

表 格 , 则 要 选 扩 展 名 为 .frm 的 文 件 。 也 可 以 高 亮 文 件 名 后 回 车 来 进 行 编 辑 。IDE 表 格 编 辑 器<br />

可 以 用 来 编 辑 对 话 框 成 用 户 需 要 的 样 式 。<br />

一 个 新 对 话 框 创 建 后 , 缺 省 就 会 有 三 个 按 钮 控 件 :OK、Cancel 和 Help, 它 们 可 以 重 新<br />

布 局 , 也 可 以 删 掉 。 不 过 要 小 心 , 这 些 按 钮 是 假 设 以 MsWindows 方 式 来 使 用 的 , 也 就 是 说 ,<br />

如 果 留 下 这 些 按 钮 , 点 击 OK 或 Cancel 时 就 会 关 闭 对 话 框 , 不 管 用 户 给 它 们 添 加 了 些 什 么 代<br />

码 。<br />

控 件<br />

控 件 是 对 话 框 的 一 部 分 , 用 户 可 以 用 它 来 进 行 某 种 动 作 。 控 件 常 用 于 选 择 ( 如 选 一 个 文<br />

件 名 )、 点 击 ( 如 点 击 OK 按 钮 ) 和 编 辑 ( 如 输 入 或 改 变 一 个 名 字 )。 对 话 框 中 的 每 个 控 件 都<br />

要 有 一 个 标 识 名 , 标 识 名 是 用 _ctl 结 尾 的 , 在 一 个 对 话 框 中 应 该 是 唯 一 的 。 在 一 个 对 话 框 中<br />

两 个 控 件 不 能 有 同 样 的 名 字 , 但 在 不 同 的 对 话 框 中 同 样 功 能 的 控 件 用 一 样 的 名 字 则 是 一 种 好<br />

习 惯 。 下 面 介 绍 在 VIP 中 可 用 的 控 件 类 型 :<br />

图 标 名 称 作 用<br />

Push Button<br />

Check Box<br />

Radio Button<br />

Static Text<br />

Edit<br />

控 件 用 于 在 应 用 程 序 中 引 发 某 个 规 定 的 动 作<br />

控 件 用 于 指 定 二 选 一 的 项 目 。 比 如 有 些 工 具<br />

状 态 可 以 是 ON 或 OFF<br />

控 件 成 组 地 使 用 以 在 多 个 可 选 项 中 进 行 选 择<br />

控 件 可 以 在 对 话 框 中 使 用 一 个 区 域 显 示 文<br />

字 。 尽 管 称 它 是 Static( 静 态 ) 的 , 但 在 执 行<br />

过 程 中 应 用 程 序 可 以 改 变 它 的 文 字 。 它 常 用<br />

于 提 示 或 是 显 示 其 它 控 件 的 名 称<br />

控 件 用 于 在 一 个 区 域 中 编 辑 文 本 ( 如 名 字 、<br />

数 字 、 文 字 常 数 等 ), 编 辑 的 文 本 串 可 以 是 多<br />

行 的<br />

List Box<br />

控 件 用 于 查 看 元 素 列 表 及 从 中 进 行 选 择 , 可<br />

以 选 择 一 项 或 多 项<br />

List Button<br />

List Edit<br />

控 件 用 于 在 弹 出 式 选 项 组 中 选 择 某 个 选 项 ,<br />

点 击 控 件 可 以 显 示 选 项 组<br />

控 件 可 以 如 同 Edit 控 件 一 样 进 行 单 行 的 文 本<br />

编 辑 , 也 可 以 在 元 素 列 表 中 做 选 择<br />

Vertical Scroll Bar 和<br />

Horixontal Scroll Bar<br />

控 件 可 以 在 刻 度 值 中 选 定 某 个 值<br />

Group Box<br />

控 件 可 以 把 多 个 控 件 在 一 个 组 名 之 下 组 合 在<br />

一 个 功 能 组 中 。 它 的 功 能 只 是 视 觉 上 的<br />

177


Icon<br />

Custom<br />

显 示 图 标 图 像<br />

控 件 可 以 是 工 程 中 创 建 的 IDE 控 件 , 也 可 以<br />

是 用 vpi::classCreate 定 义 的 窗 口 类 实 现 的 VPI<br />

用 户 定 义 的 控 件 , 或 是 从 DLL、VBX、OCX 中<br />

导 入 的 作 为 COM 包 的 控 件<br />

插 入 控 件<br />

在 对 话 框 中 插 入 一 个 新 的 控 件 , 只 需 要 先 选 择 该 控 件 , 然 后 在 对 话 框 中 要 放 置 控 件 的 位<br />

置 上 点 击 就 可 以 了 。 选 择 控 件 有 三 种 方 法 :<br />

• 在 控 件 工 具 箱 中 点 击 控 件 的 图 标<br />

• 在 IDE 的 控 件 菜 单 中 选 择<br />

• 在 弹 出 式 菜 单 中 选 择 。 右 键 点 击 IDE 设 计 器 中 的 对 话 框 就 可 以 出 现 选 择 控 件 的 弹 出<br />

式 菜 单 , 如 图 A1.3 示 。<br />

图 A1.3<br />

弹 出 菜 单<br />

控 件 工 具 箱<br />

打 开 IDE 设 计 器 时 , 控 件 工 具 箱 就 会 出 现 在 旁 边 , 控 件 工 具 箱 出 如 图 A1.4 示 。<br />

图 A1.4<br />

控 件 工 具 箱<br />

点 击 控 件 工 具 箱 中 的 图 标 , 就 可 以 选 择 要 放 置 在 被 编 辑 对 话 框 中 的 控 件 。 当 点 击 控 件 图<br />

标 时 , 光 标 会 变 成 与 该 控 件 相 应 的 一 个 图 标 , 移 动 光 标 到 设 计 区 域 ( 容 器 的 客 户 区 域 ) 点 左<br />

键 , 控 件 就 会 以 缺 省 大 小 插 入 在 指 定 位 置 上 。<br />

除 了 在 容 器 中 点 击 的 方 式 插 入 控 件 , 还 可 以 用 拖 出 矩 形 的 方 式 。 选 好 控 件 后 , 在 设 计 区<br />

中 点 击 但 不 松 开 鼠 标 键 , 拖 动 鼠 标 可 以 改 变 控 件 的 大 小 。 松 开 鼠 标 键 , 控 件 就 以 指 定 的 尺 寸<br />

安 放 好 了 。 用 这 样 的 方 法 , 不 仅 可 以 指 定 控 件 的 位 置 , 同 时 也 规 定 了 控 件 的 大 小 。<br />

用 鼠 标 在 GUI 的 容 器 中 放 置 好 了 GUI 控 件 之 后 , 它 便 会 在 指 定 位 置 上 立 即 显 现 出 来 。 要<br />

178


改 变 控 件 的 属 性 , 首 先 选 择 它 , 然 后 就 会 出 现 控 件 属 性 列 表 。<br />

控 件 工 具 箱 中 有 以 下 图 标 :<br />

图 标 名 称 选 择 后 显 示 的 光 标<br />

Push Button<br />

Check Box<br />

Radio Button<br />

Static Text Control<br />

Edit Control<br />

List Box<br />

List Button<br />

List Edit<br />

Horizontal Scroll Bar<br />

Vertical Scroll Bar<br />

Group Box<br />

Icon Control<br />

Custom Control<br />

在 对 话 框 设 计 器 中 选 择 / 撤 消 选 择 控 件<br />

在 控 件 内 点 击 就 选 择 了 该 控 件 , 被 选 择 的 控 件 上 会 出 现 一 个 框 , 它 叫 选 框 。 要 选 择 多 个<br />

控 件 , 有 两 种 熟 悉 的 方 法 。 一 个 是 把 鼠 标 光 标 移 到 对 话 框 内 要 选 择 区 域 的 一 个 角 上 , 按 住 左<br />

键 拖 动 鼠 标 , 直 到 所 有 想 要 选 择 的 控 件 都 在 选 择 区 域 内 , 然 后 松 开 鼠 标 键 。 矩 形 框 内 的 控 件<br />

都 会 被 选 中 。 另 一 个 方 法 , 当 已 经 有 一 个 或 多 个 控 件 被 选 中 了 , 要 再 加 上 一 个 控 件 , 可 以 按<br />

下 Ctrl 键 再 点 击 该 控 件 。 用 这 个 方 法 也 可 以 撤 消 选 择 个 别 的 控 件 。<br />

要 撤 消 选 择 一 个 或 一 组 控 件 , 可 以 在 选 框 外 点 击 。 撤 消 选 择 的 控 件 其 选 框 就 会 消 失 。<br />

改 变 控 件 大 小<br />

如 果 只 是 点 击 放 置 控 件 , 它 的 大 小 是 缺 省 的 。 要 改 变 控 件 的 大 小 , 先 在 控 件 内 点 击 以 选<br />

中 它 , 然 后 把 光 标 放 在 选 框 的 尺 寸 柄 上 , 这 时 会 出 现 新 的 光 标 形 状 , 表 示 可 以 在 哪 个 方 向 上<br />

调 整 控 件 的 大 小 。 按 住 鼠 标 键 并 拖 动 , 直 到 调 整 到 需 要 的 大 小 , 松 开 鼠 标 键 。 也 可 以 在 控 件<br />

属 性 表 里 改 变 控 件 的 大 小 , 选 择 好 控 件 , 然 后 改 变 X/Y 的 值 变 化 控 件 的 位 置 , 改 变 Width 和<br />

Height 的 值 变 化 控 件 的 大 小 。 有 一 个 例 外 , 图 标 不 能 改 变 大 小 , 只 能 改 变 它 的 位 置 。<br />

179


移 动 控 件<br />

要 移 动 一 个 或 一 组 控 件 , 先 选 择 好 , 然 后 把 光 标 置 于 选 框 内 , 按 住 鼠 标 键 并 拖 动 到 新 的<br />

位 置 上 松 开 鼠 标 键 。 用 光 标 键 , 可 以 做 精 细 ( 与 网 格 设 置 有 关 ) 的 移 动 。<br />

控 件 的 布 局 排 放<br />

有 三 种 方 法 可 以 进 行 控 件 的 布 局 排 放 :<br />

• 布 局 工 具 箱<br />

• IDE 菜 单 中 的 布 局 菜 单 项<br />

• 在 对 话 框 设 计 器 中 点 右 键 出 现 的 弹 出 式 菜 单<br />

要 排 置 一 组 控 件 , 先 选 择 好 控 件 , 然 后 点 击 布 局 工 具 箱 中 相 应 的 按 钮 或 是 IDE 菜 单 中<br />

Layout 菜 单 ( 或 是 弹 出 式 菜 单 ) 中 的 选 项 。<br />

图<br />

布 局 工 具 箱<br />

下 表 中 列 出 了 可 以 调 整 控 制 位 置 和 大 小 的 布 局 命 令 。<br />

图 标 命 令 名 称 作 用<br />

Align Left<br />

使 所 选 控 件 左 对 齐<br />

Align Center<br />

使 所 选 控 件 垂 直 对 中<br />

Align Right<br />

使 所 选 控 件 右 对 齐<br />

Align Top<br />

使 所 选 控 件 上 沿 对 齐<br />

Align Middle<br />

使 所 选 控 件 水 平 对 中<br />

Align Bottom<br />

Even Horizontal Spacing<br />

使 所 选 控 件 下 沿 对 齐<br />

使 所 选 控 件 在 左 右 两 端 的 控<br />

件 间 间 隔 均 匀<br />

Grid 栅 格 开 关 ( 参 见 后 面 的 叙 述 )<br />

Even Vertical Spacing<br />

Make Same Size<br />

Make Same Horizontal Size<br />

Make Same Vertical Size<br />

使 所 选 控 件 在 上 下 两 端 的 控<br />

件 间 间 隔 均 匀<br />

使 所 选 控 件 大 小 一 致<br />

使 所 选 控 件 与 模 型 控 件 宽 度<br />

一 致<br />

使 所 选 控 件 与 模 型 控 件 高 度<br />

一 致<br />

180


还 有 一 个 命 令 Size To Contents, 它 可 以 调 整 控 件 到 最 佳 尺 寸 以 显 示 控 件 的 标 题 。<br />

控 件 属 性 表<br />

在 IDE 设 计 器 中 打 开 GUI 对 话 框 或 表 格 (GUI 窗 口 ) 时 , 打 开 的 对 话 框 旁 边 就 会 出 现 控<br />

件 属 性 表 。 如 果 控 件 属 性 表 关 闭 了 , 可 以 在 某 个 控 件 上 双 击 , 控 件 属 性 表 就 会 重 新 出 现 。 用<br />

IDE 设 计 器 的 快 速 菜 单 ( 在 对 话 框 中 点 击 右 键 就 会 弹 出 ) 中 的 控 件 属 性 命 令 , 也 可 以 打 开 控<br />

件 属 性 表 。 在 A1.3 节 中 有 控 件 属 性 的 全 面 介 绍 。<br />

要 改 变 一 个 GUI 包 控 件 的 属 性 , 先 在 对 话 框 里 选 中 该 控 件 ( 点 击 它 ), 控 件 属 性 表 里 会<br />

显 示 出 它 的 当 前 设 置 值 , 在 这 里 就 可 以 改 变 控 件 属 性 了 。<br />

图<br />

控 件 属 性 表<br />

注 意 , 其 实 属 性 表 也 包 含 了 该 控 件 所 处 理 的 事 件 , 可 以 点 击 下 部 的 标 签 在 属 性 和 事 件 间<br />

切 换 。<br />

刚 开 始 的 时 候 , 如 果 没 有 选 择 什 么 控 件 , 控 件 属 性 表 中 显 示 的 是 表 格 的 属 性 ( 及 事 件 )。<br />

一 旦 选 择 了 正 在 编 辑 的 对 话 框 或 表 格 中 的 控 件 , 属 性 表 中 显 示 的 就 是 该 控 件 的 属 性 了 。 属 性<br />

的 显 示 与 控 件 的 类 型 有 关 , 控 件 属 性 表 中 有 两 类 属 性 , 一 组 是 普 通 属 性 , 适 用 于 所 有 类 型 控<br />

件 :Text 是 控 件 的 标 题 ,Constant 是 控 件 的 标 识 符 ,Control Size 组 可 以 确 定 控 件 的 位 置 与 大<br />

小 。 要 注 意 , 有 些 控 件 不 用 Text 属 性 , 如 滚 动 条 。<br />

还 有 ,Text 域 中 , 符 号 “&” 用 作 指 示 它 后 面 的 字 母 有 下 划 线 显 示 。 如 , 要 将 字 串 “E&xit”<br />

作 为 一 个 控 件 的 名 字 , 运 行 时 这 个 控 件 的 标 题 就 会 显 示 为 Exit, 字 母 x 中 会 有 下 划 线 显 示 ( 这<br />

可 以 直 观 地 标 示 出 x 是 一 个 快 速 键 )。 如 果 需 要 在 控 件 的 Text 中 显 示 符 号 “&”, 可 以 用 “&&”<br />

来 实 现 。<br />

锚 定 控 件<br />

GUI 控 件 都 有 附 加 的 锚 定 特 性 , 这 个 特 性 有 助 于 放 置 GUI 控 件 的 对 话 框 ( 表 格 ) 调 整 大<br />

小 时 控 件 位 置 ( 及 大 小 ) 的 调 整 。 每 个 GUI 控 件 有 四 个 锚 定 属 性 : 左 锚 定 、 顶 锚 定 、 右 锚 定<br />

和 底 锚 定 , 可 以 在 属 性 表 中 看 到 这 些 属 性 。 各 个 锚 定 属 性 确 定 了 控 件 规 定 的 ( 左 、 上 、 右 和<br />

底 ) 边 与 对 话 框 ( 表 格 ) 相 应 的 最 内 边 必 须 始 终 保 持 的 距 离 。<br />

181


各 个 锚 的 值 可 以 是 True 或 False。 当 锚 值 是 True 时 , 这 个 锚 就 开 始 起 作 用 , 当 对 话 框 大<br />

小 变 化 时 , 这 个 锚 就 会 用 来 确 定 控 件 的 位 置 。 比 如 , 当 左 锚 定 是 有 效 的 ( 值 为 True), 控 件<br />

的 左 边 就 会 与 对 话 框 ( 表 格 ) 左 内 侧 保 持 规 定 的 距 离 。<br />

查 看 一 个 控 件 哪 个 锚 是 有 效 的 , 最 简 单 的 办 法 是 选 择 该 控 件 , 这 时 就 能 看 到 控 件 边 与 对<br />

话 框 边 之 间 有 一 些 红 色 的 箭 头 显 示 出 来 , 每 个 箭 头 就 表 示 了 当 前 有 效 的 锚 定 。 例 如 , 控 件 顶<br />

端 到 对 话 框 顶 边 的 箭 头 , 表 示 顶 锚 定 是 有 效 的 。 在 下 面 的 图 中 , 可 以 看 到 三 个 箭 头 , 表 示 这<br />

个 按 钮 控 件 的 左 锚 定 、 顶 锚 定 和 右 锚 定 都 是 有 效 的 。<br />

箭 头 边 上 的 数 字 表 示 控 件 与 对 话 框 相 应 边 之 间 的 距 离 ( 以 对 话 框 基 准 单 位 来 计 算 )。 通<br />

常 , 各 个 控 件 都 有 一 个 水 平 方 向 和 一 个 垂 直 方 向 的 锚 定 是 有 效 的 。 这 样 , 当 对 话 框 调 整 尺 寸<br />

时 , 各 个 控 件 都 会 与 规 定 的 对 话 框 边 沿 保 持 规 定 的 距 离 , 而 控 件 本 身 的 大 小 不 会 变 化 。 如 果<br />

一 个 控 件 有 两 个 水 平 ( 或 垂 直 ) 方 向 的 锚 定 有 效 , 那 控 件 的 两 个 水 平 边 ( 或 垂 直 边 ) 都 要 与<br />

对 话 框 相 应 的 水 平 边 ( 垂 直 边 ) 保 持 规 定 的 距 离 。 这 样 一 来 , 当 对 话 框 大 小 变 化 时 , 这 个 控<br />

件 就 得 变 化 大 小 以 保 持 相 应 的 边 距 。<br />

当 一 个 控 件 没 有 任 何 一 个 锚 定 有 效 时 , 控 件 边 沿 与 对 话 框 相 应 的 边 沿 也 就 没 有 规 定 一 定<br />

要 保 持 的 距 离 。 这 类 控 件 的 位 置 是 按 比 例 来 调 整 的 。 例 如 , 对 话 框 的 宽 度 增 加 了 δ 个 基 数 单<br />

位 , 控 件 的 X 座 标 也 会 增 加 , 但 只 增 加 对 话 框 宽 度 变 化 的 二 分 之 一 ,δ/2。 这 样 , 控 件 的 尺<br />

寸 就 按 表 格 变 化 的 尺 寸 比 例 地 变 化 。<br />

控 件 的 其 它 属 性 对 不 同 的 控 件 来 说 是 不 一 样 的 , 在 下 一 节 来 描 述 。<br />

剪 切 、 拷 贝 和 粘 贴 , 撤 消 与 重 做<br />

可 以 把 一 组 选 好 的 控 件 剪 切 或 拷 贝 到 Windows 的 剪 贴 板 中 , 也 可 以 从 剪 贴 板 中 粘 贴 控<br />

件 到 对 话 框 里 。 撤 消 与 重 做 命 令 是 删 除 或 恢 复 对 表 格 进 行 的 最 近 的 编 辑 操 作 。<br />

栅 格<br />

图<br />

当 菜 单 项 Resource|Grid 有 效 时 , 或 是 在 布 局 工 具 箱 中 点 击 了<br />

的 对 话 框 :<br />

按 钮 , 就 会 出 现 下 面<br />

图 规 定 栅 格 属 性 的 对 话 框<br />

用 了 栅 格 , 在 对 话 框 中 排 放 控 件 就 比 较 容 易 。 也 可 以 告 诉 IDE 设 计 器 使 用 对 齐 栅 格 来 放<br />

置 控 件 , 得 到 满 意 的 结 果 。<br />

182


图<br />

在 对 话 框 / 窗 口 / 表 格 编 辑 器 中 使 用 栅 格<br />

测 试 模 式<br />

IDE 设 计 器 中 有 一 个 测 试 模 式 , 可 以 用 于 检 查 对 话 框 及 创 建 的 控 件 看 起 来 是 什 么 样 的 ,<br />

行 为 如 何 。 下 面 的 图 解 中 使 用 了 一 些 缺 省 值 。 测 试 模 式 可 以 从 Resource 菜 单 ( 或 弹 出 式 菜<br />

单 ) 中 激 活 和 取 消 。<br />

图<br />

测 试 模 式 中 的 对 话 框<br />

Tab Stops<br />

当 激 活 了 菜 单 ( 或 弹 出 菜 单 ) 项 Resource|Tabstops 时 , 可 以 指 定 对 话 框 中 能 够 用 Tab<br />

键 切 换 的 控 件 。 这 个 命 令 有 效 时 , 在 控 件 上 会 出 现 带 有 + 或 ‐ 符 号 的 一 个 小 按 钮 , 表 示 该 控 件<br />

是 否 可 以 用 Tab 键 切 换 , 点 击 这 个 小 按 钮 可 以 改 变 状 态 。<br />

图<br />

为 对 话 框 指 定 Tab 站 点<br />

183


要 退 出 这 个 模 式 , 只 需 要 点 击 对 话 框 中 的 空 白 处 即 可 。<br />

访 问 顺 序<br />

在 一 个 对 话 框 中 指 定 了 Tab 站 点 后 , 就 可 以 规 定 按 Tab 键 时 控 件 取 得 焦 点 的 顺 序 。<br />

图<br />

显 示 访 问 顺 序<br />

要 改 变 访 问 顺 序 , 可 以 点 击 控 件 上 显 示 访 问 顺 序 号 的 小 方 框 , 这 时 又 会 弹 出 一 个 对 话 框<br />

来 改 变 序 号 :<br />

图<br />

改 变 访 问 顺 序<br />

要 退 出 这 个 模 式 , 只 需 要 点 击 对 话 框 中 的 空 白 处 。<br />

A1.3 控 件 属 性 表<br />

在 IDE 设 计 器 中 打 开 一 个 GUI 对 话 框 或 表 格 (GUI 窗 口 ) 时 , 同 时 也 会 出 现 控 件 属 性 表 。<br />

表 中 显 示 的 属 性 与 控 件 的 类 型 有 关 , 一 般 是 分 成 两 组 , 一 组 是 普 通 的 , 一 组 是 特 殊 的 。 普 通<br />

属 性 对 所 有 控 件 是 共 同 的 , 有 Left Anchor( 左 锚 定 )、Top Anchor( 顶 锚 定 )Right Anchor( 右<br />

锚 定 )、Bottom Anchor( 底 锚 定 )、Container Name( 容 器 名 称 )、Enabled( 激 活 的 )、Lock to<br />

Container( 锁 定 于 容 器 )、Name( 名 称 )Representation( 表 示 )、TabStop(Tab 站 点 )、Title<br />

( 标 题 )X、 Y、Width( 宽 度 )、Height( 高 度 ) 和 Visible( 可 见 的 )。 不 同 类 型 的 GUI 控 件<br />

有 不 同 的 特 殊 属 性 , 有 :3State, AlignBaseline, Alignment, AllowPartialRows, Auto, AutoNotify,<br />

AutoHScroll, AutoVScroll, Border, Case, Class, Default, ExtendedSel, HideSel, HScroll, Icon Name,<br />

IgnorePrefix, LeftText, MultiColumn, MultiLine, MultiSelect, Password, ReadOnly, Rows, Sort,<br />

StaticScrollbar, Style, UseTabStops, VScroll, WantReturn, Wrap。<br />

在 控 件 属 性 表 中 可 以 编 辑 显 示 出 来 的 被 选 GUI 控 件 的 属 性 。 开 始 时 这 个 表 是 空 的 , 一 旦<br />

被 编 辑 的 GUI 对 话 框 ( 表 格 ) 中 的 控 件 被 选 中 , 控 件 属 性 表 中 就 会 显 示 出 选 中 控 件 的 属 性 。<br />

如 果 控 件 属 性 表 关 闭 了 , 可 以 双 击 任 意 一 个 控 件 打 开 它 。 在 快 速 菜 单 ( 击 右 键 可 以 弹 出 它 )<br />

184


中 的 Control Attributes 命 令 也 可 以 打 开 控 件 属 性 表 。<br />

A1.3.1 大 多 数 GUI 控 件 的 共 同 属 性<br />

有 一 些 基 本 的 控 件 属 性 , 大 多 数 控 件 都 要 用 到 , 它 们 是 :<br />

Name( 名 称 )<br />

在 Name 输 入 框 中 可 以 指 定 某 个 适 当 的 <strong>Visual</strong> <strong>Prolog</strong> 名 称 , 它 可 以 做 控 件 的 名 字 ( 标 识 )。<br />

缺 省 的 Name 是 按 控 件 类 型 ( 如 pushButton, checkBox,,listButton 等 ) 加 上 后 缀 _ctl 自 动<br />

生 成 的 。 如 果 同 类 的 控 件 有 多 个 , 会 在 控 件 类 型 后 加 序 号 来 区 分 , 如 pushButton1_ctl。 用 户<br />

可 以 对 缺 省 名 称 进 行 编 辑 或 干 脆 直 接 输 入 自 己 需 要 的 名 称 。<br />

Representation( 表 示 )<br />

这 个 属 性 可 以 有 下 面 两 种 值 :Fact Variable( 事 实 变 量 ) 和 Variable( 变 量 )。<br />

如 果 指 定 了 Fact Variable,IDE 会 生 成 事 实 变 量 , 它 可 以 用 来 存 放 该 控 件 的 对 象 。 在 代 码<br />

中 这 个 事 实 变 量 用 来 代 表 这 个 控 件 , 而 这 个 事 实 变 量 的 名 称 取 决 于 Name 属 性 , 例 如 ,ok_ctl。<br />

注 意 , 在 这 个 情 况 下 , 事 实 变 量 的 名 称 自 动 转 换 成 小 写 开 头 。<br />

如 果 规 定 是 Variable,IDE 会 用 普 通 变 量 合 一 该 控 件 的 对 象 。 在 代 码 中 这 个 变 量 用 来 表<br />

示 该 控 件 , 变 量 名 也 取 决 于 Name 属 性 , 如 Cancel_ctl。 注 意 , 此 时 变 量 的 名 称 自 动 转 换 成<br />

大 写 开 头 。<br />

缺 省 值 是 Fact Variable。<br />

Title( 标 题 )<br />

这 个 属 性 用 于 指 定 希 望 对 该 控 件 显 示 的 标 题 。 标 题 中 带 下 划 线 的 字 母 表 示 一 个 加 速 键 ,<br />

要 使 一 个 字 母 带 有 下 划 线 , 可 以 在 该 字 母 前 使 用 符 号 &, 运 行 时 这 个 字 母 就 会 带 有 下 划 线 。<br />

注 意 , 在 标 题 属 性 的 值 单 元 中 显 示 的 字 母 也 可 以 带 有 下 划 线 , 如 下 图 中 的 H:<br />

只 有 当 这 个 单 元 得 到 焦 点 时 , 才 会 显 示 出 & 符 号 , 如 下 图 :<br />

如 果 需 要 在 标 题 中 显 示 符 号 & 本 身 , 可 以 用 两 个 & 符 号 , 即 &&。<br />

Lock to Container( 锁 定 于 容 器 )<br />

可 取 的 值 是 True 和 False。 如 果 是 True, 则 该 控 件 被 锁 定 于 一 个 容 器 的 内 部 , 是 哪 个 容<br />

器 由 Container Name 属 性 指 定 。<br />

Container Name( 容 器 名 称 )<br />

在 这 个 列 表 框 里 , 可 以 选 择 在 对 话 框 或 表 格 中 定 义 的 容 器 的 名 字 ( 下 图 的 示 例 中 只 有 分<br />

组 框 groupBox 类 型 的 容 器 )。 在 同 一 容 器 中 的 所 有 控 件 是 相 关 的 同 一 组 控 件 。 可 以 用 方 向 键<br />

在 同 组 中 的 控 件 间 切 换 ( 从 一 个 移 到 下 一 个 )。 只 有 控 件 所 在 的 组 名 才 会 显 示 在 容 器 名 称 值<br />

表 中 , 例 如 下 图 :<br />

它 表 示 该 控 件 位 于 两 个 组 框 中 , 这 两 个 组 框 的 名 字 分 别 是 GroupBox_ctl 和<br />

GroupBox1_ctl。 如 果 需 要 使 该 控 件 与 任 何 容 器 无 关 , 就 应 该 选 [none]。<br />

X, Y, Width, Height<br />

185


这 些 属 性 决 定 了 控 件 的 座 标 与 大 小 。<br />

X 控 件 左 上 角 的 X 座 标<br />

Y 控 件 左 上 角 的 Y 座 标<br />

Width 控 件 的 宽 度<br />

Heigth 控 件 的 高 度<br />

它 们 都 是 以 对 话 框 基 数 为 单 位 的 。<br />

Left,Top,Right 和 Bottom Anchors<br />

这 些 属 性 决 定 了 对 话 框 或 表 格 调 整 大 小 时 应 该 怎 样 处 理 控 件 座 标 及 大 小 。<br />

Left Anchor<br />

可 取 的 值 有 True 和 False。True 要 求 控 件 的 左 边 界 与 容 纳 该 控 件 的 对 话 框 ( 表 格 ) 的 左<br />

边 界 之 间 应 该 始 终 保 持 规 定 的 距 离 ( 就 是 X 的 值 )。<br />

Top Anchor<br />

可 取 的 值 有 True 和 False。True 要 求 控 件 的 顶 边 界 与 容 纳 该 控 件 的 对 话 框 ( 表 格 ) 的 顶<br />

边 界 之 间 应 该 始 终 保 持 规 定 的 距 离 ( 就 是 Y 的 值 )。<br />

Right Anchor<br />

可 取 的 值 有 True 和 False。True 要 求 控 件 的 右 边 界 与 容 纳 该 控 件 的 对 话 框 ( 表 格 ) 的 右<br />

边 界 之 间 应 该 始 终 保 持 规 定 的 距 离 。 这 个 距 离 值 可 以 在 控 件 被 选 中 时 出 现 的 箭 头 边 上 看 到 。<br />

Bottom Anchor<br />

可 取 的 值 有 True 和 False。True 要 求 控 件 的 底 边 界 与 容 纳 该 控 件 的 对 话 框 ( 表 格 ) 的 底<br />

边 界 之 间 应 该 始 终 保 持 规 定 的 距 离 。 这 个 距 离 值 可 以 在 控 件 被 选 中 时 出 现 的 箭 头 边 上 看 到 。<br />

上 述 距 离 值 都 是 以 对 话 框 基 数 为 单 位 的 。<br />

Enabled( 激 活 的 )<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 该 控 件 初 始 创 建 时 是 激 活 的 (True) 还 是 禁<br />

用 的 (False)。 缺 省 值 是 True( 激 活 的 )。<br />

可 以 用 window::getEnabled/0‐> 谓 词 来 检 查 控 件 是 否 是 激 活 的 :<br />

IsEnabled = checkButtonName:getEnabled(),<br />

IsEnabled = true,<br />

还 可 以 用 window::setEnabled/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsEnabled = true,<br />

checkButtonName:setEnabled( IsEnabled ),<br />

Visible<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 该 控 件 初 始 创 建 时 是 可 见 的 (True) 还 是 不<br />

可 见 的 (False)。 缺 省 值 是 True( 可 见 的 )。<br />

可 以 使 用 window::getVisible/0‐> 谓 词 检 查 控 件 是 否 是 可 见 的 :<br />

IsVisible = checkButtonName:getVisible(),<br />

IsVisible = true,<br />

还 可 以 使 用 window::setVisible/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsVisible = true,<br />

checkButtonName:setVisible( IsVisible ),<br />

TabStop<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 该 控 件 是 否 可 由 Tab 键 访 问 。 缺 省 值 是 True<br />

( 控 件 属 于 一 组 可 由 Tab 键 穿 行 访 问 的 控 件 )。<br />

可 以 使 用 control::getTabStop/0‐> 谓 词 检 查 控 件 这 个 属 性 的 设 置 :<br />

IsTabStop = checkButtonName:getTabStop(),<br />

IsTabStop = true,<br />

还 可 以 用 control::setTabStop/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsTabStop = true,<br />

186


checkButtonName:setTabStop( IsTabStop ),<br />

如 果 IsTabStop 是 True, 就 可 以 通 过 按 Tab 键 使 控 件 获 得 焦 点 。<br />

A1.3.2 不 同 GUI 控 件 类 型 的 特 殊 属 性<br />

下 面 这 些 属 性 是 不 同 类 型 控 件 的 特 殊 属 性 :<br />

3State<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 一 个 核 选 框 是 否 是 三 态 的 ( 不 选 、 选 、 不 确<br />

定 )。 可 以 用 checkButton::getStyle/2 谓 词 检 查 状 态 设 置 情 况 :<br />

checkButtonName:getStyle (_ IsAuto , Is3State ),<br />

Is3State = true,<br />

还 可 以 用 checkButton::setStyle/2 predicate 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

Is3State = true,<br />

checkButtonName:setStyle ( _IsAuto , Is3State ),<br />

三 态 核 选 框 与 普 通 核 选 框 是 一 样 的 , 不 过 它 还 可 以 是 灰 色 ( 哑 ) 的 , 用 来 表 示 该 核 选 框<br />

的 状 态 是 不 确 定 的 。 缺 省 值 是 False( 三 态 )。<br />

AlignBaseline( 基 线 对 齐 )<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 该 控 件 的 标 题 要 不 要 与 其 它 在 一 个 水 平 线 上<br />

的 控 件 的 标 题 对 齐 。 这 个 属 性 对 所 有 使 用 标 题 的 控 件 ( 静 态 文 本 、 核 选 框 、 编 辑 和 表 编 辑 控<br />

件 等 ) 都 适 用 。<br />

可 以 用 getAlignBaseline 谓 词 ( 如 editControl::getAlignBaseline/0‐>) 检 查 控 件 设 置 的 状 态 :<br />

IsAllignBaseLine = checkButtonName:getAllignBaseLine(),<br />

IsAllignBaseLine = true,<br />

也 可 以 用 setAlignBaseline( 如 textControl::setAlignBaseline/1) 谓 词 在 程 序 中 对 控 件 状 态<br />

进 行 设 置 :<br />

IsAllignBaseLine = true,<br />

checkButtonName:setAllignBaseLine( IsAllignBaseLine ),<br />

缺 省 值 是 True( 水 平 对 齐 标 题 )。<br />

Alignment( 对 齐 )<br />

可 取 的 值 有 Left、Center 和 Right。 这 一 组 合 框 中 的 单 选 按 钮 确 定 了 控 件 中 的 文 本 对 齐<br />

方 式 。 缺 省 值 是 Left( 左 对 齐 )。<br />

Left 控 件 中 的 文 本 左 对 齐 。 可 以 使 用 textControl::getAlignment/0‐> 谓 词 得 到 控 件 设 置 的<br />

状 态 :<br />

AlignmentType = textControlName : getAlignment (),<br />

AlignmentType = alignLeft(),<br />

也 可 以 使 用 textControl::setAlignment/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

AlignmentType = alignLeft() ,<br />

textControlName : setAlignment ( AlignmentType ),<br />

Center 控 件 中 文 本 居 中 对 齐 ,AlignmentType = alignCenter()。<br />

Right 控 件 中 文 本 右 对 齐 ,AlignmentType = alignRight()。<br />

Auto<br />

可 取 的 值 有 True 和 False。 这 个 属 性 定 义 了 控 件 是 否 是 自 动 的 ( 自 动 的 核 选 框 或 自 动 的<br />

单 选 按 钮 )。 点 击 一 个 自 动 控 件 时 , 它 会 恰 当 地 改 变 自 身 状 态 。<br />

可 以 使 用 checkButton::getStyle/2 和 radioButton::getAuto/0‐> 谓 词 检 查 控 件 状 态 的 设 置 :<br />

checkButtonName:getStyle ( IsAuto , _Is3State ),<br />

IsAuto = true,<br />

187


也 可 以 使 用 checkButton::setStyle/2 或 radioButton::setAuto/1 谓 词 在 程 序 中 对 控 件 状 态 进<br />

行 设 置 :<br />

IsAuto = true,<br />

checkButtonName:setStyle ( IsAuto , _Is3State ),<br />

缺 省 值 是 True( 自 动 )。<br />

AllowPartialRows<br />

可 取 的 值 有 True 和 False。 如 果 设 为 True, 这 个 表 框 的 大 小 就 是 它 在 程 序 中 创 建 时 的 大<br />

小 。 通 常 ,Windows 会 调 整 表 框 以 使 它 不 会 只 显 示 部 分 项 目 。 缺 省 值 对 表 框 控 件 是 True,<br />

对 表 按 钮 和 表 编 辑 控 件 是 False。<br />

可 以 使 用 listControl::getAllowPartialRows/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsAllowPartialRows = controlName : getAllowPartialRows (),<br />

IsAllowPartialRows = true,<br />

也 可 以 使 用 listControl::setAllowPartialRows/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsAllowPartialRows = true,<br />

controlName : setAllowPartialRows ( IsAllowPartialRows ),<br />

AutoHScroll<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 当 用 户 在 行 尾 输 入 字 符 时 控 件 是 否 可 以 卷 动<br />

文 本 以 显 示 输 入 的 字 符 。 如 果 设 为 False, 只 允 许 适 合 框 边 界 的 文 本 。 用 户 回 车 时 , 控 件 把<br />

所 有 文 本 卷 动 到 零 位 。 缺 省 值 是 True( 自 动 卷 动 文 本 )。<br />

可 以 使 用 editControl::getAutoHScroll/0‐> 谓 词 检 查 控 件 的 设 置 :<br />

IsAutoHScrollSet = controlName : getAutoHScroll (),<br />

IsAutoHScrollSet = true,<br />

也 可 以 使 用 editControl::setAutoHScroll/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsAutoHScrollSet = true,<br />

controlName : setAutoHScroll ( IsAutoHScrollSet ),<br />

AutoNotify<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 只 要 用 户 在 表 框 中 点 击 或 双 击 , 该 表 框 控 件 会<br />

给 父 窗 口 一 个 输 入 消 息 的 通 报 。 缺 省 值 是 True( 通 报 )。<br />

可 以 使 用 listBox::getAutoNotify/0‐> 谓 词 检 查 控 件 的 状 态 :<br />

IsAutoNotify = controlName : getAutoNotify (),<br />

IsAutoNotify = true,<br />

也 可 以 使 用 listBox::setAutoNotify/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsAutoNotify = true,<br />

controlName : setAutoNotify ( IsAutoNotify ),<br />

AutoVScroll<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 在 多 行 编 辑 控 件 中 当 用 户 在 最 后 一 行 回 车 时<br />

是 否 自 动 上 卷 文 本 。 缺 省 值 是 False( 不 卷 动 )。 对 单 行 编 辑 控 件 这 个 属 性 无 效 。<br />

可 以 使 用 editControl::getAutoVScroll/0‐> 谓 词 检 查 控 件 的 状 态 :<br />

IsAutoVScrollSet = controlName : getAutoVScroll (),<br />

IsAutoVScrollSet = true,<br />

也 可 以 使 用 editControl::setAutoVScroll/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsAutoVScrollSet = true,<br />

controlName : setAutoVScroll ( IsAutoVScrollSet ),<br />

Border<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 控 件 创 建 时 是 否 要 一 个 边 框 , 缺 省 值 是 False。<br />

可 以 使 用 editControl::getBorder/0‐> 或 listBox::getBorder/0‐> 检 查 控 件 的 状 态 :<br />

188


BorderIsSet = controlName : getBorder (),<br />

BorderIsSet = true,<br />

也 可 以 使 用 editControl::setBorder/1 或 listBox::setBorder/1 谓 词 在 程 序 中 对 控 件 状 态 进 行<br />

设 置 :<br />

BorderIsSet = true,<br />

controlName : setBorder ( BorderIsSet ),<br />

Case<br />

可 取 的 值 有 Insensitive、Upper 和 Lower。 缺 省 值 是 Insensitive, 表 示 该 编 辑 控 件 对 输 入<br />

字 符 的 大 小 写 不 作 处 理 。<br />

Upper 表 示 把 所 有 输 入 的 字 母 转 换 成 大 写 。<br />

可 以 用 editControl::getUpperCase/0‐> 谓 词 检 查 控 件 的 状 态 :<br />

IsUpperCase = controlName : getUpperCase (),<br />

IsUpperCase = true,<br />

也 可 以 用 editControl::setUpperCase/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsUpperCase = true ,<br />

controlName : setUpperCase ( IsUpperCase ),<br />

Lower 表 示 把 所 有 输 入 的 字 母 转 换 成 小 写 。 可 以 用 editControl::getLowerCase/0‐> 谓 词<br />

检 查 控 件 的 设 置 :<br />

IsLowerCase = controlName : getLowerCase (),<br />

IsLowerCase = true,<br />

也 可 以 使 用 editControl::setLowerCase/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsLowerCase = true ,<br />

controlName : setLowerCase ( IsLowerCase ),<br />

Class<br />

对 用 户 控 件 指 定 一 个 窗 口 类 的 名 字 ( 应 用 程 序 中 必 须 用 这 个 名 字 对 用 户 控 件 进 行 登 记 )。<br />

Default<br />

可 取 的 值 有 True 和 False。 这 个 属 性 定 义 了 按 键 控 件 是 否 为 缺 省 的 按 键 。 当 用 户 回 车 时 ,<br />

缺 省 按 键 就 激 活 了 。<br />

可 以 使 用 dialog::tryGetDefaultButton/0‐> 谓 词 得 到 对 话 框 中 缺 省 按 键 的 对 象 ; 同 样 , 也<br />

可 以 使 用 dialog::setDefaultButton/1 谓 词 设 置 对 话 框 中 的 缺 省 按 键 。<br />

在 一 个 对 话 框 中 只 能 有 一 个 缺 省 按 键 , 但 可 以 有 多 个 按 键 的 这 个 属 性 值 是 True。IDE 会<br />

把 最 后 一 个 设 成 True 值 的 按 键 定 义 为 对 话 框 的 缺 省 按 键 。 只 有 OK 按 键 的 这 个 属 性 值 缺 省 时<br />

是 True, 其 它 所 有 按 键 的 缺 省 值 都 是 False。<br />

ExtendedSel<br />

可 取 的 值 有 True 和 False。 如 果 设 为 True, 表 框 控 件 允 许 用 Shift 键 和 鼠 标 或 Shift 及 方<br />

向 键 进 行 项 的 多 选 。 缺 省 是 False( 不 能 多 选 )。<br />

可 以 使 用 listBox::getExtendedSel/0‐> 谓 词 检 查 控 件 的 状 态 :<br />

IsExtendedSel = controlName : getExtendedSel (),<br />

IsExtendedSel = true,<br />

也 可 以 用 listBox::setExtendedSel/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsExtendedSel = true,<br />

controlName : setExtendedSel ( IsExtendedSel ),<br />

HideSel<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 编 辑 控 件 就 会 在 失 去 焦 点 时 隐 藏 选 项 、 得 到<br />

焦 点 时 重 新 显 示 选 项 。 缺 省 值 是 True。<br />

可 以 使 用 editControl::getHideSel/0‐> 检 查 控 件 设 置 的 状 态 :<br />

189


IsHideSel = controlName : getHideSel (),<br />

IsHideSel = true,<br />

也 可 以 使 用 editControl::setHideSel/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsHideSel = true,<br />

controlName : setHideSel ( IsHideSel ),<br />

HScroll<br />

可 取 的 值 有 True 和 False。 这 个 属 性 确 定 了 多 行 编 辑 或 表 框 控 件 在 需 要 时 是 否 会 有 一 个<br />

水 平 滚 动 条 。 缺 省 值 是 False( 没 有 水 平 滚 动 条 )。 这 个 属 性 对 单 行 编 辑 控 件 无 效 。<br />

可 以 使 用 editControl::getHScroll/0‐> 或 listBox::getHScroll/0‐> 谓 词 检 查 控 件 的 状 态 :<br />

isHScrollSet = controlName : getHScroll (),<br />

IsHScrollSet = true,<br />

也 可 以 使 用 editControl::setHScroll/1 或 listBox::setHScroll/1 predicates 谓 词 在 程 序 中 对 控<br />

件 状 态 进 行 设 置 :<br />

IsHScrollSet = true,<br />

controlName : setHScroll ( IsHScrollSet ),<br />

IconName<br />

图 标 名 称 必 须 从 工 程 中 登 记 过 的 图 标 名 称 表 中 选 择 ( 点 击 按 钮 )。 要 使 一 个 图 标 名 称<br />

出 现 在 登 记 过 的 图 标 名 称 表 中 , 必 须 把 图 标 文 件 ( 相 应 的 .ICO 文 件 ) 用 File|Add 命 令 加 入<br />

到 工 程 文 件 中 去 ( 资 源 文 件 应 该 在 文 件 类 型 中 选 择 )。<br />

IgnorePrefix<br />

可 取 的 值 有 True 和 False。 设 为 True 时 , 可 以 防 止 静 态 文 本 控 件 的 标 题 中 & 符 号 被 解 释<br />

为 加 速 键 。 缺 省 值 是 False。 要 注 意 , 静 态 文 本 控 件 本 身 是 不 能 激 活 的 , 因 此 , 如 果 静 态 文<br />

本 控 件 中 有 加 速 键 的 话 , 按 了 它 , 会 激 活 以 Tab 站 点 顺 序 排 列 的 下 一 个 控 件 。 这 个 标 志 常 常<br />

用 来 在 静 态 文 本 控 件 中 显 示 文 件 名 , 而 文 件 名 又 可 能 包 含 有 & 符 号 。<br />

可 以 使 用 textControl::getIgnorePrefix/0‐> 谓 词 来 检 查 控 件 设 置 的 状 态 :<br />

IsIgnorePrefix = controlName : getIgnorePrefix (),<br />

IsIgnorePrefix = true,<br />

也 可 以 使 用 textControl::setIgnorePrefix/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsIgnorePrefix = true,<br />

controlName : setIgnorePrefix ( IsIgnorePrefix ),<br />

LeftText<br />

可 取 的 值 有 True 和 False。 这 个 属 性 规 定 了 控 件 的 标 题 文 本 是 位 于 控 件 ( 核 选 框 或 单 选<br />

钮 ) 的 左 边 还 是 右 边 。<br />

可 以 使 用 checkButton::getLeftText/0 ‐> 或 radioButton::getLeftText/0‐> 谓 词 检 查 控 件 的 设<br />

置 :<br />

IsLeftText = checkButtonName:getLeftText(),<br />

IsLeftText = true,<br />

也 可 以 使 用 checkButton:setLeftText/1 或 radioButton:setLeftText/1 谓 词 在 程 序 中 对 控 件 状<br />

态 进 行 设 置 :<br />

IsLeftText = true,<br />

checkButtonName:setLeftText( IsLeftText ),<br />

MultiColumn<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 表 框 控 件 可 以 有 多 列 。 缺 省 值 是 False。<br />

可 以 使 用 listBox::getMultiColumn/0‐> 检 查 控 件 的 设 置 :<br />

IsMultiColumn = controlName : getMultiColumn (),<br />

190


IsMultiColumn = true,<br />

也 可 以 使 用 listBox::setMultiColumn/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsMultiColumn = true,<br />

controlName : setMultiColumn ( IsMultiColumn ),<br />

MultiLine<br />

可 取 的 值 有 True 和 False。 这 个 属 性 如 果 是 True, 则 该 编 辑 控 件 可 以 有 多 行 。 缺 省 值 是<br />

True。 注 意 :VScroll、HScroll、AutoVScroll 和 WantReturn 属 性 对 单 行 编 辑 (False) 控 件 不 起<br />

作 用 。<br />

可 以 使 用 editControl::getMultiLine/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsMultiLine = controlName : getMultiLine (),<br />

IsMultiLine = true,<br />

也 可 以 用 editControl::setMultiLine/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsMultiLine = true,<br />

controlName : setMultiLine ( IsMultiLine ),<br />

MultiSelect<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 表 框 控 件 可 以 用 鼠 标 进 行 多 项 选 择 。 缺 省 值<br />

是 False( 不 能 多 选 )。<br />

可 以 使 用 listBox::getMultiSelect/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsMultiSelect = controlName : getMultiSelect (),<br />

IsMultiSelect = true,<br />

也 可 以 使 用 listBox::setMultiSelect/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsMultiSelect<br />

= true,<br />

controlName : setMultiSelect ( IsMultiSelect ),<br />

Password<br />

可 取 的 值 有 True 和 False。 如 果 设 置 为 True, 该 编 辑 控 件 会 把 所 有 输 入 的 字 符 显 示 成 星<br />

号 (*)。 缺 省 值 是 False( 按 输 入 原 样 显 示 )。<br />

可 以 使 用 editControl::getPassword/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsPassword = controlName : getPassword (),<br />

IsPassword = true,<br />

也 可 以 使 用 editControl::setPassword/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

isPassword<br />

= true,<br />

controlName : setPassword ( IsPassword ),<br />

ReadOnly<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 编 辑 控 件 就 处 在 只 读 模 式 。 缺 省 值 是 False<br />

( 可 以 编 辑 )。<br />

可 以 使 用 editControl::getReadOnly/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsReadOnly = controlName : getReadOnly (),<br />

IsReadOnly = true,<br />

也 可 以 使 用 editControl::setReadOnly/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsReadOnly = true,<br />

controlName : setReadOnly ( IsReadOnly ),<br />

Rows<br />

可 取 的 值 是 正 数 。 对 表 按 键 和 表 编 辑 控 件 来 说 , 这 个 属 性 规 定 了 在 打 开 的 表 框 子 控 件 显<br />

示 的 最 大 行 数 。 缺 省 值 是 3。<br />

可 以 使 用 listControl::getMaxDropDownRows/0‐> 谓 词 检 查 控 件 设 置 的 数 :<br />

MaxDropDownRowNumber = controlName : getMaxDropDownRows (),<br />

191


也 可 以 用 listControl::setMaxDropDownRows/1 谓 词 在 程 序 中 对 控 件 这 个 属 性 进 行 设 置 :<br />

MaxDropDownRowNumber = 5,<br />

controlName : setMaxDropDownRows ( MaxDropDownRowNumber ),<br />

Sort<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 表 控 件 按 字 母 顺 序 对 串 进 行 排 序 。 缺 省 值 是<br />

True( 排 序 )。<br />

可 以 使 用 listControl::getSort/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsSort = controlName : getSort (),<br />

IsSort = true,<br />

也 可 以 使 用 listControl::setSort/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsSort = true,<br />

controlName : setSort ( IsSort ),<br />

StaticScrollbar<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 表 控 件 在 框 内 没 有 更 多 的 项 需 要 滚 动 显 示 时<br />

会 显 示 一 个 禁 用 的 垂 直 滚 动 条 。 如 果 是 False, 框 内 项 减 少 到 不 需 要 滚 动 显 示 时 垂 直 滚 动 条<br />

就 会 隐 藏 起 来 。 缺 省 值 是 True( 显 示 垂 直 滚 动 条 )。<br />

可 以 使 用 listControl::getStaticScrollbar/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsStaticScrollbar = controlName : getStaticScrollbar (),<br />

IsStaticScrollbar = true,<br />

也 可 以 用 listControl::setStaticScrollbar/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsStaticScrollbar = true,<br />

controlName : setStaticScrollbar ( IsStaticScrollbar ),<br />

Style<br />

按 键 和 组 框 可 以 有 好 几 种 形 态 样 式 , 这 个 属 性 规 定 控 件 使 用 哪 个 。 对 按 键 来 说 , 这 个 属<br />

性 可 取 的 值 有 :Cancel、OK 和 Regular。<br />

Cancel 这 个 形 态 的 按 键 是 由 button::newCancel/1 构 造 器 创 建 的 。 在 对 话 框 / 表 格 中 有 一<br />

个 这 样 的 按 键 时 , 该 对 话 框 / 表 格 可 以 提 供 对 ESC 键 的 标 准 处 理 过 程 —— 对 话 框 / 表 格 只 是 关<br />

闭 而 没 有 其 它 的 附 加 动 作 。<br />

OK 这 个 形 态 的 按 键 是 由 button::newOk/1 构 造 器 创 建 的 。 在 对 话 框 / 表 格 中 有 一 个 这 样<br />

的 按 键 时 , 该 对 话 框 / 表 格 在 OK 键 被 点 击 时 会 执 行 确 认 动 作 。<br />

Regular 这 个 形 态 的 按 键 是 由 button::new/1 构 造 器 创 建 的 。 这 样 的 按 键 不 会 引 发 任 何<br />

特 殊 的 处 理 。<br />

对 组 框 来 说 , 可 取 的 值 有 Group Box、Simple Border、Horizontal Separator、No Border。<br />

这 些 值 定 义 了 组 框 控 件 的 边 框 形 式 。 缺 省 值 是 Simple Border。<br />

可 以 使 用 groupBox::getBorderStyle/0‐> 谓 词 得 到 边 框 形 式 :<br />

GroupBoxBorderStyle = controlName : getBorderStyle (),<br />

也 可 以 用 groupBox::setBorderStyle/1 谓 词 在 程 序 中 设 置 边 框 形 式 :<br />

GroupBoxBorderStyle = groupBox(),<br />

controlName : setBorderStyle ( GroupBoxBorderStyle ),<br />

UseTabStops<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 该 表 框 控 件 可 以 识 别 和 展 开 TAB 字 符 。 缺 省 值<br />

是 False( 不 展 开 )。<br />

可 以 使 用 control::getUseTabStops/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsUseTabStops = controlName : getUseTabStops (),<br />

IsUseTabStops = true,<br />

也 可 以 用 control::setUseTabStops/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsUseTabStops = true,<br />

192


controlName : setUseTabStops ( IsUseTabStops ),<br />

VScroll<br />

可 取 的 值 有 True 和 False。 这 个 属 性 规 定 了 多 行 控 件 在 需 要 时 能 不 能 有 一 个 垂 直 滚 动 条 。<br />

对 编 辑 控 件 缺 省 值 是 True, 其 它 控 件 是 False。 对 单 行 编 辑 控 件 这 个 属 性 不 起 作 用 。<br />

可 以 使 用 editControl::getVScroll/0‐> 和 listControl::getVScroll/0‐> 谓 词 检 查 控 件 设 置 的 状<br />

态 :<br />

IsVScrollSet = controlName : getVScroll (),<br />

IsVScrollSet = true,<br />

也 可 以 用 editControl::setVScroll/1 和 listControl::setVScroll/0 谓 词 在 程 序 中 对 控 件 状 态 进<br />

行 设 置 :<br />

IsVScrollSet = true,<br />

controlName : setVScroll ( IsVScrollSet ),<br />

WantReturn<br />

可 取 的 值 有 True 和 False。 如 果 是 True, 用 户 在 多 行 编 辑 控 件 输 入 文 本 时 敲 击 的 回 车 也<br />

会 当 作 编 辑 串 的 一 部 分 插 入 进 来 。 如 果 是 False, 击 回 车 键 就 相 当 于 点 击 缺 省 (OK) 按 键 ,<br />

到 底 是 缺 省 按 键 还 是 OK 按 键 起 作 用 , 就 要 看 哪 个 键 的 Default=True 属 性 值 是 最 后 设 置 的 。<br />

这 个 属 性 对 单 行 编 辑 控 件 无 效 。 缺 省 值 是 False。<br />

可 以 使 用 editControl::getWantReturn/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsWantReturn = controlName : getWantReturn (),<br />

IsWantReturn = true,<br />

也 可 以 使 用 editControl::setWantReturn/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

Wrap<br />

可 取 的 值 有 True 和 False。 这 个 属 性 规 定 了 控 件 中 的 文 本 能 否 回 绕 成 多 行 。 缺 省 值 是 True<br />

( 回 绕 )。<br />

可 以 使 用 textControl::getWrap/0‐> 谓 词 检 查 控 件 设 置 的 状 态 :<br />

IsNoWrap = textControlName : getWrap (),<br />

IsNoWrap = true,<br />

也 可 以 用 textControl::setWrap/1 谓 词 在 程 序 中 对 控 件 状 态 进 行 设 置 :<br />

IsNoWrap = false,<br />

textControlName : setWrap ( IsNoWrap ),<br />

193


附 录 2 表 操 作 谓 词<br />

在 list 类 中 VIP 提 供 了 很 多 对 表 进 行 操 作 的 谓 词 , 所 有 这 些 谓 词 都 是 函 数 , 也 就 是 说 ,<br />

必 须 把 结 果 绑 定 给 一 个 变 量 。 例 如 , 谓 词 append/2 用 于 把 一 个 表 附 加 在 另 一 个 表 后 面 , 该<br />

谓 词 的 声 明 是 :<br />

append : (Elem* Front, Elem* Tail)<br />

‐> Elem* List.<br />

从 这 个 声 明 可 以 看 出 , 这 是 一 个 多 态 谓 词 。 就 是 说 , 它 可 以 接 受 任 意 输 入 , 只 要 是 个 表<br />

就 行 。 这 样 , 这 个 谓 词 可 以 用 于 整 数 表 、 串 表 , 等 等 。 谓 词 的 模 式 省 略 了 , 因 而 是 过 程 的 ;<br />

流 模 式 省 略 了 , 所 以 全 部 参 数 都 是 输 入 。 要 调 用 这 个 谓 词 , 可 以 这 样 :<br />

...<br />

List = list::append(Front, Tail),<br />

...<br />

变 量 List 将 包 含 结 果 , 也 就 是 一 个 表 , 它 是 表 Front 后 接 表 Tail 形 成 的 。 当 在 程 序 中 打<br />

开 了 list 类 后 , 则 可 以 简 化 成 :<br />

...<br />

List = append(Front, Tail),<br />

...<br />

下 面 , 对 list 类 中 一 些 重 要 的 谓 词 做 简 要 介 绍 , 在 帮 助 文 件 中 可 以 看 到 其 它 的 谓 词 。<br />

list::append/2‐><br />

append : (Elem* Front, Elem* Tail) ‐> Elem* List<br />

List 是 Front 附 加 Tail 的 结 果 。<br />

list::appendList/1‐><br />

appendList : (Elem** ListList) ‐> Elem* List<br />

这 个 谓 词 的 输 入 是 一 个 表 的 表 , 结 果 是 所 有 这 些 表 接 在 一 起 的 一 个 表 。<br />

list::getMember_nd/1‐><br />

getMember_nd : (Elem* List) ‐> Elem Value nondeterm.<br />

返 回 表 List 的 成 员<br />

20 。<br />

list::isMember/2<br />

isMember : (Elem Value, Elem* List) determ.<br />

如 果 元 素 Value 是 表 List 的 成 员 , 则 成 功 。<br />

20<br />

译 注 : 仅 看 这 段 文 字 译 者 不 明 白 它 返 回 的 是 什 么 , 原 文 是 “Returns the members Value of the list List”。 用 法 参 见 10.4 节 有 关<br />

表 内 涵 描 述 中 创 建 一 个 正 数 表 的 例 子 。 按 那 个 例 子 看 , 它 应 该 能 逐 个 返 回 表 中 的 元 素 。<br />

194


list::length/1‐><br />

length : (Elem* List) ‐> core::positive Length.<br />

返 回 表 List 的 长 度 ( 以 大 于 等 于 0 的 自 然 数 表 示 )。<br />

list::maximum/1‐><br />

maximum : (Elem* List) ‐> Elem Item.<br />

返 回 表 List 中 值 最 大 的 元 素 。<br />

list::minimum/1‐><br />

minimum : (Elem* List)<br />

‐> Elem Item.<br />

返 回 表 List 中 最 小 的 元 素 。<br />

list::nDuplicates/2‐><br />

nDuplicates : (Elem Element, core::positive N) ‐> Elem* List.<br />

输 入 参 数 一 个 是 元 素 Element 一 个 是 正 数 N, 创 建 一 个 表 , 包 含 N 个 元 素 Element。<br />

list::nth/2‐><br />

nth : ( core::positive Index, Elem* List) ‐> Elem Item procedure.<br />

返 回 表 中 第 n 个 元 素 。<br />

list::remove/2‐><br />

remove : (Elem* List, Elem Value) ‐> Elem* ListWithoutValue<br />

去 掉 表 中 第 一 个 遇 到 的 Value 元 素 。<br />

list::removeAll/2‐><br />

removeAll : (Elem* List, Elem Value) ‐> Elem* ListWithNoValues<br />

去 掉 表 中 所 有 的 Value 元 素 。<br />

list::removeDuplicates/1‐><br />

removeDuplicates : (Elem* List) ‐> Elem* ListWithNoDuplicates<br />

去 掉 表 中 所 有 重 复 的 元 素 , 最 后 出 现 的 那 一 个 重 复 元 素 要 保 留 , 这 会 影 响 结 果 表 中 的 元<br />

素 次 序 。<br />

195


list::reverse/1‐><br />

reverse : (Elem* List) ‐> Elem* Reverse<br />

逆 序 排 列 表 中 的 元 素 。<br />

list::setNth/4<br />

setNth : (core::positive Index, Elem* List, Elem Item, Elem* NewList) procedure (i,i,i,o).<br />

把 List 中 第 N 个 元 素 用 Item 替 换 , 返 回 替 换 后 的 新 表 NewList。<br />

list::sort/1‐><br />

sort : (Elem* List) ‐> Elem* SortedList<br />

对 List 进 行 排 序 。<br />

list::sort/2‐><br />

sort : ( Elem* List, sortOrder Order) ‐> Elem* SortedList.<br />

SortedList 是 表 List 经 过 排 序 后 的 新 表 。Order 指 定 排 序 方 式 , 它 可 以 为 ascending()( 升<br />

序 ) 和 descending()( 降 序 ), 缺 省 时 是 升 序 。<br />

list::sortBy/2‐><br />

sortBy : ( comparator{Elem} Comparator, Elem* List) ‐> Elem* SortedList.<br />

SortedList 是 表 List 经 过 排 序 后 的 新 表 。Comparator 是 用 户 定 义 的 一 个 比 较 器 谓 词 。<br />

list::sortBy/3‐><br />

sortBy : ( comparator{Elem} Comparator, Elem* List, sortOrder Order) ‐> Elem* SortedList<br />

SortedList 是 表 List 经 过 排 序 后 的 新 表 。Comparator 是 用 户 定 义 的 一 个 比 较 器 谓 词 。Order<br />

指 定 排 序 方 式 , 它 可 以 为 ascending()( 升 序 ) 和 descending()( 降 序 ), 缺 省 时 是 升 序 。<br />

list::tryGetIndex/2‐><br />

tryGetIndex : ( Elem Item, Elem* List) ‐> core::positive Index determ.<br />

获 取 List 中 Item 的 索 引 号 。 如 果 List 中 没 有 找 到 Item 则 该 谓 词 失 败 。<br />

196

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

Saved successfully!

Ooh no, something went wrong!