我不知道的C++

1、C++的三宝:

(1)C++继承了C语言高效、简洁、快速和可移植性的传统。

(2)C++面向对象的特性带来了全新的编程方法,这种方法是为应付复杂程度不断提高的现代编程任务而设计的。

(3)C++的模板特性提供了另一种全新的编程方法——泛型编程。

                                                                         //2016/1/19   9:13

2、面向过程与面向对象:

算法+数据=程序

(1)面向过程强调的是算法的过程性,面向对象强调的是数据。

(2)面向过程试图使问题满足语言的过程性方法,而面向对象试图让语言来满足问题的要求,它的理念是设计与问题的本质特性相对应的数据格式。

3、对C++的类与对象的进一步的理解:

(1)在C++中,类是一种规范,它描述了这种新型数据格式。

(2)对象是根据这种规范构造的特定数据结构。

4、C++名字的由来:

名称C++来自C语言中的递增运算符++,该运算符将变量加1,名称C++表明它是C的扩充版本。

//2016/1/30

5、C++中  '\n'和endl的区别:

endl确保程序继续运行前刷新输出(将其立即显示在屏幕上),而使用‘\n’不能提供这样的保证。这意味着在有些系统中,有时可能在输入信息后才会出现显示。

6、C++中的声明的作用:

指出存储类型并提供位置标签,即需要的内存以及该内存单元的名称。编译器负责分配和标记内存的细节。

7、C++变量名的命名规则:

以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器以及使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。C++对于名称的长度没有限制,而C99只保证名称中的钱63个字符有意义。

8、典型的整型溢出行为:


//2016/9/21

1、main函数是唯一被操作系统显示调用的函数

2、对于main函数,其返回类型必须是int型,在大多数系统中main函数的返回值是一个状态指示器,返回0表示main函数成功执行完毕,而非零的返回值都有操作系统定义的含义。

3、C++程序文件的后缀形式包括:.cc  .cxx  .cpp .cp  .c


//2016/11/17

1、C++里的基本内置类型:

①内置类型:整数、单个字符、布尔值(这三个统称为整型)、浮点数。

②void的特殊类型

2、内置类型的机器级表示:

让存储具有结构的最基本方法是用块处理存储,通常8位为一块,作为一个字节,32位或者4个字节为一个字。

通常int类型为一个机器字长,short为半个机器字长,long为一个或者两个机器字长。

3、给一个无符号整型赋一个超过其表示范围的数值时的不同情况:

①给一个无符号整型赋一个超过其表示范围的  正  数,比如对于8位的unsigend char,它的表示范围是0到255共256个值,若给它赋值336,则实际赋值为80,即336%256=80。

②给一个无符号整型赋一个负数,有些语言规定这是非法的,但是在C++中合法,比如将  -1 赋给unsigned char,结果是255,即 -1%256=255。

4、给一个signed类型赋一个超过其取值范围的值时的情况:

多数情况与3类似,但是一般是根据编译器情况不同而不同。

5、对于实际的程序来说,float类型精度通常是不够的——float型智能保证6位有效数字,而double型至少可以保存10位有效数字,并且双精度计算代价相对于单精度可以忽略不计,因此最好使用double型。


//2016/11/23

1、什么是对象

对象是内存中具有类型的区域。

2、初始化

变量定义时指定了初始值的称为初始化,但是初始化不是(简单的)赋值,初始化包括:创建变量、赋初值。而赋值仅仅是擦除对象的当前值并用新值替代。

初始化和赋值的区别在复杂的类的操作时会很明显!

3、构造函数的作用:

告诉我们如何初始化类类型的变量。

4、定义和声明的区别与联系:

C++程序通常由许多文件组成,为了让多个文件访问相同的变量,C++区分了声明和定义。

声明:用于向程序表明变量的类型和名字,例如:extern int i;其中名字为 i,类型为int,而extern是关键字。

定义也是声明!!!!!!!!!!!!!!

声明不是定义!!!!!!!!!!!!!!声明不分配存储空间,它只是说明变量定义在程序的其他地方。(程序中变量可以声明多次)

but!!!!当声明具有初始化式的时候,声明也就被当做成了定义!例如:extern double pi=3.1416,因为初始化式必须要有存储空间来进行初始化。——并且只有当extern声明位于函数外部时,才可以含有初始化式!!!

C++中在使用变量之前必须      定义!!!!或者!!!!!或者!!!!!或者!!!!!!声明~~~~

5、咬文嚼字理解作用域:

C++程序中每个名字都与唯一的实体(比如变量、函数和类型等)相关联,在程序中多次使用同一个名字,而用来区分名字的不同的不同意义的上下文称为作用域!!!!

低级别的作用域的三种境界:全局、局部、语句。

高级别的作用域:类作用域和命名空间作用域

6、const变量与非const变量与extern的关系:

非const变量默认为extern,而const变量不是,所以要使const变量能够在其他的文件中访问,必须显示地指定它为extern。const变量默认时是定义该变量的文件的局部变量。

7、引用与复合类型:

引用就是对象的另一个名字,复合类型就是用其他类型定义的类型。不能定义引用类型的引用,但可以定义任何其他类型的引用。

8、如何理解const引用?

首先它叫做const引用,那么实际上它就是个引用。其次它的const限制的是它所引用的那个类型的值不能变:const int ival = 1024; const int &refval = ival; 在这里refval就是const引用,即它引用的ival的值不能修改。

const引用严格的叫法应该是:指向const对象的引用~~~~~~~~~~~~~~~~~~~~~~~~

9、非const引用和const引用的区别:

非const引用只能绑定到与该引用同类型的对象。

const引用可以绑定到不同但相关的类型的对象或绑定到右值。

//2016/11/24

1、类的接口与实现的区别:

接口:由使用该类的代码需要执行的操作组成,即该类所提供的的操作。

实现:包括该类所需要的数据,以及定义该类需要的但是不供一般性使用的函数。

2、访问标号:

类的成员函数可以使用类的任何成员,而不管其访问级别。

3、头文件中一般放置什么?

类的定义、extern变量的声明、函数的声明。 不能含有:变量或函数的定义。

可以放置的三个特殊情况:类的定义、值在编译时就已知的const对象、inline函数。

const double pi=3.1416是常量表达式初始化应该放在头文件中

const double sq2=squt(2)不是用常量表达式初始化所以只能放在源文件中

4、如何叙述什么是默认构造函数?

默认构造函数就是在没有显示提供初始化式时调用的构造函数。

//2016-11-26

1、string类型教你如何能显得更专业点

