电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

对象的创建和存在时间


发布日期:2019/2/12
 

从技术角度说OOP(面向对象程序设计)只是涉及抽象的数据类型继承以及多形性但另一些问题也可能显得非常重要本节将就这些问题进行探讨

最重要的问题之一是对象的创建及破坏方式对象需要的数据位于哪儿如何控制对象的存在时间呢?针对这个问题解决的方案是各异其趣的C++认为程序的执行效率是最重要的一个问题所以它允许程序员作出选择为获得最快的运行速度存储以及存在时间可在编写程序时决定只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可这样便为存储空间的分配和释放提供了一个优先级某些情况下这种优先级的控制是非常有价值的然而我们同时也牺牲了灵活性因为在编写程序时必须知道对象的准确的数量存在时间以及类型如果要解决的是一个较常规的问题如计算机辅助设计仓储管理或者空中交通控制这一方法就显得太局限了

第二个方法是在一个内存池中动态创建对象该内存池亦叫或者内存堆若采用这种方式除非进入运行期否则根本不知道到底需要多少个对象也不知道它们的存在时间有多长以及准确的类型是什么这些参数都在程序正式运行时才决定的若需一个新对象只需在需要它的时候在内存堆里简单地创建它即可由于存储空间的管理是运行期间动态进行的所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令将堆栈指针向下或向下移动即可)由于动态创建方法使对象本来就倾向于复杂所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响除此以外更大的灵活性对于常规编程问题的解决是至关重要的

C++允许我们决定是在写程序时创建对象还是在运行期间创建这种控制方法更加灵活大家或许认为既然它如此灵活那么无论如何都应在内存堆里创建对象而不是在堆栈中创建但还要考虑另外一个问题亦即对象的存在时间或者生存时间(Lifetime)若在堆栈或者静态存储空间里创建一个对象编译器会判断对象的持续时间有多长到时会自动破坏或者清除程序员可用两种方法来破坏一个对象用程序化的方式决定何时破坏对象或者利用由运行环境提供的一种垃圾收集器特性自动寻找那些不再使用的对象并将其清除当然垃圾收集器显得方便得多但要求所有应用程序都必须容忍垃圾收集器的存在并能默许随垃圾收集带来的额外开销但这并不符合C++语言的设计宗旨所以未能包括到C++里但Java确实提供了一个垃圾收集器(Smalltalk也有这样的设计尽管Delphi默认为没有垃圾收集器但可选择安装而C++亦可使用一些由其他公司开发的垃圾收集产品)

本节剩下的部分将讨论操纵对象时要考虑的另一些因素

集合与继承器

针对一个特定问题的解决如果事先不知道需要多少个对象或者它们的持续时间有多长那么也不知道如何保存那些对象既然如此怎样才能知道那些对象要求多少空间呢?事先上根本无法提前知道除非进入运行期

