VB 程 式 设 计 内 功 讲 座 (二 )
变 数 (物 件)在 程 式 中 的 活 动 模 式
有 时 候 觉 得 写 程 式 的 工 作 跟 侦 探 差 不 多 , 因 为 在 程 式 的 开 发 过 程 中 , 总 免 不 了 会 有 bug 发 生 , 而 程 式 设 计 者 的 责 任 就 是 把 制 造 bug 的 元 凶 找 出 来 。 别 以 为 当 个 侦 探 很 好 玩 , 除 了 必 须 绞 尽 脑 汁 找 出 问 题 之 外 , 还 要 做 到 勿 枉 勿 纵 , 如 果 程 式 是 一 组 人 开 发 出 来 的 , 有 问 题 的 时 候 , 每 个 人 都 不 希 望 问 题 出 在 自 己 所 写 的 程 式 中 , 身 为 一 个 组 长 , 必 须 要 有 足 够 的 能 力 判 断 可 能 是 元 凶 的 程 式 是 哪 一 个 , 然 後 要 求 组 员 进 行 检 测 , 以 求 正 确 地 找 出 问 题 的 原 因 , 否 则 没 有 问 题 的 程 式 检 测 了 半 天 , 不 仅 劳 民 伤 财 , 还 会 延 误 程 式 开 发 的 商 机 。
程 式 错 误 的 原 因 很 多 , 但 根 本 道 理 却 在 资 料 (变 数 或 物 件 )上 面 。 我 们 可 以 把 程 式 简 单 地 分 成「程 式 码」(code)及 「资 料」(data)两 部 分 , 虽 然 两 者 都 可 能 是 造 成 错 误 的 原 因 , 但 程 式 码 的 部 分 在 执 行 阶 段 是 死 的 , 比 较 容 易 侦 测 , 而 资 料 的 部 分 则 会 随 着 程 式 执 行 的 状 况 来 改 变 , 是 比 较 难 掌 握 的 部 分 , 笔 者 作 个 比 喻 , 某 一 大 楼 发 生 了 窃 案 , 那 麽 侦 察 的 方 向 有 二 : (1) 检 查 保 全 系 统 是 否 有 漏 洞 : 由 於 保 全 系 统 是 固 定 的 , 所 以 比 较 容 易 检 查 , 如 果 与 程 式 作 个 比 较 , 它 像 是 程 式 码 的 部 分 , (2) 嫌 疑 犯 的 调 查 : 对 於 可 能 进 出 大 楼 的 人 员 进 行 调 查 , 但 由 於 人 的 行 为 是 自 由 的 , 所 以 这 个 部 分 的 调 查 工 作 要 比 保 全 系 统 的 检 查 来 得 困 难 , 如 果 与 程 式 作 个 比 较 , 它 像 是 资 料 的 部 分 。
上 一 期 我 们 介 绍 了 变 数 的 组 成 元 素 , 藉 以 了 解 资 料 的 基 本 特 性 , 本 文 让 我 们 继 续 探 讨 资 料 在 程 式 中 的 活 动 模 式 , 藉 以 在 程 式 出 状 况 时 能 够 抓 出 造 成 程 式 错 误 的 元 凶 。
记 得 有 一 种 活 动 , 是 在 湖 里 抓 鸭 子 , 别 小 看 鸭 子 笨 笨 的 , 水 中 的 鸭 子 其 实 是 很 难 抓 的 , 所 以 比 较 常 用 的 技 巧 是 将 鸭 子 赶 到 角 落 , 以 缩 小 鸭 子 的 活 动 范 围 , 最 样 子 就 比 较 容 易 抓 到 鸭 子 了 。 变 数 的 道 理 也 高 深 不 到 哪 里 去 , 但 如 果 我 们 连 变 数 的 活 动 范 围 都 搞 不 清 楚 , 那 麽 就 别 想 抓 到 因 为 变 数 所 产 生 的 错 误 。
区 域 变 数
区 域 变 数 (local variable)顾 名 思 义 就 是 指 在 某 一 个 区 域 里 面 活 动 的 变 数 , 当 程 式 进 入 此 一 区 域 时 , 区 域 中 的 变 数 便 会 诞 生 , 但 是 当 程 式 执 行 过 此 一 区 域 时 , 变 数 即 告 消 失 , 典 型 的 例 子 是 程 序 (副 程 式 、 函 数 、 或 事 件 程 序 )中 的 变 数 , 例 如 :
Sub SubX()
Dim x As Integer ' x 是 SubX 副 程 式 区 域 内 的 变 数
x = x + 100
Print x ' 每 次 都 印 出 100
End Sub
以 上 面 这 个 SubX 副 程 式 为 例 , 每 次 呼 叫 进 入 时 , 变 数 x 才 会 诞 生 , 而 当 程 式 执 行 过 End Sub 叙 述 时 (也 就 是 程 式 结 束 此 一 区 域 的 执 行 时 ), 变 数 x 即 告 消 失 , 因 此 每 次 当 SubX 副 程 式 再 度 被 呼 叫 时 , 变 数 x 的 初 值 都 等 於 0。
区 域 变 数 由 於 活 动 范 围 仅 局 限 於 某 一 程 序 , 因 此 只 要 这 个 程 序 不 是 写 得 太 大 , 变 数 的 变 化 情 形 就 很 容 易 掌 握 , 也 就 很 容 易 侦 错 了 , 这 是 程 式 设 计 中 最 常 使 用 的 变 数 类 型 。
全 域 变 数
所 谓 全 域 变 数 , 指 的 是 所 有 区 域 皆 可 使 用 的 变 数 , 也 就 是 说 不 受「程 序」范 围 所 限 制 的 变 数 。 在 VB 里 面 凡 是 撰 写 在 程 序 之 外 的 变 数 均 属 於 全 域 变 数 , 例 如 :
Dim x As Integer ' 全 域 变 数 , 可 供 多 个
程 序 共 用
Sub SubX()
… ' 可 以 使 用 变 数 x
End Sub
Sub SubY()
… ' 也 可 以 使 用 变 数 x
End Sub
在 VB 里 面 , 全 域 变 数 又 可 分 成「模 组 私 用」全 域 变 数 、「模 组 公 用」全 域 变 数 、 及「专 案」全 域 变 数 , 稍 後 笔 者 会 有 更 一 进 步 的 说 明 。
静 态 变 数
所 谓 静 态 变 数 , 在 VB 里 面 是 指 利 用 Static 保 留 字 宣 告 的 区 域 变 数 , 例 如 :
Sub SubX()
Static x As Integer ' 静 态 变 数
Dim y As Integer ' 区 域 变 数
x = x + 100
y = y + 100
Print x ' 印 出 值 依 序 是 100、 200、 300…
Print y ' 每 一 次 都 是 印 出 100
End Sub
以 上 的 静 态 变 数 x 与 区 域 变 数 y 一 样 , 其 活 动 范 围 都 限 定 於 SubX 副 程 式 之 中 , 但 每 次 进 入 SubX 副 程 式 时 变 数 y 的 值 都 会 归 0, 而 x 则 保 有 其 原 来 之 数 值 , 所 以 SubX 副 程 式 中 的 Print x 每 次 列 印 的 数 值 都 会 累 加 100。
选 择 变 数 类 型 的 基 本 原 则
当 我 们 决 定 使 用 某 个 变 数 时 , 该 将 它 宣 告 成 区 域 、 静 态 、 还 是 全 域 变 数 呢 ? 请 参 考 以 下 的 基 本 原 则 :
1.是 否 要 提 供 给 多 个 程 序 共 用 , 如 果 是 , 才 将 变 数 宣 告 成 全 域 变 数 。
有 些 人 可 能 会 觉 得 将 变 数 宣 告 成 全 域 变 数 最 方 便 , 因 为 任 何 程 序 都 可 以 使 用 , 但 是 全 域 变 数 的 缺 点 是 不 容 易 侦 错 , 以 抓 鸭 子 的 活 动 为 例 , 鸭 子 的 活 动 范 围 越 大 , 就 越 难 抓 到 , 同 样 的 , 任 何 程 序 都 可 以 使 用 的 变 数 , 万 一 其 变 数 值 与 我 们 预 期 的 结 果 不 一 样 时 (这 当 然 是 bug), 就 必 须 逐 一 对 每 一 个 程 序 进 行 侦 错 , 无 形 中 增 加 了 程 式 侦 错 的 困 难 度 。
2.是 否 必 须 记 录 程 式 执 行 的 状 态 , 如 果 是 , 则 必 须 变 数 宣 告 成 静 态 变 数 , 因 为 区 域 变 数 每 次 进 入 程 序 的 值 都 会 归 0, 所 以 无 法 一 直 纪 录 着 程 式 的 执 行 状 态 。
3. 如 果 不 属 於 情 况 1、 2, 则 将 变 数 宣 告 成 区 域 变 数 。
Option Explicit: 强 制 变 数 宣 告
VB 允 许 我 们 在 不 必 事 先 宣 告 变 数 之 下 , 就 使 用 变 数 , 例 如 :
Dim X As Integer ' 事 先 宣 告 变 数 X
Y = X + 2 ' Y变 数 未 事 先 宣 告 , 也 是 对 的
但 这 样 的 程 式 撰 写 习 惯 却 可 能 引 起 一 些 不 容 易 侦 测 的 错 误 , 例 如 :
For i = 1 to 10
Arr(i) = Arr(j)+10
Next I
以 上 程 式 中 的 Arr(j) 应 该 是 Arr(i) 才 对 , 但 由 於 VB 允 许 未 宣 告 而 直 接 使 用 变 数 , 以 致 j 被 视 为 合 法 的 变 数 , 但 j 在 回 圈 中 却 一 直 等 於 0。
在 不 必 事 先 宣 告 变 数 之 下 就 使 用 变 数 , 就 好 像 变 数 随 时 随 地 都 会 冒 起 来 , 跑 到 我 们 的 程 式 中 , 而 从 以 上 的 例 子 中 , 我 们 发 现 这 种 作 法 其 实 是 有 缺 点 的 , 为 了 改 善 这 个 缺 点 , VB 提 供 给 我 们 另 一 种 选 择 , 那 就 是 在 所 有 程 序 之 外 加 上 Option Explicit 的 叙 述 , 加 上 Option Explicit 的 作 用 是 :「要 求 VB 把 未 事 先 宣 告 的 变 数 视 为 错 误」, 因 此 以 上 程 式 若 修 改 成 :
Option Explicit
Dim i As Integer ' 事 先 宣 告 i
For i = 1 to 10
Arr(i) = Arr(j)+10
Next I
则 由 於 j 变 数 未 事 先 宣 告 , 会 被 VB 视 为 错 误 , 因 此 可 在 编 译 阶 段 提 早 被 侦 察 出 来 。
对 VB 而 言 , 一 个 专 案 可 以 含 有 多 个 模 组 , 如 果 我 们 在 某 一 个 模 组 之 中 宣 告 了 全 域 变 数 , 那 麽 这 个 全 域 变 数 可 以 提 供 给 该 模 组 的 所 有 程 序 使 用 , 是 无 庸 置 疑 的 事 , 但 是 在 多 模 组 的 专 案 中 , 我 们 则 要 考 虑 另 一 个 问 题 : 某 一 个 模 组 的 全 域 变 数 可 以 给 其 他 模 组 使 用 吗 ?
模 组 私 用 全 域 变 数
在 表 单 模 组 或 一 般 模 组 中 , 如 果 我 们 利 用 Private 或 Dim 保 留 字 来 宣 告 全 域 变 数 , 例 如 : (注 : 一 般 模 组 指 的 是 利 用 功 能 表 的「专 案 /新 增 模 组」而 加 入 於 专 案 之 中 的 模 组 , 此 类 模 组 将 来 会 以 .bas 的 副 档 名 来 储 存 )
Private x As Integer
Sub SubX()
…
End Sub
则 此 一 变 数 为「模 组 私 用」全 域 变 数 , 也 就 是 说 , 此 一 全 域 变 数 只 有 该 模 组 的 程 序 可 以 使 用 , 其 他 模 组 则 不 可 以 使 用 。
模 组 公 用 全 域 变 数
在 表 单 模 组 中 , 如 果 我 们 利 用 Public 保 留 字 来 宣 告 全 域 变 数 , 例 如 :
Public x As Integer
Sub SubX()
…
End Sub
则 此 一 变 数 为「模 组 公 用」全 域 变 数 , 也 就 是 说 , 此 一 全 域 变 数 也 可 以 给 其 他 模 组 使 用 。 但 请 注 意 , 使 用 的 语 法 必 须 在 变 数 之 前 冠 上 表 单 名 称 , 假 设 以 上 例 子 中 的 全 域 变 数 宣 告 在 Form1 之 中 , 则 以 下 是 Form1 模 组 中 的 程 序 与 Form2 模 组 中 的 程 序 , 在 使 用 变 数 x 上 的 差 异 :
' Form1 模 组
Sub SubX()
x = x + 100 ' 像 平 常 使 用 变 数 的 方 法 一 样
End Sub
' Form2 模 组
Sub SubY()
Form1.x = Form1.x + 100
End Sub
由 於 Form2 模 组 使 用 的 是 Form1 模 组 的 全 域 变 数 , 所 以 必 须 在 变 数 之 前 冠 上「Form1.」。
专 案 全 域 变 数
在 一 般 模 组 中 , 如 果 我 们 利 用 Public 保 留 字 来 宣 告 全 域 变 数 , 则 此 一 变 数 为「专 案」全 域 变 数 。 所 谓 专 案 全 域 变 数 , 指 的 是 同 一 专 案 中 , 所 有 模 组 的 所 有 程 序 均 可 使 用 的 变 数 , 因 此 以 这 种 方 式 所 宣 告 的 变 数 其 活 动 范 围 将 扩 及 整 个 专 案 。
最 後 我 们 以 一 个 实 例 来 整 理 以 上 各 种 变 数 在 程 式 中 的 活 动 范 围 , 假 设 专 案 中 有 含 有 两 个 表 单 模 组 — Form1、 Form2 及 一 个 一 般 模 组 Module1, 而 这 几 个 模 组 中 的 所 宣 告 的 变 数 如 下 :
| Form1
Private A1 |
Form2
Private B1 |
Module1
Private C1 |
则 这 些 变 数 在 几 个 副 程 式 中 的 可 使 用 性 如 下 表 : (「?? 」符 号 表 示 可 使 用 、「F1」表 示 必 须 冠 上「Form1.」才 可 以 使 用 、「F2」表 示 必 须 冠 上「Form2.」才 可 以 使 用 , 空 白 者 表 示 不 可 使 用 )
| 副 程 式 | A1 | A2 | A3 | A4 | A5 | B1 | B2 | B3 | B4 | B5 | C1 | C2 | C3 | C4 | C5 |
| SubX1 | ?? | ?? | ?? | ?? | F2 | ?? | |||||||||
| SubX2 | ?? | ?? | ?? | ?? | F2 | ?? | |||||||||
| SubY1 | F1 | ?? | ?? | ?? | ?? | ?? | |||||||||
| SubY2 | F1 | ?? | ?? | ?? | ?? | ?? | |||||||||
| SubZ1 | F1 | F2 | ?? | ?? | ?? | ?? | |||||||||
| SubZ2 | F1 | F2 | ?? | ?? | ?? | ?? |
物 件 的 活 动 范 围
物 件 按 性 质 可 分 成 控 制 元 件 、 表 单 物 件 、 及 一 般 物 件 叁 种 , 其 中 一 般 物 件 与 变 数 一 样 , 可 分 成 区 域 物 件 、 静 态 物 件 、 及 全 域 物 件 叁 种 , 活 动 范 围 也 与 变 数 一 样 , 以 下 让 笔 者 来 说 明 控 制 元 件 与 表 单 的 活 动 范 围 。
控 制 元 件 的 活 动 范 围 与 表 单 完 全 相 同 , 当 表 单 被 载 入 时 , 表 单 上 的 控 制 元 件 即 会 被 载 入 , 当 表 单 被 载 出 时 , 控 制 元 件 即 会 被 载 出 , 若 与 变 数 做 比 较 , 则 与 全 域 变 数 的 活 动 范 围 完 全 相 同 。
由 於 表 单 中 全 域 (静 态 )变 数 及 控 制 元 件 的 诞 生 (灭 亡 )取 决 於 表 单 的 载 入 (载 出 ), 因 此 我 们 必 须 特 别 注 意 表 单 载 入 与 载 出 的 时 机 。 导 致 表 单 被 载 入 的 叙 述 有「Load 表 单 名」、「表 单 .Show」、 及「使 用 表 单 的 属 性 、 全 域 变 数 、 控 制 元 件」叙 述 ; 至 於 表 单 被 载 出 的 情 况 则 有「Unload 表 单 名」叙 述 及「使 用 者 关 闭 表 单」。
当 表 单 被 载 出 系 统 时 , 全 域 变 数 的 变 数 值 会 归 零 , 表 单 及 控 制 元 件 的 属 性 值 会 还 原 成 设 计 阶 段 时 的 设 定 值 , 因 此 如 果 我 们 想 保 留 变 数 值 及 属 性 值 , 不 要 使 用「Unload 表 单 名」关 闭 表 单 , 须 使 用「表 单 名 .Hide」或「Form.Visible = False」隐 藏 表 单 。 但 笔 者 必 须 特 别 提 醒 您 一 点 , 如 果 有 表 单 被 隐 藏 , 则 程 式 结 束 前 应 该 使 用 以 下 方 法 载 出 所 有 的 表 单 :
For I = 0 To Forms.Count - 1
Unload Forms(I)
Next
否 则 表 面 上 所 有 的 表 单 已 经 被 关 闭 , 让 人 误 以 为 程 式 结 束 了 , 而 实 际 上 却 还 有 表 单 在 系 统 之 中 。
由 於 变 数 执 行 时 会 占 用 记 忆 体 , 因 此 如 何 正 确 地 使 用 变 数 , 也 会 影 响 系 统 的 运 作 , 首 先 让 我 们 思 考 一 个 问 题 :「当 程 式 越 写 越 大 时 , 程 式 所 使 用 的 变 数 也 越 来 越 多 , 会 不 会 演 变 成 程 式 执 行 所 需 之 记 忆 体 超 过 系 统 记 忆 体 , 而 不 能 执 行」, 例 如 我 们 就 经 常 看 到 某 些 产 品 在 包 装 盒 上 面 会 标 明「至 少 须 16MB 记 忆 体」之 类 的 字 眼 。
需 要 使 用 大 量 记 忆 体 的 软 体 通 常 是 功 能 强 大 的 知 名 软 体 , 我 们 自 己 写 的 程 式 是 不 是 也 会 出 现 越 来 越 吃 记 忆 体 的 情 事 呢 ? 感 觉 应 该 不 会 , 但 实 际 不 然 , 如 果 使 用 变 数 时 不 了 解 系 统 内 部 的 基 本 运 作 , 则 即 使 是 小 程 式 也 可 能 会 把 系 统 记 忆 体 吃 光 。
全 域 变 数 与 静 态 变 数
就 我 们 所 讨 论 的 叁 种 变 数 而 言 , 全 域 变 数 及 静 态 变 数 会 在 所 属 之 模 组 被 载 入 系 统 时 , 占 据 一 块 记 忆 体 , 参 考 以 下 程 式 :
Dim X(1024) As Integer ' X 占 有 2 KB 记 忆 体
Sub SubX()
Static Y(2048) As Integer ' X 占 有 4 KB
记 忆 体
…
End Sub
如 果 以 上 程 式 附 属 於「一 般 模 组」(会 在 程 式 被 载 入 时 , 即 载 入 系 统 ), 则 程 式 被 载 入 系 统 时 , X 及 Y 阵 列 将 占 据 6 KB 的 记 忆 体 , 而 直 到 程 式 被 载 出 时 , 这 6KB 的 记 忆 体 才 会 归 还 给 系 统 ; 如 果 以 上 程 式 附 属 於「表 单 模 组」, 则 必 须 等 到 表 单 被 载 入 时 , X 及 Y 阵 列 才 会 占 据 6 KB 的 记 忆 体 , 而 当 表 单 被 载 出 时 , 这 6KB 的 记 忆 体 即 会 归 还 给 系 统 。
了 解 以 上 的 基 本 特 性 之 後 , 笔 者 想 特 别 说 明 以 下 两 种 通 病 :
(1) 将 所 有 的 全 域 变 数 都 放 在「一 般 模 组」中 : 的 确 , 将 共 用 的 变 数 放 在 一 般 模 组 中 最 为 简 便 , 但 由 於 一 般 模 组 中 的 变 数 会 在 程 式 被 执 行 时 即 占 用 系 统 记 忆 体 , 而 在 程 式 结 束 时 才 归 还 记 忆 体 , 这 使 得 变 数 在 程 式 执 行 期 间 一 定 会 占 用 记 忆 体 , 而 减 少 了 系 统 可 运 用 之 记 忆 体 。 要 避 免 此 一 问 题 , 宣 告 大 型 变 数 (例 如 Dim Y(40960) As Integer会 占 用 80 KB)时 , 想 一 想 被 宣 告 的 变 数 是 不 是 只 有 某 一「表 单 模 组」需 要 使 用 , 如 果 是 , 则 将 那 些 变 数 放 在 表 单 模 组 中 , 若 是 小 型 变 数 (例 如 Dim X As Integer 只 占 用 2 bytes), 则 可 不 必 顾 虑 此 一 问 题 。
(2) 将 其 他 模 组 可 能 使 用 的 变 数 放 在 某 一「表 单 模 组」 中 , 然 後 使 用「Form名 .变 数 名」的 格 式 来 使 用 变 数 : 此 一 方 式 并 没 有 什 麽 错 误 , 但 请 注 意 当 表 单 被 载 出 时 , 表 单 中 所 有 变 数 的 记 忆 体 将 会 归 还 系 统 , 而 使 得 变 数 值 全 部 归 零 , 如 果 这 些 变 数 的 用 途 是 记 录 程 式 执 行 的 状 况 , 则 在 表 单 被 载 出 时 , 将 会 失 去 记 录 的 功 效 。
区 域 变 数
当 程 式 进 入 某 一 区 域 时 , 系 统 会 为 该 区 域 的 区 域 变 数 配 置 好 记 忆 体 , 而 程 式 执 行 过 该 区 域 时 , 变 数 所 占 用 之 记 忆 体 又 会 归 还 给 系 统 。
区 域 变 数 可 能 占 用 的 记 忆 体 有「堆 叠」及「系 统 记 忆 体」两 种 , 其 中 堆 叠 是 程 式 载 入 时 就 配 置 给 程 式 的 (通 常 小 於 64KB), 不 管 怎 麽 使 用 都 不 会 影 响 系 统 记 忆 体 , 对 於 小 型 变 数 而 言 , 使 用 的 是 堆 叠 中 的 记 忆 体 , 但 如 果 是 大 型 变 数 , 则 须 占 用 系 统 记 忆 体 。
由 於 区 域 变 数 所 使 用 的 记 忆 体 在 不 使 用 时 即 归 还 系 统 , 一 般 而 言 并 不 会 出 现 耗 尽 系 统 记 忆 体 的 现 象 , 须 特 别 注 意 的 是 递 回 呼 叫 (例 如 副 程 式 呼 叫 自 己 , 或 者 副 程 式 A 呼 叫 副 程 式 B, 而 副 程 式 B 又 呼 叫 回 副 程 式 A), 如 果 程 式 没 有 设 计 好 递 回 呼 叫 的 出 口 , 则 副 程 式 会 一 直 呼 叫 下 去 , 则 位 於 该 副 程 式 中 的 变 数 将 会 持 续 地 占 用 记 忆 体 , 而 可 能 用 尽 堆 叠 或 系 统 记 忆 体 。
物 件 与 系 统 记 忆 体
物 件 与 变 数 最 大 的 差 异 如 图 -1:
图 -1 变 数 与 物 件 的 比 较
比 较 特 别 的 是 物 件 的「具 体 物 件」部 分 , 当 我 们 宣 告 某 一 物 件 变 数 时 (例 如 Dim X As Object), 具 体 物 件 还 不 存 在 , 直 到 执 行 建 立 物 件 的 叙 述 之 後 , 物 件 才 会 含 有 具 体 物 件 (详 阅 上 一 期 VB 专 栏 )。
若 撇 开 具 体 物 件 的 部 分 , 物 件 与 变 数 在 记 忆 体 的 使 用 上 是 完 全 相 同 的 , 所 以 我 们 可 以 把 重 心 放 在 具 体 物 件 上 面 , 具 体 物 件 所 占 用 的 记 忆 体 通 常 会 跟 物 件 变 数 一 起 被 归 还 给 系 统 , 例 如 :
Sub SubX()
Dim X As Object
Set X = New 物 件 类 别 名
…
End Sub
则 程 式 执 行 到 End Sub 时 , X 所 占 用 的 记 忆 体 会 被 释 放 , 而 属 於 X 的 具 体 物 件 也 会 一 并 被 释 放 。
但 并 非 每 一 种 物 件 的 具 体 物 件 都 具 有 以 上 特 性 , 有 些 物 件 的 作 法 是 , 如 果 程 式 要 释 放 具 体 物 件 , 一 定 要 呼 叫 释 放 物 件 的 方 法 , 如 果 程 式 忘 了 这 麽 做 , 则 即 使 程 式 结 束 执 行 了 , 该 具 体 物 件 依 然 会 占 据 系 统 记 忆 体 , 还 好 的 是 此 类 物 件 并 不 多 见 , 笔 者 提 出 此 一 状 况 , 只 是 想 提 醒 您 一 点 , 当 我 们 使 用 某 一 种 新 的 物 件 的 , 须 查 阅 该 物 件 的 说 明 文 件 以 了 解 该 物 件 是 否 为 此 类 物 件 。
当 我 们 将 一 个 大 程 式 分 成 多 个 模 组 或 程 序 之 後 , 为 了 让 不 同 程 序 之 间 能 够 共 享 资 料 , 除 了 全 域 变 数 的 使 用 之 外 , 另 一 个 方 法 则 是 程 序 呼 叫 时 的 参 数 传 递 。 诚 如 我 们 前 面 在「选 择 变 数 类 型 的 基 本 原 则」段 落 中 的 说 明 , 全 域 变 数 少 用 为 宜 , 因 此 参 数 的 传 递 在 程 式 设 计 中 就 益 形 重 要 。
在 VB 里 面 , 程 序 之 间 的 参 数 传 递 有 两 种 方 式 :「传 值 呼 叫」(call by value)及「传 址 呼 叫」(call by address), 这 两 种 参 数 传 递 方 式 在 表 面 上 很 容 易 让 人 忽 略 其 差 异 性 , 但 实 际 上 却 会 影 响 程 式 执 行 的 结 果 , 以 下 先 以 一 个 例 子 来 说 明 这 个 问 题 , 假 设 有 一 副 程 式 定 义 如 下 :
Sub AddOne( x )
x = x + 1
End Sub
而 以 下 是 两 种 不 同 的 呼 叫 方 式 :
| i = 10 Call AddOne( i ) Print i |
i = 10 Call AddOne( (i) ) Print i |
结 果 呼 叫「Call AddOne(i)」之 後 , 所 得 到 的 i 值 等 於 11, 而 呼 叫「Call AddOne( (i) )」之 後 , 所 得 到 的 i 值 却 等 於 10。
注 : 在 以 上 的 呼 叫 格 式 中 , 往 往 会 省 略 Call 保 留 字 , 如 果 省 略 Call 保 留 字 , 则「Call AddOne(i)」要 写 成「AddOne i」 (去 掉 参 数 周 围 的 左 右 括 弧 ), 而「Call AddOne( (i) )」则 写 成「AddOne (i)」。
上 述 例 子 会 有 不 同 的 输 出 结 果 , 是 因 为 参 数 传 递 方 式 有 所 不 同 的 关 系 , 以 下 让 笔 者 来 解 说 上 述 两 种 参 数 传 递 的 差 异 及 工 作 原 理 。
实 际 参 数 与 形 式 参 数
当 我 们 定 义 副 程 式 时 , 例 如 前 面 的 Sub AddOne( x ), 由 於 我 们 并 不 知 道 将 来 呼 叫 者 会 传 入 哪 些 资 料 作 为 参 数 , 因 此 只 好 给 这 个 参 数 取 一 个 暂 时 性 的 名 称 , 例 如 x, 而 将 来 副 程 式 被 呼 叫 时 它 们 会 被 真 正 的 资 料 (常 数 值 或 变 数 )所 取 代 , 所 以 这 些 参 数 都 只 是 形 式 上 的 , 故 称 之 为「形 式 参 数」(formal parameter)。
而 呼 叫 程 式 端 在 呼 叫 副 程 式 时 , 必 须 以 实 际 的 资 料 来 替 代 形 式 上 的 参 数 , 使 得 副 程 式 能 够 拿 到 真 正 的 资 料 来 运 算 , 这 真 正 的 资 料 就 叫 做「实 际 参 数」(actual parameter), 例 如 Call AddOne( i ) 中 的 i。
所 谓 参 数 传 递 的 方 式 , 就 是 在 呼 叫 副 程 式 的 过 程 中 , 编 译 器 (或 解 译 器 )如 何 以 实 际 参 数 来 替 代 形 式 参 数 的 方 法 , 而 方 法 不 同 , 得 到 的 结 果 也 可 能 不 同 , 解 说 参 数 的 传 递 方 式 以 前 , 让 我 们 先 以「变 数 的 四 个 组 成 元 素」来 表 示 实 际 参 数 及 形 式 参 数 的 关 系 , 就 拿 前 面 程 式 为 例 吧 :
图 -2 所 谓 传 递 参 数 , 传 的 到 底 是 名 称 、 资 料 型 别 、 位 址 、 还 是 值 呢 ?
就 图 -2来 看 , 变 数 有 四 个 元 素 , 所 谓 传 递 参 数 , 传 的 到 底 是 名 称 、 资 料 型 别 、 位 址 、 还 是 值 呢 ?
其 实 除 了 传 递「资 料 型 别」无 法 达 到 传 递 参 数 的 目 的 之 外 , 其 他 叁 个 元 素 都 可 以 作 为 参 数 传 递 的 标 的 物 , 不 过 VB 并 不 支 援 传 递「名 称」的 方 式 , 以 下 笔 者 就 针 对 传 「值」及 传「位 址」两 种 方 式 来 加 以 解 说 。
传 值 呼 叫
这 种 传 递 参 数 的 方 式 , 是 把 呼 叫 者 的 实 际 参 数 值 , 复 制 一 份 到 副 程 式 的 形 式 参 数 位 址 内 , 尔 後 副 程 式 不 管 怎 麽 对 形 式 参 数 进 行 运 算 , 完 全 不 会 对 实 际 参 数 的 变 数 值 有 所 影 响 。
图 -3 传 值 呼 叫
以 前 面 的 例 子 来 说 , 呼 叫 程 式 端 在 实 际 参 数 的 前 後 加 上 (), 例 如「Call AddOne( (i) )」, 其 传 递 方 式 就 是 传 值 呼 叫 , 所 以 以 下 的 呼 叫 :
i = 10
Call AddOne( (i) )
Print i
变 数 i 的 值 不 会 因 执 行 了 AddOne() 而 被 改 变 , 因 此 输 出 结 果 等 於 10。
传 位 址 呼 叫
所 谓 传 值 呼 叫 , 我 们 可 以 把 它 想 成「以 实 际 参 数 的 值 取 代 形 式 参 数 的 值」, 而 传 位 址 呼 叫 则 是「以 实 际 参 数 的 位 址 取 代 形 式 参 数 的 位 址」, 示 意 图 如 图 -4:
图 -4传 址 呼 叫
从 图 -4来 看 , 我 们 可 以 把 传 位 址 呼 叫 中 的 实 际 参 数 与 形 式 参 数 想 成「同 一 个 变 数」(名 称 虽 不 同 , 但 实 际 所 占 用 的 是 同 一 块 记 忆 体 )。
以 前 面 的 例 子 来 说 , Call AddOne( i ) 由 於 未 在 变 数 i 的 前 後 再 加 上 (), 被 视 为 传 位 址 呼 叫 , 因 此 呼 叫 之 後 的 i 值 将 因 为 形 式 参 数 x(与 i 为 同 一 变 数 )的 改 变 而 跟 着 改 变 , 所 以 以 下 的 叙 述 :
i = 10
Call AddOne( i )
Print i
i 的 输 出 结 果 等 於 11。
ByVal 保 留 字 与 传 值 呼 叫
在 呼 叫 程 式 端 , 利 用 左 右 括 弧 将 参 数 框 起 来 , 可 使 参 数 传 递 成 为 传 值 呼 叫 , 此 外 , 我 们 也 可 以 在 副 程 式 (函 数 )定 义 端 , 利 用 ByVal 保 留 字 将 参 数 传 递 设 定 成 传 值 呼 叫 , 例 如 副 程 式 定 义 如 下 :
Sub AddOne( ByVal x )
x = x + 1
End Sub
则 不 管「Call AddOne( (i) )」或 是「Call AddOne( i )」均 被 视 为 传 值 呼 叫 。
传 值 呼 叫 的 限 制
在 VB 里 面 ,「阵 列」的 传 递 是 不 可 以 采 用 传 值 呼 叫 的 , 例 如 以 下 的 呼 叫 会 产 生 错 误 :
' SubX 副 程 式 的 定 义
Sub SubX( data() As Integer ) ' data() 代 表 一 阵 列 参 数
…
End Sub
' 呼 叫 SubX 副 程 式
Dim X(100) As Integer
Call SubX( X ) ' 采 传 址 呼 叫 , 正 确
Call SubX( (X) ) ' 采 传 值 呼 叫 , VB 不 接 受
传 递 阵 列 时 , VB 之 所 以 不 接 受 传 值 呼 叫 的 理 由 是 , 传 值 呼 叫 必 须「复 制」整 个 阵 列 , 这 对 於 大 型 阵 列 (例 如 含 有 30000 个 阵 列 元 素 ), 是 一 件 比 较 不 符 合 效 率 的 事 情 , 反 之 , 若 采 用 传 址 呼 叫 , 以 上 面 的 程 式 为 例 , 只 要 将 X 阵 列 的 位 址 设 定 给 data 参 数 即 可 , 而 不 必 大 量 复 制 阵 列 的 资 料 。
传 址 呼 叫 的 限 制
就 像 传 值 呼 叫 有 其 限 制 一 样 , 传 址 呼 叫 也 有 其 限 制 。 传 址 呼 叫 的 限 制 是「不 同 资 料 型 别 的 实 际 参 数 不 可 作 为 传 递 的 参 数」, 例 如 :
' SubX 副 程 式 的 定 义
Sub SubX( I As Integer ) ' 参 数 宣 告 成 Integer
…
End Sub
' 呼 叫 SubX 副 程 式
Dim L As Long
Call SubX( L ) ' 采 传 址 呼 叫
为 什 麽 不 接 受 呢 ? 请 参 考 图 -5:
图 -5 传 址 呼 叫 不 接 受 不 同 型 别 的 资 料 传 递
假 设「Call SubX( L )」是 正 确 的 , 那 麽 将 会 造 成 :「Integer 型 别 的 变 数 I, 其 位 址 却 指 向 一 块 型 别 为 Long 的 记 忆 体」, 这 显 然 违 反 了 程 式 运 作 的 原 则 , 所 以 会 产 生 错 误 。
不 过 , 如 果 我 们 在 副 程 式 定 义 端 , 将 形 式 参 数 宣 告 成 Variant(不 定 型 )或 者 不 宣 告 资 料 型 别 , 则 该 参 数 可 以 接 受 任 何 型 别 的 资 料 , 因 为 Variant 的 资 料 型 别 (及 不 定 型 型 别 )可 随 着 资 料 来 改 变 , 如 图 -6:
图 -6 传 址 呼 叫 不 接 受 不 同 型 别 的 资 料 传 递