Mindon.IDEA

Air off, Mind on ~ / Javascript+Golang, Sci, Health… /

Think in C++读书笔记

BlogMS original blog key: 1000141850, blog id: airoff History stat: 浏览/评论:550/0 , 日期:2005年1月7日 09:03

2003年05月 Thinking in C++

[>> 问题描述空间:解描述空间 <<]

[>> 对象发现,对象装配,系统构造,系统扩充,对象重用 <<]

-剧本:

故事的结构通过特征表示,特征对应于对象,情节结构对应于系统设计。

头文件表达了类设计,而我们必须能够由设计文档重新产生头文件。

[>> Booch方法 <<]

焦点是在OOP的类、方法和继承的单独性能上。

1、在抽象的特定层上确定类和对象(用自然语言声明问题和解,并且确定关键特性)

2、确定它们的语义(在相应抽象层上定义类)

3、确定它们之间的关系(CRC卡片:Class 类,Responsibility 责任,Collaboration 协作)

4、实现类

5、反复设计

[>> 责任驱动设计RDD <<]

重点在责任的授权

1、数据或状态

2、池和源

3、观察者或观点

4、辅助工具或帮助器

[>> 对象建模技术OMT <<]

用详细地绘制图表的方法,不仅描述类,而且还描述系统的各种状态

1、对象模型:WHAT - 对象图表

2、动态模型:WHEN - 状态图表

3、功能模型:HOW - 数据流程表

[>> 数据抽象 <<]

声明(非定义必须extern,函数声明extern可选)

定义(初始化:分配存储空间,可以同时是声明)

范围分解运算符::

类型在C++是严格的。

允许从问题空间把概念抽象到解空间的数据类型。

在结构内部放入函数生成的新类型被称为抽象数据类型,

用这种结构创建的变量被称为这个类型的对象或者实例,

调用对象的成员函数被称为“向对象发消息”。

头文件是存放接口规范的唯一地方,是库开发者与其用户之间的合同,该合同描述数据结构,并说明函数调用的参数和返回值。

其本规则是“只声明”,不要在头文件中做在连接时会引起混淆的任何事情。

用预处理器隔离声明避免重复。

private, protected, public

friend 声明友元,如果一个函数被声明为friend,就意味着它不是这个类的成员函数,但却可以修改类的私有成员,而且它必须被列在类的定义中(特权函数)。

存取控制通常是指实现细节的隐藏。存取控制的真正价值体现在开发阶段,防止越界。

所有存取保护检查都是由编译器来完成的,在运行期间不再检查。(连接器无保护控制信息)

句柄类(handle classes)即"Cheshire Cat"技术隐藏实现,避免重复编译。

封装和实现的隐藏大大地改善了库的使用;安全性包括初始化和清除两方面。

构造函数确保初始化(无返回值,可带参数),

析构函数确保清除(无返回值,不带参数)。

编译器自动调用,但析构函数调用的唯一根据是包含该对象的右括号。

一般说来,应该在尽可能靠近变量的使用点定义变量,并在定义时就初始化。

集合初始化

int b[6]={0]; //6个元素均为0

int c[]={1,2,3,4}; //元素数=sizeof©/sizeof(*c)

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

编译器会通过分解名字、范围和参数来产生内部名以供连接器使用。

缺省参数之后的参数都必须是缺省的。

可以让声明的参数没有标识符,目的在于可以修改函数定义而不需要修改所有的函数调用。

不能把缺省参数作为一个标志去决定执行函数的某一块,这是基本原则。

缺省参数的引用是为了使函数调用更容易,其一个重要应用是在开始定义函数时用了一组参数,一段时间后要增加参数,只要把新增参数作为缺省参数就不会影响原有调用。

输入输出流更容易、安全、有效。

istream,ostream

文件 ifstreams, ofstreams

char*内存(内核格式化)istrstreams, ostrstreams

串istringstreams, ostringstreams

<< 插入符

>> 提取符

操作算子

endl(插入新行并清空流)

flush(只清空流)

oct, dec, hex (转换)

ws (跳过空格)

格式化输入以空格为分隔符

good(), eof(), fail(), bad()

