命名空间的using声明
头文件不应包含using声明
因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,可能产生意想不到的冲突。
标准库类型string
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization)
,因为编译器创建临时对象,然后会把等号右侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化(direct initialization)
。
string::size_type类型
string的size函数返回的是一个string::size_type类型,它是一个无符号类型。切记:如果表达式中已经有size()函数就不要再使用int了,从而可以避免混用int和unsigned可能带来的问题。例如:假设n是一个负值的int,则表达式s.size() < n
的判断结果几乎肯定是true,因为负值n会转换成一个很大的无符号值。
在C++11新标准中,可以通过auto或decltype来判断变量类型:
1 | auto len = s.size(); //len的类型是string::size_type |
处理string对象中的字符
使用C++版本的C标准库头文件,C++标准兼容C语言标准库。C语言的头文件刑如name.h,C++则将文件命名为cname。在名为cname的头文件中定义的名字从属于std命名空间,而定义在名为.h的头文件中则不然。一般来说,C++程序应该使用名为cname的头文件,标准库的名字总能在std中找到。
基于范围for语句
范围for语句体内不应该改变其所遍历的序列的大小
1 | string str("Some string"); |
C++标准并不要求标准库检测下标是否合法,一旦使用了超出范围的下标,就会产生不可预知的结果。最佳实践方法是设下标的类型为string::size_type,并保证下标小于size()值
。确保下标合法的一种有效手段是尽可能使用范围for语句。
标准库类型vector
C++11新标准可以使用列表初始化的方式初始化vector对象。
1 | vector<string> articles = {"a", "an", "the"}; |
vector对象能高效增长
C++标准要求vector应该能在运行时高效快速地添加元素,那么在定义vector对象时设定其大小就没有必要了,甚至性能可能更差。
迭代器
如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器(指向尾元素的下一位置,不存在的元素)。
所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符,因此,我们使用!=从而不用在意容器类型。
begin和end运算符
begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,则返回iterator;C++11新标准引入cbegin和cend,不论对象是否为常量,返回值都是返回值都是const_iterator。
1 | vector<int> v; |
所有使用了迭代器的循环,都不要向迭代器所属的容器添加元素
迭代器运算
迭代器和一个整数相加或相减,其返回值是向前或向后移动若干位置的迭代器。移动后的迭代器或指示原容器内的一个元素,或者容器尾元素的下一个位置。
迭代器还可以使用关系运算符(<、<=、>、>=)进行比较,参与比较的迭代器必须指向同一个容器的元素。
当两个迭代器指向同一个容器元素时,还可以将其相减,结果就是两个迭代器的距离。类型为difference_type
的有符号整数。获取容器中间元素迭代器的两种方式:
1 | auto mid = v.begin() + v.size() / 2; |
C++并没有定义两个迭代器的加法运算,因为在实际中,将两个迭代器加起来是没有意义的。
数组
数组与vector的相似之处在于都是存放相同类型的对象,并且对象本身没有名字,需要通过位置访问。但数组与vector的最大不同在于数组大小固定不变,不能随意添加元素。
理解复杂的数组声明
数组的元素应该为对象,但引用不是对象,所以不存在引用的数组。
理解复杂数组声明的含义,办法就是从数组的名字开始,按照由内向外的顺序阅读。
1 | int *ptrs[10]; //因为下标优先级高于*,所以ptr是包含10个整型指针的数组 |
在使用数组下标时,通常将其定义为size_t
类型,一种与机器相关的无符号类型。在cstddef头文件中定义了size_t类型,该文件是C标准库stddef.h对应的C++语言版本。
指针和数组
在很多使用数组名字的地方,编译器会自动地将其替换为一个指向数组首元素的指针:
1 | int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //ia是一个包含10个元素的数组 |
但当使用decltype关键字时,上述的转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组。
指针也是迭代器
为了让指针的使用更简单,C++11新标准引入了begin和end函数,将数组作为参数,begin函数返回首元素的指针,end函数返回尾元素下一个位置的指针。这两个函数定义在iterator头文件中。
1 | int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //ia是一个包含10个元素的数组 |
两个指针相减的结果为ptrdiff_t类型,和size_t一样,也是一种带符号的类型。
使用数组初始化vector对象
使用数组初始化vector对象,只需指明拷贝区域的首元素地址和尾后元素地址就可以了:
1 | int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //ia是一个包含10个元素的数组 |
现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。
多维数组
多维数组其实是数组的数组。同样,按照由内向外的顺序阅读多维数组的定义。
1 | int ia[3][4]; //定义一个大小为3的数组,该数组的每个元素都是含有4个整数的数组 |
多维数组下标引用
如果表达式下标运算符数量比数组维度小,则表示的结果将是给定索引处的一个内层数组:
1 | //定义一个大小为3的数组,该数组的每个元素都是含有4个整数的数组 |
使用范围for语句处理多维数组
1 | size_t cnt = 0; |
第一个for循环遍历ia的所有元素,元素是大小为4的数组,因此row的类型是含有4个整数的数组的引用;第二个for循环遍历4元素数组中的某一个,因此col类型是整数的引用。
在上述示例中,因为要改变数组元素的值,所以我们使用引用类型作为循环控制变量。但其实深层次的原因是如果row不是引用类型,编译器初始化row时会将数组形式的元素转换成指向数组首元素的指针,这样row的类型就是int*类型,那么第二个for循环就不合法了。所以使用范围for语句处理多维数组,除最内层的循环外,其他所有循环的控制变量都应该是引用类型
。
1 | for(auto &row : ia) |
指针和多维数组
因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:
1 | int ia[3][4]; //定义一个大小为3的数组,该数组的每个元素都是含有4个整数的数组 |
在C++11新标准中,使用auto或者decltype就能尽可能避免在数组前面加上一个指针类型了:
1 | for(auto p = ia; p != ia + 3; ++p) |
类型别名简化多维数组指针
如下代码将“4个整数组成的数组”命名为int_array,用类型别名int_array定义外层循环变量让程序更易理解。
1 | using int_array = int[4]; |