C Primer Plus随笔

1.C99遵循C++的规范,可以将声明放在代码的任何位置;C99之前的编译器大多不支援此项

2.声明为变量分配内存,而赋值则是在内存中存值

3.函数原型不同于声明,声明省略参数的描述

4.整数和浮点数的存储不同,浮点数将分为小数部分和指数部分分别存储

5.浮点数往往只是实际值的近似,并不能精确表示

6.浮点数的运算比整数慢

7.%#x == 0x%x 十六进制;%#o == 0%o 八进制

8.溢出后可能会回到该类型的起始点~仅仅是可能性比较大

9.需要在转义序列和ASCII码间做选择时,尽量选择转义序列,方便记忆且更易移植

10.printf决定数据的显示方式而不是数据的存储方式

11.uint32_t等不是关键字,需要包含头文件inttypes.h

12.最小长度类型 int_least8_t/最小最快长度类型 int_fast8_t/最大的可能整数类型 intmax_t/uintmax_t

13.11&12在inttypes.h有包含对应的宏来表示打印格式

14.float至少6位有效数字,double至少10位有效数字

15.浮点数要么有小数点,要么有E或e

16.16进制浮点表示法,0xd.1fp10,d是10,1f是1/16加上15/256,p10是2的10次方(1024),十进制即为10364

17.将float型变量作为参数传入那些未在原型中显示说明参数类型的函数,如printf,即会被转换为double类型

18.float上溢无穷大,下溢损失精度

19.C规定char的长度为1个字节,所以在char为16位,double为64位的系统上,char是1字节,而double则是4字节

20.刷新缓冲区:缓冲区满,换行符,以及需要输入的时候(或者fflush)

21.scanf遇到空格(blank)、制表符(table)或者换行符(newline) 停止读取输入

22.用printf打印%:连续两个%即可

23.如果printf()语句在其他地方出现不匹配的错误,即使正确的说明符也会产生错误的结果

24.scanf对字符数组不需要使用&,因其本身即是指针,如scanf("%s", char1);,其中char1是char[]

25.除了在%c模式(它读取下一个字符)外,在读取输入时,scanf()会跳过空白字符直到第一个非空白字符处

26.模运算的正负与第一个操作数的正负有关

27.printf获取将要打印的值时,可能从最后一个开始算起

28.自增/自减运算符的规则:

如果一个变量出现在同一个函数中的多个参数中时,不要将增量或减量用于它上面

如果一个变量多次出现在一个表达式里,不要将增量或减量用于它上面

29.在浮点运算中,使用关系运算符只能是<和>,浮点运算会有精度和舍入误差的问题,造成逻辑上的不相等,另外可辅以fabs()绝对值函数来做判断

30.习惯在关系表达式中将常量放在前面

31.逗号运算符保证最左边的表达式最先计算,但其值是其最右表达式的值

32.复合赋值运算符同普通的赋值运算符优先级一样

33.关于字符类型的一些函数可参考ctype.h

34.逻辑运算符是程序的分界点,所有的副作用都会生效

35.逻辑表达式中,一旦某个部分使整个表达式无意义或能判断出结果,立即停止

36.switch后必须是整型值(能代表“整数或可转为整数的字符”的整型变量),case后不可接变量(只能接常量或者常量表达式)

37.相较于if,switch稍快,但if的适用范围更广一点

38.不建议使用goto语句:

goto label;

label:语句

39.break+3种循环/switch

continue+3种循环,不可接switch

40.如果混合使用scanf()和getchar()函数,那么当调用getchar()之前使用scanf()恰好在输入中留下一个换行符时,将会产生问题

41.递归函数多次调用会分配大量内存,可能造成系统崩溃

42.make编译多个文件gcc file1.c file2.c

43.未初始化的数组中数组元素为无用的数值,不清楚具体为何;但如果部分初始化数组,则未被初始化的元素被置为0

44.只有在函数原型或函数定义头中,指针和数组可以互换

45.当创建一个指针时,系统只分配了存储指针本身的内存空间,并不分配存储数据的内存空间。因此,在使用一个指针之前,必须给它赋予一个已分配的内存地址。

可以取一个已存在的变量的地址,或者使用malloc来分配内存

46.指向多维数组的指针每次做偏移的时候,移动的单位是都是内层数组的大小,或说是内层一个元素数组的大小

47.指向二维数组的指针的声明方法:int (* p) [6];

48.把非const指针赋值给const指针是允许的,但只能进行一层间接运算(本人理解是除直接赋值外不能进行任何运算)

49.运用puts和gets来显示或获取字符串

50.数组初始化是从静态存储区把一个字符串复制给数组,而指针初始化只是复制字符串地址