string类类型和其它许多库类型都定义了一些配套类型,通过这些配套类型,库类型的使用就能与机器无关。

例如不要使用int来接收string类的size操作返回值,而是用string::size_type来接收。

2、string类型和字符串字面值相连接时需要注意的!

当string对象和字符串字面值混合连接时,+  操作赋的左右操作数必须至少有一个是string类型的。

3、string对象中字符的处理

string对象还包含了对字符的处理这个很有用哇!

4、怎么叙述vector是个什么东西?

vector是同一类型的对象的集合。并且,vector不是一种数据类型,只是一个类模板,它是用来定义任意多种数据类型的,像vector<int>、vector<string>才是一种数据类型。

5、下标操作的误区!!!!

其实只有很少的容器支持下标操作,所以有了迭代器,迭代器对所有的容器都适用!!!

6、const的iterator和const_iterator的区别:

const_iterator是容器内置的迭代器类型,意思是不能改变迭代器指向的元素的值,但其本身可以进行+1递增操作。

而const 类型的iterator是自定义的类型,意思是iterator创建时候必须初始化指向容器某个位置的迭代器,不可以进行+1递增操作。

7、标准卡bitset类型:

bitset类型对象的区别在于长度:bitset<16>是一种类型,bitset<32>是另一种类型。

//2016-12-6

1、数组下标的类型:

size_t

2、指针提供  间接操作  其所指对象的功能。

3、指针和引用的两个重要区别:

①引用总是指向某个对象:定义引用时没有初始化式错误的;②赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。

4、指针做减法操作时用于保存结果的数据类型:

标准库类型:ptrdiff_t,并且它是与机器相关的,类型为signed。

5、C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作,而计算数组超出末端位置之后或数组首地址之前的地址都是不合法的。

//2016-12-7

1、指向const对象的指针:

const double * cptr

2、const 指针:

int errNumb=0; int * const curErr = &errNumb;

3、const的位置:

string const s1; const string s2。s1和s2都是string类型的const对象,是一样的。

4、注意typedef中使用指针时的结果:

typedef string *pstring; const pstring cstr;这里const pstring cstr等价于string * const cstr,也就是说cstr是const类型的指针。

5、C++从C语言继承下来的一种通用结构是C风格字符串,而字符串字面值就是该类型的实例,C风格字符串既不能确切地归结为C语言的类型,也不能归结为C++语言的类型,而是以空字符null结束的字符串。(以null结束很重要,如果结尾不是null则不是C风格字符串,并且不能使用C风格字符串的标准库函数)

6、strncat与strncpy比strcat和strcpy更加安全,就是因为字符串末尾的null总是被忘记。

7、动态分配的数组,若数组元素具有类类型,将调用该类的默认构造函数实现初始化,若数组元素是内置类型,则无法初始化例如:int *pia2=new int[10],分配了存储10个int对象的内存空间,但这些元素没有初始化,但是可以通过在后面加括号的方式进行初始化:int *pia2=new int[10](),只能初始化为0.

8、C++不允许定义长度为0的数组变量,但调用new动态创建长度为0的数组是合法的。

9、如何连接C++的string类型和C风格的字符串:

string st2("abcdefg");const char *str==st2.c_str();这里注意str必须是const对象,因为st2.c_str()返回的指针指向const char类型的数组。

10、使用数组初始化vector对象:

const size_t arr_size=6;  int int_arr[arr_size]={0,1,2,3,4,5}; vector<int>ivec(int_arr,int_arr+arr_size);,这里注意int_arr+arr_size指向的是被复制的最后一个元素之后的地址空间。

11、int *ip[4]和int(*ip)[4]的区别:

int *ip[4]中ip是个包含4个元素的数组,每个元素是指向int类型的指针。

int(*ip)[4]中ip是个指针,指向的是包含四个int型元素的数组。

//2016-12-12

1、悬垂指针:

double *pd = new double(18);   delete p;   此时pd仍然存放了它之前所指向对象的地址,然而pd所指向的内存已经被释放掉,因此pd不再有效,pd变成了垂悬指针,解决方法:pd=0; 将指针置为0,可以清楚地表明指针不再指向任何对象。

//2016-12-14

1、异常:

每一个标准库异常类都定义了名为what的成员函数,返回C风格字符串。

2、使用预处理器进行调试

#ifndef NDEBUG

cerr<<"starting main" <<endl;

#endif

__FILE__  文件名;__LINE__当前行号;__TIME__文件被编译的时间;__DATE__文件被编译的日期

使用:CC -DNDEBUG main.c

3、预处理宏:

assert()

4、函数调用的过程:

①用对应的实参初始化函数的形参,并将控制权转移给别调用的函数。

②主调函数的执行被挂起,被调函数开始执行。

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

5、形参和实参的区别:

形参是在函数定义的形参表中进行定义,是一个变量,其作用域为整个函数。

实参出现在函数调用中,是一个表达式,进行函数调用时,用传递给函数的实参对形参进行初始化。

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

7、vector和其它容器类型的形参:

C++程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。

8、通过引用传递数组:

如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身,并且编译器将检查数组实参的大小与形参的大小是否匹配。例如函数定义:f(int(&arr)[10]),注意括号和大小都不可以省略。

9、引用返回左值:

返回引用的函数返回一个左值!!!!

10、自动对象:

只有当定义它的函数被调用时才存在的对象称为自动对象。

11、static局部对象

12、内联函数:

内联函数的性质决定了它可能要在程序中定义不止一次,所以应该把内联函数的定义放在头文件中。

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

14、this指针:

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

15、常量成员函数:

类的成员函数:bool same_isbn(const Sales_item &rhs)const{return isbn==rhs.isbn}。后面的这个const就是修饰隐含形参this指针的,当使用对象调用这个函数时:total.same_isbn(trans)意思是this形参将是一个指向total对象的const Sales_Item*类型的指针,其中trans是对应形参rhs的实参。

16、const对象、指向const对象的指针或引用只能用于调用其const成员函数。

17、默认构造函数和合成的默认构造函数的区别

18、类的构造函数应当是public的。

19、类的对象在全局作用域中定义时,其内置类型的类成员初值依赖于对象如何定义。而类的对象在局部作用域中定义时,则内置类型成员 没有初始化。

20、函数不能仅仅基于不同的返回类型而实现重载:

如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的。

21、形参与const形参的等价性:

Record lookup(Phone)与Record lookup(const Phone)。第二个函数声明被视为第一个的重复声明。

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

23、函数重载确定,即函数匹配:

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

