C++Primer

第2章

2.1 基本内置类型

1、表示整数、字符和布尔值的算术类型合称为**整型**
2、0值算术类型代表false,任何非0的值都代表true
3、在一行末尾加“\”,可将此行和下一行当做同一行处理

2.3 变量

1、变量命名不能以数字开头
2、变量的定义用于为变量分配储存空间,还可以为变量指定初始值。变量仅且有且一个定义
3、声明用于向程序表明变量的类型和名字。,通过extern关键字声明变量而不是定义它。变量可以声明多次但是只能定义一次

4、左直:左值可以出现在赋值语句的左边或右边。右值:右值只能出现在赋值的右边,不能出现在赋值语句的左边。

2.4 const限定符

1、const把一个对象转换成常量,常量在定义后就不能被修改。例如:const int bufSize=512; bufSize=0;//尝试修改它,系统会报错。
2、在全局作用域声明的const变量是定义对该对象的文件的局部变量。此变量只存在定义的文件中,其他文件想访问,需要在定义时声明为extern。非const变量默认为extern。 

2.5 引用

1、引用就是对象的另一个名字。
2、非const引用必须与引用同类型的对象初始化。
3、当引用初始化后,只要该引用存在,它就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。
4、const引用则可以绑定到不同但相关的类型的对象或绑定到右值。例如(1) int dval=3.14;const int &ri=dval;(2)const int &r2=0。

2.6 typedef

名字主要目的:
* 隐藏特定类型的实现,强调使用类型的目的
* 简化复杂的类型定义,使其更易理解
* 允许一种类型用于多个目的,同时使得每次使用该类型的目的明确

2.7 枚举

1、定义和初始化枚举
关键字enum,其后是一个可选的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员列表。默认的,第一个枚举成员为0,后面的每个枚举成员赋的值比前面大1。
2、枚举成员是常量
不能改变枚举成员的值
3、每个enum都定义一种唯一类型
枚举类型的对象的初始化或复制,只能通过枚举成员或同一枚举类型的其他对象来进行。

2.8 类类型

1、calss关键字定义类,默认为private成员;strunc关键字定义类,默认为public成员

2.9编写自己的头文件

1、避免头文件被反复定义,导致变异报错
`#ifnedf SALESITME_H
define SALESITME_H
//头文件内容
#endif`

第3章 标准库类型

3.2 标准库string类型

1、getline接受俩个参数:一个输入流对象和一个string对象。遇到换行符,读取结束,并且不会将换行符储存在string对象中
2、string s;cin>>s;遇到空格,输入会结束
3、string对象常用操作:https://www.cnblogs.com/w-x-me/p/6790569.html
4、string.size()用string::size_type接收

5、string比较规则(1)如果俩个string对象长度不同,且短的string对象与长的string对象的前面部分匹配,则短的string对象小于长的对象。(2)如果string对象字符不同,则比价第一个不匹配的字符。

6、使用string “+”进行连接时,“+”操作符的左右操作数必须至少有一个是string类型

7、标准库不要求检查索引值,所用索引的下标越界是没有定义的,这样往往会导致严重的运行时错误。

常用API参考网址:https://www.cnblogs.com/w-x-me/p/6790569.html

3.3 标准库vector类型

1、vector不是一种数据类型,而是一个类模板,可以用来定义多种数据类型

2、vector对象的重要属性在于可以在运行时高效地添加元素。

3、如果没有指定元素的初始化,那么标准库将自行提供一个元素初始值进行初始化。

3、必须是已存在的元素才能用下标操作符进行索引。

常用API参考网址:https://www.cnblogs.com/w-x-me/p/6790565.html

3.4 迭代器

1、迭代器是一种检查容器内元素并遍历元素的数据类型,实现了对于容器中对象的间接访问。

2、每个标准容器类型都定义了一个名为iterator的成员,例如:vector<T>::iterator iter=ivec.begin();

3、每种容器都定义一对命名为begin和end的函数,用于返回迭代器。end操作返回迭代器指向vector的“末端元素的下一个”。通常称为超出末端迭代器,表明它指向一个不存在的元素。end函数只是起一个哨兵的作用,表示我们已处理完vector中所有元素。

4、迭代器可以使用解引用操作符(*操作符)来访问迭代器所指向的元素,*iter和ivec[0]就是指向同一个元素。迭代器的自加是指向下一个元素。

5、const-iterator指向的迭代器可以改变,但是迭代器指向的值不能改变(++cit合法,*cit=9非法),常用来读取容器中的数据。注意区分与const vector<int>::iterator cit=vec.begin()的区别。*cit=9合法,++cit非法

6、迭代器可以加或减一个整数,也可以俩个迭代器互减。

3.5 标准库bitset类型

1、定义biset时,要明确bitset含有多少位,需在尖括号内给出它的长度值,例如:bitset<32> bitvec;

2、string对象和bitset对象之间是反向初始化:string对象的最右边字符(及下标最大的那个字符)用来初始化bitset对象的低阶位(及下标为0的位)

第4章 数组和指针

1、数组一经创建,就不允许添加新的元素

4.1 数组

1、数组是由类型名、标识符和维数组成的复合数据类型

2、数组的维数必须用大于等于1的常量表达式定义。

3、显示初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度

4、数组如果内置类型则初始化为0,如果是类类型则调用该类的默认构造函数进行初始化

5、使用字符串初始字符数组时,注意字符串末尾的空字符。

7、不允许数组直接复制和赋值

4.2 指针的引入

1、指针用于指向对象,指针保存的是另一个对象的地址

2、&符号是取地址操作符,取地址操作符只能用于左值

3、避免使用未初始化的指针,所以一版将指针定义为NULL

4、对指针进行初始化或赋值只能使用以下四种类型的值(1)0值常量表达式;(2)类型匹配的对象的地址(3)另一个对象末的下一个地址(4)同类型的另一个有效指针

int ival;
int zero=0;
const int c_ival=0;
int *pi=ival; //error
pi= zero; //error
pi=c_ival;
pi=0;

5、void *指针可以保存任何类型对象的地址,不允许void*指针操作它所指向的对象

6、对指针解引用可访问它所指的对象,*操作符(解引用操作符)将获取指针所指的对象

7、对左值进行解引用,则修改的是指针所指向对象的值;如果没有解引用操作,则修改的是指针本身的值
8、指针和引用的比较(1)引用总是指向某个对象:定义引用时没有初始化是错误的(2)赋值行为差异:给引用赋值修改的是引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。
9、指向指针的指针:指针的储存地址可存放在指针中。

10、可以使用指针指向数组,默认指向数组中的第一个元素,可以通过指针指向数组中的其他元素

11、在指针上加上一个整型数值,其结果任然是指针

12、只要指针指向数组元素,就可以对它进行下标操作

int ia[]={1,2,3,4,5};
int *p=&ia[2];
int j=p[1]  //j=*(p+1)-->a[3]
int k=p[-2] //k=*(p-1)-->a[0]

13、指向const对象的指针,称为常量指针:(1)const对象的地址只能赋给const对象的指针(2)非const对象的地址可以赋给指向const对象的指针(3)系统会把它所指的所有对象都视为const

int value=2;
int val=3;
int const *p=&value;//指针所指向对象的值不能修改//*p=val;是非法
p=&val;//正常

14、const指针,称为指针常量:(1)指针常量必须被初始化(2)指针常量指向的对象不能被修改

int value=2;
int val=3;
 int* const q= &value; //指针所指向对象不能修改
 *q=val;是正常
 //p=&val;//非法

 

4.3 C风格字符串

1、字符串字面值的类型就是const char类型的数组。

2、C风格字符串是以空字符null结束的字符数组。

3、操作C风格字符串的标准库函数

strlen(s) // 返回s的长度,不包括字符串结束符null
strcmp(s1,s2) //比较字符串s1和s2是否相同。若相等,则返回0.若遇到第一个不相同的字符,如果s1中的字符大于s2,则返回正值,反之则返回负值
strcat(s1,s2) //将字符串s2连接到s1后,返回s1
strcpy(s1,s2) //将字符串s2复制给s1后,返回s1
strncat(s1,s2,n) //将字符串s2前n个字符连接到s1后,返回s1
strncpy(s1,s2) //将字符串s2前n个字符复制给s1后,返回s1

4、没有null结束的字符数组,则计算结果不可预料。
5、在复制和串联字符串时,一定要时刻记住算上结束符null

6、使用C++标准库类型string,会处理所有的内存管理问题。

 7、C语言程序使用一对标准库函数malloc和free在自由存储中分配存储空间,而C++语言则使用new和delete表表达式实现相同的功能。

4.3.1 创建动态数组

1、动态分配数组时,只需要指定类型和数组长度,不必为数组对象命名。

2、动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化。如果是基本类型可以在数组长度后面跟一对空圆括号。

3、在自由存储区中创建的数组储存了内置类型的const对象,则必须为这个数组提供初始化。

4.3.2 新旧代码的兼容

1、无法使用string对象初始化字符指针。

2、string类调用c_str()放回返回C风格字符串。

4.4 多维数组

1、多维数组其实就是数组的数组。

2、行下标指出需要哪个内部数组,列下标则选取改该内部数组指定的元素。

3、使用多维数组名时,实际上将其自动转化为指向该数组第一个元素的指针。

4、int (*p)[4]表示p是一个指向含有4个元素的数组的指针

 第5章 表达式

