[目录]
顺序容器概述
关于容器选择"
容器的通用操作
迭代器
容器的定义和初始化
赋值和swap
容器大小操作及关系运算
顺序容器概述
C++的STL库中提供了很多的容器类型,一个容器类型就是一个类对象,方便开发者对数据进行操作,如增删查等操作,这些操作不依赖容器中的数据的类型,而是更具数据在容器中的位置以及容器的结构。
容器 | 功能 |
---|---|
vertor | 可改变数组的大小,支持快速查找,下标查找,但是对于插入和删除操作,需要系统会移动所有元素,效率会比较慢 |
deque | 双端队列,支持快速访问,在容器的头和尾插入数据会很快,但是在除了头和尾插入或者删除数据和数组一样需要移动元素,效率较低。 |
list | 双向链表,不支持下标访问,但是能够双向顺序访问,,在list任何位置插入/删除操作都很快 |
forward_list | 单向链表,只支持单向顺序访问,在链表任何位置插入/删除操作都很快 |
array | 固定大小的数组,支持下标访问,不能添加或者或者删除元素(这里和修改数组中的元素不一样,内存固定,内存中的值可以修改,当时不能在添加元素/删除元素,这样会改变容器大小,对于这个容器是不允许的) |
string | 与vector容器类似,但是专门用来储存字符,随机访问很快在尾部插入/删除数据快,其他位置插入会移动后面的元素,效率比较低 |
关于容器的选择
- 如果不知道使用什么样的容器,vector是最好的选择,除非特殊的需要有更好的容器替代
- 如果程序中的元素很多,但是空间的开销有限制,那么尽量不要使用list和forward_list
- 如果元素只在容器的首尾操作,那么选择deque
- 关于操作在容器中的中间情况
- 先确定是否真的需要插入到中间如果不需要,那么直接添加到vector容器中的尾部
- 如果必须要插入到容器的中间,并且该容器访问频繁的话,那么使用list进行操作,之后将list的元素拷贝到vector中
- 如果一个容器中既需要访问,也需要操作,那么需要比较访问和操作的频繁程度而定。如果访问更加频繁的话,就是用vector,操作频繁的话就使用链表。这更具具体的项目而定。
容器的通用操作
在每次使用容器的时候都需要包含相应的头文件,而这个头文件的名称就是容器的名字。并且容器可以使用包含几乎所有的类型,也可以在包含一个容器类型
#include <vector>
vector<int> s; // 创建一个vector容器,里面的元素是int类型
vector<vector<int>> l; // 创建一个vector容器,里面包含的是vector<int>类型的容器
每一个容器可以看成是一个类,他们有自己的成员变量和成员函数,但是STL库中,有很多的顺序容器的成员变量和成员函数是一样的。
通用容器操作 | 说明 |
---|---|
成员变量名 | |
iterator | 容器的迭代器 |
const_iterator | 可以读取元素,但是不能修改元素的迭代器 |
size_type | 无符号整型,有些容器可以使用下标访问的下标类型,最大不能超过容器大小 |
difference_type | 两个迭代器之间的距离 |
value_type | 元素类型 |
reference | 元素的左值类型,与value_type&相同 |
const_reference | 元素const左值类型 |
构造函数(T表示容器类型) | |
T c | 默认构造 |
T c(c2) | 构造c2拷贝给c |
T c(b,e) | 构造c,将迭代器b和迭代器e范围内的元素拷贝到c (array不支持) |
T c{a,b,c,d…} | 列表初始化c |
赋值与swap | |
c1 = c2 | 将c2的值赋值给c1 |
c1 = {a,b,c,d…} | 将花括号的内容赋值给c1 |
a.swap(b) | a跟b的元素互换 |
swap(a,b) | 同上 |
大小 | |
c.size | 获取c中元素的个数(forward_list 不支持) |
c.max_size | 获取最大可保存元素的数目 |
c.empty() | 如果c中的元素为空,则放回true,反之,false |
添加和删除元素(array不支持) | 备注:一下操作不同容器接口不同 |
c.insert(args) | 将args中的元素拷贝进c |
c.emplace(inits) | 使用inits构造c中的一个元素 |
c.erase(args) | 删除args指定元素 |
c.clear() | 清空容器中的元素,返回void |
关系运算符 | |
==,!= | 所有的容器都支持等于和不等于运算符 |
获取迭代器 | |
c.begin(),c.end() | 返回指向c的首元素以及尾元素之后的迭代器 |
c.cbegin(),c.cend() | 返回常量迭代器 const_iterator |
反向容器的额外成员(不支持 forward_list) | |
reverse_iterator | 按逆序寻址元素的迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
c.rbegin(),c.rend() | 返回c的尾元素和c的首元素之前的位置 |
该表格借鉴于C++primer
迭代器
迭代器类似于一个指针,它指向容器中的元素, 允许我们通过解引用的方式获取容器中的元素的值,也能够通过运算符来移动迭代器或者比较迭代器。
迭代器运算符 | 说明 |
---|---|
*iter | 解引用得到迭代器指向容器的值,返回迭代器的元素的引用 |
iterat->men | 解引用得到该元素的men成员。 |
++iter | 移动迭代器到下一个位置 |
–iter | 移动迭代器到上一个位置(forwar_list 不可用) |
iter1==iter2 | 判断两个迭代器是否相等,如果两个迭代器指向的是同一个容器的同个位置,则相等 |
iter1 !=iter2 | 判断两个迭代器是否不相等 |
以下运算仅支持 | (string,vector,deque,array) |
iter + n | 迭代器向前移动n个位置,返回一个迭代器,这个迭代器指向移动后的元素位置,或者下一个位置 |
iter - n | 迭代器往后移动n个位置,返回一个迭代器 |
iter +=n | 迭代器向前移动n个位置后,,赋值给iter |
iter -=n | 迭代器向后移动n个位置后,,赋值给iter |
iter1-iter2 | 两个迭代器相减,得到两个迭代器之间的距离(difference_type类型),参与运算的必须是相同容器的两个迭代器 |
>,>=,<,<= | 比较的是两个迭代器在容器位置,如果iter1在iter2之前,则说明iter1>iter2 |
迭代器的范围
一个容器中的迭代器通常有begin和end两个迭代器,begin一般指向容器中的第一个元素,但是end通常指向容器中最后一个元素的下一个位置,用数学的区间表达[begin,end)。
在容器初始化的时候可以传入一个迭代器的范围,前提是这两个迭代器是同一个容器的迭代器。范围规则如下:
- 如果begin等于end,则范围为空
- 如果begin != end,那么两个迭代器之间至少有一个元素
- 我们可以对begin迭代器递增n次让他等于end,这个方法常常被用来遍历容器中的元素,并且做为停止遍历的标志
vector<int> l{ 1,2,3,4,5,6,7,8,9,10 }; // 初始化容器
for (auto u = l.begin(); u != l.end(); u++)
{
cout << *u << endl;
}
其他迭代器
容器的类型成员提供了反向迭代器,反向迭代器就是反向的遍历容器,如果使用++运算对方向迭代器进行操作返回的是指向上一个元素的迭代器。使用rbegin/rend,相当于reverse_begin/end.
迭代器还可以细分为常量迭代器和非常量迭代器,常量迭代器只能够访问容器的元素,但是不能修改。一个非常量迭代器可以转化成常量迭代器,反之不可以。
vector<int> l{ 1,2,3,4,5,6,7,8,9,10 }; // 初始化容器
auto iter1 = l.cbegin();
auto iter11 = l.cend(); // 创建两个常量迭代器
for (; iter1 != iter11; iter1++)
{
cout<<*iter1<<endl;
}
auto iter2 = l.rbegin();
auto iter22 = l.rend(); // 创建两个反向迭代器
for(;iter2 != iter22;iter2++)
{
cout << *iter2 << endl;
}
容器的定义和初始化
将一个容器初始化为另外一个容器
一个新的容器可以拷贝旧容器的元素,也可以拷贝一个迭代器范围之间的元素(array除外)。
如果拷贝的是一个旧容器的元素,则要求两个容器的类型相同,如果传入的是一个迭代器的范围,那么不要求两个类型相同,但是传入的迭代器元素类型可以进行隐式转换成新容器的类型。
vector<int> l{ 97,98,99,100,101,102,103,104,105 }; // 初始化容器
vector<char>c(l.begin(), l.end()); // 合法,根据ASCII码转换成char
for (auto a : c)
cout << a << endl;
列表初始化
在新容器创建的时候可以传入一个列表对容器中的元素进行初始化
vector<int> l{ 97,98,99,100,101,102,103,104,105 }; // 初始化容器
顺序容器大小的构造函数(array不适用)
每一个容器都相当于是一个类对象,这个对象都有一个构造函数,并且有些对象的构造函数是有多个重载的版本。
其中的一个版本是我们在创建容器的时候可以传入容器的大小和初始化容器的值。当然我们也可以传入容器的大小但是不传入初始值,这个时候按照系统默认给参。
vector<string> s(10, "hello"); // 创建大小为10的vector容器,并且每个元素初始化为hello
vector<string> s1(10); // 创建大小为10的vector容器,默认参数为" "
array初始化
array具有固定的大小,并且这个大小在初始化的时候就需要说明。还需要说明元素的类型
array<int, 10>;
//创建一个大小为10的整型数组
同理,我们在使用array的成员类型的时候也需要指定大小和类型
array<int, 10>::size_type i;
array的初始化支持默认构造函数,也可以使用列表初始化。
使用列表初始化的时候,列表元素的数目不能大于容器的大小,但是可以小于容器的大小,如果列表中的数目小于容器的大小,那么这个列表被用来初始化前面几个元素,后面的元素进行默认初始化。
需要注意的是:array与我们的内置数组不同的地方
内置数组初始化的时候不能拷贝其他的数组,但是array可以拷贝其他的数组。要求是相同容器,相等大小,相同类型。
array<int, 10> l ={1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
array<int,10> a = l;
//以下是错误示范
int b[10] = a; // 错误,不能拷贝其他数组
array<int, 20> b = l; // 错误,他们的容器大小不相等
赋值和swap
我们需要弄清楚赋值和初始化的区别,很多人都会误以为赋值就是初始化,但是他们两者之间还是存在着差别的
初始化是在对象被创建的时候就赋予初值,有的对象则调用构造函数进行初始化。但是赋值是在对象创建完成后进行元素的操作。
所有的容器都支持拷贝赋值,花括号赋值(array不支持),swap交换操作
容器的赋值操作就是将右边的值全部赋值给左边的值,使得左边元素的数量,数值与右遍元素的数量,数值相等。
vector<int> l{1,2,3,4,5,6,7,8,9,10};
l = { 1,2,3 };
for (auto u : l)
cout << u ;
// 执行结果为1 2 3
除了使用等号,花括号和swap赋值外,还可以使用assign进行赋值,但是array不支持assign
assign操作(不支持array) | 说明 |
---|---|
T.assign(b,e) | b,e是两个迭代器,这个迭代器指向的是同一个容器但是不能是自身T的迭代器,之所以array不支持是因为迭代器的范围可能会大于容器大小,所以不支持 |
T.assign(li) | li是一个列表,将列表的值赋值给T |
T.assign(n,t) | 创建 n 个 t 赋值给 T |
swap操作
swap交换的是两个容器的结构,但是不会移动容器中的元素,也就是所如果一个指针原本是指向 L1[3]中的元素,在使用swap之后指向L2[3]中的元素,指针和引用仍然有效
容器大小操作及关系运算
容器有3个成员类型跟大小有关,size,max_size,empty,但是forward_list不能够使用size来获取容器的大小。
关系运算符
每个容器都支持 == !=关系运算符,除了无序关联的容器外都支持<,>,<=,>=
容器比较的规则
容器的比较必须要同容器,同类型
- 如果两个容器大小相等,并且每个容器都相等,那么这两个容器相等
- 如果一个容器大小小于另外一个容器,但是这个容器的每一个元素都跟另外一个容器的元素相等,那么这个容器小于另外一个容器
- 如果一个容器的大小不相等,那么出现元素第一个不相等的,元素大的容器大。
例如
vector<int> l{1,2,3,4,5,6,7,8,9,10};
vector<int> p{ 1,2,4};
if (p > l)
{
cout << "p容器更大" << endl;
}
else
{
cout << "l容器更大" << endl;
}
//输出结果:p容器更大