24、精确匹配优于需要类型转换的匹配。

25候选函数与可行函数的概念。

26、实参类型转换的降序排列:

①精确匹配;②通过类型提升实现的匹配;③通过标准转换实现的匹配;④通过类类型转换实现的匹配。

27、整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。

28、仅当形参是引用或者指针时形参是否为const才有影响。

29、函数类型由其返回类型以及形参表确定,而与函数名无关。

29、返回指向函数的指针:

int (*ff(int))(int*, int); 首先:ff(int)说明ff为一个函数,它带有int型的形参,这个函数返回int(*)(int*, int)它是一个指向函数的指针。

30、具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针,但是当返回的是函数时,同样的转换操作则无法实现。:

typedef int func(int*, int);

void f1(func);//ok:f1 has a parameter of function type

func f2(int);  //error: f2 has a return type of function type

func * f3(int); //ok: f3 returns a pointer to function type

//2016-12-22

1、输出缓冲区的刷新:

三种:flush、ends和endl,其中flush不往buffer里添加任何数据,ends往buffer里添加null,endl往buffer里添加回车换行符。

2、unitbuf操作符相当于在每次执行完写操作后都刷新流,就是多次使用flush。

//2016-12-26

1、6个顺序容器:

vector(支持快速随机访问)、list(支持快速插入/删除)、deque(双端队列)以及顺序容器适配器:stack(后进先出LIFO栈)、queue(先进先出FIFO队列)、priority_queue(有优先级管理的队列)

2、顺序容器适配器:

适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。

3、将容器初始化为另一个容器的副本时要求容器类型和元素类型都必须相同,而初始化为一段元素的副本时候,不要求容器类型相同,容器内的元素类型也可以不同,但是需要它们相互兼容,相互转换。

4、容器内元素的类型约束:

a、元素类型必须支持赋值运算

b、元素类型的对象必须可以复制

5、不要存储end操作返回的迭代器,添加或删除deque或vector容器内的元素都会导致存储的迭代器失效。

//2016-12-27

1、容器的insert操作为什么是在指定迭代器位置的前一个位置插入呢?

因为end迭代器指向的是容器末尾位置元素的下一个位置。

//2016-12-29

1、适配器:

容器适配器、迭代器适配器、函数适配器-----本质上适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。

例如:stack(栈)适配器可使任何一种顺序容器以栈的方式工作。

2、适配器与其关联的容器:

对于给定的适配器,其关联的容器必须满足一定的约束条件

stack栈可以建立在vector、list或者deque容器之上

queue适配器要求其关联的容器必须提供push_front运算,一次只能建立在list容器上

priority_queue适配器要求提供随机访问功能,因此可建立在vector和deque容器上,但不能建立在list容器上。

//2017-01-18

1、如何理解map关联容器的键类型约束:

可以定义map对象以vector<int>::iterator和pair<int,string>为键关联int型对象,不能定义map对象以list<int>::iterator为键关联int型对象。因为键类型必须支持<操作,而list容器的迭代器类型不支持<操作。

2、map迭代器进行解引用将产生pair类型的对象,并且pair对象的first成员存放键,为const,而second成员则存放值。

3、有别于vector和string类型,map下标操作符返回的类型与map迭代器进行解引用获得的类型不同。

//2017/1/21

1、set容器的一个特点:

set容器中的元素不能修改

2、map容器及set容器中count及find的区别:

count用来检查map对象中某键是否存在,若存在,并且想访问该键对应的元素,则需要使用下标操作,即实际上进行了两次下标操作。

find不仅可以查找map中是否有某个键,若有的话,可以直接获取该键对应元素的引用。

3、multimap和multiset中某个键对应的实例可能有多个,并且这些实例是顺序排放的。

4、与multimap和multiset容器对应的返回迭代器的关联容器操作:

m.lower_bound(k)返回一个迭代器指向键k对应的第一个实例

m.upper_bound(k)返回一个迭代器指向键k对应的最后一个实例的下个位置

而m.eqaul_range(k)返回的是迭代器的pair对象,first成员即lower_bound,second成员即upper_bound

//2017/3/13

1、泛型算法:标准库容器定义的操作非常少,没有给容器添加大量的功能函数,而是选择一组不依赖于特定的容器类型的算法,这些算法被称为“泛型”的。

2、泛型算法的头文件:algorithm,泛化的算术算法头文件:numeric。

3、算术泛型算法例子:accumulate(vec.begin(),vec.end(),n),返回n与vec的begin到end之间的元素之和,n也可以是string。

4、标准库的find_first_of查找算法,find_first_of(vec1.begin(),vec1.end(),vec2.begin(),vec2.end()),在vec1标记的范围内查找其元素是否在vec2标记的范围内出现。

5、标准库的写容器元素的算法:fill(vec.begin(),vec.end(),n),向vec迭代器标记范围内写入元素n。还有个fill_n算法:fill_n(vec.begin(),n,m):向vec里写入n个m,但是不确保vec的初始大小。

6、back_inserter算法,它是迭代器适配器,用法:iter = back_inserter(vec):它生成一个绑定在vec上的插入迭代器iter,在试图通过这个迭代器给元素赋值时,赋值运算将调用push_back在容器中添加一个具有指定值的元素。

7、写入到目标迭代器的算法:copy。

8、算法的_copy版本:replace算法:replace(ilst.begin(),ilst.end(),0,42)将ilist迭代器范围内的0改为42。而_copy版本的replace:replace_copy(ilst.begin(),ilst.end(),back_inster(ivec),0,42)则将ilst迭代器范围内的元素放到ivec中,并将ivec中的0全都改为42。

9、unique算法:iter=unique(vec.begin(),vec.end()),返回vec迭代器范围内无重复序列的下一位置迭代器。

10、sort算法的另一个版本:stable_sort:保留相等元素的原始相对位置,并且stable_sort有第三个参数,第三个参数是个自定义的实用函数(谓词)。

11、count_if算法:count_if(vec.begin(),vec.end(),GT6),其中GT6也是自定义的实用函数(谓词)返回bool值。

//2017/3/14

1、插入迭代器:back_inserter、front_inserter、inserter,其中inserter用法:inserter(ilst,it),ilst是list,it是迭代器,意思是在迭代器it之前插入元素。

//2017/3/15

1、世界上还有一种迭代器叫做:流迭代器:iostream迭代器:istream_iterator<T>in(strm); istream_iterator<T>in; ostream_iterator<T>in(strm); ostream_iterator<T>in(strm,delim)。