1、表达式由一个或多个操作数通过操作符组合而成。

2、操作符的含义---该操作符执行什么操作以及操作结果的类型,取决于操作数的类型

3、依据操作数的数量可以确定为几元操作。

5.1 算术操作数

1、如果在求模或者除法运算中,一个数为正另一个数为符,最终结果取决于机器

5.2 关系操作符和逻辑操作符

1、使用逻辑或(||),只要有一个为真,则为真。

2、关系操作符(<、<=、>、>=)具有左结合性。先算左边再算右边,例如:i<j<k,只要k大于1,就为true。、

3、if(val)表示只要val未非零,则条件成立

5.3 位操作符

1、位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。

5.3.1 bitset对象或整型值的使用

1、标准库提供bitset操作,bitset优于整型数据的低级直接位操作。

5.3.2 将位移操作用于IO

1、标准输入输出库分别重载了位操作符>>和<<用于输入输出。

2、位移操作符优先级比算术操作符优先级低

5.4 赋值操作符

1、赋值操作符的左操作数必须是非const的左值。

2、数组名是不可修改的左值:因此数组不可用作赋值操作的目标。

5.4.1 赋值操作的右结合性

1、赋值操作具有右结合性。当表达式含有多个赋值操作符时,从右向左结合。

5.4.2 赋值操作具有低优先级

1、赋值操作优先级低于不等操作符优先级。

5.4.3 复合赋值操作符

1、使用符合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数计算了俩次。

5.5 自增和自减操作

1、自增(++)和自减(--)操作符为对象加1或减1。

2、前置操作返回加1(或减1)后的值,后置操作则是又值

int i=0,j;
j=++i;//j=1,i=1
j=i++;//j=1,i=2

3、后置操作返回为加1的值,然后进行判断或者赋值。后置操作则是先判断或者赋值在加1

vector<int>::iterator itr;
vector<int> value;
itr=value.begin();
*itr++;//先对itr解引用,然后itr会在加1和value.begin()指向的值一样。理解为使itr加1,然后返回itr原来的副本作为该表达式的结果

5.6 箭头操作符

1、箭头操作符(->)用于获取类型对象的成员。

2、解引用的优先级低于点操作符。

5.7 条件操作符

1、条件操作符是C++中唯一操作符,cond ? expr1: expr2,当cond为ture,使用expr1,为false使用expr2.

2、避免使用操作符的深度嵌套

3、条件操作符的优先级相当低,所以在输出表达是中要把条件表达是用圆括号扩起。

5.8 sizeof操作符

1、返回一个对象或类型名的长度

2、对引用类型做sizeof操作返回存放此引用类型对象所需内存空间大小,对指针做sizeof造作返回存放指针所需内存大小,如果要获取改指针所指向对象大小,则必须对指针进行解引用。对数组名做sizeof操作等效与对其元素类型做sizeof操作乘上数组元素的个数。

5.9 逗号表达式

1、逗号表达式的结果是其最右边表达式的结果,它从左往右算。

5.10 复合表达式的求值

1、含有俩个或更多操作符的表达是称为复合表达式。

2、当操作符的优先级相同时,由其结合性决定求解次序。

5.10.2 结合性

1、复制操作为右结合性,从右往左计算。算术操作符为左结合性,决定数据处理顺序

5.11 new和delete表达式

1、动态创建对象时,只需要指定其数据类型,而不必为对象命名。

2、动态创建类类型的对象,用该类的默认构造函数初始化。

3、初始化的()语法必须至于置于类型名后面。

4、如果new表达式无法获取需要的内存空间,系统将抛出名为bad_alloc的异常。

5、如果指正指向不是new分配的内存地址指针,则在该指针上使用delete是不合法的。但是如果指针指向0,调用delete的操作是合法的。

6、使用delete之后,需要重设指针的值,否则该指针会变成悬浮指针,可能会导致程序错误。

8:、动态内存管理易出错的情况:(1)删除指向动态分配内存的指针失败,所以使用new时,最好用try(2)读写已删除的对象,所以删除指针指向的对象后,将指正置位0值(3)对同一内存空间使用俩次delete表达式。

5.12 类型转换

1、俩个类型之间可以相互转换,则称俩个类型相关。

2、转换规则由编译器自动执行,称为隐式类型转换。

3、在赋值操作中,不可能更改左操作数对象的类型

5.12.1 何时发生隐式类型转换

1、在混合类型的表达式中,其操作数被转换为相同的类型

2、用做条件的表达式被转换为bool类型

3、用一表达式初始化某个变量。

5.12.2算术转换

1、C++语言为内置类型提供了一组转换规则,其中最常用的是算术转换。

2、操作数应按什么次序转换为表达式中最宽的类型。

5.12.3 其他隐式转换

1、在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针(在szieof表达式中不能当做指针看)

5.12.4 显示转换

1、显示转换也称为强制类型转换,包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast和reinterpre_cast。

5.12.5 何时需要强制类型转换

1、需要覆盖通常的标准转换或者存在或者存在多种转换时,需要选择一种特定的类型转换。

5.12.6 命名的强制类型转换

1、命名的强制类型转换符号的一版形式如下:cast-name<type><expression>。尽量少使用强制类型转换。

第6章 语句

1、C++定义了一组控制流语句,语序有条件地执行或者重复地执行某部分功能。

6.1 简单语句

1、在while或if条件后面添加额外的分号,会彻底改变程序员意图。

6.2 声明语句

1、定义语句经常被称为声明语句。

6.3 复合语句(块)

1、复合语句通常被称为块,使用一对花括号括起来的语句序列。块标识一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的字块里访问。

2、与其他大多数语句不同,块并不是以分号结束的。

6.4 语句作用域

1、在控制结构 中定义的变量,仅在定义他们的块语句结束前有效。

2、对于作用域外的变量,是不可以用到其在作用域内的残值。

6.5 if语句

1、当多个语句必须作为单个语句执行时,不能漏电花括号

2、在if没有使用花括号的情况下,通常将else匹配给最后出现的尚未批匹配的if语句来解决。

6.6 switch 语句

1、表达式中一个case标号的值匹配则程序将从该标号后面的第一个语句开始依次执行各个语句,直到switch结束或遇到break语句为止。

6.6.2 switch中的控制流

1、在case语句下,记得搭配break。因为程序在执行匹配的case标号相关联的语句后,会跨越case边界继续执行其他语句,直到switch结束或遇到break语句为止。

6.6.3 default标号

1、所有case标号与switch表达式的值都不匹配,并且default标号存在,则执行defaulte标号后面的语句。

6.6.4 switch表达式与case标号

1、case标号必须是整型常量表达式。

6.8 for循环语句的使用

1、for(initializer;conditon;expression){statement};先执行initilizer,判断condition条件是否成立,成立执行statement,在执行expression.依次循环。

2、initializer可以定义多个对象,但不管怎么样,该处只能出现一个语句,因此此处所有对象必须具有相同类型(如果不同将会被转变为相同)。

6.9 do whille语句

1、在循环条件中定义变量的话,则对变量的任何使用都会发生在变量定义之前。

6.10 break语句

1、break语句用于结束最近的while、do while、for或者switch语句,并将程序的执行权传递给紧接在被终止语句之后的语句。

6.11 continue语句

1、continue语句导致最的循环语句当次的迭代提前结束。直接进入下一次循环判断。

6.12 goto语句

1、goto语句提供了函数内部无条件的跳转,实现从goto语句跳转到同一函数内某个带标号的语句。

2、如果goto语句是向前跳转,在带标号语句和got之间定义的变量会重被新定义,不在中间定义的变量值会保持不变

 6.13 try块和异常处理

1、异常机制提供程序中错误检测与错误处理之间的通讯

2、try块,错误处理部分使用它来处理异常。try语句块以try关键字开始,并以一个或多个catch语句结束它。

6.13.1 throw表达式

1、throw表达式的类型决定了所抛出异常类型。

6.13.2 try块

1、每个catch语句包括3部分:关键字catch,圆括号内单个类型或者单个对象的声明---称为异常说明符,以及通常用花括号括起来来的语句块。

2、如果不存在处理异常的catch字句程序运行就要跳到名为terminate的标准库函数,并且依据情况终止程序的执行。

6.13.3 标准异常

1、标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。

6.14 使用预处理器进行调试

1、assert仅用于检查确实不可能的条件,只对程序的调试有帮助,单不能代替运行时的逻辑检查。

2、给函数传递实参遵循变量初始化原则。非引用类型形参以相应实参的副本初始化。

第7 章 函数

1、函数名可以重载,意味着同样的函数名可以对应多个不同的函数。

7.1 函数的定义

1、函数由函数名以及一组操作数类型唯一地表示。

2、C++语言使用调用操作符(即一对圆括号)实现函数的调用

3、函数的运行以形参的(隐式)定义和初始化开始。

4、在函数体内定义的变量只在该函数中可以访问,这种变量称为局部变量,只在函数运行时存在。

5、函数的形参为函数提供了已命名的局部存储空间。他们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递给函数的实参初始化。实参是一个表达式。

7.1.1 函数返回类型

1、函数的类型可以是内置类型(如int或者doble)、类类型或复合类型(如int&或string *),还可以是void类型,表示该函数不返回任何值。

2、在函数定义或声明时,没有显示的指定返回类型是不合法的。

