字符串、向量和数组
命名空间的using声明
- [x] `string`与`vector`是两种最重要的标准库类型,前者支持可变长字符串,后者则可以表示可变长的集合
- [x] 位于头文件的代码一般来说不应该使用using声明,这是因为头文件的内容会拷贝到所有引用它的文件中去,如如果头文件里有某个using声明,那么每个使用了该头文件的文件就会有这个声明,对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突
标准库类型string
- [x] 标准库类型strign表示可变长的字符序列,使用string类型必须先包含string的头文件,作为标准库的一部分,string定义在命名空间std中
- [x] C++标准库一方面对库类型所提供的操作做了详细规定,另一方面也对库的实现者做出一些性能上的需求,因此,标准库类型对于一般应用场合来说有足够的效率
定义和初始化string对象
string s1; //默认初始化, s1是一个空字符串
string s2 = s1; //s2是s1的副本
string s3 = "hiya"; //打完
string s4(10,'C'); //s4的内容是cccccccccc
- [x] 如果使用等号(=)初始化变量,实际上执行的是`拷贝初始化`,编译器把等号右侧的初始值拷贝到新的创建的对象中去,与之相反,如果不使用等号,则执行的是`直接初始化`
- [x] 当初始值只有一个时,使用直接初始化或拷贝初始化都行,如果初始值有多个,一般来说只能使用直接初始化的方式
os<>s //从is中读取字符串赋给s,字符串以空白符分割,返回is
getline(is,s) //从is中读取一行赋给s,返回is
s.empty() //s为空返回true,否则返回false
s.size() //返回s中字符的个数
s[n] //返回s中第n个字符的引用,位置从0计起
s1+s2 //返回s1和s2连接后的结果
s1=s2 //用s2的副本代替s1中原来的字符
s1==s2 //如果s1和s2中所含的字符完全一样,则相等
s1!=s2
<,<=,>,>= //利用字符在字典中的顺序进行比较,且对字母的大小写敏感
- [x] 在执行读取操作时,string对象会自动忽略开头的空白(即空格符,换行符,制表符等)并从第一个真正的字符开始读起,知道遇见下一处空白为止
- [x] 由于string的size函数返回的是一个无符号的整型数,因此切记,如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果
- [x] 如果一条表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题
- [x] string对象相等意味着它们的长度相同而且所包含的字符也全部相同
- [x] 标准库允许把字符字面值和字符串字面值转换成string对象,所以再需要string对象的地方就可以使用这两种字面值代替
- [x] 由于某些历史原因,也为了与C兼容,所以C++语言的字符串字面值并不是标准库类型string的对象,切记,字符串字面值与string是不同的类型
- [x] `范围for(range for)`语句遍历给定序列中的每一个元素并对序列中的每个值执行某种操作,语法形式如下:
//for(declaration : expression)
//{
// statement
//}
string str("test");
for(auto temp : str)
{
cout<
- 使用超出范围的下表将引发不可预知的结果,以此推断,使用下标访问空string也会引发不可预知的结果
- 使用下标时必须确保其在合理范围之内,也就是说,下标必须大于等于0而小于等于字符串的size()的值,一种简便易行的方法是,总是设下标的类型为string::size_type,因为此类型是无符号数,可以确保下标不会小于0,此时,代码只需保证下标小于size()的值就可以了
- C++标准并不会要求标准库检测下标是否合法,一旦使用了一个超出范围的下标,就会产生不可预知的结果
标准库类型vector
- 标准库类型
vector
表示对象的集合,其中所有对象的类型相同,集合中每个对象都有一个与之对应的索引,索引用于访问,被称为容器
- C++语言既有
类模板
,也有函数模板
,其中vector是一个类模板
- 模板本身不是类或者函数,相反可以将模板看作为编译器生成类或函数编写的一份说明,编译器根据模板创建类或函数的过程成为
实例化
,当使用模板时,需要指出编译器应该把类或函数实例化成何种类型
-
vector
是模板而非类型,由vecetor
生成的类型必须包含vector中元素的类型
,例如vector
-
vector
能容乃绝大多数类型的对象作为其元素,但引用不是对象,所以不存在包含引用的vector,除此之外,绝大多数(非引用)内置类型和类类型都可以构成vector对象,甚至组成vector的元素也可以是vector
- 某些编译器可能仍需以老式的声明语句来处理元素为vector的vector对象,如:
vector>
- 通常情况下,可以只提供vector对象容纳的元素数量而不用略去初始值,此时库会创建一个
值初始化的
元素初值,并把它赋给容器中的所有元素,这个初值由vector对象中元素的类型决定
- 如果vector初始化用的是
花括号
,可以表述成我们想列表初始化该vector对象,初始化过程会尽可能的把花括号内的值当成是元素初始值的列表来处理
- 如果vector初始化用的是
圆括号
,可以说提供的值是用来构造
vector对象的
- 要想列表初始化vector对象,花括号里面的值必须与元素类型相同
- 向vector对象中添加元素
- [x] `push_back`负责把一个值当初vector对象的尾元素压到vector对象的`尾端`
C++标准要求vector应该能在运行时高效快速的添加元素,既然vector对象能高效快速的增长,那么在定义vector对象时设定其大小也就没什么必要了,事实上如果这么做性能可能更差
- 范围for循环语句体内不应该改变其所遍历的序列的大小
- 不能用下标的形式添加元素
- [x] vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素
迭代器
- 因为
end
返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作
- 示例如下
for(auto it = s.begin ; it != s.end && !isspace(*it) ; ++it)
{
*it = toupper(*it);//将当前字符改写成大写形式
}
- 就像不知道
string
和vector
的size_type
成员到底是什么类型一样,一般来说我们也不知道(其实是无需知道)迭代器的精准类型,而实际上,那些拥有迭代器的标准库类型使用iterator
和const_iterator
来表示迭代器的类型:
vector::iterator it; //it能读写vector的元素
string::itreator it2; //it2能读写string对象中的元素
vector::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符
- 如果vector对象或string对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator
- 我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另一个元素
- 每个容器定义了一个名为iterator的类型,该类型是支持迭代器概念的一套操作
-
begin
和end
返回的具体类型由对象是否是常量决定,如果对象是常量,begin
和end
返回const_iterator;反之返回iterator
vector v;
const vector cv;
auto it1 = v.bedin(); //it1是vector::iterator
auto it2 = cv.begin(); //it2是vector::const_iterator
- 为了便于专门得到const_iterator类型的返回值,C++11专门引用如新的函数,分别是
cbegin
以及cend
- 对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该对象的迭代器,只需检查it所指字符是否为空就可以了
- C++语言定义了
箭头运算符->
,箭头运算符把解引用和成员访问两个操作结合在一起,也就是说it->mem和(*it).mem表达的意思相同
- 不能在范围for循环中向vector对象添加元素
- 任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效
但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
- 使用迭代器进行二分搜索
//text必须是有序的
//beg 和end 表示我们搜索的范围
auto beg = text.begin(),end = text.end();
auto mid = text.begin() + (end - beg)/2;//初始状态下的中间点
while(mid != end && *mid != sought)
{
if(sought < *mid)
{
end = mid;
}else{
beg = mid + 1;
}
mid = brg + (end - beg)/2
}
数组
- 数组是一种类似于标准库类型vector的数据结构,但是在性能和灵活性上的权衡又与vector有所不同
- 与vector相似的是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在的位置访问
- 与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素
- 如果不清楚元素的确切个数,请使用vector
- 和内置类型一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含义未定义的值
- 定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型
- 与vector一样,数组的元素应为对象,因此不存在引用的数组
- 使用字符串字面值对数组初始化时,要注意字符串字面值结尾处还有一个
空字符
,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去
- 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值
- 复杂数组声明如下:
int * ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10] = /*?*/; //错误:不存在引用的数组
int (*parray)[10] = &arr; //parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用一个含义10个整数的数组
-
size_t
类型是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小
指针与数组
- 使用数组的时候编译器一般会把它转换成指针
- 在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针
- vector和string支持的迭代器支持的运算,数组的指针全部支持
- 标准库函数
begin
和end
- [x] C++11新标准引入了两个名为`begin`和`end`的函数,这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数
- [x] 一共指针如果指向了某种内置类型数组的尾元素的‘下一位置’,则其具备与vector的end函数返回的迭代器类似的功能,特别注意的是,尾后指针不能执行解引用和递增操作
- 和迭代器一样,两个指针相减的结果是它们之间的距离,参与运算的两个指针必须指向同一个数组当中的元素
- 两个指针相减的结果的类型是一种名为
ptrdiff_t
的标准库类型,和size_t
一样,ptrdiff_t
也是一种定义在cstdef头文件中的机器相关的类型,因为差值可能为负值,所以ptrdiff_t
是一种带符号的类型
- 内置的下标运算符所用的索引值不是无符号类型,这一点与vector和string不一样
C风格字符串
- 尽管C++支持C风格字符串,但在C++程序中最好还是不要使用它们,这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因
与旧代码的接口
-
c_str
函数返回的是一个C风格的字符串,返回的结果是const char*
类型的指针
- 如果执行完
c_str()
函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份
- 使用数组初始化vector
int int_arr[] = {0,1,2,3,4};
vector ivec(begin(int_arr),end(int_arr));
- [x] 使用指针和数组很容易出错,一部分原因是概念上的问题,指针常用于底层操作,因此容易引发一些与繁琐细节有关的错误
多维数组
- 严格来说,C++语言中没有多维数组,通常所说的多维数组其实是
数组的数组
- 多维数组初始化
int a[3][1] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
}
- [x] 其中内层嵌套的花括号并非必须
- 要使用范围for语句来处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
for(auto & row : ia)
{
for(auto & col : row)
{
...
}
}
- 定义指向多维数组的指针时,千万别忘了这个多维数组实际上是数组的数组
- 类型别名简化多维数组的指针
using int_array = int[4]; \\C++11的新写法
typedef int[4] int_array; \\等价