51.如果一个字符串在ASCII码中的顺序落后于第二个字符串,strcmp()返回一个正数,反之则是负数

52.strcpy()和赋值操作一样,目标字符串在左边,源字符串在右边

53.声明一个数组将为数据分配存储空间;而声明一个指针只为一个地址分配存储空间

54.strncpy()如果最后的个数参数小于源字符串的大小,则不会填充空字符来结尾

55.strncat(s1,s2,n)s2的前n个字符被复制到s1指向的字符串的结尾,复制过来的s2字符串的第一个字符覆盖了s1字符串结尾的空字符;s2字符串中的空字符及其之后的任何字符都不会被复制,并且追加一个空字符到所得结果后面

56.不同于自动变量,外部变量会被自动赋初值,且只可以用常量表达式来初始化文件作用域变量

57.不可以在一个声明中使用一个以上的存储类说明符,这也就意味着不能将其他任意一个存储类说明符作为typedef的一部分

58.除非函数使用了static关键字,否则默认它是extern的

59.malloc前面的类型指派在C中是可选的,但是在C++中一定要有

60.理论上,程序结束后所有的内存都会被自动释放,但最好是使用free来主动释放它们

61.类型限定词新属性“幂等性”:const const int size == const int size

62.一个位于*左边任意位置的const使得数据成为常量,而一个位于*右边的const则使得指针成为常量

63.自动内存、静态内存和分配内存的差异

64.由retrict限定的指针被认为是提供了对其所指向的数据块的唯一访问途径

65.fgets和gets的不同之处,后者读取换行符之后将其丢弃

对应的,fputs和puts不同,fputs函数打印的时候并不添加一个换行符

66.fgetc和getc的最大区别在于前者是函数,后者是宏,getc通过宏实现,调用的时候注意参数stream不能是有副作用的表达式,有副作用的表达式,指的是表达式执行后,会改变表达式中某些变量的值,如++i等

67.ftell和fseek适用于二进制模式打开的文件,若用于文本模式打开的文件,则可能出现偏差(跨操作系统更易出错),个人理解ftell及fseek的使用都需先验证过,因不同的操作系统会导致很大的差异,使用时需注意

68.read和write无缓存,而fread和fwrite带缓存

69.一个由C/C++编译的程序占用的内存分为以下几个部分:
‘1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数名,局部变量的名等。其操作方式类似于数据结构中的栈。
’2、堆区(heap)— 由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
‘3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
’4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。
‘5、程序代码区 — 存放函数体的二进制代码。

70.如果初始化一个具有静态存储时期(比如静态外部链接、静态内部链接、静态空链接)的变量,只能使用常量值;而如果存储时期是自动的,则初始化的值则不必是常量。此条规则同样适用于struct。

71.注意复合文字(数组和结构体)的使用

72.伸缩型数组成员

必须是最后一个数组成员

结构体中必须至少有一个其他成员

像普通数组一样声明,但不要指定维数

通过malloc实现

示例如下:

struct A{

int count;

int num[];

};

struct A *a = malloc(sizeof(struct A)+n*sizeof(int));

73.C的某些枚举属性不能延伸至C++中,例如,C允许对枚举变量使用运算符++,而C++不允许

74.与数组不同,结构体名称不是结构体的地址

75.可以用while(strchr("ulton", ans) == NULL)来代替while(ans != 'u' &&ans != 'l' &&ans != 't' &&ans != 'o' &&ans != 'n')这样的写法

76.typedef与#define很相似,但有三点不同:

1)与#define不同,typedef给出的符号名称仅限于类型,而不是对值

2)typedef的解释由编译器,而不是由预处理器执行

3)虽然它的范围有限,但在其受限范围内,typedef比#define更灵活

77.为了避免信息漏过边界,位掩码至少应该与其所屏蔽的值具有相同的宽度

78.需要注意宏定义的使用及其与函数的差异:

1.宏的名字中不能有空格,但是在替代字符串中可以使用空格

2.用圆括号括住每个参数,并括住宏的整体定义,且不要使用自增或自减运算符

3.用大写字母表示宏函数名(宏常量也用大写字母表示),以提醒程序员宏可能产生的副作用

4.如果打算使用宏代替函数加快程序的运行速度,那么首先应确定宏是否会引起重大差异

5.在程序中只使用一次的宏对程序的运行时间可能不会产生明显的改善,在嵌套循环中使用宏更有助于提高速度

6.宏尽量用于简单的功能

需要注意头文件的使用:

1.常量定义;2.宏函数;3.函数声明;4.结构模板定义;5.类型定义;6.多个文件共享的外部变量;7.文件作用域、内部链接、const限定的变量或数组