7.1.2 函数形参表

1、函数形参表可以为空,但不能省略。

2、形参表由一系列逗号分隔的参数类型和(可选的)参数名组成。如果俩个参数具有相同的类型,则其类型必须重复声明。

3、调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该类型的类型。

7.2 参数传递

1、每次函数调用时,都会重新创建该函数所有的形参,此时传递的实参将会初始化对应的形参。

7.2.1 非引用形参

1、普通的非引用类型的参数通过复制对应的实参实现初始化。

2、非引用形参表示对应用实参的局部副本。

3、函数的形参可以是指针,此时将复制实参指针,被调用函数可以改变实参指向的值,但不会改变实参。

4、以下情况不适宜复制实参:(1)需要在函数中修改实参的值时(2)当需要以大型对象作为实参传递时(3)当没有办法实现对象的复制时。对于以上几种情况,有效的解决办法是将形参定义为引用或者指针类型。

7.2.2 引用形参

1、引用形参直接关联到其所绑定的对象,而并非这些对象的副本。

2、使用引用形参返回额外的信息。

3、利用const引用避免复制。

4、非const引用形参只能与完全同类型的非const对象关联。

7.2.3 vector和其他容器类型的形参

1、调用还有普通的非引用vector形参的函数将会复制vector的每一个元素。

7.2.4 数组形参

1、数组有俩个特殊的性质,影响我们定义和使用作用在数组上的函数:(1)不能复制数组(2)使用数组名字是,数组名会自动转化为指向其第一个元素的指针。

2、不需要修改数组的元素是,函数应该将形参定义为指向const对象的指针。

3、数组形参可声明为数组的引用。编译时会检查数组实参的大小和形参的大小是否匹配。

4、向函数传递多维数组时,除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定。如:void printValues(int (matrix*)[10], int rowSize);。上面的语句将matrix声明为指向含有10个int类型元素的数组指针。

7.2.5 传递给函数的数组的处理

1、非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。

7.3 return语句

1、return语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。

7.3.1 没有返回值的函数

1、返回类型是void的函数使用return语句是为了引起函数的强制结束。

2、在void函数类型中,return可以返回另一个返回类型同样是void的函数的调用结果。

7.3.2 具有返回值的函数

1、用函数返回值初始化临时对象与用实参初始化形参的方法一样。

2、当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。

3、不能返回局部变量的引用,因为函数调用完成,将会释放分配给局部对象的存储空间。

4、返回指向局部对象的指针也是错误的。一旦函数结束,局部对象被释放,返回的指针变成了指向不再存在的对象的垂悬指针(编译不会报错)

7.3.3 递归

1、递归函数必须定义一个终止条件;否则,函数就会永远调用下去,

7.4 函数声明

1、函数声明由函数返回类型、函数名和形参列表组成。

2、定义函数的源文件应包含声明该函数的头文件。

3、默认实参是一种虽然并不普遍、单在多数情况下仍然适用的实参值。调用函数时,可以省略有默认值的实参。编译器会为我们省略的实参提供默认值。

4、函数调用的实参按位置解析。

5、在一个文件中只能为默认实参指定默认实参一次。

7.5 局部对象

1、每个名字都有作用域,而每个对象都有生命周期。

7.5.1 自动对象

1、只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和结束。

7.5.2 静态局部变量

1、static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会被撤销。

2、当定义静态局部对象的函数结束是,静态局部对象不会被撤销,再次调用时会保持原来的值,并且不会被再重新定义。

7.6 内联函数

1、在函数返回类型前加上关键字inline就可以将函数指定为内联函数,可以避免函数调用的开销。

2、内联机制适用于优化小的、只有几行而且经常被调用的函数。

内联函数应该在有文件中定义,以便编译器能够在调用点内联展开改函数的代码。

7.7 类的成员函数

1、类的成员函数必须在类中定义。但是函数体也可以在类外定义。

7.7.1 定义成员函数的函数体

1、类的所有成员必须在类定义的花括号里面声明。

2、编译器隐式的将在类定义的成员函数当做内联函数。

3、每个成员函数都有一个额外的、隐藏的形参this。在调用成员函数时,形参this初始化为调用函数的对象地址。

4、在类的成员函数形参表加上const,称为常量成员函数。这样可以避免被调用的对象的数据成员被修改。

7.7.2 在类外定义成员函数

 1、在类外定义成员函数必须指明他们是类的成员。

7.7.3 编写构造函数

1、与其他成员函数不同,构造函数和类同名,而且没有返回类型。

2、一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。

 3、如果将构造函数定义为private的,则不能定义类的对象。

4、在冒号和花括号之间的代码称为构造函数的初始化列表。

5、合成的默认构造函数一般适用于仅包含类类型的成员类。

7.8 函数重载

1、出现在相同作用域中列个函数,如果具有相同名字而形参表不同,则称为重载函数。

2、编译器将根据所传递的实参类型来判断调用的是哪个函数。

3、两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果俩个函数形参表完全相同,但返回类型不同,则第二声明是错误的。

4、形参与const形参的等价仅使用于非引用形参。

7.8.1 重载与作用域

1、在函数中局部声明的名字将屏蔽在全局作用域内的同名名字。每个版本的重载函数都应该在同一个作用域中声明。

2、如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。

7.8.2 函数匹配与实参转换

1、函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。

7.8.3 重载确定三个步骤

1、实参类型与形参类型越接近则匹配越佳。

2、注意避免调用具有二义性。至少要有一个实参的匹配优于其他可行函数提供的匹配。

7.8.4 实参类型转换

1、转换等级以降序排列(1)精准匹配。实参与形参类型相同(2)通过类型提升实现的匹配(3)通过标准转换实现的匹配(4)通过类类型实现的匹配。

2、枚举类型只能用同一枚举类型的另一个对象或一个枚举成员进行初始化。

3、仅当形参是引用或指针时,形参是否为const才有影响。及使用引用或者指针时才能使用const来进行重载。

7.9 指向函数的指针

1、函数指针时指指向函数而非指向对象的指针。函数指针是有返回类型、指针名、形参标组成。

2、使用typedef简化函数指针的定义。 typedef bool (*cmpFuc)(const string &,cosnt string &);该定义表示cmpFuc是一种指向函数的指针类型的名字。

3、使用函数名对函数指针做初始化或者复制。直接引用函数等效与在函数名上应用取地址操作。

4、函数指针只能通过同类型函数或函数指针或0值常量表达式进行初始化或赋值。

5、指向不同函数类型的指针之间不存在转换。

6、调用函数指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数。

7、函数的形参可以是指向函数的指针。这种形参可以用以下俩种形式编写: void useBigger(const string &, const string references, bool(const string &,cosnt string &));或者void useBigger(const string &, const string references, bool (*)(const string &,cosnt string &));函数指针使用解引用和不使用解引用的含义一样。

8、可以返回指向函数的指针,于都函数指针声明的最佳方法是从声明的名字开始由里而外理解。int (*ffI(int)) (int *,int),它是一个指向函数的指针,所指向的函数返回ini类型,并带有俩个分别是int *和int型的形参。

9、指向重载函数的指针必须与重载函数的一个版本精准匹配。

第8章 标准IO库

8.1 面向对象的标准库

1、标准库类型不允许做复制或赋值操作。

2、形参或返回类型也不能成为流对象。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用。

8.2 条件状态

1、所有流对象都包含一个条件状态成员,该成员由setstate和clear操作管理,可以用于改变条件成员的状态。

2、在while条件中使用逗号操作符,将返回最右边的操作数作为整个操作的结果。

8.3 输入缓冲区的管理

 1、使用flush,用于刷新流,但不在输出中添加任何字符。

2、nounitbuf操作符将流恢复为使用正常的、由系统管理的缓冲区刷新方式。

 8.4 文件的输入和输出

1、fstream类型除了继承下来的行为外,还定义了两个自己的新操作--open和close,以及形参为要打开的文件名的构造函数。

8.4.1 文件流对象的使用

1、关闭流并不能改变流对象的状态。

2、文件流读写多个文件,必须在读取另外一个文件之前调用clear清除对该流的状态。

8.4.2文件模式

1、在打开文件时,无论是调用open还是以文件名作为流初始化的一部分,都需要指定文件模式。

2、对于用ofstream打开的文件,要保存文件中已存在的数据,唯一的方法就是显示的指定app模式打开。

3、fstream对象默认以in或out模式同时打开。当文件同时以out和in打开时不会清空。

8.4.3 一个打开并检查输入文件程序

8.5 字符串流

1、stringstream对象的常见用法是,需要在多种数据类型之间实现自动化格式化时使用该类类型。利用ostringstream和isstringstream可以实现string和请他类型之间的转换。

第9章 顺序容器

1、将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器。

2、三种顺序容器类型:vector、list和deque。他们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。

3、容器只定义了构造函数、添加或删除元素操作。排序、查找是由标准算法定义的。

9.1 顺序容器定义

1、将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。

2、接收容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。

9.1.2 容器内元素的类型约束

1、容器元素类型必须满足以下俩个约束:(1)元素类型必须支持复制运算(2)元素类型的对象必须可以复制

2、定义容器元素为容器类型时,在>和>之间必须使用空格。

9.2 迭代器和迭代器范围

1、list容器的迭代器不支持算术运算(加法或减法),也不支持关系运算,它只支持前置和后置的自增、自减运算以及相等(不等)运算。