2、将文本文件和输入流绑定到一块:ifstream inFile(fileName.c_str()); istream_iterator<string>inIter(inFile)。

3、将文本文件和输出流绑定到一块:ofstream outFile("fileName");ostream_iterator<string>outIter(outFile," ")。

4、反向迭代器:rcomma是个反向迭代器,利用反向迭代器的成员函数base即可使其转变为普通迭代器:rcomma.base()。

//2017/3/16

1、简单说什么是类:类就是定义了一个新的类型和一个新的作用域。

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

3、在类内部定义!!!!!的函数默认为inline。

4、对于成员函数,在其形参列表后添加关键字const,就可以将成员函数声明为常量,const成员不能改变其所操作的对象的数据成员,const必须同时出现在声明和定义中。

5、类背后蕴涵的基本思想是数据抽象和封装。而数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。

6、举例说明数据抽象和封装:标准库类型vector具备数据抽象和封装的特性:在使用方面它是抽象的,只需考虑它的接口,即它能执行的操作。它又是封装的,因为我们既无法了解该类型如何表示的细节,也无法访问其任意的实现制品。另一方面:数组在概念上类似于vector,但既不是抽象的,也不是封装的。可以通过访问存放数组的内存来直接操纵数组。

//2017/4/5

1、类可以定义自己的局部类型名字:

public:

typedef std::string::size_type index;

private:

index cursor;

2、成员函数的重载规则:

成员函数只能重载本
类的其他成员函数。类的成员函数与普通的非成员函数以及在其他类中声明的函数不相关,也不能重载它们。重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。

3、inline成员函数注意的地方:

在类内定义!!!的函数,默认是inline类型。

在类内声明!!!的函数,如果声明为inline,则在类外定义该函数时,不用再次添加 inline关键字!

在类内声明!!!的函数,如果没有声明为inline,则可以在类外定义该函数时,若想让其变为inline函数,则需要添加inline关键字。

inline成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义内体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中。

4、类的前向声明——不完全类型:

不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

5、内存分配:

定义对象时,将为其分配存储空间,但(一般而言)定义类型时不进行存储分配。

//20117/4/6

1、成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为 this,与调用成员函数的对象绑定在一起。

2、什么时候使用this:

函数返回对调用该函数的对象的引用。


3、不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用。

4、基于const的重载:const对象只能调用const成员



5、可变数据成员:

可变数据成员(mutable data member) 永远都不能为 const,甚至当它是const 对象的成员时也如此。因此,const 成员函数可以改变 mutable 成员。

6、函数返回类型不一定在类作用于中:

与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。

7、类定义的两个阶段:

1. 首先,编译成员声明;
2. 只有在所有成员出现之后,才编译它们的定义本身。

8、编译器按照成员声明在类中出现的次序来处理它们。

9、两个函数的区别如果仅返回类型不同,则不能构成合法的函数重载。

10、构造函数的工作是保证每个对象的数据成员具有合适的初始值。

11、构造函数不能声明为 const。

//2017/4/7

1、构造函数初始化只在构造函数的定义中而不是声明中指定。

2、从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。

3、没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。

4、构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。 第一个成员首先被初始化, 然后是第二个, 依次类推。

5、分别接受一个 string 和接受一个 istream& 的构造函数,都具有默认实参则不合法。

6、一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。

7、每个构造函数应该为每个内置或复合类型的成员提供初始化式。没有初始化内置或复合类型成员的构造函数,将使那些成员处于未定义的状态。

8、通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。

9、为所有形参都提供了默认实参的构造函数也定义了默认构造函数,这样的构造函数形参列表中是有形参的。

10、可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。

11、例子:same_isbn是Sales_item类的成员函数,


下面是Sales_item的两个构造函数:


这里的每个构造函数都定义了一个隐式转换。 因此, 在期待一个 Sales_item类型对象的地方,可以使用一个 string 或一个 istream:

string null_book = "9-999-99999-9";

item.same_isbn(null_book);

item.same_isbn(cin);

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

explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它。

当构造函数被声明 explicit 时,编译器将不使用它作为转换操作符。

13、只要显式地按下面这样做,就可以用显式的构造函数来生成转换:

string null_book = "9-999-99999-9";

item.same_isbn(Sales_item(null_book));

14、解释一下接受一个 string 的 Sales_item 构造函数是否应该为 explicit。将构造函数设置为 explicit 的好处是什么?缺点是什么?

接受一个string的Sales_item构造函数应该为explicit。因为如果不将其声明为explicit,则编译器可以使用它进行隐式类别转换(将一个string对象转换为Sales_item对象),而这种行为是容易发生语义错误的。

将构造函数设置为explicit的好处是可以避免因隐式类型转换而带来的错误;缺点是当用户的确需要进行相应的类型转换时,不能依靠隐式类型转换,必须显示地创建临时对象。

//2017/4/18

1、直接初始化简单的非抽象类的数据成员,可以采用与初始化数组元素相同的方式初始化其成员:


说明:只有没有定义构造函数且全体数据成员均为public的类,才可以采用与初始化数组元素相同的方式初始化其成员。

2、友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类:

友元的声明以关键字 friend 开始。它只能出现在类定义的内部。

3、必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。

4、用友元引入的类名和函数(定义或声明),可以像预先声明的一样使用:


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

6、因为 static 成员不是任何对象的组成部分,所以 static 成员函数不能被声明为 const。毕竟,将成员函数声明为 const 就是承诺不会修改该函数所属的对象。 最后, static 成员函数也不能被声明为虚函数。

7、一般而言,类的 static 成员,像普通数据成员一样,不能在类的定义体中初始化。相反,static 数据成员通常在定义时才初始化。这个规则的一个例外是,只要初始化式是一个常量表达式,整型 conststatic 数据成员就可以在类的定义体中进行初始化。

8、static 数据成员的类型可以是该成员所属的类类型。非 static 成员被限定声明为其自身类对象的指针或引用:


static 数据成员可用作默认实参。非 static 数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用。使用非 static 数据成员作默认实参,将无法提供对象以获取该成员的值,因而是错误的。

//2017/4/19

1、每种类型还定义了创建该类型的对象时会发生什么——构造函数定义了该类类型对象的初始化。类型还能控制复制、赋值或撤销该类型的对象时会发生什么——类通过特殊的成员函数:复制构造函数、赋值操作符和析构函数来控制这些行为。

2、如果没有显式定义复制构造函数或赋值操作符,编译器(通常)会为我们定义。

3、复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用 const 修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。

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

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

