寒假基本上都在旅游,所以书上的知识看不全面,很多都是教科书上存在的,我只是强调了下。下面是我的泛读摘录:

 

对象的演化

1.   类描述了一组有相同特性(数据元素)和相同行为(函数)的对象。类实际上就是数据类型。(对象:特性+行为

2.   对象设计的五个阶段:

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

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

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

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

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

3.   对象开发原则:

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

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

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

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

5)   尽量保持简单。具有明显用途的不太清楚的对象比很复杂的接口好。我们总能够从小的和简单的类开始,当我们对它有了较好地理解时再扩展这个类接口,但不可能简化已存在的类接口。

4.   Booch方法是最早、最基本和最广泛被引用的一个方法。因为它是较早发展的,所以对各种程序设计问题都有意义。它的焦点是在O O P的类、方法和继承的单独性能上,步骤如下:

1)   在抽象的特定层上确定类和对象

这是可预见的一小步。我们用自然语言声明问题和解,并且确定关键特性,例如形成类的基本名词。如果我们在烟花行业,可能想确定工人、鞭炮、顾客,进而,可能需要确定化学药剂师、组装者、处理者,业余鞭炮爱好者和专业鞭炮者、购买者和观众。甚至更专门的,能确定年轻观众、老年观众、少年观众和父母观众。

2)   确定它们的语义

这是在相应的抽象层上定义类。如果我们计划创建一个类,我们应当确定类的相应观众。例如,如果创建鞭炮类,那么谁将观察它,化学药剂师还是观众?前者想知道在结构中有什么化学药剂,而后者将对鞭炮爆炸时释放的颜色和形状感兴趣。如果化学药剂师问起一个鞭炮产生主颜色的化学药剂,最好不要回答“有点冷绿和红”。同样,观众会对鞭炮点火后喷出的只是化学方程式感到迷惑不解。也许,我们的程序是为了眼前的市场,化学药剂师和观众都会用它,在这种情况下,鞭炮应当有主题属性和客体属性,并且能以和观察者相应的外观出现。

3)   确定它们之间的关系(CRC 卡片)

定义一个类如何与其他类互相作用。将关于每个类的信息制成表格的常见的方法是使用类,责任,协作( Class, Responsibility, Collaboration, CRC)卡片。这是一种小卡片(通常是一个索引卡),在它上面写上这个类的状态变量、它的责任(也就是它发送和接受的消息)和对与它互相作用的其他类的引用。为什么需要索引卡片?理由是我们应当能在一张小卡片上存放我们需要知道的关于一个类的所有内容,如果不能满足这点,那么这个类就太复杂了。理想的类应当能在扫视间被理解,索引卡片不仅实际可行,而且刚好符合大多数人思考问题合理的信息量。一种不涉及主要技术改革的解决办法对于每个人都可用的(就像本章前面描述的草稿方法中的文档结构化一样)。

4)   实现类

现在我们知道做什么了,投入精力进行编码。在大多数项目中, 编码将影响设计。

5)   反复设计

这样的设计过程给人一种类似著名的程序开发瀑布流方法的感觉。现在对这种方法的看法是有分歧的。在第一遍查看主要的抽象是否可以将类清晰地分离之后,可能需要重复前三个步骤。Booch写了一个“粗糙旅程完型设计过程”。如果这些类真正反映了这个解的自然语言描述,那么程序有完型的观点应当是可能的。也许要记住的最重要的事情是,通过缺省—实际上是通过定义,如果修改了一个类,它的超类和子类应当仍然工作。不需要害怕修改,它不会破坏程序,对结果的任何改变将限制在子类和/或被改变的这个类的特定协作者中。为了这个类而对C R C卡片的扫视也许是我们需要检验新版本的唯一线索。

 

数据抽象

1.   “声明”向计算机介绍名字,它说,“这个名字是什么意思”;

“定义”为这个名字分配存储空间;

无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。

2.   定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A,则编译器马上为这个名字分配存储地址。

3.   声明常常使用于extern关键字。如果我们只是声明变量而不是定义它,则要求使用extern。对于函数声明, extern是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。

4.   “::”: 范围分解运算符;”.”:成员运算符。

隐藏实现