9.2.1 迭代器范围

1、迭代器中的end并不是指向最后一个元素,而是最后一个元素的下一个位置。

9.2.2 使迭代器失效的容器操作

1、任何指向已删除元素的迭代器都具有无效值。

9.3 顺序容器的操作

1、每种顺序容器都提供一组有用类型定义以及以下操作:添加、删除元素,设置容器大小。

9.3.1 容器定义的类型别名

9.3.2 begin和end成员

9.3.3 在顺序容器中添加元素

1、所有顺序容器都支持push_back操作,提供在容器尾部插入一个元素。

2、通过insert可以在迭代器指向的位置之前插入元素。

3、添加或删除deque或vector容器内的元素都可能会导致储存的迭代器失效。

9.3.4 关系操作符

1、比较的容器必须具有相同的容器类型,而且元素类型也必须相同

9.3.5 容器大小操作

1、所用容器类型都提供四种与容器大小相关的操作。

size();//返回容器中目前存在的元素个数
max_size();//返回容器中可容纳的最多的元素。
empty();//返回标记容器是否0的布尔值
c.resize(n);//调整容器c的长度大小,使其能容纳n个元素

 9.3.6 访问元素

c.back(); //返回容器中最后一个元素的引用
c.front(); //返回荣中第一个元素的引用
//以下函数只对vector和deque容器使用
c[n];//返回下标为n的元素引用
c.at[n];//返回下标为n的元素引用,如果越界会抛出异常。

9.3.7 删除元素

c.erase(p) //删除迭代器P所指向的元素,返回一个迭代器指向被删除元素后面的元素。
c.erase(p,e) //删除迭代器P和e之间元素,返回一个迭代器指向被删除元素后面的元素。
c.clear() //删除容器中所有元素
c.pop_back() //删除容器中最后一个元素
c.pop_front() //删除容器中的第一个元素,只使用于list和deque容器

9.3.8 赋值与swap

1、赋值操作符首先删除其左操作数容器中的所有元素,然后将又操作数的所有元素插入到左边的容器中。原有的迭代器全部失效。

c1.swap(c2) //c1和c2中的元素进行交换,迭代器依旧指向原来的元素。
c.assign(b,e) //间迭代器b和e标记范围内所有元素复制到c中。
c.assign(n,t) //将容器c重新设置为存储n个值为t的元素

2、assign操作先删除容器中所有元素,然后将其所指定的新元素插入到该容器中。在不同类型的容器总,元素类型相互兼容,则复制运算必须使用assign函数。

3、使用swap操作可以节省删除元素成本,所以使用起来更方便。

9.4 vector容器的自增长

1、capacity指容器在分配新存储空间之前可以存储的元素总数,而不是值目前容器中存在多少个元素。

2、reserve是为容器预留额外的存储空间,数据不会被初始化,end迭代器指向元素结尾处的下一个迭代器。

9.5 容器的选用

 1、vector和deque容器提供了对元素的快速访问,但付出的代价是,在容器任意位置插入或删除元素,比在容器尾部插入和删除的开销更大。list类型在任何位置都能快速插入和删除,但付出的代价是元素的随机访问开销教大。

2、在deque容器中非首部和尾部位置插入和删除操作将使指向该容器元素的所有迭代器都失效。

9.6 再谈string类型容器

1、介绍string类型常用操作,string可以使用关系操作符。

9.6.1 构造string对象的其他方法

string s(s2,pos2) //s2中从pos2开始的字符副本初始化s。
string s(s2,pos2,len) //s2中从pos2开始的len个字符副本初始化s。
string s(cp,n) //初始化为cp所指向数组前n个元素的副本

9.6.2 修改string对象的其他方法

1、所有的插入和删除都是在指定的下标位置之前进行删除、插入等操作

9.6.3 只适用于string类型的操作

1、主要讲述substr、append、replace函数

2、对于append操作,字符将添加在string对象的末尾。repalce函数则将这些字符插入到指定位置,从而提黄string对象中一段已存在的字符。

3、s.replace(pos,n,args) //从s中删除下标pos开始的len个字符,然后用args替代。

9.6.4 string类型的查找操作

1、find函数返回以下标形式标记查找匹配所发生的位置。

2、s.find_first_of(args) //在s中查找args的任意字符的第一次出现。

9.6.5 string对象的比较

1、对俩个string对象,关系操作符逐个字符进行比较,只到比较到某个位置上,俩个string对象对应的字符不相同为止。

9.7 容器适配器

1、顺序容器适配器:queue、priority_queue和stack..

2、适配器是标准库中通用的概念,包括容器适配器、迭代器适配器和函数适配器。是使一事物的行为类似与另一事物的行为的一种机制。

3、可以将一个顺序容器指定为适配器的第二个类型实参。stack可以建立在任何顺序容器之上。

4、所有容器适配器根据其基础容器类型所支持的操作来定义自己操作。

5、priority_queue允许用户为队列中存储的元素设置优先级,默认从大到小的排列。

第10章 关联容器

1、关联容器通过键储存和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。

2、基本的关联容器容器类型是map和set。map的元素以-值对的形式存在:键用作元素在map中的索引,而值则表示所存储和读取的数据。set仅包含一个键,并有效的支持关于莫个键是否存在的查询。

3、希望有效地存储不同值的集合,那么使用set容器比较合适,而map容器则更适用于需要存储(乃至修改)每个键所关联的值的情况。

10.1 引言:pair类型

1、pair类型在标准库中定义。

2、pair与容器一样,也是一种模板类型。

3、make_pair(v1,v2)函数,由传递给它的俩个实参生成一个新的pair对象。

10.2 关联容器

1、关联容器共享大部分---但非全部---顺序容器操作。关联容器不提供fron、push_front、pop_front、back、push_back以及pop_back操作。

2、在迭代遍历容器时,我们可确保按键的顺序访问元素,而与元素在容器中的存放位置完全无关。

10.3 map类型

1、map是键--值对的集合。map类型通常可以理解为关联数组:可使用键作为下标来获取一个值,而正如内置数组类型一样。

10.3.1 map对象的定义

1、在定义map对象时,必须分别指明键和值的类型。

2、map<k,v> m(b,e) //创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。元素的类型必须能转换为pair<const k, v>

3、键类型的约束,标准库使用键类型定义的<操作符来实现键的比较。

10.3.2 map定义的类型

1、map对象的元素是键-值对,每个元素包含俩个部分:键以及由键关联的值。

2、关于map类定义的类型使用方法待确认。

10.3.3 给map添加元素

1、可以使用insert成员实现,或者使用下标操作符获取元素,然后给元素赋值。

10.3.4 使用下标访问map对象

1、使用下标访问不存在的元素将导致map容器中添加一个新的元素。

10.3.5 map::insert使用

1、插入单个元素的insert版本使用键-值pair类型的参数。
2、insert插入返回值类型为一个pair对象,e.first的元素指向map的迭代器,e.second的与那苏指向bool类型,表示是否插入了该元素。

10.3.6 查找并读取map中的元素

1、cout和find用于检查莫个键是否存在而不会插入该键。

2、cout(k) //返回m中k的出现次数,m.find(k),返回m中指向k索引的迭代器,如果不存在,则返回超出末端迭代器。

10.3.7 从map对象中删除元素

1、通过erase函数删除元素,其删除功能类似于顺序容器,但是map容器返回的void类型。

10.3.8 map对象的迭代遍历

1、通过迭代器遍历元素

10.3.9 “单词转换”map对象

10.4 set类型

1、set容器只是单纯的键的集合。set容器不支持下标操作。

10.4.1 set容器的定义和使用

1、find函数返回的迭代器,只能读取迭代器指向的数据,不能修改迭代器的数据。

10.5 multimap和multiset类型

1、允许一个键对应多个实例。相关操作分别于map和set操作相同。但是multimap不支持下标操作。

10.5.1 元素的添加和删除

1、因为键不是唯一,所以每次调用insert总会添加一个元素。
2、调用带一个键参数的erase版本将删除拥有该键的所有元素,并返回删除的元素个数。

10.5.2 在multimap和multiset中查找元素

1、因为关联容器中元素是按顺序存储的,如果一个键对应多个实例,则这些实例在容器中相邻存放。

2、返回迭代器的关联操作

m.lower_bound(k) //返回一个迭代器,指向键不小于k的第一个元素。
m.upper_bound(k) //返回一个迭代器,指向键大于k的第一个元素。
m.equal_range(k) //返回一个迭代器的pair对象。他的成员有上面俩个函数的迭代器组成。

 第11章 泛型算法

1、“泛型”指可作用在不同类型的容器和不同类型的元素上。

2、大多数算法是通过遍历由俩个迭代器标记的一段元素来实现其功能。

11.1 概述

1、使用俩个迭代器和一个值调用find函数,检查俩个迭代器实参标记范围内的每一个元素(也可以使用指针)

11.2 初窥算法

 1、使用泛型算法必须包含algorithm头文件。

11.2.1 只读算法

1、在numeric头文件中定义,accumulate是计算元素之间的和。

2、find_first_of在第一段范围内查找与第二段范围中任意元素匹配的元素。

待补充信息:isstringstream可以将string对象中的单词单独分割出来

11.2.2 写容器元素算法

1、fill函数写入与指定输入范围数量相同的元素。

