头文件中不应该包含
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
语句就不能正确编译了。
第 3 章:字符串、向量和数组
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 前言 最先接触编程的知识是在大学里面,大学里面学了一些基础的知识,c语言,java语言,单片机的汇编语言等;大学毕...