1.   public意味着在其后声明的所有成员对所有的人都可以存取。

private关键字则意味着,除了该类型的创建者和类的内部成员函数之外,任何人都不能存取这些成员。

protectedprivate基本相似,只有一点不同:继承的结构可以访问protected成员,但不能访问private成员。

2.   如果程序员想允许不属于当前结构的一个成员函数存取结构中的数据,那该怎么办呢?他可以在struct内部声明这个函数为友元。注意,一个友元必须在一个struct内声明,因为他(和编译器)必须能读取这个结构的声明以理解这个数据类型的大小、行为等方面的规则。有一条规则在任何关系中都很重要,那就是“谁可以访问我的私有实现部分”。

3.   嵌套友元:struct holder{

struct pointer{

};

friend holder::pointer;

         };

这样可以通过pointer访问holder里的数据。

初始化与清除

1.   用构造函数确保初始化:initialize()函数。

2.   构造函数和析构函数是两个非常特殊的函数:它们没有返回值。

3.   用析构函数确保清除

函数重载与缺省参数

1.   缺省参数就是在用户调用一个函数时没有指定参数值而由编译器插入参数值的参数。

2.   函数重载的本质就是允许函数同名 

常量

1.   const可以用于集合,但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const意味着“不能改变的一块存储”。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。

2.   指向const的指针(x是一个指针,它指向一个const int

const int* xint const* x

const指针(x是一个指针,这个指针是指向intconst指针)

int* const x=&d

内联函数

1.   内联的目的是减少函数调用的开销。

命名控制

1.   C + +中,static都有两种基本的含义:

1)   在固定的地址上分配,也就是说对象是在一个特殊的静态数据区上创建的,而不是每次函数调用时在堆栈上产生的。这也是静态存储的概念。

2)   对一个特定的编译单位来说是本地的(就像我们在后面将要看到的,这在C + +中包括类的范围)。这里s t a t i c控制名字的可见性,所以这个名字在这个单元或类之外是不可见的。这也描述了连接的概念,它决定连接器将看到哪些名字。

引用和拷贝构造函数

1.   引用( & )像一个自动能被编译器逆向引用的常量型指针。它通常用于函数的参数表中和函数的返回值,但也可以独立使用。

2.   使用引用时有一定的规则:

1)   当引用被创建时,它必须被初始化。(指针则可以在任何时候被初始化。)

2)   一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。(指针则可以在任何时候指向另一个对象。)

3)   不可能有NULL引用。必须确保引用是和一块合法的存储单元关连。

3.   拷贝构造函数,它常被称为X ( X & )(“X引用的X”)。在函数调用时,这个构造函数是控制通过传值方式传递和返回用户定义类型的根本所在。

4.   指针是指向一些内存地址的变量,既可以是数据的地址也可以是函数的地址。

运算符重载

1.   运算符重载时,函数参数表中参数的个数取决于两个因素:

1)   运算符是一元的(一个参数)还是二元的(两个参数)。

2)   运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数— 对象变为左侧参数)。

动态对象创建

1.   当一个C + +对象被创建时,有两件事会发生:

1)   为对象分配内存。此步骤可以以几种方式或在可选择的时间内发生:

        i.      静态存储区域,存储空间在程序开始之前就可以分配。这个存储空间在程序的整个运行期间都存在。

       ii.      无论何时到达一个特殊的执行点(左花括号)时,存储单元都可以在栈上被创建。出了执行点(右花括号),这个存储单元自动被释放。这些栈分配运算内置在处理器的指令集中,非常有效。然而,在写程序的时候,必须知道需要多少个存储单元,以使编译器生成正确的指令。

     iii.      存储单元也可以从一块称为堆(也可称为自由存储单元)的地方分配。这称为动态内存分配,在运行时调用程序分配这些内存。这意味着可以在任何时候分配内存和决定需要多少内存。我们也负责决定何时释放内存。这块内存的生存期由我们选择决定—而不受范围限制。

2)   调用构造函数来初始化那个内存

2.   当用newnew的表达式)创建一个对象时,它就在堆里为对象分配内存并为这块内存调用构造函数。

3.   new表达式返回一个指向对象的指针一样,delete表达式需要一个对象的地址。