79.内联函数一般是比较简单的,一般无法获得内联函数的地址,且内联函数不会在调试器中显示

80.编译器在优化内联函数时,必须知道函数定义的内容。这意为着内联函数的定义和调用必须在同一文件中,正因为这样,内联函数一般具有内部链接。所以,一般会选择在头文件中定义内联函数。

81.与C++不同的是,C允许混合使用内联函数定义和外部函数定义(甚至是在同一文件中,无法确认具体调用),不建议使用

82.atexit()的调用顺序是后进先出;另外main函数退出的时候会隐式的调用exit();而在另外函数中显示调用exit()也会终止掉程序

83.qsort比较函数中的void *指针需强制转换成所需要的类型后再进行比较

84.memcpy要求确保没有重叠区域,而memmove则不做此要求

85.可变宏示例:

#define PR(...) printf(__VA_ARGS__)

可变函数:

va_list, va_start(ap, lim), va_arg(ap, double), va_end(ap), va_copy(apcopy, ap)

其中ap为va_list变量,lim为parmN,由它来指定可变参数列表中的参数个数,double为数据类型,apcopy同为va_list变量

86.sizeof用于表达式的时候,并不去计算表达式的值,例如,sizeof *p,即使p是一个无用的地址,操作一样可以成功,因为不需要对p执行解引用

87.对数组执行sizeof得到的是数组元素的大小乘以元素的个数

对引用执行sizeof则返回引用的对象的大小

88.C++:删除0值的指针是安全的;一旦用delete删除了指针所指的对象(指针成为悬垂指针),立即将指针置为0,这样就非常清楚地表明了指针不再指向任何对象

89.C++:指向任意数据类型的指针都可转换为void*类型;整形数值常量0可以转换为任意类型指针

90.强制转换操作符:dynamic_cast, const_cast, static_cast, reinterpret_cast

dynamic_cast:支持运行时识别指针或引用所指向的对象

const_cast:转换掉表达式的const性质

static_cast:常用的强制类型转换,支援所有的隐式转换

reinterpret_cast:通常为操作数的位模式提供较低层次的重新解释

实际使用中尽量避免强制类型转换

91.对于switch结构,只能在它的最后一个case标号或default标号后面定义变量,以避免代码跳过变量的定义或初始化

例外,采用{}形成语句块即可定义变量

92.goto语句和获得所转移的控制权的带标号的语句必须位于同一函数内

93.在C语言中,具有const的形参与非const形参的函数并无区别

94.在传参的时候要注意const等修饰符:例如,const指针不能传给非const形参

95.如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const类型

96.如形参是非const引用,则不能用如下参数做实参:

const类型,需要进行类型转换的变量,常量,表达式,等等,它需要与完全同类型的非const对象关联

所以尽量定义为const引用

97.通过数组传递参数时,形参中数组的长度并没有作用,不会约束实参的大小

而通过数组引用传递参数时,此时传递的是整个数组,形参大小必须与实参大小匹配

98.传递给函数的数组的用法:

1.传上下限指针

2.显示传递数组大小的形参

3.在数组内添加标记表示数组的结束(如字符串)

99.C++省略符形参有以下两种形式:

void foo(parm_list, ...);  其中parm_list一般用来提供其他实参的类型和个数等信息

void foo(...);

100.返回类型为void的函数可以返回另一个返回类型同样是void的函数

返回类型不是void的函数必须返回一个值(例外:主函数main可以无返回值,隐式返回0)

101.千万不要返回局部对象的引用;千万不要返回指向局部对象的指针

102.返回引用的函数返回一个左值,可对其赋值

103.如果一个形参具有默认实参,那么它后面的形参都必须有默认实参

排列形参时是最少使用默认实参的参数放在前面,最可能使用默认实参的参数放在最后面

104.大部分编译器不支援递归函数的内联

105.内联函数应尽量在头文件中定义,以让编译器知道其定义;编译器隐式地将定义在类内的成员函数当作内联函数

106.类的每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起--this

107.const成员函数--常量成员函数,不可以修改对象的数据成员

108.const对象、指向const对象的指针或引用只能用于调用其他const成员函数,如果尝试使用它们调用非const成员函数,则是错误的

109.在类的外面定义成员函数必须指明它们是类的成员

110.如果没有为一个类显示定义任何构造函数,编译器将自动为这个类生成默认构造函数

111.合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员

112.函数不能仅仅基于返回值类型的不同而实现重载

形参与const形参的等价性仅适用于非引用形参,引用形参及指针形参则是有区别的

113.每一个版本的重载函数都应该在同一个作用域中声明

114.函数重载确定:

1)其每个实参的匹配都不劣于其他可行函数需要的匹配

2)至少有一个实参的匹配优于其他可行函数提供的匹配

否则会具有二义性,该调用错误

115.实参类型转换,转换等级以降序排列如下:

1)精确匹配

2)通过类型提升实现的匹配

3)通过标准转换实现的匹配

4)通过类类型转换实现的匹配

以上都是针对实参而言

116.仅当形参是引用或指针时,形参是否为const才有影响

将非const引用或指针传递给对应的const引用或指针时需经过转换,进而会导致两种函数的不同

但注意不能基于指针本身是否为const来实现函数的重载(原理同普通变量)

117.返回函数指针的函数最好用typedef定义

118.允许将形参定义为函数类型,但函数的返回类型必须是指向函数的指针,而不能是函数;具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针,但是当返回的是函数时,同样的转换操作则无法实现

示例:

int (int, int)

int (*)(int, int)

使用函数指针指向重载的函数时,指针的类型必须与重载函数的一个版本精确匹配


119.标准库类型不允许做复制或赋值动作(IO对象不可复制或赋值)

只有支持复制的元素类型可以存储在vector或其他容器类型中

形参或返回类型也不能为流类型

如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用

一般情况下,如果要传递IO对象以便对它进行读写,可用非const引用的方式传递这个流对象

对IO对象的对象会改变它的状态,因此引用必须是非const的


120.流的状态由bad、fail、eof和good操作揭示。如果bad、fail或者eof的任意一个为true,则检查流本身将显示该流处于错误状态。类似地,如果这三个条件没有一个为true,则good操作将返回true。


121.输出缓冲区的刷新:

程序正常结束

缓冲区已满,下次写即会刷新

操作符ends, endl, flush显示刷新

unitbuf/nounitbuf设置流的内部状态,从而清空缓冲区

将输出流与输入流关联起来,如此,读取输入时即会刷新其关联的输出缓冲区


122.如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除该流的状态


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

文件流构造函数和open函数都提供了默认实参设置文件模式

从效果来看,为ofstream对象指定out模式等效于同时指定了out和trunc模式,对于用ofstream打开的文件,要保存文件中已存在的数据,唯一方法是显示地指定app模式打开


124.模式是文件的属性而不是流的属性

默认情况下,fstream对象以in和out模式同时打开。当文件同时以in和out打开时不清空,若只使用out模式,则文件会清空已存在的数据


125.stringstream对象不能使用open和close函数,而fstream对象则不允许使用str


126.stringstream提供转换和格式化,使用输入操作符时,空白符或换行符等将会被忽略


127.泛型:这些算法可作用于各种不同的容器类型;而这些容器又可以容纳各种不同类型的元素


128.所有的容器都定义了默认构造函数。为了使程序更清晰、简短,容器类型最常用的构造函数是默认构造函数。在大多数的程序中,使用默认构造函数能达到最佳运行时性能,并且使容器更容易使用


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


130.容器元素类型必须满足下面两个约束:

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

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

引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器,其他的内置或复合类型则都可用做元素类型

除输入输出标准库类型之外,所有其他标准库类型都是有效的容器元素类型。特别地,容器本身也满足上述要求,因此,可以定义元素本身就是容器类型的容器。


131.在容器嵌套定义中,必须用空格隔开两个相邻的>符号,以示这是两个分开的符号,否则,系统会认为>>是单个符号,为右移操作符,并导致编译时错误


132.vector和deque的迭代器支持算术运算和关系运算,而list容器的迭代器则不支援


133.任何insert或push操作都可能导致迭代器失效。当编写循环将元素插入到vector或deque容器中时,程序必须确保迭代器在每次循环后都得到更新。


134.所有容器都通过比较其元素对来实现关系运算


135.resize操作可能会使迭代器失效。在vector或deque容器上做resize操作有可能会使其所有的迭代器都失效

对于所有的容器类型,如果resize操作压缩了容器,则指向已删除的元素的迭代器失效


136.vector容器不支持pop_front操作


137.erase、pop_front和pop_back函数使指向被删除元素的所有迭代器失效。对于vector容器,指向删除点后面的元素的迭代器通常也会失效。而对于deque容器,如果删除时不包括第一个元素或最后一个元素,那么该deque容器相关的所有迭代器都会失效。


138.赋值和assign操作使左操作数容器的所有迭代器失效。swap操作则不会使迭代器失效。

完成swap操作后,尽管被交换的元素已经存放在另一容器中,但迭代器仍然指向相同的元素。

swap操作:操作数必须是相同类型的容器,而且所存储的元素类型也必须相同。


139.为了使vector容器实现快速的内存分配,其实际分配的容量要比当前所需的空间多一些