6、复制构造函数可用于:
• 根据另一个同类型的对象显式或隐式初始化一个对象。
• 复制一个对象,将它作为实参传给一个函数。
• 从函数返回时复制一个对象。
• 初始化顺序容器中的元素。
• 根据元素初始化式列表初始化数组元素。

7、C++ 支持两种初始化形式:直接初始化和复制初始化。复制初始化使用 = 符号,而直接初始化将初始化式放在圆括号中。、

8、当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。

9、支持初始化的复制形式主要是为了与 C 的用法兼容。

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

11、有些类必须对复制对象时发生的事情加以控制。这样的类经常有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义复制构造函数。

12、复制构造函数的形参并不限制为 const,但必须是一个引用。

13、为了防止复制,类必须显式声明其复制构造函数为 private。如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。

14、然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。

15、声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。通过声明(但不定义)private 复制构造函数,可以禁止任何复制类类型对象的尝试:用户代码中复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。

16、大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到 this 指针。赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为 const 引用传递。赋值操作符也返回对同一类类型的引用。

17、可以使用合成复制构造函数的类通常也可以使用合成赋值操作符。

18、类何时需要定义赋值操作符?

一般而言,如果一个类需要定义复制构造函数,则该类也需要定义赋值操作符。具体而言,如果一个类中包含指针数据成员,或者在进行赋值操作时有一些特定工作要做,则该类通常需要定义赋值操作符。

19、动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。

20、容器中的元素总是按逆序撤销:首先撤销下标为 size() - 1 的元素,然后是下标为 size() - 2 的元素……直到最后撤销下标为 [0] 的元素。

21、如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则。这个规则常称为三法则,指的是如果需要析构函数,则需要所有这三个复制控制成员。

22、析构函数是个成员函数,它的名字是在类名字之前加上一个代字号(~),它没有返回值,没有形参。因为不能指定任何形参,所以不能重载析构函数。虽然可以为一个类定义多个构造函数,但只能提供一个析构函数,应用于类的所有对象。

23、撤销 Sales_item 类型的对象时,将运行这个什么也不做的析构函数,它执行完毕后,将运行合成析构函数以撤销类的成员。

//2017/4/20

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

2、大多数 C++ 类采用以下三种方法之一管理指针成员:

1). 指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。
2). 类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。
3). 类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。

3、定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数。

4、实现使用计数有两种经典策略,其中一种需要定义一个单独的具体类用以封闭使用计数和相关指针:




5、为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数、赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或值型行为。

6、什么是值类型:

所谓值类型,是指具有值语义的类,其特征为:对该对象进行复制时,会得到一个不同的新副本,对副本所做的改变不会影响原有对象。

//2017/4/21

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

2、可重载的操作符:

3、通过连接其他合法符号可以创建新的操作符。例如,定义一个 operator**以提供求幂运算是合法的。第十八章将介绍重载 new 和 delete。

4、重载操作符必须具有至少一个类类型或枚举类型(第 2.7 节)的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。

5、操作符的优先级(第 5.10.1 节)、结合性或操作数目不能改变。

6、有四个符号(+, -, * 和 &)既可作一元操作符又可作二元操作符,这些操作符有的在其中一种情况下可以重载,有的两种都可以,定义的是哪个操作符由操作数数目控制。除了函数调用操作符 operator() 之外,重载操作符时使用默认实参是非法的。

7、重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR(第 5.2 节)和逗号操作符(第 5.9 节)的操作数求值。在 && 和 ||的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。因此,重载 &&、|| 或逗号操作符不是一种好的做法。

8、大多数重载操作符可以定义为普通非成员函数或类的成员函数。

9、作为类成员的重载函数,其形参看起来比操作数数目少 1。作为成员函数的操作符有一个隐含的 this 形参,限定为第一个操作数。

10、一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员。

11、操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元。在这种情况下,操作符通常需要访问类的私有部分。

12、重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

13、• 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
• 像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做, 如果定义非成员复合赋值操作符, 不会出现编译错误。
• 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。
• 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

14、一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符。

15、当定义符合标准库 iostream 规范的输入或输出操作符的时候,必须使它成为非成员操作符,为什么需要这样做呢?
我们不能将该操作符定义为类的成员, 否则, 左操作数将只能是该类类型的对象:


这个用法与为其他类型定义的输出操作符的正常使用方式相反。

16、输入和输出操作符有如下区别:
输入操作符必须处理错误和文件结束的可能性。

//2017/4/24

1、算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时错误。

2、既定义了算术操作符又定义了相关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。

3、箭头操作符必须定义为类成员函数。解引用操作不要求定义为成员,但将它作为成员一般也是正确的。

4、C++ 语言不要求自增操作符或自减操作符一定作为类的成员,但是,因为这些操作符改变操作对象的状态,所以更倾向于将它们作为成员。

5、 后缀式操作符函数接受一个额外的 (即, 无用的) int 型形参。使用后缀式操作符进,编译器提供 0 作为这个形参的实参。

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

7、函数对象常用于覆盖算法使用的默认操作符。例如,sort 默认使用operator< 按升序对容器进行排序。为了按降序对容器进行排序,可以传递函数对象 greater。该类将产生一个调用操作符,调用基础对象的大于操作符。

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

9、标准库定义了两个绑定器适配器:bind1st 和 bind2nd。每个绑定器接受一个函数对象和一个值。

例子:

bind1st 将给定值绑定到二元函数对象的第一个实参,bind2nd 将给定值绑定到二元函数对象的第二个实参。例如,为了计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if传递值:


传给 count_if 的第三个实参使用 bind2nd 函数适配器,该适配器返回一个函数对象,该对象用 10 作右操作数应用 <= 操作符。这个 count_if 调用计算输入范围中小于或等于 10 的元素的个数。

//2017/4/25

1、转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。

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

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

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

5、标准转换优于类类型转换。

6、一般而言,函数调用的候选集只包括成员函数或非成员函数,不会两者都包括。而确定操作符的使用时,操作符的非成员和成员版本可能都是候选者。

7、继承和动态绑定与数据抽象一起成为面向对象编程(object-oriented programming) 的基础。

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

9、面向对象编程的关键思想是多态性(polymorphism)。

10、在 C++ 中,基类必须指出希望派生类重写哪些函数,定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

11、在 C++ 中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。

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

13、保留字virtual 的目的是启用动态绑定。成员默认为非虚函数,对非虚函数的调用在编译时确定。为了指明函数为虚函数,在其返回类型前面加上保留字 virtual。除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。

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

