标准库就是武器库、功法招式。
第三章:字符串、向量和数组
标准库类型:
string
vector
迭代器:它是string和vector的配套类型,常被用于访问string中的字符或vector中的元素。
标准库类型vector
vector表示对象的集合,其中所有对象的类型相同。集合中的每个对象都有一个与之对应的索引,用于访问对象。因为vector“容纳着”其它对象,所以常被称为容器(container)。
C++语言既有类模板也有函数模板,vector是一个类模板。只有对C++有相对深入的理解才能写出模板。
模板可看作为编译器生成类或函数编写的一份说明。编译器以此创建类或函数的过程称为实例化。
对于类模板来说,通过提供一些额外信息来指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定。方法:在模板名字后面跟一对尖括号,在括号内放上信息。
这样就说明了vector内所存放对象的类型。
vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,如:vector,这是一个类型。
vector能容纳绝大多数类型的对象作为其元素,但因为引用不是对象,所以不存在包含引用的vector。
$定义和初始化vector对象
1 vector v1; 通用初始化
2 vector v2(v1); 通用初始化
2 vector v2 = v1; 通用初始化
3 vector v3(n, val); 创建指定数量的元素
3 vector v4(n); 值初始化 v4包含了n个重复地执行了值初始化的对象,此时T必须提供默认初始值,如int,string都有默认初始值。
如:vector ivec(10);
vector svec(10);
4 vector v5{a,b,c...}; 列表初始化
4 vector v5={a,b,c...}; 与上者等价,列表初始化
123为直接初始化方式,4位列表初始化
最常见的用法:先定义一个空的vector,然后当运行时获取到的元素的值后再逐一添加。还有就是:
vector ivec;
vector ivec2(ivec); 将ivec的元素拷贝给ivec2
$列表初始化vector对象
C++11新标准提供“列表初始化”方法。上面的等价式,在花括号里进行列表初始化。
列表初始值还是元素数量。前者使用花括号,用来列表初始化;后者使用括号,是用来构造。注意:若使用花括号所提供的值不能用来列表初始化,就会将这样的值来构造vector对象,对话为圆括号。如:
vector v5{"hi"};
vector v6("hi"); 错误
vector v7{10}; 编译器尝试使用()
vector v8{10,"hi"}; 编译器尝试使用()
$向vector对象中添加元素
对于vector直接初始化的方法有三种。
初始值已知且数量少、初始值是另一个vector对象的副本、所有元素的初始值都一样。
还有就是列表初始化,数量少还可以,若是多了,就需要用push_back来添加元素。压入(push) - 尾端(back)
c++标准要求vector应该能高效快速地添加元素。若直接设定大小,性能可能更差,除非所有元素的值一样,才会设定大小。
注意:先创建空的vector,然后才动态添加push_back(),性能高。
warning:当循环体内含有向vector对象添加元素的语句,则不能使用范围for循环。
$其它vector操作
v.empty() 是否包含元素
v.size() 返回元素个数 vector::size_type,由vector定义的size_type类型,可以使用auto来替代?!不行,只能通过上述方式,或用decltype(v.size())来指定它的类型。
v.push_back(t)
v[n]
v1 = v2
v1 = {a, b, c, ...}
v1 == v2
v1 != v2
<, <=, >, >=
$计算vector内对象的索引
只要vector对象不是一个常量,就能向下标运算符返回的元素赋值。也可通过计算得到vector内对象的索引,然后直接获取索引位置上的元素。
一般性方法:cin通过while语句负责读入数据,在循环体内部首先检查读入数据是否合法,然后才执行之后的逻辑。使用下标的时候,必须清楚地知道它是否在合理范围内。
$不能用下标形式添加元素
vector ivect; ivec是一个空vector,根本不包含任何元素,当然也就不能通过下标去访问任何元素!正确的方法是使用push_back
vector(string)对象的下标运算符可用于访问已存在的元素,而不能用于添加元素。只能对已知存在的元素执行下标操作!
越界访问导致缓冲区溢出(buffer overflow)是导致PC以及其它设备上应用程序出现安全问题的一个重要原因。
确保下标合法的一种有效手段是尽可能使用范围for语句
3.4 迭代器介绍
可使用下标运算符来访问string对象的字符或vector对象的元素,还可以用迭代器(iterator)。
所有标准库容器都可以使用迭代器。类似指针类型,其对象是容器中的元素或string对象中的字符。
end成员返回“尾元素的下一个位置”,仅是一个标记而已,表示我们已处理完容器中的所有元素,没什么实际含义(即不代表容器元素)。
若容器为空,begin和end返回的是同一个迭代器,都是尾后迭代器。
使用==和!=来比较两个合法的迭代器是否相同。
标准容器迭代器的运算符:
*iter 返回迭代器iter所指元素的引用,注意是引用;
iter->mem 解引用iter并获取该元素的名为mem的成员;
++iter 令iter指示容器中的下一个元素;
--iter 令iter指示容器中的上一个元素;
iter1 == iter2 判断两个迭代器是否相同(同时为尾后迭代器或同一个元素);
iter1 != iter2
将第一个字符改为大写
string s("some string");
if (s.begin() != s.end()) {
auto it = s.being(); // it表示s的第一个字符
it = toupper(it); // 将当前字符改为大写形式
}
将迭代器从一个元素移动到另外一个元素:++
for (auto it = s.begin(); it != s.end() && !isspace(it); ++it)
it = toupper(it); //it解引用
关键概念:泛型编程
使用迭代器而非下标的原因,是因为这种编程风格在标准库提供的所有容器上都有效,都定义==和!=。
只有string和vector等一些标准库类型由下标运算符,而并非全部如此。
迭代器类型:拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。前者可以读写vector或string中的元素,后缀只能读取,和常量指针差不多。若vector或string对象不是常量,两者都可以用。
每个容器类定义了一个名为iterator的类型,该类型支持迭代器概念所规定的一套操作。
begin和end运算符:
具体返回类型由对象是否是常量决定,若对象是常量,返回const_iterator,否则返回iterator。
若对象只需读操作而无须写操作的话,最好使用const_iterator。C++11引入两个新函数cbegin和cend来表示返回值为const_iterator。
auto it3 = v.cbegin(); it3的类型为:vector::const_iterator