streambuf, streampos

const 的最初动机是取代预处理器#define进行值替代。

C++编译器通常并不为const分配存储空间,相反它把这个定义保存在它的符号表里。

const在C++中默认为内部连接(C默认是外部连接)。

const意味着“不能改变的一块存储”,但其值在编译时不能被使用。

const int* x; // 指针所指值不变

int const* x; // 指针不变

编译器不允许使用存储在const指针里的地址来建立一个非const指针。

函数不能返回指向局部栈变量的指针。

在C++中,传递一个参数时,先选择通过引用传递,而企鹅是通过常量(const)的引用。

类内的const常量需要在构造函数初始化表达式表中初始化,而不能在声明的时候初始化。

任何不修改成员数据的函数应该声明为const函数。

使用关键字mutable指定一个特定的数据成员可以在一个const对象里被改变。

编译器和连接器都要检查const。

volatie语法与const一样,意思是“在编译器认识的范围外,这个数据可以被改变”。

c-v限定词,const volatile不能给程序改变,但可通过外面的工具改变。

任何在类中定义的函数自动地成为内联函数(函数类型和函数体均放在符号表中)。

类外内联函数inline函数体和声明必须结合在一起,否则当作普通函数。

在头文件中,内联函数默认为内部连接-即它是static,并且只能在它被包含的编译单元看到。

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

存取器(accessors)和修改器(mutators)

两种不能内联的情况:

1、假如函数太复杂(如任何种类的循环),编译器将不能执行内联。

2、假如要显式或隐含地区函数地址,编译器也不能执行内联。

// C++宏定义主要应用

define DEBUG(X) cout << #X " = " << X << endl;

define TRACE(S) cout << #S << endl, S

define FIELD(A) char* A##string; int A##size

static 基本的含义是指“位置不变的某个东西”,保存在静态数据区。

在固定地址上分配,对一个特定的编译单位来说是本地。

全局静态对象的构造函数是在main()之前调用的,而其析构函数则在退出main()之后执行。

一般情况下,在文件范围内的所有名字(既不嵌套在类或函数中的名字)对程序中的所有编译单元来说都是可见。这就是所谓的外部连接

在文件范围内,一个被明确声明为static的对象或函数的名字对编译单元来说是局部变量;这些名字有内部连接

连接只引用那些在连接/装载期间有地址的成员。

所有的全局对象都是隐含为静态存储类,但通过extern和static可改变其可见性。

(auto局部变量,register局部变量/寄存器)

1、namespace只能在全局范畴定义,但它们之间可以嵌套。

2、在namespace定义的结尾,右大括号的后面不必要跟一个分号。

3、一个namespace可以在多个头文件中用一个标识符来定义,这跟类重复定义一样。

4、一个namespace的名字可以用另一个名字来做别名。

5、不能像类那样川建一个名字空间的实例。

namespace_name::成员

using namespace namespace_name

成员

静态数据成员定义必须出现在类的外部(不允许内联),而且只能定义一次。

初始化静态数组不能用自动计数。

局部类中不能有静态数据成员。

静态成员函数只能访问静态成员,且没有this。

用static关键字指定了一个类的所有对象占有相同的一块存储空间。

连接转化指定

extern "C" {

// c code

}

extern "C++" {

// cpp code

}

拷贝构造函数(copy-constructor)X(X&)需要用引用(&)来实现从现有的相同类型对象产生新的对象。

引用(&)象一个自动能被编译器逆向引用的常量性指针,通常用于函数的参数表和函数返回值。

任何引用必须和存储单元联系

当引用被用作函数参数时,函数内任何对引用的更改将对汉书外的参数改变。

在C和C++中,参数是从右向左进栈,然后调用函数,调用代码负责清除栈中的参数。

通过传值方式传递参数时,编译器简单地将参数拷贝压栈;引用则仅需要将地址压栈。

函数调用过程中被函数使用的内存为函数框架(function frame)。

拷贝构造函数实现传值方式的参数传递和返回。缺省行为:位拷贝(bitcopy)。

仅当准备用传值的方式传递类对象时,才需要拷贝构造函数。

防止通过传值方式传递:声明一个私有拷贝构造函数。