140.vector的每种实现都可自由地选择自己的内存分配策略。然而,它们都必须提供reserve和capacity函数,而且必须是到必要时才分配新的内存空间。分配多少内存取决于其实现方式。不同的库采用不用的测路额实现。


141.元素是否连续存储还会显著的影响:

在容器的中间位置添加或删除元素的代价

执行容器元素的随机访问的代价


142.一般来说,除非找到选择使用其他容器的更好理由,否则vector容器都是最佳选择。


143.对于键类型,唯一的约束就是必须支持<操作符,至于是否支持其他的关系或相等运算,则不作要求


144.关联容器中map的类型是指key-value对的类型,而set则只是单纯的键的集合,value_type与key_type相同


145.multimap以及multiset中键都不是唯一的,因此每次调用insert总会添加一个元素

参数不同会导致删除的元素性质不同,带有一个键参数的erase版本将删除拥有该键的所有元素,并返回删除元素的个数,而带有一个或一对迭代器参数的版本则只删除指定的元素,并返回void


146multimap以及multiset中,如果某个键对应多个实例,则这些实例在容器中将相邻存放


147.在关联容器中,*解引用操作符返回的是value_type的值,对于map则是key-value;而[ ]作用符则不同,返回的mapped_type的值,及key所对应的value


148.算法永不执行容器提供的操作,只是依赖迭代器和迭代器的操作实现

算法不直接修改容器的大小。如果需要添加或删除元素,则必须使用容器操作


149.插入迭代器front_inserter(迭代器适配器)的使用将导致元素以相反的次序出现在目标对象中

例:front_inserter(ilist);


150.ostream_iterator对象必须与特定的流绑定在一起;istream_iterator则可在创建时不提供实参,此时该迭代器指向超出末端位置


151.流迭代器的限制:

1‘不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中

2’一旦给ostream_iterator对象赋了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator对象中每个不同的值都只能正好输出一次

3‘ostream_iterator没有->操作符


152.对于反向迭代器reverse_iterator,++运算将访问前一个元素,而--运算则访问下一个元素

流迭代器不能创建反向迭代器


153.反向迭代器用于表示范围,而所表示的范围是不对称的[ ),这个事实可推导出一个重要的结论:使用普通的迭代器对反向迭代器进行初始化或赋值时,所得到的迭代器并不是指向原迭代器指向的元素(详见图11-2)


154.五种迭代器:

Input Iterator: 只读向前遍历的迭代器。例如:istream
Output Iterator: 只写向前遍历的迭代器。例如:ostream, inserter
Forward Iterator: 可读可写向前遍历的迭代器。泛型算法replace需要至少是前向迭代器
Bidirectional Iterator: 可读可写双向遍历迭代器。例如:list, set, multiset, map, multimap
Random Access Iterator: 可读可写随机访问迭代器。例如:vector, deque, string, array

尽管map和set类型提供双向迭代器,但关联容器只能使用算法的一个子集。问题在于:关联容器的键是const对象。因此,关联容器不能使用任何写序列元素的算法。只能使用与关联容器绑在一起的迭代器来提供用于读操作的实参

向算法传递迭代器时需注意,要传递功能达到算法要求的才行


155.区别带有一个值或以个谓词函数参数的算法版本 _if

区别是否实现复制的算法版本 _copy


156.与对应的泛型算法不同,list容器特有的操作能添加和删除元素(merge, remove, reverse, sort, splice, unique)


157.类--实现与接口分离


158.在类内部定义的函数默认为inline


159.类的成员函数都有一个附加的隐含实参this


160.const成员不能改变其所操作对象的数据成员


161.可以在任意的访问标号出现之前定义类成员,如果类是struct关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用class关键字定义的,则这些成员是私有的。


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


163.因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用(类的前向声明一般用来编写相互依赖的类)


164.尽管在成员函数内部显示引用this通常是不必要的,但有一种情况下必须这样做:当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。

最常见的情况是在这样的函数中使用this:该函数返回对调用该函数的对象的引用


165.不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。非const成员函数中,this是一个指向类类型的const指针;而在const成员函数中,this的类型是一个指向const类类型对象的const指针。


166.同时基于const对象或非const对象的成员函数可以以重载实现。


167.可变数据成员mutable永远都不能成为const,它可以在const成员函数中被改变。


168.尽管在类中同名的全局对象被屏蔽了,但通过用全局作用域确定操作符来限定名字,仍然可以使用它。


169.不论成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。


170.没有默认构造函数的类类型的成员,以及const或引用类型(无论是哪种类型)的成员,都必须在构造函数初始化列表中进行初始化。因为const对象或引用类型的对象可以初始化,但不能对它们赋值。


