C++中不引人瞩目的细节

0
13

1.标准库只用iostate表示流的状态,包括badbit,failbit,eofbit,goodbit。通常我们使用good()操作来确定流的状态正常,使用fail()操作来确定流出错。实际上将流当做条件使用的代码就等价于!fail()。而eof和bad只能表示特定的错误。

2.为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。否则文件流的failbit会被置位,随后的文件流操作都会失败。

3.容器的begin()和end()成员都是重载过的,包括一个返回const成员和一个非const成员。因此哪怕不使用cbegin()或cend()也可以返回const_iterator。

4.容器赋值操作中,swap(c1,c2)通常比c1 = c2快得多。统一使用非成员版本的swap是一个好习惯。

5.顺序容器中(除了array)有一个成员函数assign()。一般对容器对象的赋值操作要求相同的类型,但是使用assign()可以将迭代器范围,初始化列表等拷贝到顺序容器中,即顺序容器的初始化时允许的操作。

6.容器的关系运算符左右两边运算对象必须是相同类型的容器。其中相等运算符==是使用元素的==运算符实现,其它关系运算符(无序关联容器没有)使用元素的 < 运算符。

7.不要保存尾后迭代器的值,在需要使用它的时候去调用end()。

8.vector的预留空间会随着元素的加入而扩张,但并不会随着元素的移除而收缩。

9.改变string的几个方法,assign(), append(), replace(), insert()。分别为拷贝,追加,替换,插入,参数非常灵活多变。记住前后参数一致即可,即使用下标加长度就统一使用下标加长度,使用迭代器就统一使用迭代器。

10.string常用操作还有子串,搜索,比较,数值转换。

11容器适配器有三种,分别是stack、queue、priority_queue。其中公共操作有pop,push,top,emplace。

12.泛型算法是基于迭代器的,并不直接操作容器(本身不会执行容器的操作),因此一般泛型算法不会改变容器大小。

13.谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。

14.可调用对象一共有四种,分别是:函数指针、成员函数、重载了函数调用运算符的类和lambda表达式。

15.lambda没有默认参数,实参数目永远和形参数目相等。

16.lambda如果使用值捕获,那么被捕获变量的值是在lambda创建时就已经拷贝了。

17.bind可以调整函数的参数。bind的返回对象可以接受无限个参数,但是只有匹配的参数才是有效的。

18.inserter插入器永远指向开始时指向的元素。因为每插入一个新元素,它就会递增然后指向原来的元素。

19.流迭代器可能会有线程不安全的问题。(个人感觉)

20.反向迭代器如果调用base转化为普通迭代器,那么它指向的位置会发生变化。例如,因为(begin(), end())和(rbegin(), rend())指向相同的范围,所以当rend转为普通迭代器时,会偏移到begin。

21.五类迭代器中,输入迭代器和输出迭代器时并列的,单遍扫描,只能递增。前向迭代器在它们两个的基础上可以多遍扫描。双向迭代器又增加了递减的功能。最后随机访问迭代器支持所有迭代器操作。

22.链表类型(单向和双向)定义了独有的sort,merge,remove,reverse和unique,还支持splice操作。优先使用这些成员函数的版本。

23.关联容器根据 1)map、set 2)是否允许重复值 3)是否有序 这三个维度分为八种不同的关联容器。

24.有序关联容器根据关键字来排序。如果关键字没有定义<运算符,我们需要自己传入比较函数(该函数必须满足严格弱序的条件)。传入时需要在模板类型和初始化参数中同时传入。

25.关联容器的关键字都是const的。

26.C++11以后,可以用{a, b}来构造pair,包括通过{a,b}返回一个pair等操作。

27.通常不对关联容器使用泛型算法。

28.非multi容器进行插入操作时,返回值是一个pair,包含指向指定关键字的元素(容器中的)和一个指示是否插入成功的bool值。而multi容器则直接返回指向新元素的迭代器。

29.erase通过关键字删除关联容器元素时,返回值是被删除元素的个数。

30.只有非const的map和unordered_map支持下标操作和at操作。如果关键字对应元素不存在,则下标操作会创建一个新元素,并将关联值进行值初始化,而at操作还是抛出异常。

31.特别的是,map的下标操作返回的类型和解引用map迭代器的返回类型不同。

32.无序容器的底层实现是基于哈希的,在存储上组织为一组桶。因此无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

