有关在C++里编程的思考

  在理想的情况下,你需要通过三个步骤完成设计一个程序的工作。首先,你取得对问题的一个清晰的理解(分析);而后你标识出在一个解决方案所涉及的关键性概念(设计);最后,你用一个程序表达这个解决方案(编程)。然而,问题的细节和解决方案中的概念,常常只有通过在一个程序中描述它们,以及让程序以可接受的方式运行的努力之下,才能真正被清楚地理解。而这正是程序设计语言选择的关键之处。

  在大部分应用中都存在一些概念,它们很不容易表示为某个基本类型,也不容易表述为没有与之关联的数据的函数。遇到一个这样的概念,请在程序里声明一个类去表示它。一个C++类就是一个类型,也就是说,它刻画了这个类的对象的行为;它们如何建立,可以被如何操作,以及它们如何销毁。一个类可能也刻画了这些对象如何表示,虽然在设计一个程序的早期阶段这并不是一个主要考虑。写出好的程序,最关键的就是去设计这些类,使它们中的每一个都能很清楚地表示某个概念。这经常意味着你必须集中注意一些这样的问题;这个类的对象应该如何建立?这个类的对象能够被复制/销毁吗?什么操作能够作用于这种对象?如果对这类问题不存在很好的回答,对应的概念或许是从一开始就很不“清楚”。这时再多想想有关的问题以及为它所设定的解决方案,而不是立即开始去围绕着那个问题编码,这样做可能是个很好的主意。

  最容易处理的概念是那些有着传统数学形式的东西:各种各样的数,集合,几何形状等。基于文本的I/O、字符串、基本容器、对这些容器的基本算法,以及一些数学类都是标准C++库的组成部分。除此之外,还存在着许多可以使用的支持通用的或者各种领域专用概念的令人眼花缭乱的库。

  概念不会存在于真空之中;总是存在着相互关联的一簇簇的概念。将程序里各个类按它们之间的关系组织起来--即确定一个解决方案所涉及到的不同概念之间的准确关系---常常比首先单独列出一些类更困难。最好别把结果弄成了一锅浆糊,其中的每一个类(概念)都相互依赖。考虑两个类,A和B。像“A调用B的函数”,“A建立起一些B”,“A包含一个B成员”之类的关系很少会造成重要的问题。而像“A使用B的数据”之类的关系通常应该能清除掉。

  威力最大的一种管理复杂性的智力工具就是某种层次性的序关系,也就是说,将相互有关的概念组织到一个树形结构中,使最一般的概念成为树根。在C++里,派生类表示的就是这种结构。一个程序常常能组织为一组类,或者一组类的有向无环图。这时,程序员刻画一组基类,每个都有它的一组派生类。虚函数常常能被用于为一个概念的最一般版本(一个基类)定义操作。如果有必要,可以针对特定的类(派生类),对这些操作的解释做进一步的精确化。

  有时,甚至一个有向无环图看起来也不足以组织起一个程序里的概念;有些概念似乎具有内在的相互依赖性。在这种情况下,我们应设法将这种循环依赖关系局部化,使它们不会影响程序的整体结构。如果你无法清除这种相互依赖,也无法将其局部化,那么你很可能进入了一个困境,没有一种程序设计语言能够帮你跳出来。除非你能设想出在基本概念之间的某种很容易陈述的关系,否则那个程序多半会变得无法管理。

  解开依赖图的一种最好的工具就是界面和实现的清晰分离。抽象类是C++处理这种问题的基本工具。

  共性的另一种形式可可以通过模版表示。一个类模版刻画了一族类。例如,一个表模板刻画了“T的表”,其中T可以是任何类型。这样,模板就是这样一种机制,它刻画的是如何通过给定另一个类作为参考,就可以生成出一个新的类来。最常见的模板是容器类,例如表、数组和关联数组,以及使用这些容器的基本算法。通过使用继承的类型表达一个类及其相关函数的参数化通常是个错误,这件事最好是用模板做。

  应该记住,许多程序设计工作能够仅用基本类型、数据结构、普通函数和若干库类完成,这样做既简单又清晰。涉及到定义新类型的全套装备应尽量不用,除了在那些确实需要它们的地方以外。

  问题“一个人怎样才能在C++里写出好的程序?”与问题“一个人怎样才能写出好的英语散文”类似。存在着两个回答:“了解你想说的是什么”以及“实践,模仿好的作品”。两者都适用于C++,就像它们适用于英语一样---去实践这一想法也同样不容易。

🔚