15、派生类对基类的 public 和 private 成员的访问权限与程序中任意其他部分一样:它可以访问 public 成员而不能访问 private 成员。

16、有时作为基类的类具有一些成员,它希望允许派生类访问但仍禁止其他用户访问这些成员。对于这样的成员应使用受保护的访问标号。protected 成员可以被派生类对象访问但不能被该类型的普通用户访问。

17、protect成员可以被类成员、友元和派生类成员访问。

18、可以认为 protected 访问标号是 private 和 public 的混合:
• 像 private 成员一样,protected 成员不能被类的用户访问。
• 像 public 成员一样,protected 成员可被该类的派生类访问。

另:派生类只能通过派生类对象访问其基类的 protected 成员,派生类对其基类类型对象的 protected 成员没有特殊访问权限。

例子:

假定 Bulk_item 定义了一个成员函数,接受一个 Bulk_item 对象的引用和一个 Item_base 对象的引用, 该函数可以访问自己对象的 protected 成员以及 Bulk_item 形参的protected 成员,但是,它不能访问 Item_base 形参的 protected 成员:


d.price 的使用正确, 因为是通过 Bulk_item 类型对象引用 price; b.price 的使用非法,因为对 Base_item 类型的对象没有特殊访问访问权限。

19、类设计与受保护成员:
如果没有继承,类只有两种用户:类本身的成员和该类的用户。将类划分为 private 和 public 访问级别反映了用户种类的这一分隔: 用户只能访问 public 接口,类成员和友元既可以访问 public 成员也可以访问 private 成员。
有了继承,就有了类的第三种用户:从类派生定义新类的程序员。派生类的提供者通常(但并不总是)需要访问(一般为 private 的)基类实现,为了允许这种访问而仍然禁止对实现的一般访问,提供了附加的protected 访问标号。类的 protected 部分仍然不能被一般程序访问,但可以被派生类访问。只有类本身和友元可以访问基类的 private 部分,派生类不能访问基类的 private 成员。
定义类充当基类时,将成员设计为 public 的标准并没有改变:仍然是接口函数应该为 public 而数据一般不应为 public。被继承的类必须决定实现的哪些部分声明为 protected 而哪些部分声明为 private。希望禁止派生类访问的成员应该设为 private,提供派生类实现所需操作或数据的成员应设为 protected。换句话说,提供给派生类型的接口是protected 成员和 public 成员的组合。

20、类派生列表可以指定多个基类。

21、如果想要继承基类的接口,则应该进行public 派生。

22、尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。

23、派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针):

例子:

Item_base 类可以定义返回 Item_base* 的虚函数,如果这样,Bulk_item 类中定义的实例可以定义为返回 Item_base* 或 Bulk_item*。

24、一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。

//2017/4/26

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

2、如果需要声明(但并不实现)一个派生类,则声明包含类名但不包含派生列表。

3、要触发动态绑定,满足两个条件:
第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。

4、可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象:

double print_total(const Item_base&, size_t);
Item_base item;
print_total(item, 10);

Item_base *p = &item;

Bulk_item bulk;

print_total(bulk, 10);

p = &bulk;

5、基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。

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

7、非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。item 的类型是 const Item_base 的引用,所以,无论在运行时 item 引用的实际对象是什么类型,调用该对象的非虚函数都将会调用 Item_base 中定义的版本。

8、希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这里可以使用作用域操作符:

Item_base *baseP = &derived;
double d = baseP->Item_base::net_price(42);
这段代码强制将 net_price 调用确定为 Item_base 中定义的版本,该调用将在编译时确定。

9、如果有用在给定调用中的默认实参值,该值将在编译时确定。如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。

10、接口继承:

public 派生类继承基类的接口,它具有与基类相同的接口。设计良好的类层次中,public 派生类的对象可以用在任何需要基类对象的地方。

11、实现继承:

使用 private 或 protected 派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。派生类在实现中使用被继承但继承基类的部分并未成为其接口的一部分。

12、定义一个类作为另一个类的公用派生类时,派生类应反映与基类的“是一种(Is A)”关系。

13、类型之间另一种常见的关系是称为“有一个(Has A)”的关系。

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

可以在 Derived 的 public部分增加一个 using 声明。

//2017/4/27

1、默认继承访问级别根据使用哪个保留字定义派生类也不相同。使用 class 保留字定义的派生默认具有 private 继承, 而用 struct 保留字定义的类默认具有 public 继承。

2、对于在基类中无法确定其行为的操作,应定义为纯虚函数。

//2017/5/11

1、友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

2、如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化。同样,可以使用派生类型的引用或对象初始化基类类型的引用。严格说来,对对象没有类似转换。编译器不会自动将派生类型对象转换为基类类型对象。但是,一般可以使用派生类型对象对基类对象进行赋值或初始化。

3、将对象传给希望接受引用的函数时,引用直接绑定到该对象,虽然看起来在传递对象,实际上实参是该对象的引用,对象本身未被复制,并且,转换不会在任何方面改变派生类型对象,该对象仍是派生类型对象。

4、将派生类对象传给希望接受基类类型对象(而不是引用)的函数时,情况完全不同。在这种情况下,形参的类型是固定的——在编译时和运行时形参都是基类类型对象。如果用派生类型对象调用这样的函数,则该派生类对象的基类部分被复制到形参。

5、3和4分別是派生类对象转换为基类类型引用,和用派生类对象对基类对象进行初始化或赋值,理解它们之间的区别很重要

6、对基类对象进行初始化或赋值,实际上是在调用函数:初始化时调用构造函数,赋值时调用赋值操作符。

7、如果知道从基类到派生类的转换是安全的,就可以使用static_cast(第 5.12.4 节)强制编译器进行转换。

//2017/5/18

1、构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。

2、重构:

重构包括重新定义类层次,将操作和/或数据从一个类移到另一个类。为了适应应用程序的需要而重新设计类以便增加新函数或处理其他改变时,最有可能需要进行重构。

3、尊重基类接口:

派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。

4、如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分。

5、赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。

6、析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。

7、在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。

8、将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。

9、什么情况下类应该具有虚析构函数:

作为基类使用的类应该具有虚析构函数,以保证在删除(指向动态分配对象的)基类指针时,根据指针实际指向的对象所属的类型运行适当的析构函数。

10、构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个
派生类对象。

11、局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须
与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。

//2017/5/19

1、C++ 中面向对象编程的一个颇具讽刺意味的地方是,不能使用对象支持面向对象编程,相反,必须使用指针或引用。:



