您的位置:首页 > 编程语言 > C语言/C++

《C++编程思想》 第一章 对象的演化

2015-05-30 02:31 441 查看
1 基本概念

1.1 对象:特性+行为

第一个面向对象的程序设计语言是 6 0年代开发的S i m u l a - 6 7。其目的是为了解决模拟问题。典型的模拟问题是银行出纳业务,包括出纳部门、顾客、业务、货币的单位等大量的“对象” 。把那些在程序执行期间除了状态之外其他方面都一样的对象归在一起,构成对象的“类” ,这就是“类”一词的来源。

类描述了一组有相同特性(数据元素)和相同行为(函数)的对象。类实际上就是数据类型,例如,浮点数也有一组特性和行为。区别在于程序员定义类是为了与具体问题相适应,而不是被迫使用已存在的数据类型。这些已存在的数据类型的设计动机仅仅是为了描述机器的存储单元。程序员可以通过增添他所需要的新数据类型来扩展这个程序设计语言。该程序设计系统欢迎创建、关注新的类,对它们进行与内部类型一样的类型检查。

抽象数据类型的创建是面向对象程序设计中的一个基本概念。抽象数据类型几乎能像内部类型一样准确工作。程序员可以创建类型的变量(在面向对象程序设计中称为“对象”或“实例” )并操纵这些变量(称为发送“消息”或“请求” ,对象根据发来的消息知道需要做什么事情)。

1.2 继承:类型关系

类型不仅仅说明一组对象上的约束,还说明与其他类型之间的关系。两个类型可以有共同的特性和行为,但是,一个类型可能包括比另一个类型更多的特性,也可以处理更多的消息(或对消息进行不同的处理) 。继承表示了基本类型和派生类型之间的相似性。一个基本类型具有所有由它派生出来的类型所共有的特性和行为。程序员创建一个基本类型以描述系统中一些对象的思想核心。由这个基本类型派生出其他类型,表达了认识该核心的不同途径。

例如,垃圾再生机要对垃圾进行分类。这里基本类型是“垃圾” , 每件垃圾有重量、价值等等,并且可以被破碎、融化或分解。这样,可以派生出更特殊的垃圾类型,它们可以有另外的特性(瓶子有颜色)或行为(铝可以被压碎,钢可以被磁化) 。另外,有些行为可以不同(纸的价值取决于它的种类和状态) 。程序员可以用继承建立类的层次结构,在该层次结构中用类型术语来表述他需要解决的问题。

第二个例子是经典的形体问题,可以用于计算机辅助设计系统或游戏模拟中。这里基本类型是“形体” ,每个形体有大小、颜色、位置等。每个形体能被绘制、擦除、移动、着色等。由此,可以派生出特殊类型的形体:圆、正方形、三角形等,它们中的每一个都有另外的特性和行为,例如,某些形体可以翻转。有些行为可以不同(计算形体的面积) 。类型层次结构既体现了形体间的类似,又体现了它们之间的区别。

用与问题相同的术语描述问题的解是非常有益的,这样,从问题描述到解的描述之间就不需要很多中间模型(程序语言解决大型问题,就需要中间模型) 。面向对象之前的语言,描述问题的解不可避免地要用计算机术语。使用对象术语,类型层次结构是主要模型,所以可以从现实世界中的系统描述直接进入代码中的系统描述。实际上,使用面向对象设计,人们的困难之一是从开始到结束过于简单。一个已经习惯于寻找复杂解的、训练有素的头脑,往往会被问题的简单性难住。

1.3 多态性

当处理类型层次结构时,程序员常常希望不把对象看作是某一特殊类型的成员,而把它看作基本类型成员,这样就可以编写不依赖于特殊类型的代码。在形体例子中,函数可以对一般形体进行操作,而不关心它们是圆、正方形还是三角形。所有的形体都能被绘制、擦除和移动,所以这些函数能简单地发送消息给一个形体对象,而不考虑这个对象如何处理这个消息。

这样,新添类型不影响原来的代码,这是扩展面向对象程序以处理新情况的最普通的方法。例如,可以派生出形体的一个新的子类,称为五边形,而不必修改那些处理一般形体的函数。通过派生新子类,很容易扩展程序,这个能力很重要,因为它极大地减少了软件维护的花费。(所谓“软件危机”正是由软件的实际花费远远超出人们的想象而产生的。 )

如果试图把派生类型的对象看作它们的基本类型(圆看作形体,自行车看作车辆,鸬鹚看作鸟) ,就有一个问题:如果一个函数告诉一个一般形体去绘制它自己,或者告诉一个一般的车辆去行驶,或者告诉一只一般的鸟去飞,则编译器在编译时就不能确切地知道应当执行哪段代码。同样的问题是,消息发送时,程序员并不想知道将执行哪段代码。绘图函数能等同地应用于圆、正方形或三角形,对象根据它的特殊类型来执行合适的代码。如果增加一个新的子类,不用修改函数调用,就可以执行不同的代码。编译器不能确切地知道执行哪段代码,那么它应该怎么办呢?