2、back_inserter函数是迭代器适配器,使用一个对象作为实参,并生成一个适应其实参行为的新对象。

3、写入到目标迭代器的算法

copy(ilist.begin(),ilist.end(),back_insertter(ivec));将迭代之间的元素插入到ivec中

4、算法的copy版本

replace(ilist.begin(),ilist.end(),0,42) //将迭代器之间的0的示例全部替换为42.
replace(ilist.begin(),ilist.end(),back_insert(ivec),0,42) //ivec储存list的一份副本,而list内所有的0在ivec中都变成了42

 11.2.3 对容器的排序算法

1、sort函数对迭代器之间的元素进行排序。

2、unique算法删除相邻重复元素,然后重新排列输入范围内的元素,并返回一个迭代器,表示重复的值范围的结束。

3、谓词是做某些检测的函数,返回用于条件判断的类型,指出条件是否成立。

4、stable_sort函数对于长度相等的元素,将保留其字典顺序。

5、count_if算法可以用来统计元素的个数。

11.3 再谈迭代器

1、C++语言提供了另外3中迭代器:(1)插入迭代器:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能(2)iostream迭代器:这类迭代器可与输入或输出流绑定在一起,用于遍历所关联的IO流(3)反向迭代器:这类迭代器实现向后遍历,而不是向前遍历

11.3.1 插入迭代器

1、三种插入器,分别在不同位置插入元素:(1)back_inserter,创建使用push_back实现插入的迭代器(2)front_inserter,使用push_front实现插入,所以需要支持push_front才能

11.3.2 iostream迭代器

 1、流迭代器只定义了最基本的迭代器操作:自增、解引用和复制操作

2、可以利用流迭代器,将输入的数据直接存储到容器中

11.3.3 反向迭代器

1、rbegin()通过自加可以移到rend(),例如反向输出vector中的元素

vector<int>::reverse_iterator r_iter;
for(r_iter=vec.rebegin();r_iter!=vec.rend();++r_iter)
    cout<<*r_iter<<endl;

2、反向迭代器和迭代器类型不兼容,需要反向迭代器调用base()成员函数进行转换。

11.3.4 const迭代器

 1、定义const_iterator类型是不希望迭代器修改容器中的元素。

11.3.5 五种迭代器

1、算法要求迭代器操作分为5个类别:输入迭代器(用于读取容器中的元素)、输出迭代器(用于向容器中写入元素)、前向迭代器(用于读写指定容器,这类迭代器只会以一个方向遍历序列)、双向迭代器(从俩个方向读写容器)、随机访问迭代器(提供下标操作)

2、向算法传递无效的迭代器类别所引起的错误,无法保证会在编译时被捕获到。

11.4 泛型算法的结构

1、算法最基本的性质是需要使用的迭代器种类。

2、俩种算法模式:一种模式由算法所带的形参定义;另一种模式则通过俩种函数命名和重载的规范定义。

11.4.1 算法的形参模式

11.4.2 算法的命名规范

11.5 容器的特有算法

1、由于list容器不支持随机访问,因此,在此容器上不能使用需要随机访问迭代器的算法。

2、算法基于迭代器操作,从而实现类型无关。

12 章 类

1、 类定义了数据成员和函数成员:数据成员用于存储与该类类型的对象相关联的状态,而函数成员则负责执行赋予数据意义的操作。

2、类类型常被称为抽象数据类型。抽象数据类型将数据和操作用于状态的操作视为一个单元。

12.1 类的定义

12.1.1类定义:扼要重述

1、在public部分定义的成员可被使用该类类型的所有代码访问;在private部分定义的成员可被其他类成员访问。

2、一旦类定义完成后,就没有任何方式可以增加成员了。

3、构造函数初始化列表由成员名和带括号的初始值组成,跟在构造函数的形参表之后,并以冒号开头。

3、在类内部,声明成员函数是必须的,而定义成员函数则是可选的。在类内部定义的函数默认为inline.

12.1.2 数据抽象和封装

1、类背后蕴含的基本思想是数据抽象和封装。

2、数据抽象是一种依赖接口和实现分离的编程技术。类设计者必须关心类是如何实现的,但对使用该类的程序不必了解这些细节。

3、封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。

4、函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。

12.1.3 关于类定义的更多内容

12.1.4 类声明与定义

1、不完全类型只能以有限的方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或返回类型的函数。

12.1.4 类对象

1、定义对象时,将为其分配存储空间,但定义类型时不进行存储分配。

2、类的定义以分号结束。分号是必须的,因为类型定义之后可以接一个对象定义列表。

12.2 隐含的this指针

 1、基于成员函数是否为const,可以重载一个成员函数。

2、希望类的数据成员(甚至在const成员函数内)可以修改,可以将其声明为mutable来实现。可变数据成员永远不能为const。

12.3 类作用域

1、每个类定义了自己的新作用域和唯一类型。

2、如果返回类型由类定义的类型,则必须使用完全限定名。

3、名字查找会现在该名字的块中查找名字的声明,如果找不到该名字,则在包围的作用域中查找。

4、类成员定义中名字查找:首先查找成员函数局部作用域中用到的名字;如果在成员函数中找不到该名字,则检查对所有类成员的声明;如果类中找不到该名字的声明,则检查此成员函数定义之前的作用域中出现的声明。

12.4 构造函数

1、构造函数的名字与类的名字相同,并且不能指定返回类型。

12.4.1 构造函数初始化

1、有些成员必须在构造函数初始化列表中进行初始化。可以初始化const对象或引用类型对象,但不能对他们赋值。

2、构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的顺序。成员被初始化的次序就是定义成员的次序。

3、尽量避免使用成员来初始化其他成员。

12.4.2默认实参与构造函数

12.4.3 默认构造函数

1、一个类哪怕定义了一个构造函数,编译也不会在生成默认构造函数

2、当对象定义在局部作用域中时,内置和复合类型的成员不进行任何初始化。

 3、如果定义了其他构造函数,则应该显示定义一个默认构造函数几乎总是是对的。

12.4.4 隐式类类型转换

1、通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数

12.4.5 类成员的显示初始化

1、根据数据成员的声明次序来使用初始化式。

12.5 友元

1、友元机制允许一个类将对其共有成员的访问权授予指定的函数或类。友元的声明以关键字friend开始。他只能出现在类定义内部。友元生命可以出现在类的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其他声明出现部分的访问控制影响

2、将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非公有成员。

3、友元生命将已命名的类或非成员函数引入到外围作用域中。

12.6 static类成员

1、每个static数据成员是与类关联的对象,并不与该类的对象关联。

2、类可以定义共享的static数据成员。

3、在成员声明前加上关键字static将成员设为 static.static成员遵循正常的共有/私有访问规则。

4、static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。

5、可以通过作用域操作符,从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。

12.6.1 static成员函数

1、在类的外部定义static时,无需重复指定static保留字,该保留字值出现在类定义内部的声明处。

12.6.2 static数据成员

1、static数据成员可以声明为任意类型,可以是常量、引用、数组、类类型。

2、static数据成员必须在类定义体外部定义。static成员不是同类构造函数进行初始化,而是应该在定义时进行初始化。

3、整型cosnt static数据成员可以在类的定义中进行初始化。

4、非static成员被限定声明为其自身类对象的指正或引用。

13 章 复制控制

1、复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。

2、析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象时构造或在对象的生命中所获取的资源。

3、复制构造函数、赋值操作符和析构函数总成为复制控制。

4、有一种特别常见的情况需要类定义自己的复制控制成员的:类具有指针成员。

13.1 复制构造函数

1、只有单个形参,而且该参数是对本类类型对象的引用(重用const修饰),这样的构造函数称为复制构造函数。

2、当形参为非引用类型的时候,将复制实参的值。类似地,以非引用类型作返回值时,将返回return语句中的值的副本。

13.1.1 合成的复制构造函数

1、合成复制构造函数的行为是,执行成员初始化,将新对象初始化为原对象的副本。

13.1.2 定义自己的复制构造函数

1、复制构造函数就是接受单个类类型引用形参(通常用const修饰)的构造函数

13.1.3 禁止复制

1、为防止复制,类必须显示声明其复制构造函数为private。

2、不定义复制构造函数或默认构造函数,会严重局限类的使用。

13.2 赋值操作符

1、重载操作符是一些函数,其名字为opetator后跟着所定义的操作符的符号。

2、操作符函数有一个返回值和一个形参表。形参表必须具有与该操作操作数数目相同的形参。

13.3 析构函数

1、析构函数可以完成所需的资源回收,作为类构造函数的补充

2、动态分配的对象只有在指向该函数的指针被删除时才撤销。

3、如果需要析构函数,则它需要赋值操作符和复制构造函数。

4、析构函数并不仅限于用来释放资源,该操作是类设计者希望该类对象的使用完毕之后执行的。

5、合成析构函数按对象创建时的逆序撤销每个非static成员。

6、即使我们编写了自己的析构函数,合成析构函数仍然运行。

13.4 消息处理示例

1、显示定义的复制构造函数不会进行任何自动复制。

13.5 管理指针成员

1、包含类的指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。

2、注意、、具有指针成员且使用默认合成复制构造函数的类具有普通指针的所有缺项。尤其是,类本身无法避免悬垂指针。复制指针时,地址是可区分的,但指针指向同一基础对象。