4.   foo* fp = new foo[100] ;

delete []fp ;

foo* fp2 = new foo ;

delete fp2 ;

5.   int* const q = new int[10];

q里的数组元素可以被修改,但对q本身的修改(例如q + +)是不合法的,因为它是一个普通数组标识符

6.   通过包含new.h,然后以我们想装入的函数地址为参数调用set_new_handler( )函数,这样就替换了new- hander(内存不够大会被调用)。

//:操作如下:

 

#include<iostream.h>

#include<stdlib.h>

#include<new.h>

 

void out_of_memory(){

    cerr<<”memory exhausted!”<<endl;

    exit(1);

}

main(){

    set_new_hander(out_of_memory);

    while(1)

       new int[1000];

}

继承和组合

1.   C++最重要的性能之一是代码重用。

2.   向上映射不会增加成员函数,总是安全的。

多态和虚函数

1.   多态性提高了代码的组织性和可读性,同时也可使得程序具有可生长性。

2.   捆绑:把函数体与函数调用相联系。当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑。

3.   virtual关键字可以改变程序的效率。

4.   多态性在C++中用虚函数实现。

模版和包容器

1.   包容器类常用于创建面向对象程序的构造模块(building block),它使得程序内部代码更容易构造。

2.   一个包容器类可描述为容纳其他对象的对象。

3.   包容器类是解决不同类型的代码重用问题的另一种方法。继承和组合为对象代码的重用提供一种方法,而C + +的模板特性为源代码的重用提供一种方法。

4.   模板的语法:template<class T>

//用法如下:

 

#include<iostream.h>

#include<assert.h>

 

template<class T>

class array{

    enum{size=100};

    T A[size];

public:

    T& operator[](int index){

       assert(index>=0 && index<size);

       return A[index];

}

};

main(){

    array<int> ia;

    array<float> fa;

    for(int i=0;i<20;i++){

       ia[i]=i*I;

       fa[i]=float(i)*1.414;

    }

    for(int j=0;j<20;j++)

       cout<<j<<”i”<<ia[j]

            <<”,”<<fa[j]<<endl;

}

5.   下面是存在于C + +标准模板库STLstandard template library)中的包容器子集:

1)   袋子:项目的(可能有重复)集合。它的元素没有特定的次序要求,STL不包含它,这是因为其功能可由表和向量来实现。

2)   集合:不允许有重复元素的袋子就是集合。在STL中,集合是以一种联合包容器(associative container)来描述的,联合包容器可以根据关键字向包容器提供和从包容器中取回数据元素。在集合中数据元素的存取检索关键字就是元素本身。一个STL的多重集合允许将同一关键值的许多副本放入不同的对象中。

3)   向量:可索引的项目序列。由于它有一致的访问时间,所以可作为缺省选择。

4)   队列:从尾部追加元素,从头部删除元素的项目序列。它是双端队列的子集,有时不实现它们,但我们可以用双端队列来代替。

5)   双端队列:具有两个端部的队列。序列中项目元素的追加和删除可从任意一端进行。在大部分的插入和删除运算发生在序列头部或尾部时,可用其代替列表以提高效率。在头部或尾部做插入和删除运算时,双端队列的时间复杂度为常量级,但在序列中部实施运算时呈线性时间复杂度。

6)   栈:在相同一端实施追加和删除的项目序列。尽管在本书中被用作一个例子,其实它是双端队列功能的一个子集。

7)   环形队列:环形结构的项目序列,元素的追加和删除位于环形结构的顶端,它是头部和尾部关联的队列。通常让其支持系统底层活动,非常有效而且其时间复杂度为常量级。例如在一个通讯中断服务例程中,插入一些字符而随后将其删除,将不用担心存储的耗尽及必须的时间分配。但是,如不加以细致地编程,环形队列会超出正常的控制范围。S T L不包含环形队列。

8)   列表:允许以相等的时间在任意点实施插入和删除的有根的项目序列。它不提供快速随机访问,但能在序列中间进行快速插入和删除运算。列表一般以单链表和双链表的形式来实现。单链表的遍历是单方向的,而双链表可在任意节点上向前向后移动。由于单链表仅包含一个指向下一个节点的指针而双链表则包含前趋和后继两个指针,所以单链表较双链表的存储开销低。但是在插入和删除工作效能上,双链表则优于单链表。