171.构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。按照与成员声明一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。


172.只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。


173.类通常应定义一个默认构造函数,而在默认构造函数中给成员提供的初始值应该指出该对象是“空”的


174."A myobj;"等价于“A myobj = A()”定义了类的对象,但“A myobj()”则是定义了类的一个函数


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


176.explicit关键字可抑制由构造函数定义的隐式转换。explicit关键字只能用于类内部的构造函数声明上,在类的定义体外部所做的定义上不再重复它。

通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显示地构造对象。


177.类成员的显式初始化要求类的全体数据成员都是public。


178.友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或是整个类。将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非公有成员。当我们将成员函数声明为友元时,函数名必须用该函数所属的类名字加以限定。


179.当我们在类的外部定义static成员时,无须重复指定static保留字,该保留字只出现在类定义体内部的声明处。


180.static函数没有this指针

static数据成员必须在类定义体的外部定义(正好一次)。static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。

static成员函数不能被声明为const,毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。但static数据成员可以为const,并且可以在类的定义体中进行初始化。const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外定义(不必再指定初始值)。最后,static成员函数也不能被声明为虚函数。


181.static成员不是类对象的组成部分

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

static数据成员可用作默认实参;非static数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用。


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

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


183.不管类是否定义了自己的析构函数,编译器都自动执行类中非static数据成员的析构函数。


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


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

如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。

然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义,这样就禁止了任何复制类类型对象的尝试:用户代码中的复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。


186.当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。


187.析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行。


188.即使对象赋值给自己,赋值操作符的正确工作也非常重要。保证这个行为的通用方法是显示检查对自身的赋值。

赋值操作符通常要做复制构造函数和析构函数也要完成的工作。在这种情况下,通用工作应放在private实用函数中。


189.重载操作符必须具有至少一个类类型或枚举类型的操作数。


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

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


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


192.IO操作符重载必须为非成员函数。

一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应改输出换行符,这些应该由用户来控制。

输入操作符必须处理错误和文件结束的可能性。


193.类赋值操作符必须是类的成员,以便编译器可以知道是否需要合成一个。


194.算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时错误。既定义了算术操作符又定义了相关复合操作符的类,一般应使用复合赋值实现算术操作符。


195.赋值必须返回对*this的引用。

一般而言,赋值操作符与复合赋值操作符应返回左操作数的引用。


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

下标操作符能用作赋值的左右操作数,一般返回引用。类定义下标操作符时,一般需要定义两个版本:一个为非const成员并返回引用,另一个为const成员并返回const引用。


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


198.为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用。

为了与内置操作符一致,后缀式操作符应返回旧值(即,尚未自增或自减的值),并且,应作为值返回,而不是返回引用。


199.函数对象的函数适配器:绑定器,求反器。


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

它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型:

operator type();这里的type表示内置类型名、类类型名或由类型别名所定义的名字。


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

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


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

即只能进行一次类类型转换,不能同时进行一次以上的转换。


203.如果两个转换操作符都可用在一个调用中,而且在转换函数之后存在标准转换,则根据该标准转换的类别选择最佳匹配。


204.避免二义性最好的方法是避免编写互相提供隐式转换的成对的类,同时保证最多只有一种途径将一个类型转换为另一类型。


205.如果重载集中的两个函数可以用同一转换函数匹配,则使用在转换之后或之前的标准转换序列的等级来确定哪个函数具有最佳匹配。

否则,如果可以使用不同转换操作,则认为这两个转换是一样好的匹配,不管可能需要或不需要的标准转换的等级如何。

只有两个转换序列使用同一转换操作时,才用类类型转换之后的标准转换序列作为选择标准。


206.如果类既定义了转换操作符又定义了重载操作符,容易产生二义性。下面几条经验规则会有所帮助:

(1)不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数,不要再为类Bar定义到类型Foo的转换操作符。

(2)避免到内置算术类型的转换。具体而言,如果定义了到算术类型的转换,则

不要定义接受算术类型的操作符的重载版本。如果用户需要使用这些操作符,转换操作符将转换你所定义的类型的对象,然后可以使用内置操作符。

不要定义转换到一个以上算术类型的转换。让标准转换提供到其他算术类型的转换。

最简单的规则是:对于那些“明显正确”的,应避免定义转换函数并限制非显示构造函数。


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


208.用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。


209.为了指明函数为虚函数,在其返回类型前面加上保留字virtual。除了构造函数以外,任意非static成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义外部出现的函数定义上。


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

只有通过引用或指针调用,虚函数才在运行时确定。


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

派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。


212.用作基类的类必须是已定义的。


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


214.只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。


215.派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。


