C++ 标准库容器
C++标准库容器分为:顺序容器与关联容器。其中关联容器又分为有序容器和无序容器,如下图所示:
接下来对每一种容器进行总结:
一、顺序容器
顺序容器包括:vector(可变大小数组)、deque(双端队列)、list(双向链表)、forward_list(单向链表)、array(固定大小数组)、string(字符串)。
1.1、vector
①特点:可变大小数组,支持数据访问,元素保存在连续的内存空间中,在尾部之外的位置插入或者删除元素可能会比较慢。
②vector如何实现空间增长的:当vector的初次分配的空间已经饱和时,当插入新的元素时,vector和string会重新分配比新的空间需求更大的空间,然后将原来空间中的元素依次移动到新的空间当中,容器之所以在新分配的空间中要预留一些多于的空间,就是为了下次插入元素时,不用在重新分配空间移动所有元素了,如下所示:
如上图所示:当插入5时,此时容器中还有一个空余位置,不会重新分配空间;当第二次插入6时,此时容器中以及没有空余的空间,此时容器会出现分配空间,并且分配的新空间为10(不一定为10,但是一定大于6),容器会将原来空间中的1,2,3,4,5元素移动到新空间中,虽然移动每一个元素到新空间 中会比较耗时,但是没移动一次之后,由于新空间预留的多于的空余空间,因往后插入新元素时不会移动以前的元素,直到空间又饱和为止。
③vector初始化
vector的初始化方法如下:
vector<int> v1; //空vector
vector<int> v2{ 1,2,3,4,5 }; //列表初始化
vector<int> v3 = { 1,2,3,4,5 }; //列表初始化
vector<int> v4(v3); //将v3拷贝给v4
vector<int> v5(v2.begin(), v2.end()); //拷贝迭代器指定范围中的元素
vector<int> v6(5); //初始化5个元素,每一个元素默认为0
vector<int> v7(5, 1); // 初始化5个元素,且每一个元素为1
④assign和swap:
vector<int> v1 = { 1,2,3,4,5 };
vector<int> v4;
v4.assign(v1.begin(), v1.end()); //将v4中的元素替换为指定迭代器之前的元素
v4.assign({ 6,7,8,9,10 });//将v4中元素替换为初始化类别中的元素
v4.assign(5, 10);//将v4中的元素替换为5个10
swap(v1, v4);//交换v1和v4容器
swap函数只是交换两个容器的内部数据结构,不会对元素进行交换,因此可以在常数时间内完成。
⑤向vector中添加元素:向vector中添加元素常用push_back,虽然也可以使用insert,但是在vector中间位置插入元素会比较缓慢,另外vector不支持push_front。
vector<int> v1;
v1.push_back(1); //插入元素1
另外还可以使用emplace函数,push_back是将插入的参数拷贝到容器中,而emplace_back是将传入的参数先传递给元素类型的构造函数直接在容器管理的内存中构造一个对象。
⑥其他成员函数:
vector<int> v1 = { 1,2,3,4,5 };
cout << v1.size() << endl; //求容器中元素个数
cout << v1.capacity() << endl; //求容器的容量,包括空余的空间
cout << v1[1] << endl; //通过下标运算符访问元素,下标越界时不会抛出异常
cout << v1.at(1) << endl; //访问容器元素,下标越界时,会抛出异常
v1.pop_back(); //删除尾元素,vector不支持pop_front()函数
v1.erase(p); //删除迭代器p所指向的元素
v1.erase(p,e); //删除迭代器p与e之间的元素
v1.clear(); //删除所有元素
1.2 deque双端队列
①特点:元素连续存储,支持快速访问,在deque两端插入或者删除元素较快,但是在中间插入或者删除元素较慢。
②deque内部结构由分段连续空间组成,当deque没有多余的内存空间时,会在末尾或者首部添加新的连续空间,因此不同于vector,deque分配空间时不会移动原来的元素。由于deque可以在首部或者尾部添加新的连续空间,因此deque可以很方便的在首部或者尾部插入或者删除元素,同时deque也没有容量这一概念,因此也没有capacity这一函数。与vector一样,如果在中间进行insert操作会比较慢,deque的内部实现示意图如下所示:
③deque容器的初始化操作与vector类型,不同于vector,deque还支持push_front和pop_front操作:
deque<int> d = { 1,2,3,4,5 };
cout << d.size() << endl; //求容器中元素个数
d.push_back(6); //在尾部插入6
d.push_front(0); // 在首部插入0
cout << d[0] << endl; //访问元素
d.pop_back();// 删除末尾元素
d.pop_front(); //删除首部元素
cout <<d.at(0)<< endl; //访问元素
d.erase(d.begin());//删除迭代器指定的元素
d.erase(p, e); //删除迭代器p与e之间的元素
d.clear();//清空容器
1.3 string
①特点:与vector相似的容器,但是专门用于保存字符,可以随机访问。
②string的操作:
string s(5, 'a');
cout << s.size() << endl; //求字符串大小
string s1 = s.substr(0, 3); //拷贝s中从0开始的3个字符到s1中
string s2 = "bbb";
s.append(s2); //将s2追加到s末尾
s.replace(1, 3, "ccc"); //将s中下标为1开始的往后的3个字符替换成"ccc"
s.erase(0, 2);//删除从0开始2个字符
1.4 list
①特点:list为双向链表,不支持随机访问,只能遍历链表且可以双向遍历,在list中任何位置插入都很快速。当我们频繁的插入或者删除数据时,可以考虑使用list。
②list内部原理如下:
③list的操作:
list<int> l = { 1,2,3,4,5 };
cout << l.size() << endl; //求容器中元素个数
cout << l.back() << endl; //返回尾部元素
cout << l.front() << endl; //返回首部元素
l.push_back(6); //在尾部插入元素
l.push_front(0); //在首部插入元素
l.insert(p, 12); //在迭代器p之前插入元素12
l.pop_back(); //删除尾部元素
l.pop_front(); //删除首部元素
l.erase(p);//删除迭代器所指定的元素
list<int>::iterator it;
for (it = l.begin(); it != l.end(); it++) //从首到尾遍历打印list中的元素
cout << *it << endl;
list<int>::reverse_iterator rit; //反向迭代器
for (rit = l.rbegin(); rit != l.rend(); rit++) //从尾到头遍历并打印元素
cout << *rit << endl;
1.5 forward_list
①特点:单向链表与list类似,不能支持随机访问,只能遍历而且只能单向遍历,在单向链表的任何位置插入或者删除元素都比较快。forward_list采用头插法,因此我们只能使用push_front和pop_front函数,而没有push_back和pop_back函数,另外forward_list也没有size()函数。但是使用insert_after函数也可以在尾部插入数据,注意:前面所讲到的所有insert()函数是在指定的迭代器之前插入元素,而insert_after()函数是在指定的迭代器之后插入元素。
②forward_list内部实现原理如下:
③forward_list的操作:
forward_list<int> fl = { 1,2,3,4,5 };
fl.push_front(6); //在单向链表头部插入元素
fl.pop_front(); //删除单向链表头部的元素
fl.insert_after(p, 10); //在指定的迭代器p后面插入元素10,
fl.erase_after(p); //删除指定迭代器p之后的元素
forward_list<int>::iterator it;
for (it = fl.begin(); it != fl.end(); it++) //遍历单向链表并打印数据,只能从头到尾单向遍历
cout << *it << endl;
1.6 array
①特点:固定大小数组,支持快速随机访问,但是不能添加和删除元素,因此也就没有push_front、push_back、pop_front/pop_back等函数,很少使用。
定义一个array时,处理声明元素类型外,还有些确定元素个数,如:
array<int, 5> a = { 1,2,3,4,5};
二、关联容器
1、关联容器可以根据关键字高效地查找和提取元素。关联容器分为有序和无序,无序的概率容器前面有unordered修饰:如unordered_set、unordered_multiset、unordered_map、unordered_multimap为无序关联容器,不加unordered的就是有序关联容器,
2、有序关联容器的底层实现原理为红黑树,因此有序关联容器中 的关键字必须定义了比较的方法,而无序关联容器的底层实现原理为哈希函数。另外,没有multi的表示容器中不能包含重复关键字,而有multi表示容器中可以包含重复关键字
3、关联容器中的关键字是不能修改的。
4、对关联容器只能使用insert插入数据,而没有push函数。
2.1 set与multiset
①set与multiset中某个元素只包含一个关键字,set支持高效的检查给定的关键字是否在set中。set中关键字是唯一的,而multiset中可以含有重复关键字,由于set只含有关键字,因此通过set迭代器只能访问set中的元素,而不能修改set中的元素。
②set的操作:
set<int> s{ 3,1,5,7,5,8,4 }; //初始化一个set集合,与前面的顺序容器类似
s.insert(10); //插入一个新元素
s.erase(3); //删除值3
s.count(4); //计算集合中值为4的个数,对于set来说,结果要么是0要么是1
cout << s.size() << endl; //求集合中元素个数,只有7个而不是8个
set<int>::iterator p = s.find(7); //find函数查询集合中是否含有元素7,有就返回该迭代器,没有就返回尾置迭代器
if (p != s.end())
cout << *p << endl;
set<int>::iterator it;
for (it = s.begin(); it != s.end(); it++) //遍历集合,输出排序后的元素:1,4,5,7,8,10
cout << *it << endl;
2.2 map与multimap
①map和multimap中保存的元素为键-值对(key-value)也就是pair类型的对象,关键字相对于索引,我们可以通过关键字快速查找到相应的值。map中关键字必须唯一,而multimap中可以含有重复的关键字。
②map与multimap的操作:
map<int, string> m = { {3,"A"},{2,"B"},{1,"C"}}; //初始化一个map,方法与顺序容器类似
m.insert({ 4,"E" }); //几种插入操作
m.insert(make_pair(5, "F")); //由于map保存的是pair类对象
m.insert(pair<int, string>(6, "G"));
m.erase(3); //删除关键字为3的键值对
m[1] = "c"; //为关键字为1的值重新赋值
map<int, string>::iterator p;
for (p = m.begin(); p != m.end(); p++) //遍历map并分别打印关键字和值
cout << p->first << " " << p->second << endl;
1、如上述代码所示:map迭代器返回的是pair类型,也就是<int,string>,如果要打印关键字和值,我们需要调用p->first得到关键字,调用p->second得到值,而使用下标运算符得到的就是键值对中的值,可以直接打印。
2、另外通过下标运算符访问map或者multimap容器,如果关键字没有在容器汇总,那么下标运算符就会添加一个新的元素,比如在上述代码中,没有关键字7,如果我们使用m[7],就会在map中生成一个新的元素。为了避免添加多于的元素,我们可以使用find函数,如:
map<int,string>::iterator p=m.find(7); //s使用find函数,如果map中不含关键字7,就可以避免添加一个新的元素
if(p!=m.end())
cout<<p->first<<""<<p->second<<endl;
3、对于multimap来说,由于关键字可以重复,因此multimap没有下标运算符,对于multimap就可以使用count和find函数,来访问给定关键字的值,因为相同关键字的元素是连续储存的,因此只要知道相同的个数以及第一个的位置,就可以得到所有值,如:
multimap<int, string> m = { {3,"A"},{2,"B"},{1,"C"}};
m.insert({ 2,"E" });
m.insert(make_pair(5, "F"));
m.insert(pair<int, string>(2, "G"));
int count = m.count(2); //计算关键字为2的值的个数
map<int, string>::iterator p=m.find(2); //find函数返回第一个关键字为2 的迭代器
while (count) //根据相同关键字的个数打印相应的值
{
cout << p->first << " " << p->second << endl;
count--;
p++; //迭代器递增
}
对于无序的关联容器来说,与有效关联容器的位于区别就是底层实现原理为哈希函数,其他操作与有效关联容器一致,就不在单独记录。另外无序关联容器定义在头文件<unordered_set>和<unordered_map>中。