33.对于自定义类类型,我们不能直接将其作为无序容器的元素类型。我们需要提供函数来代替==运算符以及哈希值计算函数。类似于有序容器的要求,无序容器的自定义类类型同样需要在模板参数和初始化参数中传入自定义的函数。

34.由于shared_ptr只有在引用计数为0时才会销毁对象并释放内存,所以我们要注意如果确实不再需要某个对象了,要主动销毁对象。例如泛型算法重排存放shared_ptr的容器后,无用的元素会在容器尾部,这时我们需要主动调用erase删除那些不再需要的元素。

35.程序使用动态内存出于以下三种原因之一:1)程序不知道自己需要使用多少对象 2)程序不知道所需对象的准确类型 3)程序需要在多个对象间共享数据

36.如果两个对象共享底层数据,当某个对象被销毁时,我们不能单方面地销毁底层数据。

37.new一个对象的时候,哪怕暂时没有初值,最好也在后面跟一对小括号,这样对于内置类型来说会值初始化。

38.默认情况下,new如果不能分配所需内存,则会抛出bad_alloc的异常。如果我们使用定位new传入nothrow对象,则相同情况下new会返回一个空指针。

39.delete的指针必须指向一个动态分配的对象或是一个空指针。(可以接受空指针)

40.使用new和delete的常见问题:1)忘记delete内存 2)使用已经释放掉的对象 3) 同一块内存释放两次

41.一旦shared_ptr接管了普通指针指向的对象,就不应该再使用普通指针来访问该对象了,否则很容易出现空悬指针的问题。

42.使用智能指针的规范:1)不用相同的内置指针初始化(或reset)多个智能指针 2)不delete get()返回的指针 3)不使用get()初始化或reset另一个智能指针 4)如果使用了reset()返回的指针,则要注意不能再智能指针释放内存后继续使用该指针 5)如果管理的资源不是new分配的内存,那么要给智能指针传递一个对应的删除器

43.new T[0]返回的指针类似于尾后指针,是非空指针。

44.new T[]返回的并不是数组类型的对象,而是一个数组元素类型的指针。因此不能对该对象调用begin或end,因为他没有数组维度,也不能用范围for来遍历该对象内的元素。

45.动态分配的数组,销毁时是按逆序销毁的,即最后一个元素先被销毁,然后是倒数第二个,以此类推。

46.构造函数不能声明成const。当我们创建一个类的const对象时,直到构造函数完成初始化过程,对象才能真正取得其const属性。因此构造函数可以在const对象构造过程中向其写值。

47.友员的声明只是指定了权限,而非一个通常意义上的函数声明。

48.类的定义分为两部分处理,先编译成员的声明,等类全部可见后再编译函数体。所以声明中出现的名字必须在使用前确保可见,而函数体则可以使用类中定义的任何名字。

49.定义一个使用默认构造函数进行初始化的对象时,不要在对象名之后加空括号对。

50.拷贝构造函数被调用的三种情况:1)用一个对象初始化另一个对象时,无论使用Type o1(o2)还是Type o1 = o2都调用拷贝构造函数。2)实参传递给形参时(形参非引用)。3)函数返回一个对象时,此时会有一个临时对象被返回的对象初始化。函数结束后函数内的对象被释放,临时对象用于调用者的后续操作。

51.需要析构函数的类几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。

52.如果一个类需要一个拷贝构造函数,那几乎可以肯定它也需要一个拷贝赋值运算符,反之亦然。

53.如果一个类有数据成员不能被默认构造、拷贝、赋值或销毁,则对应的成员函数将被定义为删除的。

54.赋值运算符应该注意处理自赋值的情况

55.大多数赋值运算符组合了析构函数和拷贝构造函数的作用。

56.使用copy and swap来定义赋值运算符解决了上面的两个问题(自赋值和重复拷贝构造、析构的代码冗余),并且天然就是异常安全的。但是如果swap操作很繁琐,有很多除了swap数据成员之外的调整操作,那么用copy and swap就得不偿失了。

57.右值引用只能绑定到临时对象(右值)上,所以使用右值引用的代码可以自由地接管所引用对象的资源。

58.因为变量表达式都是左值(即只有一个变量而没有运算符),所以我们无法将右值引用绑定到一个右值引用类型的变量上。右值引用本身是一个左值。

59.不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept。如果不这么做的话,标准库比如vector在转移自定义类型元素的时候会选择更安全的拷贝构造函数。

60.在移动操作之后,移后源对象必须保持有效的、可析构的状态,但是用户不能对其值做任何假设。