通过 pointer 和 reference 进行的调用在运行时根据它们所绑定对象的动态类型而确定。但是,使用指针或引用会加重类用户的负担。

2、句柄类:

C++ 中一个通用的技术是定义包装(cover)类或句柄类。句柄类存储和管理基类指针。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以
指向派生类型对象。用户通过句柄类访问继承层次的操作。因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象的类型而变化。因
此,句柄的用户可以获得动态行为但无须操心指针的管理。

3、指针型句柄:

定义一个名为 Sales_item 的指针型句柄类,表示 Item_base 层次。Sales_item 的用户将像使用指针一样使用它:用户将Sales_item 绑定到 Item_base 类型的对象并使用 * 和 -> 操作符执行Item_base 的操作:


4、句柄类的使用计数策略(句柄类:Sales_item有两个数据成员,都是指针,分别是Item_base指针和使用计数指针):

5、对于派生类的返回类型必须与基类实例的返回类型完全匹配的要求,但有一个例外。这个例外支持像这个类这样的情况。如果虚函数的基类实例返回类类型的引用或指针, 则该虚函数的派生类实例可以返回基类实例返回的类型的派生类(或者是类类型的指针或引用)。

//2017/5/22

1、模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参跟在类型说明符之后声明,第 16.1.5 节将进一步介绍非类型形参。类型形参跟在关键字 class 或 typename 之后定义。

2、何时必须使用typename?

如果要在函数模板内部使用在类中定义的类型成员,必须在该成员名前加上关键字typename,以告知编译器将该成员当作类型。

//2017/5/23

1、标准 C++ 为编译模板代码定义了两种模型。在两种模型中,构造程序的方式很大程度上是相同的:类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。两种模型的不同在于,编译器怎样使用来自源文件的定义。如本书所述,所有编译器都支持第一种模型,称为“包含”模型,只有一些编译器支持第二种模型,“分别编译”模型。

2、包含编译模型:

在包含编译模型中,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条 #include 指示使定义可用, 该#include 引入了包含相关定义的源文件:



3、在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:
1). 普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。
2). 类模板或函数模板的友元声明,授予对友元所有实例的访问权。
3). 只授予对类模板或函数模板的特定实例的访问权的友元声明。

4、成员模板:

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

//2017/5/25

1、模板特化:

我们并不总是能够写出对所有可能被实例化的类型都最合适的模板。某些情况下,通用模板定义对于某个类型可能是完全错误的,通用模板定义也许不能编译或者做错误的事情;另外一些情况下,可以利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数。

2、函数模板特化:

模板特化(template specialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:

• 关键字 template 后面接一对空的尖括号(<>);
• 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;
• 函数形参表;
• 函数体。

3、定义非模板函数的时候,对实参应用常规转换;当特化模板的时候,对实参类型不应用转换。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例。

4、当声明了部分特化的时候, 编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。

5、部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。

6、函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。
当然,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性。

//2017/5/26

1、类成员的指针不同于指向普通数据或函数的指针,普通指针只根据对象或函数的类型而变化,而成员的指针还必须反映成员所属的类。

2、通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。

3、C++ 的异常处理中,需要由问题检测部分抛出一个对象给处理代码,通过这个对象的类型和内容,两个部分能够就出现了什么错误进行通信。

4、异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。

5、栈展开期间会经常执行析构函数。在执行析构函数的时候,已经引发了异常但还没有处理它。如果在这个过程中析构函数本身抛出新的异常,又会发生什么
呢?新的异常应该取代仍未处理的早先的异常吗?应该忽略析构函数中的异常吗?

答案是:在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常, 将会导致调用标准库 terminate 函数。一般而言, terminate
函数将调用 abort 函数,强制从整个程序非正常退出。

6、带有因继承而相关的类型的多个 catch 子句,必须从最低派生类类到最高派生类型排序。

7、



8、有可能单个 catch 不能完全处理一个异常。在进行了一些校正行动之后,catch 可能确定该异常必须由函数调用链中更上层的函数来处理, catch 可以通过重新抛出将异常传递函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个 throw:

throw;

空 throw 语句将重新抛出异常对象,它只能出现在 catch 或者从 catch调用的函数中。如果在处理代码不活动时碰到空 throw,就调用 terminate 函数。

9、除了为每个可能的异常提供特定 catch 子句之外,因为不可能知道可能被抛出的所有异常,所以可以使用捕获所有异常 catch 子句的。捕获所有异常的 catch 子句形式为(...)。

10、如果 catch(...) 与其他 catch 子句结合使用,它必须是最后一个,否则,任何跟在它后面的 catch 子句都将不能被匹配。

11、为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数 try块。可以使用函数测试块将一组 catch 子句与函数联成一个整体。作为例子,可以将第十六章的 Handle 构造函数包装在一个用来检测 new 中失败的测试块当中:


12、构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。

//2017/5/27

1、

2、通过定义一个类来封闭资源的分配和释放,可以保证正确释放资源。这一技术常称为“资源分配即初始化”,简称 RAII。

3、


4、当 auto_ptr 被复制或赋值的时候,有不寻常的行为,因此,不能将 auto_ptrs 存储在标准库容器类型中。

5、每个 auto_ptr 对象绑定到一个对象或者指向一个对象。当 auto_ptr 对象指向一个对象的时候,可以说它“拥有”该对象。当 auto_ptr 对象超出作用域或者另外撤销的时候,就自动回收 auto_ptr 所指向的动态分配对象。

6、auto_ptr 类是接受单个类型形参的模板,该类型指定 auto_ptr 可以绑定的对象的类型,因此,可以创建任何类型的 auto_ptrs。

7、auto_ptr对象的复制和赋值是破坏性操作

auto_ptr 和内置指针对待复制和赋值有非常关键的重要区别。当复制 auto_ptr 对象或者将它的值赋给其他 auto_ptr 对象的时候,将基础对象的所有权从原来的 auto_ptr 对象转给副本,原来的 auto_ptr 对象重置为未绑定状态。

8、应该只用 get 询问 auto_ptr 对象或者使用返回的指针值,不能用 get 作为创建其他 auto_ptr 对象的实参。

使用 get 成员初始化其他 auto_ptr 对象违反 auto_ptr 类设计原则:在任意时刻只有一个 auto_ptrs 对象保存给定指针, 如果两个 auto_ptrs 对象保存相同的指针,该指针就会被 delete 两次。

9、


//2017/5/31

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

2、异常说明跟在函数形参表之后。一个异常说明在关键字 throw 之后跟着一个(可能为空的)由圆括号括住的异常类型列表:
void recoup(int) throw(runtime_error);
这个声明指出,recoup 是接受 int 值的函数,并返回 void。如果 recoup抛出一个异常,该异常将是 runtime_error 对象,或者是由 runtime_error 派生的类型的异常。

3、不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。

4、如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数unexpected。默认情况下, unexpected 函数调用 terminate 函数, terminate 函数一般会终止程序。

5、空说明列表指出函数不抛出任何异常:
void no_problem() throw();

6、在 const 成员函数声明中,异常说明跟在 const 限定符之后。

7、基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。

8、在用另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同,但是,源指针的异常说明必须至少与目标指针的一样严格。

9、库倾向于定义许多全局名字——主要是模板名、类型名或函数名。在使用来自多个供应商的库编写应用程序的时候, 这些名字中有一些几乎不可避免地会发生冲突,这种名字冲突问题称为命名空间污染问题。

10、命名空间可以是未命名的, 未命名的命名空间在定义时没有给定名字。未命名的命名空间以关键字 namespace 开头, 接在关键字 namespace 后面的是由花括号定界的声明块。

11、未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。

12、未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同。

13、


14、除了在函数或其他作用域内部,头文件不应该包含using 指示或 using 声明。在其顶级作用域包含using 指示或 using 声明的头文件,具有将该名字注入包含该头文件的文件中的效果。头文件应该只定义作为其接口的一部分的名字,不要定义在其实现中使用的名字。

15、可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像namespace cplusplus_primer { /* ... */ };

这样的长命名空间名字,可以像下面这样与较短的同义词相关联:
namespace primer = cplusplus_primer;

16、一个命名空间可以有许多别名,所有别名以及原来的命名空间名字都可以互换使用。

17、像 using 声明一样,using 指示使我们能够使用命名空间名字的简写形式。与 using 声明不同,using 指示无法控制使得哪些名字可见——它们都是可见的。

18、using 指示以关键字 using 开头,后接关键字 namespace,再接命名空间名字。如果该名字不是已经定义的命名空间名字,就会出错。

19、

//2017/6/1

1、如果命名空间内部的函数是重载的,那么,该函数名字的 using 声明声明了所有具有该名字的函数。

2、如果 using 声明在已经有同名且带相同形参表的函数的作用域中引入函数,则 using 声明出错,否则,using 定义给定名字的另一重载实例,效果是增大候选函数集合。

3、using 指示将命名空间成员提升到外围作用域。如果命名空间函数与命名空间所在的作用域中声明的函数同名,就将命名空间成员加到重载集合中。

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

5、在单个基类情况下,派生类的指针或引用可以自动转换为基类的指针或引用,对于多重继承也是如此,派生类的指针或引用可以转换为其任意其类的指针或引用。

6、假定所有根基类都将它们的析构函数适当定义为虚函数,那么,无论通过哪种指针类型删除对象,虚析构函数的处理都是一致的。

7、虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。

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

9、即使基类是虚基类,也照常可以通过基类类型的指针或引用操纵派生类的对象。

//2017/6/2

1、对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。

2、C++ 提供下面两种方法分配和释放未构造的原始内存。
1)allocator 类, 它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2)标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始的、未类型化的内存。