在面向对象的程序设计中,答案是巧妙的。编译器并不做传统意义上的函数调用。由非O O P编译器产生的函数调用会引起与被调用代码的“早捆绑” ,对于这一术语,读者可能还没有听说过,因为从来没有想到过它。早捆绑意味着编译器对特定的函数名产生调用,而连接器确定调用执行代码的绝对地址。对于 O O P,在程序运行之前,编译器不确定执行代码的地址,所以,当消息发送给一般对象时,需要采用其他的方案。

为了解决这一问题,面向对象语言采用“晚捆绑”的思想。当给对象发送消息时,在程序运行之前不去确定被调用的代码。编译器保证这个被调用的函数存在,并完成参数和返回值的类型检查,但是它不知道将执行的准确代码。为了实现晚捆绑,编译器在真正调用的地方插入一段特殊的二进制代码。通过使用存放在对象自身中的信息,这段代码在运行时计算被调用函数的地址。这样,每个对象就能根据一个指针的内容有不同的行为。当一个对象接收到消息时,它根据这个消息判断应当做什么。

程序员可以用关键字 v i r t u a l表明他希望某个函数有晚捆绑的灵活性,而并不需要懂得v i r t u a l的使用机制。没有它,就不能用 C + +做面向对象的程序设计。 Vi r t u a l函数(虚函数)表示允许在相同家族中的类有不同的行为。这些不同是引起多态行为的原因。关于虚函数表的实现机制,另开专题讨论。

2 为什么C++会成功

C++之所以使用如此之广泛,主要体现在一下几个方面:

1)C + +是对C的扩充,而不是新的文法和新的程序设计模型。程序员学习和理解这些性能,逐渐应用并继续创建有用的代码。

2)C++编译在C编译器的基础上做了很多的优化工作,极大地提供了程序的执行效率。

3)C++完善了对于异常的处理,使得程序执行更加的安全。

4)C++标准模板库提供了很多封装好的类,程序员只需要拿来使用即可,实现了代码的有效复用。

5)C++在进行大型程序设计时,更加方面代码的调试和维护。

3 对象设计

对象的设计不限于写程序的时期,它出现在一系列阶段。有这种观点很有好处,因为我们不再期望设计立刻尽善尽美,而是认识到,对对象做什么和它应当像什么的理解是随着时间的推移而产生的。这个观点也适用于不同类型程序的设计。特殊类型程序的模式是通过一次又一次地求解问题而形成的 。同样,对象有自己的模式,通过理解、使用和重用而形成。

下面是描述,不是方法。它简直就是对象期望的设计出现时的观察结果。

1) 对象发现 这个阶段出现在程序的最初分析期间。可以通过寻找外部因素与界线、系统中的元素副本和最小概念单元而发现对象。如果已经有了一组类库,某些对象是很明显的。类之间的共同性(暗示了基类和继承类) ,可以立刻出现或在设计过程的后期出现。

2) 对象装配 我们在建立对象时会发现需要一些新成员,这些新成员在对象发现时期未出现过。对象的这种内部需要可能要用新类去支持它。

3) 系统构造 对对象更多要求可能出现在以后阶段。随着不断的学习,我们会改进我们的对象。与系统中其它对象通讯和互相连接的需要,可能改变已有的类或要求新类。

4) 系统扩充 当我们向系统增添新的性能时,可能发现我们先前的设计不容易支持系统扩充。这时,我们可以重新构造部分系统,并很可能要增加新类。

5) 对象重用 这是对类的真正的重点测试。如果某些人试图在全新的情况下重用它,他们会发现一些缺点。当我们修改一个类以适应更新的程序时,类的一般原则将变得更清楚,直到我们有了一个真正可重用的对象。

对象开发原则

在这些阶段中,提出考虑开发类时所需要的一些原则:

1) 让特殊问题生成一个类,然后在解其他问题时让这个类生长和成熟。

2) 记住,发现所需要的类,是设计系统的主要内容。如果已经有了那些类,这个项目就不困难了。

3) 不要强迫自己在一开始就知道每一件事情,应当不断地学习。

4) 开始编程,让一部分能够运行,这样就可以证明或反驳已生成的设计。不要害怕过程语言风格的细面条式的代码 — 类分割可以控制它们。坏的类不会破坏好的类。

5) 尽量保持简单。具有明显用途的不太清楚的对象比很复杂的接口好。我们总能够从小的和简单的类开始,当我们对它有了较好地理解时再扩展这个类接口,但不可能简化已存在的类接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: