-
头文件中不应该包含
using声明,不然每一个包含了改头文件的文件都会有这个声明,导致不可预料的命名冲突。 -
每个
string对象的末尾都有一个'\0',名为“空字符”,用于指示字符串的结束。如果在程序中人为的修改了这个空字符,那么会发生不可预料的结果。 -
在
string类型的初始化中,如果用到了赋值运算符=,那么就是拷贝初始化,右值会先被拷贝一份,然后把拷贝后的内存交给新创建的对象。其余的都是直接初始化,分配一块内存然后直接填值。 -
在从输入流读取
string的时候,cin的1.1.会忽略掉开头的所有空白 (包括空格、'\n'和'\t'等),并从第一个合法的字符读起,直到遇到了下一处空白为止 (这处空白不会存在string中)。例如,如果输入的是" houston ",那么这个string最终的内容是houston。如果输入的是" h o u s t o n ",那么这个string只能拿到"h"。 -
如果不想上面那样子的事情发生,可以使用
getline(in, str)函数解决。该函数可以读入一整行,其中的两个参数in代表一个输入流,一般用cin,str代表要读入的字符串的标识符。所以要在调用getline(in, str)函数之前新建一个空字符串。注意,当你在一开始就按下了回车键,那么getline拿到的就是一个空串。正常输入的话,空白都会被保留,直到你按下换行键。换行键会被流读取到,但是不会存到str中。所以如果输入的是" h o u s t o n ",然后按下换行符,那么这个string拿到的就是" h o u s t o n "。 -
string::size()返回的是string::size_type类型的数字,这是一个无符号类型unsigned long的数字,所以注意:当你要用到某个string的size()函数的时候,那么其他介入的数字最好也都是无符号类型的,这样不会发生意想不到的错误。否则,如果你拿str.size()与-1相比,那么既有可能返回的是-1更大,因为-1会先转成一个无符号数字,结果可是2^64-1啊! -
当使用字符串的加法时,要确保每个加号两边都至少有一个对象是
string类型。两个字符串字面值由于是const char []类型,所以它俩是不能直接相加的,例如"hello" + ", world"是不合法的。 -
range for语句:for (auto declaration : expression) { statement }其中的
auto可以自动探测变量类型,expression部分是要被遍历的序列,declaration是一个变量,它代表序列中每次被遍历到的单个元素。举个例子:string str = "hungry!" for (auto c : str) { cout << c << '-'; } // "h-u-n-g-r-y-!-" -
字符串的下标 (索引) 会自动转为
string::size_type这个无符号的unsigned long。所以,如果你访问str[-1],其实你在x64的机器上得到的是str[18446744073709551615]。在访问字符串的某一位的时候,一定要确保该位确实有值。如果这个字符串本身为空,那么你的访问得到的结果是未定义的。所以在访问数组下标的时候,推荐的做法是:总是使用decltype(str.size())这个类型来声明所有下标的类型。 -
vector是模版而非类型。给vector提供了具体的元素类型之后,他才是一个类型,例如vector<string>。 -
vector能容纳绝大多数类型的对象,包括内置类型、用户自定义类型和其他容器。但是不能够容纳引用,因为引用只是一个别名而不是一个对象。当使用一些老旧的编译器的时候,声明一个vector套vector元素的时候,要在第一个右尖括号和第二个右尖括号之间插入一个空格,例如vector<vector<string> >,这一点要稍微注意一下。不过现在使用的编译器版本都是不需要加这个空格的啦! -
range for语句内不能改变其所遍历序列的大小。但凡是使用了迭代器的循环器,都不要想迭代器所属的容器添加元素。 -
只能对确知已存在的元素执行下标操作。否则会导致缓冲区溢出。
-
如果容器为空,那么
begin和end返回的是同一个迭代器,都是尾后迭代器。 -
执行解引用的迭代器必须合法并确实指示着某个元素。试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。
-
两个迭代器相减得到的是一个
difference_type的带符号类型。 -
在声明数组的时候,要么使用常量表达式说明数组的长度,要么使用字面值。不能使用变量来声明数组的大小。定义数组的时候必须指定数组类型,不允许使用
auto,哪怕你提供了初始值列表也不行。和vector一样,不存在引用的数组,但是有指针的数组。可以定义数组的指针和数组的引用:constexpr int sz = 10; // 常量表达式 int arr[sz]; // 常量表达式初始化数组 int *ptrs[sz]; // 指针的数组 int (*Parr)[sz] = &arr; // Parr 指向 arr int (&Rarr)[sz] = arr; // Rarr 引用 arr int *(&Rptrs)[sz] = ptrs; // Rptrs 引用 ptrs理解上述代码可能有些困难,有一个简单的方法就是看有没有括号。括号的作用是强行绑定几个元素,所以当
*或者&出现在括号里头的时候,它们归属于括号里它们右边的标识符;当*和&没有被括号限制的时候,它们归属于左边的类型。例如ptrs,它左边的*没有被括号所限制,那么*和左边的int在一起,指示的是数组的元素为指针类型。再看Parr,*被括号强行限制住了,那么*归属于Parr,代表Parr自己是个指针。同样的,Rarr左边的&和Rarr绑定,那么代表Rarr本身是一个引用。最后一个Rptrs也就好理解了,&说明Rptrs是个引用,*说明数组的元素都为指针类型。 -
前一章讲到过
auto和decltype的区别:auto不能保留引用类型和顶层常量属性,但是decltype可以。现在指出新的一点:由于单独操作数组名字时其实操作的是一个指针,所以当把数组名赋值给另一个变量的时候,这个变量用auto检测到的类型是指针而不是数组:int a1[10] = {0,1,2,3,4,5,6,7,8,9}; auto a2 = a1; // a2 是一个 int * 指针,指向 a1 的第一个元素 for (auto i : a2) {}; // 编译错误, a2 不是一个数组,所以不能用 auto decltype(a1) a3 = {1,2,3,4,5,6,7,8,9,10}; // a3 是一个 int[10] 数组 a3 = &(a1[2]); // 编译错误,a3 是一个数组,不能给它赋地址但是
decltype却不是这样,检测到的类型并不是指针而同样是一个数组。由此可见,decltype非常的底层,它能够突破引用和数组的表象深挖到对象的本质,即引用类型和数组类型;而auto会把引用当成一个别名,会把数组当成一个指针。 -
在
<iterator>头文件中有两个函数begin()和end(),能够接受数组作为参数,返回数组的头指针和尾后指针,就像迭代器那样。 -
两个数组指针相减得到的是一个
ptrdiff_t的带符号类型。 -
标准库类型
string和vector可以执行下标运算,但是限定下标是无符号类型。然而数组的下标运算符[]可以接受有符号的下标,例如p[-2]代表向前移动p两个单位。下标运算符的本质是对指针进行位移。 -
<string>是标准库头文件,<string.h>是 C 风格字符串的头文件,<cstring>是<string.h>的 C++ 风格。写 C++ 程序时,应尽量使用标准库<string>。 -
<cstring>提供一些 C 风格字符串的函数,例如strlen(p)、strcmp(p1, p2)、strcat(p1, p2)、strcpy(p1, p2)等。传入此类函数的指针必须指向一个明确以空字符作为结束的char数组,否则会发生严重错误。 -
为了方便老旧代码和新标准代码的混用,C++ 允许使用字符串字面值来初始化
string对象,允许使用以空字符结束的字符数组 (又称 C 风格字符串) 来初始化string对象或赋值,允许将string对象与 C 风格字符串相加。这是新特性向下兼容旧特性。所以反过来是不成立的,不能用string对象来直接初始化一个指向字符的指针。为了完成这一功能,string专门提供了一个函数c_str(),能够接受一个string对象,传出一个指针,指向内容一模一样的只读的 C 风格字符串:string s("C++ is hard"); char *cs = s; // 错误!不能用 string 对象初始化 char * const char *str = s.c_str(); // OK -
可以使用
begin()和end()来用数组初始化一个 vector:int ar[] = {2,3,5,7,11,13,17}; vector<int> v(begin(ar), end(ar));在上述代码中,两个指针指明了用来初始化的值的区间,所以你可以任意指定合法的区间来初始化。注意,第二个参数应是带拷贝区域的尾后指针,也就是说如果你要初始化数组的第 0 位到第 4 位,你需要传入的是
vector<int> vv(begin(ar), begin(ar)+5); -
在使用范围 for 语句处理多维数组的时候,要把索引写成引用,这样子可以避免索引被转为指针:
int arr[2][3] = { {1,3,5}, {2,4,6} }; for (auto &r : arr) { for (auto &c : r) { c *= 3; } }如果不把
r声明称引用,那么r会被处理成指针,内层的for语句就不能正确编译了。











网友评论