点击查看Evernote原文。
#@author: gr
#@date: 2014-10-01
#@email: forgerui@gmail.com
Part 2: STL顺序容器(第9章)
一、标准库array(C++11)
array的大小固定,不能省去,它是容器的一部分。
//保存42个int, 初始1,2,其余40个元素初始为0
array<int, 42> = {1, 2}
//保存24个string
array<string, 24>
array<int, 10>::size_type i; //正确
array<int>::size_type j; //错误,没有大小
//类的话必须有一个默认构造函数
array<Widget, 10> aw;
内置数组不能进行数组赋值,拷贝,array
可以。
int digs[10] = {1, 2, 3};
int cpy[10] = digs; //错误,不支持赋值
array<int, 10> dig = {1, 2, 3};
array<int, 10> cp = dig; //正确,可以进行赋值,数组大小要一致
二、容器的关系运算符
只有当元素定义了相应的比较运算符,才可以使用容器的关系运算符。
vector<int> v1 = {1, 3, 7}; //初始列,C++11
vector<int> v2 = {1, 3, 7, 9};
v1 < v2 //true,所有元素相等,v1数目更少
三、STL容器使用的是拷贝
用一个对象初始化容器,是将该对象的拷贝放进容器中的,容器中的元素修改与原来的对象没有任何影响。
四、使用emplace_back
,emplace
,emplace_front
emplace
可以直接在内存中创建对象,并且效率更高。
vector<Sales_data> c;
//直接在空间创建对象,节省开销
c.emplace_back("123", 25, 25.99);
//编译错误,没有3个参数的构造
c.push_back("123", 25, 25.99);
//构造临时对象,拷贝进入容器,销毁对象
c.push_back(Sales_data("123", 25, 25.99));
五、容器的访问成员函数
可以使用front
,back
进行直接访问,也可以通过迭代器进行解引用,获取元素,但在进行访问之前,最好要检测容器是否为空,如果为空,将会产生不确定行为。
对提供了随机访问的容器可以使用下标访问(如v[0]
),list
则不存在下标访问。下标访问返回的是元素的引用,对其赋值可以直接修改容器中元素的值。下标运算不检查是否在合法范围内,下标越界是一种严重的错误,会导致runtime error
。也可以使用at
成员函数,如果下标越界,at
会抛出一个out_of_range
异常。
vector<string> sv; //空vector
cout << sv[0]; //运行时错误
cout << sv.at(0); //抛出一个out_of_range
六、容器的删除操作
forward_list
没有pop_back
函数,vector
和string
没有pop_front
操作。erase(p);
删除迭代器p所指定的元素,erase(p, q);
删除迭代器p到q范围的元素。在删除之前,必须保证元素是存在的。
forward_list
的删除需要两个迭代器,一个是正在处理元素的,一个是前驱元素的迭代器。
//删除容器中的奇数
forward_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7};
auto prev = flst.before_begin(); //表示flst的“首前元素”
auto curr = flst.begin();
while (curr != flst.end()) {
if (*curr % 2){ //元素为奇数
curr = flst.erase_after(prev); //删除它并移动curr
}else{
prev = curr;
curr++;
}
}
七、改变容器的大小
使用resize
来改变容器的大小,array
不支持resize
。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于要求的大小,将新元素加入到容器中。
list<int> il(10, 42);
il.resize(15); //在最后加入5个0
il.resize(25, -1); //在最后加入10个-1
il.resize(5); //删除末尾的20个元素
八、在循环中保持迭代器
容器的操作可能会使已有的迭代器失效,如何在循环过程中保持这些迭代器,以使整个循环过程正确?
办法是使用成员函数操作的返回值更新迭代器,虽然list
的某些操作并不影响迭代器,但为了统一,最好所有的顺序容器都使用相同的形式。
vector<int> vi = {1, 2, 3, 4, 5};
auto iter = vi.begin();
iter = vi.insert(iter, 2); //用insert返回值更新迭代器
iter = vi.erase(iter); //删除元素
九、不要试图保存end返回的迭代器
容器的操作会改变end()
返回的迭代器,所以不要保存它。这也是为什么在循环中,为何反复调用v.end()
进行判断是否结束。
it = v.begin();
while (it != v.end()){ //反复调用v.end()检查
//...
}
十、理解vector
是如何增长的
capacity
表示容量,是已经申请的内存空间,size
是已经保存的元素的数目。
使用reserve
改变容器的容量,resize
改变容器的大小。
当vector
内存不够用的时候,会把整个vector
复制到更大的内存空间中,造成开销变大。
使用reserve
事先申请大的空间,可以避免vector
的拷贝,但如果容量过大,则会浪费。
在《Effective STL》中的Topic17里面讲解使用"swap技巧"去除多余的容量,实际上,C++11中提供了shrink_to_fit
函数来退回不需要的内存空间。
十一、string
独有的一些操作
使用string
进行字符串处理,可以极大地简化我们的工作。
-
一般程序设计语言,都会提供
substr
函数进行字符串截取。//pos开始位置,n截取的个数 s.substr(pos, n);
-
insert
,erase
基于下标版本
string
提供了基于下标版本的成员函数。s.insert(s.size(), 5, '!'); //在最后插入5个! s.erase(s.size()-5, 5); //删除最后5个元素
-
append
,replace
string s("C++ Primer"), s2 = s; s.insert(s.size(), " 4th Ed."); //追加 s.append(" 4th Ed."); //等价上面insert方法
replace
是erase
和insert
操作的简写:
s.erase(11, 3);
s.insert(11, "5th");
s2.replace(11, 3, "5th"); //等价于上面两句
s2.replace(11, 3, "Fifth"); //删除3个字符,添加5个也是可以的 -
搜索操作
string
一共有6种搜索操作,分别是find
,rfind
,find_first_of
,find_first_not_of
,find_last
,find_last_not_of
。这些都是大小写敏感,返回第一个匹配出现的位置。string name("AnnaBelle"); name.find("Anna"); //返回0 //使用find_first_of查找在list_c字符出现的第一个位置 string list_c("cnl"); name.find_first_of(list_c); //返回1,即n第一次出现的地方
-
数值转换
int i = 42; string s = to_string(i); //将i转换为字符表示形式 double d = stod(s); //将string转换为浮点数
十二、容器适配器
适配器是一种机器,接受一种已有的容器类型,对其改造,使其行为看起来像一种不同的类型。我们定义了适配器,就只能使用适配器提供的操作,不能使用底层容器的操作。
stack
栈适配器可以使用vector
,deque
,list
实现;queue
可以使用deque
,list
实现,因为vector
没有pop_front
;