第 1 页
导读
学 会一 种程 序 设计 语言 , 是一 回事 儿 ;学 会如 何以 此 语言 设计 并 实现 有效 的
程序,又是一回事儿。
尤其如 此,因为 它很不寻常 地涵盖了 罕见的威 力和丰
富的表现力 ,不但建立在一个全功能的传统语言 ( )之上,更提供极为广泛的面
向 对 象(
)性 质
,以 及 对
和
( 异 常 状 态 )的 支 持
假以适当运用,
是个可以让你感受愉悦的伙伴。各种不同的设计方式,
包括面向对象形式和传统形式,都可以直接在这个语言中表现并有效地实现出来
你可以定义新的数据型别 ,它们和语言内建的型别表面上无分轩轾,实质上则更
具 弹 性 明 智 地 选 用 一 些 谨 慎 设 计 的
自 动 完 成 内 存 管 理 、别 名
)处理 、初始化 动作与清 理动作 、型 别转换 ,以及软件 开发的其 它难题
与 祸根
可以 使程 序设 计更 容易 、更 直观 、更有 效 、更少 错误 。是 的 ,要写
出有效的
程序并不会太困难,如果你知道怎么做的话
如果没有什么训练与素养,就贸然使用
,会导致写出来的代码不易理解,
不易维护,不易扩充,缺乏效率,而且容易出错。
关键在于找出
可能绊倒你的障碍有哪些,然后学习如何避开它们。这正
是本书的目的。我假设你已经认识
并对它有某种程度的使用经验。我提供一
些准则 ,让你更有效地使用这种语言,使你的软件容易理解 ,容易维护,容易扩
充,效率高,而且行为如所预期。
我提出的忠告分为两大类 :一般性的设计策略 ,以及特殊的(比较难得一见
的)语言特性。
第 2 页
设计方面的讨论集中在如何在不同的方法(从而得以用
达成某个目标)
之 间 取 舍 如 何 在
(
继 承 )和
( 模 板 )之 间 选 择 ?如 何 在
和
在
在
(泛型指针)
之间,在
(公开继承)和
(
私有继承)之间,在
和
(分层技术)之间,
(函数重载)和
(参数
默认值)之间,
(虚拟函数)和
(非
虚 拟 函 数 ) 之 间 ,在
( 传 值 )和
(传址)之间,进行选择?一开始就做
出 正确 的决 定是 很重 要的 ,因 为不 正确 的选 择或 许不 会一 下子 就产 生影 响 ,但是
在 开发 过程 的后 期 ,矫正 它往 往很 困难 ,很 花时 间 ,很混 乱 ,很令 人沮 丧 ,事倍
功半 ,成本很高。
即 使 在 你 确 切 知 道 你 要 做 什 么 之 后 ,把 它 做 对 恐 怕 也 不 是 件 太 容 易 的 事 。
什么是
运算符的适当传回型别?当
无法找出足够的
内存时,它该有怎样的行为?
何时应
该被声明为
你应该写一
个
(成员初始化列)吗?在如斯细节中努力,也颇具有决
定 性 ,因为 如果 不这 样 ,常会 导致 意料 之外 或神 秘难 解的 程序 行为 。更 糟的 是 ,
这 类脱 轨行 为可 能不 会立 即浮 现 ,这些 恐怖 的代 码或 许能 通过 品质 检验 ,却 仍然
藏匿着许多未侦测出来的臭虫
不定时炸弹正等待引爆。
这 不 是 一 本 必 须 一 页 页 读 下 去 才 有 感 觉 的 书 籍 你 甚 至 不 需 要 按 顺 序 读 它
所有素材 被我分为
个 条款 ,每 一个 都相 当独 立 。不 过条 款之 间会 彼此 联系 ,
所 以阅 读本 书的 一种 方法 是先 从感 兴趣 的条 款开 始 ,然后 遵循 其参 考指 示 ,进一
步读下去。
所 有条 款 被我 分为 七 大类 如 果你 对某 类 主题 特 别感 兴趣 ,例如 “内存 管理 ”
或 “面 向对 象设 计” ,可 以从 相关 章节 开始 ,一 路读 下去 ,或 是跳 跃前 进 。不过
最后你会发现,本书的所有内容对于高效的
程序设计而言 ,都十分基础而重
要,所以几乎每个条款最后都会和其它条款互有牵连
这并 不是 一本
参考工具书,也不是一本让你从头学习
的书。例如,
虽然我热切告
诉你一些有关“撰写自己的
”的注 意 事 项( 条 款
,
第 3 页
但是我假设你可以从其它地方获知,
必
须传回一个
,其第一
引数(
的型别必须是
许多
语言书籍可以带给你这样的信息
这本书的目的是要强调那些其它
书籍往往浅浅带过(如果有的话)的
程
序设计概念。其它书籍描述的是
语言的各个成分,本书则告诉你如何将那些
成 分 组合 起 来 ,完 成 一 个有 效 的 程序 其 它 书籍 告 诉 你如 何 让 程序 顺 利 编译 ,本
书则告诉你如何避开编译器不会告诉你的一些问题。
和大部分语言一样,
有着丰富的“传统”,在程序员之间口耳相传,形
成这个语言的伟大传承的一部分 。我企图在这本书中以容易阅读的形式记录一些
长久累积而来的智慧
然而在此同时,我必须告诉你,本书仅限于正统的、可移植的
语 言 只
有
明列于
标准 (见 条款
)中的 性质 ,才 会被本书 采用本书 之中 ,
可 移植 性是 个关 键考 虑 因素 。如 果你 想 要寻 找因 编译 器而 异 的特 殊技 法 ,本书 不
适合你
但是,啊呀,标准规格所描述的
与社区软件商店所卖的编译器 的表现,
多 少 有 点 出 入 所 以 当 我 指 出 某 个 新 的 语 言 特 性 颇 有 用 处 时 , 我 也 会 告 诉 你 如 何
在 缺 乏 那 些 特 性 的 情 况 下 做 出 有 效 的 软 件 毕 竟 在 确 知 未 来 即 将 如 何 如 何 之 际 ,
却 忽略 那些 美丽 远景 而 尽做 些低 下的 劳 力工 作 ,容我 坦言 是 相当 愚蠢 的 ;但是 反
过来 看,你也 不能在 最新最伟 大的
编 译器 降临 世界之 前 ,空 自等待 而束
手无策呀你必须和你 手上可用的工具一起打拼 ,而本书正 打算帮助你这么做 。
注意
我说编译器
复 数 不 同 的 编 译 器 对 标 准
的满足程度各不
相
同 ,所以我鼓励你至少以两种编译器
来 开 发 程 序 这 么 做 可 以
帮 助 你 避 免 不 经
意 仰赖 某个 编译 器专 属 的语 言延 伸性 质 ,或 是误 用某 个编 译 器对 标准 规格 的错 误
阐 示 。这也 可以 帮助 你 避免 使用 过度 先 进的 编译 器特 殊技 术 ,例 如独 家厂 商才 做
得 出 来 的 某 种 语 言 新 特 性 如 此 特 性 往 往 实 现 不 够 精 良 ( “ 臭 虫 ” 多 , 要 不 就 是
表现迟缓,或两者兼具),而且
社群往往对这些特性缺
乏使用经验 ,无法给
你 应 用 上 的 忠 告 雷 霆 万 钧 之 势 固 然 令 人 兴 奋 , 但 当 你 的 目 标 是 要 产 出 可 靠 的 代
码时,恐怕还是要步步为营(并且能够与人合作)得好。
第 4 页
你在本书中找不到
的必杀秘技
,也看不到通往
完美软件的唯一真
理
项条款中的每一个带给你的都只是准则,包括如何完成较好的设计,如何
避免常见的问题,如何得到更高的效率,但任何条款都不可能放之四海而皆准。软
件的定义和实现是极为复杂的工作,常会受到硬件、操作系统以及应用软件的束缚,
所以我能够做的最好事情就是提供一些准则 让你可以依此产生出比较好的程序。
如果 任何时 候你都 奉行每 一个条 款, 应该不 太可能 掉进最 常见的 一些
陷
阱 不过 准 则毕 竟 只 是准 则 ,可 能 存在 例 外 情况 。 那正 是 为什 么 每 个条 款 都带 有
堆 解释 的原 因 。这 些解 释是 本 书最 重要 的 资产 唯有 彻 底了 解一 个条 款 背后 的基 本
原理,你才能合理决定此条款是否适用于手上的项目,或你正苦苦探索的难题上。
本书的最佳用途,就是增进你对
行为的了解,知道它为什么有那样的表
现 , 以 及 如 何 将 其 行 为 转 化 为 你 的 利 益 盲 目 运 用 本 书 所 列 的 条 款 并 不 适 当 , 不
过话说回来 ,你或许不应该在缺乏充足理由的情况下 ,任意违反任何一项条款。
这 样 性 质 的 书籍 中 ,专 用 术 语 的 解 释并 非 重 点 所 在 那 样 的 工作 顶 好 是 留 给
语言界的“律师”去做。然而有少量
词汇是每个人都应该要懂的。以下术语
一再出现,所以有必要确定你我之间对它们有共同的认知。
所 谓 声 明(
,是用来
将一个
或
的型别名称告诉编译器。声明式并不带有细节信息。下面统统都是声明:
所谓定义 (
,是 用 来 将 细 节 信 息 提 供 给 编 译 器 对
而
言 ,
其定义式是编译器为它配置内存的地点
。对
其定义式提供函数本体(
。对
义式
必须列出该
或
的所有
或
或
:
而 言 ,
而 言 , 其 定
第 5 页
/
这是对象的定义式
/
这是函数的定义式
此函数传回其参数的阿拉伯数字个数
这是
的定义式
这是
的定义式
上述程序代码把我们带往
所谓的
以“无需任何引数(
就
被调用
者 。这样的一个
意指可
如果不是
没有任何参数 ,就是每个参数都有默认值。通常当你需要定义对象数组时 ,就会
需要一个
调用
次
第 6 页
//
;
/ / 调 用
//每次都给引数
次
//这不是一个
错 误!
或许有时候你会发现,某个
的
有默认参数值,你的
编译器却拒不接受其对象数组 。例如某些编译器拒绝接受上述
的定义,
即使它
其实
符合
标准
这是存在
于
标准规格书和实际编译器行为之间
的一个矛盾例子 截至目前我所知道的每一个编译器 ,都有一些这类不兼容的缺
点
在编译器厂商追上
语言标准之前 ,请保持你的弹性,并安
慰自己,也许
不久后的
某一天,
编译器的表现就可以和
标准规格书所描述的一致了。
附带一提,如果你想要产生一个对象数组,但该对象型别没有提供
,通常 的 做法 是 定 义一 个 指针 数 组取 而 代 之 ,然 后利 用
一一将每
个指 针初 始化:
//没有调用任何
// 置并 建构 一个 对象
//同上
这个做法在任何场合几乎都够用了。 如果 不够 ,你
或许 得使用 条款
所 说
的更高层次(也因此更不为人知)的
”方法。
回到术语上来,所谓
系以某对象作为另一同型对象的初值:
:
( )
:
)
//调用
//调用
第 7 页
调用
或许
最重要的用途就是用来定义何谓“以
方式传递和
传
回对象”。例如,考虑以下效率不佳的做法,以一个函数联结两个
对 象:
其中
需 要 两
个
对
象作为参数,并传回一个
对象作
为 运 算 结 果 不 论 参 数 或 运 算 结 果 都 是 以
方式传递,所以在
进行过程中,会有一个
被调用,用以将
当作
的
初值,再
有 一 个
被
调 用 用 以 将
当作
的 初 值 , 再 有 一 个
被 唤起 ,用 以将
当作
的 初
值 事 实 上 , 只 要 编 译 器 决 定 产 生
中介
的暂时性对象 ,就会需要一些
调 用 动 作 ( 见 条 款
重点是:
便 是 “调 用
” 的 同 义
词
顺 带 一提 , 你 不 能 够真 的 像 上
述 那样 实 现
的
传回
个
是正确的(见条款
和
,但是你应该
以
方式(见条款 )传递那两个参数
其实,如果你有外
援,则并
不需要为
撰 写
的确有外援
,因为
标准程序
库(条款
就内含有一个
事实上你
型别 ,带
有 一个
,做
的事情几乎就是上述
的 行 为 本 书 中 我 并 用
和
两者 (注意前者名称以大写开头 ,后者否),但方式不同
如果我只是需要一般字符串 ,不在意它是怎么做出来的 ,那么我便使用标准程序
库提供的
这也是你应该选择的行为
然而如果我打
算剖析
的
行为,
并因 而需 要某些 实现码 来示 范或验 证 ,我便使 用非标 准的 那个
为一个程序员 ,只要必须用到字符串,就应该尽可能使用标准的
第 8 页
。身
型别;
那种“开发自己的字符串类别,以象征具备
某种成熟功力”的日子已经过去
了 (不过 你 还 是 有 必要
了 解 开 发一 个 像
那样的
所 需 知道 的 课
题)。对“示范或验证”
目的(而且可说只对此种目的)而言,
很是方便
无论如何 ,除非你有很好的理由,否则都不应该再使用旧式的
字符
串。具有良好定义的
型别如今已能够在每一方面都比
更具优势,
并且更好
包括其执行效率
(见条款
和 条款
接 下 来 两 个 需 要
掌 握 的 术 语 是
( 初 始 化 )和
( 赋
值)。对象的初始化行为发生在它初次获得一个值的时候。对于“带有
之
或
,初 始 化 总 是 经 由 调 用 某 个
达成。这和对象的
动作不同,后者发生于“已初始化之对象被赋值新值”的时候:
(初始化)
(初始化)
(初始化)
(赋值)
纯粹从操作
观点来看,
和
之 间的差异 在于前者由
执 行 , 后 者 由
执行 。换句话说 ,这两个动作对应不同的
函数动作
严格区分此二者,原因是上述两个函数所考虑的事情不同。
通常必须检验其引数的有
效性(
,而 大 部 分
运算符不必如此,
因
为其引数必然是合法的 (因为已被建构完成) 。另一方面 ,
动作的
目标 对象 并非是 尚未构 造完 成的对 象,而 是可 能已经 拥有 配置得 来的资 源。 在新
资 源 可 被 赋 值 过 去 之 前 , 旧 资 源 通 常 必 须 先 行 释 放 这 里 所 谓 的 资 源 通 常 是 指 内
存 在
面是
运算符为一个新 值配置内存之前,必须先释放 旧值的内存。下
的
和
运算
符
的可能做法:
//以下是一个可能的
: :
);
;
果指针
不是