在面向对象的设计中大多数问题的解决办法似乎都有些轻率——只是简单地创建另一种类型的对象用于解决特定问题的新型对象容纳了指向其他对象的句柄当然也可以用数组来做同样的事情那是大多数语言都具有的一种功能但不能只看到这一点这种新对象通常叫作集合(亦叫作一个容器但AWT在不同的场合应用了这个术语所以本书将一直沿用集合的称呼在需要的时候集合会自动扩充自己以便适应我们在其中置入的任何东西所以我们事先不必知道要在一个集合里容下多少东西只需创建一个集合以后的工作让它自己负责好了

幸运的是设计优良的OOP语言都配套提供了一系列集合在C++中它们是以标准模板库(STL)的形式提供的Object Pascal用自己的可视组件库(VCL)提供集合Smalltalk提供了一套非常完整的集合而Java也用自己的标准库提供了集合在某些库中一个常规集合便可满足人们的大多数要求而在另一些库中(特别是C++的库)则面向不同的需求提供了不同类型的集合例如可以用一个矢量统一对所有元素的访问方式一个链接列表则用于保证所有元素的插入统一所以我们能根据自己的需要选择适当的类型其中包括集队列散列表堆栈等等

所有集合都提供了相应的读写功能将某样东西置入集合时采用的方式是十分明显的有一个叫作(Push)添加(Add)或其他类似名字的函数用于做这件事情但将数据从集合中取出的时候方式却并不总是那么明显如果是一个数组形式的实体比如一个矢量(Vector)那么也许能用索引运算符或函数但在许多情况下这样做往往会无功而返此外单选定函数的功能是非常有限的如果想对集合中的一系列元素进行操纵或比较而不是仅仅面向一个这时又该怎么办呢?

办法就是使用一个继续器(Iterator)它属于一种对象负责选择集合内的元素并把它们提供给继承器的用户作为一个类它也提供了一级抽象利用这一级抽象可将集合细节与用于访问那个集合的代码隔离开通过继承器的作用集合被抽象成一个简单的序列继承器允许我们遍历那个序列同时毋需关心基础结构是什么——换言之不管它是一个矢量一个链接列表一个堆栈还是其他什么东西这样一来我们就可以灵活地改变基础数据不会对程序里的代码造成干扰Java最开始(在版中)提供的是一个标准继承器名为Enumeration(枚举)为它的所有集合类提供服务Java 新增一个更复杂的集合库其中包含了一个名为Iterator的继承器可以做比老式的Enumeration更多的事情

从设计角度出发我们需要的是一个全功能的序列通过对它的操纵应该能解决自己的问题如果一种类型的序列即可满足我们的所有要求那么完全没有必要再换用不同的类型有两方面的原因促使我们需要对集合作出选择首先集合提供了不同的接口类型以及外部行为堆栈的接口与行为与队列的不同而队列的接口与行为又与一个集(Set)或列表的不同利用这个特征我们解决问题时便有更大的灵活性

其次不同的集合在进行特定操作时往往有不同的效率最好的例子便是矢量(Vector)和列表(List)的区别它们都属于简单的序列拥有完全一致的接口和外部行为但在执行一些特定的任务时需要的开销却是完全不同的对矢量内的元素进行的随机访问(存取)是一种常时操作无论我们选择的选择是什么需要的时间量都是相同的但在一个链接列表中若想到处移动并随机挑选一个元素就需付出惨重的代价而且假设某个元素位于列表较远的地方找到它所需的时间也会长许多但在另一方面如果想在序列中部插入一个元素用列表就比用矢量划算得多这些以及其他操作都有不同的执行效率具体取决于序列的基础结构是什么在设计阶段我们可以先从一个列表开始最后调整性能的时候再根据情况把它换成矢量由于抽象是通过继承器进行的所以能在两者方便地切换对代码的影响则显得微不足道

最后记住集合只是一个用来放置对象的储藏所如果那个储藏所能满足我们的所有需要就完全没必要关心它具体是如何实现的(这是大多数类型对象的一个基本概念)如果在一个编程环境中工作它由于其他因素(比如在Windows下运行或者由垃圾收集器带来了开销)产生了内在的开销那么矢量和链接列表之间在系统开销上的差异就或许不是一个大问题我们可能只需要一种类型的序列甚至可以想象有一个完美的集合抽象它能根据自己的使用方式自动改变基层的实现方式

单根结构

在面向对象的程序设计中由于C++的引入而显得尤为突出的一个问题是所有类最终是否都应从单独一个基础类继承在Java中(与其他几乎所有OOP语言一样)对这个问题的答案都是肯定的而且这个终级基础类的名字很简单就是一个Object这种单根结构具有许多方面的优点

单根结构中的所有对象都有一个通用接口所以它们最终都属于相同的类型另一种方案(就象C++那样)是我们不能保证所有东西都属于相同的基本类型从向后兼容的角度看这一方案可与C模型更好地配合而且可以认为它的限制更少一些但假期我们想进行纯粹的面向对象编程那么必须构建自己的结构以期获得与内建到其他OOP语言里的同样的便利需添加我们要用到的各种新类库还要使用另一些不兼容的接口理所当然地这也需要付出额外的精力使新接口与自己的设计方案配合(可能还需要多重继承)为得到C++额外的灵活性付出这样的代价值得吗?当然如果真的需要——如果早已是C专家如果对C有难捨的情结——那么就真的很值得但假如你是一名新手首次接触这类设计象Java那样的替换方案也许会更省事一些

单根结构中的所有对象(比如所有Java对象)都可以保证拥有一些特定的功能在自己的系统中我们知道对每个对象都能进行一些基本操作一个单根结构加上所有对象都在内存堆中创建可以极大简化参数的传递(这在C++里是一个复杂的概念)

利用单根结构我们可以更方便地实现一个垃圾收集器与此有关的必要支持可安装于基础类中而垃圾收集器可将适当的消息发给系统内的任何对象如果没有这种单根结构而且系统通过一个句柄来操纵对象那么实现垃圾收集器的途径会有很大的不同而且会面临许多障碍

由于运行期的类型信息肯定存在于所有对象中所以永远不会遇到判断不出一个对象的类型的情况这对系统级的操作来说显得特别重要比如违例控制而且也能在程序设计时获得更大的灵活性

但大家也可能产生疑问既然你把好处说得这么天花乱坠为什么C++没有采用单根结构呢?事实上这是早

上一篇:Lucene 挖掘相关搜索词

下一篇:如何在代码中自动实现设计规范(组图)