61.通过在类实现代码中小心地使用std::move,可以大幅度地提升性能。但是在类实现代码之外的地方,只有在确定需要并保证安全的情况下,才可以使用std::move。

62.通常定义一个const T& 和一个 T&& 的重载函数。

63.引用限定符和const限定符一样,只能用于非static成员函数。并且必须同时出现在函数的声明和定义中。如果同时出现了const限定符和引用限定符,则引用限定符必须跟随在const限定符之后。

64.如果一个成员函数有引用限定符,那么具有相同参数列表的同名函数的所有版本都必须有引用限定符。要么就都不加引用限定符。

65.通常情况下,不应该重载逗号、取地址、逻辑与和逻辑或运算符。

66.赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。

67.通常输出运算符主要负责打印对象内容而非格式,输出运算符不应该打印换行符。

68.输入运算符必须处理输入可能失败的情况。

69.如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值运算符来实现算术运算符。

70.lambda表达式实际上生成了一个匿名的类的匿名对象,其中包含一个重载的函数调用运算符,且该重载运算符默认是const的。其值捕获的数据会变成该对象的数据成员,在生成的构造函数中,用捕获的变量的值来初始化数据成员。

71.标准库规定其函数对象对于指针同样适用。

72.有序关联容器对于关键字的排序使用的是函数对象less<key_type>,所以我们可以定义一个指针的map或set而无需直接声明less。

73.转换构造函数(一个实参)和类型转换运算符共同定义了类类型转换,也称为用户定义的类型转换。

74.可以指定explicit将隐式类型转换运算符指定为显示,这样一来就要使用显示的强制类型转换来调用。但是当包含的对象的表达式用作条件时,编译器会将显式的类型转换(隐式地)应用于它。

75.通常情况下,不要为类定义相同的类型转换,也不要在类中定义两个及两个以上转换源或转换目标是算数类型的转换,否则很可能会产生二义性。

76.除了显式地向bool类型转换之外,我们应该尽可能避免定义类型转换函数并尽可能限制那些“显然正确”的非显式构造函数。

77.如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。

78.派生类的成员和友员只能访问派生类对象中的基类部分的受保护成员,对于普通的基类对象中的成员不具有特殊的访问权限。

79.派生类向基类的转换,只有在当前的代码节点可以通过派生类访问到基类的公有成员时才能进行,否则无法转换。

80.派生类可以通过using声明把基类中可访问的名字标记出来,被标记的名字的访问权限由该using声明之前的访问说明符来决定。

81.基类的作用域在派生类作用域之外,名字查找是从静态类型所在的作用域开始的。

82.如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本。

83.C++11中派生类可以通过使用using声明,继承其直接基类的构造函数。

84.模板参数中,绑定到非类型整形参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参必须具有静态的生存期。

85.如果一个成员函数没有被使用,则它不会被实例化。这一特性使得即使某种类型不能完全符合模板操作的要求,依然能用该类型实例化类。

86.在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参。

87.无法定义一个typedef引用OBJ<T>,因为模板不是一个类型。但是C++11可以使用using来为类模板定义一个类型别名,例如template<typename T> using foo = bar<T>;

88.如果使用模板类型参数的类型成员,则必须显示地用typename关键字指示该名字是一个类型。例如typename T::value_type * p;

89.为了避免多个文件实例化相同模板带来的额外开销,我们可以使用实例化声明和实例化定义。在某个文件定义一个实例化版本,然后在其他文件只用声明即可。

90.显示实例化一个类模板时,会实例化该类的所有成员。因此我们用来显示实例化一个类模板的类型,必须能用于模板的所有成员。

91.将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换和数组或函数到指针的转换。

92.尾置返回类型出现在参数列表之后,所以可以用函数的参数来推导尾置返回类型。

93.通常不能直接定义一个引用的引用,但是通过类型别名或通过模板参数间接定义是可以的。这时会发生引用折叠。

94.只有右值引用的右值引用会折叠成右值引用。

95.std::move用来生成一个右值,而std::forward则保持原有属性,用于完美转发参数。

96.当有多个重载模板对一个调用提供同样好的匹配时,会选择最特例化的版本。

97.参数包扩展中的模式会独立运用于包中的每个元素。

98.特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。

99.只能部分特例化类模板,而不能部分特例化函数模板。

100.显示实例化和特例化的区别在于,特例化可以改变原有逻辑,而显示实例化只能保持原有逻辑。

<

发布回复

请输入评论!
请输入你的名字