9)   字典:关键字和相应值的映射,在STL中被称为“映像”(它是联合包容器的另一种形式)。关键字和相应值的映射对有时被称为“联系”。字典值的存取和关键字是相关联的。STL也提供多重映射,允许多重映射相同关键字到相应值的多个副本上,这和哈希表相当。

10)树:存在一个根节点的节点和弧(节点间的连接)的集合。树不包含环(没有封闭的路径)和交叉路径。S T L中不提供树,树具有的特性功能由S T L的其他类型来提供。

11)二叉树:一个普通树从每个节点上射出的弧的数目是没有限制的,而二叉树从每个节点上最多只射出两条弧,即“左”“右”两条弧。由于节点的插入位置随其值而定,当搜寻所期望值时则不必浏览许多节点(而线性表则不同),所以二叉树是最快的信息检索方法之一。平衡二叉树在每次插入节点时,它重新组合以保证树每一部分的深度都不超过基准值。然而每次插入时的平衡处理的开销则较高。

12)图:无根节点的节点和弧的集合。图可以包含环和交叉路径。它通常更具有理论上的意义并不常用于实际实施。它不是STL的一部分。

异常处理

1.   错误修复技术的改进是提高代码健壮性的最有效方法之一。

2.   如果在函数内抛出一个异常(或在函数调用时抛出一个异常),将在异常抛出时退出函数。如果不想在异常抛出时退出函数,可在函数内创建一个特殊块用于解决实际程序中的问题(和潜在产生的差错)。由于可通过它测试各种函数的调用,所以被称为测试块。测试块为普通作用域,由关键字try引导:

try {

// code that may generate exceptions

}

3.   异常抛出信号发出后,一旦被异常器处理接收到就被销毁。异常处理器应具备接受任何一种类型的异常的能力。异常处理器紧随try块之后,处理的方法由关键字catch引导:

try {

// code that may generate exceptions

}catch(type1 id1){

    //handle exceptions of type1

} catch(type1 id2){

    //handle exceptions of type2

}

4.   函数说明可以带有异常说明如下:

void f ( ) throw ( toobig, toosmall, divzero)

1)   unexpected( )如果函数实际抛出的异常类型与我们的异常规格说明不一致,将会产生什么样的结果呢?这时会调用特殊函数unexpected( )

2)   set_unexpected( )unexpected( )是使用指向函数的指针而实现的,所以我们可通过改变指针的指向地址来改变相对应的运算。这些可通过类似于set_new_handler( )的函数et_unexpected( ) 来实现,set_unexpected( ) 函数可获取不带输入和输出参数的函数地址和void返回值。它还返回unexpected指针的先前值,这样我们可存储unexpected( )函数的原先指针值,并在后面恢复它。为了使用set_unexpected( )函数,我们必须包含头文件except.h。下面给出一实例展示本章所讨论的各个特点的简单使用:

//:例子如下:

#include<except.h>

#include<iostream.h>

#include<stdlib.h>

#include<string.h>

 

class up{};

class fit{};

void g();

 

void f(int i)throw(up,fit){

    switch(i){

       case1:throw up();

       case2:throw fit();

    }

    g();

}

 

void g(){throw 47;}

 

void my_unecpected(){

    cout<<”unexpected exception thrown”;

    exit(1);

}

 

main(){

    set_unexpected(my_unexpected);

    for(int i=1;i<=3;i++)

       try{

           f(i);

       }catch(up){

           cout<<”up caught”<<endl;

       }catch(fit){

           cout<<”fit caught”<<endl;

       }

}

运行时类别识别

1.   运行时类型识别(Run-time type identification, RTTI)是在我们只有一个指向基类的指针或引用时确定一个对象的准确类型。

2.   typeid()带有一个参数,它可以是一个对象引用或指针,返回全局typeinfo类的常量对象的一个引用。用法如下:

1)   cout << typeid(*s).name()<<endl;s是个shape*

将显示出s所指向的对象类型。

2)   if(typeid(me).before(typeid(you))) //...

表示我们正在查询m e在排列顺序中是否在you之前。