《解析C++面向对象程序设计》清华大学出版-读书笔记
阅读数:283 评论数:0
跳转到新版页面分类
C/C++
正文
第1章 认识对象
1.面向对象分析(object-oriented analysis,OOA)的主要任务是分析问题域中的对象、对象之间的关系,然后构造出该问题域的分析模型。分析模型必须简洁、明确地抽象出目标系统必须要做哪些工作(what to do),而不是决定如何去做。
2.面向对象设计(object-oriented design,OOD)的主要任务是将分析模型转化为适合计算机处理的设计模型。目标是:明确地抽象出目标系统如何去完成任务(how to do),设计模型要适合计算机数据结构表示和程序处理。
3.面向对象编程(object-oriented programming,OOP)是指将系统的设计模型用具体的面向对角程序设计语言、数据库实现。
面向对象的UML描述
在UML中,类用一个矩形表示,顶部区域写类的名称,中间区域列出类的属性,底部区域列出类的操作。有时,可以只保留顶端区域,用一个写了类名的矩形表不一个类,其他两个区域省略。
类属性的定义格式:
visibility name:type-expression [multiplicity ordering] = initial-value {property-string}
可见性 名称 :类型 [多重性 次序] = 初值 {约束特性}
(1)可见性:用“+” “-” “#”分别表示public、private、protected3种可见性
(2)名称:属性的名称
(3)类型:表示属性所属的类型
(4)多重性和次序:多重性表示属性的数量,次序表示属性的数量在一个以上时的排列顺序。当多重性省略时,属性的默认值为1.
(5)初始值:当属性建立时所赋予的初始值
(6)约束特性:表示对属性性质的约束说明。
上面表示的属性格式中除了名称外其他均是可选项。
类操作的定义格式:
visibility name (parameter-list): return-type-expression {property-string}
可见性 名称 (参数列表):返回类型 {约束特性}
重点说明一下参数列表:参数由逗号分开,每个参数有下面的格式
kind name:type-expression = default-value
方向 名称:类型 = 默认值
方向包括in、out、inout,默认是in
第2章 发现对象和设计类
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、函数值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,并且用实参换形参,于是省去了函数调用的开销。
全局变量和静态变量在定义(分配空间)时,将位模式清零,局部变量在定义时,分配的内存空间内容保持原样。
当类中显式定义带参数的构造函数时,系统不提供默认构造函数。
当一个函数名在一个特殊的域中被声明多次时,编译器按以下步骤解释第二个的声明。
(1)如果两个函数的参数表中参数的个数或类型不同,则认为这两个函数是重载的
(2)如果两个函数的返回类型和参数表精确匹配,则第二个函数声明被视为第一个函数的重复声明。
(3)如果两个函数的参数表相同,但是函数值类型不同,则第二个声明被视为第一个函数的重复声明,会被标记为编译错误。
(4)如果两个函数的参数表中,只有默认实参不同,则第二个函数被视为第一个函数的重复定义。
(5)当一个参数类型是const时,在识别函数声明是否相同时并不考虑const修饰符。但是,如果把const应用在指针或引用参数指向的类型上,则在判断函数声明是否相同时,就要考虑const修饰符
重载函数集合中的全部函数都应在同一个域中声明,一个声明为局部的函数将隐藏一个全局中声明的函数,而不是重载一个全局中声明的函数。
析构函数的自动调用
(1)如果在一个函数中定义了一个对象(它自动局部对象),当这个函数被调用结束时,对角应该被释放,在对象释放前自动执行析构函数
(2)static局部对角在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数
(3)如果定义了一个全局对象,则程序的流程离开其作用域时(如main函数结束或调用eixt函数)时,调用该全局对象的析构函数
(4)如果使用new运算符动态建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
构造函数的调用
(1)先调用虚基类的构造函数,再调用非虚基类的构造函数
(2)基类构造函数中,如果有多个 基类,则构造函数的调用顺序是基类在类派生表中的出现顺序,而不是他们在成员初始化列表中出现的顺序
(3)若虚基类由非虚基类派生而来,则仍按先调用基类构造函数,再调用派生类的构造函数
(4)如果有多个成员类对象,则构造函数的调用顺序是对角在类中被声明的顺序,而不是它们在初始化列表中的顺序。
静态数据成员
在类的声明中仅仅是对静态数据成员进行声明,不能在类中进行初始化,因为类声明只声明一个类的“尺寸和规格”,并不进行实际实际的内存分配。与一般数据成员初始化不同,其格式如下:
<类型><类名>::<静态数据成员>=<值>;
(1)在类外必须使用成员访问运算符和类名限定符两种之一访问公共静态数据成员。
(2)静态数据成员的类型可以是其所属类,而非静态数据成员只能被声明为该类的对象的指针或引用。class A
{
public:
static A mem1;//right
A* mem2;//right
A mem3;//wrong!
}
(3)静态数据成员可以被作为类成员函数的默认实参,而非静态数据成员不能。
如果要通过非静态成员函数来访问静态数据成员,应该使用非内联函数,而且访问静态数据成员的函数,其函数体的定义应该与静态数据成员的初始化定在一个源程序文件中。这是因为静态数据成员在被访问之前必须被初始化,而编译器只能保证在调用同一源文件中定义的任何非静态成员函数之前,初始化类的静态数据成员。
表态成员函数
静态成员函数的声明除了在类体中的函数声明前加上关键字static以及不能声明为const之外,与非静态函数相同。出现在类体外的函数定义不能指定关键字static。如果静态成员函数要使用非静态成员时,必须通过参数传递方式得到对象名,然后通过对象名来访问。
第3章 详解对象
程序的存储空间可以分为3部分:
(1)代码存储区,存放源代码。
(2)静态存储区,存放程序的全局数据和静态数据(包括全局数据和静态数据)
(3)动态存储区,包括栈区(函数形参 ,函数中的局部变量,现场保护和返回地址)及堆区,堆由低地址向高地址增长,栈由高地址向低地址增加,二者相对增长。
指针的值只能赋给相同类型的指针,但是有一种类型的指针,可以存储任何类型的对象地址,就是说任何类型的指针都可以赋值给void类型的指针。强制类型转换后,通过void类型的指针便可以访问任何类型的数据。
this指针是一个const指针,成员函数不能对其进行赋值。
一个普通函数的函数名就表示它的起始地址,将起始地址赋给指针,就可以通过指针调用函数。类的成员函数虽然并不在每个对象中复制一个副本,但是语法规定必须要通过对象来调用非静态成员函数。
引用
(1)一旦引用被声明,它就不能再指向其他的对象,这也是它为什么要被初始化的原因。
(2)非常量引用不能直接和文字常量发生绑定。
const &ref1 = 100;//常量引用绑定的对象应是常量。
第9章 异常处理
异常抛出:
throw<表达式>;
异常的检测和捕获由try-catch结构实现:
try
{
<被检测可能会发生异常的的语句>
}
catch(<异常的类型 >)
{
<异常处理语句>
}
(1)如果异常声明 是一个省略号,即写成catch(...),则表示catch子句可以捕获任何类型的异常。
(2)throw表达式的类型决定异常的类型
(3)重新抛出只能出现在catch子句花括号中,并且throw后面没有表达式。如
try{...}
catch(类型)
{throw;}
(4)抛出的异常找不到与之匹配的catch子句时,由系统函数terminate()通知用户,异常没有处理,系统终止程序运行。
异常接口声明语法形式:
<函数值类型><函数名>(<函数参数表>)throw(<异常类型表>)
它是函数使用者与函数之间的协议。
exception
+logic_error(逻辑错误)
-length_error(产生一个长度值超过最大允许值的对象)
-domain_error(域错误)
-out_of_range(数组下标之类的值在不期望的范围内)
-invalid::argument(函数接收到了无效的实参)
+run_time_error(运行进错误)
-range_error(内部运算中的范围错误)
-overflow_error(运算上溢错误)
-underflow_error(运算下溢错误)
-bad_alloc(new运算符不能申请所需的存储空间)
-bad_exception(其他方法不能进行完全解析的异常)
-bad_cast(使用操作符dynamic_cast进行动态类型转换失败)
-bad_typeid(指用typeid运算符识别类型时出错)
-ios_base::failure()