常用容器
C++ 常用的stl容器包括:
1 vector 可变大小的数组,支持随机访问。在尾部之外位置插入或删除元素很慢。
2 deque 双端队列,支持快速随机访问,在头尾位置插入删除速度很快。
3 list 双向链表,支持双向访问,任何位置插入和删除都很快
4 forward_list 单向链表,只支持单向访问,在列表任何位置插入和删除都很快
5 array 固定大小数组,支持快速随机访问,不能添加和删除元素。
6 string 与vector相似,专门用于处理字符串。
基本用法
stl容器可以存储任意类型的数据,因为容器是通过模板实现的,下面列举了容器的一些用法
1 | // sales_data对象的list |
容器嵌套时尽量将>>
分开,保留一个空格,有些陈旧的编译器会将>>
当作输入流。
迭代器
迭代器可以理解为访问容器的通用工具,通过容器名.begin()返回指向第一个元素的迭代器,通过容器名.end()返回指向最后一个元素的下一个元素的迭代器。
1 | //通过begin和end访问容器元素 |
begin和end形成了一个左闭右开的空间,所以要用begin != end 判断迭代器是否走到最后一个元素的下一个元素,因为最后一个元素的下一个位置是不合法的,所以是左闭右开空间。
迭代器的类型可以通过容器类型获取
1 | // vector<string>容器的迭代器 |
容器初始化
每个容器类型都定义了一个默认构造函数,如上面代码我们定义的deque
将一个新容器创建为另一个容器的拷贝的方法有两种:
可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围。
1 | //将一个容器copy给另一个相同类型的容器 |
利用assign可以将一个容器内的数据赋值给另一个
1 | persons2.assign(persons.begin(), persons.end()); |
由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。
assign的第二个版本接受一个整型值和一个元素值。
它用指定数目且具有相同给定值的元素替换容器中原有的元素:
1 | //用指定数目和初始值 |
swap可以交换两个容器的内容
1 | //交换两个容器 |
调用swap后,svec1将包含24个string元素,svec2将包含10个string。除array外,交换两个容器内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。
除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
添加元素
push_back操作向容器末尾添加指定元素,出了array和forward_list不支持push_back,其余顺序容器都支持。
1 | // push_back添加尾元素 |
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递给非引用参数。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然。
push_front向容器头部插入元素,注意支持push_front的容器仅有deque, list, forwardlist等。
1 | // push_front添加头部元素 |
push_back和push_front操作提供了一种方便地在顺序容器尾部或头部插入单个元素的方法。
insert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。
vector、deque、list和string都支持insert成员。forward_list提供了特殊版本的insert成员.
每个insert函数都接受一个迭代器作为其第一个参数。迭代器指出了在容器中什么位置放置新元素。
它可以指向容器中任何位置,包括容器尾部之后的下一个位置。
由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器开始位置插入元素是很有用的功能,所以insert函数将元素插入到迭代器所指定的位置之前
1 | // insert 插入 |
insert有很多重载版本,也可以插入指定范围内的元素。
1 | //批量插入 |
如果我们传递给insert一对迭代器,它们不能指向添加元素的目标容器。
insert的返回值为返回新插入元素的位置,insert是将元素插入到迭代器指向元素之前的位置,插入成功后并返回这个位置,所以insert返回的是指向插入之后新元素的位置。
emplace操作
emplace也是插入操作,但是emplace接受的一个类的构造函数,这样和insert相比,减少了对类对象的构造开销
1 | // sales_data对象的list |
访问元素
可以通过front和back获取队首元素和队尾元素
1 | auto first = sales_list.front(); |
线性容器支持下标访问,比如vector,string等
1 | auto strfirst = svec3.front(); |
删除元素
list, deque支持pop_back()弹出和最后一个元素,以及pop_front弹出第一个元素。
forward_list不支持pop_back和push_back, vector和string 不支持pop_front和push_front.
1 | vector<string> strinvec; |
也可以通过迭代器删除指定位置的元素
1 | //删除头部元素 |
删除和插入会导致容器迭代器失效,所以要更新删除后的迭代器确保程序稳定
1 | for (auto iter = numlist.begin(); iter != numlist.end();) |
forward_list的删除和插入
对于forward_list 提供了特殊的删除操作,erase_after 和特殊的插入操作insert_after。
以下通过一次遍历实现删除forward_list中的偶数
1 | forward_list<int> numlist2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; |
删除操作和插入操作会导致迭代器失效,所以要时刻更新迭代器的值保证程序运行稳定。
容器大小
我们可以通过resize改变容器size大小,通过reverse改变容器capacity容量。
capacity是容量,容器预先开辟一块空间,capacity比size大,所以当插入元素导致容器size增加时不用每次都开辟空间。
当size大于等于capacity时,会扩充capacity。
1 | vector<string> strvec2 = {"zack", "lucy", "vivo", "rolin"}; |
程序输出
1 | strvec2.size is 4 |
可以看出当size >= capacity时,重新开辟capacity.
resize扩充size后,容器会填充空数据,反之resize缩小size后会删除容器尾部多余数据。
容器适配器
stl标准库还定义了三个顺序容器适配器, stack,queue和priority_queue。
stack只要求push_back、pop_back和back操作,因此可以使用除array和forward_list之外的任何容器类型来构造stack。queue适配器要求back、push_back、front和push_front,因此它可以构造于list或deque之上,但不能基于vector构造。priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector或deque之上,但不能基于list构造。
1 | //基于vector构建stack |
以下示例演示stack的使用
1 | //构造一个 |
程序输出9,8,7,6,5,4,3,2,1,0
同样stack也支持emplace操作,节省构造函数传值的开销。
队列适配器queue和栈适配器stack都是基于双端队列deque实现,而priority_queue基于vector实现。
获取队首元素用front(),弹出队首元素用pop()。
1 | //队列 |
程序输出0,1,2,3,4,5,6,7,8,9