int *p=new int(42);
int * ptr=p;
delete p;
//ptr将被悬垂

13.5.1 定义智能指针类

 1、使用有元类,保存智能指针实际指向的值和技术器的值。

13.5.2 定义值类型

第14章 重载操作符与转换 

1、通过操作符重载,程序员能够针对对类类型的操作数定义不同的操作符版本。

14.1 重载操作符的定义

1、重载操作符是具有特殊名称的函数:保留字opetator后接需要定义的操作符符号。

2、函数调用操作符可以接受任意数目的操作数。

3、重载操作符必须具有一个类类型操作数,用于内置类型的操作符,其含义不能改变。

4、操作符的优先级、结合性或操作数数目不能改变。

5、作为成员函数的操作符有一个隐含的this形参,限定为第一个操作数、

6、操作符定义为非成员函数时,通常必须将他们设置为所操作类的友元。

7、赋值(=)、下标([])、和成员访问箭头(->)、调用操作符("()")必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

8、对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

14.2 输入和输出操作

1、一般应该与标准库iostream为内置类型定义借口相同,因此,许多类都需要重载输入和输出操作符。

14.2.1 输出操作符<<的重载

14.2.2 输入操作符>>的重载

14.3 算术操作符和关系操作符

1、为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。

14.3.1 相等操作符

14.3.2 关系操作符

14.4 赋值操作符

1、类赋值操作符必须是类的成员,以便编译器可以知道是否需要合成一个。赋值操作符会因右操作数类型的不同而不同。

2、赋值操作符可以重载。无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合操作符有所不同。

3、赋值必须返回对*this的引用

14.5 下标操作符

1、下标操作符必须定义为类成员函数。

14.6 成员访问操作符

1、重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

14.7 自增操作符和自减操作符

1、后缀式操作符函数接受一个额外的(即无用的)int型形参。 

14.8 调用操作符和函数对象

1、定义了调用操作符的类,其对象常称为函数对象。 

14.8.1 将函数对象用于标准库算法

14.8.2 标准库定义的函数对象

1、每个标准库函数对象类表示一个操作符,即每个类都定义了应用命名操作的调用操作符。

14.8.3 函数对象的函数适配器

1、标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象

2、函数适配器分为如下俩类:(1)绑定器,是一种函数适配器,它通常将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象(2)求反器,是一种函数适配器,它将谓词对象的真值求反。

14.9 转换与类类型

1、可用一个实参调用的非explicit构造函数定义一个隐式转换。

14.9.1 转换为什么有用

14.9.2 转换操作符

1、转换操作符是一种特殊的类成员函数。

2、转换操作符在类定义体内声明,在保留operator之后跟着转换的目标类型

3、转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。

4、转换函数一般不应该改变被转换的对象。因为,转换操作符通常应定义为const成员。

5、类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。

 14.9.3 实参匹配和转换

1、当俩个类定义了转换时的二义性,需要显示调用转换操作符或构造函数。不能用显示类型转换来解决二义性。

2、避免二义性最好的方法是避免写相互提供隐式转换的成对的类。

14.9.4 重载确定和类的实参

1、存在类类型转换时,可以调用强制类型转换,确定类类型。

2、显示构造函数调用消除二义性。

14.9.5 重载、转换和操作符

 1、候选函数及由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。

2、具有同一名字的成员函数和非成员函数相互重载。

3、重载操作符时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本必须考虑。

4、既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性。

第15章 面向对象编程

1、面向对象编程基于三个基本概念:数据抽象、集成和动态绑定。

2、动态绑定使编译器能够在运行时决定时使用基类中定义的函数还是派生类中定义的函数。

15.1 面向对象编程:概述

1、之所以称通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类或基类型的“许多形态”。

2、多态性仅用于通过继承而相关联的类型的引用或指针。

3、派生类能够继承基类定义的成员,

4、基类必须指定希望派生类重定义哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

5、通过动态绑定我们能够编写程序使用继承层次中任意类型的对象,无需关系对象的具体类型。当形参是引用时,可以传递派生类的对象,实现动态绑定。

6、通过基类的引用(或指针)调用虚函数,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。

void print_total(ostream &os,const Itema_base &item, size_t n)
{
     item.book();      
} //依据传入的对象,决定调用那个对象的方法

15.2 定义基类和派生类

15.2.1 定义基类

1、继承层次的根类一般都要定义虚析构函数。

2、保留字virtual的目的是启用动态绑定。成员默认为非虚构函数,对非虚函数的调用在编译时确定。

3、基类通常将派生类需要重定义的任意函数定义为虚函数。

4、protected成员可以被派生类对象访问,但不能被该类类型的普通用户访问。

15.2.2 protected成员

1、project成员只能在类内部被基类和派生类访问。

 15.2.3 派生类

1、为了定义派生类,使用类派生列表制定基类。类派生表指定了一个或多个基类,具体形式如下:class classname:access-label base-class。access-label是public、protected或private,base-class是已定义的类的名字。access-label决定了对继承成员的访问权限。

2、派生类只(重)定义那些与基类不同或扩展基类方面行为的方面。

3、已定义的类才可以用作基类。如果已经声明了Item_base类,但没有定义它,则不能用Item_base做基类。

4、最底层的派生类对象包含其每个直接基类和间接基类的子对象。

15.2.4 vitural与其他成员函数

1、要触发动态绑定,必须满足俩个条件:(1)只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数(2)必须通过基类类型的引用或指针进行函数调用。

2、因为每个派生类对象都包含基类部分,所以可以将基类类型的引用绑定到派生类对象的基类的部分,也可以用指向基类的指针指向派生类对象。

3、基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型,这是仅在运行时可知的)可能不同。
4、使用基类类型的指针或引用来引用派生类型对象时,变异器都将它当做基类类型对象。

5、对象的实际类型可能不同于该对象引用或指针的静态类型。

6、引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。

7、对于非virtual在编译时会确定调用对象,在传入其他对象也是调用形参类型对象。
8、在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这时可以使用作用域调用符。

9、派生类虚函数调用基类版本时,必须显示的使用作用域操作符。

10、如果调用一个省略了具有默认值的实参,则所用值由调用该函数的类型定义

15.2.5 公用、私有和受保护的继承

1、公用继承,派生类继承基类的访问级别;受保护继承,基类的public和protected成员在类中为protected成员;私有继承,基类的所有成员在派生类中为private成员。

2、派生访问标号将控制派生类的用户(对象)对从Base继承而来的成员的访问。

3、派生访问标号还控制来自非直接派生类的访问(父类的继承方式影响子类)

4、使用private或protected派生的类不继承基类的接口,这些派生通常被称为实现继承。

5、派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松。

6、使用class保留字时是默认情况。

15.2.6 友元关系与继承

1、友元可以访问类的private和protected数据。 

2、基类的友元对从该基类派生的类型没有特殊访问权限(友元关系不会被子类继承)

15.2.7 继承与静态成员

1、如果基类定义了static成员,则整个继承层次中只有一个这样的成员。无论从基类派生多少个派生类,每个static成员只有一个实例。

2、static成员遵循常规访问控制:如果成员在基类中为private,则派生类不能访问它。

15.3 转换与继承

1、每个派生类对象包含一个基类部分,这以为这可以像使用基类对象一样在派生类对象上执行操作。

2、存在从派生类型引用到基类类型的引用的自动转换。

15.3.1 派生类到基类的转换

1、如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化,可以使用派生类型的引用或对象初始化基类类型的引用。

2、用派生类对象对基类对象进行初始化或赋值时,会将派生类部分被“切掉”。

3、派生类到基类转换是否可访问取决于在派生类的派生列表中制定的访问标号。

15.3.2 基类到派生类的转换

1、当基类指针或引用实际绑定到派生类对象时,从基类到派生类转换也存在限制

15.4 构造函数和复制构造

1、构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。

15.4.1 基类构造函数和复制控制

1、继承对基类构造函数的唯一影响是,在确定提供那些构造函数时,必须考虑一类新用户。

15.4.2 派生类构造函数

1、每个派生类构造函数除了初始化自己数据成员之外,还要初始化基类。

2、合成的派生类默认构造函数基类部分由基类的默认构造函数初始化。

3、定义默认构造函数,派生类使用定义的默认构造函数,而基类成员使用基类的构造函数进行初始化。

4、派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。

5、构造函数初始化列表根据声明次序初始化派生类的成员。

6、一个类只能初始化自己的直接基类。直接基类就是派生列表中指定的类。

15.4.3 复制控制和继承

1、具有指针成员的类一般需要定义自己的复制控制来管理这些成员。

2、如果派生类显示定义自己的复制构造函数或赋值操作符,则该定义将完全覆盖默认定义。

3、如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示赋值。

 4、每个析构函数只负责清除自己的成员。对象的撤销顺序与构造顺序相反:首先运行派生类析构函数,然后按继承层次依次向上调用各基类析构函数。

15.4.4 虚析构函数

1、指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。为避免这种情况,可以将基类中析构函数声明为虚析构。

2、如果虚构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因为指针所指对象类型的不同而不同

3、即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

4、虚函数必须在基类和派生类中具有同样的形参(虚析构函数例外)

15.4.5 构造函数和析构函数中的虚函数

1、如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。

15.5 继承情况下的类作用域

1、在继承情况下,派生类的作用域嵌套在基类作用域中

15.5.1 名字查找在编译时发生

