• (一)C++对象导言


    1.1 抽象的过程

      所有的程序设计语言都是抽象。人们能解决问题的复杂性直接与抽象的类型和质量有关。一些程序设计者认为,面向对象程序设计本身并不足以轻易解决所有程序设计问题,他们提倡结合各种方法,形成多范型的程序设计语言。

      Alan Kay总结了Smalltalk的五个基本特性,这些特性代表了纯面向对象程序设计的方法。

      (1)万物皆对象。对象可以被认为是一个奇特的变量,它能存放数据,而且可以对它“提出请求”,要求它执行自身的运算。我们可以在需要解决的问题中提出任意概念性的成分(狗,建筑物,服务等),把它标示为程序中的对象。

      (2)程序就是一组对象,对象之间通过发送消息互相通知做什么。更具体的讲,可以将消息看做是对于调用某个特定对象所属行数的请求。

      (3)每一个对象都有它自己的由其他对象构成的存储区。这样,就可以通过包含已经存在的对象创建新的对象。

      (4)每个对象都有一个类型。采用OOP术语,每个对象都是某个类的实例。类最重要的突出特征是“能向他发送什么消息”。

      (5)一个特定类型的所有多谢都能接收相同的消息。

    1.2 对象有一个接口

      可以向对象发出的请求是由它的接口定义的,而接口由类型确定。接口规定我们能向特定的对象发出什么请求。然而,必须有代码满足这种请求,再加上隐藏的数据,就组成了实现。类型对每个可能的请求都有一个相关的函数,当向对象发请求时,就调用这个函数。这个过程通常概括为向对象“发送消息”,对象根据这个消息确定做什么。

    1.3 实现的隐藏

      如果类的所有成员对于任何人都能用,那么客户程序员就可以用这个类做其中的任何事情,不存在强制规则,虽然我们不希望客户直接操作这个类的某些成员,但是应为没有访问控制。所以没有办法保护它,所有东西都暴露无遗。

      访问控制的第一个理由是防止客户程序员查收他们不应该接触的部分,也就是对于数据类型内部实施方案的必须部分,而不是用户为了解决他们的特定问题所需要的接口部分。

      访问控制的第二个理由是允许库设计者去改变这个类的工作方式,而不必担心这样做会影响到客户程序员。

      C++提供了3个关键字来设置类的边界:public,private,protected。

    1.4 实现的重用

      代码重用是面向对象程序语言的最大优点之一

      重用一个类的最简单的方法就是直接使用这个类的对象,并且还可以将这个类放到一个新类里面。我们称之为“创建一个成员对象”。可以用任意数量和类型的其他对象组成新类,通过组合得到心累所希望的功能。称为组合。

      组合带来很大的灵活性,新类的成员对象通常是私有的,使用这个类的客户程序员不能访问他们。这种特点允许我们改变这些成员而不会干扰以存在的客户代码。

      当创建新类时,程序员首先考虑组合,因为他更简单灵活。

    1.5 重用接口

      对象的思想本身就是一种很方便的工具。它允许我们将数据和功能通过概念封装在一起。使得我们能够秒速合适的问题思想空间,而不是被强制使用底层机器语言。使用class关键字,这些概念被表示为程序设计语言中的基本单元。

      然而,克服许多困难去创建一个类,并随后强制性的创造一个有类似功能的全新的类,似乎不是一种好的办法。通过继承,我们可以在原有类的基础上添加和修改,产生新的类。当我们从已存在的类来继承时,我们就创造了一个新类型。这个新类型不仅包含那个已经存在的类型的所有成员,更重要的是,他复制了这个基类的接口。也就是说,能够发送给基类的消息,也能够发送给派生类。

      有两种方法是的派生类区别与基类,第一种是简单的向派生类添加全新的函数。第二重就是改变已存在基类函数的行为,称为重载。要覆盖函数,可以直接在派生类中创建新定义。

    1.6 具有多态性的可互换对象

      当处理类层次结构时,程序员常常希望不把对象看做是某一特殊类型的成员,而想把它看做是基本类型成员。这样就允许程序员编写不依赖与特殊类型的程序代码。这样程序代码不受添加新类型的影响,而且增添新类型是扩展面向对象程序来处理新情况最普遍的方法。但是,这里有一个问题:如果一个函数告诉一个一般的形体去描绘他自己,或者告诉一个一般的车辆去行驶,或者告诉一直一般的鸟去飞翔,则编译器在编译时就不确定应当执行哪段代码。同样的问题,消息发送时,程序员并不想知道将执行哪一段代码。在面向对象程序设计中,编译器并不做传统意义上的函数调用。在OOP中,直到程序运行时,编译器才确定代码的地址。这称为“晚捆绑”。当给对象发送消息时,在程序运行时才去确定被调用的代码。编译器保证这个被调用的函数的存在,并执行参数和返回值类型的检查,但它并不知道将执行的确切代码。

      为了实现晚捆绑,C++编译器在真正调用的地方插入一段特殊代码。通过使用存放在对象自身中的信息。这段代码在运行时计算被调用函数函数体的地址。这样每个对象就能根据这段二进制代码的内容由不同的行为,当一个对象接收到消息时,它根据这个消息判断应当做什么。

      我们可以用关键字virtual声明希望某个函数有晚捆绑的灵活性。

    1.7 创建和销毁对象

      从技术角度,OOP的论域就是抽象数据类型、继承和多态性。还有一点重要的是对象的创建和销毁的方法。对象的数据存放在何处?如何控制对象的生命期?不同的程序设计语言有不同的行事之道。C++采取的方法是吧效率控制作为最重要的问题。通过将对象放在栈中或静态存储区中,存储和生命期可以在编写程序时确定。

      第二种方法称为堆的其余动态创建对象。用这种方法,可以知道运行时还不知道需要多少个对象,他的生命期是什么,他们的准确的数据类型是什么。这些决定实在程序运行之中做出的。如果需要新的对象,直接实用new关键字让他在堆上生成。当结束时,实用delete释放。因为这种存储是在运行时动态管理的,所以在堆上分配存储所需要的时间比在栈上创建存储需要的时间长的多。

      另一个问题是对象的生命周期,如果在栈上或在静态存储上创建一个对象,编译器决定这个对象持续多长时间并能自动销毁它。而在堆上不行,程序员必须编程决定何时销毁。作为一个替换,运行环境可以提供一个垃圾收集器的功能,当一个对象不再使用此功能可以自动发现并销毁这个对象。使用垃圾收集器编写程序是非常方便的,但是它需要所有应用软件能够忍垃圾收集器的存在以及垃圾收集器的系统开销。

    1.8 异常处理:应对错误

      处理错误是程序设计最难的问题之一。设计一个好的错误处理方案非常困难。

      C++异常处理将错误处理直接与程序设计语言甚至有时是操作系统联系起来。异常是一个对象,它在出错的地方抛出,并且被一段用于处理特定类型错误的、相应的异常处理代码所捕获。异常处理视乎是另一个并行的执行路径,在出错的时候被调用。由于它使用一个单独的执行路径,并不需要干涉正常的执行代码。因为不需要经常检查错误,代码会很简洁。一个抛出的异常不同于函数返回值的错误值或者为了指出错误条件而由函数设置的标记,后两者可以被忽略。而异常不能忽略,必须保证他在某点上进行处理。最后,异常提供了一个从错误状态中进行可靠恢复的方法,除了从这个程序中退出以外,我们常常可以做出正确的设置,恢复程序执行,这有助于产生更健壮的系统。

    1.9 分析与设计

      面向对象是一种新的、不同的编程思考方式,许多人一开始在学习如何处理一个OOP项目时都会感到非常困难。但是了解到任何事物都被认为是对象,并且学会用面向对象的风格去进一步思考后,我们就可以开始利用OOP所提供的优点创造出好的设计。

      方法,是一系列的过程和探索,用以降低程序设计的复杂性。尤其在OOP领域中,方法论是一个充满实验的领域,因此在我们考虑用一个方法之前,理解它师徒要解决什么问题是重要的。设计和编写一个程序时,我们所做的一切就是一个方法。它可能是我们创造时经理的一个过程。如果它是一个有效的过程,只需要略加调整以和C++配合。如果我们队自己的效率和程序生产方式不满意,就可以考虑采纳一个正式的方法或者在许多正式的方法中选择部分。

      经历开发过程时,最重要的问题是:不要迷路。大部分分析和设计方法都是为了解决最大的一些问题。大多数项目不适合这一点,因此我们通常可以用一个相对小的子集成功地进行分析和设计,但是采用某种过程,不论有什么局限,总比一上来就编程要好。

      开发过程中很容易受阻,陷入“分析瘫痪状态”,这种状态中往往由于没有弄清楚当前阶段所有小细节而感到不能继续了。不论做了多少分析,总有系统的一些问题知道设计时才暴露出来,并且更多的问题是到编程或直到程序完成运行时才出现。因此,迅速进行分析和设计并对提出的系统执行测试时相当重要的。

      也就是说,如果我们正在考虑是是一个包含丰富细节而且需要许多步骤和文档的方法学,将很难判断什么时候停止。应当牢记我们正在努力寻找的是什么。

      (1)有哪些对象?

      (2)他们的接口是什么?

      只要我们知道了对象和接口,就可以编写程序了。

      整个过程可以分为5个阶段完成,阶段0只是使用一些结构的初始约定。

    1.9.1 第0阶段:指定计划

      我们必须首先决定在此过程中应当有哪些步骤。在这个阶段,我们可能还要决定一些另外的过程结构,但不是全部。无论建造什么系统,不管如何复杂,都有其基本的目的,有其要处理的业务。有其所满足的基本需求。通过以此审视用户界面,硬件和系统的特殊细节、算法编码和效率问题,我们将最终找出它的核心。

    1.9.2 第1阶段:我们在做什么

      在过程型程序设计语言中,这一阶段称为“建立需求分析”和系统规范说明。需求分析说的是“制定一系列的指导方针,我们将利用它了解任务什么时候完成且用户什么时候满足”。系统规范说明指出“这是程序将做什么以满足需求的一个描述”。

      这一阶段中我们有必要把注意力放在核心问题上:确定这个系统要做什么。为此,最有价值的工具是一组所谓的“用例”。用例指明了系统中的关键特性,他们将展现我们使用的一些基本的类。他们实际上是对类以下述这些问题的描述性回答。

      1)谁将使用这个系统。

      2)执行者用这个系统做什么?

      3)执行者如何用这个系统工作?

      4)如果其他人也做这件事,或者同一个人执行者有不同的目标,怎么办。

      5)当使用这个系统时,会发生什么问题?(揭示异常)。

      我们可以准备许多测试用例,通过确定用户在系统中的所有交互行为,用例就生成了需求规范说明。我们试图找到系统的完整用例,完成之后,我们就得到了系统任务的核心内容。注意力集中在用例上的好处是他们总是能将我们带回到要点部分而不至于留心那些对完成任务无关紧要的问题。

      用例和执行者之间的便捷能指出用户界面的存在,但不能定义这样的用户界面。

    1.9.3 第2阶段:我们将如何建立对象

      在这一阶段,我们必须做出设计,描述这些类和他们如何交互。确定类和交互的出色技术是类职责协同卡片。只要有一组3到5英寸的卡片,在上面写。每张卡片描述一个类,在卡片上写的内容是

      (1)类的名字

      (2)类的职责:它应当做什么。通常,他可以仅由成员函数的名字陈述,但并不产生其他的注记。如果需要开始这个过程,请从一个懒程序员的立场看这个问题:你希望什么样的对象出现。

      (3)类的协同:它与其他类有哪些交互?“交互”是非常宽泛的术语。可以是一些已经存在的其他对象对这个类对象提供的服务。

      我们可能想让卡大一些,从中得到全部信息,当时他们是非常小的,这不仅能保持我们的类小,而且能够防止过早的陷入过多的细节。如果一张小卡片上放不下类所需的全部信息,那么这个类就太复杂了

      CRC卡片的最大好吃之一是在交流中。在一个组中,最好实时进行交流。每个人负责及各类。每次只解决一个情节,决定发送什么消息给不同的对象以满足每个情节,这样就能做出一个比较形象的对问题的模拟。当我们经理了这个过程后,就会找出我们所需要的类以及他们的职责和协同。

    1.9.3.1 对象设计的5个阶段

      (1)对象发现。这个阶段出现在陈旭的最初分析期间。对象可以通过寻找外部因素以及边界、系统中重复的元素和最小概念单元而发现。

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

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

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

      (5)对象重用。这是对类真正的强度测试。如果某些人试图在全新的情况下重用他,他们也许会发现一些缺点。当我们修改一个类以适应更新的程序时,类一般的原则将变得更清楚,知道我们有了一个正在可重用的对象。大多数对象都是针对特定系统的,可重用类一般共性比较少,为了重用,他们必须解决更一般的问题。

    1.9.3.2 对象设计的一般准则

      1)让特定的问题生成一个类,然后在解决其他问题期间让这个类生成和成熟。

      2)记住,发现所需的类(和他们的接口),是设计系统的主要内容。

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

      4)开始编程,让一部分能够运行,这样就可以证明或否定意见生成的设计。不要害怕过程性大杂烩式的代码——类的隔离性能控制他们。坏的类不会破坏好的类。

      5)尽量保持简单、基友明显用途的不太清楚的对象比复杂的接口好。选择简单的类,应为简单的类总是好一些。从小的和简单的类开始,当我们对它有较好的理解时再扩展这个接口,但是很难从一个类中删去元素。

    1.9.3 第3阶段:创建核心

      在这一阶段,我们的目标是寻找实现系统体系结构的核心,尽管这个系统在第一遍不太完整。我们正在创建一个框架,在将来的反复中可以完善它。我们正在完成第一遍多系统集成和测试,向客户提出反馈意见,关于他们的系统看上去如何,以及如何发展等。

      建立这个系统的一部分工作是实际检查,就是对照需求分析和系统规范说明与测试结果。确保我们的测试结果与需求和用例符合。当系统核心稳定后,我们就可以向下进行和增加更多的功能了。

    1.9.4 第4阶段:迭代用例

      一旦代码框架运行起来,我们增加的每一组特征本身就是一个小项目。在一次迭代期间,我们增加一组特征,一次迭代是一个相当短的开发时期。

      一次迭代有多长时间?理想情况下,每次迭代为一到三个星期。在这个期间的最后,我们得到一个集成的、测试过的、比前一周有更多功能的系统。特别有趣的是迭代的基础:一个用例。每个用例都是一组相关的功能,在一次迭代中加入系统。这不仅为我们更好的提供了“用例应当处于什么范围内”的概念,而且还对概念进行了巩固,在分析和设计之后这个概念并未丢弃,他是整个软件建造过程中开发的基本单元。

      迭代开发过程是有价值的。我们可以更早的揭露和解决严重的问题,客户有足够的机会改变他们的意见,程序员会更满意,更精确的掌握项目。另一个重要的好处是对风险承担者意见的反馈,他们能从羡慕当前状态看到各方面的因素。

    1.9.5 第5阶段:进化

      这是开发周期中,称为“维护”的一个阶段,包含了从“让软件正真按最初提出的方式运行”到“添加用户忘记说明的性能”,到更传统的“排除暴露的错误”和“在出现新的需求时添加性能”。

      此处使用术语“进化”。就是说,“我们不可能一次性就使软件正确”,应当不断的学习、返工和修改留有余地。当我们队问题有深入的学习和领会后,可能需要做大量的修改。如果我们使软件不断进化,直到软件正确、无论在短期内还是长期内、将产生结尾优雅的程序。进化使程序从好到优秀,是使一遍不理解的问题变得清楚的过程。

      “使软件正确”的意思不只是使程序按照要求和用例工作。还意味着我们理解代码的内部结构,并且认识到它能很好的协同工作,没有拙笨的语法和过大的对象,也没有难看的暴露的代码。

  • 相关阅读:
    经典排序之 计数排序
    经典算法 总结
    经典排序之 二路归并排序
    经典排序之 堆排序
    经典排序之 插入排序
    经典排序之 冒泡排序
    经典排序之 选择排序
    经典排序之 快速排序
    两个队列实现一个栈
    Java Web系列:JDBC 基础
  • 原文地址:https://www.cnblogs.com/a-lai/p/8344084.html
Copyright © 2020-2023  润新知