成员指针和普通指针一样具有相同的功能:可以在运行时选取特定存储单元(数据或函数)。

成员指针只和类成员一起工作而不和全局数据或者函数一起工作。

运算符重载operator

“返回临时对象”方法可以提高返回效率。

=自赋值(self-assignment)检查

关键字explicit(只能用于构造函数)阻止构造函数转换

静态内存,堆栈,堆

delete只用于删除由new创建的对象。

[>> 继承和组合 <<]

is-a 继承: (需要向上映射选用)

has-a

成员对象构造函数调用的次序由对象在类中的声明次序决定。

如果在基类中有一个函数名被重载几次,在派生类中重新定义这个函数名会掩盖所有基类版本。

构造函数和析构函数不能被继承。

operator=也不能被继承。

[>> 多态和虚函数 <<]

虚函数反映了一个类型与另一个类似类型之间的区别,只要这两个类型都是从同一个基类派生的。

虚函数加强类型概念,而类型是面向对象编程设计的核心。

取一个对象的地址(或指针或引用),并看作基类的地址。这被称为向上映射

向上映射是自动发生的,不许强制,是绝对安全的。

向下映射则是不安全的。

捆绑(binding):函数体与函数调用相联系。

早捆绑:捆绑在程序运行之前(由编译器和连接器)完成。

晚捆绑:捆绑在程序运行之时发生(动态捆绑)。

C++中晚捆绑只对virtual起作用。

如果一个函数在基类中被声明为virtual,那么在所有的派生类中都是virtual的。

在派生类中virtual函数的重定义通常称为越位。

编译器对每个包含虚函数的类创建一个表(称为VTABLE)。

在 VTABLE 中,编译器放置特定类的虚函数地址。在每个带有虚函数的类中,编译器秘密地置指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。

通过基类指针做虚函数调用时(也就是多态调用),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,并完成晚捆绑的函数调用。

纯虚函数 virtual void x() = 0;

等于告诉编译器在VTABLE中为函数保留一个间隔,但在这个特定间隔中不放地址。只要有一个函数在类中被声明为纯虚函数,则VTABLE就是不完全的。

包含有纯虚函数的类称为纯抽象类

纯虚函数防止对纯抽象类的函数以传值方式调用,也是防止对象意外使用值向上映射的一种方法。这样能保证在向上映射期间总是使用指针或引用。

纯虚函数防止产生VTABLE,防止对象切片。

在基类中,对纯虚函数提供定义是可能的。但纯虚函数在派生类中必须定义,以便创建对象。

对派生类中没有重定义的虚函数使用基类函数的地址。

使用多态的目的是让对基类对象操作的代码也能操作派生类对象。

虚函数的构造函数尽量不要使用内联。

在构造/析构函数中,只有函数的本地版本被调用(虚机制被忽略)。

构造函数不是虚的,但析构函数能够且常常必须是虚的。

创建纯虚析构函数,必须提供函数体。

任何时候在类中有虚函数,就应当直接增加虚析构函数。

[>> 模板和包容器类 <<]

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

包容器类的真正的需求是在堆上使用new创建对象和使用delete析构对象的时候体现的。

模板实现了参数化类型的概念。

模板(template)”这一关键字会告诉编译器下面的类定义将操作一个或更多的非特定类型。

当对象被定义时,这些类型必须被指定以使编译器能够替代它们。

由template<…>处理的任何东西都将意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。

注意在引用魔板名称的地方,必须伴有该模板的参数列表。

模板参数并不局限于有类定义的类型,可以使用编译器内置类型。这些参数值在模板特定实例时变成编译期间敞亮。甚至可以对这些参数使用缺省值。

运行时类型识别 Run-time type identification RTTI

typeid()

name()

尽可能地使用虚函数,必要时才使用RTTI。

典型的RTTI是通过在VTABLE中方一个额外的指针来实现的。这个指针指向一个描述该特定类型的typeinfo结构。

dynamic_cast<目标*><源指针>

从本质上说,RTTI只要两个函数就行了,一个用来指明类的准确类型的虚函数,一个取得基类的指针并将它乡下应设成排参类。

Comments