216.如果一个虚函数的调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。


217.每个类控制它所定义的成员的访问。派生类可以进一步限制但不能放松对所继承的成员的访问。


218.使用class保留字定义的派生类默认具有private继承,而用struct保留字定义的类默认具有public继承。


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


220.要确定到基类的转换是否可访问,可以考虑基类的public成员是否可访问,如果可以,转换是可访问的,否则,转换是不可访问的。


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

派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。


222.派生类只能初始化直接基类。


223.派生类析构函数不负责撤销基类对象的成员。编译器总是显示调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。


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

无论由构造函数或析构函数直接调用虚函数,或者从构造函数或析构函数所调用的函数间接调用虚函数,都应用这种绑定。


225.在继承情况下,派生类的作用于嵌套在基类作用域中。


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


227.将函数定义为纯虚能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。

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


228.类型形参由关键字class或typename后接说明符构成。用作模板形参的名字不能在模板内部重用。这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次。


229.通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当作类型。


230.在函数模板内部完成的操作限制了可用于实例化该函数的类型。

程序员的责任是,保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用那些操作的环境中那些操作运行正常。

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


231.模板实参推断:

多个类型形参的实参必须完全匹配

类型形参的实参的受限转换:

const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型和实参都忽略const,即,无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化。

数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。


232.用模板形参定义的函数形参的实参允许进行常规转换


233.类模板的成员函数只有为程序所用才进行实例化。如果某函数从未使用,则不会实例化该成员函数。


234.类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。因此指针成员变量在类对象创建时不会实例化,而是只有在对其进行操作时,才会实例化。


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


236.声明依赖性:当授予对给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板或函数模板的声明。实质上,编译器将友元声明也当作类或函数的声明对待。但如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。所以,要么直接以模板形参的方式声明友元,要么提前声明使用的类或函数是一个模板。


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


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


239.对具有同一模板实参集的同一模板,程序不能既有显示特化又有实例化。


240.在类特化外部定义成员时,成员之前不能加template<>标记。但在特化成员而不特化类时,定义特化成员时,则需要加上template<>标记。


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


242.当匹配同样好时,非模板版本优先。


243.export 用来指出编译器必须记住相关模板定义位置的关键字,供支持模板实例化的分别编译模型的编译器使用。在一个程序中一个模板只能用export关键字定义一次。


244.包含编译模型,编译器用来寻找模板定义的机制,它依赖于模板定义被包含在每个使用模板的文件中。一般而言,模板定义存储在一个头文件中,使用模板的任意文件必须包含该头文件。


245.在使用任意实参特化的模板之前,必须先出现模板特化。


246.类模板的类型实参必须在使用类的时候指定。


247.处理异常栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。


248.析构函数应该从不抛出异常。


249.除下面几种可能的区别之外,异常的类型与catch说明符的类型必须完全匹配额:

允许从非const到const的转换;允许从派生类型到基类类型的转换;将数组/函数转换为对应的指针类型。


250.通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。


251.因为catch子句按出现次序匹配,所以使用来自继承层次的异常的程序必须将它们的catch子句排序,以便派生类型的处理代码出现在其基类类型的catch之前。

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


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


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


254.auto_ptr对象只能保存一个指向对象的指针,并且不能用于指向动态分配的数组,使用auto_ptr对象指向动态分配的数组会导致未定义的运行时行为。它使用的普通的delete操作符,而不用数组的delete[]操作符。

auto_ptr的主要目的是,在保证自动删除auto_ptr对象引用的对象的同时,支持普通指针式行为。


255.auto_ptr对象的复制和赋值是破坏性操作,不能将其存储在标准容器中:与其他复制或赋值操作不同,auto_ptr的复制和赋值改变右操作数,因此,赋值的左右操作数必须都是可修改的左值。


256.不要使用auto_ptr对象保存指向静态非配对象的指针,否则当其准备被撤销的时候,将试图删除指向非动态分配对象的指针。


257.永远不要使用两个auto_ptr对象指向同一对象。

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


258.如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。

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


259.基类中的异常列表是虚函数的派生类版本可以抛出的异常列表的超集。


260.命名空间为防止名字冲突提供了更加可控的机制,命名空间能够划分全局命名空间,这样使用独立开发的库就更加容易了。

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


261.命名空间可以是不连续的,与其他作用域不同,命名空间可以在几个部分中定义。命名空间由它的分离定义部分的总和构成,命名空间是累积的。

未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。


262.不能在不相关的命名空间中定义成员。


263.如果函数具有类类型形参就使得函数可见,其原因在于,允许无须单独的using声明就可以使用概念上作为类接口组成部分的非成员函数。

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


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