1、基类类型的指针(引用或对象)只能访问对象的基类部分,而在基类中没有定义的成员不可以被访问(哪怕基类类型指针实际指向子类对象)

15.5.2 名字冲突与继承

1、与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。

15.5.3 作用域与成员函数

 1、在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型(参数)不同,基类成员也会被屏蔽。

2、成员函数也可以重载,如果子类想重载父类成员函数,可以使用using 作用于::函数名进行声明。

15.5.4 虚函数与作用域

1、如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。

15.6 纯虚函数

1、在函数形参表后面写上=0可以制定纯虚函数

2、含有(或)继承一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。

15.7 容器与继承

1、定义基类类型的容器,如果放入派生类对象,派生类对象只会保存积累部分。

15.8 句柄类与继承

1、C++一个通用的技术是定义包装类或句柄类。句柄类类储存和管理基类指针。

2、指针所指对象的类型可以变化,t它既可以指向基类类型对象又可以指向派生类型对象。

15.8.1 指针型句柄类

 1、类似智能指针具体实现,应用场景带确认。说明定义指针型句柄类的方法。

15.8.2 复制未知类型

 1、句柄类经常需要在不知道对象的确切类型时分配已知对象的新副本。

15.8.3 句柄的使用

15.9 在谈文本查询示例

15.9.1 面向对象的解决方案

15.9.2 值型句柄

 

第16章模板与泛型编程

1、使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。

16.1 模板定义

1、如果每个函数体是相同的,唯一区别是形参的类型,就可以使用模板类。

16.1.1 定义函数模板

1、函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。 

2、模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表

3、模板形参可以是表示类型的类型形参,也可以是表示常量表达式非类型形参。

template <typename T>
int Test_Template<T value>

4、使用函数模板时,编译器会推断那个模板实参绑定模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。

5、inline说明符放在模板形参表之后、返回类型之前,不能放在关键字template之前。

16.1.2 定义类模板

1、类模板是指在类中可以支持不同类型的对象。

template <class Type> class Queue
{
  Type &front();
}

Queue<int> qi; //对象中的成员函数Type被int替代。

2、类模板可以定义数据成员、函数成员和类型成员,也可以访问标号控制对成员的访问,还可以定义构造函数和析构函数等。

16.1.3 模板形参

1、模板形参选择的名字没有本质含义

template<class Glorp>

int compare(Glorp &v1,Glorp &v2)

{}

2、与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字。

3、用作模板形参的名字不能在模板内部重用

4、模板形参的名字只能在同一模板形参表中使用一次。

5、像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板。

6、每个模板类型形参前面必须带上关键字class或typename

16.1.4 模板类型形参

1、class或typename指向后面所接的名字表示一个类型。

2、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。它可用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。

3、关键字typename代替关键字class指定模板类型形参,作为标准C++的组成部分加入,因此旧的程序更有可能只用关键字class。

4、在模板定义内部指定类型,默认情况下,编译假定这样的名字指定数据成员,而不是类型,如果要将size_type当作类型,通过成员名前加关键字typename作为前缀。

16.1.5 非类型模板形参

 1、模板形参不必都是类型,在调用函数时非类型形参将用值代替。

16.1.6 编写泛型程序

1、编写模板代码时,对实参类型的要求尽可能少是很有益的

2、通过将形参设为const引用,就可以允许使用不允许复制的类型。

16.2 实例化

1、产生模板的特定类型实例的过程称为实例化。

2、类模板的每次实例化都会产生一个独立类型。

3、使用类模板,必须显示指定模板那实参

4、使用函数模板时,编译器通常会为我们推断模板实参。

16.2.1 模板实参推断

1、从函数实参确定模板实参的类型和值的过程叫做模板实参推断。

2、模板类型推断必须为每个对应的函数实参产生相同的模板实参类型。

3、不会转换实参以匹配已有的实力化,相反会产生新的实例。

4、const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针调用,无须产生新的实例化。

5、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。

6、在模板实参推断中,当形参为引用时,数组不能转换为指针。

7、类型转换的限制只适用于类型为模板形参的那些实参。

8、使用函数模板对函数指针进行初始化或赋值。

9、获取函数模板实例化的地址时,上下文必须是这样的:它允许每个模板版形参确定唯一的类型或值。

16.2.2 函数模板的显示实参

1、在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。

2、在返回类型中使用类型形参,必须由调用者显示指定。

3、通过使用显示模板实参能够消除二义性

16.3 模板编译模型

1、当编译器看到模板定义的时候,它不立即产生代码。只有在看到用到模板时,如调用函数模板或定义了类模板的对象时侯,编译器才产生特定类型的模板实例。

2、在包含编译模型中,编译器必须看到用到所有模板的定义。

3、在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是我们必须让编译器知道记住给定的模板定义,可以使用export关键字来做这件事情。

4、export关键字能够指明给定的定义可能会需要在其他文件中产生实例化。

16.4 类模板成员

主要讲述Queue类的实现,剖析标准哭queue的实现。

16.4.1 类模板成员函数

1、类模板成员函数的定义具有如下形式:(1)必须以关键字template开头,后接类的模板形参表(2)必须指出它是哪个类的成员(3)类名必须包含其模板形参。

2、在作用域操作符(::)之前使用的Queue<Type>指定成员函数所属类。

3、类模板成员函数的模板形参由调用该函数的对象类型确定。

4、类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。

16.4.2 非类型形参的模板实参

 1、非类型模板实参必须是编译时常量表达式。

16.4.3 类模板中的友元声明

1、非模板类或非模板函数可以是类模板的友元

2、友元可以是类模板或函数模板

3、除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权限。

4、当授予对给定模板所有实例的访问权的时候,在作用域中不需要存在该类模板或函数模板的声明。

16.4.4 Queue和QueueItem的友元声明

16.4.5 成员模板

1、任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。

16.4.6 完整的Queue类

16.4.7 类模板的static成员

1、类模板的每个实例化有自己的static成员。

2、可以通过类类型的对象访问类模板的static成员,或者通过使用作用域操作符直接访问成员(必须引用实际的实例化)

3、像使用任意其他static数据成员一样,必须在类外部出现数据成员的定义。

16.5 一个泛型句柄类

1、本节将实现一个泛型句柄类,提供管理使用计数和基础对象的操作。

16.5.1 定义句柄类

16.5.2 使用句柄

16.6 模板特化

 1、调用特化函数或使用特化类,与使用从通用模板实例化的版本无法区别

16.6.1 函数模板特化

1、模板特化是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。

2、如果可以从函数形参表推断模板实参,则不必显示指定模板实参。

3、省略空的形参表template<>,则结果是声明该函数的重载非模板版本。

4、在模板特化版本的调用中,实参类型必须与特化版本函数类型完全匹配。

5、如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件中出现。

6、特化出现在对该模板实例的调用之后是错误的。

16.6.2 类模板的特化

1、在类特化外部定义成员时,成员之前不能加template<>标记。

16.6.3 特化成员而不特化类

1、成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参开头。

16.6.4 模板类的部分特化

1、如果类模板有一个以上的模板形参,想要特化某些模板形参而非全部。可以使用类模板的部分特化。

2、部分特化的模板形参表只列出未知模板实参的那些形参。

3、类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。

16.7 重载与函数模板

1、函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。

2、如果重载函数中既有普通函数又有函数模板,确定函数调用步骤如下:(1)为这个函数名建立候选函数集合(2)确定哪些普通函数是可行的(3)如果需要转换来进行调用,根据转换的种类排列可行函数(4)重新排列去掉函数模板实例的可行函数。

3、设计重载函数,需要注意可以发生和不能发生的隐式转换。

4、像通常一样,当匹配同样好时,非模板优先。

17 章 用于大型程序的工具

17.1 异常处理

17.1.1 抛出类类型的异常

1、异常是通过抛出对象而引发的。

2、在异常处理时候会释放局部存储,所以被抛出的对象就不能在局部存储了。

3、抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。

17.1.2 栈展开

1、异常抛出的时候,将暂停当前函数的执行,开始查找匹配的catch字句。

2、对抛出异常的函数的调用是在try块中,则检查与该try相关的catch字句。如果找不到就在调用这个函数的函数中查找,这个过程称为栈展开。

3、如果找不大匹配的catch,程序就调用库函数terminate

17.1.3 捕获异常

1、catch子句中的异常说明符看起来像只包含一个形参的形参表,异常说明符是在其后跟一个(可选)形参名的类型名。

2、说明符的类型决定了处理代码能够捕获的异常种类

3、找到的catch不必是与异常最匹配的哪个catch,相反,将选中第一个找到的可以处理该异常的catch.

17.1.4 重新抛出

1、catch可以通过重新抛出将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw.

17.1.5 捕获所有异常处理代码

1、使用捕获所有异常catch字句的。catch子句形式为(...),必须为最后一个,否则任何在它后面的catch字句都不能被匹配。

17.1.6 函数测试块与构造函数

1、为了处理来自构造函数初始化的异常,必须将构造函数编写为函数测试块。

17.1.7 异常类层次

1、用和使用标准库类相同的方法使用自己的异常类。

17.1.8 自动资源释放

1、使用类管理分配和回收可以保证如果发生异常就释放资源。

17.1.9 auto_ptr类

1、标准库的auto_ptr类为动态分配的对象提供异常安全。