3、C++ 还提供不同的方法在原始内存中构造和撤销对象。
1) allocator 类定义了名为 construct 和 destroy 的成员, 其操作正如它们的名字所指出的那样:construct 成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。
2) 定位 new 表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。
3) 可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。
4) 算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy算法一样执行,除了它们的目的地构造对象而不是给对象赋值之外。
现代 C++ 程序一般应该使用 allocator 类来分配内存,它更安全更灵活。但是,在构造对象的时候,用 new 表达式比allocator::construct 成员更灵活。有几种情况下必须使用
new。

4、allocator 类是一个模板,它提供类型化的内存分配以及对象构造与撤销。

5、allocator 类支持的操作:



6、当使用 new表达式// new expression
string * sp = new string("initialized");的时候,实际上发生三个步骤。首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。
当使用 delete 表达式delete sp;删除动态分配对象的时候,发生两个步骤。首先,对 sp 指向的对象运行适
当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。

7、allocate 成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换(第 5.12.4 节)。类似地,deallocate 释放特定类型的内存,也不必转换为 void*。

8、类似于 construct 成员,有第三种 new 表达式,称为定位 new。定位 new表达式在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位 new 表达式使我们能够在特定的、预分配的内存地址构造一个对象。

//2017/6/5

1、编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

2、优化 new 和 delete 的行为的时候,只需要定义 operator new 和operator delete 的新版本,new 和 delete 表达式自己照管对象的构造和撤销。

3、通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。

4、通过下面两个操作符提供 RTTI:
1). typeid 操作符,返回指针或引用所指对象的实际类型。
2). dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

5、可以使用 dynamic_cast 操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。与 dynamic_cast 一起使用的指针必须是有效的——它必须为 0 或者指向一个对象。

6、为 RTTI 提供的第二个操作符是 typeid 操作符。typeid 操作符使程序能够问一个表达式:你是什么类型?

7、如果表达式的类型是类类型且该类包含一个或多个虚函数,则表达式的动态类型可能不同于它的静态编译时类型。例如,如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型;但是,如果指针实际指向派生类对象,则 typeid 操作符将说表达式的类型是派生类型。

8、如果操作数不是类类型或者是没有虚函数的类,则 typeid 操作符指出操作数的静态类型;如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型。

9、typeid 操作符的结果是名为 type_info 的标准库类型的对象引用,第18.2.4 节将更详细地讨论这个类型。要使用 type_info 类,必须包含库头文件typeinfo。

10、type_info 类的确切定义随编译器而变化,但是,标准保证所有的实现将至少提供表 18.2 列出的操作。



11、Screen 类的 contents 成员的类型为 std::string。contents 的完全类型是“Screen 类的成员, 其类型是 std::string”。 因此, 可以指向 contents 的指针的完全类型是“指向 std::string 类型的 Screen 类成员的指针”,这个类型可写为
string Screen::*
可以将指向 Screen 类的 string 成员的指针定义为
string Screen::*ps_Screen;

可以用 contents 的地址初始化 ps_Screen,代码为string Screen::*ps_Screen = &Screen::contents;

12、通过指定函数返回类型、形参表和类来定义成员函数的指针。例如,可引用不接受形参的 get 版本的 Screen 成员函数的指针具有如下类型:
char (Screen::*)() const
这个类型指定 Screen 类的 const 成员函数的指针,不接受形参并返回char 类型的值。

13、

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值