265.如果派生类定义了自己的复制构造函数或赋值操作符,则类负责复制(赋值)所有的基类子部分,而不自动复制或赋值基类部分。


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

由最低层派生类的构造函数初始化虚基类,无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。


267.内存分配与对象构造分离

allocator类/标准库中的operator new和operator delete

new:1.调用名为operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;2.运行该类型的一个构造函数,用指定初始化式构造对象;3.返回指向新分配并构造的对象的指针。

delete:1.对指针指向的对象运行适当的析构函数;2.通过调用名为operator delete的标准库函数释放该对象所用内存。

它们均是在void*指针而不是类型化的指针上进行操作;allocator类则不同。


268.定位new表达式使我们能够在特定的、预分配的内存地址构造一个对象。

对值型类而言,在适当的位置直接构造对象与构造临时对象并进行复制之间没有可观察到的区别,而且性能差别基本没有意义。但对某些类而言,使用复制构造函数是不可能的(因为复制构造函数是私有的),或者是应该避免的,在这种情况下,也许有必要使用定位new表达式。


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

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


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


271.成员new和delete函数必须是静态的(隐式为静态函数,不必显示声明),因为它们要么在构造对象之前使用(operator new),要么在撤销对象之后使用(operator delete),因此,这些函数没有成员数据可操纵。


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


273.只有当typeid的操作数是带虚函数的类类型的对象的时候,才返回动态类型信号。测试指针(相对于指针指向的对象)返回指针的静态的、编译时类型。


274.默认构造函数和复制构造函数以及赋值操作符都定义为private,所以不能定义或复制type_info类型的对象。程序中创建type_info对象的唯一方法是使用typeid操作符。


275.typeinfo对name返回值的唯一保证是,它为每个类型返回唯一的字符串。


276.调用操作符的优先级高于成员指针操作符。


277.嵌套类的名字在其外围类的作用域中可见,但在其他类作用域或定义外围类的作用域中不可见。嵌套类的名字将不会与另一作用域中声明的名字冲突。


278.在其类外部定义的嵌套类成员,必须定义在定义外围类的同一作用域中。在其类外部定义的嵌套类的成员,不能定义在外围类内部,嵌套类的成员不是外围类的成员。

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


279.每个union对象的大小在编译时是固定的:它至少与union的最大数据成员一样大。

union也可以定义成员函数,包括构造函数和析构函数。但是,union不能作为基类使用,所以成员函数不能为虚数。

union不能具有静态数据成员或引用成员,而且,union不能具有定义了构造函数、析构函数或赋值操作符的类类型的成员。

不能用于定义对象的未命名union称为匿名联合。

匿名union不能有私有成员或受保护成员,也不能定义成员函数。


280.局部类的所有成员(包括函数)必须完全定义在类定义体内部,因此,局部类远不如嵌套类有用。

局部类不能使用函数作用域中的变量,它们可以使用外围函数中定义的类型名、静态变量和枚举成员。

局部类不能有静态成员。


281.地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域也不能是类的静态成员。


282.对待const和volatile的一个重要区别是,不能使用合成的复制和赋值操作符从volatile对象进行初始化或赋值。合成的复制控制成员接受const形参,这些形参是对类类型的const引用,但是,不能将volatile对象传递给普通引用或const引用。

虽然可以定义复制控制成员来处理volatile对象,但更深入的问题是复制volatile对象是否有意义,对该问题的回答与在任意特定程序中使用volatile的原因密切相关。


283.C函数的指针与C++函数的指针具有不同的类型,不能将C函数的指针初始化或赋值为C++函数的指针(反之亦然)。


284.类的自定义内存管理有两种方式:定义自己的内部内存分配,以简化自己的数据成员的分配;定义自己的、类特定的operator new和operator delete函数,在分配类类型的新对象时使用它们。


285.运行时类型识别RTTI只适用于定义了虚函数的类,没有定义虚函数的类型的类型信息是可用的但反映静态类型。


286.volatile 告诉编译器不要执行某些优化的信号


287.嵌套类:它是在另一类的作用域中定义的类,这样的类经常定义为其外围类的具体实现类。

联合:是只能包含简单数据成员的一种特殊类。union类型的对象在任意时刻只能为它的一个数据成员定义值。联合经常嵌套在其他类类型内部。

局部类:是局部于函数而定义的非常简单的类。局部类的所有成员必须定义在类定义体中,局部类没有静态数据成员。


288.C++支持几种固有的不可移植的特征,包括位域和volatile(它们可使与硬件接口更容易)以及链接指示(它使得与用其他语言编写的程序接口更容易)。


发布了62 篇原创文章 · 获赞 3 · 访问量 15万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览