2、auto_ptr只能用于管理从new返回的一个对象,他不能管理动态分配的数组。

3、auto_ptr对象的复制和复制是破坏性操作。

4、因为复制和赋值是破坏性操作,所以不能将auto_ptr对象存储在标准容器中。

17.1.10 异常说明

1、异常说明指定,如果函数抛出异常,被抛弃的异常将是包含在该说明中的一中,或者从列出的异常中派生的类型。

2、一个异常说明在关键字throw之后跟着一个(可能为空)由圆括号括住的异常类型列表。

3、派生类不能在异常说明列表中增加异常。

17.1.11 函数指针的异常说明

1、异常说明是函数类型的一部分。这样,也可以在函数指针的定义中提供异常说明。

17.2 命名空间

1、命名空间防止名字冲突提供了更加可控的机制。

17.2.1 命名空间的定义

1、命名空间定义以关键字namespace开始,后面接命名空间的名字。

2、命名空间可以在全局作用域定义或其他作用域定义,但不能在函数或类内部定义。

3、不同命名空间引入不同作用域,所以不同命名空间可以具有同名成员。

4、命名空间可以在几个部分中定义。命名空间由它的分离定义部分总和构成,命名空间是累积的。

5、在全局作用域定义实体的每个文件将那些名字加到全局命名空间。

17.2.2 嵌套命名空间

1、嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。

17.2.3 未命名的命名空间

1、未命名的命名空间的定义局部于特定文件,从不跨越多个文件。

2、未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。

17.2.4 命名空间成员的使用

1、using声明中引入名字遵循常规作用域规则。从using声明点开始,直到包含该using声明的作用域的末尾,名字都是可见的。

2、可用命名空间别名将较短的同义词与命名空间名字相关联

17.2.5 类、命名空间和作用域

1、名字的可见性穿过任意嵌套作用域,直接引入名字块的末尾。

2、类内部所定义的成员可以使用出现在定义文本之后的名字。

3、接收类类型形参(或类类型指针及引用形参)的函数(包括重载操作符),以及与类本身定义在同一命名空间中的函数(包括重载操作符),在用类类型对象(或类类型的引用及指针)作为实参的时候是可见的。

17.2.6 重载与命名空间

1、作为俩个不同命名空间的成员函数不能相互重载。但是给定命名空间可以包含一组重载函数成员。

2、没办法编写using声明来引用特定函数声明

17.2.7 命名空间与模板

1、为了提供命名空间中所定义模板的自己特化,必须保证在包含原始模板定义命名空间中定义特化。

17.3 多重继承与虚继承

1、多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性。

17.3.1 多重继承

1、为了支持多重继承,可以通过逗号分隔基类列表。

2、派生类的构造函数可以在构造函数初始化式中给零个或多个基类传递值。

3、构造函数初始化只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中出现次序调用。

17.3.2 转换与多个基类

 1、派生类的指针或引用可以转为其任意基类的指针或引用。

2、用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。

3、在多重继承中,在通过父类指针或引用使用子类对象时,不能访问子类借口的子类特有部分和子类继承的其他父类部分

17.3.3 多重继承派生类的复制控制

17.3.4 多重继承下的类作用域

1、当一个类有多个基类的时候,通过所有直接基类同时进行名字查找。多重继承的派生类有可能从俩个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。

2、名字查找总是以俩个步骤发生:首先编译器找到一个匹配的声明(或者,在这个例子中,找到两个匹配的声明,这导致二议性),然后,编译器才能确定找到的声明是否合法。

17.3.5 虚继承

1、抽象基类管理流的条件状态并保存流所读写的缓冲区。

2、虚继承是一种机制,类通过虚继承指出它希望共享其虚基类

17.3.6 虚基类的声明

1、通过用关键字virtual修改声明,将基类指定为通过虚继承派生。

2、指定虚派生只影响从指定了虚基类的类派生的类。除了影响派生类自己的对象之外,它也是关于派生类与自己的未来派生的关系的一个陈述。

3、使用虚基类的多重继承层次比没有虚继承的引起更少的二异性。

17.3.7 特殊的初始化语义

1、在虚派生中,由最底层派生类的构造函数初始化虚基类。

2、无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。

第18章 特殊工具与技术

18.1 优化内存分配

1、C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。

18.1.1 C++中的内存分配

1、C++中,内存分配和对象构造紧密纠缠,就像对象析构和内存回收一样。

2、现代C++程序一般应该使用allocator类来分配内存,它更安全更灵活。但是,在构造对象时候,用new表达是比alloctor::construct成员更灵活。

18.1.2 allocator类

1、allocator类将内存分配和对象构造分开。

2、描述如何用allocator类实现vector的功能

18.1.3 operatpr new函数和operator delete函数

1、allocate成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位所需的内存量,他们也可以避免对operator new的返回值进行强制类型转换。

18.1.4 定位new表达式

1、定位new表达式在已分配的原始内存中初始化一个对象,它与new的其他版本不同之处在于,它不分配内存。

2、定位new表达式的形式是:new (place_address) type

3、在复制构造函数是不可用,或者应该避免的,就可以使用定位new 表达式

18.1.5 显示析构函数的调用

1、显示调用析构函数的效果是适当清楚对象本身。但是并没有释放对象所占的内存,如果需要,可以重用该内存空间。

2、调用operator delete函数不会运行析构函数,它只释放指定的内存。

18.1.6 类特定的new和delete

1、只需要定义operator new和operator delete的新版本,new和delete表达式自己照管对象的构造和撤销。

2、如果new表达式调用全局operator new函数分配内存,则delete表达式也应该调用全局operator delete函数。

18.1.7 一个内存分配器基类

1、释放元素的时候,将他们返回预先分配对象的块中,而不是将内存实际返回给系统。这种策略常被称为维持一个自由列表。

18.2 运行时类型识别

 1、通过运行时类型识别(RTTI),程序能够使用基类的指针或引用

2、通过typeid操作符,返回指针或引用所指对象的实际类型;dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

18.2.1 dynamic_cast操作符

1、可以使用dynamic_cast操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。

18.2.2 typeid操作符

1、typeid操作符使程序能够问一个表达式:你是什么类型。

2、最常见的用途是比较俩个表达式类型,或者将表达式的类型与特定类型相比较。

18.2.3 RTTI的使用

1、讲述使用RTTI如何判断俩个类是否相等

18.2.4 type_info类

1、type_info类的确切定义随编译器而变化

18.3 类成员的指针

1、成员指针包含类的类型以及成员的类型。

18.3.1 声明成员指针

18.3.2 使用类成员指针

1. ->*和.*是俩个新的操作符,他们使我们能够将成员指针绑定到实际对象。

18.4 嵌套类

1、在一个类的内部定义一个类,这样的类就是嵌套类,称为嵌套类型。嵌套类常用于定义执行类。

2、嵌套类只声明类名。

18.4.1 嵌套类的实现

1、嵌套在类模板内部的类是模板

2、在类外部定义的嵌套类成员,必须定义外围类的同一作用域中。

3、外围作用域的对象与其嵌套类型的对象之间没有联系。

18.4.2 嵌套类作用域中的名字查找

1、当处理类成员声明的时候,所有的任意名字必须在使用之前出现。当处理定义的时候,整个嵌套类和外围类均在作用域中。

18.5 联合:节省空间的类

1、联合(union)是一种特殊的类。一个union对象可以有多个数据成员,但在任何时刻,只有一个成员可以有值。当将一个值赋予给union对象的一个成员的时候,其他所有成员都变为未定义的。

2、使用union对象时,必须总是知道union对象中当前存储的是什么类型的值。

18.6 局部类

1、可以在函数体内部定义的类,这样的类称为局部类。

18.7 固有的不可移植的特征

1、将程序移到新机器的过程称为“移植”,所以说C程序是可移植的。

2、C++不可移植的特征包括:位域和volatile限定符、链接指示

18.7.1 位域

1、位域可以用来保存特定的位数。当程序需要将二进制数据传递给另一个程序或硬件设备的时候,通常使用位域。

2、地址操作符不能用于位域,所以不可能引用类位域的指针,位域也不能是类的静态成员。

18.7.2 volatile限定符

1、volatile的确切含义与机器相关,只能通过阅读编译器文档来理解。

2、关键字volatile是给编译期的指示,指出对这样的对象不应该执行优化。

3、合成的复制控制不适用于volatile对象。volatile操作数进行复制或复制时,需要定义自己的复制构造函数或赋值操作版本。

18.7.3 链接指示:extern "C"

1、C++使用链接指示指出任意非C++函数所用的语言。

2、通过对函数定义使用链接指示,使的用其他语言编写的程序可以用C++函数

4、C函数的指针与C++函数的指针具有不同的类型,不能将C函数的指针初始化赋值为C++函数的指针。

疑问及待处理事项:
1、P419的习题待实现

2、智能执政中val的值具体是什么作用,P496的使用

3、定义派生类列表的作用?

4、派生类对象不能别初始化继承的对象成员。

5、指针形句柄类的使用情况

6、explicit关键字的作用

 

 sxbb.me/OfnWQ

 

习题答案:
sxbb.me/OfnWQ
https://wenku.baidu.com/view/046911fc350cba1aa8114431b90d6c85ec3a8813.html

posted @ 2022-12-23 22:17  天码丶行满  阅读(111)  评论(0编辑  收藏  举报